%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /snap/firefox/5014/usr/lib/firefox/
Upload File :
Create Path :
Current File : //snap/firefox/5014/usr/lib/firefox/omni.ja

!=1PK
!<�(���e�e���greprefs.jsPK
!<K��b��"��8defaults/pref/PdfJsDefaultPrefs.jsPK
!<�Y.HH��1Echrome.manifestPK
!<# U�!	!	���Echrome/chrome.manifestPK
!<W������Nres/multilocale.txtPK
!<}4f����2Ocomponents/components.manifestPK
!<�����0��FXchrome/toolkit/content/global/process-content.jsPK
!<!n�__'��[[modules/extensionProcessScriptLoader.jsPK
!<��
���7���\chrome/toolkit/content/extensions/parent/ext-toolkit.jsPK
!<�o��9��;lchrome/toolkit/content/extensions/parent/ext-tabs-base.jsPK
!<O��%��0��n�chrome/en-US/locale/en-US/global/intl.propertiesPK
!<\܅�����A��f�chrome/toolkit/content/global/third_party/cfworker/json-schema.jsPK
!<a�@@I���Kchrome/en-US/locale/en-US/mozapps/downloads/unknownContentType.propertiesPK
!<�#Ŕe8e8%��MOchrome/toolkit/content/global/xul.cssPK
!<C�Cw"w"����res/contenteditable.cssPK
!<�_�O1O1$����chrome/toolkit/res/counterstyles.cssPK
!<�`�FF��2�res/designmode.cssPK
!<jfu	^^����chrome/toolkit/res/forms.cssPK
!<��))~N~N���;chrome/toolkit/res/html.cssPK
!<t(��.(.(����chrome/toolkit/res/mathml.cssPK
!<��%�uu��	�chrome/toolkit/res/noframes.cssPK
!<Jy�22����chrome/toolkit/res/quirk.cssPK
!<�n[l~~!��'�chrome/toolkit/res/scrollbars.cssPK
!<�9z3������res/svg.cssPK
!<�H�'-'-����chrome/toolkit/res/ua.cssPK
!<�3�'ZCZC5��1
chrome/toolkit/content/extensions/parent/ext-theme.jsPK
!<w�Z����>���Pchrome/toolkit/content/extensions/parent/ext-backgroundPage.jsPK
!<�Noi-��/�chrome/toolkit/skin/classic/global/global.cssPK
!<�V���	�	4���	chrome/toolkit/skin/classic/global/toolbarbutton.cssPK
!<r����5��U	chrome/toolkit/skin/classic/global/arrowscrollbox.cssPK
!<�6���:���	chrome/toolkit/content/extensions/parent/ext-webRequest.jsPK
!<hп&,�,���s-	modules/WebRequest.sys.mjsPK
!<N�Ȧ)�)4����	chrome/toolkit/skin/classic/global/global-shared.cssPK
!<���-���	modules/services-settings/SharedUtils.sys.mjsPK
!<k!W

D��3
chrome/toolkit/skin/classic/global/design-system/tokens-platform.cssPK
!<�+���H���
chrome/toolkit/skin/classic/global/design-system/text-and-typography.cssPK
!<VV֝��/���
chrome/en-US/locale/en-US/global/css.propertiesPK
!<�%�)kk)��!
chrome/en-US/locale/en-US/global/intl.cssPK
!<b@�uu/���"
chrome/en-US/locale/en-US/global/xul.propertiesPK
!<��&)���#
chrome/toolkit/content/global/widgets.cssPK
!<���dd1���(
chrome/toolkit/skin/classic/global/close-icon.cssPK
!<�ߒ�K
K
9���-
chrome/en-US/locale/en-US/global/layout_errors.propertiesPK
!<�|n
44B��;;
chrome/toolkit/skin/classic/global/design-system/tokens-shared.cssPK
!<*���+���o
chrome/toolkit/skin/classic/global/menu.cssPK
!<���**;���w
chrome/en-US/locale/en-US/global/layout/HtmlForm.propertiesPK
!<.|�\��,��`|
chrome/toolkit/skin/classic/global/popup.cssPK
!<s\��4����
chrome/en-US/locale/en-US/global/printing.propertiesPK
!<����zz/����
chrome/toolkit/skin/classic/global/splitter.cssPK
!<U��j��/��|�
chrome/toolkit/skin/classic/global/checkbox.cssPK
!<�;K��.��©
chrome/toolkit/skin/classic/global/toolbar.cssPK
!<.����0��̮
chrome/toolkit/skin/classic/global/tree/tree.cssPK
!<j~V���3����
chrome/toolkit/skin/classic/global/notification.cssPK
!<G<-6�6�3���
chrome/en-US/locale/en-US/global/dom/dom.propertiesPK
!<����QQ2���fchrome/toolkit/skin/classic/global/numberinput.cssPK
!<4��))3��Gichrome/toolkit/skin/classic/global/autocomplete.cssPK
!<EW<<8���pchrome/toolkit/skin/classic/global/popupnotification.cssPK
!<Px�m��,��S}chrome/toolkit/skin/classic/global/radio.cssPK
!<J���__2��#�chrome/toolkit/skin/classic/global/richlistbox.cssPK
!<��7UU-��ҋchrome/toolkit/skin/classic/global/button.cssPK
!<P�DPP-��r�chrome/toolkit/skin/classic/global/tabbox.cssPK
!<%���.��
�chrome/toolkit/skin/classic/global/findbar.cssPK
!<P�吷�.���chrome/toolkit/content/global/autocomplete.cssPK
!<E6��pp2���chrome/toolkit/skin/classic/global/menu-shared.cssPK
!<�>W��<����chrome/toolkit/content/global/antitracking/StripOnShare.jsonPK
!<z>t��3�3=����chrome/en-US/locale/en-US/global/layout/htmlparser.propertiesPK
!<�܌�/�/@����chrome/toolkit/content/global/antitracking/StripOnShareLGPL.jsonPK
!<��t���1���&localization/en-US/toolkit/global/textActions.ftlPK
!<"�R���4��-localization/en-US/toolkit/global/arrowscrollbox.ftlPK
!<#��1���-localization/en-US/toolkit/branding/brandings.ftlPK
!<�?�ڒ�9��D1localization/en-US/toolkit/global/contextual-identity.ftlPK
!<l傝�
�
/��-3localization/en-US/toolkit/printing/printUI.ftlPK
!<T�6���7��;Achrome/toolkit/skin/classic/global/icons/arrow-left.svgPK
!<3�3�\\5��zCchrome/toolkit/skin/classic/global/icons/close-12.svgPK
!<�B�1��)Fchrome/toolkit/skin/classic/global/icons/plus.svgPK
!<���A��3���Hchrome/toolkit/skin/classic/global/icons/reload.svgPK
!<X��|��9���Kchrome/toolkit/skin/classic/global/icons/search-glass.svgPK
!<�r�qq4��sNchrome/toolkit/skin/classic/global/icons/chevron.svgPK
!<�s2'@@%��6Qcontentaccessible/accessiblecaret.cssPK
!<R��h��/���\chrome/toolkit/skin/classic/global/menulist.cssPK
!<��N��6���_chrome/toolkit/skin/classic/global/menulist-shared.cssPK
!<�R�!$$4���flocalization/en-US/toolkit/global/mozSupportLink.ftlPK
!<cfy�llF��gchrome/en-US/locale/en-US/global-platform/unix/platformKeys.propertiesPK
!<Z����A�A0���glocalization/en-US/toolkit/about/aboutAddons.ftlPK
!<��w��;���chrome/toolkit/content/global/elements/moz-button-group.cssPK
!<�\҃

;��<�chrome/toolkit/skin/classic/global/icons/pocket-outline.svgPK
!<�%�ث�<����chrome/toolkit/skin/classic/mozapps/extensions/extension.svgPK
!<�#NGG$����chrome/toolkit/res/hiddenWindow.htmlPK
!<��TT1��-�modules/UrlClassifierExceptionListService.sys.mjsPK
!<�(�x��0����chrome/en-US/locale/en-US/global/keys.propertiesPK
!<W���4����chrome/toolkit/featuregates/feature_definitions.jsonPK
!<#�g��:����chrome/toolkit/content/global/elements/moz-message-bar.mjsPK
!<���8YY5����chrome/toolkit/content/global/elements/moz-button.mjsPK
!<�0�$�$4��d
chrome/toolkit/content/global/elements/moz-label.mjsPK
!<���.�.4��p*
chrome/toolkit/content/global/elements/named-deck.jsPK
!<����2���Y
chrome/toolkit/content/global/elements/infobar.cssPK
!<!4�'-���a
chrome/toolkit/content/extensions/dummy.xhtmlPK
!<��<BB:��c
chrome/toolkit/content/global/elements/moz-message-bar.cssPK
!</�v�??8���s
chrome/toolkit/skin/classic/global/icons/info-filled.svgPK
!<3u���5��Hv
chrome/toolkit/content/global/elements/moz-button.cssPK
!<�~�''4��-�
chrome/toolkit/content/global/elements/moz-label.cssPK
!<���ss2����
chrome/toolkit/skin/classic/global/icons/close.svgPK
!<��.���2��i�
localization/en-US/toolkit/global/notification.ftlPK
!<�i�93���
localization/en-US/toolkit/global/mozMessageBar.ftlPK
!<+��3��ѐ
modules/services-settings/RemoteSettings.worker.mjsPK
!<�����<��թ
chrome/toolkit/skin/classic/global/icons/security-broken.svgPK
!<�ܖ�1��ƭ
chrome/toolkit/skin/classic/global/icons/more.svgPK
!<�LҠ�>�>'��2�
modules/AppProvidedSearchEngine.sys.mjsPK
!<��͔������<�
modules/SearchEngine.sys.mjsPK
!<���88 ��a�modules/SearchStaticData.sys.mjsPK
!<�v�66 ����modules/OpenSearchEngine.sys.mjsPK
!<�՝ѫ���K�modules/CanonicalJSON.sys.mjsPK
!<L�iL��,��1�modules/services-settings/IDBHelpers.sys.mjsPK
!<�$D��!�!#���modules/third_party/jsesc/jsesc.mjsPK
!<_w�s1s1!��4modules/TrackingDBService.sys.mjsPK
!<"��b88:���echrome/toolkit/skin/classic/global/icons/page-portrait.svgPK
!<�p��1��Ghchrome/toolkit/skin/classic/global/icons/info.svgPK
!<�;.�L
L
0��Skchrome/toolkit/content/global/viewZoomOverlay.jsPK
!<��$EE3���xchrome/en-US/locale/en-US/global/svg/svg.propertiesPK
!<L�{��9���ychrome/en-US/locale/en-US/global/commonDialogs.propertiesPK
!<nST���9��lchrome/en-US/locale/en-US/global/mathml/mathml.propertiesPK
!<�Ԏ�--=����chrome/en-US/locale/en-US/global/security/security.propertiesPK
!<���Tgg0���chrome/en-US/locale/en-US/necko/necko.propertiesPK
!<���**%����res/locale/layout/HtmlForm.propertiesPK
!<G<-6�6���A�res/locale/dom/dom.propertiesPK
!<��P$�;�;���Wmodules/SafeBrowsing.sys.mjsPK
!<wQ
�d�d(��Փmodules/UrlClassifierListManager.sys.mjsPK
!<a=|�� ���modules/UrlClassifierLib.sys.mjsPK
!<j��LL)���modules/ContextualIdentityService.sys.mjsPK
!<E�"<"<��Aamodules/PushComponents.sys.mjsPK
!<���Y�Y�����modules/PushService.sys.mjsPK
!<#�R��$��1Wmodules/PushServiceWebSocket.sys.mjsPK
!<P�33����modules/PushDB.sys.mjsPK
!<i��Ԡ$�$���modules/PushRecord.sys.mjsPK
!<`�H@hh���Bmodules/PushCrypto.sys.mjsPK
!<ѭ�#�����modules/IndexedDBHelper.sys.mjsPK
!<��b��Z�Z ����modules/PushServiceHttp2.sys.mjsPK
!</Y&U�D�D��� modules/LoginManager.sys.mjsPK
!<�C��2���echrome/toolkit/passwordmgr/passwordstorage.sys.mjsPK
!<(�o\�����hmodules/storage-json.sys.mjsPK
!<�F�1��2����chrome/en-US/locale/en-US/pipnss/pipnss.propertiesPK
!<��euCC���modules/LoginStore.sys.mjsPK
!<`�_NN0��{modules/services-common/uptake-telemetry.sys.mjsPK
!<s��6�6!��,modules/AddonSearchEngine.sys.mjsPK
!<�q�8I8I��cmodules/RFPHelper.sys.mjsPK
!<�%����"����modules/RFPTargetConstants.sys.mjsPK
!<J{l��0�07����chrome/toolkit/content/extensions/parent/ext-runtime.jsPK
!<ʱ3.�\�\E����chrome/toolkit/res/messaging-system/lib/SpecialMessageActions.sys.mjsPK
!<��y/y/9��
Nchrome/toolkit/content/extensions/parent/ext-scripting.jsPK
!<Ph�[[#���}modules/services-sync/Weave.sys.mjsPK
!<��7xKxK��y�modules/RustSuggest.sys.mjsPK
!<�8������*�modules/UniFFI.sys.mjsPK
!<ې�H&�&�"��K�modules/RustRemoteSettings.sys.mjsPK
!<i{���3�3'����modules/ContentRelevancyManager.sys.mjsPK
!<�������modules/RustRelevancy.sys.mjsPK
!<Ҏ�OIgIg$��!Ymodules/FxAccountsWebChannel.sys.mjsPK
!<͉���"�",����modules/services-sync/SyncDisconnect.sys.mjsPK
!<�{` 5�5�����modules/FxAccounts.sys.mjsPK
!<�w/�D@D@%���modules/services-crypto/utils.sys.mjsPK
!<�[g]XX!���modules/FxAccountsStorage.sys.mjsPK
!<O6�	KFKF%���_modules/services-common/utils.sys.mjsPK
!<\^�3�_�_��`�modules/FxAccountsKeys.sys.mjsPK
!<*�����)��Rmodules/sessionstore/PrivacyLevel.sys.mjsPK
!<�I�%�)�)��y
modules/Downloads.sys.mjsPK
!<�΋������Q7modules/DownloadCore.sys.mjsPK
!<�/�D�D��8�modules/DownloadList.sys.mjsPK
!<^�:<��#��cmodules/DownloadIntegration.sys.mjsPK
!<����������modules/DownloadStore.sys.mjsPK
!<FdA[��#����actors/BrowserElementParent.sys.mjsPK
!<{�|�T�T+����modules/sessionstore/SessionHistory.sys.mjsPK
!<����	�	���Bmodules/BinarySearch.sys.mjsPK
!<���y�1�16���Lchrome/toolkit/content/global/ml/EngineProcess.sys.mjsPK
!<.��)I
I
*���~modules/sessionstore/PrivacyFilter.sys.mjsPK
!<
�V$$ ��T�actors/ContentMetaParent.sys.mjsPK
!<e'C�������modules/History.sys.mjsPK
!<:���y�y!���q modules/GMPInstallManager.sys.mjsPK
!<����LPLP*���� modules/addons/ProductAddonChecker.sys.mjsPK
!<��a�@@���<!modules/CertUtils.sys.mjsPK
!<��"T"T"$���W!modules/PushBroadcastService.sys.mjsPK
!<�q�'',���z!modules/netwerk-dns/PublicSuffixList.sys.mjsPK
!<Y�O�Y�Y*���!modules/psm/RemoteSecuritySettings.sys.mjsPK
!<�,å�G�G��9�!modules/psm/X509.sys.mjsPK
!<�����'�'��+"modules/psm/DER.sys.mjsPK
!<������/S"modules/OsEnvironment.sys.mjsPK
!<x����(��,_"modules/PageThumbsStorageService.sys.mjsPK
!<q�W7GG��f"modules/PageThumbs.worker.jsPK
!<W�)^BB:���v"modules/services-settings/RemoteSettingsComponents.sys.mjsPK
!<��8-��&z"modules/services-settings/SyncHistory.sys.mjsPK
!<)0�������"modules/workers/require.jsPK
!<^�20e!e! ���"modules/workers/PromiseWorker.jsPK
!<����U�U��"�"modules/mozIntl.sys.mjsPK
!<T?���E�EH��#chrome/toolkit/content/global/translations/TranslationsTelemetry.sys.mjsPK
!<�JUCC5��l]#chrome/toolkit/skin/classic/global/icons/settings.svgPK
!<��Z��:��f#chrome/toolkit/skin/classic/global/icons/arrow-down-12.svgPK
!<_p�0,,2��Lh#chrome/toolkit/skin/classic/global/icons/error.svgPK
!<7�l�		(���j#modules/ContentPrefServiceParent.sys.mjsPK
!<�����}#dictionaries/en-US.affPK
!<eò�	�	��M�#dictionaries/en-US.dicPK
!<�Q�gg7���,chrome/toolkit/res/autofill/FormAutofillStorage.sys.mjsPK
!<��9��;��ٗ,chrome/toolkit/res/autofill/FormAutofillStorageBase.sys.mjsPK
!<���B��$��֚-modules/shared/AddressRecord.sys.mjsPK
!</u��1*1*,����-modules/shared/FormAutofillNameUtils.sys.mjsPK
!<֪3a�>�>"���-modules/shared/PhoneNumber.sys.mjsPK
!<H���*��/.modules/shared/PhoneNumberMetaData.sys.mjsPK
!<��=w�G�G*��S/modules/shared/FormAutofillSection.sys.mjsPK
!<�SV�N:N:(��9d/modules/shared/AutofillTelemetry.sys.mjsPK
!<޺��II ��͞/actors/FormHandlerParent.sys.mjsPK
!<��#�	�	$��T�/modules/ServiceWorkerCleanUp.sys.mjsPK
!<0�s�	�	 ��g�/modules/TelemetryStorage.sys.mjsPK
!<H��6�6"����0modules/TelemetryScheduler.sys.mjsPK
!<�YY����0modules/EventPing.sys.mjsPK
!<��y
y
 ��I1modules/TelemetryArchive.sys.mjsPK
!<�`�����1modules/HealthPing.sys.mjsPK
!<
2B��1��!=1localization/en-US/crashreporter/aboutcrashes.ftlPK
!<(�Z�	�	2���@1localization/en-US/crashreporter/crashreporter.ftlPK
!<&�'��)��EK1localization/en-US/dom/XMLPrettyPrint.ftlPK
!<�B��� ��L1localization/en-US/dom/media.ftlPK
!<��vv8���L1localization/en-US/locales-preview/aboutTranslations.ftlPK
!<h����8���O1localization/en-US/security/certificates/certManager.ftlPK
!<�(
(
:���k1localization/en-US/security/certificates/deviceManager.ftlPK
!</����
�
-��v1localization/en-US/security/pippki/pippki.ftlPK
!<~7>�JJ(��N�1localization/en-US/services/accounts.ftlPK
!<*$�`/��ބ1localization/en-US/toolkit/about/aboutAbout.ftlPK
!<�����0��0�1localization/en-US/toolkit/about/aboutCompat.ftlPK
!<�[�##/��b�1localization/en-US/toolkit/about/aboutGlean.ftlPK
!<?wr���8��ҙ1localization/en-US/toolkit/about/aboutHttpsOnlyError.ftlPK
!<��X�
�
1����1localization/en-US/toolkit/about/aboutLogging.ftlPK
!<����1��'�1localization/en-US/toolkit/about/aboutMozilla.ftlPK
!<���

4��U�1localization/en-US/toolkit/about/aboutNetworking.ftlPK
!<���3��ƹ1localization/en-US/toolkit/about/aboutProcesses.ftlPK
!<T���	�	2��3�1localization/en-US/toolkit/about/aboutProfiles.ftlPK
!<U��V��0��X�1localization/en-US/toolkit/about/aboutReader.ftlPK
!<��Ĕ��0��M�1localization/en-US/toolkit/about/aboutRights.ftlPK
!<̚W���8��y�1localization/en-US/toolkit/about/aboutServiceWorkers.ftlPK
!<�A�z<z<1���2localization/en-US/toolkit/about/aboutSupport.ftlPK
!<�ϼ��3��K@2localization/en-US/toolkit/about/aboutTelemetry.ftlPK
!<3�����4��HV2localization/en-US/toolkit/about/aboutThirdParty.ftlPK
!<r�W��2���c2localization/en-US/toolkit/about/aboutWebauthn.ftlPK
!<��1�0����2localization/en-US/toolkit/about/aboutWebrtc.ftlPK
!<���;��9��ۜ2localization/en-US/toolkit/about/aboutWindowsMessages.ftlPK
!<�@���/���2localization/en-US/toolkit/about/certviewer.ftlPK
!<
��|+����2localization/en-US/toolkit/about/config.ftlPK
!<�C	C	3��S�2localization/en-US/toolkit/about/url-classifier.ftlPK
!<xq����>���2localization/en-US/toolkit/contentanalysis/contentanalysis.ftlPK
!<��^3����2localization/en-US/toolkit/downloads/downloadUI.ftlPK
!<È2?HH6��B�2localization/en-US/toolkit/downloads/downloadUtils.ftlPK
!<|�X��4����2localization/en-US/toolkit/featuregates/features.ftlPK
!<�
��	�	8����2localization/en-US/toolkit/formautofill/formAutofill.ftlPK
!<�I䥖�+���2localization/en-US/toolkit/global/alert.ftlPK
!<��,		2����2localization/en-US/toolkit/global/antiTracking.ftlPK
!<C�N��/��J�2localization/en-US/toolkit/global/appPicker.ftlPK
!<�
�[[3��b�2localization/en-US/toolkit/global/browser-utils.ftlPK
!<�,Ǐ�2���2localization/en-US/toolkit/global/commonDialog.ftlPK
!<=.�kk:���2localization/en-US/toolkit/global/cookieBannerHandling.ftlPK
!<���39����2localization/en-US/toolkit/global/createProfileWizard.ftlPK
!<e��U��/��3localization/en-US/toolkit/global/cspErrors.ftlPK
!<S$L�II0��3localization/en-US/toolkit/global/datepicker.ftlPK
!<7��1���3localization/en-US/toolkit/global/datetimebox.ftlPK
!<�Y���:���	3localization/en-US/toolkit/global/extensionPermissions.ftlPK
!<��a�  0��O3localization/en-US/toolkit/global/extensions.ftlPK
!<��w��	�	3���3localization/en-US/toolkit/global/handlerDialog.ftlPK
!<�q�~zz.���)3localization/en-US/toolkit/global/htmlForm.ftlPK
!<�u �``1��q*3localization/en-US/toolkit/global/mozFiveStar.ftlPK
!<�{ڼ��7�� +3localization/en-US/toolkit/global/popupnotification.ftlPK
!<�V����2��1,3localization/en-US/toolkit/global/processTypes.ftlPK
!<�8���6��13localization/en-US/toolkit/global/profileDowngrade.ftlPK
!<!J����6��753localization/en-US/toolkit/global/profileSelection.ftlPK
!<1��~~2��H93localization/en-US/toolkit/global/resetProfile.ftlPK
!<y�@b��:��<3localization/en-US/toolkit/global/resistFingerPrinting.ftlPK
!<���pp2��3=3localization/en-US/toolkit/global/run-from-dmg.ftlPK
!<vp�EE*���@3localization/en-US/toolkit/global/tree.ftlPK
!<`"e��8���A3localization/en-US/toolkit/global/unknownContentType.ftlPK
!<�G���3��}F3localization/en-US/toolkit/global/videocontrols.ftlPK
!<�̕��,��wM3localization/en-US/toolkit/global/wizard.ftlPK
!<�M�z��1��HP3localization/en-US/toolkit/intl/languageNames.ftlPK
!<�׺77/���g3localization/en-US/toolkit/intl/regionNames.ftlPK
!<bk�eOO7���3localization/en-US/toolkit/main-window/autocomplete.ftlPK
!<�Po��2����3localization/en-US/toolkit/main-window/findbar.ftlPK
!<�/nn1���3localization/en-US/toolkit/neterror/certError.ftlPK
!<��<�*�*0����3localization/en-US/toolkit/neterror/netError.ftlPK
!<	&�vv1��u�3localization/en-US/toolkit/neterror/nsserrors.ftlPK
!<p�F�6���K4localization/en-US/toolkit/passwordmgr/passwordmgr.ftlPK
!<���``0��2P4localization/en-US/toolkit/payments/payments.ftlPK
!<3�7�8�8/���R4localization/en-US/toolkit/pdfviewer/viewer.ftlPK
!<�De��@��֋4localization/en-US/toolkit/pictureinpicture/pictureinpicture.ftlPK
!<�KDK��6����4localization/en-US/toolkit/preferences/preferences.ftlPK
!<ː?���4��ݚ4localization/en-US/toolkit/printing/printDialogs.ftlPK
!<(�p���4���4localization/en-US/toolkit/printing/printPreview.ftlPK
!<�B�S��7���4localization/en-US/toolkit/updates/backgroundupdate.ftlPK
!<+�L0��Ь4localization/en-US/toolkit/updates/elevation.ftlPK
!<�T,���.��7�4localization/en-US/toolkit/updates/history.ftlPK
!<8U8�@�@���Y�4hyphenation/hyph_af.hyfPK
!<Nki�(k(k��Δ8hyphenation/hyph_bg.hyfPK
!<��o�((��+9hyphenation/hyph_bn.hyfPK
!<���((���9hyphenation/hyph_ca.hyfPK
!<�:�;H�H����9hyphenation/hyph_cs.hyfPK
!<�q%p�p���b;hyphenation/hyph_cy.hyfPK
!<Z'=HIHI���<hyphenation/hyph_da.hyfPK
!<����|�|����=hyphenation/hyph_de-1901.hyfPK
!<.;�\�\���:�@hyphenation/hyph_de-1996.hyfPK
!<��#�����KDhyphenation/hyph_de-CH.hyfPK
!<{)L�L�����Ghyphenation/hyph_en_US.hyfPK
!<w+�������lyJhyphenation/hyph_eo.hyfPK
!<�?�������Khyphenation/hyph_es.hyfPK
!<��Oc������Khyphenation/hyph_et.hyfPK
!<XP�݈�����Lhyphenation/hyph_fi.hyfPK
!<3�E6�W�W��t�Lhyphenation/hyph_fr.hyfPK
!<��]YHqHq��eAMhyphenation/hyph_gl.hyfPK
!<oȚc���Mhyphenation/hyph_gu.hyfPK
!<�<�((��'�Mhyphenation/hyph_hi.hyfPK
!<�)�|XX����Mhyphenation/hyph_hr.hyfPK
!<��0�H,H,���Mhyphenation/hyph_hsb.hyfPK
!<�S��������Mhyphenation/hyph_hu.hyfPK
!<���-�$�$����]hyphenation/hyph_ia.hyfPK
!<3X���.�.���^hyphenation/hyph_is.hyfPK
!<j��7xx��zN_hyphenation/hyph_it.hyfPK
!<:������'`_hyphenation/hyph_kmr.hyfPK
!<6�u�,,��o_hyphenation/hyph_kn.hyfPK
!<lMkE�
�
��~q_hyphenation/hyph_la.hyfPK
!<��?��H�H��[|_hyphenation/hyph_lt.hyfPK
!<�@����P�_hyphenation/hyph_ml.hyfPK
!<
V�r 9 9��%�_hyphenation/hyph_mn.hyfPK
!<�P{�T�T��z`hyphenation/hyph_nb.hyfPK
!<��M�����cVghyphenation/hyph_nl.hyfPK
!<.���T�T��$ujhyphenation/hyph_nn.hyfPK
!<E�ƅ��
�qhyphenation/hyph_or.hyfPK
!<	�$�����N�qhyphenation/hyph_pa.hyfPK
!<{�ӧ4�4���{�qhyphenation/hyph_pl.hyfPK
!<N����rrhyphenation/hyph_pt.hyfPK
!<�{i��@�@���wrhyphenation/hyph_ru.hyfPK
!<��S4747���thyphenation/hyph_sh.hyfPK
!<Ni�I�I��K�uhyphenation/hyph_sl.hyfPK
!<���X�X��:vhyphenation/hyph_sv.hyfPK
!<ps���
�whyphenation/hyph_ta.hyfPK
!<����((��V�whyphenation/hyph_te.hyfPK
!<U�B1������whyphenation/hyph_tr.hyfPK
!< d�xYxY��Оwhyphenation/hyph_uk.hyfPK
!<V��$��%��}�xchrome/remote/content/cdp/CDP.sys.mjsPK
!<z�y&&/��_	ychrome/remote/content/cdp/CDPConnection.sys.mjsPK
!<��P
P
'���/ychrome/remote/content/cdp/Error.sys.mjsPK
!<���

-��T=ychrome/remote/content/cdp/JSONHandler.sys.mjsPK
!<�������*���Yychrome/remote/content/cdp/Protocol.sys.mjsPK
!<Z���0��|�chrome/remote/content/cdp/StreamRegistry.sys.mjsPK
!<��xMM>���%�chrome/remote/content/cdp/domains/ContentProcessDomain.sys.mjsPK
!<:��?��D(�chrome/remote/content/cdp/domains/ContentProcessDomains.sys.mjsPK
!<*����0���,�chrome/remote/content/cdp/domains/Domain.sys.mjsPK
!<欐�5���4�chrome/remote/content/cdp/domains/DomainCache.sys.mjsPK
!<�X��==>��>A�chrome/remote/content/cdp/domains/ParentProcessDomains.sys.mjsPK
!<;��.��5���E�chrome/remote/content/cdp/domains/content/DOM.sys.mjsPK
!<7%vn��;��d�chrome/remote/content/cdp/domains/content/Emulation.sys.mjsPK
!<k�T7��9j�chrome/remote/content/cdp/domains/content/Input.sys.mjsPK
!<Tє5���q�chrome/remote/content/cdp/domains/content/Log.sys.mjsPK
!<]XJ�009��z�chrome/remote/content/cdp/domains/content/Network.sys.mjsPK
!<�=��3�36���|�chrome/remote/content/cdp/domains/content/Page.sys.mjsPK
!<v6�خ�=�����chrome/remote/content/cdp/domains/content/Performance.sys.mjsPK
!<���;iKiK9�����chrome/remote/content/cdp/domains/content/Runtime.sys.mjsPK
!<����:��e��chrome/remote/content/cdp/domains/content/Security.sys.mjsPK
!<̭:-�@�@J��h�chrome/remote/content/cdp/domains/content/runtime/ExecutionContext.sys.mjsPK
!<(N�HH8���C�chrome/remote/content/cdp/domains/parent/Browser.sys.mjsPK
!<��W�:��UI�chrome/remote/content/cdp/domains/parent/Emulation.sys.mjsPK
!<X����6���^�chrome/remote/content/cdp/domains/parent/Fetch.sys.mjsPK
!<i��V��3���a�chrome/remote/content/cdp/domains/parent/IO.sys.mjsPK
!<}��??6���n�chrome/remote/content/cdp/domains/parent/Input.sys.mjsPK
!<Be�]�:�:8��P��chrome/remote/content/cdp/domains/parent/Network.sys.mjsPK
!<��R�[a[a5��E��chrome/remote/content/cdp/domains/parent/Page.sys.mjsPK
!<��m��9����chrome/remote/content/cdp/domains/parent/Security.sys.mjsPK
!<ws�U��;��'�chrome/remote/content/cdp/domains/parent/SystemInfo.sys.mjsPK
!<t��v��7��G,�chrome/remote/content/cdp/domains/parent/Target.sys.mjsPK
!<�U���C��|L�chrome/remote/content/cdp/domains/parent/page/DialogHandler.sys.mjsPK
!<�\����<���X�chrome/remote/content/cdp/observers/ChannelEventSink.sys.mjsPK
!<tw�^]];���d�chrome/remote/content/cdp/observers/ContextObserver.sys.mjsPK
!<�U�JJ;���}�chrome/remote/content/cdp/observers/NetworkObserver.sys.mjsPK
!<�Ľ�:��ȇchrome/remote/content/cdp/observers/TargetObserver.sys.mjsPK
!<�p�l��@��/ׇchrome/remote/content/cdp/sessions/ContentProcessSession.sys.mjsPK
!<�r��=���chrome/remote/content/cdp/sessions/MainProcessSession.sys.mjsPK
!<��}�
�
2��i�chrome/remote/content/cdp/sessions/Session.sys.mjsPK
!<�Vۮ@@5��]�chrome/remote/content/cdp/sessions/TabSession.sys.mjsPK
!<�n�2��2����chrome/remote/content/cdp/sessions/frame-script.jsPK
!<�+*��;���chrome/remote/content/cdp/targets/MainProcessTarget.sys.mjsPK
!<�.��3��_�chrome/remote/content/cdp/targets/TabTarget.sys.mjsPK
!<|#�lII0����chrome/remote/content/cdp/targets/Target.sys.mjsPK
!<x��M��4��?#�chrome/remote/content/cdp/targets/TargetList.sys.mjsPK
!<�a�a%a%3��3�chrome/remote/content/components/Marionette.sys.mjsPK
!<��8�84���X�chrome/remote/content/components/RemoteAgent.sys.mjsPK
!<eGT4T4,�����chrome/remote/content/external/EventUtils.jsPK
!</��&gCgCG���Ɗchrome/remote/content/marionette/actors/MarionetteCommandsChild.sys.mjsPK
!<�2V�y.y.H��f
�chrome/remote/content/marionette/actors/MarionetteCommandsParent.sys.mjsPK
!<�ޛՎ�E��E9�chrome/remote/content/marionette/actors/MarionetteEventsChild.sys.mjsPK
!<�n����F��6B�chrome/remote/content/marionette/actors/MarionetteEventsParent.sys.mjsPK
!<�T�M��F��gO�chrome/remote/content/marionette/actors/MarionetteReftestChild.sys.mjsPK
!<8$���G��}o�chrome/remote/content/marionette/actors/MarionetteReftestParent.sys.mjsPK
!<�n�KK.���{�chrome/remote/content/marionette/addon.sys.mjsPK
!<`2�~~-��)��chrome/remote/content/marionette/atom.sys.mjsPK
!<�%��='='0���chrome/remote/content/marionette/browser.sys.mjsPK
!<Z��55-��}��chrome/remote/content/marionette/cert.sys.mjsPK
!<f9:�U#U#/���Čchrome/remote/content/marionette/cookie.sys.mjsPK
!<�=������/����chrome/remote/content/marionette/driver.sys.mjsPK
!<�	e�'�'1��ݽ�chrome/remote/content/marionette/evaluate.sys.mjsPK
!<%<O��X�X4����chrome/remote/content/marionette/interaction.sys.mjsPK
!<�U�D=D=-���>�chrome/remote/content/marionette/json.sys.mjsPK
!<#4]��-��4|�chrome/remote/content/marionette/l10n.sys.mjsPK
!<n��t"t"0����chrome/remote/content/marionette/message.sys.mjsPK
!<���6�61��Ī�chrome/remote/content/marionette/navigate.sys.mjsPK
!<�`��+�+0���chrome/remote/content/marionette/packets.sys.mjsPK
!<d�S�11.��$�chrome/remote/content/marionette/prefs.sys.mjsPK
!<���PDD3����chrome/remote/content/marionette/reftest-content.jsPK
!<����k�k0��6&�chrome/remote/content/marionette/reftest.sys.mjsPK
!<�����.��	��chrome/remote/content/marionette/reftest.xhtmlPK
!<�E�y3y3/��5��chrome/remote/content/marionette/server.sys.mjsPK
!<��8DD5���Ɛchrome/remote/content/marionette/stream-utils.sys.mjsPK
!<�n�9�?�?-����chrome/remote/content/marionette/sync.sys.mjsPK
!<	a�`�@�@2���%�chrome/remote/content/marionette/transport.sys.mjsPK
!<Q��!�!6���f�chrome/remote/content/marionette/web-reference.sys.mjsPK
!<o��;;1����chrome/remote/content/marionette/webauthn.sys.mjsPK
!<�Q)�"�"7�����chrome/remote/content/server/WebSocketHandshake.sys.mjsPK
!<�ȍxx7�����chrome/remote/content/server/WebSocketTransport.sys.mjsPK
!<#�|��t�t*��[ȑchrome/remote/content/server/httpd.sys.mjsPK
!<h���gg,��u=�chrome/remote/content/shared/AppInfo.sys.mjsPK
!<P)Pl
l
,��&F�chrome/remote/content/shared/Browser.sys.mjsPK
!<\���||,���S�chrome/remote/content/shared/Capture.sys.mjsPK
!<}��:���k�chrome/remote/content/shared/ChallengeHeaderParser.sys.mjsPK
!<���o_�_�(���t�chrome/remote/content/shared/DOM.sys.mjsPK
!<3�ߞSS+��O��chrome/remote/content/shared/Format.sys.mjsPK
!</ȴ�(���	�chrome/remote/content/shared/Log.sys.mjsPK
!<����5��5�chrome/remote/content/shared/MobileTabBrowser.sys.mjsPK
!<�4���F�F-����chrome/remote/content/shared/Navigate.sys.mjsPK
!<ô�\IVIV6���e�chrome/remote/content/shared/NavigationManager.sys.mjsPK
!<����8��~��chrome/remote/content/shared/NetworkCacheManager.sys.mjsPK
!<qj�4ss>���ϕchrome/remote/content/shared/NetworkDecodedBodySizeMap.sys.mjsPK
!<jR�
s1s13��dՕchrome/remote/content/shared/NetworkRequest.sys.mjsPK
!<��Y4��(�chrome/remote/content/shared/NetworkResponse.sys.mjsPK
!<��~8��(��� �chrome/remote/content/shared/PDF.sys.mjsPK
!<�����0���=�chrome/remote/content/shared/Permissions.sys.mjsPK
!<�ff+���N�chrome/remote/content/shared/Prompt.sys.mjsPK
!<|��R%R%*��Vd�chrome/remote/content/shared/Realm.sys.mjsPK
!<�j�@�>�>;���chrome/remote/content/shared/RecommendedPreferences.sys.mjsPK
!<}�����0��&ɖchrome/remote/content/shared/RemoteError.sys.mjsPK
!<~�C��*��b˖chrome/remote/content/shared/Stack.sys.mjsPK
!<���Q'Q')���Ӗchrome/remote/content/shared/Sync.sys.mjsPK
!<W�� 8 8/��2��chrome/remote/content/shared/TabManager.sys.mjsPK
!<Wp�0��)���3�chrome/remote/content/shared/UUID.sys.mjsPK
!<���7���5�chrome/remote/content/shared/UserContextManager.sys.mjsPK
!<�`Q��8��*P�chrome/remote/content/shared/WebSocketConnection.sys.mjsPK
!<�b�G$G$2��#a�chrome/remote/content/shared/WindowManager.sys.mjsPK
!<Z��m��M�����chrome/remote/content/shared/js-window-actors/NavigationListenerActor.sys.mjsPK
!<[����M����chrome/remote/content/shared/js-window-actors/NavigationListenerChild.sys.mjsPK
!<N�8JN��A��chrome/remote/content/shared/js-window-actors/NavigationListenerParent.sys.mjsPK
!<-�i�d	d	H�����chrome/remote/content/shared/listeners/BeforeStopRequestListener.sys.mjsPK
!<6�4..F��~��chrome/remote/content/shared/listeners/BrowsingContextListener.sys.mjsPK
!<�@�
�
A��ʗchrome/remote/content/shared/listeners/ConsoleAPIListener.sys.mjsPK
!<�%Yu��>���חchrome/remote/content/shared/listeners/ConsoleListener.sys.mjsPK
!<|~{ ;;I����chrome/remote/content/shared/listeners/ContextualIdentityListener.sys.mjsPK
!<���i
i
;����chrome/remote/content/shared/listeners/LoadListener.sys.mjsPK
!<d�7gjjA��M��chrome/remote/content/shared/listeners/NavigationListener.sys.mjsPK
!<ox7�*�*A���chrome/remote/content/shared/listeners/NetworkEventRecord.sys.mjsPK
!<C��U@@>��Y3�chrome/remote/content/shared/listeners/NetworkListener.sys.mjsPK
!<7=r� � =���A�chrome/remote/content/shared/listeners/PromptListener.sys.mjsPK
!<n�2��:��Hc�chrome/remote/content/shared/messagehandler/Errors.sys.mjsPK
!<{Xư2 2 D��nl�chrome/remote/content/shared/messagehandler/EventsDispatcher.sys.mjsPK
!<2����+�+B����chrome/remote/content/shared/messagehandler/MessageHandler.sys.mjsPK
!<I�SQ,,J��P��chrome/remote/content/shared/messagehandler/MessageHandlerRegistry.sys.mjsPK
!<�ۂ�!!:���֘chrome/remote/content/shared/messagehandler/Module.sys.mjsPK
!<���~''?��]�chrome/remote/content/shared/messagehandler/ModuleCache.sys.mjsPK
!<����F����chrome/remote/content/shared/messagehandler/RootMessageHandler.sys.mjsPK
!<H6��((N��'�chrome/remote/content/shared/messagehandler/RootMessageHandlerRegistry.sys.mjsPK
!<���N���*�chrome/remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjsPK
!<��|#2#2K���G�chrome/remote/content/shared/messagehandler/sessiondata/SessionData.sys.mjsPK
!<�ǫ��Q��7z�chrome/remote/content/shared/messagehandler/sessiondata/SessionDataReader.sys.mjsPK
!<��d�HHS��s~�chrome/remote/content/shared/messagehandler/transports/BrowsingContextUtils.sys.mjsPK
!<��NL��,��chrome/remote/content/shared/messagehandler/transports/RootTransport.sys.mjsPK
!<!ޠ���h�����chrome/remote/content/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameActor.sys.mjsPK
!<��;h�����chrome/remote/content/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameChild.sys.mjsPK
!<]�G;��i��W��chrome/remote/content/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameParent.sys.mjsPK
!<mt4�9�9<���Ǚchrome/remote/content/shared/webdriver/Accessibility.sys.mjsPK
!<Kh�::6����chrome/remote/content/shared/webdriver/Actions.sys.mjsPK
!<N0�8+5+55��h�chrome/remote/content/shared/webdriver/Assert.sys.mjsPK
!<��>�'t't;���8�chrome/remote/content/shared/webdriver/Capabilities.sys.mjsPK
!<c��c�c5��f��chrome/remote/content/shared/webdriver/Errors.sys.mjsPK
!<	���;;4��J�chrome/remote/content/shared/webdriver/Event.sys.mjsPK
!<�2���'�'6���0�chrome/remote/content/shared/webdriver/KeyData.sys.mjsPK
!<8[�{cc8��Y�chrome/remote/content/shared/webdriver/NodeCache.sys.mjsPK
!<�f��/>/>6���m�chrome/remote/content/shared/webdriver/Session.sys.mjsPK
!<y��n4n49��K��chrome/remote/content/shared/webdriver/URLPattern.sys.mjsPK
!<�Kg�!!@���chrome/remote/content/shared/webdriver/UserPromptHandler.sys.mjsPK
!<���?�	�	W��u�chrome/remote/content/shared/webdriver/process-actors/WebDriverProcessDataChild.sys.mjsPK
!<�Ƚ�==X����chrome/remote/content/shared/webdriver/process-actors/WebDriverProcessDataParent.sys.mjsPK
!<����||>��;�chrome/remote/content/webdriver-bidi/NewSessionHandler.sys.mjsPK
!<���ֱx�x8���chrome/remote/content/webdriver-bidi/RemoteValue.sys.mjsPK
!<�LkN��:����chrome/remote/content/webdriver-bidi/WebDriverBiDi.sys.mjsPK
!<��y"y"D��2��chrome/remote/content/webdriver-bidi/WebDriverBiDiConnection.sys.mjsPK
!<a�Y�>��
ӝchrome/remote/content/webdriver-bidi/modules/Intercept.sys.mjsPK
!<��f�		C���ߝchrome/remote/content/webdriver-bidi/modules/ModuleRegistry.sys.mjsPK
!<(�<��C����chrome/remote/content/webdriver-bidi/modules/RootBiDiModule.sys.mjsPK
!< �Y��	�	K��/�chrome/remote/content/webdriver-bidi/modules/WindowGlobalBiDiModule.sys.mjsPK
!<:��=��A��U��chrome/remote/content/webdriver-bidi/modules/root/browser.sys.mjsPK
!<��,�+�+�I��]�chrome/remote/content/webdriver-bidi/modules/root/browsingContext.sys.mjsPK
!<4'%?����chrome/remote/content/webdriver-bidi/modules/root/input.sys.mjsPK
!<�k ���=��T�chrome/remote/content/webdriver-bidi/modules/root/log.sys.mjsPK
!<և+�'�'�A����chrome/remote/content/webdriver-bidi/modules/root/network.sys.mjsPK
!<����E��
��chrome/remote/content/webdriver-bidi/modules/root/permissions.sys.mjsPK
!<pM�+(o(o@��1
�chrome/remote/content/webdriver-bidi/modules/root/script.sys.mjsPK
!<��X]D9D9A���y�chrome/remote/content/webdriver-bidi/modules/root/session.sys.mjsPK
!<+"���m�mA��Z��chrome/remote/content/webdriver-bidi/modules/root/storage.sys.mjsPK
!<�����A�AQ���!�chrome/remote/content/webdriver-bidi/modules/windowglobal/browsingContext.sys.mjsPK
!<��Ev��G���c�chrome/remote/content/webdriver-bidi/modules/windowglobal/input.sys.mjsPK
!<��A0��E���x�chrome/remote/content/webdriver-bidi/modules/windowglobal/log.sys.mjsPK
!<�U��||I�����chrome/remote/content/webdriver-bidi/modules/windowglobal/network.sys.mjsPK
!<�9|D;;H�����chrome/remote/content/webdriver-bidi/modules/windowglobal/script.sys.mjsPK
!<�~�%%Y���chrome/remote/content/webdriver-bidi/modules/windowglobal-in-root/browsingContext.sys.mjsPK
!<��&�M����chrome/remote/content/webdriver-bidi/modules/windowglobal-in-root/log.sys.mjsPK
!<(��ooQ����chrome/remote/content/webdriver-bidi/modules/windowglobal-in-root/network.sys.mjsPK
!<J43���P��h�chrome/remote/content/webdriver-bidi/modules/windowglobal-in-root/script.sys.mjsPK
!<�����$��^�modules/AboutCertViewerChild.sys.mjsPK
!<Aϡ]]%��5��modules/AboutCertViewerParent.sys.mjsPK
!<MShf44����modules/AboutPagesUtils.sys.mjsPK
!<��)r������F��modules/AboutReader.sys.mjsPK
!<:2Y��V�modules/AbuseReporter.sys.mjsPK
!<gx��L�L"����modules/ActorManagerParent.sys.mjsPK
!<�,�OO���U�modules/AddonManager.sys.mjsPK
!<��g�44��Zեmodules/AppConstants.sys.mjsPK
!<�^S��$����modules/AppMenuNotifications.sys.mjsPK
!<!b�o�������modules/AsyncPrefs.sys.mjsPK
!<�-�d�d�����modules/AsyncShutdown.sys.mjsPK
!<���گ�(��$��modules/AutoCompleteSimpleSearch.sys.mjsPK
!<_:��>>&����modules/BTPRemoteExceptionList.sys.mjsPK
!<'�K��b�b$�����modules/BackgroundPageThumbs.sys.mjsPK
!<��z�++&����modules/BackgroundTasksManager.sys.mjsPK
!<�e�S55$���:�modules/BackgroundTasksUtils.sys.mjsPK
!<}�XaXa��.p�modules/Bits.sys.mjsPK
!<�%|ƾƾ���ѧmodules/Blocklist.sys.mjsPK
!<��[����!�����modules/BookmarkHTMLUtils.sys.mjsPK
!<���E�E!����modules/BookmarkJSONUtils.sys.mjsPK
!<,���II���c�modules/BookmarkList.sys.mjsPK
!<dO�O���>��modules/Bookmarks.sys.mjsPK
!<�	���%���J�modules/BrowserTelemetryUtils.sys.mjsPK
!<
�=�jWjW��T�modules/BrowserUtils.sys.mjsPK
!<a=�`k
k
�����modules/CSV.sys.mjsPK
!<
����C�C��D��modules/CaptiveDetect.sys.mjsPK
!<�7�� ��P��modules/ClearDataService.sys.mjsPK
!<U��F!C!C��J��modules/ClientID.sys.mjsPK
!<������$���B�modules/ClipboardContextMenu.sys.mjsPK
!<�o`5rr���[�modules/Color.sys.mjsPK
!<��ѭ,�,��9j�modules/CommonDialog.sys.mjsPK
!<6?rBB�� ��modules/ComponentUtils.sys.mjsPK
!<�4��������modules/ConduitsChild.sys.mjsPK
!<-Cm��:�:��Ǵ�modules/ConduitsParent.sys.mjsPK
!<G2�g*X*X����modules/Console.sys.mjsPK
!<;ͳ���!��H�modules/ConsoleAPIStorage.sys.mjsPK
!<.�� O(O('��a�modules/ContentAreaDropListener.sys.mjsPK
!<��$���(�����modules/ContentBlockingAllowList.sys.mjsPK
!<ۮ����#���modules/ContentDOMReference.sys.mjsPK
!<��+��9�9&��ڬ�modules/ContentDispatchChooser.sys.mjsPK
!<1Z`¥¥#���modules/ContentPrefService2.sys.mjsPK
!<��Y���'����modules/ContentPrefServiceChild.sys.mjsPK
!<�YS0�� ��B��modules/ContentPrefStore.sys.mjsPK
!<Y�#�� ��<��modules/ContentPrefUtils.sys.mjsPK
!<��@m�*�*'����modules/CookieBannerListService.sys.mjsPK
!<�{�*

���ޯmodules/CoveragePing.sys.mjsPK
!<o���F�F���B�modules/CrashManager.sys.mjsPK
!<;M6A���ưmodules/CrashMonitor.sys.mjsPK
!<�Ư�	�	��
�modules/CrashReports.sys.mjsPK
!<+���]]����modules/CrashService.sys.mjsPK
!<g��EHEH���
�modules/CrashSubmit.sys.mjsPK
!<�Dɐa+a+(��S�modules/CredentialChooserService.sys.mjsPK
!<z�������~�modules/Credentials.sys.mjsPK
!<=L�
�7�7��׎�modules/CreditCard.sys.mjsPK
!<�hL��&��DZmodules/CustomElementsListener.sys.mjsPK
!<EaYp*p*"���˱modules/DAPTelemetrySender.sys.mjsPK
!<"�b�������modules/DAPVisitCounter.sys.mjsPK
!<�K�9!9!#���	�modules/DateTimePickerPanel.sys.mjsPK
!<W��4UU��M+�modules/DefaultCLH.sys.mjsPK
!<�D���1�1���7�modules/DeferredTask.sys.mjsPK
!<U��n�n���i�modules/DownloadHistory.sys.mjsPK
!<?^�Fbb���زmodules/DownloadLastDir.sys.mjsPK
!<��R=?=?��4��modules/DownloadLegacy.sys.mjsPK
!<d@
���7�modules/DownloadPaths.sys.mjsPK
!<kr�.�� ���J�modules/DownloadUIHelper.sys.mjsPK
!<�Vre�M�M��*j�modules/DownloadUtils.sys.mjsPK
!<�2~'�f�f��2��modules/E10SUtils.sys.mjsPK
!<��>>>"���modules/EnterprisePolicies.sys.mjsPK
!<�'���)���"�modules/EnterprisePoliciesContent.sys.mjsPK
!<w~O�!V!V(���%�modules/EnterprisePoliciesParent.sys.mjsPK
!<�̜M--��|�modules/EventEmitter.sys.mjsPK
!<j��dd!�����modules/ExtHandlerService.sys.mjsPK
!<[/������modules/Extension.sys.mjsPK
!<��'T'T �����modules/ExtensionActions.sys.mjsPK
!<�j�0R
R
$���P�modules/ExtensionActivityLog.sys.mjsPK
!<�uȤ�w�w���^�modules/ExtensionChild.sys.mjsPK
!<����+��tַmodules/ExtensionChildDevToolsUtils.sys.mjsPK
!<� v v����modules/ExtensionCommon.sys.mjsPK
!<��.�g�g� ���Y�modules/ExtensionContent.sys.mjsPK
!<�n�h�d�d���
�modules/ExtensionDNR.sys.mjsPK
!<؀ͩ��"��`r�modules/ExtensionDNRLimits.sys.mjsPK
!<g1�MM!����modules/ExtensionDNRStore.sys.mjsPK
!<z���L�L����modules/ExtensionMenus.sys.mjsPK
!<x��y;y;"��MӼmodules/ExtensionPageChild.sys.mjsPK
!<��?������modules/ExtensionParent.sys.mjsPK
!<s����+���.�modules/ExtensionPermissionMessages.sys.mjsPK
!<��u��~�~$���:�modules/ExtensionPermissions.sys.mjsPK
!<���Y�Y+���modules/ExtensionPreferencesManager.sys.mjsPK
!<!u�7�?�?&���modules/ExtensionProcessScript.sys.mjsPK
!<�ƬP--'��ST�modules/ExtensionScriptingStore.sys.mjsPK
!<���z+z+&�����modules/ExtensionSearchHandler.sys.mjsPK
!<�ų�\V\V&��f��modules/ExtensionSettingsStore.sys.mjsPK
!<U���gAgA"���modules/ExtensionShortcuts.sys.mjsPK
!<��)��J�J ���E�modules/ExtensionStorage.sys.mjsPK
!<m����*��͐�modules/ExtensionStorageComponents.sys.mjsPK
!<���msms#���modules/ExtensionStorageIDB.sys.mjsPK
!<AFl33$����modules/ExtensionStorageSync.sys.mjsPK
!<7y̮����)��/�modules/ExtensionStorageSyncKinto.sys.mjsPK
!<�j�U
/
/"����modules/ExtensionTelemetry.sys.mjsPK
!<&�i�x&x&��B�modules/ExtensionUtils.sys.mjsPK
!<�iG�bebe$���7�modules/ExtensionWorkerChild.sys.mjsPK
!<^n�O�������modules/FileUtils.sys.mjsPK
!<��	����p��modules/FillHelpers.sys.mjsPK
!<��Y���v��modules/FindBarContent.sys.mjsPK
!<M��m�������modules/FindContent.sys.mjsPK
!<��7y^y^�����modules/Finder.sys.mjsPK
!<��t�MM!��U<�modules/FinderHighlighter.sys.mjsPK
!<��SOgOg���X�modules/FinderIterator.sys.mjsPK
!<�Բ�F�F��l��modules/FinderParent.sys.mjsPK
!<DV����.����modules/FingerprintingWebCompatService.sys.mjsPK
!<�A�ЙH�H��c%�modules/FirefoxRelay.sys.mjsPK
!<��ۜ��!��6n�modules/FirefoxRelayTelemetry.mjsPK
!<������!��3u�modules/FirefoxRelayUtils.sys.mjsPK
!<��!����Yx�modules/FirstStartup.sys.mjsPK
!<��G�RR��A��modules/ForgetAboutSite.sys.mjsPK
!<�lʛ������Й�modules/FormHistory.sys.mjsPK
!<���8��'���=�modules/FormHistoryAutoComplete.sys.mjsPK
!<���	�	"���M�modules/FormHistoryStartup.sys.mjsPK
!<���5,,���W�modules/FormLikeFactory.sys.mjsPK
!<Ԁ
]oo��0r�modules/FormScenarios.sys.mjsPK
!<��?�PfPf ���}�modules/FxAccountsClient.sys.mjsPK
!<���%�%�"��h��modules/FxAccountsCommands.sys.mjsPK
!<�t.{K{K ���m�modules/FxAccountsCommon.sys.mjsPK
!<u06�** �����modules/FxAccountsConfig.sys.mjsPK
!<���L-X-X �����modules/FxAccountsDevice.sys.mjsPK
!<��]�"�"��4<�modules/FxAccountsOAuth.sys.mjsPK
!<Г|�<<!��T_�modules/FxAccountsPairing.sys.mjsPK
!<�W�{U�U�(�����modules/FxAccountsPairingChannel.sys.mjsPK
!<c�!Wpp!��5^�modules/FxAccountsProfile.sys.mjsPK
!<���j  '���w�modules/FxAccountsProfileClient.sys.mjsPK
!<��s''��@��modules/FxAccountsPush.sys.mjsPK
!<�Z5��#�����modules/FxAccountsTelemetry.sys.mjsPK
!<Ƅ�V�����modules/GMPExtractor.worker.jsPK
!<n����#�#�����modules/GMPUtils.sys.mjsPK
!<9h��#�#���
�modules/Geometry.sys.mjsPK
!<+�0�		!���.�modules/HPKEConfigManager.sys.mjsPK
!<?�1l����%8�modules/HelperAppDlg.sys.mjsPK
!<�^6�
�
��d��modules/HiddenFrame.sys.mjsPK
!<8bl�_�_/��v��modules/IdentityCredentialPromptService.sys.mjsPK
!<l�#q�
�
���U�modules/IgnoreLists.sys.mjsPK
!<	��**$���`�modules/ImageObjectProcessor.sys.mjsPK
!<��L5050���z�modules/IndexedDB.sys.mjsPK
!<�����I�I"��Z��modules/InlineSpellChecker.sys.mjsPK
!<�uu���)��#��modules/InlineSpellCheckerContent.sys.mjsPK
!<ٜ�,��%��h�modules/InsecurePasswordUtils.sys.mjsPK
!<	|��])])����modules/Integration.sys.mjsPK
!<�VO�A�A��0F�modules/JSONFile.sys.mjsPK
!<c�������modules/JsonSchema.sys.mjsPK
!<�t�U�������modules/KeywordUtils.sys.mjsPK
!<p��%,%,����modules/LangPackMatcher.sys.mjsPK
!<[�m

��K��modules/LayoutUtils.sys.mjsPK
!<�3�7�Q�Q(�����modules/LightweightThemeConsumer.sys.mjsPK
!<��>00'���4�modules/LightweightThemeManager.sys.mjsPK
!<�aII���8�modules/LocationHelper.sys.mjsPK
!<6�2G�K�K��Q>�modules/Log.sys.mjsPK
!<,����8�8��J��modules/LogManager.sys.mjsPK
!<�:`�00!��A��modules/LoginAutoComplete.sys.mjsPK
!<��+ȗ������modules/LoginCSVImport.sys.mjsPK
!<o�rNN��Z
�modules/LoginExport.sys.mjsPK
!<^�-S�S�����modules/LoginHelper.sys.mjsPK
!<D.7�`
`
��m
�modules/LoginInfo.sys.mjsPK
!<���`���modules/LoginManager.shared.mjsPK
!<���O�O�(��C,�modules/LoginManagerAuthPrompter.sys.mjsPK
!<�e�����!��خ�modules/LoginManagerChild.sys.mjsPK
!<�^^'��
r�modules/LoginManagerContextMenu.sys.mjsPK
!<�[U
�
�"�����modules/LoginManagerParent.sys.mjsPK
!<���xʗʗ$���V�modules/LoginManagerPrompter.sys.mjsPK
!<�c����%����modules/LoginManagerTelemetry.sys.mjsPK
!<����b-b-����modules/LoginRecipes.sys.mjsPK
!<0� 5�
�
"���$�modules/LoginRelatedRealms.sys.mjsPK
!<PH>tII$���2�modules/MainProcessSingleton.sys.mjsPK
!<C�JX33��;6�modules/Manifest.sys.mjsPK
!<�����U�modules/ManifestFinder.sys.mjsPK
!<�\�%�
�
���\�modules/ManifestIcons.sys.mjsPK
!<܇-���%���g�modules/ManifestMessagesChild.sys.mjsPK
!<�X�> ���t�modules/ManifestObtainer.sys.mjsPK
!<n��}K'K'!����modules/ManifestProcessor.sys.mjsPK
!<�x�WW��~��modules/MatchURLFilters.sys.mjsPK
!<]�[g$����modules/MemoryNotificationDB.sys.mjsPK
!<��l���#��_��modules/MessageManagerProxy.sys.mjsPK
!<����
�
��x��modules/ModulesPing.sys.mjsPK
!<��JJ"�����modules/MozProtocolHandler.sys.mjsPK
!<�v>����/��modules/NLP.sys.mjsPK
!<+������
��modules/NativeManifests.sys.mjsPK
!<��J()8)8����modules/NativeMessaging.sys.mjsPK
!<O�<:<:��SK�modules/NetUtil.sys.mjsPK
!<߂��;;#��ą�modules/NetworkErrorLogging.sys.mjsPK
!<�zLq�5�5*����modules/NetworkGeolocationProvider.sys.mjsPK
!<IB�55����modules/NewTabUtils.sys.mjsPK
!<;�|M`.`.��~�modules/NotificationDB.sys.mjsPK
!<䶜�#��5�modules/NotificationStorage.sys.mjsPK
!<�d�u�7�7��bJ�modules/OSKeyStore.sys.mjsPK
!<��ԕ���-��modules/ObjectUtils.sys.mjsPK
!<Q.�mCC�����modules/ObliviousHTTP.sys.mjsPK
!<H��	T.T. ��y��modules/OpenSearchLoader.sys.mjsPK
!<�f{�1:1:����modules/PageThumbUtils.sys.mjsPK
!<C�Y:z:z��x�modules/PageThumbs.sys.mjsPK
!<�Ύi��0����modules/PartitioningExceptionListService.sys.mjsPK
!<O�%��$����modules/PasswordRulesManager.sys.mjsPK
!<���y
y
 ��]��modules/PermissionsUtils.sys.mjsPK
!<�%>���� ����modules/PictureInPicture.sys.mjsPK
!<��|c��(����modules/PictureInPictureControls.sys.mjsPK
!<9,LxAA����modules/PlacesBackups.sys.mjsPK
!<M�8
������<��modules/PlacesDBUtils.sys.mjsPK
!<O^9U�� ��K��modules/PlacesExpiration.sys.mjsPK
!<z��1\1\*���,�modules/PlacesFrecencyRecalculator.sys.mjsPK
!<qq�	E5E5��
��modules/PlacesPreviews.sys.mjsPK
!<;�%'r7r7�����modules/PlacesQuery.sys.mjsPK
!<��E88��9��modules/PlacesSyncUtils.sys.mjsPK
!<p�s����"����modules/PlacesTransactions.sys.mjsPK
!<r��x�x�����modules/PlacesUtils.sys.mjsPK
!<G�d���"���h�modules/PolicySearchEngine.sys.mjsPK
!<Υu��"���u�modules/PopupNotifications.sys.mjsPK
!<����7�7��ȍ�modules/Preferences.sys.mjsPK
!<CV�F>>#�����modules/PrincipalsCollector.sys.mjsPK
!<��խ� � )��%��modules/PrivateAttributionService.sys.mjsPK
!<֨Ռ�	�	$��S��modules/PrivateBrowsingUtils.sys.mjsPK
!<�J��!!��0	�modules/ProcessType.sys.mjsPK
!<{0�[������modules/ProfileAge.sys.mjsPK
!<+�A!:!:���*�modules/PromiseWorker.sys.mjsPK
!<��w�����d�modules/PromptUtils.sys.mjsPK
!<�m�9�����{�modules/Prompter.sys.mjsPK
!<6��:�0�0"���w�modules/ProxyChannelFilter.sys.mjsPK
!<�i-�B�B#�����modules/PurgeTrackerService.sys.mjsPK
!<,�#�%�%��|��modules/Push.sys.mjsPK
!<?e�=C=C��R�modules/ReaderMode.sys.mjsPK
!<l�o>>���T�modules/Readerable.sys.mjsPK
!<��ݞ�h�h��=p�modules/Region.sys.mjsPK
!<���J66'��g��modules/RemotePageAccessManager.sys.mjsPK
!<3����#����modules/RemoteWebNavigation.sys.mjsPK
!<�q�,�����(�modules/ResetProfile.sys.mjsPK
!<38�%��{4�modules/ResponsivenessMonitor.sys.mjsPK
!<�	&�� � ���8�modules/RustSync15.sys.mjsPK
!<`;�%�%����Y�modules/RustTabs.sys.mjsPK
!<u��p	p	��BO�modules/SandboxUtils.sys.mjsPK
!<ߕ�V5�5����X�modules/Schemas.sys.mjsPK
!<�T+XX$��V�modules/SearchEngineSelector.sys.mjsPK
!<�%�-;�;����t�modules/SearchService.sys.mjsPK
!<���Y�Y��#T�modules/SearchSettings.sys.mjsPK
!<��>��`�`*����modules/SearchSuggestionController.sys.mjsPK
!<��ob
3
3!��,�modules/SearchSuggestions.sys.mjsPK
!<�qG2�6�6��uB�modules/SearchUtils.sys.mjsPK
!</\Q-00���y�modules/SecurityInfo.sys.mjsPK
!<�:�
����ũ�modules/SelectionUtils.sys.mjsPK
!<dD�������modules/ServiceRequest.sys.mjsPK
!<�	"ZZ$��l��modules/ShieldContentProcess.sys.mjsPK
!<N'��0�0����modules/ShortcutUtils.sys.mjsPK
!<K\ӧLQLQ!��,�modules/SignUpFormRuleset.sys.mjsPK
!<f�������V�modules/SimpleServices.sys.mjsPK
!<s�|T�����n�modules/SlowScriptDebug.sys.mjsPK
!<z-Χ�
�
���q�modules/Sqlite.sys.mjsPK
!<�Ux�������modules/SubDialog.sys.mjsPK
!<V1�gg����modules/Subprocess.sys.mjsPK
!<���b�b%��~2�modules/SyncedBookmarksMirror.sys.mjsPK
!<�?�UDD��u��modules/TaggingService.sys.mjsPK
!<u�c�vv#�����modules/TelemetryController.sys.mjsPK
!<�^�hh'��l��modules/TelemetryControllerBase.sys.mjsPK
!<ևQ��	�	*����modules/TelemetryControllerContent.sys.mjsPK
!<�R��n�n�)��G��modules/TelemetryControllerParent.sys.mjsPK
!<��D�mm$�����modules/TelemetryEnvironment.sys.mjsPK
!<oV��'A'A(�����modules/TelemetryReportingPolicy.sys.mjsPK
!<G�[������4�modules/TelemetrySend.sys.mjsPK
!<1k��� ����modules/TelemetrySession.sys.mjsPK
!<:G��� ��D��modules/TelemetryStartup.sys.mjsPK
!<����#��s��modules/TelemetryTimestamps.sys.mjsPK
!<˘��$�$�����modules/TelemetryUtils.sys.mjsPK
!<�]���������modules/Timer.sys.mjsPK
!<Z�pp#�����modules/TooltipTextProvider.sys.mjsPK
!<�o�y*�*���P�modules/Troubleshoot.sys.mjsPK
!<O���.�.������modules/URIFixup.sys.mjsPK
!<�#t��/��.�modules/URLDecorationAnnotationsService.sys.mjsPK
!<m~�DD��D6�modules/URLFormatter.sys.mjsPK
!<�4�t�4�4,���K�modules/URLQueryStrippingListService.sys.mjsPK
!<5ݚ��$�����modules/UntrustedModulesPing.sys.mjsPK
!<R�'����w��modules/UpdatePing.sys.mjsPK
!<����o2o2"�����modules/UpdateTimerManager.sys.mjsPK
!<2�ؒT�T���/��modules/UpdateUtils.sys.mjsPK
!<���p�p*���a�modules/UrlClassifierHashCompleter.sys.mjsPK
!<�L�@AA2����modules/UrlClassifierRemoteSettingsService.sys.mjsPK
!<�kW�e�e.����modules/UserCharacteristicsPageService.sys.mjsPK
!<?8i�� ���I�modules/UserSearchEngine.sys.mjsPK
!<*�44���O�modules/ValueExtractor.sys.mjsPK
!<���_CC��N\�modules/WebAuthnFeature.sys.mjsPK
!<@���	$	$���k�modules/WebChannel.sys.mjsPK
!<*y������modules/WebHandlerApp.sys.mjsPK
!<ř�I�/�/��Z��modules/WebNavigation.sys.mjsPK
!<籂@
@
#����modules/WebNavigationFrames.sys.mjsPK
!<���z?z? ���modules/WebRequestUpload.sys.mjsPK
!<�|�==#���"�modules/WebVTTParserWrapper.sys.mjsPK
!<��T���+��<)�modules/WellKnownOpportunisticUtils.sys.mjsPK
!<��p��-�-��x,�modules/XPCOMUtils.sys.mjsPK
!<;�V�1"1"��wZ�modules/XULStore.sys.mjsPK
!<m��6��&���|�modules/addons/AddonRepository.sys.mjsPK
!<x�
�$��"�modules/addons/AddonSettings.sys.mjsPK
!<�%	�K�K)��p�modules/addons/AddonUpdateChecker.sys.mjsPK
!<���BcBc"���_�modules/addons/GMPProvider.sys.mjsPK
!<��N�N-���modules/addons/SitePermsAddonProvider.sys.mjsPK
!<�OeEW�W�"��T�modules/addons/XPIDatabase.sys.mjsPK
!<�y�&��!����modules/addons/XPIExports.sys.mjsPK
!<�`lo"Q"Q!���modules/addons/XPIInstall.sys.mjsPK
!<����AzAz"��a9�modules/addons/XPIProvider.sys.mjsPK
!<��Z��#���modules/addons/crypto-utils.sys.mjsPK
!<#T#���,���modules/addons/siteperms-addon-utils.sys.mjsPK
!<XyT��� ����modules/amContentHandler.sys.mjsPK
!<
�P P  ���modules/amInstallTrigger.sys.mjsPK
!<Ѣ�Z�(�(����modules/amManager.sys.mjsPK
!<eI�[ [ ����modules/amWebAPI.sys.mjsPK
!<5R:�~~8��';�modules/backgroundtasks/BackgroundTask_exception.sys.mjsPK
!<F��mm6���<�modules/backgroundtasks/BackgroundTask_failure.sys.mjsPK
!<��b�+�+6���>�modules/backgroundtasks/BackgroundTask_message.sys.mjsPK
!<�S���9���j�modules/backgroundtasks/BackgroundTask_pingsender.sys.mjsPK
!<��:5�(�(>��q�modules/backgroundtasks/BackgroundTask_removeDirectory.sys.mjsPK
!<	�W���6��0��modules/backgroundtasks/BackgroundTask_success.sys.mjsPK
!<v&S���%��U��modules/backgroundtasks/dbg-actors.jsPK
!<$*CCC2�����modules/components-utils/ClientEnvironment.sys.mjsPK
!<`�w**2����modules/components-utils/FilterExpressions.sys.mjsPK
!<�m�I�I4����modules/components-utils/JsonSchemaValidator.sys.mjsPK
!<6����)���modules/components-utils/Sampling.sys.mjsPK
!<��Pw��4���1modules/components-utils/WindowsInstallsInfo.sys.mjsPK
!<�8]jm
m
3���=modules/components-utils/WindowsVersionInfo.sys.mjsPK
!<���*5*5(���Kmodules/components-utils/mozjexl.sys.mjsPK
!<�{��
�
3���modules/contentrelevancy/private/InputUtils.sys.mjsPK
!<�Z"�#�#��Q�modules/crypto-SDR.sys.mjsPK
!<?������modules/ctypes.sys.mjsPK
!<ـ�VV$��>�modules/handlers/HandlerList.sys.mjsPK
!<�9�������modules/jsdebugger.sys.mjsPK
!<�L��MM����modules/kvstore.sys.mjsPK
!<��ɕL L  ��\�modules/media/IdpSandbox.sys.mjsPK
!<!7)E�E�$���modules/media/PeerConnection.sys.mjsPK
!<���i+i+'��mmodules/media/PeerConnectionIdp.sys.mjsPK
!<.	���*��.modules/megalist/MegalistViewModel.sys.mjsPK
!<#�g%
%
.��2Gmodules/megalist/aggregator/Aggregator.sys.mjsPK
!<���??5���Qmodules/megalist/aggregator/DefaultAggregator.sys.mjsPK
!<�|ؖ�#�#>��5Tmodules/megalist/aggregator/datasources/DataSourceBase.sys.mjsPK
!<�X߫�O�O?��7xmodules/megalist/aggregator/datasources/LoginDataSource.sys.mjsPK
!<_��U9:9:'��L�modules/narrate/NarrateControls.sys.mjsPK
!<l���]1]1 ���modules/narrate/Narrator.sys.mjsPK
!<�*L��#��e4modules/narrate/VoiceSelect.sys.mjsPK
!<��MM��/Smodules/nsAsyncShutdown.sys.mjsPK
!<�j�������nmodules/nsCrashMonitor.sys.mjsPK
!<���������qmodules/pdfjs.sys.mjsPK
!<N��;

+���umodules/psm/ClientAuthDialogService.sys.mjsPK
!<_W.	�	����modules/reader/JSDOMParser.jsPK
!<�AHAH��cmodules/reader/Readability.jsPK
!<o������Xmodules/reader/Reader.worker.jsPK
!<�!���#��`modules/reader/ReaderWorker.sys.mjsPK
!<�s˔��.bmodules/reflect.sys.mjsPK
!<��0�0 0 %��{emodules/services-common/async.sys.mjsPK
!<i���q,q,*���modules/services-common/hawkclient.sys.mjsPK
!<
����+����modules/services-common/hawkrequest.sys.mjsPK
!<fo@g��1����modules/services-common/kinto-http-client.sys.mjsPK
!<��sC~C~4��Zmodules/services-common/kinto-offline-client.sys.mjsPK
!<>�R��>�>5����modules/services-common/kinto-storage-adapter.sys.mjsPK
!<.0����)���	modules/services-common/observers.sys.mjsPK
!<.A��K�K$��',	modules/services-common/rest.sys.mjsPK
!<p�~B�2�21��x	modules/services-common/tokenserverclient.sys.mjsPK
!<�$��+��!�	modules/services-crypto/WeaveCrypto.sys.mjsPK
!<9S4		(��	�	modules/services-crypto/jwcrypto.sys.mjsPK
!<;�[�[-��X�	modules/services-settings/Attachments.sys.mjsPK
!<��I��L�L*��3=
modules/services-settings/Database.sys.mjsPK
!<m�?��6��k�
modules/services-settings/RemoteSettingsClient.sys.mjsPK
!<�bJ��6���9modules/services-settings/RemoteSettingsWorker.sys.mjsPK
!<F&�7@7@'���Vmodules/services-settings/Utils.sys.mjsPK
!<.�rwUwU1���modules/services-settings/remote-settings.sys.mjsPK
!<�Q��8�8(����modules/services-sync/SyncedTabs.sys.mjsPK
!<�K-L]]'���%modules/services-sync/TabsStore.sys.mjsPK
!<��Q�� � %��z)modules/services-sync/UIState.sys.mjsPK
!<�G�:NDND.���Jmodules/services-sync/addonsreconciler.sys.mjsPK
!<:̧m/m/(���modules/services-sync/addonutils.sys.mjsPK
!<��P�<�<,��оmodules/services-sync/bridged_engine.sys.mjsPK
!<�c� � 2����modules/services-sync/collection_validator.sys.mjsPK
!<)$f��'���
modules/services-sync/constants.sys.mjsPK
!<��"yy$���3
modules/services-sync/doctor.sys.mjsPK
!<��CcCc,��IO
modules/services-sync/engines/addons.sys.mjsPK
!<a�s�r�r/��ֲ
modules/services-sync/engines/bookmarks.sys.mjsPK
!<L��-��&modules/services-sync/engines/clients.sys.mjsPK
!<{b�&U%U%7��O�modules/services-sync/engines/extension-storage.sys.mjsPK
!<�o6���+����modules/services-sync/engines/forms.sys.mjsPK
!<:-�xRR-���modules/services-sync/engines/history.sys.mjsPK
!<E=5�9�9/��fLmodules/services-sync/engines/passwords.sys.mjsPK
!<�3ò�@�@+��B�modules/services-sync/engines/prefs.sys.mjsPK
!<�%DRDR*���modules/services-sync/engines/tabs.sys.mjsPK
!<R�
W��%���modules/services-sync/engines.sys.mjsPK
!<��ff"���,modules/services-sync/keys.sys.mjsPK
!<#B��;;"��m=modules/services-sync/main.sys.mjsPK
!<V�Y�U�U�&���@modules/services-sync/policies.sys.mjsPK
!<cz���$����modules/services-sync/record.sys.mjsPK
!<�nAa a &���hmodules/services-sync/resource.sys.mjsPK
!<�H|�����%��Y�modules/services-sync/service.sys.mjsPK
!<1�93
3
-��,Ymodules/services-sync/stages/declined.sys.mjsPK
!<׺��66/���cmodules/services-sync/stages/enginesync.sys.mjsPK
!<ͨ�J��$���modules/services-sync/status.sys.mjsPK
!<(�CJ\\'��ܦmodules/services-sync/sync_auth.sys.mjsPK
!<3`�٢٢'��5modules/services-sync/telemetry.sys.mjsPK
!<`�8��T�T"��S�modules/services-sync/util.sys.mjsPK
!<w!/�3
3
/��U�modules/sessionstore/SessionStoreHelper.sys.mjsPK
!<�J��x�x'���modules/shared/AddressComponent.sys.mjsPK
!<'�y����&����modules/shared/AddressMetaData.sys.mjsPK
!<I%���-�-/���$modules/shared/AddressMetaDataExtension.sys.mjsPK
!<R����,��nRmodules/shared/AddressMetaDataLoader.sys.mjsPK
!<c���$���gmodules/shared/AddressParser.sys.mjsPK
!<ϴhM	M	'��v�modules/shared/CreditCardRecord.sys.mjsPK
!<�[x�@�@�(���modules/shared/CreditCardRuleset.sys.mjsPK
!<���?T"T"#���4modules/shared/FieldScanner.sys.mjsPK
!<
�n}|}|*��#Wmodules/shared/FormAutofillHandler.sys.mjsPK
!<���ˋˋ-����modules/shared/FormAutofillHeuristics.sys.mjsPK
!<�^`^����(���_modules/shared/FormAutofillUtils.sys.mjsPK
!</�		'���modules/shared/FormStateManager.sys.mjsPK
!<}�~�Y�Y'��Amodules/shared/HeuristicsRegExp.sys.mjsPK
!<�_��##!��Pomodules/shared/LabelUtils.sys.mjsPK
!<���8��'���}modules/shared/LoginFormFactory.sys.mjsPK
!<�'l��f�f'����modules/shared/NewPasswordModel.sys.mjsPK
!<�l�,,(����modules/shared/PasswordGenerator.sys.mjsPK
!<m��'�I�I*��*modules/shared/PasswordRulesParser.sys.mjsPK
!<�ˇ��,��pdmodules/shared/PhoneNumberNormalizer.sys.mjsPK
!<�ޙ^>S>S,��okmodules/subprocess/subprocess_common.sys.mjsPK
!<��	/��'����modules/subprocess/subprocess_shared.jsPK
!<�J[��	�	,��
�modules/subprocess/subprocess_shared_unix.jsPK
!<2��0��*��?�modules/subprocess/subprocess_unix.sys.mjsPK
!<�/~�<�<,��}�modules/subprocess/subprocess_unix.worker.jsPK
!<��||.��Q&modules/subprocess/subprocess_worker_common.jsPK
!<�0��&�&�%��:modules/third_party/fathom/fathom.mjsPK
!<b�L@@,����modules/translation/LanguageDetector.sys.mjsPK
!<
�=A�_�_!���modules/translation/cld-worker.jsPK
!<`ɖ/�/�%��15 modules/translation/cld-worker.js.memPK
!<b���%����0modules/txEXSLTRegExFunctions.sys.mjsPK
!<wܕ|3�3�����0modules/vtt.sys.mjsPK
!< �+��!���1modules/workers/PromiseWorker.mjsPK
!<~�!|�	�	'��J�1actors/AboutHttpsOnlyErrorChild.sys.mjsPK
!<��X'��(���1actors/AboutHttpsOnlyErrorParent.sys.mjsPK
!<��O��%���2actors/AboutTranslationsChild.sys.mjsPK
!<��#

&��2actors/AboutTranslationsParent.sys.mjsPK
!<�Ov,��!��`%2actors/AudioPlaybackChild.sys.mjsPK
!<��ȄGG"��m(2actors/AudioPlaybackParent.sys.mjsPK
!<��/:l)l) ���-2actors/AutoCompleteChild.sys.mjsPK
!<�%bNbN!���W2actors/AutoCompleteParent.sys.mjsPK
!<p+��w2w2��?�2actors/AutoScrollChild.sys.mjsPK
!<9��������2actors/AutoScrollParent.sys.mjsPK
!<[~=������2actors/AutoplayChild.sys.mjsPK
!<�IA����v�2actors/AutoplayParent.sys.mjsPK
!<A^�
�
(��\�2actors/BackgroundThumbnailsChild.sys.mjsPK
!<����"��8�2actors/BrowserElementChild.sys.mjsPK
!<�j<[}}��"�2actors/ContentMetaChild.sys.mjsPK
!<��*LL���3actors/ControllersChild.sys.mjsPK
!<�s(�*	*	 ��e3actors/ControllersParent.sys.mjsPK
!<P;�CdCd ��� 3actors/CookieBannerChild.sys.mjsPK
!<_����!��N�3actors/CookieBannerParent.sys.mjsPK
!<��S��"��,�3actors/DateTimePickerChild.sys.mjsPK
!<���

#��!�3actors/DateTimePickerParent.sys.mjsPK
!<�[i����l�3actors/ExtFindChild.sys.mjsPK
!<�/Z����m�3actors/FindBarChild.sys.mjsPK
!<�{�����2�3actors/FindBarParent.sys.mjsPK
!<R<�n�
�
���3actors/FinderChild.sys.mjsPK
!<��<D�9�9����3actors/FormHandlerChild.sys.mjsPK
!<Q�ψBB���-4actors/FormHistoryChild.sys.mjsPK
!<ī~� ��ZK4actors/FormHistoryParent.sys.mjsPK
!<�p����&���_4actors/InlineSpellCheckerChild.sys.mjsPK
!<n�����'���d4actors/InlineSpellCheckerParent.sys.mjsPK
!<���Vkk-���j4actors/KeyPressEventModelCheckerChild.sys.mjsPK
!<�(2wDwD���y4actors/MLEngineChild.sys.mjsPK
!<�	��gg��9�4actors/MLEngineParent.sys.mjsPK
!<��-�AA���%5actors/MegalistChild.sys.mjsPK
!<�u
������'5actors/MegalistParent.sys.mjsPK
!<�3�����,+5actors/NetErrorChild.sys.mjsPK
!<(�MQ-Q-��QH5actors/NetErrorParent.sys.mjsPK
!<
6����$���u5actors/PictureInPictureChild.sys.mjsPK
!<�FY

!���!7actors/PopupBlockingChild.sys.mjsPK
!<�=�&�!�!"��17actors/PopupBlockingParent.sys.mjsPK
!<�V,"%"%��S7actors/PrintingChild.sys.mjsPK
!<�ˑ����jx7actors/PrintingParent.sys.mjsPK
!<yg
�zz%���{7actors/PrintingSelectionChild.sys.mjsPK
!<��5��'��W~7actors/PurgeSessionHistoryChild.sys.mjsPK
!</O9�����S�7actors/RemotePageChild.sys.mjsPK
!<�͵�.�.$���7actors/ReportBrokenSiteChild.sys.mjsPK
!<�z6�P"P"%��A�7actors/ReportBrokenSiteParent.sys.mjsPK
!<�Tb<b<����7actors/SelectChild.sys.mjsPK
!<�=�Vee��n*8actors/SelectParent.sys.mjsPK
!<��
p

����8actors/ThumbnailsChild.sys.mjsPK
!< g�4WW ���8actors/TranslationsChild.sys.mjsPK
!<���iII&����8actors/TranslationsEngineChild.sys.mjsPK
!<��I���'��'�8actors/TranslationsEngineParent.sys.mjsPK
!<`������!����8actors/TranslationsParent.sys.mjsPK
!<'�Urr��+�:actors/UAWidgetsChild.sys.mjsPK
!<'^�-mm&��ؼ:actors/UnselectedTabHoverChild.sys.mjsPK
!<'QLG@@'����:actors/UnselectedTabHoverParent.sys.mjsPK
!<��
�
'���:actors/UserCharacteristicsChild.sys.mjsPK
!<>�>�YY(���:actors/UserCharacteristicsParent.sys.mjsPK
!<�m?�AA1����:actors/UserCharacteristicsWindowInfoChild.sys.mjsPK
!<��h�7070��N�:actors/ViewSourceChild.sys.mjsPK
!<J�"�;�;"���;actors/ViewSourcePageChild.sys.mjsPK
!<��ݐ��#���W;actors/ViewSourcePageParent.sys.mjsPK
!<�nx==���i;actors/WebChannelChild.sys.mjsPK
!<��(�
�
��{;actors/WebChannelParent.sys.mjsPK
!<�h	���=����;chrome/toolkit/content/extensions/child/ext-backgroundPage.jsPK
!<s��Uzz=��G�;chrome/toolkit/content/extensions/child/ext-contentScripts.jsPK
!<�!JJD���;chrome/toolkit/content/extensions/child/ext-declarativeNetRequest.jsPK
!<y�6))8��Ȝ;chrome/toolkit/content/extensions/child/ext-extension.jsPK
!<?�C�
�
7��G�;chrome/toolkit/content/extensions/child/ext-identity.jsPK
!<�f���6��%�;chrome/toolkit/content/extensions/child/ext-runtime.jsPK
!<$L8��q�;chrome/toolkit/content/extensions/child/ext-scripting.jsPK
!<�8 Յ.�.6����;chrome/toolkit/content/extensions/child/ext-storage.jsPK
!<a�ν�+�+3����;chrome/toolkit/content/extensions/child/ext-test.jsPK
!<��f

6���#<chrome/toolkit/content/extensions/child/ext-toolkit.jsPK
!<i��O3O3B��4.<chrome/toolkit/content/extensions/child/ext-userScripts-content.jsPK
!<
4H��:���a<chrome/toolkit/content/extensions/child/ext-userScripts.jsPK
!<��S���9��)x<chrome/toolkit/content/extensions/child/ext-webRequest.jsPK
!<�G��!�!8��^�<chrome/toolkit/content/extensions/ext-browser-content.jsPK
!<ye>2��U�<chrome/toolkit/content/extensions/ext-toolkit.jsonPK
!<N����;����<chrome/toolkit/content/extensions/parent/ext-activityLog.jsPK
!<Q�����6����<chrome/toolkit/content/extensions/parent/ext-alarms.jsPK
!<REW�EBEB?��2�<chrome/toolkit/content/extensions/parent/ext-browserSettings.jsPK
!<��$�1�1<��� =chrome/toolkit/content/extensions/parent/ext-browsingData.jsPK
!<���33=���R=chrome/toolkit/content/extensions/parent/ext-captivePortal.jsPK
!<;3��s
s
9��Jc=chrome/toolkit/content/extensions/parent/ext-clipboard.jsPK
!<���i��>��q=chrome/toolkit/content/extensions/parent/ext-contentScripts.jsPK
!</��W7'7'D���=chrome/toolkit/content/extensions/parent/ext-contextualIdentities.jsPK
!<nZ�wZwZ7����=chrome/toolkit/content/extensions/parent/ext-cookies.jsPK
!<�njE��|>chrome/toolkit/content/extensions/parent/ext-declarativeNetRequest.jsPK
!<��-�3���(>chrome/toolkit/content/extensions/parent/ext-dns.jsPK
!<V�*����9��?5>chrome/toolkit/content/extensions/parent/ext-downloads.jsPK
!<�,qyKK9��4�>chrome/toolkit/content/extensions/parent/ext-extension.jsPK
!<�UFJ=����>chrome/toolkit/content/extensions/parent/ext-geckoProfiler.jsPK
!<�2j�884��G�>chrome/toolkit/content/extensions/parent/ext-i18n.jsPK
!<�h�8����>chrome/toolkit/content/extensions/parent/ext-identity.jsPK
!<טI�8
8
4��5�>chrome/toolkit/content/extensions/parent/ext-idle.jsPK
!<K���f'f':���?chrome/toolkit/content/extensions/parent/ext-management.jsPK
!<S�Y.��=��})?chrome/toolkit/content/extensions/parent/ext-networkStatus.jsPK
!<D�kBB=���2?chrome/toolkit/content/extensions/parent/ext-notifications.jsPK
!<RDmL��;��lG?chrome/toolkit/content/extensions/parent/ext-permissions.jsPK
!<�-��7�77���]?chrome/toolkit/content/extensions/parent/ext-privacy.jsPK
!<̔�b��@���?chrome/toolkit/content/extensions/parent/ext-protocolHandlers.jsPK
!<=���+�+5��-�?chrome/toolkit/content/extensions/parent/ext-proxy.jsPK
!<�m�U..7��`�?chrome/toolkit/content/extensions/parent/ext-storage.jsPK
!<�Fzl9����?chrome/toolkit/content/extensions/parent/ext-telemetry.jsPK
!<�>�
��;��.@chrome/toolkit/content/extensions/parent/ext-userScripts.jsPK
!<�lt�Q Q =��w*@chrome/toolkit/content/extensions/parent/ext-webNavigation.jsPK
!<ɔ���;��#K@chrome/toolkit/content/extensions/schemas/activity_log.jsonPK
!<▎�5��X@chrome/toolkit/content/extensions/schemas/alarms.jsonPK
!<+�'CC=���l@chrome/toolkit/content/extensions/schemas/browser_action.jsonPK
!<���5��?��b�@chrome/toolkit/content/extensions/schemas/browser_settings.jsonPK
!<��~�3�3<����@chrome/toolkit/content/extensions/schemas/browsing_data.jsonPK
!<�	�A
	
	=����@chrome/toolkit/content/extensions/schemas/captive_portal.jsonPK
!<����8��`Achrome/toolkit/content/extensions/schemas/clipboard.jsonPK
!<Y�d!��>���Achrome/toolkit/content/extensions/schemas/content_scripts.jsonPK
!<��I���D���Achrome/toolkit/content/extensions/schemas/contextual_identities.jsonPK
!<q�gNN6���5Achrome/toolkit/content/extensions/schemas/cookies.jsonPK
!<~p�7>~>~F��2�Achrome/toolkit/content/extensions/schemas/declarative_net_request.jsonPK
!<����kk2���Bchrome/toolkit/content/extensions/schemas/dns.jsonPK
!<�T)�r�r8���
Bchrome/toolkit/content/extensions/schemas/downloads.jsonPK
!<vb��4�45���}Bchrome/toolkit/content/extensions/schemas/events.jsonPK
!<&A��
�
:����Bchrome/toolkit/content/extensions/schemas/experiments.jsonPK
!<(����8��ǽBchrome/toolkit/content/extensions/schemas/extension.jsonPK
!<[E�}r	r	J����Bchrome/toolkit/content/extensions/schemas/extension_protocol_handlers.jsonPK
!<�k?��>����Bchrome/toolkit/content/extensions/schemas/extension_types.jsonPK
!<�:L00<���Bchrome/toolkit/content/extensions/schemas/geckoProfiler.jsonPK
!<�ƃ��3��vCchrome/toolkit/content/extensions/schemas/i18n.jsonPK
!<MTt��7���*Cchrome/toolkit/content/extensions/schemas/identity.jsonPK
!<���JJ3���ACchrome/toolkit/content/extensions/schemas/idle.jsonPK
!<�#�ˬ-�-9�� JCchrome/toolkit/content/extensions/schemas/management.jsonPK
!<�鑦�Y�Y7��#xCchrome/toolkit/content/extensions/schemas/manifest.jsonPK
!<];����>��s�Cchrome/toolkit/content/extensions/schemas/native_manifest.jsonPK
!<���=��v�Cchrome/toolkit/content/extensions/schemas/network_status.jsonPK
!<�s���2�2<����Cchrome/toolkit/content/extensions/schemas/notifications.jsonPK
!<�ǰ��*�*:��
Dchrome/toolkit/content/extensions/schemas/page_action.jsonPK
!<,�j

:��A>Dchrome/toolkit/content/extensions/schemas/permissions.jsonPK
!<�6_N{ { 6���LDchrome/toolkit/content/extensions/schemas/privacy.jsonPK
!<k:����4��rmDchrome/toolkit/content/extensions/schemas/proxy.jsonPK
!<�Yd����6����Dchrome/toolkit/content/extensions/schemas/runtime.jsonPK
!<C��Q�8�88��Echrome/toolkit/content/extensions/schemas/scripting.jsonPK
!<�L=ӸD�D6���JEchrome/toolkit/content/extensions/schemas/storage.jsonPK
!<�Mx��:�:8���Echrome/toolkit/content/extensions/schemas/telemetry.jsonPK
!<�I���3����Echrome/toolkit/content/extensions/schemas/test.jsonPK
!<��E��4�44��$�Echrome/toolkit/content/extensions/schemas/theme.jsonPK
!<p�$�4��Fchrome/toolkit/content/extensions/schemas/types.jsonPK
!<�q'���;��k2Fchrome/toolkit/content/extensions/schemas/user_scripts.jsonPK
!<���VttC��gCFchrome/toolkit/content/extensions/schemas/user_scripts_content.jsonPK
!<~��iViV=��<KFchrome/toolkit/content/extensions/schemas/web_navigation.jsonPK
!<��Id�d�:���Fchrome/toolkit/content/extensions/schemas/web_request.jsonPK
!<�}��6����Gchrome/toolkit/content/global/TopLevelVideoDocument.jsPK
!<�U��LL-���Gchrome/toolkit/content/global/aboutAbout.htmlPK
!<R��+����Gchrome/toolkit/content/global/aboutAbout.jsPK
!<Q���3��֥Gchrome/toolkit/content/global/aboutCheckerboard.cssPK
!<��V�m	m	4��
�Gchrome/toolkit/content/global/aboutCheckerboard.htmlPK
!<O���    2��ɲGchrome/toolkit/content/global/aboutCheckerboard.jsPK
!<H6L��,��9�Gchrome/toolkit/content/global/aboutGlean.cssPK
!<��3 $$-���Gchrome/toolkit/content/global/aboutGlean.htmlPK
!<�[�TMM+����Gchrome/toolkit/content/global/aboutGlean.jsPK
!<�0�@@/�� �Gchrome/toolkit/content/global/aboutLogging.htmlPK
!<S�SbSb-���Hchrome/toolkit/content/global/aboutLogging.jsPK
!<�����-��KuHchrome/toolkit/content/global/aboutMemory.cssPK
!<.���'�',����Hchrome/toolkit/content/global/aboutMemory.jsPK
!<N�����/����Ichrome/toolkit/content/global/aboutMemory.xhtmlPK
!<�D���.��ڰIchrome/toolkit/content/global/aboutMozilla.cssPK
!<��}�Z Z 0���Ichrome/toolkit/content/global/aboutNetError.htmlPK
!<��p����/����Ichrome/toolkit/content/global/aboutNetError.mjsPK
!<�_֊b*b*2����Jchrome/toolkit/content/global/aboutNetworking.htmlPK
!<!'HI5I50��2�Jchrome/toolkit/content/global/aboutNetworking.jsPK
!<᳞�WW0���Kchrome/toolkit/content/global/aboutProcesses.cssPK
!<[��R1��nKchrome/toolkit/content/global/aboutProcesses.htmlPK
!<H�c���/���"Kchrome/toolkit/content/global/aboutProcesses.jsPK
!<�~̿(�(.���Kchrome/toolkit/content/global/aboutProfiles.jsPK
!<�"��551��
Lchrome/toolkit/content/global/aboutProfiles.xhtmlPK
!<Ͷ�yss,���Lchrome/toolkit/content/global/aboutRights.jsPK
!<_�ff/��OLchrome/toolkit/content/global/aboutRights.xhtmlPK
!<�����4��,Lchrome/toolkit/content/global/aboutServiceWorkers.jsPK
!<��8��7���?Lchrome/toolkit/content/global/aboutServiceWorkers.xhtmlPK
!<���R�R�-���FLchrome/toolkit/content/global/aboutSupport.jsPK
!<�7��!l!l0��|FMchrome/toolkit/content/global/aboutSupport.xhtmlPK
!<������0���Mchrome/toolkit/content/global/aboutTelemetry.cssPK
!<�r�0�0/���Mchrome/toolkit/content/global/aboutTelemetry.jsPK
!<c�O�+�+2���Nchrome/toolkit/content/global/aboutTelemetry.xhtmlPK
!<�[���4��1&Ochrome/toolkit/content/global/aboutUrlClassifier.cssPK
!</��R�R3��)Ochrome/toolkit/content/global/aboutUrlClassifier.jsPK
!<�pdnn6��P|Ochrome/toolkit/content/global/aboutUrlClassifier.xhtmlPK
!<�]ؕ��/���Ochrome/toolkit/content/global/aboutWebauthn.cssPK
!<��P��%�%0��%�Ochrome/toolkit/content/global/aboutWebauthn.htmlPK
!<�&�=bvbv.��A�Ochrome/toolkit/content/global/aboutWebauthn.jsPK
!<��,EE9���7Pchrome/toolkit/content/global/aboutconfig/aboutconfig.cssPK
!<��F``:���RPchrome/toolkit/content/global/aboutconfig/aboutconfig.htmlPK
!<>�ފU�U8��C_Pchrome/toolkit/content/global/aboutconfig/aboutconfig.jsPK
!<K���8��#�Pchrome/toolkit/content/global/aboutconfig/background.svgPK
!<��8��4��@�Pchrome/toolkit/content/global/aboutconfig/toggle.svgPK
!<��&&9����Pchrome/toolkit/content/global/aboutwebrtc/aboutWebrtc.cssPK
!<���==:���Pchrome/toolkit/content/global/aboutwebrtc/aboutWebrtc.htmlPK
!<稒�w�w�9����Pchrome/toolkit/content/global/aboutwebrtc/aboutWebrtc.mjsPK
!<M,x��?��i�Qchrome/toolkit/content/global/aboutwebrtc/configurationList.mjsPK
!<B�"���8����Qchrome/toolkit/content/global/aboutwebrtc/copyButton.mjsPK
!<���Oy
y
8����Qchrome/toolkit/content/global/aboutwebrtc/disclosure.mjsPK
!<������3����Qchrome/toolkit/content/global/aboutwebrtc/graph.mjsPK
!<$߹��5���	Rchrome/toolkit/content/global/aboutwebrtc/graphdb.mjsPK
!<������0���!Rchrome/toolkit/content/global/adjustableTitle.jsPK
!<%(.�.���7Rchrome/toolkit/content/global/alerts/alert.cssPK
!<С=�*1*1-��9;Rchrome/toolkit/content/global/alerts/alert.jsPK
!<fRX��	�	0���lRchrome/toolkit/content/global/alerts/alert.xhtmlPK
!<��fSS*���vRchrome/toolkit/content/global/appPicker.jsPK
!<�
D�YY-����Rchrome/toolkit/content/global/appPicker.xhtmlPK
!<�Wa8��*�Rchrome/toolkit/content/global/backgroundPageThumbs.xhtmlPK
!<9N��`B`B2����Rchrome/toolkit/content/global/bindings/calendar.jsPK
!<j j��/�/4��?�Rchrome/toolkit/content/global/bindings/datekeeper.jsPK
!<pӏ!F!F4��SSchrome/toolkit/content/global/bindings/datepicker.jsPK
!<�G�\��6���QSchrome/toolkit/content/global/bindings/datetimebox.cssPK
!<��&+V+V1���ZSchrome/toolkit/content/global/bindings/spinner.jsPK
!<�Mݛ/�/4��[�Schrome/toolkit/content/global/bindings/timekeeper.jsPK
!<�iI�!�!4��H�Schrome/toolkit/content/global/bindings/timepicker.jsPK
!<��/�00-��sTchrome/toolkit/content/global/buildconfig.cssPK
!<�E]��
�
.���Tchrome/toolkit/content/global/buildconfig.htmlPK
!<Θpj��8��Tchrome/toolkit/content/global/certviewer/certDecoder.mjsPK
!<X��v��7��O�Tchrome/toolkit/content/global/certviewer/certviewer.cssPK
!<�O�
�
8����Tchrome/toolkit/content/global/certviewer/certviewer.htmlPK
!<���/�/7��w�Tchrome/toolkit/content/global/certviewer/certviewer.mjsPK
!<��OB��O����Tchrome/toolkit/content/global/certviewer/components/about-certificate-items.mjsPK
!<�QM��Q����Tchrome/toolkit/content/global/certviewer/components/about-certificate-section.cssPK
!<vg��Q���Tchrome/toolkit/content/global/certviewer/components/about-certificate-section.mjsPK
!<����K��q�Tchrome/toolkit/content/global/certviewer/components/certificate-section.cssPK
!<�*���K���Uchrome/toolkit/content/global/certviewer/components/certificate-section.mjsPK
!<7�UUP���Uchrome/toolkit/content/global/certviewer/components/certificate-tabs-section.mjsPK
!<�����E���Uchrome/toolkit/content/global/certviewer/components/error-section.cssPK
!< j���E���"Uchrome/toolkit/content/global/certviewer/components/error-section.mjsPK
!<V�
���L���&Uchrome/toolkit/content/global/certviewer/components/info-group-container.mjsPK
!<���,,B���/Uchrome/toolkit/content/global/certviewer/components/info-group.cssPK
!<4,�}�
�
B��Z3Uchrome/toolkit/content/global/certviewer/components/info-group.mjsPK
!<�t5A��Z>Uchrome/toolkit/content/global/certviewer/components/info-item.cssPK
!<��[���A���CUchrome/toolkit/content/global/certviewer/components/info-item.mjsPK
!<^
���A���XUchrome/toolkit/content/global/certviewer/components/list-item.cssPK
!<;�ssA���\Uchrome/toolkit/content/global/certviewer/components/list-item.mjsPK
!<��3KK=���dUchrome/toolkit/content/global/certviewer/components/utils.mjsPK
!<ډc��8��ZhUchrome/toolkit/content/global/certviewer/vendor/pkijs.jsPK
!<�y��~
~
.���5dchrome/toolkit/content/global/commonDialog.cssPK
!<	���

-���@dchrome/toolkit/content/global/commonDialog.jsPK
!<�4�
�
0���[dchrome/toolkit/content/global/commonDialog.xhtmlPK
!<ZA�2����1��1jdchrome/toolkit/content/global/contentAreaUtils.jsPK
!<G����H��.echrome/toolkit/content/global/cookiebanners/CookieBannerRule.schema.jsonPK
!<�Rp�nn)��sechrome/toolkit/content/global/crashes.cssPK
!<�y��
�
*��(!echrome/toolkit/content/global/crashes.htmlPK
!<�;���'�'(��R,echrome/toolkit/content/global/crashes.jsPK
!<�xuzuz/��kTechrome/toolkit/content/global/customElements.jsPK
!<��Y�ee.��-�echrome/toolkit/content/global/datepicker.xhtmlPK
!<����EE0����echrome/toolkit/content/global/editMenuOverlay.jsPK
!<Z�1a1a8��q�echrome/toolkit/content/global/elements/arrowscrollbox.jsPK
!<^�X�B�B<���Pfchrome/toolkit/content/global/elements/autocomplete-input.jsPK
!<�^}N�Q�Q<���fchrome/toolkit/content/global/elements/autocomplete-popup.jsPK
!<:m\�v�vC����fchrome/toolkit/content/global/elements/autocomplete-richlistitem.jsPK
!<:!�y��@��]gchrome/toolkit/content/global/elements/browser-custom-element.jsPK
!<~�g%##0��oFhchrome/toolkit/content/global/elements/button.jsPK
!<_�,[		2���ihchrome/toolkit/content/global/elements/checkbox.jsPK
!<��>�k�k�5��shchrome/toolkit/content/global/elements/datetimebox.jsPK
!<E�f4C4C0���ichrome/toolkit/content/global/elements/dialog.jsPK
!<�SR��0��[`ichrome/toolkit/content/global/elements/editor.jsPK
!<�l�����1���vichrome/toolkit/content/global/elements/findbar.jsPK
!<�#A���1���'jchrome/toolkit/content/global/elements/general.jsPK
!<;��2���+jchrome/toolkit/content/global/elements/marquee.cssPK
!<�
�s�%�%1��-.jchrome/toolkit/content/global/elements/marquee.jsPK
!<_�;">">.�� Tjchrome/toolkit/content/global/elements/menu.jsPK
!<b�,�,2����jchrome/toolkit/content/global/elements/menulist.jsPK
!<"�hz�$�$3��׿jchrome/toolkit/content/global/elements/menupopup.jsPK
!<�Uң��;���jchrome/toolkit/content/global/elements/moz-button-group.mjsPK
!<
�]���3��K�jchrome/toolkit/content/global/elements/moz-card.cssPK
!<���`��3��5kchrome/toolkit/content/global/elements/moz-card.mjsPK
!<����7��pkchrome/toolkit/content/global/elements/moz-checkbox.cssPK
!<A����7��ekchrome/toolkit/content/global/elements/moz-checkbox.mjsPK
!<�qq7���*kchrome/toolkit/content/global/elements/moz-fieldset.cssPK
!<�� T<<7��K-kchrome/toolkit/content/global/elements/moz-fieldset.mjsPK
!<k�$~~8���2kchrome/toolkit/content/global/elements/moz-five-star.cssPK
!<��Is��8���7kchrome/toolkit/content/global/elements/moz-five-star.mjsPK
!<�.<l��7���?kchrome/toolkit/content/global/elements/moz-input-box.jsPK
!<g{� ��>��_kchrome/toolkit/content/global/elements/moz-page-nav-button.cssPK
!<��K��7��Okkchrome/toolkit/content/global/elements/moz-page-nav.cssPK
!<j�'��7���tkchrome/toolkit/content/global/elements/moz-page-nav.mjsPK
!<ɿQE@)@):���kchrome/toolkit/content/global/elements/moz-radio-group.mjsPK
!<"4@ܨ�4��~�kchrome/toolkit/content/global/elements/moz-radio.cssPK
!<q.�\\;��x�kchrome/toolkit/content/global/elements/moz-support-link.mjsPK
!<�]6
��5��-�kchrome/toolkit/content/global/elements/moz-toggle.cssPK
!<��y��5��#�kchrome/toolkit/content/global/elements/moz-toggle.mjsPK
!<��:�k�k9���kchrome/toolkit/content/global/elements/notificationbox.jsPK
!<+_���5���blchrome/toolkit/content/global/elements/panel-item.cssPK
!<<�J1��5���jlchrome/toolkit/content/global/elements/panel-list.cssPK
!<�n�]MiMi4��<plchrome/toolkit/content/global/elements/panel-list.jsPK
!<�M�U�"�"/����lchrome/toolkit/content/global/elements/panel.jsPK
!<z��;���lchrome/toolkit/content/global/elements/popupnotification.jsPK
!<=V�X==/���mchrome/toolkit/content/global/elements/radio.jsPK
!<���,�u�u5��)Vmchrome/toolkit/content/global/elements/richlistbox.jsPK
!<&�(���8��y�mchrome/toolkit/content/global/elements/search-textbox.jsPK
!<���6����mchrome/toolkit/content/global/elements/stringbundle.jsPK
!<�g��^�^0���mchrome/toolkit/content/global/elements/tabbox.jsPK
!<|jA�~+~+.���Mnchrome/toolkit/content/global/elements/text.jsPK
!<r�xI�'�'9���ynchrome/toolkit/content/global/elements/textrecognition.jsPK
!<�qw���7���nchrome/toolkit/content/global/elements/toolbarbutton.jsPK
!<f����.��0�nchrome/toolkit/content/global/elements/tree.jsPK
!<tfsj����7���zochrome/toolkit/content/global/elements/videocontrols.jsPK
!<ew�ݢJ�J0���Cqchrome/toolkit/content/global/elements/wizard.jsPK
!<�#�<<3����qchrome/toolkit/content/global/filepicker.propertiesPK
!<�b44.���qchrome/toolkit/content/global/globalOverlay.jsPK
!<oO��<
<
7����qchrome/toolkit/content/global/gmp-sources/openh264.jsonPK
!<A��
�
:��'�qchrome/toolkit/content/global/gmp-sources/widevinecdm.jsonPK
!<~��--=���qchrome/toolkit/content/global/gmp-sources/widevinecdm_l1.jsonPK
!<��-�;
;
;����qchrome/toolkit/content/global/httpsonlyerror/errorpage.htmlPK
!<�2���9���qchrome/toolkit/content/global/httpsonlyerror/errorpage.jsPK
!<.3.��>���qchrome/toolkit/content/global/httpsonlyerror/secure-broken.svgPK
!<m��6]�]�*��!�qchrome/toolkit/content/global/license.htmlPK
!<��..+��ƌvchrome/toolkit/content/global/lit-utils.mjsPK
!<���/rr8��=�vchrome/toolkit/content/global/megalist/MegalistAlpha.mjsPK
!<v��B��7���vchrome/toolkit/content/global/megalist/PasswordCard.mjsPK
!<3	����3����vchrome/toolkit/content/global/megalist/megalist.cssPK
!<ڳ�O��4����vchrome/toolkit/content/global/megalist/megalist.htmlPK
!<�G(�.���wchrome/toolkit/content/global/ml/MLEngine.htmlPK
!<��3�QQ4��E	wchrome/toolkit/content/global/ml/MLEngine.worker.mjsPK
!<��75v�v�1���wchrome/toolkit/content/global/ml/ModelHub.sys.mjsPK
!<i�
�U6U61����wchrome/toolkit/content/global/ml/ONNXPipeline.mjsPK
!<��{+{+.��Qxchrome/toolkit/content/global/ml/Utils.sys.mjsPK
!<�����'��,xchrome/toolkit/content/global/ml/ort.jsPK
!<���pXpX0��^��chrome/toolkit/content/global/ml/transformers.jsPK
!<.D--*���chrome/toolkit/content/global/mozilla.htmlPK
!<�Ѥ�K/K/<����chrome/toolkit/content/global/neterror/aboutNetErrorCodes.jsPK
!<�fԑ � N��6A�chrome/toolkit/content/global/neterror/supportpages/connection-not-secure.htmlPK
!<���Fv*v*D��3b�chrome/toolkit/content/global/neterror/supportpages/time-errors.htmlPK
!<��1��*����chrome/toolkit/content/global/notfound.wavPK
!<��
����8��ᒅchrome/toolkit/content/global/pictureinpicture/player.jsPK
!<mg�;���:�chrome/toolkit/content/global/pictureinpicture/player.xhtmlPK
!<��\�L�L4��PS�chrome/toolkit/content/global/preferencesBindings.jsPK
!<UJw��'��b��chrome/toolkit/content/global/print.cssPK
!<�\��G�G(�����chrome/toolkit/content/global/print.htmlPK
!<�0��c�c&�����chrome/toolkit/content/global/print.jsPK
!<[�|��1���a�chrome/toolkit/content/global/printPagination.cssPK
!<�v���.��o�chrome/toolkit/content/global/printPreview.cssPK
!<��jLL7��v�chrome/toolkit/content/global/printPreviewPagination.jsPK
!<����KpKp+�����chrome/toolkit/content/global/printUtils.jsPK
!<�K�Bs*s*5��P��chrome/toolkit/content/global/reader/aboutReader.htmlPK
!<x){VV4��*�chrome/toolkit/content/global/reader/color-input.cssPK
!</�P4���.�chrome/toolkit/content/global/reader/color-input.mjsPK
!<��n-��3��7�chrome/toolkit/content/global/reader/moz-slider.cssPK
!<QQKK3���:�chrome/toolkit/content/global/reader/moz-slider.mjsPK
!<@��###.���I�chrome/toolkit/content/global/resetProfile.cssPK
!<�G4u		-��K�chrome/toolkit/content/global/resetProfile.jsPK
!<.f�Z��0��TN�chrome/toolkit/content/global/resetProfile.xhtmlPK
!<���<��8��]W�chrome/toolkit/content/global/resetProfileProgress.xhtmlPK
!<�r�o##.���[�chrome/toolkit/content/global/selectDialog.cssPK
!<�3e���-���\�chrome/toolkit/content/global/selectDialog.jsPK
!<�xx0���e�chrome/toolkit/content/global/selectDialog.xhtmlPK
!<��))8���i�chrome/toolkit/content/global/shopping/ProductConfig.mjsPK
!<	�LD22?��3~�chrome/toolkit/content/global/shopping/ProductValidator.sys.mjsPK
!<��C�mSmS:��‚�chrome/toolkit/content/global/shopping/ShoppingProduct.mjsPK
!<�4<�DDC���։chrome/toolkit/content/global/shopping/analysis_request.schema.jsonPK
!<�� D��,ىchrome/toolkit/content/global/shopping/analysis_response.schema.jsonPK
!<����SSJ����chrome/toolkit/content/global/shopping/analysis_status_request.schema.jsonPK
!<� ��K��g�chrome/toolkit/content/global/shopping/analysis_status_response.schema.jsonPK
!<���yCCB����chrome/toolkit/content/global/shopping/analyze_request.schema.jsonPK
!<�.�C��L�chrome/toolkit/content/global/shopping/analyze_response.schema.jsonPK
!<4��F����chrome/toolkit/content/global/shopping/attribution_request.schema.jsonPK
!<(�A�@@G����chrome/toolkit/content/global/shopping/attribution_response.schema.jsonPK
!<4ΥSSJ�����chrome/toolkit/content/global/shopping/recommendations_request.schema.jsonPK
!<����K��@��chrome/toolkit/content/global/shopping/recommendations_response.schema.jsonPK
!<hL��RRD�����chrome/toolkit/content/global/shopping/reporting_request.schema.jsonPK
!<��F��E��E�chrome/toolkit/content/global/shopping/reporting_response.schema.jsonPK
!<������.��4�chrome/toolkit/content/global/simplifyMode.cssPK
!<�uP((2��8�chrome/toolkit/content/global/third_party/d3/d3.jsPK
!<����.���0�chrome/toolkit/content/global/timepicker.xhtmlPK
!<"�Y3		.��{6�chrome/toolkit/content/global/toggle-group.cssPK
!<���:�#�#9���?�chrome/toolkit/content/global/translations/Translator.mjsPK
!<E�Q�MxMxA���c�chrome/toolkit/content/global/translations/bergamot-translator.jsPK
!<m�~KF	F	H��lܐchrome/toolkit/content/global/translations/translations-document.sys.mjsPK
!<�Og��C���chrome/toolkit/content/global/translations/translations-engine.htmlPK
!<�UJk�M�MF���chrome/toolkit/content/global/translations/translations-engine.sys.mjsPK
!</�t_+V+VH��27�chrome/toolkit/content/global/translations/translations-engine.worker.jsPK
!<�G�h��;���chrome/toolkit/content/global/translations/translations.cssPK
!<�a��<��̝�chrome/toolkit/content/global/translations/translations.htmlPK
!<\�fITIT;��8��chrome/toolkit/content/global/translations/translations.mjsPK
!<�&���*����chrome/toolkit/content/global/treeUtils.jsPK
!<՟�����>����chrome/toolkit/content/global/usercharacteristics/gl-matrix.jsPK
!<}�oA��;����chrome/toolkit/content/global/usercharacteristics/ssdeep.jsPK
!<]��I����chrome/toolkit/content/global/usercharacteristics/usercharacteristics.cssPK
!<��B=KcKcJ��O��chrome/toolkit/content/global/usercharacteristics/usercharacteristics.htmlPK
!<'�K�+�+H��Z�chrome/toolkit/content/global/usercharacteristics/usercharacteristics.jsPK
!<W7V����0����chrome/toolkit/content/global/vendor/lit.all.mjsPK
!<f;Xf�7�70��M�chrome/toolkit/content/global/viewSourceUtils.jsPK
!<��t%��'��=��chrome/toolkit/content/global/win.xhtmlPK
!<98�~~4�����chrome/toolkit/content/global/xml/XMLPrettyPrint.cssPK
!<��G�EE4��Q��chrome/toolkit/content/global/xml/XMLPrettyPrint.xslPK
!<��!��A��蜘chrome/toolkit/content/mozapps/downloads/unknownContentType.xhtmlPK
!<@�T??>�����chrome/toolkit/content/mozapps/extensions/OpenH264-license.txtPK
!<Ɍ���A�A9�����chrome/toolkit/content/mozapps/extensions/aboutaddons.cssPK
!<6���GgGg:����chrome/toolkit/content/mozapps/extensions/aboutaddons.htmlPK
!<t�����8��{h�chrome/toolkit/content/mozapps/extensions/aboutaddons.jsPK
!<�s�8ee>��cZ�chrome/toolkit/content/mozapps/extensions/aboutaddonsCommon.jsPK
!<��:��$w�chrome/toolkit/content/mozapps/extensions/abuse-reports.jsPK
!<�`@?\\@���{�chrome/toolkit/content/mozapps/extensions/default-theme/icon.svgPK
!<������E��S~�chrome/toolkit/content/mozapps/extensions/default-theme/manifest.jsonPK
!<-<n��C��_��chrome/toolkit/content/mozapps/extensions/default-theme/preview.svgPK
!<�
�>>F��^��chrome/toolkit/content/mozapps/extensions/drag-drop-addon-installer.jsPK
!<rF�
�
�
7����chrome/toolkit/content/mozapps/extensions/shortcuts.cssPK
!<�	��E�E6�����chrome/toolkit/content/mozapps/extensions/shortcuts.jsPK
!<a.N��<���chrome/toolkit/content/mozapps/extensions/view-controller.jsPK
!<��0�.�.5����chrome/toolkit/content/mozapps/handling/appChooser.jsPK
!<W޲E��8���7�chrome/toolkit/content/mozapps/handling/appChooser.xhtmlPK
!<�Y3���@�chrome/toolkit/content/mozapps/handling/handler.cssPK
!<�,���;��F�chrome/toolkit/content/mozapps/handling/permissionDialog.jsPK
!<9|�OO>��(b�chrome/toolkit/content/mozapps/handling/permissionDialog.xhtmlPK
!<jp��6���h�chrome/toolkit/content/mozapps/preferences/changemp.jsPK
!<����9����chrome/toolkit/content/mozapps/preferences/changemp.xhtmlPK
!<�؎9%%9�����chrome/toolkit/content/mozapps/preferences/fontbuilder.jsPK
!<�)Z�6����chrome/toolkit/content/mozapps/preferences/removemp.jsPK
!<^��	9����chrome/toolkit/content/mozapps/preferences/removemp.xhtmlPK
!<����OO=��`��chrome/toolkit/content/mozapps/profile/createProfileWizard.jsPK
!<�j�@��
Ŝchrome/toolkit/content/mozapps/profile/createProfileWizard.xhtmlPK
!<d���((:��yМchrome/toolkit/content/mozapps/profile/profileDowngrade.jsPK
!<��;=���՜chrome/toolkit/content/mozapps/profile/profileDowngrade.xhtmlPK
!<' 
�i%i%:��sۜchrome/toolkit/content/mozapps/profile/profileSelection.jsPK
!<)U��
�
=��4�chrome/toolkit/content/mozapps/profile/profileSelection.xhtmlPK
!<̅P�n&n&/��7�chrome/toolkit/featuregates/FeatureGate.sys.mjsPK
!<"���bb=���2�chrome/toolkit/featuregates/FeatureGateImplementation.sys.mjsPK
!<}��(��2���P�chrome/toolkit/res/accessiblecaret-normal@1.5x.pngPK
!<ݪ���0���V�chrome/toolkit/res/accessiblecaret-normal@1x.pngPK
!<�j�~3���Z�chrome/toolkit/res/accessiblecaret-normal@2.25x.pngPK
!<����FF0��7c�chrome/toolkit/res/accessiblecaret-normal@2x.pngPK
!<A�G��5���k�chrome/toolkit/res/accessiblecaret-tilt-left@1.5x.pngPK
!<a����3���q�chrome/toolkit/res/accessiblecaret-tilt-left@1x.pngPK
!<�A<,,6���u�chrome/toolkit/res/accessiblecaret-tilt-left@2.25x.pngPK
!<,�9��3��s~�chrome/toolkit/res/accessiblecaret-tilt-left@2x.pngPK
!<#�G���6�����chrome/toolkit/res/accessiblecaret-tilt-right@1.5x.pngPK
!<N?xO��4�����chrome/toolkit/res/accessiblecaret-tilt-right@1x.pngPK
!<��
7��Ϗ�chrome/toolkit/res/accessiblecaret-tilt-right@2.25x.pngPK
!<�?���4��?��chrome/toolkit/res/accessiblecaret-tilt-right@2x.pngPK
!<Y�:�ll4��w��chrome/toolkit/res/autofill/FormAutofill.ios.sys.mjsPK
!<޶�t((0��5��chrome/toolkit/res/autofill/FormAutofill.sys.mjsPK
!<5�RXGG9���ʝchrome/toolkit/res/autofill/FormAutofillChild.ios.sys.mjsPK
!<����I�I5��<�chrome/toolkit/res/autofill/FormAutofillChild.sys.mjsPK
!<[Ml�QQ7��e.�chrome/toolkit/res/autofill/FormAutofillContent.sys.mjsPK
!<�ip��l�l6��7�chrome/toolkit/res/autofill/FormAutofillParent.sys.mjsPK
!<Z����8�8;��7��chrome/toolkit/res/autofill/FormAutofillPreferences.sys.mjsPK
!<X{s@I�I�8��<ݞchrome/toolkit/res/autofill/FormAutofillPrompter.sys.mjsPK
!<�~-~-4��ۂ�chrome/toolkit/res/autofill/FormAutofillSync.sys.mjsPK
!<���AA=�����chrome/toolkit/res/autofill/ProfileAutoCompleteResult.sys.mjsPK
!<��}T��#���chrome/toolkit/res/broken-image.pngPK
!<���MBB6����chrome/toolkit/res/messaging-system/lib/Logger.sys.mjsPK
!<��ު))?�����chrome/toolkit/res/messaging-system/targeting/Targeting.sys.mjsPK
!<"4ޡI�I/���chrome/toolkit/res/nimbus/ExperimentAPI.sys.mjsPK
!<_�]�]�1��[�chrome/toolkit/res/nimbus/FeatureManifest.sys.mjsPK
!<y�����7���D�chrome/toolkit/res/nimbus/lib/ExperimentManager.sys.mjsPK
!<�z$�::5����chrome/toolkit/res/nimbus/lib/ExperimentStore.sys.mjsPK
!<o�)�-�-6��L=�chrome/toolkit/res/nimbus/lib/PrefFlipsFeature.sys.mjsPK
!<�*��b�bD��tk�chrome/toolkit/res/nimbus/lib/RemoteSettingsExperimentLoader.sys.mjsPK
!<;m����3���Σchrome/toolkit/res/nimbus/lib/SharedDataMap.sys.mjsPK
!<$�!�!>���ߣchrome/toolkit/res/nimbus/schemas/NimbusEnrollment.schema.jsonPK
!<hY��7�7>��&�chrome/toolkit/res/nimbus/schemas/NimbusExperiment.schema.jsonPK
!<s*Q>��:�chrome/toolkit/res/nimbus/schemas/PrefFlipsFeature.schema.jsonPK
!<�� "$"$,��s?�chrome/toolkit/res/normandy/Normandy.sys.mjsPK
!<H=B�BB6���c�chrome/toolkit/res/normandy/NormandyMigrations.sys.mjsPK
!<��g	g	?��uw�chrome/toolkit/res/normandy/actions/AddonRollbackAction.sys.mjsPK
!<�kD|��>��9��chrome/toolkit/res/normandy/actions/AddonRolloutAction.sys.mjsPK
!<��Vu)u)6��t��chrome/toolkit/res/normandy/actions/BaseAction.sys.mjsPK
!<��HGG;��=Ƥchrome/toolkit/res/normandy/actions/BaseStudyAction.sys.mjsPK
!<��__D���ˤchrome/toolkit/res/normandy/actions/BranchedAddonStudyAction.sys.mjsPK
!<Q:Ϳcc<��G+�chrome/toolkit/res/normandy/actions/ConsoleLogAction.sys.mjsPK
!<2�+���E��.�chrome/toolkit/res/normandy/actions/MessagingExperimentAction.sys.mjsPK
!<��(#%%F��%2�chrome/toolkit/res/normandy/actions/PreferenceExperimentAction.sys.mjsPK
!<e`����D���W�chrome/toolkit/res/normandy/actions/PreferenceRollbackAction.sys.mjsPK
!<�8U� � C���c�chrome/toolkit/res/normandy/actions/PreferenceRolloutAction.sys.mjsPK
!<���U��?��-��chrome/toolkit/res/normandy/actions/ShowHeartbeatAction.sys.mjsPK
!<ӡ�e==9��O��chrome/toolkit/res/normandy/actions/schemas/index.sys.mjsPK
!<֢D��6���ޥchrome/toolkit/res/normandy/content/AboutPages.sys.mjsPK
!<��]��<�����chrome/toolkit/res/normandy/content/ShieldFrameChild.sys.mjsPK
!<�EE=����chrome/toolkit/res/normandy/content/ShieldFrameParent.sys.mjsPK
!<�t��mmC����chrome/toolkit/res/normandy/content/about-studies/about-studies.cssPK
!<�98���D���%�chrome/toolkit/res/normandy/content/about-studies/about-studies.htmlPK
!<�~�\->->B���*�chrome/toolkit/res/normandy/content/about-studies/about-studies.jsPK
!<�dc��
�
6��ai�chrome/toolkit/res/normandy/lib/ActionsManager.sys.mjsPK
!<�~Y���5���w�chrome/toolkit/res/normandy/lib/AddonRollouts.sys.mjsPK
!<f>��9�94��~��chrome/toolkit/res/normandy/lib/AddonStudies.sys.mjsPK
!<,h�/��6���ʦchrome/toolkit/res/normandy/lib/CleanupManager.sys.mjsPK
!<���
9���Ϧchrome/toolkit/res/normandy/lib/ClientEnvironment.sys.mjsPK
!<FL"w��4���ݦchrome/toolkit/res/normandy/lib/EventEmitter.sys.mjsPK
!<��j1j11����chrome/toolkit/res/normandy/lib/Heartbeat.sys.mjsPK
!<d��7����chrome/toolkit/res/normandy/lib/LegacyHeartbeat.sys.mjsPK
!<���WW2����chrome/toolkit/res/normandy/lib/LogManager.sys.mjsPK
!<j�l�VV<��.!�chrome/toolkit/res/normandy/lib/NormandyAddonManager.sys.mjsPK
!<TȸŻ�3���,�chrome/toolkit/res/normandy/lib/NormandyApi.sys.mjsPK
!<��8���5���@�chrome/toolkit/res/normandy/lib/NormandyUtils.sys.mjsPK
!<E����1���B�chrome/toolkit/res/normandy/lib/PrefUtils.sys.mjsPK
!<�?h�.�.�=��S�chrome/toolkit/res/normandy/lib/PreferenceExperiments.sys.mjsPK
!<�4���+�+:���ܧchrome/toolkit/res/normandy/lib/PreferenceRollouts.sys.mjsPK
!<��<aCUCU4����chrome/toolkit/res/normandy/lib/RecipeRunner.sys.mjsPK
!<v0�

9��O^�chrome/toolkit/res/normandy/lib/ShieldPreferences.sys.mjsPK
!<>����/���h�chrome/toolkit/res/normandy/lib/Storage.sys.mjsPK
!<�&�/��7���q�chrome/toolkit/res/normandy/lib/TelemetryEvents.sys.mjsPK
!<�M�Dm	m	.��u�chrome/toolkit/res/normandy/lib/Uptake.sys.mjsPK
!<@��	�	?���~�chrome/toolkit/res/normandy/schemas/LegacyHeartbeat.schema.jsonPK
!<Y&�5����chrome/toolkit/res/normandy/skin/shared/Heartbeat.cssPK
!<����
�
:�����chrome/toolkit/res/normandy/skin/shared/heartbeat-icon.svgPK
!<�o;!��>�����chrome/toolkit/res/normandy/skin/shared/heartbeat-star-lit.svgPK
!<�����>�����chrome/toolkit/res/normandy/skin/shared/heartbeat-star-off.svgPK
!<���Q�,�,5��r��chrome/toolkit/res/normandy/vendor/LICENSE_THIRDPARTYPK
!<�&(00/��|Ҩchrome/toolkit/res/normandy/vendor/PropTypes.jsPK
!<��3�U�U+���٨chrome/toolkit/res/normandy/vendor/React.jsPK
!<��~�=�=.��20�chrome/toolkit/res/normandy/vendor/ReactDOM.jsPK
!<�vg���0��	n�chrome/toolkit/res/normandy/vendor/classnames.jsPK
!<A|��..$��r�chrome/toolkit/res/password-hide.svgPK
!<Z�0���v�chrome/toolkit/res/password.svgPK
!<�+��__1���y�chrome/toolkit/skin/classic/global/aboutCache.cssPK
!<�B���6���~�chrome/toolkit/skin/classic/global/aboutCacheEntry.cssPK
!<���:�����chrome/toolkit/skin/classic/global/aboutHttpsOnlyError.cssPK
!<ғ3q??3����chrome/toolkit/skin/classic/global/aboutLicense.cssPK
!<1�X���3�����chrome/toolkit/skin/classic/global/aboutLogging.cssPK
!<��:A2�����chrome/toolkit/skin/classic/global/aboutMemory.cssPK
!<0/���4��ڑ�chrome/toolkit/skin/classic/global/aboutNetError.cssPK
!<&c����6���chrome/toolkit/skin/classic/global/aboutNetworking.cssPK
!<L-xB��2��3��chrome/toolkit/skin/classic/global/aboutReader.cssPK
!<4�b,,2��q,�chrome/toolkit/skin/classic/global/aboutRights.cssPK
!<3&3���.�chrome/toolkit/skin/classic/global/aboutSupport.cssPK
!<Ma���,��S=�chrome/toolkit/skin/classic/global/alert.cssPK
!<cd6^^0��2M�chrome/toolkit/skin/classic/global/appPicker.cssPK
!<YD/��@���O�chrome/toolkit/skin/classic/global/arrow/panelarrow-vertical.svgPK
!<�O�V��3���Q�chrome/toolkit/skin/classic/global/commonDialog.cssPK
!<9���&�&;���W�chrome/toolkit/skin/classic/global/datetimeinputpickers.cssPK
!<��yyA���~�chrome/toolkit/skin/classic/global/design-system/tokens-brand.cssPK
!<�g����-�����chrome/toolkit/skin/classic/global/dialog.cssPK
!<;�RT<�����chrome/toolkit/skin/classic/global/dirListing/dirListing.cssPK
!<~B
�TT8��
��chrome/toolkit/skin/classic/global/dirListing/folder.pngPK
!<ġ��{{4�����chrome/toolkit/skin/classic/global/dirListing/up.pngPK
!<bc�332�����chrome/toolkit/skin/classic/global/error-pages.cssPK
!<����;����chrome/toolkit/skin/classic/global/icons/Authentication.pngPK
!<�A��7����chrome/toolkit/skin/classic/global/icons/arrow-down.svgPK
!<�Z���:��b��chrome/toolkit/skin/classic/global/icons/arrow-left-12.svgPK
!<#�_}��;�����chrome/toolkit/skin/classic/global/icons/arrow-right-12.svgPK
!<�2���8�����chrome/toolkit/skin/classic/global/icons/arrow-right.svgPK
!<hn���8��9��chrome/toolkit/skin/classic/global/icons/arrow-up-12.svgPK
!<{*G���5�����chrome/toolkit/skin/classic/global/icons/arrow-up.svgPK
!<�b��B��ӻ�chrome/toolkit/skin/classic/global/icons/autoscroll-horizontal.svgPK
!<?����@��ྫྷchrome/toolkit/skin/classic/global/icons/autoscroll-vertical.svgPK
!<�\fߜ�7����chrome/toolkit/skin/classic/global/icons/autoscroll.svgPK
!<LW�t��7���Ŭchrome/toolkit/skin/classic/global/icons/badge-blue.svgPK
!<!�q�''4���Ǭchrome/toolkit/skin/classic/global/icons/blocked.svgPK
!<��R��9��Uʬchrome/toolkit/skin/classic/global/icons/check-filled.svgPK
!<�i����:���̬chrome/toolkit/skin/classic/global/icons/check-partial.svgPK
!<Ѓ�9,,2���άchrome/toolkit/skin/classic/global/icons/check.svgPK
!<u���6��SѬchrome/toolkit/skin/classic/global/icons/clipboard.svgPK
!<�o���7���Ԭchrome/toolkit/skin/classic/global/icons/close-fill.svgPK
!<�J:	��9���׬chrome/toolkit/skin/classic/global/icons/columnpicker.svgPK
!<��uu=���٬chrome/toolkit/skin/classic/global/icons/content-analysis.svgPK
!<�kџ�;���ެchrome/toolkit/skin/classic/global/icons/defaultFavicon.svgPK
!<U	3�443����chrome/toolkit/skin/classic/global/icons/delete.svgPK
!<���qq6��#�chrome/toolkit/skin/classic/global/icons/developer.svgPK
!<�����6����chrome/toolkit/skin/classic/global/icons/edit-copy.svgPK
!<�MJ0JJ9����chrome/toolkit/skin/classic/global/icons/edit-outline.svgPK
!<

 �1��e�chrome/toolkit/skin/classic/global/icons/edit.svgPK
!<2'?��8����chrome/toolkit/skin/classic/global/icons/experiments.svgPK
!<��v��3��%��chrome/toolkit/skin/classic/global/icons/folder.svgPK
!<#z����2���chrome/toolkit/skin/classic/global/icons/heart.svgPK
!<�g�**1���chrome/toolkit/skin/classic/global/icons/help.svgPK
!<�(�s��7���chrome/toolkit/skin/classic/global/icons/highlights.svgPK
!<�34�XXG����chrome/toolkit/skin/classic/global/icons/indicator-private-browsing.svgPK
!<��;s��6����chrome/toolkit/skin/classic/global/icons/lightbulb.svgPK
!<+Z&��1����chrome/toolkit/skin/classic/global/icons/link.svgPK
!<n����
�
4��y�chrome/toolkit/skin/classic/global/icons/loading.svgPK
!<}�6��0��u&�chrome/toolkit/skin/classic/global/icons/mdn.svgPK
!<{�Z��2���)�chrome/toolkit/skin/classic/global/icons/minus.svgPK
!<��7���8���+�chrome/toolkit/skin/classic/global/icons/open-in-new.svgPK
!<ĕ?�<<;���.�chrome/toolkit/skin/classic/global/icons/page-landscape.svgPK
!<��6ě2�29��S1�chrome/toolkit/skin/classic/global/icons/pendingpaint.pngPK
!<5��~$$8��Ed�chrome/toolkit/skin/classic/global/icons/performance.svgPK
!<���OO3���g�chrome/toolkit/skin/classic/global/icons/plugin.svgPK
!<A1p��4��_j�chrome/toolkit/skin/classic/global/icons/plus-20.svgPK
!<���ؾ�;��Wl�chrome/toolkit/skin/classic/global/icons/pocket-favicon.icoPK
!<c;�3��n}�chrome/toolkit/skin/classic/global/icons/pocket.svgPK
!<&i���2��ŀ�chrome/toolkit/skin/classic/global/icons/print.svgPK
!<9���	�	8��ƃ�chrome/toolkit/skin/classic/global/icons/rating-star.svgPK
!<�T'��4�����chrome/toolkit/skin/classic/global/icons/resizer.svgPK
!<���B55;��˓�chrome/toolkit/skin/classic/global/icons/search-textbox.svgPK
!<����=��Y��chrome/toolkit/skin/classic/global/icons/security-warning.svgPK
!<cjH�gg5��b��chrome/toolkit/skin/classic/global/icons/security.svgPK
!<:�7!YY7����chrome/toolkit/skin/classic/global/icons/sort-arrow.svgPK
!<@r[���;��ʞ�chrome/toolkit/skin/classic/global/icons/thumbs-down-20.svgPK
!<�E���9����chrome/toolkit/skin/classic/global/icons/thumbs-up-20.svgPK
!<b�8D��5��Z��chrome/toolkit/skin/classic/global/icons/trending.svgPK
!<[�ms1�����chrome/toolkit/skin/classic/global/icons/undo.svgPK
!<�蒁--8��䭭chrome/toolkit/skin/classic/global/icons/update-icon.svgPK
!<� �ww<��g��chrome/toolkit/skin/classic/global/icons/warning-fill-12.svgPK
!<�@���4��8��chrome/toolkit/skin/classic/global/icons/warning.svgPK
!<�R�׹�B��5��chrome/toolkit/skin/classic/global/illustrations/about-license.svgPK
!<f�Fm/O/OA��NЭchrome/toolkit/skin/classic/global/illustrations/about-rights.svgPK
!<��m�VEVEH����chrome/toolkit/skin/classic/global/illustrations/error-malformed-url.svgPK
!<ӗe)�)�?���e�chrome/toolkit/skin/classic/global/in-content/common-shared.cssPK
!<��~�$$8����chrome/toolkit/skin/classic/global/in-content/common.cssPK
!<ǁU���<�����chrome/toolkit/skin/classic/global/in-content/info-pages.cssPK
!<~���nn6���
�chrome/toolkit/skin/classic/global/in-content/wifi.svgPK
!<}�˧8����chrome/toolkit/skin/classic/global/media/audio-muted.svgPK
!<���jAA2���chrome/toolkit/skin/classic/global/media/audio.svgPK
!<Ut�2RR?����chrome/toolkit/skin/classic/global/media/audioNoAudioButton.svgPK
!<�W�A��H�chrome/toolkit/skin/classic/global/media/castingButton-active.svgPK
!<�G\���@����chrome/toolkit/skin/classic/global/media/castingButton-ready.svgPK
!<=�O..K��� �chrome/toolkit/skin/classic/global/media/closed-caption-settings-button.svgPK
!<�'[���G���%�chrome/toolkit/skin/classic/global/media/closedCaptionButton-cc-off.svgPK
!<�[Y���F���*�chrome/toolkit/skin/classic/global/media/closedCaptionButton-cc-on.svgPK
!<rg��2��>0�chrome/toolkit/skin/classic/global/media/error.pngPK
!<���kkB���8�chrome/toolkit/skin/classic/global/media/fullscreenEnterButton.svgPK
!<�PWWA��T<�chrome/toolkit/skin/classic/global/media/fullscreenExitButton.svgPK
!<�c����?��
@�chrome/toolkit/skin/classic/global/media/imagedoc-darknoise.pngPK
!<�;?���@��QL�chrome/toolkit/skin/classic/global/media/imagedoc-lightnoise.pngPK
!<S��|//7��B\�chrome/toolkit/skin/classic/global/media/pause-fill.svgPK
!<�*jGGF���^�chrome/toolkit/skin/classic/global/media/picture-in-picture-closed.svgPK
!<��
#W��qb�chrome/toolkit/skin/classic/global/media/picture-in-picture-enter-fullscreen-button.svgPK
!<w�K���V���d�chrome/toolkit/skin/classic/global/media/picture-in-picture-exit-fullscreen-button.svgPK
!<�F�GGD��Zh�chrome/toolkit/skin/classic/global/media/picture-in-picture-open.svgPK
!<��5''S��l�chrome/toolkit/skin/classic/global/media/picture-in-picture-seekBackward-button.svgPK
!<�U�#��R���p�chrome/toolkit/skin/classic/global/media/picture-in-picture-seekForward-button.svgPK
!<ħ�9L.L.6���u�chrome/toolkit/skin/classic/global/media/pipToggle.cssPK
!<	����6��l��chrome/toolkit/skin/classic/global/media/play-fill.svgPK
!<��QQ4�����chrome/toolkit/skin/classic/global/media/stalled.pngPK
!<��¥[[<����chrome/toolkit/skin/classic/global/media/textrecognition.cssPK
!<Q��w�w5����chrome/toolkit/skin/classic/global/media/throbber.pngPK
!<��A^�4�4:��!s�chrome/toolkit/skin/classic/global/media/videocontrols.cssPK
!<Я>�TT4����chrome/toolkit/skin/classic/global/narrate/arrow.svgPK
!<�#Q\\3�����chrome/toolkit/skin/classic/global/narrate/back.svgPK
!<=��ll3��_��chrome/toolkit/skin/classic/global/narrate/fast.svgPK
!<�^"�886����chrome/toolkit/skin/classic/global/narrate/forward.svgPK
!<R��GG?�����chrome/toolkit/skin/classic/global/narrate/headphone-active.svgPK
!<����8��L°chrome/toolkit/skin/classic/global/narrate/headphone.svgPK
!<�����?��2Űchrome/toolkit/skin/classic/global/narrate/skip-backward-20.svgPK
!<�.��>���ǰchrome/toolkit/skin/classic/global/narrate/skip-forward-20.svgPK
!<��(�!!3���ɰchrome/toolkit/skin/classic/global/narrate/slow.svgPK
!<}����4��AѰchrome/toolkit/skin/classic/global/narrate/start.svgPK
!<�kb�ii3���Ӱchrome/toolkit/skin/classic/global/narrate/stop.svgPK
!<���7��Kհchrome/toolkit/skin/classic/global/narrate-improved.cssPK
!<d	a��.����chrome/toolkit/skin/classic/global/narrate.cssPK
!<�J4��:���	�chrome/toolkit/skin/classic/global/offlineSupportPages.cssPK
!<�3�9�9>����chrome/toolkit/skin/classic/global/pictureinpicture/player.cssPK
!<�(I�E
E
B���E�chrome/toolkit/skin/classic/global/pictureinpicture/texttracks.cssPK
!<x
���J��wP�chrome/toolkit/skin/classic/global/reader/RM-Content-Width-Minus-42x16.svgPK
!<���8��I���S�chrome/toolkit/skin/classic/global/reader/RM-Content-Width-Plus-44x16.svgPK
!<(dPPH��W�chrome/toolkit/skin/classic/global/reader/RM-Line-Height-Minus-38x14.svgPK
!<ҙ8�G���Y�chrome/toolkit/skin/classic/global/reader/RM-Line-Height-Plus-38x24.svgPK
!<{��SS<���[�chrome/toolkit/skin/classic/global/reader/RM-Minus-24x24.svgPK
!<L}��uu;���]�chrome/toolkit/skin/classic/global/reader/RM-Plus-24x24.svgPK
!<�G�>��;��k_�chrome/toolkit/skin/classic/global/reader/RM-Sans-Serif.svgPK
!<
Ճ���6���f�chrome/toolkit/skin/classic/global/reader/RM-Serif.svgPK
!<69a��D���r�chrome/toolkit/skin/classic/global/reader/RM-Type-Controls-24x24.svgPK
!<$֣��=���v�chrome/toolkit/skin/classic/global/reader/align-center-20.svgPK
!<s���;���x�chrome/toolkit/skin/classic/global/reader/align-left-20.svgPK
!<�~*4��<��{�chrome/toolkit/skin/classic/global/reader/align-right-20.svgPK
!<S���OOB��}�chrome/toolkit/skin/classic/global/reader/character-spacing-20.svgPK
!<���]]>����chrome/toolkit/skin/classic/global/reader/content-width-20.svgPK
!<�Ǫ__=����chrome/toolkit/skin/classic/global/reader/line-spacing-20.svgPK
!<���\=��9��chrome/toolkit/skin/classic/global/reader/word-spacing-20.svgPK
!<c[��	�	5�����chrome/toolkit/skin/classic/global/search-textbox.cssPK
!<��{9��4��ڒ�chrome/toolkit/skin/classic/global/tree/sort-asc.svgPK
!<���4��딱chrome/toolkit/skin/classic/global/tree/sort-dsc.svgPK
!<�V�ss-�����chrome/toolkit/skin/classic/global/wizard.cssPK
!<$~�4yy5�����chrome/toolkit/skin/classic/mozapps/aboutProfiles.cssPK
!<������;�����chrome/toolkit/skin/classic/mozapps/aboutServiceWorkers.cssPK
!<	4�D�����chrome/toolkit/skin/classic/mozapps/downloads/unknownContentType.cssPK
!<�����E����chrome/toolkit/skin/classic/mozapps/extensions/category-available.svgPK
!<������D����chrome/toolkit/skin/classic/mozapps/extensions/category-discover.svgPK
!<�+�Z��F��]��chrome/toolkit/skin/classic/mozapps/extensions/category-extensions.svgPK
!<Bd�*��C�����chrome/toolkit/skin/classic/mozapps/extensions/category-plugins.svgPK
!<�n���B�����chrome/toolkit/skin/classic/mozapps/extensions/category-recent.svgPK
!<��LJ�����chrome/toolkit/skin/classic/mozapps/extensions/category-sitepermission.svgPK
!<'����D��+��chrome/toolkit/skin/classic/mozapps/extensions/dictionaryGeneric.svgPK
!<���llC��N��chrome/toolkit/skin/classic/mozapps/extensions/extensionGeneric.svgPK
!<�/P �-�-7��±chrome/toolkit/skin/classic/mozapps/extensions/line.svgPK
!<�\��hh>��?�chrome/toolkit/skin/classic/mozapps/extensions/recommended.svgPK
!<�����?���chrome/toolkit/skin/classic/mozapps/extensions/themeGeneric.svgPK
!<�Us��9��P��chrome/toolkit/skin/classic/mozapps/handling/handling.cssPK
!<�R�ii8��5��chrome/toolkit/skin/classic/mozapps/profileDowngrade.cssPK
!<�wb�xx8���chrome/toolkit/skin/classic/mozapps/profileSelection.cssPK
!<�T���6����chrome/toolkit/skin/classic/mozapps/update/updates.cssPK
!<��z�.."���chrome/pdfjs/content/PdfJs.sys.mjsPK
!<į��)���chrome/pdfjs/content/PdfJsNetwork.sys.mjsPK
!<��WW+���4�chrome/pdfjs/content/PdfJsTelemetry.sys.mjsPK
!<�܏�g	g	'��mP�chrome/pdfjs/content/PdfSandbox.sys.mjsPK
!<��A�)�)�/��Z�chrome/pdfjs/content/PdfStreamConverter.sys.mjsPK
!<��:PP'����chrome/pdfjs/content/PdfjsChild.sys.mjsPK
!<���&E@E@(��$��chrome/pdfjs/content/PdfjsParent.sys.mjsPK
!<4xX�X�"���8�chrome/pdfjs/content/build/pdf.mjsPK
!<�ۥ���7��G�chrome/pdfjs/content/build/pdf.sandbox.external.sys.mjsPK
!<lv��L�L�,��k�chrome/pdfjs/content/build/pdf.scripting.mjsPK
!<���HUd!Ud!)����chrome/pdfjs/content/build/pdf.worker.mjsPK
!<%���d	d	-�����chrome/pdfjs/content/web/cmaps/78-EUC-H.bcmapPK
!<8�uH��-��L�chrome/pdfjs/content/web/cmaps/78-EUC-V.bcmapPK
!<�U+K	K	)��D�chrome/pdfjs/content/web/cmaps/78-H.bcmapPK
!<�n�^	^	.����chrome/pdfjs/content/web/cmaps/78-RKSJ-H.bcmapPK
!<�@i��.����chrome/pdfjs/content/web/cmaps/78-RKSJ-V.bcmapPK
!<a�bة�)��y�chrome/pdfjs/content/web/cmaps/78-V.bcmapPK
!<jM��[
[
0��i�chrome/pdfjs/content/web/cmaps/78ms-RKSJ-H.bcmapPK
!<!��8""0��'�chrome/pdfjs/content/web/cmaps/78ms-RKSJ-V.bcmapPK
!<�Ll��0���(�chrome/pdfjs/content/web/cmaps/83pv-RKSJ-H.bcmapPK
!<#a���0��Y,�chrome/pdfjs/content/web/cmaps/90ms-RKSJ-H.bcmapPK
!<��Q�""0��x/�chrome/pdfjs/content/web/cmaps/90ms-RKSJ-V.bcmapPK
!<pNd��1���0�chrome/pdfjs/content/web/cmaps/90msp-RKSJ-H.bcmapPK
!<�^G�##1��4�chrome/pdfjs/content/web/cmaps/90msp-RKSJ-V.bcmapPK
!<L�gV��0��t5�chrome/pdfjs/content/web/cmaps/90pv-RKSJ-H.bcmapPK
!<��'�0���9�chrome/pdfjs/content/web/cmaps/90pv-RKSJ-V.bcmapPK
!<��;s	s	*���:�chrome/pdfjs/content/web/cmaps/Add-H.bcmapPK
!<5�Pm	m	/���D�chrome/pdfjs/content/web/cmaps/Add-RKSJ-H.bcmapPK
!<��)/��_N�chrome/pdfjs/content/web/cmaps/Add-RKSJ-V.bcmapPK
!<���v*���O�chrome/pdfjs/content/web/cmaps/Add-V.bcmapPK
!<�H�==1��-Q�chrome/pdfjs/content/web/cmaps/Adobe-CNS1-0.bcmapPK
!<h`�ss1���R�chrome/pdfjs/content/web/cmaps/Adobe-CNS1-1.bcmapPK
!<1HEYxx1��{T�chrome/pdfjs/content/web/cmaps/Adobe-CNS1-2.bcmapPK
!<����1��BV�chrome/pdfjs/content/web/cmaps/Adobe-CNS1-3.bcmapPK
!<�a���1��"X�chrome/pdfjs/content/web/cmaps/Adobe-CNS1-4.bcmapPK
!<QFC��1��Z�chrome/pdfjs/content/web/cmaps/Adobe-CNS1-5.bcmapPK
!<K��–�1���[�chrome/pdfjs/content/web/cmaps/Adobe-CNS1-6.bcmapPK
!<�Nd���4���]�chrome/pdfjs/content/web/cmaps/Adobe-CNS1-UCS2.bcmapPK
!<�#��0����chrome/pdfjs/content/web/cmaps/Adobe-GB1-0.bcmapPK
!<��{���0��2�chrome/pdfjs/content/web/cmaps/Adobe-GB1-1.bcmapPK
!<�?���0��z�chrome/pdfjs/content/web/cmaps/Adobe-GB1-2.bcmapPK
!<[�Q]��0����chrome/pdfjs/content/web/cmaps/Adobe-GB1-3.bcmapPK
!<ɮ5YY0����chrome/pdfjs/content/web/cmaps/Adobe-GB1-4.bcmapPK
!<�GOqq0��d�chrome/pdfjs/content/web/cmaps/Adobe-GB1-5.bcmapPK
!<r������3��#�chrome/pdfjs/content/web/cmaps/Adobe-GB1-UCS2.bcmapPK
!<�
&��3��*��chrome/pdfjs/content/web/cmaps/Adobe-Japan1-0.bcmapPK
!<��V��3��\��chrome/pdfjs/content/web/cmaps/Adobe-Japan1-1.bcmapPK
!<��}��3�����chrome/pdfjs/content/web/cmaps/Adobe-Japan1-2.bcmapPK
!<�!�}��3��ɓ�chrome/pdfjs/content/web/cmaps/Adobe-Japan1-3.bcmapPK
!<�h/�QQ3����chrome/pdfjs/content/web/cmaps/Adobe-Japan1-4.bcmapPK
!<DZ���3�����chrome/pdfjs/content/web/cmaps/Adobe-Japan1-5.bcmapPK
!<�Ɣ���3�����chrome/pdfjs/content/web/cmaps/Adobe-Japan1-6.bcmapPK
!<qW�����6����chrome/pdfjs/content/web/cmaps/Adobe-Japan1-UCS2.bcmapPK
!<�9��3��.;�chrome/pdfjs/content/web/cmaps/Adobe-Korea1-0.bcmapPK
!<��=��3��p<�chrome/pdfjs/content/web/cmaps/Adobe-Korea1-1.bcmapPK
!<��ȯ�3��C>�chrome/pdfjs/content/web/cmaps/Adobe-Korea1-2.bcmapPK
!<:@.�Z�Z6��@�chrome/pdfjs/content/web/cmaps/Adobe-Korea1-UCS2.bcmapPK
!<Lv�M>>)��l��chrome/pdfjs/content/web/cmaps/B5-H.bcmapPK
!<J#nŎ�)����chrome/pdfjs/content/web/cmaps/B5-V.bcmapPK
!<%��KK+��Ơ�chrome/pdfjs/content/web/cmaps/B5pc-H.bcmapPK
!<9k,���+��Z��chrome/pdfjs/content/web/cmaps/B5pc-V.bcmapPK
!<��L��.��3��chrome/pdfjs/content/web/cmaps/CNS-EUC-H.bcmapPK
!<�D��.��s��chrome/pdfjs/content/web/cmaps/CNS-EUC-V.bcmapPK
!<����+��?��chrome/pdfjs/content/web/cmaps/CNS1-H.bcmapPK
!<�[P��+��J��chrome/pdfjs/content/web/cmaps/CNS1-V.bcmapPK
!<(TY���+��"��chrome/pdfjs/content/web/cmaps/CNS2-H.bcmapPK
!<����]]+��c��chrome/pdfjs/content/web/cmaps/CNS2-V.bcmapPK
!<2W�jJJ.��	��chrome/pdfjs/content/web/cmaps/ETHK-B5-H.bcmapPK
!<h(-��.�����chrome/pdfjs/content/web/cmaps/ETHK-B5-V.bcmapPK
!<ӡ0�ee.�����chrome/pdfjs/content/web/cmaps/ETen-B5-H.bcmapPK
!<7S-E��.��:��chrome/pdfjs/content/web/cmaps/ETen-B5-V.bcmapPK
!<�bt�ee0��$��chrome/pdfjs/content/web/cmaps/ETenms-B5-H.bcmapPK
!<�T�ݬ�0�����chrome/pdfjs/content/web/cmaps/ETenms-B5-V.bcmapPK
!<�wBB*�����chrome/pdfjs/content/web/cmaps/EUC-H.bcmapPK
!<��ͪ�*��[��chrome/pdfjs/content/web/cmaps/EUC-V.bcmapPK
!<b_��	�	*��M��chrome/pdfjs/content/web/cmaps/Ext-H.bcmapPK
!<����	�	/��}��chrome/pdfjs/content/web/cmaps/Ext-RKSJ-H.bcmapPK
!<"D����/�����chrome/pdfjs/content/web/cmaps/Ext-RKSJ-V.bcmapPK
!<U/����*�����chrome/pdfjs/content/web/cmaps/Ext-V.bcmapPK
!<ܥ�8%%-�����chrome/pdfjs/content/web/cmaps/GB-EUC-H.bcmapPK
!<fk1��-��n��chrome/pdfjs/content/web/cmaps/GB-EUC-V.bcmapPK
!<	��)��l��chrome/pdfjs/content/web/cmaps/GB-H.bcmapPK
!<�7n���)����chrome/pdfjs/content/web/cmaps/GB-V.bcmapPK
!<M��d9d9.�����chrome/pdfjs/content/web/cmaps/GBK-EUC-H.bcmapPK
!<��޴�.��i0�chrome/pdfjs/content/web/cmaps/GBK-EUC-V.bcmapPK
!<�p��L�L,��i1�chrome/pdfjs/content/web/cmaps/GBK2K-H.bcmapPK
!<@%���,���~�chrome/pdfjs/content/web/cmaps/GBK2K-V.bcmapPK
!<�o�^9^9/����chrome/pdfjs/content/web/cmaps/GBKp-EUC-H.bcmapPK
!<�ԏJ��/��Q��chrome/pdfjs/content/web/cmaps/GBKp-EUC-V.bcmapPK
!<o㛱zz.��S��chrome/pdfjs/content/web/cmaps/GBT-EUC-H.bcmapPK
!<�Q���.����chrome/pdfjs/content/web/cmaps/GBT-EUC-V.bcmapPK
!<�d�ee*����chrome/pdfjs/content/web/cmaps/GBT-H.bcmapPK
!<��S���*�����chrome/pdfjs/content/web/cmaps/GBT-V.bcmapPK
!<i.���0�����chrome/pdfjs/content/web/cmaps/GBTpc-EUC-H.bcmapPK
!<4X�O��0����chrome/pdfjs/content/web/cmaps/GBTpc-EUC-V.bcmapPK
!<�g��--/����chrome/pdfjs/content/web/cmaps/GBpc-EUC-H.bcmapPK
!<s�cf��/���chrome/pdfjs/content/web/cmaps/GBpc-EUC-V.bcmapPK
!<��))&���chrome/pdfjs/content/web/cmaps/H.bcmapPK
!<R�Ǿ^
^
/��{�chrome/pdfjs/content/web/cmaps/HKdla-B5-H.bcmapPK
!<}���/��&$�chrome/pdfjs/content/web/cmaps/HKdla-B5-V.bcmapPK
!<dXLn	n	/��%�chrome/pdfjs/content/web/cmaps/HKdlb-B5-H.bcmapPK
!<�委�/���.�chrome/pdfjs/content/web/cmaps/HKdlb-B5-V.bcmapPK
!<O�z���0���/�chrome/pdfjs/content/web/cmaps/HKgccs-B5-H.bcmapPK
!<]??ϕ�0���8�chrome/pdfjs/content/web/cmaps/HKgccs-B5-V.bcmapPK
!<��#y��0���9�chrome/pdfjs/content/web/cmaps/HKm314-B5-H.bcmapPK
!<N����0��A�chrome/pdfjs/content/web/cmaps/HKm314-B5-V.bcmapPK
!<�$+v{{0���A�chrome/pdfjs/content/web/cmaps/HKm471-B5-H.bcmapPK
!<S�#��0���J�chrome/pdfjs/content/web/cmaps/HKm471-B5-V.bcmapPK
!<f�dmUU/���K�chrome/pdfjs/content/web/cmaps/HKscs-B5-H.bcmapPK
!<����/��3]�chrome/pdfjs/content/web/cmaps/HKscs-B5-V.bcmapPK
!<iF{τ�,��^�chrome/pdfjs/content/web/cmaps/Hankaku.bcmapPK
!<J�F||-���^�chrome/pdfjs/content/web/cmaps/Hiragana.bcmapPK
!<o��88.���_�chrome/pdfjs/content/web/cmaps/KSC-EUC-H.bcmapPK
!<��w��.��8g�chrome/pdfjs/content/web/cmaps/KSC-EUC-V.bcmapPK
!<k,�''*��(h�chrome/pdfjs/content/web/cmaps/KSC-H.bcmapPK
!<�KR�A�A0���o�chrome/pdfjs/content/web/cmaps/KSC-Johab-H.bcmapPK
!<VK�Ǧ�0��|��chrome/pdfjs/content/web/cmaps/KSC-Johab-V.bcmapPK
!<ul�C��*��p��chrome/pdfjs/content/web/cmaps/KSC-V.bcmapPK
!<�Q�
�
0��X��chrome/pdfjs/content/web/cmaps/KSCms-UHC-H.bcmapPK
!<�.d�
�
3�����chrome/pdfjs/content/web/cmaps/KSCms-UHC-HW-H.bcmapPK
!<�*|���3�����chrome/pdfjs/content/web/cmaps/KSCms-UHC-HW-V.bcmapPK
!<;"oۦ�0�����chrome/pdfjs/content/web/cmaps/KSCms-UHC-V.bcmapPK
!<m�7���0�����chrome/pdfjs/content/web/cmaps/KSCpc-EUC-H.bcmapPK
!<{�s��0�����chrome/pdfjs/content/web/cmaps/KSCpc-EUC-V.bcmapPK
!<����dd-�����chrome/pdfjs/content/web/cmaps/Katakana.bcmapPK
!<ٚ�  &�����chrome/pdfjs/content/web/cmaps/LICENSEPK
!<�^��
�
*�����chrome/pdfjs/content/web/cmaps/NWP-H.bcmapPK
!<j�B���*�����chrome/pdfjs/content/web/cmaps/NWP-V.bcmapPK
!<��CA+��C��chrome/pdfjs/content/web/cmaps/RKSJ-H.bcmapPK
!<L3���+�����chrome/pdfjs/content/web/cmaps/RKSJ-V.bcmapPK
!<��,�``*�����chrome/pdfjs/content/web/cmaps/Roman.bcmapPK
!<i�阼��2��=��chrome/pdfjs/content/web/cmaps/UniCNS-UCS2-H.bcmapPK
!<k5'���2��%��chrome/pdfjs/content/web/cmaps/UniCNS-UCS2-V.bcmapPK
!<L�K����3����chrome/pdfjs/content/web/cmaps/UniCNS-UTF16-H.bcmapPK
!<wr���3��Uq�chrome/pdfjs/content/web/cmaps/UniCNS-UTF16-V.bcmapPK
!<�o�����3��Br�chrome/pdfjs/content/web/cmaps/UniCNS-UTF32-H.bcmapPK
!<�B����3��Z@�chrome/pdfjs/content/web/cmaps/UniCNS-UTF32-V.bcmapPK
!< �T�}�}�2��KA�chrome/pdfjs/content/web/cmaps/UniCNS-UTF8-H.bcmapPK
!<��<ܝ�2���chrome/pdfjs/content/web/cmaps/UniCNS-UTF8-V.bcmapPK
!<gYYf�f�1���chrome/pdfjs/content/web/cmaps/UniGB-UCS2-H.bcmapPK
!<�aZ��1�����chrome/pdfjs/content/web/cmaps/UniGB-UCS2-V.bcmapPK
!<Vd�6�6�2��ʾ�chrome/pdfjs/content/web/cmaps/UniGB-UTF16-H.bcmapPK
!<1#�X��2��Pk�chrome/pdfjs/content/web/cmaps/UniGB-UTF16-V.bcmapPK
!<1T縪���2��Rl�chrome/pdfjs/content/web/cmaps/UniGB-UTF32-H.bcmapPK
!<�9�L��2��L�chrome/pdfjs/content/web/cmaps/UniGB-UTF32-V.bcmapPK
!<F`PO����1��R �chrome/pdfjs/content/web/cmaps/UniGB-UTF8-H.bcmapPK
!<�P�A��1�����chrome/pdfjs/content/web/cmaps/UniGB-UTF8-V.bcmapPK
!<��_c_c2�����chrome/pdfjs/content/web/cmaps/UniJIS-UCS2-H.bcmapPK
!< ��_ww5��I<�chrome/pdfjs/content/web/cmaps/UniJIS-UCS2-HW-H.bcmapPK
!<bq%��5��=�chrome/pdfjs/content/web/cmaps/UniJIS-UCS2-HW-V.bcmapPK
!<�RP͘�2��@�chrome/pdfjs/content/web/cmaps/UniJIS-UCS2-V.bcmapPK
!<�i����3���B�chrome/pdfjs/content/web/cmaps/UniJIS-UTF16-H.bcmapPK
!<<؃�3��Z��chrome/pdfjs/content/web/cmaps/UniJIS-UTF16-V.bcmapPK
!<���`[�[�3��.��chrome/pdfjs/content/web/cmaps/UniJIS-UTF32-H.bcmapPK
!<��?��3���~�chrome/pdfjs/content/web/cmaps/UniJIS-UTF32-V.bcmapPK
!<���ߢߢ2��Ё�chrome/pdfjs/content/web/cmaps/UniJIS-UTF8-H.bcmapPK
!<���}��2���$�chrome/pdfjs/content/web/cmaps/UniJIS-UTF8-V.bcmapPK
!<��Sbn�n�7���'�chrome/pdfjs/content/web/cmaps/UniJIS2004-UTF16-H.bcmapPK
!<��Er��7�����chrome/pdfjs/content/web/cmaps/UniJIS2004-UTF16-V.bcmapPK
!<:�嶞��7�����chrome/pdfjs/content/web/cmaps/UniJIS2004-UTF32-H.bcmapPK
!<>z���7���d�chrome/pdfjs/content/web/cmaps/UniJIS2004-UTF32-V.bcmapPK
!<���3�3�6���g�chrome/pdfjs/content/web/cmaps/UniJIS2004-UTF8-H.bcmapPK
!<9�Ǿ��6��$�chrome/pdfjs/content/web/cmaps/UniJIS2004-UTF8-V.bcmapPK
!<&h:���8��"�chrome/pdfjs/content/web/cmaps/UniJISPro-UCS2-HW-V.bcmapPK
!<��O��5��9�chrome/pdfjs/content/web/cmaps/UniJISPro-UCS2-V.bcmapPK
!<-M���5��=�chrome/pdfjs/content/web/cmaps/UniJISPro-UTF8-V.bcmapPK
!<5�P2E�E�8��f�chrome/pdfjs/content/web/cmaps/UniJISX0213-UTF32-H.bcmapPK
!<UybK��8����chrome/pdfjs/content/web/cmaps/UniJISX0213-UTF32-V.bcmapPK
!<��j�����<����chrome/pdfjs/content/web/cmaps/UniJISX02132004-UTF32-H.bcmapPK
!<Z�o��<���W�chrome/pdfjs/content/web/cmaps/UniJISX02132004-UTF32-V.bcmapPK
!<��B��d�d1��[�chrome/pdfjs/content/web/cmaps/UniKS-UCS2-H.bcmapPK
!<�e�Ʋ�1��
��chrome/pdfjs/content/web/cmaps/UniKS-UCS2-V.bcmapPK
!<�W���f�f2����chrome/pdfjs/content/web/cmaps/UniKS-UTF16-H.bcmapPK
!<.}�<��2��5(�chrome/pdfjs/content/web/cmaps/UniKS-UTF16-V.bcmapPK
!<���SgSg2��))�chrome/pdfjs/content/web/cmaps/UniKS-UTF32-H.bcmapPK
!<>9Ѩ�2��̐�chrome/pdfjs/content/web/cmaps/UniKS-UTF32-V.bcmapPK
!<>�`�l�l1��đ�chrome/pdfjs/content/web/cmaps/UniKS-UTF8-H.bcmapPK
!<���B��1�����chrome/pdfjs/content/web/cmaps/UniKS-UTF8-V.bcmapPK
!<��5e��&�����chrome/pdfjs/content/web/cmaps/V.bcmapPK
!<�!�ų�.����chrome/pdfjs/content/web/cmaps/WP-Symbol.bcmapPK
!<̵�,u	u	%����chrome/pdfjs/content/web/debugger.cssPK
!<��SFSF%��:�chrome/pdfjs/content/web/debugger.mjsPK
!<��t��/���Q�chrome/pdfjs/content/web/images/altText_add.svgPK
!<d��RE
E
6���U�chrome/pdfjs/content/web/images/altText_disclaimer.svgPK
!<��+??0��Nc�chrome/pdfjs/content/web/images/altText_done.svgPK
!<������3���g�chrome/pdfjs/content/web/images/altText_spinner.svgPK
!<|�C��3���k�chrome/pdfjs/content/web/images/altText_warning.svgPK
!<,ͽ���4��r�chrome/pdfjs/content/web/images/annotation-check.svgPK
!<h�ss6��t�chrome/pdfjs/content/web/images/annotation-comment.svgPK
!<�ڰjxx3���w�chrome/pdfjs/content/web/images/annotation-help.svgPK
!<�w2���5�����chrome/pdfjs/content/web/images/annotation-insert.svgPK
!<����2�����chrome/pdfjs/content/web/images/annotation-key.svgPK
!<�g{��;����chrome/pdfjs/content/web/images/annotation-newparagraph.svgPK
!<FJcX��5�����chrome/pdfjs/content/web/images/annotation-noicon.svgPK
!<�ZS3��s��chrome/pdfjs/content/web/images/annotation-note.svgPK
!<��F((8��Տ�chrome/pdfjs/content/web/images/annotation-paperclip.svgPK
!<	]J�ww8��S��chrome/pdfjs/content/web/images/annotation-paragraph.svgPK
!<�
�J__6�� ��chrome/pdfjs/content/web/images/annotation-pushpin.svgPK
!<�16xx>��Ӝ�chrome/pdfjs/content/web/images/cursor-editorFreeHighlight.svgPK
!<ֹ����9�����chrome/pdfjs/content/web/images/cursor-editorFreeText.svgPK
!<e�b�++4�����chrome/pdfjs/content/web/images/cursor-editorInk.svgPK
!<f�?>��'��chrome/pdfjs/content/web/images/cursor-editorTextHighlight.svgPK
!<u�R��9�����chrome/pdfjs/content/web/images/editor-toolbar-delete.svgPK
!<��BB6�����chrome/pdfjs/content/web/images/findbarButton-next.svgPK
!<��BB:����chrome/pdfjs/content/web/images/findbarButton-previous.svgPK
!<����	�	0�����chrome/pdfjs/content/web/images/loading-icon.gifPK
!<Qe#]+�����chrome/pdfjs/content/web/images/loading.svgPK
!<��	rDD<��P��chrome/pdfjs/content/web/images/messageBar_closingButton.svgPK
!<�ٴA++6�����chrome/pdfjs/content/web/images/messageBar_warning.svgPK
!<.E����M��m��chrome/pdfjs/content/web/images/secondaryToolbarButton-documentProperties.svgPK
!<�7O�D��y��chrome/pdfjs/content/web/images/secondaryToolbarButton-firstPage.svgPK
!<�ws�@@C�����chrome/pdfjs/content/web/images/secondaryToolbarButton-handTool.svgPK
!<c���C�����chrome/pdfjs/content/web/images/secondaryToolbarButton-lastPage.svgPK
!<1�TTTD����chrome/pdfjs/content/web/images/secondaryToolbarButton-rotateCcw.svgPK
!<左�@@C�����chrome/pdfjs/content/web/images/secondaryToolbarButton-rotateCw.svgPK
!<��1���K��9��chrome/pdfjs/content/web/images/secondaryToolbarButton-scrollHorizontal.svgPK
!<K����E��m��chrome/pdfjs/content/web/images/secondaryToolbarButton-scrollPage.svgPK
!<�ՙ_��I����chrome/pdfjs/content/web/images/secondaryToolbarButton-scrollVertical.svgPK
!<������H����chrome/pdfjs/content/web/images/secondaryToolbarButton-scrollWrapped.svgPK
!<��%==E��
�chrome/pdfjs/content/web/images/secondaryToolbarButton-selectTool.svgPK
!<�E����chrome/pdfjs/content/web/images/secondaryToolbarButton-spreadEven.svgPK
!<
[X��E���chrome/pdfjs/content/web/images/secondaryToolbarButton-spreadNone.svgPK
!<"�����D���chrome/pdfjs/content/web/images/secondaryToolbarButton-spreadOdd.svgPK
!<����]]:��.�chrome/pdfjs/content/web/images/toolbarButton-bookmark.svgPK
!<<��f__D����chrome/pdfjs/content/web/images/toolbarButton-currentOutlineItem.svgPK
!<�&g:����chrome/pdfjs/content/web/images/toolbarButton-download.svgPK
!<���L��@��$�chrome/pdfjs/content/web/images/toolbarButton-editorFreeText.svgPK
!<��ݎ�A��X&�chrome/pdfjs/content/web/images/toolbarButton-editorHighlight.svgPK
!<W����;��E*�chrome/pdfjs/content/web/images/toolbarButton-editorInk.svgPK
!<:����=��C/�chrome/pdfjs/content/web/images/toolbarButton-editorStamp.svgPK
!<���P��;��|2�chrome/pdfjs/content/web/images/toolbarButton-menuArrow.svgPK
!<��?߽�:��~5�chrome/pdfjs/content/web/images/toolbarButton-pageDown.svgPK
!<�Y���8���8�chrome/pdfjs/content/web/images/toolbarButton-pageUp.svgPK
!<�����B���;�chrome/pdfjs/content/web/images/toolbarButton-presentationMode.svgPK
!<�ąt��7���>�chrome/pdfjs/content/web/images/toolbarButton-print.svgPK
!<a��r��8���B�chrome/pdfjs/content/web/images/toolbarButton-search.svgPK
!<�cZ988H���G�chrome/pdfjs/content/web/images/toolbarButton-secondaryToolbarToggle.svgPK
!<�[��?��VL�chrome/pdfjs/content/web/images/toolbarButton-sidebarToggle.svgPK
!<}���::A���R�chrome/pdfjs/content/web/images/toolbarButton-viewAttachments.svgPK
!< |�k��<��dU�chrome/pdfjs/content/web/images/toolbarButton-viewLayers.svgPK
!<�&�xLL=��]X�chrome/pdfjs/content/web/images/toolbarButton-viewOutline.svgPK
!<,�gtt?��Z�chrome/pdfjs/content/web/images/toolbarButton-viewThumbnail.svgPK
!<��J��8���_�chrome/pdfjs/content/web/images/toolbarButton-zoomIn.svgPK
!<2�B^��9���c�chrome/pdfjs/content/web/images/toolbarButton-zoomOut.svgPK
!<:�*�]]6��f�chrome/pdfjs/content/web/images/treeitem-collapsed.svgPK
!<]{ɵ^^5���f�chrome/pdfjs/content/web/images/treeitem-expanded.svgPK
!<���IsIs9��zg�chrome/pdfjs/content/web/standard_fonts/FoxitDingbats.pfbPK
!<�L_ǽD�D6����chrome/pdfjs/content/web/standard_fonts/FoxitFixed.pfbPK
!<K�'�F�F:��+ �chrome/pdfjs/content/web/standard_fonts/FoxitFixedBold.pfbPK
!<_����J�J@��
g�chrome/pdfjs/content/web/standard_fonts/FoxitFixedBoldItalic.pfbPK
!<i�X�:I:I<��7��chrome/pdfjs/content/web/standard_fonts/FoxitFixedItalic.pfbPK
!<j�r�
L
L6����chrome/pdfjs/content/web/standard_fonts/FoxitSerif.pfbPK
!<I����K�K:��,H�chrome/pdfjs/content/web/standard_fonts/FoxitSerifBold.pfbPK
!<��@�P�P@��G��chrome/pdfjs/content/web/standard_fonts/FoxitSerifBoldItalic.pfbPK
!<���R�R<�����chrome/pdfjs/content/web/standard_fonts/FoxitSerifItalic.pfbPK
!<�ygYAYA7���8�chrome/pdfjs/content/web/standard_fonts/FoxitSymbol.pfbPK
!<@�E�5���z�chrome/pdfjs/content/web/standard_fonts/LICENSE_FOXITPK
!<h�&Y>>:�����chrome/pdfjs/content/web/standard_fonts/LICENSE_LIBERATIONPK
!<z�l�\\?�����chrome/pdfjs/content/web/standard_fonts/LiberationSans-Bold.ttfPK
!<�&����E��H��chrome/pdfjs/content/web/standard_fonts/LiberationSans-BoldItalic.ttfPK
!<1�Ɉ�x�xA����chrome/pdfjs/content/web/standard_fonts/LiberationSans-Italic.ttfPK
!<��$� � B���3�chrome/pdfjs/content/web/standard_fonts/LiberationSans-Regular.ttfPK
!<'$z�	�	�#��*U�chrome/pdfjs/content/web/viewer.cssPK
!<H�]*��$��t�chrome/pdfjs/content/web/viewer.htmlPK
!<�1T?T?#����chrome/pdfjs/content/web/viewer.mjsPK
!<,�_�

 ��P�defaults/autoconfig/prefcalls.jsPK
!<7:�5	5	+����defaults/backgroundtasks/backgroundtasks.jsPK
!<��p!p!���res/EditorOverride.cssPK
!<Ұ
i::%���res/table-add-column-after-active.gifPK
!<v�7�::$��7res/table-add-column-after-hover.gifPK
!<x�
::���
res/table-add-column-after.gifPK
!<�Z(99&��)res/table-add-column-before-active.gifPK
!<M�99%���res/table-add-column-before-hover.gifPK
!<�/�99��"res/table-add-column-before.gifPK
!<<�Ed99"���res/table-add-row-after-active.gifPK
!<K::!��res/table-add-row-after-hover.gifPK
!<�ؿ::���res/table-add-row-after.gifPK
!<ބw�99#���res/table-add-row-before-active.gifPK
!<��99"��wres/table-add-row-before-hover.gifPK
!<�?499��� res/table-add-row-before.gifPK
!<�	$�CC"��c$res/table-remove-column-active.gifPK
!<�&II!���'res/table-remove-column-hover.gifPK
!<��V)II��n+res/table-remove-column.gifPK
!<�	$�CC���.res/table-remove-row-active.gifPK
!<�&II��p2res/table-remove-row-hover.gifPK
!<��V)II���5res/table-remove-row.gifPK
!<�!r�ZZ��t9res/grabber.gifPK
!<�7�ʥʥ���<res/fonts/mathfont.propertiesPK
!<�BCC(���res/fonts/mathfontSTIXGeneral.propertiesPK
!<����$����res/fonts/mathfontUnicode.propertiesPK
!<�@7��m�m���	res/dtd/htmlmathml-f.entPK
!<h(������ures/language.propertiesPK
!<5Oz�  *����res/locale/layout/MediaDocument.propertiesPK
!<~3�##&���res/locale/layout/xmlparser.propertiesPK
!<�p�l��#��|�contentaccessible/ImageDocument.cssPK
!<F�g+��K�contentaccessible/TopLevelImageDocument.cssPK
!<4U휅�+����contentaccessible/TopLevelVideoDocument.cssPK
!<2Y�ll��p�contentaccessible/details.cssPK
!<��K!���contentaccessible/html/folder.pngPK
!<��3�����g�contentaccessible/plaintext.cssPK
!<�#�iKK(����contentaccessible/searchfield-cancel.svgPK
!<�lT�� ���contentaccessible/viewsource.cssPK
!<�����,���chrome/pippki/content/pippki/certManager.cssPK
!<��O�T�T+����chrome/pippki/content/pippki/certManager.jsPK
!<8��9+9+.���chrome/pippki/content/pippki/certManager.xhtmlPK
!<��.��G<chrome/pippki/content/pippki/changepassword.jsPK
!<��s�	�	1���Rchrome/pippki/content/pippki/changepassword.xhtmlPK
!<�fIdd.���\chrome/pippki/content/pippki/clientauthask.cssPK
!<4&y���-���^chrome/pippki/content/pippki/clientauthask.jsPK
!<��c�7
7
0���tchrome/pippki/content/pippki/clientauthask.xhtmlPK
!<(��V��+��J�chrome/pippki/content/pippki/deletecert.cssPK
!<_��/
/
*��{�chrome/pippki/content/pippki/deletecert.jsPK
!<��]00-���chrome/pippki/content/pippki/deletecert.xhtmlPK
!<�V�a.a..��m�chrome/pippki/content/pippki/device_manager.jsPK
!<��1���chrome/pippki/content/pippki/device_manager.xhtmlPK
!<�)Mx
x
,����chrome/pippki/content/pippki/downloadcert.jsPK
!<@h!LL/��C�chrome/pippki/content/pippki/downloadcert.xhtmlPK
!<������*����chrome/pippki/content/pippki/editcacert.jsPK
!<W'�H��-���chrome/pippki/content/pippki/editcacert.xhtmlPK
!<6(�͎�0����chrome/pippki/content/pippki/exceptionDialog.cssPK
!<{���b(b(/����chrome/pippki/content/pippki/exceptionDialog.jsPK
!<1�Y���2��h
chrome/pippki/content/pippki/exceptionDialog.xhtmlPK
!<�}�)�	�	+���&
chrome/pippki/content/pippki/load_device.jsPK
!<ʃ���.���0
chrome/pippki/content/pippki/load_device.xhtmlPK
!<���X"X"&���7
chrome/pippki/content/pippki/pippki.jsPK
!<#e<{YY-��^Z
chrome/pippki/content/pippki/resetpassword.jsPK
!<���qq0��^
chrome/pippki/content/pippki/resetpassword.xhtmlPK
!<�����.���c
chrome/pippki/content/pippki/setp12password.jsPK
!<���h��1���r
chrome/pippki/content/pippki/setp12password.xhtmlPK
!<v&#�1��.z
chrome/en-US/locale/en-US/alerts/alert.propertiesPK
!<�֔�gg:��~{
chrome/en-US/locale/en-US/autoconfig/autoconfig.propertiesPK
!<TP�--8��=}
chrome/en-US/locale/en-US/global/aboutStudies.propertiesPK
!<�Ľ�6���
chrome/en-US/locale/en-US/global/appstrings.propertiesPK
!<��#$$8��#�
chrome/en-US/locale/en-US/global/autocomplete.propertiesPK
!<{;�FF3����
chrome/en-US/locale/en-US/global/browser.propertiesPK
!<s����?��4�
chrome/en-US/locale/en-US/global/contentAreaCommands.propertiesPK
!<;v�ɠ�2���
chrome/en-US/locale/en-US/global/dialog.propertiesPK
!<�n���6���
chrome/en-US/locale/en-US/global/extensions.propertiesPK
!<���O--;��ߕ
chrome/en-US/locale/en-US/global/fallbackMenubar.propertiesPK
!<zŗ116��e�
chrome/en-US/locale/en-US/global/filepicker.propertiesPK
!<2u�,,9���
chrome/en-US/locale/en-US/global/global-strres.propertiesPK
!<5Oz�  @��m�
chrome/en-US/locale/en-US/global/layout/MediaDocument.propertiesPK
!<~3�##<���
chrome/en-US/locale/en-US/global/layout/xmlparser.propertiesPK
!<�q�[3��h�
chrome/en-US/locale/en-US/global/narrate.propertiesPK
!<F�Y�44?��8�
chrome/en-US/locale/en-US/global/nsWebBrowserPersist.propertiesPK
!<�!��7��ɨ
chrome/en-US/locale/en-US/global/printdialog.propertiesPK
!<�n��xx8���
chrome/en-US/locale/en-US/global/resetProfile.propertiesPK
!<�Ŏx��9���
chrome/en-US/locale/en-US/global/security/caps.propertiesPK
!<�R�{{8���
chrome/en-US/locale/en-US/global/security/csp.propertiesPK
!<�-����6����
chrome/en-US/locale/en-US/global/viewSource.propertiesPK
!<�晉��2���
chrome/en-US/locale/en-US/global/wizard.propertiesPK
!<��۲��5���
chrome/en-US/locale/en-US/global/xslt/xslt.propertiesPK
!<Zڬ���C����
chrome/en-US/locale/en-US/global-platform/mac/accessible.propertiesPK
!<��QT=����
chrome/en-US/locale/en-US/global-platform/mac/intl.propertiesPK
!<��uuE��D�
chrome/en-US/locale/en-US/global-platform/mac/platformKeys.propertiesPK
!<<�K�GGD���
chrome/en-US/locale/en-US/global-platform/unix/accessible.propertiesPK
!<��QT>����
chrome/en-US/locale/en-US/global-platform/unix/intl.propertiesPK
!<�����C��4�
chrome/en-US/locale/en-US/global-platform/win/accessible.propertiesPK
!<��QT=��=�
chrome/en-US/locale/en-US/global-platform/win/intl.propertiesPK
!<cfy�llE����
chrome/en-US/locale/en-US/global-platform/win/platformKeys.propertiesPK
!<�*P@��z�
chrome/en-US/locale/en-US/mozapps/downloads/downloads.propertiesPK
!<�"	"	E����
chrome/en-US/locale/en-US/mozapps/profile/profileSelection.propertiesPK
!<uw^��;��x�
chrome/en-US/locale/en-US/mozapps/update/updates.propertiesPK
!<Ev���<����
chrome/en-US/locale/en-US/passwordmgr/passwordmgr.propertiesPK
!<�j۹>q>q5����
chrome/en-US/locale/en-US/pipnss/nsserrors.propertiesPK
!<���^992��dfchrome/en-US/locale/en-US/pippki/pippki.propertiesPK
!<n���oo2���lchrome/en-US/locale/en-US/places/places.propertiesPK����PK
!<�(���e�egreprefs.js//@line 2 "$SRCDIR/modules/libpref/greprefs.js"
//@line 1 "$SRCDIR/modules/libpref/init/all.js"
pref("security.tls.insecure_fallback_hosts", "");
pref("security.default_personal_cert",   "Ask Every Time");
pref("security.remember_cert_checkbox_default_setting", true);
pref("security.signed_app_signatures.policy", 2);
pref("security.xfocsp.errorReporting.enabled", true);
pref("security.xfocsp.errorReporting.automatic", false);
pref("security.xfocsp.hideOpenInNewWindow", true);
pref("security.pki.mitm_canary_issuer", "");
pref("security.pki.mitm_canary_issuer.enabled", true);
pref("security.pki.mitm_detected", false);
pref("security.remote_settings.intermediates.enabled", true);
pref("security.remote_settings.intermediates.downloads_per_poll", 5000);
pref("security.remote_settings.intermediates.parallel_downloads", 8);
//@line 67 "$SRCDIR/modules/libpref/init/all.js"
  pref("security.remote_settings.crlite_filters.enabled", false);
//@line 69 "$SRCDIR/modules/libpref/init/all.js"
pref("security.osreauthenticator.blank_password", false);
pref("security.osreauthenticator.password_last_changed_lo", 0);
pref("security.osreauthenticator.password_last_changed_hi", 0);
pref("security.crash_tracking.js_load_1.prevCrashes", 0);
pref("security.crash_tracking.js_load_1.maxCrashes", 1);
pref("general.useragent.compatMode.firefox", false);
pref("general.config.obscure_value", 13); // for MCD .cfg files
//@line 84 "$SRCDIR/modules/libpref/init/all.js"
pref("general.autoscroll.prevent_to_start.shiftKey", true); // Shift
pref("general.autoscroll.prevent_to_start.ctrlKey", false); // Control
pref("general.autoscroll.prevent_to_start.altKey", false);  // Alt
pref("general.autoscroll.prevent_to_start.metaKey", false);
pref("general.autoscroll.prevent_to_collapse_selection_by_middle_mouse_down", false);
pref("browser.bookmarks.max_backups",       5);
pref("browser.cache.disk_cache_ssl",        true);
pref("browser.cache.frecency_half_life_hours", 6);
pref("browser.download.forbid_open_with", false);
pref("dom.indexedDB.logging.enabled", true);
pref("dom.indexedDB.logging.details", true);
pref("dom.indexedDB.logging.profiler-marks", false);
pref("dom.workers.maxPerDomain", 512);
pref("dom.serviceWorkers.idle_timeout", 30000);
pref("dom.serviceWorkers.idle_extended_timeout", 30000);
pref("dom.serviceWorkers.update_delay", 1000);
pref("dom.serviceWorkers.testUpdateOverOneDay", false);
pref("dom.keyboardevent.keypress.hack.dispatch_non_printable_keys", "www.icloud.com");
pref("dom.keyboardevent.keypress.hack.dispatch_non_printable_keys.addl", "");
pref("dom.keyboardevent.keypress.hack.use_legacy_keycode_and_charcode", "*.collabserv.com,*.gov.online.office365.us,*.officeapps-df.live.com,*.officeapps.live.com,*.online.office.de,*.partner.officewebapps.cn,*.scniris.com");
pref("dom.keyboardevent.keypress.hack.use_legacy_keycode_and_charcode.addl", "");
pref("dom.text-recognition.enabled", true);
pref("browser.sessionhistory.max_total_viewers", -1);
pref("browser.send_pings", false);
pref("browser.send_pings.max_per_link", 1);           // limit the number of pings that are sent per link click
pref("browser.send_pings.require_same_host", false);  // only send pings to the same host if this is true
pref("browser.helperApps.neverAsk.saveToDisk", "");
pref("browser.helperApps.neverAsk.openFile", "");
pref("browser.helperApps.deleteTempFileOnExit", false);
pref("browser.triple_click_selects_paragraph", true);
pref("mathml.disabled",    false);
pref("mathml.scale_stretchy_operators.enabled", true);
pref("media.throttle-factor", 2);
pref("media.volume_scale", "1.0");
pref("media.play-stand-alone", true);
//@line 201 "$SRCDIR/modules/libpref/init/all.js"
pref("media.gmp.storage.version.expected", 1);
//@line 214 "$SRCDIR/modules/libpref/init/all.js"
  pref("media.decoder-doctor.notifications-allowed", "MediaWMFNeeded,MediaWidevineNoWMF,MediaCannotInitializePulseAudio,MediaCannotPlayNoDecoders,MediaUnsupportedLibavcodec,MediaPlatformDecoderNotFound");
//@line 216 "$SRCDIR/modules/libpref/init/all.js"
pref("media.decoder-doctor.decode-errors-allowed", "");
pref("media.decoder-doctor.decode-warnings-allowed", "");
pref("media.decoder-doctor.verbose", false);
pref("media.decoder-doctor.new-issue-endpoint", "https://webcompat.com/issues/new");
pref("media.videocontrols.picture-in-picture.enabled", false);
pref("media.videocontrols.picture-in-picture.display-text-tracks.enabled", true);
pref("media.videocontrols.picture-in-picture.video-toggle.enabled", false);
pref("media.videocontrols.picture-in-picture.video-toggle.always-show", false);
pref("media.videocontrols.picture-in-picture.video-toggle.min-video-secs", 45);
pref("media.videocontrols.picture-in-picture.video-toggle.position", "right");
pref("media.videocontrols.picture-in-picture.video-toggle.has-used", false);
pref("media.videocontrols.picture-in-picture.display-text-tracks.toggle.enabled", true);
pref("media.videocontrols.picture-in-picture.display-text-tracks.size", "medium");
pref("media.videocontrols.picture-in-picture.improved-video-controls.enabled", true);
pref("media.videocontrols.picture-in-picture.respect-disablePictureInPicture", true);
pref("media.videocontrols.keyboard-tab-to-all-controls", true);
//@line 237 "$SRCDIR/modules/libpref/init/all.js"
  pref("media.navigator.video.enabled", true);
  pref("media.navigator.video.default_fps",30);
  pref("media.navigator.video.use_remb", true);
  pref("media.navigator.video.use_transport_cc", true);
  pref("media.peerconnection.video.use_rtx", true);
  pref("media.peerconnection.video.use_rtx.blocklist", "doxy.me,*.doxy.me");
  pref("media.peerconnection.sdp.quirk.duplicate_fingerprint.allowlist", "");
  pref("media.navigator.video.use_tmmbr", false);
  pref("media.navigator.audio.use_fec", true);
  pref("media.navigator.video.offer_rtcp_rsize", true);
//@line 254 "$SRCDIR/modules/libpref/init/all.js"
    pref("media.peerconnection.sdp.parser", "sipcc");
    pref("media.peerconnection.sdp.alternate_parse_mode", "never");
    pref("media.peerconnection.sdp.strict_success", false);
    pref("media.navigator.video.red_ulpfec_enabled", true);
//@line 259 "$SRCDIR/modules/libpref/init/all.js"
  pref("media.peerconnection.sdp.disable_stereo_fmtp", false);
  pref("media.webrtc.debug.log_file", "");
  pref("media.navigator.video.default_width",0);  // adaptive default
  pref("media.navigator.video.default_height",0); // adaptive default
  pref("media.navigator.video.max_fs", 12288); // Enough for 2048x1536
  pref("media.navigator.video.max_fr", 60);
//@line 270 "$SRCDIR/modules/libpref/init/all.js"
    pref("media.navigator.video.disable_h264_baseline", true);
//@line 272 "$SRCDIR/modules/libpref/init/all.js"
  pref("media.navigator.video.h264.level", 31); // 0x42E01f - level 3.1
  pref("media.navigator.video.h264.max_br", 0);
  pref("media.navigator.video.h264.max_mbps", 0);
  pref("media.peerconnection.video.vp9_enabled", true);
  pref("media.peerconnection.video.vp9_preferred", false);
  pref("media.getusermedia.audio.max_channels", 0);
//@line 282 "$SRCDIR/modules/libpref/init/all.js"
    pref("media.getusermedia.camera.off_while_disabled.enabled", true);
    pref("media.getusermedia.microphone.off_while_disabled.enabled", false);
//@line 285 "$SRCDIR/modules/libpref/init/all.js"
  pref("media.getusermedia.camera.off_while_disabled.delay_ms", 3000);
  pref("media.getusermedia.microphone.off_while_disabled.delay_ms", 3000);
  pref("media.peerconnection.video.min_bitrate", 0);
  pref("media.peerconnection.video.start_bitrate", 0);
  pref("media.peerconnection.video.max_bitrate", 0);
  pref("media.peerconnection.video.min_bitrate_estimate", 0);
  pref("media.peerconnection.video.denoising", false);
  pref("media.navigator.audio.fake_frequency", 1000);
  pref("media.navigator.permission.disabled", false);
  pref("media.navigator.streams.fake", false);
  pref("media.peerconnection.default_iceservers", "[]");
  pref("media.peerconnection.allow_old_setParameters", true);
  pref("media.peerconnection.ice.loopback", false); // Set only for testing in offline environments.
  pref("media.peerconnection.ice.tcp", true);
  pref("media.peerconnection.ice.tcp_so_sock_count", 0); // Disable SO gathering
  pref("media.peerconnection.ice.link_local", false); // Set only for testing IPV6 in networks that don't assign IPV6 addresses
  pref("media.peerconnection.ice.force_interface", ""); // Limit to only a single interface
  pref("media.peerconnection.ice.relay_only", false); // Limit candidates to TURN
  pref("media.peerconnection.use_document_iceservers", true);
  pref("media.peerconnection.identity.timeout", 10000);
  pref("media.peerconnection.ice.stun_client_maximum_transmits", 7);
  pref("media.peerconnection.ice.trickle_grace_period", 5000);
  pref("media.peerconnection.ice.no_host", false);
  pref("media.peerconnection.ice.default_address_only", false);
//@line 316 "$SRCDIR/modules/libpref/init/all.js"
    pref("media.peerconnection.ice.obfuscate_host_addresses", true);
//@line 318 "$SRCDIR/modules/libpref/init/all.js"
  pref("media.peerconnection.ice.obfuscate_host_addresses.blocklist", "");
  pref("media.peerconnection.ice.proxy_only_if_behind_proxy", false);
  pref("media.peerconnection.ice.proxy_only", false);
  pref("media.peerconnection.ice.proxy_only_if_pbmode", false);
  pref("media.peerconnection.turn.disable", false);
  pref("media.peerconnection.treat_warnings_as_errors", false);
//@line 327 "$SRCDIR/modules/libpref/init/all.js"
    pref("media.peerconnection.description.legacy.enabled", true);
//@line 329 "$SRCDIR/modules/libpref/init/all.js"
  pref("media.peerconnection.dtls.version.min", 771);
  pref("media.peerconnection.dtls.version.max", 772);
//@line 337 "$SRCDIR/modules/libpref/init/all.js"
  pref("media.getusermedia.audio.processing.platform.enabled", false);
//@line 339 "$SRCDIR/modules/libpref/init/all.js"
  pref("media.getusermedia.audio.processing.aec.enabled", true);
  pref("media.getusermedia.audio.processing.aec", 1); // kModerateSuppression
  pref("media.getusermedia.audio.processing.aec.mobile", false);
  pref("media.getusermedia.audio.processing.noise.enabled", true);
  pref("media.getusermedia.audio.processing.noise", 2); // kHigh
  pref("media.getusermedia.audio.processing.agc.enabled", true);
  pref("media.getusermedia.audio.processing.agc", 1); // kAdaptiveDigital
  pref("media.getusermedia.audio.processing.agc2.forced", true);
  pref("media.getusermedia.audio.processing.hpf.enabled", true);
  pref("media.getusermedia.audio.processing.transient.enabled", true);
//@line 352 "$SRCDIR/modules/libpref/init/all.js"
//@line 354 "$SRCDIR/modules/libpref/init/all.js"
  pref("media.getusermedia.screensharing.enabled", true);
//@line 356 "$SRCDIR/modules/libpref/init/all.js"
pref("media.getusermedia.audio.capture.enabled", false);
pref("media.webvtt.debug.logging", false);
pref("media.recorder.audio_node.enabled", false);
pref("media.recorder.video.frame_drops", true);
pref("media.video-queue.default-size", 10);
pref("media.video-queue.send-to-compositor-size", 9999);
pref("media.cubeb.output_voice_routing", true);
pref("apz.overscroll.stop_velocity_threshold", "0.01");
pref("apz.overscroll.stretch_factor", "0.35");
pref("apz.zoom-to-focused-input.enabled", true);
pref("formhelper.autozoom.force-disable.test-only", false);
//@line 397 "$SRCDIR/modules/libpref/init/all.js"
pref("gfx.downloadable_fonts.enabled", true);
pref("gfx.downloadable_fonts.fallback_delay", 3000);
pref("gfx.downloadable_fonts.fallback_delay_short", 100);
//@line 405 "$SRCDIR/modules/libpref/init/all.js"
//@line 413 "$SRCDIR/modules/libpref/init/all.js"
  pref("gfx.canvas.azure.backends", "skia");
//@line 415 "$SRCDIR/modules/libpref/init/all.js"
pref("gfx.content.azure.backends", "skia");
//@line 422 "$SRCDIR/modules/libpref/init/all.js"
pref("gfx.webrender.debug.texture-cache", false);
pref("gfx.webrender.debug.texture-cache.clear-evicted", true);
pref("gfx.webrender.debug.render-targets", false);
pref("gfx.webrender.debug.gpu-cache", false);
pref("gfx.webrender.debug.alpha-primitives", false);
pref("gfx.webrender.debug.profiler", false);
pref("gfx.webrender.debug.gpu-time-queries", false);
pref("gfx.webrender.debug.gpu-sample-queries", false);
pref("gfx.webrender.debug.disable-batching", false);
pref("gfx.webrender.debug.epochs", false);
pref("gfx.webrender.debug.echo-driver-messages", false);
pref("gfx.webrender.debug.show-overdraw", false);
pref("gfx.webrender.debug.slow-frame-indicator", false);
pref("gfx.webrender.debug.picture-caching", false);
pref("gfx.webrender.debug.force-picture-invalidation", false);
pref("gfx.webrender.debug.primitives", false);
pref("gfx.webrender.debug.small-screen", false);
pref("gfx.webrender.debug.obscure-images", false);
pref("gfx.webrender.debug.glyph-flashing", false);
pref("gfx.webrender.debug.capture-profiler", false);
pref("gfx.webrender.debug.profiler-ui", "Default");
pref("gfx.webrender.debug.window-visibility", false);
pref("gfx.webrender.multithreading", true);
//@line 453 "$SRCDIR/modules/libpref/init/all.js"
pref("gfx.webrender.pbo-uploads", true);
pref("gfx.webrender.batched-texture-uploads", false);
pref("gfx.webrender.draw-calls-for-texture-copy", false);
//@line 457 "$SRCDIR/modules/libpref/init/all.js"
pref("accessibility.warn_on_browsewithcaret", true);
pref("accessibility.browsewithcaret_shortcut.enabled", true);
//@line 469 "$SRCDIR/modules/libpref/init/all.js"
//@line 473 "$SRCDIR/modules/libpref/init/all.js"
pref("ui.textSelectDisabledBackground", "#b0b0b0");
//@line 475 "$SRCDIR/modules/libpref/init/all.js"
pref("ui.textSelectAttentionBackground", "#38d878");
pref("ui.textSelectAttentionForeground", "#ffffff");
pref("ui.textHighlightBackground", "#ef0fff");
pref("ui.textHighlightForeground", "#ffffff");
pref("accessibility.force_disabled", 0);
pref("focusmanager.testmode", false);
pref("accessibility.typeaheadfind", true);
pref("accessibility.typeaheadfind.manual", true);
pref("accessibility.typeaheadfind.autostart", true);
pref("accessibility.typeaheadfind.casesensitive", 0);
pref("accessibility.typeaheadfind.linksonly", true);
pref("accessibility.typeaheadfind.startlinksonly", false);
pref("accessibility.typeaheadfind.timeout", 4000);
pref("accessibility.typeaheadfind.soundURL", "beep");
pref("accessibility.typeaheadfind.enablesound", true);
//@line 524 "$SRCDIR/modules/libpref/init/all.js"
  pref("accessibility.typeaheadfind.prefillwithselection", true);
//@line 526 "$SRCDIR/modules/libpref/init/all.js"
pref("accessibility.typeaheadfind.matchesCountLimit", 1000);
pref("findbar.highlightAll", false);
pref("findbar.entireword", false);
pref("findbar.iteratorTimeout", 100);
pref("findbar.matchdiacritics", 0);
pref("findbar.modalHighlight", false);
pref("gfx.use_text_smoothing_setting", false);
pref("toolkit.autocomplete.richBoundaryCutoff", 200);
pref("toolkit.scrollbox.scrollIncrement", 20);
pref("toolkit.scrollbox.clickToScroll.scrollDelay", 150);
pref("toolkit.shopping.ohttpConfigURL", "https://prod.ohttp-gateway.prod.webservices.mozgcp.net/ohttp-configs");
pref("toolkit.shopping.ohttpRelayURL", "https://mozilla-ohttp.fastly-edge.com/");
pref("toolkit.shopping.environment", "prod");
pref("toolkit.sqlitejsm.loglevel", "Error");
pref("toolkit.tabbox.switchByScrolling", false);
pref("toolkit.telemetry.server", "https://incoming.telemetry.mozilla.org");
pref("toolkit.telemetry.server_owner", "Mozilla");
pref("toolkit.telemetry.debugSlowSql", false);
pref("toolkit.telemetry.unified", true);
pref("toolkit.telemetry.dap_enabled", false);
pref("toolkit.telemetry.dap.logLevel", "Warn");
pref("toolkit.telemetry.dap_task1_enabled", false);
pref("toolkit.telemetry.dap_task1_taskid", "");
pref("toolkit.telemetry.dap_visit_counting_enabled", false);
pref("toolkit.telemetry.dap_visit_counting_experiment_list", "[]");
pref("toolkit.telemetry.dap.leader.url", "https://dap-09-3.api.divviup.org");
pref("toolkit.telemetry.dap.leader.hpke", "ACkAACAAAQABACDk8wgwe2-TqHyaL74uqjVWMcF1zi9pxiwQhu4aPwncYw");
pref("toolkit.telemetry.dap.helper.url", "https://dap.services.mozilla.com");
pref("toolkit.telemetry.dap.helper.hpke", "ACkAACAAAQABACAucqWdIQRN6BxumPBRXIlg2JsxcznwWX7vyqzM3cjuQA");
pref("toolkit.telemetry.translations.logLevel", "Error");
pref("toolkit.telemetry.user_characteristics_ping.current_version", 0);
pref("toolkit.telemetry.user_characteristics_ping.last_version_sent", 0);
pref("toolkit.telemetry.user_characteristics_ping.opt-out", false);
pref("toolkit.telemetry.user_characteristics_ping.send-once", false);
pref("toolkit.telemetry.user_characteristics_ping.uuid", "");
pref("toolkit.telemetry.user_characteristics_ping.logLevel", "Warn");
//@line 617 "$SRCDIR/modules/libpref/init/all.js"
  pref("toolkit.asyncshutdown.crash_timeout", 60000); // 1 minute
//@line 619 "$SRCDIR/modules/libpref/init/all.js"
pref("toolkit.asyncshutdown.log", false);
//@line 628 "$SRCDIR/modules/libpref/init/all.js"
  pref("browser.dom.window.dump.enabled", false, sticky);
  pref("devtools.console.stdout.chrome", false, sticky);
//@line 634 "$SRCDIR/modules/libpref/init/all.js"
pref("devtools.console.stdout.content", false, sticky);
pref("toolkit.dump.emit", false);
pref("devtools.performance.recording.ui-base-url", "https://profiler.firefox.com");
pref("devtools.performance.recording.child.timeout_s", 0);
//@line 658 "$SRCDIR/modules/libpref/init/all.js"
  pref("devtools.performance.popup.feature-flag", false);
//@line 660 "$SRCDIR/modules/libpref/init/all.js"
//@line 667 "$SRCDIR/modules/libpref/init/all.js"
  pref("devtools.performance.recording.preset", "web-developer");
  pref("devtools.performance.recording.preset.remote", "web-developer");
//@line 670 "$SRCDIR/modules/libpref/init/all.js"
pref("devtools.performance.recording.active-tab-view.enabled", false);
pref("devtools.performance.recording.entries", 10000000);
pref("devtools.performance.recording.entries.remote", 10000000);
pref("devtools.performance.recording.interval", 1000);
pref("devtools.performance.recording.interval.remote", 1000);
pref("devtools.performance.recording.duration", 0);
pref("devtools.performance.recording.duration.remote", 0);
pref("devtools.performance.recording.features", "[\"js\",\"stackwalk\",\"cpu\",\"screenshots\",\"memory\"]");
pref("devtools.performance.recording.features.remote", "[\"js\",\"stackwalk\",\"cpu\",\"screenshots\",\"memory\",\"java\"]");
pref("devtools.performance.recording.threads", "[\"GeckoMain\",\"Compositor\",\"Renderer\"]");
pref("devtools.performance.recording.threads.remote", "[\"GeckoMain\",\"Compositor\",\"Renderer\"]");
pref("devtools.performance.recording.objdirs", "[]");
pref("devtools.performance.recording.power.external-url", "");
pref("devtools.performance.recording.markers.external-url", "");
pref("devtools.performance.popup.intro-displayed", false);
pref("devtools.inspector.compatibility.target-browsers", "");
pref("view_source.editor.path", "");
pref("view_source.editor.args", "");
pref("nglayout.enable_drag_images", true);
pref("browser.fixup.alternate.prefix", "www.");
pref("browser.fixup.alternate.protocol", "https");
pref("browser.fixup.alternate.suffix", ".com");
pref("browser.fixup.fallback-to-https", true);
pref("print.prefer_system_dialog", false);
pref("print.shrink-to-fit.scale-limit-percent", 20);
pref("print.show_page_setup_menu", false);
pref("print.print_headerleft", "&T");
pref("print.print_headercenter", "");
pref("print.print_headerright", "&U");
pref("print.print_footerleft", "&PT");
pref("print.print_footercenter", "");
pref("print.print_footerright", "&D");
pref("print.cups.monochrome.extra_settings", "");
pref("print.save_print_settings", true);
pref("print.more-settings.open", false);
pref("print.print_edge_top", 0);
pref("print.print_edge_left", 0);
pref("print.print_edge_right", 0);
pref("print.print_edge_bottom", 0);
//@line 785 "$SRCDIR/modules/libpref/init/all.js"
  pref("print.print_reversed", false);
  pref("print.print_in_color", true);
//@line 789 "$SRCDIR/modules/libpref/init/all.js"
pref("dom.beforeunload_timeout_ms",         1000);
pref("dom.disable_window_flip",             false);
pref("dom.disable_window_move_resize",      false);
pref("dom.allow_scripts_to_close_windows",          false);
pref("dom.popup_allowed_events", "change click dblclick auxclick mousedown mouseup pointerdown pointerup notificationclick reset submit touchend contextmenu");
pref("dom.serviceWorkers.disable_open_click_delay", 1000);
pref("dom.storage.shadow_writes", false);
pref("dom.storage.snapshot_prefill", 16384);
pref("dom.storage.snapshot_gradual_prefill", 4096);
pref("dom.storage.snapshot_reusing", true);
pref("dom.storage.client_validation", true);
pref("dom.forms.datetime.timepicker", false);
pref("dom.forms.selectSearch", false);
//@line 819 "$SRCDIR/modules/libpref/init/all.js"
  pref("dom.forms.select.customstyling", false);
//@line 823 "$SRCDIR/modules/libpref/init/all.js"
pref("dom.cycle_collector.incremental", true);
pref("privacy.resistFingerprinting.exemptedDomains", "*.example.invalid");
pref("privacy.fingerprintingProtection.overrides", "");
pref("privacy.fingerprintingProtection.granularOverrides", "");
pref("privacy.restrict3rdpartystorage.partitionedHosts", "accounts.google.com/o/oauth2/,d35nw2lg0ahg0v.cloudfront.net/,datastudio.google.com/embed/reporting/,d3qlaywcwingl6.cloudfront.net/");
pref("privacy.restrict3rdpartystorage.userInteractionRequiredForHosts", "");
pref("privacy.restrict3rdpartystorage.url_decorations", "");
pref("privacy.popups.maxReported", 100);
pref("privacy.purge_trackers.enabled", true);
//@line 864 "$SRCDIR/modules/libpref/init/all.js"
  pref("privacy.purge_trackers.logging.level", "Error");
//@line 866 "$SRCDIR/modules/libpref/init/all.js"
pref("privacy.purge_trackers.max_purge_count", 100);
pref("privacy.purge_trackers.consider_entity_list", false);
pref("dom.event.contextmenu.enabled",       true);
pref("javascript.enabled",                  true);
pref("javascript.options.wasm",                   true);
pref("javascript.options.wasm_trustedprincipals", true);
pref("javascript.options.wasm_verbose",           false);
pref("javascript.options.wasm_baselinejit",       true);
pref("javascript.options.asyncstack", true);
pref("javascript.options.asyncstack_capture_debuggee_only", true);
pref("javascript.options.discardSystemSource", false);
pref("javascript.options.mem.max", -1);
pref("javascript.options.mem.nursery.min_kb", 256);
pref("javascript.options.mem.nursery.max_kb", 65536);
pref("javascript.options.mem.gc_per_zone", true);
pref("javascript.options.mem.gc_incremental", true);
pref("javascript.options.mem.incremental_weakmap", true);
pref("javascript.options.mem.gc_incremental_slice_ms", 5);
pref("javascript.options.mem.gc_compacting", true);
pref("javascript.options.mem.gc_generational", true);
//@line 929 "$SRCDIR/modules/libpref/init/all.js"
//@line 933 "$SRCDIR/modules/libpref/init/all.js"
pref("javascript.options.mem.gc_parallel_marking", true);
//@line 937 "$SRCDIR/modules/libpref/init/all.js"
//@line 947 "$SRCDIR/modules/libpref/init/all.js"
pref("javascript.options.mem.gc_parallel_marking_threshold_mb", 16);
//@line 949 "$SRCDIR/modules/libpref/init/all.js"
pref("javascript.options.mem.gc_max_parallel_marking_threads", 2);
pref("javascript.options.mem.gc_high_frequency_time_limit_ms", 1000);
pref("javascript.options.mem.gc_small_heap_size_max_mb", 100);
pref("javascript.options.mem.gc_large_heap_size_min_mb", 500);
pref("javascript.options.mem.gc_high_frequency_small_heap_growth", 300);
pref("javascript.options.mem.gc_high_frequency_large_heap_growth", 150);
pref("javascript.options.mem.gc_low_frequency_heap_growth", 150);
pref("javascript.options.mem.gc_balanced_heap_limits", false);
pref("javascript.options.mem.gc_heap_growth_factor", 50);
pref("javascript.options.mem.gc_allocation_threshold_mb", 27);
pref("javascript.options.mem.gc_malloc_threshold_base_mb", 38);
pref("javascript.options.mem.gc_small_heap_incremental_limit", 150);
pref("javascript.options.mem.gc_large_heap_incremental_limit", 110);
pref("javascript.options.mem.gc_urgent_threshold_mb", 16);
pref("javascript.options.mem.gc_min_empty_chunk_count", 1);
pref("javascript.options.mem.gc_max_empty_chunk_count", 30);
pref("javascript.options.mem.gc_helper_thread_ratio", 50);
pref("javascript.options.mem.gc_max_helper_threads", 8);
pref("javascript.options.mem.nursery_eager_collection_threshold_kb", 256);
pref("javascript.options.mem.nursery_eager_collection_threshold_percent", 25);
pref("javascript.options.mem.nursery_eager_collection_timeout_ms", 5000);
//@line 1016 "$SRCDIR/modules/libpref/init/all.js"
pref("javascript.options.shared_memory", true);
pref("javascript.options.throw_on_debuggee_would_run", false);
pref("javascript.options.dump_stack_on_debuggee_would_run", false);
pref("image.animation_mode",                "normal");
pref("network.tickle-wifi.enabled", false);
pref("network.tickle-wifi.duration", 400);
pref("network.tickle-wifi.delay", 16);
pref("network.protocol-handler.external-default", true);      // OK to load
pref("network.protocol-handler.warn-external-default", true); // warn before load
pref("network.protocol-handler.external.hcp", false);
pref("network.protocol-handler.external.vbscript", false);
pref("network.protocol-handler.external.javascript", false);
pref("network.protocol-handler.external.data", false);
pref("network.protocol-handler.external.ie.http", false);
pref("network.protocol-handler.external.iehistory", false);
pref("network.protocol-handler.external.ierss", false);
pref("network.protocol-handler.external.mk", false);
pref("network.protocol-handler.external.ms-cxh", false);
pref("network.protocol-handler.external.ms-cxh-full", false);
pref("network.protocol-handler.external.ms-help", false);
pref("network.protocol-handler.external.ms-msdt", false);
pref("network.protocol-handler.external.res", false);
pref("network.protocol-handler.external.search", false);
pref("network.protocol-handler.external.search-ms", false);
pref("network.protocol-handler.external.shell", false);
pref("network.protocol-handler.external.vnd.ms.radio", false);
//@line 1064 "$SRCDIR/modules/libpref/init/all.js"
pref("network.protocol-handler.external.disk", false);
pref("network.protocol-handler.external.disks", false);
pref("network.protocol-handler.external.afp", false);
pref("network.protocol-handler.external.moz-icon", false);
pref("network.protocol-handler.external.ttp", false);  // http
pref("network.protocol-handler.external.htp", false);  // http
pref("network.protocol-handler.external.ttps", false); // https
pref("network.protocol-handler.external.tps", false);  // https
pref("network.protocol-handler.external.ps", false);   // https
pref("network.protocol-handler.external.htps", false); // https
pref("network.protocol-handler.external.ile", false);  // file
pref("network.protocol-handler.external.le", false);   // file
pref("network.protocol-handler.expose-all", true);
pref("network.manage-offline-status", true);
pref("network.http.version", "1.1");      // default
pref("network.http.proxy.version", "1.1");    // default
pref("network.http.proxy.respect-be-conservative", true);
pref("network.http.default-socket-type", "");
pref("network.http.keep-alive.timeout", 115);
pref("network.http.response.timeout", 300);
//@line 1134 "$SRCDIR/modules/libpref/init/all.js"
  pref("network.http.max-connections", 900);
//@line 1136 "$SRCDIR/modules/libpref/init/all.js"
pref("network.http.max-persistent-connections-per-server", 6);
pref("network.http.max-urgent-start-excessive-connections-per-host", 3);
pref("network.http.max-persistent-connections-per-proxy", 32);
pref("network.http.request.max-start-delay", 10);
pref("network.http.request.max-attempts", 10);
pref("network.http.redirection-limit", 20);
pref("network.http.accept-encoding", "gzip, deflate");
pref("network.http.accept-encoding.secure", "gzip, deflate, br, zstd");
pref("network.http.prompt-temp-redirect", false);
pref("network.http.assoc-req.enforce", false);
pref("network.http.qos", 0);
pref("network.http.connection-retry-timeout", 250);
pref("network.http.connection-timeout", 90);
pref("network.http.tls-handshake-timeout", 30);
pref("network.http.fallback-connection-timeout", 5);
pref("network.http.network-changed.timeout", 5);
//@line 1216 "$SRCDIR/modules/libpref/init/all.js"
  pref("network.http.speculative-parallel-limit", 20);
//@line 1218 "$SRCDIR/modules/libpref/init/all.js"
pref("network.http.rendering-critical-requests-prioritization", true);
pref("network.http.fast-fallback-to-IPv4", true);
pref("network.http.http3.default-qpack-table-size", 65536); // 64k
pref("network.http.http3.default-max-stream-blocked", 20);
pref("network.http.http3.alt-svc-mapping-for-testing", "");
pref("network.http.altsvc.enabled", true);
pref("network.http.altsvc.oe", false);
pref("network.http.diagnostics", false);
pref("network.http.pacing.requests.enabled", true);
pref("network.http.pacing.requests.min-parallelism", 6);
pref("network.http.pacing.requests.hz", 80);
pref("network.http.pacing.requests.burst", 10);
pref("network.http.tcp_keepalive.short_lived_connections", true);
pref("network.http.tcp_keepalive.short_lived_time", 60);
pref("network.http.tcp_keepalive.short_lived_idle_time", 10);
pref("network.http.tcp_keepalive.long_lived_connections", true);
pref("network.http.tcp_keepalive.long_lived_idle_time", 600);
pref("network.http.enforce-framing.http1", false); // should be named "strict"
pref("network.http.enforce-framing.soft", true);
pref("network.http.enforce-framing.strict_chunked_encoding", true);
pref("network.http.focused_window_transaction_ratio", "0.9");
pref("network.http.send_window_size", 1024);
//@line 1280 "$SRCDIR/modules/libpref/init/all.js"
  pref("network.http.active_tab_priority", true);
//@line 1282 "$SRCDIR/modules/libpref/init/all.js"
pref("network.http.accept", "");
pref("network.sts.max_time_for_events_between_two_polls", 100);
pref("network.sts.poll_busy_wait_period", 50);
pref("network.sts.poll_busy_wait_period_timeout", 7);
pref("network.sts.max_time_for_pr_close_during_shutdown", 5000);
pref("network.sts.pollable_event_timeout", 6);
pref("network.websocket.max-message-size", 2147483647);
pref("network.websocket.timeout.open", 20);
pref("network.websocket.timeout.close", 20);
pref("network.websocket.timeout.ping.request", 0);
pref("network.websocket.timeout.ping.response", 10);
pref("network.websocket.extensions.permessage-deflate", true);
pref("network.websocket.max-connections", 200);
pref("network.websocket.allowInsecureFromHTTPS", false);
pref("network.websocket.delay-failed-reconnects", true);
pref("dom.server-events.default-reconnection-time", 5000); // in milliseconds
pref("network.dns.ipv4OnlyDomains", "");
pref("network.dnsCacheEntries", 400);
pref("network.dnsCacheExpiration", 60);
pref("network.dns.get-ttl", true);
pref("network.dns.native-is-localhost", false);
pref("network.dnsCacheExpirationGracePeriod", 60);
pref("network.dns.disablePrefetch", false);
pref("network.dns.blockDotOnion", true);
pref("network.dns.localDomains", "");
pref("network.dns.forceResolve", "");
pref("network.dns.offline-localhost", true);
pref("network.dns.resolver-thread-extra-idle-time-seconds", 60);
pref("network.prefetch-next", true);
pref("network.negotiate-auth.trusted-uris", "");
pref("network.negotiate-auth.delegation-uris", "");
pref("network.negotiate-auth.allow-non-fqdn", false);
pref("network.negotiate-auth.allow-proxies", true);
pref("network.negotiate-auth.gsslib", "");
pref("network.negotiate-auth.using-native-gsslib", true);
//@line 1430 "$SRCDIR/modules/libpref/init/all.js"
pref("network.auth.force-generic-ntlm", false);
pref("network.automatic-ntlm-auth.allow-proxies", true);
pref("network.automatic-ntlm-auth.allow-non-fqdn", false);
pref("network.automatic-ntlm-auth.trusted-uris", "");
pref("network.generic-ntlm-auth.workstation", "WORKSTATION");
pref("network.auth.private-browsing-sso", false);
pref("network.http.throttle.enable", false);
pref("network.http.throttle.version", 1);
pref("network.http.throttle.suspend-for", 900);
pref("network.http.throttle.resume-for", 100);
pref("network.http.throttle.read-limit-bytes", 8000);
pref("network.http.throttle.read-interval-ms", 500);
pref("network.http.throttle.hold-time-ms", 800);
pref("network.http.throttle.max-time-ms", 500);
pref("network.http.on_click_priority", true);
pref("network.http.tailing.delay-quantum", 600);
pref("network.http.tailing.delay-quantum-after-domcontentloaded", 100);
pref("network.http.tailing.delay-max", 6000);
pref("network.http.tailing.total-max", 45000);
pref("network.proxy.http",                  "");
pref("network.proxy.http_port",             0);
pref("network.proxy.ssl",                   "");
pref("network.proxy.ssl_port",              0);
pref("network.proxy.socks",                 "");
pref("network.proxy.socks_port",            0);
pref("network.proxy.socks_version",         5);
pref("network.proxy.proxy_over_tls",        true);
pref("network.proxy.no_proxies_on",         "");
pref("network.proxy.failover_timeout",      1800); // 30 minutes
pref("network.online",                      true); //online/offline
pref("network.cookie.sameSite.laxByDefault.disabledHosts", "");
pref("network.cookie.maxNumber", 3000);
pref("network.cookie.maxPerHost", 180);
pref("network.cookie.quotaPerHost", 150);
pref("network.proxy.autoconfig_url", "");
pref("network.proxy.autoconfig_url.include_path", false);
pref("network.proxy.autoconfig_retry_interval_min", 5);    // 5 seconds
pref("network.proxy.autoconfig_retry_interval_max", 300);  // 5 minutes
pref("network.proxy.enable_wpad_over_dhcp", true);
pref("network.stricttransportsecurity.preloadlist", true);
pref("converter.html2txt.structs",          true); // Output structured phrases (strong, em, code, sub, sup, b, i, u)
pref("converter.html2txt.header_strategy",  1); // 0 = no indention; 1 = indention, increased with header level; 2 = numbering and slight indention
pref("intl.accept_languages",               "chrome://global/locale/intl.properties");
pref("intl.menuitems.alwaysappendaccesskeys","chrome://global/locale/intl.properties");
pref("intl.menuitems.insertseparatorbeforeaccesskeys","chrome://global/locale/intl.properties");
pref("intl.ellipsis",                       "chrome://global-platform/locale/intl.properties");
pref("intl.regional_prefs.use_os_locales",  false);
pref("font.language.group",                 "chrome://global/locale/intl.properties");
pref("font.cjk_pref_fallback_order",        "zh-cn,zh-hk,zh-tw,ja,ko");
pref("intl.l10n.pseudo", "");
pref("intl.hyphenation-alias.en", "en-us");
pref("intl.hyphenation-alias.en-*", "en-us");
pref("intl.hyphenation-alias.af-*", "af");
pref("intl.hyphenation-alias.bg-*", "bg");
pref("intl.hyphenation-alias.bn-*", "bn");
pref("intl.hyphenation-alias.ca-*", "ca");
pref("intl.hyphenation-alias.cs-*", "cs");
pref("intl.hyphenation-alias.cy-*", "cy");
pref("intl.hyphenation-alias.da-*", "da");
pref("intl.hyphenation-alias.eo-*", "eo");
pref("intl.hyphenation-alias.es-*", "es");
pref("intl.hyphenation-alias.et-*", "et");
pref("intl.hyphenation-alias.fi-*", "fi");
pref("intl.hyphenation-alias.fr-*", "fr");
pref("intl.hyphenation-alias.gl-*", "gl");
pref("intl.hyphenation-alias.gu-*", "gu");
pref("intl.hyphenation-alias.hi-*", "hi");
pref("intl.hyphenation-alias.hr-*", "hr");
pref("intl.hyphenation-alias.hsb-*", "hsb");
pref("intl.hyphenation-alias.hu-*", "hu");
pref("intl.hyphenation-alias.ia-*", "ia");
pref("intl.hyphenation-alias.is-*", "is");
pref("intl.hyphenation-alias.it-*", "it");
pref("intl.hyphenation-alias.kmr-*", "kmr");
pref("intl.hyphenation-alias.kn-*", "kn");
pref("intl.hyphenation-alias.la-*", "la");
pref("intl.hyphenation-alias.lt-*", "lt");
pref("intl.hyphenation-alias.ml-*", "ml");
pref("intl.hyphenation-alias.mn-*", "mn");
pref("intl.hyphenation-alias.nl-*", "nl");
pref("intl.hyphenation-alias.or-*", "or");
pref("intl.hyphenation-alias.pa-*", "pa");
pref("intl.hyphenation-alias.pl-*", "pl");
pref("intl.hyphenation-alias.pt-*", "pt");
pref("intl.hyphenation-alias.ru-*", "ru");
pref("intl.hyphenation-alias.sl-*", "sl");
pref("intl.hyphenation-alias.sv-*", "sv");
pref("intl.hyphenation-alias.ta-*", "ta");
pref("intl.hyphenation-alias.te-*", "te");
pref("intl.hyphenation-alias.tr-*", "tr");
pref("intl.hyphenation-alias.uk-*", "uk");
pref("intl.hyphenation-alias.as", "bn");
pref("intl.hyphenation-alias.as-*", "bn");
pref("intl.hyphenation-alias.mr", "hi");
pref("intl.hyphenation-alias.mr-*", "hi");
pref("intl.hyphenation-alias.sk", "cs");
pref("intl.hyphenation-alias.sk-*", "cs");
pref("intl.hyphenation-alias.de", "de-1996");
pref("intl.hyphenation-alias.de-*", "de-1996");
pref("intl.hyphenation-alias.de-AT-1901", "de-1901");
pref("intl.hyphenation-alias.de-DE-1901", "de-1901");
pref("intl.hyphenation-alias.de-CH-*", "de-CH");
pref("intl.hyphenation-alias.sr", "sh");
pref("intl.hyphenation-alias.bs", "sh");
pref("intl.hyphenation-alias.sh-*", "sh");
pref("intl.hyphenation-alias.sr-*", "sh");
pref("intl.hyphenation-alias.bs-*", "sh");
pref("intl.hyphenation-alias.no", "nb");
pref("intl.hyphenation-alias.no-*", "nb");
pref("intl.hyphenation-alias.nb-*", "nb");
pref("intl.hyphenation-alias.nn-*", "nn");
pref("intl.hyphenate-capitalized.de-1996", true);
pref("intl.hyphenate-capitalized.de-1901", true);
pref("intl.hyphenate-capitalized.de-CH", true);
pref("intl.hyphenate-capitalized.af", true);
pref("intl.hyphenate-capitalized.fi", true);
pref("intl.hyphenate-capitalized.nl", true);
pref("font.name.serif.ar", "");
pref("font.name.sans-serif.ar", "");
pref("font.name.monospace.ar", "");
pref("font.name.cursive.ar", "");
pref("font.name.serif.el", "");
pref("font.name.sans-serif.el", "");
pref("font.name.monospace.el", "");
pref("font.name.cursive.el", "");
pref("font.name.serif.he", "");
pref("font.name.sans-serif.he", "");
pref("font.name.monospace.he", "");
pref("font.name.cursive.he", "");
pref("font.name.serif.ja", "");
pref("font.name.sans-serif.ja", "");
pref("font.name.monospace.ja", "");
pref("font.name.cursive.ja", "");
pref("font.name.serif.ko", "");
pref("font.name.sans-serif.ko", "");
pref("font.name.monospace.ko", "");
pref("font.name.cursive.ko", "");
pref("font.name.serif.th", "");
pref("font.name.sans-serif.th", "");
pref("font.name.monospace.th", "");
pref("font.name.cursive.th", "");
pref("font.name.serif.x-cyrillic", "");
pref("font.name.sans-serif.x-cyrillic", "");
pref("font.name.monospace.x-cyrillic", "");
pref("font.name.cursive.x-cyrillic", "");
pref("font.name.serif.x-unicode", "");
pref("font.name.sans-serif.x-unicode", "");
pref("font.name.monospace.x-unicode", "");
pref("font.name.cursive.x-unicode", "");
pref("font.name.serif.x-western", "");
pref("font.name.sans-serif.x-western", "");
pref("font.name.monospace.x-western", "");
pref("font.name.cursive.x-western", "");
pref("font.name.serif.zh-CN", "");
pref("font.name.sans-serif.zh-CN", "");
pref("font.name.monospace.zh-CN", "");
pref("font.name.cursive.zh-CN", "");
pref("font.name.serif.zh-TW", "");
pref("font.name.sans-serif.zh-TW", "");
pref("font.name.monospace.zh-TW", "");
pref("font.name.cursive.zh-TW", "");
pref("font.name.serif.zh-HK", "");
pref("font.name.sans-serif.zh-HK", "");
pref("font.name.monospace.zh-HK", "");
pref("font.name.cursive.zh-HK", "");
pref("font.name.serif.x-devanagari", "");
pref("font.name.sans-serif.x-devanagari", "");
pref("font.name.monospace.x-devanagari", "");
pref("font.name.cursive.x-devanagari", "");
pref("font.name.serif.x-tamil", "");
pref("font.name.sans-serif.x-tamil", "");
pref("font.name.monospace.x-tamil", "");
pref("font.name.cursive.x-tamil", "");
pref("font.name.serif.x-armn", "");
pref("font.name.sans-serif.x-armn", "");
pref("font.name.monospace.x-armn", "");
pref("font.name.cursive.x-armn", "");
pref("font.name.serif.x-beng", "");
pref("font.name.sans-serif.x-beng", "");
pref("font.name.monospace.x-beng", "");
pref("font.name.cursive.x-beng", "");
pref("font.name.serif.x-cans", "");
pref("font.name.sans-serif.x-cans", "");
pref("font.name.monospace.x-cans", "");
pref("font.name.cursive.x-cans", "");
pref("font.name.serif.x-ethi", "");
pref("font.name.sans-serif.x-ethi", "");
pref("font.name.monospace.x-ethi", "");
pref("font.name.cursive.x-ethi", "");
pref("font.name.serif.x-geor", "");
pref("font.name.sans-serif.x-geor", "");
pref("font.name.monospace.x-geor", "");
pref("font.name.cursive.x-geor", "");
pref("font.name.serif.x-gujr", "");
pref("font.name.sans-serif.x-gujr", "");
pref("font.name.monospace.x-gujr", "");
pref("font.name.cursive.x-gujr", "");
pref("font.name.serif.x-guru", "");
pref("font.name.sans-serif.x-guru", "");
pref("font.name.monospace.x-guru", "");
pref("font.name.cursive.x-guru", "");
pref("font.name.serif.x-khmr", "");
pref("font.name.sans-serif.x-khmr", "");
pref("font.name.monospace.x-khmr", "");
pref("font.name.cursive.x-khmr", "");
pref("font.name.serif.x-mlym", "");
pref("font.name.sans-serif.x-mlym", "");
pref("font.name.monospace.x-mlym", "");
pref("font.name.cursive.x-mlym", "");
pref("font.name.serif.x-orya", "");
pref("font.name.sans-serif.x-orya", "");
pref("font.name.monospace.x-orya", "");
pref("font.name.cursive.x-orya", "");
pref("font.name.serif.x-telu", "");
pref("font.name.sans-serif.x-telu", "");
pref("font.name.monospace.x-telu", "");
pref("font.name.cursive.x-telu", "");
pref("font.name.serif.x-knda", "");
pref("font.name.sans-serif.x-knda", "");
pref("font.name.monospace.x-knda", "");
pref("font.name.cursive.x-knda", "");
pref("font.name.serif.x-sinh", "");
pref("font.name.sans-serif.x-sinh", "");
pref("font.name.monospace.x-sinh", "");
pref("font.name.cursive.x-sinh", "");
pref("font.name.serif.x-tibt", "");
pref("font.name.sans-serif.x-tibt", "");
pref("font.name.monospace.x-tibt", "");
pref("font.name.cursive.x-tibt", "");
pref("font.name.serif.x-math", "");
pref("font.name.sans-serif.x-math", "");
pref("font.name.monospace.x-math", "");
pref("font.name.cursive.x-math", "");
pref("font.name-list.serif.x-math", "Latin Modern Math, STIX Two Math, XITS Math, Cambria Math, Libertinus Math, DejaVu Math TeX Gyre, TeX Gyre Bonum Math, TeX Gyre Pagella Math, TeX Gyre Schola, TeX Gyre Termes Math, STIX Math, Asana Math, STIXGeneral, DejaVu Serif, DejaVu Sans, serif");
pref("font.name-list.sans-serif.x-math", "sans-serif");
pref("font.name-list.monospace.x-math", "monospace");
pref("font.blacklist.underline_offset", "FangSong,Gulim,GulimChe,MingLiU,MingLiU-ExtB,MingLiU_HKSCS,MingLiU-HKSCS-ExtB,MS Gothic,MS Mincho,MS PGothic,MS PMincho,MS UI Gothic,PMingLiU,PMingLiU-ExtB,SimHei,SimSun,SimSun-ExtB,Hei,Kai,Apple LiGothic,Apple LiSung,Osaka");
pref("security.dialog_enable_delay", 1000);
pref("security.notification_enable_delay", 500);
//@line 1821 "$SRCDIR/modules/libpref/init/all.js"
pref("security.insecure_field_warning.ignore_local_ip_address", true);
pref("services.settings.poll_interval", 86400); // 24H
pref("services.common.uptake.sampleRate", 1);   // 1%
pref("extensions.abuseReport.enabled", false);
pref("extensions.abuseReport.amoFormURL", "https://addons.mozilla.org/%LOCALE%/firefox/feedback/addon/%addonID%/");
pref("extensions.addonAbuseReport.url", "https://services.addons.mozilla.org/api/v5/abuse/report/addon/");
pref("extensions.blocklist.enabled", true);
pref("extensions.blocklist.detailsURL", "https://blocked.cdn.mozilla.net/");
pref("extensions.blocklist.itemURL", "https://blocked.cdn.mozilla.net/%blockID%.html");
pref("extensions.blocklist.addonItemURL", "https://addons.mozilla.org/%LOCALE%/firefox/blocked-addon/%addonID%/%addonVersion%/");
pref("extensions.blocklist.level", 2);
pref("extensions.eventPages.enabled", true);
pref("extensions.manifestV2.actionsPopupURLRestricted", false);
pref("extensions.manifestV3.enabled", true);
//@line 1854 "$SRCDIR/modules/libpref/init/all.js"
  pref("extensions.backgroundServiceWorker.enabled", false, locked);
//@line 1857 "$SRCDIR/modules/libpref/init/all.js"
//@line 1861 "$SRCDIR/modules/libpref/init/all.js"
  pref("extensions.openPopupWithoutUserGesture.enabled", false);
//@line 1863 "$SRCDIR/modules/libpref/init/all.js"
pref("extensions.install_origins.enabled", false);
pref("extensions.browser_style_mv3.supported", false);
pref("extensions.browser_style_mv3.same_as_mv2", false);
pref("middlemouse.paste", false);
pref("middlemouse.contentLoadURL", false);
pref("middlemouse.scrollbarPosition", false);
//@line 1876 "$SRCDIR/modules/libpref/init/all.js"
  pref("mousebutton.4th.enabled", true);
  pref("mousebutton.5th.enabled", true);
//@line 1882 "$SRCDIR/modules/libpref/init/all.js"
pref("mousewheel.default.action", 1);
pref("mousewheel.with_alt.action", 2);
pref("mousewheel.with_control.action", 3);
pref("mousewheel.with_meta.action", 1);
pref("mousewheel.with_shift.action", 4);
pref("mousewheel.default.action.override_x", -1);
pref("mousewheel.with_alt.action.override_x", -1);
pref("mousewheel.with_control.action.override_x", -1);
pref("mousewheel.with_meta.action.override_x", -1);
pref("mousewheel.with_shift.action.override_x", -1);
pref("mousewheel.default.delta_multiplier_x", 100);
pref("mousewheel.default.delta_multiplier_y", 100);
pref("mousewheel.default.delta_multiplier_z", 100);
pref("mousewheel.with_alt.delta_multiplier_x", 100);
pref("mousewheel.with_alt.delta_multiplier_y", 100);
pref("mousewheel.with_alt.delta_multiplier_z", 100);
pref("mousewheel.with_control.delta_multiplier_x", 100);
pref("mousewheel.with_control.delta_multiplier_y", 100);
pref("mousewheel.with_control.delta_multiplier_z", 100);
pref("mousewheel.with_meta.delta_multiplier_x", 100);
pref("mousewheel.with_meta.delta_multiplier_y", 100);
pref("mousewheel.with_meta.delta_multiplier_z", 100);
pref("mousewheel.with_shift.delta_multiplier_x", 100);
pref("mousewheel.with_shift.delta_multiplier_y", 100);
pref("mousewheel.with_shift.delta_multiplier_z", 100);
pref("gestures.enable_single_finger_input", true);
pref("dom.use_watchdog", true);
pref("dom.global_stop_script", true);
pref("input_event_queue.supported", true);
//@line 1949 "$SRCDIR/modules/libpref/init/all.js"
  pref("dom.ipc.processCount", 8);
//@line 1955 "$SRCDIR/modules/libpref/init/all.js"
pref("dom.ipc.processCount.file", 1);
pref("dom.ipc.processCount.extension", 1);
pref("dom.ipc.processCount.privilegedabout", 1);
pref("dom.ipc.processCount.privilegedmozilla", 1);
//@line 1973 "$SRCDIR/modules/libpref/init/all.js"
pref("dom.ipc.processCount.webIsolated", 4);
//@line 1975 "$SRCDIR/modules/libpref/init/all.js"
pref("dom.ipc.processCount.inference", 1);
pref("dom.ipc.keepProcessesAlive.privilegedabout", 1);
pref("svg.disabled", false);
pref("browser.tabs.remote.enforceRemoteTypeRestrictions", false);
pref("browser.tabs.remote.separatePrivilegedContentProcess", false);
pref("browser.tabs.remote.separatedMozillaDomains", "addons.mozilla.org,accounts.firefox.com");
pref("font.default.ar", "sans-serif");
pref("font.minimum-size.ar", 0);
pref("font.size.variable.ar", 16);
pref("font.size.monospace.ar", 13);
pref("font.default.el", "serif");
pref("font.minimum-size.el", 0);
pref("font.size.variable.el", 16);
pref("font.size.monospace.el", 13);
pref("font.default.he", "sans-serif");
pref("font.minimum-size.he", 0);
pref("font.size.variable.he", 16);
pref("font.size.monospace.he", 13);
pref("font.default.ja", "sans-serif");
pref("font.minimum-size.ja", 0);
pref("font.size.variable.ja", 16);
pref("font.size.monospace.ja", 16);
pref("font.default.ko", "sans-serif");
pref("font.minimum-size.ko", 0);
pref("font.size.variable.ko", 16);
pref("font.size.monospace.ko", 16);
pref("font.default.th", "sans-serif");
pref("font.minimum-size.th", 0);
pref("font.size.variable.th", 16);
pref("font.size.monospace.th", 13);
pref("font.default.x-cyrillic", "serif");
pref("font.minimum-size.x-cyrillic", 0);
pref("font.size.variable.x-cyrillic", 16);
pref("font.size.monospace.x-cyrillic", 13);
pref("font.default.x-devanagari", "serif");
pref("font.minimum-size.x-devanagari", 0);
pref("font.size.variable.x-devanagari", 16);
pref("font.size.monospace.x-devanagari", 13);
pref("font.default.x-tamil", "serif");
pref("font.minimum-size.x-tamil", 0);
pref("font.size.variable.x-tamil", 16);
pref("font.size.monospace.x-tamil", 13);
pref("font.default.x-armn", "serif");
pref("font.minimum-size.x-armn", 0);
pref("font.size.variable.x-armn", 16);
pref("font.size.monospace.x-armn", 13);
pref("font.default.x-beng", "serif");
pref("font.minimum-size.x-beng", 0);
pref("font.size.variable.x-beng", 16);
pref("font.size.monospace.x-beng", 13);
pref("font.default.x-cans", "serif");
pref("font.minimum-size.x-cans", 0);
pref("font.size.variable.x-cans", 16);
pref("font.size.monospace.x-cans", 13);
pref("font.default.x-ethi", "serif");
pref("font.minimum-size.x-ethi", 0);
pref("font.size.variable.x-ethi", 16);
pref("font.size.monospace.x-ethi", 13);
pref("font.default.x-geor", "serif");
pref("font.minimum-size.x-geor", 0);
pref("font.size.variable.x-geor", 16);
pref("font.size.monospace.x-geor", 13);
pref("font.default.x-gujr", "serif");
pref("font.minimum-size.x-gujr", 0);
pref("font.size.variable.x-gujr", 16);
pref("font.size.monospace.x-gujr", 13);
pref("font.default.x-guru", "serif");
pref("font.minimum-size.x-guru", 0);
pref("font.size.variable.x-guru", 16);
pref("font.size.monospace.x-guru", 13);
pref("font.default.x-khmr", "serif");
pref("font.minimum-size.x-khmr", 0);
pref("font.size.variable.x-khmr", 16);
pref("font.size.monospace.x-khmr", 13);
pref("font.default.x-mlym", "serif");
pref("font.minimum-size.x-mlym", 0);
pref("font.size.variable.x-mlym", 16);
pref("font.size.monospace.x-mlym", 13);
pref("font.default.x-orya", "serif");
pref("font.minimum-size.x-orya", 0);
pref("font.size.variable.x-orya", 16);
pref("font.size.monospace.x-orya", 13);
pref("font.default.x-telu", "serif");
pref("font.minimum-size.x-telu", 0);
pref("font.size.variable.x-telu", 16);
pref("font.size.monospace.x-telu", 13);
pref("font.default.x-knda", "serif");
pref("font.minimum-size.x-knda", 0);
pref("font.size.variable.x-knda", 16);
pref("font.size.monospace.x-knda", 13);
pref("font.default.x-sinh", "serif");
pref("font.minimum-size.x-sinh", 0);
pref("font.size.variable.x-sinh", 16);
pref("font.size.monospace.x-sinh", 13);
pref("font.default.x-tibt", "serif");
pref("font.minimum-size.x-tibt", 0);
pref("font.size.variable.x-tibt", 16);
pref("font.size.monospace.x-tibt", 13);
pref("font.default.x-unicode", "serif");
pref("font.minimum-size.x-unicode", 0);
pref("font.size.variable.x-unicode", 16);
pref("font.size.monospace.x-unicode", 13);
pref("font.default.x-western", "serif");
pref("font.minimum-size.x-western", 0);
pref("font.size.variable.x-western", 16);
pref("font.size.monospace.x-western", 13);
pref("font.default.zh-CN", "sans-serif");
pref("font.minimum-size.zh-CN", 0);
pref("font.size.variable.zh-CN", 16);
pref("font.size.monospace.zh-CN", 16);
pref("font.default.zh-HK", "sans-serif");
pref("font.minimum-size.zh-HK", 0);
pref("font.size.variable.zh-HK", 16);
pref("font.size.monospace.zh-HK", 16);
pref("font.default.zh-TW", "sans-serif");
pref("font.minimum-size.zh-TW", 0);
pref("font.size.variable.zh-TW", 16);
pref("font.size.monospace.zh-TW", 16);
pref("font.default.x-math", "serif");
pref("font.minimum-size.x-math", 0);
pref("font.size.variable.x-math", 16);
pref("font.size.monospace.x-math", 13);
//@line 2421 "$SRCDIR/modules/libpref/init/all.js"
//@line 2619 "$SRCDIR/modules/libpref/init/all.js"
//@line 2752 "$SRCDIR/modules/libpref/init/all.js"
//@line 2782 "$SRCDIR/modules/libpref/init/all.js"
//@line 2784 "$SRCDIR/modules/libpref/init/all.js"
  pref("network.protocol-handler.warn-external.file", false);
  pref("browser.drag_out_of_frame_style", 1);
  pref("middlemouse.paste", true);
  pref("middlemouse.scrollbarPosition", true);
  pref("helpers.global_mime_types_file", "/etc/mime.types");
  pref("helpers.global_mailcap_file", "/etc/mailcap");
  pref("helpers.private_mime_types_file", "~/.mime.types");
  pref("helpers.private_mailcap_file", "~/.mailcap");
  pref("font.name-list.emoji", "Twemoji Mozilla");
  pref("font.name-list.serif.ar", "serif");
  pref("font.name-list.sans-serif.ar", "sans-serif");
  pref("font.name-list.monospace.ar", "monospace");
  pref("font.name-list.cursive.ar", "cursive");
  pref("font.size.monospace.ar", 12);
  pref("font.name-list.serif.el", "serif");
  pref("font.name-list.sans-serif.el", "sans-serif");
  pref("font.name-list.monospace.el", "monospace");
  pref("font.name-list.cursive.el", "cursive");
  pref("font.size.monospace.el", 12);
  pref("font.name-list.serif.he", "serif");
  pref("font.name-list.sans-serif.he", "sans-serif");
  pref("font.name-list.monospace.he", "monospace");
  pref("font.name-list.cursive.he", "cursive");
  pref("font.size.monospace.he", 12);
  pref("font.name-list.serif.ja", "serif");
  pref("font.name-list.sans-serif.ja", "sans-serif");
  pref("font.name-list.monospace.ja", "monospace");
  pref("font.name-list.cursive.ja", "cursive");
  pref("font.name-list.serif.ko", "serif");
  pref("font.name-list.sans-serif.ko", "sans-serif");
  pref("font.name-list.monospace.ko", "monospace");
  pref("font.name-list.cursive.ko", "cursive");
  pref("font.name-list.serif.th", "serif");
  pref("font.name-list.sans-serif.th", "sans-serif");
  pref("font.name-list.monospace.th", "monospace");
  pref("font.name-list.cursive.th", "cursive");
  pref("font.minimum-size.th", 13);
  pref("font.name-list.serif.x-armn", "serif");
  pref("font.name-list.sans-serif.x-armn", "sans-serif");
  pref("font.name-list.monospace.x-armn", "monospace");
  pref("font.name-list.cursive.x-armn", "cursive");
  pref("font.name-list.serif.x-beng", "serif");
  pref("font.name-list.sans-serif.x-beng", "sans-serif");
  pref("font.name-list.monospace.x-beng", "monospace");
  pref("font.name-list.cursive.x-beng", "cursive");
  pref("font.name-list.serif.x-cans", "serif");
  pref("font.name-list.sans-serif.x-cans", "sans-serif");
  pref("font.name-list.monospace.x-cans", "monospace");
  pref("font.name-list.cursive.x-cans", "cursive");
  pref("font.name-list.serif.x-cyrillic", "serif");
  pref("font.name-list.sans-serif.x-cyrillic", "sans-serif");
  pref("font.name-list.monospace.x-cyrillic", "monospace");
  pref("font.name-list.cursive.x-cyrillic", "cursive");
  pref("font.size.monospace.x-cyrillic", 12);
  pref("font.name-list.serif.x-devanagari", "serif");
  pref("font.name-list.sans-serif.x-devanagari", "sans-serif");
  pref("font.name-list.monospace.x-devanagari", "monospace");
  pref("font.name-list.cursive.x-devanagari", "cursive");
  pref("font.name-list.serif.x-ethi", "serif");
  pref("font.name-list.sans-serif.x-ethi", "sans-serif");
  pref("font.name-list.monospace.x-ethi", "monospace");
  pref("font.name-list.cursive.x-ethi", "cursive");
  pref("font.name-list.serif.x-geor", "serif");
  pref("font.name-list.sans-serif.x-geor", "sans-serif");
  pref("font.name-list.monospace.x-geor", "monospace");
  pref("font.name-list.cursive.x-geor", "cursive");
  pref("font.name-list.serif.x-gujr", "serif");
  pref("font.name-list.sans-serif.x-gujr", "sans-serif");
  pref("font.name-list.monospace.x-gujr", "monospace");
  pref("font.name-list.cursive.x-gujr", "cursive");
  pref("font.name-list.serif.x-guru", "serif");
  pref("font.name-list.sans-serif.x-guru", "sans-serif");
  pref("font.name-list.monospace.x-guru", "monospace");
  pref("font.name-list.cursive.x-guru", "cursive");
  pref("font.name-list.serif.x-khmr", "serif");
  pref("font.name-list.sans-serif.x-khmr", "sans-serif");
  pref("font.name-list.monospace.x-khmr", "monospace");
  pref("font.name-list.cursive.x-khmr", "cursive");
  pref("font.name-list.serif.x-knda", "serif");
  pref("font.name-list.sans-serif.x-knda", "sans-serif");
  pref("font.name-list.monospace.x-knda", "monospace");
  pref("font.name-list.cursive.x-knda", "cursive");
  pref("font.name-list.serif.x-mlym", "serif");
  pref("font.name-list.sans-serif.x-mlym", "sans-serif");
  pref("font.name-list.monospace.x-mlym", "monospace");
  pref("font.name-list.cursive.x-mlym", "cursive");
  pref("font.name-list.serif.x-orya", "serif");
  pref("font.name-list.sans-serif.x-orya", "sans-serif");
  pref("font.name-list.monospace.x-orya", "monospace");
  pref("font.name-list.cursive.x-orya", "cursive");
  pref("font.name-list.serif.x-sinh", "serif");
  pref("font.name-list.sans-serif.x-sinh", "sans-serif");
  pref("font.name-list.monospace.x-sinh", "monospace");
  pref("font.name-list.cursive.x-sinh", "cursive");
  pref("font.name-list.serif.x-tamil", "serif");
  pref("font.name-list.sans-serif.x-tamil", "sans-serif");
  pref("font.name-list.monospace.x-tamil", "monospace");
  pref("font.name-list.cursive.x-tamil", "cursive");
  pref("font.name-list.serif.x-telu", "serif");
  pref("font.name-list.sans-serif.x-telu", "sans-serif");
  pref("font.name-list.monospace.x-telu", "monospace");
  pref("font.name-list.cursive.x-telu", "cursive");
  pref("font.name-list.serif.x-tibt", "serif");
  pref("font.name-list.sans-serif.x-tibt", "sans-serif");
  pref("font.name-list.monospace.x-tibt", "monospace");
  pref("font.name-list.cursive.x-tibt", "cursive");
  pref("font.name-list.serif.x-unicode", "serif");
  pref("font.name-list.sans-serif.x-unicode", "sans-serif");
  pref("font.name-list.monospace.x-unicode", "monospace");
  pref("font.name-list.cursive.x-unicode", "cursive");
  pref("font.size.monospace.x-unicode", 12);
  pref("font.name-list.serif.x-western", "serif");
  pref("font.name-list.sans-serif.x-western", "sans-serif");
  pref("font.name-list.monospace.x-western", "monospace");
  pref("font.name-list.cursive.x-western", "cursive");
  pref("font.size.monospace.x-western", 12);
  pref("font.name-list.serif.zh-CN", "serif");
  pref("font.name-list.sans-serif.zh-CN", "sans-serif");
  pref("font.name-list.monospace.zh-CN", "monospace");
  pref("font.name-list.cursive.zh-CN", "cursive");
  pref("font.name-list.serif.zh-HK", "serif");
  pref("font.name-list.sans-serif.zh-HK", "sans-serif");
  pref("font.name-list.monospace.zh-HK", "monospace");
  pref("font.name-list.cursive.zh-HK", "cursive");
  pref("font.name-list.serif.zh-TW", "serif");
  pref("font.name-list.sans-serif.zh-TW", "sans-serif");
  pref("font.name-list.monospace.zh-TW", "monospace");
  pref("font.name-list.cursive.zh-TW", "cursive");
  pref("ui.panel.default_level_parent", true);
  pref("intl.ime.use_simple_context_on_password_field", false);
  pref("intl.ime.hack.uim.using_key_snooper", true);
//@line 2976 "$SRCDIR/modules/libpref/init/all.js"
//@line 2996 "$SRCDIR/modules/libpref/init/all.js"
//@line 3099 "$SRCDIR/modules/libpref/init/all.js"
pref("signon.rememberSignons",              true);
pref("signon.rememberSignons.visibilityToggle", true);
pref("signon.autofillForms",                true);
pref("signon.autofillForms.autocompleteOff", true);
pref("signon.autofillForms.http",           false);
pref("signon.autologin.proxy",              false);
pref("signon.capture.inputChanges.enabled", true);
pref("signon.formlessCapture.enabled",      true);
pref("signon.formRemovalCapture.enabled",   true);
pref("signon.generation.available",               true);
pref("signon.improvedPasswordRules.enabled", true);
pref("signon.backup.enabled",               true);
pref("signon.generation.confidenceThreshold",     "0.75");
pref("signon.generation.enabled",                 true);
pref("signon.passwordEditCapture.enabled",        false);
pref("signon.privateBrowsingCapture.enabled",     true);
pref("signon.storeWhenAutocompleteOff",     true);
pref("signon.userInputRequiredToCapture.enabled", true);
pref("signon.usernameOnlyForm.lookupThreshold",  5);
pref("signon.debug",                        false);
pref("signon.recipes.path", "resource://app/defaults/settings/main/password-recipes.json");
pref("signon.recipes.remoteRecipes.enabled", true);
pref("signon.relatedRealms.enabled", false);
pref("signon.schemeUpgrades",                     true);
pref("signon.includeOtherSubdomainsInLookup",     true);
pref("signon.masterPasswordReprompt.timeout_ms", 900000); // 15 Minutes
pref("signon.showAutoCompleteFooter",             false);
pref("signon.firefoxRelay.base_url", "https://relay.firefox.com/api/v1/");
pref("signon.firefoxRelay.learn_more_url", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/firefox-relay-integration");
pref("signon.firefoxRelay.manage_url", "https://relay.firefox.com/accounts/profile/?utm_medium=firefox-desktop&utm_source=modal&utm_campaign=limit&utm_content=manage-masks-global");
pref("signon.firefoxRelay.terms_of_service_url", "https://www.mozilla.org/%LOCALE%/about/legal/terms/subscription-services/");
pref("signon.firefoxRelay.privacy_policy_url", "https://www.mozilla.org/%LOCALE%/privacy/subscription-services/");
pref("signon.signupDetection.confidenceThreshold",     "0.75");
pref("browser.formfill.debug",            false);
pref("browser.formfill.enable",           true);
pref("browser.formfill.expire_days",      180);
pref("browser.formfill.agedWeight",       2);
pref("browser.formfill.bucketSize",       1);
pref("browser.formfill.maxTimeGroupings", 25);
pref("browser.formfill.timeGroupingSize", 604800);
pref("browser.formfill.boundaryWeight",   25);
pref("browser.formfill.prefixWeight",     5);
pref("browser.zoom.full", false);
pref("toolkit.zoomManager.zoomValues", ".3,.5,.67,.8,.9,1,1.1,1.2,1.33,1.5,1.7,2,2.4,3,4,5");
pref("image.http.accept", "");
pref("network.tcp.keepalive.enabled", true);
pref("network.tcp.keepalive.idle_time", 600); // seconds; 10 mins
//@line 3174 "$SRCDIR/modules/libpref/init/all.js"
  pref("network.tcp.keepalive.retry_interval", 1); // seconds
//@line 3176 "$SRCDIR/modules/libpref/init/all.js"
//@line 3179 "$SRCDIR/modules/libpref/init/all.js"
  pref("network.tcp.keepalive.probe_count", 4);
//@line 3181 "$SRCDIR/modules/libpref/init/all.js"
pref("network.psl.onUpdate_notify", false);
//@line 3189 "$SRCDIR/modules/libpref/init/all.js"
  pref("widget.disable-workspace-management", false);
//@line 3191 "$SRCDIR/modules/libpref/init/all.js"
pref("geo.provider.network.url", "https://www.googleapis.com/geolocation/v1/geolocate?key=%GOOGLE_LOCATION_SERVICE_API_KEY%");
pref("geo.provider.network.timeToWaitBeforeSending", 5000);
pref("geo.provider.network.timeout", 60000);
//@line 3204 "$SRCDIR/modules/libpref/init/all.js"
//@line 3209 "$SRCDIR/modules/libpref/init/all.js"
pref("browser.region.log", false);
pref("browser.region.network.url", "https://location.services.mozilla.com/v1/country?key=%MOZILLA_API_KEY%");
pref("browser.region.network.scan", false);
pref("browser.region.timeout", 5000);
pref("browser.region.update.enabled", true);
pref("browser.meta_refresh_when_inactive.disabled", false);
pref("xpinstall.whitelist.required", true);
pref("xpinstall.signatures.required", false);
pref("extensions.langpacks.signatures.required", false);
pref("extensions.webExtensionsMinPlatformVersion", "42.0a1");
pref("extensions.experiments.enabled", true);
pref("extensions.webextensions.keepStorageOnUninstall", false);
pref("extensions.webextensions.keepUuidOnUninstall", false);
pref("extensions.webextensions.identity.redirectDomain", "extensions.allizom.org");
pref("extensions.webextensions.restrictedDomains", "accounts-static.cdn.mozilla.net,accounts.firefox.com,addons.cdn.mozilla.net,addons.mozilla.org,api.accounts.firefox.com,content.cdn.mozilla.net,discovery.addons.mozilla.org,install.mozilla.org,oauth.accounts.firefox.com,profile.accounts.firefox.com,support.mozilla.org,sync.services.mozilla.com");
pref("extensions.quarantinedDomains.enabled", true);
pref("extensions.quarantinedDomains.list", "");
pref("extensions.originControls.grantByDefault", true);
pref("extensions.webextensions.protocol.remote", true);
pref("extensions.webextensions.userScripts.enabled", true);
pref("extensions.webextensions.ExtensionStorageIDB.enabled", true);
pref("extensions.htmlaboutaddons.inline-options.enabled", true);
pref("extensions.htmlaboutaddons.recommendations.enabled", true);
pref("extensions.recommendations.privacyPolicyUrl", "");
pref("extensions.recommendations.themeRecommendationUrl", "");
pref("extensions.webcompat-reporter.newIssueEndpoint", "https://webcompat.com/issues/new");
//@line 3272 "$SRCDIR/modules/libpref/init/all.js"
  pref("extensions.webcompat-reporter.enabled", false);
//@line 3274 "$SRCDIR/modules/libpref/init/all.js"
pref("extensions.webextensions.base-content-security-policy", "script-src 'self' https://* http://localhost:* http://127.0.0.1:* moz-extension: blob: filesystem: 'unsafe-eval' 'wasm-unsafe-eval' 'unsafe-inline';");
pref("extensions.webextensions.base-content-security-policy.v3", "script-src 'self' 'wasm-unsafe-eval';");
pref("extensions.webextensions.default-content-security-policy", "script-src 'self' 'wasm-unsafe-eval';");
pref("extensions.webextensions.default-content-security-policy.v3", "script-src 'self'; upgrade-insecure-requests;");
pref("network.buffer.cache.count", 24);
pref("network.buffer.cache.size",  32768);
pref("dom.webnotifications.requireinteraction.count", 3);
pref("alerts.showFavicons", false);
//@line 3296 "$SRCDIR/modules/libpref/init/all.js"
//@line 3301 "$SRCDIR/modules/libpref/init/all.js"
  pref("full-screen-api.transition-duration.enter", "0 0");
  pref("full-screen-api.transition-duration.leave", "0 0");
//@line 3304 "$SRCDIR/modules/libpref/init/all.js"
pref("full-screen-api.transition.timeout", 1000);
pref("full-screen-api.warning.timeout", 3000);
pref("full-screen-api.warning.delay", 500);
pref("pointer-lock-api.warning.timeout", 3000);
pref("dom.push.loglevel", "Error");
pref("dom.push.serverURL", "wss://push.services.mozilla.com/");
pref("dom.push.userAgentID", "");
pref("dom.push.maxQuotaPerSubscription", 16);
pref("dom.push.maxRecentMessageIDsPerSubscription", 10);
pref("dom.push.quotaUpdateDelay", 3000); // 3 seconds
pref("dom.push.connection.enabled", true);
pref("dom.push.retryBaseInterval", 5000);
pref("dom.push.pingInterval", 1800000); // 30 minutes
pref("dom.push.requestTimeout", 10000);
pref("dom.push.http2.reset_retry_count_after_ms", 60000);
pref("dom.push.http2.maxRetries", 2);
pref("dom.push.http2.retryInterval", 5000);
pref("memory.ghost_window_timeout_seconds", 60);
pref("memory.dump_reports_on_oom", false);
pref("memory.blob_report.stack_frames", 0);
pref("gfx.vr.osvr.utilLibPath", "");
pref("gfx.vr.osvr.commonLibPath", "");
pref("gfx.vr.osvr.clientLibPath", "");
pref("gfx.vr.osvr.clientKitLibPath", "");
pref("memory_info_dumper.watch_fifo.enabled", false);
pref("network.captive-portal-service.minInterval", 60000); // 60 seconds
pref("network.captive-portal-service.maxInterval", 1500000); // 25 minutes
pref("network.captive-portal-service.backoffFactor", "5.0");
pref("network.captive-portal-service.enabled", false);
pref("network.connectivity-service.enabled", true);
pref("network.connectivity-service.DNSv4.domain", "example.org");
pref("network.connectivity-service.DNSv6.domain", "example.org");
pref("network.connectivity-service.DNS_HTTPS.domain", "cloudflare-dns.com");
pref("network.connectivity-service.IPv4.url", "http://detectportal.firefox.com/success.txt?ipv4");
pref("network.connectivity-service.IPv6.url", "http://detectportal.firefox.com/success.txt?ipv6");
pref("network.trr.uri", "");
pref("network.trr.credentials", "");
pref("network.trr.custom_uri", "");
pref("network.trr.confirmationNS", "example.com");
pref("network.trr.excluded-domains", "");
pref("network.trr.builtin-excluded-domains", "localhost,local");
pref("network.trr_ui.show_fallback_warning_option", false);
pref("captivedetect.canonicalURL", "http://detectportal.firefox.com/canonical.html");
pref("captivedetect.canonicalContent", "<meta http-equiv=\"refresh\" content=\"0;url=https://support.mozilla.org/kb/captive-portal\"/>");
pref("captivedetect.maxWaitingTime", 5000);
pref("captivedetect.pollingTime", 3000);
pref("captivedetect.maxRetryCount", 5);
pref("urlclassifier.malwareTable", "goog-malware-proto,goog-unwanted-proto,moztest-harmful-simple,moztest-malware-simple,moztest-unwanted-simple");
//@line 3413 "$SRCDIR/modules/libpref/init/all.js"
  pref("urlclassifier.phishTable", "goog-phish-proto,moztest-phish-simple");
//@line 3419 "$SRCDIR/modules/libpref/init/all.js"
pref("urlclassifier.downloadAllowTable", "goog-downloadwhite-proto");
pref("urlclassifier.downloadBlockTable", "goog-badbinurl-proto");
pref("urlclassifier.trackingAnnotationTable", "moztest-track-simple,ads-track-digest256,social-track-digest256,analytics-track-digest256,content-track-digest256");
pref("urlclassifier.trackingAnnotationWhitelistTable", "moztest-trackwhite-simple,mozstd-trackwhite-digest256,google-trackwhite-digest256");
pref("urlclassifier.trackingTable", "moztest-track-simple,ads-track-digest256,social-track-digest256,analytics-track-digest256");
pref("urlclassifier.trackingWhitelistTable", "moztest-trackwhite-simple,mozstd-trackwhite-digest256,google-trackwhite-digest256");
pref("urlclassifier.features.fingerprinting.blacklistTables", "base-fingerprinting-track-digest256");
pref("urlclassifier.features.fingerprinting.whitelistTables", "mozstd-trackwhite-digest256,google-trackwhite-digest256");
pref("urlclassifier.features.fingerprinting.annotate.blacklistTables", "base-fingerprinting-track-digest256");
pref("urlclassifier.features.fingerprinting.annotate.whitelistTables", "mozstd-trackwhite-digest256,google-trackwhite-digest256");
pref("urlclassifier.features.cryptomining.blacklistTables", "base-cryptomining-track-digest256");
pref("urlclassifier.features.cryptomining.whitelistTables", "mozstd-trackwhite-digest256");
pref("urlclassifier.features.cryptomining.annotate.blacklistTables", "base-cryptomining-track-digest256");
pref("urlclassifier.features.cryptomining.annotate.whitelistTables", "mozstd-trackwhite-digest256");
pref("urlclassifier.features.socialtracking.blacklistTables", "social-tracking-protection-facebook-digest256,social-tracking-protection-linkedin-digest256,social-tracking-protection-twitter-digest256");
pref("urlclassifier.features.socialtracking.whitelistTables", "mozstd-trackwhite-digest256,google-trackwhite-digest256");
pref("urlclassifier.features.socialtracking.annotate.blacklistTables", "social-tracking-protection-facebook-digest256,social-tracking-protection-linkedin-digest256,social-tracking-protection-twitter-digest256");
pref("urlclassifier.features.socialtracking.annotate.whitelistTables", "mozstd-trackwhite-digest256,google-trackwhite-digest256");
pref("urlclassifier.features.emailtracking.blocklistTables", "base-email-track-digest256");
pref("urlclassifier.features.emailtracking.allowlistTables", "mozstd-trackwhite-digest256");
pref("urlclassifier.features.emailtracking.datacollection.blocklistTables", "base-email-track-digest256,content-email-track-digest256");
pref("urlclassifier.features.emailtracking.datacollection.allowlistTables", "mozstd-trackwhite-digest256");
pref("urlclassifier.disallow_completions", "goog-downloadwhite-digest256,base-track-digest256,mozstd-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,ads-track-digest256,social-track-digest256,analytics-track-digest256,base-fingerprinting-track-digest256,content-fingerprinting-track-digest256,base-cryptomining-track-digest256,content-cryptomining-track-digest256,fanboyannoyance-ads-digest256,fanboysocial-ads-digest256,easylist-ads-digest256,easyprivacy-ads-digest256,adguard-ads-digest256,social-tracking-protection-digest256,social-tracking-protection-facebook-digest256,social-tracking-protection-linkedin-digest256,social-tracking-protection-twitter-digest256,base-email-track-digest256,content-email-track-digest256");
pref("urlclassifier.trackingAnnotationSkipURLs", "google.com/recaptcha/,*.google.com/recaptcha/,d3vox9szr7t2nm.cloudfront.net");
pref("privacy.rejectForeign.allowList", "");
pref("privacy.trackingprotection.emailtracking.webapp.domains", "mail.163.com,mail.aol.com,fastmail.com,webmail.gandi.net,mail.google.com,navigator-bs.gmx.com,app.hey.com,horde.org/apps/webmail,hushmail.com,icloud.com/mail,kolabnow.com,laposte.net/accueil,mail.lycos.com,mail.com/mail/,mail.ru,mailfence.com,outlook.live.com,email-postaci.com/,posteo.de,mail.protonmail.com,app.rackspace.com,mail.rediff.com,emailmg.ipage.com,runbox.com,mail.sina.com.cn,tutanota.com,mail.yahoo.com,mail.yandex.com,mail.zimbra.com,zoho.com/mail/");
pref("urlclassifier.gethashnoise", 4);
pref("urlclassifier.gethash.timeout_ms", 5000);
pref("urlclassifier.alternate_error_page", "blocked");
pref("browser.safebrowsing.debug", false);
pref("browser.safebrowsing.allowOverride", true);
//@line 3475 "$SRCDIR/modules/libpref/init/all.js"
  pref("browser.safebrowsing.id", "navclient-auto-ffox");
//@line 3479 "$SRCDIR/modules/libpref/init/all.js"
pref("browser.safebrowsing.downloads.enabled", true);
pref("browser.safebrowsing.downloads.remote.enabled", true);
pref("browser.safebrowsing.downloads.remote.timeout_ms", 15000);
pref("browser.safebrowsing.downloads.remote.url", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_SAFEBROWSING_API_KEY%");
pref("browser.safebrowsing.downloads.remote.block_dangerous",            true);
pref("browser.safebrowsing.downloads.remote.block_dangerous_host",       true);
pref("browser.safebrowsing.downloads.remote.block_potentially_unwanted", true);
pref("browser.safebrowsing.downloads.remote.block_uncommon",             true);
//@line 3492 "$SRCDIR/modules/libpref/init/all.js"
pref("browser.safebrowsing.provider.google.pver", "2.2");
pref("browser.safebrowsing.provider.google.lists", "goog-badbinurl-shavar,goog-downloadwhite-digest256,goog-phish-shavar,googpub-phish-shavar,goog-malware-shavar,goog-unwanted-shavar");
pref("browser.safebrowsing.provider.google.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2&key=%GOOGLE_SAFEBROWSING_API_KEY%");
pref("browser.safebrowsing.provider.google.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2");
pref("browser.safebrowsing.provider.google.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?site=");
pref("browser.safebrowsing.provider.google.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?url=");
pref("browser.safebrowsing.provider.google.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?url=");
pref("browser.safebrowsing.provider.google.advisoryURL", "https://developers.google.com/safe-browsing/v4/advisory");
pref("browser.safebrowsing.provider.google.advisoryName", "Google Safe Browsing");
pref("browser.safebrowsing.provider.google4.pver", "4");
pref("browser.safebrowsing.provider.google4.lists", "goog-badbinurl-proto,goog-downloadwhite-proto,goog-phish-proto,googpub-phish-proto,goog-malware-proto,goog-unwanted-proto,goog-harmful-proto");
pref("browser.safebrowsing.provider.google4.updateURL", "https://safebrowsing.googleapis.com/v4/threatListUpdates:fetch?$ct=application/x-protobuf&key=%GOOGLE_SAFEBROWSING_API_KEY%&$httpMethod=POST");
pref("browser.safebrowsing.provider.google4.gethashURL", "https://safebrowsing.googleapis.com/v4/fullHashes:find?$ct=application/x-protobuf&key=%GOOGLE_SAFEBROWSING_API_KEY%&$httpMethod=POST");
pref("browser.safebrowsing.provider.google4.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?site=");
pref("browser.safebrowsing.provider.google4.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?url=");
pref("browser.safebrowsing.provider.google4.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?url=");
pref("browser.safebrowsing.provider.google4.advisoryURL", "https://developers.google.com/safe-browsing/v4/advisory");
pref("browser.safebrowsing.provider.google4.advisoryName", "Google Safe Browsing");
pref("browser.safebrowsing.provider.google4.dataSharingURL", "https://safebrowsing.googleapis.com/v4/threatHits?$ct=application/x-protobuf&key=%GOOGLE_SAFEBROWSING_API_KEY%&$httpMethod=POST");
pref("browser.safebrowsing.provider.google4.dataSharing.enabled", false);
//@line 3518 "$SRCDIR/modules/libpref/init/all.js"
pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?url=");
pref("browser.safebrowsing.provider.mozilla.pver", "2.2");
pref("browser.safebrowsing.provider.mozilla.lists", "base-track-digest256,mozstd-trackwhite-digest256,google-trackwhite-digest256,content-track-digest256,mozplugin-block-digest256,mozplugin2-block-digest256,ads-track-digest256,social-track-digest256,analytics-track-digest256,base-fingerprinting-track-digest256,content-fingerprinting-track-digest256,base-cryptomining-track-digest256,content-cryptomining-track-digest256,fanboyannoyance-ads-digest256,fanboysocial-ads-digest256,easylist-ads-digest256,easyprivacy-ads-digest256,adguard-ads-digest256,social-tracking-protection-digest256,social-tracking-protection-facebook-digest256,social-tracking-protection-linkedin-digest256,social-tracking-protection-twitter-digest256,base-email-track-digest256,content-email-track-digest256");
pref("browser.safebrowsing.provider.mozilla.updateURL", "moz-sbrs:://antitracking");
pref("browser.safebrowsing.provider.mozilla.gethashURL", "https://shavar.services.mozilla.com/gethash?client=SAFEBROWSING_ID&appver=%MAJOR_VERSION%&pver=2.2");
pref("browser.safebrowsing.provider.mozilla.nextupdatetime", "1");
pref("browser.safebrowsing.provider.mozilla.lists.base", "moz-std");
pref("browser.safebrowsing.provider.mozilla.lists.content", "moz-full");
//@line 3537 "$SRCDIR/modules/libpref/init/all.js"
  pref("urlclassifier.blockedTable", "moztest-block-simple");
//@line 3539 "$SRCDIR/modules/libpref/init/all.js"
//@line 3548 "$SRCDIR/modules/libpref/init/all.js"
pref("browser.search.log", false);
pref("browser.search.update", true);
pref("browser.search.suggest.enabled", true);
pref("browser.search.suggest.enabled.private", false);
pref("browser.search.separatePrivateDefault", true);
pref("browser.search.separatePrivateDefault.ui.enabled", false);
pref("browser.search.removeEngineInfobar.enabled", true);
pref("media.gmp-manager.allowLocalSources", true);
pref("media.gmp-manager.url", "https://aus5.mozilla.org/update/3/GMP/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
pref("media.gmp-manager.checkContentSignature", true);
pref("media.gmp-manager.cert.requireBuiltIn", true);
pref("media.gmp-manager.cert.checkAttributes", true);
pref("media.gmp-manager.certs.1.issuerName", "CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US");
pref("media.gmp-manager.certs.1.commonName", "aus5.mozilla.org");
pref("media.gmp-manager.certs.2.issuerName", "CN=thawte SSL CA - G2,O=\"thawte, Inc.\",C=US");
pref("media.gmp-manager.certs.2.commonName", "aus5.mozilla.org");
pref("reader.parse-on-load.enabled", true);
pref("reader.parse-node-limit", 3000);
pref("reader.errors.includeURLs", false);
pref("reader.font_size", 5);
pref("reader.font_type", "sans-serif");
pref("reader.font_type.values", "[\"sans-serif\",\"serif\",\"monospace\"]");
pref("reader.font_weight", "regular");
pref("reader.font_weight.values", "[\"regular\",\"light\",\"bold\"]");
pref("reader.content_width", 3);
pref("reader.line_height", 4);
pref("reader.improved_text_menu.enabled", true);
pref("reader.character_spacing", 0);
pref("reader.word_spacing", 0);
pref("reader.text_alignment", "start");
pref("reader.color_scheme", "auto");
pref("reader.color_scheme.values", "[\"auto\",\"light\",\"dark\",\"sepia\",\"contrast\",\"gray\"]");
pref("reader.colors_menu.enabled", true);
pref("reader.custom_colors.foreground", "");
pref("reader.custom_colors.background", "");
pref("reader.custom_colors.unvisited-links", "");
pref("reader.custom_colors.visited-links", "");
pref("reader.custom_colors.selection-highlight", "");
pref("reader.toolbar.vertical", true);
//@line 3684 "$SRCDIR/modules/libpref/init/all.js"
  pref("narrate.enabled", true);
//@line 3688 "$SRCDIR/modules/libpref/init/all.js"
pref("narrate.test", false);
pref("narrate.rate", 0);
pref("narrate.voice", " { \"default\": \"automatic\" }");
pref("narrate.filter-voices", true);
pref("memory.report_concurrency", 10);
pref("toolkit.pageThumbs.screenSizeDivisor", 7);
pref("toolkit.pageThumbs.minWidth", 0);
pref("toolkit.pageThumbs.minHeight", 0);
pref("webextensions.tests", false);
pref("webextensions.webRequest.requestBodyMaxRawBytes", 16777216);
//@line 3709 "$SRCDIR/modules/libpref/init/all.js"
  pref("webextensions.storage.session.enforceQuota", false);
//@line 3711 "$SRCDIR/modules/libpref/init/all.js"
pref("webextensions.storage.sync.enabled", true);
pref("webextensions.storage.sync.kinto", false);
pref("webextensions.storage.sync.serverURL", "https://webextensions.settings.services.mozilla.com/v1");
pref("dom.input.fallbackUploadDir", "");
//@line 3725 "$SRCDIR/modules/libpref/init/all.js"
  pref("plugins.rewrite_youtube_embeds", true);
//@line 3727 "$SRCDIR/modules/libpref/init/all.js"
pref("media.default_volume", "1.0");
pref("dom.storageManager.prompt.testing", false);
pref("dom.storageManager.prompt.testing.allow", false);
pref("browser.storageManager.pressureNotification.minIntervalMS", 1200000);
pref("browser.storageManager.pressureNotification.usageThresholdGB", 5);
pref("browser.sanitizer.loglevel", "Warn");
pref("browser.translations.enable", false);
pref("browser.translations.select.enable", false);
pref("browser.translations.logLevel", "Error");
pref("browser.translations.mostRecentTargetLanguages", "");
pref("browser.translations.alwaysTranslateLanguages", "");
pref("browser.translations.neverTranslateLanguages", "");
pref("browser.translations.useHTML", false);
pref("browser.translations.automaticallyPopup", true);
pref("browser.translations.simulateUnsupportedEngine", false);
pref("browser.translations.chaos.errors", false);
pref("browser.translations.chaos.timeoutMS", 0);
pref("browser.ml.enable", false);
pref("browser.ml.logLevel", "Error");
pref("browser.ml.modelHubRootUrl", "https://model-hub.mozilla.org/");
pref("browser.ml.modelHubUrlTemplate", "{model}/{revision}");
pref("browser.ml.modelCacheMaxSizeBytes", 1073741824);
pref("browser.ml.modelCacheTimeout", 120000);
pref("prompts.authentication_dialog_abuse_limit", 2);
pref("dom.payments.request.supportedRegions", "US,CA");
//@line 3807 "$SRCDIR/modules/libpref/init/all.js"
pref("toolkit.aboutProcesses.showAllSubframes", false);
//@line 3814 "$SRCDIR/modules/libpref/init/all.js"
  pref("toolkit.aboutProcesses.showThreads", false);
//@line 3816 "$SRCDIR/modules/libpref/init/all.js"
pref("toolkit.aboutProcesses.showProfilerIcons", true);
pref("toolkit.aboutProcesses.profileDuration", 5);
//@line 3825 "$SRCDIR/modules/libpref/init/all.js"
  pref("toolkit.crashreporter.include_context_heap", false);
//@line 3829 "$SRCDIR/modules/libpref/init/all.js"
pref("toolkit.legacyUserProfileCustomizations.stylesheets", false);
//@line 3837 "$SRCDIR/modules/libpref/init/all.js"
  pref("datareporting.policy.dataSubmissionEnabled", true);
  pref("datareporting.policy.dataSubmissionPolicyNotifiedTime", "0");
  pref("datareporting.policy.dataSubmissionPolicyAcceptedVersion", 0);
  pref("datareporting.policy.dataSubmissionPolicyBypassNotification", false);
  pref("datareporting.policy.currentPolicyVersion", 2);
  pref("datareporting.policy.minimumPolicyVersion", 1);
  pref("datareporting.policy.minimumPolicyVersion.channel-beta", 2);
  pref("datareporting.policy.firstRunURL", "https://www.mozilla.org/privacy/firefox/");
//@line 3846 "$SRCDIR/modules/libpref/init/all.js"
//@line 3849 "$SRCDIR/modules/libpref/init/all.js"
    pref("datareporting.healthreport.infoURL", "https://www.mozilla.org/legal/privacy/firefox.html#health-report");
    pref("datareporting.healthreport.uploadEnabled", true);
//@line 3855 "$SRCDIR/modules/libpref/init/all.js"
pref("services.common.log.logger.rest.request", "Debug");
pref("services.common.log.logger.rest.response", "Debug");
pref("services.common.log.logger.tokenserverclient", "Debug");
//@line 3861 "$SRCDIR/modules/libpref/init/all.js"
  pref("services.sync.lastversion", "firstrun");
  pref("services.sync.sendVersionInfo", true);
  pref("services.sync.scheduler.idleInterval", 3600);  // 1 hour
  pref("services.sync.scheduler.activeInterval", 600);   // 10 minutes
  pref("services.sync.scheduler.immediateInterval", 90);    // 1.5 minutes
  pref("services.sync.scheduler.idleTime", 300);   // 5 minutes
  pref("services.sync.scheduler.fxa.singleDeviceInterval", 3600); // 1 hour
  pref("services.sync.engine.addons", true);
  pref("services.sync.engine.addresses", false);
  pref("services.sync.engine.bookmarks", true);
  pref("services.sync.engine.creditcards", false);
  pref("services.sync.engine.history", true);
  pref("services.sync.engine.passwords", true);
  pref("services.sync.engine.prefs", true);
  pref("services.sync.engine.tabs", true);
  pref("services.sync.engine.tabs.filteredSchemes", "about|resource|chrome|file|blob|moz-extension|data");
  pref("services.sync.engine.addresses.available", false);
  pref("services.sync.engine.creditcards.available", false);
  pref("services.sync.addons.ignoreUserEnabledChanges", false);
  pref("services.sync.addons.trustedSourceHostnames", "addons.mozilla.org");
  pref("services.sync.log.appender.console", "Fatal");
  pref("services.sync.log.appender.dump", "Error");
  pref("services.sync.log.appender.file.level", "Trace");
  pref("services.sync.log.appender.file.logOnError", true);
//@line 3905 "$SRCDIR/modules/libpref/init/all.js"
    pref("services.sync.log.appender.file.logOnSuccess", false);
//@line 3907 "$SRCDIR/modules/libpref/init/all.js"
  pref("services.sync.log.appender.file.maxErrorAge", 864000); // 10 days
  pref("services.sync.log.logger", "Debug");
  pref("services.sync.log.logger.engine", "Debug");
  pref("services.sync.log.cryptoDebug", false);
  pref("services.sync.telemetry.submissionInterval", 43200); // 12 hours in seconds
  pref("services.sync.telemetry.maxPayloadCount", 500);
//@line 3930 "$SRCDIR/modules/libpref/init/all.js"
  pref("services.sync.engine.bookmarks.validation.interval", 86400); // 24 hours in seconds
  pref("services.sync.engine.passwords.validation.interval", 86400); // 24 hours in seconds
  pref("services.sync.engine.bookmarks.validation.percentageChance", 10);
  pref("services.sync.engine.passwords.validation.percentageChance", 10);
  pref("services.sync.engine.bookmarks.validation.maxRecords", 1000);
  pref("services.sync.engine.passwords.validation.maxRecords", 1000);
  pref("services.sync.maxResyncs", 1);
  pref("identity.fxaccounts.auth.uri", "https://api.accounts.firefox.com/v1");
  pref("services.sync.extension-storage.skipPercentageChance", 50);
//@line 3956 "$SRCDIR/modules/libpref/init/all.js"
//@line 3958 "$SRCDIR/modules/libpref/init/all.js"
  pref("marionette.debugging.clicktostart", false);
  pref("marionette.port", 2828);
  pref("remote.active-protocols", 1);
//@line 3983 "$SRCDIR/modules/libpref/init/all.js"
    pref("remote.experimental.enabled", false);
//@line 3985 "$SRCDIR/modules/libpref/init/all.js"
  pref("remote.log.level", "Info");
  pref("remote.log.truncate", true);
  pref("remote.prefs.recommended", true);
//@line 4001 "$SRCDIR/modules/libpref/init/all.js"
pref("devtools.jsonview.enabled", true);
pref("devtools.theme", "auto", sticky);
pref("devtools.policy.disabled", false);
pref("devtools.errorconsole.deprecation_warnings", true);
//@line 4019 "$SRCDIR/modules/libpref/init/all.js"
  pref("devtools.debugger.prompt-connection", true, sticky);
//@line 4021 "$SRCDIR/modules/libpref/init/all.js"
//@line 4023 "$SRCDIR/modules/libpref/init/all.js"
  pref("devtools.chrome.enabled", false, sticky);
  pref("devtools.debugger.remote-enabled", false, sticky);
//@line 4032 "$SRCDIR/modules/libpref/init/all.js"
pref("devtools.debugger.features.windowless-service-workers", false);
pref("devtools.debugger.log", false);
pref("devtools.debugger.log.verbose", false);
pref("devtools.debugger.remote-port", 6000);
pref("devtools.debugger.remote-websocket", false);
pref("devtools.debugger.force-local", true);
pref("devtools.netmonitor.responseBodyLimit", 1048576);
pref("devtools.netmonitor.requestBodyLimit", 1048576);
pref("devtools.netmonitor.msg.messageDataLimit", 100000);
pref("devtools.defaultColorUnit", "authored");
pref("devtools.dump.emit", false);
pref("devtools.discovery.log", false);
pref("devtools.remote.adb.extensionID", "adb@mozilla.org");
pref("devtools.remote.adb.extensionURL", "https://ftp.mozilla.org/pub/labs/devtools/adb-extension/#OS#/adb-extension-latest-#OS#.xpi");
pref("devtools.inspector.inactive.css.enabled", true);
pref("devtools.f12_enabled", true);
//@line 4081 "$SRCDIR/modules/libpref/init/all.js"
pref("dom.postMessage.sharedArrayBuffer.bypassCOOP_COEP.insecure.enabled", false, locked);
//@line 4083 "$SRCDIR/modules/libpref/init/all.js"
pref("security.external_protocol_requires_permission", true);
pref("extensions.formautofill.available", "detect");
pref("extensions.formautofill.addresses.supported", "detect");
pref("extensions.formautofill.addresses.enabled", true);
pref("extensions.formautofill.addresses.capture.enabled", true);
//@line 4105 "$SRCDIR/modules/libpref/init/all.js"
  pref("extensions.formautofill.addresses.experiments.enabled", false);
//@line 4108 "$SRCDIR/modules/libpref/init/all.js"
pref("extensions.formautofill.addresses.ignoreAutocompleteOff", true);
pref("extensions.formautofill.addresses.supportedCountries", "US,CA,FR,DE");
pref("extensions.formautofill.creditCards.supported", "detect");
pref("extensions.formautofill.creditCards.enabled", true);
pref("extensions.formautofill.creditCards.ignoreAutocompleteOff", true);
pref("extensions.formautofill.creditCards.supportedCountries", "US,CA,GB,FR,DE,IT,ES,AT,BE,PL");
pref("extensions.formautofill.creditCards.heuristics.mode", 2);
pref("extensions.formautofill.creditCards.heuristics.fathom.types", "cc-number,cc-name");
pref("extensions.formautofill.creditCards.heuristics.fathom.confidenceThreshold", "0.5");
pref("extensions.formautofill.creditCards.heuristics.fathom.highConfidenceThreshold", "0.95");
pref("extensions.formautofill.creditCards.heuristics.fathom.testConfidence", "0");
pref("extensions.formautofill.loglevel", "Warn");
pref("extensions.formautofill.heuristics.captureOnFormRemoval", true);
pref("extensions.formautofill.heuristics.captureOnPageNavigation", true);
pref("toolkit.osKeyStore.loglevel", "Warn");
pref("extensions.formautofill.supportRTL", false);
pref("cookiebanners.listService.logLevel", "Error");
pref("cookiebanners.bannerClicking.logLevel", "Error");
pref("cookiebanners.bannerClicking.enabled", true);
pref("cookiebanners.bannerClicking.testing", false);
pref("cookiebanners.bannerClicking.timeoutAfterLoad", 5000);
pref("cookiebanners.bannerClicking.timeoutAfterDOMContentLoaded", 20000);
pref("cookiebanners.bannerClicking.pollingInterval", 500);
pref("cookiebanners.listService.testRules", "[]");
pref("cookiebanners.listService.testSkipRemoteSettings", false);
pref("dom.sitepermsaddon-provider.separatedBlocklistedDomains", "shopee.co.th,shopee.tw,shopee.co.id,shopee.com.my,shopee.vn,shopee.ph,shopee.sg,shopee.com.br,shopee.com,shopee.cn,shopee.io,shopee.pl,shopee.com.mx,shopee.com.co,shopee.cl,shopee.kr,shopee.es,shopee.in,alipay.com,miravia.es");
pref("privacy.query_stripping.listService.logLevel", "Error");
pref("extensions.webcompat.useScriptingAPI", true);
pref("privacy.fingerprintingProtection.WebCompatService.logLevel", "Error");
pref("privacy.query_stripping.strip_on_share.enableTestMode", false);
PK
!<K��b��"defaults/pref/PdfJsDefaultPrefs.js/* Copyright 2024 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// THIS FILE IS GENERATED AUTOMATICALLY, DO NOT EDIT MANUALLY!
//
// Any overrides should be placed in `PdfJsOverridePrefs.js`.

pref("pdfjs.altTextLearnMoreUrl", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/pdf-alt-text");
pref("pdfjs.annotationEditorMode", 0);
pref("pdfjs.annotationMode", 2);
pref("pdfjs.cursorToolOnLoad", 0);
pref("pdfjs.defaultZoomDelay", 400);
pref("pdfjs.defaultZoomValue", "");
pref("pdfjs.disableAutoFetch", false);
pref("pdfjs.disableFontFace", false);
pref("pdfjs.disablePageLabels", false);
pref("pdfjs.disableRange", false);
pref("pdfjs.disableStream", false);
pref("pdfjs.enableAltText", false);
pref("pdfjs.enableAltTextModelDownload", true);
pref("pdfjs.enableGuessAltText", true);
pref("pdfjs.enableHWA", false);
pref("pdfjs.enableHighlightFloatingButton", false);
pref("pdfjs.enableNewAltTextWhenAddingImage", true);
pref("pdfjs.enablePermissions", false);
pref("pdfjs.enablePrintAutoRotate", true);
pref("pdfjs.enableScripting", true);
pref("pdfjs.enableUpdatedAddImage", false);
pref("pdfjs.enableXfa", true);
pref("pdfjs.externalLinkTarget", 0);
pref("pdfjs.forcePageColors", false);
pref("pdfjs.highlightEditorColors", "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F");
pref("pdfjs.historyUpdateUrl", false);
pref("pdfjs.ignoreDestinationZoom", false);
pref("pdfjs.pageColorsBackground", "Canvas");
pref("pdfjs.pageColorsForeground", "CanvasText");
pref("pdfjs.pdfBugEnabled", false);
pref("pdfjs.scrollModeOnLoad", -1);
pref("pdfjs.sidebarViewOnLoad", -1);
pref("pdfjs.spreadModeOnLoad", -1);
pref("pdfjs.textLayerMode", 1);
pref("pdfjs.viewOnLoad", 0);

//@line 1 "$SRCDIR/toolkit/components/pdfjs/PdfJsOverridePrefs.js"
/* Copyright 2024 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//@line 25 "$SRCDIR/toolkit/components/pdfjs/PdfJsOverridePrefs.js"

  // Enable highlighting in a pdf.
  pref("pdfjs.enableHighlightEditor", true);
//@line 31 "$SRCDIR/toolkit/components/pdfjs/PdfJsOverridePrefs.js"
    pref("pdfjs.enableHighlightFloatingButton", false);
//@line 33 "$SRCDIR/toolkit/components/pdfjs/PdfJsOverridePrefs.js"

//@line 37 "$SRCDIR/toolkit/components/pdfjs/PdfJsOverridePrefs.js"

PK
!<�Y.HHchrome.manifestmanifest chrome/chrome.manifest
manifest components/components.manifest
PK
!<# U�!	!	chrome/chrome.manifestcontent remote remote/content/
content extensions toolkit/content/extensions/
content global toolkit/content/global/ contentaccessible=yes
content mozapps toolkit/content/mozapps/
content passwordmgr toolkit/content/passwordmgr/
skin global classic/1.0 toolkit/skin/classic/global/
skin help classic/1.0 toolkit/skin/classic/help/
skin mozapps classic/1.0 toolkit/skin/classic/mozapps/
content pippki pippki/content/pippki/
locale alerts en-US en-US/locale/en-US/alerts/
locale autoconfig en-US en-US/locale/en-US/autoconfig/
locale global en-US en-US/locale/en-US/global/
locale global-platform en-US en-US/locale/en-US/global-platform/mac/ os=Darwin
locale global-platform en-US en-US/locale/en-US/global-platform/unix/ os=LikeUnix os=Android
locale global-platform en-US en-US/locale/en-US/global-platform/win/ os=WINNT
locale mozapps en-US en-US/locale/en-US/mozapps/
locale necko en-US en-US/locale/en-US/necko/
locale passwordmgr en-US en-US/locale/en-US/passwordmgr/
locale pipnss en-US en-US/locale/en-US/pipnss/
locale pippki en-US en-US/locale/en-US/pippki/
locale places en-US en-US/locale/en-US/places/
override chrome://mozapps/skin/extensions/category-dictionaries.svg chrome://mozapps/skin/extensions/dictionaryGeneric.svg
override chrome://mozapps/skin/extensions/category-languages.svg chrome://mozapps/skin/extensions/localeGeneric.svg
override chrome://mozapps/skin/extensions/category-themes.svg chrome://mozapps/skin/extensions/themeGeneric.svg
override chrome://mozapps/skin/extensions/localeGeneric.svg chrome://mozapps/skin/extensions/dictionaryGeneric.svg
override chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png chrome://mozapps/skin/extensions/extensionGeneric.svg
resource autofill toolkit/res/autofill/
resource content-accessible resource://gre/contentaccessible/ contentaccessible=yes
resource default-theme toolkit/content/mozapps/extensions/default-theme/
resource featuregates toolkit/featuregates/
resource gre-resources toolkit/res/
resource messaging-system toolkit/res/messaging-system/
resource nimbus toolkit/res/nimbus/
resource normandy toolkit/res/normandy/
resource normandy-content toolkit/res/normandy/content/ contentaccessible=yes
resource normandy-vendor toolkit/res/normandy/vendor/ contentaccessible=yes
resource passwordmgr toolkit/passwordmgr/
resource pdf.js pdfjs/content/
PK
!<W���res/multilocale.txten-US
PK
!<}4f��components/components.manifestcategory update-timer addonManager @mozilla.org/addons/integration;1,getService,addon-background-update-timer,extensions.update.interval,86400
category addon-provider-module GMPProvider resource://gre/modules/addons/GMPProvider.sys.mjs
category addon-provider-module SitePermsAddonProvider resource://gre/modules/addons/SitePermsAddonProvider.sys.mjs
category app-startup MainProcessSingleton @mozilla.org/main-process-singleton;1 process=main
resource services-sync resource://gre/modules/services-sync/
resource services-common resource://gre/modules/services-common/
resource services-settings resource://gre/modules/services-settings/
category update-timer RemoteSettingsComponents @mozilla.org/services/settings;1,getService,services-settings-poll-changes,services.settings.poll_interval,86400,259200
resource services-crypto resource://gre/modules/services-crypto/
category profile-after-change TelemetryStartup @mozilla.org/base/telemetry-startup;1 process=main
category android-push-service PushServiceParent @mozilla.org/push/Service;1 process=main
category profile-after-change URLDecorationAnnotationsService @mozilla.org/tracking-url-decoration-service;1 process=main
category l10n-registry 0-toolkit resource://gre/localization/{locale}/ backgroundtask=0
category l10n-registry 0-toolkit resource://gre/localization/{locale}/ backgroundtask=1
category webextension-modules toolkit chrome://extensions/content/ext-toolkit.json
category webextension-scripts a-toolkit chrome://extensions/content/parent/ext-toolkit.js
category webextension-scripts b-tabs-base chrome://extensions/content/parent/ext-tabs-base.js
category webextension-scripts-content toolkit chrome://extensions/content/child/ext-toolkit.js
category webextension-scripts-devtools toolkit chrome://extensions/content/child/ext-toolkit.js
category webextension-scripts-addon toolkit chrome://extensions/content/child/ext-toolkit.js
category webextension-schemas events chrome://extensions/content/schemas/events.json
category webextension-schemas native_manifest chrome://extensions/content/schemas/native_manifest.json
category webextension-schemas types chrome://extensions/content/schemas/types.json
category profile-after-change nsTerminator @mozilla.org/toolkit/shutdown-terminator;1
PK
!<�����0chrome/toolkit/content/global/process-content.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/process-script */

"use strict";

// Creates a new PageListener for this process. This will listen for page loads
// and for those that match URLs provided by the parent process will set up
// a dedicated message port and notify the parent process.

Services.cpmm.addMessageListener("gmp-plugin-crash", ({ data }) => {
  Cc["@mozilla.org/gecko-media-plugin-service;1"]
    .getService(Ci.mozIGeckoMediaPluginService)
    .RunPluginCrashCallbacks(data.pluginID, data.pluginName);
});
PK
!<!n�__'modules/extensionProcessScriptLoader.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/process-script */

"use strict";

ChromeUtils.importESModule(
  "resource://gre/modules/ExtensionProcessScript.sys.mjs"
);
PK
!<��
���7chrome/toolkit/content/extensions/parent/ext-toolkit.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// These are defined on "global" which is used for the same scopes as the other
// ext-*.js files.
/* exported getCookieStoreIdForTab, getCookieStoreIdForContainer,
            getContainerForCookieStoreId,
            isValidCookieStoreId, isContainerCookieStoreId,
            EventManager, URL */
/* global getCookieStoreIdForTab:false,
          getCookieStoreIdForContainer:false,
          getContainerForCookieStoreId: false,
          isValidCookieStoreId:false, isContainerCookieStoreId:false,
          isDefaultCookieStoreId: false, isPrivateCookieStoreId:false,
          EventManager: false */

ChromeUtils.defineESModuleGetters(this, {
  ContextualIdentityService:
    "resource://gre/modules/ContextualIdentityService.sys.mjs",
});

XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);

var { ExtensionCommon } = ChromeUtils.importESModule(
  "resource://gre/modules/ExtensionCommon.sys.mjs"
);

var { ExtensionError } = ExtensionUtils;

global.EventEmitter = ExtensionCommon.EventEmitter;
global.EventManager = ExtensionCommon.EventManager;

/* globals DEFAULT_STORE, PRIVATE_STORE, CONTAINER_STORE */

global.DEFAULT_STORE = "firefox-default";
global.PRIVATE_STORE = "firefox-private";
global.CONTAINER_STORE = "firefox-container-";

global.getCookieStoreIdForTab = function (data, tab) {
  if (data.incognito) {
    return PRIVATE_STORE;
  }

  if (tab.userContextId) {
    return getCookieStoreIdForContainer(tab.userContextId);
  }

  return DEFAULT_STORE;
};

global.getCookieStoreIdForOriginAttributes = function (originAttributes) {
  if (originAttributes.privateBrowsingId) {
    return PRIVATE_STORE;
  }

  if (originAttributes.userContextId) {
    return getCookieStoreIdForContainer(originAttributes.userContextId);
  }

  return DEFAULT_STORE;
};

global.isPrivateCookieStoreId = function (storeId) {
  return storeId == PRIVATE_STORE;
};

global.isDefaultCookieStoreId = function (storeId) {
  return storeId == DEFAULT_STORE;
};

global.isContainerCookieStoreId = function (storeId) {
  return storeId !== null && storeId.startsWith(CONTAINER_STORE);
};

global.getCookieStoreIdForContainer = function (containerId) {
  return CONTAINER_STORE + containerId;
};

global.getContainerForCookieStoreId = function (storeId) {
  if (!isContainerCookieStoreId(storeId)) {
    return null;
  }

  let containerId = storeId.substring(CONTAINER_STORE.length);

  if (AppConstants.platform === "android") {
    return parseInt(containerId, 10);
  } // TODO: Bug 1643740, support ContextualIdentityService on Android

  if (ContextualIdentityService.getPublicIdentityFromId(containerId)) {
    return parseInt(containerId, 10);
  }

  return null;
};

global.isValidCookieStoreId = function (storeId) {
  return (
    isDefaultCookieStoreId(storeId) ||
    isPrivateCookieStoreId(storeId) ||
    isContainerCookieStoreId(storeId)
  );
};

global.getOriginAttributesPatternForCookieStoreId = function (cookieStoreId) {
  if (isDefaultCookieStoreId(cookieStoreId)) {
    return {
      userContextId: Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID,
      privateBrowsingId:
        Ci.nsIScriptSecurityManager.DEFAULT_PRIVATE_BROWSING_ID,
    };
  }
  if (isPrivateCookieStoreId(cookieStoreId)) {
    return {
      userContextId: Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID,
      privateBrowsingId: 1,
    };
  }
  if (isContainerCookieStoreId(cookieStoreId)) {
    let userContextId = getContainerForCookieStoreId(cookieStoreId);
    if (userContextId !== null) {
      return { userContextId };
    }
  }

  throw new ExtensionError("Invalid cookieStoreId");
};
PK
!<�o��9chrome/toolkit/content/extensions/parent/ext-tabs-base.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

/* globals EventEmitter */

ChromeUtils.defineESModuleGetters(this, {
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "containersEnabled",
  "privacy.userContext.enabled"
);

var { DefaultMap, DefaultWeakMap, ExtensionError, parseMatchPatterns } =
  ExtensionUtils;

var { defineLazyGetter } = ExtensionCommon;

/**
 * The platform-specific type of native tab objects, which are wrapped by
 * TabBase instances.
 *
 * @typedef {object | XULElement} NativeTab
 */

/**
 * @typedef {object} MutedInfo
 * @property {boolean} muted
 *        True if the tab is currently muted, false otherwise.
 * @property {string} [reason]
 *        The reason the tab is muted. Either "user", if the tab was muted by a
 *        user, or "extension", if it was muted by an extension.
 * @property {string} [extensionId]
 *        If the tab was muted by an extension, contains the internal ID of that
 *        extension.
 */

/**
 * A platform-independent base class for extension-specific wrappers around
 * native tab objects.
 *
 * @param {Extension} extension
 *        The extension object for which this wrapper is being created. Used to
 *        determine permissions for access to certain properties and
 *        functionality.
 * @param {NativeTab} nativeTab
 *        The native tab object which is being wrapped. The type of this object
 *        varies by platform.
 * @param {integer} id
 *        The numeric ID of this tab object. This ID should be the same for
 *        every extension, and for the lifetime of the tab.
 */
class TabBase {
  constructor(extension, nativeTab, id) {
    this.extension = extension;
    this.tabManager = extension.tabManager;
    this.id = id;
    this.nativeTab = nativeTab;
    this.activeTabWindowID = null;

    if (!extension.privateBrowsingAllowed && this._incognito) {
      throw new ExtensionError(`Invalid tab ID: ${id}`);
    }
  }

  /**
   * Capture the visible area of this tab, and return the result as a data: URI.
   *
   * @param {BaseContext} context
   *        The extension context for which to perform the capture.
   * @param {number} zoom
   *        The current zoom for the page.
   * @param {object} [options]
   *        The options with which to perform the capture.
   * @param {string} [options.format = "png"]
   *        The image format in which to encode the captured data. May be one of
   *        "png" or "jpeg".
   * @param {integer} [options.quality = 92]
   *        The quality at which to encode the captured image data, ranging from
   *        0 to 100. Has no effect for the "png" format.
   * @param {DOMRectInit} [options.rect]
   *        Area of the document to render, in CSS pixels, relative to the page.
   *        If null, the currently visible viewport is rendered.
   * @param {number} [options.scale]
   *        The scale to render at, defaults to devicePixelRatio.
   * @returns {Promise<string>}
   */
  async capture(context, zoom, options) {
    let win = this.browser.ownerGlobal;
    let scale = options?.scale || win.devicePixelRatio;
    let rect = options?.rect && win.DOMRect.fromRect(options.rect);

    // We only allow mozilla addons to use the resetScrollPosition option,
    // since it's not standardized.
    let resetScrollPosition = false;
    if (!context.extension.restrictSchemes) {
      resetScrollPosition = !!options?.resetScrollPosition;
    }

    let wgp = this.browsingContext.currentWindowGlobal;
    let image = await wgp.drawSnapshot(
      rect,
      scale * zoom,
      "white",
      resetScrollPosition
    );

    let canvas = new OffscreenCanvas(image.width, image.height);

    let ctx = canvas.getContext("bitmaprenderer", { alpha: false });
    ctx.transferFromImageBitmap(image);

    let blob = await canvas.convertToBlob({
      type: `image/${options?.format ?? "png"}`,
      quality: options?.quality / 100,
    });

    let dataURL = await new Promise((resolve, reject) => {
      let reader = new FileReader();
      reader.onload = () => resolve(reader.result);
      reader.onerror = () => reject(reader.error);
      reader.readAsDataURL(blob);
    });

    return dataURL;
  }

  /**
   * @property {integer | null} innerWindowID
   *        The last known innerWindowID loaded into this tab's docShell. This
   *        property must remain in sync with the last known values of
   *        properties such as `url` and `title`. Any operations on the content
   *        of an out-of-process tab will automatically fail if the
   *        innerWindowID of the tab when the message is received does not match
   *        the value of this property when the message was sent.
   *        @readonly
   */
  get innerWindowID() {
    return this.browser.innerWindowID;
  }

  /**
   * @property {boolean} hasTabPermission
   *        Returns true if the extension has permission to access restricted
   *        properties of this tab, such as `url`, `title`, and `favIconUrl`.
   *        @readonly
   */
  get hasTabPermission() {
    return (
      this.extension.hasPermission("tabs") ||
      this.hasActiveTabPermission ||
      this.matchesHostPermission
    );
  }

  /**
   * @property {boolean} hasActiveTabPermission
   *        Returns true if the extension has the "activeTab" permission, and
   *        has been granted access to this tab due to a user executing an
   *        extension action.
   *
   *        If true, the extension may load scripts and CSS into this tab, and
   *        access restricted properties, such as its `url`.
   *        @readonly
   */
  get hasActiveTabPermission() {
    return (
      (this.extension.originControls ||
        this.extension.hasPermission("activeTab")) &&
      this.activeTabWindowID != null &&
      this.activeTabWindowID === this.innerWindowID
    );
  }

  /**
   * @property {boolean} matchesHostPermission
   *        Returns true if the extensions host permissions match the current tab url.
   *        @readonly
   */
  get matchesHostPermission() {
    return this.extension.allowedOrigins.matches(this._uri);
  }

  /**
   * @property {boolean} incognito
   *        Returns true if this is a private browsing tab, false otherwise.
   *        @readonly
   */
  get _incognito() {
    return PrivateBrowsingUtils.isBrowserPrivate(this.browser);
  }

  /**
   * @property {string} _url
   *        Returns the current URL of this tab. Does not do any permission
   *        checks.
   *        @readonly
   */
  get _url() {
    return this.browser.currentURI.spec;
  }

  /**
   * @property {string | undefined} url
   *        Returns the current URL of this tab if the extension has permission
   *        to read it, or undefined otherwise.
   *        @readonly
   */
  get url() {
    if (this.hasTabPermission) {
      return this._url;
    }
    return undefined;
  }

  /**
   * @property {nsIURI} _uri
   *        Returns the current URI of this tab.
   *        @readonly
   */
  get _uri() {
    return this.browser.currentURI;
  }

  /**
   * @property {string} _title
   *        Returns the current title of this tab. Does not do any permission
   *        checks.
   *        @readonly
   */
  get _title() {
    return this.browser.contentTitle || this.nativeTab.label;
  }

  /**
   * @property {nsIURI | undefined} title
   *        Returns the current title of this tab if the extension has permission
   *        to read it, or undefined otherwise.
   *        @readonly
   */
  get title() {
    if (this.hasTabPermission) {
      return this._title;
    }
    return undefined;
  }

  /**
   * @property {string} _favIconUrl
   *        Returns the current favicon URL of this tab. Does not do any permission
   *        checks.
   *        @readonly
   *        @abstract
   */
  get _favIconUrl() {
    throw new Error("Not implemented");
  }

  /**
   * @property {nsIURI | undefined} faviconUrl
   *        Returns the current faviron URL of this tab if the extension has permission
   *        to read it, or undefined otherwise.
   *        @readonly
   */
  get favIconUrl() {
    if (this.hasTabPermission) {
      return this._favIconUrl;
    }
    return undefined;
  }

  /**
   * @property {integer} lastAccessed
   *        Returns the last time the tab was accessed as the number of
   *        milliseconds since epoch.
   *        @readonly
   *        @abstract
   */
  get lastAccessed() {
    throw new Error("Not implemented");
  }

  /**
   * @property {boolean} audible
   *        Returns true if the tab is currently playing audio, false otherwise.
   *        @readonly
   *        @abstract
   */
  get audible() {
    throw new Error("Not implemented");
  }

  /**
   * @property {boolean} autoDiscardable
   *        Returns true if the tab can be discarded on memory pressure, false otherwise.
   *        @readonly
   *        @abstract
   */
  get autoDiscardable() {
    throw new Error("Not implemented");
  }

  /**
   * @property {XULElement} browser
   *        Returns the XUL browser for the given tab.
   *        @readonly
   *        @abstract
   */
  get browser() {
    throw new Error("Not implemented");
  }

  /**
   * @property {BrowsingContext} browsingContext
   *        Returns the BrowsingContext for the given tab.
   *        @readonly
   */
  get browsingContext() {
    return this.browser?.browsingContext;
  }

  /**
   * @property {FrameLoader} frameLoader
   *        Returns the frameloader for the given tab.
   *        @readonly
   */
  get frameLoader() {
    return this.browser && this.browser.frameLoader;
  }

  /**
   * @property {string} cookieStoreId
   *        Returns the cookie store identifier for the given tab.
   *        @readonly
   *        @abstract
   */
  get cookieStoreId() {
    throw new Error("Not implemented");
  }

  /**
   * @property {integer} openerTabId
   *        Returns the ID of the tab which opened this one.
   *        @readonly
   */
  get openerTabId() {
    return null;
  }

  /**
   * @property {integer} discarded
   *        Returns true if the tab is discarded.
   *        @readonly
   *        @abstract
   */
  get discarded() {
    throw new Error("Not implemented");
  }

  /**
   * @property {integer} height
   *        Returns the pixel height of the visible area of the tab.
   *        @readonly
   *        @abstract
   */
  get height() {
    throw new Error("Not implemented");
  }

  /**
   * @property {integer} hidden
   *        Returns true if the tab is hidden.
   *        @readonly
   *        @abstract
   */
  get hidden() {
    throw new Error("Not implemented");
  }

  /**
   * @property {integer} index
   *        Returns the index of the tab in its window's tab list.
   *        @readonly
   *        @abstract
   */
  get index() {
    throw new Error("Not implemented");
  }

  /**
   * @property {MutedInfo} mutedInfo
   *        Returns information about the tab's current audio muting status.
   *        @readonly
   *        @abstract
   */
  get mutedInfo() {
    throw new Error("Not implemented");
  }

  /**
   * @property {SharingState} sharingState
   *        Returns object with tab sharingState.
   *        @readonly
   *        @abstract
   */
  get sharingState() {
    throw new Error("Not implemented");
  }

  /**
   * @property {boolean} pinned
   *        Returns true if the tab is pinned, false otherwise.
   *        @readonly
   *        @abstract
   */
  get pinned() {
    throw new Error("Not implemented");
  }

  /**
   * @property {boolean} active
   *        Returns true if the tab is the currently-selected tab, false
   *        otherwise.
   *        @readonly
   *        @abstract
   */
  get active() {
    throw new Error("Not implemented");
  }

  /**
   * @property {boolean} highlighted
   *        Returns true if the tab is highlighted.
   *        @readonly
   *        @abstract
   */
  get highlighted() {
    throw new Error("Not implemented");
  }

  /**
   * @property {string} status
   *        Returns the current loading status of the tab. May be either
   *        "loading" or "complete".
   *        @readonly
   *        @abstract
   */
  get status() {
    throw new Error("Not implemented");
  }

  /**
   * @property {integer} height
   *        Returns the pixel height of the visible area of the tab.
   *        @readonly
   *        @abstract
   */
  get width() {
    throw new Error("Not implemented");
  }

  /**
   * @property {DOMWindow} window
   *        Returns the browser window to which the tab belongs.
   *        @readonly
   *        @abstract
   */
  get window() {
    throw new Error("Not implemented");
  }

  /**
   * @property {integer} window
   *        Returns the numeric ID of the browser window to which the tab belongs.
   *        @readonly
   *        @abstract
   */
  get windowId() {
    throw new Error("Not implemented");
  }

  /**
   * @property {boolean} attention
   *          Returns true if the tab is drawing attention.
   *          @readonly
   *          @abstract
   */
  get attention() {
    throw new Error("Not implemented");
  }

  /**
   * @property {boolean} isArticle
   *        Returns true if the document in the tab can be rendered in reader
   *        mode.
   *        @readonly
   *        @abstract
   */
  get isArticle() {
    throw new Error("Not implemented");
  }

  /**
   * @property {boolean} isInReaderMode
   *        Returns true if the document in the tab is being rendered in reader
   *        mode.
   *        @readonly
   *        @abstract
   */
  get isInReaderMode() {
    throw new Error("Not implemented");
  }

  /**
   * @property {integer} successorTabId
   *        @readonly
   *        @abstract
   */
  get successorTabId() {
    throw new Error("Not implemented");
  }

  /**
   * Returns true if this tab matches the the given query info object. Omitted
   * or null have no effect on the match.
   *
   * @param {object} queryInfo
   *        The query info against which to match.
   * @param {boolean} [queryInfo.active]
   *        Matches against the exact value of the tab's `active` attribute.
   * @param {boolean} [queryInfo.audible]
   *        Matches against the exact value of the tab's `audible` attribute.
   * @param {boolean} [queryInfo.autoDiscardable]
   *        Matches against the exact value of the tab's `autoDiscardable` attribute.
   * @param {string} [queryInfo.cookieStoreId]
   *        Matches against the exact value of the tab's `cookieStoreId` attribute.
   * @param {boolean} [queryInfo.discarded]
   *        Matches against the exact value of the tab's `discarded` attribute.
   * @param {boolean} [queryInfo.hidden]
   *        Matches against the exact value of the tab's `hidden` attribute.
   * @param {boolean} [queryInfo.highlighted]
   *        Matches against the exact value of the tab's `highlighted` attribute.
   * @param {integer} [queryInfo.index]
   *        Matches against the exact value of the tab's `index` attribute.
   * @param {boolean} [queryInfo.muted]
   *        Matches against the exact value of the tab's `mutedInfo.muted` attribute.
   * @param {boolean} [queryInfo.pinned]
   *        Matches against the exact value of the tab's `pinned` attribute.
   * @param {string} [queryInfo.status]
   *        Matches against the exact value of the tab's `status` attribute.
   * @param {string} [queryInfo.title]
   *        Matches against the exact value of the tab's `title` attribute.
   * @param {string|boolean } [queryInfo.screen]
   *        Matches against the exact value of the tab's `sharingState.screen` attribute, or use true to match any screen sharing tab.
   * @param {boolean} [queryInfo.camera]
   *        Matches against the exact value of the tab's `sharingState.camera` attribute.
   * @param {boolean} [queryInfo.microphone]
   *        Matches against the exact value of the tab's `sharingState.microphone` attribute.
   *
   *        Note: Per specification, this should perform a pattern match, rather
   *        than an exact value match, and will do so in the future.
   * @param {MatchPattern} [queryInfo.url]
   *        Requires the tab's URL to match the given MatchPattern object.
   *
   * @returns {boolean}
   *        True if the tab matches the query.
   */
  matches(queryInfo) {
    const PROPS = [
      "active",
      "audible",
      "autoDiscardable",
      "discarded",
      "hidden",
      "highlighted",
      "index",
      "openerTabId",
      "pinned",
      "status",
    ];

    function checkProperty(prop, obj) {
      return queryInfo[prop] != null && queryInfo[prop] !== obj[prop];
    }

    if (PROPS.some(prop => checkProperty(prop, this))) {
      return false;
    }

    if (checkProperty("muted", this.mutedInfo)) {
      return false;
    }

    let state = this.sharingState;
    if (["camera", "microphone"].some(prop => checkProperty(prop, state))) {
      return false;
    }
    // query for screen can be boolean (ie. any) or string (ie. specific).
    if (queryInfo.screen !== null) {
      let match =
        typeof queryInfo.screen == "boolean"
          ? queryInfo.screen === !!state.screen
          : queryInfo.screen === state.screen;
      if (!match) {
        return false;
      }
    }

    if (queryInfo.cookieStoreId) {
      if (!queryInfo.cookieStoreId.includes(this.cookieStoreId)) {
        return false;
      }
    }

    if (queryInfo.url || queryInfo.title) {
      if (!this.hasTabPermission) {
        return false;
      }
      // Using _uri and _title instead of url/title to avoid repeated permission checks.
      if (queryInfo.url && !queryInfo.url.matches(this._uri)) {
        return false;
      }
      if (queryInfo.title && !queryInfo.title.matches(this._title)) {
        return false;
      }
    }

    return true;
  }

  /**
   * Converts this tab object to a JSON-compatible object containing the values
   * of its properties which the extension is permitted to access, in the format
   * required to be returned by WebExtension APIs.
   *
   * @param {object} [fallbackTabSize]
   *        A geometry data if the lazy geometry data for this tab hasn't been
   *        initialized yet.
   * @returns {object}
   */
  convert(fallbackTabSize = null) {
    let result = {
      id: this.id,
      index: this.index,
      windowId: this.windowId,
      highlighted: this.highlighted,
      active: this.active,
      attention: this.attention,
      pinned: this.pinned,
      status: this.status,
      hidden: this.hidden,
      discarded: this.discarded,
      incognito: this.incognito,
      width: this.width,
      height: this.height,
      lastAccessed: this.lastAccessed,
      audible: this.audible,
      autoDiscardable: this.autoDiscardable,
      mutedInfo: this.mutedInfo,
      isArticle: this.isArticle,
      isInReaderMode: this.isInReaderMode,
      sharingState: this.sharingState,
      successorTabId: this.successorTabId,
      cookieStoreId: this.cookieStoreId,
    };

    // If the tab has not been fully layed-out yet, fallback to the geometry
    // from a different tab (usually the currently active tab).
    if (fallbackTabSize && (!result.width || !result.height)) {
      result.width = fallbackTabSize.width;
      result.height = fallbackTabSize.height;
    }

    let opener = this.openerTabId;
    if (opener) {
      result.openerTabId = opener;
    }

    if (this.hasTabPermission) {
      for (let prop of ["url", "title", "favIconUrl"]) {
        // We use the underscored variants here to avoid the redundant
        // permissions checks imposed on the public properties.
        let val = this[`_${prop}`];
        if (val) {
          result[prop] = val;
        }
      }
    }

    return result;
  }

  /**
   * Query each content process hosting subframes of the tab, return results.
   *
   * @param {string} message
   * @param {object} options
   *        These options are also sent to the message handler in the
   *        `ExtensionContentChild`.
   * @param {number[]} options.frameIds
   *        When omitted, all frames will be queried.
   * @param {boolean} options.returnResultsWithFrameIds
   * @returns {Promise[]}
   */
  async queryContent(message, options) {
    let { frameIds } = options;

    /** @type {Map<nsIDOMProcessParent, innerWindowId[]>} */
    let byProcess = new DefaultMap(() => []);
    // We use this set to know which frame IDs are potentially invalid (as in
    // not found when visiting the tab's BC tree below) when frameIds is a
    // non-empty list of frame IDs.
    let frameIdsSet = new Set(frameIds);

    // Recursively walk the tab's BC tree, find all frames, group by process.
    function visit(bc) {
      let win = bc.currentWindowGlobal;
      let frameId = bc.parent ? bc.id : 0;

      if (win?.domProcess && (!frameIds || frameIdsSet.has(frameId))) {
        byProcess.get(win.domProcess).push(win.innerWindowId);
        frameIdsSet.delete(frameId);
      }

      if (!frameIds || frameIdsSet.size > 0) {
        bc.children.forEach(visit);
      }
    }
    visit(this.browsingContext);

    if (frameIdsSet.size > 0) {
      throw new ExtensionError(
        `Invalid frame IDs: [${Array.from(frameIdsSet).join(", ")}].`
      );
    }

    let promises = Array.from(byProcess.entries(), ([proc, windows]) =>
      proc.getActor("ExtensionContent").sendQuery(message, { windows, options })
    );

    let results = await Promise.all(promises).catch(err => {
      if (err.name === "DataCloneError") {
        let fileName = options.jsPaths.slice(-1)[0] || "<anonymous code>";
        let message = `Script '${fileName}' result is non-structured-clonable data`;
        return Promise.reject({ message, fileName });
      }
      throw err;
    });
    results = results.flat();

    if (!results.length) {
      let errorMessage = "Missing host permission for the tab";
      if (!frameIds || frameIds.length > 1 || frameIds[0] !== 0) {
        errorMessage += " or frames";
      }

      throw new ExtensionError(errorMessage);
    }

    if (frameIds && frameIds.length === 1 && results.length > 1) {
      throw new ExtensionError("Internal error: multiple windows matched");
    }

    return results;
  }

  /**
   * Inserts a script or stylesheet in the given tab, and returns a promise
   * which resolves when the operation has completed.
   *
   * @param {BaseContext} context
   *        The extension context for which to perform the injection.
   * @param {InjectDetails} details
   *        The InjectDetails object, specifying what to inject, where, and
   *        when.
   * @param {string} kind
   *        The kind of data being injected. Either "script" or "css".
   * @param {string} method
   *        The name of the method which was called to trigger the injection.
   *        Used to generate appropriate error messages on failure.
   *
   * @returns {Promise}
   *        Resolves to the result of the execution, once it has completed.
   * @private
   */
  _execute(context, details, kind, method) {
    let options = {
      jsPaths: [],
      cssPaths: [],
      removeCSS: method == "removeCSS",
      extensionId: context.extension.id,
    };

    // We require a `code` or a `file` property, but we can't accept both.
    if ((details.code === null) == (details.file === null)) {
      return Promise.reject({
        message: `${method} requires either a 'code' or a 'file' property, but not both`,
      });
    }

    if (details.frameId !== null && details.allFrames) {
      return Promise.reject({
        message: `'frameId' and 'allFrames' are mutually exclusive`,
      });
    }

    options.hasActiveTabPermission = this.hasActiveTabPermission;
    options.matches = this.extension.allowedOrigins.patterns.map(
      host => host.pattern
    );

    if (details.code !== null) {
      options[`${kind}Code`] = details.code;
    }
    if (details.file !== null) {
      let url = context.uri.resolve(details.file);
      if (!this.extension.isExtensionURL(url)) {
        return Promise.reject({
          message: "Files to be injected must be within the extension",
        });
      }
      options[`${kind}Paths`].push(url);
    }

    if (details.allFrames) {
      options.allFrames = true;
    } else if (details.frameId !== null) {
      options.frameIds = [details.frameId];
    } else if (!details.allFrames) {
      options.frameIds = [0];
    }

    if (details.matchAboutBlank) {
      options.matchAboutBlank = details.matchAboutBlank;
    }
    if (details.runAt !== null) {
      options.runAt = details.runAt;
    } else {
      options.runAt = "document_idle";
    }
    if (details.cssOrigin !== null) {
      options.cssOrigin = details.cssOrigin;
    } else {
      options.cssOrigin = "author";
    }

    options.wantReturnValue = true;

    // The scripting API (defined in `parent/ext-scripting.js`) has its own
    // `execute()` function that calls `queryContent()` as well. Make sure to
    // keep both in sync when relevant.
    return this.queryContent("Execute", options);
  }

  /**
   * Executes a script in the tab's content window, and returns a Promise which
   * resolves to the result of the evaluation, or rejects to the value of any
   * error the injection generates.
   *
   * @param {BaseContext} context
   *        The extension context for which to inject the script.
   * @param {InjectDetails} details
   *        The InjectDetails object, specifying what to inject, where, and
   *        when.
   *
   * @returns {Promise}
   *        Resolves to the result of the evaluation of the given script, once
   *        it has completed, or rejects with any error the evaluation
   *        generates.
   */
  executeScript(context, details) {
    return this._execute(context, details, "js", "executeScript");
  }

  /**
   * Injects CSS into the tab's content window, and returns a Promise which
   * resolves when the injection is complete.
   *
   * @param {BaseContext} context
   *        The extension context for which to inject the script.
   * @param {InjectDetails} details
   *        The InjectDetails object, specifying what to inject, and where.
   *
   * @returns {Promise}
   *        Resolves when the injection has completed.
   */
  insertCSS(context, details) {
    return this._execute(context, details, "css", "insertCSS").then(() => {});
  }

  /**
   * Removes CSS which was previously into the tab's content window via
   * `insertCSS`, and returns a Promise which resolves when the operation is
   * complete.
   *
   * @param {BaseContext} context
   *        The extension context for which to remove the CSS.
   * @param {InjectDetails} details
   *        The InjectDetails object, specifying what to remove, and from where.
   *
   * @returns {Promise}
   *        Resolves when the operation has completed.
   */
  removeCSS(context, details) {
    return this._execute(context, details, "css", "removeCSS").then(() => {});
  }
}

defineLazyGetter(TabBase.prototype, "incognito", function () {
  return this._incognito;
});

// Note: These must match the values in windows.json.
const WINDOW_ID_NONE = -1;
const WINDOW_ID_CURRENT = -2;

/**
 * A platform-independent base class for extension-specific wrappers around
 * native browser windows
 *
 * @param {Extension} extension
 *        The extension object for which this wrapper is being created.
 * @param {DOMWindow} window
 *        The browser DOM window which is being wrapped.
 * @param {integer} id
 *        The numeric ID of this DOM window object. This ID should be the same for
 *        every extension, and for the lifetime of the window.
 */
class WindowBase {
  constructor(extension, window, id) {
    if (!extension.canAccessWindow(window)) {
      throw new ExtensionError("extension cannot access window");
    }
    this.extension = extension;
    this.window = window;
    this.id = id;
  }

  /**
   * @property {nsIAppWindow} appWindow
   *        The nsIAppWindow object for this browser window.
   *        @readonly
   */
  get appWindow() {
    return this.window.docShell.treeOwner
      .QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIAppWindow);
  }

  /**
   * Returns true if this window is the current window for the given extension
   * context, false otherwise.
   *
   * @param {BaseContext} context
   *        The extension context for which to perform the check.
   *
   * @returns {boolean}
   */
  isCurrentFor(context) {
    if (context && context.currentWindow) {
      return this.window === context.currentWindow;
    }
    return this.isLastFocused;
  }

  /**
   * @property {string} type
   *        The type of the window, as defined by the WebExtension API. May be
   *        either "normal" or "popup".
   *        @readonly
   */
  get type() {
    let { chromeFlags } = this.appWindow;

    if (chromeFlags & Ci.nsIWebBrowserChrome.CHROME_OPENAS_DIALOG) {
      return "popup";
    }

    return "normal";
  }

  /**
   * Converts this window object to a JSON-compatible object which may be
   * returned to an extension, in the format required to be returned by
   * WebExtension APIs.
   *
   * @param {object} [getInfo]
   *        An optional object, the properties of which determine what data is
   *        available on the result object.
   * @param {boolean} [getInfo.populate]
   *        Of true, the result object will contain a `tabs` property,
   *        containing an array of converted Tab objects, one for each tab in
   *        the window.
   *
   * @returns {object}
   */
  convert(getInfo) {
    let result = {
      id: this.id,
      focused: this.focused,
      top: this.top,
      left: this.left,
      width: this.width,
      height: this.height,
      incognito: this.incognito,
      type: this.type,
      state: this.state,
      alwaysOnTop: this.alwaysOnTop,
      title: this.title,
    };

    if (getInfo && getInfo.populate) {
      result.tabs = Array.from(this.getTabs(), tab => tab.convert());
    }

    return result;
  }

  /**
   * Returns true if this window matches the the given query info object. Omitted
   * or null have no effect on the match.
   *
   * @param {object} queryInfo
   *        The query info against which to match.
   * @param {boolean} [queryInfo.currentWindow]
   *        Matches against against the return value of `isCurrentFor()` for the
   *        given context.
   * @param {boolean} [queryInfo.lastFocusedWindow]
   *        Matches against the exact value of the window's `isLastFocused` attribute.
   * @param {boolean} [queryInfo.windowId]
   *        Matches against the exact value of the window's ID, taking into
   *        account the special WINDOW_ID_CURRENT value.
   * @param {string} [queryInfo.windowType]
   *        Matches against the exact value of the window's `type` attribute.
   * @param {BaseContext} context
   *        The extension context for which the matching is being performed.
   *        Used to determine the current window for relevant properties.
   *
   * @returns {boolean}
   *        True if the window matches the query.
   */
  matches(queryInfo, context) {
    if (
      queryInfo.lastFocusedWindow !== null &&
      queryInfo.lastFocusedWindow !== this.isLastFocused
    ) {
      return false;
    }

    if (queryInfo.windowType !== null && queryInfo.windowType !== this.type) {
      return false;
    }

    if (queryInfo.windowId !== null) {
      if (queryInfo.windowId === WINDOW_ID_CURRENT) {
        if (!this.isCurrentFor(context)) {
          return false;
        }
      } else if (queryInfo.windowId !== this.id) {
        return false;
      }
    }

    if (
      queryInfo.currentWindow !== null &&
      queryInfo.currentWindow !== this.isCurrentFor(context)
    ) {
      return false;
    }

    return true;
  }

  /**
   * @property {boolean} focused
   *        Returns true if the browser window is currently focused.
   *        @readonly
   *        @abstract
   */
  get focused() {
    throw new Error("Not implemented");
  }

  /**
   * @property {integer} top
   *        Returns the pixel offset of the top of the window from the top of
   *        the screen.
   *        @readonly
   *        @abstract
   */
  get top() {
    throw new Error("Not implemented");
  }

  /**
   * @property {integer} left
   *        Returns the pixel offset of the left of the window from the left of
   *        the screen.
   *        @readonly
   *        @abstract
   */
  get left() {
    throw new Error("Not implemented");
  }

  /**
   * @property {integer} width
   *        Returns the pixel width of the window.
   *        @readonly
   *        @abstract
   */
  get width() {
    throw new Error("Not implemented");
  }

  /**
   * @property {integer} height
   *        Returns the pixel height of the window.
   *        @readonly
   *        @abstract
   */
  get height() {
    throw new Error("Not implemented");
  }

  /**
   * @property {boolean} incognito
   *        Returns true if this is a private browsing window, false otherwise.
   *        @readonly
   *        @abstract
   */
  get incognito() {
    throw new Error("Not implemented");
  }

  /**
   * @property {boolean} alwaysOnTop
   *        Returns true if this window is constrained to always remain above
   *        other windows.
   *        @readonly
   *        @abstract
   */
  get alwaysOnTop() {
    throw new Error("Not implemented");
  }

  /**
   * @property {boolean} isLastFocused
   *        Returns true if this is the browser window which most recently had
   *        focus.
   *        @readonly
   *        @abstract
   */
  get isLastFocused() {
    throw new Error("Not implemented");
  }

  /**
   * @property {string} state
   *        Returns or sets the current state of this window, as determined by
   *        `getState()`.
   *        @abstract
   */
  get state() {
    throw new Error("Not implemented");
  }

  set state(state) {
    throw new Error("Not implemented");
  }

  /**
   * @property {nsIURI | undefined} title
   *        Returns the current title of this window if the extension has permission
   *        to read it, or undefined otherwise.
   *        @readonly
   */
  get title() {
    // activeTab may be null when a new window is adopting an existing tab as its first tab
    // (See Bug 1458918 for rationale).
    if (this.activeTab && this.activeTab.hasTabPermission) {
      return this._title;
    }
    return undefined;
  }

  // The JSDoc validator does not support @returns tags in abstract functions or
  // star functions without return statements.
  /* eslint-disable valid-jsdoc */
  /**
   * Returns the window state of the given window.
   *
   * @param {DOMWindow} _window
   *        The window for which to return a state.
   *
   * @returns {string}
   *        The window's state. One of "normal", "minimized", "maximized",
   *        "fullscreen", or "docked".
   * @static
   * @abstract
   */
  static getState(_window) {
    throw new Error("Not implemented");
  }

  /**
   * Returns an iterator of TabBase objects for each tab in this window.
   *
   * @returns {Iterator<TabBase>}
   */
  getTabs() {
    throw new Error("Not implemented");
  }

  /**
   * Returns an iterator of TabBase objects for each highlighted tab in this window.
   *
   * @returns {Iterator<TabBase>}
   */
  getHighlightedTabs() {
    throw new Error("Not implemented");
  }

  /**
   * @property {TabBase} The window's currently active tab.
   */
  get activeTab() {
    throw new Error("Not implemented");
  }

  /**
   * Returns the window's tab at the specified index.
   *
   * @param {integer} _index
   *        The index of the desired tab.
   *
   * @returns {TabBase|undefined}
   */
  getTabAtIndex(_index) {
    throw new Error("Not implemented");
  }
  /* eslint-enable valid-jsdoc */
}

Object.assign(WindowBase, { WINDOW_ID_NONE, WINDOW_ID_CURRENT });

/**
 * The parameter type of "tab-attached" events, which are emitted when a
 * pre-existing tab is attached to a new window.
 *
 * @typedef {object} TabAttachedEvent
 * @property {NativeTab} tab
 *        The native tab object in the window to which the tab is being
 *        attached. This may be a different object than was used to represent
 *        the tab in the old window.
 * @property {integer} tabId
 *        The ID of the tab being attached.
 * @property {integer} newWindowId
 *        The ID of the window to which the tab is being attached.
 * @property {integer} newPosition
 *        The position of the tab in the tab list of the new window.
 */

/**
 * The parameter type of "tab-detached" events, which are emitted when a
 * pre-existing tab is detached from a window, in order to be attached to a new
 * window.
 *
 * @typedef {object} TabDetachedEvent
 * @property {NativeTab} tab
 *        The native tab object in the window from which the tab is being
 *        detached. This may be a different object than will be used to
 *        represent the tab in the new window.
 * @property {NativeTab} adoptedBy
 *        The native tab object in the window to which the tab will be attached,
 *        and is adopting the contents of this tab. This may be a different
 *        object than the tab in the previous window.
 * @property {integer} tabId
 *        The ID of the tab being detached.
 * @property {integer} oldWindowId
 *        The ID of the window from which the tab is being detached.
 * @property {integer} oldPosition
 *        The position of the tab in the tab list of the window from which it is
 *        being detached.
 */

/**
 * The parameter type of "tab-created" events, which are emitted when a
 * new tab is created.
 *
 * @typedef {object} TabCreatedEvent
 * @property {NativeTab} tab
 *        The native tab object for the tab which is being created.
 */

/**
 * The parameter type of "tab-removed" events, which are emitted when a
 * tab is removed and destroyed.
 *
 * @typedef {object} TabRemovedEvent
 * @property {NativeTab} tab
 *        The native tab object for the tab which is being removed.
 * @property {integer} tabId
 *        The ID of the tab being removed.
 * @property {integer} windowId
 *        The ID of the window from which the tab is being removed.
 * @property {boolean} isWindowClosing
 *        True if the tab is being removed because the window is closing.
 */

/**
 * An object containing basic, extension-independent information about the window
 * and tab that a XUL <browser> belongs to.
 *
 * @typedef {object} BrowserData
 * @property {integer} tabId
 *        The numeric ID of the tab that a <browser> belongs to, or -1 if it
 *        does not belong to a tab.
 * @property {integer} windowId
 *        The numeric ID of the browser window that a <browser> belongs to, or -1
 *        if it does not belong to a browser window.
 */

/**
 * A platform-independent base class for the platform-specific TabTracker
 * classes, which track the opening and closing of tabs, and manage the mapping
 * of them between numeric IDs and native tab objects.
 *
 * Instances of this class are EventEmitters which emit the following events,
 * each with an argument of the given type:
 *
 * - "tab-attached" {@link TabAttacheEvent}
 * - "tab-detached" {@link TabDetachedEvent}
 * - "tab-created" {@link TabCreatedEvent}
 * - "tab-removed" {@link TabRemovedEvent}
 */
class TabTrackerBase extends EventEmitter {
  on(...args) {
    if (!this.initialized) {
      this.init();
    }

    return super.on(...args); // eslint-disable-line mozilla/balanced-listeners
  }

  /**
   * Called to initialize the tab tracking listeners the first time that an
   * event listener is added.
   *
   * @protected
   * @abstract
   */
  init() {
    throw new Error("Not implemented");
  }

  // The JSDoc validator does not support @returns tags in abstract functions or
  // star functions without return statements.
  /* eslint-disable valid-jsdoc */
  /**
   * Returns the numeric ID for the given native tab.
   *
   * @param {NativeTab} _nativeTab
   *        The native tab for which to return an ID.
   *
   * @returns {integer}
   *        The tab's numeric ID.
   * @abstract
   */
  getId(_nativeTab) {
    throw new Error("Not implemented");
  }

  /**
   * Returns the native tab with the given numeric ID.
   *
   * @param {integer} _tabId
   *        The numeric ID of the tab to return.
   * @param {*} _default
   *        The value to return if no tab exists with the given ID.
   *
   * @returns {NativeTab}
   * @throws {ExtensionError}
   *       If no tab exists with the given ID and a default return value is not
   *       provided.
   * @abstract
   */
  getTab(_tabId, _default) {
    throw new Error("Not implemented");
  }

  /**
   * Returns basic information about the tab and window that the given browser
   * belongs to.
   *
   * @param {XULElement} browser
   *        The XUL browser element for which to return data.
   *
   * @returns {BrowserData}
   * @abstract
   */
  /* eslint-enable valid-jsdoc */
  getBrowserData() {
    throw new Error("Not implemented");
  }

  /**
   * @property {NativeTab} activeTab
   *        Returns the native tab object for the active tab in the
   *        most-recently focused window, or null if no live tabs currently
   *        exist.
   *        @abstract
   */
  get activeTab() {
    throw new Error("Not implemented");
  }
}

/**
 * A browser progress listener instance which calls a given listener function
 * whenever the status of the given browser changes.
 *
 * @param {function(object): void} listener
 *        A function to be called whenever the status of a tab's top-level
 *        browser. It is passed an object with a `browser` property pointing to
 *        the XUL browser, and a `status` property with a string description of
 *        the browser's status.
 * @private
 */
class StatusListener {
  constructor(listener) {
    this.listener = listener;
  }

  onStateChange(browser, webProgress, request, stateFlags, statusCode) {
    if (!webProgress.isTopLevel) {
      return;
    }

    let status;
    if (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
      if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
        status = "loading";
      } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
        status = "complete";
      }
    } else if (
      stateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
      statusCode == Cr.NS_BINDING_ABORTED
    ) {
      status = "complete";
    }

    if (status) {
      this.listener({ browser, status });
    }
  }

  onLocationChange(browser, webProgress, request, locationURI) {
    if (webProgress.isTopLevel) {
      let status = webProgress.isLoadingDocument ? "loading" : "complete";
      this.listener({ browser, status, url: locationURI.spec });
    }
  }
}

/**
 * A platform-independent base class for the platform-specific WindowTracker
 * classes, which track the opening and closing of windows, and manage the
 * mapping of them between numeric IDs and native tab objects.
 */
class WindowTrackerBase extends EventEmitter {
  constructor() {
    super();

    this._handleWindowOpened = this._handleWindowOpened.bind(this);

    this._openListeners = new Set();
    this._closeListeners = new Set();

    this._listeners = new DefaultMap(() => new Set());

    this._statusListeners = new DefaultWeakMap(listener => {
      return new StatusListener(listener);
    });

    this._windowIds = new DefaultWeakMap(window => {
      return window.docShell.outerWindowID;
    });
  }

  isBrowserWindow(window) {
    let { documentElement } = window.document;

    return documentElement.getAttribute("windowtype") === "navigator:browser";
  }

  // The JSDoc validator does not support @returns tags in abstract functions or
  // star functions without return statements.
  /* eslint-disable valid-jsdoc */
  /**
   * Returns an iterator for all currently active browser windows.
   *
   * @param {boolean} [includeInomplete = false]
   *        If true, include browser windows which are not yet fully loaded.
   *        Otherwise, only include windows which are.
   *
   * @returns {Iterator<DOMWindow>}
   */
  /* eslint-enable valid-jsdoc */
  *browserWindows(includeIncomplete = false) {
    // The window type parameter is only available once the window's document
    // element has been created. This means that, when looking for incomplete
    // browser windows, we need to ignore the type entirely for windows which
    // haven't finished loading, since we would otherwise skip browser windows
    // in their early loading stages.
    // This is particularly important given that the "domwindowcreated" event
    // fires for browser windows when they're in that in-between state, and just
    // before we register our own "domwindowcreated" listener.

    for (let window of Services.wm.getEnumerator("")) {
      let ok = includeIncomplete;
      if (window.document.readyState === "complete") {
        ok = this.isBrowserWindow(window);
      }

      if (ok) {
        yield window;
      }
    }
  }

  /**
   * @property {DOMWindow|null} topWindow
   *        The currently active, or topmost, browser window, or null if no
   *        browser window is currently open.
   *        @readonly
   */
  get topWindow() {
    return Services.wm.getMostRecentWindow("navigator:browser");
  }

  /**
   * @property {DOMWindow|null} topWindow
   *        The currently active, or topmost, browser window that is not
   *        private browsing, or null if no browser window is currently open.
   *        @readonly
   */
  get topNonPBWindow() {
    return Services.wm.getMostRecentNonPBWindow("navigator:browser");
  }

  /**
   * Returns the top window accessible by the extension.
   *
   * @param {BaseContext} context
   *        The extension context for which to return the current window.
   *
   * @returns {DOMWindow|null}
   */
  getTopWindow(context) {
    if (context && !context.privateBrowsingAllowed) {
      return this.topNonPBWindow;
    }
    return this.topWindow;
  }

  /**
   * Returns the numeric ID for the given browser window.
   *
   * @param {DOMWindow} window
   *        The DOM window for which to return an ID.
   *
   * @returns {integer}
   *        The window's numeric ID.
   */
  getId(window) {
    return this._windowIds.get(window);
  }

  /**
   * Returns the browser window to which the given context belongs, or the top
   * browser window if the context does not belong to a browser window.
   *
   * @param {BaseContext} context
   *        The extension context for which to return the current window.
   *
   * @returns {DOMWindow|null}
   */
  getCurrentWindow(context) {
    return (context && context.currentWindow) || this.getTopWindow(context);
  }

  /**
   * Returns the browser window with the given ID.
   *
   * @param {integer} id
   *        The ID of the window to return.
   * @param {BaseContext} context
   *        The extension context for which the matching is being performed.
   *        Used to determine the current window for relevant properties.
   * @param {boolean} [strict = true]
   *        If false, undefined will be returned instead of throwing an error
   *        in case no window exists with the given ID.
   *
   * @returns {DOMWindow|undefined}
   * @throws {ExtensionError}
   *        If no window exists with the given ID and `strict` is true.
   */
  getWindow(id, context, strict = true) {
    if (id === WINDOW_ID_CURRENT) {
      return this.getCurrentWindow(context);
    }

    let window = Services.wm.getOuterWindowWithId(id);
    if (
      window &&
      !window.closed &&
      (window.document.readyState !== "complete" ||
        this.isBrowserWindow(window))
    ) {
      if (!context || context.canAccessWindow(window)) {
        // Tolerate incomplete windows because isBrowserWindow is only reliable
        // once the window is fully loaded.
        return window;
      }
    }

    if (strict) {
      throw new ExtensionError(`Invalid window ID: ${id}`);
    }
  }

  /**
   * @property {boolean} _haveListeners
   *        Returns true if any window open or close listeners are currently
   *        registered.
   * @private
   */
  get _haveListeners() {
    return this._openListeners.size > 0 || this._closeListeners.size > 0;
  }

  /**
   * Register the given listener function to be called whenever a new browser
   * window is opened.
   *
   * @param {function(DOMWindow): void} listener
   *        The listener function to register.
   */
  addOpenListener(listener) {
    if (!this._haveListeners) {
      Services.ww.registerNotification(this);
    }

    this._openListeners.add(listener);

    for (let window of this.browserWindows(true)) {
      if (window.document.readyState !== "complete") {
        window.addEventListener("load", this);
      }
    }
  }

  /**
   * Unregister a listener function registered in a previous addOpenListener
   * call.
   *
   * @param {function(DOMWindow): void} listener
   *        The listener function to unregister.
   */
  removeOpenListener(listener) {
    this._openListeners.delete(listener);

    if (!this._haveListeners) {
      Services.ww.unregisterNotification(this);
    }
  }

  /**
   * Register the given listener function to be called whenever a browser
   * window is closed.
   *
   * @param {function(DOMWindow): void} listener
   *        The listener function to register.
   */
  addCloseListener(listener) {
    if (!this._haveListeners) {
      Services.ww.registerNotification(this);
    }

    this._closeListeners.add(listener);
  }

  /**
   * Unregister a listener function registered in a previous addCloseListener
   * call.
   *
   * @param {function(DOMWindow): void} listener
   *        The listener function to unregister.
   */
  removeCloseListener(listener) {
    this._closeListeners.delete(listener);

    if (!this._haveListeners) {
      Services.ww.unregisterNotification(this);
    }
  }

  /**
   * Handles load events for recently-opened windows, and adds additional
   * listeners which may only be safely added when the window is fully loaded.
   *
   * @param {Event} event
   *        A DOM event to handle.
   * @private
   */
  handleEvent(event) {
    if (event.type === "load") {
      event.currentTarget.removeEventListener(event.type, this);

      let window = event.target.defaultView;
      if (!this.isBrowserWindow(window)) {
        return;
      }

      for (let listener of this._openListeners) {
        try {
          listener(window);
        } catch (e) {
          Cu.reportError(e);
        }
      }
    }
  }

  /**
   * Observes "domwindowopened" and "domwindowclosed" events, notifies the
   * appropriate listeners, and adds necessary additional listeners to the new
   * windows.
   *
   * @param {DOMWindow} window
   *        A DOM window.
   * @param {string} topic
   *        The topic being observed.
   * @private
   */
  observe(window, topic) {
    if (topic === "domwindowclosed") {
      if (!this.isBrowserWindow(window)) {
        return;
      }

      window.removeEventListener("load", this);
      for (let listener of this._closeListeners) {
        try {
          listener(window);
        } catch (e) {
          Cu.reportError(e);
        }
      }
    } else if (topic === "domwindowopened") {
      window.addEventListener("load", this);
    }
  }

  /**
   * Add an event listener to be called whenever the given DOM event is received
   * at the top level of any browser window.
   *
   * @param {string} type
   *        The type of event to listen for. May be any valid DOM event name, or
   *        one of the following special cases:
   *
   *        - "progress": Adds a tab progress listener to every browser window.
   *        - "status": Adds a StatusListener to every tab of every browser
   *           window.
   *        - "domwindowopened": Acts as an alias for addOpenListener.
   *        - "domwindowclosed": Acts as an alias for addCloseListener.
   * @param {Function | object} listener
   *        The listener to invoke in response to the given events.
   *
   * @returns {undefined}
   */
  addListener(type, listener) {
    if (type === "domwindowopened") {
      return this.addOpenListener(listener);
    } else if (type === "domwindowclosed") {
      return this.addCloseListener(listener);
    }

    if (this._listeners.size === 0) {
      this.addOpenListener(this._handleWindowOpened);
    }

    if (type === "status") {
      listener = this._statusListeners.get(listener);
      type = "progress";
    }

    this._listeners.get(type).add(listener);

    // Register listener on all existing windows.
    for (let window of this.browserWindows()) {
      this._addWindowListener(window, type, listener);
    }
  }

  /**
   * Removes an event listener previously registered via an addListener call.
   *
   * @param {string} type
   *        The type of event to stop listening for.
   * @param {Function | object} listener
   *        The listener to remove.
   *
   * @returns {undefined}
   */
  removeListener(type, listener) {
    if (type === "domwindowopened") {
      return this.removeOpenListener(listener);
    } else if (type === "domwindowclosed") {
      return this.removeCloseListener(listener);
    }

    if (type === "status") {
      listener = this._statusListeners.get(listener);
      type = "progress";
    }

    let listeners = this._listeners.get(type);
    listeners.delete(listener);

    if (listeners.size === 0) {
      this._listeners.delete(type);
      if (this._listeners.size === 0) {
        this.removeOpenListener(this._handleWindowOpened);
      }
    }

    // Unregister listener from all existing windows.
    let useCapture = type === "focus" || type === "blur";
    for (let window of this.browserWindows()) {
      if (type === "progress") {
        this.removeProgressListener(window, listener);
      } else {
        window.removeEventListener(type, listener, useCapture);
      }
    }
  }

  /**
   * Adds a listener for the given event to the given window.
   *
   * @param {DOMWindow} window
   *        The browser window to which to add the listener.
   * @param {string} eventType
   *        The type of DOM event to listen for, or "progress" to add a tab
   *        progress listener.
   * @param {Function | object} listener
   *        The listener to add.
   * @private
   */
  _addWindowListener(window, eventType, listener) {
    let useCapture = eventType === "focus" || eventType === "blur";

    if (eventType === "progress") {
      this.addProgressListener(window, listener);
    } else {
      window.addEventListener(eventType, listener, useCapture);
    }
  }

  /**
   * A private method which is called whenever a new browser window is opened,
   * and adds the necessary listeners to it.
   *
   * @param {DOMWindow} window
   *        The window being opened.
   * @private
   */
  _handleWindowOpened(window) {
    for (let [eventType, listeners] of this._listeners) {
      for (let listener of listeners) {
        this._addWindowListener(window, eventType, listener);
      }
    }
  }

  /**
   * Adds a tab progress listener to the given browser window.
   *
   * @param {DOMWindow} _window
   *        The browser window to which to add the listener.
   * @param {object} _listener
   *        The tab progress listener to add.
   * @abstract
   */
  addProgressListener(_window, _listener) {
    throw new Error("Not implemented");
  }

  /**
   * Removes a tab progress listener from the given browser window.
   *
   * @param {DOMWindow} _window
   *        The browser window from which to remove the listener.
   * @param {object} _listener
   *        The tab progress listener to remove.
   * @abstract
   */
  removeProgressListener(_window, _listener) {
    throw new Error("Not implemented");
  }
}

/**
 * Manages native tabs, their wrappers, and their dynamic permissions for a
 * particular extension.
 *
 * @param {Extension} extension
 *        The extension for which to manage tabs.
 */
class TabManagerBase {
  constructor(extension) {
    this.extension = extension;

    this._tabs = new DefaultWeakMap(tab => this.wrapTab(tab));
  }

  /**
   * If the extension has requested activeTab permission, grant it those
   * permissions for the current inner window in the given native tab.
   *
   * @param {NativeTab} nativeTab
   *        The native tab for which to grant permissions.
   */
  addActiveTabPermission(nativeTab) {
    let tab = this.getWrapper(nativeTab);
    if (
      this.extension.hasPermission("activeTab") ||
      (this.extension.originControls &&
        this.extension.optionalOrigins.matches(tab._uri))
    ) {
      // Note that, unlike Chrome, we don't currently clear this permission with
      // the tab navigates. If the inner window is revived from BFCache before
      // we've granted this permission to a new inner window, the extension
      // maintains its permissions for it.
      tab.activeTabWindowID = tab.innerWindowID;
    }
  }

  /**
   * Revoke the extension's activeTab permissions for the current inner window
   * of the given native tab.
   *
   * @param {NativeTab} nativeTab
   *        The native tab for which to revoke permissions.
   */
  revokeActiveTabPermission(nativeTab) {
    this.getWrapper(nativeTab).activeTabWindowID = null;
  }

  /**
   * Returns true if the extension has requested activeTab permission, and has
   * been granted permissions for the current inner window if this tab.
   *
   * @param {NativeTab} nativeTab
   *        The native tab for which to check permissions.
   * @returns {boolean}
   *        True if the extension has activeTab permissions for this tab.
   */
  hasActiveTabPermission(nativeTab) {
    return this.getWrapper(nativeTab).hasActiveTabPermission;
  }

  /**
   * Activate MV3 content scripts if the extension has activeTab or an
   * (ungranted) host permission.
   *
   * @param {NativeTab} nativeTab
   */
  activateScripts(nativeTab) {
    let tab = this.getWrapper(nativeTab);
    if (
      this.extension.originControls &&
      !tab.matchesHostPermission &&
      (this.extension.optionalOrigins.matches(tab._uri) ||
        this.extension.hasPermission("activeTab")) &&
      (this.extension.contentScripts.length ||
        this.extension.registeredContentScripts.size)
    ) {
      tab.queryContent("ActivateScripts", { id: this.extension.id });
    }
  }

  /**
   * Returns true if the extension has permissions to access restricted
   * properties of the given native tab. In practice, this means that it has
   * either requested the "tabs" permission or has activeTab permissions for the
   * given tab.
   *
   * NOTE: Never use this method on an object that is not a native tab
   * for the current platform: this method implicitly generates a wrapper
   * for the passed nativeTab parameter and the platform-specific tabTracker
   * instance is likely to store it in a map which is cleared only when the
   * tab is closed (and so, if nativeTab is not a real native tab, it will
   * never be cleared from the platform-specific tabTracker instance),
   * See Bug 1458918 for a rationale.
   *
   * @param {NativeTab} nativeTab
   *        The native tab for which to check permissions.
   * @returns {boolean}
   *        True if the extension has permissions for this tab.
   */
  hasTabPermission(nativeTab) {
    return this.getWrapper(nativeTab).hasTabPermission;
  }

  /**
   * Returns this extension's TabBase wrapper for the given native tab. This
   * method will always return the same wrapper object for any given native tab.
   *
   * @param {NativeTab} nativeTab
   *        The tab for which to return a wrapper.
   *
   * @returns {TabBase|undefined}
   *        The wrapper for this tab.
   */
  getWrapper(nativeTab) {
    if (this.canAccessTab(nativeTab)) {
      return this._tabs.get(nativeTab);
    }
  }

  /**
   * Determines access using extension context.
   *
   * @param {NativeTab} _nativeTab
   *        The tab to check access on.
   * @returns {boolean}
   *        True if the extension has permissions for this tab.
   * @protected
   * @abstract
   */
  canAccessTab(_nativeTab) {
    throw new Error("Not implemented");
  }

  /**
   * Converts the given native tab to a JSON-compatible object, in the format
   * required to be returned by WebExtension APIs, which may be safely passed to
   * extension code.
   *
   * @param {NativeTab} nativeTab
   *        The native tab to convert.
   * @param {object} [fallbackTabSize]
   *        A geometry data if the lazy geometry data for this tab hasn't been
   *        initialized yet.
   *
   * @returns {object}
   */
  convert(nativeTab, fallbackTabSize = null) {
    return this.getWrapper(nativeTab).convert(fallbackTabSize);
  }

  // The JSDoc validator does not support @returns tags in abstract functions or
  // star functions without return statements.
  /* eslint-disable valid-jsdoc */
  /**
   * Returns an iterator of TabBase objects which match the given query info.
   *
   * @param {object | null} [queryInfo = null]
   *        An object containing properties on which to filter. May contain any
   *        properties which are recognized by {@link TabBase#matches} or
   *        {@link WindowBase#matches}. Unknown properties will be ignored.
   * @param {BaseContext|null} [context = null]
   *        The extension context for which the matching is being performed.
   *        Used to determine the current window for relevant properties.
   *
   * @returns {Iterator<TabBase>}
   */
  *query(queryInfo = null, context = null) {
    if (queryInfo) {
      if (queryInfo.url !== null) {
        queryInfo.url = parseMatchPatterns([].concat(queryInfo.url), {
          restrictSchemes: false,
        });
      }

      if (queryInfo.cookieStoreId !== null) {
        queryInfo.cookieStoreId = [].concat(queryInfo.cookieStoreId);
      }

      if (queryInfo.title !== null) {
        try {
          queryInfo.title = new MatchGlob(queryInfo.title);
        } catch (e) {
          throw new ExtensionError(`Invalid title: ${queryInfo.title}`);
        }
      }
    }
    function* candidates(windowWrapper) {
      if (queryInfo) {
        let { active, highlighted, index } = queryInfo;
        if (active === true) {
          let { activeTab } = windowWrapper;
          if (activeTab) {
            yield activeTab;
          }
          return;
        }
        if (index != null) {
          let tabWrapper = windowWrapper.getTabAtIndex(index);
          if (tabWrapper) {
            yield tabWrapper;
          }
          return;
        }
        if (highlighted === true) {
          yield* windowWrapper.getHighlightedTabs();
          return;
        }
      }
      yield* windowWrapper.getTabs();
    }
    let windowWrappers = this.extension.windowManager.query(queryInfo, context);
    for (let windowWrapper of windowWrappers) {
      for (let tabWrapper of candidates(windowWrapper)) {
        if (!queryInfo || tabWrapper.matches(queryInfo)) {
          yield tabWrapper;
        }
      }
    }
  }

  /**
   * Returns a TabBase wrapper for the tab with the given ID.
   *
   * @param {integer} _tabId
   *        The ID of the tab for which to return a wrapper.
   *
   * @returns {TabBase}
   * @throws {ExtensionError}
   *        If no tab exists with the given ID.
   * @abstract
   */
  get(_tabId) {
    throw new Error("Not implemented");
  }

  /**
   * Returns a new TabBase instance wrapping the given native tab.
   *
   * @param {NativeTab} _nativeTab
   *        The native tab for which to return a wrapper.
   *
   * @returns {TabBase}
   * @protected
   * @abstract
   */
  wrapTab(_nativeTab) {
    throw new Error("Not implemented");
  }
}

/**
 * Manages native browser windows and their wrappers for a particular extension.
 *
 * @param {Extension} extension
 *        The extension for which to manage windows.
 */
class WindowManagerBase {
  constructor(extension) {
    this.extension = extension;

    this._windows = new DefaultWeakMap(window => this.wrapWindow(window));
  }

  /**
   * Converts the given browser window to a JSON-compatible object, in the
   * format required to be returned by WebExtension APIs, which may be safely
   * passed to extension code.
   *
   * @param {DOMWindow} window
   *        The browser window to convert.
   * @param {*} args
   *        Additional arguments to be passed to {@link WindowBase#convert}.
   *
   * @returns {object}
   */
  convert(window, ...args) {
    return this.getWrapper(window).convert(...args);
  }

  /**
   * Returns this extension's WindowBase wrapper for the given browser window.
   * This method will always return the same wrapper object for any given
   * browser window.
   *
   * @param {DOMWindow} window
   *        The browser window for which to return a wrapper.
   *
   * @returns {WindowBase|undefined}
   *        The wrapper for this tab.
   */
  getWrapper(window) {
    if (this.extension.canAccessWindow(window)) {
      return this._windows.get(window);
    }
  }

  /**
   * Returns whether this window can be accessed by the extension in the given
   * context.
   *
   * @param {DOMWindow} window
   *        The browser window that is being tested
   * @param {BaseContext|null} context
   *        The extension context for which this test is being performed.
   * @returns {boolean}
   */
  canAccessWindow(window, context) {
    return (
      (context && context.canAccessWindow(window)) ||
      this.extension.canAccessWindow(window)
    );
  }

  // The JSDoc validator does not support @returns tags in abstract functions or
  // star functions without return statements.
  /* eslint-disable valid-jsdoc */
  /**
   * Returns an iterator of WindowBase objects which match the given query info.
   *
   * @param {object | null} [queryInfo = null]
   *        An object containing properties on which to filter. May contain any
   *        properties which are recognized by {@link WindowBase#matches}.
   *        Unknown properties will be ignored.
   * @param {BaseContext|null} [context = null]
   *        The extension context for which the matching is being performed.
   *        Used to determine the current window for relevant properties.
   *
   * @returns {Iterator<WindowBase>}
   */
  *query(queryInfo = null, context = null) {
    function* candidates(windowManager) {
      if (queryInfo) {
        let { currentWindow, windowId, lastFocusedWindow } = queryInfo;
        if (currentWindow === true && windowId == null) {
          windowId = WINDOW_ID_CURRENT;
        }
        if (windowId != null) {
          let window = global.windowTracker.getWindow(windowId, context, false);
          if (window) {
            yield windowManager.getWrapper(window);
          }
          return;
        }
        if (lastFocusedWindow === true) {
          let window = global.windowTracker.getTopWindow(context);
          if (window) {
            yield windowManager.getWrapper(window);
          }
          return;
        }
      }
      yield* windowManager.getAll(context);
    }
    for (let windowWrapper of candidates(this)) {
      if (!queryInfo || windowWrapper.matches(queryInfo, context)) {
        yield windowWrapper;
      }
    }
  }

  /**
   * Returns a WindowBase wrapper for the browser window with the given ID.
   *
   * @param {integer} _windowId
   *        The ID of the browser window for which to return a wrapper.
   * @param {BaseContext} _context
   *        The extension context for which the matching is being performed.
   *        Used to determine the current window for relevant properties.
   *
   * @returns {WindowBase}
   * @throws {ExtensionError}
   *        If no window exists with the given ID.
   * @abstract
   */
  get(_windowId, _context) {
    throw new Error("Not implemented");
  }

  /**
   * Returns an iterator of WindowBase wrappers for each currently existing
   * browser window.
   *
   * @returns {Iterator<WindowBase>}
   * @abstract
   */
  getAll() {
    throw new Error("Not implemented");
  }

  /**
   * Returns a new WindowBase instance wrapping the given browser window.
   *
   * @param {DOMWindow} _window
   *        The browser window for which to return a wrapper.
   *
   * @returns {WindowBase}
   * @protected
   * @abstract
   */
  wrapWindow(_window) {
    throw new Error("Not implemented");
  }
  /* eslint-enable valid-jsdoc */
}

function getUserContextIdForCookieStoreId(
  extension,
  cookieStoreId,
  isPrivateBrowsing
) {
  if (!extension.hasPermission("cookies")) {
    throw new ExtensionError(
      `No permission for cookieStoreId: ${cookieStoreId}`
    );
  }

  if (!isValidCookieStoreId(cookieStoreId)) {
    throw new ExtensionError(`Illegal cookieStoreId: ${cookieStoreId}`);
  }

  if (isPrivateBrowsing && !isPrivateCookieStoreId(cookieStoreId)) {
    throw new ExtensionError(
      `Illegal to set non-private cookieStoreId in a private window`
    );
  }

  if (!isPrivateBrowsing && isPrivateCookieStoreId(cookieStoreId)) {
    throw new ExtensionError(
      `Illegal to set private cookieStoreId in a non-private window`
    );
  }

  if (isContainerCookieStoreId(cookieStoreId)) {
    if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
      // Container tabs are not supported in perma-private browsing mode - bug 1320757
      throw new ExtensionError(
        `Contextual identities are unavailable in permanent private browsing mode`
      );
    }
    if (!containersEnabled) {
      throw new ExtensionError(`Contextual identities are currently disabled`);
    }
    let userContextId = getContainerForCookieStoreId(cookieStoreId);
    if (!userContextId) {
      throw new ExtensionError(
        `No cookie store exists with ID ${cookieStoreId}`
      );
    }
    if (!extension.canAccessContainer(userContextId)) {
      throw new ExtensionError(`Cannot access ${cookieStoreId}`);
    }
    return userContextId;
  }

  return Services.scriptSecurityManager.DEFAULT_USER_CONTEXT_ID;
}

Object.assign(global, {
  TabTrackerBase,
  TabManagerBase,
  TabBase,
  WindowTrackerBase,
  WindowManagerBase,
  WindowBase,
  getUserContextIdForCookieStoreId,
});
PK
!<O��%��0chrome/en-US/locale/en-US/global/intl.properties
intl.accept_languages=en-US, en

font.language.group=x-western

pluralRule=1

intl.menuitems.alwaysappendaccesskeys=
intl.menuitems.insertseparatorbeforeaccesskeys=true
PK
!<\܅�����Achrome/toolkit/content/global/third_party/cfworker/json-schema.js/*
 * Copyright (c) 2020 Jeremy Danyow
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

'use strict';

function deepCompareStrict(a, b) {
    const typeofa = typeof a;
    if (typeofa !== typeof b) {
        return false;
    }
    if (Array.isArray(a)) {
        if (!Array.isArray(b)) {
            return false;
        }
        const length = a.length;
        if (length !== b.length) {
            return false;
        }
        for (let i = 0; i < length; i++) {
            if (!deepCompareStrict(a[i], b[i])) {
                return false;
            }
        }
        return true;
    }
    if (typeofa === 'object') {
        if (!a || !b) {
            return a === b;
        }
        const aKeys = Object.keys(a);
        const bKeys = Object.keys(b);
        const length = aKeys.length;
        if (length !== bKeys.length) {
            return false;
        }
        for (const k of aKeys) {
            if (!deepCompareStrict(a[k], b[k])) {
                return false;
            }
        }
        return true;
    }
    return a === b;
}

function encodePointer(p) {
    return encodeURI(escapePointer(p));
}
function escapePointer(p) {
    return p.replace(/~/g, '~0').replace(/\//g, '~1');
}

const schemaKeyword = {
    additionalItems: true,
    unevaluatedItems: true,
    items: true,
    contains: true,
    additionalProperties: true,
    unevaluatedProperties: true,
    propertyNames: true,
    not: true,
    if: true,
    then: true,
    else: true
};
const schemaArrayKeyword = {
    prefixItems: true,
    items: true,
    allOf: true,
    anyOf: true,
    oneOf: true
};
const schemaMapKeyword = {
    $defs: true,
    definitions: true,
    properties: true,
    patternProperties: true,
    dependentSchemas: true
};
const ignoredKeyword = {
    id: true,
    $id: true,
    $ref: true,
    $schema: true,
    $anchor: true,
    $vocabulary: true,
    $comment: true,
    default: true,
    enum: true,
    const: true,
    required: true,
    type: true,
    maximum: true,
    minimum: true,
    exclusiveMaximum: true,
    exclusiveMinimum: true,
    multipleOf: true,
    maxLength: true,
    minLength: true,
    pattern: true,
    format: true,
    maxItems: true,
    minItems: true,
    uniqueItems: true,
    maxProperties: true,
    minProperties: true
};
let initialBaseURI = typeof self !== 'undefined' && self.location
    ?
        new URL(self.location.origin + self.location.pathname + location.search)
    : new URL('https://github.com/cfworker');
function dereference(schema, lookup = Object.create(null), baseURI = initialBaseURI, basePointer = '') {
    if (schema && typeof schema === 'object' && !Array.isArray(schema)) {
        const id = schema.$id || schema.id;
        if (id) {
            const url = new URL(id, baseURI.href);
            if (url.hash.length > 1) {
                lookup[url.href] = schema;
            }
            else {
                url.hash = '';
                if (basePointer === '') {
                    baseURI = url;
                }
                else {
                    dereference(schema, lookup, baseURI);
                }
            }
        }
    }
    else if (schema !== true && schema !== false) {
        return lookup;
    }
    const schemaURI = baseURI.href + (basePointer ? '#' + basePointer : '');
    if (lookup[schemaURI] !== undefined) {
        throw new Error(`Duplicate schema URI "${schemaURI}".`);
    }
    lookup[schemaURI] = schema;
    if (schema === true || schema === false) {
        return lookup;
    }
    if (schema.__absolute_uri__ === undefined) {
        Object.defineProperty(schema, '__absolute_uri__', {
            enumerable: false,
            value: schemaURI
        });
    }
    if (schema.$ref && schema.__absolute_ref__ === undefined) {
        const url = new URL(schema.$ref, baseURI.href);
        url.hash = url.hash;
        Object.defineProperty(schema, '__absolute_ref__', {
            enumerable: false,
            value: url.href
        });
    }
    if (schema.$recursiveRef && schema.__absolute_recursive_ref__ === undefined) {
        const url = new URL(schema.$recursiveRef, baseURI.href);
        url.hash = url.hash;
        Object.defineProperty(schema, '__absolute_recursive_ref__', {
            enumerable: false,
            value: url.href
        });
    }
    if (schema.$anchor) {
        const url = new URL('#' + schema.$anchor, baseURI.href);
        lookup[url.href] = schema;
    }
    for (let key in schema) {
        if (ignoredKeyword[key]) {
            continue;
        }
        const keyBase = `${basePointer}/${encodePointer(key)}`;
        const subSchema = schema[key];
        if (Array.isArray(subSchema)) {
            if (schemaArrayKeyword[key]) {
                const length = subSchema.length;
                for (let i = 0; i < length; i++) {
                    dereference(subSchema[i], lookup, baseURI, `${keyBase}/${i}`);
                }
            }
        }
        else if (schemaMapKeyword[key]) {
            for (let subKey in subSchema) {
                dereference(subSchema[subKey], lookup, baseURI, `${keyBase}/${encodePointer(subKey)}`);
            }
        }
        else {
            dereference(subSchema, lookup, baseURI, keyBase);
        }
    }
    return lookup;
}

const DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
const TIME = /^(\d\d):(\d\d):(\d\d)(\.\d+)?(z|[+-]\d\d(?::?\d\d)?)?$/i;
const HOSTNAME = /^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\.?$/i;
const URIREF = /^(?:[a-z][a-z0-9+\-.]*:)?(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'"()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?(?:\?(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;
const URITEMPLATE = /^(?:(?:[^\x00-\x20"'<>%\\^`{|}]|%[0-9a-f]{2})|\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?)*\})*$/i;
const URL_ = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u{00a1}-\u{ffff}0-9]+-?)*[a-z\u{00a1}-\u{ffff}0-9]+)(?:\.(?:[a-z\u{00a1}-\u{ffff}0-9]+-?)*[a-z\u{00a1}-\u{ffff}0-9]+)*(?:\.(?:[a-z\u{00a1}-\u{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/iu;
const UUID = /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i;
const JSON_POINTER = /^(?:\/(?:[^~/]|~0|~1)*)*$/;
const JSON_POINTER_URI_FRAGMENT = /^#(?:\/(?:[a-z0-9_\-.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)*)*$/i;
const RELATIVE_JSON_POINTER = /^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/;
const FASTDATE = /^\d\d\d\d-[0-1]\d-[0-3]\d$/;
const FASTTIME = /^(?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)?$/i;
const FASTDATETIME = /^\d\d\d\d-[0-1]\d-[0-3]\d[t\s](?:[0-2]\d:[0-5]\d:[0-5]\d|23:59:60)(?:\.\d+)?(?:z|[+-]\d\d(?::?\d\d)?)$/i;
const FASTURIREFERENCE = /^(?:(?:[a-z][a-z0-9+-.]*:)?\/?\/)?(?:[^\\\s#][^\s#]*)?(?:#[^\\\s]*)?$/i;
const EMAIL = (input) => {
    if (input[0] === '"')
        return false;
    const [name, host, ...rest] = input.split('@');
    if (!name ||
        !host ||
        rest.length !== 0 ||
        name.length > 64 ||
        host.length > 253)
        return false;
    if (name[0] === '.' || name.endsWith('.') || name.includes('..'))
        return false;
    if (!/^[a-z0-9.-]+$/i.test(host) ||
        !/^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+$/i.test(name))
        return false;
    return host
        .split('.')
        .every(part => /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/i.test(part));
};
const IPV4 = /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/;
const IPV6 = /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i;
const DURATION = (input) => input.length > 1 &&
    input.length < 80 &&
    (/^P\d+([.,]\d+)?W$/.test(input) ||
        (/^P[\dYMDTHS]*(\d[.,]\d+)?[YMDHS]$/.test(input) &&
            /^P([.,\d]+Y)?([.,\d]+M)?([.,\d]+D)?(T([.,\d]+H)?([.,\d]+M)?([.,\d]+S)?)?$/.test(input)));
function bind(r) {
    return r.test.bind(r);
}
const fullFormat = {
    date,
    time: time.bind(undefined, false),
    'date-time': date_time,
    duration: DURATION,
    uri,
    'uri-reference': bind(URIREF),
    'uri-template': bind(URITEMPLATE),
    url: bind(URL_),
    email: EMAIL,
    hostname: bind(HOSTNAME),
    ipv4: bind(IPV4),
    ipv6: bind(IPV6),
    regex: regex,
    uuid: bind(UUID),
    'json-pointer': bind(JSON_POINTER),
    'json-pointer-uri-fragment': bind(JSON_POINTER_URI_FRAGMENT),
    'relative-json-pointer': bind(RELATIVE_JSON_POINTER)
};
const fastFormat = {
    ...fullFormat,
    date: bind(FASTDATE),
    time: bind(FASTTIME),
    'date-time': bind(FASTDATETIME),
    'uri-reference': bind(FASTURIREFERENCE)
};
function isLeapYear(year) {
    return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
}
function date(str) {
    const matches = str.match(DATE);
    if (!matches)
        return false;
    const year = +matches[1];
    const month = +matches[2];
    const day = +matches[3];
    return (month >= 1 &&
        month <= 12 &&
        day >= 1 &&
        day <= (month == 2 && isLeapYear(year) ? 29 : DAYS[month]));
}
function time(full, str) {
    const matches = str.match(TIME);
    if (!matches)
        return false;
    const hour = +matches[1];
    const minute = +matches[2];
    const second = +matches[3];
    const timeZone = !!matches[5];
    return (((hour <= 23 && minute <= 59 && second <= 59) ||
        (hour == 23 && minute == 59 && second == 60)) &&
        (!full || timeZone));
}
const DATE_TIME_SEPARATOR = /t|\s/i;
function date_time(str) {
    const dateTime = str.split(DATE_TIME_SEPARATOR);
    return dateTime.length == 2 && date(dateTime[0]) && time(true, dateTime[1]);
}
const NOT_URI_FRAGMENT = /\/|:/;
const URI_PATTERN = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;
function uri(str) {
    return NOT_URI_FRAGMENT.test(str) && URI_PATTERN.test(str);
}
const Z_ANCHOR = /[^\\]\\Z/;
function regex(str) {
    if (Z_ANCHOR.test(str))
        return false;
    try {
        new RegExp(str);
        return true;
    }
    catch (e) {
        return false;
    }
}

function ucs2length(s) {
    let result = 0;
    let length = s.length;
    let index = 0;
    let charCode;
    while (index < length) {
        result++;
        charCode = s.charCodeAt(index++);
        if (charCode >= 0xd800 && charCode <= 0xdbff && index < length) {
            charCode = s.charCodeAt(index);
            if ((charCode & 0xfc00) == 0xdc00) {
                index++;
            }
        }
    }
    return result;
}

function validate(instance, schema, draft = '2019-09', lookup = dereference(schema), shortCircuit = true, recursiveAnchor = null, instanceLocation = '#', schemaLocation = '#', evaluated = Object.create(null)) {
    if (schema === true) {
        return { valid: true, errors: [] };
    }
    if (schema === false) {
        return {
            valid: false,
            errors: [
                {
                    instanceLocation,
                    keyword: 'false',
                    keywordLocation: instanceLocation,
                    error: 'False boolean schema.'
                }
            ]
        };
    }
    const rawInstanceType = typeof instance;
    let instanceType;
    switch (rawInstanceType) {
        case 'boolean':
        case 'number':
        case 'string':
            instanceType = rawInstanceType;
            break;
        case 'object':
            if (instance === null) {
                instanceType = 'null';
            }
            else if (Array.isArray(instance)) {
                instanceType = 'array';
            }
            else {
                instanceType = 'object';
            }
            break;
        default:
            throw new Error(`Instances of "${rawInstanceType}" type are not supported.`);
    }
    const { $ref, $recursiveRef, $recursiveAnchor, type: $type, const: $const, enum: $enum, required: $required, not: $not, anyOf: $anyOf, allOf: $allOf, oneOf: $oneOf, if: $if, then: $then, else: $else, format: $format, properties: $properties, patternProperties: $patternProperties, additionalProperties: $additionalProperties, unevaluatedProperties: $unevaluatedProperties, minProperties: $minProperties, maxProperties: $maxProperties, propertyNames: $propertyNames, dependentRequired: $dependentRequired, dependentSchemas: $dependentSchemas, dependencies: $dependencies, prefixItems: $prefixItems, items: $items, additionalItems: $additionalItems, unevaluatedItems: $unevaluatedItems, contains: $contains, minContains: $minContains, maxContains: $maxContains, minItems: $minItems, maxItems: $maxItems, uniqueItems: $uniqueItems, minimum: $minimum, maximum: $maximum, exclusiveMinimum: $exclusiveMinimum, exclusiveMaximum: $exclusiveMaximum, multipleOf: $multipleOf, minLength: $minLength, maxLength: $maxLength, pattern: $pattern, __absolute_ref__, __absolute_recursive_ref__ } = schema;
    const errors = [];
    if ($recursiveAnchor === true && recursiveAnchor === null) {
        recursiveAnchor = schema;
    }
    if ($recursiveRef === '#') {
        const refSchema = recursiveAnchor === null
            ? lookup[__absolute_recursive_ref__]
            : recursiveAnchor;
        const keywordLocation = `${schemaLocation}/$recursiveRef`;
        const result = validate(instance, recursiveAnchor === null ? schema : recursiveAnchor, draft, lookup, shortCircuit, refSchema, instanceLocation, keywordLocation, evaluated);
        if (!result.valid) {
            errors.push({
                instanceLocation,
                keyword: '$recursiveRef',
                keywordLocation,
                error: 'A subschema had errors.'
            }, ...result.errors);
        }
    }
    if ($ref !== undefined) {
        const uri = __absolute_ref__ || $ref;
        const refSchema = lookup[uri];
        if (refSchema === undefined) {
            let message = `Unresolved $ref "${$ref}".`;
            if (__absolute_ref__ && __absolute_ref__ !== $ref) {
                message += `  Absolute URI "${__absolute_ref__}".`;
            }
            message += `\nKnown schemas:\n- ${Object.keys(lookup).join('\n- ')}`;
            throw new Error(message);
        }
        const keywordLocation = `${schemaLocation}/$ref`;
        const result = validate(instance, refSchema, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, keywordLocation, evaluated);
        if (!result.valid) {
            errors.push({
                instanceLocation,
                keyword: '$ref',
                keywordLocation,
                error: 'A subschema had errors.'
            }, ...result.errors);
        }
        if (draft === '4' || draft === '7') {
            return { valid: errors.length === 0, errors };
        }
    }
    if (Array.isArray($type)) {
        let length = $type.length;
        let valid = false;
        for (let i = 0; i < length; i++) {
            if (instanceType === $type[i] ||
                ($type[i] === 'integer' &&
                    instanceType === 'number' &&
                    instance % 1 === 0 &&
                    instance === instance)) {
                valid = true;
                break;
            }
        }
        if (!valid) {
            errors.push({
                instanceLocation,
                keyword: 'type',
                keywordLocation: `${schemaLocation}/type`,
                error: `Instance type "${instanceType}" is invalid. Expected "${$type.join('", "')}".`
            });
        }
    }
    else if ($type === 'integer') {
        if (instanceType !== 'number' || instance % 1 || instance !== instance) {
            errors.push({
                instanceLocation,
                keyword: 'type',
                keywordLocation: `${schemaLocation}/type`,
                error: `Instance type "${instanceType}" is invalid. Expected "${$type}".`
            });
        }
    }
    else if ($type !== undefined && instanceType !== $type) {
        errors.push({
            instanceLocation,
            keyword: 'type',
            keywordLocation: `${schemaLocation}/type`,
            error: `Instance type "${instanceType}" is invalid. Expected "${$type}".`
        });
    }
    if ($const !== undefined) {
        if (instanceType === 'object' || instanceType === 'array') {
            if (!deepCompareStrict(instance, $const)) {
                errors.push({
                    instanceLocation,
                    keyword: 'const',
                    keywordLocation: `${schemaLocation}/const`,
                    error: `Instance does not match ${JSON.stringify($const)}.`
                });
            }
        }
        else if (instance !== $const) {
            errors.push({
                instanceLocation,
                keyword: 'const',
                keywordLocation: `${schemaLocation}/const`,
                error: `Instance does not match ${JSON.stringify($const)}.`
            });
        }
    }
    if ($enum !== undefined) {
        if (instanceType === 'object' || instanceType === 'array') {
            if (!$enum.some(value => deepCompareStrict(instance, value))) {
                errors.push({
                    instanceLocation,
                    keyword: 'enum',
                    keywordLocation: `${schemaLocation}/enum`,
                    error: `Instance does not match any of ${JSON.stringify($enum)}.`
                });
            }
        }
        else if (!$enum.some(value => instance === value)) {
            errors.push({
                instanceLocation,
                keyword: 'enum',
                keywordLocation: `${schemaLocation}/enum`,
                error: `Instance does not match any of ${JSON.stringify($enum)}.`
            });
        }
    }
    if ($not !== undefined) {
        const keywordLocation = `${schemaLocation}/not`;
        const result = validate(instance, $not, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, keywordLocation);
        if (result.valid) {
            errors.push({
                instanceLocation,
                keyword: 'not',
                keywordLocation,
                error: 'Instance matched "not" schema.'
            });
        }
    }
    let subEvaluateds = [];
    if ($anyOf !== undefined) {
        const keywordLocation = `${schemaLocation}/anyOf`;
        const errorsLength = errors.length;
        let anyValid = false;
        for (let i = 0; i < $anyOf.length; i++) {
            const subSchema = $anyOf[i];
            const subEvaluated = Object.create(evaluated);
            const result = validate(instance, subSchema, draft, lookup, shortCircuit, $recursiveAnchor === true ? recursiveAnchor : null, instanceLocation, `${keywordLocation}/${i}`, subEvaluated);
            errors.push(...result.errors);
            anyValid = anyValid || result.valid;
            if (result.valid) {
                subEvaluateds.push(subEvaluated);
            }
        }
        if (anyValid) {
            errors.length = errorsLength;
        }
        else {
            errors.splice(errorsLength, 0, {
                instanceLocation,
                keyword: 'anyOf',
                keywordLocation,
                error: 'Instance does not match any subschemas.'
            });
        }
    }
    if ($allOf !== undefined) {
        const keywordLocation = `${schemaLocation}/allOf`;
        const errorsLength = errors.length;
        let allValid = true;
        for (let i = 0; i < $allOf.length; i++) {
            const subSchema = $allOf[i];
            const subEvaluated = Object.create(evaluated);
            const result = validate(instance, subSchema, draft, lookup, shortCircuit, $recursiveAnchor === true ? recursiveAnchor : null, instanceLocation, `${keywordLocation}/${i}`, subEvaluated);
            errors.push(...result.errors);
            allValid = allValid && result.valid;
            if (result.valid) {
                subEvaluateds.push(subEvaluated);
            }
        }
        if (allValid) {
            errors.length = errorsLength;
        }
        else {
            errors.splice(errorsLength, 0, {
                instanceLocation,
                keyword: 'allOf',
                keywordLocation,
                error: `Instance does not match every subschema.`
            });
        }
    }
    if ($oneOf !== undefined) {
        const keywordLocation = `${schemaLocation}/oneOf`;
        const errorsLength = errors.length;
        const matches = $oneOf.filter((subSchema, i) => {
            const subEvaluated = Object.create(evaluated);
            const result = validate(instance, subSchema, draft, lookup, shortCircuit, $recursiveAnchor === true ? recursiveAnchor : null, instanceLocation, `${keywordLocation}/${i}`, subEvaluated);
            errors.push(...result.errors);
            if (result.valid) {
                subEvaluateds.push(subEvaluated);
            }
            return result.valid;
        }).length;
        if (matches === 1) {
            errors.length = errorsLength;
        }
        else {
            errors.splice(errorsLength, 0, {
                instanceLocation,
                keyword: 'oneOf',
                keywordLocation,
                error: `Instance does not match exactly one subschema (${matches} matches).`
            });
        }
    }
    if (instanceType === 'object' || instanceType === 'array') {
        Object.assign(evaluated, ...subEvaluateds);
    }
    if ($if !== undefined) {
        const keywordLocation = `${schemaLocation}/if`;
        const conditionResult = validate(instance, $if, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, keywordLocation, evaluated).valid;
        if (conditionResult) {
            if ($then !== undefined) {
                const thenResult = validate(instance, $then, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, `${schemaLocation}/then`, evaluated);
                if (!thenResult.valid) {
                    errors.push({
                        instanceLocation,
                        keyword: 'if',
                        keywordLocation,
                        error: `Instance does not match "then" schema.`
                    }, ...thenResult.errors);
                }
            }
        }
        else if ($else !== undefined) {
            const elseResult = validate(instance, $else, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, `${schemaLocation}/else`, evaluated);
            if (!elseResult.valid) {
                errors.push({
                    instanceLocation,
                    keyword: 'if',
                    keywordLocation,
                    error: `Instance does not match "else" schema.`
                }, ...elseResult.errors);
            }
        }
    }
    if (instanceType === 'object') {
        if ($required !== undefined) {
            for (const key of $required) {
                if (!(key in instance)) {
                    errors.push({
                        instanceLocation,
                        keyword: 'required',
                        keywordLocation: `${schemaLocation}/required`,
                        error: `Instance does not have required property "${key}".`
                    });
                }
            }
        }
        const keys = Object.keys(instance);
        if ($minProperties !== undefined && keys.length < $minProperties) {
            errors.push({
                instanceLocation,
                keyword: 'minProperties',
                keywordLocation: `${schemaLocation}/minProperties`,
                error: `Instance does not have at least ${$minProperties} properties.`
            });
        }
        if ($maxProperties !== undefined && keys.length > $maxProperties) {
            errors.push({
                instanceLocation,
                keyword: 'maxProperties',
                keywordLocation: `${schemaLocation}/maxProperties`,
                error: `Instance does not have at least ${$maxProperties} properties.`
            });
        }
        if ($propertyNames !== undefined) {
            const keywordLocation = `${schemaLocation}/propertyNames`;
            for (const key in instance) {
                const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`;
                const result = validate(key, $propertyNames, draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, keywordLocation);
                if (!result.valid) {
                    errors.push({
                        instanceLocation,
                        keyword: 'propertyNames',
                        keywordLocation,
                        error: `Property name "${key}" does not match schema.`
                    }, ...result.errors);
                }
            }
        }
        if ($dependentRequired !== undefined) {
            const keywordLocation = `${schemaLocation}/dependantRequired`;
            for (const key in $dependentRequired) {
                if (key in instance) {
                    const required = $dependentRequired[key];
                    for (const dependantKey of required) {
                        if (!(dependantKey in instance)) {
                            errors.push({
                                instanceLocation,
                                keyword: 'dependentRequired',
                                keywordLocation,
                                error: `Instance has "${key}" but does not have "${dependantKey}".`
                            });
                        }
                    }
                }
            }
        }
        if ($dependentSchemas !== undefined) {
            for (const key in $dependentSchemas) {
                const keywordLocation = `${schemaLocation}/dependentSchemas`;
                if (key in instance) {
                    const result = validate(instance, $dependentSchemas[key], draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, `${keywordLocation}/${encodePointer(key)}`, evaluated);
                    if (!result.valid) {
                        errors.push({
                            instanceLocation,
                            keyword: 'dependentSchemas',
                            keywordLocation,
                            error: `Instance has "${key}" but does not match dependant schema.`
                        }, ...result.errors);
                    }
                }
            }
        }
        if ($dependencies !== undefined) {
            const keywordLocation = `${schemaLocation}/dependencies`;
            for (const key in $dependencies) {
                if (key in instance) {
                    const propsOrSchema = $dependencies[key];
                    if (Array.isArray(propsOrSchema)) {
                        for (const dependantKey of propsOrSchema) {
                            if (!(dependantKey in instance)) {
                                errors.push({
                                    instanceLocation,
                                    keyword: 'dependencies',
                                    keywordLocation,
                                    error: `Instance has "${key}" but does not have "${dependantKey}".`
                                });
                            }
                        }
                    }
                    else {
                        const result = validate(instance, propsOrSchema, draft, lookup, shortCircuit, recursiveAnchor, instanceLocation, `${keywordLocation}/${encodePointer(key)}`);
                        if (!result.valid) {
                            errors.push({
                                instanceLocation,
                                keyword: 'dependencies',
                                keywordLocation,
                                error: `Instance has "${key}" but does not match dependant schema.`
                            }, ...result.errors);
                        }
                    }
                }
            }
        }
        const thisEvaluated = Object.create(null);
        let stop = false;
        if ($properties !== undefined) {
            const keywordLocation = `${schemaLocation}/properties`;
            for (const key in $properties) {
                if (!(key in instance)) {
                    continue;
                }
                const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`;
                const result = validate(instance[key], $properties[key], draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, `${keywordLocation}/${encodePointer(key)}`);
                if (result.valid) {
                    evaluated[key] = thisEvaluated[key] = true;
                }
                else {
                    stop = shortCircuit;
                    errors.push({
                        instanceLocation,
                        keyword: 'properties',
                        keywordLocation,
                        error: `Property "${key}" does not match schema.`
                    }, ...result.errors);
                    if (stop)
                        break;
                }
            }
        }
        if (!stop && $patternProperties !== undefined) {
            const keywordLocation = `${schemaLocation}/patternProperties`;
            for (const pattern in $patternProperties) {
                const regex = new RegExp(pattern);
                const subSchema = $patternProperties[pattern];
                for (const key in instance) {
                    if (!regex.test(key)) {
                        continue;
                    }
                    const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`;
                    const result = validate(instance[key], subSchema, draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, `${keywordLocation}/${encodePointer(pattern)}`);
                    if (result.valid) {
                        evaluated[key] = thisEvaluated[key] = true;
                    }
                    else {
                        stop = shortCircuit;
                        errors.push({
                            instanceLocation,
                            keyword: 'patternProperties',
                            keywordLocation,
                            error: `Property "${key}" matches pattern "${pattern}" but does not match associated schema.`
                        }, ...result.errors);
                    }
                }
            }
        }
        if (!stop && $additionalProperties !== undefined) {
            const keywordLocation = `${schemaLocation}/additionalProperties`;
            for (const key in instance) {
                if (thisEvaluated[key]) {
                    continue;
                }
                const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`;
                const result = validate(instance[key], $additionalProperties, draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, keywordLocation);
                if (result.valid) {
                    evaluated[key] = true;
                }
                else {
                    stop = shortCircuit;
                    errors.push({
                        instanceLocation,
                        keyword: 'additionalProperties',
                        keywordLocation,
                        error: `Property "${key}" does not match additional properties schema.`
                    }, ...result.errors);
                }
            }
        }
        else if (!stop && $unevaluatedProperties !== undefined) {
            const keywordLocation = `${schemaLocation}/unevaluatedProperties`;
            for (const key in instance) {
                if (!evaluated[key]) {
                    const subInstancePointer = `${instanceLocation}/${encodePointer(key)}`;
                    const result = validate(instance[key], $unevaluatedProperties, draft, lookup, shortCircuit, recursiveAnchor, subInstancePointer, keywordLocation);
                    if (result.valid) {
                        evaluated[key] = true;
                    }
                    else {
                        errors.push({
                            instanceLocation,
                            keyword: 'unevaluatedProperties',
                            keywordLocation,
                            error: `Property "${key}" does not match unevaluated properties schema.`
                        }, ...result.errors);
                    }
                }
            }
        }
    }
    else if (instanceType === 'array') {
        if ($maxItems !== undefined && instance.length > $maxItems) {
            errors.push({
                instanceLocation,
                keyword: 'maxItems',
                keywordLocation: `${schemaLocation}/maxItems`,
                error: `Array has too many items (${instance.length} > ${$maxItems}).`
            });
        }
        if ($minItems !== undefined && instance.length < $minItems) {
            errors.push({
                instanceLocation,
                keyword: 'minItems',
                keywordLocation: `${schemaLocation}/minItems`,
                error: `Array has too few items (${instance.length} < ${$minItems}).`
            });
        }
        const length = instance.length;
        let i = 0;
        let stop = false;
        if ($prefixItems !== undefined) {
            const keywordLocation = `${schemaLocation}/prefixItems`;
            const length2 = Math.min($prefixItems.length, length);
            for (; i < length2; i++) {
                const result = validate(instance[i], $prefixItems[i], draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, `${keywordLocation}/${i}`);
                evaluated[i] = true;
                if (!result.valid) {
                    stop = shortCircuit;
                    errors.push({
                        instanceLocation,
                        keyword: 'prefixItems',
                        keywordLocation,
                        error: `Items did not match schema.`
                    }, ...result.errors);
                    if (stop)
                        break;
                }
            }
        }
        if ($items !== undefined) {
            const keywordLocation = `${schemaLocation}/items`;
            if (Array.isArray($items)) {
                const length2 = Math.min($items.length, length);
                for (; i < length2; i++) {
                    const result = validate(instance[i], $items[i], draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, `${keywordLocation}/${i}`);
                    evaluated[i] = true;
                    if (!result.valid) {
                        stop = shortCircuit;
                        errors.push({
                            instanceLocation,
                            keyword: 'items',
                            keywordLocation,
                            error: `Items did not match schema.`
                        }, ...result.errors);
                        if (stop)
                            break;
                    }
                }
            }
            else {
                for (; i < length; i++) {
                    const result = validate(instance[i], $items, draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, keywordLocation);
                    evaluated[i] = true;
                    if (!result.valid) {
                        stop = shortCircuit;
                        errors.push({
                            instanceLocation,
                            keyword: 'items',
                            keywordLocation,
                            error: `Items did not match schema.`
                        }, ...result.errors);
                        if (stop)
                            break;
                    }
                }
            }
            if (!stop && $additionalItems !== undefined) {
                const keywordLocation = `${schemaLocation}/additionalItems`;
                for (; i < length; i++) {
                    const result = validate(instance[i], $additionalItems, draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, keywordLocation);
                    evaluated[i] = true;
                    if (!result.valid) {
                        stop = shortCircuit;
                        errors.push({
                            instanceLocation,
                            keyword: 'additionalItems',
                            keywordLocation,
                            error: `Items did not match additional items schema.`
                        }, ...result.errors);
                    }
                }
            }
        }
        if ($contains !== undefined) {
            if (length === 0 && $minContains === undefined) {
                errors.push({
                    instanceLocation,
                    keyword: 'contains',
                    keywordLocation: `${schemaLocation}/contains`,
                    error: `Array is empty. It must contain at least one item matching the schema.`
                });
            }
            else if ($minContains !== undefined && length < $minContains) {
                errors.push({
                    instanceLocation,
                    keyword: 'minContains',
                    keywordLocation: `${schemaLocation}/minContains`,
                    error: `Array has less items (${length}) than minContains (${$minContains}).`
                });
            }
            else {
                const keywordLocation = `${schemaLocation}/contains`;
                const errorsLength = errors.length;
                let contained = 0;
                for (let j = 0; j < length; j++) {
                    const result = validate(instance[j], $contains, draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${j}`, keywordLocation);
                    if (result.valid) {
                        evaluated[j] = true;
                        contained++;
                    }
                    else {
                        errors.push(...result.errors);
                    }
                }
                if (contained >= ($minContains || 0)) {
                    errors.length = errorsLength;
                }
                if ($minContains === undefined &&
                    $maxContains === undefined &&
                    contained === 0) {
                    errors.splice(errorsLength, 0, {
                        instanceLocation,
                        keyword: 'contains',
                        keywordLocation,
                        error: `Array does not contain item matching schema.`
                    });
                }
                else if ($minContains !== undefined && contained < $minContains) {
                    errors.push({
                        instanceLocation,
                        keyword: 'minContains',
                        keywordLocation: `${schemaLocation}/minContains`,
                        error: `Array must contain at least ${$minContains} items matching schema. Only ${contained} items were found.`
                    });
                }
                else if ($maxContains !== undefined && contained > $maxContains) {
                    errors.push({
                        instanceLocation,
                        keyword: 'maxContains',
                        keywordLocation: `${schemaLocation}/maxContains`,
                        error: `Array may contain at most ${$maxContains} items matching schema. ${contained} items were found.`
                    });
                }
            }
        }
        if (!stop && $unevaluatedItems !== undefined) {
            const keywordLocation = `${schemaLocation}/unevaluatedItems`;
            for (i; i < length; i++) {
                if (evaluated[i]) {
                    continue;
                }
                const result = validate(instance[i], $unevaluatedItems, draft, lookup, shortCircuit, recursiveAnchor, `${instanceLocation}/${i}`, keywordLocation);
                evaluated[i] = true;
                if (!result.valid) {
                    errors.push({
                        instanceLocation,
                        keyword: 'unevaluatedItems',
                        keywordLocation,
                        error: `Items did not match unevaluated items schema.`
                    }, ...result.errors);
                }
            }
        }
        if ($uniqueItems) {
            for (let j = 0; j < length; j++) {
                const a = instance[j];
                const ao = typeof a === 'object' && a !== null;
                for (let k = 0; k < length; k++) {
                    if (j === k) {
                        continue;
                    }
                    const b = instance[k];
                    const bo = typeof b === 'object' && b !== null;
                    if (a === b || (ao && bo && deepCompareStrict(a, b))) {
                        errors.push({
                            instanceLocation,
                            keyword: 'uniqueItems',
                            keywordLocation: `${schemaLocation}/uniqueItems`,
                            error: `Duplicate items at indexes ${j} and ${k}.`
                        });
                        j = Number.MAX_SAFE_INTEGER;
                        k = Number.MAX_SAFE_INTEGER;
                    }
                }
            }
        }
    }
    else if (instanceType === 'number') {
        if (draft === '4') {
            if ($minimum !== undefined &&
                (($exclusiveMinimum === true && instance <= $minimum) ||
                    instance < $minimum)) {
                errors.push({
                    instanceLocation,
                    keyword: 'minimum',
                    keywordLocation: `${schemaLocation}/minimum`,
                    error: `${instance} is less than ${$exclusiveMinimum ? 'or equal to ' : ''} ${$minimum}.`
                });
            }
            if ($maximum !== undefined &&
                (($exclusiveMaximum === true && instance >= $maximum) ||
                    instance > $maximum)) {
                errors.push({
                    instanceLocation,
                    keyword: 'maximum',
                    keywordLocation: `${schemaLocation}/maximum`,
                    error: `${instance} is greater than ${$exclusiveMaximum ? 'or equal to ' : ''} ${$maximum}.`
                });
            }
        }
        else {
            if ($minimum !== undefined && instance < $minimum) {
                errors.push({
                    instanceLocation,
                    keyword: 'minimum',
                    keywordLocation: `${schemaLocation}/minimum`,
                    error: `${instance} is less than ${$minimum}.`
                });
            }
            if ($maximum !== undefined && instance > $maximum) {
                errors.push({
                    instanceLocation,
                    keyword: 'maximum',
                    keywordLocation: `${schemaLocation}/maximum`,
                    error: `${instance} is greater than ${$maximum}.`
                });
            }
            if ($exclusiveMinimum !== undefined && instance <= $exclusiveMinimum) {
                errors.push({
                    instanceLocation,
                    keyword: 'exclusiveMinimum',
                    keywordLocation: `${schemaLocation}/exclusiveMinimum`,
                    error: `${instance} is less than ${$exclusiveMinimum}.`
                });
            }
            if ($exclusiveMaximum !== undefined && instance >= $exclusiveMaximum) {
                errors.push({
                    instanceLocation,
                    keyword: 'exclusiveMaximum',
                    keywordLocation: `${schemaLocation}/exclusiveMaximum`,
                    error: `${instance} is greater than or equal to ${$exclusiveMaximum}.`
                });
            }
        }
        if ($multipleOf !== undefined) {
            const remainder = instance % $multipleOf;
            if (Math.abs(0 - remainder) >= 1.1920929e-7 &&
                Math.abs($multipleOf - remainder) >= 1.1920929e-7) {
                errors.push({
                    instanceLocation,
                    keyword: 'multipleOf',
                    keywordLocation: `${schemaLocation}/multipleOf`,
                    error: `${instance} is not a multiple of ${$multipleOf}.`
                });
            }
        }
    }
    else if (instanceType === 'string') {
        const length = $minLength === undefined && $maxLength === undefined
            ? 0
            : ucs2length(instance);
        if ($minLength !== undefined && length < $minLength) {
            errors.push({
                instanceLocation,
                keyword: 'minLength',
                keywordLocation: `${schemaLocation}/minLength`,
                error: `String is too short (${length} < ${$minLength}).`
            });
        }
        if ($maxLength !== undefined && length > $maxLength) {
            errors.push({
                instanceLocation,
                keyword: 'maxLength',
                keywordLocation: `${schemaLocation}/maxLength`,
                error: `String is too long (${length} > ${$maxLength}).`
            });
        }
        if ($pattern !== undefined && !new RegExp($pattern).test(instance)) {
            errors.push({
                instanceLocation,
                keyword: 'pattern',
                keywordLocation: `${schemaLocation}/pattern`,
                error: `String does not match pattern.`
            });
        }
        if ($format !== undefined &&
            fastFormat[$format] &&
            !fastFormat[$format](instance)) {
            errors.push({
                instanceLocation,
                keyword: 'format',
                keywordLocation: `${schemaLocation}/format`,
                error: `String does not match format "${$format}".`
            });
        }
    }
    return { valid: errors.length === 0, errors };
}

class Validator {
    constructor(schema, draft = '2019-09', shortCircuit = true) {
        this.schema = schema;
        this.draft = draft;
        this.shortCircuit = shortCircuit;
        this.lookup = dereference(schema);
    }
    validate(instance) {
        return validate(instance, this.schema, this.draft, this.lookup, this.shortCircuit);
    }
    addSchema(schema, id) {
        if (id) {
            schema = { ...schema, $id: id };
        }
        dereference(schema, this.lookup);
    }
}

this.Validator = Validator;
this.deepCompareStrict = deepCompareStrict;
this.dereference = dereference;
this.encodePointer = encodePointer;
this.escapePointer = escapePointer;
this.fastFormat = fastFormat;
this.fullFormat = fullFormat;
this.ignoredKeyword = ignoredKeyword;
this.initialBaseURI = initialBaseURI;
this.schemaArrayKeyword = schemaArrayKeyword;
this.schemaKeyword = schemaKeyword;
this.schemaMapKeyword = schemaMapKeyword;
this.ucs2length = ucs2length;
this.validate = validate;
PK
!<a�@@Ichrome/en-US/locale/en-US/mozapps/downloads/unknownContentType.properties
title=Opening %S
saveDialogTitle=Enter name of file to save to…
defaultApp=%S (default)
chooseAppFilePickerTitle=Choose Helper Application
badApp=The application you chose (“%S”) could not be found.  Check the file name or choose another application.
badApp.title=Application not found
badPermissions=The file could not be saved because you do not have the proper permissions.  Choose another save directory.
badPermissions.title=Invalid Save Permissions
unknownAccept.label=Save File
unknownCancel.label=Cancel
fileType=%S file
orderedFileSizeWithType=%1$S (%2$S %3$S)
avifExtHandlerDescription=AV1 Image File (AVIF)
pdfExtHandlerDescription=Portable Document Format (PDF)
svgExtHandlerDescription=Scalable Vector Graphics (SVG)
webpExtHandlerDescription=WebP Image
xmlExtHandlerDescription=Extensible Markup Language (XML)
PK
!<�#Ŕe8e8%chrome/toolkit/content/global/xul.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Rules for everything related to XUL except scrollbars can be found in this
 * file.
 *
 * This file should also not contain any app specific styling.  Defaults for
 * widgets of a particular application should be in that application's style
 * sheet.  For example, style definitions for browser can be found in
 * browser.css.
 */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */
@namespace html url("http://www.w3.org/1999/xhtml"); /* namespace for HTML elements */

* {
  -moz-user-focus: ignore;
  display: flex;
  box-sizing: border-box;
}

/* hide the content and destroy the frame */
[hidden="true"] {
  display: none;
}

/* hide the content, but don't destroy the frames */
[collapsed="true"] {
  visibility: collapse;
}

/* TODO: investigate unifying these two root selectors
 * https://bugzilla.mozilla.org/show_bug.cgi?id=1592344
 */
*|*:root {
  --animation-easing-function: cubic-bezier(.07, .95, 0, 1);
  flex: 1;
  -moz-box-collapse: legacy;
}

:root {
  text-rendering: optimizeLegibility;
  -moz-control-character-visibility: visible;
  width: 100%;
  height: 100%;
  user-select: none;
}

:root:-moz-locale-dir(rtl) {
  direction: rtl;
}

/* XUL doesn't show outlines by default */
:focus-visible {
  outline: initial;
}

/*
 * Native anonymous popups and tooltips in html are document-level, which means
 * that they don't inherit from the root, so this is needed.
 */
popupgroup:-moz-native-anonymous:-moz-locale-dir(rtl),
tooltip:-moz-native-anonymous:-moz-locale-dir(rtl) {
  direction: rtl;
}

/* ::::::::::
   :: Rules for 'hiding' portions of the chrome for special
   :: kinds of windows (not JUST browser windows) with toolbars
   ::::: */

*|*:root[chromehidden~="menubar"] .chromeclass-menubar,
*|*:root[chromehidden~="directories"] .chromeclass-directories,
*|*:root[chromehidden~="status"] .chromeclass-status,
*|*:root[chromehidden~="extrachrome"] .chromeclass-extrachrome,
*|*:root[chromehidden~="location"] .chromeclass-location,
*|*:root[chromehidden~="location"][chromehidden~="toolbar"] .chromeclass-toolbar,
*|*:root[chromehidden~="toolbar"] .chromeclass-toolbar-additional {
  display: none;
}

/* ::::::::::
   :: Rules for forcing direction for entry and display of URIs
   :: or URI elements
   ::::: */

.uri-element {
  direction: ltr !important;
}

/****** elements that have no visual representation ******/

script, data, commandset, command,
broadcasterset, broadcaster, observes,
keyset, key, toolbarpalette, template,
treeitem, treeseparator, treerow, treecell {
  display: none;
}

/********** focus rules **********/

button,
checkbox,
menulist,
radiogroup,
richlistbox,
tree,
browser,
editor,
iframe,
label:is(.text-link, [onclick]),
tab[selected="true"]:not([ignorefocus="true"]) {
  -moz-user-focus: normal;
}

/* Avoid losing focus on tabs by keeping them focusable, since some browser
 * tests rely on this.
 *
 * TODO(emilio): Remove this and fix the tests / front-end code:
 *  * browser/base/content/test/general/browser_tabfocus.js
 */
tab:focus {
  -moz-user-focus: normal;
}

/******** window & page ******/

window {
  overflow: clip;
  flex-direction: column;
}

/******** box *******/

vbox {
  flex-direction: column;
}

/********** label **********/

label {
  display: inline-block;
}

description {
  display: flow-root;
}

label html|span.accesskey {
  text-decoration: underline;
  text-decoration-skip-ink: none;
}

description:where([value]) {
  /* Preserves legacy behavior, maybe could be removed */
  display: flex;
}

label:where([value]) {
  /* Preserves legacy behavior, maybe could be removed */
  display: inline-flex;
}

:is(label, description)[value] {
  white-space: nowrap;
}

:is(label, description)[value]::before {
  /* This displays the value attribute, along with the underlined
   * accesskey if needed */
  content: -moz-label-content;
  display: block;
}

label[value=""]::before {
  content: "\200b" !important; /* zwsp */
}

:is(label, description)[value][crop] {
  min-width: 0;
}

:is(label, description)[value][crop]::before {
  min-width: 0;
  max-width: 100%;
  /* This implements crop=end */
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Invert the direction to clip at the start rather than end */
:is(label, description)[value][crop="start"]::before {
  direction: rtl;
  /* Mark text as ltr to preserve punctuation/numeric order */
  content: "\200e" -moz-label-content;
}

:is(label, description)[value][crop="start"]:-moz-locale-dir(rtl)::before {
  direction: ltr;
  /* Mark text as rtl to preserve punctuation/numeric order */
  content: "\200f" -moz-label-content;
}

.checkbox-label-box,
.radio-label-box {
  min-width: 0;
}

/********** toolbarbutton **********/

toolbarbutton {
  align-items: center;
  justify-content: center;
}

toolbarbutton.tabbable {
  -moz-user-focus: normal !important;
}

toolbarbutton[crop] {
  min-width: 0;
}

.toolbarbutton-text {
  display: block;
  text-align: center;
}

toolbar[mode="icons"] .toolbarbutton-text,
toolbar[mode="text"] .toolbarbutton-icon,
html|label.toolbarbutton-badge:empty {
  display: none;
}

.toolbarbutton-icon,
.toolbarbutton-text,
.toolbarbutton-badge-stack,
.toolbarbutton-menu-dropmarker,
.treecol-text,
.treecol-sortdirection,
.menubar-left,
.menubar-text,
.menu-text,
.menu-iconic-text,
.menu-iconic-highlightable-text,
.menu-iconic-left,
.menu-right,
.menu-accel-container,
.button-box {
  /* Preserves legacy behavior */
  pointer-events: none;
}

/********** button **********/

button {
  -moz-default-appearance: button;
  appearance: auto;
}

dropmarker {
  -moz-default-appearance: -moz-menulist-arrow-button;
  appearance: auto;
}

/******** browser, editor, iframe ********/

browser,
editor,
iframe {
  display: inline;
}

/* Allow the browser to shrink below its intrinsic size, to match legacy
 * behavior */
browser {
  align-self: stretch;
  justify-self: stretch;
  min-height: 0;
  min-width: 0;
  contain: size;
}

/*********** popup notification ************/
popupnotification {
  flex-direction: column;
}

.popup-notification-menubutton:not([label]) {
  display: none;
}

/********** radio **********/

radiogroup {
  flex-direction: column;
}

/******** groupbox *********/

groupbox {
  flex-direction: column;
}

/******** draggable elements *********/

toolbar:not([nowindowdrag], [customizing]) {
  -moz-window-dragging: drag;
}

/* The list below is non-comprehensive and will probably need some tweaking. */
toolbaritem,
toolbarbutton,
toolbarseparator,
button,
search-textbox,
html|input,
tab,
radio,
splitter,
menu,
menulist {
  -moz-window-dragging: no-drag;
}

titlebar {
  pointer-events: auto !important;
}

/******* toolbar *******/

toolbox {
  flex-direction: column;
}

@media (-moz-platform: macos) {
  toolbar[type="menubar"] {
    min-height: 0 !important;
    border: 0 !important;
  }
}

toolbarspring {
  flex: 1000 1000;
}

/********* menu ***********/

menubar > menu:empty {
  visibility: collapse;
}

.menu-text {
  flex: 1;
}

/********* menupopup, panel, & tooltip ***********/

menupopup,
panel,
tooltip {
  position: fixed;
  -moz-top-layer: top;
  width: fit-content;
  height: fit-content;
  /* Make sure that popups are interactable when shown, since they escape the
   * usual layering rules */
  -moz-inert: none;
  /* Popups can't have overflow */
  contain: paint;
  z-index: 2147483647;
  text-shadow: none;
}

tooltip {
  appearance: auto;
  -moz-default-appearance: tooltip;

  white-space: pre-wrap;

  background-color: InfoBackground;
  color: InfoText;
  font: message-box;
  padding: 2px 3px;
  max-width: 40em;
  overflow: clip;
  pointer-events: none;
}

/**
 * It's important that these styles are in a UA sheet, because the default
 * tooltip is native anonymous content
 */
@media (-moz-platform: linux) {
  tooltip {
    padding: 6px 10px; /* Matches Adwaita. */
    line-height: 1.4;  /* For Noto Sans; note that 1.2 may clip descenders. */
  }
}

@media (-moz-platform: macos) {
  tooltip {
    padding: 2px 6px; /* Matches native metrics. */
  }
}

@media (-moz-platform: windows) {
  tooltip {
    appearance: none;
    border: 1px solid;
  }

  /* TODO(emilio): Probably make InfoText/InfoBackground do the right thing and
   * remove this? */
  @media not (prefers-contrast) {
    tooltip {
      background-color: #f9f9fb;
      color: black;
      border-color: #67676c;
      border-radius: 4px;
    }

    @media (prefers-color-scheme: dark) {
      tooltip {
        background-color: #2b2a33;
        color: white;
        border-color: #f9f9fb;
      }
    }
  }
}

/******** tree ******/

treecolpicker {
  order: 2147483646;
}

treechildren {
  display: flex;
  flex: 1;
}

tree {
  flex-direction: column;
}

tree[hidecolumnpicker="true"] treecolpicker {
  display: none;
}

treecol {
  min-width: 16px;
  /* This preserves the behavior of -moz-box-ordinal-group. To change this we'd
   * need to migrate the persisted ordinal values etc. */
  order: 1;
}

treecol[hidden="true"] {
  visibility: collapse;
  display: flex;
}

/* ::::: lines connecting cells ::::: */
tree:not([treelines="true"]) treechildren::-moz-tree-line {
  visibility: hidden;
}

treechildren::-moz-tree-cell(ltr) {
  direction: ltr !important;
}

/********** deck, tabpanels & stack *********/

tabpanels > *|*:not(:-moz-native-anonymous) {
  /* tabpanels is special: we want to avoid displaying them, but we still want
   * the hidden children to be accessible */
  -moz-subtree-hidden-only-visually: 1;
}

deck > *|*:not(:-moz-native-anonymous) {
  visibility: hidden;
}

tabpanels > .deck-selected,
deck > .deck-selected {
  -moz-subtree-hidden-only-visually: 0;
  visibility: inherit;
}

tabpanels,
deck,
stack {
  display: grid;
  position: relative;
}

/* We shouldn't style native anonymous children like scrollbars or what not. */
tabpanels > *|*:not(:-moz-native-anonymous),
deck > *|*:not(:-moz-native-anonymous),
stack > *|*:not(:-moz-native-anonymous) {
  grid-area: 1 / 1;
  z-index: 0;

  /*
    The default `min-height: auto` value makes grid items refuse to be smaller
    than their content. This doesn't match the traditional behavior of XUL stack,
    which often shoehorns tall content into a smaller stack and allows the content
    to decide how to handle overflow (e.g. by scaling down if it's an image, or
    by adding scrollbars if it's scrollable).
  */
  min-height: 0;
}

/********** tabbox *********/

tabbox {
  flex-direction: column;
  min-height: 0;
}

tabpanels {
  min-height: 0;
}

tabs {
  flex-direction: row;
}

tab {
  align-items: center;
  justify-content: center;
}

/********** tooltip *********/

tooltip[titletip="true"] {
  /* The width of the tooltip isn't limited on cropped <tree> cells. */
  max-width: none;
}

/********** basic rule for anonymous content that needs to pass box properties through
 ********** to an insertion point parent that holds the real kids **************/

.box-inherit {
  align-items: inherit;
  justify-content: inherit;
  flex-grow: inherit;
  flex-shrink: inherit;
  flex-direction: inherit;
}

/********** textbox **********/

search-textbox {
  text-shadow: none;
}

/* Prefix with (xul|*):root to workaround HTML tests loading xul.css */
:root html|textarea:not([resizable="true"]) {
  resize: none;
}

/********** autocomplete textbox **********/

.autocomplete-richlistbox {
  -moz-user-focus: ignore;
  overflow-x: hidden !important;
  flex: 1;
}

.autocomplete-richlistitem {
  flex-direction: column;
  align-items: center;
  overflow: clip;
}

/* The following rule is here to fix bug 96899 (and now 117952).
   Somehow trees create a situation
   in which a popupset flows itself as if its popup child is directly within it
   instead of the placeholder child that should actually be inside the popupset.
   This is a stopgap measure, and it does not address the real bug.  */
.autocomplete-result-popupset {
  max-width: 0px;
  width: 0 !important;
  min-width: 0%;
  min-height: 0%;
}

/********** menulist **********/

menulist[popuponly] {
  appearance: none !important;
  margin: 0 !important;
  height: 0 !important;
  min-height: 0 !important;
  border: 0 !important;
  padding: 0 !important;
}

/******** scrollbar ********/

slider {
  /* This is a hint to layerization that the scrollbar thumb can never leave
     the scrollbar track. */
  overflow: hidden;
}

/******** scrollbox ********/

scrollbox {
  /* This makes it scrollable! */
  overflow: hidden;
}

@media (prefers-reduced-motion: no-preference) {
  scrollbox[smoothscroll=true] {
    scroll-behavior: smooth;
  }
}

/********** stringbundle **********/

stringbundle,
stringbundleset {
  display: none;
}

/********** dialog **********/

dialog {
  flex: 1;
  flex-direction: column;
}

/********** wizard **********/

wizard {
  flex: 1;
  flex-direction: column;
  contain: inline-size;
  min-width: 40em;
  min-height: 30em;
}

wizard > wizardpage {
  grid-area: 1 / 1;
  min-height: 0;
}

wizard > wizardpage:not(.selected) {
  visibility: hidden;
}

wizardpage {
  flex-direction: column;
  overflow: auto;
}

/********** Rich Listbox ********/

richlistbox {
  flex-direction: column;
  overflow: auto;
  min-width: 0;
  min-height: 0;
}

richlistitem {
  flex-shrink: 0;
}

/*********** findbar ************/
findbar {
  overflow-x: hidden;
  contain: inline-size;
}

/* Some elements that in HTML blocks should be inline-level by default */
button, image {
  display: inline-flex;
}

.menu-iconic-highlightable-text:not([highlightable="true"]),
.menu-iconic-text[highlightable="true"] {
  display: none;
}

[orient="vertical"] { flex-direction: column !important; }
[orient="horizontal"] { flex-direction: row !important; }

[align="start"] { align-items: flex-start !important; }
[align="center"] { align-items: center !important; }
[align="end"] { align-items: flex-end !important; }
[align="baseline"] { align-items: baseline !important; }
[align="stretch"] { align-items: stretch !important; }

[pack="start"] { justify-content: start !important; }
[pack="center"] { justify-content: center !important; }
[pack="end"] { justify-content: flex-end !important; }

[flex="0"] { flex: none !important; }
[flex="1"] { flex: 1 !important; }
PK
!<C�Cw"w"res/contenteditable.css/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url(http://www.w3.org/1999/xhtml); /* set default namespace to HTML */

/* Scroll-anchoring shouldn't work in any editable and scrollable elements when
   user inserts something.
*/
*|*:read-write:focus,
*|*:root:read-write {
  overflow-anchor: none;
}

*|*::-moz-canvas {
  cursor: text;
}

/* Use default arrow over objects with size that
   are selected when clicked on.
   Override the browser's pointer cursor over links
*/

img:read-write, img:read-write[usemap], area:read-write,
object:read-write, object:read-write[usemap],
applet:read-write, hr:read-write, button:read-write,
select:read-write,
a:read-write:link img, a:read-write:visited img,
a:read-write:active img, a:read-write:-moz-only-whitespace[name] {
  cursor: default;
}

*|*:any-link:read-write {
  cursor: text;
}

/* Prevent clicking on links from going to link */
a:link:read-write img, a:visited:read-write img,
a:active:read-write img {
  -moz-user-input: none;
}

/* We suppress user/author's prefs for link underline,
   so we must set explicitly. This isn't good!
*/
a:link:read-write {
  color: -moz-hyperlinktext;
}

/* Allow double-clicks on these widgets to open properties dialogs
   XXX except when the widget has disabled attribute */
*|*:read-write > input:read-only,
*|*:read-write > button:read-only,
*|*:read-write > textarea:read-only {
  user-select: all;
  -moz-user-input: auto !important;
}

/* XXX Still need a better way of blocking other events to these widgets */
select:read-write,
*|*:read-write > input:disabled,
*|*:read-write > input[type="checkbox"],
*|*:read-write > input[type="radio"],
*|*:read-write > input[type="file"],
input[contenteditable="true"]:disabled,
input[contenteditable="true"][type="checkbox"],
input[contenteditable="true"][type="radio"],
input[contenteditable="true"][type="file"] {
  user-select: all;
  -moz-user-input: none !important;
}

*|*:read-write > input[type="hidden"],
input[contenteditable="true"][type="hidden"] {
  border: 1px solid black !important;
  visibility: visible !important;
}

option:read-write {
  user-select: text;
}

/* the following rules are for Image Resizing */

span[\_moz_anonclass="mozResizer"] {
  width: 5px;
  height: 5px;
  position: absolute;
  border: 1px black solid;
  background-color: white;
  user-select: none;
  z-index: 2147483646; /* max value -1 for this property */
}

/* we can't use :active below */
span[\_moz_anonclass="mozResizer"][\_moz_activated],
span[\_moz_anonclass="mozResizer"]:hover {
  background-color: black;
}

span[\_moz_anonclass="mozResizer"].hidden,
span[\_moz_anonclass="mozResizingShadow"].hidden,
img[\_moz_anonclass="mozResizingShadow"].hidden,
span[\_moz_anonclass="mozGrabber"].hidden,
span[\_moz_anonclass="mozResizingInfo"].hidden,
a[\_moz_anonclass="mozTableRemoveRow"].hidden,
a[\_moz_anonclass="mozTableRemoveColumn"].hidden {
  display: none !important;
}

span[\_moz_anonclass="mozResizer"][anonlocation="nw"] {
  cursor: nw-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="n"] {
  cursor: n-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="ne"] {
  cursor: ne-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="w"] {
  cursor: w-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="e"] {
  cursor: e-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="sw"] {
  cursor: sw-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="s"] {
  cursor: s-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="se"] {
  cursor: se-resize;
}

span[\_moz_anonclass="mozResizingShadow"],
img[\_moz_anonclass="mozResizingShadow"] {
  outline: thin dashed black;
  user-select: none;
  opacity: 0.5;
  position: absolute;
  z-index: 2147483647; /* max value for this property */
}

span[\_moz_anonclass="mozResizingInfo"] {
  font-family: sans-serif;
  font-size: x-small;
  color: black;
  background-color: #d0d0d0;
  border: ridge 2px #d0d0d0;
  padding: 2px;
  position: absolute;
  z-index: 2147483647; /* max value for this property */
}

img[\_moz_resizing] {
  outline: thin solid black;
}

*[\_moz_abspos] {
  outline: silver ridge 2px;
  z-index: 2147483645 !important; /* max value -2 for this property */
}
*[\_moz_abspos="white"] {
  background-color: white !important;
}
*[\_moz_abspos="black"] {
  background-color: black !important;
}

span[\_moz_anonclass="mozGrabber"] {
  outline: ridge 2px silver;
  padding: 2px;
  position: absolute;
  width: 12px;
  height: 12px;
  background-image: url("resource://gre/res/grabber.gif");
  background-repeat: no-repeat;
  background-position: center center;
  user-select: none;
  cursor: move;
}

/* INLINE TABLE EDITING */

a[\_moz_anonclass="mozTableAddColumnBefore"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 4px;
  height: 8px;
  background-image: url("resource://gre/res/table-add-column-before.gif");
  background-repeat: no-repeat;
  background-position: center center;
  user-select: none;
}

a[\_moz_anonclass="mozTableAddColumnBefore"]:hover {
  background-image: url("resource://gre/res/table-add-column-before-hover.gif");
}

a[\_moz_anonclass="mozTableAddColumnBefore"]:active {
  background-image: url("resource://gre/res/table-add-column-before-active.gif");
}

a[\_moz_anonclass="mozTableAddColumnAfter"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 4px;
  height: 8px;
  background-image: url("resource://gre/res/table-add-column-after.gif");
  background-repeat: no-repeat;
  background-position: center center;
  user-select: none;
}

a[\_moz_anonclass="mozTableAddColumnAfter"]:hover {
  background-image: url("resource://gre/res/table-add-column-after-hover.gif");
}

a[\_moz_anonclass="mozTableAddColumnAfter"]:active {
  background-image: url("resource://gre/res/table-add-column-after-active.gif");
}

a[\_moz_anonclass="mozTableRemoveColumn"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 8px;
  height: 8px;
  background-image: url("resource://gre/res/table-remove-column.gif");
  background-repeat: no-repeat;
  background-position: center center;
  user-select: none;
}

a[\_moz_anonclass="mozTableRemoveColumn"]:hover {
  background-image: url("resource://gre/res/table-remove-column-hover.gif");
}

a[\_moz_anonclass="mozTableRemoveColumn"]:active {
  background-image: url("resource://gre/res/table-remove-column-active.gif");
}

a[\_moz_anonclass="mozTableAddRowBefore"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 8px;
  height: 4px;
  background-image: url("resource://gre/res/table-add-row-before.gif");
  background-repeat: no-repeat;
  background-position: center center;
  user-select: none;
}

a[\_moz_anonclass="mozTableAddRowBefore"]:hover {
  background-image: url("resource://gre/res/table-add-row-before-hover.gif");
}

a[\_moz_anonclass="mozTableAddRowBefore"]:active {
  background-image: url("resource://gre/res/table-add-row-before-active.gif");
}

a[\_moz_anonclass="mozTableAddRowAfter"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 8px;
  height: 4px;
  background-image: url("resource://gre/res/table-add-row-after.gif");
  background-repeat: no-repeat;
  background-position: center center;
  user-select: none;
}

a[\_moz_anonclass="mozTableAddRowAfter"]:hover {
  background-image: url("resource://gre/res/table-add-row-after-hover.gif");
}

a[\_moz_anonclass="mozTableAddRowAfter"]:active {
  background-image: url("resource://gre/res/table-add-row-after-active.gif");
}

a[\_moz_anonclass="mozTableRemoveRow"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 8px;
  height: 8px;
  background-image: url("resource://gre/res/table-remove-row.gif");
  background-repeat: no-repeat;
  background-position: center center;
  user-select: none;
}

a[\_moz_anonclass="mozTableRemoveRow"]:hover {
  background-image: url("resource://gre/res/table-remove-row-hover.gif");
}

a[\_moz_anonclass="mozTableRemoveRow"]:active {
  background-image: url("resource://gre/res/table-remove-row-active.gif");
}
PK
!<�_�O1O1$chrome/toolkit/res/counterstyles.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* Defined in CSS Counter Styles Level 3 */

/* 6 Simple Predefined Counter Styles */

/* 6.1 Numeric */

@counter-style decimal-leading-zero {
  system: extends decimal;
  pad: 2 '0';
}

@counter-style arabic-indic {
  system: numeric;
  symbols: '\660' '\661' '\662' '\663' '\664' '\665' '\666' '\667' '\668' '\669';
}

@counter-style armenian {
  system: additive;
  range: 1 9999;
  additive-symbols: 9000 '\554', 8000 '\553', 7000 '\552', 6000 '\551', 5000 '\550', 4000 '\54F', 3000 '\54E', 2000 '\54D', 1000 '\54C', 900 '\54B', 800 '\54A', 700 '\549', 600 '\548', 500 '\547', 400 '\546', 300 '\545', 200 '\544', 100 '\543', 90 '\542', 80 '\541', 70 '\540', 60 '\53F', 50 '\53E', 40 '\53D', 30 '\53C', 20 '\53B', 10 '\53A', 9 '\539', 8 '\538', 7 '\537', 6 '\536', 5 '\535', 4 '\534', 3 '\533', 2 '\532', 1 '\531';
}

@counter-style upper-armenian {
  system: additive;
  range: 1 9999;
  additive-symbols: 9000 '\554', 8000 '\553', 7000 '\552', 6000 '\551', 5000 '\550', 4000 '\54F', 3000 '\54E', 2000 '\54D', 1000 '\54C', 900 '\54B', 800 '\54A', 700 '\549', 600 '\548', 500 '\547', 400 '\546', 300 '\545', 200 '\544', 100 '\543', 90 '\542', 80 '\541', 70 '\540', 60 '\53F', 50 '\53E', 40 '\53D', 30 '\53C', 20 '\53B', 10 '\53A', 9 '\539', 8 '\538', 7 '\537', 6 '\536', 5 '\535', 4 '\534', 3 '\533', 2 '\532', 1 '\531';
}

@counter-style lower-armenian {
  system: additive;
  range: 1 9999;
  additive-symbols: 9000 '\584', 8000 '\583', 7000 '\582', 6000 '\581', 5000 '\580', 4000 '\57F', 3000 '\57E', 2000 '\57D', 1000 '\57C', 900 '\57B', 800 '\57A', 700 '\579', 600 '\578', 500 '\577', 400 '\576', 300 '\575', 200 '\574', 100 '\573', 90 '\572', 80 '\571', 70 '\570', 60 '\56F', 50 '\56E', 40 '\56D', 30 '\56C', 20 '\56B', 10 '\56A', 9 '\569', 8 '\568', 7 '\567', 6 '\566', 5 '\565', 4 '\564', 3 '\563', 2 '\562', 1 '\561';
}

@counter-style bengali {
  system: numeric;
  symbols: '\9E6' '\9E7' '\9E8' '\9E9' '\9EA' '\9EB' '\9EC' '\9ED' '\9EE' '\9EF';
}

@counter-style cambodian {
  system: extends khmer;
}

@counter-style khmer {
  system: numeric;
  symbols: '\17E0' '\17E1' '\17E2' '\17E3' '\17E4' '\17E5' '\17E6' '\17E7' '\17E8' '\17E9';
}

@counter-style cjk-decimal {
  system: numeric;
  range: 0 infinite;
  symbols: '\3007' '\4E00' '\4E8C' '\4E09' '\56DB' '\4E94' '\516D' '\4E03' '\516B' '\4E5D';
  suffix: '\3001';
}

@counter-style devanagari {
  system: numeric;
  symbols: '\966' '\967' '\968' '\969' '\96A' '\96B' '\96C' '\96D' '\96E' '\96F';
}

@counter-style georgian {
  system: additive;
  range: 1 19999;
  additive-symbols: 10000 '\10F5', 9000 '\10F0', 8000 '\10EF', 7000 '\10F4', 6000 '\10EE', 5000 '\10ED', 4000 '\10EC', 3000 '\10EB', 2000 '\10EA', 1000 '\10E9', 900 '\10E8', 800 '\10E7', 700 '\10E6', 600 '\10E5', 500 '\10E4', 400 '\10F3', 300 '\10E2', 200 '\10E1', 100 '\10E0', 90 '\10DF', 80 '\10DE', 70 '\10DD', 60 '\10F2', 50 '\10DC', 40 '\10DB', 30 '\10DA', 20 '\10D9', 10 '\10D8', 9 '\10D7', 8 '\10F1', 7 '\10D6', 6 '\10D5', 5 '\10D4', 4 '\10D3', 3 '\10D2', 2 '\10D1', 1 '\10D0';
}

@counter-style gujarati {
  system: numeric;
  symbols: '\AE6' '\AE7' '\AE8' '\AE9' '\AEA' '\AEB' '\AEC' '\AED' '\AEE' '\AEF';
}

@counter-style gurmukhi {
  system: numeric;
  symbols: '\A66' '\A67' '\A68' '\A69' '\A6A' '\A6B' '\A6C' '\A6D' '\A6E' '\A6F';
}

/* hebrew is not included because our builtin algorithm can generate a wider
 * range of number in this style than what the spec defines. */

@counter-style kannada {
  system: numeric;
  symbols: '\CE6' '\CE7' '\CE8' '\CE9' '\CEA' '\CEB' '\CEC' '\CED' '\CEE' '\CEF';
}

@counter-style lao {
  system: numeric;
  symbols: '\ED0' '\ED1' '\ED2' '\ED3' '\ED4' '\ED5' '\ED6' '\ED7' '\ED8' '\ED9';
}

@counter-style malayalam {
  system: numeric;
  symbols: '\D66' '\D67' '\D68' '\D69' '\D6A' '\D6B' '\D6C' '\D6D' '\D6E' '\D6F';
}

@counter-style mongolian {
  system: numeric;
  symbols: '\1810' '\1811' '\1812' '\1813' '\1814' '\1815' '\1816' '\1817' '\1818' '\1819';
}

@counter-style myanmar {
  system: numeric;
  symbols: '\1040' '\1041' '\1042' '\1043' '\1044' '\1045' '\1046' '\1047' '\1048' '\1049';
}

@counter-style oriya {
  system: numeric;
  symbols: '\B66' '\B67' '\B68' '\B69' '\B6A' '\B6B' '\B6C' '\B6D' '\B6E' '\B6F';
}

@counter-style persian {
  system: numeric;
  symbols: '\6F0' '\6F1' '\6F2' '\6F3' '\6F4' '\6F5' '\6F6' '\6F7' '\6F8' '\6F9';
}

@counter-style lower-roman {
  system: additive;
  range: 1 3999;
  additive-symbols: 1000 'm', 900 'cm', 500 'd', 400 'cd', 100 'c', 90 'xc', 50 'l', 40 'xl', 10 'x', 9 'ix', 5 'v', 4 'iv', 1 'i';
}

@counter-style upper-roman {
  system: additive;
  range: 1 3999;
  additive-symbols: 1000 'M', 900 'CM', 500 'D', 400 'CD', 100 'C', 90 'XC', 50 'L', 40 'XL', 10 'X', 9 'IX', 5 'V', 4 'IV', 1 'I';
}

@counter-style tamil {
  system: numeric;
  symbols: '\BE6' '\BE7' '\BE8' '\BE9' '\BEA' '\BEB' '\BEC' '\BED' '\BEE' '\BEF';
}

@counter-style telugu {
  system: numeric;
  symbols: '\C66' '\C67' '\C68' '\C69' '\C6A' '\C6B' '\C6C' '\C6D' '\C6E' '\C6F';
}

@counter-style thai {
  system: numeric;
  symbols: '\E50' '\E51' '\E52' '\E53' '\E54' '\E55' '\E56' '\E57' '\E58' '\E59';
}

@counter-style tibetan {
  system: numeric;
  symbols: '\F20' '\F21' '\F22' '\F23' '\F24' '\F25' '\F26' '\F27' '\F28' '\F29';
}

/* 6.2 Alphabetic */

@counter-style lower-alpha {
  system: alphabetic;
  symbols: 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 'u' 'v' 'w' 'x' 'y' 'z';
}

@counter-style lower-latin {
  system: extends lower-alpha;
}

@counter-style upper-alpha {
  system: alphabetic;
  symbols: 'A' 'B' 'C' 'D' 'E' 'F' 'G' 'H' 'I' 'J' 'K' 'L' 'M' 'N' 'O' 'P' 'Q' 'R' 'S' 'T' 'U' 'V' 'W' 'X' 'Y' 'Z';
}

@counter-style upper-latin {
  system: extends upper-alpha;
}

@counter-style cjk-heavenly-stem {
  system: fixed;
  symbols: '\7532' '\4E59' '\4E19' '\4E01' '\620A' '\5DF1' '\5E9A' '\8F9B' '\58EC' '\7678';
  fallback: cjk-decimal;
  suffix: '\3001';
}

@counter-style cjk-earthly-branch {
  system: fixed;
  symbols: '\5B50' '\4E11' '\5BC5' '\536F' '\8FB0' '\5DF3' '\5348' '\672A' '\7533' '\9149' '\620C' '\4EA5';
  fallback: cjk-decimal;
  suffix: '\3001';
}

@counter-style lower-greek {
  system: alphabetic;
  symbols: '\3B1' '\3B2' '\3B3' '\3B4' '\3B5' '\3B6' '\3B7' '\3B8' '\3B9' '\3BA' '\3BB' '\3BC' '\3BD' '\3BE' '\3BF' '\3C0' '\3C1' '\3C3' '\3C4' '\3C5' '\3C6' '\3C7' '\3C8' '\3C9';
}

@counter-style hiragana {
  system: alphabetic;
  symbols: '\3042' '\3044' '\3046' '\3048' '\304A' '\304B' '\304D' '\304F' '\3051' '\3053' '\3055' '\3057' '\3059' '\305B' '\305D' '\305F' '\3061' '\3064' '\3066' '\3068' '\306A' '\306B' '\306C' '\306D' '\306E' '\306F' '\3072' '\3075' '\3078' '\307B' '\307E' '\307F' '\3080' '\3081' '\3082' '\3084' '\3086' '\3088' '\3089' '\308A' '\308B' '\308C' '\308D' '\308F' '\3090' '\3091' '\3092' '\3093';
  suffix: '\3001';
}

@counter-style hiragana-iroha {
  system: alphabetic;
  symbols: '\3044' '\308D' '\306F' '\306B' '\307B' '\3078' '\3068' '\3061' '\308A' '\306C' '\308B' '\3092' '\308F' '\304B' '\3088' '\305F' '\308C' '\305D' '\3064' '\306D' '\306A' '\3089' '\3080' '\3046' '\3090' '\306E' '\304A' '\304F' '\3084' '\307E' '\3051' '\3075' '\3053' '\3048' '\3066' '\3042' '\3055' '\304D' '\3086' '\3081' '\307F' '\3057' '\3091' '\3072' '\3082' '\305B' '\3059';
  suffix: '\3001';
}

@counter-style katakana {
  system: alphabetic;
  symbols: '\30A2' '\30A4' '\30A6' '\30A8' '\30AA' '\30AB' '\30AD' '\30AF' '\30B1' '\30B3' '\30B5' '\30B7' '\30B9' '\30BB' '\30BD' '\30BF' '\30C1' '\30C4' '\30C6' '\30C8' '\30CA' '\30CB' '\30CC' '\30CD' '\30CE' '\30CF' '\30D2' '\30D5' '\30D8' '\30DB' '\30DE' '\30DF' '\30E0' '\30E1' '\30E2' '\30E4' '\30E6' '\30E8' '\30E9' '\30EA' '\30EB' '\30EC' '\30ED' '\30EF' '\30F0' '\30F1' '\30F2' '\30F3';
  suffix: '\3001';
}

@counter-style katakana-iroha {
  system: alphabetic;
  symbols: '\30A4' '\30ED' '\30CF' '\30CB' '\30DB' '\30D8' '\30C8' '\30C1' '\30EA' '\30CC' '\30EB' '\30F2' '\30EF' '\30AB' '\30E8' '\30BF' '\30EC' '\30BD' '\30C4' '\30CD' '\30CA' '\30E9' '\30E0' '\30A6' '\30F0' '\30CE' '\30AA' '\30AF' '\30E4' '\30DE' '\30B1' '\30D5' '\30B3' '\30A8' '\30C6' '\30A2' '\30B5' '\30AD' '\30E6' '\30E1' '\30DF' '\30B7' '\30F1' '\30D2' '\30E2' '\30BB' '\30B9';
  suffix: '\3001';
}

/* 6.3 Symbolic */

/* symbolic counter styles are not included because they will be drew directly
 * by the program instead of use alternative symbols defined in the spec */

/* 7 Complex Predefined Counter Styles */

/* only alias is included as other complex counter styles will be generated by
 * specific algorithms to support the extended range. */

@counter-style cjk-ideographic {
  system: extends trad-chinese-informal;
}

/* Mozilla-specific counter styles */

/* Numeric */

@counter-style -moz-arabic-indic {
  system: extends arabic-indic;
}

@counter-style -moz-persian {
  system: extends persian;
}

@counter-style -moz-urdu {
  system: extends persian;
}

@counter-style -moz-devanagari {
  system: extends devanagari;
}

@counter-style -moz-bengali {
  system: extends bengali;
}

@counter-style -moz-gurmukhi {
  system: extends gurmukhi;
}

@counter-style -moz-gujarati {
  system: extends gujarati;
}

@counter-style -moz-oriya {
  system: extends oriya;
}

@counter-style -moz-tamil {
  system: extends tamil;
}

@counter-style -moz-telugu {
  system: extends telugu;
}

@counter-style -moz-kannada {
  system: extends kannada;
}

@counter-style -moz-malayalam {
  system: extends malayalam;
}

@counter-style -moz-thai {
  system: extends thai;
}

@counter-style -moz-lao {
  system: extends lao;
}

@counter-style -moz-myanmar {
  system: extends myanmar;
}

@counter-style -moz-khmer {
  system: extends khmer;
}

/* Alphabetic */

@counter-style -moz-cjk-heavenly-stem {
  system: extends cjk-heavenly-stem;
}
@counter-style -moz-cjk-earthly-branch {
  system: extends cjk-earthly-branch;
}

@counter-style -moz-hangul {
  system: alphabetic;
  symbols: '\AC00' '\B098' '\B2E4' '\B77C' '\B9C8' '\BC14' '\C0AC' '\C544' '\C790' '\CC28' '\CE74' '\D0C0' '\D30C' '\D558';
  suffix: ',';
}
@counter-style -moz-hangul-consonant {
  system: alphabetic;
  symbols: '\3131' '\3134' '\3137' '\3139' '\3141' '\3142' '\3145' '\3147' '\3148' '\314A' '\314B' '\314C' '\314D' '\314E';
  suffix: ',';
}

/* Ge'ez set of Ethiopic ordered list. There are other locale-dependent sets.
 * For the time being, let's implement two Ge'ez sets only
 * per Momoi san's suggestion in bug 102252.
 * For details, refer to http://www.ethiopic.org/Collation/OrderedLists.html. */
@counter-style -moz-ethiopic-halehame {
  system: alphabetic;
  symbols: '\1200' '\1208' '\1210' '\1218' '\1220' '\1228' '\1230' '\1240' '\1260' '\1270' '\1280' '\1290' '\12A0' '\12A8' '\12C8' '\12D0' '\12D8' '\12E8' '\12F0' '\1308' '\1320' '\1330' '\1338' '\1340' '\1348' '\1350';
}
@counter-style -moz-ethiopic-halehame-am {
  system: alphabetic;
  symbols: '\1200' '\1208' '\1210' '\1218' '\1220' '\1228' '\1230' '\1238' '\1240' '\1260' '\1270' '\1278' '\1280' '\1290' '\1298' '\12A0' '\12A8' '\12B8' '\12C8' '\12D0' '\12D8' '\12E0' '\12E8' '\12F0' '\1300' '\1308' '\1320' '\1328' '\1330' '\1338' '\1340' '\1348' '\1350';
}
@counter-style -moz-ethiopic-halehame-ti-er {
  system: alphabetic;
  symbols: '\1200' '\1208' '\1210' '\1218' '\1228' '\1230' '\1238' '\1240' '\1250' '\1260' '\1270' '\1278' '\1290' '\1298' '\12A0' '\12A8' '\12B8' '\12C8' '\12D0' '\12D8' '\12E0' '\12E8' '\12F0' '\1300' '\1308' '\1320' '\1328' '\1330' '\1338' '\1348' '\1350';
}
@counter-style -moz-ethiopic-halehame-ti-et {
  system: alphabetic;
  symbols: '\1200' '\1208' '\1210' '\1218' '\1220' '\1228' '\1230' '\1238' '\1240' '\1250' '\1260' '\1270' '\1278' '\1280' '\1290' '\1298' '\12A0' '\12A8' '\12B8' '\12C8' '\12D0' '\12D8' '\12E0' '\12E8' '\12F0' '\1300' '\1308' '\1320' '\1328' '\1330' '\1338' '\1340' '\1348' '\1350';
}

/* Alias */

@counter-style -moz-trad-chinese-informal {
  system: extends trad-chinese-informal;
}

@counter-style -moz-trad-chinese-formal {
  system: extends trad-chinese-formal;
}

@counter-style -moz-simp-chinese-informal {
  system: extends simp-chinese-informal;
}

@counter-style -moz-simp-chinese-formal {
  system: extends simp-chinese-formal;
}

@counter-style -moz-japanese-informal {
  system: extends japanese-informal;
}

@counter-style -moz-japanese-formal {
  system: extends japanese-formal;
}

@counter-style -moz-ethiopic-numeric {
  system: extends ethiopic-numeric;
}
PK
!<�`�FFres/designmode.css/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

*|* {
  -moz-user-modify: read-write;
}
PK
!<jfu	^^chrome/toolkit/res/forms.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
  Styles for old GFX form widgets
 **/


@namespace url(http://www.w3.org/1999/xhtml); /* set default namespace to HTML */

*|*::-moz-fieldset-content {
  display: block; /* StyleAdjuster::adjust_for_fieldset_content overrides this in some cases */
  unicode-bidi: inherit;
  text-overflow: inherit;
  overflow: inherit;
  overflow-clip-box: inherit;
  resize: inherit;
  /* Need to inherit border-radius too, so when the fieldset has rounded
     borders we don't leak out the corners for hit-testing purposes. */
  border-radius: inherit;
  padding: inherit;
  box-decoration-break: inherit;
  block-size: 100%; /* Need this so percentage block-sizes of kids work right */
  /* Please keep the declarations below in sync with ::-moz-scrolled-content in
     ua.css and ::-moz-button-content below. */
  content: inherit;
  /* Multicol container */
  column-count: inherit;
  column-width: inherit;
  column-gap: inherit;
  column-rule: inherit;
  column-fill: inherit;
  /* Flex container */
  flex-direction: inherit;
  flex-wrap: inherit;
  /* -webkit-box container (aliased from -webkit versions to -moz versions) */
  -moz-box-orient: inherit;
  -moz-box-direction: inherit;
  -moz-box-pack: inherit;
  -moz-box-align: inherit;
  /* Grid container */
  grid-auto-columns: inherit;
  grid-auto-rows: inherit;
  grid-auto-flow: inherit;
  grid-column-gap: inherit;
  grid-row-gap: inherit;
  grid-template-areas: inherit;
  grid-template-columns: inherit;
  grid-template-rows: inherit;
  /* CSS Align */
  align-content: inherit;
  align-items: inherit;
  justify-content: inherit;
  justify-items: inherit;
}

/* Miscellaneous form elements */

legend {
  display: block;
  padding-inline: 2px;
}

fieldset {
  display: block;
  margin-inline: 2px;
  padding-block: 0.35em 0.625em;
  padding-inline: 0.75em;
  border: 2px groove ThreeDFace;
  min-inline-size: min-content;
}

label {
  cursor: default;
  /* If you add declarations here, consider whether the select > label and file
   * input label need them as well. */
}

/* Default inputs, text inputs, and selects */

/* Note: Values in nsNativeTheme IsWidgetStyled function
   need to match textfield background/border values here */

input {
  display: inline-block;
  appearance: auto;
  -moz-default-appearance: textfield;
  /* The sum of border and padding on block-start and block-end
     must be the same here, for buttons, and for <select> */
  padding-block: 1px;
  padding-inline: 2px;
  border: 2px inset ButtonBorder;
  background-color: Field;
  color: FieldText;
  font: -moz-field;
  text-rendering: optimizeLegibility;
  cursor: text;
  overflow-clip-box: padding-box content-box;
}

textarea {
  display: inline-block;
  appearance: auto;
  -moz-default-appearance: textarea;
  margin-block: 1px;
  border: 2px inset ButtonBorder;
  padding: 2px;
  background-color: Field;
  color: FieldText;
  font: medium -moz-fixed;
  text-rendering: optimizeLegibility;
  vertical-align: text-bottom;
  cursor: text;
  resize: both;
  white-space: pre-wrap;
  word-wrap: break-word;
}

/* A few properties that we don't want to inherit by default: */
input, textarea, select, button, ::file-selector-button {
  text-align: initial;
  text-indent: initial;
  text-shadow: initial;
  text-transform: initial;
  word-spacing: initial;
  letter-spacing: initial;
  /* Note that line-height is also reset for all these, via the font shorthand */
}

::placeholder,
::-moz-text-control-editing-root,
::-moz-text-control-preview {
  overflow: auto;
  border: 0;
  /* This is necessary to make overflow-clip-box work */
  padding: inherit;
  margin: 0;
  text-decoration: inherit;
  display: inline-block;
  ime-mode: inherit;
  resize: inherit;
  scrollbar-width: inherit;
  -moz-control-character-visibility: visible;
  overflow-clip-box: inherit;
  visibility: hidden;
}

::placeholder,
::-moz-text-control-preview {
  /*
   * Changing resize would display a broken behaviour and will assert.
   */
  resize: none;
  overflow: hidden;

  /*
   * The placeholder or preview should be ignored by pointer / selection / etc.
   * Otherwise, we might have some unexpected behavior like the resize handle
   * not being selectable.
   */
  pointer-events: none;
  user-select: none;
}

::-moz-text-control-preview {
  font-family: system-ui;
}

::placeholder {
  -webkit-text-security: none;
  opacity: 0.54;
}

:not(:-moz-autofill-preview)::-moz-text-control-editing-root,
:placeholder-shown:not(:autofill)::placeholder,
:autofill::-moz-text-control-preview {
  visibility: inherit;
}

input::placeholder,
input::-moz-text-control-editing-root,
input::-moz-text-control-preview {
  scrollbar-width: none;
  resize: none;
  word-wrap: normal;
  white-space: pre;
  /* Make the line-height equal to the available height */
  line-height: -moz-block-height !important;
}

input[type=password] {
  -moz-default-appearance: password-input;
}

input[type=password]::-moz-text-control-editing-root,
input[type=password]::-moz-text-control-preview {
  /*
   * In password fields, any character should be put same direction.  Otherwise,
   * caret position at typing tells everybody whether the character is an RTL
   * or an LTR character.  Unfortunately, this makes odd rendering when bidi
   * text is unmasked.
   */
  unicode-bidi: bidi-override;
}

textarea::-moz-text-control-editing-root {
  scroll-behavior: inherit;
  overscroll-behavior: inherit;
  /* StyleAdjuster makes sure that the overflow value ends up being scrollable */
  overflow: inherit;
}

input:read-write,
textarea:read-write {
  -moz-user-modify: read-write !important;
}

select {
  margin: 0;
  border-color: ButtonBorder;
  font: -moz-list;
  white-space: nowrap !important;
  word-wrap: normal !important;
  cursor: default;
  box-sizing: border-box;
  user-select: none;
  border-width: 2px;
  border-style: inset;
  overflow: clip;
  /* No text-decoration reaching inside, by default */
  display: inline-block;
  page-break-inside: avoid;
  overflow-clip-box: padding-box !important; /* bug 992447 */
  padding-block: 1px;

  /* Set some styles for drop down selects. These are overridden below for
   * list box selects. */
  padding-inline: 4px;
  background-color: -moz-Combobox;
  color: -moz-ComboboxText;
  vertical-align: baseline;
  appearance: auto;
  -moz-default-appearance: menulist;
}

select:-moz-select-list-box {
  overflow-inline: hidden;
  overflow-block: scroll;
  padding-inline: 0;
  background-color: Field;
  color: FieldText;
  vertical-align: text-bottom;
  appearance: auto;
  -moz-default-appearance: listbox;
}

@media (-moz-platform: macos) {
  select:-moz-select-list-box {
    scrollbar-width: thin;
  }
}

select > button {
  padding: 0;
  border: 0;
  appearance: auto;
  -moz-default-appearance: -moz-menulist-arrow-button;
  pointer-events: none;

  /* Draw the arrow in the select's color */
  color: inherit;

  /* We don't want the button to grow the line-height */
  font: inherit;
  max-block-size: 100%;

  /* Make sure to align properly with the display frame.  Note that we want the
   * baseline of the combobox to match the baseline of the label, so the
   * dropmarker is what gets the vertical-align. */
  vertical-align: top;
}

select > label {
  display: inline-block;
  overflow: clip;
  pointer-events: none;
  cursor: unset;
}

option[label]::before {
  content: attr(label);
}

select:-moz-select-list-box option,
select:-moz-select-list-box optgroup {
  line-height: normal !important;
}

option {
  display: block;
  float: none !important;
  position: static !important;
  /* This makes sure that it is a containing block for positioned descendants. */
  will-change: -moz-fixed-pos-containing-block !important;

  min-block-size: 1em;
  padding-block: 2px;
  user-select: none;
  /*
   * Note that the "UA !important" tests in
   * layout/style/test/test_animations.html depend on this rule, because
   * they need some UA !important rule to test.  If this changes, use a
   * different one there.
   */
  white-space: nowrap !important;
  word-wrap: normal !important;
}

select > option {
  padding-inline: 4px;
}

option:checked {
  background-color: -moz-cellhighlight;
  color: -moz-cellhighlighttext;
}

select:-moz-select-list-box:focus option:checked {
  background-color: SelectedItem !important;
  color: SelectedItemText !important;
}

optgroup {
  display: block;
  float: none !important;
  position: static !important;
  font-style: italic;
  font-weight: bold;
  font-size: unset;
  user-select: none;
  white-space: nowrap !important;
  word-wrap: normal !important;
}

optgroup > option {
  padding-inline-start: 20px;
  font-style: normal;
  font-weight: normal;
}

optgroup:before {
  display: block;
  content: "\200b" attr(label);
}

@media (-moz-platform: android) {
  /* These elements are handled by the prompt module. */
  select option,
  select optgroup {
    pointer-events: none;
  }
}

*|*::-moz-dropdown-list {
  content: inherit;
  z-index: 2147483647;
  background-color: inherit;
  user-select: none;
  position: static !important;
  float: none !important;

  /*
   * We can't change the padding here, because that would affect our
   * intrinsic inline-size, since we scroll.  But at the same time, we want
   * to make sure that our inline-start border+padding matches the inline-start
   * border+padding of a combobox so that our scrollbar will line up
   * with the dropmarker.  So set our inline-start border to 2px.
   */
  border: 1px outset black !important;
  border-inline-start-width: 2px !important;
}

input:disabled,
textarea:disabled,
option:disabled,
optgroup:disabled,
select:disabled {
  color: GrayText;
  background-color: -moz-DisabledField;
  cursor: unset;
}

input:disabled,
textarea:disabled {
  cursor: default;
}

option:disabled,
optgroup:disabled {
  background-color: transparent;
}

/* hidden inputs */
input[type=hidden] {
  appearance: none;
  -moz-default-appearance: none;
  display: none !important;
  padding: unset;
  border: 0;
  cursor: auto;
  -moz-user-focus: ignore;
}

/* image buttons */
input[type=image] {
  appearance: none;
  -moz-default-appearance: none;
  padding: unset;
  border: none;
  background-color: transparent;
  font-family: sans-serif;
  font-size: small;
  cursor: pointer;
}

input[type=image]:disabled {
  cursor: unset;
}

/* colored part of the color selector button */
::-moz-color-swatch {
  width: 100%;
  height: 100%;
  min-width: 3px;
  min-height: 3px;
  margin-inline: auto;
  box-sizing: border-box;
  border: 1px solid grey;
  display: block;
}

/* radio buttons */
input[type=radio] {
  appearance: auto;
  -moz-default-appearance: radio;
  margin-block: 3px 0;
  margin-inline: 5px 3px;
}

/* check boxes */
input[type=checkbox] {
  appearance: auto;
  -moz-default-appearance: checkbox;
  margin-block: 3px;
  margin-inline: 4px 3px;
}

/* Common features of radio buttons and check boxes */

input[type=radio],
input[type=checkbox] {
  box-sizing: border-box;
  cursor: default;
  /* unset some values from the general 'input' rule above: */
  padding: unset;
  border: unset;
  background-color: unset;
  color: unset;
}

input:is([type=radio], [type=checkbox]):is(:disabled, :disabled:active, :disabled:hover:active) {
  cursor: unset;
}

input[type=search] {
  box-sizing: border-box;
}

/* buttons */

/* Note: Values in nsNativeTheme IsWidgetStyled function
   need to match button background/border values here */

/* Non text-related properties for buttons: these ones are shared with
   input[type=color] */
button,
::file-selector-button,
input:is([type=color], [type=reset], [type=button], [type=submit]) {
  appearance: auto;
  -moz-default-appearance: button;
  /* The sum of border and padding on block-start and block-end
     must be the same here, for text inputs, and for <select>.
     Note -moz-focus-inner padding does not affect button size. */
  padding-block: 1px;
  padding-inline: 8px;
  border: 2px outset ButtonBorder;
  background-color: ButtonFace;
  cursor: default;
  box-sizing: border-box;
  user-select: none;
  overflow-clip-box: padding-box;
}

/* Text-related properties for buttons: these ones are not shared with
   input[type=color] */
button,
::file-selector-button,
input:is([type=reset], [type=button], [type=submit]) {
  color: ButtonText;
  font: -moz-button;
  white-space: pre;
  text-align: center;
  padding-inline: 4px;
}

input[type=color] {
  inline-size: 64px;
  block-size: 32px;
  padding: 4px;
}

/* https://github.com/whatwg/html/issues/9976 */
input:not([type=image i], [type=range i], [type=checkbox i], [type=radio i]) {
  overflow: clip !important;
  overflow-clip-margin: 0px !important;
}

button,
::file-selector-button {
  /* Buttons should lay out like "normal" html, mostly */
  white-space: unset;
  /* But no text-decoration reaching inside, by default */
  display: inline-block;
}

*|*::-moz-button-content {
  display: block;
  /* Please keep the declarations below in sync with ::-moz-scrolled-content in
     ua.css and ::-moz-fieldset-content above. */
  content: inherit;
  /* Multicol container */
  column-count: inherit;
  column-width: inherit;
  column-gap: inherit;
  column-rule: inherit;
  column-fill: inherit;
  /* Flex container */
  flex-direction: inherit;
  flex-wrap: inherit;
  /* -webkit-box container (aliased from -webkit versions to -moz versions) */
  -moz-box-orient: inherit;
  -moz-box-direction: inherit;
  -moz-box-pack: inherit;
  -moz-box-align: inherit;
  /* Grid container */
  grid-auto-columns: inherit;
  grid-auto-rows: inherit;
  grid-auto-flow: inherit;
  grid-column-gap: inherit;
  grid-row-gap: inherit;
  grid-template-areas: inherit;
  grid-template-columns: inherit;
  grid-template-rows: inherit;
  /* CSS Align */
  align-content: inherit;
  align-items: inherit;
  justify-content: inherit;
  justify-items: inherit;
}

::file-selector-button:hover,
button:hover,
input:is([type=reset], [type=button], [type=submit], [type=color]):hover {
  color: -moz-buttonhovertext;
  background-color: -moz-buttonhoverface;
}

::file-selector-button:active:hover,
button:active:hover,
input:is([type=reset], [type=button], [type=submit], [type=color]):active:hover {
  border-style: inset;
  color: -moz-buttonactivetext;
  background-color: -moz-buttonactiveface;
}

::-moz-focus-inner {
  /* Note this padding only affects the -moz-focus-inner ring, not the button itself */
  padding-block: 0;
  padding-inline: 2px;
  border: 1px dotted transparent;
}

:focus-visible::-moz-focus-inner {
  border-color: currentColor;
}

:is(:disabled, :disabled:active)::file-selector-button,
button:is(:disabled, :disabled:active),
input:is([type=reset], [type=button], [type=submit], [type=color]):is(:disabled, :disabled:active),
select:is(:disabled, :disabled:active) > button {
  border-style: outset;
  cursor: unset;
}

:is(:disabled, :disabled:active)::file-selector-button,
button:is(:disabled, :disabled:active),
input:is([type=reset], [type=button], [type=submit]):is(:disabled, :disabled:active),
select:is(:disabled, :disabled:active) > button {
  color: GrayText;
  background-color: -moz-ButtonDisabledFace;
}

/* file selector */
input[type=file] {
  white-space: nowrap !important;
  overflow-clip-box: padding-box;
  color: unset;

  /* Revert rules which apply on all inputs. */
  appearance: none;
  -moz-default-appearance: none;
  cursor: default;

  border: none;
  background-color: transparent;
  padding: unset;
}

input[type=file] > label {
  display: inline-block;
  min-inline-size: 12em;
  text-align: match-parent;

  cursor: unset;
  user-select: none;
  unicode-bidi: plaintext;
}

/* button part of file selector */
::file-selector-button {
  font-size: unset;
  letter-spacing: unset;
  cursor: unset;
  margin-inline-end: 5px;
}

 /*
  * Make form controls inherit 'unicode-bidi' transparently as required by
  *  their various anonymous descendants and pseudo-elements:
  *
  * <textarea> and <input type=text>:
  *  inherit into the scroll frame with pseudo ::-moz-text-control-editing-root
  *  which is a (direct or indirect) child of the text control.
  *
  * Buttons (either <button>, <input type=submit>, <input type=button>
  *          or <input type=reset>)
  *  inherit into the ':-moz-button-content' pseudo-element.
  *
  * <select>:
  *  inherit into the label and the <optgroup>'s ':before' pseudo-element,
  *  which is where the label of the <optgroup> gets displayed. The <option>s
  *  don't use anonymous boxes, so they need no special rules.
  */
::placeholder,
::-moz-text-control-editing-root,
*|*::-moz-button-content,
select > label,
optgroup::before {
  unicode-bidi: inherit;
  text-overflow: inherit;
}

progress {
  appearance: auto;
  -moz-default-appearance: progress-bar;
  display: inline-block;
  vertical-align: -0.2em;

  /* Default style in case of there is appearance: none; */
  border: 1px solid ThreeDShadow;
  border-right-color: ThreeDHighlight;
  border-bottom-color: ThreeDHighlight;
  /* #e6e6e6 is a light gray. */
  background-color: #e6e6e6;
  overflow: clip;
}

progress::-moz-progress-bar,
progress::slider-fill {
  /* Prevent styling that would change the type of frame we construct. */
  display: inline-block !important;
  float: none !important;
  position: static !important;
  overflow: visible !important;
  box-sizing: border-box !important;

  appearance: auto;
  -moz-default-appearance: progresschunk;
  height: 100%;
  width: 100%;

  /* Default style in case of there is appearance: none; */
  background-color: #0064b4; /* blue */
}

meter {
  appearance: auto;
  -moz-default-appearance: meter;
  display: inline-block;
  vertical-align: -0.2em;
  background: linear-gradient(#e6e6e6, #e6e6e6, #eeeeee 20%, #cccccc 45%, #cccccc 55%);
  overflow: clip;
}

meter::-moz-meter-bar,
meter::slider-fill {
  /* Block styles that would change the type of frame we construct. */
  display: inline-block !important;
  float: none !important;
  position: static !important;
  overflow: visible !important;

  appearance: auto;
  -moz-default-appearance: meterchunk;
  height: 100%;
  width: 100%;
}

meter:-moz-meter-optimum::-moz-meter-bar,
meter:-moz-meter-optimum::slider-fill {
  /* green. */
  background: linear-gradient(#ad7, #ad7, #cea 20%, #7a3 45%, #7a3 55%);
}
meter:-moz-meter-sub-optimum::-moz-meter-bar,
meter:-moz-meter-sub-optimum::slider-fill {
  /* orange. */
  background: linear-gradient(#fe7, #fe7, #ffc 20%, #db3 45%, #db3 55%);
}
meter:-moz-meter-sub-sub-optimum::-moz-meter-bar,
meter:-moz-meter-sub-sub-optimum::slider-fill {
  /* red. */
  background: linear-gradient(#f77, #f77, #fcc 20%, #d44 45%, #d44 55%);
}

input[type=range] {
  appearance: auto;
  -moz-default-appearance: range;
  margin: 2px;
  /* Override some rules that apply on all input types: */
  cursor: default;
  padding: unset;
  border: unset;
  /* Prevent nsIFrame::HandlePress setting mouse capture to this element. */
  user-select: none !important;
}

/**
 * Layout handles positioning of this pseudo-element specially (so that content
 * authors can concentrate on styling the thumb without worrying about the
 * logic to position it). Specifically the 'margin', 'top' and 'left'
 * properties are ignored.
 *
 * If content authors want to have a vertical range, they will also need to
 * set the width/height of this pseudo-element.
 *
 * TODO(emilio, bug 1663819): Losen these restrictions once these
 * pseudo-elements are better spec'd out.
 */
input[type=range]::-moz-range-track,
input[type=range]::slider-track {
  /* Prevent styling that would change the type of frame we construct. */
  display: block !important;
  float: none !important;
  position: static !important;
  writing-mode: unset !important;
  direction: unset !important;
  block-size: 0.2em; /* same as inline-size below */
  /* Prevent nsIFrame::HandlePress setting mouse capture to this element. */
  user-select: none !important;
}

input[type=range][orient=vertical]::-moz-range-track,
input[type=range][orient=vertical]::slider-track {
  inline-size: 0.2em; /* same as block-size above */
  block-size: 100%;
}

/**
 * Layout handles positioning of this pseudo-element specially (so that content
 * authors can concentrate on styling this pseudo-element without worrying
 * about the logic to position it). Specifically the 'margin', 'top' and 'left'
 * properties are ignored. Additionally, if the range is horizontal, the width
 * property is ignored, and if the range range is vertical, the height property
 * is ignored.
 */
input[type=range]::-moz-range-progress,
input[type=range]::slider-fill {
  /* Prevent styling that would change the type of frame we construct. */
  display: block !important;
  float: none !important;
  position: static !important;
  writing-mode: unset !important;
  direction: unset !important;
  /* Since one of width/height will be ignored, this just sets the "other"
     dimension. */
  width: 0.2em;
  height: 0.2em;
  /* Prevent nsIFrame::HandlePress setting mouse capture to this element. */
  user-select: none !important;
}

/**
 * Layout handles positioning of this pseudo-element specially (so that content
 * authors can concentrate on styling the thumb without worrying about the
 * logic to position it). Specifically the 'margin', 'top' and 'left'
 * properties are ignored.
 */
input[type=range]::-moz-range-thumb,
input[type=range]::slider-thumb {
  /* Native theming is atomic for range. Set appearance on the range
   * to get rid of it. The thumb's appearance is fixed.
   */
  appearance: auto !important;
  -moz-default-appearance: range-thumb !important;
  /* Prevent styling that would change the type of frame we construct. */
  display: block !important;
  float: none !important;
  position: static !important;
  writing-mode: unset !important;
  direction: unset !important;

  width: 1em;
  height: 1em;
  border: 0.1em solid #999;
  border-radius: 0.5em;
  background-color: #F0F0F0;
  /* Prevent nsIFrame::HandlePress setting mouse capture to this element. */
  user-select: none !important;
}

input[type=number] {
  -moz-default-appearance: number-input;
}

input[type=number]::-moz-number-spin-box {
  writing-mode: horizontal-tb;
  display: flex;
  flex-direction: column;
  width: max-content;
  align-self: center;
  justify-content: center;
  /* Don't allow the spin buttons to create overflow */
  max-height: 100%;
  max-width: 100%;
  overflow: clip;
}

input[type=number]::-moz-number-spin-up,
input[type=number]::-moz-number-spin-down {
  writing-mode: horizontal-tb;
  appearance: auto;
  -moz-default-appearance: spinner-upbutton;
  display: block; /* bug 926670 */
  flex-grow: 1;
  cursor: default;
}

input[type=number]::-moz-number-spin-down {
  -moz-default-appearance: spinner-downbutton;
}

input::-moz-search-clear-button,
input::-moz-reveal {
  display: block;
  cursor: default;
  width: 1em;
  height: 1em;
  max-height: 100%;
  max-width: 100%;
  margin-inline-start: 1px;
  background-image: url("resource://content-accessible/searchfield-cancel.svg");
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
}

input::-moz-reveal {
  background-image: url("resource://gre-resources/password.svg");
  -moz-context-properties: fill;
  fill: currentColor;
}

input:-moz-revealed::-moz-reveal {
  background-image: url("resource://gre-resources/password-hide.svg");
}

input:-moz-value-empty::-moz-reveal,
input:-moz-value-empty::-moz-search-clear-button {
  visibility: hidden;
}

input:is([type=date], [type=time], [type=datetime-local]) {
  font-family: -moz-fixed;
  cursor: default;
}

input:is([type=date], [type=time], [type=datetime-local]):is(:disabled, :read-only) {
  color: GrayText;
}

input:autofill, select:autofill {
  background-color: -moz-autofill-background !important;
  background-image: none !important;
  color: FieldText !important;
}
PK
!<��))~N~Nchrome/toolkit/res/html.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url(http://www.w3.org/1999/xhtml); /* set default namespace to HTML */
@namespace xul url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);

@font-face {
  font-family: -moz-bullet-font;
  src: url("data:font/woff2;base64,d09GMgABAAAAAAScAAsAAAAACfAAAARNAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAABmAAhBIRCAqGMIRJATYCJAMsCxgABCAFhF4HaBsuCMgehXEsLOlXaXd7sgbPQ172fjLwdhYFM92mABaiTuRVdO5/mx9I5UJiJom3phLRQw/TSF2M/vo25+pNyWkNws81NrUDAAf0QOdfcf7nmPEyBPZtCbbZEDuosskKZG922ZhBenh05qcSXktf6s3Oy9dAkWsyDzMoM5QTKcwKNjFayUPSREGFhUMrdE8MJsH052qGcYNO3pbEaYIBoDTItIKGAqhDdKHRD0kkKg6QGSCWEfueTNr3P6OvYaIbDH/8ULEAbjQtUt+hT/qmX/SH/twwMLDqN4jxzXjy4PHtltSA6lINqkpRd7N7onVBP20y7YBWyDIHFEBjqKBFMmECfQcTZtD3MmEB/SATVtCPM2ED/TQTdtCfS0/pEsN4ySin3r8q8xHE+0D0XMg8gJi4YrNpNv3qetXuDyeo6OBNZrq5gi7ouC1zcmB2Fd+FewIhcUIfszpF1hzXhrmMCwxaznRyZjig6Y2W7+bBn2DL8fJu/2oHbWc6X80+LUTXcVzv8O3IOy4qKVyFw/DHaMtfzNQEi5gbFT/EDpiofbpkYq2dQui6WcpfxrVBEJ/KxbLA4ZroZoVx+iE+QeTIiI7oKuoUIk/a1ekEOoNFjozm0suYYUelq13/88K1c3PTH9O9Q0d5LZbu7+J2aT7XP23KyQs+34WTU7r8d/k0l6dLl98Dn+/BSUZc7U0mm7FqhSMO9EHV/j5FB4qYX4aXNaKwz/4RRfAWsW8xGW+hvr8PTAf6IP9mRpkDwwv7QFz9HRuOeGWfd8rkyYsWTVy4vpG/pgVNWzBx0aTeIgeq58XFVfZXVduwuOq51a77aT0XLVw4v1PBFjRsQzO+tVeVp3KIwqfKbd6sdjzGvFSuPMquGpMT29lkrhwbE5uz6cGHhIETB06oUk+6tHLJgfnu/3nVnJzYxiXNjQNjYqsu+vCgYKDla5uUOzTNDZg46eCBd3d3gSk9Kz/bFiv8hvOzCT5eqyjBGerT+EiG/JGqwbC+UQ6Bv9bmKGWMj8Yn+fPVRJxKBGqTFXQyM9DhHSlvEHmLVxyYjY/Fdd3hACfLyP3VVZLvVR7B0pxSsGIBqHhwT5sSMOMkRG0BJhpsWMkEO24KwE0pqp3tIYTWCIjJjuBiGNTKTj8JoBLKAn4TmPFnj9oywZwFGx4egp1g3oObeLHdwUO2pFcYTITZCa2c1GQbNg6RCsu9bpikThhAWnLaBi3LM8+5VbMU6rUn30owOC83ULcfBlClb5BBre46BinaWRal85SUvlCbUx1Nbyj2HWCxBTkEgWScQKzsTioJW0IanAYRZqET4mZLSKU2JABII9np01lQ7lOt3srtM1KAeqgQPhYJSMCZJRuAukRZB1CF35gB1MJBr4ilIPZchNyIlDkpRt+3s96cz9K8XKQvCdZfcqWxgtLoXZ2AoKAyVSRRxSRmsYjVOriP089q3z6gdMjE5KI7kzumWzpnAwAAAA==");
}

/* bidi */

:-moz-has-dir-attr {
  unicode-bidi: isolate;
}
:-moz-dir-attr-rtl {
  direction: rtl;
}
:-moz-dir-attr-ltr {
  direction: ltr;
}

:-moz-dir-attr-like-auto:dir(ltr) { direction: ltr; }
:-moz-dir-attr-like-auto:dir(rtl) { direction: rtl; }

/* https://html.spec.whatwg.org/#bidi-rendering */
input[type=tel]:dir(ltr) {
  direction: ltr;
}

/* To ensure http://www.w3.org/TR/REC-html40/struct/dirlang.html#style-bidi:
 *
 * "When a block element that does not have a dir attribute is transformed to
 * the style of an inline element by a style sheet, the resulting presentation
 * should be equivalent, in terms of bidirectional formatting, to the
 * formatting obtained by explicitly adding a dir attribute (assigned the
 * inherited value) to the transformed element."
 *
 * and the rules in http://dev.w3.org/html5/spec/rendering.html#rendering
 */

address,
article,
aside,
blockquote,
body,
caption,
center,
col,
colgroup,
dd,
dir,
div,
dl,
dt,
fieldset,
figcaption,
figure,
footer,
form,
h1,
h2,
h3,
h4,
h5,
h6,
header,
hgroup,
hr,
html,
legend,
li,
listing,
main,
marquee,
menu,
nav,
noframes,
ol,
p,
plaintext,
pre,
search,
section,
summary,
table,
tbody,
td,
tfoot,
th,
thead,
tr,
ul,
xmp {
  unicode-bidi: isolate;
}

bdi, output {
  unicode-bidi: isolate;
}
/* We need the "bdo:-moz-has-dir-attr" bit because "bdo" has lower
   specificity than the ":-moz-has-dir-attr" selector above. */
bdo, bdo:-moz-has-dir-attr {
  unicode-bidi: isolate-override;
}
textarea:-moz-dir-attr-like-auto,
pre:-moz-dir-attr-like-auto {
    unicode-bidi: plaintext;
}

/* blocks */

article,
aside,
details,
div,
dt,
figcaption,
footer,
form,
header,
hgroup,
html,
main,
nav,
search,
section,
summary {
  display: block;
}

body {
  display: block;
  margin: 8px;
}

p, dl {
  display: block;
  margin-block-start: 1em;
  margin-block-end: 1em;
}

dd {
  display: block;
  margin-inline-start: 40px;
}

blockquote, figure {
  display: block;
  margin-block: 1em;
  margin-inline: 40px;
}

address {
  display: block;
  font-style: italic;
}

center {
  display: block;
  text-align: -moz-center;
}

h1 {
  display: block;
  font-size: 2em;
  font-weight: bold;
  margin-block: .67em;
}

h2 {
  display: block;
  font-size: 1.5em;
  font-weight: bold;
  margin-block: .83em;
}

h3 {
  display: block;
  font-size: 1.17em;
  font-weight: bold;
  margin-block: 1em;
}

h4 {
  display: block;
  font-size: 1.00em;
  font-weight: bold;
  margin-block: 1.33em;
}

h5 {
  display: block;
  font-size: 0.83em;
  font-weight: bold;
  margin-block: 1.67em;
}

h6 {
  display: block;
  font-size: 0.67em;
  font-weight: bold;
  margin-block: 2.33em;
}

/* stylelint-disable-next-line media-query-no-invalid */
@media (-moz-bool-pref: "layout.css.h1-in-section-ua-styles.enabled") {
  :is(article, aside, nav, section)
  h1 {
    margin-block: 0.83em;
    font-size: 1.50em;
  }

  :is(article, aside, nav, section)
  :is(article, aside, nav, section)
  h1 {
    margin-block: 1.00em;
    font-size: 1.17em;
  }

  :is(article, aside, nav, section)
  :is(article, aside, nav, section)
  :is(article, aside, nav, section)
  h1 {
    margin-block: 1.33em;
    font-size: 1.00em;
  }

  :is(article, aside, nav, section)
  :is(article, aside, nav, section)
  :is(article, aside, nav, section)
  :is(article, aside, nav, section)
  h1 {
    margin-block: 1.67em;
    font-size: 0.83em;
  }

  :is(article, aside, nav, section)
  :is(article, aside, nav, section)
  :is(article, aside, nav, section)
  :is(article, aside, nav, section)
  :is(article, aside, nav, section)
  h1 {
    margin-block: 2.33em;
    font-size: 0.67em;
  }
}

listing {
  display: block;
  font-family: -moz-fixed;
  font-size: medium;
  white-space: pre;
  margin-block: 1em;
}

xmp, pre, plaintext {
  display: block;
  font-family: -moz-fixed;
  white-space: pre;
  margin-block: 1em;
}

/* tables */

table {
  display: table;
  border-spacing: 2px;
  border-collapse: separate;
  /* XXXldb do we want this if we're border-collapse:collapse ? */
  box-sizing: border-box;
  text-indent: 0;
}

table[align="left"] {
  float: left;
}

table[align="right"] {
  float: right;
  text-align: start;
}


/* border collapse rules */

  /* Set hidden if we have 'frame' or 'rules' attribute.
     Set it on all sides when we do so there's more consistency
     in what authors should expect */

  /* Put this first so 'border' and 'frame' rules can override it. */
table[rules] {
  border-width: thin;
  border-style: hidden;
}

  /* 'border' before 'frame' so 'frame' overrides
      A border with a given value should, of course, pass that value
      as the border-width in pixels -> attr mapping */

  /* :-moz-table-border-nonzero is like [border]:not([border="0"]) except it
     also checks for other zero-like values according to HTML attribute
     parsing rules */
table:-moz-table-border-nonzero {
  border-width: thin;
  border-style: outset;
}

table[frame] {
  border: thin hidden;
}

/* specificity must beat table:-moz-table-border-nonzero rule above */
table[frame="void"]   { border-style: hidden; }
table[frame="above"]  { border-style: outset hidden hidden hidden; }
table[frame="below"]  { border-style: hidden hidden outset hidden; }
table[frame="lhs"]    { border-style: hidden hidden hidden outset; }
table[frame="rhs"]    { border-style: hidden outset hidden hidden; }
table[frame="hsides"] { border-style: outset hidden; }
table[frame="vsides"] { border-style: hidden outset; }
table[frame="box"],
table[frame="border"] { border-style: outset; }


/* Internal Table Borders */

  /* 'border' cell borders first */

table:-moz-table-border-nonzero > * > tr > td,
table:-moz-table-border-nonzero > * > tr > th,
table:-moz-table-border-nonzero > * > td,
table:-moz-table-border-nonzero > * > th,
table:-moz-table-border-nonzero > td,
table:-moz-table-border-nonzero > th
{
  border-width: thin;
  border-style: inset;
}

/* collapse only if rules are really specified */
table[rules]:not([rules="none"], [rules=""]) {
  border-collapse: collapse;
}

/* only specified rules override 'border' settings
  (increased specificity to achieve this) */
table[rules]:not([rules=""])> tr > td,
table[rules]:not([rules=""])> * > tr > td,
table[rules]:not([rules=""])> tr > th,
table[rules]:not([rules=""])> * > tr > th,
table[rules]:not([rules=""])> td,
table[rules]:not([rules=""])> th
{
  border-width: thin;
  border-style: none;
}


table[rules][rules="none"]  > tr > td,
table[rules][rules="none"] > * > tr > td,
table[rules][rules="none"] > tr > th,
table[rules][rules="none"] > * > tr > th,
table[rules][rules="none"] > td,
table[rules][rules="none"] > th
{
  border-width: thin;
  border-style: none;
}

table[rules][rules="all"] > tr > td,
table[rules][rules="all"] > * > tr > td,
table[rules][rules="all"] > tr > th,
table[rules][rules="all"] > * > tr > th,
table[rules][rules="all"] > td,
table[rules][rules="all"] > th
{
  border-width: thin;
  border-style: solid;
}

table[rules][rules="rows"] > tr,
table[rules][rules="rows"] > * > tr {
  border-block-start-width: thin;
  border-block-end-width: thin;
  border-block-start-style: solid;
  border-block-end-style: solid;
}


table[rules][rules="cols"] > tr > td,
table[rules][rules="cols"] > * > tr > td,
table[rules][rules="cols"] > tr > th,
table[rules][rules="cols"] > * > tr > th {
  border-inline-width: thin;
  border-inline-style: solid;
}

table[rules][rules="groups"] > colgroup {
  border-inline-width: thin;
  border-inline-style: solid;
}
table[rules][rules="groups"] > tfoot,
table[rules][rules="groups"] > thead,
table[rules][rules="groups"] > tbody {
  border-block-width: thin;
  border-block-style: solid;
}


/* caption inherits from table not table-outer */
caption {
  display: table-caption;
  text-align: center;
}

table[align="center"] > caption {
  margin-inline: auto;
}

table[align="center"] > caption[align="left"]:dir(ltr) {
  margin-inline-end: 0;
}
table[align="center"] > caption[align="left"]:dir(rtl) {
  margin-inline-start: 0;
}

table[align="center"] > caption[align="right"]:dir(ltr) {
  margin-inline-start: 0;
}
table[align="center"] > caption[align="right"]:dir(rtl) {
  margin-inline-end: 0;
}

tr {
  display: table-row;
  vertical-align: inherit;
}

col {
  display: table-column;
}

colgroup {
  display: table-column-group;
}

tbody {
  display: table-row-group;
  vertical-align: middle;
}

thead {
  display: table-header-group;
  vertical-align: middle;
}

tfoot {
  display: table-footer-group;
  vertical-align: middle;
}

/* for XHTML tables without tbody */
table > tr {
  vertical-align: middle;
}

td {
  display: table-cell;
  vertical-align: inherit;
  text-align: unset;
  padding: 1px;
}

th {
  display: table-cell;
  vertical-align: inherit;
  font-weight: bold;
  padding: 1px;
  text-align: -moz-center-or-inherit;
}

:is(tr, tbody, thead, tfoot, table) > form:-moz-is-html {
  /* Important: don't show these forms in HTML */
  display: none !important;
}

table[bordercolor] > tbody,
table[bordercolor] > thead,
table[bordercolor] > tfoot,
table[bordercolor] > col,
table[bordercolor] > colgroup,
table[bordercolor] > tr,
table[bordercolor] > * > tr,
table[bordercolor]  > tr > td,
table[bordercolor] > * > tr > td,
table[bordercolor]  > tr > th,
table[bordercolor] > * > tr > th {
  border-color: inherit;
}

/* inlines */

q:before {
  content: open-quote;
}

q:after {
  content: close-quote;
}

b, strong {
  font-weight: bolder;
}

i, cite, em, var, dfn {
  font-style: italic;
}

tt, code, kbd, samp {
  font-family: -moz-fixed;
}

u, ins {
  text-decoration: underline;
}

s, strike, del {
  text-decoration: line-through;
}

big {
  font-size: larger;
}

small {
  font-size: smaller;
}

sub {
  vertical-align: sub;
  font-size: smaller;
}

sup {
  vertical-align: super;
  font-size: smaller;
}

nobr {
  white-space: nowrap;
}

mark {
  background: Mark;
  color: MarkText;
}

/* titles */
abbr[title], acronym[title] {
  text-decoration: dotted underline;
}

/* lists */

ul, menu, dir {
  display: block;
  list-style-type: disc;
  margin-block-start: 1em;
  margin-block-end: 1em;
  padding-inline-start: 40px;
}

ul, ol, menu {
  counter-reset: list-item;
}

ol {
  display: block;
  list-style-type: decimal;
  margin-block-start: 1em;
  margin-block-end: 1em;
  padding-inline-start: 40px;
}

li {
  display: list-item;
  text-align: match-parent;
}

/* nested lists have no top/bottom margins */
:is(ul, ol, dir, menu, dl) ul,
:is(ul, ol, dir, menu, dl) ol,
:is(ul, ol, dir, menu, dl) dir,
:is(ul, ol, dir, menu, dl) menu,
:is(ul, ol, dir, menu, dl) dl {
  margin-block: 0;
}

/* 2 deep unordered lists use a circle */
:is(ol, ul, menu, dir) ul,
:is(ol, ul, menu, dir) menu,
:is(ol, ul, menu, dir) dir {
  list-style-type: circle;
}

/* 3 deep (or more) unordered lists use a square */
:is(ol, ul, menu, dir) :is(ol, ul, menu, dir) ul,
:is(ol, ul, menu, dir) :is(ol, ul, menu, dir) menu,
:is(ol, ul, menu, dir) :is(ol, ul, menu, dir) dir {
  list-style-type: square;
}


/* leafs */

/* <hr> noshade and color attributes are handled completely by
 * HTMLHRElement::MapAttributesIntoRule.
 * https://html.spec.whatwg.org/#the-hr-element-2
 */
hr {
  color: gray;
  border-width: 1px;
  border-style: inset;
  margin-block: 0.5em;
  margin-inline: auto;
  overflow: hidden;

  /* FIXME: This is not really per spec */
  display: block;
}

hr[size="1"] {
  border-style: solid none none none;
}

/* Note that we only intend for the alt content to show up if the image is
 * broken. But non-broken images/inputs will have a replaced box, and thus we
 * won't we don't generate the pseudo-element anyways. This prevents
 * unnecessary reframing when images become broken / non-broken. */
input[type=image]::before,
img::before {
  content: -moz-alt-content !important;
  unicode-bidi: isolate;
}

img[usemap], object[usemap] {
  color: blue;
}

frameset {
  display: block ! important;
  overflow: clip;
  position: static ! important;
  float: none ! important;
  border: none ! important;
}

frame {
  border-radius: 0 ! important;
}

iframe {
  border: 2px inset;
}

spacer {
  position: static ! important;
  float: none ! important;
}

canvas {
  user-select: none;
}

iframe:focus-visible,
body:focus-visible,
html:focus-visible {
  /* These elements historically don't show outlines when focused by default.
   * We could consider changing that if needed. */
  outline-style: none;
}

/* hidden elements: https://html.spec.whatwg.org/#hidden-elements
 *
 * Exceptions:
 *
 *  * area declaration needs to be !important, see below / bug 135040.  That's
 *    hacky and broken.
 *
 *  * [hidden] is implemented as a presentation attribute to avoid a global
 *    selector in a UA sheet.
 */
base, basefont, datalist, head, link, meta, noembed,
noframes, param, rp, script, style, template, title {
   display: none;
}

area {
  /* Don't give it frames other than its imageframe */
  display: none ! important;
}

iframe:fullscreen {
  /* iframes in full-screen mode don't show a border. */
  border: none !important;
  padding: unset !important;
}

/* Details and summary
 * https://html.spec.whatwg.org/#the-details-and-summary-elements
 *
 * Note that these rules need to be duplicated in details.css for the anonymous
 * summary, which wouldn't match otherwise.
 *
 * The spec here says something different, see
 * https://github.com/whatwg/html/issues/8610
 */
details > summary:first-of-type {
  display: list-item;
  counter-increment: list-item 0;
  list-style: disclosure-closed inside;
}
details[open] > summary:first-of-type {
  list-style-type: disclosure-open;
}

/* media elements */
video {
  object-fit: contain;
}

video > img:-moz-native-anonymous {
  /* Video poster images should render with the video element's "object-fit" &
     "object-position" properties */
  object-fit: inherit !important;
  object-position: inherit !important;
}

audio:not([controls]) {
  display: none !important;
}

audio[controls] {
  /* This ensures that intrinsic sizing can reliably shrinkwrap our
      controls (which are also always horizontal) and produce a
      reasonable intrinsic size from them. */
  writing-mode: horizontal-tb !important;
}

*|*::-moz-html-canvas-content {
  display: block !important;
  /* we want to be an absolute and fixed container */
  transform: translate(0) !important;
}

video > .caption-box {
  width: 100%;
  height: 100%;
  position: relative;
}

/**
 * The pseudo element won't inherit CSS styles from its direct parent, `::cue`
 * would actually inherit styles from video because it's video's pseudo element.
 * Therefore, we have to explicitly set some styles which are already defined
 * in its parent element in vtt.sys.mjs.
 */
::cue {
  color: rgba(255, 255, 255, 1);
  white-space: pre-line;
  background-color: rgba(0, 0, 0, 0.8);
  font: 10px sans-serif;
  overflow-wrap: break-word;
  /* TODO : enable unicode-bidi, right now enable it would cause incorrect
            display direction, maybe related with bug 1558431. */
}

/* <dialog> element styles */

dialog {
  position: absolute;
  display: block;
  inset-inline-start: 0;
  inset-inline-end: 0;
  margin: auto;
  border-width: initial;
  border-style: solid;
  border-color: initial;
  border-image: initial;
  padding: 1em;
  background-color: Canvas;
  color: CanvasText;
  width: -moz-fit-content;
  height: -moz-fit-content;
}

dialog:not([open]) {
  display: none;
}

dialog:modal {
  -moz-top-layer: top !important;
  position: fixed;
  overflow: auto;
  visibility: visible;
  inset-block-start: 0;
  inset-block-end: 0;
  max-width: calc(100% - 6px - 2em);
  max-height: calc(100% - 6px - 2em);
}

/* https://html.spec.whatwg.org/#flow-content-3 */
dialog::backdrop {
  background: rgba(0, 0, 0, 0.1);
}

/* https://html.spec.whatwg.org/#the-marquee-element-2 */
marquee {
  display: inline-block;
  text-align: initial;
  overflow: hidden !important;

  /* See https://github.com/whatwg/html/issues/10249 */
  inline-size: -moz-available;
  vertical-align: text-bottom;
  white-space: nowrap;
}

marquee:is([direction="up"], [direction="down"]) {
  block-size: 200px;
  white-space: unset;
}

/* Ruby */

ruby {
  display: ruby;
}
rb {
  display: ruby-base;
  white-space: nowrap;
}
rt {
  display: ruby-text;
}
rtc {
  display: ruby-text-container;
}
rtc, rt {
  white-space: nowrap;
  font-size: 50%;
  -moz-min-font-size-ratio: 50%;
  line-height: 1;
}
@media not (-moz-platform: windows) {
  rtc, rt {
    /* The widely-used Windows font Meiryo doesn't work fine with this
    * setting, so disable this on Windows. We should re-enable it once
    * Microsoft fixes this issue. See bug 1164279. */
    font-variant-east-asian: ruby;
  }
}
rtc, rt {
  text-emphasis: none;
}
rtc:lang(zh), rt:lang(zh) {
  ruby-align: center;
}
rtc:lang(zh-TW), rt:lang(zh-TW) {
  font-size: 30%; /* bopomofo */
  -moz-min-font-size-ratio: 30%;
}
rtc > rt {
  font-size: unset;
}
ruby, rb, rt, rtc {
  unicode-bidi: isolate;
}

/* Shadow DOM v1
 * https://drafts.csswg.org/css-scoping/#slots-in-shadow-tree */
slot {
  display: contents;
}

/* Hide noscript elements if scripting is enabled */
@media (scripting) {
  noscript {
    display: none !important;
  }
}

@media print {
  input, textarea, select, button, details {
    -moz-user-input: none !important;
    pointer-events: none !important;
  }
}

/* Popover UA style, https://html.spec.whatwg.org/#flow-content-3 */
/* stylelint-disable-next-line media-query-no-invalid */
@media (-moz-bool-pref: "dom.element.popover.enabled") {
  [popover]:not(:popover-open):not(dialog[open]) {
    display:none;
  }

  dialog:popover-open {
    display:block;
  }

  [popover] {
    position: fixed;
    inset: 0;
    width: fit-content;
    height: fit-content;
    margin: auto;
    border: solid;
    padding: 0.25em;
    overflow: auto;
    color: CanvasText;
    background-color: Canvas;
  }

  :popover-open {
    -moz-top-layer: top;
  }

  :popover-open::backdrop {
    position: fixed;
    inset: 0;
    pointer-events: none !important;
    background-color: transparent;
  }
}
PK
!<t(��.(.(chrome/toolkit/res/mathml.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


/**************************************************************************/
/* namespace for MathML elements                                          */
/**************************************************************************/

@namespace url(http://www.w3.org/1998/Math/MathML);

* {
  writing-mode: horizontal-tb !important;
  font-size: math;
}

/**************************************************************************/
/* <math> - outermost math element                                        */
/**************************************************************************/

math {
  direction: ltr;
  unicode-bidi: embed;
  display: inline;
  font-size: inherit;
  font-style: normal;
  font-weight: normal;
  text-indent: 0;
  font-family: serif;
  line-height: normal;
  word-spacing: normal;
  letter-spacing: normal;
  text-rendering: optimizeLegibility;
  -moz-float-edge: margin-box;
  math-style: compact;
  math-depth: 0;
}
math[display="block" i] {
  display: block;
  text-align: -moz-center;
  math-style: normal;
}
math[display="inline" i] {
  display: inline;
  math-style: compact;
}

/**************************************************************************/
/* Links                                                                  */
/**************************************************************************/
:any-link {
  text-decoration: none !important;
}

/**************************************************************************/
/* attributes common to all tags                                          */
/**************************************************************************/

/* These attributes are mapped to style in MathMLElement.cpp:

   - background -> background                             (deprecated)
   - color -> color                                       (deprecated)
   - fontfamily -> font-family                            (deprecated)
   - fontsize -> font-size                                (deprecated)
   - fontstyle -> font-style                              (deprecated)
   - fontweight -> font-weight                            (deprecated)
   - mathvariant -> -moz-math-variant
   - scriptlevel -> math-depth
   - mathsize -> font-size
   - mathcolor -> color
   - mathbackground -> background

*/

/* Fractions */
mfrac {
  padding-inline: 1px;
}

/**************************************************************************/
/* merror                                                                 */
/**************************************************************************/

merror {
  border: 1px solid red;
  background-color: lightyellow;
}

/**************************************************************************/
/* mtable and its related tags                                            */
/**************************************************************************/

mtable {
  display: inline-table;
  border-collapse: separate;
  border-spacing: 0;
  text-indent: 0;
}
mtable[frame="none"] {
  border: none;
}
mtable[frame="solid"] {
  border: solid thin;
}
mtable[frame="dashed"] {
  border: dashed thin;
}

mtr, mlabeledtr {
  display: table-row;
  vertical-align: baseline;
}

mtd {
  display: table-cell;
  vertical-align: inherit;
  text-align: -moz-center;
  white-space: nowrap;
}

/* Don't support m(labeled)tr without mtable, nor mtd without m(labeled)tr */
:not(mtable) > mtr,
:not(mtable) > mlabeledtr,
:not(mtr, mlabeledtr) > mtd {
  display: none !important;
}

/* Hide the label because mlabeledtr is not supported yet (bug 356870). This
   rule can be overriden by users. */
mlabeledtr > mtd:first-child {
    display: none;
}

/**********************************************************************/
/* rules to achieve the default spacing between cells. When rowspacing,
   columnspacing and framespacing aren't set on mtable.  The back-end code
   will set the internal attributes depending on the cell's position.
   When they are set, the spacing behaviour is handled outside of CSS */
mtd {
  padding-right: 0.4em;  /* half of columnspacing[colindex] */
  padding-left: 0.4em;   /* half of columnspacing[colindex-1] */
  padding-bottom: 0.5ex; /* half of rowspacing[rowindex] */
  padding-top: 0.5ex;    /* half of rowspacing[rowindex-1] */
}
/* turn off the spacing at the periphery of boundary cells */
mtr:first-child > mtd {
  padding-top: 0ex;
}
mtr:last-child > mtd {
  padding-bottom: 0ex;
}
mtd:first-child {
  padding-inline-start: 0em;
}
mtd:last-child {
  padding-inline-end: 0em;
}
/* re-instate the spacing if the table has a surrounding frame */
mtable[frame="solid"] > mtr:first-child > mtd,
mtable[frame="dashed"] > mtr:first-child > mtd {
  padding-top: 0.5ex; /* framespacing.top */
}
mtable[frame="solid"] > mtr:last-child > mtd,
mtable[frame="dashed"] > mtr:last-child > mtd {
  padding-bottom: 0.5ex; /* framespacing.bottom */
}
mtable[frame="solid"] > mtr > mtd:first-child,
mtable[frame="dashed"] > mtr > mtd:first-child {
  padding-inline-start: 0.4em; /* framespacing.left (or right in rtl)*/
}
mtable[frame="solid"] > mtr > mtd:last-child,
mtable[frame="dashed"] > mtr > mtd:last-child {
  padding-inline-end: 0.4em; /* framespacing.right (or left in rtl)*/
}

mtable[rowspacing] > mtr > mtd,
mtable[columnspacing] > mtr > mtd,
mtable[framespacing] > mtr > mtd {
  /* Spacing handled outside of CSS */
  padding: 0px;
}

/**********************************************************************/
/* This is used when wrapping non-MathML inline elements inside math. */
*|*::-moz-mathml-anonymous-block {
  display: inline-block !important;
  position: static !important;
  text-indent: 0;
}

/**************************************************************************/
/* Controlling Displaystyle and Scriptlevel                               */
/**************************************************************************/

/*
  http://www.w3.org/Math/draft-spec/chapter3.html#presm.scriptlevel

  The determination of math-style for <math> involves the displaystyle
  and display attributes. See the <math> section above.
*/

/*  munder, mover and munderover change the scriptlevels of their children
   using -moz-math-increment-script-level because regular CSS rules are
   insufficient to control when the scriptlevel should be incremented. All other
   cases can be described using regular CSS, so we do it this way because it's
   more efficient and less code. */
:-moz-math-increment-script-level { math-depth: add(1); }

/*
   The mfrac element sets displaystyle to "false", or if it was already false
   increments scriptlevel by 1, within numerator and denominator.
*/
mfrac > * {
    math-depth: auto-add;
    math-style: compact;
}

/*
   The mroot element increments scriptlevel by 2, and sets displaystyle to
   "false", within index, but leaves both attributes unchanged within base.
   The msqrt element leaves both attributes unchanged within its argument.
*/
mroot > :not(:first-child) {
    math-depth: add(2);
    math-style: compact;
}

/*
   The msub element [...] increments scriptlevel by 1, and sets displaystyle to
   "false", within subscript, but leaves both attributes unchanged within base.

   The msup element [...] increments scriptlevel by 1, and sets displaystyle to
   "false", within superscript, but leaves both attributes unchanged within
   base.

   The msubsup element [...] increments scriptlevel by 1, and sets displaystyle
   to "false", within subscript and superscript, but leaves both attributes
   unchanged within base.

   The mmultiscripts element increments scriptlevel by 1, and sets displaystyle
   to "false", within each of its arguments except base, but leaves both
   attributes unchanged within base.
 */
msub > :not(:first-child),
msup > :not(:first-child),
msubsup > :not(:first-child),
mmultiscripts > :not(:first-child) {
    math-depth: add(1);
    math-style: compact;
}

/*
   The munder element [...] always sets displaystyle to "false" within the
   underscript, but increments scriptlevel by 1 only when accentunder is
   "false". Within base, it always leaves both attributes unchanged.

   The mover element [...] always sets displaystyle to "false" within
   overscript, but increments scriptlevel by 1 only when accent is "false".
   Within base, it always leaves both attributes unchanged.

   The munderover [..] always sets displaystyle to "false" within underscript
   and overscript, but increments scriptlevel by 1 only when accentunder or
   accent, respectively, are "false". Within base, it always leaves both
   attributes unchanged.
*/
munder > :not(:first-child),
mover > :not(:first-child),
munderover > :not(:first-child) {
    math-style: compact;
}

/*
   The displaystyle attribute is allowed on the mtable element to set the
   inherited value of the attribute. If the attribute is not present, the
   mtable element sets displaystyle to "false" within the table elements.
*/
mtable { math-style: compact; }

/*
   The mscarries element sets displaystyle to "false", and increments
   scriptlevel by 1, so the children are typically displayed in a smaller font.
   XXXfredw: This element is not implemented yet. See bug 534967.
mscarries {
  math-depth: add(1);
  math-style: compact;
}
*/

/* "The mphantom element renders invisibly, but with the same size and other
   dimensions, including baseline position, that its contents would have if
   they were rendered normally.".
   Also, we do not expose the <mphantom> element to the accessible tree
   (see bug 1108378). */
mphantom {
    visibility: hidden;
}

/* Implement MathML Core's semantics/maction support
   https://w3c.github.io/mathml-core/#dfn-semantics
   https://w3c.github.io/mathml-core/#dfn-maction */
maction > :not(:first-child),
semantics > :not(:first-child) {
  display: none;
}

/* stylelint-disable-next-line media-query-no-invalid */
@media (-moz-bool-pref: "mathml.legacy_mathvariant_attribute.disabled") {
  /* Implement MathML Core's automatic italic on mi.
     https://w3c.github.io/mathml-core/#the-mathvariant-attribute */
  mi {
    text-transform: math-auto;
  }
}
PK
!<��%�uuchrome/toolkit/res/noframes.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* This sheet is added to the style set for documents with frames disabled */

noframes {
  display: block;
}

frame, frameset, iframe {
  display: none !important;
}
PK
!<Jy�22chrome/toolkit/res/quirk.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url(http://www.w3.org/1999/xhtml); /* set default namespace to HTML */


/* Quirk: make orphaned LIs have inside bullet (b=1049) */

/* force inside position for orphaned lis */
li {
  list-style-position: inside;
}

/* restore outside position for lists inside LIs */
li :is(ul, ol, dir, menu) {
  list-style-position: outside;
}

/* undo previous two rules for properly nested lists */
:is(ul, ol, dir, menu) :is(ul, ol, dir, menu, li) {
  list-style-position: unset;
}


/* Quirk: ensure that we get proper padding if the very first
 * node in an LI is another UL or OL. This is an ugly way to
 * fix the problem, because it extends the LI up into what
 * would otherwise appear to be the ULs space. (b=38832) */

/* Note: this fix will fail once we implement marker box
 * alignment correctly. */
li > ul:-moz-first-node,
li > ol:-moz-first-node {
  padding-block-start: 1em;
}


table {
  text-align: start;
  white-space: normal; /* compatible with IE & spec */
  line-height: normal;

  /* Quirk: cut off all font inheritance in tables except for family. */
  font-size: initial;
  font-weight: initial;
  font-style: initial;
  font-variant: initial;
}


/* Quirk: collapse top margin of BODY and TD and bottom margin of TD */

/*
 * While it may seem simpler to use :-moz-first-node and :-moz-last-node without
 * tags, it's slower, since we have to do the :-moz-first-node or :-moz-last-node
 * check on every single element in the document.  If we list all the
 * element names for which the UA stylesheet specifies a margin, the
 * selectors will be hashed in the selector maps and things will be much more
 * efficient.
 */
:is(body, td, th) > :is(p, dl, blockquote, h1, h2, h3, h4, h5, h6, listing, plaintext, xmp, pre, ul, menu, dir, ol):-moz-first-node {
  margin-block-start: 0;
}

td > p:-moz-last-node, th > p:-moz-last-node {
  margin-block-end: 0;
}

/* Similar as above, but for empty elements
 *  collapse the bottom or top margins of empty elements
 *  - see bug 97361
 */
:is(body, td, th) > :is(p, dl, blockquote, h1, h2, h3, h4, h5, h6, listing, plaintext, xmp, pre, ul, menu, dir, ol):-moz-only-whitespace:-moz-first-node {
  margin-block-end: 0;
}

:is(td, th) > :is(p, dl, blockquote, h1, h2, h3, h4, h5, h6, listing, plaintext, xmp, pre, ul, menu, dir, ol):-moz-only-whitespace:-moz-last-node {
  margin-block-start: 0;
}

/* Quirk: Make floated images have a margin  (b=58899) */
img[align=left] {
  margin-right: 3px;
}

img[align=right] {
  margin-left: 3px;
}

/*
 * Quirk: Use border-box box sizing for text inputs, password inputs, and
 * textareas.  (b=184478 on why we use content-box sizing in standards mode)
 */

/* Note that all other <input>s already use border-box
   sizing, so we're ok with this selector */
input:not([type=image]), textarea {
  box-sizing: border-box;
}

/* Quirk: give form margin for compat (b=41806) */
form {
  margin-block-end: 1em;
}
PK
!<�n[l~~!chrome/toolkit/res/scrollbars.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* Rules required for style caching of anonymous content scrollbar parts */
/* stylelint-disable-next-line media-query-no-invalid */
@media (-moz-bool-pref: "layout.css.cached-scrollbar-styles.enabled") {
  :is(scrollcorner, resizer, scrollbar, scrollbarbutton, slider):where(:-moz-native-anonymous) {
    /* All scrollbar parts must not inherit any properties from the scrollable
     * element (except for visibility and pointer-events), for the anonymous
     * content style caching system to work.
     */
    all: initial;
    visibility: inherit;
    pointer-events: inherit;

    /* These properties are not included in 'all'. */
    -moz-context-properties: initial;
    -moz-control-character-visibility: initial;
    -moz-min-font-size-ratio: initial;
    -moz-box-collapse: initial;
    -moz-theme: initial;

    /* We don't want zoom on our ancestors to affect our styles. */
    zoom: document;

    math-depth: initial;
    /* As long as inert implies pointer-events: none as it does now, we're
     * good. */
    -moz-inert: initial;

    /* direction: initial is not sufficient, since its initial value can depend
     * on the document's language. But we specify ltr explicitly below */

    /* Similarly for font properties, whose initial values depend on the
     * document's language.  Scrollbar parts don't have any text or rely on
     * font metrics.
     */
    font: 16px sans-serif;

    /* The initial value of justify-items is `legacy`, which makes it depend on
     * the parent style.
     *
     * Reset it to something else.
     */
    justify-items: start;

    /* Avoid `object > *` rule in html.css from setting a useless, non-initial
     * value of vertical-align.
     */
    vertical-align: initial !important;
  }

  /* There are other rules that set the cursor on the scrollbar, expecting them
   * to inherit into its children. Explicitly inherit it, overriding the
   * 'all: initial;' declaration above.
   */
  :is(scrollbarbutton, slider, thumb):where(:-moz-native-anonymous) {
    cursor: inherit;
  }
}

scrollbar, scrollbarbutton, scrollcorner, slider, thumb, resizer {
  /* We need a display value that doesn't get blockified to preserve the
   * scrollbar sizing asserts. In practice it doesn't matter since these get
   * special frames */
  display: block;
  box-sizing: border-box;

  /* Our scrollbar layout uses physical coordinates, we wouldn't want an
   * horizontal scrollbar to flip in rtl for example. */
  direction: ltr;
  writing-mode: initial;

  -moz-user-focus: ignore;
  /* Prevent -moz-user-modify declaration from designmode.css having an effect. */
  -moz-user-modify: initial;
  user-select: none;
}


/********** resizer **********/

resizer {
  position: relative;
  z-index: 2147483647;

  background: url("chrome://global/skin/icons/resizer.svg") no-repeat;
  background-size: 100% 100%;
  cursor: se-resize;
  width: 15px;
  height: 15px;
}

resizer[dir="bottom"][flip],
resizer[dir="bottomleft"] {
  transform: scaleX(-1);
}

resizer[dir="bottomleft"] {
  cursor: sw-resize;
}

resizer[dir="top"],
resizer[dir="bottom"] {
  cursor: ns-resize;
}

resizer[dir="left"] {
  transform: scaleX(-1);
}

resizer[dir="left"],
resizer[dir="right"] {
  cursor: ew-resize;
}

resizer[dir="topleft"] {
  cursor: nw-resize;
}

resizer[dir="topright"] {
  cursor: ne-resize;
}

thumb {
  appearance: auto;
  -moz-default-appearance: scrollbarthumb-horizontal;
}

thumb[orient="vertical"] {
  -moz-default-appearance: scrollbarthumb-vertical;
}

scrollbar[disabled] thumb {
  visibility: hidden;
}

@media (-moz-platform: android) {
  scrollbar, resizer, scrollcorner {
    pointer-events: none;
  }
}

scrollbar {
  appearance: auto;
  -moz-default-appearance: scrollbar-horizontal;
  cursor: default;
}

scrollbar[orient="vertical"] {
  -moz-default-appearance: scrollbar-vertical;
}

scrollbar[root] {
  position: relative;
  z-index: 2147483647; /* largest positive value of a signed 32-bit integer */
}

@media (-moz-overlay-scrollbars) {
  scrollbar {
    opacity: 1;
    will-change: opacity;
    transition-property: opacity;
    transition-duration: env(-moz-overlay-scrollbar-fade-duration);
  }
  scrollbar:not([active]),
  scrollbar[disabled] {
    pointer-events: none;
    opacity: 0;
  }
  scrollcorner {
    pointer-events: none;
  }
}

slider {
  appearance: auto;
  -moz-default-appearance: scrollbartrack-horizontal;
}

slider[orient="vertical"] {
  -moz-default-appearance: scrollbartrack-vertical;
}

scrollbarbutton {
  appearance: auto;
  -moz-default-appearance: scrollbarbutton-right;
}

scrollbar[orient="vertical"] > scrollbarbutton {
  -moz-default-appearance: scrollbarbutton-down;
}

scrollbarbutton[type="decrement"] {
  -moz-default-appearance: scrollbarbutton-left;
}

scrollbar[orient="vertical"] > scrollbarbutton[type="decrement"] {
  -moz-default-appearance: scrollbarbutton-up;
}

scrollcorner {
  appearance: auto;
  -moz-default-appearance: scrollcorner;
  width: 16px;
  cursor: default;
}

@media (-moz-scrollbar-start-backward: 0) {
  scrollbarbutton[sbattr="scrollbar-up-top"] {
    display: none;
  }
}

@media (-moz-scrollbar-start-forward: 0) {
  scrollbarbutton[sbattr="scrollbar-down-top"] {
    display: none;
  }
}

@media (-moz-scrollbar-end-backward: 0) {
  scrollbarbutton[sbattr="scrollbar-up-bottom"] {
    display: none;
  }
}

@media (-moz-scrollbar-end-forward: 0) {
  scrollbarbutton[sbattr="scrollbar-down-bottom"] {
    display: none;
  }
}
PK
!<�9z3��res/svg.css/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url(http://www.w3.org/2000/svg);
@namespace xml url(http://www.w3.org/XML/1998/namespace);

style, script {
 display: none;
}

svg:not(:root), symbol, image, marker, pattern, foreignObject {
 overflow: hidden;
}

@media all and (-moz-is-glyph) {
 :root {
   fill: context-fill;
   fill-opacity: context-fill-opacity;
   stroke: context-stroke;
   stroke-opacity: context-stroke-opacity;
   stroke-width: context-value;
   stroke-dasharray: context-value;
   stroke-dashoffset: context-value;
 }
}

foreignObject {
  appearance: none ! important;
  margin: 0 ! important;
  padding: 0 ! important;
  border-width: 0 ! important;
  white-space: normal;
}

@media all and (-moz-is-resource-document) {
 foreignObject *|* {
   appearance: none !important;
 }
}

*|*::-moz-svg-foreign-content {
  display: block !important;
  /* We need to be an absolute and fixed container */
  transform: translate(0) !important;
  text-indent: 0;
}

/* Set |transform-origin:0 0;| for all SVG elements except outer-<svg>,
   noting that 'svg' as a child of 'foreignObject' counts as outer-<svg>.
*/
*:not(svg),
*:not(foreignObject) > svg {
  transform-origin:0 0;
}

*|*::-moz-svg-text {
  unicode-bidi: inherit;
  vector-effect: inherit;
}

*[xml|space=preserve] {
  white-space: -moz-pre-space;
}

*|*::-moz-svg-marker-anon-child {
  clip-path: inherit;
  filter: inherit;
  mask: inherit;
  opacity: inherit;
}

/* Make SVG shapes unselectable to avoid triggering AccessibleCaret on tap.
   <mesh> will be supported in bug 1238882. */
circle, ellipse, line, mesh, path, polygon, polyline, rect {
  user-select: none;
}

a:any-link {
  /* We don't want SVG link to be underlined */
  text-decoration: none;
}
PK
!<�H�'-'-chrome/toolkit/res/ua.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace parsererror url(http://www.mozilla.org/newlayout/xml/parsererror.xml);
@namespace html url(http://www.w3.org/1999/xhtml);
@namespace xul url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);

/* magic -- some of these rules are important to keep pages from overriding
            them
*/

/* Tables */

*|*::-moz-table {
  display: table;
  box-sizing: border-box; /* XXX do we really want this? */
}

*|*::-moz-inline-table {
  display: inline-table;
  box-sizing: border-box; /* XXX do we really want this? */
}

*|*::-moz-table-wrapper {
  /* The inherited properties here need to be safe to have on both the
   * table and the table wrapper, generally because code ignores them
   * for the table. */
  display: inherit; /* table or inline-table */
  -moz-top-layer: inherit;
  margin: inherit;
  float: inherit;
  clear: inherit;
  position: inherit;
  top: inherit;
  right: inherit;
  bottom: inherit;
  left: inherit;
  z-index: inherit;
  page-break-before: inherit;
  page-break-after: inherit;
  page-break-inside: inherit;
  vertical-align: inherit; /* needed for inline-table */
  line-height: inherit; /* needed for vertical-align on inline-table */
  /* Bug 722777 */
  transform: inherit;
  transform-origin: inherit;
  /* Bug 724750 */
  backface-visibility: inherit;
  clip: inherit;
  /* Other transform-related properties */
  /* transform-style: inherit; Bug 1560704 */
  rotate: inherit;
  scale: inherit;
  translate: inherit;
  /* When the table wrapper is a Flex/Grid item we need these: */
  align-self: inherit;
  justify-self: inherit;
  grid-column-start: inherit;
  grid-column-end: inherit;
  grid-row-start: inherit;
  grid-row-end: inherit;
  order: inherit;
  outline: inherit;
  outline-offset: inherit;
  column-span: inherit; /* needed if <table> has "column-span:all" */
  contain: inherit; /* needed if table has 'contain:layout' or 'paint' */
  container: inherit; /* Bug 1805588 */
  scroll-margin: inherit;  /* Bug 1633192 */
}

*|*::-moz-table-row {
  display: table-row;
}

/* The ::-moz-table-column pseudo-element is for extra columns at the end
   of a table. */
*|*::-moz-table-column {
  display: table-column;
  /* Make sure anonymous columns don't interfere with hit testing.  Basically,
   * they should pretend as much as possible to not exist (since in the spec
   * they do not exist).
   *
   * Please make sure to not reintroduce
   * https://bugzilla.mozilla.org/show_bug.cgi?id=1403293 if you change this
   * bit!
   */
  visibility: hidden;
}

*|*::-moz-table-column-group {
  display: table-column-group;
  /* Make sure anonymous colgroups don't interfere with hit testing.  Basically,
   * they should pretend as much as possible to not exist (since in the spec
   * they do not exist).
   *
   * Please make sure to not reintroduce
   * https://bugzilla.mozilla.org/show_bug.cgi?id=1403293 if you change this
   * bit!
   */
  visibility: hidden;
}

*|*::-moz-table-row-group {
  display: table-row-group;
}

*|*::-moz-table-cell {
  display: table-cell;
  white-space: inherit;
}

/* Ruby */
*|*::-moz-ruby {
  display: ruby;
  unicode-bidi: isolate;
}
*|*::-moz-ruby-base {
  display: ruby-base;
  unicode-bidi: isolate;
}
*|*::-moz-ruby-text {
  display: ruby-text;
  unicode-bidi: isolate;
}
*|*::-moz-ruby-base-container {
  display: ruby-base-container;
  unicode-bidi: isolate;
}
*|*::-moz-ruby-text-container {
  display: ruby-text-container;
  unicode-bidi: isolate;
}

/* https://drafts.csswg.org/css-lists-3/#ua-stylesheet */
::marker {
  text-align: end;
  text-transform: none;
  unicode-bidi: isolate;
  font-variant-numeric: tabular-nums;
  white-space: pre;
}

/* SVG documents don't always load this file but they do have links.
 * If you change the link rules, consider carefully whether to make
 * the same changes to svg.css.
 */

/* Links and focusable content */

:any-link {
  cursor: pointer;
  text-decoration: underline;
}

:link {
  color: LinkText;
}

:any-link:active {
  color: ActiveText;
}

:visited {
  color: VisitedText;
}

/* stylelint-disable-next-line media-query-no-invalid */
@media (-moz-bool-pref: "layout.css.always_underline_links") {
  :any-link {
    text-decoration: underline !important;
  }
}

:focus-visible {
  outline: 1px auto;
}

/* Inert subtrees */
:-moz-inert {
  -moz-inert: inert;
}

/* Miscellaneous */

*|*::-moz-cell-content {
  display: block;
  unicode-bidi: inherit;
  text-overflow: inherit;
  overflow: inherit;
  overflow-clip-box: inherit;
  resize: inherit;
  padding: inherit;
  box-decoration-break: inherit;
}

*|*::-moz-block-inside-inline-wrapper {
  display: block;
  /* we currently inherit from the inline that is split */
  position: inherit; /* static or relative or sticky */
  outline: inherit;
  outline-offset: inherit;
  clip-path: inherit;
  filter: inherit;
  mask: inherit;
  opacity: inherit;
  text-decoration: inherit;
  overflow-clip-box: inherit;
  unicode-bidi: inherit;
  user-select: inherit;
  text-overflow: inherit;
  /* The properties below here don't apply if our position is static,
     and we do want them to have an effect if it's not, so it's fine
     to always inherit them. */
  top: inherit;
  left: inherit;
  bottom: inherit;
  right: inherit;
  z-index: inherit;
}

*|*::-moz-scrolled-content,
*|*::-moz-scrolled-canvas {
  /* e.g., text inputs, select boxes */
  padding: inherit;
  /* The display doesn't affect the kind of frame constructed here.  This just
     affects auto-width sizing of the block we create. */
  display: block;
  /* make unicode-bidi inherit, otherwise it has no effect on text inputs and
     blocks with overflow: scroll; */
  unicode-bidi: inherit;
  text-overflow: inherit;
  /* Please keep the declarations below in sync with ::-moz-fieldset-content /
     ::-moz-button-content in forms.css */
  content: inherit;
  /* Multicol container */
  column-count: inherit;
  column-width: inherit;
  column-gap: inherit;
  column-rule: inherit;
  column-fill: inherit;
  /* Flex container */
  flex-direction: inherit;
  flex-wrap: inherit;
  /* -webkit-box container (aliased from -webkit versions to -moz versions) */
  -moz-box-orient: inherit;
  -moz-box-direction: inherit;
  -moz-box-pack: inherit;
  -moz-box-align: inherit;
  -webkit-line-clamp: inherit;
  /* Grid container */
  grid-auto-columns: inherit;
  grid-auto-rows: inherit;
  grid-auto-flow: inherit;
  grid-column-gap: inherit;
  grid-row-gap: inherit;
  grid-template-areas: inherit;
  grid-template-columns: inherit;
  grid-template-rows: inherit;
  /* CSS Align */
  align-content: inherit;
  align-items: inherit;
  justify-content: inherit;
  justify-items: inherit;
  /* Do not change these. nsCSSFrameConstructor depends on them to create a good
     frame tree. */
  overflow-clip-box: inherit;
}

*|*::-moz-viewport, *|*::-moz-viewport-scroll, *|*::-moz-canvas, *|*::-moz-scrolled-canvas {
  display: block;
  background-color: inherit;
}

*|*::-moz-viewport-scroll {
  overflow: auto;
}

*|*::-moz-column-set,
*|*::-moz-column-content {
  /* the column boxes inside a column-flowed block */
  /* make unicode-bidi inherit, otherwise it has no effect on column boxes */
  unicode-bidi: inherit;
  text-overflow: inherit;
  /* Both -moz-column-set and -moz-column-content need to be blocks. */
  display: block;
}

*|*::-moz-column-set {
  /* Inherit from ColumnSetWrapperFrame so that nsColumnSetFrame is aware of
   them.*/
  columns: inherit;
  column-gap: inherit;
  column-rule: inherit;
  column-fill: inherit;
}

*|*::-moz-column-span-wrapper {
  /* As a result of the discussion in
   * https://github.com/w3c/csswg-drafts/issues/1072, most of the styles
   * currently applied to ::-moz-block-inside-inline-wrapper should not
   * apply here. */
  display: block;
  column-span: all;
}

*|*::-moz-anonymous-item {
  /* Anonymous blocks that wrap contiguous runs of text
   * inside of a flex / grid / -moz-box container. */
  display: block;
}

*|*::-moz-page-sequence {
  /* Collection of pages in print/print preview. Visual styles may only appear
   * in print preview. */
  display: block;
  background: #606060 linear-gradient(#606060, #8a8a8a) fixed;
  print-color-adjust: exact;
  /* We always fill the available space in both directions */
  height: 100%;
  width: 100%;
}

*|*::-moz-printed-sheet {
  /* Individual sheet of paper in print/print preview. Visual styles may only
   * appear in print preview. */
  display: block;
  background: white;
  print-color-adjust: exact;
  box-shadow: 5px 5px 8px #202020;
  box-decoration-break: clone;
  /* TODO: Remove margin if viewport is small or something? */
  margin: 0.125in 0.25in;
}

@media (monochrome) and (-moz-print-preview) {
  *|*::-moz-page {
    filter: grayscale(1);
  }
}

*|*::-moz-page-content {
  display: block;
  margin: auto;
}

*|*::-moz-page-break {
  display: block;
}

/* Printing */

@media print {
  * {
    cursor: default !important;
  }
}

:fullscreen:not(:root) {
  -moz-top-layer: top !important;
  position: fixed !important;
  inset: 0 !important;
  width: 100% !important;
  height: 100% !important;
  margin: 0 !important;
  min-width: 0 !important;
  max-width: none !important;
  min-height: 0 !important;
  max-height: none !important;
  box-sizing: border-box !important;
  object-fit: contain;
  transform: none !important;
}

/* This pseudo-class is used to remove the inertness for the topmost modal
 * element in top layer.
 *
 * Avoid doing this if the element is explicitly inert though. */
:-moz-topmost-modal:not(html|*[inert]) {
  -moz-inert: none;
  /* Topmost modal elements need to be selectable even though ancestors are
   * inert, but allow users to override this if they want to. */
  user-select: text;
}

/**
 * Ensure we recompute the default color for the root based on its
 * computed color-scheme. This matches other browsers.
 *
 * For the default background, we look at the root
 * element style frame in
 * PresShell::GetDefaultBackgroundColorToDraw, however we
 * can't make the initial style (the style the root element
 * inherits from) depend on the root element's style, as that
 * is trivially cyclic.
 */
:root {
  color: CanvasText;
}

::backdrop {
  -moz-top-layer: top !important;
  display: block;
  position: fixed;
  inset: 0;
  /* This prevents undesired interactions with the selection code. */
  user-select: none;
}

:fullscreen:not(:root)::backdrop {
  background: black;
}

/* XML parse error reporting */

parsererror|parsererror {
  display: block;
  font-family: sans-serif;
  font-weight: bold;
  white-space: pre;
  margin: 1em;
  padding: 1em;
  border-width: thin;
  border-style: inset;
  border-color: red;
  font-size: 14pt;
  background-color: lightyellow;
  color: black;
}

parsererror|sourcetext {
  display: block;
  white-space: pre;
  font-family: -moz-fixed;
  margin-top: 2em;
  margin-bottom: 1em;
  color: red;
  font-weight: bold;
  font-size: 12pt;
}

/* Custom content container in the CanvasFrame, positioned on top of everything
   everything else, not reacting to pointer events. */
.moz-custom-content-container:-moz-native-anonymous {
  pointer-events: none;
  user-select: none;
  -moz-top-layer: top;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  /* Initial direction depends on the document, make sure to reset it */
  direction: ltr;
}
PK
!<�3�'ZCZC5chrome/toolkit/content/extensions/parent/ext-theme.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/* global windowTracker, EventManager, EventEmitter */

/* eslint-disable complexity */

ChromeUtils.defineESModuleGetters(this, {
  LightweightThemeManager:
    "resource://gre/modules/LightweightThemeManager.sys.mjs",
});

const onUpdatedEmitter = new EventEmitter();

// Represents an empty theme for convenience of use
const emptyTheme = {
  details: { colors: null, images: null, properties: null },
};

let defaultTheme = emptyTheme;
// Map[windowId -> Theme instance]
let windowOverrides = new Map();

/**
 * Class representing either a global theme affecting all windows or an override on a specific window.
 * Any extension updating the theme with a new global theme will replace the singleton defaultTheme.
 */
class Theme {
  /**
   * Creates a theme instance.
   *
   * @param {object} options
   * @param {string} options.extension Extension that created the theme.
   * @param {Integer} options.windowId The windowId where the theme is applied.
   * @param {object} options.details
   * @param {object} options.darkDetails
   * @param {object} options.experiment
   * @param {object} options.startupData startupData if this is a static theme.
   */
  constructor({
    extension,
    details,
    darkDetails,
    windowId,
    experiment,
    startupData,
  }) {
    this.extension = extension;
    this.details = details;
    this.darkDetails = darkDetails;
    this.windowId = windowId;

    if (startupData?.lwtData) {
      // Parsed theme from a previous load() already available in startupData
      // of parsed theme. We assume that reparsing the theme will yield the same
      // result, and therefore reuse the value of startupData. This is a minor
      // optimization; the more important use of startupData is before startup,
      // by Extension.sys.mjs for LightweightThemeManager.fallbackThemeData.
      //
      // Note: the assumption "yield the same result" is not obviously true: the
      // startupData persists across application updates, so it is possible for
      // a browser update to occur that interprets the static theme differently.
      // In this case we would still be using the old interpretation instead of
      // the new one, until the user disables and re-enables/installs the theme.
      this.lwtData = startupData.lwtData;
      this.lwtStyles = startupData.lwtStyles;
      this.lwtDarkStyles = startupData.lwtDarkStyles;
      this.experiment = startupData.experiment;
    } else {
      // lwtData will be populated by load().
      this.lwtData = null;
      // TODO(ntim): clean this in bug 1550090
      this.lwtStyles = {};
      this.lwtDarkStyles = darkDetails ? {} : null;
      this.experiment = null;
      if (experiment) {
        if (extension.canUseThemeExperiment()) {
          this.lwtStyles.experimental = {
            colors: {},
            images: {},
            properties: {},
          };
          if (this.lwtDarkStyles) {
            this.lwtDarkStyles.experimental = {
              colors: {},
              images: {},
              properties: {},
            };
          }
          const { baseURI } = this.extension;
          if (experiment.stylesheet) {
            experiment.stylesheet = baseURI.resolve(experiment.stylesheet);
          }
          this.experiment = experiment;
        } else {
          const { logger } = this.extension;
          logger.warn("This extension is not allowed to run theme experiments");
          return;
        }
      }
    }
    this.load();
  }

  /**
   * Loads a theme by reading the properties from the extension's manifest.
   * This method will override any currently applied theme.
   */
  load() {
    // this.lwtData is usually null, unless populated from startupData.
    if (!this.lwtData) {
      this.loadDetails(this.details, this.lwtStyles);
      if (this.darkDetails) {
        this.loadDetails(this.darkDetails, this.lwtDarkStyles);
      }

      this.lwtData = {
        theme: this.lwtStyles,
        darkTheme: this.lwtDarkStyles,
      };

      if (this.experiment) {
        this.lwtData.experiment = this.experiment;
      }

      if (this.extension.type === "theme") {
        // Store the parsed theme in startupData, so it is available early at
        // browser startup, to use as LightweightThemeManager.fallbackThemeData,
        // which is assigned from Extension.sys.mjs to avoid having to wait for
        // this ext-theme.js file to be loaded.
        this.extension.startupData = {
          lwtData: this.lwtData,
          lwtStyles: this.lwtStyles,
          lwtDarkStyles: this.lwtDarkStyles,
          experiment: this.experiment,
        };
        this.extension.saveStartupData();
      }
    }

    if (this.windowId) {
      this.lwtData.window = windowTracker.getWindow(
        this.windowId
      ).docShell.outerWindowID;
      windowOverrides.set(this.windowId, this);
    } else {
      windowOverrides.clear();
      defaultTheme = this;
      LightweightThemeManager.fallbackThemeData = this.lwtData;
    }
    onUpdatedEmitter.emit("theme-updated", this.details, this.windowId);

    Services.obs.notifyObservers(
      this.lwtData,
      "lightweight-theme-styling-update"
    );
  }

  /**
   * @param {object} details Details
   * @param {object} styles Styles object in which to store the colors.
   */
  loadDetails(details, styles) {
    if (details.colors) {
      this.loadColors(details.colors, styles);
    }

    if (details.images) {
      this.loadImages(details.images, styles);
    }

    if (details.properties) {
      this.loadProperties(details.properties, styles);
    }

    this.loadMetadata(this.extension, styles);
  }

  /**
   * Helper method for loading colors found in the extension's manifest.
   *
   * @param {object} colors Dictionary mapping color properties to values.
   * @param {object} styles Styles object in which to store the colors.
   */
  loadColors(colors, styles) {
    for (let color of Object.keys(colors)) {
      let val = colors[color];

      if (!val) {
        continue;
      }

      let cssColor = val;
      if (Array.isArray(val)) {
        cssColor =
          "rgb" + (val.length > 3 ? "a" : "") + "(" + val.join(",") + ")";
      }

      switch (color) {
        case "frame":
          styles.accentcolor = cssColor;
          break;
        case "frame_inactive":
          styles.accentcolorInactive = cssColor;
          break;
        case "tab_background_text":
          styles.textcolor = cssColor;
          break;
        case "toolbar":
          styles.toolbarColor = cssColor;
          break;
        case "toolbar_text":
        case "bookmark_text":
          styles.toolbar_text = cssColor;
          break;
        case "icons":
          styles.icon_color = cssColor;
          break;
        case "icons_attention":
          styles.icon_attention_color = cssColor;
          break;
        case "tab_background_separator":
        case "tab_loading":
        case "tab_text":
        case "tab_line":
        case "tab_selected":
        case "toolbar_field":
        case "toolbar_field_text":
        case "toolbar_field_border":
        case "toolbar_field_focus":
        case "toolbar_field_text_focus":
        case "toolbar_field_border_focus":
        case "toolbar_top_separator":
        case "toolbar_bottom_separator":
        case "toolbar_vertical_separator":
        case "button_background_hover":
        case "button_background_active":
        case "popup":
        case "popup_text":
        case "popup_border":
        case "popup_highlight":
        case "popup_highlight_text":
        case "ntp_background":
        case "ntp_card_background":
        case "ntp_text":
        case "sidebar":
        case "sidebar_border":
        case "sidebar_text":
        case "sidebar_highlight":
        case "sidebar_highlight_text":
        case "toolbar_field_highlight":
        case "toolbar_field_highlight_text":
          styles[color] = cssColor;
          break;
        default:
          if (
            this.experiment &&
            this.experiment.colors &&
            color in this.experiment.colors
          ) {
            styles.experimental.colors[color] = cssColor;
          } else {
            const { logger } = this.extension;
            logger.warn(`Unrecognized theme property found: colors.${color}`);
          }
          break;
      }
    }
  }

  /**
   * Helper method for loading images found in the extension's manifest.
   *
   * @param {object} images Dictionary mapping image properties to values.
   * @param {object} styles Styles object in which to store the colors.
   */
  loadImages(images, styles) {
    const { baseURI, logger } = this.extension;

    for (let image of Object.keys(images)) {
      let val = images[image];

      if (!val) {
        continue;
      }

      switch (image) {
        case "additional_backgrounds": {
          let backgroundImages = val.map(img => baseURI.resolve(img));
          styles.additionalBackgrounds = backgroundImages;
          break;
        }
        case "theme_frame": {
          let resolvedURL = baseURI.resolve(val);
          styles.headerURL = resolvedURL;
          break;
        }
        default: {
          if (
            this.experiment &&
            this.experiment.images &&
            image in this.experiment.images
          ) {
            styles.experimental.images[image] = baseURI.resolve(val);
          } else {
            logger.warn(`Unrecognized theme property found: images.${image}`);
          }
          break;
        }
      }
    }
  }

  /**
   * Helper method for preparing properties found in the extension's manifest.
   * Properties are commonly used to specify more advanced behavior of colors,
   * images or icons.
   *
   * @param {object} properties Dictionary mapping properties to values.
   * @param {object} styles Styles object in which to store the colors.
   */
  loadProperties(properties, styles) {
    let additionalBackgroundsCount =
      (styles.additionalBackgrounds && styles.additionalBackgrounds.length) ||
      0;
    const assertValidAdditionalBackgrounds = (property, valueCount) => {
      const { logger } = this.extension;
      if (!additionalBackgroundsCount) {
        logger.warn(
          `The '${property}' property takes effect only when one ` +
            `or more additional background images are specified using the 'additional_backgrounds' property.`
        );
        return false;
      }
      if (additionalBackgroundsCount !== valueCount) {
        logger.warn(
          `The amount of values specified for '${property}' ` +
            `(${valueCount}) is not equal to the amount of additional background ` +
            `images (${additionalBackgroundsCount}), which may lead to unexpected results.`
        );
      }
      return true;
    };

    for (let property of Object.getOwnPropertyNames(properties)) {
      let val = properties[property];

      if (!val) {
        continue;
      }

      switch (property) {
        case "additional_backgrounds_alignment": {
          if (!assertValidAdditionalBackgrounds(property, val.length)) {
            break;
          }

          styles.backgroundsAlignment = val.join(",");
          break;
        }
        case "additional_backgrounds_tiling": {
          if (!assertValidAdditionalBackgrounds(property, val.length)) {
            break;
          }

          let tiling = [];
          for (let i = 0, l = styles.additionalBackgrounds.length; i < l; ++i) {
            tiling.push(val[i] || "no-repeat");
          }
          styles.backgroundsTiling = tiling.join(",");
          break;
        }
        case "color_scheme":
        case "content_color_scheme": {
          styles[property] = val;
          break;
        }
        default: {
          if (
            this.experiment &&
            this.experiment.properties &&
            property in this.experiment.properties
          ) {
            styles.experimental.properties[property] = val;
          } else {
            const { logger } = this.extension;
            logger.warn(
              `Unrecognized theme property found: properties.${property}`
            );
          }
          break;
        }
      }
    }
  }

  /**
   * Helper method for loading extension metadata required by downstream
   * consumers.
   *
   * @param {object} extension Extension object.
   * @param {object} styles Styles object in which to store the colors.
   */
  loadMetadata(extension, styles) {
    styles.id = extension.id;
    styles.version = extension.version;
  }

  static unload(windowId) {
    let lwtData = {
      theme: null,
    };

    if (windowId) {
      lwtData.window = windowTracker.getWindow(windowId).docShell.outerWindowID;
      windowOverrides.delete(windowId);
    } else {
      windowOverrides.clear();
      defaultTheme = emptyTheme;
      LightweightThemeManager.fallbackThemeData = null;
    }
    onUpdatedEmitter.emit("theme-updated", {}, windowId);

    Services.obs.notifyObservers(lwtData, "lightweight-theme-styling-update");
  }
}

this.theme = class extends ExtensionAPIPersistent {
  PERSISTENT_EVENTS = {
    onUpdated({ fire, context }) {
      let callback = (event, theme, windowId) => {
        if (windowId) {
          // Force access validation for incognito mode by getting the window.
          if (windowTracker.getWindow(windowId, context, false)) {
            fire.async({ theme, windowId });
          }
        } else {
          fire.async({ theme });
        }
      };

      onUpdatedEmitter.on("theme-updated", callback);
      return {
        unregister() {
          onUpdatedEmitter.off("theme-updated", callback);
        },
        convert(_fire, _context) {
          fire = _fire;
          context = _context;
        },
      };
    },
  };

  onManifestEntry() {
    let { extension } = this;
    let { manifest } = extension;

    // Note: only static themes are processed here; extensions with the "theme"
    // permission do not enter this code path.
    defaultTheme = new Theme({
      extension,
      details: manifest.theme,
      darkDetails: manifest.dark_theme,
      experiment: manifest.theme_experiment,
      startupData: extension.startupData,
    });
    if (extension.startupData.lwtData?._processedColors) {
      // We should ideally not be modifying startupData, but we did so before,
      // before bug 1830136 was fixed. startupData persists across browser
      // updates and is only erased when the theme is updated or uninstalled.
      // To prevent this stale _processedColors from bloating the database
      // unnecessarily, we delete it here.
      delete extension.startupData.lwtData._processedColors;
      extension.saveStartupData();
    }
  }

  onShutdown(isAppShutdown) {
    if (isAppShutdown) {
      return;
    }

    let { extension } = this;
    for (let [windowId, theme] of windowOverrides) {
      if (theme.extension === extension) {
        Theme.unload(windowId);
      }
    }

    if (defaultTheme.extension === extension) {
      Theme.unload();
    }
  }

  getAPI(context) {
    let { extension } = context;

    return {
      theme: {
        getCurrent: windowId => {
          // Take last focused window when no ID is supplied.
          if (!windowId) {
            windowId = windowTracker.getId(windowTracker.topWindow);
          }
          // Force access validation for incognito mode by getting the window.
          if (!windowTracker.getWindow(windowId, context)) {
            return Promise.reject(`Invalid window ID: ${windowId}`);
          }

          if (windowOverrides.has(windowId)) {
            return Promise.resolve(windowOverrides.get(windowId).details);
          }
          return Promise.resolve(defaultTheme.details);
        },
        update: (windowId, details) => {
          if (windowId) {
            const browserWindow = windowTracker.getWindow(windowId, context);
            if (!browserWindow) {
              return Promise.reject(`Invalid window ID: ${windowId}`);
            }
          }

          new Theme({
            extension,
            details,
            windowId,
            experiment: this.extension.manifest.theme_experiment,
          });
        },
        reset: windowId => {
          if (windowId) {
            const browserWindow = windowTracker.getWindow(windowId, context);
            if (!browserWindow) {
              return Promise.reject(`Invalid window ID: ${windowId}`);
            }

            let theme = windowOverrides.get(windowId) || defaultTheme;
            if (theme.extension !== extension) {
              return;
            }
          } else if (defaultTheme.extension !== extension) {
            return;
          }

          Theme.unload(windowId);
        },
        onUpdated: new EventManager({
          context,
          module: "theme",
          event: "onUpdated",
          extensionApi: this,
        }).api(),
      },
    };
  }
};
PK
!<w�Z����>chrome/toolkit/content/extensions/parent/ext-backgroundPage.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var { ExtensionParent } = ChromeUtils.importESModule(
  "resource://gre/modules/ExtensionParent.sys.mjs"
);
var {
  HiddenExtensionPage,
  promiseBackgroundViewLoaded,
  watchExtensionWorkerContextLoaded,
} = ExtensionParent;

ChromeUtils.defineESModuleGetters(this, {
  ExtensionTelemetry: "resource://gre/modules/ExtensionTelemetry.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

ChromeUtils.defineLazyGetter(this, "serviceWorkerManager", () => {
  return Cc["@mozilla.org/serviceworkers/manager;1"].getService(
    Ci.nsIServiceWorkerManager
  );
});

XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "backgroundIdleTimeout",
  "extensions.background.idle.timeout",
  30000,
  null,
  // Minimum 100ms, max 5min
  delay => Math.min(Math.max(delay, 100), 5 * 60 * 1000)
);

// Pref used in tests to assert background page state set to
// stopped on an extension process crash.
XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "disableRestartPersistentAfterCrash",
  "extensions.background.disableRestartPersistentAfterCrash",
  false
);

// eslint-disable-next-line mozilla/reject-importGlobalProperties
Cu.importGlobalProperties(["DOMException"]);

function notifyBackgroundScriptStatus(addonId, isRunning) {
  // Notify devtools when the background scripts is started or stopped
  // (used to show the current status in about:debugging).
  const subject = { addonId, isRunning };
  Services.obs.notifyObservers(subject, "extension:background-script-status");
}

// Same as nsITelemetry msSinceProcessStartExcludingSuspend but returns
// undefined instead of throwing an extension.
function msSinceProcessStartExcludingSuspend() {
  let now;
  try {
    now = Services.telemetry.msSinceProcessStartExcludingSuspend();
  } catch (err) {
    Cu.reportError(err);
  }
  return now;
}

/**
 * Background Page state transitions:
 *
 *    ------> STOPPED <-------
 *    |         |            |
 *    |         v            |
 *    |      STARTING >------|
 *    |         |            |
 *    |         v            ^
 *    |----< RUNNING ----> SUSPENDING
 *              ^            v
 *              |------------|
 *
 * STARTING:   The background is being built.
 * RUNNING:    The background is running.
 * SUSPENDING: The background is suspending, runtime.onSuspend will be called.
 * STOPPED:    The background is not running.
 *
 * For persistent backgrounds, SUSPENDING is not used.
 *
 * See BackgroundContextOwner for the exact relation.
 */
const BACKGROUND_STATE = {
  STARTING: "starting",
  RUNNING: "running",
  SUSPENDING: "suspending",
  STOPPED: "stopped",
};

// Responsible for the background_page section of the manifest.
class BackgroundPage extends HiddenExtensionPage {
  constructor(extension, options) {
    super(extension, "background");

    this.page = options.page || null;
    this.isGenerated = !!options.scripts;

    // Last background/event page created time (retrieved using
    // Services.telemetry.msSinceProcessStartExcludingSuspend when the
    // parent process proxy context has been created).
    this.msSinceCreated = null;

    if (this.page) {
      this.url = this.extension.baseURI.resolve(this.page);
    } else if (this.isGenerated) {
      this.url = this.extension.baseURI.resolve(
        "_generated_background_page.html"
      );
    }
  }

  async build() {
    const { extension } = this;
    ExtensionTelemetry.backgroundPageLoad.stopwatchStart(extension, this);

    let context;
    try {
      await this.createBrowserElement();
      if (!this.browser) {
        throw new Error(
          "Extension shut down before the background page was created"
        );
      }
      extension._backgroundPageFrameLoader = this.browser.frameLoader;

      extensions.emit("extension-browser-inserted", this.browser);

      let contextPromise = promiseBackgroundViewLoaded(this.browser);
      this.browser.fixupAndLoadURIString(this.url, {
        triggeringPrincipal: extension.principal,
      });

      context = await contextPromise;
      // NOTE: context can be null if the load failed.

      this.msSinceCreated = msSinceProcessStartExcludingSuspend();

      ExtensionTelemetry.backgroundPageLoad.stopwatchFinish(extension, this);
    } catch (e) {
      // Extension was down before the background page has loaded.
      ExtensionTelemetry.backgroundPageLoad.stopwatchCancel(extension, this);
      throw e;
    }

    return context;
  }

  shutdown() {
    this.extension._backgroundPageFrameLoader = null;
    super.shutdown();
  }
}

// Responsible for the background.service_worker section of the manifest.
class BackgroundWorker {
  constructor(extension, options) {
    this.extension = extension;
    this.workerScript = options.service_worker;

    if (!this.workerScript) {
      throw new Error("Missing mandatory background.service_worker property");
    }
  }

  get registrationInfo() {
    const { principal } = this.extension;
    return serviceWorkerManager.getRegistrationForAddonPrincipal(principal);
  }

  getWorkerInfo(descriptorId) {
    return this.registrationInfo?.getWorkerByID(descriptorId);
  }

  validateWorkerInfoForContext(context) {
    const { extension } = this;
    if (!this.getWorkerInfo(context.workerDescriptorId)) {
      throw new Error(
        `ServiceWorkerInfo not found for ${extension.policy.debugName} contextId ${context.contextId}`
      );
    }
  }

  async build() {
    const { extension } = this;
    let context;
    const contextPromise = new Promise(resolve => {
      // TODO bug 1844486: resolve and/or unwatch when startup is interrupted.
      let unwatch = watchExtensionWorkerContextLoaded(
        { extension, viewType: "background_worker" },
        context => {
          unwatch();
          this.validateWorkerInfoForContext(context);
          resolve(context);
        }
      );
    });

    // TODO(Bug 17228327): follow up to spawn the active worker for a previously installed
    // background service worker.
    await serviceWorkerManager.registerForAddonPrincipal(
      this.extension.principal
    );

    // TODO bug 1844486: Confirm that a shutdown() call during the above or
    // below `await` calls can interrupt build() without leaving a stray worker
    // registration behind.

    context = await contextPromise;

    await this.waitForActiveWorker();
    return context;
  }

  shutdown(isAppShutdown) {
    // All service worker registrations related to the extensions will be unregistered
    // - when the extension is shutting down if the application is not also shutting down
    //   shutdown (in which case a previously registered service worker is expected to stay
    //   active across browser restarts).
    // - when the extension has been uninstalled
    if (!isAppShutdown) {
      this.registrationInfo?.forceShutdown();
    }
  }

  waitForActiveWorker() {
    const { extension, registrationInfo } = this;
    return new Promise((resolve, reject) => {
      const resolveOnActive = () => {
        if (
          registrationInfo.activeWorker?.state ===
          Ci.nsIServiceWorkerInfo.STATE_ACTIVATED
        ) {
          resolve();
          return true;
        }
        return false;
      };

      const rejectOnUnregistered = () => {
        if (registrationInfo.unregistered) {
          reject(
            new Error(
              `Background service worker unregistered for "${extension.policy.debugName}"`
            )
          );
          return true;
        }
        return false;
      };

      if (resolveOnActive() || rejectOnUnregistered()) {
        return;
      }

      const listener = {
        onChange() {
          if (resolveOnActive() || rejectOnUnregistered()) {
            registrationInfo.removeListener(listener);
          }
        },
      };
      registrationInfo.addListener(listener);
    });
  }
}

/**
 * The BackgroundContextOwner is instantiated at most once per extension and
 * tracks the state of the background context. State changes can be triggered
 * by explicit calls to methods with the "setBgState" prefix, but also by the
 * background context itself, e.g. via an extension process crash.
 *
 * This class identifies the following stages of interest:
 *
 * 1. Initially no active background, waiting for a signal to get started.
 *    - method: none (at constructor and after setBgStateStopped)
 *    - state: STOPPED
 *    - context: null
 * 2. Parent-triggered background startup
 *    - method: setBgStateStarting
 *    - state: STARTING (was STOPPED)
 *    - context: null
 * 3. Background context creation observed in parent
 *    - method: none (observed by ExtensionParent's recvCreateProxyContext)
 *      TODO: add method to observe and keep track of it sooner than stage 4.
 *    - state: STARTING
 *    - context: ProxyContextParent subclass (was null)
 * 4. Parent-observed background startup completion
 *    - method: setBgStateRunning
 *    - state: RUNNING (was STARTING)
 *    - context: ProxyContextParent (was null)
 * 5. Background context unloaded for any reason
 *    - method: setBgStateStopped
 *      TODO bug 1844217: This is only implemented for process crashes and
 *          intentionally triggered terminations, not navigations/reloads.
 *          When unloads happen due to navigations/reloads, context will be
 *          null but the state will still be RUNNING.
 *    - state: STOPPED (was STOPPED, STARTING, RUNNING or SUSPENDING)
 *    - context: null (was ProxyContextParent if stage 4 ran).
 *    - Continue at stage 1 if the extension has not shut down yet.
 */
class BackgroundContextOwner {
  /**
   * @property {BackgroundBuilder} backgroundBuilder
   *
   * The source of parent-triggered background state changes.
   */
  backgroundBuilder;

  /**
   * @property {Extension} [extension]
   *
   * The Extension associated with the background. This is always set and
   * cleared at extension shutdown.
   */
  extension;

  /**
   * @property {BackgroundPage|BackgroundWorker} [bgInstance]
   *
   * The BackgroundClass instance responsible for creating the background
   * context. This is set as soon as there is a desire to start a background,
   * and cleared as soon as the background context is not wanted any more.
   *
   * This field is set iff extension.backgroundState is not STOPPED.
   */
  bgInstance = null;

  /**
   * @property {ExtensionPageContextParent|BackgroundWorkerContextParent} [context]
   *
   * The parent-side counterpart to a background context in a child. The value
   * is a subclass of ProxyContextParent, which manages its own lifetime. The
   * class is ultimately instantiated through bgInstance. It can be destroyed by
   * bgInstance or externally (e.g. by the context itself or a process crash).
   * The reference to the context is cleared as soon as the context is unloaded.
   *
   * This is currently set when the background has fully loaded. To access the
   * background context before that, use |extension.backgroundContext|.
   *
   * This field is set when extension.backgroundState is RUNNING or SUSPENDING.
   */
  context = null;

  /**
   * @property {boolean} [canBePrimed]
   *
   * This property reflects whether persistent listeners can be primed. This
   * means that `backgroundState` is `STOPPED` and the listeners haven't been
   * primed yet. It is initially `true`, and set to `false` as soon as
   * listeners are primed. It can become `true` again if `primeBackground` was
   * skipped due to `shouldPrimeBackground` being `false`.
   * NOTE: this flag is set for both event pages and persistent background pages.
   */
  canBePrimed = true;

  /**
   * @property {boolean} [shouldPrimeBackground]
   *
   * This property controls whether we should prime listeners. Under normal
   * conditions, this should always be `true` but when too many crashes have
   * occurred, we might have to disable process spawning, which would lead to
   * this property being set to `false`.
   */
  shouldPrimeBackground = true;

  get #hasEnteredShutdown() {
    // This getter is just a small helper to make sure we always check for
    // the extension shutdown being already initiated.
    // Ordinarily the extension object is expected to be nullified from the
    // onShutdown method, but extension.hasShutdown is set earlier and because
    // the shutdown goes through some async steps there is a chance for other
    // internals to be hit while the hasShutdown flag is set bug onShutdown
    // not hit yet.
    return this.extension.hasShutdown || Services.startup.shuttingDown;
  }

  /**
   * @param {BackgroundBuilder} backgroundBuilder
   * @param {Extension} extension
   */
  constructor(backgroundBuilder, extension) {
    this.backgroundBuilder = backgroundBuilder;
    this.extension = extension;
    this.onExtensionProcessCrashed = this.onExtensionProcessCrashed.bind(this);
    this.onApplicationInForeground = this.onApplicationInForeground.bind(this);
    this.onExtensionEnableProcessSpawning =
      this.onExtensionEnableProcessSpawning.bind(this);

    extension.backgroundState = BACKGROUND_STATE.STOPPED;

    extensions.on("extension-process-crash", this.onExtensionProcessCrashed);
    extensions.on(
      "extension-enable-process-spawning",
      this.onExtensionEnableProcessSpawning
    );
    // We only defer handling extension process crashes for persistent
    // background context.
    if (extension.persistentBackground) {
      extensions.on("application-foreground", this.onApplicationInForeground);
    }
  }

  /**
   * setBgStateStarting - right before the background context is initialized.
   *
   * @param {BackgroundWorker|BackgroundPage} bgInstance
   */
  setBgStateStarting(bgInstance) {
    if (!this.extension) {
      throw new Error(`Cannot start background after extension shutdown.`);
    }
    if (this.bgInstance) {
      throw new Error(`Cannot start multiple background instances`);
    }
    this.extension.backgroundState = BACKGROUND_STATE.STARTING;
    this.bgInstance = bgInstance;
    // Often already false, except if we're waking due to a listener that was
    // registered with isInStartup=true.
    this.canBePrimed = false;
  }

  /**
   * setBgStateRunning - when the background context has fully loaded.
   *
   * This method may throw if the background should no longer be active; if that
   * is the case, the caller should make sure that the background is cleaned up
   * by calling setBgStateStopped.
   *
   * @param {ExtensionPageContextParent|BackgroundWorkerContextParent} context
   */
  setBgStateRunning(context) {
    if (!this.extension) {
      // Caller should have checked this.
      throw new Error(`Extension has shut down before startup completion.`);
    }
    if (this.context) {
      // This can currently not happen - we set the context only once.
      // TODO bug 1844217: Handle navigation (bug 1286083). For now, reject.
      throw new Error(`Context already set before at startup completion.`);
    }
    if (!context) {
      throw new Error(`Context not found at startup completion.`);
    }
    if (context.unloaded) {
      throw new Error(`Context has unloaded before startup completion.`);
    }
    this.extension.backgroundState = BACKGROUND_STATE.RUNNING;
    this.context = context;
    context.callOnClose(this);

    // When the background startup completes successfully, update the set of
    // events that should be persisted.
    EventManager.clearPrimedListeners(this.extension, true);

    // This notification will be balanced in setBgStateStopped / close.
    notifyBackgroundScriptStatus(this.extension.id, true);

    this.extension.emit("background-script-started");
  }

  /**
   * setBgStateStopped - when the background context has unloaded or should be
   * unloaded. Regardless of the actual state at the entry of this method, upon
   * returning the background is considered stopped.
   *
   * If the context was active at the time of the invocation, the actual unload
   * of |this.context| is asynchronous as it may involve a round-trip to the
   * child process.
   *
   * @param {boolean} [isAppShutdown]
   */
  setBgStateStopped(isAppShutdown) {
    const backgroundState = this.extension.backgroundState;
    if (this.context) {
      this.context.forgetOnClose(this);
      this.context = null;
      // This is the counterpart to the notification in setBgStateRunning.
      notifyBackgroundScriptStatus(this.extension.id, false);
    }

    // We only need to call clearPrimedListeners for states STOPPED and STARTING
    // because setBgStateRunning clears all primed listeners when it switches
    // from STARTING to RUNNING. Further, the only way to get primed listeners
    // is by a primeListeners call, which only happens in the STOPPED state.
    if (
      backgroundState === BACKGROUND_STATE.STOPPED ||
      backgroundState === BACKGROUND_STATE.STARTING
    ) {
      EventManager.clearPrimedListeners(this.extension, false);
    }

    // Ensure any idle background timer is not running.
    this.backgroundBuilder.idleManager.clearState();

    const bgInstance = this.bgInstance;
    if (bgInstance) {
      this.bgInstance = null;
      isAppShutdown ||= Services.startup.shuttingDown;
      // bgInstance.shutdown() unloads the associated context, if any.
      bgInstance.shutdown(isAppShutdown);
      this.backgroundBuilder.onBgInstanceShutdown(bgInstance);
    }

    this.extension.backgroundState = BACKGROUND_STATE.STOPPED;
    if (backgroundState === BACKGROUND_STATE.STARTING) {
      this.extension.emit("background-script-aborted");
    }

    if (this.extension.hasShutdown) {
      this.extension = null;
    } else if (this.shouldPrimeBackground) {
      // Prime again, so that a stopped background can always be revived when
      // needed.
      this.backgroundBuilder.primeBackground(false);
    } else {
      this.canBePrimed = true;
    }
  }

  // Called by registration via context.callOnClose (if this.context is set).
  close() {
    // close() is called when:
    // - background context unloads (without replacement context).
    // - extension process crashes (without replacement context).
    // - background context reloads (context likely replaced by new context).
    // - background context navigates (context likely replaced by new context).
    //
    // When the background is gone without replacement, switch to STOPPED.
    // TODO bug 1286083: Drop support for navigations.

    // To fully maintain the state, we should call this.setBgStateStopped();
    // But we cannot do that yet because that would close background pages upon
    // reload and navigation, which would be a backwards-incompatible change.
    // For now, we only do the bare minimum here.
    //
    // Note that once a navigation or reload starts, that the context is
    // untracked. This is a pre-existing issue that we should fix later.
    // TODO bug 1844217: Detect context replacement and update this.context.
    if (this.context) {
      this.context.forgetOnClose(this);
      this.context = null;
      // This is the counterpart to the notification in setBgStateRunning.
      notifyBackgroundScriptStatus(this.extension.id, false);
    }
  }

  restartPersistentBackgroundAfterCrash() {
    const { extension } = this;
    if (
      this.#hasEnteredShutdown ||
      // Ignore if the background state isn't the one expected to be set
      // after a crash.
      extension.backgroundState !== BACKGROUND_STATE.STOPPED ||
      // Auto-restart persistent background scripts after crash disabled by prefs.
      disableRestartPersistentAfterCrash
    ) {
      return;
    }

    // Persistent background pages are re-primed from setBgStateStopped when we
    // are hitting a crash (if the threshold was not exceeded, otherwise they
    // are going to be re-primed from onExtensionEnableProcessSpawning).
    extension.emit("start-background-script");
  }

  onExtensionEnableProcessSpawning() {
    if (this.#hasEnteredShutdown) {
      return;
    }

    if (!this.canBePrimed) {
      return;
    }

    // Allow priming again.
    this.shouldPrimeBackground = true;
    this.backgroundBuilder.primeBackground(false);

    if (this.extension.persistentBackground) {
      this.restartPersistentBackgroundAfterCrash();
    }
  }

  onApplicationInForeground(eventName, data) {
    if (
      this.#hasEnteredShutdown ||
      // Past the silent crash handling threashold.
      data.processSpawningDisabled
    ) {
      return;
    }

    this.restartPersistentBackgroundAfterCrash();
  }

  onExtensionProcessCrashed(eventName, data) {
    if (this.#hasEnteredShutdown) {
      return;
    }

    // data.childID holds the process ID of the crashed extension process.
    // For now, assume that there is only one, so clean up unconditionally.

    this.shouldPrimeBackground = !data.processSpawningDisabled;

    // We only need to clean up if a bgInstance has been created. Without it,
    // there is only state in the parent process, not the child, and a crashed
    // extension process doesn't affect us.
    if (this.bgInstance) {
      this.setBgStateStopped();
    }

    if (this.extension.persistentBackground) {
      // Defer to when back in foreground and/or process spawning is explicitly re-enabled.
      if (!data.appInForeground || data.processSpawningDisabled) {
        return;
      }

      this.restartPersistentBackgroundAfterCrash();
    }
  }

  // Called by ExtensionAPI.onShutdown (once).
  onShutdown(isAppShutdown) {
    // If a background context was active during extension shutdown, then
    // close() was called before onShutdown, which clears |this.extension|.
    // If the background has not fully started yet, then we have to clear here.
    if (this.extension) {
      this.setBgStateStopped(isAppShutdown);
    }
    extensions.off("extension-process-crash", this.onExtensionProcessCrashed);
    extensions.off(
      "extension-enable-process-spawning",
      this.onExtensionEnableProcessSpawning
    );
    extensions.off("application-foreground", this.onApplicationInForeground);
  }
}

/**
 * BackgroundBuilder manages the creation and parent-triggered termination of
 * the background context. Non-parent-triggered terminations are usually due to
 * an external cause (e.g. crashes) and detected by BackgroundContextOwner.
 *
 * Because these external terminations can happen at any time, and the creation
 * and suspension of the background context is async, the methods of this
 * BackgroundBuilder class necessarily need to check the state of the background
 * before proceeding with the operation (and abort + clean up as needed).
 *
 * The following interruptions are explicitly accounted for:
 * - Extension shuts down.
 * - Background unloads for any reason.
 * - Another background instance starts in the meantime.
 */
class BackgroundBuilder {
  constructor(extension) {
    this.extension = extension;
    this.backgroundContextOwner = new BackgroundContextOwner(this, extension);
    this.idleManager = new IdleManager(extension);
  }

  async build() {
    if (this.backgroundContextOwner.bgInstance) {
      return;
    }

    let { extension } = this;
    let { manifest } = extension;
    extension.backgroundState = BACKGROUND_STATE.STARTING;

    this.isWorker =
      !!manifest.background.service_worker &&
      WebExtensionPolicy.backgroundServiceWorkerEnabled;

    let BackgroundClass = this.isWorker ? BackgroundWorker : BackgroundPage;

    const bgInstance = new BackgroundClass(extension, manifest.background);
    this.backgroundContextOwner.setBgStateStarting(bgInstance);
    let context;
    try {
      context = await bgInstance.build();
    } catch (e) {
      Cu.reportError(e);
      // If background startup gets interrupted (e.g. extension shutdown),
      // bgInstance.shutdown() is called and backgroundContextOwner.bgInstance
      // is cleared.
      if (this.backgroundContextOwner.bgInstance === bgInstance) {
        this.backgroundContextOwner.setBgStateStopped();
      }
      return;
    }

    if (context) {
      // Wait until all event listeners registered by the script so far
      // to be handled. We then set listenerPromises to null, which indicates
      // to addListener that the background script has finished loading.
      await Promise.all(context.listenerPromises);
      context.listenerPromises = null;
    }

    if (this.backgroundContextOwner.bgInstance !== bgInstance) {
      // Background closed/restarted in the meantime.
      return;
    }

    try {
      this.backgroundContextOwner.setBgStateRunning(context);
    } catch (e) {
      Cu.reportError(e);
      this.backgroundContextOwner.setBgStateStopped();
    }
  }

  primeBackground(isInStartup = true) {
    let { extension } = this;

    if (this.backgroundContextOwner.bgInstance) {
      // This should never happen. The need to prime listeners is mutually
      // exclusive with the existence of a background instance.
      throw new Error(`bgInstance exists before priming ${extension.id}`);
    }

    // Used by runtime messaging to wait for background page listeners.
    let bgStartupPromise = new Promise(resolve => {
      let done = () => {
        extension.off("background-script-started", done);
        extension.off("background-script-aborted", done);
        extension.off("shutdown", done);
        resolve();
      };
      extension.on("background-script-started", done);
      extension.on("background-script-aborted", done);
      extension.on("shutdown", done);
    });

    extension.promiseBackgroundStarted = () => {
      return bgStartupPromise;
    };

    extension.wakeupBackground = () => {
      if (extension.hasShutdown) {
        return Promise.reject(
          new Error(
            "wakeupBackground called while the extension was already shutting down"
          )
        );
      }
      extension.emit("background-script-event");
      // `extension.wakeupBackground` is set back to the original arrow function
      // when the background page is terminated and `primeBackground` is called again.
      extension.wakeupBackground = () => bgStartupPromise;
      return bgStartupPromise;
    };

    let resetBackgroundIdle = (event, { reason }) => {
      if (
        extension.backgroundState == BACKGROUND_STATE.SUSPENDING &&
        // After we begin suspending the background, parent API calls from
        // runtime.onSuspend listeners shouldn't cancel the suspension.
        reason !== "parentapicall"
      ) {
        extension.backgroundState = BACKGROUND_STATE.RUNNING;
        extension.emit("background-script-suspend-canceled");
      }

      if (extension.backgroundState !== BACKGROUND_STATE.RUNNING) {
        // If STOPPED (or STARTING), then there is no idle timer and we should
        // not reset the timer, because that would actually start the timer that
        // eventually calls terminateBackground(), see bug 1905505 for example.
        //
        // When at state STARTING, we expect the startup to complete soon which
        // in turn will start the idleManager timer.
        if (
          extension.backgroundState === BACKGROUND_STATE.STOPPED &&
          // Skip logging for "event" because it can currently be encountered in
          // practice due to bug 1905504, as explained in bug 1905505.
          reason !== "event"
        ) {
          Cu.reportError(
            `Background keepalive reset with reason "${reason}" failed for ${extension.id}, state stopped.`
          );
        }
        return;
      }

      this.idleManager.resetTimer();

      if (this.isWorker) {
        // TODO(Bug 1790087): record similar telemetry for service workers.
        return;
      }
      if (reason === "event" || reason === "parentapicall") {
        // Bug 1868960: not recording these because too frequent.
        return;
      }

      // Keep in sync with categories in WEBEXT_EVENTPAGE_IDLE_RESULT_COUNT.
      let KNOWN = ["nativeapp", "streamfilter", "listeners"];
      ExtensionTelemetry.eventPageIdleResult.histogramAdd({
        extension,
        category: `reset_${KNOWN.includes(reason) ? reason : "other"}`,
      });
    };

    let idleWaitUntil = (_, { promise, reason }) => {
      if (extension.backgroundState === BACKGROUND_STATE.STOPPED) {
        // Sanity check: do not start idleManager.waitUntil if we are already
        // stopped. The purpose of waitUntil is to keep the background alive,
        // but if it was already stopped we cannot revive. At worst, we could
        // interfere with a future bgInstance.
        Cu.reportError(
          `Background keepalive with reason "${reason}" failed for ${extension.id}, state stopped.`
        );
        return;
      }
      this.idleManager.waitUntil(promise, reason);
    };

    if (!extension.persistentBackground) {
      // Listen for events from the EventManager
      extension.on("background-script-reset-idle", resetBackgroundIdle);

      // After the background is started, initiate the first timer
      extension.once("background-script-started", () => {
        this.idleManager.resetTimer();
      });

      extension.on("background-script-idle-waituntil", idleWaitUntil);
    }

    // TODO bug 1844488: terminateBackground should account for externally
    // triggered background restarts. It does currently performs various
    // backgroundState checks, but it is possible for the background to have
    // been crashes or restarted in the meantime.
    extension.terminateBackground = async ({
      ignoreDevToolsAttached = false,
      disableResetIdleForTest = false, // Disable all reset idle checks for testing purpose.
    } = {}) => {
      if (extension.backgroundState === BACKGROUND_STATE.STOPPED) {
        Cu.reportError(
          `Cannot terminate background of ${extension.id} because it was already stopped.`
        );
        // If we were to continue we'd wait until the next background startup
        // and terminate immediately, which is undesired.
        return;
      }
      await bgStartupPromise;
      if (!this.extension || this.extension.hasShutdown) {
        // Extension was already shut down.
        return;
      }
      if (extension.backgroundState != BACKGROUND_STATE.RUNNING) {
        return;
      }

      if (
        !ignoreDevToolsAttached &&
        ExtensionParent.DebugUtils.hasDevToolsAttached(extension.id)
      ) {
        extension.emit("background-script-suspend-ignored");
        return;
      }

      // Similar to what happens in recent Chrome version for MV3 extensions, extensions non-persistent
      // background scripts with a nativeMessaging port still open or a sendNativeMessage request still
      // pending an answer are exempt from being terminated when the idle timeout expires.
      // The motivation, as for the similar change that Chrome applies to MV3 extensions, is that using
      // the native messaging API have already an higher barrier due to having to specify a native messaging
      // host app in their manifest and the user also have to install the native app separately as a native
      // application).
      if (
        !disableResetIdleForTest &&
        extension.backgroundContext?.hasActiveNativeAppPorts
      ) {
        extension.emit("background-script-reset-idle", { reason: "nativeapp" });
        return;
      }

      if (
        !disableResetIdleForTest &&
        extension.backgroundContext?.pendingRunListenerPromisesCount
      ) {
        extension.emit("background-script-reset-idle", {
          reason: "listeners",
          pendingListeners:
            extension.backgroundContext.pendingRunListenerPromisesCount,
        });
        // Clear the pending promises being tracked when we have reset the idle
        // once because some where still pending, so that the pending listeners
        // calls can reset the idle timer only once.
        extension.backgroundContext.clearPendingRunListenerPromises();
        return;
      }

      const childId = extension.backgroundContext?.childId;
      if (
        childId !== undefined &&
        extension.hasPermission("webRequestBlocking") &&
        (extension.manifestVersion <= 3 ||
          extension.hasPermission("webRequestFilterResponse"))
      ) {
        // Ask to the background page context in the child process to check if there are
        // StreamFilter instances active (e.g. ones with status "transferringdata" or "suspended",
        // see StreamFilterStatus enum defined in StreamFilter.webidl).
        // TODO(Bug 1748533): consider additional changes to prevent a StreamFilter that never gets to an
        // inactive state from preventing an even page from being ever suspended.
        const hasActiveStreamFilter =
          await ExtensionParent.ParentAPIManager.queryStreamFilterSuspendCancel(
            extension.backgroundContext.childId
          ).catch(err => {
            // an AbortError raised from the JSWindowActor is expected if the background page was already been
            // terminated in the meantime, and so we only log the errors that don't match these particular conditions.
            if (
              extension.backgroundState == BACKGROUND_STATE.STOPPED &&
              DOMException.isInstance(err) &&
              err.name === "AbortError"
            ) {
              return false;
            }
            Cu.reportError(err);
            return false;
          });
        if (!disableResetIdleForTest && hasActiveStreamFilter) {
          extension.emit("background-script-reset-idle", {
            reason: "streamfilter",
          });
          return;
        }

        // Return earlier if extension have started or completed its shutdown in the meantime.
        if (
          extension.backgroundState !== BACKGROUND_STATE.RUNNING ||
          extension.hasShutdown
        ) {
          return;
        }
      }

      extension.backgroundState = BACKGROUND_STATE.SUSPENDING;
      this.idleManager.clearState();
      // call runtime.onSuspend
      await extension.emit("background-script-suspend");
      // If in the meantime another event fired, state will be RUNNING,
      // and if it was shutdown it will be STOPPED.
      if (extension.backgroundState != BACKGROUND_STATE.SUSPENDING) {
        return;
      }
      extension.off("background-script-reset-idle", resetBackgroundIdle);
      extension.off("background-script-idle-waituntil", idleWaitUntil);

      // TODO(Bug 1790087): record similar telemetry for background service worker.
      if (!this.isWorker) {
        ExtensionTelemetry.eventPageIdleResult.histogramAdd({
          extension,
          category: "suspend",
        });
      }

      this.backgroundContextOwner.setBgStateStopped(false);
    };

    EventManager.primeListeners(extension, isInStartup);
    // Avoid setting the flag to false when called during extension startup.
    if (!isInStartup) {
      this.backgroundContextOwner.canBePrimed = false;
    }

    // TODO: start-background-script and background-script-event should be
    // unregistered when build() starts or when the extension shuts down.
    extension.once("start-background-script", async () => {
      if (!this.extension || this.extension.hasShutdown) {
        // Extension was shut down. Don't build the background page.
        // Primed listeners have been cleared in onShutdown.
        return;
      }
      await this.build();
    });

    // There are two ways to start the background page:
    // 1. If a primed event fires, then start the background page as
    //    soon as we have painted a browser window.
    // 2. After all windows have been restored on startup (see onManifestEntry).
    extension.once("background-script-event", async () => {
      await ExtensionParent.browserPaintedPromise;
      extension.emit("start-background-script");
    });
  }

  onBgInstanceShutdown(bgInstance) {
    const { msSinceCreated } = bgInstance;
    const { extension } = this;

    // Emit an event for tests.
    extension.emit("shutdown-background-script");

    if (msSinceCreated) {
      const now = msSinceProcessStartExcludingSuspend();
      if (
        now &&
        // TODO(Bug 1790087): record similar telemetry for background service worker.
        !(this.isWorker || extension.persistentBackground)
      ) {
        ExtensionTelemetry.eventPageRunningTime.histogramAdd({
          extension,
          value: now - msSinceCreated,
        });
      }
    }
  }
}

/**
 * Times the suspension of the background page, acts like a 3-state machine:
 *  - suspended (or uninitialized)
 *  - waiting for a timeout (now() < sleepTime)
 *  - waiting on a promise (keepAlive.size > 0)
 *
 * When suspended, backgroundState is STOPPED and IdleManager does not have any
 * pending timers or termination requests. The clearState() in setBgStateStopped
 * ensures this.
 *
 * When backgroundState is in STARTING, waitUntil() may be called to register a
 * termination blocker since the blocker may be relevant at the next state
 * transition, to RUNNING.
 *
 * When backgroundState is in RUNNING, resetTimer() can be called for the first
 * time to start the countdown to termination. resetTimer() may be called
 * repeatedly to postpone termination.
 *
 * Eventually, if the timer will fire and call terminateBackground(), which
 * initiates the transition from RUNNING to SUSPENDING to STOPPED.
 */
var IdleManager = class IdleManager {
  sleepTime = 0;
  /** @type {nsITimer} */
  timer = null;
  /** @type {Map<promise, string>} */
  keepAlive = new Map();

  constructor(extension) {
    this.extension = extension;
  }

  waitUntil(originalPromise, reason) {
    // Wrap the passed in promise so that we can resolve our .finally() handler
    // in clearState() below, while also not keeping the originalPromise alive.
    let { promise, resolve } = Promise.withResolvers();
    originalPromise.finally(() => resolve());
    let start = Cu.now();

    this.keepAlive.set(promise, { reason, resolve });
    promise.finally(() => {
      if (
        this.keepAlive.delete(promise) &&
        !this.keepAlive.size &&
        this.extension.backgroundState === BACKGROUND_STATE.RUNNING
      ) {
        this.resetTimer();
      }

      if (Cu.now() - start > backgroundIdleTimeout) {
        ExtensionTelemetry.eventPageIdleResult.histogramAdd({
          extension: this.extension,
          category: reason,
          value: Math.round((Cu.now() - start) / backgroundIdleTimeout),
        });
      }
    });
  }

  clearState() {
    for (let { resolve } of this.keepAlive.values()) {
      resolve();
    }
    this.keepAlive.clear();
    this.clearTimer();
  }

  clearTimer() {
    this.timer?.cancel();
    this.timer = null;
  }

  resetTimer() {
    this.sleepTime = Cu.now() + backgroundIdleTimeout;
    if (!this.timer) {
      this.createTimer();
    }
  }

  createTimer() {
    let timeLeft = this.sleepTime - Cu.now();
    this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    this.timer.init(() => this.timeout(), timeLeft, Ci.nsITimer.TYPE_ONE_SHOT);
  }

  timeout() {
    this.clearTimer();
    if (!this.keepAlive.size) {
      if (Cu.now() < this.sleepTime) {
        this.createTimer();
      } else {
        // As explained in the comment before the IdleManager class, the timer
        // is only scheduled while in the RUNNING state. Whenever the background
        // transitions to another state (SUSPENDING or STOPPED), the timer is
        // canceled and we won't reach this point.
        this.extension.terminateBackground();
      }
    }
  }
};

this.backgroundPage = class extends ExtensionAPI {
  async onManifestEntry() {
    let { extension } = this;

    // When in PPB background pages all run in a private context.  This check
    // simply avoids an extraneous error in the console since the BaseContext
    // will throw.
    if (
      PrivateBrowsingUtils.permanentPrivateBrowsing &&
      !extension.privateBrowsingAllowed
    ) {
      return;
    }

    this.backgroundBuilder = new BackgroundBuilder(extension);

    // runtime.onStartup event support.  We listen for the first
    // background startup then emit a first-run event.
    extension.once("background-script-started", () => {
      extension.emit("background-first-run");
    });

    this.backgroundBuilder.primeBackground();

    // Persistent backgrounds are started immediately except during APP_STARTUP.
    // Non-persistent backgrounds must be started immediately for new install or enable
    // to initialize the addon and create the persisted listeners.
    // updateReason is set when an extension is updated during APP_STARTUP.
    if (
      extension.testNoDelayedStartup ||
      extension.startupReason !== "APP_STARTUP" ||
      extension.updateReason
    ) {
      // TODO bug 1543354: Avoid AsyncShutdown timeouts by removing the await
      // here, at least for non-test situations.
      await this.backgroundBuilder.build();

      // The task in ExtensionParent.browserPaintedPromise below would be fully
      // skipped because of the above build() that sets bgInstance. Return early
      // so that it is obvious that the logic is skipped.
      return;
    }

    ExtensionParent.browserStartupPromise.then(() => {
      // Return early if the background has started in the meantime. This can
      // happen if a primed listener (isInStartup) has been triggered.
      if (
        !this.backgroundBuilder ||
        this.backgroundBuilder.backgroundContextOwner.bgInstance ||
        !this.backgroundBuilder.backgroundContextOwner.canBePrimed
      ) {
        return;
      }

      // We either start the background page immediately, or fully prime for
      // real.
      this.backgroundBuilder.backgroundContextOwner.canBePrimed = false;

      // If there are no listeners for the extension that were persisted, we need to
      // start the event page so they can be registered.
      if (
        extension.persistentBackground ||
        !extension.persistentListeners?.size ||
        // If runtime.onStartup has a listener and this is app_startup,
        // start the extension so it will fire the event.
        (extension.startupReason == "APP_STARTUP" &&
          extension.persistentListeners?.get("runtime").has("onStartup"))
      ) {
        extension.emit("start-background-script");
      } else {
        // During startup we only prime startup blocking listeners.  At
        // this stage we need to prime all listeners for event pages.
        EventManager.clearPrimedListeners(extension, false);
        // Allow re-priming by deleting existing listeners.
        extension.persistentListeners = null;
        EventManager.primeListeners(extension, false);
      }
    });
  }

  onShutdown(isAppShutdown) {
    if (this.backgroundBuilder) {
      this.backgroundBuilder.backgroundContextOwner.onShutdown(isAppShutdown);
      this.backgroundBuilder = null;
    }
  }
};
PK
!<�Noi-chrome/toolkit/skin/classic/global/global.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== global.css =====================================================
  == Styles that apply everywhere.
  ======================================================================= */

@import url("chrome://global/skin/global-shared.css");

@namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");

@media (-moz-menubar-drag) {
  xul|toolbar[type="menubar"] {
    -moz-window-dragging: drag;
  }
}

/* ::::: Alert icons :::::*/

.message-icon {
  list-style-image: url("moz-icon://stock/gtk-dialog-info?size=dialog");
}

.alert-dialog #infoIcon,
.alert-icon {
  list-style-image: url("moz-icon://stock/gtk-dialog-warning?size=dialog");
}

.error-icon {
  list-style-image: url("moz-icon://stock/gtk-dialog-error?size=dialog");
}

.question-icon {
  list-style-image: url("moz-icon://stock/gtk-dialog-question?size=dialog");
}

.authentication-icon {
  list-style-image: url("chrome://global/skin/icons/Authentication.png");
}

/* XXX(ntim): [mode="text"] is only used by comm-central */

xul|toolbar[mode="text"] .toolbarbutton-text {
  padding: 0 !important;
  margin: 3px 5px !important;
}

/* Separators */

xul|separator:not([orient="vertical"]) {
  height: 1.5em;
}
xul|separator[orient="vertical"] {
  width: 1.5em;
}

xul|separator.thin:not([orient="vertical"]) {
  height: 0.5em;
}
xul|separator.thin[orient="vertical"] {
  width: 0.5em;
}

xul|separator.groove:not([orient="vertical"]) {
  border-top: 1px solid ThreeDShadow;
  border-bottom: 1px solid ThreeDHighlight;
  height: 0;
  margin-block: 0.4em;
}
xul|separator.groove[orient="vertical"] {
  border-left: 1px solid ThreeDShadow;
  border-right: 1px solid ThreeDHighlight;
  width: 0;
  margin-inline: 0.4em;
}

/* Other margins */

xul|notification > xul|hbox > xul|button {
  margin-block: 0;
}
PK
!<�V���	�	4chrome/toolkit/skin/classic/global/toolbarbutton.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

toolbarbutton {
  appearance: auto;
  -moz-default-appearance: toolbarbutton;
  background-color: transparent;
  margin: 0;
  padding: 3px;

  @media (-moz-platform: macos) {
    appearance: none;
    margin-inline: 2px;
    padding-inline: 2px;
  }

  @media (-moz-platform: windows) {
    &:focus-visible {
      outline: var(--default-focusring);
      outline-offset: calc(-1 * var(--default-focusring-width) - 1px);
    }
  }

  @media (-moz-platform: linux) {
    &:where(:hover) {
      color: -moz-buttonhovertext;
    }
    &:where([checked="true"], :hover:active, [open="true"]) {
      color: ButtonText;
    }
  }

  &:where([disabled="true"]) {
    color: GrayText;
    text-shadow: none;
  }
}

.toolbarbutton-text {
  margin: 0;
  padding: 0;
  vertical-align: middle;
}

/* ::::: toolbarbutton menu ::::: */

.toolbarbutton-menu-dropmarker,
.toolbarbutton-combined-buttons-dropmarker {
  appearance: none;
  -moz-context-properties: fill;
  fill: currentColor;
}

.toolbarbutton-menu-dropmarker {
  content: url("chrome://global/skin/icons/arrow-down-12.svg");
}

.toolbarbutton-combined-buttons-dropmarker {
  list-style-image: url("chrome://global/skin/icons/arrow-down-12.svg");
}

/* ::::: toolbarbutton badged ::::: */

.toolbarbutton-badge {
  box-sizing: border-box;
  overflow: hidden;
  white-space: nowrap;
  background-color: #d90000;
  font-size: 10px;
  padding: 0 2px 1px;
  color: #fff;
  text-shadow: none;
  border-radius: 2px;
  box-shadow: 0 1px 0 hsla(0, 100%, 100%, .2) inset,
              0 -1px 0 hsla(0, 0%, 0%, .1) inset,
              0 1px 0 hsla(206, 50%, 10%, .2);
  margin: -5px 0 0 !important;
  margin-inline-end: -4px !important;
  min-width: 14px;
  max-width: 20px;
  line-height: 10px;
  text-align: center;
  align-self: start;
  justify-self: end;

  @media (-moz-platform: windows) {
    font-weight: bold;
  }
  @media (-moz-platform: macos) {
    font-size: 9px;
    padding-top: 1px;
  }
}

@media not (-moz-platform: macos) {
  .toolbarbutton-badge-stack > .toolbarbutton-icon[label]:not([label=""]) {
    margin-inline-end: 0;
  }
}

@media (-moz-platform: macos) {
  toolbar[mode="icons"] > *|* > .toolbarbutton-badge {
    margin-inline-end: -10px !important;
  }
}
PK
!<r����5chrome/toolkit/skin/classic/global/arrowscrollbox.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:host([scrolledtostart])::part(overflow-start-indicator),
:host([scrolledtoend])::part(overflow-end-indicator),
:host(:not([overflowing])) > toolbarbutton {
  visibility: collapse;
}

scrollbox {
  min-width: 0;
  min-height: 0;
}

slot {
  display: flex;
  flex: 1 0 0;
  flex-direction: inherit;
}

/* Scroll arrows */

toolbarbutton {
  color: inherit;
  list-style-image: url("chrome://global/skin/icons/arrow-down-12.svg");
  -moz-context-properties: fill, fill-opacity;
  fill: currentColor;
  fill-opacity: 1;
  padding: 2px;
  /* Make sure we draw on top of our scrollbox, since otherwise the up button
   * won't be hittable */
  position: relative;

  &[disabled] {
    fill-opacity: var(--toolbarbutton-disabled-opacity);
  }

  :host([orient="horizontal"]) > & {
    list-style-image: url("chrome://global/skin/icons/arrow-left.svg");
  }

  @media not (-moz-platform: macos) {
    :host(:not([clicktoscroll])) > & {
      appearance: none;
    }
  }

  > .toolbarbutton-text {
    display: none;
  }
}

#scrollbutton-up > .toolbarbutton-icon {
  transform: scaleY(-1);
}

:host([orient="horizontal"]) > #scrollbutton-down:-moz-locale-dir(ltr) > .toolbarbutton-icon,
:host([orient="horizontal"]) > #scrollbutton-up:-moz-locale-dir(rtl) > .toolbarbutton-icon {
  transform: scaleX(-1);
}
PK
!<�6���:chrome/toolkit/content/extensions/parent/ext-webRequest.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

ChromeUtils.defineESModuleGetters(this, {
  WebRequest: "resource://gre/modules/WebRequest.sys.mjs",
});

var { parseMatchPatterns } = ExtensionUtils;

// The guts of a WebRequest event handler.  Takes care of converting
// |details| parameter when invoking listeners.
function registerEvent(
  extension,
  eventName,
  fire,
  filter,
  info,
  remoteTab = null
) {
  let listener = async data => {
    let event = data.serialize(eventName);
    if (data.registerTraceableChannel) {
      // If this is a primed listener, no tabParent was passed in here,
      // but the convert() callback later in this function will be called
      // when the background page is started.  Force that to happen here
      // after which we'll have a valid tabParent.
      if (fire.wakeup) {
        await fire.wakeup();
      }
      data.registerTraceableChannel(extension.policy, remoteTab);
    }

    return fire.sync(event);
  };

  let filter2 = {};
  if (filter.urls) {
    let perms = new MatchPatternSet([
      ...extension.allowedOrigins.patterns,
      ...extension.optionalOrigins.patterns,
    ]);

    filter2.urls = parseMatchPatterns(filter.urls);

    if (!perms.overlapsAll(filter2.urls)) {
      Cu.reportError(
        "The webRequest.addListener filter doesn't overlap with host permissions."
      );
    }
  }
  if (filter.types) {
    filter2.types = filter.types;
  }
  if (filter.tabId !== undefined) {
    filter2.tabId = filter.tabId;
  }
  if (filter.windowId !== undefined) {
    filter2.windowId = filter.windowId;
  }
  if (filter.incognito !== undefined) {
    filter2.incognito = filter.incognito;
  }

  let blockingAllowed =
    eventName == "onAuthRequired"
      ? extension.hasPermission("webRequestBlocking") ||
        extension.hasPermission("webRequestAuthProvider")
      : extension.hasPermission("webRequestBlocking");

  let info2 = [];
  if (info) {
    for (let desc of info) {
      if ((desc == "blocking" || desc == "asyncBlocking") && !blockingAllowed) {
        // This is usually checked in the child process (based on the API schemas, where these options
        // should be checked with the "webRequestBlockingPermissionRequired" postprocess property),
        // but it is worth to also check it here just in case a new webRequest has been added and
        // it has not yet using the expected postprocess property).
        Cu.reportError(
          "Using webRequest.addListener with the blocking option " +
            "requires the 'webRequestBlocking' permission."
        );
      } else {
        info2.push(desc);
      }
    }
  }

  let listenerDetails = {
    addonId: extension.id,
    policy: extension.policy,
    blockingAllowed,
  };
  WebRequest[eventName].addListener(listener, filter2, info2, listenerDetails);

  return {
    unregister: () => {
      WebRequest[eventName].removeListener(listener);
    },
    convert(_fire, context) {
      fire = _fire;
      remoteTab = context.xulBrowser.frameLoader.remoteTab;
    },
  };
}

function makeWebRequestEventAPI(context, event, extensionApi) {
  return new EventManager({
    context,
    module: "webRequest",
    event,
    extensionApi,
  }).api();
}

function makeWebRequestEventRegistrar(event) {
  return function ({ fire, context }, params) {
    // ExtensionAPIPersistent makes sure this function will be bound
    // to the ExtensionAPIPersistent instance.
    const { extension } = this;

    const [filter, info] = params;

    // When we are registering the real listener coming from the extension context,
    // we should get the additional remoteTab parameter value from the extension context
    // (which is then used by the registerTraceableChannel helper to register stream
    // filters to the channel and associate them to the extension context that has
    // created it and will be handling the filter onstart/ondata/onend events).
    let remoteTab;
    if (context) {
      remoteTab = context.xulBrowser.frameLoader.remoteTab;
    }

    return registerEvent(extension, event, fire, filter, info, remoteTab);
  };
}

this.webRequest = class extends ExtensionAPIPersistent {
  primeListener(event, fire, params, isInStartup) {
    // During early startup if the listener does not use blocking we do not prime it.
    if (
      !isInStartup ||
      params[1]?.some(v => v === "blocking" || v === "asyncBlocking")
    ) {
      return super.primeListener(event, fire, params, isInStartup);
    }
  }

  PERSISTENT_EVENTS = {
    onBeforeRequest: makeWebRequestEventRegistrar("onBeforeRequest"),
    onBeforeSendHeaders: makeWebRequestEventRegistrar("onBeforeSendHeaders"),
    onSendHeaders: makeWebRequestEventRegistrar("onSendHeaders"),
    onHeadersReceived: makeWebRequestEventRegistrar("onHeadersReceived"),
    onAuthRequired: makeWebRequestEventRegistrar("onAuthRequired"),
    onBeforeRedirect: makeWebRequestEventRegistrar("onBeforeRedirect"),
    onResponseStarted: makeWebRequestEventRegistrar("onResponseStarted"),
    onErrorOccurred: makeWebRequestEventRegistrar("onErrorOccurred"),
    onCompleted: makeWebRequestEventRegistrar("onCompleted"),
  };

  getAPI(context) {
    return {
      webRequest: {
        onBeforeRequest: makeWebRequestEventAPI(
          context,
          "onBeforeRequest",
          this
        ),
        onBeforeSendHeaders: makeWebRequestEventAPI(
          context,
          "onBeforeSendHeaders",
          this
        ),
        onSendHeaders: makeWebRequestEventAPI(context, "onSendHeaders", this),
        onHeadersReceived: makeWebRequestEventAPI(
          context,
          "onHeadersReceived",
          this
        ),
        onAuthRequired: makeWebRequestEventAPI(context, "onAuthRequired", this),
        onBeforeRedirect: makeWebRequestEventAPI(
          context,
          "onBeforeRedirect",
          this
        ),
        onResponseStarted: makeWebRequestEventAPI(
          context,
          "onResponseStarted",
          this
        ),
        onErrorOccurred: makeWebRequestEventAPI(
          context,
          "onErrorOccurred",
          this
        ),
        onCompleted: makeWebRequestEventAPI(context, "onCompleted", this),
        getSecurityInfo: (requestId, options) => {
          options ??= {};
          return WebRequest.getSecurityInfo({
            id: requestId,
            policy: context.extension.policy,
            remoteTab: context.xulBrowser.frameLoader.remoteTab,
            options,
          });
        },
        handlerBehaviorChanged: function () {
          // TODO: Flush all caches.
        },
      },
    };
  }
};
PK
!<hп&,�,�modules/WebRequest.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// @ts-nocheck Defer for now.

const { nsIHttpActivityObserver, nsISocketTransport } = Ci;

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ExtensionDNR: "resource://gre/modules/ExtensionDNR.sys.mjs",
  ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs",
  ExtensionUtils: "resource://gre/modules/ExtensionUtils.sys.mjs",
  SecurityInfo: "resource://gre/modules/SecurityInfo.sys.mjs",
  WebRequestUpload: "resource://gre/modules/WebRequestUpload.sys.mjs",
});

// WebRequest.sys.mjs's only consumer is ext-webRequest.js, so we can depend on
// the apiManager.global being initialized.
ChromeUtils.defineLazyGetter(lazy, "tabTracker", () => {
  return lazy.ExtensionParent.apiManager.global.tabTracker;
});
ChromeUtils.defineLazyGetter(
  lazy,
  "getCookieStoreIdForOriginAttributes",
  () => {
    return lazy.ExtensionParent.apiManager.global
      .getCookieStoreIdForOriginAttributes;
  }
);

// URI schemes that service workers are allowed to load scripts from (any other
// scheme is not allowed by the specs and it is not expected by the service workers
// internals neither, which would likely trigger unexpected behaviors).
const ALLOWED_SERVICEWORKER_SCHEMES = ["https", "http", "moz-extension"];

// Response HTTP Headers matching the following patterns are restricted for changes
// applied by MV3 extensions.
const MV3_RESTRICTED_HEADERS_PATTERNS = [
  /^cross-origin-embedder-policy$/,
  /^cross-origin-opener-policy$/,
  /^cross-origin-resource-policy$/,
  /^x-frame-options$/,
  /^access-control-/,
];

// Classes of requests that should be sent immediately instead of batched.
// Covers basically anything that can delay first paint or DOMContentLoaded:
// top frame HTML, <head> blocking CSS, fonts preflight, sync JS and XHR.
const URGENT_CLASSES =
  Ci.nsIClassOfService.Leader |
  Ci.nsIClassOfService.Unblocked |
  Ci.nsIClassOfService.UrgentStart |
  Ci.nsIClassOfService.TailForbidden;

function runLater(job) {
  Services.tm.dispatchToMainThread(job);
}

function parseFilter(filter) {
  if (!filter) {
    filter = {};
  }

  return {
    urls: filter.urls || null,
    types: filter.types || null,
    tabId: filter.tabId ?? null,
    windowId: filter.windowId ?? null,
    incognito: filter.incognito ?? null,
  };
}

function parseExtra(extra, allowed = [], optionsObj = {}) {
  if (extra) {
    for (let ex of extra) {
      if (!allowed.includes(ex)) {
        throw new lazy.ExtensionUtils.ExtensionError(`Invalid option ${ex}`);
      }
    }
  }

  let result = Object.assign({}, optionsObj);
  for (let al of allowed) {
    if (extra && extra.includes(al)) {
      result[al] = true;
    }
  }
  // From a parent process perspective an asyncBlocking listener
  // will be the same as a blocking listener that returned a
  // promise in the child process (because the callback used
  // by asyncBlocking listeners is a difference only visible
  // in the child process where the listener runs).
  if (result.asyncBlocking) {
    result.blocking = true;
  }
  return result;
}

function isThenable(value) {
  return value && typeof value === "object" && typeof value.then === "function";
}

// Verify a requested redirect and throw a more explicit error.
function verifyRedirect(channel, redirectUri, finalUrl, addonId) {
  const { isServiceWorkerScript } = channel;

  if (
    isServiceWorkerScript &&
    channel.loadInfo?.internalContentPolicyType ===
      Ci.nsIContentPolicy.TYPE_INTERNAL_SERVICE_WORKER
  ) {
    throw new Error(
      `Invalid redirectUrl ${redirectUri?.spec} on service worker main script ${finalUrl} requested by ${addonId}`
    );
  }

  if (
    isServiceWorkerScript &&
    (channel.loadInfo?.internalContentPolicyType ===
      Ci.nsIContentPolicy.TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS ||
      channel.loadInfo?.internalContentPolicyType ===
        Ci.nsIContentPolicy.TYPE_INTERNAL_WORKER_STATIC_MODULE) &&
    !ALLOWED_SERVICEWORKER_SCHEMES.includes(redirectUri?.scheme)
  ) {
    throw new Error(
      `Invalid redirectUrl ${redirectUri?.spec} on service worker imported script ${finalUrl} requested by ${addonId}`
    );
  }
}

class HeaderChanger {
  constructor(channel) {
    this.channel = channel;

    this.array = this.readHeaders();
  }

  getMap() {
    if (!this.map) {
      this.map = new Map();
      for (let header of this.array) {
        this.map.set(header.name.toLowerCase(), header);
      }
    }
    return this.map;
  }

  toArray() {
    return this.array;
  }

  validateHeaders(headers) {
    // We should probably use schema validation for this.

    if (!Array.isArray(headers)) {
      return false;
    }

    return headers.every(header => {
      if (typeof header !== "object" || header === null) {
        return false;
      }

      if (typeof header.name !== "string") {
        return false;
      }

      return (
        typeof header.value === "string" || Array.isArray(header.binaryValue)
      );
    });
  }

  applyChanges(headers, opts = {}) {
    if (!this.validateHeaders(headers)) {
      Cu.reportError(`Invalid header array: ${uneval(headers)}`);
      return;
    }

    let newHeaders = new Set(headers.map(({ name }) => name.toLowerCase()));

    // Remove missing headers.
    let origHeaders = this.getMap();
    for (let name of origHeaders.keys()) {
      if (!newHeaders.has(name)) {
        this.setHeader(name, "", false, opts, name);
      }
    }

    // Set new or changed headers.  If there are multiple headers with the same
    // name (e.g. Set-Cookie), merge them, instead of having new values
    // overwrite previous ones.
    //
    // When the new value of a header is equal the existing value of the header
    // (e.g. the initial response set "Set-Cookie: examplename=examplevalue",
    // and an extension also added the header
    // "Set-Cookie: examplename=examplevalue") then the header value is not
    // re-set, but subsequent headers of the same type will be merged in.
    //
    // Multiple addons will be able to provide modifications to any headers
    // listed in the default set.
    let headersAlreadySet = new Set();
    for (let { name, value, binaryValue } of headers) {
      if (binaryValue) {
        value = String.fromCharCode(...binaryValue);
      }

      let lowerCaseName = name.toLowerCase();
      let original = origHeaders.get(lowerCaseName);

      if (!original || value !== original.value) {
        let shouldMerge = headersAlreadySet.has(lowerCaseName);
        this.setHeader(name, value, shouldMerge, opts, lowerCaseName);
      }

      headersAlreadySet.add(lowerCaseName);
    }
  }
}

const checkRestrictedHeaderValue = (value, opts = {}) => {
  let uri = Services.io.newURI(`https://${value}/`);
  let { policy } = opts;

  if (policy && !policy.allowedOrigins.matches(uri)) {
    throw new Error(`Unable to set host header, url missing from permissions.`);
  }

  if (WebExtensionPolicy.isRestrictedURI(uri)) {
    throw new Error(`Unable to set host header to restricted url.`);
  }
};

class RequestHeaderChanger extends HeaderChanger {
  setHeader(name, value, merge, opts, lowerCaseName) {
    try {
      if (value && lowerCaseName === "host") {
        checkRestrictedHeaderValue(value, opts);
      }
      this.channel.setRequestHeader(name, value, merge);
    } catch (e) {
      Cu.reportError(new Error(`Error setting request header ${name}: ${e}`));
    }
  }

  readHeaders() {
    return this.channel.getRequestHeaders();
  }
}

class ResponseHeaderChanger extends HeaderChanger {
  didModifyCSP = false;

  setHeader(name, value, merge, opts, lowerCaseName) {
    if (lowerCaseName === "content-security-policy") {
      // When multiple add-ons change the CSP, enforce the combined (strictest)
      // policy - see bug 1462989 for motivation.
      // When value is unset, don't force the header to be merged, to allow
      // add-ons to clear the header if wanted.
      if (value) {
        merge = merge || this.didModifyCSP;
      }

      // For manifest_version 3 extension, we are currently only allowing to
      // merge additional CSP strings to the existing ones, which will be initially
      // stricter than currently allowed to manifest_version 2 extensions, then
      // following up with either a new permission and/or some more changes to the
      // APIs (and possibly making the behavior more deterministic than it is for
      // manifest_version 2 at the moment).
      if (opts.policy.manifestVersion > 2) {
        if (value) {
          // If the given CSP header value is non empty, then it should be
          // merged to the existing one.
          merge = true;
        } else {
          // If the given CSP header value is empty (which would be clearing the
          // CSP header), it should be considered a no-op and this.didModifyCSP
          // shouldn't be changed to true.
          return;
        }
      }

      this.didModifyCSP = true;
    } else if (
      opts.policy.manifestVersion > 2 &&
      this.isResponseHeaderRestricted(lowerCaseName)
    ) {
      // TODO (Bug 1787155 and Bug 1273281) open this up to MV3 extensions,
      // locked behind manifest.json declarative permission and a separate
      // explicit user-controlled permission (and ideally also check for
      // changes that would lead to security downgrades).
      Cu.reportError(
        `Disallowed change restricted response header ${name} on ${this.channel.finalURL} from ${opts.policy.debugName}`
      );
      return;
    }

    try {
      this.channel.setResponseHeader(name, value, merge);
    } catch (e) {
      Cu.reportError(new Error(`Error setting response header ${name}: ${e}`));
    }
  }

  isResponseHeaderRestricted(lowerCaseHeaderName) {
    return MV3_RESTRICTED_HEADERS_PATTERNS.some(regex =>
      regex.test(lowerCaseHeaderName)
    );
  }

  readHeaders() {
    return this.channel.getResponseHeaders();
  }
}

const MAYBE_CACHED_EVENTS = new Set([
  "onResponseStarted",
  "onHeadersReceived",
  "onBeforeRedirect",
  "onCompleted",
  "onErrorOccurred",
]);

const OPTIONAL_PROPERTIES = [
  "requestHeaders",
  "responseHeaders",
  "statusCode",
  "statusLine",
  "error",
  "redirectUrl",
  "requestBody",
  "scheme",
  "realm",
  "isProxy",
  "challenger",
  "proxyInfo",
  "ip",
  "frameAncestors",
  "urlClassification",
  "requestSize",
  "responseSize",
];

function serializeRequestData(eventName) {
  let data = {
    requestId: this.requestId,
    url: this.url,
    originUrl: this.originUrl,
    documentUrl: this.documentUrl,
    method: this.method,
    type: this.type,
    timeStamp: Date.now(),
    tabId: this.tabId,
    frameId: this.frameId,
    parentFrameId: this.parentFrameId,
    incognito: this.incognito,
    thirdParty: this.thirdParty,
    cookieStoreId: this.cookieStoreId,
    urgentSend: this.urgentSend,
  };

  if (MAYBE_CACHED_EVENTS.has(eventName)) {
    data.fromCache = !!this.fromCache;
  }

  for (let opt of OPTIONAL_PROPERTIES) {
    if (typeof this[opt] !== "undefined") {
      data[opt] = this[opt];
    }
  }

  if (this.urlClassification) {
    data.urlClassification = {
      firstParty: this.urlClassification.firstParty.filter(
        c => !c.startsWith("socialtracking_")
      ),
      thirdParty: this.urlClassification.thirdParty.filter(
        c => !c.startsWith("socialtracking_")
      ),
    };
  }

  return data;
}

var HttpObserverManager;

var ChannelEventSink = {
  _classDescription: "WebRequest channel event sink",
  _classID: Components.ID("115062f8-92f1-11e5-8b7f-080027b0f7ec"),
  _contractID: "@mozilla.org/webrequest/channel-event-sink;1",

  QueryInterface: ChromeUtils.generateQI(["nsIChannelEventSink", "nsIFactory"]),

  init() {
    Components.manager
      .QueryInterface(Ci.nsIComponentRegistrar)
      .registerFactory(
        this._classID,
        this._classDescription,
        this._contractID,
        this
      );
  },

  register() {
    Services.catMan.addCategoryEntry(
      "net-channel-event-sinks",
      this._contractID,
      this._contractID,
      false,
      true
    );
  },

  unregister() {
    Services.catMan.deleteCategoryEntry(
      "net-channel-event-sinks",
      this._contractID,
      false
    );
  },

  // nsIChannelEventSink implementation
  asyncOnChannelRedirect(oldChannel, newChannel, flags, redirectCallback) {
    runLater(() => redirectCallback.onRedirectVerifyCallback(Cr.NS_OK));
    try {
      HttpObserverManager.onChannelReplaced(oldChannel, newChannel);
    } catch (e) {
      // we don't wanna throw: it would abort the redirection
    }
  },

  // nsIFactory implementation
  createInstance(iid) {
    return this.QueryInterface(iid);
  },
};

ChannelEventSink.init();

// nsIAuthPrompt2 implementation for onAuthRequired
class AuthRequestor {
  constructor(channel, httpObserver) {
    this.notificationCallbacks = channel.notificationCallbacks;
    this.loadGroupCallbacks =
      channel.loadGroup && channel.loadGroup.notificationCallbacks;
    this.httpObserver = httpObserver;
  }

  getInterface(iid) {
    if (iid.equals(Ci.nsIAuthPromptProvider) || iid.equals(Ci.nsIAuthPrompt2)) {
      return this;
    }
    try {
      return this.notificationCallbacks.getInterface(iid);
    } catch (e) {}
    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
  }

  _getForwardedInterface(iid) {
    try {
      return this.notificationCallbacks.getInterface(iid);
    } catch (e) {
      return this.loadGroupCallbacks.getInterface(iid);
    }
  }

  // nsIAuthPromptProvider getAuthPrompt
  getAuthPrompt(reason, iid) {
    // This should never get called without getInterface having been called first.
    if (iid.equals(Ci.nsIAuthPrompt2)) {
      return this;
    }
    return this._getForwardedInterface(Ci.nsIAuthPromptProvider).getAuthPrompt(
      reason,
      iid
    );
  }

  // nsIAuthPrompt2 promptAuth
  promptAuth(channel, level, authInfo) {
    this._getForwardedInterface(Ci.nsIAuthPrompt2).promptAuth(
      channel,
      level,
      authInfo
    );
  }

  _getForwardPrompt(data) {
    let reason = data.isProxy
      ? Ci.nsIAuthPromptProvider.PROMPT_PROXY
      : Ci.nsIAuthPromptProvider.PROMPT_NORMAL;
    for (let callbacks of [
      this.notificationCallbacks,
      this.loadGroupCallbacks,
    ]) {
      try {
        return callbacks
          .getInterface(Ci.nsIAuthPromptProvider)
          .getAuthPrompt(reason, Ci.nsIAuthPrompt2);
      } catch (e) {}
      try {
        return callbacks.getInterface(Ci.nsIAuthPrompt2);
      } catch (e) {}
    }
    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
  }

  // nsIAuthPrompt2 asyncPromptAuth
  asyncPromptAuth(channel, callback, context, level, authInfo) {
    let wrapper = ChannelWrapper.get(channel);

    let uri = channel.URI;
    let proxyInfo;
    let isProxy = !!(authInfo.flags & authInfo.AUTH_PROXY);
    if (isProxy && channel instanceof Ci.nsIProxiedChannel) {
      proxyInfo = channel.proxyInfo;
    }
    let data = {
      scheme: authInfo.authenticationScheme,
      realm: authInfo.realm,
      isProxy,
      challenger: {
        host: proxyInfo ? proxyInfo.host : uri.host,
        port: proxyInfo ? proxyInfo.port : uri.port,
      },
    };

    // In the case that no listener provides credentials, we fallback to the
    // previously set callback class for authentication.
    wrapper.authPromptForward = () => {
      try {
        let prompt = this._getForwardPrompt(data);
        prompt.asyncPromptAuth(channel, callback, context, level, authInfo);
      } catch (e) {
        Cu.reportError(`webRequest asyncPromptAuth failure ${e}`);
        callback.onAuthCancelled(context, false);
      }
      wrapper.authPromptForward = null;
      wrapper.authPromptCallback = null;
    };
    wrapper.authPromptCallback = authCredentials => {
      // The API allows for canceling the request, providing credentials or
      // doing nothing, so we do not provide a way to call onAuthCanceled.
      // Canceling the request will result in canceling the authentication.
      if (
        authCredentials &&
        typeof authCredentials.username === "string" &&
        typeof authCredentials.password === "string"
      ) {
        authInfo.username = authCredentials.username;
        authInfo.password = authCredentials.password;
        try {
          callback.onAuthAvailable(context, authInfo);
        } catch (e) {
          Cu.reportError(`webRequest onAuthAvailable failure ${e}`);
        }
        // At least one addon has responded, so we won't forward to the regular
        // prompt handlers.
        wrapper.authPromptForward = null;
        wrapper.authPromptCallback = null;
      }
    };

    this.httpObserver.runChannelListener(wrapper, "onAuthRequired", data);

    return {
      QueryInterface: ChromeUtils.generateQI(["nsICancelable"]),
      cancel() {
        try {
          callback.onAuthCancelled(context, false);
        } catch (e) {
          Cu.reportError(`webRequest onAuthCancelled failure ${e}`);
        }
        wrapper.authPromptForward = null;
        wrapper.authPromptCallback = null;
      },
    };
  }
}

AuthRequestor.prototype.QueryInterface = ChromeUtils.generateQI([
  "nsIInterfaceRequestor",
  "nsIAuthPromptProvider",
  "nsIAuthPrompt2",
]);

// Most WebRequest events are implemented via the observer services, but
// a few use custom xpcom interfaces.  This class (HttpObserverManager)
// serves two main purposes:
//  1. It abstracts away the names and details of the underlying
//     implementation (e.g., onBeforeBeforeRequest is dispatched from
//     the http-on-modify-request observable).
//  2. It aggregates multiple listeners so that a single observer or
//     handler can serve multiple webRequest listeners.
HttpObserverManager = {
  listeners: {
    // onBeforeRequest uses http-on-modify observer for HTTP(S).
    onBeforeRequest: new Map(),

    // onBeforeSendHeaders and onSendHeaders correspond to the
    // http-on-before-connect observer.
    onBeforeSendHeaders: new Map(),
    onSendHeaders: new Map(),

    // onHeadersReceived corresponds to the http-on-examine-* obserservers.
    onHeadersReceived: new Map(),

    // onAuthRequired is handled via the nsIAuthPrompt2 xpcom interface
    // which is managed here by AuthRequestor.
    onAuthRequired: new Map(),

    // onBeforeRedirect is handled by the nsIChannelEVentSink xpcom interface
    // which is managed here by ChannelEventSink.
    onBeforeRedirect: new Map(),

    // onResponseStarted, onErrorOccurred, and OnCompleted correspond
    // to events dispatched by the ChannelWrapper EventTarget.
    onResponseStarted: new Map(),
    onErrorOccurred: new Map(),
    onCompleted: new Map(),
  },
  // Whether there are any registered declarativeNetRequest rules. These DNR
  // rules may match new requests and result in request modifications.
  dnrActive: false,

  openingInitialized: false,
  beforeConnectInitialized: false,
  examineInitialized: false,
  redirectInitialized: false,
  activityInitialized: false,
  needTracing: false,
  hasRedirects: false,

  getWrapper(nativeChannel) {
    let wrapper = ChannelWrapper.get(nativeChannel);
    if (!wrapper._addedListeners) {
      /* eslint-disable mozilla/balanced-listeners */
      if (this.listeners.onErrorOccurred.size) {
        wrapper.addEventListener("error", this);
      }
      if (this.listeners.onResponseStarted.size) {
        wrapper.addEventListener("start", this);
      }
      if (this.listeners.onCompleted.size) {
        wrapper.addEventListener("stop", this);
      }
      /* eslint-enable mozilla/balanced-listeners */

      wrapper._addedListeners = true;
    }
    return wrapper;
  },

  get activityDistributor() {
    return Cc["@mozilla.org/network/http-activity-distributor;1"].getService(
      Ci.nsIHttpActivityDistributor
    );
  },

  // This method is called whenever webRequest listeners are added or removed.
  // It reconciles the set of listeners with underlying observers, event
  // handlers, etc. by adding new low-level handlers for any newly added
  // webRequest listeners and removing those that are no longer needed if
  // there are no more listeners for corresponding webRequest events.
  addOrRemove() {
    let needOpening = this.listeners.onBeforeRequest.size || this.dnrActive;
    let needBeforeConnect =
      this.listeners.onBeforeSendHeaders.size ||
      this.listeners.onSendHeaders.size ||
      this.dnrActive;
    if (needOpening && !this.openingInitialized) {
      this.openingInitialized = true;
      Services.obs.addObserver(this, "http-on-modify-request");
    } else if (!needOpening && this.openingInitialized) {
      this.openingInitialized = false;
      Services.obs.removeObserver(this, "http-on-modify-request");
    }
    if (needBeforeConnect && !this.beforeConnectInitialized) {
      this.beforeConnectInitialized = true;
      Services.obs.addObserver(this, "http-on-before-connect");
    } else if (!needBeforeConnect && this.beforeConnectInitialized) {
      this.beforeConnectInitialized = false;
      Services.obs.removeObserver(this, "http-on-before-connect");
    }

    let haveBlocking = Object.values(this.listeners).some(listeners =>
      Array.from(listeners.values()).some(listener => listener.blockingAllowed)
    );

    this.needTracing =
      this.listeners.onResponseStarted.size ||
      this.listeners.onErrorOccurred.size ||
      this.listeners.onCompleted.size ||
      haveBlocking;

    let needExamine =
      this.needTracing ||
      this.listeners.onHeadersReceived.size ||
      this.listeners.onAuthRequired.size ||
      this.dnrActive;

    if (needExamine && !this.examineInitialized) {
      this.examineInitialized = true;
      Services.obs.addObserver(this, "http-on-examine-response");
      Services.obs.addObserver(this, "http-on-examine-cached-response");
      Services.obs.addObserver(this, "http-on-examine-merged-response");
    } else if (!needExamine && this.examineInitialized) {
      this.examineInitialized = false;
      Services.obs.removeObserver(this, "http-on-examine-response");
      Services.obs.removeObserver(this, "http-on-examine-cached-response");
      Services.obs.removeObserver(this, "http-on-examine-merged-response");
    }

    // If we have any listeners, we need the channelsink so the channelwrapper is
    // updated properly. Otherwise events for channels that are redirected will not
    // happen correctly.  If we have no listeners, shut it down.
    this.hasRedirects = this.listeners.onBeforeRedirect.size > 0;
    let needRedirect =
      this.hasRedirects || needExamine || needOpening || needBeforeConnect;
    if (needRedirect && !this.redirectInitialized) {
      this.redirectInitialized = true;
      ChannelEventSink.register();
    } else if (!needRedirect && this.redirectInitialized) {
      this.redirectInitialized = false;
      ChannelEventSink.unregister();
    }

    let needActivity = this.listeners.onErrorOccurred.size;
    if (needActivity && !this.activityInitialized) {
      this.activityInitialized = true;
      this.activityDistributor.addObserver(this);
    } else if (!needActivity && this.activityInitialized) {
      this.activityInitialized = false;
      this.activityDistributor.removeObserver(this);
    }
  },

  addListener(kind, callback, opts) {
    this.listeners[kind].set(callback, opts);
    this.addOrRemove();
  },

  removeListener(kind, callback) {
    this.listeners[kind].delete(callback);
    this.addOrRemove();
  },

  setDNRHandlingEnabled(dnrActive) {
    this.dnrActive = dnrActive;
    this.addOrRemove();
  },

  observe(subject, topic, data) {
    let channel = this.getWrapper(subject);
    switch (topic) {
      case "http-on-modify-request":
        this.runChannelListener(channel, "onBeforeRequest");
        break;
      case "http-on-before-connect":
        this.runChannelListener(channel, "onBeforeSendHeaders");
        break;
      case "http-on-examine-cached-response":
      case "http-on-examine-merged-response":
        channel.fromCache = true;
      // falls through
      case "http-on-examine-response":
        this.examine(channel, topic, data);
        break;
    }
  },

  // We map activity values with tentative error names, e.g. "STATUS_RESOLVING" => "NS_ERROR_NET_ON_RESOLVING".
  get activityErrorsMap() {
    let prefix = /^(?:ACTIVITY_SUBTYPE_|STATUS_)/;
    let map = new Map();
    for (let iface of [nsIHttpActivityObserver, nsISocketTransport]) {
      for (let c of Object.keys(iface).filter(name => prefix.test(name))) {
        map.set(iface[c], c.replace(prefix, "NS_ERROR_NET_ON_"));
      }
    }
    delete this.activityErrorsMap;
    this.activityErrorsMap = map;
    return this.activityErrorsMap;
  },
  GOOD_LAST_ACTIVITY: nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_HEADER,
  observeActivity(
    nativeChannel,
    activityType,
    activitySubtype /* , aTimestamp, aExtraSizeData, aExtraStringData */
  ) {
    // Sometimes we get a NullHttpChannel, which implements
    // nsIHttpChannel but not nsIChannel.
    if (!(nativeChannel instanceof Ci.nsIChannel)) {
      return;
    }
    let channel = this.getWrapper(nativeChannel);

    let lastActivity = channel.lastActivity || 0;
    if (
      activitySubtype ===
        nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE &&
      lastActivity &&
      lastActivity !== this.GOOD_LAST_ACTIVITY
    ) {
      // Since the channel's security info is assigned in onStartRequest and
      // errorCheck is called in ChannelWrapper::onStartRequest, we should check
      // the errorString after onStartRequest to make sure errors have a chance
      // to be processed before we fall back to a generic error string.
      channel.addEventListener(
        "start",
        () => {
          if (!channel.errorString) {
            this.runChannelListener(channel, "onErrorOccurred", {
              error:
                this.activityErrorsMap.get(lastActivity) ||
                `NS_ERROR_NET_UNKNOWN_${lastActivity}`,
            });
          }
        },
        { once: true }
      );
    } else if (
      lastActivity !== this.GOOD_LAST_ACTIVITY &&
      lastActivity !==
        nsIHttpActivityObserver.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE
    ) {
      channel.lastActivity = activitySubtype;
    }
  },

  getRequestData(channel, extraData) {
    let originAttributes = channel.loadInfo?.originAttributes;
    let cos = channel.channel.QueryInterface(Ci.nsIClassOfService);

    let data = {
      requestId: String(channel.id),
      url: channel.finalURL,
      method: channel.method,
      type: channel.type,
      fromCache: channel.fromCache,
      incognito: originAttributes?.privateBrowsingId > 0,
      thirdParty: channel.thirdParty,

      originUrl: channel.originURL || undefined,
      documentUrl: channel.documentURL || undefined,

      tabId: this.getBrowserData(channel).tabId,
      frameId: channel.frameId,
      parentFrameId: channel.parentFrameId,

      frameAncestors: channel.frameAncestors || undefined,

      ip: channel.remoteAddress,

      proxyInfo: channel.proxyInfo,

      serialize: serializeRequestData,
      requestSize: channel.requestSize,
      responseSize: channel.responseSize,
      urlClassification: channel.urlClassification,

      // Figure out if this is an urgent request that shouldn't be batched.
      urgentSend: (cos.classFlags & URGENT_CLASSES) > 0,
    };

    if (originAttributes) {
      data.cookieStoreId =
        lazy.getCookieStoreIdForOriginAttributes(originAttributes);
    }

    return Object.assign(data, extraData);
  },

  handleEvent(event) {
    let channel = event.currentTarget;
    switch (event.type) {
      case "error":
        this.runChannelListener(channel, "onErrorOccurred", {
          error: channel.errorString,
        });
        break;
      case "start":
        this.runChannelListener(channel, "onResponseStarted");
        break;
      case "stop":
        this.runChannelListener(channel, "onCompleted");
        break;
    }
  },

  STATUS_TYPES: new Set([
    "onHeadersReceived",
    "onAuthRequired",
    "onBeforeRedirect",
    "onResponseStarted",
    "onCompleted",
  ]),
  FILTER_TYPES: new Set([
    "onBeforeRequest",
    "onBeforeSendHeaders",
    "onSendHeaders",
    "onHeadersReceived",
    "onAuthRequired",
    "onBeforeRedirect",
  ]),

  getBrowserData(wrapper) {
    let browserData = wrapper._browserData;
    if (!browserData) {
      if (wrapper.browserElement) {
        browserData = lazy.tabTracker.getBrowserData(wrapper.browserElement);
      } else {
        browserData = { tabId: -1, windowId: -1 };
      }
      wrapper._browserData = browserData;
    }
    return browserData;
  },

  runChannelListener(channel, kind, extraData = null) {
    let handlerResults = [];
    let requestHeaders;
    let responseHeaders;

    try {
      if (kind !== "onErrorOccurred" && channel.errorString) {
        return;
      }
      if (this.dnrActive) {
        // DNR may modify (but not cancel) the request at this stage.
        lazy.ExtensionDNR.beforeWebRequestEvent(channel, kind);
      }

      let registerFilter = this.FILTER_TYPES.has(kind);
      let commonData = null;
      let requestBody;
      this.listeners[kind].forEach((opts, callback) => {
        if (opts.filter.tabId !== null || opts.filter.windowId !== null) {
          const { tabId, windowId } = this.getBrowserData(channel);
          if (
            (opts.filter.tabId !== null && tabId != opts.filter.tabId) ||
            (opts.filter.windowId !== null && windowId != opts.filter.windowId)
          ) {
            return;
          }
        }
        // ChannelWrapper::Matches does not only match the filter, it also
        // checks whether the extension is allowed to modify the request.
        // Regardless of extension permissions, if channel.canModify is false,
        // then the request is not matched.
        // There is one exception: When opts.isProxy is set (in onAuthRequired)
        // AND the extension has the "proxy" permission, then the extension is
        // allowed to handle the request regardless of channel.canModify.
        if (!channel.matches(opts.filter, opts.policy, extraData)) {
          return;
        }

        let extension = opts.policy?.extension;
        // TODO: Move this logic to ChannelWrapper::matches, see bug 1699481
        if (
          extension?.userContextIsolation &&
          !extension.canAccessContainer(
            channel.loadInfo?.originAttributes.userContextId
          )
        ) {
          return;
        }

        if (!commonData) {
          commonData = this.getRequestData(channel, extraData);
          if (this.STATUS_TYPES.has(kind)) {
            commonData.statusCode = channel.statusCode;
            commonData.statusLine = channel.statusLine;
          }
        }
        let data = Object.create(commonData);
        data.urgentSend = data.urgentSend && opts.blocking;

        if (registerFilter && opts.blocking && opts.policy) {
          data.registerTraceableChannel = (policy, remoteTab) => {
            // `channel` is a ChannelWrapper, which contains the actual
            // underlying nsIChannel in `channel.channel`.  For startup events
            // that are held until the extension background page is started,
            // it is possible that the underlying channel can be closed and
            // cleaned up between the time the event occurred and the time
            // we reach this code.
            if (channel.channel) {
              channel.registerTraceableChannel(policy, remoteTab);
            }
          };
        }

        if (opts.requestHeaders) {
          requestHeaders = requestHeaders || new RequestHeaderChanger(channel);
          data.requestHeaders = requestHeaders.toArray();
        }

        if (opts.responseHeaders) {
          try {
            responseHeaders =
              responseHeaders || new ResponseHeaderChanger(channel);
            data.responseHeaders = responseHeaders.toArray();
          } catch (e) {
            /* headers may not be available on some redirects */
          }
        }

        if (opts.requestBody) {
          requestBody =
            requestBody ||
            lazy.WebRequestUpload.createRequestBody(channel.channel);
          data.requestBody = requestBody;
        }

        try {
          let result = callback(data);

          if (typeof result === "object" && opts.blocking) {
            handlerResults.push({ opts, result });
          }
        } catch (e) {
          Cu.reportError(e);
        }
      });
    } catch (e) {
      Cu.reportError(e);
    }

    if (this.dnrActive && lazy.ExtensionDNR.handleRequest(channel, kind)) {
      return;
    }

    return this.applyChanges(
      kind,
      channel,
      handlerResults,
      requestHeaders,
      responseHeaders
    );
  },

  async applyChanges(
    kind,
    channel,
    handlerResults,
    requestHeaders,
    responseHeaders
  ) {
    const { finalURL, id: chanId } = channel;
    let shouldResume = !channel.suspended;
    // NOTE: if a request has been suspended before the GeckoProfiler
    // has been activated and then resumed while the GeckoProfiler is active
    // and collecting data, the resulting "Extension Suspend" marker will be
    // recorded with an empty marker text (and so without url, chan id and
    // the supenders addon ids).
    let markerText = "";
    if (Services.profiler?.IsActive()) {
      const suspenders = handlerResults
        .filter(({ result }) => isThenable(result))
        .map(({ opts }) => opts.addonId)
        .join(", ");
      markerText = `${kind} ${finalURL} by ${suspenders} (chanId: ${chanId})`;
    }
    try {
      for (let { opts, result } of handlerResults) {
        if (isThenable(result)) {
          channel.suspend(markerText);
          try {
            result = await result;
          } catch (e) {
            let error;

            if (e instanceof Error) {
              error = e;
            } else if (typeof e === "object" && e.message) {
              error = new Error(e.message, e.fileName, e.lineNumber);
            }

            Cu.reportError(error);
            continue;
          }
          if (!result || typeof result !== "object") {
            continue;
          }
        }

        if (kind === "onAuthRequired") {
          if (result.authCredentials && channel.authPromptCallback) {
            channel.authPromptCallback(result.authCredentials);
          }

          // We allow proxy auth to cancel or handle authCredentials regardless
          // of canModify, but ensure we do nothing else.
          if (!channel.canModify) {
            continue;
          }
        }

        if (result.cancel) {
          channel.resume();
          channel.cancel(
            Cr.NS_ERROR_ABORT,
            Ci.nsILoadInfo.BLOCKING_REASON_EXTENSION_WEBREQUEST
          );
          ChromeUtils.addProfilerMarker(
            "Extension Canceled",
            { category: "Network" },
            `${kind} ${finalURL} canceled by ${opts.addonId} (chanId: ${chanId})`
          );
          if (opts.policy) {
            let properties = channel.channel.QueryInterface(
              Ci.nsIWritablePropertyBag
            );
            properties.setProperty("cancelledByExtension", opts.policy.id);
          }
          return;
        }

        if (result.redirectUrl) {
          try {
            const { redirectUrl } = result;
            channel.resume();
            const redirectUri = Services.io.newURI(redirectUrl);
            verifyRedirect(channel, redirectUri, finalURL, opts.addonId);
            channel.redirectTo(redirectUri);
            ChromeUtils.addProfilerMarker(
              "Extension Redirected",
              { category: "Network" },
              `${kind} ${finalURL} redirected to ${redirectUrl} by ${opts.addonId} (chanId: ${chanId})`
            );
            if (opts.policy) {
              let properties = channel.channel.QueryInterface(
                Ci.nsIWritablePropertyBag
              );
              properties.setProperty("redirectedByExtension", opts.policy.id);
            }

            // Web Extensions using the WebRequest API are allowed
            // to redirect a channel to a data: URI, hence we mark
            // the channel to let the redirect blocker know. Please
            // note that this marking needs to happen after the
            // channel.redirectTo is called because the channel's
            // RedirectTo() implementation explicitly drops the flag
            // to avoid additional redirects not caused by the
            // Web Extension.
            channel.loadInfo.allowInsecureRedirectToDataURI = true;

            // To pass CORS checks, we pretend the current request's
            // response allows the triggering origin to access.
            let origin = channel.getRequestHeader("Origin");
            if (origin) {
              channel.setResponseHeader("Access-Control-Allow-Origin", origin);
              channel.setResponseHeader(
                "Access-Control-Allow-Credentials",
                "true"
              );

              // Compute an arbitrary 'Access-Control-Allow-Headers'
              // for the  internal Redirect

              let allowHeaders = channel
                .getRequestHeaders()
                .map(header => header.name)
                .join();
              channel.setResponseHeader(
                "Access-Control-Allow-Headers",
                allowHeaders
              );

              channel.setResponseHeader(
                "Access-Control-Allow-Methods",
                channel.method
              );
            }

            return;
          } catch (e) {
            Cu.reportError(e);
          }
        }

        if (result.upgradeToSecure && kind === "onBeforeRequest") {
          try {
            channel.upgradeToSecure();
          } catch (e) {
            Cu.reportError(e);
          }
        }

        if (opts.requestHeaders && result.requestHeaders && requestHeaders) {
          requestHeaders.applyChanges(result.requestHeaders, opts);
        }

        if (opts.responseHeaders && result.responseHeaders && responseHeaders) {
          responseHeaders.applyChanges(result.responseHeaders, opts);
        }
      }

      // If a listener did not cancel the request or provide credentials, we
      // forward the auth request to the base handler.
      if (kind === "onAuthRequired" && channel.authPromptForward) {
        channel.authPromptForward();
      }

      if (kind === "onBeforeSendHeaders" && this.listeners.onSendHeaders.size) {
        this.runChannelListener(channel, "onSendHeaders");
      } else if (kind !== "onErrorOccurred") {
        channel.errorCheck();
      }
    } catch (e) {
      Cu.reportError(e);
    }

    // Only resume the channel if it was suspended by this call.
    if (shouldResume) {
      channel.resume();
    }
  },

  shouldHookListener(listener, channel, extraData) {
    if (listener.size == 0) {
      return false;
    }

    for (let opts of listener.values()) {
      if (channel.matches(opts.filter, opts.policy, extraData)) {
        return true;
      }
    }
    return false;
  },

  examine(channel) {
    if (this.listeners.onHeadersReceived.size || this.dnrActive) {
      this.runChannelListener(channel, "onHeadersReceived");
    }

    if (
      !channel.hasAuthRequestor &&
      this.shouldHookListener(this.listeners.onAuthRequired, channel, {
        isProxy: true,
      })
    ) {
      channel.channel.notificationCallbacks = new AuthRequestor(
        channel.channel,
        this
      );
      channel.hasAuthRequestor = true;
    }
  },

  onChannelReplaced(oldChannel, newChannel) {
    let channel = this.getWrapper(oldChannel);

    // We want originalURI, this will provide a moz-ext rather than jar or file
    // uri on redirects.
    if (this.hasRedirects) {
      this.runChannelListener(channel, "onBeforeRedirect", {
        redirectUrl: newChannel.originalURI.spec,
      });
    }
    channel.channel = newChannel;
  },
};

function HttpEvent(internalEvent, options) {
  this.internalEvent = internalEvent;
  this.options = options;
}

HttpEvent.prototype = {
  addListener(callback, filter = null, options = null, optionsObject = null) {
    let opts = parseExtra(options, this.options, optionsObject);
    opts.filter = parseFilter(filter);
    HttpObserverManager.addListener(this.internalEvent, callback, opts);
  },

  removeListener(callback) {
    HttpObserverManager.removeListener(this.internalEvent, callback);
  },
};

var onBeforeRequest = new HttpEvent("onBeforeRequest", [
  "blocking",
  "requestBody",
]);
var onBeforeSendHeaders = new HttpEvent("onBeforeSendHeaders", [
  "requestHeaders",
  "blocking",
]);
var onSendHeaders = new HttpEvent("onSendHeaders", ["requestHeaders"]);
var onHeadersReceived = new HttpEvent("onHeadersReceived", [
  "blocking",
  "responseHeaders",
]);
var onAuthRequired = new HttpEvent("onAuthRequired", [
  "asyncBlocking",
  "blocking",
  "responseHeaders",
]);
var onBeforeRedirect = new HttpEvent("onBeforeRedirect", ["responseHeaders"]);
var onResponseStarted = new HttpEvent("onResponseStarted", ["responseHeaders"]);
var onCompleted = new HttpEvent("onCompleted", ["responseHeaders"]);
var onErrorOccurred = new HttpEvent("onErrorOccurred");

export var WebRequest = {
  setDNRHandlingEnabled: dnrActive => {
    HttpObserverManager.setDNRHandlingEnabled(dnrActive);
  },
  getTabIdForChannelWrapper: channel => {
    // Warning: This method should only be called after the initialization of
    // ExtensionParent.apiManager.global. Generally, this means that this method
    // should only be used by implementations of extension API methods (which
    // themselves are loaded in ExtensionParent.apiManager.global and therefore
    // imply the initialization of ExtensionParent.apiManager.global).
    return HttpObserverManager.getBrowserData(channel).tabId;
  },

  onBeforeRequest,
  onBeforeSendHeaders,
  onSendHeaders,
  onHeadersReceived,
  onAuthRequired,
  onBeforeRedirect,
  onResponseStarted,
  onCompleted,
  onErrorOccurred,

  getSecurityInfo: details => {
    let channel = ChannelWrapper.getRegisteredChannel(
      details.id,
      details.policy,
      details.remoteTab
    );
    if (channel) {
      return lazy.SecurityInfo.getSecurityInfo(
        channel.channel,
        details.options
      );
    }
  },
};
PK
!<N�Ȧ)�)4chrome/toolkit/skin/classic/global/global-shared.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* all localizable skin settings shall live here */

@import url("chrome://global/skin/design-system/tokens-platform.css");
@import url("chrome://global/skin/design-system/text-and-typography.css");

@import url("chrome://global/locale/intl.css");
@import url("chrome://global/content/widgets.css");

@import url("close-icon.css");

@namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");

:root {
  --default-focusring-width: 2px;
  --default-focusring: var(--default-focusring-width) dotted;

  --arrowpanel-background: Field;
  --arrowpanel-color: FieldText;
  --arrowpanel-border-color: ThreeDShadow;
  --arrowpanel-border-radius: 8px;
  --arrowpanel-padding: 16px;

  --arrowpanel-dimmed: color-mix(in srgb, currentColor 17%, transparent);
  --arrowpanel-dimmed-further: color-mix(in srgb, currentColor 30%, transparent);

  --panel-separator-color: color-mix(in srgb, currentColor 25%, transparent);
  --panel-disabled-color: color-mix(in srgb, currentColor 40%, transparent);

  --popup-notification-body-width: calc(31em - calc(2 * var(--arrowpanel-padding)));

  --input-border-color: color-mix(in srgb, currentColor 46%, transparent);

  --toolbarbutton-icon-fill: currentColor;
  --toolbarbutton-disabled-opacity: 0.4;

  --toolbar-field-background-color: Field;
  --toolbar-field-color: FieldText;
  --toolbar-field-border-color: var(--input-border-color);
  --toolbar-field-focus-background-color: Field;
  --toolbar-field-focus-color: FieldText;
  --toolbar-field-focus-border-color: color-mix(in srgb, var(--focus-outline-color) 50%, transparent);

  --toolbar-non-lwt-bgcolor: color-mix(in srgb, -moz-dialog 85%, white);
  --toolbar-non-lwt-textcolor: -moz-dialogtext;
  --toolbar-bgcolor: var(--toolbar-non-lwt-bgcolor);
  --toolbar-color: var(--toolbar-non-lwt-textcolor);

  &[lwtheme] {
    --toolbar-bgcolor: rgba(255,255,255,.4);
    --toolbar-color: var(--lwt-text-color, inherit);
  }

  /* This color scheme should match --toolbar-color. However, note that
   * individual toolbars might override this (see ToolbarIconColor and the
   * brighttext attribute). */
  --toolbar-color-scheme: light dark;
  &[lwt-toolbar="light"] {
    --toolbar-color-scheme: light;
  }
  &[lwt-toolbar="dark"] {
    --toolbar-color-scheme: dark;
  }

  @media (prefers-contrast) {
    /* Reduce the opacity of disabled toolbar buttons in order to increase
       contrast with the enabled state. */
    --toolbarbutton-disabled-opacity: 0.3;

    --panel-separator-color: currentColor;
    --toolbar-field-focus-border-color: var(--focus-outline-color);

    --input-border-color: currentColor;

    &:not([lwtheme]) {
      --panel-disabled-color: GrayText;
    }
  }

  @media (forced-colors) {
    --input-border-color: ThreeDShadow;
  }

  background-color: -moz-Dialog;
  color: -moz-DialogText;
  font: message-box;

  &[dialogroot] {
    font: menu;
  }
}

:is(menupopup, panel):where([type=arrow]) {
  --panel-background: var(--arrowpanel-background);
  --panel-color: var(--arrowpanel-color);
  --panel-border-color: var(--arrowpanel-border-color);
  --panel-border-radius: var(--arrowpanel-border-radius);
  --panel-padding: var(--arrowpanel-padding);
}

/* Lightweight theme roots */

:root[lwtheme] {
  toolbar,
  &[lwt-popup="light"] panel {
    color-scheme: light;
  }
  toolbar[brighttext],
  &[lwt-popup="dark"] panel {
    color-scheme: dark;
  }
}

:root[lwtheme-image] {
  text-shadow: 0 -0.5px 1.5px white;
}

:root[lwtheme-image][lwtheme-brighttext] {
  text-shadow: 1px 1px 1.5px black;
}

/* General styles */

moz-input-box,
html|input {
  min-width: 0;
}

html|input {
  margin: 2px 4px;
}

html|button,
html|input,
html|select,
html|textarea {
  font: inherit;
}

html|input:where(:not([type=radio i], [type=checkbox i], [type=range i])),
html|textarea {
  appearance: none;
  padding: var(--space-small);
  border: 1px solid var(--input-border-color);
  border-radius: var(--border-radius-small);
  margin: 0;
  background-color: var(--input-bgcolor, Field);
  color: var(--input-color, FieldText);

  &:where(:user-invalid) {
    border-color: var(--outline-color-error);
  }

  &:where(:focus-visible) {
    outline: var(--focus-outline);
    outline-offset: var(--focus-outline-inset);
  }
}

/* adapted from toolkit/themes/shared/menulist-shared.css */
html|select:where(:not([size], [multiple])) {
  appearance: none;
  padding: 6px 16px;
  padding-inline-end: 32px; /* 2 * 10px padding + image's 12px width */
  margin: 5px 2px 3px;
  border: none;
  border-radius: var(--border-radius-small);

  font-weight: var(--font-weight-bold);

  color: var(--button-color, ButtonText);
  background-color: var(--button-bgcolor, ButtonFace);
  background-image: url(chrome://global/skin/icons/arrow-down-12.svg);
  background-position: right 10px center;
  background-repeat: no-repeat;
  -moz-context-properties: fill;
  fill: currentColor;

  &:-moz-locale-dir(rtl) {
    background-position-x: left 10px;
  }

  &:hover {
    background-color: var(--button-hover-bgcolor, color-mix(in srgb, currentColor 10%, ButtonFace));
  }

  &:hover:active {
    background-color: var(--button-active-bgcolor, color-mix(in srgb, currentColor 20%, ButtonFace));
  }

  &:focus-visible {
    outline: var(--focus-outline);
    outline-offset: var(--focus-outline-offset);
  }
}

.header {
  font-weight: var(--font-weight-bold);
}

.indent {
  margin-inline-start: 23px;
}

.box-padded {
  padding: 5px;
}

/* XUL iframe */

xul|iframe {
  border: none;
  min-width: 10px;
  min-height: 10px;
}

/* Label/description formatting */

xul|description,
xul|label {
  cursor: default;
  margin-block: 1px 2px;
  margin-inline: 6px 5px;
}

xul|description {
  margin-bottom: 4px;
}

xul|label[disabled="true"] {
  color: GrayText;
}

.tooltip-label {
  margin: 0;
  word-wrap: break-word;
  /* We must specify a min-width, otherwise word-wrap:break-word doesn't work.
     See Bug 630864. */
  min-width: 1px;
}

/* Links */

.text-link,
a,
::part(support-link) {
  color: var(--link-color);
  cursor: pointer;
  text-decoration: underline;

  &:focus-visible {
    outline: var(--focus-outline);
    outline-offset: var(--link-focus-outline-offset);
    border-radius: 1px;
  }
}


/* Override properties set on buttons, to display the links without unwanted styling */
button.text-link {
  appearance: none;
  background-color: transparent;
  border: none;
  min-width: 0;
  margin: 0;
  padding: 0;
  font: inherit;
}

button.text-link .button-text {
 /* Cancel out the default internal margin */
  margin: 0;
}

/* Textbox context menu */

.textbox-contextmenu:-moz-locale-dir(rtl) {
  direction: rtl;
}

/* Panel footer buttons */

.panel-footer {
  margin: 8px 16px 16px;
}

.footer-button {
  appearance: none;
  border: 0;
  border-radius: var(--border-radius-small);
  color: var(--button-color, inherit);
  background-color: var(--button-bgcolor, color-mix(in srgb, currentColor 13%, transparent));
  padding: .45em 1em;
  min-height: var(--size-item-large);
  font-weight: 600;
  min-width: 0;
  margin-inline: 8px 0;
  margin-bottom: 0;

  &.small-button {
    margin: 0;
    min-height: var(--size-item-medium);
    font-size: var(--font-size-small);
    align-items: center;
  }

  &[disabled] {
    opacity: 0.4;
  }

  &:focus-visible {
    outline: var(--focus-outline);
    outline-offset: var(--focus-outline-offset);
  }

  &:not([disabled]) {
    &:hover {
      background-color: var(--button-hover-bgcolor, color-mix(in srgb, currentColor 17%, transparent));
    }
    &:hover:active {
      background-color: var(--button-active-bgcolor, color-mix(in srgb, currentColor 30%, transparent));
    }
    &[default],
    &.primary {
      color: var(--button-primary-color);
      background-color: var(--button-primary-bgcolor);

      &:hover {
        background-color: var(--button-primary-hover-bgcolor);
      }
      &:hover:active {
        background-color: var(--button-primary-active-bgcolor);
      }
    }
  }

  &[type=menu] > .button-box > .button-menu-dropmarker {
    appearance: none;
    display: flex;
    content: url("chrome://global/skin/icons/arrow-down-12.svg");
    -moz-context-properties: fill;
    fill: currentColor;
  }
}

/* Autoscroll popup */

.autoscroller {
  appearance: none;

  /* Resets the native styles in windows and macOS */
  border: none;
  background-color: transparent;
  -moz-window-shadow: none;

  --autoscroll-background-image: url("chrome://global/skin/icons/autoscroll.svg");
  --panel-border-color: rgba(0,0,0,.4);
  --panel-padding: 0;
  --panel-background: rgba(249,249,250,.8) no-repeat center var(--autoscroll-background-image);
  --panel-shadow-margin: 4px;
  /* Set pointer-events: none; so that mousemove events can be handled by AutoScrollChild.sys.mjs. */
  pointer-events: none;
}

.autoscroller::part(content) {
  border-radius: 100%;
  background-clip: padding-box;
  box-shadow: 0 0 var(--panel-shadow-margin) rgba(0,0,0,.2);
  width: 100%;
  height: 100%;
}

.autoscroller[scrolldir="NS"] {
  --autoscroll-background-image: url("chrome://global/skin/icons/autoscroll-vertical.svg");
}

.autoscroller[scrolldir="EW"] {
  --autoscroll-background-image: url("chrome://global/skin/icons/autoscroll-horizontal.svg");
}

/* Combobox dropdown renderer */
#ContentSelectDropdown > menupopup {
  /* The menupopup itself should always be rendered LTR to ensure the scrollbar aligns with
   * the dropdown arrow on the dropdown widget. If a menuitem is RTL, its style will be set accordingly */
  direction: ltr;
}

#ContentSelectDropdown > menupopup::part(arrowscrollbox-scrollbox) {
  scrollbar-width: var(--content-select-scrollbar-width, auto);
}

#ContentSelectDropdown > menupopup[customoptionstyling="true"]::part(arrowscrollbox) {
  /* When authors specify a custom background, we use background-image to specify the author-supplied color.
   * In that case, we don't want stuff like custom appearance or custom
   * backgrounds, so we make sure to apply it on top of the default background. */
  background-image: var(--content-select-background-image, none);
  background-color: -moz-Combobox;
}

/* Full width separator in select */
#ContentSelectDropdown menuseparator {
  padding-inline: 0;
}

/* Indent options in optgroups */
.contentSelectDropdown-ingroup .menu-iconic-text {
  padding-inline-start: 2em;
}

.deemphasized {
  color: var(--text-color-deemphasized);
}
PK
!<���-modules/services-settings/SharedUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Common logic shared by RemoteSettingsWorker.js (Worker) and the main thread.
 */

export var SharedUtils = {
  /**
   * Check that the specified content matches the expected size and SHA-256 hash.
   * @param {ArrayBuffer} buffer binary content
   * @param {Number} size expected file size
   * @param {String} size expected file SHA-256 as hex string
   * @returns {boolean}
   */
  async checkContentHash(buffer, size, hash) {
    const bytes = new Uint8Array(buffer);
    // Has expected size? (saves computing hash)
    if (bytes.length !== size) {
      return false;
    }
    // Has expected content?
    const hashBuffer = await crypto.subtle.digest("SHA-256", bytes);
    const hashBytes = new Uint8Array(hashBuffer);
    const toHex = b => b.toString(16).padStart(2, "0");
    const hashStr = Array.from(hashBytes, toHex).join("");
    return hashStr == hash;
  },

  /**
   * Load (from disk) the JSON file distributed with the release for this collection.
   * @param {String}  bucket
   * @param {String}  collection
   */
  async loadJSONDump(bucket, collection) {
    // When using the preview bucket, we still want to load the main dump.
    // But we store it locally in the preview bucket.
    const jsonBucket = bucket.replace("-preview", "");
    const fileURI = `resource://app/defaults/settings/${jsonBucket}/${collection}.json`;
    let response;
    try {
      response = await fetch(fileURI);
    } catch (e) {
      // Return null if file is missing.
      return { data: null, timestamp: null };
    }
    // Will throw if JSON is invalid.
    return response.json();
  },
};
PK
!<k!W

Dchrome/toolkit/skin/classic/global/design-system/tokens-platform.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* DO NOT EDIT this file directly, instead modify design-tokens.json
 * and run `npm run build` to see your changes. */

@import url("chrome://global/skin/design-system/tokens-shared.css");

@layer tokens-foundation {
  :root,
  :host(.anonymous-content-host) {
    /** Attention Dot **/
    --attention-dot-color: AccentColor;

    /** Background Color **/
    --background-color-canvas: Canvas;

    /** Border **/
    --border-color-deemphasized: color-mix(in srgb, currentColor 24%, transparent);
    --border-color-interactive: color-mix(in srgb, currentColor 15%, var(--color-gray-60));

    /** Button **/
    --button-background-color: var(--button-bgcolor); /* TODO Bug 1821203 - Gray use needs to be consolidated */
    --button-background-color-hover: var(--button-hover-bgcolor);
    --button-background-color-active: var(--button-active-bgcolor);
    --button-text-color: var(--button-color);
    --button-text-color-primary: var(--button-primary-color);

    /** Color **/
    --color-accent-primary: var(--button-primary-bgcolor, AccentColor);
    --color-accent-primary-hover: var(--button-primary-hover-bgcolor);
    --color-accent-primary-active: var(--button-primary-active-bgcolor);

    /** Font Size **/
    --font-size-root: unset;
    --font-size-small: unset;
    --font-size-large: unset;
    --font-size-xlarge: unset;
    --font-size-xxlarge: unset;

    /** Link **/
    --link-color: LinkText;
    --link-color-hover: LinkText;
    --link-color-active: ActiveText;
    --link-color-visited: var(--link-color);

    /** Text **/
    --text-color: currentColor;
  }
}
PK
!<�+���Hchrome/toolkit/skin/classic/global/design-system/text-and-typography.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* Typography scale */
:root {
  font: message-box;
}

h1,
.heading-xlarge {
  font-weight: var(--font-weight-bold);
  font-size: var(--font-size-xxlarge);
}

h2,
.heading-large {
  font-weight: var(--font-weight-bold);
  font-size: var(--font-size-xlarge);
}

h3,
.heading-medium {
  font-weight: var(--font-weight-bold);
  font-size: var(--font-size-large);
}

/* Text helpers */
.text-deemphasized {
  font-size: var(--font-size-small);
  color: var(--text-color-deemphasized);
}

.text-error {
  color: var(--text-color-error);
  font-weight: var(--font-weight-bold);
}

.text-truncated-ellipsis {
  /* Will not work on `display: flex` items
    unless you wrap its contents with another element that has this class. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
PK
!<VV֝��/chrome/en-US/locale/en-US/global/css.properties
MimeNotCss=The stylesheet %1$S was not loaded because its MIME type, “%2$S”, is not “text/css”.
MimeNotCssWarn=The stylesheet %1$S was loaded as CSS even though its MIME type, “%2$S”, is not “text/css”.

PEDeclDropped=Declaration dropped.
PEDeclSkipped=Skipped to next declaration.
PEUnknownProperty=Unknown property ‘%1$S’.
PEPRSyntaxFieldEmptyInput=@property syntax descriptor is empty.
PEPRSyntaxFieldExpectedPipe=@property syntax descriptor ‘%S’ contains components without a pipe between them.
PEPRSyntaxFieldInvalidNameStart=@property syntax descriptor ‘%S’ contains a component name that starts with an invalid character.
PEPRSyntaxFieldInvalidName=@property syntax descriptor ‘%S’ contains a component name with an invalid character.
PEPRSyntaxFieldUnclosedDataTypeName=@property syntax descriptor ‘%S’ contains an unclosed data type name.
PEPRSyntaxFieldUnexpectedEOF=@property syntax descriptor ‘%S’ is incomplete.
PEPRSyntaxFieldUnknownDataTypeName=@property syntax descriptor ‘%S’ contains an unknown data type name.
PEValueParsingError=Error in parsing value for ‘%1$S’.
PEUnknownAtRule=Unrecognized at-rule or error parsing at-rule ‘%1$S’.
PEMQUnexpectedOperator=Unexpected operator in media list.
PEMQUnexpectedToken=Unexpected token ‘%1$S’ in media list.
PEAtNSUnexpected=Unexpected token within @namespace: ‘%1$S’.
PEKeyframeBadName=Expected identifier for name of @keyframes rule.
PEBadSelectorRSIgnored=Ruleset ignored due to bad selector.
PEBadSelectorKeyframeRuleIgnored=Keyframe rule ignored due to bad selector.
PESelectorGroupNoSelector=Selector expected.
PESelectorGroupExtraCombinator=Dangling combinator.
PEClassSelNotIdent=Expected identifier for class selector but found ‘%1$S’.
PETypeSelNotType=Expected element name or ‘*’ but found ‘%1$S’.
PEUnknownNamespacePrefix=Unknown namespace prefix ‘%1$S’.
PEAttributeNameExpected=Expected identifier for attribute name but found ‘%1$S’.
PEAttributeNameOrNamespaceExpected=Expected attribute name or namespace but found ‘%1$S’.
PEAttSelNoBar=Expected ‘|’ but found ‘%1$S’.
PEAttSelUnexpected=Unexpected token in attribute selector: ‘%1$S’.
PEAttSelBadValue=Expected identifier or string for value in attribute selector but found ‘%1$S’.
PEPseudoSelBadName=Expected identifier for pseudo-class or pseudo-element but found ‘%1$S’.
PEPseudoSelEndOrUserActionPC=Expected end of selector or a user action pseudo-class after pseudo-element but found ‘%1$S’.
PEPseudoSelUnknown=Unknown pseudo-class or pseudo-element ‘%1$S’.
PEPseudoClassArgNotIdent=Expected identifier for pseudo-class parameter but found ‘%1$S’.
PEColorNotColor=Expected color but found ‘%1$S’.
PEParseDeclarationDeclExpected=Expected declaration but found ‘%1$S’.
PEUnknownFontDesc=Unknown descriptor ‘%1$S’ in @font-face rule.
PEMQExpectedFeatureName=Expected media feature name but found ‘%1$S’.
PEMQNoMinMaxWithoutValue=Media features with min- or max- must have a value.
PEMQExpectedFeatureValue=Found invalid value for media feature.
PEExpectedNoneOrURL=Expected ‘none’ or URL but found ‘%1$S’.
PEExpectedNoneOrURLOrFilterFunction=Expected ‘none’, URL, or filter function but found ‘%1$S’.
PEDisallowedImportRule=@import rules are not yet valid in constructed stylesheets.
PENeverMatchingHostSelector=:host selector in ‘%S’ is not featureless and will never match. Maybe you intended to use :host()?
PEImportantDeclError=Property cannot be declared as !important in this context.

TooLargeDashedRadius=Border radius is too large for ‘dashed’ style (the limit is 100000px). Rendering as solid.
TooLargeDottedRadius=Border radius is too large for ‘dotted’ style (the limit is 100000px). Rendering as solid.
PK
!<�%�)kk)chrome/en-US/locale/en-US/global/intl.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This file contains all localizable skin settings such as
 *   font, layout, and geometry
 */
window {
  font: 3mm tahoma,arial,helvetica,sans-serif;
}
PK
!<b@�uu/chrome/en-US/locale/en-US/global/xul.properties
PINotInProlog=<?%1$S?> processing instruction does not have any effect outside the prolog anymore (see bug 360119).
PK
!<��&)chrome/toolkit/content/global/widgets.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== widgets.css =====================================================
   == Styles ported from XBL <resources>, loaded by "global.css".
   ======================================================================= */

@import url("chrome://global/content/autocomplete.css");
@import url("chrome://global/skin/autocomplete.css");
@import url("chrome://global/skin/button.css");
@import url("chrome://global/skin/checkbox.css");
@import url("chrome://global/skin/findbar.css");
@import url("chrome://global/skin/menu.css");
@import url("chrome://global/skin/notification.css");
@import url("chrome://global/skin/numberinput.css");
@import url("chrome://global/skin/popup.css");
@import url("chrome://global/skin/popupnotification.css");
@import url("chrome://global/skin/radio.css");
@import url("chrome://global/skin/richlistbox.css");
@import url("chrome://global/skin/splitter.css");
@import url("chrome://global/skin/tabbox.css");
@import url("chrome://global/skin/toolbar.css");
@import url("chrome://global/skin/toolbarbutton.css");
@import url("chrome://global/skin/tree/tree.css");
PK
!<���dd1chrome/toolkit/skin/classic/global/close-icon.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

.close-icon {
  appearance: none;
  -moz-context-properties: fill, fill-opacity;
  list-style-image: url(chrome://global/skin/icons/close.svg);
  border-radius: 4px;
  color: inherit;
  fill: currentColor;
  padding: 2px;
  width: 20px;
  height: auto;
  outline: var(--toolbarbutton-outline);

  &:hover {
    outline-color: var(--toolbarbutton-hover-outline-color);
  }

  &:hover:active {
    outline-color: var(--toolbarbutton-active-outline-color);
  }
}

@media not (forced-colors) {
  .close-icon:hover {
    background-color: color-mix(in srgb, currentColor 10%, transparent);
  }

  .close-icon:hover:active {
    background-color: color-mix(in srgb, currentColor 20%, transparent);
  }
}

.close-icon > .button-icon,
.close-icon > .button-box > .button-icon,
.close-icon > .toolbarbutton-icon {
  margin: 0;
}

.close-icon > .button-box > .button-text,
.close-icon > .toolbarbutton-text {
  display: none;
}
PK
!<�ߒ�K
K
9chrome/en-US/locale/en-US/global/layout_errors.properties
ImageMapRectBoundsError=The “coords” attribute of the <area shape="rect"> tag is not in the “left,top,right,bottom” format.
ImageMapCircleWrongNumberOfCoords=The “coords” attribute of the <area shape="circle"> tag is not in the “center-x,center-y,radius” format.
ImageMapCircleNegativeRadius=The “coords” attribute of the <area shape="circle"> tag has a negative radius.
ImageMapPolyWrongNumberOfCoords=The “coords” attribute of the <area shape="poly"> tag is not in the “x1,y1,x2,y2 …” format.
ImageMapPolyOddNumberOfCoords=The “coords” attribute of the <area shape="poly"> tag is missing the last “y” coordinate (the correct format is “x1,y1,x2,y2 …”).

ScrollLinkedEffectFound3=This site appears to use a scroll-linked positioning effect. This may not work well with asynchronous panning; see https://firefox-source-docs.mozilla.org/performance/scroll-linked_effects.html for further details and to join the discussion on related tools and features!

CompositorAnimationWarningContentTooLargeArea=Animation cannot be run on the compositor because the area of the frame (%1$S) is too large relative to the viewport (larger than %2$S)
CompositorAnimationWarningContentTooLarge2=Animation cannot be run on the compositor because the frame size (%1$S, %2$S) is too large relative to the viewport (larger than (%3$S, %4$S)) or larger than the maximum allowed value (%5$S, %6$S)
CompositorAnimationWarningTransformSVG=Animations of ‘transform’ on elements with SVG transforms cannot be run on the compositor
CompositorAnimationWarningTransformFrameInactive=Animation cannot be run on the compositor because the frame was not marked active for ‘transform’ animation
CompositorAnimationWarningTransformIsBlockedByImportantRules=Transform animation cannot be run on the compositor because transform-related properties are overridden by !important rules
CompositorAnimationWarningOpacityFrameInactive=Animation cannot be run on the compositor because the frame was not marked active for ‘opacity’ animation
CompositorAnimationWarningHasRenderingObserver=Animation cannot be run on the compositor because the element has rendering observers (-moz-element or SVG clipping/masking)
CompositorAnimationWarningHasCurrentColor=Animations of ‘background-color’ cannot be run on the compositor with ‘current-color’ keyframe.

ZoomPropertyWarning=This page uses the non standard property “zoom”. Consider using calc() in the relevant property values, or using “transform” along with “transform-origin: 0 0”.

PrincipalWritingModePropagationWarning=When rendering the <html> element, the used values of CSS properties “writing-mode”, “direction”, and “text-orientation” on the <html> element are taken from the computed values of the <body> element, not from the <html> element’s own values. Consider setting these properties on the :root CSS pseudo-class. For more information see “The Principal Writing Mode” in https://www.w3.org/TR/css-writing-modes-3/#principal-flow

ScrollAnchoringDisabledInContainer=Scroll anchoring was disabled in a scroll container because of too many consecutive adjustments (%1$S) with too little total distance (%2$S px average, %3$S px total).

ForcedLayoutStart=Layout was forced before the page was fully loaded. If stylesheets are not yet loaded this may cause a flash of unstyled content.
PK
!<�|n
44Bchrome/toolkit/skin/classic/global/design-system/tokens-shared.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* DO NOT EDIT this file directly, instead modify design-tokens.json
 * and run `npm run build` to see your changes. */

@layer tokens-foundation, tokens-prefers-contrast, tokens-forced-colors;

@layer tokens-foundation {
  :root,
  :host(.anonymous-content-host) {
    /** Background Color **/
    --background-color-box: light-dark(var(--color-white), var(--color-gray-80));
    --background-color-critical: light-dark(var(--color-red-05), var(--color-red-80));
    --background-color-information: light-dark(var(--color-blue-05), var(--color-blue-80));
    --background-color-success: light-dark(var(--color-green-05), var(--color-green-80));
    --background-color-warning: light-dark(var(--color-yellow-05), var(--color-yellow-80));

    /** Border **/
    --border-color-interactive-hover: var(--border-color-interactive);
    --border-color-interactive-active: var(--border-color-interactive);
    --border-color-interactive-disabled: var(--border-color-interactive);
    --border-radius-circle: 9999px;
    --border-radius-small: 4px;
    --border-radius-medium: 8px;
    --border-width: 1px;

    /** Box Shadow **/
    --box-shadow-10: 0 1px 4px var(--color-black-a10);

    /** Button **/
    --button-background-color-disabled: var(--button-background-color);
    --button-background-color-destructive: light-dark(var(--color-red-50), var(--color-red-30));
    --button-background-color-destructive-hover: light-dark(var(--color-red-60), var(--color-red-10));
    --button-background-color-destructive-active: light-dark(var(--color-red-70), var(--color-red-05));
    --button-background-color-destructive-disabled: var(--button-background-color-destructive);
    --button-background-color-ghost: transparent;
    --button-background-color-ghost-hover: var(--button-background-color-hover);
    --button-background-color-ghost-active: var(--button-background-color-active);
    --button-background-color-ghost-disabled: var(--button-background-color-ghost);
    --button-background-color-primary: var(--color-accent-primary);
    --button-background-color-primary-hover: var(--color-accent-primary-hover);
    --button-background-color-primary-active: var(--color-accent-primary-active);
    --button-background-color-primary-disabled: var(--button-background-color-primary);
    --button-border: var(--border-width) solid var(--button-border-color);
    --button-border-color: transparent;
    --button-border-color-hover: var(--button-border-color);
    --button-border-color-active: var(--button-border-color);
    --button-border-color-disabled: var(--button-border-color);
    --button-border-color-destructive: transparent;
    --button-border-color-destructive-hover: var(--button-border-color-destructive);
    --button-border-color-destructive-active: var(--button-border-color-destructive);
    --button-border-color-destructive-disabled: var(--button-border-color-destructive);
    --button-border-color-ghost: var(--button-border-color);
    --button-border-color-ghost-hover: var(--button-border-color-hover);
    --button-border-color-ghost-active: var(--button-border-color-active);
    --button-border-color-ghost-disabled: var(--button-border-color-disabled);
    --button-border-color-primary: transparent;
    --button-border-color-primary-hover: var(--button-border-color-primary);
    --button-border-color-primary-active: var(--button-border-color-primary);
    --button-border-color-primary-disabled: var(--button-border-color-primary);
    --button-border-radius: var(--border-radius-small);
    --button-font-size: var(--font-size-root);
    --button-font-size-small: var(--font-size-small);
    --button-font-weight: var(--font-weight-bold);
    --button-min-height: var(--size-item-large);
    --button-min-height-small: var(--size-item-medium);
    --button-opacity-disabled: 0.5;
    --button-padding: var(--space-xsmall) var(--space-large);
    --button-padding-icon: 0;
    --button-size-icon: var(--button-min-height);
    --button-size-icon-small: var(--button-min-height-small);
    --button-text-color-hover: var(--button-text-color);
    --button-text-color-active: var(--button-text-color);
    --button-text-color-disabled: var(--button-text-color);
    --button-text-color-destructive: light-dark(var(--color-gray-05), var(--color-gray-100));
    --button-text-color-destructive-hover: var(--button-text-color-destructive);
    --button-text-color-destructive-active: var(--button-text-color-destructive);
    --button-text-color-destructive-disabled: var(--button-text-color-destructive);
    --button-text-color-ghost: var(--button-text-color);
    --button-text-color-ghost-hover: var(--button-text-color-hover);
    --button-text-color-ghost-active: var(--button-text-color-active);
    --button-text-color-ghost-disabled: var(--button-text-color-disabled);
    --button-text-color-primary-hover: var(--button-text-color-primary);
    --button-text-color-primary-active: var(--button-text-color-primary-hover);
    --button-text-color-primary-disabled: var(--button-text-color-primary);

    /** Checkbox **/
    --checkbox-margin-inline: var(--space-small);
    --checkbox-size: var(--size-item-small); /* TODO Bug 1876537: Make this em-based, probably? */

    /** Color **/
    --color-black-a10: rgba(0, 0, 0, 0.1);
    --color-blue-05: #deeafc;
    --color-blue-30: #73a7f3;
    --color-blue-50: #0060df;
    --color-blue-60: #0250bb;
    --color-blue-70: #054096;
    --color-blue-80: #003070;
    --color-cyan-20: #aaf2ff;
    --color-cyan-30: #80ebff;
    --color-cyan-50: #00ddff;
    --color-gray-05: #fbfbfe;
    --color-gray-30: #bac2ca;
    --color-gray-50: #bfbfc9;
    --color-gray-60: #8f8f9d;
    --color-gray-70: #5b5b66;
    --color-gray-80: #23222b;
    --color-gray-90: #1c1b22;
    --color-gray-100: #15141a;
    --color-green-05: #d8eedc;
    --color-green-30: #4dbc87;
    --color-green-50: #017a40;
    --color-green-80: #004725;
    --color-red-05: #ffe8e8;
    --color-red-10: #ffbdc5;
    --color-red-20: #ff9aa2;
    --color-red-30: #f37f98;
    --color-red-50: #d7264c;
    --color-red-60: #ac1e3d;
    --color-red-70: #8a1831;
    --color-red-80: #690f22;
    --color-white: #ffffff;
    --color-yellow-05: #ffebcd;
    --color-yellow-30: #e49c49;
    --color-yellow-50: #cd411e;
    --color-yellow-80: #5a3100;

    /** Focus Outline **/
    --focus-outline: var(--focus-outline-width) solid var(--focus-outline-color);
    --focus-outline-color: var(--color-accent-primary);
    --focus-outline-inset: calc(-1 * var(--focus-outline-width));
    --focus-outline-offset: 2px;
    --focus-outline-width: 2px;

    /** Font Weight **/
    --font-weight: normal;
    --font-weight-bold: 600;

    /** Icon **/
    --icon-color: light-dark(var(--color-gray-70), var(--color-gray-05));
    --icon-color-critical: light-dark(var(--color-red-50), var(--color-red-30));
    --icon-color-information: light-dark(var(--color-blue-50), var(--color-blue-30));
    --icon-color-success: light-dark(var(--color-green-50), var(--color-green-30));
    --icon-color-warning: light-dark(var(--color-yellow-50), var(--color-yellow-30));
    --icon-size-default: var(--size-item-small);

    /** Input - Text **/
    --input-text-min-height: var(--button-min-height);

    /** Input - Space **/
    --input-space-block: var(--space-xsmall);

    /** Link **/
    /**
     * Not using --force-outline-offset for links because that's intended for
     * elements with a background, and we only want a slight offset here while
     * not overlapping adjacent text
     */
    --link-focus-outline-offset: 1px;

    /** Outline Color **/
    --outline-color-error: light-dark(var(--color-red-50), var(--color-red-20));

    /** Size **/
    --size-item-small: 16px;
    --size-item-medium: 28px;
    --size-item-large: 32px;

    /** Space **/
    --space-xxsmall: calc(0.5 * var(--space-xsmall));
    --space-xsmall: 0.267rem;
    --space-small: calc(2 * var(--space-xsmall));
    --space-medium: calc(3 * var(--space-xsmall));
    --space-large: calc(4 * var(--space-xsmall));
    --space-xlarge: calc(6 * var(--space-xsmall));
    --space-xxlarge: calc(8 * var(--space-xsmall));

    /** Text **/
    --text-color-deemphasized: color-mix(in srgb, currentColor 69%, transparent);
    --text-color-error: light-dark(var(--color-red-50), var(--color-red-20));
  }
}

/* Bug 1879900: Can't nest media queries inside of :host, :root selector
   until Bug 1879349 lands */
@layer tokens-prefers-contrast {
  @media (prefers-contrast) {
    :root,
    :host(.anonymous-content-host) {
      /** Attention Dot **/
      --attention-dot-color: AccentColor;

      /** Background Color **/
      --background-color-box: var(--background-color-canvas);
      --background-color-canvas: Canvas;
      --background-color-critical: var(--background-color-canvas);
      --background-color-information: var(--background-color-canvas);
      --background-color-success: var(--background-color-canvas);
      --background-color-warning: var(--background-color-canvas);

      /** Border **/
      --border-color: var(--text-color);
      --border-color-deemphasized: currentColor;
      --border-color-interactive: var(--text-color);

      /** Button **/
      --button-background-color-ghost: var(--button-background-color);
      --button-border-color: var(--button-text-color);

      /** Icon **/
      --icon-color: var(--text-color);
      --icon-color-critical: var(--icon-color);
      --icon-color-information: var(--icon-color);
      --icon-color-success: var(--icon-color);
      --icon-color-warning: var(--icon-color);

      /** Link **/
      --link-color: LinkText;
      --link-color-hover: LinkText;
      --link-color-active: ActiveText;
      --link-color-visited: var(--link-color);

      /** Outline Color **/
      --outline-color-error: var(--border-color);

      /** Text **/
      --text-color: CanvasText;
      --text-color-deemphasized: inherit;
      --text-color-error: inherit;
    }
  }
}

/* Bug 1879900: Can't nest media queries inside of :host, :root selector
   until Bug 1879349 lands */
@layer tokens-forced-colors {
  @media (forced-colors) {
    :root,
    :host(.anonymous-content-host) {
      /** Border **/
      --border-color-deemphasized: ButtonText;
      --border-color-interactive: ButtonText;
      --border-color-interactive-hover: SelectedItem;
      --border-color-interactive-active: ButtonText;
      --border-color-interactive-disabled: GrayText;

      /** Button **/
      --button-background-color: ButtonFace; /* TODO Bug 1821203 - Gray use needs to be consolidated */
      --button-background-color-hover: SelectedItemText;
      --button-background-color-active: SelectedItemText;
      --button-background-color-disabled: ButtonFace;
      --button-background-color-destructive: var(--button-background-color-primary);
      --button-background-color-destructive-hover: var(--button-background-color-primary-hover);
      --button-background-color-destructive-active: var(--button-background-color-primary-active);
      --button-background-color-destructive-disabled: var(--button-background-color-primary-disabled);
      --button-background-color-ghost: var(--button-background-color);
      --button-background-color-ghost-disabled: var(--button-background-color-disabled);
      --button-background-color-primary-disabled: var(--button-text-color-disabled);
      --button-border-color: var(--border-color-interactive);
      --button-border-color-hover: var(--border-color-interactive-hover);
      --button-border-color-active: var(--border-color-interactive-active);
      --button-border-color-disabled: var(--border-color-interactive-disabled);
      --button-border-color-destructive: var(--button-border-color-primary);
      --button-border-color-destructive-hover: var(--button-border-color-primary-hover);
      --button-border-color-destructive-active: var(--button-border-color-primary-active);
      --button-border-color-destructive-disabled: var(--button-border-color-primary-disabled);
      --button-border-color-primary: ButtonFace;
      --button-border-color-primary-hover: SelectedItemText;
      --button-border-color-primary-active: ButtonText;
      --button-opacity-disabled: 1;
      --button-text-color: ButtonText;
      --button-text-color-hover: SelectedItem;
      --button-text-color-active: SelectedItem;
      --button-text-color-disabled: GrayText;
      --button-text-color-destructive: var(--button-text-color-primary);
      --button-text-color-destructive-hover: var(--button-text-color-primary-hover);
      --button-text-color-destructive-active: var(--button-text-color-primary-active);
      --button-text-color-destructive-disabled: var(--button-text-color-primary-disabled);
      --button-text-color-primary: ButtonFace;
      --button-text-color-primary-hover: SelectedItemText;

      /** Color **/
      --color-accent-primary: ButtonText;
      --color-accent-primary-hover: SelectedItem;
      --color-accent-primary-active: var(--color-accent-primary-hover);

      /** Focus Outline **/
      --focus-outline-color: var(--text-color);
    }
  }
}
PK
!<*���+chrome/toolkit/skin/classic/global/menu.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/menu-shared.css");

/* ===== menu.css =======================================================
  == Styles used by XUL menu-related elements.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: menu/menuitem ::::: */

menubar > menu {
  color: inherit;
  background-color: transparent;
  border-radius: 0;
  padding-bottom: 1px;
  border-bottom: 3px solid transparent;
}

menubar > menu[open] {
  border-bottom-color: AccentColor;
}

menuitem[default="true"],
menuitem.spell-suggestion,
menucaption {
  font-weight: bold;
}

/* ::::: menu/menuitems in menulist popups ::::: */

menulist > menupopup {
  font: inherit;
}

menulist > menupopup > :is(menuitem, menucaption, menu) {
  max-width: none;
}

/* ..... internal content .... */

.menu-text,
.menu-iconic-left,
.menu-iconic-text,
.menubar-left {
  margin-block: 0;
  margin-inline: 0 2px;
}

.menu-text {
  /* This is (18 + the size of end-padding on .menu-iconic-left)px */
  margin-inline-start: 21px;
}

.menu-accel,
.menu-iconic-accel {
  margin-block: 0;
  margin-inline-start: 7px;
}

.menu-accel-container {
  justify-content: flex-end;
}

.menu-iconic-left {
  min-width: 16px;
  /* We can only hardcode this, to make the default GTK icon<->label spacing */
  padding-inline-end: 3px;
}

.menu-iconic-icon {
  width: 16px;
  height: 16px;
}

.menubar-text {
  margin: 0 1px;
}

menulist > menupopup > :is(menuitem, menucaption, menu) > .menu-iconic-left {
  display: none;
}

menuitem:is([type="checkbox"], [checked="true"]) .menu-iconic-icon {
  appearance: auto;
  -moz-default-appearance: checkbox;
}

menuitem[type="radio"] .menu-iconic-icon {
  appearance: auto;
  -moz-default-appearance: radio;
}
PK
!<���**;chrome/en-US/locale/en-US/global/layout/HtmlForm.properties
Reset=Reset
Submit=Submit Query
Browse=Browse…
FileUpload=File Upload
DirectoryUpload=Select Folder to Upload
DirectoryPickerOkButtonLabel=Upload
ForgotPostWarning=Form contains enctype=%S, but does not contain method=post.  Submitting normally with method=GET and no enctype instead.
ForgotFileEnctypeWarning=Form contains a file input, but is missing method=POST and enctype=multipart/form-data on the form.  The file will not be sent.
DefaultFormSubject=Form Post from %S
CannotEncodeAllUnicode=A form was submitted in the %S encoding which cannot encode all Unicode characters, so user input may get corrupted. To avoid this problem, the page should be changed so that the form is submitted in the UTF-8 encoding either by changing the encoding of the page itself to UTF-8 or by specifying accept-charset=utf-8 on the form element.
AllSupportedTypes=All Supported Types
NoFileSelected=No file selected.
NoFilesSelected=No files selected.
NoDirSelected=No directory selected.
XFilesSelected=%S files selected.
ColorPicker=Choose a color
DefaultSummary=Details
PK
!<.|�\��,chrome/toolkit/skin/classic/global/popup.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: menupopup ::::: */

menupopup,
panel {
  /* We can always render in the preferred color scheme (unless otherwise
   * overridden). */
  color-scheme: light dark;

  min-width: 1px;
  --panel-background: Menu;
  --panel-color: MenuText;
  --panel-padding-block: 4px;
  --panel-padding: var(--panel-padding-block) 0;
  --panel-border-radius: 4px;
  --panel-border-color: ThreeDShadow;
  --panel-width: initial;

  --panel-shadow-margin: 0px;
  --panel-shadow: 0 0 var(--panel-shadow-margin) hsla(0,0%,0%,.2);
  -moz-window-input-region-margin: var(--panel-shadow-margin);
  margin: calc(-1 * var(--panel-shadow-margin));

  /* Panel design token theming */
  --background-color-canvas: var(--panel-background);

  @media (-moz-platform: linux) {
    --panel-border-radius: 8px;
    --panel-padding-block: 3px;

    @media (prefers-contrast) {
      --panel-border-color: color-mix(in srgb, currentColor 60%, transparent);
    }
  }

  @media (-moz-platform: linux) or (-moz-platform: windows) {
    /* To account for the box-shadow below */
    --panel-shadow-margin: 4px;
  }

  /* On some linux WMs we need to draw square menus because alpha is not available */
  @media (-moz-platform: linux) and (not (-moz-gtk-csd-transparency-available)) {
    --panel-shadow-margin: 0px !important;
    --panel-border-radius: 0px !important;
  }

  @media (-moz-platform: macos) {
    appearance: auto;
    -moz-default-appearance: menupopup;
    /* We set the default background here, rather than on ::part(content),
     * because otherwise it'd interfere with the native look. Non-native-looking
     * popups should get their background via --panel-background */
    background-color: Menu;
    --panel-background: none;
    --panel-border-color: transparent;
    /* This should be kept in sync with GetMenuMaskImage() */
    --panel-border-radius: 6px;
  }

  &::part(content) {
    display: flex;
    box-sizing: border-box;

    padding: var(--panel-padding);
    color: var(--panel-color);
    background: var(--panel-background);
    border-radius: var(--panel-border-radius);
    border: 1px solid var(--panel-border-color);
    width: var(--panel-width);
    box-shadow: var(--panel-shadow);
    margin: var(--panel-shadow-margin);

    min-width: 0;
    min-height: 0;

    /* Makes popup constraints work. Round up to avoid subpixel rounding
     * causing overflow, see bug 1846050 */
    max-height: round(up, 100% - 2 * var(--panel-shadow-margin), 1px);
    max-width: round(up, 100% - 2 * var(--panel-shadow-margin), 1px);

    overflow: clip; /* Don't let panel content overflow the border */
  }

  &[orient=vertical]::part(content) {
    flex-direction: column;
  }
}

menupopup {
  /* Also apply the padding in the inline axis for menus */
  --panel-padding: var(--panel-padding-block);

  @media (-moz-platform: windows) {
    > menu,
    > menuitem {
      padding-block: 0.5em;
      padding-inline-start: 1em;
    }
  }

  > menu > menupopup {
    /* Vertically align nested menupopups: the shadow plus the top padding plus top border */
    margin-top: calc(-1 * (var(--panel-shadow-margin) + var(--panel-padding-block) + 1px));
  }
}

/* Rules for popups associated with menulists */
menulist > menupopup {
  min-width: 0;

  @media (-moz-platform: windows) {
    font: inherit;
  }

  @media (-moz-platform: macos) {
    &:not([position]) {
      margin-inline-start: -13px;
      margin-top: -1px;
    }
  }
}

/* ::::: arrow panel ::::: */

panel:where([type="arrow"]) {
  appearance: none;
  background-color: transparent;

  &.panel-no-padding::part(content) {
    padding: 0;
  }
}

/* ::::: panel animations ::::: */

.animatable-menupopup,
panel[type="arrow"] {
  transition-timing-function: var(--animation-easing-function), ease-out;

  @media (-moz-panel-animations) and (prefers-reduced-motion: no-preference) {
    &:not([animate="false"]) {
      transition-duration: 0.18s;
    }
  }

  @media not (-moz-platform: macos) {
    transition-property: transform, opacity;
    will-change: transform, opacity;
    opacity: 0;
    transform: translateY(-70px);

    &[side="bottom"] {
      transform: translateY(70px);
    }
  }

  /* On Mac, use the properties "-moz-window-transform" and "-moz-window-opacity"
   * instead of "transform" and "opacity" for these animations.
   * The -moz-window* properties apply to the whole window including the
   * window's shadow, and they don't affect the window's "shape", so the
   * system doesn't have to recompute the shadow shape during the animation.
   * This makes them a lot faster. These properties are not implemented on
   * other platforms.
   */
  @media (-moz-platform: macos) {
    transition-property: -moz-window-transform, -moz-window-opacity;
    /* Only do the fade-in animation on pre-Big Sur to avoid missing shadows on
     * Big Sur, see bug 1672091. */
    @media not (-moz-mac-big-sur-theme) {
      -moz-window-opacity: 0;
      -moz-window-transform: translateY(-70px);

      &[side="bottom"] {
        -moz-window-transform: translateY(70px);
      }
    }
    /* If the @media rule above is removed, then we can also remove this */
    &[animate="cancel"] {
      -moz-window-opacity: 0;
    }
  }

  &[animate="cancel"] {
    -moz-window-transform: none;
    transform: none;
  }

  &:is([animate="false"], [animate="open"]) {
    opacity: 1;
    transform: none;
    -moz-window-opacity: 1;
    -moz-window-transform: none;
    transition-timing-function: var(--animation-easing-function), ease-in-out;
  }

  &[animating] {
    pointer-events: none;
  }
}
PK
!<s\��4chrome/en-US/locale/en-US/global/printing.properties
pagenumber=%1$d

pageofpages=%1$d of %2$d

PrintToFile=Print To File
print_error_dialog_title=Printer Error
printpreview_error_dialog_title=Print Preview Error

PERR_FAILURE=An error occurred while printing.

PERR_ABORT=The print job was aborted, or canceled.
PERR_NOT_AVAILABLE=Some printing functionality is not currently available.
PERR_NOT_IMPLEMENTED=Some printing functionality is not implemented yet.
PERR_OUT_OF_MEMORY=There is not enough free memory to print.
PERR_UNEXPECTED=There was an unexpected problem while printing.

PERR_GFX_PRINTER_NO_PRINTER_AVAILABLE=No printers available.
PERR_GFX_PRINTER_NO_PRINTER_AVAILABLE_PP=No printers available, cannot show print preview.
PERR_GFX_PRINTER_NAME_NOT_FOUND=The selected printer could not be found.
PERR_GFX_PRINTER_COULD_NOT_OPEN_FILE=Failed to open output file for print to file.
PERR_GFX_PRINTER_STARTDOC=Printing failed while starting the print job.
PERR_GFX_PRINTER_ENDDOC=Printing failed while completing the print job.
PERR_GFX_PRINTER_STARTPAGE=Printing failed while starting a new page.
PERR_GFX_PRINTER_DOC_IS_BUSY=Cannot print this document yet, it is still being loaded.
PERR_GFX_PRINTER_DOC_IS_BUSY_PP=Cannot print-preview this document yet, it is still being loaded.
PK
!<����zz/chrome/toolkit/skin/classic/global/splitter.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== splitter.css ===================================================
  == Styles used by the XUL splitter element.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: splitter (vertical) ::::: */

splitter {
  align-items: center;
  justify-content: center;
  cursor: ew-resize;

  @media (-moz-platform: linux) {
    appearance: auto;
    -moz-default-appearance: splitter;
  }
  @media (-moz-platform: macos) {
    min-width: 9px;
    min-height: 9px;
  }
  @media (-moz-platform: windows) {
    border-width: 0 1px;
    border-style: solid;
    border-inline-start-color: ThreeDHighlight;
    border-inline-end-color: ThreeDShadow;
    min-width: 6px;
    background-color: ThreeDFace;
  }
}

splitter[state="collapsed"][collapse="before"],
splitter[state="collapsed"][substate="before"],
splitter[state="collapsed"][collapse="after"]:-moz-locale-dir(rtl),
splitter[state="collapsed"][substate="after"]:-moz-locale-dir(rtl) {
  cursor: e-resize;
}

splitter[state="collapsed"][collapse="after"],
splitter[state="collapsed"][substate="after"],
splitter[state="collapsed"][collapse="before"]:-moz-locale-dir(rtl),
splitter[state="collapsed"][substate="before"]:-moz-locale-dir(rtl) {
  cursor: w-resize;
}

/* ::::: splitter (horizontal) ::::: */

splitter[orient="vertical"] {
  cursor: ns-resize;

  @media (-moz-platform: windows) {
    border-width: 1px 0;
    border-top-color: ThreeDHighlight;
    border-bottom-color: ThreeDShadow;
    min-height: 6px;
  }
}

splitter[orient="vertical"][state="collapsed"][collapse="before"],
splitter[orient="vertical"][state="collapsed"][substate="before"] {
  cursor: s-resize;
}

splitter[orient="vertical"][state="collapsed"][collapse="after"],
splitter[orient="vertical"][state="collapsed"][substate="after"] {
  cursor: n-resize;
}

splitter[disabled="true"] {
  cursor: default !important;
}
PK
!<U��j��/chrome/toolkit/skin/classic/global/checkbox.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");

checkbox {
  align-items: center;
  margin: 4px 2px;

  &[disabled="true"][native] {
    color: GrayText;
  }

  &[disabled="true"]:not([native]) > .checkbox-label-box {
    opacity: 0.4;
  }
}

.checkbox-icon {
  margin-inline-end: 2px;

  &:not([src]) {
    display: none;
  }
}

.checkbox-label {
  margin: 1px 0;
}

/* ::::: checkmark image ::::: */

.checkbox-check {
  appearance: auto;
  -moz-default-appearance: checkbox;
  margin-inline-end: var(--checkbox-margin-inline);

  &:not([native]) {
    -moz-theme: non-native;
    width: var(--checkbox-size);
    height: var(--checkbox-size);
  }

  checkbox:focus-visible > & {
    outline-style: auto;
  }
}

.checkbox-label[native] {
  margin: 0;
}

.checkbox-check[native] {
  align-items: center;

  @media (-moz-platform: windows) {
    width: 13px;
    height: 13px;
    margin-inline-end: 5px;
  }

  @media (-moz-platform: linux) {
    margin: 2px;
    margin-inline-end: 4px;
  }

  @media (-moz-platform: macos) {
    width: 1.3em;
    height: 1.3em;
    margin-block: 1px 0;
    margin-inline: 1px 3px;
    /* vertical-align tells native theming where to snap to. However, this doesn't
     * always work reliably because of bug 503833. */
    vertical-align: top;
  }
}

@media (-moz-platform: windows) {
  checkbox:where([native]) {
    margin: 2px 4px;
    padding-block: 1px;
    padding-inline: 4px 2px;
  }
}

@media (-moz-platform: macos) {
  checkbox:where([native]) {
    margin: 4px 2px;
  }

  .checkbox-label[native] {
    margin: 1px 0;
  }
}

@media (-moz-platform: linux) {
  checkbox:where([native]) {
    margin: 2px 4px;

    &:focus-visible > .checkbox-label-box {
      outline: var(--focus-outline);
    }
  }
}
PK
!<�;K��.chrome/toolkit/skin/classic/global/toolbar.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== toolbar.css ====================================================
  == Styles used by XUL toolbar-related elements.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

toolbar {
  background-color: -moz-headerbar;
  color: -moz-headerbartext;
  min-width: 1px;
  min-height: 20px;
  padding: 2px 0;

  &:-moz-window-inactive {
    background-color: -moz-headerbarinactive;
    color: -moz-headerbarinactivetext;
  }

  &[type="menubar"] {
    background-color: transparent;
    padding: 1px 0;
  }
}

toolbarseparator {
  min-width: 2px;
}

toolbarspacer {
  width: 15px;
}

toolbarpaletteitem {
  cursor: grab;
}

/* Drag and drop feedback */

toolbarpaletteitem[place="toolbar"] {
  margin-inline: -2px;
  border-inline: 2px solid transparent;
}

toolbarpaletteitem[dragover="left"] {
  border-left-color: #000000;
}

toolbarpaletteitem[dragover="right"] {
  border-right-color: #000000;
}
PK
!<.����0chrome/toolkit/skin/classic/global/tree/tree.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== tree.css ===================================================
  == Styles used by the XUL tree element.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");

/* ::::: tree ::::: */

tree {
  margin: 0 4px;
  background-color: Field;
  color: FieldText;
  appearance: auto;
  -moz-default-appearance: listbox;
  @media (-moz-platform: linux) {
    appearance: none;
    border: 1px solid ThreeDShadow;
  }
}

/********** splitter **********/

.tree-splitter {
  margin-inline: -4px;
  width: 8px;
  max-width: 8px;
  min-width: 8px;
  appearance: none !important;
  border: none !important;
  background: none !important;
  order: 2147483646;
  z-index: 2147483646;
}


/* ::::: tree rows ::::: */

treechildren::-moz-tree-row {
  border: 1px solid transparent;
}

treechildren::-moz-tree-row,
treecol:not([hideheader="true"]),
.tree-columnpicker-button {
  min-height: max(24px, 1.3em);
}

treechildren::-moz-tree-row(multicol, odd) {
  background-color: -moz-oddtreerow;
}

@media not (prefers-contrast) {
  treechildren::-moz-tree-row(hover) {
    background-color: hsla(0,0%,50%,.15);
  }
}

treechildren::-moz-tree-row(selected) {
  background-color: -moz-cellhighlight;
}

treechildren::-moz-tree-row(selected, focus) {
  background-color: SelectedItem;
}

treechildren::-moz-tree-row(current, focus) {
  outline: var(--default-focusring);
  outline-color: SelectedItem;
  outline-offset: calc(-1 * var(--default-focusring-width));
}

treechildren::-moz-tree-row(selected, current, focus) {
  outline-color: #F3D982;
}

/* ::::: tree cells ::::: */

treechildren::-moz-tree-cell {
  padding: 0 2px;
}

treechildren::-moz-tree-cell-text {
  color: inherit;
}

treechildren::-moz-tree-image(selected),
treechildren::-moz-tree-twisty(selected),
treechildren::-moz-tree-cell-text(selected) {
  color: -moz-cellhighlighttext;
}

treechildren::-moz-tree-image(selected, focus),
treechildren::-moz-tree-twisty(selected, focus),
treechildren::-moz-tree-cell-text(selected, focus) {
  color: SelectedItemText;
}


/* ::::: lines connecting cells ::::: */

treechildren::-moz-tree-line {
  border: 1px dotted ThreeDShadow;
}

treechildren::-moz-tree-line(selected, focus) {
  border: 1px dotted SelectedItemText;
}


/* ::::: tree separator ::::: */

treechildren::-moz-tree-separator {
  border-top: 1px solid ThreeDShadow;
  border-bottom: 1px solid ThreeDHighlight;
}


/* ::::: drop feedback ::::: */

treechildren::-moz-tree-cell-text(primary, dropOn) {
  background-color: SelectedItem;
  color: SelectedItemText;
}

treechildren::-moz-tree-drop-feedback {
  background-color: SelectedItem;
  width: 50px;
  height: 2px;
  margin-inline-start: 5px;
}

/* ::::: tree columns ::::: */

treecol,
.tree-columnpicker-button {
  background-color: -moz-ColHeader;
  color: -moz-ColHeaderText;
  align-items: center;
  justify-content: center;
  padding: 0 4px;
  margin: 0;

  border-inline-start: 1px solid ThreeDLightShadow;
  box-shadow: inset 0 -1px ThreeDLightShadow;

  &:where(:hover) {
    background-color: -moz-ColHeaderHover;
    color: -moz-ColHeaderHoverText;
  }
  &:where(:hover:active) {
    background-color: -moz-ColHeaderActive;
    color: -moz-ColHeaderActiveText;
  }
}

.treecol-text {
  margin: 0 !important;
}

treecol:where([ordinal="1"]) {
  border-inline-start: none;
}

treecol[hideheader="true"] {
  appearance: none;
}

/* ::::: column drag and drop styles ::::: */

treecol[dragging="true"] {
  color: Graytext;
}

treechildren::-moz-tree-column(insertbefore) {
  border-inline-start: 1px solid ThreeDShadow;
}

treechildren::-moz-tree-column(insertafter) {
  border-inline-end: 1px solid ThreeDShadow;
}

/* ::::: column picker :::::  */

.tree-columnpicker-button {
  list-style-image: url("chrome://global/skin/icons/columnpicker.svg");
  -moz-context-properties: fill;
  fill: currentColor;
  min-width: 0;
  padding: 1px 0;
  appearance: none;
}

.tree-columnpicker-button .button-text {
  display: none;
}

/* ::::: tree icons ::::: */

treechildren::-moz-tree-image {
  -moz-context-properties: fill;
  fill: currentColor;
}

/* ::::: twisty :::::  */

treechildren::-moz-tree-twisty {
  padding-top: 1px;
  padding-inline: 4px;
  width: 12px; /* The image's width is 12 pixels */
  list-style-image: url("chrome://global/skin/icons/arrow-right-12.svg");
  -moz-context-properties: fill;
  fill: currentColor;
}

treechildren:-moz-locale-dir(rtl)::-moz-tree-twisty(closed) {
  list-style-image: url("chrome://global/skin/icons/arrow-left-12.svg");
}

treechildren::-moz-tree-twisty(open) {
  list-style-image: url("chrome://global/skin/icons/arrow-down-12.svg");
}

treechildren::-moz-tree-indentation {
  width: 16px;
}

/* ::::: editable tree ::::: */

treechildren::-moz-tree-row(selected, editing) {
  background-color: transparent;
  border: none;
}

treechildren::-moz-tree-cell-text(selected, editing),
treechildren::-moz-tree-image(selected, editing) {
  color: inherit;
}

html|input.tree-input {
  position: absolute;
  top: 0;
  left: 0;
  appearance: none;
  flex: 1;
  border: 0;
  border-radius: 2px;
  outline: var(--focus-outline);
  margin-block: -1px 0;
  margin-inline: -2px 0;
  padding: 1px;
  font: inherit;
}

.scrollbar-topmost {
  position: relative;
  z-index: 2147483647;
}

/* ::::: sort direction indicator :::::  */
.treecol-sortdirection {
  list-style-image: url("chrome://global/skin/tree/sort-asc.svg");
  -moz-context-properties: fill;
  fill: currentColor;
  visibility: hidden;

  treecol[sortDirection="ascending"]:not([hideheader="true"]) > & {
    visibility: inherit;
  }

  treecol[sortDirection="descending"]:not([hideheader="true"]) > & {
    visibility: inherit;
    list-style-image: url("chrome://global/skin/tree/sort-dsc.svg");
  }
}

@media (-moz-platform: macos) and (not (prefers-contrast)) {
    /* We show a `SelectedItem` background on selected rows, so we do not need the
       dotted outline in that case. For users who have Full Keyboard Access
       enabled, macOS draws a blue highlight above the row highlight, so keyboard-
       only users can still discern the currently-selected row when multiple rows
       are selected. */
  treechildren::-moz-tree-row(selected, current, focus) {
    outline: none;
  }
}
PK
!<j~V���3chrome/toolkit/skin/classic/global/notification.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html "http://www.w3.org/1999/xhtml";

.notificationbox-stack {
  background-color: var(--toolbar-bgcolor);
  width: 100%;

  /* Prevent the animation from overlapping the navigation toolbar */
  overflow: clip;

  &[notificationside="top"] {
    /* Create a stacking context for the box-shadow */
    position: relative;
    z-index: 1;
  }
}

notification {
  min-height: 40px;
  padding-inline-start: 16px;
  background: var(--notification-background);
  color: var(--notification-text);
  border-color: var(--notification-border);
  border-style: solid;
  border-width: 1px 0;
  text-shadow: none;
  font-size: 1.15em;

  --notification-background: Window;
  --notification-text: WindowText;
  --notification-border: rgba(12, 12, 13, 0.2);
  --notification-button-background: rgba(12,12,13,.1);
  --notification-button-background-hover: rgba(12,12,13,0.2);
  --notification-button-background-active: rgba(12,12,13,0.3);
  --notification-button-text: inherit;
  --notification-primary-button-background: #0060df;
  --notification-primary-button-background-hover: #003eaa;
  --notification-primary-button-background-active: #002275;
  --notification-primary-button-text: rgb(249, 249, 250);

  @media (prefers-color-scheme: dark) {
    &[type="info"] {
      --notification-button-background: rgba(249,249,250,.1);
      --notification-button-background-hover: rgba(249,249,250,.2);
      --notification-button-background-active: rgba(249,249,250,.3);

      *|*:root[lwtheme] & {
        --notification-background: #38383d;
        --notification-text: rgb(249, 249, 250);
      }
    }
  }

  .notificationbox-stack[notificationside="top"] > & {
    border-top-style: none;
  }

  .notificationbox-stack[notificationside="bottom"] > & {
    border-bottom-style: none;
  }

  &[type="warning"] {
    --notification-background: #ffe900;
    --notification-text: #0c0c0d;
  }

  &[type="critical"] {
    --notification-background: #d70022;
    --notification-text: #fff;

    > .close-icon:hover {
      background-color: color-mix(in srgb, currentColor 20%, transparent);
    }

    > .close-icon:hover:active {
      background-color: color-mix(in srgb, currentColor 30%, transparent);
    }
  }
}

html|notification-message.animated,
notification.animated {
  transition: margin-top 300ms var(--animation-easing-function), opacity 300ms var(--animation-easing-function);
}

.messageText {
  margin-inline-start: 12px !important;
  margin-block: 0;

  > .text-link {
    text-decoration: underline;
    margin-block: 0;

    &:not(.notification-link) {
      color: inherit !important;
      margin-inline: 0;
    }
  }
}

.messageImage {
  width: 24px;
  margin: 4px 0;
  -moz-context-properties: fill;
  fill: currentColor;

  notification[type="info"] > hbox > & {
    list-style-image: url("chrome://global/skin/icons/help.svg");
  }

  notification[type="warning"] > hbox > & {
    list-style-image: url("chrome://global/skin/icons/warning.svg");
  }

  notification[type="critical"] > hbox > & {
    list-style-image: url("chrome://global/skin/icons/error.svg");
  }
}

.messageCloseButton {
  margin: 0;
  padding: 0;

  > .toolbarbutton-icon {
    padding: 6px;
    width: 32px;
    /* Close button needs to be clickable from the edge of the window */
    margin-inline-end: 8px;
  }

  &:focus-visible {
    /* Override the dotted outline from button.css */
    outline: none;

    > .toolbarbutton-icon {
      outline: var(--focus-outline);
      outline-offset: var(--focus-outline-inset);
      border-radius: var(--toolbarbutton-border-radius, 4px);
    }
  }
}

.notification-button {
  appearance: none;
  border: 1px solid transparent;
  border-radius: 4px;
  background-color: var(--notification-button-background);
  color: var(--notification-button-text);
  padding: 0 6px;
  margin: 4px 8px;
  height: 24px;

  &[disabled] {
    opacity: 0.5;
  }

  &:not([disabled]):hover {
    background-color: var(--notification-button-background-hover);
  }

  &:not([disabled]):hover:active {
    background-color: var(--notification-button-background-active);
  }

  &:focus-visible {
    outline: var(--focus-outline);
    outline-offset: var(--focus-outline-offset);
  }

  &.primary {
    background-color: var(--notification-primary-button-background);
    color: var(--notification-primary-button-text);

    &:not([disabled]):hover {
      background-color: var(--notification-primary-button-background-hover);
    }

    &:not([disabled]):hover:active {
      background-color: var(--notification-primary-button-background-active);
    }
  }
}
PK
!<G<-6�6�3chrome/en-US/locale/en-US/global/dom/dom.properties
KillScriptTitle=Warning: Unresponsive script
KillScriptMessage=A script on this page may be busy, or it may have stopped responding. You can stop the script now, or you can continue to see if the script will complete.
KillScriptWithDebugMessage=A script on this page may be busy, or it may have stopped responding. You can stop the script now, open the script in the debugger, or let the script continue.
KillScriptLocation=Script: %S

KillAddonScriptTitle=Warning: Unresponsive add-on script
KillAddonScriptMessage=A script from the extension “%1$S” is running on this page, and making %2$S unresponsive.\n\nIt may be busy, or it may have stopped responding permanently. You can stop the script now, or you can continue to see if it will complete.
KillAddonScriptGlobalMessage=Prevent the extension script from running on this page until it next reloads

StopScriptButton=Stop script
DebugScriptButton=Debug script
WaitForScriptButton=Continue
DontAskAgain=&Don’t ask me again
WindowCloseByScriptBlockedWarning=Scripts may only close windows that were opened by a script.
OnBeforeUnloadTitle=Are you sure?
OnBeforeUnloadMessage2=This page is asking you to confirm that you want to leave — information you’ve entered may not be saved.
OnBeforeUnloadStayButton=Stay on page
OnBeforeUnloadLeaveButton=Leave page
OnBeforeUnloadPDFjsTitle=Save PDF before leaving?
OnBeforeUnloadPDFjsMessage=Save this document to avoid losing your changes.
EmptyGetElementByIdParam=Empty string passed to getElementById().
SpeculationFailed2=An unbalanced tree was written using document.write() causing data from the network to be reparsed. More information: https://developer.mozilla.org/en-US/docs/Glossary/speculative_parsing
DocumentWriteIgnored=A call to document.write() from an asynchronously-loaded external script was ignored.
EditorFileDropFailed=Dropping a file into a contenteditable element failed: %S.
FormValidationTextTooLong=Please shorten this text to %S characters or less (you are currently using %S characters).
FormValidationTextTooShort=Please use at least %S characters (you are currently using %S characters).
FormValidationValueMissing=Please fill out this field.
FormValidationCheckboxMissing=Please check this box if you want to proceed.
FormValidationRadioMissing=Please select one of these options.
FormValidationFileMissing=Please select a file.
FormValidationSelectMissing=Please select an item in the list.
FormValidationInvalidEmail=Please enter an email address.
FormValidationInvalidURL=Please enter a URL.
FormValidationInvalidDate=Please enter a valid date.
FormValidationInvalidTime=Please enter a valid time.
FormValidationInvalidDateTime=Please enter valid date and time.
FormValidationInvalidDateMonth=Please enter a valid month.
FormValidationInvalidDateWeek=Please enter a valid week.
FormValidationPatternMismatch=Please match the requested format.
FormValidationPatternMismatchWithTitle=Please match the requested format: %S.
FormValidationNumberRangeOverflow=Please select a value that is no more than %S.
FormValidationDateTimeRangeOverflow=Please select a value that is no later than %S.
FormValidationNumberRangeUnderflow=Please select a value that is no less than %S.
FormValidationDateTimeRangeUnderflow=Please select a value that is no earlier than %S.
FormValidationStepMismatch=Please select a valid value. The two nearest valid values are %S and %S.
FormValidationStepMismatchOneValue=Please select a valid value. The nearest valid value is %S.
FormValidationTimeReversedRangeUnderflowAndOverflow=Please select a value between %1$S and %2$S.
FormValidationBadInputNumber=Please enter a number.
FullscreenDeniedDisabled=Request for fullscreen was denied because Fullscreen API is disabled by user preference.
FullscreenDeniedHidden=Request for fullscreen was denied because the document is no longer visible.
FullscreenDeniedHTMLDialog=Request for fullscreen was denied because requesting element is a <dialog> element.
FullscreenDeniedContainerNotAllowed=Request for fullscreen was denied because at least one of the document’s containing elements is not an iframe or does not have an “allowfullscreen” attribute.
FullscreenDeniedNotInputDriven=Request for fullscreen was denied because Element.requestFullscreen() was not called from inside a short running user-generated event handler.
FullscreenDeniedMouseEventOnlyLeftBtn=Request for fullscreen was denied because Element.requestFullscreen() was called from inside a mouse event handler not triggered by left mouse button.
FullscreenDeniedNotHTMLSVGOrMathML=Request for fullscreen was denied because requesting element is not <svg>, <math>, or an HTML element.
FullscreenDeniedNotInDocument=Request for fullscreen was denied because requesting element is no longer in its document.
FullscreenDeniedMovedDocument=Request for fullscreen was denied because requesting element has moved document.
FullscreenDeniedLostWindow=Request for fullscreen was denied because we no longer have a window.
FullscreenDeniedPopoverOpen=Request for fullscreen was denied because the element is already open as a popover.
FullscreenDeniedSubDocFullscreen=Request for fullscreen was denied because a subdocument of the document requesting fullscreen is already fullscreen.
FullscreenDeniedNotFocusedTab=Request for fullscreen was denied because requesting element is not in the currently focused tab.
FullscreenDeniedFeaturePolicy=Request for fullscreen was denied because of FeaturePolicy directives.
FullscreenExitWindowFocus=Exited fullscreen because a window was focused.
RemovedFullscreenElement=Exited fullscreen because fullscreen element was removed from document.
PointerLockDeniedDisabled=Request for pointer lock was denied because Pointer Lock API is disabled by user preference.
PointerLockDeniedInUse=Request for pointer lock was denied because the pointer is currently controlled by a different document.
PointerLockDeniedNotInDocument=Request for pointer lock was denied because the requesting element is not in a document.
PointerLockDeniedSandboxed=Request for pointer lock was denied because Pointer Lock API is restricted via sandbox.
PointerLockDeniedHidden=Request for pointer lock was denied because the document is not visible.
PointerLockDeniedNotFocused=Request for pointer lock was denied because the document is not focused.
PointerLockDeniedMovedDocument=Request for pointer lock was denied because the requesting element has moved document.
PointerLockDeniedNotInputDriven=Request for pointer lock was denied because Element.requestPointerLock() was not called from inside a short running user-generated event handler, and the document is not in full screen.
PointerLockDeniedFailedToLock=Request for pointer lock was denied because the browser failed to lock the pointer.
HTMLSyncXHRWarning=HTML parsing in XMLHttpRequest is not supported in the synchronous mode.
ForbiddenHeaderWarning=Attempt to set a forbidden header was denied: %S
ResponseTypeSyncXHRWarning=Use of XMLHttpRequest’s responseType attribute is no longer supported in the synchronous mode in window context.
TimeoutSyncXHRWarning=Use of XMLHttpRequest’s timeout attribute is not supported in the synchronous mode in window context.
UseSendBeaconDuringUnloadAndPagehideWarning=Use of navigator.sendBeacon instead of synchronous XMLHttpRequest during unload and pagehide improves user experience.
JSONCharsetWarning=An attempt was made to declare a non-UTF-8 encoding for JSON retrieved using XMLHttpRequest. Only UTF-8 is supported for decoding JSON.
MediaElementAudioSourceNodeCrossOrigin=The HTMLMediaElement passed to createMediaElementSource has a cross-origin resource, the node will output silence.
MediaStreamAudioSourceNodeCrossOrigin=The MediaStream passed to createMediaStreamSource has a cross-origin resource, the node will output silence.
MediaStreamTrackAudioSourceNodeCrossOrigin=The MediaStreamTrack passed to createMediaStreamTrackSource is a cross-origin resource, the node will output silence.
MediaElementAudioCaptureOfMediaStreamError=The captured HTMLMediaElement is playing a MediaStream. Applying volume or mute status is not currently supported.
MediaElementStreamCaptureCycle=The MediaStream assigned to srcObject comes from a capture of this HTMLMediaElement, forming a cycle, assignment ignored.
MediaLoadExhaustedCandidates=All candidate resources failed to load. Media load paused.
MediaLoadSourceMissingSrc=<source> element has no “src” attribute. Media resource load failed.
MediaStreamAudioSourceNodeDifferentRate=Connecting AudioNodes from AudioContexts with different sample-rate is currently not supported.
MediaLoadHttpError=HTTP load failed with status %1$S. Load of media resource %2$S failed.
MediaLoadInvalidURI=Invalid URI. Load of media resource %S failed.
MediaLoadUnsupportedTypeAttribute=Specified “type” attribute of “%1$S” is not supported. Load of media resource %2$S failed.
MediaLoadUnsupportedTypeAttributeLoadingNextChild=Specified “type” attribute of “%1$S” is not supported. Load of media resource %2$S failed. Trying to load from next <source> element.
MediaLoadUnsupportedMimeType=HTTP “Content-Type” of “%1$S” is not supported. Load of media resource %2$S failed.
MediaLoadDecodeError=Media resource %S could not be decoded.
MediaWidevineNoWMF=Trying to play Widevine with no Windows Media Foundation. See https://support.mozilla.org/kb/fix-video-audio-problems-firefox-windows
MediaWMFNeeded=To play video formats %S, you need to install extra Microsoft software, see https://support.mozilla.org/kb/fix-video-audio-problems-firefox-windows
MediaPlatformDecoderNotFound=The video on this page can’t be played. Your system may not have the required video codecs for: %S
MediaUnsupportedLibavcodec=The video on this page can’t be played. Your system has an unsupported version of libavcodec
MediaDecodeError=Media resource %1$S could not be decoded, error: %2$S
MediaDecodeWarning=Media resource %1$S could be decoded, but with error: %2$S
MediaCannotPlayNoDecoders=Cannot play media. No decoders for requested formats: %S
MediaNoDecoders=No decoders for some of the requested formats: %S
MediaCannotInitializePulseAudio=Unable to use PulseAudio
MediaEMEInsecureContextDeprecatedWarning=Using Encrypted Media Extensions at %S on an insecure (i.e. non-HTTPS) context is deprecated and will soon be removed. You should consider switching to a secure origin such as HTTPS.
MediaEMENoCapabilitiesDeprecatedWarning=Calling navigator.requestMediaKeySystemAccess() (at %S) without passing a candidate MediaKeySystemConfiguration containing audioCapabilities or videoCapabilities is deprecated and will soon become unsupported.
MediaEMENoCodecsDeprecatedWarning=Calling navigator.requestMediaKeySystemAccess() (at %S) passing a candidate MediaKeySystemConfiguration containing audioCapabilities or videoCapabilities without a contentType with a “codecs” string is deprecated and will soon become unsupported.

DOMAttrModifiedWarning=Adding a listener for DOMAttrModified is deprecated and will be removed soon. Instead of a MutationEvent, use MutationObserver. https://developer.mozilla.org/docs/Web/API/MutationObserver
DOMCharacterDataModifiedWarning=Adding a listener for DOMCharacterDataModified is deprecated and will be removed soon. Instead of a MutationEvent, use MutationObserver. https://developer.mozilla.org/docs/Web/API/MutationObserver
DOMNodeInsertedWarning=Adding a listener for DOMNodeInserted is deprecated and will be removed soon. Instead of a MutationEvent, use MutationObserver. https://developer.mozilla.org/docs/Web/API/MutationObserver
DOMNodeInsertedIntoDocumentWarning=Adding a listener for DOMNodeInsertedIntoDocument is deprecated and will be removed soon. Instead of a MutationEvent, use MutationObserver. https://developer.mozilla.org/docs/Web/API/MutationObserver
DOMNodeRemovedWarning=Adding a listener for DOMNodeRemoved is deprecated and will be removed soon. Instead of a MutationEvent, use MutationObserver. https://developer.mozilla.org/docs/Web/API/MutationObserver
DOMNodeRemovedFromDocumentWarning=Adding a listener for DOMNodeRemovedFromDocument is deprecated and will be removed soon. Instead of a MutationEvent, use MutationObserver. https://developer.mozilla.org/docs/Web/API/MutationObserver
DOMSubtreeModifiedWarning=Adding a listener for DOMSubtreeModified is deprecated and will be removed soon. Instead of a MutationEvent, use MutationObserver. https://developer.mozilla.org/docs/Web/API/MutationObserver

BlockAutoplayError=Autoplay is only allowed when approved by the user, the site is activated by the user, or media is muted.
BlockAutoplayWebAudioStartError=An AudioContext was prevented from starting automatically. It must be created or resumed after a user gesture on the page.
ComponentsWarning=The Components object is deprecated. It will soon be removed.
PluginHangUITitle=Warning: Unresponsive plugin
PluginHangUIMessage=%S may be busy, or it may have stopped responding. You can stop the plugin now, or you can continue to see if the plugin will complete.
PluginHangUIWaitButton=Continue
PluginHangUIStopButton=Stop plugin
NodeIteratorDetachWarning=Calling detach() on a NodeIterator no longer has an effect.
LenientThisWarning=Ignoring get or set of property that has [LenientThis] because the “this” object is incorrect.
UseOfCaptureEventsWarning=Use of captureEvents() is deprecated. To upgrade your code, use the DOM 2 addEventListener() method. For more help http://developer.mozilla.org/en/docs/DOM:element.addEventListener
UseOfReleaseEventsWarning=Use of releaseEvents() is deprecated. To upgrade your code, use the DOM 2 removeEventListener() method. For more help http://developer.mozilla.org/en/docs/DOM:element.removeEventListener
SyncXMLHttpRequestDeprecatedWarning=Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user’s experience. For more help https://xhr.spec.whatwg.org/#sync-warning
Window_Cc_ontrollersWarning=window.controllers/Controllers is deprecated. Do not use it for UA detection.
ImportXULIntoContentWarning=Importing XUL nodes into a content document is deprecated. This functionality may be removed soon.
IndexedDBTransactionAbortNavigation=An IndexedDB transaction that was not yet complete has been aborted due to page navigation.
IgnoringWillChangeOverBudgetWarning=Will-change memory consumption is too high. Budget limit is the document surface area multiplied by %1$S (%2$S px). Occurrences of will-change over the budget will be ignored.
HittingMaxWorkersPerDomain2=A Worker could not be started immediately because other documents in the same origin are already using the maximum number of workers. The Worker is now queued and will be started after some of the other workers have completed.
AppCacheWarning=The Application Cache API (AppCache) is deprecated and will be removed at a future date.  Please consider using ServiceWorker for offline support.
EmptyWorkerSourceWarning=Attempting to create a Worker from an empty source. This is probably unintentional.
NavigatorGetUserMediaWarning=navigator.mozGetUserMedia has been replaced by navigator.mediaDevices.getUserMedia
RTCPeerConnectionGetStreamsWarning=RTCPeerConnection.getLocalStreams/getRemoteStreams are deprecated. Use RTCPeerConnection.getSenders/getReceivers instead.
InterceptionFailedWithURL=Failed to load ‘%S’. A ServiceWorker intercepted the request and encountered an unexpected error.
CorsResponseForSameOriginRequest=Failed to load ‘%1$S’ by responding ‘%2$S’. A ServiceWorker is not allowed to synthesize a cors Response for a same-origin Request.
BadOpaqueInterceptionRequestModeWithURL=Failed to load ‘%1$S’. A ServiceWorker passed an opaque Response to FetchEvent.respondWith() while handling a ‘%2$S’ FetchEvent. Opaque Response objects are only valid when the RequestMode is ‘no-cors’.
InterceptedErrorResponseWithURL=Failed to load ‘%S’. A ServiceWorker passed an Error Response to FetchEvent.respondWith(). This typically means the ServiceWorker performed an invalid fetch() call.
InterceptedUsedResponseWithURL=Failed to load ‘%S’. A ServiceWorker passed a used Response to FetchEvent.respondWith(). The body of a Response may only be read once. Use Response.clone() to access the body multiple times.
BadOpaqueRedirectInterceptionWithURL=Failed to load ‘%S’. A ServiceWorker passed an opaqueredirect Response to FetchEvent.respondWith() while handling a non-navigation FetchEvent.
BadRedirectModeInterceptionWithURL=Failed to load ‘%S’. A ServiceWorker passed a redirected Response to FetchEvent.respondWith() while RedirectMode is not ‘follow’.
InterceptionCanceledWithURL=Failed to load ‘%S’. A ServiceWorker canceled the load by calling FetchEvent.preventDefault().
InterceptionRejectedResponseWithURL=Failed to load ‘%1$S’. A ServiceWorker passed a promise to FetchEvent.respondWith() that rejected with ‘%2$S’.
InterceptedNonResponseWithURL=Failed to load ‘%1$S’. A ServiceWorker passed a promise to FetchEvent.respondWith() that resolved with non-Response value ‘%2$S’.

ServiceWorkerScopePathMismatch=Failed to register a ServiceWorker: The path of the provided scope ‘%1$S’ is not under the max scope allowed ‘%2$S’. Adjust the scope, move the Service Worker script, or use the Service-Worker-Allowed HTTP header to allow the scope.
ServiceWorkerRegisterNetworkError=Failed to register/update a ServiceWorker for scope ‘%1$S’: Load failed with status %2$S for script ‘%3$S’.
ServiceWorkerRegisterMimeTypeError2=Failed to register/update a ServiceWorker for scope ‘%1$S’: Bad Content-Type of ‘%2$S’ received for script ‘%3$S’.  Must be a JavaScript MIME type.
ServiceWorkerRegisterStorageError=Failed to register/update a ServiceWorker for scope ‘%S’: Storage access is restricted in this context due to user settings or private browsing mode.
ServiceWorkerGetRegistrationStorageError=Failed to get service worker registration(s): Storage access is restricted in this context due to user settings or private browsing mode.
ServiceWorkerGetClientStorageError=Failed to get service worker’s client(s): Storage access is restricted in this context due to user settings or private browsing mode.
ServiceWorkerPostMessageStorageError=The ServiceWorker for scope ‘%S’ failed to execute ‘postMessage‘ because storage access is restricted in this context due to user settings or private browsing mode.
ServiceWorkerGraceTimeoutTermination=Terminating ServiceWorker for scope ‘%1$S’ with pending waitUntil/respondWith promises because of grace timeout.
ServiceWorkerNoFetchHandler=Fetch event handlers must be added during the worker script’s initial evaluation.
ExecCommandCutCopyDeniedNotInputDriven=document.execCommand(‘cut’/‘copy’) was denied because it was not called from inside a short running user-generated event handler.
ManifestIdIsInvalid=The id member did not resolve to a valid URL.
ManifestIdNotSameOrigin=The id member must have the same origin as the start_url member.
ManifestShouldBeObject=Manifest should be an object.
ManifestScopeURLInvalid=The scope URL is invalid.
ManifestScopeNotSameOrigin=The scope URL must be same origin as document.
ManifestStartURLOutsideScope=The start URL is outside the scope, so the scope is invalid.
ManifestStartURLInvalid=The start URL is invalid.
ManifestStartURLShouldBeSameOrigin=The start URL must be same origin as document.
ManifestInvalidType=Expected the %1$S’s %2$S member to be a %3$S.
ManifestInvalidCSSColor=%1$S: %2$S is not a valid CSS color.
ManifestLangIsInvalid=%1$S: %2$S is not a valid language code.
ManifestImageURLIsInvalid=%1$S item at index %2$S is invalid. The %3$S member is an invalid URL %4$S
ManifestImageUnusable=%1$S item at index %2$S lacks a usable purpose. It will be ignored.
ManifestImageUnsupportedPurposes=%1$S item at index %2$S includes unsupported purpose(s): %3$S.
ManifestImageRepeatedPurposes=%1$S item at index %2$S includes repeated purpose(s): %3$S.
PatternAttributeCompileFailurev2=Unable to check <input pattern=‘%1$S’> because ‘/%1$S/%2$S’ is not a valid regexp: %3$S
TargetPrincipalDoesNotMatch=Failed to execute ‘postMessage’ on ‘DOMWindow’: The target origin provided (‘%S’) does not match the recipient window’s origin (‘%S’).
RewriteYouTubeEmbed=Rewriting old-style YouTube Flash embed (%S) to iframe embed (%S). Please update page to use iframe instead of embed/object, if possible.
RewriteYouTubeEmbedPathParams=Rewriting old-style YouTube Flash embed (%S) to iframe embed (%S). Params were unsupported by iframe embeds and converted. Please update page to use iframe instead of embed/object, if possible.
PushMessageBadEncryptionHeader=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘Encryption’ header must include a unique ‘salt‘ parameter for each message. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-3.1 for more information.
PushMessageBadCryptoKeyHeader=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘Crypto-Key‘ header must include a ‘dh‘ parameter containing the app server’s public key. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-4 for more information.
PushMessageBadEncryptionKeyHeader=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘Encryption-Key’ header must include a ‘dh‘ parameter. This header is deprecated and will soon be removed. Please use ‘Crypto-Key‘ with ‘Content-Encoding: aesgcm‘ instead. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-4 for more information.
PushMessageBadEncodingHeader=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘Content-Encoding‘ header must be ‘aesgcm‘. ‘aesgcm128‘ is allowed, but deprecated and will soon be removed. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-2 for more information.
PushMessageBadSenderKey=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘dh‘ parameter in the ‘Crypto-Key‘ header must be the app server’s Diffie-Hellman public key, base64url-encoded (https://tools.ietf.org/html/rfc7515#appendix-C) and in “uncompressed” or “raw” form (65 bytes before encoding). See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-4 for more information.
PushMessageBadSalt=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘salt‘ parameter in the ‘Encryption‘ header must be base64url-encoded (https://tools.ietf.org/html/rfc7515#appendix-C), and be at least 16 bytes before encoding. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-3.1 for more information.
PushMessageBadRecordSize=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘rs‘ parameter of the ‘Encryption‘ header must be between %2$S and 2^36-31, or omitted entirely. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-3.1 for more information.
PushMessageBadPaddingError=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. A record in the encrypted message was not padded correctly. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-2 for more information.
PushMessageBadCryptoError=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. For help with encryption, please see https://developer.mozilla.org/docs/Web/API/Push_API/Using_the_Push_API#Encryption
PreventDefaultFromPassiveListenerWarning=Ignoring ‘preventDefault()’ call on event of type ‘%1$S’ from a listener registered as ‘passive’.
ImageBitmapRenderingContext_TransferImageBitmapWarning=ImageBitmapRenderingContext.transferImageBitmap is deprecated and will be removed soon. Use ImageBitmapRenderingContext.transferFromImageBitmap instead.
IIRFilterChannelCountChangeWarning=IIRFilterNode channel count changes may produce audio glitches.
BiquadFilterChannelCountChangeWarning=BiquadFilterNode channel count changes may produce audio glitches.
GenericImageNamePNG=image.png
GenericFileName=file
GeolocationInsecureRequestIsForbidden=A Geolocation request can only be fulfilled in a secure context.
NotificationsInsecureRequestIsForbidden=The Notification permission may only be requested in a secure context.
NotificationsCrossOriginIframeRequestIsForbidden=The Notification permission may only be requested in a top-level document or same-origin iframe.
NotificationsRequireUserGesture=The Notification permission may only be requested from inside a short running user-generated event handler.
NotificationsRequireUserGestureDeprecationWarning=Requesting Notification permission outside a short running user-generated event handler is deprecated and will not be supported in the future.
WindowContentUntrustedWarning=The ‘content’ attribute of Window objects is deprecated.  Please use ‘window.top’ instead.

SVGRefLoopWarning=The SVG <%S> with ID “%S” has a reference loop.
SVGRefChainLengthExceededWarning=An SVG <%S> reference chain which is too long was abandoned at the element with ID “%S”.
SVGDeselectAllWarning=SVGSVGElement.deselectAll is deprecated as it duplicates functionality from the Selection API.
SVGNearestViewportElementWarning=SVGGraphicsElement.nearestViewportElement is deprecated and will be removed at a future date. Use SVGElement.viewportElement instead.
SVGFarthestViewportElementWarning=SVGGraphicsElement.farthestViewportElement is deprecated and will be removed at a future date.

ScriptSourceEmpty=‘%S’ attribute of <script> element is empty.
ScriptSourceInvalidUri=‘%S’ attribute of <script> element is not a valid URI: “%S”
ScriptSourceLoadFailed=Loading failed for the <script> with source “%S”.
ModuleSourceLoadFailed=Loading failed for the module with source “%S”.
ScriptSourceMalformed=<script> source URI is malformed: “%S”.
ModuleSourceMalformed=Module source URI is malformed: “%S”.
ScriptSourceNotAllowed=<script> source URI is not allowed in this document: “%S”.
ModuleSourceNotAllowed=Module source URI is not allowed in this document: “%S”.
WebExtContentScriptModuleSourceNotAllowed=WebExtension content scripts may only load modules with moz-extension URLs and not: “%S”.
ModuleResolveFailureNoWarn=Error resolving module specifier “%S”.
ModuleResolveFailureWarnRelative=Error resolving module specifier “%S”. Relative module specifiers must start with “./”, “../” or “/”.
IDBObjectStoreCreateIndexLocaleWarning=The ‘locale’ option for IDBObjectStore.createIndex() is deprecated.
ImportMapInvalidTopLevelKey=An invalid top-level key “%S” was present in the import map.
ImportMapEmptySpecifierKeys=Specifier keys cannot be empty strings.
ImportMapAddressesNotStrings=Addresses need to be strings.
ImportMapInvalidAddress=Address “%S” was invalid.
ImportMapAddressNotEndsWithSlash=An invalid address was given for the specifier key “%1$S”; since “%1$S” ended in a slash, the address “%2$S” needs to as well.
ImportMapScopePrefixNotParseable=The scope prefix URL “%S” was not parseable.
ImportMapResolutionBlockedByNullEntry=Resolution of specifier “%S” was blocked by a null entry.
ImportMapResolutionBlockedByAfterPrefix=Resolution of specifier “%S” was blocked since the substring after prefix could not be parsed as a URL relative to the address in the import map.
ImportMapResolutionBlockedByBacktrackingPrefix=Resolution of specifier “%S” was blocked since the parsed URL does not start with the address in the import map.
ImportMapResolveInvalidBareSpecifierWarnRelative=The specifier “%S” was a bare specifier, but was not remapped to anything. Relative module specifiers must start with “./”, “../” or “/”.
ImportMapExternalNotSupported=External import maps are not supported: <script type='importmap'> with a src attribute is currently not supported.
ImportMapNotAllowedMultiple=Multiple import maps are not allowed.
ImportMapNotAllowedAfterModuleLoad=Import maps are not allowed after a module load or preload has started.
InvalidKeyframePropertyValue=Keyframe property value “%1$S” is invalid according to the syntax for “%2$S”.
ReadableStreamReadingFailed=Failed to read data from the ReadableStream: “%S”.
RegisterProtocolHandlerPrivateBrowsingWarning=Can’t use registerProtocolHandler inside private browsing mode.
MotionEventWarning=Use of the motion sensor is deprecated.
OrientationEventWarning=Use of the orientation sensor is deprecated.
ProximityEventWarning=Use of the proximity sensor is deprecated.
AmbientLightEventWarning=Use of the ambient light sensor is deprecated.
UnsupportedEntryTypesIgnored=Ignoring unsupported entryTypes: %S.
AllEntryTypesIgnored=No valid entryTypes; aborting registration.
GTK2Conflict2=Key event not available on GTK2: key=“%S” modifiers=“%S” id=“%S”
WinConflict2=Key event not available on some keyboard layouts: key=“%S” modifiers=“%S” id=“%S”
DocumentSetDomainNotAllowedWarning=Setting document.domain in a cross-origin isolated environment is not allowed.

DeprecatedTestingInterfaceWarning=TestingDeprecatedInterface is a testing-only interface and this is its testing deprecation message.
DeprecatedTestingMethodWarning=TestingDeprecatedInterface.deprecatedMethod() is a testing-only method and this is its testing deprecation message.
DeprecatedTestingAttributeWarning=TestingDeprecatedInterface.deprecatedAttribute is a testing-only attribute and this is its testing deprecation message.
CreateImageBitmapCanvasRenderingContext2DWarning=Use of CanvasRenderingContext2D in createImageBitmap is deprecated.

DrawWindowCanvasRenderingContext2DWarning=Use of drawWindow method from CanvasRenderingContext2D is deprecated. Use tabs.captureTab extensions API instead https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/tabs/captureTab

MozRequestFullScreenDeprecatedPrefixWarning=mozRequestFullScreen() is deprecated.
MozfullscreenchangeDeprecatedPrefixWarning=onmozfullscreenchange is deprecated.
MozfullscreenerrorDeprecatedPrefixWarning=onmozfullscreenerror is deprecated.
External_AddSearchProviderWarning=AddSearchProvider is deprecated.

MouseEvent_MozPressureWarning=MouseEvent.mozPressure is deprecated. Use PointerEvent.pressure instead.
MozInputSourceWarning=MouseEvent.mozInputSource is deprecated. Use PointerEvent.pointerType instead.
InitMouseEventWarning=initMouseEvent() is deprecated. Use the MouseEvent() constructor instead.
InitNSMouseEventWarning=initNSMouseEvent() is deprecated. Use the MouseEvent() constructor instead.
MathML_DeprecatedMathSpaceValue2Warning=MathML length value “%S” is deprecated and will be removed at a future date.
MathML_DeprecatedMathVariantWarning=“mathvariant='%S'” on MathML elements is deprecated and will be removed at a future date.
MathML_DeprecatedStixgeneralOperatorStretchingWarning=Support for rendering stretched MathML operators with STIXGeneral fonts is deprecated and may be removed at a future date. For details about newer fonts that will continue to be supported, see %S
FormSubmissionUntrustedEventWarning=Form submission via untrusted submit event is deprecated and will be removed at a future date.

SizeToContentWarning=sizeToContent() is deprecated and will be removed in the future.

WebShareAPI_Failed=The share operation has failed.
WebShareAPI_Aborted=The share operation was aborted.
UnknownProtocolNavigationPrevented=Prevented navigation to “%1$S” due to an unknown protocol.
PostMessageSharedMemoryObjectToCrossOriginWarning=Cannot post message containing a shared memory object to a cross-origin window.
UnusedLinkPreloadPending=The resource at “%S” preloaded with link preload was not used within a few seconds. Make sure all attributes of the preload tag are set correctly.

RequestStorageAccessNullPrincipal=document.requestStorageAccess() may not be called on a document with an opaque origin, such as a sandboxed iframe without allow-same-origin in its sandbox attribute.
RequestStorageAccessSandboxed=document.requestStorageAccess() may not be called in a sandboxed iframe without allow-storage-access-by-user-activation in its sandbox attribute.
RequestStorageAccessNested=document.requestStorageAccess() may not be called in a nested iframe.
RequestStorageAccessUserGesture=document.requestStorageAccess() may only be requested from inside a short running user-generated event handler.
RequestStorageAccessPermissionsPolicy=document.requestStorageAccess() may not be called where the storage-access feature is blocked by the Permissions Policy.
RequestStorageAccessNotSecureContext=document.requestStorageAccess() may only grant access to secure contexts.
LocChangeFloodingPrevented=Too many calls to Location or History APIs within a short timeframe.
FolderUploadPrompt.title = Confirm Upload
FolderUploadPrompt.message = Are you sure you want to upload all files from “%S”? Only do this if you trust the site.
FolderUploadPrompt.acceptButtonLabel = Upload
InputPickerBlockedNoUserActivation=<input> picker was blocked due to lack of user activation.
ExternalProtocolFrameBlockedNoUserActivation=Iframe with external protocol was blocked due to lack of user activation, or because not enough time has passed since the last such iframe was loaded.
MultiplePopupsBlockedNoUserActivation=Opening multiple popups was blocked due to lack of user activation.
PreloadIgnoredInvalidAttr=Preload of %S was ignored due to unknown “as” or “type” values, or non-matching “media” attribute.
PartitionKeyDifferentError=Cannot access blob URL “%S” with a different partition key.
ElementSetCaptureWarning=Element.setCapture() is deprecated. Use Element.setPointerCapture() instead. For more help https://developer.mozilla.org/docs/Web/API/Element/setPointerCapture
ElementReleaseCaptureWarning=Element.releaseCapture() is deprecated. Use Element.releasePointerCapture() instead. For more help https://developer.mozilla.org/docs/Web/API/Element/releasePointerCapture
DocumentReleaseCaptureWarning=Document.releaseCapture() is deprecated. Use Element.releasePointerCapture() instead. For more help https://developer.mozilla.org/docs/Web/API/Element/releasePointerCapture

WebExtensionUncheckedLastError=browser.runtime.lastError value was not checked: %S

OffscreenCanvasToBlobWarning=OffscreenCanvas.toBlob() is deprecated. Use OffscreenCanvas.convertToBlob() instead.

InstallTriggerDeprecatedWarning=InstallTrigger is deprecated and will be removed in the future.
InstallTriggerInstallDeprecatedWarning=InstallTrigger.install() is deprecated and will be removed in the future. For more help https://extensionworkshop.com/documentation/publish/self-distribution/

SelectOptionsLengthAssignmentWarning=Refused to expand <select> option list via assignment to HTMLOptionsCollection.length (value %1$S). The maximum supported size is %2$S.

InvalidFormControlUnfocusable=An invalid form control is not focusable.
InvalidNamedFormControlUnfocusable=The invalid form control with name=‘%S’ is not focusable.
PK
!<����QQ2chrome/toolkit/skin/classic/global/numberinput.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== numberinput.css ================================================
  == Styles used by the input[type="number"] element.
  ======================================================================= */

@namespace url("http://www.w3.org/1999/xhtml");

input[type="number"] {
  text-align: right;
}

input[type="number"][hidespinbuttons="true"] {
  appearance: textfield !important;
}
PK
!<4��))3chrome/toolkit/skin/classic/global/autocomplete.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== autocomplete.css =================================================
  == Styles used by the autocomplete widget.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");

/* ::::: autocomplete ::::: */

html|input[nomatch="true"][highlightnonmatches="true"] {
  color: red;
}

/* ::::: autocomplete popups ::::: */

panel[type="autocomplete-richlistbox"] {
  --panel-color: FieldText;
  --panel-background: Field;
}

/* ::::: richlistbox autocomplete ::::: */

.autocomplete-richlistbox {
  appearance: none;
  margin: 1px;
  background-color: transparent;
}

.autocomplete-richlistitem[selected] {
  background-color: SelectedItem;
  color: SelectedItemText;
}

.ac-type-icon {
  display: none;
  min-width: 16px;
  min-height: 16px;
  max-width: 16px;
  max-height: 16px;
  margin-inline-start: 6px;
  margin-inline-end: 6px;
}

.ac-site-icon {
  display: none;
  min-width: 16px;
  min-height: 16px;
  max-width: 16px;
  max-height: 16px;
  margin-inline-start: 6px;
  margin-inline-end: 8px;
  -moz-context-properties: fill;
  fill: currentColor;
}

.ac-title {
  margin-inline-start: 0;
  margin-inline-end: 6px;
}

.ac-separator {
  display: none;
  margin-inline-start: 0;
  margin-inline-end: 6px;
}

.ac-url {
  display: none;
}

/* Better align the URL with the title. */
.ac-separator,
.ac-url {
  margin-bottom: -2px;
}

.ac-title-text,
.ac-separator-text,
.ac-url-text,
.ac-text-overflow-container {
  padding: 0 !important;
  margin: 0 !important;
}
PK
!<EW<<8chrome/toolkit/skin/classic/global/popupnotification.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

.popup-notification-panel::part(content) {
  /* To keep the rounded borders of the panel, we use overflow: hidden; from the
   * panel-no-padding class to ensure the contents are clipped to the border box.
   * That causes us to override the "display" property so that the height of the
   * contents is computed correctly. */
  display: flex;
  /* Make multiple popupnotifications stack vertically. */
  flex-direction: column;
}

.popup-notification-panel > popupnotification:not([hidden]) {
  /* Since the anonymous parent (::part(content)) has display: flex, sizing
   * computations work better with display: block; than with the XUL default
   * display: flex;
   * TODO(emilio): we can probably remove this. */
  display: block;
}

popupnotification {
  font: caption;
}

popupnotificationcontent {
  margin-top: .5em;
}

.popup-notification-header-container,
.popup-notification-footer-container {
  display: flex;
  max-width: calc(2 * var(--arrowpanel-padding) + 16px /* Icon width */ + var(--panel-border-radius) + var(--popup-notification-body-width));
}

.popup-notification-body-container {
  padding: var(--arrowpanel-padding);
}

.popup-notification-icon {
  height: 16px;
  width: 16px;
  margin-inline-end: 6px;

  &:not([hasicon]) {
    display: none;
  }
}

.popup-notification-body {
  width: var(--popup-notification-body-width);
  flex: 1 auto;
}

.popup-notification-closebutton {
  margin-inline-end: -8px;
  margin-top: -8px;
}

.popup-notification-origin {
  margin-bottom: .3em !important;

  &:not([value]) {
    display: none;
  }
}

.popup-notification-hint-text {
  margin-top: .5em !important;

  &:empty {
    display: none;
  }
}

.popup-notification-secondary-button:not([dropmarkerhidden="true"]) {
  border-start-end-radius: 0;
  border-end-end-radius: 0;

  /**
   * The focus ring is an outline thicker than 1px, meaning that for split buttons,
   * if the main button part of the split button has :focus-visible, the ring will
   * be partially hidden behind the dropmarker button. We work around this by
   * temporarily boosting the z-index of the main button while showing its focus
   * ring.
   */
  &:focus-visible {
    z-index: 2;
  }
}

.popup-notification-dropmarker {
  border-start-start-radius: 0 !important;
  border-end-start-radius: 0 !important;
  margin-inline-start: 1px !important;
  padding: 8px !important;
  max-width: 32px;

  > .button-box > hbox {
    display: none;
  }

  > .button-box > .button-menu-dropmarker {
    appearance: none;
    display: flex;
    content: url(chrome://global/skin/icons/arrow-down.svg);
    -moz-context-properties: fill;
    fill: currentColor;
  }
}

.popup-notification-warning {
  color: #d74345;
}

.popup-notification-checkbox > .checkbox-label-box > .checkbox-label {
  opacity: 0.7;
}

.popup-notification-learnmore-link {
  text-decoration: underline;
  margin-block: 4px 0;

  &:not([href]) {
    display: none;
  }
}
PK
!<Px�m��,chrome/toolkit/skin/classic/global/radio.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== radio.css ===================================================
  == Styles used by the XUL radio element.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: radio ::::: */

radio {
  align-items: center;
  margin: 2px 4px;

  @media (-moz-platform: windows) {
    padding-block: 1px;
    padding-inline: 4px 2px;
  }

  &[disabled="true"] {
    color: GrayText;
  }
}

.radio-label {
  margin: 0;
}

.radio-check {
  appearance: auto;
  -moz-default-appearance: radio;
  margin: 1px 0;

  radiogroup:focus-visible radio[focused] > & {
    outline-style: auto;
  }

  @media (-moz-platform: windows) {
    width: 13px;
    height: 13px;
  }

  @media (-moz-platform: linux) {
    margin: 2px;
  }

  @media (-moz-platform: macos) {
    width: 1.3em;
    height: 1.3em;
    margin: 0 1px 1px;
    /* vertical-align tells native theming where to snap to. However, this
     * doesn't always work reliably because of bug 503833. */
    vertical-align: bottom;
  }
}

.radio-icon[src] {
  margin-inline-end: 2px;
}

/* On the linux native theme, focus is not indicated by the radio itself, so
 * indicate it in the label.
 * TODO(emilio): Consider using non-native radio buttons / checkboxes
 * everywhere instead */
@media (-moz-platform: linux) {
  radiogroup:focus-visible > radio[focused="true"] > .radio-label-box {
    outline: var(--focus-outline);
  }
}

@media (-moz-platform: windows) {
  .radio-label-box {
    margin-inline-start: 2px;
    padding-bottom: 1px;
    padding-inline-start: 1px;
  }
}

@media (-moz-platform: macos) {
  .radio-label,
  radiogroup {
    margin: 1px 0;
  }
}
PK
!<J���__2chrome/toolkit/skin/classic/global/richlistbox.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

richlistbox {
  appearance: auto;
  -moz-default-appearance: listbox;
  margin: 2px 4px;
  background-color: Field;
  color: FieldText;
}

richlistbox[disabled="true"] {
  color: GrayText;
}

richlistitem[selected="true"] {
  background-color: -moz-cellhighlight;
  color: -moz-cellhighlighttext;
}

richlistbox:where(:focus) > richlistitem[selected="true"] {
  background-color: SelectedItem;
  color: SelectedItemText;
}

richlistbox[seltype="multiple"]:focus > richlistitem[current="true"],
richlistbox.theme-listbox:focus > richlistitem[current="true"] {
  outline: var(--default-focusring);
  outline-color: SelectedItem;
  outline-offset: calc(-1 * var(--default-focusring-width));
}

richlistbox[seltype="multiple"]:focus > richlistitem[current="true"][selected="true"],
richlistbox.theme-listbox:focus > richlistitem[current="true"][selected="true"] {
  outline-color: #F3D982; /* TODO: find a suitable system color */
}

richlistbox.theme-listbox:not(:focus) > richlistitem[selected="true"] {
  background-color: -moz-cellhighlight;
  color: -moz-cellhighlighttext;
}

richlistbox.theme-listbox > richlistitem > label {
  margin: 0px;
  padding-top: 0px;
  padding-bottom: 1px;
  padding-inline-start: 4px;
  padding-inline-end: 0px;
  white-space: nowrap;
}

listheader {
  background-color: -moz-Dialog;
  color: -moz-DialogText;
}
PK
!<��7UU-chrome/toolkit/skin/classic/global/button.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== button.css =====================================================
  == Styles used by the XUL button element.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* :::::::::: button :::::::::: */

button {
  margin: 1px 5px 2px;
  min-width: 6.3em;
  color: ButtonText;
  text-shadow: none;
}

.button-text {
  margin: 0;
  margin-inline-start: 2px;
  text-align: center;
}

/* .......... hover state .......... */

button:where(:hover:not([checked="true"])) {
  color: -moz-buttonhovertext;
}

/* .......... active state .......... */

button:where(:hover:active, [open="true"]) {
  color: -moz-buttonactivetext;
}

/* .......... disabled state .......... */

button:where([disabled="true"]) {
  color: GrayText;
}

/* .......... focused state .......... */

button:where(:focus-visible) {
  outline: auto;
}

/* ::::: menu buttons ::::: */

.button-menu-dropmarker {
  appearance: auto;
  -moz-default-appearance: toolbarbutton-dropdown;
}

/* ::::: plain buttons ::::: */

button.plain {
  margin: 0 !important;
  padding: 0 !important;
}
PK
!<P�DPP-chrome/toolkit/skin/classic/global/tabbox.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== tabbox.css =================================================
  == Styles used by XUL tab-related elements.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");


/* ::::: tabs ::::: */

tabs {
  position: relative;
  z-index: 0;
}

/* ::::: tabpanels ::::: */

tabpanels {
  appearance: auto;
  -moz-default-appearance: tabpanels;
  padding: 8px;
  color: -moz-DialogText;
}

/* ::::: tab ::::: */

tab {
  position: relative;
  appearance: auto;
  -moz-default-appearance: tab;
  margin-top: 2px;
  padding: 3px 4px;
  color: -moz-DialogText;
}

tab:where([visuallyselected]) {
  z-index: 1;
  margin-top: 0;
  margin-bottom: -2px;
  padding-top: 4px;
  padding-bottom: 6px;
}

tab:where(:not(:first-of-type)) {
  margin-inline-start: -2px;
}

.tab-text {
  margin: 0 !important;
}
PK
!<%���.chrome/toolkit/skin/classic/global/findbar.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");

xul|findbar {
  border-top: 1px solid ThreeDShadow;
  min-width: 1px;
  transition-property: margin-bottom, visibility;
  transition-duration: 150ms, 0s;
  transition-timing-function: ease-in-out, linear;
  padding-block: 6px;
  background-color: -moz-dialog;
  color: -moz-dialogtext;

  &:where([hidden]) {
    /* Override display:none to make the transition work. */
    display: flex;
    margin-bottom: calc(-1 * (28px + 12px + 1px)); /* findbar-container's height + padding-block + top border */
    visibility: collapse;
    transition-delay: 0s, 150ms;

    > .findbar-container,
    > .findbar-closebutton {
      opacity: 0;
    }
  }

  @media (prefers-reduced-motion) {
    transition-duration: 0s;
    transition-delay: 0s;
  }

  &[noanim],
  &[noanim] > .findbar-container,
  &[noanim] > .findbar-closebutton {
    transition-duration: 0s;
    transition-delay: 0s;
  }
}

.findbar-container {
  margin-inline-start: 8px;
  height: 28px;
  overflow-inline: hidden; /* Ensures the close button stays visible. */
  transition: opacity 150ms ease-in-out;
}

/* Remove start margin when close button is on the left side (on macOS) */
.findbar-closebutton + .findbar-container {
  margin-inline-start: 0;
}

/* Search field */

.findbar-textbox {
  appearance: none;
  background-color: var(--input-bgcolor, var(--toolbar-field-background-color));
  color: var(--input-color, var(--toolbar-field-color));
  border: 1px solid var(--input-border-color);
  border-radius: 4px;
  outline: none;
  margin: 0;
  padding: 2px 5px;
  padding-inline-start: 8px;
  width: 18em;
  box-sizing: border-box;

  &::placeholder {
    opacity: 0.69;
  }

  &:focus {
    background-color: var(--toolbar-field-focus-background-color);
    color: var(--toolbar-field-focus-color);
    border-color: transparent;
    outline: var(--focus-outline);
    outline-offset: var(--focus-outline-inset);
    outline-color: var(--toolbar-field-focus-border-color);
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.23);
  }

  :root[lwtheme] &::selection {
    background-color: var(--lwt-toolbar-field-highlight, Highlight);
    color: var(--lwt-toolbar-field-highlight-text, HighlightText);
  }

  :root[lwtheme] &:not(:focus)::selection {
    background-color: var(--lwt-toolbar-field-highlight, text-select-background-disabled);
  }

  &[status="notfound"] {
    background-color: rgba(226,40,80,.3);
    color: inherit;
  }

  &[flash="true"] {
    background-color: rgba(255,233,0,.3);
    color: inherit;
  }
}

/* Previous/next buttons */

.findbar-find-previous,
.findbar-find-next {
  appearance: none;
  padding: 4px 7px;
  -moz-context-properties: fill;
  fill: var(--toolbarbutton-icon-fill);
  color: inherit;
  border-radius: 4px;

  &:focus-visible {
    outline: var(--focus-outline);
    outline-offset: var(--focus-outline-inset);
  }

  &:not([disabled]):hover {
    background-color: var(--toolbarbutton-hover-background, rgba(190,190,190,.2));
  }

  &:not([disabled]):hover:active {
    background-color: var(--toolbarbutton-active-background, rgba(190,190,190,.4));
  }

  &[disabled="true"] > .toolbarbutton-icon {
    opacity: var(--toolbarbutton-disabled-opacity);
  }
}

.findbar-find-previous {
  list-style-image: url(chrome://global/skin/icons/arrow-up.svg);
  margin-inline: 8px 0;
}

.findbar-find-next {
  list-style-image: url(chrome://global/skin/icons/arrow-down.svg);
  margin-inline: 0 8px;
}

/* Checkboxes & Labels */

.findbar-container > checkbox,
.findbar-label {
  margin: 0 8px;
  flex-shrink: 0;
}

.findbar-find-status,
.found-matches {
  margin-inline-start: 12px;
}

.findbar-find-status[status="notfound"] {
  font-weight: bold;
}

.find-status-icon {
  display: none;
}

/* Close button */

/* Increase specificity to override close-icon.css */
.close-icon.findbar-closebutton {
  margin: 2px 8px;
  width: 24px;
  fill: var(--toolbarbutton-icon-fill);
  transition: opacity 150ms ease-in-out;

  &:hover {
    background-color: var(--toolbarbutton-hover-background, rgba(190,190,190,.2));
    outline: none;
  }

  &:hover:active {
    background-color: var(--toolbarbutton-active-background, rgba(190,190,190,.4));
  }
}
PK
!<P�吷�.chrome/toolkit/content/global/autocomplete.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");

/* Apply crisp rendering for favicons at exactly 2dppx resolution */
@media (resolution: 2dppx) {
  .ac-site-icon {
    image-rendering: -moz-crisp-edges;
  }
}

.autocomplete-richlistbox > richlistitem {
  flex-direction: row;
  overflow: hidden;
}

.ac-title-text,
.ac-url-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
PK
!<E6��pp2chrome/toolkit/skin/classic/global/menu-shared.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

*|*:root {
  --menu-icon-opacity: 1;
  --menu-arrow-width: 1em;

  @media (-moz-platform: windows) {
    --menu-arrow-width: max(1em, 16px);

    @media (prefers-color-scheme: light) {
      --menu-icon-opacity: 0.7;
    }
  }
}

/* Menu separator */

menuseparator {
  /* Using padding instead of margin to increase the hit area, as some
     separators (e.g. in bookmarks menus) can be dragged or have a context
     menu. */
  padding-block: 4px;

  @media (-moz-platform: macos) {
    padding-block: 5px;
    margin-inline: 9px;
  }
}

menuseparator::before {
  border-top: 1px solid var(--panel-separator-color);
  content: "";
  display: block;
  flex: 1;
}

/* Accel text */

@media not (prefers-contrast) {
  menuitem:not([disabled]) > .menu-accel-container > :is(.menu-accel, .menu-iconic-accel) {
    color: var(--panel-disabled-color);
  }
}

/* Scroll buttons */

/* Hide arrow buttons when there's nothing to scroll in that direction */
.menupopup-arrowscrollbox[scrolledtostart]::part(scrollbutton-up),
.menupopup-arrowscrollbox[scrolledtoend]::part(scrollbutton-down) {
  display: none;
}

.menupopup-arrowscrollbox::part(scrollbox) {
  /* This makes the padding / margin trick below work */
  overflow-clip-box-block: content-box;
}

/* Prevent the scrolled contents of the menupopup from jumping vertically when
 * the arrow buttons appear / disappear, by positioning ::part(scrollbox) in
 * such a way that its edges are at the same position as the edges of
 * arrowscrollbox regardless of scroll button visibility.
 */
.menupopup-arrowscrollbox:not([scrolledtostart])::part(scrollbox) {
  /* scrollbutton-up is visible; shift our top edge up by its height. */
  margin-top: -16px;
  padding-top: 16px;
}

.menupopup-arrowscrollbox:not([scrolledtoend])::part(scrollbox) {
  /* scrollbutton-down is visible; shift our bottom edge down by its height. */
  margin-bottom: -16px;
  padding-bottom: 16px;
}

@media (-moz-platform: windows) or (-moz-platform: linux) {
  menupopup,
  menubar {
    font: menu;
  }
}

@media (-moz-platform: macos) {
  menupopup {
    /* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
    font: -moz-pull-down-menu;
  }
}

menu,
menuitem,
menucaption {
  border-radius: calc(var(--panel-border-radius) / 2);
  align-items: center;
  flex-shrink: 0;
  list-style-image: none;
  max-width: 42em;

  @media (-moz-platform: linux) {
    padding: 4px 6px;
  }

  @media (-moz-platform: macos) {
    padding: 3px 9px;
  }
}

menu,
menuitem {
  &:where([disabled="true"]) {
    color: var(--panel-disabled-color);
    text-shadow: none;
  }

  &:where([_moz-menuactive]:not([disabled="true"])) {
    color: -moz-menuhovertext;
    background-color: -moz-menuhover;
  }

  &:where([_moz-menuactive="true"][disabled="true"]) {
    background-color: -moz-menuhoverdisabled;
  }
}

menuitem:is([default="true"], .spell-suggestion),
menucaption {
  font-weight: bold;
}

/* ..... menu arrow box ..... */

.menu-right {
  list-style-image: url("chrome://global/skin/icons/arrow-right.svg");
  -moz-context-properties: fill, fill-opacity;
  fill: currentColor;
  fill-opacity: var(--menu-icon-opacity);

  &:-moz-locale-dir(rtl) {
    list-style-image: url("chrome://global/skin/icons/arrow-left.svg");
  }

  > image {
    width: var(--menu-arrow-width);
  }

  @media (-moz-platform: linux) {
    margin-block: 0;
    margin-inline: 6px 0;
  }

  @media (-moz-platform: macos) {
    margin-inline: 22px -3px;
  }

  @media (-moz-platform: windows) {
    margin-inline-end: 1em;
  }
}

@media (-moz-platform: macos) {
  :is(.menu-accel, .menu-iconic-accel)[value] {
    margin-inline-start: 25px;
  }
}
PK
!<�>W��<chrome/toolkit/content/global/antitracking/StripOnShare.json{
  "global": {
    "queryParams": [
      "utm_ad",
      "utm_affiliate",
      "utm_brand",
      "utm_campaign",
      "utm_campaignid",
      "utm_channel",
      "utm_cid",
      "utm_content",
      "utm_creative",
      "utm_emcid",
      "utm_emmid",
      "utm_id",
      "utm_id_",
      "utm_keyword",
      "utm_medium",
      "utm_name",
      "utm_place",
      "utm_product",
      "utm_pubreferrer",
      "utm_reader",
      "utm_referrer",
      "utm_serial",
      "utm_session",
      "utm_siteid",
      "utm_social",
      "utm_social-type",
      "utm_source",
      "utm_supplier",
      "utm_swu",
      "utm_term",
      "utm_umguk",
      "utm_userid",
      "utm_viz_id",
      "vero_conv",
      "ymid",
      "var",
      "s_cid",
      "hsa_grp",
      "hsa_cam",
      "hsa_src",
      "hsa_ad",
      "hsa_acc",
      "hsa_kw",
      "hsa_tgt",
      "hsa_ver",
      "hsa_la",
      "hsa_ol",
      "hsa_net",
      "hsa_mt"
    ],
    "topLevelSites": ["*"]
  },
  "twitter": {
    "queryParams": ["ref_src", "ref_url"],
    "topLevelSites": ["www.twitter.com", "twitter.com", "x.com"]
  },
  "instagram": {
    "queryParams": ["igshid", "ig_rid"],
    "topLevelSites": ["www.instagram.com"]
  },
  "amazon": {
    "queryParams": [
      "keywords",
      "pd_rd_r",
      "pd_rd_w",
      "pd_rd_wg",
      "pf_rd_r",
      "pf_rd_p",
      "sr",
      "content-id"
    ],
    "topLevelSites": [
      "www.amazon.com",
      "www.amazon.de",
      "www.amazon.nl",
      "www.amazon.fr",
      "www.amazon.co.jp",
      "www.amazon.in",
      "www.amazon.es",
      "www.amazon.ac",
      "www.amazon.cn",
      "www.amazon.eg",
      "www.amazon.in",
      "www.amazon.co.uk",
      "www.amazon.it",
      "www.amazon.pl",
      "www.amazon.sg",
      "www.amazon.ca"
    ]
  },
  "handelsblatt": {
    "queryParams": ["share"],
    "topLevelSites": ["www.handelsblatt.com"]
  }
}
PK
!<z>t��3�3=chrome/en-US/locale/en-US/global/layout/htmlparser.properties
EncNoDeclarationFrame=The character encoding of a framed document was not declared. The document may appear different if viewed without the document framing it.
EncXmlDecl=The character encoding of an HTML document was declared using the XML declaration syntax. This is non-conforming, and declaring the encoding using a meta tag at the start of the head part is more efficient.
EncMetaTooLate=A meta tag attempting to declare the character encoding declaration was found too late, and the encoding was guessed from content instead. The meta tag needs to be moved to the start of the head part of the document.
EncMetaTooLateFrame=A meta tag attempting to declare the character encoding declaration was found too late, and the encoding of the parent document was used instead. The meta tag needs to be moved to the start of the head part of the document.
EncMetaAfterHeadInKilobyte=The meta tag declaring the character encoding of the document should be moved to start of the head part of the document.
EncNoDecl=The character encoding of the document was not declared, so the encoding was guessed from content. The character encoding needs to be declared in the Content-Type HTTP header, using a meta tag, or using a byte order mark.
EncNoDeclPlain=The character encoding of the document was not declared, so the encoding was guessed from content. The character encoding needs to be declared in the Content-Type HTTP header or using a byte order mark.
EncMetaUnsupported=An unsupported character encoding was declared for the HTML document using a meta tag. The declaration was ignored.
EncProtocolUnsupported=An unsupported character encoding was declared on the transfer protocol level. The declaration was ignored.
EncMetaUtf16=A meta tag was used to declare the character encoding as UTF-16. This was interpreted as an UTF-8 declaration instead.
EncMetaUserDefined=A meta tag was used to declare the character encoding as x-user-defined. This was interpreted as a windows-1252 declaration instead for compatibility with intentionally mis-encoded legacy fonts. This site should migrate to Unicode.
EncMetaReplacement=A meta tag was used to declare an encoding that is a cross-site scripting hazard. The replacement encoding was used instead.
EncProtocolReplacement=An encoding that is a cross-site scripting hazard was declared on the transfer protocol level. The replacement encoding was used instead.
EncDetectorReload=The character encoding of the document was not declared, and the encoding was guessable from content only late. This caused the document to be reloaded. The character encoding needs to be declared in the Content-Type HTTP header, using a meta tag, or using a byte order mark.
EncDetectorReloadPlain=The character encoding of the document was not declared, and the encoding was guessable from content only late. This caused the document to be reloaded. The character encoding needs to be declared in the Content-Type HTTP header or using a byte order mark.
EncError=The byte stream was erroneous according to the character encoding that was declared. The character encoding declaration may be incorrect.
EncErrorFrame=The byte stream was erroneous according to the character encoding that was inherited from the parent document. The character encoding needs to be declared in the Content-Type HTTP header, using a meta tag, or using a byte order mark.
EncErrorFramePlain=The byte stream was erroneous according to the character encoding that was inherited from the parent document. The character encoding needs to be declared in the Content-Type HTTP header or using a byte order mark.
EncSpeculationFailMeta=The start of the document was reparsed, because there were non-ASCII characters before the meta tag that declared the encoding. The meta should be the first child of head without non-ASCII comments before.
EncSpeculationFailXml=The start of the document was reparsed, because there were non-ASCII characters in the part of the document that was unsuccessfully searched for a meta tag before falling back to the XML declaration syntax. A meta tag at the start of the head part should be used instead of the XML declaration syntax.
EncSpeculationFail2022=The start of the document was reparsed, because ISO-2022-JP is an ASCII-incompatible encoding.


errGarbageAfterLtSlash=Garbage after “</”.
errLtSlashGt=Saw “</>”. Probable causes: Unescaped “<” (escape as “&lt;”) or mistyped end tag.
errCharRefLacksSemicolon=Character reference was not terminated by a semicolon.
errNoDigitsInNCR=No digits in numeric character reference.
errGtInSystemId=“>” in system identifier.
errGtInPublicId=“>” in public identifier.
errNamelessDoctype=Nameless doctype.
errConsecutiveHyphens=Consecutive hyphens did not terminate a comment. “--” is not permitted inside a comment, but e.g. “- -” is.
errPrematureEndOfComment=Premature end of comment. Use “-->” to end a comment properly.
errBogusComment=Bogus comment.
errUnquotedAttributeLt=“<” in an unquoted attribute value. Probable cause: Missing “>” immediately before.
errUnquotedAttributeGrave=“`” in an unquoted attribute value. Probable cause: Using the wrong character as a quote.
errUnquotedAttributeQuote=Quote in an unquoted attribute value. Probable causes: Attributes running together or a URL query string in an unquoted attribute value.
errUnquotedAttributeEquals=“=” in an unquoted attribute value. Probable causes: Attributes running together or a URL query string in an unquoted attribute value.
errSlashNotFollowedByGt=A slash was not immediately followed by “>”.
errNoSpaceBetweenAttributes=No space between attributes.
errUnquotedAttributeStartLt=“<” at the start of an unquoted attribute value. Probable cause: Missing “>” immediately before.
errUnquotedAttributeStartGrave=“`” at the start of an unquoted attribute value. Probable cause: Using the wrong character as a quote.
errUnquotedAttributeStartEquals=“=” at the start of an unquoted attribute value. Probable cause: Stray duplicate equals sign.
errAttributeValueMissing=Attribute value missing.
errBadCharBeforeAttributeNameLt=Saw “<” when expecting an attribute name. Probable cause: Missing “>” immediately before.
errEqualsSignBeforeAttributeName=Saw “=” when expecting an attribute name. Probable cause: Attribute name missing.
errBadCharAfterLt=Bad character after “<”. Probable cause: Unescaped “<”. Try escaping it as “&lt;”.
errLtGt=Saw “<>”. Probable causes: Unescaped “<” (escape as “&lt;”) or mistyped start tag.
errProcessingInstruction=Saw “<?”. Probable cause: Attempt to use an XML processing instruction in HTML. (XML processing instructions are not supported in HTML.)
errUnescapedAmpersandInterpretedAsCharacterReference=The string following “&” was interpreted as a character reference. (“&” probably should have been escaped as “&amp;”.)
errNotSemicolonTerminated=Named character reference was not terminated by a semicolon. (Or “&” should have been escaped as “&amp;”.)
errNoNamedCharacterMatch=“&” did not start a character reference. (“&” probably should have been escaped as “&amp;”.)
errQuoteBeforeAttributeName=Saw a quote when expecting an attribute name. Probable cause: “=” missing immediately before.
errLtInAttributeName=“<” in attribute name. Probable cause: “>” missing immediately before.
errQuoteInAttributeName=Quote in attribute name. Probable cause: Matching quote missing somewhere earlier.
errExpectedPublicId=Expected a public identifier but the doctype ended.
errBogusDoctype=Bogus doctype.
maybeErrAttributesOnEndTag=End tag had attributes.
maybeErrSlashInEndTag=Stray “/” at the end of an end tag.
errNcrNonCharacter=Character reference expands to a non-character.
errNcrSurrogate=Character reference expands to a surrogate.
errNcrControlChar=Character reference expands to a control character.
errNcrCr=A numeric character reference expanded to carriage return.
errNcrInC1Range=A numeric character reference expanded to the C1 controls range.
errEofInPublicId=End of file inside public identifier.
errEofInComment=End of file inside comment.
errEofInDoctype=End of file inside doctype.
errEofInAttributeValue=End of file reached when inside an attribute value. Ignoring tag.
errEofInAttributeName=End of file occurred in an attribute name. Ignoring tag.
errEofWithoutGt=Saw end of file without the previous tag ending with “>”. Ignoring tag.
errEofInTagName=End of file seen when looking for tag name. Ignoring tag.
errEofInEndTag=End of file inside end tag. Ignoring tag.
errEofAfterLt=End of file after “<”.
errNcrOutOfRange=Character reference outside the permissible Unicode range.
errNcrUnassigned=Character reference expands to a permanently unassigned code point.
errDuplicateAttribute=Duplicate attribute.
errEofInSystemId=End of file inside system identifier.
errExpectedSystemId=Expected a system identifier but the doctype ended.
errMissingSpaceBeforeDoctypeName=Missing space before doctype name.
errNestedComment=Saw “<!--” within a comment. Probable cause: Nested comment (not allowed).
errNcrZero=Character reference expands to zero.
errNoSpaceBetweenDoctypeSystemKeywordAndQuote=No space between the doctype “SYSTEM” keyword and the quote.
errNoSpaceBetweenPublicAndSystemIds=No space between the doctype public and system identifiers.
errNoSpaceBetweenDoctypePublicKeywordAndQuote=No space between the doctype “PUBLIC” keyword and the quote.

errDeepTree=The document tree is too deep. The tree will be flattened to be 513 elements deep.
errStrayStartTag2=Stray start tag “%1$S”.
errStrayEndTag=Stray end tag “%1$S”.
errUnclosedElements=End tag “%1$S” seen, but there were open elements.
errUnclosedElementsImplied=End tag “%1$S” implied, but there were open elements.
errUnclosedElementsCell=A table cell was implicitly closed, but there were open elements.
errStrayDoctype=Stray doctype.
errAlmostStandardsDoctype=Almost standards mode doctype. Expected “<!DOCTYPE html>”.
errQuirkyDoctype=Quirky doctype. Expected “<!DOCTYPE html>”.
errAlmostStandardsDoctypeVerbose=This page is in Almost Standards Mode. Page layout may be impacted. For Standards Mode use “<!DOCTYPE html>”.
errQuirkyDoctypeVerbose=This page is in Quirks Mode. Page layout may be impacted. For Standards Mode use “<!DOCTYPE html>”.
errNonSpaceInTrailer=Non-space character in page trailer.
errNonSpaceAfterFrameset=Non-space after “frameset”.
errNonSpaceInFrameset=Non-space in “frameset”.
errNonSpaceAfterBody=Non-space character after body.
errNonSpaceInColgroupInFragment=Non-space in “colgroup” when parsing fragment.
errNonSpaceInNoscriptInHead=Non-space character inside “noscript” inside “head”.
errFooBetweenHeadAndBody=“%1$S” element between “head” and “body”.
errStartTagWithoutDoctype=Start tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.
errNoSelectInTableScope=No “select” in table scope.
errStartSelectWhereEndSelectExpected=“select” start tag where end tag expected.
errStartTagWithSelectOpen=“%1$S” start tag with “select” open.
errBadStartTagInNoscriptInHead=Bad start tag “%1$S” in “noscript” in “head”.
errImage=Saw a start tag “image”.
errFooSeenWhenFooOpen2=Start tag “%1$S” seen but an element of the same type was already open.
errHeadingWhenHeadingOpen=Heading cannot be a child of another heading.
errFramesetStart=“frameset” start tag seen.
errNoCellToClose=No cell to close.
errStartTagInTable=Start tag “%1$S” seen in “table”.
errFormWhenFormOpen=Saw a “form” start tag, but there was already an active “form” element. Nested forms are not allowed. Ignoring the tag.
errTableSeenWhileTableOpen=Start tag for “table” seen but the previous “table” is still open.
errStartTagInTableBody=“%1$S” start tag in table body.
errEndTagSeenWithoutDoctype=End tag seen without seeing a doctype first. Expected “<!DOCTYPE html>”.
errEndTagAfterBody=Saw an end tag after “body” had been closed.
errEndTagSeenWithSelectOpen=“%1$S” end tag with “select” open.
errGarbageInColgroup=Garbage in “colgroup” fragment.
errEndTagBr=End tag “br”.
errNoElementToCloseButEndTagSeen=No “%1$S” element in scope but a “%1$S” end tag seen.
errHtmlStartTagInForeignContext=HTML start tag “%1$S” in a foreign namespace context.
errNoTableRowToClose=No table row to close.
errNonSpaceInTable=Misplaced non-space characters inside a table.
errUnclosedChildrenInRuby=Unclosed children in “ruby”.
errStartTagSeenWithoutRuby=Start tag “%1$S” seen without a “ruby” element being open.
errSelfClosing=Self-closing syntax (“/>”) used on a non-void HTML element. Ignoring the slash and treating as a start tag.
errNoCheckUnclosedElementsOnStack=Unclosed elements on stack.
errEndTagDidNotMatchCurrentOpenElement=End tag “%1$S” did not match the name of the current open element (“%2$S”).
errEndTagViolatesNestingRules=End tag “%1$S” violates nesting rules.
errEndWithUnclosedElements=End tag for “%1$S” seen, but there were unclosed elements.
errListUnclosedStartTags=Unclosed element or elements.
PK
!<�܌�/�/@chrome/toolkit/content/global/antitracking/StripOnShareLGPL.json{
  "global": {
    "queryParams": [
      "at_campaign",
      "at_send_date",
      "at_campaign_type",
      "at_recipient_list",
      "at_creation",
      "at_recipient_id",
      "at_emailtype",
      "at_ptr_name",
      "at_link",
      "at_link_id",
      "at_medium",
      "at_link_type",
      "at_link_origin",
      "utm_email",
      "utm_email_id",
      "utm_campaign_id",
      "utm_newsletter_id"
    ],
    "topLevelSites": ["*"]
  },
  "twitter": {
    "queryParams": ["refsrc", "cxt", "s"],
    "topLevelSites": ["www.twitter.com", "twitter.com", "x.com"]
  },
  "amazon": {
    "queryParams": [
      "pd_rd_i",
      "pf_rd_i",
      "pf_rd_m",
      "pf_rd_s",
      "pf_rd_t",
      "pf_rd_w",
      "adgrpid",
      "ascsubtag",
      "creative",
      "creativeASIN",
      "dchild",
      "field-lbr_brands_browse-bin",
      "hvadid",
      "hvbmt",
      "hvdev",
      "hvlocphy",
      "hvnetw",
      "hvrand",
      "hvtargid",
      "hydadcr",
      "initialIssue",
      "ingress",
      "linkCode",
      "linkId",
      "plattr",
      "qid",
      "rdc",
      "refRID",
      "th",
      "ts_id",
      "visitId",
      "vtr",
      "spIA",
      "_encoding",
      "qualifier"
    ],
    "topLevelSites": [
      "www.amazon.com",
      "www.amazon.de",
      "www.amazon.nl",
      "www.amazon.fr",
      "www.amazon.co.jp",
      "www.amazon.in",
      "www.amazon.es",
      "www.amazon.ac",
      "www.amazon.cn",
      "www.amazon.eg",
      "www.amazon.in",
      "www.amazon.co.uk",
      "www.amazon.it",
      "www.amazon.pl",
      "www.amazon.sg",
      "www.amazon.ca"
    ]
  },
  "youtube": {
    "queryParams": ["si", "feature", "kw"],
    "topLevelSites": ["www.youtube.com", "youtu.be"]
  },
  "carousell": {
    "queryParams": [
      "referrer_search_query",
      "referrer_search_query_source",
      "referrer_request_id",
      "referrer_sort_by",
      "referrer_source",
      "referrer_page_type",
      "referrer_category_id",
      "t-source",
      "tap_index",
      "t-referrer_browse_type",
      "t-id"
    ],
    "topLevelSites": [
      "ca.carousell.com",
      "www.carousell.sg",
      "www.carousell.com.hk",
      "us.carousell.com",
      "au.carousell.com"
    ]
  },
  "etsy": {
    "queryParams": ["click_sum", "ref", "click_key", "organic_search_click"],
    "topLevelSites": ["www.etsy.com"]
  },
  "wikipedia": {
    "queryParams": ["wprov"],
    "topLevelSites": ["www.wikipedia.org", "en.wikipedia.org"]
  },
  "wallstreetjournal": {
    "queryParams": ["reflink"],
    "topLevelSites": ["www.wsj.com"]
  },
  "theathletic": {
    "queryParams": ["source"],
    "topLevelSites": ["theathletic.com"]
  },
  "forbes": {
    "queryParams": ["sh"],
    "topLevelSites": ["www.forbes.com"]
  },
  "bloomberg": {
    "queryParams": ["leadSource", "sref", "srnd"],
    "topLevelSites": ["www.bloomberg.com"]
  },
  "tiktok": {
    "queryParams": [
      "share_app_id",
      "share_author_id",
      "tt_from",
      "embed_source",
      "referer_url",
      "refer",
      "is_from_webapp",
      "sender_device",
      "sec_uid",
      "web_id",
      "enter_method",
      "t",
      "q"
    ],
    "topLevelSites": ["www.tiktok.com"]
  },
  "bbc": {
    "queryParams": [
      "facebook_page",
      "at_bbc_team",
      "ocid",
      "ns_mchannel",
      "ns_source",
      "ns_campaign",
      "ns_linkname",
      "ns_fee",
      "xtor"
    ],
    "topLevelSites": ["www.bbc.com"]
  },
  "cnn": {
    "queryParams": ["ref"],
    "topLevelSites": ["www.cnn.co.jp", "www.cnn.com", "edition.cnn.com"]
  },
  "imdb": {
    "queryParams": [
      "pf_rd_p",
      "pf_rd_i",
      "pf_rd_m",
      "pf_rd_s",
      "pf_rd_t",
      "pf_rd_r"
    ],
    "topLevelSites": ["www.imdb.com", "m.imdb.com"]
  },
  "mirror": {
    "queryParams": ["int_source"],
    "topLevelSites": ["www.mirror.co.uk"]
  },
  "wise": {
    "queryParams": ["partnerizecampaignID", "clickref", "adref", "partnerID"],
    "topLevelSites": ["wise.com"]
  },
  "facebook": {
    "queryParams": ["tracking", "extid", "mibextid"],
    "topLevelSites": ["www.facebook.com"]
  },
  "lazada": {
    "queryParams": [
      "mkttid",
      "laz_trackid",
      "spm",
      "clickTrackInfo",
      "trafficFrom",
      "scm",
      "acm",
      "ad_src",
      "did",
      "mp",
      "cid",
      "pos"
    ],
    "topLevelSites": [
      "www.lazada.com.ph",
      "www.lazada.vn",
      "www.lazada.sg",
      "www.lazada.com.my",
      "pages.lazada.co.id",
      "www.lazada.co.id"
    ]
  },
  "msn": {
    "queryParams": ["cvid", "pc", "ei"],
    "topLevelSites": ["www.msn.com"]
  },
  "aliexpress": {
    "queryParams": [
      "sk",
      "dp",
      "spm",
      "scm",
      "pvid",
      "scm_id",
      "scm-url",
      "utparam",
      "aff_fsk",
      "aff_fcid",
      "terminal_id",
      "aff_trace_key",
      "pdp_npi",
      "aff_short_key",
      "aff_platform",
      "algo_pvid",
      "curPageLogUid",
      "mall_affr",
      "algo_expid",
      "gps-id"
    ],
    "topLevelSites": [
      "www.aliexpress.com",
      "robotime.aliexpress.com",
      "ar.aliexpress.com"
    ]
  },
  "reddit": {
    "queryParams": [
      "ref",
      "ref_source",
      "ref_campaign",
      "correlation_id",
      "share_id"
    ],
    "topLevelSites": ["www.reddit.com"]
  },
  "wired": {
    "queryParams": ["mbid"],
    "topLevelSites": ["www.wired.co.uk", "www.wired.com"]
  },
  "yandex": {
    "queryParams": [
      "utm-term",
      "did",
      "from",
      "msid",
      "stid",
      "mlid",
      "persistent_id",
      "source-serpid",
      "suggest_reqid",
      "clid"
    ],
    "topLevelSites": ["yandex.com"]
  },
  "ebay": {
    "queryParams": [
      "_trkparms",
      "mkcid",
      "_trksid",
      "mkevt",
      "amdata",
      "ssuid",
      "mkrid",
      "campid",
      "sssrc",
      "ssspo",
      "_from",
      "hash"
    ],
    "topLevelSites": ["www.ebay.com", "www.ebay.co.uk"]
  },
  "flipkart": {
    "queryParams": [
      "_refId",
      "store",
      "ssid",
      "affid",
      "cid",
      "iid",
      "pwsvid",
      "pageUID",
      "lid"
    ],
    "topLevelSites": ["www.flipkart.com"]
  },
  "google": {
    "queryParams": [
      "sca_esv",
      "ved",
      "pcampaignid",
      "rlz",
      "ei",
      "sxsrf",
      "sourceid",
      "aqs",
      "cad",
      "usg",
      "dpr",
      "dcr",
      "sei",
      "cd",
      "vet",
      "esrc",
      "site",
      "gs_l",
      "gs_lp",
      "sclient",
      "oe",
      "visit_id",
      "biw",
      "bih",
      "gs_lcp",
      "gs_lcrp"
    ],
    "topLevelSites": [
      "www.google.com",
      "www.google.ad",
      "www.google.ae",
      "www.google.com.af",
      "www.google.com.ag",
      "www.google.al",
      "www.google.am",
      "www.google.co.ao",
      "www.google.com.ar",
      "www.google.as",
      "www.google.at",
      "www.google.com.au",
      "www.google.az",
      "www.google.ba",
      "www.google.com.bd",
      "www.google.be",
      "www.google.bf",
      "www.google.bg",
      "www.google.com.bh",
      "www.google.bi",
      "www.google.bj",
      "www.google.com.bn",
      "www.google.com.bo",
      "www.google.com.br",
      "www.google.bs",
      "www.google.bt",
      "www.google.co.bw",
      "www.google.by",
      "www.google.com.bz",
      "www.google.ca",
      "www.google.cd",
      "www.google.cf",
      "www.google.cg",
      "www.google.ch",
      "www.google.ci",
      "www.google.co.ck",
      "www.google.cl",
      "www.google.cm",
      "www.google.cn",
      "www.google.com.co",
      "www.google.co.cr",
      "www.google.com.cu",
      "www.google.cv",
      "www.google.com.cy",
      "www.google.cz",
      "www.google.de",
      "www.google.dj",
      "www.google.dk",
      "www.google.dm",
      "www.google.com.do",
      "www.google.dz",
      "www.google.com.ec",
      "www.google.ee",
      "www.google.com.eg",
      "www.google.es",
      "www.google.com.et",
      "www.google.fi",
      "www.google.com.fj",
      "www.google.fm",
      "www.google.fr",
      "www.google.ga",
      "www.google.ge",
      "www.google.gg",
      "www.google.com.gh",
      "www.google.com.gi",
      "www.google.gl",
      "www.google.gm",
      "www.google.gr",
      "www.google.com.gt",
      "www.google.gy",
      "www.google.com.hk",
      "www.google.hn",
      "www.google.hr",
      "www.google.ht",
      "www.google.hu",
      "www.google.co.id",
      "www.google.ie",
      "www.google.co.il",
      "www.google.im",
      "www.google.co.in",
      "www.google.iq",
      "www.google.is",
      "www.google.it",
      "www.google.je",
      "www.google.com.jm",
      "www.google.jo",
      "www.google.co.jp",
      "www.google.co.ke",
      "www.google.com.kh",
      "www.google.ki",
      "www.google.kg",
      "www.google.co.kr",
      "www.google.com.kw",
      "www.google.kz",
      "www.google.la",
      "www.google.com.lb",
      "www.google.li",
      "www.google.lk",
      "www.google.co.ls",
      "www.google.lt",
      "www.google.lu",
      "www.google.lv",
      "www.google.com.ly",
      "www.google.co.ma",
      "www.google.md",
      "www.google.me",
      "www.google.mg",
      "www.google.mk",
      "www.google.ml",
      "www.google.com.mm",
      "www.google.mn",
      "www.google.com.mt",
      "www.google.mu",
      "www.google.mv",
      "www.google.mw",
      "www.google.com.mx",
      "www.google.com.my",
      "www.google.co.mz",
      "www.google.com.na",
      "www.google.com.ng",
      "www.google.com.ni",
      "www.google.ne",
      "www.google.nl",
      "www.google.no",
      "www.google.com.np",
      "www.google.nr",
      "www.google.nu",
      "www.google.co.nz",
      "www.google.com.om",
      "www.google.com.pa",
      "www.google.com.pe",
      "www.google.com.pg",
      "www.google.com.ph",
      "www.google.com.pk",
      "www.google.pl",
      "www.google.pn",
      "www.google.com.pr",
      "www.google.ps",
      "www.google.pt",
      "www.google.com.py",
      "www.google.com.qa",
      "www.google.ro",
      "www.google.ru",
      "www.google.rw",
      "www.google.com.sa",
      "www.google.com.sb",
      "www.google.sc",
      "www.google.se",
      "www.google.com.sg",
      "www.google.sh",
      "www.google.si",
      "www.google.sk",
      "www.google.com.sl",
      "www.google.sn",
      "www.google.so",
      "www.google.sm",
      "www.google.sr",
      "www.google.st",
      "www.google.com.sv",
      "www.google.td",
      "www.google.tg",
      "www.google.co.th",
      "www.google.com.tj",
      "www.google.tl",
      "www.google.tm",
      "www.google.tn",
      "www.google.to",
      "www.google.com.tr",
      "www.google.tt",
      "www.google.com.tw",
      "www.google.co.tz",
      "www.google.com.ua",
      "www.google.co.ug",
      "www.google.co.uk",
      "www.google.com.uy",
      "www.google.co.uz",
      "www.google.com.vc",
      "www.google.co.ve",
      "www.google.co.vi",
      "www.google.com.vn",
      "www.google.vu",
      "www.google.ws",
      "www.google.rs",
      "www.google.co.za",
      "www.google.co.zm",
      "www.google.co.zw",
      "www.google.cat",
      "consent.google.de",
      "support.google.com"
    ]
  },
  "bing": {
    "queryParams": ["qp", "cvid", "qs", "form", "sk", "sc", "sp"],
    "topLevelSites": ["www.bing.com"]
  },
  "twitch": {
    "queryParams": ["tt_content", "tt_medium"],
    "topLevelSites": ["www.twitch.tv", "dev.twitch.tv"]
  },
  "cnet": {
    "queryParams": ["ftag"],
    "topLevelSites": ["www.cnet.com"]
  },
  "nytimes": {
    "queryParams": ["smid"],
    "topLevelSites": ["www.nytimes.com"]
  },
  "github": {
    "queryParams": ["email_token", "email_source"],
    "topLevelSites": ["github.com"]
  },
  "linkedin": {
    "queryParams": ["refId", "trk", "trackingId"],
    "topLevelSites": ["ca.linkedin.com", "ro.linkedin.com"]
  },
  "newyorker": {
    "queryParams": ["esrc", "bxid", "cndid", "source", "mbid"],
    "topLevelSites": ["www.newyorker.com"]
  },
  "bestbuy": {
    "queryParams": ["acampID", "irclickid", "irgwc", "loc", "mpid", "intl"],
    "topLevelSites": ["www.bestbuy.com"]
  }
}
PK
!<��t���1localization/en-US/toolkit/global/textActions.ftl
text-action-undo =
    .label = Undo
    .accesskey = U

text-action-undo-shortcut =
    .key = Z

text-action-redo =
    .label = Redo
    .accesskey = R

text-action-redo-shortcut =
    .key = Y

text-action-cut =
    .label = Cut
    .accesskey = t

text-action-cut-shortcut =
    .key = X

text-action-copy =
    .label = Copy
    .accesskey = C

text-action-copy-shortcut =
    .key = C

text-action-strip-on-share =
    .label = Copy Without Site Tracking
    .accesskey = n

text-action-paste =
    .label = Paste
    .accesskey = P

text-action-paste-no-formatting =
    .label = Paste Without Formatting
    .accesskey = m

text-action-paste-shortcut =
    .key = V

text-action-delete =
    .label = Delete
    .accesskey = D

text-action-select-all =
    .label = Select All
    .accesskey = A

text-action-select-all-shortcut =
    .key = A

text-action-spell-no-suggestions =
    .label = No Spelling Suggestions

text-action-spell-add-to-dictionary =
    .label = Add to Dictionary
    .accesskey = o

text-action-spell-undo-add-to-dictionary =
    .label = Undo Add To Dictionary
    .accesskey = n

text-action-spell-check-toggle =
    .label = Check Spelling
    .accesskey = g

text-action-spell-add-dictionaries =
    .label = Add Dictionaries…
    .accesskey = A

text-action-spell-dictionaries =
    .label = Languages
    .accesskey = L

text-action-search-text-box-clear =
    .title = Clear

text-action-highlight-selection =
    .label = Highlight Selection
PK
!<"�R���4localization/en-US/toolkit/global/arrowscrollbox.ftl

overflow-scroll-button-backwards =
    .tooltiptext = Scroll backwards
overflow-scroll-button-forwards =
    .tooltiptext = Scroll forwards
PK
!<#��1localization/en-US/toolkit/branding/brandings.ftl

-facebook-container-brand-name = Facebook Container
-monitor-brand-name = Firefox Monitor
-monitor-brand-short-name = Monitor
-mozmonitor-brand-name = Mozilla Monitor
-pocket-brand-name = Pocket
-send-brand-name = Firefox Send
-screenshots-brand-name = Firefox Screenshots
-mozilla-vpn-brand-name = Mozilla VPN
-profiler-brand-name = Firefox Profiler
-translations-brand-name = Firefox Translations
-focus-brand-name = Firefox Focus
-relay-brand-name = Firefox Relay
-relay-brand-short-name = Relay
-fakespot-brand-name = Fakespot

-fakespot-website-name = Fakespot.com

-fakespot-brand-full-name = Fakespot by Mozilla

-firefox-suggest-brand-name = Firefox Suggest

-firefox-home-brand-name = Firefox Home

-firefoxview-brand-name = Firefox View

-firefoxlabs-brand-name = Firefox Labs
PK
!<�?�ڒ�9localization/en-US/toolkit/global/contextual-identity.ftl

user-context-personal =
    .label = Personal
    .accesskey = P
user-context-work =
    .label = Work
    .accesskey = W
user-context-banking =
    .label = Banking
    .accesskey = B
user-context-shopping =
    .label = Shopping
    .accesskey = S


user-context-none =
    .label = No Container
    .accesskey = N
user-context-manage-containers =
    .label = Manage containers
    .accesskey = o
PK
!<l傝�
�
/localization/en-US/toolkit/printing/printUI.ftl
printui-title = Print
printui-save-to-pdf-title = Save As

printui-sheets-count =
    { $sheetCount ->
        [one] { $sheetCount } sheet of paper
       *[other] { $sheetCount } sheets of paper
    }

printui-page-range-all = All
printui-page-range-current = Current
printui-page-range-odd = Odd
printui-page-range-even = Even
printui-page-range-custom = Custom
printui-page-range-label = Pages
printui-page-range-picker =
  .aria-label = Pick page range
printui-page-custom-range-input =
  .aria-label = Enter custom page range
  .placeholder = e.g. 2-6, 9, 12-16

printui-copies-label = Copies

printui-orientation = Orientation
printui-landscape = Landscape
printui-portrait = Portrait

printui-destination-label = Destination
printui-destination-pdf-label = Save to PDF

printui-more-settings = More settings
printui-less-settings = Fewer settings

printui-paper-size-label = Paper size

printui-scale = Scale
printui-scale-fit-to-page-width = Fit to page width
printui-scale-pcent = Scale

printui-two-sided-printing = Two-sided printing
printui-two-sided-printing-off = Off
printui-two-sided-printing-long-edge = Flip on long edge
printui-two-sided-printing-short-edge = Flip on short edge

printui-options = Options
printui-headers-footers-checkbox = Print headers and footers
printui-backgrounds-checkbox = Print backgrounds


printui-source-label = Format
printui-source-radio = Original
printui-selection-radio = Selection
printui-simplify-page-radio = Simplified


printui-color-mode-label = Color mode
printui-color-mode-color = Color
printui-color-mode-bw = Black and white

printui-margins = Margins
printui-margins-default = Default
printui-margins-min = Minimum
printui-margins-none = None
printui-margins-custom-inches = Custom (inches)
printui-margins-custom-mm = Custom (mm)
printui-margins-custom-top = Top
printui-margins-custom-top-inches = Top (inches)
printui-margins-custom-top-mm = Top (mm)
printui-margins-custom-bottom = Bottom
printui-margins-custom-bottom-inches = Bottom (inches)
printui-margins-custom-bottom-mm = Bottom (mm)
printui-margins-custom-left = Left
printui-margins-custom-left-inches = Left (inches)
printui-margins-custom-left-mm = Left (mm)
printui-margins-custom-right = Right
printui-margins-custom-right-inches = Right (inches)
printui-margins-custom-right-mm = Right (mm)

printui-system-dialog-link = Print using the system dialog…

printui-primary-button = Print
printui-primary-button-save = Save
printui-cancel-button = Cancel
printui-close-button = Close

printui-loading = Preparing Preview

printui-preview-label =
    .aria-label = Print Preview

printui-pages-per-sheet = Pages per sheet

printui-print-progress-indicator = Printing…
printui-print-progress-indicator-saving = Saving…


printui-paper-a5 = A5
printui-paper-a4 = A4
printui-paper-a3 = A3
printui-paper-a2 = A2
printui-paper-a1 = A1
printui-paper-a0 = A0
printui-paper-b5 = B5
printui-paper-b4 = B4
printui-paper-jis-b5 = JIS-B5
printui-paper-jis-b4 = JIS-B4
printui-paper-letter = US Letter
printui-paper-legal = US Legal
printui-paper-tabloid = Tabloid


printui-error-invalid-scale = Scale must be a number between 10 and 200.
printui-error-invalid-margin = Please enter a valid margin for the selected paper size.
printui-error-invalid-copies = Copies must be a number between 1 and 10000.

printui-error-invalid-range = Range must be a number between 1 and { $numPages }.
printui-error-invalid-start-overflow = The “from” page number must be smaller than the “to” page number.
PK
!<T�6���7chrome/toolkit/skin/classic/global/icons/arrow-left.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m5.001 8.352 5.465 5.466a.626.626 0 0 0 .884-.886L6.416 7.999l4.933-4.932a.626.626 0 0 0-.885-.885L5 7.647l.001.705z"/>
</svg>
PK
!<3�3�\\5chrome/toolkit/skin/classic/global/icons/close-12.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" width="12" height="12" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M5.859 6.849 2.854 9.854a.5.5 0 0 1-.707-.707l3.005-3.005 0-.283-3.005-3.006a.5.5 0 0 1 .707-.707l3.005 3.005.283 0 3.005-3.005a.5.5 0 0 1 .707.707L6.849 5.859l0 .283 3.005 3.005a.5.5 0 0 1-.707.707L6.142 6.849l.001.001-.284-.001z"/>
</svg>
PK
!<�B�1chrome/toolkit/skin/classic/global/icons/plus.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M7 9.75 7 14a.625.625 0 0 0 1.25 0l0-4.25.5-.5 4.25 0A.625.625 0 0 0 13 8L8.75 8l-.5-.5 0-4.25a.625.625 0 0 0-1.25 0L7 7.5l-.5.5-4.25 0a.625.625 0 0 0 0 1.25l4.137 0z"/>
</svg>
PK
!<���A��3chrome/toolkit/skin/classic/global/icons/reload.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M10.707 6 14.7 6l.3-.3 0-3.993a.5.5 0 0 0-.854-.354l-1.459 1.459A6.95 6.95 0 0 0 8 1C4.141 1 1 4.141 1 8s3.141 7 7 7a6.97 6.97 0 0 0 6.968-6.322.626.626 0 0 0-.562-.682.635.635 0 0 0-.682.562A5.726 5.726 0 0 1 8 13.75c-3.171 0-5.75-2.579-5.75-5.75S4.829 2.25 8 2.25a5.71 5.71 0 0 1 3.805 1.445l-1.451 1.451a.5.5 0 0 0 .353.854z"/>
</svg>
PK
!<X��|��9chrome/toolkit/skin/classic/global/icons/search-glass.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m10.089 10.973 3.845 3.844a.62.62 0 0 0 .883-.001.625.625 0 0 0-.001-.884L10.983 10.1l.006-.427A5.5 5.5 0 1 0 1 6.5 5.5 5.5 0 0 0 6.5 12c1.189 0 2.288-.38 3.187-1.022l.402-.005zM6.5 10.75c-2.343 0-4.25-1.907-4.25-4.25S4.157 2.25 6.5 2.25s4.25 1.907 4.25 4.25-1.907 4.25-4.25 4.25z"/>
</svg>
PK
!<�r�qq4chrome/toolkit/skin/classic/global/icons/chevron.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m7.999 8.352-5.465 5.466a.626.626 0 0 1-.884-.886l4.935-4.934-4.934-4.931a.626.626 0 0 1 .885-.885L8 7.647l-.001.705z"/>
  <path d="m13.999 8.352-5.465 5.466a.626.626 0 0 1-.884-.886l4.935-4.934-4.934-4.931a.626.626 0 0 1 .885-.885L14 7.647l-.001.705z"/>
</svg>
PK
!<�s2'@@%contentaccessible/accessiblecaret.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:host {
  /* Add transition effect to make caret size changing smoother. */
  transition-property: width, height, margin-left;

  position: absolute;
  z-index: 2147483647;
}

#image,
#text-overlay {
  width: 100%;

  /* Override this property in moz-custom-content-container to make dummy touch
   * listener work. */
  pointer-events: auto;
}

#image {
  background-position: center top;
  background-size: 100%;
  background-repeat: no-repeat;
  background-origin: content-box;
}

:host(.normal) #image {
  background-image: image-set(
    url("resource://gre-resources/accessiblecaret-normal@1x.png"),
    url("resource://gre-resources/accessiblecaret-normal@1.5x.png") 1.5x,
    url("resource://gre-resources/accessiblecaret-normal@2x.png") 2x,
    url("resource://gre-resources/accessiblecaret-normal@2.25x.png") 2.25x
  );
}

:host(.left) #image,
:host(.left) #text-overlay {
  margin-left: -39%;
}

:host(.left) > #image {
  background-image: image-set(
    url("resource://gre-resources/accessiblecaret-tilt-left@1x.png"),
    url("resource://gre-resources/accessiblecaret-tilt-left@1.5x.png") 1.5x,
    url("resource://gre-resources/accessiblecaret-tilt-left@2x.png") 2x,
    url("resource://gre-resources/accessiblecaret-tilt-left@2.25x.png") 2.25x
  );
}

:host(.right) #image,
:host(.right) #text-overlay {
  margin-left: 41%;
}

:host(.right) #image {
  background-image: image-set(
    url("resource://gre-resources/accessiblecaret-tilt-right@1x.png"),
    url("resource://gre-resources/accessiblecaret-tilt-right@1.5x.png") 1.5x,
    url("resource://gre-resources/accessiblecaret-tilt-right@2x.png") 2x,
    url("resource://gre-resources/accessiblecaret-tilt-right@2.25x.png") 2.25x
  );
}

:host(.none) {
  display: none;
}

:host(.hidden) {
  visibility: hidden;
}

@media (-moz-platform: android) {
  #image,
  #text-overlay {
    /* border: 0.1px solid red; */ /* Uncomment border to see the touch target. */
    padding-left: 59%; /* Enlarge the touch area. ((48-22)/2)px / 22px ~= 59% */
    padding-right: 59%; /* Enlarge the touch area. */
    margin-left: -59%;
  }

  #image {
    padding-bottom: 59%; /* Enlarge the touch area. */
  }

  :host(.normal) #image {
    background-image: url("resource://gre-resources/accessiblecaret-normal.svg");
  }

  :host(.left) #image,
  :host(.left) #text-overlay {
    margin-left: -109%;
  }

  :host(.left) #image {
    background-image: url("resource://gre-resources/accessiblecaret-tilt-left.svg");
  }

  :host(.right) #image,
  :host(.right) #text-overlay {
    margin-left: -12%;
  }

  :host(.right) #image {
    background-image: url("resource://gre-resources/accessiblecaret-tilt-right.svg");
  }
}
PK
!<R��h��/chrome/toolkit/skin/classic/global/menulist.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/menulist-shared.css");

:host([native]) {
  margin: 2px 4px;
  color: -moz-DialogText;
  font: menu;
}

:host([native]:not([disabled="true"]):hover) {
  color: -moz-buttonhovertext;
}

:host([native]:not([disabled="true"]):hover:active),
:host([native]:not([disabled="true"])[open="true"]) {
  color: -moz-buttonactivetext;
}

:host([native]) label {
  margin: 1px 3px;
}

:host([native]) dropmarker {
  display: none;
}
PK
!<��N��6chrome/toolkit/skin/classic/global/menulist-shared.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:host([native]) {
  appearance: auto;
  -moz-default-appearance: menulist;
  text-shadow: none;
}

:host([native][disabled="true"]) {
  color: GrayText;
}

:host(:not([native])) {
  appearance: none;
  background-color: var(--button-bgcolor, ButtonFace);
  color: var(--button-color, ButtonText);
  border-radius: 4px;
  padding-block: 4px;
  padding-inline: 12px 8px;
  margin: 5px 2px 3px;
}

:host(:not([native])[size="medium"]) {
  padding-block: 6px;
  padding-inline: 16px 10px;
}

:host(:not([native])[size="large"]) {
  padding-block: 8px;
  padding-inline: 16px 12px;
}

:host(:not([native]):hover) {
  background-color: var(--button-hover-bgcolor, color-mix(in srgb, currentColor 10%, ButtonFace));
}

:host(:not([native]):hover:active) {
  background-color: var(--button-active-bgcolor, color-mix(in srgb, currentColor 20%, ButtonFace));
}

:host(:not([native]):focus-visible) {
  outline: var(--focus-outline);
  outline-offset: var(--focus-outline-offset);
}

#label-box {
  min-width: 0;
  pointer-events: none;
  align-items: center;
  justify-content: center;

  :host(:not([native])) & {
    font-weight: 600;
  }
}

dropmarker {
  pointer-events: none;
  flex: 0;

  :host(:not([native])) & {
    display: flex;
    appearance: none;
    content: url(chrome://global/skin/icons/arrow-down-12.svg);
    -moz-context-properties: fill;
    fill: currentColor;
  }
}

:host(:not([highlightable])) #highlightable-label,
:host([highlightable]) #label {
  display: none;
}

label {
  margin: 0 3px;
}
PK
!<�R�!$$4localization/en-US/toolkit/global/mozSupportLink.ftl
moz-support-link-text = Learn more
PK
!<cfy�llFchrome/en-US/locale/en-US/global-platform/unix/platformKeys.properties

VK_SHIFT=Shift

VK_COMMAND_OR_WIN=Win

VK_ALT=Alt

VK_CONTROL=Ctrl

VK_RETURN=Enter

MODIFIER_SEPARATOR=+
PK
!<Z����A�A0localization/en-US/toolkit/about/aboutAddons.ftl
addons-page-title = Add-ons Manager

search-header =
    .placeholder = Search addons.mozilla.org
    .searchbuttonlabel = Search


list-empty-get-extensions-message =
    Get extensions and themes on <a data-l10n-name="get-extensions">{ $domain }</a>

list-empty-get-dictionaries-message =
    Get dictionaries on <a data-l10n-name="get-extensions">{ $domain }</a>

list-empty-get-language-packs-message =
    Get language packs on <a data-l10n-name="get-extensions">{ $domain }</a>


list-empty-installed =
    .value = You don’t have any add-ons of this type installed

list-empty-available-updates =
    .value = No updates found

list-empty-recent-updates =
    .value = You haven’t recently updated any add-ons

list-empty-find-updates =
    .label = Check For Updates

list-empty-button =
    .label = Learn more about add-ons

help-button = Add-ons Support
sidebar-help-button-title =
    .title = Add-ons Support

addons-settings-button = { -brand-short-name } Settings
sidebar-settings-button-title =
    .title = { -brand-short-name } Settings

show-unsigned-extensions-button =
    .label = Some extensions could not be verified

show-all-extensions-button =
    .label = Show all extensions

detail-version =
    .label = Version

detail-last-updated =
    .label = Last Updated

addon-detail-description-expand = Show more
addon-detail-description-collapse = Show less

detail-contributions-description = The developer of this add-on asks that you help support its continued development by making a small contribution.

detail-contributions-button = Contribute
    .title = Contribute to the development of this add-on
    .accesskey = C

detail-update-type =
    .value = Automatic Updates

detail-update-default =
    .label = Default
    .tooltiptext = Automatically install updates only if that’s the default

detail-update-automatic =
    .label = On
    .tooltiptext = Automatically install updates

detail-update-manual =
    .label = Off
    .tooltiptext = Don’t automatically install updates

detail-private-browsing-label = Run in Private Windows

detail-private-disallowed-label = Not Allowed in Private Windows
detail-private-disallowed-description2 = This extension does not run while private browsing. <a data-l10n-name="learn-more">Learn more</a>

detail-private-required-label = Requires Access to Private Windows
detail-private-required-description2 = This extension has access to your online activities while private browsing. <a data-l10n-name="learn-more">Learn more</a>

detail-private-browsing-on =
    .label = Allow
    .tooltiptext = Enable in Private Browsing

detail-private-browsing-off =
    .label = Don’t Allow
    .tooltiptext = Disable in Private Browsing

detail-home =
    .label = Homepage

detail-home-value =
    .value = { detail-home.label }

detail-repository =
    .label = Add-on Profile

detail-repository-value =
    .value = { detail-repository.label }

detail-check-for-updates =
    .label = Check for Updates
    .accesskey = U
    .tooltiptext = Check for updates for this add-on

detail-show-preferences =
    .label =
        { PLATFORM() ->
            [windows] Options
           *[other] Preferences
        }
    .accesskey =
        { PLATFORM() ->
            [windows] O
           *[other] P
        }
    .tooltiptext =
        { PLATFORM() ->
            [windows] Change this add-on’s options
           *[other] Change this add-on’s preferences
        }

detail-rating =
    .value = Rating

addon-restart-now =
    .label = Restart now

disabled-unsigned-heading =
    .value = Some add-ons have been disabled

disabled-unsigned-description =
    The following add-ons have not been verified for use in { -brand-short-name }. You can
    <label data-l10n-name="find-addons">find replacements</label> or ask the developer to get them verified.

disabled-unsigned-learn-more = Learn more about our efforts to help keep you safe online.

disabled-unsigned-devinfo =
    Developers interested in getting their add-ons verified can continue by reading our
    <label data-l10n-name="learn-more">manual</label>.

plugin-deprecation-description =
    Missing something? Some plugins are no longer supported by { -brand-short-name }. <label data-l10n-name="learn-more">Learn More.</label>

legacy-warning-show-legacy = Show legacy extensions

legacy-extensions =
    .value = Legacy Extensions

legacy-extensions-description =
    These extensions do not meet current { -brand-short-name } standards so they have been deactivated. <label data-l10n-name="legacy-learn-more">Learn about the changes to add-ons</label>

private-browsing-description2 =
    { -brand-short-name } is changing how extensions work in private browsing. Any new extensions you add to
    { -brand-short-name } won’t run by default in Private Windows. Unless you allow it in settings, the
    extension won’t work while private browsing, and won’t have access to your online activities
    there. We’ve made this change to keep your private browsing private.
    <label data-l10n-name="private-browsing-learn-more">Learn how to manage extension settings</label>

addon-category-discover = Recommendations
addon-category-discover-title =
    .title = Recommendations
addon-category-extension = Extensions
addon-category-extension-title =
    .title = Extensions
addon-category-theme = Themes
addon-category-theme-title =
    .title = Themes
addon-category-plugin = Plugins
addon-category-plugin-title =
    .title = Plugins
addon-category-dictionary = Dictionaries
addon-category-dictionary-title =
    .title = Dictionaries
addon-category-locale = Languages
addon-category-locale-title =
    .title = Languages
addon-category-available-updates = Available Updates
addon-category-available-updates-title =
    .title = Available Updates
addon-category-recent-updates = Recent Updates
addon-category-recent-updates-title =
    .title = Recent Updates
addon-category-sitepermission = Site Permissions
addon-category-sitepermission-title =
    .title = Site Permissions
addon-sitepermission-host = Site Permissions for { $host }


extensions-warning-safe-mode2 =
    .message = All add-ons have been disabled by safe mode.
extensions-warning-check-compatibility2 =
    .message = Add-on compatibility checking is disabled. You may have incompatible add-ons.
extensions-warning-check-compatibility-button = Enable
    .title = Enable add-on compatibility checking
extensions-warning-update-security2 =
    .message = Add-on update security checking is disabled. You may be compromised by updates.
extensions-warning-update-security-button = Enable
    .title = Enable add-on update security checking
extensions-warning-imported-addons2 =
    .message = Please finalize the installation of extensions that were imported to { -brand-short-name }.
extensions-warning-imported-addons-button = Install Extensions


addon-updates-check-for-updates = Check for Updates
    .accesskey = C
addon-updates-view-updates = View Recent Updates
    .accesskey = V


addon-updates-update-addons-automatically = Update Add-ons Automatically
    .accesskey = A


addon-updates-reset-updates-to-automatic = Reset All Add-ons to Update Automatically
    .accesskey = R
addon-updates-reset-updates-to-manual = Reset All Add-ons to Update Manually
    .accesskey = R


addon-updates-updating = Updating add-ons
addon-updates-installed = Your add-ons have been updated.
addon-updates-none-found = No updates found
addon-updates-manual-updates-found = View Available Updates


addon-install-from-file = Install Add-on From File…
    .accesskey = I
addon-install-from-file-dialog-title = Select add-on to install
addon-install-from-file-filter-name = Add-ons
addon-open-about-debugging = Debug Add-ons
    .accesskey = b


addon-manage-extensions-shortcuts = Manage Extension Shortcuts
    .accesskey = S

shortcuts-no-addons = You don’t have any extensions enabled.
shortcuts-no-commands = The following extensions do not have shortcuts:
shortcuts-input =
  .placeholder = Type a shortcut
shortcuts-remove-button =
  .aria-label = Remove shortcut

shortcuts-browserAction2 = Activate toolbar button
shortcuts-pageAction = Activate page action
shortcuts-sidebarAction = Toggle the sidebar

shortcuts-modifier-mac = Include Ctrl, Alt, or ⌘
shortcuts-modifier-other = Include Ctrl or Alt
shortcuts-invalid = Invalid combination
shortcuts-letter = Type a letter
shortcuts-system = Can’t override a { -brand-short-name } shortcut

shortcuts-duplicate = Duplicate shortcut

shortcuts-duplicate-warning-message2 =
    .message = { $shortcut } is being used as a shortcut in more than one case. Duplicate shortcuts may cause unexpected behavior.

shortcuts-exists = Already in use by { $addon }

shortcuts-card-expand-button =
    { $numberToShow ->
        *[other] Show { $numberToShow } More
    }

shortcuts-card-collapse-button = Show Less

header-back-button =
    .title = Go back


discopane-intro =
    Extensions and themes are like apps for your browser, and they let you
    protect passwords, download videos, find deals, block annoying ads, change
    how your browser looks, and much more. These small software programs are
    often developed by a third party. Here’s a selection { -brand-product-name }
    <a data-l10n-name="learn-more-trigger">recommends</a> for exceptional
    security, performance, and functionality.

discopane-notice-recommendations2 =
    .message =
        Some of these recommendations are personalized. They are based on other
        extensions you’ve installed, profile preferences, and usage statistics.
discopane-notice-learn-more = Learn more

privacy-policy = Privacy Policy

created-by-author = by <a data-l10n-name="author">{ $author }</a>
user-count = Users: { $dailyUsers }
install-extension-button = Add to { -brand-product-name }
install-theme-button = Install Theme
manage-addon-button = Manage
find-more-addons = Find more add-ons
find-more-themes = Find more themes

addon-options-button =
    .aria-label = More Options


report-addon-button = Report
remove-addon-button = Remove
remove-addon-disabled-button = Can’t Be Removed <a data-l10n-name="link">Why?</a>
disable-addon-button = Disable
enable-addon-button = Enable
extension-enable-addon-button-label =
    .aria-label = Enable
preferences-addon-button =
    { PLATFORM() ->
        [windows] Options
       *[other] Preferences
    }
details-addon-button = Details
release-notes-addon-button = Release Notes
permissions-addon-button = Permissions

extension-enabled-heading = Enabled
extension-disabled-heading = Disabled

theme-enabled-heading = Enabled
theme-disabled-heading2 = Saved Themes

plugin-enabled-heading = Enabled
plugin-disabled-heading = Disabled

dictionary-enabled-heading = Enabled
dictionary-disabled-heading = Disabled

locale-enabled-heading = Enabled
locale-disabled-heading = Disabled

sitepermission-enabled-heading = Enabled
sitepermission-disabled-heading = Disabled

always-activate-button = Always Activate
never-activate-button = Never Activate

addon-detail-author-label = Author
addon-detail-version-label = Version
addon-detail-last-updated-label = Last Updated
addon-detail-homepage-label = Homepage
addon-detail-rating-label = Rating

install-postponed-message2 =
    .message = This extension will be updated when { -brand-short-name } restarts.
install-postponed-button = Update Now

addon-name-disabled = { $name } (disabled)

addon-detail-reviews-link =
    { $numberOfReviews ->
        [one] { $numberOfReviews } review
       *[other] { $numberOfReviews } reviews
    }


pending-uninstall-description2 =
    .message = { $addon } has been removed.
pending-uninstall-undo-button = Undo

addon-detail-updates-label = Allow automatic updates
addon-detail-updates-radio-default = Default
addon-detail-updates-radio-on = On
addon-detail-updates-radio-off = Off
addon-detail-update-check-label = Check for Updates
install-update-button = Update
addon-detail-group-label-updates =
    .aria-label = { addon-detail-updates-label }

addon-badge-private-browsing-allowed2 =
    .title = Allowed in private windows
    .aria-label = { addon-badge-private-browsing-allowed2.title }
addon-detail-private-browsing-help = When allowed, the extension will have access to your online activities while private browsing. <a data-l10n-name="learn-more">Learn more</a>
addon-detail-private-browsing-allow = Allow
addon-detail-private-browsing-disallow = Don’t Allow
addon-detail-group-label-private-browsing =
    .aria-label = { detail-private-browsing-label }


addon-detail-quarantined-domains-label = Run on sites with restrictions
addon-detail-quarantined-domains-help = When allowed, the extension will have access to sites restricted by { -vendor-short-name }. Allow only if you trust this extension.
addon-detail-quarantined-domains-allow = Allow
addon-detail-quarantined-domains-disallow = Don’t Allow
addon-detail-group-label-quarantined-domains =
    .aria-label = { addon-detail-quarantined-domains-label }


addon-badge-recommended2 =
  .title = { -brand-product-name } only recommends extensions that meet our standards for security and performance
  .aria-label = { addon-badge-recommended2.title }
addon-badge-line3 =
  .title = Official extension built by Mozilla. Meets security and performance standards
  .aria-label = { addon-badge-line3.title }
addon-badge-verified2 =
  .title = This extension has been reviewed to meet our standards for security and performance
  .aria-label = { addon-badge-verified2.title }


available-updates-heading = Available Updates
recent-updates-heading = Recent Updates

release-notes-loading = Loading…
release-notes-error = Sorry, but there was an error loading the release notes.

addon-permissions-empty = This extension doesn’t require any permissions
addon-permissions-required = Required permissions for core functionality:
addon-permissions-optional = Optional permissions for added functionality:
addon-permissions-learnmore = Learn more about permissions

recommended-extensions-heading = Recommended Extensions
recommended-themes-heading = Recommended Themes

addon-sitepermissions-required = Grants the following capabilities to <span data-l10n-name="hostname">{ $hostname }</span>:

recommended-theme-1 = Feeling creative? <a data-l10n-name="link">Build your own theme with Firefox Color.</a>


extension-heading = Manage Your Extensions
theme-heading = Manage Your Themes
plugin-heading = Manage Your Plugins
dictionary-heading = Manage Your Dictionaries
locale-heading = Manage Your Languages
updates-heading = Manage Your Updates
sitepermission-heading = Manage Your Site Permissions
discover-heading = Personalize Your { -brand-short-name }
shortcuts-heading = Manage Extension Shortcuts

default-heading-search-label = Find more add-ons
addons-heading-search-input =
    .placeholder = Search addons.mozilla.org

addon-page-options-button =
    .title = Tools for all add-ons


details-notification-incompatible2 =
    .message = { $name } is incompatible with { -brand-short-name } { $version }.
details-notification-incompatible-link = More Information

details-notification-unsigned-and-disabled2 =
    .message = { $name } could not be verified for use in { -brand-short-name } and has been disabled.
details-notification-unsigned-and-disabled-link = More Information

details-notification-unsigned2 =
    .message = { $name } could not be verified for use in { -brand-short-name }. Proceed with caution.
details-notification-unsigned-link = More Information

details-notification-blocked2 =
    .message = { $name } has been disabled due to security or stability issues.
details-notification-blocked-link = More Information

details-notification-softblocked2 =
    .message = { $name } is known to cause security or stability issues.
details-notification-softblocked-link = More Information

details-notification-gmp-pending2 =
    .message = { $name } will be installed shortly.


plugins-gmp-license-info = License information
plugins-gmp-privacy-info = Privacy Information

plugins-openh264-name = OpenH264 Video Codec provided by Cisco Systems, Inc.
plugins-openh264-description = This plugin is automatically installed by Mozilla to comply with the WebRTC specification and to enable WebRTC calls with devices that require the H.264 video codec. Visit https://www.openh264.org/ to view the codec source code and learn more about the implementation.

plugins-widevine-name = Widevine Content Decryption Module provided by Google Inc.
plugins-widevine-description = This plugin enables playback of encrypted media in compliance with the Encrypted Media Extensions specification. Encrypted media is typically used by sites to protect against copying of premium media content. Visit https://www.w3.org/TR/encrypted-media/ for more information on Encrypted Media Extensions.
PK
!<��w��;chrome/toolkit/content/global/elements/moz-button-group.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:host {
  display: flex;
  justify-content: flex-end;
}

::slotted(button) {
  margin: 0 !important;
}

::slotted(button:not(:first-child, .popup-notification-dropmarker)),
::slotted(moz-button:not(:first-child)) {
  margin-inline-start: var(--space-small) !important;
}
PK
!<�\҃

;chrome/toolkit/skin/classic/global/icons/pocket-outline.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m13 3.25.75.75 0 3.498c0 .801-.148 1.527-.466 2.248-.28.68-.68 1.285-1.222 1.85-.5.521-1.102.929-1.789 1.212a5.899 5.899 0 0 1-2.248.466 5.424 5.424 0 0 1-2.248-.466 5.836 5.836 0 0 1-1.832-1.204 5.967 5.967 0 0 1-1.23-1.832 5.92 5.92 0 0 1-.465-2.248L2.25 4 3 3.25l10 0M13 2 3 2a2 2 0 0 0-2 2l0 3.524c0 .942.204 1.858.56 2.724.382.865.865 1.604 1.502 2.24a7.14 7.14 0 0 0 2.24 1.476c.865.382 1.756.56 2.724.56a7.15 7.15 0 0 0 2.724-.56 6.433 6.433 0 0 0 2.215-1.502 7.14 7.14 0 0 0 1.476-2.24A6.623 6.623 0 0 0 15 7.498L15 4a2 2 0 0 0-2-2z"/>
  <path d="M7.66 10 4.182 6.523a.626.626 0 0 1 .885-.885L8 8.572l2.933-2.934a.626.626 0 0 1 .885.885L8.34 10l-.68 0z"/>
</svg>
PK
!<�%�ث�<chrome/toolkit/skin/classic/mozapps/extensions/extension.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m13.375 16-9.75 0A1.626 1.626 0 0 1 2 14.375L2 11.5a.75.75 0 0 1 .75-.75l1.75 0c.689 0 1.25-.561 1.25-1.25S5.189 8.25 4.5 8.25l-1.75 0A.75.75 0 0 1 2 7.5l0-1.875C2 4.728 2.728 4 3.625 4L6 4l0-1.352C6 1.341 6.938.147 8.238.014A2.502 2.502 0 0 1 11 2.5L11 4l2.375 0C14.272 4 15 4.728 15 5.625l0 8.75c0 .897-.728 1.625-1.625 1.625zM3.25 12l0 2.15.6.6 9.3 0 .6-.6 0-8.3-.6-.6-2.65 0a.75.75 0 0 1-.75-.75l0-2c0-.689-.561-1.25-1.25-1.25s-1.25.561-1.25 1.25l0 2a.75.75 0 0 1-.75.75l-2.75 0-.5.6 0 1.15 1.103 0c1.308 0 2.502.939 2.634 2.24A2.503 2.503 0 0 1 4.5 12l-1.25 0z"/>
</svg>
PK
!<�#NGG$chrome/toolkit/res/hiddenWindow.html<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title></title>
  </head>
  <body></body>
</html>
PK
!<��TT1modules/UrlClassifierExceptionListService.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export function UrlClassifierExceptionListService() {}

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
});

const COLLECTION_NAME = "url-classifier-skip-urls";

class Feature {
  constructor(name, prefName) {
    this.name = name;
    this.prefName = prefName;
    this.observers = new Set();
    this.prefValue = null;
    this.remoteEntries = null;

    if (prefName) {
      this.prefValue = Services.prefs.getStringPref(this.prefName, null);
      Services.prefs.addObserver(prefName, this);
    }
  }

  async addAndRunObserver(observer) {
    this.observers.add(observer);
    this.notifyObservers(observer);
  }

  removeObserver(observer) {
    this.observers.delete(observer);
  }

  observe(subject, topic, data) {
    if (topic != "nsPref:changed" || data != this.prefName) {
      console.error(`Unexpected event ${topic} with ${data}`);
      return;
    }

    this.prefValue = Services.prefs.getStringPref(this.prefName, null);
    this.notifyObservers();
  }

  onRemoteSettingsUpdate(entries) {
    this.remoteEntries = [];

    for (let entry of entries) {
      if (entry.feature == this.name) {
        this.remoteEntries.push(entry.pattern.toLowerCase());
      }
    }
  }

  notifyObservers(observer = null) {
    let entries = [];
    if (this.prefValue) {
      entries = this.prefValue.split(",");
    }

    if (this.remoteEntries) {
      for (let entry of this.remoteEntries) {
        entries.push(entry);
      }
    }

    let entriesAsString = entries.join(",").toLowerCase();
    if (observer) {
      observer.onExceptionListUpdate(entriesAsString);
    } else {
      for (let obs of this.observers) {
        obs.onExceptionListUpdate(entriesAsString);
      }
    }
  }
}

UrlClassifierExceptionListService.prototype = {
  classID: Components.ID("{b9f4fd03-9d87-4bfd-9958-85a821750ddc}"),
  QueryInterface: ChromeUtils.generateQI([
    "nsIUrlClassifierExceptionListService",
  ]),

  features: {},
  _initialized: false,

  async lazyInit() {
    if (this._initialized) {
      return;
    }

    let rs = lazy.RemoteSettings(COLLECTION_NAME);
    rs.on("sync", event => {
      let {
        data: { current },
      } = event;
      this.entries = current || [];
      this.onUpdateEntries(current);
    });

    this._initialized = true;

    // If the remote settings list hasn't been populated yet we have to make sure
    // to do it before firing the first notification.
    // This has to be run after _initialized is set because we'll be
    // blocked while getting entries from RemoteSetting, and we don't want
    // LazyInit is executed again.
    try {
      // The data will be initially available from the local DB (via a
      // resource:// URI).
      this.entries = await rs.get();
    } catch (e) {}

    // RemoteSettings.get() could return null, ensure passing a list to
    // onUpdateEntries.
    if (!this.entries) {
      this.entries = [];
    }

    this.onUpdateEntries(this.entries);
  },

  onUpdateEntries(entries) {
    for (let key of Object.keys(this.features)) {
      let feature = this.features[key];
      feature.onRemoteSettingsUpdate(entries);
      feature.notifyObservers();
    }
  },

  registerAndRunExceptionListObserver(feature, prefName, observer) {
    // We don't await this; the caller is C++ and won't await this function,
    // and because we prevent re-entering into this method, once it's been
    // called once any subsequent calls will early-return anyway - so
    // awaiting that would be meaningless. Instead, `Feature` implementations
    // make sure not to call into observers until they have data, and we
    // make sure to let feature instances know whether we have data
    // immediately.
    this.lazyInit();

    if (!this.features[feature]) {
      let featureObj = new Feature(feature, prefName);
      this.features[feature] = featureObj;
      // If we've previously initialized, we need to pass the entries
      // we already have to the new feature.
      if (this.entries) {
        featureObj.onRemoteSettingsUpdate(this.entries);
      }
    }
    this.features[feature].addAndRunObserver(observer);
  },

  unregisterExceptionListObserver(feature, observer) {
    if (!this.features[feature]) {
      return;
    }
    this.features[feature].removeObserver(observer);
  },

  clear() {
    this.features = {};
    this._initialized = false;
    this.entries = null;
  },
};
PK
!<�(�x��0chrome/en-US/locale/en-US/global/keys.properties

VK_F1=F1
VK_F2=F2
VK_F3=F3
VK_F4=F4
VK_F5=F5
VK_F6=F6
VK_F7=F7
VK_F8=F8
VK_F9=F9
VK_F10=F10

VK_F11=F11
VK_F12=F12
VK_F13=F13
VK_F14=F14
VK_F15=F15
VK_F16=F16
VK_F17=F17
VK_F18=F18
VK_F19=F19
VK_F20=F20

VK_UP=Up Arrow
VK_DOWN=Down Arrow
VK_LEFT=Left Arrow
VK_RIGHT=Right Arrow
VK_PAGE_UP=Page Up
VK_PAGE_DOWN=Page Down


VK_TAB=Tab
VK_BACK=Backspace
VK_DELETE=Del
VK_HOME=Home
VK_END=End

VK_ESCAPE=Esc
VK_INSERT=Ins
PK
!<W���4chrome/toolkit/featuregates/feature_definitions.json{"sidebar": {"group": "experimental-features-group-customize-browsing", "title": "sidebar-title", "description": "sidebar-description", "preference": "sidebar.revamp", "type": "boolean", "id": "sidebar", "descriptionLinks": {"connect": "https://connect.mozilla.org/t5/discussions/firefox-sidebar-and-vertical-tabs-try-them-out-in-nightly/m-p/63231#M22243"}, "restartRequired": false, "bugNumbers": [1906140], "isPublicJexl": "nightly_build", "defaultValueJexl": "false"}, "vertical-tabs": {"group": "experimental-features-group-customize-browsing", "title": "vertical-tabs-title", "description": "vertical-tabs-description", "preference": "sidebar.verticalTabs", "type": "boolean", "id": "vertical-tabs", "descriptionLinks": {"connect": "https://connect.mozilla.org/t5/discussions/firefox-sidebar-and-vertical-tabs-try-them-out-in-nightly/m-p/63231#M22243"}, "restartRequired": false, "bugNumbers": [1906140], "isPublicJexl": "nightly_build", "defaultValueJexl": "false"}, "genai-chat": {"group": "experimental-features-group-customize-browsing", "title": "genai-settings-chat-title", "description": "genai-settings-chat-description", "preference": "browser.ml.chat.enabled", "type": "boolean", "id": "genai-chat", "descriptionLinks": {"connect": "https://connect.mozilla.org/t5/discussions/share-your-feedback-on-the-ai-services-experiment-in-nightly/m-p/60519#M21202"}, "restartRequired": false, "bugNumbers": [1894999], "isPublicJexl": "!'browser.ml.chat.hideFromLabs'|preferenceValue", "defaultValueJexl": "false"}, "auto-pip": {"group": "experimental-features-group-customize-browsing", "title": "experimental-features-auto-pip", "description": "experimental-features-auto-pip-description", "preference": "media.videocontrols.picture-in-picture.enable-when-switching-tabs.enabled", "type": "boolean", "id": "auto-pip", "restartRequired": false, "bugNumbers": [1647800], "isPublicJexl": "true", "defaultValueJexl": "false"}, "url-bar-ime-search": {"group": "experimental-features-group-customize-browsing", "title": "experimental-features-ime-search", "description": "experimental-features-ime-search-description", "preference": "browser.urlbar.keepPanelOpenDuringImeComposition", "type": "boolean", "id": "url-bar-ime-search", "restartRequired": false, "bugNumbers": [1673971], "isPublicJexl": "true", "defaultValueJexl": "false"}, "webrtc-global-mute-toggles": {"group": "experimental-features-group-customize-browsing", "title": "experimental-features-webrtc-global-mute-toggles", "description": "experimental-features-webrtc-global-mute-toggles-description", "preference": "privacy.webrtc.globalMuteToggles", "type": "boolean", "id": "webrtc-global-mute-toggles", "restartRequired": false, "bugNumbers": [1643027], "isPublicJexl": "nightly_build", "defaultValueJexl": "false"}, "media-jxl": {"group": "experimental-features-group-webpage-display", "title": "experimental-features-media-jxl", "description": "experimental-features-media-jxl-description", "preference": "image.jxl.enabled", "type": "boolean", "id": "media-jxl", "descriptionLinks": {"bugzilla": "https://bugzilla.mozilla.org/show_bug.cgi?id=1539075"}, "restartRequired": false, "bugNumbers": [1539075], "isPublicJexl": "nightly_build", "defaultValueJexl": "false"}, "css-masonry": {"group": "experimental-features-group-webpage-display", "title": "experimental-features-css-masonry2", "description": "experimental-features-css-masonry-description", "preference": "layout.css.grid-template-masonry-value.enabled", "type": "boolean", "id": "css-masonry", "descriptionLinks": {"explainer": "https://github.com/w3c/csswg-drafts/blob/master/css-grid-2/MASONRY-EXPLAINER.md", "w3c-issue": "https://github.com/w3c/csswg-drafts/issues/4650", "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1607439"}, "restartRequired": false, "bugNumbers": [1607954], "isPublicJexl": "nightly_build", "defaultValueJexl": "nightly_build || thunderbird"}, "web-api-webgpu": {"group": "experimental-features-group-webpage-display", "title": "experimental-features-web-gpu2", "description": "experimental-features-web-gpu-description3", "preference": "dom.webgpu.enabled", "type": "boolean", "id": "web-api-webgpu", "descriptionLinks": {"wikipedia-webgpu": "https://en.wikipedia.org/wiki/WebGPU", "wikipedia-gpu": "https://en.wikipedia.org/wiki/Graphics_processing_unit", "spec": "https://gpuweb.github.io/gpuweb/", "bugzilla": "https://bugzilla.mozilla.org/show_bug.cgi?id=1616739"}, "restartRequired": false, "bugNumbers": [1616739], "isPublicJexl": "nightly_build", "defaultValueJexl": "nightly_build"}, "devtools-serviceworker-debugger-support": {"group": "experimental-features-group-developer-tools", "title": "experimental-features-devtools-serviceworker-debugger-support", "description": "experimental-features-devtools-serviceworker-debugger-support-description", "preference": "devtools.debugger.features.windowless-service-workers", "type": "boolean", "id": "devtools-serviceworker-debugger-support", "restartRequired": true, "bugNumbers": [1651607], "isPublicJexl": "true", "defaultValueJexl": "false"}}PK
!<#�g��:chrome/toolkit/content/global/elements/moz-message-bar.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { html, ifDefined } from "../vendor/lit.all.mjs";
import { MozLitElement } from "../lit-utils.mjs";
// eslint-disable-next-line import/no-unassigned-import
import "chrome://global/content/elements/moz-button.mjs";

window.MozXULElement?.insertFTLIfNeeded("toolkit/global/mozMessageBar.ftl");

const messageTypeToIconData = {
  info: {
    iconSrc: "chrome://global/skin/icons/info-filled.svg",
    l10nId: "moz-message-bar-icon-info",
  },
  warning: {
    iconSrc: "chrome://global/skin/icons/warning.svg",
    l10nId: "moz-message-bar-icon-warning",
  },
  success: {
    iconSrc: "chrome://global/skin/icons/check-filled.svg",
    l10nId: "moz-message-bar-icon-success",
  },
  error: {
    iconSrc: "chrome://global/skin/icons/error.svg",
    l10nId: "moz-message-bar-icon-error",
  },
  critical: {
    iconSrc: "chrome://global/skin/icons/error.svg",
    l10nId: "moz-message-bar-icon-error",
  },
};

/**
 * A simple message bar element that can be used to display
 * important information to users.
 *
 * @tagname moz-message-bar
 * @property {string} type - The type of the displayed message.
 * @property {string} heading - The heading of the message.
 * @property {string} message - The message text.
 * @property {boolean} dismissable - Whether or not the element is dismissable.
 * @property {string} messageL10nId - l10n ID for the message.
 * @property {string} messageL10nArgs - Any args needed for the message l10n ID.
 * @fires message-bar:close
 *  Custom event indicating that message bar was closed.
 * @fires message-bar:user-dismissed
 *  Custom event indicating that message bar was dismissed by the user.
 */

export default class MozMessageBar extends MozLitElement {
  static queries = {
    actionsSlot: "slot[name=actions]",
    actionsEl: ".actions",
    closeButton: "moz-button.close",
    supportLinkSlot: "slot[name=support-link]",
  };

  static properties = {
    type: { type: String },
    heading: { type: String },
    message: { type: String },
    dismissable: { type: Boolean },
    messageL10nId: { type: String },
    messageL10nArgs: { type: String },
  };

  constructor() {
    super();
    this.type = "info";
    this.dismissable = false;
  }

  onSlotchange() {
    let actions = this.actionsSlot.assignedNodes();
    this.actionsEl.classList.toggle("active", actions.length);
  }

  connectedCallback() {
    super.connectedCallback();
    this.setAttribute("role", "alert");
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    this.dispatchEvent(new CustomEvent("message-bar:close"));
  }

  get supportLinkEls() {
    return this.supportLinkSlot.assignedElements();
  }

  iconTemplate() {
    let iconData = messageTypeToIconData[this.type];
    if (iconData) {
      let { iconSrc, l10nId } = iconData;
      return html`
        <div class="icon-container">
          <img
            class="icon"
            src=${iconSrc}
            data-l10n-id=${l10nId}
            data-l10n-attrs="alt"
          />
        </div>
      `;
    }
    return "";
  }

  headingTemplate() {
    if (this.heading) {
      return html`<strong class="heading">${this.heading}</strong>`;
    }
    return "";
  }

  closeButtonTemplate({ size } = {}) {
    if (this.dismissable) {
      return html`
        <moz-button
          type="icon ghost"
          class="close"
          size=${ifDefined(size)}
          data-l10n-id="moz-message-bar-close-button"
          @click=${this.dismiss}
        ></moz-button>
      `;
    }
    return "";
  }

  render() {
    return html`
      <link
        rel="stylesheet"
        href="chrome://global/content/elements/moz-message-bar.css"
      />
      <div class="container">
        <div class="content">
          <div class="text-container">
            ${this.iconTemplate()}
            <div class="text-content">
              ${this.headingTemplate()}
              <div>
                <span
                  class="message"
                  data-l10n-id=${ifDefined(this.messageL10nId)}
                  data-l10n-args=${ifDefined(
                    JSON.stringify(this.messageL10nArgs)
                  )}
                >
                  ${this.message}
                </span>
                <span class="link">
                  <slot name="support-link"></slot>
                </span>
              </div>
            </div>
          </div>
          <span class="actions">
            <slot name="actions" @slotchange=${this.onSlotchange}></slot>
          </span>
        </div>
        ${this.closeButtonTemplate()}
      </div>
    `;
  }

  dismiss() {
    this.dispatchEvent(new CustomEvent("message-bar:user-dismissed"));
    this.close();
  }

  close() {
    this.remove();
  }
}

customElements.define("moz-message-bar", MozMessageBar);
PK
!<���8YY5chrome/toolkit/content/global/elements/moz-button.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { html, ifDefined, classMap } from "../vendor/lit.all.mjs";
import { MozLitElement } from "../lit-utils.mjs";

// eslint-disable-next-line import/no-unassigned-import
import "chrome://global/content/elements/moz-label.mjs";

/**
 * A button with multiple types and two sizes.
 *
 * @tagname moz-button
 * @property {string} label - The button's label, will be overridden by slotted content.
 * @property {string} type - The button type.
 *   Options: default, primary, destructive, icon, icon ghost, ghost.
 * @property {string} size - The button size.
 *   Options: default, small.
 * @property {boolean} disabled - The disabled state.
 * @property {string} title - The button's title attribute, used in shadow DOM and therefore not as an attribute on moz-button.
 * @property {string} titleAttribute - Internal, map title attribute to the title JS property.
 * @property {string} tooltipText - Set the title property, the title attribute will be used first.
 * @property {string} ariaLabel - The button's arial-label attribute, used in shadow DOM and therefore not as an attribute on moz-button.
 * @property {string} iconSrc - Path to the icon that should be displayed in the button.
 * @property {string} ariaLabelAttribute - Internal, map aria-label attribute to the ariaLabel JS property.
 * @property {string} hasVisibleLabel - Internal, tracks whether or not the button has a visible label.
 * @property {HTMLButtonElement} buttonEl - The internal button element in the shadow DOM.
 * @property {HTMLButtonElement} slotEl - The internal slot element in the shadow DOM.
 * @slot default - The button's content, overrides label property.
 * @fires click - The click event.
 */
export default class MozButton extends MozLitElement {
  static shadowRootOptions = {
    ...MozLitElement.shadowRootOptions,
    delegatesFocus: true,
  };

  static properties = {
    label: { type: String, reflect: true, fluent: true },
    type: { type: String, reflect: true },
    size: { type: String, reflect: true },
    disabled: { type: Boolean, reflect: true },
    title: { type: String, state: true },
    titleAttribute: { type: String, attribute: "title", reflect: true },
    tooltipText: { type: String, fluent: true },
    ariaLabelAttribute: {
      type: String,
      attribute: "aria-label",
      reflect: true,
    },
    ariaLabel: { type: String, state: true },
    iconSrc: { type: String },
    hasVisibleLabel: { type: Boolean, state: true },
    accessKeyAttribute: { type: String, attribute: "accesskey", reflect: true },
    accessKey: { type: String, state: true },
  };

  static queries = {
    buttonEl: "button",
    slotEl: "slot",
  };

  constructor() {
    super();
    this.type = "default";
    this.size = "default";
    this.disabled = false;
    this.hasVisibleLabel = !!this.label;
  }

  willUpdate(changes) {
    if (changes.has("titleAttribute")) {
      this.title = this.titleAttribute;
      this.titleAttribute = null;
    }
    if (changes.has("ariaLabelAttribute")) {
      this.ariaLabel = this.ariaLabelAttribute;
      this.ariaLabelAttribute = null;
    }
    if (changes.has("accessKeyAttribute")) {
      this.accessKey = this.accessKeyAttribute;
      this.accessKeyAttribute = null;
    }
  }

  // Delegate clicks on host to the button element.
  click() {
    this.buttonEl.click();
  }

  checkForLabelText() {
    this.hasVisibleLabel = this.slotEl
      ?.assignedNodes()
      .some(node => node.textContent.trim());
  }

  labelTemplate() {
    if (this.label) {
      return this.label;
    }
    return html`<slot @slotchange=${this.checkForLabelText}></slot>`;
  }

  render() {
    return html`
      <link
        rel="stylesheet"
        href="chrome://global/content/elements/moz-button.css"
      />
      <button
        type=${this.type}
        size=${this.size}
        ?disabled=${this.disabled}
        title=${ifDefined(this.title || this.tooltipText)}
        aria-label=${ifDefined(this.ariaLabel)}
        part="button"
        class=${classMap({ labelled: this.label || this.hasVisibleLabel })}
        accesskey=${ifDefined(this.accessKey)}
      >
        ${this.iconSrc
          ? html`<img src=${this.iconSrc} role="presentation" />`
          : ""}
        <label
          is="moz-label"
          part="label"
          shownaccesskey=${ifDefined(this.accessKey)}
        >
          ${this.labelTemplate()}
        </label>
      </button>
    `;
  }
}
customElements.define("moz-button", MozButton);
PK
!<�0�$�$4chrome/toolkit/content/global/elements/moz-label.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * An extension of the label element that provides accesskey styling and
 * formatting as well as click handling logic.
 *
 * @tagname moz-label
 * @attribute {string} accesskey - Key used for keyboard access.
 * @attribute {string} shownaccesskey - Key to underline but not set as
 *   accesskey, this is useful to work around an issue where multiple accesskeys
 *   on the same element cause it to be focused isntead of activated.
 */
class MozTextLabel extends HTMLLabelElement {
  #insertSeparator = false;
  #alwaysAppendAccessKey = false;
  #lastFormattedAccessKey = null;
  #observer = null;

  // Default to underlining accesskeys for Windows and Linux.
  static #underlineAccesskey = !navigator.platform.includes("Mac");
  static get observedAttributes() {
    return ["accesskey", "shownaccesskey"];
  }

  static stylesheetUrl = "chrome://global/content/elements/moz-label.css";

  constructor() {
    super();
    this.#register();
    this.addEventListener("click", this._onClick);
  }

  #register() {
    if (window.IS_STORYBOOK) {
      MozTextLabel.#underlineAccesskey = true;
    } else if (typeof Services !== "undefined") {
      MozTextLabel.#underlineAccesskey = !!Services.prefs.getIntPref(
        "ui.key.menuAccessKey",
        Number(!navigator.platform.includes("Mac"))
      );
      if (MozTextLabel.#underlineAccesskey) {
        try {
          const nsIPrefLocalizedString = Ci.nsIPrefLocalizedString;
          const prefNameInsertSeparator =
            "intl.menuitems.insertseparatorbeforeaccesskeys";
          const prefNameAlwaysAppendAccessKey =
            "intl.menuitems.alwaysappendaccesskeys";

          let val = Services.prefs.getComplexValue(
            prefNameInsertSeparator,
            nsIPrefLocalizedString
          ).data;
          this.#insertSeparator = val == "true";
          val = Services.prefs.getComplexValue(
            prefNameAlwaysAppendAccessKey,
            nsIPrefLocalizedString
          ).data;
          this.#alwaysAppendAccessKey = val == "true";
        } catch (e) {
          this.#insertSeparator = this.#alwaysAppendAccessKey = true;
        }
      }
    }
  }

  connectedCallback() {
    this.#setStyles();
    this.formatAccessKey();
    if (!this.#observer) {
      this.#observer = new MutationObserver(() => {
        this.formatAccessKey();
      }).observe(this, { characterData: true, childList: true, subtree: true });
    }
  }

  disconnectedCallback() {
    if (this.#observer) {
      this.#observer.disconnect();
      this.#observer = null;
    }
  }

  // Bug 1820588 - we may want to generalize this into
  // MozHTMLElement.insertCssIfNeeded(style)
  #setStyles() {
    let root = this.getRootNode();
    let container = root.head ?? root;

    for (let link of container.querySelectorAll("link")) {
      if (link.getAttribute("href") == this.constructor.stylesheetUrl) {
        return;
      }
    }

    let style = document.createElement("link");
    style.rel = "stylesheet";
    style.href = this.constructor.stylesheetUrl;
    container.appendChild(style);
  }

  set textContent(val) {
    super.textContent = val;
    this.#lastFormattedAccessKey = null;
    this.formatAccessKey();
  }

  get textContent() {
    return super.textContent;
  }

  attributeChangedCallback(attrName, oldValue, newValue) {
    if (oldValue == newValue) {
      return;
    }

    // Note that this is only happening when "accesskey" attribute changes.
    this.formatAccessKey();
  }

  _onClick() {
    let controlElement = this.labeledControlElement;
    if (!controlElement || this.disabled) {
      return;
    }
    controlElement.focus();

    if (
      (controlElement.localName == "checkbox" ||
        controlElement.localName == "radio") &&
      controlElement.getAttribute("disabled") == "true"
    ) {
      return;
    }

    if (controlElement.localName == "checkbox") {
      controlElement.checked = !controlElement.checked;
    } else if (controlElement.localName == "radio") {
      controlElement.control.selectedItem = controlElement;
    }
  }

  set accessKey(val) {
    this.setAttribute("accesskey", val);
    let control = this.labeledControlElement;
    if (control) {
      control.setAttribute("accesskey", val);
    }
  }

  get accessKey() {
    let accessKey = this.getAttribute("accesskey");
    return accessKey ? accessKey[0] : null;
  }

  get labeledControlElement() {
    let control = this.control;
    return control ? document.getElementById(control) : null;
  }

  set control(val) {
    this.setAttribute("control", val);
  }

  get control() {
    return this.getAttribute("control");
  }

  // This is used to match the rendering of accesskeys from nsTextBoxFrame.cpp (i.e. when the
  // label uses [value]). So this is just for when we have textContent.
  formatAccessKey() {
    // Skip doing any DOM manipulation whenever possible:
    let accessKey = this.accessKey || this.getAttribute("shownaccesskey");
    if (
      !MozTextLabel.#underlineAccesskey ||
      this.#lastFormattedAccessKey == accessKey ||
      !this.textContent ||
      !this.textContent.trim()
    ) {
      return;
    }
    this.#lastFormattedAccessKey = accessKey;
    if (this.accessKeySpan) {
      // Clear old accesskey
      mergeElement(this.accessKeySpan);
      this.accessKeySpan = null;
    }

    if (this.hiddenColon) {
      mergeElement(this.hiddenColon);
      this.hiddenColon = null;
    }

    if (this.accessKeyParens) {
      this.accessKeyParens.remove();
      this.accessKeyParens = null;
    }

    // If we used to have an accessKey but not anymore, we're done here
    if (!accessKey) {
      return;
    }

    let labelText = this.textContent;
    let accessKeyIndex = -1;
    if (!this.#alwaysAppendAccessKey) {
      accessKeyIndex = labelText.indexOf(accessKey);
      if (accessKeyIndex < 0) {
        // Try again in upper case
        accessKeyIndex = labelText
          .toUpperCase()
          .indexOf(accessKey.toUpperCase());
      }
    } else if (labelText.endsWith(`(${accessKey.toUpperCase()})`)) {
      accessKeyIndex = labelText.length - (1 + accessKey.length); // = index of accessKey.
    }

    const HTML_NS = "http://www.w3.org/1999/xhtml";
    this.accessKeySpan = document.createElementNS(HTML_NS, "span");
    this.accessKeySpan.className = "accesskey";

    // Note that if you change the following code, see the comment of
    // nsTextBoxFrame::UpdateAccessTitle.

    // If accesskey is in the string, underline it:
    if (accessKeyIndex >= 0) {
      wrapChar(this, this.accessKeySpan, accessKeyIndex);
      return;
    }

    // If accesskey is not in string, append in parentheses
    // If end is colon, we should insert before colon.
    // i.e., "label:" -> "label(X):"
    let colonHidden = false;
    if (/:$/.test(labelText)) {
      labelText = labelText.slice(0, -1);
      this.hiddenColon = document.createElementNS(HTML_NS, "span");
      this.hiddenColon.className = "hiddenColon";
      this.hiddenColon.style.display = "none";
      // Hide the last colon by using span element.
      // I.e., label<span style="display:none;">:</span>
      wrapChar(this, this.hiddenColon, labelText.length);
      colonHidden = true;
    }
    // If end is space(U+20),
    // we should not add space before parentheses.
    let endIsSpace = false;
    if (/ $/.test(labelText)) {
      endIsSpace = true;
    }

    this.accessKeyParens = document.createElementNS(
      "http://www.w3.org/1999/xhtml",
      "span"
    );
    this.appendChild(this.accessKeyParens);
    if (this.#insertSeparator && !endIsSpace) {
      this.accessKeyParens.textContent = " (";
    } else {
      this.accessKeyParens.textContent = "(";
    }
    this.accessKeySpan.textContent = accessKey.toUpperCase();
    this.accessKeyParens.appendChild(this.accessKeySpan);
    if (!colonHidden) {
      this.accessKeyParens.appendChild(document.createTextNode(")"));
    } else {
      this.accessKeyParens.appendChild(document.createTextNode("):"));
    }
  }
}
customElements.define("moz-label", MozTextLabel, { extends: "label" });

function mergeElement(element) {
  // If the element has been removed already, return:
  if (!element.isConnected) {
    return;
  }
  // `isInstance` isn't available to web content (i.e. Storybook) so we need to
  // fallback to using `instanceof`.
  if (
    Text.hasOwnProperty("isInstance")
      ? Text.isInstance(element.previousSibling)
      : // eslint-disable-next-line mozilla/use-isInstance
        element.previousSibling instanceof Text
  ) {
    element.previousSibling.appendData(element.textContent);
  } else {
    element.parentNode.insertBefore(element.firstChild, element);
  }
  element.remove();
}

function wrapChar(parentNode, element, index) {
  let treeWalker = document.createNodeIterator(
    parentNode,
    NodeFilter.SHOW_TEXT,
    null
  );
  let node = treeWalker.nextNode();
  while (index >= node.length) {
    index -= node.length;
    node = treeWalker.nextNode();
  }
  if (index) {
    node = node.splitText(index);
  }

  node.parentNode.insertBefore(element, node);
  if (node.length > 1) {
    node.splitText(1);
  }
  element.appendChild(node);
}
PK
!<���.�.4chrome/toolkit/content/global/elements/named-deck.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
  /**
   * This element is for use with the <named-deck> element. Set the target
   * <named-deck>'s ID in the "deck" attribute and the button's selected state
   * will reflect the deck's state. When the button is clicked, it will set the
   * view in the <named-deck> to the button's "name" attribute.
   *
   * The "tab" role will be added unless a different role is provided. Wrapping
   * a set of these buttons in a <button-group> element will add the key handling
   * for a tablist.
   *
   * NOTE: This does not observe changes to the "deck" or "name" attributes, so
   * changing them likely won't work properly.
   *
   * <button is="named-deck-button" deck="pet-deck" name="dogs">Dogs</button>
   * <named-deck id="pet-deck">
   *   <p name="cats">I like cats.</p>
   *   <p name="dogs">I like dogs.</p>
   * </named-deck>
   *
   * let btn = document.querySelector('button[name="dogs"]');
   * let deck = document.querySelector("named-deck");
   * deck.selectedViewName == "cats";
   * btn.selected == false; // Selected was pulled from the related deck.
   * btn.click();
   * deck.selectedViewName == "dogs";
   * btn.selected == true; // Selected updated when view changed.
   */
  class NamedDeckButton extends HTMLButtonElement {
    connectedCallback() {
      this.id = `${this.deckId}-button-${this.name}`;
      if (!this.hasAttribute("role")) {
        this.setAttribute("role", "tab");
      }
      this.setSelectedFromDeck();
      this.addEventListener("click", this);
      this.getRootNode().addEventListener("view-changed", this, {
        capture: true,
      });
    }

    disconnectedCallback() {
      this.removeEventListener("click", this);
      this.getRootNode().removeEventListener("view-changed", this, {
        capture: true,
      });
    }

    attributeChangedCallback(name, oldVal, newVal) {
      if (name == "selected") {
        this.selected = newVal;
      }
    }

    get deckId() {
      return this.getAttribute("deck");
    }

    set deckId(val) {
      this.setAttribute("deck", val);
    }

    get deck() {
      return this.getRootNode().querySelector(`#${this.deckId}`);
    }

    handleEvent(e) {
      if (e.type == "view-changed" && e.target.id == this.deckId) {
        this.setSelectedFromDeck();
      } else if (e.type == "click") {
        let { deck } = this;
        if (deck) {
          deck.selectedViewName = this.name;
        }
      }
    }

    get name() {
      return this.getAttribute("name");
    }

    get selected() {
      return this.hasAttribute("selected");
    }

    set selected(val) {
      if (this.selected != val) {
        this.toggleAttribute("selected", val);
      }
      this.setAttribute("aria-selected", !!val);
    }

    setSelectedFromDeck() {
      let { deck } = this;
      this.selected = deck && deck.selectedViewName == this.name;
      if (this.selected) {
        this.dispatchEvent(
          new CustomEvent("button-group:selected", { bubbles: true })
        );
      }
    }
  }
  customElements.define("named-deck-button", NamedDeckButton, {
    extends: "button",
  });

  class ButtonGroup extends HTMLElement {
    static get observedAttributes() {
      return ["orientation"];
    }

    connectedCallback() {
      this.setAttribute("role", "tablist");

      if (!this.observer) {
        this.observer = new MutationObserver(changes => {
          for (let change of changes) {
            this.setChildAttributes(change.addedNodes);
            for (let node of change.removedNodes) {
              if (this.activeChild == node) {
                // Ensure there's still an active child.
                this.activeChild = this.firstElementChild;
              }
            }
          }
        });
      }
      this.observer.observe(this, { childList: true });

      // Set the role and tabindex for the current children.
      this.setChildAttributes(this.children);

      // Try assigning the active child again, this will run through the checks
      // to ensure it's still valid.
      this.activeChild = this._activeChild;

      this.addEventListener("button-group:selected", this);
      this.addEventListener("keydown", this);
      this.addEventListener("mousedown", this);
      this.getRootNode().addEventListener("keypress", this);
    }

    disconnectedCallback() {
      this.observer.disconnect();
      this.removeEventListener("button-group:selected", this);
      this.removeEventListener("keydown", this);
      this.removeEventListener("mousedown", this);
      this.getRootNode().removeEventListener("keypress", this);
    }

    attributeChangedCallback(name) {
      if (name == "orientation") {
        if (this.isVertical) {
          this.setAttribute("aria-orientation", this.orientation);
        } else {
          this.removeAttribute("aria-orientation");
        }
      }
    }

    setChildAttributes(nodes) {
      for (let node of nodes) {
        if (node.nodeType == Node.ELEMENT_NODE && node != this.activeChild) {
          node.setAttribute("tabindex", "-1");
        }
      }
    }

    // The activeChild is the child that can be focused with tab.
    get activeChild() {
      return this._activeChild;
    }

    set activeChild(node) {
      let prevActiveChild = this._activeChild;
      let newActiveChild;

      if (node && this.contains(node)) {
        newActiveChild = node;
      } else {
        newActiveChild = this.firstElementChild;
      }

      this._activeChild = newActiveChild;

      if (newActiveChild) {
        newActiveChild.setAttribute("tabindex", "0");
      }

      if (prevActiveChild && prevActiveChild != newActiveChild) {
        prevActiveChild.setAttribute("tabindex", "-1");
      }
    }

    get isVertical() {
      return this.orientation == "vertical";
    }

    get orientation() {
      return this.getAttribute("orientation") == "vertical"
        ? "vertical"
        : "horizontal";
    }

    set orientation(val) {
      if (val == "vertical") {
        this.setAttribute("orientation", val);
      } else {
        this.removeAttribute("orientation");
      }
    }

    _navigationKeys() {
      if (this.isVertical) {
        return {
          previousKey: "ArrowUp",
          nextKey: "ArrowDown",
        };
      }
      if (document.dir == "rtl") {
        return {
          previousKey: "ArrowRight",
          nextKey: "ArrowLeft",
        };
      }
      return {
        previousKey: "ArrowLeft",
        nextKey: "ArrowRight",
      };
    }

    handleEvent(e) {
      let { previousKey, nextKey } = this._navigationKeys();
      if (e.type == "keydown" && (e.key == previousKey || e.key == nextKey)) {
        this.setAttribute("last-input-type", "keyboard");
        e.preventDefault();
        let oldFocus = this.activeChild;
        this.walker.currentNode = oldFocus;
        let newFocus;
        if (e.key == previousKey) {
          newFocus = this.walker.previousNode();
        } else {
          newFocus = this.walker.nextNode();
        }
        if (newFocus) {
          this.activeChild = newFocus;
          this.dispatchEvent(new CustomEvent("button-group:key-selected"));
        }
      } else if (e.type == "button-group:selected") {
        this.activeChild = e.target;
      } else if (e.type == "mousedown") {
        this.setAttribute("last-input-type", "mouse");
      } else if (e.type == "keypress" && e.key == "Tab") {
        this.setAttribute("last-input-type", "keyboard");
      }
    }

    get walker() {
      if (!this._walker) {
        this._walker = document.createTreeWalker(
          this,
          NodeFilter.SHOW_ELEMENT,
          {
            acceptNode: node => {
              if (node.hidden || node.disabled) {
                return NodeFilter.FILTER_REJECT;
              }
              node.focus();
              return this.getRootNode().activeElement == node
                ? NodeFilter.FILTER_ACCEPT
                : NodeFilter.FILTER_REJECT;
            },
          }
        );
      }
      return this._walker;
    }
  }
  customElements.define("button-group", ButtonGroup);

  /**
   * A deck that is indexed by the "name" attribute of its children. The
   * <named-deck-button> element is a companion element that can update its state
   * and change the view of a <named-deck>.
   *
   * When the deck is connected it will set the first child as the selected view
   * if a view is not already selected.
   *
   * The deck is implemented using a named slot. Setting a slot directly on a
   * child element of the deck is not supported.
   *
   * You can get or set the selected view by name with the `selectedViewName`
   * property or by setting the "selected-view" attribute.
   *
   * <named-deck>
   *   <section name="cats">Some info about cats.</section>
   *   <section name="dogs">Some dog stuff.</section>
   * </named-deck>
   *
   * let deck = document.querySelector("named-deck");
   * deck.selectedViewName == "cats"; // Cat info is shown.
   * deck.selectedViewName = "dogs";
   * deck.selectedViewName == "dogs"; // Dog stuff is shown.
   * deck.setAttribute("selected-view", "cats");
   * deck.selectedViewName == "cats"; // Cat info is shown.
   *
   * Add the is-tabbed attribute to <named-deck> if you want
   * each of its children to have a tabpanel role and aria-labelledby
   * referencing the NamedDeckButton component.
   */
  class NamedDeck extends HTMLElement {
    static get observedAttributes() {
      return ["selected-view"];
    }

    constructor() {
      super();
      this.attachShadow({ mode: "open" });

      // Create a slot for the visible content.
      let selectedSlot = document.createElement("slot");
      selectedSlot.setAttribute("name", "selected");
      this.shadowRoot.appendChild(selectedSlot);

      this.observer = new MutationObserver(() => {
        this._setSelectedViewAttributes();
      });
    }

    connectedCallback() {
      if (this.selectedViewName) {
        // Make sure the selected view is shown.
        this._setSelectedViewAttributes();
      } else {
        // If there's no selected view, default to the first.
        let firstView = this.firstElementChild;
        if (firstView) {
          // This will trigger showing the first view.
          this.selectedViewName = firstView.getAttribute("name");
        }
      }
      this.observer.observe(this, { childList: true });
    }

    disconnectedCallback() {
      this.observer.disconnect();
    }

    attributeChangedCallback(attr, oldVal, newVal) {
      if (attr == "selected-view" && oldVal != newVal) {
        // Update the slot attribute on the views.
        this._setSelectedViewAttributes();

        // Notify that the selected view changed.
        this.dispatchEvent(new CustomEvent("view-changed"));
      }
    }

    get selectedViewName() {
      return this.getAttribute("selected-view");
    }

    set selectedViewName(name) {
      this.setAttribute("selected-view", name);
    }

    /**
     * Set the slot attribute on all of the views to ensure only the selected view
     * is shown.
     */
    _setSelectedViewAttributes() {
      let { selectedViewName } = this;
      for (let view of this.children) {
        let name = view.getAttribute("name");

        if (this.hasAttribute("is-tabbed")) {
          view.setAttribute("aria-labelledby", `${this.id}-button-${name}`);
          view.setAttribute("role", "tabpanel");
        }

        if (name === selectedViewName) {
          view.slot = "selected";
        } else {
          view.slot = "";
        }
      }
    }
  }
  customElements.define("named-deck", NamedDeck);
}
PK
!<����2chrome/toolkit/content/global/elements/infobar.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:host(.infobar) {
  --info-bar-background-color: light-dark(var(--color-white), rgb(66, 65, 77));
  --info-bar-text-color: light-dark(var(--color-gray-100), var(--color-gray-05));
  position: relative;

  &::before {
    content: "";
    display: block;
    width: 2px;
    position: absolute;
    top: 0;
    inset-inline-start: 0;
    height: 100%;
    border-start-start-radius: 4px;
    border-end-start-radius: 4px;
  }

  .container {
    /* Don't let lwthemes set a text-shadow. */
    text-shadow: none;
    padding-block: 3px;
    align-items: center;
  }

  .content {
    gap: 0 12px;
    height: fit-content;
  }

  .close {
    margin-block: 2px;
    margin-inline-start: 8px;
    align-self: flex-start;
  }
}

@media (prefers-contrast) {
  :host(.infobar)::before {
    background-color: CanvasText;
  }
}

@media not (prefers-contrast) {
  :host(.infobar) {
    box-shadow: 0 1px 2px rgba(58, 57, 68, 0.1);
    background-color: var(--info-bar-background-color);
    color: var(--info-bar-text-color);

    &::before {
      background-image: linear-gradient(0deg, #9059ff 0%, #ff4aa2 52.08%, #ffbd4f 100%);
    }
  }
}

:host([message-bar-type=infobar]:first-of-type) {
  margin-top: 4px;
}

:host([message-bar-type=infobar]) {
  margin: 0 4px 4px;
}

::slotted(.notification-button-container) {
  gap: 8px;
  display: inline-flex;
}

::slotted(.text-link) {
  margin: 0 !important;
}

img.inline-icon {
  /* Align inline icon images in the message content */
  vertical-align: middle;
  /* Ensure they get the right fill color. */
  -moz-context-properties: fill;
  fill: currentColor;
}

strong {
  font-weight: var(--font-weight-bold);
}

/* type="system" infobar styles */

:host([type=system]) .icon {
  display: none;
}

:host([type=system]) .content {
  margin-inline-start: 0;
}
PK
!<!4�'-chrome/toolkit/content/extensions/dummy.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this file,
   - You can obtain one at http://mozilla.org/MPL/2.0/. -->

<window id="documentElement" />
PK
!<��<BB:chrome/toolkit/content/global/elements/moz-message-bar.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:host {
  /* Icon */
  --message-bar-icon-color: var(--icon-color-information);
  --message-bar-icon-size: var(--size-item-small);
  --message-bar-icon-close-url: url("chrome://global/skin/icons/close.svg");

  /* Container */
  --message-bar-container-min-height: var(--size-item-large);

  /* Border */
  --message-bar-border-color: color-mix(in srgb, currentColor 9%, transparent);
  --message-bar-border-radius: var(--border-radius-small);
  --message-bar-border-width: var(--border-width);

  /* Text */
  --message-bar-text-color: var(--text-color);
  --message-bar-text-line-height: 1.5em;

  /* Background */
  --message-bar-background-color: var(--background-color-information);

  background-color: var(--message-bar-background-color);
  border: var(--message-bar-border-width) solid var(--message-bar-border-color);
  border-radius: var(--message-bar-border-radius);
  color: var(--message-bar-text-color);
  text-align: start;
}

@media (prefers-contrast) {
  :host {
    --message-bar-border-color: var(--border-color);
  }
}

/* Make the host to behave as a block by default, but allow hidden to hide it. */
:host(:not([hidden])) {
  display: block;
}

/* MozMessageBar layout */

.container {
  display: flex;
  gap: var(--space-small);
  min-height: var(--message-bar-container-min-height);
  padding-inline: var(--space-large) var(--space-small);
  padding-block: var(--space-small);
}

.content {
  display: flex;
  flex-grow: 1;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-small) var(--space-medium);
  margin-inline-start: var(--space-xlarge);
}

.text-container {
  display: flex;
  gap: var(--space-xsmall) var(--space-small);
  padding-block: calc((var(--message-bar-container-min-height) - var(--message-bar-text-line-height)) / 2);
}

.text-content {
  display: inline-flex;
  gap: var(--space-xsmall) var(--space-small);
  flex-wrap: wrap;
  word-break: break-word;
  line-height: var(--message-bar-text-line-height);
}

/* MozMessageBar icon style */

.icon-container {
  height: var(--message-bar-text-line-height);
  display: flex;
  justify-content: center;
  align-items: center;
  margin-inline-start: calc(-1 * var(--space-xlarge));
}

.icon {
  width: var(--message-bar-icon-size);
  height: var(--message-bar-icon-size);
  flex-shrink: 0;
  appearance: none;
  -moz-context-properties: fill, stroke;
  fill: currentColor;
  stroke: currentColor;
  color: var(--message-bar-icon-color);
}

/* MozMessageBar heading style */

.heading {
  font-weight: 600;
}

/* MozMessageBar message style */

.message {
  margin-inline-end: var(--space-xsmall);
}

/* MozMessageBar link style */

.link {
  display: inline-block;
}

.link ::slotted(a) {
  margin-inline-end: var(--space-xsmall);
}

/* MozMessageBar actions style */

.actions {
  display: none;
}

.actions.active {
  display: inline-flex;
  gap: var(--space-small);
}

.actions ::slotted(button) {
  /* Enforce micro-button width. */
  min-width: fit-content !important;

  margin: 0 !important;
  padding: var(--space-xsmall) var(--space-large) !important;
}

/* Close icon styles */

moz-button::part(button) {
  background-image: var(--message-bar-icon-close-url);
}

@media not (prefers-contrast) {
  /* MozMessageBar colors by message type */
  /* Colors from: https://www.figma.com/file/zd3B9UyknB2XNZNdrYLm2W/Outreachy?type=design&node-id=59-1921&mode=design&t=ZYS4e6pAbAlXGvun-4 */

  :host([type=warning]) {
    --message-bar-background-color: var(--background-color-warning);

    .icon {
      --message-bar-icon-color: var(--icon-color-warning);
    }
  }

  :host([type=success]) {
    --message-bar-background-color: var(--background-color-success);

    .icon {
      --message-bar-icon-color: var(--icon-color-success);
    }
  }

  :host([type=error]),
  :host([type=critical]) {
    --message-bar-background-color: var(--background-color-critical);

    .icon {
      --message-bar-icon-color: var(--icon-color-critical);
    }
  }
}
PK
!</�v�??8chrome/toolkit/skin/classic/global/icons/info-filled.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill light-dark(black, white)" fill-opacity="context-fill-opacity">
  <path d="M7.625.5a7.5 7.5 0 1 0 0 15 7.5 7.5 0 0 0 0-15zm.625 10.875a.625.625 0 0 1-1.25 0l0-4.437a.625.625 0 0 1 1.25 0l0 4.437zM8.25 5 8 5.25l-.75 0L7 5l0-.75.25-.25L8 4l.25.25 0 .75z"/>
</svg>
PK
!<3u���5chrome/toolkit/content/global/elements/moz-button.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:host {
  display: inline-block;
  height: fit-content;
  width: fit-content;
}

button {
  appearance: none;
  min-height: var(--button-min-height);
  color: var(--button-text-color);
  border: var(--button-border);
  border-radius: var(--button-border-radius);
  background-color: var(--button-background-color);
  padding: var(--button-padding);
  /* HTML button gets `font: -moz-button` from UA styles,
   * but we want it to match the root font styling. */
  font: inherit;
  font-weight: var(--button-font-weight);
  /* Ensure font-size isn't overridden by widget styling (e.g. in forms.css) */
  font-size: var(--button-font-size);
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;

  &[size=small] {
    min-height: var(--button-min-height-small);
    font-size: var(--button-font-size-small);
  }

  &:hover {
    background-color: var(--button-background-color-hover);
    border-color: var(--button-border-color-hover);
    color: var(--button-text-color-hover);
  }

  &:hover:active {
    background-color: var(--button-background-color-active);
    border-color: var(--button-border-color-active);
    color: var(--button-text-color-active);
  }

  &:disabled {
    background-color: var(--button-background-color-disabled);
    border-color: var(--button-border-color-disabled);
    color: var(--button-text-color-disabled);
    opacity: var(--button-opacity-disabled);
  }

  &:focus-visible {
    outline: var(--focus-outline);
    outline-offset: var(--focus-outline-offset);
  }

  &[type="primary"] {
    background-color: var(--button-background-color-primary);
    border-color: var(--button-border-color-primary);
    color: var(--button-text-color-primary);

    &:hover {
      background-color: var(--button-background-color-primary-hover);
      border-color: var(--button-border-color-primary-hover);
      color: var(--button-text-color-primary-hover);
    }

    &:hover:active {
      background-color: var(--button-background-color-primary-active);
      border-color: var(--button-border-color-primary-active);
      color: var(--button-text-color-primary-active);
    }

    &:disabled {
      background-color: var(--button-background-color-primary-disabled);
      border-color: var(--button-border-color-primary-disabled);
      color: var(--button-text-color-primary-disabled);
    }
  }

  &[type="destructive"] {
    background-color: var(--button-background-color-destructive);
    border-color: var(--button-border-color-destructive);
    color: var(--button-text-color-destructive);

    &:hover {
      background-color: var(--button-background-color-destructive-hover);
      border-color: var(--button-border-color-destructive-hover);
      color: var(--button-text-color-destructive-hover);
    }

    &:hover:active {
      background-color: var(--button-background-color-destructive-active);
      border-color: var(--button-border-color-destructive-active);
      color: var(--button-text-color-destructive-active);
    }

    &:disabled {
      background-color: var(--button-background-color-destructive-disabled);
      border-color: var(--button-border-color-destructive-disabled);
      color: var(--button-text-color-destructive-disabled);
    }
  }

  &[type~=ghost] {
    background-color: var(--button-background-color-ghost);
    border-color: var(--button-border-color-ghost);
    color: var(--button-text-color-ghost);

    &:hover {
      background-color: var(--button-background-color-ghost-hover);
      border-color: var(--button-border-color-ghost-hover);
      color: var(--button-text-color-ghost-hover);
    }

    &:hover:active {
      background-color: var(--button-background-color-ghost-active);
      border-color: var(--button-border-color-ghost-active);
      color: var(--button-text-color-ghost-active);
    }

    &:disabled {
      background-color: var(--button-background-color-ghost-disabled);
      border-color: var(--button-border-color-ghost-disabled);
      color: var(--button-text-color-ghost-disabled);
    }
  }

  &.labelled {
    gap: var(--space-small)
  }

  &[type~=icon]:not(.labelled) {
    background-size: var(--icon-size-default);
    background-position: center;
    background-repeat: no-repeat;
  }

  &[type~=icon]:not(.labelled),
  &:not(.labelled):has(img) {
    width: var(--button-size-icon);
    height: var(--button-size-icon);
    padding: var(--button-padding-icon);

    &[size=small] {
      width: var(--button-size-icon-small);
      height: var(--button-size-icon-small);
    }
  }

  img,
  &[type~=icon]:not(.labelled) {
    -moz-context-properties: fill, fill-opacity, stroke;
    fill: currentColor;
    stroke: currentColor;
  }

  img {
    width: var(--icon-size-default);
    height: var(--icon-size-default);
    pointer-events: none;
  }
}
PK
!<�~�''4chrome/toolkit/content/global/elements/moz-label.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

label span.accesskey {
  text-decoration: underline;
  text-decoration-skip-ink: none;
}
PK
!<���ss2chrome/toolkit/skin/classic/global/icons/close.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m9.108 7.776 4.709-4.709a.626.626 0 0 0-.884-.885L8.244 6.871l-.488 0-4.689-4.688a.625.625 0 1 0-.884.885L6.87 7.754l0 .491-4.687 4.687a.626.626 0 0 0 .884.885L7.754 9.13l.491 0 4.687 4.687a.627.627 0 0 0 .885 0 .626.626 0 0 0 0-.885L9.108 8.223l0-.447z"/>
</svg>
PK
!<��.���2localization/en-US/toolkit/global/notification.ftl
notification-learnmore-default-label =
  .value = Learn more

notification-close-button =
  .aria-label = Close
  .title = Close

close-notification-message =
    .tooltiptext = Close this message
PK
!<�i�93localization/en-US/toolkit/global/mozMessageBar.ftl
moz-message-bar-icon-info =
  .alt = Info

moz-message-bar-icon-warning =
  .alt = Warning

moz-message-bar-icon-success =
  .alt = Success

moz-message-bar-icon-error =
  .alt = Error

moz-message-bar-close-button =
  .aria-label = Close
  .title = Close
PK
!<+��3modules/services-settings/RemoteSettings.worker.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * A worker dedicated to Remote Settings.
 */

// These files are imported into the worker scope, and are not shared singletons
// with the main thread.
/* eslint-disable mozilla/reject-import-system-module-from-non-system */
import { CanonicalJSON } from "resource://gre/modules/CanonicalJSON.sys.mjs";
import { IDBHelpers } from "resource://services-settings/IDBHelpers.sys.mjs";
import { SharedUtils } from "resource://services-settings/SharedUtils.sys.mjs";
import { jsesc } from "resource://gre/modules/third_party/jsesc/jsesc.mjs";
/* eslint-enable mozilla/reject-import-system-module-from-non-system */

const IDB_RECORDS_STORE = "records";
const IDB_TIMESTAMPS_STORE = "timestamps";

let gShutdown = false;

const Agent = {
  /**
   * Return the canonical JSON serialization of the specified records.
   * It has to match what is done on the server (See Kinto/kinto-signer).
   *
   * @param {Array<Object>} records
   * @param {String} timestamp
   * @returns {String}
   */
  async canonicalStringify(records, timestamp) {
    // Sort list by record id.
    let allRecords = records.sort((a, b) => {
      if (a.id < b.id) {
        return -1;
      }
      return a.id > b.id ? 1 : 0;
    });
    // All existing records are replaced by the version from the server
    // and deleted records are removed.
    for (let i = 0; i < allRecords.length /* no increment! */; ) {
      const rec = allRecords[i];
      const next = allRecords[i + 1];
      if ((next && rec.id == next.id) || rec.deleted) {
        allRecords.splice(i, 1); // remove local record
      } else {
        i++;
      }
    }
    const toSerialize = {
      last_modified: "" + timestamp,
      data: allRecords,
    };
    return CanonicalJSON.stringify(toSerialize, jsesc);
  },

  /**
   * If present, import the JSON file into the Remote Settings IndexedDB
   * for the specified bucket and collection.
   * (eg. blocklists/certificates, main/onboarding)
   * @param {String} bucket
   * @param {String} collection
   * @returns {int} Number of records loaded from dump or -1 if no dump found.
   */
  async importJSONDump(bucket, collection) {
    const { data: records, timestamp } = await SharedUtils.loadJSONDump(
      bucket,
      collection
    );
    if (records === null) {
      // Return -1 if file is missing.
      return -1;
    }
    if (gShutdown) {
      throw new Error("Can't import when we've started shutting down.");
    }
    await importDumpIDB(bucket, collection, records, timestamp);
    return records.length;
  },

  /**
   * Check that the specified file matches the expected size and SHA-256 hash.
   * @param {String} fileUrl file URL to read from
   * @param {Number} size expected file size
   * @param {String} size expected file SHA-256 as hex string
   * @returns {boolean}
   */
  async checkFileHash(fileUrl, size, hash) {
    let resp;
    try {
      resp = await fetch(fileUrl);
    } catch (e) {
      // File does not exist.
      return false;
    }
    const buffer = await resp.arrayBuffer();
    return SharedUtils.checkContentHash(buffer, size, hash);
  },

  async prepareShutdown() {
    gShutdown = true;
    // Ensure we can iterate and abort (which may delete items) by cloning
    // the list.
    let transactions = Array.from(gPendingTransactions);
    for (let transaction of transactions) {
      try {
        transaction.abort();
      } catch (ex) {
        // We can hit this case if the transaction has finished but
        // we haven't heard about it yet.
      }
    }
  },

  _test_only_import(bucket, collection, records, timestamp) {
    return importDumpIDB(bucket, collection, records, timestamp);
  },
};

/**
 * Wrap worker invocations in order to return the `callbackId` along
 * the result. This will allow to transform the worker invocations
 * into promises in `RemoteSettingsWorker.sys.mjs`.
 */
self.onmessage = event => {
  const { callbackId, method, args = [] } = event.data;
  Agent[method](...args)
    .then(result => {
      self.postMessage({ callbackId, result });
    })
    .catch(error => {
      console.log(`RemoteSettingsWorker error: ${error}`);
      self.postMessage({ callbackId, error: "" + error });
    });
};

let gPendingTransactions = new Set();

/**
 * Import the records into the Remote Settings Chrome IndexedDB.
 *
 * Note: This duplicates some logics from `kinto-offline-client.sys.mjs`.
 *
 * @param {String} bucket
 * @param {String} collection
 * @param {Array<Object>} records
 * @param {Number} timestamp
 */
async function importDumpIDB(bucket, collection, records, timestamp) {
  // Open the DB. It will exist since if we are running this, it means
  // we already tried to read the timestamp in `remote-settings.sys.mjs`
  const db = await IDBHelpers.openIDB(false /* do not allow upgrades */);

  // try...finally to ensure we always close the db.
  try {
    if (gShutdown) {
      throw new Error("Can't import when we've started shutting down.");
    }

    // Each entry of the dump will be stored in the records store.
    // They are indexed by `_cid`.
    const cid = bucket + "/" + collection;
    // We can just modify the items in-place, as we got them from SharedUtils.loadJSONDump().
    records.forEach(item => {
      item._cid = cid;
    });
    // Store the collection timestamp.
    let { transaction, promise } = IDBHelpers.executeIDB(
      db,
      [IDB_RECORDS_STORE, IDB_TIMESTAMPS_STORE],
      "readwrite",
      ([recordsStore, timestampStore], rejectTransaction) => {
        // Wipe before loading
        recordsStore.delete(IDBKeyRange.bound([cid], [cid, []], false, true));
        IDBHelpers.bulkOperationHelper(
          recordsStore,
          {
            reject: rejectTransaction,
            completion() {
              timestampStore.put({ cid, value: timestamp });
            },
          },
          "put",
          records
        );
      }
    );
    gPendingTransactions.add(transaction);
    promise = promise.finally(() => gPendingTransactions.delete(transaction));
    await promise;
  } finally {
    // Close now that we're done.
    db.close();
  }
}
PK
!<�����<chrome/toolkit/skin/classic/global/icons/security-broken.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M5.287 3.635A2.744 2.744 0 0 1 8 1.25 2.752 2.752 0 0 1 10.75 4l0 3-2.098 0 1.25 1.25 2.248 0 .6.6 0 2.248 1.25 1.25L14 9a2 2 0 0 0-2-2l0-3c0-2.206-1.794-4-4-4-1.716 0-3.17 1.091-3.737 2.611l1.024 1.024z"/>
  <path d="m12.75 13.868 0 .282-.6.6-8.3 0-.6-.6 0-5.3.6-.6 3.282 0L5.882 7 5.25 7l0-.632L4 5.118 4 7a2 2 0 0 0-2 2l0 5a2 2 0 0 0 2 2l8 0a1.99 1.99 0 0 0 1.801-1.145l-1.051-.987z"/>
  <path fill="#F90D3F" d="M14.375 16a.623.623 0 0 1-.442-.183L1.183 3.068a.626.626 0 0 1 .884-.885l12.75 12.75a.628.628 0 0 1 0 .885.622.622 0 0 1-.442.182z"/>
</svg>
PK
!<�ܖ�1chrome/toolkit/skin/classic/global/icons/more.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M3 7 1.5 7l-.5.5L1 9l.5.5 1.5 0 .5-.5 0-1.5z"/>
  <path d="m8.75 7-1.5 0-.5.5 0 1.5.5.5 1.5 0 .5-.5 0-1.5z"/>
  <path d="M14.5 7 13 7l-.5.5 0 1.5.5.5 1.5 0L15 9l0-1.5z"/>
</svg>
PK
!<�LҠ�>�>'modules/AppProvidedSearchEngine.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint no-shadow: error, mozilla/no-aArgs: error */

import {
  SearchEngine,
  EngineURL,
} from "resource://gre/modules/SearchEngine.sys.mjs";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
  SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "idleService",
  "@mozilla.org/widget/useridleservice;1",
  "nsIUserIdleService"
);

// After the user has been idle for 30s, we'll update icons if we need to.
const ICON_UPDATE_ON_IDLE_DELAY = 30;

/**
 * Handles loading application provided search engine icons from remote settings.
 */
class IconHandler {
  /**
   * The remote settings client for the search engine icons.
   *
   * @type {?RemoteSettingsClient}
   */
  #iconCollection = null;

  /**
   * The list of icon records from the remote settings collection.
   *
   * @type {?object[]}
   */
  #iconList = null;

  /**
   * A flag that indicates if we have queued an idle observer to update icons.
   *
   * @type {boolean}
   */
  #queuedIdle = false;

  /**
   * A map of pending updates that need to be applied to the engines. This is
   * keyed via record id, so that if multiple updates are queued for the same
   * record, then we will only update the engine once.
   *
   * @type {Map<string, object>}
   */
  #pendingUpdatesMap = new Map();

  constructor() {
    this.#iconCollection = lazy.RemoteSettings("search-config-icons");
    this.#iconCollection.on("sync", this._onIconListUpdated.bind(this));
  }

  /**
   * Returns the icon for the record that matches the engine identifier
   * and the preferred width.
   *
   * @param {string} engineIdentifier
   *   The identifier of the engine to match against.
   * @param {number} preferredWidth
   *   The preferred with of the icon.
   * @returns {string}
   *   An object URL that can be used to reference the contents of the specified
   *   source object.
   */
  async getIcon(engineIdentifier, preferredWidth) {
    if (!this.#iconList) {
      await this.#getIconList();
    }

    let iconRecords = this.#iconList.filter(r =>
      this._identifierMatches(engineIdentifier, r.engineIdentifiers)
    );

    if (!iconRecords.length) {
      console.warn("No icon found for", engineIdentifier);
      return null;
    }

    // Default to the first record, in the event we don't have any records
    // that match the width.
    let iconRecord = iconRecords[0];
    for (let record of iconRecords) {
      // TODO: Bug 1655070. We should be using the closest size, but for now use
      // an exact match.
      if (record.imageSize == preferredWidth) {
        iconRecord = record;
        break;
      }
    }

    let iconData;
    try {
      iconData = await this.#iconCollection.attachments.get(iconRecord);
    } catch (ex) {
      console.error(ex);
    }
    if (!iconData) {
      console.warn("Unable to find the icon for", engineIdentifier);
      // Queue an update in case we haven't downloaded it yet.
      this.#pendingUpdatesMap.set(iconRecord.id, iconRecord);
      this.#maybeQueueIdle();
      return null;
    }

    if (iconData.record.last_modified != iconRecord.last_modified) {
      // The icon we have stored is out of date, queue an update so that we'll
      // download the new icon.
      this.#pendingUpdatesMap.set(iconRecord.id, iconRecord);
      this.#maybeQueueIdle();
    }
    return URL.createObjectURL(
      new Blob([iconData.buffer], { type: iconRecord.attachment.mimetype })
    );
  }

  QueryInterface = ChromeUtils.generateQI(["nsIObserver"]);

  /**
   * Called when there is an update queued and the user has been observed to be
   * idle for ICON_UPDATE_ON_IDLE_DELAY seconds.
   *
   * This will always download new icons (added or updated), even if there is
   * no current engine that matches the identifiers. This is to ensure that we
   * have pre-populated the cache if the engine is added later for this user.
   *
   * We do not handle deletes, as remote settings will handle the cleanup of
   * removed records. We also do not expect the case where an icon is removed
   * for an active engine.
   *
   * @param {nsISupports} subject
   *   The subject of the observer.
   * @param {string} topic
   *   The topic of the observer.
   */
  async observe(subject, topic) {
    if (topic != "idle") {
      return;
    }

    this.#queuedIdle = false;
    lazy.idleService.removeIdleObserver(this, ICON_UPDATE_ON_IDLE_DELAY);

    // Update the icon list, in case engines will call getIcon() again.
    await this.#getIconList();

    let appProvidedEngines = await Services.search.getAppProvidedEngines();
    for (let record of this.#pendingUpdatesMap.values()) {
      let iconData;
      try {
        iconData = await this.#iconCollection.attachments.download(record);
      } catch (ex) {
        console.error("Could not download new icon", ex);
        continue;
      }

      for (let engine of appProvidedEngines) {
        await engine.maybeUpdateIconURL(
          record.engineIdentifiers,
          URL.createObjectURL(
            new Blob([iconData.buffer], {
              type: record.attachment.mimetype,
            })
          )
        );
      }
    }

    this.#pendingUpdatesMap.clear();
  }

  /**
   * Checks if the identifier matches any of the engine identifiers.
   *
   * @param {string} identifier
   *   The identifier of the engine.
   * @param {string[]} engineIdentifiers
   *   The list of engine identifiers to match against. This can include
   *   wildcards at the end of strings.
   * @returns {boolean}
   *   Returns true if the identifier matches any of the engine identifiers.
   */
  _identifierMatches(identifier, engineIdentifiers) {
    return engineIdentifiers.some(i => {
      if (i.endsWith("*")) {
        return identifier.startsWith(i.slice(0, -1));
      }
      return identifier == i;
    });
  }

  /**
   * Obtains the icon list from the remote settings collection.
   */
  async #getIconList() {
    try {
      this.#iconList = await this.#iconCollection.get();
    } catch (ex) {
      console.error(ex);
      this.#iconList = [];
    }
    if (!this.#iconList.length) {
      console.error("Failed to obtain search engine icon list records");
    }
  }

  /**
   * Called via a callback when remote settings updates the icon list. This
   * stores potential updates and queues an idle observer to apply them.
   *
   * @param {object} payload
   *   The payload from the remote settings collection.
   * @param {object} payload.data
   *   The payload data from the remote settings collection.
   * @param {object[]} payload.data.created
   *    The list of created records.
   * @param {object[]} payload.data.updated
   *    The list of updated records.
   */
  async _onIconListUpdated({ data: { created, updated } }) {
    created.forEach(record => {
      this.#pendingUpdatesMap.set(record.id, record);
    });
    for (let record of updated) {
      if (record.new) {
        this.#pendingUpdatesMap.set(record.new.id, record.new);
      }
    }
    this.#maybeQueueIdle();
  }

  /**
   * Queues an idle observer if there are pending updates.
   */
  #maybeQueueIdle() {
    if (this.#pendingUpdatesMap && !this.#queuedIdle) {
      this.#queuedIdle = true;
      lazy.idleService.addIdleObserver(this, ICON_UPDATE_ON_IDLE_DELAY);
    }
  }
}

/**
 * AppProvidedSearchEngine represents a search engine defined by the
 * search configuration.
 */
export class AppProvidedSearchEngine extends SearchEngine {
  static URL_TYPE_MAP = new Map([
    ["search", lazy.SearchUtils.URL_TYPE.SEARCH],
    ["suggestions", lazy.SearchUtils.URL_TYPE.SUGGEST_JSON],
    ["trending", lazy.SearchUtils.URL_TYPE.TRENDING_JSON],
  ]);
  static iconHandler = new IconHandler();

  /**
   * A promise for the blob URL of the icon. We save the promise to avoid
   * reentrancy issues.
   *
   * @type {?Promise<string>}
   */
  #blobURLPromise = null;

  /**
   * The identifier from the configuration.
   *
   * @type {?string}
   */
  #configurationId = null;

  /**
   * Whether or not this is a general purpose search engine.
   *
   * @type {boolean}
   */
  #isGeneralPurposeSearchEngine = false;

  /**
   * @param {object} options
   *   The options for this search engine.
   * @param {object} options.config
   *   The engine config from Remote Settings.
   * @param {object} [options.settings]
   *   The saved settings for the user.
   */
  constructor({ config, settings }) {
    super({
      loadPath: "[app]" + config.identifier,
      isAppProvided: true,
      id: config.identifier,
    });

    this.#configurationId = config.identifier;
    this.#init(config);

    this._loadSettings(settings);
  }

  /**
   * Used to clean up the engine when it is removed. This will revoke the blob
   * URL for the icon.
   */
  async cleanup() {
    if (this.#blobURLPromise) {
      URL.revokeObjectURL(await this.#blobURLPromise);
      this.#blobURLPromise = null;
    }
  }

  /**
   * Update this engine based on new config, used during
   * config upgrades.

   * @param {object} options
   *   The options object.
   *
   * @param {object} options.configuration
   *   The search engine configuration for application provided engines.
   */
  update({ configuration } = {}) {
    this._urls = [];
    this.#init(configuration);
    lazy.SearchUtils.notifyAction(this, lazy.SearchUtils.MODIFIED_TYPE.CHANGED);
  }

  /**
   * Whether or not this engine is provided by the application, e.g. it is
   * in the list of configured search engines. Overrides the definition in
   * `SearchEngine`.
   *
   * @returns {boolean}
   */
  get isAppProvided() {
    return true;
  }

  /**
   * Whether or not this engine is an in-memory only search engine.
   * These engines are typically application provided or policy engines,
   * where they are loaded every time on SearchService initialization
   * using the policy JSON or the extension manifest. Minimal details of the
   * in-memory engines are saved to disk, but they are never loaded
   * from the user's saved settings file.
   *
   * @returns {boolean}
   *   Only returns true for application provided engines.
   */
  get inMemory() {
    return true;
  }

  /**
   * Whether or not this engine is a "general" search engine, e.g. is it for
   * generally searching the web, or does it have a specific purpose like
   * shopping.
   *
   * @returns {boolean}
   */
  get isGeneralPurposeEngine() {
    return this.#isGeneralPurposeSearchEngine;
  }

  /**
   * Returns the icon URL for the search engine closest to the preferred width.
   *
   * @param {number} preferredWidth
   *   The preferred width of the image.
   * @returns {Promise<string>}
   *   A promise that resolves to the URL of the icon.
   */
  async getIconURL(preferredWidth) {
    if (this.#blobURLPromise) {
      return this.#blobURLPromise;
    }
    this.#blobURLPromise = AppProvidedSearchEngine.iconHandler.getIcon(
      this.#configurationId,
      preferredWidth
    );
    return this.#blobURLPromise;
  }

  /**
   * This will update the icon URL for the search engine if the engine
   * identifier matches the given engine identifiers.
   *
   * @param {string[]} engineIdentifiers
   *   The engine identifiers to check against.
   * @param {string} blobURL
   *   The new icon URL for the search engine.
   */
  async maybeUpdateIconURL(engineIdentifiers, blobURL) {
    // TODO: Bug 1875912. Once newSearchConfigEnabled has been enabled, we will
    // be able to use `this.id` instead of `this.#configurationId`. At that
    // point, `IconHandler._identifierMatches` can be made into a private
    // function, as this if statement can be handled within `IconHandler.observe`.
    if (
      !AppProvidedSearchEngine.iconHandler._identifierMatches(
        this.#configurationId,
        engineIdentifiers
      )
    ) {
      return;
    }
    if (this.#blobURLPromise) {
      URL.revokeObjectURL(await this.#blobURLPromise);
      this.#blobURLPromise = null;
    }
    this.#blobURLPromise = Promise.resolve(blobURL);
    lazy.SearchUtils.notifyAction(
      this,
      lazy.SearchUtils.MODIFIED_TYPE.ICON_CHANGED
    );
  }

  /**
   * Creates a JavaScript object that represents this engine.
   *
   * @returns {object}
   *   An object suitable for serialization as JSON.
   */
  toJSON() {
    // For applicaiton provided engines we don't want to store all their data in
    // the settings file so just store the relevant metadata.
    return {
      id: this.id,
      _name: this.name,
      _isAppProvided: true,
      _metaData: this._metaData,
    };
  }

  /**
   * Initializes the engine.
   *
   * @param {object} [engineConfig]
   *   The search engine configuration for application provided engines.
   */
  #init(engineConfig) {
    this._orderHint = engineConfig.orderHint;
    this._telemetryId = engineConfig.identifier;
    this.#isGeneralPurposeSearchEngine =
      engineConfig.classification == "general";

    if (engineConfig.charset) {
      this._queryCharset = engineConfig.charset;
    }

    if (engineConfig.telemetrySuffix) {
      this._telemetryId += `-${engineConfig.telemetrySuffix}`;
    }

    if (engineConfig.clickUrl) {
      this.clickUrl = engineConfig.clickUrl;
    }

    this._name = engineConfig.name.trim();
    this._definedAliases =
      engineConfig.aliases?.map(alias => `@${alias}`) ?? [];

    for (const [type, urlData] of Object.entries(engineConfig.urls)) {
      this.#setUrl(type, urlData, engineConfig.partnerCode);
    }
  }

  /**
   * This sets the urls for the search engine based on the supplied parameters.
   *
   * @param {string} type
   *   The type of url. This could be a url for search, suggestions, or trending.
   * @param {object} urlData
   *   The url data contains the template/base url and url params.
   * @param {string} partnerCode
   *   The partner code associated with the search engine.
   */
  #setUrl(type, urlData, partnerCode) {
    let urlType = AppProvidedSearchEngine.URL_TYPE_MAP.get(type);

    if (!urlType) {
      console.warn("unexpected engine url type.", type);
      return;
    }

    let engineURL = new EngineURL(
      urlType,
      urlData.method || "GET",
      urlData.base
    );

    if (urlData.params) {
      for (const param of urlData.params) {
        switch (true) {
          case "value" in param:
            engineURL.addParam(
              param.name,
              param.value == "{partnerCode}" ? partnerCode : param.value
            );
            break;
          case "experimentConfig" in param:
            engineURL._addMozParam({
              name: param.name,
              pref: param.experimentConfig,
              condition: "pref",
            });
            break;
          case "searchAccessPoint" in param:
            for (const [key, value] of Object.entries(
              param.searchAccessPoint
            )) {
              engineURL.addParam(
                param.name,
                value,
                key == "addressbar" ? "keyword" : key
              );
            }
            break;
        }
      }
    }

    if (
      !("searchTermParamName" in urlData) &&
      !urlData.base.includes("{searchTerms}") &&
      !urlType.includes("trending")
    ) {
      throw new Error("Search terms missing from engine URL.");
    }

    if ("searchTermParamName" in urlData) {
      // The search term parameter is always added last, which will add it to the
      // end of the URL. This is because in the past we have seen users trying to
      // modify their searches by altering the end of the URL.
      engineURL.addParam(urlData.searchTermParamName, "{searchTerms}");
    }

    this._urls.push(engineURL);
  }
}
PK
!<��͔����modules/SearchEngine.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint no-shadow: error, mozilla/no-aArgs: error */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
  SearchSettings: "resource://gre/modules/SearchSettings.sys.mjs",
  SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",
  OpenSearchEngine: "resource://gre/modules/OpenSearchEngine.sys.mjs",
});

const BinaryInputStream = Components.Constructor(
  "@mozilla.org/binaryinputstream;1",
  "nsIBinaryInputStream",
  "setInputStream"
);

ChromeUtils.defineLazyGetter(lazy, "logConsole", () => {
  return console.createInstance({
    prefix: "SearchEngine",
    maxLogLevel: lazy.SearchUtils.loggingEnabled ? "Debug" : "Warn",
  });
});

const USER_DEFINED = "searchTerms";

// Supported OpenSearch parameters
// See http://opensearch.a9.com/spec/1.1/querysyntax/#core
const OS_PARAM_INPUT_ENCODING = "inputEncoding";
const OS_PARAM_LANGUAGE = "language";
const OS_PARAM_OUTPUT_ENCODING = "outputEncoding";

// Default values
const OS_PARAM_LANGUAGE_DEF = "*";
const OS_PARAM_OUTPUT_ENCODING_DEF = "UTF-8";

// "Unsupported" OpenSearch parameters. For example, we don't support
// page-based results, so if the engine requires that we send the "page index"
// parameter, we'll always send "1".
const OS_PARAM_COUNT = "count";
const OS_PARAM_START_INDEX = "startIndex";
const OS_PARAM_START_PAGE = "startPage";

// Default values
const OS_PARAM_COUNT_DEF = "20"; // 20 results
const OS_PARAM_START_INDEX_DEF = "1"; // start at 1st result
const OS_PARAM_START_PAGE_DEF = "1"; // 1st page

// A array of arrays containing parameters that we don't fully support, and
// their default values. We will only send values for these parameters if
// required, since our values are just really arbitrary "guesses" that should
// give us the output we want.
var OS_UNSUPPORTED_PARAMS = [
  [OS_PARAM_COUNT, OS_PARAM_COUNT_DEF],
  [OS_PARAM_START_INDEX, OS_PARAM_START_INDEX_DEF],
  [OS_PARAM_START_PAGE, OS_PARAM_START_PAGE_DEF],
];

// An array of attributes that are saved in the engines `_metaData` object.
// Attributes not in this array are considered as system attributes.
const USER_ATTRIBUTES = ["alias", "order", "hideOneOffButton"];

/**
 * Truncates big blobs of (data-)URIs to console-friendly sizes
 *
 * @param {string} str
 *   String to tone down
 * @param {number} len
 *   Maximum length of the string to return. Defaults to the length of a tweet.
 * @returns {string}
 *   The shortend string.
 */
function limitURILength(str, len) {
  len = len || 140;
  if (str.length > len) {
    return str.slice(0, len) + "...";
  }
  return str;
}

/**
 * Tries to rescale an icon to a given size.
 *
 * @param {Array} byteArray
 *   Byte array containing the icon payload.
 * @param {string} contentType
 *   Mime type of the payload.
 * @param {number} [size]
 *   Desired icon size.
 * @returns {Array}
 *   An array of two elements - an array of integers and a string for the content
 *   type.
 * @throws if the icon cannot be rescaled or the rescaled icon is too big.
 */
function rescaleIcon(byteArray, contentType, size = 32) {
  if (contentType == "image/svg+xml") {
    throw new Error("Cannot rescale SVG image");
  }

  let imgTools = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools);
  let arrayBuffer = new Int8Array(byteArray).buffer;
  let container = imgTools.decodeImageFromArrayBuffer(arrayBuffer, contentType);
  let stream = imgTools.encodeScaledImage(container, "image/png", size, size);
  let streamSize = stream.available();
  if (streamSize > lazy.SearchUtils.MAX_ICON_SIZE) {
    throw new Error("Icon is too big");
  }
  let bis = new BinaryInputStream(stream);
  return [bis.readByteArray(streamSize), "image/png"];
}

/**
 * A simple class to handle caching of preferences that may be read from
 * parameters.
 */
const ParamPreferenceCache = {
  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
  ]),

  initCache() {
    // Preference params are normally only on the default branch to avoid these being easily changed.
    // We allow them on the normal branch in nightly builds to make testing easier.
    let branchFetcher = AppConstants.NIGHTLY_BUILD
      ? "getBranch"
      : "getDefaultBranch";
    this.branch = Services.prefs[branchFetcher](
      lazy.SearchUtils.BROWSER_SEARCH_PREF + "param."
    );
    this.cache = new Map();
    this.nimbusCache = new Map();
    for (let prefName of this.branch.getChildList("")) {
      this.cache.set(prefName, this.branch.getCharPref(prefName, null));
    }
    this.branch.addObserver("", this, true);

    this.onNimbusUpdate = this.onNimbusUpdate.bind(this);
    this.onNimbusUpdate();
    lazy.NimbusFeatures.search.onUpdate(this.onNimbusUpdate);
    lazy.NimbusFeatures.search.ready().then(this.onNimbusUpdate);
  },

  observe(subject, topic, data) {
    this.cache.set(data, this.branch.getCharPref(data, null));
  },

  onNimbusUpdate() {
    let extraParams =
      lazy.NimbusFeatures.search.getVariable("extraParams") || [];
    this.nimbusCache.clear();
    // The try catch ensures that if the params were incorrect for some reason,
    // the search service can still startup properly.
    try {
      for (const { key, value } of extraParams) {
        this.nimbusCache.set(key, value);
      }
    } catch (ex) {
      console.error("Failed to load nimbus variables for extraParams:", ex);
    }
  },

  getPref(prefName) {
    if (!this.cache) {
      this.initCache();
    }
    return this.nimbusCache.has(prefName)
      ? this.nimbusCache.get(prefName)
      : this.cache.get(prefName);
  },
};

/**
 * Represents a name/value pair for a parameter
 */
class QueryParameter {
  /**
   * @param {string} name
   *   The parameter's name. Must not be null.
   * @param {string} value
   *   The value of the parameter. May be an empty string, must not be null or
   *   undefined.
   * @param {string} purpose
   *   The search purpose for which matches when this parameter should be
   *   applied, e.g. "searchbar", "contextmenu".
   */
  constructor(name, value, purpose = null) {
    if (!name || value == null) {
      throw Components.Exception(
        "missing name or value for QueryParameter!",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    this.name = name;
    this._value = value;
    this.purpose = purpose;
  }

  get value() {
    return this._value;
  }

  toJSON() {
    const result = {
      name: this.name,
      value: this.value,
    };
    if (this.purpose) {
      result.purpose = this.purpose;
    }
    return result;
  }
}

/**
 * Represents a special paramater that can be set by preferences. The
 * value is read from the 'browser.search.param.*' default preference
 * branch.
 */
class QueryPreferenceParameter extends QueryParameter {
  /**
   * @param {string} name
   *   The name of the parameter as injected into the query string.
   * @param {string} prefName
   *   The name of the preference to read from the branch.
   * @param {string} purpose
   *   The search purpose for which matches when this parameter should be
   *   applied, e.g. `searchbar`, `contextmenu`.
   */
  constructor(name, prefName, purpose) {
    super(name, prefName, purpose);
  }

  get value() {
    const prefValue = ParamPreferenceCache.getPref(this._value);
    return prefValue ? encodeURIComponent(prefValue) : null;
  }

  /**
   * Converts the object to json. This object is converted with a mozparam flag
   * as it gets written to the cache and hence we then know what type it is
   * when reading it back.
   *
   * @returns {object}
   */
  toJSON() {
    const result = {
      condition: "pref",
      mozparam: true,
      name: this.name,
      pref: this._value,
    };
    if (this.purpose) {
      result.purpose = this.purpose;
    }
    return result;
  }
}

/**
 * Perform OpenSearch parameter substitution on a parameter value.
 *
 * @see http://opensearch.a9.com/spec/1.1/querysyntax/#core
 *
 * @param {string} paramValue
 *   The OpenSearch search parameters.
 * @param {string} searchTerms
 *   The user-provided search terms. This string will inserted into
 *   paramValue as the value of the OS_PARAM_USER_DEFINED parameter.
 *   This value must already be escaped appropriately - it is inserted
 *   as-is.
 * @param {string} queryCharset
 *   The character set of the search engine to use for query encoding.
 * @returns {string}
 *   An updated parameter string.
 */
function ParamSubstitution(paramValue, searchTerms, queryCharset) {
  const PARAM_REGEXP = /\{((?:\w+:)?\w+)(\??)\}/g;
  return paramValue.replace(PARAM_REGEXP, function (match, name, optional) {
    // {searchTerms} is by far the most common param so handle it first.
    if (name == USER_DEFINED) {
      return searchTerms;
    }

    // {inputEncoding} is the second most common param.
    if (name == OS_PARAM_INPUT_ENCODING) {
      return queryCharset;
    }

    // Handle the less common OpenSearch parameters we're confident about.
    if (name == OS_PARAM_LANGUAGE) {
      return Services.locale.requestedLocale || OS_PARAM_LANGUAGE_DEF;
    }
    if (name == OS_PARAM_OUTPUT_ENCODING) {
      return OS_PARAM_OUTPUT_ENCODING_DEF;
    }

    // At this point, if a parameter is optional, just omit it.
    if (optional) {
      return "";
    }

    // Replace unsupported parameters that only have hardcoded default values.
    for (let param of OS_UNSUPPORTED_PARAMS) {
      if (name == param[0]) {
        return param[1];
      }
    }

    // Don't replace unknown non-optional parameters.
    return match;
  });
}

/**
 * EngineURL holds a query URL and all associated parameters.
 */
export class EngineURL {
  params = [];
  rels = [];

  /**
   * Creates an EngineURL.
   *
   * @param {string} mimeType
   *   The name of the MIME type of the search results returned by this URL.
   * @param {string} requestMethod
   *   The HTTP request method. Must be a case insensitive value of either
   *   "GET" or "POST".
   * @param {string} template
   *   The URL to which search queries should be sent. For GET requests,
   *   must contain the string "{searchTerms}", to indicate where the user
   *   entered search terms should be inserted.
   *
   * @see http://opensearch.a9.com/spec/1.1/querysyntax/#urltag
   *
   * @throws NS_ERROR_NOT_IMPLEMENTED if aType is unsupported.
   */
  constructor(mimeType, requestMethod, template) {
    if (!mimeType || !requestMethod || !template) {
      throw Components.Exception(
        "missing mimeType, method or template for EngineURL!",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    var method = requestMethod.toUpperCase();
    var type = mimeType.toLowerCase();

    if (method != "GET" && method != "POST") {
      throw Components.Exception(
        'method passed to EngineURL must be "GET" or "POST"',
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    this.type = type;
    this.method = method;

    var templateURI = lazy.SearchUtils.makeURI(template);
    if (!templateURI) {
      throw Components.Exception(
        "new EngineURL: template is not a valid URI!",
        Cr.NS_ERROR_FAILURE
      );
    }

    switch (templateURI.scheme) {
      case "http":
      case "https":
        this.template = template;
        break;
      default:
        throw Components.Exception(
          "new EngineURL: template uses invalid scheme!",
          Cr.NS_ERROR_FAILURE
        );
    }

    this.templateHost = templateURI.host;
  }

  addParam(name, value, purpose) {
    this.params.push(new QueryParameter(name, value, purpose));
  }

  /**
   * Adds a MozParam to the parameters list for this URL. For purpose based params
   * these are saved as standard parameters, for preference based we save them
   * as a special type.
   *
   * @param {object} param
   *   The parameter to add.
   * @param {string} param.name
   *   The name of the parameter to add to the url.
   * @param {string} [param.condition]
   *   The type of parameter this is, e.g. "pref" for a preference parameter,
   *   or "purpose" for a value-based parameter with a specific purpose. The
   *   default is "purpose".
   * @param {string} [param.value]
   *   The value if it is a "purpose" parameter.
   * @param {string} [param.purpose]
   *   The purpose of the parameter for when it is applied, e.g. for `searchbar`
   *   searches.
   * @param {string} [param.pref]
   *   The preference name of the parameter, that gets appended to
   *   `browser.search.param.`.
   */
  _addMozParam(param) {
    const purpose = param.purpose || undefined;
    if (param.condition && param.condition == "pref") {
      this.params.push(
        new QueryPreferenceParameter(param.name, param.pref, purpose)
      );
    } else {
      this.addParam(param.name, param.value || undefined, purpose);
    }
  }

  /**
   * Returns a complete URL with parameter data that can be used for submitting
   * a suggestion query or loading a search page.
   *
   * @param {string} searchTerms
   *   The user's search terms.
   * @param {string} queryCharset
   *   The character set that is being used for the query.
   * @param {string} purpose
   *   The source of the search (e.g. searchbar, addressbar).
   * @returns {Submission}
   *   The submission data containing the URL and post data for the URL.
   */
  getSubmission(searchTerms, queryCharset, purpose) {
    var url = ParamSubstitution(this.template, searchTerms, queryCharset);
    // Default to searchbar if the purpose is not provided
    var requestPurpose = purpose || "searchbar";

    // If a particular purpose isn't defined in the plugin, fallback to 'searchbar'.
    if (
      requestPurpose != "searchbar" &&
      !this.params.some(p => p.purpose && p.purpose == requestPurpose)
    ) {
      requestPurpose = "searchbar";
    }

    // Create an application/x-www-form-urlencoded representation of our params
    // (name=value&name=value&name=value)
    let dataArray = [];
    for (var i = 0; i < this.params.length; ++i) {
      var param = this.params[i];

      // If this parameter has a purpose, only add it if the purpose matches
      if (param.purpose && param.purpose != requestPurpose) {
        continue;
      }

      // Preference MozParams might not have a preferenced saved, or a valid value.
      if (param.value != null) {
        var value = ParamSubstitution(param.value, searchTerms, queryCharset);

        dataArray.push(param.name + "=" + value);
      }
    }
    let dataString = dataArray.join("&");

    var postData = null;
    if (this.method == "GET") {
      // GET method requests have no post data, and append the encoded
      // query string to the url...
      if (dataString) {
        if (url.includes("?")) {
          url = `${url}&${dataString}`;
        } else {
          url = `${url}?${dataString}`;
        }
      }
    } else if (this.method == "POST") {
      // POST method requests must wrap the encoded text in a MIME
      // stream and supply that as POSTDATA.
      var stringStream = Cc[
        "@mozilla.org/io/string-input-stream;1"
      ].createInstance(Ci.nsIStringInputStream);
      stringStream.data = dataString;

      postData = Cc["@mozilla.org/network/mime-input-stream;1"].createInstance(
        Ci.nsIMIMEInputStream
      );
      postData.addHeader("Content-Type", "application/x-www-form-urlencoded");
      postData.setData(stringStream);
    }

    return new Submission(Services.io.newURI(url), postData);
  }

  _getTermsParameterName() {
    let searchTerms = "{" + USER_DEFINED + "}";
    let paramName = this.params.find(p => p.value == searchTerms)?.name;
    // Some query params might not be added to this.params
    // in the engine construction process, so try checking the URL
    // template for the existence of the query parameter value.
    if (!paramName) {
      let urlParms = new URL(this.template).searchParams;
      for (let [name, value] of urlParms.entries()) {
        if (value == searchTerms) {
          paramName = name;
          break;
        }
      }
    }
    return paramName ?? "";
  }

  _hasRelation(rel) {
    return this.rels.some(e => e == rel.toLowerCase());
  }

  _initWithJSON(json) {
    if (!json.params) {
      return;
    }

    this.rels = json.rels;

    for (let i = 0; i < json.params.length; ++i) {
      let param = json.params[i];
      // mozparam and purpose are only supported for app-provided engines.
      // Since we do not store the details for those engines, we don't want
      // to handle it here.
      if (!param.mozparam && !param.purpose) {
        this.addParam(param.name, param.value);
      }
    }
  }

  /**
   * Creates a JavaScript object that represents this URL.
   *
   * @returns {object}
   *   An object suitable for serialization as JSON.
   */
  toJSON() {
    var json = {
      params: this.params,
      rels: this.rels,
      template: this.template,
    };

    if (this.type != lazy.SearchUtils.URL_TYPE.SEARCH) {
      json.type = this.type;
    }
    if (this.method != "GET") {
      json.method = this.method;
    }

    return json;
  }
}

/**
 * SearchEngine represents WebExtension based search engines.
 */
export class SearchEngine {
  QueryInterface = ChromeUtils.generateQI(["nsISearchEngine"]);
  // Data set by the user.
  _metaData = {};
  // Anonymized path of where we initially loaded the engine from.
  // This will stay null for engines installed in the profile before we moved
  // to a JSON storage.
  _loadPath = null;
  // The engine's description
  _description = "";
  // Set to true if the engine has a preferred icon (an icon that should not be
  // overridden by a non-preferred icon).
  _hasPreferredIcon = null;
  // The engine's name.
  _name = null;
  // The name of the charset used to submit the search terms.
  _queryCharset = null;
  // The order hint from the configuration (if any).
  _orderHint = null;
  // The telemetry id from the configuration (if any).
  _telemetryId = null;
  // Set to true once the engine has been added to the store, and the initial
  // notification sent. This allows to skip sending notifications during
  // initialization.
  _engineAddedToStore = false;
  // The aliases coming from the engine definition (via webextension
  // keyword field for example).
  _definedAliases = [];
  // The urls associated with this engine.
  _urls = [];
  // The query parameter name of the search url, cached in memory to avoid
  // repeated look-ups.
  _searchUrlQueryParamName = null;
  // The known public suffix of the search url, cached in memory to avoid
  // repeated look-ups.
  _searchUrlPublicSuffix = null;
  /**
   * The unique id of the Search Engine.
   *
   * @type {string}
   */
  #id;

  /**
   *  Creates a Search Engine.
   *
   * @param {object} options
   *   The options for this search engine.
   * @param {string} [options.id]
   *   The identifier to use for this engine, if none is specified a random
   *   uuid is created.
   * @param {string} options.loadPath
   *   The path of the engine was originally loaded from. Should be anonymized.
   */
  constructor(options = {}) {
    this.#id = options.id ?? this.#uuid();
    if (!("loadPath" in options)) {
      throw new Error("loadPath missing from options.");
    }
    this._loadPath = options.loadPath;
  }

  /**
   * Attempts to find an EngineURL object in the set of EngineURLs for
   * this Engine that has the given type string.  (This corresponds to the
   * "type" attribute in the "Url" node in the OpenSearch spec.)
   *
   * @param {string} type
   *   The type to match the EngineURL's type attribute.
   * @param {string} [rel]
   *   Only return URLs that with this rel value.
   * @returns {EngineURL|null}
   *   Returns the first matching URL found, null otherwise.
   */
  _getURLOfType(type, rel) {
    for (let url of this._urls) {
      if (url.type == type && (!rel || url._hasRelation(rel))) {
        return url;
      }
    }

    return null;
  }

  /**
   * Creates a key by serializing an object that contains the icon's width
   * and height.
   *
   * @param {number} width
   *   Width of the icon.
   * @param {number} height
   *   Height of the icon.
   * @returns {string}
   *   Key string.
   */
  _getIconKey(width, height) {
    let keyObj = {
      width,
      height,
    };

    return JSON.stringify(keyObj);
  }

  /**
   * Add an icon to the icon map used by getIconURL().
   *
   * @param {number} width
   *   Width of the icon.
   * @param {number} height
   *   Height of the icon.
   * @param {string} uriSpec
   *   String with the icon's URI.
   */
  _addIconToMap(width, height, uriSpec) {
    if (width == 16 && height == 16) {
      // The 16x16 icon is stored in _iconURL, we don't need to store it twice.
      return;
    }

    // Use an object instead of a Map() because it needs to be serializable.
    this._iconMapObj = this._iconMapObj || {};
    let key = this._getIconKey(width, height);
    this._iconMapObj[key] = uriSpec;
  }

  /**
   * Sets the .iconURI property of the engine. If both aWidth and aHeight are
   * provided an entry will be added to _iconMapObj that will enable accessing
   * icon's data through getIconURL() APIs.
   *
   * @param {string} iconURL
   *   A URI string pointing to the engine's icon. Must have a http[s]
   *   or data scheme. Icons with HTTP[S] schemes will be
   *   downloaded and converted to data URIs for storage in the engine
   *   XML files, if the engine is not built-in.
   * @param {boolean} isPreferred
   *   Whether or not this icon is to be preferred. Preferred icons can
   *   override non-preferred icons.
   * @param {number} [width]
   *   Width of the icon.
   * @param {number} [height]
   *   Height of the icon.
   */
  _setIcon(iconURL, isPreferred, width, height) {
    var uri = lazy.SearchUtils.makeURI(iconURL);

    // Ignore bad URIs
    if (!uri) {
      return;
    }

    lazy.logConsole.debug(
      "_setIcon: Setting icon url for",
      this.name,
      "to",
      limitURILength(uri.spec)
    );
    // Only accept remote icons from http[s]
    switch (uri.scheme) {
      // Fall through to the data case
      case "moz-extension":
      case "data":
        if (!this._hasPreferredIcon || isPreferred) {
          this._iconURI = uri;

          this._hasPreferredIcon = isPreferred;
        }

        if (width && height) {
          this._addIconToMap(width, height, iconURL);
        }
        break;
      case "http":
      case "https": {
        let iconLoadCallback = function (byteArray, contentType) {
          // This callback may run after we've already set a preferred icon,
          // so check again.
          if (this._hasPreferredIcon && !isPreferred) {
            return;
          }

          if (!byteArray) {
            lazy.logConsole.warn("iconLoadCallback: load failed");
            return;
          }

          if (byteArray.length > lazy.SearchUtils.MAX_ICON_SIZE) {
            try {
              lazy.logConsole.debug("iconLoadCallback: rescaling icon");
              [byteArray, contentType] = rescaleIcon(byteArray, contentType);
            } catch (ex) {
              lazy.logConsole.error(
                "Unable to set icon for the search engine:",
                ex
              );
              return;
            }
          }

          let dataURL =
            "data:" +
            contentType +
            ";base64," +
            btoa(String.fromCharCode.apply(null, byteArray));

          this._iconURI = lazy.SearchUtils.makeURI(dataURL);

          if (width && height) {
            this._addIconToMap(width, height, dataURL);
          }

          if (this._engineAddedToStore) {
            lazy.SearchUtils.notifyAction(
              this,
              lazy.SearchUtils.MODIFIED_TYPE.ICON_CHANGED
            );
          }
          this._hasPreferredIcon = isPreferred;
        };

        let chan = lazy.SearchUtils.makeChannel(
          uri,
          Ci.nsIContentPolicy.TYPE_IMAGE
        );
        let listener = new lazy.SearchUtils.LoadListener(
          chan,
          /^image\//,
          iconLoadCallback.bind(this)
        );
        chan.notificationCallbacks = listener;
        chan.asyncOpen(listener);
        break;
      }
    }
  }

  /**
   * Initialize an EngineURL object from metadata.
   *
   * @param {string} type
   *   The url type.
   * @param {object} params
   *   The URL parameters.
   * @param {string | Array} [params.getParams]
   *   Any parameters for a GET method. This is either a query string, or
   *   an array of objects which have name/value pairs.
   * @param {string} [params.method]
   *   The type of method, defaults to GET.
   * @param {string} [params.mozParams]
   *   Any special Mozilla Parameters.
   * @param {string | Array} [params.postParams]
   *   Any parameters for a POST method. This is either a query string, or
   *   an array of objects which have name/value pairs.
   * @param {string} params.template
   *   The url template.
   * @returns {EngineURL}
   *   The newly created EngineURL.
   */
  _getEngineURLFromMetaData(type, params) {
    let url = new EngineURL(type, params.method || "GET", params.template);

    // Do the MozParams first, so that we are more likely to get the query
    // on the end of the URL, rather than the MozParams (xref bug 1484232).
    if (params.mozParams) {
      for (let p of params.mozParams) {
        if ((p.condition || p.purpose) && !this.isAppProvided) {
          continue;
        }
        url._addMozParam(p);
      }
    }
    if (params.postParams) {
      if (Array.isArray(params.postParams)) {
        for (let { name, value } of params.postParams) {
          url.addParam(name, value);
        }
      } else {
        for (let [name, value] of new URLSearchParams(params.postParams)) {
          url.addParam(name, value);
        }
      }
    }

    if (params.getParams) {
      if (Array.isArray(params.getParams)) {
        for (let { name, value } of params.getParams) {
          url.addParam(name, value);
        }
      } else {
        for (let [name, value] of new URLSearchParams(params.getParams)) {
          url.addParam(name, value);
        }
      }
    }

    return url;
  }

  /**
   * Initialize this engine object.
   *
   * @param {object} details
   *   The details of the engine.
   * @param {string} details.name
   *   The name of the engine.
   * @param {string} details.keyword
   *   The keyword for the engine.
   * @param {string} details.iconURL
   *   The url to use for the icon of the engine.
   * @param {string} details.search_url
   *   The search url template for the engine.
   * @param {string} [details.search_url_get_params]
   *   The search url parameters for use with the GET method.
   * @param {string} [details.search_url_post_params]
   *   The search url parameters for use with the POST method.
   * @param {object} [details.params]
   *   Any special Mozilla parameters.
   * @param {string} [details.suggest_url]
   *   The suggestion url template for the engine.
   * @param {string} [details.suggest_url_get_params]
   *   The suggestion url parameters for use with the GET method.
   * @param {string} [details.suggest_url_post_params]
   *   The suggestion url parameters for use with the POST method.
   * @param {string} [details.encoding]
   *   The encoding to use for the engine.
   * @param {object} [configuration]
   *   The search engine configuration for application provided engines, that
   *   may be overriding some of the WebExtension's settings.
   */
  _initWithDetails(details, configuration = {}) {
    this._orderHint = configuration.orderHint;
    this._name = details.name.trim();

    this._definedAliases = [];
    if (Array.isArray(details.keyword)) {
      this._definedAliases = details.keyword.map(k => k.trim());
    } else if (details.keyword?.trim()) {
      this._definedAliases = [details.keyword?.trim()];
    }

    this._description = details.description;
    if (details.iconURL) {
      this._setIcon(details.iconURL, true);
    }
    this._setUrls(details, configuration);
  }

  /**
   * This sets the urls for the search engine based on the supplied parameters.
   * If you add anything here, please consider if it needs to be handled in the
   * overrideWithEngine / removeExtensionOverride functions as well.
   *
   * @param {object} details
   *   The details of the engine.
   * @param {string} details.search_url
   *   The search url template for the engine.
   * @param {string} [details.search_url_get_params]
   *   The search url parameters for use with the GET method.
   * @param {string} [details.search_url_post_params]
   *   The search url parameters for use with the POST method.
   * @param {object} [details.params]
   *   Any special Mozilla parameters.
   * @param {string} [details.suggest_url]
   *   The suggestion url template for the engine.
   * @param {string} [details.suggest_url_get_params]
   *   The suggestion url parameters for use with the GET method.
   * @param {string} [details.suggest_url_post_params]
   *   The suggestion url parameters for use with the POST method.
   * @param {string} [details.encoding]
   *   The encoding to use for the engine.
   * @param {object} [configuration]
   *   The search engine configuration for application provided engines, that
   *   may be overriding some of the WebExtension's settings.
   */
  _setUrls(details, configuration = {}) {
    let postParams =
      configuration.params?.searchUrlPostParams ||
      details.search_url_post_params ||
      "";
    let url = this._getEngineURLFromMetaData(lazy.SearchUtils.URL_TYPE.SEARCH, {
      method: (postParams && "POST") || "GET",
      // AddonManager will sometimes encode the URL via `new URL()`. We want
      // to ensure we're always dealing with decoded urls.
      template: decodeURI(details.search_url),
      getParams:
        configuration.params?.searchUrlGetParams ||
        details.search_url_get_params ||
        "",
      postParams,
      mozParams: configuration.extraParams || details.params || [],
    });

    this._urls.push(url);

    if (details.suggest_url) {
      let suggestPostParams =
        configuration.params?.suggestUrlPostParams ||
        details.suggest_url_post_params ||
        "";
      url = this._getEngineURLFromMetaData(
        lazy.SearchUtils.URL_TYPE.SUGGEST_JSON,
        {
          method: (suggestPostParams && "POST") || "GET",
          // suggest_url doesn't currently get encoded.
          template: details.suggest_url,
          getParams:
            configuration.params?.suggestUrlGetParams ||
            details.suggest_url_get_params ||
            "",
          postParams: suggestPostParams,
          mozParams: configuration.suggestExtraParams || [],
        }
      );

      this._urls.push(url);
    }

    if (configuration?.urls?.trending) {
      let trending = this._getEngineURLFromMetaData(
        lazy.SearchUtils.URL_TYPE.TRENDING_JSON,
        {
          method: "GET",
          template: decodeURI(configuration.urls.trending.fullPath),
          getParams: configuration.urls.trending.query,
        }
      );
      this._urls.push(trending);
    }

    if (configuration.clickUrl) {
      this.clickUrl = configuration.clickUrl;
    }

    if (details.encoding) {
      this._queryCharset = details.encoding;
    }
  }

  checkSearchUrlMatchesManifest(details) {
    let existingUrl = this._getURLOfType(lazy.SearchUtils.URL_TYPE.SEARCH);

    let newUrl = this._getEngineURLFromMetaData(
      lazy.SearchUtils.URL_TYPE.SEARCH,
      {
        method: (details.search_url_post_params && "POST") || "GET",
        // AddonManager will sometimes encode the URL via `new URL()`. We want
        // to ensure we're always dealing with decoded urls.
        template: decodeURI(details.search_url),
        getParams: details.search_url_get_params || "",
        postParams: details.search_url_post_params || "",
      }
    );

    let existingSubmission = existingUrl.getSubmission("", this.queryCharset);
    let newSubmission = newUrl.getSubmission("", this.queryCharset);

    return (
      existingSubmission.uri.equals(newSubmission.uri) &&
      existingSubmission.postData?.data.data ==
        newSubmission.postData?.data.data
    );
  }

  /**
   * Overrides the urls/parameters with those of the provided engine or extension.
   * The url parameters are not saved to the search settings - the code handling
   * the extension should set these on every restart, this avoids potential
   * third party modifications and means that we can verify the WebExtension is
   * still in the allow list.
   *
   * @param {string} options
   *   The options for this function.
   * @param {AddonSearchEngine|OpenSearchEngine} [options.engine]
   *   The search engine to override with this engine. If not specified, `manifest`
   *   must be provided.
   * @param {object} [options.extension]
   *   An object representing the WebExtensions. If not specified,
   *   `engine` must be provided
   */
  overrideWithEngine({ engine, extension }) {
    this._overriddenData = {
      urls: this._urls,
      queryCharset: this._queryCharset,
    };
    if (engine) {
      // Copy any saved user data (alias, order etc).
      this.copyUserSettingsFrom(engine);

      this._urls = engine._urls;
      this.setAttr("overriddenBy", engine._extensionID ?? engine.id);
      if (engine instanceof lazy.OpenSearchEngine) {
        this.setAttr("overriddenByOpenSearch", engine.toJSON());
      }
    } else {
      this._urls = [];
      this.setAttr("overriddenBy", extension.id);
      this._setUrls(
        extension.manifest.chrome_settings_overrides.search_provider
      );
    }
    lazy.SearchUtils.notifyAction(this, lazy.SearchUtils.MODIFIED_TYPE.CHANGED);
  }

  /**
   * Resets the overrides for the engine if it has been overridden.
   */
  removeExtensionOverride() {
    if (this.getAttr("overriddenBy")) {
      // If the attribute is set, but there is no data, skip it. Worst case,
      // the urls will be reset on a restart.
      if (this._overriddenData) {
        this._urls = this._overriddenData.urls;
        this._queryCharset = this._overriddenData.queryCharset;
        delete this._overriddenData;
      } else {
        lazy.logConsole.error(
          `${this._name} had overriddenBy set, but no _overriddenData`
        );
      }
      this.clearAttr("overriddenBy");
      lazy.SearchUtils.notifyAction(
        this,
        lazy.SearchUtils.MODIFIED_TYPE.CHANGED
      );
    }
  }

  /**
   * Copies settings from the supplied search engine. Typically used for
   * restoring settings when removing an override.
   *
   * @param {SearchEngine|object} engine
   *   The engine to copy the settings from, or the engine settings from
   *   the user's saved settings.
   */
  copyUserSettingsFrom(engine) {
    for (let attribute of USER_ATTRIBUTES) {
      if (attribute in engine._metaData) {
        this._metaData[attribute] = engine._metaData[attribute];
      }
    }
  }

  /**
   * Init from a JSON record.
   *
   * @param {object} json
   *   The json record to use.
   */
  _initWithJSON(json) {
    this.#id = json.id ?? this.#id;
    this._name = json._name;
    this._description = json.description;
    this._hasPreferredIcon = json._hasPreferredIcon == undefined;
    this._queryCharset =
      json.queryCharset || lazy.SearchUtils.DEFAULT_QUERY_CHARSET;
    this._iconURI = lazy.SearchUtils.makeURI(json._iconURL);
    this._iconMapObj = json._iconMapObj || null;
    this._metaData = json._metaData || {};
    this._orderHint = json._orderHint || null;
    this._definedAliases = json._definedAliases || [];
    // These changed keys in Firefox 80, maintain the old keys
    // for backwards compatibility.
    if (json._definedAlias) {
      this._definedAliases.push(json._definedAlias);
    }
    this._filePath = json.filePath || json._filePath || null;

    for (let i = 0; i < json._urls.length; ++i) {
      let url = json._urls[i];
      let engineURL = new EngineURL(
        url.type || lazy.SearchUtils.URL_TYPE.SEARCH,
        url.method || "GET",
        url.template
      );
      engineURL._initWithJSON(url);
      this._urls.push(engineURL);
    }
  }

  /**
   * Creates a JavaScript object that represents this engine.
   *
   * @returns {object}
   *   An object suitable for serialization as JSON.
   */
  toJSON() {
    const fieldsToCopy = [
      "id",
      "_name",
      "_loadPath",
      "description",
      "_iconURL",
      "_iconMapObj",
      "_metaData",
      "_urls",
      "_orderHint",
      "_telemetryId",
      "_filePath",
      "_definedAliases",
    ];

    let json = {};
    for (const field of fieldsToCopy) {
      if (field in this) {
        json[field] = this[field];
      }
    }

    if (!this._hasPreferredIcon) {
      json._hasPreferredIcon = this._hasPreferredIcon;
    }
    if (this.queryCharset != lazy.SearchUtils.DEFAULT_QUERY_CHARSET) {
      json.queryCharset = this.queryCharset;
    }

    return json;
  }

  setAttr(name, val) {
    this._metaData[name] = val;
  }

  getAttr(name) {
    return this._metaData[name] || undefined;
  }

  clearAttr(name) {
    delete this._metaData[name];
  }

  /**
   * Loads engine settings (_metaData) from the list of settings, finding
   * the appropriate details for this engine.
   *
   * @param {object} [settings]
   *   The saved settings for the user.
   */
  _loadSettings(settings) {
    if (!settings) {
      return;
    }

    let engineSettings = lazy.SearchSettings.findSettingsForEngine(
      settings,
      this.id,
      this.name
    );
    if (engineSettings?._metaData) {
      this._metaData = structuredClone(engineSettings._metaData);
    }
  }

  /**
   * Gets the order hint for this engine. This is determined from the search
   * configuration when the engine is initialized.
   *
   * @type {number}
   */
  get orderHint() {
    return this._orderHint;
  }

  /**
   * Get the user-defined alias.
   *
   * @type {string}
   */
  get alias() {
    return this.getAttr("alias") || "";
  }

  set alias(val) {
    var value = val ? val.trim() : "";
    if (value != this.alias) {
      this.setAttr("alias", value);
      lazy.SearchUtils.notifyAction(
        this,
        lazy.SearchUtils.MODIFIED_TYPE.CHANGED
      );
    }
  }

  /**
   * Returns a list of aliases, including a user defined alias and
   * a list defined by webextension keywords.
   *
   * @returns {Array}
   */
  get aliases() {
    return [
      ...(this.getAttr("alias") ? [this.getAttr("alias")] : []),
      ...this._definedAliases,
    ];
  }

  /**
   * Returns the appropriate identifier to use for telemetry. It is based on
   * the following order:
   *
   * - telemetryId: The telemetry id from the configuration, or derived from
   *                the WebExtension name.
   * - other-<name>: The engine name prefixed by `other-` for non-app-provided
   *                 engines.
   *
   * @returns {string}
   */
  get telemetryId() {
    let telemetryId = this._telemetryId || `other-${this.name}`;
    if (this.getAttr("overriddenBy")) {
      return telemetryId + "-addon";
    }
    return telemetryId;
  }

  /**
   * Return the built-in identifier of app-provided engines.
   *
   * @returns {string|null}
   *   Returns a valid if this is a built-in engine, null otherwise.
   */
  get identifier() {
    // No identifier if If the engine isn't app-provided
    return this.isAppProvided ? this._telemetryId : null;
  }

  get description() {
    return this._description;
  }

  get hidden() {
    return this.getAttr("hidden") || false;
  }
  set hidden(val) {
    var value = !!val;
    if (value != this.hidden) {
      this.setAttr("hidden", value);
      lazy.SearchUtils.notifyAction(
        this,
        lazy.SearchUtils.MODIFIED_TYPE.CHANGED
      );
    }
  }

  get hideOneOffButton() {
    return this.getAttr("hideOneOffButton") || false;
  }
  set hideOneOffButton(val) {
    const value = !!val;
    if (value != this.hideOneOffButton) {
      this.setAttr("hideOneOffButton", value);
      lazy.SearchUtils.notifyAction(
        this,
        lazy.SearchUtils.MODIFIED_TYPE.CHANGED
      );
    }
  }

  get _iconURL() {
    if (!this._iconURI) {
      return "";
    }
    return this._iconURI.spec;
  }

  // Where the engine is being loaded from: will return the URI's spec if the
  // engine is being downloaded and does not yet have a file. This is only used
  // for logging and error messages.
  get _location() {
    if (this._uri) {
      return this._uri.spec;
    }

    return this._loadPath;
  }

  /**
   * Whether or not this engine is provided by the application, e.g. it is
   * in the list of configured search engines.
   *
   * @returns {boolean}
   *   This returns false for most engines, but may be overridden by particular
   *   engine types, such as add-on engines which are used by the application.
   */
  get isAppProvided() {
    return false;
  }

  /**
   * Whether or not this engine is an in-memory only search engine.
   * These engines are typically application provided or policy engines,
   * where they are loaded every time on SearchService initialization
   * using the policy JSON or the extension manifest. Minimal details of the
   * in-memory engines are saved to disk, but they are never loaded
   * from the user's saved settings file.
   *
   * @returns {boolean}
   *   This results false for most engines, but may be overridden by particular
   *   engine types, such as add-on engines and policy engines.
   */
  get inMemory() {
    return false;
  }

  get isGeneralPurposeEngine() {
    return false;
  }

  get _hasUpdates() {
    return false;
  }

  get name() {
    return this._name;
  }

  get queryCharset() {
    return this._queryCharset || lazy.SearchUtils.DEFAULT_QUERY_CHARSET;
  }

  /**
   * Gets an object that contains information about what to send to the search
   * engine, for a request. This will be a URI and may also include data for POST
   * requests.
   *
   * @param {string} searchTerms
   *   The search term(s) for the submission.
   *   Note: If an empty data string is supplied, the search form of the search
   *   engine will be returned. This is intentional, as in some cases on the current
   *   UI an empty search is intended to open the search engine's home/search page.
   * @param {lazy.SearchUtils.URL_TYPE} [responseType]
   *   The MIME type that we'd like to receive in response
   *   to this submission.  If null, will default to "text/html".
   * @param {string} [purpose]
   *   A string that indicates the context of the search request. This may then
   *   be used to provide different submission data depending on the context.
   * @returns {nsISearchSubmission|null}
   *   The submission data. If no appropriate submission can be determined for
   *   the request type, this may be null.
   */
  getSubmission(searchTerms, responseType, purpose) {
    // We can't use a default parameter as that doesn't work correctly with
    // the idl interfaces.
    if (!responseType) {
      responseType = lazy.SearchUtils.URL_TYPE.SEARCH;
    }

    var url = this._getURLOfType(responseType);

    if (!url) {
      return null;
    }

    if (
      !searchTerms &&
      responseType != lazy.SearchUtils.URL_TYPE.TRENDING_JSON
    ) {
      lazy.logConsole.warn("getSubmission: searchTerms is empty!");
    }

    var submissionData = "";
    try {
      submissionData = Services.textToSubURI.ConvertAndEscape(
        this.queryCharset,
        searchTerms
      );
    } catch (ex) {
      lazy.logConsole.warn(
        "getSubmission: Falling back to default queryCharset!"
      );
      submissionData = Services.textToSubURI.ConvertAndEscape(
        lazy.SearchUtils.DEFAULT_QUERY_CHARSET,
        searchTerms
      );
    }
    return url.getSubmission(submissionData, this.queryCharset, purpose);
  }

  /**
   * Returns a search URL with no search terms. This is typically used for
   * purposes where we want to check something on the URL, but not use it for
   * an actual submission to the search engine.
   *
   * Note: getSubmission cannot be used for this case, as that returns the
   * search form when passed an empty string.
   *
   * @returns {nsIURI}
   */
  get searchURLWithNoTerms() {
    return this._getURLOfType(lazy.SearchUtils.URL_TYPE.SEARCH).getSubmission(
      "",
      this
    ).uri;
  }

  /**
   * Returns the search term of a possible search result URI if and only if:
   * - The URI has the same scheme, host, and path as the engine.
   * - All query parameters of the URI have a matching name and value in the engine.
   * - An exception to the equality check is the engine's termsParameterName
   *   value, which contains a placeholder, i.e. {searchTerms}.
   * - If an engine has query parameters with "null" values, they will be ignored.
   *
   * @param {nsIURI} uri
   *   A URI that may or may not be from a search result matching the engine.
   *
   * @param {boolean?} skipParamMatching
   *   Whether to skip the step to match the parameters of the input URI with
   *   the URI generated by the Engine. If not provided, it is assumed the
   *   step should not be skipped.
   *
   * @returns {string}
   *   A string representing the termsParameterName value of the URI,
   *   or an empty string if the URI isn't matched to the engine.
   */
  searchTermFromResult(uri, skipParamMatching) {
    let url = this._getURLOfType(lazy.SearchUtils.URL_TYPE.SEARCH);
    if (!url) {
      return "";
    }

    // The engine URL and URI should have the same scheme, host, and path.
    if (
      uri.spec.split("?")[0].toLowerCase() !=
      url.template.split("?")[0].toLowerCase()
    ) {
      return "";
    }

    let engineParams;
    if (url.params.length) {
      engineParams = new URLSearchParams();
      for (let { name, value } of url.params) {
        // Some values might be null, so avoid adding
        // them since the input is unlikely to have it too.
        if (value) {
          // Use append() rather than set() so multiple
          // values of the same name can be stored.
          engineParams.append(name, value);
        }
      }
    } else {
      // Try checking the template for the presence of query params.
      engineParams = new URL(url.template).searchParams;
    }

    let uriParams = new URLSearchParams(uri.query);
    if (
      !skipParamMatching &&
      new Set([...uriParams.keys()]).size !=
        new Set([...engineParams.keys()]).size
    ) {
      return "";
    }

    let termsParameterName = this.getURLParsingInfo().termsParameterName;
    if (!skipParamMatching) {
      for (let [name, value] of uriParams.entries()) {
        // Don't check the name matching the search
        // query because its value will differ.
        if (name == termsParameterName) {
          continue;
        }
        // All params of an input must have a matching
        // key and value in the list of engine parameters.
        if (!engineParams.getAll(name).includes(value)) {
          return "";
        }
      }
    }

    // An engine can use a non UTF-8 charset, which URLSearchParams
    // might not parse properly. Convert the terms parameter value
    // from the original input using the appropriate charset.
    if (this.queryCharset.toLowerCase() != "utf-8") {
      let name = `${termsParameterName}=`;
      let queryString = uri.query
        .split("&")
        .filter(str => str.startsWith(name))
        .pop();
      return Services.textToSubURI.UnEscapeAndConvert(
        this.queryCharset,
        queryString.substring(queryString.indexOf("=") + 1).replace(/\+/g, " ")
      );
    }

    return uriParams.get(termsParameterName) ?? "";
  }

  get searchUrlQueryParamName() {
    if (this._searchUrlQueryParamName != null) {
      return this._searchUrlQueryParamName;
    }

    let submission = this.getSubmission(
      "{searchTerms}",
      lazy.SearchUtils.URL_TYPE.SEARCH
    );

    if (submission.postData) {
      console.error("searchUrlQueryParamName can't handle POST urls.");
      return (this._searchUrlQueryParamName = "");
    }

    let queryParams = new URLSearchParams(submission.uri.query);
    let searchUrlQueryParamName = "";
    for (let [key, value] of queryParams) {
      if (value == "{searchTerms}") {
        searchUrlQueryParamName = key;
      }
    }

    return (this._searchUrlQueryParamName = searchUrlQueryParamName);
  }

  get searchUrlPublicSuffix() {
    if (this._searchUrlPublicSuffix != null) {
      return this._searchUrlPublicSuffix;
    }
    let searchURLPublicSuffix = Services.eTLD.getKnownPublicSuffix(
      this.searchURLWithNoTerms
    );
    return (this._searchUrlPublicSuffix = searchURLPublicSuffix);
  }

  // from nsISearchEngine
  supportsResponseType(type) {
    return this._getURLOfType(type) != null;
  }

  // from nsISearchEngine
  get searchUrlDomain() {
    let url = this._getURLOfType(lazy.SearchUtils.URL_TYPE.SEARCH);
    if (url) {
      return url.templateHost;
    }
    return "";
  }

  /**
   * @returns {string}
   *   URL to the main page of the search engine.
   *   By default this is the pre path of the search URL.
   */
  get searchForm() {
    return this.searchURLWithNoTerms.prePath;
  }

  /**
   * @returns {object}
   *   URL parsing properties used by _buildParseSubmissionMap.
   */
  getURLParsingInfo() {
    let url = this._getURLOfType(lazy.SearchUtils.URL_TYPE.SEARCH);
    if (!url || url.method != "GET") {
      return null;
    }

    let termsParameterName = url._getTermsParameterName();
    if (!termsParameterName) {
      return null;
    }

    let templateUrl = Services.io.newURI(url.template);
    return {
      mainDomain: templateUrl.host,
      path: templateUrl.filePath.toLowerCase(),
      termsParameterName,
    };
  }

  get wrappedJSObject() {
    return this;
  }

  /**
   * Retrieves the icon URL for this search engine, if any.
   *
   * @param {number} preferredWidth
   *   Width of the requested icon. If not specified, it is assumed that
   *   16x16 is desired.
   * @returns {Promise<string|undefined>}
   */
  async getIconURL(preferredWidth) {
    // XPCOM interfaces pass optional number parameters as 0 and can't be
    // handled in the same way.
    if (!preferredWidth) {
      preferredWidth = 16;
    }

    if (preferredWidth == 16) {
      return this._iconURL || undefined;
    }

    if (!this._iconMapObj) {
      return undefined;
    }

    let key = this._getIconKey(preferredWidth, preferredWidth);
    if (key in this._iconMapObj) {
      return this._iconMapObj[key];
    }
    return undefined;
  }

  /**
   * Opens a speculative connection to the engine's search URI
   * (and suggest URI, if different) to reduce request latency
   *
   * @param {object} options
   *   The options object
   * @param {DOMWindow} options.window
   *   The content window for the window performing the search.
   * @param {object} options.originAttributes
   *   The originAttributes for performing the search
   * @throws NS_ERROR_INVALID_ARG if options is omitted or lacks required
   *         elements
   */
  speculativeConnect(options) {
    if (!options || !options.window) {
      console.error(
        "invalid options arg passed to nsISearchEngine.speculativeConnect"
      );
      throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
    }
    let connector = Services.io.QueryInterface(Ci.nsISpeculativeConnect);

    let searchURI = this.searchURLWithNoTerms;

    let callbacks = options.window.docShell.QueryInterface(Ci.nsILoadContext);

    // Using the content principal which is constructed by the search URI
    // and given originAttributes. If originAttributes are not given, we
    // fallback to use the docShell's originAttributes.
    let attrs = options.originAttributes;

    if (!attrs) {
      attrs = options.window.docShell.getOriginAttributes();
    }

    let principal = Services.scriptSecurityManager.createContentPrincipal(
      searchURI,
      attrs
    );

    try {
      connector.speculativeConnect(searchURI, principal, callbacks, false);
    } catch (e) {
      // Can't setup speculative connection for this url, just ignore it.
      console.error(e);
    }

    if (this.supportsResponseType(lazy.SearchUtils.URL_TYPE.SUGGEST_JSON)) {
      let suggestURI = this.getSubmission(
        "dummy",
        lazy.SearchUtils.URL_TYPE.SUGGEST_JSON
      ).uri;
      if (suggestURI.prePath != searchURI.prePath) {
        try {
          connector.speculativeConnect(suggestURI, principal, callbacks, false);
        } catch (e) {
          // Can't setup speculative connection for this url, just ignore it.
          console.error(e);
        }
      }
    }
  }

  get id() {
    return this.#id;
  }

  /**
   * Generates an UUID.
   *
   * @returns {string}
   *   An UUID string, without leading or trailing braces.
   */
  #uuid() {
    let uuid = Services.uuid.generateUUID().toString();
    return uuid.slice(1, uuid.length - 1);
  }
}

/**
 * Implements nsISearchSubmission.
 */
class Submission {
  QueryInterface = ChromeUtils.generateQI(["nsISearchSubmission"]);

  constructor(uri, postData = null) {
    this._uri = uri;
    this._postData = postData;
  }

  get uri() {
    return this._uri;
  }
  get postData() {
    return this._postData;
  }
}
PK
!<���88 modules/SearchStaticData.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This module contains additional data about default search engines that is the
 * same across all languages.  This information is defined outside of the actual
 * search engine definition files, so that localizers don't need to update them
 * when a change is made.
 *
 * This separate module is also easily overridable, in case a hotfix is needed.
 * No high-level processing logic is applied here.
 */

// To update this list of known alternate domains, just cut-and-paste from
// https://www.google.com/supported_domains
const gGoogleDomainsSource =
  ".google.com .google.ad .google.ae .google.com.af .google.com.ag .google.com.ai .google.al .google.am .google.co.ao .google.com.ar .google.as .google.at .google.com.au .google.az .google.ba .google.com.bd .google.be .google.bf .google.bg .google.com.bh .google.bi .google.bj .google.com.bn .google.com.bo .google.com.br .google.bs .google.bt .google.co.bw .google.by .google.com.bz .google.ca .google.cd .google.cf .google.cg .google.ch .google.ci .google.co.ck .google.cl .google.cm .google.cn .google.com.co .google.co.cr .google.com.cu .google.cv .google.com.cy .google.cz .google.de .google.dj .google.dk .google.dm .google.com.do .google.dz .google.com.ec .google.ee .google.com.eg .google.es .google.com.et .google.fi .google.com.fj .google.fm .google.fr .google.ga .google.ge .google.gg .google.com.gh .google.com.gi .google.gl .google.gm .google.gp .google.gr .google.com.gt .google.gy .google.com.hk .google.hn .google.hr .google.ht .google.hu .google.co.id .google.ie .google.co.il .google.im .google.co.in .google.iq .google.is .google.it .google.je .google.com.jm .google.jo .google.co.jp .google.co.ke .google.com.kh .google.ki .google.kg .google.co.kr .google.com.kw .google.kz .google.la .google.com.lb .google.li .google.lk .google.co.ls .google.lt .google.lu .google.lv .google.com.ly .google.co.ma .google.md .google.me .google.mg .google.mk .google.ml .google.com.mm .google.mn .google.ms .google.com.mt .google.mu .google.mv .google.mw .google.com.mx .google.com.my .google.co.mz .google.com.na .google.com.nf .google.com.ng .google.com.ni .google.ne .google.nl .google.no .google.com.np .google.nr .google.nu .google.co.nz .google.com.om .google.com.pa .google.com.pe .google.com.pg .google.com.ph .google.com.pk .google.pl .google.pn .google.com.pr .google.ps .google.pt .google.com.py .google.com.qa .google.ro .google.ru .google.rw .google.com.sa .google.com.sb .google.sc .google.se .google.com.sg .google.sh .google.si .google.sk .google.com.sl .google.sn .google.so .google.sm .google.sr .google.st .google.com.sv .google.td .google.tg .google.co.th .google.com.tj .google.tk .google.tl .google.tm .google.tn .google.to .google.com.tr .google.tt .google.com.tw .google.co.tz .google.com.ua .google.co.ug .google.co.uk .google.com.uy .google.co.uz .google.com.vc .google.co.ve .google.vg .google.co.vi .google.com.vn .google.vu .google.ws .google.rs .google.co.za .google.co.zm .google.co.zw .google.cat";
const gGoogleDomains = gGoogleDomainsSource.split(" ").map(d => "www" + d);

export var SearchStaticData = {
  /**
   * Returns a list of alternate domains for a given search engine domain.
   *
   * @param {string} aDomain
   *   Lowercase host name to look up. For example, if this argument is
   *   "www.google.com" or "www.google.co.uk", the function returns the
   *   full list of supported Google domains.
   *
   * @returns {Array}
   *   Containing one entry for each alternate host name, or empty array
   *   if none is known.  The returned array should not be modified.
   */
  getAlternateDomains(aDomain) {
    return !gGoogleDomains.includes(aDomain) ? [] : gGoogleDomains;
  },
};
PK
!<�v�66 modules/OpenSearchEngine.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint no-shadow: error, mozilla/no-aArgs: error */

import {
  EngineURL,
  SearchEngine,
} from "resource://gre/modules/SearchEngine.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  loadAndParseOpenSearchEngine:
    "resource://gre/modules/OpenSearchLoader.sys.mjs",
  SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logConsole", () => {
  return console.createInstance({
    prefix: "OpenSearchEngine",
    maxLogLevel: lazy.SearchUtils.loggingEnabled ? "Debug" : "Warn",
  });
});

// The default engine update interval, in days. This is only used if an engine
// specifies an updateURL, but not an updateInterval.
const OPENSEARCH_DEFAULT_UPDATE_INTERVAL = 7;

/**
 * OpenSearchEngine represents an OpenSearch base search engine.
 */
export class OpenSearchEngine extends SearchEngine {
  // The data describing the engine, in the form of an XML document element.
  _data = null;
  // The number of days between update checks for new versions
  _updateInterval = null;
  // The url to check at for a new update
  _updateURL = null;
  // The url to check for a new icon
  _iconUpdateURL = null;

  /**
   * Creates a OpenSearchEngine.
   *
   * @param {object} [options]
   *   The options object
   * @param {object} [options.json]
   *   An object that represents the saved JSON settings for the engine.
   * @param {OpenSearchProperties} [options.engineData]
   *   The engine data for this search engine that will have been loaded via
   *   `OpenSearchLoader`.
   */
  constructor(options = {}) {
    super({
      loadPath:
        options.json?._loadPath ??
        OpenSearchEngine.getAnonymizedLoadPath(
          lazy.SearchUtils.sanitizeName(options.engineData.name),
          options.engineData.installURL
        ),
    });

    if (options.engineData) {
      this.#setEngineData(options.engineData);

      // As this is a new engine, we must set the verification hash for the load
      // path set in the constructor.
      this.setAttr(
        "loadPathHash",
        lazy.SearchUtils.getVerificationHash(this._loadPath)
      );

      if (this.hasUpdates) {
        this.#setNextUpdateTime();
      }
    } else {
      this._initWithJSON(options.json);
      this._updateInterval = options.json._updateInterval ?? null;
      this._updateURL = options.json._updateURL ?? null;
      this._iconUpdateURL = options.json._iconUpdateURL ?? null;
    }
  }

  /**
   * Creates a JavaScript object that represents this engine.
   *
   * @returns {object}
   *   An object suitable for serialization as JSON.
   */
  toJSON() {
    let json = super.toJSON();
    json._updateInterval = this._updateInterval;
    json._updateURL = this._updateURL;
    json._iconUpdateURL = this._iconUpdateURL;
    return json;
  }

  /**
   * Determines if this search engine has updates url.
   *
   * @returns {boolean}
   *   Returns true if this search engine may update itself.
   */
  get hasUpdates() {
    // Whether or not the engine has an update URL
    let selfURL = this._getURLOfType(
      lazy.SearchUtils.URL_TYPE.OPENSEARCH,
      "self"
    );
    return !!(this._updateURL || this._iconUpdateURL || selfURL);
  }

  /**
   * Returns the engine's updateURI if it exists and returns null otherwise
   *
   * @returns {?string}
   */
  get updateURI() {
    let updateURL = this._getURLOfType(lazy.SearchUtils.URL_TYPE.OPENSEARCH);
    let updateURI =
      updateURL && updateURL._hasRelation("self")
        ? updateURL.getSubmission("", this).uri
        : lazy.SearchUtils.makeURI(this._updateURL);
    return updateURI;
  }

  /**
   * Considers if this engine needs to be updated, and updates it if necessary.
   */
  async maybeUpdate() {
    if (!this.hasUpdates) {
      return;
    }

    let currentTime = Date.now();

    let expireTime = this.getAttr("updateexpir");

    if (!expireTime || !(expireTime <= currentTime)) {
      lazy.logConsole.debug(this.name, "Skipping update, not expired yet.");
      return;
    }

    await this.#update();

    this.#setNextUpdateTime();
  }

  /**
   * Updates the OpenSearch engine details from the server.
   */
  async #update() {
    let updateURI = this.updateURI;
    if (updateURI) {
      let data = await lazy.loadAndParseOpenSearchEngine(
        updateURI,
        this.getAttr("updatelastmodified")
      );

      this.#setEngineData(data);

      lazy.SearchUtils.notifyAction(
        this,
        lazy.SearchUtils.MODIFIED_TYPE.CHANGED
      );

      // Keep track of the last modified date, so that we can make conditional
      // server requests for future updates.
      this.setAttr("updatelastmodified", new Date().toUTCString());
    }

    if (this._iconUpdateURL) {
      // Force update of the icon from the icon URL.
      this._setIcon(this._iconUpdateURL, true);
    }
  }

  /**
   * Sets the data for this engine based on the OpenSearch properties.
   *
   * @param {OpenSearchProperties} data
   *   The OpenSearch data.
   */
  #setEngineData(data) {
    let name = data.name.trim();
    if (!this._engineToUpdate) {
      if (Services.search.getEngineByName(name)) {
        throw Components.Exception(
          "Found a duplicate engine",
          Ci.nsISearchService.ERROR_DUPLICATE_ENGINE
        );
      }
    }

    this._name = name;
    this._description = data.description ?? "";
    this._queryCharset = data.queryCharset ?? "UTF-8";

    for (let url of data.urls) {
      let engineURL;
      try {
        engineURL = new EngineURL(url.type, url.method, url.template);
      } catch (ex) {
        throw Components.Exception(
          `Failed to add ${url.template} as an Engine URL`,
          Cr.NS_ERROR_FAILURE
        );
      }

      if (url.rels.length) {
        engineURL.rels = url.rels;
      }

      for (let param of url.params) {
        try {
          engineURL.addParam(param.name, param.value);
        } catch (ex) {
          // Ignore failure
          lazy.logConsole.error("OpenSearch url has an invalid param", param);
        }
      }

      this._urls.push(engineURL);
    }

    for (let image of data.images) {
      this._setIcon(image.url, image.isPrefered, image.width, image.height);
    }
  }

  /**
   * Sets the next update time for this engine.
   */
  #setNextUpdateTime() {
    var interval = this._updateInterval || OPENSEARCH_DEFAULT_UPDATE_INTERVAL;
    var milliseconds = interval * 86400000; // |interval| is in days
    this.setAttr("updateexpir", Date.now() + milliseconds);
  }

  /**
   * This indicates where we found the .xml file to load the engine,
   * and attempts to hide user-identifiable data (such as username).
   *
   * @param {string} sanitizedName
   *   The sanitized name of the engine.
   * @param {nsIURI} uri
   *   The uri the engine was loaded from.
   * @returns {string}
   *   A load path with reduced data.
   */
  static getAnonymizedLoadPath(sanitizedName, uri) {
    return `[${uri.scheme}]${uri.host}/${sanitizedName}.xml`;
  }
}
PK
!<�՝ѫ�modules/CanonicalJSON.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

export var CanonicalJSON = {
  /**
   * Return the canonical JSON form of the passed source, sorting all the object
   * keys recursively. Note that this method will cause an infinite loop if
   * cycles exist in the source (bug 1265357).
   *
   * @param source
   *        The elements to be serialized.
   *
   * The output will have all unicode chars escaped with the unicode codepoint
   * as lowercase hexadecimal.
   *
   * @usage
   *        CanonicalJSON.stringify(listOfRecords);
   **/
  stringify: function stringify(source, jsescFn) {
    if (typeof jsescFn != "function") {
      const { jsesc } = ChromeUtils.importESModule(
        "resource://gre/modules/third_party/jsesc/jsesc.mjs"
      );
      jsescFn = jsesc;
    }
    if (Array.isArray(source)) {
      const jsonArray = source.map(x => (typeof x === "undefined" ? null : x));
      return (
        "[" + jsonArray.map(item => stringify(item, jsescFn)).join(",") + "]"
      );
    }

    if (typeof source === "number") {
      if (source === 0) {
        return Object.is(source, -0) ? "-0" : "0";
      }
    }

    // Leverage jsesc library, mainly for unicode escaping.
    const toJSON = input => jsescFn(input, { lowercaseHex: true, json: true });

    if (typeof source !== "object" || source === null) {
      return toJSON(source);
    }

    // Dealing with objects, ordering keys.
    const sortedKeys = Object.keys(source).sort();
    const lastIndex = sortedKeys.length - 1;
    return (
      sortedKeys.reduce((serial, key, index) => {
        const value = source[key];
        // JSON.stringify drops keys with an undefined value.
        if (typeof value === "undefined") {
          return serial;
        }
        const jsonValue = value && value.toJSON ? value.toJSON() : value;
        const suffix = index !== lastIndex ? "," : "";
        const escapedKey = toJSON(key);
        return (
          serial + escapedKey + ":" + stringify(jsonValue, jsescFn) + suffix
        );
      }, "{") + "}"
    );
  },
};
PK
!<L�iL��,modules/services-settings/IDBHelpers.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const DB_NAME = "remote-settings";
const DB_VERSION = 3;

/**
 * Wrap IndexedDB errors to catch them more easily.
 */
class IndexedDBError extends Error {
  constructor(error, method = "", identifier = "") {
    if (typeof error == "string") {
      error = new Error(error);
    }
    super(`IndexedDB: ${identifier} ${method} ${error && error.message}`);
    this.name = error.name;
    this.stack = error.stack;
  }
}

class ShutdownError extends IndexedDBError {
  constructor(error, method = "", identifier = "") {
    super(error, method, identifier);
  }
}

// We batch operations in order to reduce round-trip latency to the IndexedDB
// database thread. The trade-offs are that the more records in the batch, the
// more time we spend on this thread in structured serialization, and the
// greater the chance to jank PBackground and this thread when the responses
// come back. The initial choice of 250 was made targeting 2-3ms on a fast
// machine and 10-15ms on a slow machine.
// Every chunk waits for success before starting the next, and
// the final chunk's completion will fire transaction.oncomplete .
function bulkOperationHelper(
  store,
  { reject, completion },
  operation,
  list,
  listIndex = 0
) {
  try {
    const CHUNK_LENGTH = 250;
    const max = Math.min(listIndex + CHUNK_LENGTH, list.length);
    let request;
    for (; listIndex < max; listIndex++) {
      request = store[operation](list[listIndex]);
    }
    if (listIndex < list.length) {
      // On error, `transaction.onerror` is called.
      request.onsuccess = bulkOperationHelper.bind(
        null,
        store,
        { reject, completion },
        operation,
        list,
        listIndex
      );
    } else if (completion) {
      completion();
    }
    // otherwise, we're done, and the transaction will complete on its own.
  } catch (e) {
    // The executeIDB callsite has a try... catch, but it will not catch
    // errors in subsequent bulkOperationHelper calls chained through
    // request.onsuccess above. We do want to catch those, so we have to
    // feed them through manually. We cannot use an async function with
    // promises, because if we wait a microtask after onsuccess fires to
    // put more requests on the transaction, the transaction will auto-commit
    // before we can add more requests.
    reject(e);
  }
}

/**
 * Helper to wrap some IDBObjectStore operations into a promise.
 *
 * @param {IDBDatabase} db
 * @param {String|String[]} storeNames - either a string or an array of strings.
 * @param {String} mode
 * @param {function} callback
 * @param {String} description of the operation for error handling purposes.
 */
function executeIDB(db, storeNames, mode, callback, desc) {
  if (!Array.isArray(storeNames)) {
    storeNames = [storeNames];
  }
  const transaction = db.transaction(storeNames, mode);
  let promise = new Promise((resolve, reject) => {
    let stores = storeNames.map(name => transaction.objectStore(name));
    let result;
    let rejectWrapper = e => {
      reject(new IndexedDBError(e, desc || "execute()", storeNames.join(", ")));
      try {
        transaction.abort();
      } catch (ex) {
        console.error(ex);
      }
    };
    // Add all the handlers before using the stores.
    transaction.onerror = event =>
      reject(new IndexedDBError(event.target.error, desc || "execute()"));
    transaction.onabort = event =>
      reject(
        new IndexedDBError(
          event.target.error || transaction.error || "IDBTransaction aborted",
          desc || "execute()"
        )
      );
    transaction.oncomplete = () => resolve(result);
    // Simplify access to a single datastore:
    if (stores.length == 1) {
      stores = stores[0];
    }
    try {
      // Although this looks sync, once the callback places requests
      // on the datastore, it can independently keep the transaction alive and
      // keep adding requests. Even once we exit this try.. catch, we may
      // therefore experience errors which should abort the transaction.
      // This is why we pass the rejection handler - then the consumer can
      // continue to ensure that errors are handled appropriately.
      // In theory, exceptions thrown from onsuccess handlers should also
      // cause IndexedDB to abort the transaction, so this is a belt-and-braces
      // approach.
      result = callback(stores, rejectWrapper);
    } catch (e) {
      rejectWrapper(e);
    }
  });
  return { promise, transaction };
}

/**
 * Helper to wrap indexedDB.open() into a promise.
 */
async function openIDB(allowUpgrades = true) {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open(DB_NAME, DB_VERSION);
    request.onupgradeneeded = event => {
      if (!allowUpgrades) {
        reject(
          new Error(
            `IndexedDB: Error accessing ${DB_NAME} IDB at version ${DB_VERSION}`
          )
        );
        return;
      }
      // When an upgrade is needed, a transaction is started.
      const transaction = event.target.transaction;
      transaction.onabort = event => {
        const error =
          event.target.error ||
          transaction.error ||
          new DOMException("The operation has been aborted", "AbortError");
        reject(new IndexedDBError(error, "open()"));
      };

      const db = event.target.result;
      db.onerror = event => reject(new IndexedDBError(event.target.error));

      if (event.oldVersion < 1) {
        // Records store
        const recordsStore = db.createObjectStore("records", {
          keyPath: ["_cid", "id"],
        });
        // An index to obtain all the records in a collection.
        recordsStore.createIndex("cid", "_cid");
        // Last modified field
        recordsStore.createIndex("last_modified", ["_cid", "last_modified"]);
        // Timestamps store
        db.createObjectStore("timestamps", {
          keyPath: "cid",
        });
      }
      if (event.oldVersion < 2) {
        // Collections store
        db.createObjectStore("collections", {
          keyPath: "cid",
        });
      }
      if (event.oldVersion < 3) {
        // Attachment store
        db.createObjectStore("attachments", {
          keyPath: ["cid", "attachmentId"],
        });
      }
    };
    request.onerror = event => reject(new IndexedDBError(event.target.error));
    request.onsuccess = event => {
      const db = event.target.result;
      resolve(db);
    };
  });
}

function destroyIDB() {
  const request = indexedDB.deleteDatabase(DB_NAME);
  return new Promise((resolve, reject) => {
    request.onerror = event => reject(new IndexedDBError(event.target.error));
    request.onsuccess = () => resolve();
  });
}

export var IDBHelpers = {
  bulkOperationHelper,
  executeIDB,
  openIDB,
  destroyIDB,
  IndexedDBError,
  ShutdownError,
};
PK
!<�$D��!�!#modules/third_party/jsesc/jsesc.mjs/*
DO NOT TOUCH THIS FILE DIRECTLY. See the README for instructions.

Copyright Mathias Bynens <https://mathiasbynens.be/>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

var global = {};

/*! https://mths.be/jsesc v1.0.0 by @mathias */
;(function(root) {

	// Detect free variables `exports`
	var freeExports = typeof exports == 'object' && exports;

	// Detect free variable `module`
	var freeModule = typeof module == 'object' && module &&
		module.exports == freeExports && module;

	// Detect free variable `global`, from Node.js or Browserified code,
	// and use it as `root`
	var freeGlobal = typeof global == 'object' && global;
	if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
		root = freeGlobal;
	}

	/*--------------------------------------------------------------------------*/

	var object = {};
	var hasOwnProperty = object.hasOwnProperty;
	var forOwn = function(object, callback) {
		var key;
		for (key in object) {
			if (hasOwnProperty.call(object, key)) {
				callback(key, object[key]);
			}
		}
	};

	var extend = function(destination, source) {
		if (!source) {
			return destination;
		}
		forOwn(source, function(key, value) {
			destination[key] = value;
		});
		return destination;
	};

	var forEach = function(array, callback) {
		var length = array.length;
		var index = -1;
		while (++index < length) {
			callback(array[index]);
		}
	};

	var toString = object.toString;
	var isArray = function(value) {
		return toString.call(value) == '[object Array]';
	};
	var isObject = function(value) {
		// This is a very simple check, but it’s good enough for what we need.
		return toString.call(value) == '[object Object]';
	};
	var isString = function(value) {
		return typeof value == 'string' ||
			toString.call(value) == '[object String]';
	};
	var isFunction = function(value) {
		// In a perfect world, the `typeof` check would be sufficient. However,
		// in Chrome 1–12, `typeof /x/ == 'object'`, and in IE 6–8
		// `typeof alert == 'object'` and similar for other host objects.
		return typeof value == 'function' ||
			toString.call(value) == '[object Function]';
	};

	/*--------------------------------------------------------------------------*/

	// https://mathiasbynens.be/notes/javascript-escapes#single
	var singleEscapes = {
		'"': '\\"',
		'\'': '\\\'',
		'\\': '\\\\',
		'\b': '\\b',
		'\f': '\\f',
		'\n': '\\n',
		'\r': '\\r',
		'\t': '\\t'
		// `\v` is omitted intentionally, because in IE < 9, '\v' == 'v'.
		// '\v': '\\x0B'
	};
	var regexSingleEscape = /["'\\\b\f\n\r\t]/;

	var regexDigit = /[0-9]/;
	var regexWhitelist = /[ !#-&\(-\[\]-~]/;

	var jsesc = function(argument, options) {
		// Handle options
		var defaults = {
			'escapeEverything': false,
			'quotes': 'single',
			'wrap': false,
			'es6': false,
			'json': false,
			'compact': true,
			'lowercaseHex': false,
			'indent': '\t',
			'__indent__': ''
		};
		var json = options && options.json;
		if (json) {
			defaults.quotes = 'double';
			defaults.wrap = true;
		}
		options = extend(defaults, options);
		if (options.quotes != 'single' && options.quotes != 'double') {
			options.quotes = 'single';
		}
		var quote = options.quotes == 'double' ? '"' : '\'';
		var compact = options.compact;
		var indent = options.indent;
		var oldIndent;
		var newLine = compact ? '' : '\n';
		var result;
		var isEmpty = true;

		if (json && argument && isFunction(argument.toJSON)) {
			argument = argument.toJSON();
		}

		if (!isString(argument)) {
			if (isArray(argument)) {
				result = [];
				options.wrap = true;
				oldIndent = options.__indent__;
				indent += oldIndent;
				options.__indent__ = indent;
				forEach(argument, function(value) {
					isEmpty = false;
					result.push(
						(compact ? '' : indent) +
						jsesc(value, options)
					);
				});
				if (isEmpty) {
					return '[]';
				}
				return '[' + newLine + result.join(',' + newLine) + newLine +
					(compact ? '' : oldIndent) + ']';
			} else if (!isObject(argument)) {
				if (json) {
					// For some values (e.g. `undefined`, `function` objects),
					// `JSON.stringify(value)` returns `undefined` (which isn’t valid
					// JSON) instead of `'null'`.
					return JSON.stringify(argument) || 'null';
				}
				return String(argument);
			} else { // it’s an object
				result = [];
				options.wrap = true;
				oldIndent = options.__indent__;
				indent += oldIndent;
				options.__indent__ = indent;
				forOwn(argument, function(key, value) {
					isEmpty = false;
					result.push(
						(compact ? '' : indent) +
						jsesc(key, options) + ':' +
						(compact ? '' : ' ') +
						jsesc(value, options)
					);
				});
				if (isEmpty) {
					return '{}';
				}
				return '{' + newLine + result.join(',' + newLine) + newLine +
					(compact ? '' : oldIndent) + '}';
			}
		}

		var string = argument;
		// Loop over each code unit in the string and escape it
		var index = -1;
		var length = string.length;
		var first;
		var second;
		var codePoint;
		result = '';
		while (++index < length) {
			var character = string.charAt(index);
			if (options.es6) {
				first = string.charCodeAt(index);
				if ( // check if it’s the start of a surrogate pair
					first >= 0xD800 && first <= 0xDBFF && // high surrogate
					length > index + 1 // there is a next code unit
				) {
					second = string.charCodeAt(index + 1);
					if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate
						// https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
						codePoint = (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
						var hexadecimal = codePoint.toString(16);
						if (!options.lowercaseHex) {
							hexadecimal = hexadecimal.toUpperCase();
						}
						result += '\\u{' + hexadecimal + '}';
						index++;
						continue;
					}
				}
			}
			if (!options.escapeEverything) {
				if (regexWhitelist.test(character)) {
					// It’s a printable ASCII character that is not `"`, `'` or `\`,
					// so don’t escape it.
					result += character;
					continue;
				}
				if (character == '"') {
					result += quote == character ? '\\"' : character;
					continue;
				}
				if (character == '\'') {
					result += quote == character ? '\\\'' : character;
					continue;
				}
			}
			if (
				character == '\0' &&
				!json &&
				!regexDigit.test(string.charAt(index + 1))
			) {
				result += '\\0';
				continue;
			}
			if (regexSingleEscape.test(character)) {
				// no need for a `hasOwnProperty` check here
				result += singleEscapes[character];
				continue;
			}
			var charCode = character.charCodeAt(0);
			var hexadecimal = charCode.toString(16);
			if (!options.lowercaseHex) {
				hexadecimal = hexadecimal.toUpperCase();
			}
			var longhand = hexadecimal.length > 2 || json;
			var escaped = '\\' + (longhand ? 'u' : 'x') +
				('0000' + hexadecimal).slice(longhand ? -4 : -2);
			result += escaped;
			continue;
		}
		if (options.wrap) {
			result = quote + result + quote;
		}
		return result;
	};

	jsesc.version = '1.0.0';

	/*--------------------------------------------------------------------------*/

	// Some AMD build optimizers, like r.js, check for specific condition patterns
	// like the following:
	if (
		typeof define == 'function' &&
		typeof define.amd == 'object' &&
		define.amd
	) {
		define(function() {
			return jsesc;
		});
	}	else if (freeExports && !freeExports.nodeType) {
		if (freeModule) { // in Node.js or RingoJS v0.8.0+
			freeModule.exports = jsesc;
		} else { // in Narwhal or RingoJS v0.7.0-
			freeExports.jsesc = jsesc;
		}
	} else { // in Rhino or a web browser
		root.jsesc = jsesc;
	}

}(global));

export var jsesc = global.jsesc;
PK
!<_w�s1s1!modules/TrackingDBService.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { Sqlite } from "resource://gre/modules/Sqlite.sys.mjs";

const SCHEMA_VERSION = 1;
const TRACKERS_BLOCKED_COUNT = "contentblocking.trackers_blocked_count";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "DB_PATH", function () {
  return PathUtils.join(PathUtils.profileDir, "protections.sqlite");
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "social_enabled",
  "privacy.socialtracking.block_cookies.enabled",
  false
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "fpp_enabled",
  "privacy.fingerprintingProtection",
  false
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "milestoneMessagingEnabled",
  "browser.contentblocking.cfr-milestone.enabled",
  false
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "milestones",
  "browser.contentblocking.cfr-milestone.milestones",
  "[]",
  null,
  JSON.parse
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "oldMilestone",
  "browser.contentblocking.cfr-milestone.milestone-achieved",
  0
);

// How often we check if the user is eligible for seeing a "milestone"
// doorhanger. 24 hours by default.
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "MILESTONE_UPDATE_INTERVAL",
  "browser.contentblocking.cfr-milestone.update-interval",
  24 * 60 * 60 * 1000
);

ChromeUtils.defineESModuleGetters(lazy, {
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
});

/**
 * All SQL statements should be defined here.
 */
const SQL = {
  createEvents:
    "CREATE TABLE events (" +
    "id INTEGER PRIMARY KEY, " +
    "type INTEGER NOT NULL, " +
    "count INTEGER NOT NULL, " +
    "timestamp DATE " +
    ");",

  addEvent:
    "INSERT INTO events (type, count, timestamp) " +
    "VALUES (:type, 1, date(:date));",

  incrementEvent: "UPDATE events SET count = count + 1 WHERE id = :id;",

  selectByTypeAndDate:
    "SELECT * FROM events " +
    "WHERE type = :type " +
    "AND timestamp = date(:date);",

  deleteEventsRecords: "DELETE FROM events;",

  removeRecordsSince: "DELETE FROM events WHERE timestamp >= date(:date);",

  selectByDateRange:
    "SELECT * FROM events " +
    "WHERE timestamp BETWEEN date(:dateFrom) AND date(:dateTo);",

  sumAllEvents: "SELECT sum(count) FROM events;",

  getEarliestDate:
    "SELECT timestamp FROM events ORDER BY timestamp ASC LIMIT 1;",
};

/**
 * Creates the database schema.
 */
async function createDatabase(db) {
  await db.execute(SQL.createEvents);
}

async function removeAllRecords(db) {
  await db.execute(SQL.deleteEventsRecords);
}

async function removeRecordsSince(db, date) {
  await db.execute(SQL.removeRecordsSince, { date });
}

export function TrackingDBService() {
  this._initPromise = this._initialize();
}

TrackingDBService.prototype = {
  classID: Components.ID("{3c9c43b6-09eb-4ed2-9b87-e29f4221eef0}"),
  QueryInterface: ChromeUtils.generateQI(["nsITrackingDBService"]),
  // This is the connection to the database, opened in _initialize and closed on _shutdown.
  _db: null,
  waitingTasks: new Set(),
  finishedShutdown: true,

  async ensureDB() {
    await this._initPromise;
    return this._db;
  },

  async _initialize() {
    let db = await Sqlite.openConnection({ path: lazy.DB_PATH });

    try {
      // Check to see if we need to perform any migrations.
      let dbVersion = parseInt(await db.getSchemaVersion());

      // getSchemaVersion() returns a 0 int if the schema
      // version is undefined.
      if (dbVersion === 0) {
        await createDatabase(db);
      } else if (dbVersion < SCHEMA_VERSION) {
        // TODO
        // await upgradeDatabase(db, dbVersion, SCHEMA_VERSION);
      }

      await db.setSchemaVersion(SCHEMA_VERSION);
    } catch (e) {
      // Close the DB connection before passing the exception to the consumer.
      await db.close();
      throw e;
    }

    lazy.AsyncShutdown.profileBeforeChange.addBlocker(
      "TrackingDBService: Shutting down the content blocking database.",
      () => this._shutdown()
    );
    this.finishedShutdown = false;
    this._db = db;
  },

  async _shutdown() {
    let db = await this.ensureDB();
    this.finishedShutdown = true;
    await Promise.all(Array.from(this.waitingTasks, task => task.finalize()));
    await db.close();
  },

  async recordContentBlockingLog(data) {
    if (this.finishedShutdown) {
      // The database has already been closed.
      return;
    }
    let task = new lazy.DeferredTask(async () => {
      try {
        await this.saveEvents(data);
      } finally {
        this.waitingTasks.delete(task);
      }
    }, 0);
    task.arm();
    this.waitingTasks.add(task);
  },

  identifyType(events) {
    let result = null;
    let isTracker = false;
    for (let [state, blocked] of events) {
      if (
        state &
          Ci.nsIWebProgressListener.STATE_LOADED_LEVEL_1_TRACKING_CONTENT ||
        state & Ci.nsIWebProgressListener.STATE_LOADED_LEVEL_2_TRACKING_CONTENT
      ) {
        isTracker = true;
      }
      if (blocked) {
        if (
          state &
            Ci.nsIWebProgressListener.STATE_BLOCKED_FINGERPRINTING_CONTENT ||
          state &
            Ci.nsIWebProgressListener.STATE_REPLACED_FINGERPRINTING_CONTENT
        ) {
          result = Ci.nsITrackingDBService.FINGERPRINTERS_ID;
        } else if (
          lazy.fpp_enabled &&
          state &
            Ci.nsIWebProgressListener.STATE_BLOCKED_SUSPICIOUS_FINGERPRINTING
        ) {
          // The suspicious fingerprinting event gets filed in standard windows
          // regardless of whether the fingerprinting protection is enabled. To
          // avoid recording the case where our protection doesn't apply, we
          // only record blocking suspicious fingerprinting if the
          // fingerprinting protection is enabled in the normal windows.
          //
          // TODO(Bug 1864909): We don't need to check if fingerprinting
          // protection is enabled once the event only gets filed when
          // fingerprinting protection is enabled for the context.
          result = Ci.nsITrackingDBService.SUSPICIOUS_FINGERPRINTERS_ID;
        } else if (
          // If STP is enabled and either a social tracker or cookie is blocked.
          lazy.social_enabled &&
          (state &
            Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_SOCIALTRACKER ||
            state &
              Ci.nsIWebProgressListener.STATE_BLOCKED_SOCIALTRACKING_CONTENT)
        ) {
          result = Ci.nsITrackingDBService.SOCIAL_ID;
        } else if (
          // If there is a tracker blocked. If there is a social tracker blocked, but STP is not enabled.
          state & Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT ||
          state & Ci.nsIWebProgressListener.STATE_BLOCKED_SOCIALTRACKING_CONTENT
        ) {
          result = Ci.nsITrackingDBService.TRACKERS_ID;
        } else if (
          // If a tracking cookie was blocked attribute it to tracking cookies.
          // This includes social tracking cookies since STP is not enabled.
          state & Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_TRACKER ||
          state & Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_SOCIALTRACKER
        ) {
          result = Ci.nsITrackingDBService.TRACKING_COOKIES_ID;
        } else if (
          state &
            Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_BY_PERMISSION ||
          state & Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_ALL ||
          state & Ci.nsIWebProgressListener.STATE_COOKIES_BLOCKED_FOREIGN
        ) {
          result = Ci.nsITrackingDBService.OTHER_COOKIES_BLOCKED_ID;
        } else if (
          state & Ci.nsIWebProgressListener.STATE_BLOCKED_CRYPTOMINING_CONTENT
        ) {
          result = Ci.nsITrackingDBService.CRYPTOMINERS_ID;
        } else if (
          state & Ci.nsIWebProgressListener.STATE_PURGED_BOUNCETRACKER
        ) {
          result = Ci.nsITrackingDBService.BOUNCETRACKERS_ID;
        }
      }
    }
    // if a cookie is blocked for any reason, and it is identified as a tracker,
    // then add to the tracking cookies count.
    if (
      result == Ci.nsITrackingDBService.OTHER_COOKIES_BLOCKED_ID &&
      isTracker
    ) {
      result = Ci.nsITrackingDBService.TRACKING_COOKIES_ID;
    }

    return result;
  },

  /**
   * Saves data rows to the DB.
   * @param data
   *        An array of JS objects representing row items to save.
   */
  async saveEvents(data) {
    let db = await this.ensureDB();
    let log = JSON.parse(data);
    try {
      await db.executeTransaction(async () => {
        for (let thirdParty in log) {
          // "type" will be undefined if there is no blocking event, or 0 if it is a
          // cookie which is not a tracking cookie. These should not be added to the database.
          let type = this.identifyType(log[thirdParty]);
          if (type) {
            // Send the blocked event to Telemetry
            Services.telemetry.scalarAdd(TRACKERS_BLOCKED_COUNT, 1);

            // today is a date "YYY-MM-DD" which can compare with what is
            // already saved in the database.
            let today = new Date().toISOString().split("T")[0];
            let row = await db.executeCached(SQL.selectByTypeAndDate, {
              type,
              date: today,
            });
            let todayEntry = row[0];

            // If previous events happened today (local time), aggregate them.
            if (todayEntry) {
              let id = todayEntry.getResultByName("id");
              await db.executeCached(SQL.incrementEvent, { id });
            } else {
              // Event is created on a new day, add a new entry.
              await db.executeCached(SQL.addEvent, { type, date: today });
            }
          }
        }
      });
    } catch (e) {
      console.error(e);
    }

    // If milestone CFR messaging is not enabled we don't need to update the milestone pref or send the event.
    // We don't do this check too frequently, for performance reasons.
    if (
      !lazy.milestoneMessagingEnabled ||
      (this.lastChecked &&
        Date.now() - this.lastChecked < lazy.MILESTONE_UPDATE_INTERVAL)
    ) {
      return;
    }
    this.lastChecked = Date.now();
    let totalSaved = await this.sumAllEvents();

    let reachedMilestone = null;
    let nextMilestone = null;
    for (let [index, milestone] of lazy.milestones.entries()) {
      if (totalSaved >= milestone) {
        reachedMilestone = milestone;
        nextMilestone = lazy.milestones[index + 1];
      }
    }

    // Show the milestone message if the user is not too close to the next milestone.
    // Or if there is no next milestone.
    if (
      reachedMilestone &&
      (!nextMilestone || nextMilestone - totalSaved > 3000) &&
      (!lazy.oldMilestone || lazy.oldMilestone < reachedMilestone)
    ) {
      Services.obs.notifyObservers(
        {
          wrappedJSObject: {
            event: "ContentBlockingMilestone",
          },
        },
        "SiteProtection:ContentBlockingMilestone"
      );
    }
  },

  async clearAll() {
    let db = await this.ensureDB();
    await removeAllRecords(db);
  },

  async clearSince(date) {
    let db = await this.ensureDB();
    date = new Date(date).toISOString();
    await removeRecordsSince(db, date);
  },

  async getEventsByDateRange(dateFrom, dateTo) {
    let db = await this.ensureDB();
    dateFrom = new Date(dateFrom).toISOString();
    dateTo = new Date(dateTo).toISOString();
    return db.execute(SQL.selectByDateRange, { dateFrom, dateTo });
  },

  async sumAllEvents() {
    let db = await this.ensureDB();
    let results = await db.execute(SQL.sumAllEvents);
    if (!results[0]) {
      return 0;
    }
    let total = results[0].getResultByName("sum(count)");
    return total || 0;
  },

  async getEarliestRecordedDate() {
    let db = await this.ensureDB();
    let date = await db.execute(SQL.getEarliestDate);
    if (!date[0]) {
      return null;
    }
    let earliestDate = date[0].getResultByName("timestamp");

    // All of our dates are recorded as 00:00 GMT, add 12 hours to the timestamp
    // to ensure we display the correct date no matter the user's location.
    let hoursInMS12 = 12 * 60 * 60 * 1000;
    let earliestDateInMS = new Date(earliestDate).getTime() + hoursInMS12;

    return earliestDateInMS || null;
  },
};
PK
!<"��b88:chrome/toolkit/skin/classic/global/icons/page-portrait.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M9.422 1 4 1a2 2 0 0 0-2 2l0 10a2 2 0 0 0 2 2l8 0a2 2 0 0 0 2-2l0-7.422a2 2 0 0 0-.586-1.414l-2.578-2.578A2 2 0 0 0 9.422 1zm3.328 5 0 7.15-.6.6-8.3 0-.6-.6 0-10.3.6-.6 5.15 0L9 5.4l.6.6 3.15 0z"/>
</svg>
PK
!<�p��1chrome/toolkit/skin/classic/global/icons/info.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill light-dark(black, white)" fill-opacity="context-fill-opacity">
  <path d="M8.25 6.938a.625.625 0 0 0-1.25 0l0 4.438a.625.625 0 0 0 1.25-.001l0-4.437z"/>
  <path d="m8 4-.75 0-.25.25L7 5l.25.25.75 0L8.25 5l0-.75z"/>
  <path d="M7.625 1.75c3.446 0 6.25 2.804 6.25 6.25s-2.804 6.25-6.25 6.25-6.25-2.804-6.25-6.25 2.804-6.25 6.25-6.25m0-1.25a7.5 7.5 0 1 0 0 15 7.5 7.5 0 0 0 0-15z"/>
</svg>
PK
!<�;.�L
L
0chrome/toolkit/content/global/viewZoomOverlay.js// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/** Document Zoom Management Code
 *
 * To use this, you'll need to have a global gBrowser variable
 * or use the methods that accept a browser to be modified.
 **/

var ZoomManager = {
  set useFullZoom(aVal) {
    Services.prefs.setBoolPref("browser.zoom.full", aVal);
  },

  get zoom() {
    return this.getZoomForBrowser(gBrowser);
  },

  useFullZoomForBrowser: function ZoomManager_useFullZoomForBrowser(aBrowser) {
    return this.useFullZoom || aBrowser.isSyntheticDocument;
  },

  getFullZoomForBrowser: function ZoomManager_getFullZoomForBrowser(aBrowser) {
    if (!this.useFullZoomForBrowser(aBrowser)) {
      return 1.0;
    }
    return this.getZoomForBrowser(aBrowser);
  },

  getZoomForBrowser: function ZoomManager_getZoomForBrowser(aBrowser) {
    let zoom = this.useFullZoomForBrowser(aBrowser)
      ? aBrowser.fullZoom
      : aBrowser.textZoom;
    // Round to remove any floating-point error.
    return Number(zoom ? zoom.toFixed(2) : 1);
  },

  set zoom(aVal) {
    this.setZoomForBrowser(gBrowser, aVal);
  },

  setZoomForBrowser: function ZoomManager_setZoomForBrowser(aBrowser, aVal) {
    if (aVal < this.MIN || aVal > this.MAX) {
      throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
    }

    if (this.useFullZoomForBrowser(aBrowser)) {
      aBrowser.textZoom = 1;
      aBrowser.fullZoom = aVal;
    } else {
      aBrowser.textZoom = aVal;
      aBrowser.fullZoom = 1;
    }
  },

  enlarge: function ZoomManager_enlarge() {
    var i = this.zoomValues.indexOf(this.snap(this.zoom)) + 1;
    if (i < this.zoomValues.length) {
      this.zoom = this.zoomValues[i];
    }
  },

  reduce: function ZoomManager_reduce() {
    var i = this.zoomValues.indexOf(this.snap(this.zoom)) - 1;
    if (i >= 0) {
      this.zoom = this.zoomValues[i];
    }
  },

  reset: function ZoomManager_reset() {
    this.zoom = 1;
  },

  toggleZoom: function ZoomManager_toggleZoom() {
    var zoomLevel = this.zoom;

    this.useFullZoom = !this.useFullZoom;
    this.zoom = zoomLevel;
  },

  snap: function ZoomManager_snap(aVal) {
    var values = this.zoomValues;
    for (var i = 0; i < values.length; i++) {
      if (values[i] >= aVal) {
        if (i > 0 && aVal - values[i - 1] < values[i] - aVal) {
          i--;
        }
        return values[i];
      }
    }
    return values[i - 1];
  },
};

XPCOMUtils.defineLazyPreferenceGetter(
  ZoomManager,
  "MIN",
  "zoom.minPercent",
  30,
  null,
  v => v / 100
);
XPCOMUtils.defineLazyPreferenceGetter(
  ZoomManager,
  "MAX",
  "zoom.maxPercent",
  500,
  null,
  v => v / 100
);

XPCOMUtils.defineLazyPreferenceGetter(
  ZoomManager,
  "zoomValues",
  "toolkit.zoomManager.zoomValues",
  ".3,.5,.67,.8,.9,1,1.1,1.2,1.33,1.5,1.7,2,2.4,3,4,5",
  null,
  zoomValues => {
    zoomValues = zoomValues.split(",").map(parseFloat);
    zoomValues.sort((a, b) => a - b);

    while (zoomValues[0] < this.MIN) {
      zoomValues.shift();
    }

    while (zoomValues[zoomValues.length - 1] > this.MAX) {
      zoomValues.pop();
    }

    return zoomValues;
  }
);

XPCOMUtils.defineLazyPreferenceGetter(
  ZoomManager,
  "useFullZoom",
  "browser.zoom.full"
);
PK
!<��$EE3chrome/en-US/locale/en-US/global/svg/svg.properties
AttributeParseWarning=Unexpected value %2$S parsing %1$S attribute.
PK
!<L�{��9chrome/en-US/locale/en-US/global/commonDialogs.properties
Alert=Alert
Confirm=Confirm
ConfirmCheck=Confirm
Prompt=Prompt
PromptUsernameAndPassword3=Authentication Required - %S
PromptPassword3=Password Required - %S
Select=Select
OK=OK
Cancel=Cancel
Yes=&Yes
No=&No
Save=&Save
Revert=&Revert
DontSave=Do&n’t Save
ScriptDlgGenericHeading=[JavaScript Application]
ScriptDlgHeading=The page at %S says:
ScriptDlgNullPrincipalHeading=This page says:
ScriptDialogLabel=Prevent this page from creating additional dialogs
ScriptDialogLabelNullPrincipal=Don’t allow this site to prompt you again
ScriptDialogLabelContentPrincipal=Don’t allow %S to prompt you again
ScriptDialogPreventTitle=Confirm Dialog Preference
EnterLoginForRealm3=%2$S is requesting your username and password. The site says: “%1$S”
EnterLoginForProxy3=The proxy %2$S is requesting a username and password. The site says: “%1$S”
EnterUserPasswordFor2=%1$S is requesting your username and password.
EnterUserPasswordForCrossOrigin2=%1$S is requesting your username and password. WARNING: Your password will not be sent to the website you are currently visiting!
EnterPasswordFor=Enter password for %1$S on %2$S
EnterCredentials=This site is asking you to sign in.
EnterPasswordOnlyFor=This site is asking you to sign in as %S.
EnterCredentialsCrossOrigin=This site is asking you to sign in. Warning: Your login information will be shared with %S, not the website you are currently visiting.
SignIn=Sign in
PK
!<nST���9chrome/en-US/locale/en-US/global/mathml/mathml.properties
InvalidChild=Invalid markup: <%1$S> is not allowed as a child of <%2$S>.
ChildCountIncorrect=Invalid markup: Incorrect number of children for <%1$S/> tag.
DuplicateMprescripts=Invalid markup: More than one <mprescripts/> in <mmultiscripts/>.
NoBase=Invalid markup: Expected exactly one Base element in <mmultiscripts/>.  Found none.
SubSupMismatch=Invalid markup: Incomplete subscript/superscript pair in <mmultiscripts/>.

AttributeParsingError=Error in parsing the value ‘%1$S’ for ‘%2$S’ attribute of <%3$S/>.  Attribute ignored.
AttributeParsingErrorNoTag=Error in parsing the value ‘%1$S’ for ‘%2$S’ attribute.  Attribute ignored.
LengthParsingError=Error in parsing MathML attribute value ‘%1$S’ as length.  Attribute ignored.
PK
!<�Ԏ�--=chrome/en-US/locale/en-US/global/security/security.properties
BlockMixedDisplayContent = Blocked loading mixed display content “%1$S”
BlockMixedActiveContent = Blocked loading mixed active content “%1$S”

CORSDisabled=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS disabled).
CORSDidNotSucceed2=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS request did not succeed). Status code: %2$S.
CORSOriginHeaderNotAdded=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS header ‘Origin’ cannot be added).
CORSExternalRedirectNotAllowed=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS request external redirect not allowed).
CORSRequestNotHttp=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS request not http).
CORSMissingAllowOrigin2=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: %2$S.
CORSMultipleAllowOriginNotAllowed=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: Multiple CORS header ‘Access-Control-Allow-Origin’ not allowed).
CORSAllowOriginNotMatchingOrigin=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS header ‘Access-Control-Allow-Origin’ does not match ‘%2$S’).
CORSNotSupportingCredentials=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at ‘%1$S’. (Reason: Credential is not supported if the CORS header ‘Access-Control-Allow-Origin’ is ‘*’).
CORSMethodNotFound=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: Did not find method in CORS header ‘Access-Control-Allow-Methods’).
CORSMissingAllowCredentials=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: expected ‘true’ in CORS header ‘Access-Control-Allow-Credentials’).
CORSPreflightDidNotSucceed3=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: CORS preflight response did not succeed). Status code: %2$S.
CORSInvalidAllowMethod=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: invalid token ‘%2$S’ in CORS header ‘Access-Control-Allow-Methods’).
CORSInvalidAllowHeader=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: invalid token ‘%2$S’ in CORS header ‘Access-Control-Allow-Headers’).
CORSMissingAllowHeaderFromPreflight2=Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at %1$S. (Reason: header ‘%2$S’ is not allowed according to header ‘Access-Control-Allow-Headers’ from CORS preflight response).
CORSAllowHeaderFromPreflightDeprecation=Cross-Origin Request Warning: The Same Origin Policy will disallow reading the remote resource at %1$S soon. (Reason: When the `Access-Control-Allow-Headers` is `*`, the `Authorization` header is not covered. To include the `Authorization` header, it must be explicitly listed in CORS header `Access-Control-Allow-Headers`).

STSUnknownError=Strict-Transport-Security: An unknown error occurred processing the header specified by the site.
STSCouldNotParseHeader=Strict-Transport-Security: The site specified a header that could not be parsed successfully.
STSNoMaxAge=Strict-Transport-Security: The site specified a header that did not include a ‘max-age’ directive.
STSMultipleMaxAges=Strict-Transport-Security: The site specified a header that included multiple ‘max-age’ directives.
STSInvalidMaxAge=Strict-Transport-Security: The site specified a header that included an invalid ‘max-age’ directive.
STSMultipleIncludeSubdomains=Strict-Transport-Security: The site specified a header that included multiple ‘includeSubDomains’ directives.
STSInvalidIncludeSubdomains=Strict-Transport-Security: The site specified a header that included an invalid ‘includeSubDomains’ directive.
STSCouldNotSaveState=Strict-Transport-Security: An error occurred noting the site as a Strict-Transport-Security host.

InsecurePasswordsPresentOnPage=Password fields present on an insecure (http://) page. This is a security risk that allows user login credentials to be stolen.
InsecureFormActionPasswordsPresent=Password fields present in a form with an insecure (http://) form action. This is a security risk that allows user login credentials to be stolen.
InsecurePasswordsPresentOnIframe=Password fields present on an insecure (http://) iframe. This is a security risk that allows user login credentials to be stolen.
LoadingMixedActiveContent2=Loading mixed (insecure) active content “%1$S” on a secure page
LoadingMixedDisplayContent2=Loading mixed (insecure) display content “%1$S” on a secure page
LoadingMixedDisplayObjectSubrequestDeprecation=Loading mixed (insecure) content “%1$S” within a plugin on a secure page is discouraged and will be blocked soon.
MixedContentBlockedDownload = Blocked downloading insecure content “%S”.

BothAllowScriptsAndSameOriginPresent=An iframe which has both allow-scripts and allow-same-origin for its sandbox attribute can remove its sandboxing.
BothAllowTopNavigationAndUserActivationPresent=An iframe which has both allow-top-navigation and allow-top-navigation-by-user-activation for its sandbox attribute will permit top navigations.

MalformedIntegrityHash=The script element has a malformed hash in its integrity attribute: “%1$S”. The correct format is “<hash algorithm>-<hash value>”.
InvalidIntegrityLength=The hash contained in the integrity attribute has the wrong length.
InvalidIntegrityBase64=The hash contained in the integrity attribute could not be decoded.
IntegrityMismatch2=None of the “%1$S” hashes in the integrity attribute match the content of the subresource. The computed hash is “%2$S”.
IneligibleResource=“%1$S” is not eligible for integrity checks since it’s neither CORS-enabled nor same-origin.
UnsupportedHashAlg=Unsupported hash algorithm in the integrity attribute: “%1$S”
NoValidMetadata=The integrity attribute does not contain any valid metadata.

WeakCipherSuiteWarning=This site uses the cipher RC4 for encryption, which is deprecated and insecure.

DeprecatedTLSVersion2=This site uses a deprecated version of TLS. Please upgrade to TLS 1.2 or 1.3.

MimeTypeMismatch2=The resource from “%1$S” was blocked due to MIME type (“%2$S”) mismatch (X-Content-Type-Options: nosniff).
XCTOHeaderValueMissing=X-Content-Type-Options header warning: value was “%1$S”; did you mean to send “nosniff”?
XTCOWithMIMEValueMissing=The resource from “%1$S” was not rendered due to an unknown, incorrect or missing MIME type (X-Content-Type-Options: nosniff).

BlockScriptWithWrongMimeType2=Script from “%1$S” was blocked because of a disallowed MIME type (“%2$S”).
WarnScriptWithWrongMimeType=The script from “%1$S” was loaded even though its MIME type (“%2$S”) is not a valid JavaScript MIME type.
BlockImportScriptsWithWrongMimeType=Loading script from “%1$S” with importScripts() was blocked because of a disallowed MIME type (“%2$S”).
BlockWorkerWithWrongMimeType=Loading Worker from “%1$S” was blocked because of a disallowed MIME type (“%2$S”).
BlockModuleWithWrongMimeType=Loading module from “%1$S” was blocked because of a disallowed MIME type (“%2$S”).

BlockTopLevelDataURINavigation=Navigation to toplevel data: URI not allowed (Blocked loading of: “%1$S”)
BlockRedirectToDataURI=Redirecting to data: URI not allowed (Blocked loading of: “%1$S”)

BlockFileScriptWithWrongMimeType=Loading script from file: URI (“%1$S”) was blocked because its MIME type (“%2$S”) is not a valid JavaScript MIME type.

BlockExtensionScriptWithWrongExt=Loading script with URI “%S” was blocked because the file extension is not allowed.

RestrictBrowserEvalUsage=eval() and eval-like uses are not allowed in the Parent Process or in System Contexts (Blocked usage in “%1$S”)

MixedContentAutoUpgrade=Upgrading insecure display request ‘%1$S’ to use ‘%2$S’
RunningClearSiteDataValue=Clear-Site-Data header forced the clean up of “%S” data.
UnknownClearSiteDataValue=Clear-Site-Data header found. Unknown value “%S”.

ReportingHeaderInvalidJSON=Reporting Header: invalid JSON value received.
ReportingHeaderInvalidNameItem=Reporting Header: invalid name for group.
ReportingHeaderDuplicateGroup=Reporting Header: ignoring duplicated group named “%S”.
ReportingHeaderInvalidItem=Reporting Header: ignoring invalid item named “%S”.
ReportingHeaderInvalidEndpoint=Reporting Header: ignoring invalid endpoint for item named “%S”.
ReportingHeaderInvalidURLEndpoint=Reporting Header: ignoring invalid endpoint URL “%1$S” for item named “%2$S”.

FeaturePolicyUnsupportedFeatureName=Feature Policy: Skipping unsupported feature name “%S”.
FeaturePolicyInvalidEmptyAllowValue= Feature Policy: Skipping empty allow list for feature: “%S”.
FeaturePolicyInvalidAllowValue=Feature Policy: Skipping unsupported allow value “%S”.

ReferrerLengthOverLimitation=HTTP Referrer header: Length is over “%1$S” bytes limit - stripping referrer header down to origin: “%2$S”
ReferrerOriginLengthOverLimitation=HTTP Referrer header: Length of origin within referrer is over “%1$S” bytes limit - removing referrer with origin “%2$S”.

ReferrerPolicyDisallowRelaxingWarning=Referrer Policy: Less restricted policies, including ‘no-referrer-when-downgrade’, ‘origin-when-cross-origin’ and ‘unsafe-url’, will be ignored soon for the cross-site request: %S
ReferrerPolicyDisallowRelaxingMessage=Referrer Policy: Ignoring the less restricted referrer policy “%1$S” for the cross-site request: %2$S

XFrameOptionsInvalid = Invalid X-Frame-Options header was found when loading “%2$S”: “%1$S” is not a valid directive.
XFrameOptionsDeny=The loading of “%2$S” in a frame is denied by “X-Frame-Options“ directive set to “%1$S“.

HTTPSOnlyUpgradeRequest = Upgrading insecure request “%1$S” to use “%2$S”.
HTTPSOnlyNoUpgradeException = Not upgrading insecure request “%1$S” because it is exempt.
HTTPSOnlyFailedRequest = Upgrading insecure request “%1$S” failed. (%2$S)
HTTPSOnlyFailedDowngradeAgain = Upgrading insecure request “%S” failed. Downgrading to “http” again.
HTTPSOnlyUpgradeSpeculativeConnection = Upgrading insecure speculative TCP connection “%1$S” to use “%2$S”.

HTTPSFirstSchemeless = Upgrading URL loaded in the address bar without explicit protocol scheme to use HTTPS.
HTTPSFirstAddingSessionException = Website does not appear to support HTTPS. Further attempts to load “http://%S” securely will be skipped temporarily.

IframeSandboxBlockedDownload = Download of “%S” was blocked because the triggering iframe has the sandbox flag set.

SandboxBlockedCustomProtocols = Blocked navigation to custom protocol “%S” from a sandboxed context.

SanitizerRcvdNoInput = Received empty or no input. Returning an empty DocumentFragment.
PK
!<���Tgg0chrome/en-US/locale/en-US/necko/necko.properties
3=Looking up %1$S…
4=Connected to %1$S…
5=Sending request to %1$S…
6=Transferring data from %1$S…
7=Connecting to %1$S…
8=Read %1$S
9=Wrote %1$S
10=Waiting for %1$S…
11=Looked up %1$S…
12=Performing a TLS handshake to %1$S…
13=The TLS handshake finished for %1$S…

RepostFormData=This web page is being redirected to a new location. Would you like to resend the form data you have typed to the new location?

DirTitle=Index of %1$S
DirGoUp=Up to higher level directory
ShowHidden=Show hidden objects
DirColName=Name
DirColSize=Size
DirColMTime=Last Modified
DirFileLabel=File:

SuperfluousAuth=You are about to log in to the site “%1$S” with the username “%2$S”, but the website does not require authentication. This may be an attempt to trick you.\n\nIs “%1$S” the site you want to visit?
AutomaticAuth=You are about to log in to the site “%1$S” with the username “%2$S”.

TrackerUriBlocked=The resource at “%1$S” was blocked because content blocking is enabled.
UnsafeUriBlocked=The resource at “%1$S” was blocked by Safe Browsing.

StrictUrlProtocolSetter=Url “%1$S“ change to protocol “%2$S“ was blocked.

CORPBlocked=The resource at “%1$S” was blocked due to its Cross-Origin-Resource-Policy header (or lack thereof). See %2$S
CookieBlockedByPermission=Request to access cookies or storage on “%1$S” was blocked because of custom cookie permission.
CookieBlockedTracker=Request to access cookie or storage on “%1$S” was blocked because it came from a tracker and content blocking is enabled.
CookieBlockedAll=Request to access cookie or storage on “%1$S” was blocked because we are blocking all storage access requests.
CookieBlockedForeign=Request to access cookie or storage on “%1$S” was blocked because we are blocking all third-party storage access requests and content blocking is enabled.
CookiePartitionedForeign2=Partitioned cookie or storage access was provided to “%1$S” because it is loaded in the third-party context and dynamic state partitioning is enabled.

CookieAllowedForOriginByStorageAccessAPI=Storage access granted for origin “%2$S” on “%1$S”.
CookieAllowedForOriginByHeuristic=Storage access automatically granted for origin “%2$S” on “%1$S”.
CookieAllowedForDFPIByHeuristic=Storage access automatically granted for Dynamic State Partitioning “%2$S” on “%1$S”.

CookieRejectedNonRequiresSecure2=Cookie “%1$S” rejected because it has the “SameSite=None” attribute but is missing the “secure” attribute.
CookieRejectedNonRequiresSecureForBeta3=Cookie “%1$S” will be soon rejected because it has the “SameSite” attribute set to “None” without the “secure” attribute. To know more about the “SameSite“ attribute, read %2$S
CookieLaxForced2=Cookie “%1$S” has “SameSite” policy set to “Lax” because it is missing a “SameSite” attribute, and “SameSite=Lax” is the default value for this attribute.
CookieLaxForcedForBeta2=Cookie “%1$S” does not have a proper “SameSite” attribute value. Soon, cookies without the “SameSite” attribute or with an invalid value will be treated as “Lax”. This means that the cookie will no longer be sent in third-party contexts. If your application depends on this cookie being available in such contexts, please add the “SameSite=None“ attribute to it. To know more about the “SameSite“ attribute, read %2$S
CookieSameSiteValueInvalid2=Invalid “SameSite“ value for cookie “%1$S”. The supported values are: “Lax“, “Strict“, “None“.
CookieInvalidMaxAgeAttribute=Invalid “max-age“ value for cookie “%1$S”. The attribute is ignored.
CookieOversize=Cookie “%1$S” is invalid because its size is too big. Max size is %2$S B.
CookieRejectedByPermissionManager=Cookie “%1$S” has been rejected by user set permissions.
CookieRejectedEmptyNameAndValue=Cookie with an empty name and an empty value has been rejected.
CookieRejectedInvalidCharName=Cookie “%1$S” has been rejected for invalid characters in the name.
CookieRejectedInvalidCharAttributes=Cookie “%1$S” has been rejected for invalid characters in the attributes.
CookieRejectedInvalidDomain=Cookie “%1$S” has been rejected for invalid domain.
CookieRejectedInvalidPrefix=Cookie “%1$S” has been rejected for invalid prefix.
CookieRejectedInvalidCharValue=Cookie “%1$S” has been rejected for invalid characters in the value.
CookieRejectedHttpOnlyButFromScript=Cookie “%1$S” has been rejected because there is already an HTTP-Only cookie but script tried to store a new one.
CookieRejectedSecureButNonHttps=Cookie “%1$S” has been rejected because a non-HTTPS cookie can’t be set as “secure”.
CookieRejectedThirdParty=Cookie “%1$S” has been rejected as third-party.
CookieRejectedNonsecureOverSecure=Cookie “%1$S” has been rejected because there is an existing “secure” cookie.
CookieRejectedForNonSameSiteness=Cookie “%1$S” has been rejected because it is in a cross-site context and its “SameSite” is “Lax” or “Strict”.
CookieRejectedPartitionedRequiresSecure=Cookie “%1$S” has been rejected because it has the “Partitioned” attribute but is missing the “secure” attribute.
CookieAttributeIgnored=The value of the attribute “%2$S” for the cookie “%1$S” has been rejected because its size is too big. Max size is %3$S B.
CookieAttributeOverwritten=The value of the attribute “%2$S” for the cookie “%1$S” has been overwritten.

CookieForeignNoPartitionedWarning=Cookie “%1$S” will soon be rejected because it is foreign and does not have the “Partitioned“ attribute.

CookieForeignNoPartitionedError=Cookie “%1$S” has been rejected because it is foreign and does not have the “Partitioned“ attribute.

CookieBlockedCrossSiteRedirect=Cookie “%1$S” with the “SameSite” attribute value “Lax” or “Strict” was omitted because of a cross-site redirect.

APIDeprecationWarning=Warning: ‘%1$S’ deprecated, please use ‘%2$S’

ResourceBlockedORB=The resource at “%1$S” was blocked by OpaqueResponseBlocking. Reason: “%2$S”.

InvalidHTTPResponseStatusLine=The status line of the HTTP response is invalid
PK
!<���**%res/locale/layout/HtmlForm.properties
Reset=Reset
Submit=Submit Query
Browse=Browse…
FileUpload=File Upload
DirectoryUpload=Select Folder to Upload
DirectoryPickerOkButtonLabel=Upload
ForgotPostWarning=Form contains enctype=%S, but does not contain method=post.  Submitting normally with method=GET and no enctype instead.
ForgotFileEnctypeWarning=Form contains a file input, but is missing method=POST and enctype=multipart/form-data on the form.  The file will not be sent.
DefaultFormSubject=Form Post from %S
CannotEncodeAllUnicode=A form was submitted in the %S encoding which cannot encode all Unicode characters, so user input may get corrupted. To avoid this problem, the page should be changed so that the form is submitted in the UTF-8 encoding either by changing the encoding of the page itself to UTF-8 or by specifying accept-charset=utf-8 on the form element.
AllSupportedTypes=All Supported Types
NoFileSelected=No file selected.
NoFilesSelected=No files selected.
NoDirSelected=No directory selected.
XFilesSelected=%S files selected.
ColorPicker=Choose a color
DefaultSummary=Details
PK
!<G<-6�6�res/locale/dom/dom.properties
KillScriptTitle=Warning: Unresponsive script
KillScriptMessage=A script on this page may be busy, or it may have stopped responding. You can stop the script now, or you can continue to see if the script will complete.
KillScriptWithDebugMessage=A script on this page may be busy, or it may have stopped responding. You can stop the script now, open the script in the debugger, or let the script continue.
KillScriptLocation=Script: %S

KillAddonScriptTitle=Warning: Unresponsive add-on script
KillAddonScriptMessage=A script from the extension “%1$S” is running on this page, and making %2$S unresponsive.\n\nIt may be busy, or it may have stopped responding permanently. You can stop the script now, or you can continue to see if it will complete.
KillAddonScriptGlobalMessage=Prevent the extension script from running on this page until it next reloads

StopScriptButton=Stop script
DebugScriptButton=Debug script
WaitForScriptButton=Continue
DontAskAgain=&Don’t ask me again
WindowCloseByScriptBlockedWarning=Scripts may only close windows that were opened by a script.
OnBeforeUnloadTitle=Are you sure?
OnBeforeUnloadMessage2=This page is asking you to confirm that you want to leave — information you’ve entered may not be saved.
OnBeforeUnloadStayButton=Stay on page
OnBeforeUnloadLeaveButton=Leave page
OnBeforeUnloadPDFjsTitle=Save PDF before leaving?
OnBeforeUnloadPDFjsMessage=Save this document to avoid losing your changes.
EmptyGetElementByIdParam=Empty string passed to getElementById().
SpeculationFailed2=An unbalanced tree was written using document.write() causing data from the network to be reparsed. More information: https://developer.mozilla.org/en-US/docs/Glossary/speculative_parsing
DocumentWriteIgnored=A call to document.write() from an asynchronously-loaded external script was ignored.
EditorFileDropFailed=Dropping a file into a contenteditable element failed: %S.
FormValidationTextTooLong=Please shorten this text to %S characters or less (you are currently using %S characters).
FormValidationTextTooShort=Please use at least %S characters (you are currently using %S characters).
FormValidationValueMissing=Please fill out this field.
FormValidationCheckboxMissing=Please check this box if you want to proceed.
FormValidationRadioMissing=Please select one of these options.
FormValidationFileMissing=Please select a file.
FormValidationSelectMissing=Please select an item in the list.
FormValidationInvalidEmail=Please enter an email address.
FormValidationInvalidURL=Please enter a URL.
FormValidationInvalidDate=Please enter a valid date.
FormValidationInvalidTime=Please enter a valid time.
FormValidationInvalidDateTime=Please enter valid date and time.
FormValidationInvalidDateMonth=Please enter a valid month.
FormValidationInvalidDateWeek=Please enter a valid week.
FormValidationPatternMismatch=Please match the requested format.
FormValidationPatternMismatchWithTitle=Please match the requested format: %S.
FormValidationNumberRangeOverflow=Please select a value that is no more than %S.
FormValidationDateTimeRangeOverflow=Please select a value that is no later than %S.
FormValidationNumberRangeUnderflow=Please select a value that is no less than %S.
FormValidationDateTimeRangeUnderflow=Please select a value that is no earlier than %S.
FormValidationStepMismatch=Please select a valid value. The two nearest valid values are %S and %S.
FormValidationStepMismatchOneValue=Please select a valid value. The nearest valid value is %S.
FormValidationTimeReversedRangeUnderflowAndOverflow=Please select a value between %1$S and %2$S.
FormValidationBadInputNumber=Please enter a number.
FullscreenDeniedDisabled=Request for fullscreen was denied because Fullscreen API is disabled by user preference.
FullscreenDeniedHidden=Request for fullscreen was denied because the document is no longer visible.
FullscreenDeniedHTMLDialog=Request for fullscreen was denied because requesting element is a <dialog> element.
FullscreenDeniedContainerNotAllowed=Request for fullscreen was denied because at least one of the document’s containing elements is not an iframe or does not have an “allowfullscreen” attribute.
FullscreenDeniedNotInputDriven=Request for fullscreen was denied because Element.requestFullscreen() was not called from inside a short running user-generated event handler.
FullscreenDeniedMouseEventOnlyLeftBtn=Request for fullscreen was denied because Element.requestFullscreen() was called from inside a mouse event handler not triggered by left mouse button.
FullscreenDeniedNotHTMLSVGOrMathML=Request for fullscreen was denied because requesting element is not <svg>, <math>, or an HTML element.
FullscreenDeniedNotInDocument=Request for fullscreen was denied because requesting element is no longer in its document.
FullscreenDeniedMovedDocument=Request for fullscreen was denied because requesting element has moved document.
FullscreenDeniedLostWindow=Request for fullscreen was denied because we no longer have a window.
FullscreenDeniedPopoverOpen=Request for fullscreen was denied because the element is already open as a popover.
FullscreenDeniedSubDocFullscreen=Request for fullscreen was denied because a subdocument of the document requesting fullscreen is already fullscreen.
FullscreenDeniedNotFocusedTab=Request for fullscreen was denied because requesting element is not in the currently focused tab.
FullscreenDeniedFeaturePolicy=Request for fullscreen was denied because of FeaturePolicy directives.
FullscreenExitWindowFocus=Exited fullscreen because a window was focused.
RemovedFullscreenElement=Exited fullscreen because fullscreen element was removed from document.
PointerLockDeniedDisabled=Request for pointer lock was denied because Pointer Lock API is disabled by user preference.
PointerLockDeniedInUse=Request for pointer lock was denied because the pointer is currently controlled by a different document.
PointerLockDeniedNotInDocument=Request for pointer lock was denied because the requesting element is not in a document.
PointerLockDeniedSandboxed=Request for pointer lock was denied because Pointer Lock API is restricted via sandbox.
PointerLockDeniedHidden=Request for pointer lock was denied because the document is not visible.
PointerLockDeniedNotFocused=Request for pointer lock was denied because the document is not focused.
PointerLockDeniedMovedDocument=Request for pointer lock was denied because the requesting element has moved document.
PointerLockDeniedNotInputDriven=Request for pointer lock was denied because Element.requestPointerLock() was not called from inside a short running user-generated event handler, and the document is not in full screen.
PointerLockDeniedFailedToLock=Request for pointer lock was denied because the browser failed to lock the pointer.
HTMLSyncXHRWarning=HTML parsing in XMLHttpRequest is not supported in the synchronous mode.
ForbiddenHeaderWarning=Attempt to set a forbidden header was denied: %S
ResponseTypeSyncXHRWarning=Use of XMLHttpRequest’s responseType attribute is no longer supported in the synchronous mode in window context.
TimeoutSyncXHRWarning=Use of XMLHttpRequest’s timeout attribute is not supported in the synchronous mode in window context.
UseSendBeaconDuringUnloadAndPagehideWarning=Use of navigator.sendBeacon instead of synchronous XMLHttpRequest during unload and pagehide improves user experience.
JSONCharsetWarning=An attempt was made to declare a non-UTF-8 encoding for JSON retrieved using XMLHttpRequest. Only UTF-8 is supported for decoding JSON.
MediaElementAudioSourceNodeCrossOrigin=The HTMLMediaElement passed to createMediaElementSource has a cross-origin resource, the node will output silence.
MediaStreamAudioSourceNodeCrossOrigin=The MediaStream passed to createMediaStreamSource has a cross-origin resource, the node will output silence.
MediaStreamTrackAudioSourceNodeCrossOrigin=The MediaStreamTrack passed to createMediaStreamTrackSource is a cross-origin resource, the node will output silence.
MediaElementAudioCaptureOfMediaStreamError=The captured HTMLMediaElement is playing a MediaStream. Applying volume or mute status is not currently supported.
MediaElementStreamCaptureCycle=The MediaStream assigned to srcObject comes from a capture of this HTMLMediaElement, forming a cycle, assignment ignored.
MediaLoadExhaustedCandidates=All candidate resources failed to load. Media load paused.
MediaLoadSourceMissingSrc=<source> element has no “src” attribute. Media resource load failed.
MediaStreamAudioSourceNodeDifferentRate=Connecting AudioNodes from AudioContexts with different sample-rate is currently not supported.
MediaLoadHttpError=HTTP load failed with status %1$S. Load of media resource %2$S failed.
MediaLoadInvalidURI=Invalid URI. Load of media resource %S failed.
MediaLoadUnsupportedTypeAttribute=Specified “type” attribute of “%1$S” is not supported. Load of media resource %2$S failed.
MediaLoadUnsupportedTypeAttributeLoadingNextChild=Specified “type” attribute of “%1$S” is not supported. Load of media resource %2$S failed. Trying to load from next <source> element.
MediaLoadUnsupportedMimeType=HTTP “Content-Type” of “%1$S” is not supported. Load of media resource %2$S failed.
MediaLoadDecodeError=Media resource %S could not be decoded.
MediaWidevineNoWMF=Trying to play Widevine with no Windows Media Foundation. See https://support.mozilla.org/kb/fix-video-audio-problems-firefox-windows
MediaWMFNeeded=To play video formats %S, you need to install extra Microsoft software, see https://support.mozilla.org/kb/fix-video-audio-problems-firefox-windows
MediaPlatformDecoderNotFound=The video on this page can’t be played. Your system may not have the required video codecs for: %S
MediaUnsupportedLibavcodec=The video on this page can’t be played. Your system has an unsupported version of libavcodec
MediaDecodeError=Media resource %1$S could not be decoded, error: %2$S
MediaDecodeWarning=Media resource %1$S could be decoded, but with error: %2$S
MediaCannotPlayNoDecoders=Cannot play media. No decoders for requested formats: %S
MediaNoDecoders=No decoders for some of the requested formats: %S
MediaCannotInitializePulseAudio=Unable to use PulseAudio
MediaEMEInsecureContextDeprecatedWarning=Using Encrypted Media Extensions at %S on an insecure (i.e. non-HTTPS) context is deprecated and will soon be removed. You should consider switching to a secure origin such as HTTPS.
MediaEMENoCapabilitiesDeprecatedWarning=Calling navigator.requestMediaKeySystemAccess() (at %S) without passing a candidate MediaKeySystemConfiguration containing audioCapabilities or videoCapabilities is deprecated and will soon become unsupported.
MediaEMENoCodecsDeprecatedWarning=Calling navigator.requestMediaKeySystemAccess() (at %S) passing a candidate MediaKeySystemConfiguration containing audioCapabilities or videoCapabilities without a contentType with a “codecs” string is deprecated and will soon become unsupported.

DOMAttrModifiedWarning=Adding a listener for DOMAttrModified is deprecated and will be removed soon. Instead of a MutationEvent, use MutationObserver. https://developer.mozilla.org/docs/Web/API/MutationObserver
DOMCharacterDataModifiedWarning=Adding a listener for DOMCharacterDataModified is deprecated and will be removed soon. Instead of a MutationEvent, use MutationObserver. https://developer.mozilla.org/docs/Web/API/MutationObserver
DOMNodeInsertedWarning=Adding a listener for DOMNodeInserted is deprecated and will be removed soon. Instead of a MutationEvent, use MutationObserver. https://developer.mozilla.org/docs/Web/API/MutationObserver
DOMNodeInsertedIntoDocumentWarning=Adding a listener for DOMNodeInsertedIntoDocument is deprecated and will be removed soon. Instead of a MutationEvent, use MutationObserver. https://developer.mozilla.org/docs/Web/API/MutationObserver
DOMNodeRemovedWarning=Adding a listener for DOMNodeRemoved is deprecated and will be removed soon. Instead of a MutationEvent, use MutationObserver. https://developer.mozilla.org/docs/Web/API/MutationObserver
DOMNodeRemovedFromDocumentWarning=Adding a listener for DOMNodeRemovedFromDocument is deprecated and will be removed soon. Instead of a MutationEvent, use MutationObserver. https://developer.mozilla.org/docs/Web/API/MutationObserver
DOMSubtreeModifiedWarning=Adding a listener for DOMSubtreeModified is deprecated and will be removed soon. Instead of a MutationEvent, use MutationObserver. https://developer.mozilla.org/docs/Web/API/MutationObserver

BlockAutoplayError=Autoplay is only allowed when approved by the user, the site is activated by the user, or media is muted.
BlockAutoplayWebAudioStartError=An AudioContext was prevented from starting automatically. It must be created or resumed after a user gesture on the page.
ComponentsWarning=The Components object is deprecated. It will soon be removed.
PluginHangUITitle=Warning: Unresponsive plugin
PluginHangUIMessage=%S may be busy, or it may have stopped responding. You can stop the plugin now, or you can continue to see if the plugin will complete.
PluginHangUIWaitButton=Continue
PluginHangUIStopButton=Stop plugin
NodeIteratorDetachWarning=Calling detach() on a NodeIterator no longer has an effect.
LenientThisWarning=Ignoring get or set of property that has [LenientThis] because the “this” object is incorrect.
UseOfCaptureEventsWarning=Use of captureEvents() is deprecated. To upgrade your code, use the DOM 2 addEventListener() method. For more help http://developer.mozilla.org/en/docs/DOM:element.addEventListener
UseOfReleaseEventsWarning=Use of releaseEvents() is deprecated. To upgrade your code, use the DOM 2 removeEventListener() method. For more help http://developer.mozilla.org/en/docs/DOM:element.removeEventListener
SyncXMLHttpRequestDeprecatedWarning=Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user’s experience. For more help https://xhr.spec.whatwg.org/#sync-warning
Window_Cc_ontrollersWarning=window.controllers/Controllers is deprecated. Do not use it for UA detection.
ImportXULIntoContentWarning=Importing XUL nodes into a content document is deprecated. This functionality may be removed soon.
IndexedDBTransactionAbortNavigation=An IndexedDB transaction that was not yet complete has been aborted due to page navigation.
IgnoringWillChangeOverBudgetWarning=Will-change memory consumption is too high. Budget limit is the document surface area multiplied by %1$S (%2$S px). Occurrences of will-change over the budget will be ignored.
HittingMaxWorkersPerDomain2=A Worker could not be started immediately because other documents in the same origin are already using the maximum number of workers. The Worker is now queued and will be started after some of the other workers have completed.
AppCacheWarning=The Application Cache API (AppCache) is deprecated and will be removed at a future date.  Please consider using ServiceWorker for offline support.
EmptyWorkerSourceWarning=Attempting to create a Worker from an empty source. This is probably unintentional.
NavigatorGetUserMediaWarning=navigator.mozGetUserMedia has been replaced by navigator.mediaDevices.getUserMedia
RTCPeerConnectionGetStreamsWarning=RTCPeerConnection.getLocalStreams/getRemoteStreams are deprecated. Use RTCPeerConnection.getSenders/getReceivers instead.
InterceptionFailedWithURL=Failed to load ‘%S’. A ServiceWorker intercepted the request and encountered an unexpected error.
CorsResponseForSameOriginRequest=Failed to load ‘%1$S’ by responding ‘%2$S’. A ServiceWorker is not allowed to synthesize a cors Response for a same-origin Request.
BadOpaqueInterceptionRequestModeWithURL=Failed to load ‘%1$S’. A ServiceWorker passed an opaque Response to FetchEvent.respondWith() while handling a ‘%2$S’ FetchEvent. Opaque Response objects are only valid when the RequestMode is ‘no-cors’.
InterceptedErrorResponseWithURL=Failed to load ‘%S’. A ServiceWorker passed an Error Response to FetchEvent.respondWith(). This typically means the ServiceWorker performed an invalid fetch() call.
InterceptedUsedResponseWithURL=Failed to load ‘%S’. A ServiceWorker passed a used Response to FetchEvent.respondWith(). The body of a Response may only be read once. Use Response.clone() to access the body multiple times.
BadOpaqueRedirectInterceptionWithURL=Failed to load ‘%S’. A ServiceWorker passed an opaqueredirect Response to FetchEvent.respondWith() while handling a non-navigation FetchEvent.
BadRedirectModeInterceptionWithURL=Failed to load ‘%S’. A ServiceWorker passed a redirected Response to FetchEvent.respondWith() while RedirectMode is not ‘follow’.
InterceptionCanceledWithURL=Failed to load ‘%S’. A ServiceWorker canceled the load by calling FetchEvent.preventDefault().
InterceptionRejectedResponseWithURL=Failed to load ‘%1$S’. A ServiceWorker passed a promise to FetchEvent.respondWith() that rejected with ‘%2$S’.
InterceptedNonResponseWithURL=Failed to load ‘%1$S’. A ServiceWorker passed a promise to FetchEvent.respondWith() that resolved with non-Response value ‘%2$S’.

ServiceWorkerScopePathMismatch=Failed to register a ServiceWorker: The path of the provided scope ‘%1$S’ is not under the max scope allowed ‘%2$S’. Adjust the scope, move the Service Worker script, or use the Service-Worker-Allowed HTTP header to allow the scope.
ServiceWorkerRegisterNetworkError=Failed to register/update a ServiceWorker for scope ‘%1$S’: Load failed with status %2$S for script ‘%3$S’.
ServiceWorkerRegisterMimeTypeError2=Failed to register/update a ServiceWorker for scope ‘%1$S’: Bad Content-Type of ‘%2$S’ received for script ‘%3$S’.  Must be a JavaScript MIME type.
ServiceWorkerRegisterStorageError=Failed to register/update a ServiceWorker for scope ‘%S’: Storage access is restricted in this context due to user settings or private browsing mode.
ServiceWorkerGetRegistrationStorageError=Failed to get service worker registration(s): Storage access is restricted in this context due to user settings or private browsing mode.
ServiceWorkerGetClientStorageError=Failed to get service worker’s client(s): Storage access is restricted in this context due to user settings or private browsing mode.
ServiceWorkerPostMessageStorageError=The ServiceWorker for scope ‘%S’ failed to execute ‘postMessage‘ because storage access is restricted in this context due to user settings or private browsing mode.
ServiceWorkerGraceTimeoutTermination=Terminating ServiceWorker for scope ‘%1$S’ with pending waitUntil/respondWith promises because of grace timeout.
ServiceWorkerNoFetchHandler=Fetch event handlers must be added during the worker script’s initial evaluation.
ExecCommandCutCopyDeniedNotInputDriven=document.execCommand(‘cut’/‘copy’) was denied because it was not called from inside a short running user-generated event handler.
ManifestIdIsInvalid=The id member did not resolve to a valid URL.
ManifestIdNotSameOrigin=The id member must have the same origin as the start_url member.
ManifestShouldBeObject=Manifest should be an object.
ManifestScopeURLInvalid=The scope URL is invalid.
ManifestScopeNotSameOrigin=The scope URL must be same origin as document.
ManifestStartURLOutsideScope=The start URL is outside the scope, so the scope is invalid.
ManifestStartURLInvalid=The start URL is invalid.
ManifestStartURLShouldBeSameOrigin=The start URL must be same origin as document.
ManifestInvalidType=Expected the %1$S’s %2$S member to be a %3$S.
ManifestInvalidCSSColor=%1$S: %2$S is not a valid CSS color.
ManifestLangIsInvalid=%1$S: %2$S is not a valid language code.
ManifestImageURLIsInvalid=%1$S item at index %2$S is invalid. The %3$S member is an invalid URL %4$S
ManifestImageUnusable=%1$S item at index %2$S lacks a usable purpose. It will be ignored.
ManifestImageUnsupportedPurposes=%1$S item at index %2$S includes unsupported purpose(s): %3$S.
ManifestImageRepeatedPurposes=%1$S item at index %2$S includes repeated purpose(s): %3$S.
PatternAttributeCompileFailurev2=Unable to check <input pattern=‘%1$S’> because ‘/%1$S/%2$S’ is not a valid regexp: %3$S
TargetPrincipalDoesNotMatch=Failed to execute ‘postMessage’ on ‘DOMWindow’: The target origin provided (‘%S’) does not match the recipient window’s origin (‘%S’).
RewriteYouTubeEmbed=Rewriting old-style YouTube Flash embed (%S) to iframe embed (%S). Please update page to use iframe instead of embed/object, if possible.
RewriteYouTubeEmbedPathParams=Rewriting old-style YouTube Flash embed (%S) to iframe embed (%S). Params were unsupported by iframe embeds and converted. Please update page to use iframe instead of embed/object, if possible.
PushMessageBadEncryptionHeader=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘Encryption’ header must include a unique ‘salt‘ parameter for each message. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-3.1 for more information.
PushMessageBadCryptoKeyHeader=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘Crypto-Key‘ header must include a ‘dh‘ parameter containing the app server’s public key. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-4 for more information.
PushMessageBadEncryptionKeyHeader=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘Encryption-Key’ header must include a ‘dh‘ parameter. This header is deprecated and will soon be removed. Please use ‘Crypto-Key‘ with ‘Content-Encoding: aesgcm‘ instead. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-4 for more information.
PushMessageBadEncodingHeader=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘Content-Encoding‘ header must be ‘aesgcm‘. ‘aesgcm128‘ is allowed, but deprecated and will soon be removed. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-2 for more information.
PushMessageBadSenderKey=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘dh‘ parameter in the ‘Crypto-Key‘ header must be the app server’s Diffie-Hellman public key, base64url-encoded (https://tools.ietf.org/html/rfc7515#appendix-C) and in “uncompressed” or “raw” form (65 bytes before encoding). See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-4 for more information.
PushMessageBadSalt=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘salt‘ parameter in the ‘Encryption‘ header must be base64url-encoded (https://tools.ietf.org/html/rfc7515#appendix-C), and be at least 16 bytes before encoding. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-3.1 for more information.
PushMessageBadRecordSize=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. The ‘rs‘ parameter of the ‘Encryption‘ header must be between %2$S and 2^36-31, or omitted entirely. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-3.1 for more information.
PushMessageBadPaddingError=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. A record in the encrypted message was not padded correctly. See https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-02#section-2 for more information.
PushMessageBadCryptoError=The ServiceWorker for scope ‘%1$S’ failed to decrypt a push message. For help with encryption, please see https://developer.mozilla.org/docs/Web/API/Push_API/Using_the_Push_API#Encryption
PreventDefaultFromPassiveListenerWarning=Ignoring ‘preventDefault()’ call on event of type ‘%1$S’ from a listener registered as ‘passive’.
ImageBitmapRenderingContext_TransferImageBitmapWarning=ImageBitmapRenderingContext.transferImageBitmap is deprecated and will be removed soon. Use ImageBitmapRenderingContext.transferFromImageBitmap instead.
IIRFilterChannelCountChangeWarning=IIRFilterNode channel count changes may produce audio glitches.
BiquadFilterChannelCountChangeWarning=BiquadFilterNode channel count changes may produce audio glitches.
GenericImageNamePNG=image.png
GenericFileName=file
GeolocationInsecureRequestIsForbidden=A Geolocation request can only be fulfilled in a secure context.
NotificationsInsecureRequestIsForbidden=The Notification permission may only be requested in a secure context.
NotificationsCrossOriginIframeRequestIsForbidden=The Notification permission may only be requested in a top-level document or same-origin iframe.
NotificationsRequireUserGesture=The Notification permission may only be requested from inside a short running user-generated event handler.
NotificationsRequireUserGestureDeprecationWarning=Requesting Notification permission outside a short running user-generated event handler is deprecated and will not be supported in the future.
WindowContentUntrustedWarning=The ‘content’ attribute of Window objects is deprecated.  Please use ‘window.top’ instead.

SVGRefLoopWarning=The SVG <%S> with ID “%S” has a reference loop.
SVGRefChainLengthExceededWarning=An SVG <%S> reference chain which is too long was abandoned at the element with ID “%S”.
SVGDeselectAllWarning=SVGSVGElement.deselectAll is deprecated as it duplicates functionality from the Selection API.
SVGNearestViewportElementWarning=SVGGraphicsElement.nearestViewportElement is deprecated and will be removed at a future date. Use SVGElement.viewportElement instead.
SVGFarthestViewportElementWarning=SVGGraphicsElement.farthestViewportElement is deprecated and will be removed at a future date.

ScriptSourceEmpty=‘%S’ attribute of <script> element is empty.
ScriptSourceInvalidUri=‘%S’ attribute of <script> element is not a valid URI: “%S”
ScriptSourceLoadFailed=Loading failed for the <script> with source “%S”.
ModuleSourceLoadFailed=Loading failed for the module with source “%S”.
ScriptSourceMalformed=<script> source URI is malformed: “%S”.
ModuleSourceMalformed=Module source URI is malformed: “%S”.
ScriptSourceNotAllowed=<script> source URI is not allowed in this document: “%S”.
ModuleSourceNotAllowed=Module source URI is not allowed in this document: “%S”.
WebExtContentScriptModuleSourceNotAllowed=WebExtension content scripts may only load modules with moz-extension URLs and not: “%S”.
ModuleResolveFailureNoWarn=Error resolving module specifier “%S”.
ModuleResolveFailureWarnRelative=Error resolving module specifier “%S”. Relative module specifiers must start with “./”, “../” or “/”.
IDBObjectStoreCreateIndexLocaleWarning=The ‘locale’ option for IDBObjectStore.createIndex() is deprecated.
ImportMapInvalidTopLevelKey=An invalid top-level key “%S” was present in the import map.
ImportMapEmptySpecifierKeys=Specifier keys cannot be empty strings.
ImportMapAddressesNotStrings=Addresses need to be strings.
ImportMapInvalidAddress=Address “%S” was invalid.
ImportMapAddressNotEndsWithSlash=An invalid address was given for the specifier key “%1$S”; since “%1$S” ended in a slash, the address “%2$S” needs to as well.
ImportMapScopePrefixNotParseable=The scope prefix URL “%S” was not parseable.
ImportMapResolutionBlockedByNullEntry=Resolution of specifier “%S” was blocked by a null entry.
ImportMapResolutionBlockedByAfterPrefix=Resolution of specifier “%S” was blocked since the substring after prefix could not be parsed as a URL relative to the address in the import map.
ImportMapResolutionBlockedByBacktrackingPrefix=Resolution of specifier “%S” was blocked since the parsed URL does not start with the address in the import map.
ImportMapResolveInvalidBareSpecifierWarnRelative=The specifier “%S” was a bare specifier, but was not remapped to anything. Relative module specifiers must start with “./”, “../” or “/”.
ImportMapExternalNotSupported=External import maps are not supported: <script type='importmap'> with a src attribute is currently not supported.
ImportMapNotAllowedMultiple=Multiple import maps are not allowed.
ImportMapNotAllowedAfterModuleLoad=Import maps are not allowed after a module load or preload has started.
InvalidKeyframePropertyValue=Keyframe property value “%1$S” is invalid according to the syntax for “%2$S”.
ReadableStreamReadingFailed=Failed to read data from the ReadableStream: “%S”.
RegisterProtocolHandlerPrivateBrowsingWarning=Can’t use registerProtocolHandler inside private browsing mode.
MotionEventWarning=Use of the motion sensor is deprecated.
OrientationEventWarning=Use of the orientation sensor is deprecated.
ProximityEventWarning=Use of the proximity sensor is deprecated.
AmbientLightEventWarning=Use of the ambient light sensor is deprecated.
UnsupportedEntryTypesIgnored=Ignoring unsupported entryTypes: %S.
AllEntryTypesIgnored=No valid entryTypes; aborting registration.
GTK2Conflict2=Key event not available on GTK2: key=“%S” modifiers=“%S” id=“%S”
WinConflict2=Key event not available on some keyboard layouts: key=“%S” modifiers=“%S” id=“%S”
DocumentSetDomainNotAllowedWarning=Setting document.domain in a cross-origin isolated environment is not allowed.

DeprecatedTestingInterfaceWarning=TestingDeprecatedInterface is a testing-only interface and this is its testing deprecation message.
DeprecatedTestingMethodWarning=TestingDeprecatedInterface.deprecatedMethod() is a testing-only method and this is its testing deprecation message.
DeprecatedTestingAttributeWarning=TestingDeprecatedInterface.deprecatedAttribute is a testing-only attribute and this is its testing deprecation message.
CreateImageBitmapCanvasRenderingContext2DWarning=Use of CanvasRenderingContext2D in createImageBitmap is deprecated.

DrawWindowCanvasRenderingContext2DWarning=Use of drawWindow method from CanvasRenderingContext2D is deprecated. Use tabs.captureTab extensions API instead https://developer.mozilla.org/docs/Mozilla/Add-ons/WebExtensions/API/tabs/captureTab

MozRequestFullScreenDeprecatedPrefixWarning=mozRequestFullScreen() is deprecated.
MozfullscreenchangeDeprecatedPrefixWarning=onmozfullscreenchange is deprecated.
MozfullscreenerrorDeprecatedPrefixWarning=onmozfullscreenerror is deprecated.
External_AddSearchProviderWarning=AddSearchProvider is deprecated.

MouseEvent_MozPressureWarning=MouseEvent.mozPressure is deprecated. Use PointerEvent.pressure instead.
MozInputSourceWarning=MouseEvent.mozInputSource is deprecated. Use PointerEvent.pointerType instead.
InitMouseEventWarning=initMouseEvent() is deprecated. Use the MouseEvent() constructor instead.
InitNSMouseEventWarning=initNSMouseEvent() is deprecated. Use the MouseEvent() constructor instead.
MathML_DeprecatedMathSpaceValue2Warning=MathML length value “%S” is deprecated and will be removed at a future date.
MathML_DeprecatedMathVariantWarning=“mathvariant='%S'” on MathML elements is deprecated and will be removed at a future date.
MathML_DeprecatedStixgeneralOperatorStretchingWarning=Support for rendering stretched MathML operators with STIXGeneral fonts is deprecated and may be removed at a future date. For details about newer fonts that will continue to be supported, see %S
FormSubmissionUntrustedEventWarning=Form submission via untrusted submit event is deprecated and will be removed at a future date.

SizeToContentWarning=sizeToContent() is deprecated and will be removed in the future.

WebShareAPI_Failed=The share operation has failed.
WebShareAPI_Aborted=The share operation was aborted.
UnknownProtocolNavigationPrevented=Prevented navigation to “%1$S” due to an unknown protocol.
PostMessageSharedMemoryObjectToCrossOriginWarning=Cannot post message containing a shared memory object to a cross-origin window.
UnusedLinkPreloadPending=The resource at “%S” preloaded with link preload was not used within a few seconds. Make sure all attributes of the preload tag are set correctly.

RequestStorageAccessNullPrincipal=document.requestStorageAccess() may not be called on a document with an opaque origin, such as a sandboxed iframe without allow-same-origin in its sandbox attribute.
RequestStorageAccessSandboxed=document.requestStorageAccess() may not be called in a sandboxed iframe without allow-storage-access-by-user-activation in its sandbox attribute.
RequestStorageAccessNested=document.requestStorageAccess() may not be called in a nested iframe.
RequestStorageAccessUserGesture=document.requestStorageAccess() may only be requested from inside a short running user-generated event handler.
RequestStorageAccessPermissionsPolicy=document.requestStorageAccess() may not be called where the storage-access feature is blocked by the Permissions Policy.
RequestStorageAccessNotSecureContext=document.requestStorageAccess() may only grant access to secure contexts.
LocChangeFloodingPrevented=Too many calls to Location or History APIs within a short timeframe.
FolderUploadPrompt.title = Confirm Upload
FolderUploadPrompt.message = Are you sure you want to upload all files from “%S”? Only do this if you trust the site.
FolderUploadPrompt.acceptButtonLabel = Upload
InputPickerBlockedNoUserActivation=<input> picker was blocked due to lack of user activation.
ExternalProtocolFrameBlockedNoUserActivation=Iframe with external protocol was blocked due to lack of user activation, or because not enough time has passed since the last such iframe was loaded.
MultiplePopupsBlockedNoUserActivation=Opening multiple popups was blocked due to lack of user activation.
PreloadIgnoredInvalidAttr=Preload of %S was ignored due to unknown “as” or “type” values, or non-matching “media” attribute.
PartitionKeyDifferentError=Cannot access blob URL “%S” with a different partition key.
ElementSetCaptureWarning=Element.setCapture() is deprecated. Use Element.setPointerCapture() instead. For more help https://developer.mozilla.org/docs/Web/API/Element/setPointerCapture
ElementReleaseCaptureWarning=Element.releaseCapture() is deprecated. Use Element.releasePointerCapture() instead. For more help https://developer.mozilla.org/docs/Web/API/Element/releasePointerCapture
DocumentReleaseCaptureWarning=Document.releaseCapture() is deprecated. Use Element.releasePointerCapture() instead. For more help https://developer.mozilla.org/docs/Web/API/Element/releasePointerCapture

WebExtensionUncheckedLastError=browser.runtime.lastError value was not checked: %S

OffscreenCanvasToBlobWarning=OffscreenCanvas.toBlob() is deprecated. Use OffscreenCanvas.convertToBlob() instead.

InstallTriggerDeprecatedWarning=InstallTrigger is deprecated and will be removed in the future.
InstallTriggerInstallDeprecatedWarning=InstallTrigger.install() is deprecated and will be removed in the future. For more help https://extensionworkshop.com/documentation/publish/self-distribution/

SelectOptionsLengthAssignmentWarning=Refused to expand <select> option list via assignment to HTMLOptionsCollection.length (value %1$S). The maximum supported size is %2$S.

InvalidFormControlUnfocusable=An invalid form control is not focusable.
InvalidNamedFormControlUnfocusable=The invalid form control with name=‘%S’ is not focusable.
PK
!<��P$�;�;modules/SafeBrowsing.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const PREF_DEBUG_ENABLED = "browser.safebrowsing.debug";
let loggingEnabled = false;

// Log only if browser.safebrowsing.debug is true
function log(...stuff) {
  if (!loggingEnabled) {
    return;
  }

  var d = new Date();
  let msg = "SafeBrowsing: " + d.toTimeString() + ": " + stuff.join(" ");
  dump(Services.urlFormatter.trimSensitiveURLs(msg) + "\n");
}

function getLists(prefName) {
  log("getLists: " + prefName);
  let pref = Services.prefs.getCharPref(prefName, "");

  // Splitting an empty string returns [''], we really want an empty array.
  if (!pref) {
    return [];
  }

  return pref.split(",").map(value => value.trim());
}

const FEATURES = [
  {
    name: "phishing",
    list: ["urlclassifier.phishTable"],
    enabled() {
      return Services.prefs.getBoolPref(
        "browser.safebrowsing.phishing.enabled"
      );
    },
    update() {
      return Services.prefs.getBoolPref(
        "browser.safebrowsing.features.phishing.update",
        this.enabled()
      );
    },
  },
  {
    name: "malware",
    list: ["urlclassifier.malwareTable"],
    enabled() {
      return Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled");
    },
    update() {
      return Services.prefs.getBoolPref(
        "browser.safebrowsing.features.malware.update",
        this.enabled()
      );
    },
  },
  {
    name: "blockedURIs",
    list: ["urlclassifier.blockedTable"],
    enabled() {
      return Services.prefs.getBoolPref(
        "browser.safebrowsing.blockedURIs.enabled"
      );
    },
    update() {
      return Services.prefs.getBoolPref(
        "browser.safebrowsing.features.blockedURIs.update",
        this.enabled()
      );
    },
  },
  {
    name: "downloads",
    list: [
      "urlclassifier.downloadBlockTable",
      "urlclassifier.downloadAllowTable",
    ],
    enabled() {
      return (
        Services.prefs.getBoolPref("browser.safebrowsing.downloads.enabled") &&
        Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled")
      );
    },
    update() {
      return Services.prefs.getBoolPref(
        "browser.safebrowsing.features.downloads.update",
        this.enabled()
      );
    },
  },
  {
    name: "trackingAnnotation",
    list: [
      "urlclassifier.trackingAnnotationTable",
      "urlclassifier.trackingAnnotationWhitelistTable",
    ],
    enabled() {
      return Services.prefs.getBoolPref(
        "privacy.trackingprotection.annotate_channels"
      );
    },
    update() {
      return Services.prefs.getBoolPref(
        "browser.safebrowsing.features.trackingAnnotation.update",
        this.enabled()
      );
    },
  },
  {
    name: "trackingProtection",
    list: [
      "urlclassifier.trackingTable",
      "urlclassifier.trackingWhitelistTable",
    ],
    enabled() {
      return (
        Services.prefs.getBoolPref("privacy.trackingprotection.enabled") ||
        Services.prefs.getBoolPref("privacy.trackingprotection.pbmode.enabled")
      );
    },
    update() {
      return Services.prefs.getBoolPref(
        "browser.safebrowsing.features.trackingProtection.update",
        this.enabled()
      );
    },
  },
  {
    name: "fingerprinting-annotation",
    list: [
      "urlclassifier.features.fingerprinting.annotate.blacklistTables",
      "urlclassifier.features.fingerprinting.annotate.whitelistTables",
    ],
    enabled() {
      // Annotation features are enabled by default.
      return true;
    },
    update() {
      return Services.prefs.getBoolPref(
        "browser.safebrowsing.features.fingerprinting.annotate.update",
        this.enabled()
      );
    },
  },
  {
    name: "fingerprinting-protection",
    list: [
      "urlclassifier.features.fingerprinting.blacklistTables",
      "urlclassifier.features.fingerprinting.whitelistTables",
    ],
    enabled() {
      return Services.prefs.getBoolPref(
        "privacy.trackingprotection.fingerprinting.enabled",
        false
      );
    },
    update() {
      return Services.prefs.getBoolPref(
        "browser.safebrowsing.features.fingerprinting.update",
        this.enabled()
      );
    },
  },
  {
    name: "cryptomining-annotation",
    list: [
      "urlclassifier.features.cryptomining.annotate.blacklistTables",
      "urlclassifier.features.cryptomining.annotate.whitelistTables",
    ],
    enabled() {
      // Annotation features are enabled by default.
      return true;
    },
    update() {
      return Services.prefs.getBoolPref(
        "browser.safebrowsing.features.cryptomining.annotate.update",
        this.enabled()
      );
    },
  },
  {
    name: "cryptomining-protection",
    list: [
      "urlclassifier.features.cryptomining.blacklistTables",
      "urlclassifier.features.cryptomining.whitelistTables",
    ],
    enabled() {
      return Services.prefs.getBoolPref(
        "privacy.trackingprotection.cryptomining.enabled",
        false
      );
    },
    update() {
      return Services.prefs.getBoolPref(
        "browser.safebrowsing.features.cryptomining.update",
        this.enabled()
      );
    },
  },
  {
    name: "socialtracking-annotation",
    list: [
      "urlclassifier.features.socialtracking.annotate.blacklistTables",
      "urlclassifier.features.socialtracking.annotate.whitelistTables",
    ],
    enabled() {
      // Annotation features are enabled by default.
      return true;
    },
    update() {
      return Services.prefs.getBoolPref(
        "browser.safebrowsing.features.socialtracking.annotate.update",
        this.enabled()
      );
    },
  },
  {
    name: "socialtracking-protection",
    list: [
      "urlclassifier.features.socialtracking.blacklistTables",
      "urlclassifier.features.socialtracking.whitelistTables",
    ],
    enabled() {
      return Services.prefs.getBoolPref(
        "privacy.trackingprotection.socialtracking.enabled",
        false
      );
    },
    update() {
      return Services.prefs.getBoolPref(
        "browser.safebrowsing.features.socialtracking.update",
        this.enabled()
      );
    },
  },
  {
    name: "emailtracking-protection",
    list: [
      "urlclassifier.features.emailtracking.blocklistTables",
      "urlclassifier.features.emailtracking.allowlistTables",
    ],
    enabled() {
      return (
        Services.prefs.getBoolPref(
          "privacy.trackingprotection.emailtracking.enabled",
          false
        ) ||
        Services.prefs.getBoolPref(
          "privacy.trackingprotection.emailtracking.pbmode.enabled",
          false
        )
      );
    },
    update() {
      return Services.prefs.getBoolPref(
        "browser.safebrowsing.features.emailtracking.update",
        this.enabled()
      );
    },
  },
  {
    name: "emailtracking-data-collection",
    list: [
      "urlclassifier.features.emailtracking.datacollection.blocklistTables",
      "urlclassifier.features.emailtracking.datacollection.allowlistTables",
    ],
    enabled() {
      // Data collection features are enabled by default.
      return true;
    },
    update() {
      return Services.prefs.getBoolPref(
        "browser.safebrowsing.features.emailtracking.datacollection.update",
        this.enabled()
      );
    },
  },
];

export var SafeBrowsing = {
  init() {
    if (this.initialized) {
      log("Already initialized");
      return;
    }

    Services.prefs.addObserver("browser.safebrowsing", this);
    Services.prefs.addObserver("privacy.trackingprotection", this);
    Services.prefs.addObserver("urlclassifier", this);

    this.readPrefs();

    this.controlUpdateChecking();
    this.initialized = true;

    log("init() finished");
  },

  registerTableWithURLs(listname) {
    let listManager = Cc[
      "@mozilla.org/url-classifier/listmanager;1"
    ].getService(Ci.nsIUrlListManager);

    let providerName = this.listToProvider[listname];
    let provider = this.providers[providerName];

    if (!providerName || !provider) {
      log("No provider info found for " + listname);
      log("Check browser.safebrowsing.provider.[google/mozilla].lists");
      return;
    }

    if (!provider.updateURL) {
      log("Invalid update url " + listname);
      return;
    }

    listManager.registerTable(
      listname,
      providerName,
      provider.updateURL,
      provider.gethashURL
    );
  },

  registerTables() {
    this.features.forEach(feature => {
      feature.list.forEach(table => {
        this.registerTableWithURLs(table);
      });
    });
  },

  unregisterTables(obsoleteLists) {
    let listManager = Cc[
      "@mozilla.org/url-classifier/listmanager;1"
    ].getService(Ci.nsIUrlListManager);

    obsoleteLists.forEach(list => {
      list.forEach(table => {
        listManager.unregisterTable(table);
      });
    });
  },

  initialized: false,

  features: [],

  updateURL: null,
  gethashURL: null,
  reportURL: null,

  getReportURL(kind, info) {
    let pref;
    switch (kind) {
      case "Phish":
        pref = "browser.safebrowsing.reportPhishURL";
        break;

      case "PhishMistake":
      case "MalwareMistake":
        pref =
          "browser.safebrowsing.provider." +
          info.provider +
          ".report" +
          kind +
          "URL";
        break;

      default:
        let err =
          "SafeBrowsing getReportURL() called with unknown kind: " + kind;
        console.error(err);
        throw err;
    }

    // The "Phish" reports are about submitting new phishing URLs to Google so
    // they don't have an associated list URL
    if (kind != "Phish" && (!info.list || !info.uri)) {
      return null;
    }

    let reportUrl = Services.urlFormatter.formatURLPref(pref);
    // formatURLPref might return "about:blank" if getting the pref fails
    if (reportUrl == "about:blank") {
      reportUrl = null;
    }

    if (reportUrl) {
      reportUrl += encodeURIComponent(info.uri);
    }
    return reportUrl;
  },

  observe(aSubject, aTopic, aData) {
    // skip nextupdatetime and lastupdatetime
    if (aData.includes("lastupdatetime") || aData.includes("nextupdatetime")) {
      return;
    }

    if (aData == PREF_DEBUG_ENABLED) {
      loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED);
      return;
    }

    this.readPrefs();
  },

  readPrefs() {
    loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED);
    log("reading prefs");

    let obsoleteLists = [];
    // Make a copy of the original lists before we re-read the prefs.
    if (this.initialized) {
      obsoleteLists = this.features.map(feature => {
        return feature.list;
      });
    }

    // Allow to disable all feature updates with a single preference for tests.
    let update = Services.prefs.getBoolPref(
      "browser.safebrowsing.update.enabled",
      true
    );

    this.features = [];
    for (let i = 0; i < FEATURES.length; ++i) {
      this.features[i] = {
        name: FEATURES[i].name,
        list: [],
        enabled: FEATURES[i].enabled(),
        update: FEATURES[i].update() && update,
      };

      FEATURES[i].list.forEach(pref => {
        this.features[i].list.push(...getLists(pref));
      });
    }

    for (let i = 0; i < obsoleteLists.length; ++i) {
      obsoleteLists[i] = obsoleteLists[i].filter(
        list => !this.features[i].list.includes(list)
      );
    }

    this.updateProviderURLs();
    this.registerTables();
    if (obsoleteLists) {
      this.unregisterTables(obsoleteLists);
    }

    // XXX The listManager backend gets confused if this is called before the
    // lists are registered. So only call it here when a pref changes, and not
    // when doing initialization. I expect to refactor this later, so pardon the hack.
    if (this.initialized) {
      this.controlUpdateChecking();
    }
  },

  updateProviderURLs() {
    try {
      var clientID = Services.prefs.getCharPref("browser.safebrowsing.id");
    } catch (e) {
      clientID = Services.appinfo.name;
    }

    log("initializing safe browsing URLs, client id", clientID);

    // Get the different providers
    let branch = Services.prefs.getBranch("browser.safebrowsing.provider.");
    let children = branch.getChildList("");
    this.providers = {};
    this.listToProvider = {};

    for (let child of children) {
      log("Child: " + child);
      let prefComponents = child.split(".");
      let providerName = prefComponents[0];
      this.providers[providerName] = {};
    }

    if (loggingEnabled) {
      let providerStr = "";
      Object.keys(this.providers).forEach(function (provider) {
        if (providerStr === "") {
          providerStr = provider;
        } else {
          providerStr += ", " + provider;
        }
      });
      log("Providers: " + providerStr);
    }

    Object.keys(this.providers).forEach(function (provider) {
      if (provider == "test") {
        return; // skip
      }
      let updateURL = Services.urlFormatter.formatURLPref(
        "browser.safebrowsing.provider." + provider + ".updateURL"
      );
      let gethashURL = Services.urlFormatter.formatURLPref(
        "browser.safebrowsing.provider." + provider + ".gethashURL"
      );
      updateURL = updateURL.replace("SAFEBROWSING_ID", clientID);
      gethashURL = gethashURL.replace("SAFEBROWSING_ID", clientID);

      // Disable updates and gethash if the Google API key is missing.
      let googleSafebrowsingKey = Services.urlFormatter
        .formatURL("%GOOGLE_SAFEBROWSING_API_KEY%")
        .trim();
      if (
        (provider == "google" || provider == "google4") &&
        (!googleSafebrowsingKey ||
          googleSafebrowsingKey == "no-google-safebrowsing-api-key")
      ) {
        log(
          "Missing Google SafeBrowsing API key, clearing updateURL and gethashURL."
        );
        updateURL = "";
        gethashURL = "";
      }

      log("Provider: " + provider + " updateURL=" + updateURL);
      log("Provider: " + provider + " gethashURL=" + gethashURL);

      // Urls used to update DB
      this.providers[provider].updateURL = updateURL;
      this.providers[provider].gethashURL = gethashURL;

      // Get lists this provider manages
      let lists = getLists(
        "browser.safebrowsing.provider." + provider + ".lists"
      );
      if (lists) {
        lists.forEach(function (list) {
          this.listToProvider[list] = provider;
        }, this);
      } else {
        log("Update URL given but no lists managed for provider: " + provider);
      }
    }, this);
  },

  controlUpdateChecking() {
    if (loggingEnabled) {
      this.features.forEach(feature => {
        log("feature " + feature.name + ":");
        log("  enabled:" + feature.enabled);
        log("  update:" + feature.update);
        log("  tables:" + feature.list);
      });
    }

    let listManager = Cc[
      "@mozilla.org/url-classifier/listmanager;1"
    ].getService(Ci.nsIUrlListManager);

    listManager.disableAllUpdates();

    this.features.forEach(feature => {
      if (feature.update) {
        feature.list.forEach(table => {
          listManager.enableUpdate(table);
        });
      }
    });

    listManager.maybeToggleUpdateChecking();
  },
};
PK
!<wQ
�d�d(modules/UrlClassifierListManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

// This is the only implementation of nsIUrlListManager.
// A class that manages lists, namely exception and block lists for
// phishing or malware protection. The ListManager knows how to fetch,
// update, and store lists.
//
// There is a single listmanager for the whole application.
//
// TODO more comprehensive update tests, for example add unittest check
//      that the listmanagers tables are properly written on updates

// Lower and upper limits on the server-provided polling frequency
const minDelayMs = 5 * 60 * 1000;
const maxDelayMs = 24 * 60 * 60 * 1000;
const defaultUpdateIntervalMs = 30 * 60 * 1000;
// The threshold to check if the browser is idle. We will defer the update in
// order to save the power consumption if the browser has been idle for one hour
// because it's likely that the browser will keep idle for a longer period.
const browserIdleThresholdMs = 60 * 60 * 1000;
const PREF_DEBUG_ENABLED = "browser.safebrowsing.debug";
const PREF_TEST_NOTIFICATIONS =
  "browser.safebrowsing.test-notifications.enabled";

let loggingEnabled = false;

// Variables imported from library.
let BindToObject, RequestBackoffV4;

// Log only if browser.safebrowsing.debug is true
function log(...stuff) {
  if (!loggingEnabled) {
    return;
  }

  var d = new Date();
  let msg = "listmanager: " + d.toTimeString() + ": " + stuff.join(" ");
  msg = Services.urlFormatter.trimSensitiveURLs(msg);
  Services.console.logStringMessage(msg);
  dump(msg + "\n");
}

/**
 * A ListManager keeps track of exception and block lists and knows
 * how to update them.
 *
 * @constructor
 */
function PROT_ListManager() {
  loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED);

  log("Initializing list manager");

  // A map of tableNames to objects of type
  // { updateUrl: <updateUrl>, gethashUrl: <gethashUrl> }
  this.tablesData = {};
  // A map of updateUrls to maps of tables requiring updates, e.g.
  // { safebrowsing-update-url: { goog-phish-shavar: true,
  //                              goog-malware-shavar: true }
  this.needsUpdate_ = {};

  // A map of updateUrls to single-use nsITimer. An entry exists if and only if
  // there is at least one table with updates enabled for that url. nsITimers
  // are reset when enabling/disabling updates or on update callbacks (update
  // success, update failure, download error).
  this.updateCheckers_ = {};
  this.requestBackoffs_ = {};

  // This is only used by testcases to ensure SafeBrowsing.sys.mjs is inited
  this.registered = false;

  this.dbService_ = Cc["@mozilla.org/url-classifier/dbservice;1"].getService(
    Ci.nsIUrlClassifierDBService
  );

  this.idleService_ = Cc["@mozilla.org/widget/useridleservice;1"].getService(
    Ci.nsIUserIdleService
  );

  Services.obs.addObserver(this, "quit-application");
  Services.prefs.addObserver(PREF_DEBUG_ENABLED, this);
}

/**
 * Register a new table table
 * @param tableName - the name of the table
 * @param updateUrl - the url for updating the table
 * @param gethashUrl - the url for fetching hash completions
 * @returns true if the table could be created; false otherwise
 */
PROT_ListManager.prototype.registerTable = function (
  tableName,
  providerName,
  updateUrl,
  gethashUrl
) {
  this.registered = true;

  this.tablesData[tableName] = {};
  if (!updateUrl) {
    log("Can't register table " + tableName + " without updateUrl");
    return false;
  }
  log("registering " + tableName + " with " + updateUrl);
  this.tablesData[tableName].updateUrl = updateUrl;
  this.tablesData[tableName].gethashUrl = gethashUrl;
  this.tablesData[tableName].provider = providerName;

  // Keep track of all of our update URLs.
  if (!this.needsUpdate_[updateUrl]) {
    this.needsUpdate_[updateUrl] = {};

    // Using the V4 backoff algorithm for both V2 and V4. See bug 1273398.
    this.requestBackoffs_[updateUrl] = new RequestBackoffV4(
      4 /* num requests */,
      60 * 60 * 1000 /* request time, 60 min */,
      providerName /* used by testcase */
    );
  }
  this.needsUpdate_[updateUrl][tableName] = false;

  return true;
};

/**
 * Unregister a table table from list
 */
PROT_ListManager.prototype.unregisterTable = function (tableName) {
  log("unregistering " + tableName);
  var table = this.tablesData[tableName];
  if (table) {
    if (
      !this.updatesNeeded_(table.updateUrl) &&
      this.updateCheckers_[table.updateUrl]
    ) {
      this.updateCheckers_[table.updateUrl].cancel();
      this.updateCheckers_[table.updateUrl] = null;
    }
    delete this.needsUpdate_[table.updateUrl][tableName];
  }
  delete this.tablesData[tableName];
};

/**
 * Delete all of our data tables which seem to leak otherwise.
 * Remove observers
 */
PROT_ListManager.prototype.shutdown_ = function () {
  this.stopUpdateCheckers();
  for (var name in this.tablesData) {
    delete this.tablesData[name];
  }
  Services.obs.removeObserver(this, "quit-application");
  Services.prefs.removeObserver(PREF_DEBUG_ENABLED, this);
};

/**
 * xpcom-shutdown callback
 */
PROT_ListManager.prototype.observe = function (aSubject, aTopic, aData) {
  switch (aTopic) {
    case "quit-application":
      this.shutdown_();
      break;
    case "nsPref:changed":
      if (aData == PREF_DEBUG_ENABLED) {
        loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED);
      }
      break;
  }
};

PROT_ListManager.prototype.getGethashUrl = function (tableName) {
  if (this.tablesData[tableName] && this.tablesData[tableName].gethashUrl) {
    return this.tablesData[tableName].gethashUrl;
  }
  return "";
};

PROT_ListManager.prototype.getUpdateUrl = function (tableName) {
  if (this.tablesData[tableName] && this.tablesData[tableName].updateUrl) {
    return this.tablesData[tableName].updateUrl;
  }
  return "";
};

/**
 * Enable updates for a single table.
 */
PROT_ListManager.prototype.enableUpdate = function (tableName) {
  var table = this.tablesData[tableName];
  if (table) {
    log("Enabling table updates for " + tableName);
    this.needsUpdate_[table.updateUrl][tableName] = true;
  }
};

PROT_ListManager.prototype.isRegistered = function () {
  return this.registered;
};

/**
 * Returns true if any table associated with the updateUrl requires updates.
 * @param updateUrl - the updateUrl
 */
PROT_ListManager.prototype.updatesNeeded_ = function (updateUrl) {
  let updatesNeeded = false;
  for (var tableName in this.needsUpdate_[updateUrl]) {
    if (this.needsUpdate_[updateUrl][tableName]) {
      updatesNeeded = true;
    }
  }
  return updatesNeeded;
};

/**
 * Disable updates for all tables.
 */
PROT_ListManager.prototype.disableAllUpdates = function () {
  for (const tableName of Object.keys(this.tablesData)) {
    this.disableUpdate(tableName);
  }
};

/**
 * Disables updates for a single table. Avoid this internal function
 * and use disableAllUpdates() instead.
 */
PROT_ListManager.prototype.disableUpdate = function (tableName) {
  var table = this.tablesData[tableName];
  if (table) {
    log("Disabling table updates for " + tableName);
    this.needsUpdate_[table.updateUrl][tableName] = false;
    if (
      !this.updatesNeeded_(table.updateUrl) &&
      this.updateCheckers_[table.updateUrl]
    ) {
      this.updateCheckers_[table.updateUrl].cancel();
      this.updateCheckers_[table.updateUrl] = null;
    }
  }
};

/**
 * Determine if we have some tables that need updating.
 */
PROT_ListManager.prototype.requireTableUpdates = function () {
  for (var name in this.tablesData) {
    // Tables that need updating even if other tables don't require it
    if (this.needsUpdate_[this.tablesData[name].updateUrl][name]) {
      return true;
    }
  }

  return false;
};

/**
 *  Set timer to check update after delay
 */
PROT_ListManager.prototype.setUpdateCheckTimer = function (updateUrl, delay) {
  this.updateCheckers_[updateUrl] = Cc["@mozilla.org/timer;1"].createInstance(
    Ci.nsITimer
  );

  // A helper function to trigger the table update.
  let update = function () {
    if (!this.checkForUpdates(updateUrl)) {
      // Make another attempt later.
      this.setUpdateCheckTimer(updateUrl, defaultUpdateIntervalMs);
    }
  }.bind(this);

  this.updateCheckers_[updateUrl].initWithCallback(
    () => {
      this.updateCheckers_[updateUrl] = null;
      // Check if we are in the idle mode. We will stop the current update and
      // defer it to the next user interaction active if the browser is
      // considered in idle mode.
      if (this.idleService_.idleTime > browserIdleThresholdMs) {
        let observer = function () {
          Services.obs.removeObserver(observer, "user-interaction-active");
          update();
        };

        Services.obs.addObserver(observer, "user-interaction-active");
        return;
      }

      update();
    },
    delay,
    Ci.nsITimer.TYPE_ONE_SHOT
  );
};
/**
 * Acts as a nsIUrlClassifierCallback for getTables.
 */
PROT_ListManager.prototype.kickoffUpdate_ = function () {
  this.startingUpdate_ = false;
  var initialUpdateDelay = 3000;
  // Add a fuzz of 0-1 minutes for both v2 and v4 according to Bug 1305478.
  initialUpdateDelay += Math.floor(Math.random() * (1 * 60 * 1000));

  // If the user has never downloaded tables, do the check now.
  log("needsUpdate: " + JSON.stringify(this.needsUpdate_, undefined, 2));
  for (var updateUrl in this.needsUpdate_) {
    // If we haven't already kicked off updates for this updateUrl, set a
    // non-repeating timer for it. The timer delay will be reset either on
    // updateSuccess to the default update interval, or backed off on
    // downloadError. Don't set the updateChecker unless at least one table has
    // updates enabled.
    if (this.updatesNeeded_(updateUrl) && !this.updateCheckers_[updateUrl]) {
      let provider = null;
      Object.keys(this.tablesData).forEach(function (table) {
        if (this.tablesData[table].updateUrl === updateUrl) {
          let newProvider = this.tablesData[table].provider;
          if (provider) {
            if (newProvider !== provider) {
              log(
                "Multiple tables for the same updateURL have a different provider?!"
              );
            }
          } else {
            provider = newProvider;
          }
        }
      }, this);
      log(
        "Initializing update checker for " +
          updateUrl +
          " provided by " +
          provider
      );

      // Use the initialUpdateDelay + fuzz unless we had previous updates
      // and the server told us when to try again.
      let updateDelay = initialUpdateDelay;
      let nextUpdatePref =
        "browser.safebrowsing.provider." + provider + ".nextupdatetime";
      let nextUpdate = Services.prefs.getCharPref(nextUpdatePref, "");

      if (nextUpdate) {
        updateDelay = Math.min(
          maxDelayMs,
          Math.max(0, nextUpdate - Date.now())
        );
        log("Next update at " + nextUpdate);
      }
      log("Next update " + Math.round(updateDelay / 60000) + "min from now");

      this.setUpdateCheckTimer(updateUrl, updateDelay);
    } else {
      log("No updates needed or already initialized for " + updateUrl);
    }
  }
};

PROT_ListManager.prototype.stopUpdateCheckers = function () {
  log("Stopping updates");
  for (var updateUrl in this.updateCheckers_) {
    if (this.updateCheckers_[updateUrl]) {
      this.updateCheckers_[updateUrl].cancel();
      this.updateCheckers_[updateUrl] = null;
    }
  }
};

/**
 * Determine if we have any tables that require updating.  Different
 * Wardens may call us with new tables that need to be updated.
 */
PROT_ListManager.prototype.maybeToggleUpdateChecking = function () {
  // We update tables if we have some tables that want updates.  If there
  // are no tables that want to be updated - we dont need to check anything.
  if (this.requireTableUpdates()) {
    log("Starting managing lists");

    // Get the list of existing tables from the DBService before making any
    // update requests.
    if (!this.startingUpdate_) {
      this.startingUpdate_ = true;
      // check the current state of tables in the database
      this.kickoffUpdate_();
    }
  } else {
    log("Stopping managing lists (if currently active)");
    this.stopUpdateCheckers(); // Cancel pending updates
  }
};

/**
 * Force updates for the given tables. This API may trigger more than one update
 * if the table lists provided belong to multiple updateurl (multiple provider).
 * Return false when any update is fail due to back-off algorithm.
 */
PROT_ListManager.prototype.forceUpdates = function (tables) {
  log("forceUpdates with " + tables);
  if (!tables) {
    return false;
  }

  let updateUrls = new Set();
  tables.split(",").forEach(table => {
    if (this.tablesData[table]) {
      updateUrls.add(this.tablesData[table].updateUrl);
    }
  });

  let ret = true;

  updateUrls.forEach(url => {
    // Cancel current update timer for the url because we are forcing an update.
    if (this.updateCheckers_[url]) {
      this.updateCheckers_[url].cancel();
      this.updateCheckers_[url] = null;
    }

    // Trigger an update for the given url.
    if (!this.checkForUpdates(url, true)) {
      ret = false;
    }
  });

  return ret;
};

/**
 * Updates our internal tables from the update server
 *
 * @param updateUrl: request updates for tables associated with that url, or
 * for all tables if the url is empty.
 * @param manual: the update is triggered manually
 */
PROT_ListManager.prototype.checkForUpdates = function (
  updateUrl,
  manual = false
) {
  log("checkForUpdates with " + updateUrl);
  // See if we've triggered the request backoff logic.
  if (!updateUrl) {
    return false;
  }

  // Disable SafeBrowsing updates in Safe Mode, but still allow manually
  // triggering an update for debugging.
  if (Services.appinfo.inSafeMode && !manual) {
    log("update is disabled in Safe Mode");
    return false;
  }

  if (lazy.enableTestNotifications) {
    Services.obs.notifyObservers(
      null,
      "safebrowsing-update-attempt",
      updateUrl
    );
  }

  if (
    !this.requestBackoffs_[updateUrl] ||
    !this.requestBackoffs_[updateUrl].canMakeRequest()
  ) {
    log("Can't make update request");
    return false;
  }
  // Grab the current state of the tables from the database
  this.dbService_.getTables(
    BindToObject(this.makeUpdateRequest_, this, updateUrl)
  );
  return true;
};

/**
 * Method that fires the actual HTTP update request.
 * First we reset any tables that have disappeared.
 * @param tableData List of table data already in the database, in the form
 *        tablename;<chunk ranges>\n
 */
PROT_ListManager.prototype.makeUpdateRequest_ = function (
  updateUrl,
  tableData
) {
  log("this.tablesData: " + JSON.stringify(this.tablesData, undefined, 2));
  log("existing chunks: " + tableData + "\n");
  // Disallow blank updateUrls
  if (!updateUrl) {
    return;
  }
  // An object of the form
  // { tableList: comma-separated list of tables to request,
  //   tableNames: map of tables that need updating,
  //   request: list of tables and existing chunk ranges from tableData
  // }
  var streamerMap = {
    tableList: null,
    tableNames: {},
    requestPayload: "",
    isPostRequest: true,
  };

  let useProtobuf = false;
  let onceThru = false;
  for (var tableName in this.tablesData) {
    // Skip tables not matching this update url
    if (this.tablesData[tableName].updateUrl != updateUrl) {
      continue;
    }

    // Check if |updateURL| is for 'proto'. (only v4 uses protobuf for now.)
    // We use the table name 'goog-*-proto' and an additional provider "google4"
    // to describe the v4 settings.
    let isCurTableProto = tableName.endsWith("-proto");
    if (!onceThru) {
      useProtobuf = isCurTableProto;
      onceThru = true;
    } else if (useProtobuf !== isCurTableProto) {
      log(
        'ERROR: Cannot mix "proto" tables with other types ' +
          "within the same provider."
      );
    }

    if (this.needsUpdate_[this.tablesData[tableName].updateUrl][tableName]) {
      streamerMap.tableNames[tableName] = true;
    }
    if (!streamerMap.tableList) {
      streamerMap.tableList = tableName;
    } else {
      streamerMap.tableList += "," + tableName;
    }
  }

  if (useProtobuf) {
    let tableArray = [];
    Object.keys(streamerMap.tableNames).forEach(aTableName => {
      if (streamerMap.tableNames[aTableName]) {
        tableArray.push(aTableName);
      }
    });

    // Build the <tablename, stateBase64> mapping.
    let tableState = {};
    tableData.split("\n").forEach(line => {
      let p = line.indexOf(";");
      if (-1 === p) {
        return;
      }
      let tableName = line.substring(0, p);
      if (tableName in streamerMap.tableNames) {
        let metadata = line.substring(p + 1).split(":");
        let stateBase64 = metadata[0];
        log(tableName + " ==> " + stateBase64);
        tableState[tableName] = stateBase64;
      }
    });

    // The state is a byte stream which server told us from the
    // last table update. The state would be used to do the partial
    // update and the empty string means the table has
    // never been downloaded. See Bug 1287058 for supporting
    // partial update.
    let stateArray = [];
    tableArray.forEach(listName => {
      stateArray.push(tableState[listName] || "");
    });

    log("stateArray: " + stateArray);

    let urlUtils = Cc["@mozilla.org/url-classifier/utils;1"].getService(
      Ci.nsIUrlClassifierUtils
    );

    streamerMap.requestPayload = urlUtils.makeUpdateRequestV4(
      tableArray,
      stateArray
    );
    streamerMap.isPostRequest = false;
  } else {
    // Build the request. For each table already in the database, include the
    // chunk data from the database
    var lines = tableData.split("\n");
    for (var i = 0; i < lines.length; i++) {
      var fields = lines[i].split(";");
      var name = fields[0];
      if (streamerMap.tableNames[name]) {
        streamerMap.requestPayload += lines[i] + "\n";
        delete streamerMap.tableNames[name];
      }
    }
    // For each requested table that didn't have chunk data in the database,
    // request it fresh
    for (let tableName in streamerMap.tableNames) {
      streamerMap.requestPayload += tableName + ";\n";
    }

    streamerMap.isPostRequest = true;
  }

  log("update request: " + JSON.stringify(streamerMap, undefined, 2) + "\n");

  // Don't send an empty request.
  if (streamerMap.requestPayload.length) {
    this.makeUpdateRequestForEntry_(
      updateUrl,
      streamerMap.tableList,
      streamerMap.requestPayload,
      streamerMap.isPostRequest
    );
  } else {
    // We were disabled between kicking off getTables and now.
    log("Not sending empty request");
  }
};

PROT_ListManager.prototype.makeUpdateRequestForEntry_ = function (
  updateUrl,
  tableList,
  requestPayload,
  isPostRequest
) {
  log(
    "makeUpdateRequestForEntry_: requestPayload " +
      requestPayload +
      " update: " +
      updateUrl +
      " tablelist: " +
      tableList +
      "\n"
  );
  var streamer = Cc["@mozilla.org/url-classifier/streamupdater;1"].getService(
    Ci.nsIUrlClassifierStreamUpdater
  );

  this.requestBackoffs_[updateUrl].noteRequest();

  if (
    !streamer.downloadUpdates(
      tableList,
      requestPayload,
      isPostRequest,
      updateUrl,
      BindToObject(this.updateSuccess_, this, tableList, updateUrl),
      BindToObject(this.updateError_, this, tableList, updateUrl),
      BindToObject(this.downloadError_, this, tableList, updateUrl)
    )
  ) {
    // Our alarm gets reset in one of the 3 callbacks.
    log("pending update, queued request until later");
  } else {
    let table = Object.keys(this.tablesData).find(key => {
      return this.tablesData[key].updateUrl === updateUrl;
    });
    let provider = this.tablesData[table].provider;
    Services.obs.notifyObservers(null, "safebrowsing-update-begin", provider);
  }
};

/**
 * Callback function if the update request succeeded.
 * @param waitForUpdate String The number of seconds that the client should
 *        wait before requesting again.
 */
PROT_ListManager.prototype.updateSuccess_ = function (
  tableList,
  updateUrl,
  waitForUpdateSec
) {
  log(
    "update success for " +
      tableList +
      " from " +
      updateUrl +
      ": " +
      waitForUpdateSec +
      "\n"
  );

  // The time unit below are all milliseconds if not specified.

  var delay = 0;
  if (waitForUpdateSec) {
    delay = parseInt(waitForUpdateSec, 10) * 1000;
  }
  // As long as the delay is something sane (5 min to 1 day), update
  // our delay time for requesting updates. We always use a non-repeating
  // timer since the delay is set differently at every callback.
  if (delay > maxDelayMs) {
    log(
      "Ignoring delay from server (too long), waiting " +
        Math.round(maxDelayMs / 60000) +
        "min"
    );
    delay = maxDelayMs;
  } else if (delay < minDelayMs) {
    log(
      "Ignoring delay from server (too short), waiting " +
        Math.round(defaultUpdateIntervalMs / 60000) +
        "min"
    );
    delay = defaultUpdateIntervalMs;
  } else {
    log("Waiting " + Math.round(delay / 60000) + "min");
  }

  this.setUpdateCheckTimer(updateUrl, delay);

  // Let the backoff object know that we completed successfully.
  this.requestBackoffs_[updateUrl].noteServerResponse(200);

  // Set last update time for provider
  // Get the provider for these tables, check for consistency
  let tables = tableList.split(",");
  let provider = null;
  for (let table of tables) {
    let newProvider = this.tablesData[table].provider;
    if (provider) {
      if (newProvider !== provider) {
        log(
          "Multiple tables for the same updateURL have a different provider?!"
        );
      }
    } else {
      provider = newProvider;
    }
  }

  // Store the last update time (needed to know if the table is "fresh")
  // and the next update time (to know when to update next).
  let lastUpdatePref =
    "browser.safebrowsing.provider." + provider + ".lastupdatetime";
  let now = Date.now();
  log("Setting last update of " + provider + " to " + now);
  Services.prefs.setCharPref(lastUpdatePref, now.toString());

  let nextUpdatePref =
    "browser.safebrowsing.provider." + provider + ".nextupdatetime";
  let targetTime = now + delay;
  log(
    "Setting next update of " +
      provider +
      " to " +
      targetTime +
      " (" +
      Math.round(delay / 60000) +
      "min from now)"
  );
  Services.prefs.setCharPref(nextUpdatePref, targetTime.toString());

  Services.obs.notifyObservers(null, "safebrowsing-update-finished", "success");
};

/**
 * Callback function if the update request succeeded.
 * @param result String The error code of the failure
 */
PROT_ListManager.prototype.updateError_ = function (table, updateUrl, result) {
  log(
    "update error for " + table + " from " + updateUrl + ": " + result + "\n"
  );
  // There was some trouble applying the updates. Don't try again for at least
  // updateInterval milliseconds.
  this.setUpdateCheckTimer(updateUrl, defaultUpdateIntervalMs);

  Services.obs.notifyObservers(
    null,
    "safebrowsing-update-finished",
    "update error: " + result
  );
};

/**
 * Callback function when the download failed
 * @param status String http status or an empty string if connection refused.
 */
PROT_ListManager.prototype.downloadError_ = function (
  table,
  updateUrl,
  status
) {
  log("download error for " + table + ": " + status + "\n");
  // If status is empty, then we assume that we got an NS_CONNECTION_REFUSED
  // error.  In this case, we treat this is a http 500 error.
  if (!status) {
    status = 500;
  }
  status = parseInt(status, 10);
  this.requestBackoffs_[updateUrl].noteServerResponse(status);
  let delay = defaultUpdateIntervalMs;
  if (this.requestBackoffs_[updateUrl].isErrorStatus(status)) {
    // Schedule an update for when our backoff is complete
    delay = this.requestBackoffs_[updateUrl].nextRequestDelay();
  } else {
    log("Got non error status for error callback?!");
  }

  this.setUpdateCheckTimer(updateUrl, delay);

  Services.obs.notifyObservers(
    null,
    "safebrowsing-update-finished",
    "download error: " + status
  );
};

/**
 * Get back-off time for the given provider.
 * Return 0 if we are not in back-off mode.
 */
PROT_ListManager.prototype.getBackOffTime = function (provider) {
  let updateUrl = "";
  for (var table in this.tablesData) {
    if (this.tablesData[table].provider == provider) {
      updateUrl = this.tablesData[table].updateUrl;
      break;
    }
  }

  if (!updateUrl || !this.requestBackoffs_[updateUrl]) {
    return 0;
  }

  let delay = this.requestBackoffs_[updateUrl].nextRequestDelay();
  return delay == 0 ? 0 : Date.now() + delay;
};

PROT_ListManager.prototype.QueryInterface = ChromeUtils.generateQI([
  "nsIUrlListManager",
  "nsIObserver",
  "nsITimerCallback",
]);

let initialized = false;
function Init() {
  if (initialized) {
    return;
  }

  // Pull the library in.
  var jslib =
    Cc["@mozilla.org/url-classifier/jslib;1"].getService().wrappedJSObject;
  BindToObject = jslib.BindToObject;
  RequestBackoffV4 = jslib.RequestBackoffV4;

  initialized = true;
}

export function RegistrationData() {
  Init();
  return new PROT_ListManager();
}

const lazy = {};

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "enableTestNotifications",
  PREF_TEST_NOTIFICATIONS,
  false
);
PK
!<a=|�� modules/UrlClassifierLib.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// We wastefully reload the same JS files across components.  This puts all
// the common JS files used by safebrowsing and url-classifier into a
// single component.

const PREF_DISABLE_TEST_BACKOFF =
  "browser.safebrowsing.provider.test.disableBackoff";

/**
 * Partially applies a function to a particular "this object" and zero or
 * more arguments. The result is a new function with some arguments of the first
 * function pre-filled and the value of |this| "pre-specified".
 *
 * Remaining arguments specified at call-time are appended to the pre-
 * specified ones.
 *
 * Usage:
 * var barMethBound = BindToObject(myFunction, myObj, "arg1", "arg2");
 * barMethBound("arg3", "arg4");
 *
 * @param fn {string} Reference to the function to be bound
 *
 * @param self {object} Specifies the object which |this| should point to
 * when the function is run. If the value is null or undefined, it will default
 * to the global object.
 *
 * @returns {function} A partially-applied form of the speficied function.
 */
export function BindToObject(fn, self) {
  var boundargs = fn.boundArgs_ || [];
  boundargs = boundargs.concat(
    Array.prototype.slice.call(arguments, 2, arguments.length)
  );

  if (fn.boundSelf_) {
    self = fn.boundSelf_;
  }
  if (fn.boundFn_) {
    fn = fn.boundFn_;
  }

  var newfn = function () {
    // Combine the static args and the new args into one big array
    var args = boundargs.concat(Array.prototype.slice.call(arguments));
    return fn.apply(self, args);
  };

  newfn.boundArgs_ = boundargs;
  newfn.boundSelf_ = self;
  newfn.boundFn_ = fn;

  return newfn;
}

// This implements logic for stopping requests if the server starts to return
// too many errors.  If we get MAX_ERRORS errors in ERROR_PERIOD minutes, we
// back off for TIMEOUT_INCREMENT minutes.  If we get another error
// immediately after we restart, we double the timeout and add
// TIMEOUT_INCREMENT minutes, etc.
//
// This is similar to the logic used by the search suggestion service.

// HTTP responses that count as an error.  We also include any 5xx response
// as an error.
const HTTP_FOUND = 302;
const HTTP_SEE_OTHER = 303;
const HTTP_TEMPORARY_REDIRECT = 307;

/**
 * @param maxErrors Number of times to request before backing off.
 * @param retryIncrement Time (ms) for each retry before backing off.
 * @param maxRequests Number the number of requests needed to trigger backoff
 * @param requestPeriod Number time (ms) in which maxRequests have to occur to
 *     trigger the backoff behavior (0 to disable maxRequests)
 * @param timeoutIncrement Number time (ms) the starting timeout period
 *     we double this time for consecutive errors
 * @param maxTimeout Number time (ms) maximum timeout period
 * @param tolerance Checking next request tolerance.
 */
function RequestBackoff(
  maxErrors,
  retryIncrement,
  maxRequests,
  requestPeriod,
  timeoutIncrement,
  maxTimeout,
  tolerance,
  provider = null
) {
  this.MAX_ERRORS_ = maxErrors;
  this.RETRY_INCREMENT_ = retryIncrement;
  this.MAX_REQUESTS_ = maxRequests;
  this.REQUEST_PERIOD_ = requestPeriod;
  this.TIMEOUT_INCREMENT_ = timeoutIncrement;
  this.MAX_TIMEOUT_ = maxTimeout;
  this.TOLERANCE_ = tolerance;

  // Queue of ints keeping the time of all requests
  this.requestTimes_ = [];

  this.numErrors_ = 0;
  this.errorTimeout_ = 0;
  this.nextRequestTime_ = 0;

  // For test provider, we will disable backoff if preference is set to false.
  if (provider === "test") {
    this.canMakeRequestDefault = this.canMakeRequest;
    this.canMakeRequest = function () {
      if (Services.prefs.getBoolPref(PREF_DISABLE_TEST_BACKOFF, true)) {
        return true;
      }
      return this.canMakeRequestDefault();
    };
  }
}

/**
 * Reset the object for reuse. This deliberately doesn't clear requestTimes_.
 */
RequestBackoff.prototype.reset = function () {
  this.numErrors_ = 0;
  this.errorTimeout_ = 0;
  this.nextRequestTime_ = 0;
};

/**
 * Check to see if we can make a request.
 */
RequestBackoff.prototype.canMakeRequest = function () {
  var now = Date.now();
  // Note that nsITimer delay is approximate: the timer can be fired before the
  // requested time has elapsed. So, give it a tolerance
  if (now + this.TOLERANCE_ < this.nextRequestTime_) {
    return false;
  }

  return (
    this.requestTimes_.length < this.MAX_REQUESTS_ ||
    now - this.requestTimes_[0] > this.REQUEST_PERIOD_
  );
};

RequestBackoff.prototype.noteRequest = function () {
  var now = Date.now();
  this.requestTimes_.push(now);

  // We only care about keeping track of MAX_REQUESTS
  if (this.requestTimes_.length > this.MAX_REQUESTS_) {
    this.requestTimes_.shift();
  }
};

RequestBackoff.prototype.nextRequestDelay = function () {
  return Math.max(0, this.nextRequestTime_ - Date.now());
};

/**
 * Notify this object of the last server response.  If it's an error,
 */
RequestBackoff.prototype.noteServerResponse = function (status) {
  if (this.isErrorStatus(status)) {
    this.numErrors_++;

    if (this.numErrors_ < this.MAX_ERRORS_) {
      this.errorTimeout_ = this.RETRY_INCREMENT_;
    } else if (this.numErrors_ == this.MAX_ERRORS_) {
      this.errorTimeout_ = this.TIMEOUT_INCREMENT_;
    } else {
      this.errorTimeout_ *= 2;
    }

    this.errorTimeout_ = Math.min(this.errorTimeout_, this.MAX_TIMEOUT_);
    this.nextRequestTime_ = Date.now() + this.errorTimeout_;
  } else {
    // Reset error timeout, allow requests to go through.
    this.reset();
  }
};

/**
 * We consider 302, 303, 307, 4xx, and 5xx http responses to be errors.
 * @param status Number http status
 * @return Boolean true if we consider this http status an error
 */
RequestBackoff.prototype.isErrorStatus = function (status) {
  return (
    (400 <= status && status <= 599) ||
    HTTP_FOUND == status ||
    HTTP_SEE_OTHER == status ||
    HTTP_TEMPORARY_REDIRECT == status
  );
};

// Wrap a general-purpose |RequestBackoff| to a v4-specific one
// since both listmanager and hashcompleter would use it.
// Note that |maxRequests| and |requestPeriod| is still configurable
// to throttle pending requests.
function RequestBackoffV4(maxRequests, requestPeriod, provider = null) {
  let rand = Math.random();
  let retryInterval = Math.floor(15 * 60 * 1000 * (rand + 1)); // 15 ~ 30 min.
  let backoffInterval = Math.floor(30 * 60 * 1000 * (rand + 1)); // 30 ~ 60 min.

  return new RequestBackoff(
    2 /* max errors */,
    retryInterval /* retry interval, 15~30 min */,
    maxRequests /* num requests */,
    requestPeriod /* request time, 60 min */,
    backoffInterval /* backoff interval, 60 min */,
    24 * 60 * 60 * 1000 /* max backoff, 24hr */,
    1000 /* tolerance of 1 sec */,
    provider /* provider name */
  );
}

export function UrlClassifierLib() {
  this.wrappedJSObject = {
    RequestBackoff,
    RequestBackoffV4,
    BindToObject,
  };
}

UrlClassifierLib.prototype.QueryInterface = ChromeUtils.generateQI([]);
PK
!<j��LL)modules/ContextualIdentityService.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// The maximum valid numeric value for the userContextId.
const MAX_USER_CONTEXT_ID = -1 >>> 0;
const LAST_CONTAINERS_JSON_VERSION = 5;
const SAVE_DELAY_MS = 1500;
const CONTEXTUAL_IDENTITY_ENABLED_PREF = "privacy.userContext.enabled";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "l10n", function () {
  return new Localization(["toolkit/global/contextual-identity.ftl"], true);
});

ChromeUtils.defineLazyGetter(lazy, "gTextDecoder", function () {
  return new TextDecoder();
});

ChromeUtils.defineLazyGetter(lazy, "gTextEncoder", function () {
  return new TextEncoder();
});

ChromeUtils.defineESModuleGetters(lazy, {
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
});

function _TabRemovalObserver(resolver, remoteTabIds) {
  this._resolver = resolver;
  this._remoteTabIds = remoteTabIds;
  Services.obs.addObserver(this, "ipc:browser-destroyed");
}

_TabRemovalObserver.prototype = {
  _resolver: null,
  _remoteTabIds: null,

  QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),

  observe(subject) {
    let remoteTab = subject.QueryInterface(Ci.nsIRemoteTab);
    if (this._remoteTabIds.has(remoteTab.tabId)) {
      this._remoteTabIds.delete(remoteTab.tabId);
      if (this._remoteTabIds.size == 0) {
        Services.obs.removeObserver(this, "ipc:browser-destroyed");
        this._resolver();
      }
    }
  },
};

function _ContextualIdentityService(path) {
  this.init(path);
}

_ContextualIdentityService.prototype = {
  LAST_CONTAINERS_JSON_VERSION,

  _userIdentities: [
    {
      icon: "fingerprint",
      color: "blue",
      l10nId: "user-context-personal",
    },
    {
      icon: "briefcase",
      color: "orange",
      l10nId: "user-context-work",
    },
    {
      icon: "dollar",
      color: "green",
      l10nId: "user-context-banking",
    },
    {
      icon: "cart",
      color: "pink",
      l10nId: "user-context-shopping",
    },
  ],
  _systemIdentities: [
    {
      public: false,
      icon: "",
      color: "",
      name: "userContextIdInternal.thumbnail",
      accessKey: "",
    },
    // This userContextId is used by ExtensionStorageIDB.sys.mjs to create an IndexedDB database
    // opened with the extension principal but not directly accessible to the extension code
    // (do not change the userContextId assigned here, otherwise the installed extensions will
    // not be able to access the data previously stored with the browser.storage.local API).
    {
      userContextId: MAX_USER_CONTEXT_ID,
      public: false,
      icon: "",
      color: "",
      name: "userContextIdInternal.webextStorageLocal",
      accessKey: "",
    },
  ],

  _defaultIdentities: [],

  _identities: null,
  _openedIdentities: new Set(),
  _lastUserContextId: 0,

  _path: null,
  _dataReady: false,

  _saver: null,

  init(path) {
    this._path = path;

    Services.prefs.addObserver(CONTEXTUAL_IDENTITY_ENABLED_PREF, this);

    // Initialize default identities based on policy if available
    this._defaultIdentities = [];
    let userContextId = 1;
    let policyIdentities =
      Services.policies?.getActivePolicies()?.Containers?.Default;
    if (policyIdentities) {
      for (let identity of policyIdentities) {
        identity.public = true;
        identity.userContextId = userContextId;
        userContextId++;
        this._defaultIdentities.push(identity);
      }
    } else {
      for (let identity of this._userIdentities) {
        identity.public = true;
        identity.userContextId = userContextId;
        userContextId++;
        this._defaultIdentities.push(identity);
      }
    }
    for (let identity of this._systemIdentities) {
      if (!("userContextId" in identity)) {
        identity.userContextId = userContextId;
        userContextId++;
      }
      this._defaultIdentities.push(identity);
    }
  },

  async observe(aSubject, aTopic) {
    if (aTopic === "nsPref:changed") {
      const contextualIdentitiesEnabled = Services.prefs.getBoolPref(
        CONTEXTUAL_IDENTITY_ENABLED_PREF
      );
      if (!contextualIdentitiesEnabled) {
        await this.closeContainerTabs();
        this.notifyAllContainersCleared();
        this.resetDefault();
      }
    }
  },

  load() {
    return IOUtils.read(this._path).then(
      bytes => {
        // If synchronous loading happened in the meantime, exit now.
        if (this._dataReady) {
          return;
        }

        try {
          this.parseData(bytes);
        } catch (error) {
          this.loadError(error);
        }
      },
      error => {
        this.loadError(error);
      }
    );
  },

  resetDefault() {
    this._identities = [];

    // Compute the last used context id (excluding the reserved userContextIds
    // "userContextIdInternal.webextStorageLocal" which has UINT32_MAX as its
    // userContextId).
    this._lastUserContextId = this._defaultIdentities
      .filter(identity => identity.userContextId < MAX_USER_CONTEXT_ID)
      .map(identity => identity.userContextId)
      .sort((a, b) => a >= b)
      .pop();

    // Clone the array
    for (let identity of this._defaultIdentities) {
      this._identities.push(Object.assign({}, identity));
    }
    this._openedIdentities = new Set();

    this._dataReady = true;

    // Let's delete all the data of any userContextId. 1 is the first valid
    // userContextId value.
    this.deleteContainerData();

    this.saveSoon();
  },

  loadError(error) {
    if (error != null && error.name != "NotFoundError") {
      // Let's report the error.
      console.error(error);
    }

    // If synchronous loading happened in the meantime, exit now.
    if (this._dataReady) {
      return;
    }

    this.resetDefault();
  },

  saveSoon() {
    if (!this._saver) {
      this._saverCallback = () => this._saver.finalize();

      this._saver = new lazy.DeferredTask(() => this.save(), SAVE_DELAY_MS);
      lazy.AsyncShutdown.profileBeforeChange.addBlocker(
        "ContextualIdentityService: writing data",
        this._saverCallback
      );
    } else {
      this._saver.disarm();
    }

    this._saver.arm();
  },

  save() {
    lazy.AsyncShutdown.profileBeforeChange.removeBlocker(this._saverCallback);

    this._saver = null;
    this._saverCallback = null;

    let object = {
      version: LAST_CONTAINERS_JSON_VERSION,
      lastUserContextId: this._lastUserContextId,
      identities: this._identities,
    };

    let bytes = lazy.gTextEncoder.encode(JSON.stringify(object));
    return IOUtils.write(this._path, bytes, {
      tmpPath: this._path + ".tmp",
    });
  },

  create(name, icon, color) {
    this.ensureDataReady();

    // Retrieve the next userContextId available.
    let userContextId = ++this._lastUserContextId;

    // Throw an error if the next userContextId available is invalid (the one associated to
    // MAX_USER_CONTEXT_ID is already reserved to "userContextIdInternal.webextStorageLocal", which
    // is also the last valid userContextId available, because its userContextId is equal to UINT32_MAX).
    if (userContextId >= MAX_USER_CONTEXT_ID) {
      throw new Error(
        `Unable to create a new userContext with id '${userContextId}'`
      );
    }

    if (!name.trim()) {
      throw new Error(
        "Contextual identity names cannot contain only whitespace."
      );
    }

    let identity = {
      userContextId,
      public: true,
      icon,
      color,
      name,
    };

    this._identities.push(identity);
    this.saveSoon();
    Services.obs.notifyObservers(
      this.getIdentityObserverOutput(identity),
      "contextual-identity-created"
    );

    return Cu.cloneInto(identity, {});
  },

  update(userContextId, name, icon, color) {
    this.ensureDataReady();

    let identity = this._identities.find(
      identity => identity.userContextId == userContextId && identity.public
    );

    if (!name.trim()) {
      throw new Error(
        "Contextual identity names cannot contain only whitespace."
      );
    }

    if (identity && name) {
      identity.name = name;
      identity.color = color;
      identity.icon = icon;
      delete identity.l10nId;

      this.saveSoon();
      Services.obs.notifyObservers(
        this.getIdentityObserverOutput(identity),
        "contextual-identity-updated"
      );
    }

    return !!identity;
  },

  move(userContextIds, position) {
    this.ensureDataReady();

    if (position < -1) {
      return false;
    }
    let movedIdentities = this._identities.filter(
      identity =>
        identity.public && userContextIds.includes(identity.userContextId)
    );

    if (movedIdentities.length) {
      if (position === -1) {
        position = this._identities.length;
      }
      // Shift position by preceding non-public identities
      position = this._identities.reduce(
        (dest, identity, idx) =>
          dest + (!identity.public && dest >= idx ? 1 : 0),
        position
      );
      this._identities = this._identities.filter(
        identity =>
          !identity.public || !userContextIds.includes(identity.userContextId)
      );
      this._identities.splice(position, 0, ...movedIdentities);
      this.saveSoon();
    }

    return !!movedIdentities.length;
  },

  remove(userContextId) {
    this.ensureDataReady();

    let index = this._identities.findIndex(
      i => i.userContextId == userContextId && i.public
    );
    if (index == -1) {
      return false;
    }

    Services.clearData.deleteDataFromOriginAttributesPattern({ userContextId });

    let deletedOutput = this.getIdentityObserverOutput(
      this.getPublicIdentityFromId(userContextId)
    );
    this._identities.splice(index, 1);
    this._openedIdentities.delete(userContextId);
    this.saveSoon();
    Services.obs.notifyObservers(deletedOutput, "contextual-identity-deleted");

    return true;
  },

  getIdentityObserverOutput(identity) {
    let wrappedJSObject = {
      name: this.getUserContextLabel(identity.userContextId),
      icon: identity.icon,
      color: identity.color,
      userContextId: identity.userContextId,
    };

    return { wrappedJSObject };
  },

  parseData(bytes) {
    let data = JSON.parse(lazy.gTextDecoder.decode(bytes));
    if (data.version == 1) {
      this.resetDefault();
      return;
    }

    let saveNeeded = false;

    if (data.version == 2) {
      data = this.migrate2to3(data);
      saveNeeded = true;
    }

    if (data.version == 3) {
      data = this.migrate3to4(data);
      saveNeeded = true;
    }

    if (data.version == 4) {
      data = this.migrate4to5(data);
      saveNeeded = true;
    }

    if (data.version != LAST_CONTAINERS_JSON_VERSION) {
      dump(
        "ERROR - ContextualIdentityService - Unknown version found in " +
          this._path +
          "\n"
      );
      this.loadError(null);
      return;
    }

    this._identities = data.identities;
    this._lastUserContextId = data.lastUserContextId;

    // If we had a migration, let's force the saving of the file.
    if (saveNeeded) {
      this.saveSoon();
    }

    this._dataReady = true;
  },

  ensureDataReady() {
    if (this._dataReady) {
      return;
    }

    try {
      // This reads the file and automatically detects the UTF-8 encoding.
      let inputStream = Cc[
        "@mozilla.org/network/file-input-stream;1"
      ].createInstance(Ci.nsIFileInputStream);
      inputStream.init(
        new lazy.FileUtils.File(this._path),
        lazy.FileUtils.MODE_RDONLY,
        lazy.FileUtils.PERMS_FILE,
        0
      );
      try {
        let bytes = lazy.NetUtil.readInputStream(
          inputStream,
          inputStream.available()
        );
        this.parseData(bytes);
      } finally {
        inputStream.close();
      }
    } catch (error) {
      this.loadError(error);
    }
  },

  getPublicUserContextIds() {
    return this._identities
      .filter(identity => identity.public)
      .map(identity => identity.userContextId);
  },

  getPrivateUserContextIds() {
    return this._identities
      .filter(identity => !identity.public)
      .map(identity => identity.userContextId);
  },

  getPublicIdentities() {
    this.ensureDataReady();
    return Cu.cloneInto(
      this._identities.filter(info => info.public),
      {}
    );
  },

  getPrivateIdentity(name) {
    this.ensureDataReady();
    return Cu.cloneInto(
      this._identities.find(info => !info.public && info.name == name),
      {}
    );
  },

  // getDefaultPrivateIdentity is similar to getPrivateIdentity but it only looks in the
  // default identities (e.g. it is used in the data migration methods to retrieve a new default
  // private identity and add it to the containers data stored on file).
  getDefaultPrivateIdentity(name) {
    return Cu.cloneInto(
      this._defaultIdentities.find(info => !info.public && info.name == name),
      {}
    );
  },

  getPublicIdentityFromId(userContextId) {
    this.ensureDataReady();
    return Cu.cloneInto(
      this._identities.find(
        info => info.userContextId == userContextId && info.public
      ),
      {}
    );
  },

  formatContextLabel(l10nId) {
    const [msg] = lazy.l10n.formatMessagesSync([l10nId]);
    for (let attr of msg.attributes) {
      if (attr.name === "label") {
        return attr.value;
      }
    }
    return "";
  },

  getUserContextLabel(userContextId) {
    let identity = this.getPublicIdentityFromId(userContextId);

    // We cannot localize the user-created identity names.
    if (identity?.name) {
      return identity.name;
    }

    if (identity?.l10nId) {
      return this.formatContextLabel(identity.l10nId);
    }

    return "";
  },

  setTabStyle(tab) {
    if (!tab.hasAttribute("usercontextid")) {
      return;
    }

    let userContextId = tab.getAttribute("usercontextid");
    let identity = this.getPublicIdentityFromId(userContextId);

    let prefix = "identity-color-";
    /* Remove the existing container color highlight if it exists */
    for (let className of tab.classList) {
      if (className.startsWith(prefix)) {
        tab.classList.remove(className);
      }
    }
    if (identity && identity.color) {
      tab.classList.add(prefix + identity.color);
    }
  },

  countContainerTabs(userContextId = 0) {
    let count = 0;
    this._forEachContainerTab(function () {
      ++count;
    }, userContextId);
    return count;
  },

  closeContainerTabs(userContextId = 0, removeTabOptions = {}) {
    return new Promise(resolve => {
      let remoteTabIds = new Set();
      this._forEachContainerTab((tab, tabbrowser) => {
        let frameLoader = tab.linkedBrowser.frameLoader;

        // We don't have remoteTab in non-e10s mode.
        if (frameLoader.remoteTab) {
          remoteTabIds.add(frameLoader.remoteTab.tabId);
        }

        tabbrowser.removeTab(tab, removeTabOptions);
      }, userContextId);

      if (remoteTabIds.size == 0) {
        resolve();
        return;
      }

      new _TabRemovalObserver(resolve, remoteTabIds);
    });
  },

  notifyAllContainersCleared() {
    for (let identity of this._identities) {
      // Don't clear the data related to private identities (e.g. the one used internally
      // for the thumbnails and the one used for the storage.local IndexedDB backend).
      if (!identity.public) {
        continue;
      }
      Services.clearData.deleteDataFromOriginAttributesPattern({
        userContextId: identity.userContextId,
      });
    }
  },

  _forEachContainerTab(callback, userContextId = 0) {
    for (let win of Services.wm.getEnumerator("navigator:browser")) {
      if (win.closed || !win.gBrowser) {
        continue;
      }

      let tabbrowser = win.gBrowser;
      for (let i = tabbrowser.tabs.length - 1; i >= 0; --i) {
        let tab = tabbrowser.tabs[i];
        if (
          tab.hasAttribute("usercontextid") &&
          (!userContextId ||
            parseInt(tab.getAttribute("usercontextid"), 10) == userContextId)
        ) {
          callback(tab, tabbrowser);
        }
      }
    }
  },

  createNewInstanceForTesting(path) {
    return new _ContextualIdentityService(path);
  },

  deleteContainerData() {
    // The userContextId 0 is reserved to the default firefox identity,
    // and it should not be clear when we delete the public containers data.
    let minUserContextId = 1;

    // Collect the userContextId related to the identities that should not be cleared
    // (the ones marked as `public = false`).
    const keepDataContextIds = this.getPrivateUserContextIds();

    // Collect the userContextIds currently used by any stored cookie.
    let cookiesUserContextIds = new Set();

    for (let cookie of Services.cookies.cookies) {
      // Skip any userContextIds that should not be cleared.
      if (
        cookie.originAttributes.userContextId >= minUserContextId &&
        !keepDataContextIds.includes(cookie.originAttributes.userContextId)
      ) {
        cookiesUserContextIds.add(cookie.originAttributes.userContextId);
      }
    }

    for (let userContextId of cookiesUserContextIds) {
      Services.clearData.deleteDataFromOriginAttributesPattern({
        userContextId,
      });
    }
  },

  migrate2to3(data) {
    // migrating from 2 to 3 is basically just increasing the version id.
    // This migration was needed for bug 1419591. See bug 1419591 to know more.
    data.version = 3;

    return data;
  },

  migrate3to4(data) {
    // Migrating from 3 to 4 is:
    // - adding the reserver userContextId used by the webextension storage.local API
    // - add the keepData property to all the existent identities
    // - increasing the version id.
    //
    // This migration was needed for Bug 1406181. See bug 1406181 for rationale.
    const webextStorageLocalIdentity = this.getDefaultPrivateIdentity(
      "userContextIdInternal.webextStorageLocal"
    );

    data.identities.push(webextStorageLocalIdentity);

    data.version = 4;

    return data;
  },

  migrate4to5(data) {
    // Migrating from 4 to 5 is:
    // - replacing the StringBundle l10nID with a Fluent l10nId for default identities
    // - dropping the accessKey property
    // - increasing the version id.
    //
    // This migration was needed for Bug 1814969. See bug 1814969 for rationale.

    for (let identity of data.identities) {
      switch (identity.l10nID) {
        case "userContextPersonal.label":
          identity.l10nId = "user-context-personal";
          break;
        case "userContextWork.label":
          identity.l10nId = "user-context-work";
          break;
        case "userContextBanking.label":
          identity.l10nId = "user-context-banking";
          break;
        case "userContextShopping.label":
          identity.l10nId = "user-context-shopping";
          break;
      }
      delete identity.l10nID;
      delete identity.accessKey;
    }

    data.version = 5;

    return data;
  },
};

let path = PathUtils.join(
  Services.dirsvc.get("ProfD", Ci.nsIFile).path,
  "containers.json"
);
export var ContextualIdentityService = new _ContextualIdentityService(path);
PK
!<E�"<"<modules/PushComponents.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file exports XPCOM components for C++ and chrome JavaScript callers to
 * interact with the Push service.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

var isParent =
  Services.appinfo.processType === Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;

const lazy = {};

// The default Push service implementation.
ChromeUtils.defineLazyGetter(lazy, "PushService", function () {
  if (Services.prefs.getBoolPref("dom.push.enabled")) {
    const { PushService } = ChromeUtils.importESModule(
      "resource://gre/modules/PushService.sys.mjs"
    );
    PushService.init();
    return PushService;
  }

  throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
});

// Observer notification topics for push messages and subscription status
// changes. These are duplicated and used in `nsIPushNotifier`. They're exposed
// on `nsIPushService` so that JS callers only need to import this service.
const OBSERVER_TOPIC_PUSH = "push-message";
const OBSERVER_TOPIC_SUBSCRIPTION_CHANGE = "push-subscription-change";
const OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED = "push-subscription-modified";

/**
 * `PushServiceBase`, `PushServiceParent`, and `PushServiceContent` collectively
 * implement the `nsIPushService` interface. This interface provides calls
 * similar to the Push DOM API, but does not require service workers.
 *
 * Push service methods may be called from the parent or content process. The
 * parent process implementation loads `PushService.sys.mjs` at app startup, and
 * calls its methods directly. The content implementation forwards calls to
 * the parent Push service via IPC.
 *
 * The implementations share a class and contract ID.
 */
function PushServiceBase() {
  this.wrappedJSObject = this;
  this._addListeners();
}

PushServiceBase.prototype = {
  classID: Components.ID("{daaa8d73-677e-4233-8acd-2c404bd01658}"),
  contractID: "@mozilla.org/push/Service;1",
  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
    "nsIPushService",
    "nsIPushQuotaManager",
    "nsIPushErrorReporter",
  ]),

  pushTopic: OBSERVER_TOPIC_PUSH,
  subscriptionChangeTopic: OBSERVER_TOPIC_SUBSCRIPTION_CHANGE,
  subscriptionModifiedTopic: OBSERVER_TOPIC_SUBSCRIPTION_MODIFIED,

  ensureReady() {},

  _addListeners() {
    for (let message of this._messages) {
      this._mm.addMessageListener(message, this);
    }
  },

  _isValidMessage(message) {
    return this._messages.includes(message.name);
  },

  observe(subject, topic) {
    if (topic === "android-push-service") {
      // Load PushService immediately.
      this.ensureReady();
    }
  },

  _deliverSubscription(request, props) {
    if (!props) {
      request.onPushSubscription(Cr.NS_OK, null);
      return;
    }
    request.onPushSubscription(Cr.NS_OK, new PushSubscription(props));
  },

  _deliverSubscriptionError(request, error) {
    let result =
      typeof error.result == "number" ? error.result : Cr.NS_ERROR_FAILURE;
    request.onPushSubscription(result, null);
  },
};

/**
 * The parent process implementation of `nsIPushService`. This version loads
 * `PushService.sys.mjs` at startup and calls its methods directly. It also
 * receives and responds to requests from the content process.
 */
let parentInstance;
function PushServiceParent() {
  if (parentInstance) {
    return parentInstance;
  }
  parentInstance = this;

  PushServiceBase.call(this);
}

PushServiceParent.prototype = Object.create(PushServiceBase.prototype);

XPCOMUtils.defineLazyServiceGetter(
  PushServiceParent.prototype,
  "_mm",
  "@mozilla.org/parentprocessmessagemanager;1",
  "nsISupports"
);

Object.assign(PushServiceParent.prototype, {
  _messages: [
    "Push:Register",
    "Push:Registration",
    "Push:Unregister",
    "Push:Clear",
    "Push:NotificationForOriginShown",
    "Push:NotificationForOriginClosed",
    "Push:ReportError",
  ],

  // nsIPushService methods

  subscribe(scope, principal, callback) {
    this.subscribeWithKey(scope, principal, [], callback);
  },

  subscribeWithKey(scope, principal, key, callback) {
    this._handleRequest("Push:Register", principal, {
      scope,
      appServerKey: key,
    })
      .then(
        result => {
          this._deliverSubscription(callback, result);
        },
        error => {
          this._deliverSubscriptionError(callback, error);
        }
      )
      .catch(console.error);
  },

  unsubscribe(scope, principal, callback) {
    this._handleRequest("Push:Unregister", principal, {
      scope,
    })
      .then(
        result => {
          callback.onUnsubscribe(Cr.NS_OK, result);
        },
        () => {
          callback.onUnsubscribe(Cr.NS_ERROR_FAILURE, false);
        }
      )
      .catch(console.error);
  },

  getSubscription(scope, principal, callback) {
    return this._handleRequest("Push:Registration", principal, {
      scope,
    })
      .then(
        result => {
          this._deliverSubscription(callback, result);
        },
        error => {
          this._deliverSubscriptionError(callback, error);
        }
      )
      .catch(console.error);
  },

  clearForDomain(domain, callback) {
    return this._handleRequest("Push:Clear", null, {
      domain,
    })
      .then(
        () => {
          callback.onClear(Cr.NS_OK);
        },
        () => {
          callback.onClear(Cr.NS_ERROR_FAILURE);
        }
      )
      .catch(console.error);
  },

  // nsIPushQuotaManager methods

  notificationForOriginShown(origin) {
    this.service.notificationForOriginShown(origin);
  },

  notificationForOriginClosed(origin) {
    this.service.notificationForOriginClosed(origin);
  },

  // nsIPushErrorReporter methods

  reportDeliveryError(messageId, reason) {
    this.service.reportDeliveryError(messageId, reason);
  },

  receiveMessage(message) {
    if (!this._isValidMessage(message)) {
      return;
    }
    let { name, target, data } = message;
    if (name === "Push:NotificationForOriginShown") {
      this.notificationForOriginShown(data);
      return;
    }
    if (name === "Push:NotificationForOriginClosed") {
      this.notificationForOriginClosed(data);
      return;
    }
    if (name === "Push:ReportError") {
      this.reportDeliveryError(data.messageId, data.reason);
      return;
    }
    this._handleRequest(name, data.principal, data)
      .then(
        result => {
          target.sendAsyncMessage(this._getResponseName(name, "OK"), {
            requestID: data.requestID,
            result,
          });
        },
        error => {
          target.sendAsyncMessage(this._getResponseName(name, "KO"), {
            requestID: data.requestID,
            result: error.result,
          });
        }
      )
      .catch(console.error);
  },

  ensureReady() {
    this.service.init();
  },

  _toPageRecord(principal, data) {
    if (!data.scope) {
      throw new Error("Invalid page record: missing scope");
    }
    if (!principal) {
      throw new Error("Invalid page record: missing principal");
    }
    if (principal.isNullPrincipal || principal.isExpandedPrincipal) {
      throw new Error("Invalid page record: unsupported principal");
    }

    // System subscriptions can only be created by chrome callers, and are
    // exempt from the background message quota and permission checks. They
    // also do not fire service worker events.
    data.systemRecord = principal.isSystemPrincipal;

    data.originAttributes = ChromeUtils.originAttributesToSuffix(
      principal.originAttributes
    );

    return data;
  },

  async _handleRequest(name, principal, data) {
    if (name == "Push:Clear") {
      return this.service.clear(data);
    }

    let pageRecord;
    try {
      pageRecord = this._toPageRecord(principal, data);
    } catch (e) {
      return Promise.reject(e);
    }

    if (name === "Push:Register") {
      return this.service.register(pageRecord);
    }
    if (name === "Push:Registration") {
      return this.service.registration(pageRecord);
    }
    if (name === "Push:Unregister") {
      return this.service.unregister(pageRecord);
    }

    return Promise.reject(new Error("Invalid request: unknown name"));
  },

  _getResponseName(requestName, suffix) {
    let name = requestName.slice("Push:".length);
    return "PushService:" + name + ":" + suffix;
  },

  // Methods used for mocking in tests.

  replaceServiceBackend(options) {
    return this.service.changeTestServer(options.serverURI, options);
  },

  restoreServiceBackend() {
    var defaultServerURL = Services.prefs.getCharPref("dom.push.serverURL");
    return this.service.changeTestServer(defaultServerURL);
  },
});

// Used to replace the implementation with a mock.
Object.defineProperty(PushServiceParent.prototype, "service", {
  get() {
    return this._service || lazy.PushService;
  },
  set(impl) {
    this._service = impl;
  },
});

let contentInstance;
/**
 * The content process implementation of `nsIPushService`. This version
 * uses the child message manager to forward calls to the parent process.
 * The parent Push service instance handles the request, and responds with a
 * message containing the result.
 */
function PushServiceContent() {
  if (contentInstance) {
    return contentInstance;
  }
  contentInstance = this;

  PushServiceBase.apply(this, arguments);
  this._requests = new Map();
  this._requestId = 0;
}

PushServiceContent.prototype = Object.create(PushServiceBase.prototype);

XPCOMUtils.defineLazyServiceGetter(
  PushServiceContent.prototype,
  "_mm",
  "@mozilla.org/childprocessmessagemanager;1",
  "nsISupports"
);

Object.assign(PushServiceContent.prototype, {
  _messages: [
    "PushService:Register:OK",
    "PushService:Register:KO",
    "PushService:Registration:OK",
    "PushService:Registration:KO",
    "PushService:Unregister:OK",
    "PushService:Unregister:KO",
    "PushService:Clear:OK",
    "PushService:Clear:KO",
  ],

  // nsIPushService methods

  subscribe(scope, principal, callback) {
    this.subscribeWithKey(scope, principal, [], callback);
  },

  subscribeWithKey(scope, principal, key, callback) {
    let requestID = this._addRequest(callback);
    this._mm.sendAsyncMessage("Push:Register", {
      scope,
      appServerKey: key,
      requestID,
      principal,
    });
  },

  unsubscribe(scope, principal, callback) {
    let requestID = this._addRequest(callback);
    this._mm.sendAsyncMessage("Push:Unregister", {
      scope,
      requestID,
      principal,
    });
  },

  getSubscription(scope, principal, callback) {
    let requestID = this._addRequest(callback);
    this._mm.sendAsyncMessage("Push:Registration", {
      scope,
      requestID,
      principal,
    });
  },

  clearForDomain(domain, callback) {
    let requestID = this._addRequest(callback);
    this._mm.sendAsyncMessage("Push:Clear", {
      domain,
      requestID,
    });
  },

  // nsIPushQuotaManager methods

  notificationForOriginShown(origin) {
    this._mm.sendAsyncMessage("Push:NotificationForOriginShown", origin);
  },

  notificationForOriginClosed(origin) {
    this._mm.sendAsyncMessage("Push:NotificationForOriginClosed", origin);
  },

  // nsIPushErrorReporter methods

  reportDeliveryError(messageId, reason) {
    this._mm.sendAsyncMessage("Push:ReportError", {
      messageId,
      reason,
    });
  },

  _addRequest(data) {
    let id = ++this._requestId;
    this._requests.set(id, data);
    return id;
  },

  _takeRequest(requestId) {
    let d = this._requests.get(requestId);
    this._requests.delete(requestId);
    return d;
  },

  receiveMessage(message) {
    if (!this._isValidMessage(message)) {
      return;
    }
    let { name, data } = message;
    let request = this._takeRequest(data.requestID);

    if (!request) {
      return;
    }

    switch (name) {
      case "PushService:Register:OK":
      case "PushService:Registration:OK":
        this._deliverSubscription(request, data.result);
        break;

      case "PushService:Register:KO":
      case "PushService:Registration:KO":
        this._deliverSubscriptionError(request, data);
        break;

      case "PushService:Unregister:OK":
        if (typeof data.result === "boolean") {
          request.onUnsubscribe(Cr.NS_OK, data.result);
        } else {
          request.onUnsubscribe(Cr.NS_ERROR_FAILURE, false);
        }
        break;

      case "PushService:Unregister:KO":
        request.onUnsubscribe(Cr.NS_ERROR_FAILURE, false);
        break;

      case "PushService:Clear:OK":
        request.onClear(Cr.NS_OK);
        break;

      case "PushService:Clear:KO":
        request.onClear(Cr.NS_ERROR_FAILURE);
        break;

      default:
        break;
    }
  },
});

/** `PushSubscription` instances are passed to all subscription callbacks. */
function PushSubscription(props) {
  this._props = props;
}

PushSubscription.prototype = {
  QueryInterface: ChromeUtils.generateQI(["nsIPushSubscription"]),

  /** The URL for sending messages to this subscription. */
  get endpoint() {
    return this._props.endpoint;
  },

  /** The last time a message was sent to this subscription. */
  get lastPush() {
    return this._props.lastPush;
  },

  /** The total number of messages sent to this subscription. */
  get pushCount() {
    return this._props.pushCount;
  },

  /** The number of remaining background messages that can be sent to this
   * subscription, or -1 of the subscription is exempt from the quota.
   */
  get quota() {
    return this._props.quota;
  },

  /**
   * Indicates whether this subscription was created with the system principal.
   * System subscriptions are exempt from the background message quota and
   * permission checks.
   */
  get isSystemSubscription() {
    return !!this._props.systemRecord;
  },

  /** The private key used to decrypt incoming push messages, in JWK format */
  get p256dhPrivateKey() {
    return this._props.p256dhPrivateKey;
  },

  /**
   * Indicates whether this subscription is subject to the background message
   * quota.
   */
  quotaApplies() {
    return this.quota >= 0;
  },

  /**
   * Indicates whether this subscription exceeded the background message quota,
   * or the user revoked the notification permission. The caller must request a
   * new subscription to continue receiving push messages.
   */
  isExpired() {
    return this.quota === 0;
  },

  /**
   * Returns a key for encrypting messages sent to this subscription. JS
   * callers receive the key buffer as a return value, while C++ callers
   * receive the key size and buffer as out parameters.
   */
  getKey(name) {
    switch (name) {
      case "p256dh":
        return this._getRawKey(this._props.p256dhKey);

      case "auth":
        return this._getRawKey(this._props.authenticationSecret);

      case "appServer":
        return this._getRawKey(this._props.appServerKey);
    }
    return [];
  },

  _getRawKey(key) {
    if (!key) {
      return [];
    }
    return new Uint8Array(key);
  },
};

// Export the correct implementation depending on whether we're running in
// the parent or content process.
export let Service = isParent ? PushServiceParent : PushServiceContent;
PK
!<���Y�Y�modules/PushService.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

export var PushServiceWebSocket;
export var PushServiceHttp2;

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gPushNotifier",
  "@mozilla.org/push/Notifier;1",
  "nsIPushNotifier"
);
ChromeUtils.defineESModuleGetters(lazy, {
  PushCrypto: "resource://gre/modules/PushCrypto.sys.mjs",
  pushBroadcastService: "resource://gre/modules/PushBroadcastService.sys.mjs",
});

const CONNECTION_PROTOCOLS = (function () {
  if ("android" != AppConstants.MOZ_WIDGET_TOOLKIT) {
    ({ PushServiceWebSocket } = ChromeUtils.importESModule(
      "resource://gre/modules/PushServiceWebSocket.sys.mjs"
    ));
    ({ PushServiceHttp2 } = ChromeUtils.importESModule(
      "resource://gre/modules/PushServiceHttp2.sys.mjs"
    ));
    return [PushServiceWebSocket, PushServiceHttp2];
  }
  return [];
})();

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  let { ConsoleAPI } = ChromeUtils.importESModule(
    "resource://gre/modules/Console.sys.mjs"
  );
  return new ConsoleAPI({
    maxLogLevelPref: "dom.push.loglevel",
    prefix: "PushService",
  });
});

const prefs = Services.prefs.getBranch("dom.push.");

const PUSH_SERVICE_UNINIT = 0;
const PUSH_SERVICE_INIT = 1; // No serverURI
const PUSH_SERVICE_ACTIVATING = 2; // activating db
const PUSH_SERVICE_CONNECTION_DISABLE = 3;
const PUSH_SERVICE_ACTIVE_OFFLINE = 4;
const PUSH_SERVICE_RUNNING = 5;

/**
 * State is change only in couple of functions:
 *   init - change state to PUSH_SERVICE_INIT if state was PUSH_SERVICE_UNINIT
 *   changeServerURL - change state to PUSH_SERVICE_ACTIVATING if serverURL
 *                     present or PUSH_SERVICE_INIT if not present.
 *   changeStateConnectionEnabledEvent - it is call on pref change or during
 *                                       the service activation and it can
 *                                       change state to
 *                                       PUSH_SERVICE_CONNECTION_DISABLE
 *   changeStateOfflineEvent - it is called when offline state changes or during
 *                             the service activation and it change state to
 *                             PUSH_SERVICE_ACTIVE_OFFLINE or
 *                             PUSH_SERVICE_RUNNING.
 *   uninit - change state to PUSH_SERVICE_UNINIT.
 **/

// This is for starting and stopping service.
const STARTING_SERVICE_EVENT = 0;
const CHANGING_SERVICE_EVENT = 1;
const STOPPING_SERVICE_EVENT = 2;
const UNINIT_EVENT = 3;

// Returns the backend for the given server URI.
function getServiceForServerURI(uri) {
  // Insecure server URLs are allowed for development and testing.
  let allowInsecure = prefs.getBoolPref(
    "testing.allowInsecureServerURL",
    false
  );
  if (AppConstants.MOZ_WIDGET_TOOLKIT == "android") {
    if (uri.scheme == "https" || (allowInsecure && uri.scheme == "http")) {
      return CONNECTION_PROTOCOLS;
    }
    return null;
  }
  if (uri.scheme == "wss" || (allowInsecure && uri.scheme == "ws")) {
    return PushServiceWebSocket;
  }
  if (uri.scheme == "https" || (allowInsecure && uri.scheme == "http")) {
    return PushServiceHttp2;
  }
  return null;
}

/**
 * Annotates an error with an XPCOM result code. We use this helper
 * instead of `Components.Exception` because the latter can assert in
 * `nsXPCComponents_Exception::HasInstance` when inspected at shutdown.
 */
function errorWithResult(message, result = Cr.NS_ERROR_FAILURE) {
  let error = new Error(message);
  error.result = result;
  return error;
}

/**
 * The implementation of the push system. It uses WebSockets
 * (PushServiceWebSocket) to communicate with the server and PushDB (IndexedDB)
 * for persistence.
 */
export var PushService = {
  _service: null,
  _state: PUSH_SERVICE_UNINIT,
  _db: null,
  _options: null,
  _visibleNotifications: new Map(),

  // Callback that is called after attempting to
  // reduce the quota for a record. Used for testing purposes.
  _updateQuotaTestCallback: null,

  // Set of timeout ID of tasks to reduce quota.
  _updateQuotaTimeouts: new Set(),

  // When serverURI changes (this is used for testing), db is cleaned up and a
  // a new db is started. This events must be sequential.
  _stateChangeProcessQueue: null,
  _stateChangeProcessEnqueue(op) {
    if (!this._stateChangeProcessQueue) {
      this._stateChangeProcessQueue = Promise.resolve();
    }

    this._stateChangeProcessQueue = this._stateChangeProcessQueue
      .then(op)
      .catch(error => {
        lazy.console.error(
          "stateChangeProcessEnqueue: Error transitioning state",
          error
        );
        return this._shutdownService();
      })
      .catch(error => {
        lazy.console.error(
          "stateChangeProcessEnqueue: Error shutting down service",
          error
        );
      });
    return this._stateChangeProcessQueue;
  },

  // Pending request. If a worker try to register for the same scope again, do
  // not send a new registration request. Therefore we need queue of pending
  // register requests. This is the list of scopes which pending registration.
  _pendingRegisterRequest: {},
  _notifyActivated: null,
  _activated: null,
  _checkActivated() {
    if (this._state < PUSH_SERVICE_ACTIVATING) {
      return Promise.reject(new Error("Push service not active"));
    }
    if (this._state > PUSH_SERVICE_ACTIVATING) {
      return Promise.resolve();
    }
    if (!this._activated) {
      this._activated = new Promise((resolve, reject) => {
        this._notifyActivated = { resolve, reject };
      });
    }
    return this._activated;
  },

  _makePendingKey(aPageRecord) {
    return aPageRecord.scope + "|" + aPageRecord.originAttributes;
  },

  _lookupOrPutPendingRequest(aPageRecord) {
    let key = this._makePendingKey(aPageRecord);
    if (this._pendingRegisterRequest[key]) {
      return this._pendingRegisterRequest[key];
    }

    return (this._pendingRegisterRequest[key] =
      this._registerWithServer(aPageRecord));
  },

  _deletePendingRequest(aPageRecord) {
    let key = this._makePendingKey(aPageRecord);
    if (this._pendingRegisterRequest[key]) {
      delete this._pendingRegisterRequest[key];
    }
  },

  _setState(aNewState) {
    lazy.console.debug(
      "setState()",
      "new state",
      aNewState,
      "old state",
      this._state
    );

    if (this._state == aNewState) {
      return;
    }

    if (this._state == PUSH_SERVICE_ACTIVATING) {
      // It is not important what is the new state as soon as we leave
      // PUSH_SERVICE_ACTIVATING
      if (this._notifyActivated) {
        if (aNewState < PUSH_SERVICE_ACTIVATING) {
          this._notifyActivated.reject(new Error("Push service not active"));
        } else {
          this._notifyActivated.resolve();
        }
      }
      this._notifyActivated = null;
      this._activated = null;
    }
    this._state = aNewState;
  },

  async _changeStateOfflineEvent(offline, calledFromConnEnabledEvent) {
    lazy.console.debug("changeStateOfflineEvent()", offline);

    if (
      this._state < PUSH_SERVICE_ACTIVE_OFFLINE &&
      this._state != PUSH_SERVICE_ACTIVATING &&
      !calledFromConnEnabledEvent
    ) {
      return;
    }

    if (offline) {
      if (this._state == PUSH_SERVICE_RUNNING) {
        this._service.disconnect();
      }
      this._setState(PUSH_SERVICE_ACTIVE_OFFLINE);
      return;
    }

    if (this._state == PUSH_SERVICE_RUNNING) {
      // PushService was not in the offline state, but got notification to
      // go online (a offline notification has not been sent).
      // Disconnect first.
      this._service.disconnect();
    }

    let broadcastListeners = await lazy.pushBroadcastService.getListeners();

    // In principle, a listener could be added to the
    // pushBroadcastService here, after we have gotten listeners and
    // before we're RUNNING, but this can't happen in practice because
    // the only caller that can add listeners is PushBroadcastService,
    // and it waits on the same promise we are before it can add
    // listeners. If PushBroadcastService gets woken first, it will
    // update the value that is eventually returned from
    // getListeners.
    this._setState(PUSH_SERVICE_RUNNING);

    this._service.connect(broadcastListeners);
  },

  _changeStateConnectionEnabledEvent(enabled) {
    lazy.console.debug("changeStateConnectionEnabledEvent()", enabled);

    if (
      this._state < PUSH_SERVICE_CONNECTION_DISABLE &&
      this._state != PUSH_SERVICE_ACTIVATING
    ) {
      return Promise.resolve();
    }

    if (enabled) {
      return this._changeStateOfflineEvent(Services.io.offline, true);
    }

    if (this._state == PUSH_SERVICE_RUNNING) {
      this._service.disconnect();
    }
    this._setState(PUSH_SERVICE_CONNECTION_DISABLE);
    return Promise.resolve();
  },

  // Used for testing.
  changeTestServer(url, options = {}) {
    lazy.console.debug("changeTestServer()");

    return this._stateChangeProcessEnqueue(_ => {
      if (this._state < PUSH_SERVICE_ACTIVATING) {
        lazy.console.debug("changeTestServer: PushService not activated?");
        return Promise.resolve();
      }

      return this._changeServerURL(url, CHANGING_SERVICE_EVENT, options);
    });
  },

  observe: function observe(aSubject, aTopic, aData) {
    switch (aTopic) {
      /*
       * We need to call uninit() on shutdown to clean up things that modules
       * aren't very good at automatically cleaning up, so we don't get shutdown
       * leaks on browser shutdown.
       */
      case "quit-application":
        this.uninit();
        break;
      case "network:offline-status-changed":
        this._stateChangeProcessEnqueue(_ =>
          this._changeStateOfflineEvent(aData === "offline", false)
        );
        break;

      case "nsPref:changed":
        if (aData == "serverURL") {
          lazy.console.debug(
            "observe: dom.push.serverURL changed for websocket",
            prefs.getStringPref("serverURL")
          );
          this._stateChangeProcessEnqueue(_ =>
            this._changeServerURL(
              prefs.getStringPref("serverURL"),
              CHANGING_SERVICE_EVENT
            )
          );
        } else if (aData == "connection.enabled") {
          this._stateChangeProcessEnqueue(_ =>
            this._changeStateConnectionEnabledEvent(
              prefs.getBoolPref("connection.enabled")
            )
          );
        }
        break;

      case "idle-daily":
        this._dropExpiredRegistrations().catch(error => {
          lazy.console.error(
            "Failed to drop expired registrations on idle",
            error
          );
        });
        break;

      case "perm-changed":
        this._onPermissionChange(aSubject, aData).catch(error => {
          lazy.console.error(
            "onPermissionChange: Error updating registrations:",
            error
          );
        });
        break;

      case "clear-origin-attributes-data":
        this._clearOriginData(aData).catch(error => {
          lazy.console.error(
            "clearOriginData: Error clearing origin data:",
            error
          );
        });
        break;
    }
  },

  _clearOriginData(data) {
    lazy.console.log("clearOriginData()");

    if (!data) {
      return Promise.resolve();
    }

    let pattern = JSON.parse(data);
    return this._dropRegistrationsIf(record =>
      record.matchesOriginAttributes(pattern)
    );
  },

  /**
   * Sends an unregister request to the server in the background. If the
   * service is not connected, this function is a no-op.
   *
   * @param {PushRecord} record The record to unregister.
   * @param {Number} reason An `nsIPushErrorReporter` unsubscribe reason,
   *  indicating why this record was removed.
   */
  _backgroundUnregister(record, reason) {
    lazy.console.debug("backgroundUnregister()");

    if (!this._service.isConnected() || !record) {
      return;
    }

    lazy.console.debug("backgroundUnregister: Notifying server", record);
    this._sendUnregister(record, reason)
      .then(() => {
        lazy.gPushNotifier.notifySubscriptionModified(
          record.scope,
          record.principal
        );
      })
      .catch(e => {
        lazy.console.error("backgroundUnregister: Error notifying server", e);
      });
  },

  _findService(serverURL) {
    lazy.console.debug("findService()");

    if (!serverURL) {
      lazy.console.warn("findService: No dom.push.serverURL found");
      return [];
    }

    let uri;
    try {
      uri = Services.io.newURI(serverURL);
    } catch (e) {
      lazy.console.warn(
        "findService: Error creating valid URI from",
        "dom.push.serverURL",
        serverURL
      );
      return [];
    }

    let service = getServiceForServerURI(uri);
    return [service, uri];
  },

  _changeServerURL(serverURI, event, options = {}) {
    lazy.console.debug("changeServerURL()");

    switch (event) {
      case UNINIT_EVENT:
        return this._stopService(event);

      case STARTING_SERVICE_EVENT: {
        let [service, uri] = this._findService(serverURI);
        if (!service) {
          this._setState(PUSH_SERVICE_INIT);
          return Promise.resolve();
        }
        return this._startService(service, uri, options).then(_ =>
          this._changeStateConnectionEnabledEvent(
            prefs.getBoolPref("connection.enabled")
          )
        );
      }
      case CHANGING_SERVICE_EVENT:
        let [service, uri] = this._findService(serverURI);
        if (service) {
          if (this._state == PUSH_SERVICE_INIT) {
            this._setState(PUSH_SERVICE_ACTIVATING);
            // The service has not been running - start it.
            return this._startService(service, uri, options).then(_ =>
              this._changeStateConnectionEnabledEvent(
                prefs.getBoolPref("connection.enabled")
              )
            );
          }
          this._setState(PUSH_SERVICE_ACTIVATING);
          // If we already had running service - stop service, start the new
          // one and check connection.enabled and offline state(offline state
          // check is called in changeStateConnectionEnabledEvent function)
          return this._stopService(CHANGING_SERVICE_EVENT)
            .then(_ => this._startService(service, uri, options))
            .then(_ =>
              this._changeStateConnectionEnabledEvent(
                prefs.getBoolPref("connection.enabled")
              )
            );
        }
        if (this._state == PUSH_SERVICE_INIT) {
          return Promise.resolve();
        }
        // The new serverUri is empty or misconfigured - stop service.
        this._setState(PUSH_SERVICE_INIT);
        return this._stopService(STOPPING_SERVICE_EVENT);

      default:
        lazy.console.error("Unexpected event in _changeServerURL", event);
        return Promise.reject(new Error(`Unexpected event ${event}`));
    }
  },

  /**
   * PushService initialization is divided into 4 parts:
   * init() - start listening for quit-application and serverURL changes.
   *          state is change to PUSH_SERVICE_INIT
   * startService() - if serverURL is present this function is called. It starts
   *                  listening for broadcasted messages, starts db and
   *                  PushService connection (WebSocket).
   *                  state is change to PUSH_SERVICE_ACTIVATING.
   * startObservers() - start other observers.
   * changeStateConnectionEnabledEvent  - checks prefs and offline state.
   *                                      It changes state to:
   *                                        PUSH_SERVICE_RUNNING,
   *                                        PUSH_SERVICE_ACTIVE_OFFLINE or
   *                                        PUSH_SERVICE_CONNECTION_DISABLE.
   */
  async init(options = {}) {
    lazy.console.debug("init()");

    if (this._state > PUSH_SERVICE_UNINIT) {
      return;
    }

    this._setState(PUSH_SERVICE_ACTIVATING);

    prefs.addObserver("serverURL", this);
    Services.obs.addObserver(this, "quit-application");

    if (options.serverURI) {
      // this is use for xpcshell test.

      await this._stateChangeProcessEnqueue(_ =>
        this._changeServerURL(
          options.serverURI,
          STARTING_SERVICE_EVENT,
          options
        )
      );
    } else {
      // This is only used for testing. Different tests require connecting to
      // slightly different URLs.
      await this._stateChangeProcessEnqueue(_ =>
        this._changeServerURL(
          prefs.getStringPref("serverURL"),
          STARTING_SERVICE_EVENT
        )
      );
    }
  },

  _startObservers() {
    lazy.console.debug("startObservers()");

    if (this._state != PUSH_SERVICE_ACTIVATING) {
      return;
    }

    Services.obs.addObserver(this, "clear-origin-attributes-data");

    // The offline-status-changed event is used to know
    // when to (dis)connect. It may not fire if the underlying OS changes
    // networks; in such a case we rely on timeout.
    Services.obs.addObserver(this, "network:offline-status-changed");

    // Used to monitor if the user wishes to disable Push.
    prefs.addObserver("connection.enabled", this);

    // Prunes expired registrations and notifies dormant service workers.
    Services.obs.addObserver(this, "idle-daily");

    // Prunes registrations for sites for which the user revokes push
    // permissions.
    Services.obs.addObserver(this, "perm-changed");
  },

  _startService(service, serverURI, options) {
    lazy.console.debug("startService()");

    if (this._state != PUSH_SERVICE_ACTIVATING) {
      return Promise.reject();
    }

    this._service = service;

    this._db = options.db;
    if (!this._db) {
      this._db = this._service.newPushDB();
    }

    return this._service.init(options, this, serverURI).then(() => {
      this._startObservers();
      return this._dropExpiredRegistrations();
    });
  },

  /**
   * PushService uninitialization is divided into 3 parts:
   * stopObservers() - stot observers started in startObservers.
   * stopService() - It stops listening for broadcasted messages, stops db and
   *                 PushService connection (WebSocket).
   *                 state is changed to PUSH_SERVICE_INIT.
   * uninit() - stop listening for quit-application and serverURL changes.
   *            state is change to PUSH_SERVICE_UNINIT
   */
  _stopService(event) {
    lazy.console.debug("stopService()");

    if (this._state < PUSH_SERVICE_ACTIVATING) {
      return Promise.resolve();
    }

    this._stopObservers();

    this._service.disconnect();
    this._service.uninit();
    this._service = null;

    this._updateQuotaTimeouts.forEach(timeoutID => clearTimeout(timeoutID));
    this._updateQuotaTimeouts.clear();

    if (!this._db) {
      return Promise.resolve();
    }
    if (event == UNINIT_EVENT) {
      // If it is uninitialized just close db.
      this._db.close();
      this._db = null;
      return Promise.resolve();
    }

    return this.dropUnexpiredRegistrations().then(
      _ => {
        this._db.close();
        this._db = null;
      },
      () => {
        this._db.close();
        this._db = null;
      }
    );
  },

  _stopObservers() {
    lazy.console.debug("stopObservers()");

    if (this._state < PUSH_SERVICE_ACTIVATING) {
      return;
    }

    prefs.removeObserver("connection.enabled", this);

    Services.obs.removeObserver(this, "network:offline-status-changed");
    Services.obs.removeObserver(this, "clear-origin-attributes-data");
    Services.obs.removeObserver(this, "idle-daily");
    Services.obs.removeObserver(this, "perm-changed");
  },

  _shutdownService() {
    let promiseChangeURL = this._changeServerURL("", UNINIT_EVENT);
    this._setState(PUSH_SERVICE_UNINIT);
    lazy.console.debug("shutdownService: shutdown complete!");
    return promiseChangeURL;
  },

  async uninit() {
    lazy.console.debug("uninit()");

    if (this._state == PUSH_SERVICE_UNINIT) {
      return;
    }

    prefs.removeObserver("serverURL", this);
    Services.obs.removeObserver(this, "quit-application");

    await this._stateChangeProcessEnqueue(_ => this._shutdownService());
  },

  /**
   * Drops all active registrations and notifies the associated service
   * workers. This function is called when the user switches Push servers,
   * or when the server invalidates all existing registrations.
   *
   * We ignore expired registrations because they're already handled in other
   * code paths. Registrations that expired after exceeding their quotas are
   * evicted at startup, or on the next `idle-daily` event. Registrations that
   * expired because the user revoked the notification permission are evicted
   * once the permission is reinstated.
   */
  dropUnexpiredRegistrations() {
    return this._db.clearIf(record => {
      if (record.isExpired()) {
        return false;
      }
      this._notifySubscriptionChangeObservers(record);
      return true;
    });
  },

  _notifySubscriptionChangeObservers(record) {
    if (!record) {
      return;
    }
    lazy.gPushNotifier.notifySubscriptionChange(record.scope, record.principal);
  },

  /**
   * Drops a registration and notifies the associated service worker. If the
   * registration does not exist, this function is a no-op.
   *
   * @param {String} keyID The registration ID to remove.
   * @returns {Promise} Resolves once the worker has been notified.
   */
  dropRegistrationAndNotifyApp(aKeyID) {
    return this._db
      .delete(aKeyID)
      .then(record => this._notifySubscriptionChangeObservers(record));
  },

  /**
   * Replaces an existing registration and notifies the associated service
   * worker.
   *
   * @param {String} aOldKey The registration ID to replace.
   * @param {PushRecord} aNewRecord The new record.
   * @returns {Promise} Resolves once the worker has been notified.
   */
  updateRegistrationAndNotifyApp(aOldKey, aNewRecord) {
    return this.updateRecordAndNotifyApp(aOldKey, _ => aNewRecord);
  },
  /**
   * Updates a registration and notifies the associated service worker.
   *
   * @param {String} keyID The registration ID to update.
   * @param {Function} updateFunc Returns the updated record.
   * @returns {Promise} Resolves with the updated record once the worker
   *  has been notified.
   */
  updateRecordAndNotifyApp(aKeyID, aUpdateFunc) {
    return this._db.update(aKeyID, aUpdateFunc).then(record => {
      this._notifySubscriptionChangeObservers(record);
      return record;
    });
  },

  ensureCrypto(record) {
    if (
      record.hasAuthenticationSecret() &&
      record.p256dhPublicKey &&
      record.p256dhPrivateKey
    ) {
      return Promise.resolve(record);
    }

    let keygen = Promise.resolve([]);
    if (!record.p256dhPublicKey || !record.p256dhPrivateKey) {
      keygen = lazy.PushCrypto.generateKeys();
    }
    // We do not have a encryption key. so we need to generate it. This
    // is only going to happen on db upgrade from version 4 to higher.
    return keygen.then(
      ([pubKey, privKey]) => {
        return this.updateRecordAndNotifyApp(record.keyID, record => {
          if (!record.p256dhPublicKey || !record.p256dhPrivateKey) {
            record.p256dhPublicKey = pubKey;
            record.p256dhPrivateKey = privKey;
          }
          if (!record.hasAuthenticationSecret()) {
            record.authenticationSecret =
              lazy.PushCrypto.generateAuthenticationSecret();
          }
          return record;
        });
      },
      error => {
        return this.dropRegistrationAndNotifyApp(record.keyID).then(() =>
          Promise.reject(error)
        );
      }
    );
  },

  /**
   * Dispatches an incoming message to a service worker, recalculating the
   * quota for the associated push registration. If the quota is exceeded,
   * the registration and message will be dropped, and the worker will not
   * be notified.
   *
   * @param {String} keyID The push registration ID.
   * @param {String} messageID The message ID, used to report service worker
   *  delivery failures. For Web Push messages, this is the version. If empty,
   *  failures will not be reported.
   * @param {Object} headers The encryption headers.
   * @param {ArrayBuffer|Uint8Array} data The encrypted message data.
   * @param {Function} updateFunc A function that receives the existing
   *  registration record as its argument, and returns a new record. If the
   *  function returns `null` or `undefined`, the record will not be updated.
   *  `PushServiceWebSocket` uses this to drop incoming updates with older
   *  versions.
   * @returns {Promise} Resolves with an `nsIPushErrorReporter` ack status
   *  code, indicating whether the message was delivered successfully.
   */
  receivedPushMessage(keyID, messageID, headers, data, updateFunc) {
    lazy.console.debug("receivedPushMessage()");

    return this._updateRecordAfterPush(keyID, updateFunc)
      .then(record => {
        if (record.quotaApplies()) {
          // Update quota after the delay, at which point
          // we check for visible notifications.
          let timeoutID = setTimeout(_ => {
            this._updateQuota(keyID);
            if (!this._updateQuotaTimeouts.delete(timeoutID)) {
              lazy.console.debug(
                "receivedPushMessage: quota update timeout missing?"
              );
            }
          }, prefs.getIntPref("quotaUpdateDelay"));
          this._updateQuotaTimeouts.add(timeoutID);
        }
        return this._decryptAndNotifyApp(record, messageID, headers, data);
      })
      .catch(error => {
        lazy.console.error("receivedPushMessage: Error notifying app", error);
        return Ci.nsIPushErrorReporter.ACK_NOT_DELIVERED;
      });
  },

  /**
   * Dispatches a broadcast notification to the BroadcastService.
   *
   * @param {Object} message The reply received by PushServiceWebSocket
   * @param {Object} context Additional information about the context in which the
   *  notification was received.
   */
  receivedBroadcastMessage(message, context) {
    lazy.pushBroadcastService
      .receivedBroadcastMessage(message.broadcasts, context)
      .catch(e => {
        lazy.console.error(e);
      });
  },

  /**
   * Updates a registration record after receiving a push message.
   *
   * @param {String} keyID The push registration ID.
   * @param {Function} updateFunc The function passed to `receivedPushMessage`.
   * @returns {Promise} Resolves with the updated record, or rejects if the
   *  record was not updated.
   */
  _updateRecordAfterPush(keyID, updateFunc) {
    return this.getByKeyID(keyID)
      .then(record => {
        if (!record) {
          throw new Error("No record for key ID " + keyID);
        }
        return record
          .getLastVisit()
          .then(lastVisit => {
            // As a special case, don't notify the service worker if the user
            // cleared their history.
            if (!isFinite(lastVisit)) {
              throw new Error("Ignoring message sent to unvisited origin");
            }
            return lastVisit;
          })
          .then(lastVisit => {
            // Update the record, resetting the quota if the user has visited the
            // site since the last push.
            return this._db.update(keyID, record => {
              let newRecord = updateFunc(record);
              if (!newRecord) {
                return null;
              }
              // Because `unregister` is advisory only, we can still receive messages
              // for stale Simple Push registrations from the server. To work around
              // this, we check if the record has expired before *and* after updating
              // the quota.
              if (newRecord.isExpired()) {
                return null;
              }
              newRecord.receivedPush(lastVisit);
              return newRecord;
            });
          });
      })
      .then(record => {
        lazy.gPushNotifier.notifySubscriptionModified(
          record.scope,
          record.principal
        );
        return record;
      });
  },

  /**
   * Decrypts an incoming message and notifies the associated service worker.
   *
   * @param {PushRecord} record The receiving registration.
   * @param {String} messageID The message ID.
   * @param {Object} headers The encryption headers.
   * @param {ArrayBuffer|Uint8Array} data The encrypted message data.
   * @returns {Promise} Resolves with an ack status code.
   */
  _decryptAndNotifyApp(record, messageID, headers, data) {
    return lazy.PushCrypto.decrypt(
      record.p256dhPrivateKey,
      record.p256dhPublicKey,
      record.authenticationSecret,
      headers,
      data
    ).then(
      message => this._notifyApp(record, messageID, message),
      error => {
        lazy.console.warn(
          "decryptAndNotifyApp: Error decrypting message",
          record.scope,
          messageID,
          error
        );

        let message = error.format(record.scope);
        lazy.gPushNotifier.notifyError(
          record.scope,
          record.principal,
          message,
          Ci.nsIScriptError.errorFlag
        );
        return Ci.nsIPushErrorReporter.ACK_DECRYPTION_ERROR;
      }
    );
  },

  _updateQuota(keyID) {
    lazy.console.debug("updateQuota()");

    this._db
      .update(keyID, record => {
        // Record may have expired from an earlier quota update.
        if (record.isExpired()) {
          lazy.console.debug(
            "updateQuota: Trying to update quota for expired record",
            record
          );
          return null;
        }
        // If there are visible notifications, don't apply the quota penalty
        // for the message.
        if (record.uri && !this._visibleNotifications.has(record.uri.prePath)) {
          record.reduceQuota();
        }
        return record;
      })
      .then(record => {
        if (record.isExpired()) {
          // Drop the registration in the background. If the user returns to the
          // site, the service worker will be notified on the next `idle-daily`
          // event.
          this._backgroundUnregister(
            record,
            Ci.nsIPushErrorReporter.UNSUBSCRIBE_QUOTA_EXCEEDED
          );
        } else {
          lazy.gPushNotifier.notifySubscriptionModified(
            record.scope,
            record.principal
          );
        }
        if (this._updateQuotaTestCallback) {
          // Callback so that test may be notified when the quota update is complete.
          this._updateQuotaTestCallback();
        }
      })
      .catch(error => {
        lazy.console.debug(
          "updateQuota: Error while trying to update quota",
          error
        );
      });
  },

  notificationForOriginShown(origin) {
    lazy.console.debug("notificationForOriginShown()", origin);
    let count;
    if (this._visibleNotifications.has(origin)) {
      count = this._visibleNotifications.get(origin);
    } else {
      count = 0;
    }
    this._visibleNotifications.set(origin, count + 1);
  },

  notificationForOriginClosed(origin) {
    lazy.console.debug("notificationForOriginClosed()", origin);
    let count;
    if (this._visibleNotifications.has(origin)) {
      count = this._visibleNotifications.get(origin);
    } else {
      lazy.console.debug(
        "notificationForOriginClosed: closing notification that has not been shown?"
      );
      return;
    }
    if (count > 1) {
      this._visibleNotifications.set(origin, count - 1);
    } else {
      this._visibleNotifications.delete(origin);
    }
  },

  reportDeliveryError(messageID, reason) {
    lazy.console.debug("reportDeliveryError()", messageID, reason);
    if (this._state == PUSH_SERVICE_RUNNING && this._service.isConnected()) {
      // Only report errors if we're initialized and connected.
      this._service.reportDeliveryError(messageID, reason);
    }
  },

  _notifyApp(aPushRecord, messageID, message) {
    if (
      !aPushRecord ||
      !aPushRecord.scope ||
      aPushRecord.originAttributes === undefined
    ) {
      lazy.console.error("notifyApp: Invalid record", aPushRecord);
      return Ci.nsIPushErrorReporter.ACK_NOT_DELIVERED;
    }

    lazy.console.debug("notifyApp()", aPushRecord.scope);

    // If permission has been revoked, trash the message.
    if (!aPushRecord.hasPermission()) {
      lazy.console.warn("notifyApp: Missing push permission", aPushRecord);
      return Ci.nsIPushErrorReporter.ACK_NOT_DELIVERED;
    }

    let payload = ArrayBuffer.isView(message)
      ? new Uint8Array(message.buffer)
      : message;

    if (aPushRecord.quotaApplies()) {
      // Don't record telemetry for chrome push messages.
      Services.telemetry.getHistogramById("PUSH_API_NOTIFY").add();
    }

    if (payload) {
      lazy.gPushNotifier.notifyPushWithData(
        aPushRecord.scope,
        aPushRecord.principal,
        messageID,
        payload
      );
    } else {
      lazy.gPushNotifier.notifyPush(
        aPushRecord.scope,
        aPushRecord.principal,
        messageID
      );
    }

    return Ci.nsIPushErrorReporter.ACK_DELIVERED;
  },

  getByKeyID(aKeyID) {
    return this._db.getByKeyID(aKeyID);
  },

  getAllUnexpired() {
    return this._db.getAllUnexpired();
  },

  _sendRequest(action, ...params) {
    if (this._state == PUSH_SERVICE_CONNECTION_DISABLE) {
      return Promise.reject(new Error("Push service disabled"));
    }
    if (this._state == PUSH_SERVICE_ACTIVE_OFFLINE) {
      return Promise.reject(new Error("Push service offline"));
    }
    // Ensure the backend is ready. `getByPageRecord` already checks this, but
    // we need to check again here in case the service was restarted in the
    // meantime.
    return this._checkActivated().then(_ => {
      switch (action) {
        case "register":
          return this._service.register(...params);
        case "unregister":
          return this._service.unregister(...params);
      }
      return Promise.reject(new Error("Unknown request type: " + action));
    });
  },

  /**
   * Called on message from the child process. aPageRecord is an object sent by
   * the push manager, identifying the sending page and other fields.
   */
  _registerWithServer(aPageRecord) {
    lazy.console.debug("registerWithServer()", aPageRecord);

    return this._sendRequest("register", aPageRecord)
      .then(
        record => this._onRegisterSuccess(record),
        err => this._onRegisterError(err)
      )
      .then(
        record => {
          this._deletePendingRequest(aPageRecord);
          lazy.gPushNotifier.notifySubscriptionModified(
            record.scope,
            record.principal
          );
          return record.toSubscription();
        },
        err => {
          this._deletePendingRequest(aPageRecord);
          throw err;
        }
      );
  },

  _sendUnregister(aRecord, aReason) {
    return this._sendRequest("unregister", aRecord, aReason);
  },

  /**
   * Exceptions thrown in _onRegisterSuccess are caught by the promise obtained
   * from _service.request, causing the promise to be rejected instead.
   */
  _onRegisterSuccess(aRecord) {
    lazy.console.debug("_onRegisterSuccess()");

    return this._db.put(aRecord).catch(error => {
      // Unable to save. Destroy the subscription in the background.
      this._backgroundUnregister(
        aRecord,
        Ci.nsIPushErrorReporter.UNSUBSCRIBE_MANUAL
      );
      throw error;
    });
  },

  /**
   * Exceptions thrown in _onRegisterError are caught by the promise obtained
   * from _service.request, causing the promise to be rejected instead.
   */
  _onRegisterError(reply) {
    lazy.console.debug("_onRegisterError()");

    if (!reply.error) {
      lazy.console.warn(
        "onRegisterError: Called without valid error message!",
        reply
      );
      throw new Error("Registration error");
    }
    throw reply.error;
  },

  notificationsCleared() {
    this._visibleNotifications.clear();
  },

  _getByPageRecord(pageRecord) {
    return this._checkActivated().then(_ =>
      this._db.getByIdentifiers(pageRecord)
    );
  },

  register(aPageRecord) {
    lazy.console.debug("register()", aPageRecord);

    let keyPromise;
    if (aPageRecord.appServerKey && aPageRecord.appServerKey.length) {
      let keyView = new Uint8Array(aPageRecord.appServerKey);
      keyPromise = lazy.PushCrypto.validateAppServerKey(keyView).catch(() => {
        // Normalize Web Crypto exceptions. `nsIPushService` will forward the
        // error result to the DOM API implementation in `PushManager.cpp` or
        // `Push.js`, which will convert it to the correct `DOMException`.
        throw errorWithResult(
          "Invalid app server key",
          Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR
        );
      });
    } else {
      keyPromise = Promise.resolve(null);
    }

    return Promise.all([keyPromise, this._getByPageRecord(aPageRecord)]).then(
      ([appServerKey, record]) => {
        aPageRecord.appServerKey = appServerKey;
        if (!record) {
          return this._lookupOrPutPendingRequest(aPageRecord);
        }
        if (!record.matchesAppServerKey(appServerKey)) {
          throw errorWithResult(
            "Mismatched app server key",
            Cr.NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR
          );
        }
        if (record.isExpired()) {
          return record
            .quotaChanged()
            .then(isChanged => {
              if (isChanged) {
                // If the user revisited the site, drop the expired push
                // registration and re-register.
                return this.dropRegistrationAndNotifyApp(record.keyID);
              }
              throw new Error("Push subscription expired");
            })
            .then(_ => this._lookupOrPutPendingRequest(aPageRecord));
        }
        return record.toSubscription();
      }
    );
  },

  /*
   * Called only by the PushBroadcastService on the receipt of a new
   * subscription. Don't call this directly. Go through PushBroadcastService.
   */
  async subscribeBroadcast(broadcastId, version) {
    if (this._state != PUSH_SERVICE_RUNNING) {
      // Ignore any request to subscribe before we send a hello.
      // We'll send all the broadcast listeners as part of the hello
      // anyhow.
      return;
    }

    await this._service.sendSubscribeBroadcast(broadcastId, version);
  },

  /**
   * Called on message from the child process.
   *
   * Why is the record being deleted from the local database before the server
   * is told?
   *
   * Unregistration is for the benefit of the app and the AppServer
   * so that the AppServer does not keep pinging a channel the UserAgent isn't
   * watching The important part of the transaction in this case is left to the
   * app, to tell its server of the unregistration.  Even if the request to the
   * PushServer were to fail, it would not affect correctness of the protocol,
   * and the server GC would just clean up the channelID/subscription
   * eventually.  Since the appserver doesn't ping it, no data is lost.
   *
   * If rather we were to unregister at the server and update the database only
   * on success: If the server receives the unregister, and deletes the
   * channelID/subscription, but the response is lost because of network
   * failure, the application is never informed. In addition the application may
   * retry the unregister when it fails due to timeout (websocket) or any other
   * reason at which point the server will say it does not know of this
   * unregistration.  We'll have to make the registration/unregistration phases
   * have retries and attempts to resend messages from the server, and have the
   * client acknowledge. On a server, data is cheap, reliable notification is
   * not.
   */
  unregister(aPageRecord) {
    lazy.console.debug("unregister()", aPageRecord);

    return this._getByPageRecord(aPageRecord).then(record => {
      if (record === null) {
        return false;
      }

      let reason = Ci.nsIPushErrorReporter.UNSUBSCRIBE_MANUAL;
      return Promise.all([
        this._sendUnregister(record, reason),
        this._db.delete(record.keyID).then(rec => {
          if (rec) {
            lazy.gPushNotifier.notifySubscriptionModified(
              rec.scope,
              rec.principal
            );
          }
        }),
      ]).then(([success]) => success);
    });
  },

  clear(info) {
    return this._checkActivated()
      .then(_ => {
        return this._dropRegistrationsIf(
          record =>
            info.domain == "*" ||
            (record.uri &&
              Services.eTLD.hasRootDomain(record.uri.prePath, info.domain))
        );
      })
      .catch(e => {
        lazy.console.warn(
          "clear: Error dropping subscriptions for domain",
          info.domain,
          e
        );
        return Promise.resolve();
      });
  },

  registration(aPageRecord) {
    lazy.console.debug("registration()");

    return this._getByPageRecord(aPageRecord).then(record => {
      if (!record) {
        return null;
      }
      if (record.isExpired()) {
        return record.quotaChanged().then(isChanged => {
          if (isChanged) {
            return this.dropRegistrationAndNotifyApp(record.keyID).then(
              _ => null
            );
          }
          return null;
        });
      }
      return record.toSubscription();
    });
  },

  _dropExpiredRegistrations() {
    lazy.console.debug("dropExpiredRegistrations()");

    return this._db.getAllExpired().then(records => {
      return Promise.all(
        records.map(record =>
          record
            .quotaChanged()
            .then(isChanged => {
              if (isChanged) {
                // If the user revisited the site, drop the expired push
                // registration and notify the associated service worker.
                this.dropRegistrationAndNotifyApp(record.keyID);
              }
            })
            .catch(error => {
              lazy.console.error(
                "dropExpiredRegistrations: Error dropping registration",
                record.keyID,
                error
              );
            })
        )
      );
    });
  },

  _onPermissionChange(subject, data) {
    lazy.console.debug("onPermissionChange()");

    if (data == "cleared") {
      return this._clearPermissions();
    }

    let permission = subject.QueryInterface(Ci.nsIPermission);
    if (permission.type != "desktop-notification") {
      return Promise.resolve();
    }

    return this._updatePermission(permission, data);
  },

  _clearPermissions() {
    lazy.console.debug("clearPermissions()");

    return this._db.clearIf(record => {
      if (!record.quotaApplies()) {
        // Only drop registrations that are subject to quota.
        return false;
      }
      this._backgroundUnregister(
        record,
        Ci.nsIPushErrorReporter.UNSUBSCRIBE_PERMISSION_REVOKED
      );
      return true;
    });
  },

  _updatePermission(permission, type) {
    lazy.console.debug("updatePermission()");

    let isAllow = permission.capability == Ci.nsIPermissionManager.ALLOW_ACTION;
    let isChange = type == "added" || type == "changed";

    if (isAllow && isChange) {
      // Permission set to "allow". Drop all expired registrations for this
      // site, notify the associated service workers, and reset the quota
      // for active registrations.
      return this._forEachPrincipal(permission.principal, (record, cursor) =>
        this._permissionAllowed(record, cursor)
      );
    } else if (isChange || (isAllow && type == "deleted")) {
      // Permission set to "block" or "always ask," or "allow" permission
      // removed. Expire all registrations for this site.
      return this._forEachPrincipal(permission.principal, (record, cursor) =>
        this._permissionDenied(record, cursor)
      );
    }

    return Promise.resolve();
  },

  _forEachPrincipal(principal, callback) {
    return this._db.forEachOrigin(
      principal.URI.prePath,
      ChromeUtils.originAttributesToSuffix(principal.originAttributes),
      callback
    );
  },

  /**
   * The update function called for each registration record if the push
   * permission is revoked. We only expire the record so we can notify the
   * service worker as soon as the permission is reinstated. If we just
   * deleted the record, the worker wouldn't be notified until the next visit
   * to the site.
   *
   * @param {PushRecord} record The record to expire.
   * @param {IDBCursor} cursor The IndexedDB cursor.
   */
  _permissionDenied(record, cursor) {
    lazy.console.debug("permissionDenied()");

    if (!record.quotaApplies() || record.isExpired()) {
      // Ignore already-expired records.
      return;
    }
    // Drop the registration in the background.
    this._backgroundUnregister(
      record,
      Ci.nsIPushErrorReporter.UNSUBSCRIBE_PERMISSION_REVOKED
    );
    record.setQuota(0);
    cursor.update(record);
  },

  /**
   * The update function called for each registration record if the push
   * permission is granted. If the record has expired, it will be dropped;
   * otherwise, its quota will be reset to the default value.
   *
   * @param {PushRecord} record The record to update.
   * @param {IDBCursor} cursor The IndexedDB cursor.
   */
  _permissionAllowed(record, cursor) {
    lazy.console.debug("permissionAllowed()");

    if (!record.quotaApplies()) {
      return;
    }
    if (record.isExpired()) {
      // If the registration has expired, drop and notify the worker
      // unconditionally.
      this._notifySubscriptionChangeObservers(record);
      cursor.delete();
      return;
    }
    record.resetQuota();
    cursor.update(record);
  },

  /**
   * Drops all matching registrations from the database. Notifies the
   * associated service workers if permission is granted, and removes
   * unexpired registrations from the server.
   *
   * @param {Function} predicate A function called for each record.
   * @returns {Promise} Resolves once the registrations have been dropped.
   */
  _dropRegistrationsIf(predicate) {
    return this._db.clearIf(record => {
      if (!predicate(record)) {
        return false;
      }
      if (record.hasPermission()) {
        // "Clear Recent History" and the Forget button remove permissions
        // before clearing registrations, but it's possible for the worker to
        // resubscribe if the "dom.push.testing.ignorePermission" pref is set.
        this._notifySubscriptionChangeObservers(record);
      }
      if (!record.isExpired()) {
        // Only unregister active registrations, since we already told the
        // server about expired ones.
        this._backgroundUnregister(
          record,
          Ci.nsIPushErrorReporter.UNSUBSCRIBE_MANUAL
        );
      }
      return true;
    });
  },
};
PK
!<#�R��$modules/PushServiceWebSocket.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { PushDB } from "resource://gre/modules/PushDB.sys.mjs";
import { PushRecord } from "resource://gre/modules/PushRecord.sys.mjs";
import { PushCrypto } from "resource://gre/modules/PushCrypto.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
  pushBroadcastService: "resource://gre/modules/PushBroadcastService.sys.mjs",
});

const kPUSHWSDB_DB_NAME = "pushapi";
const kPUSHWSDB_DB_VERSION = 5; // Change this if the IndexedDB format changes
const kPUSHWSDB_STORE_NAME = "pushapi";

// WebSocket close code sent by the server to indicate that the client should
// not automatically reconnect.
const kBACKOFF_WS_STATUS_CODE = 4774;

// Maps ack statuses, unsubscribe reasons, and delivery error reasons to codes
// included in request payloads.
const kACK_STATUS_TO_CODE = {
  [Ci.nsIPushErrorReporter.ACK_DELIVERED]: 100,
  [Ci.nsIPushErrorReporter.ACK_DECRYPTION_ERROR]: 101,
  [Ci.nsIPushErrorReporter.ACK_NOT_DELIVERED]: 102,
};

const kUNREGISTER_REASON_TO_CODE = {
  [Ci.nsIPushErrorReporter.UNSUBSCRIBE_MANUAL]: 200,
  [Ci.nsIPushErrorReporter.UNSUBSCRIBE_QUOTA_EXCEEDED]: 201,
  [Ci.nsIPushErrorReporter.UNSUBSCRIBE_PERMISSION_REVOKED]: 202,
};

const kDELIVERY_REASON_TO_CODE = {
  [Ci.nsIPushErrorReporter.DELIVERY_UNCAUGHT_EXCEPTION]: 301,
  [Ci.nsIPushErrorReporter.DELIVERY_UNHANDLED_REJECTION]: 302,
  [Ci.nsIPushErrorReporter.DELIVERY_INTERNAL_ERROR]: 303,
};

const prefs = Services.prefs.getBranch("dom.push.");

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  let { ConsoleAPI } = ChromeUtils.importESModule(
    "resource://gre/modules/Console.sys.mjs"
  );
  return new ConsoleAPI({
    maxLogLevelPref: "dom.push.loglevel",
    prefix: "PushServiceWebSocket",
  });
});

/**
 * A proxy between the PushService and the WebSocket. The listener is used so
 * that the PushService can silence messages from the WebSocket by setting
 * PushWebSocketListener._pushService to null. This is required because
 * a WebSocket can continue to send messages or errors after it has been
 * closed but the PushService may not be interested in these. It's easier to
 * stop listening than to have checks at specific points.
 */
var PushWebSocketListener = function (pushService) {
  this._pushService = pushService;
};

PushWebSocketListener.prototype = {
  onStart(context) {
    if (!this._pushService) {
      return;
    }
    this._pushService._wsOnStart(context);
  },

  onStop(context, statusCode) {
    if (!this._pushService) {
      return;
    }
    this._pushService._wsOnStop(context, statusCode);
  },

  onAcknowledge() {
    // EMPTY
  },

  onBinaryMessageAvailable() {
    // EMPTY
  },

  onMessageAvailable(context, message) {
    if (!this._pushService) {
      return;
    }
    this._pushService._wsOnMessageAvailable(context, message);
  },

  onServerClose(context, aStatusCode, aReason) {
    if (!this._pushService) {
      return;
    }
    this._pushService._wsOnServerClose(context, aStatusCode, aReason);
  },
};

// websocket states
// websocket is off
const STATE_SHUT_DOWN = 0;
// Websocket has been opened on client side, waiting for successful open.
// (_wsOnStart)
const STATE_WAITING_FOR_WS_START = 1;
// Websocket opened, hello sent, waiting for server reply (_handleHelloReply).
const STATE_WAITING_FOR_HELLO = 2;
// Websocket operational, handshake completed, begin protocol messaging.
const STATE_READY = 3;

export var PushServiceWebSocket = {
  QueryInterface: ChromeUtils.generateQI(["nsINamed", "nsIObserver"]),
  name: "PushServiceWebSocket",

  _mainPushService: null,
  _serverURI: null,
  _currentlyRegistering: new Set(),

  newPushDB() {
    return new PushDB(
      kPUSHWSDB_DB_NAME,
      kPUSHWSDB_DB_VERSION,
      kPUSHWSDB_STORE_NAME,
      "channelID",
      PushRecordWebSocket
    );
  },

  disconnect() {
    this._shutdownWS();
  },

  observe(aSubject, aTopic, aData) {
    if (aTopic == "nsPref:changed" && aData == "dom.push.userAgentID") {
      this._onUAIDChanged();
    } else if (aTopic == "timer-callback") {
      this._onTimerFired(aSubject);
    }
  },

  /**
   * Handles a UAID change. Unlike reconnects, we cancel all pending requests
   * after disconnecting. Existing subscriptions stored in IndexedDB will be
   * dropped on reconnect.
   */
  _onUAIDChanged() {
    lazy.console.debug("onUAIDChanged()");

    this._shutdownWS();
    this._startBackoffTimer();
  },

  /** Handles a ping, backoff, or request timeout timer event. */
  _onTimerFired(timer) {
    lazy.console.debug("onTimerFired()");

    if (timer == this._pingTimer) {
      this._sendPing();
      return;
    }

    if (timer == this._backoffTimer) {
      lazy.console.debug("onTimerFired: Reconnecting after backoff");
      this._beginWSSetup();
      return;
    }

    if (timer == this._requestTimeoutTimer) {
      this._timeOutRequests();
    }
  },

  /**
   * Sends a ping to the server. Bypasses the request queue, but starts the
   * request timeout timer. If the socket is already closed, or the server
   * does not respond within the timeout, the client will reconnect.
   */
  _sendPing() {
    lazy.console.debug("sendPing()");

    this._startRequestTimeoutTimer();
    try {
      this._wsSendMessage({});
      this._lastPingTime = Date.now();
    } catch (e) {
      lazy.console.debug("sendPing: Error sending ping", e);
      this._reconnect();
    }
  },

  /** Times out any pending requests. */
  _timeOutRequests() {
    lazy.console.debug("timeOutRequests()");

    if (!this._hasPendingRequests()) {
      // Cancel the repeating timer and exit early if we aren't waiting for
      // pongs or requests.
      this._requestTimeoutTimer.cancel();
      return;
    }

    let now = Date.now();

    // Set to true if at least one request timed out, or we're still waiting
    // for a pong after the request timeout.
    let requestTimedOut = false;

    if (
      this._lastPingTime > 0 &&
      now - this._lastPingTime > this._requestTimeout
    ) {
      lazy.console.debug("timeOutRequests: Did not receive pong in time");
      requestTimedOut = true;
    } else {
      for (let [key, request] of this._pendingRequests) {
        let duration = now - request.ctime;
        // If any of the registration requests time out, all the ones after it
        // also made to fail, since we are going to be disconnecting the
        // socket.
        requestTimedOut |= duration > this._requestTimeout;
        if (requestTimedOut) {
          request.reject(new Error("Request timed out: " + key));
          this._pendingRequests.delete(key);
        }
      }
    }

    // The most likely reason for a pong or registration request timing out is
    // that the socket has disconnected. Best to reconnect.
    if (requestTimedOut) {
      this._reconnect();
    }
  },

  get _UAID() {
    return prefs.getStringPref("userAgentID");
  },

  set _UAID(newID) {
    if (typeof newID !== "string") {
      lazy.console.warn(
        "Got invalid, non-string UAID",
        newID,
        "Not updating userAgentID"
      );
      return;
    }
    lazy.console.debug("New _UAID", newID);
    prefs.setStringPref("userAgentID", newID);
  },

  _ws: null,
  _pendingRequests: new Map(),
  _currentState: STATE_SHUT_DOWN,
  _requestTimeout: 0,
  _requestTimeoutTimer: null,
  _retryFailCount: 0,

  /**
   * According to the WS spec, servers should immediately close the underlying
   * TCP connection after they close a WebSocket. This causes wsOnStop to be
   * called with error NS_BASE_STREAM_CLOSED. Since the client has to keep the
   * WebSocket up, it should try to reconnect. But if the server closes the
   * WebSocket because it wants the client to back off, then the client
   * shouldn't re-establish the connection. If the server sends the backoff
   * close code, this field will be set to true in wsOnServerClose. It is
   * checked in wsOnStop.
   */
  _skipReconnect: false,

  /** Indicates whether the server supports Web Push-style message delivery. */
  _dataEnabled: false,

  /**
   * The last time the client sent a ping to the server. If non-zero, keeps the
   * request timeout timer active. Reset to zero when the server responds with
   * a pong or pending messages.
   */
  _lastPingTime: 0,

  /**
   * A one-shot timer used to ping the server, to avoid timing out idle
   * connections. Reset to the ping interval on each incoming message.
   */
  _pingTimer: null,

  /** A one-shot timer fired after the reconnect backoff period. */
  _backoffTimer: null,

  /**
   * Sends a message to the Push Server through an open websocket.
   * typeof(msg) shall be an object
   */
  _wsSendMessage(msg) {
    if (!this._ws) {
      lazy.console.warn(
        "wsSendMessage: No WebSocket initialized.",
        "Cannot send a message"
      );
      return;
    }
    msg = JSON.stringify(msg);
    lazy.console.debug("wsSendMessage: Sending message", msg);
    this._ws.sendMsg(msg);
  },

  init(options, mainPushService, serverURI) {
    lazy.console.debug("init()");

    this._mainPushService = mainPushService;
    this._serverURI = serverURI;
    // Filled in at connect() time
    this._broadcastListeners = null;

    // Override the default WebSocket factory function. The returned object
    // must be null or satisfy the nsIWebSocketChannel interface. Used by
    // the tests to provide a mock WebSocket implementation.
    if (options.makeWebSocket) {
      this._makeWebSocket = options.makeWebSocket;
    }

    this._requestTimeout = prefs.getIntPref("requestTimeout");

    return Promise.resolve();
  },

  _reconnect() {
    lazy.console.debug("reconnect()");
    this._shutdownWS(false);
    this._startBackoffTimer();
  },

  _shutdownWS(shouldCancelPending = true) {
    lazy.console.debug("shutdownWS()");

    if (this._currentState == STATE_READY) {
      prefs.removeObserver("userAgentID", this);
    }

    this._currentState = STATE_SHUT_DOWN;
    this._skipReconnect = false;

    if (this._wsListener) {
      this._wsListener._pushService = null;
    }
    try {
      this._ws.close(0, null);
    } catch (e) {}
    this._ws = null;

    this._lastPingTime = 0;

    if (this._pingTimer) {
      this._pingTimer.cancel();
    }

    if (shouldCancelPending) {
      this._cancelPendingRequests();
    }

    if (this._notifyRequestQueue) {
      this._notifyRequestQueue();
      this._notifyRequestQueue = null;
    }
  },

  uninit() {
    // All pending requests (ideally none) are dropped at this point. We
    // shouldn't have any applications performing registration/unregistration
    // or receiving notifications.
    this._shutdownWS();

    if (this._backoffTimer) {
      this._backoffTimer.cancel();
    }
    if (this._requestTimeoutTimer) {
      this._requestTimeoutTimer.cancel();
    }

    this._mainPushService = null;

    this._dataEnabled = false;
  },

  /**
   * How retries work: If the WS is closed due to a socket error,
   * _startBackoffTimer() is called. The retry timer is started and when
   * it times out, beginWSSetup() is called again.
   *
   * If we are in the middle of a timeout (i.e. waiting), but
   * a register/unregister is called, we don't want to wait around anymore.
   * _sendRequest will automatically call beginWSSetup(), which will cancel the
   * timer. In addition since the state will have changed, even if a pending
   * timer event comes in (because the timer fired the event before it was
   * cancelled), so the connection won't be reset.
   */
  _startBackoffTimer() {
    lazy.console.debug("startBackoffTimer()");

    // Calculate new timeout, but cap it to pingInterval.
    let retryTimeout =
      prefs.getIntPref("retryBaseInterval") * Math.pow(2, this._retryFailCount);
    retryTimeout = Math.min(retryTimeout, prefs.getIntPref("pingInterval"));

    this._retryFailCount++;

    lazy.console.debug(
      "startBackoffTimer: Retry in",
      retryTimeout,
      "Try number",
      this._retryFailCount
    );

    if (!this._backoffTimer) {
      this._backoffTimer = Cc["@mozilla.org/timer;1"].createInstance(
        Ci.nsITimer
      );
    }
    this._backoffTimer.init(this, retryTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
  },

  /** Indicates whether we're waiting for pongs or requests. */
  _hasPendingRequests() {
    return this._lastPingTime > 0 || this._pendingRequests.size > 0;
  },

  /**
   * Starts the request timeout timer unless we're already waiting for a pong
   * or register request.
   */
  _startRequestTimeoutTimer() {
    if (this._hasPendingRequests()) {
      return;
    }
    if (!this._requestTimeoutTimer) {
      this._requestTimeoutTimer = Cc["@mozilla.org/timer;1"].createInstance(
        Ci.nsITimer
      );
    }
    this._requestTimeoutTimer.init(
      this,
      this._requestTimeout,
      Ci.nsITimer.TYPE_REPEATING_SLACK
    );
  },

  /** Starts or resets the ping timer. */
  _startPingTimer() {
    if (!this._pingTimer) {
      this._pingTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    }
    this._pingTimer.init(
      this,
      prefs.getIntPref("pingInterval"),
      Ci.nsITimer.TYPE_ONE_SHOT
    );
  },

  _makeWebSocket(uri) {
    if (!prefs.getBoolPref("connection.enabled")) {
      lazy.console.warn(
        "makeWebSocket: connection.enabled is not set to true.",
        "Aborting."
      );
      return null;
    }
    if (Services.io.offline) {
      lazy.console.warn("makeWebSocket: Network is offline.");
      return null;
    }
    let contractId =
      uri.scheme == "ws"
        ? "@mozilla.org/network/protocol;1?name=ws"
        : "@mozilla.org/network/protocol;1?name=wss";
    let socket = Cc[contractId].createInstance(Ci.nsIWebSocketChannel);

    socket.initLoadInfo(
      null, // aLoadingNode
      Services.scriptSecurityManager.getSystemPrincipal(),
      null, // aTriggeringPrincipal
      Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
      Ci.nsIContentPolicy.TYPE_WEBSOCKET
    );
    // Allow deprecated HTTP request from SystemPrincipal
    socket.loadInfo.allowDeprecatedSystemRequests = true;

    return socket;
  },

  _beginWSSetup() {
    lazy.console.debug("beginWSSetup()");
    if (this._currentState != STATE_SHUT_DOWN) {
      lazy.console.error(
        "_beginWSSetup: Not in shutdown state! Current state",
        this._currentState
      );
      return;
    }

    // Stop any pending reconnects scheduled for the near future.
    if (this._backoffTimer) {
      this._backoffTimer.cancel();
    }

    let uri = this._serverURI;
    if (!uri) {
      return;
    }
    let socket = this._makeWebSocket(uri);
    if (!socket) {
      return;
    }
    this._ws = socket.QueryInterface(Ci.nsIWebSocketChannel);

    lazy.console.debug("beginWSSetup: Connecting to", uri.spec);
    this._wsListener = new PushWebSocketListener(this);
    this._ws.protocol = "push-notification";

    try {
      // Grab a wakelock before we open the socket to ensure we don't go to
      // sleep before connection the is opened.
      this._ws.asyncOpen(uri, uri.spec, {}, 0, this._wsListener, null);
      this._currentState = STATE_WAITING_FOR_WS_START;
    } catch (e) {
      lazy.console.error(
        "beginWSSetup: Error opening websocket.",
        "asyncOpen failed",
        e
      );
      this._reconnect();
    }
  },

  connect(broadcastListeners) {
    lazy.console.debug("connect()", broadcastListeners);
    this._broadcastListeners = broadcastListeners;
    this._beginWSSetup();
  },

  isConnected() {
    return !!this._ws;
  },

  /**
   * Protocol handler invoked by server message.
   */
  _handleHelloReply(reply) {
    lazy.console.debug("handleHelloReply()");
    if (this._currentState != STATE_WAITING_FOR_HELLO) {
      lazy.console.error(
        "handleHelloReply: Unexpected state",
        this._currentState,
        "(expected STATE_WAITING_FOR_HELLO)"
      );
      this._shutdownWS();
      return;
    }

    if (typeof reply.uaid !== "string") {
      lazy.console.error("handleHelloReply: Received invalid UAID", reply.uaid);
      this._shutdownWS();
      return;
    }

    if (reply.uaid === "") {
      lazy.console.error("handleHelloReply: Received empty UAID");
      this._shutdownWS();
      return;
    }

    // To avoid sticking extra large values sent by an evil server into prefs.
    if (reply.uaid.length > 128) {
      lazy.console.error(
        "handleHelloReply: UAID received from server was too long",
        reply.uaid
      );
      this._shutdownWS();
      return;
    }

    let sendRequests = () => {
      if (this._notifyRequestQueue) {
        this._notifyRequestQueue();
        this._notifyRequestQueue = null;
      }
      this._sendPendingRequests();
    };

    function finishHandshake() {
      this._UAID = reply.uaid;
      this._currentState = STATE_READY;
      prefs.addObserver("userAgentID", this);

      // Handle broadcasts received in response to the "hello" message.
      if (!lazy.ObjectUtils.isEmpty(reply.broadcasts)) {
        // The reply isn't technically a broadcast message, but it has
        // the shape of a broadcast message (it has a broadcasts field).
        const context = { phase: lazy.pushBroadcastService.PHASES.HELLO };
        this._mainPushService.receivedBroadcastMessage(reply, context);
      }

      this._dataEnabled = !!reply.use_webpush;
      if (this._dataEnabled) {
        this._mainPushService
          .getAllUnexpired()
          .then(records =>
            Promise.all(
              records.map(record =>
                this._mainPushService.ensureCrypto(record).catch(error => {
                  lazy.console.error(
                    "finishHandshake: Error updating record",
                    record.keyID,
                    error
                  );
                })
              )
            )
          )
          .then(sendRequests);
      } else {
        sendRequests();
      }
    }

    // By this point we've got a UAID from the server that we are ready to
    // accept.
    //
    // We unconditionally drop all existing registrations and notify service
    // workers if we receive a new UAID. This ensures we expunge all stale
    // registrations if the `userAgentID` pref is reset.
    if (this._UAID != reply.uaid) {
      lazy.console.debug("handleHelloReply: Received new UAID");

      this._mainPushService
        .dropUnexpiredRegistrations()
        .then(finishHandshake.bind(this));

      return;
    }

    // otherwise we are good to go
    finishHandshake.bind(this)();
  },

  /**
   * Protocol handler invoked by server message.
   */
  _handleRegisterReply(reply) {
    lazy.console.debug("handleRegisterReply()");

    let tmp = this._takeRequestForReply(reply);
    if (!tmp) {
      return;
    }

    if (reply.status == 200) {
      try {
        Services.io.newURI(reply.pushEndpoint);
      } catch (e) {
        tmp.reject(new Error("Invalid push endpoint: " + reply.pushEndpoint));
        return;
      }

      let record = new PushRecordWebSocket({
        channelID: reply.channelID,
        pushEndpoint: reply.pushEndpoint,
        scope: tmp.record.scope,
        originAttributes: tmp.record.originAttributes,
        version: null,
        systemRecord: tmp.record.systemRecord,
        appServerKey: tmp.record.appServerKey,
        ctime: Date.now(),
      });
      tmp.resolve(record);
    } else {
      lazy.console.error(
        "handleRegisterReply: Unexpected server response",
        reply
      );
      tmp.reject(
        new Error("Wrong status code for register reply: " + reply.status)
      );
    }
  },

  _handleUnregisterReply(reply) {
    lazy.console.debug("handleUnregisterReply()");

    let request = this._takeRequestForReply(reply);
    if (!request) {
      return;
    }

    let success = reply.status === 200;
    request.resolve(success);
  },

  _handleDataUpdate(update) {
    let promise;
    if (typeof update.channelID != "string") {
      lazy.console.warn(
        "handleDataUpdate: Discarding update without channel ID",
        update
      );
      return;
    }
    function updateRecord(record) {
      // Ignore messages that we've already processed. This can happen if the
      // connection drops between notifying the service worker and acking the
      // the message. In that case, the server will re-send the message on
      // reconnect.
      if (record.hasRecentMessageID(update.version)) {
        lazy.console.warn(
          "handleDataUpdate: Ignoring duplicate message",
          update.version
        );
        return null;
      }
      record.noteRecentMessageID(update.version);
      return record;
    }
    if (typeof update.data != "string") {
      promise = this._mainPushService.receivedPushMessage(
        update.channelID,
        update.version,
        null,
        null,
        updateRecord
      );
    } else {
      let message = ChromeUtils.base64URLDecode(update.data, {
        // The Push server may append padding.
        padding: "ignore",
      });
      promise = this._mainPushService.receivedPushMessage(
        update.channelID,
        update.version,
        update.headers,
        message,
        updateRecord
      );
    }
    promise
      .then(
        status => {
          this._sendAck(update.channelID, update.version, status);
        },
        err => {
          lazy.console.error(
            "handleDataUpdate: Error delivering message",
            update,
            err
          );
          this._sendAck(
            update.channelID,
            update.version,
            Ci.nsIPushErrorReporter.ACK_DECRYPTION_ERROR
          );
        }
      )
      .catch(err => {
        lazy.console.error(
          "handleDataUpdate: Error acknowledging message",
          update,
          err
        );
      });
  },

  /**
   * Protocol handler invoked by server message.
   */
  _handleNotificationReply(reply) {
    lazy.console.debug("handleNotificationReply()");
    if (this._dataEnabled) {
      this._handleDataUpdate(reply);
      return;
    }

    if (typeof reply.updates !== "object") {
      lazy.console.warn(
        "handleNotificationReply: Missing updates",
        reply.updates
      );
      return;
    }

    lazy.console.debug("handleNotificationReply: Got updates", reply.updates);
    for (let i = 0; i < reply.updates.length; i++) {
      let update = reply.updates[i];
      lazy.console.debug("handleNotificationReply: Handling update", update);
      if (typeof update.channelID !== "string") {
        lazy.console.debug(
          "handleNotificationReply: Invalid update at index",
          i,
          update
        );
        continue;
      }

      if (update.version === undefined) {
        lazy.console.debug("handleNotificationReply: Missing version", update);
        continue;
      }

      let version = update.version;

      if (typeof version === "string") {
        version = parseInt(version, 10);
      }

      if (typeof version === "number" && version >= 0) {
        // FIXME(nsm): this relies on app update notification being infallible!
        // eventually fix this
        this._receivedUpdate(update.channelID, version);
      }
    }
  },

  _handleBroadcastReply(reply) {
    let phase = lazy.pushBroadcastService.PHASES.BROADCAST;
    // Check if this reply is the result of registration.
    for (const id of Object.keys(reply.broadcasts)) {
      const wasRegistering = this._currentlyRegistering.delete(id);
      if (wasRegistering) {
        // If we get multiple broadcasts and only one is "registering",
        // then we consider the phase to be REGISTER for all of them.
        // It is acceptable since registrations do not happen so often,
        // and are all very likely to occur soon after browser startup.
        phase = lazy.pushBroadcastService.PHASES.REGISTER;
      }
    }
    const context = { phase };
    this._mainPushService.receivedBroadcastMessage(reply, context);
  },

  reportDeliveryError(messageID, reason) {
    lazy.console.debug("reportDeliveryError()");
    let code = kDELIVERY_REASON_TO_CODE[reason];
    if (!code) {
      throw new Error("Invalid delivery error reason");
    }
    let data = { messageType: "nack", version: messageID, code };
    this._queueRequest(data);
  },

  _sendAck(channelID, version, status) {
    lazy.console.debug("sendAck()");
    let code = kACK_STATUS_TO_CODE[status];
    if (!code) {
      throw new Error("Invalid ack status");
    }
    let data = { messageType: "ack", updates: [{ channelID, version, code }] };
    this._queueRequest(data);
  },

  _generateID() {
    // generateUUID() gives a UUID surrounded by {...}, slice them off.
    return Services.uuid.generateUUID().toString().slice(1, -1);
  },

  register(record) {
    lazy.console.debug("register() ", record);

    let data = { channelID: this._generateID(), messageType: "register" };

    if (record.appServerKey) {
      data.key = ChromeUtils.base64URLEncode(record.appServerKey, {
        // The Push server requires padding.
        pad: true,
      });
    }

    return this._sendRequestForReply(record, data).then(record => {
      if (!this._dataEnabled) {
        return record;
      }
      return PushCrypto.generateKeys().then(([publicKey, privateKey]) => {
        record.p256dhPublicKey = publicKey;
        record.p256dhPrivateKey = privateKey;
        record.authenticationSecret = PushCrypto.generateAuthenticationSecret();
        return record;
      });
    });
  },

  unregister(record, reason) {
    lazy.console.debug("unregister() ", record, reason);

    return Promise.resolve().then(_ => {
      let code = kUNREGISTER_REASON_TO_CODE[reason];
      if (!code) {
        throw new Error("Invalid unregister reason");
      }
      let data = {
        channelID: record.channelID,
        messageType: "unregister",
        code,
      };

      return this._sendRequestForReply(record, data);
    });
  },

  _queueStart: Promise.resolve(),
  _notifyRequestQueue: null,
  _queue: null,
  _enqueue(op) {
    lazy.console.debug("enqueue()");
    if (!this._queue) {
      this._queue = this._queueStart;
    }
    this._queue = this._queue.then(op).catch(_ => {});
  },

  /** Sends a request to the server. */
  _send(data) {
    if (this._currentState != STATE_READY) {
      lazy.console.warn(
        "send: Unexpected state; ignoring message",
        this._currentState
      );
      return;
    }
    if (!this._requestHasReply(data)) {
      this._wsSendMessage(data);
      return;
    }
    // If we're expecting a reply, check that we haven't cancelled the request.
    let key = this._makePendingRequestKey(data);
    if (!this._pendingRequests.has(key)) {
      lazy.console.log("send: Request cancelled; ignoring message", key);
      return;
    }
    this._wsSendMessage(data);
  },

  /** Indicates whether a request has a corresponding reply from the server. */
  _requestHasReply(data) {
    return data.messageType == "register" || data.messageType == "unregister";
  },

  /**
   * Sends all pending requests that expect replies. Called after the connection
   * is established and the handshake is complete.
   */
  _sendPendingRequests() {
    this._enqueue(_ => {
      for (let request of this._pendingRequests.values()) {
        this._send(request.data);
      }
    });
  },

  /** Queues an outgoing request, establishing a connection if necessary. */
  _queueRequest(data) {
    lazy.console.debug("queueRequest()", data);

    if (this._currentState == STATE_READY) {
      // If we're ready, no need to queue; just send the request.
      this._send(data);
      return;
    }

    // Otherwise, we're still setting up. If we don't have a request queue,
    // make one now.
    if (!this._notifyRequestQueue) {
      let promise = new Promise(resolve => {
        this._notifyRequestQueue = resolve;
      });
      this._enqueue(_ => promise);
    }

    let isRequest = this._requestHasReply(data);
    if (!isRequest) {
      // Don't queue requests, since they're stored in `_pendingRequests`, and
      // `_sendPendingRequests` will send them after reconnecting. Without this
      // check, we'd send requests twice.
      this._enqueue(_ => this._send(data));
    }

    if (!this._ws) {
      // This will end up calling notifyRequestQueue().
      this._beginWSSetup();
      // If beginWSSetup does not succeed to make ws, notifyRequestQueue will
      // not be call.
      if (!this._ws && this._notifyRequestQueue) {
        this._notifyRequestQueue();
        this._notifyRequestQueue = null;
      }
    }
  },

  _receivedUpdate(aChannelID, aLatestVersion) {
    lazy.console.debug(
      "receivedUpdate: Updating",
      aChannelID,
      "->",
      aLatestVersion
    );

    this._mainPushService
      .receivedPushMessage(aChannelID, "", null, null, record => {
        if (record.version === null || record.version < aLatestVersion) {
          lazy.console.debug(
            "receivedUpdate: Version changed for",
            aChannelID,
            aLatestVersion
          );
          record.version = aLatestVersion;
          return record;
        }
        lazy.console.debug(
          "receivedUpdate: No significant version change for",
          aChannelID,
          aLatestVersion
        );
        return null;
      })
      .then(status => {
        this._sendAck(aChannelID, aLatestVersion, status);
      })
      .catch(err => {
        lazy.console.error(
          "receivedUpdate: Error acknowledging message",
          aChannelID,
          aLatestVersion,
          err
        );
      });
  },

  // begin Push protocol handshake
  _wsOnStart() {
    lazy.console.debug("wsOnStart()");

    if (this._currentState != STATE_WAITING_FOR_WS_START) {
      lazy.console.error(
        "wsOnStart: NOT in STATE_WAITING_FOR_WS_START. Current",
        "state",
        this._currentState,
        "Skipping"
      );
      return;
    }

    this._mainPushService
      .getAllUnexpired()
      .then(
        records => this._sendHello(records),
        err => {
          lazy.console.warn(
            "Error fetching existing records before handshake; assuming none",
            err
          );
          this._sendHello([]);
        }
      )
      .catch(err => {
        // If we failed to send the handshake, back off and reconnect.
        lazy.console.warn("Failed to send handshake; reconnecting", err);
        this._reconnect();
      });
  },

  /**
   * Sends a `hello` handshake to the server.
   *
   * @param {Array<PushRecordWebSocket>} An array of records for existing
   *        subscriptions, used to determine whether to rotate our UAID.
   */
  _sendHello(records) {
    let data = {
      messageType: "hello",
      broadcasts: this._broadcastListeners,
      use_webpush: true,
    };

    if (records.length && this._UAID) {
      // Only send our UAID if we have existing push subscriptions, to
      // avoid tying a persistent identifier to the connection (bug
      // 1617136). The push server will issue our client a new UAID in
      // the `hello` response, which we'll store until either the next
      // time we reconnect, or the user subscribes to push. Once we have a
      // push subscription, we'll stop rotating the UAID when we connect,
      // so that we can receive push messages for them.
      data.uaid = this._UAID;
    }

    this._wsSendMessage(data);
    this._currentState = STATE_WAITING_FOR_HELLO;
  },

  /**
   * This statusCode is not the websocket protocol status code, but the TCP
   * connection close status code.
   *
   * If we do not explicitly call ws.close() then statusCode is always
   * NS_BASE_STREAM_CLOSED, even on a successful close.
   */
  _wsOnStop(context, statusCode) {
    lazy.console.debug("wsOnStop()");

    if (statusCode != Cr.NS_OK && !this._skipReconnect) {
      lazy.console.debug(
        "wsOnStop: Reconnecting after socket error",
        statusCode
      );
      this._reconnect();
      return;
    }

    this._shutdownWS();
  },

  _wsOnMessageAvailable(context, message) {
    lazy.console.debug("wsOnMessageAvailable()", message);

    // Clearing the last ping time indicates we're no longer waiting for a pong.
    this._lastPingTime = 0;

    let reply;
    try {
      reply = JSON.parse(message);
    } catch (e) {
      lazy.console.warn("wsOnMessageAvailable: Invalid JSON", message, e);
      return;
    }

    // If we receive a message, we know the connection succeeded. Reset the
    // connection attempt and ping interval counters.
    this._retryFailCount = 0;

    let doNotHandle = false;
    if (
      message === "{}" ||
      reply.messageType === undefined ||
      reply.messageType === "ping" ||
      typeof reply.messageType != "string"
    ) {
      lazy.console.debug("wsOnMessageAvailable: Pong received");
      doNotHandle = true;
    }

    // Reset the ping timer.  Note: This path is executed at every step of the
    // handshake, so this timer does not need to be set explicitly at startup.
    this._startPingTimer();

    // If it is a ping, do not handle the message.
    if (doNotHandle) {
      if (!this._hasPendingRequests()) {
        this._requestTimeoutTimer.cancel();
      }
      return;
    }

    // An allowlist of protocol handlers. Add to these if new messages are added
    // in the protocol.
    let handlers = [
      "Hello",
      "Register",
      "Unregister",
      "Notification",
      "Broadcast",
    ];

    // Build up the handler name to call from messageType.
    // e.g. messageType == "register" -> _handleRegisterReply.
    let handlerName =
      reply.messageType[0].toUpperCase() +
      reply.messageType.slice(1).toLowerCase();

    if (!handlers.includes(handlerName)) {
      lazy.console.warn(
        "wsOnMessageAvailable: No allowlisted handler",
        handlerName,
        "for message",
        reply.messageType
      );
      return;
    }

    let handler = "_handle" + handlerName + "Reply";

    if (typeof this[handler] !== "function") {
      lazy.console.warn(
        "wsOnMessageAvailable: Handler",
        handler,
        "allowlisted but not implemented"
      );
      return;
    }

    this[handler](reply);
  },

  /**
   * The websocket should never be closed. Since we don't call ws.close(),
   * _wsOnStop() receives error code NS_BASE_STREAM_CLOSED (see comment in that
   * function), which calls reconnect and re-establishes the WebSocket
   * connection.
   *
   * If the server requested that we back off, we won't reconnect until the
   * next network state change event, or until we need to send a new register
   * request.
   */
  _wsOnServerClose(context, aStatusCode, aReason) {
    lazy.console.debug("wsOnServerClose()", aStatusCode, aReason);

    if (aStatusCode == kBACKOFF_WS_STATUS_CODE) {
      lazy.console.debug("wsOnServerClose: Skipping automatic reconnect");
      this._skipReconnect = true;
    }
  },

  /**
   * Rejects all pending register requests with errors.
   */
  _cancelPendingRequests() {
    for (let request of this._pendingRequests.values()) {
      request.reject(new Error("Request aborted"));
    }
    this._pendingRequests.clear();
  },

  /** Creates a case-insensitive map key for a request that expects a reply. */
  _makePendingRequestKey(data) {
    return (data.messageType + "|" + data.channelID).toLowerCase();
  },

  /** Sends a request and waits for a reply from the server. */
  _sendRequestForReply(record, data) {
    return Promise.resolve().then(_ => {
      // start the timer since we now have at least one request
      this._startRequestTimeoutTimer();

      let key = this._makePendingRequestKey(data);
      if (!this._pendingRequests.has(key)) {
        let request = {
          data,
          record,
          ctime: Date.now(),
        };
        request.promise = new Promise((resolve, reject) => {
          request.resolve = resolve;
          request.reject = reject;
        });
        this._pendingRequests.set(key, request);
        this._queueRequest(data);
      }

      return this._pendingRequests.get(key).promise;
    });
  },

  /** Removes and returns a pending request for a server reply. */
  _takeRequestForReply(reply) {
    if (typeof reply.channelID !== "string") {
      return null;
    }
    let key = this._makePendingRequestKey(reply);
    let request = this._pendingRequests.get(key);
    if (!request) {
      return null;
    }
    this._pendingRequests.delete(key);
    if (!this._hasPendingRequests()) {
      this._requestTimeoutTimer.cancel();
    }
    return request;
  },

  sendSubscribeBroadcast(serviceId, version) {
    this._currentlyRegistering.add(serviceId);
    let data = {
      messageType: "broadcast_subscribe",
      broadcasts: {
        [serviceId]: version,
      },
    };

    this._queueRequest(data);
  },
};

function PushRecordWebSocket(record) {
  PushRecord.call(this, record);
  this.channelID = record.channelID;
  this.version = record.version;
}

PushRecordWebSocket.prototype = Object.create(PushRecord.prototype, {
  keyID: {
    get() {
      return this.channelID;
    },
  },
});

PushRecordWebSocket.prototype.toSubscription = function () {
  let subscription = PushRecord.prototype.toSubscription.call(this);
  subscription.version = this.version;
  return subscription;
};
PK
!<P�33modules/PushDB.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { IndexedDBHelper } from "resource://gre/modules/IndexedDBHelper.sys.mjs";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  let { ConsoleAPI } = ChromeUtils.importESModule(
    "resource://gre/modules/Console.sys.mjs"
  );
  return new ConsoleAPI({
    maxLogLevelPref: "dom.push.loglevel",
    prefix: "PushDB",
  });
});

export function PushDB(dbName, dbVersion, dbStoreName, keyPath, model) {
  lazy.console.debug("PushDB()");
  this._dbStoreName = dbStoreName;
  this._keyPath = keyPath;
  this._model = model;

  // set the indexeddb database
  this.initDBHelper(dbName, dbVersion, [dbStoreName]);
}

PushDB.prototype = {
  __proto__: IndexedDBHelper.prototype,

  toPushRecord(record) {
    if (!record) {
      return null;
    }
    return new this._model(record);
  },

  isValidRecord(record) {
    return (
      record &&
      typeof record.scope == "string" &&
      typeof record.originAttributes == "string" &&
      record.quota >= 0 &&
      typeof record[this._keyPath] == "string"
    );
  },

  upgradeSchema(aTransaction, aDb, aOldVersion) {
    if (aOldVersion <= 3) {
      // XXXnsm We haven't shipped Push during this upgrade, so I'm just going to throw old
      // registrations away without even informing the app.
      if (aDb.objectStoreNames.contains(this._dbStoreName)) {
        aDb.deleteObjectStore(this._dbStoreName);
      }

      let objectStore = aDb.createObjectStore(this._dbStoreName, {
        keyPath: this._keyPath,
      });

      // index to fetch records based on endpoints. used by unregister
      objectStore.createIndex("pushEndpoint", "pushEndpoint", { unique: true });

      // index to fetch records by identifiers.
      // In the current security model, the originAttributes distinguish between
      // different 'apps' on the same origin. Since ServiceWorkers are
      // same-origin to the scope they are registered for, the attributes and
      // scope are enough to reconstruct a valid principal.
      objectStore.createIndex("identifiers", ["scope", "originAttributes"], {
        unique: true,
      });
      objectStore.createIndex("originAttributes", "originAttributes", {
        unique: false,
      });
    }

    if (aOldVersion < 4) {
      let objectStore = aTransaction.objectStore(this._dbStoreName);

      // index to fetch active and expired registrations.
      objectStore.createIndex("quota", "quota", { unique: false });
    }
  },

  /*
   * @param aRecord
   *        The record to be added.
   */

  put(aRecord) {
    lazy.console.debug("put()", aRecord);
    if (!this.isValidRecord(aRecord)) {
      return Promise.reject(
        new TypeError(
          "Scope, originAttributes, and quota are required! " +
            JSON.stringify(aRecord)
        )
      );
    }

    return new Promise((resolve, reject) =>
      this.newTxn(
        "readwrite",
        this._dbStoreName,
        (aTxn, aStore) => {
          aTxn.result = undefined;

          aStore.put(aRecord).onsuccess = aEvent => {
            lazy.console.debug(
              "put: Request successful. Updated record",
              aEvent.target.result
            );
            aTxn.result = this.toPushRecord(aRecord);
          };
        },
        resolve,
        reject
      )
    );
  },

  /*
   * @param aKeyID
   *        The ID of record to be deleted.
   */
  delete(aKeyID) {
    lazy.console.debug("delete()");

    return new Promise((resolve, reject) =>
      this.newTxn(
        "readwrite",
        this._dbStoreName,
        (aTxn, aStore) => {
          lazy.console.debug("delete: Removing record", aKeyID);
          aStore.get(aKeyID).onsuccess = event => {
            aTxn.result = this.toPushRecord(event.target.result);
            aStore.delete(aKeyID);
          };
        },
        resolve,
        reject
      )
    );
  },

  // testFn(record) is called with a database record and should return true if
  // that record should be deleted.
  clearIf(testFn) {
    lazy.console.debug("clearIf()");
    return new Promise((resolve, reject) =>
      this.newTxn(
        "readwrite",
        this._dbStoreName,
        (aTxn, aStore) => {
          aTxn.result = undefined;

          aStore.openCursor().onsuccess = event => {
            let cursor = event.target.result;
            if (cursor) {
              let record = this.toPushRecord(cursor.value);
              if (testFn(record)) {
                let deleteRequest = cursor.delete();
                deleteRequest.onerror = e => {
                  lazy.console.error(
                    "clearIf: Error removing record",
                    record.keyID,
                    e
                  );
                };
              }
              cursor.continue();
            }
          };
        },
        resolve,
        reject
      )
    );
  },

  getByPushEndpoint(aPushEndpoint) {
    lazy.console.debug("getByPushEndpoint()");

    return new Promise((resolve, reject) =>
      this.newTxn(
        "readonly",
        this._dbStoreName,
        (aTxn, aStore) => {
          aTxn.result = undefined;

          let index = aStore.index("pushEndpoint");
          index.get(aPushEndpoint).onsuccess = aEvent => {
            let record = this.toPushRecord(aEvent.target.result);
            lazy.console.debug("getByPushEndpoint: Got record", record);
            aTxn.result = record;
          };
        },
        resolve,
        reject
      )
    );
  },

  getByKeyID(aKeyID) {
    lazy.console.debug("getByKeyID()");

    return new Promise((resolve, reject) =>
      this.newTxn(
        "readonly",
        this._dbStoreName,
        (aTxn, aStore) => {
          aTxn.result = undefined;

          aStore.get(aKeyID).onsuccess = aEvent => {
            let record = this.toPushRecord(aEvent.target.result);
            lazy.console.debug("getByKeyID: Got record", record);
            aTxn.result = record;
          };
        },
        resolve,
        reject
      )
    );
  },

  /**
   * Iterates over all records associated with an origin.
   *
   * @param {String} origin The origin, matched as a prefix against the scope.
   * @param {String} originAttributes Additional origin attributes. Requires
   *  an exact match.
   * @param {Function} callback A function with the signature `(record,
   *  cursor)`, called for each record. `record` is the registration, and
   *  `cursor` is an `IDBCursor`.
   * @returns {Promise} Resolves once all records have been processed.
   */
  forEachOrigin(origin, originAttributes, callback) {
    lazy.console.debug("forEachOrigin()");

    return new Promise((resolve, reject) =>
      this.newTxn(
        "readwrite",
        this._dbStoreName,
        (aTxn, aStore) => {
          aTxn.result = undefined;

          let index = aStore.index("identifiers");
          let range = IDBKeyRange.bound(
            [origin, originAttributes],
            [origin + "\x7f", originAttributes]
          );
          index.openCursor(range).onsuccess = event => {
            let cursor = event.target.result;
            if (!cursor) {
              return;
            }
            callback(this.toPushRecord(cursor.value), cursor);
            cursor.continue();
          };
        },
        resolve,
        reject
      )
    );
  },

  // Perform a unique match against { scope, originAttributes }
  getByIdentifiers(aPageRecord) {
    lazy.console.debug("getByIdentifiers()", aPageRecord);
    if (!aPageRecord.scope || aPageRecord.originAttributes == undefined) {
      lazy.console.error(
        "getByIdentifiers: Scope and originAttributes are required",
        aPageRecord
      );
      return Promise.reject(new TypeError("Invalid page record"));
    }

    return new Promise((resolve, reject) =>
      this.newTxn(
        "readonly",
        this._dbStoreName,
        (aTxn, aStore) => {
          aTxn.result = undefined;

          let index = aStore.index("identifiers");
          let request = index.get(
            IDBKeyRange.only([aPageRecord.scope, aPageRecord.originAttributes])
          );
          request.onsuccess = aEvent => {
            aTxn.result = this.toPushRecord(aEvent.target.result);
          };
        },
        resolve,
        reject
      )
    );
  },

  _getAllByKey(aKeyName, aKeyValue) {
    return new Promise((resolve, reject) =>
      this.newTxn(
        "readonly",
        this._dbStoreName,
        (aTxn, aStore) => {
          aTxn.result = undefined;

          let index = aStore.index(aKeyName);
          // It seems ok to use getAll here, since unlike contacts or other
          // high storage APIs, we don't expect more than a handful of
          // registrations per domain, and usually only one.
          let getAllReq = index.mozGetAll(aKeyValue);
          getAllReq.onsuccess = aEvent => {
            aTxn.result = aEvent.target.result.map(record =>
              this.toPushRecord(record)
            );
          };
        },
        resolve,
        reject
      )
    );
  },

  // aOriginAttributes must be a string!
  getAllByOriginAttributes(aOriginAttributes) {
    if (typeof aOriginAttributes !== "string") {
      return Promise.reject("Expected string!");
    }
    return this._getAllByKey("originAttributes", aOriginAttributes);
  },

  getAllKeyIDs() {
    lazy.console.debug("getAllKeyIDs()");

    return new Promise((resolve, reject) =>
      this.newTxn(
        "readonly",
        this._dbStoreName,
        (aTxn, aStore) => {
          aTxn.result = undefined;
          aStore.mozGetAll().onsuccess = event => {
            aTxn.result = event.target.result.map(record =>
              this.toPushRecord(record)
            );
          };
        },
        resolve,
        reject
      )
    );
  },

  _getAllByPushQuota(range) {
    lazy.console.debug("getAllByPushQuota()");

    return new Promise((resolve, reject) =>
      this.newTxn(
        "readonly",
        this._dbStoreName,
        (aTxn, aStore) => {
          aTxn.result = [];

          let index = aStore.index("quota");
          index.openCursor(range).onsuccess = event => {
            let cursor = event.target.result;
            if (cursor) {
              aTxn.result.push(this.toPushRecord(cursor.value));
              cursor.continue();
            }
          };
        },
        resolve,
        reject
      )
    );
  },

  getAllUnexpired() {
    lazy.console.debug("getAllUnexpired()");
    return this._getAllByPushQuota(IDBKeyRange.lowerBound(1));
  },

  getAllExpired() {
    lazy.console.debug("getAllExpired()");
    return this._getAllByPushQuota(IDBKeyRange.only(0));
  },

  /**
   * Updates an existing push registration.
   *
   * @param {String} aKeyID The registration ID.
   * @param {Function} aUpdateFunc A function that receives the existing
   *  registration record as its argument, and returns a new record.
   * @returns {Promise} A promise resolved with either the updated record.
   *  Rejects if the record does not exist, or the function returns an invalid
   *  record.
   */
  update(aKeyID, aUpdateFunc) {
    return new Promise((resolve, reject) =>
      this.newTxn(
        "readwrite",
        this._dbStoreName,
        (aTxn, aStore) => {
          aStore.get(aKeyID).onsuccess = aEvent => {
            aTxn.result = undefined;

            let record = aEvent.target.result;
            if (!record) {
              throw new Error("Record " + aKeyID + " does not exist");
            }
            let newRecord = aUpdateFunc(this.toPushRecord(record));
            if (!this.isValidRecord(newRecord)) {
              lazy.console.error(
                "update: Ignoring invalid update",
                aKeyID,
                newRecord
              );
              throw new Error("Invalid update for record " + aKeyID);
            }
            function putRecord() {
              let req = aStore.put(newRecord);
              req.onsuccess = () => {
                lazy.console.debug(
                  "update: Update successful",
                  aKeyID,
                  newRecord
                );
                aTxn.result = newRecord;
              };
            }
            if (aKeyID === newRecord.keyID) {
              putRecord();
            } else {
              // If we changed the primary key, delete the old record to avoid
              // unique constraint errors.
              aStore.delete(aKeyID).onsuccess = putRecord;
            }
          };
        },
        resolve,
        reject
      )
    );
  },

  drop() {
    lazy.console.debug("drop()");

    return new Promise((resolve, reject) =>
      this.newTxn(
        "readwrite",
        this._dbStoreName,
        function txnCb(aTxn, aStore) {
          aStore.clear();
        },
        resolve,
        reject
      )
    );
  },
};
PK
!<i��Ԡ$�$modules/PushRecord.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventDispatcher: "resource://gre/modules/Messaging.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

const prefs = Services.prefs.getBranch("dom.push.");

/**
 * The push subscription record, stored in IndexedDB.
 */
export function PushRecord(props) {
  this.pushEndpoint = props.pushEndpoint;
  this.scope = props.scope;
  this.originAttributes = props.originAttributes;
  this.pushCount = props.pushCount || 0;
  this.lastPush = props.lastPush || 0;
  this.p256dhPublicKey = props.p256dhPublicKey;
  this.p256dhPrivateKey = props.p256dhPrivateKey;
  this.authenticationSecret = props.authenticationSecret;
  this.systemRecord = !!props.systemRecord;
  this.appServerKey = props.appServerKey;
  this.recentMessageIDs = props.recentMessageIDs;
  this.setQuota(props.quota);
  this.ctime = typeof props.ctime === "number" ? props.ctime : 0;
}

PushRecord.prototype = {
  setQuota(suggestedQuota) {
    if (this.quotaApplies()) {
      let quota = +suggestedQuota;
      this.quota =
        quota >= 0 ? quota : prefs.getIntPref("maxQuotaPerSubscription");
    } else {
      this.quota = Infinity;
    }
  },

  resetQuota() {
    this.quota = this.quotaApplies()
      ? prefs.getIntPref("maxQuotaPerSubscription")
      : Infinity;
  },

  updateQuota(lastVisit) {
    if (this.isExpired() || !this.quotaApplies()) {
      // Ignore updates if the registration is already expired, or isn't
      // subject to quota.
      return;
    }
    if (lastVisit < 0) {
      // If the user cleared their history, but retained the push permission,
      // mark the registration as expired.
      this.quota = 0;
      return;
    }
    if (lastVisit > this.lastPush) {
      // If the user visited the site since the last time we received a
      // notification, reset the quota. `Math.max(0, ...)` ensures the
      // last visit date isn't in the future.
      let daysElapsed = Math.max(
        0,
        (Date.now() - lastVisit) / 24 / 60 / 60 / 1000
      );
      this.quota = Math.min(
        Math.round(8 * Math.pow(daysElapsed, -0.8)),
        prefs.getIntPref("maxQuotaPerSubscription")
      );
    }
  },

  receivedPush(lastVisit) {
    this.updateQuota(lastVisit);
    this.pushCount++;
    this.lastPush = Date.now();
  },

  /**
   * Records a message ID sent to this push registration. We track the last few
   * messages sent to each registration to avoid firing duplicate events for
   * unacknowledged messages.
   */
  noteRecentMessageID(id) {
    if (this.recentMessageIDs) {
      this.recentMessageIDs.unshift(id);
    } else {
      this.recentMessageIDs = [id];
    }
    // Drop older message IDs from the end of the list.
    let maxRecentMessageIDs = Math.min(
      this.recentMessageIDs.length,
      Math.max(prefs.getIntPref("maxRecentMessageIDsPerSubscription"), 0)
    );
    this.recentMessageIDs.length = maxRecentMessageIDs || 0;
  },

  hasRecentMessageID(id) {
    return this.recentMessageIDs && this.recentMessageIDs.includes(id);
  },

  reduceQuota() {
    if (!this.quotaApplies()) {
      return;
    }
    this.quota = Math.max(this.quota - 1, 0);
  },

  /**
   * Queries the Places database for the last time a user visited the site
   * associated with a push registration.
   *
   * @returns {Promise} A promise resolved with either the last time the user
   *  visited the site, or `-Infinity` if the site is not in the user's history.
   *  The time is expressed in milliseconds since Epoch.
   */
  async getLastVisit() {
    if (!this.quotaApplies() || this.isTabOpen()) {
      // If the registration isn't subject to quota, or the user already
      // has the site open, skip expensive database queries.
      return Date.now();
    }

    if (AppConstants.MOZ_GECKOVIEW_HISTORY) {
      let result = await lazy.EventDispatcher.instance.sendRequestForResult({
        type: "History:GetPrePathLastVisitedTimeMilliseconds",
        prePath: this.uri.prePath,
      });
      return result == 0 ? -Infinity : result;
    }

    // Places History transition types that can fire a
    // `pushsubscriptionchange` event when the user visits a site with expired push
    // registrations. Visits only count if the user sees the origin in the address
    // bar. This excludes embedded resources, downloads, and framed links.
    const QUOTA_REFRESH_TRANSITIONS_SQL = [
      Ci.nsINavHistoryService.TRANSITION_LINK,
      Ci.nsINavHistoryService.TRANSITION_TYPED,
      Ci.nsINavHistoryService.TRANSITION_BOOKMARK,
      Ci.nsINavHistoryService.TRANSITION_REDIRECT_PERMANENT,
      Ci.nsINavHistoryService.TRANSITION_REDIRECT_TEMPORARY,
    ].join(",");

    let db = await lazy.PlacesUtils.promiseDBConnection();
    // We're using a custom query instead of `nsINavHistoryQueryOptions`
    // because the latter doesn't expose a way to filter by transition type:
    // `setTransitions` performs a logical "and," but we want an "or." We
    // also avoid an unneeded left join with favicons, and an `ORDER BY`
    // clause that emits a suboptimal index warning.
    let rows = await db.executeCached(
      `SELECT MAX(visit_date) AS lastVisit
       FROM moz_places p
       JOIN moz_historyvisits ON p.id = place_id
       WHERE rev_host = get_unreversed_host(:host || '.') || '.'
         AND url BETWEEN :prePath AND :prePath || X'FFFF'
         AND visit_type IN (${QUOTA_REFRESH_TRANSITIONS_SQL})
      `,
      {
        // Restrict the query to all pages for this origin.
        host: this.uri.host,
        prePath: this.uri.prePath,
      }
    );

    if (!rows.length) {
      return -Infinity;
    }
    // Places records times in microseconds.
    let lastVisit = rows[0].getResultByName("lastVisit");

    return lastVisit / 1000;
  },

  isTabOpen() {
    for (let window of Services.wm.getEnumerator("navigator:browser")) {
      if (window.closed || lazy.PrivateBrowsingUtils.isWindowPrivate(window)) {
        continue;
      }
      for (let tab of window.gBrowser.tabs) {
        let tabURI = tab.linkedBrowser.currentURI;
        if (tabURI.prePath == this.uri.prePath) {
          return true;
        }
      }
    }
    return false;
  },

  /**
   * Indicates whether the registration can deliver push messages to its
   * associated service worker. System subscriptions are exempt from the
   * permission check.
   */
  hasPermission() {
    if (
      this.systemRecord ||
      prefs.getBoolPref("testing.ignorePermission", false)
    ) {
      return true;
    }
    let permission = Services.perms.testExactPermissionFromPrincipal(
      this.principal,
      "desktop-notification"
    );
    return permission == Ci.nsIPermissionManager.ALLOW_ACTION;
  },

  quotaChanged() {
    if (!this.hasPermission()) {
      return Promise.resolve(false);
    }
    return this.getLastVisit().then(lastVisit => lastVisit > this.lastPush);
  },

  quotaApplies() {
    return !this.systemRecord;
  },

  isExpired() {
    return this.quota === 0;
  },

  matchesOriginAttributes(pattern) {
    if (this.systemRecord) {
      return false;
    }
    return ChromeUtils.originAttributesMatchPattern(
      this.principal.originAttributes,
      pattern
    );
  },

  hasAuthenticationSecret() {
    return (
      !!this.authenticationSecret && this.authenticationSecret.byteLength == 16
    );
  },

  matchesAppServerKey(key) {
    if (!this.appServerKey) {
      return !key;
    }
    if (!key) {
      return false;
    }
    return (
      this.appServerKey.length === key.length &&
      this.appServerKey.every((value, index) => value === key[index])
    );
  },

  toSubscription() {
    return {
      endpoint: this.pushEndpoint,
      lastPush: this.lastPush,
      pushCount: this.pushCount,
      p256dhKey: this.p256dhPublicKey,
      p256dhPrivateKey: this.p256dhPrivateKey,
      authenticationSecret: this.authenticationSecret,
      appServerKey: this.appServerKey,
      quota: this.quotaApplies() ? this.quota : -1,
      systemRecord: this.systemRecord,
    };
  },
};

// Define lazy getters for the principal and scope URI. IndexedDB can't store
// `nsIPrincipal` objects, so we keep them in a private weak map.
var principals = new WeakMap();
Object.defineProperties(PushRecord.prototype, {
  principal: {
    get() {
      if (this.systemRecord) {
        return Services.scriptSecurityManager.getSystemPrincipal();
      }
      let principal = principals.get(this);
      if (!principal) {
        let uri = Services.io.newURI(this.scope);
        // Allow tests to omit origin attributes.
        let originSuffix = this.originAttributes || "";
        principal = Services.scriptSecurityManager.createContentPrincipal(
          uri,
          ChromeUtils.createOriginAttributesFromOrigin(originSuffix)
        );
        principals.set(this, principal);
      }
      return principal;
    },
    configurable: true,
  },

  uri: {
    get() {
      return this.principal.URI;
    },
    configurable: true,
  },
});
PK
!<`�H@hhmodules/PushCrypto.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "gDOMBundle", () =>
  Services.strings.createBundle("chrome://global/locale/dom/dom.properties")
);

// getCryptoParamsFromHeaders is exported for test purposes.
const UTF8 = new TextEncoder();

const ECDH_KEY = { name: "ECDH", namedCurve: "P-256" };
const ECDSA_KEY = { name: "ECDSA", namedCurve: "P-256" };
const HMAC_SHA256 = { name: "HMAC", hash: "SHA-256" };
const NONCE_INFO = UTF8.encode("Content-Encoding: nonce");

// A default keyid with a name that won't conflict with a real keyid.
const DEFAULT_KEYID = "";

/** Localized error property names. */

// `Encryption` header missing or malformed.
const BAD_ENCRYPTION_HEADER = "PushMessageBadEncryptionHeader";
// `Crypto-Key` or legacy `Encryption-Key` header missing.
const BAD_CRYPTO_KEY_HEADER = "PushMessageBadCryptoKeyHeader";
const BAD_ENCRYPTION_KEY_HEADER = "PushMessageBadEncryptionKeyHeader";
// `Content-Encoding` header missing or contains unsupported encoding.
const BAD_ENCODING_HEADER = "PushMessageBadEncodingHeader";
// `dh` parameter of `Crypto-Key` header missing or not base64url-encoded.
const BAD_DH_PARAM = "PushMessageBadSenderKey";
// `salt` parameter of `Encryption` header missing or not base64url-encoded.
const BAD_SALT_PARAM = "PushMessageBadSalt";
// `rs` parameter of `Encryption` header not a number or less than pad size.
const BAD_RS_PARAM = "PushMessageBadRecordSize";
// Invalid or insufficient padding for encrypted chunk.
const BAD_PADDING = "PushMessageBadPaddingError";
// Generic crypto error.
const BAD_CRYPTO = "PushMessageBadCryptoError";

class CryptoError extends Error {
  /**
   * Creates an error object indicating an incoming push message could not be
   * decrypted.
   *
   * @param {String} message A human-readable error message. This is only for
   * internal module logging, and doesn't need to be localized.
   * @param {String} property The localized property name from `dom.properties`.
   * @param {String...} params Substitutions to insert into the localized
   *  string.
   */
  constructor(message, property, ...params) {
    super(message);
    this.isCryptoError = true;
    this.property = property;
    this.params = params;
  }

  /**
   * Formats a localized string for reporting decryption errors to the Web
   * Console.
   *
   * @param {String} scope The scope of the service worker receiving the
   *  message, prepended to any other substitutions in the string.
   * @returns {String} The localized string.
   */
  format(scope) {
    let params = [scope, ...this.params].map(String);
    return lazy.gDOMBundle.formatStringFromName(this.property, params);
  }
}

function getEncryptionKeyParams(encryptKeyField) {
  if (!encryptKeyField) {
    return null;
  }
  var params = encryptKeyField.split(",");
  return params.reduce((m, p) => {
    var pmap = p.split(";").reduce(parseHeaderFieldParams, {});
    if (pmap.keyid && pmap.dh) {
      m[pmap.keyid] = pmap.dh;
    }
    if (!m[DEFAULT_KEYID] && pmap.dh) {
      m[DEFAULT_KEYID] = pmap.dh;
    }
    return m;
  }, {});
}

function getEncryptionParams(encryptField) {
  if (!encryptField) {
    throw new CryptoError("Missing encryption header", BAD_ENCRYPTION_HEADER);
  }
  var p = encryptField.split(",", 1)[0];
  if (!p) {
    throw new CryptoError(
      "Encryption header missing params",
      BAD_ENCRYPTION_HEADER
    );
  }
  return p.split(";").reduce(parseHeaderFieldParams, {});
}

// Extracts the sender public key, salt, and record size from the payload for the
// aes128gcm scheme.
function getCryptoParamsFromPayload(payload) {
  if (payload.byteLength < 21) {
    throw new CryptoError("Truncated header", BAD_CRYPTO);
  }
  let rs =
    (payload[16] << 24) |
    (payload[17] << 16) |
    (payload[18] << 8) |
    payload[19];
  let keyIdLen = payload[20];
  if (keyIdLen != 65) {
    throw new CryptoError("Invalid sender public key", BAD_DH_PARAM);
  }
  if (payload.byteLength <= 21 + keyIdLen) {
    throw new CryptoError("Truncated payload", BAD_CRYPTO);
  }
  return {
    salt: payload.slice(0, 16),
    rs,
    senderKey: payload.slice(21, 21 + keyIdLen),
    ciphertext: payload.slice(21 + keyIdLen),
  };
}

// Extracts the sender public key, salt, and record size from the `Crypto-Key`,
// `Encryption-Key`, and `Encryption` headers for the aesgcm and aesgcm128
// schemes.
export function getCryptoParamsFromHeaders(headers) {
  if (!headers) {
    return null;
  }

  var keymap;
  if (headers.encoding == AESGCM_ENCODING) {
    // aesgcm uses the Crypto-Key header, 2 bytes for the pad length, and an
    // authentication secret.
    // https://tools.ietf.org/html/draft-ietf-httpbis-encryption-encoding-01
    keymap = getEncryptionKeyParams(headers.crypto_key);
    if (!keymap) {
      throw new CryptoError("Missing Crypto-Key header", BAD_CRYPTO_KEY_HEADER);
    }
  } else if (headers.encoding == AESGCM128_ENCODING) {
    // aesgcm128 uses Encryption-Key, 1 byte for the pad length, and no secret.
    // https://tools.ietf.org/html/draft-thomson-http-encryption-02
    keymap = getEncryptionKeyParams(headers.encryption_key);
    if (!keymap) {
      throw new CryptoError(
        "Missing Encryption-Key header",
        BAD_ENCRYPTION_KEY_HEADER
      );
    }
  }

  var enc = getEncryptionParams(headers.encryption);
  var dh = keymap[enc.keyid || DEFAULT_KEYID];
  var senderKey = base64URLDecode(dh);
  if (!senderKey) {
    throw new CryptoError("Invalid dh parameter", BAD_DH_PARAM);
  }

  var salt = base64URLDecode(enc.salt);
  if (!salt) {
    throw new CryptoError("Invalid salt parameter", BAD_SALT_PARAM);
  }
  var rs = enc.rs ? parseInt(enc.rs, 10) : 4096;
  if (isNaN(rs)) {
    throw new CryptoError("rs parameter must be a number", BAD_RS_PARAM);
  }
  return {
    salt,
    rs,
    senderKey,
  };
}

// Decodes an unpadded, base64url-encoded string.
function base64URLDecode(string) {
  if (!string) {
    return null;
  }
  try {
    return ChromeUtils.base64URLDecode(string, {
      // draft-ietf-httpbis-encryption-encoding-01 prohibits padding.
      padding: "reject",
    });
  } catch (ex) {}
  return null;
}

var parseHeaderFieldParams = (m, v) => {
  var i = v.indexOf("=");
  if (i >= 0) {
    // A quoted string with internal quotes is invalid for all the possible
    // values of this header field.
    m[v.substring(0, i).trim()] = v
      .substring(i + 1)
      .trim()
      .replace(/^"(.*)"$/, "$1");
  }
  return m;
};

function chunkArray(array, size) {
  var start = array.byteOffset || 0;
  array = array.buffer || array;
  var index = 0;
  var result = [];
  while (index + size <= array.byteLength) {
    result.push(new Uint8Array(array, start + index, size));
    index += size;
  }
  if (index < array.byteLength) {
    result.push(new Uint8Array(array, start + index));
  }
  return result;
}

function concatArray(arrays) {
  var size = arrays.reduce((total, a) => total + a.byteLength, 0);
  var index = 0;
  return arrays.reduce((result, a) => {
    result.set(new Uint8Array(a), index);
    index += a.byteLength;
    return result;
  }, new Uint8Array(size));
}

function hmac(key) {
  this.keyPromise = crypto.subtle.importKey("raw", key, HMAC_SHA256, false, [
    "sign",
  ]);
}

hmac.prototype.hash = function (input) {
  return this.keyPromise.then(k => crypto.subtle.sign("HMAC", k, input));
};

function hkdf(salt, ikm) {
  this.prkhPromise = new hmac(salt).hash(ikm).then(prk => new hmac(prk));
}

hkdf.prototype.extract = function (info, len) {
  var input = concatArray([info, new Uint8Array([1])]);
  return this.prkhPromise
    .then(prkh => prkh.hash(input))
    .then(h => {
      if (h.byteLength < len) {
        throw new CryptoError("HKDF length is too long", BAD_CRYPTO);
      }
      return h.slice(0, len);
    });
};

/* generate a 96-bit nonce for use in GCM, 48-bits of which are populated */
function generateNonce(base, index) {
  if (index >= Math.pow(2, 48)) {
    throw new CryptoError("Nonce index is too large", BAD_CRYPTO);
  }
  var nonce = base.slice(0, 12);
  nonce = new Uint8Array(nonce);
  for (var i = 0; i < 6; ++i) {
    nonce[nonce.byteLength - 1 - i] ^= (index / Math.pow(256, i)) & 0xff;
  }
  return nonce;
}

function encodeLength(buffer) {
  return new Uint8Array([0, buffer.byteLength]);
}

class Decoder {
  /**
   * Creates a decoder for decrypting an incoming push message.
   *
   * @param {JsonWebKey} privateKey The static subscription private key.
   * @param {BufferSource} publicKey The static subscription public key.
   * @param {BufferSource} authenticationSecret The subscription authentication
   *  secret, or `null` if not used by the scheme.
   * @param {Object} cryptoParams An object containing the ephemeral sender
   *  public key, salt, and record size.
   * @param {BufferSource} ciphertext The encrypted message data.
   */
  constructor(
    privateKey,
    publicKey,
    authenticationSecret,
    cryptoParams,
    ciphertext
  ) {
    this.privateKey = privateKey;
    this.publicKey = publicKey;
    this.authenticationSecret = authenticationSecret;
    this.senderKey = cryptoParams.senderKey;
    this.salt = cryptoParams.salt;
    this.rs = cryptoParams.rs;
    this.ciphertext = ciphertext;
  }

  /**
   * Derives the decryption keys and decodes the push message.
   *
   * @throws {CryptoError} if decryption fails.
   * @returns {Uint8Array} The decrypted message data.
   */
  async decode() {
    if (this.ciphertext.byteLength === 0) {
      // Zero length messages will be passed as null.
      return null;
    }
    try {
      let ikm = await this.computeSharedSecret();
      let [gcmBits, nonce] = await this.deriveKeyAndNonce(ikm);
      let key = await crypto.subtle.importKey(
        "raw",
        gcmBits,
        "AES-GCM",
        false,
        ["decrypt"]
      );

      let r = await Promise.all(
        chunkArray(this.ciphertext, this.chunkSize).map(
          (slice, index, chunks) =>
            this.decodeChunk(
              slice,
              index,
              nonce,
              key,
              index >= chunks.length - 1
            )
        )
      );

      return concatArray(r);
    } catch (error) {
      if (error.isCryptoError) {
        throw error;
      }
      // Web Crypto returns an unhelpful "operation failed for an
      // operation-specific reason" error if decryption fails. We don't have
      // context about what went wrong, so we throw a generic error instead.
      throw new CryptoError("Bad encryption", BAD_CRYPTO);
    }
  }

  /**
   * Computes the ECDH shared secret, used as the input key material for HKDF.
   *
   * @throws if the static or ephemeral ECDH keys are invalid.
   * @returns {ArrayBuffer} The shared secret.
   */
  async computeSharedSecret() {
    let [appServerKey, subscriptionPrivateKey] = await Promise.all([
      crypto.subtle.importKey("raw", this.senderKey, ECDH_KEY, false, [
        "deriveBits",
      ]),
      crypto.subtle.importKey("jwk", this.privateKey, ECDH_KEY, false, [
        "deriveBits",
      ]),
    ]);
    return crypto.subtle.deriveBits(
      { name: "ECDH", public: appServerKey },
      subscriptionPrivateKey,
      256
    );
  }

  /**
   * Derives the content encryption key and nonce.
   *
   * @param {BufferSource} ikm The ECDH shared secret.
   * @returns {Array} A `[gcmBits, nonce]` tuple.
   */
  async deriveKeyAndNonce() {
    throw new Error("Missing `deriveKeyAndNonce` implementation");
  }

  /**
   * Decrypts and removes padding from an encrypted record.
   *
   * @throws {CryptoError} if decryption fails or padding is incorrect.
   * @param {Uint8Array} slice The encrypted record.
   * @param {Number} index The record sequence number.
   * @param {Uint8Array} nonce The nonce base, used to generate the IV.
   * @param {Uint8Array} key The content encryption key.
   * @param {Boolean} last Indicates if this is the final record.
   * @returns {Uint8Array} The decrypted block with padding removed.
   */
  async decodeChunk(slice, index, nonce, key, last) {
    let params = {
      name: "AES-GCM",
      iv: generateNonce(nonce, index),
    };
    let decoded = await crypto.subtle.decrypt(params, key, slice);
    return this.unpadChunk(new Uint8Array(decoded), last);
  }

  /**
   * Removes padding from a decrypted block.
   *
   * @throws {CryptoError} if padding is missing or invalid.
   * @param {Uint8Array} chunk The decrypted block with padding.
   * @returns {Uint8Array} The block with padding removed.
   */
  unpadChunk() {
    throw new Error("Missing `unpadChunk` implementation");
  }

  /** The record chunking size. */
  get chunkSize() {
    throw new Error("Missing `chunkSize` implementation");
  }
}

class OldSchemeDecoder extends Decoder {
  async decode() {
    // For aesgcm and aesgcm128, the ciphertext length can't fall on a record
    // boundary.
    if (
      this.ciphertext.byteLength > 0 &&
      this.ciphertext.byteLength % this.chunkSize === 0
    ) {
      throw new CryptoError("Encrypted data truncated", BAD_CRYPTO);
    }
    return super.decode();
  }

  /**
   * For aesgcm, the padding length is a 16-bit unsigned big endian integer.
   * For aesgcm128, the padding is an 8-bit integer.
   */
  unpadChunk(decoded) {
    if (decoded.length < this.padSize) {
      throw new CryptoError("Decoded array is too short!", BAD_PADDING);
    }
    var pad = decoded[0];
    if (this.padSize == 2) {
      pad = (pad << 8) | decoded[1];
    }
    if (pad > decoded.length - this.padSize) {
      throw new CryptoError("Padding is wrong!", BAD_PADDING);
    }
    // All padded bytes must be zero except the first one.
    for (var i = this.padSize; i < this.padSize + pad; i++) {
      if (decoded[i] !== 0) {
        throw new CryptoError("Padding is wrong!", BAD_PADDING);
      }
    }
    return decoded.slice(pad + this.padSize);
  }

  /**
   * aesgcm and aesgcm128 don't account for the authentication tag as part of
   * the record size.
   */
  get chunkSize() {
    return this.rs + 16;
  }

  get padSize() {
    throw new Error("Missing `padSize` implementation");
  }
}

/** New encryption scheme (draft-ietf-httpbis-encryption-encoding-06). */

const AES128GCM_ENCODING = "aes128gcm";
const AES128GCM_KEY_INFO = UTF8.encode("Content-Encoding: aes128gcm\0");
const AES128GCM_AUTH_INFO = UTF8.encode("WebPush: info\0");
const AES128GCM_NONCE_INFO = UTF8.encode("Content-Encoding: nonce\0");

class aes128gcmDecoder extends Decoder {
  /**
   * Derives the aes128gcm decryption key and nonce. The PRK info string for
   * HKDF is "WebPush: info\0", followed by the unprefixed receiver and sender
   * public keys.
   */
  async deriveKeyAndNonce(ikm) {
    let authKdf = new hkdf(this.authenticationSecret, ikm);
    let authInfo = concatArray([
      AES128GCM_AUTH_INFO,
      this.publicKey,
      this.senderKey,
    ]);
    let prk = await authKdf.extract(authInfo, 32);
    let prkKdf = new hkdf(this.salt, prk);
    return Promise.all([
      prkKdf.extract(AES128GCM_KEY_INFO, 16),
      prkKdf.extract(AES128GCM_NONCE_INFO, 12),
    ]);
  }

  unpadChunk(decoded, last) {
    let length = decoded.length;
    while (length--) {
      if (decoded[length] === 0) {
        continue;
      }
      let recordPad = last ? 2 : 1;
      if (decoded[length] != recordPad) {
        throw new CryptoError("Padding is wrong!", BAD_PADDING);
      }
      return decoded.slice(0, length);
    }
    throw new CryptoError("Zero plaintext", BAD_PADDING);
  }

  /** aes128gcm accounts for the authentication tag in the record size. */
  get chunkSize() {
    return this.rs;
  }
}

/** Older encryption scheme (draft-ietf-httpbis-encryption-encoding-01). */

const AESGCM_ENCODING = "aesgcm";
const AESGCM_KEY_INFO = UTF8.encode("Content-Encoding: aesgcm\0");
const AESGCM_AUTH_INFO = UTF8.encode("Content-Encoding: auth\0"); // note nul-terminus
const AESGCM_P256DH_INFO = UTF8.encode("P-256\0");

class aesgcmDecoder extends OldSchemeDecoder {
  /**
   * Derives the aesgcm decryption key and nonce. We mix the authentication
   * secret with the ikm using HKDF. The context string for the PRK is
   * "Content-Encoding: auth\0". The context string for the key and nonce is
   * "Content-Encoding: <blah>\0P-256\0" then the length and value of both the
   * receiver key and sender key.
   */
  async deriveKeyAndNonce(ikm) {
    // Since we are using an authentication secret, we need to run an extra
    // round of HKDF with the authentication secret as salt.
    let authKdf = new hkdf(this.authenticationSecret, ikm);
    let prk = await authKdf.extract(AESGCM_AUTH_INFO, 32);
    let prkKdf = new hkdf(this.salt, prk);
    let keyInfo = concatArray([
      AESGCM_KEY_INFO,
      AESGCM_P256DH_INFO,
      encodeLength(this.publicKey),
      this.publicKey,
      encodeLength(this.senderKey),
      this.senderKey,
    ]);
    let nonceInfo = concatArray([
      NONCE_INFO,
      new Uint8Array([0]),
      AESGCM_P256DH_INFO,
      encodeLength(this.publicKey),
      this.publicKey,
      encodeLength(this.senderKey),
      this.senderKey,
    ]);
    return Promise.all([
      prkKdf.extract(keyInfo, 16),
      prkKdf.extract(nonceInfo, 12),
    ]);
  }

  get padSize() {
    return 2;
  }
}

/** Oldest encryption scheme (draft-thomson-http-encryption-02). */

const AESGCM128_ENCODING = "aesgcm128";
const AESGCM128_KEY_INFO = UTF8.encode("Content-Encoding: aesgcm128");

class aesgcm128Decoder extends OldSchemeDecoder {
  constructor(privateKey, publicKey, cryptoParams, ciphertext) {
    super(privateKey, publicKey, null, cryptoParams, ciphertext);
  }

  /**
   * The aesgcm128 scheme ignores the authentication secret, and uses
   * "Content-Encoding: <blah>" for the context string. It should eventually
   * be removed: bug 1230038.
   */
  deriveKeyAndNonce(ikm) {
    let prkKdf = new hkdf(this.salt, ikm);
    return Promise.all([
      prkKdf.extract(AESGCM128_KEY_INFO, 16),
      prkKdf.extract(NONCE_INFO, 12),
    ]);
  }

  get padSize() {
    return 1;
  }
}

export var PushCrypto = {
  concatArray,

  generateAuthenticationSecret() {
    return crypto.getRandomValues(new Uint8Array(16));
  },

  validateAppServerKey(key) {
    return crypto.subtle
      .importKey("raw", key, ECDSA_KEY, true, ["verify"])
      .then(_ => key);
  },

  generateKeys() {
    return crypto.subtle
      .generateKey(ECDH_KEY, true, ["deriveBits"])
      .then(cryptoKey =>
        Promise.all([
          crypto.subtle.exportKey("raw", cryptoKey.publicKey),
          crypto.subtle.exportKey("jwk", cryptoKey.privateKey),
        ])
      );
  },

  /**
   * Decrypts a push message.
   *
   * @throws {CryptoError} if decryption fails.
   * @param {JsonWebKey} privateKey The ECDH private key of the subscription
   *  receiving the message, in JWK form.
   * @param {BufferSource} publicKey The ECDH public key of the subscription
   *  receiving the message, in raw form.
   * @param {BufferSource} authenticationSecret The 16-byte shared
   *  authentication secret of the subscription receiving the message.
   * @param {Object} headers The encryption headers from the push server.
   * @param {BufferSource} payload The encrypted message payload.
   * @returns {Uint8Array} The decrypted message data.
   */
  async decrypt(privateKey, publicKey, authenticationSecret, headers, payload) {
    if (!headers) {
      return null;
    }

    let encoding = headers.encoding;
    if (!headers.encoding) {
      throw new CryptoError(
        "Missing Content-Encoding header",
        BAD_ENCODING_HEADER
      );
    }

    let decoder;
    if (encoding == AES128GCM_ENCODING) {
      // aes128gcm includes the salt, record size, and sender public key in a
      // binary header preceding the ciphertext.
      let cryptoParams = getCryptoParamsFromPayload(new Uint8Array(payload));
      decoder = new aes128gcmDecoder(
        privateKey,
        publicKey,
        authenticationSecret,
        cryptoParams,
        cryptoParams.ciphertext
      );
    } else if (encoding == AESGCM128_ENCODING || encoding == AESGCM_ENCODING) {
      // aesgcm and aesgcm128 include the salt, record size, and sender public
      // key in the `Crypto-Key` and `Encryption` HTTP headers.
      let cryptoParams = getCryptoParamsFromHeaders(headers);
      if (headers.encoding == AESGCM_ENCODING) {
        decoder = new aesgcmDecoder(
          privateKey,
          publicKey,
          authenticationSecret,
          cryptoParams,
          payload
        );
      } else {
        decoder = new aesgcm128Decoder(
          privateKey,
          publicKey,
          cryptoParams,
          payload
        );
      }
    }

    if (!decoder) {
      throw new CryptoError(
        "Unsupported Content-Encoding: " + encoding,
        BAD_ENCODING_HEADER
      );
    }

    return decoder.decode();
  },

  /**
   * Encrypts a payload suitable for using in a push message. The encryption
   * is always done with a record size of 4096 and no padding.
   *
   * @throws {CryptoError} if encryption fails.
   * @param {plaintext} Uint8Array The plaintext to encrypt.
   * @param {receiverPublicKey} Uint8Array The public key of the recipient
   *  of the message as a buffer.
   * @param {receiverAuthSecret} Uint8Array The auth secret of the of the
   *  message recipient as a buffer.
   * @param {options} Object Encryption options, used for tests.
   * @returns {ciphertext, encoding} The encrypted payload and encoding.
   */
  async encrypt(
    plaintext,
    receiverPublicKey,
    receiverAuthSecret,
    options = {}
  ) {
    const encoding = options.encoding || AES128GCM_ENCODING;
    // We only support one encoding type.
    if (encoding != AES128GCM_ENCODING) {
      throw new CryptoError(
        `Only ${AES128GCM_ENCODING} is supported`,
        BAD_ENCODING_HEADER
      );
    }
    // We typically use an ephemeral key for this message, but for testing
    // purposes we allow it to be specified.
    const senderKeyPair =
      options.senderKeyPair ||
      (await crypto.subtle.generateKey(ECDH_KEY, true, ["deriveBits"]));
    // allowing a salt to be specified is useful for tests.
    const salt = options.salt || crypto.getRandomValues(new Uint8Array(16));
    const rs = options.rs === undefined ? 4096 : options.rs;

    const encoder = new aes128gcmEncoder(
      plaintext,
      receiverPublicKey,
      receiverAuthSecret,
      senderKeyPair,
      salt,
      rs
    );
    return encoder.encode();
  },
};

// A class for aes128gcm encryption - the only kind we support.
class aes128gcmEncoder {
  constructor(
    plaintext,
    receiverPublicKey,
    receiverAuthSecret,
    senderKeyPair,
    salt,
    rs
  ) {
    this.receiverPublicKey = receiverPublicKey;
    this.receiverAuthSecret = receiverAuthSecret;
    this.senderKeyPair = senderKeyPair;
    this.salt = salt;
    this.rs = rs;
    this.plaintext = plaintext;
  }

  async encode() {
    const sharedSecret = await this.computeSharedSecret(
      this.receiverPublicKey,
      this.senderKeyPair.privateKey
    );

    const rawSenderPublicKey = await crypto.subtle.exportKey(
      "raw",
      this.senderKeyPair.publicKey
    );
    const [gcmBits, nonce] = await this.deriveKeyAndNonce(
      sharedSecret,
      rawSenderPublicKey
    );

    const contentEncryptionKey = await crypto.subtle.importKey(
      "raw",
      gcmBits,
      "AES-GCM",
      false,
      ["encrypt"]
    );
    const payloadHeader = this.createHeader(rawSenderPublicKey);

    const ciphertextChunks = await this.encrypt(contentEncryptionKey, nonce);
    return {
      ciphertext: concatArray([payloadHeader, ...ciphertextChunks]),
      encoding: "aes128gcm",
    };
  }

  // Perform the actual encryption of the payload.
  async encrypt(key, nonce) {
    if (this.rs < 18) {
      throw new CryptoError("recordsize is too small", BAD_RS_PARAM);
    }

    let chunks;
    if (this.plaintext.byteLength === 0) {
      // Send an authentication tag for empty messages.
      chunks = [
        await crypto.subtle.encrypt(
          {
            name: "AES-GCM",
            iv: generateNonce(nonce, 0),
          },
          key,
          new Uint8Array([2])
        ),
      ];
    } else {
      // Use specified recordsize, though we burn 1 for padding and 16 byte
      // overhead.
      let inChunks = chunkArray(this.plaintext, this.rs - 1 - 16);
      chunks = await Promise.all(
        inChunks.map(async function (slice, index) {
          let isLast = index == inChunks.length - 1;
          let padding = new Uint8Array([isLast ? 2 : 1]);
          let input = concatArray([slice, padding]);
          return crypto.subtle.encrypt(
            {
              name: "AES-GCM",
              iv: generateNonce(nonce, index),
            },
            key,
            input
          );
        })
      );
    }
    return chunks;
  }

  // Note: this is a dupe of aes128gcmDecoder.deriveKeyAndNonce, but tricky
  // to rationalize without a larger refactor.
  async deriveKeyAndNonce(sharedSecret, senderPublicKey) {
    const authKdf = new hkdf(this.receiverAuthSecret, sharedSecret);
    const authInfo = concatArray([
      AES128GCM_AUTH_INFO,
      this.receiverPublicKey,
      senderPublicKey,
    ]);
    const prk = await authKdf.extract(authInfo, 32);
    const prkKdf = new hkdf(this.salt, prk);
    return Promise.all([
      prkKdf.extract(AES128GCM_KEY_INFO, 16),
      prkKdf.extract(AES128GCM_NONCE_INFO, 12),
    ]);
  }

  // Note: this duplicates some of Decoder.computeSharedSecret, but the key
  // management is slightly different.
  async computeSharedSecret(receiverPublicKey, senderPrivateKey) {
    const receiverPublicCryptoKey = await crypto.subtle.importKey(
      "raw",
      receiverPublicKey,
      ECDH_KEY,
      false,
      ["deriveBits"]
    );

    return crypto.subtle.deriveBits(
      { name: "ECDH", public: receiverPublicCryptoKey },
      senderPrivateKey,
      256
    );
  }

  // create aes128gcm's header.
  createHeader(key) {
    // layout is "salt|32-bit-int|8-bit-int|key"
    if (key.byteLength != 65) {
      throw new CryptoError("Invalid key length for header", BAD_DH_PARAM);
    }
    // the 2 ints
    let ints = new Uint8Array(5);
    let intsv = new DataView(ints.buffer);
    intsv.setUint32(0, this.rs); // bigendian
    intsv.setUint8(4, key.byteLength);
    return concatArray([this.salt, ints, key]);
  }
}
PK
!<ѭ�#��modules/IndexedDBHelper.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

var DEBUG = 0;
var debug;
if (DEBUG) {
  debug = function (s) {
    dump("-*- IndexedDBHelper: " + s + "\n");
  };
} else {
  debug = function () {};
}

function getErrorName(err) {
  return (err && err.name) || "UnknownError";
}

export function IndexedDBHelper() {}

IndexedDBHelper.prototype = {
  // Close the database
  close: function close() {
    if (this._db) {
      this._db.close();
      this._db = null;
    }
  },

  /**
   * Open a new database.
   * User has to provide upgradeSchema.
   *
   * @param successCb
   *        Success callback to call once database is open.
   * @param failureCb
   *        Error callback to call when an error is encountered.
   */
  open: function open(aCallback) {
    if (aCallback && !this._waitForOpenCallbacks.has(aCallback)) {
      this._waitForOpenCallbacks.add(aCallback);
      if (this._waitForOpenCallbacks.size !== 1) {
        return;
      }
    }

    let self = this;
    let invokeCallbacks = err => {
      for (let callback of self._waitForOpenCallbacks) {
        callback(err);
      }
      self._waitForOpenCallbacks.clear();
    };

    if (DEBUG) {
      debug("Try to open database:" + self.dbName + " " + self.dbVersion);
    }
    let req;
    try {
      req = indexedDB.open(this.dbName, this.dbVersion);
    } catch (e) {
      if (DEBUG) {
        debug("Error opening database: " + self.dbName);
      }
      Services.tm.dispatchToMainThread(() => invokeCallbacks(getErrorName(e)));
      return;
    }
    req.onsuccess = function (event) {
      if (DEBUG) {
        debug("Opened database:" + self.dbName + " " + self.dbVersion);
      }
      self._db = event.target.result;
      self._db.onversionchange = function () {
        if (DEBUG) {
          debug("WARNING: DB modified from a different window.");
        }
      };
      invokeCallbacks();
    };

    req.onupgradeneeded = function (aEvent) {
      if (DEBUG) {
        debug(
          "Database needs upgrade:" +
            self.dbName +
            aEvent.oldVersion +
            aEvent.newVersion
        );
        debug(
          "Correct new database version:" +
            (aEvent.newVersion == this.dbVersion)
        );
      }

      let _db = aEvent.target.result;
      self.upgradeSchema(
        req.transaction,
        _db,
        aEvent.oldVersion,
        aEvent.newVersion
      );
    };
    req.onerror = function (aEvent) {
      if (DEBUG) {
        debug("Failed to open database: " + self.dbName);
      }
      invokeCallbacks(getErrorName(aEvent.target.error));
    };
    req.onblocked = function () {
      if (DEBUG) {
        debug("Opening database request is blocked.");
      }
    };
  },

  /**
   * Use the cached DB or open a new one.
   *
   * @param successCb
   *        Success callback to call.
   * @param failureCb
   *        Error callback to call when an error is encountered.
   */
  ensureDB: function ensureDB(aSuccessCb, aFailureCb) {
    if (this._db) {
      if (DEBUG) {
        debug("ensureDB: already have a database, returning early.");
      }
      if (aSuccessCb) {
        Services.tm.dispatchToMainThread(aSuccessCb);
      }
      return;
    }
    this.open(aError => {
      if (aError) {
        aFailureCb && aFailureCb(aError);
      } else {
        aSuccessCb && aSuccessCb();
      }
    });
  },

  /**
   * Start a new transaction.
   *
   * @param txn_type
   *        Type of transaction (e.g. "readwrite")
   * @param store_name
   *        The object store you want to be passed to the callback
   * @param callback
   *        Function to call when the transaction is available. It will
   *        be invoked with the transaction and the `store' object store.
   * @param successCb
   *        Success callback to call on a successful transaction commit.
   *        The result is stored in txn.result (in the callback function).
   * @param failureCb
   *        Error callback to call when an error is encountered.
   */
  newTxn: function newTxn(
    txn_type,
    store_name,
    callback,
    successCb,
    failureCb
  ) {
    this.ensureDB(() => {
      if (DEBUG) {
        debug("Starting new transaction" + txn_type);
      }
      let txn;
      try {
        txn = this._db.transaction(
          Array.isArray(store_name) ? store_name : this.dbStoreNames,
          txn_type
        );
      } catch (e) {
        if (DEBUG) {
          debug("Error starting transaction: " + this.dbName);
        }
        failureCb(getErrorName(e));
        return;
      }
      if (DEBUG) {
        debug("Retrieving object store: " + this.dbName);
      }
      let stores;
      if (Array.isArray(store_name)) {
        stores = [];
        for (let i = 0; i < store_name.length; ++i) {
          stores.push(txn.objectStore(store_name[i]));
        }
      } else {
        stores = txn.objectStore(store_name);
      }

      txn.oncomplete = function () {
        if (DEBUG) {
          debug("Transaction complete. Returning to callback.");
        }
        /*
         * txn.result property is not part of the transaction object returned
         * by this._db.transaction method called above.
         * The property is expected to be set in the callback function.
         * However, it can happen that the property is not set for some reason,
         * so we have to check if the property exists before calling the
         * success callback.
         */
        if (successCb) {
          if ("result" in txn) {
            successCb(txn.result);
          } else {
            successCb();
          }
        }
      };

      txn.onabort = function () {
        if (DEBUG) {
          debug("Caught error on transaction");
        }
        /*
         * txn.error property is part of the transaction object returned by
         * this._db.transaction method called above.
         * The attribute is defined in IDBTranscation WebIDL interface.
         * It may be null.
         */
        if (failureCb) {
          failureCb(getErrorName(txn.error));
        }
      };
      callback(txn, stores);
    }, failureCb);
  },

  /**
   * Initialize the DB. Does not call open.
   *
   * @param aDBName
   *        DB name for the open call.
   * @param aDBVersion
   *        Current DB version. User has to implement upgradeSchema.
   * @param aDBStoreName
   *        ObjectStore that is used.
   */
  initDBHelper: function initDBHelper(aDBName, aDBVersion, aDBStoreNames) {
    this.dbName = aDBName;
    this.dbVersion = aDBVersion;
    this.dbStoreNames = aDBStoreNames;
    // Cache the database.
    this._db = null;
    this._waitForOpenCallbacks = new Set();
  },
};
PK
!<��b��Z�Z modules/PushServiceHttp2.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { PushDB } from "resource://gre/modules/PushDB.sys.mjs";
import { PushRecord } from "resource://gre/modules/PushRecord.sys.mjs";

import { NetUtil } from "resource://gre/modules/NetUtil.sys.mjs";
import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs";

import { PushCrypto } from "resource://gre/modules/PushCrypto.sys.mjs";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  let { ConsoleAPI } = ChromeUtils.importESModule(
    "resource://gre/modules/Console.sys.mjs"
  );
  return new ConsoleAPI({
    maxLogLevelPref: "dom.push.loglevel",
    prefix: "PushServiceHttp2",
  });
});

const prefs = Services.prefs.getBranch("dom.push.");

const kPUSHHTTP2DB_DB_NAME = "pushHttp2";
const kPUSHHTTP2DB_DB_VERSION = 5; // Change this if the IndexedDB format changes
const kPUSHHTTP2DB_STORE_NAME = "pushHttp2";

/**
 * A proxy between the PushService and connections listening for incoming push
 * messages. The PushService can silence messages from the connections by
 * setting PushSubscriptionListener._pushService to null. This is required
 * because it can happen that there is an outstanding push message that will
 * be send on OnStopRequest but the PushService may not be interested in these.
 * It's easier to stop listening than to have checks at specific points.
 */
var PushSubscriptionListener = function (pushService, uri) {
  lazy.console.debug("PushSubscriptionListener()");
  this._pushService = pushService;
  this.uri = uri;
};

PushSubscriptionListener.prototype = {
  QueryInterface: ChromeUtils.generateQI([
    "nsIHttpPushListener",
    "nsIStreamListener",
  ]),

  getInterface(aIID) {
    return this.QueryInterface(aIID);
  },

  onStartRequest() {
    lazy.console.debug("PushSubscriptionListener: onStartRequest()");
    // We do not do anything here.
  },

  onDataAvailable(aRequest, aStream, aOffset, aCount) {
    lazy.console.debug("PushSubscriptionListener: onDataAvailable()");
    // Nobody should send data, but just to be sure, otherwise necko will
    // complain.
    if (aCount === 0) {
      return;
    }

    let inputStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
      Ci.nsIScriptableInputStream
    );

    inputStream.init(aStream);
    inputStream.read(aCount);
  },

  onStopRequest(aRequest, aStatusCode) {
    lazy.console.debug("PushSubscriptionListener: onStopRequest()");
    if (!this._pushService) {
      return;
    }

    this._pushService.connOnStop(
      aRequest,
      Components.isSuccessCode(aStatusCode),
      this.uri
    );
  },

  onPush(associatedChannel, pushChannel) {
    lazy.console.debug("PushSubscriptionListener: onPush()");
    var pushChannelListener = new PushChannelListener(this);
    pushChannel.asyncOpen(pushChannelListener);
  },

  disconnect() {
    this._pushService = null;
  },
};

/**
 * The listener for pushed messages. The message data is collected in
 * OnDataAvailable and send to the app in OnStopRequest.
 */
var PushChannelListener = function (pushSubscriptionListener) {
  lazy.console.debug("PushChannelListener()");
  this._mainListener = pushSubscriptionListener;
  this._message = [];
  this._ackUri = null;
};

PushChannelListener.prototype = {
  onStartRequest(aRequest) {
    this._ackUri = aRequest.URI.spec;
  },

  onDataAvailable(aRequest, aStream, aOffset, aCount) {
    lazy.console.debug("PushChannelListener: onDataAvailable()");

    if (aCount === 0) {
      return;
    }

    let inputStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
      Ci.nsIBinaryInputStream
    );

    inputStream.setInputStream(aStream);
    let chunk = new ArrayBuffer(aCount);
    inputStream.readArrayBuffer(aCount, chunk);
    this._message.push(chunk);
  },

  onStopRequest(aRequest, aStatusCode) {
    lazy.console.debug(
      "PushChannelListener: onStopRequest()",
      "status code",
      aStatusCode
    );
    if (
      Components.isSuccessCode(aStatusCode) &&
      this._mainListener &&
      this._mainListener._pushService
    ) {
      let headers = {
        encryption_key: getHeaderField(aRequest, "Encryption-Key"),
        crypto_key: getHeaderField(aRequest, "Crypto-Key"),
        encryption: getHeaderField(aRequest, "Encryption"),
        encoding: getHeaderField(aRequest, "Content-Encoding"),
      };
      let msg = PushCrypto.concatArray(this._message);

      this._mainListener._pushService._pushChannelOnStop(
        this._mainListener.uri,
        this._ackUri,
        headers,
        msg
      );
    }
  },
};

function getHeaderField(aRequest, name) {
  try {
    return aRequest.getRequestHeader(name);
  } catch (e) {
    // getRequestHeader can throw.
    return null;
  }
}

var PushServiceDelete = function (resolve, reject) {
  this._resolve = resolve;
  this._reject = reject;
};

PushServiceDelete.prototype = {
  onStartRequest() {},

  onDataAvailable(aRequest, aStream, aOffset, aCount) {
    // Nobody should send data, but just to be sure, otherwise necko will
    // complain.
    if (aCount === 0) {
      return;
    }

    let inputStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
      Ci.nsIScriptableInputStream
    );

    inputStream.init(aStream);
    inputStream.read(aCount);
  },

  onStopRequest(aRequest, aStatusCode) {
    if (Components.isSuccessCode(aStatusCode)) {
      this._resolve();
    } else {
      this._reject(new Error("Error removing subscription: " + aStatusCode));
    }
  },
};

var SubscriptionListener = function (
  aSubInfo,
  aResolve,
  aReject,
  aServerURI,
  aPushServiceHttp2
) {
  lazy.console.debug("SubscriptionListener()");
  this._subInfo = aSubInfo;
  this._resolve = aResolve;
  this._reject = aReject;
  this._serverURI = aServerURI;
  this._service = aPushServiceHttp2;
  this._ctime = Date.now();
  this._retryTimeoutID = null;
};

SubscriptionListener.prototype = {
  onStartRequest() {},

  onDataAvailable() {},

  onStopRequest(aRequest, aStatus) {
    lazy.console.debug("SubscriptionListener: onStopRequest()");

    // Check if pushService is still active.
    if (!this._service.hasmainPushService()) {
      this._reject(new Error("Push service unavailable"));
      return;
    }

    if (!Components.isSuccessCode(aStatus)) {
      this._reject(new Error("Error listening for messages: " + aStatus));
      return;
    }

    var statusCode = aRequest.QueryInterface(Ci.nsIHttpChannel).responseStatus;

    if (Math.floor(statusCode / 100) == 5) {
      if (this._subInfo.retries < prefs.getIntPref("http2.maxRetries")) {
        this._subInfo.retries++;
        var retryAfter = retryAfterParser(aRequest);
        this._retryTimeoutID = setTimeout(_ => {
          this._reject({
            retry: true,
            subInfo: this._subInfo,
          });
          this._service.removeListenerPendingRetry(this);
          this._retryTimeoutID = null;
        }, retryAfter);
        this._service.addListenerPendingRetry(this);
      } else {
        this._reject(new Error("Unexpected server response: " + statusCode));
      }
      return;
    } else if (statusCode != 201) {
      this._reject(new Error("Unexpected server response: " + statusCode));
      return;
    }

    var subscriptionUri;
    try {
      subscriptionUri = aRequest.getResponseHeader("location");
    } catch (err) {
      this._reject(new Error("Missing Location header"));
      return;
    }

    lazy.console.debug("onStopRequest: subscriptionUri", subscriptionUri);

    var linkList;
    try {
      linkList = aRequest.getResponseHeader("link");
    } catch (err) {
      this._reject(new Error("Missing Link header"));
      return;
    }

    var linkParserResult;
    try {
      linkParserResult = linkParser(linkList, this._serverURI);
    } catch (e) {
      this._reject(e);
      return;
    }

    if (!subscriptionUri) {
      this._reject(new Error("Invalid Location header"));
      return;
    }
    try {
      Services.io.newURI(subscriptionUri);
    } catch (e) {
      lazy.console.error(
        "onStopRequest: Invalid subscription URI",
        subscriptionUri
      );
      this._reject(
        new Error("Invalid subscription endpoint: " + subscriptionUri)
      );
      return;
    }

    let reply = new PushRecordHttp2({
      subscriptionUri,
      pushEndpoint: linkParserResult.pushEndpoint,
      pushReceiptEndpoint: linkParserResult.pushReceiptEndpoint,
      scope: this._subInfo.record.scope,
      originAttributes: this._subInfo.record.originAttributes,
      systemRecord: this._subInfo.record.systemRecord,
      appServerKey: this._subInfo.record.appServerKey,
      ctime: Date.now(),
    });

    this._resolve(reply);
  },

  abortRetry() {
    if (this._retryTimeoutID != null) {
      clearTimeout(this._retryTimeoutID);
      this._retryTimeoutID = null;
    } else {
      lazy.console.debug(
        "SubscriptionListener.abortRetry: aborting non-existent retry?"
      );
    }
  },
};

function retryAfterParser(aRequest) {
  let retryAfter = 0;
  try {
    let retryField = aRequest.getResponseHeader("retry-after");
    if (isNaN(retryField)) {
      retryAfter = Date.parse(retryField) - new Date().getTime();
    } else {
      retryAfter = parseInt(retryField, 10) * 1000;
    }
    retryAfter = retryAfter > 0 ? retryAfter : 0;
  } catch (e) {}

  return retryAfter;
}

function linkParser(linkHeader, serverURI) {
  let linkList = linkHeader.split(",");
  if (linkList.length < 1) {
    throw new Error("Invalid Link header");
  }

  let pushEndpoint;
  let pushReceiptEndpoint;

  linkList.forEach(link => {
    let linkElems = link.split(";");

    if (linkElems.length == 2) {
      if (linkElems[1].trim() === 'rel="urn:ietf:params:push"') {
        pushEndpoint = linkElems[0].substring(
          linkElems[0].indexOf("<") + 1,
          linkElems[0].indexOf(">")
        );
      } else if (linkElems[1].trim() === 'rel="urn:ietf:params:push:receipt"') {
        pushReceiptEndpoint = linkElems[0].substring(
          linkElems[0].indexOf("<") + 1,
          linkElems[0].indexOf(">")
        );
      }
    }
  });

  lazy.console.debug("linkParser: pushEndpoint", pushEndpoint);
  lazy.console.debug("linkParser: pushReceiptEndpoint", pushReceiptEndpoint);
  // Missing pushReceiptEndpoint is allowed.
  if (!pushEndpoint) {
    throw new Error("Missing push endpoint");
  }

  const pushURI = Services.io.newURI(pushEndpoint, null, serverURI);
  let pushReceiptURI;
  if (pushReceiptEndpoint) {
    pushReceiptURI = Services.io.newURI(pushReceiptEndpoint, null, serverURI);
  }

  return {
    pushEndpoint: pushURI.spec,
    pushReceiptEndpoint: pushReceiptURI ? pushReceiptURI.spec : "",
  };
}

/**
 * The implementation of the WebPush.
 */
export var PushServiceHttp2 = {
  _mainPushService: null,
  _serverURI: null,

  // Keep information about all connections, e.g. the channel, listener...
  _conns: {},
  _started: false,

  // Set of SubscriptionListeners that are pending a subscription retry attempt.
  _listenersPendingRetry: new Set(),

  newPushDB() {
    return new PushDB(
      kPUSHHTTP2DB_DB_NAME,
      kPUSHHTTP2DB_DB_VERSION,
      kPUSHHTTP2DB_STORE_NAME,
      "subscriptionUri",
      PushRecordHttp2
    );
  },

  hasmainPushService() {
    return this._mainPushService !== null;
  },

  async connect() {
    let subscriptions = await this._mainPushService.getAllUnexpired();
    this.startConnections(subscriptions);
  },

  async sendSubscribeBroadcast() {
    // Not implemented yet
  },

  isConnected() {
    return this._mainPushService != null;
  },

  disconnect() {
    this._shutdownConnections(false);
  },

  _makeChannel(aUri) {
    var chan = NetUtil.newChannel({
      uri: aUri,
      loadUsingSystemPrincipal: true,
    }).QueryInterface(Ci.nsIHttpChannel);

    var loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(
      Ci.nsILoadGroup
    );
    chan.loadGroup = loadGroup;
    return chan;
  },

  /**
   * Subscribe new resource.
   */
  register(aRecord) {
    lazy.console.debug("subscribeResource()");

    return this._subscribeResourceInternal({
      record: aRecord,
      retries: 0,
    }).then(result =>
      PushCrypto.generateKeys().then(([publicKey, privateKey]) => {
        result.p256dhPublicKey = publicKey;
        result.p256dhPrivateKey = privateKey;
        result.authenticationSecret = PushCrypto.generateAuthenticationSecret();
        this._conns[result.subscriptionUri] = {
          channel: null,
          listener: null,
          countUnableToConnect: 0,
          lastStartListening: 0,
          retryTimerID: 0,
        };
        this._listenForMsgs(result.subscriptionUri);
        return result;
      })
    );
  },

  _subscribeResourceInternal(aSubInfo) {
    lazy.console.debug("subscribeResourceInternal()");

    return new Promise((resolve, reject) => {
      var listener = new SubscriptionListener(
        aSubInfo,
        resolve,
        reject,
        this._serverURI,
        this
      );

      var chan = this._makeChannel(this._serverURI.spec);
      chan.requestMethod = "POST";
      chan.asyncOpen(listener);
    }).catch(err => {
      if ("retry" in err) {
        return this._subscribeResourceInternal(err.subInfo);
      }
      throw err;
    });
  },

  _deleteResource(aUri) {
    return new Promise((resolve, reject) => {
      var chan = this._makeChannel(aUri);
      chan.requestMethod = "DELETE";
      chan.asyncOpen(new PushServiceDelete(resolve, reject));
    });
  },

  /**
   * Unsubscribe the resource with a subscription uri aSubscriptionUri.
   * We can't do anything about it if it fails, so we don't listen for response.
   */
  _unsubscribeResource(aSubscriptionUri) {
    lazy.console.debug("unsubscribeResource()");

    return this._deleteResource(aSubscriptionUri);
  },

  /**
   * Start listening for messages.
   */
  _listenForMsgs(aSubscriptionUri) {
    lazy.console.debug("listenForMsgs()", aSubscriptionUri);
    if (!this._conns[aSubscriptionUri]) {
      lazy.console.warn(
        "listenForMsgs: We do not have this subscription",
        aSubscriptionUri
      );
      return;
    }

    var chan = this._makeChannel(aSubscriptionUri);
    var conn = {};
    conn.channel = chan;
    var listener = new PushSubscriptionListener(this, aSubscriptionUri);
    conn.listener = listener;

    chan.notificationCallbacks = listener;

    try {
      chan.asyncOpen(listener);
    } catch (e) {
      lazy.console.error(
        "listenForMsgs: Error connecting to push server.",
        "asyncOpen failed",
        e
      );
      conn.listener.disconnect();
      chan.cancel(Cr.NS_ERROR_ABORT);
      this._retryAfterBackoff(aSubscriptionUri, -1);
      return;
    }

    this._conns[aSubscriptionUri].lastStartListening = Date.now();
    this._conns[aSubscriptionUri].channel = conn.channel;
    this._conns[aSubscriptionUri].listener = conn.listener;
  },

  _ackMsgRecv(aAckUri) {
    lazy.console.debug("ackMsgRecv()", aAckUri);
    return this._deleteResource(aAckUri);
  },

  init(aOptions, aMainPushService, aServerURL) {
    lazy.console.debug("init()");
    this._mainPushService = aMainPushService;
    this._serverURI = aServerURL;

    return Promise.resolve();
  },

  _retryAfterBackoff(aSubscriptionUri, retryAfter) {
    lazy.console.debug("retryAfterBackoff()");

    var resetRetryCount = prefs.getIntPref("http2.reset_retry_count_after_ms");
    // If it was running for some time, reset retry counter.
    if (
      Date.now() - this._conns[aSubscriptionUri].lastStartListening >
      resetRetryCount
    ) {
      this._conns[aSubscriptionUri].countUnableToConnect = 0;
    }

    let maxRetries = prefs.getIntPref("http2.maxRetries");
    if (this._conns[aSubscriptionUri].countUnableToConnect >= maxRetries) {
      this._shutdownSubscription(aSubscriptionUri);
      this._resubscribe(aSubscriptionUri);
      return;
    }

    if (retryAfter !== -1) {
      // This is a 5xx response.
      this._conns[aSubscriptionUri].countUnableToConnect++;
      this._conns[aSubscriptionUri].retryTimerID = setTimeout(
        _ => this._listenForMsgs(aSubscriptionUri),
        retryAfter
      );
      return;
    }

    retryAfter =
      prefs.getIntPref("http2.retryInterval") *
      Math.pow(2, this._conns[aSubscriptionUri].countUnableToConnect);

    retryAfter = retryAfter * (0.8 + Math.random() * 0.4); // add +/-20%.

    this._conns[aSubscriptionUri].countUnableToConnect++;
    this._conns[aSubscriptionUri].retryTimerID = setTimeout(
      _ => this._listenForMsgs(aSubscriptionUri),
      retryAfter
    );

    lazy.console.debug("retryAfterBackoff: Retry in", retryAfter);
  },

  // Close connections.
  _shutdownConnections(deleteInfo) {
    lazy.console.debug("shutdownConnections()");

    for (let subscriptionUri in this._conns) {
      if (this._conns[subscriptionUri]) {
        if (this._conns[subscriptionUri].listener) {
          this._conns[subscriptionUri].listener._pushService = null;
        }

        if (this._conns[subscriptionUri].channel) {
          try {
            this._conns[subscriptionUri].channel.cancel(Cr.NS_ERROR_ABORT);
          } catch (e) {}
        }
        this._conns[subscriptionUri].listener = null;
        this._conns[subscriptionUri].channel = null;

        if (this._conns[subscriptionUri].retryTimerID > 0) {
          clearTimeout(this._conns[subscriptionUri].retryTimerID);
        }

        if (deleteInfo) {
          delete this._conns[subscriptionUri];
        }
      }
    }
  },

  // Start listening if subscriptions present.
  startConnections(aSubscriptions) {
    lazy.console.debug("startConnections()", aSubscriptions.length);

    for (let i = 0; i < aSubscriptions.length; i++) {
      let record = aSubscriptions[i];
      this._mainPushService.ensureCrypto(record).then(
        record => {
          this._startSingleConnection(record);
        },
        error => {
          lazy.console.error(
            "startConnections: Error updating record",
            record.keyID,
            error
          );
        }
      );
    }
  },

  _startSingleConnection(record) {
    lazy.console.debug("_startSingleConnection()");
    if (typeof this._conns[record.subscriptionUri] != "object") {
      this._conns[record.subscriptionUri] = {
        channel: null,
        listener: null,
        countUnableToConnect: 0,
        retryTimerID: 0,
      };
    }
    if (!this._conns[record.subscriptionUri].conn) {
      this._listenForMsgs(record.subscriptionUri);
    }
  },

  // Close connection and notify apps that subscription are gone.
  _shutdownSubscription(aSubscriptionUri) {
    lazy.console.debug("shutdownSubscriptions()");

    if (typeof this._conns[aSubscriptionUri] == "object") {
      if (this._conns[aSubscriptionUri].listener) {
        this._conns[aSubscriptionUri].listener._pushService = null;
      }

      if (this._conns[aSubscriptionUri].channel) {
        try {
          this._conns[aSubscriptionUri].channel.cancel(Cr.NS_ERROR_ABORT);
        } catch (e) {}
      }
      delete this._conns[aSubscriptionUri];
    }
  },

  uninit() {
    lazy.console.debug("uninit()");
    this._abortPendingSubscriptionRetries();
    this._shutdownConnections(true);
    this._mainPushService = null;
  },

  _abortPendingSubscriptionRetries() {
    this._listenersPendingRetry.forEach(listener => listener.abortRetry());
    this._listenersPendingRetry.clear();
  },

  unregister(aRecord) {
    this._shutdownSubscription(aRecord.subscriptionUri);
    return this._unsubscribeResource(aRecord.subscriptionUri);
  },

  reportDeliveryError(messageID, reason) {
    lazy.console.warn(
      "reportDeliveryError: Ignoring message delivery error",
      messageID,
      reason
    );
  },

  /** Push server has deleted subscription.
   *  Re-subscribe - if it succeeds send update db record and send
   *                 pushsubscriptionchange,
   *               - on error delete record and send pushsubscriptionchange
   *  TODO: maybe pushsubscriptionerror will be included.
   */
  _resubscribe(aSubscriptionUri) {
    this._mainPushService.getByKeyID(aSubscriptionUri).then(record =>
      this.register(record).then(
        recordNew => {
          if (this._mainPushService) {
            this._mainPushService
              .updateRegistrationAndNotifyApp(aSubscriptionUri, recordNew)
              .catch(console.error);
          }
        },
        () => {
          if (this._mainPushService) {
            this._mainPushService
              .dropRegistrationAndNotifyApp(aSubscriptionUri)
              .catch(console.error);
          }
        }
      )
    );
  },

  connOnStop(aRequest, aSuccess, aSubscriptionUri) {
    lazy.console.debug("connOnStop() succeeded", aSuccess);

    var conn = this._conns[aSubscriptionUri];
    if (!conn) {
      // there is no connection description that means that we closed
      // connection, so do nothing. But we should have already deleted
      // the listener.
      return;
    }

    conn.channel = null;
    conn.listener = null;

    if (!aSuccess) {
      this._retryAfterBackoff(aSubscriptionUri, -1);
    } else if (Math.floor(aRequest.responseStatus / 100) == 5) {
      var retryAfter = retryAfterParser(aRequest);
      this._retryAfterBackoff(aSubscriptionUri, retryAfter);
    } else if (Math.floor(aRequest.responseStatus / 100) == 4) {
      this._shutdownSubscription(aSubscriptionUri);
      this._resubscribe(aSubscriptionUri);
    } else if (Math.floor(aRequest.responseStatus / 100) == 2) {
      // This should be 204
      setTimeout(_ => this._listenForMsgs(aSubscriptionUri), 0);
    } else {
      this._retryAfterBackoff(aSubscriptionUri, -1);
    }
  },

  addListenerPendingRetry(aListener) {
    this._listenersPendingRetry.add(aListener);
  },

  removeListenerPendingRetry(aListener) {
    if (!this._listenersPendingRetry.remove(aListener)) {
      lazy.console.debug("removeListenerPendingRetry: listener not in list?");
    }
  },

  _pushChannelOnStop(aUri, aAckUri, aHeaders, aMessage) {
    lazy.console.debug("pushChannelOnStop()");

    this._mainPushService
      .receivedPushMessage(aUri, "", aHeaders, aMessage, record => {
        // Always update the stored record.
        return record;
      })
      .then(_ => this._ackMsgRecv(aAckUri))
      .catch(err => {
        lazy.console.error("pushChannelOnStop: Error receiving message", err);
      });
  },
};

function PushRecordHttp2(record) {
  PushRecord.call(this, record);
  this.subscriptionUri = record.subscriptionUri;
  this.pushReceiptEndpoint = record.pushReceiptEndpoint;
}

PushRecordHttp2.prototype = Object.create(PushRecord.prototype, {
  keyID: {
    get() {
      return this.subscriptionUri;
    },
  },
});

PushRecordHttp2.prototype.toSubscription = function () {
  let subscription = PushRecord.prototype.toSubscription.call(this);
  subscription.pushReceiptEndpoint = this.pushReceiptEndpoint;
  return subscription;
};
PK
!</Y&U�D�Dmodules/LoginManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const PERMISSION_SAVE_LOGINS = "login-saving";
const MAX_DATE_MS = 8640000000000000;

import { LoginManagerStorage } from "resource://passwordmgr/passwordstorage.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  let logger = lazy.LoginHelper.createLogger("LoginManager");
  return logger;
});

const MS_PER_DAY = 24 * 60 * 60 * 1000;

if (Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT) {
  throw new Error("LoginManager.sys.mjs should only run in the parent process");
}

export function LoginManager() {
  this.init();
}

LoginManager.prototype = {
  classID: Components.ID("{cb9e0de8-3598-4ed7-857b-827f011ad5d8}"),
  QueryInterface: ChromeUtils.generateQI([
    "nsILoginManager",
    "nsISupportsWeakReference",
    "nsIInterfaceRequestor",
  ]),
  getInterface(aIID) {
    if (aIID.equals(Ci.mozIStorageConnection) && this._storage) {
      let ir = this._storage.QueryInterface(Ci.nsIInterfaceRequestor);
      return ir.getInterface(aIID);
    }

    if (aIID.equals(Ci.nsIVariant)) {
      // Allows unwrapping the JavaScript object for regression tests.
      return this;
    }

    throw new Components.Exception(
      "Interface not available",
      Cr.NS_ERROR_NO_INTERFACE
    );
  },

  /* ---------- private members ---------- */

  _storage: null, // Storage component which contains the saved logins

  /**
   * Initialize the Login Manager. Automatically called when service
   * is created.
   *
   * Note: Service created in BrowserGlue#_scheduleStartupIdleTasks()
   */
  init() {
    // Cache references to current |this| in utility objects
    this._observer._pwmgr = this;

    Services.obs.addObserver(this._observer, "xpcom-shutdown");
    Services.obs.addObserver(this._observer, "passwordmgr-storage-replace");

    // Initialize storage so that asynchronous data loading can start.
    this._initStorage();

    Services.obs.addObserver(this._observer, "gather-telemetry");
  },

  _initStorage() {
    this.initializationPromise = new Promise(resolve => {
      this._storage = LoginManagerStorage.create(() => {
        resolve();

        lazy.log.debug(
          "initializationPromise is resolved, updating isPrimaryPasswordSet in sharedData"
        );
        Services.ppmm.sharedData.set(
          "isPrimaryPasswordSet",
          lazy.LoginHelper.isPrimaryPasswordSet()
        );
      });
    });
  },

  /* ---------- Utility objects ---------- */

  /**
   * Internal utility object, implements the nsIObserver interface.
   * Used to receive notification for: form submission, preference changes.
   */
  _observer: {
    _pwmgr: null,

    QueryInterface: ChromeUtils.generateQI([
      "nsIObserver",
      "nsISupportsWeakReference",
    ]),

    // nsIObserver
    observe(subject, topic, data) {
      if (topic == "xpcom-shutdown") {
        delete this._pwmgr._storage;
        this._pwmgr = null;
      } else if (topic == "passwordmgr-storage-replace") {
        (async () => {
          await this._pwmgr._storage.terminate();
          this._pwmgr._initStorage();
          await this._pwmgr.initializationPromise;
          Services.obs.notifyObservers(
            null,
            "passwordmgr-storage-replace-complete"
          );
        })();
      } else if (topic == "gather-telemetry") {
        // When testing, the "data" parameter is a string containing the
        // reference time in milliseconds for time-based statistics.
        this._pwmgr._gatherTelemetry(
          data ? parseInt(data) : new Date().getTime()
        );
      } else {
        lazy.log.debug(`Unexpected notification: ${topic}.`);
      }
    },
  },

  /**
   * Collects statistics about the current logins and settings. The telemetry
   * histograms used here are not accumulated, but are reset each time this
   * function is called, since it can be called multiple times in a session.
   *
   * This function might also not be called at all in the current session.
   *
   * @param referenceTimeMs
   *        Current time used to calculate time-based statistics, expressed as
   *        the number of milliseconds since January 1, 1970, 00:00:00 UTC.
   *        This is set to a fake value during unit testing.
   */
  async _gatherTelemetry(referenceTimeMs) {
    function clearAndGetHistogram(histogramId) {
      let histogram = Services.telemetry.getHistogramById(histogramId);
      histogram.clear();
      return histogram;
    }

    clearAndGetHistogram("PWMGR_BLOCKLIST_NUM_SITES").add(
      this.getAllDisabledHosts().length
    );
    clearAndGetHistogram("PWMGR_NUM_SAVED_PASSWORDS").add(
      this.countLogins("", "", "")
    );
    clearAndGetHistogram("PWMGR_NUM_HTTPAUTH_PASSWORDS").add(
      this.countLogins("", null, "")
    );
    Services.obs.notifyObservers(
      null,
      "weave:telemetry:histogram",
      "PWMGR_BLOCKLIST_NUM_SITES"
    );
    Services.obs.notifyObservers(
      null,
      "weave:telemetry:histogram",
      "PWMGR_NUM_SAVED_PASSWORDS"
    );

    // This is a boolean histogram, and not a flag, because we don't want to
    // record any value if _gatherTelemetry is not called.
    clearAndGetHistogram("PWMGR_SAVING_ENABLED").add(lazy.LoginHelper.enabled);
    Services.obs.notifyObservers(
      null,
      "weave:telemetry:histogram",
      "PWMGR_SAVING_ENABLED"
    );

    // Don't try to get logins if MP is enabled, since we don't want to show a MP prompt.
    if (!this.isLoggedIn) {
      return;
    }

    let logins = await this.getAllLogins();

    let usernamePresentHistogram = clearAndGetHistogram(
      "PWMGR_USERNAME_PRESENT"
    );
    let loginLastUsedDaysHistogram = clearAndGetHistogram(
      "PWMGR_LOGIN_LAST_USED_DAYS"
    );

    let originCount = new Map();
    for (let login of logins) {
      usernamePresentHistogram.add(!!login.username);

      let origin = login.origin;
      originCount.set(origin, (originCount.get(origin) || 0) + 1);

      login.QueryInterface(Ci.nsILoginMetaInfo);
      let timeLastUsedAgeMs = referenceTimeMs - login.timeLastUsed;
      if (timeLastUsedAgeMs > 0) {
        loginLastUsedDaysHistogram.add(
          Math.floor(timeLastUsedAgeMs / MS_PER_DAY)
        );
      }
    }
    Services.obs.notifyObservers(
      null,
      "weave:telemetry:histogram",
      "PWMGR_LOGIN_LAST_USED_DAYS"
    );

    let passwordsCountHistogram = clearAndGetHistogram(
      "PWMGR_NUM_PASSWORDS_PER_HOSTNAME"
    );
    for (let count of originCount.values()) {
      passwordsCountHistogram.add(count);
    }
    Services.obs.notifyObservers(
      null,
      "weave:telemetry:histogram",
      "PWMGR_NUM_PASSWORDS_PER_HOSTNAME"
    );

    Services.obs.notifyObservers(null, "passwordmgr-gather-telemetry-complete");
  },

  /**
   * Ensures that a login isn't missing any necessary fields.
   *
   * @param login
   *        The login to check.
   */
  _checkLogin(login) {
    // Sanity check the login
    if (login.origin == null || !login.origin.length) {
      throw new Error("Can't add a login with a null or empty origin.");
    }

    // For logins w/o a username, set to "", not null.
    if (login.username == null) {
      throw new Error("Can't add a login with a null username.");
    }

    if (login.password == null || !login.password.length) {
      throw new Error("Can't add a login with a null or empty password.");
    }

    // Duplicated from toolkit/components/passwordmgr/LoginHelper.sys.jms
    // TODO: move all validations into this function.
    //
    // In theory these nulls should just be rolled up into the encrypted
    // values, but nsISecretDecoderRing doesn't use nsStrings, so the
    // nulls cause truncation. Check for them here just to avoid
    // unexpected round-trip surprises.
    if (login.username.includes("\0") || login.password.includes("\0")) {
      throw new Error("login values can't contain nulls");
    }

    if (login.formActionOrigin || login.formActionOrigin == "") {
      // We have a form submit URL. Can't have a HTTP realm.
      if (login.httpRealm != null) {
        throw new Error(
          "Can't add a login with both a httpRealm and formActionOrigin."
        );
      }
    } else if (login.httpRealm || login.httpRealm == "") {
      // We have a HTTP realm. Can't have a form submit URL.
      if (login.formActionOrigin != null) {
        throw new Error(
          "Can't add a login with both a httpRealm and formActionOrigin."
        );
      }
    } else {
      // Need one or the other!
      throw new Error(
        "Can't add a login without a httpRealm or formActionOrigin."
      );
    }

    login.QueryInterface(Ci.nsILoginMetaInfo);
    for (let pname of ["timeCreated", "timeLastUsed", "timePasswordChanged"]) {
      // Invalid dates
      if (login[pname] > MAX_DATE_MS) {
        throw new Error("Can't add a login with invalid date properties.");
      }
    }
  },

  /* ---------- Primary Public interfaces ---------- */

  /**
   * @type Promise
   * This promise is resolved when initialization is complete, and is rejected
   * in case the asynchronous part of initialization failed.
   */
  initializationPromise: null,

  /**
   * Add a new login to login storage.
   */
  async addLoginAsync(login) {
    this._checkLogin(login);

    lazy.log.debug("Adding login");
    const [resultLogin] = await this._storage.addLoginsAsync([login]);
    return resultLogin;
  },

  /**
   * Add multiple logins to login storage.
   * TODO: rename to `addLoginsAsync` https://bugzilla.mozilla.org/show_bug.cgi?id=1832757
   */
  async addLogins(logins) {
    if (logins.length === 0) {
      return logins;
    }

    const validLogins = logins.filter(login => {
      try {
        this._checkLogin(login);
        return true;
      } catch (e) {
        console.error(e);
        return false;
      }
    });
    lazy.log.debug("Adding logins");
    return this._storage.addLoginsAsync(validLogins, true);
  },

  /**
   * Remove the specified login from the stored logins.
   */
  removeLogin(login) {
    lazy.log.debug(
      "Removing login",
      login.QueryInterface(Ci.nsILoginMetaInfo).guid
    );
    return this._storage.removeLogin(login);
  },

  /**
   * Change the specified login to match the new login or new properties.
   */
  modifyLogin(oldLogin, newLogin) {
    lazy.log.debug(
      "Modifying login",
      oldLogin.QueryInterface(Ci.nsILoginMetaInfo).guid
    );
    return this._storage.modifyLogin(oldLogin, newLogin);
  },

  /**
   * Record that the password of a saved login was used (e.g. submitted or copied).
   */
  recordPasswordUse(
    login,
    privateContextWithoutExplicitConsent,
    loginType,
    filled
  ) {
    lazy.log.debug(
      "Recording password use",
      loginType,
      login.QueryInterface(Ci.nsILoginMetaInfo).guid
    );
    if (!privateContextWithoutExplicitConsent) {
      // don't record non-interactive use in private browsing
      this._storage.recordPasswordUse(login);
    }

    Services.telemetry.recordEvent(
      "pwmgr",
      "saved_login_used",
      loginType,
      null,
      {
        filled: "" + filled,
      }
    );
  },

  /**
   * Get a dump of all stored logins asynchronously. Used by the login manager UI.
   *
   * @return {nsILoginInfo[]} - If there are no logins, the array is empty.
   */
  async getAllLogins() {
    lazy.log.debug("Getting a list of all logins asynchronously.");
    return this._storage.getAllLogins();
  },

  /**
   * Get a dump of all stored logins asynchronously. Used by the login detection service.
   */
  getAllLoginsWithCallback(aCallback) {
    lazy.log.debug("Searching a list of all logins asynchronously.");
    this._storage.getAllLogins().then(logins => {
      aCallback.onSearchComplete(logins);
    });
  },

  /**
   * Remove all user facing stored logins.
   *
   * This will not remove the FxA Sync key, which is stored with the rest of a user's logins.
   */
  removeAllUserFacingLogins() {
    lazy.log.debug("Removing all user facing logins.");
    this._storage.removeAllUserFacingLogins();
  },

  /**
   * Remove all logins from data store, including the FxA Sync key.
   *
   * NOTE: You probably want `removeAllUserFacingLogins()` instead of this function.
   * This function will remove the FxA Sync key, which will break syncing of saved user data
   * e.g. bookmarks, history, open tabs, logins and passwords, add-ons, and options
   */
  removeAllLogins() {
    lazy.log.debug("Removing all logins from local store, including FxA key.");
    this._storage.removeAllLogins();
  },

  /**
   * Get a list of all origins for which logins are disabled.
   *
   * @param {Number} count - only needed for XPCOM.
   *
   * @return {String[]} of disabled origins. If there are no disabled origins,
   *                    the array is empty.
   */
  getAllDisabledHosts() {
    lazy.log.debug("Getting a list of all disabled origins.");

    let disabledHosts = [];
    for (let perm of Services.perms.all) {
      if (
        perm.type == PERMISSION_SAVE_LOGINS &&
        perm.capability == Services.perms.DENY_ACTION
      ) {
        disabledHosts.push(perm.principal.URI.displayPrePath);
      }
    }

    lazy.log.debug(`Returning ${disabledHosts.length} disabled hosts.`);
    return disabledHosts;
  },

  /**
   * Search for the known logins for entries matching the specified criteria.
   */
  findLogins(origin, formActionOrigin, httpRealm) {
    lazy.log.debug(
      "Searching for logins matching origin:",
      origin,
      "formActionOrigin:",
      formActionOrigin,
      "httpRealm:",
      httpRealm
    );

    return this._storage.findLogins(origin, formActionOrigin, httpRealm);
  },

  async searchLoginsAsync(matchData) {
    lazy.log.debug(
      `Searching for matching logins for origin: ${matchData.origin}`
    );

    if (!matchData.origin) {
      throw new Error("searchLoginsAsync: An `origin` is required");
    }

    return this._storage.searchLoginsAsync(matchData);
  },

  /**
   * @return {nsILoginInfo[]} which are decrypted.
   */
  searchLogins(matchData) {
    lazy.log.debug(
      `Searching for matching logins for origin: ${matchData.origin}`
    );

    matchData.QueryInterface(Ci.nsIPropertyBag2);
    if (!matchData.hasKey("guid")) {
      if (!matchData.hasKey("origin")) {
        lazy.log.warn("An `origin` field is recommended.");
      }
    }

    return this._storage.searchLogins(matchData);
  },

  /**
   * Search for the known logins for entries matching the specified criteria,
   * returns only the count.
   */
  countLogins(origin, formActionOrigin, httpRealm) {
    const loginsCount = this._storage.countLogins(
      origin,
      formActionOrigin,
      httpRealm
    );

    lazy.log.debug(
      `Found ${loginsCount} matching origin: ${origin}, formActionOrigin: ${formActionOrigin} and realm: ${httpRealm}`
    );

    return loginsCount;
  },

  /* Sync metadata functions */
  async getSyncID() {
    return this._storage.getSyncID();
  },

  async setSyncID(id) {
    await this._storage.setSyncID(id);
  },

  async getLastSync() {
    return this._storage.getLastSync();
  },

  async setLastSync(timestamp) {
    await this._storage.setLastSync(timestamp);
  },

  async ensureCurrentSyncID(newSyncID) {
    let existingSyncID = await this.getSyncID();
    if (existingSyncID == newSyncID) {
      return existingSyncID;
    }
    lazy.log.debug(
      `ensureCurrentSyncID: newSyncID: ${newSyncID} existingSyncID: ${existingSyncID}`
    );

    await this.setSyncID(newSyncID);
    await this.setLastSync(0);
    return newSyncID;
  },

  get uiBusy() {
    return this._storage.uiBusy;
  },

  get isLoggedIn() {
    return this._storage.isLoggedIn;
  },

  /**
   * Check to see if user has disabled saving logins for the origin.
   */
  getLoginSavingEnabled(origin) {
    lazy.log.debug(`Checking if logins to ${origin} can be saved.`);
    if (!lazy.LoginHelper.enabled) {
      return false;
    }

    try {
      let uri = Services.io.newURI(origin);
      let principal = Services.scriptSecurityManager.createContentPrincipal(
        uri,
        {}
      );
      return (
        Services.perms.testPermissionFromPrincipal(
          principal,
          PERMISSION_SAVE_LOGINS
        ) != Services.perms.DENY_ACTION
      );
    } catch (ex) {
      if (!origin.startsWith("chrome:")) {
        console.error(ex);
      }
      return false;
    }
  },

  /**
   * Enable or disable storing logins for the specified origin.
   */
  setLoginSavingEnabled(origin, enabled) {
    // Throws if there are bogus values.
    lazy.LoginHelper.checkOriginValue(origin);

    let uri = Services.io.newURI(origin);
    let principal = Services.scriptSecurityManager.createContentPrincipal(
      uri,
      {}
    );
    if (enabled) {
      Services.perms.removeFromPrincipal(principal, PERMISSION_SAVE_LOGINS);
    } else {
      Services.perms.addFromPrincipal(
        principal,
        PERMISSION_SAVE_LOGINS,
        Services.perms.DENY_ACTION
      );
    }

    lazy.log.debug(
      `Enabling login saving for ${origin} now enabled? ${enabled}.`
    );
    lazy.LoginHelper.notifyStorageChanged(
      enabled ? "hostSavingEnabled" : "hostSavingDisabled",
      origin
    );
  },
}; // end of LoginManager implementation
PK
!<�C��2chrome/toolkit/passwordmgr/passwordstorage.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { LoginManagerStorage_json } from "resource://gre/modules/storage-json.sys.mjs";

export class LoginManagerStorage extends LoginManagerStorage_json {
  static #storage = null;

  static create(callback) {
    if (!LoginManagerStorage.#storage) {
      LoginManagerStorage.#storage = new LoginManagerStorage();
      LoginManagerStorage.#storage.initialize().then(callback);
    } else if (callback) {
      callback();
    }

    return LoginManagerStorage.#storage;
  }
}
PK
!<(�o\��modules/storage-json.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * LoginManagerStorage implementation for the JSON back-end.
 */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  FXA_PWDMGR_HOST: "resource://gre/modules/FxAccountsCommon.sys.mjs",
  FXA_PWDMGR_REALM: "resource://gre/modules/FxAccountsCommon.sys.mjs",
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
  LoginStore: "resource://gre/modules/LoginStore.sys.mjs",
});

const SYNCABLE_LOGIN_FIELDS = [
  // `nsILoginInfo` fields.
  "hostname",
  "formSubmitURL",
  "httpRealm",
  "username",
  "password",
  "usernameField",
  "passwordField",

  // `nsILoginMetaInfo` fields.
  "timeCreated",
  "timePasswordChanged",
];

// Compares two logins to determine if their syncable fields changed. The login
// manager fires `modifyLogin` for changes to all fields, including ones we
// don't sync. In particular, `timeLastUsed` changes shouldn't mark the login
// for upload; otherwise, we might overwrite changed passwords before they're
// downloaded (bug 973166).
function isSyncableChange(oldLogin, newLogin) {
  oldLogin.QueryInterface(Ci.nsILoginMetaInfo).QueryInterface(Ci.nsILoginInfo);
  newLogin.QueryInterface(Ci.nsILoginMetaInfo).QueryInterface(Ci.nsILoginInfo);
  return SYNCABLE_LOGIN_FIELDS.some(prop => oldLogin[prop] != newLogin[prop]);
}

// Returns true if the argument is for the FxA login.
function isFXAHost(login) {
  return login.hostname == lazy.FXA_PWDMGR_HOST;
}

export class LoginManagerStorage_json {
  constructor() {
    this.__crypto = null; // nsILoginManagerCrypto service
    this.__decryptedPotentiallyVulnerablePasswords = null;
  }

  get _crypto() {
    if (!this.__crypto) {
      this.__crypto = Cc["@mozilla.org/login-manager/crypto/SDR;1"].getService(
        Ci.nsILoginManagerCrypto
      );
    }
    return this.__crypto;
  }

  get _decryptedPotentiallyVulnerablePasswords() {
    if (!this.__decryptedPotentiallyVulnerablePasswords) {
      this._store.ensureDataReady();
      this.__decryptedPotentiallyVulnerablePasswords = [];
      for (const potentiallyVulnerablePassword of this._store.data
        .potentiallyVulnerablePasswords) {
        const decryptedPotentiallyVulnerablePassword = this._crypto.decrypt(
          potentiallyVulnerablePassword.encryptedPassword
        );
        this.__decryptedPotentiallyVulnerablePasswords.push(
          decryptedPotentiallyVulnerablePassword
        );
      }
    }
    return this.__decryptedPotentiallyVulnerablePasswords;
  }

  initialize() {
    try {
      // Force initialization of the crypto module.
      // See bug 717490 comment 17.
      this._crypto;

      let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile).path;

      // Set the reference to LoginStore synchronously.
      let jsonPath = PathUtils.join(profileDir, "logins.json");
      let backupPath = "";
      let loginsBackupEnabled = Services.prefs.getBoolPref(
        "signon.backup.enabled"
      );
      if (loginsBackupEnabled) {
        backupPath = PathUtils.join(profileDir, "logins-backup.json");
      }
      this._store = new lazy.LoginStore(jsonPath, backupPath);

      return (async () => {
        // Load the data asynchronously.
        this.log(`Opening database at ${this._store.path}.`);
        await this._store.load();
      })().catch(console.error);
    } catch (e) {
      this.log(`Initialization failed ${e.name}.`);
      throw new Error("Initialization failed");
    }
  }

  /**
   * Internal method used by regression tests only.  It is called before
   * replacing this storage module with a new instance.
   */
  terminate() {
    this._store._saver.disarm();
    return this._store._save();
  }

  /**
   * Returns the "sync id" used by Sync to know whether the store is current with
   * respect to the sync servers. It is stored encrypted, but only so we
   * can detect failure to decrypt (for example, a "reset" of the primary
   * password will leave all logins alone, but they will fail to decrypt. We
   * also want this metadata to be unavailable in that scenario)
   *
   * Returns null if the data doesn't exist or if the data can't be
   * decrypted (including if the primary-password prompt is cancelled). This is
   * OK for Sync as it can't even begin syncing if the primary-password is
   * locked as the sync encrytion keys are stored in this login manager.
   */
  async getSyncID() {
    await this._store.load();
    if (!this._store.data.sync) {
      return null;
    }
    let raw = this._store.data.sync.syncID;
    try {
      return raw ? this._crypto.decrypt(raw) : null;
    } catch (e) {
      if (e.result == Cr.NS_ERROR_FAILURE) {
        this.log("Could not decrypt the syncID - returning null.");
        return null;
      }
      // any other errors get re-thrown.
      throw e;
    }
  }

  async setSyncID(syncID) {
    await this._store.load();
    if (!this._store.data.sync) {
      this._store.data.sync = {};
    }
    this._store.data.sync.syncID = syncID ? this._crypto.encrypt(syncID) : null;
    this._store.saveSoon();
  }

  async getLastSync() {
    await this._store.load();
    if (!this._store.data.sync) {
      return 0;
    }
    return this._store.data.sync.lastSync || 0.0;
  }

  async setLastSync(timestamp) {
    await this._store.load();
    if (!this._store.data.sync) {
      this._store.data.sync = {};
    }
    this._store.data.sync.lastSync = timestamp;
    this._store.saveSoon();
  }

  #incrementSyncCounter(login) {
    login.syncCounter++;
  }

  async resetSyncCounter(guid, value) {
    this._store.ensureDataReady();

    // This will also find deleted items.
    let login = this._store.data.logins.find(login => login.guid == guid);
    if (login?.syncCounter > 0) {
      login.syncCounter = Math.max(0, login.syncCounter - value);
      login.everSynced = true;
    }

    this._store.saveSoon();
  }

  // Returns false if the login has marked as deleted or doesn't exist.
  loginIsDeleted(guid) {
    let login = this._store.data.logins.find(l => l.guid == guid);
    return !!login?.deleted;
  }

  // Synrhronuously stores encrypted login, returns login clone with upserted
  // uuid and updated timestamps
  #addLogin(login) {
    this._store.ensureDataReady();

    // Throws if there are bogus values.
    lazy.LoginHelper.checkLoginValues(login);

    // Clone the login, so we don't modify the caller's object.
    let loginClone = login.clone();

    // Initialize the nsILoginMetaInfo fields, unless the caller gave us values
    loginClone.QueryInterface(Ci.nsILoginMetaInfo);
    if (loginClone.guid) {
      let guid = loginClone.guid;
      if (!this._isGuidUnique(guid)) {
        // We have an existing GUID, but it's possible that entry is unable
        // to be decrypted - if that's the case we remove the existing one
        // and allow this one to be added.
        let existing = this._searchLogins({ guid })[0];
        if (this._decryptLogins(existing).length) {
          // Existing item is good, so it's an error to try and re-add it.
          throw new Error("specified GUID already exists");
        }
        // find and remove the existing bad entry.
        let foundIndex = this._store.data.logins.findIndex(l => l.guid == guid);
        if (foundIndex == -1) {
          throw new Error("can't find a matching GUID to remove");
        }
        this._store.data.logins.splice(foundIndex, 1);
      }
    } else {
      loginClone.guid = Services.uuid.generateUUID().toString();
    }

    // Set timestamps
    let currentTime = Date.now();
    if (!loginClone.timeCreated) {
      loginClone.timeCreated = currentTime;
    }
    if (!loginClone.timeLastUsed) {
      loginClone.timeLastUsed = currentTime;
    }
    if (!loginClone.timePasswordChanged) {
      loginClone.timePasswordChanged = currentTime;
    }
    if (!loginClone.timesUsed) {
      loginClone.timesUsed = 1;
    }

    // If the everSynced is already set, then this login is an incoming
    // sync record, so there is no need to mark this as needed to be synced.
    if (!loginClone.everSynced && !isFXAHost(loginClone)) {
      this.#incrementSyncCounter(loginClone);
    }

    this._store.data.logins.push({
      id: this._store.data.nextId++,
      hostname: loginClone.origin,
      httpRealm: loginClone.httpRealm,
      formSubmitURL: loginClone.formActionOrigin,
      usernameField: loginClone.usernameField,
      passwordField: loginClone.passwordField,
      encryptedUsername: loginClone.username,
      encryptedPassword: loginClone.password,
      guid: loginClone.guid,
      encType: this._crypto.defaultEncType,
      timeCreated: loginClone.timeCreated,
      timeLastUsed: loginClone.timeLastUsed,
      timePasswordChanged: loginClone.timePasswordChanged,
      timesUsed: loginClone.timesUsed,
      syncCounter: loginClone.syncCounter,
      everSynced: loginClone.everSynced,
      encryptedUnknownFields: loginClone.unknownFields,
    });
    this._store.saveSoon();

    return loginClone;
  }

  async addLoginsAsync(logins, continueOnDuplicates = false) {
    if (logins.length === 0) {
      return logins;
    }

    const encryptedLogins = await this.#encryptLogins(logins);

    const resultLogins = [];
    for (const [login, encryptedLogin] of encryptedLogins) {
      // check for duplicates
      let loginData = {
        origin: login.origin,
        formActionOrigin: login.formActionOrigin,
        httpRealm: login.httpRealm,
      };
      const existingLogins = await Services.logins.searchLoginsAsync(loginData);

      const matchingLogin = existingLogins.find(l => login.matches(l, true));
      if (matchingLogin) {
        if (continueOnDuplicates) {
          continue;
        } else {
          throw lazy.LoginHelper.createLoginAlreadyExistsError(
            matchingLogin.guid
          );
        }
      }

      const resultLogin = this.#addLogin(encryptedLogin);

      // restore unencrypted username and password for use in `addLogin` event
      // and return value
      resultLogin.username = login.username;
      resultLogin.password = login.password;

      // Send a notification that a login was added.
      lazy.LoginHelper.notifyStorageChanged("addLogin", resultLogin);

      resultLogins.push(resultLogin);
    }

    return resultLogins;
  }

  removeLogin(login, fromSync) {
    this._store.ensureDataReady();

    let [idToDelete, storedLogin] = this._getIdForLogin(login);
    if (!idToDelete) {
      throw new Error("No matching logins");
    }

    let foundIndex = this._store.data.logins.findIndex(l => l.id == idToDelete);
    if (foundIndex != -1) {
      let login = this._store.data.logins[foundIndex];
      if (!login.deleted) {
        if (fromSync) {
          this.#replaceLoginWithTombstone(login);
        } else if (login.everSynced) {
          // The login has been synced, so mark it as deleted.
          this.#incrementSyncCounter(login);
          this.#replaceLoginWithTombstone(login);
        } else {
          // The login was never synced, so just remove it from the data.
          this._store.data.logins.splice(foundIndex, 1);
        }

        this._store.saveSoon();
      }
    }

    lazy.LoginHelper.notifyStorageChanged("removeLogin", storedLogin);
  }

  modifyLogin(oldLogin, newLoginData, fromSync) {
    this._store.ensureDataReady();

    let [idToModify, oldStoredLogin] = this._getIdForLogin(oldLogin);
    if (!idToModify) {
      throw new Error("No matching logins");
    }

    let newLogin = lazy.LoginHelper.buildModifiedLogin(
      oldStoredLogin,
      newLoginData
    );

    // Check if the new GUID is duplicate.
    if (
      newLogin.guid != oldStoredLogin.guid &&
      !this._isGuidUnique(newLogin.guid)
    ) {
      throw new Error("specified GUID already exists");
    }

    // Look for an existing entry in case key properties changed.
    if (!newLogin.matches(oldLogin, true)) {
      let loginData = {
        origin: newLogin.origin,
        formActionOrigin: newLogin.formActionOrigin,
        httpRealm: newLogin.httpRealm,
      };

      let logins = this.searchLogins(
        lazy.LoginHelper.newPropertyBag(loginData)
      );

      let matchingLogin = logins.find(login => newLogin.matches(login, true));
      if (matchingLogin) {
        throw lazy.LoginHelper.createLoginAlreadyExistsError(
          matchingLogin.guid
        );
      }
    }

    // Don't sync changes to the accounts password or when changes were only
    // made to fields that should not be synced.
    if (
      !fromSync &&
      !isFXAHost(newLogin) &&
      isSyncableChange(oldLogin, newLogin)
    ) {
      this.#incrementSyncCounter(newLogin);
    }

    // Get the encrypted value of the username and password.
    let [encUsername, encPassword, encType, encUnknownFields] =
      this._encryptLogin(newLogin);

    for (let loginItem of this._store.data.logins) {
      if (loginItem.id == idToModify && !loginItem.deleted) {
        loginItem.hostname = newLogin.origin;
        loginItem.httpRealm = newLogin.httpRealm;
        loginItem.formSubmitURL = newLogin.formActionOrigin;
        loginItem.usernameField = newLogin.usernameField;
        loginItem.passwordField = newLogin.passwordField;
        loginItem.encryptedUsername = encUsername;
        loginItem.encryptedPassword = encPassword;
        loginItem.guid = newLogin.guid;
        loginItem.encType = encType;
        loginItem.timeCreated = newLogin.timeCreated;
        loginItem.timeLastUsed = newLogin.timeLastUsed;
        loginItem.timePasswordChanged = newLogin.timePasswordChanged;
        loginItem.timesUsed = newLogin.timesUsed;
        loginItem.encryptedUnknownFields = encUnknownFields;
        loginItem.syncCounter = newLogin.syncCounter;
        this._store.saveSoon();
        break;
      }
    }

    lazy.LoginHelper.notifyStorageChanged("modifyLogin", [
      oldStoredLogin,
      newLogin,
    ]);
  }

  // Replace the login with a tombstone. It has a guid and sync-related properties,
  // but does not contain the login or password information.
  #replaceLoginWithTombstone(login) {
    login.deleted = true;

    // Delete all fields except guid, timePasswordChanged, syncCounter
    // and everSynced;
    delete login.hostname;
    delete login.httpRealm;
    delete login.formSubmitURL;
    delete login.usernameField;
    delete login.passwordField;
    delete login.encryptedUsername;
    delete login.encryptedPassword;
    delete login.encType;
    delete login.timeCreated;
    delete login.timeLastUsed;
    delete login.timesUsed;
    delete login.encryptedUnknownFields;
  }

  recordPasswordUse(login) {
    // Update the lastUsed timestamp and increment the use count.
    let propBag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
      Ci.nsIWritablePropertyBag
    );
    propBag.setProperty("timeLastUsed", Date.now());
    propBag.setProperty("timesUsedIncrement", 1);
    this.modifyLogin(login, propBag);
  }

  async recordBreachAlertDismissal(loginGUID) {
    this._store.ensureDataReady();
    const dismissedBreachAlertsByLoginGUID =
      this._store._data.dismissedBreachAlertsByLoginGUID;

    dismissedBreachAlertsByLoginGUID[loginGUID] = {
      timeBreachAlertDismissed: new Date().getTime(),
    };

    return this._store.saveSoon();
  }

  getBreachAlertDismissalsByLoginGUID() {
    this._store.ensureDataReady();
    return this._store._data.dismissedBreachAlertsByLoginGUID;
  }

  /**
   * Returns an array of nsILoginInfo. If decryption of a login
   * fails due to a corrupt entry, the login is not included in
   * the resulting array.
   *
   * @resolve {nsILoginInfo[]}
   */
  async getAllLogins(includeDeleted) {
    this._store.ensureDataReady();

    let [logins] = this._searchLogins({}, includeDeleted);
    if (!logins.length) {
      return [];
    }

    return this.#decryptLogins(logins);
  }

  async searchLoginsAsync(matchData, includeDeleted) {
    this.log(`Searching for matching logins for origin ${matchData.origin}.`);
    let result = this.searchLogins(
      lazy.LoginHelper.newPropertyBag(matchData),
      includeDeleted
    );
    // Emulate being async:
    return Promise.resolve(result);
  }

  /**
   * Public wrapper around _searchLogins to convert the nsIPropertyBag to a
   * JavaScript object and decrypt the results.
   *
   * @return {nsILoginInfo[]} which are decrypted.
   */
  searchLogins(matchData, includeDeleted) {
    this._store.ensureDataReady();

    let realMatchData = {};
    let options = {};

    matchData.QueryInterface(Ci.nsIPropertyBag2);
    if (matchData.hasKey("guid")) {
      // Enforce GUID-based filtering when available, since the origin of the
      // login may not match the origin of the form in the case of scheme
      // upgrades.
      realMatchData = { guid: matchData.getProperty("guid") };
    } else {
      // Convert nsIPropertyBag to normal JS object.
      for (let prop of matchData.enumerator) {
        switch (prop.name) {
          // Some property names aren't field names but are special options to
          // affect the search.
          case "acceptDifferentSubdomains":
          case "schemeUpgrades":
          case "acceptRelatedRealms":
          case "relatedRealms": {
            options[prop.name] = prop.value;
            break;
          }
          default: {
            realMatchData[prop.name] = prop.value;
            break;
          }
        }
      }
    }

    let [logins] = this._searchLogins(realMatchData, includeDeleted, options);

    // Decrypt entries found for the caller.
    logins = this._decryptLogins(logins);

    return logins;
  }

  /**
   * Private method to perform arbitrary searches on any field. Decryption is
   * left to the caller.
   *
   * formActionOrigin is handled specially for compatibility. If a null string
   * is passed and other match fields are present, it is treated as if it was
   * not present.
   *
   * Returns [logins, ids] for logins that match the arguments, where logins
   * is an array of encrypted nsLoginInfo and ids is an array of associated
   * ids in the database.
   */
  _searchLogins(
    matchData,
    includeDeleted = false,
    aOptions = {
      schemeUpgrades: false,
      acceptDifferentSubdomains: false,
      acceptRelatedRealms: false,
      relatedRealms: [],
    },
    candidateLogins = this._store.data.logins
  ) {
    function match(aLoginItem) {
      for (let field in matchData) {
        let wantedValue = matchData[field];

        // Override the storage field name for some fields due to backwards
        // compatibility with Sync/storage.
        let storageFieldName = field;
        switch (field) {
          case "formActionOrigin": {
            storageFieldName = "formSubmitURL";
            break;
          }
          case "origin": {
            storageFieldName = "hostname";
            break;
          }
        }

        switch (field) {
          case "formActionOrigin":
            if (wantedValue != null) {
              // Historical compatibility requires this special case
              if (
                aLoginItem.formSubmitURL == "" ||
                (wantedValue == "" && Object.keys(matchData).length != 1)
              ) {
                break;
              }
              if (
                !lazy.LoginHelper.isOriginMatching(
                  aLoginItem[storageFieldName],
                  wantedValue,
                  aOptions
                )
              ) {
                return false;
              }
              break;
            }
          // fall through
          case "origin":
            if (wantedValue != null) {
              // needed for formActionOrigin fall through
              if (
                !lazy.LoginHelper.isOriginMatching(
                  aLoginItem[storageFieldName],
                  wantedValue,
                  aOptions
                )
              ) {
                return false;
              }
              break;
            }
          // Normal cases.
          // fall through
          case "httpRealm":
          case "id":
          case "usernameField":
          case "passwordField":
          case "encryptedUsername":
          case "encryptedPassword":
          case "guid":
          case "encType":
          case "timeCreated":
          case "timeLastUsed":
          case "timePasswordChanged":
          case "timesUsed":
          case "syncCounter":
          case "everSynced":
            if (wantedValue == null && aLoginItem[storageFieldName]) {
              return false;
            } else if (aLoginItem[storageFieldName] != wantedValue) {
              return false;
            }
            break;
          // Fail if caller requests an unknown property.
          default:
            throw new Error("Unexpected field: " + field);
        }
      }
      return true;
    }

    let foundLogins = [],
      foundIds = [];
    for (let loginItem of candidateLogins) {
      if (loginItem.deleted && !includeDeleted) {
        continue; // skip deleted items
      }

      if (match(loginItem)) {
        // Create the new nsLoginInfo object, push to array
        let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(
          Ci.nsILoginInfo
        );
        login.init(
          loginItem.hostname,
          loginItem.formSubmitURL,
          loginItem.httpRealm,
          loginItem.encryptedUsername,
          loginItem.encryptedPassword,
          loginItem.usernameField,
          loginItem.passwordField
        );
        // set nsILoginMetaInfo values
        login.QueryInterface(Ci.nsILoginMetaInfo);
        login.guid = loginItem.guid;
        login.timeCreated = loginItem.timeCreated;
        login.timeLastUsed = loginItem.timeLastUsed;
        login.timePasswordChanged = loginItem.timePasswordChanged;
        login.timesUsed = loginItem.timesUsed;
        login.syncCounter = loginItem.syncCounter;
        login.everSynced = loginItem.everSynced;

        // Any unknown fields along for the ride
        login.unknownFields = loginItem.encryptedUnknownFields;
        foundLogins.push(login);
        foundIds.push(loginItem.id);
      }
    }

    this.log(
      `Returning ${foundLogins.length} logins for specified origin with options ${aOptions}`
    );
    return [foundLogins, foundIds];
  }

  /**
   * Removes all logins from local storage, including FxA Sync key.
   *
   * NOTE: You probably want removeAllUserFacingLogins instead of this function.
   *
   */
  removeAllLogins() {
    this.#removeLogins(false, true);
  }

  /**
   * Removes all user facing logins from storage. e.g. all logins except the FxA Sync key
   *
   * If you need to remove the FxA key, use `removeAllLogins` instead
   *
   * @param fullyRemove remove the logins rather than mark them deleted.
   */
  removeAllUserFacingLogins(fullyRemove) {
    this.#removeLogins(fullyRemove, false);
  }

  /**
   * Removes all logins from storage. If removeFXALogin is true, then the FxA Sync
   * key is also removed.
   *
   * @param fullyRemove remove the logins rather than mark them deleted.
   * @param removeFXALogin also remove the FxA Sync key.
   */
  #removeLogins(fullyRemove, removeFXALogin = false) {
    this._store.ensureDataReady();
    this.log("Removing all logins.");

    let removedLogins = [];
    let remainingLogins = [];
    for (let login of this._store.data.logins) {
      if (
        !removeFXALogin &&
        isFXAHost(login) &&
        login.httpRealm == lazy.FXA_PWDMGR_REALM
      ) {
        remainingLogins.push(login);
      } else {
        removedLogins.push(login);
        if (!fullyRemove && login?.everSynced) {
          // The login has been synced, so mark it as deleted.
          this.#incrementSyncCounter(login);
          this.#replaceLoginWithTombstone(login);
          remainingLogins.push(login);
        }
      }
    }
    this._store.data.logins = remainingLogins;

    this._store.data.potentiallyVulnerablePasswords = [];
    this.__decryptedPotentiallyVulnerablePasswords = null;
    this._store.data.dismissedBreachAlertsByLoginGUID = {};
    this._store.saveSoon();

    lazy.LoginHelper.notifyStorageChanged("removeAllLogins", removedLogins);
  }

  findLogins(origin, formActionOrigin, httpRealm) {
    this._store.ensureDataReady();

    let loginData = {
      origin,
      formActionOrigin,
      httpRealm,
    };
    let matchData = {};
    for (let field of ["origin", "formActionOrigin", "httpRealm"]) {
      if (loginData[field] != "") {
        matchData[field] = loginData[field];
      }
    }
    let [logins] = this._searchLogins(matchData);

    // Decrypt entries found for the caller.
    logins = this._decryptLogins(logins);

    this.log(`Returning ${logins.length} logins.`);
    return logins;
  }

  countLogins(origin, formActionOrigin, httpRealm) {
    this._store.ensureDataReady();

    let loginData = {
      origin,
      formActionOrigin,
      httpRealm,
    };
    let matchData = {};
    for (let field of ["origin", "formActionOrigin", "httpRealm"]) {
      if (loginData[field] != "") {
        matchData[field] = loginData[field];
      }
    }
    let [logins] = this._searchLogins(matchData);

    this.log(`Counted ${logins.length} logins.`);
    return logins.length;
  }

  addPotentiallyVulnerablePassword(login) {
    this._store.ensureDataReady();
    // this breached password is already stored
    if (this.isPotentiallyVulnerablePassword(login)) {
      return;
    }
    this.__decryptedPotentiallyVulnerablePasswords.push(login.password);

    this._store.data.potentiallyVulnerablePasswords.push({
      encryptedPassword: this._crypto.encrypt(login.password),
    });
    this._store.saveSoon();
  }

  isPotentiallyVulnerablePassword(login) {
    return this._decryptedPotentiallyVulnerablePasswords.includes(
      login.password
    );
  }

  clearAllPotentiallyVulnerablePasswords() {
    this._store.ensureDataReady();
    if (!this._store.data.potentiallyVulnerablePasswords.length) {
      // No need to write to disk
      return;
    }
    this._store.data.potentiallyVulnerablePasswords = [];
    this._store.saveSoon();
    this.__decryptedPotentiallyVulnerablePasswords = null;
  }

  get uiBusy() {
    return this._crypto.uiBusy;
  }

  get isLoggedIn() {
    return this._crypto.isLoggedIn;
  }

  /**
   * Returns an array with two items: [id, login]. If the login was not
   * found, both items will be null. The returned login contains the actual
   * stored login (useful for looking at the actual nsILoginMetaInfo values).
   */
  _getIdForLogin(login) {
    this._store.ensureDataReady();

    let matchData = {};
    for (let field of ["origin", "formActionOrigin", "httpRealm"]) {
      if (login[field] != "") {
        matchData[field] = login[field];
      }
    }
    let [logins, ids] = this._searchLogins(matchData);

    let id = null;
    let foundLogin = null;

    // The specified login isn't encrypted, so we need to ensure
    // the logins we're comparing with are decrypted. We decrypt one entry
    // at a time, lest _decryptLogins return fewer entries and screw up
    // indices between the two.
    for (let i = 0; i < logins.length; i++) {
      let [decryptedLogin] = this._decryptLogins([logins[i]]);

      if (!decryptedLogin || !decryptedLogin.equals(login)) {
        continue;
      }

      // We've found a match, set id and break
      foundLogin = decryptedLogin;
      id = ids[i];
      break;
    }

    return [id, foundLogin];
  }

  /**
   * Checks to see if the specified GUID already exists.
   */
  _isGuidUnique(guid) {
    this._store.ensureDataReady();

    return this._store.data.logins.every(l => l.guid != guid);
  }

  /*
   * Asynchronously encrypt multiple logins.
   * Returns a promise resolving to an array of arrays containing two entries:
   * the original login and a clone with encrypted properties.
   */
  async #encryptLogins(logins) {
    if (logins.length === 0) {
      return logins;
    }

    const plaintexts = logins.reduce(
      (memo, { username, password, unknownFields }) =>
        memo.concat([username, password, unknownFields]),
      []
    );
    const ciphertexts = await this._crypto.encryptMany(plaintexts);

    return logins.map((login, i) => {
      const [encryptedUsername, encryptedPassword, encryptedUnknownFields] =
        ciphertexts.slice(3 * i, 3 * i + 3);

      const encryptedLogin = login.clone();
      encryptedLogin.username = encryptedUsername;
      encryptedLogin.password = encryptedPassword;
      encryptedLogin.unknownFields = encryptedUnknownFields;

      return [login, encryptedLogin];
    });
  }

  /*
   * Asynchronously decrypt multiple logins.
   * Returns a promise resolving to an array of clones with decrypted properties.
   */
  async #decryptLogins(logins) {
    if (logins.length === 0) {
      return logins;
    }

    const ciphertexts = logins.reduce(
      (memo, { username, password, unknownFields }) =>
        memo.concat([username, password, unknownFields]),
      []
    );
    const plaintexts = await this._crypto.decryptMany(ciphertexts);

    return logins
      .map((login, i) => {
        // Deleted logins don't have any info to decrypt.
        const decryptedLogin = login.clone();
        if (this.loginIsDeleted(login.guid)) {
          return decryptedLogin;
        }

        const [username, password, unknownFields] = plaintexts.slice(
          3 * i,
          3 * i + 3
        );

        // If the username or password is blank it means that decryption may have
        // failed during decryptMany but we can't differentiate an empty string
        // value from a failure so we attempt to decrypt again and check the
        // result.
        if (!username || !password) {
          try {
            this._crypto.decrypt(login.username);
            this._crypto.decrypt(login.password);
          } catch (e) {
            // If decryption failed (corrupt entry?), just return it as it is.
            // Rethrow other errors (like canceling entry of a primary pw)
            if (e.result == Cr.NS_ERROR_FAILURE) {
              this.log(
                `Could not decrypt login: ${
                  login.QueryInterface(Ci.nsILoginMetaInfo).guid
                }.`
              );
              return null;
            }
            throw e;
          }
        }

        decryptedLogin.username = username;
        decryptedLogin.password = password;
        decryptedLogin.unknownFields = unknownFields;

        return decryptedLogin;
      })
      .filter(Boolean);
  }

  /**
   * Returns the encrypted username, password, and encrypton type for the specified
   * login. Can throw if the user cancels a primary password entry.
   */
  _encryptLogin(login) {
    let encUsername = this._crypto.encrypt(login.username);
    let encPassword = this._crypto.encrypt(login.password);

    // Unknown fields should be encrypted since we can't know whether new fields
    // from other clients will contain sensitive data or not
    let encUnknownFields = null;
    if (login.unknownFields) {
      encUnknownFields = this._crypto.encrypt(login.unknownFields);
    }
    let encType = this._crypto.defaultEncType;

    return [encUsername, encPassword, encType, encUnknownFields];
  }

  /**
   * Decrypts username and password fields in the provided array of
   * logins.
   *
   * The entries specified by the array will be decrypted, if possible.
   * An array of successfully decrypted logins will be returned. The return
   * value should be given to external callers (since still-encrypted
   * entries are useless), whereas internal callers generally don't want
   * to lose unencrypted entries (eg, because the user clicked Cancel
   * instead of entering their primary password)
   */
  _decryptLogins(logins) {
    let result = [];

    for (let login of logins) {
      if (this.loginIsDeleted(login.guid)) {
        result.push(login);
        continue;
      }

      try {
        login.username = this._crypto.decrypt(login.username);
        login.password = this._crypto.decrypt(login.password);
        // Verify unknownFields actually has a value
        if (login.unknownFields) {
          login.unknownFields = this._crypto.decrypt(login.unknownFields);
        }
      } catch (e) {
        // If decryption failed (corrupt entry?), just skip it.
        // Rethrow other errors (like canceling entry of a primary pw)
        if (e.result == Cr.NS_ERROR_FAILURE) {
          continue;
        }
        throw e;
      }
      result.push(login);
    }

    return result;
  }
}

ChromeUtils.defineLazyGetter(LoginManagerStorage_json.prototype, "log", () => {
  let logger = lazy.LoginHelper.createLogger("Login storage");
  return logger.log.bind(logger);
});
PK
!<�F�1��2chrome/en-US/locale/en-US/pipnss/pipnss.properties
CertPasswordPrompt=Please enter the password for the PKCS#11 token %S.

CertPasswordPromptDefault=Please enter your Primary Password.


RootCertModuleName=Builtin Roots Module
ManufacturerID=Mozilla.org
LibraryDescription=PSM Internal Crypto Services
TokenDescription=Generic Crypto Services
PrivateTokenDescription=Software Security Device
SlotDescription=PSM Internal Cryptographic Services
PrivateSlotDescription=PSM Private Keys
Fips140TokenDescription=Software Security Device (FIPS)
Fips140SlotDescription=FIPS 140 Cryptographic, Key and Certificate Services

nick_template=%1$s’s %2$s ID

CertDumpKUSign=Signing
CertDumpKUNonRep=Non-repudiation
CertDumpKUEnc=Key Encipherment
CertDumpKUDEnc=Data Encipherment
CertDumpKUKA=Key Agreement
CertDumpKUCertSign=Certificate Signer
CertDumpKUCRLSigner=CRL Signer

PSMERR_SSL_Disabled=Can’t connect securely because the SSL protocol has been disabled.
PSMERR_SSL2_Disabled=Can’t connect securely because the site uses an older, insecure version of the SSL protocol.
PSMERR_HostReusedIssuerSerial=You have received an invalid certificate.  Please contact the server administrator or email correspondent and give them the following information:\n\nYour certificate contains the same serial number as another certificate issued by the certificate authority.  Please get a new certificate containing a unique serial number.

SSLConnectionErrorPrefix2=An error occurred during a connection to %1$S. %2$S\n

certErrorIntro=%S uses an invalid security certificate.

certErrorTrust_SelfSigned=The certificate is not trusted because it is self-signed.
certErrorTrust_UnknownIssuer=The certificate is not trusted because the issuer certificate is unknown.
certErrorTrust_UnknownIssuer2=The server might not be sending the appropriate intermediate certificates.
certErrorTrust_UnknownIssuer3=An additional root certificate may need to be imported.
certErrorTrust_CaInvalid=The certificate is not trusted because it was issued by an invalid CA certificate.
certErrorTrust_Issuer=The certificate is not trusted because the issuer certificate is not trusted.
certErrorTrust_SignatureAlgorithmDisabled=The certificate is not trusted because it was signed using a signature algorithm that was disabled because that algorithm is not secure.
certErrorTrust_ExpiredIssuer=The certificate is not trusted because the issuer certificate has expired.
certErrorTrust_Untrusted=The certificate does not come from a trusted source.
certErrorTrust_MitM=Your connection is being intercepted by a TLS proxy. Uninstall it if possible or configure your device to trust its root certificate.

certErrorMismatch=The certificate is not valid for the name %S.
certErrorMismatchSinglePrefix=The certificate is only valid for %S.
certErrorMismatchMultiple=The certificate is only valid for the following names:

certErrorExpiredNow=The certificate expired on %1$S. The current time is %2$S.

certErrorNotYetValidNow=The certificate will not be valid until %1$S. The current time is %2$S.

certErrorMitM=Websites prove their identity via certificates, which are issued by certificate authorities.
certErrorMitM2=%S is backed by the non-profit Mozilla, which administers a completely open certificate authority (CA) store. The CA store helps ensure that certificate authorities are following best practices for user security.
certErrorMitM3=%S uses the Mozilla CA store to verify that a connection is secure, rather than certificates supplied by the user’s operating system. So, if an antivirus program or a network is intercepting a connection with a security certificate issued by a CA that is not in the Mozilla CA store, the connection is considered unsafe.

certErrorSymantecDistrustAdministrator=You may notify the website’s administrator about this problem.

certErrorCodePrefix3=Error code: %S

P12DefaultNickname=Imported Certificate
CertUnknown=Unknown
CertNoEmailAddress=(no email address)
CaCertExists=This certificate is already installed as a certificate authority.
NotACACert=This is not a certificate authority certificate, so it can’t be imported into the certificate authority list.
UserCertIgnoredNoPrivateKey=This personal certificate can’t be installed because you do not own the corresponding private key which was created when the certificate was requested.
UserCertImported=Your personal certificate has been installed. You should keep a backup copy of this certificate.
CertOrgUnknown=(Unknown)
CertNotStored=(Not Stored)
CertExceptionPermanent=Permanent
CertExceptionTemporary=Temporary
PK
!<��euCCmodules/LoginStore.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Handles serialization of the data and persistence into a file.
 *
 * The file is stored in JSON format, without indentation, using UTF-8 encoding.
 * With indentation applied, the file would look like this:
 *
 * {
 *   "logins": [
 *     {
 *       "id": 2,
 *       "hostname": "http://www.example.com",
 *       "httpRealm": null,
 *       "formSubmitURL": "http://www.example.com",
 *       "usernameField": "username_field",
 *       "passwordField": "password_field",
 *       "encryptedUsername": "...",
 *       "encryptedPassword": "...",
 *       "guid": "...",
 *       "encType": 1,
 *       "timeCreated": 1262304000000,
 *       "timeLastUsed": 1262304000000,
 *       "timePasswordChanged": 1262476800000,
 *       "timesUsed": 1
 *        // only present if other clients had fields we didn't know about
 *       "encryptedUnknownFields: "...",
 *     },
 *     {
 *       "id": 4,
 *       (...)
 *     }
 *   ],
 *   "nextId": 10,
 *   "version": 1
 * }
 */

// Globals

import { JSONFile } from "resource://gre/modules/JSONFile.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  FXA_PWDMGR_HOST: "resource://gre/modules/FxAccountsCommon.sys.mjs",
  FXA_PWDMGR_REALM: "resource://gre/modules/FxAccountsCommon.sys.mjs",
});

/**
 * Current data version assigned by the code that last touched the data.
 *
 * This number should be updated only when it is important to understand whether
 * an old version of the code has touched the data, for example to execute an
 * update logic.  In most cases, this number should not be changed, in
 * particular when no special one-time update logic is needed.
 *
 * For example, this number should NOT be changed when a new optional field is
 * added to a login entry.
 */
const kDataVersion = 3;

const MAX_DATE_MS = 8640000000000000;

// LoginStore

/**
 * Inherits from JSONFile and handles serialization of login-related data and
 * persistence into a file.
 *
 * @param aPath
 *        String containing the file path where data should be saved.
 */
export function LoginStore(aPath, aBackupPath = "") {
  JSONFile.call(this, {
    path: aPath,
    dataPostProcessor: this._dataPostProcessor.bind(this),
    backupTo: aBackupPath,
  });
}

LoginStore.prototype = Object.create(JSONFile.prototype);
LoginStore.prototype.constructor = LoginStore;

LoginStore.prototype._save = async function () {
  await JSONFile.prototype._save.call(this);
  // Notify tests that writes to the login store is complete.
  Services.obs.notifyObservers(null, "password-storage-updated");

  if (this._options.backupTo) {
    await this._backupHandler();
  }
};

/**
 * Delete logins backup file if the last saved login was removed using
 * removeLogin() or if all logins were removed at once using removeAllUserFacingLogins().
 * Note that if the user has a fxa key stored as a login, we just update the
 * backup to only store the key when the last saved user facing login is removed.
 */
LoginStore.prototype._backupHandler = async function () {
  const logins = this._data.logins;
  // Return early if more than one login is stored because the backup won't need
  // updating in this case.
  if (logins.length > 1) {
    return;
  }

  // If one login is stored and it's a fxa sync key, we update the backup to store the
  // key only.
  if (
    logins.length &&
    logins[0].hostname == lazy.FXA_PWDMGR_HOST &&
    logins[0].httpRealm == lazy.FXA_PWDMGR_REALM
  ) {
    try {
      await IOUtils.copy(this.path, this._options.backupTo);

      // This notification is specifically sent out for a test.
      Services.obs.notifyObservers(null, "logins-backup-updated");
    } catch (ex) {
      console.error(ex);
    }
  } else if (!logins.length) {
    // If no logins are stored anymore, delete backup.
    await IOUtils.remove(this._options.backupTo, {
      ignoreAbsent: true,
    });
  }
};

/**
 * Synchronously work on the data just loaded into memory.
 */
LoginStore.prototype._dataPostProcessor = function (data) {
  if (data.nextId === undefined) {
    data.nextId = 1;
  }

  // Create any arrays that are not present in the saved file.
  if (!data.logins) {
    data.logins = [];
  }

  if (!data.potentiallyVulnerablePasswords) {
    data.potentiallyVulnerablePasswords = [];
  }

  if (!data.dismissedBreachAlertsByLoginGUID) {
    data.dismissedBreachAlertsByLoginGUID = {};
  }

  // sanitize dates in logins
  if (!("version" in data) || data.version < 3) {
    let dateProperties = ["timeCreated", "timeLastUsed", "timePasswordChanged"];
    let now = Date.now();
    function getEarliestDate(login, defaultDate) {
      let earliestDate = dateProperties.reduce((earliest, pname) => {
        let ts = login[pname];
        return !ts ? earliest : Math.min(ts, earliest);
      }, defaultDate);
      return earliestDate;
    }
    for (let login of data.logins) {
      for (let pname of dateProperties) {
        let earliestDate;
        if (!login[pname] || login[pname] > MAX_DATE_MS) {
          login[pname] =
            earliestDate || (earliestDate = getEarliestDate(login, now));
        }
      }
    }
  }

  // Indicate that the current version of the code has touched the file.
  data.version = kDataVersion;

  return data;
};
PK
!<`�_NN0modules/services-common/uptake-telemetry.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  ClientID: "resource://gre/modules/ClientID.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "CryptoHash", () => {
  return Components.Constructor(
    "@mozilla.org/security/hash;1",
    "nsICryptoHash",
    "initWithString"
  );
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gSampleRate",
  "services.common.uptake.sampleRate"
);

// Telemetry events id (see Events.yaml).
const TELEMETRY_EVENTS_ID = "uptake.remotecontent.result";

/**
 * A wrapper around certain low-level operations that can be substituted for testing.
 */
export var Policy = {
  _clientIDHash: null,

  getClientID() {
    return lazy.ClientID.getClientID();
  },

  /**
   * Compute an integer in the range [0, 100) using a hash of the
   * client ID.
   *
   * This is useful for sampling clients when trying to report
   * telemetry only for a sample of clients.
   */
  async getClientIDHash() {
    if (this._clientIDHash === null) {
      this._clientIDHash = this._doComputeClientIDHash();
    }
    return this._clientIDHash;
  },

  async _doComputeClientIDHash() {
    const clientID = await this.getClientID();
    let byteArr = new TextEncoder().encode(clientID);
    let hash = new lazy.CryptoHash("sha256");
    hash.update(byteArr, byteArr.length);
    const bytes = hash.finish(false);
    let rem = 0;
    for (let i = 0, len = bytes.length; i < len; i++) {
      rem = ((rem << 8) + (bytes[i].charCodeAt(0) & 0xff)) % 100;
    }
    return rem;
  },

  getChannel() {
    return AppConstants.MOZ_UPDATE_CHANNEL;
  },
};

/**
 * A Telemetry helper to report uptake of remote content.
 */
export class UptakeTelemetry {
  /**
   * Supported uptake statuses:
   *
   * - `UP_TO_DATE`: Local content was already up-to-date with remote content.
   * - `SUCCESS`: Local content was updated successfully.
   * - `BACKOFF`: Remote server asked clients to backoff.
   * - `PARSE_ERROR`: Parsing server response has failed.
   * - `CONTENT_ERROR`: Server response has unexpected content.
   * - `PREF_DISABLED`: Update is disabled in user preferences.
   * - `SIGNATURE_ERROR`: Signature verification after diff-based sync has failed.
   * - `SIGNATURE_RETRY_ERROR`: Signature verification after full fetch has failed.
   * - `CONFLICT_ERROR`: Some remote changes are in conflict with local changes.
   * - `CORRUPTION_ERROR`: Error related to corrupted local data.
   * - `SYNC_ERROR`: Synchronization of remote changes has failed.
   * - `APPLY_ERROR`: Application of changes locally has failed.
   * - `SERVER_ERROR`: Server failed to respond.
   * - `CERTIFICATE_ERROR`: Server certificate verification has failed.
   * - `DOWNLOAD_ERROR`: Data could not be fully retrieved.
   * - `TIMEOUT_ERROR`: Server response has timed out.
   * - `NETWORK_ERROR`: Communication with server has failed.
   * - `NETWORK_OFFLINE_ERROR`: Network not available.
   * - `SHUTDOWN_ERROR`: Error occuring during shutdown.
   * - `UNKNOWN_ERROR`: Uncategorized error.
   * - `CLEANUP_ERROR`: Clean-up of temporary files has failed.
   * - `SYNC_BROKEN_ERROR`: Synchronization is broken.
   * - `CUSTOM_1_ERROR`: Update source specific error #1.
   * - `CUSTOM_2_ERROR`: Update source specific error #2.
   * - `CUSTOM_3_ERROR`: Update source specific error #3.
   * - `CUSTOM_4_ERROR`: Update source specific error #4.
   * - `CUSTOM_5_ERROR`: Update source specific error #5.
   *
   * @type {Object}
   */
  static get STATUS() {
    return {
      UP_TO_DATE: "up_to_date",
      SUCCESS: "success",
      BACKOFF: "backoff",
      PARSE_ERROR: "parse_error",
      CONTENT_ERROR: "content_error",
      PREF_DISABLED: "pref_disabled",
      SIGNATURE_ERROR: "sign_error",
      SIGNATURE_RETRY_ERROR: "sign_retry_error",
      CONFLICT_ERROR: "conflict_error",
      CORRUPTION_ERROR: "corruption_error",
      SYNC_ERROR: "sync_error",
      APPLY_ERROR: "apply_error",
      SERVER_ERROR: "server_error",
      CERTIFICATE_ERROR: "certificate_error",
      DOWNLOAD_ERROR: "download_error",
      TIMEOUT_ERROR: "timeout_error",
      NETWORK_ERROR: "network_error",
      NETWORK_OFFLINE_ERROR: "offline_error",
      SHUTDOWN_ERROR: "shutdown_error",
      UNKNOWN_ERROR: "unknown_error",
      CLEANUP_ERROR: "cleanup_error",
      SYNC_BROKEN_ERROR: "sync_broken_error",
      CUSTOM_1_ERROR: "custom_1_error",
      CUSTOM_2_ERROR: "custom_2_error",
      CUSTOM_3_ERROR: "custom_3_error",
      CUSTOM_4_ERROR: "custom_4_error",
      CUSTOM_5_ERROR: "custom_5_error",
    };
  }

  static get Policy() {
    return Policy;
  }

  /**
   * Reports the uptake status for the specified source.
   *
   * @param {string} component     the component reporting the uptake (eg. "normandy").
   * @param {string} status        the uptake status (eg. "network_error")
   * @param {Object} extra         extra values to report
   * @param {string} extra.source  the update source (eg. "recipe-42").
   * @param {string} extra.trigger what triggered the polling/fetching (eg. "broadcast", "timer").
   * @param {int}    extra.age     age of pulled data in seconds
   */
  static async report(component, status, extra = {}) {
    const { source } = extra;

    if (!source) {
      throw new Error("`source` value is mandatory.");
    }

    if (!Object.values(UptakeTelemetry.STATUS).includes(status)) {
      throw new Error(`Unknown status '${status}'`);
    }

    // Report event for real-time monitoring. See Events.yaml for registration.
    // Contrary to histograms, Telemetry Events are not enabled by default.
    // Enable them on first call to `report()`.
    if (!this._eventsEnabled) {
      Services.telemetry.setEventRecordingEnabled(TELEMETRY_EVENTS_ID, true);
      this._eventsEnabled = true;
    }

    const hash = await UptakeTelemetry.Policy.getClientIDHash();
    const channel = UptakeTelemetry.Policy.getChannel();
    const shouldSendEvent =
      !["release", "esr"].includes(channel) || hash < lazy.gSampleRate;
    if (shouldSendEvent) {
      // The Event API requires `extra` values to be of type string. Force it!
      const extraStr = Object.keys(extra).reduce((acc, k) => {
        acc[k] = extra[k].toString();
        return acc;
      }, {});
      Services.telemetry.recordEvent(
        TELEMETRY_EVENTS_ID,
        "uptake",
        component,
        status,
        extraStr
      );
    }
  }
}
PK
!<s��6�6!modules/AddonSearchEngine.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint no-shadow: error, mozilla/no-aArgs: error */

import { SearchEngine } from "resource://gre/modules/SearchEngine.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs",
  SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logConsole", () => {
  return console.createInstance({
    prefix: "AddonSearchEngine",
    maxLogLevel: lazy.SearchUtils.loggingEnabled ? "Debug" : "Warn",
  });
});

/**
 * AddonSearchEngine represents a search engine defined by an add-on.
 */
export class AddonSearchEngine extends SearchEngine {
  // Whether the engine is provided by the application.
  #isAppProvided = false;
  // The extension ID if added by an extension.
  _extensionID = null;
  // The locale, or "DEFAULT", if required.
  _locale = null;

  /**
   * Creates a AddonSearchEngine.
   *
   * @param {object} options
   *   The options object
   * @param {boolean} options.isAppProvided
   *   Indicates whether the engine is provided by Firefox, either
   *   shipped in omni.ja or via Normandy. If it is, it will
   *   be treated as read-only.
   * @param {object} [options.details]
   *   An object that simulates the manifest object from a WebExtension.
   * @param {object} [options.json]
   *   An object that represents the saved JSON settings for the engine.
   */
  constructor({ isAppProvided, details, json } = {}) {
    let extensionId =
      details?.extensionID ?? json.extensionID ?? json._extensionID;
    let id = extensionId + (details?.locale ?? json._locale);

    super({
      loadPath: "[addon]" + extensionId,
      id,
    });

    this._extensionID = extensionId;
    this.#isAppProvided = isAppProvided;

    if (json) {
      this._initWithJSON(json);
    }
  }

  _initWithJSON(json) {
    super._initWithJSON(json);
    this._extensionID = json.extensionID || json._extensionID || null;
    this._locale = json.extensionLocale || json._locale || null;
  }

  /**
   * Call to initalise an instance with extension details. Does not need to be
   * called if json has been passed to the constructor.
   *
   * @param {object} options
   *   The options object.
   * @param {Extension} options.extension
   *   The extension object representing the add-on.
   * @param {object} options.locale
   *   The locale to use from the extension for getting details of the search
   *   engine.
   * @param {object} [options.config]
   *   The search engine configuration for application provided engines, that
   *   may be overriding some of the WebExtension's settings.
   * @param {object} [options.settings]
   *   The saved settings for the user.
   */
  async init({ extension, locale, config, settings }) {
    let { baseURI, manifest } = await this.#getExtensionDetailsForLocale(
      extension,
      locale
    );

    this.#initFromManifest(baseURI, manifest, locale, config);
    this._loadSettings(settings);
  }

  /**
   * Manages updates to this engine.
   *
   * @param {object} options
   *   The options object.
   * @param {object} [options.configuration]
   *   The search engine configuration for application provided engines, that
   *   may be overriding some of the WebExtension's settings.
   * @param {object} [options.extension]
   *   The extension associated with this search engine, if known.
   * @param {string} [options.locale]
   *   The locale to use from the extension for getting details of the search
   *   engine.
   */
  async update({ configuration, extension, locale } = {}) {
    let { baseURI, manifest } = await this.#getExtensionDetailsForLocale(
      extension,
      locale
    );

    let originalName = this.name;
    let name = manifest.chrome_settings_overrides.search_provider.name.trim();
    if (originalName != name && Services.search.getEngineByName(name)) {
      throw new Error("Can't upgrade to the same name as an existing engine");
    }

    this.#updateFromManifest(baseURI, manifest, locale, configuration);
  }

  /**
   * Whether or not this engine is provided by the application, e.g. it is
   * in the list of configured search engines. Overrides the definition in
   * `SearchEngine`.
   *
   * @returns {boolean}
   */
  get isAppProvided() {
    return false;
  }

  /**
   * Whether or not this engine is an in-memory only search engine.
   * These engines are typically application provided or policy engines,
   * where they are loaded every time on SearchService initialization
   * using the policy JSON or the extension manifest. Minimal details of the
   * in-memory engines are saved to disk, but they are never loaded
   * from the user's saved settings file.
   *
   * @returns {boolean}
   *   Only returns true for application provided engines.
   */
  get inMemory() {
    return this.#isAppProvided;
  }

  /**
   * Creates a JavaScript object that represents this engine.
   *
   * @returns {object}
   *   An object suitable for serialization as JSON.
   */
  toJSON() {
    // For built-in engines we don't want to store all their data in the settings
    // file so just store the relevant metadata.
    if (this.#isAppProvided) {
      return {
        id: this.id,
        _name: this.name,
        _isAppProvided: true,
        _metaData: this._metaData,
      };
    }
    let json = super.toJSON();
    json._extensionID = this._extensionID;
    json._locale = this._locale;
    return json;
  }

  /**
   * Checks to see if this engine's settings are in sync with what the add-on
   * manager has, and reports the results to telemetry.
   */
  async checkAndReportIfSettingsValid() {
    let addon = await lazy.AddonManager.getAddonByID(this._extensionID);

    if (!addon) {
      lazy.logConsole.debug(
        `Add-on ${this._extensionID} for search engine ${this.name} is not installed!`
      );
      Services.telemetry.keyedScalarSet(
        "browser.searchinit.engine_invalid_webextension",
        this._extensionID,
        1
      );
    } else if (!addon.isActive) {
      lazy.logConsole.debug(
        `Add-on ${this._extensionID} for search engine ${this.name} is not active!`
      );
      Services.telemetry.keyedScalarSet(
        "browser.searchinit.engine_invalid_webextension",
        this._extensionID,
        2
      );
    } else {
      let policy = await AddonSearchEngine.getWebExtensionPolicy(
        this._extensionID
      );
      let providerSettings =
        policy.extension.manifest?.chrome_settings_overrides?.search_provider;

      if (!providerSettings) {
        lazy.logConsole.debug(
          `Add-on ${this._extensionID} for search engine ${this.name} no longer has an engine defined`
        );
        Services.telemetry.keyedScalarSet(
          "browser.searchinit.engine_invalid_webextension",
          this._extensionID,
          4
        );
      } else if (this.name != providerSettings.name) {
        lazy.logConsole.debug(
          `Add-on ${this._extensionID} for search engine ${this.name} has a different name!`
        );
        Services.telemetry.keyedScalarSet(
          "browser.searchinit.engine_invalid_webextension",
          this._extensionID,
          5
        );
      } else if (!this.checkSearchUrlMatchesManifest(providerSettings)) {
        lazy.logConsole.debug(
          `Add-on ${this._extensionID} for search engine ${this.name} has out-of-date manifest!`
        );
        Services.telemetry.keyedScalarSet(
          "browser.searchinit.engine_invalid_webextension",
          this._extensionID,
          6
        );
      }
    }
  }

  /**
   * Initializes the engine based on the manifest and other values.
   *
   * @param {string} extensionBaseURI
   *   The Base URI of the WebExtension.
   * @param {object} manifest
   *   An object representing the WebExtensions' manifest.
   * @param {string} locale
   *   The locale that is being used for the WebExtension.
   * @param {object} [configuration]
   *   The search engine configuration for application provided engines, that
   *   may be overriding some of the WebExtension's settings.
   */
  #initFromManifest(extensionBaseURI, manifest, locale, configuration = {}) {
    let searchProvider = manifest.chrome_settings_overrides.search_provider;

    this._locale = locale;

    // We only set _telemetryId for app-provided engines. See also telemetryId
    // getter.
    if (this.#isAppProvided) {
      if (configuration.telemetryId) {
        this._telemetryId = configuration.telemetryId;
      } else {
        let telemetryId = this._extensionID.split("@")[0];
        if (locale != lazy.SearchUtils.DEFAULT_TAG) {
          telemetryId += "-" + locale;
        }
        this._telemetryId = telemetryId;
      }
    }

    // Set the main icon URL for the engine.
    let iconURL = searchProvider.favicon_url;

    if (!iconURL) {
      iconURL =
        manifest.icons &&
        extensionBaseURI.resolve(
          lazy.ExtensionParent.IconDetails.getPreferredIcon(manifest.icons).icon
        );
    }

    // Record other icons that the WebExtension has.
    if (manifest.icons) {
      let iconList = Object.entries(manifest.icons).map(icon => {
        return {
          width: icon[0],
          height: icon[0],
          url: extensionBaseURI.resolve(icon[1]),
        };
      });
      for (let icon of iconList) {
        this._addIconToMap(icon.size, icon.size, icon.url);
      }
    }

    // Filter out any untranslated parameters, the extension has to list all
    // possible mozParams for each engine where a 'locale' may only provide
    // actual values for some (or none).
    if (searchProvider.params) {
      searchProvider.params = searchProvider.params.filter(param => {
        return !(param.value && param.value.startsWith("__MSG_"));
      });
    }

    this._initWithDetails(
      { ...searchProvider, iconURL, description: manifest.description },
      configuration
    );
  }

  /**
   * Update this engine based on new manifest, used during
   * webextension upgrades.
   *
   * @param {string} extensionBaseURI
   *   The Base URI of the WebExtension.
   * @param {object} manifest
   *   An object representing the WebExtensions' manifest.
   * @param {string} locale
   *   The locale that is being used for the WebExtension.
   * @param {object} [configuration]
   *   The search engine configuration for application provided engines, that
   *   may be overriding some of the WebExtension's settings.
   */
  #updateFromManifest(extensionBaseURI, manifest, locale, configuration = {}) {
    this._urls = [];
    this._iconMapObj = null;
    this.#initFromManifest(extensionBaseURI, manifest, locale, configuration);
    lazy.SearchUtils.notifyAction(this, lazy.SearchUtils.MODIFIED_TYPE.CHANGED);
  }

  /**
   * Get the localized manifest from the WebExtension for the given locale or
   * manifest default locale.
   *
   * The search service configuration overloads the add-on manager concepts of
   * locales, and forces particular locales within the WebExtension to be used,
   * ignoring the user's current locale. The user's current locale is taken into
   * account within the configuration, just not in the WebExtension.
   *
   * @param {object} [extension]
   *   The extension to get the manifest from.
   * @param {string} locale
   *   The locale to load from the WebExtension. If this is `DEFAULT_TAG`, then
   *   the default locale is loaded.
   * @returns {object}
   *   The loaded manifest.
   */
  async #getExtensionDetailsForLocale(extension, locale) {
    // If we haven't been passed an extension object, then go and find it.
    if (!extension) {
      extension = (
        await AddonSearchEngine.getWebExtensionPolicy(this._extensionID)
      ).extension;
    }

    let manifest = extension.manifest;

    let localeToLoad;
    if (this.#isAppProvided) {
      // If the locale we want from the WebExtension is the extension's default
      // then we get that from the manifest here. We do this because if we
      // are reloading due to the locale change, the add-on manager might not
      // have updated the WebExtension's manifest to the new version by the
      // time we hit this code.
      localeToLoad =
        locale == lazy.SearchUtils.DEFAULT_TAG
          ? manifest.default_locale
          : locale;
    } else {
      // For user installed add-ons, we have to simulate the add-on manager
      // code for loading the correct locale.
      // We do this, as in the case of a live language switch, the add-on manager
      // may not have yet reloaded the extension, and there's no way for us to
      // listen for that reload to complete.
      // See also https://bugzilla.mozilla.org/show_bug.cgi?id=1781768#c3 for
      // more background.
      localeToLoad = Services.locale.negotiateLanguages(
        Services.locale.appLocalesAsBCP47,
        [...extension.localeData.locales.keys()],
        manifest.default_locale
      )[0];
    }

    if (localeToLoad) {
      manifest = await extension.getLocalizedManifest(localeToLoad);
    }
    return { baseURI: extension.baseURI, manifest };
  }

  /**
   * Gets the WebExtensionPolicy for an add-on.
   *
   * @param {string} id
   *   The WebExtension id.
   * @returns {WebExtensionPolicy}
   */
  static async getWebExtensionPolicy(id) {
    let policy = WebExtensionPolicy.getByID(id);
    if (!policy) {
      let idPrefix = id.split("@")[0];
      let path = `resource://search-extensions/${idPrefix}/`;
      await lazy.AddonManager.installBuiltinAddon(path);
      policy = WebExtensionPolicy.getByID(id);
    }
    // On startup the extension may have not finished parsing the
    // manifest, wait for that here.
    await policy.readyPromise;
    return policy;
  }
}
PK
!<�q�8I8Imodules/RFPHelper.sys.mjs// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at https://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import * as constants from "resource://gre/modules/RFPTargetConstants.sys.mjs";

const kPrefResistFingerprinting = "privacy.resistFingerprinting";
const kPrefSpoofEnglish = "privacy.spoof_english";
const kTopicHttpOnModifyRequest = "http-on-modify-request";

const kPrefLetterboxing = "privacy.resistFingerprinting.letterboxing";
const kPrefLetterboxingDimensions =
  "privacy.resistFingerprinting.letterboxing.dimensions";
const kPrefLetterboxingTesting =
  "privacy.resistFingerprinting.letterboxing.testing";
const kTopicDOMWindowOpened = "domwindowopened";

var logConsole;
function log(msg) {
  if (!logConsole) {
    logConsole = console.createInstance({
      prefix: "RFPHelper",
      maxLogLevelPref: "privacy.resistFingerprinting.jsmloglevel",
    });
  }

  logConsole.log(msg);
}

class _RFPHelper {
  // ============================================================================
  // Shared Setup
  // ============================================================================
  constructor() {
    this._initialized = false;
  }

  init() {
    if (this._initialized) {
      return;
    }
    this._initialized = true;

    // Add unconditional observers
    Services.prefs.addObserver(kPrefResistFingerprinting, this);
    Services.prefs.addObserver(kPrefLetterboxing, this);
    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "_letterboxingDimensions",
      kPrefLetterboxingDimensions,
      "",
      null,
      this._parseLetterboxingDimensions
    );
    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "_isLetterboxingTesting",
      kPrefLetterboxingTesting,
      false
    );

    // Add RFP and Letterboxing observers if prefs are enabled
    this._handleResistFingerprintingChanged();
    this._handleLetterboxingPrefChanged();
  }

  uninit() {
    if (!this._initialized) {
      return;
    }
    this._initialized = false;

    // Remove unconditional observers
    Services.prefs.removeObserver(kPrefResistFingerprinting, this);
    Services.prefs.removeObserver(kPrefLetterboxing, this);
    // Remove the RFP observers, swallowing exceptions if they weren't present
    this._removeRFPObservers();
  }

  observe(subject, topic, data) {
    switch (topic) {
      case "nsPref:changed":
        this._handlePrefChanged(data);
        break;
      case kTopicHttpOnModifyRequest:
        this._handleHttpOnModifyRequest(subject, data);
        break;
      case kTopicDOMWindowOpened:
        // We attach to the newly created window by adding tabsProgressListener
        // and event listener on it. We listen for new tabs being added or
        // the change of the content principal and apply margins accordingly.
        this._handleDOMWindowOpened(subject);
        break;
      default:
        break;
    }
  }

  handleEvent(aMessage) {
    switch (aMessage.type) {
      case "TabOpen": {
        let tab = aMessage.target;
        this._addOrClearContentMargin(tab.linkedBrowser);
        break;
      }
      default:
        break;
    }
  }

  _handlePrefChanged(data) {
    switch (data) {
      case kPrefResistFingerprinting:
        this._handleResistFingerprintingChanged();
        break;
      case kPrefSpoofEnglish:
        this._handleSpoofEnglishChanged();
        break;
      case kPrefLetterboxing:
        this._handleLetterboxingPrefChanged();
        break;
      default:
        break;
    }
  }

  contentSizeUpdated(win) {
    this._updateMarginsForTabsInWindow(win);
  }

  // ============================================================================
  // Language Prompt
  // ============================================================================
  _addRFPObservers() {
    Services.prefs.addObserver(kPrefSpoofEnglish, this);
    if (this._shouldPromptForLanguagePref()) {
      Services.obs.addObserver(this, kTopicHttpOnModifyRequest);
    }
  }

  _removeRFPObservers() {
    try {
      Services.prefs.removeObserver(kPrefSpoofEnglish, this);
    } catch (e) {
      // do nothing
    }
    try {
      Services.obs.removeObserver(this, kTopicHttpOnModifyRequest);
    } catch (e) {
      // do nothing
    }
  }

  _handleResistFingerprintingChanged() {
    if (Services.prefs.getBoolPref(kPrefResistFingerprinting)) {
      this._addRFPObservers();
    } else {
      this._removeRFPObservers();
    }
  }

  _handleSpoofEnglishChanged() {
    switch (Services.prefs.getIntPref(kPrefSpoofEnglish)) {
      case 0: // will prompt
      // This should only happen when turning privacy.resistFingerprinting off.
      // Works like disabling accept-language spoofing.
      // fall through
      case 1: // don't spoof
        // We don't reset intl.accept_languages. Instead, setting
        // privacy.spoof_english to 1 allows user to change preferred language
        // settings through Preferences UI.
        break;
      case 2: // spoof
        Services.prefs.setCharPref("intl.accept_languages", "en-US, en");
        break;
      default:
        break;
    }
  }

  _shouldPromptForLanguagePref() {
    return (
      Services.locale.appLocaleAsBCP47.substr(0, 2) !== "en" &&
      Services.prefs.getIntPref(kPrefSpoofEnglish) === 0
    );
  }

  _handleHttpOnModifyRequest(subject) {
    // If we are loading an HTTP page from content, show the
    // "request English language web pages?" prompt.
    let httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);

    let notificationCallbacks = httpChannel.notificationCallbacks;
    if (!notificationCallbacks) {
      return;
    }

    let loadContext = notificationCallbacks.getInterface(Ci.nsILoadContext);
    if (!loadContext || !loadContext.isContent) {
      return;
    }

    if (!subject.URI.schemeIs("http") && !subject.URI.schemeIs("https")) {
      return;
    }
    // The above QI did not throw, the scheme is http[s], and we know the
    // load context is content, so we must have a true HTTP request from content.
    // Stop the observer and display the prompt if another window has
    // not already done so.
    Services.obs.removeObserver(this, kTopicHttpOnModifyRequest);

    if (!this._shouldPromptForLanguagePref()) {
      return;
    }

    this._promptForLanguagePreference();

    // The Accept-Language header for this request was set when the
    // channel was created. Reset it to match the value that will be
    // used for future requests.
    let val = this._getCurrentAcceptLanguageValue(subject.URI);
    if (val) {
      httpChannel.setRequestHeader("Accept-Language", val, false);
    }
  }

  _promptForLanguagePreference() {
    // Display two buttons, both with string titles.
    const l10n = new Localization(
      ["toolkit/global/resistFingerPrinting.ftl"],
      true
    );
    const message = l10n.formatValueSync("privacy-spoof-english");
    const flags = Services.prompt.STD_YES_NO_BUTTONS;
    const response = Services.prompt.confirmEx(
      null,
      "",
      message,
      flags,
      null,
      null,
      null,
      null,
      { value: false }
    );

    // Update preferences to reflect their response and to prevent the prompt
    // from being displayed again.
    Services.prefs.setIntPref(kPrefSpoofEnglish, response == 0 ? 2 : 1);
  }

  _getCurrentAcceptLanguageValue(uri) {
    let channel = Services.io.newChannelFromURI(
      uri,
      null, // aLoadingNode
      Services.scriptSecurityManager.getSystemPrincipal(),
      null, // aTriggeringPrincipal
      Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
      Ci.nsIContentPolicy.TYPE_OTHER
    );
    let httpChannel;
    try {
      httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
    } catch (e) {
      return null;
    }
    return httpChannel.getRequestHeader("Accept-Language");
  }

  // ==============================================================================
  // Letterboxing
  // ============================================================================
  /**
   * We use the TabsProgressListener to catch the change of the content
   * principal. We would clear the margins around the content viewport if
   * it is the system principal.
   */
  onLocationChange(aBrowser) {
    this._addOrClearContentMargin(aBrowser);
  }

  _handleLetterboxingPrefChanged() {
    if (Services.prefs.getBoolPref(kPrefLetterboxing, false)) {
      Services.ww.registerNotification(this);
      this._registerLetterboxingActor();
      this._attachAllWindows();
    } else {
      this._unregisterLetterboxingActor();
      this._detachAllWindows();
      Services.ww.unregisterNotification(this);
    }
  }

  _registerLetterboxingActor() {
    /*
     * It turns out that this triggers a warning that we're registering a Desktop-only actor
     * in toolkit (which will also run on mobile.)  It just happens this actor only handles
     * letterboxing, which isn't used on mobile, but we should resolve this.
     */
    ChromeUtils.registerWindowActor("RFPHelper", {
      parent: {
        esModuleURI: "resource:///actors/RFPHelperParent.sys.mjs",
      },
      child: {
        esModuleURI: "resource:///actors/RFPHelperChild.sys.mjs",
        events: {
          resize: {},
        },
      },
      allFrames: true,
    });
  }

  _unregisterLetterboxingActor() {
    ChromeUtils.unregisterWindowActor("RFPHelper");
  }

  // The function to parse the dimension set from the pref value. The pref value
  // should be formated as 'width1xheight1, width2xheight2, ...'. For
  // example, '100x100, 200x200, 400x200 ...'.
  _parseLetterboxingDimensions(aPrefValue) {
    if (!aPrefValue || !aPrefValue.match(/^(?:\d+x\d+,\s*)*(?:\d+x\d+)$/)) {
      if (aPrefValue) {
        console.error(
          `Invalid pref value for ${kPrefLetterboxingDimensions}: ${aPrefValue}`
        );
      }
      return [];
    }

    return aPrefValue.split(",").map(item => {
      let sizes = item.split("x").map(size => parseInt(size, 10));

      return {
        width: sizes[0],
        height: sizes[1],
      };
    });
  }

  _addOrClearContentMargin(aBrowser) {
    let tab = aBrowser.getTabBrowser().getTabForBrowser(aBrowser);

    // We won't do anything for lazy browsers.
    if (!aBrowser.isConnected) {
      return;
    }

    // We should apply no margin around an empty tab or a tab with system
    // principal.
    if (tab.isEmpty || aBrowser.contentPrincipal.isSystemPrincipal) {
      this._clearContentViewMargin(aBrowser);
    } else {
      this._roundContentView(aBrowser);
    }
  }

  /**
   * Given a width or height, returns the appropriate margin to apply.
   */
  steppedRange(aDimension) {
    let stepping;
    if (aDimension <= 50) {
      return 0;
    } else if (aDimension <= 500) {
      stepping = 50;
    } else if (aDimension <= 1600) {
      stepping = 100;
    } else {
      stepping = 200;
    }

    return (aDimension % stepping) / 2;
  }

  /**
   * The function will round the given browser by adding margins around the
   * content viewport.
   */
  async _roundContentView(aBrowser) {
    let logId = Math.random();
    log("_roundContentView[" + logId + "]");
    let win = aBrowser.ownerGlobal;
    let browserContainer = aBrowser
      .getTabBrowser()
      .getBrowserContainer(aBrowser);

    let { contentWidth, contentHeight, containerWidth, containerHeight } =
      await win.promiseDocumentFlushed(() => {
        let contentWidth = aBrowser.clientWidth;
        let contentHeight = aBrowser.clientHeight;
        let containerWidth = browserContainer.clientWidth;
        let containerHeight = browserContainer.clientHeight;

        // If the findbar or devtools are out, we need to subtract their height (plus 1
        // for the separator) from the container height, because we need to adjust our
        // letterboxing to account for it; however it is not included in that dimension
        // (but rather is subtracted from the content height.)
        let findBar = win.gFindBarInitialized ? win.gFindBar : undefined;
        let findBarOffset =
          findBar && !findBar.hidden ? findBar.clientHeight + 1 : 0;
        let devtools = browserContainer.getElementsByClassName(
          "devtools-toolbox-bottom-iframe"
        );
        let devtoolsOffset = devtools.length ? devtools[0].clientHeight : 0;

        return {
          contentWidth,
          contentHeight,
          containerWidth,
          containerHeight: containerHeight - findBarOffset - devtoolsOffset,
        };
      });

    log(
      "_roundContentView[" +
        logId +
        "] contentWidth=" +
        contentWidth +
        " contentHeight=" +
        contentHeight +
        " containerWidth=" +
        containerWidth +
        " containerHeight=" +
        containerHeight +
        " "
    );

    let calcMargins = (aWidth, aHeight) => {
      let result;
      log(
        "_roundContentView[" +
          logId +
          "] calcMargins(" +
          aWidth +
          ", " +
          aHeight +
          ")"
      );
      // If the set is empty, we will round the content with the default
      // stepping size.
      if (!this._letterboxingDimensions.length) {
        result = {
          width: this.steppedRange(aWidth),
          height: this.steppedRange(aHeight),
        };
        log(
          "_roundContentView[" +
            logId +
            "] calcMargins(" +
            aWidth +
            ", " +
            aHeight +
            ") = " +
            result.width +
            " x " +
            result.height
        );
        return result;
      }

      let matchingArea = aWidth * aHeight;
      let minWaste = Number.MAX_SAFE_INTEGER;
      let targetDimensions = undefined;

      // Find the desired dimensions which waste the least content area.
      for (let dim of this._letterboxingDimensions) {
        // We don't need to consider the dimensions which cannot fit into the
        // real content size.
        if (dim.width > aWidth || dim.height > aHeight) {
          continue;
        }

        let waste = matchingArea - dim.width * dim.height;

        if (waste >= 0 && waste < minWaste) {
          targetDimensions = dim;
          minWaste = waste;
        }
      }

      // If we cannot find any dimensions match to the real content window, this
      // means the content area is smaller the smallest size in the set. In this
      // case, we won't apply any margins.
      if (!targetDimensions) {
        result = {
          width: 0,
          height: 0,
        };
      } else {
        result = {
          width: (aWidth - targetDimensions.width) / 2,
          height: (aHeight - targetDimensions.height) / 2,
        };
      }

      log(
        "_roundContentView[" +
          logId +
          "] calcMargins(" +
          aWidth +
          ", " +
          aHeight +
          ") = " +
          result.width +
          " x " +
          result.height
      );
      return result;
    };

    // Calculating the margins around the browser element in order to round the
    // content viewport. We will use a 200x100 stepping if the dimension set
    // is not given.
    let margins = calcMargins(containerWidth, containerHeight);

    // If the size of the content is already quantized, we do nothing.
    if (aBrowser.style.margin == `${margins.height}px ${margins.width}px`) {
      log("_roundContentView[" + logId + "] is_rounded == true");
      if (this._isLetterboxingTesting) {
        log(
          "_roundContentView[" +
            logId +
            "] is_rounded == true test:letterboxing:update-margin-finish"
        );
        Services.obs.notifyObservers(
          null,
          "test:letterboxing:update-margin-finish"
        );
      }
      return;
    }

    win.requestAnimationFrame(() => {
      log(
        "_roundContentView[" +
          logId +
          "] setting margins to " +
          margins.width +
          " x " +
          margins.height
      );
      // One cannot (easily) control the color of a margin unfortunately.
      // An initial attempt to use a border instead of a margin resulted
      // in offset event dispatching; so for now we use a colorless margin.
      aBrowser.style.margin = `${margins.height}px ${margins.width}px`;
    });
  }

  _clearContentViewMargin(aBrowser) {
    aBrowser.ownerGlobal.requestAnimationFrame(() => {
      aBrowser.style.margin = "";
    });
  }

  _updateMarginsForTabsInWindow(aWindow) {
    let tabBrowser = aWindow.gBrowser;

    for (let tab of tabBrowser.tabs) {
      let browser = tab.linkedBrowser;
      this._addOrClearContentMargin(browser);
    }
  }

  _attachWindow(aWindow) {
    aWindow.gBrowser.addTabsProgressListener(this);
    aWindow.addEventListener("TabOpen", this);

    // Rounding the content viewport.
    this._updateMarginsForTabsInWindow(aWindow);
  }

  _attachAllWindows() {
    let windowList = Services.wm.getEnumerator("navigator:browser");

    while (windowList.hasMoreElements()) {
      let win = windowList.getNext();

      if (win.closed || !win.gBrowser) {
        continue;
      }

      this._attachWindow(win);
    }
  }

  _detachWindow(aWindow) {
    let tabBrowser = aWindow.gBrowser;
    tabBrowser.removeTabsProgressListener(this);
    aWindow.removeEventListener("TabOpen", this);

    // Clear all margins and tooltip for all browsers.
    for (let tab of tabBrowser.tabs) {
      let browser = tab.linkedBrowser;
      this._clearContentViewMargin(browser);
    }
  }

  _detachAllWindows() {
    let windowList = Services.wm.getEnumerator("navigator:browser");

    while (windowList.hasMoreElements()) {
      let win = windowList.getNext();

      if (win.closed || !win.gBrowser) {
        continue;
      }

      this._detachWindow(win);
    }
  }

  _handleDOMWindowOpened(win) {
    let self = this;

    win.addEventListener(
      "load",
      () => {
        // We attach to the new window when it has been loaded if the new loaded
        // window is a browsing window.
        if (
          win.document.documentElement.getAttribute("windowtype") !==
          "navigator:browser"
        ) {
          return;
        }
        self._attachWindow(win);
      },
      { once: true }
    );
  }

  getTargets() {
    return constants.Targets;
  }

  getTargetDefaults() {
    const key =
      Services.appinfo.OS === "Android" ? "ANDROID_DEFAULT" : "DESKTOP_DEFAULT";
    return constants.DefaultTargets[key];
  }
}

export let RFPHelper = new _RFPHelper();
PK
!<�%����"modules/RFPTargetConstants.sys.mjs// This is a generated file. Please do not edit.
// See extract_rfp_targets.py, RFPTargets.inc, and RFPTargetsDefault.inc files instead.
export const Targets = {
	"TouchEvents":                               BigInt(1) << BigInt(0),
	"PointerEvents":                             BigInt(1) << BigInt(1),
	"KeyboardEvents":                            BigInt(1) << BigInt(2),
	"ScreenOrientation":                         BigInt(1) << BigInt(3),
	"SpeechSynthesis":                           BigInt(1) << BigInt(4),
	"CSSPrefersColorScheme":                     BigInt(1) << BigInt(5),
	"CSSPrefersReducedMotion":                   BigInt(1) << BigInt(6),
	"CSSPrefersContrast":                        BigInt(1) << BigInt(7),
	"CanvasRandomization":                       BigInt(1) << BigInt(8),
	"CanvasImageExtractionPrompt":               BigInt(1) << BigInt(9),
	"CanvasExtractionFromThirdPartiesIsBlocked": BigInt(1) << BigInt(10),
	"CanvasExtractionBeforeUserInputIsBlocked":  BigInt(1) << BigInt(11),
	"JSLocale":                                  BigInt(1) << BigInt(12),
	"NavigatorAppVersion":                       BigInt(1) << BigInt(13),
	"NavigatorBuildID":                          BigInt(1) << BigInt(14),
	"NavigatorHWConcurrency":                    BigInt(1) << BigInt(15),
	"NavigatorOscpu":                            BigInt(1) << BigInt(16),
	"NavigatorPlatform":                         BigInt(1) << BigInt(17),
	"NavigatorUserAgent":                        BigInt(1) << BigInt(18),
	"StreamTrackLabel":                          BigInt(1) << BigInt(19),
	"StreamVideoFacingMode":                     BigInt(1) << BigInt(20),
	"JSDateTimeUTC":                             BigInt(1) << BigInt(21),
	"JSMathFdlibm":                              BigInt(1) << BigInt(22),
	"Gamepad":                                   BigInt(1) << BigInt(23),
	"HttpUserAgent":                             BigInt(1) << BigInt(24),
	"WindowOuterSize":                           BigInt(1) << BigInt(25),
	"WindowScreenXY":                            BigInt(1) << BigInt(26),
	"WindowInnerScreenXY":                       BigInt(1) << BigInt(27),
	"ScreenPixelDepth":                          BigInt(1) << BigInt(28),
	"ScreenRect":                                BigInt(1) << BigInt(29),
	"ScreenAvailRect":                           BigInt(1) << BigInt(30),
	"VideoElementMozFrames":                     BigInt(1) << BigInt(31),
	"VideoElementMozFrameDelay":                 BigInt(1) << BigInt(32),
	"VideoElementPlaybackQuality":               BigInt(1) << BigInt(33),
	"ReduceTimerPrecision":                      BigInt(1) << BigInt(34),
	"WidgetEvents":                              BigInt(1) << BigInt(35),
	"MediaDevices":                              BigInt(1) << BigInt(36),
	"MediaCapabilities":                         BigInt(1) << BigInt(37),
	"AudioSampleRate":                           BigInt(1) << BigInt(38),
	"NavigatorConnection":                       BigInt(1) << BigInt(39),
	"WindowDevicePixelRatio":                    BigInt(1) << BigInt(40),
	"MouseEventScreenPoint":                     BigInt(1) << BigInt(41),
	"FontVisibilityBaseSystem":                  BigInt(1) << BigInt(42),
	"FontVisibilityLangPack":                    BigInt(1) << BigInt(43),
	"DeviceSensors":                             BigInt(1) << BigInt(44),
	"FrameRate":                                 BigInt(1) << BigInt(45),
	"RoundWindowSize":                           BigInt(1) << BigInt(46),
	"UseStandinsForNativeColors":                BigInt(1) << BigInt(47),
	"AudioContext":                              BigInt(1) << BigInt(48),
	"MediaError":                                BigInt(1) << BigInt(49),
	"DOMStyleOsxFontSmoothing":                  BigInt(1) << BigInt(50),
	"CSSDeviceSize":                             BigInt(1) << BigInt(51),
	"CSSColorInfo":                              BigInt(1) << BigInt(52),
	"CSSResolution":                             BigInt(1) << BigInt(53),
	"CSSPrefersReducedTransparency":             BigInt(1) << BigInt(54),
	"CSSInvertedColors":                         BigInt(1) << BigInt(55),
	"CSSVideoDynamicRange":                      BigInt(1) << BigInt(56),
	"CSSPointerCapabilities":                    BigInt(1) << BigInt(57),
	"WebGLRenderCapability":                     BigInt(1) << BigInt(58),
	"WebGLRenderInfo":                           BigInt(1) << BigInt(59),
	"SiteSpecificZoom":                          BigInt(1) << BigInt(60),
	"FontVisibilityRestrictGenerics":            BigInt(1) << BigInt(61),
	"IsAlwaysEnabledForPrecompute":              BigInt("0"),
	"AllTargets":                                BigInt("0xFFFFFFFFFFFFFFFF"),
}

export const DefaultTargets = {
	"DESKTOP_DEFAULT": ["CanvasRandomization","FontVisibilityLangPack",],
	"ANDROID_DEFAULT": ["CanvasRandomization",],
}
PK
!<J{l��0�07chrome/toolkit/content/extensions/parent/ext-runtime.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This file expects tabTracker to be defined in the global scope (e.g.
// by ext-browser.js or ext-android.js).
/* global tabTracker */

var { ExtensionParent } = ChromeUtils.importESModule(
  "resource://gre/modules/ExtensionParent.sys.mjs"
);

var { DefaultWeakMap } = ExtensionUtils;

ChromeUtils.defineESModuleGetters(this, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs",
  DevToolsShim: "chrome://devtools-startup/content/DevToolsShim.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "gRuntimeTimeout",
  "extensions.webextensions.runtime.timeout",
  5000
);

this.runtime = class extends ExtensionAPIPersistent {
  PERSISTENT_EVENTS = {
    // Despite not being part of PERSISTENT_EVENTS, the following events are
    // still triggered (after waking up the background context if needed):
    // - runtime.onConnect
    // - runtime.onConnectExternal
    // - runtime.onMessage
    // - runtime.onMessageExternal
    // For details, see bug 1852317 and test_ext_eventpage_messaging_wakeup.js.

    onInstalled({ fire }) {
      let { extension } = this;
      let temporary = !!extension.addonData.temporarilyInstalled;

      let listener = () => {
        switch (extension.startupReason) {
          case "APP_STARTUP":
            if (AddonManagerPrivate.browserUpdated) {
              fire.sync({ reason: "browser_update", temporary });
            }
            break;
          case "ADDON_INSTALL":
            fire.sync({ reason: "install", temporary });
            break;
          case "ADDON_UPGRADE":
            fire.sync({
              reason: "update",
              previousVersion: extension.addonData.oldVersion,
              temporary,
            });
            break;
        }
      };
      extension.on("background-first-run", listener);
      return {
        unregister() {
          extension.off("background-first-run", listener);
        },
        convert(_fire) {
          fire = _fire;
        },
      };
    },
    onUpdateAvailable({ fire }) {
      let { extension } = this;
      let instanceID = extension.addonData.instanceID;
      AddonManager.addUpgradeListener(instanceID, upgrade => {
        extension.upgrade = upgrade;
        let details = {
          version: upgrade.version,
        };
        fire.sync(details);
      });
      return {
        unregister() {
          AddonManager.removeUpgradeListener(instanceID);
        },
        convert(_fire) {
          fire = _fire;
        },
      };
    },
    onPerformanceWarning({ fire }) {
      let { extension } = this;

      let observer = subject => {
        let report = subject.QueryInterface(Ci.nsIHangReport);

        if (report?.addonId !== extension.id) {
          return;
        }

        const performanceWarningEventDetails = {
          category: "content_script",
          severity: "high",
          description:
            "Slow extension content script caused a page hang, user was warned.",
        };

        let scriptBrowser = report.scriptBrowser;
        let nativeTab =
          scriptBrowser?.ownerGlobal.gBrowser?.getTabForBrowser(scriptBrowser);
        if (nativeTab) {
          performanceWarningEventDetails.tabId = tabTracker.getId(nativeTab);
        }

        fire.async(performanceWarningEventDetails);
      };

      Services.obs.addObserver(observer, "process-hang-report");
      return {
        unregister: () => {
          Services.obs.removeObserver(observer, "process-hang-report");
        },
        convert(_fire) {
          fire = _fire;
        },
      };
    },
  };

  // Although we have an internal context.contextId field, we generate a new one here because
  // the internal type is an integer and the public field a (UUID) string.
  // TODO: Move the implementation elsewhere when contextId is used anywhere other than
  // runtime.getContexts. See https://bugzilla.mozilla.org/show_bug.cgi?id=1628178#c5
  //
  // Map<ProxyContextParent, string>
  #contextUUIDMap = new DefaultWeakMap(_context =>
    String(Services.uuid.generateUUID()).slice(1, -1)
  );

  getContexts(filter) {
    const { extension } = this;
    const { proxyContexts } = ExtensionParent.ParentAPIManager;
    const results = [];
    for (const proxyContext of proxyContexts.values()) {
      if (proxyContext.extension !== extension) {
        continue;
      }
      let ctx;
      try {
        ctx = proxyContext.toExtensionContext();
      } catch (err) {
        // toExtensionContext may throw if the contextType getter
        // raised an exception due to an internal viewType has
        // not be mapped with a contextType value.
        //
        // When running in DEBUG builds we reject the getContexts
        // call, while in non DEBUG build we just omit the result
        // and log a warning in the Browser Console.
        if (AppConstants.DEBUG) {
          throw err;
        } else {
          Cu.reportError(err);
        }
      }

      if (this.matchContextFilter(filter, ctx)) {
        results.push({
          ...ctx,
          contextId: this.#contextUUIDMap.get(proxyContext),
        });
      }
    }
    return results;
  }

  matchContextFilter(filter, ctx) {
    if (!ctx) {
      // Filter out subclasses that do not return any ExtensionContext details
      // from their toExtensionContext method.
      return false;
    }
    if (filter.contextIds && !filter.contextIds.includes(ctx.contextId)) {
      return false;
    }
    if (filter.contextTypes && !filter.contextTypes.includes(ctx.contextType)) {
      return false;
    }
    if (filter.documentIds && !filter.documentIds.includes(ctx.documentId)) {
      return false;
    }
    if (
      filter.documentOrigins &&
      !filter.documentOrigins.includes(ctx.documentOrigin)
    ) {
      return false;
    }
    if (filter.documentUrls && !filter.documentUrls.includes(ctx.documentUrl)) {
      return false;
    }
    if (filter.frameIds && !filter.frameIds.includes(ctx.frameId)) {
      return false;
    }
    if (filter.tabIds && !filter.tabIds.includes(ctx.tabId)) {
      return false;
    }
    if (filter.windowIds && !filter.windowIds.includes(ctx.windowId)) {
      return false;
    }
    if (
      typeof filter.incognito === "boolean" &&
      ctx.incognito !== filter.incognito
    ) {
      return false;
    }
    return true;
  }

  getAPI(context) {
    let { extension } = context;
    return {
      runtime: {
        getContexts: filter => this.getContexts(filter),

        // onStartup is special-cased in ext-backgroundPages to cause
        // an immediate startup.  We do not prime onStartup.
        onStartup: new EventManager({
          context,
          module: "runtime",
          event: "onStartup",
          register: fire => {
            if (context.incognito || extension.startupReason != "APP_STARTUP") {
              // This event should not fire if we are operating in a private profile.
              return () => {};
            }
            let listener = () => {
              return fire.sync();
            };

            extension.on("background-first-run", listener);

            return () => {
              extension.off("background-first-run", listener);
            };
          },
        }).api(),

        onInstalled: new EventManager({
          context,
          module: "runtime",
          event: "onInstalled",
          extensionApi: this,
        }).api(),

        onUpdateAvailable: new EventManager({
          context,
          module: "runtime",
          event: "onUpdateAvailable",
          extensionApi: this,
        }).api(),

        onSuspend: new EventManager({
          context,
          name: "runtime.onSuspend",
          resetIdleOnEvent: false,
          register: fire => {
            let listener = async () => {
              let timedOut = false;
              async function promiseFire() {
                try {
                  await fire.async();
                } catch (e) {}
              }
              await Promise.race([
                promiseFire(),
                ExtensionUtils.promiseTimeout(gRuntimeTimeout).then(() => {
                  timedOut = true;
                }),
              ]);
              if (timedOut) {
                Cu.reportError(
                  `runtime.onSuspend in ${extension.id} took too long`
                );
              }
            };
            extension.on("background-script-suspend", listener);
            return () => {
              extension.off("background-script-suspend", listener);
            };
          },
        }).api(),

        onSuspendCanceled: new EventManager({
          context,
          name: "runtime.onSuspendCanceled",
          register: fire => {
            let listener = () => {
              fire.async();
            };
            extension.on("background-script-suspend-canceled", listener);
            return () => {
              extension.off("background-script-suspend-canceled", listener);
            };
          },
        }).api(),

        onPerformanceWarning: new EventManager({
          context,
          module: "runtime",
          event: "onPerformanceWarning",
          extensionApi: this,
        }).api(),

        reload: async () => {
          if (extension.upgrade) {
            // If there is a pending update, install it now.
            extension.upgrade.install();
          } else {
            // Otherwise, reload the current extension.
            let addon = await AddonManager.getAddonByID(extension.id);
            addon.reload();
          }
        },

        get lastError() {
          // TODO(robwu): Figure out how to make sure that errors in the parent
          // process are propagated to the child process.
          // lastError should not be accessed from the parent.
          return context.lastError;
        },

        getBrowserInfo: function () {
          const { name, vendor, version, appBuildID } = Services.appinfo;
          const info = { name, vendor, version, buildID: appBuildID };
          return Promise.resolve(info);
        },

        getPlatformInfo: function () {
          return Promise.resolve(ExtensionParent.PlatformInfo);
        },

        openOptionsPage: function () {
          if (!extension.optionsPageProperties) {
            return Promise.reject({ message: "No `options_ui` declared" });
          }

          // This expects openOptionsPage to be defined in the file using this,
          // e.g. the browser/ version of ext-runtime.js
          /* global openOptionsPage:false */
          return openOptionsPage(extension).then(() => {});
        },

        setUninstallURL: function (url) {
          if (url === null || url.length === 0) {
            extension.uninstallURL = null;
            return Promise.resolve();
          }

          let uri;
          try {
            uri = new URL(url);
          } catch (e) {
            return Promise.reject({
              message: `Invalid URL: ${JSON.stringify(url)}`,
            });
          }

          if (uri.protocol != "http:" && uri.protocol != "https:") {
            return Promise.reject({
              message: "url must have the scheme http or https",
            });
          }

          extension.uninstallURL = url;
          return Promise.resolve();
        },

        // This function is not exposed to the extension js code and it is only
        // used by the alert function redefined into the background pages to be
        // able to open the BrowserConsole from the main process.
        openBrowserConsole() {
          if (AppConstants.platform !== "android") {
            DevToolsShim.openBrowserConsole();
          }
        },

        async internalWakeupBackground() {
          const { background } = extension.manifest;
          if (
            background &&
            (background.page || background.scripts) &&
            // Note: if background.service_worker is specified, it takes
            // precedence over page/scripts, and persistentBackground is false.
            !extension.persistentBackground
          ) {
            await extension.wakeupBackground();
          }
        },
      },
    };
  }
};
PK
!<ʱ3.�\�\Echrome/toolkit/res/messaging-system/lib/SpecialMessageActions.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const DOH_DOORHANGER_DECISION_PREF = "doh-rollout.doorhanger-decision";
const NETWORK_TRR_MODE_PREF = "network.trr.mode";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  // eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
  CustomizableUI: "resource:///modules/CustomizableUI.sys.mjs",
  FxAccounts: "resource://gre/modules/FxAccounts.sys.mjs",
  MigrationUtils: "resource:///modules/MigrationUtils.sys.mjs",
  PlacesTransactions: "resource://gre/modules/PlacesTransactions.sys.mjs",
  // eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
  PlacesUIUtils: "resource:///modules/PlacesUIUtils.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
  // eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
  Spotlight: "resource:///modules/asrouter/Spotlight.sys.mjs",
  UIState: "resource://services-sync/UIState.sys.mjs",
  UITour: "resource:///modules/UITour.sys.mjs",
});

export const SpecialMessageActions = {
  // This is overridden by ASRouter.init
  blockMessageById() {
    throw new Error("ASRouter not intialized yet");
  },

  /**
   * loadAddonIconInURLBar - load addons-notification icon by displaying
   * box containing addons icon in urlbar. See Bug 1513882
   *
   * @param  {Browser} browser browser element for showing addons icon
   */
  loadAddonIconInURLBar(browser) {
    if (!browser) {
      return;
    }
    const chromeDoc = browser.ownerDocument;
    let notificationPopupBox = chromeDoc.getElementById(
      "notification-popup-box"
    );
    if (!notificationPopupBox) {
      return;
    }
    if (
      notificationPopupBox.style.display === "none" ||
      notificationPopupBox.style.display === ""
    ) {
      notificationPopupBox.style.display = "block";
    }
  },

  /**
   *
   * @param {Browser} browser The revelant Browser
   * @param {string} url URL to look up install location
   * @param {string} telemetrySource Telemetry information to pass to getInstallForURL
   */
  async installAddonFromURL(browser, url, telemetrySource = "amo") {
    try {
      this.loadAddonIconInURLBar(browser);
      const aUri = Services.io.newURI(url);
      const systemPrincipal =
        Services.scriptSecurityManager.getSystemPrincipal();

      // AddonManager installation source associated to the addons installed from activitystream's CFR
      // and RTAMO (source is going to be "amo" if not configured explicitly in the message provider).
      const telemetryInfo = { source: telemetrySource };
      const install = await lazy.AddonManager.getInstallForURL(aUri.spec, {
        telemetryInfo,
      });
      await lazy.AddonManager.installAddonFromWebpage(
        "application/x-xpinstall",
        browser,
        systemPrincipal,
        install
      );
    } catch (e) {
      console.error(e);
    }
  },

  /**
   * Pin Firefox to taskbar.
   *
   * @param {Window} window Reference to a window object
   * @param {boolean} pin Private Browsing Mode if true
   */
  pinFirefoxToTaskbar(window, privateBrowsing = false) {
    return window.getShellService().pinToTaskbar(privateBrowsing);
  },

  /**
   * Pin current application to Windows start menu.
   */
  pinToStartMenu(window) {
    return window.getShellService().pinToStartMenu();
  },

  /**
   *  Set browser as the operating system default browser.
   *
   *  @param {Window} window Reference to a window object
   */
  async setDefaultBrowser(window) {
    await window.getShellService().setAsDefault();
  },

  /**
   * Set browser as the default PDF handler.
   *
   * @param {Window} window Reference to a window object
   */
  setDefaultPDFHandler(window, onlyIfKnownBrowser = false) {
    window.getShellService().setAsDefaultPDFHandler(onlyIfKnownBrowser);
  },

  /**
   * Reset browser homepage and newtab to default with a certain section configuration
   *
   * @param {"default"|null} home Value to set for browser homepage
   * @param {"default"|null} newtab Value to set for browser newtab
   * @param {obj} layout Configuration options for newtab sections
   * @returns {undefined}
   */
  configureHomepage({ homePage = null, newtab = null, layout = null }) {
    // Homepage can be default, blank or a custom url
    if (homePage === "default") {
      Services.prefs.clearUserPref("browser.startup.homepage");
    }
    // Newtab page can only be default or blank
    if (newtab === "default") {
      Services.prefs.clearUserPref("browser.newtabpage.enabled");
    }
    if (layout) {
      // Existing prefs that interact with the newtab page layout, we default to true
      // or payload configuration
      let newtabConfigurations = [
        [
          // controls the search bar
          "browser.newtabpage.activity-stream.showSearch",
          layout.search,
        ],
        [
          // controls the topsites
          "browser.newtabpage.activity-stream.feeds.topsites",
          layout.topsites,
          // User can control number of topsite rows
          ["browser.newtabpage.activity-stream.topSitesRows"],
        ],
        [
          // controls the highlights section
          "browser.newtabpage.activity-stream.feeds.section.highlights",
          layout.highlights,
          // User can control number of rows and highlight sources
          [
            "browser.newtabpage.activity-stream.section.highlights.rows",
            "browser.newtabpage.activity-stream.section.highlights.includeVisited",
            "browser.newtabpage.activity-stream.section.highlights.includePocket",
            "browser.newtabpage.activity-stream.section.highlights.includeDownloads",
            "browser.newtabpage.activity-stream.section.highlights.includeBookmarks",
          ],
        ],
        [
          // controls the topstories section
          "browser.newtabpage.activity-stream.feeds.system.topstories",
          layout.topstories,
        ],
      ].filter(
        // If a section has configs that the user changed we will skip that section
        ([, , sectionConfigs]) =>
          !sectionConfigs ||
          sectionConfigs.every(
            prefName => !Services.prefs.prefHasUserValue(prefName)
          )
      );

      for (let [prefName, prefValue] of newtabConfigurations) {
        Services.prefs.setBoolPref(prefName, prefValue);
      }
    }
  },

  /**
   * Set prefs with special message actions
   *
   * @param {Object} pref - A pref to be updated.
   * @param {string} pref.name - The name of the pref to be updated
   * @param {string} [pref.value] - The value of the pref to be updated. If not included, the pref will be reset.
   */
  setPref(pref) {
    // Array of prefs that are allowed to be edited by SET_PREF
    const allowedPrefs = [
      "browser.aboutwelcome.didSeeFinalScreen",
      "browser.dataFeatureRecommendations.enabled",
      "browser.migrate.content-modal.about-welcome-behavior",
      "browser.migrate.content-modal.import-all.enabled",
      "browser.migrate.preferences-entrypoint.enabled",
      "browser.shopping.experience2023.active",
      "browser.shopping.experience2023.optedIn",
      "browser.shopping.experience2023.survey.optedInTime",
      "browser.shopping.experience2023.survey.hasSeen",
      "browser.shopping.experience2023.survey.pdpVisits",
      "browser.startup.homepage",
      "browser.startup.windowsLaunchOnLogin.disableLaunchOnLoginPrompt",
      "browser.privateWindowSeparation.enabled",
      "browser.firefox-view.feature-tour",
      "browser.pdfjs.feature-tour",
      "browser.newtab.feature-tour",
      "browser.newtabpage.activity-stream.newtabWallpapers.wallpaper-light",
      "browser.newtabpage.activity-stream.newtabWallpapers.wallpaper-dark",
      "cookiebanners.service.mode",
      "cookiebanners.service.mode.privateBrowsing",
      "cookiebanners.service.detectOnly",
      "messaging-system.askForFeedback",
      "browser.toolbars.bookmarks.visibility",
    ];

    if (
      !allowedPrefs.includes(pref.name) &&
      !pref.name.startsWith("messaging-system-action.")
    ) {
      pref.name = `messaging-system-action.${pref.name}`;
    }
    // If pref has no value, reset it, otherwise set it to desired value
    switch (typeof pref.value) {
      case "object":
      case "undefined":
        Services.prefs.clearUserPref(pref.name);
        break;
      case "string":
        Services.prefs.setStringPref(pref.name, pref.value);
        break;
      case "number":
        Services.prefs.setIntPref(pref.name, pref.value);
        break;
      case "boolean":
        Services.prefs.setBoolPref(pref.name, pref.value);
        break;
      default:
        throw new Error(
          `Special message action with type SET_PREF, pref of "${pref.name}" is an unsupported type.`
        );
    }
  },

  /**
   * Open an FxA sign-in page and automatically close it once sign-in
   * completes.
   *
   * @param {any=} data
   * @param {Browser} browser the xul:browser rendering the page
   * @returns {Promise<boolean>} true if the user signed in, false otherwise
   */
  async fxaSignInFlow(data, browser) {
    if (!(await lazy.FxAccounts.canConnectAccount())) {
      return false;
    }
    const url = await lazy.FxAccounts.config.promiseConnectAccountURI(
      data?.entrypoint || "activity-stream-firstrun",
      data?.extraParams || {}
    );

    let window = browser.ownerGlobal;

    let fxaBrowser = await new Promise(resolve => {
      window.openLinkIn(url, data?.where || "tab", {
        private: false,
        triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal(
          {}
        ),
        csp: null,
        resolveOnContentBrowserCreated: resolve,
        forceForeground: true,
      });
    });

    let gBrowser = fxaBrowser.getTabBrowser();
    let fxaTab = gBrowser.getTabForBrowser(fxaBrowser);

    let didSignIn = await new Promise(resolve => {
      // We're going to be setting up a listener and an observer for this
      // mechanism.
      //
      // 1. An event listener for the TabClose event, to detect if the user
      //    closes the tab before completing sign-in
      // 2. An nsIObserver that listens for the UIState for FxA to reach
      //    STATUS_SIGNED_IN.
      //
      // We want to clean up both the listener and observer when all of this
      // is done.
      //
      // We use an AbortController to make it easier to manage the cleanup.
      let controller = new AbortController();
      let { signal } = controller;

      // This nsIObserver will listen for the UIState status to change to
      // STATUS_SIGNED_IN as our definitive signal that FxA sign-in has
      // completed. It will then resolve the outer Promise to `true`.
      let fxaObserver = {
        QueryInterface: ChromeUtils.generateQI([
          Ci.nsIObserver,
          Ci.nsISupportsWeakReference,
        ]),

        observe() {
          let state = lazy.UIState.get();
          if (state.status === lazy.UIState.STATUS_SIGNED_IN) {
            // We completed sign-in, so tear down our listener / observer and resolve
            // didSignIn to true.
            controller.abort();
            resolve(true);
          }
        },
      };

      // The TabClose event listener _can_ accept the AbortController signal,
      // which will then remove the event listener after controller.abort is
      // called.
      fxaTab.addEventListener(
        "TabClose",
        () => {
          // If the TabClose event was fired before the event handler was
          // removed, this means that the tab was closed and sign-in was
          // not completed, which means we should resolve didSignIn to false.
          controller.abort();
          resolve(false);
        },
        { once: true, signal }
      );

      let window = fxaTab.ownerGlobal;
      window.addEventListener("unload", () => {
        // If the hosting window unload event was fired before the event handler
        // was removed, this means that the window was closed and sign-in was
        // not completed, which means we should resolve didSignIn to false.
        controller.abort();
        resolve(false);
      });

      Services.obs.addObserver(fxaObserver, lazy.UIState.ON_UPDATE);

      // Unfortunately, nsIObserverService.addObserver does not accept an
      // AbortController signal as a parameter, so instead we listen for the
      // abort event on the signal to remove the observer.
      signal.addEventListener(
        "abort",
        () => {
          Services.obs.removeObserver(fxaObserver, lazy.UIState.ON_UPDATE);
        },
        { once: true }
      );
    });

    // If the user completed sign-in, we'll close the fxaBrowser tab for
    // them to bring them back to the about:welcome flow.
    //
    // If the sign-in page was loaded in a new window, this will close the
    // tab for that window. That will close the window as well if it's the
    // last tab in that window.
    if (didSignIn && data?.autoClose !== false) {
      gBrowser.removeTab(fxaTab);
    }

    return didSignIn;
  },

  /**
   * Sets the visibility of bookmarks toolbar.
   *
   * @param {Window} window Reference to a window object
   * @param {string} visibility Visibility options which can be "always", "newtab", or "never"
   */
  setBookmarksToolbarVisibility(window, visibility) {
    Services.prefs.setCharPref(
      "browser.toolbars.bookmarks.visibility",
      visibility
    );

    lazy.CustomizableUI.setToolbarVisibility(
      window.document.getElementById("PersonalToolbar").id,
      visibility,
      false
    );
  },

  /**
   * Bookmarks the current tab.
   *
   * @param {Window} window Reference to a window object
   * @param {boolean} shouldHideDialog True if bookmark dialog should be hidden
   * @param {boolean} shouldHideConfirmationHint True if bookmark confirmation hint should be hidden
   */
  async bookmarkCurrentTab(
    window,
    shouldHideDialog = false,
    shouldHideConfirmationHint = false
  ) {
    if (!window.top.gBrowser) {
      return;
    }

    // Bookmark current tab without showing the bookmark dialog
    if (shouldHideDialog) {
      let browser = window.top.gBrowser.selectedBrowser;
      let url = URL.fromURI(Services.io.createExposableURI(browser.currentURI));

      let info = await lazy.PlacesUtils.bookmarks.fetch({ url });
      let parentGuid = await lazy.PlacesUIUtils.defaultParentGuid;
      info = { url, parentGuid };
      let charset = null;
      let isErrorPage = false;

      // Check if the current tab is an error page,
      // if so, attempt to get the title from the history entry
      if (browser.documentURI) {
        isErrorPage = /^about:(neterror|certerror|blocked)/.test(
          browser.documentURI.spec
        );
      }
      try {
        if (isErrorPage) {
          let entry = await lazy.PlacesUtils.history.fetch(browser.currentURI);
          if (entry) {
            info.title = entry.title;
          }
        } else {
          info.title = browser.contentTitle;
        }
        info.title = info.title || url.href;
        charset = browser.characterSet;
      } catch (e) {
        console.error(e);
      }

      // Creates new bookmark
      info.guid = await lazy.PlacesTransactions.NewBookmark(info).transact();

      if (charset) {
        lazy.PlacesUIUtils.setCharsetForPage(url, charset, window).catch(
          console.error
        );
      }
      window.gURLBar.handleRevert();

      if (!shouldHideConfirmationHint) {
        window.StarUI.showConfirmation();
      }
    } else {
      // Bookmarks current tab with bookmark dialog shown
      window.top.PlacesCommandHook.bookmarkTabs([
        window.top.gBrowser.selectedTab,
      ]);
    }
  },

  /**
   * Processes "Special Message Actions", which are definitions of behaviors such as opening tabs
   * installing add-ons, or focusing the awesome bar that are allowed to can be triggered from
   * Messaging System interactions.
   *
   * @param {{type: string, data?: any}} action User action defined in message JSON.
   * @param browser {Browser} The browser most relevant to the message.
   * @returns {Promise<unknown>} Type depends on action type. See cases below.
   */
  /* eslint-disable-next-line complexity */
  async handleAction(action, browser) {
    const window = browser.ownerGlobal;
    switch (action.type) {
      case "SHOW_MIGRATION_WIZARD":
        Services.tm.dispatchToMainThread(() =>
          lazy.MigrationUtils.showMigrationWizard(window, {
            entrypoint: lazy.MigrationUtils.MIGRATION_ENTRYPOINTS.NEWTAB,
            migratorKey: action.data?.source,
          })
        );
        break;
      case "OPEN_PRIVATE_BROWSER_WINDOW":
        // Forcefully open about:privatebrowsing
        window.OpenBrowserWindow({ private: true });
        break;
      case "OPEN_URL":
        window.openLinkIn(
          Services.urlFormatter.formatURL(action.data.args),
          action.data.where || "current",
          {
            private: false,
            triggeringPrincipal:
              Services.scriptSecurityManager.createNullPrincipal({}),
            csp: null,
          }
        );
        break;
      case "OPEN_ABOUT_PAGE":
        let aboutPageURL = new URL(`about:${action.data.args}`);
        if (action.data.entrypoint) {
          aboutPageURL.search = action.data.entrypoint;
        }
        window.openTrustedLinkIn(
          aboutPageURL.toString(),
          action.data.where || "tab"
        );
        break;
      case "OPEN_FIREFOX_VIEW":
        window.FirefoxViewHandler.openTab();
        break;
      case "OPEN_PREFERENCES_PAGE":
        window.openPreferences(
          action.data.category || action.data.args,
          action.data.entrypoint && {
            urlParams: { entrypoint: action.data.entrypoint },
          }
        );
        break;
      case "OPEN_APPLICATIONS_MENU":
        lazy.UITour.showMenu(window, action.data.args);
        break;
      case "HIGHLIGHT_FEATURE":
        const highlight = await lazy.UITour.getTarget(window, action.data.args);
        if (highlight) {
          await lazy.UITour.showHighlight(window, highlight, "none", {
            autohide: true,
          });
        }
        break;
      case "INSTALL_ADDON_FROM_URL":
        await this.installAddonFromURL(
          browser,
          action.data.url,
          action.data.telemetrySource
        );
        break;
      case "PIN_FIREFOX_TO_TASKBAR":
        await this.pinFirefoxToTaskbar(window, action.data?.privatePin);
        break;
      case "PIN_FIREFOX_TO_START_MENU":
        await this.pinToStartMenu(window);
        break;
      case "PIN_AND_DEFAULT":
        // We must explicitly await pinning to the taskbar before
        // trying to set as default. If we fall back to setting
        // as default through the Windows Settings menu that interferes
        // with showing the pinning notification as we no longer have
        // window focus.
        await this.pinFirefoxToTaskbar(window, action.data?.privatePin);
        await this.setDefaultBrowser(window);
        break;
      case "SET_DEFAULT_BROWSER":
        await this.setDefaultBrowser(window);
        break;
      case "SET_DEFAULT_PDF_HANDLER":
        this.setDefaultPDFHandler(
          window,
          action.data?.onlyIfKnownBrowser ?? false
        );
        break;
      case "DECLINE_DEFAULT_PDF_HANDLER":
        Services.prefs.setBoolPref(
          "browser.shell.checkDefaultPDF.silencedByUser",
          true
        );
        break;
      case "CONFIRM_LAUNCH_ON_LOGIN":
        const { WindowsLaunchOnLogin } = ChromeUtils.importESModule(
          "resource://gre/modules/WindowsLaunchOnLogin.sys.mjs"
        );
        await WindowsLaunchOnLogin.createLaunchOnLogin();
        break;
      case "PIN_CURRENT_TAB":
        let tab = window.gBrowser.selectedTab;
        window.gBrowser.pinTab(tab);
        window.ConfirmationHint.show(tab, "confirmation-hint-pin-tab", {
          descriptionId: "confirmation-hint-pin-tab-description",
        });
        break;
      case "SHOW_FIREFOX_ACCOUNTS":
        if (!(await lazy.FxAccounts.canConnectAccount())) {
          break;
        }
        const data = action.data;
        const url = await lazy.FxAccounts.config.promiseConnectAccountURI(
          data && data.entrypoint,
          (data && data.extraParams) || {}
        );
        // Use location provided; if not specified, replace the current tab.
        window.openLinkIn(url, data.where || "current", {
          private: false,
          triggeringPrincipal:
            Services.scriptSecurityManager.createNullPrincipal({}),
          csp: null,
        });
        break;
      case "FXA_SIGNIN_FLOW":
        /** @returns {Promise<boolean>} */
        return this.fxaSignInFlow(action.data, browser);
      case "OPEN_PROTECTION_PANEL":
        let { gProtectionsHandler } = window;
        gProtectionsHandler.showProtectionsPopup({});
        break;
      case "OPEN_PROTECTION_REPORT":
        window.gProtectionsHandler.openProtections();
        break;
      case "OPEN_AWESOME_BAR":
        window.gURLBar.search("");
        break;
      case "DISABLE_STP_DOORHANGERS":
        await this.blockMessageById([
          "SOCIAL_TRACKING_PROTECTION",
          "FINGERPRINTERS_PROTECTION",
          "CRYPTOMINERS_PROTECTION",
        ]);
        break;
      case "DISABLE_DOH":
        Services.prefs.setStringPref(
          DOH_DOORHANGER_DECISION_PREF,
          "UIDisabled"
        );
        Services.prefs.setIntPref(NETWORK_TRR_MODE_PREF, 5);
        break;
      case "ACCEPT_DOH":
        Services.prefs.setStringPref(DOH_DOORHANGER_DECISION_PREF, "UIOk");
        break;
      case "CANCEL":
        // A no-op used by CFRs that minimizes the notification but does not
        // trigger a dismiss or block (it keeps the notification around)
        break;
      case "CONFIGURE_HOMEPAGE":
        this.configureHomepage(action.data);
        break;
      case "SHOW_SPOTLIGHT":
        lazy.Spotlight.showSpotlightDialog(browser, action.data);
        break;
      case "BLOCK_MESSAGE":
        await this.blockMessageById(action.data.id);
        break;
      case "SET_PREF":
        this.setPref(action.data.pref);
        break;
      case "MULTI_ACTION":
        await Promise.all(
          action.data.actions.map(async action => {
            try {
              await this.handleAction(action, browser);
            } catch (err) {
              throw new Error(`Error in MULTI_ACTION event: ${err.message}`);
            }
          })
        );
        break;
      default:
        throw new Error(
          `Special message action with type ${action.type} is unsupported.`
        );
      case "CLICK_ELEMENT":
        const clickElement = window.document.querySelector(
          action.data.selector
        );
        clickElement?.click();
        break;
      case "RELOAD_BROWSER":
        browser.reload();
        break;
      case "FOCUS_URLBAR":
        window.gURLBar.focus();
        window.gURLBar.select();
        break;
      case "BOOKMARK_CURRENT_TAB":
        this.bookmarkCurrentTab(
          window,
          action.data?.shouldHideDialog,
          action.data?.shouldHideConfirmationHint
        );
        break;
      case "SET_BOOKMARKS_TOOLBAR_VISIBILITY":
        this.setBookmarksToolbarVisibility(window, action.data?.visibility);
        break;
    }
    return undefined;
  },
};
PK
!<��y/y/9chrome/toolkit/content/extensions/parent/ext-scripting.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const {
  ExtensionScriptingStore,
  makeInternalContentScript,
  makePublicContentScript,
} = ChromeUtils.importESModule(
  "resource://gre/modules/ExtensionScriptingStore.sys.mjs"
);

var { ExtensionError, parseMatchPatterns } = ExtensionUtils;

// Map<Extension, Map<string, number>> - For each extension, we keep a map
// where the key is a user-provided script ID, the value is an internal
// generated integer.
const gScriptIdsMap = new Map();

/**
 * Inserts a script or style in the given tab, and returns a promise which
 * resolves when the operation has completed.
 *
 * @param {BaseContext} context
 *        The extension context for which to perform the injection.
 * @param {object} details
 *        The details object, specifying what to inject, where, and when.
 *        Derived from the ScriptInjection or CSSInjection types.
 * @param {string} kind
 *        The kind of data being injected. Possible choices: "js" or "css".
 * @param {string} method
 *        The name of the method which was called to trigger the injection.
 *        Used to generate appropriate error messages on failure.
 *
 * @returns {Promise}
 *        Resolves to the result of the execution, once it has completed.
 */
const execute = (context, details, kind, method) => {
  const { tabManager } = context.extension;

  let options = {
    jsPaths: [],
    cssPaths: [],
    removeCSS: method == "removeCSS",
    extensionId: context.extension.id,
  };

  const { tabId, frameIds, allFrames } = details.target;
  const tab = tabManager.get(tabId);

  options.hasActiveTabPermission = tab.hasActiveTabPermission;
  options.matches = tab.extension.allowedOrigins.patterns.map(
    host => host.pattern
  );

  const codeKey = kind === "js" ? "func" : "css";
  if ((details.files === null) == (details[codeKey] === null)) {
    throw new ExtensionError(
      `Exactly one of files and ${codeKey} must be specified.`
    );
  }

  if (details[codeKey]) {
    options[`${kind}Code`] = details[codeKey];
  }

  if (details.files) {
    for (const file of details.files) {
      let url = context.uri.resolve(file);
      if (!tab.extension.isExtensionURL(url)) {
        throw new ExtensionError(
          "Files to be injected must be within the extension"
        );
      }
      options[`${kind}Paths`].push(url);
    }
  }

  if (allFrames && frameIds) {
    throw new ExtensionError("Cannot specify both 'allFrames' and 'frameIds'.");
  }

  if (allFrames) {
    options.allFrames = allFrames;
  } else if (frameIds) {
    options.frameIds = frameIds;
  } else {
    options.frameIds = [0];
  }

  options.runAt = details.injectImmediately
    ? "document_start"
    : "document_idle";
  options.world = details.world || "ISOLATED";
  options.matchOriginAsFallback = true; // Also implies matchAboutBlank:true.
  options.wantReturnValue = true;
  // With this option set to `true`, we'll receive executeScript() results with
  // `frameId/result` properties and an `error` property will also be returned
  // in case of an error.
  options.returnResultsWithFrameIds = kind === "js";

  if (details.origin) {
    options.cssOrigin = details.origin.toLowerCase();
  } else {
    options.cssOrigin = "author";
  }

  // There is no need to execute anything when we have an empty list of frame
  // IDs because (1) it isn't invalid and (2) nothing will get executed.
  if (options.frameIds && options.frameIds.length === 0) {
    return [];
  }

  // This function is derived from `_execute()` in `parent/ext-tabs-base.js`,
  // make sure to keep both in sync when relevant.
  return tab.queryContent("Execute", options);
};

const ensureValidScriptId = id => {
  if (!id.length || id.startsWith("_")) {
    throw new ExtensionError("Invalid content script id.");
  }
};

const ensureValidScriptParams = (extension, script) => {
  if (!script.js?.length && !script.css?.length) {
    throw new ExtensionError("At least one js or css must be specified.");
  }

  if (!script.matches?.length) {
    throw new ExtensionError("matches must be specified.");
  }

  // This will throw if a match pattern is invalid.
  parseMatchPatterns(script.matches, {
    // This only works with MV2, not MV3. See Bug 1780507 for more information.
    restrictSchemes: extension.restrictSchemes,
  });

  if (script.excludeMatches) {
    // This will throw if a match pattern is invalid.
    parseMatchPatterns(script.excludeMatches, {
      // This only works with MV2, not MV3. See Bug 1780507 for more information.
      restrictSchemes: extension.restrictSchemes,
    });
  }
};

this.scripting = class extends ExtensionAPI {
  constructor(extension) {
    super(extension);

    // We initialize the scriptIdsMap for the extension with the scriptIds of
    // the store because this store initializes the extension before we
    // construct the scripting API here (and we need those IDs for some of the
    // API methods below).
    gScriptIdsMap.set(
      extension,
      ExtensionScriptingStore.getInitialScriptIdsMap(extension)
    );
  }

  onShutdown() {
    // When the extension is unloaded, the following happens:
    //
    // 1. The shared memory is cleared in the parent, see [1]
    // 2. The policy is marked as invalid, see [2]
    //
    // The following are not explicitly cleaned up:
    //
    // - `extension.registeredContentScripts
    // - `ExtensionProcessScript.registeredContentScripts` +
    //   `policy.contentScripts` (via `policy.unregisterContentScripts`)
    //
    // This means the script won't run again, but there is still potential for
    // memory leaks if there is a reference to `extension` or `policy`
    // somewhere.
    //
    // [1]: https://searchfox.org/mozilla-central/rev/211649f071259c4c733b4cafa94c44481c5caacc/toolkit/components/extensions/Extension.jsm#2974-2976
    // [2]: https://searchfox.org/mozilla-central/rev/211649f071259c4c733b4cafa94c44481c5caacc/toolkit/components/extensions/ExtensionProcessScript.jsm#239

    gScriptIdsMap.delete(this.extension);
  }

  getAPI(context) {
    const { extension } = context;

    return {
      scripting: {
        executeScriptInternal: async details => {
          return execute(context, details, "js", "executeScript");
        },

        insertCSS: async details => {
          return execute(context, details, "css", "insertCSS").then(() => {});
        },

        removeCSS: async details => {
          return execute(context, details, "css", "removeCSS").then(() => {});
        },

        registerContentScripts: async scripts => {
          // Map<string, number>
          const scriptIdsMap = gScriptIdsMap.get(extension);
          // Map<string, { scriptId: number, options: Object }>
          const scriptsToRegister = new Map();

          for (const script of scripts) {
            ensureValidScriptId(script.id);

            if (scriptIdsMap.has(script.id)) {
              throw new ExtensionError(
                `Content script with id "${script.id}" is already registered.`
              );
            }

            if (scriptsToRegister.has(script.id)) {
              throw new ExtensionError(
                `Script ID "${script.id}" found more than once in 'scripts' array.`
              );
            }

            ensureValidScriptParams(extension, script);

            scriptsToRegister.set(
              script.id,
              makeInternalContentScript(extension, script)
            );
          }

          for (const [id, { scriptId, options }] of scriptsToRegister) {
            scriptIdsMap.set(id, scriptId);
            extension.registeredContentScripts.set(scriptId, options);
          }
          extension.updateContentScripts();

          ExtensionScriptingStore.persistAll(extension);

          await extension.broadcast("Extension:RegisterContentScripts", {
            id: extension.id,
            scripts: Array.from(scriptsToRegister.values()),
          });
        },

        getRegisteredContentScripts: async details => {
          // Map<string, number>
          const scriptIdsMap = gScriptIdsMap.get(extension);

          return Array.from(scriptIdsMap.entries())
            .filter(([id]) => !details?.ids || details.ids.includes(id))
            .map(([, scriptId]) => {
              const options = extension.registeredContentScripts.get(scriptId);

              return makePublicContentScript(extension, options);
            });
        },

        unregisterContentScripts: async details => {
          // Map<string, number>
          const scriptIdsMap = gScriptIdsMap.get(extension);

          let ids = [];

          if (details?.ids) {
            for (const id of details.ids) {
              ensureValidScriptId(id);

              if (!scriptIdsMap.has(id)) {
                throw new ExtensionError(
                  `Content script with id "${id}" does not exist.`
                );
              }
            }

            ids = details.ids;
          } else {
            ids = Array.from(scriptIdsMap.keys());
          }

          if (ids.length === 0) {
            return;
          }

          const scriptIds = [];
          for (const id of ids) {
            const scriptId = scriptIdsMap.get(id);

            extension.registeredContentScripts.delete(scriptId);
            scriptIdsMap.delete(id);
            scriptIds.push(scriptId);
          }
          extension.updateContentScripts();

          ExtensionScriptingStore.persistAll(extension);

          await extension.broadcast("Extension:UnregisterContentScripts", {
            id: extension.id,
            scriptIds,
          });
        },

        updateContentScripts: async scripts => {
          // Map<string, number>
          const scriptIdsMap = gScriptIdsMap.get(extension);
          // Map<string, { scriptId: number, options: Object }>
          const scriptsToUpdate = new Map();

          for (const script of scripts) {
            ensureValidScriptId(script.id);

            if (!scriptIdsMap.has(script.id)) {
              throw new ExtensionError(
                `Content script with id "${script.id}" does not exist.`
              );
            }

            if (scriptsToUpdate.has(script.id)) {
              throw new ExtensionError(
                `Script ID "${script.id}" found more than once in 'scripts' array.`
              );
            }

            // Retrieve the existing script options.
            const scriptId = scriptIdsMap.get(script.id);
            const options = extension.registeredContentScripts.get(scriptId);

            // Use existing values if not specified in the update.
            script.allFrames ??= options.allFrames;
            script.css ??= options.cssPaths;
            script.excludeMatches ??= options.excludeMatches;
            script.js ??= options.jsPaths;
            script.matches ??= options.matches;
            script.matchOriginAsFallback ??= options.matchOriginAsFallback;
            script.runAt ??= options.runAt;
            script.world ??= options.world;
            script.persistAcrossSessions ??= options.persistAcrossSessions;

            ensureValidScriptParams(extension, script);

            scriptsToUpdate.set(script.id, {
              ...makeInternalContentScript(extension, script),
              // Re-use internal script ID.
              scriptId,
            });
          }

          for (const { scriptId, options } of scriptsToUpdate.values()) {
            extension.registeredContentScripts.set(scriptId, options);
          }
          extension.updateContentScripts();

          ExtensionScriptingStore.persistAll(extension);

          await extension.broadcast("Extension:UpdateContentScripts", {
            id: extension.id,
            scripts: Array.from(scriptsToUpdate.values()),
          });
        },
      },
    };
  }
};
PK
!<Ph�[[#modules/services-sync/Weave.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  CLIENT_NOT_CONFIGURED: "resource://services-sync/constants.sys.mjs",
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "syncUsername",
  "services.sync.username"
);

/**
 * Sync's XPCOM service.
 *
 * It is named "Weave" for historical reasons.
 *
 * It's worth noting how Sync is lazily loaded. We register a timer that
 * loads Sync a few seconds after app startup. This is so Sync does not
 * adversely affect application start time.
 *
 * If Sync is not configured, no extra Sync code is loaded. If an
 * external component (say the UI) needs to interact with Sync, it
 * should use the promise-base function whenLoaded() - something like the
 * following:
 *
 * // 1. Grab a handle to the Sync XPCOM service.
 * let service = Cc["@mozilla.org/weave/service;1"]
 *                 .getService(Components.interfaces.nsISupports)
 *                 .wrappedJSObject;
 *
 * // 2. Use the .then method of the promise.
 * service.whenLoaded().then(() => {
 *   // You are free to interact with "Weave." objects.
 *   return;
 * });
 *
 * And that's it!  However, if you really want to avoid promises and do it
 * old-school, then
 *
 * // 1. Get a reference to the service as done in (1) above.
 *
 * // 2. Check if the service has been initialized.
 * if (service.ready) {
 *   // You are free to interact with "Weave." objects.
 *   return;
 * }
 *
 * // 3. Install "ready" listener.
 * Services.obs.addObserver(function onReady() {
 *   Services.obs.removeObserver(onReady, "weave:service:ready");
 *
 *   // You are free to interact with "Weave." objects.
 * }, "weave:service:ready", false);
 *
 * // 4. Trigger loading of Sync.
 * service.ensureLoaded();
 */
export function WeaveService() {
  this.wrappedJSObject = this;
  this.ready = false;
}

WeaveService.prototype = {
  classID: Components.ID("{74b89fb0-f200-4ae8-a3ec-dd164117f6de}"),

  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
  ]),

  get Weave() {
    const { Weave } = ChromeUtils.importESModule(
      "resource://services-sync/main.sys.mjs"
    );
    return Weave;
  },

  ensureLoaded() {
    // Side-effect of accessing the service is that it is instantiated.
    this.Weave.Service;
  },

  whenLoaded() {
    if (this.ready) {
      return Promise.resolve();
    }
    let onReadyPromise = new Promise(resolve => {
      Services.obs.addObserver(function onReady() {
        Services.obs.removeObserver(onReady, "weave:service:ready");
        resolve();
      }, "weave:service:ready");
    });
    this.ensureLoaded();
    return onReadyPromise;
  },

  init() {
    // Force Weave service to load if it hasn't triggered from overlays
    this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    this.timer.initWithCallback(
      {
        notify: () => {
          let isConfigured = false;
          // We only load more if it looks like Sync is configured.
          if (this.enabled) {
            // We have an associated FxAccount. So, do a more thorough check.
            // This will import a number of modules and thus increase memory
            // accordingly. We could potentially copy code performed by
            // this check into this file if our above code is yielding too
            // many false positives.
            var { Weave } = ChromeUtils.importESModule(
              "resource://services-sync/main.sys.mjs"
            );
            isConfigured =
              Weave.Status.checkSetup() != lazy.CLIENT_NOT_CONFIGURED;
          }
          if (isConfigured) {
            this.ensureLoaded();
          }
        },
      },
      10000,
      Ci.nsITimer.TYPE_ONE_SHOT
    );
  },

  /**
   * Whether Sync appears to be enabled.
   *
   * This returns true if we have an associated FxA account and Sync is enabled.
   *
   * It does *not* perform a robust check to see if the client is working.
   * For that, you'll want to check Weave.Status.checkSetup().
   */
  get enabled() {
    return (
      !!lazy.syncUsername &&
      Services.prefs.getBoolPref("identity.fxaccounts.enabled")
    );
  },
};

export function AboutWeaveLog() {}
AboutWeaveLog.prototype = {
  classID: Components.ID("{d28f8a0b-95da-48f4-b712-caf37097be41}"),

  QueryInterface: ChromeUtils.generateQI([
    "nsIAboutModule",
    "nsISupportsWeakReference",
  ]),

  getURIFlags() {
    return 0;
  },

  newChannel(aURI, aLoadInfo) {
    let dir = lazy.FileUtils.getDir("ProfD", ["weave", "logs"]);
    try {
      dir.create(Ci.nsIFile.DIRECTORY_TYPE, lazy.FileUtils.PERMS_DIRECTORY);
    } catch (ex) {
      if (ex.result != Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
        throw ex;
      }
      // Ignore the exception due to a directory that already exists.
    }
    let uri = Services.io.newFileURI(dir);
    let channel = Services.io.newChannelFromURIWithLoadInfo(uri, aLoadInfo);

    channel.originalURI = aURI;

    // Ensure that the about page has the same privileges as a regular directory
    // view. That way links to files can be opened. make sure we use the correct
    // origin attributes when creating the principal for accessing the
    // about:sync-log data.
    let principal = Services.scriptSecurityManager.createContentPrincipal(
      uri,
      aLoadInfo.originAttributes
    );

    channel.owner = principal;
    return channel;
  },
};
PK
!<��7xKxKmodules/RustSuggest.sys.mjs// This file was autogenerated by the `uniffi-bindgen-gecko-js` crate.
// Trust me, you don't want to mess with it!

import { UniFFITypeError } from "resource://gre/modules/UniFFI.sys.mjs";



// Objects intended to be used in the unit tests
export var UnitTestObjs = {};

// Write/Read data to/from an ArrayBuffer
class ArrayBufferDataStream {
    constructor(arrayBuffer) {
        this.dataView = new DataView(arrayBuffer);
        this.pos = 0;
    }

    readUint8() {
        let rv = this.dataView.getUint8(this.pos);
        this.pos += 1;
        return rv;
    }

    writeUint8(value) {
        this.dataView.setUint8(this.pos, value);
        this.pos += 1;
    }

    readUint16() {
        let rv = this.dataView.getUint16(this.pos);
        this.pos += 2;
        return rv;
    }

    writeUint16(value) {
        this.dataView.setUint16(this.pos, value);
        this.pos += 2;
    }

    readUint32() {
        let rv = this.dataView.getUint32(this.pos);
        this.pos += 4;
        return rv;
    }

    writeUint32(value) {
        this.dataView.setUint32(this.pos, value);
        this.pos += 4;
    }

    readUint64() {
        let rv = this.dataView.getBigUint64(this.pos);
        this.pos += 8;
        return Number(rv);
    }

    writeUint64(value) {
        this.dataView.setBigUint64(this.pos, BigInt(value));
        this.pos += 8;
    }


    readInt8() {
        let rv = this.dataView.getInt8(this.pos);
        this.pos += 1;
        return rv;
    }

    writeInt8(value) {
        this.dataView.setInt8(this.pos, value);
        this.pos += 1;
    }

    readInt16() {
        let rv = this.dataView.getInt16(this.pos);
        this.pos += 2;
        return rv;
    }

    writeInt16(value) {
        this.dataView.setInt16(this.pos, value);
        this.pos += 2;
    }

    readInt32() {
        let rv = this.dataView.getInt32(this.pos);
        this.pos += 4;
        return rv;
    }

    writeInt32(value) {
        this.dataView.setInt32(this.pos, value);
        this.pos += 4;
    }

    readInt64() {
        let rv = this.dataView.getBigInt64(this.pos);
        this.pos += 8;
        return Number(rv);
    }

    writeInt64(value) {
        this.dataView.setBigInt64(this.pos, BigInt(value));
        this.pos += 8;
    }

    readFloat32() {
        let rv = this.dataView.getFloat32(this.pos);
        this.pos += 4;
        return rv;
    }

    writeFloat32(value) {
        this.dataView.setFloat32(this.pos, value);
        this.pos += 4;
    }

    readFloat64() {
        let rv = this.dataView.getFloat64(this.pos);
        this.pos += 8;
        return rv;
    }

    writeFloat64(value) {
        this.dataView.setFloat64(this.pos, value);
        this.pos += 8;
    }


    writeString(value) {
      const encoder = new TextEncoder();
      // Note: in order to efficiently write this data, we first write the
      // string data, reserving 4 bytes for the size.
      const dest = new Uint8Array(this.dataView.buffer, this.pos + 4);
      const encodeResult = encoder.encodeInto(value, dest);
      if (encodeResult.read != value.length) {
        throw new UniFFIError(
            "writeString: out of space when writing to ArrayBuffer.  Did the computeSize() method returned the wrong result?"
        );
      }
      const size = encodeResult.written;
      // Next, go back and write the size before the string data
      this.dataView.setUint32(this.pos, size);
      // Finally, advance our position past both the size and string data
      this.pos += size + 4;
    }

    readString() {
      const decoder = new TextDecoder();
      const size = this.readUint32();
      const source = new Uint8Array(this.dataView.buffer, this.pos, size)
      const value = decoder.decode(source);
      this.pos += size;
      return value;
    }

    // Reads a SuggestStore pointer from the data stream
    // UniFFI Pointers are **always** 8 bytes long. That is enforced
    // by the C++ and Rust Scaffolding code.
    readPointerSuggestStore() {
        const pointerId = 2; // suggest:SuggestStore
        const res = UniFFIScaffolding.readPointer(pointerId, this.dataView.buffer, this.pos);
        this.pos += 8;
        return res;
    }

    // Writes a SuggestStore pointer into the data stream
    // UniFFI Pointers are **always** 8 bytes long. That is enforced
    // by the C++ and Rust Scaffolding code.
    writePointerSuggestStore(value) {
        const pointerId = 2; // suggest:SuggestStore
        UniFFIScaffolding.writePointer(pointerId, value, this.dataView.buffer, this.pos);
        this.pos += 8;
    }
    

    // Reads a SuggestStoreBuilder pointer from the data stream
    // UniFFI Pointers are **always** 8 bytes long. That is enforced
    // by the C++ and Rust Scaffolding code.
    readPointerSuggestStoreBuilder() {
        const pointerId = 3; // suggest:SuggestStoreBuilder
        const res = UniFFIScaffolding.readPointer(pointerId, this.dataView.buffer, this.pos);
        this.pos += 8;
        return res;
    }

    // Writes a SuggestStoreBuilder pointer into the data stream
    // UniFFI Pointers are **always** 8 bytes long. That is enforced
    // by the C++ and Rust Scaffolding code.
    writePointerSuggestStoreBuilder(value) {
        const pointerId = 3; // suggest:SuggestStoreBuilder
        UniFFIScaffolding.writePointer(pointerId, value, this.dataView.buffer, this.pos);
        this.pos += 8;
    }
    
}

function handleRustResult(result, liftCallback, liftErrCallback) {
    switch (result.code) {
        case "success":
            return liftCallback(result.data);

        case "error":
            throw liftErrCallback(result.data);

        case "internal-error":
            if (result.data) {
                throw new UniFFIInternalError(FfiConverterString.lift(result.data));
            } else {
                throw new UniFFIInternalError("Unknown error");
            }

        default:
            throw new UniFFIError(`Unexpected status code: ${result.code}`);
    }
}

class UniFFIError {
    constructor(message) {
        this.message = message;
    }

    toString() {
        return `UniFFIError: ${this.message}`
    }
}

class UniFFIInternalError extends UniFFIError {}

// Base class for FFI converters
class FfiConverter {
    // throw `UniFFITypeError` if a value to be converted has an invalid type
    static checkType(value) {
        if (value === undefined ) {
            throw new UniFFITypeError(`undefined`);
        }
        if (value === null ) {
            throw new UniFFITypeError(`null`);
        }
    }
}

// Base class for FFI converters that lift/lower by reading/writing to an ArrayBuffer
class FfiConverterArrayBuffer extends FfiConverter {
    static lift(buf) {
        return this.read(new ArrayBufferDataStream(buf));
    }

    static lower(value) {
        const buf = new ArrayBuffer(this.computeSize(value));
        const dataStream = new ArrayBufferDataStream(buf);
        this.write(dataStream, value);
        return buf;
    }
}

// Symbols that are used to ensure that Object constructors
// can only be used with a proper UniFFI pointer
const uniffiObjectPtr = Symbol("uniffiObjectPtr");
const constructUniffiObject = Symbol("constructUniffiObject");
UnitTestObjs.uniffiObjectPtr = uniffiObjectPtr;

// Export the FFIConverter object to make external types work.
export class FfiConverterU8 extends FfiConverter {
    static checkType(value) {
        super.checkType(value);
        if (!Number.isInteger(value)) {
            throw new UniFFITypeError(`${value} is not an integer`);
        }
        if (value < 0 || value > 256) {
            throw new UniFFITypeError(`${value} exceeds the U8 bounds`);
        }
    }
    static computeSize() {
        return 1;
    }
    static lift(value) {
        return value;
    }
    static lower(value) {
        return value;
    }
    static write(dataStream, value) {
        dataStream.writeUint8(value)
    }
    static read(dataStream) {
        return dataStream.readUint8()
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterI32 extends FfiConverter {
    static checkType(value) {
        super.checkType(value);
        if (!Number.isInteger(value)) {
            throw new UniFFITypeError(`${value} is not an integer`);
        }
        if (value < -2147483648 || value > 2147483647) {
            throw new UniFFITypeError(`${value} exceeds the I32 bounds`);
        }
    }
    static computeSize() {
        return 4;
    }
    static lift(value) {
        return value;
    }
    static lower(value) {
        return value;
    }
    static write(dataStream, value) {
        dataStream.writeInt32(value)
    }
    static read(dataStream) {
        return dataStream.readInt32()
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterU64 extends FfiConverter {
    static checkType(value) {
        super.checkType(value);
        if (!Number.isSafeInteger(value)) {
            throw new UniFFITypeError(`${value} exceeds the safe integer bounds`);
        }
        if (value < 0) {
            throw new UniFFITypeError(`${value} exceeds the U64 bounds`);
        }
    }
    static computeSize() {
        return 8;
    }
    static lift(value) {
        return value;
    }
    static lower(value) {
        return value;
    }
    static write(dataStream, value) {
        dataStream.writeUint64(value)
    }
    static read(dataStream) {
        return dataStream.readUint64()
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterI64 extends FfiConverter {
    static checkType(value) {
        super.checkType(value);
        if (!Number.isSafeInteger(value)) {
            throw new UniFFITypeError(`${value} exceeds the safe integer bounds`);
        }
    }
    static computeSize() {
        return 8;
    }
    static lift(value) {
        return value;
    }
    static lower(value) {
        return value;
    }
    static write(dataStream, value) {
        dataStream.writeInt64(value)
    }
    static read(dataStream) {
        return dataStream.readInt64()
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterF64 extends FfiConverter {
    static computeSize() {
        return 8;
    }
    static lift(value) {
        return value;
    }
    static lower(value) {
        return value;
    }
    static write(dataStream, value) {
        dataStream.writeFloat64(value)
    }
    static read(dataStream) {
        return dataStream.readFloat64()
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterBool extends FfiConverter {
    static computeSize() {
        return 1;
    }
    static lift(value) {
        return value == 1;
    }
    static lower(value) {
        if (value) {
            return 1;
        } else {
            return 0;
        }
    }
    static write(dataStream, value) {
        dataStream.writeUint8(this.lower(value))
    }
    static read(dataStream) {
        return this.lift(dataStream.readUint8())
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterString extends FfiConverter {
    static checkType(value) {
        super.checkType(value);
        if (typeof value !== "string") {
            throw new UniFFITypeError(`${value} is not a string`);
        }
    }

    static lift(buf) {
        const decoder = new TextDecoder();
        const utf8Arr = new Uint8Array(buf);
        return decoder.decode(utf8Arr);
    }
    static lower(value) {
        const encoder = new TextEncoder();
        return encoder.encode(value).buffer;
    }

    static write(dataStream, value) {
        dataStream.writeString(value);
    }

    static read(dataStream) {
        return dataStream.readString();
    }

    static computeSize(value) {
        const encoder = new TextEncoder();
        return 4 + encoder.encode(value).length
    }
}

export class SuggestStore {
    // Use `init` to instantiate this class.
    // DO NOT USE THIS CONSTRUCTOR DIRECTLY
    constructor(opts) {
        if (!Object.prototype.hasOwnProperty.call(opts, constructUniffiObject)) {
            throw new UniFFIError("Attempting to construct an object using the JavaScript constructor directly" +
            "Please use a UDL defined constructor, or the init function for the primary constructor")
        }
        if (!opts[constructUniffiObject] instanceof UniFFIPointer) {
            throw new UniFFIError("Attempting to create a UniFFI object with a pointer that is not an instance of UniFFIPointer")
        }
        this[uniffiObjectPtr] = opts[constructUniffiObject];
    }
    /**
     * A constructor for SuggestStore.
     * 
     * @returns { SuggestStore }
     */
    static init(path,settingsConfig = null) {
        const liftResult = (result) => FfiConverterTypeSuggestStore.lift(result);
        const liftError = (data) => FfiConverterTypeSuggestApiError.lift(data);
        const functionCall = () => {
            try {
                FfiConverterString.checkType(path)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("path");
                }
                throw e;
            }
            try {
                FfiConverterOptionalTypeRemoteSettingsConfig.checkType(settingsConfig)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("settingsConfig");
                }
                throw e;
            }
            return UniFFIScaffolding.callSync(
                13, // suggest:uniffi_suggest_fn_constructor_suggeststore_new
                FfiConverterString.lower(path),
                FfiConverterOptionalTypeRemoteSettingsConfig.lower(settingsConfig),
            )
        }
        return handleRustResult(functionCall(), liftResult, liftError);}

    clear() {
        const liftResult = (result) => undefined;
        const liftError = (data) => FfiConverterTypeSuggestApiError.lift(data);
        const functionCall = () => {
            return UniFFIScaffolding.callAsync(
                14, // suggest:uniffi_suggest_fn_method_suggeststore_clear
                FfiConverterTypeSuggestStore.lower(this),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    clearDismissedSuggestions() {
        const liftResult = (result) => undefined;
        const liftError = (data) => FfiConverterTypeSuggestApiError.lift(data);
        const functionCall = () => {
            return UniFFIScaffolding.callAsync(
                15, // suggest:uniffi_suggest_fn_method_suggeststore_clear_dismissed_suggestions
                FfiConverterTypeSuggestStore.lower(this),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    dismissSuggestion(rawSuggestionUrl) {
        const liftResult = (result) => undefined;
        const liftError = (data) => FfiConverterTypeSuggestApiError.lift(data);
        const functionCall = () => {
            try {
                FfiConverterString.checkType(rawSuggestionUrl)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("rawSuggestionUrl");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                16, // suggest:uniffi_suggest_fn_method_suggeststore_dismiss_suggestion
                FfiConverterTypeSuggestStore.lower(this),
                FfiConverterString.lower(rawSuggestionUrl),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    fetchGlobalConfig() {
        const liftResult = (result) => FfiConverterTypeSuggestGlobalConfig.lift(result);
        const liftError = (data) => FfiConverterTypeSuggestApiError.lift(data);
        const functionCall = () => {
            return UniFFIScaffolding.callAsync(
                17, // suggest:uniffi_suggest_fn_method_suggeststore_fetch_global_config
                FfiConverterTypeSuggestStore.lower(this),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    fetchProviderConfig(provider) {
        const liftResult = (result) => FfiConverterOptionalTypeSuggestProviderConfig.lift(result);
        const liftError = (data) => FfiConverterTypeSuggestApiError.lift(data);
        const functionCall = () => {
            try {
                FfiConverterTypeSuggestionProvider.checkType(provider)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("provider");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                18, // suggest:uniffi_suggest_fn_method_suggeststore_fetch_provider_config
                FfiConverterTypeSuggestStore.lower(this),
                FfiConverterTypeSuggestionProvider.lower(provider),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    ingest(constraints) {
        const liftResult = (result) => FfiConverterTypeSuggestIngestionMetrics.lift(result);
        const liftError = (data) => FfiConverterTypeSuggestApiError.lift(data);
        const functionCall = () => {
            try {
                FfiConverterTypeSuggestIngestionConstraints.checkType(constraints)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("constraints");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                19, // suggest:uniffi_suggest_fn_method_suggeststore_ingest
                FfiConverterTypeSuggestStore.lower(this),
                FfiConverterTypeSuggestIngestionConstraints.lower(constraints),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    interrupt(kind = null) {
        const liftResult = (result) => undefined;
        const liftError = null;
        const functionCall = () => {
            try {
                FfiConverterOptionalTypeInterruptKind.checkType(kind)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("kind");
                }
                throw e;
            }
            return UniFFIScaffolding.callSync(
                20, // suggest:uniffi_suggest_fn_method_suggeststore_interrupt
                FfiConverterTypeSuggestStore.lower(this),
                FfiConverterOptionalTypeInterruptKind.lower(kind),
            )
        }
        return handleRustResult(functionCall(), liftResult, liftError);
    }

    query(query) {
        const liftResult = (result) => FfiConverterSequenceTypeSuggestion.lift(result);
        const liftError = (data) => FfiConverterTypeSuggestApiError.lift(data);
        const functionCall = () => {
            try {
                FfiConverterTypeSuggestionQuery.checkType(query)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("query");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                21, // suggest:uniffi_suggest_fn_method_suggeststore_query
                FfiConverterTypeSuggestStore.lower(this),
                FfiConverterTypeSuggestionQuery.lower(query),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    queryWithMetrics(query) {
        const liftResult = (result) => FfiConverterTypeQueryWithMetricsResult.lift(result);
        const liftError = (data) => FfiConverterTypeSuggestApiError.lift(data);
        const functionCall = () => {
            try {
                FfiConverterTypeSuggestionQuery.checkType(query)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("query");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                22, // suggest:uniffi_suggest_fn_method_suggeststore_query_with_metrics
                FfiConverterTypeSuggestStore.lower(this),
                FfiConverterTypeSuggestionQuery.lower(query),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeSuggestStore extends FfiConverter {
    static lift(value) {
        const opts = {};
        opts[constructUniffiObject] = value;
        return new SuggestStore(opts);
    }

    static lower(value) {
        const ptr = value[uniffiObjectPtr];
        if (!(ptr instanceof UniFFIPointer)) {
            throw new UniFFITypeError("Object is not a 'SuggestStore' instance");
        }
        return ptr;
    }

    static read(dataStream) {
        return this.lift(dataStream.readPointerSuggestStore());
    }

    static write(dataStream, value) {
        dataStream.writePointerSuggestStore(value[uniffiObjectPtr]);
    }

    static computeSize(value) {
        return 8;
    }
}

export class SuggestStoreBuilder {
    // Use `init` to instantiate this class.
    // DO NOT USE THIS CONSTRUCTOR DIRECTLY
    constructor(opts) {
        if (!Object.prototype.hasOwnProperty.call(opts, constructUniffiObject)) {
            throw new UniFFIError("Attempting to construct an object using the JavaScript constructor directly" +
            "Please use a UDL defined constructor, or the init function for the primary constructor")
        }
        if (!opts[constructUniffiObject] instanceof UniFFIPointer) {
            throw new UniFFIError("Attempting to create a UniFFI object with a pointer that is not an instance of UniFFIPointer")
        }
        this[uniffiObjectPtr] = opts[constructUniffiObject];
    }
    /**
     * A constructor for SuggestStoreBuilder.
     * 
     * @returns { SuggestStoreBuilder }
     */
    static init() {
        const liftResult = (result) => FfiConverterTypeSuggestStoreBuilder.lift(result);
        const liftError = null;
        const functionCall = () => {
            return UniFFIScaffolding.callSync(
                24, // suggest:uniffi_suggest_fn_constructor_suggeststorebuilder_new
            )
        }
        return handleRustResult(functionCall(), liftResult, liftError);}

    build() {
        const liftResult = (result) => FfiConverterTypeSuggestStore.lift(result);
        const liftError = (data) => FfiConverterTypeSuggestApiError.lift(data);
        const functionCall = () => {
            return UniFFIScaffolding.callSync(
                25, // suggest:uniffi_suggest_fn_method_suggeststorebuilder_build
                FfiConverterTypeSuggestStoreBuilder.lower(this),
            )
        }
        return handleRustResult(functionCall(), liftResult, liftError);
    }

    cachePath(path) {
        const liftResult = (result) => FfiConverterTypeSuggestStoreBuilder.lift(result);
        const liftError = null;
        const functionCall = () => {
            try {
                FfiConverterString.checkType(path)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("path");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                26, // suggest:uniffi_suggest_fn_method_suggeststorebuilder_cache_path
                FfiConverterTypeSuggestStoreBuilder.lower(this),
                FfiConverterString.lower(path),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    dataPath(path) {
        const liftResult = (result) => FfiConverterTypeSuggestStoreBuilder.lift(result);
        const liftError = null;
        const functionCall = () => {
            try {
                FfiConverterString.checkType(path)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("path");
                }
                throw e;
            }
            return UniFFIScaffolding.callSync(
                27, // suggest:uniffi_suggest_fn_method_suggeststorebuilder_data_path
                FfiConverterTypeSuggestStoreBuilder.lower(this),
                FfiConverterString.lower(path),
            )
        }
        return handleRustResult(functionCall(), liftResult, liftError);
    }

    loadExtension(libraryName,entrypoint) {
        const liftResult = (result) => FfiConverterTypeSuggestStoreBuilder.lift(result);
        const liftError = null;
        const functionCall = () => {
            try {
                FfiConverterString.checkType(libraryName)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("libraryName");
                }
                throw e;
            }
            try {
                FfiConverterOptionalstring.checkType(entrypoint)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("entrypoint");
                }
                throw e;
            }
            return UniFFIScaffolding.callSync(
                28, // suggest:uniffi_suggest_fn_method_suggeststorebuilder_load_extension
                FfiConverterTypeSuggestStoreBuilder.lower(this),
                FfiConverterString.lower(libraryName),
                FfiConverterOptionalstring.lower(entrypoint),
            )
        }
        return handleRustResult(functionCall(), liftResult, liftError);
    }

    remoteSettingsBucketName(bucketName) {
        const liftResult = (result) => FfiConverterTypeSuggestStoreBuilder.lift(result);
        const liftError = null;
        const functionCall = () => {
            try {
                FfiConverterString.checkType(bucketName)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("bucketName");
                }
                throw e;
            }
            return UniFFIScaffolding.callSync(
                29, // suggest:uniffi_suggest_fn_method_suggeststorebuilder_remote_settings_bucket_name
                FfiConverterTypeSuggestStoreBuilder.lower(this),
                FfiConverterString.lower(bucketName),
            )
        }
        return handleRustResult(functionCall(), liftResult, liftError);
    }

    remoteSettingsServer(server) {
        const liftResult = (result) => FfiConverterTypeSuggestStoreBuilder.lift(result);
        const liftError = null;
        const functionCall = () => {
            try {
                FfiConverterTypeRemoteSettingsServer.checkType(server)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("server");
                }
                throw e;
            }
            return UniFFIScaffolding.callSync(
                30, // suggest:uniffi_suggest_fn_method_suggeststorebuilder_remote_settings_server
                FfiConverterTypeSuggestStoreBuilder.lower(this),
                FfiConverterTypeRemoteSettingsServer.lower(server),
            )
        }
        return handleRustResult(functionCall(), liftResult, liftError);
    }

}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeSuggestStoreBuilder extends FfiConverter {
    static lift(value) {
        const opts = {};
        opts[constructUniffiObject] = value;
        return new SuggestStoreBuilder(opts);
    }

    static lower(value) {
        const ptr = value[uniffiObjectPtr];
        if (!(ptr instanceof UniFFIPointer)) {
            throw new UniFFITypeError("Object is not a 'SuggestStoreBuilder' instance");
        }
        return ptr;
    }

    static read(dataStream) {
        return this.lift(dataStream.readPointerSuggestStoreBuilder());
    }

    static write(dataStream, value) {
        dataStream.writePointerSuggestStoreBuilder(value[uniffiObjectPtr]);
    }

    static computeSize(value) {
        return 8;
    }
}

export class LabeledTimingSample {
    constructor({ label, value } = {}) {
        try {
            FfiConverterString.checkType(label)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("label");
            }
            throw e;
        }
        try {
            FfiConverterU64.checkType(value)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("value");
            }
            throw e;
        }
        this.label = label;
        this.value = value;
    }
    equals(other) {
        return (
            this.label == other.label &&
            this.value == other.value
        )
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeLabeledTimingSample extends FfiConverterArrayBuffer {
    static read(dataStream) {
        return new LabeledTimingSample({
            label: FfiConverterString.read(dataStream),
            value: FfiConverterU64.read(dataStream),
        });
    }
    static write(dataStream, value) {
        FfiConverterString.write(dataStream, value.label);
        FfiConverterU64.write(dataStream, value.value);
    }

    static computeSize(value) {
        let totalSize = 0;
        totalSize += FfiConverterString.computeSize(value.label);
        totalSize += FfiConverterU64.computeSize(value.value);
        return totalSize
    }

    static checkType(value) {
        super.checkType(value);
        if (!(value instanceof LabeledTimingSample)) {
            throw new UniFFITypeError(`Expected 'LabeledTimingSample', found '${typeof value}'`);
        }
        try {
            FfiConverterString.checkType(value.label);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".label");
            }
            throw e;
        }
        try {
            FfiConverterU64.checkType(value.value);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".value");
            }
            throw e;
        }
    }
}

export class QueryWithMetricsResult {
    constructor({ suggestions, queryTimes } = {}) {
        try {
            FfiConverterSequenceTypeSuggestion.checkType(suggestions)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("suggestions");
            }
            throw e;
        }
        try {
            FfiConverterSequenceTypeLabeledTimingSample.checkType(queryTimes)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("queryTimes");
            }
            throw e;
        }
        this.suggestions = suggestions;
        this.queryTimes = queryTimes;
    }
    equals(other) {
        return (
            this.suggestions == other.suggestions &&
            this.queryTimes == other.queryTimes
        )
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeQueryWithMetricsResult extends FfiConverterArrayBuffer {
    static read(dataStream) {
        return new QueryWithMetricsResult({
            suggestions: FfiConverterSequenceTypeSuggestion.read(dataStream),
            queryTimes: FfiConverterSequenceTypeLabeledTimingSample.read(dataStream),
        });
    }
    static write(dataStream, value) {
        FfiConverterSequenceTypeSuggestion.write(dataStream, value.suggestions);
        FfiConverterSequenceTypeLabeledTimingSample.write(dataStream, value.queryTimes);
    }

    static computeSize(value) {
        let totalSize = 0;
        totalSize += FfiConverterSequenceTypeSuggestion.computeSize(value.suggestions);
        totalSize += FfiConverterSequenceTypeLabeledTimingSample.computeSize(value.queryTimes);
        return totalSize
    }

    static checkType(value) {
        super.checkType(value);
        if (!(value instanceof QueryWithMetricsResult)) {
            throw new UniFFITypeError(`Expected 'QueryWithMetricsResult', found '${typeof value}'`);
        }
        try {
            FfiConverterSequenceTypeSuggestion.checkType(value.suggestions);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".suggestions");
            }
            throw e;
        }
        try {
            FfiConverterSequenceTypeLabeledTimingSample.checkType(value.queryTimes);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".queryTimes");
            }
            throw e;
        }
    }
}

export class SuggestGlobalConfig {
    constructor({ showLessFrequentlyCap } = {}) {
        try {
            FfiConverterI32.checkType(showLessFrequentlyCap)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("showLessFrequentlyCap");
            }
            throw e;
        }
        this.showLessFrequentlyCap = showLessFrequentlyCap;
    }
    equals(other) {
        return (
            this.showLessFrequentlyCap == other.showLessFrequentlyCap
        )
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeSuggestGlobalConfig extends FfiConverterArrayBuffer {
    static read(dataStream) {
        return new SuggestGlobalConfig({
            showLessFrequentlyCap: FfiConverterI32.read(dataStream),
        });
    }
    static write(dataStream, value) {
        FfiConverterI32.write(dataStream, value.showLessFrequentlyCap);
    }

    static computeSize(value) {
        let totalSize = 0;
        totalSize += FfiConverterI32.computeSize(value.showLessFrequentlyCap);
        return totalSize
    }

    static checkType(value) {
        super.checkType(value);
        if (!(value instanceof SuggestGlobalConfig)) {
            throw new UniFFITypeError(`Expected 'SuggestGlobalConfig', found '${typeof value}'`);
        }
        try {
            FfiConverterI32.checkType(value.showLessFrequentlyCap);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".showLessFrequentlyCap");
            }
            throw e;
        }
    }
}

export class SuggestIngestionConstraints {
    constructor({ providers = null, emptyOnly = false } = {}) {
        try {
            FfiConverterOptionalSequenceTypeSuggestionProvider.checkType(providers)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("providers");
            }
            throw e;
        }
        try {
            FfiConverterBool.checkType(emptyOnly)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("emptyOnly");
            }
            throw e;
        }
        this.providers = providers;
        this.emptyOnly = emptyOnly;
    }
    equals(other) {
        return (
            this.providers == other.providers &&
            this.emptyOnly == other.emptyOnly
        )
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeSuggestIngestionConstraints extends FfiConverterArrayBuffer {
    static read(dataStream) {
        return new SuggestIngestionConstraints({
            providers: FfiConverterOptionalSequenceTypeSuggestionProvider.read(dataStream),
            emptyOnly: FfiConverterBool.read(dataStream),
        });
    }
    static write(dataStream, value) {
        FfiConverterOptionalSequenceTypeSuggestionProvider.write(dataStream, value.providers);
        FfiConverterBool.write(dataStream, value.emptyOnly);
    }

    static computeSize(value) {
        let totalSize = 0;
        totalSize += FfiConverterOptionalSequenceTypeSuggestionProvider.computeSize(value.providers);
        totalSize += FfiConverterBool.computeSize(value.emptyOnly);
        return totalSize
    }

    static checkType(value) {
        super.checkType(value);
        if (!(value instanceof SuggestIngestionConstraints)) {
            throw new UniFFITypeError(`Expected 'SuggestIngestionConstraints', found '${typeof value}'`);
        }
        try {
            FfiConverterOptionalSequenceTypeSuggestionProvider.checkType(value.providers);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".providers");
            }
            throw e;
        }
        try {
            FfiConverterBool.checkType(value.emptyOnly);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".emptyOnly");
            }
            throw e;
        }
    }
}

export class SuggestIngestionMetrics {
    constructor({ ingestionTimes, downloadTimes } = {}) {
        try {
            FfiConverterSequenceTypeLabeledTimingSample.checkType(ingestionTimes)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("ingestionTimes");
            }
            throw e;
        }
        try {
            FfiConverterSequenceTypeLabeledTimingSample.checkType(downloadTimes)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("downloadTimes");
            }
            throw e;
        }
        this.ingestionTimes = ingestionTimes;
        this.downloadTimes = downloadTimes;
    }
    equals(other) {
        return (
            this.ingestionTimes == other.ingestionTimes &&
            this.downloadTimes == other.downloadTimes
        )
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeSuggestIngestionMetrics extends FfiConverterArrayBuffer {
    static read(dataStream) {
        return new SuggestIngestionMetrics({
            ingestionTimes: FfiConverterSequenceTypeLabeledTimingSample.read(dataStream),
            downloadTimes: FfiConverterSequenceTypeLabeledTimingSample.read(dataStream),
        });
    }
    static write(dataStream, value) {
        FfiConverterSequenceTypeLabeledTimingSample.write(dataStream, value.ingestionTimes);
        FfiConverterSequenceTypeLabeledTimingSample.write(dataStream, value.downloadTimes);
    }

    static computeSize(value) {
        let totalSize = 0;
        totalSize += FfiConverterSequenceTypeLabeledTimingSample.computeSize(value.ingestionTimes);
        totalSize += FfiConverterSequenceTypeLabeledTimingSample.computeSize(value.downloadTimes);
        return totalSize
    }

    static checkType(value) {
        super.checkType(value);
        if (!(value instanceof SuggestIngestionMetrics)) {
            throw new UniFFITypeError(`Expected 'SuggestIngestionMetrics', found '${typeof value}'`);
        }
        try {
            FfiConverterSequenceTypeLabeledTimingSample.checkType(value.ingestionTimes);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".ingestionTimes");
            }
            throw e;
        }
        try {
            FfiConverterSequenceTypeLabeledTimingSample.checkType(value.downloadTimes);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".downloadTimes");
            }
            throw e;
        }
    }
}

export class SuggestionQuery {
    constructor({ keyword, providers, limit = null } = {}) {
        try {
            FfiConverterString.checkType(keyword)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("keyword");
            }
            throw e;
        }
        try {
            FfiConverterSequenceTypeSuggestionProvider.checkType(providers)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("providers");
            }
            throw e;
        }
        try {
            FfiConverterOptionali32.checkType(limit)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("limit");
            }
            throw e;
        }
        this.keyword = keyword;
        this.providers = providers;
        this.limit = limit;
    }
    equals(other) {
        return (
            this.keyword == other.keyword &&
            this.providers == other.providers &&
            this.limit == other.limit
        )
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeSuggestionQuery extends FfiConverterArrayBuffer {
    static read(dataStream) {
        return new SuggestionQuery({
            keyword: FfiConverterString.read(dataStream),
            providers: FfiConverterSequenceTypeSuggestionProvider.read(dataStream),
            limit: FfiConverterOptionali32.read(dataStream),
        });
    }
    static write(dataStream, value) {
        FfiConverterString.write(dataStream, value.keyword);
        FfiConverterSequenceTypeSuggestionProvider.write(dataStream, value.providers);
        FfiConverterOptionali32.write(dataStream, value.limit);
    }

    static computeSize(value) {
        let totalSize = 0;
        totalSize += FfiConverterString.computeSize(value.keyword);
        totalSize += FfiConverterSequenceTypeSuggestionProvider.computeSize(value.providers);
        totalSize += FfiConverterOptionali32.computeSize(value.limit);
        return totalSize
    }

    static checkType(value) {
        super.checkType(value);
        if (!(value instanceof SuggestionQuery)) {
            throw new UniFFITypeError(`Expected 'SuggestionQuery', found '${typeof value}'`);
        }
        try {
            FfiConverterString.checkType(value.keyword);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".keyword");
            }
            throw e;
        }
        try {
            FfiConverterSequenceTypeSuggestionProvider.checkType(value.providers);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".providers");
            }
            throw e;
        }
        try {
            FfiConverterOptionali32.checkType(value.limit);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".limit");
            }
            throw e;
        }
    }
}


export const InterruptKind = {
    READ: 1,
    WRITE: 2,
    READ_WRITE: 3,
};

Object.freeze(InterruptKind);
// Export the FFIConverter object to make external types work.
export class FfiConverterTypeInterruptKind extends FfiConverterArrayBuffer {
    static read(dataStream) {
        switch (dataStream.readInt32()) {
            case 1:
                return InterruptKind.READ
            case 2:
                return InterruptKind.WRITE
            case 3:
                return InterruptKind.READ_WRITE
            default:
                throw new UniFFITypeError("Unknown InterruptKind variant");
        }
    }

    static write(dataStream, value) {
        if (value === InterruptKind.READ) {
            dataStream.writeInt32(1);
            return;
        }
        if (value === InterruptKind.WRITE) {
            dataStream.writeInt32(2);
            return;
        }
        if (value === InterruptKind.READ_WRITE) {
            dataStream.writeInt32(3);
            return;
        }
        throw new UniFFITypeError("Unknown InterruptKind variant");
    }

    static computeSize(value) {
        return 4;
    }

    static checkType(value) {
      if (!Number.isInteger(value) || value < 1 || value > 3) {
          throw new UniFFITypeError(`${value} is not a valid value for InterruptKind`);
      }
    }
}





export class SuggestApiError extends Error {}


export class Interrupted extends SuggestApiError {

    constructor(
        ...params
    ) {
        super(...params);
    }
    toString() {
        return `Interrupted: ${super.toString()}`
    }
}

export class Backoff extends SuggestApiError {

    constructor(
        seconds,
        ...params
    ) {
        const message = `seconds: ${ seconds }`;
        super(message, ...params);
        this.seconds = seconds;
    }
    toString() {
        return `Backoff: ${super.toString()}`
    }
}

export class Network extends SuggestApiError {

    constructor(
        reason,
        ...params
    ) {
        const message = `reason: ${ reason }`;
        super(message, ...params);
        this.reason = reason;
    }
    toString() {
        return `Network: ${super.toString()}`
    }
}

export class Other extends SuggestApiError {

    constructor(
        reason,
        ...params
    ) {
        const message = `reason: ${ reason }`;
        super(message, ...params);
        this.reason = reason;
    }
    toString() {
        return `Other: ${super.toString()}`
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeSuggestApiError extends FfiConverterArrayBuffer {
    static read(dataStream) {
        switch (dataStream.readInt32()) {
            case 1:
                return new Interrupted(
                    );
            case 2:
                return new Backoff(
                    FfiConverterU64.read(dataStream)
                    );
            case 3:
                return new Network(
                    FfiConverterString.read(dataStream)
                    );
            case 4:
                return new Other(
                    FfiConverterString.read(dataStream)
                    );
            default:
                throw new UniFFITypeError("Unknown SuggestApiError variant");
        }
    }
    static computeSize(value) {
        // Size of the Int indicating the variant
        let totalSize = 4;
        if (value instanceof Interrupted) {
            return totalSize;
        }
        if (value instanceof Backoff) {
            totalSize += FfiConverterU64.computeSize(value.seconds);
            return totalSize;
        }
        if (value instanceof Network) {
            totalSize += FfiConverterString.computeSize(value.reason);
            return totalSize;
        }
        if (value instanceof Other) {
            totalSize += FfiConverterString.computeSize(value.reason);
            return totalSize;
        }
        throw new UniFFITypeError("Unknown SuggestApiError variant");
    }
    static write(dataStream, value) {
        if (value instanceof Interrupted) {
            dataStream.writeInt32(1);
            return;
        }
        if (value instanceof Backoff) {
            dataStream.writeInt32(2);
            FfiConverterU64.write(dataStream, value.seconds);
            return;
        }
        if (value instanceof Network) {
            dataStream.writeInt32(3);
            FfiConverterString.write(dataStream, value.reason);
            return;
        }
        if (value instanceof Other) {
            dataStream.writeInt32(4);
            FfiConverterString.write(dataStream, value.reason);
            return;
        }
        throw new UniFFITypeError("Unknown SuggestApiError variant");
    }

    static errorClass = SuggestApiError;
}


export class SuggestProviderConfig {}
SuggestProviderConfig.Weather = class extends SuggestProviderConfig{
    constructor(
        minKeywordLength
        ) {
            super();
            this.minKeywordLength = minKeywordLength;
        }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeSuggestProviderConfig extends FfiConverterArrayBuffer {
    static read(dataStream) {
        switch (dataStream.readInt32()) {
            case 1:
                return new SuggestProviderConfig.Weather(
                    FfiConverterI32.read(dataStream)
                    );
            default:
                throw new UniFFITypeError("Unknown SuggestProviderConfig variant");
        }
    }

    static write(dataStream, value) {
        if (value instanceof SuggestProviderConfig.Weather) {
            dataStream.writeInt32(1);
            FfiConverterI32.write(dataStream, value.minKeywordLength);
            return;
        }
        throw new UniFFITypeError("Unknown SuggestProviderConfig variant");
    }

    static computeSize(value) {
        // Size of the Int indicating the variant
        let totalSize = 4;
        if (value instanceof SuggestProviderConfig.Weather) {
            totalSize += FfiConverterI32.computeSize(value.minKeywordLength);
            return totalSize;
        }
        throw new UniFFITypeError("Unknown SuggestProviderConfig variant");
    }

    static checkType(value) {
      if (!(value instanceof SuggestProviderConfig)) {
        throw new UniFFITypeError(`${value} is not a subclass instance of SuggestProviderConfig`);
      }
    }
}



export class Suggestion {}
Suggestion.Amp = class extends Suggestion{
    constructor(
        title,
        url,
        rawUrl,
        icon,
        iconMimetype,
        fullKeyword,
        blockId,
        advertiser,
        iabCategory,
        impressionUrl,
        clickUrl,
        rawClickUrl,
        score
        ) {
            super();
            this.title = title;
            this.url = url;
            this.rawUrl = rawUrl;
            this.icon = icon;
            this.iconMimetype = iconMimetype;
            this.fullKeyword = fullKeyword;
            this.blockId = blockId;
            this.advertiser = advertiser;
            this.iabCategory = iabCategory;
            this.impressionUrl = impressionUrl;
            this.clickUrl = clickUrl;
            this.rawClickUrl = rawClickUrl;
            this.score = score;
        }
}
Suggestion.Pocket = class extends Suggestion{
    constructor(
        title,
        url,
        score,
        isTopPick
        ) {
            super();
            this.title = title;
            this.url = url;
            this.score = score;
            this.isTopPick = isTopPick;
        }
}
Suggestion.Wikipedia = class extends Suggestion{
    constructor(
        title,
        url,
        icon,
        iconMimetype,
        fullKeyword
        ) {
            super();
            this.title = title;
            this.url = url;
            this.icon = icon;
            this.iconMimetype = iconMimetype;
            this.fullKeyword = fullKeyword;
        }
}
Suggestion.Amo = class extends Suggestion{
    constructor(
        title,
        url,
        iconUrl,
        description,
        rating,
        numberOfRatings,
        guid,
        score
        ) {
            super();
            this.title = title;
            this.url = url;
            this.iconUrl = iconUrl;
            this.description = description;
            this.rating = rating;
            this.numberOfRatings = numberOfRatings;
            this.guid = guid;
            this.score = score;
        }
}
Suggestion.Yelp = class extends Suggestion{
    constructor(
        url,
        title,
        icon,
        iconMimetype,
        score,
        hasLocationSign,
        subjectExactMatch,
        locationParam
        ) {
            super();
            this.url = url;
            this.title = title;
            this.icon = icon;
            this.iconMimetype = iconMimetype;
            this.score = score;
            this.hasLocationSign = hasLocationSign;
            this.subjectExactMatch = subjectExactMatch;
            this.locationParam = locationParam;
        }
}
Suggestion.Mdn = class extends Suggestion{
    constructor(
        title,
        url,
        description,
        score
        ) {
            super();
            this.title = title;
            this.url = url;
            this.description = description;
            this.score = score;
        }
}
Suggestion.Weather = class extends Suggestion{
    constructor(
        score
        ) {
            super();
            this.score = score;
        }
}
Suggestion.Fakespot = class extends Suggestion{
    constructor(
        fakespotGrade,
        productId,
        rating,
        title,
        totalReviews,
        url,
        icon,
        iconMimetype,
        score
        ) {
            super();
            this.fakespotGrade = fakespotGrade;
            this.productId = productId;
            this.rating = rating;
            this.title = title;
            this.totalReviews = totalReviews;
            this.url = url;
            this.icon = icon;
            this.iconMimetype = iconMimetype;
            this.score = score;
        }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeSuggestion extends FfiConverterArrayBuffer {
    static read(dataStream) {
        switch (dataStream.readInt32()) {
            case 1:
                return new Suggestion.Amp(
                    FfiConverterString.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterOptionalSequenceu8.read(dataStream),
                    FfiConverterOptionalstring.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterI64.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterF64.read(dataStream)
                    );
            case 2:
                return new Suggestion.Pocket(
                    FfiConverterString.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterF64.read(dataStream),
                    FfiConverterBool.read(dataStream)
                    );
            case 3:
                return new Suggestion.Wikipedia(
                    FfiConverterString.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterOptionalSequenceu8.read(dataStream),
                    FfiConverterOptionalstring.read(dataStream),
                    FfiConverterString.read(dataStream)
                    );
            case 4:
                return new Suggestion.Amo(
                    FfiConverterString.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterOptionalstring.read(dataStream),
                    FfiConverterI64.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterF64.read(dataStream)
                    );
            case 5:
                return new Suggestion.Yelp(
                    FfiConverterString.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterOptionalSequenceu8.read(dataStream),
                    FfiConverterOptionalstring.read(dataStream),
                    FfiConverterF64.read(dataStream),
                    FfiConverterBool.read(dataStream),
                    FfiConverterBool.read(dataStream),
                    FfiConverterString.read(dataStream)
                    );
            case 6:
                return new Suggestion.Mdn(
                    FfiConverterString.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterF64.read(dataStream)
                    );
            case 7:
                return new Suggestion.Weather(
                    FfiConverterF64.read(dataStream)
                    );
            case 8:
                return new Suggestion.Fakespot(
                    FfiConverterString.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterF64.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterI64.read(dataStream),
                    FfiConverterString.read(dataStream),
                    FfiConverterOptionalSequenceu8.read(dataStream),
                    FfiConverterOptionalstring.read(dataStream),
                    FfiConverterF64.read(dataStream)
                    );
            default:
                throw new UniFFITypeError("Unknown Suggestion variant");
        }
    }

    static write(dataStream, value) {
        if (value instanceof Suggestion.Amp) {
            dataStream.writeInt32(1);
            FfiConverterString.write(dataStream, value.title);
            FfiConverterString.write(dataStream, value.url);
            FfiConverterString.write(dataStream, value.rawUrl);
            FfiConverterOptionalSequenceu8.write(dataStream, value.icon);
            FfiConverterOptionalstring.write(dataStream, value.iconMimetype);
            FfiConverterString.write(dataStream, value.fullKeyword);
            FfiConverterI64.write(dataStream, value.blockId);
            FfiConverterString.write(dataStream, value.advertiser);
            FfiConverterString.write(dataStream, value.iabCategory);
            FfiConverterString.write(dataStream, value.impressionUrl);
            FfiConverterString.write(dataStream, value.clickUrl);
            FfiConverterString.write(dataStream, value.rawClickUrl);
            FfiConverterF64.write(dataStream, value.score);
            return;
        }
        if (value instanceof Suggestion.Pocket) {
            dataStream.writeInt32(2);
            FfiConverterString.write(dataStream, value.title);
            FfiConverterString.write(dataStream, value.url);
            FfiConverterF64.write(dataStream, value.score);
            FfiConverterBool.write(dataStream, value.isTopPick);
            return;
        }
        if (value instanceof Suggestion.Wikipedia) {
            dataStream.writeInt32(3);
            FfiConverterString.write(dataStream, value.title);
            FfiConverterString.write(dataStream, value.url);
            FfiConverterOptionalSequenceu8.write(dataStream, value.icon);
            FfiConverterOptionalstring.write(dataStream, value.iconMimetype);
            FfiConverterString.write(dataStream, value.fullKeyword);
            return;
        }
        if (value instanceof Suggestion.Amo) {
            dataStream.writeInt32(4);
            FfiConverterString.write(dataStream, value.title);
            FfiConverterString.write(dataStream, value.url);
            FfiConverterString.write(dataStream, value.iconUrl);
            FfiConverterString.write(dataStream, value.description);
            FfiConverterOptionalstring.write(dataStream, value.rating);
            FfiConverterI64.write(dataStream, value.numberOfRatings);
            FfiConverterString.write(dataStream, value.guid);
            FfiConverterF64.write(dataStream, value.score);
            return;
        }
        if (value instanceof Suggestion.Yelp) {
            dataStream.writeInt32(5);
            FfiConverterString.write(dataStream, value.url);
            FfiConverterString.write(dataStream, value.title);
            FfiConverterOptionalSequenceu8.write(dataStream, value.icon);
            FfiConverterOptionalstring.write(dataStream, value.iconMimetype);
            FfiConverterF64.write(dataStream, value.score);
            FfiConverterBool.write(dataStream, value.hasLocationSign);
            FfiConverterBool.write(dataStream, value.subjectExactMatch);
            FfiConverterString.write(dataStream, value.locationParam);
            return;
        }
        if (value instanceof Suggestion.Mdn) {
            dataStream.writeInt32(6);
            FfiConverterString.write(dataStream, value.title);
            FfiConverterString.write(dataStream, value.url);
            FfiConverterString.write(dataStream, value.description);
            FfiConverterF64.write(dataStream, value.score);
            return;
        }
        if (value instanceof Suggestion.Weather) {
            dataStream.writeInt32(7);
            FfiConverterF64.write(dataStream, value.score);
            return;
        }
        if (value instanceof Suggestion.Fakespot) {
            dataStream.writeInt32(8);
            FfiConverterString.write(dataStream, value.fakespotGrade);
            FfiConverterString.write(dataStream, value.productId);
            FfiConverterF64.write(dataStream, value.rating);
            FfiConverterString.write(dataStream, value.title);
            FfiConverterI64.write(dataStream, value.totalReviews);
            FfiConverterString.write(dataStream, value.url);
            FfiConverterOptionalSequenceu8.write(dataStream, value.icon);
            FfiConverterOptionalstring.write(dataStream, value.iconMimetype);
            FfiConverterF64.write(dataStream, value.score);
            return;
        }
        throw new UniFFITypeError("Unknown Suggestion variant");
    }

    static computeSize(value) {
        // Size of the Int indicating the variant
        let totalSize = 4;
        if (value instanceof Suggestion.Amp) {
            totalSize += FfiConverterString.computeSize(value.title);
            totalSize += FfiConverterString.computeSize(value.url);
            totalSize += FfiConverterString.computeSize(value.rawUrl);
            totalSize += FfiConverterOptionalSequenceu8.computeSize(value.icon);
            totalSize += FfiConverterOptionalstring.computeSize(value.iconMimetype);
            totalSize += FfiConverterString.computeSize(value.fullKeyword);
            totalSize += FfiConverterI64.computeSize(value.blockId);
            totalSize += FfiConverterString.computeSize(value.advertiser);
            totalSize += FfiConverterString.computeSize(value.iabCategory);
            totalSize += FfiConverterString.computeSize(value.impressionUrl);
            totalSize += FfiConverterString.computeSize(value.clickUrl);
            totalSize += FfiConverterString.computeSize(value.rawClickUrl);
            totalSize += FfiConverterF64.computeSize(value.score);
            return totalSize;
        }
        if (value instanceof Suggestion.Pocket) {
            totalSize += FfiConverterString.computeSize(value.title);
            totalSize += FfiConverterString.computeSize(value.url);
            totalSize += FfiConverterF64.computeSize(value.score);
            totalSize += FfiConverterBool.computeSize(value.isTopPick);
            return totalSize;
        }
        if (value instanceof Suggestion.Wikipedia) {
            totalSize += FfiConverterString.computeSize(value.title);
            totalSize += FfiConverterString.computeSize(value.url);
            totalSize += FfiConverterOptionalSequenceu8.computeSize(value.icon);
            totalSize += FfiConverterOptionalstring.computeSize(value.iconMimetype);
            totalSize += FfiConverterString.computeSize(value.fullKeyword);
            return totalSize;
        }
        if (value instanceof Suggestion.Amo) {
            totalSize += FfiConverterString.computeSize(value.title);
            totalSize += FfiConverterString.computeSize(value.url);
            totalSize += FfiConverterString.computeSize(value.iconUrl);
            totalSize += FfiConverterString.computeSize(value.description);
            totalSize += FfiConverterOptionalstring.computeSize(value.rating);
            totalSize += FfiConverterI64.computeSize(value.numberOfRatings);
            totalSize += FfiConverterString.computeSize(value.guid);
            totalSize += FfiConverterF64.computeSize(value.score);
            return totalSize;
        }
        if (value instanceof Suggestion.Yelp) {
            totalSize += FfiConverterString.computeSize(value.url);
            totalSize += FfiConverterString.computeSize(value.title);
            totalSize += FfiConverterOptionalSequenceu8.computeSize(value.icon);
            totalSize += FfiConverterOptionalstring.computeSize(value.iconMimetype);
            totalSize += FfiConverterF64.computeSize(value.score);
            totalSize += FfiConverterBool.computeSize(value.hasLocationSign);
            totalSize += FfiConverterBool.computeSize(value.subjectExactMatch);
            totalSize += FfiConverterString.computeSize(value.locationParam);
            return totalSize;
        }
        if (value instanceof Suggestion.Mdn) {
            totalSize += FfiConverterString.computeSize(value.title);
            totalSize += FfiConverterString.computeSize(value.url);
            totalSize += FfiConverterString.computeSize(value.description);
            totalSize += FfiConverterF64.computeSize(value.score);
            return totalSize;
        }
        if (value instanceof Suggestion.Weather) {
            totalSize += FfiConverterF64.computeSize(value.score);
            return totalSize;
        }
        if (value instanceof Suggestion.Fakespot) {
            totalSize += FfiConverterString.computeSize(value.fakespotGrade);
            totalSize += FfiConverterString.computeSize(value.productId);
            totalSize += FfiConverterF64.computeSize(value.rating);
            totalSize += FfiConverterString.computeSize(value.title);
            totalSize += FfiConverterI64.computeSize(value.totalReviews);
            totalSize += FfiConverterString.computeSize(value.url);
            totalSize += FfiConverterOptionalSequenceu8.computeSize(value.icon);
            totalSize += FfiConverterOptionalstring.computeSize(value.iconMimetype);
            totalSize += FfiConverterF64.computeSize(value.score);
            return totalSize;
        }
        throw new UniFFITypeError("Unknown Suggestion variant");
    }

    static checkType(value) {
      if (!(value instanceof Suggestion)) {
        throw new UniFFITypeError(`${value} is not a subclass instance of Suggestion`);
      }
    }
}



export const SuggestionProvider = {
    AMP: 1,
    POCKET: 2,
    WIKIPEDIA: 3,
    AMO: 4,
    YELP: 5,
    MDN: 6,
    WEATHER: 7,
    AMP_MOBILE: 8,
    FAKESPOT: 9,
};

Object.freeze(SuggestionProvider);
// Export the FFIConverter object to make external types work.
export class FfiConverterTypeSuggestionProvider extends FfiConverterArrayBuffer {
    static read(dataStream) {
        switch (dataStream.readInt32()) {
            case 1:
                return SuggestionProvider.AMP
            case 2:
                return SuggestionProvider.POCKET
            case 3:
                return SuggestionProvider.WIKIPEDIA
            case 4:
                return SuggestionProvider.AMO
            case 5:
                return SuggestionProvider.YELP
            case 6:
                return SuggestionProvider.MDN
            case 7:
                return SuggestionProvider.WEATHER
            case 8:
                return SuggestionProvider.AMP_MOBILE
            case 9:
                return SuggestionProvider.FAKESPOT
            default:
                throw new UniFFITypeError("Unknown SuggestionProvider variant");
        }
    }

    static write(dataStream, value) {
        if (value === SuggestionProvider.AMP) {
            dataStream.writeInt32(1);
            return;
        }
        if (value === SuggestionProvider.POCKET) {
            dataStream.writeInt32(2);
            return;
        }
        if (value === SuggestionProvider.WIKIPEDIA) {
            dataStream.writeInt32(3);
            return;
        }
        if (value === SuggestionProvider.AMO) {
            dataStream.writeInt32(4);
            return;
        }
        if (value === SuggestionProvider.YELP) {
            dataStream.writeInt32(5);
            return;
        }
        if (value === SuggestionProvider.MDN) {
            dataStream.writeInt32(6);
            return;
        }
        if (value === SuggestionProvider.WEATHER) {
            dataStream.writeInt32(7);
            return;
        }
        if (value === SuggestionProvider.AMP_MOBILE) {
            dataStream.writeInt32(8);
            return;
        }
        if (value === SuggestionProvider.FAKESPOT) {
            dataStream.writeInt32(9);
            return;
        }
        throw new UniFFITypeError("Unknown SuggestionProvider variant");
    }

    static computeSize(value) {
        return 4;
    }

    static checkType(value) {
      if (!Number.isInteger(value) || value < 1 || value > 9) {
          throw new UniFFITypeError(`${value} is not a valid value for SuggestionProvider`);
      }
    }
}


// Export the FFIConverter object to make external types work.
export class FfiConverterOptionali32 extends FfiConverterArrayBuffer {
    static checkType(value) {
        if (value !== undefined && value !== null) {
            FfiConverterI32.checkType(value)
        }
    }

    static read(dataStream) {
        const code = dataStream.readUint8(0);
        switch (code) {
            case 0:
                return null
            case 1:
                return FfiConverterI32.read(dataStream)
            default:
                throw UniFFIError(`Unexpected code: ${code}`);
        }
    }

    static write(dataStream, value) {
        if (value === null || value === undefined) {
            dataStream.writeUint8(0);
            return;
        }
        dataStream.writeUint8(1);
        FfiConverterI32.write(dataStream, value)
    }

    static computeSize(value) {
        if (value === null || value === undefined) {
            return 1;
        }
        return 1 + FfiConverterI32.computeSize(value)
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterOptionalstring extends FfiConverterArrayBuffer {
    static checkType(value) {
        if (value !== undefined && value !== null) {
            FfiConverterString.checkType(value)
        }
    }

    static read(dataStream) {
        const code = dataStream.readUint8(0);
        switch (code) {
            case 0:
                return null
            case 1:
                return FfiConverterString.read(dataStream)
            default:
                throw UniFFIError(`Unexpected code: ${code}`);
        }
    }

    static write(dataStream, value) {
        if (value === null || value === undefined) {
            dataStream.writeUint8(0);
            return;
        }
        dataStream.writeUint8(1);
        FfiConverterString.write(dataStream, value)
    }

    static computeSize(value) {
        if (value === null || value === undefined) {
            return 1;
        }
        return 1 + FfiConverterString.computeSize(value)
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterOptionalTypeInterruptKind extends FfiConverterArrayBuffer {
    static checkType(value) {
        if (value !== undefined && value !== null) {
            FfiConverterTypeInterruptKind.checkType(value)
        }
    }

    static read(dataStream) {
        const code = dataStream.readUint8(0);
        switch (code) {
            case 0:
                return null
            case 1:
                return FfiConverterTypeInterruptKind.read(dataStream)
            default:
                throw UniFFIError(`Unexpected code: ${code}`);
        }
    }

    static write(dataStream, value) {
        if (value === null || value === undefined) {
            dataStream.writeUint8(0);
            return;
        }
        dataStream.writeUint8(1);
        FfiConverterTypeInterruptKind.write(dataStream, value)
    }

    static computeSize(value) {
        if (value === null || value === undefined) {
            return 1;
        }
        return 1 + FfiConverterTypeInterruptKind.computeSize(value)
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterOptionalTypeSuggestProviderConfig extends FfiConverterArrayBuffer {
    static checkType(value) {
        if (value !== undefined && value !== null) {
            FfiConverterTypeSuggestProviderConfig.checkType(value)
        }
    }

    static read(dataStream) {
        const code = dataStream.readUint8(0);
        switch (code) {
            case 0:
                return null
            case 1:
                return FfiConverterTypeSuggestProviderConfig.read(dataStream)
            default:
                throw UniFFIError(`Unexpected code: ${code}`);
        }
    }

    static write(dataStream, value) {
        if (value === null || value === undefined) {
            dataStream.writeUint8(0);
            return;
        }
        dataStream.writeUint8(1);
        FfiConverterTypeSuggestProviderConfig.write(dataStream, value)
    }

    static computeSize(value) {
        if (value === null || value === undefined) {
            return 1;
        }
        return 1 + FfiConverterTypeSuggestProviderConfig.computeSize(value)
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterOptionalSequenceu8 extends FfiConverterArrayBuffer {
    static checkType(value) {
        if (value !== undefined && value !== null) {
            FfiConverterSequenceu8.checkType(value)
        }
    }

    static read(dataStream) {
        const code = dataStream.readUint8(0);
        switch (code) {
            case 0:
                return null
            case 1:
                return FfiConverterSequenceu8.read(dataStream)
            default:
                throw UniFFIError(`Unexpected code: ${code}`);
        }
    }

    static write(dataStream, value) {
        if (value === null || value === undefined) {
            dataStream.writeUint8(0);
            return;
        }
        dataStream.writeUint8(1);
        FfiConverterSequenceu8.write(dataStream, value)
    }

    static computeSize(value) {
        if (value === null || value === undefined) {
            return 1;
        }
        return 1 + FfiConverterSequenceu8.computeSize(value)
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterOptionalSequenceTypeSuggestionProvider extends FfiConverterArrayBuffer {
    static checkType(value) {
        if (value !== undefined && value !== null) {
            FfiConverterSequenceTypeSuggestionProvider.checkType(value)
        }
    }

    static read(dataStream) {
        const code = dataStream.readUint8(0);
        switch (code) {
            case 0:
                return null
            case 1:
                return FfiConverterSequenceTypeSuggestionProvider.read(dataStream)
            default:
                throw UniFFIError(`Unexpected code: ${code}`);
        }
    }

    static write(dataStream, value) {
        if (value === null || value === undefined) {
            dataStream.writeUint8(0);
            return;
        }
        dataStream.writeUint8(1);
        FfiConverterSequenceTypeSuggestionProvider.write(dataStream, value)
    }

    static computeSize(value) {
        if (value === null || value === undefined) {
            return 1;
        }
        return 1 + FfiConverterSequenceTypeSuggestionProvider.computeSize(value)
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterOptionalTypeRemoteSettingsConfig extends FfiConverterArrayBuffer {
    static checkType(value) {
        if (value !== undefined && value !== null) {
            FfiConverterTypeRemoteSettingsConfig.checkType(value)
        }
    }

    static read(dataStream) {
        const code = dataStream.readUint8(0);
        switch (code) {
            case 0:
                return null
            case 1:
                return FfiConverterTypeRemoteSettingsConfig.read(dataStream)
            default:
                throw UniFFIError(`Unexpected code: ${code}`);
        }
    }

    static write(dataStream, value) {
        if (value === null || value === undefined) {
            dataStream.writeUint8(0);
            return;
        }
        dataStream.writeUint8(1);
        FfiConverterTypeRemoteSettingsConfig.write(dataStream, value)
    }

    static computeSize(value) {
        if (value === null || value === undefined) {
            return 1;
        }
        return 1 + FfiConverterTypeRemoteSettingsConfig.computeSize(value)
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterSequenceu8 extends FfiConverterArrayBuffer {
    static read(dataStream) {
        const len = dataStream.readInt32();
        const arr = [];
        for (let i = 0; i < len; i++) {
            arr.push(FfiConverterU8.read(dataStream));
        }
        return arr;
    }

    static write(dataStream, value) {
        dataStream.writeInt32(value.length);
        value.forEach((innerValue) => {
            FfiConverterU8.write(dataStream, innerValue);
        })
    }

    static computeSize(value) {
        // The size of the length
        let size = 4;
        for (const innerValue of value) {
            size += FfiConverterU8.computeSize(innerValue);
        }
        return size;
    }

    static checkType(value) {
        if (!Array.isArray(value)) {
            throw new UniFFITypeError(`${value} is not an array`);
        }
        value.forEach((innerValue, idx) => {
            try {
                FfiConverterU8.checkType(innerValue);
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart(`[${idx}]`);
                }
                throw e;
            }
        })
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterSequenceTypeLabeledTimingSample extends FfiConverterArrayBuffer {
    static read(dataStream) {
        const len = dataStream.readInt32();
        const arr = [];
        for (let i = 0; i < len; i++) {
            arr.push(FfiConverterTypeLabeledTimingSample.read(dataStream));
        }
        return arr;
    }

    static write(dataStream, value) {
        dataStream.writeInt32(value.length);
        value.forEach((innerValue) => {
            FfiConverterTypeLabeledTimingSample.write(dataStream, innerValue);
        })
    }

    static computeSize(value) {
        // The size of the length
        let size = 4;
        for (const innerValue of value) {
            size += FfiConverterTypeLabeledTimingSample.computeSize(innerValue);
        }
        return size;
    }

    static checkType(value) {
        if (!Array.isArray(value)) {
            throw new UniFFITypeError(`${value} is not an array`);
        }
        value.forEach((innerValue, idx) => {
            try {
                FfiConverterTypeLabeledTimingSample.checkType(innerValue);
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart(`[${idx}]`);
                }
                throw e;
            }
        })
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterSequenceTypeSuggestion extends FfiConverterArrayBuffer {
    static read(dataStream) {
        const len = dataStream.readInt32();
        const arr = [];
        for (let i = 0; i < len; i++) {
            arr.push(FfiConverterTypeSuggestion.read(dataStream));
        }
        return arr;
    }

    static write(dataStream, value) {
        dataStream.writeInt32(value.length);
        value.forEach((innerValue) => {
            FfiConverterTypeSuggestion.write(dataStream, innerValue);
        })
    }

    static computeSize(value) {
        // The size of the length
        let size = 4;
        for (const innerValue of value) {
            size += FfiConverterTypeSuggestion.computeSize(innerValue);
        }
        return size;
    }

    static checkType(value) {
        if (!Array.isArray(value)) {
            throw new UniFFITypeError(`${value} is not an array`);
        }
        value.forEach((innerValue, idx) => {
            try {
                FfiConverterTypeSuggestion.checkType(innerValue);
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart(`[${idx}]`);
                }
                throw e;
            }
        })
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterSequenceTypeSuggestionProvider extends FfiConverterArrayBuffer {
    static read(dataStream) {
        const len = dataStream.readInt32();
        const arr = [];
        for (let i = 0; i < len; i++) {
            arr.push(FfiConverterTypeSuggestionProvider.read(dataStream));
        }
        return arr;
    }

    static write(dataStream, value) {
        dataStream.writeInt32(value.length);
        value.forEach((innerValue) => {
            FfiConverterTypeSuggestionProvider.write(dataStream, innerValue);
        })
    }

    static computeSize(value) {
        // The size of the length
        let size = 4;
        for (const innerValue of value) {
            size += FfiConverterTypeSuggestionProvider.computeSize(innerValue);
        }
        return size;
    }

    static checkType(value) {
        if (!Array.isArray(value)) {
            throw new UniFFITypeError(`${value} is not an array`);
        }
        value.forEach((innerValue, idx) => {
            try {
                FfiConverterTypeSuggestionProvider.checkType(innerValue);
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart(`[${idx}]`);
                }
                throw e;
            }
        })
    }
}

import {
  FfiConverterTypeRemoteSettingsConfig,
  RemoteSettingsConfig,
} from "resource://gre/modules/RustRemoteSettings.sys.mjs";

// Export the FFIConverter object to make external types work.
export { FfiConverterTypeRemoteSettingsConfig, RemoteSettingsConfig };

import {
  FfiConverterTypeRemoteSettingsServer,
  RemoteSettingsServer,
} from "resource://gre/modules/RustRemoteSettings.sys.mjs";

// Export the FFIConverter object to make external types work.
export { FfiConverterTypeRemoteSettingsServer, RemoteSettingsServer };





export function rawSuggestionUrlMatches(rawUrl,url) {

        const liftResult = (result) => FfiConverterBool.lift(result);
        const liftError = null;
        const functionCall = () => {
            try {
                FfiConverterString.checkType(rawUrl)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("rawUrl");
                }
                throw e;
            }
            try {
                FfiConverterString.checkType(url)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("url");
                }
                throw e;
            }
            return UniFFIScaffolding.callSync(
                31, // suggest:uniffi_suggest_fn_func_raw_suggestion_url_matches
                FfiConverterString.lower(rawUrl),
                FfiConverterString.lower(url),
            )
        }
        return handleRustResult(functionCall(), liftResult, liftError);
}
PK
!<�8����modules/UniFFI.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// This JS module contains shared functionality for the generated UniFFI JS
// code.

// TypeError for UniFFI calls
//
// This extends TypeError to add support for recording a nice description of
// the item that fails the type check. This is especially useful for invalid
// values nested in objects/arrays/maps, etc.
//
// To accomplish this, the FfiConverter.checkType methods of records, arrays,
// maps, etc. catch UniFFITypeError, call `addItemDescriptionPart()` with a
// string representing the child item, then re-raise the exception.  We then
// join all the parts together, in reverse order, to create item description
// strings like `foo.bar[123]["key"]`
export class UniFFITypeError extends TypeError {
  constructor(reason) {
    // our `message` getter isn't invoked in all cases, so we supply a default
    // to the `TypeError` constructor.
    super(reason);
    this.reason = reason;
    this.itemDescriptionParts = [];
  }

  addItemDescriptionPart(part) {
    this.itemDescriptionParts.push(part);
    this.updateMessage();
  }

  itemDescription() {
    const itemDescriptionParts = [...this.itemDescriptionParts];
    itemDescriptionParts.reverse();
    return itemDescriptionParts.join("");
  }

  updateMessage() {
    this.message = `${this.itemDescription()}: ${this.reason}`;
  }
}
PK
!<ې�H&�&�"modules/RustRemoteSettings.sys.mjs// This file was autogenerated by the `uniffi-bindgen-gecko-js` crate.
// Trust me, you don't want to mess with it!

import { UniFFITypeError } from "resource://gre/modules/UniFFI.sys.mjs";



// Objects intended to be used in the unit tests
export var UnitTestObjs = {};

// Write/Read data to/from an ArrayBuffer
class ArrayBufferDataStream {
    constructor(arrayBuffer) {
        this.dataView = new DataView(arrayBuffer);
        this.pos = 0;
    }

    readUint8() {
        let rv = this.dataView.getUint8(this.pos);
        this.pos += 1;
        return rv;
    }

    writeUint8(value) {
        this.dataView.setUint8(this.pos, value);
        this.pos += 1;
    }

    readUint16() {
        let rv = this.dataView.getUint16(this.pos);
        this.pos += 2;
        return rv;
    }

    writeUint16(value) {
        this.dataView.setUint16(this.pos, value);
        this.pos += 2;
    }

    readUint32() {
        let rv = this.dataView.getUint32(this.pos);
        this.pos += 4;
        return rv;
    }

    writeUint32(value) {
        this.dataView.setUint32(this.pos, value);
        this.pos += 4;
    }

    readUint64() {
        let rv = this.dataView.getBigUint64(this.pos);
        this.pos += 8;
        return Number(rv);
    }

    writeUint64(value) {
        this.dataView.setBigUint64(this.pos, BigInt(value));
        this.pos += 8;
    }


    readInt8() {
        let rv = this.dataView.getInt8(this.pos);
        this.pos += 1;
        return rv;
    }

    writeInt8(value) {
        this.dataView.setInt8(this.pos, value);
        this.pos += 1;
    }

    readInt16() {
        let rv = this.dataView.getInt16(this.pos);
        this.pos += 2;
        return rv;
    }

    writeInt16(value) {
        this.dataView.setInt16(this.pos, value);
        this.pos += 2;
    }

    readInt32() {
        let rv = this.dataView.getInt32(this.pos);
        this.pos += 4;
        return rv;
    }

    writeInt32(value) {
        this.dataView.setInt32(this.pos, value);
        this.pos += 4;
    }

    readInt64() {
        let rv = this.dataView.getBigInt64(this.pos);
        this.pos += 8;
        return Number(rv);
    }

    writeInt64(value) {
        this.dataView.setBigInt64(this.pos, BigInt(value));
        this.pos += 8;
    }

    readFloat32() {
        let rv = this.dataView.getFloat32(this.pos);
        this.pos += 4;
        return rv;
    }

    writeFloat32(value) {
        this.dataView.setFloat32(this.pos, value);
        this.pos += 4;
    }

    readFloat64() {
        let rv = this.dataView.getFloat64(this.pos);
        this.pos += 8;
        return rv;
    }

    writeFloat64(value) {
        this.dataView.setFloat64(this.pos, value);
        this.pos += 8;
    }


    writeString(value) {
      const encoder = new TextEncoder();
      // Note: in order to efficiently write this data, we first write the
      // string data, reserving 4 bytes for the size.
      const dest = new Uint8Array(this.dataView.buffer, this.pos + 4);
      const encodeResult = encoder.encodeInto(value, dest);
      if (encodeResult.read != value.length) {
        throw new UniFFIError(
            "writeString: out of space when writing to ArrayBuffer.  Did the computeSize() method returned the wrong result?"
        );
      }
      const size = encodeResult.written;
      // Next, go back and write the size before the string data
      this.dataView.setUint32(this.pos, size);
      // Finally, advance our position past both the size and string data
      this.pos += size + 4;
    }

    readString() {
      const decoder = new TextDecoder();
      const size = this.readUint32();
      const source = new Uint8Array(this.dataView.buffer, this.pos, size)
      const value = decoder.decode(source);
      this.pos += size;
      return value;
    }

    // Reads a RemoteSettings pointer from the data stream
    // UniFFI Pointers are **always** 8 bytes long. That is enforced
    // by the C++ and Rust Scaffolding code.
    readPointerRemoteSettings() {
        const pointerId = 1; // remote_settings:RemoteSettings
        const res = UniFFIScaffolding.readPointer(pointerId, this.dataView.buffer, this.pos);
        this.pos += 8;
        return res;
    }

    // Writes a RemoteSettings pointer into the data stream
    // UniFFI Pointers are **always** 8 bytes long. That is enforced
    // by the C++ and Rust Scaffolding code.
    writePointerRemoteSettings(value) {
        const pointerId = 1; // remote_settings:RemoteSettings
        UniFFIScaffolding.writePointer(pointerId, value, this.dataView.buffer, this.pos);
        this.pos += 8;
    }
    
}

function handleRustResult(result, liftCallback, liftErrCallback) {
    switch (result.code) {
        case "success":
            return liftCallback(result.data);

        case "error":
            throw liftErrCallback(result.data);

        case "internal-error":
            if (result.data) {
                throw new UniFFIInternalError(FfiConverterString.lift(result.data));
            } else {
                throw new UniFFIInternalError("Unknown error");
            }

        default:
            throw new UniFFIError(`Unexpected status code: ${result.code}`);
    }
}

class UniFFIError {
    constructor(message) {
        this.message = message;
    }

    toString() {
        return `UniFFIError: ${this.message}`
    }
}

class UniFFIInternalError extends UniFFIError {}

// Base class for FFI converters
class FfiConverter {
    // throw `UniFFITypeError` if a value to be converted has an invalid type
    static checkType(value) {
        if (value === undefined ) {
            throw new UniFFITypeError(`undefined`);
        }
        if (value === null ) {
            throw new UniFFITypeError(`null`);
        }
    }
}

// Base class for FFI converters that lift/lower by reading/writing to an ArrayBuffer
class FfiConverterArrayBuffer extends FfiConverter {
    static lift(buf) {
        return this.read(new ArrayBufferDataStream(buf));
    }

    static lower(value) {
        const buf = new ArrayBuffer(this.computeSize(value));
        const dataStream = new ArrayBufferDataStream(buf);
        this.write(dataStream, value);
        return buf;
    }
}

// Symbols that are used to ensure that Object constructors
// can only be used with a proper UniFFI pointer
const uniffiObjectPtr = Symbol("uniffiObjectPtr");
const constructUniffiObject = Symbol("constructUniffiObject");
UnitTestObjs.uniffiObjectPtr = uniffiObjectPtr;

// Export the FFIConverter object to make external types work.
export class FfiConverterU64 extends FfiConverter {
    static checkType(value) {
        super.checkType(value);
        if (!Number.isSafeInteger(value)) {
            throw new UniFFITypeError(`${value} exceeds the safe integer bounds`);
        }
        if (value < 0) {
            throw new UniFFITypeError(`${value} exceeds the U64 bounds`);
        }
    }
    static computeSize() {
        return 8;
    }
    static lift(value) {
        return value;
    }
    static lower(value) {
        return value;
    }
    static write(dataStream, value) {
        dataStream.writeUint64(value)
    }
    static read(dataStream) {
        return dataStream.readUint64()
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterBool extends FfiConverter {
    static computeSize() {
        return 1;
    }
    static lift(value) {
        return value == 1;
    }
    static lower(value) {
        if (value) {
            return 1;
        } else {
            return 0;
        }
    }
    static write(dataStream, value) {
        dataStream.writeUint8(this.lower(value))
    }
    static read(dataStream) {
        return this.lift(dataStream.readUint8())
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterString extends FfiConverter {
    static checkType(value) {
        super.checkType(value);
        if (typeof value !== "string") {
            throw new UniFFITypeError(`${value} is not a string`);
        }
    }

    static lift(buf) {
        const decoder = new TextDecoder();
        const utf8Arr = new Uint8Array(buf);
        return decoder.decode(utf8Arr);
    }
    static lower(value) {
        const encoder = new TextEncoder();
        return encoder.encode(value).buffer;
    }

    static write(dataStream, value) {
        dataStream.writeString(value);
    }

    static read(dataStream) {
        return dataStream.readString();
    }

    static computeSize(value) {
        const encoder = new TextEncoder();
        return 4 + encoder.encode(value).length
    }
}

export class RemoteSettings {
    // Use `init` to instantiate this class.
    // DO NOT USE THIS CONSTRUCTOR DIRECTLY
    constructor(opts) {
        if (!Object.prototype.hasOwnProperty.call(opts, constructUniffiObject)) {
            throw new UniFFIError("Attempting to construct an object using the JavaScript constructor directly" +
            "Please use a UDL defined constructor, or the init function for the primary constructor")
        }
        if (!opts[constructUniffiObject] instanceof UniFFIPointer) {
            throw new UniFFIError("Attempting to create a UniFFI object with a pointer that is not an instance of UniFFIPointer")
        }
        this[uniffiObjectPtr] = opts[constructUniffiObject];
    }
    /**
     * A constructor for RemoteSettings.
     * 
     * @returns { RemoteSettings }
     */
    static init(remoteSettingsConfig) {
        const liftResult = (result) => FfiConverterTypeRemoteSettings.lift(result);
        const liftError = (data) => FfiConverterTypeRemoteSettingsError.lift(data);
        const functionCall = () => {
            try {
                FfiConverterTypeRemoteSettingsConfig.checkType(remoteSettingsConfig)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("remoteSettingsConfig");
                }
                throw e;
            }
            return UniFFIScaffolding.callSync(
                8, // remote_settings:uniffi_remote_settings_fn_constructor_remotesettings_new
                FfiConverterTypeRemoteSettingsConfig.lower(remoteSettingsConfig),
            )
        }
        return handleRustResult(functionCall(), liftResult, liftError);}

    downloadAttachmentToPath(attachmentId,path) {
        const liftResult = (result) => undefined;
        const liftError = (data) => FfiConverterTypeRemoteSettingsError.lift(data);
        const functionCall = () => {
            try {
                FfiConverterString.checkType(attachmentId)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("attachmentId");
                }
                throw e;
            }
            try {
                FfiConverterString.checkType(path)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("path");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                9, // remote_settings:uniffi_remote_settings_fn_method_remotesettings_download_attachment_to_path
                FfiConverterTypeRemoteSettings.lower(this),
                FfiConverterString.lower(attachmentId),
                FfiConverterString.lower(path),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    getRecords() {
        const liftResult = (result) => FfiConverterTypeRemoteSettingsResponse.lift(result);
        const liftError = (data) => FfiConverterTypeRemoteSettingsError.lift(data);
        const functionCall = () => {
            return UniFFIScaffolding.callAsync(
                10, // remote_settings:uniffi_remote_settings_fn_method_remotesettings_get_records
                FfiConverterTypeRemoteSettings.lower(this),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    getRecordsSince(timestamp) {
        const liftResult = (result) => FfiConverterTypeRemoteSettingsResponse.lift(result);
        const liftError = (data) => FfiConverterTypeRemoteSettingsError.lift(data);
        const functionCall = () => {
            try {
                FfiConverterU64.checkType(timestamp)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("timestamp");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                11, // remote_settings:uniffi_remote_settings_fn_method_remotesettings_get_records_since
                FfiConverterTypeRemoteSettings.lower(this),
                FfiConverterU64.lower(timestamp),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeRemoteSettings extends FfiConverter {
    static lift(value) {
        const opts = {};
        opts[constructUniffiObject] = value;
        return new RemoteSettings(opts);
    }

    static lower(value) {
        const ptr = value[uniffiObjectPtr];
        if (!(ptr instanceof UniFFIPointer)) {
            throw new UniFFITypeError("Object is not a 'RemoteSettings' instance");
        }
        return ptr;
    }

    static read(dataStream) {
        return this.lift(dataStream.readPointerRemoteSettings());
    }

    static write(dataStream, value) {
        dataStream.writePointerRemoteSettings(value[uniffiObjectPtr]);
    }

    static computeSize(value) {
        return 8;
    }
}

export class Attachment {
    constructor({ filename, mimetype, location, hash, size } = {}) {
        try {
            FfiConverterString.checkType(filename)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("filename");
            }
            throw e;
        }
        try {
            FfiConverterString.checkType(mimetype)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("mimetype");
            }
            throw e;
        }
        try {
            FfiConverterString.checkType(location)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("location");
            }
            throw e;
        }
        try {
            FfiConverterString.checkType(hash)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("hash");
            }
            throw e;
        }
        try {
            FfiConverterU64.checkType(size)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("size");
            }
            throw e;
        }
        this.filename = filename;
        this.mimetype = mimetype;
        this.location = location;
        this.hash = hash;
        this.size = size;
    }
    equals(other) {
        return (
            this.filename == other.filename &&
            this.mimetype == other.mimetype &&
            this.location == other.location &&
            this.hash == other.hash &&
            this.size == other.size
        )
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeAttachment extends FfiConverterArrayBuffer {
    static read(dataStream) {
        return new Attachment({
            filename: FfiConverterString.read(dataStream),
            mimetype: FfiConverterString.read(dataStream),
            location: FfiConverterString.read(dataStream),
            hash: FfiConverterString.read(dataStream),
            size: FfiConverterU64.read(dataStream),
        });
    }
    static write(dataStream, value) {
        FfiConverterString.write(dataStream, value.filename);
        FfiConverterString.write(dataStream, value.mimetype);
        FfiConverterString.write(dataStream, value.location);
        FfiConverterString.write(dataStream, value.hash);
        FfiConverterU64.write(dataStream, value.size);
    }

    static computeSize(value) {
        let totalSize = 0;
        totalSize += FfiConverterString.computeSize(value.filename);
        totalSize += FfiConverterString.computeSize(value.mimetype);
        totalSize += FfiConverterString.computeSize(value.location);
        totalSize += FfiConverterString.computeSize(value.hash);
        totalSize += FfiConverterU64.computeSize(value.size);
        return totalSize
    }

    static checkType(value) {
        super.checkType(value);
        if (!(value instanceof Attachment)) {
            throw new UniFFITypeError(`Expected 'Attachment', found '${typeof value}'`);
        }
        try {
            FfiConverterString.checkType(value.filename);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".filename");
            }
            throw e;
        }
        try {
            FfiConverterString.checkType(value.mimetype);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".mimetype");
            }
            throw e;
        }
        try {
            FfiConverterString.checkType(value.location);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".location");
            }
            throw e;
        }
        try {
            FfiConverterString.checkType(value.hash);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".hash");
            }
            throw e;
        }
        try {
            FfiConverterU64.checkType(value.size);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".size");
            }
            throw e;
        }
    }
}

export class RemoteSettingsConfig {
    constructor({ collectionName, bucketName = null, serverUrl = null, server = null } = {}) {
        try {
            FfiConverterString.checkType(collectionName)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("collectionName");
            }
            throw e;
        }
        try {
            FfiConverterOptionalstring.checkType(bucketName)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("bucketName");
            }
            throw e;
        }
        try {
            FfiConverterOptionalstring.checkType(serverUrl)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("serverUrl");
            }
            throw e;
        }
        try {
            FfiConverterOptionalTypeRemoteSettingsServer.checkType(server)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("server");
            }
            throw e;
        }
        this.collectionName = collectionName;
        this.bucketName = bucketName;
        this.serverUrl = serverUrl;
        this.server = server;
    }
    equals(other) {
        return (
            this.collectionName == other.collectionName &&
            this.bucketName == other.bucketName &&
            this.serverUrl == other.serverUrl &&
            this.server == other.server
        )
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeRemoteSettingsConfig extends FfiConverterArrayBuffer {
    static read(dataStream) {
        return new RemoteSettingsConfig({
            collectionName: FfiConverterString.read(dataStream),
            bucketName: FfiConverterOptionalstring.read(dataStream),
            serverUrl: FfiConverterOptionalstring.read(dataStream),
            server: FfiConverterOptionalTypeRemoteSettingsServer.read(dataStream),
        });
    }
    static write(dataStream, value) {
        FfiConverterString.write(dataStream, value.collectionName);
        FfiConverterOptionalstring.write(dataStream, value.bucketName);
        FfiConverterOptionalstring.write(dataStream, value.serverUrl);
        FfiConverterOptionalTypeRemoteSettingsServer.write(dataStream, value.server);
    }

    static computeSize(value) {
        let totalSize = 0;
        totalSize += FfiConverterString.computeSize(value.collectionName);
        totalSize += FfiConverterOptionalstring.computeSize(value.bucketName);
        totalSize += FfiConverterOptionalstring.computeSize(value.serverUrl);
        totalSize += FfiConverterOptionalTypeRemoteSettingsServer.computeSize(value.server);
        return totalSize
    }

    static checkType(value) {
        super.checkType(value);
        if (!(value instanceof RemoteSettingsConfig)) {
            throw new UniFFITypeError(`Expected 'RemoteSettingsConfig', found '${typeof value}'`);
        }
        try {
            FfiConverterString.checkType(value.collectionName);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".collectionName");
            }
            throw e;
        }
        try {
            FfiConverterOptionalstring.checkType(value.bucketName);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".bucketName");
            }
            throw e;
        }
        try {
            FfiConverterOptionalstring.checkType(value.serverUrl);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".serverUrl");
            }
            throw e;
        }
        try {
            FfiConverterOptionalTypeRemoteSettingsServer.checkType(value.server);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".server");
            }
            throw e;
        }
    }
}

export class RemoteSettingsRecord {
    constructor({ id, lastModified, deleted, attachment, fields } = {}) {
        try {
            FfiConverterString.checkType(id)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("id");
            }
            throw e;
        }
        try {
            FfiConverterU64.checkType(lastModified)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("lastModified");
            }
            throw e;
        }
        try {
            FfiConverterBool.checkType(deleted)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("deleted");
            }
            throw e;
        }
        try {
            FfiConverterOptionalTypeAttachment.checkType(attachment)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("attachment");
            }
            throw e;
        }
        try {
            FfiConverterTypeRsJsonObject.checkType(fields)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("fields");
            }
            throw e;
        }
        this.id = id;
        this.lastModified = lastModified;
        this.deleted = deleted;
        this.attachment = attachment;
        this.fields = fields;
    }
    equals(other) {
        return (
            this.id == other.id &&
            this.lastModified == other.lastModified &&
            this.deleted == other.deleted &&
            this.attachment == other.attachment &&
            this.fields == other.fields
        )
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeRemoteSettingsRecord extends FfiConverterArrayBuffer {
    static read(dataStream) {
        return new RemoteSettingsRecord({
            id: FfiConverterString.read(dataStream),
            lastModified: FfiConverterU64.read(dataStream),
            deleted: FfiConverterBool.read(dataStream),
            attachment: FfiConverterOptionalTypeAttachment.read(dataStream),
            fields: FfiConverterTypeRsJsonObject.read(dataStream),
        });
    }
    static write(dataStream, value) {
        FfiConverterString.write(dataStream, value.id);
        FfiConverterU64.write(dataStream, value.lastModified);
        FfiConverterBool.write(dataStream, value.deleted);
        FfiConverterOptionalTypeAttachment.write(dataStream, value.attachment);
        FfiConverterTypeRsJsonObject.write(dataStream, value.fields);
    }

    static computeSize(value) {
        let totalSize = 0;
        totalSize += FfiConverterString.computeSize(value.id);
        totalSize += FfiConverterU64.computeSize(value.lastModified);
        totalSize += FfiConverterBool.computeSize(value.deleted);
        totalSize += FfiConverterOptionalTypeAttachment.computeSize(value.attachment);
        totalSize += FfiConverterTypeRsJsonObject.computeSize(value.fields);
        return totalSize
    }

    static checkType(value) {
        super.checkType(value);
        if (!(value instanceof RemoteSettingsRecord)) {
            throw new UniFFITypeError(`Expected 'RemoteSettingsRecord', found '${typeof value}'`);
        }
        try {
            FfiConverterString.checkType(value.id);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".id");
            }
            throw e;
        }
        try {
            FfiConverterU64.checkType(value.lastModified);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".lastModified");
            }
            throw e;
        }
        try {
            FfiConverterBool.checkType(value.deleted);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".deleted");
            }
            throw e;
        }
        try {
            FfiConverterOptionalTypeAttachment.checkType(value.attachment);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".attachment");
            }
            throw e;
        }
        try {
            FfiConverterTypeRsJsonObject.checkType(value.fields);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".fields");
            }
            throw e;
        }
    }
}

export class RemoteSettingsResponse {
    constructor({ records, lastModified } = {}) {
        try {
            FfiConverterSequenceTypeRemoteSettingsRecord.checkType(records)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("records");
            }
            throw e;
        }
        try {
            FfiConverterU64.checkType(lastModified)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("lastModified");
            }
            throw e;
        }
        this.records = records;
        this.lastModified = lastModified;
    }
    equals(other) {
        return (
            this.records == other.records &&
            this.lastModified == other.lastModified
        )
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeRemoteSettingsResponse extends FfiConverterArrayBuffer {
    static read(dataStream) {
        return new RemoteSettingsResponse({
            records: FfiConverterSequenceTypeRemoteSettingsRecord.read(dataStream),
            lastModified: FfiConverterU64.read(dataStream),
        });
    }
    static write(dataStream, value) {
        FfiConverterSequenceTypeRemoteSettingsRecord.write(dataStream, value.records);
        FfiConverterU64.write(dataStream, value.lastModified);
    }

    static computeSize(value) {
        let totalSize = 0;
        totalSize += FfiConverterSequenceTypeRemoteSettingsRecord.computeSize(value.records);
        totalSize += FfiConverterU64.computeSize(value.lastModified);
        return totalSize
    }

    static checkType(value) {
        super.checkType(value);
        if (!(value instanceof RemoteSettingsResponse)) {
            throw new UniFFITypeError(`Expected 'RemoteSettingsResponse', found '${typeof value}'`);
        }
        try {
            FfiConverterSequenceTypeRemoteSettingsRecord.checkType(value.records);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".records");
            }
            throw e;
        }
        try {
            FfiConverterU64.checkType(value.lastModified);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".lastModified");
            }
            throw e;
        }
    }
}




export class RemoteSettingsError extends Error {}


export class JsonError extends RemoteSettingsError {

    constructor(message, ...params) {
        super(...params);
        this.message = message;
    }
    toString() {
        return `JsonError: ${super.toString()}`
    }
}

export class FileError extends RemoteSettingsError {

    constructor(message, ...params) {
        super(...params);
        this.message = message;
    }
    toString() {
        return `FileError: ${super.toString()}`
    }
}

export class RequestError extends RemoteSettingsError {

    constructor(message, ...params) {
        super(...params);
        this.message = message;
    }
    toString() {
        return `RequestError: ${super.toString()}`
    }
}

export class UrlParsingError extends RemoteSettingsError {

    constructor(message, ...params) {
        super(...params);
        this.message = message;
    }
    toString() {
        return `UrlParsingError: ${super.toString()}`
    }
}

export class BackoffError extends RemoteSettingsError {

    constructor(message, ...params) {
        super(...params);
        this.message = message;
    }
    toString() {
        return `BackoffError: ${super.toString()}`
    }
}

export class ResponseError extends RemoteSettingsError {

    constructor(message, ...params) {
        super(...params);
        this.message = message;
    }
    toString() {
        return `ResponseError: ${super.toString()}`
    }
}

export class AttachmentsUnsupportedError extends RemoteSettingsError {

    constructor(message, ...params) {
        super(...params);
        this.message = message;
    }
    toString() {
        return `AttachmentsUnsupportedError: ${super.toString()}`
    }
}

export class ConfigError extends RemoteSettingsError {

    constructor(message, ...params) {
        super(...params);
        this.message = message;
    }
    toString() {
        return `ConfigError: ${super.toString()}`
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeRemoteSettingsError extends FfiConverterArrayBuffer {
    static read(dataStream) {
        switch (dataStream.readInt32()) {
            case 1:
                return new JsonError(FfiConverterString.read(dataStream));
            case 2:
                return new FileError(FfiConverterString.read(dataStream));
            case 3:
                return new RequestError(FfiConverterString.read(dataStream));
            case 4:
                return new UrlParsingError(FfiConverterString.read(dataStream));
            case 5:
                return new BackoffError(FfiConverterString.read(dataStream));
            case 6:
                return new ResponseError(FfiConverterString.read(dataStream));
            case 7:
                return new AttachmentsUnsupportedError(FfiConverterString.read(dataStream));
            case 8:
                return new ConfigError(FfiConverterString.read(dataStream));
            default:
                throw new UniFFITypeError("Unknown RemoteSettingsError variant");
        }
    }
    static computeSize(value) {
        // Size of the Int indicating the variant
        let totalSize = 4;
        if (value instanceof JsonError) {
            return totalSize;
        }
        if (value instanceof FileError) {
            return totalSize;
        }
        if (value instanceof RequestError) {
            return totalSize;
        }
        if (value instanceof UrlParsingError) {
            return totalSize;
        }
        if (value instanceof BackoffError) {
            return totalSize;
        }
        if (value instanceof ResponseError) {
            return totalSize;
        }
        if (value instanceof AttachmentsUnsupportedError) {
            return totalSize;
        }
        if (value instanceof ConfigError) {
            return totalSize;
        }
        throw new UniFFITypeError("Unknown RemoteSettingsError variant");
    }
    static write(dataStream, value) {
        if (value instanceof JsonError) {
            dataStream.writeInt32(1);
            return;
        }
        if (value instanceof FileError) {
            dataStream.writeInt32(2);
            return;
        }
        if (value instanceof RequestError) {
            dataStream.writeInt32(3);
            return;
        }
        if (value instanceof UrlParsingError) {
            dataStream.writeInt32(4);
            return;
        }
        if (value instanceof BackoffError) {
            dataStream.writeInt32(5);
            return;
        }
        if (value instanceof ResponseError) {
            dataStream.writeInt32(6);
            return;
        }
        if (value instanceof AttachmentsUnsupportedError) {
            dataStream.writeInt32(7);
            return;
        }
        if (value instanceof ConfigError) {
            dataStream.writeInt32(8);
            return;
        }
        throw new UniFFITypeError("Unknown RemoteSettingsError variant");
    }

    static errorClass = RemoteSettingsError;
}


export class RemoteSettingsServer {}
RemoteSettingsServer.Prod = class extends RemoteSettingsServer{
    constructor(
        ) {
            super();
        }
}
RemoteSettingsServer.Stage = class extends RemoteSettingsServer{
    constructor(
        ) {
            super();
        }
}
RemoteSettingsServer.Dev = class extends RemoteSettingsServer{
    constructor(
        ) {
            super();
        }
}
RemoteSettingsServer.Custom = class extends RemoteSettingsServer{
    constructor(
        url
        ) {
            super();
            this.url = url;
        }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeRemoteSettingsServer extends FfiConverterArrayBuffer {
    static read(dataStream) {
        switch (dataStream.readInt32()) {
            case 1:
                return new RemoteSettingsServer.Prod(
                    );
            case 2:
                return new RemoteSettingsServer.Stage(
                    );
            case 3:
                return new RemoteSettingsServer.Dev(
                    );
            case 4:
                return new RemoteSettingsServer.Custom(
                    FfiConverterString.read(dataStream)
                    );
            default:
                throw new UniFFITypeError("Unknown RemoteSettingsServer variant");
        }
    }

    static write(dataStream, value) {
        if (value instanceof RemoteSettingsServer.Prod) {
            dataStream.writeInt32(1);
            return;
        }
        if (value instanceof RemoteSettingsServer.Stage) {
            dataStream.writeInt32(2);
            return;
        }
        if (value instanceof RemoteSettingsServer.Dev) {
            dataStream.writeInt32(3);
            return;
        }
        if (value instanceof RemoteSettingsServer.Custom) {
            dataStream.writeInt32(4);
            FfiConverterString.write(dataStream, value.url);
            return;
        }
        throw new UniFFITypeError("Unknown RemoteSettingsServer variant");
    }

    static computeSize(value) {
        // Size of the Int indicating the variant
        let totalSize = 4;
        if (value instanceof RemoteSettingsServer.Prod) {
            return totalSize;
        }
        if (value instanceof RemoteSettingsServer.Stage) {
            return totalSize;
        }
        if (value instanceof RemoteSettingsServer.Dev) {
            return totalSize;
        }
        if (value instanceof RemoteSettingsServer.Custom) {
            totalSize += FfiConverterString.computeSize(value.url);
            return totalSize;
        }
        throw new UniFFITypeError("Unknown RemoteSettingsServer variant");
    }

    static checkType(value) {
      if (!(value instanceof RemoteSettingsServer)) {
        throw new UniFFITypeError(`${value} is not a subclass instance of RemoteSettingsServer`);
      }
    }
}


// Export the FFIConverter object to make external types work.
export class FfiConverterOptionalstring extends FfiConverterArrayBuffer {
    static checkType(value) {
        if (value !== undefined && value !== null) {
            FfiConverterString.checkType(value)
        }
    }

    static read(dataStream) {
        const code = dataStream.readUint8(0);
        switch (code) {
            case 0:
                return null
            case 1:
                return FfiConverterString.read(dataStream)
            default:
                throw UniFFIError(`Unexpected code: ${code}`);
        }
    }

    static write(dataStream, value) {
        if (value === null || value === undefined) {
            dataStream.writeUint8(0);
            return;
        }
        dataStream.writeUint8(1);
        FfiConverterString.write(dataStream, value)
    }

    static computeSize(value) {
        if (value === null || value === undefined) {
            return 1;
        }
        return 1 + FfiConverterString.computeSize(value)
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterOptionalTypeAttachment extends FfiConverterArrayBuffer {
    static checkType(value) {
        if (value !== undefined && value !== null) {
            FfiConverterTypeAttachment.checkType(value)
        }
    }

    static read(dataStream) {
        const code = dataStream.readUint8(0);
        switch (code) {
            case 0:
                return null
            case 1:
                return FfiConverterTypeAttachment.read(dataStream)
            default:
                throw UniFFIError(`Unexpected code: ${code}`);
        }
    }

    static write(dataStream, value) {
        if (value === null || value === undefined) {
            dataStream.writeUint8(0);
            return;
        }
        dataStream.writeUint8(1);
        FfiConverterTypeAttachment.write(dataStream, value)
    }

    static computeSize(value) {
        if (value === null || value === undefined) {
            return 1;
        }
        return 1 + FfiConverterTypeAttachment.computeSize(value)
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterOptionalTypeRemoteSettingsServer extends FfiConverterArrayBuffer {
    static checkType(value) {
        if (value !== undefined && value !== null) {
            FfiConverterTypeRemoteSettingsServer.checkType(value)
        }
    }

    static read(dataStream) {
        const code = dataStream.readUint8(0);
        switch (code) {
            case 0:
                return null
            case 1:
                return FfiConverterTypeRemoteSettingsServer.read(dataStream)
            default:
                throw UniFFIError(`Unexpected code: ${code}`);
        }
    }

    static write(dataStream, value) {
        if (value === null || value === undefined) {
            dataStream.writeUint8(0);
            return;
        }
        dataStream.writeUint8(1);
        FfiConverterTypeRemoteSettingsServer.write(dataStream, value)
    }

    static computeSize(value) {
        if (value === null || value === undefined) {
            return 1;
        }
        return 1 + FfiConverterTypeRemoteSettingsServer.computeSize(value)
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterSequenceTypeRemoteSettingsRecord extends FfiConverterArrayBuffer {
    static read(dataStream) {
        const len = dataStream.readInt32();
        const arr = [];
        for (let i = 0; i < len; i++) {
            arr.push(FfiConverterTypeRemoteSettingsRecord.read(dataStream));
        }
        return arr;
    }

    static write(dataStream, value) {
        dataStream.writeInt32(value.length);
        value.forEach((innerValue) => {
            FfiConverterTypeRemoteSettingsRecord.write(dataStream, innerValue);
        })
    }

    static computeSize(value) {
        // The size of the length
        let size = 4;
        for (const innerValue of value) {
            size += FfiConverterTypeRemoteSettingsRecord.computeSize(innerValue);
        }
        return size;
    }

    static checkType(value) {
        if (!Array.isArray(value)) {
            throw new UniFFITypeError(`${value} is not an array`);
        }
        value.forEach((innerValue, idx) => {
            try {
                FfiConverterTypeRemoteSettingsRecord.checkType(innerValue);
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart(`[${idx}]`);
                }
                throw e;
            }
        })
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeRsJsonObject extends FfiConverter {
    static lift(buf) {
        return FfiConverterString.lift(buf);    
    }
    
    static lower(buf) {
        return FfiConverterString.lower(buf);
    }
    
    static write(dataStream, value) {
        FfiConverterString.write(dataStream, value);
    } 
    
    static read(buf) {
        return FfiConverterString.read(buf);
    }
    
    static computeSize(value) {
        return FfiConverterString.computeSize(value);
    }
}
// TODO: We should also allow JS to customize the type eventually.




PK
!<i{���3�3'modules/ContentRelevancyManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  getFrecentRecentCombinedUrls:
    "resource://gre/modules/contentrelevancy/private/InputUtils.sys.mjs",
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
  RelevancyStore: "resource://gre/modules/RustRelevancy.sys.mjs",
  InterestVector: "resource://gre/modules/RustRelevancy.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "timerManager",
  "@mozilla.org/updates/timer-manager;1",
  "nsIUpdateTimerManager"
);

// Constants used by `nsIUpdateTimerManager` for a cross-session timer.
const TIMER_ID = "content-relevancy-timer";
const PREF_TIMER_LAST_UPDATE = `app.update.lastUpdateTime.${TIMER_ID}`;
const PREF_TIMER_INTERVAL = "toolkit.contentRelevancy.timerInterval";
const PREF_LOG_ENABLED = "toolkit.contentRelevancy.log";
// Set the timer interval to 1 day for validation.
const DEFAULT_TIMER_INTERVAL_SECONDS = 1 * 24 * 60 * 60;

// Default maximum input URLs to fetch from Places.
const DEFAULT_MAX_URLS = 100;
// Default minimal input URLs for clasification.
const DEFAULT_MIN_URLS = 0;

// File name of the relevancy database
const RELEVANCY_STORE_FILENAME = "content-relevancy.sqlite";

// Nimbus variables
const NIMBUS_VARIABLE_ENABLED = "enabled";
const NIMBUS_VARIABLE_MAX_INPUT_URLS = "maxInputUrls";
const NIMBUS_VARIABLE_MIN_INPUT_URLS = "minInputUrls";
const NIMBUS_VARIABLE_TIMER_INTERVAL = "timerInterval";
const NIMBUS_VARIABLE_INGEST_ENABLED = "ingestEnabled";

// Setup the `lazy.log` object.  This is called on startup and also whenever `PREF_LOG_ENABLED`
// changes.
function setupLogging() {
  ChromeUtils.defineLazyGetter(lazy, "log", () => {
    return console.createInstance({
      prefix: "ContentRelevancyManager",
      maxLogLevel: Services.prefs.getBoolPref(PREF_LOG_ENABLED, false)
        ? "Debug"
        : "Error",
    });
  });
}

setupLogging();

class RelevancyManager {
  get initialized() {
    return this.#initialized;
  }

  /**
   * Init the manager. An update timer is registered if the feature is enabled.
   * The pref observer is always set so we can toggle the feature without restarting
   * the browser.
   *
   * Note that this should be called once only. `#enable` and `#disable` can be
   * used to toggle the feature once the manager is initialized.
   */
  init() {
    if (this.initialized) {
      return;
    }

    lazy.log.info("Initializing the manager");

    this.#storeManager = new RustRelevancyStoreManager(this.#storePath);
    if (this.shouldEnable) {
      this.#enable();
    }

    Services.prefs.addObserver(PREF_LOG_ENABLED, this);
    this._nimbusUpdateCallback = this.#onNimbusUpdate.bind(this);
    // This will handle both Nimbus updates and pref changes.
    lazy.NimbusFeatures.contentRelevancy.onUpdate(this._nimbusUpdateCallback);
    this.#initialized = true;
  }

  uninit() {
    if (!this.initialized) {
      return;
    }

    lazy.log.info("Uninitializing the manager");

    lazy.NimbusFeatures.contentRelevancy.offUpdate(this._nimbusUpdateCallback);
    Services.prefs.removeObserver(PREF_LOG_ENABLED, this);
    this.#disable();
    this.#storeManager = null;

    this.#initialized = false;
  }

  /**
   * Determine whether the feature should be enabled based on prefs and Nimbus.
   */
  get shouldEnable() {
    return (
      lazy.NimbusFeatures.contentRelevancy.getVariable(
        NIMBUS_VARIABLE_ENABLED
      ) ?? false
    );
  }

  #startUpTimer() {
    // Log the last timer tick for debugging.
    const lastTick = Services.prefs.getIntPref(PREF_TIMER_LAST_UPDATE, 0);
    if (lastTick) {
      lazy.log.debug(
        `Last timer tick: ${lastTick}s (${
          Math.round(Date.now() / 1000) - lastTick
        })s ago`
      );
    } else {
      lazy.log.debug("Last timer tick: none");
    }

    const interval =
      lazy.NimbusFeatures.contentRelevancy.getVariable(
        NIMBUS_VARIABLE_TIMER_INTERVAL
      ) ??
      Services.prefs.getIntPref(
        PREF_TIMER_INTERVAL,
        DEFAULT_TIMER_INTERVAL_SECONDS
      );
    lazy.timerManager.registerTimer(
      TIMER_ID,
      this,
      interval,
      interval != 0 // Do not skip the first timer tick for a zero interval for testing
    );
  }

  get #storePath() {
    return PathUtils.join(
      Services.dirsvc.get("ProfLD", Ci.nsIFile).path,
      RELEVANCY_STORE_FILENAME
    );
  }

  #enable() {
    this.#storeManager.enable();
    this._shutdownBlocker = () => this.interrupt();
    // Interrupt sooner prior to the `profile-before-change` phase to allow
    // all the in-progress IOs to exit.
    lazy.AsyncShutdown.profileChangeTeardown.addBlocker(
      "ContentRelevancyManager: Interrupt IO operations on relevancy store",
      this._shutdownBlocker
    );
    this.#startUpTimer();
  }

  /**
   * The reciprocal of `#enable()`, ensure this is safe to call when you add
   * new disabling code here. It should be so even if `#enable()` hasn't been
   * called.
   */
  #disable() {
    if (this._shutdownBlocker) {
      lazy.AsyncShutdown.profileChangeTeardown.removeBlocker(
        this._shutdownBlocker
      );
      this._shutdownBlocker = null;
    }
    lazy.timerManager.unregisterTimer(TIMER_ID);
    this.#storeManager.disable();
  }

  #toggleFeature() {
    if (this.shouldEnable) {
      this.#enable();
    } else {
      this.#disable();
    }
  }

  /**
   * nsITimerCallback
   */
  notify() {
    lazy.log.info("Background job timer fired");
    this.#doClassification();
  }

  get isInProgress() {
    return this.#isInProgress;
  }

  /**
   * Perform classification based on browsing history.
   *
   * It will fetch up to `DEFAULT_MAX_URLS` (or the corresponding Nimbus value)
   * URLs from top frecent URLs and use most recent URLs as a fallback if the
   * former is insufficient. The returned URLs might be fewer than requested.
   *
   * The classification will not be performed if the total number of input URLs
   * is less than `DEFAULT_MIN_URLS` (or the corresponding Nimbus value).
   *
   * @param {object} options
   *   options.minUrlsForTest {number} A minimal URL count used only for testing.
   */
  async #doClassification(options = {}) {
    if (this.isInProgress) {
      lazy.log.info(
        "Another classification is in progress, aborting interest classification"
      );
      return;
    }

    // Set a flag indicating this classification. Ensure it's cleared upon early
    // exit points & success.
    this.#isInProgress = true;

    let timerId;

    try {
      lazy.log.info("Fetching input data for interest classification");

      const maxUrls =
        lazy.NimbusFeatures.contentRelevancy.getVariable(
          NIMBUS_VARIABLE_MAX_INPUT_URLS
        ) ?? DEFAULT_MAX_URLS;
      const minUrls =
        lazy.NimbusFeatures.contentRelevancy.getVariable(
          NIMBUS_VARIABLE_MIN_INPUT_URLS
        ) ??
        options.minUrlsForTest ??
        DEFAULT_MIN_URLS;
      const urls = await lazy.getFrecentRecentCombinedUrls(maxUrls);
      if (urls.length < minUrls) {
        lazy.log.info("Aborting interest classification: insufficient input");
        Glean.relevancyClassify.fail.record({ reason: "insufficient-input" });
        return;
      }

      lazy.log.info("Starting interest classification");
      timerId = Glean.relevancyClassify.duration.start();

      const interestVector = await this.#classifyUrls(urls);
      const sortedVector = Object.entries(interestVector).sort(
        ([, a], [, b]) => b - a // descending
      );
      lazy.log.info(`Classification results: ${JSON.stringify(sortedVector)}`);

      Glean.relevancyClassify.duration.stopAndAccumulate(timerId);
      Glean.relevancyClassify.succeed.record({
        input_size: urls.length,
        input_classified_size: sortedVector.reduce((acc, [, v]) => acc + v, 0),
        input_inconclusive_size: interestVector.inconclusive,
        output_interest_size: sortedVector.filter(([, v]) => v != 0).length,
        interest_top_1_hits: sortedVector[0][1],
        interest_top_2_hits: sortedVector[1][1],
        interest_top_3_hits: sortedVector[2][1],
      });
    } catch (error) {
      let reason;

      if (error instanceof StoreDisabledError) {
        lazy.log.error(
          "RustRelevancyStoreManager not enabled, aborting interest classification"
        );
        reason = "store-not-ready";
      } else {
        lazy.log.error("Classification error: " + (error.reason ?? error));
        reason = "component-errors";
      }
      Glean.relevancyClassify.fail.record({ reason });
      Glean.relevancyClassify.duration.cancel(timerId); // No error is recorded if `start` was not called.
    } finally {
      this.#isInProgress = false;
    }

    lazy.log.info("Finished interest classification");
  }

  /**
   * Interrupt all the IO operations on the relevancy store.
   */
  interrupt() {
    if (this.#storeManager.enabled) {
      try {
        lazy.log.debug(
          "Interrupting all the IO operations on the relevancy store"
        );
        this.#storeManager.store.interrupt();
      } catch (error) {
        lazy.log.error("Interrupt error: " + (error.reason ?? error));
      }
    }
  }

  /**
   * Exposed for testing.
   */
  async _test_doClassification(options = {}) {
    await this.#doClassification(options);
  }

  /**
   * Classify a list of URLs.
   *
   * If the nimbus feature is disabled, then this will succeed but return an empty InterestVector.
   *
   * @param {Array} urls
   *   An array of URLs.
   * @returns {InterestVector}
   *   An interest vector.
   * @throws {StoreDisabledError}
   *   Thrown when storeManager is disabled.
   * @throws {RelevancyAPIError}
   *   Thrown for other API errors on the store.
   */
  async #classifyUrls(urls) {
    lazy.log.info("Classification input: " + urls);
    let interestVector = new lazy.InterestVector({
      animals: 0,
      arts: 0,
      autos: 0,
      business: 0,
      career: 0,
      education: 0,
      fashion: 0,
      finance: 0,
      food: 0,
      government: 0,
      hobbies: 0,
      home: 0,
      news: 0,
      realEstate: 0,
      society: 0,
      sports: 0,
      tech: 0,
      travel: 0,
      inconclusive: 0,
    });

    if (
      lazy.NimbusFeatures.contentRelevancy.getVariable(
        NIMBUS_VARIABLE_INGEST_ENABLED
      ) ??
      false
    ) {
      interestVector = await this.#storeManager.store.ingest(urls);
    }

    return interestVector;
  }

  /**
   * Nimbus update listener.
   */
  #onNimbusUpdate(_event, _reason) {
    this.#toggleFeature();
  }

  /**
   * Preference observer
   */
  observe(_subj, topic, data) {
    switch (topic) {
      case "nsPref:changed":
        if (data === PREF_LOG_ENABLED) {
          // Call setupLogging again so that the logger will be re-created with the updated pref
          setupLogging();
        }
        break;
    }
  }

  // The `RustRelevancyStoreManager`
  #storeManager;

  // Whether or not the module is initialized.
  #initialized = false;

  // Whether or not there is an in-progress classification. Used to prevent
  // duplicate classification tasks.
  #isInProgress = false;
}

/**
 * Holds the RustRelevancyStore and handles enabling/disabling it.
 */
class RustRelevancyStoreManager {
  constructor(path, rustRelevancyStore = undefined) {
    lazy.log.info(`Initializing RelevancyStore: ${path}`);
    if (rustRelevancyStore === undefined) {
      rustRelevancyStore = lazy.RelevancyStore;
    }
    this.#store = rustRelevancyStore.init(path);
  }

  get store() {
    if (!this.enabled) {
      throw new StoreDisabledError();
    }
    return this.#store;
  }

  enable() {
    this.enabled = true;
  }

  disable() {
    // Calling `close()` makes the store release all resources.  In particular, it closes all
    // database connections until a read/write method is called.  The `store` method ensures that
    // this won't happen before `enable` is called.
    this.#store.close();
    this.enabled = false;
  }

  // The `RustRelevancy` store.
  #store;

  // Is the store enabled?
  enabled = false;
}

/**
 * Error raised when attempting to access a null store.
 */
class StoreDisabledError extends Error {
  constructor() {
    super("RustRelevancyStoreManager is disabled");
    this.name = "StoreDisabledError";
  }
}

export var ContentRelevancyManager = new RelevancyManager();

/**
 * Exposed to provide an easy way for users to run a single classification
 *
 * This allows users to test out the relevancy component without too much hassle:
 *
 *  - Enable the `toolkit.contentRelevancy.log` pref
 *  - Enable the `devtools.chrome.enabled` pref if it wasn't already
 *  - Open the browser console and enter:
 *    `ChromeUtils.importESModule("resource://gre/modules/ContentRelevancyManager.sys.mjs").classifyOnce()`
 */
export function classifyOnce() {
  ContentRelevancyManager._test_doClassification();
}

export var exposedForTesting = {
  RustRelevancyStoreManager,
};
PK
!<����modules/RustRelevancy.sys.mjs// This file was autogenerated by the `uniffi-bindgen-gecko-js` crate.
// Trust me, you don't want to mess with it!

import { UniFFITypeError } from "resource://gre/modules/UniFFI.sys.mjs";



// Objects intended to be used in the unit tests
export var UnitTestObjs = {};

// Write/Read data to/from an ArrayBuffer
class ArrayBufferDataStream {
    constructor(arrayBuffer) {
        this.dataView = new DataView(arrayBuffer);
        this.pos = 0;
    }

    readUint8() {
        let rv = this.dataView.getUint8(this.pos);
        this.pos += 1;
        return rv;
    }

    writeUint8(value) {
        this.dataView.setUint8(this.pos, value);
        this.pos += 1;
    }

    readUint16() {
        let rv = this.dataView.getUint16(this.pos);
        this.pos += 2;
        return rv;
    }

    writeUint16(value) {
        this.dataView.setUint16(this.pos, value);
        this.pos += 2;
    }

    readUint32() {
        let rv = this.dataView.getUint32(this.pos);
        this.pos += 4;
        return rv;
    }

    writeUint32(value) {
        this.dataView.setUint32(this.pos, value);
        this.pos += 4;
    }

    readUint64() {
        let rv = this.dataView.getBigUint64(this.pos);
        this.pos += 8;
        return Number(rv);
    }

    writeUint64(value) {
        this.dataView.setBigUint64(this.pos, BigInt(value));
        this.pos += 8;
    }


    readInt8() {
        let rv = this.dataView.getInt8(this.pos);
        this.pos += 1;
        return rv;
    }

    writeInt8(value) {
        this.dataView.setInt8(this.pos, value);
        this.pos += 1;
    }

    readInt16() {
        let rv = this.dataView.getInt16(this.pos);
        this.pos += 2;
        return rv;
    }

    writeInt16(value) {
        this.dataView.setInt16(this.pos, value);
        this.pos += 2;
    }

    readInt32() {
        let rv = this.dataView.getInt32(this.pos);
        this.pos += 4;
        return rv;
    }

    writeInt32(value) {
        this.dataView.setInt32(this.pos, value);
        this.pos += 4;
    }

    readInt64() {
        let rv = this.dataView.getBigInt64(this.pos);
        this.pos += 8;
        return Number(rv);
    }

    writeInt64(value) {
        this.dataView.setBigInt64(this.pos, BigInt(value));
        this.pos += 8;
    }

    readFloat32() {
        let rv = this.dataView.getFloat32(this.pos);
        this.pos += 4;
        return rv;
    }

    writeFloat32(value) {
        this.dataView.setFloat32(this.pos, value);
        this.pos += 4;
    }

    readFloat64() {
        let rv = this.dataView.getFloat64(this.pos);
        this.pos += 8;
        return rv;
    }

    writeFloat64(value) {
        this.dataView.setFloat64(this.pos, value);
        this.pos += 8;
    }


    writeString(value) {
      const encoder = new TextEncoder();
      // Note: in order to efficiently write this data, we first write the
      // string data, reserving 4 bytes for the size.
      const dest = new Uint8Array(this.dataView.buffer, this.pos + 4);
      const encodeResult = encoder.encodeInto(value, dest);
      if (encodeResult.read != value.length) {
        throw new UniFFIError(
            "writeString: out of space when writing to ArrayBuffer.  Did the computeSize() method returned the wrong result?"
        );
      }
      const size = encodeResult.written;
      // Next, go back and write the size before the string data
      this.dataView.setUint32(this.pos, size);
      // Finally, advance our position past both the size and string data
      this.pos += size + 4;
    }

    readString() {
      const decoder = new TextDecoder();
      const size = this.readUint32();
      const source = new Uint8Array(this.dataView.buffer, this.pos, size)
      const value = decoder.decode(source);
      this.pos += size;
      return value;
    }

    // Reads a RelevancyStore pointer from the data stream
    // UniFFI Pointers are **always** 8 bytes long. That is enforced
    // by the C++ and Rust Scaffolding code.
    readPointerRelevancyStore() {
        const pointerId = 0; // relevancy:RelevancyStore
        const res = UniFFIScaffolding.readPointer(pointerId, this.dataView.buffer, this.pos);
        this.pos += 8;
        return res;
    }

    // Writes a RelevancyStore pointer into the data stream
    // UniFFI Pointers are **always** 8 bytes long. That is enforced
    // by the C++ and Rust Scaffolding code.
    writePointerRelevancyStore(value) {
        const pointerId = 0; // relevancy:RelevancyStore
        UniFFIScaffolding.writePointer(pointerId, value, this.dataView.buffer, this.pos);
        this.pos += 8;
    }
    
}

function handleRustResult(result, liftCallback, liftErrCallback) {
    switch (result.code) {
        case "success":
            return liftCallback(result.data);

        case "error":
            throw liftErrCallback(result.data);

        case "internal-error":
            if (result.data) {
                throw new UniFFIInternalError(FfiConverterString.lift(result.data));
            } else {
                throw new UniFFIInternalError("Unknown error");
            }

        default:
            throw new UniFFIError(`Unexpected status code: ${result.code}`);
    }
}

class UniFFIError {
    constructor(message) {
        this.message = message;
    }

    toString() {
        return `UniFFIError: ${this.message}`
    }
}

class UniFFIInternalError extends UniFFIError {}

// Base class for FFI converters
class FfiConverter {
    // throw `UniFFITypeError` if a value to be converted has an invalid type
    static checkType(value) {
        if (value === undefined ) {
            throw new UniFFITypeError(`undefined`);
        }
        if (value === null ) {
            throw new UniFFITypeError(`null`);
        }
    }
}

// Base class for FFI converters that lift/lower by reading/writing to an ArrayBuffer
class FfiConverterArrayBuffer extends FfiConverter {
    static lift(buf) {
        return this.read(new ArrayBufferDataStream(buf));
    }

    static lower(value) {
        const buf = new ArrayBuffer(this.computeSize(value));
        const dataStream = new ArrayBufferDataStream(buf);
        this.write(dataStream, value);
        return buf;
    }
}

// Symbols that are used to ensure that Object constructors
// can only be used with a proper UniFFI pointer
const uniffiObjectPtr = Symbol("uniffiObjectPtr");
const constructUniffiObject = Symbol("constructUniffiObject");
UnitTestObjs.uniffiObjectPtr = uniffiObjectPtr;

// Export the FFIConverter object to make external types work.
export class FfiConverterU32 extends FfiConverter {
    static checkType(value) {
        super.checkType(value);
        if (!Number.isInteger(value)) {
            throw new UniFFITypeError(`${value} is not an integer`);
        }
        if (value < 0 || value > 4294967295) {
            throw new UniFFITypeError(`${value} exceeds the U32 bounds`);
        }
    }
    static computeSize() {
        return 4;
    }
    static lift(value) {
        return value;
    }
    static lower(value) {
        return value;
    }
    static write(dataStream, value) {
        dataStream.writeUint32(value)
    }
    static read(dataStream) {
        return dataStream.readUint32()
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterString extends FfiConverter {
    static checkType(value) {
        super.checkType(value);
        if (typeof value !== "string") {
            throw new UniFFITypeError(`${value} is not a string`);
        }
    }

    static lift(buf) {
        const decoder = new TextDecoder();
        const utf8Arr = new Uint8Array(buf);
        return decoder.decode(utf8Arr);
    }
    static lower(value) {
        const encoder = new TextEncoder();
        return encoder.encode(value).buffer;
    }

    static write(dataStream, value) {
        dataStream.writeString(value);
    }

    static read(dataStream) {
        return dataStream.readString();
    }

    static computeSize(value) {
        const encoder = new TextEncoder();
        return 4 + encoder.encode(value).length
    }
}

export class RelevancyStore {
    // Use `init` to instantiate this class.
    // DO NOT USE THIS CONSTRUCTOR DIRECTLY
    constructor(opts) {
        if (!Object.prototype.hasOwnProperty.call(opts, constructUniffiObject)) {
            throw new UniFFIError("Attempting to construct an object using the JavaScript constructor directly" +
            "Please use a UDL defined constructor, or the init function for the primary constructor")
        }
        if (!opts[constructUniffiObject] instanceof UniFFIPointer) {
            throw new UniFFIError("Attempting to create a UniFFI object with a pointer that is not an instance of UniFFIPointer")
        }
        this[uniffiObjectPtr] = opts[constructUniffiObject];
    }
    /**
     * A constructor for RelevancyStore.
     * 
     * @returns { RelevancyStore }
     */
    static init(dbpath) {
        const liftResult = (result) => FfiConverterTypeRelevancyStore.lift(result);
        const liftError = null;
        const functionCall = () => {
            try {
                FfiConverterString.checkType(dbpath)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("dbpath");
                }
                throw e;
            }
            return UniFFIScaffolding.callSync(
                1, // relevancy:uniffi_relevancy_fn_constructor_relevancystore_new
                FfiConverterString.lower(dbpath),
            )
        }
        return handleRustResult(functionCall(), liftResult, liftError);}

    calculateMetrics() {
        const liftResult = (result) => FfiConverterTypeInterestMetrics.lift(result);
        const liftError = (data) => FfiConverterTypeRelevancyApiError.lift(data);
        const functionCall = () => {
            return UniFFIScaffolding.callAsync(
                2, // relevancy:uniffi_relevancy_fn_method_relevancystore_calculate_metrics
                FfiConverterTypeRelevancyStore.lower(this),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    close() {
        const liftResult = (result) => undefined;
        const liftError = null;
        const functionCall = () => {
            return UniFFIScaffolding.callSync(
                3, // relevancy:uniffi_relevancy_fn_method_relevancystore_close
                FfiConverterTypeRelevancyStore.lower(this),
            )
        }
        return handleRustResult(functionCall(), liftResult, liftError);
    }

    ingest(topUrls) {
        const liftResult = (result) => FfiConverterTypeInterestVector.lift(result);
        const liftError = (data) => FfiConverterTypeRelevancyApiError.lift(data);
        const functionCall = () => {
            try {
                FfiConverterSequencestring.checkType(topUrls)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("topUrls");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                4, // relevancy:uniffi_relevancy_fn_method_relevancystore_ingest
                FfiConverterTypeRelevancyStore.lower(this),
                FfiConverterSequencestring.lower(topUrls),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    interrupt() {
        const liftResult = (result) => undefined;
        const liftError = null;
        const functionCall = () => {
            return UniFFIScaffolding.callSync(
                5, // relevancy:uniffi_relevancy_fn_method_relevancystore_interrupt
                FfiConverterTypeRelevancyStore.lower(this),
            )
        }
        return handleRustResult(functionCall(), liftResult, liftError);
    }

    userInterestVector() {
        const liftResult = (result) => FfiConverterTypeInterestVector.lift(result);
        const liftError = (data) => FfiConverterTypeRelevancyApiError.lift(data);
        const functionCall = () => {
            return UniFFIScaffolding.callAsync(
                6, // relevancy:uniffi_relevancy_fn_method_relevancystore_user_interest_vector
                FfiConverterTypeRelevancyStore.lower(this),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeRelevancyStore extends FfiConverter {
    static lift(value) {
        const opts = {};
        opts[constructUniffiObject] = value;
        return new RelevancyStore(opts);
    }

    static lower(value) {
        const ptr = value[uniffiObjectPtr];
        if (!(ptr instanceof UniFFIPointer)) {
            throw new UniFFITypeError("Object is not a 'RelevancyStore' instance");
        }
        return ptr;
    }

    static read(dataStream) {
        return this.lift(dataStream.readPointerRelevancyStore());
    }

    static write(dataStream, value) {
        dataStream.writePointerRelevancyStore(value[uniffiObjectPtr]);
    }

    static computeSize(value) {
        return 8;
    }
}

export class InterestMetrics {
    constructor({ topSingleInterestSimilarity, top2interestSimilarity, top3interestSimilarity } = {}) {
        try {
            FfiConverterU32.checkType(topSingleInterestSimilarity)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("topSingleInterestSimilarity");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(top2interestSimilarity)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("top2interestSimilarity");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(top3interestSimilarity)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("top3interestSimilarity");
            }
            throw e;
        }
        this.topSingleInterestSimilarity = topSingleInterestSimilarity;
        this.top2interestSimilarity = top2interestSimilarity;
        this.top3interestSimilarity = top3interestSimilarity;
    }
    equals(other) {
        return (
            this.topSingleInterestSimilarity == other.topSingleInterestSimilarity &&
            this.top2interestSimilarity == other.top2interestSimilarity &&
            this.top3interestSimilarity == other.top3interestSimilarity
        )
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeInterestMetrics extends FfiConverterArrayBuffer {
    static read(dataStream) {
        return new InterestMetrics({
            topSingleInterestSimilarity: FfiConverterU32.read(dataStream),
            top2interestSimilarity: FfiConverterU32.read(dataStream),
            top3interestSimilarity: FfiConverterU32.read(dataStream),
        });
    }
    static write(dataStream, value) {
        FfiConverterU32.write(dataStream, value.topSingleInterestSimilarity);
        FfiConverterU32.write(dataStream, value.top2interestSimilarity);
        FfiConverterU32.write(dataStream, value.top3interestSimilarity);
    }

    static computeSize(value) {
        let totalSize = 0;
        totalSize += FfiConverterU32.computeSize(value.topSingleInterestSimilarity);
        totalSize += FfiConverterU32.computeSize(value.top2interestSimilarity);
        totalSize += FfiConverterU32.computeSize(value.top3interestSimilarity);
        return totalSize
    }

    static checkType(value) {
        super.checkType(value);
        if (!(value instanceof InterestMetrics)) {
            throw new UniFFITypeError(`Expected 'InterestMetrics', found '${typeof value}'`);
        }
        try {
            FfiConverterU32.checkType(value.topSingleInterestSimilarity);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".topSingleInterestSimilarity");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.top2interestSimilarity);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".top2interestSimilarity");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.top3interestSimilarity);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".top3interestSimilarity");
            }
            throw e;
        }
    }
}

export class InterestVector {
    constructor({ animals, arts, autos, business, career, education, fashion, finance, food, government, hobbies, home, news, realEstate, society, sports, tech, travel, inconclusive } = {}) {
        try {
            FfiConverterU32.checkType(animals)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("animals");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(arts)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("arts");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(autos)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("autos");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(business)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("business");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(career)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("career");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(education)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("education");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(fashion)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("fashion");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(finance)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("finance");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(food)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("food");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(government)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("government");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(hobbies)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("hobbies");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(home)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("home");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(news)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("news");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(realEstate)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("realEstate");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(society)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("society");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(sports)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("sports");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(tech)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("tech");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(travel)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("travel");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(inconclusive)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("inconclusive");
            }
            throw e;
        }
        this.animals = animals;
        this.arts = arts;
        this.autos = autos;
        this.business = business;
        this.career = career;
        this.education = education;
        this.fashion = fashion;
        this.finance = finance;
        this.food = food;
        this.government = government;
        this.hobbies = hobbies;
        this.home = home;
        this.news = news;
        this.realEstate = realEstate;
        this.society = society;
        this.sports = sports;
        this.tech = tech;
        this.travel = travel;
        this.inconclusive = inconclusive;
    }
    equals(other) {
        return (
            this.animals == other.animals &&
            this.arts == other.arts &&
            this.autos == other.autos &&
            this.business == other.business &&
            this.career == other.career &&
            this.education == other.education &&
            this.fashion == other.fashion &&
            this.finance == other.finance &&
            this.food == other.food &&
            this.government == other.government &&
            this.hobbies == other.hobbies &&
            this.home == other.home &&
            this.news == other.news &&
            this.realEstate == other.realEstate &&
            this.society == other.society &&
            this.sports == other.sports &&
            this.tech == other.tech &&
            this.travel == other.travel &&
            this.inconclusive == other.inconclusive
        )
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeInterestVector extends FfiConverterArrayBuffer {
    static read(dataStream) {
        return new InterestVector({
            animals: FfiConverterU32.read(dataStream),
            arts: FfiConverterU32.read(dataStream),
            autos: FfiConverterU32.read(dataStream),
            business: FfiConverterU32.read(dataStream),
            career: FfiConverterU32.read(dataStream),
            education: FfiConverterU32.read(dataStream),
            fashion: FfiConverterU32.read(dataStream),
            finance: FfiConverterU32.read(dataStream),
            food: FfiConverterU32.read(dataStream),
            government: FfiConverterU32.read(dataStream),
            hobbies: FfiConverterU32.read(dataStream),
            home: FfiConverterU32.read(dataStream),
            news: FfiConverterU32.read(dataStream),
            realEstate: FfiConverterU32.read(dataStream),
            society: FfiConverterU32.read(dataStream),
            sports: FfiConverterU32.read(dataStream),
            tech: FfiConverterU32.read(dataStream),
            travel: FfiConverterU32.read(dataStream),
            inconclusive: FfiConverterU32.read(dataStream),
        });
    }
    static write(dataStream, value) {
        FfiConverterU32.write(dataStream, value.animals);
        FfiConverterU32.write(dataStream, value.arts);
        FfiConverterU32.write(dataStream, value.autos);
        FfiConverterU32.write(dataStream, value.business);
        FfiConverterU32.write(dataStream, value.career);
        FfiConverterU32.write(dataStream, value.education);
        FfiConverterU32.write(dataStream, value.fashion);
        FfiConverterU32.write(dataStream, value.finance);
        FfiConverterU32.write(dataStream, value.food);
        FfiConverterU32.write(dataStream, value.government);
        FfiConverterU32.write(dataStream, value.hobbies);
        FfiConverterU32.write(dataStream, value.home);
        FfiConverterU32.write(dataStream, value.news);
        FfiConverterU32.write(dataStream, value.realEstate);
        FfiConverterU32.write(dataStream, value.society);
        FfiConverterU32.write(dataStream, value.sports);
        FfiConverterU32.write(dataStream, value.tech);
        FfiConverterU32.write(dataStream, value.travel);
        FfiConverterU32.write(dataStream, value.inconclusive);
    }

    static computeSize(value) {
        let totalSize = 0;
        totalSize += FfiConverterU32.computeSize(value.animals);
        totalSize += FfiConverterU32.computeSize(value.arts);
        totalSize += FfiConverterU32.computeSize(value.autos);
        totalSize += FfiConverterU32.computeSize(value.business);
        totalSize += FfiConverterU32.computeSize(value.career);
        totalSize += FfiConverterU32.computeSize(value.education);
        totalSize += FfiConverterU32.computeSize(value.fashion);
        totalSize += FfiConverterU32.computeSize(value.finance);
        totalSize += FfiConverterU32.computeSize(value.food);
        totalSize += FfiConverterU32.computeSize(value.government);
        totalSize += FfiConverterU32.computeSize(value.hobbies);
        totalSize += FfiConverterU32.computeSize(value.home);
        totalSize += FfiConverterU32.computeSize(value.news);
        totalSize += FfiConverterU32.computeSize(value.realEstate);
        totalSize += FfiConverterU32.computeSize(value.society);
        totalSize += FfiConverterU32.computeSize(value.sports);
        totalSize += FfiConverterU32.computeSize(value.tech);
        totalSize += FfiConverterU32.computeSize(value.travel);
        totalSize += FfiConverterU32.computeSize(value.inconclusive);
        return totalSize
    }

    static checkType(value) {
        super.checkType(value);
        if (!(value instanceof InterestVector)) {
            throw new UniFFITypeError(`Expected 'InterestVector', found '${typeof value}'`);
        }
        try {
            FfiConverterU32.checkType(value.animals);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".animals");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.arts);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".arts");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.autos);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".autos");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.business);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".business");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.career);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".career");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.education);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".education");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.fashion);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".fashion");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.finance);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".finance");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.food);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".food");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.government);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".government");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.hobbies);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".hobbies");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.home);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".home");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.news);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".news");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.realEstate);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".realEstate");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.society);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".society");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.sports);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".sports");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.tech);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".tech");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.travel);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".travel");
            }
            throw e;
        }
        try {
            FfiConverterU32.checkType(value.inconclusive);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".inconclusive");
            }
            throw e;
        }
    }
}


export const Interest = {
    ANIMALS: 1,
    ARTS: 2,
    AUTOS: 3,
    BUSINESS: 4,
    CAREER: 5,
    EDUCATION: 6,
    FASHION: 7,
    FINANCE: 8,
    FOOD: 9,
    GOVERNMENT: 10,
    HOBBIES: 11,
    HOME: 12,
    NEWS: 13,
    REAL_ESTATE: 14,
    SOCIETY: 15,
    SPORTS: 16,
    TECH: 17,
    TRAVEL: 18,
    INCONCLUSIVE: 19,
};

Object.freeze(Interest);
// Export the FFIConverter object to make external types work.
export class FfiConverterTypeInterest extends FfiConverterArrayBuffer {
    static read(dataStream) {
        switch (dataStream.readInt32()) {
            case 1:
                return Interest.ANIMALS
            case 2:
                return Interest.ARTS
            case 3:
                return Interest.AUTOS
            case 4:
                return Interest.BUSINESS
            case 5:
                return Interest.CAREER
            case 6:
                return Interest.EDUCATION
            case 7:
                return Interest.FASHION
            case 8:
                return Interest.FINANCE
            case 9:
                return Interest.FOOD
            case 10:
                return Interest.GOVERNMENT
            case 11:
                return Interest.HOBBIES
            case 12:
                return Interest.HOME
            case 13:
                return Interest.NEWS
            case 14:
                return Interest.REAL_ESTATE
            case 15:
                return Interest.SOCIETY
            case 16:
                return Interest.SPORTS
            case 17:
                return Interest.TECH
            case 18:
                return Interest.TRAVEL
            case 19:
                return Interest.INCONCLUSIVE
            default:
                throw new UniFFITypeError("Unknown Interest variant");
        }
    }

    static write(dataStream, value) {
        if (value === Interest.ANIMALS) {
            dataStream.writeInt32(1);
            return;
        }
        if (value === Interest.ARTS) {
            dataStream.writeInt32(2);
            return;
        }
        if (value === Interest.AUTOS) {
            dataStream.writeInt32(3);
            return;
        }
        if (value === Interest.BUSINESS) {
            dataStream.writeInt32(4);
            return;
        }
        if (value === Interest.CAREER) {
            dataStream.writeInt32(5);
            return;
        }
        if (value === Interest.EDUCATION) {
            dataStream.writeInt32(6);
            return;
        }
        if (value === Interest.FASHION) {
            dataStream.writeInt32(7);
            return;
        }
        if (value === Interest.FINANCE) {
            dataStream.writeInt32(8);
            return;
        }
        if (value === Interest.FOOD) {
            dataStream.writeInt32(9);
            return;
        }
        if (value === Interest.GOVERNMENT) {
            dataStream.writeInt32(10);
            return;
        }
        if (value === Interest.HOBBIES) {
            dataStream.writeInt32(11);
            return;
        }
        if (value === Interest.HOME) {
            dataStream.writeInt32(12);
            return;
        }
        if (value === Interest.NEWS) {
            dataStream.writeInt32(13);
            return;
        }
        if (value === Interest.REAL_ESTATE) {
            dataStream.writeInt32(14);
            return;
        }
        if (value === Interest.SOCIETY) {
            dataStream.writeInt32(15);
            return;
        }
        if (value === Interest.SPORTS) {
            dataStream.writeInt32(16);
            return;
        }
        if (value === Interest.TECH) {
            dataStream.writeInt32(17);
            return;
        }
        if (value === Interest.TRAVEL) {
            dataStream.writeInt32(18);
            return;
        }
        if (value === Interest.INCONCLUSIVE) {
            dataStream.writeInt32(19);
            return;
        }
        throw new UniFFITypeError("Unknown Interest variant");
    }

    static computeSize(value) {
        return 4;
    }

    static checkType(value) {
      if (!Number.isInteger(value) || value < 1 || value > 19) {
          throw new UniFFITypeError(`${value} is not a valid value for Interest`);
      }
    }
}





export class RelevancyApiError extends Error {}


export class Unexpected extends RelevancyApiError {

    constructor(
        reason,
        ...params
    ) {
        const message = `reason: ${ reason }`;
        super(message, ...params);
        this.reason = reason;
    }
    toString() {
        return `Unexpected: ${super.toString()}`
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeRelevancyApiError extends FfiConverterArrayBuffer {
    static read(dataStream) {
        switch (dataStream.readInt32()) {
            case 1:
                return new Unexpected(
                    FfiConverterString.read(dataStream)
                    );
            default:
                throw new UniFFITypeError("Unknown RelevancyApiError variant");
        }
    }
    static computeSize(value) {
        // Size of the Int indicating the variant
        let totalSize = 4;
        if (value instanceof Unexpected) {
            totalSize += FfiConverterString.computeSize(value.reason);
            return totalSize;
        }
        throw new UniFFITypeError("Unknown RelevancyApiError variant");
    }
    static write(dataStream, value) {
        if (value instanceof Unexpected) {
            dataStream.writeInt32(1);
            FfiConverterString.write(dataStream, value.reason);
            return;
        }
        throw new UniFFITypeError("Unknown RelevancyApiError variant");
    }

    static errorClass = RelevancyApiError;
}

// Export the FFIConverter object to make external types work.
export class FfiConverterSequencestring extends FfiConverterArrayBuffer {
    static read(dataStream) {
        const len = dataStream.readInt32();
        const arr = [];
        for (let i = 0; i < len; i++) {
            arr.push(FfiConverterString.read(dataStream));
        }
        return arr;
    }

    static write(dataStream, value) {
        dataStream.writeInt32(value.length);
        value.forEach((innerValue) => {
            FfiConverterString.write(dataStream, innerValue);
        })
    }

    static computeSize(value) {
        // The size of the length
        let size = 4;
        for (const innerValue of value) {
            size += FfiConverterString.computeSize(innerValue);
        }
        return size;
    }

    static checkType(value) {
        if (!Array.isArray(value)) {
            throw new UniFFITypeError(`${value} is not an array`);
        }
        value.forEach((innerValue, idx) => {
            try {
                FfiConverterString.checkType(innerValue);
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart(`[${idx}]`);
                }
                throw e;
            }
        })
    }
}




PK
!<Ҏ�OIgIg$modules/FxAccountsWebChannel.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Firefox Accounts Web Channel.
 *
 * Uses the WebChannel component to receive messages
 * about account state changes.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import {
  COMMAND_PROFILE_CHANGE,
  COMMAND_LOGIN,
  COMMAND_LOGOUT,
  COMMAND_OAUTH,
  COMMAND_DELETE,
  COMMAND_CAN_LINK_ACCOUNT,
  COMMAND_SYNC_PREFERENCES,
  COMMAND_CHANGE_PASSWORD,
  COMMAND_FXA_STATUS,
  COMMAND_PAIR_HEARTBEAT,
  COMMAND_PAIR_SUPP_METADATA,
  COMMAND_PAIR_AUTHORIZE,
  COMMAND_PAIR_DECLINE,
  COMMAND_PAIR_COMPLETE,
  COMMAND_PAIR_PREFERENCES,
  COMMAND_FIREFOX_VIEW,
  OAUTH_CLIENT_ID,
  ON_PROFILE_CHANGE_NOTIFICATION,
  PREF_LAST_FXA_USER,
  WEBCHANNEL_ID,
  log,
  logPII,
} from "resource://gre/modules/FxAccountsCommon.sys.mjs";
import { SyncDisconnect } from "resource://services-sync/SyncDisconnect.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  CryptoUtils: "resource://services-crypto/utils.sys.mjs",
  FxAccountsPairingFlow: "resource://gre/modules/FxAccountsPairing.sys.mjs",
  FxAccountsStorageManagerCanStoreField:
    "resource://gre/modules/FxAccountsStorage.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
  Weave: "resource://services-sync/main.sys.mjs",
  WebChannel: "resource://gre/modules/WebChannel.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => {
  return ChromeUtils.importESModule(
    "resource://gre/modules/FxAccounts.sys.mjs"
  ).getFxAccountsSingleton();
});
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "pairingEnabled",
  "identity.fxaccounts.pairing.enabled"
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "separatePrivilegedMozillaWebContentProcess",
  "browser.tabs.remote.separatePrivilegedMozillaWebContentProcess",
  false
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "separatedMozillaDomains",
  "browser.tabs.remote.separatedMozillaDomains",
  "",
  false,
  val => val.split(",")
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "accountServer",
  "identity.fxaccounts.remote.root",
  null,
  false,
  val => Services.io.newURI(val)
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "oauthEnabled",
  "identity.fxaccounts.oauth.enabled",
  false
);

// These engines were added years after Sync had been introduced, they need
// special handling since they are system add-ons and are un-available on
// older versions of Firefox.
const EXTRA_ENGINES = ["addresses", "creditcards"];

// These engines will be displayed to the user to pick which they would like to
// use
const CHOOSE_WHAT_TO_SYNC = [
  "addons",
  "addresses",
  "bookmarks",
  "creditcards",
  "history",
  "passwords",
  "preferences",
  "tabs",
];

/**
 * A helper function that extracts the message and stack from an error object.
 * Returns a `{ message, stack }` tuple. `stack` will be null if the error
 * doesn't have a stack trace.
 */
function getErrorDetails(error) {
  // Replace anything that looks like it might be a filepath on Windows or Unix
  let cleanMessage = String(error)
    .replace(/\\.*\\/gm, "[REDACTED]")
    .replace(/\/.*\//gm, "[REDACTED]");
  let details = { message: cleanMessage, stack: null };

  // Adapted from Console.sys.mjs.
  if (error.stack) {
    let frames = [];
    for (let frame = error.stack; frame; frame = frame.caller) {
      frames.push(String(frame).padStart(4));
    }
    details.stack = frames.join("\n");
  }

  return details;
}

/**
 * Create a new FxAccountsWebChannel to listen for account updates
 *
 * @param {Object} options Options
 *   @param {Object} options
 *     @param {String} options.content_uri
 *     The FxA Content server uri
 *     @param {String} options.channel_id
 *     The ID of the WebChannel
 *     @param {String} options.helpers
 *     Helpers functions. Should only be passed in for testing.
 * @constructor
 */
export function FxAccountsWebChannel(options) {
  if (!options) {
    throw new Error("Missing configuration options");
  }
  if (!options.content_uri) {
    throw new Error("Missing 'content_uri' option");
  }
  this._contentUri = options.content_uri;

  if (!options.channel_id) {
    throw new Error("Missing 'channel_id' option");
  }
  this._webChannelId = options.channel_id;

  // options.helpers is only specified by tests.
  ChromeUtils.defineLazyGetter(this, "_helpers", () => {
    return options.helpers || new FxAccountsWebChannelHelpers(options);
  });

  this._setupChannel();
}

FxAccountsWebChannel.prototype = {
  /**
   * WebChannel that is used to communicate with content page
   */
  _channel: null,

  /**
   * Helpers interface that does the heavy lifting.
   */
  _helpers: null,

  /**
   * WebChannel ID.
   */
  _webChannelId: null,
  /**
   * WebChannel origin, used to validate origin of messages
   */
  _webChannelOrigin: null,

  /**
   * Release all resources that are in use.
   */
  tearDown() {
    this._channel.stopListening();
    this._channel = null;
    this._channelCallback = null;
  },

  /**
   * Configures and registers a new WebChannel
   *
   * @private
   */
  _setupChannel() {
    // if this.contentUri is present but not a valid URI, then this will throw an error.
    try {
      this._webChannelOrigin = Services.io.newURI(this._contentUri);
      this._registerChannel();
    } catch (e) {
      log.error(e);
      throw e;
    }
  },

  _receiveMessage(message, sendingContext) {
    const { command, data } = message;
    let shouldCheckRemoteType =
      lazy.separatePrivilegedMozillaWebContentProcess &&
      lazy.separatedMozillaDomains.some(function (val) {
        return (
          lazy.accountServer.asciiHost == val ||
          lazy.accountServer.asciiHost.endsWith("." + val)
        );
      });
    let { currentRemoteType } = sendingContext.browsingContext;
    if (shouldCheckRemoteType && currentRemoteType != "privilegedmozilla") {
      log.error(
        `Rejected FxA webchannel message from remoteType = ${currentRemoteType}`
      );
      return;
    }

    let browser = sendingContext.browsingContext.top.embedderElement;
    switch (command) {
      case COMMAND_PROFILE_CHANGE:
        Services.obs.notifyObservers(
          null,
          ON_PROFILE_CHANGE_NOTIFICATION,
          data.uid
        );
        break;
      case COMMAND_LOGIN:
        this._helpers
          .login(data)
          .catch(error => this._sendError(error, message, sendingContext));
        break;
      case COMMAND_OAUTH:
        this._helpers
          .oauthLogin(data)
          .catch(error => this._sendError(error, message, sendingContext));
        break;
      case COMMAND_LOGOUT:
      case COMMAND_DELETE:
        this._helpers
          .logout(data.uid)
          .catch(error => this._sendError(error, message, sendingContext));
        break;
      case COMMAND_CAN_LINK_ACCOUNT:
        let canLinkAccount = this._helpers.shouldAllowRelink(data.email);

        let response = {
          command,
          messageId: message.messageId,
          data: { ok: canLinkAccount },
        };

        log.debug("FxAccountsWebChannel response", response);
        this._channel.send(response, sendingContext);
        break;
      case COMMAND_SYNC_PREFERENCES:
        this._helpers.openSyncPreferences(browser, data.entryPoint);
        break;
      case COMMAND_PAIR_PREFERENCES:
        if (lazy.pairingEnabled) {
          let window = browser.ownerGlobal;
          // We should close the FxA tab after we open our pref page
          let selectedTab = window.gBrowser.selectedTab;
          window.switchToTabHavingURI(
            "about:preferences?action=pair#sync",
            true,
            {
              ignoreQueryString: true,
              replaceQueryString: true,
              adoptIntoActiveWindow: true,
              ignoreFragment: "whenComparing",
              triggeringPrincipal:
                Services.scriptSecurityManager.getSystemPrincipal(),
            }
          );
          // close the tab
          window.gBrowser.removeTab(selectedTab);
        }
        break;
      case COMMAND_FIREFOX_VIEW:
        this._helpers.openFirefoxView(browser, data.entryPoint);
        break;
      case COMMAND_CHANGE_PASSWORD:
        this._helpers
          .changePassword(data)
          .catch(error => this._sendError(error, message, sendingContext));
        break;
      case COMMAND_FXA_STATUS:
        log.debug("fxa_status received");

        const service = data && data.service;
        const isPairing = data && data.isPairing;
        const context = data && data.context;
        this._helpers
          .getFxaStatus(service, sendingContext, isPairing, context)
          .then(fxaStatus => {
            let response = {
              command,
              messageId: message.messageId,
              data: fxaStatus,
            };
            this._channel.send(response, sendingContext);
          })
          .catch(error => this._sendError(error, message, sendingContext));
        break;
      case COMMAND_PAIR_HEARTBEAT:
      case COMMAND_PAIR_SUPP_METADATA:
      case COMMAND_PAIR_AUTHORIZE:
      case COMMAND_PAIR_DECLINE:
      case COMMAND_PAIR_COMPLETE:
        log.debug(`Pairing command ${command} received`);
        const { channel_id: channelId } = data;
        delete data.channel_id;
        const flow = lazy.FxAccountsPairingFlow.get(channelId);
        if (!flow) {
          log.warn(`Could not find a pairing flow for ${channelId}`);
          return;
        }
        flow.onWebChannelMessage(command, data).then(replyData => {
          this._channel.send(
            {
              command,
              messageId: message.messageId,
              data: replyData,
            },
            sendingContext
          );
        });
        break;
      default:
        log.warn("Unrecognized FxAccountsWebChannel command", command);
        // As a safety measure we also terminate any pending FxA pairing flow.
        lazy.FxAccountsPairingFlow.finalizeAll();
        break;
    }
  },

  _sendError(error, incomingMessage, sendingContext) {
    log.error("Failed to handle FxAccountsWebChannel message", error);
    this._channel.send(
      {
        command: incomingMessage.command,
        messageId: incomingMessage.messageId,
        data: {
          error: getErrorDetails(error),
        },
      },
      sendingContext
    );
  },

  /**
   * Create a new channel with the WebChannelBroker, setup a callback listener
   * @private
   */
  _registerChannel() {
    /**
     * Processes messages that are called back from the FxAccountsChannel
     *
     * @param webChannelId {String}
     *        Command webChannelId
     * @param message {Object}
     *        Command message
     * @param sendingContext {Object}
     *        Message sending context.
     *        @param sendingContext.browsingContext {BrowsingContext}
     *               The browsingcontext from which the
     *               WebChannelMessageToChrome was sent.
     *        @param sendingContext.eventTarget {EventTarget}
     *               The <EventTarget> where the message was sent.
     *        @param sendingContext.principal {Principal}
     *               The <Principal> of the EventTarget where the message was sent.
     * @private
     *
     */
    let listener = (webChannelId, message, sendingContext) => {
      if (message) {
        log.debug("FxAccountsWebChannel message received", message.command);
        if (logPII()) {
          log.debug("FxAccountsWebChannel message details", message);
        }
        try {
          this._receiveMessage(message, sendingContext);
        } catch (error) {
          this._sendError(error, message, sendingContext);
        }
      }
    };

    this._channelCallback = listener;
    this._channel = new lazy.WebChannel(
      this._webChannelId,
      this._webChannelOrigin
    );
    this._channel.listen(listener);
    log.debug(
      "FxAccountsWebChannel registered: " +
        this._webChannelId +
        " with origin " +
        this._webChannelOrigin.prePath
    );
  },
};

export function FxAccountsWebChannelHelpers(options) {
  options = options || {};

  this._fxAccounts = options.fxAccounts || lazy.fxAccounts;
  this._weaveXPCOM = options.weaveXPCOM || null;
  this._privateBrowsingUtils =
    options.privateBrowsingUtils || lazy.PrivateBrowsingUtils;
}

FxAccountsWebChannelHelpers.prototype = {
  // If the last fxa account used for sync isn't this account, we display
  // a modal dialog checking they really really want to do this...
  // (This is sync-specific, so ideally would be in sync's identity module,
  // but it's a little more seamless to do here, and sync is currently the
  // only fxa consumer, so...
  shouldAllowRelink(acctName) {
    return (
      !this._needRelinkWarning(acctName) || this._promptForRelink(acctName)
    );
  },

  async _initializeSync() {
    // A sync-specific hack - we want to ensure sync has been initialized
    // before we set the signed-in user.
    // XXX - probably not true any more, especially now we have observerPreloads
    // in FxAccounts.sys.mjs?
    let xps =
      this._weaveXPCOM ||
      Cc["@mozilla.org/weave/service;1"].getService(Ci.nsISupports)
        .wrappedJSObject;
    await xps.whenLoaded();
    return xps;
  },

  _setEnabledEngines(offeredEngines, declinedEngines) {
    if (offeredEngines && declinedEngines) {
      EXTRA_ENGINES.forEach(engine => {
        if (
          offeredEngines.includes(engine) &&
          !declinedEngines.includes(engine)
        ) {
          // These extra engines are disabled by default.
          Services.prefs.setBoolPref(`services.sync.engine.${engine}`, true);
        }
      });
      log.debug("Received declined engines", declinedEngines);
      lazy.Weave.Service.engineManager.setDeclined(declinedEngines);
      declinedEngines.forEach(engine => {
        Services.prefs.setBoolPref(`services.sync.engine.${engine}`, false);
      });
    }
  },
  /**
   * stores sync login info it in the fxaccounts service
   *
   * @param accountData the user's account data and credentials
   */
  async login(accountData) {
    const signedInUser = await this._fxAccounts.getSignedInUser();
    if (signedInUser) {
      await this._disconnect();
    }
    // We don't act on customizeSync anymore, it used to open a dialog inside
    // the browser to selecte the engines to sync but we do it on the web now.
    log.debug("Webchannel is logging a user in.");
    delete accountData.customizeSync;

    // Save requested services for later.
    const requestedServices = accountData.services;
    delete accountData.services;

    // the user has already been shown the "can link account"
    // screen. No need to keep this data around.
    delete accountData.verifiedCanLinkAccount;

    // Remember who it was so we can log out next time.
    if (accountData.verified) {
      this.setPreviousAccountNameHashPref(accountData.email);
    }

    await this._fxAccounts.telemetry.recordConnection(
      Object.keys(requestedServices || {}),
      "webchannel"
    );

    if (lazy.oauthEnabled) {
      await this._fxAccounts._internal.setSignedInUser(accountData);
    } else {
      const xps = await this._initializeSync();
      await this._fxAccounts._internal.setSignedInUser(accountData);

      if (requestedServices) {
        // User has enabled Sync.
        if (requestedServices.sync) {
          const { offeredEngines, declinedEngines } = requestedServices.sync;
          this._setEnabledEngines(offeredEngines, declinedEngines);
          log.debug("Webchannel is enabling sync");
          await xps.Weave.Service.configure();
        }
      }
    }
  },

  /**
   * Disconnects the user from Sync and FxA
   *
   */
  _disconnect() {
    return SyncDisconnect.disconnect(false);
  },

  /**
   * Logins in to sync by completing an OAuth flow
   * @param { Object } oauthData: The oauth code and state as returned by the server */
  async oauthLogin(oauthData) {
    log.debug("Webchannel is completing the oauth flow");
    const xps = await this._initializeSync();
    const { code, state, declinedSyncEngines, offeredSyncEngines } = oauthData;
    const { sessionToken } =
      await this._fxAccounts._internal.getUserAccountData(["sessionToken"]);
    // First we finish the ongoing oauth flow
    const { scopedKeys, refreshToken } =
      await this._fxAccounts._internal.completeOAuthFlow(
        sessionToken,
        code,
        state
      );

    // We don't currently use the refresh token in Firefox Desktop, lets be good citizens and revoke it.
    await this._fxAccounts._internal.destroyOAuthToken({ token: refreshToken });

    // Then, we persist the sync keys
    await this._fxAccounts._internal.setScopedKeys(scopedKeys);

    // Now that we have the scoped keys, we set our status to verified
    await this._fxAccounts._internal.setUserVerified();
    this._setEnabledEngines(offeredSyncEngines, declinedSyncEngines);
    log.debug("Webchannel is enabling sync");
    xps.Weave.Service.configure();
  },

  /**
   * logout the fxaccounts service
   *
   * @param the uid of the account which have been logged out
   */
  async logout(uid) {
    let fxa = this._fxAccounts;
    let userData = await fxa._internal.getUserAccountData(["uid"]);
    if (userData && userData.uid === uid) {
      await fxa.telemetry.recordDisconnection(null, "webchannel");
      // true argument is `localOnly`, because server-side stuff
      // has already been taken care of by the content server
      await fxa.signOut(true);
    }
  },

  /**
   * Check if `sendingContext` is in private browsing mode.
   */
  isPrivateBrowsingMode(sendingContext) {
    if (!sendingContext) {
      log.error("Unable to check for private browsing mode, assuming true");
      return true;
    }

    let browser = sendingContext.browsingContext.top.embedderElement;
    const isPrivateBrowsing =
      this._privateBrowsingUtils.isBrowserPrivate(browser);
    log.debug("is private browsing", isPrivateBrowsing);
    return isPrivateBrowsing;
  },

  /**
   * Check whether sending fxa_status data should be allowed.
   */
  shouldAllowFxaStatus(service, sendingContext, isPairing, context) {
    // Return user data for any service in non-PB mode. In PB mode,
    // only return user data if service==="sync" or is in pairing mode
    // (as service will be equal to the OAuth client ID and not "sync").
    //
    // This behaviour allows users to click the "Manage Account"
    // link from about:preferences#sync while in PB mode and things
    // "just work". While in non-PB mode, users can sign into
    // Pocket w/o entering their password a 2nd time, while in PB
    // mode they *will* have to enter their email/password again.
    //
    // The difference in behaviour is to try to match user
    // expectations as to what is and what isn't part of the browser.
    // Sync is viewed as an integral part of the browser, interacting
    // with FxA as part of a Sync flow should work all the time. If
    // Sync is broken in PB mode, users will think Firefox is broken.
    // See https://bugzilla.mozilla.org/show_bug.cgi?id=1323853
    log.debug("service", service);
    return (
      !this.isPrivateBrowsingMode(sendingContext) ||
      service === "sync" ||
      context === "fx_desktop_v3" ||
      isPairing
    );
  },

  /**
   * Get fxa_status information. Resolves to { signedInUser: <user_data> }.
   * If returning status information is not allowed or no user is signed into
   * Sync, `user_data` will be null.
   */
  async getFxaStatus(service, sendingContext, isPairing, context) {
    let signedInUser = null;

    if (
      this.shouldAllowFxaStatus(service, sendingContext, isPairing, context)
    ) {
      const userData = await this._fxAccounts._internal.getUserAccountData([
        "email",
        "sessionToken",
        "uid",
        "verified",
      ]);
      if (userData) {
        signedInUser = {
          email: userData.email,
          sessionToken: userData.sessionToken,
          uid: userData.uid,
          verified: userData.verified,
        };
      }
    }

    const capabilities = this._getCapabilities();

    return {
      signedInUser,
      clientId: OAUTH_CLIENT_ID,
      capabilities,
    };
  },
  _getCapabilities() {
    if (lazy.oauthEnabled) {
      return {
        multiService: true,
        pairing: lazy.pairingEnabled,
        choose_what_to_sync: true,
        engines: CHOOSE_WHAT_TO_SYNC,
      };
    }
    return {
      multiService: true,
      pairing: lazy.pairingEnabled,
      engines: this._getAvailableExtraEngines(),
    };
  },

  _getAvailableExtraEngines() {
    return EXTRA_ENGINES.filter(engineName => {
      try {
        return Services.prefs.getBoolPref(
          `services.sync.engine.${engineName}.available`
        );
      } catch (e) {
        return false;
      }
    });
  },

  async changePassword(credentials) {
    // If |credentials| has fields that aren't handled by accounts storage,
    // updateUserAccountData will throw - mainly to prevent errors in code
    // that hard-codes field names.
    // However, in this case the field names aren't really in our control.
    // We *could* still insist the server know what fields names are valid,
    // but that makes life difficult for the server when Firefox adds new
    // features (ie, new fields) - forcing the server to track a map of
    // versions to supported field names doesn't buy us much.
    // So we just remove field names we know aren't handled.
    let newCredentials = {
      device: null, // Force a brand new device registration.
      // We force the re-encryption of the send tab keys using the new sync key after the password change
      encryptedSendTabKeys: null,
    };
    for (let name of Object.keys(credentials)) {
      if (
        name == "email" ||
        name == "uid" ||
        lazy.FxAccountsStorageManagerCanStoreField(name)
      ) {
        newCredentials[name] = credentials[name];
      } else {
        log.info("changePassword ignoring unsupported field", name);
      }
    }
    await this._fxAccounts._internal.updateUserAccountData(newCredentials);
    await this._fxAccounts._internal.updateDeviceRegistration();
  },

  /**
   * Get the hash of account name of the previously signed in account
   */
  getPreviousAccountNameHashPref() {
    try {
      return Services.prefs.getStringPref(PREF_LAST_FXA_USER);
    } catch (_) {
      return "";
    }
  },

  /**
   * Given an account name, set the hash of the previously signed in account
   *
   * @param acctName the account name of the user's account.
   */
  setPreviousAccountNameHashPref(acctName) {
    Services.prefs.setStringPref(
      PREF_LAST_FXA_USER,
      lazy.CryptoUtils.sha256Base64(acctName)
    );
  },

  /**
   * Open Sync Preferences in the current tab of the browser
   *
   * @param {Object} browser the browser in which to open preferences
   * @param {String} [entryPoint] entryPoint to use for logging
   */
  openSyncPreferences(browser, entryPoint) {
    let uri = "about:preferences";
    if (entryPoint) {
      uri += "?entrypoint=" + encodeURIComponent(entryPoint);
    }
    uri += "#sync";

    browser.loadURI(Services.io.newURI(uri), {
      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
    });
  },

  /**
   * Open Firefox View in the browser's window
   *
   * @param {Object} browser the browser in whose window we'll open Firefox View
   */
  openFirefoxView(browser) {
    browser.ownerGlobal.FirefoxViewHandler.openTab("syncedtabs");
  },

  /**
   * If a user signs in using a different account, the data from the
   * previous account and the new account will be merged. Ask the user
   * if they want to continue.
   *
   * @private
   */
  _needRelinkWarning(acctName) {
    let prevAcctHash = this.getPreviousAccountNameHashPref();
    return (
      prevAcctHash && prevAcctHash != lazy.CryptoUtils.sha256Base64(acctName)
    );
  },

  /**
   * Show the user a warning dialog that the data from the previous account
   * and the new account will be merged.
   *
   * @private
   */
  _promptForRelink(acctName) {
    let sb = Services.strings.createBundle(
      "chrome://browser/locale/syncSetup.properties"
    );
    let continueLabel = sb.GetStringFromName("continue.label");
    let title = sb.GetStringFromName("relinkVerify.title");
    let description = sb.formatStringFromName("relinkVerify.description", [
      acctName,
    ]);
    let body =
      sb.GetStringFromName("relinkVerify.heading") + "\n\n" + description;
    let ps = Services.prompt;
    let buttonFlags =
      ps.BUTTON_POS_0 * ps.BUTTON_TITLE_IS_STRING +
      ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL +
      ps.BUTTON_POS_1_DEFAULT;

    // If running in context of the browser chrome, window does not exist.
    let pressed = Services.prompt.confirmEx(
      null,
      title,
      body,
      buttonFlags,
      continueLabel,
      null,
      null,
      null,
      {}
    );
    return pressed === 0; // 0 is the "continue" button
  },
};

var singleton;

// The entry-point for this module, which ensures only one of our channels is
// ever created - we require this because the WebChannel is global in scope
// (eg, it uses the observer service to tell interested parties of interesting
// things) and allowing multiple channels would cause such notifications to be
// sent multiple times.
export var EnsureFxAccountsWebChannel = () => {
  let contentUri = Services.urlFormatter.formatURLPref(
    "identity.fxaccounts.remote.root"
  );
  if (singleton && singleton._contentUri !== contentUri) {
    singleton.tearDown();
    singleton = null;
  }
  if (!singleton) {
    try {
      if (contentUri) {
        // The FxAccountsWebChannel listens for events and updates
        // the state machine accordingly.
        singleton = new FxAccountsWebChannel({
          content_uri: contentUri,
          channel_id: WEBCHANNEL_ID,
        });
      } else {
        log.warn("FxA WebChannel functionaly is disabled due to no URI pref.");
      }
    } catch (ex) {
      log.error("Failed to create FxA WebChannel", ex);
    }
  }
};
PK
!<͉���"�",modules/services-sync/SyncDisconnect.sys.mjs// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// This module provides a facility for disconnecting Sync and FxA, optionally
// sanitizing profile data as part of the process.

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  Log: "resource://gre/modules/Log.sys.mjs",
  PREF_LAST_FXA_USER: "resource://gre/modules/FxAccountsCommon.sys.mjs",
  Sanitizer: "resource:///modules/Sanitizer.sys.mjs",
  Utils: "resource://services-sync/util.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => {
  return ChromeUtils.importESModule(
    "resource://gre/modules/FxAccounts.sys.mjs"
  ).getFxAccountsSingleton();
});

export const SyncDisconnectInternal = {
  lockRetryInterval: 1000, // wait 1 seconds before trying for the lock again.
  lockRetryCount: 120, // Try 120 times (==2 mins) before giving up in disgust.
  promiseDisconnectFinished: null, // If we are sanitizing, a promise for completion.

  // mocked by tests.
  getWeave() {
    return ChromeUtils.importESModule("resource://services-sync/main.sys.mjs")
      .Weave;
  },

  // Returns a promise that resolves when we are not syncing, waiting until
  // a current Sync completes if necessary. Resolves with true if we
  // successfully waited, in which case the sync lock will have been taken to
  // ensure future syncs don't state, or resolves with false if we gave up
  // waiting for the sync to complete (in which case we didn't take a lock -
  // but note that Sync probably remains locked in this case regardless.)
  async promiseNotSyncing(abortController) {
    let weave = this.getWeave();
    let log = lazy.Log.repository.getLogger("Sync.Service");
    // We might be syncing - poll for up to 2 minutes waiting for the lock.
    // (2 minutes seems extreme, but should be very rare.)
    return new Promise(resolve => {
      abortController.signal.onabort = () => {
        resolve(false);
      };

      let attempts = 0;
      let checkLock = () => {
        if (abortController.signal.aborted) {
          // We've already resolved, so don't want a new timer to ever start.
          return;
        }
        if (weave.Service.lock()) {
          resolve(true);
          return;
        }
        attempts += 1;
        if (attempts >= this.lockRetryCount) {
          log.error(
            "Gave up waiting for the sync lock - going ahead with sanitize anyway"
          );
          resolve(false);
          return;
        }
        log.debug("Waiting a couple of seconds to get the sync lock");
        lazy.setTimeout(checkLock, this.lockRetryInterval);
      };
      checkLock();
    });
  },

  // Sanitize Sync-related data.
  async doSanitizeSyncData() {
    let weave = this.getWeave();
    // Get the sync logger - if stuff goes wrong it can be useful to have that
    // recorded in the sync logs.
    let log = lazy.Log.repository.getLogger("Sync.Service");
    log.info("Starting santitize of Sync data");
    try {
      // We clobber data for all Sync engines that are enabled.
      await weave.Service.promiseInitialized;
      weave.Service.enabled = false;

      log.info("starting actual sanitization");
      for (let engine of weave.Service.engineManager.getAll()) {
        if (engine.enabled) {
          try {
            log.info("Wiping engine", engine.name);
            await engine.wipeClient();
          } catch (ex) {
            log.error("Failed to wipe engine", ex);
          }
        }
      }
      // Reset the pref which is used to show a warning when a different user
      // signs in - this is no longer a concern now that we've removed the
      // data from the profile.
      Services.prefs.clearUserPref(lazy.PREF_LAST_FXA_USER);

      log.info("Finished wiping sync data");
    } catch (ex) {
      log.error("Failed to sanitize Sync data", ex);
      console.error("Failed to sanitize Sync data", ex);
    }
    try {
      // ensure any logs we wrote are flushed to disk.
      await weave.Service.errorHandler.resetFileLog();
    } catch (ex) {
      console.log("Failed to flush the Sync log", ex);
    }
  },

  // Sanitize all Browser data.
  async doSanitizeBrowserData() {
    try {
      // sanitize everything other than "open windows" (and we don't do that
      // because it may confuse the user - they probably want to see
      // about:prefs with the disconnection reflected.
      let itemsToClear = Object.keys(lazy.Sanitizer.items).filter(
        k => k != "openWindows"
      );
      await lazy.Sanitizer.sanitize(itemsToClear);
    } catch (ex) {
      console.error("Failed to sanitize other data", ex);
    }
  },

  async doSyncAndAccountDisconnect(shouldUnlock) {
    // We do a startOver of Sync first - if we do the account first we end
    // up with Sync configured but FxA not configured, which causes the browser
    // UI to briefly enter a "needs reauth" state.
    let Weave = this.getWeave();
    await Weave.Service.promiseInitialized;
    await Weave.Service.startOver();
    await lazy.fxAccounts.signOut();
    // Sync may have been disabled if we santized, so re-enable it now or
    // else the user will be unable to resync should they sign in before a
    // restart.
    Weave.Service.enabled = true;

    // and finally, if we managed to get the lock before, we should unlock it
    // now.
    if (shouldUnlock) {
      Weave.Service.unlock();
    }
  },

  // Start the sanitization process. Returns a promise that resolves when
  // the sanitize is complete, and an AbortController which can be used to
  // abort the process of waiting for a sync to complete.
  async _startDisconnect(abortController, sanitizeData = false) {
    // This is a bit convoluted - we want to wait for a sync to finish before
    // sanitizing, but want to abort that wait if the browser shuts down while
    // we are waiting (in which case we'll charge ahead anyway).
    // So we do this by using an AbortController and passing that to the
    // function that waits for the sync lock - it will immediately resolve
    // if the abort controller is aborted.
    let log = lazy.Log.repository.getLogger("Sync.Service");

    // If the master-password is locked then we will fail to fully sanitize,
    // so prompt for that now. If canceled, we just abort now.
    log.info("checking master-password state");
    if (!lazy.Utils.ensureMPUnlocked()) {
      log.warn(
        "The master-password needs to be unlocked to fully disconnect from sync"
      );
      return;
    }

    log.info("waiting for any existing syncs to complete");
    let locked = await this.promiseNotSyncing(abortController);

    if (sanitizeData) {
      await this.doSanitizeSyncData();

      // We disconnect before sanitizing the browser data - in a worst-case
      // scenario where the sanitize takes so long that even the shutdown
      // blocker doesn't allow it to finish, we should still at least be in
      // a disconnected state on the next startup.
      log.info("disconnecting account");
      await this.doSyncAndAccountDisconnect(locked);

      await this.doSanitizeBrowserData();
    } else {
      log.info("disconnecting account");
      await this.doSyncAndAccountDisconnect(locked);
    }
  },

  async disconnect(sanitizeData) {
    if (this.promiseDisconnectFinished) {
      throw new Error("A disconnect is already in progress");
    }
    let abortController = new AbortController();
    let promiseDisconnectFinished = this._startDisconnect(
      abortController,
      sanitizeData
    );
    this.promiseDisconnectFinished = promiseDisconnectFinished;
    let shutdownBlocker = () => {
      // oh dear - we are sanitizing (probably stuck waiting for a sync to
      // complete) and the browser is shutting down. Let's avoid the wait
      // for sync to complete and continue the process anyway.
      abortController.abort();
      return promiseDisconnectFinished;
    };
    lazy.AsyncShutdown.quitApplicationGranted.addBlocker(
      "SyncDisconnect: removing requested data",
      shutdownBlocker
    );

    // wait for it to finish - hopefully without the blocker being called.
    await promiseDisconnectFinished;
    this.promiseDisconnectFinished = null;

    // sanitize worked so remove our blocker - it's a noop if the blocker
    // did call us.
    lazy.AsyncShutdown.quitApplicationGranted.removeBlocker(shutdownBlocker);
  },
};

export const SyncDisconnect = {
  get promiseDisconnectFinished() {
    return SyncDisconnectInternal.promiseDisconnectFinished;
  },

  disconnect(sanitizeData) {
    return SyncDisconnectInternal.disconnect(sanitizeData);
  },
};
PK
!<�{` 5�5�modules/FxAccounts.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { CryptoUtils } from "resource://services-crypto/utils.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs";

import { FxAccountsStorageManager } from "resource://gre/modules/FxAccountsStorage.sys.mjs";

import {
  ERRNO_INVALID_AUTH_TOKEN,
  ERROR_AUTH_ERROR,
  ERROR_INVALID_PARAMETER,
  ERROR_NO_ACCOUNT,
  ERROR_TO_GENERAL_ERROR_CLASS,
  ERROR_UNKNOWN,
  ERROR_UNVERIFIED_ACCOUNT,
  FXA_PWDMGR_PLAINTEXT_FIELDS,
  FXA_PWDMGR_REAUTH_ALLOWLIST,
  FXA_PWDMGR_SECURE_FIELDS,
  OAUTH_CLIENT_ID,
  ON_ACCOUNT_STATE_CHANGE_NOTIFICATION,
  ONLOGIN_NOTIFICATION,
  ONLOGOUT_NOTIFICATION,
  ON_PRELOGOUT_NOTIFICATION,
  ONVERIFIED_NOTIFICATION,
  ON_DEVICE_DISCONNECTED_NOTIFICATION,
  POLL_SESSION,
  PREF_ACCOUNT_ROOT,
  PREF_LAST_FXA_USER,
  SERVER_ERRNO_TO_ERROR,
  log,
  logPII,
  logManager,
} from "resource://gre/modules/FxAccountsCommon.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  FxAccountsClient: "resource://gre/modules/FxAccountsClient.sys.mjs",
  FxAccountsCommands: "resource://gre/modules/FxAccountsCommands.sys.mjs",
  FxAccountsConfig: "resource://gre/modules/FxAccountsConfig.sys.mjs",
  FxAccountsDevice: "resource://gre/modules/FxAccountsDevice.sys.mjs",
  FxAccountsKeys: "resource://gre/modules/FxAccountsKeys.sys.mjs",
  FxAccountsOAuth: "resource://gre/modules/FxAccountsOAuth.sys.mjs",
  FxAccountsProfile: "resource://gre/modules/FxAccountsProfile.sys.mjs",
  FxAccountsTelemetry: "resource://gre/modules/FxAccountsTelemetry.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "mpLocked", () => {
  return ChromeUtils.importESModule("resource://services-sync/util.sys.mjs")
    .Utils.mpLocked;
});

ChromeUtils.defineLazyGetter(lazy, "ensureMPUnlocked", () => {
  return ChromeUtils.importESModule("resource://services-sync/util.sys.mjs")
    .Utils.ensureMPUnlocked;
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "FXA_ENABLED",
  "identity.fxaccounts.enabled",
  true
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "oauthEnabled",
  "identity.fxaccounts.oauth.enabled",
  true
);

export const ERROR_INVALID_ACCOUNT_STATE = "ERROR_INVALID_ACCOUNT_STATE";

// An AccountState object holds all state related to one specific account.
// It is considered "private" to the FxAccounts modules.
// Only one AccountState is ever "current" in the FxAccountsInternal object -
// whenever a user logs out or logs in, the current AccountState is discarded,
// making it impossible for the wrong state or state data to be accidentally
// used.
// In addition, it has some promise-related helpers to ensure that if an
// attempt is made to resolve a promise on a "stale" state (eg, if an
// operation starts, but a different user logs in before the operation
// completes), the promise will be rejected.
// It is intended to be used thusly:
// somePromiseBasedFunction: function() {
//   let currentState = this.currentAccountState;
//   return someOtherPromiseFunction().then(
//     data => currentState.resolve(data)
//   );
// }
// If the state has changed between the function being called and the promise
// being resolved, the .resolve() call will actually be rejected.
export function AccountState(storageManager) {
  this.storageManager = storageManager;
  this.inFlightTokenRequests = new Map();
  this.promiseInitialized = this.storageManager
    .getAccountData()
    .then(data => {
      this.oauthTokens = data && data.oauthTokens ? data.oauthTokens : {};
    })
    .catch(err => {
      log.error("Failed to initialize the storage manager", err);
      // Things are going to fall apart, but not much we can do about it here.
    });
}

AccountState.prototype = {
  oauthTokens: null,
  whenVerifiedDeferred: null,
  whenKeysReadyDeferred: null,

  // If the storage manager has been nuked then we are no longer current.
  get isCurrent() {
    return this.storageManager != null;
  },

  abort() {
    if (this.whenVerifiedDeferred) {
      this.whenVerifiedDeferred.reject(
        new Error("Verification aborted; Another user signing in")
      );
      this.whenVerifiedDeferred = null;
    }
    if (this.whenKeysReadyDeferred) {
      this.whenKeysReadyDeferred.reject(
        new Error("Key fetching aborted; Another user signing in")
      );
      this.whenKeysReadyDeferred = null;
    }
    this.inFlightTokenRequests.clear();
    return this.signOut();
  },

  // Clobber all cached data and write that empty data to storage.
  async signOut() {
    this.cert = null;
    this.keyPair = null;
    this.oauthTokens = null;
    this.inFlightTokenRequests.clear();

    // Avoid finalizing the storageManager multiple times (ie, .signOut()
    // followed by .abort())
    if (!this.storageManager) {
      return;
    }
    const storageManager = this.storageManager;
    this.storageManager = null;

    await storageManager.deleteAccountData();
    await storageManager.finalize();
  },

  // Get user account data. Optionally specify explicit field names to fetch
  // (and note that if you require an in-memory field you *must* specify the
  // field name(s).)
  getUserAccountData(fieldNames = null) {
    if (!this.isCurrent) {
      return Promise.reject(new Error("Another user has signed in"));
    }
    return this.storageManager.getAccountData(fieldNames).then(result => {
      return this.resolve(result);
    });
  },

  async updateUserAccountData(updatedFields) {
    if ("uid" in updatedFields) {
      const existing = await this.getUserAccountData(["uid"]);
      if (existing.uid != updatedFields.uid) {
        throw new Error(
          "The specified credentials aren't for the current user"
        );
      }
      // We need to nuke uid as storage will complain if we try and
      // update it (even when the value is the same)
      updatedFields = Cu.cloneInto(updatedFields, {}); // clone it first
      delete updatedFields.uid;
    }
    if (!this.isCurrent) {
      return Promise.reject(new Error(ERROR_INVALID_ACCOUNT_STATE));
    }
    return this.storageManager.updateAccountData(updatedFields);
  },

  resolve(result) {
    if (!this.isCurrent) {
      log.info(
        "An accountState promise was resolved, but was actually rejected" +
          " due to the account state changing. This can happen if a new account signed in, or" +
          " the account was signed out. Originally resolved with, ",
        result
      );
      return Promise.reject(new Error(ERROR_INVALID_ACCOUNT_STATE));
    }
    return Promise.resolve(result);
  },

  reject(error) {
    // It could be argued that we should just let it reject with the original
    // error - but this runs the risk of the error being (eg) a 401, which
    // might cause the consumer to attempt some remediation and cause other
    // problems.
    if (!this.isCurrent) {
      log.info(
        "An accountState promise was rejected, but we are ignoring that" +
          " reason and rejecting it due to the account state changing. This can happen if" +
          " a different account signed in or the account was signed out" +
          " originally resolved with, ",
        error
      );
      return Promise.reject(new Error(ERROR_INVALID_ACCOUNT_STATE));
    }
    return Promise.reject(error);
  },

  // Abstractions for storage of cached tokens - these are all sync, and don't
  // handle revocation etc - it's just storage (and the storage itself is async,
  // but we don't return the storage promises, so it *looks* sync)
  // These functions are sync simply so we can handle "token races" - when there
  // are multiple in-flight requests for the same scope, we can detect this
  // and revoke the redundant token.

  // A preamble for the cache helpers...
  _cachePreamble() {
    if (!this.isCurrent) {
      throw new Error(ERROR_INVALID_ACCOUNT_STATE);
    }
  },

  // Set a cached token. |tokenData| must have a 'token' element, but may also
  // have additional fields.
  // The 'get' functions below return the entire |tokenData| value.
  setCachedToken(scopeArray, tokenData) {
    this._cachePreamble();
    if (!tokenData.token) {
      throw new Error("No token");
    }
    let key = getScopeKey(scopeArray);
    this.oauthTokens[key] = tokenData;
    // And a background save...
    this._persistCachedTokens();
  },

  // Return data for a cached token or null (or throws on bad state etc)
  getCachedToken(scopeArray) {
    this._cachePreamble();
    let key = getScopeKey(scopeArray);
    let result = this.oauthTokens[key];
    if (result) {
      // later we might want to check an expiry date - but we currently
      // have no such concept, so just return it.
      log.trace("getCachedToken returning cached token");
      return result;
    }
    return null;
  },

  // Remove a cached token from the cache.  Does *not* revoke it from anywhere.
  // Returns the entire token entry if found, null otherwise.
  removeCachedToken(token) {
    this._cachePreamble();
    let data = this.oauthTokens;
    for (let [key, tokenValue] of Object.entries(data)) {
      if (tokenValue.token == token) {
        delete data[key];
        // And a background save...
        this._persistCachedTokens();
        return tokenValue;
      }
    }
    return null;
  },

  // A hook-point for tests.  Returns a promise that's ignored in most cases
  // (notable exceptions are tests and when we explicitly are saving the entire
  // set of user data.)
  _persistCachedTokens() {
    this._cachePreamble();
    return this.updateUserAccountData({ oauthTokens: this.oauthTokens }).catch(
      err => {
        log.error("Failed to update cached tokens", err);
      }
    );
  },
};

/* Given an array of scopes, make a string key by normalizing. */
function getScopeKey(scopeArray) {
  let normalizedScopes = scopeArray.map(item => item.toLowerCase());
  return normalizedScopes.sort().join("|");
}

function getPropertyDescriptor(obj, prop) {
  return (
    Object.getOwnPropertyDescriptor(obj, prop) ||
    getPropertyDescriptor(Object.getPrototypeOf(obj), prop)
  );
}

/**
 * Copies properties from a given object to another object.
 *
 * @param from (object)
 *        The object we read property descriptors from.
 * @param to (object)
 *        The object that we set property descriptors on.
 * @param thisObj (object)
 *        The object that will be used to .bind() all function properties we find to.
 * @param keys ([...])
 *        The names of all properties to be copied.
 */
function copyObjectProperties(from, to, thisObj, keys) {
  for (let prop of keys) {
    // Look for the prop in the prototype chain.
    let desc = getPropertyDescriptor(from, prop);

    if (typeof desc.value == "function") {
      desc.value = desc.value.bind(thisObj);
    }

    if (desc.get) {
      desc.get = desc.get.bind(thisObj);
    }

    if (desc.set) {
      desc.set = desc.set.bind(thisObj);
    }

    Object.defineProperty(to, prop, desc);
  }
}

/**
 * The public API.
 *
 * TODO - *all* non-underscore stuff here should have sphinx docstrings so
 * that docs magically appear on https://firefox-source-docs.mozilla.org/
 * (although |./mach doc| is broken on windows (bug 1232403) and on Linux for
 * markh (some obscure npm issue he gave up on) - so later...)
 */
export class FxAccounts {
  constructor(mocks = null) {
    this._internal = new FxAccountsInternal();
    if (mocks) {
      // it's slightly unfortunate that we need to mock the main "internal" object
      // before calling initialize, primarily so a mock `newAccountState` is in
      // place before initialize calls it, but we need to initialize the
      // "sub-object" mocks after. This can probably be fixed, but whatever...
      copyObjectProperties(
        mocks,
        this._internal,
        this._internal,
        Object.keys(mocks).filter(key => !["device", "commands"].includes(key))
      );
    }
    this._internal.initialize();
    // allow mocking our "sub-objects" too.
    if (mocks) {
      for (let subobject of [
        "currentAccountState",
        "keys",
        "fxaPushService",
        "device",
        "commands",
      ]) {
        if (typeof mocks[subobject] == "object") {
          copyObjectProperties(
            mocks[subobject],
            this._internal[subobject],
            this._internal[subobject],
            Object.keys(mocks[subobject])
          );
        }
      }
    }
  }

  get commands() {
    return this._internal.commands;
  }

  static get config() {
    return lazy.FxAccountsConfig;
  }

  get device() {
    return this._internal.device;
  }

  get keys() {
    return this._internal.keys;
  }

  get telemetry() {
    return this._internal.telemetry;
  }

  _withCurrentAccountState(func) {
    return this._internal.withCurrentAccountState(func);
  }

  _withVerifiedAccountState(func) {
    return this._internal.withVerifiedAccountState(func);
  }

  _withSessionToken(func, mustBeVerified = true) {
    return this._internal.withSessionToken(func, mustBeVerified);
  }

  /**
   * Returns an array listing all the OAuth clients connected to the
   * authenticated user's account. This includes browsers and web sessions - no
   * filtering is done of the set returned by the FxA server.
   *
   * @typedef {Object} AttachedClient
   * @property {String} id - OAuth `client_id` of the client.
   * @property {Number} lastAccessedDaysAgo - How many days ago the client last
   *    accessed the FxA server APIs.
   *
   * @returns {Array.<AttachedClient>} A list of attached clients.
   */
  async listAttachedOAuthClients() {
    // We expose last accessed times in 'days ago'
    const ONE_DAY = 24 * 60 * 60 * 1000;

    return this._withSessionToken(async sessionToken => {
      const response = await this._internal.fxAccountsClient.attachedClients(
        sessionToken
      );
      const attachedClients = response.body;
      const timestamp = response.headers["x-timestamp"];
      const now =
        timestamp !== undefined
          ? new Date(parseInt(timestamp, 10))
          : Date.now();
      return attachedClients.map(client => {
        const daysAgo = client.lastAccessTime
          ? Math.max(Math.floor((now - client.lastAccessTime) / ONE_DAY), 0)
          : null;
        return {
          id: client.clientId,
          lastAccessedDaysAgo: daysAgo,
        };
      });
    });
  }

  /**
   * Get an OAuth token for the user.
   *
   * @param options
   *        {
   *          scope: (string/array) the oauth scope(s) being requested. As a
   *                 convenience, you may pass a string if only one scope is
   *                 required, or an array of strings if multiple are needed.
   *          ttl: (number) OAuth token TTL in seconds.
   *        }
   *
   * @return Promise.<string | Error>
   *        The promise resolves the oauth token as a string or rejects with
   *        an error object ({error: ERROR, details: {}}) of the following:
   *          INVALID_PARAMETER
   *          NO_ACCOUNT
   *          UNVERIFIED_ACCOUNT
   *          NETWORK_ERROR
   *          AUTH_ERROR
   *          UNKNOWN_ERROR
   */
  async getOAuthToken(options = {}) {
    try {
      return await this._internal.getOAuthToken(options);
    } catch (err) {
      throw this._internal._errorToErrorClass(err);
    }
  }

  /** Gets both the OAuth token and the users scoped keys for that token
   * and verifies that both operations were done for the same user,
   * preventing race conditions where a caller
   * can get the key for one user, and the id of another if the user
   * is rapidly switching between accounts
   *
   * @param options
   *        {
   *          scope: string the oauth scope being requested. This must
   *          be a scope with an associated key, otherwise an error
   *          will be thrown that the key is not available.
   *          ttl: (number) OAuth token TTL in seconds
   *        }
   *
   * @return Promise.<Object | Error>
   * The promise resolve to both the access token being requested, and the scoped key
   *        {
   *         token: (string) access token
   *         key: (object) the scoped key object
   *        }
   * The promise can reject, with one of the errors `getOAuthToken`, `FxAccountKeys.getKeyForScope`, or
   * error if the user changed in-between operations
   */
  getOAuthTokenAndKey(options = {}) {
    return this._withCurrentAccountState(async () => {
      const key = await this.keys.getKeyForScope(options.scope);
      const token = await this.getOAuthToken(options);
      return { token, key };
    });
  }

  /**
   * Remove an OAuth token from the token cache. Callers should call this
   * after they determine a token is invalid, so a new token will be fetched
   * on the next call to getOAuthToken().
   *
   * @param options
   *        {
   *          token: (string) A previously fetched token.
   *        }
   * @return Promise.<undefined> This function will always resolve, even if
   *         an unknown token is passed.
   */
  removeCachedOAuthToken(options) {
    return this._internal.removeCachedOAuthToken(options);
  }

  /**
   * Get details about the user currently signed in to Firefox Accounts.
   *
   * @return Promise
   *        The promise resolves to the credentials object of the signed-in user:
   *        {
   *          email: String: The user's email address
   *          uid: String: The user's unique id
   *          verified: Boolean: email verification status
   *          displayName: String or null if not known.
   *          avatar: URL of the avatar for the user. May be the default
   *                  avatar, or null in edge-cases (eg, if there's an account
   *                  issue, etc
   *          avatarDefault: boolean - whether `avatar` is specific to the user
   *                         or the default avatar.
   *        }
   *
   *        or null if no user is signed in. This function never fails except
   *        in pathological cases (eg, file-system errors, etc)
   */
  getSignedInUser() {
    // Note we don't return the session token, but use it to see if we
    // should fetch the profile.
    const ACCT_DATA_FIELDS = ["email", "uid", "verified", "sessionToken"];
    const PROFILE_FIELDS = ["displayName", "avatar", "avatarDefault"];
    return this._withCurrentAccountState(async currentState => {
      const data = await currentState.getUserAccountData(ACCT_DATA_FIELDS);
      if (!data) {
        return null;
      }
      if (!lazy.FXA_ENABLED) {
        await this.signOut();
        return null;
      }
      if (!this._internal.isUserEmailVerified(data) && !lazy.oauthEnabled) {
        // If the email is not verified, start polling for verification,
        // but return null right away.  We don't want to return a promise
        // that might not be fulfilled for a long time.
        this._internal.startVerifiedCheck(data);
      }

      let profileData = null;
      if (data.sessionToken) {
        delete data.sessionToken;
        try {
          profileData = await this._internal.profile.getProfile();
        } catch (error) {
          log.error("Could not retrieve profile data", error);
        }
      }
      for (let field of PROFILE_FIELDS) {
        data[field] = profileData ? profileData[field] : null;
      }
      // and email is a special case - if we have profile data we prefer the
      // email from that, as the email we stored for the account itself might
      // not have been updated if the email changed since the user signed in.
      if (profileData && profileData.email) {
        data.email = profileData.email;
      }
      return data;
    });
  }

  /**
   * Checks the status of the account. Resolves with Promise<boolean>, where
   * true indicates the account status is OK and false indicates there's some
   * issue with the account - either that there's no user currently signed in,
   * the entire account has been deleted (in which case there will be no user
   * signed in after this call returns), or that the user must reauthenticate (in
   * which case `this.hasLocalSession()` will return `false` after this call
   * returns).
   *
   * Typically used when some external code which uses, for example, oauth tokens
   * received a 401 error using the token, or that this external code has some
   * other reason to believe the account status may be bad. Note that this will
   * be called automatically in many cases - for example, if calls to fetch the
   * profile, or fetch keys, etc return a 401, there's no need to call this
   * function.
   *
   * Because this hits the server, you should only call this method when you have
   * good reason to believe the session very recently became invalid (eg, because
   * you saw an auth related exception from a remote service.)
   */
  checkAccountStatus() {
    // Note that we don't use _withCurrentAccountState here because that will
    // cause an exception to be thrown if we end up signing out due to the
    // account not existing, which isn't what we want here.
    let state = this._internal.currentAccountState;
    return this._internal.checkAccountStatus(state);
  }

  /**
   * Checks if we have a valid local session state for the current account.
   *
   * @return Promise
   *        Resolves with a boolean, with true indicating that we appear to
   *        have a valid local session, or false if we need to reauthenticate
   *        with the content server to obtain one.
   *        Note that this only checks local state, although typically that's
   *        OK, because we drop the local session information whenever we detect
   *        we are in this state. However, see checkAccountStatus() for a way to
   *        check the account and session status with the server, which can be
   *        considered the canonical, albiet expensive, way to determine the
   *        status of the account.
   */
  hasLocalSession() {
    return this._withCurrentAccountState(async state => {
      let data = await state.getUserAccountData(["sessionToken"]);
      return !!(data && data.sessionToken);
    });
  }

  /** Returns a promise that resolves to true if we can currently connect (ie,
   *  sign in, or re-connect after a password change) to a Firefox Account.
   *  If this returns false, the caller can assume that some UI was shown
   *  which tells the user why we could not connect.
   *
   *  Currently, the primary password being locked is the only reason why
   *  this returns false, and in this scenario, the primary password unlock
   *  dialog will have been shown.
   *
   *  This currently doesn't need to return a promise, but does so that
   *  future enhancements, such as other explanatory UI which requires
   *  async can work without modification of the call-sites.
   */
  static canConnectAccount() {
    return Promise.resolve(!lazy.mpLocked() || lazy.ensureMPUnlocked());
  }

  /**
   * Send a message to a set of devices in the same account
   *
   * @param deviceIds: (null/string/array) The device IDs to send the message to.
   *                   If null, will be sent to all devices.
   *
   * @param excludedIds: (null/string/array) If deviceIds is null, this may
   *                     list device IDs which should not receive the message.
   *
   * @param payload: (object) The payload, which will be JSON.stringified.
   *
   * @param TTL: How long the message should be retained before it is discarded.
   */
  // XXX - used only by sync to tell other devices that the clients collection
  // has changed so they should sync asap. The API here is somewhat vague (ie,
  // "an object"), but to be useful across devices, the payload really needs
  // formalizing. We should try and do something better here.
  notifyDevices(deviceIds, excludedIds, payload, TTL) {
    return this._internal.notifyDevices(deviceIds, excludedIds, payload, TTL);
  }

  /**
   * Resend the verification email for the currently signed-in user.
   *
   */
  resendVerificationEmail() {
    return this._withSessionToken((token, currentState) => {
      this._internal.startPollEmailStatus(currentState, token, "start");
      return this._internal.fxAccountsClient.resendVerificationEmail(token);
    }, false);
  }

  async signOut(localOnly) {
    // Note that we do not use _withCurrentAccountState here, otherwise we
    // end up with an exception due to the user signing out before the call is
    // complete - but that's the entire point of this method :)
    return this._internal.signOut(localOnly);
  }

  // XXX - we should consider killing this - the only reason it is public is
  // so that sync can change it when it notices the device name being changed,
  // and that could probably be replaced with a pref observer.
  updateDeviceRegistration() {
    return this._withCurrentAccountState(_ => {
      return this._internal.updateDeviceRegistration();
    });
  }

  // we should try and kill this too.
  whenVerified(data) {
    return this._withCurrentAccountState(_ => {
      return this._internal.whenVerified(data);
    });
  }

  /**
   * Generate a log file for the FxA action that just completed
   * and refresh the input & output streams.
   */
  async flushLogFile() {
    const logType = await logManager.resetFileLog();
    if (logType == logManager.ERROR_LOG_WRITTEN) {
      console.error(
        "FxA encountered an error - see about:sync-log for the log file."
      );
    }
    Services.obs.notifyObservers(null, "service:log-manager:flush-log-file");
  }
}

var FxAccountsInternal = function () {};

/**
 * The internal API's prototype.
 */
FxAccountsInternal.prototype = {
  // Make a local copy of this constant so we can mock it in testing
  POLL_SESSION,

  // The timeout (in ms) we use to poll for a verified mail for the first
  // VERIFICATION_POLL_START_SLOWDOWN_THRESHOLD minutes if the user has
  // logged-in in this session.
  VERIFICATION_POLL_TIMEOUT_INITIAL: 60000, // 1 minute.
  // All the other cases (> 5 min, on restart etc).
  VERIFICATION_POLL_TIMEOUT_SUBSEQUENT: 5 * 60000, // 5 minutes.
  // After X minutes, the polling will slow down to _SUBSEQUENT if we have
  // logged-in in this session.
  VERIFICATION_POLL_START_SLOWDOWN_THRESHOLD: 5,

  _fxAccountsClient: null,

  // All significant initialization should be done in this initialize() method
  // to help with our mocking story.
  initialize() {
    ChromeUtils.defineLazyGetter(this, "fxaPushService", function () {
      return Cc["@mozilla.org/fxaccounts/push;1"].getService(
        Ci.nsISupports
      ).wrappedJSObject;
    });

    this.keys = new lazy.FxAccountsKeys(this);

    if (!this.observerPreloads) {
      // A registry of promise-returning functions that `notifyObservers` should
      // call before sending notifications. Primarily used so parts of Firefox
      // which have yet to load for performance reasons can be force-loaded, and
      // thus not miss notifications.
      this.observerPreloads = [
        // Sync
        () => {
          let { Weave } = ChromeUtils.importESModule(
            "resource://services-sync/main.sys.mjs"
          );
          return Weave.Service.promiseInitialized;
        },
      ];
    }

    this.currentTimer = null;
    // This object holds details about, and storage for, the current user. It
    // is replaced when a different user signs in. Instead of using it directly,
    // you should try and use `withCurrentAccountState`.
    this.currentAccountState = this.newAccountState();
  },

  async withCurrentAccountState(func) {
    const state = this.currentAccountState;
    let result;
    try {
      result = await func(state);
    } catch (ex) {
      return state.reject(ex);
    }
    return state.resolve(result);
  },

  async withVerifiedAccountState(func) {
    return this.withCurrentAccountState(async state => {
      let data = await state.getUserAccountData();
      if (!data) {
        // No signed-in user
        throw this._error(ERROR_NO_ACCOUNT);
      }

      if (!this.isUserEmailVerified(data)) {
        // Signed-in user has not verified email
        throw this._error(ERROR_UNVERIFIED_ACCOUNT);
      }
      return func(state);
    });
  },

  async withSessionToken(func, mustBeVerified = true) {
    const state = this.currentAccountState;
    let data = await state.getUserAccountData();
    if (!data) {
      // No signed-in user
      throw this._error(ERROR_NO_ACCOUNT);
    }

    if (mustBeVerified && !this.isUserEmailVerified(data)) {
      // Signed-in user has not verified email
      throw this._error(ERROR_UNVERIFIED_ACCOUNT);
    }

    if (!data.sessionToken) {
      throw this._error(ERROR_AUTH_ERROR, "no session token");
    }
    try {
      // Anyone who needs the session token is going to send it to the server,
      // so there's a chance we'll see an auth related error - so handle that
      // here rather than requiring each caller to remember to.
      let result = await func(data.sessionToken, state);
      return state.resolve(result);
    } catch (err) {
      return this._handleTokenError(err);
    }
  },

  get fxAccountsClient() {
    if (!this._fxAccountsClient) {
      this._fxAccountsClient = new lazy.FxAccountsClient();
    }
    return this._fxAccountsClient;
  },

  // The profile object used to fetch the actual user profile.
  _profile: null,
  get profile() {
    if (!this._profile) {
      let profileServerUrl = Services.urlFormatter.formatURLPref(
        "identity.fxaccounts.remote.profile.uri"
      );
      this._profile = new lazy.FxAccountsProfile({
        fxa: this,
        profileServerUrl,
      });
    }
    return this._profile;
  },

  _commands: null,
  get commands() {
    if (!this._commands) {
      this._commands = new lazy.FxAccountsCommands(this);
    }
    return this._commands;
  },

  _device: null,
  get device() {
    if (!this._device) {
      this._device = new lazy.FxAccountsDevice(this);
    }
    return this._device;
  },

  _oauth: null,
  get oauth() {
    if (!this._oauth) {
      this._oauth = new lazy.FxAccountsOAuth(this.fxAccountsClient, this.keys);
    }
    return this._oauth;
  },

  _telemetry: null,
  get telemetry() {
    if (!this._telemetry) {
      this._telemetry = new lazy.FxAccountsTelemetry(this);
    }
    return this._telemetry;
  },

  beginOAuthFlow(scopes) {
    return this.oauth.beginOAuthFlow(scopes);
  },

  completeOAuthFlow(sessionToken, code, state) {
    return this.oauth.completeOAuthFlow(sessionToken, code, state);
  },

  setScopedKeys(scopedKeys) {
    return this.keys.setScopedKeys(scopedKeys);
  },

  // A hook-point for tests who may want a mocked AccountState or mocked storage.
  newAccountState(credentials) {
    let storage = new FxAccountsStorageManager();
    storage.initialize(credentials);
    return new AccountState(storage);
  },

  notifyDevices(deviceIds, excludedIds, payload, TTL) {
    if (typeof deviceIds == "string") {
      deviceIds = [deviceIds];
    }
    return this.withSessionToken(sessionToken => {
      return this.fxAccountsClient.notifyDevices(
        sessionToken,
        deviceIds,
        excludedIds,
        payload,
        TTL
      );
    });
  },

  /**
   * Return the current time in milliseconds as an integer.  Allows tests to
   * manipulate the date to simulate token expiration.
   */
  now() {
    return this.fxAccountsClient.now();
  },

  /**
   * Return clock offset in milliseconds, as reported by the fxAccountsClient.
   * This can be overridden for testing.
   *
   * The offset is the number of milliseconds that must be added to the client
   * clock to make it equal to the server clock.  For example, if the client is
   * five minutes ahead of the server, the localtimeOffsetMsec will be -300000.
   */
  get localtimeOffsetMsec() {
    return this.fxAccountsClient.localtimeOffsetMsec;
  },

  /**
   * Ask the server whether the user's email has been verified
   */
  checkEmailStatus: function checkEmailStatus(sessionToken, options = {}) {
    if (!sessionToken) {
      return Promise.reject(
        new Error("checkEmailStatus called without a session token")
      );
    }
    return this.fxAccountsClient
      .recoveryEmailStatus(sessionToken, options)
      .catch(error => this._handleTokenError(error));
  },

  // set() makes sure that polling is happening, if necessary.
  // get() does not wait for verification, and returns an object even if
  // unverified. The caller of get() must check .verified .
  // The "fxaccounts:onverified" event will fire only when the verified
  // state goes from false to true, so callers must register their observer
  // and then call get(). In particular, it will not fire when the account
  // was found to be verified in a previous boot: if our stored state says
  // the account is verified, the event will never fire. So callers must do:
  //   register notification observer (go)
  //   userdata = get()
  //   if (userdata.verified()) {go()}

  /**
   * Set the current user signed in to Firefox Accounts.
   *
   * @param credentials
   *        The credentials object obtained by logging in or creating
   *        an account on the FxA server:
   *        {
   *          authAt: The time (seconds since epoch) that this record was
   *                  authenticated
   *          email: The users email address
   *          keyFetchToken: a keyFetchToken which has not yet been used
   *          sessionToken: Session for the FxA server
   *          uid: The user's unique id
   *          unwrapBKey: used to unwrap kB, derived locally from the
   *                      password (not revealed to the FxA server)
   *          verified: true/false
   *        }
   * @return Promise
   *         The promise resolves to null when the data is saved
   *         successfully and is rejected on error.
   */
  async setSignedInUser(credentials) {
    if (!lazy.FXA_ENABLED) {
      throw new Error("Cannot call setSignedInUser when FxA is disabled.");
    }
    for (const pref of Services.prefs.getChildList(PREF_ACCOUNT_ROOT)) {
      Services.prefs.clearUserPref(pref);
    }
    log.debug("setSignedInUser - aborting any existing flows");
    const signedInUser = await this.currentAccountState.getUserAccountData();
    if (signedInUser) {
      await this._signOutServer(
        signedInUser.sessionToken,
        signedInUser.oauthTokens
      );
    }
    await this.abortExistingFlow();
    const currentAccountState = (this.currentAccountState =
      this.newAccountState(
        Cu.cloneInto(credentials, {}) // Pass a clone of the credentials object.
      ));
    // This promise waits for storage, but not for verification.
    // We're telling the caller that this is durable now (although is that
    // really something we should commit to? Why not let the write happen in
    // the background? Already does for updateAccountData ;)
    await currentAccountState.promiseInitialized;
    // Starting point for polling if new user
    if (!this.isUserEmailVerified(credentials) && !lazy.oauthEnabled) {
      this.startVerifiedCheck(credentials);
    }
    await this.notifyObservers(ONLOGIN_NOTIFICATION);
    await this.updateDeviceRegistration();
    return currentAccountState.resolve();
  },

  /**
   * Update account data for the currently signed in user.
   *
   * @param credentials
   *        The credentials object containing the fields to be updated.
   *        This object must contain the |uid| field and it must
   *        match the currently signed in user.
   */
  updateUserAccountData(credentials) {
    log.debug(
      "updateUserAccountData called with fields",
      Object.keys(credentials)
    );
    if (logPII()) {
      log.debug("updateUserAccountData called with data", credentials);
    }
    let currentAccountState = this.currentAccountState;
    return currentAccountState.promiseInitialized.then(() => {
      if (!credentials.uid) {
        throw new Error("The specified credentials have no uid");
      }
      return currentAccountState.updateUserAccountData(credentials);
    });
  },

  /*
   * Reset state such that any previous flow is canceled.
   */
  abortExistingFlow() {
    if (this.currentTimer) {
      log.debug("Polling aborted; Another user signing in");
      clearTimeout(this.currentTimer);
      this.currentTimer = 0;
    }
    if (this._profile) {
      this._profile.tearDown();
      this._profile = null;
    }
    if (this._commands) {
      this._commands = null;
    }
    if (this._device) {
      this._device.reset();
    }
    // We "abort" the accountState and assume our caller is about to throw it
    // away and replace it with a new one.
    return this.currentAccountState.abort();
  },

  async checkVerificationStatus() {
    log.trace("checkVerificationStatus");
    let state = this.currentAccountState;
    let data = await state.getUserAccountData();
    if (!data) {
      log.trace("checkVerificationStatus - no user data");
      return null;
    }

    // Always check the verification status, even if the local state indicates
    // we're already verified. If the user changed their password, the check
    // will fail, and we'll enter the reauth state.
    log.trace("checkVerificationStatus - forcing verification status check");
    return this.startPollEmailStatus(state, data.sessionToken, "push");
  },

  /** Destroyes an OAuth Token by sending a request to the FxA server
   * @param { Object } tokenData: The token's data, with `tokenData.token` being the token itself
   **/
  destroyOAuthToken(tokenData) {
    return this.fxAccountsClient.oauthDestroy(OAUTH_CLIENT_ID, tokenData.token);
  },

  _destroyAllOAuthTokens(tokenInfos) {
    if (!tokenInfos) {
      return Promise.resolve();
    }
    // let's just destroy them all in parallel...
    let promises = [];
    for (let tokenInfo of Object.values(tokenInfos)) {
      promises.push(this.destroyOAuthToken(tokenInfo));
    }
    return Promise.all(promises);
  },

  async signOut(localOnly) {
    let sessionToken;
    let tokensToRevoke;
    const data = await this.currentAccountState.getUserAccountData();
    // Save the sessionToken, tokens before resetting them in _signOutLocal().
    if (data) {
      sessionToken = data.sessionToken;
      tokensToRevoke = data.oauthTokens;
    }
    await this.notifyObservers(ON_PRELOGOUT_NOTIFICATION);
    await this._signOutLocal();
    if (!localOnly) {
      // Do this in the background so *any* slow request won't
      // block the local sign out.
      Services.tm.dispatchToMainThread(async () => {
        await this._signOutServer(sessionToken, tokensToRevoke);
        lazy.FxAccountsConfig.resetConfigURLs();
        this.notifyObservers("testhelper-fxa-signout-complete");
      });
    } else {
      // We want to do this either way -- but if we're signing out remotely we
      // need to wait until we destroy the oauth tokens if we want that to succeed.
      lazy.FxAccountsConfig.resetConfigURLs();
    }
    return this.notifyObservers(ONLOGOUT_NOTIFICATION);
  },

  async _signOutLocal() {
    for (const pref of Services.prefs.getChildList(PREF_ACCOUNT_ROOT)) {
      Services.prefs.clearUserPref(pref);
    }
    await this.currentAccountState.signOut();
    // this "aborts" this.currentAccountState but doesn't make a new one.
    await this.abortExistingFlow();
    this.currentAccountState = this.newAccountState();
    return this.currentAccountState.promiseInitialized;
  },

  async _signOutServer(sessionToken, tokensToRevoke) {
    log.debug("Unsubscribing from FxA push.");
    try {
      await this.fxaPushService.unsubscribe();
    } catch (err) {
      log.error("Could not unsubscribe from push.", err);
    }
    if (sessionToken) {
      log.debug("Destroying session and device.");
      try {
        await this.fxAccountsClient.signOut(sessionToken, { service: "sync" });
      } catch (err) {
        log.error("Error during remote sign out of Firefox Accounts", err);
      }
    } else {
      log.warn("Missing session token; skipping remote sign out");
    }
    log.debug("Destroying all OAuth tokens.");
    try {
      await this._destroyAllOAuthTokens(tokensToRevoke);
    } catch (err) {
      log.error("Error during destruction of oauth tokens during signout", err);
    }
  },

  getUserAccountData(fieldNames = null) {
    return this.currentAccountState.getUserAccountData(fieldNames);
  },

  isUserEmailVerified: function isUserEmailVerified(data) {
    return !!(data && data.verified);
  },

  /**
   * Setup for and if necessary do email verification polling.
   */
  loadAndPoll() {
    let currentState = this.currentAccountState;
    return currentState.getUserAccountData().then(data => {
      if (data) {
        if (!this.isUserEmailVerified(data)) {
          this.startPollEmailStatus(
            currentState,
            data.sessionToken,
            "browser-startup"
          );
        }
      }
      return data;
    });
  },

  startVerifiedCheck(data) {
    log.debug("startVerifiedCheck", data && data.verified);
    if (logPII()) {
      log.debug("startVerifiedCheck with user data", data);
    }

    // Get us to the verified state. This returns a promise that will fire when
    // verification is complete.

    // The callers of startVerifiedCheck never consume a returned promise (ie,
    // this is simply kicking off a background fetch) so we must add a rejection
    // handler to avoid runtime warnings about the rejection not being handled.
    this.whenVerified(data).catch(err =>
      log.info("startVerifiedCheck promise was rejected: " + err)
    );
  },

  whenVerified(data) {
    let currentState = this.currentAccountState;
    if (data.verified) {
      log.debug("already verified");
      return currentState.resolve(data);
    }
    if (!currentState.whenVerifiedDeferred) {
      log.debug("whenVerified promise starts polling for verified email");
      this.startPollEmailStatus(currentState, data.sessionToken, "start");
    }
    return currentState.whenVerifiedDeferred.promise.then(result =>
      currentState.resolve(result)
    );
  },

  async notifyObservers(topic, data) {
    for (let f of this.observerPreloads) {
      try {
        await f();
      } catch (O_o) {}
    }
    log.debug("Notifying observers of " + topic);
    Services.obs.notifyObservers(null, topic, data);
  },

  startPollEmailStatus(currentState, sessionToken, why) {
    log.debug("entering startPollEmailStatus: " + why);
    // If we were already polling, stop and start again.  This could happen
    // if the user requested the verification email to be resent while we
    // were already polling for receipt of an earlier email.
    if (this.currentTimer) {
      log.debug(
        "startPollEmailStatus starting while existing timer is running"
      );
      clearTimeout(this.currentTimer);
      this.currentTimer = null;
    }

    this.pollStartDate = Date.now();
    if (!currentState.whenVerifiedDeferred) {
      currentState.whenVerifiedDeferred = Promise.withResolvers();
      // This deferred might not end up with any handlers (eg, if sync
      // is yet to start up.)  This might cause "A promise chain failed to
      // handle a rejection" messages, so add an error handler directly
      // on the promise to log the error.
      currentState.whenVerifiedDeferred.promise.then(
        () => {
          log.info("the user became verified");
          // We are now ready for business. This should only be invoked once
          // per setSignedInUser(), regardless of whether we've rebooted since
          // setSignedInUser() was called.
          this.notifyObservers(ONVERIFIED_NOTIFICATION);
        },
        err => {
          log.info("the wait for user verification was stopped: " + err);
        }
      );
    }
    return this.pollEmailStatus(currentState, sessionToken, why);
  },

  // We return a promise for testing only. Other callers can ignore this,
  // since verification polling continues in the background.
  async pollEmailStatus(currentState, sessionToken, why) {
    log.debug("entering pollEmailStatus: " + why);
    let nextPollMs;
    try {
      const response = await this.checkEmailStatus(sessionToken, {
        reason: why,
      });
      log.debug("checkEmailStatus -> " + JSON.stringify(response));
      if (response && response.verified) {
        await this.onPollEmailSuccess(currentState);
        return;
      }
    } catch (error) {
      if (error && error.code && error.code == 401) {
        let error = new Error("Verification status check failed");
        this._rejectWhenVerified(currentState, error);
        return;
      }
      if (error && error.retryAfter) {
        // If the server told us to back off, back off the requested amount.
        nextPollMs = (error.retryAfter + 3) * 1000;
        log.warn(
          `the server rejected our email status check and told us to try again in ${nextPollMs}ms`
        );
      } else {
        log.error(`checkEmailStatus failed to poll`, error);
      }
    }
    if (why == "push") {
      return;
    }
    let pollDuration = Date.now() - this.pollStartDate;
    // Polling session expired.
    if (pollDuration >= this.POLL_SESSION) {
      if (currentState.whenVerifiedDeferred) {
        let error = new Error("User email verification timed out.");
        this._rejectWhenVerified(currentState, error);
      }
      log.debug("polling session exceeded, giving up");
      return;
    }
    // Poll email status again after a short delay.
    if (nextPollMs === undefined) {
      let currentMinute = Math.ceil(pollDuration / 60000);
      nextPollMs =
        why == "start" &&
        currentMinute < this.VERIFICATION_POLL_START_SLOWDOWN_THRESHOLD
          ? this.VERIFICATION_POLL_TIMEOUT_INITIAL
          : this.VERIFICATION_POLL_TIMEOUT_SUBSEQUENT;
    }
    this._scheduleNextPollEmailStatus(
      currentState,
      sessionToken,
      nextPollMs,
      why
    );
  },

  // Easy-to-mock testable method
  _scheduleNextPollEmailStatus(currentState, sessionToken, nextPollMs, why) {
    log.debug("polling with timeout = " + nextPollMs);
    this.currentTimer = setTimeout(() => {
      this.pollEmailStatus(currentState, sessionToken, why);
    }, nextPollMs);
  },

  async onPollEmailSuccess(currentState) {
    try {
      await currentState.updateUserAccountData({ verified: true });
      const accountData = await currentState.getUserAccountData();
      this._setLastUserPref(accountData.email);
      // Now that the user is verified, we can proceed to fetch keys
      if (currentState.whenVerifiedDeferred) {
        currentState.whenVerifiedDeferred.resolve(accountData);
        delete currentState.whenVerifiedDeferred;
      }
    } catch (e) {
      log.error(e);
    }
  },

  _rejectWhenVerified(currentState, error) {
    currentState.whenVerifiedDeferred.reject(error);
    delete currentState.whenVerifiedDeferred;
  },

  /**
   * Does the actual fetch of an oauth token for getOAuthToken()
   * using the account session token.
   *
   * It's split out into a separate method so that we can easily
   * stash in-flight calls in a cache.
   *
   * @param {String} scopeString
   * @param {Number} ttl
   * @returns {Promise<string>}
   * @private
   */
  async _doTokenFetchWithSessionToken(sessionToken, scopeString, ttl) {
    const result = await this.fxAccountsClient.accessTokenWithSessionToken(
      sessionToken,
      OAUTH_CLIENT_ID,
      scopeString,
      ttl
    );
    return result.access_token;
  },

  getOAuthToken(options = {}) {
    log.debug("getOAuthToken enter");
    let scope = options.scope;
    if (typeof scope === "string") {
      scope = [scope];
    }

    if (!scope || !scope.length) {
      return Promise.reject(
        this._error(
          ERROR_INVALID_PARAMETER,
          "Missing or invalid 'scope' option"
        )
      );
    }

    return this.withSessionToken(async (sessionToken, currentState) => {
      // Early exit for a cached token.
      let cached = currentState.getCachedToken(scope);
      if (cached) {
        log.debug("getOAuthToken returning a cached token");
        return cached.token;
      }

      // Build the string we use in our "inflight" map and that we send to the
      // server. Because it's used as a key in the map we sort the scopes.
      let scopeString = scope.sort().join(" ");

      // We keep a map of in-flight requests to avoid multiple promise-based
      // consumers concurrently requesting the same token.
      let maybeInFlight = currentState.inFlightTokenRequests.get(scopeString);
      if (maybeInFlight) {
        log.debug("getOAuthToken has an in-flight request for this scope");
        return maybeInFlight;
      }

      // We need to start a new fetch and stick the promise in our in-flight map
      // and remove it when it resolves.
      let promise = this._doTokenFetchWithSessionToken(
        sessionToken,
        scopeString,
        options.ttl
      )
        .then(token => {
          // As a sanity check, ensure something else hasn't raced getting a token
          // of the same scope. If something has we just make noise rather than
          // taking any concrete action because it should never actually happen.
          if (currentState.getCachedToken(scope)) {
            log.error(`detected a race for oauth token with scope ${scope}`);
          }
          // If we got one, cache it.
          if (token) {
            let entry = { token };
            currentState.setCachedToken(scope, entry);
          }
          return token;
        })
        .finally(() => {
          // Remove ourself from the in-flight map. There's no need to check the
          // result of .delete() to handle a signout race, because setCachedToken
          // above will fail in that case and cause the entire call to fail.
          currentState.inFlightTokenRequests.delete(scopeString);
        });

      currentState.inFlightTokenRequests.set(scopeString, promise);
      return promise;
    });
  },

  /**
   * Remove an OAuth token from the token cache
   * and makes a network request to FxA server to destroy the token.
   *
   * @param options
   *        {
   *          token: (string) A previously fetched token.
   *        }
   * @return Promise.<undefined> This function will always resolve, even if
   *         an unknown token is passed.
   */
  removeCachedOAuthToken(options) {
    if (!options.token || typeof options.token !== "string") {
      throw this._error(
        ERROR_INVALID_PARAMETER,
        "Missing or invalid 'token' option"
      );
    }
    return this.withCurrentAccountState(currentState => {
      let existing = currentState.removeCachedToken(options.token);
      if (existing) {
        // background destroy.
        this.destroyOAuthToken(existing).catch(err => {
          log.warn("FxA failed to revoke a cached token", err);
        });
      }
    });
  },

  /** Sets the user to be verified in the account state,
   * This prevents any polling for the user's verification state from the FxA server
   **/
  async setUserVerified() {
    await this.withCurrentAccountState(async currentState => {
      const userData = await currentState.getUserAccountData();
      if (!userData.verified) {
        await currentState.updateUserAccountData({ verified: true });
      }
    });
    await this.notifyObservers(ONVERIFIED_NOTIFICATION);
  },

  async _getVerifiedAccountOrReject() {
    let data = await this.currentAccountState.getUserAccountData();
    if (!data) {
      // No signed-in user
      throw this._error(ERROR_NO_ACCOUNT);
    }
    if (!this.isUserEmailVerified(data)) {
      // Signed-in user has not verified email
      throw this._error(ERROR_UNVERIFIED_ACCOUNT);
    }
    return data;
  },

  // _handle* methods used by push, used when the account/device status is
  // changed on a different device.
  async _handleAccountDestroyed(uid) {
    let state = this.currentAccountState;
    const accountData = await state.getUserAccountData();
    const localUid = accountData ? accountData.uid : null;
    if (!localUid) {
      log.info(
        `Account destroyed push notification received, but we're already logged-out`
      );
      return null;
    }
    if (uid == localUid) {
      const data = JSON.stringify({ isLocalDevice: true });
      await this.notifyObservers(ON_DEVICE_DISCONNECTED_NOTIFICATION, data);
      return this.signOut(true);
    }
    log.info(
      `The destroyed account uid doesn't match with the local uid. ` +
        `Local: ${localUid}, account uid destroyed: ${uid}`
    );
    return null;
  },

  async _handleDeviceDisconnection(deviceId) {
    let state = this.currentAccountState;
    const accountData = await state.getUserAccountData();
    if (!accountData || !accountData.device) {
      // Nothing we can do here.
      return;
    }
    const localDeviceId = accountData.device.id;
    const isLocalDevice = deviceId == localDeviceId;
    if (isLocalDevice) {
      this.signOut(true);
    }
    const data = JSON.stringify({ isLocalDevice });
    await this.notifyObservers(ON_DEVICE_DISCONNECTED_NOTIFICATION, data);
  },

  _setLastUserPref(newEmail) {
    Services.prefs.setStringPref(
      PREF_LAST_FXA_USER,
      CryptoUtils.sha256Base64(newEmail)
    );
  },

  async _handleEmailUpdated(newEmail) {
    this._setLastUserPref(newEmail);
    await this.currentAccountState.updateUserAccountData({ email: newEmail });
  },

  /*
   * Coerce an error into one of the general error cases:
   *          NETWORK_ERROR
   *          AUTH_ERROR
   *          UNKNOWN_ERROR
   *
   * These errors will pass through:
   *          INVALID_PARAMETER
   *          NO_ACCOUNT
   *          UNVERIFIED_ACCOUNT
   */
  _errorToErrorClass(aError) {
    if (aError.errno) {
      let error = SERVER_ERRNO_TO_ERROR[aError.errno];
      return this._error(
        ERROR_TO_GENERAL_ERROR_CLASS[error] || ERROR_UNKNOWN,
        aError
      );
    } else if (
      aError.message &&
      (aError.message === "INVALID_PARAMETER" ||
        aError.message === "NO_ACCOUNT" ||
        aError.message === "UNVERIFIED_ACCOUNT" ||
        aError.message === "AUTH_ERROR")
    ) {
      return aError;
    }
    return this._error(ERROR_UNKNOWN, aError);
  },

  _error(aError, aDetails) {
    log.error("FxA rejecting with error ${aError}, details: ${aDetails}", {
      aError,
      aDetails,
    });
    let reason = new Error(aError);
    if (aDetails) {
      reason.details = aDetails;
    }
    return reason;
  },

  // Attempt to update the auth server with whatever device details are stored
  // in the account data. Returns a promise that always resolves, never rejects.
  // If the promise resolves to a value, that value is the device id.
  updateDeviceRegistration() {
    return this.device.updateDeviceRegistration();
  },

  /**
   * Delete all the persisted credentials we store for FxA. After calling
   * this, the user will be forced to re-authenticate to continue.
   *
   * @return Promise resolves when the user data has been persisted
   */
  dropCredentials(state) {
    // Delete all fields except those required for the user to
    // reauthenticate.
    let updateData = {};
    let clearField = field => {
      if (!FXA_PWDMGR_REAUTH_ALLOWLIST.has(field)) {
        updateData[field] = null;
      }
    };
    FXA_PWDMGR_PLAINTEXT_FIELDS.forEach(clearField);
    FXA_PWDMGR_SECURE_FIELDS.forEach(clearField);

    return state.updateUserAccountData(updateData);
  },

  async checkAccountStatus(state) {
    log.info("checking account status...");
    let data = await state.getUserAccountData(["uid", "sessionToken"]);
    if (!data) {
      log.info("account status: no user");
      return false;
    }
    // If we have a session token, then check if that remains valid - if this
    // works we know the account must also be OK.
    if (data.sessionToken) {
      if (await this.fxAccountsClient.sessionStatus(data.sessionToken)) {
        log.info("account status: ok");
        return true;
      }
    }
    let exists = await this.fxAccountsClient.accountStatus(data.uid);
    if (!exists) {
      // Delete all local account data. Since the account no longer
      // exists, we can skip the remote calls.
      log.info("account status: deleted");
      await this._handleAccountDestroyed(data.uid);
    } else {
      // Note that we may already have been in a "needs reauth" state (ie, if
      // this function was called when we already had no session token), but
      // that's OK - re-notifying etc should cause no harm.
      log.info("account status: needs reauthentication");
      await this.dropCredentials(this.currentAccountState);
      // Notify the account state has changed so the UI updates.
      await this.notifyObservers(ON_ACCOUNT_STATE_CHANGE_NOTIFICATION);
    }
    return false;
  },

  async _handleTokenError(err) {
    if (!err || err.code != 401 || err.errno != ERRNO_INVALID_AUTH_TOKEN) {
      throw err;
    }
    log.warn("handling invalid token error", err);
    // Note that we don't use `withCurrentAccountState` here as that will cause
    // an error to be thrown if we sign out due to the account not existing.
    let state = this.currentAccountState;
    let ok = await this.checkAccountStatus(state);
    if (ok) {
      log.warn("invalid token error, but account state appears ok?");
    }
    // always re-throw the error.
    throw err;
  },
};

let fxAccountsSingleton = null;

export function getFxAccountsSingleton() {
  if (fxAccountsSingleton) {
    return fxAccountsSingleton;
  }

  fxAccountsSingleton = new FxAccounts();

  // XXX Bug 947061 - We need a strategy for resuming email verification after
  // browser restart
  fxAccountsSingleton._internal.loadAndPoll();

  return fxAccountsSingleton;
}

// `AccountState` is exported for tests.
PK
!<�w/�D@D@%modules/services-crypto/utils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Observers } from "resource://services-common/observers.sys.mjs";

import { CommonUtils } from "resource://services-common/utils.sys.mjs";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "textEncoder", function () {
  return new TextEncoder();
});

/**
 * A number of `Legacy` suffixed functions are exposed by CryptoUtils.
 * They work with octet strings, which were used before Javascript
 * got ArrayBuffer and friends.
 */
export var CryptoUtils = {
  xor(a, b) {
    let bytes = [];

    if (a.length != b.length) {
      throw new Error(
        "can't xor unequal length strings: " + a.length + " vs " + b.length
      );
    }

    for (let i = 0; i < a.length; i++) {
      bytes[i] = a.charCodeAt(i) ^ b.charCodeAt(i);
    }

    return String.fromCharCode.apply(String, bytes);
  },

  /**
   * Generate a string of random bytes.
   * @returns {String} Octet string
   */
  generateRandomBytesLegacy(length) {
    let bytes = CryptoUtils.generateRandomBytes(length);
    return CommonUtils.arrayBufferToByteString(bytes);
  },

  generateRandomBytes(length) {
    return crypto.getRandomValues(new Uint8Array(length));
  },

  /**
   * UTF8-encode a message and hash it with the given hasher. Returns a
   * string containing bytes.
   */
  digestUTF8(message, hasher) {
    let data = lazy.textEncoder.encode(message);
    hasher.update(data, data.length);
    let result = hasher.finish(false);
    return result;
  },

  /**
   * Treat the given message as a bytes string (if necessary) and hash it with
   * the given hasher. Returns a string containing bytes.
   */
  digestBytes(bytes, hasher) {
    if (typeof bytes == "string" || bytes instanceof String) {
      bytes = CommonUtils.byteStringToArrayBuffer(bytes);
    }
    return CryptoUtils.digestBytesArray(bytes, hasher);
  },

  digestBytesArray(bytes, hasher) {
    hasher.update(bytes, bytes.length);
    let result = hasher.finish(false);
    return result;
  },

  /**
   * Encode the message into UTF-8 and feed the resulting bytes into the
   * given hasher. Does not return a hash. This can be called multiple times
   * with a single hasher, but eventually you must extract the result
   * yourself.
   */
  updateUTF8(message, hasher) {
    let bytes = lazy.textEncoder.encode(message);
    hasher.update(bytes, bytes.length);
  },

  sha256(message) {
    let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
      Ci.nsICryptoHash
    );
    hasher.init(hasher.SHA256);
    return CommonUtils.bytesAsHex(CryptoUtils.digestUTF8(message, hasher));
  },

  sha256Base64(message) {
    let data = lazy.textEncoder.encode(message);
    let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
      Ci.nsICryptoHash
    );
    hasher.init(hasher.SHA256);
    hasher.update(data, data.length);
    return hasher.finish(true);
  },

  /**
   * @param {string} alg Hash algorithm (common values are SHA-1 or SHA-256)
   * @param {string} key Key as an octet string.
   * @param {string} data Data as an octet string.
   */
  async hmacLegacy(alg, key, data) {
    if (!key || !key.length) {
      key = "\0";
    }
    data = CommonUtils.byteStringToArrayBuffer(data);
    key = CommonUtils.byteStringToArrayBuffer(key);
    const result = await CryptoUtils.hmac(alg, key, data);
    return CommonUtils.arrayBufferToByteString(result);
  },

  /**
   * @param {string} ikm IKM as an octet string.
   * @param {string} salt Salt as an Hex string.
   * @param {string} info Info as a regular string.
   * @param {Number} len Desired output length in bytes.
   */
  async hkdfLegacy(ikm, xts, info, len) {
    ikm = CommonUtils.byteStringToArrayBuffer(ikm);
    xts = CommonUtils.byteStringToArrayBuffer(xts);
    info = lazy.textEncoder.encode(info);
    const okm = await CryptoUtils.hkdf(ikm, xts, info, len);
    return CommonUtils.arrayBufferToByteString(okm);
  },

  /**
   * @param {String} alg Hash algorithm (common values are SHA-1 or SHA-256)
   * @param {ArrayBuffer} key
   * @param {ArrayBuffer} data
   * @param {Number} len Desired output length in bytes.
   * @returns {Uint8Array}
   */
  async hmac(alg, key, data) {
    const hmacKey = await crypto.subtle.importKey(
      "raw",
      key,
      { name: "HMAC", hash: alg },
      false,
      ["sign"]
    );
    const result = await crypto.subtle.sign("HMAC", hmacKey, data);
    return new Uint8Array(result);
  },

  /**
   * @param {ArrayBuffer} ikm
   * @param {ArrayBuffer} salt
   * @param {ArrayBuffer} info
   * @param {Number} len Desired output length in bytes.
   * @returns {Uint8Array}
   */
  async hkdf(ikm, salt, info, len) {
    const key = await crypto.subtle.importKey(
      "raw",
      ikm,
      { name: "HKDF" },
      false,
      ["deriveBits"]
    );
    const okm = await crypto.subtle.deriveBits(
      {
        name: "HKDF",
        hash: "SHA-256",
        salt,
        info,
      },
      key,
      len * 8
    );
    return new Uint8Array(okm);
  },

  /**
   * PBKDF2 password stretching with SHA-256 hmac.
   *
   * @param {string} passphrase Passphrase as an octet string.
   * @param {string} salt Salt as an octet string.
   * @param {string} iterations Number of iterations, a positive integer.
   * @param {string} len Desired output length in bytes.
   */
  async pbkdf2Generate(passphrase, salt, iterations, len) {
    passphrase = CommonUtils.byteStringToArrayBuffer(passphrase);
    salt = CommonUtils.byteStringToArrayBuffer(salt);
    const key = await crypto.subtle.importKey(
      "raw",
      passphrase,
      { name: "PBKDF2" },
      false,
      ["deriveBits"]
    );
    const output = await crypto.subtle.deriveBits(
      {
        name: "PBKDF2",
        hash: "SHA-256",
        salt,
        iterations,
      },
      key,
      len * 8
    );
    return CommonUtils.arrayBufferToByteString(new Uint8Array(output));
  },

  /**
   * Compute the HTTP MAC SHA-1 for an HTTP request.
   *
   * @param  identifier
   *         (string) MAC Key Identifier.
   * @param  key
   *         (string) MAC Key.
   * @param  method
   *         (string) HTTP request method.
   * @param  URI
   *         (nsIURI) HTTP request URI.
   * @param  extra
   *         (object) Optional extra parameters. Valid keys are:
   *           nonce_bytes - How many bytes the nonce should be. This defaults
   *             to 8. Note that this many bytes are Base64 encoded, so the
   *             string length of the nonce will be longer than this value.
   *           ts - Timestamp to use. Should only be defined for testing.
   *           nonce - String nonce. Should only be defined for testing as this
   *             function will generate a cryptographically secure random one
   *             if not defined.
   *           ext - Extra string to be included in MAC. Per the HTTP MAC spec,
   *             the format is undefined and thus application specific.
   * @returns
   *         (object) Contains results of operation and input arguments (for
   *           symmetry). The object has the following keys:
   *
   *           identifier - (string) MAC Key Identifier (from arguments).
   *           key - (string) MAC Key (from arguments).
   *           method - (string) HTTP request method (from arguments).
   *           hostname - (string) HTTP hostname used (derived from arguments).
   *           port - (string) HTTP port number used (derived from arguments).
   *           mac - (string) Raw HMAC digest bytes.
   *           getHeader - (function) Call to obtain the string Authorization
   *             header value for this invocation.
   *           nonce - (string) Nonce value used.
   *           ts - (number) Integer seconds since Unix epoch that was used.
   */
  async computeHTTPMACSHA1(identifier, key, method, uri, extra) {
    let ts = extra && extra.ts ? extra.ts : Math.floor(Date.now() / 1000);
    let nonce_bytes = extra && extra.nonce_bytes > 0 ? extra.nonce_bytes : 8;

    // We are allowed to use more than the Base64 alphabet if we want.
    let nonce =
      extra && extra.nonce
        ? extra.nonce
        : btoa(CryptoUtils.generateRandomBytesLegacy(nonce_bytes));

    let host = uri.asciiHost;
    let port;
    let usedMethod = method.toUpperCase();

    if (uri.port != -1) {
      port = uri.port;
    } else if (uri.scheme == "http") {
      port = "80";
    } else if (uri.scheme == "https") {
      port = "443";
    } else {
      throw new Error("Unsupported URI scheme: " + uri.scheme);
    }

    let ext = extra && extra.ext ? extra.ext : "";

    let requestString =
      ts.toString(10) +
      "\n" +
      nonce +
      "\n" +
      usedMethod +
      "\n" +
      uri.pathQueryRef +
      "\n" +
      host +
      "\n" +
      port +
      "\n" +
      ext +
      "\n";

    const mac = await CryptoUtils.hmacLegacy("SHA-1", key, requestString);

    function getHeader() {
      return CryptoUtils.getHTTPMACSHA1Header(
        this.identifier,
        this.ts,
        this.nonce,
        this.mac,
        this.ext
      );
    }

    return {
      identifier,
      key,
      method: usedMethod,
      hostname: host,
      port,
      mac,
      nonce,
      ts,
      ext,
      getHeader,
    };
  },

  /**
   * Obtain the HTTP MAC Authorization header value from fields.
   *
   * @param  identifier
   *         (string) MAC key identifier.
   * @param  ts
   *         (number) Integer seconds since Unix epoch.
   * @param  nonce
   *         (string) Nonce value.
   * @param  mac
   *         (string) Computed HMAC digest (raw bytes).
   * @param  ext
   *         (optional) (string) Extra string content.
   * @returns
   *         (string) Value to put in Authorization header.
   */
  getHTTPMACSHA1Header: function getHTTPMACSHA1Header(
    identifier,
    ts,
    nonce,
    mac,
    ext
  ) {
    let header =
      'MAC id="' +
      identifier +
      '", ' +
      'ts="' +
      ts +
      '", ' +
      'nonce="' +
      nonce +
      '", ' +
      'mac="' +
      btoa(mac) +
      '"';

    if (!ext) {
      return header;
    }

    return (header += ', ext="' + ext + '"');
  },

  /**
   * Given an HTTP header value, strip out any attributes.
   */

  stripHeaderAttributes(value) {
    value = value || "";
    let i = value.indexOf(";");
    return value
      .substring(0, i >= 0 ? i : undefined)
      .trim()
      .toLowerCase();
  },

  /**
   * Compute the HAWK client values (mostly the header) for an HTTP request.
   *
   * @param  URI
   *         (nsIURI) HTTP request URI.
   * @param  method
   *         (string) HTTP request method.
   * @param  options
   *         (object) extra parameters (all but "credentials" are optional):
   *           credentials - (object, mandatory) HAWK credentials object.
   *             All three keys are required:
   *             id - (string) key identifier
   *             key - (string) raw key bytes
   *           ext - (string) application-specific data, included in MAC
   *           localtimeOffsetMsec - (number) local clock offset (vs server)
   *           payload - (string) payload to include in hash, containing the
   *                     HTTP request body. If not provided, the HAWK hash
   *                     will not cover the request body, and the server
   *                     should not check it either. This will be UTF-8
   *                     encoded into bytes before hashing. This function
   *                     cannot handle arbitrary binary data, sorry (the
   *                     UTF-8 encoding process will corrupt any codepoints
   *                     between U+0080 and U+00FF). Callers must be careful
   *                     to use an HTTP client function which encodes the
   *                     payload exactly the same way, otherwise the hash
   *                     will not match.
   *           contentType - (string) payload Content-Type. This is included
   *                         (without any attributes like "charset=") in the
   *                         HAWK hash. It does *not* affect interpretation
   *                         of the "payload" property.
   *           hash - (base64 string) pre-calculated payload hash. If
   *                  provided, "payload" is ignored.
   *           ts - (number) pre-calculated timestamp, secs since epoch
   *           now - (number) current time, ms-since-epoch, for tests
   *           nonce - (string) pre-calculated nonce. Should only be defined
   *                   for testing as this function will generate a
   *                   cryptographically secure random one if not defined.
   * @returns
   *         Promise<Object> Contains results of operation. The object has the
   *         following keys:
   *           field - (string) HAWK header, to use in Authorization: header
   *           artifacts - (object) other generated values:
   *             ts - (number) timestamp, in seconds since epoch
   *             nonce - (string)
   *             method - (string)
   *             resource - (string) path plus querystring
   *             host - (string)
   *             port - (number)
   *             hash - (string) payload hash (base64)
   *             ext - (string) app-specific data
   *             MAC - (string) request MAC (base64)
   */
  async computeHAWK(uri, method, options) {
    let credentials = options.credentials;
    let ts =
      options.ts ||
      Math.floor(
        ((options.now || Date.now()) + (options.localtimeOffsetMsec || 0)) /
          1000
      );
    let port;
    if (uri.port != -1) {
      port = uri.port;
    } else if (uri.scheme == "http") {
      port = 80;
    } else if (uri.scheme == "https") {
      port = 443;
    } else {
      throw new Error("Unsupported URI scheme: " + uri.scheme);
    }

    let artifacts = {
      ts,
      nonce: options.nonce || btoa(CryptoUtils.generateRandomBytesLegacy(8)),
      method: method.toUpperCase(),
      resource: uri.pathQueryRef, // This includes both path and search/queryarg.
      host: uri.asciiHost.toLowerCase(), // This includes punycoding.
      port: port.toString(10),
      hash: options.hash,
      ext: options.ext,
    };

    let contentType = CryptoUtils.stripHeaderAttributes(options.contentType);

    if (
      !artifacts.hash &&
      options.hasOwnProperty("payload") &&
      options.payload
    ) {
      const buffer = lazy.textEncoder.encode(
        `hawk.1.payload\n${contentType}\n${options.payload}\n`
      );
      const hash = await crypto.subtle.digest("SHA-256", buffer);
      // HAWK specifies this .hash to use +/ (not _-) and include the
      // trailing "==" padding.
      artifacts.hash = ChromeUtils.base64URLEncode(hash, { pad: true })
        .replace(/-/g, "+")
        .replace(/_/g, "/");
    }

    let requestString =
      "hawk.1.header\n" +
      artifacts.ts.toString(10) +
      "\n" +
      artifacts.nonce +
      "\n" +
      artifacts.method +
      "\n" +
      artifacts.resource +
      "\n" +
      artifacts.host +
      "\n" +
      artifacts.port +
      "\n" +
      (artifacts.hash || "") +
      "\n";
    if (artifacts.ext) {
      requestString += artifacts.ext.replace("\\", "\\\\").replace("\n", "\\n");
    }
    requestString += "\n";

    const hash = await CryptoUtils.hmacLegacy(
      "SHA-256",
      credentials.key,
      requestString
    );
    artifacts.mac = btoa(hash);
    // The output MAC uses "+" and "/", and padded== .

    function escape(attribute) {
      // This is used for "x=y" attributes inside HTTP headers.
      return attribute.replace(/\\/g, "\\\\").replace(/\"/g, '\\"');
    }
    let header =
      'Hawk id="' +
      credentials.id +
      '", ' +
      'ts="' +
      artifacts.ts +
      '", ' +
      'nonce="' +
      artifacts.nonce +
      '", ' +
      (artifacts.hash ? 'hash="' + artifacts.hash + '", ' : "") +
      (artifacts.ext ? 'ext="' + escape(artifacts.ext) + '", ' : "") +
      'mac="' +
      artifacts.mac +
      '"';
    return {
      artifacts,
      field: header,
    };
  },
};

var Svc = {};

Observers.add("xpcom-shutdown", function unloadServices() {
  Observers.remove("xpcom-shutdown", unloadServices);

  for (let k in Svc) {
    delete Svc[k];
  }
});
PK
!<�[g]XX!modules/FxAccountsStorage.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import {
  DATA_FORMAT_VERSION,
  DEFAULT_STORAGE_FILENAME,
  FXA_PWDMGR_HOST,
  FXA_PWDMGR_PLAINTEXT_FIELDS,
  FXA_PWDMGR_REALM,
  FXA_PWDMGR_SECURE_FIELDS,
  log,
} from "resource://gre/modules/FxAccountsCommon.sys.mjs";

// A helper function so code can check what fields are able to be stored by
// the storage manager without having a reference to a manager instance.
export function FxAccountsStorageManagerCanStoreField(fieldName) {
  return (
    FXA_PWDMGR_PLAINTEXT_FIELDS.has(fieldName) ||
    FXA_PWDMGR_SECURE_FIELDS.has(fieldName)
  );
}

// The storage manager object.
export var FxAccountsStorageManager = function (options = {}) {
  this.options = {
    filename: options.filename || DEFAULT_STORAGE_FILENAME,
    baseDir: options.baseDir || Services.dirsvc.get("ProfD", Ci.nsIFile).path,
  };
  this.plainStorage = new JSONStorage(this.options);
  // Tests may want to pretend secure storage isn't available.
  let useSecure = "useSecure" in options ? options.useSecure : true;
  if (useSecure) {
    this.secureStorage = new LoginManagerStorage();
  } else {
    this.secureStorage = null;
  }
  this._clearCachedData();
  // See .initialize() below - this protects against it not being called.
  this._promiseInitialized = Promise.reject("initialize not called");
  // A promise to avoid storage races - see _queueStorageOperation
  this._promiseStorageComplete = Promise.resolve();
};

FxAccountsStorageManager.prototype = {
  _initialized: false,
  _needToReadSecure: true,

  // An initialization routine that *looks* synchronous to the callers, but
  // is actually async as everything else waits for it to complete.
  initialize(accountData) {
    if (this._initialized) {
      throw new Error("already initialized");
    }
    this._initialized = true;
    // If we just throw away our pre-rejected promise it is reported as an
    // unhandled exception when it is GCd - so add an empty .catch handler here
    // to prevent this.
    this._promiseInitialized.catch(() => {});
    this._promiseInitialized = this._initialize(accountData);
  },

  async _initialize(accountData) {
    log.trace("initializing new storage manager");
    try {
      if (accountData) {
        // If accountData is passed we don't need to read any storage.
        this._needToReadSecure = false;
        // split it into the 2 parts, write it and we are done.
        for (let [name, val] of Object.entries(accountData)) {
          if (FXA_PWDMGR_PLAINTEXT_FIELDS.has(name)) {
            this.cachedPlain[name] = val;
          } else if (FXA_PWDMGR_SECURE_FIELDS.has(name)) {
            this.cachedSecure[name] = val;
          } else {
            // Unknown fields are silently discarded, because there is no way
            // for them to be read back later.
            log.error(
              "Unknown FxA field name in user data, it will be ignored",
              name
            );
          }
        }
        // write it out and we are done.
        await this._write();
        return;
      }
      // So we were initialized without account data - that means we need to
      // read the state from storage. We try and read plain storage first and
      // only attempt to read secure storage if the plain storage had a user.
      this._needToReadSecure = await this._readPlainStorage();
      if (this._needToReadSecure && this.secureStorage) {
        await this._doReadAndUpdateSecure();
      }
    } finally {
      log.trace("initializing of new storage manager done");
    }
  },

  finalize() {
    // We can't throw this instance away while it is still writing or we may
    // end up racing with the newly created one.
    log.trace("StorageManager finalizing");
    return this._promiseInitialized
      .then(() => {
        return this._promiseStorageComplete;
      })
      .then(() => {
        this._promiseStorageComplete = null;
        this._promiseInitialized = null;
        this._clearCachedData();
        log.trace("StorageManager finalized");
      });
  },

  // We want to make sure we don't end up doing multiple storage requests
  // concurrently - which has a small window for reads if the master-password
  // is locked at initialization time and becomes unlocked later, and always
  // has an opportunity for updates.
  // We also want to make sure we finished writing when finalizing, so we
  // can't accidentally end up with the previous user's write finishing after
  // a signOut attempts to clear it.
  // So all such operations "queue" themselves via this.
  _queueStorageOperation(func) {
    // |result| is the promise we return - it has no .catch handler, so callers
    // of the storage operation still see failure as a normal rejection.
    let result = this._promiseStorageComplete.then(func);
    // But the promise we assign to _promiseStorageComplete *does* have a catch
    // handler so that rejections in one storage operation does not prevent
    // future operations from starting (ie, _promiseStorageComplete must never
    // be in a rejected state)
    this._promiseStorageComplete = result.catch(err => {
      log.error("${func} failed: ${err}", { func, err });
    });
    return result;
  },

  // Get the account data by combining the plain and secure storage.
  // If fieldNames is specified, it may be a string or an array of strings,
  // and only those fields are returned. If not specified the entire account
  // data is returned except for "in memory" fields. Note that not specifying
  // field names will soon be deprecated/removed - we want all callers to
  // specify the fields they care about.
  async getAccountData(fieldNames = null) {
    await this._promiseInitialized;
    // We know we are initialized - this means our .cachedPlain is accurate
    // and doesn't need to be read (it was read if necessary by initialize).
    // So if there's no uid, there's no user signed in.
    if (!("uid" in this.cachedPlain)) {
      return null;
    }
    let result = {};
    if (fieldNames === null) {
      // The "old" deprecated way of fetching a logged in user.
      for (let [name, value] of Object.entries(this.cachedPlain)) {
        result[name] = value;
      }
      // But the secure data may not have been read, so try that now.
      await this._maybeReadAndUpdateSecure();
      // .cachedSecure now has as much as it possibly can (which is possibly
      // nothing if (a) secure storage remains locked and (b) we've never updated
      // a field to be stored in secure storage.)
      for (let [name, value] of Object.entries(this.cachedSecure)) {
        result[name] = value;
      }
      return result;
    }
    // The new explicit way of getting attributes.
    if (!Array.isArray(fieldNames)) {
      fieldNames = [fieldNames];
    }
    let checkedSecure = false;
    for (let fieldName of fieldNames) {
      if (FXA_PWDMGR_PLAINTEXT_FIELDS.has(fieldName)) {
        if (this.cachedPlain[fieldName] !== undefined) {
          result[fieldName] = this.cachedPlain[fieldName];
        }
      } else if (FXA_PWDMGR_SECURE_FIELDS.has(fieldName)) {
        // We may not have read secure storage yet.
        if (!checkedSecure) {
          await this._maybeReadAndUpdateSecure();
          checkedSecure = true;
        }
        if (this.cachedSecure[fieldName] !== undefined) {
          result[fieldName] = this.cachedSecure[fieldName];
        }
      } else {
        throw new Error("unexpected field '" + fieldName + "'");
      }
    }
    return result;
  },

  // Update just the specified fields. This DOES NOT allow you to change to
  // a different user, nor to set the user as signed-out.
  async updateAccountData(newFields) {
    await this._promiseInitialized;
    if (!("uid" in this.cachedPlain)) {
      // If this storage instance shows no logged in user, then you can't
      // update fields.
      throw new Error("No user is logged in");
    }
    if (!newFields || "uid" in newFields) {
      throw new Error("Can't change uid");
    }
    log.debug("_updateAccountData with items", Object.keys(newFields));
    // work out what bucket.
    for (let [name, value] of Object.entries(newFields)) {
      if (value == null) {
        delete this.cachedPlain[name];
        // no need to do the "delete on null" thing for this.cachedSecure -
        // we need to keep it until we have managed to read so we can nuke
        // it on write.
        this.cachedSecure[name] = null;
      } else if (FXA_PWDMGR_PLAINTEXT_FIELDS.has(name)) {
        this.cachedPlain[name] = value;
      } else if (FXA_PWDMGR_SECURE_FIELDS.has(name)) {
        this.cachedSecure[name] = value;
      } else {
        // Throwing seems reasonable here as some client code has explicitly
        // specified the field name, so it's either confused or needs to update
        // how this field is to be treated.
        throw new Error("unexpected field '" + name + "'");
      }
    }
    // If we haven't yet read the secure data, do so now, else we may write
    // out partial data.
    await this._maybeReadAndUpdateSecure();
    // Now save it - but don't wait on the _write promise - it's queued up as
    // a storage operation, so .finalize() will wait for completion, but no need
    // for us to.
    this._write();
  },

  _clearCachedData() {
    this.cachedPlain = {};
    // If we don't have secure storage available we have cachedPlain and
    // cachedSecure be the same object.
    this.cachedSecure = this.secureStorage == null ? this.cachedPlain : {};
  },

  /* Reads the plain storage and caches the read values in this.cachedPlain.
     Only ever called once and unlike the "secure" storage, is expected to never
     fail (ie, plain storage is considered always available, whereas secure
     storage may be unavailable if it is locked).

     Returns a promise that resolves with true if valid account data was found,
     false otherwise.

     Note: _readPlainStorage is only called during initialize, so isn't
     protected via _queueStorageOperation() nor _promiseInitialized.
  */
  async _readPlainStorage() {
    let got;
    try {
      got = await this.plainStorage.get();
    } catch (err) {
      // File hasn't been created yet.  That will be done
      // when write is called.
      if (!err.name == "NotFoundError") {
        log.error("Failed to read plain storage", err);
      }
      // either way, we return null.
      got = null;
    }
    if (
      !got ||
      !got.accountData ||
      !got.accountData.uid ||
      got.version != DATA_FORMAT_VERSION
    ) {
      return false;
    }
    // We need to update our .cachedPlain, but can't just assign to it as
    // it may need to be the exact same object as .cachedSecure
    // As a sanity check, .cachedPlain must be empty (as we are called by init)
    // XXX - this would be a good use-case for a RuntimeAssert or similar, as
    // being added in bug 1080457.
    if (Object.keys(this.cachedPlain).length) {
      throw new Error("should be impossible to have cached data already.");
    }
    for (let [name, value] of Object.entries(got.accountData)) {
      this.cachedPlain[name] = value;
    }
    return true;
  },

  /* If we haven't managed to read the secure storage, try now, so
     we can merge our cached data with the data that's already been set.
  */
  _maybeReadAndUpdateSecure() {
    if (this.secureStorage == null || !this._needToReadSecure) {
      return null;
    }
    return this._queueStorageOperation(() => {
      if (this._needToReadSecure) {
        // we might have read it by now!
        return this._doReadAndUpdateSecure();
      }
      return null;
    });
  },

  /* Unconditionally read the secure storage and merge our cached data (ie, data
     which has already been set while the secure storage was locked) with
     the read data
  */
  async _doReadAndUpdateSecure() {
    let { uid, email } = this.cachedPlain;
    try {
      log.debug(
        "reading secure storage with existing",
        Object.keys(this.cachedSecure)
      );
      // If we already have anything in .cachedSecure it means something has
      // updated cachedSecure before we've read it. That means that after we do
      // manage to read we must write back the merged data.
      let needWrite = !!Object.keys(this.cachedSecure).length;
      let readSecure = await this.secureStorage.get(uid, email);
      // and update our cached data with it - anything already in .cachedSecure
      // wins (including the fact it may be null or undefined, the latter
      // which means it will be removed from storage.
      if (readSecure && readSecure.version != DATA_FORMAT_VERSION) {
        log.warn("got secure data but the data format version doesn't match");
        readSecure = null;
      }
      if (readSecure && readSecure.accountData) {
        log.debug(
          "secure read fetched items",
          Object.keys(readSecure.accountData)
        );
        for (let [name, value] of Object.entries(readSecure.accountData)) {
          if (!(name in this.cachedSecure)) {
            this.cachedSecure[name] = value;
          }
        }
        if (needWrite) {
          log.debug("successfully read secure data; writing updated data back");
          await this._doWriteSecure();
        }
      }
      this._needToReadSecure = false;
    } catch (ex) {
      if (ex instanceof this.secureStorage.STORAGE_LOCKED) {
        log.debug("setAccountData: secure storage is locked trying to read");
      } else {
        log.error("failed to read secure storage", ex);
        throw ex;
      }
    }
  },

  _write() {
    // We don't want multiple writes happening concurrently, and we also need to
    // know when an "old" storage manager is done (this.finalize() waits for this)
    return this._queueStorageOperation(() => this.__write());
  },

  async __write() {
    // Write everything back - later we could track what's actually dirty,
    // but for now we write it all.
    log.debug("writing plain storage", Object.keys(this.cachedPlain));
    let toWritePlain = {
      version: DATA_FORMAT_VERSION,
      accountData: this.cachedPlain,
    };
    await this.plainStorage.set(toWritePlain);

    // If we have no secure storage manager we are done.
    if (this.secureStorage == null) {
      return;
    }
    // and only attempt to write to secure storage if we've managed to read it,
    // otherwise we might clobber data that's already there.
    if (!this._needToReadSecure) {
      await this._doWriteSecure();
    }
  },

  /* Do the actual write of secure data. Caller is expected to check if we actually
     need to write and to ensure we are in a queued storage operation.
  */
  async _doWriteSecure() {
    // We need to remove null items here.
    for (let [name, value] of Object.entries(this.cachedSecure)) {
      if (value == null) {
        delete this.cachedSecure[name];
      }
    }
    log.debug("writing secure storage", Object.keys(this.cachedSecure));
    let toWriteSecure = {
      version: DATA_FORMAT_VERSION,
      accountData: this.cachedSecure,
    };
    try {
      await this.secureStorage.set(this.cachedPlain.uid, toWriteSecure);
    } catch (ex) {
      if (!(ex instanceof this.secureStorage.STORAGE_LOCKED)) {
        throw ex;
      }
      // This shouldn't be possible as once it is unlocked it can't be
      // re-locked, and we can only be here if we've previously managed to
      // read.
      log.error("setAccountData: secure storage is locked trying to write");
    }
  },

  // Delete the data for an account - ie, called on "sign out".
  deleteAccountData() {
    return this._queueStorageOperation(() => this._deleteAccountData());
  },

  async _deleteAccountData() {
    log.debug("removing account data");
    await this._promiseInitialized;
    await this.plainStorage.set(null);
    if (this.secureStorage) {
      await this.secureStorage.set(null);
    }
    this._clearCachedData();
    log.debug("account data reset");
  },
};

/**
 * JSONStorage constructor that creates instances that may set/get
 * to a specified file, in a directory that will be created if it
 * doesn't exist.
 *
 * @param options {
 *                  filename: of the file to write to
 *                  baseDir: directory where the file resides
 *                }
 * @return instance
 */
function JSONStorage(options) {
  this.baseDir = options.baseDir;
  this.path = PathUtils.join(options.baseDir, options.filename);
}

JSONStorage.prototype = {
  set(contents) {
    log.trace(
      "starting write of json user data",
      contents ? Object.keys(contents.accountData) : "null"
    );
    let start = Date.now();
    return IOUtils.makeDirectory(this.baseDir, { ignoreExisting: true })
      .then(IOUtils.writeJSON.bind(null, this.path, contents))
      .then(result => {
        log.trace(
          "finished write of json user data - took",
          Date.now() - start
        );
        return result;
      });
  },

  get() {
    log.trace("starting fetch of json user data");
    let start = Date.now();
    return IOUtils.readJSON(this.path).then(result => {
      log.trace("finished fetch of json user data - took", Date.now() - start);
      return result;
    });
  },
};

function StorageLockedError() {}

/**
 * LoginManagerStorage constructor that creates instances that set/get
 * data stored securely in the nsILoginManager.
 *
 * @return instance
 */

export function LoginManagerStorage() {}

LoginManagerStorage.prototype = {
  STORAGE_LOCKED: StorageLockedError,
  // The fields in the credentials JSON object that are stored in plain-text
  // in the profile directory.  All other fields are stored in the login manager,
  // and thus are only available when the master-password is unlocked.

  // a hook point for testing.
  get _isLoggedIn() {
    return Services.logins.isLoggedIn;
  },

  // Clear any data from the login manager.  Returns true if the login manager
  // was unlocked (even if no existing logins existed) or false if it was
  // locked (meaning we don't even know if it existed or not.)
  async _clearLoginMgrData() {
    try {
      // Services.logins might be third-party and broken...
      await Services.logins.initializationPromise;
      if (!this._isLoggedIn) {
        return false;
      }
      let logins = await Services.logins.searchLoginsAsync({
        origin: FXA_PWDMGR_HOST,
        httpRealm: FXA_PWDMGR_REALM,
      });
      for (let login of logins) {
        Services.logins.removeLogin(login);
      }
      return true;
    } catch (ex) {
      log.error("Failed to clear login data: ${}", ex);
      return false;
    }
  },

  async set(uid, contents) {
    if (!contents) {
      // Nuke it from the login manager.
      let cleared = await this._clearLoginMgrData();
      if (!cleared) {
        // just log a message - we verify that the uid matches when
        // we reload it, so having a stale entry doesn't really hurt.
        log.info("not removing credentials from login manager - not logged in");
      }
      log.trace("storage set finished clearing account data");
      return;
    }

    // We are saving actual data.
    log.trace("starting write of user data to the login manager");
    try {
      // Services.logins might be third-party and broken...
      // and the stuff into the login manager.
      await Services.logins.initializationPromise;
      // If MP is locked we silently fail - the user may need to re-auth
      // next startup.
      if (!this._isLoggedIn) {
        log.info("not saving credentials to login manager - not logged in");
        throw new this.STORAGE_LOCKED();
      }
      // write the data to the login manager.
      let loginInfo = new Components.Constructor(
        "@mozilla.org/login-manager/loginInfo;1",
        Ci.nsILoginInfo,
        "init"
      );
      let login = new loginInfo(
        FXA_PWDMGR_HOST,
        null, // aFormActionOrigin,
        FXA_PWDMGR_REALM, // aHttpRealm,
        uid, // aUsername
        JSON.stringify(contents), // aPassword
        "", // aUsernameField
        ""
      ); // aPasswordField

      let existingLogins = await Services.logins.searchLoginsAsync({
        origin: FXA_PWDMGR_HOST,
        httpRealm: FXA_PWDMGR_REALM,
      });
      if (existingLogins.length) {
        Services.logins.modifyLogin(existingLogins[0], login);
      } else {
        await Services.logins.addLoginAsync(login);
      }
      log.trace("finished write of user data to the login manager");
    } catch (ex) {
      if (ex instanceof this.STORAGE_LOCKED) {
        throw ex;
      }
      // just log and consume the error here - it may be a 3rd party login
      // manager replacement that's simply broken.
      log.error("Failed to save data to the login manager", ex);
    }
  },

  async get(uid, email) {
    log.trace("starting fetch of user data from the login manager");

    try {
      // Services.logins might be third-party and broken...
      // read the data from the login manager and merge it for return.
      await Services.logins.initializationPromise;

      if (!this._isLoggedIn) {
        log.info(
          "returning partial account data as the login manager is locked."
        );
        throw new this.STORAGE_LOCKED();
      }

      let logins = await Services.logins.searchLoginsAsync({
        origin: FXA_PWDMGR_HOST,
        httpRealm: FXA_PWDMGR_REALM,
      });
      if (!logins.length) {
        // This could happen if the MP was locked when we wrote the data.
        log.info("Can't find any credentials in the login manager");
        return null;
      }
      let login = logins[0];
      // Support either the uid or the email as the username - as of bug 1183951
      // we store the uid, but we support having either for b/w compat.
      if (login.username == uid || login.username == email) {
        return JSON.parse(login.password);
      }
      log.info("username in the login manager doesn't match - ignoring it");
      await this._clearLoginMgrData();
    } catch (ex) {
      if (ex instanceof this.STORAGE_LOCKED) {
        throw ex;
      }
      // just log and consume the error here - it may be a 3rd party login
      // manager replacement that's simply broken.
      log.error("Failed to get data from the login manager", ex);
    }
    return null;
  },
};
PK
!<O6�	KFKF%modules/services-common/utils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Log } from "resource://gre/modules/Log.sys.mjs";

export var CommonUtils = {
  /*
   * Set manipulation methods. These should be lifted into toolkit, or added to
   * `Set` itself.
   */

  /**
   * Return elements of `a` or `b`.
   */
  union(a, b) {
    let out = new Set(a);
    for (let x of b) {
      out.add(x);
    }
    return out;
  },

  /**
   * Return elements of `a` that are not present in `b`.
   */
  difference(a, b) {
    let out = new Set(a);
    for (let x of b) {
      out.delete(x);
    }
    return out;
  },

  /**
   * Return elements of `a` that are also in `b`.
   */
  intersection(a, b) {
    let out = new Set();
    for (let x of a) {
      if (b.has(x)) {
        out.add(x);
      }
    }
    return out;
  },

  /**
   * Return true if `a` and `b` are the same size, and
   * every element of `a` is in `b`.
   */
  setEqual(a, b) {
    if (a.size != b.size) {
      return false;
    }
    for (let x of a) {
      if (!b.has(x)) {
        return false;
      }
    }
    return true;
  },

  /**
   * Checks elements in two arrays for equality, as determined by the `===`
   * operator. This function does not perform a deep comparison; see Sync's
   * `Util.deepEquals` for that.
   */
  arrayEqual(a, b) {
    if (a.length !== b.length) {
      return false;
    }
    for (let i = 0; i < a.length; i++) {
      if (a[i] !== b[i]) {
        return false;
      }
    }
    return true;
  },

  /**
   * Encode byte string as base64URL (RFC 4648).
   *
   * @param bytes
   *        (string) Raw byte string to encode.
   * @param pad
   *        (bool) Whether to include padding characters (=). Defaults
   *        to true for historical reasons.
   */
  encodeBase64URL: function encodeBase64URL(bytes, pad = true) {
    let s = btoa(bytes).replace(/\+/g, "-").replace(/\//g, "_");

    if (!pad) {
      return s.replace(/=+$/, "");
    }

    return s;
  },

  /**
   * Create a nsIURI instance from a string.
   */
  makeURI: function makeURI(URIString) {
    if (!URIString) {
      return null;
    }
    try {
      return Services.io.newURI(URIString);
    } catch (e) {
      let log = Log.repository.getLogger("Common.Utils");
      log.debug("Could not create URI", e);
      return null;
    }
  },

  /**
   * Execute a function on the next event loop tick.
   *
   * @param callback
   *        Function to invoke.
   * @param thisObj [optional]
   *        Object to bind the callback to.
   */
  nextTick: function nextTick(callback, thisObj) {
    if (thisObj) {
      callback = callback.bind(thisObj);
    }
    Services.tm.dispatchToMainThread(callback);
  },

  /**
   * Return a timer that is scheduled to call the callback after waiting the
   * provided time or as soon as possible. The timer will be set as a property
   * of the provided object with the given timer name.
   */
  namedTimer: function namedTimer(callback, wait, thisObj, name) {
    if (!thisObj || !name) {
      throw new Error(
        "You must provide both an object and a property name for the timer!"
      );
    }

    // Delay an existing timer if it exists
    if (name in thisObj && thisObj[name] instanceof Ci.nsITimer) {
      thisObj[name].delay = wait;
      return thisObj[name];
    }

    // Create a special timer that we can add extra properties
    let timer = Object.create(
      Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer)
    );

    // Provide an easy way to clear out the timer
    timer.clear = function () {
      thisObj[name] = null;
      timer.cancel();
    };

    // Initialize the timer with a smart callback
    timer.initWithCallback(
      {
        notify: function notify() {
          // Clear out the timer once it's been triggered
          timer.clear();
          callback.call(thisObj, timer);
        },
      },
      wait,
      timer.TYPE_ONE_SHOT
    );

    return (thisObj[name] = timer);
  },

  encodeUTF8: function encodeUTF8(str) {
    try {
      str = this._utf8Converter.ConvertFromUnicode(str);
      return str + this._utf8Converter.Finish();
    } catch (ex) {
      return null;
    }
  },

  decodeUTF8: function decodeUTF8(str) {
    try {
      str = this._utf8Converter.ConvertToUnicode(str);
      return str + this._utf8Converter.Finish();
    } catch (ex) {
      return null;
    }
  },

  byteArrayToString: function byteArrayToString(bytes) {
    return bytes.map(byte => String.fromCharCode(byte)).join("");
  },

  stringToByteArray: function stringToByteArray(bytesString) {
    return Array.prototype.slice.call(bytesString).map(c => c.charCodeAt(0));
  },

  // A lot of Util methods work with byte strings instead of ArrayBuffers.
  // A patch should address this problem, but in the meantime let's provide
  // helpers method to convert byte strings to Uint8Array.
  byteStringToArrayBuffer(byteString) {
    if (byteString === undefined) {
      return new Uint8Array();
    }
    const bytes = new Uint8Array(byteString.length);
    for (let i = 0; i < byteString.length; ++i) {
      bytes[i] = byteString.charCodeAt(i) & 0xff;
    }
    return bytes;
  },

  arrayBufferToByteString(buffer) {
    return CommonUtils.byteArrayToString([...buffer]);
  },

  bufferToHex(buffer) {
    return Array.prototype.map
      .call(buffer, x => ("00" + x.toString(16)).slice(-2))
      .join("");
  },

  bytesAsHex: function bytesAsHex(bytes) {
    let s = "";
    for (let i = 0, len = bytes.length; i < len; i++) {
      let c = (bytes[i].charCodeAt(0) & 0xff).toString(16);
      if (c.length == 1) {
        c = "0" + c;
      }
      s += c;
    }
    return s;
  },

  stringAsHex: function stringAsHex(str) {
    return CommonUtils.bytesAsHex(CommonUtils.encodeUTF8(str));
  },

  stringToBytes: function stringToBytes(str) {
    return CommonUtils.hexToBytes(CommonUtils.stringAsHex(str));
  },

  hexToBytes: function hexToBytes(str) {
    let bytes = [];
    for (let i = 0; i < str.length - 1; i += 2) {
      bytes.push(parseInt(str.substr(i, 2), 16));
    }
    return String.fromCharCode.apply(String, bytes);
  },

  hexToArrayBuffer(str) {
    const octString = CommonUtils.hexToBytes(str);
    return CommonUtils.byteStringToArrayBuffer(octString);
  },

  hexAsString: function hexAsString(hex) {
    return CommonUtils.decodeUTF8(CommonUtils.hexToBytes(hex));
  },

  base64urlToHex(b64str) {
    return CommonUtils.bufferToHex(
      new Uint8Array(ChromeUtils.base64URLDecode(b64str, { padding: "reject" }))
    );
  },

  /**
   * Base32 encode (RFC 4648) a string
   */
  encodeBase32: function encodeBase32(bytes) {
    const key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
    let leftover = bytes.length % 5;

    // Pad the last quantum with zeros so the length is a multiple of 5.
    if (leftover) {
      for (let i = leftover; i < 5; i++) {
        bytes += "\0";
      }
    }

    // Chop the string into quanta of 5 bytes (40 bits). Each quantum
    // is turned into 8 characters from the 32 character base.
    let ret = "";
    for (let i = 0; i < bytes.length; i += 5) {
      let c = Array.prototype.slice
        .call(bytes.slice(i, i + 5))
        .map(byte => byte.charCodeAt(0));
      ret +=
        key[c[0] >> 3] +
        key[((c[0] << 2) & 0x1f) | (c[1] >> 6)] +
        key[(c[1] >> 1) & 0x1f] +
        key[((c[1] << 4) & 0x1f) | (c[2] >> 4)] +
        key[((c[2] << 1) & 0x1f) | (c[3] >> 7)] +
        key[(c[3] >> 2) & 0x1f] +
        key[((c[3] << 3) & 0x1f) | (c[4] >> 5)] +
        key[c[4] & 0x1f];
    }

    switch (leftover) {
      case 1:
        return ret.slice(0, -6) + "======";
      case 2:
        return ret.slice(0, -4) + "====";
      case 3:
        return ret.slice(0, -3) + "===";
      case 4:
        return ret.slice(0, -1) + "=";
      default:
        return ret;
    }
  },

  /**
   * Base32 decode (RFC 4648) a string.
   */
  decodeBase32: function decodeBase32(str) {
    const key = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

    let padChar = str.indexOf("=");
    let chars = padChar == -1 ? str.length : padChar;
    let bytes = Math.floor((chars * 5) / 8);
    let blocks = Math.ceil(chars / 8);

    // Process a chunk of 5 bytes / 8 characters.
    // The processing of this is known in advance,
    // so avoid arithmetic!
    function processBlock(ret, cOffset, rOffset) {
      let c, val;

      // N.B., this relies on
      //   undefined | foo == foo.
      function accumulate(val) {
        ret[rOffset] |= val;
      }

      function advance() {
        c = str[cOffset++];
        if (!c || c == "" || c == "=") {
          // Easier than range checking.
          throw new Error("Done");
        } // Will be caught far away.
        val = key.indexOf(c);
        if (val == -1) {
          throw new Error(`Unknown character in base32: ${c}`);
        }
      }

      // Handle a left shift, restricted to bytes.
      function left(octet, shift) {
        return (octet << shift) & 0xff;
      }

      advance();
      accumulate(left(val, 3));
      advance();
      accumulate(val >> 2);
      ++rOffset;
      accumulate(left(val, 6));
      advance();
      accumulate(left(val, 1));
      advance();
      accumulate(val >> 4);
      ++rOffset;
      accumulate(left(val, 4));
      advance();
      accumulate(val >> 1);
      ++rOffset;
      accumulate(left(val, 7));
      advance();
      accumulate(left(val, 2));
      advance();
      accumulate(val >> 3);
      ++rOffset;
      accumulate(left(val, 5));
      advance();
      accumulate(val);
      ++rOffset;
    }

    // Our output. Define to be explicit (and maybe the compiler will be smart).
    let ret = new Array(bytes);
    let i = 0;
    let cOff = 0;
    let rOff = 0;

    for (; i < blocks; ++i) {
      try {
        processBlock(ret, cOff, rOff);
      } catch (ex) {
        // Handle the detection of padding.
        if (ex.message == "Done") {
          break;
        }
        throw ex;
      }
      cOff += 8;
      rOff += 5;
    }

    // Slice in case our shift overflowed to the right.
    return CommonUtils.byteArrayToString(ret.slice(0, bytes));
  },

  /**
   * Trim excess padding from a Base64 string and atob().
   *
   * See bug 562431 comment 4.
   */
  safeAtoB: function safeAtoB(b64) {
    let len = b64.length;
    let over = len % 4;
    return over ? atob(b64.substr(0, len - over)) : atob(b64);
  },

  /**
   * Ensure that the specified value is defined in integer milliseconds since
   * UNIX epoch.
   *
   * This throws an error if the value is not an integer, is negative, or looks
   * like seconds, not milliseconds.
   *
   * If the value is null or 0, no exception is raised.
   *
   * @param value
   *        Value to validate.
   */
  ensureMillisecondsTimestamp: function ensureMillisecondsTimestamp(value) {
    if (!value) {
      return;
    }

    if (!/^[0-9]+$/.test(value)) {
      throw new Error("Timestamp value is not a positive integer: " + value);
    }

    let intValue = parseInt(value, 10);

    if (!intValue) {
      return;
    }

    // Catch what looks like seconds, not milliseconds.
    if (intValue < 10000000000) {
      throw new Error("Timestamp appears to be in seconds: " + intValue);
    }
  },

  /**
   * Read bytes from an nsIInputStream into a string.
   *
   * @param stream
   *        (nsIInputStream) Stream to read from.
   * @param count
   *        (number) Integer number of bytes to read. If not defined, or
   *        0, all available input is read.
   */
  readBytesFromInputStream: function readBytesFromInputStream(stream, count) {
    let BinaryInputStream = Components.Constructor(
      "@mozilla.org/binaryinputstream;1",
      "nsIBinaryInputStream",
      "setInputStream"
    );
    if (!count) {
      count = stream.available();
    }

    return new BinaryInputStream(stream).readBytes(count);
  },

  /**
   * Generate a new UUID using nsIUUIDGenerator.
   *
   * Example value: "1e00a2e2-1570-443e-bf5e-000354124234"
   *
   * @return string A hex-formatted UUID string.
   */
  generateUUID: function generateUUID() {
    let uuid = Services.uuid.generateUUID().toString();

    return uuid.substring(1, uuid.length - 1);
  },

  /**
   * Obtain an epoch value from a preference.
   *
   * This reads a string preference and returns an integer. The string
   * preference is expected to contain the integer milliseconds since epoch.
   * For best results, only read preferences that have been saved with
   * setDatePref().
   *
   * We need to store times as strings because integer preferences are only
   * 32 bits and likely overflow most dates.
   *
   * If the pref contains a non-integer value, the specified default value will
   * be returned.
   *
   * @param branch
   *        (Preferences) Branch from which to retrieve preference.
   * @param pref
   *        (string) The preference to read from.
   * @param def
   *        (Number) The default value to use if the preference is not defined.
   * @param log
   *        (Log.Logger) Logger to write warnings to.
   */
  getEpochPref: function getEpochPref(branch, pref, def = 0, log = null) {
    if (!Number.isInteger(def)) {
      throw new Error("Default value is not a number: " + def);
    }

    let valueStr = branch.getStringPref(pref, null);

    if (valueStr !== null) {
      let valueInt = parseInt(valueStr, 10);
      if (Number.isNaN(valueInt)) {
        if (log) {
          log.warn(
            "Preference value is not an integer. Using default. " +
              pref +
              "=" +
              valueStr +
              " -> " +
              def
          );
        }

        return def;
      }

      return valueInt;
    }

    return def;
  },

  /**
   * Obtain a Date from a preference.
   *
   * This is a wrapper around getEpochPref. It converts the value to a Date
   * instance and performs simple range checking.
   *
   * The range checking ensures the date is newer than the oldestYear
   * parameter.
   *
   * @param branch
   *        (Preferences) Branch from which to read preference.
   * @param pref
   *        (string) The preference from which to read.
   * @param def
   *        (Number) The default value (in milliseconds) if the preference is
   *        not defined or invalid.
   * @param log
   *        (Log.Logger) Logger to write warnings to.
   * @param oldestYear
   *        (Number) Oldest year to accept in read values.
   */
  getDatePref: function getDatePref(
    branch,
    pref,
    def = 0,
    log = null,
    oldestYear = 2010
  ) {
    let valueInt = this.getEpochPref(branch, pref, def, log);
    let date = new Date(valueInt);

    if (valueInt == def || date.getFullYear() >= oldestYear) {
      return date;
    }

    if (log) {
      log.warn(
        "Unexpected old date seen in pref. Returning default: " +
          pref +
          "=" +
          date +
          " -> " +
          def
      );
    }

    return new Date(def);
  },

  /**
   * Store a Date in a preference.
   *
   * This is the opposite of getDatePref(). The same notes apply.
   *
   * If the range check fails, an Error will be thrown instead of a default
   * value silently being used.
   *
   * @param branch
   *        (Preference) Branch from which to read preference.
   * @param pref
   *        (string) Name of preference to write to.
   * @param date
   *        (Date) The value to save.
   * @param oldestYear
   *        (Number) The oldest year to accept for values.
   */
  setDatePref: function setDatePref(branch, pref, date, oldestYear = 2010) {
    if (date.getFullYear() < oldestYear) {
      throw new Error(
        "Trying to set " +
          pref +
          " to a very old time: " +
          date +
          ". The current time is " +
          new Date() +
          ". Is the system clock wrong?"
      );
    }

    branch.setStringPref(pref, "" + date.getTime());
  },

  /**
   * Convert a string between two encodings.
   *
   * Output is only guaranteed if the input stream is composed of octets. If
   * the input string has characters with values larger than 255, data loss
   * will occur.
   *
   * The returned string is guaranteed to consist of character codes no greater
   * than 255.
   *
   * @param s
   *        (string) The source string to convert.
   * @param source
   *        (string) The current encoding of the string.
   * @param dest
   *        (string) The target encoding of the string.
   *
   * @return string
   */
  convertString: function convertString(s, source, dest) {
    if (!s) {
      throw new Error("Input string must be defined.");
    }

    let is = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
      Ci.nsIStringInputStream
    );
    is.setData(s, s.length);

    let listener = Cc["@mozilla.org/network/stream-loader;1"].createInstance(
      Ci.nsIStreamLoader
    );

    let result;

    listener.init({
      onStreamComplete: function onStreamComplete(
        loader,
        context,
        status,
        length,
        data
      ) {
        result = String.fromCharCode.apply(this, data);
      },
    });

    let converter = this._converterService.asyncConvertData(
      source,
      dest,
      listener,
      null
    );
    converter.onStartRequest(null, null);
    converter.onDataAvailable(null, is, 0, s.length);
    converter.onStopRequest(null, null, null);

    return result;
  },
};

ChromeUtils.defineLazyGetter(CommonUtils, "_utf8Converter", function () {
  let converter = Cc[
    "@mozilla.org/intl/scriptableunicodeconverter"
  ].createInstance(Ci.nsIScriptableUnicodeConverter);
  converter.charset = "UTF-8";
  return converter;
});

ChromeUtils.defineLazyGetter(CommonUtils, "_converterService", function () {
  return Cc["@mozilla.org/streamConverters;1"].getService(
    Ci.nsIStreamConverterService
  );
});
PK
!<\^�3�_�_modules/FxAccountsKeys.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { CommonUtils } from "resource://services-common/utils.sys.mjs";

import { CryptoUtils } from "resource://services-crypto/utils.sys.mjs";

import {
  SCOPE_APP_SYNC,
  DEPRECATED_SCOPE_ECOSYSTEM_TELEMETRY,
  OAUTH_CLIENT_ID,
  log,
  logPII,
} from "resource://gre/modules/FxAccountsCommon.sys.mjs";

// The following top-level fields have since been deprecated and exist here purely
// to be removed from the account state when seen. After a reasonable period of time
// has passed, where users have been migrated away from those keys they should be safe to be removed
const DEPRECATED_DERIVED_KEYS_NAMES = [
  "kSync",
  "kXCS",
  "kExtSync",
  "kExtKbHash",
  "ecosystemUserId",
  "ecosystemAnonId",
];

// This scope and its associated key material were used by the old Kinto webextension
// storage backend, but has since been decommissioned. It's here entirely so that we
// remove the corresponding key from storage if present. We should be safe to remove it
// after some sensible period of time has elapsed to allow most clients to update.
const DEPRECATED_SCOPE_WEBEXT_SYNC = "sync:addon_storage";

// These are the scopes that correspond to new storage for the `LEGACY_DERIVED_KEYS_NAMES`.
// We will, if necessary, migrate storage for those keys so that it's associated with
// these scopes.
const LEGACY_DERIVED_KEY_SCOPES = [SCOPE_APP_SYNC];

// These are scopes that we used to store, but are no longer using,
// and hence should be deleted from storage if present.
const DEPRECATED_KEY_SCOPES = [
  DEPRECATED_SCOPE_ECOSYSTEM_TELEMETRY,
  DEPRECATED_SCOPE_WEBEXT_SYNC,
];

/**
 * Utilities for working with key material linked to the user's account.
 *
 * Each Firefox Account has 32 bytes of root key material called `kB` which is
 * linked to the user's password, and which is used to derive purpose-specific
 * subkeys for things like encrypting the user's sync data. This class provides
 * the interface for working with such key material.
 *
 * Most recent FxA clients obtain appropriate key material directly as part of
 * their sign-in flow, using a special extension of the OAuth2.0 protocol to
 * securely deliver the derived keys without revealing `kB`. Keys obtained in
 * in this way are called "scoped keys" since each corresponds to a particular
 * OAuth scope, and this class provides a `getKeyForScope` method that is the
 * preferred method for consumers to work with such keys.
 *
 * However, since the FxA integration in Firefox Desktop pre-dates the use of
 * OAuth2.0, we also have a lot of code for fetching keys via an older flow.
 * This flow uses a special `keyFetchToken` to obtain `kB` and then derive various
 * sub-keys from it. Consumers should consider this an internal implementation
 * detail of the `FxAccountsKeys` class and should prefer `getKeyForScope` where
 * possible.  We intend to remove support for Firefox ever directly handling `kB`
 * at some point in the future.
 */
export class FxAccountsKeys {
  constructor(fxAccountsInternal) {
    this._fxai = fxAccountsInternal;
  }

  /**
   * Checks if we currently have the key for a given scope, or if we have enough to
   * be able to successfully fetch and unwrap it for the signed-in-user.
   *
   * Unlike `getKeyForScope`, this will not hit the network to fetch wrapped keys if
   * they aren't available locally.
   */
  canGetKeyForScope(scope) {
    return this._fxai.withCurrentAccountState(async currentState => {
      let userData = await currentState.getUserAccountData();
      if (!userData) {
        throw new Error("Can't possibly get keys; User is not signed in");
      }
      if (!userData.verified) {
        log.info("Can't get keys; user is not verified");
        return false;
      }

      if (userData.scopedKeys && userData.scopedKeys.hasOwnProperty(scope)) {
        return true;
      }

      // If we have a `keyFetchToken` we can fetch `kB`.
      if (userData.keyFetchToken) {
        return true;
      }

      log.info("Can't get keys; no key material or tokens available");
      return false;
    });
  }

  /**
   * Get the key for a specified OAuth scope.
   *
   * @param {String} scope The OAuth scope whose key should be returned
   *
   * @return Promise<JWK>
   *        If no key is available the promise resolves to `null`.
   *        If a key is available for the given scope, th promise resolves to a JWK with fields:
   *        {
   *          scope: The requested scope
   *          kid: Key identifier
   *          k: Derived key material
   *          kty: Always "oct" for scoped keys
   *        }
   *
   */
  async getKeyForScope(scope) {
    const { scopedKeys } = await this._loadOrFetchKeys();
    if (!scopedKeys.hasOwnProperty(scope)) {
      throw new Error(`Key not available for scope "${scope}"`);
    }
    return {
      scope,
      ...scopedKeys[scope],
    };
  }

  /**
   * Validates if the given scoped keys are valid keys
   *
   * @param { Object } scopedKeys: The scopedKeys bundle
   *
   * @return { Boolean }: true if the scopedKeys bundle is valid, false otherwise
   */
  validScopedKeys(scopedKeys) {
    for (const expectedScope of Object.keys(scopedKeys)) {
      const key = scopedKeys[expectedScope];
      if (
        !key.hasOwnProperty("scope") ||
        !key.hasOwnProperty("kid") ||
        !key.hasOwnProperty("kty") ||
        !key.hasOwnProperty("k")
      ) {
        return false;
      }
      const { scope, kid, kty, k } = key;
      if (scope != expectedScope || kty != "oct") {
        return false;
      }
      // We verify the format of the key id is `timestamp-fingerprint`
      if (!kid.includes("-")) {
        return false;
      }
      const dashIndex = kid.indexOf("-");
      const keyRotationTimestamp = kid.substring(0, dashIndex);
      const fingerprint = kid.substring(dashIndex + 1);
      // We then verify that the timestamp is a valid timestamp
      const keyRotationTimestampNum = Number(keyRotationTimestamp);
      // If the value we got back is falsy it's not a valid timestamp
      // note that we treat a 0 timestamp as invalid
      if (!keyRotationTimestampNum) {
        return false;
      }
      // For extra safety, we validate that the timestamp can be converted into a valid
      // Date object
      const date = new Date(keyRotationTimestampNum);
      if (isNaN(date.getTime()) || date.getTime() <= 0) {
        return false;
      }

      // Finally, we validate that the fingerprint and the key itself are valid base64 values
      // Note that we can't verify the fingerprint is correct here because we don't have kb
      const validB64String = b64String => {
        let decoded;
        try {
          decoded = ChromeUtils.base64URLDecode(b64String, {
            padding: "reject",
          });
        } catch (e) {
          return false;
        }
        return !!decoded;
      };
      if (!validB64String(fingerprint) || !validB64String(k)) {
        return false;
      }
    }
    return true;
  }

  /**
   * Format a JWK kid as hex rather than base64.
   *
   * This is a backwards-compatibility helper for code that needs a raw key fingerprint
   * for use as a key identifier, rather than the timestamp+fingerprint format used by
   * FxA scoped keys.
   *
   * @param {Object} jwk The JWK from which to extract the `kid` field as hex.
   */
  kidAsHex(jwk) {
    // The kid format is "{timestamp}-{b64url(fingerprint)}", but we have to be careful
    // because the fingerprint component may contain "-" as well, and we want to ensure
    // the timestamp component was non-empty.
    const idx = jwk.kid.indexOf("-") + 1;
    if (idx <= 1) {
      throw new Error(`Invalid kid: ${jwk.kid}`);
    }
    return CommonUtils.base64urlToHex(jwk.kid.slice(idx));
  }

  /**
   * Fetch encryption keys for the signed-in-user from the FxA API server.
   *
   * Not for user consumption.  Exists to cause the keys to be fetched.
   *
   * Returns user data so that it can be chained with other methods.
   *
   * @return Promise
   *        The promise resolves to the credentials object of the signed-in user:
   *        {
   *          email: The user's email address
   *          uid: The user's unique id
   *          sessionToken: Session for the FxA server
   *          scopedKeys: Object mapping OAuth scopes to corresponding derived keys
   *          verified: email verification status
   *        }
   * @throws If there is no user signed in.
   */
  async _loadOrFetchKeys() {
    return this._fxai.withCurrentAccountState(async currentState => {
      try {
        let userData = await currentState.getUserAccountData();
        if (!userData) {
          throw new Error("Can't get keys; User is not signed in");
        }
        // If we have all the keys in latest storage location, we're good.
        if (userData.scopedKeys) {
          if (
            LEGACY_DERIVED_KEY_SCOPES.every(scope =>
              userData.scopedKeys.hasOwnProperty(scope)
            ) &&
            !DEPRECATED_KEY_SCOPES.some(scope =>
              userData.scopedKeys.hasOwnProperty(scope)
            ) &&
            !DEPRECATED_DERIVED_KEYS_NAMES.some(keyName =>
              userData.hasOwnProperty(keyName)
            )
          ) {
            return userData;
          }
        }
        // If not, we've got work to do, and we debounce to avoid duplicating it.
        if (!currentState.whenKeysReadyDeferred) {
          currentState.whenKeysReadyDeferred = Promise.withResolvers();
          // N.B. we deliberately don't `await` here, and instead use the promise
          // to resolve `whenKeysReadyDeferred` (which we then `await` below).
          this._migrateOrFetchKeys(currentState, userData).then(
            dataWithKeys => {
              currentState.whenKeysReadyDeferred.resolve(dataWithKeys);
              currentState.whenKeysReadyDeferred = null;
            },
            err => {
              currentState.whenKeysReadyDeferred.reject(err);
              currentState.whenKeysReadyDeferred = null;
            }
          );
        }
        return await currentState.whenKeysReadyDeferred.promise;
      } catch (err) {
        return this._fxai._handleTokenError(err);
      }
    });
  }

  /**
   * Set externally derived scoped keys in internal storage
   * @param { Object } scopedKeys: The scoped keys object derived by the oauth flow
   *
   * @return { Promise }: A promise that resolves if the keys were successfully stored,
   *    or rejects if we failed to persist the keys, or if the user is not signed in already
   */
  async setScopedKeys(scopedKeys) {
    return this._fxai.withCurrentAccountState(async currentState => {
      const userData = await currentState.getUserAccountData();
      if (!userData) {
        throw new Error("Cannot persist keys, no user signed in");
      }
      await currentState.updateUserAccountData({
        scopedKeys,
      });
    });
  }

  /**
   * Key storage migration or fetching logic.
   *
   * This method contains the doing-expensive-operations part of the logic of
   * _loadOrFetchKeys(), factored out into a separate method so we can debounce it.
   *
   */
  async _migrateOrFetchKeys(currentState, userData) {
    // If the required scopes are present in `scopedKeys`, then we know that we've
    // previously applied all earlier migrations
    // so we are safe to delete deprecated fields that older migrations
    // might have depended on.
    if (
      userData.scopedKeys &&
      LEGACY_DERIVED_KEY_SCOPES.every(scope =>
        userData.scopedKeys.hasOwnProperty(scope)
      )
    ) {
      return this._removeDeprecatedKeys(currentState, userData);
    }

    // Otherwise, we need to fetch from the network and unwrap.
    if (!userData.sessionToken) {
      throw new Error("No sessionToken");
    }
    if (!userData.keyFetchToken) {
      throw new Error("No keyFetchToken");
    }
    return this._fetchAndUnwrapAndDeriveKeys(
      currentState,
      userData.sessionToken,
      userData.keyFetchToken
    );
  }

  /**
   * Removes deprecated keys from storage and returns an
   * updated user data object
   */
  async _removeDeprecatedKeys(currentState, userData) {
    // Bug 1838708: Delete any deprecated high level keys from storage
    const keysToRemove = DEPRECATED_DERIVED_KEYS_NAMES.filter(keyName =>
      userData.hasOwnProperty(keyName)
    );
    if (keysToRemove.length) {
      const removedKeys = {};
      for (const keyName of keysToRemove) {
        removedKeys[keyName] = null;
      }
      await currentState.updateUserAccountData({
        ...removedKeys,
      });
      userData = await currentState.getUserAccountData();
    }
    // Bug 1697596 - delete any deprecated scoped keys from storage.
    const scopesToRemove = DEPRECATED_KEY_SCOPES.filter(scope =>
      userData.scopedKeys.hasOwnProperty(scope)
    );
    if (scopesToRemove.length) {
      const updatedScopedKeys = {
        ...userData.scopedKeys,
      };
      for (const scope of scopesToRemove) {
        delete updatedScopedKeys[scope];
      }
      await currentState.updateUserAccountData({
        scopedKeys: updatedScopedKeys,
      });
      userData = await currentState.getUserAccountData();
    }
    return userData;
  }

  /**
   * Fetch keys from the server, unwrap them, and derive required sub-keys.
   *
   * Once the user's email is verified, we can resquest the root key `kB` from the
   * FxA server, unwrap it using the client-side secret `unwrapBKey`, and then
   * derive all the sub-keys required for operation of the browser.
   */
  async _fetchAndUnwrapAndDeriveKeys(
    currentState,
    sessionToken,
    keyFetchToken
  ) {
    if (logPII()) {
      log.debug(
        `fetchAndUnwrapKeys: sessionToken: ${sessionToken}, keyFetchToken: ${keyFetchToken}`
      );
    }

    // Sign out if we don't have the necessary tokens.
    if (!sessionToken || !keyFetchToken) {
      // this seems really bad and we should remove this - bug 1572313.
      log.warn("improper _fetchAndUnwrapKeys() call: token missing");
      await this._fxai.signOut();
      return null;
    }

    // Deriving OAuth scoped keys requires additional metadata from the server.
    // We fetch this first, before fetching the actual key material, because the
    // keyFetchToken is single-use and we don't want to do a potentially-fallible
    // operation after consuming it.
    const scopedKeysMetadata = await this._fetchScopedKeysMetadata(
      sessionToken
    );

    // Fetch the wrapped keys.
    // It would be nice to be able to fetch this in a single operation with fetching
    // the metadata above, but that requires server-side changes in FxA.
    let { wrapKB } = await this._fetchKeys(keyFetchToken);

    let data = await currentState.getUserAccountData();

    // Sanity check that the user hasn't changed out from under us (which should
    // be impossible given this is called within _withCurrentAccountState, but...)
    if (data.keyFetchToken !== keyFetchToken) {
      throw new Error("Signed in user changed while fetching keys!");
    }

    let kBbytes = CryptoUtils.xor(
      CommonUtils.hexToBytes(data.unwrapBKey),
      wrapKB
    );

    if (logPII()) {
      log.debug("kBbytes: " + kBbytes);
    }

    let updateData = {
      ...(await this._deriveKeys(data.uid, kBbytes, scopedKeysMetadata)),
      keyFetchToken: null, // null values cause the item to be removed.
      unwrapBKey: null,
    };

    if (logPII()) {
      log.debug(`Keys Obtained: ${updateData.scopedKeys}`);
    } else {
      log.debug(
        "Keys Obtained: " + Object.keys(updateData.scopedKeys).join(", ")
      );
    }

    // Just double-check that scoped keys are there now
    if (!updateData.scopedKeys) {
      throw new Error(`user data missing: scopedKeys`);
    }

    await currentState.updateUserAccountData(updateData);
    return currentState.getUserAccountData();
  }

  /**
   * Fetch the wrapped root key `wrapKB` from the FxA server.
   *
   * This consumes the single-use `keyFetchToken`.
   */
  _fetchKeys(keyFetchToken) {
    let client = this._fxai.fxAccountsClient;
    log.debug(
      `Fetching keys with token ${!!keyFetchToken} from ${client.host}`
    );
    if (logPII()) {
      log.debug("fetchKeys - the token is " + keyFetchToken);
    }
    return client.accountKeys(keyFetchToken);
  }

  /**
   * Fetch additional metadata required for deriving scoped keys.
   *
   * This includes timestamps and a server-provided secret to mix in to
   * the derived value in order to support key rotation.
   */
  async _fetchScopedKeysMetadata(sessionToken) {
    // Hard-coded list of scopes that we know about.
    // This list will probably grow in future.
    const scopes = [SCOPE_APP_SYNC].join(" ");
    const scopedKeysMetadata =
      await this._fxai.fxAccountsClient.getScopedKeyData(
        sessionToken,
        OAUTH_CLIENT_ID,
        scopes
      );
    // The server may decline us permission for some of those scopes, although it really shouldn't.
    // We can live without them...except for the sync scope, whose absence would be catastrophic.
    if (!scopedKeysMetadata.hasOwnProperty(SCOPE_APP_SYNC)) {
      log.warn(
        "The FxA server did not grant Firefox the sync scope; this is most unexpected!" +
          ` scopes were: ${Object.keys(scopedKeysMetadata)}`
      );
      throw new Error("The FxA server did not grant Firefox the sync scope");
    }
    return scopedKeysMetadata;
  }

  /**
   * Derive purpose-specific keys from the root FxA key `kB`.
   *
   * Everything that uses an encryption key from FxA uses a purpose-specific derived
   * key. For new uses this is derived in a structured way based on OAuth scopes,
   * while for legacy uses (mainly Firefox Sync) it is derived in a more ad-hoc fashion.
   * This method does all the derivations for the uses that we know about.
   *
   */
  async _deriveKeys(uid, kBbytes, scopedKeysMetadata) {
    const scopedKeys = await this._deriveScopedKeys(
      uid,
      kBbytes,
      scopedKeysMetadata
    );
    return {
      scopedKeys,
    };
  }

  /**
   * Derive various scoped keys from the root FxA key `kB`.
   *
   * The `scopedKeysMetadata` object is additional information fetched from the server that
   * that gets mixed in to the key derivation, with each member of the object corresponding
   * to an OAuth scope that keys its own scoped key.
   *
   * As a special case for backwards-compatibility, sync-related scopes get special
   * treatment to use a legacy derivation algorithm.
   *
   */
  async _deriveScopedKeys(uid, kBbytes, scopedKeysMetadata) {
    const scopedKeys = {};
    for (const scope in scopedKeysMetadata) {
      if (LEGACY_DERIVED_KEY_SCOPES.includes(scope)) {
        scopedKeys[scope] = await this._deriveLegacyScopedKey(
          uid,
          kBbytes,
          scope,
          scopedKeysMetadata[scope]
        );
      } else {
        scopedKeys[scope] = await this._deriveScopedKey(
          uid,
          kBbytes,
          scope,
          scopedKeysMetadata[scope]
        );
      }
    }
    return scopedKeys;
  }

  /**
   * Derive a scoped key for an individual OAuth scope.
   *
   * The derivation here uses HKDF to combine:
   *   - the root key material kB
   *   - a unique identifier for this scoped key
   *   - a server-provided secret that allows for key rotation
   *   - the account uid as an additional salt
   *
   * It produces 32 bytes of (secret) key material along with a (potentially public)
   * key identifier, formatted as a JWK.
   *
   * The full details are in the technical docs at
   * https://docs.google.com/document/d/1IvQJFEBFz0PnL4uVlIvt8fBS_IPwSK-avK0BRIHucxQ/
   */
  async _deriveScopedKey(uid, kBbytes, scope, scopedKeyMetadata) {
    kBbytes = CommonUtils.byteStringToArrayBuffer(kBbytes);

    const FINGERPRINT_LENGTH = 16;
    const KEY_LENGTH = 32;
    const VALID_UID = /^[0-9a-f]{32}$/i;
    const VALID_ROTATION_SECRET = /^[0-9a-f]{64}$/i;

    // Engage paranoia mode for input data.
    if (!VALID_UID.test(uid)) {
      throw new Error("uid must be a 32-character hex string");
    }
    if (kBbytes.length != 32) {
      throw new Error("kBbytes must be exactly 32 bytes");
    }
    if (
      typeof scopedKeyMetadata.identifier !== "string" ||
      scopedKeyMetadata.identifier.length < 10
    ) {
      throw new Error("identifier must be a string of length >= 10");
    }
    if (typeof scopedKeyMetadata.keyRotationTimestamp !== "number") {
      throw new Error("keyRotationTimestamp must be a number");
    }
    if (!VALID_ROTATION_SECRET.test(scopedKeyMetadata.keyRotationSecret)) {
      throw new Error("keyRotationSecret must be a 64-character hex string");
    }

    // The server returns milliseconds, we want seconds as a string.
    const keyRotationTimestamp =
      "" + Math.round(scopedKeyMetadata.keyRotationTimestamp / 1000);
    if (keyRotationTimestamp.length < 10) {
      throw new Error("keyRotationTimestamp must round to a 10-digit number");
    }

    const keyRotationSecret = CommonUtils.hexToArrayBuffer(
      scopedKeyMetadata.keyRotationSecret
    );
    const salt = CommonUtils.hexToArrayBuffer(uid);
    const context = new TextEncoder().encode(
      "identity.mozilla.com/picl/v1/scoped_key\n" + scopedKeyMetadata.identifier
    );

    const inputKey = new Uint8Array(64);
    inputKey.set(kBbytes, 0);
    inputKey.set(keyRotationSecret, 32);

    const derivedKeyMaterial = await CryptoUtils.hkdf(
      inputKey,
      salt,
      context,
      FINGERPRINT_LENGTH + KEY_LENGTH
    );
    const fingerprint = derivedKeyMaterial.slice(0, FINGERPRINT_LENGTH);
    const key = derivedKeyMaterial.slice(
      FINGERPRINT_LENGTH,
      FINGERPRINT_LENGTH + KEY_LENGTH
    );

    return {
      kid:
        keyRotationTimestamp +
        "-" +
        ChromeUtils.base64URLEncode(fingerprint, {
          pad: false,
        }),
      k: ChromeUtils.base64URLEncode(key, {
        pad: false,
      }),
      kty: "oct",
    };
  }

  /**
   * Derive the scoped key for the one of our legacy sync-related scopes.
   *
   * These uses a different key-derivation algoritm that incorporates less server-provided
   * data, for backwards-compatibility reasons.
   *
   */
  async _deriveLegacyScopedKey(uid, kBbytes, scope, scopedKeyMetadata) {
    let kid, key;
    if (scope == SCOPE_APP_SYNC) {
      kid = await this._deriveXClientState(kBbytes);
      key = await this._deriveSyncKey(kBbytes);
    } else {
      throw new Error(`Unexpected legacy key-bearing scope: ${scope}`);
    }
    kid = CommonUtils.byteStringToArrayBuffer(kid);
    key = CommonUtils.byteStringToArrayBuffer(key);
    return this._formatLegacyScopedKey(kid, key, scope, scopedKeyMetadata);
  }

  /**
   * Format key material for a legacy scyne-related scope as a JWK.
   *
   * @param {ArrayBuffer} kid bytes of the key hash to use in the key identifier
   * @param {ArrayBuffer} key bytes of the derived sync key
   * @param {String} scope the scope with which this key is associated
   * @param {Number} keyRotationTimestamp server-provided timestamp of last key rotation
   * @returns {Object} key material formatted as a JWK object
   */
  _formatLegacyScopedKey(kid, key, scope, { keyRotationTimestamp }) {
    kid = ChromeUtils.base64URLEncode(kid, {
      pad: false,
    });
    key = ChromeUtils.base64URLEncode(key, {
      pad: false,
    });
    return {
      kid: `${keyRotationTimestamp}-${kid}`,
      k: key,
      kty: "oct",
    };
  }

  /**
   * Derive the Sync Key given the byte string kB.
   *
   * @returns Promise<HKDF(kB, undefined, "identity.mozilla.com/picl/v1/oldsync", 64)>
   */
  async _deriveSyncKey(kBbytes) {
    return CryptoUtils.hkdfLegacy(
      kBbytes,
      undefined,
      "identity.mozilla.com/picl/v1/oldsync",
      2 * 32
    );
  }

  /**
   * Derive the X-Client-State header given the byte string kB.
   *
   * @returns Promise<SHA256(kB)[:16]>
   */
  async _deriveXClientState(kBbytes) {
    return this._sha256(kBbytes).slice(0, 16);
  }

  _sha256(bytes) {
    let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
      Ci.nsICryptoHash
    );
    hasher.init(hasher.SHA256);
    return CryptoUtils.digestBytes(bytes, hasher);
  }
}
PK
!<*�����)modules/sessionstore/PrivacyLevel.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

// The following constants represent the different possible privacy levels that
// can be set by the user and that we need to consider when collecting text
// data, and cookies.
//
// Collect data from all sites (http and https).
const PRIVACY_NONE = 0;
// Collect data from unencrypted sites (http), only.
const PRIVACY_ENCRYPTED = 1;
// Collect no data.
const PRIVACY_FULL = 2;

export var PrivacyLevel = {
  /**
   * Returns whether the current privacy level allows saving data for the given
   * |url|.
   *
   * @param url The URL we want to save data for.
   * @return bool
   */
  check(url) {
    return PrivacyLevel.canSave(url.startsWith("https:"));
  },

  /**
   * Checks whether we're allowed to save data for a specific site.
   *
   * @param isHttps A boolean that tells whether the site uses TLS.
   * @return {bool} Whether we can save data for the specified site.
   */
  canSave(isHttps) {
    // Never save any data when full privacy is requested.
    if (this.privacyLevel == PRIVACY_FULL) {
      return false;
    }

    // Don't save data for encrypted sites when requested.
    if (isHttps && this.privacyLevel == PRIVACY_ENCRYPTED) {
      return false;
    }

    return true;
  },

  canSaveAnything() {
    return this.privacyLevel != PRIVACY_FULL;
  },

  shouldSaveEverything() {
    return this.privacyLevel == PRIVACY_NONE;
  },
};

XPCOMUtils.defineLazyPreferenceGetter(
  PrivacyLevel,
  "privacyLevel",
  "browser.sessionstore.privacy_level"
);
PK
!<�I�%�)�)modules/Downloads.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Main entry point to get references to all the back-end objects.
 */

import { Integration } from "resource://gre/modules/Integration.sys.mjs";

import {
  Download,
  DownloadError,
} from "resource://gre/modules/DownloadCore.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  DownloadCombinedList: "resource://gre/modules/DownloadList.sys.mjs",
  DownloadList: "resource://gre/modules/DownloadList.sys.mjs",
  DownloadSummary: "resource://gre/modules/DownloadList.sys.mjs",
});

Integration.downloads.defineESModuleGetter(
  lazy,
  "DownloadIntegration",
  "resource://gre/modules/DownloadIntegration.sys.mjs"
);

/**
 * This object is exposed directly to the consumers of this JavaScript module,
 * and provides the only entry point to get references to back-end objects.
 */
export const Downloads = {
  /**
   * Work on downloads that were not started from a private browsing window.
   */
  get PUBLIC() {
    return "{Downloads.PUBLIC}";
  },
  /**
   * Work on downloads that were started from a private browsing window.
   */
  get PRIVATE() {
    return "{Downloads.PRIVATE}";
  },
  /**
   * Work on both Downloads.PRIVATE and Downloads.PUBLIC downloads.
   */
  get ALL() {
    return "{Downloads.ALL}";
  },

  /**
   * Creates a new Download object.
   *
   * @param properties
   *        Provides the initial properties for the newly created download.
   *        This matches the serializable representation of a Download object.
   *        Some of the most common properties in this object include:
   *        {
   *          source: String containing the URI for the download source.
   *                  Alternatively, may be an nsIURI, a DownloadSource object,
   *                  or an object with the following properties:
   *          {
   *            url: String containing the URI for the download source.
   *            isPrivate: Indicates whether the download originated from a
   *                       private window.  If omitted, the download is public.
   *            referrerInfo: String or nsIReferrerInfo object represents the
   *                          referrerInfo of the download source.  Can be
   *                          omitted or null for example when the download
   *                          source is not HTTP.
   *            cookieJarSettings: The nsICookieJarSettings object represents
   *                               the cookieJarSetting of the download source.
   *                               Can be omitted or null if the download source
   *                               is not from a document.
   *          },
   *          target: String containing the path of the target file.
   *                  Alternatively, may be an nsIFile, a DownloadTarget object,
   *                  or an object with the following properties:
   *          {
   *            path: String containing the path of the target file.
   *          },
   *          saver: String representing the class of the download operation.
   *                 If omitted, defaults to "copy".  Alternatively, may be the
   *                 serializable representation of a DownloadSaver object.
   *        }
   *
   * @return {Promise}
   * @resolves The newly created Download object.
   * @rejects JavaScript exception.
   */
  async createDownload(properties) {
    return Download.fromSerializable(properties);
  },

  /**
   * Downloads data from a remote network location to a local file.
   *
   * This download method does not provide user interface, or the ability to
   * cancel or restart the download programmatically.  For that, you should
   * obtain a reference to a Download object using the createDownload function.
   *
   * Since the download cannot be restarted, any partially downloaded data will
   * not be kept in case the download fails.
   *
   * @param source
   *        String containing the URI for the download source.  Alternatively,
   *        may be an nsIURI or a DownloadSource object.
   * @param target
   *        String containing the path of the target file.  Alternatively, may
   *        be an nsIFile or a DownloadTarget object.
   * @param options
   *        An optional object used to control the behavior of this function.
   *        You may pass an object with a subset of the following fields:
   *        {
   *          isPrivate: Indicates whether the download originated from a
   *                     private window.
   *        }
   *
   * @return {Promise}
   * @resolves When the download has finished successfully.
   * @rejects JavaScript exception if the download failed.
   */
  async fetch(source, target, options) {
    const download = await this.createDownload({ source, target });

    if (options?.isPrivate) {
      download.source.isPrivate = options.isPrivate;
    }
    return download.start();
  },

  /**
   * Retrieves the specified type of DownloadList object.  There is one download
   * list for each type, and this method always retrieves a reference to the
   * same download list when called with the same argument.
   *
   * Calling this function may cause the list of public downloads to be reloaded
   * from the previous session, if it wasn't loaded already.
   *
   * @param type
   *        This can be Downloads.PUBLIC, Downloads.PRIVATE, or Downloads.ALL.
   *        Downloads added to the Downloads.PUBLIC and Downloads.PRIVATE lists
   *        are reflected in the Downloads.ALL list, and downloads added to the
   *        Downloads.ALL list are also added to either the Downloads.PUBLIC or
   *        the Downloads.PRIVATE list based on their properties.
   *
   * @return {Promise}
   * @resolves The requested DownloadList or DownloadCombinedList object.
   * @rejects JavaScript exception.
   */
  async getList(type) {
    if (!this._promiseListsInitialized) {
      this._promiseListsInitialized = (async () => {
        let publicList = new lazy.DownloadList();
        let privateList = new lazy.DownloadList();
        let combinedList = new lazy.DownloadCombinedList(
          publicList,
          privateList
        );

        try {
          await lazy.DownloadIntegration.addListObservers(publicList, false);
          await lazy.DownloadIntegration.addListObservers(privateList, true);
          await lazy.DownloadIntegration.initializePublicDownloadList(
            publicList
          );
        } catch (err) {
          console.error(err);
        }

        let publicSummary = await this.getSummary(Downloads.PUBLIC);
        let privateSummary = await this.getSummary(Downloads.PRIVATE);
        let combinedSummary = await this.getSummary(Downloads.ALL);

        await publicSummary.bindToList(publicList);
        await privateSummary.bindToList(privateList);
        await combinedSummary.bindToList(combinedList);

        this._lists[Downloads.PUBLIC] = publicList;
        this._lists[Downloads.PRIVATE] = privateList;
        this._lists[Downloads.ALL] = combinedList;
      })();
    }

    await this._promiseListsInitialized;

    return this._lists[type];
  },

  /**
   * Promise resolved when the initialization of the download lists has
   * completed, or null if initialization has never been requested.
   */
  _promiseListsInitialized: null,

  /**
   * After initialization, this object is populated with one key for each type
   * of download list that can be returned (Downloads.PUBLIC, Downloads.PRIVATE,
   * or Downloads.ALL).  The values are the DownloadList objects.
   */
  _lists: {},

  /**
   * Retrieves the specified type of DownloadSummary object.  There is one
   * download summary for each type, and this method always retrieves a
   * reference to the same download summary when called with the same argument.
   *
   * Calling this function does not cause the list of public downloads to be
   * reloaded from the previous session.  The summary will behave as if no
   * downloads are present until the getList method is called.
   *
   * @param type
   *        This can be Downloads.PUBLIC, Downloads.PRIVATE, or Downloads.ALL.
   *
   * @return {Promise}
   * @resolves The requested DownloadList or DownloadCombinedList object.
   * @rejects JavaScript exception.
   */
  async getSummary(type) {
    if (
      type != Downloads.PUBLIC &&
      type != Downloads.PRIVATE &&
      type != Downloads.ALL
    ) {
      throw new Error("Invalid type argument.");
    }

    if (!(type in this._summaries)) {
      this._summaries[type] = new lazy.DownloadSummary();
    }

    return this._summaries[type];
  },

  /**
   * This object is populated by the getSummary method with one key for each
   * type of object that can be returned (Downloads.PUBLIC, Downloads.PRIVATE,
   * or Downloads.ALL).  The values are the DownloadSummary objects.
   */
  _summaries: {},

  /**
   * Returns the system downloads directory asynchronously.
   *   Mac OSX:
   *     User downloads directory
   *   XP/2K:
   *     My Documents/Downloads
   *   Vista and others:
   *     User downloads directory
   *   Linux:
   *     XDG user dir spec, with a fallback to Home/Downloads
   *   Android:
   *     standard downloads directory i.e. /sdcard
   *
   * @return {Promise}
   * @resolves The downloads directory string path.
   */
  getSystemDownloadsDirectory() {
    return lazy.DownloadIntegration.getSystemDownloadsDirectory();
  },

  /**
   * Returns the preferred downloads directory based on the user preferences
   * in the current profile asynchronously.
   *
   * @return {Promise}
   * @resolves The downloads directory string path.
   */
  getPreferredDownloadsDirectory() {
    return lazy.DownloadIntegration.getPreferredDownloadsDirectory();
  },

  /**
   * Returns the temporary directory where downloads are placed before the
   * final location is chosen, or while the document is opened temporarily
   * with an external application. This may or may not be the system temporary
   * directory, based on the platform asynchronously.
   *
   * @return {Promise}
   * @resolves The downloads directory string path.
   */
  getTemporaryDownloadsDirectory() {
    return lazy.DownloadIntegration.getTemporaryDownloadsDirectory();
  },

  /**
   * Constructor for a DownloadError object.  When you catch an exception during
   * a download, you can use this to verify if "ex instanceof Downloads.Error",
   * before reading the exception properties with the error details.
   */
  Error: DownloadError,
};
PK
!<�΋����modules/DownloadCore.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Main implementation of the Downloads API objects. Consumers should get
 * references to these objects through the "Downloads.sys.mjs" module.
 */

import { Integration } from "resource://gre/modules/Integration.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  DownloadHistory: "resource://gre/modules/DownloadHistory.sys.mjs",
  DownloadPaths: "resource://gre/modules/DownloadPaths.sys.mjs",
  E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gExternalAppLauncher",
  "@mozilla.org/uriloader/external-helper-app-service;1",
  Ci.nsPIExternalAppLauncher
);
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gExternalHelperAppService",
  "@mozilla.org/uriloader/external-helper-app-service;1",
  Ci.nsIExternalHelperAppService
);

Integration.downloads.defineESModuleGetter(
  lazy,
  "DownloadIntegration",
  "resource://gre/modules/DownloadIntegration.sys.mjs"
);

const BackgroundFileSaverStreamListener = Components.Constructor(
  "@mozilla.org/network/background-file-saver;1?mode=streamlistener",
  "nsIBackgroundFileSaver"
);

/**
 * Returns true if the given value is a primitive string or a String object.
 */
function isString(aValue) {
  // We cannot use the "instanceof" operator reliably across module boundaries.
  return (
    typeof aValue == "string" ||
    (typeof aValue == "object" && "charAt" in aValue)
  );
}

/**
 * Serialize the unknown properties of aObject into aSerializable.
 */
function serializeUnknownProperties(aObject, aSerializable) {
  if (aObject._unknownProperties) {
    for (let property in aObject._unknownProperties) {
      aSerializable[property] = aObject._unknownProperties[property];
    }
  }
}

/**
 * Check for any unknown properties in aSerializable and preserve those in the
 * _unknownProperties field of aObject. aFilterFn is called for each property
 * name of aObject and should return true only for unknown properties.
 */
function deserializeUnknownProperties(aObject, aSerializable, aFilterFn) {
  for (let property in aSerializable) {
    if (aFilterFn(property)) {
      if (!aObject._unknownProperties) {
        aObject._unknownProperties = {};
      }

      aObject._unknownProperties[property] = aSerializable[property];
    }
  }
}

/**
 * Check if the file is a placeholder.
 *
 * @return {Promise}
 * @resolves {boolean}
 * @rejects Never.
 */
async function isPlaceholder(path) {
  try {
    if ((await IOUtils.stat(path)).size == 0) {
      return true;
    }
  } catch (ex) {
    // Canceling the download may have removed the placeholder already.
    if (ex.name != "NotFoundError") {
      console.error(ex);
    }
  }
  return false;
}

/**
 * This determines the minimum time interval between updates to the number of
 * bytes transferred, and is a limiting factor to the sequence of readings used
 * in calculating the speed of the download.
 */
const kProgressUpdateIntervalMs = 400;

/**
 * Represents a single download, with associated state and actions.  This object
 * is transient, though it can be included in a DownloadList so that it can be
 * managed by the user interface and persisted across sessions.
 */
export var Download = function () {
  this._deferSucceeded = Promise.withResolvers();
};

Download.prototype = {
  /**
   * DownloadSource object associated with this download.
   */
  source: null,

  /**
   * DownloadTarget object associated with this download.
   */
  target: null,

  /**
   * DownloadSaver object associated with this download.
   */
  saver: null,

  /**
   * Indicates that the download never started, has been completed successfully,
   * failed, or has been canceled.  This property becomes false when a download
   * is started for the first time, or when a failed or canceled download is
   * restarted.
   */
  stopped: true,

  /**
   * Indicates that the download has been completed successfully.
   */
  succeeded: false,

  /**
   * Indicates that the download has been canceled.  This property can become
   * true, then it can be reset to false when a canceled download is restarted.
   *
   * This property becomes true as soon as the "cancel" method is called, though
   * the "stopped" property might remain false until the cancellation request
   * has been processed.  Temporary files or part files may still exist even if
   * they are expected to be deleted, until the "stopped" property becomes true.
   */
  canceled: false,

  /**
   * Downloaded files can be deleted from within Firefox, e.g. via the context
   * menu. Currently Firefox does not track file moves (see bug 1746386), so if
   * a download's target file stops existing we have to assume it's "moved or
   * missing." To distinguish files intentionally deleted within Firefox from
   * files that are moved/missing, we mark them as "deleted" with this property.
   */
  deleted: false,

  /**
   * When the download fails, this is set to a DownloadError instance indicating
   * the cause of the failure.  If the download has been completed successfully
   * or has been canceled, this property is null.  This property is reset to
   * null when a failed download is restarted.
   */
  error: null,

  /**
   * Indicates the start time of the download.  When the download starts,
   * this property is set to a valid Date object.  The default value is null
   * before the download starts.
   */
  startTime: null,

  /**
   * Indicates whether this download's "progress" property is able to report
   * partial progress while the download proceeds, and whether the value in
   * totalBytes is relevant.  This depends on the saver and the download source.
   */
  hasProgress: false,

  /**
   * Progress percent, from 0 to 100.  Intermediate values are reported only if
   * hasProgress is true.
   *
   * @note You shouldn't rely on this property being equal to 100 to determine
   *       whether the download is completed.  You should use the individual
   *       state properties instead.
   */
  progress: 0,

  /**
   * When hasProgress is true, indicates the total number of bytes to be
   * transferred before the download finishes, that can be zero for empty files.
   *
   * When hasProgress is false, this property is always zero.
   *
   * @note This property may be different than the final file size on disk for
   *       downloads that are encoded during the network transfer.  You can use
   *       the "size" property of the DownloadTarget object to get the actual
   *       size on disk once the download succeeds.
   */
  totalBytes: 0,

  /**
   * Number of bytes currently transferred.  This value starts at zero, and may
   * be updated regardless of the value of hasProgress.
   *
   * @note You shouldn't rely on this property being equal to totalBytes to
   *       determine whether the download is completed.  You should use the
   *       individual state properties instead.  This property may not be
   *       updated during the last part of the download.
   */
  currentBytes: 0,

  /**
   * Fractional number representing the speed of the download, in bytes per
   * second.  This value is zero when the download is stopped, and may be
   * updated regardless of the value of hasProgress.
   */
  speed: 0,

  /**
   * Indicates whether, at this time, there is any partially downloaded data
   * that can be used when restarting a failed or canceled download.
   *
   * Even if the download has partial data on disk, hasPartialData will be false
   * if that data cannot be used to restart the download. In order to determine
   * if a part file is being used which contains partial data the
   * Download.target.partFilePath should be checked.
   *
   * This property is relevant while the download is in progress, and also if it
   * failed or has been canceled.  If the download has been completed
   * successfully, this property is always false.
   *
   * Whether partial data can actually be retained depends on the saver and the
   * download source, and may not be known before the download is started.
   */
  hasPartialData: false,

  /**
   * Indicates whether, at this time, there is any data that has been blocked.
   * Since reputation blocking takes place after the download has fully
   * completed a value of true also indicates 100% of the data is present.
   */
  hasBlockedData: false,

  /**
   * This can be set to a function that is called after other properties change.
   */
  onchange: null,

  /**
   * This tells if the user has chosen to open/run the downloaded file after
   * download has completed.
   */
  launchWhenSucceeded: false,

  /**
   * When a download starts, we typically want to automatically open the
   * downloads panel if the pref browser.download.alwaysOpenPanel is enabled.
   * However, there are conditions where we want to prevent this. For example, a
   * false value can prevent the downloads panel from opening when an add-on
   * creates a download without user input as part of some background operation.
   */
  openDownloadsListOnStart: true,

  /**
   * This represents the MIME type of the download.
   */
  contentType: null,

  /**
   * This indicates the path of the application to be used to launch the file,
   * or null if the file should be launched with the default application.
   */
  launcherPath: null,

  /**
   * Raises the onchange notification.
   */
  _notifyChange: function D_notifyChange() {
    try {
      if (this.onchange) {
        this.onchange();
      }
    } catch (ex) {
      console.error(ex);
    }
  },

  /**
   * The download may be stopped and restarted multiple times before it
   * completes successfully. This may happen if any of the download attempts is
   * canceled or fails.
   *
   * This property contains a promise that is linked to the current attempt, or
   * null if the download is either stopped or in the process of being canceled.
   * If the download restarts, this property is replaced with a new promise.
   *
   * The promise is resolved if the attempt it represents finishes successfully,
   * and rejected if the attempt fails.
   */
  _currentAttempt: null,

  /**
   * The download was launched to open from the Downloads Panel.
   */
  _launchedFromPanel: false,

  /**
   * Starts the download for the first time, or restarts a download that failed
   * or has been canceled.
   *
   * Calling this method when the download has been completed successfully has
   * no effect, and the method returns a resolved promise.  If the download is
   * in progress, the method returns the same promise as the previous call.
   *
   * If the "cancel" method was called but the cancellation process has not
   * finished yet, this method waits for the cancellation to finish, then
   * restarts the download immediately.
   *
   * @note If you need to start a new download from the same source, rather than
   *       restarting a failed or canceled one, you should create a separate
   *       Download object with the same source as the current one.
   *
   * @return {Promise}
   * @resolves When the download has finished successfully.
   * @rejects JavaScript exception if the download failed.
   */
  start: function D_start() {
    // If the download succeeded, it's the final state, we have nothing to do.
    if (this.succeeded) {
      return Promise.resolve();
    }

    // If the download already started and hasn't failed or hasn't been
    // canceled, return the same promise as the previous call, allowing the
    // caller to wait for the current attempt to finish.
    if (this._currentAttempt) {
      return this._currentAttempt;
    }

    // While shutting down or disposing of this object, we prevent the download
    // from returning to be in progress.
    if (this._finalized) {
      return Promise.reject(
        new DownloadError({
          message: "Cannot start after finalization.",
        })
      );
    }

    if (this.error && this.error.becauseBlockedByReputationCheck) {
      return Promise.reject(
        new DownloadError({
          message: "Cannot start after being blocked by a reputation check.",
        })
      );
    }

    // Initialize all the status properties for a new or restarted download.
    this.stopped = false;
    this.canceled = false;
    this.error = null;
    // Avoid serializing the previous error, or it would be restored on the next
    // startup, even if the download was restarted.
    delete this._unknownProperties?.errorObj;
    this.hasProgress = false;
    this.hasBlockedData = false;
    this.progress = 0;
    this.totalBytes = 0;
    this.currentBytes = 0;
    this.startTime = new Date();

    // Create a new deferred object and an associated promise before starting
    // the actual download.  We store it on the download as the current attempt.
    let deferAttempt = Promise.withResolvers();
    let currentAttempt = deferAttempt.promise;
    this._currentAttempt = currentAttempt;

    // Restart the progress and speed calculations from scratch.
    this._lastProgressTimeMs = 0;

    // This function propagates progress from the DownloadSaver object, unless
    // it comes in late from a download attempt that was replaced by a new one.
    // If the cancellation process for the download has started, then the update
    // is ignored.
    function DS_setProgressBytes(aCurrentBytes, aTotalBytes, aHasPartialData) {
      if (this._currentAttempt == currentAttempt) {
        this._setBytes(aCurrentBytes, aTotalBytes, aHasPartialData);
      }
    }

    // This function propagates download properties from the DownloadSaver
    // object, unless it comes in late from a download attempt that was
    // replaced by a new one.  If the cancellation process for the download has
    // started, then the update is ignored.
    function DS_setProperties(aOptions) {
      if (this._currentAttempt != currentAttempt) {
        return;
      }

      let changeMade = false;

      for (let property of [
        "contentType",
        "progress",
        "hasPartialData",
        "hasBlockedData",
      ]) {
        if (property in aOptions && this[property] != aOptions[property]) {
          this[property] = aOptions[property];
          changeMade = true;
        }
      }

      if (changeMade) {
        this._notifyChange();
      }
    }

    // Now that we stored the promise in the download object, we can start the
    // task that will actually execute the download.
    deferAttempt.resolve(
      (async () => {
        // Wait upon any pending operation before restarting.
        if (this._promiseCanceled) {
          await this._promiseCanceled;
        }
        if (this._promiseRemovePartialData) {
          try {
            await this._promiseRemovePartialData;
          } catch (ex) {
            // Ignore any errors, which are already reported by the original
            // caller of the removePartialData method.
          }
        }

        // In case the download was restarted while cancellation was in progress,
        // but the previous attempt actually succeeded before cancellation could
        // be processed, it is possible that the download has already finished.
        if (this.succeeded) {
          return;
        }

        try {
          if (this.downloadingToSameFile()) {
            throw new DownloadError({
              message: "Can't overwrite the source file.",
              becauseTargetFailed: true,
            });
          }

          // Disallow download if parental controls service restricts it.
          if (
            await lazy.DownloadIntegration.shouldBlockForParentalControls(this)
          ) {
            throw new DownloadError({ becauseBlockedByParentalControls: true });
          }

          // We should check if we have been canceled in the meantime, after all
          // the previous asynchronous operations have been executed and just
          // before we call the "execute" method of the saver.
          if (this._promiseCanceled) {
            // The exception will become a cancellation in the "catch" block.
            throw new Error(undefined);
          }

          // Execute the actual download through the saver object.
          this._saverExecuting = true;
          try {
            await this.saver.execute(
              DS_setProgressBytes.bind(this),
              DS_setProperties.bind(this)
            );
          } catch (ex) {
            // Remove the target file placeholder and all partial data when
            // needed, independently of which code path failed. In some cases, the
            // component executing the download may have already removed the file.
            if (!this.hasPartialData && !this.hasBlockedData) {
              await this.saver.removeData(true);
            }
            throw ex;
          }

          // Now that the actual saving finished, read the actual file size on
          // disk, that may be different from the amount of data transferred.
          await this.target.refresh();

          // Check for the last time if the download has been canceled. This must
          // be done right before setting the "stopped" property of the download,
          // without any asynchronous operations in the middle, so that another
          // cancellation request cannot start in the meantime and stay unhandled.
          if (this._promiseCanceled) {
            // To keep the internal state of the Download object consistent, we
            // just delete the target and effectively cancel the download. Since
            // the DownloadSaver succeeded, we already renamed the ".part" file to
            // the final name, and this results in all the data being deleted.
            await this.saver.removeData(true);

            // Cancellation exceptions will be changed in the catch block below.
            throw new DownloadError();
          }

          // Update the status properties for a successful download.
          this.progress = 100;
          this.succeeded = true;
          this.hasPartialData = false;
        } catch (originalEx) {
          // We may choose a different exception to propagate in the code below,
          // or wrap the original one. We do this mutation in a different variable
          // because of the "no-ex-assign" ESLint rule.
          let ex = originalEx;

          // Fail with a generic status code on cancellation, so that the caller
          // is forced to actually check the status properties to see if the
          // download was canceled or failed because of other reasons.
          if (this._promiseCanceled) {
            throw new DownloadError({ message: "Download canceled." });
          }

          // An HTTP 450 error code is used by Windows to indicate that a uri is
          // blocked by parental controls. This will prevent the download from
          // occuring, so an error needs to be raised. This is not performed
          // during the parental controls check above as it requires the request
          // to start.
          if (this._blockedByParentalControls) {
            ex = new DownloadError({ becauseBlockedByParentalControls: true });
          }

          // Update the download error, unless a new attempt already started. The
          // change in the status property is notified in the finally block.
          if (this._currentAttempt == currentAttempt || !this._currentAttempt) {
            if (!(ex instanceof DownloadError)) {
              let properties = { innerException: ex };

              if (ex.message) {
                properties.message = ex.message;
              }

              ex = new DownloadError(properties);
            }
            // Don't store an error if it's an abort caused by shutdown, so the
            // download can be retried automatically at the next startup.
            if (
              originalEx.result != Cr.NS_ERROR_ABORT ||
              !Services.startup.isInOrBeyondShutdownPhase(
                Ci.nsIAppStartup.SHUTDOWN_PHASE_APPSHUTDOWNCONFIRMED
              )
            ) {
              this.error = ex;
            }
          }
          throw ex;
        } finally {
          // Any cancellation request has now been processed.
          this._saverExecuting = false;
          this._promiseCanceled = null;

          // Update the status properties, unless a new attempt already started.
          if (this._currentAttempt == currentAttempt || !this._currentAttempt) {
            this._currentAttempt = null;
            this.stopped = true;
            this.speed = 0;
            this._notifyChange();
            if (this.succeeded) {
              await this._succeed();
            }
          }
        }
      })()
    );

    // Notify the new download state before returning.
    this._notifyChange();
    return currentAttempt;
  },

  /**
   * Perform the actions necessary when a Download succeeds.
   *
   * @return {Promise}
   * @resolves When the steps to take after success have completed.
   * @rejects  JavaScript exception if any of the operations failed.
   */
  async _succeed() {
    await lazy.DownloadIntegration.downloadDone(this);

    this._deferSucceeded.resolve();

    if (this.launchWhenSucceeded) {
      this.launch().catch(console.error);

      // Always schedule files to be deleted at the end of the private browsing
      // mode, regardless of the value of the pref.
      if (this.source.isPrivate) {
        lazy.gExternalAppLauncher.deleteTemporaryPrivateFileWhenPossible(
          new lazy.FileUtils.File(this.target.path)
        );
      } else if (
        Services.prefs.getBoolPref("browser.helperApps.deleteTempFileOnExit") &&
        Services.prefs.getBoolPref(
          "browser.download.start_downloads_in_tmp_dir",
          false
        )
      ) {
        lazy.gExternalAppLauncher.deleteTemporaryFileOnExit(
          new lazy.FileUtils.File(this.target.path)
        );
      }
    }
  },

  /**
   * When a request to unblock the download is received, contains a promise
   * that will be resolved when the unblock request is completed. This property
   * will then continue to hold the promise indefinitely.
   */
  _promiseUnblock: null,

  /**
   * When a request to confirm the block of the download is received, contains
   * a promise that will be resolved when cleaning up the download has
   * completed. This property will then continue to hold the promise
   * indefinitely.
   */
  _promiseConfirmBlock: null,

  /**
   * Unblocks a download which had been blocked by reputation.
   *
   * The file will be moved out of quarantine and the download will be
   * marked as succeeded.
   *
   * @return {Promise}
   * @resolves When the Download has been unblocked and succeeded.
   * @rejects  JavaScript exception if any of the operations failed.
   */
  unblock() {
    if (this._promiseUnblock) {
      return this._promiseUnblock;
    }

    if (this._promiseConfirmBlock) {
      return Promise.reject(
        new Error("Download block has been confirmed, cannot unblock.")
      );
    }

    if (this.error?.becauseBlockedByReputationCheck) {
      Services.telemetry
        .getKeyedHistogramById("DOWNLOADS_USER_ACTION_ON_BLOCKED_DOWNLOAD")
        .add(this.error.reputationCheckVerdict, 2); // unblock
    }

    if (
      this.error?.reputationCheckVerdict == DownloadError.BLOCK_VERDICT_INSECURE
    ) {
      // In this Error case, the download was actually canceled before it was
      // passed to the Download UI. So we need to start the download here.
      this.error = null;
      this.succeeded = false;
      this.hasBlockedData = false;
      // This ensures the verdict will not get set again after the browser
      // restarts and the download gets serialized and de-serialized again.
      delete this._unknownProperties?.errorObj;
      this.start()
        .catch(err => {
          if (err.becauseTargetFailed) {
            // In case we cannot write to the target file
            // retry with a new unique name
            let uniquePath = lazy.DownloadPaths.createNiceUniqueFile(
              new lazy.FileUtils.File(this.target.path)
            ).path;
            this.target.path = uniquePath;
            return this.start();
          }
          return Promise.reject(err);
        })
        .catch(err => {
          if (!this.canceled) {
            console.error(err);
          }
          this._notifyChange();
        });
      this._notifyChange();
      this._promiseUnblock = lazy.DownloadIntegration.downloadDone(this);
      return this._promiseUnblock;
    }

    if (!this.hasBlockedData) {
      return Promise.reject(
        new Error("unblock may only be called on Downloads with blocked data.")
      );
    }

    this._promiseUnblock = (async () => {
      try {
        await IOUtils.move(this.target.partFilePath, this.target.path);
        await this.target.refresh();
      } catch (ex) {
        await this.refresh();
        this._promiseUnblock = null;
        throw ex;
      }

      this.succeeded = true;
      this.hasBlockedData = false;
      this._notifyChange();
      await this._succeed();
    })();

    return this._promiseUnblock;
  },

  /**
   * Confirms that a blocked download should be cleaned up.
   *
   * If a download was blocked but retained on disk this method can be used
   * to remove the file.
   *
   * @return {Promise}
   * @resolves When the Download's data has been removed.
   * @rejects  JavaScript exception if any of the operations failed.
   */
  confirmBlock() {
    if (this._promiseConfirmBlock) {
      return this._promiseConfirmBlock;
    }

    if (this._promiseUnblock) {
      return Promise.reject(
        new Error("Download is being unblocked, cannot confirmBlock.")
      );
    }

    if (this.error?.becauseBlockedByReputationCheck) {
      // We have to record the telemetry in both DownloadsCommon.deleteDownload
      // and confirmBlock here. The former is for cases where users click
      // "Remove file" in the download panel and the latter is when
      // users click "X" button in about:downloads.
      Services.telemetry
        .getKeyedHistogramById("DOWNLOADS_USER_ACTION_ON_BLOCKED_DOWNLOAD")
        .add(this.error.reputationCheckVerdict, 1); // confirm block
    }

    if (!this.hasBlockedData) {
      return Promise.reject(
        new Error(
          "confirmBlock may only be called on Downloads with blocked data."
        )
      );
    }

    this._promiseConfirmBlock = (async () => {
      // This call never throws exceptions. If the removal fails, the blocked
      // data remains stored on disk in the ".part" file.
      await this.saver.removeData();

      this.hasBlockedData = false;
      this._notifyChange();
    })();

    return this._promiseConfirmBlock;
  },

  /*
   * Launches the file after download has completed. This can open
   * the file with the default application for the target MIME type
   * or file extension, or with a custom application if launcherPath
   * is set.
   *
   * @param options.openWhere  Optional string indicating how to open when handling
   *                           download by opening the target file URI.
   *                           One of "window", "tab", "tabshifted"
   * @param options.useSystemDefault
   *                           Optional value indicating how to handle launching this download,
   *                           this time only. Will override the associated mimeInfo.preferredAction
   * @return {Promise}
   * @resolves When the instruction to launch the file has been
   *           successfully given to the operating system. Note that
   *           the OS might still take a while until the file is actually
   *           launched.
   * @rejects  JavaScript exception if there was an error trying to launch
   *           the file.
   */
  launch(options = {}) {
    if (!this.succeeded) {
      return Promise.reject(
        new Error("launch can only be called if the download succeeded")
      );
    }

    if (this._launchedFromPanel) {
      Services.telemetry.scalarAdd("downloads.file_opened", 1);
    }

    return lazy.DownloadIntegration.launchDownload(this, options);
  },

  /*
   * Shows the folder containing the target file, or where the target file
   * will be saved. This may be called at any time, even if the download
   * failed or is currently in progress.
   *
   * @return {Promise}
   * @resolves When the instruction to open the containing folder has been
   *           successfully given to the operating system. Note that
   *           the OS might still take a while until the folder is actually
   *           opened.
   * @rejects  JavaScript exception if there was an error trying to open
   *           the containing folder.
   */
  showContainingDirectory: function D_showContainingDirectory() {
    return lazy.DownloadIntegration.showContainingDirectory(this.target.path);
  },

  /**
   * When a request to cancel the download is received, contains a promise that
   * will be resolved when the cancellation request is processed.  When the
   * request is processed, this property becomes null again.
   */
  _promiseCanceled: null,

  /**
   * True between the call to the "execute" method of the saver and the
   * completion of the current download attempt.
   */
  _saverExecuting: false,

  /**
   * Cancels the download.
   *
   * The cancellation request is asynchronous.  Until the cancellation process
   * finishes, temporary files or part files may still exist even if they are
   * expected to be deleted.
   *
   * In case the download completes successfully before the cancellation request
   * could be processed, this method has no effect, and it returns a resolved
   * promise.  You should check the properties of the download at the time the
   * returned promise is resolved to determine if the download was cancelled.
   *
   * Calling this method when the download has been completed successfully,
   * failed, or has been canceled has no effect, and the method returns a
   * resolved promise.  This behavior is designed for the case where the call
   * to "cancel" happens asynchronously, and is consistent with the case where
   * the cancellation request could not be processed in time.
   *
   * @return {Promise}
   * @resolves When the cancellation process has finished.
   * @rejects Never.
   */
  cancel: function D_cancel() {
    // If the download is currently stopped, we have nothing to do.
    if (this.stopped) {
      return Promise.resolve();
    }

    if (!this._promiseCanceled) {
      // Start a new cancellation request.
      this._promiseCanceled = new Promise(resolve => {
        this._currentAttempt.then(resolve, resolve);
      });

      // The download can already be restarted.
      this._currentAttempt = null;

      // Notify that the cancellation request was received.
      this.canceled = true;
      this._notifyChange();

      // Execute the actual cancellation through the saver object, in case it
      // has already started.  Otherwise, the cancellation will be handled just
      // before the saver is started.
      if (this._saverExecuting) {
        this.saver.cancel();
      }
    }

    return this._promiseCanceled;
  },

  /**
   * Indicates whether any partially downloaded data should be retained, to use
   * when restarting a failed or canceled download.  The default is false.
   *
   * Whether partial data can actually be retained depends on the saver and the
   * download source, and may not be known before the download is started.
   *
   * To have any effect, this property must be set before starting the download.
   * Resetting this property to false after the download has already started
   * will not remove any partial data.
   *
   * If this property is set to true, care should be taken that partial data is
   * removed before the reference to the download is discarded.  This can be
   * done using the removePartialData or the "finalize" methods.
   */
  tryToKeepPartialData: false,

  /**
   * When a request to remove partially downloaded data is received, contains a
   * promise that will be resolved when the removal request is processed.  When
   * the request is processed, this property becomes null again.
   */
  _promiseRemovePartialData: null,

  /**
   * Removes any partial data kept as part of a canceled or failed download.
   *
   * If the download is not canceled or failed, this method has no effect, and
   * it returns a resolved promise.  If the "cancel" method was called but the
   * cancellation process has not finished yet, this method waits for the
   * cancellation to finish, then removes the partial data.
   *
   * After this method has been called, if the tryToKeepPartialData property is
   * still true when the download is restarted, partial data will be retained
   * during the new download attempt.
   *
   * @return {Promise}
   * @resolves When the partial data has been successfully removed.
   * @rejects JavaScript exception if the operation could not be completed.
   */
  removePartialData() {
    if (!this.canceled && !this.error) {
      return Promise.resolve();
    }

    if (!this._promiseRemovePartialData) {
      this._promiseRemovePartialData = (async () => {
        try {
          // Wait upon any pending cancellation request.
          if (this._promiseCanceled) {
            await this._promiseCanceled;
          }
          // Ask the saver object to remove any partial data.
          await this.saver.removeData();
          // For completeness, clear the number of bytes transferred.
          if (this.currentBytes != 0 || this.hasPartialData) {
            this.currentBytes = 0;
            this.hasPartialData = false;
            this.target.refreshPartFileState();
            this._notifyChange();
          }
        } finally {
          this._promiseRemovePartialData = null;
        }
      })();
    }

    return this._promiseRemovePartialData;
  },

  /**
   * Returns true if the download source is the same as the target file.
   */
  downloadingToSameFile() {
    if (!this.source.url || !this.source.url.startsWith("file:")) {
      return false;
    }

    try {
      let sourceUri = lazy.NetUtil.newURI(this.source.url);
      let targetUri = lazy.NetUtil.newURI(
        new lazy.FileUtils.File(this.target.path)
      );
      return sourceUri.equals(targetUri);
    } catch (ex) {
      return false;
    }
  },

  /**
   * This deferred object contains a promise that is resolved as soon as this
   * download finishes successfully, and is never rejected.  This property is
   * initialized when the download is created, and never changes.
   */
  _deferSucceeded: null,

  /**
   * Returns a promise that is resolved as soon as this download finishes
   * successfully, even if the download was stopped and restarted meanwhile.
   *
   * You can use this property for scheduling download completion actions in the
   * current session, for downloads that are controlled interactively.  If the
   * download is not controlled interactively, you should use the promise
   * returned by the "start" method instead, to check for success or failure.
   *
   * @return {Promise}
   * @resolves When the download has finished successfully.
   * @rejects Never.
   */
  whenSucceeded: function D_whenSucceeded() {
    return this._deferSucceeded.promise;
  },

  /**
   * Updates the state of a finished, failed, or canceled download based on the
   * current state in the file system.  If the download is in progress or it has
   * been finalized, this method has no effect, and it returns a resolved
   * promise.
   *
   * This allows the properties of the download to be updated in case the user
   * moved or deleted the target file or its associated ".part" file.
   *
   * @return {Promise}
   * @resolves When the operation has completed.
   * @rejects Never.
   */
  refresh() {
    return (async () => {
      if (!this.stopped || this._finalized) {
        return;
      }

      if (this.succeeded) {
        let oldExists = this.target.exists;
        let oldSize = this.target.size;
        await this.target.refresh();
        if (oldExists != this.target.exists || oldSize != this.target.size) {
          this._notifyChange();
        }
        return;
      }

      // Update the current progress from disk if we retained partial data.
      if (
        (this.hasPartialData || this.hasBlockedData) &&
        this.target.partFilePath
      ) {
        try {
          let stat = await IOUtils.stat(this.target.partFilePath);

          // Ignore the result if the state has changed meanwhile.
          if (!this.stopped || this._finalized) {
            return;
          }

          // Update the bytes transferred and the related progress properties.
          this.currentBytes = stat.size;
          if (this.totalBytes > 0) {
            this.hasProgress = true;
            this.progress = Math.floor(
              (this.currentBytes / this.totalBytes) * 100
            );
          }
        } catch (ex) {
          if (ex.name != "NotFoundError") {
            throw ex;
          }
          // Ignore the result if the state has changed meanwhile.
          if (!this.stopped || this._finalized) {
            return;
          }
          // In case we've blocked the Download becasue its
          // insecure, we should not set hasBlockedData to
          // false as its required to show the Unblock option.
          if (
            this.error.reputationCheckVerdict ==
            DownloadError.BLOCK_VERDICT_INSECURE
          ) {
            return;
          }

          this.hasBlockedData = false;
          this.hasPartialData = false;
        }

        this._notifyChange();
      }
    })().catch(console.error);
  },

  /**
   * True if the "finalize" method has been called.  This prevents the download
   * from starting again after having been stopped.
   */
  _finalized: false,

  /**
   * True if the "finalize" has been called and fully finished it's execution.
   */
  _finalizeExecuted: false,

  /**
   * Ensures that the download is stopped, and optionally removes any partial
   * data kept as part of a canceled or failed download.  After this method has
   * been called, the download cannot be started again.
   *
   * This method should be used in place of "cancel" and removePartialData while
   * shutting down or disposing of the download object, to prevent other callers
   * from interfering with the operation.  This is required because cancellation
   * and other operations are asynchronous.
   *
   * @param aRemovePartialData
   *        Whether any partially downloaded data should be removed after the
   *        download has been stopped.
   *
   * @return {Promise}
   * @resolves When the operation has finished successfully.
   * @rejects JavaScript exception if an error occurred while removing the
   *          partially downloaded data.
   */
  finalize(aRemovePartialData) {
    // Prevents the download from starting again after having been stopped.
    this._finalized = true;
    let promise;

    if (aRemovePartialData) {
      // Cancel the download, in case it is currently in progress, then remove
      // any partially downloaded data.  The removal operation waits for
      // cancellation to be completed before resolving the promise it returns.
      this.cancel();
      promise = this.removePartialData();
    } else {
      // Just cancel the download, in case it is currently in progress.
      promise = this.cancel();
    }
    promise.then(() => {
      // At this point, either removing data / just cancelling the download should be done.
      this._finalizeExecuted = true;
    });

    return promise;
  },

  /**
   * Deletes all file data associated with a download, preserving the download
   * object itself and updating it for download views.
   */
  async manuallyRemoveData() {
    let { path } = this.target;
    if (this.succeeded) {
      // Temp files are made "read-only" by DownloadIntegration.downloadDone, so
      // reset the permission bits to read/write. This won't be necessary after
      // bug 1733587 since Downloads won't ever be temporary.
      await IOUtils.setPermissions(path, 0o660);
      await IOUtils.remove(path, { ignoreAbsent: true });
    }
    this.deleted = true;
    await this.cancel();
    await this.removePartialData();
    // We need to guarantee that the UI is refreshed irrespective of what state
    // the download is in when this is called, to ensure the download doesn't
    // wind up stuck displaying as if it exists when it actually doesn't. And
    // that means updating this.target.partFileExists no matter what.
    await this.target.refreshPartFileState();
    await this.refresh();
    // The above methods will sometimes call _notifyChange, but not always. It
    // depends on whether the download is `succeeded`, `stopped`, `canceled`,
    // etc. Since this method needs to update the UI and can be invoked on any
    // download as long as its target has some file on the system, we need to
    // call _notifyChange no matter what state the download is in.
    this._notifyChange();
  },

  /**
   * Indicates the time of the last progress notification, expressed as the
   * number of milliseconds since January 1, 1970, 00:00:00 UTC.  This is zero
   * until some bytes have actually been transferred.
   */
  _lastProgressTimeMs: 0,

  /**
   * Updates progress notifications based on the number of bytes transferred.
   *
   * The number of bytes transferred is not updated unless enough time passed
   * since this function was last called.  This limits the computation load, in
   * particular when the listeners update the user interface in response.
   *
   * @param aCurrentBytes
   *        Number of bytes transferred until now.
   * @param aTotalBytes
   *        Total number of bytes to be transferred, or -1 if unknown.
   * @param aHasPartialData
   *        Indicates whether the partially downloaded data can be used when
   *        restarting the download if it fails or is canceled.
   */
  _setBytes: function D_setBytes(aCurrentBytes, aTotalBytes, aHasPartialData) {
    let changeMade = this.hasPartialData != aHasPartialData;
    this.hasPartialData = aHasPartialData;

    // Unless aTotalBytes is -1, we can report partial download progress.  In
    // this case, notify when the related properties changed since last time.
    if (
      aTotalBytes != -1 &&
      (!this.hasProgress || this.totalBytes != aTotalBytes)
    ) {
      this.hasProgress = true;
      this.totalBytes = aTotalBytes;
      changeMade = true;
    }

    // Updating the progress and computing the speed require that enough time
    // passed since the last update, or that we haven't started throttling yet.
    let currentTimeMs = Date.now();
    let intervalMs = currentTimeMs - this._lastProgressTimeMs;
    if (intervalMs >= kProgressUpdateIntervalMs) {
      // Don't compute the speed unless we started throttling notifications.
      if (this._lastProgressTimeMs != 0) {
        // Calculate the speed in bytes per second.
        let rawSpeed =
          ((aCurrentBytes - this.currentBytes) / intervalMs) * 1000;
        if (this.speed == 0) {
          // When the previous speed is exactly zero instead of a fractional
          // number, this can be considered the first element of the series.
          this.speed = rawSpeed;
        } else {
          // Apply exponential smoothing, with a smoothing factor of 0.1.
          this.speed = rawSpeed * 0.1 + this.speed * 0.9;
        }
      }

      // Start throttling notifications only when we have actually received some
      // bytes for the first time.  The timing of the first part of the download
      // is not reliable, due to possible latency in the initial notifications.
      // This also allows automated tests to receive and verify the number of
      // bytes initially transferred.
      if (aCurrentBytes > 0) {
        this._lastProgressTimeMs = currentTimeMs;

        // Update the progress now that we don't need its previous value.
        this.currentBytes = aCurrentBytes;
        if (this.totalBytes > 0) {
          this.progress = Math.floor(
            (this.currentBytes / this.totalBytes) * 100
          );
        }
        changeMade = true;
      }

      if (this.hasProgress && this.target && !this.target.partFileExists) {
        this.target.refreshPartFileState();
      }
    }

    if (changeMade) {
      this._notifyChange();
    }
  },

  /**
   * Returns a static representation of the current object state.
   *
   * @return A JavaScript object that can be serialized to JSON.
   */
  toSerializable() {
    let serializable = {
      source: this.source.toSerializable(),
      target: this.target.toSerializable(),
    };

    let saver = this.saver.toSerializable();
    if (!serializable.source || !saver) {
      // If we are unable to serialize either the source or the saver,
      // we won't persist the download.
      return null;
    }

    // Simplify the representation for the most common saver type.  If the saver
    // is an object instead of a simple string, we can't simplify it because we
    // need to persist all its properties, not only "type".  This may happen for
    // savers of type "copy" as well as other types.
    if (saver !== "copy") {
      serializable.saver = saver;
    }

    if (this.error) {
      serializable.errorObj = this.error.toSerializable();
    }

    if (this.startTime) {
      serializable.startTime = this.startTime.toJSON();
    }

    // These are serialized unless they are false, null, or empty strings.
    for (let property of kPlainSerializableDownloadProperties) {
      if (this[property]) {
        serializable[property] = this[property];
      }
    }

    serializeUnknownProperties(this, serializable);

    return serializable;
  },

  /**
   * Returns a value that changes only when one of the properties of a Download
   * object that should be saved into a file also change.  This excludes
   * properties whose value doesn't usually change during the download lifetime.
   *
   * This function is used to determine whether the download should be
   * serialized after a property change notification has been received.
   *
   * @return String representing the relevant download state.
   */
  getSerializationHash() {
    // The "succeeded", "canceled", "error", and startTime properties are not
    // taken into account because they all change before the "stopped" property
    // changes, and are not altered in other cases.
    return (
      this.stopped +
      "," +
      this.totalBytes +
      "," +
      this.hasPartialData +
      "," +
      this.contentType
    );
  },
};

/**
 * Defines which properties of the Download object are serializable.
 */
const kPlainSerializableDownloadProperties = [
  "succeeded",
  "canceled",
  "totalBytes",
  "hasPartialData",
  "hasBlockedData",
  "tryToKeepPartialData",
  "launcherPath",
  "launchWhenSucceeded",
  "contentType",
  "handleInternally",
  "openDownloadsListOnStart",
];

/**
 * Creates a new Download object from a serializable representation.  This
 * function is used by the createDownload method of Downloads.sys.mjs when a new
 * Download object is requested, thus some properties may refer to live objects
 * in place of their serializable representations.
 *
 * @param aSerializable
 *        An object with the following fields:
 *        {
 *          source: DownloadSource object, or its serializable representation.
 *                  See DownloadSource.fromSerializable for details.
 *          target: DownloadTarget object, or its serializable representation.
 *                  See DownloadTarget.fromSerializable for details.
 *          saver: Serializable representation of a DownloadSaver object.  See
 *                 DownloadSaver.fromSerializable for details.  If omitted,
 *                 defaults to "copy".
 *        }
 *
 * @return The newly created Download object.
 */
Download.fromSerializable = function (aSerializable) {
  let download = new Download();
  if (aSerializable.source instanceof DownloadSource) {
    download.source = aSerializable.source;
  } else {
    download.source = DownloadSource.fromSerializable(aSerializable.source);
  }
  if (aSerializable.target instanceof DownloadTarget) {
    download.target = aSerializable.target;
  } else {
    download.target = DownloadTarget.fromSerializable(aSerializable.target);
  }
  if ("saver" in aSerializable) {
    download.saver = DownloadSaver.fromSerializable(aSerializable.saver);
  } else {
    download.saver = DownloadSaver.fromSerializable("copy");
  }
  download.saver.download = download;

  if ("startTime" in aSerializable) {
    let time = aSerializable.startTime.getTime
      ? aSerializable.startTime.getTime()
      : aSerializable.startTime;
    download.startTime = new Date(time);
  }

  // If 'errorObj' is present it will take precedence over the 'error' property.
  // 'error' is a legacy property only containing message, which is insufficient
  // to represent all of the error information.
  //
  // Instead of just replacing 'error' we use a new 'errorObj' so that previous
  // versions will keep it as an unknown property.
  if ("errorObj" in aSerializable) {
    download.error = DownloadError.fromSerializable(aSerializable.errorObj);
  } else if ("error" in aSerializable) {
    download.error = aSerializable.error;
  }

  for (let property of kPlainSerializableDownloadProperties) {
    if (property in aSerializable) {
      download[property] = aSerializable[property];
    }
  }

  deserializeUnknownProperties(
    download,
    aSerializable,
    property =>
      !kPlainSerializableDownloadProperties.includes(property) &&
      property != "startTime" &&
      property != "source" &&
      property != "target" &&
      property != "error" &&
      property != "saver"
  );

  return download;
};

/**
 * Represents the source of a download, for example a document or an URI.
 */
export var DownloadSource = function () {};

DownloadSource.prototype = {
  /**
   * String containing the URI for the download source.
   */
  url: null,

  /**
   * String containing the original URL for the download source.
   */
  originalUrl: null,

  /**
   * Indicates whether the download originated from a private window.  This
   * determines the context of the network request that is made to retrieve the
   * resource.
   */
  isPrivate: false,

  /**
   * Represents the referrerInfo of the download source, could be null for
   * example if the download source is not HTTP.
   */
  referrerInfo: null,

  /**
   * For downloads handled by the (default) DownloadCopySaver, this function
   * can adjust the network channel before it is opened, for example to change
   * the HTTP headers or to upload a stream as POST data.
   *
   * @note If this is defined this object will not be serializable, thus the
   *       Download object will not be persisted across sessions.
   *
   * @param aChannel
   *        The nsIChannel to be adjusted.
   *
   * @return {Promise}
   * @resolves When the channel has been adjusted and can be opened.
   * @rejects JavaScript exception that will cause the download to fail.
   */
  adjustChannel: null,

  /**
   * For downloads handled by the (default) DownloadCopySaver, this function
   * will determine, if provided, if a download can progress or has to be
   * cancelled based on the HTTP status code of the network channel.
   *
   * @note If this is defined this object will not be serializable, thus the
   *       Download object will not be persisted across sessions.
   *
   * @param aDownload
   *        The download asking.
   * @param aStatus
   *        The HTTP status in question
   *
   * @return {Boolean} Download can progress
   */
  allowHttpStatus: null,

  /**
   * Represents the loadingPrincipal of the download source,
   * could be null, in which case the system principal is used instead.
   */
  loadingPrincipal: null,

  /**
   * Represents the cookieJarSettings of the download source, could be null if
   * the download source is not from a document.
   */
  cookieJarSettings: null,

  /**
   * Represents the authentication header of the download source, could be null if
   * the download source had no authentication header.
   */
  authHeader: null,
  /**
   * Returns a static representation of the current object state.
   *
   * @return A JavaScript object that can be serialized to JSON.
   */
  toSerializable() {
    if (this.adjustChannel) {
      // If the callback was used, we can't reproduce this across sessions.
      return null;
    }

    if (this.allowHttpStatus) {
      // If the callback was used, we can't reproduce this across sessions.
      return null;
    }

    let serializable = { url: this.url };
    if (this.isPrivate) {
      serializable.isPrivate = true;
    }

    if (this.referrerInfo && isString(this.referrerInfo)) {
      serializable.referrerInfo = this.referrerInfo;
    } else if (this.referrerInfo) {
      serializable.referrerInfo = lazy.E10SUtils.serializeReferrerInfo(
        this.referrerInfo
      );
    }

    if (this.loadingPrincipal) {
      serializable.loadingPrincipal = isString(this.loadingPrincipal)
        ? this.loadingPrincipal
        : lazy.E10SUtils.serializePrincipal(this.loadingPrincipal);
    }

    if (this.cookieJarSettings) {
      serializable.cookieJarSettings = isString(this.cookieJarSettings)
        ? this.cookieJarSettings
        : lazy.E10SUtils.serializeCookieJarSettings(this.cookieJarSettings);
    }

    serializeUnknownProperties(this, serializable);

    // Simplify the representation if we don't have other details.
    if (Object.keys(serializable).length === 1) {
      // serializable's only key is "url", just return the URL as a string.
      return this.url;
    }
    return serializable;
  },
};

/**
 * Creates a new DownloadSource object from its serializable representation.
 *
 * @param aSerializable
 *        Serializable representation of a DownloadSource object.  This may be a
 *        string containing the URI for the download source, an nsIURI, or an
 *        object with the following properties:
 *        {
 *          url: String containing the URI for the download source.
 *          isPrivate: Indicates whether the download originated from a private
 *                     window.  If omitted, the download is public.
 *          referrerInfo: represents the referrerInfo of the download source.
 *                        Can be omitted or null for example if the download
 *                        source is not HTTP.
 *          cookieJarSettings: represents the cookieJarSettings of the download
 *                             source. Can be omitted or null if the download
 *                             source is not from a document.
 *          adjustChannel: For downloads handled by (default) DownloadCopySaver,
 *                         this function can adjust the network channel before
 *                         it is opened, for example to change the HTTP headers
 *                         or to upload a stream as POST data.  Optional.
 *          allowHttpStatus: For downloads handled by the (default)
 *                           DownloadCopySaver, this function will determine, if
 *                           provided, if a download can progress or has to be
 *                           cancelled based on the HTTP status code of the
 *                           network channel.
 *        }
 *
 * @return The newly created DownloadSource object.
 */
DownloadSource.fromSerializable = function (aSerializable) {
  let source = new DownloadSource();
  if (isString(aSerializable)) {
    // Convert String objects to primitive strings at this point.
    source.url = aSerializable.toString();
  } else if (aSerializable instanceof Ci.nsIURI) {
    source.url = aSerializable.spec;
  } else {
    // Convert String objects to primitive strings at this point.
    source.url = aSerializable.url.toString();
    for (let propName of ["isPrivate", "userContextId", "browsingContextId"]) {
      if (propName in aSerializable) {
        source[propName] = aSerializable[propName];
      }
    }
    if ("originalUrl" in aSerializable) {
      source.originalUrl = aSerializable.originalUrl;
    }
    if ("referrerInfo" in aSerializable) {
      // Quick pass, pass directly nsIReferrerInfo, we don't need to serialize
      // and deserialize
      if (aSerializable.referrerInfo instanceof Ci.nsIReferrerInfo) {
        source.referrerInfo = aSerializable.referrerInfo;
      } else {
        source.referrerInfo = lazy.E10SUtils.deserializeReferrerInfo(
          aSerializable.referrerInfo
        );
      }
    }
    if ("loadingPrincipal" in aSerializable) {
      // Quick pass, pass directly nsIPrincipal, we don't need to serialize
      // and deserialize
      if (aSerializable.loadingPrincipal instanceof Ci.nsIPrincipal) {
        source.loadingPrincipal = aSerializable.loadingPrincipal;
      } else {
        source.loadingPrincipal = lazy.E10SUtils.deserializePrincipal(
          aSerializable.loadingPrincipal
        );
      }
    }
    if ("adjustChannel" in aSerializable) {
      source.adjustChannel = aSerializable.adjustChannel;
    }

    if ("allowHttpStatus" in aSerializable) {
      source.allowHttpStatus = aSerializable.allowHttpStatus;
    }

    if ("cookieJarSettings" in aSerializable) {
      if (aSerializable.cookieJarSettings instanceof Ci.nsICookieJarSettings) {
        source.cookieJarSettings = aSerializable.cookieJarSettings;
      } else {
        source.cookieJarSettings = lazy.E10SUtils.deserializeCookieJarSettings(
          aSerializable.cookieJarSettings
        );
      }
    }

    if ("authHeader" in aSerializable) {
      source.authHeader = aSerializable.authHeader;
    }

    deserializeUnknownProperties(
      source,
      aSerializable,
      property =>
        property != "url" &&
        property != "originalUrl" &&
        property != "isPrivate" &&
        property != "referrerInfo" &&
        property != "cookieJarSettings" &&
        property != "authHeader"
    );
  }

  return source;
};

/**
 * Represents the target of a download, for example a file in the global
 * downloads directory, or a file in the system temporary directory.
 */
export var DownloadTarget = function () {};

DownloadTarget.prototype = {
  /**
   * String containing the path of the target file.
   */
  path: null,

  /**
   * String containing the path of the ".part" file containing the data
   * downloaded so far, or null to disable the use of a ".part" file to keep
   * partially downloaded data.
   */
  partFilePath: null,

  /**
   * Indicates whether the target file exists.
   *
   * This is a dynamic property updated when the download finishes or when the
   * "refresh" method of the Download object is called. It can be used by the
   * front-end to reduce I/O compared to checking the target file directly.
   */
  exists: false,

  /**
   * Indicates whether the part file exists. Like `exists`, this is updated
   * dynamically to reduce I/O compared to checking the target file directly.
   */
  partFileExists: false,

  /**
   * Size in bytes of the target file, or zero if the download has not finished.
   *
   * Even if the target file does not exist anymore, this property may still
   * have a value taken from the download metadata. If the metadata has never
   * been available in this session and the size cannot be obtained from the
   * file because it has already been deleted, this property will be zero.
   *
   * For single-file downloads, this property will always match the actual file
   * size on disk, while the totalBytes property of the Download object, when
   * available, may represent the size of the encoded data instead.
   *
   * For downloads involving multiple files, like complete web pages saved to
   * disk, the meaning of this value is undefined. It currently matches the size
   * of the main file only rather than the sum of all the written data.
   *
   * This is a dynamic property updated when the download finishes or when the
   * "refresh" method of the Download object is called. It can be used by the
   * front-end to reduce I/O compared to checking the target file directly.
   */
  size: 0,

  /**
   * Sets the "exists" and "size" properties based on the actual file on disk.
   *
   * @return {Promise}
   * @resolves When the operation has finished successfully.
   * @rejects JavaScript exception.
   */
  async refresh() {
    try {
      this.size = (await IOUtils.stat(this.path)).size;
      this.exists = true;
    } catch (ex) {
      // Report any error not caused by the file not being there. In any case,
      // the size of the download is not updated and the known value is kept.
      if (ex.name != "NotFoundError") {
        console.error(ex);
      }
      this.exists = false;
    }
    this.refreshPartFileState();
  },

  async refreshPartFileState() {
    if (!this.partFilePath) {
      this.partFileExists = false;
      return;
    }
    try {
      this.partFileExists = (await IOUtils.stat(this.partFilePath)).size > 0;
    } catch (ex) {
      if (ex.name != "NotFoundError") {
        console.error(ex);
      }
      this.partFileExists = false;
    }
  },

  /**
   * Returns a static representation of the current object state.
   *
   * @return A JavaScript object that can be serialized to JSON.
   */
  toSerializable() {
    // Simplify the representation if we don't have other details.
    if (!this.partFilePath && !this._unknownProperties) {
      return this.path;
    }

    let serializable = { path: this.path, partFilePath: this.partFilePath };
    serializeUnknownProperties(this, serializable);
    return serializable;
  },
};

/**
 * Creates a new DownloadTarget object from its serializable representation.
 *
 * @param aSerializable
 *        Serializable representation of a DownloadTarget object.  This may be a
 *        string containing the path of the target file, an nsIFile, or an
 *        object with the following properties:
 *        {
 *          path: String containing the path of the target file.
 *          partFilePath: optional string containing the part file path.
 *        }
 *
 * @return The newly created DownloadTarget object.
 */
DownloadTarget.fromSerializable = function (aSerializable) {
  let target = new DownloadTarget();
  if (isString(aSerializable)) {
    // Convert String objects to primitive strings at this point.
    target.path = aSerializable.toString();
  } else if (aSerializable instanceof Ci.nsIFile) {
    // Read the "path" property of nsIFile after checking the object type.
    target.path = aSerializable.path;
  } else {
    // Read the "path" property of the serializable DownloadTarget
    // representation, converting String objects to primitive strings.
    target.path = aSerializable.path.toString();
    if ("partFilePath" in aSerializable) {
      target.partFilePath = aSerializable.partFilePath;
    }

    deserializeUnknownProperties(
      target,
      aSerializable,
      property => property != "path" && property != "partFilePath"
    );
  }
  return target;
};

/**
 * Provides detailed information about a download failure.
 *
 * @param aProperties
 *        Object which may contain any of the following properties:
 *          {
 *            result: Result error code, defaulting to Cr.NS_ERROR_FAILURE
 *            message: String error message to be displayed in the console, or
 *                     null to use the message associated with the result code.
 *            inferCause: If true, attempts to determine if the cause of the
 *                        download is a network failure or a local file failure,
 *                        based on a set of known values of the result code.
 *                        This is useful when the error is received by a
 *                        component that handles both aspects of the download.
 *            localizedReason: If available, is a localized reason for the error
 *                             that can be directly displayed in the UI.
 *          }
 *        The properties object may also contain any of the DownloadError's
 *        because properties, which will be set accordingly in the error object.
 */
export var DownloadError = function (aProperties) {
  const NS_ERROR_MODULE_BASE_OFFSET = 0x45;
  const NS_ERROR_MODULE_NETWORK = 6;
  const NS_ERROR_MODULE_FILES = 13;

  // Set the error name used by the Error object prototype first.
  this.name = "DownloadError";
  this.result = aProperties.result || Cr.NS_ERROR_FAILURE;
  this.localizedReason = aProperties.localizedReason;
  if (aProperties.message) {
    this.message = aProperties.message;
  } else if (
    aProperties.becauseBlocked ||
    aProperties.becauseBlockedByParentalControls ||
    aProperties.becauseBlockedByReputationCheck
  ) {
    this.message = "Download blocked.";
  } else {
    let exception = new Components.Exception("", this.result);
    this.message = exception.toString();
  }
  if (aProperties.inferCause) {
    let module =
      ((this.result & 0x7fff0000) >> 16) - NS_ERROR_MODULE_BASE_OFFSET;
    this.becauseSourceFailed = module == NS_ERROR_MODULE_NETWORK;
    this.becauseTargetFailed = module == NS_ERROR_MODULE_FILES;
  } else {
    if (aProperties.becauseSourceFailed) {
      this.becauseSourceFailed = true;
    }
    if (aProperties.becauseTargetFailed) {
      this.becauseTargetFailed = true;
    }
  }

  if (aProperties.becauseBlockedByParentalControls) {
    this.becauseBlocked = true;
    this.becauseBlockedByParentalControls = true;
  } else if (aProperties.becauseBlockedByReputationCheck) {
    this.becauseBlocked = true;
    this.becauseBlockedByReputationCheck = true;
    this.reputationCheckVerdict = aProperties.reputationCheckVerdict || "";
  } else if (aProperties.becauseBlocked) {
    this.becauseBlocked = true;
  }

  if (aProperties.innerException) {
    this.innerException = aProperties.innerException;
  }

  this.stack = new Error().stack;
};

/**
 * These constants are used by the reputationCheckVerdict property and indicate
 * the detailed reason why a download is blocked.
 *
 * @note These values should not be changed because they can be serialized.
 */
DownloadError.BLOCK_VERDICT_MALWARE = "Malware";
DownloadError.BLOCK_VERDICT_POTENTIALLY_UNWANTED = "PotentiallyUnwanted";
DownloadError.BLOCK_VERDICT_INSECURE = "Insecure";
DownloadError.BLOCK_VERDICT_UNCOMMON = "Uncommon";
DownloadError.BLOCK_VERDICT_DOWNLOAD_SPAM = "DownloadSpam";

DownloadError.prototype = {
  /**
   * The result code associated with this error.
   */
  result: false,

  /**
   * Indicates an error occurred while reading from the remote location.
   */
  becauseSourceFailed: false,

  /**
   * Indicates an error occurred while writing to the local target.
   */
  becauseTargetFailed: false,

  /**
   * Indicates the download failed because it was blocked.  If the reason for
   * blocking is known, the corresponding property will be also set.
   */
  becauseBlocked: false,

  /**
   * Indicates the download was blocked because downloads are globally
   * disallowed by the Parental Controls or Family Safety features on Windows.
   */
  becauseBlockedByParentalControls: false,

  /**
   * Indicates the download was blocked because it failed the reputation check
   * and may be malware.
   */
  becauseBlockedByReputationCheck: false,

  /**
   * If becauseBlockedByReputationCheck is true, indicates the detailed reason
   * why the download was blocked, according to the "BLOCK_VERDICT_" constants.
   *
   * If the download was not blocked or the reason for the block is unknown,
   * this will be an empty string.
   */
  reputationCheckVerdict: "",

  /**
   * If this DownloadError was caused by an exception this property will
   * contain the original exception. This will not be serialized when saving
   * to the store.
   */
  innerException: null,

  /**
   * Returns a static representation of the current object state.
   *
   * @return A JavaScript object that can be serialized to JSON.
   */
  toSerializable() {
    let serializable = {
      result: this.result,
      localizedReason: this.localizedReason,
      message: this.message,
      becauseSourceFailed: this.becauseSourceFailed,
      becauseTargetFailed: this.becauseTargetFailed,
      becauseBlocked: this.becauseBlocked,
      becauseBlockedByParentalControls: this.becauseBlockedByParentalControls,
      becauseBlockedByReputationCheck: this.becauseBlockedByReputationCheck,
      reputationCheckVerdict: this.reputationCheckVerdict,
    };

    serializeUnknownProperties(this, serializable);
    return serializable;
  },
};
Object.setPrototypeOf(DownloadError.prototype, Error.prototype);

/**
 * Creates a new DownloadError object from its serializable representation.
 *
 * @param aSerializable
 *        Serializable representation of a DownloadError object.
 *
 * @return The newly created DownloadError object.
 */
DownloadError.fromSerializable = function (aSerializable) {
  let e = new DownloadError(aSerializable);
  deserializeUnknownProperties(
    e,
    aSerializable,
    property =>
      property != "result" &&
      property != "message" &&
      property != "becauseSourceFailed" &&
      property != "becauseTargetFailed" &&
      property != "becauseBlocked" &&
      property != "becauseBlockedByParentalControls" &&
      property != "becauseBlockedByReputationCheck" &&
      property != "reputationCheckVerdict"
  );

  return e;
};

/**
 * Template for an object that actually transfers the data for the download.
 */
export var DownloadSaver = function () {};

DownloadSaver.prototype = {
  /**
   * Download object for raising notifications and reading properties.
   *
   * If the tryToKeepPartialData property of the download object is false, the
   * saver should never try to keep partially downloaded data if the download
   * fails.
   */
  download: null,

  /**
   * Executes the download.
   *
   * @param aSetProgressBytesFn
   *        This function may be called by the saver to report progress. It
   *        takes three arguments: the first is the number of bytes transferred
   *        until now, the second is the total number of bytes to be
   *        transferred (or -1 if unknown), the third indicates whether the
   *        partially downloaded data can be used when restarting the download
   *        if it fails or is canceled.
   * @param aSetPropertiesFn
   *        This function may be called by the saver to report information
   *        about new download properties discovered by the saver during the
   *        download process. It takes an object where the keys represents
   *        the names of the properties to set, and the value represents the
   *        value to set.
   *
   * @return {Promise}
   * @resolves When the download has finished successfully.
   * @rejects JavaScript exception if the download failed.
   */
  async execute() {
    throw new Error("Not implemented.");
  },

  /**
   * Cancels the download.
   */
  cancel: function DS_cancel() {
    throw new Error("Not implemented.");
  },

  /**
   * Removes any target file placeholder and any partial data kept as part of a
   * canceled, failed, or temporarily blocked download.
   *
   * This method is never called until the promise returned by "execute" is
   * either resolved or rejected, and the "execute" method is not called again
   * until the promise returned by this method is resolved or rejected.
   *
   * @param canRemoveFinalTarget
   *        True if can remove target file regardless of it being a placeholder.
   * @return {Promise}
   * @resolves When the operation has finished successfully.
   * @rejects Never.
   */
  async removeData() {},

  /**
   * This can be called by the saver implementation when the download is already
   * started, to add it to the browsing history.  This method has no effect if
   * the download is private.
   */
  addToHistory() {
    if (AppConstants.MOZ_PLACES) {
      lazy.DownloadHistory.addDownloadToHistory(this.download).catch(
        console.error
      );
    }
  },

  /**
   * Returns a static representation of the current object state.
   *
   * @return A JavaScript object that can be serialized to JSON.
   */
  toSerializable() {
    throw new Error("Not implemented.");
  },

  /**
   * Returns the SHA-256 hash of the downloaded file, if it exists.
   */
  getSha256Hash() {
    throw new Error("Not implemented.");
  },

  getSignatureInfo() {
    throw new Error("Not implemented.");
  },
}; // DownloadSaver

/**
 * Creates a new DownloadSaver object from its serializable representation.
 *
 * @param aSerializable
 *        Serializable representation of a DownloadSaver object.  If no initial
 *        state information for the saver object is needed, can be a string
 *        representing the class of the download operation, for example "copy".
 *
 * @return The newly created DownloadSaver object.
 */
DownloadSaver.fromSerializable = function (aSerializable) {
  let serializable = isString(aSerializable)
    ? { type: aSerializable }
    : aSerializable;
  let saver;
  switch (serializable.type) {
    case "copy":
      saver = DownloadCopySaver.fromSerializable(serializable);
      break;
    case "legacy":
      saver = DownloadLegacySaver.fromSerializable(serializable);
      break;
    default:
      throw new Error("Unrecoginzed download saver type.");
  }
  return saver;
};

/**
 * Saver object that simply copies the entire source file to the target.
 */
export var DownloadCopySaver = function () {};

DownloadCopySaver.prototype = {
  /**
   * BackgroundFileSaver object currently handling the download.
   */
  _backgroundFileSaver: null,

  /**
   * Indicates whether the "cancel" method has been called.  This is used to
   * prevent the request from starting in case the operation is canceled before
   * the BackgroundFileSaver instance has been created.
   */
  _canceled: false,

  /**
   * Save the SHA-256 hash in raw bytes of the downloaded file. This is null
   * unless BackgroundFileSaver has successfully completed saving the file.
   */
  _sha256Hash: null,

  /**
   * Save the signature info as an Array of Array of raw bytes of nsIX509Cert
   * if the file is signed. This is empty if the file is unsigned, and null
   * unless BackgroundFileSaver has successfully completed saving the file.
   */
  _signatureInfo: null,

  /**
   * Save the redirects chain as an nsIArray of nsIPrincipal.
   */
  _redirects: null,

  /**
   * True if the associated download has already been added to browsing history.
   */
  alreadyAddedToHistory: false,

  /**
   * String corresponding to the entityID property of the nsIResumableChannel
   * used to execute the download, or null if the channel was not resumable or
   * the saver was instructed not to keep partially downloaded data.
   */
  entityID: null,

  /**
   * Implements "DownloadSaver.execute".
   */
  async execute(aSetProgressBytesFn, aSetPropertiesFn) {
    this._canceled = false;

    let download = this.download;
    let targetPath = download.target.path;
    let partFilePath = download.target.partFilePath;
    let keepPartialData = download.tryToKeepPartialData;

    // Add the download to history the first time it is started in this
    // session.  If the download is restarted in a different session, a new
    // history visit will be added.  We do this just to avoid the complexity
    // of serializing this state between sessions, since adding a new visit
    // does not have any noticeable side effect.
    if (!this.alreadyAddedToHistory) {
      this.addToHistory();
      this.alreadyAddedToHistory = true;
    }

    // To reduce the chance that other downloads reuse the same final target
    // file name, we should create a placeholder as soon as possible, before
    // starting the network request.  The placeholder is also required in case
    // we are using a ".part" file instead of the final target while the
    // download is in progress.
    try {
      // If the file already exists, don't delete its contents yet.
      await IOUtils.writeUTF8(targetPath, "", { mode: "appendOrCreate" });
    } catch (ex) {
      if (!DOMException.isInstance(ex)) {
        throw ex;
      }
      // Throw a DownloadError indicating that the operation failed because of
      // the target file.  We cannot translate this into a specific result
      // code, but we preserve the original message.
      let error = new DownloadError({ message: ex.message });
      error.becauseTargetFailed = true;
      throw error;
    }

    let deferSaveComplete = Promise.withResolvers();

    if (this._canceled) {
      // Don't create the BackgroundFileSaver object if we have been
      // canceled meanwhile.
      throw new DownloadError({ message: "Saver canceled." });
    }

    // Create the object that will save the file in a background thread.
    let backgroundFileSaver = new BackgroundFileSaverStreamListener();
    backgroundFileSaver.QueryInterface(Ci.nsIStreamListener);

    try {
      // When the operation completes, reflect the status in the promise
      // returned by this download execution function.
      backgroundFileSaver.observer = {
        onTargetChange() {},
        onSaveComplete: (aSaver, aStatus) => {
          // Send notifications now that we can restart if needed.
          if (Components.isSuccessCode(aStatus)) {
            // Save the hash before freeing backgroundFileSaver.
            this._sha256Hash = aSaver.sha256Hash;
            this._signatureInfo = aSaver.signatureInfo;
            this._redirects = aSaver.redirects;
            deferSaveComplete.resolve();
          } else {
            // Infer the origin of the error from the failure code, because
            // BackgroundFileSaver does not provide more specific data.
            let properties = { result: aStatus, inferCause: true };
            deferSaveComplete.reject(new DownloadError(properties));
          }
          // Free the reference cycle, to release resources earlier.
          backgroundFileSaver.observer = null;
          this._backgroundFileSaver = null;
        },
      };

      // If we have data that we can use to resume the download from where
      // it stopped, try to use it.
      let resumeAttempted = false;
      let resumeFromBytes = 0;

      const notificationCallbacks = {
        QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
        getInterface: ChromeUtils.generateQI(["nsIProgressEventSink"]),
        onProgress: function DCSE_onProgress(
          aRequest,
          aProgress,
          aProgressMax
        ) {
          let currentBytes = resumeFromBytes + aProgress;
          let totalBytes =
            aProgressMax == -1 ? -1 : resumeFromBytes + aProgressMax;
          aSetProgressBytesFn(
            currentBytes,
            totalBytes,
            aProgress > 0 && partFilePath && keepPartialData
          );
        },
        onStatus() {},
      };

      const streamListener = {
        onStartRequest: function (aRequest) {
          backgroundFileSaver.onStartRequest(aRequest);

          if (aRequest instanceof Ci.nsIHttpChannel) {
            // Check if the request's response has been blocked by Windows
            // Parental Controls with an HTTP 450 error code.
            if (aRequest.responseStatus == 450) {
              // Set a flag that can be retrieved later when handling the
              // cancellation so that the proper error can be thrown.
              this.download._blockedByParentalControls = true;
              aRequest.cancel(Cr.NS_BINDING_ABORTED);
              return;
            }

            // Check back with the initiator if we should allow a certain
            // HTTP code. By default, we'll just save error pages too,
            // however a consumer down the line, such as the WebExtensions
            // downloads API might want to handle this differently.
            if (
              download.source.allowHttpStatus &&
              !download.source.allowHttpStatus(
                download,
                aRequest.responseStatus
              )
            ) {
              aRequest.cancel(Cr.NS_BINDING_ABORTED);
              return;
            }
          }

          if (aRequest instanceof Ci.nsIChannel) {
            aSetPropertiesFn({ contentType: aRequest.contentType });

            // Ensure we report the value of "Content-Length", if available,
            // even if the download doesn't generate any progress events
            // later.
            if (aRequest.contentLength >= 0) {
              aSetProgressBytesFn(0, aRequest.contentLength);
            }
          }

          // If the URL we are downloading from includes a file extension
          // that matches the "Content-Encoding" header, for example ".gz"
          // with a "gzip" encoding, we should save the file in its encoded
          // form.  In all other cases, we decode the body while saving.
          if (
            aRequest instanceof Ci.nsIEncodedChannel &&
            aRequest.contentEncodings
          ) {
            let uri = aRequest.URI;
            if (uri instanceof Ci.nsIURL && uri.fileExtension) {
              // Only the first, outermost encoding is considered.
              let encoding = aRequest.contentEncodings.getNext();
              if (encoding) {
                aRequest.applyConversion =
                  lazy.gExternalHelperAppService.applyDecodingForExtension(
                    uri.fileExtension,
                    encoding
                  );
              }
            }
          }

          if (keepPartialData) {
            // If the source is not resumable, don't keep partial data even
            // if we were asked to try and do it.
            if (aRequest instanceof Ci.nsIResumableChannel) {
              try {
                // If reading the ID succeeds, the source is resumable.
                this.entityID = aRequest.entityID;
              } catch (ex) {
                if (
                  !(ex instanceof Components.Exception) ||
                  ex.result != Cr.NS_ERROR_NOT_RESUMABLE
                ) {
                  throw ex;
                }
                keepPartialData = false;
              }
            } else {
              keepPartialData = false;
            }
          }

          // Enable hashing and signature verification before setting the
          // target.
          backgroundFileSaver.enableSha256();
          backgroundFileSaver.enableSignatureInfo();
          if (partFilePath) {
            // If we actually resumed a request, append to the partial data.
            if (resumeAttempted) {
              // TODO: Handle Cr.NS_ERROR_ENTITY_CHANGED
              backgroundFileSaver.enableAppend();
            }

            // Use a part file, determining if we should keep it on failure.
            backgroundFileSaver.setTarget(
              new lazy.FileUtils.File(partFilePath),
              keepPartialData
            );
          } else {
            // Set the final target file, and delete it on failure.
            backgroundFileSaver.setTarget(
              new lazy.FileUtils.File(targetPath),
              false
            );
          }
        }.bind(this),

        onStopRequest(aRequest, aStatusCode) {
          try {
            backgroundFileSaver.onStopRequest(aRequest, aStatusCode);
          } finally {
            // If the data transfer completed successfully, indicate to the
            // background file saver that the operation can finish.  If the
            // data transfer failed, the saver has been already stopped.
            if (Components.isSuccessCode(aStatusCode)) {
              backgroundFileSaver.finish(Cr.NS_OK);
            }
          }
        },

        onDataAvailable: (aRequest, aInputStream, aOffset, aCount) => {
          // Check if the download have been canceled in the mean time,
          // and close the channel and return earlier, BackgroundFileSaver
          // methods shouldn't be called anymore after `finish` was called
          // on download cancellation.
          if (this._canceled) {
            aRequest.cancel(Cr.NS_BINDING_ABORTED);
            return;
          }
          backgroundFileSaver.onDataAvailable(
            aRequest,
            aInputStream,
            aOffset,
            aCount
          );
        },
      };

      // Wrap the channel creation, to prevent the listener code from
      // accidentally using the wrong channel.
      // The channel that is created here is not necessarily the same channel
      // that will eventually perform the actual download.
      // When a HTTP redirect happens, the http backend will create a new
      // channel, this initial channel will be abandoned, and its properties
      // will either return incorrect data, or worse, will throw exceptions
      // upon access.
      const open = async () => {
        // Create a channel from the source, and listen to progress
        // notifications.
        let channel;
        if (download.source.loadingPrincipal) {
          channel = lazy.NetUtil.newChannel({
            uri: download.source.url,
            contentPolicyType: Ci.nsIContentPolicy.TYPE_SAVEAS_DOWNLOAD,
            loadingPrincipal: download.source.loadingPrincipal,
            // triggeringPrincipal must be the system principal to prevent the
            // request from being mistaken as a third-party request.
            triggeringPrincipal:
              Services.scriptSecurityManager.getSystemPrincipal(),
            securityFlags:
              Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
          });
        } else {
          channel = lazy.NetUtil.newChannel({
            uri: download.source.url,
            contentPolicyType: Ci.nsIContentPolicy.TYPE_SAVEAS_DOWNLOAD,
            loadUsingSystemPrincipal: true,
          });
        }
        if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
          channel.setPrivate(download.source.isPrivate);
        }
        if (
          channel instanceof Ci.nsIHttpChannel &&
          download.source.referrerInfo
        ) {
          channel.referrerInfo = download.source.referrerInfo;
          // Stored computed referrerInfo;
          download.source.referrerInfo = channel.referrerInfo;
        }
        if (
          channel instanceof Ci.nsIHttpChannel &&
          download.source.cookieJarSettings
        ) {
          channel.loadInfo.cookieJarSettings =
            download.source.cookieJarSettings;
        }
        if (
          channel instanceof Ci.nsIHttpChannel &&
          download.source.authHeader
        ) {
          try {
            channel.setRequestHeader(
              "Authorization",
              download.source.authHeader,
              true
            );
          } catch (e) {}
        }

        if (download.source.userContextId) {
          // Getters and setters only exist on originAttributes,
          // so it has to be cloned, changed, and re-set
          channel.loadInfo.originAttributes = {
            ...channel.loadInfo.originAttributes,
            userContextId: download.source.userContextId,
          };
        }

        // This makes the channel be corretly throttled during page loads
        // and also prevents its caching.
        if (channel instanceof Ci.nsIHttpChannelInternal) {
          channel.channelIsForDownload = true;

          // Include cookies even if cookieBehavior is BEHAVIOR_REJECT_FOREIGN.
          channel.forceAllowThirdPartyCookie = true;
        }

        if (
          channel instanceof Ci.nsIResumableChannel &&
          this.entityID &&
          partFilePath &&
          keepPartialData
        ) {
          try {
            let stat = await IOUtils.stat(partFilePath);
            channel.resumeAt(stat.size, this.entityID);
            resumeAttempted = true;
            resumeFromBytes = stat.size;
          } catch (ex) {
            if (ex.name != "NotFoundError") {
              throw ex;
            }
          }
        }

        channel.notificationCallbacks = notificationCallbacks;

        // If the callback was set, handle it now before opening the channel.
        if (download.source.adjustChannel) {
          await download.source.adjustChannel(channel);
        }
        channel.asyncOpen(streamListener);
      };

      // Kick off the download, creating and opening the channel.
      await open();

      // We should check if we have been canceled in the meantime, after
      // all the previous asynchronous operations have been executed and
      // just before we set the _backgroundFileSaver property.
      if (this._canceled) {
        throw new DownloadError({ message: "Saver canceled." });
      }

      // If the operation succeeded, store the object to allow cancellation.
      this._backgroundFileSaver = backgroundFileSaver;
    } catch (ex) {
      // In case an error occurs while setting up the chain of objects for
      // the download, ensure that we release the resources of the saver.
      backgroundFileSaver.finish(Cr.NS_ERROR_FAILURE);
      // Since we're not going to handle deferSaveComplete.promise below,
      // we need to make sure that the rejection is handled.
      deferSaveComplete.promise.catch(() => {});
      throw ex;
    }

    // We will wait on this promise in case no error occurred while setting
    // up the chain of objects for the download.
    await deferSaveComplete.promise;

    await this._checkReputationAndMove(aSetPropertiesFn);
  },

  /**
   * Perform the reputation check and cleanup the downloaded data if required.
   * If the download passes the reputation check and is using a part file we
   * will move it to the target path since reputation checking is the final
   * step in the saver.
   *
   * @param aSetPropertiesFn
   *        Function provided to the "execute" method.
   *
   * @return {Promise}
   * @resolves When the reputation check and cleanup is complete.
   * @rejects DownloadError if the download should be blocked.
   */
  async _checkReputationAndMove(aSetPropertiesFn) {
    let download = this.download;
    let targetPath = this.download.target.path;
    let partFilePath = this.download.target.partFilePath;

    let { shouldBlock, verdict } =
      await lazy.DownloadIntegration.shouldBlockForReputationCheck(download);
    if (shouldBlock) {
      Services.telemetry
        .getKeyedHistogramById("DOWNLOADS_USER_ACTION_ON_BLOCKED_DOWNLOAD")
        .add(verdict, 0);

      let newProperties = { progress: 100, hasPartialData: false };

      // We will remove the potentially dangerous file if instructed by
      // DownloadIntegration. We will always remove the file when the
      // download did not use a partial file path, meaning it
      // currently has its final filename.
      if (!lazy.DownloadIntegration.shouldKeepBlockedData() || !partFilePath) {
        await this.removeData(!partFilePath);
      } else {
        newProperties.hasBlockedData = true;
      }

      aSetPropertiesFn(newProperties);

      throw new DownloadError({
        becauseBlockedByReputationCheck: true,
        reputationCheckVerdict: verdict,
      });
    }

    if (partFilePath) {
      try {
        await IOUtils.move(partFilePath, targetPath);
      } catch (e) {
        if (e.name === "NotAllowedError") {
          // In case we cannot write to the target file
          // retry with a new unique name
          let uniquePath = lazy.DownloadPaths.createNiceUniqueFile(
            new lazy.FileUtils.File(targetPath)
          ).path;
          await IOUtils.move(partFilePath, uniquePath);
          this.download.target.path = uniquePath;
        } else {
          throw e;
        }
      }
    }
  },

  /**
   * Implements "DownloadSaver.cancel".
   */
  cancel: function DCS_cancel() {
    this._canceled = true;
    if (this._backgroundFileSaver) {
      this._backgroundFileSaver.finish(Cr.NS_ERROR_FAILURE);
      this._backgroundFileSaver = null;
    }
  },

  /**
   * Implements "DownloadSaver.removeData".
   */
  async removeData(canRemoveFinalTarget = false) {
    // Defined inline so removeData can be shared with DownloadLegacySaver.
    async function _tryToRemoveFile(path) {
      try {
        await IOUtils.remove(path);
      } catch (ex) {
        // On Windows we may get an access denied error instead of a no such
        // file error if the file existed before, and was recently deleted. This
        // is likely to happen when the component that executed the download has
        // just deleted the target file itself.
        if (!["NotFoundError", "NotAllowedError"].includes(ex.name)) {
          console.error(ex);
        }
      }
    }

    if (this.download.target.partFilePath) {
      await _tryToRemoveFile(this.download.target.partFilePath);
    }

    if (this.download.target.path) {
      if (
        canRemoveFinalTarget ||
        (await isPlaceholder(this.download.target.path))
      ) {
        await _tryToRemoveFile(this.download.target.path);
      }
      this.download.target.exists = false;
      this.download.target.size = 0;
    }
  },

  /**
   * Implements "DownloadSaver.toSerializable".
   */
  toSerializable() {
    // Simplify the representation if we don't have other details.
    if (!this.entityID && !this._unknownProperties) {
      return "copy";
    }

    let serializable = { type: "copy", entityID: this.entityID };
    serializeUnknownProperties(this, serializable);
    return serializable;
  },

  /**
   * Implements "DownloadSaver.getSha256Hash"
   */
  getSha256Hash() {
    return this._sha256Hash;
  },

  /*
   * Implements DownloadSaver.getSignatureInfo.
   */
  getSignatureInfo() {
    return this._signatureInfo;
  },

  /*
   * Implements DownloadSaver.getRedirects.
   */
  getRedirects() {
    return this._redirects;
  },
};
Object.setPrototypeOf(DownloadCopySaver.prototype, DownloadSaver.prototype);

/**
 * Creates a new DownloadCopySaver object, with its initial state derived from
 * its serializable representation.
 *
 * @param aSerializable
 *        Serializable representation of a DownloadCopySaver object.
 *
 * @return The newly created DownloadCopySaver object.
 */
DownloadCopySaver.fromSerializable = function (aSerializable) {
  let saver = new DownloadCopySaver();
  if ("entityID" in aSerializable) {
    saver.entityID = aSerializable.entityID;
  }

  deserializeUnknownProperties(
    saver,
    aSerializable,
    property => property != "entityID" && property != "type"
  );

  return saver;
};

/**
 * Saver object that integrates with the legacy nsITransfer interface.
 *
 * For more background on the process, see the DownloadLegacyTransfer object.
 */
export var DownloadLegacySaver = function () {
  this.deferExecuted = Promise.withResolvers();
  this.deferCanceled = Promise.withResolvers();
};

DownloadLegacySaver.prototype = {
  /**
   * Save the SHA-256 hash in raw bytes of the downloaded file. This may be
   * null when nsExternalHelperAppService (and thus BackgroundFileSaver) is not
   * invoked.
   */
  _sha256Hash: null,

  /**
   * Save the signature info as an Array of Array of raw bytes of nsIX509Cert
   * if the file is signed. This is empty if the file is unsigned, and null
   * unless BackgroundFileSaver has successfully completed saving the file.
   */
  _signatureInfo: null,

  /**
   * Save the redirect chain as an nsIArray of nsIPrincipal.
   */
  _redirects: null,

  /**
   * nsIRequest object associated to the status and progress updates we
   * received.  This object is null before we receive the first status and
   * progress update, and is also reset to null when the download is stopped.
   */
  request: null,

  /**
   * This deferred object contains a promise that is resolved as soon as this
   * download finishes successfully, and is rejected in case the download is
   * canceled or receives a failure notification through nsITransfer.
   */
  deferExecuted: null,

  /**
   * This deferred object contains a promise that is resolved if the download
   * receives a cancellation request through the "cancel" method, and is never
   * rejected.  The nsITransfer implementation will register a handler that
   * actually causes the download cancellation.
   */
  deferCanceled: null,

  /**
   * This is populated with the value of the aSetProgressBytesFn argument of the
   * "execute" method, and is null before the method is called.
   */
  setProgressBytesFn: null,

  /**
   * Called by the nsITransfer implementation while the download progresses.
   *
   * @param aCurrentBytes
   *        Number of bytes transferred until now.
   * @param aTotalBytes
   *        Total number of bytes to be transferred, or -1 if unknown.
   */
  onProgressBytes: function DLS_onProgressBytes(aCurrentBytes, aTotalBytes) {
    this.progressWasNotified = true;

    // Ignore progress notifications until we are ready to process them.
    if (!this.setProgressBytesFn) {
      // Keep the data from the last progress notification that was received.
      this.currentBytes = aCurrentBytes;
      this.totalBytes = aTotalBytes;
      return;
    }

    let hasPartFile = !!this.download.target.partFilePath;

    this.setProgressBytesFn(
      aCurrentBytes,
      aTotalBytes,
      aCurrentBytes > 0 && hasPartFile
    );
  },

  /**
   * Whether the onProgressBytes function has been called at least once.
   */
  progressWasNotified: false,

  /**
   * Called by the nsITransfer implementation when the request has started.
   *
   * @param aRequest
   *        nsIRequest associated to the status update.
   */
  onTransferStarted(aRequest) {
    // Store a reference to the request, used in some cases when handling
    // completion, and also checked during the download by unit tests.
    this.request = aRequest;

    // Store the entity ID to use for resuming if required.
    if (
      this.download.tryToKeepPartialData &&
      aRequest instanceof Ci.nsIResumableChannel
    ) {
      try {
        // If reading the ID succeeds, the source is resumable.
        this.entityID = aRequest.entityID;
      } catch (ex) {
        if (
          !(ex instanceof Components.Exception) ||
          ex.result != Cr.NS_ERROR_NOT_RESUMABLE
        ) {
          throw ex;
        }
      }
    }

    // For legacy downloads, we must update the referrerInfo at this time.
    if (aRequest instanceof Ci.nsIHttpChannel) {
      this.download.source.referrerInfo = aRequest.referrerInfo;
    }

    // Don't open the download panel when the user initiated to save a
    // link or document.
    if (
      aRequest instanceof Ci.nsIChannel &&
      aRequest.loadInfo.isUserTriggeredSave
    ) {
      this.download.openDownloadsListOnStart = false;
    }

    this.addToHistory();
  },

  /**
   * Called by the nsITransfer implementation when the request has finished.
   *
   * @param {nsresult} status
   *        Status code received by the nsITransfer implementation.
   * @param {string} [localizedReason]
   *        Optional localized error message associated with a failure
   */
  onTransferFinished(status, localizedReason) {
    if (Components.isSuccessCode(status)) {
      this.deferExecuted.resolve();
    } else {
      // Infer the origin of the error from the failure code, because more
      // specific data is not available through the nsITransfer implementation.
      let properties = {
        result: status,
        inferCause: true,
        localizedReason,
      };
      this.deferExecuted.reject(new DownloadError(properties));
    }
  },

  /**
   * When the first execution of the download finished, it can be restarted by
   * using a DownloadCopySaver object instead of the original legacy component
   * that executed the download.
   */
  firstExecutionFinished: false,

  /**
   * In case the download is restarted after the first execution finished, this
   * property contains a reference to the DownloadCopySaver that is executing
   * the new download attempt.
   */
  copySaver: null,

  /**
   * String corresponding to the entityID property of the nsIResumableChannel
   * used to execute the download, or null if the channel was not resumable or
   * the saver was instructed not to keep partially downloaded data.
   */
  entityID: null,

  /**
   * Implements "DownloadSaver.execute".
   */
  async execute(aSetProgressBytesFn, aSetPropertiesFn) {
    // Check if this is not the first execution of the download.  The Download
    // object guarantees that this function is not re-entered during execution.
    if (this.firstExecutionFinished) {
      if (!this.copySaver) {
        this.copySaver = new DownloadCopySaver();
        this.copySaver.download = this.download;
        this.copySaver.entityID = this.entityID;
        this.copySaver.alreadyAddedToHistory = true;
      }
      await this.copySaver.execute.apply(this.copySaver, arguments);
      return;
    }

    this.setProgressBytesFn = aSetProgressBytesFn;
    if (this.progressWasNotified) {
      this.onProgressBytes(this.currentBytes, this.totalBytes);
    }

    try {
      // Wait for the component that executes the download to finish.
      await this.deferExecuted.promise;

      // At this point, the "request" property has been populated.  Ensure we
      // report the value of "Content-Length", if available, even if the
      // download didn't generate any progress events.
      if (
        !this.progressWasNotified &&
        this.request instanceof Ci.nsIChannel &&
        this.request.contentLength >= 0
      ) {
        aSetProgressBytesFn(0, this.request.contentLength);
      }

      // If the component executing the download provides the path of a
      // ".part" file, it means that it expects the listener to move the file
      // to its final target path when the download succeeds.  In this case,
      // an empty ".part" file is created even if no data was received from
      // the source.
      //
      // When no ".part" file path is provided the download implementation may
      // not have created the target file (if no data was received from the
      // source).  In this case, ensure that an empty file is created as
      // expected.
      if (!this.download.target.partFilePath) {
        try {
          // This atomic operation is more efficient than an existence check.
          await IOUtils.writeUTF8(this.download.target.path, "", {
            mode: "create",
          });
        } catch (ex) {
          if (
            !DOMException.isInstance(ex) ||
            ex.name !== "NoModificationAllowedError"
          ) {
            throw ex;
          }
        }
      }

      await this._checkReputationAndMove(aSetPropertiesFn);
    } catch (ex) {
      // In case the operation failed, ensure we stop downloading data.  Since
      // we never re-enter this function, deferCanceled is always available.
      this.deferCanceled.resolve();
      throw ex;
    } finally {
      // We don't need the reference to the request anymore.  We must also set
      // deferCanceled to null in order to free any indirect references it
      // may hold to the request.
      this.request = null;
      this.deferCanceled = null;
      // Allow the download to restart through a DownloadCopySaver.
      this.firstExecutionFinished = true;
    }
  },

  _checkReputationAndMove() {
    return DownloadCopySaver.prototype._checkReputationAndMove.apply(
      this,
      arguments
    );
  },

  /**
   * Implements "DownloadSaver.cancel".
   */
  cancel: function DLS_cancel() {
    // We may be using a DownloadCopySaver to handle resuming.
    if (this.copySaver) {
      return this.copySaver.cancel.apply(this.copySaver, arguments);
    }

    // If the download hasn't stopped already, resolve deferCanceled so that the
    // operation is canceled as soon as a cancellation handler is registered.
    // Note that the handler might not have been registered yet.
    if (this.deferCanceled) {
      this.deferCanceled.resolve();
    }
  },

  /**
   * Implements "DownloadSaver.removeData".
   */
  removeData(canRemoveFinalTarget) {
    // DownloadCopySaver and DownloadLegacySaver use the same logic for removing
    // partially downloaded data, though this implementation isn't shared by
    // other saver types, thus it isn't found on their shared prototype.
    return DownloadCopySaver.prototype.removeData.call(
      this,
      canRemoveFinalTarget
    );
  },

  /**
   * Implements "DownloadSaver.toSerializable".
   */
  toSerializable() {
    // This object depends on legacy components that are created externally,
    // thus it cannot be rebuilt during deserialization.  To support resuming
    // across different browser sessions, this object is transformed into a
    // DownloadCopySaver for the purpose of serialization.
    return DownloadCopySaver.prototype.toSerializable.call(this);
  },

  /**
   * Implements "DownloadSaver.getSha256Hash".
   */
  getSha256Hash() {
    if (this.copySaver) {
      return this.copySaver.getSha256Hash();
    }
    return this._sha256Hash;
  },

  /**
   * Called by the nsITransfer implementation when the hash is available.
   */
  setSha256Hash(hash) {
    this._sha256Hash = hash;
  },

  /**
   * Implements "DownloadSaver.getSignatureInfo".
   */
  getSignatureInfo() {
    if (this.copySaver) {
      return this.copySaver.getSignatureInfo();
    }
    return this._signatureInfo;
  },

  /**
   * Called by the nsITransfer implementation when the hash is available.
   */
  setSignatureInfo(signatureInfo) {
    this._signatureInfo = signatureInfo;
  },

  /**
   * Implements "DownloadSaver.getRedirects".
   */
  getRedirects() {
    if (this.copySaver) {
      return this.copySaver.getRedirects();
    }
    return this._redirects;
  },

  /**
   * Called by the nsITransfer implementation when the redirect chain is
   * available.
   */
  setRedirects(redirects) {
    this._redirects = redirects;
  },
};
Object.setPrototypeOf(DownloadLegacySaver.prototype, DownloadSaver.prototype);

/**
 * Returns a new DownloadLegacySaver object.  This saver type has a
 * deserializable form only when creating a new object in memory, because it
 * cannot be serialized to disk.
 */
DownloadLegacySaver.fromSerializable = function () {
  return new DownloadLegacySaver();
};
PK
!<�/�D�Dmodules/DownloadList.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Provides collections of Download objects and aggregate views on them.
 */

const FILE_EXTENSIONS = [
  "aac",
  "adt",
  "adts",
  "accdb",
  "accde",
  "accdr",
  "accdt",
  "aif",
  "aifc",
  "aiff",
  "apng",
  "aspx",
  "avi",
  "avif",
  "bat",
  "bin",
  "bmp",
  "cab",
  "cda",
  "csv",
  "dif",
  "dll",
  "doc",
  "docm",
  "docx",
  "dot",
  "dotx",
  "eml",
  "eps",
  "exe",
  "flac",
  "flv",
  "gif",
  "htm",
  "html",
  "ico",
  "ini",
  "iso",
  "jar",
  "jfif",
  "jpg",
  "jpeg",
  "json",
  "m4a",
  "mdb",
  "mid",
  "midi",
  "mov",
  "mp3",
  "mp4",
  "mpeg",
  "mpg",
  "msi",
  "mui",
  "oga",
  "ogg",
  "ogv",
  "opus",
  "pdf",
  "pjpeg",
  "pjp",
  "png",
  "pot",
  "potm",
  "potx",
  "ppam",
  "pps",
  "ppsm",
  "ppsx",
  "ppt",
  "pptm",
  "pptx",
  "psd",
  "pst",
  "pub",
  "rar",
  "rdf",
  "rtf",
  "shtml",
  "sldm",
  "sldx",
  "svg",
  "swf",
  "sys",
  "tif",
  "tiff",
  "tmp",
  "txt",
  "vob",
  "vsd",
  "vsdm",
  "vsdx",
  "vss",
  "vssm",
  "vst",
  "vstm",
  "vstx",
  "wav",
  "wbk",
  "webm",
  "webp",
  "wks",
  "wma",
  "wmd",
  "wmv",
  "wmz",
  "wms",
  "wpd",
  "wp5",
  "xht",
  "xhtml",
  "xla",
  "xlam",
  "xll",
  "xlm",
  "xls",
  "xlsm",
  "xlsx",
  "xlt",
  "xltm",
  "xltx",
  "xml",
  "zip",
];

const TELEMETRY_EVENT_CATEGORY = "downloads";

/**
 * Represents a collection of Download objects that can be viewed and managed by
 * the user interface, and persisted across sessions.
 */
export class DownloadList {
  constructor() {
    /**
     * Array of Download objects currently in the list.
     */
    this._downloads = [];

    /**
     * Set of currently registered views.
     */
    this._views = new Set();
  }

  /**
   * Retrieves a snapshot of the downloads that are currently in the list.  The
   * returned array does not change when downloads are added or removed, though
   * the Download objects it contains are still updated in real time.
   *
   * @return {Promise}
   * @resolves An array of Download objects.
   * @rejects JavaScript exception.
   */
  async getAll() {
    return Array.from(this._downloads);
  }

  /**
   * Adds a new download to the end of the items list.
   *
   * @note When a download is added to the list, its "onchange" event is
   *       registered by the list, thus it cannot be used to monitor the
   *       download.  To receive change notifications for downloads that are
   *       added to the list, use the addView method to register for
   *       onDownloadChanged notifications.
   *
   * @param download
   *        The Download object to add.
   *
   * @return {Promise}
   * @resolves When the download has been added.
   * @rejects JavaScript exception.
   */
  async add(download) {
    this._downloads.push(download);
    download.onchange = this._change.bind(this, download);
    this._notifyAllViews("onDownloadAdded", download);
  }

  /**
   * Removes a download from the list.  If the download was already removed,
   * this method has no effect.
   *
   * This method does not change the state of the download, to allow adding it
   * to another list, or control it directly.  If you want to dispose of the
   * download object, you should cancel it afterwards, and remove any partially
   * downloaded data if needed.
   *
   * @param download
   *        The Download object to remove.
   *
   * @return {Promise}
   * @resolves When the download has been removed.
   * @rejects JavaScript exception.
   */
  async remove(download) {
    let index = this._downloads.indexOf(download);
    if (index != -1) {
      this._downloads.splice(index, 1);
      download.onchange = null;
      this._notifyAllViews("onDownloadRemoved", download);
    }
  }

  /**
   * This function is called when "onchange" events of downloads occur.
   *
   * @param download
   *        The Download object that changed.
   */
  _change(download) {
    this._notifyAllViews("onDownloadChanged", download);
  }

  /**
   * Adds a view that will be notified of changes to downloads.  The newly added
   * view will receive onDownloadAdded notifications for all the downloads that
   * are already in the list.
   *
   * @param view
   *        The view object to add.  The following methods may be defined:
   *        {
   *          onDownloadAdded: function (download) {
   *            // Called after download is added to the end of the list.
   *          },
   *          onDownloadChanged: function (download) {
   *            // Called after the properties of download change.
   *          },
   *          onDownloadRemoved: function (download) {
   *            // Called after download is removed from the list.
   *          },
   *          onDownloadBatchStarting: function () {
   *            // Called before multiple changes are made at the same time.
   *          },
   *          onDownloadBatchEnded: function () {
   *            // Called after all the changes have been made.
   *          },
   *        }
   *
   * @return {Promise}
   * @resolves When the view has been registered and all the onDownloadAdded
   *           notifications for the existing downloads have been sent.
   * @rejects JavaScript exception.
   */
  async addView(view) {
    this._views.add(view);

    if ("onDownloadAdded" in view) {
      this._notifyAllViews("onDownloadBatchStarting");
      for (let download of this._downloads) {
        try {
          view.onDownloadAdded(download);
        } catch (ex) {
          console.error(ex);
        }
      }
      this._notifyAllViews("onDownloadBatchEnded");
    }
  }

  /**
   * Removes a view that was previously added using addView.
   *
   * @param view
   *        The view object to remove.
   *
   * @return {Promise}
   * @resolves When the view has been removed.  At this point, the removed view
   *           will not receive any more notifications.
   * @rejects JavaScript exception.
   */
  async removeView(view) {
    this._views.delete(view);
  }

  /**
   * Notifies all the views of a download addition, change, removal, or other
   * event. The additional arguments are passed to the called method.
   *
   * @param methodName
   *        String containing the name of the method to call on the view.
   */
  _notifyAllViews(methodName, ...args) {
    for (let view of this._views) {
      try {
        if (methodName in view) {
          view[methodName](...args);
        }
      } catch (ex) {
        console.error(ex);
      }
    }
  }

  /**
   * Removes downloads from the list that have finished, have failed, or have
   * been canceled without keeping partial data.  A filter function may be
   * specified to remove only a subset of those downloads.
   *
   * This method finalizes each removed download, ensuring that any partially
   * downloaded data associated with it is also removed.
   *
   * @param filterFn
   *        The filter function is called with each download as its only
   *        argument, and should return true to remove the download and false
   *        to keep it.  This parameter may be null or omitted to have no
   *        additional filter.
   */
  removeFinished(filterFn) {
    (async () => {
      let list = await this.getAll();
      for (let download of list) {
        // Remove downloads that have been canceled, even if the cancellation
        // operation hasn't completed yet so we don't check "stopped" here.
        // Failed downloads with partial data are also removed.
        if (
          download.stopped &&
          (!download.hasPartialData || download.error) &&
          (!filterFn || filterFn(download))
        ) {
          // Remove the download first, so that the views don't get the change
          // notifications that may occur during finalization.
          await this.remove(download);
          // Find if a file with the same path is also downloading.
          let sameFileIsDownloading = false;
          for (let otherDownload of await this.getAll()) {
            if (
              download !== otherDownload &&
              download.target.path == otherDownload.target.path &&
              !otherDownload.error
            ) {
              sameFileIsDownloading = true;
            }
          }
          // Ensure that the download is stopped and no partial data is kept.
          // This works even if the download state has changed meanwhile.  We
          // don't need to wait for the procedure to be complete before
          // processing the other downloads in the list.
          let removePartialData = !sameFileIsDownloading;
          download.finalize(removePartialData).catch(console.error);
        }
      }
    })().catch(console.error);
  }
}

/**
 * Provides a unified, unordered list combining public and private downloads.
 *
 * Download objects added to this list are also added to one of the two
 * underlying lists, based on their "source.isPrivate" property.  Views on this
 * list will receive notifications for both public and private downloads.
 *
 * @param publicList
 *        Underlying DownloadList containing public downloads.
 * @param privateList
 *        Underlying DownloadList containing private downloads.
 */
export class DownloadCombinedList extends DownloadList {
  constructor(publicList, privateList) {
    super();

    /**
     * Underlying DownloadList containing public downloads.
     */
    this._publicList = publicList;

    /**
     * Underlying DownloadList containing private downloads.
     */
    this._privateList = privateList;

    publicList.addView(this).catch(console.error);
    privateList.addView(this).catch(console.error);
  }

  /**
   * Adds a new download to the end of the items list.
   *
   * @note When a download is added to the list, its "onchange" event is
   *       registered by the list, thus it cannot be used to monitor the
   *       download.  To receive change notifications for downloads that are
   *       added to the list, use the addView method to register for
   *       onDownloadChanged notifications.
   *
   * @param download
   *        The Download object to add.
   *
   * @return {Promise}
   * @resolves When the download has been added.
   * @rejects JavaScript exception.
   */
  add(download) {
    let extension = download.target.path.split(".").pop();

    if (!FILE_EXTENSIONS.includes(extension)) {
      extension = "other";
    }

    try {
      Services.telemetry.recordEvent(
        TELEMETRY_EVENT_CATEGORY,
        "added",
        "fileExtension",
        extension,
        {}
      );
    } catch (ex) {
      console.error(
        "DownloadsCommon: error recording telemetry event.",
        ex.message
      );
    }

    if (download.source.isPrivate) {
      return this._privateList.add(download);
    }

    return this._publicList.add(download);
  }

  /**
   * Removes a download from the list.  If the download was already removed,
   * this method has no effect.
   *
   * This method does not change the state of the download, to allow adding it
   * to another list, or control it directly.  If you want to dispose of the
   * download object, you should cancel it afterwards, and remove any partially
   * downloaded data if needed.
   *
   * @param download
   *        The Download object to remove.
   *
   * @return {Promise}
   * @resolves When the download has been removed.
   * @rejects JavaScript exception.
   */
  remove(download) {
    if (download.source.isPrivate) {
      return this._privateList.remove(download);
    }
    return this._publicList.remove(download);
  }

  // DownloadList callback
  onDownloadAdded(download) {
    this._downloads.push(download);
    this._notifyAllViews("onDownloadAdded", download);
  }

  // DownloadList callback
  onDownloadChanged(download) {
    this._notifyAllViews("onDownloadChanged", download);
  }

  // DownloadList callback
  onDownloadRemoved(download) {
    let index = this._downloads.indexOf(download);
    if (index != -1) {
      this._downloads.splice(index, 1);
    }
    this._notifyAllViews("onDownloadRemoved", download);
  }
}
/**
 * Provides an aggregated view on the contents of a DownloadList.
 */
export class DownloadSummary {
  constructor() {
    /**
     * Array of Download objects that are currently part of the summary.
     */
    this._downloads = [];

    /**
     * Set of currently registered views.
     */
    this._views = new Set();
  }

  /**
   * Underlying DownloadList whose contents should be summarized.
   */
  _list = null;

  /**
   * Indicates whether all the downloads are currently stopped.
   */
  allHaveStopped = true;

  /**
   * Indicates whether whether all downloads have an unknown final size.
   */
  allUnknownSize = true;

  /**
   * Indicates the total number of bytes to be transferred before completing all
   * the downloads that are currently in progress.
   *
   * For downloads that do not have a known final size, the number of bytes
   * currently transferred is reported as part of this property.
   *
   * This is zero if no downloads are currently in progress.
   */
  progressTotalBytes = 0;

  /**
   * Number of bytes currently transferred as part of all the downloads that are
   * currently in progress.
   *
   * This is zero if no downloads are currently in progress.
   */
  progressCurrentBytes = 0;

  /**
   * This method may be called once to bind this object to a DownloadList.
   *
   * Views on the summarized data can be registered before this object is bound
   * to an actual list.  This allows the summary to be used without requiring
   * the initialization of the DownloadList first.
   *
   * @param list
   *        Underlying DownloadList whose contents should be summarized.
   *
   * @return {Promise}
   * @resolves When the view on the underlying list has been registered.
   * @rejects JavaScript exception.
   */
  async bindToList(list) {
    if (this._list) {
      throw new Error("bindToList may be called only once.");
    }

    await list.addView(this);
    // Set the list reference only after addView has returned, so that we don't
    // send a notification to our views for each download that is added.
    this._list = list;
    this._onListChanged();
  }

  /**
   * Adds a view that will be notified of changes to the summary.  The newly
   * added view will receive an initial onSummaryChanged notification.
   *
   * @param view
   *        The view object to add.  The following methods may be defined:
   *        {
   *          onSummaryChanged: function () {
   *            // Called after any property of the summary has changed.
   *          },
   *        }
   *
   * @return {Promise}
   * @resolves When the view has been registered and the onSummaryChanged
   *           notification has been sent.
   * @rejects JavaScript exception.
   */
  async addView(view) {
    this._views.add(view);

    if ("onSummaryChanged" in view) {
      try {
        view.onSummaryChanged();
      } catch (ex) {
        console.error(ex);
      }
    }
  }

  /**
   * Removes a view that was previously added using addView.
   *
   * @param view
   *        The view object to remove.
   *
   * @return {Promise}
   * @resolves When the view has been removed.  At this point, the removed view
   *           will not receive any more notifications.
   * @rejects JavaScript exception.
   */
  async removeView(view) {
    this._views.delete(view);
  }

  /**
   * This function is called when any change in the list of downloads occurs,
   * and will recalculate the summary and notify the views in case the
   * aggregated properties are different.
   */
  _onListChanged() {
    let allHaveStopped = true;
    let allUnknownSize = true;
    let progressTotalBytes = 0;
    let progressCurrentBytes = 0;

    // Recalculate the aggregated state.  See the description of the individual
    // properties for an explanation of the summarization logic.
    for (let download of this._downloads) {
      if (!download.stopped) {
        allHaveStopped = false;
        if (download.hasProgress) {
          allUnknownSize = false;
          progressTotalBytes += download.totalBytes;
        } else {
          progressTotalBytes += download.currentBytes;
        }
        progressCurrentBytes += download.currentBytes;
      }
    }

    // Exit now if the properties did not change.
    if (
      this.allHaveStopped == allHaveStopped &&
      this.allUnknownSize == allUnknownSize &&
      this.progressTotalBytes == progressTotalBytes &&
      this.progressCurrentBytes == progressCurrentBytes
    ) {
      return;
    }

    this.allHaveStopped = allHaveStopped;
    this.allUnknownSize = allUnknownSize;
    this.progressTotalBytes = progressTotalBytes;
    this.progressCurrentBytes = progressCurrentBytes;

    // Notify all the views that our properties changed.
    for (let view of this._views) {
      try {
        if ("onSummaryChanged" in view) {
          view.onSummaryChanged();
        }
      } catch (ex) {
        console.error(ex);
      }
    }
  }

  // DownloadList callback
  onDownloadAdded(download) {
    this._downloads.push(download);
    if (this._list) {
      this._onListChanged();
    }
  }

  // DownloadList callback
  onDownloadChanged() {
    this._onListChanged();
  }

  // DownloadList callback
  onDownloadRemoved(download) {
    let index = this._downloads.indexOf(download);
    if (index != -1) {
      this._downloads.splice(index, 1);
    }
    this._onListChanged();
  }
}
PK
!<^�:<��#modules/DownloadIntegration.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Provides functions to integrate with the host application, handling for
 * example the global prompts on shutdown.
 */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

import { Downloads } from "resource://gre/modules/Downloads.sys.mjs";
import { Integration } from "resource://gre/modules/Integration.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  DownloadSpamProtection: "resource:///modules/DownloadSpamProtection.sys.mjs",
  DownloadStore: "resource://gre/modules/DownloadStore.sys.mjs",
  DownloadUIHelper: "resource://gre/modules/DownloadUIHelper.sys.mjs",
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gDownloadPlatform",
  "@mozilla.org/toolkit/download-platform;1",
  "mozIDownloadPlatform"
);
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gMIMEService",
  "@mozilla.org/mime;1",
  "nsIMIMEService"
);
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gExternalProtocolService",
  "@mozilla.org/uriloader/external-protocol-service;1",
  "nsIExternalProtocolService"
);

ChromeUtils.defineLazyGetter(lazy, "gParentalControlsService", function () {
  if ("@mozilla.org/parental-controls-service;1" in Cc) {
    return Cc["@mozilla.org/parental-controls-service;1"].createInstance(
      Ci.nsIParentalControlsService
    );
  }
  return null;
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gApplicationReputationService",
  "@mozilla.org/reputationservice/application-reputation-service;1",
  Ci.nsIApplicationReputationService
);

Integration.downloads.defineESModuleGetter(
  lazy,
  "DownloadIntegration",
  "resource://gre/modules/DownloadIntegration.sys.mjs"
);
ChromeUtils.defineLazyGetter(lazy, "gCombinedDownloadIntegration", () => {
  return lazy.DownloadIntegration;
});

ChromeUtils.defineLazyGetter(lazy, "stringBundle", () =>
  Services.strings.createBundle(
    "chrome://mozapps/locale/downloads/downloads.properties"
  )
);

const Timer = Components.Constructor(
  "@mozilla.org/timer;1",
  "nsITimer",
  "initWithCallback"
);

/**
 * Indicates the delay between a change to the downloads data and the related
 * save operation.
 *
 * For best efficiency, this value should be high enough that the input/output
 * for opening or closing the target file does not overlap with the one for
 * saving the list of downloads.
 */
const kSaveDelayMs = 1500;

/**
 * List of observers to listen against
 */
const kObserverTopics = [
  "quit-application-requested",
  "offline-requested",
  "last-pb-context-exiting",
  "last-pb-context-exited",
  "sleep_notification",
  "suspend_process_notification",
  "wake_notification",
  "resume_process_notification",
  "network:offline-about-to-go-offline",
  "network:offline-status-changed",
  "xpcom-will-shutdown",
  "blocked-automatic-download",
];

/**
 * Maps nsIApplicationReputationService verdicts with the DownloadError ones.
 */
const kVerdictMap = {
  [Ci.nsIApplicationReputationService.VERDICT_DANGEROUS]:
    Downloads.Error.BLOCK_VERDICT_MALWARE,
  [Ci.nsIApplicationReputationService.VERDICT_UNCOMMON]:
    Downloads.Error.BLOCK_VERDICT_UNCOMMON,
  [Ci.nsIApplicationReputationService.VERDICT_POTENTIALLY_UNWANTED]:
    Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED,
  [Ci.nsIApplicationReputationService.VERDICT_DANGEROUS_HOST]:
    Downloads.Error.BLOCK_VERDICT_MALWARE,
};

/**
 * Provides functions to integrate with the host application, handling for
 * example the global prompts on shutdown.
 */
export var DownloadIntegration = {
  /**
   * Main DownloadStore object for loading and saving the list of persistent
   * downloads, or null if the download list was never requested and thus it
   * doesn't need to be persisted.
   */
  _store: null,

  /**
   * Returns whether data for blocked downloads should be kept on disk.
   * Implementations which support unblocking downloads may return true to
   * keep the blocked download on disk until its fate is decided.
   *
   * If a download is blocked and the partial data is kept the Download's
   * 'hasBlockedData' property will be true. In this state Download.unblock()
   * or Download.confirmBlock() may be used to either unblock the download or
   * remove the downloaded data respectively.
   *
   * Even if shouldKeepBlockedData returns true, if the download did not use a
   * partFile the blocked data will be removed - preventing the complete
   * download from existing on disk with its final filename.
   *
   * @return boolean True if data should be kept.
   */
  shouldKeepBlockedData() {
    const FIREFOX_ID = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
    return Services.appinfo.ID == FIREFOX_ID;
  },

  /**
   * Performs initialization of the list of persistent downloads, before its
   * first use by the host application.  This function may be called only once
   * during the entire lifetime of the application.
   *
   * @param list
   *        DownloadList object to be initialized.
   *
   * @return {Promise}
   * @resolves When the list has been initialized.
   * @rejects JavaScript exception.
   */
  async initializePublicDownloadList(list) {
    try {
      await this.loadPublicDownloadListFromStore(list);
    } catch (ex) {
      console.error(ex);
    }

    if (AppConstants.MOZ_PLACES) {
      // After the list of persistent downloads has been loaded, we can add the
      // history observers, even if the load operation failed. This object is kept
      // alive by the history service.
      new DownloadHistoryObserver(list);
    }
  },

  /**
   * Called by initializePublicDownloadList to load the list of persistent
   * downloads, before its first use by the host application.  This function may
   * be called only once during the entire lifetime of the application.
   *
   * @param list
   *        DownloadList object to be populated with the download objects
   *        serialized from the previous session.  This list will be persisted
   *        to disk during the session lifetime.
   *
   * @return {Promise}
   * @resolves When the list has been populated.
   * @rejects JavaScript exception.
   */
  async loadPublicDownloadListFromStore(list) {
    if (this._store) {
      throw new Error("Initialization may be performed only once.");
    }

    this._store = new lazy.DownloadStore(
      list,
      PathUtils.join(PathUtils.profileDir, "downloads.json")
    );
    this._store.onsaveitem = this.shouldPersistDownload.bind(this);

    try {
      await this._store.load();
    } catch (ex) {
      console.error(ex);
    }

    // Add the view used for detecting changes to downloads to be persisted.
    // We must do this after the list of persistent downloads has been loaded,
    // even if the load operation failed. We wait for a complete initialization
    // so other callers cannot modify the list without being detected. The
    // DownloadAutoSaveView is kept alive by the underlying DownloadList.
    await new DownloadAutoSaveView(list, this._store).initialize();
  },

  /**
   * Determines if a Download object from the list of persistent downloads
   * should be saved into a file, so that it can be restored across sessions.
   *
   * This function allows filtering out downloads that the host application is
   * not interested in persisting across sessions, for example downloads that
   * finished successfully.
   *
   * @param aDownload
   *        The Download object to be inspected.  This is originally taken from
   *        the global DownloadList object for downloads that were not started
   *        from a private browsing window.  The item may have been removed
   *        from the list since the save operation started, though in this case
   *        the save operation will be repeated later.
   *
   * @return True to save the download, false otherwise.
   */
  shouldPersistDownload(aDownload) {
    // On all platforms, we save all the downloads currently in progress, as
    // well as stopped downloads for which we retained partially downloaded
    // data or we have blocked data.
    // On Android we store all history; on Desktop, stopped downloads for which
    // we don't need to track the presence of a ".part" file are only retained
    // in the browser history.
    return (
      !aDownload.stopped ||
      aDownload.hasPartialData ||
      aDownload.hasBlockedData ||
      AppConstants.platform == "android"
    );
  },

  /**
   * Returns the system downloads directory asynchronously.
   *
   * @return {Promise}
   * @resolves The downloads directory string path.
   */
  async getSystemDownloadsDirectory() {
    if (this._downloadsDirectory) {
      return this._downloadsDirectory;
    }

    if (AppConstants.platform == "android") {
      // Android doesn't have a $HOME directory, and by default we only have
      // write access to /data/data/org.mozilla.{$APP} and /sdcard
      this._downloadsDirectory = Services.env.get("DOWNLOADS_DIRECTORY");
      if (!this._downloadsDirectory) {
        throw new Components.Exception(
          "DOWNLOADS_DIRECTORY is not set.",
          Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH
        );
      }
    } else {
      try {
        this._downloadsDirectory = this._getDirectory("DfltDwnld");
      } catch (e) {
        this._downloadsDirectory = await this._createDownloadsDirectory("Home");
      }
    }

    return this._downloadsDirectory;
  },
  _downloadsDirectory: null,

  /**
   * Returns the user downloads directory asynchronously.
   *
   * On platforms where external helper apps use the downloads directory, the
   * behavior should match that of the synchronous function of the same name
   * exposed by nsIExternalHelperAppService.
   *
   * @return {Promise}
   * @resolves The downloads directory string path.
   */
  async getPreferredDownloadsDirectory() {
    let directoryPath = null;
    let prefValue = Services.prefs.getIntPref("browser.download.folderList", 1);

    switch (prefValue) {
      case 0: // Desktop
        directoryPath = this._getDirectory("Desk");
        break;
      case 1: // Downloads
        directoryPath = await this.getSystemDownloadsDirectory();
        break;
      case 2: // Custom
        try {
          let directory = Services.prefs.getComplexValue(
            "browser.download.dir",
            Ci.nsIFile
          );
          directoryPath = directory.path;
          await IOUtils.makeDirectory(directoryPath, {
            createAncestors: false,
          });
        } catch (ex) {
          console.error(ex);
          // Either the preference isn't set or the directory cannot be created.
          directoryPath = await this.getSystemDownloadsDirectory();
        }
        break;
      default:
        directoryPath = await this.getSystemDownloadsDirectory();
    }
    return directoryPath;
  },

  /**
   * Returns the temporary downloads directory asynchronously.
   *
   * @return {Promise}
   * @resolves The downloads directory string path.
   */
  async getTemporaryDownloadsDirectory() {
    let directoryPath = null;
    if (AppConstants.platform == "macosx") {
      directoryPath = await this.getPreferredDownloadsDirectory();
    } else if (AppConstants.platform == "android") {
      directoryPath = await this.getSystemDownloadsDirectory();
    } else {
      directoryPath = this._getDirectory("TmpD");
    }
    return directoryPath;
  },

  /**
   * Checks to determine whether to block downloads for parental controls.
   *
   * aParam aDownload
   *        The download object.
   *
   * @return {Promise}
   * @resolves The boolean indicates to block downloads or not.
   */
  shouldBlockForParentalControls(aDownload) {
    let isEnabled =
      lazy.gParentalControlsService &&
      lazy.gParentalControlsService.parentalControlsEnabled;
    let shouldBlock =
      isEnabled && lazy.gParentalControlsService.blockFileDownloadsEnabled;

    // Log the event if required by parental controls settings.
    if (isEnabled && lazy.gParentalControlsService.loggingEnabled) {
      lazy.gParentalControlsService.log(
        lazy.gParentalControlsService.ePCLog_FileDownload,
        shouldBlock,
        lazy.NetUtil.newURI(aDownload.source.url),
        null
      );
    }

    return Promise.resolve(shouldBlock);
  },

  /**
   * Checks to determine whether to block downloads because they might be
   * malware, based on application reputation checks.
   *
   * aParam aDownload
   *        The download object.
   *
   * @return {Promise}
   * @resolves Object with the following properties:
   *           {
   *             shouldBlock: Whether the download should be blocked.
   *             verdict: Detailed reason for the block, according to the
   *                      "Downloads.Error.BLOCK_VERDICT_" constants, or empty
   *                      string if the reason is unknown.
   *           }
   */
  shouldBlockForReputationCheck(aDownload) {
    let hash;
    let sigInfo;
    let channelRedirects;
    try {
      hash = aDownload.saver.getSha256Hash();
      sigInfo = aDownload.saver.getSignatureInfo();
      channelRedirects = aDownload.saver.getRedirects();
    } catch (ex) {
      // Bail if DownloadSaver doesn't have a hash or signature info.
      return Promise.resolve({
        shouldBlock: false,
        verdict: "",
      });
    }
    if (!hash || !sigInfo) {
      return Promise.resolve({
        shouldBlock: false,
        verdict: "",
      });
    }
    return new Promise(resolve => {
      lazy.gApplicationReputationService.queryReputation(
        {
          sourceURI: lazy.NetUtil.newURI(aDownload.source.url),
          referrerInfo: aDownload.source.referrerInfo,
          fileSize: aDownload.currentBytes,
          sha256Hash: hash,
          suggestedFileName: PathUtils.filename(aDownload.target.path),
          signatureInfo: sigInfo,
          redirects: channelRedirects,
        },
        function onComplete(aShouldBlock, aRv, aVerdict) {
          resolve({
            shouldBlock: aShouldBlock,
            verdict: (aShouldBlock && kVerdictMap[aVerdict]) || "",
          });
        }
      );
    });
  },

  /**
   * Checks whether downloaded files should be marked as coming from
   * Internet Zone.
   *
   * @return true if files should be marked
   */
  _shouldSaveZoneInformation() {
    let key = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
      Ci.nsIWindowsRegKey
    );
    try {
      key.open(
        Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
        "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\Attachments",
        Ci.nsIWindowsRegKey.ACCESS_QUERY_VALUE
      );
      try {
        return key.readIntValue("SaveZoneInformation") != 1;
      } finally {
        key.close();
      }
    } catch (ex) {
      // If the key is not present, files should be marked by default.
      return true;
    }
  },

  /**
   * Builds a key and URL value pair for the "Zone.Identifier" Alternate Data
   * Stream.
   *
   * @param aKey
   *        String to write before the "=" sign. This is not validated.
   * @param aUrl
   *        URL string to write after the "=" sign. Only the "http(s)" and
   *        "ftp" schemes are allowed, and usernames and passwords are
   *        stripped.
   * @param [optional] aFallback
   *        Value to place after the "=" sign in case the URL scheme is not
   *        allowed. If unspecified, an empty string is returned when the
   *        scheme is not allowed.
   *
   * @return Line to add to the stream, including the final CRLF, or an empty
   *         string if the validation failed.
   */
  _zoneIdKey(aKey, aUrl, aFallback) {
    try {
      let url;
      const uri = lazy.NetUtil.newURI(aUrl);
      if (["http", "https", "ftp"].includes(uri.scheme)) {
        url = uri.mutate().setUserPass("").finalize().spec;
      } else if (aFallback) {
        url = aFallback;
      } else {
        return "";
      }
      return aKey + "=" + url + "\r\n";
    } catch (e) {
      return "";
    }
  },

  /**
   * Performs platform-specific operations when a download is done.
   *
   * aParam aDownload
   *        The Download object.
   *
   * @return {Promise}
   * @resolves When all the operations completed successfully.
   * @rejects JavaScript exception if any of the operations failed.
   */
  async downloadDone(aDownload) {
    // On Windows, we mark any file saved to the NTFS file system as coming
    // from the Internet security zone unless Group Policy disables the
    // feature.  We do this by writing to the "Zone.Identifier" Alternate
    // Data Stream directly, because the Save method of the
    // IAttachmentExecute interface would trigger operations that may cause
    // the application to hang, or other performance issues.
    // The stream created in this way is forward-compatible with all the
    // current and future versions of Windows.
    if (AppConstants.platform == "win" && this._shouldSaveZoneInformation()) {
      let zone;
      try {
        zone = lazy.gDownloadPlatform.mapUrlToZone(aDownload.source.url);
      } catch (e) {
        // Default to Internet Zone if mapUrlToZone failed for
        // whatever reason.
        zone = Ci.mozIDownloadPlatform.ZONE_INTERNET;
      }
      // Don't write zone IDs for Local, Intranet, or Trusted sites
      // to match Windows behavior.
      if (zone >= Ci.mozIDownloadPlatform.ZONE_INTERNET) {
        let path = aDownload.target.path + ":Zone.Identifier";
        try {
          let zoneId = "[ZoneTransfer]\r\nZoneId=" + zone + "\r\n";
          let { url, isPrivate, referrerInfo } = aDownload.source;
          if (!isPrivate) {
            let referrer = referrerInfo
              ? referrerInfo.computedReferrerSpec
              : "";
            zoneId +=
              this._zoneIdKey("ReferrerUrl", referrer) +
              this._zoneIdKey("HostUrl", url, "about:internet");
          }
          await IOUtils.writeUTF8(
            PathUtils.toExtendedWindowsPath(path),
            zoneId
          );
        } catch (ex) {
          // If writing to the file fails, we ignore the error and continue.
          if (!DOMException.isInstance(ex)) {
            console.error(ex);
          }
        }
      }
    }

    // The file with the partially downloaded data has restrictive permissions
    // that don't allow other users on the system to access it.  Now that the
    // download is completed, we need to adjust permissions based on whether
    // this is a permanently downloaded file or a temporary download to be
    // opened read-only with an external application.
    try {
      let isTemporaryDownload =
        aDownload.launchWhenSucceeded && aDownload.source.isPrivate;
      // Permanently downloaded files are made accessible by other users on
      // this system, while temporary downloads are marked as read-only.
      let unixMode;
      if (isTemporaryDownload) {
        unixMode = 0o400;
      } else {
        unixMode = 0o666;
      }
      // On Unix, the umask of the process is respected.
      await IOUtils.setPermissions(aDownload.target.path, unixMode);
    } catch (ex) {
      // We should report errors with making the permissions less restrictive
      // or marking the file as read-only on Unix and Mac, but this should not
      // prevent the download from completing.
      if (!DOMException.isInstance(ex)) {
        console.error(ex);
      }
    }

    let aReferrer = null;
    if (aDownload.source.referrerInfo) {
      aReferrer = aDownload.source.referrerInfo.originalReferrer;
    }

    await lazy.gDownloadPlatform.downloadDone(
      lazy.NetUtil.newURI(aDownload.source.url),
      aReferrer,
      new lazy.FileUtils.File(aDownload.target.path),
      aDownload.contentType,
      aDownload.source.isPrivate
    );
  },

  /**
   * Decide whether a download of this type, opened from the downloads
   * list, should open internally.
   *
   * @param aMimeType
   *        The MIME type of the file, as a string
   * @param [optional] aExtension
   *        The file extension, which can match instead of the MIME type.
   */
  shouldViewDownloadInternally() {
    // Refuse all files by default, this is meant to be replaced with a check
    // for specific types via Integration.downloads.register().
    return false;
  },

  /**
   * Launches a file represented by the target of a download. This can
   * open the file with the default application for the target MIME type
   * or file extension, or with a custom application if
   * aDownload.launcherPath is set.
   *
   * @param    aDownload
   *           A Download object that contains the necessary information
   *           to launch the file. The relevant properties are: the target
   *           file, the contentType and the custom application chosen
   *           to launch it.
   * @param options.openWhere     Optional string indicating how to open when handling
   *                              download by opening the target file URI.
   *                              One of "window", "tab", "tabshifted"
   * @param options.useSystemDefault
   *                              Optional value indicating how to handle launching this download,
   *                              this time only. Will override the associated mimeInfo.preferredAction
   *
   * @return {Promise}
   * @resolves When the instruction to launch the file has been
   *           successfully given to the operating system. Note that
   *           the OS might still take a while until the file is actually
   *           launched.
   * @rejects  JavaScript exception if there was an error trying to launch
   *           the file.
   */
  async launchDownload(aDownload, { openWhere, useSystemDefault = null }) {
    let file = new lazy.FileUtils.File(aDownload.target.path);

    // In case of a double extension, like ".tar.gz", we only
    // consider the last one, because the MIME service cannot
    // handle multiple extensions.
    let fileExtension = null,
      mimeInfo = null;
    let match = file.leafName.match(/\.([^.]+)$/);
    if (match) {
      fileExtension = match[1];
    }

    let isWindowsExe =
      AppConstants.platform == "win" &&
      fileExtension &&
      fileExtension.toLowerCase() == "exe";

    let isExemptExecutableExtension =
      Services.policies.isExemptExecutableExtension(
        aDownload.source.url,
        fileExtension
      );

    // Ask for confirmation if the file is executable, except for .exe on
    // Windows where the operating system will show the prompt based on the
    // security zone.  We do this here, instead of letting the caller handle
    // the prompt separately in the user interface layer, for two reasons.  The
    // first is because of its security nature, so that add-ons cannot forget
    // to do this check.  The second is that the system-level security prompt
    // would be displayed at launch time in any case.
    // We allow policy to override this behavior for file extensions on specific domains.
    if (
      file.isExecutable() &&
      !isWindowsExe &&
      !isExemptExecutableExtension &&
      !(await this.confirmLaunchExecutable(file.path))
    ) {
      return;
    }

    try {
      // The MIME service might throw if contentType == "" and it can't find
      // a MIME type for the given extension, so we'll treat this case as
      // an unknown mimetype.
      mimeInfo = lazy.gMIMEService.getFromTypeAndExtension(
        aDownload.contentType,
        fileExtension
      );
    } catch (e) {}

    if (aDownload.launcherPath) {
      if (!mimeInfo) {
        // This should not happen on normal circumstances because launcherPath
        // is only set when we had an instance of nsIMIMEInfo to retrieve
        // the custom application chosen by the user.
        throw new Error(
          "Unable to create nsIMIMEInfo to launch a custom application"
        );
      }

      // Custom application chosen
      let localHandlerApp = Cc[
        "@mozilla.org/uriloader/local-handler-app;1"
      ].createInstance(Ci.nsILocalHandlerApp);
      localHandlerApp.executable = new lazy.FileUtils.File(
        aDownload.launcherPath
      );

      mimeInfo.preferredApplicationHandler = localHandlerApp;
      mimeInfo.preferredAction = Ci.nsIMIMEInfo.useHelperApp;

      this.launchFile(file, mimeInfo);
      // After an attempt has been made to launch the download, clear the
      // launchWhenSucceeded bit so future attempts to open the download can go
      // through Firefox when possible.
      aDownload.launchWhenSucceeded = false;
      return;
    }

    if (!useSystemDefault && mimeInfo) {
      useSystemDefault = mimeInfo.preferredAction == mimeInfo.useSystemDefault;
    }
    if (!useSystemDefault) {
      // No explicit instruction was passed to launch this download using the default system viewer.
      if (
        aDownload.handleInternally ||
        (mimeInfo &&
          this.shouldViewDownloadInternally(mimeInfo.type, fileExtension) &&
          !mimeInfo.alwaysAskBeforeHandling &&
          (mimeInfo.preferredAction === Ci.nsIHandlerInfo.handleInternally ||
            (["image/svg+xml", "text/xml", "application/xml"].includes(
              mimeInfo.type
            ) &&
              mimeInfo.preferredAction === Ci.nsIHandlerInfo.saveToDisk)) &&
          !aDownload.launchWhenSucceeded)
      ) {
        lazy.DownloadUIHelper.loadFileIn(file, {
          browsingContextId: aDownload.source.browsingContextId,
          isPrivate: aDownload.source.isPrivate,
          openWhere,
          userContextId: aDownload.source.userContextId,
        });
        return;
      }
    }

    // An attempt will now be made to launch the download, clear the
    // launchWhenSucceeded bit so future attempts to open the download can go
    // through Firefox when possible.
    aDownload.launchWhenSucceeded = false;

    // When a file has no extension, and there's an executable file with the
    // same name in the same folder, Windows shell can get confused.
    // For this reason we show the file in the containing folder instead of
    // trying to open it.
    // We also don't trust mimeinfo, it could be a type we can forward to a
    // system handler, but it could also be an executable type, and we
    // don't have an exhaustive list with all of them.
    if (!fileExtension && AppConstants.platform == "win") {
      // We can't check for the existance of a same-name file with every
      // possible executable extension, so this is a catch-all.
      this.showContainingDirectory(aDownload.target.path);
      return;
    }

    // No custom application chosen, let's launch the file with the default
    // handler. First, let's try to launch it through the MIME service.
    if (mimeInfo) {
      mimeInfo.preferredAction = Ci.nsIMIMEInfo.useSystemDefault;
      try {
        this.launchFile(file, mimeInfo);
        return;
      } catch (ex) {}
    }

    // If it didn't work or if there was no MIME info available,
    // let's try to directly launch the file.
    try {
      this.launchFile(file);
      return;
    } catch (ex) {}

    // If our previous attempts failed, try sending it through
    // the system's external "file:" URL handler.
    lazy.gExternalProtocolService.loadURI(
      lazy.NetUtil.newURI(file),
      Services.scriptSecurityManager.getSystemPrincipal()
    );
  },

  /**
   * Asks for confirmation for launching the specified executable file. This
   * can be overridden by regression tests to avoid the interactive prompt.
   */
  async confirmLaunchExecutable(path) {
    // We don't anchor the prompt to a specific window intentionally, not
    // only because this is the same behavior as the system-level prompt,
    // but also because the most recently active window is the right choice
    // in basically all cases.
    return lazy.DownloadUIHelper.getPrompter().confirmLaunchExecutable(path);
  },

  /**
   * Launches the specified file, unless overridden by regression tests.
   * @note Always use launchDownload() from the outside of this module, it is
   *       both more powerful and safer.
   */
  launchFile(file, mimeInfo) {
    if (mimeInfo) {
      mimeInfo.launchWithFile(file);
    } else {
      file.launch();
    }
  },

  /**
   * Shows the containing folder of a file.
   *
   * @param aFilePath
   *        The path to the file.
   *
   * @return {Promise}
   * @resolves When the instruction to open the containing folder has been
   *           successfully given to the operating system. Note that
   *           the OS might still take a while until the folder is actually
   *           opened.
   * @rejects  JavaScript exception if there was an error trying to open
   *           the containing folder.
   */
  async showContainingDirectory(aFilePath) {
    let file = new lazy.FileUtils.File(aFilePath);

    try {
      // Show the directory containing the file and select the file.
      file.reveal();
      return;
    } catch (ex) {}

    // If reveal fails for some reason (e.g., it's not implemented on unix
    // or the file doesn't exist), try using the parent if we have it.
    let parent = file.parent;
    if (!parent) {
      throw new Error(
        "Unexpected reference to a top-level directory instead of a file"
      );
    }

    try {
      // Open the parent directory to show where the file should be.
      parent.launch();
      return;
    } catch (ex) {}

    // If launch also fails (probably because it's not implemented), let
    // the OS handler try to open the parent.
    lazy.gExternalProtocolService.loadURI(
      lazy.NetUtil.newURI(parent),
      Services.scriptSecurityManager.getSystemPrincipal()
    );
  },

  /**
   * Calls the directory service, create a downloads directory and returns an
   * nsIFile for the downloads directory.
   *
   * @return {Promise}
   * @resolves The directory string path.
   */
  _createDownloadsDirectory(aName) {
    // We read the name of the directory from the list of translated strings
    // that is kept by the UI helper module, even if this string is not strictly
    // displayed in the user interface.
    let directoryPath = PathUtils.join(
      this._getDirectory(aName),
      lazy.stringBundle.GetStringFromName("downloadsFolder")
    );

    // Create the Downloads folder and ignore if it already exists.
    return IOUtils.makeDirectory(directoryPath, {
      createAncestors: false,
    }).then(() => directoryPath);
  },

  /**
   * Returns the string path for the given directory service location name. This
   * can be overridden by regression tests to return the path of the system
   * temporary directory in all cases.
   */
  _getDirectory(name) {
    return Services.dirsvc.get(name, Ci.nsIFile).path;
  },

  /**
   * Initializes the DownloadSpamProtection instance.
   * This is used to observe and group multiple automatic downloads.
   */
  _initializeDownloadSpamProtection() {
    if (!this.downloadSpamProtection) {
      this.downloadSpamProtection = new lazy.DownloadSpamProtection();
    }
  },

  /**
   * Register the downloads interruption observers.
   *
   * @param aList
   *        The public or private downloads list.
   * @param aIsPrivate
   *        True if the list is private, false otherwise.
   *
   * @return {Promise}
   * @resolves When the views and observers are added.
   */
  addListObservers(aList, aIsPrivate) {
    DownloadObserver.registerView(aList, aIsPrivate);
    if (!DownloadObserver.observersAdded) {
      DownloadObserver.observersAdded = true;
      for (let topic of kObserverTopics) {
        Services.obs.addObserver(DownloadObserver, topic);
      }
    }
    return Promise.resolve();
  },

  /**
   * Force a save on _store if it exists. Used to ensure downloads do not
   * persist after being sanitized on Android.
   *
   * @return {Promise}
   * @resolves When _store.save() completes.
   */
  forceSave() {
    if (this._store) {
      return this._store.save();
    }
    return Promise.resolve();
  },
};

var DownloadObserver = {
  /**
   * Flag to determine if the observers have been added previously.
   */
  observersAdded: false,

  /**
   * Timer used to delay restarting canceled downloads upon waking and returning
   * online.
   */
  _wakeTimer: null,

  /**
   * Set that contains the in progress publics downloads.
   * It's kept updated when a public download is added, removed or changes its
   * properties.
   */
  _publicInProgressDownloads: new Set(),

  /**
   * Set that contains the in progress private downloads.
   * It's kept updated when a private download is added, removed or changes its
   * properties.
   */
  _privateInProgressDownloads: new Set(),

  /**
   * Set that contains the downloads that have been canceled when going offline
   * or to sleep. These are started again when returning online or waking. This
   * list is not persisted so when exiting and restarting, the downloads will not
   * be started again.
   */
  _canceledOfflineDownloads: new Set(),

  /**
   * Registers a view that updates the corresponding downloads state set, based
   * on the aIsPrivate argument. The set is updated when a download is added,
   * removed or changes its properties.
   *
   * @param aList
   *        The public or private downloads list.
   * @param aIsPrivate
   *        True if the list is private, false otherwise.
   */
  registerView: function DO_registerView(aList, aIsPrivate) {
    let downloadsSet = aIsPrivate
      ? this._privateInProgressDownloads
      : this._publicInProgressDownloads;
    let downloadsView = {
      onDownloadAdded: aDownload => {
        if (!aDownload.stopped) {
          downloadsSet.add(aDownload);
        }
      },
      onDownloadChanged: aDownload => {
        if (aDownload.stopped) {
          downloadsSet.delete(aDownload);
        } else {
          downloadsSet.add(aDownload);
        }
      },
      onDownloadRemoved: aDownload => {
        downloadsSet.delete(aDownload);
        // The download must also be removed from the canceled when offline set.
        this._canceledOfflineDownloads.delete(aDownload);
      },
    };

    // We register the view asynchronously.
    aList.addView(downloadsView).catch(console.error);
  },

  /**
   * Wrapper that handles the test mode before calling the prompt that display
   * a warning message box that informs that there are active downloads,
   * and asks whether the user wants to cancel them or not.
   *
   * @param aCancel
   *        The observer notification subject.
   * @param aDownloadsCount
   *        The current downloads count.
   * @param aPrompter
   *        The prompter object that shows the confirm dialog.
   * @param aPromptType
   *        The type of prompt notification depending on the observer.
   */
  _confirmCancelDownloads: function DO_confirmCancelDownload(
    aCancel,
    aDownloadsCount,
    aPromptType
  ) {
    // Handle test mode
    if (lazy.gCombinedDownloadIntegration._testPromptDownloads) {
      lazy.gCombinedDownloadIntegration._testPromptDownloads = aDownloadsCount;
      return;
    }

    if (!aDownloadsCount) {
      return;
    }

    // If user has already dismissed the request, then do nothing.
    if (aCancel instanceof Ci.nsISupportsPRBool && aCancel.data) {
      return;
    }

    let prompter = lazy.DownloadUIHelper.getPrompter();
    aCancel.data = prompter.confirmCancelDownloads(
      aDownloadsCount,
      prompter[aPromptType]
    );
  },

  /**
   * Resume all downloads that were paused when going offline, used when waking
   * from sleep or returning from being offline.
   */
  _resumeOfflineDownloads: function DO_resumeOfflineDownloads() {
    this._wakeTimer = null;

    for (let download of this._canceledOfflineDownloads) {
      download.start().catch(() => {});
    }
    this._canceledOfflineDownloads.clear();
  },

  // nsIObserver
  observe: function DO_observe(aSubject, aTopic, aData) {
    let downloadsCount;
    switch (aTopic) {
      case "quit-application-requested":
        downloadsCount =
          this._publicInProgressDownloads.size +
          this._privateInProgressDownloads.size;
        this._confirmCancelDownloads(aSubject, downloadsCount, "ON_QUIT");
        break;
      case "offline-requested":
        downloadsCount =
          this._publicInProgressDownloads.size +
          this._privateInProgressDownloads.size;
        this._confirmCancelDownloads(aSubject, downloadsCount, "ON_OFFLINE");
        break;
      case "last-pb-context-exiting":
        downloadsCount = this._privateInProgressDownloads.size;
        this._confirmCancelDownloads(
          aSubject,
          downloadsCount,
          "ON_LEAVE_PRIVATE_BROWSING"
        );
        break;
      case "last-pb-context-exited":
        let promise = (async function () {
          let list = await Downloads.getList(Downloads.PRIVATE);
          let downloads = await list.getAll();

          // We can remove the downloads and finalize them in parallel.
          for (let download of downloads) {
            list.remove(download).catch(console.error);
            download.finalize(true).catch(console.error);
          }
        })();
        // Handle test mode
        if (lazy.gCombinedDownloadIntegration._testResolveClearPrivateList) {
          lazy.gCombinedDownloadIntegration._testResolveClearPrivateList(
            promise
          );
        } else {
          promise.catch(ex => console.error(ex));
        }
        break;
      case "sleep_notification":
      case "suspend_process_notification":
      case "network:offline-about-to-go-offline":
        // Ignore shutdown notification so aborted downloads will be restarted
        // on the next session.
        if (
          Services.startup.isInOrBeyondShutdownPhase(
            Ci.nsIAppStartup.SHUTDOWN_PHASE_APPSHUTDOWNCONFIRMED
          )
        ) {
          break;
        }
        for (let download of this._publicInProgressDownloads) {
          download.cancel();
          this._canceledOfflineDownloads.add(download);
        }
        for (let download of this._privateInProgressDownloads) {
          download.cancel();
          this._canceledOfflineDownloads.add(download);
        }
        break;
      case "wake_notification":
      case "resume_process_notification":
        let wakeDelay = Services.prefs.getIntPref(
          "browser.download.manager.resumeOnWakeDelay",
          10000
        );

        if (wakeDelay >= 0) {
          this._wakeTimer = new Timer(
            this._resumeOfflineDownloads.bind(this),
            wakeDelay,
            Ci.nsITimer.TYPE_ONE_SHOT
          );
        }
        break;
      case "network:offline-status-changed":
        if (aData == "online") {
          this._resumeOfflineDownloads();
        }
        break;
      // We need to unregister observers explicitly before we reach the
      // "xpcom-shutdown" phase, otherwise observers may be notified when some
      // required services are not available anymore. We can't unregister
      // observers on "quit-application", because this module is also loaded
      // during "make package" automation, and the quit notification is not sent
      // in that execution environment (bug 973637).
      case "xpcom-will-shutdown":
        for (let topic of kObserverTopics) {
          Services.obs.removeObserver(this, topic);
        }
        break;
      case "blocked-automatic-download":
        if (AppConstants.MOZ_BUILD_APP == "browser") {
          DownloadIntegration._initializeDownloadSpamProtection();
          DownloadIntegration.downloadSpamProtection.update(
            aData,
            aSubject.topChromeWindow
          );
        }
        break;
    }
  },

  QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
};

/**
 * Registers a Places observer so that operations on download history are
 * reflected on the provided list of downloads.
 *
 * You do not need to keep a reference to this object in order to keep it alive,
 * because the history service already keeps a strong reference to it.
 *
 * @param aList
 *        DownloadList object linked to this observer.
 */
var DownloadHistoryObserver = function (aList) {
  this._list = aList;

  const placesObserver = new PlacesWeakCallbackWrapper(
    this.handlePlacesEvents.bind(this)
  );
  PlacesObservers.addListener(
    ["history-cleared", "page-removed"],
    placesObserver
  );
};

DownloadHistoryObserver.prototype = {
  /**
   * DownloadList object linked to this observer.
   */
  _list: null,

  handlePlacesEvents(events) {
    for (const event of events) {
      switch (event.type) {
        case "history-cleared": {
          this._list.removeFinished();
          break;
        }
        case "page-removed": {
          if (event.isRemovedFromStore) {
            this._list.removeFinished(
              download => event.url === download.source.url
            );
          }
          break;
        }
      }
    }
  },
};

/**
 * This view can be added to a DownloadList object to trigger a save operation
 * in the given DownloadStore object when a relevant change occurs.  You should
 * call the "initialize" method in order to register the view and load the
 * current state from disk.
 *
 * You do not need to keep a reference to this object in order to keep it alive,
 * because the DownloadList object already keeps a strong reference to it.
 *
 * @param aList
 *        The DownloadList object on which the view should be registered.
 * @param aStore
 *        The DownloadStore object used for saving.
 */
var DownloadAutoSaveView = function (aList, aStore) {
  this._list = aList;
  this._store = aStore;
  this._downloadsMap = new Map();
  this._writer = new lazy.DeferredTask(() => this._store.save(), kSaveDelayMs);
  lazy.AsyncShutdown.profileBeforeChange.addBlocker(
    "DownloadAutoSaveView: writing data",
    () => this._writer.finalize()
  );
};

DownloadAutoSaveView.prototype = {
  /**
   * DownloadList object linked to this view.
   */
  _list: null,

  /**
   * The DownloadStore object used for saving.
   */
  _store: null,

  /**
   * True when the initial state of the downloads has been loaded.
   */
  _initialized: false,

  /**
   * Registers the view and loads the current state from disk.
   *
   * @return {Promise}
   * @resolves When the view has been registered.
   * @rejects JavaScript exception.
   */
  initialize() {
    // We set _initialized to true after adding the view, so that
    // onDownloadAdded doesn't cause a save to occur.
    return this._list.addView(this).then(() => (this._initialized = true));
  },

  /**
   * This map contains only Download objects that should be saved to disk, and
   * associates them with the result of their getSerializationHash function, for
   * the purpose of detecting changes to the relevant properties.
   */
  _downloadsMap: null,

  /**
   * DeferredTask for the save operation.
   */
  _writer: null,

  /**
   * Called when the list of downloads changed, this triggers the asynchronous
   * serialization of the list of downloads.
   */
  saveSoon() {
    this._writer.arm();
  },

  // DownloadList callback
  onDownloadAdded(aDownload) {
    if (lazy.gCombinedDownloadIntegration.shouldPersistDownload(aDownload)) {
      this._downloadsMap.set(aDownload, aDownload.getSerializationHash());
      if (this._initialized) {
        this.saveSoon();
      }
    }
  },

  // DownloadList callback
  onDownloadChanged(aDownload) {
    if (!lazy.gCombinedDownloadIntegration.shouldPersistDownload(aDownload)) {
      if (this._downloadsMap.has(aDownload)) {
        this._downloadsMap.delete(aDownload);
        this.saveSoon();
      }
      return;
    }

    let hash = aDownload.getSerializationHash();
    if (this._downloadsMap.get(aDownload) != hash) {
      this._downloadsMap.set(aDownload, hash);
      this.saveSoon();
    }
  },

  // DownloadList callback
  onDownloadRemoved(aDownload) {
    if (this._downloadsMap.has(aDownload)) {
      this._downloadsMap.delete(aDownload);
      this.saveSoon();
    }
  },
};
PK
!<������modules/DownloadStore.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Handles serialization of Download objects and persistence into a file, so
 * that the state of downloads can be restored across sessions.
 *
 * The file is stored in JSON format, without indentation.  With indentation
 * applied, the file would look like this:
 *
 * {
 *   "list": [
 *     {
 *       "source": "http://www.example.com/download.txt",
 *       "target": "/home/user/Downloads/download.txt"
 *     },
 *     {
 *       "source": {
 *         "url": "http://www.example.com/download.txt",
 *         "referrerInfo": serialized string represents referrerInfo object
 *       },
 *       "target": "/home/user/Downloads/download-2.txt"
 *     }
 *   ]
 * }
 */

// Time after which insecure downloads that have not been dealt with on shutdown
// get removed (5 minutes).
const MAX_INSECURE_DOWNLOAD_AGE_MS = 5 * 60 * 1000;

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Downloads: "resource://gre/modules/Downloads.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "gTextDecoder", function () {
  return new TextDecoder();
});

ChromeUtils.defineLazyGetter(lazy, "gTextEncoder", function () {
  return new TextEncoder();
});

/**
 * Handles serialization of Download objects and persistence into a file, so
 * that the state of downloads can be restored across sessions.
 *
 * @param aList
 *        DownloadList object to be populated or serialized.
 * @param aPath
 *        String containing the file path where data should be saved.
 */
export var DownloadStore = function (aList, aPath) {
  this.list = aList;
  this.path = aPath;
};

DownloadStore.prototype = {
  /**
   * DownloadList object to be populated or serialized.
   */
  list: null,

  /**
   * String containing the file path where data should be saved.
   */
  path: "",

  /**
   * This function is called with a Download object as its first argument, and
   * should return true if the item should be saved.
   */
  onsaveitem: () => true,

  /**
   * Loads persistent downloads from the file to the list.
   *
   * @return {Promise}
   * @resolves When the operation finished successfully.
   * @rejects JavaScript exception.
   */
  load: function DS_load() {
    return (async () => {
      let bytes;
      try {
        bytes = await IOUtils.read(this.path);
      } catch (ex) {
        if (!(ex.name == "NotFoundError")) {
          throw ex;
        }
        // If the file does not exist, there are no downloads to load.
        return;
      }

      // Set this to true when we make changes to the download list that should
      // be reflected in the file again.
      let storeChanges = false;
      let removePromises = [];
      let storeData = JSON.parse(lazy.gTextDecoder.decode(bytes));

      // Create live downloads based on the static snapshot.
      for (let downloadData of storeData.list) {
        try {
          let download = await lazy.Downloads.createDownload(downloadData);

          // Insecure downloads that have not been dealt with on shutdown should
          // get cleaned up and removed from the download list on restart unless
          // they are very new
          if (
            download.error?.becauseBlockedByReputationCheck &&
            download.error.reputationCheckVerdict == "Insecure" &&
            Date.now() - download.startTime > MAX_INSECURE_DOWNLOAD_AGE_MS
          ) {
            removePromises.push(download.removePartialData());
            storeChanges = true;
            continue;
          }

          try {
            if (!download.succeeded && !download.canceled && !download.error) {
              // Try to restart the download if it was in progress during the
              // previous session.  Ignore errors.
              download.start().catch(() => {});
            } else {
              // If the download was not in progress, try to update the current
              // progress from disk.  This is relevant in case we retained
              // partially downloaded data.
              await download.refresh();
            }
          } finally {
            // Add the download to the list if we succeeded in creating it,
            // after we have updated its initial state.
            await this.list.add(download);
          }
        } catch (ex) {
          // If an item is unrecognized, don't prevent others from being loaded.
          console.error(ex);
        }
      }

      if (storeChanges) {
        try {
          await Promise.all(removePromises);
          await this.save();
        } catch (ex) {
          console.error(ex);
        }
      }
    })();
  },

  /**
   * Saves persistent downloads from the list to the file.
   *
   * If an error occurs, the previous file is not deleted.
   *
   * @return {Promise}
   * @resolves When the operation finished successfully.
   * @rejects JavaScript exception.
   */
  save: function DS_save() {
    return (async () => {
      let downloads = await this.list.getAll();

      // Take a static snapshot of the current state of all the downloads.
      let storeData = { list: [] };
      let atLeastOneDownload = false;
      for (let download of downloads) {
        try {
          if (!this.onsaveitem(download)) {
            continue;
          }

          let serializable = download.toSerializable();
          if (!serializable) {
            // This item cannot be persisted across sessions.
            continue;
          }
          storeData.list.push(serializable);
          atLeastOneDownload = true;
        } catch (ex) {
          // If an item cannot be converted to a serializable form, don't
          // prevent others from being saved.
          console.error(ex);
        }
      }

      if (atLeastOneDownload) {
        // Create or overwrite the file if there are downloads to save.
        let bytes = lazy.gTextEncoder.encode(JSON.stringify(storeData));
        await IOUtils.write(this.path, bytes, {
          tmpPath: this.path + ".tmp",
        });
      } else {
        // Remove the file if there are no downloads to save at all.
        try {
          await IOUtils.remove(this.path);
        } catch (ex) {
          if (!(ex.name == "NotFoundError" || ex.name == "NotAllowedError")) {
            throw ex;
          }
          // On Windows, we may get an access denied error instead of a no such
          // file error if the file existed before, and was recently deleted.
        }
      }
    })();
  },
};
PK
!<FdA[��#actors/BrowserElementParent.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * The BrowserElementParent is for performing actions on one or more subframes of
 * a <xul:browser> from the browser element binding.
 */
export class BrowserElementParent extends JSWindowActorParent {
  receiveMessage(message) {
    switch (message.name) {
      case "DOMWindowClose": {
        // This message is sent whenever window.close() is called within a window
        // that had originally been opened via window.open. Double-check that this is
        // coming from a top-level frame, and then dispatch the DOMWindowClose event
        // on the browser so that the front-end code can do the right thing with the
        // request to close.
        if (!this.manager.browsingContext.parent) {
          let browser = this.manager.browsingContext.embedderElement;
          let win = browser.ownerGlobal;
          // If this is a non-remote browser, the DOMWindowClose event will bubble
          // up naturally, and doesn't need to be re-dispatched.
          if (browser.isRemoteBrowser) {
            browser.dispatchEvent(
              new win.CustomEvent("DOMWindowClose", {
                bubbles: true,
              })
            );
          }
        }
        break;
      }
    }
  }
}
PK
!<{�|�T�T+modules/sessionstore/SessionHistory.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
});

/**
 * The external API exported by this module.
 */
export var SessionHistory = Object.freeze({
  isEmpty(docShell) {
    return SessionHistoryInternal.isEmpty(docShell);
  },

  collect(docShell, aFromIdx = -1) {
    if (Services.appinfo.sessionHistoryInParent) {
      throw new Error("Use SessionHistory.collectFromParent instead");
    }
    return SessionHistoryInternal.collect(docShell, aFromIdx);
  },

  collectFromParent(uri, documentHasChildNodes, history, aFromIdx = -1) {
    return SessionHistoryInternal.collectCommon(
      uri,
      documentHasChildNodes,
      history,
      aFromIdx
    );
  },

  collectNonWebControlledBlankLoadingSession(browsingContext) {
    return SessionHistoryInternal.collectNonWebControlledBlankLoadingSession(
      browsingContext
    );
  },

  restore(docShell, tabData) {
    if (Services.appinfo.sessionHistoryInParent) {
      throw new Error("Use SessionHistory.restoreFromParent instead");
    }
    return SessionHistoryInternal.restore(
      docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory
        .legacySHistory,
      tabData
    );
  },

  restoreFromParent(history, tabData) {
    return SessionHistoryInternal.restore(history, tabData);
  },
});

/**
 * The internal API for the SessionHistory module.
 */
var SessionHistoryInternal = {
  /**
   * Mapping from legacy docshellIDs to docshellUUIDs.
   */
  _docshellUUIDMap: new Map(),

  /**
   * Returns whether the given docShell's session history is empty.
   *
   * @param docShell
   *        The docShell that owns the session history.
   */
  isEmpty(docShell) {
    let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
    let history = webNavigation.sessionHistory;
    if (!webNavigation.currentURI) {
      return true;
    }
    let uri = webNavigation.currentURI.spec;
    return uri == "about:blank" && history.count == 0;
  },

  /**
   * Collects session history data for a given docShell.
   *
   * @param docShell
   *        The docShell that owns the session history.
   * @param aFromIdx
   *        The starting local index to collect the history from.
   * @return An object reprereseting a partial global history update.
   */
  collect(docShell, aFromIdx = -1) {
    let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
    let uri = webNavigation.currentURI.displaySpec;
    let body = webNavigation.document.body;
    let history = webNavigation.sessionHistory;
    return this.collectCommon(
      uri,
      body && body.hasChildNodes(),
      history.legacySHistory,
      aFromIdx
    );
  },

  collectCommon(uri, documentHasChildNodes, shistory, aFromIdx) {
    let data = {
      entries: [],
      requestedIndex: shistory.requestedIndex + 1,
    };

    // We want to keep track how many entries we *could* have collected and
    // how many we skipped, so we can sanitiy-check the current history index
    // and also determine whether we need to get any fallback data or not.
    let skippedCount = 0,
      entryCount = 0;

    if (shistory && shistory.count > 0) {
      let count = shistory.count;
      for (; entryCount < count; entryCount++) {
        let shEntry = shistory.getEntryAtIndex(entryCount);
        if (entryCount <= aFromIdx) {
          skippedCount++;
          continue;
        }
        let entry = this.serializeEntry(shEntry);
        data.entries.push(entry);
      }

      // Ensure the index isn't out of bounds if an exception was thrown above.
      data.index = Math.min(shistory.index + 1, entryCount);
    }

    // If either the session history isn't available yet or doesn't have any
    // valid entries, make sure we at least include the current page,
    // unless of course we just skipped all entries because aFromIdx was big enough.
    if (!data.entries.length && (skippedCount != entryCount || aFromIdx < 0)) {
      // We landed here because the history is inaccessible or there are no
      // history entries. In that case we should at least record the docShell's
      // current URL as a single history entry. If the URL is not about:blank
      // or it's a blank tab that was modified (like a custom newtab page),
      // record it. For about:blank we explicitly want an empty array without
      // an 'index' property to denote that there are no history entries.
      if (uri != "about:blank" || documentHasChildNodes) {
        data.entries.push({
          url: uri,
          triggeringPrincipal_base64: lazy.E10SUtils.SERIALIZED_SYSTEMPRINCIPAL,
        });
        data.index = 1;
      }
    }

    data.fromIdx = aFromIdx;

    return data;
  },

  collectNonWebControlledBlankLoadingSession(browsingContext) {
    if (
      browsingContext.sessionHistory?.count === 0 &&
      browsingContext.nonWebControlledBlankURI &&
      browsingContext.mostRecentLoadingSessionHistoryEntry
    ) {
      return {
        entries: [
          this.serializeEntry(
            browsingContext.mostRecentLoadingSessionHistoryEntry
          ),
        ],
        // Set 1 to the index, as the array of session entries is 1-based.
        index: 1,
        fromIdx: -1,
        requestedIndex: browsingContext.sessionHistory.requestedIndex + 1,
      };
    }

    return null;
  },

  /**
   * Get an object that is a serialized representation of a History entry.
   *
   * @param shEntry
   *        nsISHEntry instance
   * @return object
   */
  serializeEntry(shEntry) {
    let entry = { url: shEntry.URI.displaySpec, title: shEntry.title };

    if (shEntry.isSubFrame) {
      entry.subframe = true;
    }

    entry.cacheKey = shEntry.cacheKey;
    entry.ID = shEntry.ID;
    entry.docshellUUID = shEntry.docshellID.toString();

    // We will include the property only if it's truthy to save a couple of
    // bytes when the resulting object is stringified and saved to disk.
    if (shEntry.referrerInfo) {
      entry.referrerInfo = lazy.E10SUtils.serializeReferrerInfo(
        shEntry.referrerInfo
      );
    }

    if (shEntry.originalURI) {
      entry.originalURI = shEntry.originalURI.spec;
    }

    if (shEntry.resultPrincipalURI) {
      entry.resultPrincipalURI = shEntry.resultPrincipalURI.spec;

      // For downgrade compatibility we store the loadReplace property as it
      // would be stored before result principal URI introduction so that
      // the old code can still create URL based principals for channels
      // correctly.  When resultPrincipalURI is non-null and not equal to
      // channel's orignalURI in the new code, it's equal to setting
      // LOAD_REPLACE in the old code.  Note that we only do 'the best we can'
      // here to derivate the 'old' loadReplace flag value.
      entry.loadReplace = entry.resultPrincipalURI != entry.originalURI;
    } else {
      // We want to store the property to let the backward compatibility code,
      // when reading the stored session, work. When this property is undefined
      // that code will derive the result principal URI from the load replace
      // flag.
      entry.resultPrincipalURI = null;
    }

    if (shEntry.loadReplace) {
      // Storing under a new property name, since it has changed its meaning
      // with the result principal URI introduction.
      entry.loadReplace2 = shEntry.loadReplace;
    }

    if (shEntry.isSrcdocEntry) {
      entry.srcdocData = shEntry.srcdocData;
      entry.isSrcdocEntry = shEntry.isSrcdocEntry;
    }

    if (shEntry.baseURI) {
      entry.baseURI = shEntry.baseURI.spec;
    }

    if (shEntry.contentType) {
      entry.contentType = shEntry.contentType;
    }

    if (shEntry.scrollRestorationIsManual) {
      entry.scrollRestorationIsManual = true;
    } else {
      let x = {},
        y = {};
      shEntry.getScrollPosition(x, y);
      if (x.value !== 0 || y.value !== 0) {
        entry.scroll = x.value + "," + y.value;
      }

      let layoutHistoryState = shEntry.layoutHistoryState;
      if (layoutHistoryState && layoutHistoryState.hasStates) {
        let presStates = layoutHistoryState
          .getKeys()
          .map(key => this._getSerializablePresState(layoutHistoryState, key))
          .filter(
            presState =>
              // Only keep presState entries that contain more than the key itself.
              Object.getOwnPropertyNames(presState).length > 1
          );

        if (presStates.length) {
          entry.presState = presStates;
        }
      }
    }

    // Collect triggeringPrincipal data for the current history entry.
    if (shEntry.principalToInherit) {
      entry.principalToInherit_base64 = lazy.E10SUtils.serializePrincipal(
        shEntry.principalToInherit
      );
    }

    if (shEntry.partitionedPrincipalToInherit) {
      entry.partitionedPrincipalToInherit_base64 =
        lazy.E10SUtils.serializePrincipal(
          shEntry.partitionedPrincipalToInherit
        );
    }

    entry.hasUserInteraction = shEntry.hasUserInteraction;

    if (shEntry.triggeringPrincipal) {
      entry.triggeringPrincipal_base64 = lazy.E10SUtils.serializePrincipal(
        shEntry.triggeringPrincipal
      );
    }

    if (shEntry.csp) {
      entry.csp = lazy.E10SUtils.serializeCSP(shEntry.csp);
    }

    entry.docIdentifier = shEntry.bfcacheID;

    if (shEntry.stateData != null) {
      let stateData = shEntry.stateData;
      entry.structuredCloneState = stateData.getDataAsBase64();
      entry.structuredCloneVersion = stateData.formatVersion;
    }

    if (shEntry.wireframe != null) {
      entry.wireframe = shEntry.wireframe;
    }

    if (shEntry.childCount > 0 && !shEntry.hasDynamicallyAddedChild()) {
      let children = [];
      for (let i = 0; i < shEntry.childCount; i++) {
        let child = shEntry.GetChildAt(i);

        if (child) {
          children.push(this.serializeEntry(child));
        }
      }

      if (children.length) {
        entry.children = children;
      }
    }

    entry.persist = shEntry.persist;

    return entry;
  },

  /**
   * Get an object that is a serializable representation of a PresState.
   *
   * @param layoutHistoryState
   *        nsILayoutHistoryState instance
   * @param stateKey
   *        The state key of the presState to be retrieved.
   * @return object
   */
  _getSerializablePresState(layoutHistoryState, stateKey) {
    let presState = { stateKey };
    let x = {},
      y = {},
      scrollOriginDowngrade = {},
      res = {};

    layoutHistoryState.getPresState(stateKey, x, y, scrollOriginDowngrade, res);
    if (x.value !== 0 || y.value !== 0) {
      presState.scroll = x.value + "," + y.value;
    }
    if (scrollOriginDowngrade.value === false) {
      presState.scrollOriginDowngrade = scrollOriginDowngrade.value;
    }
    if (res.value != 1.0) {
      presState.res = res.value;
    }

    return presState;
  },

  /**
   * Restores session history data for a given docShell.
   *
   * @param history
   *        The session history object.
   * @param tabData
   *        The tabdata including all history entries.
   * @return A reference to the docShell's nsISHistory interface.
   */
  restore(history, tabData) {
    if (history.count > 0) {
      history.purgeHistory(history.count);
    }

    let idMap = { used: {} };
    let docIdentMap = {};
    for (let i = 0; i < tabData.entries.length; i++) {
      let entry = tabData.entries[i];
      // XXXzpao Wallpaper patch for bug 514751
      if (!entry.url) {
        continue;
      }
      let persist = "persist" in entry ? entry.persist : true;
      let shEntry = this.deserializeEntry(entry, idMap, docIdentMap, history);

      // To enable a smooth migration, we treat values of null/undefined as having
      // user interaction (because we don't want to hide all session history that was
      // added before we started recording user interaction).
      //
      // This attribute is only set on top-level SH history entries, so we set it
      // outside of deserializeEntry since that is called recursively.
      if (entry.hasUserInteraction == undefined) {
        shEntry.hasUserInteraction = true;
      } else {
        shEntry.hasUserInteraction = entry.hasUserInteraction;
      }

      history.addEntry(shEntry, persist);
    }

    // Select the right history entry.
    let index = tabData.index - 1;
    if (index < history.count && history.index != index) {
      history.index = index;
    }
    return history;
  },

  /**
   * Expands serialized history data into a session-history-entry instance.
   *
   * @param entry
   *        Object containing serialized history data for a URL
   * @param idMap
   *        Hash for ensuring unique frame IDs
   * @param docIdentMap
   *        Hash to ensure reuse of BFCache entries
   * @returns nsISHEntry
   */
  deserializeEntry(entry, idMap, docIdentMap, shistory) {
    var shEntry = shistory.createEntry();

    shEntry.URI = Services.io.newURI(entry.url);
    shEntry.title = entry.title || entry.url;
    if (entry.subframe) {
      shEntry.isSubFrame = entry.subframe || false;
    }
    shEntry.setLoadTypeAsHistory();
    if (entry.contentType) {
      shEntry.contentType = entry.contentType;
    }
    // Referrer information is now stored as a referrerInfo property. We should
    // also cope with the old format of passing `referrer` and `referrerPolicy`
    // separately.
    if (entry.referrerInfo) {
      shEntry.referrerInfo = lazy.E10SUtils.deserializeReferrerInfo(
        entry.referrerInfo
      );
    } else if (entry.referrer) {
      let ReferrerInfo = Components.Constructor(
        "@mozilla.org/referrer-info;1",
        "nsIReferrerInfo",
        "init"
      );
      shEntry.referrerInfo = new ReferrerInfo(
        entry.referrerPolicy,
        true,
        Services.io.newURI(entry.referrer)
      );
    }

    if (entry.originalURI) {
      shEntry.originalURI = Services.io.newURI(entry.originalURI);
    }
    if (typeof entry.resultPrincipalURI === "undefined" && entry.loadReplace) {
      // This is backward compatibility code for stored sessions saved prior to
      // introduction of the resultPrincipalURI property.  The equivalent of this
      // property non-null value used to be the URL while the LOAD_REPLACE flag
      // was set.
      shEntry.resultPrincipalURI = shEntry.URI;
    } else if (entry.resultPrincipalURI) {
      shEntry.resultPrincipalURI = Services.io.newURI(entry.resultPrincipalURI);
    }
    if (entry.loadReplace2) {
      shEntry.loadReplace = entry.loadReplace2;
    }
    if (entry.isSrcdocEntry) {
      shEntry.srcdocData = entry.srcdocData;
    }
    if (entry.baseURI) {
      shEntry.baseURI = Services.io.newURI(entry.baseURI);
    }

    if (entry.cacheKey) {
      shEntry.cacheKey = entry.cacheKey;
    }

    if (entry.ID) {
      // get a new unique ID for this frame (since the one from the last
      // start might already be in use)
      var id = idMap[entry.ID] || 0;
      if (!id) {
        // eslint-disable-next-line no-empty
        for (id = Date.now(); id in idMap.used; id++) {}
        idMap[entry.ID] = id;
        idMap.used[id] = true;
      }
      shEntry.ID = id;
    }

    // If we have the legacy docshellID on our entry, upgrade it to a
    // docshellUUID by going through the mapping.
    if (entry.docshellID) {
      if (!this._docshellUUIDMap.has(entry.docshellID)) {
        // Convert the nsID to a string so that the docshellUUID property
        // is correctly stored as a string.
        this._docshellUUIDMap.set(
          entry.docshellID,
          Services.uuid.generateUUID().toString()
        );
      }
      entry.docshellUUID = this._docshellUUIDMap.get(entry.docshellID);
      delete entry.docshellID;
    }

    if (entry.docshellUUID) {
      shEntry.docshellID = Components.ID(entry.docshellUUID);
    }

    if (entry.structuredCloneState && entry.structuredCloneVersion) {
      var stateData = Cc[
        "@mozilla.org/docshell/structured-clone-container;1"
      ].createInstance(Ci.nsIStructuredCloneContainer);

      stateData.initFromBase64(
        entry.structuredCloneState,
        entry.structuredCloneVersion
      );
      shEntry.stateData = stateData;
    }

    if (entry.scrollRestorationIsManual) {
      shEntry.scrollRestorationIsManual = true;
    } else {
      if (entry.scroll) {
        shEntry.setScrollPosition(
          ...this._deserializeScrollPosition(entry.scroll)
        );
      }

      if (entry.presState) {
        let layoutHistoryState = shEntry.initLayoutHistoryState();

        for (let presState of entry.presState) {
          this._deserializePresState(layoutHistoryState, presState);
        }
      }
    }

    let childDocIdents = {};
    if (entry.docIdentifier) {
      // If we have a serialized document identifier, try to find an SHEntry
      // which matches that doc identifier and adopt that SHEntry's
      // BFCacheEntry.  If we don't find a match, insert shEntry as the match
      // for the document identifier.
      let matchingEntry = docIdentMap[entry.docIdentifier];
      if (!matchingEntry) {
        matchingEntry = { shEntry, childDocIdents };
        docIdentMap[entry.docIdentifier] = matchingEntry;
      } else {
        shEntry.adoptBFCacheEntry(matchingEntry.shEntry);
        childDocIdents = matchingEntry.childDocIdents;
      }
    }

    // Every load must have a triggeringPrincipal to load otherwise we prevent it,
    // this code *must* always return a valid principal:
    shEntry.triggeringPrincipal = lazy.E10SUtils.deserializePrincipal(
      entry.triggeringPrincipal_base64,
      () => {
        // This callback fires when we failed to deserialize the principal (or we don't have one)
        // and this ensures we always have a principal returned from this function.
        // We must always have a triggering principal for a load to work.
        // A null principal won't always work however is safe to use.
        console.warn(
          "Couldn't deserialize the triggeringPrincipal, falling back to NullPrincipal"
        );
        return Services.scriptSecurityManager.createNullPrincipal({});
      }
    );
    // As both partitionedPrincipal and principalToInherit are both not required to load
    // it's ok to keep these undefined when we don't have a previously defined principal.
    if (entry.partitionedPrincipalToInherit_base64) {
      shEntry.partitionedPrincipalToInherit =
        lazy.E10SUtils.deserializePrincipal(
          entry.partitionedPrincipalToInherit_base64
        );
    }
    if (entry.principalToInherit_base64) {
      shEntry.principalToInherit = lazy.E10SUtils.deserializePrincipal(
        entry.principalToInherit_base64
      );
    }
    if (entry.csp) {
      shEntry.csp = lazy.E10SUtils.deserializeCSP(entry.csp);
    }
    if (entry.wireframe) {
      shEntry.wireframe = entry.wireframe;
    }

    if (entry.children) {
      for (var i = 0; i < entry.children.length; i++) {
        // XXXzpao Wallpaper patch for bug 514751
        if (!entry.children[i].url) {
          continue;
        }

        // We're getting sessionrestore.js files with a cycle in the
        // doc-identifier graph, likely due to bug 698656.  (That is, we have
        // an entry where doc identifier A is an ancestor of doc identifier B,
        // and another entry where doc identifier B is an ancestor of A.)
        //
        // If we were to respect these doc identifiers, we'd create a cycle in
        // the SHEntries themselves, which causes the docshell to loop forever
        // when it looks for the root SHEntry.
        //
        // So as a hack to fix this, we restrict the scope of a doc identifier
        // to be a node's siblings and cousins, and pass childDocIdents, not
        // aDocIdents, to _deserializeHistoryEntry.  That is, we say that two
        // SHEntries with the same doc identifier have the same document iff
        // they have the same parent or their parents have the same document.

        shEntry.AddChild(
          this.deserializeEntry(
            entry.children[i],
            idMap,
            childDocIdents,
            shistory
          ),
          i
        );
      }
    }

    return shEntry;
  },

  /**
   * Expands serialized PresState data and adds it to the given nsILayoutHistoryState.
   *
   * @param layoutHistoryState
   *        nsILayoutHistoryState instance
   * @param presState
   *        Object containing serialized PresState data.
   */
  _deserializePresState(layoutHistoryState, presState) {
    let stateKey = presState.stateKey;
    let scrollOriginDowngrade =
      typeof presState.scrollOriginDowngrade == "boolean"
        ? presState.scrollOriginDowngrade
        : true;
    let res = presState.res || 1.0;

    layoutHistoryState.addNewPresState(
      stateKey,
      ...this._deserializeScrollPosition(presState.scroll),
      scrollOriginDowngrade,
      res
    );
  },

  /**
   * Expands serialized scroll position data into an array containing the x and y coordinates,
   * defaulting to 0,0 if no scroll position was found.
   *
   * @param scroll
   *        Object containing serialized scroll position data.
   * @return An array containing the scroll position's x and y coordinates.
   */
  _deserializeScrollPosition(scroll = "0,0") {
    return scroll.split(",").map(pos => parseInt(pos, 10) || 0);
  },
};
PK
!<����	�	modules/BinarySearch.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

export var BinarySearch = Object.freeze({
  /**
   * Returns the index of the given target in the given array or -1 if the
   * target is not found.
   *
   * See search() for a description of this function's parameters.
   *
   * @return The index of `target` in `array` or -1 if `target` is not found.
   */
  indexOf(comparator, array, target) {
    let [found, idx] = this.search(comparator, array, target);
    return found ? idx : -1;
  },

  /**
   * Returns the index within the given array where the given target may be
   * inserted to keep the array ordered.
   *
   * See search() for a description of this function's parameters.
   *
   * @return The index in `array` where `target` may be inserted to keep `array`
   *         ordered.
   */
  insertionIndexOf(comparator, array, target) {
    return this.search(comparator, array, target)[1];
  },

  /**
   * Searches for the given target in the given array.
   *
   * @param  comparator
   *         A function that takes two arguments and compares them, returning a
   *         negative number if the first should be ordered before the second,
   *         zero if the first and second have the same ordering, or a positive
   *         number if the second should be ordered before the first.  The first
   *         argument is always `target`, and the second argument is a value
   *         from the array.
   * @param  array
   *         An array whose elements are ordered by `comparator`.
   * @param  target
   *         The value to search for.
   * @return An array with two elements.  If `target` is found, the first
   *         element is true, and the second element is its index in the array.
   *         If `target` is not found, the first element is false, and the
   *         second element is the index where it may be inserted to keep the
   *         array ordered.
   */
  search(comparator, array, target) {
    let low = 0;
    let high = array.length - 1;
    while (low <= high) {
      // Thanks to http://jsperf.com/code-review-1480 for this tip.
      let mid = (low + high) >> 1;
      let cmp = comparator(target, array[mid]);
      if (cmp == 0) {
        return [true, mid];
      }
      if (cmp < 0) {
        high = mid - 1;
      } else {
        low = mid + 1;
      }
    }
    return [false, low];
  },
});
PK
!<���y�1�16chrome/toolkit/content/global/ml/EngineProcess.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(
  lazy,
  {
    HiddenFrame: "resource://gre/modules/HiddenFrame.sys.mjs",
  },
  { global: "current" }
);

/**
 * @typedef {import("../actors/MLEngineParent.sys.mjs").MLEngineParent} MLEngineParent
 * @typedef {import("../content/Utils.sys.mjs").ProgressAndStatusCallbackParams} ProgressAndStatusCallbackParams
 */

/**
 * @typedef {import("../../translations/actors/TranslationsEngineParent.sys.mjs").TranslationsEngineParent} TranslationsEngineParent
 */

/**
 * This class encapsulates the options for a pipeline process.
 */
export class PipelineOptions {
  /**
   * The identifier for the engine to be used by the pipeline.
   *
   * @type {?string}
   */
  engineId = "default-engine";

  /**
   * The name of the task the pipeline is configured for.
   *
   * @type {?string}
   */
  taskName = null;

  /**
   * The maximum amount of time in milliseconds the pipeline should wait for a response.
   *
   * @type {?number}
   */
  timeoutMS = null;

  /**
   * The root URL of the model hub where models are hosted.
   *
   * @type {?string}
   */
  modelHubRootUrl = null;

  /**
   * A template URL for building the full URL for the model.
   *
   * @type {?string}
   */
  modelHubUrlTemplate = null;

  /**
   * The identifier for the specific model to be used by the pipeline.
   *
   * @type {?string}
   */
  modelId = null;

  /**
   * The revision for the specific model to be used by the pipeline.
   *
   * @type {?string}
   */
  modelRevision = null;

  /**
   * The identifier for the tokenizer associated with the model, used for pre-processing inputs.
   *
   * @type {?string}
   */
  tokenizerId = null;

  /**
   * The revision for the tokenizer associated with the model, used for pre-processing inputs.
   *
   * @type {?string}
   */
  tokenizerRevision = null;

  /**
   * The identifier for any processor required by the model, used for additional input processing.
   *
   * @type {?string}
   */
  processorId = null;

  /**
   * The revision for any processor required by the model, used for additional input processing.
   *
   * @type {?string}
   */

  processorRevision = null;

  /**
   * The log level used in the worker
   *
   * @type {?string}
   */
  logLevel = null;

  /**
   * Name of the runtime wasm file
   *
   * @type {?string}
   */
  runtimeFilename = null;

  /**
   * Create a PipelineOptions instance.
   *
   * @param {object} options - The options for the pipeline. Must include mandatory fields.
   */
  constructor(options) {
    this.updateOptions(options);
  }

  /**
   * Updates multiple options at once.
   *
   * @param {object} options - An object containing the options to update.
   * @throws {Error} Throws an error if an invalid option is provided.
   */
  updateOptions(options) {
    const allowedKeys = [
      "engineId",
      "taskName",
      "modelHubRootUrl",
      "modelHubUrlTemplate",
      "timeoutMS",
      "modelId",
      "modelRevision",
      "tokenizerId",
      "tokenizerRevision",
      "processorId",
      "processorRevision",
      "logLevel",
      "runtimeFilename",
    ];

    if (options instanceof PipelineOptions) {
      options = options.getOptions();
    }

    let optionsKeys = Object.keys(options);

    allowedKeys.forEach(key => {
      // If options does not have the key we can ignore it.
      // We also ignore `null` values.
      if (!optionsKeys.includes(key) || options[key] == null) {
        return;
      }
      this[key] = options[key];
    });
  }

  /**
   * Returns an object containing all current options.

   * @returns {object} An object with the current options.
   */
  getOptions() {
    return {
      engineId: this.engineId,
      taskName: this.taskName,
      modelHubRootUrl: this.modelHubRootUrl,
      modelHubUrlTemplate: this.modelHubUrlTemplate,
      timeoutMS: this.timeoutMS,
      modelId: this.modelId,
      modelRevision: this.modelRevision,
      tokenizerId: this.tokenizerId,
      tokenizerRevision: this.tokenizerRevision,
      processorId: this.processorId,
      processorRevision: this.processorRevision,
      logLevel: this.logLevel,
      runtimeFilename: this.runtimeFilename,
    };
  }

  /**
   * Updates the given configuration object with the options.
   *
   * @param {object} config - The configuration object to be updated.
   */
  applyToConfig(config) {
    const options = this.getOptions();
    Object.keys(options).forEach(key => {
      if (options[key] !== null) {
        config[key] = options[key];
      }
    });
  }

  /**
   * Checks if this PipelineOptions instance is equal to another.
   *
   * @param {PipelineOptions} other - The other PipelineOptions instance to compare with.
   * @returns {boolean} True if the instances are equal, false otherwise.
   */
  equals(other) {
    if (!(other instanceof PipelineOptions)) {
      return false;
    }
    const options = this.getOptions();
    const otherOptions = other.getOptions();

    const isEqual = (val1, val2) => {
      if (val1 === val2) {
        return true;
      }
      if (val1 == null || val2 == null) {
        return false;
      }
      if (typeof val1 !== "object" || typeof val2 !== "object") {
        return false;
      }
      const keys1 = Object.keys(val1);
      const keys2 = Object.keys(val2);
      if (keys1.length !== keys2.length) {
        return false;
      }
      return keys1.every(key => isEqual(val1[key], val2[key]));
    };

    return Object.keys(options).every(key =>
      isEqual(options[key], otherOptions[key])
    );
  }
}

/**
 * This class controls the life cycle of the engine process used both in the
 * Translations engine and the MLEngine component.
 */
export class EngineProcess {
  /**
   * @type {Promise<{ hiddenFrame: HiddenFrame, actor: TranslationsEngineParent }> | null}
   */

  /** @type {Promise<HiddenFrame> | null} */
  static #hiddenFrame = null;
  /** @type {Promise<TranslationsEngineParent> | null} */
  static translationsEngineParent = null;
  /** @type {Promise<MLEngineParent> | null} */
  static mlEngineParent = null;

  /** @type {((actor: TranslationsEngineParent) => void) | null} */
  resolveTranslationsEngineParent = null;

  /** @type {((actor: MLEngineParent) => void) | null} */
  resolveMLEngineParent = null;

  /**
   * See if all engines are terminated. This is useful for testing.
   *
   * @returns {boolean}
   */
  static areAllEnginesTerminated() {
    return (
      !EngineProcess.#hiddenFrame &&
      !EngineProcess.translationsEngineParent &&
      !EngineProcess.mlEngineParent
    );
  }

  /**
   * @returns {Promise<TranslationsEngineParent>}
   */
  static async getTranslationsEngineParent() {
    if (!this.translationsEngineParent) {
      this.translationsEngineParent = this.#attachBrowser({
        id: "translations-engine-browser",
        url: "chrome://global/content/translations/translations-engine.html",
        resolverName: "resolveTranslationsEngineParent",
      });
    }
    return this.translationsEngineParent;
  }

  /**
   * @returns {Promise<MLEngineParent>}
   */
  static async getMLEngineParent() {
    // the pref is off by default
    if (!Services.prefs.getBoolPref("browser.ml.enable")) {
      throw new Error("MLEngine is disabled. Check the browser.ml prefs.");
    }

    if (!this.mlEngineParent) {
      this.mlEngineParent = this.#attachBrowser({
        id: "ml-engine-browser",
        url: "chrome://global/content/ml/MLEngine.html",
        resolverName: "resolveMLEngineParent",
      });
    }
    return this.mlEngineParent;
  }

  /**
   * @param {object} config
   * @param {string} config.url
   * @param {string} config.id
   * @param {string} config.resolverName
   * @returns {Promise<TranslationsEngineParent>}
   */
  static async #attachBrowser({ url, id, resolverName }) {
    const hiddenFrame = await this.#getHiddenFrame();
    const chromeWindow = await hiddenFrame.get();
    const doc = chromeWindow.document;

    if (doc.getElementById(id)) {
      throw new Error(
        "Attempting to append the translations-engine.html <browser> when one " +
          "already exists."
      );
    }

    const browser = doc.createXULElement("browser");
    browser.setAttribute("id", id);
    browser.setAttribute("remote", "true");
    browser.setAttribute("remoteType", "inference");
    browser.setAttribute("disableglobalhistory", "true");
    browser.setAttribute("type", "content");
    browser.setAttribute("src", url);

    ChromeUtils.addProfilerMarker(
      "EngineProcess",
      {},
      `Creating the "${id}" process`
    );
    doc.documentElement.appendChild(browser);

    const { promise, resolve } = Promise.withResolvers();

    // The engine parents must resolve themselves when they are ready.
    this[resolverName] = resolve;

    return promise;
  }

  /**
   * @returns {HiddenFrame}
   */
  static async #getHiddenFrame() {
    if (!EngineProcess.#hiddenFrame) {
      EngineProcess.#hiddenFrame = new lazy.HiddenFrame();
    }
    return EngineProcess.#hiddenFrame;
  }

  /**
   * Destroy the translations engine, and remove the hidden frame if no other
   * engines exist.
   */
  static destroyTranslationsEngine() {
    return this.#destroyEngine({
      id: "translations-engine-browser",
      keyName: "translationsEngineParent",
    });
  }

  /**
   * Destroy the ML engine, and remove the hidden frame if no other engines exist.
   */
  static destroyMLEngine() {
    return this.#destroyEngine({
      id: "ml-engine-browser",
      keyName: "mlEngineParent",
    });
  }

  /**
   * Destroy the specified engine and maybe the entire hidden frame as well if no engines
   * are remaining.
   */
  static async #destroyEngine({ id, keyName }) {
    ChromeUtils.addProfilerMarker(
      "EngineProcess",
      {},
      `Destroying the "${id}" engine`
    );

    let actorShutdown = this.forceActorShutdown(id, keyName);

    this[keyName] = null;

    const hiddenFrame = EngineProcess.#hiddenFrame;
    if (hiddenFrame && !this.translationsEngineParent && !this.mlEngineParent) {
      EngineProcess.#hiddenFrame = null;

      // Both actors are destroyed, also destroy the hidden frame.
      actorShutdown = actorShutdown.then(() => {
        // Double check a race condition that no new actors have been created during
        // shutdown.
        if (this.translationsEngineParent && this.mlEngineParent) {
          return;
        }
        if (!hiddenFrame) {
          return;
        }
        hiddenFrame.destroy();
        ChromeUtils.addProfilerMarker(
          "EngineProcess",
          {},
          `Removing the hidden frame`
        );
      });
    }

    // Infallibly resolve this promise even if there are errors.
    try {
      await actorShutdown;
    } catch (error) {
      console.error(error);
    }
  }

  /**
   * Shut down an actor and remove its <browser> element.
   *
   * @param {string} id
   * @param {string} keyName
   */
  static async forceActorShutdown(id, keyName) {
    const actorPromise = this[keyName];
    if (!actorPromise) {
      return;
    }

    let actor;
    try {
      actor = await actorPromise;
    } catch {
      // The actor failed to initialize, so it doesn't need to be shut down.
      return;
    }

    // Shut down the actor.
    try {
      await actor.forceShutdown();
    } catch (error) {
      console.error("Failed to shut down the actor " + id, error);
      return;
    }

    if (!EngineProcess.#hiddenFrame) {
      // The hidden frame was already removed.
      return;
    }

    // Remove the <brower> element.
    const chromeWindow = EngineProcess.#hiddenFrame.getWindow();
    const doc = chromeWindow.document;
    const element = doc.getElementById(id);
    if (!element) {
      console.error("Could not find the <browser> element for " + id);
      return;
    }
    element.remove();
  }
}

/**
 * Creates a new ML engine instance with the provided options.
 *
 * @param {object} options - Configuration options for the ML engine.
 * @param {?function(ProgressAndStatusCallbackParams):void} notificationsCallback A function to call to indicate notifications.
 * @returns {Promise<MLEngine>} - A promise that resolves to the ML engine instance.
 */
export async function createEngine(options, notificationsCallback = null) {
  const pipelineOptions = new PipelineOptions(options);
  const engineParent = await EngineProcess.getMLEngineParent();
  return engineParent.getEngine(pipelineOptions, notificationsCallback);
}
PK
!<.��)I
I
*modules/sessionstore/PrivacyFilter.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PrivacyLevel: "resource://gre/modules/sessionstore/PrivacyLevel.sys.mjs",
});

/**
 * A module that provides methods to filter various kinds of data collected
 * from a tab by the current privacy level as set by the user.
 */
export var PrivacyFilter = Object.freeze({
  /**
   * Filters the given (serialized) session storage |data| according to the
   * current privacy level and returns a new object containing only data that
   * we're allowed to store.
   *
   * @param data The session storage data as collected from a tab.
   * @return object
   */
  filterSessionStorageData(data) {
    let retval = {};

    if (lazy.PrivacyLevel.shouldSaveEverything()) {
      return data;
    }

    if (!lazy.PrivacyLevel.canSaveAnything()) {
      return null;
    }

    for (let host of Object.keys(data)) {
      if (lazy.PrivacyLevel.check(host)) {
        retval[host] = data[host];
      }
    }

    return Object.keys(retval).length ? retval : null;
  },

  /**
   * Filters the given (serialized) form |data| according to the current
   * privacy level and returns a new object containing only data that we're
   * allowed to store.
   *
   * @param data The form data as collected from a tab.
   * @return object
   */
  filterFormData(data) {
    if (lazy.PrivacyLevel.shouldSaveEverything()) {
      return Object.keys(data).length ? data : null;
    }

    if (!lazy.PrivacyLevel.canSaveAnything()) {
      return null;
    }

    // If the given form data object has an associated URL that we are not
    // allowed to store data for, bail out. We explicitly discard data for any
    // children as well even if storing data for those frames would be allowed.
    if (!data || (data.url && !lazy.PrivacyLevel.check(data.url))) {
      return null;
    }

    let retval = {};

    for (let key of Object.keys(data)) {
      if (key === "children") {
        let recurse = child => this.filterFormData(child);
        let children = data.children.map(recurse).filter(child => child);

        if (children.length) {
          retval.children = children;
        }
        // Only copy keys other than "children" if we have a valid URL in
        // data.url and we thus passed the privacy level check.
      } else if (data.url) {
        retval[key] = data[key];
      }
    }

    return Object.keys(retval).length ? retval : null;
  },

  /**
   * Removes any private windows and tabs from a given browser state object.
   *
   * @param browserState (object)
   *        The browser state for which we remove any private windows and tabs.
   *        The given object will be modified.
   */
  filterPrivateWindowsAndTabs(browserState) {
    // Remove private opened windows.
    for (let i = browserState.windows.length - 1; i >= 0; i--) {
      let win = browserState.windows[i];

      if (win.isPrivate) {
        browserState.windows.splice(i, 1);

        if (browserState.selectedWindow >= i) {
          browserState.selectedWindow--;
        }
      }
    }

    // Remove private closed windows.
    browserState._closedWindows = browserState._closedWindows.filter(
      win => !win.isPrivate
    );
  },
});
PK
!<
�V$$ actors/ContentMetaParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class ContentMetaParent extends JSWindowActorParent {
  receiveMessage(message) {
    if (message.name == "Meta:SetPageInfo") {
      let browser = this.manager.browsingContext.top.embedderElement;
      if (browser) {
        let event = new browser.ownerGlobal.CustomEvent("pageinfo", {
          bubbles: true,
          cancelable: false,
          detail: {
            url: message.data.url,
            description: message.data.description,
            previewImageURL: message.data.previewImageURL,
          },
        });
        browser.dispatchEvent(event);
      }
    }
  }
}
PK
!<e'C���modules/History.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Asynchronous API for managing history.
 *
 *
 * The API makes use of `PageInfo` and `VisitInfo` objects, defined as follows.
 *
 * A `PageInfo` object is any object that contains A SUBSET of the
 * following properties:
 * - guid: (string)
 *     The globally unique id of the page.
 * - url: (URL)
 *     or (nsIURI)
 *     or (string)
 *     The full URI of the page. Note that `PageInfo` values passed as
 *     argument may hold `nsIURI` or `string` values for property `url`,
 *     but `PageInfo` objects returned by this module always hold `URL`
 *     values.
 * - title: (string)
 *     The title associated with the page, if any.
 * - description: (string)
 *     The description of the page, if any.
 * - previewImageURL: (URL)
 *     or (nsIURI)
 *     or (string)
 *     The preview image URL of the page, if any.
 * - frecency: (number)
 *     The frecency of the page, if any.
 *     See https://developer.mozilla.org/en-US/docs/Mozilla/Tech/Places/Frecency_algorithm
 *     Note that this property may not be used to change the actualy frecency
 *     score of a page, only to retrieve it. In other words, any `frecency` field
 *     passed as argument to a function of this API will be ignored.
 *  - visits: (Array<VisitInfo>)
 *     All the visits for this page, if any.
 *  - annotations: (Map)
 *     A map containing key/value pairs of the annotations for this page, if any.
 *
 * See the documentation of individual methods to find out which properties
 * are required for `PageInfo` arguments or returned for `PageInfo` results.
 *
 * A `VisitInfo` object is any object that contains A SUBSET of the following
 * properties:
 * - date: (Date)
 *     The time the visit occurred.
 * - transition: (number)
 *     How the user reached the page. See constants `TRANSITIONS.*`
 *     for the possible transition types.
 * - referrer: (URL)
 *          or (nsIURI)
 *          or (string)
 *     The referring URI of this visit. Note that `VisitInfo` passed
 *     as argument may hold `nsIURI` or `string` values for property `referrer`,
 *     but `VisitInfo` objects returned by this module always hold `URL`
 *     values.
 * See the documentation of individual methods to find out which properties
 * are required for `VisitInfo` arguments or returned for `VisitInfo` results.
 *
 *
 *
 * Each successful operation notifies through the PlacesObservers. To listen to such
 * notifications you must register using
 * PlacesObservers `addListener` and `removeListener` methods.
 * @see PlacesObservers
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "asyncHistory",
  "@mozilla.org/browser/history;1",
  "mozIAsyncHistory"
);

/**
 * Whenever we update numerous pages, it is preferable to yield time to the main
 * thread every so often to avoid janking.
 * These constants determine the maximal number of notifications we
 * may emit before we yield.
 */
const ONRESULT_CHUNK_SIZE = 300;

// This constant determines the maximum number of remove pages before we cycle.
const REMOVE_PAGES_CHUNKLEN = 300;

export var History = Object.freeze({
  ANNOTATION_EXPIRE_NEVER: 4,
  // Constants for the type of annotation.
  ANNOTATION_TYPE_STRING: 3,
  ANNOTATION_TYPE_INT64: 5,

  /**
   * Fetch the available information for one page.
   *
   * @param {URL|nsIURI|string} guidOrURI: (string) or (URL, nsIURI or href)
   *      Either the full URI of the page or the GUID of the page.
   * @param {object} [options]
   *      An optional object whose properties describe options:
   *        - `includeVisits` (boolean) set this to true if `visits` in the
   *           PageInfo needs to contain VisitInfo in a reverse chronological order.
   *           By default, `visits` is undefined inside the returned `PageInfo`.
   *        - `includeMeta` (boolean) set this to true to fetch page meta fields,
   *           i.e. `description`, `site_name` and `preview_image_url`.
   *        - `includeAnnotations` (boolean) set this to true to fetch any
   *           annotations that are associated with the page.
   *
   * @return (Promise)
   *      A promise resolved once the operation is complete.
   * @resolves (PageInfo | null) If the page could be found, the information
   *      on that page.
   * @note the VisitInfo objects returned while fetching visits do not
   *       contain the property `referrer`.
   *       TODO: Add `referrer` to VisitInfo. See Bug #1365913.
   * @note the visits returned will not contain `TRANSITION_EMBED` visits.
   *
   * @throws (Error)
   *      If `guidOrURI` does not have the expected type or if it is a string
   *      that may be parsed neither as a valid URL nor as a valid GUID.
   */
  fetch(guidOrURI, options = {}) {
    // First, normalize to guid or string, and throw if not possible
    guidOrURI = lazy.PlacesUtils.normalizeToURLOrGUID(guidOrURI);

    // See if options exists and make sense
    if (!options || typeof options !== "object") {
      throw new TypeError("options should be an object and not null");
    }

    let hasIncludeVisits = "includeVisits" in options;
    if (hasIncludeVisits && typeof options.includeVisits !== "boolean") {
      throw new TypeError("includeVisits should be a boolean if exists");
    }

    let hasIncludeMeta = "includeMeta" in options;
    if (hasIncludeMeta && typeof options.includeMeta !== "boolean") {
      throw new TypeError("includeMeta should be a boolean if exists");
    }

    let hasIncludeAnnotations = "includeAnnotations" in options;
    if (
      hasIncludeAnnotations &&
      typeof options.includeAnnotations !== "boolean"
    ) {
      throw new TypeError("includeAnnotations should be a boolean if exists");
    }

    return lazy.PlacesUtils.promiseDBConnection().then(db =>
      fetch(db, guidOrURI, options)
    );
  },

  /**
   * Fetches all pages which have one or more of the specified annotations.
   *
   * @param annotations: An array of strings containing the annotation names to
   *                     find.
   * @return (Promise)
   *      A promise resolved once the operation is complete.
   * @resolves (Map)
   *      A Map containing the annotations, pages and their contents, e.g.
   *      Map("anno1" => [{page, content}, {page, content}]), "anno2" => ....);
   * @rejects (Error) XXX
   *      Rejects if the insert was unsuccessful.
   */
  fetchAnnotatedPages(annotations) {
    // See if options exists and make sense
    if (!annotations || !Array.isArray(annotations)) {
      throw new TypeError("annotations should be an Array and not null");
    }
    if (annotations.some(name => typeof name !== "string")) {
      throw new TypeError("all annotation values should be strings");
    }

    return lazy.PlacesUtils.promiseDBConnection().then(db =>
      fetchAnnotatedPages(db, annotations)
    );
  },

  /**
   * Fetch multiple pages.
   *
   * @param {string[]|nsIURI[]|URL[]} guidOrURIs: Array of href or URLs to fetch.
   * @returns {Promise}
   *   A promise resolved once the operation is complete.
   * @resolves {Map<string, string>} Map of PageInfos, keyed by the input info,
   *   either guid or href. We don't use nsIURI or URL as keys to avoid
   *   complexity, in all the cases the caller knows which objects is handling,
   *   and can unwrap them. Unknown input pages will have no entry in the Map.
   * @throws (Error)
   *   If input is invalid, for example not a valid GUID or URL.
   */
  fetchMany(guidOrURIs) {
    if (!Array.isArray(guidOrURIs)) {
      throw new TypeError("Input is not an array");
    }
    // First, normalize to guid or URL, throw if not possible
    guidOrURIs = guidOrURIs.map(v => lazy.PlacesUtils.normalizeToURLOrGUID(v));
    return lazy.PlacesUtils.promiseDBConnection().then(db =>
      fetchMany(db, guidOrURIs)
    );
  },

  /**
   * Adds a number of visits for a single page.
   *
   * Any change may be observed through PlacesObservers.
   *
   * @param pageInfo: (PageInfo)
   *      Information on a page. This `PageInfo` MUST contain
   *        - a property `url`, as specified by the definition of `PageInfo`.
   *        - a property `visits`, as specified by the definition of
   *          `PageInfo`, which MUST contain at least one visit.
   *      If a property `title` is provided, the title of the page
   *      is updated.
   *      If the `date` of a visit is not provided, it defaults
   *      to now.
   *      If the `transition` of a visit is not provided, it defaults to
   *      TRANSITION_LINK.
   *
   * @return (Promise)
   *      A promise resolved once the operation is complete.
   * @resolves (PageInfo)
   *      A PageInfo object populated with data after the insert is complete.
   * @rejects (Error)
   *      Rejects if the insert was unsuccessful.
   *
   * @throws (Error)
   *      If the `url` specified was for a protocol that should not be
   *      stored (@see nsNavHistory::CanAddURI).
   * @throws (Error)
   *      If `pageInfo` has an unexpected type.
   * @throws (Error)
   *      If `pageInfo` does not have a `url`.
   * @throws (Error)
   *      If `pageInfo` does not have a `visits` property or if the
   *      value of `visits` is ill-typed or is an empty array.
   * @throws (Error)
   *      If an element of `visits` has an invalid `date`.
   * @throws (Error)
   *      If an element of `visits` has an invalid `transition`.
   */
  insert(pageInfo) {
    let info = lazy.PlacesUtils.validatePageInfo(pageInfo);

    return lazy.PlacesUtils.withConnectionWrapper(
      "History.sys.mjs: insert",
      db => insert(db, info)
    );
  },

  /**
   * Adds a number of visits for a number of pages.
   *
   * Any change may be observed through PlacesObservers.
   *
   * @param pageInfos: (Array<PageInfo>)
   *      Information on a page. This `PageInfo` MUST contain
   *        - a property `url`, as specified by the definition of `PageInfo`.
   *        - a property `visits`, as specified by the definition of
   *          `PageInfo`, which MUST contain at least one visit.
   *      If a property `title` is provided, the title of the page
   *      is updated.
   *      If the `date` of a visit is not provided, it defaults
   *      to now.
   *      If the `transition` of a visit is not provided, it defaults to
   *      TRANSITION_LINK.
   * @param onResult: (function(PageInfo))
   *      A callback invoked for each page inserted.
   * @param onError: (function(PageInfo))
   *      A callback invoked for each page which generated an error
   *      when an insert was attempted.
   *
   * @return (Promise)
   *      A promise resolved once the operation is complete.
   * @resolves (null)
   * @rejects (Error)
   *      Rejects if all of the inserts were unsuccessful.
   *
   * @throws (Error)
   *      If the `url` specified was for a protocol that should not be
   *      stored (@see nsNavHistory::CanAddURI).
   * @throws (Error)
   *      If `pageInfos` has an unexpected type.
   * @throws (Error)
   *      If a `pageInfo` does not have a `url`.
   * @throws (Error)
   *      If a `PageInfo` does not have a `visits` property or if the
   *      value of `visits` is ill-typed or is an empty array.
   * @throws (Error)
   *      If an element of `visits` has an invalid `date`.
   * @throws (Error)
   *      If an element of `visits` has an invalid `transition`.
   */
  insertMany(pageInfos, onResult, onError) {
    let infos = [];

    if (!Array.isArray(pageInfos)) {
      throw new TypeError("pageInfos must be an array");
    }
    if (!pageInfos.length) {
      throw new TypeError("pageInfos may not be an empty array");
    }

    if (onResult && typeof onResult != "function") {
      throw new TypeError(`onResult: ${onResult} is not a valid function`);
    }
    if (onError && typeof onError != "function") {
      throw new TypeError(`onError: ${onError} is not a valid function`);
    }

    for (let pageInfo of pageInfos) {
      let info = lazy.PlacesUtils.validatePageInfo(pageInfo);
      infos.push(info);
    }

    return lazy.PlacesUtils.withConnectionWrapper(
      "History.sys.mjs: insertMany",
      db => insertMany(db, infos, onResult, onError)
    );
  },

  /**
   * Remove pages from the database.
   *
   * Any change may be observed through PlacesObservers.
   *
   *
   * @param page: (URL or nsIURI)
   *      The full URI of the page.
   *             or (string)
   *      Either the full URI of the page or the GUID of the page.
   *             or (Array<URL|nsIURI|string>)
   *      An array of the above, to batch requests.
   * @param onResult: (function(PageInfo))
   *      A callback invoked for each page found.
   *
   * @return (Promise)
   *      A promise resolved once the operation is complete.
   * @resolve (bool)
   *      `true` if at least one page was removed, `false` otherwise.
   * @throws (TypeError)
   *       If `pages` has an unexpected type or if a string provided
   *       is neither a valid GUID nor a valid URI or if `pages`
   *       is an empty array.
   */
  remove(pages, onResult = null) {
    // Normalize and type-check arguments
    if (Array.isArray(pages)) {
      if (!pages.length) {
        throw new TypeError("Expected at least one page");
      }
    } else {
      pages = [pages];
    }

    let guids = [];
    let urls = [];
    for (let page of pages) {
      // Normalize to URL or GUID, or throw if `page` cannot
      // be normalized.
      let normalized = lazy.PlacesUtils.normalizeToURLOrGUID(page);
      if (typeof normalized === "string") {
        guids.push(normalized);
      } else {
        urls.push(normalized.href);
      }
    }

    // At this stage, we know that either `guids` is not-empty
    // or `urls` is not-empty.

    if (onResult && typeof onResult != "function") {
      throw new TypeError("Invalid function: " + onResult);
    }

    return (async function () {
      let removedPages = false;
      let count = 0;
      while (guids.length || urls.length) {
        if (count && count % 2 == 0) {
          // Every few cycles, yield time back to the main
          // thread to avoid jank.
          await Promise.resolve();
        }
        count++;
        let guidsSlice = guids.splice(0, REMOVE_PAGES_CHUNKLEN);
        let urlsSlice = [];
        if (guidsSlice.length < REMOVE_PAGES_CHUNKLEN) {
          urlsSlice = urls.splice(0, REMOVE_PAGES_CHUNKLEN - guidsSlice.length);
        }

        let pages = { guids: guidsSlice, urls: urlsSlice };

        let result = await lazy.PlacesUtils.withConnectionWrapper(
          "History.sys.mjs: remove",
          db => remove(db, pages, onResult)
        );

        removedPages = removedPages || result;
      }
      return removedPages;
    })();
  },

  /**
   * Remove visits matching specific characteristics.
   *
   * Any change may be observed through PlacesObservers.
   *
   * @param filter: (object)
   *      The `object` may contain some of the following
   *      properties:
   *          - beginDate: (Date) Remove visits that have
   *                been added since this date (inclusive).
   *          - endDate: (Date) Remove visits that have
   *                been added before this date (inclusive).
   *          - limit: (Number) Limit the number of visits
   *                we remove to this number
   *          - url: (URL) Only remove visits to this URL
   *          - transition: (Integer)
   *                The type of the transition (see TRANSITIONS below)
   *      If both `beginDate` and `endDate` are specified,
   *      visits between `beginDate` (inclusive) and `end`
   *      (inclusive) are removed.
   *
   * @param onResult: (function(VisitInfo), [optional])
   *     A callback invoked for each visit found and removed.
   *     Note that the referrer property of `VisitInfo`
   *     is NOT populated.
   *
   * @return (Promise)
   * @resolve (bool)
   *      `true` if at least one visit was removed, `false`
   *      otherwise.
   * @throws (TypeError)
   *      If `filter` does not have the expected type, in
   *      particular if the `object` is empty.
   */
  removeVisitsByFilter(filter, onResult = null) {
    if (!filter || typeof filter != "object") {
      throw new TypeError("Expected a filter");
    }

    let hasBeginDate = "beginDate" in filter;
    let hasEndDate = "endDate" in filter;
    let hasURL = "url" in filter;
    let hasLimit = "limit" in filter;
    let hasTransition = "transition" in filter;
    if (hasBeginDate) {
      this.ensureDate(filter.beginDate);
    }
    if (hasEndDate) {
      this.ensureDate(filter.endDate);
    }
    if (hasBeginDate && hasEndDate && filter.beginDate > filter.endDate) {
      throw new TypeError("`beginDate` should be at least as old as `endDate`");
    }
    if (hasTransition && !this.isValidTransition(filter.transition)) {
      throw new TypeError("`transition` should be valid");
    }
    if (
      !hasBeginDate &&
      !hasEndDate &&
      !hasURL &&
      !hasLimit &&
      !hasTransition
    ) {
      throw new TypeError("Expected a non-empty filter");
    }

    if (
      hasURL &&
      !URL.isInstance(filter.url) &&
      typeof filter.url != "string" &&
      !(filter.url instanceof Ci.nsIURI)
    ) {
      throw new TypeError("Expected a valid URL for `url`");
    }

    if (
      hasLimit &&
      (typeof filter.limit != "number" ||
        filter.limit <= 0 ||
        !Number.isInteger(filter.limit))
    ) {
      throw new TypeError("Expected a non-zero positive integer as a limit");
    }

    if (onResult && typeof onResult != "function") {
      throw new TypeError("Invalid function: " + onResult);
    }

    return lazy.PlacesUtils.withConnectionWrapper(
      "History.sys.mjs: removeVisitsByFilter",
      db => removeVisitsByFilter(db, filter, onResult)
    );
  },

  /**
   * Remove pages from the database based on a filter.
   *
   * Any change may be observed through PlacesObservers
   *
   *
   * @param filter: An object containing a non empty subset of the following
   * properties:
   * - host: (string)
   *     Hostname with or without subhost. Examples:
   *       "mozilla.org" removes pages from mozilla.org but not its subdomains
   *       ".mozilla.org" removes pages from mozilla.org and its subdomains
   *       "." removes local files
   * - beginDate: (Date)
   *     The first time the page was visited (inclusive)
   * - endDate: (Date)
   *     The last time the page was visited (inclusive)
   * @param [optional] onResult: (function(PageInfo))
   *      A callback invoked for each page found.
   *
   * @note This removes pages with at least one visit inside the timeframe.
   *       Any visits outside the timeframe will also be removed with the page.
   * @return (Promise)
   *      A promise resolved once the operation is complete.
   * @resolve (bool)
   *      `true` if at least one page was removed, `false` otherwise.
   * @throws (TypeError)
   *       if `filter` does not have the expected type, in particular
   *       if the `object` is empty, or its components do not satisfy the
   *       criteria given above
   */
  removeByFilter(filter, onResult) {
    if (!filter || typeof filter !== "object") {
      throw new TypeError("Expected a filter object");
    }

    let hasHost = filter.host;
    if (hasHost) {
      if (typeof filter.host !== "string") {
        throw new TypeError("`host` should be a string");
      }
      filter.host = filter.host.toLowerCase();
      if (filter.host.length > 1 && filter.host.lastIndexOf(".") == 0) {
        // The input contains only an initial period, thus it may be a
        // wildcarded local host, like ".localhost". Ideally the consumer should
        // pass just "localhost", because there is no concept of subhosts for
        // it, but we are being more lenient to allow for simpler input.
        // Anyway, in this case we remove the wildcard to avoid clearing too
        // much if the consumer wrongly passes in things like ".com".
        filter.host = filter.host.slice(1);
      }
    }

    let hasBeginDate = "beginDate" in filter;
    if (hasBeginDate) {
      this.ensureDate(filter.beginDate);
    }

    let hasEndDate = "endDate" in filter;
    if (hasEndDate) {
      this.ensureDate(filter.endDate);
    }

    if (hasBeginDate && hasEndDate && filter.beginDate > filter.endDate) {
      throw new TypeError("`beginDate` should be at least as old as `endDate`");
    }

    if (!hasBeginDate && !hasEndDate && !hasHost) {
      throw new TypeError("Expected a non-empty filter");
    }

    // Check the host format.
    // Either it has no dots, or has multiple dots, or it's a single dot char.
    if (
      hasHost &&
      (!/^(\.?([.a-z0-9-]+\.[a-z0-9-]+)?|[a-z0-9-]+)\.?$/.test(filter.host) ||
        filter.host.includes(".."))
    ) {
      throw new TypeError(
        "Expected well formed hostname string for `host` with atmost 1 wildcard."
      );
    }

    if (onResult && typeof onResult != "function") {
      throw new TypeError("Invalid function: " + onResult);
    }

    return lazy.PlacesUtils.withConnectionWrapper(
      "History.sys.mjs: removeByFilter",
      db => removeByFilter(db, filter, onResult)
    );
  },

  /**
   * Determine if a page has been visited.
   *
   * @param guidOrURI: (string) or (URL, nsIURI or href)
   *      Either the full URI of the page or the GUID of the page.
   * @return (Promise)
   *      A promise resolved once the operation is complete.
   * @resolve (bool)
   *      `true` if the page has been visited, `false` otherwise.
   * @throws (Error)
   *      If `guidOrURI` has an unexpected type or if a string provided
   *      is neither not a valid GUID nor a valid URI.
   */
  hasVisits(guidOrURI) {
    // Quick fallback to the cpp version.
    if (guidOrURI instanceof Ci.nsIURI) {
      return new Promise(resolve => {
        lazy.asyncHistory.isURIVisited(guidOrURI, (aURI, aIsVisited) => {
          resolve(aIsVisited);
        });
      });
    }

    guidOrURI = lazy.PlacesUtils.normalizeToURLOrGUID(guidOrURI);
    let isGuid = typeof guidOrURI == "string";
    let sqlFragment = isGuid
      ? "guid = :val"
      : "url_hash = hash(:val) AND url = :val ";

    return lazy.PlacesUtils.promiseDBConnection().then(async db => {
      let rows = await db.executeCached(
        `SELECT 1 FROM moz_places
                                         WHERE ${sqlFragment}
                                         AND last_visit_date NOTNULL`,
        { val: isGuid ? guidOrURI : guidOrURI.href }
      );
      return !!rows.length;
    });
  },

  /**
   * Clear all history.
   *
   * @return (Promise)
   *      A promise resolved once the operation is complete.
   */
  clear() {
    return lazy.PlacesUtils.withConnectionWrapper(
      "History.sys.mjs: clear",
      clear
    );
  },

  /**
   * Is a value a valid transition type?
   *
   * @param transition: (String)
   * @return (Boolean)
   */
  isValidTransition(transition) {
    return Object.values(History.TRANSITIONS).includes(transition);
  },

  /**
   * Throw if an object is not a Date object.
   */
  ensureDate(arg) {
    if (
      !arg ||
      typeof arg != "object" ||
      arg.constructor.name != "Date" ||
      isNaN(arg)
    ) {
      throw new TypeError("Expected a valid Date, got " + arg);
    }
  },

  /**
   * Update information for a page.
   *
   * Currently, it supports updating the description, preview image URL and annotations
   * for a page, any other fields will be ignored.
   *
   * Note that this function will ignore the update if the target page has not
   * yet been stored in the database. `History.fetch` could be used to check
   * whether the page and its meta information exist or not. Beware that
   * fetch&update might fail as they are not executed in a single transaction.
   *
   * @param pageInfo: (PageInfo)
   *      pageInfo must contain a URL of the target page. It will be ignored
   *      if a valid page `guid` is also provided.
   *
   *      If a property `description` is provided, the description of the
   *      page is updated. Note that:
   *      1). An empty string or null `description` will clear the existing
   *          value in the database.
   *      2). Descriptions longer than DB_DESCRIPTION_LENGTH_MAX will be
   *          truncated.
   *
   *      If a property `siteName` is provided, the site name of the
   *      page is updated. Note that:
   *      1). An empty string or null `siteName` will clear the existing
   *          value in the database.
   *      2). Descriptions longer than DB_SITENAME_LENGTH_MAX will be
   *          truncated.
   *
   *      If a property `previewImageURL` is provided, the preview image
   *      URL of the page is updated. Note that:
   *      1). A null `previewImageURL` will clear the existing value in the
   *          database.
   *      2). It throws if its length is greater than DB_URL_LENGTH_MAX
   *          defined in PlacesUtils.sys.mjs.
   *
   *      If a property `annotations` is provided, the annotations will be
   *      updated. Note that:
   *      1). It should be a Map containing key/value pairs to be updated.
   *      2). If the value is falsy, the annotation will be removed.
   *      3). If the value is non-falsy, the annotation will be added or updated.
   *      For `annotations` the keys must all be strings, the values should be
   *      Boolean, Number or Strings. null and undefined are supported as falsy values.
   *
   * @return (Promise)
   *      A promise resolved once the update is complete.
   * @rejects (Error)
   *      Rejects if the update was unsuccessful.
   *
   * @throws (Error)
   *      If `pageInfo` has an unexpected type.
   * @throws (Error)
   *      If `pageInfo` has an invalid `url` or an invalid `guid`.
   * @throws (Error)
   *      If `pageInfo` has neither `description` nor `previewImageURL`.
   * @throws (Error)
   *      If the length of `pageInfo.previewImageURL` is greater than
   *      DB_URL_LENGTH_MAX defined in PlacesUtils.sys.mjs.
   */
  update(pageInfo) {
    let info = lazy.PlacesUtils.validatePageInfo(pageInfo, false);

    if (
      info.description === undefined &&
      info.siteName === undefined &&
      info.previewImageURL === undefined &&
      info.annotations === undefined
    ) {
      throw new TypeError(
        "pageInfo object must at least have either a description, siteName, previewImageURL or annotations property."
      );
    }

    return lazy.PlacesUtils.withConnectionWrapper(
      "History.sys.mjs: update",
      db => update(db, info)
    );
  },

  /**
   * Possible values for the `transition` property of `VisitInfo`
   * objects.
   */

  TRANSITIONS: {
    /**
     * The user followed a link and got a new toplevel window.
     */
    LINK: Ci.nsINavHistoryService.TRANSITION_LINK,

    /**
     * The user typed the page's URL in the URL bar or selected it from
     * URL bar autocomplete results, clicked on it from a history query
     * (from the History sidebar, History menu, or history query in the
     * personal toolbar or Places organizer.
     */
    TYPED: Ci.nsINavHistoryService.TRANSITION_TYPED,

    /**
     * The user followed a bookmark to get to the page.
     */
    BOOKMARK: Ci.nsINavHistoryService.TRANSITION_BOOKMARK,

    /**
     * Some inner content is loaded. This is true of all images on a
     * page, and the contents of the iframe. It is also true of any
     * content in a frame if the user did not explicitly follow a link
     * to get there.
     */
    EMBED: Ci.nsINavHistoryService.TRANSITION_EMBED,

    /**
     * Set when the transition was a permanent redirect.
     */
    REDIRECT_PERMANENT: Ci.nsINavHistoryService.TRANSITION_REDIRECT_PERMANENT,

    /**
     * Set when the transition was a temporary redirect.
     */
    REDIRECT_TEMPORARY: Ci.nsINavHistoryService.TRANSITION_REDIRECT_TEMPORARY,

    /**
     * Set when the transition is a download.
     */
    DOWNLOAD: Ci.nsINavHistoryService.TRANSITION_DOWNLOAD,

    /**
     * The user followed a link and got a visit in a frame.
     */
    FRAMED_LINK: Ci.nsINavHistoryService.TRANSITION_FRAMED_LINK,

    /**
     * The user reloaded a page.
     */
    RELOAD: Ci.nsINavHistoryService.TRANSITION_RELOAD,
  },
});

/**
 * Convert a PageInfo object into the format expected by updatePlaces.
 *
 * Note: this assumes that the PageInfo object has already been validated
 * via PlacesUtils.validatePageInfo.
 *
 * @param pageInfo: (PageInfo)
 * @return (info)
 */
function convertForUpdatePlaces(pageInfo) {
  let info = {
    guid: pageInfo.guid,
    uri: lazy.PlacesUtils.toURI(pageInfo.url),
    title: pageInfo.title,
    visits: [],
  };

  for (let inVisit of pageInfo.visits) {
    let visit = {
      visitDate: lazy.PlacesUtils.toPRTime(inVisit.date),
      transitionType: inVisit.transition,
      referrerURI: inVisit.referrer
        ? lazy.PlacesUtils.toURI(inVisit.referrer)
        : undefined,
    };
    info.visits.push(visit);
  }
  return info;
}

// Inner implementation of History.clear().
var clear = async function (db) {
  await db.executeTransaction(async function () {
    // Since all metadata must be removed, remove it before pages, to save on
    // foreign key delete cascading.
    await db.execute("DELETE FROM moz_places_metadata");

    // Remove all non-bookmarked places entries first, this will speed up the
    // triggers work.
    await db.execute(`DELETE FROM moz_places WHERE foreign_count = 0`);
    await db.execute(`DELETE FROM moz_updateoriginsdelete_temp`);

    // Expire orphan icons.
    await db.executeCached(`DELETE FROM moz_pages_w_icons
                            WHERE page_url_hash NOT IN (SELECT url_hash FROM moz_places)`);
    await removeOrphanIcons(db);

    // Expire annotations.
    await db.execute(`DELETE FROM moz_annos WHERE NOT EXISTS (
                        SELECT 1 FROM moz_places WHERE id = place_id
                      )`);

    // Expire inputhistory.
    await db.execute(`DELETE FROM moz_inputhistory WHERE place_id IN (
                        SELECT i.place_id FROM moz_inputhistory i
                        LEFT JOIN moz_places h ON h.id = i.place_id
                        WHERE h.id IS NULL)`);

    // Remove all history.
    await db.execute("DELETE FROM moz_historyvisits");
  });

  PlacesObservers.notifyListeners([new PlacesHistoryCleared()]);
};

/**
 * Clean up pages whose history has been modified, by either
 * removing them entirely (if they are marked for removal,
 * typically because all visits have been removed and there
 * are no more foreign keys such as bookmarks) or updating
 * their frecency (otherwise).
 *
 * @param db: (Sqlite connection)
 *      The database.
 * @param pages: (Array of objects)
 *      Pages that have been touched and that need cleaning up.
 *      Each object should have the following properties:
 *          - id: (number) The `moz_places` identifier for the place.
 *          - hasVisits: (boolean) If `true`, there remains at least one
 *              visit to this page, so the page should be kept and its
 *              frecency updated.
 *          - hasForeign: (boolean) If `true`, the page has at least
 *              one foreign reference (i.e. a bookmark), so the page should
 *              be kept and its frecency updated.
 * @return (Promise)
 */
var cleanupPages = async function (db, pages) {
  let pagesToRemove = pages.filter(p => !p.hasForeign && !p.hasVisits);
  if (!pagesToRemove.length) {
    return;
  }

  // Note, we are already in a transaction, since callers create it.
  // Check relations regardless, to avoid creating orphans in case of
  // async race conditions.
  for (let chunk of lazy.PlacesUtils.chunkArray(
    pagesToRemove,
    db.variableLimit
  )) {
    let idsToRemove = chunk.map(p => p.id);
    await db.execute(
      `DELETE FROM moz_places
       WHERE id IN ( ${lazy.PlacesUtils.sqlBindPlaceholders(idsToRemove)} )
         AND foreign_count = 0 AND last_visit_date ISNULL`,
      idsToRemove
    );

    // Expire orphans.
    let hashesToRemove = chunk.map(p => p.hash);
    await db.executeCached(
      `DELETE FROM moz_pages_w_icons
       WHERE page_url_hash IN (${lazy.PlacesUtils.sqlBindPlaceholders(
         hashesToRemove
       )})`,
      hashesToRemove
    );

    await db.execute(
      `DELETE FROM moz_annos
       WHERE place_id IN ( ${lazy.PlacesUtils.sqlBindPlaceholders(
         idsToRemove
       )} )`,
      idsToRemove
    );
    await db.execute(
      `DELETE FROM moz_inputhistory
       WHERE place_id IN ( ${lazy.PlacesUtils.sqlBindPlaceholders(
         idsToRemove
       )} )`,
      idsToRemove
    );
  }
  // Hosts accumulated during the places delete are updated through a trigger
  // (see nsPlacesTriggers.h).
  await db.executeCached(`DELETE FROM moz_updateoriginsdelete_temp`);

  await removeOrphanIcons(db);
};

/**
 * Remove icons whose origin is not in moz_origins, unless referenced.
 * @param db: (Sqlite connection)
 *      The database.
 */
function removeOrphanIcons(db) {
  return db.executeCached(`
    DELETE FROM moz_icons WHERE id IN (
      SELECT id FROM moz_icons WHERE root = 0
      UNION ALL
      SELECT id FROM moz_icons
      WHERE root = 1
        AND get_host_and_port(icon_url) NOT IN (SELECT host FROM moz_origins)
        AND fixup_url(get_host_and_port(icon_url)) NOT IN (SELECT host FROM moz_origins)
      EXCEPT
      SELECT icon_id FROM moz_icons_to_pages
    )`);
}

/**
 * Notify observers that pages have been removed/updated.
 *
 * @param db: (Sqlite connection)
 *      The database.
 * @param pages: (Array of objects)
 *      Pages that have been touched and that need cleaning up.
 *      Each object should have the following properties:
 *          - id: (number) The `moz_places` identifier for the place.
 *          - hasVisits: (boolean) If `true`, there remains at least one
 *              visit to this page, so the page should be kept and its
 *              frecency updated.
 *          - hasForeign: (boolean) If `true`, the page has at least
 *              one foreign reference (i.e. a bookmark), so the page should
 *              be kept and its frecency updated.
 * @param transitionType: (Number)
 *      Set to a valid TRANSITIONS value to indicate all transitions of a
 *      certain type have been removed, otherwise defaults to 0 (unknown value).
 * @return (Promise)
 */
var notifyCleanup = async function (db, pages, transitionType = 0) {
  const notifications = [];

  for (let page of pages) {
    const isRemovedFromStore = !page.hasVisits && !page.hasForeign;
    notifications.push(
      new PlacesVisitRemoved({
        url: page.url.href,
        pageGuid: page.guid,
        reason: PlacesVisitRemoved.REASON_DELETED,
        transitionType,
        isRemovedFromStore,
        isPartialVisistsRemoval: !isRemovedFromStore && page.hasVisits > 0,
      })
    );
  }

  PlacesObservers.notifyListeners(notifications);
};

/**
 * Notify an `onResult` callback of a set of operations
 * that just took place.
 *
 * @param data: (Array)
 *      The data to send to the callback.
 * @param onResult: (function [optional])
 *      If provided, call `onResult` with `data[0]`, `data[1]`, etc.
 *      Otherwise, do nothing.
 */
var notifyOnResult = async function (data, onResult) {
  if (!onResult) {
    return;
  }
  let notifiedCount = 0;
  for (let info of data) {
    try {
      onResult(info);
    } catch (ex) {
      // Errors should be reported but should not stop the operation.
      Promise.reject(ex);
    }
    if (++notifiedCount % ONRESULT_CHUNK_SIZE == 0) {
      // Every few notifications, yield time back to the main
      // thread to avoid jank.
      await Promise.resolve();
    }
  }
};

// Inner implementation of History.fetch.
var fetch = async function (db, guidOrURL, options) {
  let whereClauseFragment = "";
  let params = {};
  if (URL.isInstance(guidOrURL)) {
    whereClauseFragment = "WHERE h.url_hash = hash(:url) AND h.url = :url";
    params.url = guidOrURL.href;
  } else {
    whereClauseFragment = "WHERE h.guid = :guid";
    params.guid = guidOrURL;
  }

  let visitSelectionFragment = "";
  let joinFragment = "";
  let visitOrderFragment = "";
  if (options.includeVisits) {
    visitSelectionFragment = ", v.visit_date, v.visit_type";
    joinFragment = "JOIN moz_historyvisits v ON h.id = v.place_id";
    visitOrderFragment = "ORDER BY v.visit_date DESC";
  }

  let pageMetaSelectionFragment = "";
  if (options.includeMeta) {
    pageMetaSelectionFragment = ", description, site_name, preview_image_url";
  }

  let query = `SELECT h.id, guid, url, title, frecency
               ${pageMetaSelectionFragment} ${visitSelectionFragment}
               FROM moz_places h ${joinFragment}
               ${whereClauseFragment}
               ${visitOrderFragment}`;
  let pageInfo = null;
  let placeId = null;
  await db.executeCached(query, params, row => {
    if (pageInfo === null) {
      // This means we're on the first row, so we need to get all the page info.
      pageInfo = {
        guid: row.getResultByName("guid"),
        url: new URL(row.getResultByName("url")),
        frecency: row.getResultByName("frecency"),
        title: row.getResultByName("title") || "",
      };
      placeId = row.getResultByName("id");
    }
    if (options.includeMeta) {
      pageInfo.description = row.getResultByName("description") || "";
      pageInfo.siteName = row.getResultByName("site_name") || "";
      let previewImageURL = row.getResultByName("preview_image_url");
      pageInfo.previewImageURL = previewImageURL
        ? new URL(previewImageURL)
        : null;
    }
    if (options.includeVisits) {
      // On every row (not just the first), we need to collect visit data.
      if (!("visits" in pageInfo)) {
        pageInfo.visits = [];
      }
      let date = lazy.PlacesUtils.toDate(row.getResultByName("visit_date"));
      let transition = row.getResultByName("visit_type");

      // TODO: Bug #1365913 add referrer URL to the `VisitInfo` data as well.
      pageInfo.visits.push({ date, transition });
    }
  });

  // Only try to get annotations if requested, and if there's an actual page found.
  if (pageInfo && options.includeAnnotations) {
    let rows = await db.executeCached(
      `
      SELECT n.name, a.content FROM moz_anno_attributes n
      JOIN moz_annos a ON n.id = a.anno_attribute_id
      WHERE a.place_id = :placeId
    `,
      { placeId }
    );

    pageInfo.annotations = new Map(
      rows.map(row => [
        row.getResultByName("name"),
        row.getResultByName("content"),
      ])
    );
  }
  return pageInfo;
};

// Inner implementation of History.fetchAnnotatedPages.
var fetchAnnotatedPages = async function (db, annotations) {
  let result = new Map();
  let rows = await db.execute(
    `
    SELECT n.name, h.url, a.content FROM moz_anno_attributes n
    JOIN moz_annos a ON n.id = a.anno_attribute_id
    JOIN moz_places h ON h.id = a.place_id
    WHERE n.name IN (${new Array(annotations.length).fill("?").join(",")})
  `,
    annotations
  );

  for (let row of rows) {
    let uri;
    try {
      uri = new URL(row.getResultByName("url"));
    } catch (ex) {
      console.error("Invalid URL read from database in fetchAnnotatedPages");
      continue;
    }

    let anno = {
      uri,
      content: row.getResultByName("content"),
    };
    let annoName = row.getResultByName("name");
    let pageAnnos = result.get(annoName);
    if (!pageAnnos) {
      pageAnnos = [];
      result.set(annoName, pageAnnos);
    }
    pageAnnos.push(anno);
  }

  return result;
};

// Inner implementation of History.fetchMany.
var fetchMany = async function (db, guidOrURLs) {
  let resultsMap = new Map();
  for (let chunk of lazy.PlacesUtils.chunkArray(guidOrURLs, db.variableLimit)) {
    let urls = [];
    let guids = [];
    for (let v of chunk) {
      if (URL.isInstance(v)) {
        urls.push(v);
      } else {
        guids.push(v);
      }
    }
    let wheres = [];
    let params = [];
    if (urls.length) {
      wheres.push(`
        (
          url_hash IN(${lazy.PlacesUtils.sqlBindPlaceholders(
            urls,
            "hash(",
            ")"
          )}) AND
          url IN(${lazy.PlacesUtils.sqlBindPlaceholders(urls)})
        )`);
      let hrefs = urls.map(u => u.href);
      params = [...params, ...hrefs, ...hrefs];
    }
    if (guids.length) {
      wheres.push(`guid IN(${lazy.PlacesUtils.sqlBindPlaceholders(guids)})`);
      params = [...params, ...guids];
    }

    let rows = await db.executeCached(
      `
      SELECT h.id, guid, url, title, frecency
      FROM moz_places h
      WHERE ${wheres.join(" OR ")}
    `,
      params
    );
    for (let row of rows) {
      let pageInfo = {
        guid: row.getResultByName("guid"),
        url: new URL(row.getResultByName("url")),
        frecency: row.getResultByName("frecency"),
        title: row.getResultByName("title") || "",
      };
      if (guidOrURLs.includes(pageInfo.guid)) {
        resultsMap.set(pageInfo.guid, pageInfo);
      } else {
        resultsMap.set(pageInfo.url.href, pageInfo);
      }
    }
  }
  return resultsMap;
};

// Inner implementation of History.removeVisitsByFilter.
var removeVisitsByFilter = async function (db, filter, onResult = null) {
  // 1. Determine visits that took place during the interval.  Note
  // that the database uses microseconds, while JS uses milliseconds,
  // so we need to *1000 one way and /1000 the other way.
  let conditions = [];
  let args = {};
  let transition = -1;
  if ("beginDate" in filter) {
    conditions.push("v.visit_date >= :begin * 1000");
    args.begin = Number(filter.beginDate);
  }
  if ("endDate" in filter) {
    conditions.push("v.visit_date <= :end * 1000");
    args.end = Number(filter.endDate);
  }
  if ("limit" in filter) {
    args.limit = Number(filter.limit);
  }
  if ("transition" in filter) {
    conditions.push("v.visit_type = :transition");
    args.transition = filter.transition;
    transition = filter.transition;
  }

  let optionalJoin = "";
  if ("url" in filter) {
    let url = filter.url;
    if (url instanceof Ci.nsIURI) {
      url = filter.url.spec;
    } else {
      url = new URL(url).href;
    }
    optionalJoin = `JOIN moz_places h ON h.id = v.place_id`;
    conditions.push("h.url_hash = hash(:url)", "h.url = :url");
    args.url = url;
  }

  let visitsToRemove = [];
  let pagesToInspect = new Set();
  let onResultData = onResult ? [] : null;

  await db.executeCached(
    `SELECT v.id, place_id, visit_date / 1000 AS date, visit_type FROM moz_historyvisits v
             ${optionalJoin}
             WHERE ${conditions.join(" AND ")}${
      args.limit ? " LIMIT :limit" : ""
    }`,
    args,
    row => {
      let id = row.getResultByName("id");
      let place_id = row.getResultByName("place_id");
      visitsToRemove.push(id);
      pagesToInspect.add(place_id);

      if (onResult) {
        onResultData.push({
          date: new Date(row.getResultByName("date")),
          transition: row.getResultByName("visit_type"),
        });
      }
    }
  );

  if (!visitsToRemove.length) {
    // Nothing to do
    return false;
  }

  let pages = [];
  await db.executeTransaction(async function () {
    // 2. Remove all offending visits.
    for (let chunk of lazy.PlacesUtils.chunkArray(
      visitsToRemove,
      db.variableLimit
    )) {
      await db.execute(
        `DELETE FROM moz_historyvisits
         WHERE id IN (${lazy.PlacesUtils.sqlBindPlaceholders(chunk)})`,
        chunk
      );
    }

    // 3. Find out which pages have been orphaned
    for (let chunk of lazy.PlacesUtils.chunkArray(
      [...pagesToInspect],
      db.variableLimit
    )) {
      await db.execute(
        `SELECT id, url, url_hash, guid,
          (foreign_count != 0) AS has_foreign,
          (last_visit_date NOTNULL) as has_visits
         FROM moz_places
         WHERE id IN (${lazy.PlacesUtils.sqlBindPlaceholders(chunk)})`,
        chunk,
        row => {
          let page = {
            id: row.getResultByName("id"),
            guid: row.getResultByName("guid"),
            hasForeign: row.getResultByName("has_foreign"),
            hasVisits: row.getResultByName("has_visits"),
            url: new URL(row.getResultByName("url")),
            hash: row.getResultByName("url_hash"),
          };
          pages.push(page);
        }
      );
    }

    // 4. Clean up and notify
    await cleanupPages(db, pages);
  });

  notifyCleanup(db, pages, transition);
  notifyOnResult(onResultData, onResult); // don't wait

  return !!visitsToRemove.length;
};

// Inner implementation of History.removeByFilter
var removeByFilter = async function (db, filter, onResult = null) {
  // 1. Create fragment for date filtration
  let dateFilterSQLFragment = "";
  let conditions = [];
  let params = {};
  if ("beginDate" in filter) {
    conditions.push("v.visit_date >= :begin");
    params.begin = lazy.PlacesUtils.toPRTime(filter.beginDate);
  }
  if ("endDate" in filter) {
    conditions.push("v.visit_date <= :end");
    params.end = lazy.PlacesUtils.toPRTime(filter.endDate);
  }

  if (conditions.length !== 0) {
    dateFilterSQLFragment = `EXISTS
         (SELECT id FROM moz_historyvisits v WHERE v.place_id = h.id AND
         ${conditions.join(" AND ")}
         LIMIT 1)`;
  }

  // 2. Create fragment for host and subhost filtering
  let hostFilterSQLFragment = "";
  if (filter.host) {
    // There are four cases that we need to consider:
    // mozilla.org, .mozilla.org, localhost, and local files
    let revHost = filter.host.split("").reverse().join("");
    if (filter.host == ".") {
      // Local files.
      hostFilterSQLFragment = `h.rev_host = :revHost`;
    } else if (filter.host.startsWith(".")) {
      // Remove the subhost wildcard.
      revHost = revHost.slice(0, -1);
      hostFilterSQLFragment = `h.rev_host between :revHost || "." and :revHost || "/"`;
    } else {
      // This covers non-wildcarded hosts (e.g.: mozilla.org, localhost)
      hostFilterSQLFragment = `h.rev_host = :revHost || "."`;
    }
    params.revHost = revHost;
  }

  // 3. Find out what needs to be removed
  let fragmentArray = [hostFilterSQLFragment, dateFilterSQLFragment];
  let query = `SELECT h.id, url, url_hash, rev_host, guid, title, frecency, foreign_count
       FROM moz_places h WHERE
       (${fragmentArray.filter(f => f !== "").join(") AND (")})`;
  let onResultData = onResult ? [] : null;
  let pages = [];
  let hasPagesToRemove = false;

  await db.executeCached(query, params, row => {
    let hasForeign = row.getResultByName("foreign_count") != 0;
    if (!hasForeign) {
      hasPagesToRemove = true;
    }
    let id = row.getResultByName("id");
    let guid = row.getResultByName("guid");
    let url = row.getResultByName("url");
    let page = {
      id,
      guid,
      hasForeign,
      hasVisits: false,
      url: new URL(url),
      hash: row.getResultByName("url_hash"),
    };
    pages.push(page);
    if (onResult) {
      onResultData.push({
        guid,
        title: row.getResultByName("title"),
        frecency: row.getResultByName("frecency"),
        url: new URL(url),
      });
    }
  });

  if (pages.length === 0) {
    // Nothing to do
    return false;
  }

  await db.executeTransaction(async function () {
    // 4. Actually remove visits
    let pageIds = pages.map(p => p.id);
    for (let chunk of lazy.PlacesUtils.chunkArray(pageIds, db.variableLimit)) {
      await db.execute(
        `DELETE FROM moz_historyvisits
         WHERE place_id IN(${lazy.PlacesUtils.sqlBindPlaceholders(chunk)})`,
        chunk
      );
    }
    // 5. Clean up and notify
    await cleanupPages(db, pages);
  });

  notifyCleanup(db, pages);
  notifyOnResult(onResultData, onResult);

  return hasPagesToRemove;
};

// Inner implementation of History.remove.
var remove = async function (db, { guids, urls }, onResult = null) {
  // 1. Find out what needs to be removed
  let onResultData = onResult ? [] : null;
  let pages = [];
  let hasPagesToRemove = false;
  function onRow(row) {
    let hasForeign = row.getResultByName("foreign_count") != 0;
    if (!hasForeign) {
      hasPagesToRemove = true;
    }
    let id = row.getResultByName("id");
    let guid = row.getResultByName("guid");
    let url = row.getResultByName("url");
    let page = {
      id,
      guid,
      hasForeign,
      hasVisits: false,
      url: new URL(url),
      hash: row.getResultByName("url_hash"),
    };
    pages.push(page);
    if (onResult) {
      onResultData.push({
        guid,
        title: row.getResultByName("title"),
        frecency: row.getResultByName("frecency"),
        url: new URL(url),
      });
    }
  }
  for (let chunk of lazy.PlacesUtils.chunkArray(guids, db.variableLimit)) {
    let query = `SELECT id, url, url_hash, guid, foreign_count, title, frecency
       FROM moz_places
       WHERE guid IN (${lazy.PlacesUtils.sqlBindPlaceholders(guids)})
      `;
    await db.execute(query, chunk, onRow);
  }
  for (let chunk of lazy.PlacesUtils.chunkArray(urls, db.variableLimit)) {
    // Make an array of variables like `["?1", "?2", ...]`, up to the length of
    // the chunk. This lets us bind each URL once, reusing the binding for the
    // `url_hash IN (...)` and `url IN (...)` clauses. We add 1 because indexed
    // parameters start at 1, not 0.
    let variables = Array.from(
      { length: chunk.length },
      (_, i) => "?" + (i + 1)
    );
    let query = `SELECT id, url, url_hash, guid, foreign_count, title, frecency
       FROM moz_places
       WHERE url_hash IN (${variables.map(v => `hash(${v})`).join(",")}) AND
             url IN (${variables.join(",")})
      `;
    await db.execute(query, chunk, onRow);
  }

  if (!pages.length) {
    // Nothing to do
    return false;
  }

  await db.executeTransaction(async function () {
    // 2. Remove all visits to these pages.
    let pageIds = pages.map(p => p.id);
    for (let chunk of lazy.PlacesUtils.chunkArray(pageIds, db.variableLimit)) {
      await db.execute(
        `DELETE FROM moz_historyvisits
         WHERE place_id IN (${lazy.PlacesUtils.sqlBindPlaceholders(chunk)})`,
        chunk
      );
    }

    // 3. Clean up and notify
    await cleanupPages(db, pages);
  });

  notifyCleanup(db, pages);
  notifyOnResult(onResultData, onResult); // don't wait

  return hasPagesToRemove;
};

/**
 * Merges an updateInfo object, as returned by asyncHistory.updatePlaces
 * into a PageInfo object as defined in this file.
 *
 * @param updateInfo: (Object)
 *      An object that represents a page that is generated by
 *      asyncHistory.updatePlaces.
 * @param pageInfo: (PageInfo)
 *      An PageInfo object into which to merge the data from updateInfo.
 *      Defaults to an empty object so that this method can be used
 *      to simply convert an updateInfo object into a PageInfo object.
 *
 * @return (PageInfo)
 *      A PageInfo object populated with data from updateInfo.
 */
function mergeUpdateInfoIntoPageInfo(updateInfo, pageInfo = {}) {
  pageInfo.guid = updateInfo.guid;
  pageInfo.title = updateInfo.title;
  if (!pageInfo.url) {
    pageInfo.url = URL.fromURI(updateInfo.uri);
    pageInfo.title = updateInfo.title;
    pageInfo.placeId = updateInfo.placeId;
    pageInfo.visits = updateInfo.visits.map(visit => {
      return {
        visitId: visit.visitId,
        date: lazy.PlacesUtils.toDate(visit.visitDate),
        transition: visit.transitionType,
        referrer: visit.referrerURI ? URL.fromURI(visit.referrerURI) : null,
      };
    });
  }
  return pageInfo;
}

// Inner implementation of History.insert.
var insert = function (db, pageInfo) {
  let info = convertForUpdatePlaces(pageInfo);

  return new Promise((resolve, reject) => {
    lazy.asyncHistory.updatePlaces(info, {
      handleError: error => {
        reject(error);
      },
      handleResult: result => {
        pageInfo = mergeUpdateInfoIntoPageInfo(result, pageInfo);
      },
      handleCompletion: () => {
        resolve(pageInfo);
      },
    });
  });
};

// Inner implementation of History.insertMany.
var insertMany = function (db, pageInfos, onResult, onError) {
  let infos = [];
  let onResultData = [];
  let onErrorData = [];

  for (let pageInfo of pageInfos) {
    let info = convertForUpdatePlaces(pageInfo);
    infos.push(info);
  }

  return new Promise((resolve, reject) => {
    lazy.asyncHistory.updatePlaces(infos, {
      handleError: (resultCode, result) => {
        let pageInfo = mergeUpdateInfoIntoPageInfo(result);
        onErrorData.push(pageInfo);
      },
      handleResult: result => {
        let pageInfo = mergeUpdateInfoIntoPageInfo(result);
        onResultData.push(pageInfo);
      },
      ignoreErrors: !onError,
      ignoreResults: !onResult,
      handleCompletion: updatedCount => {
        notifyOnResult(onResultData, onResult);
        notifyOnResult(onErrorData, onError);
        if (updatedCount > 0) {
          resolve();
        } else {
          reject({ message: "No items were added to history." });
        }
      },
    });
  });
};

// Inner implementation of History.update.
var update = async function (db, pageInfo) {
  // Check for page existence first; we can skip most of the work if it doesn't
  // exist and anyway we'll need the place id multiple times later.
  // Prefer GUID over url if it's present.
  let id;
  if (typeof pageInfo.guid === "string") {
    let rows = await db.executeCached(
      "SELECT id FROM moz_places WHERE guid = :guid",
      { guid: pageInfo.guid }
    );
    id = rows.length ? rows[0].getResultByName("id") : null;
  } else {
    let rows = await db.executeCached(
      "SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url",
      { url: pageInfo.url.href }
    );
    id = rows.length ? rows[0].getResultByName("id") : null;
  }
  if (!id) {
    return;
  }

  let updateFragments = [];
  let params = {};
  if ("description" in pageInfo) {
    updateFragments.push("description");
    params.description = pageInfo.description;
  }
  if ("siteName" in pageInfo) {
    updateFragments.push("site_name");
    params.site_name = pageInfo.siteName;
  }
  if ("previewImageURL" in pageInfo) {
    updateFragments.push("preview_image_url");
    params.preview_image_url = pageInfo.previewImageURL
      ? pageInfo.previewImageURL.href
      : null;
  }
  if (updateFragments.length) {
    // Since this data may be written at every visit and is textual, avoid
    // overwriting the existing record if it didn't change.
    await db.execute(
      `
      UPDATE moz_places
      SET ${updateFragments.map(v => `${v} = :${v}`).join(", ")}
      WHERE id = :id
        AND (${updateFragments
          .map(v => `IFNULL(${v}, '') <> IFNULL(:${v}, '')`)
          .join(" OR ")})
    `,
      { id, ...params }
    );
  }

  if (pageInfo.annotations) {
    let annosToRemove = [];
    let annosToUpdate = [];

    for (let anno of pageInfo.annotations) {
      anno[1] ? annosToUpdate.push(anno[0]) : annosToRemove.push(anno[0]);
    }

    await db.executeTransaction(async function () {
      if (annosToUpdate.length) {
        await db.execute(
          `
          INSERT OR IGNORE INTO moz_anno_attributes (name)
          VALUES ${Array.from(annosToUpdate.keys())
            .map(k => `(:${k})`)
            .join(", ")}
        `,
          Object.assign({}, annosToUpdate)
        );

        for (let anno of annosToUpdate) {
          let content = pageInfo.annotations.get(anno);
          // TODO: We only really need to save the type whilst we still support
          // accessing page annotations via the annotation service.
          let type =
            typeof content == "string"
              ? History.ANNOTATION_TYPE_STRING
              : History.ANNOTATION_TYPE_INT64;
          let date = lazy.PlacesUtils.toPRTime(new Date());

          // This will replace the id every time an annotation is updated. This is
          // not currently an issue as we're not joining on the id field.
          await db.execute(
            `
            INSERT OR REPLACE INTO moz_annos
              (place_id, anno_attribute_id, content, flags,
               expiration, type, dateAdded, lastModified)
            VALUES (:id,
                    (SELECT id FROM moz_anno_attributes WHERE name = :anno_name),
                    :content, 0, :expiration, :type, :date_added,
                    :last_modified)
          `,
            {
              id,
              anno_name: anno,
              content,
              expiration: History.ANNOTATION_EXPIRE_NEVER,
              type,
              // The date fields are unused, so we just set them both to the latest.
              date_added: date,
              last_modified: date,
            }
          );
        }
      }

      for (let anno of annosToRemove) {
        // We don't remove anything from the moz_anno_attributes table. If we
        // delete the last item of a given name, that item really should go away.
        // It will be cleaned up by expiration.
        await db.execute(
          `
          DELETE FROM moz_annos
          WHERE place_id = :id
          AND anno_attribute_id =
            (SELECT id FROM moz_anno_attributes WHERE name = :anno_name)
        `,
          { id, anno_name: anno }
        );
      }
    });
  }
};
PK
!<:���y�y!modules/GMPInstallManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// 1 day default
const DEFAULT_SECONDS_BETWEEN_CHECKS = 60 * 60 * 24;

import { Log } from "resource://gre/modules/Log.sys.mjs";
import {
  GMPPrefs,
  GMPUtils,
  GMP_PLUGIN_IDS,
  WIDEVINE_L1_ID,
  WIDEVINE_L3_ID,
} from "resource://gre/modules/GMPUtils.sys.mjs";

import { ProductAddonChecker } from "resource://gre/modules/addons/ProductAddonChecker.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  CertUtils: "resource://gre/modules/CertUtils.sys.mjs",
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
  ServiceRequest: "resource://gre/modules/ServiceRequest.sys.mjs",
  UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
});

function getScopedLogger(prefix) {
  // `PARENT_LOGGER_ID.` being passed here effectively links this logger
  // to the parentLogger.
  return Log.repository.getLoggerWithMessagePrefix("Toolkit.GMP", prefix + " ");
}

const LOCAL_GMP_SOURCES = [
  {
    id: "gmp-gmpopenh264",
    src: "chrome://global/content/gmp-sources/openh264.json",
    installByDefault: true,
  },
  {
    id: "gmp-widevinecdm",
    src: "chrome://global/content/gmp-sources/widevinecdm.json",
    installByDefault: true,
  },
  {
    id: "gmp-widevinecdm-l1",
    src: "chrome://global/content/gmp-sources/widevinecdm_l1.json",
    installByDefault: false,
  },
];

function getLocalSources() {
  if (GMPPrefs.getBool(GMPPrefs.KEY_ALLOW_LOCAL_SOURCES, true)) {
    return LOCAL_GMP_SOURCES;
  }

  let log = getScopedLogger("GMPInstallManager.checkForAddons");
  log.info("ignoring local sources");
  return [];
}

function downloadJSON(uri) {
  let log = getScopedLogger("GMPInstallManager.checkForAddons");
  log.info("fetching config from: " + uri);
  return new Promise((resolve, reject) => {
    let xmlHttp = new lazy.ServiceRequest({ mozAnon: true });

    xmlHttp.onload = function () {
      resolve(JSON.parse(this.responseText));
    };

    xmlHttp.onerror = function (e) {
      reject("Fetching " + uri + " results in error code: " + e.target.status);
    };

    xmlHttp.open("GET", uri);
    xmlHttp.overrideMimeType("application/json");
    xmlHttp.send();
  });
}

/**
 * If downloading from the network fails (AUS server is down),
 * load the sources from local build configuration.
 */
function downloadLocalConfig(sources) {
  if (!sources.length) {
    return Promise.resolve({ addons: [] });
  }

  let log = getScopedLogger("GMPInstallManager.downloadLocalConfig");
  return Promise.all(
    sources.map(conf => {
      return downloadJSON(conf.src).then(addons => {
        let platforms = addons.vendors[conf.id].platforms;
        let target = Services.appinfo.OS + "_" + lazy.UpdateUtils.ABI;
        let details = null;

        while (!details) {
          if (!(target in platforms)) {
            // There was no matching platform so return false, this addon
            // will be filtered from the results below
            log.info("no details found for: " + target);
            return false;
          }
          // Field either has the details of the binary or is an alias
          // to another build target key that does
          if (platforms[target].alias) {
            target = platforms[target].alias;
          } else {
            details = platforms[target];
          }
        }

        log.info("found plugin: " + conf.id);
        return {
          id: conf.id,
          URL: details.fileUrl,
          hashFunction: addons.hashFunction,
          hashValue: details.hashValue,
          version: addons.vendors[conf.id].version,
          size: details.filesize,
          usedFallback: true,
        };
      });
    })
  ).then(addons => {
    // Some filters may not match this platform so
    // filter those out
    return { addons: addons.filter(x => x !== false) };
  });
}

/**
 * Provides an easy API for downloading and installing GMP Addons
 */
export function GMPInstallManager() {}

/**
 * Temp file name used for downloading
 */
GMPInstallManager.prototype = {
  /**
   * Obtains a URL with replacement of vars
   */
  async _getURL() {
    let log = getScopedLogger("GMPInstallManager._getURL");
    // Use the override URL if it is specified.  The override URL is just like
    // the normal URL but it does not check the cert.
    let url = GMPPrefs.getString(GMPPrefs.KEY_URL_OVERRIDE, "");
    if (url) {
      log.info("Using override url: " + url);
    } else {
      url = GMPPrefs.getString(GMPPrefs.KEY_URL);
      log.info("Using url: " + url);
    }

    url = await lazy.UpdateUtils.formatUpdateURL(url);

    log.info("Using url (with replacement): " + url);
    return url;
  },

  /**
   * Determines the root to use for verifying content signatures.
   * @param url
   *        The Balrog URL, i.e. the return value of _getURL().
   */
  _getContentSignatureRootForURL(url) {
    // The prod and stage URLs of Balrog are documented at:
    // https://mozilla-balrog.readthedocs.io/en/latest/infrastructure.html
    // Note: we are matching by prefix without the full domain nor slash, to
    // enable us to move to a different host name in the future if desired.
    if (url.startsWith("https://aus")) {
      return Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot;
    }
    if (url.startsWith("https://stage.")) {
      return Ci.nsIContentSignatureVerifier.ContentSignatureStageRoot;
    }
    if (Services.env.exists("XPCSHELL_TEST_PROFILE_DIR")) {
      return Ci.nsIX509CertDB.AppXPCShellRoot;
    }
    // When content signature verification for GMP was added (bug 1714621), a
    // pref existed to configure an arbitrary root, which enabled local testing.
    // This pref was removed later in bug 1769669, and replaced with hard-coded
    // roots (prod and tests only). Support for testing against the stage server
    // was restored in bug 1771992.
    // Note: other verifiers ultimately fall back to ContentSignatureLocalRoot,
    // to support local development. Here we use ContentSignatureProdRoot to
    // minimize risk (and the unclear demand for "local" development).
    return Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot;
  },

  /**
   * Records telemetry results on if fetching update.xml from Balrog succeeded
   * when content signature was used to verify the response from Balrog.
   * @param didGetAddonList
   *        A boolean indicating if an update.xml containing the addon list was
   *        successfully fetched (true) or not (false).
   * @param err
   *        The error that was thrown (if it exists) for the failure case. This
   *        is expected to have a addonCheckerErr member which provides further
   *        information on why the addon checker failed.
   */
  recordUpdateXmlTelemetryForContentSignature(didGetAddonList, err = null) {
    let log = getScopedLogger(
      "GMPInstallManager.recordUpdateXmlTelemetryForContentSignature"
    );
    try {
      let updateResultHistogram = Services.telemetry.getHistogramById(
        "MEDIA_GMP_UPDATE_XML_FETCH_RESULT"
      );

      // The non-glean telemetry used here will be removed in future and just
      // the glean data will be gathered.
      if (didGetAddonList) {
        updateResultHistogram.add("content_sig_ok");
        Glean.gmp.updateXmlFetchResult.content_sig_success.add(1);
        return;
      }
      // All remaining cases are failure cases.
      updateResultHistogram.add("content_sig_fail");
      if (!err?.addonCheckerErr) {
        // Unknown error case. If this is happening we should audit error paths
        // to identify why we're not getting an error, or not getting it
        // labelled.
        Glean.gmp.updateXmlFetchResult.content_sig_unknown_error.add(1);
        return;
      }
      const errorToHistogramMap = {
        [ProductAddonChecker.NETWORK_REQUEST_ERR]:
          "content_sig_net_request_error",
        [ProductAddonChecker.NETWORK_TIMEOUT_ERR]: "content_sig_net_timeout",
        [ProductAddonChecker.ABORT_ERR]: "content_sig_abort",
        [ProductAddonChecker.VERIFICATION_MISSING_DATA_ERR]:
          "content_sig_missing_data",
        [ProductAddonChecker.VERIFICATION_FAILED_ERR]: "content_sig_failed",
        [ProductAddonChecker.VERIFICATION_INVALID_ERR]: "content_sig_invalid",
        [ProductAddonChecker.XML_PARSE_ERR]: "content_sig_xml_parse_error",
      };
      let metricID =
        errorToHistogramMap[err.addonCheckerErr] ?? "content_sig_unknown_error";
      let metric = Glean.gmp.updateXmlFetchResult[metricID];
      metric.add(1);
    } catch (e) {
      // We don't expect this path to be hit, but we don't want telemetry
      // failures to break GMP updates, so catch any issues here and let the
      // update machinery continue.
      log.error(
        `Failed to record telemetry result of getProductAddonList, got error: ${e}`
      );
    }
  },

  /**
   * Records telemetry results on if fetching update.xml from Balrog succeeded
   * when cert pinning was used to verify the response from Balrog. This
   * should be removed once we're no longer using cert pinning.
   * @param didGetAddonList
   *        A boolean indicating if an update.xml containing the addon list was
   *        successfully fetched (true) or not (false).
   * @param err
   *        The error that was thrown (if it exists) for the failure case. This
   *        is expected to have a addonCheckerErr member which provides further
   *        information on why the addon checker failed.
   */
  recordUpdateXmlTelemetryForCertPinning(didGetAddonList, err = null) {
    let log = getScopedLogger(
      "GMPInstallManager.recordUpdateXmlTelemetryForCertPinning"
    );
    try {
      let updateResultHistogram = Services.telemetry.getHistogramById(
        "MEDIA_GMP_UPDATE_XML_FETCH_RESULT"
      );

      // The non-glean telemetry used here will be removed in future and just
      // the glean data will be gathered.
      if (didGetAddonList) {
        updateResultHistogram.add("cert_pinning_ok");
        Glean.gmp.updateXmlFetchResult.cert_pin_success.add(1);
        return;
      }
      // All remaining cases are failure cases.
      updateResultHistogram.add("cert_pinning_fail");
      if (!err?.addonCheckerErr) {
        // Unknown error case. If this is happening we should audit error paths
        // to identify why we're not getting an error, or not getting it
        // labelled.
        Glean.gmp.updateXmlFetchResult.cert_pin_unknown_error.add(1);
        return;
      }
      const errorToHistogramMap = {
        [ProductAddonChecker.NETWORK_REQUEST_ERR]: "cert_pin_net_request_error",
        [ProductAddonChecker.NETWORK_TIMEOUT_ERR]: "cert_pin_net_timeout",
        [ProductAddonChecker.ABORT_ERR]: "cert_pin_abort",
        [ProductAddonChecker.VERIFICATION_MISSING_DATA_ERR]:
          "cert_pin_missing_data",
        [ProductAddonChecker.VERIFICATION_FAILED_ERR]: "cert_pin_failed",
        [ProductAddonChecker.VERIFICATION_INVALID_ERR]: "cert_pin_invalid",
        [ProductAddonChecker.XML_PARSE_ERR]: "cert_pin_xml_parse_error",
      };
      let metricID =
        errorToHistogramMap[err.addonCheckerErr] ?? "cert_pin_unknown_error";
      let metric = Glean.gmp.updateXmlFetchResult[metricID];
      metric.add(1);
    } catch (e) {
      // We don't expect this path to be hit, but we don't want telemetry
      // failures to break GMP updates, so catch any issues here and let the
      // update machinery continue.
      log.error(
        `Failed to record telemetry result of getProductAddonList, got error: ${e}`
      );
    }
  },

  /**
   * Performs an addon check.
   * @return a promise which will be resolved or rejected.
   *         The promise is resolved with an object with properties:
   *           addons: array of addons
   *           usedFallback: whether the data was collected from online or
   *                         from fallback data within the build
   *         The promise is rejected with an object with properties:
   *           target: The XHR request object
   *           status: The HTTP status code
   *           type: Sometimes specifies type of rejection
   */
  async checkForAddons() {
    let log = getScopedLogger("GMPInstallManager.checkForAddons");
    if (this._deferred) {
      log.error("checkForAddons already called");
      return Promise.reject({ type: "alreadycalled" });
    }

    if (!GMPPrefs.getBool(GMPPrefs.KEY_UPDATE_ENABLED, true)) {
      log.info("Updates are disabled via media.gmp-manager.updateEnabled");
      return { usedFallback: true, addons: [] };
    }

    this._deferred = Promise.withResolvers();
    let deferredPromise = this._deferred.promise;

    // Should content signature checking of Balrog replies be used? If so this
    // will be done instead of the older cert pinning method.
    let checkContentSignature = GMPPrefs.getBool(
      GMPPrefs.KEY_CHECK_CONTENT_SIGNATURE,
      true
    );

    let allowNonBuiltIn = true;
    let certs = null;
    // Only check certificates if we're not using a custom URL, and only if
    // we're not checking a content signature.
    if (
      !Services.prefs.prefHasUserValue(GMPPrefs.KEY_URL_OVERRIDE) &&
      !checkContentSignature
    ) {
      allowNonBuiltIn = !GMPPrefs.getString(
        GMPPrefs.KEY_CERT_REQUIREBUILTIN,
        true
      );
      if (GMPPrefs.getBool(GMPPrefs.KEY_CERT_CHECKATTRS, true)) {
        certs = lazy.CertUtils.readCertPrefs(GMPPrefs.KEY_CERTS_BRANCH);
      }
    }

    let url = await this._getURL();
    let trustedContentSignatureRoot = this._getContentSignatureRootForURL(url);

    log.info(
      `Fetching product addon list url=${url}, allowNonBuiltIn=${allowNonBuiltIn}, certs=${certs}, checkContentSignature=${checkContentSignature}, trustedContentSignatureRoot=${trustedContentSignatureRoot}`
    );

    let success = true;
    let res;
    try {
      res = await ProductAddonChecker.getProductAddonList(
        url,
        allowNonBuiltIn,
        certs,
        checkContentSignature,
        trustedContentSignatureRoot
      );

      if (checkContentSignature) {
        this.recordUpdateXmlTelemetryForContentSignature(true);
      } else {
        this.recordUpdateXmlTelemetryForCertPinning(true);
      }
    } catch (err) {
      success = false;
      if (checkContentSignature) {
        this.recordUpdateXmlTelemetryForContentSignature(false, err);
      } else {
        this.recordUpdateXmlTelemetryForCertPinning(false, err);
      }
    }

    let localSources = getLocalSources();

    try {
      if (!success) {
        log.info("Falling back to local config");
        let fallbackSources = localSources.filter(function (gmpSource) {
          return gmpSource.installByDefault;
        });
        res = await downloadLocalConfig(fallbackSources);
      }
    } catch (err) {
      this._deferred.reject(err);
      delete this._deferred;
      return deferredPromise;
    }

    let addons;
    if (res && res.addons) {
      addons = res.addons.map(a => new GMPAddon(a));
    } else {
      addons = [];
    }

    // We need to merge in the addons that are only available via fallback that
    // the user has requested be forced installed regardless of our update
    // server configuration.
    try {
      let forcedSources = localSources.filter(function (gmpSource) {
        return GMPPrefs.getBool(
          GMPPrefs.KEY_PLUGIN_FORCE_INSTALL,
          false,
          gmpSource.id
        );
      });

      let forcedConfigs = await downloadLocalConfig(
        forcedSources.filter(function (gmpSource) {
          return !addons.find(gmpAddon => gmpAddon.id == gmpSource.id);
        })
      );

      let forcedAddons = forcedConfigs.addons.map(
        config => new GMPAddon(config)
      );

      log.info("Forced " + forcedAddons.length + " addons.");
      addons = addons.concat(forcedAddons);
    } catch (err) {
      log.info("Failed to force addons: " + err);
    }

    this._deferred.resolve({ addons });
    delete this._deferred;
    return deferredPromise;
  },
  /**
   * Installs the specified addon and calls a callback when done.
   * @param gmpAddon The GMPAddon object to install
   * @return a promise which will be resolved or rejected
   *         The promise will resolve with an array of paths that were extracted
   *         The promise will reject with an error object:
   *           target: The XHR request object
   *           status: The HTTP status code
   *           type: A string to represent the type of error
   *                 downloaderr, verifyerr or previouserrorencountered
   */
  installAddon(gmpAddon) {
    if (this._deferred) {
      let log = getScopedLogger("GMPInstallManager.installAddon");
      log.error("previous error encountered");
      return Promise.reject({ type: "previouserrorencountered" });
    }
    this.gmpDownloader = new GMPDownloader(gmpAddon);
    return this.gmpDownloader.start();
  },
  _getTimeSinceLastCheck() {
    let now = Math.round(Date.now() / 1000);
    // Default to 0 here because `now - 0` will be returned later if that case
    // is hit. We want a large value so a check will occur.
    let lastCheck = GMPPrefs.getInt(GMPPrefs.KEY_UPDATE_LAST_CHECK, 0);
    // Handle clock jumps, return now since we want it to represent
    // a lot of time has passed since the last check.
    if (now < lastCheck) {
      return now;
    }
    return now - lastCheck;
  },
  get _isEMEEnabled() {
    return GMPPrefs.getBool(GMPPrefs.KEY_EME_ENABLED, true);
  },
  _isAddonEnabled(aAddon) {
    return GMPPrefs.getBool(GMPPrefs.KEY_PLUGIN_ENABLED, true, aAddon);
  },
  _isAddonUpdateEnabled(aAddon) {
    return (
      this._isAddonEnabled(aAddon) &&
      GMPPrefs.getBool(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, true, aAddon)
    );
  },
  _updateLastCheck() {
    let now = Math.round(Date.now() / 1000);
    GMPPrefs.setInt(GMPPrefs.KEY_UPDATE_LAST_CHECK, now);
  },
  _versionchangeOccurred() {
    let savedBuildID = GMPPrefs.getString(GMPPrefs.KEY_BUILDID, "");
    let buildID = Services.appinfo.platformBuildID || "";
    if (savedBuildID == buildID) {
      return false;
    }
    GMPPrefs.setString(GMPPrefs.KEY_BUILDID, buildID);
    return true;
  },
  /**
   * Wrapper for checkForAddons and installAddon.
   * Will only install if not already installed and will log the results.
   * This will only install/update the OpenH264 and EME plugins
   * @return a promise which will be resolved if all addons could be installed
   *         successfully, rejected otherwise.
   */
  async simpleCheckAndInstall() {
    let log = getScopedLogger("GMPInstallManager.simpleCheckAndInstall");

    if (this._versionchangeOccurred()) {
      log.info(
        "A version change occurred. Ignoring " +
          "media.gmp-manager.lastCheck to check immediately for " +
          "new or updated GMPs."
      );
    } else {
      let secondsBetweenChecks = GMPPrefs.getInt(
        GMPPrefs.KEY_SECONDS_BETWEEN_CHECKS,
        DEFAULT_SECONDS_BETWEEN_CHECKS
      );
      let secondsSinceLast = this._getTimeSinceLastCheck();
      log.info(
        "Last check was: " +
          secondsSinceLast +
          " seconds ago, minimum seconds: " +
          secondsBetweenChecks
      );
      if (secondsBetweenChecks > secondsSinceLast) {
        log.info("Will not check for updates.");
        return { status: "too-frequent-no-check" };
      }
    }

    try {
      let { addons } = await this.checkForAddons();
      this._updateLastCheck();
      log.info("Found " + addons.length + " addons advertised.");
      let addonsToInstall = addons.filter(function (gmpAddon) {
        log.info("Found addon: " + gmpAddon.toString());

        if (!gmpAddon.isValid) {
          log.info("Addon |" + gmpAddon.id + "| is invalid.");
          return false;
        }

        if (GMPUtils.isPluginHidden(gmpAddon)) {
          log.info("Addon |" + gmpAddon.id + "| has been hidden.");
          return false;
        }

        if (gmpAddon.isInstalled) {
          log.info("Addon |" + gmpAddon.id + "| already installed.");
          return false;
        }

        // Do not install from fallback if already installed as it
        // may be a downgrade
        if (gmpAddon.usedFallback && gmpAddon.isUpdate) {
          log.info(
            "Addon |" +
              gmpAddon.id +
              "| not installing updates based " +
              "on fallback."
          );
          return false;
        }

        let addonUpdateEnabled = false;
        if (GMP_PLUGIN_IDS.includes(gmpAddon.id)) {
          if (!this._isAddonEnabled(gmpAddon.id)) {
            log.info(
              "GMP |" + gmpAddon.id + "| has been disabled; skipping check."
            );
          } else if (!this._isAddonUpdateEnabled(gmpAddon.id)) {
            log.info(
              "Auto-update is off for " + gmpAddon.id + ", skipping check."
            );
          } else {
            addonUpdateEnabled = true;
          }
        } else {
          // Currently, we only support installs of OpenH264 and EME plugins.
          log.info(
            "Auto-update is off for unknown plugin '" +
              gmpAddon.id +
              "', skipping check."
          );
        }

        return addonUpdateEnabled;
      }, this);

      if (!addonsToInstall.length) {
        let now = Math.round(Date.now() / 1000);
        GMPPrefs.setInt(GMPPrefs.KEY_UPDATE_LAST_EMPTY_CHECK, now);
        log.info("No new addons to install, returning");
        return { status: "nothing-new-to-install" };
      }

      let installResults = [];
      let failureEncountered = false;
      for (let addon of addonsToInstall) {
        try {
          await this.installAddon(addon);
          installResults.push({
            id: addon.id,
            result: "succeeded",
          });
        } catch (e) {
          failureEncountered = true;
          installResults.push({
            id: addon.id,
            result: "failed",
          });
        }
      }
      if (failureEncountered) {
        // eslint-disable-next-line no-throw-literal
        throw { status: "failed", results: installResults };
      }
      return { status: "succeeded", results: installResults };
    } catch (e) {
      log.error("Could not check for addons", e);
      throw e;
    }
  },

  /**
   * Makes sure everything is cleaned up
   */
  uninit() {
    let log = getScopedLogger("GMPInstallManager.uninit");
    if (this._request) {
      log.info("Aborting request");
      this._request.abort();
    }
    if (this._deferred) {
      log.info("Rejecting deferred");
      this._deferred.reject({ type: "uninitialized" });
    }
    log.info("Done cleanup");
  },

  /**
   * If set to true, specifies to leave the temporary downloaded zip file.
   * This is useful for tests.
   */
  overrideLeaveDownloadedZip: false,
};

/**
 * Used to construct a single GMP addon
 * GMPAddon objects are returns from GMPInstallManager.checkForAddons
 * GMPAddon objects can also be used in calls to GMPInstallManager.installAddon
 *
 * @param addon The ProductAddonChecker `addon` object
 */
export function GMPAddon(addon) {
  let log = getScopedLogger("GMPAddon.constructor");
  this.usedFallback = false;
  for (let name of Object.keys(addon)) {
    this[name] = addon[name];
  }
  log.info("Created new addon: " + this.toString());
}

GMPAddon.prototype = {
  /**
   * Returns a string representation of the addon
   */
  toString() {
    return (
      this.id +
      " (" +
      "isValid: " +
      this.isValid +
      ", isInstalled: " +
      this.isInstalled +
      ", hashFunction: " +
      this.hashFunction +
      ", hashValue: " +
      this.hashValue +
      (this.size !== undefined ? ", size: " + this.size : "") +
      ")"
    );
  },
  /**
   * If all the fields aren't specified don't consider this addon valid
   * @return true if the addon is parsed and valid
   */
  get isValid() {
    return (
      this.id &&
      this.URL &&
      this.version &&
      this.hashFunction &&
      !!this.hashValue
    );
  },
  get isInstalled() {
    return (
      this.version &&
      !!this.hashValue &&
      GMPPrefs.getString(GMPPrefs.KEY_PLUGIN_VERSION, "", this.id) ===
        this.version &&
      GMPPrefs.getString(GMPPrefs.KEY_PLUGIN_HASHVALUE, "", this.id) ===
        this.hashValue
    );
  },
  get isEME() {
    return this.id == WIDEVINE_L1_ID || this.id == WIDEVINE_L3_ID;
  },
  get isOpenH264() {
    return this.id == "gmp-gmpopenh264";
  },
  /**
   * @return true if the addon has been previously installed and this is
   * a new version, if this is a fresh install return false
   */
  get isUpdate() {
    return (
      this.version &&
      GMPPrefs.getBool(GMPPrefs.KEY_PLUGIN_VERSION, false, this.id)
    );
  },
};

/**
 * Constructs a GMPExtractor object which is used to extract a GMP zip
 * into the specified location.
 * @param zipPath The path on disk of the zip file to extract
 * @param relativePath The relative path components inside the profile directory
 *                     to extract the zip to.
 */
export function GMPExtractor(zipPath, relativeInstallPath) {
  this.zipPath = zipPath;
  this.relativeInstallPath = relativeInstallPath;
}

GMPExtractor.prototype = {
  /**
   * Installs the this.zipPath contents into the directory used to store GMP
   * addons for the current platform.
   *
   * @return a promise which will be resolved or rejected
   *         See GMPInstallManager.installAddon for resolve/rejected info
   */
  install() {
    this._deferred = Promise.withResolvers();
    let deferredPromise = this._deferred;
    let { zipPath, relativeInstallPath } = this;
    // Escape the zip path since the worker will use it as a URI
    let zipFile = new lazy.FileUtils.File(zipPath);
    let zipURI = Services.io.newFileURI(zipFile).spec;
    let worker = new ChromeWorker(
      "resource://gre/modules/GMPExtractor.worker.js"
    );
    worker.onmessage = function (msg) {
      let log = getScopedLogger("GMPExtractor");
      worker.terminate();
      if (msg.data.result != "success") {
        log.error("Failed to extract zip file: " + zipURI);
        log.error("Exception: " + msg.data.exception);
        return deferredPromise.reject({
          target: this,
          status: msg.data.exception,
          type: "exception",
        });
      }
      log.info("Successfully extracted zip file: " + zipURI);
      return deferredPromise.resolve(msg.data.extractedPaths);
    };
    worker.postMessage({ zipURI, relativeInstallPath });
    return this._deferred.promise;
  },
};

/**
 * Constructs an object which downloads and initiates an install of
 * the specified GMPAddon object.
 * @param gmpAddon The addon to install.
 */
export function GMPDownloader(gmpAddon) {
  this._gmpAddon = gmpAddon;
}

GMPDownloader.prototype = {
  /**
   * Starts the download process for an addon.
   * @return a promise which will be resolved or rejected
   *         See GMPInstallManager.installAddon for resolve/rejected info
   */
  start() {
    let log = getScopedLogger("GMPDownloader");
    let gmpAddon = this._gmpAddon;
    let now = Math.round(Date.now() / 1000);
    GMPPrefs.setInt(GMPPrefs.KEY_PLUGIN_LAST_INSTALL_START, now, gmpAddon.id);

    if (!gmpAddon.isValid) {
      log.info("gmpAddon is not valid, will not continue");
      return Promise.reject({
        target: this,
        type: "downloaderr",
      });
    }
    // If the HTTPS-Only Mode is enabled, every insecure request gets upgraded
    // by default. This upgrade has to be prevented for openh264 downloads since
    // the server doesn't support https://
    const downloadOptions = {
      httpsOnlyNoUpgrade: gmpAddon.isOpenH264,
    };
    return ProductAddonChecker.downloadAddon(gmpAddon, downloadOptions).then(
      zipPath => {
        let now = Math.round(Date.now() / 1000);
        GMPPrefs.setInt(GMPPrefs.KEY_PLUGIN_LAST_DOWNLOAD, now, gmpAddon.id);
        log.info(
          `install to directory path: ${gmpAddon.id}/${gmpAddon.version}`
        );
        let gmpInstaller = new GMPExtractor(zipPath, [
          gmpAddon.id,
          gmpAddon.version,
        ]);
        let installPromise = gmpInstaller.install();
        return installPromise
          .then(
            extractedPaths => {
              // Success, set the prefs
              let now = Math.round(Date.now() / 1000);
              GMPPrefs.setInt(
                GMPPrefs.KEY_PLUGIN_LAST_UPDATE,
                now,
                gmpAddon.id
              );
              // Remember our ABI, so that if the profile is migrated to another
              // platform or from 32 -> 64 bit, we notice and don't try to load the
              // unexecutable plugin library.
              let abi = GMPUtils._expectedABI(gmpAddon);
              log.info("Setting ABI to '" + abi + "' for " + gmpAddon.id);
              GMPPrefs.setString(GMPPrefs.KEY_PLUGIN_ABI, abi, gmpAddon.id);
              // We use the combination of the hash and version to ensure we are
              // up to date.
              GMPPrefs.setString(
                GMPPrefs.KEY_PLUGIN_HASHVALUE,
                gmpAddon.hashValue,
                gmpAddon.id
              );
              // Setting the version pref signals installation completion to consumers,
              // if you need to set other prefs etc. do it before this.
              GMPPrefs.setString(
                GMPPrefs.KEY_PLUGIN_VERSION,
                gmpAddon.version,
                gmpAddon.id
              );
              return extractedPaths;
            },
            reason => {
              GMPPrefs.setString(
                GMPPrefs.KEY_PLUGIN_LAST_INSTALL_FAIL_REASON,
                reason,
                gmpAddon.id
              );
              let now = Math.round(Date.now() / 1000);
              GMPPrefs.setInt(
                GMPPrefs.KEY_PLUGIN_LAST_INSTALL_FAILED,
                now,
                gmpAddon.id
              );
              throw reason;
            }
          )
          .finally(() => {
            log.info(`Deleting ${gmpAddon.id} temporary zip file ${zipPath}`);
            // We need to send out an observer event to ensure the nsZipReaderCache
            // clears its cache entries associated with our temporary file. Otherwise
            // if the addons downloader reuses the temporary file path, then we may hit
            // the cache and get different contents than expected.
            Services.obs.notifyObservers(null, "flush-cache-entry", zipPath);
            IOUtils.remove(zipPath);
          });
      },
      reason => {
        GMPPrefs.setString(
          GMPPrefs.KEY_PLUGIN_LAST_DOWNLOAD_FAIL_REASON,
          reason,
          gmpAddon.id
        );
        let now = Math.round(Date.now() / 1000);
        GMPPrefs.setInt(
          GMPPrefs.KEY_PLUGIN_LAST_DOWNLOAD_FAILED,
          now,
          gmpAddon.id
        );
        throw reason;
      }
    );
  },
};
PK
!<����LPLP*modules/addons/ProductAddonChecker.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Log } from "resource://gre/modules/Log.sys.mjs";
import { CertUtils } from "resource://gre/modules/CertUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ServiceRequest: "resource://gre/modules/ServiceRequest.sys.mjs",
});

// This will inherit settings from the "addons" logger.
var logger = Log.repository.getLogger("addons.productaddons");
// We want to set the level of this logger independent from its parent to help
// debug things like GMP updates. Link this to its own level pref.
logger.manageLevelFromPref("extensions.logging.productaddons.level");

/**
 * Number of milliseconds after which we need to cancel `downloadXMLWithRequest`
 * and `conservativeFetch`.
 *
 * Bug 1087674 suggests that the XHR/ServiceRequest we use in
 * `downloadXMLWithRequest` may never terminate in presence of network nuisances
 * (e.g. strange antivirus behavior). This timeout is a defensive measure to
 * ensure that we fail cleanly in such case.
 */
const TIMEOUT_DELAY_MS = 20000;

/**
 * Gets the status of an XMLHttpRequest either directly or from its underlying
 * channel.
 *
 * @param  request
 *         The XMLHttpRequest.
 * @returns {Object} result - An object containing the results.
 * @returns {integer} result.status - Request status code, if available, else the channel nsresult.
 * @returns {integer} result.channelStatus - Channel nsresult.
 * @returns {integer} result.errorCode - Request error code.
 */
function getRequestStatus(request) {
  let status = null;
  let errorCode = null;
  let channelStatus = null;

  try {
    status = request.status;
  } catch (e) {}
  try {
    errorCode = request.errorCode;
  } catch (e) {}
  try {
    channelStatus = request.channel.QueryInterface(Ci.nsIRequest).status;
  } catch (e) {}

  if (status == null) {
    status = channelStatus;
  }

  return { status, channelStatus, errorCode };
}

/**
 * A wrapper around `ServiceRequest` that behaves like a limited `fetch()`.
 * This doesn't handle headers like fetch, but can be expanded as callers need.
 *
 * Use this in order to leverage the `beConservative` flag, for
 * example to avoid using HTTP3 to fetch critical data.
 *
 * @param input a resource
 * @returns a Response object
 */
async function conservativeFetch(input) {
  return new Promise(function (resolve, reject) {
    const request = new lazy.ServiceRequest({ mozAnon: true });
    request.timeout = TIMEOUT_DELAY_MS;

    request.onerror = () => {
      let err = new TypeError("NetworkError: Network request failed");
      err.addonCheckerErr = ProductAddonChecker.NETWORK_REQUEST_ERR;
      reject(err);
    };
    request.ontimeout = () => {
      let err = new TypeError("Timeout: Network request failed");
      err.addonCheckerErr = ProductAddonChecker.NETWORK_TIMEOUT_ERR;
      reject(err);
    };
    request.onabort = () => {
      let err = new DOMException("Aborted", "AbortError");
      err.addonCheckerErr = ProductAddonChecker.ABORT_ERR;
      reject(err);
    };
    request.onload = () => {
      const responseAttributes = {
        status: request.status,
        statusText: request.statusText,
        url: request.responseURL,
      };
      resolve(new Response(request.response, responseAttributes));
    };

    const method = "GET";

    request.open(method, input, true);

    request.send();
  });
}

/**
 * Verifies the content signature on GMP's update.xml. When we fetch update.xml
 * balrog should send back content signature headers, which this function
 * is used to verify.
 *
 * @param  data
 *         The data received from balrog. I.e. the xml contents of update.xml.
 * @param  contentSignatureHeader
 *         The contents of the 'content-signature' header received along with
 *         `data`.
 * @param  trustedRoot
 *         The identifier of the trusted root to use for certificate validation.
 * @return A promise that will resolve to nothing if the signature verification
 *         succeeds, or rejects on failure, with an Error that sets its
 *         addonCheckerErr property disambiguate failure cases and a message
 *         explaining the error.
 */
async function verifyGmpContentSignature(
  data,
  contentSignatureHeader,
  trustedRoot
) {
  if (!contentSignatureHeader) {
    logger.warn(
      "Unexpected missing content signature header during content signature validation"
    );
    let err = new Error(
      "Content signature validation failed: missing content signature header"
    );
    err.addonCheckerErr = ProductAddonChecker.VERIFICATION_MISSING_DATA_ERR;
    throw err;
  }
  // Split out the header. It should contain a the following fields, separated by a semicolon
  // - x5u - a URI to the cert chain. See also https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.5
  // - p384ecdsa - the signature to verify. See also https://github.com/mozilla-services/autograph/blob/main/signer/contentsignaturepki/README.md
  const headerFields = contentSignatureHeader
    .split(";") // Split fields...
    .map(s => s.trim()) // Remove whitespace...
    .map(s => [
      // Break each field into it's name and value. This more verbose version is
      // used instead of `split()` to handle values that contain = characters. This
      // shouldn't happen for the signature because it's base64_url (no = padding),
      // but it's not clear if it's possible for the x5u URL (as part of a query).
      // Guard anyway, better safe than sorry.
      s.substring(0, s.indexOf("=")), // Get field name...
      s.substring(s.indexOf("=") + 1), // and field value.
    ]);

  let x5u;
  let signature;
  for (const [fieldName, fieldValue] of headerFields) {
    if (fieldName == "x5u") {
      x5u = fieldValue;
    } else if (fieldName == "p384ecdsa") {
      // The signature needs to contain 'p384ecdsa', so stich it back together.
      signature = `p384ecdsa=${fieldValue}`;
    }
  }

  if (!x5u) {
    logger.warn("Unexpected missing x5u during content signature validation");
    let err = Error("Content signature validation failed: missing x5u");
    err.addonCheckerErr = ProductAddonChecker.VERIFICATION_MISSING_DATA_ERR;
    throw err;
  }

  if (!signature) {
    logger.warn(
      "Unexpected missing signature during content signature validation"
    );
    let err = Error("Content signature validation failed: missing signature");
    err.addonCheckerErr = ProductAddonChecker.VERIFICATION_MISSING_DATA_ERR;
    throw err;
  }

  // The x5u field should contain the location of the cert chain, fetch it.
  // Use `conservativeFetch` so we get conservative behaviour and ensure (more)
  // reliable fetching.
  const certChain = await (await conservativeFetch(x5u)).text();

  const verifier = Cc[
    "@mozilla.org/security/contentsignatureverifier;1"
  ].createInstance(Ci.nsIContentSignatureVerifier);

  let valid;
  try {
    valid = await verifier.asyncVerifyContentSignature(
      data,
      signature,
      certChain,
      "aus.content-signature.mozilla.org",
      trustedRoot
    );
  } catch (err) {
    logger.warn(`Unexpected error while validating content signature: ${err}`);
    let newErr = new Error(`Content signature validation failed: ${err}`);
    newErr.addonCheckerErr = ProductAddonChecker.VERIFICATION_FAILED_ERR;
    throw newErr;
  }

  if (!valid) {
    logger.warn("Unexpected invalid content signature found during validation");
    let err = new Error("Content signature is not valid");
    err.addonCheckerErr = ProductAddonChecker.VERIFICATION_INVALID_ERR;
    throw err;
  }
}

/**
 * Downloads an XML document from a URL optionally testing the SSL certificate
 * for certain attributes.
 *
 * @param  url
 *         The url to download from.
 * @param  allowNonBuiltIn
 *         Whether to trust SSL certificates without a built-in CA issuer.
 * @param  allowedCerts
 *         The list of certificate attributes to match the SSL certificate
 *         against or null to skip checks.
 * @return a promise that resolves to the ServiceRequest request on success or
 *         rejects with a JS exception in case of error.
 */
function downloadXMLWithRequest(
  url,
  allowNonBuiltIn = false,
  allowedCerts = null
) {
  return new Promise((resolve, reject) => {
    let request = new lazy.ServiceRequest();
    // This is here to let unit test code override the ServiceRequest.
    if (request.wrappedJSObject) {
      request = request.wrappedJSObject;
    }
    request.open("GET", url, true);
    request.channel.notificationCallbacks = new CertUtils.BadCertHandler(
      allowNonBuiltIn
    );
    // Prevent the request from reading from the cache.
    request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
    // Prevent the request from writing to the cache.
    request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
    // Don't send any cookies
    request.channel.loadFlags |= Ci.nsIRequest.LOAD_ANONYMOUS;
    request.timeout = TIMEOUT_DELAY_MS;

    request.overrideMimeType("text/xml");
    // The Cache-Control header is only interpreted by proxies and the
    // final destination. It does not help if a resource is already
    // cached locally.
    request.setRequestHeader("Cache-Control", "no-cache");
    // HTTP/1.0 servers might not implement Cache-Control and
    // might only implement Pragma: no-cache
    request.setRequestHeader("Pragma", "no-cache");

    let fail = event => {
      let request = event.target;
      let status = getRequestStatus(request);
      let message =
        "Failed downloading XML, status: " +
        status.status +
        ", channelStatus: " +
        status.channelStatus +
        ", errorCode: " +
        status.errorCode +
        ", reason: " +
        event.type;
      logger.warn(message);
      let ex = new Error(message);
      ex.status = status.status;
      if (event.type == "error") {
        ex.addonCheckerErr = ProductAddonChecker.NETWORK_REQUEST_ERR;
      } else if (event.type == "abort") {
        ex.addonCheckerErr = ProductAddonChecker.ABORT_ERR;
      } else if (event.type == "timeout") {
        ex.addonCheckerErr = ProductAddonChecker.NETWORK_TIMEOUT_ERR;
      }
      reject(ex);
    };

    let success = event => {
      logger.info("Completed downloading document");
      let request = event.target;

      try {
        CertUtils.checkCert(request.channel, allowNonBuiltIn, allowedCerts);
      } catch (ex) {
        logger.error("Request failed certificate checks: " + ex);
        ex.status = getRequestStatus(request).requestStatus;
        ex.addonCheckerErr = ProductAddonChecker.VERIFICATION_FAILED_ERR;
        reject(ex);
        return;
      }

      resolve(request);
    };

    request.addEventListener("error", fail);
    request.addEventListener("abort", fail);
    request.addEventListener("timeout", fail);
    request.addEventListener("load", success);

    logger.info("sending request to: " + url);
    request.send(null);
  });
}

/**
 * Downloads an XML document from a URL optionally testing the SSL certificate
 * for certain attributes, and/or testing the content signature.
 *
 * @param  url
 *         The url to download from.
 * @param  allowNonBuiltIn
 *         Whether to trust SSL certificates without a built-in CA issuer.
 * @param  allowedCerts
 *         The list of certificate attributes to match the SSL certificate
 *         against or null to skip checks.
 * @param  verifyContentSignature
 *         When true, will verify the content signature information from the
 *         response header. Failure to verify will result in an error.
 * @param  trustedContentSignatureRoot
 *         The trusted root to use for certificate validation.
 *         Must be set if verifyContentSignature is true.
 * @return a promise that resolves to the DOM document downloaded or rejects
 *         with a JS exception in case of error.
 */
async function downloadXML(
  url,
  allowNonBuiltIn = false,
  allowedCerts = null,
  verifyContentSignature = false,
  trustedContentSignatureRoot = null
) {
  let request = await downloadXMLWithRequest(
    url,
    allowNonBuiltIn,
    allowedCerts
  );
  if (verifyContentSignature) {
    await verifyGmpContentSignature(
      request.response,
      request.getResponseHeader("content-signature"),
      trustedContentSignatureRoot
    );
  }
  return request.responseXML;
}

/**
 * Parses a list of add-ons from a DOM document.
 *
 * @param  document
 *         The DOM document to parse.
 * @return null if there is no <addons> element otherwise an object containing
 *         an array of the addons listed and a field notifying whether the
 *         fallback was used.
 */
function parseXML(document) {
  // Check that the root element is correct
  if (document.documentElement.localName != "updates") {
    let err = new Error(
      "got node name: " +
        document.documentElement.localName +
        ", expected: updates"
    );
    err.addonCheckerErr = ProductAddonChecker.XML_PARSE_ERR;
    throw err;
  }

  // Check if there are any addons elements in the updates element
  let addons = document.querySelector("updates:root > addons");
  if (!addons) {
    return null;
  }

  let results = [];
  let addonList = document.querySelectorAll("updates:root > addons > addon");
  for (let addonElement of addonList) {
    let addon = {};

    for (let name of [
      "id",
      "URL",
      "hashFunction",
      "hashValue",
      "version",
      "size",
    ]) {
      if (addonElement.hasAttribute(name)) {
        addon[name] = addonElement.getAttribute(name);
      }
    }
    addon.size = Number(addon.size) || undefined;

    results.push(addon);
  }

  return {
    usedFallback: false,
    addons: results,
  };
}

/**
 * Downloads file from a URL using ServiceRequest.
 *
 * @param  url
 *         The url to download from.
 * @param  options (optional)
 * @param  options.httpsOnlyNoUpgrade
 *         Prevents upgrade to https:// when HTTPS-Only Mode is enabled.
 * @return a promise that resolves to the path of a temporary file or rejects
 *         with a JS exception in case of error.
 */
function downloadFile(url, options = { httpsOnlyNoUpgrade: false }) {
  return new Promise((resolve, reject) => {
    let sr = new lazy.ServiceRequest();

    sr.onload = function () {
      logger.info("downloadFile File download. status=" + sr.status);
      if (sr.status != 200 && sr.status != 206) {
        reject(Components.Exception("File download failed", sr.status));
        return;
      }
      (async function () {
        const path = await IOUtils.createUniqueFile(
          PathUtils.tempDir,
          "tmpaddon"
        );
        logger.info(`Downloaded file will be saved to ${path}`);
        await IOUtils.write(path, new Uint8Array(sr.response));

        return path;
      })().then(resolve, reject);
    };

    let fail = event => {
      let request = event.target;
      let status = getRequestStatus(request);
      let message =
        "Failed downloading via ServiceRequest, status: " +
        status.status +
        ", channelStatus: " +
        status.channelStatus +
        ", errorCode: " +
        status.errorCode +
        ", reason: " +
        event.type;
      logger.warn(message);
      let ex = new Error(message);
      ex.status = status.status;
      reject(ex);
    };
    sr.addEventListener("error", fail);
    sr.addEventListener("abort", fail);

    sr.responseType = "arraybuffer";
    try {
      sr.open("GET", url);
      if (options.httpsOnlyNoUpgrade) {
        sr.channel.loadInfo.httpsOnlyStatus |= Ci.nsILoadInfo.HTTPS_ONLY_EXEMPT;
      }
      // Allow deprecated HTTP request from SystemPrincipal
      sr.channel.loadInfo.allowDeprecatedSystemRequests = true;
      sr.send(null);
    } catch (ex) {
      reject(ex);
    }
  });
}

/**
 * Verifies that a downloaded file matches what was expected.
 *
 * @param  properties
 *         The properties to check, `hashFunction` with `hashValue`
 *         are supported. Any properties missing won't be checked.
 * @param  path
 *         The path of the file to check.
 * @return a promise that resolves if the file matched or rejects with a JS
 *         exception in case of error.
 */
var verifyFile = async function (properties, path) {
  if (properties.size !== undefined) {
    let stat = await IOUtils.stat(path);
    if (stat.size != properties.size) {
      throw new Error(
        "Downloaded file was " +
          stat.size +
          " bytes but expected " +
          properties.size +
          " bytes."
      );
    }
  }

  if (properties.hashFunction !== undefined) {
    let expectedDigest = properties.hashValue.toLowerCase();
    let digest = await IOUtils.computeHexDigest(path, properties.hashFunction);
    if (digest != expectedDigest) {
      throw new Error(
        "Hash was `" + digest + "` but expected `" + expectedDigest + "`."
      );
    }
  }
};

export const ProductAddonChecker = {
  // More specific error names to help debug and report failures.
  NETWORK_REQUEST_ERR: "NetworkRequestError",
  NETWORK_TIMEOUT_ERR: "NetworkTimeoutError",
  ABORT_ERR: "AbortError", // Doesn't have network prefix to work with existing convention.
  VERIFICATION_MISSING_DATA_ERR: "VerificationMissingDataError",
  VERIFICATION_FAILED_ERR: "VerificationFailedError",
  VERIFICATION_INVALID_ERR: "VerificationInvalidError",
  XML_PARSE_ERR: "XMLParseError",

  /**
   * Downloads a list of add-ons from a URL optionally testing the SSL
   * certificate for certain attributes, and/or testing the content signature.
   *
   * @param  url
   *         The url to download from.
   * @param  allowNonBuiltIn
   *         Whether to trust SSL certificates without a built-in CA issuer.
   * @param  allowedCerts
   *         The list of certificate attributes to match the SSL certificate
   *         against or null to skip checks.
   * @param  verifyContentSignature
   *         When true, will verify the content signature information from the
   *         response header. Failure to verify will result in an error.
   * @param  trustedContentSignatureRoot
   *         The trusted root to use for certificate validation.
   *         Must be set if verifyContentSignature is true.
   * @return a promise that resolves to an object containing the list of add-ons
   *         and whether the local fallback was used, or rejects with a JS
   *         exception in case of error. In the case of an error, a best effort
   *         is made to set the error addonCheckerErr property to one of the
   *         more specific names used by the product addon checker.
   */
  getProductAddonList(
    url,
    allowNonBuiltIn = false,
    allowedCerts = null,
    verifyContentSignature = false,
    trustedContentSignatureRoot = null
  ) {
    return downloadXML(
      url,
      allowNonBuiltIn,
      allowedCerts,
      verifyContentSignature,
      trustedContentSignatureRoot
    ).then(parseXML);
  },

  /**
   * Downloads an add-on to a local file and checks that it matches the expected
   * file. The caller is responsible for deleting the temporary file returned.
   *
   * @param  addon
   *         The addon to download.
   * @param  options (optional)
   * @param  options.httpsOnlyNoUpgrade
   *         Prevents upgrade to https:// when HTTPS-Only Mode is enabled.
   * @return a promise that resolves to the temporary file downloaded or rejects
   *         with a JS exception in case of error.
   */
  async downloadAddon(addon, options = { httpsOnlyNoUpgrade: false }) {
    let path = await downloadFile(addon.URL, options);
    try {
      await verifyFile(addon, path);
      return path;
    } catch (e) {
      await IOUtils.remove(path);
      throw e;
    }
  },
};

// For test use only.
export const ProductAddonCheckerTestUtils = {
  /**
   * Used to override ServiceRequest calls with a mock request.
   * @param mockRequest The mocked ServiceRequest object.
   * @param callback Method called with the overridden ServiceRequest. The override
   *        is undone after the callback returns.
   */
  async overrideServiceRequest(mockRequest, callback) {
    let originalServiceRequest = lazy.ServiceRequest;
    lazy.ServiceRequest = function () {
      return mockRequest;
    };
    try {
      return await callback();
    } finally {
      lazy.ServiceRequest = originalServiceRequest;
    }
  },
};
PK
!<��a�@@modules/CertUtils.sys.mjs/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Ce = Components.Exception;

/**
 * Reads a set of expected certificate attributes from preferences. The returned
 * array can be passed to validateCert or checkCert to validate that a
 * certificate matches the expected attributes. The preferences should look like
 * this:
 *   prefix.1.attribute1
 *   prefix.1.attribute2
 *   prefix.2.attribute1
 *   etc.
 * Each numeric branch contains a set of required attributes for a single
 * certificate. Having multiple numeric branches means that multiple
 * certificates would be accepted by validateCert.
 *
 * @param  aPrefBranch
 *         The prefix for all preferences, should end with a ".".
 * @return An array of JS objects with names / values corresponding to the
 *         expected certificate's attribute names / values.
 */
function readCertPrefs(aPrefBranch) {
  if (!Services.prefs.getBranch(aPrefBranch).getChildList("").length) {
    return null;
  }

  let certs = [];
  let counter = 1;
  while (true) {
    let prefBranchCert = Services.prefs.getBranch(aPrefBranch + counter + ".");
    let prefCertAttrs = prefBranchCert.getChildList("");
    if (!prefCertAttrs.length) {
      break;
    }

    let certAttrs = {};
    for (let prefCertAttr of prefCertAttrs) {
      certAttrs[prefCertAttr] = prefBranchCert.getCharPref(prefCertAttr);
    }

    certs.push(certAttrs);
    counter++;
  }

  return certs;
}

/**
 * Verifies that an nsIX509Cert matches the expected certificate attribute
 * values.
 *
 * @param  aCertificate
 *         The nsIX509Cert to compare to the expected attributes.
 * @param  aCerts
 *         An array of JS objects with names / values corresponding to the
 *         expected certificate's attribute names / values. If this is null or
 *         an empty array then no checks are performed.
 * @throws NS_ERROR_ILLEGAL_VALUE if a certificate attribute name from the
 *         aCerts param does not exist or the value for a certificate attribute
 *         from the aCerts param is different than the expected value or
 *         aCertificate wasn't specified and aCerts is not null or an empty
 *         array.
 */
function validateCert(aCertificate, aCerts) {
  // If there are no certificate requirements then just exit
  if (!aCerts || !aCerts.length) {
    return;
  }

  if (!aCertificate) {
    const missingCertErr = "A required certificate was not present.";
    console.error(missingCertErr);
    throw new Ce(missingCertErr, Cr.NS_ERROR_ILLEGAL_VALUE);
  }

  var errors = [];
  for (var i = 0; i < aCerts.length; ++i) {
    var error = false;
    var certAttrs = aCerts[i];
    for (var name in certAttrs) {
      if (!(name in aCertificate)) {
        error = true;
        errors.push(
          "Expected attribute '" + name + "' not present in certificate."
        );
        break;
      }
      if (aCertificate[name] != certAttrs[name]) {
        error = true;
        errors.push(
          "Expected certificate attribute '" +
            name +
            "' " +
            "value incorrect, expected: '" +
            certAttrs[name] +
            "', got: '" +
            aCertificate[name] +
            "'."
        );
        break;
      }
    }

    if (!error) {
      break;
    }
  }

  if (error) {
    errors.forEach(Cu.reportError.bind(Cu));
    const certCheckErr =
      "Certificate checks failed. See previous errors for details.";
    console.error(certCheckErr);
    throw new Ce(certCheckErr, Cr.NS_ERROR_ILLEGAL_VALUE);
  }
}

/**
 * Checks if the connection must be HTTPS and if so, only allows built-in
 * certificates and validates application specified certificate attribute
 * values.
 * See bug 340198 and bug 544442.
 *
 * @param  aChannel
 *         The nsIChannel that will have its certificate checked.
 * @param  aAllowNonBuiltInCerts (optional)
 *         When true certificates that aren't builtin are allowed. When false
 *         or not specified the certificate must be a builtin certificate.
 * @param  aCerts (optional)
 *         An array of JS objects with names / values corresponding to the
 *         channel's expected certificate's attribute names / values. If it
 *         isn't null or not specified the the scheme for the channel's
 *         originalURI must be https.
 * @throws NS_ERROR_UNEXPECTED if a certificate is expected and the URI scheme
 *         is not https.
 *         NS_ERROR_ILLEGAL_VALUE if a certificate attribute name from the
 *         aCerts param does not exist or the value for a certificate attribute
 *         from the aCerts  param is different than the expected value.
 *         NS_ERROR_ABORT if the certificate issuer is not built-in.
 */
function checkCert(aChannel, aAllowNonBuiltInCerts, aCerts) {
  if (!aChannel.originalURI.schemeIs("https")) {
    // Require https if there are certificate values to verify
    if (aCerts) {
      throw new Ce(
        "SSL is required and URI scheme is not https.",
        Cr.NS_ERROR_UNEXPECTED
      );
    }
    return;
  }

  let secInfo = aChannel.securityInfo;
  let cert = secInfo.serverCert;

  validateCert(cert, aCerts);

  if (aAllowNonBuiltInCerts === true) {
    return;
  }
  const certNotBuiltInErr = "Certificate issuer is not built-in.";
  if (!secInfo.isBuiltCertChainRootBuiltInRoot) {
    throw new Ce(certNotBuiltInErr, Cr.NS_ERROR_ABORT);
  }
}

/**
 * This class implements nsIChannelEventSink. Its job is to perform extra checks
 * on the certificates used for some connections when those connections
 * redirect.
 *
 * @param  aAllowNonBuiltInCerts (optional)
 *         When true certificates that aren't builtin are allowed. When false
 *         or not specified the certificate must be a builtin certificate.
 */
function BadCertHandler(aAllowNonBuiltInCerts) {
  this.allowNonBuiltInCerts = aAllowNonBuiltInCerts;
}
BadCertHandler.prototype = {
  // nsIChannelEventSink
  asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
    if (this.allowNonBuiltInCerts) {
      callback.onRedirectVerifyCallback(Cr.NS_OK);
      return;
    }

    // make sure the certificate of the old channel checks out before we follow
    // a redirect from it.  See bug 340198.
    // Don't call checkCert for internal redirects. See bug 569648.
    if (!(flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL)) {
      checkCert(oldChannel);
    }

    callback.onRedirectVerifyCallback(Cr.NS_OK);
  },

  // nsIInterfaceRequestor
  getInterface(iid) {
    return this.QueryInterface(iid);
  },

  // nsISupports
  QueryInterface: ChromeUtils.generateQI([
    "nsIChannelEventSink",
    "nsIInterfaceRequestor",
  ]),
};

export var CertUtils = {
  BadCertHandler,
  checkCert,
  readCertPrefs,
  validateCert,
};
PK
!<��"T"T"$modules/PushBroadcastService.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  JSONFile: "resource://gre/modules/JSONFile.sys.mjs",
  PushService: "resource://gre/modules/PushService.sys.mjs",
});

// BroadcastService is exported for test purposes.
// We are supposed to ignore any updates with this version.
const DUMMY_VERSION_STRING = "____NOP____";

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  let { ConsoleAPI } = ChromeUtils.importESModule(
    "resource://gre/modules/Console.sys.mjs"
  );
  return new ConsoleAPI({
    maxLogLevelPref: "dom.push.loglevel",
    prefix: "BroadcastService",
  });
});

class InvalidSourceInfo extends Error {
  constructor(message) {
    super(message);
    this.name = "InvalidSourceInfo";
  }
}

const BROADCAST_SERVICE_VERSION = 1;

export var BroadcastService = class {
  constructor(pushService, path) {
    this.PHASES = {
      HELLO: "hello",
      REGISTER: "register",
      BROADCAST: "broadcast",
    };

    this.pushService = pushService;
    this.jsonFile = new lazy.JSONFile({
      path,
      dataPostProcessor: this._initializeJSONFile,
    });
    this.initializePromise = this.jsonFile.load();
  }

  /**
   * Convert the listeners from our on-disk format to the format
   * needed by a hello message.
   */
  async getListeners() {
    await this.initializePromise;
    return Object.entries(this.jsonFile.data.listeners).reduce(
      (acc, [k, v]) => {
        acc[k] = v.version;
        return acc;
      },
      {}
    );
  }

  _initializeJSONFile(data) {
    if (!data.version) {
      data.version = BROADCAST_SERVICE_VERSION;
    }
    if (!data.hasOwnProperty("listeners")) {
      data.listeners = {};
    }
    return data;
  }

  /**
   * Reset to a state akin to what you would get in a new profile.
   * In particular, wipe anything from storage.
   *
   * Used mainly for testing.
   */
  async _resetListeners() {
    await this.initializePromise;
    this.jsonFile.data = this._initializeJSONFile({});
    this.initializePromise = Promise.resolve();
  }

  /**
   * Ensure that a sourceInfo is correct (has the expected fields).
   */
  _validateSourceInfo(sourceInfo) {
    const { moduleURI, symbolName } = sourceInfo;
    if (typeof moduleURI !== "string") {
      throw new InvalidSourceInfo(
        `moduleURI must be a string (got ${typeof moduleURI})`
      );
    }
    if (typeof symbolName !== "string") {
      throw new InvalidSourceInfo(
        `symbolName must be a string (got ${typeof symbolName})`
      );
    }
  }

  /**
   * Add an entry for a given listener if it isn't present, or update
   * one if it is already present.
   *
   * Note that this means only a single listener can be set for a
   * given subscription. This is a limitation in the current API that
   * stems from the fact that there can only be one source of truth
   * for the subscriber's version. As a workaround, you can define a
   * listener which calls multiple other listeners.
   *
   * @param {string} broadcastId The broadcastID to listen for
   * @param {string} version The most recent version we have for
   *   updates from this broadcastID
   * @param {Object} sourceInfo A description of the handler for
   *   updates on this broadcastID
   */
  async addListener(broadcastId, version, sourceInfo) {
    lazy.console.info(
      "addListener: adding listener",
      broadcastId,
      version,
      sourceInfo
    );
    await this.initializePromise;
    this._validateSourceInfo(sourceInfo);
    if (typeof version !== "string") {
      throw new TypeError("version should be a string");
    }
    if (!version) {
      throw new TypeError("version should not be an empty string");
    }

    const isNew = !this.jsonFile.data.listeners.hasOwnProperty(broadcastId);
    const oldVersion =
      !isNew && this.jsonFile.data.listeners[broadcastId].version;
    if (!isNew && oldVersion != version) {
      lazy.console.warn(
        "Versions differ while adding listener for",
        broadcastId,
        ". Got",
        version,
        "but JSON file says",
        oldVersion,
        "."
      );
    }

    // Update listeners before telling the pushService to subscribe,
    // in case it would disregard the update in the small window
    // between getting listeners and setting state to RUNNING.
    //
    // Keep the old version (if we have it) because Megaphone is
    // really the source of truth for the current version of this
    // broadcaster, and the old version is whatever we've either
    // gotten from Megaphone or what we've told to Megaphone and
    // haven't been corrected.
    this.jsonFile.data.listeners[broadcastId] = {
      version: oldVersion || version,
      sourceInfo,
    };
    this.jsonFile.saveSoon();

    if (isNew) {
      await this.pushService.subscribeBroadcast(broadcastId, version);
    }
  }

  /**
   * Call the listeners of the specified broadcasts.
   *
   * @param {Array<Object>} broadcasts Map between broadcast ids and versions.
   * @param {Object} context Additional information about the context in which the
   *  broadcast notification was originally received. This is transmitted to listeners.
   * @param {String} context.phase One of `BroadcastService.PHASES`
   */
  async receivedBroadcastMessage(broadcasts, context) {
    lazy.console.info("receivedBroadcastMessage:", broadcasts, context);
    await this.initializePromise;
    for (const broadcastId in broadcasts) {
      const version = broadcasts[broadcastId];
      if (version === DUMMY_VERSION_STRING) {
        lazy.console.info(
          "Ignoring",
          version,
          "because it's the dummy version"
        );
        continue;
      }
      // We don't know this broadcastID. This is probably a bug?
      if (!this.jsonFile.data.listeners.hasOwnProperty(broadcastId)) {
        lazy.console.warn(
          "receivedBroadcastMessage: unknown broadcastId",
          broadcastId
        );
        continue;
      }

      const { sourceInfo } = this.jsonFile.data.listeners[broadcastId];
      try {
        this._validateSourceInfo(sourceInfo);
      } catch (e) {
        lazy.console.error(
          "receivedBroadcastMessage: malformed sourceInfo",
          sourceInfo,
          e
        );
        continue;
      }

      const { moduleURI, symbolName } = sourceInfo;

      let module;
      try {
        module = ChromeUtils.importESModule(moduleURI);
      } catch (e) {
        lazy.console.error(
          "receivedBroadcastMessage: couldn't invoke",
          broadcastId,
          "because import of module",
          moduleURI,
          "failed",
          e
        );
        continue;
      }

      if (!module[symbolName]) {
        lazy.console.error(
          "receivedBroadcastMessage: couldn't invoke",
          broadcastId,
          "because module",
          moduleURI,
          "missing attribute",
          symbolName
        );
        continue;
      }

      const handler = module[symbolName];

      if (!handler.receivedBroadcastMessage) {
        lazy.console.error(
          "receivedBroadcastMessage: couldn't invoke",
          broadcastId,
          "because handler returned by",
          `${moduleURI}.${symbolName}`,
          "has no receivedBroadcastMessage method"
        );
        continue;
      }

      try {
        await handler.receivedBroadcastMessage(version, broadcastId, context);
      } catch (e) {
        lazy.console.error(
          "receivedBroadcastMessage: handler for",
          broadcastId,
          "threw error:",
          e
        );
        continue;
      }

      // Broadcast message applied successfully. Update the version we
      // received if it's different than the one we had.  We don't
      // enforce an ordering here (i.e. we use != instead of <)
      // because we don't know what the ordering of the service's
      // versions is going to be.
      if (this.jsonFile.data.listeners[broadcastId].version != version) {
        this.jsonFile.data.listeners[broadcastId].version = version;
        this.jsonFile.saveSoon();
      }
    }
  }

  // For test only.
  _saveImmediately() {
    return this.jsonFile._save();
  }
};

function initializeBroadcastService() {
  // Fallback path for xpcshell tests.
  let path = "broadcast-listeners.json";
  try {
    if (PathUtils.profileDir) {
      // Real path for use in a real profile.
      path = PathUtils.join(PathUtils.profileDir, path);
    }
  } catch (e) {}
  return new BroadcastService(lazy.PushService, path);
}

export var pushBroadcastService = initializeBroadcastService();
PK
!<�q�'',modules/netwerk-dns/PublicSuffixList.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { RemoteSettings } from "resource://services-settings/remote-settings.sys.mjs";

const FileUtils = ChromeUtils.importESModule(
  "resource://gre/modules/FileUtils.sys.mjs"
).FileUtils;

const RECORD_ID = "tld-dafsa";
const SIGNAL = "public-suffix-list-updated";

export const PublicSuffixList = {
  CLIENT: RemoteSettings("public-suffix-list"),

  init() {
    // Only initialize once.
    if (this._initialized) {
      return;
    }
    this._initialized = true;

    this.CLIENT.on("sync", this.onUpdate.bind(this));
    /* We have a single record for this collection. Let's see if we already have it locally.
     * Note that on startup, we don't need to synchronize immediately on new profiles.
     */
    this.CLIENT.get({ syncIfEmpty: false, filters: { id: RECORD_ID } })
      .then(async records => {
        if (records.length == 1) {
          // Get the downloaded file URI (most likely to be a no-op here, since file will exist).
          const fileURI = await this.CLIENT.attachments.downloadToDisk(
            records[0]
          );
          // Send a signal so that the C++ code loads the updated list on startup.
          this.notifyUpdate(fileURI);
        }
      })
      .catch(err => console.error(err));
  },

  /**
   * This method returns the path to the file based on the file uri received
   * Example:
   * On windows "file://C:/Users/AppData/main/public-suffix-list/dafsa.bin"
   * will be converted to "C:\\Users\\main\\public-suffix-list\\dafsa.bin
   *
   * On macOS/linux "file:///home/main/public-suffix-list/dafsa.bin"
   * will be converted to "/home/main/public-suffix-list/dafsa.bin"
   */
  getFilePath(fileURI) {
    const uri = Services.io.newURI(fileURI);
    const file = uri.QueryInterface(Ci.nsIFileURL).file;
    return file.path;
  },

  notifyUpdate(fileURI) {
    if (!Services.prefs.getBoolPref("network.psl.onUpdate_notify", false)) {
      // Updating the PSL while Firefox is running could cause principals to
      // have a different base domain before/after the update.
      // See bug 1582647 comment 30
      return;
    }

    const filePath = this.getFilePath(fileURI);
    const nsifile = new FileUtils.File(filePath);
    /* Send a signal to be read by the C++, the method
     * ::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
     * at netwerk/dns/nsEffectiveTLDService.cpp
     */
    Services.obs.notifyObservers(
      nsifile, // aSubject
      SIGNAL, // aTopic
      filePath // aData
    );
  },

  async onUpdate({ data: { created, updated, deleted } }) {
    // In theory, this will never happen, we will never delete the record.
    if (deleted.length == 1) {
      await this.CLIENT.attachments.deleteFromDisk(deleted[0]);
    }
    // Handle creation and update the same way
    const changed = created.concat(updated.map(u => u.new));
    /* In theory, we should never have more than one record. And if we receive
     * this event, it's because the single record was updated.
     */
    if (changed.length != 1) {
      console.warn("Unsupported sync event for Public Suffix List");
      return;
    }
    // Download the updated file.
    let fileURI;
    try {
      fileURI = await this.CLIENT.attachments.downloadToDisk(changed[0]);
    } catch (err) {
      console.error(err);
      return;
    }

    // Notify the C++ part to reload it from disk.
    this.notifyUpdate(fileURI);
  },
};
PK
!<Y�O�Y�Y*modules/psm/RemoteSecuritySettings.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { RemoteSettings } from "resource://services-settings/remote-settings.sys.mjs";

import { X509 } from "resource://gre/modules/psm/X509.sys.mjs";

const SECURITY_STATE_BUCKET = "security-state";
const SECURITY_STATE_SIGNER = "onecrl.content-signature.mozilla.org";

const INTERMEDIATES_DL_PER_POLL_PREF =
  "security.remote_settings.intermediates.downloads_per_poll";
const INTERMEDIATES_DL_PARALLEL_REQUESTS =
  "security.remote_settings.intermediates.parallel_downloads";
const INTERMEDIATES_ENABLED_PREF =
  "security.remote_settings.intermediates.enabled";
const LOGLEVEL_PREF = "browser.policies.loglevel";

const CRLITE_FILTERS_ENABLED_PREF =
  "security.remote_settings.crlite_filters.enabled";

const CRLITE_FILTER_CHANNEL_PREF = "security.pki.crlite_channel";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "gTextDecoder", () => new TextDecoder());

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  let { ConsoleAPI } = ChromeUtils.importESModule(
    "resource://gre/modules/Console.sys.mjs"
  );
  return new ConsoleAPI({
    prefix: "RemoteSecuritySettings",
    // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
    // messages during development. See LOG_LEVELS in Console.sys.mjs for details.
    maxLogLevel: "error",
    maxLogLevelPref: LOGLEVEL_PREF,
  });
});

// Converts a JS string to an array of bytes consisting of the char code at each
// index in the string.
function stringToBytes(s) {
  let b = [];
  for (let i = 0; i < s.length; i++) {
    b.push(s.charCodeAt(i));
  }
  return b;
}

// Converts an array of bytes to a JS string using fromCharCode on each byte.
function bytesToString(bytes) {
  if (bytes.length > 65535) {
    throw new Error("input too long for bytesToString");
  }
  return String.fromCharCode.apply(null, bytes);
}

class CRLiteCoverage {
  constructor(b64LogID, minTimestamp, maxTimestamp) {
    this.b64LogID = b64LogID;
    this.minTimestamp = minTimestamp;
    this.maxTimestamp = maxTimestamp;
  }
}
CRLiteCoverage.prototype.QueryInterface = ChromeUtils.generateQI([
  "nsICRLiteCoverage",
]);

class CertInfo {
  constructor(cert, subject) {
    this.cert = cert;
    this.subject = subject;
    this.trust = Ci.nsICertStorage.TRUST_INHERIT;
  }
}
CertInfo.prototype.QueryInterface = ChromeUtils.generateQI(["nsICertInfo"]);

class RevocationState {
  constructor(state) {
    this.state = state;
  }
}

class IssuerAndSerialRevocationState extends RevocationState {
  constructor(issuer, serial, state) {
    super(state);
    this.issuer = issuer;
    this.serial = serial;
  }
}
IssuerAndSerialRevocationState.prototype.QueryInterface =
  ChromeUtils.generateQI(["nsIIssuerAndSerialRevocationState"]);

class SubjectAndPubKeyRevocationState extends RevocationState {
  constructor(subject, pubKey, state) {
    super(state);
    this.subject = subject;
    this.pubKey = pubKey;
  }
}
SubjectAndPubKeyRevocationState.prototype.QueryInterface =
  ChromeUtils.generateQI(["nsISubjectAndPubKeyRevocationState"]);

function setRevocations(certStorage, revocations) {
  return new Promise(resolve =>
    certStorage.setRevocations(revocations, resolve)
  );
}

/**
 * Helper function that returns a promise that will resolve with whether or not
 * the nsICertStorage implementation has prior data of the given type.
 *
 * @param {Integer} dataType a Ci.nsICertStorage.DATA_TYPE_* constant
 *                           indicating the type of data

 * @returns {Promise} a promise that will resolve with true if the data type is
 *                   present
 */
function hasPriorData(dataType) {
  let certStorage = Cc["@mozilla.org/security/certstorage;1"].getService(
    Ci.nsICertStorage
  );
  return new Promise(resolve => {
    certStorage.hasPriorData(dataType, (rv, hasPriorData) => {
      if (rv == Cr.NS_OK) {
        resolve(hasPriorData);
      } else {
        // If calling hasPriorData failed, assume we need to reload everything
        // (even though it's unlikely doing so will succeed).
        resolve(false);
      }
    });
  });
}

/**
 * Revoke the appropriate certificates based on the records from the blocklist.
 *
 * @param {object} options
 * @param {object} options.data Current records in the local db.
 * @param {Array} options.data.current
 * @param {Array} options.data.created
 * @param {Array} options.data.updated
 * @param {Array} options.data.deleted
 */
const updateCertBlocklist = async function ({
  data: { current, created, updated, deleted },
}) {
  let items = [];

  // See if we have prior revocation data (this can happen when we can't open
  // the database and we have to re-create it (see bug 1546361)).
  let hasPriorRevocationData = await hasPriorData(
    Ci.nsICertStorage.DATA_TYPE_REVOCATION
  );

  // If we don't have prior data, make it so we re-load everything.
  if (!hasPriorRevocationData) {
    deleted = [];
    updated = [];
    created = current;
  }

  let toDelete = deleted.concat(updated.map(u => u.old));
  for (let item of toDelete) {
    if (item.issuerName && item.serialNumber) {
      items.push(
        new IssuerAndSerialRevocationState(
          item.issuerName,
          item.serialNumber,
          Ci.nsICertStorage.STATE_UNSET
        )
      );
    } else if (item.subject && item.pubKeyHash) {
      items.push(
        new SubjectAndPubKeyRevocationState(
          item.subject,
          item.pubKeyHash,
          Ci.nsICertStorage.STATE_UNSET
        )
      );
    }
  }

  const toAdd = created.concat(updated.map(u => u.new));

  for (let item of toAdd) {
    if (item.issuerName && item.serialNumber) {
      items.push(
        new IssuerAndSerialRevocationState(
          item.issuerName,
          item.serialNumber,
          Ci.nsICertStorage.STATE_ENFORCE
        )
      );
    } else if (item.subject && item.pubKeyHash) {
      items.push(
        new SubjectAndPubKeyRevocationState(
          item.subject,
          item.pubKeyHash,
          Ci.nsICertStorage.STATE_ENFORCE
        )
      );
    }
  }

  try {
    const certList = Cc["@mozilla.org/security/certstorage;1"].getService(
      Ci.nsICertStorage
    );
    await setRevocations(certList, items);
  } catch (e) {
    lazy.log.error(e);
  }
};

export var RemoteSecuritySettings = {
  _initialized: false,
  OneCRLBlocklistClient: null,
  IntermediatePreloadsClient: null,
  CRLiteFiltersClient: null,

  /**
   * Initialize the clients (cheap instantiation) and setup their sync event.
   * This static method is called from BrowserGlue.sys.mjs soon after startup.
   *
   * @returns {object} instantiated clients for security remote settings.
   */
  init() {
    // Avoid repeated initialization (work-around for bug 1730026).
    if (this._initialized) {
      return this;
    }
    this._initialized = true;

    this.OneCRLBlocklistClient = RemoteSettings("onecrl", {
      bucketName: SECURITY_STATE_BUCKET,
      signerName: SECURITY_STATE_SIGNER,
    });
    this.OneCRLBlocklistClient.on("sync", updateCertBlocklist);

    this.IntermediatePreloadsClient = new IntermediatePreloads();

    this.CRLiteFiltersClient = new CRLiteFilters();

    return this;
  },
};

class IntermediatePreloads {
  constructor() {
    this.client = RemoteSettings("intermediates", {
      bucketName: SECURITY_STATE_BUCKET,
      signerName: SECURITY_STATE_SIGNER,
      localFields: ["cert_import_complete"],
    });

    this.client.on("sync", this.onSync.bind(this));
    Services.obs.addObserver(
      this.onObservePollEnd.bind(this),
      "remote-settings:changes-poll-end"
    );

    lazy.log.debug("Intermediate Preloading: constructor");
  }

  async updatePreloadedIntermediates() {
    if (!Services.prefs.getBoolPref(INTERMEDIATES_ENABLED_PREF, true)) {
      lazy.log.debug("Intermediate Preloading is disabled");
      Services.obs.notifyObservers(
        null,
        "remote-security-settings:intermediates-updated",
        "disabled"
      );
      return;
    }

    // Download attachments that are awaiting download, up to a max.
    const maxDownloadsPerRun = Services.prefs.getIntPref(
      INTERMEDIATES_DL_PER_POLL_PREF,
      100
    );
    const parallelDownloads = Services.prefs.getIntPref(
      INTERMEDIATES_DL_PARALLEL_REQUESTS,
      8
    );

    // Bug 1519256: Move this to a separate method that's on a separate timer
    // with a higher frequency (so we can attempt to download outstanding
    // certs more than once daily)

    // See if we have prior cert data (this can happen when we can't open the database and we
    // have to re-create it (see bug 1546361)).
    let hasPriorCertData = await hasPriorData(
      Ci.nsICertStorage.DATA_TYPE_CERTIFICATE
    );
    // If we don't have prior data, make it so we re-load everything.
    if (!hasPriorCertData) {
      let current;
      try {
        current = await this.client.db.list();
      } catch (err) {
        lazy.log.warn(
          `Unable to list intermediate preloading collection: ${err}`
        );
        return;
      }
      const toReset = current.filter(record => record.cert_import_complete);
      try {
        await this.client.db.importChanges(
          undefined, // do not touch metadata.
          undefined, // do not touch collection timestamp.
          toReset.map(r => ({ ...r, cert_import_complete: false }))
        );
      } catch (err) {
        lazy.log.warn(
          `Unable to update intermediate preloading collection: ${err}`
        );
        return;
      }
    }

    try {
      // fetches a bundle containing all attachments, download() is called further down to force a re-sync on hash mismatches for old data or if the bundle fails to download
      await this.client.attachments.cacheAll();
    } catch (err) {
      lazy.log.warn(
        `Error fetching/caching attachment bundle in intermediate preloading: ${err}`
      );
    }

    let current;
    try {
      current = await this.client.db.list();
    } catch (err) {
      lazy.log.warn(
        `Unable to list intermediate preloading collection: ${err}`
      );
      return;
    }
    const waiting = current.filter(record => !record.cert_import_complete);

    lazy.log.debug(
      `There are ${waiting.length} intermediates awaiting download.`
    );
    if (!waiting.length) {
      // Nothing to do.
      Services.obs.notifyObservers(
        null,
        "remote-security-settings:intermediates-updated",
        "success"
      );
      return;
    }

    let toDownload = waiting.slice(0, maxDownloadsPerRun);
    let recordsCertsAndSubjects = [];
    for (let i = 0; i < toDownload.length; i += parallelDownloads) {
      const chunk = toDownload.slice(i, i + parallelDownloads);
      const downloaded = await Promise.all(
        chunk.map(record => this.maybeDownloadAttachment(record))
      );
      recordsCertsAndSubjects = recordsCertsAndSubjects.concat(downloaded);
    }

    let certInfos = [];
    let recordsToUpdate = [];
    for (let { record, cert, subject } of recordsCertsAndSubjects) {
      if (cert && subject) {
        certInfos.push(new CertInfo(cert, subject));
        recordsToUpdate.push(record);
      }
    }
    const certStorage = Cc["@mozilla.org/security/certstorage;1"].getService(
      Ci.nsICertStorage
    );
    let result = await new Promise(resolve => {
      certStorage.addCerts(certInfos, resolve);
    }).catch(err => err);
    if (result != Cr.NS_OK) {
      lazy.log.error(`certStorage.addCerts failed: ${result}`);
      return;
    }
    try {
      await this.client.db.importChanges(
        undefined, // do not touch metadata.
        undefined, // do not touch collection timestamp.
        recordsToUpdate.map(r => ({ ...r, cert_import_complete: true }))
      );
    } catch (err) {
      lazy.log.warn(
        `Unable to update intermediate preloading collection: ${err}`
      );
      return;
    }

    Services.obs.notifyObservers(
      null,
      "remote-security-settings:intermediates-updated",
      "success"
    );
  }

  async onObservePollEnd(subject, topic) {
    lazy.log.debug(`onObservePollEnd ${subject} ${topic}`);

    try {
      await this.updatePreloadedIntermediates();
    } catch (err) {
      lazy.log.warn(`Unable to update intermediate preloads: ${err}`);
    }
  }

  // This method returns a promise to RemoteSettingsClient.maybeSync method.
  async onSync({ data: { deleted } }) {
    if (!Services.prefs.getBoolPref(INTERMEDIATES_ENABLED_PREF, true)) {
      lazy.log.debug("Intermediate Preloading is disabled");
      return;
    }

    lazy.log.debug(`Removing ${deleted.length} Intermediate certificates`);
    await this.removeCerts(deleted);
  }

  /**
   * Attempts to download the attachment, assuming it's not been processed
   * already. Does not retry, and always resolves (e.g., does not reject upon
   * failure.) Errors are reported via console.error.
   *
   * @param  {AttachmentRecord} record defines which data to obtain
   * @returns {Promise}          a Promise that will resolve to an object with the properties
   *                            record, cert, and subject. record is the original record.
   *                            cert is the base64-encoded bytes of the downloaded certificate (if
   *                            downloading was successful), and null otherwise.
   *                            subject is the base64-encoded bytes of the subject distinguished
   *                            name of the same.
   */
  async maybeDownloadAttachment(record) {
    let result = { record, cert: null, subject: null };

    let dataAsString = null;
    try {
      let { buffer } = await this.client.attachments.download(record, {
        retries: 0,
        checkHash: true,
      });
      dataAsString = lazy.gTextDecoder.decode(new Uint8Array(buffer));
    } catch (err) {
      if (err.name == "BadContentError") {
        lazy.log.debug(`Bad attachment content.`);
      } else {
        lazy.log.error(`Failed to download attachment: ${err}`);
      }
      return result;
    }

    let certBase64;
    let subjectBase64;
    try {
      // split off the header and footer
      certBase64 = dataAsString.split("-----")[2].replace(/\s/g, "");
      // get an array of bytes so we can use X509.sys.mjs
      let certBytes = stringToBytes(atob(certBase64));
      let cert = new X509.Certificate();
      cert.parse(certBytes);
      // get the DER-encoded subject and get a base64-encoded string from it
      // TODO(bug 1542028): add getters for _der and _bytes
      subjectBase64 = btoa(
        bytesToString(cert.tbsCertificate.subject._der._bytes)
      );
    } catch (err) {
      lazy.log.error(`Failed to decode cert: ${err}`);
      return result;
    }
    result.cert = certBase64;
    result.subject = subjectBase64;
    return result;
  }

  async maybeSync(expectedTimestamp, options) {
    return this.client.maybeSync(expectedTimestamp, options);
  }

  async removeCerts(recordsToRemove) {
    let certStorage = Cc["@mozilla.org/security/certstorage;1"].getService(
      Ci.nsICertStorage
    );
    let hashes = recordsToRemove.map(record => record.derHash);
    let result = await new Promise(resolve => {
      certStorage.removeCertsByHashes(hashes, resolve);
    }).catch(err => err);
    if (result != Cr.NS_OK) {
      lazy.log.error(`Failed to remove some intermediate certificates`);
    }
  }
}

// Helper function to compare filters. One filter is "less than" another filter (i.e. it sorts
// earlier) if its timestamp is farther in the past than the other.
function compareFilters(filterA, filterB) {
  return filterA.effectiveTimestamp - filterB.effectiveTimestamp;
}

class CRLiteFilters {
  constructor() {
    this.client = RemoteSettings("cert-revocations", {
      bucketName: SECURITY_STATE_BUCKET,
      signerName: SECURITY_STATE_SIGNER,
      localFields: ["loaded_into_cert_storage"],
    });

    Services.obs.addObserver(
      this.onObservePollEnd.bind(this),
      "remote-settings:changes-poll-end"
    );
    Services.prefs.addObserver(CRLITE_FILTER_CHANNEL_PREF, this);
  }

  async observe(subject, topic, prefName) {
    if (topic == "nsPref:changed" && prefName == CRLITE_FILTER_CHANNEL_PREF) {
      // When the user changes from channel A to channel B, mark the records
      // for channel A (and all other channels) with loaded_into_cert_storage =
      // false. If we don't do this, then the user will fail to reinstall the
      // channel A artifacts if they switch back to channel A.
      let records = await this.client.db.list();
      let newChannel = Services.prefs.getStringPref(
        CRLITE_FILTER_CHANNEL_PREF,
        "none"
      );
      let toReset = records.filter(record => record.channel != newChannel);
      await this.client.db.importChanges(
        undefined, // do not touch metadata.
        undefined, // do not touch collection timestamp.
        toReset.map(r => ({ ...r, loaded_into_cert_storage: false }))
      );
    }
  }

  async getFilteredRecords() {
    let records = await this.client.db.list();
    records = await this.client._filterEntries(records);
    return records;
  }

  async onObservePollEnd() {
    if (!Services.prefs.getBoolPref(CRLITE_FILTERS_ENABLED_PREF, true)) {
      lazy.log.debug("CRLite filter downloading is disabled");
      Services.obs.notifyObservers(
        null,
        "remote-security-settings:crlite-filters-downloaded",
        "disabled"
      );
      return;
    }

    let hasPriorFilter = await hasPriorData(
      Ci.nsICertStorage.DATA_TYPE_CRLITE_FILTER_FULL
    );
    if (!hasPriorFilter) {
      let current = await this.getFilteredRecords();
      let toReset = current.filter(
        record => !record.incremental && record.loaded_into_cert_storage
      );
      await this.client.db.importChanges(
        undefined, // do not touch metadata.
        undefined, // do not touch collection timestamp.
        toReset.map(r => ({ ...r, loaded_into_cert_storage: false }))
      );
    }
    let hasPriorStash = await hasPriorData(
      Ci.nsICertStorage.DATA_TYPE_CRLITE_FILTER_INCREMENTAL
    );
    if (!hasPriorStash) {
      let current = await this.getFilteredRecords();
      let toReset = current.filter(
        record => record.incremental && record.loaded_into_cert_storage
      );
      await this.client.db.importChanges(
        undefined, // do not touch metadata.
        undefined, // do not touch collection timestamp.
        toReset.map(r => ({ ...r, loaded_into_cert_storage: false }))
      );
    }

    let current = await this.getFilteredRecords();
    let fullFilters = current.filter(filter => !filter.incremental);
    if (fullFilters.length < 1) {
      lazy.log.debug("no full CRLite filters to download?");
      Services.obs.notifyObservers(
        null,
        "remote-security-settings:crlite-filters-downloaded",
        "unavailable"
      );
      return;
    }
    fullFilters.sort(compareFilters);
    lazy.log.debug("fullFilters:", fullFilters);
    let fullFilter = fullFilters.pop(); // the most recent filter sorts last
    let incrementalFilters = current.filter(
      filter =>
        // Return incremental filters that are more recent than (i.e. sort later than) the full
        // filter.
        filter.incremental && compareFilters(filter, fullFilter) > 0
    );
    incrementalFilters.sort(compareFilters);
    // Map of id to filter where that filter's parent has the given id.
    let parentIdMap = {};
    for (let filter of incrementalFilters) {
      if (filter.parent in parentIdMap) {
        lazy.log.debug(`filter with parent id ${filter.parent} already seen?`);
      } else {
        parentIdMap[filter.parent] = filter;
      }
    }
    let filtersToDownload = [];
    let nextFilter = fullFilter;
    while (nextFilter) {
      filtersToDownload.push(nextFilter);
      nextFilter = parentIdMap[nextFilter.id];
    }
    const certList = Cc["@mozilla.org/security/certstorage;1"].getService(
      Ci.nsICertStorage
    );
    filtersToDownload = filtersToDownload.filter(
      filter => !filter.loaded_into_cert_storage
    );
    lazy.log.debug("filtersToDownload:", filtersToDownload);
    let filtersDownloaded = [];
    for (let filter of filtersToDownload) {
      try {
        let attachment = await this.client.attachments.downloadAsBytes(filter);
        let bytes = new Uint8Array(attachment);
        lazy.log.debug(
          `Downloaded ${filter.details.name}: ${bytes.length} bytes`
        );
        filter.bytes = bytes;
        filtersDownloaded.push(filter);
      } catch (e) {
        lazy.log.error("failed to download CRLite filter", e);
      }
    }
    let fullFiltersDownloaded = filtersDownloaded.filter(
      filter => !filter.incremental
    );
    if (fullFiltersDownloaded.length) {
      if (fullFiltersDownloaded.length > 1) {
        lazy.log.warn("trying to install more than one full CRLite filter?");
      }
      let filter = fullFiltersDownloaded[0];

      let coverage = [];
      if (filter.coverage) {
        for (let entry of filter.coverage) {
          coverage.push(
            new CRLiteCoverage(
              entry.logID,
              entry.minTimestamp,
              entry.maxTimestamp
            )
          );
        }
      }
      let enrollment = filter.enrolledIssuers ? filter.enrolledIssuers : [];

      await new Promise(resolve => {
        certList.setFullCRLiteFilter(filter.bytes, enrollment, coverage, rv => {
          lazy.log.debug(`setFullCRLiteFilter: ${rv}`);
          resolve();
        });
      });
    }
    let stashes = filtersDownloaded.filter(filter => filter.incremental);
    let totalLength = stashes.reduce(
      (sum, filter) => sum + filter.bytes.length,
      0
    );
    let concatenatedStashes = new Uint8Array(totalLength);
    let offset = 0;
    for (let filter of stashes) {
      concatenatedStashes.set(filter.bytes, offset);
      offset += filter.bytes.length;
    }
    if (concatenatedStashes.length) {
      lazy.log.debug(
        `adding concatenated incremental updates of total length ${concatenatedStashes.length}`
      );
      await new Promise(resolve => {
        certList.addCRLiteStash(concatenatedStashes, rv => {
          lazy.log.debug(`addCRLiteStash: ${rv}`);
          resolve();
        });
      });
    }

    for (let filter of filtersDownloaded) {
      delete filter.bytes;
    }

    await this.client.db.importChanges(
      undefined, // do not touch metadata.
      undefined, // do not touch collection timestamp.
      filtersDownloaded.map(r => ({ ...r, loaded_into_cert_storage: true }))
    );

    Services.obs.notifyObservers(
      null,
      "remote-security-settings:crlite-filters-downloaded",
      `finished;${filtersDownloaded
        .map(filter => filter.details.name)
        .join(",")}`
    );
  }
}
PK
!<�,å�G�Gmodules/psm/X509.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { DER } from "resource://gre/modules/psm/DER.sys.mjs";

const ERROR_UNSUPPORTED_ASN1 = "unsupported asn.1";
const ERROR_TIME_NOT_VALID = "Time not valid";
const ERROR_LIBRARY_FAILURE = "library failure";

const X509v3 = 2;

/**
 * Helper function to read a NULL tag from the given DER.
 *
 * @param {DER} der a DER object to read a NULL from
 * @returns {null} an object representing an ASN.1 NULL
 */
function readNULL(der) {
  return new NULL(der.readTagAndGetContents(DER.NULL));
}

/**
 * Class representing an ASN.1 NULL. When encoded as DER, the only valid value
 * is 05 00, and thus the contents should always be an empty array.
 */
class NULL {
  /**
   * @param {number[]} bytes the contents of the NULL tag (should be empty)
   */
  constructor(bytes) {
    // Lint TODO: bytes should be an empty array
    this._contents = bytes;
  }
}

/**
 * Helper function to read an OBJECT IDENTIFIER from the given DER.
 *
 * @param {DER} der the DER to read an OBJECT IDENTIFIER from
 * @returns {OID} the value of the OBJECT IDENTIFIER
 */
function readOID(der) {
  return new OID(der.readTagAndGetContents(DER.OBJECT_IDENTIFIER));
}

/** Class representing an ASN.1 OBJECT IDENTIFIER */
class OID {
  /**
   * @param {number[]} bytes the encoded contents of the OBJECT IDENTIFIER
   *                   (not including the ASN.1 tag or length bytes)
   */
  constructor(bytes) {
    this._values = [];
    // First octet has value 40 * value1 + value2
    // Lint TODO: validate that value1 is one of {0, 1, 2}
    // Lint TODO: validate that value2 is in [0, 39] if value1 is 0 or 1
    let value1 = Math.floor(bytes[0] / 40);
    let value2 = bytes[0] - 40 * value1;
    this._values.push(value1);
    this._values.push(value2);
    bytes.shift();
    let accumulator = 0;
    // Lint TODO: prevent overflow here
    while (bytes.length) {
      let value = bytes.shift();
      accumulator *= 128;
      if (value > 128) {
        accumulator += value - 128;
      } else {
        accumulator += value;
        this._values.push(accumulator);
        accumulator = 0;
      }
    }
  }
}

/**
 * Class that serves as an abstract base class for more specific classes that
 * represent datatypes from RFC 5280 and others. Given an array of bytes
 * representing the DER encoding of such types, this framework simplifies the
 * process of making a new DER object, attempting to parse the given bytes, and
 * catching and stashing thrown exceptions. Subclasses are to implement
 * parseOverride, which should read from this._der to fill out the structure's
 * values.
 */
class DecodedDER {
  constructor() {
    this._der = null;
    this._error = null;
  }

  /**
   * Returns the first exception encountered when decoding or null if none has
   * been encountered.
   *
   * @returns {Error} the first exception encountered when decoding or null
   */
  get error() {
    return this._error;
  }

  /**
   * Does the actual work of parsing the data. To be overridden by subclasses.
   * If an implementation of parseOverride throws an exception, parse will catch
   * that exception and stash it in the error property. This enables parent
   * levels in a nested decoding hierarchy to continue to decode as much as
   * possible.
   */
  parseOverride() {
    throw new Error(ERROR_LIBRARY_FAILURE);
  }

  /**
   * Public interface to be called to parse all data. Calls parseOverride inside
   * a try/catch block. If an exception is thrown, stashes the error, which can
   * be obtained via the error getter (above).
   *
   * @param {number[]} bytes encoded DER to be decoded
   */
  parse(bytes) {
    this._der = new DER.DERDecoder(bytes);
    try {
      this.parseOverride();
    } catch (e) {
      this._error = e;
    }
  }
}

/**
 * Helper function for reading the next SEQUENCE out of a DER and creating a new
 * DER out of the resulting bytes.
 *
 * @param {DER} der the underlying DER object
 * @returns {DER} the contents of the SEQUENCE
 */
function readSEQUENCEAndMakeDER(der) {
  return new DER.DERDecoder(der.readTagAndGetContents(DER.SEQUENCE));
}

/**
 * Helper function for reading the next item identified by tag out of a DER and
 * creating a new DER out of the resulting bytes.
 *
 * @param {DER} der the underlying DER object
 * @param {number} tag the expected next tag in the DER
 * @returns {DER} the contents of the tag
 */
function readTagAndMakeDER(der, tag) {
  return new DER.DERDecoder(der.readTagAndGetContents(tag));
}

// Certificate  ::=  SEQUENCE  {
//      tbsCertificate       TBSCertificate,
//      signatureAlgorithm   AlgorithmIdentifier,
//      signatureValue       BIT STRING  }
class Certificate extends DecodedDER {
  constructor() {
    super();
    this._tbsCertificate = new TBSCertificate();
    this._signatureAlgorithm = new AlgorithmIdentifier();
    this._signatureValue = [];
  }

  get tbsCertificate() {
    return this._tbsCertificate;
  }

  get signatureAlgorithm() {
    return this._signatureAlgorithm;
  }

  get signatureValue() {
    return this._signatureValue;
  }

  parseOverride() {
    let contents = readSEQUENCEAndMakeDER(this._der);
    this._tbsCertificate.parse(contents.readTLV());
    this._signatureAlgorithm.parse(contents.readTLV());

    let signatureValue = contents.readBIT_STRING();
    if (signatureValue.unusedBits != 0) {
      throw new Error(ERROR_UNSUPPORTED_ASN1);
    }
    this._signatureValue = signatureValue.contents;
    contents.assertAtEnd();
    this._der.assertAtEnd();
  }
}

// TBSCertificate  ::=  SEQUENCE  {
//      version         [0]  EXPLICIT Version DEFAULT v1,
//      serialNumber         CertificateSerialNumber,
//      signature            AlgorithmIdentifier,
//      issuer               Name,
//      validity             Validity,
//      subject              Name,
//      subjectPublicKeyInfo SubjectPublicKeyInfo,
//      issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
//                           -- If present, version MUST be v2 or v3
//      subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
//                           -- If present, version MUST be v2 or v3
//      extensions      [3]  EXPLICIT Extensions OPTIONAL
//                           -- If present, version MUST be v3
//      }
class TBSCertificate extends DecodedDER {
  constructor() {
    super();
    this._version = null;
    this._serialNumber = [];
    this._signature = new AlgorithmIdentifier();
    this._issuer = new Name();
    this._validity = new Validity();
    this._subject = new Name();
    this._subjectPublicKeyInfo = new SubjectPublicKeyInfo();
    this._extensions = [];
  }

  get version() {
    return this._version;
  }

  get serialNumber() {
    return this._serialNumber;
  }

  get signature() {
    return this._signature;
  }

  get issuer() {
    return this._issuer;
  }

  get validity() {
    return this._validity;
  }

  get subject() {
    return this._subject;
  }

  get subjectPublicKeyInfo() {
    return this._subjectPublicKeyInfo;
  }

  get extensions() {
    return this._extensions;
  }

  parseOverride() {
    let contents = readSEQUENCEAndMakeDER(this._der);

    let versionTag = DER.CONTEXT_SPECIFIC | DER.CONSTRUCTED | 0;
    if (!contents.peekTag(versionTag)) {
      this._version = 1;
    } else {
      let versionContents = readTagAndMakeDER(contents, versionTag);
      let versionBytes = versionContents.readTagAndGetContents(DER.INTEGER);
      if (versionBytes.length == 1 && versionBytes[0] == X509v3) {
        this._version = 3;
      } else {
        // Lint TODO: warn about non-v3 certificates (this INTEGER could take up
        // multiple bytes, be negative, and so on).
        this._version = versionBytes;
      }
      versionContents.assertAtEnd();
    }

    let serialNumberBytes = contents.readTagAndGetContents(DER.INTEGER);
    this._serialNumber = serialNumberBytes;
    this._signature.parse(contents.readTLV());
    this._issuer.parse(contents.readTLV());
    this._validity.parse(contents.readTLV());
    this._subject.parse(contents.readTLV());
    this._subjectPublicKeyInfo.parse(contents.readTLV());

    // Lint TODO: warn about unsupported features
    let issuerUniqueIDTag = DER.CONTEXT_SPECIFIC | DER.CONSTRUCTED | 1;
    if (contents.peekTag(issuerUniqueIDTag)) {
      contents.readTagAndGetContents(issuerUniqueIDTag);
    }
    let subjectUniqueIDTag = DER.CONTEXT_SPECIFIC | DER.CONSTRUCTED | 2;
    if (contents.peekTag(subjectUniqueIDTag)) {
      contents.readTagAndGetContents(subjectUniqueIDTag);
    }

    let extensionsTag = DER.CONTEXT_SPECIFIC | DER.CONSTRUCTED | 3;
    if (contents.peekTag(extensionsTag)) {
      let extensionsSequence = readTagAndMakeDER(contents, extensionsTag);
      let extensionsContents = readSEQUENCEAndMakeDER(extensionsSequence);
      while (!extensionsContents.atEnd()) {
        // TODO: parse extensions
        this._extensions.push(extensionsContents.readTLV());
      }
      extensionsContents.assertAtEnd();
      extensionsSequence.assertAtEnd();
    }
    contents.assertAtEnd();
    this._der.assertAtEnd();
  }
}

// AlgorithmIdentifier  ::=  SEQUENCE  {
//      algorithm               OBJECT IDENTIFIER,
//      parameters              ANY DEFINED BY algorithm OPTIONAL  }
class AlgorithmIdentifier extends DecodedDER {
  constructor() {
    super();
    this._algorithm = null;
    this._parameters = null;
  }

  get algorithm() {
    return this._algorithm;
  }

  get parameters() {
    return this._parameters;
  }

  parseOverride() {
    let contents = readSEQUENCEAndMakeDER(this._der);
    this._algorithm = readOID(contents);
    if (!contents.atEnd()) {
      if (contents.peekTag(DER.NULL)) {
        this._parameters = readNULL(contents);
      } else if (contents.peekTag(DER.OBJECT_IDENTIFIER)) {
        this._parameters = readOID(contents);
      }
    }
    contents.assertAtEnd();
    this._der.assertAtEnd();
  }
}

// Name ::= CHOICE { -- only one possibility for now --
//   rdnSequence  RDNSequence }
//
// RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
class Name extends DecodedDER {
  constructor() {
    super();
    this._rdns = [];
  }

  get rdns() {
    return this._rdns;
  }

  parseOverride() {
    let contents = readSEQUENCEAndMakeDER(this._der);
    while (!contents.atEnd()) {
      let rdn = new RelativeDistinguishedName();
      rdn.parse(contents.readTLV());
      this._rdns.push(rdn);
    }
    contents.assertAtEnd();
    this._der.assertAtEnd();
  }
}

// RelativeDistinguishedName ::=
//   SET SIZE (1..MAX) OF AttributeTypeAndValue
class RelativeDistinguishedName extends DecodedDER {
  constructor() {
    super();
    this._avas = [];
  }

  get avas() {
    return this._avas;
  }

  parseOverride() {
    let contents = readTagAndMakeDER(this._der, DER.SET);
    // Lint TODO: enforce SET SIZE restrictions
    while (!contents.atEnd()) {
      let ava = new AttributeTypeAndValue();
      ava.parse(contents.readTLV());
      this._avas.push(ava);
    }
    contents.assertAtEnd();
    this._der.assertAtEnd();
  }
}

// AttributeTypeAndValue ::= SEQUENCE {
//   type     AttributeType,
//   value    AttributeValue }
//
// AttributeType ::= OBJECT IDENTIFIER
//
// AttributeValue ::= ANY -- DEFINED BY AttributeType
class AttributeTypeAndValue extends DecodedDER {
  constructor() {
    super();
    this._type = null;
    this._value = new DirectoryString();
  }

  get type() {
    return this._type;
  }

  get value() {
    return this._value;
  }

  parseOverride() {
    let contents = readSEQUENCEAndMakeDER(this._der);
    this._type = readOID(contents);
    // We don't support universalString or bmpString.
    // IA5String is supported because it is valid if `type == id-emailaddress`.
    // Lint TODO: validate that the type of string is valid given `type`.
    this._value.parse(
      contents.readTLVChoice([
        DER.UTF8String,
        DER.PrintableString,
        DER.TeletexString,
        DER.IA5String,
      ])
    );
    contents.assertAtEnd();
    this._der.assertAtEnd();
  }
}

// DirectoryString ::= CHOICE {
//       teletexString           TeletexString (SIZE (1..MAX)),
//       printableString         PrintableString (SIZE (1..MAX)),
//       universalString         UniversalString (SIZE (1..MAX)),
//       utf8String              UTF8String (SIZE (1..MAX)),
//       bmpString               BMPString (SIZE (1..MAX)) }
class DirectoryString extends DecodedDER {
  constructor() {
    super();
    this._type = null;
    this._value = null;
  }

  get type() {
    return this._type;
  }

  get value() {
    return this._value;
  }

  parseOverride() {
    if (this._der.peekTag(DER.UTF8String)) {
      this._type = DER.UTF8String;
    } else if (this._der.peekTag(DER.PrintableString)) {
      this._type = DER.PrintableString;
    } else if (this._der.peekTag(DER.TeletexString)) {
      this._type = DER.TeletexString;
    } else if (this._der.peekTag(DER.IA5String)) {
      this._type = DER.IA5String;
    }
    // Lint TODO: validate that the contents are actually valid for the type
    this._value = this._der.readTagAndGetContents(this._type);
    this._der.assertAtEnd();
  }
}

// Time ::= CHOICE {
//      utcTime        UTCTime,
//      generalTime    GeneralizedTime }
class Time extends DecodedDER {
  constructor() {
    super();
    this._type = null;
    this._time = null;
  }

  get time() {
    return this._time;
  }

  parseOverride() {
    if (this._der.peekTag(DER.UTCTime)) {
      this._type = DER.UTCTime;
    } else if (this._der.peekTag(DER.GeneralizedTime)) {
      this._type = DER.GeneralizedTime;
    }
    let contents = readTagAndMakeDER(this._der, this._type);
    let year;
    // Lint TODO: validate that the appropriate one of {UTCTime,GeneralizedTime}
    // is used according to RFC 5280 and what the value of the date is.
    // TODO TODO: explain this better (just quote the rfc).
    if (this._type == DER.UTCTime) {
      // UTCTime is YYMMDDHHMMSSZ in RFC 5280. If YY is greater than or equal
      // to 50, the year is 19YY. Otherwise, it is 20YY.
      let y1 = this._validateDigit(contents.readByte());
      let y2 = this._validateDigit(contents.readByte());
      let yy = y1 * 10 + y2;
      if (yy >= 50) {
        year = 1900 + yy;
      } else {
        year = 2000 + yy;
      }
    } else {
      // GeneralizedTime is YYYYMMDDHHMMSSZ in RFC 5280.
      year = 0;
      for (let i = 0; i < 4; i++) {
        let y = this._validateDigit(contents.readByte());
        year = year * 10 + y;
      }
    }

    let m1 = this._validateDigit(contents.readByte());
    let m2 = this._validateDigit(contents.readByte());
    let month = m1 * 10 + m2;
    if (month == 0 || month > 12) {
      throw new Error(ERROR_TIME_NOT_VALID);
    }

    let d1 = this._validateDigit(contents.readByte());
    let d2 = this._validateDigit(contents.readByte());
    let day = d1 * 10 + d2;
    if (day == 0 || day > 31) {
      throw new Error(ERROR_TIME_NOT_VALID);
    }

    let h1 = this._validateDigit(contents.readByte());
    let h2 = this._validateDigit(contents.readByte());
    let hour = h1 * 10 + h2;
    if (hour > 23) {
      throw new Error(ERROR_TIME_NOT_VALID);
    }

    let min1 = this._validateDigit(contents.readByte());
    let min2 = this._validateDigit(contents.readByte());
    let minute = min1 * 10 + min2;
    if (minute > 59) {
      throw new Error(ERROR_TIME_NOT_VALID);
    }

    let s1 = this._validateDigit(contents.readByte());
    let s2 = this._validateDigit(contents.readByte());
    let second = s1 * 10 + s2;
    if (second > 60) {
      // leap-seconds mean this can be as much as 60
      throw new Error(ERROR_TIME_NOT_VALID);
    }

    let z = contents.readByte();
    if (z != "Z".charCodeAt(0)) {
      throw new Error(ERROR_TIME_NOT_VALID);
    }
    // Lint TODO: verify that the Time doesn't specify a nonsensical
    // month/day/etc.
    // months are zero-indexed in JS
    this._time = new Date(Date.UTC(year, month - 1, day, hour, minute, second));

    contents.assertAtEnd();
    this._der.assertAtEnd();
  }

  /**
   * Takes a byte that is supposed to be in the ASCII range for "0" to "9".
   * Validates the range and then converts it to the range 0 to 9.
   *
   * @param {number} d the digit in question (as ASCII in the range ["0", "9"])
   * @returns {number} the numerical value of the digit (in the range [0, 9])
   */
  _validateDigit(d) {
    if (d < "0".charCodeAt(0) || d > "9".charCodeAt(0)) {
      throw new Error(ERROR_TIME_NOT_VALID);
    }
    return d - "0".charCodeAt(0);
  }
}

// Validity ::= SEQUENCE {
//      notBefore      Time,
//      notAfter       Time }
class Validity extends DecodedDER {
  constructor() {
    super();
    this._notBefore = new Time();
    this._notAfter = new Time();
  }

  get notBefore() {
    return this._notBefore;
  }

  get notAfter() {
    return this._notAfter;
  }

  parseOverride() {
    let contents = readSEQUENCEAndMakeDER(this._der);
    this._notBefore.parse(
      contents.readTLVChoice([DER.UTCTime, DER.GeneralizedTime])
    );
    this._notAfter.parse(
      contents.readTLVChoice([DER.UTCTime, DER.GeneralizedTime])
    );
    contents.assertAtEnd();
    this._der.assertAtEnd();
  }
}

// SubjectPublicKeyInfo  ::=  SEQUENCE  {
//      algorithm            AlgorithmIdentifier,
//      subjectPublicKey     BIT STRING  }
class SubjectPublicKeyInfo extends DecodedDER {
  constructor() {
    super();
    this._algorithm = new AlgorithmIdentifier();
    this._subjectPublicKey = null;
  }

  get algorithm() {
    return this._algorithm;
  }

  get subjectPublicKey() {
    return this._subjectPublicKey;
  }

  parseOverride() {
    let contents = readSEQUENCEAndMakeDER(this._der);
    this._algorithm.parse(contents.readTLV());
    let subjectPublicKeyBitString = contents.readBIT_STRING();
    if (subjectPublicKeyBitString.unusedBits != 0) {
      throw new Error(ERROR_UNSUPPORTED_ASN1);
    }
    this._subjectPublicKey = subjectPublicKeyBitString.contents;

    contents.assertAtEnd();
    this._der.assertAtEnd();
  }
}

export var X509 = { Certificate };
PK
!<�����'�'modules/psm/DER.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// A minimal ASN.1 DER decoder. Supports input lengths up to 65539 (one byte for
// the outer tag, one byte for the 0x82 length-length indicator, two bytes
// indicating a contents length of 65535, and then the 65535 bytes of contents).
// Intended to be used like so:
//
// let bytes = <an array of bytes describing a SEQUENCE OF INTEGER>;
// let der = new DER.DERDecoder(bytes);
// let contents = new DER.DERDecoder(der.readTagAndGetContents(DER.SEQUENCE));
// while (!contents.atEnd()) {
//   let integerBytes = contents.readTagAndGetContents(DER.INTEGER);
//   <... do something with integerBytes ...>
// }
// der.assertAtEnd();
//
// For CHOICE, use readTLVChoice and pass an array of acceptable tags.
// The convenience function readBIT_STRING is provided to handle the unused bits
// aspect of BIT STRING. It returns an object that has the properties contents
// (an array of bytes consisting of the bytes making up the BIT STRING) and
// unusedBits (indicating the number of unused bits at the end).
// All other functions generally return an array of bytes or a single byte as
// appropriate.
// peekTag can be used to see if the next tag is an expected given tag.
// readTLV reads and returns an entire (tag, length, value) tuple (again
// returned as an array of bytes).
//
// NB: While DERDecoder must be given an array, it does not validate that each
// element in the array is an integer in the range [0, 255]. If the input to be
// decoded could conceivably violate this property, callers should perform this
// check before using DERDecoder.

const UNIVERSAL = 0 << 6;
const CONSTRUCTED = 1 << 5;
const CONTEXT_SPECIFIC = 2 << 6;

const INTEGER = UNIVERSAL | 0x02; // 0x02
const BIT_STRING = UNIVERSAL | 0x03; // 0x03
const NULL = UNIVERSAL | 0x05; // 0x05
const OBJECT_IDENTIFIER = UNIVERSAL | 0x06; // 0x06
const PrintableString = UNIVERSAL | 0x13; // 0x13
const TeletexString = UNIVERSAL | 0x14; // 0x14
const IA5String = UNIVERSAL | 0x16; // 0x16
const UTCTime = UNIVERSAL | 0x17; // 0x17
const GeneralizedTime = UNIVERSAL | 0x18; // 0x18
const UTF8String = UNIVERSAL | 0x0c; // 0x0c
const SEQUENCE = UNIVERSAL | CONSTRUCTED | 0x10; // 0x30
const SET = UNIVERSAL | CONSTRUCTED | 0x11; // 0x31

const ERROR_INVALID_INPUT = "invalid input";
const ERROR_DATA_TRUNCATED = "data truncated";
const ERROR_EXTRA_DATA = "extra data";
const ERROR_INVALID_LENGTH = "invalid length";
const ERROR_UNSUPPORTED_ASN1 = "unsupported asn.1";
const ERROR_UNSUPPORTED_LENGTH = "unsupported length";
const ERROR_INVALID_BIT_STRING = "invalid BIT STRING encoding";

/** Class representing a decoded BIT STRING. */
class BitString {
  /**
   * @param {number} unusedBits the number of unused bits
   * @param {number[]} contents an array of bytes comprising the BIT STRING
   */
  constructor(unusedBits, contents) {
    this._unusedBits = unusedBits;
    this._contents = contents;
  }

  /**
   * Get the number of unused bits in the BIT STRING
   *
   * @returns {number} the number of unused bits
   */
  get unusedBits() {
    return this._unusedBits;
  }

  /**
   * Get the contents of the BIT STRING
   *
   * @returns {number[]} an array of bytes representing the contents
   */
  get contents() {
    return this._contents;
  }
}

/** Class representing DER-encoded data. Provides methods for decoding it. */
class DERDecoder {
  /**
   * @param {number[]} bytes an array of bytes representing the encoded data
   */
  constructor(bytes) {
    // Reject non-array inputs.
    if (!Array.isArray(bytes)) {
      throw new Error(ERROR_INVALID_INPUT);
    }
    if (bytes.length > 65539) {
      throw new Error(ERROR_UNSUPPORTED_LENGTH);
    }
    this._bytes = bytes;
    this._cursor = 0;
  }

  /**
   * Asserts that the decoder is at the end of the given data. Throws an error
   * if this is not the case.
   */
  assertAtEnd() {
    if (!this.atEnd()) {
      throw new Error(ERROR_EXTRA_DATA);
    }
  }

  /**
   * Determines whether or not the decoder is at the end of the given data.
   *
   * @returns {boolean} true if the decoder is at the end and false otherwise
   */
  atEnd() {
    return this._cursor == this._bytes.length;
  }

  /**
   * Reads the next byte of data. Throws if no more data is available.
   *
   * @returns {number} the next byte of data
   */
  readByte() {
    if (this._cursor >= this._bytes.length) {
      throw new Error(ERROR_DATA_TRUNCATED);
    }
    let val = this._bytes[this._cursor];
    this._cursor++;
    return val;
  }

  /**
   * Given the next expected tag, reads and asserts that the next tag is in fact
   * the given tag.
   *
   * @param {number} expectedTag the expected next tag
   */
  _readExpectedTag(expectedTag) {
    let tag = this.readByte();
    if (tag != expectedTag) {
      throw new Error(`unexpected tag: found ${tag} instead of ${expectedTag}`);
    }
  }

  /**
   * Decodes and returns the length portion of an ASN.1 TLV tuple. Throws if the
   * length is incorrectly encoded or if it describes a length greater than
   * 65535 bytes. Indefinite-length encoding is not supported.
   *
   * @returns {number} the length of the value of the TLV tuple
   */
  _readLength() {
    let nextByte = this.readByte();
    if (nextByte < 0x80) {
      return nextByte;
    }
    if (nextByte == 0x80) {
      throw new Error(ERROR_UNSUPPORTED_ASN1);
    }
    if (nextByte == 0x81) {
      let length = this.readByte();
      if (length < 0x80) {
        throw new Error(ERROR_INVALID_LENGTH);
      }
      return length;
    }
    if (nextByte == 0x82) {
      let length1 = this.readByte();
      let length2 = this.readByte();
      let length = (length1 << 8) | length2;
      if (length < 256) {
        throw new Error(ERROR_INVALID_LENGTH);
      }
      return length;
    }
    throw new Error(ERROR_UNSUPPORTED_LENGTH);
  }

  /**
   * Reads <length> bytes of data if available. Throws if less than <length>
   * bytes are available.
   *
   * @param {number} length the number of bytes to read. Must be non-negative.
   * @returns {number[]} the next <length> bytes of data
   */
  readBytes(length) {
    if (length < 0) {
      throw new Error(ERROR_INVALID_LENGTH);
    }
    if (this._cursor + length > this._bytes.length) {
      throw new Error(ERROR_DATA_TRUNCATED);
    }
    let bytes = this._bytes.slice(this._cursor, this._cursor + length);
    this._cursor += length;
    return bytes;
  }

  /**
   * Given an expected next ASN.1 tag, ensures that that tag is next and returns
   * the contents of that tag. Throws if a different tag is encountered or if
   * the data is otherwise incorrectly encoded.
   *
   * @param {number} tag the next expected ASN.1 tag
   * @returns {number[]} the contents of the tag
   */
  readTagAndGetContents(tag) {
    this._readExpectedTag(tag);
    let length = this._readLength();
    return this.readBytes(length);
  }

  /**
   * Returns the next byte without advancing the decoder. Throws if no more data
   * is available.
   *
   * @returns {number} the next available byte
   */
  _peekByte() {
    if (this._cursor >= this._bytes.length) {
      throw new Error(ERROR_DATA_TRUNCATED);
    }
    return this._bytes[this._cursor];
  }

  /**
   * Given an expected tag, reads the next entire ASN.1 TLV tuple, asserting
   * that the tag matches.
   *
   * @param {number} tag the expected tag
   * @returns {number[]} an array of bytes representing the TLV tuple
   */
  _readExpectedTLV(tag) {
    let mark = this._cursor;
    this._readExpectedTag(tag);
    let length = this._readLength();
    // read the bytes so we know they're there (also to advance the cursor)
    this.readBytes(length);
    return this._bytes.slice(mark, this._cursor);
  }

  /**
   * Reads the next ASN.1 tag, length, and value and returns them as an array of
   * bytes.
   *
   * @returns {number[]} an array of bytes representing the next ASN.1 TLV
   */
  readTLV() {
    let nextTag = this._peekByte();
    return this._readExpectedTLV(nextTag);
  }

  /**
   * Convenience function for decoding a BIT STRING. Reads and returns the
   * contents of the expected next BIT STRING. Throws if the next TLV isn't a
   * BIT STRING or if the BIT STRING is incorrectly encoded.
   *
   * @returns {BitString} the next BIT STRING
   */
  readBIT_STRING() {
    let contents = this.readTagAndGetContents(BIT_STRING);
    if (contents.length < 1) {
      throw new Error(ERROR_INVALID_BIT_STRING);
    }
    let unusedBits = contents[0];
    if (unusedBits > 7) {
      throw new Error(ERROR_INVALID_BIT_STRING);
    }
    // Zero bytes of content but some amount of padding is invalid.
    if (contents.length == 1 && unusedBits != 0) {
      throw new Error(ERROR_INVALID_BIT_STRING);
    }
    return new BitString(unusedBits, contents.slice(1, contents.length));
  }

  /**
   * Looks to see if the next ASN.1 tag is the expected given tag.
   *
   * @param {number} tag the expected next ASN.1 tag
   * @returns {boolean} true if the next tag is the given one and false otherwise
   */
  peekTag(tag) {
    if (this._cursor >= this._bytes.length) {
      return false;
    }
    return this._bytes[this._cursor] == tag;
  }

  /**
   * Given a list of possible next ASN.1 tags, returns the next TLV if the next
   * tag is in the list. Throws if the next tag is not in the list or if the
   * data is incorrectly encoded.
   *
   * @param {number[]} tagList the list of potential next tags
   * @returns {number[]} the contents of the next TLV if the next tag is in
   *                    <tagList>
   */
  readTLVChoice(tagList) {
    let tag = this._peekByte();
    if (!tagList.includes(tag)) {
      throw new Error(
        `unexpected tag: found ${tag} instead of one of ${tagList}`
      );
    }
    return this._readExpectedTLV(tag);
  }
}

export const DER = {
  UNIVERSAL,
  CONSTRUCTED,
  CONTEXT_SPECIFIC,
  INTEGER,
  BIT_STRING,
  NULL,
  OBJECT_IDENTIFIER,
  PrintableString,
  TeletexString,
  IA5String,
  UTCTime,
  GeneralizedTime,
  UTF8String,
  SEQUENCE,
  SET,
  DERDecoder,
};
PK
!<����modules/OsEnvironment.sys.mjs/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs",
  WindowsVersionInfo:
    "resource://gre/modules/components-utils/WindowsVersionInfo.sys.mjs",
});

export let OsEnvironment = {
  /**
   * This is a policy object used to override behavior for testing.
   */
  Policy: {
    getAllowedAppSources: () =>
      lazy.WindowsRegistry.readRegKey(
        Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
        "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer",
        "AicEnabled"
      ),
    windowsVersionHasAppSourcesFeature: () => {
      let windowsVersion = parseFloat(Services.sysinfo.getProperty("version"));
      if (isNaN(windowsVersion)) {
        throw new Error("Unable to parse Windows version");
      }
      if (windowsVersion < 10) {
        return false;
      }

      // The App Sources feature was added in Windows 10, build 15063.
      const { buildNumber } = lazy.WindowsVersionInfo.get();
      return buildNumber >= 15063;
    },
  },

  reportAllowedAppSources() {
    if (AppConstants.platform != "win") {
      // This is currently a windows-only feature.
      return;
    }

    const appSourceScalar = "os.environment.allowed_app_sources";

    let haveAppSourcesFeature;
    try {
      haveAppSourcesFeature =
        OsEnvironment.Policy.windowsVersionHasAppSourcesFeature();
    } catch (ex) {
      console.error(ex);
      Services.telemetry.scalarSet(appSourceScalar, "Error");
      return;
    }
    if (!haveAppSourcesFeature) {
      Services.telemetry.scalarSet(appSourceScalar, "NoSuchFeature");
      return;
    }

    let allowedAppSources;
    try {
      allowedAppSources = OsEnvironment.Policy.getAllowedAppSources();
    } catch (ex) {
      console.error(ex);
      Services.telemetry.scalarSet(appSourceScalar, "Error");
      return;
    }
    if (allowedAppSources === undefined) {
      // A return value of undefined means that the registry value didn't
      // exist. Windows treats a missing registry entry the same as if the
      // value is "Anywhere". Make sure to differentiate a missing registry
      // entry from one containing an empty string, since it is unclear how
      // Windows would treat such a setting, but it may not be the same as
      // if the value is missing.
      allowedAppSources = "Anywhere";
    }

    const expectedValues = [
      "Anywhere",
      "Recommendations",
      "PreferStore",
      "StoreOnly",
    ];
    if (!expectedValues.includes(allowedAppSources)) {
      allowedAppSources = "Error";
    }

    Services.telemetry.scalarSet(appSourceScalar, allowedAppSources);
  },
};
PK
!<x����(modules/PageThumbsStorageService.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const THUMBNAIL_DIRECTORY = "thumbnails";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "gCryptoHash", function () {
  return Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
});

ChromeUtils.defineLazyGetter(lazy, "textEncoder", function () {
  return new TextEncoder();
});
export function PageThumbsStorageService() {}

PageThumbsStorageService.prototype = {
  classID: Components.ID("{97943eec-0e48-49ef-b7b7-cf4aa0109bb6}"),
  QueryInterface: ChromeUtils.generateQI(["nsIPageThumbsStorageService"]),
  // The path for the storage
  _path: null,
  get path() {
    if (!this._path) {
      this._path = PathUtils.join(
        PathUtils.localProfileDir,
        THUMBNAIL_DIRECTORY
      );
    }
    return this._path;
  },

  getLeafNameForURL(aURL) {
    if (typeof aURL != "string") {
      throw new TypeError("Expecting a string");
    }
    let hash = this._calculateMD5Hash(aURL);
    return hash + ".png";
  },

  getFilePathForURL(aURL) {
    return PathUtils.join(this.path, this.getLeafNameForURL(aURL));
  },

  _calculateMD5Hash(aValue) {
    let hash = lazy.gCryptoHash;
    let value = lazy.textEncoder.encode(aValue);

    hash.init(hash.MD5);
    hash.update(value, value.length);
    return this._convertToHexString(hash.finish(false));
  },

  _convertToHexString(aData) {
    let hex = "";
    for (let i = 0; i < aData.length; i++) {
      hex += ("0" + aData.charCodeAt(i).toString(16)).slice(-2);
    }
    return hex;
  },
};
PK
!<q�W7GGmodules/PageThumbs.worker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * A worker dedicated for the I/O component of PageThumbs storage.
 */

"use strict";

/* import-globals-from /toolkit/components/workerloader/require.js */
importScripts("resource://gre/modules/workers/require.js");

var PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js");

var worker = new PromiseWorker.AbstractWorker();
worker.dispatch = function (method, args = []) {
  return Agent[method](...args);
};
worker.postMessage = function (message, ...transfers) {
  self.postMessage(message, ...transfers);
};
worker.close = function () {
  self.close();
};

self.addEventListener("message", msg => worker.handleMessage(msg));
self.addEventListener("unhandledrejection", function (error) {
  throw error.reason;
});

var Agent = {
  // Checks if the specified file exists and has an age less than as
  // specifed (in seconds).
  async isFileRecent(path, maxAge) {
    try {
      let stat = await IOUtils.stat(path);
      let maxDate = new Date();
      maxDate.setSeconds(maxDate.getSeconds() - maxAge);
      return stat.lastModified > maxDate;
    } catch (ex) {
      if (!(ex instanceof DOMException)) {
        throw ex;
      }
      // file doesn't exist (or can't be stat'd) - must be stale.
      return false;
    }
  },

  async remove(path) {
    try {
      await IOUtils.remove(path);
      return true;
    } catch (e) {
      return false;
    }
  },

  async expireFilesInDirectory(path, filesToKeep, minChunkSize) {
    let entries = await this.getFileEntriesInDirectory(path, filesToKeep);
    let limit = Math.max(minChunkSize, Math.round(entries.length / 2));

    for (let entry of entries) {
      await this.remove(entry);

      // Check if we reached the limit of files to remove.
      if (--limit <= 0) {
        break;
      }
    }

    return true;
  },

  async getFileEntriesInDirectory(path, skipFiles) {
    let children = await IOUtils.getChildren(path);
    let skip = new Set(skipFiles);

    let entries = [];
    for (let entry of children) {
      let stat = await IOUtils.stat(entry);
      if (stat.type === "regular" && !skip.has(PathUtils.filename(entry))) {
        entries.push(entry);
      }
    }
    return entries;
  },

  async moveOrDeleteAllThumbnails(pathFrom, pathTo) {
    await IOUtils.makeDirectory(pathTo);
    if (pathFrom == pathTo) {
      return true;
    }
    let children = await IOUtils.getChildren(pathFrom);
    for (let entry of children) {
      let stat = await IOUtils.stat(entry);
      if (stat.type !== "regular") {
        continue;
      }

      let fileName = PathUtils.filename(entry);
      let from = PathUtils.join(pathFrom, fileName);
      let to = PathUtils.join(pathTo, fileName);

      try {
        await IOUtils.move(from, to, { noOverwrite: true });
      } catch (e) {
        await IOUtils.remove(from);
      }
    }

    try {
      await IOUtils.remove(pathFrom, { recursive: true });
    } catch (e) {
      // This could fail if there's something in
      // the folder we're not permitted to remove.
    }

    return true;
  },

  writeAtomic(path, buffer, options) {
    return IOUtils.write(path, buffer, options);
  },

  makeDir(path, options) {
    return IOUtils.makeDirectory(path, options);
  },

  copy(source, dest, options) {
    return IOUtils.copy(source, dest, options);
  },

  async wipe(path) {
    let children = await IOUtils.getChildren(path);
    try {
      await Promise.all(children.map(entry => IOUtils.remove(entry)));
    } catch (ex) {
      // If a file cannot be removed, we should still continue.
      // This can happen at least for any of the following reasons:
      // - access denied;
      // - file has been removed recently during a previous wipe
      //  and the file system has not flushed that yet (yes, this
      //  can happen under Windows);
      // - file has been removed by the user or another process.
    }
  },

  exists(path) {
    return IOUtils.exists(path);
  },
};
PK
!<W�)^BB:modules/services-settings/RemoteSettingsComponents.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
});

export var RemoteSettingsTimer = function () {};
RemoteSettingsTimer.prototype = {
  QueryInterface: ChromeUtils.generateQI(["nsITimerCallback"]),
  classID: Components.ID("{5e756573-234a-49ea-bbe4-59ec7a70657d}"),
  contractID: "@mozilla.org/services/settings;1",

  // By default, this timer fires once every 24 hours. See the "services.settings.poll_interval" pref.
  notify() {
    lazy.RemoteSettings.pollChanges({ trigger: "timer" }).catch(e =>
      console.error(e)
    );
  },
};
PK
!<��8-modules/services-settings/SyncHistory.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  KeyValueService: "resource://gre/modules/kvstore.sys.mjs",
});

/**
 * A helper to keep track of synchronization statuses.
 *
 * We rely on a different storage backend than for storing Remote Settings data,
 * because the eventual goal is to be able to detect `IndexedDB` issues and act
 * accordingly.
 */
export class SyncHistory {
  // Internal reference to underlying rkv store.
  #store;

  /**
   * @param {String} source the synchronization source (eg. `"settings-sync"`)
   * @param {Object} options
   * @param {int} options.size Maximum number of entries per source.
   */
  constructor(source, { size } = { size: 100 }) {
    this.source = source;
    this.size = size;
  }

  /**
   * Store the synchronization status. The ETag is converted and stored as
   * a millisecond epoch timestamp.
   * The entries with the oldest timestamps will be deleted to maintain the
   * history size under the configured maximum.
   *
   * @param {String} etag the ETag value from the server (eg. `"1647961052593"`)
   * @param {String} status the synchronization status (eg. `"success"`)
   * @param {Object} infos optional additional information to keep track of
   */
  async store(etag, status, infos = {}) {
    const rkv = await this.#init();
    const timestamp = parseInt(etag.replace('"', ""), 10);
    if (Number.isNaN(timestamp)) {
      throw new Error(`Invalid ETag value ${etag}`);
    }
    const key = `v1-${this.source}\t${timestamp}`;
    const value = { timestamp, status, infos };
    await rkv.put(key, JSON.stringify(value));
    // Trim old entries.
    const allEntries = await this.list();
    for (let i = this.size; i < allEntries.length; i++) {
      let { timestamp } = allEntries[i];
      await rkv.delete(`v1-${this.source}\t${timestamp}`);
    }
  }

  /**
   * Retrieve the stored history entries for a certain source, sorted by
   * timestamp descending.
   *
   * @returns {Array<Object>} a list of objects
   */
  async list() {
    const rkv = await this.#init();
    const entries = [];
    // The "from" and "to" key parameters to nsIKeyValueStore.enumerate()
    // are inclusive and exclusive, respectively, and keys are tuples
    // of source and datetime joined by a tab (\t), which is character code 9;
    // so enumerating ["source", "source\n"), where the line feed (\n)
    // is character code 10, enumerates all pairs with the given source.
    for (const { value } of await rkv.enumerate(
      `v1-${this.source}`,
      `v1-${this.source}\n`
    )) {
      try {
        const stored = JSON.parse(value);
        entries.push({ ...stored, datetime: new Date(stored.timestamp) });
      } catch (e) {
        // Ignore malformed entries.
        console.error(e);
      }
    }
    // Sort entries by `timestamp` descending.
    entries.sort((a, b) => (a.timestamp > b.timestamp ? -1 : 1));
    return entries;
  }

  /**
   * Return the most recent entry.
   */
  async last() {
    // List is sorted from newer to older.
    return (await this.list())[0];
  }

  /**
   * Wipe out the **whole** store.
   */
  async clear() {
    const rkv = await this.#init();
    await rkv.clear();
  }

  /**
   * Initialize the rkv store in the user profile.
   *
   * @returns {Object} the underlying `KeyValueService` instance.
   */
  async #init() {
    if (!this.#store) {
      // Get and cache a handle to the kvstore.
      const dir = PathUtils.join(PathUtils.profileDir, "settings");
      await IOUtils.makeDirectory(dir);
      this.#store = await lazy.KeyValueService.getOrCreate(dir, "synchistory");
    }
    return this.#store;
  }
}
PK
!<)0���modules/workers/require.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Implementation of a CommonJS module loader for workers.
 *
 * Use:
 * // in the .js file loaded by the constructor of the worker
 * importScripts("resource://gre/modules/workers/require.js");
 * let module = require("resource://gre/modules/worker/myModule.js");
 *
 * // in myModule.js
 * // Load dependencies
 * let SimpleTest = require("resource://gre/modules/workers/SimpleTest.js");
 * let Logger = require("resource://gre/modules/workers/Logger.js");
 *
 * // Define things that will not be exported
 * let someValue = // ...
 *
 * // Export symbols
 * exports.foo = // ...
 * exports.bar = // ...
 *
 *
 * Note #1:
 * Properties |fileName| and |stack| of errors triggered from a module
 * contain file names that do not correspond to human-readable module paths.
 * Human readers should rather use properties |moduleName| and |moduleStack|.
 *
 * Note #2:
 * By opposition to some other module loader implementations, this module
 * loader does not enforce separation of global objects. Consequently, if
 * a module modifies a global object (e.g. |String.prototype|), all other
 * modules in the same worker may be affected.
 */

/* global require */
/* exported require */

(function (exports) {
  "use strict";

  if (exports.require) {
    // Avoid double-imports
    return;
  }

  // Simple implementation of |require|
  let require = (function () {
    /**
     * Mapping from module URI to module exports.
     *
     * @keys {string} The absolute URI to a module.
     * @values {object} The |exports| objects for that module.
     */
    let modules = new Map();

    /**
     * A human-readable version of |stack|.
     *
     * @type {string}
     */
    Object.defineProperty(Error.prototype, "moduleStack", {
      get() {
        return this.stack;
      },
    });
    /**
     * A human-readable version of |fileName|.
     *
     * @type {string}
     */
    Object.defineProperty(Error.prototype, "moduleName", {
      get() {
        let match = this.stack.match(/\@(.*):.*:/);
        if (match) {
          return match[1];
        }
        return "(unknown module)";
      },
    });

    /**
     * Import a module
     *
     * @param {string} baseURL The URL of the modules from which we load a new module.
     *        This will be null for the first loaded module and so expect an absolute URI in path.
     *        Note that this first parameter is bound before `require` method is passed outside
     *        of this module. So that typical callsites will only path the second `path` parameter.
     * @param {string} path The path to the module.
     * @return {*} An object containing the properties exported by the module.
     */
    return function require(baseURL, path) {
      let startTime = performance.now();
      if (typeof path != "string") {
        throw new TypeError(
          "The argument to require() must be a string got " + path
        );
      }

      // Resolve relative paths
      if ((path.startsWith("./") || path.startsWith("../")) && baseURL) {
        path = new URL(path, baseURL).href;
      }

      if (!path.includes("://")) {
        throw new TypeError(
          "The argument to require() must be a string uri, got " + path
        );
      }
      // Automatically add ".js" if there is no extension
      let uri;
      if (path.lastIndexOf(".") <= path.lastIndexOf("/")) {
        uri = path + ".js";
      } else {
        uri = path;
      }

      // Exports provided by the module
      let exports = Object.create(null);

      // Identification of the module
      let module = {
        id: path,
        uri,
        exports,
      };

      // Make module available immediately
      // (necessary in case of circular dependencies)
      if (modules.has(uri)) {
        return modules.get(uri).exports;
      }
      modules.set(uri, module);

      try {
        // Load source of module, synchronously
        let xhr = new XMLHttpRequest();
        xhr.open("GET", uri, false);
        xhr.responseType = "text";
        xhr.send();

        let source = xhr.responseText;
        if (source == "") {
          // There doesn't seem to be a better way to detect that the file couldn't be found
          throw new Error("Could not find module " + path);
        }
        // Use `Function` to leave this scope, use `eval` to start the line
        // number from 1 that is observed by `source` and the error message
        // thrown from the module, and also use `arguments` for accessing
        // `source` and `uri` to avoid polluting the module's environment.
        let code = new Function(
          "exports",
          "require",
          "module",
          `eval(arguments[3] + "\\n//# sourceURL=" + arguments[4] + "\\n")`
        );
        code(exports, require.bind(null, path), module, source, uri);
      } catch (ex) {
        // Module loading has failed, exports should not be made available
        // after all.
        modules.delete(uri);
        throw ex;
      } finally {
        ChromeUtils.addProfilerMarker("require", startTime, path);
      }

      Object.freeze(module.exports);
      Object.freeze(module);
      return module.exports;
    };
  })();

  Object.freeze(require);

  Object.defineProperty(exports, "require", {
    value: require.bind(null, null),
    enumerable: true,
    configurable: false,
  });
})(this);
PK
!<^�20e!e! modules/workers/PromiseWorker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env commonjs */

/**
 * A wrapper around `self` with extended capabilities designed
 * to simplify main thread-to-worker thread asynchronous function calls.
 *
 * This wrapper:
 * - groups requests and responses as a method `post` that returns a `Promise`;
 * - ensures that exceptions thrown on the worker thread are correctly serialized;
 * - provides some utilities for benchmarking various operations.
 *
 * Generally, you should use PromiseWorker.js or PromiseWorker.mjs along with
 * its main thread-side counterpart PromiseWorker.sys.mjs.
 */


"use strict";


if (typeof Components != "undefined") {
  throw new Error("This module is meant to be used from the worker thread");
}

if (typeof require == "undefined" || typeof module == "undefined") {
  throw new Error(
    "this module is meant to be imported using the implementation of require() at resource://gre/modules/workers/require.js"
  );
}

/* import-globals-from /toolkit/components/workerloader/require.js */
importScripts("resource://gre/modules/workers/require.js");


/**
 * Built-in JavaScript exceptions that may be serialized without
 * loss of information.
 */
const EXCEPTION_NAMES = {
  EvalError: "EvalError",
  InternalError: "InternalError",
  RangeError: "RangeError",
  ReferenceError: "ReferenceError",
  SyntaxError: "SyntaxError",
  TypeError: "TypeError",
  URIError: "URIError",
};

/**
 * A constructor used to return data to the caller thread while
 * also executing some specific treatment (e.g. shutting down
 * the current thread, transmitting data instead of copying it).
 *
 * @param {object=} data The data to return to the caller thread.
 * @param {object=} meta Additional instructions, as an object
 * that may contain the following fields:
 * - {bool} shutdown If |true|, shut down the current thread after
 *   having sent the result.
 * - {Array} transfers An array of objects that should be transferred
 *   instead of being copied.
 *
 * @constructor
 */
function Meta(data, meta) {
  this.data = data;
  this.meta = meta;
}
exports.Meta = Meta;

/**
 * Base class for a worker.
 *
 * Derived classes are expected to provide the following methods:
 * {
 *   dispatch: function(method, args) {
 *     // Dispatch a call to method `method` with args `args`
 *   },
 *   log: function(...msg) {
 *     // Log (or discard) messages (optional)
 *   },
 *   postMessage: function(message, ...transfers) {
 *     // Post a message to the main thread
 *   },
 *   close: function() {
 *     // Close the worker
 *   }
 * }
 *
 * By default, the AbstractWorker is not connected to a message port,
 * hence will not receive anything.
 *
 * To connect it, use `onmessage`, as follows:
 *   self.addEventListener("message", msg => myWorkerInstance.handleMessage(msg));
 * To handle rejected promises we receive from handleMessage, we must connect it to
 * the onError handler as follows:
 *   self.addEventListener("unhandledrejection", function(error) {
 *    throw error.reason;
 *   });
 */
function AbstractWorker(agent) {
  this._agent = agent;
  this._deferredJobs = new Map();
  this._deferredJobId = 0;
}

AbstractWorker.prototype = {
  // Default logger: discard all messages
  log() {},

  _generateDeferredJobId() {
    this._deferredJobId += 1;
    return "WorkerToThread-" + this._deferredJobId;
  },

  /**
   * Post and wait for an answer from the thread.
   */
  callMainThread(funcName, args) {
    const messageId = this._generateDeferredJobId();

    const message = {
      id: messageId,
      fun: funcName,
      args,
    };

    return new Promise((resolve, reject) => {
      this._deferredJobs.set(messageId, { resolve, reject });
      this.postMessage(message);
    });
  },

  /**
   * Handle a message.
   */
  async handleMessage(msg) {
    let data = msg.data;
    let id = data.id;

    // if the id is found in _deferredJobs, we proceed with the message
    if (this._deferredJobs.has(id)) {
      const { resolve, reject } = this._deferredJobs.get(id);

      if ("ok" in data) {
        resolve(data);
      } else if ("fail" in data) {
        reject(data);
      }
      this._deferredJobs.delete(id);
      return;
    }

    let start;
    let options;
    if (data.args) {
      options = data.args[data.args.length - 1];
    }
    // If |outExecutionDuration| option was supplied, start measuring the
    // duration of the operation.
    if (
      options &&
      typeof options === "object" &&
      "outExecutionDuration" in options
    ) {
      start = Date.now();
    }

    let result;
    let exn;
    let durationMs;
    let method = data.fun;
    try {
      this.log("Calling method", method);
      result = await this.dispatch(method, data.args);
      this.log("Method", method, "succeeded");
    } catch (ex) {
      exn = ex;
      this.log(
        "Error while calling agent method",
        method,
        exn,
        exn.moduleStack || exn.stack || ""
      );
    }

    if (start) {
      // Record duration
      durationMs = Date.now() - start;
      this.log("Method took", durationMs, "ms");
    }

    // Now, post a reply, possibly as an uncaught error.
    // We post this message from outside the |try ... catch| block
    // to avoid capturing errors that take place during |postMessage| and
    // built-in serialization.
    if (!exn) {
      this.log("Sending positive reply", result, "id is", id);
      if (result instanceof Meta) {
        if ("transfers" in result.meta) {
          // Take advantage of zero-copy transfers
          this.postMessage(
            { ok: result.data, id, durationMs },
            result.meta.transfers
          );
        } else {
          this.postMessage({ ok: result.data, id, durationMs });
        }
        if (result.meta.shutdown || false) {
          // Time to close the worker
          this.close();
        }
      } else {
        this.postMessage({ ok: result, id, durationMs });
      }
    } else if (exn.constructor.name == "DOMException") {
      // We can receive instances of DOMExceptions with file I/O.
      // DOMExceptions are not yet serializable (Bug 1561357) and must be
      // handled differently, as they only have a name and message
      this.log("Sending back DOM exception", exn.constructor.name);
      let error = {
        exn: exn.constructor.name,
        message: exn.message,
      };
      this.postMessage({ fail: error, id, durationMs });
    } else if (exn.constructor.name in EXCEPTION_NAMES) {
      // Rather than letting the DOM mechanism [de]serialize built-in
      // JS errors, which loses lots of information (in particular,
      // the constructor name, the moduleName and the moduleStack),
      // we [de]serialize them manually with a little more care.
      this.log("Sending back exception", exn.constructor.name, "id is", id);
      let error = {
        exn: exn.constructor.name,
        message: exn.message,
        fileName: exn.moduleName || exn.fileName,
        lineNumber: exn.lineNumber,
        stack: exn.moduleStack,
      };
      this.postMessage({ fail: error, id, durationMs });
    } else if ("toMsg" in exn) {
      // Extension mechanism for exception [de]serialization. We
      // assume that any exception with a method `toMsg()` knows how
      // to serialize itself. The other side is expected to have
      // registered a deserializer using the `ExceptionHandlers`
      // object.
      this.log(
        "Sending back an error that knows how to serialize itself",
        exn,
        "id is",
        id
      );
      let msg = exn.toMsg();
      this.postMessage({ fail: msg, id, durationMs });
    } else {
      // If we encounter an exception for which we have no
      // serialization mechanism in place, we have no choice but to
      // let the DOM handle said [de]serialization. We can just
      // attempt to mitigate the data loss by injecting `moduleName` and
      // `moduleStack`.
      this.log(
        "Sending back regular error",
        exn,
        exn.moduleStack || exn.stack,
        "id is",
        id
      );

      try {
        // Attempt to introduce human-readable filename and stack
        exn.filename = exn.moduleName;
        exn.stack = exn.moduleStack;
      } catch (_) {
        // Nothing we can do
      }
      throw exn;
    }
  },
};
exports.AbstractWorker = AbstractWorker;
PK
!<����U�Umodules/mozIntl.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const mozIntlHelper = Cc["@mozilla.org/mozintlhelper;1"].getService(
  Ci.mozIMozIntlHelper
);
const osPrefs = Cc["@mozilla.org/intl/ospreferences;1"].getService(
  Ci.mozIOSPreferences
);

/**
 * RegExp used to parse variant subtags from a BCP47 language tag.
 * For example: ca-valencia
 */
const variantSubtagsMatch = /(?:-(?:[a-z0-9]{5,8}|[0-9][a-z0-9]{3}))+$/;

function getDateTimePatternStyle(option) {
  switch (option) {
    case "full":
      return osPrefs.dateTimeFormatStyleFull;
    case "long":
      return osPrefs.dateTimeFormatStyleLong;
    case "medium":
      return osPrefs.dateTimeFormatStyleMedium;
    case "short":
      return osPrefs.dateTimeFormatStyleShort;
    default:
      return osPrefs.dateTimeFormatStyleNone;
  }
}

/**
 * Number of milliseconds in other time units.
 *
 * This is used by relative time format best unit
 * calculations.
 */
const second = 1e3;
const minute = 6e4;
const hour = 36e5;
const day = 864e5;

/**
 * Use by RelativeTimeFormat.
 *
 * Allows for defining a cached getter to perform
 * calculations only once.
 *
 * @param {Object} obj - Object to place the getter on.
 * @param {String} prop - Name of the property.
 * @param {Function} get - Function that will be used as a getter.
 */
function defineCachedGetter(obj, prop, get) {
  defineGetter(obj, prop, function () {
    if (!this._[prop]) {
      this._[prop] = get.call(this);
    }
    return this._[prop];
  });
}

/**
 * Used by RelativeTimeFormat.
 *
 * Defines a getter on an object
 *
 * @param {Object} obj - Object to place the getter on.
 * @param {String} prop - Name of the property.
 * @param {Function} get - Function that will be used as a getter.
 */
function defineGetter(obj, prop, get) {
  Object.defineProperty(obj, prop, { get, enumerable: true });
}

/**
 * Used by RelativeTimeFormat.
 *
 * Allows for calculation of the beginning of
 * a period for discrete distances.
 *
 * @param {Date} date - Date of which we're looking to find a start of.
 * @param {String} unit - Period to calculate the start of.
 *
 * @returns {Date}
 */
function startOf(date, unit) {
  date = new Date(date.getTime());
  switch (unit) {
    case "year":
      date.setMonth(0);
    // falls through
    case "month":
      date.setDate(1);
    // falls through
    case "day":
      date.setHours(0);
    // falls through
    case "hour":
      date.setMinutes(0);
    // falls through
    case "minute":
      date.setSeconds(0);
    // falls through
    case "second":
      date.setMilliseconds(0);
  }
  return date;
}

/**
 * Used by RelativeTimeFormat.
 *
 * Calculates the best fit unit to use for an absolute diff distance based
 * on thresholds.
 *
 * @param {Object} absDiff - Object with absolute diff per unit calculated.
 *
 * @returns {String}
 */
function bestFit(absDiff) {
  switch (true) {
    case absDiff.years > 0 && absDiff.months > threshold.month:
      return "year";
    case absDiff.months > 0 && absDiff.weeks > threshold.week:
      return "month";
    case absDiff.weeks > 0 && absDiff.days > threshold.day:
      return "week";
    case absDiff.days > 0 && absDiff.hours > threshold.hour:
      return "day";
    case absDiff.hours > 0 && absDiff.minutes > threshold.minute:
      return "hour";
    case absDiff.minutes > 0 && absDiff.seconds > threshold.second:
      return "minute";
    default:
      return "second";
  }
}

/**
 * Used by RelativeTimeFormat.
 *
 * Thresholds to use for calculating the best unit for relative time fromatting.
 */
const threshold = {
  month: 11, // at least 11 months before using year.
  week: 3, // at least 3 weeks before using month.
  day: 6, // at least 6 days before using week.
  hour: 6, // at least 6 hours before using day.
  minute: 59, // at least 59 minutes before using hour.
  second: 59, // at least 59 seconds before using minute.
};

/**
 * Notice: If you're updating this list, you should also
 *         update the list in
 *         languageNames.ftl and regionNames.ftl.
 */
const availableLocaleDisplayNames = {
  region: new Set([
    "ad",
    "ae",
    "af",
    "ag",
    "ai",
    "al",
    "am",
    "ao",
    "aq",
    "ar",
    "as",
    "at",
    "au",
    "aw",
    "az",
    "ba",
    "bb",
    "bd",
    "be",
    "bf",
    "bg",
    "bh",
    "bi",
    "bj",
    "bl",
    "bm",
    "bn",
    "bo",
    "bq",
    "br",
    "bs",
    "bt",
    "bv",
    "bw",
    "by",
    "bz",
    "ca",
    "cc",
    "cd",
    "cf",
    "cg",
    "ch",
    "ci",
    "ck",
    "cl",
    "cm",
    "cn",
    "co",
    "cp",
    "cr",
    "cu",
    "cv",
    "cw",
    "cx",
    "cy",
    "cz",
    "de",
    "dg",
    "dj",
    "dk",
    "dm",
    "do",
    "dz",
    "ec",
    "ee",
    "eg",
    "eh",
    "er",
    "es",
    "et",
    "fi",
    "fj",
    "fk",
    "fm",
    "fo",
    "fr",
    "ga",
    "gb",
    "gd",
    "ge",
    "gf",
    "gg",
    "gh",
    "gi",
    "gl",
    "gm",
    "gn",
    "gp",
    "gq",
    "gr",
    "gs",
    "gt",
    "gu",
    "gw",
    "gy",
    "hk",
    "hm",
    "hn",
    "hr",
    "ht",
    "hu",
    "id",
    "ie",
    "il",
    "im",
    "in",
    "io",
    "iq",
    "ir",
    "is",
    "it",
    "je",
    "jm",
    "jo",
    "jp",
    "ke",
    "kg",
    "kh",
    "ki",
    "km",
    "kn",
    "kp",
    "kr",
    "kw",
    "ky",
    "kz",
    "la",
    "lb",
    "lc",
    "li",
    "lk",
    "lr",
    "ls",
    "lt",
    "lu",
    "lv",
    "ly",
    "ma",
    "mc",
    "md",
    "me",
    "mf",
    "mg",
    "mh",
    "mk",
    "ml",
    "mm",
    "mn",
    "mo",
    "mp",
    "mq",
    "mr",
    "ms",
    "mt",
    "mu",
    "mv",
    "mw",
    "mx",
    "my",
    "mz",
    "na",
    "nc",
    "ne",
    "nf",
    "ng",
    "ni",
    "nl",
    "no",
    "np",
    "nr",
    "nu",
    "nz",
    "om",
    "pa",
    "pe",
    "pf",
    "pg",
    "ph",
    "pk",
    "pl",
    "pm",
    "pn",
    "pr",
    "pt",
    "pw",
    "py",
    "qa",
    "qm",
    "qs",
    "qu",
    "qw",
    "qx",
    "qz",
    "re",
    "ro",
    "rs",
    "ru",
    "rw",
    "sa",
    "sb",
    "sc",
    "sd",
    "se",
    "sg",
    "sh",
    "si",
    "sk",
    "sl",
    "sm",
    "sn",
    "so",
    "sr",
    "ss",
    "st",
    "sv",
    "sx",
    "sy",
    "sz",
    "tc",
    "td",
    "tf",
    "tg",
    "th",
    "tj",
    "tk",
    "tl",
    "tm",
    "tn",
    "to",
    "tr",
    "tt",
    "tv",
    "tw",
    "tz",
    "ua",
    "ug",
    "us",
    "uy",
    "uz",
    "va",
    "vc",
    "ve",
    "vg",
    "vi",
    "vn",
    "vu",
    "wf",
    "ws",
    "xa",
    "xb",
    "xc",
    "xd",
    "xe",
    "xg",
    "xh",
    "xj",
    "xk",
    "xl",
    "xm",
    "xp",
    "xq",
    "xr",
    "xs",
    "xt",
    "xu",
    "xv",
    "xw",
    "ye",
    "yt",
    "za",
    "zm",
    "zw",
  ]),
  language: new Set([
    "aa",
    "ab",
    "ach",
    "ae",
    "af",
    "ak",
    "am",
    "an",
    "ar",
    "as",
    "ast",
    "av",
    "ay",
    "az",
    "ba",
    "be",
    "bg",
    "bh",
    "bi",
    "bm",
    "bn",
    "bo",
    "br",
    "bs",
    "ca",
    "cak",
    "ce",
    "ch",
    "co",
    "cr",
    "crh",
    "cs",
    "csb",
    "cu",
    "cv",
    "cy",
    "da",
    "de",
    "dsb",
    "dv",
    "dz",
    "ee",
    "el",
    "en",
    "eo",
    "es",
    "et",
    "eu",
    "fa",
    "ff",
    "fi",
    "fj",
    "fo",
    "fr",
    "fur",
    "fy",
    "ga",
    "gd",
    "gl",
    "gn",
    "gu",
    "gv",
    "ha",
    "haw",
    "he",
    "hi",
    "hil",
    "ho",
    "hr",
    "hsb",
    "ht",
    "hu",
    "hy",
    "hz",
    "ia",
    "id",
    "ie",
    "ig",
    "ii",
    "ik",
    "io",
    "is",
    "it",
    "iu",
    "ja",
    "jv",
    "ka",
    "kab",
    "kg",
    "ki",
    "kj",
    "kk",
    "kl",
    "km",
    "kn",
    "ko",
    "kok",
    "kr",
    "ks",
    "ku",
    "kv",
    "kw",
    "ky",
    "la",
    "lb",
    "lg",
    "li",
    "lij",
    "ln",
    "lo",
    "lt",
    "ltg",
    "lu",
    "lv",
    "mai",
    "meh",
    "mg",
    "mh",
    "mi",
    "mix",
    "mk",
    "ml",
    "mn",
    "mr",
    "ms",
    "mt",
    "my",
    "na",
    "nb",
    "nd",
    "ne",
    "ng",
    "nl",
    "nn",
    "no",
    "nr",
    "nso",
    "nv",
    "ny",
    "oc",
    "oj",
    "om",
    "or",
    "os",
    "pa",
    "pi",
    "pl",
    "ps",
    "pt",
    "qu",
    "rm",
    "rn",
    "ro",
    "ru",
    "rw",
    "sa",
    "sat",
    "sc",
    "sco",
    "sd",
    "se",
    "sg",
    "si",
    "sk",
    "skr",
    "sl",
    "sm",
    "sn",
    "so",
    "son",
    "sq",
    "sr",
    "ss",
    "st",
    "su",
    "sv",
    "sw",
    "szl",
    "ta",
    "te",
    "tg",
    "th",
    "ti",
    "tig",
    "tk",
    "tl",
    "tlh",
    "tn",
    "to",
    "tr",
    "trs",
    "ts",
    "tt",
    "tw",
    "ty",
    "ug",
    "uk",
    "ur",
    "uz",
    "ve",
    "vi",
    "vo",
    "wa",
    "wen",
    "wo",
    "xh",
    "yi",
    "yo",
    "za",
    "zam",
    "zh",
    "zu",
  ]),
};

/**
 * Notice: If you're updating these names, you should also update the data
 *         in python/mozbuild/mozbuild/action/langpack_localeNames.json.
 */
const nativeLocaleNames = new Map(
  Object.entries({
    ach: "Acholi",
    af: "Afrikaans",
    an: "Aragonés",
    ar: "العربية",
    ast: "Asturianu",
    az: "Azərbaycanca",
    be: "Беларуская",
    bg: "Български",
    bn: "বাংলা",
    bo: "བོད་སྐད",
    br: "Brezhoneg",
    brx: "बड़ो",
    bs: "Bosanski",
    ca: "Català",
    "ca-valencia": "Català (Valencià)",
    cak: "Kaqchikel",
    cs: "Čeština",
    cy: "Cymraeg",
    da: "Dansk",
    de: "Deutsch",
    dsb: "Dolnoserbšćina",
    el: "Ελληνικά",
    "en-CA": "English (CA)",
    "en-GB": "English (GB)",
    "en-US": "English (US)",
    eo: "Esperanto",
    "es-AR": "Español (AR)",
    "es-CL": "Español (CL)",
    "es-ES": "Español (ES)",
    "es-MX": "Español (MX)",
    et: "Eesti",
    eu: "Euskara",
    fa: "فارسی",
    ff: "Pulaar",
    fi: "Suomi",
    fr: "Français",
    fur: "Furlan",
    "fy-NL": "Frysk",
    "ga-IE": "Gaeilge",
    gd: "Gàidhlig",
    gl: "Galego",
    gn: "Guarani",
    "gu-IN": "ગુજરાતી",
    he: "עברית",
    "hi-IN": "हिन्दी",
    hr: "Hrvatski",
    hsb: "Hornjoserbšćina",
    hu: "Magyar",
    "hy-AM": "հայերեն",
    ia: "Interlingua",
    id: "Indonesia",
    is: "Islenska",
    it: "Italiano",
    ja: "日本語",
    "ja-JP-mac": "日本語",
    ka: "ქართული",
    kab: "Taqbaylit",
    kk: "қазақ тілі",
    km: "ខ្មែរ",
    kn: "ಕನ್ನಡ",
    ko: "한국어",
    lij: "Ligure",
    lo: "ລາວ",
    lt: "Lietuvių",
    ltg: "Latgalīšu",
    lv: "Latviešu",
    mk: "македонски",
    ml: "മലയാളം",
    mr: "मराठी",
    ms: "Melayu",
    my: "မြန်မာ",
    "nb-NO": "Norsk Bokmål",
    "ne-NP": "नेपाली",
    nl: "Nederlands",
    "nn-NO": "Nynorsk",
    oc: "Occitan",
    or: "ଓଡ଼ିଆ",
    "pa-IN": "ਪੰਜਾਬੀ",
    pl: "Polski",
    "pt-BR": "Português (BR)",
    "pt-PT": "Português (PT)",
    rm: "Rumantsch",
    ro: "Română",
    ru: "Русский",
    sat: "ᱥᱟᱱᱛᱟᱲᱤ",
    sc: "Sardu",
    sco: "Scots",
    si: "සිංහල",
    sk: "Slovenčina",
    skr: "سرائیکی",
    sl: "Slovenščina",
    son: "Soŋay",
    sq: "Shqip",
    sr: "Cрпски",
    "sv-SE": "Svenska",
    szl: "Ślōnsko",
    ta: "தமிழ்",
    te: "తెలుగు",
    tg: "Тоҷикӣ",
    th: "ไทย",
    tl: "Tagalog",
    tr: "Türkçe",
    trs: "Triqui",
    uk: "Українська",
    ur: "اردو",
    uz: "O‘zbek",
    vi: "Tiếng Việt",
    wo: "Wolof",
    xh: "IsiXhosa",
    "zh-CN": "简体中文",
    "zh-TW": "正體中文",
  })
);

class MozRelativeTimeFormat extends Intl.RelativeTimeFormat {
  constructor(locales, options = {}, ...args) {
    // If someone is asking for MozRelativeTimeFormat, it's likely they'll want
    // to use `formatBestUnit` which works better with `auto`
    if (options.numeric === undefined) {
      options.numeric = "auto";
    }
    super(locales, options, ...args);
  }

  formatBestUnit(date, { now = new Date() } = {}) {
    const diff = {
      _: {},
      ms: date.getTime() - now.getTime(),
      years: date.getFullYear() - now.getFullYear(),
    };

    defineCachedGetter(diff, "months", function () {
      return this.years * 12 + date.getMonth() - now.getMonth();
    });
    defineCachedGetter(diff, "weeks", function () {
      return Math.trunc(this.days / 7);
    });
    defineCachedGetter(diff, "days", function () {
      return Math.trunc((startOf(date, "day") - startOf(now, "day")) / day);
    });
    defineCachedGetter(diff, "hours", function () {
      return Math.trunc((startOf(date, "hour") - startOf(now, "hour")) / hour);
    });
    defineCachedGetter(diff, "minutes", function () {
      return Math.trunc(
        (startOf(date, "minute") - startOf(now, "minute")) / minute
      );
    });
    defineCachedGetter(diff, "seconds", function () {
      return Math.trunc(
        (startOf(date, "second") - startOf(now, "second")) / second
      );
    });

    const absDiff = {
      _: {},
    };

    for (let prop of Object.keys(diff)) {
      defineGetter(absDiff, prop, function () {
        return Math.abs(diff[prop]);
      });
    }
    const unit = bestFit(absDiff);

    switch (unit) {
      case "year":
        return this.format(diff.years, unit);
      case "month":
        return this.format(diff.months, unit);
      case "week":
        return this.format(diff.weeks, unit);
      case "day":
        return this.format(diff.days, unit);
      case "hour":
        return this.format(diff.hours, unit);
      case "minute":
        return this.format(diff.minutes, unit);
      default:
        if (unit !== "second") {
          throw new TypeError(`Unsupported unit "${unit}"`);
        }
        return this.format(diff.seconds, unit);
    }
  }
}

export class MozIntl {
  Collator = Intl.Collator;
  ListFormat = Intl.ListFormat;
  Locale = Intl.Locale;
  NumberFormat = Intl.NumberFormat;
  PluralRules = Intl.PluralRules;
  RelativeTimeFormat = MozRelativeTimeFormat;

  constructor() {
    this._cache = {};
    Services.obs.addObserver(this, "intl:app-locales-changed", true);
  }

  observe() {
    // Clear cache when things change.
    this._cache = {};
  }

  getCalendarInfo(locales, ...args) {
    if (!this._cache.hasOwnProperty("getCalendarInfo")) {
      mozIntlHelper.addGetCalendarInfo(this._cache);
    }

    return this._cache.getCalendarInfo(locales, ...args);
  }

  getDisplayNamesDeprecated(locales, options = {}) {
    // Helper for IntlUtils.webidl, will be removed once Intl.DisplayNames is
    // available in non-privileged code.

    let { type, style, calendar, keys = [] } = options;

    let dn = new this.DisplayNames(locales, { type, style, calendar });
    let {
      locale: resolvedLocale,
      type: resolvedType,
      style: resolvedStyle,
      calendar: resolvedCalendar,
    } = dn.resolvedOptions();
    let values = keys.map(key => dn.of(key));

    return {
      locale: resolvedLocale,
      type: resolvedType,
      style: resolvedStyle,
      calendar: resolvedCalendar,
      values,
    };
  }

  getAvailableLocaleDisplayNames(type) {
    if (availableLocaleDisplayNames.hasOwnProperty(type)) {
      return Array.from(availableLocaleDisplayNames[type]);
    }

    return new Error("Unimplemented!");
  }

  getLanguageDisplayNames(locales, langCodes) {
    if (locales !== undefined) {
      throw new Error("First argument support not implemented yet");
    }

    if (!this._cache.hasOwnProperty("languageLocalization")) {
      const loc = new Localization(["toolkit/intl/languageNames.ftl"], true);
      this._cache.languageLocalization = loc;
    }

    const loc = this._cache.languageLocalization;

    return langCodes.map(langCode => {
      if (typeof langCode !== "string") {
        throw new TypeError("All language codes must be strings.");
      }
      let lcLangCode = langCode.toLowerCase();
      if (availableLocaleDisplayNames.language.has(lcLangCode)) {
        const value = loc.formatValueSync(`language-name-${lcLangCode}`);
        if (value !== null) {
          return value;
        }
      }
      return lcLangCode;
    });
  }

  getRegionDisplayNames(locales, regionCodes) {
    if (locales !== undefined) {
      throw new Error("First argument support not implemented yet");
    }

    if (!this._cache.hasOwnProperty("regionLocalization")) {
      const loc = new Localization(["toolkit/intl/regionNames.ftl"], true);
      this._cache.regionLocalization = loc;
    }

    const loc = this._cache.regionLocalization;

    return regionCodes.map(regionCode => {
      if (typeof regionCode !== "string") {
        throw new TypeError("All region codes must be strings.");
      }
      let lcRegionCode = regionCode.toLowerCase();
      if (availableLocaleDisplayNames.region.has(lcRegionCode)) {
        let regionID;
        // Allow changing names over time for specific regions
        switch (lcRegionCode) {
          case "bq":
            regionID = "region-name-bq-2018";
            break;
          case "cv":
            regionID = "region-name-cv-2020";
            break;
          case "cz":
            regionID = "region-name-cz-2019";
            break;
          case "mk":
            regionID = "region-name-mk-2019";
            break;
          case "sz":
            regionID = "region-name-sz-2019";
            break;
          default:
            regionID = `region-name-${lcRegionCode}`;
        }

        const value = loc.formatValueSync(regionID);
        if (value !== null) {
          return value;
        }
      }
      return regionCode.toUpperCase();
    });
  }

  getLocaleDisplayNames(locales, localeCodes, options = {}) {
    const { preferNative = false } = options;

    if (locales !== undefined) {
      throw new Error("First argument support not implemented yet");
    }
    // Patterns hardcoded from CLDR 33 english.
    // We can later look into fetching them from CLDR directly.
    const localePattern = "{0} ({1})";
    const localeSeparator = ", ";

    return localeCodes.map(localeCode => {
      if (typeof localeCode !== "string") {
        throw new TypeError("All locale codes must be strings.");
      }

      if (preferNative && nativeLocaleNames.has(localeCode)) {
        return nativeLocaleNames.get(localeCode);
      }

      let locale;
      try {
        locale = new Intl.Locale(localeCode.replaceAll("_", "-"));
      } catch {
        return localeCode;
      }

      const {
        language: languageSubtag,
        script: scriptSubtag,
        region: regionSubtag,
      } = locale;

      const variantSubtags = locale.baseName.match(variantSubtagsMatch);

      const displayName = [
        this.getLanguageDisplayNames(locales, [languageSubtag])[0],
      ];

      if (scriptSubtag) {
        displayName.push(scriptSubtag);
      }

      if (regionSubtag) {
        displayName.push(
          this.getRegionDisplayNames(locales, [regionSubtag])[0]
        );
      }

      if (variantSubtags) {
        displayName.push(...variantSubtags[0].substr(1).split("-")); // Collapse multiple variants.
      }

      let modifiers;
      if (displayName.length === 1) {
        return displayName[0];
      } else if (displayName.length > 2) {
        modifiers = displayName.slice(1).join(localeSeparator);
      } else {
        modifiers = displayName[1];
      }
      return localePattern
        .replace("{0}", displayName[0])
        .replace("{1}", modifiers);
    });
  }

  getScriptDirection(locale) {
    // This is a crude implementation until Bug 1693576 lands.
    // See justification in toolkit/components/mozintl/mozIMozIntl.idl
    const { language } = new Intl.Locale(locale);
    if (
      language == "ar" ||
      language == "ckb" ||
      language == "fa" ||
      language == "he" ||
      language == "ur"
    ) {
      return "rtl";
    }
    return "ltr";
  }

  stringHasRTLChars(str) {
    return mozIntlHelper.stringHasRTLChars(str);
  }

  get DateTimeFormat() {
    if (!this._cache.hasOwnProperty("DateTimeFormat")) {
      mozIntlHelper.addDateTimeFormatConstructor(this._cache);

      const DateTimeFormat = this._cache.DateTimeFormat;

      class MozDateTimeFormat extends DateTimeFormat {
        constructor(locales, options, ...args) {
          let resolvedLocales = DateTimeFormat.supportedLocalesOf(locales);
          if (options) {
            if (options.dateStyle || options.timeStyle) {
              options.pattern = osPrefs.getDateTimePattern(
                getDateTimePatternStyle(options.dateStyle),
                getDateTimePatternStyle(options.timeStyle),
                resolvedLocales[0]
              );
            } else {
              // make sure that user doesn't pass a pattern explicitly
              options.pattern = undefined;
            }
          }
          super(resolvedLocales, options, ...args);
        }
      }
      this._cache.MozDateTimeFormat = MozDateTimeFormat;
    }

    return this._cache.MozDateTimeFormat;
  }

  get DisplayNames() {
    if (!this._cache.hasOwnProperty("DisplayNames")) {
      mozIntlHelper.addDisplayNamesConstructor(this._cache);
    }

    return this._cache.DisplayNames;
  }
}

MozIntl.prototype.classID = Components.ID(
  "{35ec195a-e8d0-4300-83af-c8a2cc84b4a3}"
);
MozIntl.prototype.QueryInterface = ChromeUtils.generateQI([
  "mozIMozIntl",
  "nsIObserver",
  "nsISupportsWeakReference",
]);
PK
!<T?���E�EHchrome/toolkit/content/global/translations/TranslationsTelemetry.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  return console.createInstance({
    maxLogLevelPref: "toolkit.telemetry.translations.logLevel",
    prefix: "TranslationsTelemetry",
  });
});

/**
 * Telemetry functions for Translations actors
 */
export class TranslationsTelemetry {
  /**
   * A cached value to hold the current flowId.
   */
  static #flowId = null;

  /**
   * Logs the telemetry event to the console if enabled by toolkit.telemetry.translations.logLevel
   *
   * @param {Function} caller - The calling function.
   * @param {object} [data] - Optional data passed to telemetry.
   */
  static logEventToConsole(caller, data) {
    const id = TranslationsTelemetry.getOrCreateFlowId().substring(0, 5);
    lazy.console?.debug(
      `flowId[${id}]: ${caller.name}`,
      ...(data ? [data] : [])
    );
  }

  /**
   * Telemetry functions for the Full Page Translations panel.
   *
   * @returns {FullPageTranslationsPanelTelemetry}
   */
  static fullPagePanel() {
    return FullPageTranslationsPanelTelemetry;
  }

  /**
   * Telemetry functions for the SelectTranslationsPanel.
   *
   * @returns {SelectTranslationsPanelTelemetry}
   */
  static selectTranslationsPanel() {
    return SelectTranslationsPanelTelemetry;
  }

  /**
   * Forces the creation of a new Translations telemetry flowId and returns it.
   *
   * @returns {string}
   */
  static createFlowId() {
    const flowId = crypto.randomUUID();
    TranslationsTelemetry.#flowId = flowId;
    return flowId;
  }

  /**
   * Returns a Translations telemetry flowId by retrieving the cached value
   * if available, or creating a new one otherwise.
   *
   * @returns {string}
   */
  static getOrCreateFlowId() {
    // If we have the flowId cached, return it.
    if (TranslationsTelemetry.#flowId) {
      return TranslationsTelemetry.#flowId;
    }

    // If no flowId exists, create one.
    return TranslationsTelemetry.createFlowId();
  }

  /**
   * Records a telemetry event when full page translation fails.
   *
   * @param {string} errorMessage
   */
  static onError(errorMessage) {
    Glean.translations.errorRate.addToNumerator(1);
    Glean.translations.error.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
      reason: errorMessage,
    });
    TranslationsTelemetry.logEventToConsole(TranslationsTelemetry.onError, {
      errorMessage,
    });
  }

  /**
   * Records a telemetry event when a full-page translation request is sent.
   *
   * @param {object} data
   * @param {boolean} data.autoTranslate
   * @param {string} data.docLangTag
   * @param {string} data.fromLanguage
   * @param {string} data.toLanguage
   * @param {string} data.topPreferredLanguage
   * @param {string} data.requestTarget
   * @param {number} data.sourceTextCodeUnits
   * @param {number} data.sourceTextWordCount
   */
  static onTranslate(data) {
    const {
      autoTranslate,
      docLangTag,
      fromLanguage,
      requestTarget,
      toLanguage,
      topPreferredLanguage,
      sourceTextCodeUnits,
      sourceTextWordCount,
    } = data;
    Glean.translations.requestsCount.add(1);
    Glean.translations.requestCount[requestTarget ?? "full_page"].add(1);
    Glean.translations.translationRequest.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
      from_language: fromLanguage,
      to_language: toLanguage,
      auto_translate: autoTranslate,
      document_language: docLangTag,
      top_preferred_language: topPreferredLanguage,
      request_target: requestTarget ?? "full_page",
      source_text_code_units: sourceTextCodeUnits,
      source_text_word_count: sourceTextWordCount,
    });
    TranslationsTelemetry.logEventToConsole(
      TranslationsTelemetry.onTranslate,
      data
    );
  }

  static onRestorePage() {
    Glean.translations.restorePage.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      TranslationsTelemetry.onRestorePage
    );
  }
}

/**
 * Telemetry functions for the FullPageTranslationsPanel UI
 */
class FullPageTranslationsPanelTelemetry {
  /**
   * Records a telemetry event when the FullPageTranslationsPanel is opened.
   *
   * @param {object} data
   * @param {string} data.viewName
   * @param {string} data.docLangTag
   * @param {boolean} data.autoShow
   * @param {boolean} data.maintainFlow
   * @param {boolean} data.openedFromAppMenu
   */
  static onOpen(data) {
    Glean.translationsPanel.open.record({
      flow_id: data.maintainFlow
        ? TranslationsTelemetry.getOrCreateFlowId()
        : TranslationsTelemetry.createFlowId(),
      auto_show: data.autoShow,
      view_name: data.viewName,
      document_language: data.docLangTag,
      opened_from: data.openedFromAppMenu ? "appMenu" : "translationsButton",
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onOpen,
      data
    );
  }

  static onClose() {
    Glean.translationsPanel.close.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onClose
    );
  }

  static onOpenFromLanguageMenu() {
    Glean.translationsPanel.openFromLanguageMenu.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onOpenFromLanguageMenu
    );
  }

  static onChangeFromLanguage(langTag) {
    Glean.translationsPanel.changeFromLanguage.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
      language: langTag,
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onChangeFromLanguage,
      {
        langTag,
      }
    );
  }

  static onCloseFromLanguageMenu() {
    Glean.translationsPanel.closeFromLanguageMenu.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onCloseFromLanguageMenu
    );
  }

  static onOpenToLanguageMenu() {
    Glean.translationsPanel.openToLanguageMenu.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onOpenToLanguageMenu
    );
  }

  static onChangeToLanguage(langTag) {
    Glean.translationsPanel.changeToLanguage.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
      language: langTag,
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onChangeToLanguage,
      {
        langTag,
      }
    );
  }

  static onCloseToLanguageMenu() {
    Glean.translationsPanel.closeToLanguageMenu.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onChangeToLanguage
    );
  }

  static onOpenSettingsMenu() {
    Glean.translationsPanel.openSettingsMenu.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onOpenSettingsMenu
    );
  }

  static onCloseSettingsMenu() {
    Glean.translationsPanel.closeSettingsMenu.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onCloseSettingsMenu
    );
  }

  static onCancelButton() {
    Glean.translationsPanel.cancelButton.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onCancelButton
    );
  }

  static onChangeSourceLanguageButton() {
    Glean.translationsPanel.changeSourceLanguageButton.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onChangeSourceLanguageButton
    );
  }

  static onDismissErrorButton() {
    Glean.translationsPanel.dismissErrorButton.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onDismissErrorButton
    );
  }

  static onRestorePageButton() {
    Glean.translationsPanel.restorePageButton.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onRestorePageButton
    );
  }

  static onTranslateButton() {
    Glean.translationsPanel.translateButton.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onTranslateButton
    );
  }

  static onAlwaysOfferTranslations(toggledOn) {
    Glean.translationsPanel.alwaysOfferTranslations.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
      toggled_on: toggledOn,
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onAlwaysOfferTranslations,
      {
        toggledOn,
      }
    );
  }

  static onAlwaysTranslateLanguage(langTag, toggledOn) {
    Glean.translationsPanel.alwaysTranslateLanguage.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
      language: langTag,
      toggled_on: toggledOn,
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onAlwaysTranslateLanguage,
      {
        langTag,
        toggledOn,
      }
    );
  }

  static onNeverTranslateLanguage(langTag, toggledOn) {
    Glean.translationsPanel.neverTranslateLanguage.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
      language: langTag,
      toggled_on: toggledOn,
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onNeverTranslateLanguage,
      {
        langTag,
        toggledOn,
      }
    );
  }

  static onNeverTranslateSite(toggledOn) {
    Glean.translationsPanel.neverTranslateSite.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
      toggled_on: toggledOn,
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onNeverTranslateSite,
      {
        toggledOn,
      }
    );
  }

  static onManageLanguages() {
    Glean.translationsPanel.manageLanguages.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onManageLanguages
    );
  }

  static onAboutTranslations() {
    Glean.translationsPanel.aboutTranslations.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onAboutTranslations
    );
  }

  static onLearnMoreLink() {
    Glean.translationsPanel.learnMore.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      FullPageTranslationsPanelTelemetry.onLearnMoreLink
    );
  }
}

/**
 * Telemetry functions for the SelectTranslationsPanel UI
 */
class SelectTranslationsPanelTelemetry {
  /**
   * Records a telemetry event when the SelectTranslationsPanel is opened.
   *
   * @param {object} data
   * @param {string} data.docLangTag
   * @param {boolean} data.maintainFlow
   * @param {string} data.fromLanguage
   * @param {string} data.toLanguage
   * @param {string} data.topPreferredLanguage
   * @param {string} data.textSource
   */
  static onOpen(data) {
    Glean.translationsSelectTranslationsPanel.open.record({
      flow_id: data.maintainFlow
        ? TranslationsTelemetry.getOrCreateFlowId()
        : TranslationsTelemetry.createFlowId(),
      document_language: data.docLangTag,
      from_language: data.fromLanguage,
      to_language: data.toLanguage,
      top_preferred_language: data.topPreferredLanguage,
      text_source: data.textSource,
    });
    TranslationsTelemetry.logEventToConsole(
      SelectTranslationsPanelTelemetry.onOpen,
      data
    );
  }

  static onClose() {
    Glean.translationsSelectTranslationsPanel.close.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      SelectTranslationsPanelTelemetry.onClose
    );
  }

  static onCancelButton() {
    Glean.translationsSelectTranslationsPanel.cancelButton.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      SelectTranslationsPanelTelemetry.onCancelButton
    );
  }

  static onCopyButton() {
    Glean.translationsSelectTranslationsPanel.copyButton.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      SelectTranslationsPanelTelemetry.onCopyButton
    );
  }

  static onDoneButton() {
    Glean.translationsSelectTranslationsPanel.doneButton.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      SelectTranslationsPanelTelemetry.onDoneButton
    );
  }

  static onTranslateButton({ detectedLanguage, fromLanguage, toLanguage }) {
    Glean.translationsSelectTranslationsPanel.translateButton.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
      detected_language: detectedLanguage,
      from_language: fromLanguage,
      to_language: toLanguage,
    });
    TranslationsTelemetry.logEventToConsole(
      SelectTranslationsPanelTelemetry.onTranslateButton,
      {
        detectedLanguage,
        fromLanguage,
        toLanguage,
      }
    );
  }

  static onTranslateFullPageButton() {
    Glean.translationsSelectTranslationsPanel.translateFullPageButton.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      SelectTranslationsPanelTelemetry.onTranslateFullPageButton
    );
  }

  static onTryAgainButton() {
    Glean.translationsSelectTranslationsPanel.tryAgainButton.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      SelectTranslationsPanelTelemetry.onTryAgainButton
    );
  }

  static onChangeFromLanguage({ previousLangTag, currentLangTag, docLangTag }) {
    Glean.translationsSelectTranslationsPanel.changeFromLanguage.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
      document_language: docLangTag,
      previous_language: previousLangTag,
      language: currentLangTag,
    });
    TranslationsTelemetry.logEventToConsole(
      SelectTranslationsPanelTelemetry.onChangeFromLanguage,
      { previousLangTag, currentLangTag, docLangTag }
    );
  }

  static onChangeToLanguage(langTag) {
    Glean.translationsSelectTranslationsPanel.changeToLanguage.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
      language: langTag,
    });
    TranslationsTelemetry.logEventToConsole(
      SelectTranslationsPanelTelemetry.onChangeToLanguage,
      { langTag }
    );
  }

  static onOpenSettingsMenu() {
    Glean.translationsSelectTranslationsPanel.openSettingsMenu.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      SelectTranslationsPanelTelemetry.onOpenSettingsMenu
    );
  }

  static onTranslationSettings() {
    Glean.translationsSelectTranslationsPanel.translationSettings.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      SelectTranslationsPanelTelemetry.onTranslationSettings
    );
  }

  static onAboutTranslations() {
    Glean.translationsSelectTranslationsPanel.aboutTranslations.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
    });
    TranslationsTelemetry.logEventToConsole(
      SelectTranslationsPanelTelemetry.onAboutTranslations
    );
  }

  static onInitializationFailureMessage() {
    Glean.translationsSelectTranslationsPanel.initializationFailureMessage.record(
      {
        flow_id: TranslationsTelemetry.getOrCreateFlowId(),
      }
    );
    TranslationsTelemetry.logEventToConsole(
      SelectTranslationsPanelTelemetry.onInitializationFailureMessage
    );
  }

  /**
   * Records a telemetry event when the translation-failure message is displayed.
   *
   * @param {object} data
   * @param {string} data.fromLanguage
   * @param {string} data.toLanguage
   */
  static onTranslationFailureMessage(data) {
    Glean.translationsSelectTranslationsPanel.translationFailureMessage.record({
      flow_id: TranslationsTelemetry.getOrCreateFlowId(),
      from_language: data.fromLanguage,
      to_language: data.toLanguage,
    });
    TranslationsTelemetry.logEventToConsole(
      SelectTranslationsPanelTelemetry.onTranslationFailureMessage,
      data
    );
  }

  /**
   * Records a telemetry event when the unsupported-language message is displayed.
   *
   * @param {object} data
   * @param {string} data.docLangTag
   * @param {string} data.detectedLanguage
   */
  static onUnsupportedLanguageMessage(data) {
    Glean.translationsSelectTranslationsPanel.unsupportedLanguageMessage.record(
      {
        flow_id: TranslationsTelemetry.getOrCreateFlowId(),
        document_language: data.docLangTag,
        detected_language: data.detectedLanguage,
      }
    );
    TranslationsTelemetry.logEventToConsole(
      SelectTranslationsPanelTelemetry.onUnsupportedLanguageMessage,
      data
    );
  }
}
PK
!<�JUCC5chrome/toolkit/skin/classic/global/icons/settings.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill light-dark(black, white)" fill-opacity="context-fill-opacity">
  <path d="m8.628 16-1.25 0a1.632 1.632 0 0 1-1.562-1.177l-.406-1.414a5.939 5.939 0 0 1-.78-.466l-1.448.36a1.632 1.632 0 0 1-1.799-.765l-.625-1.082a1.633 1.633 0 0 1 .229-1.931l1.045-1.101A3.279 3.279 0 0 1 2 8c0-.153.014-.302.032-.45L.999 6.479a1.63 1.63 0 0 1-.238-1.94l.625-1.083a1.636 1.636 0 0 1 1.787-.768l1.477.355c.258-.179.51-.329.761-.452l.406-1.414A1.63 1.63 0 0 1 7.378 0l1.25 0c.714 0 1.354.478 1.559 1.163l.425 1.436c.242.121.484.266.739.444l1.478-.355a1.635 1.635 0 0 1 1.786.768l.625 1.083c.36.625.263 1.422-.237 1.941l-1.035 1.07c.018.148.032.297.032.45 0 .145-.014.285-.031.424l1.045 1.101c.492.519.586 1.312.229 1.931l-.625 1.083a1.63 1.63 0 0 1-1.8.764l-1.447-.36c-.259.182-.51.333-.759.458l-.425 1.437A1.638 1.638 0 0 1 8.628 16zm-4.016-4.341.528.109a4.72 4.72 0 0 0 1.032.615l.359.404.485 1.691c.046.16.194.271.36.271l1.25 0a.37.37 0 0 0 .359-.269l.506-1.707.355-.398c.324-.137.645-.33 1.01-.608l.529-.109 1.731.431a.378.378 0 0 0 .416-.176l.625-1.083a.378.378 0 0 0-.053-.446l-1.25-1.317-.167-.505.022-.174c.021-.127.041-.255.041-.388 0-.149-.021-.293-.042-.437l-.021-.15.171-.513 1.244-1.289a.378.378 0 0 0 .055-.448l-.625-1.082a.38.38 0 0 0-.412-.177l-1.758.422-.521-.108a4.7 4.7 0 0 0-.991-.594l-.356-.398-.506-1.707a.373.373 0 0 0-.36-.269l-1.25 0a.377.377 0 0 0-.36.271l-.486 1.691-.36.405a4.71 4.71 0 0 0-1.013.6l-.521.109-1.757-.422a.383.383 0 0 0-.413.177l-.625 1.083a.375.375 0 0 0 .055.447L3.142 6.9l.171.514-.021.15A2.981 2.981 0 0 0 3.25 8c0 .133.02.261.037.389l.022.174-.167.505-1.25 1.317a.38.38 0 0 0-.053.446l.625 1.083a.38.38 0 0 0 .415.176l1.733-.431z"/>
  <path d="M8 6.25c.965 0 1.75.785 1.75 1.75S8.965 9.75 8 9.75 6.25 8.965 6.25 8 7.035 6.25 8 6.25M8 5a3 3 0 1 0 0 6 3 3 0 0 0 0-6z"/>
</svg>
PK
!<��Z��:chrome/toolkit/skin/classic/global/icons/arrow-down-12.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" width="12" height="12" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m6.24 8.999 3.614-3.613a.498.498 0 0 0 0-.707.5.5 0 0 0-.707 0L5.999 7.826 2.854 4.68a.5.5 0 0 0-.707.707L5.759 9l.481-.001z"/>
</svg>
PK
!<_p�0,,2chrome/toolkit/skin/classic/global/icons/error.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M7.625 15.5a7.5 7.5 0 1 1 0-15 7.5 7.5 0 0 1 0 15zM8.25 4.625a.625.625 0 0 0-1.25 0l0 4.438a.625.625 0 0 0 1.25 0l0-4.438zm0 6.375L8 10.75l-.75 0L7 11l0 .75.25.25.75 0 .25-.25 0-.75z"/>
</svg>
PK
!<7�l�		(modules/ContentPrefServiceParent.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  _methodsCallableFromChild: "resource://gre/modules/ContentPrefUtils.sys.mjs",
});

let loadContext = Cu.createLoadContext();
let privateLoadContext = Cu.createPrivateLoadContext();

function contextArg(context) {
  return context && context.usePrivateBrowsing
    ? privateLoadContext
    : loadContext;
}

export class ContentPrefsParent extends JSProcessActorParent {
  constructor() {
    super();

    // The names we're using this observer object for, used to keep track
    // of the number of names we care about as well as for removing this
    // observer if its associated process goes away.
    this._prefsToObserve = new Set();
    this._observer = null;
  }

  didDestroy() {
    if (this._observer) {
      for (let i of this._prefsToObserve) {
        lazy.cps2.removeObserverForName(i, this._observer);
      }
      this._observer = null;
    }
  }

  receiveMessage(msg) {
    switch (msg.name) {
      case "ContentPrefs:AddObserverForName": {
        // The child process is responsible for not adding multiple parent
        // observers for the same name.
        let actor = this;
        if (!this._observer) {
          this._observer = {
            onContentPrefSet(group, name, value, isPrivate) {
              actor.onContentPrefSet(group, name, value, isPrivate);
            },
            onContentPrefRemoved(group, name, isPrivate) {
              actor.onContentPrefRemoved(group, name, isPrivate);
            },
          };
        }

        let prefName = msg.data.name;
        this._prefsToObserve.add(prefName);
        lazy.cps2.addObserverForName(prefName, this._observer);
        break;
      }

      case "ContentPrefs:RemoveObserverForName": {
        let prefName = msg.data.name;
        this._prefsToObserve.delete(prefName);
        if (this._prefsToObserve.size == 0) {
          lazy.cps2.removeObserverForName(prefName, this._observer);
          this._observer = null;
        }
        break;
      }

      case "ContentPrefs:FunctionCall":
        let data = msg.data;
        let signature;

        if (
          !lazy._methodsCallableFromChild.some(([method, args]) => {
            if (method == data.call) {
              signature = args;
              return true;
            }
            return false;
          })
        ) {
          throw new Error(`Can't call ${data.call} from child!`);
        }

        let actor = this;
        let args = data.args;

        return new Promise(() => {
          let listener = {
            handleResult(pref) {
              actor.sendAsyncMessage("ContentPrefs:HandleResult", {
                requestId: data.requestId,
                contentPref: {
                  domain: pref.domain,
                  name: pref.name,
                  value: pref.value,
                },
              });
            },

            handleError(error) {
              actor.sendAsyncMessage("ContentPrefs:HandleError", {
                requestId: data.requestId,
                error,
              });
            },
            handleCompletion(reason) {
              actor.sendAsyncMessage("ContentPrefs:HandleCompletion", {
                requestId: data.requestId,
                reason,
              });
            },
          };
          // Push our special listener.
          args.push(listener);

          // Process context argument for forwarding
          let contextIndex = signature.indexOf("context");
          if (contextIndex > -1) {
            args[contextIndex] = contextArg(args[contextIndex]);
          }

          // And call the function.
          lazy.cps2[data.call](...args);
        });
    }

    return undefined;
  }

  onContentPrefSet(group, name, value, isPrivate) {
    this.sendAsyncMessage("ContentPrefs:NotifyObservers", {
      name,
      callback: "onContentPrefSet",
      args: [group, name, value, isPrivate],
    });
  }

  onContentPrefRemoved(group, name, isPrivate) {
    this.sendAsyncMessage("ContentPrefs:NotifyObservers", {
      name,
      callback: "onContentPrefRemoved",
      args: [group, name, isPrivate],
    });
  }
}

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "cps2",
  "@mozilla.org/content-pref/service;1",
  "nsIContentPrefService2"
);
PK
!<���dictionaries/en-US.affSET ISO8859-1
TRY esianrtolcdugmphbyfvkwzESIANRTOLCDUGMPHBYFVKWZ'
NOSUGGEST !

# ordinal numbers
COMPOUNDMIN 1
# only in compounds: 1th, 2th, 3th
ONLYINCOMPOUND c
# compound rules:
# 1. [0-9]*1[0-9]th (10th, 11th, 12th, 56714th, etc.)
# 2. [0-9]*[02-9](1st|2nd|3rd|[4-9]th) (21st, 22nd, 123rd, 1234th, etc.)
COMPOUNDRULE 2
COMPOUNDRULE n*1t
COMPOUNDRULE n*mp
WORDCHARS 0123456789

PFX A Y 1
PFX A   0     re         .

PFX I Y 1
PFX I   0     in         .

PFX U Y 1
PFX U   0     un         .

PFX C Y 1
PFX C   0     de          .

PFX E Y 1
PFX E   0     dis         .

PFX F Y 1
PFX F   0     con         .

PFX K Y 1
PFX K   0     pro         .

SFX V N 2
SFX V   e     ive        e
SFX V   0     ive        [^e]

SFX N Y 3
SFX N   e     ion        e
SFX N   y     ication    y 
SFX N   0     en         [^ey] 

SFX X Y 3
SFX X   e     ions       e
SFX X   y     ications   y
SFX X   0     ens        [^ey]

SFX H N 2
SFX H   y     ieth       y
SFX H   0     th         [^y] 

SFX Y Y 1
SFX Y   0     ly         .

SFX G Y 2
SFX G   e     ing        e
SFX G   0     ing        [^e] 

SFX J Y 2
SFX J   e     ings       e
SFX J   0     ings       [^e]

SFX D Y 4
SFX D   0     d          e
SFX D   y     ied        [^aeiou]y
SFX D   0     ed         [^ey]
SFX D   0     ed         [aeiou]y

SFX T N 4
SFX T   0     st         e
SFX T   y     iest       [^aeiou]y
SFX T   0     est        [aeiou]y
SFX T   0     est        [^ey]

SFX R Y 4
SFX R   0     r          e
SFX R   y     ier        [^aeiou]y
SFX R   0     er         [aeiou]y
SFX R   0     er         [^ey]

SFX Z Y 4
SFX Z   0     rs         e
SFX Z   y     iers       [^aeiou]y
SFX Z   0     ers        [aeiou]y
SFX Z   0     ers        [^ey]

SFX S Y 4
SFX S   y     ies        [^aeiou]y
SFX S   0     s          [aeiou]y
SFX S   0     es         [sxzh]
SFX S   0     s          [^sxzhy]

SFX P Y 3
SFX P   y     iness      [^aeiou]y
SFX P   0     ness       [aeiou]y
SFX P   0     ness       [^y]

SFX M Y 1
SFX M   0     's         .

SFX B Y 3
SFX B   0     able       [^aeiou]
SFX B   0     able       ee
SFX B   e     able       [^aeiou]e

SFX L Y 1
SFX L   0     ment       .

REP 90
REP a ei
REP ei a
REP a ey
REP ey a
REP ai ie
REP ie ai
REP alot a_lot
REP are air
REP are ear
REP are eir
REP air are
REP air ere
REP ere air
REP ere ear
REP ere eir
REP ear are
REP ear air
REP ear ere
REP eir are
REP eir ere
REP ch te
REP te ch
REP ch ti
REP ti ch
REP ch tu
REP tu ch
REP ch s
REP s ch
REP ch k
REP k ch
REP f ph
REP ph f
REP gh f
REP f gh
REP i igh
REP igh i
REP i uy
REP uy i
REP i ee
REP ee i
REP j di
REP di j
REP j gg
REP gg j
REP j ge
REP ge j
REP s ti
REP ti s
REP s ci
REP ci s
REP k cc
REP cc k
REP k qu
REP qu k
REP kw qu
REP o eau
REP eau o
REP o ew
REP ew o
REP oo ew
REP ew oo
REP ew ui
REP ui ew
REP oo ui
REP ui oo
REP ew u
REP u ew
REP oo u
REP u oo
REP u oe
REP oe u
REP u ieu
REP ieu u
REP ue ew
REP ew ue
REP uff ough
REP oo ieu
REP ieu oo
REP ier ear
REP ear ier
REP ear air
REP air ear
REP w qu
REP qu w
REP z ss
REP ss z
REP shun tion
REP shun sion
REP shun cion
REP size cise
PK
!<eò�	�	dictionaries/en-US.dic53569
0/nm
0th/pt
1/n1
1st/p
1th/tc
2/nm
2nd/p
2th/tc
3/nm
3rd/p
3th/tc
4/nm
4th/pt
5/nm
5th/pt
6/nm
6th/pt
7/nm
7th/pt
8/nm
8th/pt
9/nm
9th/pt
A/SM
AA/M
AAA
AB/M
ABA
ABC/M
ABM/SM
ABS
AC/M
ACLU/M
ACT
ACTH/M
AD/M
ADC
ADD
ADM
ADP/M
AF
AFAIK
AFB
AFC/M
AFDC
AFN
AFT
AI/SM
AIDS/M
AK
AL
AM/M
AMA
AMD/M
ANSI/S
ANZUS/M
AOL/M
AP/M
APB
APC
API/SM
APO
APR
AR
ARC
ASAP
ASCII/SM
ASL/M
ASPCA
ATM/M
ATP/M
ATPase/M
ATV
AV
AVI
AWACS/M
AWOL/M
AWS/M
AZ/M
AZT/M
Aachen/M
Aaliyah/M
Aaron/M
Ab's
Abba/S
Abbas/M
Abbasid/M
Abbie/M
Abbott/M
Abby/M
Abdel/M
Abdul/M
Abe/M
AbeBooks
Abel/M
Abelard/M
Abelson/M
Aberdeen/M
Abernathy/M
Abidjan/M
Abie/M
Abigail/M
Abilene/M
Abner/M
Aborigine/MS
Abra/M
Abraham/M
Abram/MS
Abramo/M
Abrams/M
Absalom/M
Abuja/M
Abyssinia/M
Abyssinian/M
Ac/M
Acadia/M
Acapulco/M
Accenture/M
Accra/M
Acevedo/M
Achaean/M
Achebe/M
Achernar/M
Acheson/M
Achilles/M
Aconcagua/M
Acosta/M
Acropolis
Acrux/M
Actaeon/M
ActiveX/M
Acton/M
Acts/M
Acuff/M
Acura/M
Ada/SM
Adah/M
Adair/M
Adaline/M
Adam/SM
Adamo/M
Adams/M
Adan/M
Adana/M
Adar/M
AddThis/M
Adda/M
Addams/M
Adderley/M
Addi/M
Addie/M
Addison/M
Addy/M
Ade/M
Adel/M
Adela/M
Adelaida/M
Adelaide/M
Adelbert/M
Adele/M
Adelheid/M
Adelina/M
Adeline/M
Adelle/M
Aden/M
Adena/M
Adenauer/M
Adey/M
Adham/M
Adhara/M
Adi/M
Adidas/M
Adina/M
Adirondack/SM
Adirondacks/M
Adkins/M
Adlai/M
Adler/M
Adm
Admiralty
Adolf/M
Adolfo/M
Adolph/M
Adolphe/M
Adolphus/M
Adonis/MS
Adore's
Adrenalin/MS
Adria/M
Adrian/M
Adriana/M
Adriane/M
Adrianna/M
Adrianne/M
Adriano/M
Adriatic/M
Adrien/M
Adrienne/M
Advent/MS
Adventist/MS
Advil/M
Aegean/M
Aelfric/M
Aeneas/M
Aeneid/M
Aeolus/M
Aeroflot/M
Aeschylus/M
Aesculapius/M
Aesop/M
Afghan/SM
Afghani/M
Afghanistan/M
Afr
Africa/M
African/SM
Afrikaans/M
Afrikaner/SM
Afro/SM
Afrocentric
Afrocentrism/M
Afton/M
Ag/M
Agamemnon/M
Agana
Agassi/M
Agassiz/M
Agata/M
Agatha/M
Agathe/M
Aggie/M
Agilent
Aglaia/M
Agnes/M
Agnese/M
Agnew/M
Agni/M
Agosto/M
Agra/M
Agricola/M
Agrippa/M
Agrippina/M
Aguadilla/M
Aguascalientes
Aguilar/M
Aguinaldo/M
Aguirre/M
Agustin/M
Ahab/M
Aharon/M
Ahmad/M
Ahmadabad/M
Ahmadinejad/M
Ahmed/M
Ahriman/M
Aida/M
Aidan/M
Aiken/M
Aila/M
Aileen/M
Ailey/M
Aime/M
Aimee/M
Ainsley/M
Ainslie/M
Ainu/M
Airedale/MS
Aires/M
Aisha/M
Ajax/M
Ajay/M
Akbar/M
Akhmatova/M
Akihito/M
Akim/M
Akita/M
Akiva/M
Akkad/M
Akron/M
Al/M
Ala/S
Alabama/M
Alabaman/MS
Alabamian/SM
Aladdin/M
Alain/M
Alameda/M
Alamo/M
Alamogordo/M
Alan/M
Alana/M
Aland/M
Alanna/M
Alanson/M
Alar/M
Alaric/M
Alasdair/M
Alaska/M
Alaskan/MS
Alastair/M
Alba/M
Albania/M
Albanian/MS
Albany/M
Albee/M
Alberio/M
Albert/M
Alberta/M
Albertan
Albertina/M
Albertine/M
Alberto/M
Albie/M
Albigensian/M
Albina/M
Albion/M
Albireo/M
Albrecht/M
Albuquerque/M
Alcatraz/M
Alcestis/M
Alcibiades/M
Alcindor/M
Alcmena/M
Alcoa/M
Alcott/M
Alcuin/M
Alcyone/M
Aldan/M
Aldebaran/M
Alden/M
Alderamin/M
Aldin/M
Aldis/M
Aldo/M
Aldon/M
Aldous/M
Aldrich/M
Aldridge/M
Aldrin/M
Aldus/M
Alec/M
Aleichem/M
Alejandra/M
Alejandro/M
Aleksandr/M
Alembert/M
Alena/M
Alene/M
Aleppo/M
Alessandra/M
Alessandro/M
Alethea/M
Aleut/MS
Aleutian/SM
Alex/M
Alexa/M
Alexander/MS
Alexandr/M
Alexandra/M
Alexandre/M
Alexandria/M
Alexandrian
Alexandrina/M
Alexandros
Alexei/M
Alexi/MS
Alexia/M
Alexina/M
Alexis/M
Alf/M
Alfie/M
Alfons/M
Alfonse/M
Alfonso/M
Alfonzo/M
Alford/M
Alfred/M
Alfreda/M
Alfredo/M
Algenib/M
Alger/M
Algeria/M
Algerian/SM
Algernon/M
Algieba/M
Algiers/M
Algol/M
Algonquian/SM
Algonquin/MS
Alhambra/M
Alhena/M
Ali/SM
Alia/M
Alice/M
Alicia/M
Alick/M
Alida/M
Alighieri/M
Alina/M
Aline/M
Alioth/M
Alisa/M
Alisha/M
Alison/M
Alissa/M
Alistair/M
Alister/M
Alix/M
Alkaid/M
Alla/M
Allah/M
Allahabad/M
Allan/M
Allard/M
Alleghenies/M
Allegheny/SM
Allegra/M
Allen/M
Allende/M
Allene/M
Allentown/M
Alleyn/M
Allhallows/M
Allie/MS
Allison/M
Allister/M
Allstate/M
Allyn/M
Allyson/M
Alma/M
Almach/M
Almaty/M
Almeria/M
Almighty/M
Almira/M
Almohad/M
Almoravid/M
Alnilam/M
Alnitak/M
Alon/M
Alonso/M
Alonzo/M
Aloysius/M
Alpert/M
Alphard/M
Alphecca/M
Alpheratz/M
Alphonse/M
Alphonso/M
Alpine/M
Alpo/M
Alps/M
Alric/M
Alsace/M
Alsatian/SM
Alsop/M
Alston/M
Alta/M
Altaba/M
Altai/M
Altaic/M
Altair/M
Altamira/M
Althea/M
Altiplano/M
Altman/M
Altoids/M
Alton/M
Altoona/M
Aludra/M
Alva/M
Alvan/M
Alvarado/M
Alvarez/M
Alvaro/M
Alvin/M
Alvina/M
Alvis/M
Alvy/M
Alwin/M
Alwyn/M
Alyce/M
Alyosha/M
Alys/M
Alyson/M
Alyss
Alyssa/M
Alzheimer/M
Am/MNR
Amabel/M
Amadeus/M
Amado/M
Amalia/M
Amalie/M
Amanda/M
Amara/M
Amarillo/M
Amaru/M
Amata/M
Amaterasu/M
Amati/M
Amazon/SM
Amazonian
Amber/M
Ambros/M
Ambrose
Ambrosio/M
Ambrosius/M
Ame/SM
Amelia/M
Amelie/M
Amen/M
Amenhotep/M
Amerasian/M
America/SM
American/MS
Americana/M
Americanism/MS
Americanization/MS
Americanize/GDS
Amerigo/M
Amerind/SM
Amerindian/MS
Amery/M
Ames/M
Ameslan/M
Amgen/M
Amharic/M
Amherst/M
Ami/M
Amie/M
Amiga/M
Amil/M
Amish/M
Amman/M
Amoco/M
Amory/M
Amos/M
Amparo/M
Ampere/M
Amritsar/M
Amsterdam/M
Amtrak/M
Amundsen/M
Amur/M
Amway/M
Amy/M
Ana/M
Anabaptist/M
Anabel/M
Anacin/M
Anacreon/M
Anaheim/M
Analects/M
Analise/M
Ananias/M
Anasazi/M
Anastasia/M
Anatol/M
Anatole/M
Anatolia/M
Anatolian/M
Anaxagoras/M
Anchorage/M
Andalusia/M
Andalusian/M
Andaman/M
Andean/M
Anders/N
Andersen/M
Anderson/M
Andes/M
Andi/M
Andie/M
Andorra/M
Andorran/SM
Andra/MS
Andre/MS
Andrea/SM
Andree/M
Andrei/M
Andrej/M
Andres/M
Andretti/M
Andrew/SM
Andrews/M
Andrey/M
Andria/M
Andrianampoinimerina/M
Android/M
Andromache/M
Andromeda/M
Andropov/M
Andros
Andrus/M
Andy/M
Anet/M
Angara/M
Ange/M
Angel/M
Angela/M
Angele/SM
Angeles/M
Angeli/M
Angelia/M
Angelica/M
Angelico/M
Angelika/M
Angelina/M
Angeline/M
Angelique/M
Angelita/M
Angelo/M
Angelou/M
Angevin/M
Angie/M
Angkor/M
Angle/MS
Angleton/M
Anglia/M
Anglican/SM
Anglicanism/MS
Anglicism/MS
Anglicization
Anglicize
Anglo/M
Anglophile/M
Anglophobe
Angola/M
Angolan/MS
Angora/SM
Angstrom/M
Anguilla/M
Angus/M
Anhui/M
Ania/M
Aniakchak/M
Anibal/M
Anita/M
Ankara/M
Ann/M
Anna/M
Annabel/M
Annabella/M
Annabelle/M
Annalise/M
Annam/M
Annapolis/M
Annapurna/M
Anne/M
Anneliese/M
Annelise/M
Annemarie/M
Annette/M
Anni/SM
Annie/M
Anniston/M
Annmarie/M
Annunciation/SM
Anny/M
Anouilh/M
Ansel/M
Ansell/M
Anselm/M
Anselmo/M
Anshan/M
Ansley/M
Anson/M
Anstice/M
Antaeus/M
Antananarivo/M
Antarctic/M
Antarctica/M
Antares/M
Anthea/M
Anthony/M
Anthropocene
Antichrist/SM
Antietam/M
Antifa/M
Antigone/M
Antigua/M
Antillean
Antilles/M
Antin/M
Antioch/M
Antipas/M
Antipodes
Antofagasta/M
Antoine/M
Antoinette/M
Anton/M
Antone/M
Antonella/M
Antoni/M
Antonia/M
Antonie/M
Antonietta/M
Antonin/M
Antonina/M
Antonino/M
Antoninus/M
Antonio/M
Antonius/M
Antony/M
Antwan/M
Antwerp/M
Anubis/M
Any's
Anya/M
Anzac/M
Apache/SM
Apalachicola/M
Apatosaurus
Apennines/M
Aphrodite/M
Apia/M
Apocalypse/M
Apocrypha/M
Apollinaire/M
Apollo/SM
Apollonian/M
Apostle/M
Appalachia/M
Appalachian/SM
Appalachians/M
Appaloosa/SM
Apple/M
Appleseed/M
Appleton/M
Appomattox/M
Apr/M
April/MS
Apuleius/M
Aquafresh/M
Aquarian
Aquarius/MS
Aquila/M
Aquinas/M
Aquino/M
Aquitaine/M
Ar/MY
Ara/M
Arab/SM
Arabella/M
Arabia/M
Arabian/MS
Arabic/M
Arabidopsis
Arabist/MS
Araby/M
Araceli/M
Arafat/M
Aragon
Araguaya/M
Aral/M
Aramaic/M
Aramco/M
Arapaho/MS
Arapahoes
Ararat/M
Araucanian/M
Arawak/M
Arawakan/M
Arbitron/M
Arcadia/M
Arcadian/M
Archambault/M
Archean/M
Archibald/M
Archie/M
Archimedes/M
Archy/M
Arctic/M
Arcturus/M
Arda/M
Ardabil
Arden/M
Ardis/M
Arduino/M
Arecibo/M
Arequipa/M
Ares/M
Aretha/M
Argentina/M
Argentine/M
Argentinean
Argentinian/MS
Argo/SM
Argonaut/MS
Argonne/M
Argos/M
Argus/M
Ari/M
Ariadne/M
Arial/M
Ariana/M
Arianism/M
Arie/SM
Ariel/M
Arielle/M
Aries/MS
Arin/M
Ariosto/M
Aristarchus/M
Aristides/M
Aristophanes/M
Aristotelian/M
Aristotle/M
Arius/M
Ariz
Arizona/M
Arizonan/SM
Arizonian/MS
Arjuna/M
Ark/M
Arkansan/MS
Arkansas/M
Arkhangelsk/M
Arkwright/M
Arlen/M
Arlene/M
Arlette/M
Arley/M
Arlie/M
Arlin/M
Arline/M
Arlington/M
Arly/M
Armageddon/SM
Armagnac/M
Arman/M
Armand/M
Armando/M
Armani/M
Armenia/M
Armenian/SM
Armin/M
Arminius/M
Armonk/M
Armour/M
Armstrong/M
Arnaldo/M
Arne
Arneb/M
Arnhem/M
Arni/M
Arnie/M
Arno/M
Arnold/M
Arnoldo/M
Arnulfo/M
Aron/M
Arrhenius/M
Arron/M
Art/M
Artaxerxes/M
Arte/M
Artemas
Artemis/M
Artemus/M
Arthur/M
Arthurian/M
Artie/M
Artur/M
Arturo/M
Artus/M
Arty's
Aruba/M
Arv/M
Arvin/M
Aryan/MS
As/M
Asa/M
Asama/M
Ascella/M
Ascension/M
Ase/M
Asgard/M
Ashanti/M
Ashby/M
Ashcroft/M
Ashe/RM
Asheville/M
Ashgabat
Ashikaga/M
Ashkenazim/M
Ashkhabad/M
Ashlee/M
Ashleigh/M
Ashley/M
Ashmolean/M
Ashton
Ashurbanipal/M
Asia/M
Asiago
Asian/MS
Asiatic/SM
Asimov/M
Asmara/M
Asoka/M
Aspell/M
Aspen/M
Asperger/M
Aspidiske/M
Asquith/M
Assad/M
Assam/M
Assamese/M
Assembly
Assisi/M
Assyria/M
Assyriaca/M
Assyrian/SM
Astaire/M
Astana/M
Astarte/M
Aston/M
Astor/M
Astoria/M
Astra/M
Astrakhan/M
Astrid/M
AstroTurf/M
Asturias/M
Asuncion/M
Asunci�n/M
Aswan/M
At/SM
Atacama/M
Atahualpa/M
Atalanta/M
Atari/M
Atascadero/M
Ataturk/M
Atat�rk/M
Athabasca/M
Athabaska
Athabaskan/SM
Athanasius
Athena/M
Athene/M
Athenian/SM
Athens/M
Athlon/M
Atkins/M
Atkinson/M
Atlanta/M
Atlantes
Atlantic/M
Atlantis/M
Atlas/MS
Atman/M
Atonement
Atreus/M
Atria/M
Atropos/M
Attic/M
Attica/M
Attila/M
Attlee/M
Attn
Attucks/M
Atwood/M
Au/M
Aube
Aubert/M
Aubrey/M
Aubry/M
Auburn/M
Auckland/M
Auden/M
Audi/M
Audie/M
Audion/M
Audra/M
Audre/M
Audrey/M
Audubon/M
Aug/M
Augean/M
Augie/M
Augsburg/M
August/MS
Augusta/M
Augustan/M
Auguste/M
Augustin/M
Augustine/M
Augustinian/MS
Augusto/M
Augustus/M
Aurangzeb/M
Aurea/M
Aurel/M
Aurelia/M
Aurelie/M
Aurelio/M
Aurelius/M
Aureomycin/M
Auriga/M
Aurora/M
Aurore/M
Auschwitz/M
Aussie/MS
Austen/M
Austerlitz/M
Austin/MS
Australasia/M
Australasian
Australia/M
Australian/SM
Australoid/M
Australopithecus/M
Austria/M
Austrian/SM
Austronesian/M
Autumn/M
Av/M
Ava/M
Avalon/M
Ave/M
Aveline/M
Aventine/M
Averell/M
Averil/M
Averill/M
Avernus/M
Averroes/M
Avery/M
Avesta/M
Avicenna/M
Avigdor/M
Avignon/M
Avila/M
Avior/M
Avis/M
Aviva/M
Avogadro/M
Avon/M
Avondale/M
Avram/M
Avril/M
Axe/M
Axel
Axis
Axum/M
Ayala/M
Ayers/M
Aylmer/M
Aymara/M
Aymer/M
Ayn/M
Ayrshire/M
Ayurveda/M
Ayyubid/M
Azana/M
Azania/M
Azazel/M
Azerbaijan/M
Azerbaijani/MS
Azores/M
Azov/M
Aztec/SM
Aztecan/M
Aztlan/M
B/MNRTG
BA/M
BASIC/SM
BB/M
BBB/M
BBC/M
BBQ
BBS
BBSes
BC/M
BFF
BIA
BIOS
BITNET
BLT/SM
BM/M
BMW/M
BO
BP/M
BPOE
BR
BS/M
BSA
BSD/SM
BTU
BTW
BYOB
Ba/M
Baal/SM
Baath/M
Baathist/M
Bab/SM
Babb/M
Babbage/M
Babbie/M
Babbitt/M
Babel/MS
Babette/M
Babylon/MS
Babylonia/M
Babylonian/SM
Bacall/M
Bacardi/M
Bacchanalia/M
Bacchic
Bacchus/M
Bach/M
Backus/M
Bacon/M
Bactria/M
Baden/M
Badlands/M
Baedeker/MS
Baez/M
Baffin/M
Baggies/M
Baghdad/M
Baguio/M
Baha'i/M
Baha'ullah/M
Bahama/SM
Bahamanian
Bahamas/M
Bahamian/MS
Bahasa/M
Bahia/M
Bahrain/M
Baidu/M
Baikal/M
Bailey/M
Bailie/M
Baillie/M
Baily/M
Baird/M
Bakelite/M
Baker/M
Bakersfield/M
Baku/M
Bakunin/M
Balanchine/M
Balaton/M
Balboa/M
Bald's
Balder/M
Baldwin/SM
Balearic/M
Balfour/M
Bali/M
Balinese/M
Balkan/MS
Balkans/M
Balkhash/M
Ball/M
Ballard/M
Balthazar/M
Baltic/M
Baltimore/M
Baluchistan/M
Balzac/M
Bamako/M
Bambi/M
Banach/M
Bancorp
Bancroft/M
Bandung/M
Bangalore/M
Bangkok/M
Bangladesh/M
Bangladeshi/SM
Bangor/M
Bangui/M
Banjarmasin/M
Banjul/M
Banks/M
Banneker/M
Bannister/M
Banting/M
Bantu/MS
Baotou/M
Baptist/SM
Baptiste/M
Barabbas/M
Barack/M
Barbadian/SM
Barbados/M
Barbara/M
Barbarella/M
Barbarossa/M
Barbary/M
Barbe/MR
Barbee/M
Barber/M
Barbette/M
Barbey/M
Barbie/M
Barbour/M
Barbra/M
Barbuda/M
Barcelona/M
Barceloneta/M
Barclay/SM
Barclays/M
Barde/M
Bardeen/M
Barents/M
Bari/M
Barker/M
Barkley/M
Barlow/M
Barnabas/M
Barnaby/M
Barnard/M
Barnaul/M
Barnes/M
Barnett/M
Barney/M
Barnum/M
Baroda/M
Barquisimeto/M
Barr/M
Barranquilla/M
Barrera/M
Barret/M
Barrett/M
Barri/MS
Barrie/M
Barron/M
Barry/M
Barrymore/M
Bart/M
Bartel/M
Barth/MS
Barthel/M
Bartholdi/M
Bartholomew/M
Bartlet/M
Bartlett/M
Bartok/M
Bartolomeo/M
Barton/M
Bartram/M
Barty/M
Bart�k/M
Baruch/M
Bary/M
Baryshnikov/M
Basel/M
Basho/M
Basia/M
Basic
Basie/M
Basil/M
Basile/M
Basilio/M
Basilius/M
Basque/MS
Basra/M
Bass/M
Basseterre/M
Bastian/M
Bastien/M
Bastille/M
Basutoland/M
Bataan/M
Bates/M
Bathsheba/M
Batista/M
Batman/M
Battle/M
Batu/M
Baudelaire/M
Baudoin/M
Baudouin/M
Baudrillard/M
Bauer/M
Bauhaus/M
Baum/M
Bavaria/M
Bavarian/M
Bax
Baxter/M
Bayamon
Bayard
Bayer/M
Bayes/M
Bayesian/M
Bayeux/M
Baylor/M
Bayonne/M
Bayreuth/M
Baywatch/M
Be/MH
Bea/M
Beach/M
Beadle/M
Beale/M
Bean/M
Beard/M
Beardmore/M
Beardsley/M
Bearnaise/M
Beasley/M
Beatlemania/M
Beatles/M
Beatrice/M
Beatrix/M
Beatriz/M
Beatty/M
Beau/M
Beaufort/M
Beaujolais/M
Beaumarchais/M
Beaumont/M
Beauregard/M
Beauvoir/M
Bebe/M
Becca/M
Bechtel/M
Beck/MR
Becka/M
Becker/M
Becket/M
Beckett/M
Beckham/M
Beckie/M
Beckley/M
Beckman
Becky/M
Becquerel/M
Bede/M
Bedouin/SM
Beebe/M
Beecher/M
Beefaroni/M
Beelzebub/M
Beerbohm/M
Beethoven/M
Beeton/M
Begin/M
Behan/M
Behring/M
Beiderbecke/M
Beijing/M
Beirut/M
Bekesy/M
Bel/M
Bela/M
Belarus/M
Belarusian
Belau/M
Belem/M
Belfast/M
Belg
Belgian/SM
Belgium/M
Belgrade/M
Belinda/M
Belize/M
Belkin/M
Bell/M
Bella/M
Bellamy/M
Bellatrix/M
Belleek/M
Bellevue/M
Bellingham/M
Bellini/M
Bellow/M
Belmont/M
Belmopan/M
Beloit/M
Belorussian/MS
Belshazzar/M
Beltane/M
Beltran/M
Belushi/M
Belva/M
Ben/M
Benacerraf/M
Benares/M
Benchley/M
Bend/MR
Bender/M
Bendictus
Bendix/M
Benedetta/M
Benedetto/M
Benedick/M
Benedict/M
Benedicta/M
Benedictine/MS
Benedicto/M
Benedikt/M
Benelux/M
Benet/M
Benetton/M
Bengal/SM
Bengali/M
Benghazi/M
Bengt/M
Benin/M
Beninese/M
Benita/M
Benito/M
Benjamin/M
Benji/M
Benjie/M
Benjy/M
Benn/M
Bennett/M
Bennie/M
Benny/M
Benoit/M
Benson/M
Bentham/M
Bentley/M
Benton/M
Benz/M
Benzedrine/M
Beowulf/M
Ber/MG
Berber/SM
Berenice/M
Beretta/M
Berg/MNR
Bergen/M
Berger/M
Bergerac/M
Bergman/M
Bergson/M
Beria/M
Bering/M
Berk/M
Berke/M
Berkeley/M
Berkley/M
Berkshire/SM
Berkshires/M
Berle/M
Berlin/SZMR
Berliner/M
Berlioz/M
Berlitz/M
Bermuda/SM
Bermudan/SM
Bermudian/SM
Bern/M
Bernadette/M
Bernadine/M
Bernanke/M
Bernard/M
Bernardine
Bernardo/M
Bernays/M
Bernbach/M
Bernese
Bernhard/M
Bernhardt/M
Berni/M
Bernice/M
Bernie/M
Bernini/M
Bernoulli/M
Bernstein/M
Berra/M
Berri/M
Berry/M
Bert/M
Berta/M
Bertelsmann/M
Bertha/M
Berthe/M
Berti/M
Bertie/M
Bertillon/M
Berton/M
Bertram/M
Bertrand/M
Berwick/M
Beryl/M
Berzelius/M
Bess/M
Bessel/M
Bessemer/M
Bessie/M
Bessy/M
Best/M
Betelgeuse/M
Beth/M
Bethany/M
Bethe/M
Bethesda/M
Bethlehem/M
Bethune/M
Betsey/M
Betsy/M
Betta/M
Bette/M
Betti
Bettie/M
Bettina/M
Betty/M
Bettye/M
Beulah/M
Bevan
Beveridge
Beverley/M
Beverly/M
Bevin
Bevvy's
Beyer/M
Bharat/M
Bhopal/M
Bhutan/M
Bhutanese/M
Bhutto/M
Bi/M
Bialystok/M
Bianca/M
Bib
BibSonomy/M
BibTeX/M
Bibby/M
Bibi/M
Bible/MS
Biblical/M
Bic/M
Biddle/M
Biden/M
Bierce/M
BigQuery/M
Bigfoot/M
Biggles/M
Biko/M
Bil/MY
Bilbao/M
Bilbo/M
Bili/M
Bill/MJ
Billie/M
Billings/M
Billy/M
Bimini/M
Bing/M
Binghamton/M
Bink/M
Binky/M
Binnie/M
Biogen/M
Bioko/M
Bird/M
Birdseye/M
Birgit/M
Birgitta/M
Birk/M
Birkenstock/M
Birmingham/M
Biro/M
Biron/M
Biscay/M
Biscayne/M
Bishkek/M
Bishop/M
Bismarck/M
Bismark/M
Bisquick/M
Bissau/M
BitTorrent/M
BizRate/M
Bizet/M
Bjerknes/M
Bjork/M
Bjorn/M
Bk/M
Black/MS
BlackBerry/M
Blackbeard/M
Blackburn/M
Blackfeet/M
Blackfoot/M
Blackpool/M
Blacksburg/M
Blackshirt/M
Blackstone/M
Blackwell/M
Blaine/M
Blair/M
Blake/M
Blakeley/M
Blanca/M
Blanch's
Blanchard/M
Blanche/M
Blane/M
Blankenship/M
Blantyre/M
Blatz/M
Blavatsky/M
Blenheim/M
Blevins/M
Bligh/M
BlinkList/M
Blithe's
Bloch/M
Blockbuster/M
Bloemfontein/M
Blondel/M
Blondie/M
Bloom/MR
Bloomberg/M
Bloomer/M
Bloomfield/M
Bloomingdale/M
Bloomington/M
Bloomsburg/M
Bloomsbury/M
Blu
Blucher/M
Bluebeard/M
Bluetooth/M
Blvd
Blythe/M
Bo/MRZ
Boadicea
Boas/M
Bob/M
Bobbi/M
Bobbie/M
Bobbitt/M
Bobby/M
Boccaccio/M
Bodhidharma/M
Bodhisattva/M
Bodleian
Boeing/M
Boeotia/M
Boeotian/M
Boer/M
Boethius/M
Bogart/M
Bogota/M
Bogot�/M
Bohemia/M
Bohemian/SM
Bohr/M
Boise/M
Bojangles/M
Boleyn/M
Bolivar/M
Bolivia/M
Bolivian/MS
Bollywood/M
Bologna/M
Bolshevik/SM
Bolsheviki
Bolshevism/M
Bolshevist/M
Bolshoi/M
Bolton/M
Boltzmann/M
Bombay/M
Bonaparte/M
Bonaventure/M
Bond/M
Bondy/M
Bonhoeffer/M
Boniface/M
Bonita/M
Bonn/MR
Bonner/M
Bonneville/M
Bonnie/M
Bono/M
Booker/M
Boole/M
Boolean/M
Boone/M
Bootes/M
Booth/M
Boothe/M
Bord/MN
Bordeaux/M
Borden/M
Bordon/M
Boreas/M
Borg/SM
Borges/M
Borgia/M
Borglum/M
Boris/M
Bork/M
Borlaug/M
Born/M
Borneo/M
Borobudur/M
Borodin/M
Boru/M
Bosch/M
Bose/M
Bosnia/M
Bosnian
Bosporus/M
Boston/MS
Bostonian/M
Boswell/M
Botha
Botox
Botswana/M
Botticelli/M
Boulder/M
Boulez/M
Bourbaki/M
Bourbon/SM
Bourke/M
Bournemouth/M
Bovary/M
Bowditch/M
Bowell/M
Bowen/M
Bowers/M
Bowery/M
Bowie/M
Bowman/M
Boyce/M
Boyd/M
Boyer/M
Boyle/M
Bo�tes/M
Br/MNT
Brad/MNY
Bradbury/M
Braddock/M
Braden/M
Bradenton/M
Bradford/M
Bradley/M
Bradly/M
Bradshaw/M
Bradstreet/M
Brady/M
Bragg/M
Brahe/M
Brahma/MS
Brahmagupta/M
Brahman/MS
Brahmani
Brahmanism/SM
Brahmaputra/M
Brahms/M
Braille/MS
Brain/M
Bram/M
Brampton/M
Bran/M
Branch/M
Brande/MR
Brandeis/M
Branden/M
Brandenburg/M
Brander/M
Brandi/M
Brandie/M
Brando/M
Brandon/M
Brandt/M
Brandy/M
Brannon/M
Brant/M
Brantley/M
Braque/M
Brasilia/M
Bratislava/M
Brattain/M
Bray/M
Brazil/M
Brazilian/MS
Brazos/M
Brazzaville/M
Breakspear/M
Breathalyzer
Brecht/M
Breckenridge/M
Bree/M
Bremen/M
Bremerton/M
Bren/M
Brenda/M
Brendan/M
Brenden/M
Brendon/M
Brenna/M
Brennan/M
Brenner/M
Brent/M
Brenton/M
Brest/M
Bret/M
Breton/M
Brett/M
Brewer/M
Brewster/M
Brexit
Brezhnev/M
Brian/M
Briana/M
Brianna/M
Brianne/M
Briant/M
Brice/M
Bridalveil/M
Bridgeport/M
Bridger/M
Bridges/M
Bridget/M
Bridgetown/M
Bridgett/M
Bridgette/M
Bridgman/M
Bridie/M
Brie/SM
Brien/M
Brigadoon/M
Brigg/MS
Briggs/M
Brigham/M
Bright/M
Brighton/M
Brigid/M
Brigida/M
Brigit/M
Brigitta/M
Brigitte/M
Brillo/M
Brillouin
Brinkley/M
Briny's
Brion/M
Brisbane/M
Bristol/M
Brit/SM
Brita/M
Britain/M
Britannia/M
Britannic/M
Britannica/M
Briticism/SM
British/MRZ
Britisher/M
Britney/M
Briton/MS
Britt/MN
Britta/M
Brittan/M
Brittany/SM
Britten/M
Brittney/M
Brno/M
Broadway/SM
Brobdingnag/M
Brobdingnagian/M
Brock/M
Brod/M
Broderick/M
Brodie/M
Brody/M
Brokaw/M
Bron/M
Bronson/M
Bronte/M
Brontosaurus
Bronx/M
Brooke/MS
Brooklyn/M
Brooks/M
Bros
Brose/M
Brown/MG
Browne/M
Brownian/M
Brownie/S
Browning/M
Brownshirt/M
Brownsville/M
Brubeck/M
Bruce/M
Bruckner/M
Bruegel
Brummel/M
Brunei/M
Bruneian/MS
Brunelleschi/M
Brunhilde/M
Bruno/M
Brunswick/M
Brussels/M
Brut/M
Brutus/M
Bryan/M
Bryant/M
Bryce/M
Bryn/M
Brynn/MR
Brynner/M
Bryon/M
Brzezinski/M
Btu/M
Buber/M
Buchanan/M
Bucharest/M
Buchenwald/M
Buchwald/M
Buck/M
Buckingham/M
Buckley/M
Buckner/M
Bucky/M
Bud/M
Budapest/M
Budd/M
Buddha/SM
Buddhism/SM
Buddhist/SM
Buddy/M
Budweiser/M
Buenos
Buffalo/M
Buffy/M
Buford/M
Bugatti/M
Bugzilla/M
Buick/M
Bujumbura/M
Bukhara/M
Bukharin/M
Bulawayo/M
Bulfinch/M
Bulganin/M
Bulgar/M
Bulgari/M
Bulgaria/M
Bulgarian/SM
Bullock/M
Bullwinkle/M
Bultmann/M
Bumppo/M
Bunche/M
Bundesbank/M
Bundestag/M
Bunin/M
Bunker/M
Bunsen/M
Bunuel/M
Bunyan/M
Burbank/M
Burberry/M
Burch/M
Burger/M
Burgess/M
Burgoyne/M
Burgundian/M
Burgundy/SM
Burk/SM
Burke/M
Burkina/M
Burks/M
Burl/M
Burlington/M
Burma/M
Burmese/M
Burnaby/M
Burnard/M
Burnett/M
Burns/M
Burnside/M
Burr/M
Burris/M
Burroughs/M
Bursa/M
Burt/M
Burton/M
Burundi/M
Burundian/MS
Busch/M
Bush/M
Bushido/M
Bushnell/M
Butler/M
Butterfingers/M
Buxtehude/M
Bu�uel/M
Byblos/M
Byers/M
Byram/M
Byrd/M
Byrom/M
Byron/M
Byronic/M
Byzantine/MS
Byzantium/M
C/SMD
CA
CAD/M
CAI
CAM
CAP
CAPTCHA
CARE
CATV
CB
CBC/M
CBS/M
CCTV
CCU
CD/SM
CDC
CDT
CEO/SM
CF
CFC/SM
CFO
CGI
CIA/M
CID
CNN/M
CNS/M
CO/M
COBOL/SM
COD
COL
COLA
COVID
CPA/M
CPI/M
CPO
CPR/M
CPU/M
CRT/SM
CSS/M
CST/M
CT/M
CV
CVS/M
CZ
Ca/M
Cabernet/M
Cabot/M
Cabral/M
Cabrera/M
Cabrini/M
Cad/M
Cadette
Cadillac/M
Cadiz/M
Caedmon/M
Caerphilly/M
Caesar/SM
Cage/M
Cagney/M
Cahokia/M
Caiaphas/M
Caicos/M
Cain/SM
Cairo/M
Caitlin/M
Cajun/MS
Cal/MY
Calais/M
Calcutta/M
Calder/M
Calderon/M
Caldwell/M
Cale/M
Caleb/M
Caledonia/M
Calexico/M
Calgary/M
Calhoun/M
Cali/M
Caliban/M
Calif
California/M
Californian/SM
Caligula/M
Callaghan/M
Callahan/M
Callao/M
Callas/M
Calley/M
Calli/M
Callie/M
Calliope/M
Callisto/M
Cally/M
Caloocan/M
Calvary/M
Calvert/M
Calvin/M
Calvinism/MS
Calvinist/MS
Calvinistic
Camacho/M
Camarillo/M
Cambodia/M
Cambodian/SM
Cambrian/SM
Cambridge/M
Cambridgeshire/M
Camden/M
Camel/M
Camelopardalis/M
Camelot/MS
Camembert/MS
Cameron/M
Cameroon/SM
Cameroonian/MS
Cami/M
Camila/M
Camilla/M
Camille/M
Cammie/M
Cammy/M
Camoens/M
Campanella/M
Campbell/M
Campinas/M
Campos/M
Camry/M
Camus/M
Can/M
Canaan/M
Canaanite/MS
Canad
Canada/M
Canadian/SM
Canadianism
Canaletto/M
Canaries/M
Canaveral/M
Canberra/M
Cancer/SM
Cancun/M
Candace/M
Candi/M
Candice/M
Candida/M
Candide/M
Candra/M
Candy/M
Cannes/M
Cannon/M
Canon/M
Canopus/M
Cantabrigian/M
Canterbury/M
Canton/M
Cantonese/M
Cantor/M
Cantrell/M
Cantu/M
Canute/M
Capablanca/M
Capek/M
Capella/M
Capet/M
Capetian/M
Capetown/M
Caph/M
Capistrano/M
Capitol/SM
Capitoline/M
Capone/M
Capote/M
Capra/M
Capri/M
Capricorn/MS
Capt
Capuchin/M
Capulet/M
Cara/M
Caracalla/M
Caracas/M
Caravaggio/M
Carboloy/M
Carbondale/M
Carboniferous/M
Carborundum/M
Cardenas/M
Cardiff/M
Cardin/M
Cardozo/M
CareerBuilder/M
Caren/M
Carey/M
Cari/M
Caria
Carib/MS
Caribbean/MS
Carin/M
Carina/M
Carine/M
Carissa/M
Carita/M
Carl/GMN
Carla/M
Carlen/M
Carlene/M
Carleton/M
Carley/M
Carlie/M
Carlin/M
Carling/M
Carlisle/M
Carlo/MS
Carlos/M
Carlota
Carlotta/M
Carlsbad/M
Carlson/M
Carlton/M
Carly/M
Carlyle/M
Carmel
Carmela/M
Carmelita/M
Carmella/M
Carmelo/M
Carmen/M
Carmichael/M
Carmina/M
Carmine/M
Carnap/M
Carnation/M
Carnegie/M
Carney/M
Carnot/M
Caro/M
Carol/M
Carola/M
Carolan/M
Carole/M
Carolina/M
Caroline/M
Carolingian/M
Carolinian/M
Carolus/M
Carolyn/M
Carolyne/M
Caron/M
Carpathian/SM
Carpathians/M
Carpenter/M
Carr/M
Carranza/M
Carrie/RM
Carrier/M
Carrillo/M
Carrol/M
Carroll/M
Carson/M
Carter/M
Cartersville/M
Cartesian/M
Carthage/M
Carthaginian/MS
Cartier/M
Cartwright/M
Caruso/M
Carver/M
Cary/M
Caryl/M
Caryn/M
Casablanca/M
Casals/M
Casandra/M
Casanova/SM
Casar/M
Cascades/M
Case/M
Casey/M
Cash/M
Casi/M
Casio/M
Caspar/M
Casper/M
Caspian/M
Cass/M
Cassandra/SM
Cassatt/M
Cassi/M
Cassidy/M
Cassie/M
Cassini/M
Cassiopeia/M
Cassius/M
Cassy/M
Castaneda/M
Castile/M
Castilian
Castillo/M
Castlereagh/M
Castor/M
Castries/M
Castro/M
Catalan/SM
Catalina/M
Catalonia/M
Catarina/M
Catawba/M
Cate/M
Caterina/M
Caterpillar/M
Catharina/M
Catharine/M
Cathay/M
Cather/M
Catherine/M
Cathie/M
Cathleen/M
Catholic/MS
Catholicism/MS
Cathryn/M
Cathy/M
Cati/M
Catie/M
Catiline/M
Catlin/M
Cato/M
Catrina/M
Catriona/M
Catskill/SM
Catskills/M
Catt/M
Catullus/M
Caucasian/MS
Caucasoid
Caucasus/M
Cauchy/M
Cavendish/M
Cavour/M
Caxton/M
Caye/M
Cayenne/M
Cayman/M
Cayuga/SM
Cayuse
Caz/M
Cb
Cd/M
Ce/M
Ceausescu/M
Cebu/M
Cebuano/M
Cece/M
Cecelia/M
Cecil/M
Cecile/M
Cecilia/M
Cecilio/M
Cecily/M
Ced/M
Cedric/M
Ceil/M
Cele/M
Celebes/M
Celeste/M
Celestia/M
Celestina/M
Celestine/M
Celgene/M
Celia/M
Celie/M
Celina/M
Celine/M
Celle/M
Cellini/M
Celsius/M
Celt/SM
Celtic/SM
Cenozoic/M
Centaurus/M
Centigrade
Central
Centro/M
Cepheid/M
Cepheus/M
Cerberus/M
Cerenkov/M
Ceres/M
Cerf/M
Cervantes/M
Cesar/M
Cesare/M
Cesarean/M
Cessna/M
Cetus/M
Ceylon/M
Ceylonese
Cezanne/M
Cf/M
Ch'in/M
Ch/NRS
Chablis/M
Chad/M
Chadian/MS
Chadwick/M
Chagall/M
Chaim/M
Chaitanya/M
Chaitin/M
Chaldea
Chaldean/M
Challenger/M
Chalmers
Chamberlain/M
Chambers/M
Chambersburg/M
Champaign/M
Champlain/M
Champollion/M
Chan/M
Chance/M
Chancellorsville/M
Chancey/M
Chanda/M
Chandigarh/M
Chandler/M
Chandon/M
Chandra/M
Chandragupta/M
Chandrasekhar/M
Chane/M
Chanel/M
Chaney/M
Chang/M
Changchun/M
Changsha/M
Channa/M
Chantal/M
Chantilly/M
Chaplin/M
Chaplinesque
Chapman/M
Chappaquiddick/M
Chapultepec/M
Chara
Charbray/M
Chardonnay/M
Charis
Charisse/M
Charity/M
Charlemagne/M
Charlene/M
Charles/M
Charleston/MS
Charley/M
Charlie/M
Charlot/M
Charlotta/M
Charlotte/M
Charlottesville/M
Charlottetown/M
Charlton
Charmaine/M
Charmian/M
Charmin/M
Charolais/M
Charon/M
Chartism/M
Chartres/M
Charybdis/M
Chas
Chase/M
Chasity/M
ChatZilla/M
Chateaubriand/M
Chatham/M
Chattahoochee/M
Chattanooga/M
Chatterley/M
Chatterton/M
Chaucer/M
Chauncey/M
Chautauqua/M
Chavez/M
Chayefsky/M
Che/M
Chechen/M
Chechnya/M
Cheddar/M
Cheer/M
Cheerios/M
Cheetos/M
Cheever/M
Chekhov/M
Chekhovian
Chelsea/M
Chelyabinsk/M
Chen/M
Cheney/M
Chengdu/M
Chennai/M
Cheops/M
Chere/M
Cheri/M
Cherie/M
Cherise/M
Cherish's
Chernenko/M
Chernobyl/M
Chernomyrdin/M
Cherokee/MS
Cherry/M
Cheryl/M
Chesapeake/M
Cheshire/M
Chester/M
Chesterfield/M
Chesterton/M
Chet/M
Chev/M
Chevalier/M
Cheviot/M
Chevrolet/M
Chevron/M
Chevy/M
Cheyenne/SM
Chi/M
Chianti/MS
Chiba/M
Chibcha/M
Chicago/M
Chicagoan/M
Chicana/M
Chicano/M
Chickasaw/MS
Chiclets/M
Chico/M
Chihuahua/MS
Chile/M
Chilean/MS
Chilton/M
Chimborazo/M
Chimera/MS
Chimu/M
Chin/M
China/M
Chinatown/M
Chinese/M
Chinook/MS
Chipewyan/M
Chippendale/M
Chippewa/SM
Chiquita/M
Chirico/M
Chisholm/M
Chisinau/M
Chittagong/M
Chivas/M
Chloe/M
Chloris/M
Choctaw/SM
Choi/M
Chomsky/M
Chongqing/M
Chopin/M
Chopra/M
Chou/M
Chretien/M
Chris/M
Chrissie/M
Chrissy/M
Christ/MS
Christa/M
Christabel/M
Christchurch/M
Christel/M
Christen's
Christendom/MS
Christensen/M
Christi/M
Christian/SM
Christiana/M
Christiane/M
Christianity/SM
Christianize/DG
Christie/M
Christina/M
Christine/M
Christlike
Christmas/MS
Christmastide/MS
Christmastime/MS
Christoper/M
Christoph/MR
Christophe
Christopher/M
Christos/M
Chromebook/MS
Chronicles
Chrysler/M
Chrysostom/M
Chrystal/M
Chuck/M
Chukchi/M
Chumash/M
Chung/M
Church/M
Churchill/M
Churriguera/M
Chuvash/M
Ci/M
Cicely/M
Cicero/M
Cid/M
Ciel/M
Cimabue/M
Cincinnati/M
Cinderella/MS
Cindi/M
Cindy/M
CinemaScope/M
Cinerama/M
Cingular/M
Cipro/M
Circe/M
Cirillo/M
Ciro/M
Cisco/M
Cissy/M
Citibank/M
Citigroup/M
Citroen/M
Cl/MV
Claiborne/M
Clair/M
Claire/M
Clairol/M
Clancy/M
Clapeyron/M
Clapton/M
Clara/M
Clare/M
Clarence/M
Clarendon/M
Clari/M
Claribel/M
Clarice/M
Clarinda/M
Clarissa/M
Clarisse/M
Clarita/M
Clark/M
Clarke/M
Clarkson/M
Clarksville/M
Clary/M
Claude/M
Claudette/M
Claudia/M
Claudian/M
Claudine/M
Claudio/M
Claudius/M
Claus/M
Clausewitz/M
Clausius/M
Clay/M
Clayborne/M
Clayton/M
Clea/M
Clearasil/M
Clem/XM
Clemence/M
Clemenceau/M
Clemens/M
Clement/MS
Clemente/M
Clementina/M
Clementine/M
Clements/M
Clemmie/M
Clemons/M
Clemson/M
Cleo/M
Cleon
Cleopatra/M
Clerc/M
Cletus/M
Cleve/M
Cleveland/M
Cliburn/M
Cliff/M
Clifford/M
Clifton/M
Cline/M
Clint/M
Clinton/M
Clio/M
Clive/M
Clo/M
Clojure/M
Clorets/M
Clorox/M
Closure/M
Clotho/M
Clotilda/M
Clouseau/M
Clovis/M
Clyde/M
Clydesdale/M
Clytemnestra/M
Cm/M
Cmdr
Co/M
Cobain/M
Cobb/M
Cochabamba/M
Cochin/M
Cochise/M
Cochran/M
Cockney/M
Cocteau/M
Cod
Cody/M
Coffey/M
Cognac/M
Cohan/M
Cohen/M
Coimbatore/M
Cointreau/M
Coke/SM
Col/M
Colbert/M
Colby/M
ColdFusion/M
Cole/M
Coleen/M
Coleman/M
Coleridge/M
Colet
Coletta/M
Colette/M
Colfax/M
Colgate/M
Colin/M
Colleen/M
Collen/M
Collette/M
Collier/M
Collin/SM
Colline/M
Collins/M
Colman/M
Colo
Cologne/M
Colombia/M
Colombian/MS
Colombo/M
Colon/M
Coloradan/SM
Colorado/M
Coloradoan
Colosseum/M
Colt/M
Coltrane/M
Columbia/M
Columbine/M
Columbus/M
Com
Comanche/MS
Combs/M
Comcast/M
Comdr
Comintern/M
Commandment
Commons/M
Commonwealth
Communion/SM
Communism
Communist/SM
Como/M
Comoran
Comoros/M
Compaq/M
Compton/M
CompuServe/M
Computerworld/M
Comte/M
Conakry/M
Conan/M
Conant/M
Concepcion/M
Concepci�n/M
Concetta/M
Conchita/M
Concord/SM
Concorde/M
Concordia/M
Condillac/M
Condorcet/M
Conestoga/M
Confederacy/M
Confederate/MS
Confucian/SM
Confucianism/MS
Confucius/M
Cong/M
Congo/M
Congolese/M
Congregational
Congregationalist/MS
Congress/MS
Congressional/Y
Congreve/M
Conley/M
Conn/MR
Connecticut/M
Connellsville/M
Connemara/M
Conner/M
Connery/M
Connie/M
Connolly/M
Connor/SM
Connors/M
Conny/M
Conrad/M
Conrado/M
Conrail/M
Conroe/M
Conroy/M
Conservative
Constable/M
Constance/M
Constancia/M
Constanta/M
Constantia
Constantin/M
Constantine/M
Constantino/M
Constantinople/M
Constitution
Consuela/M
Consuelo/M
Continent/M
Continental/M
Contreras/M
Conway/M
Cook/M
Cooke/M
Cooley/M
Coolidge/M
Cooper/M
Cooperstown/M
Coors/M
Copacabana/M
Copeland/M
Copenhagen/M
Copernican/M
Copernicus/M
Copland/M
Copley/M
Copperfield/M
Coppertone/M
Coppola/M
Coptic/M
Cora/M
Coralie/M
Corbet/M
Corbett/M
Corbie/M
Corbin/M
Corby
Cordelia/M
Cordell/M
Cordilleras/M
Cordoba/M
Cordy/M
Coretta/M
Corey/M
Corfu/M
Cori/M
Corina/M
Corine/M
Corinna/M
Corinne/M
Corinth/M
Corinthian/MS
Corinthians/M
Coriolanus/M
Coriolis/M
Cork
Corleone/M
Corliss/M
Cormack/M
Corneille/M
Cornelia/M
Cornelius/M
Cornell/M
Corney/M
Corning/M
Cornish/MS
Cornwall/M
Cornwallis/M
Corny's
Coronado/M
Corot/M
Corp
Correggio/M
Corrie/M
Corrine/M
Corry/M
Corsica/M
Corsican/M
Cort/M
Cortes/MS
Cortland/M
Corvallis/M
Corvette/M
Corvus/M
Cory/M
Cosby/M
Cosette/M
Cosimo/M
Cosme/M
Cosmo/M
CosmosDB/M
Cossack/M
Costa/M
Costanza/M
Costco/M
Costello/M
Costner/M
Cote/M
Cotonou/M
Cotopaxi/M
Cotswold/M
Cotton/M
Coulomb/M
Coulter/M
Councillor/MS
Couperin/M
Courbet/M
Courtenay/M
Courtney/M
Cousteau/M
Coventry/SM
Covington/M
Coward/M
Cowell/M
Cowley/M
Cowper/M
Cox/M
Coy/M
Coyle/M
Cozumel/M
Cpl
Cr/MT
Crabbe/M
Craft/M
Craggy's
Craig/M
Craigslist/M
Cranach/M
Crane/M
Cranmer/M
Crater/M
Crawford/M
Cray/M
Crayola/M
Creation/M
Creator/M
Crecy/M
Cree/DSM
Creek/SM
Creighton/M
Creole/SM
Creon/M
Cressida/M
Crest/M
Cretaceous/M
Cretan/SM
Crete/M
Crichton/M
Crick/M
Crimea/M
Crimean/M
Criollo/M
Cris/M
Crisco/M
Crissy/M
Crista/M
Cristal/M
Cristian/M
Cristiano/M
Cristina/M
Cristobal/M
Croat/SM
Croatia/M
Croatian/MS
Croce/M
Crockett/M
Croesus/M
Cromwell/M
Cromwellian/M
Cronin/M
Cronkite/M
Cronus/M
Crookes/M
Crosby/M
Cross/M
Crow/SM
Crowley/M
Crucifixion/MS
Cruikshank/M
Cruise/M
Crusades's
Crusoe/M
Crux/M
Cruz/M
Cryptozoic/M
Crystal/M
Csonka/M
Ct
Ctesiphon/M
Cthulhu/M
Cu/M
Cuba/M
Cuban/SM
Cuchulain/M
Cuisinart/M
Culbertson/M
Cullen/M
Culley/M
Cully/M
Culver/M
Cumberland/M
Cumbria/M
Cummings/M
Cunard/M
Cunningham/M
Cupid/M
Curacao/M
Curcio/M
Curie/M
Curitiba/M
Curr/M
Curran/M
Currey/M
Currie/M
Currier/M
Curry/RM
Curt/M
Curtice/M
Curtis/M
Custer/M
Cuvier/M
Cuzco/M
Cy
Cybele/M
Cybil/M
Cyclades/M
Cyclopes/M
Cyclops/M
Cygnus/M
Cymbeline/M
Cyndi/M
Cynthia/M
Cynthy/M
Cyprian/M
Cypriot/MS
Cyprus/M
Cyrano/M
Cyril/M
Cyrille/M
Cyrillic/M
Cyrus/M
Czech/M
Czechia/M
Czechoslovak
Czechoslovakia/M
Czechoslovakian/SM
Czechs
Czerny/M
D'Arcy
D/M
DA/M
DAR
DAT/M
DBMS/M
DC/M
DD/M
DDS/M
DDT/S
DE
DEA
DEC/SD
DH
DHS
DI
DJ
DMCA
DMD/M
DMZ
DNA/M
DOA
DOB
DOD
DOE
DOS/M
DOT
DP/SM
DPT
DRM
DST
DTP
DUI
DVD/S
DVR/SM
DWI
Dacey/M
Dachau/M
Dacia
Dacron/SM
Dada/M
Dadaism/M
Daedalus/M
Daffy's
Dag's
Dagmar/M
Dagny/M
Daguerre/M
Dagwood/M
Dahomey/M
Daimler/M
Daisy/M
Dakar/M
Dakota/SM
Dakotan/M
Dal/M
Dalai
Dale/M
Daley/M
Dali/M
Dalia/M
Dalian/M
Dalila/M
Dall/M
Dallas/M
Dalmatia/M
Dalmatian/SM
Dalton/M
Damara/M
Damaris/M
Damascus/M
Dame/MN
Damian/M
Damiano/M
Damien/M
Damion/M
Damocles/M
Damon/M
Dan/M
Dana/M
Danae/M
Dana�/M
Danbury/M
Dane/SM
Danelaw/M
Danette/M
Dangerfield/M
Dani/M
Dania/M
Danial/M
Danica/M
Daniel/SM
Daniela/M
Daniele/M
Daniella/M
Danielle/M
Daniels/M
Danish/M
Danna/M
Danni/M
Dannie/M
Danny/M
Danone/M
Dante/M
Danton/M
Danube/M
Danubian/M
Danville/M
Daphne/M
Dar/MNH
Dara/M
Darby/M
Darcey/M
Darci/M
Darcie/M
Darcy/M
Dardanelles/M
Dare/M
Daren/M
Darfur/M
Dari/M
Daria/M
Darin/M
Dario/M
Darius/M
Darjeeling/M
Darla/M
Darlene/M
Darling/M
Darnell/M
Daron/M
Darrel/M
Darrell/M
Darren/M
Darrin/M
Darrow/M
Darryl/M
Darsie/M
Darth/M
Dartmoor/M
Dartmouth/M
Darvon/M
Darwin/M
Darwinian/M
Darwinism/SM
Darwinist
Darya/M
Daryl/M
Dasha/M
Datamation
Daugherty/M
Daumier/M
Dav/M
Davao/M
Dave/M
Davenport/M
Davey/M
David/MS
Davida/M
Davide/M
Davidson/M
Davie/M
Davies/M
Davin/M
Davina/M
Davis/M
Davy/SM
Dawes/M
Dawkins
Dawn/M
Dawson/M
Day/M
Dayan
Dayna/M
Dayton/M
Daytona/M
De/RSMN
DeGeneres/M
DeKalb/M
Deadhead/M
DealTime/M
Dean/M
Deana/M
Deandre/M
Deane/M
Deann/M
Deanna/M
Deanne/M
Death/M
Deb/SM
Debbi/M
Debbie/M
Debby/M
Debi/M
Debian/M
Debora/M
Deborah/M
Debouillet/M
Debra/M
Debs/M
Debussy/M
Dec/M
Decalogue/M
Decatur/M
Decca/M
Deccan/M
December/SM
Decker/M
Dede/M
Dedekind/M
Dee/M
Deedee/M
Deena/M
Deere/M
Defoe/M
Degas/M
Deidre/M
Deimos/M
Deirdre/M
Deity
Dejesus/M
Del/M
Dela/M
Delacroix/M
Delacruz/M
Delaney/M
Delano/M
Delaware/MS
Delawarean/SM
Delbert/M
Deleon/M
Delgado/M
Delhi/M
Delia/M
Delibes/M
Delicious/M
Delilah/M
Delilahs
Delius/M
Dell/M
Della/M
Delmar/M
Delmarva/M
Delmer/M
Delmonico/M
Delmore/M
Deloitte/M
Delores/M
Deloria/M
Deloris/M
Delphi/M
Delphic/M
Delphine/M
Delphinus/M
Delta/M
Deltona/M
Dem/G
Demavend/M
Demerol/M
Demeter/M
Demetri/M
Demetria/M
Demetrius/M
Deming/M
Democrat/SM
Democratic
Democritus/M
Demosthenes/M
Demott/M
Dempsey/M
Dena/M
Denali
Dene
Deneb/M
Denebola/M
Deng/M
Deni/SM
Denis/M
Denise/M
Denmark/M
Denney/M
Dennie/M
Dennis/M
Dennison/M
Denny/M
Denton/M
Denver/M
Deny's
Denys
Deon/M
Depp/M
Der/M
Derby/M
Derek/M
Derick/M
Derk/M
Dermot/M
Derrick/M
Derrida/M
Derry
Descartes/M
Desdemona/M
Deseret/M
Desi/M
Desiree/M
Desmond/M
Detroit/M
Deuteronomy/M
Deutschmark/SM
Dev/M
Deva/M
Devan/M
Devanagari/M
Devi/M
Devil/M
Devin/M
Devlin/M
Devon/M
Devonian/M
Dewar/M
Dewayne/M
Dewey/M
Dewitt/M
Dex/M
Dexedrine/M
Dexter/M
Dhabi
Dhaka/M
Dhaulagiri/M
Di/SM
DiCaprio/M
DiMaggio/M
Diaghilev/M
Dial/M
Dian/M
Diana/M
Diane/M
Diann/M
Dianna/M
Dianne/M
Dias
Diaspora/MS
Dick/XM
Dickens/M
Dickensian
Dickerson/M
Dickie/M
Dickinson/M
Dickson/M
Dicky/M
Dictaphone/SM
Diderot/M
Didi/M
Dido/M
Didrikson/M
Diefenbaker/M
Diego/M
Diem/M
Dierdre/M
Dieter/M
Dietrich/M
Digg/SM
Dijkstra/M
Dijon/M
Dilbert/MS
Dillard/M
Dillinger/M
Dillon/M
Dimitri/M
Dina/M
Dinah/M
Dinny/M
Dino/M
Diocletian/M
Diogenes/M
Dion/M
Dione
Dionisio/M
Dionne/M
Dionysian/M
Dionysus/M
Diophantine/M
Dior/M
Dipper/M
Dir
Dirac/M
Dirichlet/M
Dirk/M
Dis/M
Disney/M
Disneyland/M
Disraeli/M
Dita/M
DivX/M
Divine/M
Diwali/M
Dix/M
Dixie/M
Dixiecrat/M
Dixieland/SM
Dixon/M
Django/M
Djibouti/M
Dmitri/M
Dnepr
Dnepropetrovsk/M
Dnieper/M
Dniester/M
Dobbin/M
Doberman/M
Dobro/M
Doctor
Doctorow/M
Dodge/M
Dodgson/M
Dodi/M
Dodie/M
Dodoma/M
Dodson/M
Doe/M
Doha/M
Dolby/M
Dole/M
Dolf/M
Dolley/M
Dollie/M
Dolly/M
Dolores/M
Dolph/M
Dom
Domenic/M
Domenico/M
Domesday/M
Domingo/M
Dominguez/M
Domini/M
Dominic/M
Dominica/M
Dominican/MS
Dominick/M
Dominik/M
Dominion
Dominique/M
Domitian/M
Don/SM
Dona/M
Donahue/M
Donal/M
Donald/M
Donaldson/M
Donatello/M
Donetsk/M
Donizetti/M
Donn/MR
Donna/M
Donne/M
Donnell/M
Donner/M
Donnie/M
Donny/M
Donovan/M
Dooley/M
Doolittle/M
Doonesbury/M
Doppler/M
Dora/M
Dorcas/M
Dore/M
Doreen/M
Dorey/M
Dori/SM
Doria/M
Dorian/M
Doric/M
Dorie/M
Doris/M
Dorise/M
Doritos/M
Doro/M
Dorotea/M
Dorothea/M
Dorothee/M
Dorothy/M
Dorrie/M
Dorris
Dorset/M
Dorsey/M
Dorthy/M
Dortmund/M
Dosi/M
Dostoevsky/M
Dot/M
Dothan/M
Dotson/M
Dottie/M
Dotty's
Douala/M
Douay/M
Doubleday/M
Doug/M
Dougie/M
Douglas/M
Douglass/M
Douro/M
Dov/MR
Dover/M
Dow/M
Downs/M
Downy/M
Doy/M
Doyle/M
Dr
Draco/M
Draconian/M
Dracula/M
Drake/M
Dramamine/SM
Drambuie/M
Drano/M
Dravidian/M
Dre/M
Dreamweaver/M
Dreiser/M
Dresden/M
Drew/M
Dreyfus/M
Dristan/M
Drona/M
Dropbox/M
Dru/M
Drudge/M
Druid/M
Drupal/M
Drusilla/M
Dryden/M
Dschubba/M
Du
DuPont/M
Duane/M
Dubai/M
Dubcek/M
Dubhe/M
Dublin/M
Dubrovnik/M
Dubuque/M
Duchamp/M
Dudley/M
Duff/M
Duffie/M
Duffy/M
Dugald/M
Duisburg/M
Duke/M
Dulce/M
Dulcie/M
Dulcinea/M
Dulles/M
Duluth/M
Dumas/M
Dumbledore/M
Dumbo/M
Dunant/M
Dunbar/M
Duncan/M
Dundee
Dunedin/M
Dunkirk/M
Dunlap/M
Dunn/M
Dunne/M
Dunstan
Dur/R
Duracell/M
Duran/M
Durand/M
Durant/M
Durante/M
Durban/M
Durer/M
Durex/M
Durham/MS
Durkheim/M
Duroc/M
Durocher/M
Durward/M
Duse/M
Dushanbe/M
Dusseldorf/M
Dustbuster/M
Dustin/M
Dusty/M
Dutch/M
Dutchman/M
Dutchmen/M
Dutchwoman
Duvalier/M
Dvina/M
Dvorak/M
Dvor�k/M
Dwayne/M
Dwight/M
Dy/M
Dyan/M
Dyer/M
Dylan/M
Dyna/M
DynamoDB/M
Dyson/M
Dzerzhinsky/M
Dzungaria/M
D�rer/M
D�sseldorf/M
E/SMY
EC
ECG/M
ECMAScript/M
EDP/M
EDT
EEC/M
EEG/M
EEO
EEOC
EFL
EFT
EKG/M
ELF/M
EM
EMT
ENE/M
EOE
EPA/M
ER
ERA
ESE/M
ESL
ESP/M
ESPN/M
ESR
EST/M
ET
ETA
ETD
EU
EULA/S
Eadie/M
Eakins/M
Eal/M
Eamon/M
Earhart/M
Earl/M
Earle/M
Earlene/M
Earline/M
Early's
Earnest/M
Earnestine/M
Earnhardt/M
Earp/M
East/SZMR
Easter/M
Eastern/R
Eastman/M
Easton/M
Eastwood/M
Eaton/M
Eb/MN
Eba/M
Ebba/M
Eben/M
Ebeneezer/M
Ebenezer/M
Eberhard/M
Ebert/M
Ebola/M
Ebonics/M
Ebony/M
Ebro/M
Ecclesiastes/M
Ecma/M
Eco/M
Ecstasy
Ecuador/M
Ecuadoran/SM
Ecuadorean
Ecuadorian/SM
Ed/MNX
Eda/M
Edam/SM
Edd/M
Edda/M
Eddie/M
Eddington/M
Eddy/M
Ede
Eden/M
Edgar/M
Edgard/M
Edgardo/M
Edi/MH
Edie/M
Edin/M
Edinburgh/M
Edison/M
Edith/M
Editha/M
Edlin/M
Edmond/M
Edmonton/M
Edmund/M
Edna/M
Edouard/M
Edsel/M
Eduard/M
Eduardo/M
Edvard/M
Edward/SM
Edwardian/M
Edwardo/M
Edwards/M
Edwin/M
Edwina/M
Edy/M
Edythe/M
Eeyore/M
Effie/M
Efrain/M
Efrem/M
Efren/M
Egan/M
Egbert
Eggo/M
Egon/M
Egor/M
Egypt/M
Egyptian/MS
Egyptology/M
Ehrenberg/M
Ehrlich/M
Eichmann/M
Eiffel/M
Eileen/M
Einstein/MS
Einsteinian/M
Eire/M
Eisenhower/M
Eisenstein/M
Eisner/M
Ekaterina/M
El/Y
Elaina/M
Elaine/M
Elam/M
Elana/M
Elanor/M
Elasticsearch/M
Elastoplast/M
Elayne/M
Elba/M
Elbe/M
Elbert/M
Elbrus/M
Elden/M
Eldersburg/M
Eldin/M
Eldon/M
Eldredge/M
Eldridge/M
Eleanor/M
Eleanora/M
Eleanore/M
Eleazar/M
Electra/M
Elena/M
Elene/M
Eleni/M
Eleonora/M
Eleonore/M
Elfreda/M
Elfrida/M
Elgar/M
Eli/M
Elia/S
Elias/M
Elie/M
Elihu/M
Elijah/M
Elinor/M
Eliot/M
Elisa/M
Elisabet/M
Elisabeth/M
Elisabetta/M
Elise/M
Eliseo/M
Elisha/M
Elissa/M
Eliza/M
Elizabeth/M
Elizabethan/SM
Elizabethtown/M
Elke/M
Elkhart/M
Ella/M
Elle/M
Ellen/M
Ellery/M
Ellesmere/M
Elli/SM
Ellie/M
Ellington/M
Elliot/M
Elliott/M
Ellis/M
Ellison/M
Ellsworth/M
Ellwood/M
Elly/M
Ellyn/M
Elma/M
Elmer/M
Elmira/M
Elmo/M
Elmore/M
Elnath/M
Elnora/M
Elohim/M
Eloisa/M
Eloise/M
Eloy/M
Elroy/M
Elsa/M
Elsbeth/M
Else's
Elsevier/M
Elsey/M
Elsie/M
Elsinore/M
Elspeth/M
Elston/M
Eltanin/M
Elton/M
Elul/M
Elva/M
Elvia/M
Elvin/M
Elvira/M
Elvis/M
Elway/M
Elwin/M
Elwood/M
Elwyn/M
Elyria/M
Elyse/M
Elysee/M
Elysian/M
Elysium/SM
Elyssa/M
Elys�e/M
Ema/M
Emacs/M
Emanuel/M
Emanuele/M
Emeline/M
Emerson/M
Emery/M
Emeryville/M
Emil/M
Emile/M
Emilia/M
Emilie/M
Emilio/M
Emily/M
Eminem/M
Eminence
Emlen/M
Emlyn/M
Emma/M
Emmaline/M
Emmanuel/M
Emmeline/M
Emmerich/M
Emmet/M
Emmett/M
Emmie/M
Emmy/M
Emory/M
Encarta/M
EndNote/M
Endymion/M
Eng/M
Engelbert/M
Engels/M
England/M
English/MRS
Englishman/M
Englishmen/M
Englishwoman/M
Englishwomen/M
Enid/M
Enif/M
Eniwetok/M
Enkidu/M
Ennis
Enoch/M
Enos/M
Enrica/M
Enrico/M
Enrique/M
Enron/M
Enterprise/M
Eocene/M
Epcot/M
Ephesian/MS
Ephesus/M
Ephraim/M
Ephrem/M
Epictetus/M
Epicurean/M
Epicurus/M
Epimethius/M
Epiphany/SM
Episcopal
Episcopalian/MS
Epistle
Epsom/M
Epson/M
Epstein/M
Equuleus/M
Er/M
Eran/M
Erasmus/M
Erastus/M
Erato/M
Eratosthenes/M
Erda/M
Erebus/M
Erector/M
Erewhon/M
Erhard/M
Eric/M
Erica/M
Erich/M
Erick/M
Ericka/M
Erickson/M
Ericson/M
Ericsson/M
Eridanus/M
Erie/M
Erik/M
Erika/M
Erin/M
Eris/MS
Eritrea/M
Eritrean/SM
Erl/M
Erlang/M
Erlenmeyer/M
Erma/M
Erna/M
Ernest/M
Ernestine/M
Ernesto/M
Ernie/M
Ernst/M
Eros/MS
Errol/M
Erroll/M
Erse/M
Erskine
Erv/M
ErvIn/M
Erwin/M
Esau/M
Escher/M
Escherichia/M
Escondido
Esdras
Eskimo/MS
Esme/M
Esmeralda/M
Esperanto/M
Esperanza/M
Espinoza/M
Esq/M
Esquire/MS
Essa/M
Essen/M
Essene/M
Essequibo/M
Essex/M
Essie/M
Esta/M
Establishment
Esteban/M
Estela/M
Estella/M
Estelle/M
Ester/M
Esterhazy/M
Esterh�zy/M
Estes/M
Estevan/M
Esther/M
Estonia/M
Estonian/SM
Estrada/M
Estrella/M
Ethan/M
Ethel/M
Ethelbert
Ethelred/M
Ethelyn/M
Ethernet/M
Ethiopia/M
Ethiopian/SM
Etienne/M
Etna/M
Eton/M
Etruria/M
Etruscan/M
Etta/M
Ettie/M
Ettore/M
Etty/M
Eu/M
Eucharist/MS
Eucharistic
Euclid/M
Euclidean/M
Eudora/M
Eugen/M
Eugene/M
Eugenia/M
Eugenie/M
Eugenio/M
Eugenius/M
Eula/M
Eulalie/M
Euler/M
Eumenides/M
Eunice/M
Euphemia/M
Euphrates/M
Eur
Eurasia/M
Eurasian/MS
Euripides/M
Eurodollar/SM
Europa/M
Europaea/M
Europe/M
European/MS
Eurydice/M
Eustace/M
Eustachian/M
Eustacia/M
Eustis/M
Euterpe/M
Ev/M
Eva/M
Evan/SM
Evangelical
Evangelina/M
Evangeline/M
Evangelist/M
Evans/M
Evansville/M
Eve/M
Evelina/M
Eveline/M
Evelyn/M
Evenki/M
EverReady/M
Everard/M
Everest/M
Everett/M
Everette/M
Everglades/M
Evert/M
Evey/M
Evian/M
Evie/M
Evin/M
Evita/M
Evy/M
Ewan/M
Ewart/M
Ewell/M
Ewen/M
Ewing/M
Excalibur/M
Excedrin/M
Excellency/SM
Exchequer
Exercycle/M
Exocet/M
Exodus/M
Expedia/M
Exxon/M
Eyck/M
Eyre/M
Eysenck/M
Ezekiel/M
Ezequiel/M
Ezra/M
F/MD
FAA
FAQ/SM
FBI/M
FCC
FD
FDA
FDIC/M
FDR/M
FHA/M
FICA/M
FIFO
FL
FM/SM
FNMA/M
FOFL
FORTRAN/M
FPO
FSF/M
FSLIC
FTC
FTM/SM
FUD/S
FWD
FWIW
FY
FYI
Faber/M
Faberge/M
Faberg�/M
Fabian/MS
Fabien/M
Fabio/M
Facebook/M
Fae/M
Faeroe/M
Fafnir/M
Fagin/M
Fahd/M
Fahrenheit/M
Fairbanks/M
Fairfax
Fairfield/M
Fairhope/M
Fairleigh/M
Fairlie/M
Faisal/M
Faisalabad/M
Faith/M
Fajardo/M
Falasha/M
Falkland/SM
Falklands/M
Falkner
Fallon/M
Fallopian/M
Falstaff/M
Falwell/M
Fania/M
Fannie/M
Fanny/M
Far's
Fara/M
Faraday/M
Farah/M
Fargo/M
Farley/M
Farmer/M
Farmington/M
Farr/M
Farragut/M
Farrah/M
Farrakhan/M
Farrand/M
Farrel/M
Farrell/M
Farris/M
Farrow/M
Farsi/M
Fascist
Faso/M
Fassbinder/M
Fatah/M
Fates/M
Father/SM
Fatima/M
Fatimid/M
Faulkner/M
Faulknerian/M
Fauntleroy/M
Faust/M
Faustian/M
Faustina/M
Faustino/M
Faustus/M
Fawkes/M
Fay/M
Faye/M
Fayette/M
Fayetteville/M
Fayre/M
Fe/M
Feb/M
February/SM
Fed/SM
FedEx/M
Federal/MS
Federalist/M
Federico/M
Feds/M
Felecia/M
Felice/M
Felicia/M
Felicity/M
Feliks/M
Felipe/M
Felix/M
Fellini/M
Feng/M
Fenian/M
Fenix/M
Fennec/M
Feodor/M
Ferber/M
Ferd/M
Ferdie/M
Ferdinand/M
Ferdy/M
Fergus/M
Ferguson/M
Ferlinghetti/M
Fermat/M
Fermi/M
Fern/M
Fernanda/M
Fernande/M
Fernandez/M
Fernandina/M
Fernando/M
Ferne/M
Ferrari/M
Ferraro/M
Ferrel/M
Ferrell/M
Ferris/M
Fey's
Feynman/M
Fez/M
Fianna
Fiat/M
Fiberglas/M
Fibonacci/M
Fichte/M
Fidel/M
Fidelia/M
Fidelio/M
Fido/M
Fielding/M
Fields/M
Fifi/M
Figaro/M
Figueroa/M
Fiji/M
Fijian/MS
Filip/M
Filipino/MS
Filippo/M
Fillmore/M
FilmSpot/M
Filmer/M
Filofax/M
Fina/M
Finch/M
FindArticles/M
FindLaw/M
Findlay/M
Findley/M
Finland/M
Finlay/M
Finley/M
Finn/SM
Finnbogadottir/M
Finnegan/M
Finnish/M
Fiona/M
Firebase/M
Firefox/M
Firestone/M
Fischer/M
Fisher/M
Fisk/M
Fitch/M
Fitchburg/M
Fitz/M
Fitzgerald/M
Fitzpatrick/M
Fitzroy/M
Fizeau/M
Fla
Flagstaff/M
Flanagan/M
Flanders/M
Flathead
Flatt/M
Flaubert/M
Fleischer/M
Flem/G
Fleming/M
Flemish/M
Flemming/M
Fletch/MR
Fletcher/M
Fleur/M
Flickr/M
Flin/M
Flinn/M
Flint/M
Flintstones/M
Flo/M
Flor/M
Flora/M
Flore/SM
Florence/M
Florencia/M
Florentine/M
Flores/M
Florette/M
Florian/M
Florida/M
Floridan/M
Floridian/SM
Florinda/M
Florine/M
Floris
Florrie/M
Florsheim/M
Flory/M
Flossie/M
Flossy's
Flowers/M
Floyd/M
Flynn/M
Fm/M
Foch/M
Fokker/M
Foley/M
Folgers/M
Folsom/M
Fomalhaut/M
Fonda/M
Fons
Foosball/M
Forbes/M
Ford/M
Foreman/M
Forest/MR
Forester/M
Forex/M
Formica/MS
Formosa/M
Formosan/M
Forrest/MR
Forrester/M
Forster/M
Fortaleza/M
Fortran
Foss/M
Fosse/M
Foster/M
Fotomat/M
Foucault/M
Fourier/M
Fourneyron/M
Fourth
Fowler/M
Fox/MS
Fr/MD
Fragonard/M
Fran/SM
France/SM
Frances/M
Francesca/M
Francesco/M
Francine/M
Francis/M
Francisca/M
Franciscan/MS
Francisco/M
Franck/M
Franco/M
Francois/M
Francoise/M
Francophile
Franglais/M
Frank/SM
Frankel/M
Frankenstein/M
Frankfort/M
Frankfurt/MR
Frankfurter/M
Frankie/M
Frankish
Franklin/M
Franklyn/M
Franks/M
Franky/M
Frannie/M
Franny/M
Fransisco/M
Franz/MN
Franzen/M
Fraser/M
Frasier/M
Frau/MN
Fraulein
Frazer
Frazier/M
Fred/M
Freda/M
Freddie/M
Freddy/M
Frederic/M
Frederica/M
Frederich/M
Frederick/M
Fredericksburg/M
Frederico/M
Fredericton/M
Frederik/M
Frederique/M
Fredric/M
Fredrick/M
Fredrika/M
Free's
FreeBSD/M
Freeland/M
Freeman/M
Freemason/SM
Freemasonry/SM
Freetown/M
Freida/M
Fremont/M
French/MS
Frenchman/M
Frenchmen/M
Frenchwoman/M
Frenchwomen/M
Freon/M
Fresnel/M
Fresno/M
Freud/M
Freudian/M
Frey/M
Freya/M
Fri/M
Friday/SM
Frieda/M
Friedan/M
Friederike/M
Friedman/M
Friedmann/M
Friedrich
Friend/SM
Frigga/M
Frigidaire/M
Frisbee/M
Frisco/M
Frisian/MS
Frito/M
Fritz/M
Friulian/M
Frobisher/M
Frodo/M
Froissart/M
Fromm/M
Fronde/M
FrontPage/M
Frontenac/M
Frost/M
Frostbelt/M
Frunze/M
Fry/M
Frye/M
Fuchs/M
Fuentes/M
Fugger/M
Fuji/M
Fujian/M
Fujitsu/M
Fujiwara/M
Fujiyama/M
Fukuoka/M
Fukushima/M
Fukuyama/M
Fulani/M
Fulbright/M
Fuller/M
Fullerton/M
Fulton/M
Fulvia/M
Funafuti/M
Fundy/M
Furies/M
Furman/M
Furtwangler/M
Furtw�ngler/M
Fushun/M
Fuzhou/M
Fuzzbuster/M
G/MNRB
GA
GAO
GATT/M
GB/M
GCC/M
GDP/M
GDPR
GE/M
GED
GHQ/M
GHz/M
GI
GIF
GIGO
GM/M
GMAT
GMO
GMT/M
GNP/M
GNU/M
GOP/M
GP/M
GPA
GPO
GPS
GPU
GSA
GTE/M
GU
GUI/M
Ga/M
GaAs
Gabby/M
Gabe/M
Gabi/M
Gable/M
Gabon/M
Gabonese/M
Gaborone/M
Gabriel/M
Gabriela/M
Gabriele/M
Gabriella/M
Gabrielle/M
Gaby/M
Gacrux/M
Gadsden/M
Gae/M
Gaea/M
Gael/SM
Gaelic/M
Gagarin/M
Gage/M
Gaia/M
Gail/M
Gaiman/M
Gaines/M
Gainesville/M
Gainsborough/M
Galahad/SM
Galapagos/M
Galatea/M
Galatia/M
Galatians/M
Galaxy
Galbraith/M
Gale/M
Galen/M
Galibi/M
Galilean/SM
Galilee/M
Galileo/M
Galina/M
Gall/M
Gallagher/M
Gallegos/M
Gallic/M
Gallicism/SM
Gallo/M
Galloway/M
Gallup/M
Galois/M
Galsworthy/M
Galvan/M
Galvani/M
Galveston/M
Galvin/M
Gama
Gamaliel/M
Gamay/M
Gambia/M
Gambian/SM
Gamble/M
GameCube/M
GameFAQs/M
GameSpot/M
Gamow/M
Gan/M
Gandalf/M
Gandhi/M
Gandhian/M
Ganesha/M
Ganges/M
Gangtok/M
Gannon/M
Gansu/M
Gantry/M
Ganymede/M
Gap/M
Garamond
Garbo/M
Garcia/M
Gard
Gardiner
Gardner/M
Gare/MH
Gareth/M
Garey/M
Garfield/M
Garfunkel/M
Gargantua/M
Garibaldi/M
Garland/M
Garner/M
Garrard/M
Garrett/M
Garrick/M
Garrison/M
Garry/M
Garth/M
Garvey/M
Garvin/M
Garwood/M
Gary/M
Garza/M
Gascony/M
Gaspar
Gaspard/M
Gasparo/M
Gasper/M
Gasser/M
Gaston/M
Gastonia/M
Gastroenterology
Gates/M
Gatling/M
Gatorade/M
Gatsby/M
Gatun/M
Gauguin/M
Gaul/SM
Gaulish
Gauss/M
Gaussian/M
Gautama/M
Gauthier/M
Gautier/M
Gav/MN
Gavan/M
Gaven/M
Gavin/M
Gawain/M
Gay/M
Gaye/M
Gayle/M
Gaylord/M
Gaynor/M
Gaza/M
Gaziantep/M
Gbps
Gd/M
Gdansk/M
Ge/M
Gecko/M
Geffen/M
Gehenna/M
Gehrig/M
Geiger/M
Gelbvieh/M
Geller/M
Gemini/MS
Gen/M
GenBank/M
Gena/M
Genaro/M
Gene/M
Genesis/M
Genet/M
Geneva/M
Genevieve/M
Genevra/M
Genghis/M
Genia/M
Genna/M
Genny/M
Geno/M
Genoa/SM
Gentoo/M
Gentry/M
Geo/M
Geoff/M
Geoffrey/M
Geordie
Georg/M
George/MS
Georgetown/M
Georgette/M
Georgi/M
Georgia/M
Georgian/MS
Georgiana/M
Georgianna/M
Georgie/M
Georgina/M
Georgy/M
Ger/M
Gerald/M
Geraldine/M
Gerard/M
Gerardo/M
Gerber/M
Gerda/M
Gere/M
Gerhard/M
Gerhardt/M
Geri/M
Geritol/M
Germain/M
Germaine/M
German/MS
Germanic/M
Germany/M
Gerome/M
Geronimo/M
Gerrard/M
Gerri/M
Gerry/M
Gershwin/M
Gert/M
Gertie/M
Gertrud/M
Gertrude/M
Gertrudis/M
Gerty/M
Gery/M
Gestapo/SM
Gethsemane/M
Getty/M
Gettysburg/M
Gewurztraminer/M
Gew�rztraminer/M
Ghana/M
Ghanaian
Ghats/M
Ghazvanid/M
Ghent/M
Ghibelline/M
Giacometti/M
Giacomo/M
Gian/M
Gianna/M
Gianni/M
Giannini/M
Giauque/M
Gib/M
Gibb/SM
Gibbon/M
Gibbs/M
Gibraltar/MS
Gibson/M
Gide/M
Gideon/M
Gielgud/M
Gienah/M
Giff/M
Giffard/M
Gifford/M
Gigi/M
Gil/MY
Gila/M
Gilbert/M
Gilberte/M
Gilberto/M
Gilchrist/M
Gilda/M
Gilead/M
Giles/M
Gilgamesh/M
Gill/M
Gillan/M
Gilles
Gillespie/M
Gillette/M
Gilliam/M
Gillian/M
Gillie's
Gilligan/M
Gilly/M
Gilman
Gilmore/M
Gilroy/M
Gina/M
Ginevra/M
Ginger/M
Gingrich/M
Ginnie/M
Ginny/M
Gino/M
Ginsberg/M
Ginsburg/M
Ginsu/M
Giordano/M
Giorgi/M
Giorgio/M
Giorgione/M
Giotto/M
Giovanna/M
Giovanni/M
Giraud
Giraudoux/M
Gisela/M
Gisele/M
Giselle/M
Gish/M
GitHub/M
Giulia/M
Giuliani/M
Giulietta/M
Giulio/M
Giuseppe/M
Giusto/M
Giza/M
Gk
Gladstone/MS
Gladys/M
Glaser/M
Glasgow/M
Glass/M
Glastonbury/M
Glaswegian/SM
Glaxo/M
Gleason/M
Glen/M
Glenda/M
Glendale
Glendon/M
Glenlivet/M
Glenn/M
Glenna/M
Glennie/M
Gloria/M
Gloriana/M
Gloucester/M
Gloucestershire/M
Glover/M
Glyn/M
Glynis/M
Glynn/M
GmbH
Gnostic/M
Gnosticism/M
GnuPG
Goa/M
Gobi/M
God/M
Godard/M
Goddard/M
Godel/M
Godfrey/M
Godhead/M
Godiva/M
Godot/M
Godspeed/SM
Godthaab/M
Godunov/M
Godwin
Godzilla/M
Goebbels/M
Goering/M
Goethals/M
Goethe/M
Goff/M
Gog/M
Gogol/M
Goiania/M
Golan/M
Golconda/M
Golda/M
Goldberg/M
Golden/M
Goldie/M
Goldilocks/M
Golding/M
Goldman/M
Goldsboro/M
Goldsmith/M
Goldwater/M
Goldwyn/M
Goldy/M
Golgi/M
Golgotha/M
Goliath/M
Gomez/M
Gomorrah/M
Gompers/M
Gomulka/M
Gondwanaland/M
Gonzales/M
Gonzalez/M
Gonzalo/M
Good/M
Goodall/M
Goode/M
Goodman/M
Goodrich/M
Goodwill/M
Goodwin/M
Goodyear/M
Google/M
Goolagong/M
Gopher
Goran/M
Gorbachev/M
Gordan/M
Gorden/M
Gordian/M
Gordie/M
Gordimer/M
Gordon/M
Gordy/M
Gore/M
Goren/M
Gorey/M
Gorgas/M
Gorgon/M
Gorgonzola/M
Gorky/M
Gospel/MS
Goteborg/M
Goth/M
Gotham/M
Gothic/MS
Goths
Gottfried/M
Gouda/SM
Gould/M
Gounod/M
Governor
Goya/M
Gr/B
Grable/M
Gracchus/M
Grace/M
Graceland/M
Gracia/M
Gracie/M
Graciela/M
Grady/M
Graeme/M
Graffias/M
Grafton/M
Graham/M
Grahame/M
Grail/M
Grammy/M
Grampians/M
Gran's
Granada/M
Grange/R
Grannie/M
Grant/M
Grantham/M
Grantley/M
Granville/M
Grass/M
Grata/M
Gratia/M
Graves/M
Gray/M
Grayslake/M
Grazia/M
Grecian/M
Greece/M
Greek/SM
Greeley/M
Green/SM
Greene/M
Greenland/M
Greenlandic
Greenpeace/M
Greensboro/M
Greensleeves/M
Greenspan/M
Greenville/M
Greenwich/M
Greer/M
Greg/M
Gregg/M
Gregoire/M
Gregor/M
Gregorian/M
Gregorio/M
Gregorius/M
Gregory/M
Grenada/M
Grenadian/MS
Grenadines/M
Grendel/M
Grenoble/M
Grenville
Gresham/M
Greta/M
Gretchen/M
Grete/M
Gretel/M
Gretna/M
Gretta/M
Gretzky/M
Grey/M
Grieg/M
Grier/M
Griff/M
Griffin/M
Griffith/M
Griffiths
Grimes/M
Grimm/M
Grinch/M
Gris/M
Griselda
Griswold/M
Gromyko/M
Gropius/M
Gross/M
Grosz/M
Grotius/M
Grover/M
Grozny
Grumman/M
Grundy/M
Grunewald/M
Grus/M
Gruyere/SM
Gruy�re/M
Gr�newald/M
Guadalajara/M
Guadalcanal/M
Guadalquivir/M
Guadalupe/M
Guadeloupe/M
Guallatiri/M
Guam/M
Guamanian
Guangdong/M
Guangzhou/M
Guantanamo/M
Guarani/M
Guarnieri/M
Guatemala/M
Guatemalan/MS
Guayama/M
Guayaquil/M
Gucci/M
Guelph/M
Guenevere/M
Guernsey/MS
Guerra/M
Guerrero/M
Guevara/M
Guggenheim/M
Guglielmo/M
Gui/M
Guiana/M
Guido/M
Guilbert/M
Guildford/M
Guillaume/M
Guillermo/M
Guinea/M
Guinean/MS
Guinevere/M
Guinness/M
Guiyang/M
Guizhou/M
Guizot/M
Gujarat/M
Gujarati/M
Gujranwala/M
Gulfport/M
Gullah/M
Gulliver/M
Gumbel/M
Gunilla/M
Gunter
Gunther/M
Guofeng/M
Gupta/M
Gurkha/M
Gus/M
Gussie/M
Gussy's
Gusta/M
Gustaf/M
Gustav/M
Gustave/M
Gustavo/M
Gustavus/M
Gusti/M
Gusty's
Gutenberg/M
Guthrie/M
Gutierrez/M
Guy/M
Guyana/M
Guyanese/M
Guzman/M
Gwalior/M
Gwen/M
Gwendolen/M
Gwendoline/M
Gwendolyn/M
Gwyn/M
Gwyneth/M
Gwynne/M
Gypsy/SM
G�del/M
G�teborg/M
H/M
HBO/M
HBase/M
HDD
HDMI
HDTV
HF/M
HHS
HI
HIV/M
HM
HMO/M
HMS
HOV
HP/M
HPV
HQ/M
HR
HRH
HS
HSBC/M
HST
HT
HTML/M
HTTP
HTTPS
HUD/M
HVAC
Ha/M
Haas/M
Habakkuk/M
Haber/M
Had's
Hadar/M
Hades/M
Hadleigh/M
Hadley/M
Hadoop/M
Hadria/M
Hadrian/M
Hafiz/M
Hagan/M
Hagar/M
Hagen
Hagerstown/M
Haggai/M
Hagiographa/M
Hague/M
Hahn/M
Haida/SM
Haifa/M
Hailey/M
Hainan/M
Haiphong/M
Haiti/M
Haitian/MS
Hakeem/M
Hakim/M
Hakka/M
Hakluyt/M
Hal/SMY
Haldane/M
Hale/M
Haleakala/M
Haley/M
Hali/M
Halifax/M
Hall/M
Halley/M
Halliburton/M
Hallie/M
Hallmark/M
Halloween/MS
Hallstatt/M
Hally/M
Halon/M
Hals/M
Halsey/M
Ham/M
Haman/M
Hamas/M
Hamburg/MS
Hamel/M
Hamhung/M
Hamid/M
Hamil/M
Hamilcar/M
Hamill/M
Hamilton/M
Hamiltonian/M
Hamish/M
Hamitic/M
Hamlet/M
Hamlin/M
Hammad/M
Hammarskjold/M
Hammerstein/M
Hammett/M
Hammond/M
Hammurabi/M
Hampshire/M
Hampton/M
Hamsun/M
Han/SM
Hana/M
Hanan/M
Hancock/M
Handel/M
Handy/M
Haney/M
Hanford/M
Hangul/M
Hangzhou/M
Hank/M
Hanna/M
Hannah/M
Hanni/M
Hannibal/M
Hanoi/M
Hanover/M
Hanoverian/M
Hans/MN
Hansel/M
Hansen/M
Hanson/M
Hanuka
Hanukah/M
Hanukkah/M
Hanukkahs
Happy's
Hapsburg/M
Harald/M
Harare/M
Harbert/M
Harbin/M
Harcourt/M
Hardin/M
Harding/M
Hardy/M
Hargreaves/M
Harlan/M
Harland/M
Harlem/M
Harlequin/M
Harley/M
Harlin/M
Harlingen/M
Harlow/M
Harman/M
Harmon/M
Harmonia/M
Harmonie/M
Harold/M
Haroun/M
Harper/M
Harpy/SM
Harrell/M
Harri/SM
Harriet/M
Harriett/M
Harriette/M
Harrington/M
Harriot/M
Harriott/M
Harris/M
Harrisburg/M
Harrison/M
Harrisonburg/M
Harrods/M
Harry/M
Hart/M
Harte/M
Hartford/M
Hartley
Hartline/M
Hartman/M
Hartwell/M
Harv/M
Harvard/M
Harvey/M
Harwell/M
Hasbro/M
Hashim/M
Hasidim/M
Haskell/M
Haslett/M
Hassan/M
Hastie/M
Hastings/M
Hasty's
Hatfield/M
Hathaway/M
Hatsheput/M
Hatteras/M
Hatti/M
Hattie/M
Hattiesburg/M
Hatty/M
Hauptmann/M
Hausa/M
Hausdorff/M
Havana/MS
Havarti/M
Havel/M
Havoline/M
Haw
Hawaii/M
Hawaiian/SM
Hawking/M
Hawkins/M
Hawks
Hawthorne/M
Hay/SM
Hayden/M
Haydn/M
Haydon/M
Hayek/M
Hayes/M
Hayley/M
Haynes/M
Hays/M
Hayward/M
Haywood/M
Hayworth/M
Hayyim/M
Hazel/M
Hazleton/M
Hazlett/M
Hazlitt/M
He/M
Head/M
Hearst/M
Heath/MR
Heather/M
Heaviside/M
Heb
Hebe/M
Hebei/M
Hebert/M
Hebraic/M
Hebraism/SM
Hebrew/MS
Hebrews/M
Hebrides/M
Hecate/M
Hector/M
Hecuba/M
Hedda/M
Hedi/M
Hedwig/M
Hedy/M
Heep/M
Hefner/M
Hegel/M
Hegelian/M
Hegira/M
Heidegger/M
Heidelberg/M
Heidi/M
Heifetz/M
Heilongjiang/M
Heimlich/M
Heine/M
Heineken/M
Heinlein/M
Heinrich/M
Heinz/M
Heisenberg/M
Heisman/M
Hejira's
Helaina/M
Helen/M
Helena/M
Helene/M
Helga/M
Helge/M
Helicobacter
Helicon/M
Heliopolis/M
Helios/M
Hellene/SM
Hellenic/M
Hellenism/MS
Hellenist
Hellenistic/M
Hellenization/M
Hellenize/MD
Heller/M
Hellespont/M
Hellman/M
Helmholtz/M
Heloise/M
Helsinki/M
Helvetian
Helvetica
Helvetius/M
Hemet/M
Hemingway/M
Henan/M
Hench/M
Henderson/M
Hendrick/MS
Hendricks/M
Hendrik/M
Hendrix/M
Henley/M
Hennessy/M
Henri/M
Henrietta/M
Henriette/M
Henrik/M
Henry/M
Hensley/M
Henson/M
Hepburn/M
Hephaestus/M
Hephzibah/M
Hepplewhite/M
Hera/M
Heracles/M
Heraclitus/M
Herakles/M
Herbart/M
Herbert/M
Herbie/M
Herby/M
Herc/M
Herculaneum/M
Hercule/MS
Herculean
Hercules/M
Herder/M
Hereford/SM
Herero/M
Heriberto/M
Herman/M
Hermann/M
Hermaphroditus/M
Hermes/M
Hermia/M
Hermine/M
Herminia/M
Hermione/M
Hermitage/M
Hermite/M
Hermon
Hermosillo/M
Hernandez/M
Hernando/M
Herod/M
Herodotus/M
Heroku/M
Herold/M
Herr/MG
Herrera/M
Herrick/M
Herring/M
Hersch/M
Herschel/M
Hersey/M
Hersh/M
Hershel/M
Hershey/M
Herta/M
Hertfordshire/M
Hertha/M
Hertz/M
Hertzsprung/M
Herve/M
Hervey/M
Herzegovina/M
Herzl/M
Heshvan/M
Hesiod/M
Hesperia/M
Hesperus/M
Hess/M
Hesse/M
Hessian/M
Hester/M
Hestia/M
Heston/M
Hettie/M
Hetty/M
Hew's
Hewett/M
Hewitt/M
Hewlett/M
Heyerdahl/M
Heywood/M
Hezbollah/M
Hezekiah/M
Hf/M
Hg/M
Hi's
Hialeah/M
Hiawatha/M
Hibernia/M
Hibernian
Hickman/M
Hickok/M
Hickory/M
Hicks/M
Hieronymus/M
Higashiosaka
Higgins/M
Highlander/SM
Highlands
Highness/M
Hightstown/M
Hilario/M
Hilary/M
Hilbert/M
Hilda/M
Hilde/M
Hildebrand/M
Hildegarde/M
Hildy/M
Hilfiger/M
Hill/M
Hillard/M
Hillary/M
Hillel/M
Hillery/M
Hilliard
Hillier/M
Hillsborough/M
Hilly's
Hillyer/M
Hilton/M
Himalaya/SM
Himalayan
Himalayas/M
Himmler/M
Hinayana/M
Hindemith/M
Hindenburg/M
Hindi/M
Hindu/SM
Hinduism/SM
Hindustan/M
Hindustani/SM
Hines/M
Hinesville/M
Hinton/M
Hinze/M
Hipparchus/M
Hippocrates/M
Hippocratic/M
Hiram/M
Hirobumi/M
Hirohito/M
Hiroshima/M
Hirsch/M
Hispanic/SM
Hispanica/M
Hispaniola/M
Hiss/M
Hitachi/M
Hitchcock/M
Hitler/MS
Hittite/SM
Hmong/M
Ho/M
Hobart/M
Hobbes/M
Hobbs/M
Hobie/M
Hockney/M
Hodge/SM
Hodges/M
Hodgkin/M
Hoff/M
Hoffa/M
Hoffman/M
Hofstadter/M
Hogan/M
Hogarth/M
Hogwarts/M
Hohenlohe/M
Hohenstaufen/M
Hohenzollern/M
Hohhot/M
Hohokam/M
Hokkaido/M
Hokusai/M
Holbein/M
Holcomb/M
Holden/M
Holder/M
Holiday/M
Holiness
Holland/ZSMR
Hollander/M
Hollandica/M
Hollerith/M
Holley/M
Hollie/M
Hollis/M
Holloway/M
Holly/M
Hollywood/M
Holman/M
Holmes/M
Holocaust/M
Holocene/M
Holst/M
Holstein/SM
Holt/M
Homer/M
Homeric/M
Hon
Honda/M
Honduran/MS
Honduras/M
Honecker/M
Honeywell/M
Hong
Honiara/M
Honolulu/M
Honorable
Honoria/M
Honshu/M
Hood/M
Hooke/RM
Hooker/M
Hooper/M
Hoosier/MS
Hooters/M
Hoover/MS
Hope/M
Hopewell/M
Hopi/SM
Hopkins/M
Hopper/M
Horace/M
Horacio/M
Horatia/M
Horatio/M
Horatius/M
Hormel/M
Hormuz/M
Horn/M
Hornblower/M
Horne/M
Horowitz/M
Horst/M
Hort/M
Hortense
Hortensia/M
Horthy/M
Horton/M
Horus/M
Hosea/M
Host/SM
Hotmail/M
Hotpoint/M
Hottentot/SM
Houdini/M
Houghton/M
Houma/M
House/M
Housman/M
Houston/M
Houyhnhnm/M
Hovhaness/M
Howard/M
Howe/M
Howell/MS
Howells/M
Howey/M
Howie/M
Howrah
Hoyle/M
Hoyt/M
Hrothgar/M
Hts
Huang/M
Hubbard/M
Hubble/M
Hubei/M
Huber/M
Hubert/M
Huck/M
Huddersfield
Hudson/M
Huerta/M
Huey/M
Huff/M
Huffman/M
Huggins/M
Hugh/MS
Hughes/M
Hughie
Hugo/M
Huguenot/MS
Hugues/M
Hui/M
Huitzilopotchli/M
Hulda/M
Hull/M
Humbert/M
Humberto/M
Humboldt/M
Hume/M
Humfrey/M
Hummel/M
Hummer/M
Humphrey/SM
Humvee/M
Hun/SM
Hunan/M
Hung/M
Hungarian/SM
Hungary/M
Hunspell/M
Hunt/MR
Hunter/M
Huntington/M
Huntley/M
Huntsville/M
Hurd/M
Hurley/M
Huron/M
Hurst/M
Hus/M
Husein/M
Hussein/M
Husserl/M
Hussite/M
Huston/M
Hutchinson/M
Hutton/M
Hutu/M
Huxley/M
Huygens/M
Hy/M
Hyacinthe/M
Hyades/M
Hyatt/M
Hyde/M
Hyderabad/M
Hydra/M
Hyman/M
Hymen/M
Hymie
Hyperion/M
Hyundai/M
Hz/M
H�loise/M
I'd
I'll
I'm
I've
I/M
IA
IANAL
IBM/M
ICBM/SM
ICC
ICU
ID/SM
IDE
IE
IED
IEEE
IIRC
IKEA/M
IL
IMDb/M
IMDbPro/M
IMF/M
IMHO
IMNSHO
IMO
IN
ING/M
INRI
INS
IOU/M
IP
IPA
IPO/SM
IQ/M
IRA/SM
IRC
IRS/M
ISBN
ISIS
ISO/M
ISP/SM
ISS
IT
IUD
IV/SM
IVF
Ia
Iaccoca/M
Iago/M
Iain/M
Ian/M
Ianthe/M
Iapetus/M
Ibadan/M
Iberia/M
Iberian/M
Ibiza/M
Iblis/M
Ibo/M
Ibrahim/M
Ibsen/M
Icahn/M
Icarus/M
Ice
Iceland/MRZ
Icelander/M
Icelandic/M
Ichabod/M
Ida/M
Idaho/SM
Idahoan/MS
Idahoes
Ieyasu/M
Iggy/M
Ignace/M
Ignacio/M
Ignatius/M
Ignaz/M
Ignazio/M
Igor/M
Iguassu/M
Ijsselmeer/M
Ike/M
Ikey/M
Ikhnaton/M
Ila/M
Ileana/M
Ilene/M
Iliad/SM
Ilka/M
Ill
Illa/M
Illinois/M
Illinoisan/MS
Illuminati/M
Ilsa/M
Ilse/M
Ilyushin/M
Imam
Imelda/M
Imhotep/M
Immanuel
Imodium/M
Imogen/M
Imogene/M
Imus/M
In/MP
Ina/M
Inc
Inca/SM
Inchon/M
Incorporated
Ind
Independence/M
India/M
Indian/MS
Indiana/M
Indianan/SM
Indianapolis/M
Indianian
Indies/M
Indio/M
Indira/M
Indochina/M
Indochinese/M
Indonesia/M
Indonesian/SM
Indore/M
Indra/M
Indus/M
Indy/SM
Ines/M
Inez/M
Inga/M
Inge/RM
Ingeborg/M
Ingemar/M
Inger/M
Inglewood
Inglis/M
Ingmar/M
Ingram/M
Ingres/M
Ingrid/M
Inigo/M
Inna/M
Inness/M
Innis/M
Innocent/M
Innsbruck
Inonu/M
Inquisition/M
Inst
Instagram/M
Instamatic/M
Intel/M
Intelsat/M
Internationale/M
Internet/SM
Interpol/M
Inuit/MS
Inuktitut/M
Invar/M
Io/M
Iona
Ionesco/M
Ionian/MS
Ionic/SM
Iowa/SM
Iowan/MS
Iphigenia/M
Ipswich
Iqaluit/M
Iqbal/M
Iquitos/M
Ir/M
Ira/M
Iran/M
Iranian/SM
Iraq/M
Iraqi/MS
Ireland/M
Irena/M
Irene/M
Irina/M
Iris/M
Irish/MR
Irishman/M
Irishmen/M
Irishwoman/M
Irishwomen/M
Irkutsk/M
Irma/M
Iroquoian/SM
Iroquois/M
Irrawaddy/M
Irtish/M
Irv/MG
Irvin/M
Irvine/M
Irving/M
Irwin/M
Isa
Isaac/M
Isaak/M
Isabel/M
Isabela/M
Isabella/M
Isabelle/M
Isadora/M
Isadore/M
Isaiah/M
Isak/M
Iscariot/M
Isfahan/M
Isherwood/M
Ishim/M
Ishmael/M
Ishtar/M
Isiah/M
Isidor/M
Isidora/M
Isidore/M
Isidoro/M
Isidro/M
Isis/M
Islam/MS
Islamabad/M
Islamic/M
Islamica/M
Islamism/M
Islamist/M
Islamophobia
Islamophobic
Ismael/M
Ismail/M
Isobel/M
Isolde/M
Ispell/M
Israel/SM
Israeli/SM
Israelite/M
Issac/M
Issachar/M
Issy/M
Istanbul/M
Isuzu/M
It
Itaipu/M
Ital
Italia/M
Italian/SM
Italianate
Italy/M
Itasca/M
Ithaca/M
Ithacan/M
Ito/M
Iva/M
Ivan/M
Ivanhoe/M
Ivar/M
Ive/RSM
Iver/M
Ives/M
Ivie/M
Ivoire
Ivor/M
Ivorian
Ivory/M
Ivy/M
Iyar/M
Izaak/M
Izanagi/M
Izanami/M
Izhevsk/M
Izmir/M
Izod/M
Izvestia/M
Izzy/M
J/MDNX
JCS
JD
JFK/M
JP
JPEG/SM
JSON
JV
Jabez/M
Jacinta/M
Jack/M
Jacki/M
Jackie/M
Jacklin/M
Jacklyn/M
Jackson/M
Jacksonian/M
Jacksonville/M
Jacky/M
Jaclyn/M
Jacob/SM
Jacobean/M
Jacobi/M
Jacobin/M
Jacobite/M
Jacobo/M
Jacobs/M
Jacobson/M
Jacquard/M
Jacqueline/M
Jacquelyn/M
Jacques/M
Jacqui/M
Jacquie/M
Jacuzzi/M
Jada/M
Jae/M
Jagger/M
Jagiellon/M
Jaguar/M
Jahangir/M
Jaime/M
Jain/M
Jainism/M
Jaipur/M
Jakarta/M
Jake/M
Jakob/M
Jamaal/M
Jamaica/M
Jamaican/SM
Jamal/M
Jamar/M
Jame/SM
Jamel/M
James/M
Jameson
Jamestown/M
Jamey/M
Jami/M
Jamie/M
Jamil/M
Jamison/M
Jan/M
Jana/M
Janacek/M
Jane/M
Janek/M
Janell/M
Janelle/M
Janesville/M
Janet/M
Janette/M
Janey/M
Janice/M
Janie/M
Janina
Janine/M
Janis/M
Janissary/M
Janjaweed/M
Janna/M
Jannie/M
Janos/M
Jansen/M
Jansenist/M
January/SM
Janus/M
Jany/M
Jap/SM
Japan/M
Japanese/MS
Japura/M
Jared/M
Jarlsberg/M
Jarred/M
Jarret/M
Jarrett/M
Jarrod/M
Jarvis/M
Jase/M
Jasmin/M
Jasmine/M
Jason/M
Jasper/M
Jataka/M
Java/SM
JavaScript/M
Javanese/M
Javier/M
Jaxartes/M
Jay/M
Jayapura/M
Jayawardene/M
Jaycee/MS
Jaycees/M
Jaye/M
Jayme/M
Jayne/M
Jayson/M
Jean/M
Jeana/M
Jeane/M
Jeanette/M
Jeanie/M
Jeanine/M
Jeanne/M
Jeannette/M
Jeannie/M
Jeannine/M
Jed/M
Jedediah/M
Jedi/M
Jedidiah/M
Jeep/M
Jeeves/M
Jeff/M
Jefferey/M
Jefferson/M
Jeffersonian/M
Jeffery/M
Jeffrey/M
Jeffry/M
Jehoshaphat/M
Jehovah/M
Jehu
Jekyll/M
Jemima/M
Jemmy/M
Jen/M
Jena/M
Jenifer/M
Jenkins/M
Jenn/MRJ
Jenna/M
Jenner/M
Jenni/M
Jennie/M
Jennifer/M
Jennings/M
Jenny/M
Jeno/M
Jensen/M
Jephthah/M
Jerald/M
Jere/M
Jeremiah/M
Jeremiahs
Jeremias/M
Jeremie/M
Jeremy/M
Jeri/M
Jericho/M
Jermaine/M
Jeroboam/M
Jerold/M
Jerome/M
Jerri/M
Jerrie/M
Jerrod/M
Jerrold/M
Jerry/M
Jersey/MS
Jerusalem/M
Jervis/M
Jess/M
Jessamine/M
Jessamyn/M
Jesse/M
Jessey/M
Jessi/M
Jessica/M
Jessie/M
Jessy/M
Jesuit/MS
Jesus/M
Jethro
Jetway/M
Jew/SM
Jewel/M
Jewell/M
Jewess/MS
Jewish/PM
Jewry/M
Jezebel/SM
Jiangsu/M
Jiangxi/M
Jidda/M
Jilin/M
Jill/M
Jillian/M
Jilly/M
Jim/M
Jimenez/M
Jimmie/M
Jimmy/M
Jinan/M
Jinnah/M
Jinny/M
Jivaro/M
Jo/MY
Joachim
Joan/M
Joana/M
Joane/M
Joanie/M
Joann/M
Joanna/M
Joanne/SM
Joaquin/M
Job/SM
Jobs/M
Joby/M
Jocasta/M
Jocelin/M
Jocelyn/M
Jocelyne/M
Jock/M
Jockey/M
Jocko/M
Jodi/M
Jodie/M
Jody/M
Joe/M
Joel/M
Joelle/M
Joey/M
Jogjakarta/M
Johan/M
Johann/M
Johanna/M
Johannes/M
Johannesburg/M
John/SM
Johnathan/M
Johnathon/M
Johnie/M
Johnnie/M
Johnny/M
Johns/M
Johnson/M
Johnston/M
Johnstown/M
Jojo/M
Jolene/M
Joli/M
Jolie/M
Joliet/M
Jolson/M
Joly/M
Jon/M
Jonah/M
Jonahs
Jonas/M
Jonathan/M
Jonathon/M
Jone/SM
Jones/M
Jonesboro/M
Joni/M
Jonson/M
Joplin/M
Jordan/M
Jordana/M
Jordanian/MS
Jordon/M
Jorge/M
Jori/M
Jory/M
Joscelin/M
Jose/M
Josef/M
Josefa/M
Josefina/M
Joseph/M
Josepha/M
Josephine/M
Josephs
Josephson/M
Josephus/M
Josey/M
Josh/M
Joshua/M
Josiah/M
Josias/M
Josie/M
Josselyn/M
Josue/M
Joule/M
Jourdain/M
Jourdan/M
Jove/M
Jovian/M
Joy/M
Joya/M
Joyce/M
Joycean/M
Joye/M
Joyner/M
Jozef/M
Jpn
Jr/M
Juan/M
Juana/M
Juanita/M
Juarez/M
Jubal/M
Jud
Judaeo
Judah/M
Judaic
Judaical
Judaism/MS
Judas/MS
Judd/M
Jude/M
Judea/M
Judges
Judi/MH
Judith/M
Judson/M
Judy/M
Juggernaut/M
Jul
Jule/SM
Jules/M
Juli/M
Julia/M
Julian/M
Juliana/M
Juliane/M
Julianna/M
Julianne/M
Julie/M
Julienne's
Juliet/M
Juliette/M
Julio/M
Julius/M
Julliard/M
July/SM
Jun/M
June/SM
Juneau/M
Juneteenth/M
Jung/M
Jungfrau/M
Jungian/M
Junia/M
Junie/M
Junior/SM
Junker/SM
Juno/M
Jupiter/M
Jurassic/M
Jurua/M
Justice/M
Justin/M
Justina/M
Justine/M
Justinian/M
Justus/M
Jutland/M
Juvenal/M
Jyoti/M
K/SMNRGJ
KB/M
KC
KFC/M
KGB/M
KIA
KKK/M
KO/M
KP
KS
KY
Kaaba/M
Kabul/M
Kafka/M
Kafkaesque/M
Kagoshima/M
Kahlil/M
Kahlua/M
Kahului/M
Kai/M
Kaia/M
Kaifeng/M
Kaila/M
Kailua/M
Kain/M
Kaine/M
Kaiser/MS
Kaitlin/M
Kaitlyn/M
Kaja/M
Kala/M
Kalahari/M
Kalamazoo/M
Kalashnikov/M
Kalb/M
Kalevala/M
Kalgoorlie/M
Kali/M
Kalil/M
Kalina/M
Kalle/M
Kalmyk/M
Kama/M
Kamchatka/M
Kamehameha/M
Kampala/M
Kampuchea/M
Kan/SM
Kanchenjunga/M
Kandahar/M
Kandinsky/M
Kandy
Kane/M
Kaneohe/M
Kania/M
Kankakee/M
Kannada/M
Kano/M
Kanpur/M
Kansan/MS
Kansas/M
Kant/M
Kantian/M
Kanya/M
Kaohsiung/M
Kaposi/M
Kara/M
Karachi/M
Karaganda/M
Karakorum/M
Karamazov/M
Kare/M
Kareem/M
Karel/M
Karen/M
Karenina/M
Kari/M
Karim/M
Karin/M
Karina/M
Karine/M
Karl/MN
Karla/M
Karlen/M
Karloff/M
Karly/M
Karna/M
Karnataka/M
Karo/MY
Karol/M
Karolina/M
Karoline/M
Karoly/M
Karon/M
Karroo/M
Karyn/M
Kasai/M
Kasey/M
Kashmir/SM
Kaspar/M
Kasparov/M
Kasper/M
Kass
Kassandra/M
Kat/M
Kata/M
Katalin/M
Kate/M
Katelyn/M
Katerina/M
Kath/M
Katha/M
Katharina/M
Katharine/M
Kathe/M
Katherina/M
Katherine/M
Katheryn/M
Kathi/M
Kathiawar/M
Kathie/M
Kathleen/M
Kathmandu/M
Kathrine/M
Kathryn/M
Kathy/M
Kati/M
Katie/M
Katina/M
Katinka/M
Katmai/M
Katmandu/M
Katowice/M
Katrina/M
Katrine
Katrinka/M
Katy/M
Katya/M
Kauai/M
Kaufman/M
Kaunas/M
Kaunda/M
Kavanaugh/M
Kawabata/M
Kawasaki/M
Kay/M
Kaycee/M
Kaye/M
Kayla/M
Kaylee/M
Kayne/M
Kazakh/M
Kazakhs
Kazakhstan/M
Kazan/M
Kazantzakis/M
Kb/M
Kean
Keane/M
Kearney/M
Keaton/M
Keats/M
Keck/M
Keefe/RM
Keefer/M
Keeley/M
Keely/M
Keenan/M
Keene/M
Keewatin/M
Keillor/M
Keir/M
Keisha/M
Keith/M
Kellen/M
Keller/M
Kelley/M
Kelli/M
Kellie/M
Kellogg/M
Kelly/M
Kelsey/M
Kelvin/M
Kemerovo/M
Kemp/M
Kempis/M
Ken/M
Kendal/M
Kendall/M
Kendell/M
Kendra/M
Kendrick/MS
Kenmore/M
Kenn/M
Kenna/M
Kennan/M
Kennedy/M
Kenneth/M
Kennett/M
Kennewick/M
Kennith/M
Kenny/M
Kenosha/M
Kent/M
Kenton/M
Kentuckian/MS
Kentucky/M
Kenya/M
Kenyan/SM
Kenyatta/M
Kenyon/M
Keogh/M
Keokuk/M
Kepler/M
Ker/M
Kerala/M
Kerby/M
Kerensky/M
Keri/M
Kerk/M
Kermit/M
Kern/M
Kerouac/M
Kerr/M
Kerri/M
Kerrie/M
Kerry/M
Kerstin/M
Kerwin/M
Kettering/M
Kev/MN
Kevan/M
Keven/M
Kevin/M
Kevlar/M
Kevorkian/M
Kewpie/M
Key/M
Keynes/M
Keynesian/M
Khabarovsk/M
Khachaturian/M
Khalid/M
Khalil/M
Khan/M
Kharkov/M
Khartoum/M
Khayyam/M
Khazar/M
Khazarica/M
Khmer/M
Khoikhoi/M
Khoisan/M
Khomeini/M
Khorana/M
Khrushchev/M
Khufu/M
Khulna/M
Khwarizmi/M
Khyber/M
Ki/M
Kickapoo/M
Kidd/M
Kiel/M
Kierkegaard/M
Kieth/M
Kiev/M
Kigali/M
Kikuyu/M
Kilauea/M
Kile/M
Kiley/M
Kilian/M
Kilimanjaro/M
Killeen/M
Killian/M
Kilroy/M
Kim/M
Kimball/M
Kimbell/M
Kimberley/M
Kimberly/M
Kimble/M
Kimmy/M
Kincaid/M
King/M
Kingsley
Kingsport/M
Kingston/M
Kingstown/M
Kinko/M
Kinney/M
Kinsey/M
Kinshasa/M
Kinsley/M
Kiowa/MS
Kip/M
Kipling/M
Kipp/M
Kira/M
Kirby/M
Kirchhoff/M
Kirchner/M
Kirghistan/M
Kirghiz/M
Kirghizia/M
Kiri/M
Kiribati/M
Kirinyaga/M
Kirk/M
Kirkland/M
Kirkpatrick/M
Kirov/M
Kirsten/M
Kisangani/M
Kishinev/M
Kislev/M
Kissimmee/M
Kissinger/M
Kit/M
Kitakyushu/M
Kitchener/M
Kitts/M
Kitty/M
Kiwanis/M
Klan/M
Klansman/M
Klara/M
Klaus/M
Klee/M
Kleenex/MS
Klein/M
Klemens/M
Klement/M
Klimt/M
Kline/M
Klingon/M
Klondike/MS
Kmart/M
Knapp/M
Knesset/M
Kngwarreye/M
Knickerbocker/M
Knievel/M
Knight/M
Knopf/M
Knossos/M
Knowles/M
Knox/M
Knoxville/M
Knudsen/M
Knuth/M
Knuths
Kobe/M
Koch/M
Kochab/M
Kodachrome/M
Kodak/M
Kodaly/M
Kodiak/M
Koestler/M
Kohinoor/M
Kohl/M
Koizumi/M
Kojak/M
Kokomo/M
Kolyma/M
Kommunizma/M
Kong/M
Kongo/M
Konrad/M
Konstantin/M
Koo/M
Koontz/M
Koppel/M
Kora/M
Koran/MS
Koranic
Kore/M
Korea/M
Korean/SM
Koren/M
Kori/M
Kornberg/M
Kort/M
Kory/M
Korzybski/M
Kosciusko/M
Kosovo/M
Kossuth/M
Kosygin/M
Kotlin/M
Koufax/M
Kowloon/M
Kr/M
Kraft/M
Krakatau/M
Krakatoa/M
Krakow/M
Kramer/M
Krasnodar/M
Krasnoyarsk/M
Krebs/M
Kremlin/M
Kremlinologist
Kremlinology
Kresge/M
Kringle/M
Kris/M
Krishna/M
Krishnamurti/M
Krista/M
Kristen/M
Kristi/M
Kristian/M
Kristie/M
Kristin/M
Kristina/M
Kristine/M
Kristopher/M
Kristy/M
Kroc/M
Kroger/M
Kronecker/M
Kropotkin/M
Kruger/M
Krugerrand/M
Krupp/M
Krystal/M
Krystyna/M
Kshatriya/M
Kuala/M
Kubernetes/M
Kublai/M
Kubrick/M
Kuhn/M
Kuibyshev/M
Kulthumm/M
Kunming/M
Kuomintang/M
Kurd/M
Kurdish/M
Kurdistan/M
Kurosawa/M
Kurt/M
Kurtis/M
Kusch/M
Kutuzov/M
Kuwait/M
Kuwaiti/SM
Kuznets/M
Kuznetsk/M
Kwakiutl/M
Kwan/M
Kwangchow/M
Kwangju/M
Kwanzaa/MS
Ky/MH
Kyiv/M
Kyla/M
Kyle/M
Kylie/M
Kym/M
Kyoto/M
Kyrgyzstan/M
Kyushu/M
L'Amour/M
L'Enfant
L'Oreal/M
L'Ouverture/M
L/MN
LA
LAN/M
LBJ/M
LC
LCD/M
LCM
LDC
LED/M
LG/M
LGBT
LIFO
LISTSERV/M
LL
LLB/M
LLD/M
LNG
LOGO
LP/M
LPG
LPN/SM
LSAT
LSD/M
LVN
La/SM
Lab
Laban/M
Labrador/SM
Labradorean
Labradorian
Lacey/M
Lachesis/M
Lactobacillus
Lacy/M
Ladoga/M
Ladonna/M
Lady/M
Ladyship/MS
Laetitia/M
Lafayette/M
Lafitte/M
Lagos/M
Lagrange/M
Lagrangian/M
Lahore/M
Lainey/M
Laius/M
Lajos/M
Lakeisha/M
Lakeland/M
Lakers/M
Lakewood
Lakisha/M
Lakota/M
Lakshmi/M
Lalo/M
Lamaism/SM
Lamar/M
Lamarck/M
Lamaze/M
Lamb/M
Lambert/M
Lamborghini/M
Lambrusco/M
Lamentations
Lamond/M
Lamont/M
Lana/M
Lanai/M
Lancashire/M
Lancaster/M
Lance/M
Lancelot/M
Land/M
Landon/M
Landry/M
Landsat/M
Landsteiner/M
Lane/M
Laney/M
Lang/M
Langerhans/M
Langland/M
Langley/M
Langmuir/M
Langston/M
Lani/M
Lanie/M
Lanka/M
Lankan/M
Lanna/M
Lanny/M
Lansing/M
Lanzhou/M
Lao/SM
Laocoon/M
Laos/M
Laotian/SM
Laplace/M
Laplacian
Lapland/MR
Lapp/SM
Lara/M
Laramie/M
Lardner/M
Laredo/M
Lari/M
Larisa/M
Larissa/M
Larousse/M
Larry/M
Lars/MN
Larsen/M
Larson/M
Lascaux/M
Lassa/M
Lassen/M
Lassie/M
Lat/M
Latasha/M
Lateran/M
Latham/M
Latin/MRS
Latina
Latino/SM
Latinx
Latisha/M
Latonya/M
Latoya/M
Latrobe/M
Latvia/M
Latvian/MS
Laud/MR
Lauder/M
Laue/M
Laughton
Launce/M
Laundromat/M
Laura/M
Laurasia/M
Laure/M
Laurel/M
Lauren/SM
Laurence/M
Laurent/M
Lauretta/M
Laurette/M
Lauri/M
Laurie/M
Lauryn/M
Laval/M
Lavern/M
Laverne/M
Lavina/M
Lavinia/M
Lavoisier/M
Lavonne/M
Lawanda/M
Lawrence/M
Lawry/M
Lawson/M
Lawton/M
Layamon/M
Layla/M
Layne/M
Layton/M
Lazar/M
Lazare/M
Lazaro/M
Lazarus/M
Le/SM
Lea/M
Leach/M
Leadbelly/M
Leah/M
Leakey/M
Lean/M
Leander/M
Leandra/M
Leann/M
Leanna/M
Leanne/M
Lear/M
Learjet/M
Leary/M
Leavenworth/M
Lebanese/M
Lebanon/M
Lebesgue/M
Leblanc/M
Leda/M
Lederberg/M
Lee/M
Leeds/M
Leela/M
Leena/M
Leesburg/M
Leese/M
Leeuwenhoek/M
Leeward/M
Left
Legendre/M
Leger/M
Leghorn/M
Lego/M
Legree/M
Lehman/M
Leia/M
Leibniz/M
Leica/M
Leicester/SM
Leiden/M
Leif/M
Leigh/M
Leighton/M
Leila/M
Leipzig/M
Lek/M
Lela/M
Leland/M
Lelia/M
Lem/M
Lemaitre/M
Lemuel/M
Lemuria/M
Len/M
Lena/M
Lenard/M
Lenin/M
Leningrad/M
Leninism/M
Leninist/M
Lennard/M
Lennie/M
Lennon/M
Lenny/M
Leno/M
Lenoir/M
Lenora/M
Lenore/M
Lent/SMN
Lenten/M
Leo/SM
Leola/M
Leominster/M
Leon/M
Leona/M
Leonard/M
Leonardo/M
Leoncavallo/M
Leone/M
Leonel/M
Leonhard/M
Leonid/M
Leonidas/M
Leonie/M
Leonor/M
Leonora/M
Leonore/M
Leontine/M
Leopold/M
Leopoldo/M
Leora/M
Lepidus/M
Lepke/M
Lepus/M
Lerner/M
Leroi/M
Leroy/M
Les/M
Lesa/M
Lesley/M
Leslie/M
Lesotho/M
Lesseps/M
Lessie/M
Lester/M
Lestrade/M
Leta/M
Letha/M
Lethe/M
Leticia/M
Letitia/M
Letizia/M
Letterman/M
Lettie/M
Letty/M
Lev
Levant/M
Levesque/M
Levey/M
Levi/SM
Leviathan/M
Levin/M
Levine/M
Leviticus/M
Levitt/M
Levon/M
Levy/M
Lew/M
Lewes
Lewinsky/M
Lewis/M
Lewiston/M
Lewisville/M
Lexi/M
Lexie/M
Lexington/M
LexisNexis/M
Lexus/M
Lexy/M
Leyla/M
Lhasa/MS
Lhotse/M
Li/MY
Lia/M
Liam/M
Lian/M
Liana/M
Liane/M
Lianne/M
Liaoning/M
Libbey/M
Libbie/M
Libby/M
Liberace/M
Liberal
Liberia/M
Liberian/SM
Libra/MS
LibreOffice/M
Libreville/M
Librium/M
Libya/M
Libyan/SM
Lichtenstein/M
Lida/M
Lidia/M
Lie/M
Lieberman/M
Liebfraumilch/M
Liechtenstein/ZMR
Liechtensteiner/M
Lief's
Liege/M
Lieut
Lil/MY
Lila/M
Lilah/M
Lilia/MS
Lilian/M
Liliana/M
Liliane/M
Lilith/M
Liliuokalani/M
Lilla/M
Lille/M
Lilli/M
Lillian/M
Lillie/M
Lilliput/M
Lilliputian/MS
Lilly/M
Lilongwe/M
Lily/M
Lima/M
Limbaugh/M
Limbo
Limburger/M
Limoges/M
Limousin/M
Limpopo/M
Lin/M
Lina/M
Linc/M
Lincoln/MS
Lincolnshire/M
Lind/M
Linda/M
Lindbergh/M
Lindi/M
Lindon/M
Lindsay/M
Lindsey/M
Lindy/M
Linea/M
LinkedIn/M
Linn/M
Linnaeus/M
Linnea/M
Linnell/M
Linotype/M
Linton/M
Linus/M
Linux/MS
Linwood/M
Lionel/M
Lipizzaner/M
Lippi/M
Lippmann/M
Lipscomb/M
Lipton/M
Lisa/M
Lisbeth/M
Lisbon/M
Lise/M
Lisette/M
Lisle/M
Lissa/M
Lissajous/M
Lissy/M
Lister/M
Listerine/M
Liston/M
Liszt/M
Lita/M
Lithuania/M
Lithuanian/MS
Little/M
Litton/M
Liv/M
LiveJournal/M
Livermore/M
Liverpool/M
Liverpudlian/SM
Livia/M
Livingston/M
Livingstone/M
Livonia/M
Livvy/M
Livy/M
Liz/M
Liza/M
Lizabeth/M
Lizbeth/M
Lizette/M
Lizzie/M
Lizzy/M
Ljubljana/M
Llewellyn/M
Lloyd/M
Ln
Loafer/SM
Lobachevsky/M
Lochinvar/M
Locke/M
Lockean/M
Lockheed/M
Lockwood/M
Lodge/M
Lodi/M
Lodovico/M
Lodz/M
Loewe/M
Loewi/M
Loews/M
Logan/M
Logitech/M
Lohengrin/M
Loire/M
Lois/M
Loki/M
Lola/M
Lolita/M
Lollard/M
Lollobrigida/M
Lolly's
Lombard/M
Lombardi/M
Lombardy/M
Lome/M
Lompoc/M
Lon/M
Lona/M
London/MRZ
Londoner/M
Long/M
Longfellow/M
Longmont/M
Longstreet/M
Longueuil
Longview/M
Loni/M
Lonnie/M
Lonny/M
LookSmart/M
Lopez/M
Lora/M
Lorain/M
Loraine/M
Lorant/M
Lord/SM
Lordship/SM
Lorelei/M
Loren/M
Lorena/M
Lorene/M
Lorentz/M
Lorentzian
Lorenz/M
Lorenza/M
Lorenzo/M
Loretta/M
Lorette/M
Lori/M
Loria/M
Lorie/M
Lorin/M
Lorinda/M
Lorna/M
Lorne/M
Lorraine/M
Lorre/M
Lorrie/M
Lory/M
Los
Lot/M
Lothario/SM
Lott/M
Lotta/M
Lotte/M
Lotti/M
Lottie/M
Lotty/M
Lou/M
Louella/M
Louie/M
Louis/M
Louisa/M
Louise/M
Louisiana/M
Louisianan/MS
Louisianian/MS
Louisville/M
Lourdes/M
Louvre/M
Love/M
Lovecraft/M
Lovelace/M
Lovell
Lowe/M
Lowell/M
Lowenbrau/M
Lowery/M
Lowlands
Loy/M
Loyang/M
Loyd/M
Loyola/M
Lr
Lt
Ltd
Lu/M
Luanda/M
Luann/M
Lubavitcher/M
Lubbock/M
Lubumbashi/M
Luca/SM
Lucas/M
Luce/M
Luci/MN
Lucia/M
Lucian/M
Luciana/M
Luciano/M
Lucie/M
Lucien/M
Lucienne/M
Lucifer/M
Lucile/M
Lucille/M
Lucina
Lucinda/M
Lucio/M
Lucite/SM
Lucius/M
Lucknow/M
Lucky's
Lucretia/M
Lucretius/M
Lucy/M
Luddite/MS
Ludhiana/M
Ludovico/M
Ludvig/M
Ludwig/M
Luella/M
Lufthansa/M
Luftwaffe/M
Luger/M
Lugosi/M
Luigi/M
Luis/M
Luisa/M
Luise/M
Lukas/M
Luke/M
Lula/M
Lully/M
Lulu/M
Lumiere/M
Lumi�re/M
Luna/M
Lupe/M
Lupercalia/M
Lupus/M
Lura/M
Luria/M
Lusaka/M
Lusitania/M
Luther/M
Lutheran/SM
Lutheranism/MS
Luvs/M
Luxembourg/ZMR
Luxembourger/M
Luxembourgian
Luz/M
Luzon/M
Lvov/M
Ly/MY
LyX/M
Lyallpur
Lycos/M
Lycra/M
Lycurgus/M
Lyda/M
Lydia/M
Lydian/SM
Lydie/M
Lydon/M
Lyell/M
Lyle/M
Lyly/M
Lyman/M
Lyme/M
Lyn/M
Lynch/M
Lynchburg/M
Lynda/M
Lynde/M
Lyndon/M
Lyndsay/M
Lyndsey/M
Lynette/M
Lynn/M
Lynne/M
Lynnette/M
Lyon/SM
Lyons/M
Lyra/M
Lysenko/M
Lysistrata/M
Lysol/M
Lyssa/M
M/SMGB
MA/M
MASH
MB/M
MBA/M
MC
MCI/M
MD/M
MDF
MDT
ME
MEGO/S
MFA/M
MGM/M
MHz/M
MI/M
MIA
MIDI/M
MIPS
MIRV
MIT/M
MM
MN
MO
MOOC
MP/M
MPEG/SM
MRI/M
MS/M
MSG/M
MST/M
MSW
MT/M
MTF/SM
MTV/M
MVP/M
MW
Maalox/M
Mab
Mabel/M
Mable/M
Mac/M
MacArthur/M
MacBride/M
MacDonald/M
MacLeish/M
Macao/M
Macau/M
Macaulay/M
Macbeth/M
Maccabees
Maccabeus/M
Mace/M
Macedon/M
Macedonia/M
Macedonian/SM
Mach/M
Machiavelli/M
Machiavellian/M
Macias/M
Macintosh/M
Mack/M
Mackenzie/M
Mackinac/M
Mackinaw/M
Macmillan/M
Macon/M
Macromedia/M
Macumba/M
Macy/M
Mada/M
Madagascan/SM
Madagascar/M
Madalyn/M
Madam
Maddalena/M
Madden/M
Maddi/M
Maddie/M
Maddox/M
Maddy/M
Madeira/SM
Madelaine/M
Madeleine/M
Madeline/M
Madelon/M
Madelyn/M
Madera/M
Madge/M
Madison/M
Madonna/SM
Madras/M
Madrid/M
Madurai/M
Mae/M
Maeterlinck/M
Mafia/MS
Mafioso/M
Magda/M
Magdalen
Magdalena/M
Magdalene/M
Magellan/M
Magellanic/M
Maggi/M
Maggie/M
Maggy/M
Maghreb/M
Magi
Maginot/M
Magnificat
Magnitogorsk/M
Magog/M
Magoo/M
Magritte/M
Magsaysay/M
Magus
Magyar/SM
Mahabharata/M
Mahala/M
Mahalia/M
Maharashtra/M
Mahavira/M
Mahayana/M
Mahayanist/M
Mahdi/M
Mahfouz/M
Mahican/SM
Mahler/M
Mahmoud/M
Mahmud/M
Mai/M
Maia/M
Maidenform/M
Maigret/M
Mailer/M
Maillol/M
Maiman/M
Maimonides/M
Maine/MZR
Mainer/M
Mair/M
Maire/M
Maisie/M
Maison/M
Maitreya/M
Maj
Majesty
Major/M
Majorca/M
Majuro/M
Makarios/M
Maker/M
Mal
Mala/M
Malabar/M
Malabo/M
Malacca/M
Malachi/M
Malagasy/M
Malamud/M
Malaprop/M
Malawi/M
Malawian/SM
Malay/MS
Malaya/M
Malayalam/M
Malayan/MS
Malaysia/M
Malaysian/MS
Malcolm/M
Maldive/MS
Maldives/M
Maldivian/MS
Maldonado/M
Male/M
Mali/M
Malia/M
Malian/SM
Malibu/M
Malina/M
Malinda/M
Malinowski/M
Malissa/M
Mallarme/M
Mallarm�/M
Mallomars/M
Mallory/M
Malone/M
Malory/M
Malplaquet/M
Malraux/M
Malta/M
Maltese/M
Malthus/M
Malthusian/SM
Malva/M
Malvin/M
Malvina/M
Mame/M
Mameluke/M
Mamet/M
Mamie/M
Mammon/SM
Mamore/M
Man/M
Managua/M
Manama/M
Manasseh/M
Manchester/M
Manchu/SM
Manchuria/M
Manchurian/M
Mancini/M
Mancunian/MS
Manda/M
Mandalay/M
Mandarin/M
Mandel/M
Mandela/M
Mandelbrot/M
Mandeville/M
Mandi/M
Mandie/M
Mandingo/M
Mandrell/M
Mandy/M
Manet/M
Manfred/M
Manhattan/SM
Mani/M
Manichean/M
Manila/SM
Manitoba/M
Manitoulin/M
Mankato/M
Manley/M
Mann/GM
Mannheim/M
Mannie/M
Manning/M
Manny/M
Mano/M
Manolo/M
Manon/M
Mansfield/M
Manson/M
Manteca/M
Mantegna/M
Mantle/M
Manuel/M
Manuela/M
Manx/M
Manya/M
Mao/M
Maoism/SM
Maoist/SM
Maori/MS
MapQuest/M
Mapplethorpe/M
Maputo/M
Mar/SMN
Mara/M
Maracaibo/M
Marat/M
Maratha/M
Marathi/M
Marathon/M
Marc/M
Marceau/M
Marcel/M
Marcela/M
Marcelino/M
Marcella/M
Marcelle/M
Marcello/M
Marcellus
Marcelo/M
March/MS
Marci/M
Marcia/M
Marciano/M
Marcie/M
Marco/MS
Marconi/M
Marcos/M
Marcus/M
Marcuse
Marcy/M
Marduk/M
Maren/M
Marga/M
Margalit/M
Margaret/M
Margareta/M
Margarete/M
Margaretha/M
Margarethe/M
Margaretta/M
Margarita/M
Margarito/M
Margaux
Marge/M
Margery/M
Marget/M
Margie/M
Margit/M
Margo/M
Margot/M
Margret/M
Margrethe/M
Marguerite/M
Margy/M
Mari/SM
Maria/M
MariaDB/M
Mariam/M
Marian/M
Mariana/SM
Marianas/M
Marianna/M
Marianne/M
Mariano/M
Maribel/M
Maribeth/M
Maricela/M
Marie/M
Mariel/M
Marielle/M
Marietta/M
Mariette/M
Marika/M
Marilee/M
Marilyn/M
Marin/M
Marina/M
Marine/SM
Mario/M
Marion/M
Maris/M
Marisa/M
Mariska/M
Marisol/M
Marissa/M
Marita/M
Maritain/M
Maritza/M
Mariupol
Marius/M
Marj/M
Marja/M
Marjorie/M
Marjory/M
Mark/SM
Markab/M
Markham/M
Markos
Markov/M
Marks/M
Markus/M
Marla/M
Marlboro/M
Marlborough/M
Marlee/M
Marleen/M
Marlena/M
Marlene/M
Marley/M
Marlin/M
Marline/M
Marlo/M
Marlon/M
Marlow/M
Marlowe/M
Marmaduke/M
Marmara/M
Marne/M
Marney/M
Marni/M
Marnie/M
Maronite/M
Marple/M
Marquesas/M
Marquette/M
Marquez/M
Marquis/M
Marquita/M
Marrakesh/M
Marriott/M
Marris/M
Mars/MS
Marsala/M
Marseillaise/MS
Marseilles/M
Marsh/M
Marsha/M
Marshall/M
Marta/M
Martel/M
Martha/M
Marthe/M
Marti/M
Martial/M
Martian/SM
Martie/M
Martin/M
Martina/M
Martinez/M
Martinique/M
Martino/M
Marty/M
Martyn/M
Marv/M
Marva/M
Marvell/M
Marvin/M
Marx/M
Marxian
Marxism/SM
Marxist/SM
Mary/M
Marya/M
Maryann/M
Maryanne/M
Marybeth/M
Maryellen/M
Maryland/MR
Marylander/M
Marylin/M
Marylou/M
Marys
Marysville/M
Masada/M
Masai/M
Masaryk/M
Mascagni/M
Masefield/M
Maserati/M
Maseru/M
Masha/M
Mashhad/M
Mason/MS
Masonic/M
Masonite/M
Mass/MS
Massachusetts/M
Massasoit/M
Massenet/M
Massey/M
Massimiliano/M
Massimo/M
Master/S
MasterCard/M
Masters/M
Mata/M
Mateo/M
MathML/M
Mathe/MR
Mather/M
Matheson/M
Mathew/SM
Mathews/M
Mathewson/M
Mathias/M
Mathilda/M
Mathilde/M
Mathis/M
Matias/M
Matilda/M
Matilde/M
Matisse/M
Matlab/M
Matt/M
Mattel/M
Matteo/M
Matterhorn/M
Matthew/SM
Matthews/M
Matthias/M
Matthieu/M
Matti/M
Mattias/M
Mattie/M
Matty/M
Maud/M
Maude/M
Maudie/M
Maugham/M
Maui/M
Mauldin/M
Maupassant/M
Maura/M
Maureen/M
Mauriac/M
Maurice/M
Mauricio/M
Maurie/M
Maurine/M
Mauritania/M
Mauritanian/SM
Mauritian/SM
Mauritius/M
Maurits/M
Maurizio/M
Mauro/M
Maurois/M
Maury
Mauryan/M
Mauser/M
Mavis/M
Max/M
Maxie/M
Maximilian/M
Maximilien/M
Maximo/M
Maxine/M
Maxwell/M
May/SMR
Maya/SM
Mayan/MS
Maybelle/M
Maye/M
Mayer/M
Mayfair/M
Mayflower/M
Maynard/M
Mayne/M
Mayo/M
Maypole
Mayra/M
Mays/M
Maytag/M
Mazama/M
Mazarin/M
Mazatlan/M
Mazda/M
Mazola/M
Mazzini/M
Mb/M
Mbabane/M
Mbini/M
Mbps
McAdam/M
McAfee/M
McAllen/M
McBride/M
McCain/M
McCall/M
McCann/M
McCarthy/M
McCarthyism/M
McCartney/M
McCarty/M
McClain/M
McClellan/M
McClure/M
McConnell/M
McCormick/M
McCoy/M
McCray/M
McCullough/M
McDaniel/M
McDonald/M
McDonnell/M
McDowell/M
McEnroe/M
McFadden/M
McFarland/M
McGee/M
McGovern/M
McGowan/M
McGuffey/M
McGuire/M
McHenry/M
McIntosh/M
McIntyre/M
McJob
McKay/M
McKee/M
McKenzie/M
McKinley/M
McKinney/M
McKnight/M
McLaughlin/M
McLean/M
McLeod/M
McLuhan/M
McMahon/M
McMillan/M
McNamara/M
McNaughton/M
McNeil/M
McPherson/M
McQueen/M
McVeigh/M
Md/M
Me
Mead/M
Meade/M
Meadows/M
Meagan/M
Meaghan/M
Meany/M
Meara/M
Mecca/MS
Medan/M
Medea/M
Medellin/M
Medford/M
Media/M
Medicaid/SM
Medicare/SM
Medici/M
Medina/M
Mediterranean/MS
Medline/M
Medusa/M
Meg/M
Megan/M
Meggie/M
Meghan/M
Mei/MR
Meier/M
Meighen/M
Meiji/M
Meir/M
Mejia/M
Mekong/M
Mel/MY
Mela/M
Melanesia/M
Melanesian/M
Melania/M
Melanie/M
Melba/M
Melbourne/M
Melchior/M
Melchizedek/M
Melendez/M
Melicent/M
Melina/M
Melinda/M
Melisa/M
Melisande/M
Melissa/M
Melita/M
Mella/M
Melli/M
Mellie/M
Mellon/M
Melly/M
Melodie/M
Melody/M
Melony/M
Melpomene/M
Melton/M
Melva/M
Melville/M
Melvin/M
Melvyn/M
Memcached/M
Memling/M
Memphis/M
Menander/M
Menard/M
Mencius/M
Mencken/M
Mendel/M
Mendeleev/M
Mendelian/M
Mendelssohn/M
Mendez/M
Mendocino/M
Mendoza/M
Menelaus/M
Menelik/M
Menes/M
Mengzi
Menifee/M
Menkalinan/M
Menkar/M
Menkent/M
Mennen/M
Mennonite/MS
Menominee/M
Menotti/M
Mensa/M
Mentholatum/M
Menuhin/M
Menzies/M
Mephisto
Mephistopheles/M
Merak/M
Mercado/M
Mercator/M
Merced/M
Mercedes/M
Mercer/M
Merci/M
Mercia/M
Merck/M
Mercurochrome/M
Mercury/SM
Meredith/M
Meriel/M
Merino/M
Merl/M
Merle/M
Merlin/M
Merlot/M
Merovingian/M
Merriam/M
Merrick/M
Merrie/M
Merrill/M
Merrily's
Merrimack/M
Merritt/M
Merry's
Mersey
Merthiolate/M
Merton/M
Merv/M
Mervin/M
Merwin/M
Merwyn/M
Meryl/M
Mesa/M
Mesabi/M
Meshed/M
Mesmer/M
Mesolithic/M
Mesopotamia/M
Mesopotamian
Mesozoic/M
Messerschmidt/M
Messiaen/M
Messiah/M
Messiahs
Messianic
Messieurs
Metacafe/M
Metallica/M
Metamucil/M
Methodism/SM
Methodist/SM
Methuselah/M
Metternich/M
Meuse/M
Mex
Mexicali/M
Mexican/MS
Mexico/M
Meyer/MS
Meyerbeer/M
Meyers/M
Mfume/M
Mg/M
Mgr
MiG/M
Mia/M
Miami/MS
Miaplacidus/M
Micaela/M
Micah/M
Micawber/M
Mich/M
Michael/M
Michaela/M
Michaelmas/MS
Michail/M
Michal/M
Micheal/M
Michel/M
Michelangelo/M
Michele/M
Michelin/M
Micheline/M
Michell/M
Michelle/M
Michelob/M
Michelson/M
Michigan/M
Michigander/MS
Michiganite
Mick/M
Mickey/M
Micki/M
Mickie/M
Micky/M
Micmac/SM
Micronesia/M
Micronesian/M
Microsoft/M
Midas/M
Middleton/M
Middletown/M
Mideast
Mideastern
Midland/MS
Midway/M
Midwest/M
Midwestern/MR
Mignon/M
Miguel/M
Mikael/M
Mike/M
Mikel/M
Mikey/M
Mikhail/M
Mikkel/M
Mikoyan/M
Milagros/M
Milan/M
Milanese
Mildred/M
Milena/M
Miles/M
Milford/M
Milken/M
Mill/SMR
Millard/M
Millay/M
Miller/M
Millet/M
Milli/M
Millicent/M
Millie/M
Millikan/M
Mills/M
Milly/M
Milne/M
Milo/M
Milosevic/M
Milquetoast/M
Miltiades/M
Milton/M
Miltonian
Miltonic/M
Miltown/M
Milwaukee/M
Mimi/M
Mimosa/M
Min/M
Mina/M
Minamoto/M
Minda/M
Mindanao/M
Mindoro/M
Mindy/M
Minerva/M
Minette/M
Ming/M
Mingus/M
Minn
Minna
Minne/M
Minneapolis/M
Minnelli/M
Minnesota/M
Minnesotan/SM
Minnie/M
Minny/M
Minoan/MS
Minolta/M
Minos/M
Minot/M
Minotaur/M
Minsk/M
Minsky/M
Minta/M
Mintaka/M
Minuit/M
Minuteman/M
Miocene/M
Mir/M
Mira/M
Mirabeau/M
Mirabel/M
Mirabella/M
Mirabelle/M
Mirach/M
Miran/M
Miranda/M
Mireille/M
Mirella/M
Mirfak/M
Miriam/M
Mirna/M
Miro/M
Mirzam/M
Mischa/M
Misha/M
Miskito/M
Miss
Missie/M
Mississauga/M
Mississippi/M
Mississippian/SM
Missoula/M
Missouri/M
Missourian/MS
Missy/M
Mistassini/M
Mister
Mistress
Misty/M
Mitch/M
Mitchel/M
Mitchell/M
Mitford/M
Mithra/M
Mithridates/M
Mitsubishi/M
Mitterrand/M
Mitty/M
Mitzi/M
Mixtec/M
Mizar/M
Mk
Mlle
Mme/S
Mn/M
Mnemosyne/M
Mo/M
Mobil/M
Mobile/M
Mobutu/M
Modesto/M
Modigliani/M
Moe/M
Moet/M
Mogadishu/M
Mogul/MS
Mohacs/M
Mohamed/M
Mohammad/M
Mohammedan/SM
Mohammedanism/SM
Mohandas/M
Mohave/SM
Mohawk/SM
Mohegan
Moho/M
Mohorovicic/M
Moira/M
Moise/MS
Moises/M
Moiseyev/M
Moishe/M
Mojave/SM
Moldavia/M
Moldavian
Moldova/M
Moldovan
Moliere/M
Molina/M
Moll/M
Mollie/M
Molly/M
Molnar/M
Moloch/M
Molokai/M
Molotov/M
Moluccas/M
Mombasa/M
Mon/SM
Mona/M
Monacan
Monaco/M
Mondale/M
Monday/SM
Mondrian/M
Monegasque/SM
Monera/M
Monessen/M
Monet/M
MongoDB/M
Mongol/SM
Mongolia/M
Mongolian/SM
Mongolic/M
Mongolica/M
Mongoloid
Monica/M
Monika/M
Monique/M
Monk/M
Monmouth/M
Monongahela/M
Monro/M
Monroe/M
Monrovia/M
Monsanto/M
Monsieur/M
Monsignor/SM
Mont/M
Montague/M
Montaigne/M
Montana/M
Montanan/SM
Montcalm/M
Monte/M
Montenegrin/M
Montenegro/M
Monterey/M
Monterrey/M
Montesquieu/M
Montessori/M
Monteverdi/M
Montevideo/M
Montezuma/M
Montgolfier/M
Montgomery/M
Monti/M
Monticello/M
Montoya/M
Montpelier/M
Montrachet/M
Montreal/M
Montserrat/M
Monty/M
Moody/M
Moog/M
Moon/M
Mooney/M
Moor/SM
Moore/M
Moorish/M
Mora/M
Morales/M
Moran/M
Moravia/M
Moravian/M
Mord/M
Mordecai
Mordred/M
More/M
Morena/M
Moreno/M
Morey/M
Morgan/SM
Morgana/M
Morgantown/M
Morgen/M
Moria/M
Moriarty/M
Morin/M
Morison/M
Morita/M
Moritz/M
Morley/M
Mormon/SM
Mormonism/SM
Morna/M
Moro/M
Moroccan/SM
Morocco/M
Moroni/M
Morpheus/M
Morphy/M
Morrie/M
Morris/M
Morrison/M
Morristown/M
Morrow/M
Morse/M
Mort/MN
Morten/M
Mortimer/M
Morton/M
Morty/M
Mosaic/M
Moscow/M
Mose/SM
Moseley/M
Moselle/M
Moses/M
Moshe/M
Moslem/M
Mosley/M
Moss/M
Mosul/M
Motorola/M
Motown/M
Motrin/M
Mott/M
Moulton/M
Mount/M
Mountbatten/M
Mountie/MS
Moussorgsky/M
Mouthe/M
Mouton/M
Mowgli/M
Moyra/M
Mozambican/SM
Mozambique/M
Mozart/M
Mozilla/M
Mozillian/MS
Mr/SM
Ms/S
Msgr
Mt
Muawiya/M
Mubarak/M
Mueller/M
Muenster/MS
Mugabe/M
Muhammad/M
Muhammadan/MS
Muhammadanism/SM
Muir/M
Mujib/M
Mulder/M
Mullen/M
Muller/M
Mulligan/M
Mullikan/M
Mullins/M
Mulroney/M
Multan/M
Multics
Mumbai/M
Mumford/M
Munch/M
Munchausen/M
Muncie/M
Munich/M
Munoz/M
Munro/M
Munroe/M
Munster/M
Muppet/M
Murasaki/M
Murat/M
Murchison/M
Murcia
Murdoch/M
Murdock/M
Murfreesboro/M
Muriel/M
Murillo/M
Murine/M
Murmansk/M
Murphy/M
Murray/M
Murrieta/M
Murrow/M
Murrumbidgee/M
Murry/M
Muscat/M
Muscovite/M
Muscovy/M
Muse/M
Musharraf/M
Musial/M
Muskegon/M
Muskogee/M
Muslim/MS
Mussolini/M
Mussorgsky/M
Mutsuhito/M
Muzak/M
My's
MySQL/M
MySpace/M
MySpell/M
Myanmar/M
Mycenae/M
Mycenaean/M
Myer/SM
Myers/M
Mylar/MS
Myles/M
Myra/M
Myrdal/M
Myriam/M
Myrna/M
Myron/M
Myrtle/M
Mysore/M
Myst/M
M�nchhausen/M
N'Djamena
N/MD
NAACP/M
NAFTA/M
NASA/M
NASCAR/M
NASDAQ/M
NATO/M
NB
NBA/M
NBC/M
NBS
NC
NCAA/M
NCO
ND
NE/M
NEH
NF
NFC
NFL/M
NGO/SM
NH
NHL/M
NIH
NIMBY
NJ
NLRB
NM
NORAD/M
NOW
NP
NPR/M
NR
NRA
NRC
NS
NSA/M
NSC
NSF
NSFW
NSPR/M
NSS/M
NT
NV
NVIDIA/M
NW/M
NWT
NY
NYC
NYSE
NZ
Na/M
Nabisco/M
Nabokov/M
Nada/M
Nader/M
Nadia/M
Nadine/M
Nadu/M
Nadya/M
Nagasaki/M
Nagoya/M
Nagpur/M
Nagy/M
Nahuatl/MS
Nahum/M
Naipaul/M
Nair/M
Nairobi/M
Naismith/M
Nam/M
Namath/M
Namibia/M
Namibian/MS
Nampa/M
Nan/M
Nana/M
Nanak/M
Nance/M
Nanchang/M
Nanci/M
Nancy/M
Nanette/M
Nani/M
Nanjing/M
Nanni/M
Nannie/M
Nanon/M
Nanook/M
Nansen/M
Nantes/M
Nantucket/M
Naomi/M
Napa/M
Naphtali/M
Napier/M
Naples/M
Napoleon/MS
Napoleonic/M
Napster/M
Nara
Narcissus/M
Nari/M
Narmada/M
Narnia/M
Narraganset
Narragansett/M
Naruto/M
Nash/M
Nashua/M
Nashville/M
Nassau/M
Nasser/M
Nat/M
Nata/M
Natal's
Natale/M
Natalia/M
Natalie/M
Natalya/M
Natasha/M
Natchez/M
Nate/MN
Nathalie/M
Nathan/SM
Nathanael
Nathanial/M
Nathaniel/M
Nathans/M
Nation/M
Nationwide/M
Natividad/M
Nativity/M
Natty's
Nature
Naugahyde/M
Nauru/M
Nautilus/M
Navajo/SM
Navajoes
Navarre/M
Navarro/M
Navratilova/M
Navy
Nazarene/M
Nazareth/M
Nazca/M
Nazi/SM
Nazism/MS
Nb/M
Nd/M
Ndjamena/M
Ne/M
NeWS
NeWSes
Neal/M
Neale/M
Nealy/M
Neanderthal/SM
Neapolitan/M
Neb
Nebr
Nebraska/M
Nebraskan/MS
Nebuchadnezzar/M
Necko/M
Ned/M
Neda/M
Nedda/M
Neddy/M
Nederland/SM
Neel/M
Neely/M
Nefertiti/M
Negev/M
Negress/MS
Negritude
Negro/MS
Negroes
Negroid/SM
Negros/M
Nehemiah/M
Nehru/M
Neil/SM
Neill/M
Nelda/M
Nell/M
Nelle/M
Nelli/M
Nellie/M
Nelly/M
Nels/N
Nelsen/M
Nelson/M
Nembutal/M
Nemesis/M
Neo/M
Neogene/M
Neolithic
Nepal/M
Nepalese/M
Nepali/MS
Neptune/M
Nereid/M
Nerf/M
Nerissa/M
Nero/M
Neruda/M
Nescafe/M
Nessa/M
Nesselrode/M
Nessie/M
Nesta/M
Nester/M
Nestle/M
Nestor/M
Nestorius/M
NetBSD/M
Netflix/M
Netherlander/SM
Netherlands/M
Netscape/M
Netta/M
Nettie/M
Netzahualcoyotl/M
Nev/M
Neva/M
Nevada/M
Nevadan/SM
Nevadian
Nevil/M
Nevile/M
Neville/M
Nevin/MS
Nevis/M
Nevsky/M
Newark/M
Newburgh/M
Newcastle/M
Newfoundland/MRS
Newman/M
Newport/M
Newsweek/M
Newton/M
Newtonian/M
NexTag/M
Nexis/M
Nextel/M
Ngaliema/M
Nguyen/M
Ni/M
Niagara/M
Nial/M
Niall/M
Niamey/M
Nibelung/M
Nicaea/M
Nicaragua/M
Nicaraguan/SM
Niccolo/M
Nice/M
Nicene/M
Nicephori/M
Nichiren/M
Nichol/SM
Nicholas/M
Nichole/M
Nichols/M
Nicholson/M
Nick/M
Nickelodeon/M
Nicki/M
Nickie/M
Nicklaus/M
Nicko/M
Nickolas/M
Nicky/M
Nico/M
Nicobar/M
Nicodemus/M
Nicol/M
Nicola/SM
Nicolai
Nicolas/M
Nicole/M
Nicolette/M
Nicolis
Nicolle/M
Nicosia/M
Niebuhr/M
Niel/SM
Nielsen/M
Nietzsche/M
Nieves/M
Nigel/M
Niger/M
Nigeria/M
Nigerian/MS
Nigeriana/M
Nigerien/M
Nightingale/M
Nijinsky/M
Nike/M
Niki/M
Nikita/M
Nikkei/M
Nikki/M
Niko/SM
Nikola/SM
Nikolai/M
Nikolaos/M
Nikolaus/M
Nikolayev/M
Nikon/M
Nile/SM
Nils
Nilson/M
Nimitz/M
Nimrod/M
Nina/M
Ninette/M
Nineveh/M
Ninon/M
Nintendo/M
Niobe/M
Nippon/M
Nipponese/M
Nirenberg/M
Nirvana/M
Nisan/M
Nisei/M
Nissan/M
Nita/M
Niue/M
Nivea/M
Niven/M
Nixon/M
Nkrumah/M
No/SM
NoDoz/M
Noah/M
Noam/M
Nobel/M
Nobelist/MS
Noble/M
Noe/M
Noel/SM
Noelle/M
Noemi/M
Nokia/M
Nola/M
Nolan/M
Noland/M
Noll/M
Nome/M
Nomi/M
Nona/M
Noni/M
Nonie/M
Nonna/M
Nootka/M
Nora/M
Norah/M
Norbert/M
Norberto/M
Nordic/MS
Noreen/M
Norfolk/M
Noriega/M
Norma/M
Normal/M
Norman/MS
Normand/M
Normandy/M
Norplant/M
Norrie/M
Norris/M
Norse/M
Norseman/M
Norsemen/M
Nortel/M
North/M
Northampton/M
Northeast/MS
Northern/MR
Northerner/M
Northrop/M
Northrup/M
Norths
Northwest/SM
Norton/M
Norw
Norway/M
Norwegian/SM
Norwich/M
Nosferatu/M
Nostradamus/M
Notre
Nottingham/M
Nouakchott/M
Noumea/M
Nov/M
Nova/M
Novartis/M
November/MS
Novgorod/M
Novocain/MS
Novocaine
Novokuznetsk/M
Novosibirsk/M
Nowell/M
Noxzema/M
Noyce/M
Noyes/M
Np/M
Nubia/M
Nubian/M
Nukualofa/M
Numbers/M
Nunavut/M
Nunez/M
Nunki/M
Nuremberg/M
Nureyev/M
NutraSweet/M
NyQuil/M
Nyasa/M
Nye/M
Nyerere/M
Nyssa/M
O'Brien/M
O'Casey/M
O'Connell/M
O'Connor/M
O'Donnell/M
O'Hara/M
O'Higgins/M
O'Keeffe/M
O'Neil/M
O'Neill/M
O'Rourke/M
O'Toole/M
O/SM
OAS/M
OB
OCR
OD/SM
OE
OED
OH
OHSA/M
OJ
OK/SMDG
OMB/M
OMG
ON
OPEC/M
OR
OS/M
OSHA/M
OSes
OT
OTB
OTC
OTOH
Oahu/M
Oakland/M
Oakley/M
Oates/M
Oaxaca/M
Ob/MD
Obadiah/M
Obama/M
Obamacare
Obed/M
Oberlin/M
Oberon/M
Obie
Ocala/M
Ocaml/M
Occam/M
Occident
Occidental/MS
Oceania/M
Oceanside
Oceanus/M
Ochoa/M
Oct/M
Octavia/M
Octavian/M
Octavio/M
Octavius/M
October/SM
Odell/M
Oder/M
Odessa/M
Odets/M
Odette/M
Odie/M
Odin/M
Odis/M
Odo/M
Odom/M
Ody/M
Odysseus/M
Odyssey/M
Oedipal/M
Oedipus/M
Oersted/M
Ofelia/M
Offenbach/M
OfficeMax/M
Ogbomosho/M
Ogden/M
Ogilvy/M
Oglethorpe/M
Ohio/M
Ohioan/SM
Oise/M
Ojibwa/SM
Okayama
Okeechobee/M
Okefenokee/M
Okhotsk/M
Okinawa/M
Okinawan
Okla
Oklahoma/M
Oklahoman/M
Oktoberfest/M
Ola/M
Olaf/M
Olajuwon/M
Olav/M
Oldenburg/M
Oldfield/M
Oldsmobile/M
Olduvai/M
Olen/M
Olenek/M
Olga/M
Oligocene/M
Olimpia/M
Olin/M
Olive/MR
Oliver/M
Olivetti/M
Olivia/M
Olivier/M
Ollie/M
Olly/M
Olmec/M
Olmsted/M
Olsen/M
Olson/M
Olwen/M
Olympe/M
Olympia/SM
Olympiad/MS
Olympian/MS
Olympic/SM
Olympics/M
Olympus/M
Omaha/MS
Oman/M
Omani/MS
Omar/M
Omayyad/M
Omdurman/M
Omnipotent
Omsk/M
Onassis/M
Oneal/M
Onega/M
Onegin/M
Oneida/MS
Onion/M
Ono/M
Onondaga/MS
Onsager/M
Ont
Ontarian
Ontario/M
Oona/M
Oort/M
Opal/M
Opaline/M
Opel/M
OpenBSD/M
OpenOffice/M
Ophelia/M
Ophiuchus/M
Oppenheimer/M
Opposition
Oprah/M
Ora/M
Oracle/M
Oran/M
Orange/M
Oranjestad/M
Orazio/M
Orbison/M
Ordovician/M
Ore/N
Oreg
Oregon/M
Oregonian/SM
Orel
Orem/M
Oren/M
Oreo/M
Orestes/M
Oriana/M
Orient/M
Oriental/MS
Orientalism
Orin/M
Orinoco/M
Orion/M
Oriya/M
Orizaba/M
Orkney/M
Orlan/M
Orland/M
Orlando/M
Orleans/M
Orlon/MS
Orly/M
Orpheus/M
Orphic/M
Orr/MN
Orren/M
Orrin/M
Orson/M
Ortega/M
Orthodox
Ortiz/M
Orton/M
Orv/M
Orval/M
Orville/M
Orwell/M
Orwellian/M
Oryza/M
Os/M
Osage/MS
Osaka/M
Osbert/M
Osborn/M
Osborne/M
Osbourne/M
Oscar/MS
Osceola/M
Osgood/M
Oshawa/M
Oshkosh/M
Osiris/M
Oslo/M
Osman/M
Osmond/M
Osmund/M
Ossie/M
Ostrogoth/M
Ostwald/M
Osvaldo/M
Oswald/M
Othello/M
Otho/M
Otis/M
Ottawa/SM
Ottilie/M
Otto/M
Ottoman/M
Ottomana/M
Ouagadougou/M
Ouija/MS
Ovid/M
Owen/SM
Owens/M
Owensboro/M
Oxford/SM
Oxley/M
Oxnard/M
Oxonian/M
Oxus/M
Oxycontin/M
Oz/M
Ozark/MS
Ozarks/M
Ozymandias/M
Ozzie/M
Ozzy/M
P/MN
PA/M
PAC/M
PARC/S
PASCAL
PBS/M
PBX
PC/SM
PCB
PCMCIA
PCP/M
PD
PDA/SM
PDF/SM
PDQ
PDT
PE
PET/M
PFC
PG
PGP
PHP/M
PIN
PJ's
PLO/M
PM/SMDG
PMS/M
PNG/SM
PO
POTUS/M
POW/M
PP
PPS
PR
PRC/M
PRNewswire/M
PRO
PS/M
PST/M
PT
PTA/M
PTO
PVC/M
PW
PX
Pa/M
Paar/M
Pablo/M
Pablum/M
Pabst/M
Pace/M
Pacheco/M
Pacific/M
Pacino/M
Packard/M
Paco/M
Padang
Paderewski/M
Padgett/M
Padilla/M
Padraic/M
Padraig/M
Paganini/M
Page/M
Paglia/M
Pahlavi/M
Paige/M
Paine/M
Paiute/SM
Pakistan/M
Pakistani/SM
Palau/M
Palembang/M
Paleocene/M
Paleogene/M
Paleolithic/M
Paleozoic/M
Palermo/M
Palestine/M
Palestinian/SM
Palestrina/M
Paley/M
Palikir/M
Palin/M
Palisades/M
Palladio/M
Palmdale/M
Palmer/M
Palmerston/M
Palmolive/M
Palmyra/M
Paloma/M
Palomar/M
Pam/M
Pamela/M
Pamirs/M
Pampers/M
Pan/M
Panama/SM
Panamanian/MS
Panasonic/M
Pancho/M
Pandora/M
Pangaea/M
Pankhurst/M
Panmunjom/M
Pansy/M
Pantagruel/M
Pantaloon/M
Pantheon/M
Panza/M
Paola/M
Paolina/M
Paolo/M
Papageno/M
Papua/M
Paracelsus/M
Paraclete/M
Paradise
Paraguay/M
Paraguayan/MS
Paralympic/S
Paramaribo/M
Paramount/M
Parana/M
Paran�/M
Parcheesi/M
Pareto/M
Paris/M
Parisian/MS
Park/SMR
Parke/M
Parker/M
Parkersburg/M
Parkinson/M
Parkinsonism
Parkman/M
Parks/M
Parliament/M
Parmenides
Parmesan/MS
Parnassus/MS
Parnell/M
Parr/M
Parrish/M
Parsee/SM
Parsi/MS
Parsifal/M
Parsons/M
Parthenon/M
Parthia/M
Pasadena/M
Pascagoula/M
Pascal/SM
Pascale/M
Pasco/M
Pasquale/M
Passion/SM
Passover/MS
Pasternak/M
Pasteur/M
Pat/MN
Patagonia/M
Patagonian/M
Pate/M
Patel/M
Paten/M
Paterson/M
Patna/M
Paton
Patric/M
Patrica/M
Patrice/M
Patricia/M
Patricio/M
Patrick/M
Patrizia/M
Patsy/M
Patten/M
Patterson/M
Patti/M
Pattie/M
Patton/M
Patty/M
Paul/GM
Paula/M
Paule/M
Paulette/M
Pauli/M
Paulie/M
Paulina/M
Pauline/M
Pauling/M
Paulo/M
Pauly/M
Pavarotti/M
Pavel/M
Pavia/M
Pavlov/M
Pavlova/M
Pavlovian/M
Pawnee/SM
Paxton
PayPal/M
Payne/M
Payton/M
Pb/M
Pd/M
Peabody/M
Peace/M
Peadar/M
Peale/M
Pearce/M
Pearl/M
Pearle/M
Pearlie/M
Pearson/M
Peary/M
Pechora/M
Peck/M
Peckinpah/M
Pecos/M
Peder/M
Pedro/M
Peel/M
Peg/M
Pegasus/MS
Pegeen/M
Peggy/M
Pei/M
Peiping/M
Peirce/M
Pekinese/M
Peking/SM
Pekingese/SM
Pele/M
Pelee/M
Peloponnese/M
Pembroke/M
Pen/M
Pena/M
Penderecki/M
Penelope/M
Penn/M
Penna
Penney/M
Pennington/M
Pennsylvania/M
Pennsylvanian/MS
Penny/M
Pennzoil/M
Penrod/M
Pensacola/M
Pentagon/M
Pentateuch/M
Pentax/M
Pentecost/SM
Pentecostal/MS
Pentecostalism
Pentium/SM
Peoria/M
Pepe/M
Pepi/M
Pepin/M
Pepita/M
Pepsi/M
Pepys/M
Pequot/M
Perceval
Percheron/M
Percival/M
Percy/M
Perelman/M
Perez/M
Peri/M
Periclean/M
Pericles/M
Perkin/MS
Perkins/M
Perl/SM
Perla/M
Perle/M
Perm/M
Permalloy/M
Permian/M
Pernod/M
Peron/M
Perot/M
Perri/MR
Perrier/M
Perrine/M
Perry/M
Perseid/M
Persephone/M
Persepolis/M
Perseus/M
Pershing/M
Persia/M
Persian/SM
Persis
Perth/M
Peru/M
Peruvian/MS
Peshawar/M
Peta/M
Petain/M
Petaluma/M
Pete/RMZ
Peter/M
Peterborough/M
Peters/MN
Petersen/M
Peterson/M
Petey/M
Petr/M
Petra/M
Petrarch/M
Petronella/M
Petronilla/M
Petty/M
Peugeot/M
Peyronie's
Peyton/M
Pfc
Pfizer/M
PhD/M
Phaedra/M
Phaethon/M
Phanerozoic/M
Pharaoh/M
Pharaohs
Pharisaic
Pharisaical
Pharisee/MS
Phebe
Phekda/M
Phelps/M
Phidias/M
Phil/MY
Philadelphia/M
Philby/M
Philemon/M
Philip/MS
Philippa/M
Philippe/M
Philippians/M
Philippine/SM
Philippines/M
Philips/M
Philistine/M
Phillida/M
Phillip/SM
Phillipa/M
Phillipe/M
Phillips/M
Phillis/M
Philly/M
Philomena/M
Phineas/M
Phipps/M
Phobos/M
Phoebe/M
Phoenicia/M
Phoenician/SM
Phoenix/M
Photoshop/M
Photostat/MS
Photostatted
Photostatting
Phrygia/M
Phyllida/M
Phyllis/M
Pia/M
Piaf/M
Piaget/M
Pianola/M
Picasso/M
Piccadilly/M
Pickering/M
Pickett/M
Pickford/M
Pickwick/M
Pict/M
Pictor
Piedmont/M
Pierce/M
Pierre/M
Pierrette/M
Pierrot/M
Pierson/M
Pieter/M
Pietra/M
Pietro/M
Pike/M
Pilate/MS
Pilates/M
Pilcomayo/M
Pilgrim/SM
Pillsbury/M
Pinatubo/M
Pinchas/M
Pincus/M
Pindar/M
Pinkerton/M
Pinocchio/M
Pinochet/M
Pinter/M
Pinyin
Piotr/M
Pippa/M
Pippin/M
Piraeus/M
Pirandello/M
Pisa/M
Pisces/M
Pisistratus/M
Pissaro/M
Pitcairn/M
Pitt/SM
Pittman/M
Pitts/M
Pittsburgh/M
Pittsfield/M
Pius/M
Pixar/M
Pizarro/M
Pkwy
Pl
Place
Planck/M
Plano
Plantagenet/M
Plasticine/M
Plataea/M
Plath/M
Plato/M
Platonic
Platonism/M
Platonist/M
Platte/M
Plautus/M
PlayStation/M
Playboy/M
Playtex/M
Pleiades/M
Pleistocene/M
Plexiglas/MS
Pliny/M
Pliocene/SM
Plutarch/M
Pluto/M
Plymouth/M
Pm/M
Po/M
Pocahontas/M
Pocatello/M
Pocono/SM
Poconos/M
Podgorica/M
Podhoretz/M
Podunk/M
Poe/M
Pogo/M
Poincare/M
Poincar�/M
Poiret/M
Poirot/M
Poisson/M
Poitier/M
Pokemon/M
Pok�mon/M
Pol/MY
Poland/M
Polanski/M
Polaris/M
Polaroid/MS
Pole/SM
Polish/M
Politburo/M
Polk/M
Pollard/M
Pollock/M
Pollux/M
Polly/M
Pollyanna/M
Polo/M
Polska
Poltava/M
Polyhymnia/M
Polynesia/M
Polynesian/MS
Polyphemus/M
Pomerania/M
Pomeranian/M
Pomona/M
Pompadour/M
Pompeian
Pompeii/M
Pompey/M
Ponce/M
Pontchartrain/M
Pontiac/M
Pontianak/M
Pooh/M
Poole/M
Poona/M
Pope/M
Popeye/M
Popocatepetl/M
Popper/M
Poppins/M
Popsicle/M
Porfirio/M
Porrima/M
Porsche/M
Port/MR
Porter/M
Porterville/M
Portia/M
Portland/M
Porto/M
Portsmouth/M
Portugal/M
Portuguese/M
Poseidon/M
Post/M
PostScript/M
PostgreSQL/M
Potemkin/M
Potomac/M
Potsdam/M
Pottawatomie/M
Potter/M
Potts/M
Pottstown/M
Poughkeepsie/M
Poul/M
Pound/M
Poussin/M
Powell/M
PowerPC/M
PowerPoint/M
Powers/M
Powhatan/M
Poynter/M
Poznan/M
Pr/M
Prada/M
Prado/M
Praetorian/M
Praetoriana/M
Prague/M
Praia/M
Prakrit/M
Pratchett/M
Pratt/M
Pravda/M
Praxiteles/M
Preakness/M
Precambrian/M
Preminger/M
Premyslid/M
Prensa/M
Prentice/M
Prentiss/M
Pres
Presbyterian/SM
Presbyterianism/MS
Prescott/M
Presley/M
Preston/M
Pretoria/M
Priam/M
Pribilof/M
Price/M
Priceline/M
Priestley/M
Prince/M
Princeton/M
Principe/M
Prinz
Pris
Prisca/M
Priscilla/M
Prius/M
Private
Prix
ProQuest/M
Procrustean/M
Procrustes/M
Procter/M
Procyon/M
Prof
Prohibition
Prokofiev/M
Promethean/M
Prometheus/M
Prophets
Proserpina/M
Proserpine/M
Protagoras/M
Proterozoic/M
Protestant/MS
Protestantism/SM
Proteus/M
Proudhon/M
Proust/M
Provencal/MS
Provence/M
Proven�al/M
Proverbs
Providence/SM
Provo/M
Prozac/MS
Pru/M
Prudence/M
Prudential/M
Prudy/M
Prue/M
Pruitt/M
Prussia/M
Prussian/MS
Prut/M
Pryce/M
Pryor/M
Psalms/M
Psalter/MS
Pseudomonas/M
Psyche/M
Pt/M
Ptah/M
Ptolemaic/M
Ptolemy/SM
Pu/M
PubMed/M
Puccini/M
Puck/M
Puckett/M
Puebla/M
Pueblo/M
Puerto
Puget/M
Pugh/M
Pulaski/M
Pulitzer/M
Pullman/MS
Punch/M
Punic/M
Punjab/M
Punjabi/M
Purana/M
Purcell/M
Purdue/M
Purgatory
Purim/MS
Purina/M
Puritan/M
Puritanism/MS
Purus/M
Pusan/M
Pusey/M
Pushkin/M
Pushtu/M
Putin/M
Putnam/M
Puzo/M
Pvt
PyTorch/M
Pygmalion/M
Pygmy/SM
Pyle/M
Pym/M
Pynchon/M
Pyongyang/M
Pyotr/M
Pyrenees/M
Pyrex/MS
Pyrrhic/M
Pythagoras/M
Pythagorean/M
Pythias/M
Python/M
P�tain/M
P�rto/M
Q
QA
QB
QC
QED
QM
QWERTY
Qaddafi/M
Qaeda/M
Qantas/M
Qatar/M
Qatari/MS
Qingdao/M
Qinghai/M
Qiqihar/M
Qom/M
Quaalude/M
Quaker/MS
Quakerism/SM
Qualcomm/M
Quaoar/M
Quasimodo/M
Quaternary/M
Quayle/M
Que
Quebec/M
Quebecker
Quebecois/M
Quechua/M
Queen/MS
Queenie/M
Queens/M
Queensland/M
Quent/M
Quentin/M
Quetzalcoatl/M
Quezon/M
QuickList/M
QuickTime/M
Quillan/M
Quincey/M
Quincy/M
Quinlan/M
Quinn/M
Quint/M
Quinta/M
Quintana/M
Quintilian/M
Quintin/M
Quinton/M
Quintus/M
Quirinal/M
Quisling/M
Quito/M
Quixote/M
Quixotism/M
Qumran/M
Quonset/M
Qur'an/MS
Qur'anic
Quran
Quranic
Qu�becois/M
Qwest/M
R/MD
RAF/M
RAM/SM
RBI
RC
RCA/M
RCMP
RD
RDA
RDS/M
REIT
REM/SM
RF
RFC/S
RFD
RI
RIF
RIP
RISC
RN/M
RNA/M
ROFL
ROM/M
ROTC/M
RP
RR
RSFSR
RSI
RSV
RSVP
RTFM
RV/SM
Ra/M
Rab/M
Rabat/M
Rabelais/M
Rabelaisian/M
Rabi
Rabin/M
Rachael/M
Rachel/M
Rachelle/M
Rachmaninoff/M
Racine/M
Radcliff/M
Radcliffe/M
Rae/M
Raf/M
Rafa/M
Rafael/M
Rafe/M
Raff/M
Raffaello/M
Rafferty/M
Raffles/M
Rafi/M
Ragnar/M
Ragnarok/M
Ragnar�k/M
Rahel/M
Raimondo/M
Raimund/M
Raimundo/M
Raina/M
Raine/MR
Rainer/M
Rainier/M
Raleigh/M
Ralf/M
Ralph/M
Rama/M
Ramada/M
Ramadan/MS
Ramakrishna/M
Ramanujan/M
Ramayana/M
Rambo/M
Ramirez/M
Ramiro/M
Ramon/M
Ramona/M
Ramos/M
Ramsay/M
Ramses/M
Ramsey/M
Rana/M
Rance/M
Rand/M
Randa/M
Randal/M
Randall/M
Randell/M
Randi/M
Randolph/M
Randy/M
Rangoon/M
Rani/M
Rankin/M
Rankine/M
Raoul/M
Raphael/M
Rappaport/M
Rapunzel/M
Raquel/M
Rasalgethi/M
Rasalhague/M
Rasmussen/M
Rasputin/M
Rasta
Rastaban/M
Rastafarian/MS
Rastafarianism
Rather/M
Ratliff/M
Raul/M
Ravel/M
Ravi/M
Ravid/M
Raviv/M
Rawalpindi/M
Rawley/M
Ray/M
RayBan/M
Rayburn/M
Raye/M
Rayleigh/M
Raymond/M
Raymund/M
Raymundo/M
Rayna/M
Rayner/M
Raynor/M
Rb/M
Rd
Re/M
Rea/M
Reade/G
Reading/M
Reagan/M
Reaganomics/M
Realtor/M
Reasoner/M
Reba/M
Rebeca/M
Rebecca/M
Rebecka/M
Rebekah/M
Recife/M
Reconstruction/M
Red/SM
Redd/GM
Redding/M
Redeemer/M
Redford/M
Redgrave/M
Redis/M
Redmond/M
Redshift/M
Ree/DSM
Reebok/M
Reece/M
Reed/M
Reena/M
Reese/M
Reeves/M
Reformation/MS
Refugio/M
Regan/M
Regen/M
Reggie/M
Regina/M
Reginae/M
Reginald/M
Regine/M
Regor/M
Regulus/M
Rehnquist/M
Reich/M
Reichstag's
Reid/M
Reiko/M
Reilly/M
Reina/M
Reinaldo/M
Reine/M
Reinhard/M
Reinhardt/M
Reinhold/M
Remanence
Remanent
Remarque/M
Rembrandt/M
Remington/M
Remus/M
Remy/M
Rena/M
Renae/M
Renaissance/SM
Renaldo/M
Renard/M
Renascence
Renata/M
Renate/M
Renato/M
Renaud/M
Renault/M
Rene/M
Renee/M
Renie/M
Rennie/M
Reno/M
Renoir/M
Rep
Representative
Republican/SM
Republicanism
Requiem/MS
Resistance
Restoration/M
Resurrection
Reta/M
Reuben/M
Reunion/M
Reuters/M
Reuther/M
Reuven/M
Rev
Reva/M
Revelation/SM
Revelations/M
Revere/M
Reverend/M
Revlon/M
Rex/M
Rey/M
Reyes/M
Reykjavik/M
Reyna/M
Reynaldo/M
Reynard/M
Reynold/MS
Reynolds/M
Rf/M
Rh/M
Rhea/M
Rhee/M
Rheingau/M
Rhenish/M
Rhett/M
Rhianna/M
Rhiannon/M
Rhine/M
Rhineland/M
Rhoda/M
Rhode/S
Rhodes/M
Rhodesia/M
Rhodesian
Rhona/M
Rhonda/M
Rhone/M
Rhys/M
Ribbentrop/M
Ric/M
Rica/M
Ricard/M
Ricardo/M
Riccardo/M
Rice/M
Rich/M
Richard/MS
Richards/M
Richardson/M
Richart/M
Richelieu/M
Richie/M
Richmond/M
Richter/M
Richthofen/M
Rick/M
Rickard/M
Rickenbacker/M
Rickert/M
Rickey/M
Ricki/M
Rickie/M
Rickover/M
Ricky/M
Rico/M
Riddle/M
Ride/M
Riefenstahl/M
Riel/M
Riemann/M
Riesling/MS
Riga/M
Rigel/M
Riggs/M
Right
Rigoberto/M
Rigoletto/M
Rik/M
Riki/M
Rikki/M
Riley/M
Rilke/M
Rimbaud/M
Rina/M
Rinaldo/M
Ringling/M
Ringo/M
Rio/SM
Riordan/M
Rios/M
Ripley/M
Risa/M
Risorgimento/M
Rita/M
Ritalin/M
Ritchie/M
Ritz/M
Riva/SM
Rivas/M
Rivera/M
Rivers/M
Riverside/M
Riviera/MS
Riyadh/M
Rizal/M
Rn/M
Roach/M
Roanoke/M
Roarke/M
Rob/M
Robb/M
Robbie/M
Robbin/MS
Robbins/M
Robby/M
Roberson/M
Robert/MS
Roberta/M
Roberto/M
Roberts/M
Robertson/M
Robeson/M
Robespierre/M
Robin/M
Robina/M
Robinet/M
Robinette/M
Robinia/M
Robinson/M
Robitussin/M
Robles/M
Robson/M
Robt/M
Roby/M
Robyn/M
Rocco/M
Roch/M
Rocha/M
Rochambeau/M
Roche/M
Rochelle/M
Rochester/M
Rock/M
Rockefeller/M
Rockford/M
Rockies/M
Rockne/M
Rockwell/M
Rocky/SM
Rod/M
Roda/M
Rodd/M
Roddenberry/M
Roddy/M
Roderic/M
Roderick/M
Roderigo/M
Rodger/MS
Rodgers/M
Rodham/M
Rodi/M
Rodin/M
Rodney/M
Rodolfo/M
Rodolph/M
Rodolphe/M
Rodrick/M
Rodrigo/M
Rodriguez/M
Rodriquez/M
Roeg/M
Roentgen
Rog/MRZ
Rogelio/M
Roger/M
Rogers/M
Roget/M
Roi/SM
Rojas/M
Roku/M
Rolaids/M
Roland/M
Rolando/M
Roldan/M
Rolex/M
Rolf
Rolfe/M
Rolland/M
Rollerblade/M
Rollie/M
Rollin/MS
Rollins/M
Rollo
Rolodex/M
Rolph/M
Rolvaag/M
Rom
Roma/M
Romain/M
Roman/MS
Romana/M
Romanesque/MS
Romania/M
Romanian/MS
Romano/M
Romanov/M
Romans/M
Romansh/M
Romantic
Romanticism
Romany/SM
Rome/SM
Romeo/M
Romero/M
Rommel/M
Romney/M
Romola/M
Romulus/M
Romy/M
Ron/M
Rona/M
Ronald/M
Ronda/M
Ronni/M
Ronnie/M
Ronny/M
Ronstadt/M
Rontgen
Rooney/M
Roosevelt/M
Root/M
Roquefort/SM
Rorke/M
Rorschach/M
Rory/M
Ros
Rosa/M
Rosaleen/M
Rosales/M
Rosalia/M
Rosalie/M
Rosalind/M
Rosalinda/M
Rosaline/M
Rosalyn/M
Rosamond/M
Rosamund/M
Rosanna/M
Rosanne/M
Rosario/M
Rosco/M
Roscoe/M
Rose/M
Roseann/M
Roseanne/M
Roseau/M
Rosecrans/M
Rosella/M
Roselle/M
Rosemarie/M
Rosemary/M
Rosenberg/M
Rosendo/M
Rosenzweig/M
Rosetta/M
Rosicrucian/M
Rosie/M
Rosina/M
Rosita/M
Roslyn/M
Ross/M
Rossetti/M
Rossie/M
Rossini/M
Rostand/M
Rostov/M
Rostropovich/M
Roswell/M
Rosy's
Rotarian/M
Roth/M
Rothko/M
Rothschild/M
Rotterdam/M
Rottweiler/M
Rouault/M
Rourke/M
Rousseau/M
Routledge/M
Rove/RM
Rover/M
Rowan/M
Rowe/M
Rowen/M
Rowena/M
Rowland/M
Rowling/M
Roxana/M
Roxane/M
Roxanna/M
Roxanne/M
Roxie/M
Roxy/M
Roy/M
Royal/M
Royall/M
Royce/M
Roz/M
Rozelle/M
Rte
Ru/MH
Rubaiyat/M
Rubbermaid/M
Ruben/SM
Rubens/M
Rubi/M
Rubia/M
Rubicon/MS
Rubik/M
Rubin/M
Rubinstein/M
Ruby/M
Ruchbah/M
Rudd/M
Ruddy's
Rudiger/M
Rudolf/M
Rudolfo/M
Rudolph/M
Rudy/M
Rudyard/M
Rufe/M
Rufus/M
Rugby
Ruggiero/M
Ruhr/M
Ruiz/M
Rukeyser/M
Rumanian/SM
Rumpelstiltskin/M
Rumsfeld/M
Runnymede/M
Runyon/M
Rupert/M
Ruprecht/M
Rurik
Rush/M
Rushdie/M
Rushmore/M
Ruskin/M
Russ/M
Russel/M
Russell/M
Russia/M
Russian/SM
Russo/M
Rustbelt/M
Rustin/M
Rusty/M
Rutan/M
Rutger/MS
Rutgers/M
Ruth/M
Rutherford/M
Ruthie/M
Rutledge/M
Rutter/M
Ruy/M
Rwanda/MS
Rwandan/SM
Rwy
Rx
Ry
Ryan/M
Rydberg/M
Ryder/M
Ryukyu/M
S/MN
SA
SAC
SALT/M
SAM/M
SAP/M
SARS/M
SASE
SAT
SBA
SC/M
SCOTUS/M
SCSI/M
SD
SDI
SE/M
SEATO
SEC/M
SF
SGML/M
SIDS/M
SJ
SJW
SK
SLR
SME/SM
SMEs/M
SNP/SM
SO/S
SOB/M
SOP/M
SOS/M
SOSes
SPCA
SPF
SQL
SQLite/M
SRO
SS
SSA
SSE/M
SSN
SSS
SST
SSW/M
ST
STD
STOL
SUSE/M
SUV
SVN/M
SW/M
SWAK
SWAT
Saab/M
Saar/M
Saarinen/M
Saatchi/M
Saba/M
Sabbath/M
Sabbaths
Sabik/M
Sabin/M
Sabina/M
Sabine/M
Sabre/M
Sabrina/M
Sacajawea/M
Saccharomyces/M
Sacco/M
Sacha/M
Sachs/M
Sacramento/M
Sada/M
Sadat/M
Saddam/M
Sadducee/M
Sade/M
Sadie/M
Sadr/M
Safavid/M
Safeway/M
Sagan/M
Saginaw/M
Sagittarius/MS
Sahara/M
Saharan/M
Sahel/M
Saigon/M
Saiph/M
Sakai/M
Sakha/M
Sakhalin/M
Sakharov/M
Saki/M
Saks/M
Sal/MY
Saladin/M
Salado/M
Salamis/M
Salas/M
Salazar/M
Saleem/M
Salem/M
Salerno/M
Salesforce/M
Salim/M
Salinas/M
Salinger/M
Salisbury/M
Salish/M
Salk/M
Sallee/M
Sallie/M
Sallust/M
Sally/M
Salome/M
Salomon/M
Salonika/M
Salton/M
Salvador/M
Salvadoran/SM
Salvadorean/MS
Salvadorian/MS
Salvatore/M
Salween/M
Salyut/M
Sam/M
Samantha/M
Samar/M
Samara/M
Samaria
Samaritan/MS
Samarkand/M
Sammie/M
Sammy/M
Samoa/M
Samoan/SM
Samoset/M
Samoyed/M
Sampson/M
Samson/M
Samsonite/M
Samsung/M
Samuel/M
Samuelson/M
San'a
San/M
Sana/M
Sanaa/M
Sanchez/M
Sancho/M
Sand/ZM
Sandburg/M
Sande/M
Sanders/M
Sanderson/M
Sandi/M
Sandie/M
Sandinista/M
Sandor/M
Sandoval/M
Sandra/M
Sandro/M
Sandy/M
Sanford/M
Sanforized/M
Sang/MR
Sanger/M
Sanhedrin/M
Sanka/M
Sankara/M
Sanskrit/M
Sanson/M
Sansone/M
Santa/M
Santana/M
Santayana/M
Santeria/M
Santiago/M
Santos/M
Sapphira
Sappho/M
Sapporo/M
Sara/M
Saracen/MS
Saragossa/M
Sarah/M
Sarajevo/M
Saran/M
Sarasota/M
Saratov/M
Sarawak/M
Sarbanes/M
Sardinia/M
Sardinian/M
Saree/M
Sargasso/M
Sarge/M
Sargent/M
Sargon/M
Sarina/M
Sarita/M
Sarnoff/M
Saroyan/M
Sarto/M
Sartre/M
Sascha/M
Sasha/M
Sask
Saskatchewan/M
Saskatoon/M
Sasquatch/MS
Sassanian/M
Sassoon/M
Sat/M
Satan/M
Satanism/M
Satanist/M
Saturday/MS
Saturn/M
Saturnalia/M
Saudi/MS
Saul/M
Saunders/M
Saunderson/M
Saundra/M
Saussure/M
Sauternes
Sauveur/M
Savage/M
Savannah/M
Savina/M
Savior/M
Savonarola/M
Savoy/M
Savoyard/M
Sawyer/M
Saxe/M
Saxon/MS
Saxony/M
Sayer/MS
Sayers/M
Sayre/M
Sb/M
Sc/M
Scala/M
Scan
Scandinavia/M
Scandinavian/MS
Scaramouch/M
Scarborough/M
Scarface/M
Scarlatti/M
Scarlett/M
Scheat/M
Schedar/M
Scheherazade/M
Schelling/M
Schenectady/M
Schiaparelli/M
Schick/M
Schiller/M
Schindler/M
Schlesinger/M
Schliemann/M
Schlitz/M
Schloss/M
Schmidt/M
Schnabel/M
Schnauzer/M
Schneider/M
Schoenberg/M
Schopenhauer/M
Schrieffer/M
Schrodinger/M
Schroeder/M
Schr�dinger/M
Schubert/M
Schultz/M
Schulz/M
Schumann/M
Schumpeter/M
Schuyler/M
Schuylkill/M
Schwartz/M
Schwarz/M
Schwarzenegger/M
Schwarzkopf/M
Schweitzer/M
Schweppes/M
Schwinger/M
Schwinn/M
Scientologist/SM
Scientology/M
Scipio/M
Scopes/M
Scorpio/SM
Scorpius/M
Scorsese/M
Scot/SM
Scotch/MS
Scotchman/M
Scotchmen/M
Scotchwoman/M
Scotchwomen/M
Scotia/M
Scotland/M
Scotsman/M
Scotsmen/M
Scotswoman/M
Scotswomen/M
Scott/M
Scotti/M
Scottie/SM
Scottish/M
Scottsdale/M
Scrabble/MS
Scranton/M
Scriabin/M
Scribner/M
Scripture/SM
Scrooge/M
Scruggs/M
Scud/M
Sculley/M
Scunthorpe/M
Scylla/M
Scythia/M
Scythian/M
Se/MHN
SeaMonkey/M
Seaborg/M
Seagram/M
Seamus/M
Sean/M
Sears/M
Seaside/M
Seattle/M
Sebastian/M
Sebastiano/M
Sebastien/M
Sebring/M
Sec
Seconal/M
Secretariat/M
Secretary
Seder/MS
Sedna/M
Seebeck/M
Seeger/M
Sega/M
Segovia/M
Segre/M
Segundo/M
Segway/S
Seiko/M
Seine/M
Seinfeld/M
Sejong/M
Sela/M
Selassie/M
Selby/M
Selectric/M
Selena/M
Selene/M
Seleucid/M
Seleucus/M
Selig/M
Selim/M
Selina/M
Seljuk/M
Selkirk/M
Sella/M
Selle/MZ
Sellers/M
Selma/M
Selznick/M
Semarang/M
Seminole/MS
Semiramis/M
Semite/MS
Semitic/SM
Semtex/M
Sena/M
Senate/MS
Sendai/M
Seneca/MS
Senegal/M
Senegalese/M
Senghor/M
Senior/M
Sennacherib/M
Sennett/M
Sensurround/M
Seoul/M
Sep
Sephardi/M
Sepoy/M
Sept/M
September/MS
Septuagint/MS
Sequoya/M
Serb/SM
Serbia/M
Serbian/MS
Serena/M
Serengeti/M
Sergei/M
Sergent/M
Sergio/M
Serpens/M
Serra/M
Serrano/M
Set/M
Seth/M
Seton/M
Seurat/M
Seuss/M
Sevastopol/M
Severn/M
Severus/M
Seville/M
Sevres/M
Seward/M
Sextans/M
Sexton/M
Seychelles/M
Seyfert/M
Seymour/M
Sgt
Shaanxi/M
Shackleton/M
Shaffer/M
Shah/M
Shaka/M
Shaker
Shakespeare/M
Shakespearean/M
Shalom's
Shamus/M
Shana/M
Shandong/M
Shandy/M
Shane/M
Shanghai/M
Shani/M
Shankara/M
Shanna/M
Shannon/M
Shanta/M
Shantung/M
Shanxi/M
Shapiro/M
Shara/M
SharePoint/M
Shari'a/M
Shari/M
Sharif/M
Sharlene/M
Sharma/M
Sharon/M
Sharp/M
Sharpe/M
Sharron/M
Shasta/M
Shaula/M
Shaun/M
Shauna/M
Shavian/M
Shavuot/M
Shaw/M
Shawn/M
Shawna/M
Shawnee/SM
Shayla/M
Shayna/M
Shayne/M
Shcharansky/M
Shea/M
Sheba/M
Shebeli/M
Sheboygan/M
Sheela/M
Sheena/M
Sheetrock/M
Sheffield/M
Sheila/M
Sheilah/M
Shel/MY
Shelagh/M
Shelby/M
Sheldon/M
Shelia/M
Shell/M
Shelley/M
Shelly/M
Shelton/M
Shem/M
Shen/M
Shenandoah/M
Shenyang/M
Shenzhen/M
Sheol/M
Shep/M
Shepard/M
Shepherd/M
Sheppard/M
Sher/M
Sheratan/M
Sheraton/M
Sheree/M
Sheri/M
Sheridan/M
Sherlock/M
Sherm/M
Sherman/M
Sherpa/M
Sherri/M
Sherrie/M
Sherry/M
Sherwin/M
Sherwood/M
Sheryl/M
Shetland/SM
Shetlands/M
Shevardnadze/M
Shevat/M
Shi'ite/M
Shields/M
Shiite/MS
Shijiazhuang/M
Shikoku/M
Shillong/M
Shiloh/M
Shina/M
Shinto/MS
Shintoism/MS
Shintoist/MS
Shir/M
Shiraz/M
Shirl/M
Shirley/M
Shiva/M
Shockley/M
Sholom/M
Short/M
Shorthorn/M
Shoshana/M
Shoshone/SM
Shostakovitch/M
Shrek/M
Shreveport/M
Shriner/M
Shropshire/M
Shula/M
Shylock/M
Shylockian/M
Si/M
Siam/M
Siamese/M
Sian/M
Sib/M
Sibelius/M
Siberia/M
Siberian/MS
Sibley/M
Sibyl/M
Sibylla/M
Sibylle/M
Sichuan/M
Sicilian/SM
Sicily/M
Sid/M
Siddhartha/M
Sidney/M
Sidonia/M
Siegfried/M
Siemens/M
Sierpinski/M
Sierras
Sig
Sigismondo/M
Sigismund/M
Sigmund/M
Sigrid/M
Sigurd/M
Sihanouk/M
Sikh/M
Sikhism
Sikhs
Sikkim/M
Sikkimese/M
Sikorsky/M
Silas/M
Silesia/M
Silurian/SM
Silva/M
Silvan/M
Silvana/M
Silvano/M
Silvanus/M
Silvester/M
Silvia/M
Silvie/M
Silvio/M
Sim's
Simenon/M
Simeon/M
Simmental/M
Simmonds/M
Simmons/M
Simon/M
Simona/M
Simone/M
Simpson/SM
Simpsons/M
Simpsonville/M
Sims/M
Sinai/M
Sinatra/M
Sinbad/M
Sinclair/M
Sindbad/M
Sindhi/M
Singapore/M
Singaporean/SM
Singer/M
Singh/M
Singleton/M
Sinhalese/M
Sinica/M
Sinkiang/M
Siobhan/M
Sioux/M
Sir/SM
Sirius/M
Sissie/M
Sister/MS
Sistine/M
Sisyphean/M
Sisyphus/M
Siva/M
Sivan/M
Siward/M
Sjaelland/M
Skelly/M
Skinner/M
Skippy/M
Skipton/M
Skopje/M
Skye/M
Skylab/M
Skylar/M
Skyler/M
Skype/M
Slackware/M
Slade/M
Slashdot/M
Slater/M
Slav/SM
Slavic/M
Slavonic/M
Slidell/M
Slinky/M
Sloan/M
Sloane/M
Slocum/M
Slovak/SM
Slovakia/M
Slovakian
Slovene/SM
Slovenia/M
Slovenian/MS
Slurpee/M
Sly's
Sm/M
Small/M
Smetana/M
Smirnoff/M
Smith/M
Smithson/M
Smithsonian/M
Smitty/M
Smokey/M
Smolensk/M
Smollett/M
Smuts/M
Smyrna
Sn/M
Snake/M
Snapple/M
Snead/M
Snell/M
Snickers/M
Snider/M
Snoopy/M
Snow/M
Snowbelt/M
Snyder/M
Soave/M
Soc
Socastee/M
Socorro/M
Socrates/M
Socratic/M
Soddy/M
Sodom/M
Sofia/M
Sofie/M
Soho/M
Sol/MY
Solaris/M
Solis/M
Solly/M
Solomon/M
Solon/M
Solzhenitsyn/M
Somali/SM
Somalia/M
Somalian/MS
Somerset
Somme/M
Somoza/M
Son/M
Sondheim/M
Sondra/M
Songhai/M
Songhua/M
Sonia/M
Sonja/M
Sonny/M
Sonora/M
Sontag/M
Sony/M
Sonya/M
Sophi/M
Sophia/M
Sophie/M
Sophoclean/M
Sophocles/M
Sophronia/M
Sopwith/M
Sorbonne/M
Sorcha/M
Sosa/M
Soto/M
Souphanouvong/M
Sourceforge/M
Sousa/M
South/M
Southampton/M
Southeast/MS
Southern/ZR
Southerner/M
Southey/M
Souths
Southwest/MS
Soviet/M
Sovietica/M
Soweto/M
Soyinka/M
Soyuz/M
Sp
Spaatz/M
Spackle/M
Spahn/M
Spain/M
Spam/M
Span
Spanglish
Spaniard/SM
Spanish/M
Sparc
Sparks/M
Sparta/M
Spartacus/M
Spartan/MS
Spartanburg/M
Spears/M
Speer/M
Spence/RM
Spencer/M
Spencerian/M
Spengler/M
Spenglerian/M
Spenser/M
Spenserian/M
Sperry/M
Sphinx/M
Spica/M
SpiderMonkey/M
Spielberg/M
Spillane/M
Spinoza/M
Spinx/M
Spiro/M
Spirograph/M
Spitsbergen/M
Spitz/M
Spock/M
Spokane/M
Springdale/M
Springfield/M
Springsteen/M
Sprint/M
Sprite/M
Sputnik/M
Sq
Squanto/M
Squibb/M
Sr/M
Srinagar/M
Srivijaya/M
St
Sta
Stace/M
Stacey/M
Staci/M
Stacie/M
Stacy/M
Stael/M
Stafford/M
StairMaster/M
Stalin/M
Stalingrad/M
Stalinist/M
Stallone/M
Stamford/M
Stan/MY
Standford/M
Standish/M
Stanfield/M
Stanford/M
Stanislas/M
Stanislaus/M
Stanislavsky/M
Stanislaw/M
Stanley/M
Stanly/M
Stanton/M
Stanwood/M
Staples/M
Starbucks/M
Stark/M
Starkey/M
Starr/M
Statehouse/MS
Staten/M
States
Stateside
Staubach/M
Staunton/M
Stavros
Ste
Steadicam/M
Stearn/M
Steele/M
Stefan/M
Stefania/M
Stefanie/M
Stefano/M
Steffen/M
Steffi/M
Stein/MR
Steinbeck/M
Steinem/M
Steiner/M
Steinmetz/M
Steinway/M
Stella/M
Stendhal/M
Stengel/M
Stephan/M
Stephani/M
Stephanie/M
Stephanus/M
Stephen/MS
Stephens/M
Stephenson/M
Sterling/M
Stern/M
Sterne/M
Sterno/M
Stetson/M
Steuben/M
Steubenville/M
Steve/M
Steven/MS
Stevens/M
Stevenson/M
Stevie/M
Stewart/M
Stieglitz/M
Stillman/M
Stilton/SM
Stimson/M
Stine/M
Stinky's
Stirling/M
Stockhausen/M
Stockholm/M
Stockton/M
Stoddard/M
Stoic/SM
Stoicism/MS
Stokes/M
Stolichnaya/M
Stolypin/M
Stone/M
Stonehenge/M
Stoppard/M
Stormy's
Stout/M
Stowe/M
Strabo/M
Stradivari
Stradivarius/M
Strasbourg/M
Strauss/M
Stravinsky/M
Streisand/M
Strickland/M
Strindberg/M
Stromboli/M
Strong/M
Stu/M
Stuart/MS
Studebaker/M
StumbleUpon/M
Stuttgart/M
Stuyvesant/M
Stygian/M
Styrofoam/SM
Styron/M
Styx/M
Suarez/M
Subaru/M
Sucre/M
Sucrets/M
Sudan/M
Sudanese/M
Sudetenland/M
Sudoku/M
Sudra/M
Sue/M
Suetonius/M
Suez/M
Suffolk/M
Sufi/M
Sufism/M
Suharto/M
Sui/M
Sukarno/M
Sukey/M
Suki/M
Sukkot
Sukkoth/M
Sukkoths
Sula/M
Sulawesi/M
Suleiman/M
Sulla/M
Sullivan/M
Sumatra/M
Sumatran/SM
Sumeria/M
Sumerian/SM
Sumerica/M
Summer/MS
Summers/M
Sumner/M
Sumter/M
Sun/SM
Sunbeam/M
Sunbelt/M
Sundanese/M
Sundas/M
Sunday/MS
Sunderland/M
Sung/M
Sunkist/M
Sunni/SM
Sunnite/MS
Sunny's
Sunnyvale/M
Suomi/M
Superbowl/M
Superfund/M
Superglue/M
Superior/M
Superman/M
Supt
Surabaja
Surabaya/M
Surat/M
Suriname/M
Surinamese
Surya/M
Susan/M
Susana/M
Susann/M
Susanna/M
Susannah/M
Susanne/M
Susi/M
Susie/M
Susquehanna/M
Sussex/M
Susy/M
Sutherland/M
Sutton/M
Suva/M
Suwanee/M
Suzanna/M
Suzanne/M
Suzette/M
Suzhou/M
Suzi/M
Suzie/M
Suzuki/M
Suzy/M
Svalbard/M
Sven/M
Svend/M
Svengali/M
Sverdlovsk
Swahili/SM
Swammerdam/M
Swanee/M
Swansea/M
Swanson/M
Swazi/SM
Swaziland/M
Swed/N
Swede/SM
Sweden/M
Swedenborg/M
Swedish/M
Sweeney/M
Sweet/M
Swift/M
Swinburne/M
Swindon/M
Swiss/MS
Swissair/M
Switz
Switzerland/M
Sybil/M
Sybilla/M
Sybille/M
Syd/M
Sydney/M
Sykes/M
Sylvan's
Sylvester/M
Sylvia/M
Sylvie/M
Symantec/M
Symbian/M
Symon/M
Synge/M
Syracuse/M
Syria/M
Syriac/M
Syrian/MS
Syriana/M
Szilard/M
Szymborska/M
S�vres/M
T'ang/M
T/MDG
TA
TARP
TB/M
TBA
TD
TDD
TEFL
TELNET/S
TELNETTed
TELNETTing
TESL
TESOL
TEirtza/M
TGIF
THC
THz/M
TIF/SM
TIFF/SM
TKO/M
TLC/M
TM
TN
TNT/M
TOEFL
TQM
TV/SM
TVA
TWA/M
TWX
TX
Ta/M
Tabasco/SM
Tabatha/M
Tabb/M
Taber/M
Tabernacle/MS
Tabitha/M
Tabor
Tabriz/MS
Tacitus/M
Tacoma/M
Tad/M
Taddeo/M
Tadzhik/M
Taegu/M
Taejon/M
Taft/M
Tagalog/SM
Tagore/M
Tagus/M
Tahiti/M
Tahitian/MS
Tahoe/M
Taichung/M
Tainan
Taine/M
Taipei/M
Taiping/M
Tait/M
Taiwan/M
Taiwanese/M
Taiyuan/M
Tajikistan/M
Taklamakan/M
Talbert/M
Talbot/M
Talia/M
Taliban/M
Taliesin/M
Tallahassee/M
Tallchief/M
Talley/M
Talleyrand/M
Tallinn/M
Tallulah/M
Talmud/MS
Talmudic
Talmudist
Talya/M
Tamar/M
Tamara/M
Tamas
Tameka/M
Tamera/M
Tamerlane/M
Tami/M
Tamika/M
Tamil/MS
Tammany/M
Tammi/M
Tammie/M
Tammuz/M
Tammy/M
Tampa/M
Tampax/M
Tamra/M
Tamworth/M
Tana
Tancred/M
Tandy/M
Taney/M
Tanganyika/M
Tangier/MS
Tangshan/M
Tani/M
Tania/M
Tanisha/M
Tann/MR
Tanner/M
Tannhauser/M
Tannh�user/M
Tantalus/M
Tanya/M
Tanzania/M
Tanzanian/SM
Tao/M
Taoism/MS
Taoist/MS
Tara/M
Tarantino/M
Tarawa/M
Tarazed/M
Tarbell/M
Target/M
Tarim/M
Tarkenton/M
Tarkington/M
Tartar/MS
Tartary/M
Tartuffe/M
Taryn/M
Tarzan/M
Tasha/M
Tashkent/M
Tasman/M
Tasmania/M
Tasmanian/M
Tass/M
Tatar/MS
Tate/M
Tatiana/M
Tatum/M
Taurus/MS
Tavares/M
Tawney/M
Taylor/M
Tb/M
Tbilisi/M
Tc/M
Tchaikovsky/M
Te/M
TeX
TeXes
Teasdale/M
TechRepublic/M
Technicolor/M
Technorati/M
Tecumseh/M
Ted/M
Teddie/M
Teddy/M
Tedi/M
Teena/M
Teflon/MS
Tegucigalpa/M
Tehran
TelePrompTer
TelePrompter/M
Telemachus/M
Telemann/M
Teletype
Tell/MR
Teller/M
Telugu/M
Temecula/M
Tempe
Templar/M
Temple/M
Templeton/M
Tenn/M
Tennessean/SM
Tennessee/M
Tennyson/M
Tennysonian
Tenochtitlan/M
TensorFlow/M
Teodor/M
Teodora/M
Teodoro/M
Teotihuacan/M
Tera/M
Terence/M
Teresa/M
Terese/M
Tereshkova/M
Teresita/M
Teri/M
Terkel/M
Terpsichore/M
Terr/M
Terra/M
Terran/M
Terrance/M
Terrell/M
Terrence/M
Terri/M
Terrie/M
Terrill/M
Territory
Terry/M
Tertiary/M
Terza/M
Tesco/M
Tesla/M
Tess/M
Tessa/M
Tessie/M
Tet/M
Tethys/M
Tetons/M
Teuton/MS
Teutonic/M
Tevet/M
Tex/M
Texaco/M
Texan/MS
Texarkana/M
Texas/M
TextEdit/M
Th/M
Thacher/M
Thackeray/M
Thad/M
Thaddeus/M
Thai/SM
Thailand/M
Thales/M
Thalia/M
Thames/M
Thanh/M
Thanksgiving/MS
Thant/M
Thar/M
Tharp/M
Thatcher/M
Thatcherism
Thaxter/M
Thea/M
Thebes/M
Theda/M
Theia
Theiler/M
Thekla/M
Thelma/M
Themistocles/M
Theo/M
Theobald/M
Theocritus/M
Theodor/M
Theodora/M
Theodore/M
Theodoric/M
Theodosia/M
Theodosius/M
Theosophy/M
Theravada/M
Theresa/M
Therese/M
Thermopylae/M
Thermos
Theron/M
Theseus/M
Thespian/M
Thespis/M
Thessalonian/SM
Thessaloniki/M
Thessalon�ki/M
Thessaly/M
Thia/M
Thibaut/M
Thieu/M
Thimbu/M
Thimphu
Thom/M
Thoma/SM
Thomas/M
Thomasin/M
Thomasina/M
Thomism/M
Thomistic/M
Thompson/M
Thomson/M
Thor/M
Thorazine/M
Thoreau/M
Thorin/M
Thorndike
Thornton/M
Thorny's
Thoroughbred/M
Thorpe/M
Thorstein/M
Thorsten/M
Thorvald/M
Thoth/M
Thrace/M
Thracian/M
Thu
Thucydides/M
Thule/M
Thunderbird/M
Thur/S
Thurber/M
Thurman/M
Thurmond/M
Thursday/SM
Thurstan/M
Thurston/M
Thutmose/M
Ti/M
Tia/M
Tianjin/M
Tiber/M
Tiberius/M
Tibet/M
Tibetan/MS
Ticketmaster/M
Ticonderoga/M
Tide/M
Tiebout/M
Tienanmen/M
Tientsin/M
Tierney/M
Tiffany/M
Tigris/M
Tijuana/M
Tilda/M
Tildi/M
Tildy/M
Tillich/M
Tillie/M
Tillman/M
Tilly/M
Tilsit/M
Tim/M
Timbuktu/M
Timex/M
Timi/M
Timmy/M
Timon/M
Timor/M
Timotheus/M
Timothy/M
Timur/M
Timurid/M
Tina/M
Ting/M
Tinkerbell/M
Tinkertoy/M
Tinseltown/M
Tintoretto/M
Tippecanoe/M
Tipperary/M
Tirane
Tiresias/M
Tirol/M
Tirolean
Tish/M
Tisha/M
Tishri/M
Titan/SM
Titania/M
Titanic/M
Titian/M
Titicaca/M
Tito/M
Titus/M
Titusville/M
Tl/M
Tlaloc/M
Tlingit/M
Tm/M
Tobago/M
Tobe/M
Tobey
Tobi/M
Tobias/M
Tobie/M
Tobin/M
Tobit/M
Toby/M
Tocantins/M
Tocqueville/M
Tod/M
Todd/M
Togo/M
Togolese/M
Tojo/M
Tokay/M
Tokugawa/M
Tokyo/M
Tokyoite
Toledo/MS
Tolkien/M
Tolstoy/M
Toltec/M
Tolyatti/M
Tom/M
Toma/SM
Tomas/M
Tomaso/M
Tombaugh/M
Tomi/M
Tomlin/M
Tommie/M
Tommy/M
Tompkins/M
Tomsk/M
Tonga/M
Tongan/MS
Toni/M
Tonia/M
Tonto/M
Tony/M
Tonya/M
Tootsie/M
Topeka/M
Topsy/M
Torah/M
Torahs
Tore's
Torey/M
Tori/M
Torin/M
Toronto/M
Torquemada/M
Torr/MX
Torrance/M
Torre/SM
Torrence/M
Torrens/M
Torres/M
Torrey/M
Torricelli/M
Torrie/M
Torry/M
Tortola/M
Tortuga/M
Torvalds/M
Tory/SM
Tosca/M
Toscanini/M
Toshiba/M
Toto/M
Toulouse/M
Tova/M
Tove/M
Townes/M
Townsend/M
Toynbee/M
Toyoda/M
Toyota/M
Tracey/M
Traci/M
Tracie/M
Tracy/M
Trafalgar/M
Trailways/M
Trajan/M
Tran/M
Transcaucasia/M
Transvaal/M
Transylvania/M
Transylvanian/M
Trappist/SM
Traver/MS
Travis/M
Travolta/M
Treasury/SM
Treblinka/M
Trekkie/M
Tremain/M
Tremaine/M
Tremayne/M
Trent/M
Trenton/M
Treo/M
Trev/M
Trevelyan/M
Trevino/M
Trevor/M
Trey/M
Triangulum/M
Triassic/M
Tricia/M
Trident/M
Trieste/M
Trimurti/M
Trina/M
Trinidad/M
Trinidadian/MS
Trinity/SM
TripAdvisor/M
Tripitaka/M
Tripoli/M
Tripp/M
Trippe/M
Tris
Trish/M
Trisha/M
Trista/M
Tristan/M
Triton/M
Trix/M
Trixie/M
Trobriand/M
Troilus/M
Trojan/MS
Trollope/M
Trondheim/M
Tropicana/M
Trotsky/M
Troy/M
Troyes
Truckee/M
Trude/M
Trudeau/M
Trudi/M
Trudy/M
Trueman/M
Truffaut/M
Trujillo/M
Truman/M
Trumbull/M
Trump/M
Truth/M
Tsimshian/M
Tsiolkovsky/M
Tsitsihar/M
Tsongkhapa/M
Tswana/M
Tu/M
Tuamotu/M
Tuareg/M
Tubman/M
Tucker/M
Tucson/M
Tucuman/M
Tudor/SM
Tue/S
Tues/M
Tuesday/MS
Tulane/M
Tull/M
Tully/M
Tulsa/M
Tulsidas/M
Tums/M
Tungus/M
Tunguska/M
Tunis/M
Tunisia/M
Tunisian/MS
Tunney/M
Tupi/M
Tupperware/M
Tupungato/M
Turgenev/M
Turin/M
Turing/M
Turk/SM
Turkestan/M
Turkey/M
Turkic/MS
Turkish/M
Turkmenistan/M
Turlock/M
Turner/M
Turpin/M
Tuscaloosa/M
Tuscan/M
Tuscany/M
Tuscarora/MS
Tuscon/M
Tuskegee/M
Tussaud/M
Tut/M
Tutankhamen/M
Tutsi/M
Tutu/M
Tuvalu/M
Tuvaluan
Twain/M
Tweed/M
Tweedledee/M
Tweedledum/M
Twila/M
Twinkies/M
Twitter/M
Twizzlers/M
Twp
Twyla/M
Ty/M
Tybalt/M
Tycho/M
Tye/M
Tylenol/M
Tyler/M
Tynan/M
Tyndale/M
Tyndall/M
Tyne/M
Tyre/M
Tyree/M
Tyrolean
Tyrone/M
Tyrus/M
Tyson/M
U/M
UAR
UAW
UBS/M
UCLA/M
UFO/SM
UHF/M
UI/SM
UK/M
UL
UN/M
UNESCO/M
UNICEF/M
UNIX/M
UPC
UPI/M
UPS/M
URL/S
US/M
USA/M
USAF
USB
USCG
USDA/M
USIA
USMC
USN
USO
USP
USPS
USS
USSR/M
UT/M
UTC
UV/M
Ubangi/M
Ubuntu/M
Ucayali/M
Uccello/M
Udall/M
Udell/M
Ufa/M
Uganda/M
Ugandan/MS
Ugo/M
Uighur/M
Ujungpandang/M
Ukraine/M
Ukrainian/SM
Ula/M
Ulick/M
Ulises/M
Ulla/M
Ulric/M
Ulrica/M
Ulrich/M
Ulrike/M
Ulster/M
Ultrasuede/M
Ulyanovsk/M
Ulysses/M
Umberto/M
Umbriel/M
Una/M
Underwood/M
Ungava/M
Unicode/M
Unilever/M
Union/SM
Unionist
Uniontown/M
Uniroyal/M
Unitarian/MS
Unitarianism/MS
Unitas/M
Unix/S
Unukalhai/M
Upanishads/M
Updike/M
Upjohn/M
Upton/M
Ur/M
Ural/SM
Urals/M
Urania/M
Uranus/M
Urbain/M
Urban/M
Urbano/M
Urdu/M
Urey/M
Uri/SM
Uriah/M
Uriel/M
Uris/M
Urquhart/M
Ursa/M
Ursula/M
Ursuline/M
Uruguay/M
Uruguayan/MS
Urumqi/M
Usenet/MS
Ustinov/M
Ut
Uta/M
Utah/M
Utahan/MS
Ute/SM
Utica/M
Utopia/SM
Utopian/SM
Utrecht/M
Utrillo/M
Uzbek/M
Uzbekistan/M
Uzi/SM
V/M
VA
VAT/M
VAX
VAXes
VBA/M
VCR/M
VD/M
VDT
VDU
VF
VFW/M
VG
VGA
VHF/M
VHS
VI/M
VIP/SM
VISTA
VJ
VLF/M
VOA
VP
VPN/SM
VT
VTOL
Va/M
Vacaville/M
Vachel/M
Vaclav/M
Vader/M
Vaduz/M
Vail/M
Val/M
Valarie/M
Valdemar/M
Valdez/M
Valdosta/M
Valencia/SM
Valenti/M
Valentia/M
Valentin/M
Valentina/M
Valentine/M
Valentino/M
Valenzuela/M
Valera
Valeria/M
Valerian/M
Valerie/M
Valery/M
Valhalla/M
Valium/MS
Valkyrie/SM
Valle/M
Vallejo/M
Valletta/M
Valli/M
Valois/M
Valparaiso/M
Valvoline/M
Val�ry/M
Van/M
Vance/M
Vancouver/M
Vanda/M
Vandal/MS
Vanderbilt/M
Vandyke/M
Vanessa/M
Vang/M
Vania/M
Vanna/M
Vanni/M
Vanuatu/M
Vanya/M
Vanzetti/M
Varanasi/M
Varese/M
Vargas/M
Vaseline/SM
Vasili/M
Vasily/M
Vasquez/M
Vassar/M
Vassili/M
Vassily/M
Vatican/M
Vauban/M
Vaughan/M
Vaughn/M
Vazquez/M
Veblen/M
Veda/SM
Vedanta/M
Veep
Vega/SM
Vegas/M
Vegemite/M
Vela/M
Velasquez/M
Velazquez/M
Velcro/MS
Velez/M
Velma/M
Velveeta/M
Vel�squez/M
Vel�zquez/M
Venetian/SM
Venezuela/M
Venezuelan/SM
Venice/M
Venn/M
Ventolin/M
Venus/MS
Venusian/M
Vera/M
Veracruz/M
Verde/M
Verdi/M
Verdun/M
Vere/M
Verena/M
Verizon/M
Verlaine/M
Vermeer/M
Vermont/ZMR
Vermonter/M
Vern/M
Verna/M
Verne/M
Verney/M
Vernon/M
Vernor/M
Verona/M
Veronese/M
Veronica/M
Veronika/M
Veronique
Versailles/M
Vesalius/M
Vespasian/M
Vespucci/M
Vesta/M
Vesuvius/M
Vi/M
Viacom/M
Viagra/M
Vic/M
Vicente/M
Vichy/M
Vick/M
Vicki/M
Vickie/M
Vicksburg/M
Vicky/M
Vicodin/M
Victor/M
Victoria/M
Victorian/MS
Victorianism
Victorville/M
Victrola/M
Vida/M
Vidal/M
Vienna/M
Viennese/M
Vientiane/M
Vietcong/M
Vietminh/M
Vietnam/M
Vietnamese/M
Vijayanagar/M
Vijayawada/M
Viking/MS
Vikki/M
Vila/M
Villa/SM
Villarreal/M
Villas/M
Villon/M
Vilma/M
Vilnius/M
Vilyui/M
Vin/M
Vina/M
Vince/M
Vincent/M
Vindemiatrix/M
Vineland/M
Vinnie/M
Vinny/M
Vinson/M
Viola/M
Violante/M
Violet/M
Violetta/M
Violette/M
Virgie/M
Virgil/M
Virgilio/M
Virginia/M
Virginian/SM
Virginie/M
Virgo/SM
Visa/M
Visakhapatnam/M
Visalia/M
Visayans/M
Vishnu/M
Visigoth/M
Visigoths
Vistula/M
Vite/M
Vitim/M
Vito/M
Vitoria
Vittoria/M
Vittorio/M
Vitus/M
Viv/M
Vivaldi/M
Vivekananda/M
Vivi/MN
Vivian/M
Viviana/M
Vivie/M
Vivien/M
Vivienne/M
Vlad/M
Vladimir/M
Vladivostok/M
Vlaminck/M
Vlasic/M
VoIP
Vodafone/M
Vogue/M
Volcker/M
Voldemort/M
Volga/M
Volgograd/M
Volkswagen/M
Volstead/M
Volta/M
Voltaire/M
Volvo/M
Von/M
Vonda/M
Vonnegut/M
Voronezh/M
Vorster/M
Voyager/M
Vt
Vuitton/M
Vulcan/M
Vulg
Vulgate/SM
W/MDT
WA
WAC
WASP/SM
WATS/M
WC
WHO/M
WI
WMD
WNW/M
WP
WSW/M
WTF
WTO
WV
WW
WWI
WWII
WWW/M
WY
WYSIWYG
Wabash/M
Wac
Waco/M
Wade/M
Wadsworth/M
Wagner/M
Wagnerian/M
Wahhabi/M
Waikiki/M
Wainwright/M
Waite/M
Wake/M
Wakefield
Waksman/M
Wald/MN
Waldemar/M
Walden/M
Waldensian/M
Waldheim/M
Waldo/M
Waldorf/M
Wales/M
Walesa/M
Walgreen/SM
Walgreens/M
Walker/M
Walkman/M
Wall/SMR
Wallace/M
Wallas/M
Wallenstein/M
Waller/M
Wallie/M
Wallis/M
Walloon/M
Walls/M
Wally/M
Walmart/M
Walpole/M
Walpurgisnacht/M
Walsh/M
Walt/MRZ
Walter/M
Walters/M
Walther/M
Walton/M
Wanamaker/M
Wanda/M
Wang/M
Wankel/M
Warcraft/M
Ward/M
Warde/M
Ware/MG
Warhol/M
Waring/M
Warner/M
Warren/M
Warsaw/M
Warwick/M
Warwickshire/M
Wasatch/M
Wash/M
Washington/M
Washingtonian/MS
Wassermann/M
Wat/MZ
Waterbury/M
Waterford/M
Watergate/M
Waterloo/MS
Waters/M
Watertown/M
Watkins/M
Watson/M
Watsonville/M
Watt/SM
Watteau/M
Watts/M
Watusi/M
Waugh/M
Wausau/M
Wave
Waverley/M
Waverly/M
Wayland/M
Waylon/M
Wayne/M
Waynesboro/M
Weaver/M
Web/MR
WebSphere/M
Webb/M
Weber/M
Webern/M
Webster/MS
Wed/M
Weddell/M
Wedgwood/M
Wednesday/MS
Weeks/M
Wehrmacht/M
Wei/M
Weider/M
Weierstrass/M
Weill/M
Weinberg/M
Weirton/M
Weiss/M
Weissmuller/M
Weizmann/M
Welby/M
Weldon/M
Welland/M
Weller/M
Welles/M
Wellington/SM
Wells/M
Welsh/M
Welshman/M
Welshmen/M
Welshwoman
Wenatchee/M
Wendel/M
Wendell/M
Wendi/M
Wendy/M
Werner/M
Wernher/M
Wes
Wesak/M
Wesley/M
Wesleyan/M
Wessex/M
Wesson/M
West/SM
Westbrook/M
Western/MRS
Westinghouse/M
Westley/M
Westminster/M
Weston/M
Westphalia/M
Weyden/M
Wezen/M
Wharton/M
Wheaties/M
Wheatstone/M
Wheeler/M
Wheeling/M
Whig/SM
Whipple/M
Whirlpool/M
Whistler/M
Whitaker/M
Whitby/M
White/SM
Whitefield/M
Whitehall/M
Whitehead/M
Whitehorse/M
Whiteley/M
Whitfield/M
Whitley/M
Whitman/M
Whitney/M
Whitsunday/MS
Whittaker/M
Whittier/M
Wi-Fi/M
WiFi/M
Wicca/M
Wichita/M
Wiemar/M
Wiesel/M
Wiesenthal/M
Wiggins/M
Wigner/M
Wii/M
WikiPatents/M
Wikibooks/M
Wikileaks
Wikimedia/M
Wikinews/M
Wikipedia/M
Wikiquote/M
Wikisource/M
Wiktionary/M
Wilberforce/M
Wilbert/M
Wilbur/M
Wilburn/M
Wilcox/M
Wilda/M
Wilde/MR
Wilden/M
Wilder/M
Wiles/M
Wiley/M
Wilford/M
Wilfred/M
Wilfredo/M
Wilfrid/M
Wilhelm/M
Wilhelmina/M
Wilhelmine
Wilkerson/M
Wilkes/M
Wilkins/M
Wilkinson/M
Will/M
Willa/M
Willamette/M
Willard/M
Willem/M
Willemstad/M
Willey/M
Willi/MS
William/SM
Williams/M
Williamsburg/M
Williamson/M
Williamsport/M
Willie/M
Willis/M
Willy/M
Wilma/M
Wilmer/M
Wilmette/M
Wilmington/M
Wilson/M
Wilsonian/M
Wilton/M
Wiltshire/M
Wimbledon/M
Wimsey/M
Winchell/M
Winchester/MS
Windbreaker/M
Windex/M
Windham/M
Windhoek/M
Windows/M
Windsor/SM
Windward/M
Windy's
Winesap/M
Winfield/M
Winfred/M
Winfrey/M
Winifred/M
Winkle/M
Winn/M
Winna/M
Winne/M
Winnebago/M
Winnie/M
Winnifred/M
Winnipeg/M
Winny/M
Winona/M
Winslow/M
Winston/M
Winters/M
Winthrop/M
Wis
Wisc
Wisconsin/M
Wisconsinite/MS
Wise/M
Witt/M
Wittgenstein/M
Witty's
Witwatersrand/M
Wm/M
Wobegon/M
Wodehouse/M
Wolf/M
Wolfe/M
Wolff/M
Wolfgang/M
Wollongong/M
Wollstonecraft/M
Wolsey/M
Wolverhampton
Wonder/M
Wonderbra/M
Wong/M
Wood/SM
Woodard/M
Woodhull/M
Woodie/M
Woodland/M
Woodrow/M
Woods/M
Woodstock/M
Woodward/M
Woolf/M
Woolite/M
Woolongong/M
Woolworth/M
Wooster/M
Wooten/M
Worcester/SM
Worcestershire/M
WordPress/M
Worden/M
Wordsworth/M
Workman/M
WorldCat/M
Worms/M
Worthington/M
Wotan/M
Wovoka/M
Wozniak/M
Wozzeck/M
Wrangell/M
Wren/M
Wright/M
Wrigley/M
Wroclaw/M
Wu/M
Wuhan/M
Wurlitzer/M
Wyatt/M
Wycherley/M
Wycliffe/M
Wye/H
Wyeth/M
Wylie/M
Wyn/M
Wyndham/M
Wynn/M
Wynne/M
Wyo
Wyoming/M
Wyomingite/SM
X/M
XBL/M
XEmacs/M
XL/M
XLS/SM
XLSX/SM
XML
XPCOM/M
XPConnect/M
XPInstall/M
XS
XUL/M
XULRunner/M
XXL
Xamarin/M
Xanadu/M
Xanax/M
Xanthippe/M
Xavier/M
Xbox/M
Xe/SM
Xena/M
Xenakis/M
Xenia/M
Xenophon/M
Xerox/MS
Xerxes/M
Xhosa/M
Xi'an/M
Xian/SM
Xiaoping/M
Ximenes/M
Ximenez/M
Xingu/M
Xinjiang/M
Xiongnu/M
Xizang/M
Xmas/MS
Xochipilli/M
Xuzhou/M
Y/M
YMCA/M
YMHA
YMMV
YT
YWCA/M
YWHA
Yacc/M
Yahoo/M
Yahtzee/M
Yahweh/M
Yakima/M
Yakut/M
Yakutsk/M
Yale/M
Yalow/M
Yalta/M
Yalu/M
Yamagata/M
Yamaha/M
Yamoussoukro/M
Yancey/M
Yancy/M
Yang/M
Yangon/M
Yangtze/M
Yank/SM
Yankee/SM
Yaobang/M
Yaounde/M
Yaqui/M
Yardley/M
Yaren
Yaroslavl/M
Yasmin/M
Yataro/M
Yates/M
Yauco/M
Yb/M
Yeager/M
Yeats/M
Yehudi/M
Yekaterinburg/M
Yelena/M
Yellowknife/M
Yellowstone/M
Yeltsin/M
Yemen/M
Yemeni/SM
Yemenite
Yenisei/M
Yerevan/M
Yerkes/M
Yesenia/M
Yetta/M
Yevtushenko/M
Yggdrasil/M
Yiddish/M
Ymir/M
Ynez/M
Yoda/M
Yoknapatawpha/M
Yoko/M
Yokohama/M
Yolanda/M
Yolande/M
Yong/M
Yonkers/M
York/MR
Yorke/M
Yorker/M
Yorkie/M
Yorkshire/MS
Yorktown/M
Yoruba/M
Yosemite/M
Yoshi/M
Yoshiko/M
Yossarian/M
YouTube/M
Young/M
Youngstown/M
Ypres/M
Ypsilanti/M
Ysabel/M
Yuan/M
Yucatan/M
Yugo/M
Yugoslav/MS
Yugoslavia/M
Yugoslavian/SM
Yukon/M
Yul/M
Yule/SM
Yuletide/MS
Yuma/SM
Yunnan/M
Yuri/M
Yves/M
Yvette/M
Yvon/M
Yvonne/M
Yvor/M
Z/SMNXT
ZDNet/M
Zaccaria/M
Zach
Zachariah/M
Zacharias
Zachary/M
Zachery/M
Zack/M
Zagreb/M
Zahara/M
Zaire/M
Zairian
Zak/M
Zambezi/M
Zambia/M
Zambian/SM
Zamboni/M
Zamenhof/M
Zamora/M
Zane/M
Zanuck/M
Zanzibar/M
Zapata/M
Zaporozhye/M
Zapotec/M
Zappa/M
Zara/M
Zarah/M
Zarathustra/M
Zaria/M
Zea/M
Zealand/M
Zeb/M
Zebedee/M
Zebulon/M
Zechariah/M
Zedekiah/M
Zedong/M
Zeffirelli/M
Zeke/M
Zelda/M
Zelig/M
Zelma/M
Zen/M
Zena/M
Zenger/M
Zenia/M
Zeno/M
Zephaniah/M
Zephyrhills/M
Zephyrus/M
Zeppelin/M
Zest/M
Zeus/M
Zhang/M
Zhao/M
Zhdanov
Zhejiang/M
Zhengzhou/M
Zhivago/M
Zhou/M
Zhukov/M
Zia/M
Zibo/M
Ziegfeld/M
Ziegler/M
Ziff/M
Ziggy/M
Zika
Zimbabwe/M
Zimbabwean/SM
Zimmerman/M
Zinfandel/M
Zion/SM
Zionism/SM
Zionist/SM
Ziploc/M
Zita/M
Zn/M
Zoe/M
Zola/M
Zollverein/M
Zoloft/M
Zomba/M
Zora/M
Zorn/M
Zoroaster/M
Zoroastrian/MS
Zoroastrianism/SM
Zorro/M
Zosma/M
Zr/M
Zsigmondy/M
Zubenelgenubi/M
Zubeneschamali/M
Zukor/M
Zulu/SM
Zululand
Zune/M
Zuni/M
Zurich/M
Zwingli/M
Zworykin/M
Zyrtec/M
Zyuganov/M
Zzz
Z�rich/M
a/S
aah
aardvark/SM
ab/SDY
aback
abacus/MS
abaft
abalone/SM
abandon/LSDG
abandonment/M
abase/LGDS
abasement/M
abash/GLDS
abashed/UY
abashment/M
abate/LGDS
abated/U
abatement/M
abattoir/MS
abbe/SM
abbess/MS
abbey/MS
abbot/MS
abbr
abbrev/S
abbreviate/DSGNX
abbreviation/M
abb�/SM
abdicate/GNDSX
abdication/M
abdomen/SM
abdominal/Y
abduct/DSG
abductee/MS
abduction/SM
abductor/MS
abeam
aberrant
aberration/MS
aberrational
abet/S
abetted
abetter/SM
abetting
abettor/SM
abeyance/M
abhor/S
abhorred
abhorrence/M
abhorrent/Y
abhorring
abidance/M
abide/GS
abiding/Y
ability/IEMS
abject/YP
abjection/MS
abjectness/M
abjuration/SM
abjuratory
abjure/ZGDRS
abjurer/M
ablate/XGNVDS
ablation/M
ablative/MS
ablaze
able/UT
ableism
abler
abloom
ablution/SM
abnegate/GNDS
abnegation/M
abnormal/Y
abnormality/SM
aboard
abode/MS
abolish/ZGLDRS
abolishment/SM
abolition/M
abolitionism/M
abolitionist/SM
abominable
abominably
abominate/DSGNX
abomination/M
aboriginal/MS
aborigine/SM
aborning
abort/GVDS
abortion/MS
abortionist/MS
abortive/Y
abound/DSG
about
above/M
aboveboard
abracadabra/M
abrade/GDS
abrasion/MS
abrasive/MYPS
abrasiveness/M
abreast
abridge/DSLG
abridgement/MS
abridgment/MS
abroad
abrogate/XGNDS
abrogation/M
abrogator/MS
abrupt/TPRY
abruptness/M
abs/M
abscess/MDSG
abscissa/SM
abscission/M
abscond/ZGSDR
absconder/M
abseil/MDSG
absence/SM
absent/DYSG
absentee/MS
absenteeism/M
absentminded/YP
absentmindedness/M
absinthe/M
absolute/PMYTNS
absoluteness/M
absolution/M
absolutism/M
absolutist/MS
absolve/DSG
absorb/AZGDRS
absorbance/S
absorbancy/M
absorbency/M
absorbent/SM
absorbing/Y
absorption/M
absorptive
abstain/DRZGS
abstainer/M
abstemious/PY
abstemiousness/M
abstention/MS
abstinence/M
abstinent
abstract/GSPMDY
abstracted/YP
abstractedness/M
abstraction/SM
abstractness/MS
abstruse/YP
abstruseness/M
absurd/TPRY
absurdist/MS
absurdity/SM
absurdness/M
abundance/SM
abundant/Y
abuse's
abuse/EGVDS
abuser/MS
abusive/YP
abusiveness/M
abut/SL
abutment/MS
abutted
abutting
abuzz
abysmal/Y
abyss/MS
abyssal
ac
acacia/MS
academe/M
academia/M
academic/SM
academical/Y
academician/MS
academy/SM
acanthus/MS
accede/GDS
accelerate/GNXDS
acceleration/M
accelerator/SM
accelerometer/SM
accent/MDSG
accented/U
accentual
accentuate/GNDS
accentuation/M
accept/DSBG
acceptability/M
acceptableness/M
acceptably/U
acceptance/SM
acceptation/MS
accepted/U
access/MDSG
accessibility/IM
accessible/I
accessibly/I
accession/MDGS
accessorize/DSG
accessory/SM
accident/MS
accidental/SMY
acclaim/MDGS
acclamation/M
acclimate/DSGN
acclimation/M
acclimatization/M
acclimatize/DSG
acclivity/SM
accolade/SM
accommodate/XGNDS
accommodating/Y
accommodation/M
accompanied/U
accompaniment/MS
accompanist/SM
accompany/DSG
accomplice/SM
accomplish/DRSLZG
accomplished/U
accomplishment/MS
accord/GMDS
accordance/M
accordant
according/Y
accordion/MS
accordionist/MS
accost/GMDS
account/MDSBG
accountability/SM
accountable/U
accountancy/M
accountant/MS
accounted/U
accounting/M
accouter/SGD
accouterments/M
accoutre/DSG
accoutrements
accredit/SGD
accreditation/M
accredited/U
accrete/NDSX
accretion/M
accrual/MS
accrue/GDS
acct
acculturate/DSGN
acculturation/M
accumulate/XGNVDS
accumulation/M
accumulator/MS
accuracy/IM
accurate/IY
accurateness/M
accursed/P
accursedness/M
accusation/MS
accusative/MS
accusatory
accuse/ZGDRS
accuser/M
accusing/Y
accustom/DSG
accustomed/U
ace/DSMG
acerbate/DSG
acerbic
acerbically
acerbity/M
acetabular
acetabulum
acetaminophen/M
acetate/MS
acetic
acetone/M
acetonic
acetyl
acetylene/M
ache/DSMG
achene/MS
achievable/U
achieve/BLZGDRS
achievement/SM
achiever/M
aching/Y
achoo/M
achromatic
achy/TR
acid/SMY
acidic
acidify/GDS
acidity/M
acidosis/M
acidulous
acknowledge/DSGL
acknowledged/U
acknowledgement/MS
acknowledgment/SM
acme/SM
acne/M
acolyte/MS
aconite/MS
acorn/MS
acoustic/S
acoustical/Y
acoustics/M
acquaint/AGSD
acquaintance/SM
acquaintanceship/M
acquainted/U
acquiesce/DSG
acquiescence/M
acquiescent/Y
acquire/ZGBDRSL
acquirement/M
acquisition/MS
acquisitive/YP
acquisitiveness/M
acquit/S
acquittal/MS
acquitted
acquitting
acre/SM
acreage/MS
acrid/PTRY
acridity/M
acridness/M
acrimonious/YP
acrimoniousness/M
acrimony/M
acrobat/MS
acrobatic/S
acrobatically
acrobatics/M
acronym/MS
acrophobia/M
acropolis/MS
across
acrostic/SM
acrylamide
acrylic/MS
act's
act/ASDGV
actin
acting/M
actinium/M
action/ASM
actionable
activate/ICANGSD
activation/ICAM
activator/MS
active's
active/ISY
activeness/M
activism/M
activist/MS
activity/ISM
actor/AMS
actress/MS
actual/Y
actuality/SM
actualization/M
actualize/GDS
actuarial
actuary/SM
actuate/GNDS
actuation/M
actuator/SM
acuity/M
acumen/M
acupressure/M
acupuncture/M
acupuncturist/SM
acute/PMYTRS
acuteness/M
acyclovir/M
acyl
ad/SM
adage/MS
adagio/MS
adamant/MY
adapt/BZGVDRS
adaptability/M
adaptation/MS
adapter/M
adaption/S
adaptive/PY
adaptivity
add-on/S
add/SDRBZG
addend/MS
addenda
addendum/MS
adder/M
addible
addict/GVMDS
addiction/SM
addictive/P
addition/SM
additional/Y
additive/SM
addle/GDS
address's
address/AGDS
addressable
addressed/U
addressee/SM
adduce/GDS
adduction/SM
adenine/M
adenocarcinoma
adenoid/SM
adenoidal
adept/MYPS
adeptness/M
adequacy/IM
adequate/IY
adequateness/M
adhere/GDS
adherence/M
adherent/SM
adhesion/M
adhesive/PSM
adhesiveness/M
adiabatic
adieu/MS
adios
adipose
adi�s
adj
adjacency/M
adjacent/Y
adjectival/Y
adjective/MS
adjoin/GDS
adjourn/DGLS
adjournment/SM
adjudge/GDS
adjudicate/GNVXDS
adjudication/M
adjudicator/SM
adjudicatory
adjunct/MS
adjuration/MS
adjure/GDS
adjust/AGDSL
adjustable
adjuster/SM
adjustment/AMS
adjutant/SM
adman/M
admen
admin/MS
administer/DGS
administrate/XDSGNV
administration/M
administrative/Y
administrator/MS
admirably
admiral/MS
admiralty/M
admiration/M
admire/BZGDRS
admirer/M
admiring/Y
admissibility/IM
admissible/I
admissibly
admission/AM
admissions
admit/AS
admittance/M
admitted/Y
admitting/A
admix/GDS
admixture/SM
admonish/LDSG
admonishment/MS
admonition/MS
admonitory
ado/M
adobe/MS
adolescence/SM
adolescent/SM
adopt/AGVDS
adoptable
adoptee/MS
adopter/MS
adoption/SM
adorableness/M
adorably
adoration/M
adorbs
adore/BZGDRS
adorer/M
adoring/Y
adorn/LGDS
adorned/U
adornment/MS
adrenal/MS
adrenalin's
adrenaline/M
adrenergic
adrift
adroit/PY
adroitness/M
adsorb/SDG
adsorbent/MS
adsorption/SM
adulate/DSGN
adulation/M
adulator/MS
adulatory
adult/MS
adulterant/MS
adulterate/GNDS
adulterated/U
adulteration/M
adulterer/SM
adulteress/MS
adulterous
adultery/SM
adulthood/M
adumbrate/GNDS
adumbration/M
adv
advance/LDSMG
advancement/SM
advantage/EDSMG
advantageous/EY
advent/SM
adventitious/Y
adventure/DRSMZG
adventurer/M
adventuresome
adventuress/MS
adventurism
adventurist/S
adventurous/YP
adventurousness/M
adverb/SM
adverbial/SMY
adversarial
adversary/SM
adverse/PRYT
adverseness/M
adversity/SM
advert/SMDG
advertise/LZGDRS
advertised/U
advertisement/MS
advertiser/M
advertising/M
advertorial/SM
advice/M
advisability/IM
advisable/I
advisably
advise/LDRSZGB
advised/UY
advisement/M
adviser/M
advisor/SM
advisory/SM
advocacy/M
advocate/MGDS
advocator/MS
advt
adware/SM
adz/MS
adze/M
aegis/M
aeon/SM
aerate/DSGN
aeration/M
aerator/SM
aerial/SMY
aerialist/MS
aerie/MS
aerobatic/S
aerobatics/M
aerobic/S
aerobically
aerobics/M
aerodrome/MS
aerodynamic/S
aerodynamically
aerodynamics/M
aerogram/S
aerogramme/S
aeronautic/S
aeronautical
aeronautics/M
aerosol/MS
aerospace/M
aery
aesthete/MS
aesthetic/S
aesthetically
aestheticism/M
aesthetics/M
afar
affability/M
affable
affably
affair/MS
affect's
affect/EGVDS
affectation/SM
affected/UY
affecting/Y
affection/EM
affectionate/Y
affections
afferent
affiance/GDS
affidavit/SM
affiliate's
affiliate/EGNDS
affiliated/U
affiliation/EM
affiliations
affine
affinity/SM
affirm/AGDS
affirmation/AMS
affirmative/MYS
affix/GMDS
afflatus/M
afflict/GDS
affliction/SM
affluence/M
affluent/Y
afford/GDSB
affordability
affordable/U
affordably/U
affordance/S
afforest/EGSD
afforestation/M
affray/MS
affront/GMDS
afghan/MS
aficionado/MS
afield
afire
aflame
afloat
aflutter
afoot
aforementioned
aforesaid
aforethought
afoul
afraid/U
afresh
aft/RZ
afterbirth/M
afterbirths
afterburner/MS
aftercare/M
aftereffect/MS
afterglow/SM
afterimage/MS
afterlife/M
afterlives
aftermarket/MS
aftermath/M
aftermaths
afternoon/MS
aftershave/SM
aftershock/SM
aftertaste/SM
afterthought/SM
afterward/S
afterword/MS
again
against
agape/M
agar/M
agate/MS
agave/M
age/DSMGJ
ageing/SM
ageism/M
ageist/SM
ageless/YP
agelessness/M
agency/SM
agenda/SM
agenesis
agent/AMS
ageratum/M
agglomerate/DSMGNX
agglomeration/M
agglutinate/DSXGN
agglutination/M
aggrandize/GLDS
aggrandizement/M
aggravate/GNXDS
aggravating/Y
aggravation/M
aggregate/MGNDSX
aggregation/M
aggregator/SM
aggress/DSGV
aggression/M
aggressive/PY
aggressiveness/M
aggressor/SM
aggrieve/DSG
aggro
aghast
agile/Y
agility/M
aging/M
agitate/XGNDS
agitation/M
agitator/MS
agitprop/M
agleam
aglitter
aglow
agnostic/MS
agnosticism/M
ago
agog
agonist/S
agonize/GDS
agonizing/Y
agony/SM
agoraphobia/M
agoraphobic/MS
agrarian/MS
agrarianism/M
agree/EBLDS
agreeableness/EM
agreeably/E
agreeing/E
agreement/ESM
agribusiness/MS
agricultural/Y
agriculturalist/MS
agriculture/M
agriculturist/MS
agronomic
agronomist/MS
agronomy/M
aground
ague/M
ah
aha
ahchoo
ahead
ahem
ahoy
aid/SMDRG
aide/SM
aided/U
aider/M
aigrette/MS
ail/SDLG
aileron/SM
ailment/SM
aim/SMDG
aimless/YP
aimlessness/M
ain't
air/SMDJG
airbag/MS
airbase/SM
airbed/S
airborne
airbrush/MDSG
airbus/MS
aircraft/M
aircraftman
aircraftmen
aircrew/S
airdrome/S
airdrop/SM
airdropped
airdropping
airfare/SM
airfield/SM
airflow/M
airfoil/SM
airfreight/M
airgun/S
airhead/SM
airily
airiness/M
airing/M
airless/P
airlessness/M
airletter/S
airlift/SGMD
airline/RSMZ
airliner/M
airlock/SM
airmail/GSMD
airman/M
airmen
airplane/MS
airplay/M
airport/SM
airship/SM
airshow/S
airsick/P
airsickness/M
airspace/M
airspeed
airstrike/MS
airstrip/SM
airtight
airtime/M
airwaves/M
airway/MS
airwoman
airwomen
airworthiness/M
airworthy/P
airy/PTR
aisle/MS
aitch/MS
ajar
aka
akimbo
akin
al/YV
alabaster/M
alack
alacrity/M
alarm/GMDS
alarming/Y
alarmist/SM
alas
alb/SM
albacore/SM
albatross/MS
albeit
albinism/M
albino/MS
album/MNS
albumen/M
albumin/M
albuminous
alchemist/SM
alchemy/M
alcohol/SM
alcoholic/MS
alcoholically
alcoholism/M
alcove/MS
alder/MS
alderman/M
aldermen
alderwoman/M
alderwomen
ale/SM
aleatory
alehouse/SM
alembic/SM
alert/GMDYPS
alertness/M
alewife/M
alewives
alfalfa/M
alfresco
alga/M
algae
algal
algebra/SM
algebraic
algebraically
algorithm/SM
algorithmic
algorithmically
alias/GMDS
alibi/GMDS
alien/BGMDS
alienable/IU
alienate/DSGN
alienation/M
alienist/SM
alight/GDS
align/ALGDS
aligned/U
aligner/MS
alignment/AMS
alike/U
aliment/MDSG
alimentary
alimony/M
aliveness/M
aliyah/M
aliyahs
alkali/MS
alkalies
alkaline
alkalinity/M
alkalize/DSG
alkaloid/SM
alkoxy
alkyd/MS
all-nighter/S
all/M
allay/GDS
allegation/MS
allege/GDS
alleged/Y
allegiance/MS
allegoric
allegorical/Y
allegorist/MS
allegory/SM
allegretto/MS
allegro/MS
allele/MS
alleluia/SM
allergen/SM
allergenic
allergic
allergically
allergist/SM
allergy/SM
alleviate/DSGN
alleviation/M
alley/MS
alleyway/SM
alliance/SM
alligator/MS
alliterate/DSXGNV
alliteration/M
alliterative/Y
allocate/ADSGN
allocation/AM
allocations
allot/LS
allotment/SM
allotted
allotting
allover
allow/EGDS
allowable/U
allowably
allowance/SM
alloy/GMDS
alloyed/U
allspice/M
allude/GDS
allure/MGLDS
allurement/MS
alluring/Y
allusion/SM
allusive/PY
allusiveness/M
alluvial/M
alluvium/SM
ally/GDSM
almanac/SM
almighty
almond/MS
almoner/SM
almost
alms/M
almshouse/MS
aloe/SM
aloft
aloha/MS
alone
along
alongshore
alongside
aloof/PY
aloofness/M
aloud
alp/SM
alpaca/MS
alpha/MS
alphabet/SM
alphabetic
alphabetical/Y
alphabetization/SM
alphabetize/ZGDRS
alphabetizer/M
alphanumeric
alphanumerical/Y
alpine/S
already
alright
also
alt/S
altar/MS
altarpiece/SM
alter/GDBS
alterable/U
alteration/MS
altercation/SM
altered/U
alternate/DSMYGNVX
alternation/M
alternative/MYS
alternator/SM
although
altimeter/MS
altitude/MS
alto/SM
altogether
altruism/M
altruist/SM
altruistic
altruistically
alum/SM
alumina/M
aluminize/D
aluminum/M
alumna/M
alumnae
alumni
alumnus/M
alveolar/S
always
am/N
amalgam/SM
amalgamate/XGNDS
amalgamation/M
amanuenses
amanuensis/M
amaranth/M
amaranths
amaretto/M
amaryllis/MS
amass/GDS
amateur/SM
amateurish/YP
amateurishness/M
amateurism/M
amatory
amaze/LMGDS
amazement/M
amazing/Y
amazon/MS
amazonian
ambassador/SM
ambassadorial
ambassadorship/MS
ambassadress/MS
amber/M
ambergris/M
ambiance/MS
ambidexterity/M
ambidextrous/Y
ambience/MS
ambient
ambiguity/SM
ambiguous/UY
ambit
ambition/MS
ambitious/YP
ambitiousness/M
ambivalence/M
ambivalent/Y
amble/MZGDRS
ambler/M
ambrosia/M
ambrosial
ambulance/MS
ambulanceman
ambulancemen
ambulancewoman
ambulancewomen
ambulant
ambulate/DSXGN
ambulation/M
ambulatory/SM
ambuscade/MGDS
ambush/GMDS
ameba/MS
amebae
amebic
ameboid
ameliorate/GNVDS
amelioration/M
amen/B
amenability/M
amenably
amend/BLGDS
amendment/SM
amenity/SM
amerce/GLDS
amercement/SM
americium/M
amethyst/SM
amiability/M
amiable
amiably
amicability/M
amicable
amicably
amici
amicus
amid
amide/MS
amidship/S
amidst
amigo/MS
amine/S
amino
amir/SM
amiss
amitriptyline
amity/M
ammeter/SM
ammo/M
ammonia/M
ammonium
ammunition/M
amnesia/M
amnesiac/MS
amnesic/SM
amnesty/GDSM
amniocenteses
amniocentesis/M
amnion/MS
amniotic
amoeba/MS
amoebae
amoebic
amok
among
amongst
amontillado/SM
amoral/Y
amorality/M
amorous/YP
amorousness/M
amorphous/PY
amorphousness/M
amortization/SM
amortize/DSGB
amount/GMDS
amour/MS
amoxicillin
amp/SMDY
amperage/M
ampere/MS
ampersand/MS
amphetamine/SM
amphibian/MS
amphibious/Y
amphitheater/SM
amphora/M
amphorae
ampicillin
ample/TR
amplification/M
amplifier/M
amplify/NDRSXZG
amplitude/SM
ampoule/MS
ampule/MS
amputate/GNDSX
amputation/M
amputee/MS
amt
amuck
amulet/MS
amuse/LGDS
amusement/MS
amusing/Y
amygdala
amylase/M
amyloid
an/CS
anabolism/M
anachronism/SM
anachronistic
anachronistically
anaconda/SM
anaerobe/SM
anaerobic
anaerobically
anagram/MS
anal/Y
analgesia/M
analgesic/SM
analog/MS
analogical/Y
analogize/GDS
analogous/YP
analogousness/M
analogue/SM
analogy/SM
analysand/MS
analyses/A
analysis/AM
analyst/SM
analytic/S
analytical/Y
analyticalally
analyzable
analyze/ADSG
analyzer/SM
anapest/SM
anapestic/MS
anaphylactic
anaphylaxes
anaphylaxis
anarchic
anarchically
anarchism/M
anarchist/MS
anarchistic
anarchy/M
anathema/SM
anathematize/DSG
anatomic
anatomical/Y
anatomist/SM
anatomize/DSG
anatomy/SM
ancestor/SM
ancestral/Y
ancestress/MS
ancestry/SM
anchor/MDGS
anchorage/MS
anchorite/MS
anchorman/M
anchormen
anchorpeople
anchorperson/SM
anchorwoman/M
anchorwomen
anchovy/SM
ancient/SPMRYT
ancientness/M
ancillary/SM
and
andante/SM
andiron/SM
androgen/M
androgenic
androgynous
androgyny/M
android/SM
anecdotal/Y
anecdote/MS
anemia/M
anemic
anemically
anemometer/SM
anemone/SM
anent
anesthesia/M
anesthesiologist/SM
anesthesiology/M
anesthetic/SM
anesthetist/MS
anesthetization/M
anesthetize/GDS
aneurysm/SM
anew
angel/MS
angelfish/MS
angelic
angelica/M
angelical/Y
anger/GMDS
angina/M
angioplasty/SM
angiosperm/SM
angle/MZGDRS
angler/M
angleworm/MS
anglicism/S
anglicize/GDS
angling/M
anglophile/S
anglophone/S
angora/MS
angostura
angrily
angry/TR
angst/M
angstrom/MS
anguish/GMDS
angular
angularity/SM
angulation
anhydrous
aniline/M
anilingus
animadversion/MS
animadvert/GSD
animal/MS
animalcule/SM
animate/ADSGN
animated/Y
animation/AM
animations
animator/MS
anime/M
animism/M
animist/SM
animistic
animosity/SM
animus/M
anion/MS
anionic
anise/M
aniseed/M
anisette/M
ankh/M
ankhs
ankle/MS
anklebone/MS
anklet/MS
annalist/SM
annals/M
anneal/GDS
annelid/MS
annex/GMDS
annexation/MS
annihilate/DSGN
annihilation/M
annihilator/SM
anniversary/SM
annotate/DSXGNV
annotation/M
annotator/MS
announce/DRSLZG
announced/U
announcement/MS
announcer/M
annoy/GDS
annoyance/MS
annoying/Y
annual/MYS
annualize/DG
annuitant/SM
annuity/SM
annul/LS
annular
annulled
annulling
annulment/SM
annulus
annunciation/SM
anode/MS
anodize/GDS
anodyne/MS
anoint/GDLS
anointment/M
anomalous/Y
anomaly/SM
anon/S
anonymity/M
anonymization/MS
anonymize/DSG
anonymous/Y
anopheles/M
anorak/MS
anorectic/SM
anorexia/M
anorexic/MS
another/M
answer/BMDGS
answerable/U
answered/U
answerphone/S
ant/SMD
antacid/SM
antagonism/SM
antagonist/SM
antagonistic
antagonistically
antagonize/DSG
antarctic
ante/SM
anteater/MS
antebellum
antecedence/M
antecedent/SM
antechamber/SM
antedate/GDS
antediluvian
anteing
antelope/MS
antenatal
antenna/SM
antennae
anterior
anteroom/MS
anthem/MS
anther/MS
anthill/SM
anthologist/SM
anthologize/DSG
anthology/SM
anthracite/M
anthrax/M
anthropocentric
anthropoid/MS
anthropological/Y
anthropologist/SM
anthropology/M
anthropomorphic
anthropomorphically
anthropomorphism/M
anthropomorphize/DS
anthropomorphous
anti/SM
antiabortion
antiabortionist/MS
antiaircraft
antibacterial/MS
antibiotic/MS
antibody/SM
antic/MS
anticancer
antichrist/SM
anticipate/GNXDS
anticipated/U
anticipation/M
anticipatory
anticked
anticking
anticlerical
anticlimactic
anticlimactically
anticlimax/MS
anticline/SM
anticlockwise
anticoagulant/MS
anticommunism/M
anticommunist/SM
anticyclone/SM
anticyclonic
antidemocratic
antidepressant/MS
antiderivative/S
antidote/MS
antifa
antifascist/MS
antiferromagnetic
antifreeze/M
antigen/SM
antigenic
antigenicity/M
antihero/M
antiheroes
antihistamine/SM
antiknock/M
antilabor
antilogarithm/SM
antimacassar/MS
antimalarial
antimatter/M
antimicrobial
antimissile
antimony/M
antineutrino/SM
antineutron/MS
antinuclear
antioxidant/MS
antiparticle/SM
antipasti
antipasto/MS
antipathetic
antipathy/SM
antipersonnel
antiperspirant/SM
antiphon/SM
antiphonal/MYS
antipodal/S
antipodean/MS
antipodes/M
antipollution
antipoverty
antiproton/MS
antiquarian/SM
antiquarianism/M
antiquary/SM
antiquate/GDS
antique/DSMG
antiquity/SM
antirrhinum/S
antiscience
antisemitic
antisemitism/M
antisense
antisepsis/M
antiseptic/SM
antiseptically
antiserum/MS
antislavery
antisocial/Y
antispasmodic/MS
antisubmarine
antitank
antitheses
antithesis/M
antithetic
antithetical/Y
antitoxin/MS
antitrust
antivenin/MS
antivenom
antiviral/MS
antivirus/M
antivivisectionist/MS
antiwar
antler/MDS
antonym/SM
antonymous
antrum
antsy/TR
anus/MS
anvil/MS
anxiety/SM
anxious/YP
anxiousness/M
any
anybody/SM
anyhow
anymore
anyone/M
anyplace
anything/SM
anytime
anyway/S
anywhere
anywise
aorta/MS
aortic
apace
apart
apartheid/M
apartment/MS
apathetic
apathetically
apathy/M
apatite/M
apatosaurus/M
ape/DSMG
apelike
aperitif/MS
aperture/SM
apex/MS
aphasia/M
aphasic/MS
aphelia
aphelion/SM
aphid/MS
aphorism/MS
aphoristic
aphoristically
aphrodisiac/SM
apiarist/SM
apiary/SM
apical/Y
apiece
apish/Y
aplenty
aplomb/M
apocalypse/SM
apocalyptic
apocrypha/M
apocryphal/Y
apogee/MS
apolitical/Y
apologetic/U
apologetically
apologia/SM
apologist/MS
apologize/GDS
apology/SM
apoplectic
apoplexy/SM
apoptosis
apoptotic
apostasy/SM
apostate/SM
apostatize/GDS
apostle/MS
apostleship/M
apostolic
apostrophe/MS
apothecary/SM
apothegm/SM
apotheoses
apotheosis/M
app/SM
appall/GDS
appalling/Y
appaloosa/MS
apparatchik/S
apparatus/MS
apparel/MDGS
apparent/Y
apparition/SM
appeal/GBMDS
appealing/UY
appear/AESDG
appearance/EAMS
appease/LZGDRS
appeasement/SM
appeaser/M
appellant/SM
appellate/XN
appellation/M
append/GDS
appendage/SM
appendectomy/SM
appendices
appendicitis/M
appendix/MS
appertain/GDS
appetite/SM
appetizer/MS
appetizing/Y
applaud/ZGDRS
applauder/M
applause/M
apple/MS
applejack/M
applesauce/M
applet/MS
appliance/SM
applicability/M
applicable/I
applicably
applicant/SM
application/AM
applicator/SM
applier/MS
applique/DSM
appliqueing
appliqu�/SMG
appliqu�d
apply/ANXGDS
appoint/AELSVGZRD
appointee/SM
appointment/AESM
apportion/AGDLS
apportionment/AM
appose/GDS
apposite/YNVP
appositeness/M
apposition/M
appositive/SM
appraisal/AMS
appraise/ADSG
appraiser/MS
appreciable/I
appreciably/I
appreciate/DSXGNV
appreciated/U
appreciation/M
appreciative/Y
appreciator/MS
appreciatory
apprehend/GDS
apprehension/MS
apprehensive/YP
apprehensiveness/M
apprentice/DSMG
apprenticeship/MS
apprise/GDS
apprize/GDS
approach/GBMDS
approachable/UI
approbation/EM
approbations
appropriate/PYGNXDS
appropriated/U
appropriateness/IM
appropriation/M
appropriator/SM
approval/EM
approvals
approve/EGDS
approved/U
approving/EY
approx
approximate/DSXYGN
approximation/M
appurtenance/SM
appurtenant
apricot/MS
apron/MS
apropos
apse/SM
apt/IYPT
apter
aptitude/SM
aptness/IM
aqua/SM
aquaculture/M
aqualung/MS
aquamarine/SM
aquanaut/MS
aquaplane/MGDS
aquarium/MS
aquatic/SM
aquatically
aquatics/M
aquatint/S
aquavit/M
aqueduct/MS
aqueous
aquifer/SM
aquiline
arXiv/M
arabesque/MS
arability/M
arachnid/MS
arachnophobia
arbiter/SM
arbitrage/MZGDRS
arbitrager/M
arbitrageur/SM
arbitrament/SM
arbitrarily
arbitrariness/M
arbitrary/P
arbitrate/GNDS
arbitration/M
arbitrator/MS
arbor/MS
arboreal
arboretum/SM
arborvitae/SM
arbutus/MS
arc/SMDG
arcade/MS
arcane
arch/PZTGVMDRSY
archaeoastronomy/M
archaeologic
archaeological/Y
archaeologist/SM
archaeology/M
archaeomagnetic
archaeomagnetism
archaic
archaically
archaism/MS
archaist/MS
archangel/MS
archbishop/SM
archbishopric/SM
archdeacon/SM
archdiocesan
archdiocese/MS
archduchess/MS
archduke/MS
archenemy/SM
archeological/Y
archeologist/SM
archeology/M
archer/M
archery/M
archetypal
archetype/MS
archfiend/MS
archiepiscopal
archipelago/MS
archipelagoes
architect/SM
architectonic/S
architectonics/M
architectural/Y
architecture/MS
architrave/SM
archival
archive/DSMG
archivist/MS
archness/M
archway/SM
arctic/MS
ardent/Y
ardor/MS
arduous/YP
arduousness/M
are/SMB
area/SM
areal
aren't
arena/MS
areola/S
areolae
areolar
areolate
argent/M
arginine
argon/M
argosy/SM
argot/MS
arguable/IU
arguably/U
argue/ZGDRS
arguer/M
argument/MS
argumentation/M
argumentative/PY
argumentativeness/M
argyle/MS
aria/SM
arid/Y
aridity/M
aright
arise/GS
arisen
aristocracy/SM
aristocrat/SM
aristocratic
aristocratically
arithmetic/M
arithmetical/Y
arithmetician/MS
ark/SM
arm's
arm/EAGDS
armada/MS
armadillo/SM
armament/AEM
armaments
armature/MS
armband/MS
armchair/MS
armed/U
armful/MS
armhole/SM
armistice/SM
armlet/MS
armload/S
armor/ZGMDRS
armored/U
armorer/M
armorial
armory/SM
armpit/MS
armrest/SM
army/SM
aroma/MS
aromatherapist/MS
aromatherapy/M
aromatic/MS
aromatically
arose
around
arousal/M
arouse/GDS
arpeggio/MS
arr
arraign/DGSL
arraignment/SM
arrange/AESDLG
arrangement/AESM
arranger/SM
arrant
arras/MS
array/EGMDS
arrears/M
arrest/AGMDS
arrestee/S
arrhythmia/M
arrhythmic
arrhythmical
arrival/MS
arrive/GDS
arrogance/M
arrogant/Y
arrogate/GNDS
arrogation/M
arrow/MS
arrowhead/MS
arrowroot/M
arroyo/MS
arsed
arsenal/MS
arsenic/M
arsing
arson/M
arsonist/SM
art/SM
arterial
arteriole/MS
arteriosclerosis/M
artery/SM
artful/PY
artfulness/M
arthritic/MS
arthritis/M
arthroplasty
arthropod/MS
arthroscope/SM
arthroscopic
arthroscopy
artichoke/SM
article/MDS
articulacy/I
articular
articulate/YGNPDSX
articulateness/IM
articulation/M
artifact/SM
artifice/RSMZ
artificer/M
artificial/Y
artificiality/M
artillery/M
artilleryman/M
artillerymen
artiness/M
artisan/MS
artisanal/Y
artisanship/S
artist/MS
artiste/MS
artistic/I
artistically
artistry/M
artless/PY
artlessness/M
artsy/TR
artwork/MS
arty/PTR
arugula
arum/SM
aryl/SM
asap
asbestos/M
ascend/AGDS
ascendance/M
ascendancy/M
ascendant/SM
ascension/MS
ascent/MS
ascertain/GDSBL
ascertainment/M
ascetic/MS
ascetically
asceticism/M
ascot/MS
ascribe/GBDS
ascription/M
aseptic
aseptically
asexual/SY
asexuality/M
ash/MDNSG
ashamed/UY
ashcan/MS
ashlar/MS
ashore
ashram/MS
ashtray/SM
ashy/TR
aside/MS
asinine/Y
asininity/SM
ask/SDG
askance
asked/U
askew
aslant
asleep
asocial
asp/SMNX
asparagus/M
aspartame/M
aspect/MS
aspen/M
asperity/SM
aspersion/MS
asphalt/MDGS
asphodel/SM
asphyxia/M
asphyxiate/DSXGN
asphyxiation/M
aspic/MS
aspidistra/MS
aspirant/MS
aspirate/MGNDSX
aspiration/M
aspirational/Y
aspirator/SM
aspire/GDS
aspirin/MS
ass/MS
assail/GBDS
assailable/U
assailant/SM
assassin/SM
assassinate/GNXDS
assassination/M
assault/MDRGS
assay/ZGMDRS
assayer/M
assemblage/SM
assemble/ERZGSD
assembler/EM
assemblies
assembly/EAM
assemblyman/M
assemblymen
assemblywoman/M
assemblywomen
assent/GMDS
assert/AGVDS
assertion/AM
assertions
assertive/YP
assertiveness/M
assess/ALGDS
assessment/ASM
assessor/MS
asset/MS
asseverate/DSGN
asseveration/M
asshole/MS!
assiduity/M
assiduous/PY
assiduousness/M
assign's
assign/ALGDS
assignable
assignation/MS
assigned/U
assignee/M
assigner/MS
assignment/AMS
assignor/MS
assimilate/DSGN
assimilated/U
assimilation/M
assist/GVMDS
assistance/M
assistant/SM
assisted/U
assize/MS
assn
assoc
associate's
associate/EDSGNV
association/EM
associations
associativity
assonance/M
assonant/MS
assort/GLDS
assortative
assortment/MS
asst
assuage/GDS
assume/BGDS
assumption/SM
assumptive
assurance/ASM
assure/AGDS
assured/MYS
astatine/M
aster/EMS
asterisk/GMDS
astern
asteroid/MS
asthma/M
asthmatic/SM
asthmatically
astigmatic
astigmatism/SM
astir
astonish/DSLG
astonishing/Y
astonishment/M
astound/GDS
astounding/Y
astraddle
astrakhan/M
astral
astray
astride
astringency/M
astringent/SMY
astroarchaeology/SM
astrobiology/M
astrobleme/S
astrolabe/SM
astrologer/SM
astrological/Y
astrologist/MS
astrology/M
astronaut/MS
astronautic/S
astronautical
astronautics/M
astronomer/SM
astronomic
astronomical/Y
astronomy/M
astrophysical
astrophysicist/MS
astrophysics/M
astute/PYTR
astuteness/M
asunder
asylum/SM
asymmetric
asymmetrical/Y
asymmetry/SM
asymptomatic
asymptote/S
asymptotic
asymptotically
asynchronicity
asynchronous/Y
at
atavism/M
atavist/SM
atavistic
ataxia/M
ataxic/MS
ate
atelier/SM
atheism/M
atheist/MS
atheistic
atherosclerosis/M
atherosclerotic
athirst
athlete/MS
athletic/S
athletically
athleticism
athletics/M
athwart
atilt
atishoo
atlas/MS
atmosphere/MS
atmospheric/S
atmospherically
atmospherics/M
atoll/MS
atom/SM
atomic
atomically
atomize/ZGDRS
atomizer/M
atonal/Y
atonality/M
atone/LGDS
atonement/M
atop
atria
atrial
atrioventricular
atrium/M
atrocious/PY
atrociousness/M
atrocity/SM
atrophy/DSMG
atropine/M
attach/ALGDS
attache/BM
attached/U
attachment/AM
attachments
attach�/MS
attack/ZGMDRS
attacker/M
attain/AGDS
attainability/M
attainable/U
attainder/M
attainment/SM
attar/M
attempt's
attempt/ASDG
attend/SDRZG
attendance/SM
attendant/SM
attended/U
attendee/SM
attention/IM
attentions
attentive/IPY
attentiveness/IM
attenuate/DSGN
attenuated/U
attenuation/M
attest/SDG
attestation/SM
attested/U
attic/SM
attire/DSMG
attitude/SM
attitudinal
attitudinize/GDS
attn
attorney/MS
attract/SGVDB
attractant/MS
attraction/MS
attractive/UY
attractiveness/M
attribute/DSMGNVBX
attributed/U
attribution/M
attributive/MYS
attrition/M
attune/DSG
atty
atwitter
atypical/Y
aubergine/S
auburn/M
auction/MDGS
auctioneer/SM
audacious/YP
audaciousness/M
audacity/M
audibility/IM
audible/MS
audibly/I
audience/MS
audio/MS
audiological
audiologist/SM
audiology/M
audiometer/SM
audiophile/SM
audiotape/SM
audiovisual/S
audiovisuals/M
audit/BGMDS
auditability
auditee
audition/SMDG
auditor/MS
auditorium/SM
auditory
auger/MS
aught/MS
augment/DRZGS
augmentation/MS
augmentative
augmenter/M
augur/GMDS
augury/SM
august/PTRY
augustness/M
auk/SM
aunt/SM
auntie/SM
aura/MS
aural/Y
aurei
aureola/M
aureole/SM
aureus
auricle/SM
auricular
aurora/SM
auscultate/GNDSX
auscultation/M
auspice/SM
auspicious/IY
auspiciousness/M
austere/RYT
austerity/SM
austral
auteur/SM
authentic/IU
authentically
authenticate/XGNDS
authenticated/U
authentication/M
authenticity/M
author/SMDG
authoress/MS
authorial
authoritarian/MS
authoritarianism/M
authoritative/YP
authoritativeness/M
authority/SM
authorization/MS
authorize/AGDS
authorized/U
authorship/M
autism/M
autistic
auto/MS
autobahn/SM
autobiographer/SM
autobiographic
autobiographical/Y
autobiography/SM
autoclave/MS
autocomplete/S
autocorrect/SGMD
autocracy/SM
autocrat/SM
autocratic
autocratically
autocross
autodidact/SM
autograph/MDG
autographs
autoimmune
autoimmunity/M
automaker/SM
automata
automate/GNDS
automatic/SM
automatically
automation/M
automatism/M
automatize/GDS
automaton/SM
automobile/DSMG
automotive
autonomic
autonomous/Y
autonomy/M
autopilot/SM
autopsy/GDSM
autosuggestion
autoworker/MS
autumn/SM
autumnal
aux
auxiliary/SM
auxin/M
av/RZ
avail/BGMDS
availability/UM
available/U
avalanche/SM
avant-garde
avarice/M
avaricious/Y
avast
avatar/MS
avaunt
avdp
ave
avenge/ZGDRS
avenger/M
avenue/MS
average/MYGDS
averred
averring
averse/XN
aversion/M
avert/GDS
avg
avian
aviary/SM
aviation/M
aviator/MS
aviatrices
aviatrix/MS
avid/Y
avidity/M
avionic/S
avionics/M
avitaminosis/M
avo/S
avocado/SM
avocation/MS
avocational
avoid/SDGB
avoidable/U
avoidably/U
avoidance/M
avoidant
avoirdupois/M
avouch/DSG
avow/EDGS
avowal/ESM
avowed/Y
avuncular/Y
aw
await/GDS
awake/GS
awaken/AGDS
awakening/SM
award/GMDS
awardee/S
aware/UP
awareness/UM
awash
away
awe/DSMG
aweigh
awesome/YP
awesomeness/M
awestruck
awful/YP
awfuller
awfullest
awfulness/M
awhile
awkward/RYPT
awkwardness/M
awl/SM
awn/GJSM
awning/M
awoke
awoken
awol
awry
ax/MDSG
axe/M
axial/Y
axiom/SM
axiomatic
axiomatically
axis/M
axle/MS
axletree/SM
axolotl/SM
axon/MS
ayah/M
ayahs
ayatollah/M
ayatollahs
aye/SM
azalea/SM
azimuth/M
azimuths
azure/SM
b/KDT
baa/SMDG
babble/MZGDRS
babbler/M
babe/SM
babel/MS
baboon/MS
babushka/SM
baby/TGDRSM
babyhood/M
babyish
babysat
babysit/S
babysitter/MS
babysitting/M
baccalaureate/SM
baccarat/M
bacchanal/MS
bacchanalia/M
bacchanalian/MS
baccy
bachelor/SM
bachelorhood/M
bacillary
bacilli
bacillus/M
back/SJZGMDR
backache/MS
backbench/S
backbit
backbite/ZGRS
backbiter/M
backbitten
backboard/SM
backbone/MS
backbreaking
backchat
backcloth
backcloths
backcomb/DSG
backdate/GDS
backdoor
backdrop/MS
backer/M
backfield/SM
backfire/MGDS
backgammon/M
background/MRZS
backgrounder/M
backhand/MDRSZG
backhanded/Y
backhander/M
backhoe/MS
backing/M
backlash/MS
backless
backlight/MSG
backlighting/M
backlit
backlog/MS
backlogged
backlogging
backpack/ZGMDRS
backpacker/M
backpacking/M
backpedal/SDG
backrest/SM
backroom/S
backscratching/M
backseat/SM
backside/SM
backslapper/SM
backslapping/M
backslash/MS
backslid
backslide/RSZG
backslider/M
backspace/DSMG
backspin/M
backsplash/S
backstab/S
backstabber/MS
backstabbing
backstabby
backstage/M
backstair/S
backstop/SM
backstopped
backstopping
backstory/S
backstreet/S
backstretch/MS
backstroke/MGDS
backtalk/M
backtrack/SDG
backup/MS
backward/PSY
backwardness/M
backwash/M
backwater/SM
backwoods/M
backwoodsman/M
backwoodsmen
backyard/SM
bacon/M
bacteria/M
bacterial
bactericidal
bactericide/SM
bacteriologic
bacteriological
bacteriologist/SM
bacteriology/M
bacterium/M
bad/MYP
badass/S
badder
baddest
baddie/M
baddy/SM
bade
badge/MZGRS
badger/GMD
badinage/M
badlands/M
badman/M
badmen
badminton/M
badmouth/GD
badmouths
badness/M
baffle/MZGDRSL
bafflement/M
baffler/M
bag/SM
bagatelle/SM
bagel/MS
bagful/MS
baggage/M
bagged
baggie/M
baggily
bagginess/M
bagging
baggy/PTRS
bagpipe/MZRS
bagpiper/M
baguette/MS
bah
baht/SM
bail/SBGMD
bailey/S
bailiff/S
bailiwick/MS
bailout/SM
bailsman/M
bailsmen
bairn/MS
bait/SGMD
baize/M
bake/DRSMZG
baked/U
baker/M
bakery/SM
bakeshop/MS
baklava/M
baksheesh/M
balaclava/MS
balalaika/MS
balance's
balance/UDSG
balancer/S
balanitis
balanoposthitis
balboa/SM
balcony/SM
bald/STGPDRY
balderdash/M
baldfaced
baldness/M
baldric/SM
baldy/S
bale/DRSMZG
baleen/M
baleful/PY
balefulness/M
baler/M
balk/SGMD
balkanization
balkanize/GDS
balky/RT
ball/SGMD
ballad/SM
balladeer/MS
balladry/M
ballast/GSMD
ballcock/MS
ballerina/SM
ballet/SM
balletic
ballgame/MS
ballgirl/S
ballgown/S
ballistic/S
ballistically
ballistics/M
balloon/SGMD
balloonist/MS
ballot/SMDG
ballpark/MS
ballplayer/MS
ballpoint/MS
ballroom/MS
balls/DSG
ballsy/RT
bally
ballyhoo/SMDG
balm/SM
balminess/M
balmy/RTP
baloney/M
balsa/MS
balsam/SM
balsamic
baluster/SM
balustrade/MS
bamboo/SM
bamboozle/DSG
ban/SM
banal/Y
banality/SM
banana/SM
banc/S
band's
band/ESGD
bandage/DSMG
bandana/SM
bandanna/MS
bandbox/MS
bandeau/M
bandeaux
bandicoot/MS
bandit/SM
banditry/M
bandleader/S
bandmaster/SM
bandoleer/SM
bandolier/SM
bandsman/M
bandsmen
bandstand/SM
bandwagon/SM
bandwidth
bandwidths
bandy/DRSTG
bane/SM
baneful
bang/SGMDR
bangle/SM
bani
banish/GLDS
banishment/M
banister/SM
banjo/MS
banjoist/SM
bank/SZGBMDR
bankbook/SM
bankcard/SM
banker/M
banking/M
banknote/SM
bankroll/SGMD
bankrupt/SGMD
bankruptcy/SM
banned
banner/SM
banning
bannock/MS
banns/M
banquet/ZGMDRS
banqueter/M
banquette/SM
banshee/MS
bantam/SM
bantamweight/SM
banter/GSMD
bantering/Y
banyan/SM
banzai/SM
baobab/SM
bap/S
baptism/MS
baptismal
baptist/S
baptistery/SM
baptistry/SM
baptize/ZGDRS
baptized/U
baptizer/M
bar's
bar/ECAUTS
barb/SZGMDR
barbacoa
barbarian/SM
barbarianism/MS
barbaric
barbarically
barbarism/SM
barbarity/SM
barbarize/DSG
barbarous/Y
barbecue/DSMG
barbel/SM
barbell/MS
barber/GMD
barberry/SM
barbershop/MS
barbie/S
barbiturate/SM
barbwire/M
barcarole/SM
barcode/GDS
bard/SM
bardic
bare/DRSPYG
bareback/D
barefaced/Y
barefoot/D
barehanded
bareheaded
barelegged
bareness/M
barf/SGMDY
barfly/SM
bargain/MDRZGS
bargainer/M
barge/MGDS
bargeman/M
bargemen
barhop/S
barhopped
barhopping
barista/MS
baritone/MS
barium/M
bark's
bark/CSGD
barkeep/ZMRS
barkeeper/M
barker/SM
barley/M
barmaid/MS
barman/M
barmen
barmy/RT
barn/SM
barnacle/MDS
barney/S
barnstorm/SDRZG
barnstormer/M
barnyard/SM
barometer/MS
barometric
barometrically
baron/MS
baronage/MS
baroness/MS
baronet/MS
baronetcy/SM
baronial
barony/SM
baroque/M
barque/SM
barrack/MDGS
barracuda/SM
barrage/MGDS
barre/MGJDS
barred/UEC
barrel/GSMD
barrelled
barrelling
barren/TPSMR
barrenness/M
barrette/SM
barricade/MGDS
barrier/MS
barring/ECU
barrio/SM
barrister/MS
barroom/MS
barrow/SM
bartender/SM
barter/ZGSMDR
barterer/M
baryon/SM
baryonic
basal/Y
basalt/M
basaltic
base's
base/CDSLTG
baseball/SM
baseboard/MS
baseless
baseline/MS
basely
baseman/M
basemen
basement/CMS
baseness/M
baser
bash/GMDS
bashful/PY
bashfulness/M
bashing/M
basic/MS
basically
basil/M
basilar
basilica/MS
basilisk/MS
basin/MS
basinful/MS
basis/M
bask/SGD
basket/SM
basketball/MS
basketry/M
basketwork/M
basque/S
bass/MS
basset/SM
bassinet/MS
bassist/MS
basso/MS
bassoon/MS
bassoonist/SM
basswood/MS
bast/M
bastard/MS
bastardization/MS
bastardize/GDS
bastardy/M
baste/ZGNXDRS
baster/M
bastion/M
bat/SM
batch/MDSG
bate/KACGSD
bath/ZGMDRS
bathe/M
bather/M
bathetic
bathhouse/MS
bathing/M
bathmat/MS
bathos/M
bathrobe/SM
bathroom/SM
baths
bathtub/MS
bathwater
bathyscaph/MS
bathyscaphe/M
bathyscaphs
bathysphere/MS
batik/MS
batiste/M
batman/M
batmen
baton/MS
batsman/M
batsmen
battalion/SM
batted
batten/GSMD
batter/JZGSMDR
batterer/M
battery/SM
batting/M
battle/LDRSMZG
battleax/MS
battleaxe/M
battledore/SM
battledress
battlefield/MS
battlefront/MS
battleground/MS
battlement/SM
battler/M
battleship/SM
batty/RT
bauble/SM
baud/SM
bauxite/M
bawd/SM
bawdily
bawdiness/M
bawdy/PRT
bawl/SGMD
bay/SMDG
bayberry/SM
bayonet/SMDG
bayou/MS
bazaar/SM
bazillion/S
bazooka/SM
bbl
bdrm
be
beach/MDSG
beachcomber/SM
beachfront
beachhead/MS
beachwear/M
beacon/SM
bead/SGMD
beading/M
beadle/SM
beady/RT
beagle/SM
beak/SZMDR
beaker/M
beam/SGMD
bean/SGMD
beanbag/MS
beanfeast/S
beanie/SM
beanpole/MS
beansprout/S
beanstalk/MS
bear/SZGBJMR
bearable/U
bearably/U
beard/MDGS
beardless
bearer/M
bearing/M
bearish/PY
bearishness/M
bearlike
bearskin/MS
beast/MS
beastliness/M
beastly/TPRM
beat/SZGBMNRJ
beatable/U
beaten/U
beater/M
beatific
beatifically
beatification/M
beatify/GXNDS
beating/M
beatitude/SM
beatnik/MS
beau/SM
beaucoup
beaut/MS
beauteous/Y
beautician/SM
beautification/M
beautifier/M
beautiful/Y
beautify/NDRSZG
beauty/SM
beaux
beaver/SGMD
bebop/MS
becalm/GSD
became
because
beck/SM
beckon/SGD
becloud/GDS
become/S
becoming/UY
becquerel/S
bed/SM
bedaub/GSD
bedazzle/GDSL
bedazzlement/M
bedbug/SM
bedchamber/S
bedclothes/M
bedded
bedder
bedding/M
bedeck/GSD
bedevil/LGDS
bedevilment/M
bedfellow/SM
bedhead/S
bedim/S
bedimmed
bedimming
bedizen/GDS
bedlam/SM
bedpan/SM
bedpost/SM
bedraggle/GDS
bedridden
bedrock/SM
bedroll/SM
bedroom/SM
bedside/SM
bedsit/S
bedsitter/S
bedsore/SM
bedspread/SM
bedstead/SM
bedtime/SM
bee/RSMZGJ
beebread/M
beech/MS
beechnut/MS
beef/SGMD
beefburger/SM
beefcake/MS
beefiness/M
beefsteak/MS
beefy/RPT
beehive/MS
beekeeper/MS
beekeeping/M
beeline/MS
been
beep/SZGMDR
beeper/M
beer/M
beery/TR
beeswax/M
beet/SM
beetle/MGDS
beetroot/S
beeves
befall/SGN
befell
befit/S
befitted
befitting/Y
befog/S
befogged
befogging
before
beforehand
befoul/DGS
befriend/SGD
befuddle/GLDS
befuddlement/M
beg/S
began
begat
beget/S
begetter/S
begetting
beggar/MDYGS
beggary/M
begged
begging
begin/S
beginner/SM
beginning/MS
begone
begonia/SM
begot
begotten
begrime/DSG
begrudge/DSG
begrudging/Y
beguile/DRSZGL
beguilement/M
beguiler/M
beguiling/Y
beguine/SM
begum/MS
begun
behalf/M
behalves
behave/GDS
behavior/SM
behavioral/Y
behaviorism/M
behaviorist/MS
behead/DGS
beheld
behemoth/M
behemoths
behest/MS
behind/MS
behindhand
behold/NRZGS
beholder/M
behoove/DSG
beige/M
being/M
bejewel/SDG
belabor/SDG
belated/Y
belay/GDS
belch/ZGMDRS
belcher/M
beleaguer/GSD
belfry/SM
belie/DS
belief/EUM
beliefs
believable/U
believably/U
believe/EDRSZG
believer/EUMS
believing/U
belittle/LDSG
belittlement/M
bell/SGMD
belladonna/M
bellboy/SM
belle/MS
belled/A
belletrist/MS
belletristic
bellhop/SM
bellicose
bellicosity/M
belligerence/M
belligerency/M
belligerent/MYS
belling/A
bellman/M
bellmen
bellow/MDGS
bellwether/MS
belly/GDSM
bellyache/MGDS
bellybutton/SM
bellyful/MS
belong/JDGS
belonging/M
beloved/SM
below
belt/SGMD
beltway/SM
beluga/MS
belying
bemire/GDS
bemoan/DGS
bemuse/LGDS
bemused/Y
bemusement/M
bench/GMDS
benchmark/MS
bend/BSZGMR
bender/M
bendy/TR
beneath
benedictine
benediction/SM
benedictory
benefaction/SM
benefactor/MS
benefactress/MS
benefice/SM
beneficence/M
beneficent/Y
beneficial/Y
beneficiary/SM
benefit/SMDG
benevolence/SM
benevolent/Y
benighted/Y
benign/Y
benignant
benignity/M
bent/SM
bentonite
bentwood/M
benumb/DSG
benzene/M
benzine/M
benzyl
bequeath/DG
bequeaths
bequest/MS
berate/GDS
bereave/DSLG
bereavement/MS
bereft
beret/MS
berg/SM
beriberi/M
berk/S
berkelium/M
berm/SM
berry/GDSM
berrylike
berserk
berth/GMD
berths
beryl/MS
beryllium/M
beseech/ZGRS
beseecher/M
beseeching/Y
beseem/DSG
beset/S
besetting
beside/S
besiege/ZGDRS
besieger/M
besmear/DSG
besmirch/GDS
besom/MS
besot/S
besotted
besotting
besought
bespangle/DSG
bespatter/GSD
bespeak/SG
bespectacled
bespoke
bespoken
best/SGMD
bestial/Y
bestiality/M
bestiary/SM
bestir/S
bestirred
bestirring
bestow/DGS
bestowal/SM
bestrew/SDG
bestrewn
bestridden
bestride/SG
bestrode
bestseller/MS
bestselling
bet/SM
beta/SM
betake/GS
betaken
betcha
betel/M
bethink/SG
bethought
betide/GDS
betimes
betoken/GDS
betook
betray/DRZGS
betrayal/SM
betrayer/M
betroth/DG
betrothal/SM
betrothed/M
betroths
better/MDGLS
betterment/M
betting
bettor/MS
between
betwixt
bevel/GMDS
beverage/SM
bevvy/S
bevy/SM
bewail/DGS
beware/GDS
bewhiskered
bewigged
bewilder/LSGD
bewildering/Y
bewilderment/M
bewitch/GLDS
bewitching/Y
bewitchment/M
bey/SM
beyond
bezel/MS
bf
bhaji
bi/SMRZ
biannual/Y
bias/GMDS
biased/U
biassed
biassing
biathlon/SM
bib/SM
bible/MS
biblical/Y
bibliographer/MS
bibliographic
bibliographical/Y
bibliography/SM
bibliophile/SM
bibulous
bicameral
bicameralism/M
bicarb/MS
bicarbonate/MS
bicentenary/SM
bicentennial/SM
bicep/MS
biceps/M
bicker/MDRZGS
bickerer/M
biconcave
biconvex
bicuspid/MS
bicycle/DRSMZG
bicycler/M
bicyclist/SM
bid/SMG
biddable
bidden/U
bidder/MS
bidding/M
biddy/SM
bide/S
bidet/MS
bidirectional/Y
biennial/MYS
biennium/MS
bier/M
biff/SGD
bifida
bifocal/S
bifocals/M
bifurcate/XDSGN
bifurcation/M
big/P
bigamist/SM
bigamous
bigamy/M
bigger
biggest
biggie/MS
biggish
bighead/SM
bighearted/P
bigheartedness/M
bighorn/SM
bight/MS
bigmouth/M
bigmouths
bigness/M
bigot/MDS
bigotry/SM
bigwig/MS
bijection/S
bijou/M
bijoux
bike/DRSMZG
biker/M
bikini/MS
bilabial/MS
bilateral/Y
bilberry/S
bile/M
bilge/MS
bilingual/SMY
bilingualism/M
bilious/P
biliousness/M
bilirubin
bilk/SZGDR
bilker/M
bill/SBJGMD
billboard/MS
billet/GMDS
billfold/SM
billhook/S
billiard/S
billiards/M
billing/M
billingsgate/M
billion/MHS
billionaire/SM
billionth/M
billionths
billow/GMDS
billowy
billy/SM
billycan/S
bimbo/MS
bimetallic/SM
bimetallism/M
bimodal
bimonthly/SM
bin/SM
binary/SM
binaural
bind's
bind/AUGS
binder/MS
bindery/SM
binding/MS
bindweed/M
binge/MGDS
bingeable
bingeing
bingo/M
binman
binmen
binnacle/SM
binned
binning
binocular/MS
binomial/SM
bio/SM
biochem
biochemical/SMY
biochemist/MS
biochemistry/M
biocomputing/M
biodegradability/M
biodegrade/DSGB
biodiesel/M
biodiversity/M
bioethics/M
biofeedback/M
biofilm/MS
biog
biograph/ZGMDR
biographer/M
biographic
biographical/Y
biographs
biography/SM
biohacker/MS
biohacking
bioinformatic/MS
biol
biologic
biological/Y
biologist/MS
biology/SM
biomarker/MS
biomass/MS
biomechanics
biomedical
biomedicine
bionic/S
bionically
bionics/M
biophysical
biophysicist/MS
biophysics/M
biopic/MS
biopsy/GDSM
bioreactor/S
biorhythm/MS
biorobotics
biosecurity/M
biosensor/S
biosphere/SM
biostatistics
biosyntheses
biosynthesis
biotech/M
biotechnological
biotechnology/SM
biotherapy/SM
biotin/M
biotope/SM
biozone/SM
bipartisan
bipartisanship/M
bipartite
biped/MS
bipedal
biplane/MS
bipolar
bipolarity/M
biracial
birch/GMDS
bird/SZGMDR
birdbath/M
birdbaths
birdbrain/SMD
birdcage/S
birder/M
birdhouse/MS
birdie/MDS
birdieing
birdlike
birdlime/M
birdseed/M
birdsong
birdwatcher/SM
birdying
biretta/SM
birth/ZGMDR
birthday/MS
birther/M
birthmark/MS
birthplace/MS
birthrate/MS
birthright/MS
births/A
birthstone/SM
biscotti
biscotto
biscuit/SM
bisect/DGS
bisection/MS
bisector/SM
bisexual/MYS
bisexuality/M
bishop/MS
bishopric/SM
bismuth/M
bison/M
bisque/M
bistro/MS
bit/CSMG
bitch/GMDS
bitchily
bitchiness/M
bitchy/PRT
bitcoin/SM
bite/RSMZ
biter/M
biting/Y
bitmap/S
bitten
bitter/PMRYTS
bittern/SM
bitterness/M
bitters/M
bittersweet/MS
bitty/TR
bitumen/M
bituminous
bivalent
bivalve/SM
bivouac/MS
bivouacked
bivouacking
biweekly/SM
biyearly
biz/M
bizarre/Y
bk
bl/DG
blab/SM
blabbed
blabber/DGS
blabbermouth/M
blabbermouths
blabbing
black/PXTGMDNRYS
blackamoor/MS
blackball/SGMD
blackberry/GSM
blackbird/SM
blackboard/MS
blackcurrant/S
blacken/DG
blackface
blackguard/SM
blackhead/MS
blacking/M
blackish
blackjack/MDGS
blackleg/S
blacklist/MDSG
blackmail/MDRSZG
blackmailer/M
blackness/M
blackout/SM
blacksmith/MG
blacksmiths
blacksnake/SM
blackthorn/SM
blacktop/SM
blacktopped
blacktopping
bladder/MS
blade/MDS
blag/S
blagged
blagging
blah/M
blahs/M
blame/BMGDRS
blameable
blameless/YP
blamelessness/M
blameworthiness/M
blameworthy/P
blammo
blanch/GDS
blanche
blancmange/MS
bland/PTRY
blandish/DSLG
blandishment/SM
blandness/M
blank/TGPMDRYS
blanket/GMDS
blankness/M
blare/MGDS
blarney/SMDG
blase
blaspheme/ZGDRS
blasphemer/M
blasphemous/Y
blasphemy/SM
blast/ZGMDRS
blaster/M
blastoff/MS
blas�
blat/S
blatancy/SM
blatant/Y
blather/SMDG
blaze/MZGDRS
blazer/M
blazon/MDGS
bldg
bleach/MDRSZG
bleached/U
bleacher/M
bleak/TPRY
bleakness/M
blear
blearily
bleariness/M
bleary/PRT
bleat/GMDS
bleed/ZGRS
bleeder/M
bleeding/M
bleep/ZGMDRS
bleeper/M
blemish/GMDS
blemished/U
blench/DSG
blend/ZGMDRS
blender/M
bless/GDSJ
blessed/YP
blessedness/M
blessing/M
bletch
blew
blight/ZGMDRS
blimey
blimp/MS
blimpish
blind/PZTGMDRYS
blinder/M
blindfold/SMDG
blinding/Y
blindness/M
blindside/DSG
blini/MS
blink/ZGMDRS
blinker/MDG
blintz/MS
blintze/M
blip/SM
bliss/M
blissful/YP
blissfulness/M
blister/GMDS
blistering/Y
blistery
blithe/PYTR
blitheness/M
blither/G
blithesome
blitz/GMDS
blitzkrieg/MS
blivet/S
blizzard/SM
bloat/ZGDRS
bloatware
blob/SM
blobbed
blobbing
bloc/SM
block's
block/UGDS
blockade/MZGDRS
blockader/M
blockage/MS
blockbuster/SM
blockbusting/M
blockchain/MS
blocker/MS
blockhead/SM
blockhouse/MS
blog/SM
blogged
blogger/MS
blogging
blogroll/SM
bloke/MS
blokish
blond/PTMRS
blonde/MS
blondish
blondness/M
blood/GMDS
bloodbath/M
bloodbaths
bloodcurdling
bloodhound/SM
bloodily
bloodiness/M
bloodless/YP
bloodlessness/M
bloodletting/M
bloodline/SM
bloodmobile/MS
bloodshed/M
bloodshot
bloodstain/SMD
bloodstock/M
bloodstream/SM
bloodsucker/SM
bloodsucking
bloodthirstily
bloodthirstiness/M
bloodthirsty/RPT
bloody/PTGDRS
bloom/ZGMDRS
bloomer/M
bloop/ZGMDRS
blooper/M
blossom/GMDS
blossomy
blot/SM
blotch/GMDS
blotchy/TR
blotted
blotter/MS
blotting
blotto
blouse/MGDS
bloviate/GNDS
bloviator/MS
blow/SZGMR
blower/M
blowfly/SM
blowgun/MS
blowhard/MS
blowhole/S
blowjob/SM
blowlamp/S
blown
blowout/SM
blowpipe/SM
blowsy/RT
blowtorch/MS
blowup/MS
blowy/TR
blowzy/RT
blubber/GSMD
blubbery
bludgeon/MDGS
blue/DRSPMTG
bluebell/MS
blueberry/SM
bluebird/MS
bluebonnet/SM
bluebottle/SM
bluefish/MS
bluegill/MS
bluegrass/M
blueing/M
blueish
bluejacket/SM
bluejay/SM
bluejeans/M
blueness/M
bluenose/MS
bluepoint/MS
blueprint/MDGS
bluestocking/SM
bluesy/RT
bluet/MS
bluff/ZTGPMDRYS
bluffer/M
bluffness/M
bluing/M
bluish
blunder/MDRZGS
blunderbuss/MS
blunderer/M
blunt/PTGDRYS
bluntness/M
blur/SM
blurb/MS
blurred
blurriness/M
blurring
blurry/TRP
blurt/GDS
blush/ZGMDRS
blusher/M
bluster/MDRSZG
blusterer/M
blusterous
blustery
blvd
boa/SM
boar/SM
board/ZGMDRS
boarder/M
boarding/M
boardinghouse/MS
boardroom/MS
boardwalk/MS
boast/ZGMDRS
boaster/M
boastful/PY
boastfulness/M
boat/SZGMDR
boater/M
boathouse/MS
boating/M
boatload/S
boatman/M
boatmen
boatswain/SM
boatyard/S
bob/SM
bobbed
bobbin/MS
bobbing
bobble/MGDS
bobby/SM
bobbysoxer/SM
bobcat/MS
bobolink/SM
bobsled/SM
bobsledded
bobsledder/MS
bobsledding
bobsleigh/M
bobsleighs
bobtail/SM
bobwhite/MS
bocce/M
bocci/M
boccie/M
bock/M
bod/SMDG
bodacious
bode/S
bodega/MS
bodge/GDS
bodice/MS
bodily
bodkin/MS
body/DSM
bodybuilder/SM
bodybuilding/M
bodyguard/MS
bodysuit/SM
bodywork/M
boffin/S
boffo
bog/SM
boga
bogey/GMDS
bogeyman/M
bogeymen
bogged
bogging
boggle/GDS
boggy/TR
bogie/MS
bogon
bogosity
bogus
bogyman/M
bogymen
bohemian/SM
bohemianism/M
boil/SJZGMDR
boiler/M
boilermaker/SM
boilerplate/M
boink/GDS
boisterous/YP
boisterousness/M
bola/SM
bold/PTRY
boldface/DM
boldness/M
bole/SM
bolero/MS
bolivar/MS
bolivares
boll/SM
bollard/S
bollix/GMDS
bollocking/S
bollocks
bologna/M
bolshevik/SM
bolshie
bolshy
bolster/GMDS
bolt's
bolt/USGD
bolthole/S
bolus/MS
bomb/SJZGMDR
bombard/GDLS
bombardier/MS
bombardment/SM
bombast/M
bombastic
bombastically
bomber/M
bombproof
bombshell/SM
bombsite/S
bon/S
bona
bonanza/MS
bonbon/MS
bonce/S
bond/SGMD
bondage/M
bondholder/MS
bonding/M
bondman/M
bondmen
bondsman/M
bondsmen
bondwoman/M
bondwomen
bone/DRSMZG
bonehead/SMD
boneless
boner/M
boneshaker/S
boneyard
bonfire/MS
bong/SGMD
bongo/MS
bonhomie/M
boniness/M
bonito/MS
bonk/SZGD
bonnet/MS
bonny/TR
bono
bonobo/MS
bonsai/M
bonus/MS
bony/PTR
boo/SMDHG
boob/SGMD
booboo/MS
booby/SM
boodle/MS
booger/S
boogeyman/M
boogeymen
boogie/MDS
boogieing
boogieman/M
boohoo/GMDS
book/SBJGMD
bookbinder/SM
bookbindery/SM
bookbinding/M
bookcase/MS
bookend/MS
bookie/MS
booking/M
bookish
bookkeeper/MS
bookkeeping/M
booklet/MS
bookmaker/SM
bookmaking/M
bookmark/SMDG
bookmobile/SM
bookplate/MS
bookseller/MS
bookselling
bookshelf/M
bookshelves
bookshop/SM
bookstall/S
bookstore/MS
bookworm/SM
boolean
boom/SZGMDR
boombox/MS
boomerang/MDGS
boon/SM
boondocks/M
boondoggle/MZGDRS
boondoggler/M
boonies/M
boor/SM
boorish/PY
boorishness/MS
boost/ZGMDRS
booster/M
boot's
boot/ASGD
bootblack/SM
bootee/MS
booth/M
booths
bootie/M
bootlace/S
bootleg/MS
bootlegged
bootlegger/MS
bootlegging/M
bootless
bootstrap/MS
bootstrapped
bootstrapping
booty/SM
booze/MZGDRS
boozer/M
boozy/TR
bop/SM
bopped
bopping
borax/M
bordello/MS
border/GMDS
borderland/MS
borderline/MS
bore/DRSMZG
boredom/M
borehole/S
borer/M
boring/Y
born/IAU
borne
boron/M
borough/M
boroughs
borrow/SDRZGJ
borrower/M
borrowing/M
borsch/M
borscht/M
borstal/S
borzoi/SM
bosh/M
bosom's
bosom/US
bosomy
boson
boss/DSGM
bossily
bossiness/M
bossism/M
bossy/RTP
bosun/SM
bot/S
botanic
botanical/Y
botanist/SM
botany/M
botch/DRSZGM
botcher/M
both
bother/SMDG
botheration
bothered/U
bothersome
botnet/SM
bottle/DRSMZG
bottleneck/MS
bottler/M
bottom/SMDG
bottomless
botulinum
botulism/M
boudoir/SM
bouffant/SM
bougainvillea/MS
bough/M
boughs
bought
bougie/S
bouillabaisse/SM
bouillon/MS
boulder/SGMR
boulderer/M
boules
boulevard/SM
bounce/DRSMZG
bouncer/M
bouncily
bounciness/M
bouncy/RTP
bound/ASMGD
boundary/SM
bounden
bounder/SM
boundless/PY
boundlessness/M
bounteous/YP
bounteousness/M
bountiful/YP
bountifulness/M
bounty/SM
bouquet/SM
bourbon/SM
bourgeois/M
bourgeoisie/M
boustrophedon
bout/MS
boutique/SM
boutonniere/MS
boutonni�re/MS
bouzouki/MS
bovine/SM
bovver
bow/ZGSMDR
bowdlerization/MS
bowdlerize/DSG
bowed/U
bowel/SM
bower/M
bowl/MDRZGS
bowleg/SM
bowlegged
bowler/M
bowlful/SM
bowline/SM
bowling/M
bowman/M
bowmen
bowsprit/SM
bowstring/SM
bowwow/SM
box/ZGMDNRS
boxcar/SM
boxer/M
boxing/M
boxlike
boxroom/S
boxwood/M
boxy/RT
boy/SM
boycott/SGMD
boyfriend/MS
boyhood/SM
boyish/YP
boyishness/M
boysenberry/SM
bozo/MS
bpm
bps
bra/SM
brace/MZGDRS
bracelet/MS
bracer/M
bracero/MS
bracken/M
bracket/GMDS
brackish/P
brackishness/M
bract/MS
brad/SM
bradawl/S
bradycardia
brae/SM
brag/SM
braggadocio/SM
braggart/SM
bragged
bragger/MS
bragging
braid/GMDS
braiding/M
braille/M
brain/GMDS
brainchild/M
brainchildren/M
braininess/M
brainless/Y
brainpower
brainstorm/SMDG
brainstorming/M
brainteaser/SM
brainwash/DSG
brainwashing/M
brainwave/S
brainy/PTR
braise/GDS
brake/MGDS
brakeman/M
brakemen
bramble/MS
brambly
bran/M
branch/GMDS
branchlike
brand/ZGMDRS
branded/UA
brander/M
brandish/DSG
brandy/GDSM
brash/PTRY
brashness/M
brass/MS
brasserie/MS
brassiere/MS
brassily
brassiness/M
brassy/PTR
brat/SM
bratty/RT
bratwurst/SM
bravado/M
brave/GPMYDTRS
braveness/M
bravery/M
bravo/SM
bravura/SM
brawl/SDRZGM
brawler/M
brawn/M
brawniness/M
brawny/RTP
bray/DGSM
braze/DRSZG
brazen/SDYGP
brazenness/M
brazer/M
brazier/SM
breach/GMDS
bread/GMDHS
breadbasket/SM
breadboard/SM
breadbox/MS
breadcrumb/MS
breadfruit/SM
breadline/MS
breadth/M
breadths
breadwinner/SM
break/BMZGRS
breakable/MS
breakage/MS
breakaway/MS
breakdown/MS
breaker/M
breakeven/M
breakfast/MDGS
breakfront/MS
breakneck
breakout/MS
breakpoints
breakthrough/M
breakthroughs
breakup/SM
breakwater/SM
bream/MS
breast/SMDG
breastbone/MS
breastfed
breastfeed/GS
breastplate/SM
breaststroke/SM
breastwork/MS
breath/MDRSZGB
breathability
breathalyze/ZGDRS
breathe
breather/M
breathing/M
breathless/PY
breathlessness/M
breaths
breathtaking/Y
breathy/RT
bred/I
breech/MS
breed/SRZGM
breeder/M
breeding/IM
breeze/DSMG
breezeway/SM
breezily
breeziness/M
breezy/RTP
brethren
breve/SM
brevet/SM
brevetted
brevetting
breviary/SM
brevity/M
brew/MDRZGS
brewer/M
brewery/SM
brewpub/SM
briar/SM
bribe/DRSMZG
briber/M
bribery/M
brick/SMDG
brickbat/SM
brickie/S
bricklayer/MS
bricklaying/M
brickwork/M
brickyard/S
bridal/SM
bride/SM
bridegroom/SM
bridesmaid/MS
bridesman/M
bridesmen
bridge/DSMG
bridgeable/U
bridgehead/SM
bridgework/M
bridle/DSMG
bridled/U
bridleway/S
brie/MZR
brief's
brief/CSDTGJ
briefcase/SM
briefer
briefing/CM
briefly
briefness/M
brier/M
brig/MS
brigade/SM
brigadier/MS
brigand/SM
brigandage/M
brigantine/MS
bright/SPNRYXT
brighten/DRZG
brightener/M
brightness/M
brights/M
brill
brilliance/M
brilliancy/M
brilliant/MYS
brilliantine/M
brim/MS
brimful
brimless
brimmed
brimming
brimstone/M
brindle/DM
brine/M
bring/SRZG
bringer/M
brininess/M
brink/SM
brinkmanship/M
briny/RTP
brioche/SM
briquet/SM
briquette/MS
brisk/SDRYTGP
brisket/SM
briskness/M
bristle/DSMG
bristly/TR
britches/M
brittle/PRMT
brittleness/M
bro/SMH
broach/MDSG
broad/SMNRYXTP
broadband/M
broadcast/AMDGS
broadcaster/MS
broadcasting/M
broadcloth/M
broaden/DG
broadloom/M
broadminded
broadness/M
broadsheet/SM
broadside/MGDS
broadsword/SM
brocade/DSMG
broccoli/M
brochette/SM
brochure/MS
brogan/SM
brogue/SM
broil/SMDRZG
broiler/M
broke
broken/YP
brokenhearted/Y
brokenness/M
broker/SMDG
brokerage/MS
brolly/S
bromide/SM
bromidic
bromine/M
bronc/SM
bronchi
bronchial
bronchitic
bronchitis/M
bronchus/M
bronco/SM
broncobuster/SM
brontosaur/MS
brontosaurus/MS
bronze/DSMG
brooch/MS
brood/SMDRZG
brooder/M
broodily
brooding/MY
broodmare/MS
broody/RMPT
brook/SMDG
brooklet/SM
broom/SM
broomstick/MS
broth/MRZ
brothel/MS
brother/MY
brotherhood/MS
brotherliness/M
broths
brougham/SM
brought
brouhaha/SM
brow/MS
browbeat/SNG
brown/SMDRPTG
brownfield
brownie/MS
brownish
brownness/M
brownout/SM
brownstone/MS
browse/DRSMZG
browser/M
brr
bruin/SM
bruise/DRSMZG
bruiser/M
bruising/M
bruit/SDG
brunch/MDSG
brunet/SM
brunette/MS
brunt/M
brush/MDSG
brushoff/SM
brushstroke/S
brushwood/M
brushwork/M
brusque/RPYT
brusqueness/M
brutal/Y
brutality/SM
brutalization/M
brutalize/GDS
brute/SM
brutish/PY
brutishness/M
bu
bub/SM
bubble/DSMG
bubblegum/M
bubbly/RMT
bubo/M
buboes
bubonic
buccaneer/SGMD
buck/MDGS
buckaroo/SM
buckboard/MS
bucket/SGMD
bucketful/MS
buckeye/MS
buckle's
buckle/UDSG
buckler/MS
buckram/M
bucksaw/MS
buckshot/M
buckskin/MS
buckteeth
bucktooth/MD
buckwheat/M
buckyball/SM
bucolic/MS
bucolically
bud/SM
budded
budding/S
buddy/SM
budge/DSG
budgerigar/MS
budget/SGMD
budgetary
budgie/SM
buff/AMDGS
buffalo/MDG
buffaloes
buffer/SMDG
buffet/SMDGJ
buffoon/SM
buffoonery/M
buffoonish
bug's
bug/CS
bugaboo/SM
bugbear/SM
bugged/C
bugger/SMDG
buggery
bugging/C
buggy/RSMT
bugle/DRSMZG
bugler/M
build/SMRZGJ
builder/M
building/M
buildup/SM
built/AI
builtin
bulb/MS
bulbous
bulge/DSMG
bulgy/RT
bulimarexia/M
bulimia/M
bulimic/SM
bulk/MDGS
bulkhead/MS
bulkiness/M
bulky/RTP
bull/MDGS
bulldog/SM
bulldogged
bulldogging
bulldoze/ZGDRS
bulldozer/M
bullet/SMD
bulletin/MDGS
bulletproof/SDG
bullfight/SMRZG
bullfighter/M
bullfighting/M
bullfinch/MS
bullfrog/MS
bullhead/MDS
bullheaded/PY
bullheadedness/M
bullhorn/MS
bullion/M
bullish/YP
bullishness/M
bullock/SM
bullpen/SM
bullring/MS
bullseye/S
bullshit/MS!
bullshitted/!
bullshitter/SM!
bullshitting/!
bullwhip/S
bully/DSMG
bulrush/MS
bulwark/MS
bum/SM
bumbag/S
bumble/DRSZG
bumblebee/SM
bumbler/M
bumf
bummed
bummer/SM
bummest
bumming
bump/MDRZGS
bumper/M
bumph
bumpiness/M
bumpkin/MS
bumptious/PY
bumptiousness/M
bumpy/PRT
bun/SM
bunch/MDSG
bunchy/RT
bunco/SMDG
buncombe/M
bundle/DSMG
bung/MDGS
bungalow/MS
bungee/SM
bunghole/MS
bungle/DRSMZG
bungler/M
bunion/SM
bunk's
bunk/CDGS
bunker/SM
bunkhouse/SM
bunko/SMDG
bunkum/M
bunny/SM
bunt/MDGSJ
bunting/M
buoy/MDGS
buoyancy/M
buoyant/Y
bupkis
bur/SMY
burble/DSMG
burbs/M
burden's
burden/USGD
burdensome
burdock/M
bureau/SM
bureaucracy/SM
bureaucrat/MS
bureaucratic
bureaucratically
bureaucratization/M
bureaucratize/GDS
burg/MRZS
burgeon/DSG
burger/M
burgh/MRZ
burgher/M
burghs
burglar/MS
burglarize/GDS
burglarproof
burglary/SM
burgle/DSG
burgomaster/SM
burgundy/SM
burial/ASM
burka/SM
burl/MDS
burlap/M
burlesque/MGDS
burliness/M
burly/RPT
burn/MDRZGSB
burnable/SM
burner/M
burnish/ZGMDRS
burnisher/M
burnoose/MS
burnous/MS
burnout/MS
burnt
burp/MDGS
burr/MDGS
burrito/MS
burro/SM
burrow/SMDRZG
burrower/M
bursa/M
bursae
bursar/SM
bursary/SM
bursitis/M
burst/SMG
bury/ADSG
bus/AMS
busboy/SM
busby/SM
bused
busgirl/MS
bush/MDSGJ
bushel/SGMD
bushiness/M
bushing/M
bushman/M
bushmaster/SM
bushmen
bushwhack/DRSZG
bushwhacker/M
bushy/RPT
busily
business/MS
businesslike
businessman/M
businessmen
businessperson/SM
businesswoman/M
businesswomen
busing/M
busk/DRZGS
buskin/SM
busload/S
buss/MDSG
bust/MDRZGS
buster/M
bustle/DSMG
busty/RZT
busy/DRSTGP
busybody/SM
busyness/M
busywork/M
but/ACS
butane/M
butch/MRSZ
butcher/MDG
butchery/SM
butler/SM
butt/MDRZGS
butte/SM
butted/A
butter/MDG
butterball/MS
buttercream
buttercup/SM
butterfat/M
butterfingered
butterfingers/M
butterfly/GDSM
buttermilk/M
butternut/SM
butterscotch/M
buttery/TRSM
butting/A
buttock/SM
button's
button/USDG
buttonhole/DSMG
buttonwood/MS
buttress/MDSG
butty/S
buxom
buy/ZGSMR
buyback/SM
buyer/M
buyout/SM
buzz/MDRSZG
buzzard/MS
buzzer/M
buzzkill/SM
buzzword/SM
bx
bxs
by/M
bye/SM
bygone/SM
bylaw/SM
byline/SM
bypass/GMDS
bypath/M
bypaths
byplay/M
byproduct/MS
byre/S
byroad/SM
bystander/MS
byte/MS
byway/SM
byword/SM
byzantine
c/IES
cDNA
ca
cab/SMRZ
cabal/MS
cabala/M
caballero/MS
cabana/SM
cabaret/SM
cabbage/MS
cabbed
cabbie/M
cabbing
cabby/SM
cabdriver/SM
cabin/MS
cabinet/SM
cabinetmaker/MS
cabinetmaking/M
cabinetry/M
cabinetwork/M
cable/MGDS
cablecast/GMS
cablegram/MS
cabochon/SM
caboodle/M
caboose/SM
cabriolet/SM
cabstand/SM
cacao/MS
cache/MGDS
cachepot/SM
cachet/MS
cackle/MZGDRS
cackler/M
cacophonous
cacophony/SM
cacti
cactus/M
cad/SM
cadaver/SM
cadaverous
caddie/MDS
caddish/YP
caddishness/M
caddying
cadence/DSM
cadenza/SM
cadet/MS
cadge/ZGDRS
cadger/M
cadmium/M
cadre/MS
caducei
caduceus/M
caesarean/MS
caesura/SM
cafe/SM
cafeteria/MS
cafetiere/S
caff/CS
caffeinated
caffeine/M
caftan/MS
caf�/SM
cage/DSMG
cagey
cagier
cagiest
cagily
caginess/M
cagoule/S
cahoot/MS
caiman/MS
cairn/MS
caisson/SM
caitiff/SM
cajole/ZGLDRS
cajolement/M
cajoler/M
cajolery/M
cake/DSMG
cakewalk/SM
cal
calabash/MS
calaboose/SM
calamari/SM
calamine/M
calamitous/Y
calamity/SM
calcareous
calciferous
calcification/M
calcify/GNDS
calcimine/DSMG
calcine/DSG
calcite/M
calcium/M
calculable/I
calculate/AGNVDSX
calculated/Y
calculating/Y
calculation/AM
calculator/SM
calculi
calculus/M
caldera/SM
caldron/SM
calendar/MDGS
calf/M
calfskin/M
caliber/SM
calibrate/GNDSX
calibration/M
calibrator/SM
calico/MS
calicoes
californium/M
caliper/SGMD
caliph/M
caliphate/MS
caliphs
calisthenic/S
calisthenics/M
calk/SGMD
call/ASGMD
calla/MS
callable
callback/MS
called/U
caller/MS
calligrapher/SM
calligraphic
calligraphist/MS
calligraphy/M
calling/SM
calliope/MS
callosity/SM
callous/PGDSY
callousness/M
callow/RPT
callowness/M
callus/MDSG
calm/PSTGMDRY
calmness/M
caloric
calorie/MS
calorific
calumet/MS
calumniate/GNDS
calumniation/M
calumniator/MS
calumnious
calumny/SM
calve/GDS
calypso/MS
calyx/MS
cam/SM
camaraderie/M
camber/MDSG
cambial
cambium/SM
cambric/M
camcorder/SM
came
camel/MS
camelhair/M
camellia/MS
cameo/MS
camera/MS
cameraman/M
cameramen
camerapeople
cameraperson
camerawoman/M
camerawomen
camerawork
camiknickers
camisole/SM
camomile/SM
camouflage/MZGDRS
camouflager/M
camp's
camp/CSGD
campaign/SMDRZG
campaigner/M
campanile/SM
campanologist/MS
campanology/M
camper/MS
campfire/SM
campground/SM
camphor/M
camping/M
campsite/SM
campus/MS
campy/TR
camshaft/SM
can't
can/SMDRZG
canal/MS
canalization/M
canalize/GDS
canape/MS
canap�/MS
canard/MS
canary/SM
canasta/M
cancan/MS
cancel/DRSZG
canceler/M
cancellation/SM
cancelled/U
canceller/M
cancelling
cancelous
cancer/MS
cancerous
candelabra/SM
candelabrum/M
candid/YP
candida
candidacy/SM
candidate/MS
candidature/SM
candidness/M
candle/MZGDRS
candlelight/M
candlelit
candlepower/M
candler/M
candlestick/MS
candlewick/SM
candor/M
candy/GDSM
candyfloss
cane/SM
canebrake/MS
caner/M
canine/MS
canister/SM
canker/GMDS
cankerous
cannabis/MS
canned
cannelloni/M
cannery/SM
cannibal/SM
cannibalism/M
cannibalistic
cannibalization/M
cannibalize/GDS
cannily/U
canniness/M
canning
cannon/GMDS
cannonade/MGDS
cannonball/SM
cannot
canny/UTR
canoe/MDS
canoeing
canoeist/SM
canola/M
canon/MS
canonical/Y
canonicalization/MS
canonicalize/GDS
canonization/SM
canonize/DSG
canoodle/DSG
canopy/GDSM
canst
cant's
cant/CZRDGS
cantabile
cantaloupe/SM
cantankerous/PY
cantankerousness/M
cantata/MS
canteen/MS
canter/CM
cantered
cantering
canticle/MS
cantilever/MDGS
cantina/S
canto/MS
canton/MLS
cantonal
cantonment/MS
cantor/MS
canvas/MGDS
canvasback/SM
canvass/MDRSZG
canvasser/M
canyon/MGS
cap/SMDRBZ
capabilities
capability/IM
capable/I
capably/I
capacious/PY
capaciousness/M
capacitance/M
capacities
capacitor/SM
capacity/IM
caparison/MDGS
cape/SM
caper/GMD
capeskin/M
capillarity/M
capillary/SM
capita
capital/MSY
capitalism/M
capitalist/SM
capitalistic
capitalistically
capitalization/M
capitalize/ADSG
capitation/CSM
capitol/SM
capitulate/ADSXGN
capitulation/AM
caplet/MS
capo/SM
capon/MS
capped/UA
capping/UA
cappuccino/SM
caprice/SM
capricious/PY
capriciousness/M
capsicum/SM
capsize/DSG
capstan/SM
capstone/MS
capsular
capsule/DSMG
capsulize/DSG
capt
captain/SMDG
captaincy/SM
captcha/S
caption/SMDG
captious/YP
captiousness/M
captivate/DSGN
captivation/M
captivator/SM
captive/SM
captivity/SM
captor/MS
capture/ADSMG
car/SMDRZG
carabiner
carafe/MS
caramel/SM
caramelize/DSG
carapace/SM
carat/MS
caravan/SM
caravansarai/S
caravansary/SM
caravanserai/M
caravel/SM
caraway/SM
carbide/SM
carbine/SM
carbohydrate/SM
carbolic
carbon/MS
carbonaceous
carbonate/MGNDS
carbonation/M
carboniferous
carbonize/GDS
carborundum/M
carboxylic
carboy/MS
carbs
carbuncle/SM
carbuncular
carburetor/SM
carcass/MS
carcinogen/SM
carcinogenic/MS
carcinogenicity/M
carcinoma/MS
card/ESGMD
cardamom/SM
cardamon/S
cardboard/M
carder/MS
cardholder/S
cardiac
cardie/S
cardigan/SM
cardinal/SMY
cardinality/S
cardio
cardiogram/SM
cardiograph/M
cardiographs
cardiologist/MS
cardiology/M
cardiomegaly
cardiomyopathy
cardiopulmonary
cardiovascular
cardsharp/MRZS
cardsharper/M
care/SM
careen/DGS
career/MDGS
careerism
careerist/SM
carefree
careful/YP
carefuller
carefullest
carefulness/M
caregiver/SM
careless/PY
carelessness/M
carer/M
caress/MDSG
caret/MS
caretaker/MS
careworn
carfare/M
cargo/M
cargoes
carhop/MS
caribou/SM
caricature/MGDS
caricaturist/SM
caries/M
carillon/SM
caring/M
carious
carjack/JSDRZG
carjacker/M
carjacking/M
carload/SM
carmaker/S
carmine/SM
carnage/M
carnal/Y
carnality/M
carnation/IMS
carnelian/MS
carney/MS
carnie/M
carnitas
carnival/MS
carnivora
carnivore/SM
carnivorous/PY
carnivorousness/M
carny/SM
carob/MS
carol/ZGMDRS
caroler/M
carom/GMDS
carotene/M
carotid/SM
carousal/SM
carouse/DRSMZG
carousel/SM
carouser/M
carp/SZGMDR
carpal/MS
carpel/MS
carpenter/MDGS
carpentry/M
carper/M
carpet/MDGS
carpetbag/MS
carpetbagged
carpetbagger/MS
carpetbagging
carpeting/M
carpi
carpool/SMDG
carport/SM
carpus/M
carrel/MS
carriage/SM
carriageway/S
carrier/M
carrion/M
carrot/MS
carroty
carry/ZGDRSM
carryall/SM
carrycot/S
carryout
carryover/MS
carsick/P
carsickness/M
cart/SZGMDR
cartage/M
carte/S
cartel/MS
carter/M
carthorse/SM
cartilage/SM
cartilaginous
cartload/SM
cartographer/SM
cartographic
cartography/M
carton/MS
cartoon/SMDG
cartoonist/MS
cartridge/MS
cartwheel/GMDS
carve/JZGDRS
carver/M
carvery/S
carving/M
caryatid/MS
casaba/MS
cascade/DSMG
cascara/SM
case/LDSJMG
casebook/S
cased/U
caseharden/DGS
casein/M
caseload/MS
casement/MS
casework/ZMR
caseworker/M
cash/GMDS
cashback/M
cashbook/MS
cashew/MS
cashier/GSMD
cashless
cashmere/M
casing/M
casino/MS
cask/SM
casket/MS
cassava/SM
casserole/DSMG
cassette/MS
cassia/MS
cassock/SM
cassowary/SM
cast/ASGM
castanet/MS
castaway/MS
caste/JMZRS
castellated
caster/M
castigate/DSGN
castigation/M
castigator/SM
casting/AM
castle/MGDS
castoff/SM
castor/MS
castrate/GNXDS
castration/M
casual/PMYS
casualness/M
casualty/SM
casuist/SM
casuistic
casuistry/M
cat/SM
cataclysm/MS
cataclysmal
cataclysmic
catacomb/SM
catafalque/MS
catalepsy/M
cataleptic/MS
catalog/ZGSMDR
cataloger/M
catalogue/DSMG
catalogued/U
catalpa/SM
catalyses
catalysis/M
catalyst/MS
catalytic/M
catalyze/GDS
catamaran/SM
catapult/GMDS
cataract/MS
catarrh/M
catastrophe/MS
catastrophic
catastrophically
catatonia/M
catatonic/SM
catbird/SM
catboat/SM
catcall/GSMD
catch/ZGJLMRS
catchall/MS
catcher/M
catchment/MS
catchpenny
catchphrase/SM
catchword/MS
catchy/RT
catechism/SM
catechist/SM
catechize/DSG
categorical/Y
categorization/MS
categorize/GDS
category/SM
cater/ZGJDRS
catercorner
caterer/M
caterpillar/MS
caterwaul/SMDG
catfish/MS
catgut/M
catharses
catharsis/M
cathartic/SM
cathedral/SM
catheter/SM
catheterize/DSG
cathode/SM
cathodic
catholic
catholicity/M
cation/MS
catkin/MS
catlike
catnap/MS
catnapped
catnapping
catnip/M
catsuit/S
catsup/MS
cattail/SM
catted
cattery/S
cattily
cattiness/M
catting
cattle/M
cattleman/M
cattlemen
catty/TPR
catwalk/SM
caucus/MDSG
caudal/Y
caught/U
cauldron/MS
cauliflower/SM
caulk/ZGMDRS
caulker/M
causal/Y
causality/SM
causation/M
causative
cause/MZGDRS
causeless
causer/M
causerie/SM
causeway/SM
caustic/SM
caustically
causticity/M
cauterization/M
cauterize/GDS
caution/SMDG
cautionary
cautious/IY
cautiousness/M
cavalcade/MS
cavalier/SMY
cavalry/SM
cavalryman/M
cavalrymen
cave/DRSMZG
caveat/MS
caveman/M
cavemen
cavern/MS
cavernous/Y
caviar/M
cavil/ZGJMDRS
caviler/M
caving/M
cavitation
cavity/FSM
cavort/DGS
caw/SMDG
cay/CSM
cayenne/M
cayuse/MS
cc
cease/CMGDS
ceasefire/MS
ceaseless/YP
ceaselessness/M
ceca
cecal
cecum/M
cedar/MS
cede/FAGSD
ceder/MS
cedilla/SM
ceilidh
ceilidhs
ceiling/MS
celandine/M
celeb/S
celebrant/SM
celebrate/DSGNX
celebration/M
celebrator/SM
celebratory
celebrity/SM
celeriac
celerity/M
celery/M
celesta/MS
celestial/Y
celibacy/M
celibate/MS
cell/SMD
cellar/MS
cellist/SM
cellmate/SM
cello/MS
cellophane/M
cellphone/MS
cellular/SM
cellulite/M
cellulitis
celluloid/M
cellulose/M
cement/MDRZGS
cementer/M
cementum/M
cemetery/SM
cenobite/MS
cenobitic
cenotaph/M
cenotaphs
censer/MS
censor/MDGS
censored/U
censorial
censorious/PY
censoriousness/M
censorship/M
censure/BDRSMZG
censurer/M
census/MDSG
cent/SZMR
centaur/SM
centavo/SM
centenarian/MS
centenary/SM
centennial/MYS
center/MDG
centerboard/SM
centerfold/MS
centerpiece/MS
centigrade
centigram/SM
centiliter/MS
centime/SM
centimeter/MS
centipede/SM
central/SMY
centralism
centralist
centrality/M
centralization/CM
centralize/CGDS
centralizer/MS
centrifugal/Y
centrifuge/DSMG
centripetal/Y
centrism/M
centrist/MS
centurion/SM
century/SM
cephalic
ceramic/SM
ceramicist/SM
ceramics/M
ceramist/MS
cereal/MS
cerebellar
cerebellum/SM
cerebra
cerebral
cerebrate/GNDS
cerebration/M
cerebrovascular
cerebrum/MS
cerement/MS
ceremonial/SMY
ceremonious/UY
ceremoniousness/M
ceremony/SM
cerevisiae/MS
cerise/M
cerium/M
cermet/M
cert/S
certain/UY
certainty/USM
certifiable
certifiably
certificate/MGDS
certification/AMS
certify/CDSNXG
certitude/IM
certitudes
cerulean/M
cervical
cervices
cervix/M
cesarean/MS
cesium/M
cessation/MS
cession/KAFSM
cesspit/S
cesspool/MS
cetacean/MS
ceteris
cf
cg
ch/IFVT
chad/S
chafe/GDS
chaff/GMDS
chaffinch/MS
chagrin/GSMD
chain's
chain/UGDS
chainsaw/MDGS
chair/GMDS
chairlift/MS
chairman/M
chairmanship/SM
chairmen
chairperson/SM
chairwoman/M
chairwomen
chaise/MS
chalcedony/M
chalet/MS
chalice/SM
chalk/GMDS
chalkboard/SM
chalkiness/M
chalky/PRT
challenge/DRSMZG
challenged/U
challenger/M
challis/M
chamber/SMD
chamberlain/MS
chambermaid/MS
chambray/M
chameleon/SM
chamois/M
chamomile/MS
champ/ZGMDS
champagne/MS
champertous
champerty
champion/GMDS
championship/MS
chance/MGDS
chancel/SM
chancellery/SM
chancellor/MS
chancellorship/M
chancery/SM
chanciness/M
chancre/SM
chancy/PRT
chandelier/SM
chandler/MS
change/MZGDRS
changeability/M
changeable/P
changeableness/M
changeably
changed/U
changeless/Y
changeling/SM
changeover/SM
changer/M
changing/U
channel/GSMD
channelization/M
channelize/DSG
chanson/SM
chant/ZGMDRS
chanter/M
chanteuse/MS
chantey/SM
chanticleer/MS
chaos/M
chaotic
chaotically
chap/SM
chaparral/SM
chapati/S
chapatti/S
chapbook/MS
chapeau/SM
chapel/MS
chaperon/MDGS
chaperonage/M
chaperone/SM
chaperoned/U
chaplain/MS
chaplaincy/SM
chaplet/SM
chapped
chapping
chappy/S
chapter/SM
char/SM
charabanc/MS
character/MS
characterful
characteristic/SM
characteristically/U
characterization/MS
characterize/DSG
characterless
charade/SM
charbroil/GDS
charcoal/MS
charcuterie
chard/M
chardonnay/SM
charge/AESDGM
chargeable/A
charged/U
charger/SM
charily
chariness/M
chariot/SM
charioteer/MS
charisma/M
charismatic/MS
charitable/P
charitableness/M
charitably/U
charity/SM
charlady/S
charlatan/SM
charlatanism/M
charlatanry/M
charlie/S
charm/ZGMDRS
charmer/M
charming/Y
charmless
charred
charring
chart/GMDS
charted/U
charter's
charter/ASGD
charterer/MS
chartreuse/M
charwoman/M
charwomen
chary/TRP
chase/MZGDRS
chaser/M
chasm/MS
chassis/M
chaste/PYTR
chasten/DGS
chasteness/M
chastise/DRSZGL
chastisement/SM
chastiser/M
chastity/M
chasuble/SM
chat/SM
chateau/SM
chateaux
chatelaine/SM
chatline/S
chatroom/M
chatted
chattel/MS
chatter/MDRZGS
chatterbox/MS
chatterer/M
chattily
chattiness/M
chatting
chatty/TPR
chauffeur/GMDS
chauvinism/M
chauvinist/SM
chauvinistic
chauvinistically
cheap/PXTNRY
cheapen/DG
cheapness/M
cheapo
cheapskate/MS
cheat/ZGMDRS
cheater/M
check's/A
check/UAGDS
checkbook/SM
checkbox
checker/MDGS
checkerboard/SM
checkers/M
checklist/MS
checkmate/MGDS
checkoff/SM
checkout/SM
checkpoint/SM
checkroom/MS
checksum
checkup/MS
cheddar/M
cheek/GMDS
cheekbone/SM
cheekily
cheekiness/M
cheeky/TPR
cheep/GMDS
cheer/ZGMDRS
cheerer/M
cheerful/YP
cheerfuller
cheerfullest
cheerfulness/M
cheerily
cheeriness/M
cheerio/MS
cheerleader/SM
cheerless/PY
cheerlessness/M
cheery/TPR
cheese/MGDS
cheeseboard/S
cheeseburger/SM
cheesecake/SM
cheesecloth/M
cheeseparing/M
cheesiness/M
cheesy/TPR
cheetah/M
cheetahs
chef/SM
chem
chemical/SMY
chemise/MS
chemist/MS
chemistry/SM
chemo/M
chemotherapeutic
chemotherapy/M
chemurgy/M
chenille/M
cherish/DSG
cheroot/MS
cherry/SM
chert/M
cherub/MS
cherubic
cherubim
chervil/M
chess/M
chessboard/MS
chessman/M
chessmen
chest/MDS
chesterfield/SM
chestful/SM
chestnut/SM
chesty/TR
chevalier/SM
cheviot/M
chevron/MS
chew/SBZGMDR
chewer/M
chewiness/M
chewy/PTR
chg
chge
chi/SM
chiaroscuro/M
chic/PTMR
chicane/MS
chicanery/SM
chichi/MS
chick/XMNS
chickadee/SM
chicken/MDG
chickenfeed/M
chickenhearted
chickenpox/M
chickenshit/MS!
chickpea/SM
chickweed/M
chicle/M
chicness/M
chicory/SM
chide/GDS
chiding/Y
chief/TMRYS
chiefdom/M
chieftain/MS
chieftainship/SM
chiffon/M
chiffonier/MS
chigger/MS
chignon/MS
chihuahua/SM
chilblain/SM
child/M
childbearing/M
childbirth/M
childbirths
childcare/M
childhood/SM
childish/YP
childishness/M
childless/P
childlessness/M
childlike
childminder/S
childminding
childproof/GSD
children/M
chili/M
chilies
chill/JPZTGMDRS
chiller/M
chilliness/M
chilling/Y
chillness/M
chilly/TPR
chimaera/MS
chime/MZGDRS
chimer/M
chimera/MS
chimeric
chimerical
chimney/MS
chimp/MS
chimpanzee/SM
chin/SM
china/M
chinaware/M
chinchilla/MS
chine/MS
chink/GMDS
chinless
chinned
chinning
chino/MS
chinstrap/MS
chintz/M
chintzy/RT
chinwag/S
chip/SM
chipboard
chipmunk/SM
chipolata/S
chipped
chipper/MS
chippie
chipping/S
chippy/S
chiral
chirality
chirography/M
chiropodist/MS
chiropody/M
chiropractic/SM
chiropractor/SM
chirp/GMDS
chirpily
chirpy/PTR
chirrup/GMDS
chisel/ZGMDRS
chiseler/M
chiselled
chiseller/MS
chiselling
chit/SM
chitchat/SM
chitchatted
chitchatting
chitin/M
chitinous
chitlins/M
chitosan
chitterlings/M
chivalrous/PY
chivalrousness/M
chivalry/M
chive/MS
chivvy/GDS
chivy/GDS
chlamydia/MS
chlamydiae
chloral/M
chlordane/M
chloride/MS
chlorinate/GNDS
chlorination/M
chlorine/M
chlorofluorocarbon/SM
chloroform/SGMD
chlorophyll/M
chloroplast/MS
chm
choc/S
chock/GMDS
chockablock
chocoholic/SM
chocolate/MS
chocolatey
chocolaty
choice/MTRS
choir/MS
choirboy/MS
choirmaster/SM
choke/MZGDRS
chokecherry/SM
choker/M
cholecystectomy
cholecystitis
choler/M
cholera/M
choleric
cholesterol/M
cholla/MS
chomp/ZGMDRS
choose/ZGRS
chooser/M
choosiness/M
choosy/TPR
chop/SM
chophouse/SM
chopped
chopper/MDGS
choppily
choppiness/M
chopping
choppy/TPR
chopstick/SM
choral/MYS
chorale/MS
chord/MS
chordal
chordate/SM
chore/MS
chorea/M
choreograph/DRZG
choreographer/M
choreographic
choreographically
choreographs
choreography/M
chorister/SM
chorizo/MS
choroid/MS
chortle/MZGDRS
chortler/M
chorus/GMDS
chose
chosen
chow/SGMD
chowder/MS
chrism/M
christen/ASGD
christening/MS
christian/U
christology
chromatic
chromatically
chromatin/M
chromatography
chrome/MGDS
chromium/M
chromosomal
chromosome/MS
chronic
chronically
chronicle/DRSMZG
chronicler/M
chronograph/M
chronographs
chronological/Y
chronologist/MS
chronology/SM
chronometer/SM
chrysalis/MS
chrysanthemum/MS
chub/SM
chubbiness/M
chubby/TPR
chuck/GMDS
chuckhole/SM
chuckle/MGDS
chuffed
chug/SM
chugged
chugging
chukka/MS
chum/SM
chummed
chummily
chumminess/M
chumming
chummy/PTR
chump/MS
chunder/GDS
chunk/GMDS
chunkiness/M
chunky/PTR
chunter/DGS
church/MS
churchgoer/SM
churchgoing/M
churchman/M
churchmen
churchwarden/MS
churchwoman
churchwomen
churchyard/SM
churl/MS
churlish/PY
churlishness/M
churn/ZGMDRS
churner/M
chute/MS
chutney/MS
chutzpah/M
chyme/M
chyron/MS
ch�teau/M
ch�teaux
ch�telaine/SM
ciabatta/SM
ciao/S
cicada/MS
cicatrices
cicatrix/M
cicerone/SM
ciceroni
cider's
cider/S
cigar/MS
cigarette/MS
cigarillo/MS
cilantro/M
cilia
cilium/M
cinch/GMDS
cinchona/SM
cincture/SM
cinder/GMDS
cine
cinema/MS
cinematic
cinematographer/MS
cinematographic
cinematography/M
cinnabar/M
cinnamon/M
cipher's
cipher/CGDS
ciphertext/S
cir
circa
circadian
circle/MGDS
circlet/MS
circuit/MDGS
circuital
circuitous/YP
circuitousness/M
circuitry/M
circuity/M
circular/SMY
circularity/M
circularize/DSG
circulate/ADSG
circulation/SM
circulatory
circumcise/XDSGN
circumcised/U
circumcision/M
circumference/MS
circumferential
circumflex/MS
circumlocution/MS
circumlocutory
circumnavigate/XGNDS
circumnavigation/M
circumpolar
circumscribe/GDS
circumscription/MS
circumspect/Y
circumspection/M
circumstance/MGDS
circumstantial/Y
circumvent/DSG
circumvention/M
circus/MS
cirque/MS
cirrhosis/M
cirrhotic/SM
cirri
cirrus/M
cis
cisgender/D
cistern/MS
cit
citadel/MS
citation/AMS
cite's
cite/IAGSD
citified
citizen/MS
citizenry/M
citizenship/M
citric
citron/MS
citronella/M
citrous
citrus/MS
city/SM
citywide
civet/MS
civic/S
civically
civics/M
civil/UY
civilian/MS
civility/ISM
civilization/MS
civilize/GDS
civilized/U
civvies/M
ck
cl
clack/GMDS
clad/U
cladding/M
clade
claim's
claim/CKEAGDS
claimable/AKE
claimant/MS
claimed/U
claimer/ECSM
clairvoyance/M
clairvoyant/MS
clam/SM
clambake/MS
clamber/ZGMDRS
clamberer/M
clammed
clammily
clamminess/M
clamming
clammy/PTR
clamor/GMDS
clamorous
clamp/GMDS
clampdown/MS
clan/SM
clandestine/Y
clang/ZGMDRS
clangor/M
clangorous/Y
clank/GMDS
clannish/P
clannishness/M
clansman/M
clansmen
clanswoman
clanswomen
clap/SM
clapboard/MDGS
clapped
clapper/MS
clapperboard/S
clapping/M
claptrap/M
claque/MS
claret/MS
clarification/M
clarify/XDSNG
clarinet/SM
clarinetist/SM
clarinettist/MS
clarion/MDGS
clarity/M
clash/GMDS
clasp's
clasp/UGDS
class/GMDS
classic/MS
classical/MY
classicism/M
classicist/MS
classifiable
classification/CAM
classifications
classified's
classified/U
classifieds
classifier/MS
classify/ACSDGN
classiness/M
classism
classless/P
classmate/MS
classroom/MS
classwork/M
classy/TRP
clatter/GMDS
clausal
clause/MS
claustrophobia/M
claustrophobic
clavichord/SM
clavicle/MS
clavier/MS
claw's
claw/CSGD
clay/M
clayey
clayier
clayiest
clean/BJPZTGDRYS
cleaner/M
cleaning/M
cleanliness/UM
cleanly/UTPR
cleanness/UM
cleanse/ZGDRS
cleanser/M
cleanup/MS
clear/JPTGMDRYS
clearance/SM
clearheaded
clearing/M
clearinghouse/SM
clearness/M
clearway/S
cleat/MS
cleavage/MS
cleave/ZGDRS
cleaver/M
clef/SM
cleft/MS
clematis/MS
clemency/IM
clement/Y
clementine/S
clench/GMDS
clerestory/SM
clergy/SM
clergyman/M
clergymen
clergywoman/M
clergywomen
cleric/MS
clerical/Y
clericalism/M
clerk/GMDS
clerkship/M
clever/PTRY
cleverness/M
clevis/MS
clew/SGMD
cliche/MDS
clich�/MS
clich�d
click/BZGMDRS
clickbait
clicker/M
client/MS
clientele/MS
client�le/MS
cliff/MS
cliffhanger/SM
cliffhanging
clifftop/S
clii
climacteric/M
climactic
climate/SM
climatic
climatically
climatologist/SM
climatology/M
climax/MDSG
climb/SMDRZGB
climber/M
climbing/M
clime/SM
clinch/MDRSZG
clincher/M
cling/SMRZG
clinger/M
clingfilm
clingy/RT
clinic/SM
clinical/Y
clinician/SM
clink/SMDRZG
clinker/M
cliometric/S
cliometrician/MS
cliometrics/M
clip/SM
clipboard/MS
clipped
clipper/SM
clipping/SM
clique/SM
cliquey
cliquish/YP
cliquishness/M
clit/SM
clitoral
clitorides
clitoris/MS
clix
cloaca/M
cloacae
cloak's
cloak/USDG
cloakroom/MS
clobber/SMDG
cloche/SM
clock/SMDG
clockwise
clockwork/SM
clod/MS
cloddish
clodhopper/MS
clog's
clog/US
clogged/U
clogging/U
cloisonne/M
cloisonn�/M
cloister/SMDG
cloistral
clomp/SDG
clonal
clone/DSMG
clonidine
clonk/SMDG
clop/MS
clopped
clopping
closable/I
close/DRSMYTGBJP
closefisted
closemouthed
closeness/M
closeout/MS
closet/SMDG
closeup/SM
closing/M
closure/ESM
clot/MS
cloth/M
clothe/UDSG
clotheshorse/MS
clothesline/SM
clothespin/SM
clothier/MS
clothing/M
cloths
clotted
clotting
cloture/SM
cloud/SMDG
cloudburst/SM
clouded/U
cloudiness/M
cloudless
cloudy/RPT
clout/SMDG
clove/RSMZ
cloven
clover/M
cloverleaf/SM
cloverleaves
clown/SMDG
clownish/YP
clownishness/M
cloy/DGS
cloying/Y
club/MS
clubbable
clubbed
clubber/S
clubbing
clubfeet
clubfoot/MD
clubhouse/SM
clubland
cluck/SMDG
clue/MGDS
clueless
clump/SMDG
clumpy/TR
clumsily
clumsiness/M
clumsy/TRP
clung
clunk/SMDRZG
clunker/M
clunky/TR
cluster/MDSG
clutch/GMDS
clutter's
clutter/UDSG
clvi
clvii
clxi
clxii
clxiv
clxix
clxvi
clxvii
cm
cnidarian/MS
co/ESD
coach/MDSG
coachload/S
coachman/M
coachmen
coachwork
coadjutor/MS
coagulant/MS
coagulate/GNDS
coagulation/M
coagulator/MS
coal/MDGS
coalesce/GDS
coalescence/M
coalescent
coalface/MS
coalfield/S
coalition/MS
coalitionist/MS
coalmine/S
coarse/RYTP
coarsen/SDG
coarseness/M
coast/SMDRZG
coastal
coaster/M
coastguard/S
coastline/MS
coat/MDGJS
coating/M
coatroom/S
coattail/SM
coauthor/MDGS
coax/DRSZG
coaxer/M
coaxial/Y
coaxing/Y
cob/SM
cobalt/M
cobber/S
cobble/DRSMZG
cobbler/M
cobblestone/SM
cobnut/S
cobra/SM
cobweb/SM
cobwebbed
cobwebby/RT
coca/M
cocaine/M
cocci/S
coccus/M
coccyges
coccyx/M
cochineal/M
cochlea/SM
cochleae
cochlear
cock/MDGS
cockade/SM
cockamamie
cockatiel/MS
cockatoo/SM
cockatrice/SM
cockchafer/S
cockcrow/SM
cockerel/SM
cockeyed
cockfight/MGS
cockfighting/M
cockily
cockiness/M
cockle/SM
cockleshell/SM
cockney/SM
cockpit/SM
cockroach/MS
cockscomb/SM
cocksucker/MS!
cocksure
cocktail/MS
cocky/RTP
coco/MS
cocoa/SM
coconut/SM
cocoon/SMDG
cod/SM
coda/MS
codded
codding
coddle/DSG
code's
code/CZGDRS
codebook/M
codec/SM
codeine/M
codependency/M
codependent/SM
coder/CM
codex/M
codfish/MS
codger/SM
codices
codicil/SM
codification/M
codifier/M
codify/XDRSNZG
codon/SM
codpiece/MS
codswallop
coed/MS
coeducation/M
coeducational
coefficient/MS
coelenterate/MS
coenzyme
coequal/MYS
coerce/DRSZGNV
coercer/M
coercion/M
coeval/SMY
coexist/DSG
coexistence/M
coexistent
coextensive
coffee/SM
coffeecake/SM
coffeehouse/MS
coffeemaker/SM
coffeepot/MS
coffer/SM
cofferdam/MS
coffin/SMDG
cofunction/MS
cog/SM
cogency/M
cogent/Y
cogitate/DSXGNV
cogitation/M
cogitator/MS
cognac/SM
cognate/MS
cognition/AM
cognitional
cognitive/Y
cognizable
cognizance/AM
cognizant
cognomen/SM
cognoscente/M
cognoscenti
cogwheel/SM
cohabit/SGD
cohabitant/MS
cohabitation/M
coheir/SM
cohere/DSG
coherence/IM
coherency/M
coherent/IY
cohesion/M
cohesive/YP
cohesiveness/M
coho/MS
cohort/SM
coif/MDGS
coiffed
coiffing
coiffure/DSMG
coil's/A
coil/UADGS
coin/MDRZGS
coinage/SM
coincide/DSG
coincidence/MS
coincident
coincidental/Y
coiner/M
coinsurance/M
coir
coital
coitus/M
coke/MGDS
col/S
cola/MS
colander/SM
cold/MRYTPS
coldblooded
coldness/M
coleslaw/M
coleus/MS
coley/S
coli
colic/M
colicky
coliseum/MS
colitis/M
coll
collaborate/DSXGNV
collaboration/M
collaborationist
collaborative/Y
collaborator/MS
collage/SM
collagen
collapse/MGDS
collapsible
collar/SMDG
collarbone/SM
collard/SM
collarless
collate/DSXGN
collateral/MY
collateralize
collation/M
collator/MS
colleague/MS
collect's
collect/ASGVD
collectable/MS
collected/U
collectedly
collectible/SM
collection/AMS
collective/MYS
collectivism/M
collectivist/SM
collectivization/M
collectivize/DSG
collector/MS
colleen/SM
college/SM
collegial
collegiality/M
collegian/MS
collegiate
collide/DRSZG
collie/RSMZ
collier/M
colliery/SM
collimate/DGN
collinear
collinearity
collision/SM
collocate/MGNDSX
collocation/M
colloid/SM
colloidal
colloq
colloquial/Y
colloquialism/SM
colloquies
colloquium/MS
colloquy/M
collude/DSG
collusion/M
collusive
colocate/XDSGN
cologne/SM
colon/SM
colonel/SM
colonelcy/M
colones
colonial/SMY
colonialism/M
colonialist/MS
colonist/SM
colonization/ACM
colonize/CAGSD
colonizer/MS
colonnade/MDS
colonoscope/MS
colonoscopy/SM
colony/SM
colophon/SM
color's
color/AEGDS
colorant/SM
coloration/EM
coloratura/MS
colorblind/P
colorblindness/M
colored's
colored/U
coloreds
colorfast/P
colorfastness/M
colorful/PY
colorfulness/M
colorimeter/SM
coloring's
colorist/S
colorization/M
colorize/DSG
colorless/PY
colorlessness/M
colorway/S
colossal/Y
colossi
colossus/M
colostomy/SM
colostrum/M
colt/MS
coltish
columbine/SM
column/SMD
columnar
columnist/SM
com/J
coma/MS
comaker/SM
comatose
comb/MDRZGJS
combat/SMDGV
combatant/SM
combativeness/M
combed/U
comber/M
combination/SM
combinatorics
combine's
combine/ADSG
combined/U
combiner/MS
combings/M
combo/SM
combust/SGVD
combustibility/M
combustible/MS
combustion/M
come/IMZGRS
comeback/MS
comedian/MS
comedic
comedienne/MS
comedown/MS
comedy/SM
comeliness/M
comely/RPT
comer's
comestible/SM
comet/SM
comeuppance/SM
comfit's
comfit/ES
comfort/ESMDG
comfortable/P
comfortableness/M
comfortably/U
comforter/MS
comforting/Y
comfortless
comfy/RT
comic/SM
comical/Y
comicality/M
coming/M
comity/M
comm
comma/SM
command/SMDRLZG
commandant/MS
commandeer/GDS
commander/M
commandment/MS
commando/SM
commemorate/XGNVDS
commemoration/M
commemorator/MS
commence/ADSLG
commencement/AM
commencements
commend/ASDBG
commendably
commendation/AMS
commendatory
commensurability
commensurable
commensurate/IY
comment/ZGSMDR
commentary/SM
commentate/DSG
commentator/SM
commenter/M
commerce/M
commercial/SMY
commercialism/M
commercialization/M
commercialize/GDS
commie/SM
commingle/DSG
commiserate/GNVDSX
commiseration/M
commissar/SM
commissariat/SM
commissary/SM
commission's
commission/ACSGD
commissionaire/S
commissioner/SM
commit/AS
commitment/MS
committal/SM
committed/AU
committee/SM
committeeman/M
committeemen
committeewoman/M
committeewomen
committer/S
committing/A
commode's
commode/EIS
commodification
commodious/Y
commoditization
commodity/SM
commodore/SM
common's
common/UPRYT
commonality/S
commonalty/M
commoner/MS
commonness/UM
commonplace/MS
commons
commonsense
commonweal/MH
commonwealth/M
commonwealths
commotion/SM
communal/Y
commune/XDSMGN
communicability/M
communicable/I
communicably
communicant/MS
communicate/GNVDSX
communication/M
communicative/U
communicator/SM
communion/M
communique/SM
communism/M
communist/SM
communistic
community/SM
commutation/MS
commutative
commutativity
commutator/SM
commute/BDRSMZG
commuter/M
comorbidity/S
comp/MDYGS
compact/TGSMDRYP
compaction
compactness/M
compactor/SM
companion/SBM
companionably
companionship/M
companionway/MS
company/SM
comparability/M
comparable/I
comparably/I
comparative/MYS
compare/BDSG
comparison/MS
compartment/SM
compartmental
compartmentalization/M
compartmentalize/DSG
compass/GMDS
compassion/M
compassionate/UY
compatibility/ISM
compatible/IMS
compatibly/I
compatriot/MS
compeer/SM
compel/S
compelled
compelling/Y
compendious
compendium/SM
compensate/DSXGN
compensated/U
compensation/M
compensatory
compere/DSG
compete/DSG
competence/IM
competences
competencies
competency/IM
competent/IY
competition/SM
competitive/PY
competitiveness/M
competitor/SM
compilation/AM
compilations
compile/DRSZG
compiler/M
complacence/M
complacency/M
complacent/Y
complain/DRZGS
complainant/MS
complainer/M
complaint/SM
complaisance/M
complaisant/Y
complected
complement/SGMD
complementarity/SM
complementary
complete/PYTGNXDRS
completed/U
completeness/IM
completion/M
complex/MSY
complexion/MDS
complexional
complexity/SM
compliance/M
compliant/Y
complicate/GDS
complicated/Y
complication/M
complicit
complicity/M
compliment/MDGS
complimentary/U
comply/NDSXG
compo
component/SM
comport/LSGD
comportment/M
compos/RBZ
compose/AECGSD
composedly
composer/M
composite/MYGNXPDS
composition/CM
compositional
compositor/SM
compost/SGMD
composure/EM
compote/SM
compound/GMDBS
compounded/U
comprehend/SDG
comprehensibility/IM
comprehensible/I
comprehensibly/I
comprehension/IM
comprehensions
comprehensive/PMYS
comprehensiveness/M
compress's
compress/CGVDS
compressed/U
compressible
compression/CMS
compressor/SM
comprise/GDS
compromise/MGDS
comptroller/MS
compulsion/MS
compulsive/YP
compulsiveness/M
compulsorily
compulsory/SM
compunction/SM
computation/SM
computational/Y
compute/ADSG
computer/MS
computerate
computerization/M
computerize/GDS
computing/M
comp�re/DSG
comrade/SMY
comradery
comradeship/M
con/GSM
concatenate/XDSGN
concatenation/M
concave/YP
concaveness/M
conceal/SDRZGBL
concealed/U
concealer/M
concealment/M
conceit/SMD
conceited/PY
conceitedness/M
conceivable/I
conceivably/I
conceive/DSGB
concentrate/DSMGNX
concentration/M
concentric
concentrically
concept/SM
conception/SM
conceptional
conceptual/Y
conceptualization/MS
conceptualize/DSG
concern/UMD
concerned/UY
concerning
concerns
concert's
concert/ESDG
concerted/Y
concertgoer/S
concertina/SGMD
concertize/DSG
concertmaster/MS
concerto/SM
concessionaire/MS
concessional
concessionary
conch/M
conchie/S
conchs
concierge/MS
conciliate/DSGN
conciliation/AM
conciliator/SM
conciliatory
concise/RPYTN
conciseness/M
concision/M
conclave/SM
conclude/DSG
conclusion/MS
conclusive/IYP
conclusiveness/IM
concoct/SDG
concoction/MS
concomitant/MYS
concord/M
concordance/SM
concordant
concordat/SM
concourse/SM
concrete/DSPMYGNX
concreteness/M
concretion/M
concubinage/M
concubine/MS
concupiscence/M
concupiscent
concur/S
concurred
concurrence/SM
concurrency
concurring
concuss/V
concussion/SM
condemn/SDRZG
condemnation/MS
condemnatory
condemner/M
condensate/MNXS
condensation/M
condense/DRSZG
condenser/M
condescending/Y
condescension/M
condign
condiment/MS
condition's
condition/AGSD
conditional/SMY
conditionality
conditioned/U
conditioner/SM
conditioning/M
condo/SM
condolence/SM
condom/SM
condominium/MS
condone/DSG
condor/SM
conduce/DSGV
conduct/MDGV
conductance/M
conductibility/M
conductible
conduction/M
conductivity/M
conductor/MS
conductress/MS
conduit/SM
cone/M
coney/SM
confab/SM
confabbed
confabbing
confabulate/XDSGN
confabulation/M
confection/SZMR
confectioner/M
confectionery/SM
confederacy/SM
confederate/M
confer/SB
conferee/SM
conference/MGDS
conferrable
conferral/M
conferred
conferrer/MS
conferring
confessed/Y
confession/SM
confessional/SM
confessor/MS
confetti/M
confidant/MS
confidante/SM
confide/DRSZG
confidence/SM
confident/Y
confidential/Y
confidentiality/M
confider/M
confiding/Y
configuration/S
configure/ABD
confined/U
confinement/MS
confirm/ASDG
confirmation/ASM
confirmatory
confirmed/U
confiscate/DSGNX
confiscation/M
confiscator/SM
confiscatory
conflagration/MS
conflate/XDSGN
conflation/M
conflict/SGMD
confluence/MS
confluent
conform/ZB
conformable/U
conformal
conformance/M
conformant
conformism/M
conformist/SM
conformity/M
confrere/MS
confrontation/SM
confrontational
confr�re/SM
confuse/RZ
confused/Y
confusing/Y
confutation/M
confute/DSG
conga/SMDG
congeal/SLDG
congealment/M
conger/SM
congeries/M
congest/SDGV
congestion/M
conglomerate/DSXMGN
conglomeration/M
congrats/M
congratulate/XGNDS
congratulation/M
congratulatory
congregant/MS
congregate/GNDSX
congregation/M
congregational
congregationalism/M
congregationalist/MS
congress/MS
congressional/Y
congressman/M
congressmen
congresspeople
congressperson/MS
congresswoman/M
congresswomen
congruence/M
congruent/IY
congruity/ISM
congruous
conic/SM
conical/Y
conifer/SM
coniferous
conjectural
conjecture/MGDS
conjoint
conjugal/Y
conjugate/DSXGN
conjugation/M
conjunct/VMS
conjunctiva/SM
conjunctive/SM
conjunctivitis/M
conjuration/MS
conjure/DRSZG
conjurer/M
conk/MDRZ
connect/AEDVGS
connectable
connected/U
connection/EMS
connective/MS
connectivity/M
connector/MS
conned
conning
conniption/MS
connivance/M
connive/DRSZG
conniver/M
connoisseur/SM
connotative
connubial
conquer/ASDG
conquerable/U
conquered/U
conqueror/MS
conquest/AM
conquistador/SM
cons/DSG
consanguineous
consanguinity/M
conscienceless
conscientious/PY
conscientiousness/M
conscious/UYP
consciousness/UM
consciousnesses
conscription/M
consecrate/ADSGN
consecrated/U
consecration/AM
consecrations
consecutive/Y
consensual
consensus/MS
consent/SMDG
consequence/SM
consequent/Y
consequential/IY
conservancy/SM
conservation/M
conservationism/M
conservationist/SM
conservatism/M
conservative/MYS
conservatoire/S
conservator/SM
conservatory/SM
consider/AGSD
considerable/I
considerably
considerate/IPYN
considerateness/IM
consideration/AIM
considerations
considered/U
consign/ASDG
consignee/MS
consignment/MS
consist/SDG
consistence/MS
consistency/ISM
consistent/IY
consistory/SM
consolable/I
consolation/MS
consolatory
consolidate/XDSGN
consolidated/U
consolidation/M
consolidator/MS
consoling/Y
consomme/M
consomm�/M
consonance/SM
consonant/SMY
consortia
consortium/M
conspectus/MS
conspicuous/IPY
conspicuousness/IM
conspiracy/SM
conspirator/MS
conspiratorial/Y
conspire/GD
constable/SM
constabulary/SM
constancy/IM
constant/MYS
constellation/SM
consternation/M
constipate/GNDS
constipation/M
constituency/SM
constituent/SM
constitute/ADSGNV
constitution/AM
constitutional/MYS
constitutionalism
constitutionality/UM
constitutions
constrained/U
constraint/SM
constrict/GVSD
constriction/SM
constrictor/SM
construable
construct/CADVGS
construction/CAMS
constructional
constructionist/CSM
constructive/YP
constructiveness/M
constructor/MS
construe/GDS
consul/KSM
consular/K
consulate/SM
consulship/M
consult/GSD
consultancy/SM
consultant/MS
consultation/MS
consultative
consumable/SM
consume/BDRSZG
consumed/U
consumer/M
consumerism/M
consumerist/MS
consummate/YGNXDS
consummated/U
consumption/M
consumptive/SM
cont
contact/ASDG
contactable
contactless
contagion/MS
contagious/PY
contagiousness/M
contain/SBLDRZG
contained/U
container/M
containerization/M
containerize/DSG
containment/M
contaminant/SM
contaminate/ACDSG
contaminated/U
contamination/CM
contaminator/SM
contd
contemn/SDG
contemplate/DSGNV
contemplation/M
contemplative/SMY
contemporaneity/M
contemporaneous/Y
contempt/M
contemptible
contemptibly
contemptuous/YP
contemptuousness/M
contender/MS
content/ESLMDG
contented/EY
contentedness/M
contention/SM
contentious/YP
contentiousness/M
contently
contentment/EM
conterminous/Y
contestable/I
contestant/MS
contested/U
contextualization
contextualize/CDSG
contiguity/M
contiguous/Y
continence/IM
continent/SM
continental/SM
continentalism/M
contingency/SM
contingent/SMY
continua
continual/Y
continuance/EMS
continuation/EMS
continue/EGDS
continuity/ESM
continuous/EY
continuum/M
contort/GD
contortion/MS
contortionist/SM
contra
contraband/M
contrabass/MS
contrabassoon/S
contraception/M
contraceptive/SM
contract/MDG
contractible
contractile
contractility
contraction/S
contractual/Y
contradict/SDG
contradicted/U
contradiction/SM
contradictory
contradistinction/MS
contraflow/S
contrail/MS
contraindicate/GNXDS
contraindication/M
contralto/SM
contraption/SM
contrapuntal/Y
contrarian/SM
contrarianism
contrariety/M
contrarily
contrariness/M
contrariwise
contrary/PSM
contrast/MDGS
contravene/GDS
contravention/SM
contretemps/M
contribute/XGND
contribution/M
contributor/MS
contributory
contrition/M
contrivance/MS
contrive/ZGDRS
contriver/M
control's
control/CS
controllable/U
controlled/UC
controller/MS
controlling/C
controversial/Y
controversy/SM
controvert/DSG
controvertible/I
contumacious/Y
contumacy/M
contumelious
contumely/SM
contuse/XDSGN
contusion/M
conundrum/SM
conurbation/MS
convalesce/DSG
convalescence/MS
convalescent/SM
convection/M
convectional
convective
convector/S
convene/ADSG
convener/MS
convenience/IMS
convenient/IY
convenor/MS
convent/SM
conventicle/MS
convention/SM
conventional/UY
conventionality/UM
conventionalize/GDS
conventioneer/S
convergence/MS
convergent
conversant
conversation/MS
conversational/Y
conversationalist/SM
converse/Y
convert's
convert/AGSD
converted/U
converter/SM
convertibility/M
convertible/SM
convertor/SM
convex/Y
convexity/M
convey/SBDG
conveyance/MGS
conveyor/MS
convict/GSMD
conviction/MS
convince/GDS
convinced/U
convincing/UY
convivial/Y
conviviality/M
convoke/DSG
convoluted
convolution/MS
convolutional
convoy/SMDG
convulse/GNVXDS
convulsion/M
convulsive/Y
cony/SM
coo/GSMD
cook's
cook/ADGS
cookbook/MS
cooked/U
cooker/SM
cookery/SM
cookhouse/S
cookie/M
cooking/M
cookout/SM
cookware/SM
cooky/SM
cool/MDRYZTGPS
coolant/SM
cooler/M
coolie/SM
coolness/M
coon/MS!
coonskin/MS
coop/MDRZGS
cooper/MDG
cooperage/M
cooperate/DSGNV
cooperation/M
cooperative/PMYS
cooperativeness/M
cooperator/SM
coordinate/DSMYGN
coordinated/U
coordination/M
coordinator/MS
coot/MS
cootie/SM
cop/GJSMD
copacetic
copay/M
cope/MS
copier/SM
copilot/SM
coping/M
copious/PY
copiousness/M
copped
copper/SM
copperhead/SM
copperplate/M
coppery
copping
copra/M
coproduct/MS
copse/SM
copter/SM
copula/SM
copulate/GNVDS
copulation/M
copulative/SM
copy's
copy/ADSG
copybook/SM
copycat/MS
copycatted
copycatting
copyist/MS
copyleft
copyright/GSBMD
copyrightable/U
copywriter/MS
coquetry/SM
coquette/DSMG
coquettish/Y
cor
coracle/SM
coral/SM
corbel/SM
cord/EASGDM
cordage/M
cordial/SMY
cordiality/M
cordillera/MS
cordite/M
cordless
cordon/SMDG
cordovan/M
corduroy/MS
corduroys/M
core/MZGDRS
coreligionist/S
corer/M
corespondent/MS
corgi/SM
coriander/M
cork's
cork/UDGS
corkage
corker/SM
corkscrew/SMDG
corm/MS
cormorant/SM
corn/MDRZGS
cornball/MS
cornbread/M
corncob/MS
corncrake/S
cornea/SM
corneal
corner/GMD
cornerstone/SM
cornet/SM
cornfield/S
cornflakes/M
cornflour
cornflower/SM
cornice/MS
cornily
corniness/M
cornmeal/M
cornrow/MDGS
cornstalk/SM
cornstarch/M
cornucopia/MS
corny/PRT
corolla/MS
corollary/SM
corona/SM
coronal/MS
coronary/SM
coronation/SM
coronavirus/MS
coroner/MS
coronet/MS
corp
corpora
corporal/SM
corporate/XYN
corporation/IM
corporatism
corporeal/Y
corporeality/M
corps/MS
corpse/M
corpsman/M
corpsmen
corpulence/M
corpulent
corpus/M
corpuscle/MS
corpuscular
corr
corral/SM
corralled
corralling
correct/DRYTGVSBP
corrected/U
correction/SM
correctional
corrective/SM
correctness/IM
corrector
correlate/XDSMGNV
correlated/U
correlation/M
correlational
correlative/MS
correspond/SDG
correspondence/SM
correspondent/SM
corresponding/Y
corridor/SM
corrie/S
corrigibility/IM
corrigible/I
corroborate/GNVDSX
corroborated/U
corroboration/M
corroborator/SM
corroboratory
corrode/GDS
corrosion/M
corrosive/SMY
corrugate/GNXDS
corrugation/M
corrupt/DRYPSTG
corruptibility/IM
corruptible/I
corruptibly/I
corruption/MS
corruptness/M
corsage/MS
corsair/MS
corset/SGMD
cortege/MS
cortex/M
cortical
cortices
cortisol
cortisone/M
cort�ge/SM
corundum/M
coruscate/GNDS
coruscation/M
corvette/SM
cos/M
cosh/DSG
cosign/ZGSDR
cosignatory/SM
cosigner/M
cosine/SM
cosmetic/SM
cosmetically
cosmetician/MS
cosmetologist/MS
cosmetology/M
cosmic
cosmically
cosmogonist/SM
cosmogony/SM
cosmological
cosmologist/SM
cosmology/SM
cosmonaut/SM
cosmopolitan/MS
cosmopolitanism/M
cosmos/MS
cosplay/DRSZG
cosponsor/GSMD
cosset/SGD
cossetted
cossetting
cost/MDYGSJ
costar/SM
costarred
costarring
costliness/M
costly/PTR
costume/MZGDRS
costumer/M
costumier/S
cot/SM
cotangent/MS
cote/MS
coterie/MS
coterminous
cotillion/SM
cottage/MZGRS
cottager/M
cottar/SM
cotter/SM
cotton/SGMD
cottonmouth/M
cottonmouths
cottonseed/MS
cottontail/MS
cottonwood/SM
cottony
cotyledon/MS
couch/MDSG
couchette/S
cougar/SM
cough/MDG
coughs
could
could've
couldn't
coulee/SM
coulis
coulomb/MS
coul�e/SM
council/MS
councilman/M
councilmen
councilor/MS
councilperson/SM
councilwoman/M
councilwomen
counsel/JMDGS
counselor/MS
count/EASMDG
countable/U
countably
countdown/MS
counted/U
countenance/EMGDS
counter/EMS
counteract/SGVD
counteraction/MS
counterargument/S
counterattack/GMDS
counterbalance/MGDS
counterblast/S
counterclaim/GSMD
counterclockwise
counterculture/SM
countered
counterespionage/M
counterexample/S
counterfactual
counterfeit/ZGMDRS
counterfeiter/M
counterfoil/MS
countering
counterinsurgency/SM
counterintelligence/M
counterintuitive/Y
counterman/M
countermand/GMDS
countermeasure/SM
countermelody/S
countermen
countermove/S
counteroffensive/SM
counteroffer/SM
counterpane/SM
counterpart/SM
counterpetition
counterpoint/MDGS
counterpoise/MGDS
counterproductive
counterproposal
counterrevolution/SM
counterrevolutionary/SM
countersign/GSMD
countersignature/MS
countersink/GSM
counterspy/SM
counterstrike/SM
counterstroke/SM
countersunk
countertenor/MS
countertop/S
countervail/GSD
counterweight/MS
countess/MS
countless
countrified
country/SM
countryman/M
countrymen
countryside/MS
countrywide
countrywoman/M
countrywomen
county/SM
countywide
coup's
coup/AS
coupe/SM
couple's
couple/UCGZSRD
couplet/MS
coupling/SM
coupon/SM
courage/M
courageous/YP
courageousness/M
courgette/S
courier/MDSG
course/EDGMS
coursebook/S
courser/MS
coursework
court-martial/SGD
court/SMDYG
courteous/EY
courteousness/M
courtesan/SM
courtesy/ESM
courthouse/MS
courtier/SM
courtliness/M
courtly/PRT
courtroom/MS
courtship/MS
courtyard/MS
couscous/M
cousin/SM
couture/M
couturier/MS
covalent
covariance
covariant
covariate/S
cove/MS
coven/SM
covenant/MDSG
cover's
cover/AEUGDS
coverage/M
coverall/MS
covering's
coverings
coverlet/MS
covert/SPMY
covertness/M
coverup/MS
covet/SDG
covetous/YP
covetousness/M
covey/SM
cow/ZGSMDR
coward/SMY
cowardice/M
cowardliness/M
cowbell/MS
cowbird/MS
cowboy/SM
cowcatcher/MS
cower/DG
cowgirl/MS
cowhand/MS
cowherd/MS
cowhide/MS
cowl/MGSJ
cowlick/MS
cowling/M
cowman/M
cowmen
coworker/MS
cowpat/S
cowpoke/MS
cowpox/M
cowpuncher/SM
cowrie/SM
cowshed/S
cowslip/SM
cox/GDS
coxcomb/MS
coxswain/MS
coy/TPRY
coyness/M
coyote/SM
coypu/SM
cozen/SDG
cozenage/M
cozily
coziness/M
cozy/RSMTP
cpd
cpl
cps
crab/MS
crabbed
crabber/SM
crabbily
crabbiness/M
crabbing
crabby/PRT
crabgrass/M
crablike
crabwise
crack/SMDRYZGJ
crackdown/MS
cracker/M
crackerjack/MS
crackhead/MS
crackle/DSJMG
crackling/M
crackpot/MS
crackup/SM
cradle/DSMG
craft/SMDG
craftily
craftiness/M
craftsman/M
craftsmanship/M
craftsmen
craftspeople
craftswoman/M
craftswomen
crafty/RTP
crag/MS
cragginess/M
craggy/RPT
cram/S
crammed
crammer/S
cramming
cramp/SMDG
cramping/M
crampon/SM
cranberry/SM
crane/DSMG
cranial
cranium/SM
crank/SMDG
crankcase/SM
crankily
crankiness/M
crankshaft/MS
cranky/PRT
cranny/DSM
crap/MS
crape/SM
crapola
crapped
crapper/S
crappie/M
crapping
crappy/RSPT
craps/M
crapshooter/MS
crash/MDSG
crass/RYTP
crassness/M
crate/DRSMZG
crater/MDG
cravat/SM
crave/DSGJ
craven/SMYP
cravenness/M
craving/M
craw/MS
crawdad/SM
crawl/SMDRZG
crawler/M
crawlspace/SM
crawly/TRSM
cray/S
crayfish/MS
crayola/S
crayon/GSMD
craze/DSMG
crazily
craziness/M
crazy/PRSMT
creak/SMDG
creakily
creakiness/M
creaky/RPT
cream/SMDRZG
creamer/M
creamery/SM
creamily
creaminess/M
creamy/RPT
crease/ICGMSD
create/AKVNGSDX
creatine
creation/KAM
creationism/SM
creationist/SM
creative/SMYP
creativeness/M
creativity/M
creator/MS
creature/SM
creche/SM
cred
credence/M
credential/SGMD
credenza/SM
credibility/IM
credible/I
credibly/I
credit/EGSBMD
creditably/E
creditor/SM
creditworthy/P
credo/SM
credulity/IM
credulous/IY
credulousness/M
creed/SM
creek/SM
creel/SM
creep/SMRZG
creeper/M
creepily
creepiness/M
creepy/TPR
cremains/M
cremate/GNDSX
cremation/M
crematoria
crematorium/MS
crematory/SM
creme/SM
crenelate/XGNDS
crenelation/M
creole/SM
creosote/MGDS
crepe/SM
crept
crepuscular
crescendo/CSM
crescent/MS
cress/M
crest/SMDG
crestfallen
crestless
cretaceous
cretin/SM
cretinism/M
cretinous
cretonne/M
crevasse/SM
crevice/MS
crew/MDGS
crewel/M
crewelwork/M
crewman/M
crewmen
crib/MS
cribbage/M
cribbed
cribber/MS
cribbing
crick/SMDG
cricket/MRSZG
cricketer/M
crier/M
crikey
crime/SM
crimeware/M
criminal/MYS
criminality/M
criminalization/C
criminalize/CGDS
criminologist/MS
criminology/M
crimp/SMDG
crimson/SMDG
cringe/DSMG
crinkle/DSMG
crinkly/RT
crinoline/SM
cripes
cripple/DRSMZG
crippler/M
crippleware
crippling/Y
crises
crisis/M
crisp/SMDRYTGP
crispbread/S
crispiness/M
crispness/M
crispy/PRT
crisscross/GMDS
criteria
criterion/M
critic/SM
critical/UY
criticality
criticism/MS
criticize/ZGDRS
criticizer/M
critique/MGDS
critter/SM
croak/SMDG
croaky/RT
crochet/SMDRZG
crocheter/M
crocheting/M
crock/SMD
crockery/M
crocodile/SM
crocus/MS
croft/SRZG
croissant/MS
crone/SM
crony/SM
cronyism/M
crook/SMDG
crooked/PTRY
crookedness/M
crookneck/SM
croon/SMDRZG
crooner/M
crop/MS
cropland/SM
cropped
cropper/MS
cropping
croquet/M
croquette/SM
crore/SM
crosier/MS
cross's
cross/AUGTSD
crossbar/SM
crossbeam/MS
crossbones/M
crossbow/SM
crossbowman/M
crossbowmen
crossbred
crossbreed/SGM
crosscheck/SMDG
crosscurrent/MS
crosscut/SM
crosscutting
crosser
crossfire/MS
crosshair/MS
crosshatch/GDS
crossing/SM
crossly
crossness/M
crossover/MS
crosspatch/MS
crosspiece/SM
crossroad/MS
crossroads/M
crosstown
crosswalk/MS
crosswind/MS
crosswise
crossword/MS
crotch/MS
crotchet/SM
crotchety
crouch/GMDS
croup/M
croupier/M
croupy/ZTR
crouton/MS
crow/MDGS
crowbar/MS
crowd/SMDG
crowded/U
crowdfund/SDG
crowdsource/DSG
crowfeet
crowfoot/SM
crown/SMDG
crowned/U
crozier/MS
cro�ton/MS
crucial/Y
crucible/SM
crucifix/MS
crucifixion/SM
cruciform/SM
crucify/DSG
crud/M
cruddy/TR
crude/RMYTP
crudeness/M
crudites/M
crudity/SM
crudit�s/M
cruel/RYPT
cruelness/M
cruelty/SM
cruet/SM
cruft/SD
crufty
cruise/DRSMZG
cruiser/M
cruller/MS
crumb/SMDYG
crumble/MGDS
crumbliness/M
crumbly/TPR
crumby/TR
crumminess/M
crummy/PTR
crumpet/MS
crumple/MGDS
crunch/GMDRS
crunchiness/M
crunchy/TRP
crupper/MS
crusade/MZGDRS
crusader/M
cruse/SM
crush/MDRSZG
crusher/M
crushing/Y
crust/SMDG
crustacean/SM
crustal
crustily
crustiness/M
crusty/TRP
crutch/MS
crux/MS
cry/ZGJDRSM
crybaby/SM
cryogenic/S
cryogenics/M
cryonic/S
cryosurgery/M
crypt's
crypt/S
cryptanalysis
cryptic
cryptically
crypto/M
cryptocurrency/SM
cryptogram/SM
cryptographer/SM
cryptographic
cryptography/M
cryptologist/MS
cryptosystem/S
crystal/SM
crystalize/DSG
crystalline
crystallization/M
crystallize/ADSG
crystallographic
crystallography
cr�che/MS
ct
ctn
ctr
cu
cub/ZGSMDR
cubbyhole/MS
cube/MS
cuber/M
cubic
cubical
cubicle/MS
cubism/M
cubist/SM
cubit/SM
cuboid/S
cuckold/MDSG
cuckoldry/M
cuckoo/SM
cucumber/SM
cud/SM
cuddle/DSMG
cuddly/TR
cudgel/SGMDJ
cue/DSMG
cuff/MDGS
cuisine/SM
cul-de-sac
culinary
cull/MDGS
cullender/MS
culminate/XDSGN
culmination/M
culotte/SM
culpa/S
culpability/M
culpable/I
culpably
culprit/SM
cult/MS
cultism/M
cultist/MS
cultivable
cultivar/SM
cultivate/BDSGN
cultivated/U
cultivation/M
cultivator/MS
cultural/Y
culture/MGDS
cultured/U
culvert/MS
cum/SM
cumber/SDG
cumbersome/P
cumbersomeness/M
cumbrous
cumin/M
cummerbund/MS
cumming
cumulative/Y
cumuli
cumulonimbi
cumulonimbus/M
cumulus/M
cuneiform/M
cunnilingus/M
cunning/MRYT
cunt/MS!
cup/SM
cupboard/SM
cupcake/MS
cupful/SM
cupid/SM
cupidity/M
cupola/SMD
cuppa/S
cupped
cupping
cupric
cur/SMY
curability/M
curacao
curacy/SM
curare/M
curate/DSMGNV
curative/MS
curator/KMS
curatorial
cura�ao
curb/MDGS
curbing/M
curbside
curbstone/SM
curd/MS
curdle/DSG
cure's
cure/KZGBDRS
cured/U
curer/KM
curettage/M
curfew/SM
curia/M
curiae
curie/SM
curio/SM
curiosity/SM
curious/YP
curiousness/M
curium/M
curl's
curl/UDGS
curler/SM
curlew/SM
curlicue/DSMG
curliness/M
curling/M
curly/RPT
curmudgeon/MYS
currant/MS
currency/SM
current's
current/FSY
curricula
curricular
curriculum/M
curry/DSMG
currycomb/SGMD
curse's
curse/ADSGV
cursed/Y
cursive's
cursive/EAY
cursor/SM
cursorily
cursoriness/M
cursory/P
curt/RYTP
curtail/GDSL
curtailment/SM
curtain/GMDS
curtness/M
curtsy/GDSM
curvaceous/P
curvaceousness/M
curvature/SM
curve/DSMG
curvy/RT
cushion/MDSG
cushy/RT
cusp/MS
cuspid/SM
cuspidor/SM
cuss's
cuss/EFGSD
cussed/PY
custard/MS
custodial
custodian/MS
custodianship/M
custody/M
custom/SZMR
customarily
customary/U
customer/M
customhouse/SM
customization/M
customize/DSGB
cut/TSMR
cutaneous
cutaway/MS
cutback/MS
cute/YP
cuteness/M
cutesy/TR
cutey/S
cuticle/MS
cutie/SM
cutlass/MS
cutler/SM
cutlery/M
cutlet/SM
cutoff/SM
cutout/SM
cutter/SM
cutthroat/SM
cutting/MYS
cuttlefish/MS
cutup/SM
cutworm/MS
cw
cwt
cyan/M
cyanide/M
cyanobacteria
cyber
cyberattack/SM
cyberbully/SM
cybercafe/S
cybercaf�/S
cybernetic/S
cybernetics/M
cyberpunk/SM
cybersecurity
cybersex
cyberspace/MS
cyborg/SM
cyclamen/MS
cycle/ADSMG
cyclic
cyclical/Y
cyclist/MS
cyclometer/MS
cyclone/MS
cyclonic
cyclopaedia/MS
cyclopedia/MS
cyclopes
cyclops/M
cyclotron/MS
cygnet/MS
cylinder/MS
cylindrical
cymbal/MS
cymbalist/MS
cynic/SM
cynical/Y
cynicism/M
cynosure/MS
cypher/M
cypress/MS
cyst/MS
cysteine/SM
cystic
cysticerci
cysticercoid/S
cysticercoses
cysticercosis
cysticercus
cystitis
cystoscope
cystoscopic
cystoscopy
cytokine/SM
cytologist/SM
cytology/M
cytoplasm/M
cytoplasmic
cytosine/M
czar/MS
czarina/SM
czarism
czarist/SM
d'Arezzo/M
d'Estaing/M
d/NXGJ
dB
dab/SM
dabbed
dabber/MS
dabbing
dabble/ZGDRS
dabbler/M
dace/SM
dacha/MS
dachshund/MS
dactyl/MS
dactylic/MS
dad/SM
dadaism/M
dadaist/MS
daddy/SM
dado/SM
dadoes
daemon/MS
daemonic
daffiness/M
daffodil/SM
daffy/PTR
daft/PTRY
daftness/M
dag/S
dagger/MS
dago/S
dagoes
daguerreotype/DSMG
dahlia/MS
dailiness/M
daily/PSM
daintily
daintiness/M
dainty/RSMTP
daiquiri/MS
dairy/GSM
dairying/M
dairymaid/MS
dairyman/M
dairymen
dairywoman/M
dairywomen
dais/MS
daisy/SM
dale/SM
dalliance/MS
dallier/M
dally/ZGDRS
dalmatian/MS
dam/SM
damage/MGDS
damageable
damaged/U
damages/M
damask/MDGS
dame/SM
dammed
damming
dammit
damn/SBGMD
damnably
damnation/M
damned/T
damp/SPXZTGMDNRY
dampen/ZGDR
dampener/M
damper/M
dampness/M
damsel/MS
damselfly/SM
damson/MS
dance/MZGDRS
dancer/M
dancing/M
dandelion/SM
dander/M
dandify/GDS
dandle/GDS
dandruff/M
dandy/TRSM
dang/SZGDR
danger/M
dangerous/Y
dangle/ZGDRS
dangler/M
danish/MS
dank/PTRY
dankness/M
danseuse/MS
dapper/TR
dapple/MGDS
dare/DRSMZG
daredevil/MS
daredevilry/M
darer/M
daresay
daring/MY
dark/PXTMNRY
darken/ZGDR
darkener/M
darkie/S
darkness/M
darkroom/MS
darling/MS
darn/SZGMDR
darned/TR
darner/M
dart/SZGMDR
dartboard/MS
darter/M
dash/ZGMDRS
dashboard/SM
dasher/M
dashiki/MS
dashing/Y
dastard/MYS
dastardliness/M
data
database/SM
dataset/MS
datasheet/SM
datatype
date/DRSMZGV
datebook/S
dated/U
dateless
dateline/MGDS
dater/M
dateset
dative/MS
datum/M
daub/SZGMDR
dauber/M
daughter/SMY
daunt/GDS
daunting/Y
dauntless/YP
dauntlessness/M
dauphin/MS
davenport/MS
davit/MS
dawdle/ZGDRS
dawdler/M
dawn/SGMD
day/SM
daybed/MS
daybreak/M
daycare/M
daydream/MDRZGS
daydreamer/M
daylight/MS
daylights/M
daylong
daytime/M
daze/DSMG
dazed/Y
dazzle/MZGDRS
dazzler/M
dazzling/Y
db
dbl
dc
dd/SDG
dded/K
dding/K
de
deacon/MS
deaconess/MS
dead/XTMNRY
deadbeat/MS
deadbolt/SM
deaden/GD
deadhead/SDG
deadline/SM
deadliness/M
deadlock/GSMD
deadly/TPR
deadpan/MS
deadpanned
deadpanning
deadwood/M
deaf/PXTNR
deafen/GD
deafening/Y
deafness/M
deal/SJZGMR
dealer/M
dealership/SM
dealing/M
dealt
dean/M
deanery/SM
deanship/M
dear/SPTMRYH
dearest/S
dearie/M
dearness/M
dearth/M
dearths
deary/SM
death/MY
deathbed/SM
deathblow/MS
deathless/Y
deathlike
deaths
deathtrap/MS
deathwatch/MS
deaves
deb/SM
debacle/MS
debarkation/M
debarment/M
debatable/U
debate/BMZR
debater/M
debating/M
debauch/MDSG
debauchee/MS
debauchery/SM
debenture/MS
debilitate/DSGN
debilitation/M
debility/SM
debit/D
debonair/PY
debonairness/M
debouch/GDS
debridement
debris/M
debt/SM
debtor/MS
debugger/S
debut/GMD
debutante/SM
decade/MS
decadence/M
decadency/M
decadent/MYS
decaf/MS
decaffeinate/DSG
decagon/MS
decal/MS
decampment/M
decapitate/XGNDS
decapitator/MS
decarboxylation
decathlete/S
decathlon/SM
decay/GD
deceased/M
decedent/MS
deceit/MS
deceitful/YP
deceitfulness/M
deceive/UGDS
deceiver/MS
deceiving/Y
decelerate/GNDS
deceleration/M
decelerator/SM
decency/ISM
decennial/SM
decent/IY
deception/MS
deceptive/YP
deceptiveness/M
decibel/MS
decidable/U
decide/BZGDRS
decided/Y
deciduous
decile/S
deciliter/MS
decimal/SM
decimalization
decimate/DSGN
decimation/M
decimeter/MS
decipherable/UI
decision/IM
decisions
decisis
decisive/IPY
decisiveness/IM
deck/SGMD
deckchair/S
deckhand/SM
deckle/S
declamation/MS
declamatory
declaration/MS
declarative
declaratory
declare/DRSZGB
declared/U
declarer/M
declension/SM
declination/M
decline/DRSMZG
decliner/M
declivity/SM
decoherence
decolletage/SM
decollete
decongestant/MS
deconstructionism
decor/MS
decorate/AGNVDS
decorating/M
decoration/AM
decorations
decorative/Y
decorator/MS
decorous/IY
decorousness/M
decorum/M
decoupage/DSMG
decoy/GMDS
decreasing/Y
decree/MDS
decreeing
decrement/GDS
decrepit
decrepitude/M
decriminalization/M
decry/GDS
decrypt/BSGD
decryption
dedendum/SM
dedicate/AGDS
dedication/SM
dedicator/SM
dedicatory
deduce/GDS
deducible
deduct/GVD
deductible/SM
deduction/SM
deductive/Y
deed/GD
deejay/MS
deem/ASGD
deep/SPXTMNRY
deepen/GD
deepfake/SM
deepness/M
deer/M
deerskin/M
deerstalker/S
def/Z
defacement/M
defacer/SM
defalcate/DSXGN
defalcation/M
defamation/M
defamatory
defame/ZGDRS
defamer/M
defaulter/SM
defeat/MDRZGS
defeated/U
defeater/M
defeatism/M
defeatist/MS
defecate/GNDS
defecation/M
defect/MDGVS
defection/MS
defective/MPYS
defectiveness/M
defector/MS
defendant/SM
defended/U
defenestration/S
defense/DSMGV
defenseless/YP
defenselessness/M
defensible/I
defensibly/I
defensive/MYP
defensiveness/M
deference/M
deferential/Y
deferral/MS
deferred
deferring
deffer
deffest
defiant/Y
defibrillation
defibrillator/S
deficiency/SM
deficient
deficit/SM
defilement/M
definable/IU
define/AGDS
defined/U
definer/MS
definite/IYVP
definiteness/IM
definition/AM
definitional/Y
definitions
definitive/Y
deflate/GNDS
deflation/M
deflationary
deflect/DGVS
deflection/MS
deflector/SM
defogger/SM
defoliant/SM
defoliate/DSGN
defoliation/M
defoliator/MS
deformity/SM
defraud/DRZGS
defrauder/M
defrayal/M
defrock/DG
defroster/MS
deft/PTRY
deftness/M
defunct
defy/GDS
deg
degeneracy/M
degenerate/MVX
degrade/B
degree/MS
dehydrator/SM
dehydrogenase/M
deicer/MS
deification/M
deify/NGDS
deign/GDS
deist/MS
deistic
deity/SM
deject/GDS
dejected/Y
dejection/M
delay/ZDR
delectable
delectably
delectation/M
delegate/GD
delete/XGNDS
deleterious
deletion/M
delft/M
delftware/M
deli/SM
deliberate/XYVP
deliberateness/M
delicacy/ISM
delicate/IY
delicateness/M
delicatessen/SM
delicious/PY
deliciousness/M
delighted/Y
delightful/Y
deliminator
delineate/GNXDS
delineation/M
delinquency/SM
delinquent/SMY
deliquesce/DSG
deliquescent
delirious/YP
deliriousness/M
delirium/SM
deliver/ADGS
deliverable/S
deliverables/U
deliverance/M
delivered/U
deliverer/SM
dell/SM
delphinium/MS
delta/MS
delude/GDS
deluge/MGDS
delusion/MS
delusional
delusive/Y
deluxe
delve/ZGDRS
delver/M
demagogic
demagogically
demagogue/SM
demagoguery/M
demagogy/M
demand/GMDS
demanding/U
demarcate/DSGNX
demarcation/M
demean/GDS
demeanor/M
demented/Y
dementia/M
demesne/MS
demigod/MS
demigoddess/MS
demijohn/SM
demimondaine/SM
demimonde/M
demise/MGD
demitasse/MS
demo/GMD
democracy/SM
democrat/MS
democratic/U
democratically
democratization/M
democratize/GDS
demode
demographer/SM
demographic/SM
demographically
demographics/M
demography/M
demolish/DSG
demolition/MS
demon/MS
demonetization/M
demoniac
demoniacal/Y
demonic
demonically
demonize/GDS
demonology/SM
demonstrability
demonstrable/I
demonstrably
demonstrate/XGNVDS
demonstration/M
demonstrative/MYSP
demonstrativeness/M
demonstrator/MS
demonym/S
demote/GD
demotic
demount
demulcent/SM
demur/TMRS
demure/PY
demureness/M
demurral/SM
demurred
demurrer/SM
demurring
den/M
denationalization
denaturation
denature/DG
dendrite/SM
dengue/M
deniability
deniable/U
denial/MS
denier/M
denigrate/DSGN
denigration/M
denim/MS
denitrification
denizen/MS
denominational
denotative
denouement/MS
denounce/LDSG
denouncement/SM
dense/PYTR
denseness/M
density/SM
dent/ISGMD
dental/Y
dentifrice/SM
dentin/M
dentine/M
dentist/MS
dentistry/M
dentition/M
denture/IMS
denuclearize/GDS
denudation/M
denude/GDS
denunciation/SM
deny/ZGDRS
deodorant/SM
deodorization/M
deodorize/DRSZG
deodorizer/M
departed/M
department/MS
departmental/Y
departmentalization/M
departmentalize/GDS
departure/SM
dependability/M
dependable/U
dependably
dependence/IM
dependency/SM
dependent/IMYS
depict/GDS
depiction/MS
depilatory/SM
deplete/GNDS
depletion/M
deplorably
deplore/BGDS
deploy/ALGDS
deployment/AM
deployments
deponent/MS
deportation/MS
deportee/MS
deportment/M
deposit/AGMDS
depositor/MS
depository/SM
deprave/GDS
depravity/SM
deprecate/GNDS
deprecating/Y
deprecation/M
deprecatory
depreciate/DSGN
depreciation/M
depredation/SM
depressant/SM
depressing/Y
depression/SM
depressive/SM
depressor/MS
depressurization
deprive/GDS
deprogramming
depth/M
depths
deputation/MS
depute/DSG
deputize/DSG
deputy/SM
derailleur/SM
derailment/SM
derangement/M
derby/SM
dereliction/M
deride/GDS
derision/M
derisive/PY
derisiveness/M
derisory
derivation/MS
derivative/MS
derive/B
dermal
dermatitis/M
dermatological
dermatologist/SM
dermatology/M
dermis/M
derogate/DSGN
derogation/M
derogatorily
derogatory
derrick/SM
derriere/SM
derringer/SM
derri�re/SM
derv
dervish/MS
desalinate/GNDS
desalination/M
desalinator/SM
desalinization/M
desalinize/GDS
descant/M
descend/FGDS
descendant/MS
descender
describable/I
describe/BZGDR
describer/M
description/SM
descriptive/PY
descriptiveness/M
descriptor/S
descry/GDS
desecrate/DSGN
desecration/M
deselection
desert/SDRZGM
deserter/M
desertification
desertion/SM
deserved/UY
deserving/U
deshabille/M
desiccant/SM
desiccate/DSGN
desiccation/M
desiccator/SM
desiderata
desideratum/M
designate/DSGNX
designated/U
designation/M
designee
desirability/UM
desirableness/M
desirably/U
desire/B
desired/U
desirous
desist/SDG
desk/SM
deskill/G
desktop/SM
desolate/PDSYGN
desolateness/M
desolation/M
despair/SMDG
despairing/Y
desperado/M
desperadoes
desperate/YNP
desperateness/M
desperation/M
despicable
despicably
despise/DSG
despite
despoilment/M
despondence/M
despondency/M
despondent/Y
despotic
despotically
despotism/M
dessert/SM
dessertspoon/S
dessertspoonful/S
destination/SM
destine/DSG
destiny/SM
destitute/N
destitution/M
destroy/SZGDR
destroyer/M
destruct/GVD
destructibility/IM
destructible/I
destruction/M
destructive/PY
destructiveness/M
desuetude/M
desultorily
desultory
detach/BLGDS
detachment/MS
detain/LGDS
detainee/MS
detainment/M
detect/SDGVB
detectable/U
detected/U
detection/M
detective/SM
detector/SM
detente/SMNX
detention/M
deter/SL
detergent/SM
deteriorate/DSGN
deterioration/M
determent/M
determinable/I
determinant/SM
determinate
determine/AGDS
determined/U
determinedly
determiner/SM
determinism/M
deterministic
deterministically
deterred/U
deterrence/M
deterrent/MS
deterring
detestably
detestation/M
dethrone/DSLG
dethronement/M
detonate/GNDSX
detonation/M
detonator/SM
detox/MDSG
detoxification/M
detoxify/DSGN
detract/GD
detriment/SM
detrimental/Y
detritus/M
deuce/SM
deuteride/S
deuterium/M
devastate/GNDS
devastating/Y
devastation/M
devastator/MS
develop/ASGDL
developed/U
developer/SM
development/ASM
developmental/Y
deviance/M
deviancy/M
deviant/SM
deviate/DSMGNX
deviating/U
deviation/M
devil/SMDGL
devilish/YP
devilishness/M
devilment/M
devilry/SM
deviltry/SM
devious/YP
deviousness/M
devoid
devolution/M
devolve/DSG
devoted/Y
devotee/SM
devotion/MS
devotional/SM
devour/SDG
devout/PRYT
devoutness/M
dew/M
dewberry/SM
dewclaw/SM
dewdrop/SM
dewiness/M
dewlap/SM
dewy/RTP
dexterity/M
dexterous/YP
dexterousness/M
dextrose/M
dharma
dhoti/SM
dhow/MS
diabetes/M
diabetic/SM
diabolic
diabolical/Y
diacritic/MS
diacritical
diadem/SM
diaereses
diaeresis/M
diagnose/DSG
diagnosis/M
diagnostic/S
diagnostically
diagnostician/SM
diagnostics/M
diagonal/SMY
diagram/SM
diagrammatic
diagrammatically
diagrammed
diagramming
dial/AMDGS
dialect/SM
dialectal
dialectic/SM
dialectical
dialectics/M
dialing/S
dialog/SDG
dialogue/DRSMG
dialyses
dialysis/M
dialyzes
diam
diamagnetic
diamagnetism
diamante
diamant�
diameter/SM
diametric
diametrical/Y
diamond/SM
diamondback/MS
diapason/SM
diaper/SMDG
diaphanous
diaphragm/SM
diaphragmatic
diarist/SM
diarrhea/M
diary/SM
diaspora/SM
diastase/M
diastole/M
diastolic
diathermy/M
diatom/SM
diatomaceous
diatomic
diatonic
diatribe/SM
diazepam
dibble/DSMG
dibs/M
dice/GDS
dices/I
dicey
dichotomous
dichotomy/SM
dicier
diciest
dick/MRXZS
dicker/DG
dickey/SM
dickhead/S
dicky/SM
dickybird/S
dicotyledon/MS
dicotyledonous
dict
dicta
dictate/DSMGNX
dictation/M
dictator/SM
dictatorial/Y
dictatorship/SM
diction/M
dictionary/SM
dictum/M
did/AU
didactic
didactically
diddle/DRSZG
diddler/M
diddly
diddlysquat
diddums
didgeridoo/S
didn't
dido/MS
didoes
didst
die/DSM
diehard/SM
dielectric/MS
diereses
dieresis/M
diesel/SMDG
diet/MDRZGS
dietary/SM
dieter/M
dietetic/S
dietetics/M
dietician/MS
dietitian/MS
diff/DRZGS
differ/DG
difference/IM
differences
different/IY
differentiable
differential/SM
differentiate/DSGN
differentiated/U
differentiation/M
differentiator/S
difficult/Y
difficulty/SM
diffidence/M
diffident/Y
diffract/GSD
diffraction/M
diffuse/DSYGNVP
diffuseness/M
diffusion/M
diffusivity
dig/SM
digerati/M
digest/SMDGV
digested/U
digestibility/M
digestible/I
digestion/IM
digestions
digestive/S
digger/SM
digging/S
diggings/M
digicam/S
digit/SM
digital/Y
digitalis/M
digitalize/DSG
digitization
digitize/GDS
dignified/U
dignify/DSG
dignitary/SM
dignity/ISM
digraph/M
digraphs
digress/GVDS
digression/MS
dihydro
dike/MGDS
diktat/S
dilapidated
dilapidation/M
dilatation/M
dilate/DSGN
dilation/M
dilator/SM
dilatory
dildo/S
dilemma/MS
dilettante/SM
dilettantish
dilettantism/M
diligence/M
diligent/Y
dill/MS
dilly/SM
dillydally/DSG
diluent
dilute/DSGNX
diluted/U
dilution/M
dim/PSRY
dime/MS
dimension/SM
dimensional
dimensionless
diminish/GDS
diminished/U
diminuendo/SM
diminuendoes
diminution/SM
diminutive/SM
dimity/M
dimmed/U
dimmer/SM
dimmest
dimming
dimness/M
dimple/DSMG
dimply
dimwit/SM
dimwitted
din/ZGSMDR
dinar/SM
dine/S
diner/M
dinette/MS
ding/MDG
dingbat/MS
dingdong/SGMD
dinghy/SM
dingily
dinginess/M
dingle/SM
dingo/M
dingoes
dingus/MS
dingy/RPT
dink/R
dinky/RSMT
dinned
dinner/SMDG
dinnertime/M
dinnerware/M
dinning
dinosaur/SM
dint/M
diocesan/MS
diocese/MS
diode/SM
diorama/SM
dioxide/SM
dioxin/SM
dip/SM
diphtheria/M
diphthong/SM
diploid/SM
diploma/SM
diplomacy/M
diplomat/MS
diplomata
diplomatic/U
diplomatically
diplomatist/MS
diplopia
dipole/SM
dipped
dipper/SM
dipping
dippy/RT
dipso/S
dipsomania/M
dipsomaniac/MS
dipstick/SM
dipterous
diptych/M
diptychs
dire/YTR
direct/ASDGVT
directer
direction/IM
directional
directionality/SM
directionless
directions/A
directive/SM
directly
directness/IM
director/MS
directorate/SM
directorial
directorship/SM
directory/SM
direful
dirge/SM
dirigible/MS
dirk/MS
dirndl/SM
dirt/M
dirtball/S
dirtily
dirtiness/M
dirty/DRSTGP
dis/M
disable/DSGL
disablement/M
disambiguate/DSGN
disappointing/Y
disarming/Y
disastrous/Y
disbandment/M
disbarment/SM
disbelieving/Y
disbursal/M
disburse/DSGL
disbursement/MS
disc/M
discern/LSDG
discernible/I
discernibly
discerning/Y
discernment/M
discharged/U
disciple/SM
discipleship/M
disciplinarian/SM
disciplinary
discipline/DSMG
disciplined/U
disclose/DSBG
disclosed/U
disco/MG
discography/SM
discoloration/S
discombobulate/DSGN
discombobulation/M
discomfit/DG
discomfiture/M
discommode/DG
disconcerting/Y
disconnected/PY
disconnectedness/M
disconsolate/Y
discordance/M
discordant/Y
discotheque/SM
discourage/LGDS
discouragement/SM
discouraging/Y
discover/ABSDG
discoverability
discovered/U
discoverer/MS
discovery/ASM
discreet/PRYT
discreetness/M
discrepancy/SM
discrepant
discrete/PYN
discreteness/M
discretion/IM
discretionary
discriminant
discriminate/GNDS
discriminating/U
discrimination/M
discriminator/MS
discriminatory
discursiveness/M
discus/MS
discussant/SM
discussion/SM
disdain/SMDG
disdainful/Y
disembowel/SDLG
disembowelment/M
disfigurement/SM
disfranchisement/M
disgorgement/M
disgruntle/LGDS
disgruntlement/M
disguise/GD
disguised/U
disgusted/Y
disgusting/Y
dish/MDSG
dishabille/M
disharmonious
dishcloth/M
dishcloths
disheartening/Y
dishevel/DGLS
dishevelment/M
dishpan/SM
dishrag/SM
dishtowel/MS
dishware/M
dishwasher/MS
dishwater/M
dishy
disillusion/GLD
disillusionment/M
disinfectant/MS
disinfection/M
disinterested/PY
disinterestedness/M
disjointed/YP
disjointedness/M
disjunctive
disjuncture
disk/MS
diskette/MS
dislodge/GDS
dismal/Y
dismantlement/M
dismay/SMDG
dismayed/U
dismember/LGD
dismemberment/M
dismissive/Y
disorder/Y
disorganization/M
disparage/DSGL
disparagement/M
disparaging/Y
disparate/Y
dispatcher/MS
dispel/S
dispelled
dispelling
dispensary/SM
dispensation/MS
dispense/BZGDRS
dispenser/M
dispersal/M
disperse/GNDS
dispersion/M
dispirit/GDS
displeasure/M
disposable/SM
disposal/SM
disposed/I
disposition/ISM
dispossession/M
disproof/SM
disproportional
disprove/B
disputable/I
disputably/I
disputant/MS
disputation/SM
disputatious/Y
dispute/DRSMZGB
disputed/U
disputer/M
disquiet/GSMD
disquisition/MS
disregardful
disrepair/M
disrepute/MB
disrupt/GVSD
disruption/SM
disruptive/Y
dissect/SDG
dissed
dissemblance/M
dissemble/ZGDRS
dissembler/M
disseminate/GNDS
dissemination/M
dissension/SM
dissent/SMDRZG
dissenter/M
dissentious
dissertation/SM
disses
dissidence/M
dissident/MS
dissimilar
dissimilitude/S
dissing
dissipate/GNDS
dissipation/M
dissociate/GNVDS
dissociation/M
dissoluble/I
dissolute/YNP
dissoluteness/M
dissolve/AGDS
dissolved/U
dissonance/SM
dissonant
dissuade/GDS
dissuasive
dist
distaff/SM
distal/Y
distance/DSMG
distant/Y
distaste/SM
distemper/M
distention/SM
distillate/SMNX
distillation/M
distillery/SM
distinct/IYTVP
distincter
distinction/SM
distinctive/YP
distinctiveness/M
distinctness/IM
distinguish/GDSB
distinguishable/I
distinguished/U
distort/GDR
distortion/MS
distract/DGB
distractability
distracted/Y
distractible
distraction/S
distrait
distraught
distress/DG
distressful
distressing/Y
distribute/AGNVDS
distributed/U
distribution/AM
distributional
distributions
distributive/Y
distributor's
distributor/AS
distributorship/S
district's
district/AS
disturb/ZGSDR
disturbance/SM
disturbed/U
disturber/M
disturbing/Y
disunion/M
disyllabic
ditch/MDSG
dither/SMDRZG
ditherer/M
ditransitive
ditsy
ditto/SMDG
dittoes
ditty/SM
ditz/MS
ditzy/R
diuretic/MS
diurnal/Y
div
diva/MS
divalent
divan/SM
dive/MZTGDRS
diver/M
diverge/DSG
divergence/MS
divergent
diverse/XYNP
diverseness/M
diversification/M
diversify/GNDS
diversion/M
diversionary
diversity/SM
divert/SDRZG
diverticulitis/M
divest/SLDG
divestiture/MS
divestment/M
divide/DRSMZGB
divided/U
dividend/MS
divider/M
divination/M
divine/DRSMYZTG
diviner/M
diving/M
divinity/SM
divisibility/IM
divisible/I
division/MS
divisional
divisive/PY
divisiveness/M
divisor/SM
divorce/DSLMG
divorcee/MS
divorcement/MS
divorc�e/MS
divot/SM
divulge/GDS
divvy/DSMG
dixieland/M
dizygotic
dizygous
dizzily
dizziness/M
dizzy/DRSPTG
djellaba/MS
djellabahs
djinn
do/SJMRHZG
doable
dob/S
dobbed
dobbin/SM
dobbing
doberman/MS
dobro
doc/SM
docent/SM
docile/Y
docility/M
dock/MDRZGS
docket/SMDG
dockland/S
dockside
dockworker/MS
dockyard/MS
doctor/SMDG
doctoral
doctorate/MS
doctrinaire/MS
doctrinal
doctrine/MS
docudrama/SM
document/GMDS
documentary/SM
documentation/SM
documented/U
dodder/SMDG
doddery
doddle
dodge/DRSMZG
dodgem/S
dodger/M
dodgy/RT
dodo/MS
doe/SM
doer/M
does/AU
doeskin/MS
doesn't
doff/DGS
dog/SM
dogcart/SM
dogcatcher/SM
doge/MS
dogear/SMDG
dogfight/SM
dogfish/MS
dogged/PY
doggedness/M
doggerel/M
doggie/M
dogging
doggone/TGDRS
doggoned/TR
doggy/RSMT
doghouse/SM
dogie/SM
dogleg/SM
doglegged
doglegging
doglike
dogma/SM
dogmatic
dogmatically
dogmatism/M
dogmatist/SM
dognapper
dogsbody/S
dogsled/S
dogtrot/MS
dogtrotted
dogtrotting
dogwood/MS
doh/M
doily/SM
doing/USM
doldrums/M
dole's
dole/FGDS
doleful/YP
dolefulness/M
doll/MDGS
dollar/SM
dollhouse/SM
dollop/SGMD
dolly/SM
dolmen/SM
dolomite/M
dolor/M
dolorous/Y
dolphin/MS
dolt/MS
doltish/YP
doltishness/M
domain/SM
dome/MGDS
domestic/SM
domestically
domesticate/DSGN
domesticated/U
domestication/M
domesticity/M
domicile/DSMG
domiciliary
dominance/M
dominant/SMY
dominate/DSGNV
domination/M
dominator
dominatrices
dominatrix/M
domineer/SGD
domineering/Y
dominion/SM
domino/M
dominoes
don't
don/SM
dona/MS
donate/DSXGN
donation/M
donator/MS
done/FAU
dong/MDGS
dongle/SM
donkey/SM
donned
donning
donnish
donnybrook/MS
donor/SM
donuts
doodad/SM
doodah
doodahs
doodle/DRSMZG
doodlebug/SM
doodler/M
doohickey/SM
doolally
doom/MDGS
doomsayer/MS
doomsday/M
doomster/S
door's
door/IS
doorbell/MS
doorjamb/S
doorkeeper/MS
doorknob/MS
doorknocker/S
doorman/M
doormat/SM
doormen
doorplate/SM
doorpost/S
doorstep/MS
doorstepped
doorstepping
doorstop/MS
doorway/SM
dooryard/MS
doozie
doozy
dopa/M
dopamine
dope/MZGDRS
doper/M
dopey
dopier
dopiest
dopiness/M
doping/M
doppelganger/S
doppelg�nger/S
dork/MS
dorky/RT
dorm/MRZS
dormancy/M
dormant
dormer/M
dormice
dormitory/SM
dormouse/M
dorsal/Y
dory/SM
dosage/SM
dose/MGDS
dosh
dosimeter/SM
doss/DRSZG
dosshouse/S
dossier/MS
dost
dot/ZGSMDR
dotage/M
dotard/SM
dotcom/SM
dote/S
doter/M
doting/Y
dotted
dotting
dotty/RT
double's
double/ADSG
doubleheader/MS
doublespeak/M
doublet/MS
doubloon/SM
doubly
doubt/SMDRZG
doubter/M
doubtful/PY
doubtfulness/M
doubting/Y
doubtless/Y
douche/DSMG
dough/M
doughnut/SM
doughty/RT
doughy/TR
dour/RYTP
dourness/M
douse/DSG
dove/MS
dovecot/S
dovecote/SM
dovetail/MDSG
dovish
dowager/MS
dowdily
dowdiness/M
dowdy/RSPT
dowel/SMDG
dower/SMDG
down/MDRZGS
downbeat/SM
downcast
downdraft/MS
downer/M
downfall/SMN
downfield
downgrade/DSMG
downhearted/PY
downheartedness/M
downhill/MS
download/MDBSG
downmarket
downplay/DSG
downpour/MS
downrange
downright
downriver
downscale
downshift/SGD
downside/MS
downsize/GDS
downsizing/M
downspout/MS
downstage
downstairs/M
downstate/M
downstream
downswing/MS
downtempo
downtime/M
downtown/M
downtrend/MS
downtrodden
downturn/MS
downward/S
downwind
downy/RT
dowry/SM
dowse/DRSZG
dowser/M
dox/GDS
doxastic
doxology/SM
doxx/DSG
doyen/SM
doyenne/MS
doz/XGDNS
doze/M
dozen/MH
dozily
dozy/RTP
dpi
dpt
drab/MYSP
drabber
drabbest
drabness/M
drachma/MS
draconian
draft's
draft/ASDG
draftee/SM
drafter/SM
draftily
draftiness/M
drafting/M
draftsman/M
draftsmanship/M
draftsmen
draftswoman/M
draftswomen
drafty/RTP
drag/MS
dragged
dragging
draggy/TR
dragnet/SM
dragon/SM
dragonfly/SM
dragoon/SMDG
dragster/S
drain/SMDRZG
drainage/M
drainboard/SM
drainer/M
drainpipe/MS
drake/SM
dram/MS
drama/SM
dramatic/S
dramatically
dramatics/M
dramatist/SM
dramatization/SM
dramatize/DSG
drank
drape/DRSMZG
draper/M
drapery/SM
drastic
drastically
drat
dratted
draughtboard/S
draw/MRZGSJ
drawback/MS
drawbridge/MS
drawer/M
drawing/M
drawl/SMDG
drawn/A
drawstring/MS
dray/MS
dread/SMDG
dreadful/PY
dreadfulness/M
dreadlocks/M
dreadnought/MS
dream/SMDRZG
dreamboat/MS
dreamed/U
dreamer/M
dreamily
dreaminess/M
dreamland/M
dreamless
dreamlike
dreamworld/SM
dreamy/RPT
drear
drearily
dreariness/M
dreary/RPT
dreck
dreckish
drecky
dredge/DRSMZG
dredger/M
dregs/M
drek
drench/GDS
dress/AUGSDM
dressage/M
dresser/MS
dressiness/M
dressing/SM
dressmaker/SM
dressmaking/M
dressy/TPR
drew/A
dribble/MZGDRS
dribbler/M
driblet/MS
drier/M
drift/SMDRZG
drifter/M
driftnet/S
driftwood/M
drill/SMDRZG
driller/M
drillmaster/SM
drily
drink/SMRBJZG
drinkable/U
drinker/M
drip/MS
dripped
dripping/SM
drippy/TR
drive/RSMZGJ
drivel/SZGMDR
driveler/M
driven
driver/M
driveshaft/SM
driveway/MS
drizzle/MGDS
drizzly
drogue/SM
droid/S
droll/RPT
drollery/SM
drollness/M
drolly
dromedary/SM
drone/DSMG
drool/SMDG
droop/GSMD
droopiness/M
droopy/TPR
drop/MS
dropkick/MS
droplet/SM
dropout/SM
dropped
dropper/SM
dropping/S
droppings/M
dropsical
dropsy/M
dross/M
drought/SM
drove/RSMZ
drover/M
drown/GSJD
drowning/M
drowse/MGDS
drowsily
drowsiness/M
drowsy/RTP
drub/S
drubbed
drubber/SM
drubbing/MS
drudge/MGDS
drudgery/M
drug/MS
drugged
druggie/SM
drugging
druggist/SM
druggy
drugstore/MS
druid/SM
druidism/M
drum/MS
drumbeat/SM
drumlin/SM
drummed
drummer/SM
drumming
drumstick/SM
drunk/STMNR
drunkard/MS
drunken/PY
drunkenness/M
drupe/SM
druthers/M
dry/ZTGDRSMY
dryad/SM
dryer/SM
dryness/M
drys
drywall/M
dual
dualism/M
duality/M
dub/SM
dubbed
dubber/SM
dubbin/M
dubbing
dubiety/M
dubious/YP
dubiousness/M
ducal
ducat/SM
duchess/MS
duchy/SM
duck/MDGS
duckbill/SM
duckboards
duckling/SM
duckpins/M
duckweed/M
ducky/TRSM
duct's/K
duct/CKIFS
ductile
ductility/M
ducting
ductless
dud/GSMD
dude/MS
dudgeon/M
due's
due/IS
duel/MDRJZGS
dueler/M
duelist/SM
duenna/MS
duet/MS
duff/MDRZGS
duffel/S
duffer/M
dug
dugout/MS
duh
duke/MS
dukedom/SM
dulcet
dulcimer/MS
dull/DRPTGS
dullard/SM
dullness/M
dully
duly/U
dumb/DRYPT
dumbbell/SM
dumbfound/SDG
dumbness/M
dumbo/S
dumbstruck
dumbwaiter/SM
dumdum/MS
dummy/SM
dump/MDRZGS
dumpiness/M
dumpling/SM
dumpsite/S
dumpster/SM
dumpy/PTR
dun/SM
dunce/SM
dunderhead/MS
dune/MS
dung/MDGS
dungaree/MS
dungeon/SM
dunghill/MS
dunk/MDGS
dunned
dunner
dunnest
dunning
dunno
duo/SM
duodecimal
duodena
duodenal
duodenum/M
duopoly/S
dupe/MZGDRS
duper/M
duple
duplex/MS
duplicate's
duplicate/AGNVDS
duplication/AM
duplicator/MS
duplicitous
duplicity/M
durability/M
durable
durably
durance/M
duration/M
duress/M
durian/SM
during
durst
durum/M
dusk/M
duskiness/M
dusky/RTP
dust/MDRZGS
dustbin/SM
dustcart/S
duster/M
dustiness/M
dustless
dustman
dustmen
dustpan/SM
dustsheet/S
dusty/RTP
dutch
duteous/Y
dutiable
dutiful/YP
dutifulness/M
duty/SM
duvet/SM
dwarf/SGMD
dwarfish
dwarfism/M
dweeb/SM
dwell/SJZGR
dweller/M
dwelling/M
dwelt/I
dwindle/DSG
dyad
dyadic
dyadically
dybbuk/SM
dybbukim
dye/DRSMZG
dyeing/A
dyer/M
dyestuff/M
dying/M
dyke/MS
dynamic/MS
dynamical/Y
dynamics/M
dynamism/M
dynamite/MZGDRS
dynamiter/M
dynamo/SM
dynastic
dynasty/SM
dysentery/M
dysfunction/MS
dysfunctional
dyslectic/SM
dyslexia/M
dyslexic/SM
dyspepsia/M
dyspeptic/MS
dysphagia
dysphoria
dysphoric
dysprosium/M
dystonia
dystopi
dystopia/S
dystopian/S
dz
d�bridement
d�butante/SM
d�colletage/SM
d�collet�
d�mod�
d�railleur/MS
d�shabill�/M
d�tente/M
e'en
e'er
e/FDST
eBay/M
eBook/MS
eCommerce/M
eMusic/M
ea
each
eager/PTRY
eagerness/M
eagle/MS
eaglet/MS
ear/SMDY
earache/SM
earbud/SM
eardrum/SM
earful/SM
earl/MS
earldom/SM
earliness/M
earlobe/SM
early/RTP
earmark/SMDG
earmuff/SM
earn/DRZTGJS
earned/U
earner/M
earnest/SMYP
earnestness/M
earnings/M
earphone/MS
earpiece/S
earplug/SM
earring/SM
earshot/M
earsplitting
earth's
earth/UDYG
earthbound
earthen
earthenware/M
earthiness/M
earthling/MS
earthly/RT
earthquake/SM
earths/U
earthshaking
earthward/S
earthwork/MS
earthworm/MS
earthy/RTP
earwax/M
earwig/SM
ease/EDSM
easel/SM
easement/SM
easily/U
easiness/UM
easing
east/M
eastbound
easterly/SM
eastern/ZR
easterner/M
easternmost
eastward/S
easy/URTP
easygoing
eat/ZGBSNR
eatable/SM
eaten/U
eater/M
eatery/SM
eave/MS
eavesdrop/S
eavesdropped
eavesdropper/SM
eavesdropping
ebb/SMDG
ebony/SM
ebullience/M
ebullient/Y
ebullition/M
eccentric/SM
eccentrically
eccentricity/SM
eccl
ecclesial
ecclesiastic/SM
ecclesiastical/Y
echelon/SM
echidna
echinoderm/SM
echo's
echo/ADG
echoes/A
echoic
echolocation/M
echos
eclair/SM
eclat/M
eclectic/SM
eclectically
eclecticism/M
eclipse/DSMG
ecliptic/M
eclogue/SM
ecocide/M
ecol
ecologic
ecological/Y
ecologist/MS
ecology/M
econ
econometric/S
economic/S
economical/UY
economics/M
economist/SM
economize/DRSZG
economizer/M
economy/SM
ecosystem/MS
ecotourism/M
ecotourist/MS
ecru/M
ecstasy/SM
ecstatic
ecstatically
ectopic
ectopically
ecu/S
ecumenical/Y
ecumenicism/M
ecumenism/M
eczema/M
ed/ACSM
edamame
eddy/DSMG
edelweiss/M
edema/SM
edge/MZGJDRS
edger/M
edgeways
edgewise
edgily
edginess/M
edging/M
edgy/RTP
edibility/M
edible/SMP
edibleness/M
edict/SM
edification/M
edifice/SM
edifier/M
edify/DRSZGN
edifying/U
edit's
edit/ADGS
editable
edited/U
edition/MS
editor/SM
editorial/SMY
editorialize/DSG
editorship/M
educ
educability/M
educable/I
educate/ADSGNV
educated/U
education/AM
educational/Y
educationalist/S
educationist/S
educations
educator/MS
educe/DSGB
edutainment/M
eek
eel/SM
eerie/RT
eerily
eeriness/M
eff/GSD
efface/DSLG
effacement/M
effect/SMDGV
effective/IPY
effectiveness/IM
effectual/IY
effectuate/DSG
effeminacy/M
effeminate/Y
effendi/SM
efferent
effervesce/GDS
effervescence/M
effervescent/Y
effete/YP
effeteness/M
efficacious/Y
efficacy/IM
efficiency/ISM
efficient/IY
effigy/SM
efflorescence/M
efflorescent
effluence/M
effluent/MS
effluvia
effluvium/M
efflux
effort/SM
effortful
effortless/YP
effortlessness/M
effrontery/M
effulgence/M
effulgent
effuse/DSGNVX
effusion/M
effusive/YP
effusiveness/M
egad/S
egalitarian/SM
egalitarianism/M
egg/GSMD
eggbeater/MS
eggcup/SM
egghead/SM
eggnog/M
eggplant/MS
eggshell/SM
eglantine/SM
ego/SM
egocentric/MS
egocentrically
egocentricity/M
egoism/M
egoist/SM
egoistic
egoistical/Y
egomania/M
egomaniac/MS
egotism/M
egotist/SM
egotistic
egotistical/Y
egregious/PY
egregiousness/M
egress/MS
egret/SM
eh
eider/SM
eiderdown/MS
eigenvalue/S
eigenvector/S
eight/SM
eighteen/MHS
eighteenth/M
eighteenths
eighth/M
eighths
eightieth/M
eightieths
eighty/SMH
einsteinium/M
eisteddfod/S
either
ejaculate/GNXDS
ejaculation/M
ejaculatory
eject/SDG
ejection/MS
ejector/SM
eke/DSG
elaborate/YGNDSPX
elaborateness/M
elaboration/M
elan/M
eland/SM
elapse/DSG
elastic/MS
elastically
elasticated
elasticity/M
elasticize/DSG
elate/DSGN
elated/Y
elation/M
elbow/SMDG
elbowroom/M
elder/SMY
elderberry/SM
eldercare/M
eldest
eldritch
elect's
elect/ASDGV
electable
election/AMS
electioneer/DGS
elective/MS
elector/MS
electoral/Y
electorate/MS
electric/S
electrical/Y
electrician/MS
electricity/M
electrification/M
electrifier/M
electrify/ZGNDRS
electrocardiogram/MS
electrocardiograph/M
electrocardiographs
electrocardiography/M
electrocute/DSXGN
electrocution/M
electrode/SM
electrodynamics
electroencephalogram/MS
electroencephalograph/M
electroencephalographic
electroencephalographs
electroencephalography/M
electrologist/SM
electrolysis/M
electrolyte/MS
electrolytic
electromagnet/MS
electromagnetic
electromagnetically
electromagnetism/M
electromotive
electron/MS
electronic/S
electronica/M
electronically
electronics/M
electroplate/DSG
electroscope/SM
electroscopic
electroshock/M
electrostatic/S
electrostatics/M
electrotype/MS
electroweak
eleemosynary
elegance/IM
elegant/IY
elegiac/MS
elegiacal
elegy/SM
elem
element/MS
elemental/Y
elementary
elephant/SM
elephantiasis/M
elephantine
elev
elevate/XDSGN
elevation/M
elevator/MS
eleven/SMH
elevens/S
eleventh/M
elevenths
elf/M
elfin
elfish
elicit/SDG
elicitation/M
elicitor/MS
elide/DSG
eligibility/IM
eligible
eliminate/XDSGN
elimination/M
eliminator/S
elision/MS
elite/SM
elitism/M
elitist/MS
elixir/SM
elk/SM
ell/SM
ellipse/MS
ellipsis/M
ellipsoid/SM
ellipsoidal
elliptic
elliptical/Y
elm/SM
elocution/M
elocutionary
elocutionist/SM
elodea/SM
elongate/DSGNX
elongation/M
elope/DSGL
elopement/MS
eloquence/M
eloquent/Y
else/M
elsewhere
elucidate/DSGNX
elucidation/M
elude/DSG
elusive/YP
elusiveness/M
elver/SM
elves
elvish
em's
em/S
emaciate/GNDS
emaciation/M
email/SMDG
emanate/XDSGN
emanation/M
emancipate/DSGN
emancipation/M
emancipator/MS
emasculate/GNDS
emasculation/M
embalm/SZGDR
embalmer/M
embank/SLGD
embankment/SM
embargo/MDG
embargoes
embark/AEGDS
embarkation/EM
embarkations
embarrass/GLDS
embarrassed/U
embarrassing/Y
embarrassment/SM
embassy/SM
embattled
embattlement/SM
embed/S
embedded
embedding
embellish/LGDS
embellishment/SM
ember/SM
embezzle/ZGLDRS
embezzlement/M
embezzler/M
embiggen
embitter/GLDS
embitterment/M
emblazon/GDLS
emblazonment/M
emblem/SM
emblematic
emblematically
embodiment/EM
embody/AEGSD
embolden/DGS
embolism/MS
embolization
emboss/DRSZG
embosser/M
embouchure/M
embower/SGD
embrace/DSMG
embraceable
embrasure/MS
embrocation/MS
embroider/SDRZG
embroiderer/M
embroidery/SM
embroil/DGLS
embroilment/M
embryo/SM
embryological
embryologist/MS
embryology/M
embryonic
emcee/DSM
emceeing
emend/SDG
emendation/MS
emerald/MS
emerge/ADSG
emergence/AM
emergency/SM
emergent
emerita
emeritus
emery/M
emetic/SM
emf/S
emigrant/SM
emigrate/DSXGN
emigration/M
emigre/SM
eminence/MS
eminent/Y
emir/MS
emirate/MS
emissary/SM
emission/SM
emit/S
emitted
emitter/MS
emitting
emo/SM
emoji/SM
emollient/MS
emolument/MS
emote/XDSGNV
emoticon/SM
emotion/M
emotional/UY
emotionalism/M
emotionalize/GDS
emotionless
emotive/Y
empanel/GDS
empathetic
empathic
empathically
empathize/DSG
empathy/M
emperor/MS
emphases
emphasis/M
emphasize/AGDS
emphatic/U
emphatically
emphysema/M
empire/SM
empiric
empirical/Y
empiricism/M
empiricist/SM
emplace/LGDS
emplacement/SM
employ's
employ/ADGLS
employable/U
employee/SM
employer/SM
employment/UAM
employments
emporium/SM
empower/SDGL
empowerment/M
empress/MS
emptily
emptiness/M
empty/TGPDRSM
empyrean/M
emu/SM
emulate/DSGNVX
emulation/M
emulator/SM
emulsification/M
emulsifier/M
emulsify/NDRSZG
emulsion/MS
en/SM
enable/DRSZG
enabler/M
enact/ASLDG
enactment/ASM
enamel/JSZGMDR
enameler/M
enamelware/M
enamor/SGD
enc
encamp/LSGD
encampment/MS
encapsulate/XGNDS
encapsulation/M
encase/LDSG
encasement/M
encephalitic
encephalitis/M
enchain/DGS
enchant/ELDGS
enchanter/MS
enchanting/Y
enchantment/EM
enchantments
enchantress/MS
enchilada/SM
encipher/SGD
encircle/DSGL
encirclement/M
encl
enclave/MS
enclose/GDS
enclosed/U
enclosure/SM
encode/DRSJZG
encoder/M
encomium/MS
encompass/GDS
encore/DSMG
encounter/GSMD
encourage/DSLG
encouragement/SM
encouraging/Y
encroach/GLDS
encroachment/SM
encrust/DGS
encrustation/SM
encrypt/DGS
encrypted/U
encryption
encumber/EGSD
encumbered/U
encumbrance/MS
ency
encyclical/SM
encyclopaedia
encyclopedia/MS
encyclopedic
encyclopedist/MS
encyst/LSGD
encystment/M
end/GVSJMD
endanger/SGDL
endangerment/M
endear/SGLD
endearing/Y
endearment/SM
endeavor/GSMD
endemic/MS
endemically
endgame/S
ending/M
endive/SM
endless/PY
endlessness/M
endmost
endocarditis
endocrine/MS
endocrinologist/MS
endocrinology/M
endogenous/Y
endometrial
endometriosis
endometrium
endorphin/MS
endorse/LZGDRS
endorsement/MS
endorser/M
endoscope/MS
endoscopic
endoscopy/M
endothelial
endothermic
endotracheal
endow/SDLG
endowment/MS
endpoint/SM
endue/DSG
endurable/U
endurance/M
endure/DSBG
endways
enema/SM
enemy/SM
energetic
energetically
energize/ZGDRS
energizer/M
energy/SM
enervate/GNDS
enervation/M
enfeeble/GDSL
enfeeblement/M
enfilade/DSMG
enfold/SGD
enforce/LZGDRS
enforceable/U
enforced/U
enforcement/M
enforcer/M
enfranchise/EGDSL
enfranchisement/EM
engage/EADSG
engagement/EMS
engagingly
engender/SGD
engine/SM
engineer/MDGS
engineering/M
engorge/LGDS
engorgement/M
engram/SM
engrave/ZGJDRS
engraver/M
engraving/M
engross/GLDS
engrossment/M
engulf/SLGD
engulfment/M
enhance/LZGDRS
enhancement/SM
enigma/SM
enigmatic
enigmatically
enjambment/SM
enjoin/SGD
enjoy/GBLSD
enjoyably
enjoyment/SM
enlarge/LZGDRS
enlargeable
enlargement/MS
enlarger/M
enlighten/SGLD
enlightened/U
enlightenment/M
enlist/ADGSL
enlistee/SM
enlistment/AM
enlistments
enliven/SLDG
enlivenment/M
enmesh/DSGL
enmeshment/M
enmity/SM
ennoble/DSGL
ennoblement/M
ennui/M
enormity/SM
enormous/PY
enormousness/M
enough/M
enplane/DSG
enqueue/DSG
enquirer/S
enquiringly
enrage/GDS
enrapture/DSG
enrich/DSLG
enrichment/M
enroll/DLSG
enrollment/MS
ensconce/DSG
ensemble/SM
enshrine/GLDS
enshrinement/M
enshroud/DGS
ensign/MS
ensilage/M
enslave/DSGL
enslavement/M
ensnare/DSLG
ensnarement/M
ensue/DSG
ensure/ZGDRS
ensurer/M
entail/DSGL
entailment/M
entangle/EDSLG
entanglement/EM
entanglements
entente/SM
enter/ASGD
enteral
enteric
enteritis/M
enterprise/MGS
enterprising/Y
entertain/ZGDRSL
entertainer/M
entertaining/MY
entertainment/MS
enthrall/GDSL
enthrallment/M
enthrone/GDSL
enthronement/SM
enthuse/DSG
enthusiasm/MS
enthusiast/MS
enthusiastic/U
enthusiastically
entice/GDSL
enticement/MS
enticing/Y
entire/Y
entirety/M
entitle/DSGL
entitlement/SM
entity/SM
entomb/DSGL
entombment/M
entomological
entomologist/MS
entomology/M
entourage/SM
entr'acte
entrails/M
entrained
entrance/LDSMG
entrancement/M
entrancing/Y
entrant/SM
entrap/LS
entrapment/M
entrapped
entrapping
entreat/GSD
entreating/Y
entreaty/SM
entree/MS
entrench/DSGL
entrenchment/MS
entrepreneur/SM
entrepreneurial
entrepreneurship
entropy/M
entrust/SGD
entry/ASM
entryphone/S
entryway/MS
entr�e/MS
entwine/DSG
enumerable
enumerate/DSGNX
enumeration/M
enumerator/SM
enunciate/DSGN
enunciation/M
enure/DSG
enuresis/M
envelop/SLDRZG
envelope/SM
enveloper/M
envelopment/M
envenom/SDG
enviable/U
enviably
envious/PY
enviousness/M
environment/MS
environmental/Y
environmentalism/M
environmentalist/SM
environs/M
envisage/GDS
envision/DGS
envoy/SM
envy/DSMG
envying/Y
enzymatic
enzyme/SM
eolian
eon/SM
eosinophil/S
eosinophilic
epaulet/SM
epee/MS
ephedrine/M
ephemera/M
ephemeral/Y
epic/MS
epical/Y
epicenter/MS
epicure/SM
epicurean/MS
epicycle
epidemic/SM
epidemically
epidemiological
epidemiologist/SM
epidemiology/M
epidermal
epidermic
epidermis/MS
epidural/S
epiglottis/MS
epigram/SM
epigrammatic
epigraph/M
epigraphs
epigraphy/M
epilepsy/M
epileptic/SM
epilogue/MS
epinephrine/M
epiphany/SM
episcopacy/M
episcopal
episcopate/M
episode/SM
episodic
episodically
epistemic
epistemological
epistemology
epistle/SM
epistolary
epitaph/M
epitaphs
epithelial
epithelium/M
epithet/SM
epitome/SM
epitomize/GDS
epoch/M
epochal
epochs
eponymous
epoxy/DSMG
epsilon/SM
equability/M
equable
equably
equal/SMDYG
equality/IM
equalization/M
equalize/ZGDRS
equalizer/M
equanimity/M
equate/DSGNBX
equation/M
equator/SM
equatorial
equerry/SM
equestrian/SM
equestrianism/M
equestrienne/SM
equidistant/Y
equilateral/SM
equilibrium/EM
equine/SM
equinoctial
equinox/MS
equip/AS
equipage/MS
equipment/M
equipoise/M
equipped/UA
equipping/A
equitable/I
equitably/I
equitation/M
equity/ISM
equiv
equivalence/MS
equivalency/SM
equivalent/MYS
equivocal/UY
equivocalness/M
equivocate/GNXDS
equivocation/M
equivocator/SM
er/C
era/SM
eradicable/I
eradicate/DSGN
eradication/M
eradicator/MS
erase/DRSBZG
eraser/M
erasure/SM
erbium/M
ere
erect/PSGDY
erectile
erection/SM
erectness/M
erector/MS
erelong
eremite/MS
erg/SM
ergo
ergodic
ergodicity
ergonomic/S
ergonomically
ergonomics/M
ergosterol/M
ergot/M
ermine/SM
erode/DSG
erodible
erogenous
erosion/M
erosive
erotic/S
erotica/M
erotically
eroticism/M
err/GSD
errand/SM
errant/I
errata/SM
erratic
erratically
erratum/M
erroneous/Y
error/SM
ersatz/MS
erst
erstwhile
eruct/SDG
eructation/SM
erudite/YN
erudition/M
erupt/SDGV
eruption/MS
erysipelas/M
erythrocyte/SM
erythromycin
escalate/CDSGN
escalation/CM
escalations
escalator/MS
escallop/SGMD
escalope/S
escapade/MS
escape/LMGDS
escapee/MS
escapement/SM
escapism/M
escapist/MS
escapologist/S
escapology
escargot/MS
escarole/MS
escarpment/MS
eschatological
eschatologist/SM
eschatology
eschew/SDG
escort/SMDG
escritoire/MS
escrow/SM
escudo/SM
escutcheon/SM
esophageal
esophagi
esophagus/MS
esoteric
esoterically
esp
espadrille/MS
espalier/MDSG
especial/Y
espionage/M
esplanade/MS
espousal/M
espouse/GDS
espresso/MS
esprit/M
espy/DSG
esquire/SM
essay/SMDRZG
essayer/M
essayist/SM
essence/SM
essential/IMS
essentially
establish/AESDGL
establishment/AEM
establishments
estate/SM
esteem/ESMDG
ester/SM
esthetic/S
estimable/I
estimate/MGNDSX
estimation/M
estimator/SM
estoppel
estradiol
estrange/LDSG
estrangement/MS
estrogen/MS
estrous
estrus/MS
estuary/SM
et
eta/SM
etc
etch/DRSZGJ
etcher/M
etching/M
eternal/YP
eternalness/M
eternity/SM
ethane/M
ethanol/M
ether/M
ethereal/Y
ethic/SM
ethical/UY
ethicist/SM
ethics/M
ethmoid
ethnic/SM
ethnically
ethnicity/M
ethnocentric
ethnocentrism/M
ethnographer/S
ethnographic
ethnographically
ethnography
ethnological/Y
ethnologist/SM
ethnology/M
ethological
ethologist/MS
ethology/M
ethos/M
ethyl/M
ethylene/M
etiolated
etiologic
etiological
etiology/SM
etiquette/M
etude/SM
etymological/Y
etymologist/SM
etymology/SM
eucalypti
eucalyptus/MS
eucaryote/SM
eucaryotic
euchre/DSMG
euclidean
eugenic/S
eugenically
eugenicist/MS
eugenics/M
eukaryote/SM
eukaryotic
eulogist/MS
eulogistic
eulogize/ZGDRS
eulogizer/M
eulogy/SM
eunuch/M
eunuchs
euphemism/SM
euphemistic
euphemistically
euphonious/Y
euphony/M
euphoria/M
euphoric
euphorically
eureka
euro/MS
europium/M
eutectic
euthanasia/M
euthanize/DSG
euthenics/M
eutrophic
eutrophication
evacuate/XDSGN
evacuation/M
evacuee/MS
evade/DRSZG
evader/M
evaluate/AGNVDSX
evaluation/AM
evaluator/S
evanescence/M
evanescent
evangelic
evangelical/SMY
evangelicalism/M
evangelism/M
evangelist/MS
evangelistic
evangelize/GDS
evaporate/GNDS
evaporation/M
evaporator/SM
evasion/SM
evasive/YP
evasiveness/M
eve/ASM
even/MDRYTGSJP
evenhanded/Y
evening/M
evenness/UM
evensong/M
event/SM
eventful/UY
eventfulness/M
eventide/M
eventual/Y
eventuality/SM
eventuate/GDS
ever
everglade/SM
evergreen/SM
everlasting/MYS
evermore
every
everybody/M
everyday
everyone/M
everyplace
everything/M
everywhere
evict/SDG
eviction/MS
evidence/MGDS
evident/Y
evidential/Y
evidentiality
evidentiary
evil/MRYTSP
evildoer/SM
evildoing/M
eviller
evillest
evilness/M
evince/DSG
eviscerate/DSGN
evisceration/M
evocation/MS
evocative/Y
evoke/DSG
evolution/M
evolutionary
evolutionist/SM
evolve/DSG
ewe/RSMZ
ewer/M
ex/MS
exabyte/MS
exacerbate/GNDS
exacerbation/M
exact/SBPDRYTG
exacta/S
exacting/PY
exaction/SM
exactitude/M
exactness/IM
exactor/MS
exaggerate/XDSGN
exaggerated/Y
exaggeration/M
exaggerator/MS
exajoule/S
exalt/SDG
exaltation/M
exam/MS
examination/AMS
examine/AGDS
examined/U
examiner/MS
example/MGDS
exampled/U
exasperate/DSGN
exasperated/Y
exasperating/Y
exasperation/M
exbibyte/MS
excavate/GNDSX
excavation/M
excavator/SM
exceed/GSD
exceeding/Y
excel/S
excelled
excellence/M
excellency/SM
excellent/Y
excelling
excelsior/M
except/GSD
exception/BSM
exceptionable/U
exceptional/UY
exceptionalism
excerpt/MDGS
excess/VMS
excessive/Y
exchange/DSMG
exchangeable
exchequer/SM
excise/XDSMGN
excision/M
excitability/M
excitably
excitation/M
excite/BDRSLZG
excited/Y
excitement/SM
exciter/M
exciting/Y
exciton
excl
exclaim/DGS
exclamation/SM
exclamatory
exclude/GDS
exclusion/MS
exclusionary
exclusive/PMYS
exclusiveness/M
exclusivity/M
excommunicate/GNDSX
excommunication/M
excoriate/DSGNX
excoriation/M
excrement/M
excremental
excrescence/MS
excrescent
excreta/M
excrete/XGNDS
excretion/M
excretory
excruciating/Y
exculpate/DSGN
exculpation/M
exculpatory
excursion/MS
excursionist/MS
excursive/YP
excursiveness/M
excusable/I
excusably/I
excuse/DSBMG
excused/U
exec/MS
execrable
execrably
execrate/DSGN
execration/M
execute/BXGNVDS
execution/ZMR
executioner/M
executive/SM
executor/MS
executrices
executrix/M
exegeses
exegesis/M
exegetic
exegetical
exemplar/SM
exemplary
exemplification/M
exemplify/GDSXN
exempt/SGD
exemption/SM
exercise/DRSMZG
exerciser/M
exert/SDG
exertion/MS
exeunt
exfiltrate/GNXDS
exfoliate/GNDS
exhalation/MS
exhale/DSG
exhaust/GVMDS
exhaustible/I
exhaustion/M
exhaustive/YP
exhaustiveness/M
exhibit/GMDS
exhibition/MS
exhibitionism/M
exhibitionist/MS
exhibitor/SM
exhilarate/DSGN
exhilaration/M
exhort/SDG
exhortation/MS
exhumation/MS
exhume/DSG
exigence/MS
exigency/SM
exigent
exiguity/M
exiguous
exile/DSMG
exilic
exist/SDG
existence/MS
existent
existential/Y
existentialism/M
existentialist/MS
exit/MDGS
exobiology/M
exodus/MS
exogenous
exon/MS
exonerate/GNDSX
exoneration/M
exoplanet/MS
exorbitance/M
exorbitant/Y
exorcise/DSG
exorcism/SM
exorcist/SM
exoskeleton/SM
exosphere/SM
exothermic
exotic/SM
exotica
exotically
exoticism/M
exp
expand/BGSD
expanse/XMNVS
expansible
expansion/M
expansionary
expansionism/M
expansionist/MS
expansive/YP
expansiveness/M
expat/S
expatiate/GNDS
expatiation/M
expatriate/DSMGN
expatriation/M
expect/GSD
expectancy/M
expectant/Y
expectation/SM
expectorant/SM
expectorate/DSGN
expectoration/M
expedience/IM
expediences
expediencies
expediency/IM
expedient/SMY
expedite/DRSZGNX
expediter/M
expedition/M
expeditionary
expeditious/PY
expeditiousness/M
expel/S
expelled
expelling
expend/GSBD
expendable/SM
expenditure/SM
expense/MS
expensive/IYP
expensiveness/IM
experience/IMD
experiences
experiencing
experiential
experiment/MDRSZG
experimental/Y
experimentalism
experimentalist
experimentation/M
experimenter/M
expert/SPMY
expertise/M
expertness/M
expiate/GNDS
expiation/M
expiatory
expiration/M
expire/DSG
expired/U
expiry/M
explain/ADGS
explainable/U
explained/U
explainer/S
explanation/MS
explanatory
expletive/MS
explicable/I
explicate/XGNDS
explication/M
explicit/PY
explicitness/M
explode/GDS
exploded/U
exploit/ZGVBMDRS
exploitation/M
exploitative
exploited/U
exploiter/M
exploration/MS
explorative
exploratory
explore/ZGDRS
explored/U
explorer/M
explosion/SM
explosive/SPMY
explosiveness/M
expo/MS
exponent/MS
exponential/Y
exponentiation
export/BSZGMDR
exportation/M
exporter/M
expose/DSMG
exposed/U
exposition/SM
expositor/SM
expository
expostulate/GNXDS
expostulation/M
exposure/MS
expos�
expound/ZGDRS
expounder/M
express/GVMDSY
expressed/U
expressible/I
expression/SM
expressionism/M
expressionist/SM
expressionistic
expressionless/Y
expressive/PY
expressiveness/M
expressway/SM
expropriate/GNXDS
expropriation/M
expropriator/SM
expulsion/MS
expunction
expunge/LGDS
expungement/S
expurgate/DSGNX
expurgated/U
expurgation/M
exquisite/YP
exquisiteness/M
ext
extant
extemporaneous/PY
extemporaneousness/M
extempore
extemporization/M
extemporize/GDS
extend/SZGDRB
extender/M
extendible
extensibility
extensible
extension/SM
extensional
extensive/YP
extensiveness/M
extensor/MS
extent/SM
extenuate/DSGN
extenuation/M
exterior/MS
exterminate/DSXGN
extermination/M
exterminator/MS
external/MYS
externalization/SM
externalize/DSG
extinct/GDS
extinction/MS
extinguish/ZGBDRS
extinguishable/I
extinguisher/M
extirpate/GNDS
extirpation/M
extol/S
extolled
extolling
extort/SGD
extortion/MRZ
extortionary
extortionate/Y
extortioner/M
extortionist/MS
extra/SM
extracellular
extract/MDGVS
extraction/SM
extractor/MS
extracurricular
extradite/GNBXDS
extradition/M
extrajudicial
extralegal
extramarital
extramural
extraneous/Y
extraordinaire
extraordinarily
extraordinary
extrapolate/XGNDS
extrapolation/M
extrasensory
extraterrestrial/MS
extraterritorial
extraterritoriality/M
extravagance/MS
extravagant/Y
extravaganza/MS
extravehicular
extrema
extreme/PMYTRS
extremeness/M
extremism/M
extremist/MS
extremity/SM
extremum/S
extricable/I
extricate/GNDS
extrication/M
extrinsic
extrinsically
extroversion/M
extrovert/SMD
extrude/GDS
extrusion/SM
extrusive
exuberance/M
exuberant/Y
exudation/M
exude/DSG
exult/SDG
exultant/Y
exultation/M
exurb/SM
exurban
exurbanite/SM
exurbia/M
eye/DSMG
eyeball/GMDS
eyebrow/SM
eyedropper/SM
eyeful/SM
eyeglass/MS
eyeing
eyelash/MS
eyeless
eyelet/SM
eyelid/SM
eyeliner/MS
eyeopener/MS
eyeopening
eyepiece/MS
eyesight/M
eyesore/MS
eyestrain/M
eyeteeth
eyetooth/M
eyewash/M
eyewitness/MS
f/CIAVTR
fMRI
fa/M
fab
fable/DSM
fabric/SM
fabricate/DSGNX
fabrication/M
fabricator/SM
fabulous/Y
facade/SM
face's
face/ACSDG
facecloth/M
facecloths
faceless
facelift/SM
facepalm/SDG
facet/SMDG
facetious/YP
facetiousness/M
facial/SMY
facile/Y
facilitate/GNDS
facilitation/M
facilitator/MS
facility/SM
facing/SM
facsimile/DSM
facsimileing
fact/MS
faction/SM
factional
factionalism/M
factious
factitious
facto
factoid/SM
factor's
factor/ASDG
factorial/MS
factorization
factorize/GDS
factory/SM
factotum/SM
factual/Y
faculty/SM
fad/GSMD
faddish/P
faddist/MS
faddy/P
fade/MS
fading/U
faerie/SM
faff/DGS
fag/SM
fagged
fagging
faggot/SMG
fagot/SMG
faience/M
fail/DGJS
failing/M
faille/M
failure/SM
fain/RT
faint/SMDRYTGP
fainthearted
faintness/M
fair/MRYTGJPS
fairground/MS
fairing/M
fairness/UM
fairway/SM
fairy/SM
fairyland/SM
faith/M
faithful's
faithful/UPY
faithfulness/UM
faithfuls
faithless/PY
faithlessness/M
faiths
fajita/SM
fajitas/M
fake/MZGDRS
faker/M
fakir/SM
falcon/SMRZ
falconer/M
falconry/M
fall/MNGS
fallacious/Y
fallacy/SM
fallback
fallibility/IM
fallible/P
fallibleness/M
fallibly/I
falloff/SM
fallout/M
fallow/SMDG
false/PRYT
falsehood/SM
falseness/M
falsetto/SM
falsie/SM
falsifiable/U
falsification/M
falsifier/M
falsify/DRSZGNX
falsity/SM
falter/GSJMD
faltering/Y
fame's
fame/D
familial
familiar/MYS
familiarity/UM
familiarization/M
familiarize/GDS
family/SM
famine/SM
famish/DSG
famous/IY
fan/SM
fanatic/SM
fanatical/Y
fanaticism/M
fanboy/SM
fanciable
fancier/M
fanciful/YP
fancifulness/M
fancily
fanciness/M
fancy/DRSMZTGP
fancywork/M
fandango/MS
fandom
fanfare/SM
fang/MDS
fanlight/SM
fanned
fanning
fanny/SM
fantail/MS
fantasia/SM
fantasist/S
fantasize/GDS
fantastic
fantastical/Y
fantasy/DSMG
fanzine/MS
far
farad/SM
faradize/DG
faraway
farce/SM
farcical/Y
fare/MGDS
farewell/SM
farfetched
farina/M
farinaceous
farm/MDRZGSJ
farmer/M
farmhand/SM
farmhouse/SM
farming/M
farmland/MS
farmstead/MS
farmyard/MS
faro/M
farrago/M
farragoes
farrier/MS
farrow/SMDG
farseeing
farsighted/P
farsightedness/M
fart/MDGS
farther
farthermost
farthest
farthing/SM
fascia/SM
fascicle/SM
fascinate/GNDSX
fascinating/Y
fascination/M
fascism/M
fascist/MS
fascistic
fashion/ZGBMDRS
fashionable/U
fashionably/U
fashioner/M
fashionista/MS
fast/MDRTGSP
fastback/SM
fastball/SM
fasten/UAGDS
fastener/SM
fastening/MS
fastidious/PY
fastidiousness/M
fastness/MS
fat/GSPMD
fatal/Y
fatalism/M
fatalist/SM
fatalistic
fatalistically
fatality/SM
fatback/M
fate/MS
fateful/YP
fatefulness/M
fathead/MDS
father/SGMDY
fatherhood/M
fatherland/MS
fatherless
fathom/SMDGB
fathomable/U
fathomless
fatigue/MDSG
fatigues/M
fatness/M
fatso/S
fatten/SDG
fatter
fattest
fattiness/M
fatty/RSMTP
fatuity/M
fatuous/YP
fatuousness/M
fatwa/SM
faucet/SM
fault/CSMDG
faultfinder/SM
faultfinding/M
faultily
faultiness/M
faultless/PY
faultlessness/M
faulty/PRT
faun/MS
fauna/SM
fauvism/M
fauvist/SM
faux
fav/S
fave/S
favor/ESMDG
favorable/U
favorably/U
favorite/SM
favoritism/M
fawn/MDRZGS
fawner/M
fax/GMDS
fay/TSMR
faze/GDS
fazed/U
fa�ence/M
fealty/M
fear/MDGS
fearful/YP
fearfulness/M
fearless/PY
fearlessness/M
fearmonger/MSG
fearsome
feasibility/M
feasible/IU
feasibly
feast/SMDRZG
feaster/M
feat/MS
feather/SGMD
featherbedding/M
featherbrained
featherless
featherweight/MS
feathery/TR
feature/DSMG
featureless
febrile
fecal
feces/M
feckless/PY
fecund
fecundate/GNDS
fecundation/M
fecundity/M
fed/SM
federal/SMY
federalism/M
federalist/MS
federalization/M
federalize/GDS
federate/FXDSGN
federation/FM
fedora/SM
fee/SM
feeble/RTP
feebleness/M
feebly
feed/MRZGSJ
feedback/M
feedbag/SM
feeder/M
feeding/M
feedlot/SM
feel/MRZGSJ
feeler/M
feelgood
feeling/MY
feet
feign/SDG
feigned/U
feint/SMDG
feisty/TR
feldspar/M
felicitate/GNXDS
felicitation/M
felicitous/Y
felicity/ISM
feline/SM
fell/MDRZTGS
fella/S
fellatio/M
fellow/SM
fellowman/M
fellowmen
fellowship/MS
felon/SM
felonious
felony/SM
felt/MDGS
fem
female/PSM
femaleness/M
feminine/SMY
femininity/M
feminism/M
feminist/SM
feminize/DSG
femoral
femur/SM
fen/SM
fence/DRSMZG
fencer/M
fencing/M
fend/CDRZGS
fender/CM
fenestration/M
fennel/M
fentanyl/M
feral
ferment/FCMS
fermentation/M
fermented
fermenting
fermion
fermium/M
fern/MS
ferny/RT
ferocious/PY
ferociousness/M
ferocity/M
ferret/GSMD
ferric
ferrite
ferritin
ferromagnetic
ferromagnetism
ferrous
ferrule/MS
ferry/DSMG
ferryboat/SM
ferryman/M
ferrymen
fertile/I
fertility/IM
fertilization/M
fertilize/DRSZG
fertilized/U
fertilizer/M
ferule/SM
fervency/M
fervent/Y
fervid/Y
fervor/M
fess/FKGSD
fest/MRZVS
festal
fester/GMD
festival/SM
festive/YP
festiveness/M
festivity/SM
festoon/GMDS
feta/M
fetal
fetch/DRSZG
fetcher/M
fetching/Y
fete/MGDS
fetid/P
fetidness/M
fetish/MS
fetishism/M
fetishist/SM
fetishistic
fetlock/MS
fetter's
fetter/USGD
fettle/M
fettuccine/M
fetus/MS
feud/MDGS
feudal
feudalism/M
feudalistic
fever/SMD
feverish/YP
feverishness/M
few/TPMR
fewness/M
fey
fez/M
fezzes
ff
fiance/CM
fiancee/MS
fiances
fianc�/SM
fianc�e/MS
fiasco/SM
fiascoes
fiat/MS
fib/ZSMR
fibbed
fibber/SM
fibbing
fiber/M
fiberboard/M
fiberfill/M
fiberglass/M
fibril/SM
fibrillate/GNDS
fibrillation/M
fibrin/M
fibroid
fibromyalgia/M
fibromyalgic/S
fibrosis/M
fibrous
fibula/M
fibulae
fibular
fiche/SM
fichu/SM
fickle/RPT
fickleness/M
fiction/MS
fictional/Y
fictionalization/SM
fictionalize/DSG
fictitious/Y
fictive
ficus/M
fiddle/DRSMZG
fiddler/M
fiddlesticks
fiddly/TR
fidelity/IM
fides
fidget/SGMD
fidgety
fiduciary/SM
fie
fief/MS
fiefdom/MS
field/ISMRZ
fielded
fielder/IM
fielding
fieldsman
fieldsmen
fieldwork/MRZ
fieldworker/M
fiend/SM
fiendish/Y
fierce/PRYT
fierceness/M
fieriness/M
fiery/RPT
fiesta/SM
fife/MZRS
fifer/M
fifteen/MHS
fifteenth/M
fifteenths
fifth/MY
fifths
fiftieth/M
fiftieths
fifty/SMH
fig/FSM
fight/SMRZG
fightback
fighter/IMS
fighting/IM
figment/MS
figuration/FM
figurative/Y
figure's
figure/EFGSD
figurehead/SM
figurine/MS
filament/MS
filamentous
filbert/MS
filch/DSG
file/KCSRDGZM
filename/S
filer/KCM
filesystem/SM
filet
filial
filibuster/MDRSZG
filibusterer/M
filigree/DSM
filigreeing
filing's
filings
fill's
fill/AIDGS
filled/U
filler/MS
fillet/MDGS
filling/SM
fillip/MDGS
filly/SM
film/MDGS
filminess/M
filmmaker/SM
filmography
filmstrip/MS
filmy/TPR
filo
filter/MDRBSZG
filtered/U
filterer/M
filth/M
filthily
filthiness/M
filthy/RPT
filtrate's
filtrate/IGNDS
filtration/IM
fin/SMR
finagle/DRSZG
finagler/M
final/SMY
finale/MS
finalist/SM
finality/M
finalization/M
finalize/DSG
finance's
finance/ADSG
financial/YS
financier/MS
financing/M
finch/MS
find/BJMRZGS
finder/M
finding/M
findings/M
fine's/F
fine/CAFTGDS
fineable
finely
fineness/M
finery/AM
finespun
finesse/DSMG
finger/MDGSJ
fingerboard/SM
fingering/M
fingerling/SM
fingermark/S
fingernail/SM
fingerprint/SGMD
fingertip/MS
finial/MS
finical
finickiness/M
finicky/RPT
finis/MS
finish's
finish/ADSG
finished/U
finisher/MS
finite/IY
fink/MDGS
finned
finny
fintech
fintechs
fir/ZGSJMDRH
fire/MS
firearm/SM
fireball/MS
firebomb/MDSJG
firebox/MS
firebrand/SM
firebreak/SM
firebrick/SM
firebug/SM
firecracker/SM
firedamp/M
firefight/MRSZG
firefighter/M
firefighting/M
firefly/SM
fireguard/S
firehouse/SM
firelight/ZMR
fireman/M
firemen
fireplace/SM
fireplug/MS
firepower/M
fireproof/DSG
firer/M
firescreen/S
fireside/MS
firestorm/MS
firetrap/MS
firetruck/MS
firewall/MS
firewater/M
firewood/M
firework/SM
firm/MDRYPTGS
firmament/SM
firmness/M
firmware/M
first/SMY
firstborn/SM
firsthand
firth/M
firths
fiscal/MYS
fish/MDRSZG
fishbowl/SM
fishcake/SM
fisher/M
fisherman/M
fishermen
fishery/SM
fishhook/SM
fishily
fishiness/M
fishing/M
fishmonger/MS
fishnet/SM
fishpond/MS
fishtail/DGS
fishwife/M
fishwives
fishy/TRP
fissile
fission/BM
fissionable/S
fissure/SM
fist/MS
fistfight/MS
fistful/SM
fisticuffs/M
fistula/SM
fistulous/M
fit/KAMS
fitful/YP
fitfulness/M
fitly
fitment/S
fitness/UM
fitted/UA
fitter/MS
fittest
fitting/SMY
five/MZRS
fix/ZGBJMDRS
fixate/GNVDSX
fixation/M
fixative/MS
fixed/Y
fixer/M
fixings/M
fixity/M
fixture/MS
fizz/MDSG
fizzle/DSMG
fizzy/RT
fjord/SM
fl/JDG
flab/M
flabbergast/SGD
flabbily
flabbiness/M
flabby/RPT
flaccid/Y
flaccidity/M
flack/SM
flag/MS
flagella
flagellant/S
flagellate/GNDS
flagellation/M
flagellum/M
flagged
flagging/U
flagman/M
flagmen
flagon/MS
flagpole/SM
flagrance/M
flagrancy/M
flagrant/Y
flagship/SM
flagstaff/MS
flagstone/MS
flail/SGMD
flair/SM
flak/M
flake/DSMG
flakiness/M
flaky/TRP
flamage
flambe/MS
flambeed
flambeing
flamboyance/M
flamboyancy/M
flamboyant/Y
flamb�/MD
flame/DRSJMZG
flamenco/MS
flameproof/DGS
flamethrower/SM
flamingo/MS
flammability/IM
flammable/SM
flan/MS
flaneur/SM
flange/MS
flank/SZGMDR
flanker/M
flannel/SGMD
flannelette/M
flap/MS
flapjack/MS
flapped
flapper/SM
flapping
flare/DSMG
flareup/SM
flash/ZTGMDRS
flashback/SM
flashbulb/SM
flashcard/SM
flashcube/SM
flasher/M
flashgun/SM
flashily
flashiness/M
flashing/M
flashlight/MS
flashpoint/SM
flashy/RTP
flask/SM
flat/MYPS
flatbed/SM
flatboat/SM
flatbread
flatcar/SM
flatfeet
flatfish/MS
flatfoot/SMD
flatiron/SM
flatland/M
flatlet/S
flatmate/S
flatness/M
flatted
flatten/SDG
flatter/SDRZG
flatterer/M
flattering/Y
flattery/M
flattest
flatting
flattish
flattop/SM
flatulence/M
flatulent
flatus/M
flatware/M
flatworm/SM
flaunt/MDSG
flaunting/Y
flavor/MDSGJ
flavored/U
flavorful
flavoring/M
flavorless
flavorsome
flaw/MDGS
flawless/PY
flawlessness/M
flax/MN
flay/DGS
flea/MS
fleabag/SM
fleabite/S
fleapit/S
fleck/SGMD
fledged/U
fledgling/MS
flee/S
fleece/MZGDRS
fleecer/M
fleeciness/M
fleecy/RTP
fleeing
fleet/STGMDRYP
fleetingly/M
fleetingness/M
fleetness/M
flesh/GMDSY
fleshly/TR
fleshpot/MS
fleshy/RT
flew
flex/AMS
flexed
flexibility/IM
flexible/I
flexibly/I
flexing
flexion
flexor/MS
flextime/M
flibbertigibbet/SM
flick/SZGMDR
flicker/GMD
flier/M
flight/MS
flightiness/M
flightless
flighty/PTR
flimflam/SM
flimflammed
flimflamming
flimsily
flimsiness/M
flimsy/TRP
flinch/GMDS
fling/GM
flint/SM
flintlock/SM
flinty/TR
flip/MS
flippancy/M
flippant/Y
flipped
flipper/MS
flippest
flipping
flippy/S
flirt/SGMD
flirtation/MS
flirtatious/YP
flirtatiousness/M
flirty
flit/MS
flitted
flitting
float/SMDRZG
floater/M
flock/SMDG
flocking/M
floe/MS
flog/S
flogged
flogger/SM
flogging/MS
flood/SMDRG
floodgate/MS
floodlight/MDSG
floodlit
floodplain/MS
floodwater/MS
floor/SMDG
floorboard/MS
flooring/M
floorwalker/SM
floozie/M
floozy/SM
flop/MS
flophouse/MS
flopped
floppily
floppiness/M
flopping
floppy/PRSMT
flora/SM
floral
florescence/IM
florescent/I
floret/SM
florid/PY
floridness/M
florin/SM
florist/SM
floss/MDSG
flossy/RT
flotation/SM
flotilla/MS
flotsam/M
flounce/DSMG
flouncy
flounder/MDSG
flour/SMDG
flourish/GMDS
floury
flout/SMDRZG
flouter/M
flow/MDGS
flowchart/SM
flower's
flower/CSDG
flowerbed/MS
floweriness/M
flowering/S
flowerless
flowerpot/MS
flowery/PTR
flown
flt
flu/M
flub/MS
flubbed
flubbing
fluctuate/GNDSX
fluctuation/M
flue/MS
fluency/M
fluent/Y
fluff/SMDG
fluffiness/M
fluffy/RPT
fluid/SMY
fluidity/M
fluidize/GS
fluke/SM
fluky/RT
flume/SM
flummox/DSG
flung
flunk/SMDG
flunky/SM
fluoresce/DSG
fluorescence/M
fluorescent
fluoridate/GNDS
fluoridation/M
fluoride/SM
fluorine/M
fluorite/M
fluorocarbon/MS
fluoroscope/SM
fluoroscopic
fluoxetine
flurry/GDSM
flush/MDRSTG
fluster/MDSG
flute/DSMG
fluting/M
flutist/MS
flutter/MDSG
fluttery
fluvial
flux/ADGSM
fly/ZTGBDRSM
flyaway
flyblown
flyby/M
flybys
flycatcher/MS
flyer/SM
flying/M
flyleaf/M
flyleaves
flyover/MS
flypaper/SM
flypast/S
flysheet/S
flyspeck/GMDS
flyswatter/MS
flytrap/S
flyway/SM
flyweight/SM
flywheel/MS
fo'c'sle/MS
foal/MDGS
foam/MDGS
foaminess/M
foamy/RTP
fob/SM
fobbed
fobbing
focal/Y
foci
focus's
focus/ADSG
focused/U
fodder/SM
foe/SM
fog's
fog/CS
fogbound
fogged/C
foggily
fogginess/M
fogging/C
foggy/RTP
foghorn/MS
fogy/SM
fogyish
foible/SM
foil/MDGS
foist/SDG
fol
fold's
fold/AUSGD
foldaway
folder/SM
foldout/MS
foliage/M
folic
folio/SM
folk/MS
folklore/M
folkloric
folklorist/MS
folksiness/M
folksinger/SM
folksinging/M
folksy/PTR
folktale/MS
folkway/MS
foll
follicle/MS
follow/SDRZGJ
follower/M
following/M
followup/S
folly/SM
foment/SGD
fomentation/M
fomite/S
fond/RYTP
fondant/MS
fondle/DSG
fondness/M
fondue/SM
font/MS
fontanel/MS
fontanelle/MS
foo
foobar
food/MS
foodie/SM
foodstuff/SM
fool/MDGS
foolery/SM
foolhardily
foolhardiness/M
foolhardy/TPR
foolish/YP
foolishness/M
foolproof
foolscap/M
foot/MDRZGSJ
footage/M
football/MRZGS
footballer/M
footbridge/SM
footfall/MS
foothill/MS
foothold/MS
footie
footing/M
footless
footlights/M
footling/MS
footlocker/SM
footloose
footman/M
footmen
footnote/MGDS
footpath/M
footpaths
footplate/S
footprint/SM
footrace/MS
footrest/MS
footsie/SM
footslogging
footsore
footstep/MS
footstool/SM
footwear/M
footwork/M
footy
fop/SM
foppery/M
foppish/P
foppishness/M
for/H
fora
forage/DRSMZG
forager/M
foray/SMDG
forbade
forbear/SMG
forbearance/M
forbid/S
forbidden
forbidding/YS
forbore
forborne
force/DSMG
forced/U
forceful/PY
forcefulness/M
forceps/M
forcible
forcibly
ford/MDGSB
fore/MS
forearm/GSMD
forebear/MS
forebode/GJDS
foreboding/M
forecast/MRZGS
forecaster/M
forecastle/MS
foreclose/DSG
foreclosure/MS
forecourt/SM
foredoom/DGS
forefather/MS
forefeet
forefinger/SM
forefoot/M
forefront/SM
foregather/GDS
forego/G
foregoes
foregone
foreground/GMDS
forehand/MS
forehead/MS
foreign/ZRP
foreigner/M
foreignness/M
foreknew
foreknow/GS
foreknowledge/M
foreknown
foreleg/SM
forelimb/MS
forelock/MS
foreman/M
foremast/MS
foremen
foremost
forename/MDS
forenoon/MS
forensic/MS
forensically
forensics/M
foreordain/GSD
forepart/MS
foreperson/SM
foreplay/M
forequarter/MS
forerunner/MS
foresail/MS
foresaw
foresee/RSBZ
foreseeable/U
foreseeing
foreseen/U
foreseer/M
foreshadow/GDS
foreshore/S
foreshorten/DSG
foresight/MD
foresightedness/M
foreskin/MS
forest's
forest/ACGDS
forestall/SGD
forestation/ACM
forester/MS
forestland/M
forestry/M
foretaste/DSMG
foretell/GS
forethought/M
foretold
forever/M
forevermore
forewarn/DSG
forewent
forewoman/M
forewomen
foreword/MS
forfeit/GSMD
forfeiture/SM
forgather/SDG
forgave
forge/DRSMZGVJ
forger/M
forgery/SM
forget/S
forgetful/YP
forgetfulness/M
forgettable/U
forgetting
forging/M
forgivable/U
forgive/BRSZGP
forgiven
forgiveness/M
forgiver/M
forgiving/U
forgo/RZG
forgoer/M
forgoes
forgone
forgot
forgotten/U
fork/MDGS
forkful/SM
forklift/MS
forlorn/Y
form's
form/CAIFDGS
forma/K
formal/SMY
formaldehyde/M
formalin
formalism/M
formalist/MS
formalities
formality/IM
formalization/M
formalize/GDS
format/SMV
formation/CFASM
formatted/A
formatting/M
formed/U
former/FIAM
formerly
formfitting
formic
formidable
formidably
formless/PY
formlessness/M
formula/MS
formulae
formulaic
formulate/ADSGNX
formulated/U
formulation/AM
formulator/SM
fornicate/GNDS
fornication/M
fornicator/MS
forsake/GS
forsaken
forsook
forsooth
forswear/SG
forswore
forsworn
forsythia/SM
fort/MS
forte/SM
forthcoming/M
forthright/YP
forthrightness/M
forthwith
fortieth/M
fortieths
fortification/M
fortified/U
fortifier/M
fortify/DRSNZGX
fortissimo
fortitude/M
fortnight/MYS
fortress/MS
fortuitous/YP
fortuitousness/M
fortuity/M
fortunate/UY
fortune/MS
fortuneteller/SM
fortunetelling/M
forty/SMH
forum/SM
forward/MDRYZTGSP
forwarder/M
forwardness/M
forwent
fossa
fossil/SM
fossilization/M
fossilize/GDS
foster/GSD
fought
foul/MDRYTGSP
foulard/M
foulmouthed
foulness/M
found/FSDG
foundation/SM
foundational
founded/U
founder/GMDS
foundling/SM
foundry/SM
fount/SM
fountain/SM
fountainhead/MS
four/MHS
fourfold
fourposter/SM
fourscore/M
foursome/SM
foursquare
fourteen/SMH
fourteenth/M
fourteenths
fourth/MY
fourths
fowl/MDGS
fox/GMDS
foxfire/M
foxglove/SM
foxhole/MS
foxhound/SM
foxhunt/GS
foxily
foxiness/M
foxtrot/MS
foxtrotted
foxtrotting
foxy/RTP
foyer/SM
fps
fr
fracas/MS
frack/SDRZG
fractal/SM
fraction/ISM
fractional/Y
fractious/YP
fractiousness/M
fracture/MGDS
frag/S
fragile/RT
fragility/M
fragment/GMDS
fragmentary/M
fragmentation/M
fragrance/MS
fragrant/Y
frail/RYTP
frailness/M
frailty/SM
frame/DRSMZG
framed/U
framer/M
framework/SM
franc/SM
franca
franchise's
franchise/EDSG
franchisee/SM
franchiser/SM
franchisor/SM
francium/M
francophone
frangibility/M
frangible
frank/SMDRYTGP
frankfurter/MS
frankincense/M
frankness/M
frantic
frantically
frappe/SM
frapp�/M
frat/MS
fraternal/Y
fraternity/FSM
fraternization/M
fraternize/ZGDRS
fraternizer/M
fratricidal
fratricide/MS
fraud's
fraud/S
fraudster/S
fraudulence/M
fraudulent/Y
fraught
fray's
fray/CDGS
frazzle/MGDS
freak/SMDG
freakish/YP
freakishness/M
freakout/MS
freaky/RT
freckle/DSMG
freckly
free/YTDRS
freebase/MGDS
freebee/SM
freebie/SM
freebooter/SM
freeborn
freedman/M
freedmen
freedom/SM
freegan/S
freehand
freehold/ZMRS
freeholder/M
freeing
freelance/DRSMZG
freelancer/M
freeload/SDRZG
freeloader/M
freeman/M
freemasonry
freemen
freephone
freesia/S
freestanding
freestone/SM
freestyle/SM
freethinker/SM
freethinking/M
freeware/M
freeway/MS
freewheel/DGS
freewill
freezable
freeze's
freeze/UAGS
freezer/MS
freezing's
freight/MDRZGS
freighter/M
french
frenemy/S
frenetic
frenetically
frenzied/Y
frenzy/DSM
freq
frequencies
frequency/IM
frequent/DRYSZTG
frequented/U
frequenter/M
fresco/M
frescoes
fresh/PNRYXZT
freshen/ZGDR
freshener/M
freshet/MS
freshman/M
freshmen
freshness/M
freshwater/M
fret/MS
fretful/YP
fretfulness/M
fretsaw/MS
fretted
fretting
fretwork/M
friable
friar/SM
friary/SM
fricassee/DSM
fricasseeing
fricative/SM
friction/SM
frictional
fridge/SM
friedcake/MS
friend's
friend/UGSDY
friendless
friendlies
friendliness/UM
friendly's
friendly/UPTR
friendship/MS
frieze/SM
frig/S
frigate/MS
frigged
frigging
fright/SXGMDN
frighten/DG
frightening/Y
frightful/PY
frightfulness/M
frigid/YP
frigidity/M
frigidness/M
frill/SMD
frilly/TR
fringe's
fringe/IDSG
frippery/SM
frisk/SDG
friskily
friskiness/M
frisky/TRP
frisson/S
fritter/MDSG
fritz/M
frivolity/SM
frivolous/PY
frivolousness/M
frizz/MDSYG
frizzle/MGDS
frizzy/TR
fro
frock's
frock/CUS
frog/MS
frogging/S
frogman/M
frogmarch/GDS
frogmen
frogspawn
frolic/SM
frolicked
frolicker/SM
frolicking
frolicsome
from
frond/SM
front's
front/FSDG
frontage/MS
frontal/Y
frontbench/ZRS
frontier/MS
frontiersman/M
frontiersmen
frontierswoman
frontierswomen
frontispiece/MS
frontward/S
frosh/M
frost's
frost/CSDG
frostbit
frostbite/MGS
frostbitten
frostily
frostiness/M
frosting/SM
frosty/TPR
froth/MDG
frothiness/M
froths
frothy/TPR
froufrou/M
frown/SMDG
frowsy/TR
frowzily
frowziness/M
frowzy/TPR
froze/AU
frozen/UA
fructify/DSG
fructose/M
frugal/Y
frugality/M
fruit/SMDG
fruitcake/MS
fruiterer/S
fruitful/YP
fruitfulness/M
fruitiness/M
fruition/M
fruitless/PY
fruitlessness/M
fruity/TPR
frump/SM
frumpish
frumpy/TR
frustrate/GNXDS
frustrating/Y
frustration/M
frustum/MS
fry/GDSM
fryer/SM
ft
ftp/ZGS
fuchsia/MS
fuck/SMGDRZ!
fucker/M!
fuckhead/SM!
fuddle/DSMG
fudge/DSMG
fuehrer/MS
fuel's
fuel/ADGS
fug
fugacious/PY
fugal
fuggy
fugitive/MS
fugue/SM
fuhrer/SM
fulcrum/MS
fulfill/LDGS
fulfilled/U
fulfilling/U
fulfillment/M
full/MDRZTGSP
fullback/MS
fuller/M
fullness/M
fully
fulminate/DSXGN
fulmination/M
fulsome/PY
fulsomeness/M
fum/S
fumble/DRSMZG
fumbler/M
fumbling/Y
fume/MGDS
fumigant/MS
fumigate/GNDS
fumigation/M
fumigator/SM
fumy/RT
fun/M
function/MDGS
functional/Y
functionalism
functionalist/S
functionality/S
functionary/SM
functor
fund/AMDRZGS
fundamental/SMY
fundamentalism/M
fundamentalist/SM
funded/U
funding/M
fundraiser/MS
fundraising
funeral/MS
funerary
funereal/Y
funfair/S
fungal
fungi
fungible/MS
fungicidal
fungicide/MS
fungoid
fungous
fungus/M
funicular/SM
funk/MDGS
funkiness/M
funky/PRT
funnel/MDGS
funner
funnest
funnily
funniness/M
funny/TPRSM
funnyman/M
funnymen
fur/SM
furbelow/M
furbish/ADSG
furious/Y
furl's
furl/UDGS
furlong/SM
furlough/GMD
furloughs
furn
furnace/SM
furnish/ADSG
furnished/U
furnishings/M
furniture/M
furor/SM
furosemide
furred
furrier/M
furriness/M
furring/M
furrow/MDSG
furry/ZTRP
further/SGD
furtherance/M
furthermore
furthermost
furthest
furtive/YP
furtiveness/M
fury/SM
furze/M
fuse's/A
fuse/CAIFGDS
fusee/SM
fuselage/SM
fusibility/M
fusible
fusileer/SM
fusilier/SM
fusillade/MS
fusion/IFKSM
fuss/MDSG
fussbudget/MS
fussily
fussiness/M
fusspot/SM
fussy/TRP
fustian/M
fustiness/M
fusty/TRP
fut
futile/Y
futility/M
futon/SM
future/MS
futurism/M
futurist/MS
futuristic
futurity/SM
futurologist/MS
futurology/M
futz/DSG
fuzz/MDSG
fuzzball/S
fuzzily
fuzziness/M
fuzzy/PTR
fwd
fwy
f�te/SM
g/SNXVB
gab/SM
gabardine/SM
gabbed
gabbiness/M
gabbing
gabble/DSMG
gabby/RTP
gaberdine/SM
gabfest/MS
gable/DSM
gad/S
gadabout/SM
gadded
gadder/SM
gadding
gadfly/SM
gadget/SM
gadgetry/M
gadolinium/M
gaff/MDRZGS
gaffe/SM
gaffer/M
gag/SM
gaga
gagged
gagging
gaggle/SM
gaiety/M
gaily
gain's
gain/ADGS
gainer/SM
gainful/Y
gainsaid
gainsay/ZGRS
gainsayer/M
gait/MRZS
gaiter/M
gal/SM
gala/MS
galactic
galaxy/SM
gale's
gale/AS
galena/M
gall/MDGS
gallant/SMY
gallantry/M
gallbladder/MS
galleon/SM
galleria/MS
gallery/SM
galley/SM
gallimaufry/SM
gallium/M
gallivant/GSD
gallon/SM
gallop/SMDG
gallows/M
gallstone/MS
galoot/SM
galore
galosh/MS
galumph/DG
galumphs
galvanic
galvanism/M
galvanization/M
galvanize/DSG
galvanometer/MS
gambit/SM
gamble/DRSMZG
gambler/M
gambling/M
gambol/SMDG
game/MYTGDRSP
gamecock/MS
gamekeeper/MS
gameness/M
gamesmanship/M
gamester/MS
gamete/SM
gametic
gamey
gamify/DSNG
gamin/SM
gamine/SM
gaminess/M
gaming/M
gamma/SM
gammon/M
gammy
gamut/SM
gamy/RTP
gander/SM
gang/MDGS
gangbusters/M
gangland/M
ganglia
gangling
ganglion/M
ganglionic
gangplank/SM
gangrene/DSMG
gangrenous
gangsta/S
gangster/SM
gangway/MS
ganja
gannet/SM
gantlet/MS
gantry/SM
gap/GSMD
gape/MS
gar/SLM
garage/DSMG
garb/MDGS
garbage/M
garbageman
garbanzo/SM
garble/DSG
garcon/SM
garden/SZGMDR
gardener/M
gardenia/MS
gardening/M
garfish/MS
gargantuan
gargle/DSMG
gargoyle/SM
garish/PY
garishness/M
garland/MDGS
garlic/M
garlicky
garment/MS
garner/SGD
garnet/SM
garnish/GLMDS
garnishee/DSM
garnisheeing
garnishment/SM
garotte/MGDS
garret/SM
garrison/MDSG
garrote/MZGDRS
garroter/M
garrotte/MGDS
garrulity/M
garrulous/PY
garrulousness/M
garter/SM
gar�on/SM
gas's
gas/CS
gasbag/SM
gaseous
gash/MDSG
gasholder/S
gasket/SM
gaslight/MDGS
gasman
gasmen
gasohol/M
gasoline/M
gasometer/S
gasp/MDGS
gassed/C
gasses
gassing/C
gassy/RT
gastric
gastritis/M
gastroenteritis/M
gastroenterologist/M
gastroenterology
gastrointestinal
gastronome/S
gastronomic
gastronomical/Y
gastronomy/M
gastropod/SM
gasworks/M
gate/MGDS
gateau
gateaux
gatecrash/DRSZG
gatecrasher/M
gatehouse/SM
gatekeeper/MS
gatekeeping/M
gatepost/MS
gateway/MS
gather/SJZGMDR
gatherer/M
gathering/M
gator/SM
gauche/RPYT
gaucheness/M
gaucherie/M
gaucho/SM
gaudily
gaudiness/M
gaudy/RPT
gauge/DSMG
gaunt/RPT
gauntlet/MS
gauntness/M
gauze/M
gauziness/M
gauzy/RPT
gave
gavel/SM
gavotte/MS
gawd
gawk/DGS
gawkily
gawkiness/M
gawky/RPT
gawp/DGS
gay/TSPMR
gayness/M
gaze/MZGDRS
gazebo/SM
gazelle/MS
gazer/M
gazette/MGDS
gazetteer/MS
gazillion/HS
gazpacho/M
gazump/DGS
gear/MDGS
gearbox/MS
gearing/M
gearshift/MS
gearwheel/SM
gecko/SM
geddit
gee/DS
geeing
geek/MS
geeky/RT
geese
geezer/MS
geisha/M
gel/SM
gelatin/M
gelatinous
gelcap/M
geld/DJGS
gelding/M
gelid
gelignite/M
gelled
gelling
gem/SM
gemmology/M
gemological
gemologist/MS
gemology/M
gemstone/MS
gendarme/MS
gender/MDSG
gene/MS
genealogical/Y
genealogist/MS
genealogy/SM
genera
general/SMY
generalissimo/MS
generalist/MS
generality/SM
generalization/MS
generalize/GDS
generalship/M
generate/ACDSGNV
generation's/A
generation/CSM
generational
generator/SM
generic/SM
generically
generosity/SM
generous/PY
generousness/M
genes/S
genesis/M
genetic/S
genetically
geneticist/MS
genetics/M
genial/FY
geniality/FM
geniculate
genie/SM
genii
genital/FY
genitalia/M
genitals/M
genitive/MS
genitourinary
genius/MS
genned
genning
genocidal
genocide/MS
genome/MS
genomic/SM
genre/SM
gent/AMS
genteel/YP
genteelness/M
gentian/SM
gentile/SM
gentility/M
gentle/TGDRSP
gentlefolk/MS
gentlefolks/M
gentleman/MY
gentlemanly/U
gentlemen
gentleness/M
gentlewoman/M
gentlewomen
gently
gentrification/M
gentrify/DSGN
gentry/SM
genuflect/DGS
genuflection/MS
genuine/PY
genuineness/M
genus/M
geocache/DSG
geocentric
geocentrically
geocentricism
geocentrism
geochemistry/M
geode/SM
geodesic/SM
geodesy/M
geodetic
geoengineering
geog
geographer/SM
geographic
geographical/Y
geography/SM
geologic
geological/Y
geologist/MS
geology/SM
geom
geomagnetic
geomagnetism/M
geometer
geometric
geometrical/Y
geometry/SM
geophysical
geophysicist/SM
geophysics/M
geopolitical/Y
geopolitics/M
geostationary
geosynchronous
geosyncline/MS
geothermal
geothermic
geranium/MS
gerbil/MS
geriatric/S
geriatrician/S
geriatrics/M
germ/MS
germane
germanium/M
germicidal
germicide/MS
germinal/M
germinate/GNDS
germination/M
gerontocracy
gerontological
gerontologist/MS
gerontology/M
gerrymander/GMDS
gerrymandering/M
gerund/MS
gestalt/S
gestapo/MS
gestate/GNDS
gestation/M
gestational
gesticulate/DSGNX
gesticulation/M
gestural
gesture/MGDS
gesundheit
get/S
getaway/SM
getting
getup/M
gewgaw/SM
geyser/SM
ghastliness/M
ghastly/TPR
ghat/MS
ghee
gherkin/MS
ghetto/SM
ghettoize/GDS
ghost/SMDYG
ghostliness/M
ghostly/RTP
ghostwrite/ZGRS
ghostwriter/M
ghostwritten
ghostwrote
ghoul/SM
ghoulish/YP
ghoulishness/M
giant/SM
giantess/MS
gibber/GDS
gibberish/M
gibbet/GMDS
gibbon/MS
gibbous
gibe/MGDS
gibibyte/MS
giblet/SM
giddily
giddiness/M
giddy/RTP
gift/MDGS
gig/SM
gigabit/SM
gigabyte/MS
gigagram/S
gigahertz/M
gigajoule/SM
gigameter/S
gigantic
gigantically
gigapascal/S
gigapixel/MS
gigawatt/SM
gigged
gigging
giggle/DRSMZG
giggler/M
giggly/RT
gigolo/SM
gild/MDRZGS
gilder/M
gilding/M
gill/MS
gillie/S
gillion/S
gilt/MS
gimbals/M
gimcrack/SM
gimcrackery/M
gimlet/GSMD
gimme/SM
gimmick/MS
gimmickry/M
gimmicky
gimp/MDGS
gimpy
gin/SM
ginger/GSMDY
gingerbread/M
gingersnap/SM
gingery
gingham/M
gingivitis/M
ginkgo/SM
ginkgoes
ginned
ginning
ginormous
ginseng/M
giraffe/MS
gird/DRZGS
girder/M
girdle/DSMG
girl/MS
girlfriend/MS
girlhood/SM
girlie
girlish/YP
girlishness/M
girly/S
giro/S
girt/MDGS
girth/M
girths
gist/M
git/S
gite/S
give/ZGJRS
giveaway/MS
giveback/MS
given/SM
giver/M
gizmo/SM
gizzard/MS
glace/S
glaceed
glaceing
glacial/Y
glaciate/XGNDS
glaciation/M
glacier/MS
glac�/SDG
glad/MYSP
gladden/GDS
gladder
gladdest
glade/SM
gladiator/SM
gladiatorial
gladiola/SM
gladioli
gladiolus/M
gladness/M
gladsome
glam
glamor/SGMD
glamorization/M
glamorize/DSG
glamorous/Y
glamour/GMDS
glamping
glance/DSMG
gland/SM
glandes
glandular
glans/M
glare/DSMG
glaring/Y
glasnost/M
glass/MDSG
glassblower/MS
glassblowing/M
glassful/SM
glasshouse/S
glassily
glassiness/M
glassware/M
glassy/RTP
glaucoma/M
glaze/DSMG
glazier/SM
glazing/M
gleam/SMDGJ
glean/SDRZGJ
gleaner/M
gleanings/M
glee/M
gleeful/YP
gleefulness/M
glen/MS
glenohumeral
glenoid
glib/YP
glibber
glibbest
glibness/M
glide/DRSMZG
glider/M
gliding/M
glimmer/MDGJS
glimmering/M
glimpse/MGDS
glint/SMDG
glissandi
glissando/M
glisten/MDSG
glister/DSG
glitch/GMDS
glitter/MDSG
glitterati
glittery
glitz/M
glitzy/TR
gloaming/SM
gloat/SMDG
gloating/Y
glob/MDGS
global/Y
globalism/M
globalist/MS
globalization/M
globalize/GDS
globe/SM
globetrotter/MS
globetrotting
globular
globule/MS
globulin/M
glockenspiel/SM
glom/DGS
gloom/M
gloomily
gloominess/M
gloomy/TRP
glop/M
gloppy
glorification/M
glorify/GDSN
glorious/IY
glory/DSMG
gloss/MDSG
glossary/SM
glossily
glossiness/M
glossolalia/M
glossy/PTRSM
glottal
glottis/MS
glove/DSMG
glow/MDRZGS
glower/GMD
glowing/Y
glowworm/MS
glucagon
glucose/M
glue/MGDS
glued/U
gluey
gluier
gluiest
glum/YP
glummer
glummest
glumness/M
gluon/S
glut/MNS
gluten/M
glutenous
glutinous/Y
glutted
glutting
glutton/MS
gluttonous/Y
gluttony/M
glycerin/M
glycerine/M
glycerol/M
glycogen/M
glycol
glyph
gm
gnarl/SMDG
gnarly/TR
gnash/MDSG
gnat/MS
gnaw/DGS
gneiss/M
gnocchi
gnome/SM
gnomic
gnomish
gnu/SM
go/JMRHZG
goad/MDGS
goal/MS
goalie/SM
goalkeeper/MS
goalkeeping/M
goalless
goalmouth
goalmouths
goalpost/MS
goalscorer/S
goaltender/MS
goat/MS
goatee/SM
goatherd/MS
goatskin/MS
gob/SM
gobbed
gobbet/SM
gobbing
gobble/DRSMZG
gobbledygook/M
gobbler/M
goblet/SM
goblin/SM
gobsmacked
gobstopper/S
gochujang
god/SM
godawful
godchild/M
godchildren/M
goddam/D
goddammit
goddamn/D
goddaughter/MS
goddess/MS
godfather/SM
godforsaken
godhead/M
godhood/M
godless/PY
godlessness/M
godlike
godliness/UM
godly/URTP
godmother/SM
godparent/SM
godsend/SM
godson/SM
godspeed
goer/M
goes
gofer/SM
goggle/DSMG
goggles/M
going/M
goiter/SM
gold/MNS
goldbrick/ZGSMDR
goldbricker/M
golden/TR
goldenrod/M
goldfield/S
goldfinch/MS
goldfish/MS
goldmine/SM
goldsmith/M
goldsmiths
golem
golf/MDRZGS
golfer/M
golliwog/S
golly/SM
gonad/SM
gonadal
gondola/MS
gondolier/SM
gone/ZR
goner/M
gong/MDGS
gonk/S
gonna
gonorrhea/M
gonorrheal
gonzo
goo/M
goober/SM
good/MYSP
goodby/M
goodbye/MS
goodbys
goodhearted
goodie/M
goodish
goodly/TR
goodness/M
goodnight
goods/M
goodwill/M
goody/SM
gooey
goof/MDGS
goofball/SM
goofiness/M
goofy/RPT
google/DSMG
googly/S
gooier
gooiest
gook/MS
goon/MS
goop/M
goose/DSMG
gooseberry/SM
goosebumps/M
gooseflesh/M
goosestep/S
goosestepped
goosestepping
gopher/SM
gore/MGDS
gorge's
gorge/EDSG
gorgeous/YP
gorgeousness/M
gorgon/SM
gorilla/MS
gorily
goriness/M
gormandize/DRSZG
gormandizer/M
gormless
gorp/MS
gorse/M
gory/RTP
gosh
goshawk/MS
gosling/SM
gospel/MS
gossamer/M
gossip/MDRZGS
gossiper/M
gossipy
got
gotcha/S
gothic/P
gothically
goths
gotta
gotten
gouache/S
gouge/DRSMZG
gouger/M
goulash/MS
gourd/SM
gourde/MS
gourmand/SM
gourmet/SM
gout/M
gouty/TR
gov
govern/DGSBL
governable/U
governance/M
governed/U
governess/MS
government/MS
governmental
governor/SM
governorship/M
govt
gown/MDGS
gr
grab/MS
grabbed
grabber/MS
grabbing
grabby/TR
grace/EDSMG
graceful/EPY
gracefulness/EM
graceless/PY
gracelessness/M
gracious/UY
graciousness/M
grackle/MS
grad/MRZSB
gradate/XGNDS
gradation/CM
grade's
grade/CADSG
graded/U
grader/M
gradient/MS
gradual/PY
gradualism/M
gradualness/M
graduate/XMGNDS
graduation/M
graffiti
graffito/M
graft/SMDRZG
grafter/M
graham/S
grail
grain/ISMD
graininess/M
grainy/PTR
gram/KMS
grammar/MS
grammarian/SM
grammatical/UY
grammatically/K
gramophone/MS
grampus/MS
gran/S
granary/SM
grand/SMRYPT
grandad/MS
grandam/MS
grandaunt/MS
grandchild/M
grandchildren/M
granddad/SM
granddaddy/SM
granddaughter/SM
grande
grandee/MS
grandeur/M
grandfather/GMDYS
grandiloquence/M
grandiloquent
grandiose/Y
grandiosity/M
grandma/MS
grandmother/MYS
grandnephew/MS
grandness/M
grandniece/MS
grandpa/MS
grandparent/MS
grandson/MS
grandstand/SGMD
granduncle/SM
grange/SM
granite/M
granitic
grannie/M
granny/SM
granola/M
grant/SMDRZG
grantee/MS
granter/M
grantor/MS
grantsmanship/M
granular
granularity/M
granulate/GNDS
granulation/M
granule/MS
grape/SM
grapefruit/MS
grapeshot/M
grapevine/SM
graph/MDG
graphic/MS
graphical/Y
graphite/M
graphologist/MS
graphology/M
graphs
grapnel/MS
grapple/MGDS
grasp/SMDBG
grass/MDSG
grasshopper/MS
grassland/MS
grassroots
grassy/TR
grate/DRSMZGJ
grateful/UYP
gratefulness/UM
grater/M
gratification/M
gratify/GNXDS
gratifying/Y
gratin/S
grating/MY
gratis
gratitude/IM
gratuitous/YP
gratuitousness/M
gratuity/SM
gravamen/MS
grave/DRSMYTGP
gravedigger/SM
gravel/SGMDY
graven
graveness/M
graveside/MS
gravestone/SM
graveyard/MS
gravid
gravimeter/MS
gravitas
gravitate/GNDS
gravitation/M
gravitational
gravity/M
gravy/SM
gray/MDRTGSP
graybeard/SM
grayish
grayness/M
grayscale
graze/DRSMZG
grazer/M
grease/DRSMZG
greasepaint/M
greasily
greasiness/M
greasy/PTR
great/SMRYPT
greatcoat/SM
greathearted
greatness/M
grebe/SM
greed/M
greedily
greediness/M
greedy/PTR
green/GPSMDRYT
greenback/MS
greenbelt/MS
greenery/M
greenfield
greenfly/S
greengage/MS
greengrocer/SM
greenhorn/SM
greenhouse/SM
greenish
greenmail/M
greenness/M
greenroom/SM
greenstone
greensward/M
greenwood/M
greet/ZGJSDR
greeter/M
greeting/M
gregarious/PY
gregariousness/M
gremlin/SM
grenade/SM
grenadier/MS
grenadine/M
grep/S
grepped
grepping
grew/A
grey/MDRTGS
greybeard's
greybeards
greyhound/SM
greyness's
gribble/S
grid/MS
griddle/SM
griddlecake/SM
gridiron/SM
gridlock/SMD
grief/SM
grievance/MS
grieve/ZGDRS
griever/M
grievous/PY
grievousness/M
griffin/SM
griffon/SM
grill/SGMDJ
grille/MS
grim/DYPG
grimace/DSMG
grime/SM
griminess/M
grimmer
grimmest
grimness/M
grimy/TRP
grin/MS
grind/SZGMRJ
grinder/M
grindstone/MS
gringo/MS
grinned
grinning
grip/MDRSZG
gripe/SM
griper/M
grippe/MZGDR
gripper/M
grisliness/M
grisly/RTP
grist/MY
gristle/M
gristmill/MS
grit/MS
grits/M
gritted
gritter/SM
grittiness/M
gritting
gritty/RTP
grizzle/DSG
grizzly/TRSM
groan/SGMD
groat/SM
grocer/MS
grocery/SM
grog/M
groggily
grogginess/M
groggy/PRT
groin/SM
grok/S
grokked
grokking
grommet/SM
groom/SZGMDR
groomer/M
grooming/M
groomsman/M
groomsmen
groove/MGDS
groovy/RT
grope/DRSMZG
groper/M
grosbeak/MS
grosgrain/M
gross/PTGMDRSY
grossness/M
grotesque/SPMY
grotesqueness/M
grotto/M
grottoes
grotty/TR
grouch/GMDS
grouchily
grouchiness/M
grouchy/RTP
ground/ZGMDRJS
groundbreaking/MS
groundcloth
groundcloths
grounder/M
groundhog/MS
grounding/M
groundless/Y
groundnut/MS
groundsheet/S
groundskeeper/S
groundsman
groundsmen
groundswell/SM
groundwater/M
groundwork/M
group/JSZGMDR
grouper/M
groupie/MS
grouping/M
groupware/M
grouse/MZGDRS
grouser/M
grout/SGMD
grove/SM
grovel/ZGDRS
groveler/M
grovelled
grovelling
grow/AHSG
grower/MS
growing/I
growl/SZGMDR
growler/M
grown/AI
grownup/MS
growth/AM
growths
grub/MS
grubbed
grubber/MS
grubbily
grubbiness/M
grubbing
grubby/TRP
grubstake/M
grudge/MGDS
grudging/Y
grue/S
gruel/GJM
grueling/Y
gruesome/RYTP
gruesomeness/M
gruff/TPRY
gruffness/M
grumble/DRSMZGJ
grumbler/M
grump/SM
grumpily
grumpiness/M
grumpy/PRT
grunge/MS
grungy/RT
grunion/SM
grunt/SGMD
gt
guac
guacamole/M
guanine/M
guano/M
guarani/MS
guaranies
guarantee/MDS
guaranteeing
guarantor/MS
guaranty/GDSM
guard/SZGMDR
guarded/Y
guarder/M
guardhouse/SM
guardian/SM
guardianship/M
guardrail/SM
guardroom/SM
guardsman/M
guardsmen
guava/SM
gubernatorial
guerilla/SM
guerrilla/SM
guess/ZGBMDRS
guesser/M
guesstimate/DSMG
guesswork/M
guest/SGMD
guestbook/SM
guesthouse/S
guestroom/S
guff/M
guffaw/MDGS
guidance/M
guide/DRSMZG
guidebook/SM
guided/U
guideline/SM
guidepost/SM
guider/M
guild/SZMR
guilder/M
guildhall/MS
guile/M
guileful
guileless/YP
guilelessness/M
guillemot/S
guillotine/DSMG
guilt/M
guiltily
guiltiness/M
guiltless
guilty/PRT
guinea/MS
guise/ESM
guitar/MS
guitarist/SM
gulag/SM
gulch/MS
gulden/MS
gulf/MS
gull/MDSG
gullet/MS
gullibility/M
gullible
gully/SM
gulp/MDRSZG
gulper/M
gum/SM
gumball/S
gumbo/SM
gumboil/SM
gumboot/S
gumdrop/SM
gummed
gumming
gummy/TR
gumption/M
gumshoe/MDS
gumshoeing
gun/SM
gunboat/SM
gunfight/MRZS
gunfighter/M
gunfire/M
gunge
gungy
gunk/M
gunky
gunman/M
gunmen
gunmetal/M
gunned
gunnel/MS
gunner/MS
gunnery/M
gunning
gunny/M
gunnysack/MS
gunpoint/M
gunpowder/M
gunrunner/MS
gunrunning/M
gunship/MS
gunshot/MS
gunslinger/SM
gunsmith/M
gunsmiths
gunsmoke
gunwale/MS
guppy/SM
gurgle/MGDS
gurney/MS
guru/MS
gush/MDRSZG
gusher/M
gushing/Y
gushy/TR
gusset/MSDG
gussy/DSG
gust/EMDSG
gustatory
gustily
gusto/M
gusty/RT
gut/SM
gutless/P
gutlessness/M
gutsy/RT
gutted
gutter/SMDG
guttersnipe/MS
gutting
guttural/MS
gutty/RT
guv/S
guvnor/S
guy/SGMD
guzzle/DRSZG
guzzler/M
gym/SM
gymkhana/MS
gymnasium/MS
gymnast/MS
gymnastic/S
gymnastically
gymnastics/M
gymnosperm/SM
gymslip/S
gynecologic
gynecological
gynecologist/SM
gynecology/M
gyp/SM
gypped
gypper/SM
gypping
gypster/SM
gypsum/M
gypsy/SM
gyrate/DSGNX
gyration/M
gyrator/SM
gyrfalcon/MS
gyro/MS
gyroscope/MS
gyroscopic
gyve/MGDS
h'm
h/NRSXZGVJ
ha/SH
habanero/MS
habeas
haberdasher/SM
haberdashery/SM
habiliment/SM
habit's
habit/ISB
habitability/M
habitat/SM
habitation/MS
habitual/YP
habitualness/M
habituate/GNDS
habituation/M
habitue/SM
habitu�/SM
hacienda/SM
hack/MDRZGS
hacker/M
hacking/M
hackish
hackle/MS
hackney/SMDG
hacksaw/SM
hacktivist/MS
hackwork/M
had
haddock/SM
hadith/S
hadn't
hadron
hadronic
hadst
hafnium/M
haft/MS
hag/SM
haggard/YP
haggardness/M
haggis/MS
haggish
haggle/MZGDRS
haggler/M
hagiographer/SM
hagiography/SM
hah
hahnium/M
haiku/M
hail/MDGS
hailstone/MS
hailstorm/MS
hair/MDS
hairball/MS
hairband/S
hairbreadth/M
hairbreadths
hairbrush/MS
haircloth/M
haircut/SM
hairdo/MS
hairdresser/SM
hairdressing/M
hairdrier/MS
hairdryer/MS
hairgrip/S
hairiness/M
hairless
hairlike
hairline/SM
hairnet/SM
hairpiece/MS
hairpin/SM
hairsbreadth/M
hairsbreadths
hairsplitter/SM
hairsplitting/M
hairspray/S
hairspring/MS
hairstyle/MGS
hairstylist/SM
hairy/TRP
haj
hajj/M
hajjes
hajji/SM
hake/MS
halal/M
halberd/SM
halcyon
hale/ITGDRS
half/M
halfback/SM
halfhearted/PY
halfheartedness/M
halfpence
halfpenny/SM
halftime/MS
halftone/MS
halfway
halfwit/SM
halibut/SM
halite/M
halitosis/M
hall/MS
hallelujah/M
hallelujahs
hallmark/GMDS
halloo/MDSG
hallow/DSG
hallowed/U
hallucinate/GNXDS
hallucination/M
hallucinatory
hallucinogen/SM
hallucinogenic/SM
hallway/SM
halo/MDGS
halogen/SM
halon
halt/MDRZGS
halter/GMD
halterneck/S
halting/Y
halve/DSG
halyard/MS
ham/SM
hamburg/SZMR
hamburger/M
hamlet/MS
hammed
hammer/MDRSJZG
hammerer/M
hammerhead/SM
hammerlock/SM
hammertoe/MS
hamming
hammock/SM
hammy/TR
hamper/GMDS
hampered/U
hamster/MS
hamstring/GSM
hamstrung
hand's
hand/UDGS
handbag/SM
handball/MS
handbarrow/SM
handbill/MS
handbook/MS
handbrake/S
handcar/SM
handcart/MS
handclasp/MS
handcraft/SMDG
handcuff/MDGS
handed/P
handful/SM
handgun/SM
handheld/MS
handhold/MS
handicap/MS
handicapped
handicapper/MS
handicapping
handicraft/MS
handily
handiness/M
handiwork/M
handkerchief/MS
handle/MZGDRS
handlebar/MS
handler/M
handmade
handmaid/XMNS
handmaiden/M
handout/SM
handover/S
handpick/GDS
handrail/MS
handsaw/SM
handset/SM
handshake/JMGS
handsome/PYTR
handsomeness/M
handspring/MS
handstand/SM
handwashing
handwork/M
handwoven
handwrite/GS
handwriting/M
handwritten
handwrote
handy/UTR
handyman/M
handymen
hang/MDRJZGS
hangar/MS
hangdog
hanger/M
hanging/M
hangman/M
hangmen
hangnail/MS
hangout/SM
hangover/MS
hangup/MS
hank/MRZS
hanker/GJD
hankering/M
hankie/M
hanky/SM
hansom/MS
hap/MY
haphazard/YP
haphazardness/M
hapless/YP
haplessness/M
haploid/MS
happen/SDGJ
happening/M
happenstance/SM
happily/U
happiness/UM
happy/URTP
haptic/S
haptical/Y
harangue/MGDS
harass/LZGDRS
harasser/M
harassment/M
harbinger/SM
harbor/GMDS
harbormaster/S
hard/NRYXTP
hardback/MS
hardball/M
hardboard/M
hardbound
hardcore
hardcover/SM
harden/ZGDR
hardened/U
hardener/M
hardhat/MS
hardheaded/PY
hardheadedness/M
hardhearted/PY
hardheartedness/M
hardihood/M
hardily
hardiness/M
hardliner/MS
hardness/M
hardscrabble
hardship/SM
hardstand/SM
hardtack/M
hardtop/SM
hardware/M
hardwired
hardwood/SM
hardworking
hardy/PTR
hare/MGDS
harebell/MS
harebrained
harelip/SM
harelipped
harem/SM
haricot/S
harissa
hark/DGS
harlequin/SM
harlot/SM
harlotry/M
harm/MDGS
harmed/U
harmful/YP
harmfulness/M
harmless/PY
harmlessness/M
harmonic/SM
harmonica/MS
harmonically
harmonies
harmonious/PY
harmoniousness/M
harmonium/MS
harmonization/M
harmonize/ZGDRS
harmonizer/M
harmony/EM
harness's
harness/UDSG
harp/MDGS
harpist/SM
harpoon/ZGSMDR
harpooner/M
harpsichord/MS
harpsichordist/SM
harpy/SM
harridan/MS
harrier/M
harrow/SMDG
harrumph/GD
harrumphs
harry/DRSZG
harsh/RYTP
harshness/M
hart/MS
harvest/SMDRZG
harvested/U
harvester/M
hash/AMDSG
hashish/M
hashtag/SM
hasn't
hasp/MS
hassle/DSMG
hassock/SM
hast/DNXG
haste/SM
hasten/DG
hastily
hastiness/M
hasty/RTP
hat/ZGSMDR
hatband/S
hatbox/MS
hatch/MDSG
hatchback/MS
hatcheck/SM
hatched/U
hatchery/SM
hatchet/SM
hatching/M
hatchling
hatchway/SM
hate/MS
hateful/PY
hatefulness/M
hatemonger/MS
hater/M
hatpin/S
hatred/SM
hatstand/S
hatted
hatter/SM
hatting
hauberk/SM
haughtily
haughtiness/M
haughty/PRT
haul/MDRZGS
haulage/M
hauler/M
haulier/S
haunch/MS
haunt/SMDRZG
haunter/M
haunting/Y
hauteur/M
have/MGS
haven't
haven/SM
haversack/SM
havoc/M
haw/GSMD
hawk/MDRZGS
hawker/M
hawkish/P
hawkishness/M
hawser/SM
hawthorn/MS
hay/GSMD
haycock/SM
hayloft/SM
haymaker/S
haymaking
haymow/SM
hayrick/MS
hayride/MS
hayseed/MS
haystack/SM
haywire
hazard/SMDG
hazardous/Y
haze/MZGJDRS
hazel/SM
hazelnut/MS
hazer/M
hazily
haziness/M
hazing/M
hazmat
hazy/RTP
hdqrs
he'd
he'll
he/M
head/MDRZGJS
headache/MS
headband/MS
headbanger/S
headbanging
headboard/SM
headbutt/DSG
headcase/S
headcheese
headcount/S
headdress/MS
header/M
headfirst
headgear/M
headhunt/DRSZG
headhunter/M
headhunting/M
headily
headiness/M
heading/M
headlamp/MS
headland/MS
headless
headlight/MS
headline/MZGDRS
headliner/M
headlock/MS
headlong
headman/M
headmaster/SM
headmen
headmistress/MS
headphone/MS
headpiece/MS
headpin/SM
headquarter/SDG
headquarters/M
headrest/MS
headroom/M
headscarf
headscarves
headset/SM
headship/SM
headshrinker/SM
headsman/M
headsmen
headspace
headstall/SM
headstand/SM
headstone/SM
headstrong
headteacher/S
headwaiter/SM
headwaters/M
headway/M
headwind/SM
headword/SM
heady/RTP
heal/DRHZGS
healed/U
healer/M
health/M
healthcare
healthful/PY
healthfulness/M
healthily/U
healthiness/UM
healthy/UTRP
heap/MDGS
hear/AHGJS
heard/AU
hearer/SM
hearing/AM
hearken/SGD
hearsay/M
hearse's
hearse/AS
heart/SM
heartache/MS
heartbeat/MS
heartbreak/SMG
heartbroken
heartburn/M
hearten/ESGD
heartfelt
hearth/M
hearthrug/S
hearths
hearthstone/SM
heartily
heartiness/M
heartland/MS
heartless/PY
heartlessness/M
heartrending/Y
heartsick/P
heartsickness/M
heartstrings/M
heartthrob/MS
heartwarming
heartwood/M
hearty/RSMPT
heat's
heat/ADGS
heated/U
heatedly
heater/SM
heath/MNRX
heathen/M
heathendom/M
heathenish
heathenism/M
heather/M
heaths
heating/M
heatproof
heatstroke/M
heatwave/S
heave/DRSMZG
heaven/SMY
heavenly/TR
heavens/M
heavenward/S
heaver/M
heavily
heaviness/M
heavy/RSMTP
heavyhearted
heavyset
heavyweight/MS
heck/M
heckle/DRSMZG
heckler/M
heckling/M
hectare/SM
hectic
hectically
hectogram/SM
hectometer/MS
hector/SMDG
hedge/DRSMZG
hedgehog/MS
hedgehop/S
hedgehopped
hedgehopping
hedger/M
hedgerow/SM
hedonism/M
hedonist/MS
hedonistic
heed/MDGS
heeded/U
heedful/Y
heedless/PY
heedlessness/M
heehaw/SMDG
heel/MDGS
heelless
heft/MDGS
heftily
heftiness/M
hefty/PRT
hegemonic
hegemony/M
hegira/SM
heifer/SM
height/XSMN
heighten/DG
heinous/YP
heinousness/M
heir/MS
heiress/MS
heirloom/SM
heist/SMDG
held
helical
helices
helicity/S
helicopter/SGMD
heliocentric
heliocentrically
heliocentricism
heliocentrism
heliotrope/SM
helipad/S
heliport/MS
helium/M
helix/M
hell/M
hellbent
hellcat/MS
hellebore/M
hellfire
hellhole/MS
hellion/MS
hellish/YP
hellishness/M
hello/SM
helluva
helm/MS
helmet/SMD
helmsman/M
helmsmen
helot/SM
help/MDRZGSJ
helper/M
helpful/UY
helpfulness/M
helping/M
helpless/PY
helplessness/M
helpline/SM
helpmate/SM
helve/SM
hem/SM
hematite/M
hematologic
hematological
hematologist/MS
hematology/M
heme/M
hemiplegia
hemisphere/SM
hemispheric
hemispherical
hemline/SM
hemlock/SM
hemmed
hemmer/SM
hemming
hemoglobin/M
hemophilia/M
hemophiliac/MS
hemorrhage/MGDS
hemorrhagic
hemorrhoid/MS
hemostat/MS
hemp/MN
hemstitch/MDSG
hen/M
hence
henceforth
henceforward
henchman/M
henchmen
henna/SMDG
henpeck/GSD
hentai
hep
heparin/M
hepatic
hepatitis/M
hepatocyte/S
hepper
heppest
heptagon/MS
heptagonal
heptathlon/SM
herald/SMDG
heralded/U
heraldic
heraldry/M
herb/MS
herbaceous
herbage/M
herbal/S
herbalist/MS
herbicidal
herbicide/MS
herbivore/SM
herbivorous
herculean
herd/MDRZGS
herder/M
herdsman/M
herdsmen
here/M
hereabout/S
hereafter/SM
hereby
hereditary
heredity/M
herein
hereinafter
hereof
hereon
heresy/SM
heretic/SM
heretical
hereto
heretofore
hereunder
hereunto
hereupon
herewith
heritability
heritable/I
heritage/MS
hermaphrodite/SM
hermaphroditic
hermetic
hermetical/Y
hermit/SM
hermitage/MS
hermitian
hernia/SM
hernial
herniate/GNDS
herniation/M
hero/M
heroes
heroic/S
heroically
heroics/M
heroin/SM
heroine/SM
heroism/M
heron/SM
herpes/M
herpetologist/SM
herpetology/M
herring/MS
herringbone/M
herself
hertz/M
hesitance/M
hesitancy/M
hesitant/Y
hesitate/DSGNX
hesitating/UY
hesitation/M
hessian
hetero/SM
heterodox
heterodoxy/M
heterogeneity/M
heterogeneous/Y
heterosexual/MYS
heterosexuality/M
heuristic/MS
heuristically
heuristics/M
hew/ZGSDR
hewer/M
hex/GMDS
hexadecimal/S
hexagon/MS
hexagonal
hexagram/SM
hexameter/SM
hexane/SM
hey
heyday/SM
hf
hgt
hgwy
hi/SD
hiatus/MS
hibachi/MS
hibernate/GNDS
hibernation/M
hibernator/MS
hibiscus/MS
hiccough/DG
hiccoughs
hiccup/GSMD
hick/MS
hickey/SM
hickory/SM
hid
hidden
hide/MZGJDRS
hideaway/SM
hidebound
hideous/YP
hideousness/M
hideout/MS
hider/M
hiding/M
hie/S
hieing
hierarchic
hierarchical/Y
hierarchy/SM
hieroglyph/M
hieroglyphic/MS
hieroglyphs
high/MRYZTP
highball/SM
highborn
highboy/MS
highbrow/SM
highchair/MS
highfalutin
highhanded/PY
highhandedness/M
highland/MRZS
highlander/M
highlight/SMDRZG
highlighter/M
highness/M
highroad/MS
highs
hightail/DSG
highway/MS
highwayman/M
highwaymen
hijab/SM
hijack/SJZGMDR
hijacker/M
hijacking/M
hike/MZGDRS
hiker/M
hiking/M
hilarious/PY
hilariousness/M
hilarity/M
hill/MS
hillbilly/SM
hilliness/M
hillock/MS
hillside/SM
hilltop/MS
hilly/PRT
hilt/MS
him/S
himself
hind/MRZS
hinder/GD
hindered/U
hindmost
hindquarter/MS
hindrance/SM
hindsight/M
hinge's
hinge/UDSG
hint/MDRZGS
hinter/M
hinterland/SM
hip/SPM
hipbath
hipbaths
hipbone/MS
hiphuggers
hipness/M
hipped
hipper
hippest
hippie/M
hipping
hippo/SM
hippocampus
hippodrome/SM
hippopotami
hippopotamus/MS
hippy/SM
hipster/MS
hiragana
hire's
hire/AGDS
hireling/MS
hirsute/P
hirsuteness/M
hiss/MDSG
hist
histamine/MS
histogram/MS
histologist/SM
histology/M
histopathology
historian/MS
historic
historical/Y
historicity/M
historiographer/MS
historiography/M
history/SM
histrionic/S
histrionically
histrionics/M
hit/SM
hitch's
hitch/UDSG
hitcher/MS
hitchhike/DRSMZG
hitchhiker/M
hither
hitherto
hitter/SM
hitting
hive/MGDS
hivemind/SM
hiya
hm
hmm
ho/SMDRYZ
hoagie/MS
hoard/SZGMDRJ
hoarder/M
hoarding/M
hoarfrost/M
hoariness/M
hoarse/YTRP
hoarseness/M
hoary/TRP
hoax/MDRSZG
hoaxer/M
hob/SM
hobbit/S
hobble/MZGDRS
hobbler/M
hobby/SM
hobbyhorse/MS
hobbyist/SM
hobgoblin/MS
hobnail/SGMD
hobnob/S
hobnobbed
hobnobbing
hobo/MS
hoboes
hoc
hock/MDSG
hockey/M
hockshop/MS
hod/SM
hodgepodge/SM
hoe/SM
hoecake/SM
hoedown/SM
hoeing
hoer/M
hog/SM
hogan/SM
hogback/SM
hogged
hogging
hoggish/Y
hogshead/SM
hogtie/DS
hogtying
hogwash/M
hoick/SGD
hoist/SGMD
hoke/GDS
hokey
hokier
hokiest
hokum/M
hold/MRJSZG
holdall/S
holdem
holder/M
holding/M
holdout/SM
holdover/SM
holdup/MS
hole/MGDS
holey
holiday/SMDG
holidaymaker/S
holiness/UM
holism
holistic
holistically
holler/MDGS
hollow/MDRYPSTG
hollowness/M
holly/SM
hollyhock/MS
holmium/M
holocaust/SM
hologram/MS
holograph/M
holographic
holographs
holography/M
hols
holster/SMDG
holy/URPT
homage/MS
hombre/MS
homburg/SM
home/MYZGDRS
homebody/SM
homeboy/SM
homecoming/SM
homegrown
homeland/MS
homeless/MP
homelessness/M
homelike
homeliness/M
homely/PRT
homemade
homemaker/SM
homemaking/M
homeopath/M
homeopathic
homeopaths
homeopathy/M
homeostasis/M
homeostatic
homeowner/MS
homepage/MS
homer/GMD
homeroom/MS
homeschooling/M
homesick/P
homesickness/M
homespun/M
homestead/SMDRZG
homesteader/M
homestretch/MS
hometown/MS
homeward/S
homework/MRZG
homewrecker/SM
homey/SMP
homeyness/M
homicidal
homicide/MS
homie/RSMT
homiletic
homily/SM
hominess/M
hominid/SM
hominoid/S
hominy/M
homo/MS
homoerotic
homogeneity/M
homogeneous/Y
homogenization/M
homogenize/DSG
homograph/M
homographs
homologous
homology
homonym/SM
homophobia/M
homophobic
homophone/MS
homosexual/SM
homosexuality/M
hon/SZTGMDR
honcho/MS
hone/MS
honer/M
honest/EYT
honester
honesty/EM
honey/SGMD
honeybee/SM
honeycomb/MDSG
honeydew/SM
honeylocust/M
honeymoon/ZGMDRS
honeymooner/M
honeypot/S
honeysuckle/SM
honk/MDRSZG
honker/M
honkie/M
honky/SM
honor/ESGMDB
honorableness/M
honorably/E
honorarily
honorarium/MS
honorary
honoree/SM
honorer/SM
honorific/MS
hooch/M
hood/MDSG
hoodie/MS
hoodlum/SM
hoodoo/MDSG
hoodwink/DGS
hooey/M
hoof/MDRSZG
hook's
hook/UDSG
hookah/M
hookahs
hooker/MS
hookup/MS
hookworm/MS
hooky/M
hooligan/MS
hooliganism/M
hoop/MDSG
hoopla/M
hooray/MS
hoosegow/SM
hoot/MDRSZG
hootch/M
hootenanny/SM
hooter/M
hoover/DSG
hooves
hop/SGMD
hope/MS
hopeful/PSMY
hopefulness/M
hopeless/YP
hopelessness/M
hophead/S
hopped
hopper/MS
hopping
hopscotch/MDSG
hora/MS
horde/DSMG
horehound/SM
horizon/SM
horizontal/SMY
hormesis
hormonal
hormone/SM
horn/MDS
hornbeam/S
hornblende/M
hornet/MS
hornless
hornlike
hornpipe/MS
horny/TR
horologic
horological
horologist/MS
horology/M
horoscope/SM
horrendous/Y
horrible/P
horribleness/M
horribly
horrid/Y
horrific
horrifically
horrify/DSG
horrifying/Y
horror/MS
horse's
horse/UDSG
horseback/M
horsebox/S
horseflesh/M
horsefly/SM
horsehair/M
horsehide/M
horselaugh/M
horselaughs
horseless
horseman/M
horsemanship/M
horsemen
horseplay/M
horsepower/M
horseradish/MS
horseshit/!
horseshoe/DSM
horseshoeing
horsetail/SM
horsetrading
horsewhip/SM
horsewhipped
horsewhipping
horsewoman/M
horsewomen
horsey
horsy/TR
hortatory
horticultural
horticulturalist/S
horticulture/M
horticulturist/MS
hosanna/SM
hose/MGDS
hosepipe/S
hosier/MS
hosiery/M
hosp
hospholipase
hospice/MS
hospitable/I
hospitably/I
hospital/SM
hospitality/M
hospitalization/SM
hospitalize/DSG
host/MDSG
hostage/MS
hostel/ZGMDRS
hosteler/M
hostelry/SM
hostess/MDSG
hostile/MYS
hostilities/M
hostility/SM
hostler/MS
hot/SYP
hotbed/MS
hotblooded
hotbox/MS
hotcake/SM
hotdog/MS
hotdogged
hotdogging
hotel/SM
hotelier/MS
hotfoot/MDGS
hothead/DSM
hotheaded/YP
hotheadedness/M
hothouse/SM
hotkey/S
hotline/MS
hotlink/S
hotness/M
hotplate/SM
hotpot/S
hots/M
hotshot/MS
hotted
hotter
hottest
hottie/S
hotting
hound/SGMD
hour/MYS
hourglass/MS
houri/SM
house's
house/ADSG
houseboat/SM
housebound
houseboy/SM
housebreak/RSZG
housebreaker/M
housebreaking/M
housebroke
housebroken
houseclean/DSG
housecleaning/M
housecoat/SM
housefly/SM
houseful/SM
household/SMRZ
householder/M
househusband/SM
housekeeper/MS
housekeeping/M
houselights/M
housemaid/SM
houseman/M
housemaster/S
housemate/S
housemen
housemistress/S
housemother/SM
houseparent/SM
houseplant/MS
houseproud
houseroom
housetop/SM
housewares/M
housewarming/SM
housewife/MY
housewives
housework/M
housing/MS
hove
hovel/SM
hover/SGD
hoverboard/MS
hovercraft/MS
how'd
how're
how/SM
howbeit
howdah/M
howdahs
howdy
however
howitzer/SM
howl/MDRSZG
howler/M
howsoever
howto/SM
hoyden/MS
hoydenish
hp
hr/S
ht
huarache/SM
hub/SM
hubbub/SM
hubby/SM
hubcap/SM
hubris/M
huckleberry/SM
huckster/SGMD
hucksterism/M
huddle/DSMG
hue/DSM
huff/MDSG
huffily
huffiness/M
huffy/PRT
hug/STMR
huge/YP
hugeness/M
hugged
hugging
huh
hula/MS
hulk/MSG
hull/MDRSZG
hullabaloo/SM
huller/M
hum/SM
human/SMRYTP
humane/PY
humaneness/M
humanism/M
humanist/SM
humanistic
humanitarian/MS
humanitarianism/M
humanities/M
humanity/ISM
humanization/CM
humanize/CDSG
humanizer/SM
humankind/M
humanness/M
humanoid/SM
humble/DRSZTGJP
humbleness/M
humbler/M
humbly
humbug/SM
humbugged
humbugging
humdinger/MS
humdrum/M
humeral
humeri
humerus/M
humid/Y
humidification/M
humidifier/CM
humidify/CZGDRS
humidity/M
humidor/SM
humiliate/DSGNX
humiliating/Y
humiliation/M
humility/M
hummed
hummer/SM
humming
hummingbird/SM
hummock/SM
hummocky
hummus/M
humongous
humor/SMDG
humoresque
humorist/MS
humorless/YP
humorlessness/M
humorous/PY
humorousness/M
hump/MDSG
humpback/MDS
humph/DG
humphs
humus/M
hunch/MDSG
hunchback/SMD
hundred/SMH
hundredfold
hundredth/M
hundredths
hundredweight/SM
hung
hunger/SMDG
hungover
hungrily
hungriness/M
hungry/PRT
hunk/MRSZ
hunker/DG
hunky/RT
hunt/MDRSZG
hunter/M
hunting/M
huntress/MS
huntsman/M
huntsmen
hurdle/DRSMZG
hurdler/M
hurdling/M
hurl/MDRSZG
hurler/M
hurling/M
hurrah/GMD
hurrahs
hurray
hurricane/MS
hurried/UY
hurry/DSMG
hurt/MSG
hurtful/YP
hurtfulness/M
hurtle/DSG
husband/GMDS
husbandman/M
husbandmen
husbandry/M
hush/MDSG
husk/MDRSZG
husker/M
huskily
huskiness/M
husky/PRSMT
hussar/SM
hussy/SM
hustings/M
hustle/DRSMZG
hustler/M
hut/SM
hutch/MS
huzza/GSMD
huzzah/MDG
huzzahs
hwy
hyacinth/M
hyacinths
hybrid/SM
hybridism/M
hybridization/M
hybridize/DSG
hydra/SM
hydrangea/SM
hydrant/MS
hydrate's
hydrate/CGNDS
hydration/CM
hydraulic/S
hydraulically
hydraulics/M
hydride
hydro/M
hydrocarbon/MS
hydrocephalus/M
hydrochloride
hydrocortisone
hydrodynamic/S
hydrodynamics/M
hydroelectric
hydroelectrically
hydroelectricity/M
hydrofoil/MS
hydrogen/M
hydrogenate/CGDS
hydrogenation/M
hydrogenous
hydrologist/MS
hydrology/M
hydrolyses
hydrolysis/M
hydrolyze/DSG
hydrometer/SM
hydrometry/M
hydrophilic
hydrophobia/M
hydrophobic
hydrophone/SM
hydroplane/GDSM
hydroponic/S
hydroponically
hydroponics/M
hydrosphere/M
hydrotherapy/M
hydrothermal
hydrous
hydroxide/SM
hyena/SM
hygiene/M
hygienic/U
hygienically
hygienist/MS
hygrometer/SM
hying
hymen/SM
hymeneal
hymn/MDSG
hymnal/MS
hymnbook/SM
hype/MGDRS
hyperactive
hyperactivity/M
hyperbola/SM
hyperbole/M
hyperbolic
hypercritical/Y
hypercube
hyperglycemia/M
hyperinflation
hyperlink/GSMD
hypermarket/S
hypermedia/M
hyperparathyroidism
hyperplane
hypersensitive/P
hypersensitiveness/M
hypersensitivity/SM
hyperspace/S
hypertension/M
hypertensive/SM
hypertext/M
hyperthyroid/M
hyperthyroidism/M
hypertrophy/DSMG
hyperventilate/GNDS
hyperventilation/M
hypervisor/MS
hyphen/MDSG
hyphenate/XDSMGN
hyphenation/M
hypnoses
hypnosis/M
hypnotherapist/S
hypnotherapy/M
hypnotic/SM
hypnotically
hypnotism/M
hypnotist/MS
hypnotize/GDS
hypo/MS
hypoallergenic
hypochondria/M
hypochondriac/SM
hypocrisy/SM
hypocrite/MS
hypocritical/Y
hypodermic/MS
hypoglycemia/M
hypoglycemic/SM
hypotenuse/MS
hypothalami
hypothalamus/M
hypothermia/M
hypotheses
hypothesis/M
hypothesize/DSG
hypothetical/YS
hypothyroid/M
hypothyroidism/M
hyssop/M
hysterectomy/SM
hysteresis
hysteria/M
hysteric/SM
hysterical/Y
hysterics/M
i/US
iOS/M
iPad/M
iPhone/M
iPod/MS
iTunes/M
iamb/MS
iambi
iambic/SM
iambus/MS
iatrogenesis
iatrogenic
ibex/MS
ibid
ibidem
ibis/MS
ibuprofen/M
ice's
ice/CDSG
iceberg/SM
iceboat/SM
icebound
icebox/MS
icebreaker/SM
icecap/SM
iceman/M
icemen
ichthyologist/MS
ichthyology/M
icicle/SM
icily
iciness/M
icing/SM
icky/RT
icon/MS
iconic
iconoclasm/M
iconoclast/SM
iconoclastic
iconography/M
ictus/M
icy/TPR
id/SMY
idea/MS
ideal/SMY
idealism/M
idealist/SM
idealistic
idealistically
idealization/MS
idealize/DSG
idem
idempotent
identical/Y
identifiable/U
identification/M
identified/U
identify/ZGNDRSX
identikit/S
identity/SM
ideogram/SM
ideograph/M
ideographs
ideological/Y
ideologist/SM
ideologue/MS
ideology/SM
ides/M
idiocy/SM
idiom/SM
idiomatic/U
idiomatically
idiopathic
idiosyncrasy/SM
idiosyncratic
idiosyncratically
idiot/SM
idiotic
idiotically
idle/MZTGDRSP
idleness/M
idler/M
idol/MS
idolater/SM
idolator/SM
idolatress/MS
idolatrous
idolatry/M
idolization/M
idolize/GDS
idyll/SM
idyllic
idyllically
if/SM
iffiness/M
iffy/RTP
iftar/S
igloo/SM
igneous
ignite/ZGNBXDRS
igniter/M
ignition/M
ignoble
ignobly
ignominious/Y
ignominy/SM
ignoramus/MS
ignorance/M
ignorant/Y
ignore/GDS
iguana/MS
ii
iii
ilea
ileitis/M
ileum/M
ilia
ilium/M
ilk/SM
ill/SMP
illegal/MYS
illegality/SM
illegibility/M
illegible
illegibly
illegitimacy/M
illegitimate/Y
illiberal/Y
illiberality/M
illicit/YP
illicitness/M
illimitable
illiteracy/M
illiterate/MYS
illness/MS
illogical/Y
illogicality/M
illuminate/GNXDS
illuminating/Y
illumination/M
illumine/DSBG
illus/V
illusion/EMS
illusionist/SM
illusory
illustrate/GNVXDS
illustration/M
illustrative/Y
illustrator/SM
illustrious/PY
illustriousness/M
image/DSMG
imagery/M
imaginable/U
imaginably/U
imaginal
imaginary
imagination/MS
imaginative/UY
imagine/DSBJG
imago/M
imagoes
imam/MS
imbalance/DSM
imbecile/MS
imbecilic
imbecility/SM
imbibe/ZGDRS
imbiber/M
imbrication/M
imbroglio/SM
imbue/DSG
imitable/I
imitate/DSGNVX
imitation/M
imitative/PY
imitativeness/M
imitator/SM
immaculate/PY
immaculateness/M
immanence/M
immanency/M
immanent/Y
immaterial/YP
immateriality/M
immaterialness/M
immature/Y
immaturity/M
immeasurable
immeasurably
immediacies/M
immediacy/SM
immediate/PY
immediateness/M
immemorial/Y
immense/Y
immensity/SM
immerse/XDSGNV
immersible
immersion/M
immigrant/SM
immigrate/DSGN
immigration/M
imminence/M
imminent/Y
immobile
immobility/M
immobilization/M
immobilize/ZGDRS
immoderate/Y
immodest/Y
immodesty/M
immolate/DSGN
immolation/M
immoral/Y
immorality/SM
immortal/MYS
immortality/M
immortalize/DSG
immovability/M
immovable
immovably
immune
immunity/M
immunization/SM
immunize/GDS
immunodeficiency/M
immunodeficient
immunoglobulin/S
immunologic
immunological
immunologist/MS
immunology/M
immure/DSG
immutability/M
immutable
immutably
imp/SMR
impact/SMDG
impactful
impaction
impair/SDGL
impaired/U
impairment/MS
impala/SM
impale/DSGL
impalement/M
impalpable
impalpably
impanel/SDG
impart/SDG
impartial/Y
impartiality/M
impassably
impasse/BSMV
impassibility/M
impassible
impassibly
impassioned
impassive/YP
impassiveness/M
impassivity/M
impasto/M
impatience/MS
impatiens/M
impatient/Y
impeach/ZGBLDRS
impeachable/U
impeacher/M
impeachment/SM
impeccability/M
impeccable
impeccably
impecunious/PY
impecuniousness/M
impedance/M
impede/DSG
impeded/U
impediment/SM
impedimenta/M
impel/S
impelled
impeller/MS
impelling
impend/SDG
impenetrability/M
impenetrable
impenetrably
impenitence/M
impenitent/Y
imperative/SMY
imperceptibility/M
imperceptible
imperceptibly
imperceptive
imperf
imperfect/SMYP
imperfection/MS
imperfectness/M
imperial/MYS
imperialism/M
imperialist/SM
imperialistic
imperialistically
imperil/GSLD
imperilment/M
imperious/PY
imperiousness/M
imperishable
imperishably
impermanence/M
impermanent/Y
impermeability/M
impermeable
impermeably
impermissible
impersonal/Y
impersonate/GNXDS
impersonation/M
impersonator/SM
impertinence/MS
impertinent/Y
imperturbability/M
imperturbable
imperturbably
impervious/Y
impetigo/M
impetuosity/M
impetuous/YP
impetuousness/M
impetus/MS
impiety/SM
impinge/LDSG
impingement/M
impious/PY
impiousness/M
impish/YP
impishness/M
implacability/M
implacable
implacably
implant/BSGMD
implantation/M
implausibility/SM
implausible
implausibly
implement/ZGBMDRS
implementable/U
implementation/SM
implemented/U
implicate/DSG
implication/M
implicit/PY
implicitness/M
implode/DSG
implore/DSG
imploring/Y
implosion/MS
implosive
imply/XDSGN
impolite/YP
impoliteness/MS
impolitic
imponderable/MS
import/ZGBSMDR
importance/M
important/Y
importation/MS
importer/M
importunate/Y
importune/GDS
importunity/M
impose/ADSG
imposer/MS
imposing/U
imposingly
imposition/MS
impossibility/SM
impossible/S
impossibly
impost/ZSMR
imposter/M
impostor/SM
imposture/MS
impotence/M
impotency/M
impotent/Y
impound/DGS
impoverish/DSLG
impoverishment/M
impracticability
impracticable
impracticably
impractical/Y
impracticality/M
imprecate/DSXGN
imprecation/M
imprecise/PYN
impreciseness/M
imprecision/M
impregnability/M
impregnable
impregnably
impregnate/GNDS
impregnation/M
impresario/SM
impress/MDSGV
impressed/U
impressibility/M
impressible
impression/BSM
impressionability/M
impressionism/M
impressionist/SM
impressionistic
impressive/PY
impressiveness/M
imprimatur/SM
imprint/MDRZGS
imprinter/M
imprison/SDLG
imprisonment/SM
improbability/SM
improbable
improbably
impromptu/SM
improper/Y
impropriety/SM
improve/GBDSL
improved/U
improvement/MS
improvidence/M
improvident/Y
improvisation/SM
improvisational
improvise/ZGDRS
improviser/M
improvisor/SM
imprudence/M
imprudent/Y
impudence/M
impudent/Y
impugn/ZGSDR
impugner/M
impulse/MGNVDS
impulsion/M
impulsive/PY
impulsiveness/M
impulsivity
impunity/M
impure/RYT
impurity/SM
imputation/SM
impute/BDSG
in/ASM
inaccuracy/S
inaction/M
inadequacy/S
inadvertence/M
inadvertent/Y
inalienability/M
inalienably
inamorata/SM
inane/RYT
inanimate/PY
inanimateness/M
inanity/SM
inappropriate/Y
inarticulate/Y
inasmuch
inaudible
inaugural/SM
inaugurate/XGNDS
inauguration/M
inboard/MS
inbound
inbox/MS
inbreed/S
inc/TGD
incalculably
incandescence/M
incandescent/Y
incantation/SM
incapacitate/GNDS
incarcerate/XDSGN
incarceration/M
incarnadine/DSG
incarnate/AXGNDS
incarnation/AM
incendiary/SM
incense/MGDS
incentive's
incentive/ES
incentivize/EDSG
inception/SM
incessant/Y
incest/M
incestuous/PY
incestuousness/M
inch/MDSG
inchoate
inchworm/SM
incidence/SM
incident/SM
incidental/MYS
incinerate/DSGN
incineration/M
incinerator/MS
incipience/M
incipient/Y
incise/XGNVDS
incision/M
incisive/PY
incisiveness/M
incisor/MS
incitement/MS
inciter/MS
incl
inclement
inclination/EM
inclinations
incline's
incline/EGDS
include/GDS
inclusion/MS
inclusive/YP
inclusiveness/M
incognito/MS
incombustible
incommode/GD
incommodious
incommunicado
incompetent/MS
incomplete/Y
inconceivability/M
incongruous/PY
incongruousness/M
inconsolably
inconstant/Y
incontestability/M
incontestably
incontinent
incontrovertibly
inconvenience/GD
incorporate/ADSGN
incorporated/U
incorporation/AM
incorporator/S
incorporeal
incorrect/Y
incorrigible/P
incorrigibly
increasing/Y
increment/SMDG
incremental/Y
incrementalism
incrementalist/SM
incriminate/GNDS
incrimination/M
incriminatory
incrustation/SM
incubate/GNDS
incubation/M
incubator/SM
incubus/MS
inculcate/DSGN
inculcation/M
inculpate/DSG
incumbency/SM
incumbent/SM
incunabula
incunabulum/M
incur/SB
incurable/MS
incurably
incurious
incurred
incurring
incursion/MS
ind
indebted/P
indebtedness/M
indeed
indefatigable
indefatigably
indefeasible
indefeasibly
indefinably
indelible
indelibly
indemnification/M
indemnify/GDSXN
indemnity/SM
indentation/MS
indention/M
indenture/DG
indescribably
indestructibly
indeterminably
indeterminacy/M
indeterminate/Y
index/ZGMDRS
indexation/SM
indexer/M
indicate/XDSGNV
indication/M
indicative/SMY
indicator/MS
indict/GDSBL
indictment/SM
indie/S
indigence/M
indigenous
indigent/SMY
indignant/Y
indignation/M
indigo/M
indirect/Y
indiscipline
indiscreet/Y
indiscretion/S
indiscriminate/Y
indispensability/M
indispensable/MS
indispensably
indissolubility
indissolubly
indistinguishably
indite/GDS
indium/M
individual/MYS
individualism/M
individualist/MS
individualistic
individualistically
individuality/M
individualization/M
individualize/GDS
individuate/DSGN
individuation/M
indivisibly
indoctrinate/GNDS
indoctrination/M
indolence/M
indolent/Y
indomitable
indomitably
indubitable
indubitably
induce/DRSZGL
inducement/SM
inducer/M
induct/DGV
inductance/M
inductee/SM
induction/MS
inductive/Y
inductor/SM
indue/DG
indulge/DSG
indulgence/SM
indulgent/Y
industrial/Y
industrialism/M
industrialist/SM
industrialization/M
industrialize/DSG
industrious/YP
industriousness/M
industry/SM
indwell/SG
inebriate/MGNDS
inebriation/M
inedible
ineffability/M
ineffable
ineffably
inelastic
ineligible/MS
ineligibly
ineluctable
ineluctably
inept/YP
ineptitude/M
ineptness/M
inequality/S
inert/YP
inertia/M
inertial
inertness/M
inescapable
inescapably
inestimably
inevitability/M
inevitable/M
inevitably
inexact/Y
inexhaustibly
inexorability
inexorable
inexorably
inexpedient
inexpert/Y
inexpiable
inexplicably
inexpressibly
inexpressive
inextricably
inf/ZT
infallible
infamy/SM
infancy/M
infant/MS
infanticide/MS
infantile
infantry/SM
infantryman/M
infantrymen
infarct/MS
infarction/M
infatuate/DSXGN
infatuation/M
infect/AESDG
infected/U
infection/ASM
infectious/PY
infectiousness/M
infelicitous
inference/SM
inferential
inferior/MS
inferiority/M
infernal/Y
inferno/MS
inferred
inferring
infest/GDS
infestation/MS
infidel/MS
infidelity/S
infiltrator/SM
infinite/MV
infinitesimal/SMY
infinitival
infinitive/MS
infinitude/M
infinitum
infinity/SM
infirm
infirmary/SM
infirmity/SM
infix
inflame/DSG
inflammable
inflammation/SM
inflammatory
inflatable/SM
inflate/ADSG
inflation/EM
inflationary
inflect/SDG
inflection/MS
inflectional
inflict/SDGV
infliction/M
inflight
inflow/SM
influence/MGDS
influenced/U
influential/Y
influenza/M
influx/MS
info/M
infomercial/SM
inform/Z
informal/Y
informant/SM
informatics
information/EM
informational
informative/PY
informativeness/M
informed/U
infotainment/M
infra
infrared/M
infrasonic
infrastructural
infrastructure/SM
infrequence/M
infrequent/Y
infringe/LZR
infringement/MS
infuriate/GDS
infuriating/Y
infuser/SM
ingenious/PY
ingeniousness/M
ingenue/SM
ingenuity/M
ingenuous/EY
ingenuousness/M
ingest/SDG
ingestion/M
inglenook/SM
ingot/SM
ingrain/G
ingrate/SM
ingratiate/GNDS
ingratiating/Y
ingratiation/M
ingredient/MS
ingress/MS
inguinal
ing�nue/SM
inhabit/DG
inhabitable/U
inhabitant/SM
inhabited/U
inhalant/SM
inhalation/MS
inhalator/MS
inhaler/SM
inharmonious
inhere/DSG
inherent/Y
inherit/EGSD
inheritance/EM
inheritances
inheritor/SM
inhibit/GSD
inhibition/SM
inhibitor/SM
inhibitory
inhuman/Y
inhumane/Y
inimical/Y
inimitably
iniquitous/Y
iniquity/SM
initial/SGMDY
initialism/S
initialization
initialize/DRSZG
initialized/AU
initiate/XMGNVDS
initiated/U
initiation/M
initiative/SM
initiator/MS
initiatory
initio
inject/SDG
injection/SM
injector/SM
injunctive
injure/DRSZG
injured/U
injurer/M
injurious
ink/MD
inkblot/SM
inkiness/M
inkjet/SM
inkling/SM
inkstand/SM
inkwell/MS
inky/RTP
inland/M
inline
inmate/SM
inmost
inn/SGMRJ
innards/M
innate/PY
innateness/M
innermost
innersole/SM
innerspring
innervate/GNDS
innervation/M
inning/M
innit
innkeeper/MS
innocence/M
innocent/MYS
innocuous/PY
innocuousness/M
innovate/XDSGNV
innovation/M
innovator/MS
innovatory
innuendo/SM
innuendoes
innumerably
innumerate
inoculate/AGDS
inoculation/MS
inoperative
inordinate/Y
inorganic
inositol
inquire/ZGDR
inquirer/M
inquiring/Y
inquiry/SM
inquisition/MS
inquisitional
inquisitive/YP
inquisitiveness/M
inquisitor/SM
inquisitorial
inrush/MS
insane/T
insatiability/M
insatiably
inscribe/ZGDR
inscriber/M
inscription/MS
inscrutability/M
inscrutable/P
inscrutableness/M
inscrutably
inseam/SM
insecticidal
insecticide/MS
insectivore/MS
insectivorous
insecure/Y
inseminate/DSGN
insemination/M
insensate
insensible
insensitive/Y
insentient
inseparable/MS
insert's
insert/AGSD
insertion/AM
insertions
insetting
inshore
inside/RSMZ
insider/M
insidious/YP
insidiousness/M
insight/MS
insightful
insignia/SM
insinuate/GNVDSX
insinuation/M
insinuator/SM
insipid/PY
insipidity/M
insist/SGD
insistence/M
insistent/Y
insisting/Y
insofar
insole/SM
insolence/M
insolent/Y
insoluble
insolubly
insolvency/S
insomnia/M
insomniac/SM
insomuch
insouciance/M
insouciant
inspect/AGDS
inspection/SM
inspector/MS
inspectorate/MS
inspiration/MS
inspirational
inspiratory
inspired/U
inspiring/U
inst
instability/S
install/UBZRSDG
installation/MS
installer/UM
installment/SM
instance/GD
instant/MRYS
instantaneous/Y
instantiate/DSXGN
instar
instate/AGDS
instead
instigate/DSGN
instigation/M
instigator/MS
instillation/M
instinct/VMS
instinctive/Y
instinctual
institute/XMZGNDRS
instituter/M
institution/M
institutional/Y
institutionalization/M
institutionalize/DSG
institutor/MS
instr
instruct/SDGV
instructed/U
instruction/MS
instructional
instructive/Y
instructor/MS
instrument/MDSG
instrumental/MYS
instrumentalist/SM
instrumentality/M
instrumentation/M
insubordinate
insufferable
insufferably
insula
insular
insularity/M
insulate/GNDS
insulation/M
insulator/MS
insulin/M
insult/SMDG
insulting/Y
insuperable
insuperably
insurance/SM
insure/DRSZGB
insured/SM
insurer/M
insurgence/SM
insurgency/SM
insurgent/MS
insurmountably
insurrection/SM
insurrectionist/SM
int
intact
intaglio/MS
integer/MS
integral/SMY
integrand
integrate/AEVNGXSD
integration/AEM
integrationist/SM
integrator
integrity/M
integument/SM
intel/M
intellect/MS
intellectual/MYS
intellectualism/M
intellectualize/GDS
intelligence/M
intelligent/Y
intelligentsia/M
intelligibility/M
intelligible/U
intelligibly/U
intended/SM
intense/YTVR
intensification/M
intensifier/M
intensify/DRSZGN
intensity/S
intensive/MYPS
intensiveness/M
intent/SMYP
intention/MS
intentional/UY
intentness/M
inter/ESL
interact/SGVD
interaction/SM
interactive/Y
interactivity
interbred
interbreed/GS
intercede/GDS
intercellular
intercept/GMDS
interception/MS
interceptor/SM
intercession/SM
intercessor/MS
intercessory
interchange/DSMG
interchangeability
interchangeable
interchangeably
intercity
intercollegiate
intercom/SM
intercommunicate/DSGN
intercommunication/M
interconnect/GDS
interconnection/SM
intercontinental
intercourse/M
intercultural
interdenominational
interdepartmental
interdependence/M
interdependent/Y
interdict/GMDS
interdiction/M
interdisciplinary
interest/ESMD
interested/U
interesting/Y
interface/MGDS
interfaith
interfere/GDS
interference/M
interferon/M
interfile/GDS
intergalactic
intergovernmental
interim/M
interior/SM
interj
interject/GDS
interjection/SM
interlace/GDS
interlard/DGS
interleave/DSG
interleukin/M
interline/GDSJ
interlinear
interlining/M
interlink/DSG
interlock/GMDS
interlocutor/SM
interlocutory
interlope/ZGDRS
interloper/M
interlude/MGDS
intermarriage/SM
intermarry/GDS
intermediacy/S
intermediary/SM
intermediate/XMYGNPDS
intermediation/ES
intermediator/MS
interment/EM
interments
intermezzi
intermezzo/MS
interminably
intermingle/DSG
intermission/SM
intermittence/S
intermittency/S
intermittent/Y
intermix/GDS
intermolecular/Y
internal/SY
internalization/M
internalize/GDS
international/SMY
internationalism/M
internationalist/SM
internationalization
internationalize/DSG
interne/GDL
internecine
internee/SM
interneship/S
internet
internist/MS
internment/M
internship/MS
interoffice
interoperability
interoperable
interoperate/S
interpenetrate/DSGN
interpersonal
interplanetary
interplay/M
interpolate/XDSGN
interpolation/M
interpose/GDS
interposition/M
interpret/AGVDS
interpretation/AMS
interpretative
interpreted/U
interpreter/MS
interquartile
interracial
interred/E
interregnum/SM
interrelate/XDSGN
interrelation/M
interrelationship/MS
interring/E
interrogate/DSGNVX
interrogation/M
interrogative/MYS
interrogator/SM
interrogatory/SM
interrupt/ZGMDRS
interrupter/M
interruptible/U
interruption/MS
interscholastic
intersect/GDS
intersection/SM
intersectional
intersectionality
intersession/SM
intersex
intersexual/MS
intersexualism
intersexuality
intersperse/GNDS
interspersion/M
interstate/MS
interstellar
interstice/MS
interstitial
intertwine/GDS
interurban
interval/SM
intervene/GDS
intervention/SM
interventionism/M
interventionist/SM
interview/ZGMDRS
interviewee/MS
interviewer/M
intervocalic
interwar
interweave/GS
interwove
interwoven
intestacy/M
intestate
intestinal
intestine/MS
intifada/S
intimacy/SM
intimate/MYGNDSX
intimation/M
intimidate/GNDS
intimidating/Y
intimidation/M
intl
intonation/SM
intoxicant/SM
intoxicate/DSGN
intoxication/M
intracranial
intramural
intramuscular
intranet/MS
intransigence/M
intransigent/MYS
intrastate
intrauterine
intravenous/MSY
intrepid/Y
intrepidity/M
intricacy/SM
intricate/Y
intrigue/DRSMZG
intriguer/M
intriguing/Y
intrinsic
intrinsically
intro/SM
introduce/AGDS
introduction/AM
introductions
introductory
introit/SM
introspect/GVDS
introspection/M
introspective/Y
introversion/M
introvert/MDS
intrude/DRSZG
intruder/M
intrusion/SM
intrusive/YP
intrusiveness/M
intuit/SDGV
intuition/S
intuitive/PY
intuitiveness/M
inundate/XDSGN
inundation/M
inure/DSG
invade/DRSZG
invader/M
invalid/GMDYS
invalidism/M
invaluable
invaluably
invariant
invasion/MS
invasive
invective/M
inveigh/GD
inveighs
inveigle/ZGDRS
inveigler/M
invent/ASGVD
invention/AMS
inventive/PY
inventiveness/M
inventor/MS
inventory/DSMG
inverse/SMY
invert/SMDRZG
inverter/M
invertible
invest/ASDGL
investigate/GNVDSX
investigation/M
investigator/SM
investigatory
investiture/MS
investment/AEM
investor/SM
inveteracy/M
inveterate
invidious/YP
invidiousness/M
invigilate/GNDS
invigilator/S
invigorate/ADSG
invigorating/Y
invigoration/M
invincibility/M
invincibly
inviolability/M
inviolably
inviolate
invitation/SM
invitational/SM
invite/DSMG
invited/U
invitee/SM
inviting/Y
invoke/DSG
involuntariness/M
involuntary/P
involution/M
involve/LDSG
involved/U
involvement/SM
inward/SY
ioctl
iodide/SM
iodine/M
iodize/DSG
ion/USM
ionic
ionization/UM
ionize/UDSG
ionizer/MS
ionosphere/MS
ionospheric
iota/MS
ipecac/SM
irascibility/M
irascible
irascibly
irate/YP
irateness/M
ire/M
ireful
irenic
irides
iridescence/M
iridescent/Y
iridium/M
iris/MS
irk/SGD
irksome/YP
irksomeness/M
iron/MDSG
ironclad/MS
ironic/U
ironical/Y
ironically/U
ironing/M
ironmonger/S
ironmongery
ironstone/M
ironware/M
ironwood/MS
ironwork/M
irony/SM
irradiate/DSGN
irradiation/M
irrational/SMY
irrationality/M
irreclaimable
irreconcilability/M
irreconcilable
irreconcilably
irrecoverable
irrecoverably
irredeemable
irredeemably
irreducible
irreducibly
irrefutable
irrefutably
irregular/MYS
irregularity/SM
irrelevance/MS
irrelevancy/MS
irrelevant/Y
irreligion
irreligious
irremediable
irremediably
irremovable
irreparable
irreparably
irreplaceable
irrepressible
irrepressibly
irreproachable
irreproachably
irresistible
irresistibly
irresolute/PYN
irresoluteness/M
irresolution/M
irrespective
irresponsibility/M
irresponsible
irresponsibly
irretrievable
irretrievably
irreverence/M
irreverent/Y
irreversible
irreversibly
irrevocability
irrevocable/P
irrevocably
irrigable
irrigate/DSGN
irrigation/M
irritability/M
irritable
irritably
irritant/SM
irritate/DSXGN
irritating/Y
irritation/M
irrupt/DGVS
irruption/SM
ischemia
ischemic
isinglass/M
isl
island/SZMR
islander/M
isle/MS
islet/SM
ism/CM
isms
isn't
isobar/MS
isobaric
isolate/DSMGN
isolation/M
isolationism/M
isolationist/SM
isomer/MS
isomeric
isomerism/M
isometric/S
isometrically
isometrics/M
isometry
isomorphic
isomorphism
isosceles
isospin/S
isotherm/SM
isotope/SM
isotopic
isotropic
issuance/M
issue/ADSMG
issuer/MS
isthmian
isthmus/MS
it'd
it'll
it/USM
ital
italic/SM
italicization/M
italicize/GDS
italics/M
itch/MDSG
itchiness/M
itchy/RPT
item/MS
itemization/M
itemize/GDS
iterate/AXGNVDS
iteration/AM
iterative/Y
iterator/S
itinerant/SM
itinerary/SM
itself
iv/U
ivory/SM
ivy/DSM
ix
j/F
jab/SM
jabbed
jabber/SMDRZG
jabberer/M
jabbing
jabot/SM
jacaranda/MS
jack/MDGS
jackal/SM
jackass/MS
jackboot/SMD
jackdaw/MS
jacket/SMD
jackhammer/MS
jackknife/MGDS
jackknives
jackpot/MS
jackrabbit/MS
jackstraw/MS
jacquard/M
jade/MGDS
jaded/PY
jadedness/M
jadeite/M
jag/SM
jagged/TPRY
jaggedness/M
jaggies
jaguar/SM
jail/MDRZGS
jailbird/SM
jailbreak/SM
jailer/M
jailhouse/S
jalapeno/MS
jalape�o/MS
jalopy/SM
jalousie/MS
jam/SM
jamb/MS
jambalaya/M
jamboree/MS
jammed
jamming
jammy/RT
jangle/DRSMZG
jangler/M
janitor/SM
janitorial
japan/SM
japanned
japanning
jape/MGDS
japonica
jar/SM
jardiniere/SM
jardini�re/SM
jarful/MS
jargon/M
jarred
jarring/Y
jasmine/SM
jasper/M
jato/MS
jaundice/DSMG
jaunt/SGMD
jauntily
jauntiness/M
jaunty/RPT
java/M
javelin/SM
jaw/SGMD
jawbone/DSMG
jawbreaker/MS
jawline/S
jay/SM
jaybird/SM
jaywalk/DRSZG
jaywalker/M
jaywalking/M
jazz/MDSG
jazzy/TR
jct
jealous/Y
jealousy/SM
jean/MS
jeans/M
jeep/MS
jeer/MDSG
jeering/MY
jeez
jejuna
jejune
jejunum/M
jell/DSG
jello/SM
jelly/GDSM
jellybean/MS
jellyfish/MS
jellylike
jellyroll/SM
jemmy/GDS
jennet/MS
jenny/SM
jeopardize/GDS
jeopardy/M
jeremiad/MS
jerk/MDSG
jerkily
jerkin/MS
jerkiness/M
jerkwater
jerky/TRMP
jeroboam/S
jerrican/S
jerrybuilt
jerrycan/S
jersey/MS
jest/MDRSZG
jester/M
jesting/Y
jet/SM
jetliner/SM
jetport/MS
jetsam/M
jetted
jetting
jettison/MDSG
jetty/SM
jewel/SZGMDR
jeweler/M
jewellery
jewelry/SM
jg
jib/SGMD
jibbed
jibbing
jibe/MS
jiff/MS
jiffy/SM
jig's
jig/AS
jigged/A
jigger's
jigger/ASDG
jigging/A
jiggle/DSMG
jiggly
jigsaw/SMDG
jihad/SM
jihadist/SM
jilt/MDSG
jimmy/DSMG
jimsonweed/M
jingle/DSMG
jingly
jingoism/M
jingoist/SM
jingoistic
jink/DSG
jinn/MS
jinni/M
jinricksha/SM
jinrikisha/SM
jinx/MDSG
jitney/SM
jitter/SDG
jitterbug/MS
jitterbugged
jitterbugger/M
jitterbugging
jitters/M
jittery/RT
jive/MGDS
job/SM
jobbed
jobber/SM
jobbing
jobholder/MS
jobless/P
joblessness/M
jobshare/S
jobsworth
jobsworths
jock/MS
jockey/SGMD
jockstrap/MS
jocose/PY
jocoseness/M
jocosity/M
jocular/Y
jocularity/M
jocund/Y
jocundity/M
jodhpurs/M
joey/S
jog/SM
jogged
jogger/SM
jogging/M
joggle/DSMG
john/MS
johnny/SM
johnnycake/MS
join's
join/AFDSG
joiner/FMS
joinery/M
joint's
joint/EGSD
jointly/F
joist/SM
jojoba
joke/MZGDRS
joker/M
jokester/S
jokey
jokier
jokiest
joking/Y
jollification/SM
jollily
jolliness/M
jollity/M
jolly/TGPDRSM
jolt/MDRSZG
jolter/M
jonquil/SM
josh/MDRSZG
josher/M
jostle/MGDS
jot/SM
jotted
jotter/MS
jotting/MS
joule/SM
jounce/MGDS
jouncy
journal/MS
journalese/M
journalism/M
journalist/SM
journalistic
journey/ZGMDRS
journeyer/M
journeyman/M
journeymen
journo/S
joust/SZGMDR
jouster/M
jousting/M
jovial/Y
joviality/M
jowl/MS
jowly/TR
joy/SGMD
joyful/YP
joyfuller
joyfullest
joyfulness/M
joyless/PY
joylessness/M
joyous/YP
joyousness/M
joyridden
joyride/RSMZG
joyrider/M
joyriding/M
joyrode
joystick/SM
jr
jubilant/Y
jubilation/M
jubilee/SM
judder/GDS
judge's
judge/ADSG
judgement/SM
judgemental
judgeship/MS
judgey
judgment/SM
judgmental/Y
judgy
judicatory/SM
judicature/M
judicial/Y
judiciary/SM
judicious/IYP
judiciousness/IM
judo/M
jug/SM
jugful/MS
jugged
juggernaut/SM
jugging
juggle/MZGDRS
juggler/M
jugglery/M
jugular/SM
juice/DRSMZG
juicer/M
juicily
juiciness/M
juicy/PTR
jujitsu/M
jujube/MS
jukebox/MS
julep/SM
julienne
jumble/MGDS
jumbo/SM
jump/MDRSZG
jumper/M
jumpily
jumpiness/M
jumpsuit/MS
jumpy/TRP
jun
junco/SM
junction/FISM
juncture/FMS
jungle/MS
junior/MS
juniper/SM
junk/MDRSZG
junker/M
junket/MDRSZG
junketeer/MS
junketer/M
junkie/M
junky/TRSM
junkyard/MS
junta/SM
juridic
juridical/Y
jurisdiction/SM
jurisdictional
jurisprudence/M
jurist/MS
juristic
juror/FSM
jury/ISM
juryman/M
jurymen
jurywoman/M
jurywomen
just/RYPT
justice/IMS
justifiable/U
justifiably/U
justification/M
justified/U
justify/XGDSN
justness/M
jut/SM
jute/M
jutted
jutting
juvenile/SM
juxtapose/DSG
juxtaposition/SM
k/IFGS
kHz
kW
kWh
kabbala
kabbalah
kabob/SM
kabocha
kaboom
kabuki/M
kaddish/MS
kaffeeklatch/MS
kaffeeklatsch/MS
kahuna/S
kaiser/MS
kakistocracy
kale/M
kaleidoscope/MS
kaleidoscopic
kaleidoscopically
kamikaze/MS
kana
kangaroo/MS
kanji
kaolin/M
kapok/M
kappa/SM
kaput
karakul/M
karaoke/MS
karat/SM
karate/M
karma/M
karmic
kart/MS
katakana
katydid/SM
kayak/SMDG
kayaking/M
kayo/MDSG
kazoo/SM
kbps
kc
kebab/SM
kebob/SM
kedgeree
keel/MDSG
keelhaul/DGS
keen/MDRYSTGP
keenness/M
keep/MRSZG
keeper/M
keeping/M
keepsake/MS
keester/S
keg/SM
keister/S
kelp/M
kelvin/SM
ken/SM
kenned
kennel/SGMD
kenning
keno/M
kepi/MS
kept
keratin/M
keratitis
kerbside
kerchief/SM
kerfuffle/S
kern/G
kerne
kernel/SM
kerosene/M
kestrel/MS
ketch/MS
ketchup/M
keto
ketogenic
ketone/S
kettle/SM
kettledrum/SM
key/SGMD
keybinding/S
keyboard/ZGSMDR
keyboarder/M
keyboardist/SM
keyhole/MS
keylogger/MS
keylogging/SM
keynote/MZGDRS
keynoter/M
keypad/SM
keypunch/ZGMDRS
keypuncher/M
keystone/MS
keystroke/SM
keyword/MS
kg
khaki/SM
khan/MS
kibble/DSMG
kibbutz/MS
kibbutzim
kibibyte/SM
kibitz/ZGDRS
kibitzer/M
kibosh/M
kick/MDRSZG
kickback/SM
kickball/M
kickboxing
kicker/M
kickoff/MS
kickstand/MS
kicky/RT
kid/SM
kidded
kidder/SM
kiddie/SM
kidding
kiddish
kiddo/SM
kidnap/S
kidnapped
kidnapper/MS
kidnapping/MS
kidney/SM
kidskin/M
kielbasa/MS
kielbasi
kike/S
kill/JMDRSZG
killdeer/SM
killer/M
killing/M
killjoy/SM
kiln/MDSG
kilo/MS
kilobyte/SM
kilocoulomb/S
kilocycle/SM
kilogram/SM
kilohertz/M
kilojoule/S
kiloliter/MS
kilometer/MS
kilonewton/S
kilopascal/S
kiloton/SM
kilovolt/S
kilowatt/SM
kilt/MDRS
kilter/M
kimono/MS
kin/M
kinase
kind's
kind/UPRYT
kinda
kindergarten/MS
kindergartner/SM
kinderg�rtner/SM
kindhearted/PY
kindheartedness/M
kindle/AGDS
kindliness/M
kindling/M
kindly/URT
kindness/UM
kindnesses
kindred/M
kinds
kine/S
kinematic/S
kinematics/M
kinetic/S
kinetically
kinetics/M
kinfolk/SM
kinfolks/M
king/MYS
kingdom/SM
kingfisher/SM
kingly/RT
kingmaker/S
kingpin/SM
kingship/M
kink/MDSG
kinkily
kinkiness/M
kinky/TPR
kinsfolk/M
kinship/M
kinsman/M
kinsmen
kinswoman/M
kinswomen
kiosk/SM
kip/SM
kipped
kipper/MDGS
kipping
kirsch/MS
kismet/M
kiss/MDRSBZG
kisser/M
kissoff/SM
kissogram/S
kit/SGMD
kitbag/MS
kitchen/SM
kitchenette/MS
kitchenware/M
kite/MS
kith/M
kitsch/M
kitschy
kitted
kitten/MS
kittenish
kitting
kitty/SM
kiwi/MS
kiwifruit/MS
kl
klansmen
klaxon/S
kleptocracy/S
kleptomania/M
kleptomaniac/SM
kludge/GDS
kludgy
kluge/DS
klutz/MS
klutziness/M
klutzy/TRP
km
kn
knack/SZMR
knacker/GD
knackwurst/MS
knapsack/MS
knave/SM
knavery/M
knavish/Y
knead/SZGDR
kneader/M
knee/MDS
kneecap/SM
kneecapped
kneecapping
kneeing
kneel/SG
knell/SGMD
knelt
knew
knicker/S
knickerbockers/M
knickers/M
knickknack/MS
knife/DSMG
knight/MDYSG
knighthood/MS
knightliness/M
knish/MS
knit/MS
knitted
knitter/SM
knitting/M
knitwear/M
knives
knob/MS
knobbly
knobby/TR
knock/SZGMDR
knockabout
knockdown/SM
knocker/M
knockoff/SM
knockout/SM
knockwurst/SM
knoll/SM
knot/MS
knothole/SM
knotted
knotting
knotty/TR
know/SB
knowing/UYS
knowledge/M
knowledgeable
knowledgeably
known
knuckle/DSMG
knuckleduster/S
knucklehead/MS
knurl/SGMD
koala/SM
koan/S
kohl
kohlrabi/M
kohlrabies
kola/MS
kombucha
kook/MS
kookaburra/SM
kookiness/M
kooky/TPR
kopeck/MS
kopek/SM
korma
kosher/DSG
kowtow/GMDS
kph
kraal/SM
kraut/SM!
krill/M
krona/M
krone/RM
kronor
kronur
krypton/M
kryptonite
kr�na/M
kr�nur
kt
kuchen/SM
kudos/M
kudzu/SM
kumquat/MS
kvetch/ZGMDRS
kvetcher/M
kw
l/SDXTGJ
la/M
lab/SM
label's
label/ASDG
labeled/U
labelled/U
labia
labial/SM
labile
labium/M
labor/SMDRZG
laboratory/SM
laborer/M
laborious/PY
laboriousness/M
laborsaving
laburnum/MS
labyrinth/M
labyrinthine
labyrinths
lac/M
lace's
lace/UGDS
lacerate/DSGNX
laceration/M
lacewing/SM
lacework/M
lachrymal
lachrymose
lack/MDSG
lackadaisical/Y
lackey/SM
lackluster
laconic
laconically
lacquer/GMDS
lacrimal
lacrosse/M
lactate/GNDS
lactation/M
lacteal
lactic
lactose/M
lacuna/M
lacunae
lacy/RT
lad/SGMDNJ
ladder/GSMD
laddie/SM
laddish/P
lade/S
laden/U
lading/M
ladle/DSMG
lady/SM
ladybird/SM
ladybug/MS
ladyfinger/MS
ladylike/U
ladylove/MS
ladyship/MS
laetrile/M
lag/SZMR
lager/M
laggard/MYS
lagged
lagging/M
laggy
lagniappe/SM
lagoon/SM
laid/IA
lain
lair/MS
laird/SM
laissez-faire
laity/M
lake/MS
lakefront/S
lakeside
lallygag/S
lallygagged
lallygagging
lam/SM
lama/MS
lamasery/SM
lamb/MDSG
lambada/MS
lambast/GDS
lambaste/S
lambda/SM
lambency/M
lambent/Y
lambkin/SM
lambskin/SM
lambswool
lame/MYZTGDRSP
lamebrain/MDS
lameness/M
lament/BSMDG
lamentably
lamentation/MS
lamina/M
laminae
laminar
laminate/MGNDS
lamination/M
lammed
lamming
lamp/MS
lampblack/M
lamplight/MRZ
lamplighter/M
lampoon/SGMD
lamppost/SM
lamprey/MS
lampshade/SM
lanai/SM
lance/DRSMZG
lancer/M
lancet/SM
land/MDRSGJ
landau/SM
landfall/MS
landfill/MS
landholder/SM
landholding/MS
landing/M
landlady/SM
landless/M
landline/MS
landlocked
landlord/MS
landlubber/MS
landmark/MS
landmass/MS
landmine/S
landowner/MS
landownership
landowning/SM
landscape/MZGDRS
landscaper/M
landslid
landslide/MGS
landslip/S
landsman/M
landsmen
landward/S
lane/MS
language/MS
langue/SM
languid/PY
languidness/M
languish/DSG
languor/SM
languorous/Y
lank/RYTP
lankiness/M
lankness/M
lanky/RTP
lanolin/M
lantern/MS
lanthanum/M
lanyard/MS
lap/SM
laparoscopic
laparoscopy
laparotomy
lapboard/SM
lapdog/SM
lapel/SM
lapidary/SM
lapin/SM
lapped
lappet/SM
lapping
lapse/AKGMSD
laptop/SM
lapwing/MS
larboard/SM
larcenist/SM
larcenous
larceny/SM
larch/MS
lard/MDRSZG
larder/M
lardy/RT
large/RSPMYT
largehearted
largeness/M
largess/M
largish
largo/SM
lariat/SM
lark/MDSG
larkspur/SM
larva/M
larvae
larval
laryngeal
larynges
laryngitis/M
larynx/M
lasagna/MS
lascivious/YP
lasciviousness/M
lase/ZGDRS
laser/M
lash/MDSGJ
lashing/M
lass/MS
lassie/SM
lassitude/M
lasso/SMDG
last/MDYSG
lasting/UY
lat/S
latch's
latch/UDSG
latchkey/SM
late/YTRP
latecomer/MS
latency/M
lateness/M
latent
lateral/MDYSG
latest/M
latex/M
lath/MDRSZG
lathe/M
lather/GMD
lathery
laths
latices
latish
latitude/MS
latitudinal
latitudinarian/MS
latrine/MS
latte/RSM
latter/MY
lattice/MDS
latticework/SM
laud/MDSGB
laudably
laudanum/M
laudatory
laugh/BMDG
laughably
laughing/MY
laughingstock/SM
laughs
laughter/M
launch/AGMDS
launcher/SM
launchpad/SM
launder/DRZGS
launderer/M
launderette/SM
laundress/MS
laundromat/MS
laundry/SM
laundryman/M
laundrymen
laundrywoman/M
laundrywomen
laureate/MS
laureateship/M
laurel/SM
lav/SGD
lava/M
lavage/M
lavaliere/SM
lavatorial
lavatory/SM
lave/S
lavender/SM
lavish/PTGDRSY
lavishness/M
law/SM
lawbreaker/SM
lawbreaking/M
lawful/UPY
lawfulness/UM
lawgiver/MS
lawless/PY
lawlessness/M
lawmaker/MS
lawmaking/M
lawman/M
lawmen
lawn/MS
lawnmower/SM
lawrencium/M
lawsuit/MS
lawyer/SMY
lax/TRYP
laxative/MS
laxity/M
laxness/M
lay/AICSGM
layabout/S
layaway/M
layer/CSM
layered
layering/M
layette/MS
layman/M
laymen
layoff/SM
layout/SM
layover/MS
laypeople
layperson/MS
layup/SM
laywoman/M
laywomen
laze/MGDS
lazily
laziness/M
lazy/DRSTGP
lazybones/M
lb/S
lbw
lea/SM
leach/DSG
leachate/S
lead/MDNRSZG
leader/M
leaderless
leadership/SM
leading/M
leaf/MDSG
leafage/M
leafless
leaflet/GMDS
leafstalk/MS
leafy/RT
league/DSMG
leak/MDSG
leakage/MS
leakiness/M
leaky/PRT
lean/MDRSTGJP
leaning/M
leanness/M
leap/MDRSZG
leaper/M
leapfrog/MS
leapfrogged
leapfrogging
leapt
learn/AUGDS
learnability
learnable
learnedly
learner/MS
learning's
learnt
lease/ADSMG
leaseback/SM
leasehold/MRSZ
leaseholder/M
leaser/SM
leash's
leash/UDSG
least/M
leastwise
leather/MS
leatherette/M
leatherneck/MS
leathery
leave/DRSMZGJ
leaven/SGMD
leavened/U
leavening/M
leaver/M
leavings/M
lech/MDRSZG
lecher/M
lecherous/PY
lecherousness/M
lechery/M
lecithin/M
lectern/MS
lector/SM
lecture/MZGDRS
lecturer/M
lectureship/SM
lede
ledge/RSMZ
ledger/M
lee/RSMZ
leech/MDSG
leek/MS
leer/MDG
leeriness/M
leery/RPT
leeward/SM
leeway/M
left/MRST
leftism/M
leftist/SM
leftmost
leftover/SM
leftward/S
lefty/SM
leg/SM
legacy/SM
legal/SMY
legalese/M
legalism/MS
legalistic
legalistically
legality/SM
legalization/M
legalize/GDS
legate/CXMNS
legatee/MS
legation's/AC
legato/SM
legend/SM
legendarily
legendary
legerdemain/M
legged
leggin/SM
legginess/M
legging/MS
leggy/RPT
leghorn/MS
legibility/M
legible
legibly
legion/SM
legionary/SM
legionnaire/SM
legislate/DSGNV
legislation/M
legislative/Y
legislator/MS
legislature/SM
legit
legitimacy/M
legitimate/DSYG
legitimatize/GDS
legitimization/M
legitimize/DSG
legless
legman/M
legmen
legroom/SM
legume/MS
leguminous
legwarmer/S
legwork/M
lei/SM
leisure/DMY
leisureliness/M
leisurewear/M
leitmotif/MS
leitmotiv/MS
lemma/S
lemme/JG
lemming/M
lemon/SM
lemonade/SM
lemongrass
lemony
lemur/SM
lend/RSZG
lender/M
length/MNX
lengthen/GD
lengthily
lengthiness/M
lengths
lengthwise
lengthy/PRT
lenience/M
leniency/M
lenient/Y
lenitive
lens/MS
lent
lentil/MS
lento
leonine
leopard/SM
leopardess/MS
leotard/SM
leper/SM
lepidopterist/MS
leprechaun/MS
leprosy/M
leprous
lepta
lepton/MS
lesbian/SM
lesbianism/M
lesion/MS
less/MNRX
lessee/MS
lessen/GD
lesson/MS
lessor/MS
let/ISM
letdown/SM
lethal/Y
lethality
lethargic
lethargically
lethargy/M
letter/ZGMDRS
letterbomb/S
letterbox/S
lettered/U
letterer/M
letterhead/MS
lettering/M
letterpress/M
letting/S
lettuce/MS
letup/SM
leucine
leucotomy/S
leukemia/M
leukemic/SM
leukocyte/MS
levee/SM
level/PSZGMDRY
leveler/M
levelheaded/P
levelheadedness/M
levelness/M
lever/SGMD
leverage's
leverage/CDSG
leviathan/MS
levier/M
levitate/DSGN
levitation/M
levity/M
levy/DRSMZG
lewd/RYPT
lewdness/M
lexer/S
lexical
lexicographer/MS
lexicographic
lexicographical
lexicography/M
lexicological
lexicologist/M
lexicology/SM
lexicon/SM
lexis
lg
liabilities
liability/AM
liable/A
liaise/GDS
liaison/MS
liar/MS
lib/M
libation/SM
libber/MS
libel/SZGMDR
libeler/M
libelous
liberal/MYPS
liberalism/M
liberality/M
liberalization/SM
liberalize/GDS
liberalness/M
liberate/CDSGN
liberation/CM
liberator/MS
libertarian/SM
libertine/MS
liberty/SM
libidinal
libidinous
libido/MS
librarian/MS
librarianship
library/SM
librettist/MS
libretto/SM
lice
license/MGDS
licensed/U
licensee/MS
licensor
licentiate/SM
licentious/YP
licentiousness/M
lichen/MS
licit/Y
lick/MDJSG
licking/M
licorice/SM
lid/SM
lidded
lidless
lido/MS
lie/DSM
lied/MR
lief/RT
liege/SM
lien/MS
lieu/M
lieutenancy/M
lieutenant/MS
life/MZR
lifebelt/S
lifeblood/M
lifeboat/MS
lifebuoy/MS
lifecycle
lifeforms
lifeguard/SM
lifeless/YP
lifelessness/M
lifelike
lifeline/MS
lifelong
lifer/M
lifesaver/SM
lifesaving/M
lifespan/S
lifestyle/SM
lifetime/MS
lifework/MS
lift/MDRSZG
lifter/M
liftoff/SM
ligament/MS
ligate/GNDS
ligation/M
ligature/MGDS
light's/C
light/CASTGD
lighted/U
lighten/SDRZG
lightener/M
lighter/SM
lightface/MD
lightheaded
lighthearted/YP
lightheartedness/M
lighthouse/MS
lighting's
lightly
lightness/M
lightning/MDS
lightproof
lightship/MS
lightweight/SM
ligneous
lignin
lignite/M
lii
likability/M
likable/P
likableness/M
like/EMGDST
likeability/M
likeable/P
likeableness/M
likelihood/UM
likelihoods
likeliness/UM
likely/UPRT
liken/SGD
likeness/UM
likenesses
liker
likewise
liking/M
lilac/SM
lilliputian
lilo/S
lilt/MDSG
lily/SM
limb/MS
limber/UDSG
limberness/M
limbless
limbo/SM
lime/MGDS
limeade/SM
limelight/M
limerick/SM
limescale
limestone/M
limey/S
limit's
limit/CSZGDR
limitation/CM
limitations
limited/U
limiter's
limiting/S
limitless/P
limitlessness/M
limn/DSG
limnological
limnologist/MS
limnology/M
limo/MS
limousine/MS
limp/MDRYSPTG
limpet/MS
limpid/YP
limpidity/M
limpidness/M
limpness/M
limy/RT
linage/M
linchpin/SM
linden/MS
line/MZGDRSJ
lineage/MS
lineal/Y
lineament/SM
linear/Y
linearity/M
linebacker/MS
lined/U
linefeed
lineman/M
linemen
linen/SM
linens/M
liner/M
linesman/M
linesmen
lineup/MS
ling/M
linger/ZGJDRS
lingerer/M
lingerie/M
lingering/Y
lingo/M
lingoes
lingua
lingual
linguine/M
linguini/SM
linguist/SM
linguistic/S
linguistical/Y
linguistics/M
liniment/SM
lining/M
link's
link/UDSG
linkage/MS
linker
linkman
linkmen
linkup/MS
linnet/MS
lino
linoleum/M
linseed/M
lint's
lint/CDG
lintel/MS
lints
linty/TR
lion/MS
lioness/MS
lionhearted
lionization/M
lionize/GDS
lip/SM
lipid/SM
liposuction/M
lipped
lippy
lipread/GRS
lipreader/M
lipreading/M
lipstick/MDSG
liq
liquefaction/M
liquefy/DSG
liqueur/SM
liquid/MS
liquidate/XGNDS
liquidation/M
liquidator/MS
liquidity/M
liquidize/ZGDRS
liquidizer/M
liquor/MDGS
lira/M
lire
lisle/M
lisp/MDRSZG
lisper/M
lissome
list/MDNSJXG
listed/U
listen/BMDRZG
listener/M
listeria
listing/M
listless/YP
listlessness/M
listserv/M
lit/ZR
litany/SM
litchi/MS
lite
liter/M
literacy/M
literal/SMYP
literalness/M
literariness/M
literary/P
literate/SMY
literati/M
literature/M
lithe/RPYT
litheness/M
lithesome
lithium/M
lithograph/MDRZG
lithographer/M
lithographic
lithographically
lithographs
lithography/M
lithosphere/SM
litigant/SM
litigate/DSGN
litigation/M
litigator/MS
litigious/P
litigiousness/M
litmus/M
litotes/M
litter/MDRSZG
litterateur/MS
litterbug/MS
litterer/M
little/MTRP
littleness/M
littoral/SM
litt�rateur/SM
liturgical/Y
liturgist/SM
liturgy/SM
livability/M
livable/U
live/ATGDSB
livelihood/SM
liveliness/M
livelong/S
lively/PRT
liven/SGD
liver's
liver/S
liveried
liverish
liverwort/MS
liverwurst/M
livery/CSM
liveryman/CM
liverymen/C
livestock/M
liveware
livid/Y
living/MS
lix/K
lizard/MS
ll
llama/SM
llano/SM
lo
load's
load/AUGSD
loadable
loader/MS
loading's
loaf/MDRSZG
loafer/M
loam/M
loamy/TR
loan/MDRSZG
loaner/M
loansharking/M
loanword/MS
loath/JZGDRS
loathe
loather/M
loathing/M
loathsome/PY
loathsomeness/M
loaves
lob/SMD
lobar
lobbed
lobber/MS
lobbing
lobby/GDSM
lobbyist/MS
lobe/MS
lobotomize/DSG
lobotomy/SM
lobster/MS
local/SMY
locale/MS
locality/SM
localization/M
localize/DRSZG
locate/AESDNGX
location/EAM
locator/MS
locavore/SM
loci
lock/MDRSBZG
locker/M
locket/MS
lockjaw/M
lockout/MS
locksmith/M
locksmiths
lockstep/M
lockup/MS
loco/S
locomotion/M
locomotive/MS
locoweed/SM
locum/S
locus/M
locust/SM
locution/MS
lode/MS
lodestar/MS
lodestone/MS
lodge/DRSJMZG
lodger/M
lodging/M
lodgings/M
loft/MDSG
loftily
loftiness/M
lofty/PRT
log/SM
loganberry/SM
logarithm/SM
logarithmic
logbook/SM
loge/MS
logged
logger/SM
loggerhead/SM
loggia/SM
logging/M
logic/M
logical/Y
logicality/M
logician/MS
login/SM
logistic/S
logistical/Y
logistics/M
logjam/SM
logo/MS
logoff/SM
logon/SM
logotype/SM
logout/SM
logrolling/M
logy/RT
loin/MS
loincloth/M
loincloths
loiter/ZGSDR
loiterer/M
loitering/M
lolcat/SM
loll/DSG
lollipop/SM
lollop/GSD
lolly/S
lollygag/S
lollygagged
lollygagging
lollypop/MS
lone/YZR
loneliness/M
lonely/PTR
loner/M
lonesome/YP
lonesomeness/M
long's
long/KDSTG
longboat/MS
longbow/MS
longer
longevity/M
longhair/MS
longhand/M
longhorn/MS
longhouse/S
longing/MYS
longish
longitude/MS
longitudinal/Y
longshoreman/M
longshoremen
longsighted
longstanding
longtime
longueur/SM
longways
loo
loofah/M
loofahs
look/MDRSZG
lookalike/MS
looker/M
lookout/MS
lookup
loom/MDSG
loon/MS
loonie/M
loony/RSMT
loop/MDSG
loophole/MS
loopy/RT
loos/NRX
loose/UDSTG
loosely
loosen/UGSD
looseness/M
loot/MDRSZG
looter/M
looting/M
lop/S
lope/MGDS
lopped
lopping
lopsided/YP
lopsidedness/M
loquacious/PY
loquaciousness/M
loquacity/M
lord/MDYSG
lordliness/M
lordly/TPR
lordship/SM
lore/M
lorgnette/SM
loris/MS
lorn
lorry/SM
lose/ZGRSJ
loser/M
losing/M
loss/MS
lossless
lost
lot/SM
lotion/SM
lottery/SM
lotto/M
lotus/MS
louche
loud/RYTP
loudhailer/SM
loudmouth/MD
loudmouths
loudness/M
loudspeaker/MS
lough
loughs
lounge/MZGDRS
lounger/M
lour/DSG
louse's
louse/CDSG
lousily
lousiness/M
lousy/TPR
lout/MS
loutish/PY
louver/MDS
lovableness/M
lovably
love/MYZGDRSB
lovebird/SM
lovechild/M
loved/U
loveless
loveliness/M
lovelorn
lovely/RSMTP
lovemaking/M
lover/M
loveseat/SM
lovesick
lovey/S
loving/Y
low/SZTGMDRYP
lowborn
lowboy/MS
lowbrow/SM
lowdown/M
lower/GD
lowercase/M
lowermost
lowish
lowland/SZMR
lowlander/M
lowlife/SM
lowliness/M
lowly/TPR
lowness/M
lox/M
loyal/ETY
loyaler
loyalism/M
loyalist/SM
loyalties
loyalty/EM
lozenge/SM
ltd
luau/MS
lubber/MYS
lube/MGDS
lubricant/SM
lubricate/DSGN
lubrication/M
lubricator/MS
lubricious/Y
lubricity/M
lucid/PY
lucidity/M
lucidness/M
luck/MDSG
luckily/U
luckiness/UM
luckless
lucky/UPTR
lucrative/YP
lucrativeness/M
lucre/M
lucubrate/GNDS
lucubration/M
ludicrous/YP
ludicrousness/M
ludo
luff/DSG
lug/SM
luge/S
luggage/M
lugged
lugger/MS
lugging
lughole/S
lugsail/SM
lugubrious/YP
lugubriousness/M
lukewarm/YP
lukewarmness/M
lull/MDSG
lullaby/SM
lulu/S
lumbago/M
lumbar
lumber/MDRZGS
lumberer/M
lumbering/M
lumberjack/SM
lumberman/M
lumbermen
lumberyard/SM
lumen/S
lumina
luminary/SM
luminescence/M
luminescent
luminosity/M
luminous/Y
lummox/MS
lump/MDNSG
lumpectomy/S
lumpenproletariat
lumpiness/M
lumpish
lumpy/TRP
lunacy/SM
lunar
lunatic/SM
lunch/GMDS
lunchbox/S
luncheon/SM
luncheonette/SM
lunchroom/MS
lunchtime/MS
lung/MDSG
lunge/SM
lungfish/MS
lungful/S
lunkhead/MS
lupine/MS
lupus/M
lurch/GMDS
lure/MGDS
lurgy
lurid/PY
luridness/M
lurk/DRSZG
luscious/PY
lusciousness/M
lush/MRSYPT
lushness/M
lust/MDRSG
luster/M
lusterless
lustful/Y
lustily
lustiness/M
lustrous/Y
lusty/PTR
lutanist/SM
lute/MS
lutenist/SM
lutetium/M
lux
luxuriance/M
luxuriant/Y
luxuriate/DSGN
luxuriation/M
luxurious/PY
luxuriousness/M
luxury/SM
lvi
lvii
lxi
lxii
lxiv
lxix
lxvi
lxvii
lyceum/MS
lychee/MS
lychgate/S
lye/MG
lying/M
lymph/M
lymphatic/SM
lymphocyte/SM
lymphoid
lymphoma/SM
lynch/JZGDRS
lyncher/M
lynching/M
lynx/MS
lyre/MS
lyrebird/MS
lyric/SM
lyrical/Y
lyricism/M
lyricist/SM
lysosomal
lysosomes
m/KAS
mRNA
ma'am
ma/SMH
mac/SGMD
macOS/M
macabre
macadam/M
macadamia/SM
macadamize/GDS
macaque/MS
macaroni/MS
macaroon/MS
macaw/SM
mace/MS
macerate/DSGN
maceration/M
mach/M
machete/SM
machinate/GNDSX
machination/M
machine/DSMGB
machinery/M
machinist/MS
machismo/M
macho/M
mack/MS
mackerel/SM
mackinaw/SM
mackintosh/MS
macrame/M
macram�/M
macro/SM
macrobiotic/S
macrobiotics/M
macrocosm/SM
macroeconomic/S
macroeconomics/M
macrology/S
macron/MS
macrophages
macroscopic
macroscopically
mad/SMYP
madam/SM
madame/M
madcap/MS
madden/DGS
maddening/Y
madder/MS
maddest
madding
made/AU
mademoiselle/MS
madhouse/SM
madman/M
madmen
madness/M
madras/MS
madrasa/SM
madrasah/M
madrasahs
madrassa/SM
madrigal/SM
madwoman/M
madwomen
maelstrom/SM
maestro/SM
mafia/SM
mafiosi
mafioso/M
mag/SM
magazine/SM
mage/MS
magenta/M
maggot/MS
maggoty
magi/M
magic/SM
magical/Y
magician/SM
magicked
magicking
magisterial/Y
magistracy/M
magistrate/SM
magma/M
magnanimity/M
magnanimous/Y
magnate/SM
magnesia/M
magnesium/M
magnet/MS
magnetic
magnetically
magnetism/M
magnetite/M
magnetizable
magnetization/CM
magnetize/CGDS
magneto/SM
magnetometer/SM
magnetosphere
magnification/M
magnificence/M
magnificent/Y
magnifier/M
magnify/ZGXDRSN
magniloquence/M
magniloquent
magnitude/SM
magnolia/MS
magnon
magnum/MS
magpie/MS
magus/M
maharaja/SM
maharajah/M
maharajahs
maharanee/MS
maharani/SM
maharishi/SM
mahatma/SM
mahjong/M
mahogany/SM
mahout/MS
maid/MNSX
maiden/MY
maidenhair/M
maidenhead/SM
maidenhood/M
maidservant/SM
mail/JMDRSZG
mailbag/SM
mailbomb/GSD
mailbox/MS
mailer/M
mailing/M
maillot/SM
mailman/M
mailmen
mailshot/S
maim/DSG
main/MYS
mainframe/SM
mainland/MS
mainline/MGDS
mainmast/MS
mainsail/MS
mainspring/MS
mainstay/MS
mainstream/SMDG
maintain/ZGBDRS
maintainability
maintainable/U
maintained/U
maintenance/M
maintop/SM
maisonette/MS
maize/SM
majestic
majestically
majesty/SM
majolica/M
major/SGMDY
majordomo/MS
majorette/MS
majoritarian/SM
majoritarianism
majority/SM
make's/A
make/UAGS
makeover/MS
maker/SM
makeshift/SM
makeup/MS
makeweight/S
making/MS
makings/M
malachite/M
maladjusted
maladjustment/M
maladministration
maladroit/PY
maladroitness/M
malady/SM
malaise/M
malamute/MS
malapropism/SM
malaria/M
malarial
malarkey/M
malathion/M
malcontent/MS
male/MPS
malediction/SM
malefaction/M
malefactor/SM
malefic
maleficence/M
maleficent
maleness/M
malevolence/M
malevolent/Y
malfeasance/M
malform/SD
malformation/SM
malfunction/MDSG
malice/M
malicious/PY
maliciousness/M
malign/DSG
malignancy/SM
malignant/Y
malignity/M
malinger/ZGSDR
malingerer/M
mall/MS
mallard/SM
malleability/M
malleable
mallet/MS
mallow/MS
malnourished
malnutrition/M
malocclusion/M
malodorous
malpractice/SM
malt/MDSG
malted/MS
maltose/M
maltreat/GLDS
maltreatment/M
malty/TR
malware/SM
mam/S
mama/MS
mamba/SM
mambo/SGMD
mamma/M
mammal/MS
mammalia
mammalian/MS
mammary
mammogram/MS
mammography/M
mammon/M
mammoth/M
mammoths
mammy/SM
man's/F
man/UFY
manacle/DSMG
manage/ZGDRSL
manageability/M
manageable/U
management/MS
manager/M
manageress/S
managerial
manana/MS
manatee/SM
mandala/SM
mandamus/MS
mandarin/MS
mandate/DSMG
mandatory
mandible/MS
mandibular
mandolin/MS
mandrake/MS
mandrel/SM
mandrill/MS
mane/MDS
manege/M
maneuver/MDGSBJ
maneuverability/M
manful/Y
manga/M
manganese/M
mange/DRMZ
manger/M
mangetout/S
manginess/M
mangle/MZGDRS
mango/M
mangoes
mangrove/MS
mangy/TRP
manhandle/GDS
manhole/SM
manhood/M
manhunt/SM
mania/SM
maniac/MS
maniacal/Y
manic/SM
manically
manicure/MGDS
manicurist/MS
manifest/MDYSG
manifestation/SM
manifesto/SM
manifold/GMDS
manikin/SM
manila/M
manioc/MS
manipulable
manipulate/XGNVDS
manipulation/M
manipulative/Y
manipulator/MS
mankind/M
manky
manlike
manliness/M
manly/URT
manna/M
manned/U
mannequin/SM
manner/MDYS
mannerism/SM
mannerly/U
manning/U
mannish/YP
mannishness/M
manometer/SM
manor/SM
manorial
manpower/M
manque
manqu�
mans
mansard/MS
manse/SXMN
manservant/M
mansion/M
manslaughter/M
manta/SM
mantel/MS
mantelpiece/SM
mantelshelf
mantelshelves
mantes
manticore
mantilla/SM
mantis/MS
mantissa/SM
mantle's
mantle/EGDS
mantra/MS
manual/MYS
manufacture/DRSMZG
manufacturer/M
manufacturing/M
manumission/SM
manumit/S
manumitted
manumitting
manure/MGDS
manuscript/MS
many/M
man�ge/M
map's
map/AS
maple/SM
mapmaker/SM
mapped/A
mapper/MS
mapping/S
mar/S
marabou/MS
marabout/SM
maraca/MS
maraschino/MS
marathon/SMRZ
marathoner/M
maraud/ZGDRS
marauder/M
marble/MGDS
marbleize/GDS
marbling/M
march/ZGMDRS
marcher/M
marchioness/MS
mare/MS
margarine/M
margarita/MS
marge
margin/MS
marginal/YS
marginalia/M
marginalization/M
marginalize/GDS
maria/M
mariachi/MS
marigold/MS
marijuana/M
marimba/SM
marina/MS
marinade/DSMG
marinara/M
marinate/DSGN
marination/M
marine/MZRS
mariner/M
marionette/MS
marital/Y
maritime
marjoram/M
mark/AMDSG
markdown/SM
marked/U
markedly
marker/MS
market/MDRZGBS
marketability/M
marketable/U
marketeer/SM
marketer/M
marketing/M
marketplace/SM
marking/SM
markka/M
markkaa
marksman/M
marksmanship/M
marksmen
markup/MS
marl/M
marlin/MS
marlinespike/SM
marmalade/M
marmoreal
marmoset/SM
marmot/MS
maroon/MDGS
marque/MS
marquee/SM
marquess/MS
marquetry/M
marquis/MS
marquise/M
marquisette/M
marred/U
marriage/ASM
marriageability/M
marriageable
married/SM
marring
marrow/MS
marry/AGDS
marsh/MS
marshal/SMDG
marshland/SM
marshmallow/SM
marshy/RT
marsupial/MS
mart/MNSX
marten/M
martensite
martial/Y
martian/S
martin/MS
martinet/MS
martingale/MS
martini/SM
martyr/MDGS
martyrdom/M
marvel/MDGS
marvelous/Y
marzipan/M
masc
mascara/GMDS
mascot/MS
masculine/SM
masculinity/M
maser/SM
mash/MDRSZG
masher/M
mashup/MS
mask's
mask/UDSG
masker/MS
masochism/M
masochist/SM
masochistic
masochistically
mason/SM
masonic
masonry/M
masque/MS
masquerade/DRSMZG
masquerader/M
mass/MDSGV
massacre/MGDS
massage/DSMG
masse
masseur/SM
masseuse/MS
massif/MS
massive/PY
massiveness/M
massless
mast/MDS
mastectomy/SM
master's
master/ADGS
masterclass/S
masterful/Y
masterly
mastermind/SGMD
masterpiece/MS
masterstroke/SM
masterwork/MS
mastery/M
masthead/MS
mastic/M
masticate/GNDS
mastication/M
mastiff/SM
mastitis
mastodon/SM
mastoid/SM
masturbate/GNDS
masturbation/M
masturbatory
mat/SZGMDR
matador/SM
match/AMS
matchbook/SM
matchbox/MS
matched/U
matching
matchless
matchlock/SM
matchmaker/MS
matchmaking/M
matchstick/MS
matchwood/M
mate/MS
material/SMY
materialism/M
materialist/SM
materialistic
materialistically
materialization/M
materialize/DSG
materiel/M
maternal/Y
maternity/M
matey/S
mathematical/Y
mathematician/SM
mathematics/M
matinee/SM
mating/M
matins/M
matin�e/SM
matriarch/M
matriarchal
matriarchs
matriarchy/SM
matrices
matricidal
matricide/MS
matriculate/DSGN
matriculation/M
matrimonial
matrimony/M
matrix/M
matron/MYS
matte/DRSMZG
matter/MDG
matting/M
mattock/SM
mattress/MS
maturate/GNDS
maturation/M
mature/YTGDRS
maturity/SM
matzo/SMH
matzoh/M
matzohs
matzot
mat�riel/M
maudlin
maul/MDRSZG
mauler/M
maunder/SDG
mausoleum/SM
mauve/M
maven/SM
maverick/SM
maw/SM
mawkish/PY
mawkishness/M
max/GMDS
maxi/MS
maxilla/M
maxillae
maxillary
maxim/SM
maxima
maximal/Y
maximalist/SM
maximization/M
maximize/GDS
maximum/SM
may/M
maybe/SM
mayday/MS
mayflower/MS
mayfly/SM
mayhem/M
mayn't
mayo/M
mayonnaise/M
mayor/SM
mayoral
mayoralty/M
mayoress/MS
maypole/SM
mayst
maze/MS
mazurka/MS
ma�ana/M
mdse
me/DSH
mea/S
mead/M
meadow/MS
meadowlark/MS
meager/PY
meagerness/M
meal/MS
mealiness/M
mealtime/SM
mealy/TPR
mealybug/SM
mealymouthed
mean/MRYJPSTG
meander/SMDJG
meanderings/M
meanie/M
meaning/M
meaningful/PY
meaningfulness/M
meaningless/YP
meaninglessness/M
meanness/M
meant/U
meantime/M
meanwhile/M
meany/SM
measles/M
measly/RT
measurably
measure/LDRSBMG
measured/U
measureless
measurement/MS
meat/MS
meatball/MS
meathead/MS
meatiness/M
meatless
meatloaf/M
meatloaves
meatpacking/M
meaty/TPR
mebibyte/SM
mecca/SM
mechanic/MS
mechanical/Y
mechanics/M
mechanism/SM
mechanistic
mechanistically
mechanization/M
mechanize/DSG
medal/SM
medalist/MS
medallion/SM
meddle/ZGDRS
meddler/M
meddlesome
media/SM
medial/AY
median/MS
mediate/ADSGN
mediated/U
mediation/AM
mediator/MS
medic/SM
medicaid/M
medical/SMY
medicament/M
medicare/M
medicate/GNXDS
medication/M
medicinal/Y
medicine/MS
medico/MS
medieval
medievalist/MS
mediocre
mediocrity/SM
meditate/DSGNVX
meditation/M
meditative/Y
medium/MS
medley/MS
medulla/SM
medusa
medusae
meed/M
meek/RYPT
meekness/M
meerkats
meerschaum/SM
meet/MJSG
meeting/M
meetinghouse/SM
meetup/MS
meg/S
mega
megabit/SM
megabucks/M
megabyte/MS
megachurch/MS
megacycle/SM
megadeath/M
megadeaths
megagram/S
megahertz/M
megajoule/MS
megalith/M
megalithic
megaliths
megalomania/M
megalomaniac/SM
megalopolis/MS
megameter/S
megapascal/S
megaphone/DSMG
megapixel/SM
megastar/S
megaton/SM
megawatt/MS
meh
meiosis/M
meiotic
melamine/M
melancholia/M
melancholic/S
melancholy/M
melange/MS
melanin/M
melanoma/SM
meld/MDSG
melee/SM
meliorate/GNVDS
melioration/M
mellifluous/PY
mellifluousness/M
mellow/PTGDRYS
mellowness/M
melodic
melodically
melodious/YP
melodiousness/M
melodrama/MS
melodramatic/S
melodramatically
melodramatics/M
melody/SM
melon/SM
melt's
melt/ADSG
meltdown/SM
member's
member/EAS
membership/SM
membrane/SM
membranous
meme/MS
memento/MS
memo/MS
memoir/MS
memorabilia/M
memorability/M
memorable/U
memorably
memorandum/MS
memorial/SM
memorialize/DSG
memorization/M
memorize/DSG
memory/SM
memsahib/S
men/M
menace/MGDS
menacing/Y
menage/MS
menagerie/MS
mend/MDRSZG
mendacious/Y
mendacity/M
mendelevium/M
mender/M
mendicancy/M
mendicant/SM
mending/M
menfolk/MS
menfolks/M
menhaden/M
menial/MYS
meningeal
meninges
meningitis/M
meninx/M
menisci
meniscus/M
menopausal
menopause/M
menorah/M
menorahs
mensch/MS
menservants
menses/M
menstrual
menstruate/GNDS
menstruation/M
mensurable
mensuration/M
menswear/M
mental/Y
mentalist/SM
mentality/SM
menthol/M
mentholated
mention/GSMD
mentioned/U
mentor/MDSG
mentorship
menu/MS
meow/MDSG
mercantile
mercantilism/M
mercenary/SM
mercer/MS
mercerize/GDS
merchandise/MZGDRS
merchandiser/M
merchandising/M
merchant/GMBS
merchantability
merchantman/M
merchantmen
merciful/UY
merciless/PY
mercilessness/M
mercurial/Y
mercuric
mercury/M
mercy/SM
mere/MYTS
meretricious/YP
meretriciousness/M
merganser/MS
merge/DRSZG
merger/M
meridian/MS
meringue/MS
merino/MS
merit/CSM
merited/U
meriting
meritless
meritocracy/SM
meritocratic
meritorious/PY
meritoriousness/M
mermaid/SM
merman/M
mermen
merrily
merriment/M
merriness/M
merry/TRP
merrymaker/MS
merrymaking/M
mesa/MS
mescal/MS
mescalin
mescaline/M
mesdames
mesdemoiselles
mesh/MDSG
mesmeric
mesmerism/M
mesmerize/ZGDRS
mesmerizer/M
mesomorph/M
mesomorphs
meson/SM
mesosphere/SM
mesothelioma/M
mesquite/SM
mess/MDSG
message/MGDS
messeigneurs
messenger/SM
messiah/M
messiahs
messianic
messieurs
messily
messiness/M
messmate/SM
messy/PTR
mestizo/MS
met
meta
metabolic
metabolically
metabolism/SM
metabolite/SM
metabolize/DSG
metacarpal/SM
metacarpi
metacarpus/M
metadata/M
metal/SMD
metalanguage/MS
metallic
metallurgic
metallurgical
metallurgist/MS
metallurgy/M
metalwork/MRZG
metalworker/M
metalworking/M
metamorphic
metamorphism/M
metamorphose/GDS
metamorphosis/M
metaphor/MS
metaphoric
metaphorical/Y
metaphysical/Y
metaphysics/M
metastases
metastasis/M
metastasize/DSG
metastatic
metatarsal/MS
metatarsi
metatarsus/M
metatheses
metathesis/M
mete/MZGDRS
metempsychoses
metempsychosis/M
meteor/MS
meteoric
meteorically
meteorite/SM
meteoroid/SM
meteorologic
meteorological
meteorologist/SM
meteorology/M
meter/GMD
metformin
methadone/M
methamphetamine/M
methane/M
methanol/M
methinks
method/MS
methodical/YP
methodicalness/M
methodological/Y
methodology/SM
methotrexate
methought
methoxy
meths
methyl/M
meticulous/YP
meticulousness/M
metier/MS
metric/S
metrical/Y
metricate/GNDS
metrication/M
metricize/GDS
metro/SM
metronome/MS
metropolis/MS
metropolitan
mettle/M
mettlesome
mew/SGMD
mewl/DSG
mews/M
mezzanine/MS
mezzo/SM
mfg
mfr/S
mg
mgr
mi/MNX
miasma/MS
mic/S
mica/M
mice
mick/S
mickey/MS
micro/SM
microaggression/SM
microbe/MS
microbial
microbiological
microbiologist/MS
microbiology/M
microbrewery/SM
microchip/MS
microcircuit/SM
microcode
microcomputer/MS
microcosm/MS
microcosmic
microcredit
microdot/SM
microeconomics/M
microelectronic/S
microelectronics/M
microfiber/MS
microfiche/M
microfilm/GMDS
microfinance
microfloppies
microgram
microgroove/SM
microlight/MS
microloan/MS
micromanage/ZGDRSL
micromanagement/M
micromanager/M
micrometeorite/SM
micrometer/MS
micron/MS
microorganism/MS
micropayment/S
microphone/SM
microplastics
microprocessor/MS
microscope/SM
microscopic
microscopical/Y
microscopy/M
microsecond/MS
microsurgery/M
microwave/DSMGB
microwaveable
mid
midair/M
midday/M
midden/MS
middle/MGS
middlebrow/SM
middleman/M
middlemen
middlemost
middleweight/MS
middy/SM
midfield/RZ
midge/SM
midget/MS
midi/MS
midland/MS
midlife/M
midmost
midnight/M
midpoint/MS
midrib/MS
midriff/MS
midsection/MS
midshipman/M
midshipmen
midships
midsize
midst/M
midstream/M
midsummer/M
midterm/MS
midtown/M
midway/MS
midweek/MS
midwife/MGDS
midwifery/SM
midwinter/M
midwived
midwives
midwiving
midyear/MS
mien/M
miff/DSG
might've
might/M
mightily
mightiness/M
mightn't
mighty/TRP
mignonette/SM
migraine/MS
migrant/MS
migrate/AGDS
migration/SM
migrator/MS
migratory
mikado/MS
mike/MGDS
mil/SZMR
milady/SM
milch
mild/MRYTP
mildew/SMDG
mildness/M
mile/MS
mileage/SM
milepost/MS
miler/M
milestone/MS
milf/MS
milieu/SM
militancy/M
militant/MYS
militarily
militarism/M
militarist/SM
militaristic
militarization/CM
militarize/CDSG
military/M
militate/GDS
militia/SM
militiaman/M
militiamen
milk/MDRSZG
milker/M
milkiness/M
milkmaid/MS
milkman/M
milkmen
milkshake/SM
milksop/MS
milkweed/SM
milky/RTP
mill/MDRSZGJ
millage/M
millennia
millennial/MS
millennium/MS
miller/M
millet/M
milliard/MS
millibar/MS
milligram/MS
milliliter/MS
millimeter/MS
milliner/MS
millinery/M
milling/M
million/HSM
millionaire/SM
millionairess/S
millionth/M
millionths
millipede/SM
millisecond/SM
millisievert/S
millpond/SM
millrace/SM
millstone/SM
millstream/MS
millwright/SM
milometer/S
milquetoast/SM
milt/MDSG
mime/MGDS
mimeograph/GMD
mimeographs
mimetic
mimic/SM
mimicked
mimicker/SM
mimicking
mimicry/SM
mimosa/SM
min/S
minaret/MS
minatory
mince/DRSMZG
mincemeat/M
mincer/M
mind's
mind/ADRSZG
mindbogglingly
minded/P
mindful/YP
mindfulness/M
mindless/YP
mindlessness/M
mindset/MS
mine/MZGNDRSX
minefield/SM
miner/M
mineral/MS
mineralogical
mineralogist/MS
mineralogy/M
minestrone/M
minesweeper/SM
mingle/DSG
mingy
mini/MS
miniature/MS
miniaturist/MS
miniaturization/M
miniaturize/GDS
minibar/S
minibike/SM
minibus/MS
minicab/S
minicam/MS
minicomputer/SM
minifloppies
minim/SM
minima
minimal/Y
minimalism/M
minimalist/MS
minimization/M
minimize/DSG
minimum/MS
mining/M
minion/M
miniseries/M
miniskirt/MS
minister/SGMD
ministerial
ministrant/MS
ministration/MS
ministry/SM
minivan/MS
mink/MS
minnesinger/MS
minnow/SM
minor/SMDG
minority/SM
minoxidil/M
minster/MS
minstrel/SM
minstrelsy/M
mint/MDRSZG
mintage/M
minter/M
minty/RT
minuend/MS
minuet/SM
minus/MS
minuscule/MS
minute/PDRSMYTG
minuteman/M
minutemen
minuteness/M
minutia/M
minutiae
minx/MS
miracle/MS
miraculous/Y
mirage/SM
mire/MGDS
mirror/GSMD
mirth/M
mirthful/PY
mirthfulness/M
mirthless/Y
miry/RT
misaddress/DSG
misadventure/MS
misaligned
misalignment/M
misalliance/MS
misandrist/MS
misandry
misanthrope/SM
misanthropic
misanthropically
misanthropist/MS
misanthropy/M
misapplication/M
misapply/DSGNX
misapprehend/GSD
misapprehension/MS
misappropriate/XDSGN
misappropriation/M
misbegotten
misbehave/GDS
misbehavior/M
misc
miscalculate/DSXGN
miscalculation/M
miscall/DSG
miscarriage/MS
miscarry/GDS
miscast/SG
miscegenation/M
miscellanea
miscellaneous/Y
miscellany/SM
mischance/SM
mischaracterization/S
mischaracterize/GD
mischief/M
mischievous/YP
mischievousness/M
miscibility/M
miscible
misclassify/DGXN
miscommunication/S
misconceive/GDS
misconception/SM
misconduct/MDGS
misconfiguration
misconstruction/MS
misconstrue/GDS
miscount/MDSG
miscreant/SM
miscue/DSMG
misdeal/GMS
misdealt
misdeed/MS
misdemeanor/MS
misdiagnose/GDS
misdiagnosis/M
misdid
misdirect/SDG
misdirection/M
misdo/JG
misdoes
misdoing/M
misdone
mise/CKS
miser/SBMY
miserableness/M
miserably
miserliness/M
misery/SM
misfeasance/M
misfeature/S
misfile/GDS
misfire/MGDS
misfit/SM
misfitted
misfitting
misfortune/SM
misgender/SDG
misgiving/MS
misgovern/SDGL
misgovernment/M
misguidance/M
misguide/DSG
misguided/Y
mishandle/DSG
mishap/SM
mishear/GS
misheard
mishit/S
mishitting
mishmash/MS
misidentify/GDS
misinform/DGS
misinformation/M
misinterpret/SGD
misinterpretation/SM
misjudge/LDSG
misjudgement/SM
misjudgment/SM
mislabel/GSD
mislaid
mislay/GS
mislead/GS
misleading/Y
misled
mismanage/LGDS
mismanagement/M
mismatch/GMDS
misname/GDS
misnomer/MS
misogamist/MS
misogamy/M
misogynist/SM
misogynistic
misogynous
misogyny/M
misplace/GLDS
misplacement/M
misplay/GMDS
misprint/GMDS
misprision/M
mispronounce/DSG
mispronunciation/SM
misquotation/MS
misquote/MGDS
misread/GJS
misreading/M
misremember/GDS
misreport/MDGS
misrepresent/GDS
misrepresentation/MS
misrule/MGDS
miss's
miss/EDSGV
missal/ESM
missed/U
misshape/GDS
misshapen
missile/MS
missileer
missilery/M
mission/AMS
missionary/SM
missioner/SM
missis/MS
missive/MS
misspeak/GS
misspell/GDJS
misspelling/M
misspend/GS
misspent
misspoke
misspoken
misstate/GDSL
misstatement/SM
misstep/MS
missus/MS
mist's
mist/CDRSZG
mistakable/U
mistake/BMGS
mistaken/Y
mister's
mistily
mistime/GDS
mistiness/M
mistletoe/M
mistook
mistral/MS
mistranslated
mistreat/LDGS
mistreatment/M
mistress/MS
mistrial/MS
mistrust/MDSG
mistrustful/Y
misty/PRT
mistype/GDS
misunderstand/SGJ
misunderstanding/M
misunderstood
misuse/DSMG
mite/MZRS
miter/MDG
mitigable
mitigate/XDSGN
mitigated/U
mitigation/M
mitochondria
mitochondrial
mitochondrion
mitoses
mitosis/M
mitotic
mitral
mitt/MNSX
mitten/M
mitzvah
mix/ZGMDRSB
mixed/U
mixer/M
mixture/SM
mizzen/MS
mizzenmast/SM
mkay
mks
ml
mm
mnemonic/MS
mnemonically
mo/CKHS
moan/MDRSZG
moaner/M
moat/MDS
mob's
mob/CS
mobbed/C
mobbing/C
mobile/MS
mobility/M
mobilization/CM
mobilizations
mobilize/CDSG
mobilizer/SM
mobster/SM
moccasin/SM
mocha/SM
mock/DRSZG
mocker/M
mockery/SM
mocking/Y
mockingbird/SM
mocktail/S
mockumentary/S
mod/STM
modal/SM
modality/S
modded
modding
mode/MS
model/ZGSJMDR
modeler/M
modeling/M
modeller/MS
modelling/MS
modem/SM
moderate/MYGNPDS
moderateness/M
moderation/M
moderator/SM
modern/MYPS
modernism/M
modernist/SM
modernistic
modernity/M
modernization/M
modernize/DRSZG
modernizer/M
modernness/M
modest/Y
modesty/M
modicum/SM
modifiable
modification/M
modified/U
modifier/M
modify/DRSXZGN
modish/YP
modishness/M
modular
modularization
modulate/CGNDS
modulation/CM
modulations
modulator/MS
module/MS
modulo
modulus
moggy
mogul/SM
mohair/M
moi
moiety/SM
moil/MDSG
moire/SM
moist/XTPNRY
moisten/DRZG
moistener/M
moistness/M
moisture/M
moisturize/ZGDRS
moisturizer/M
mojo/S
molar/SM
molasses/M
mold/MDRJSZG
moldboard/SM
molder/GMD
moldiness/M
molding/M
moldy/TPR
mole/MS
molecular
molecularity/M
molecule/SM
molehill/SM
moleskin/M
molest/DRZGS
molestation/M
molested/U
molester/M
moll/MS
mollification/M
mollify/DSNG
mollusc/SM
molluscan/SM
mollusk/SM
molluskan/S
molly/SM
mollycoddle/DSMG
molt/MDNRSZG
molter/M
molybdenum/M
mom/SM
moment/MS
momenta
momentarily
momentariness/M
momentary/P
momentous/PY
momentousness/M
momentum/M
mommy/SM
monad
monarch/M
monarchic
monarchical
monarchism/M
monarchist/MS
monarchistic
monarchs
monarchy/SM
monastery/SM
monastic/MS
monastical/Y
monasticism/M
monaural
monetarily
monetarism/M
monetarist/MS
monetary
monetization/C
monetize/CGDS
money/SMD
moneybag/MS
moneybox/S
moneygrubber/SM
moneygrubbing/M
moneylender/SM
moneymaker/SM
moneymaking/M
monger/MDGS
mongol/S
mongolism/M
mongoloid/MS
mongoose/MS
mongrel/SM
monied
monies
moniker/SM
monism/M
monist/MS
monition/SM
monitor/SMDG
monitory
monk/MS
monkey/MDGS
monkeyshine/SM
monkish
monkshood/SM
mono/M
monochromatic
monochrome/MS
monocle/DSM
monoclonal
monocotyledon/SM
monocotyledonous
monocular
monodic
monodist/SM
monody/SM
monofilament
monogamist/MS
monogamous/Y
monogamy/M
monogram/SM
monogrammed
monogramming
monograph/M
monographs
monolingual/MS
monolith/M
monolithic
monoliths
monologist/SM
monologue/SM
monologuist/SM
monomania/M
monomaniac/MS
monomaniacal
monomer/SM
monomial
mononucleosis/M
monophonic
monoplane/SM
monopolist/SM
monopolistic
monopolization/M
monopolize/DRSZG
monopolizer/M
monopoly/SM
monorail/MS
monosyllabic
monosyllable/MS
monotheism/M
monotheist/SM
monotheistic
monotone/MS
monotonic
monotonically
monotonous/PY
monotonousness/M
monotony/M
monounsaturated
monoxide/MS
monozygotic
monozygous
monseigneur/M
monsieur/M
monsignor/SM
monsoon/SM
monsoonal
monster/SM
monstrance/ASM
monstrosity/SM
monstrous/Y
montage/SM
month/MY
monthly/SM
months
monument/MS
monumental/Y
moo/SGMD
mooch/ZGMDRS
moocher/M
mood/MS
moodily
moodiness/M
moody/TPR
moon/MDSG
moonbeam/MS
moonless
moonlight/SMDRZG
moonlighter/M
moonlighting/M
moonlit
moonscape/SM
moonshine/MZRS
moonshiner/M
moonshot/MS
moonstone/MS
moonstruck
moonwalk/MS
moor/MDJSG
moorhen/S
mooring/M
moorland/MS
moose/M
moot/DSG
mop/SZGMDR
mope/MS
moped/SM
moper/M
mopey
mopier
mopiest
mopish
mopped
moppet/MS
mopping
moraine/SM
moral/SMY
morale/M
moralism
moralist/MS
moralistic
moralistically
moralities
morality/UM
moralization/CM
moralize/CGDS
moralizer/MS
morass/MS
moratorium/SM
moray/SM
morbid/YP
morbidity/M
morbidness/M
mordancy/M
mordant/SMY
more/MS
moreish
morel/SM
moreover
mores/M
morgue/MS
moribund
morn/MJSG
morning/M
morocco/M
moron/SM
moronic
moronically
morose/YP
moroseness/M
morph/GD
morpheme/MS
morphemic
morphia/M
morphine/M
morphing/M
morphological
morphology/M
morphs
morrow/MS
morsel/MS
mortal/MYS
mortality/M
mortar/MDSG
mortarboard/SM
mortgage's
mortgage/AGDS
mortgagee/MS
mortgagor/MS
mortician/MS
mortification/M
mortify/NGDS
mortise/DSMG
mortuary/SM
mosaic/MS
mosey/SGD
mosh/DSG
mosque/MS
mosquito/SM
mosquitoes
moss/MS
mossback/SM
mossy/TR
most/MY
mot/SM
mote's
mote/KCXSVN
motel/SM
motet/SM
moth/M
mothball/GMDS
mother/MDYSG
motherboard/SM
motherfucker/MS!
motherfucking/!
motherhood/M
motherland/MS
motherless
motherliness/M
moths
motif/SM
motile/S
motility/M
motion/KCM
motioned
motioning
motionless/YP
motionlessness/M
motivate/CDSG
motivated/U
motivation/SM
motivational
motivator/SM
motive/MS
motiveless
motley/MS
motlier
motliest
motocross/MS
motor/SGMD
motorbike/MGDS
motorboat/MS
motorcade/MS
motorcar/SM
motorcycle/DSMG
motorcyclist/MS
motorist/SM
motorization/M
motorize/DSG
motorman/M
motormen
motormouth/M
motormouths
motorsport/SM
motorway/SM
mottle/GDS
motto/M
mottoes
moue/MS
mound/SGMD
mount/EASGMD
mountable
mountain/SM
mountaineer/SMDG
mountaineering/M
mountainous
mountainside/SM
mountaintop/SM
mountebank/MS
mounted/U
mounter/MS
mounting/SM
mourn/SZGDR
mourned/U
mourner/M
mournful/YP
mournfulness/M
mourning/M
mouse/DRSMZG
mouser/M
mousetrap/SM
mousetrapped
mousetrapping
mousey
mousiness/M
moussaka/S
mousse/MGDS
mousy/PTR
mouth/GMD
mouthfeel
mouthful/MS
mouthiness/M
mouthpiece/MS
mouths
mouthwash/MS
mouthwatering
mouthy/PTR
mouton/M
movable/SM
movant
move/AMZGDRSB
moveable/SM
moved/U
movement/SM
movent
mover/AM
movie/SM
moviegoer/SM
moving/Y
mow/SZGMDR
mower/M
moxie/M
mozzarella/M
mp
mpg
mph
mt
mtg
mtge
mu/SM
much/M
mucilage/M
mucilaginous
muck/MDSG
muckrake/DRSZG
muckraker/M
mucky/TR
mucous
mucus/M
mud/M
muddily
muddiness/M
muddle/MGDS
muddleheaded
muddy/PTGDRS
mudflap/S
mudflat/MS
mudguard/SM
mudpack/S
mudroom/MS
mudslide/MS
mudslinger/SM
mudslinging/M
muenster/M
muesli
muezzin/MS
muff/MDSG
muffin/MS
muffle/ZGDRS
muffler/M
mufti/SM
mug/SM
mugful/MS
mugged
mugger/MS
mugginess/M
mugging/MS
muggins
muggle/MS
muggy/PTR
mugshot/MS
mugwump/MS
mujaheddin
mujahedin/M
mukluk/MS
mulatto/M
mulattoes
mulberry/SM
mulch/GMDS
mulct/SGMD
mule/MS
muleskinner/MS
muleteer/MS
mulish/PY
mulishness/M
mull/DSG
mullah/M
mullahs
mullein/M
mullet/MS
mulligan/SM
mulligatawny/M
mullion/SMD
multi
multicast
multicellular
multichannel
multicolored
multicultural
multiculturalism/M
multidimensional
multidisciplinary
multifaceted
multifamily
multifarious/PY
multifariousness/M
multiform
multigrain
multilateral/Y
multilayered
multilevel
multilingual
multilingualism/M
multimedia/M
multimillionaire/SM
multinational/SM
multipart
multiparty
multiplayer/M
multiple/MS
multiplex/ZGMDRS
multiplexer/M
multiplicand/MS
multiplication/M
multiplicative
multiplicity/SM
multiplier/M
multiply/NZGDRSX
multiprocessing
multiprocessor/SM
multipurpose
multiracial
multistage
multistory
multitask/GS
multitasking/M
multitude/SM
multitudinous
multivariable
multivariate
multiverse/SM
multivitamin/MS
multiyear
mum
mumble/MZGDRS
mumbler/M
mumbletypeg/M
mummer/MS
mummery/M
mummification/M
mummify/GNDS
mummy/SM
mumps/M
mun
munch/GDS
munchie/S
munchies/M
munchkin/SM
mundane/SY
mung/DSG
municipal/SMY
municipality/SM
munificence/M
munificent/Y
munition/MDGS
mural/SM
muralist/SM
murder/ZGMDRS
murderer/M
murderess/MS
murderous/Y
murine
murk/MS
murkily
murkiness/M
murky/PTR
murmur/ZGJMDRS
murmurer/M
murmuring/M
murmurous
murrain/M
muscat/MS
muscatel/SM
muscle/MGDS
musclebound
muscleman
musclemen
muscly
muscular/Y
muscularity/M
musculature/M
musculoskeletal
musculus
muse/MGDSJ
musette/MS
museum/MS
mush/MDRSZG
mushiness/M
mushroom/GSMD
mushy/PTR
music/SM
musical/MYS
musicale/MS
musicality/M
musician/SMY
musicianship/M
musicological
musicologist/MS
musicology/M
musing/MY
musk/M
muskeg/MS
muskellunge/MS
musket/MS
musketeer/MS
musketry/M
muskie/M
muskiness/M
muskmelon/SM
muskox/MN
muskrat/MS
musky/PTRS
muslin/M
muss/MDSG
mussel/MS
mussy/TR
must've
must/MRSZ
mustache/MDS
mustachio/SMD
mustang/MS
mustard/M
muster/GMD
mustily
mustiness/M
mustn't
musty/PTR
mutability/M
mutably
mutagen/MS
mutagenic
mutant/MS
mutate/XGNVDS
mutation/M
mutational
mute/MYTGDRSPB
muteness/M
mutilate/DSGNX
mutilation/M
mutilator/SM
mutineer/SM
mutinous/Y
mutiny/GDSM
mutt/MS
mutter/ZGJMDRS
mutterer/M
muttering/M
mutton/M
muttonchops/M
muttony
mutual/Y
mutuality/M
muumuu/MS
muzak
muzzily
muzzle/DSMG
muzzy/P
my
mycologist/SM
mycology/M
myelitis/M
myna/MS
mynah/MS
myocardial
myocardium
myopia/M
myopic
myopically
myriad/SM
myrmidon/MS
myrrh/M
myrtle/SM
mys
myself
mysterious/PY
mysteriousness/M
mystery/SM
mystic/SM
mystical/Y
mysticism/M
mystification/CM
mystify/CDSGN
mystique/M
myth/M
mythic
mythical
mythological
mythologist/SM
mythologize/DSG
mythology/SM
myths
myxomatosis
m�tier/MS
m�l�e/MS
n/IKTH
naan/S
nab/S
nabbed
nabbing
nabob/SM
nacelle/SM
nacho/SM
nacre/M
nacreous
nadir/SM
nae
naff/RT
nag/SM
nagged
nagger/MS
nagging
nagware
nah
naiad/SM
naif/MS
nail/MDSG
nailbrush/MS
naive/RYT
naivete/M
naivety/M
naivet�/M
naked/PY
nakedness/M
name's
name/AGDS
nameable/U
named/U
nameless/Y
namely
nameplate/MS
namesake/SM
namespace/SM
nanny/SM
nano
nanobot/S
nanometer/S
nanosecond/SM
nanotechnology/SM
nanotube
nap/SM
napalm/MDSG
nape/MS
naphtha/M
naphthalene/M
napkin/MS
napless
napoleon/SM
napped
napper/MS
napping
nappy/TRSM
narc/MS
narcissism/M
narcissist/MS
narcissistic
narcissus/M
narcolepsy/M
narcoleptic
narcoses
narcosis/M
narcotic/SM
narcotization/M
narcotize/GDS
nark
narky
narrate/GNVDSX
narration/M
narrative/SM
narrator/SM
narrow/PTGMDRYS
narrowness/M
narwhal/MS
nary
nasal/SMY
nasality/M
nasalization/M
nasalize/DSG
nascence/AM
nascent/A
nastily
nastiness/M
nasturtium/SM
nasty/PTR
natal
natch
nation/MS
national/MYS
nationalism/M
nationalist/SM
nationalistic
nationalistically
nationality/SM
nationalization/MS
nationalize/CDSG
nationhood/M
nationwide
native/MYS
nativity/SM
natl
natter/GMDS
nattily
nattiness/M
natty/PTR
natural's
natural/UPY
naturalism/M
naturalist/SM
naturalistic
naturalization/M
naturalize/DSG
naturalness/UM
naturals
nature's
nature/CS
naturism
naturist/S
naught/MS
naughtily
naughtiness/M
naughty/PTR
nausea/M
nauseam
nauseate/GDS
nauseating/Y
nauseous/PY
nauseousness/M
nautical/Y
nautilus/MS
naval
nave/MS
navel/SM
navigability/M
navigable
navigate/DSGN
navigation/M
navigational
navigator/MS
navvy/S
navy/SM
nay/SM
naysayer/MS
na�ve/RYT
na�vety/M
na�vet�/M
ne'er
neanderthal/MS
neap/MS
near/DRYSPTG
nearby
nearness/M
nearshore
nearside
nearsighted/YP
nearsightedness/M
neat/NRYPXT
neaten/GD
neath
neatness/M
neato
nebula/M
nebulae
nebular
nebulous/PY
nebulousness/M
necessarily/U
necessary/SM
necessitate/DSG
necessitous
necessity/SM
neck/MDSG
neckband/S
neckerchief/MS
necking/M
necklace/MGDSJ
neckline/MS
necktie/MS
necrology/M
necromancer/SM
necromancy/M
necrophilia
necrophiliac/S
necropolis/MS
necroses
necrosis/M
necrotic
nectar/M
nectarine/MS
nee
need/MDSG
needed/U
needful/Y
neediness/M
needle/MGDS
needlepoint/M
needless/YP
needlessness/M
needlewoman/M
needlewomen
needlework/M
needn't
needy/PTR
nefarious/YP
nefariousness/M
neg
negate/DSGNVX
negation/M
negative/MYGPDS
negativeness/M
negativism/M
negativity/M
neglect/SGMD
neglectful/YP
neglectfulness/M
negligee/MS
negligence/M
negligent/Y
negligible
negligibly
negotiability/M
negotiable/A
negotiate/ADSGN
negotiation/AM
negotiations
negotiator/MS
negritude/M
negro
negroid
neigh/MDG
neighbor/SMDYG
neighborhood/SM
neighborliness/M
neighs
neither
nelson/SM
nematode/SM
nemeses
nemesis/M
neoadjuvant
neoclassic
neoclassical
neoclassicism/M
neocolonialism/M
neocolonialist/MS
neocon/SM
neoconservative/SM
neocortex
neodymium/M
neolithic
neologism/SM
neon/M
neonatal
neonate/MS
neophilia
neophyte/MS
neoplasm/MS
neoplastic
neoprene/M
nepenthe/M
nephew/SM
nephrite/M
nephritic
nephritis/M
nephropathy
nepotism/M
nepotist/SM
nepotistic
neptunium/M
nerd/MS
nerdy/RT
nerve's
nerve/UDSG
nerveless/YP
nervelessness/M
nerviness/M
nervous/YP
nervousness/M
nervy/TPR
nest/MDSG
nestle/GJDS
nestling/M
net/SM
netball
netbook/MS
nether
nethermost
netherworld/M
netiquette/S
netted
netter/S
netting/M
nettle/MGDS
nettlesome
network/SGMD
networking/M
neural/Y
neuralgia/M
neuralgic
neurasthenia/M
neurasthenic/MS
neuritic/MS
neuritis/M
neurocysticercoses
neurocysticercosis
neurological/Y
neurologist/SM
neurology/M
neuron/MS
neuronal
neurophysiology's
neuroscience/MS
neuroscientist/MS
neuroses
neurosis/M
neurosurgeon/MS
neurosurgery/M
neurosurgical
neurotic/MS
neurotically
neuroticism
neurotoxin/S
neurotransmitter/SM
neut
neuter/MDGS
neutral/SMY
neutralism/M
neutralist/SM
neutrality/M
neutralization/M
neutralize/DRSZG
neutralizer/M
neutrino/SM
neutron/SM
never
nevermore
nevertheless
nevi
nevus/M
new/STMRYP
newbie/MS
newborn/SM
newcomer/SM
newel/SM
newfangled
newfound
newish
newline/S
newlywed/SM
newness/M
news/M
newsagent/S
newsboy/SM
newscast/SMRZ
newscaster/M
newsdealer/SM
newsflash/S
newsgirl/SM
newsgroup/MS
newshound/S
newsletter/MS
newsman/M
newsmen
newspaper/MS
newspaperman/M
newspapermen
newspaperwoman/M
newspaperwomen
newspeak
newsprint/M
newsreader/S
newsreel/MS
newsroom/MS
newsstand/SM
newsweekly/SM
newswires
newswoman/M
newswomen
newsworthiness/M
newsworthy/P
newsy/TR
newt/MS
newton/MS
next/M
nexus/MS
niacin/M
nib/SM
nibble/MZGDRS
nibbler/M
nice/PYTR
niceness/M
nicety/SM
niche/SM
nick/MDRSZG
nickel/MS
nickelodeon/SM
nicker/MDG
nickle/S
nickname/DSMG
nicotine/M
niece/SM
nifedipine
niff
niffy
nifty/TR
nigga/MS!
niggard/SMY
niggardliness/M
niggaz/!
nigger/SM!
niggle/MZGDRS
niggler/M
nigh/RT
night/SMY
nightcap/SM
nightclothes/M
nightclub/SM
nightclubbed
nightclubbing
nightdress/MS
nightfall/M
nightgown/SM
nighthawk/SM
nightie/M
nightingale/SM
nightlife/M
nightlight/S
nightlong
nightmare/SM
nightmarish
nightshade/SM
nightshirt/SM
nightspot/MS
nightstand/SM
nightstick/SM
nighttime/M
nightwatchman
nightwatchmen
nightwear/M
nighty/SM
nihilism/M
nihilist/MS
nihilistic
nil/M
nimbi
nimble/TPR
nimbleness/M
nimbly
nimbus/M
nimby
nimrod/MS
nincompoop/SM
nine/MS
ninepin/MS
ninepins/M
nineteen/SMH
nineteenth/M
nineteenths
ninetieth/M
ninetieths
ninety/HSM
ninja/SM
ninny/SM
ninth/M
ninths
niobium/M
nip/SM
nipped
nipper/MS
nippiness/M
nipping
nipple/MS
nippy/TPR
nirvana/M
nisei/M
nit/SMR
nite/MS
niter/M
nitpick/SZGDR
nitpicker/M
nitpicking/M
nitrate/DSMGN
nitration/M
nitric
nitrification/M
nitrile/S
nitrite/SM
nitro
nitrocellulose/M
nitrogen/M
nitrogenous
nitroglycerin/M
nitroglycerine/M
nitty
nitty-gritty
nitwit/MS
nix/GMDS
no/SM
nob/SY
nobble/GDS
nobelium/M
nobility/M
noble/RSPMT
nobleman/M
noblemen
nobleness/M
noblewoman/M
noblewomen
nobody/SM
nocturnal/Y
nocturne/MS
nod/SM
nodal
nodded
nodding
noddle/MS
noddy
node/MS
nodular
nodule/MS
noel/MS
noes
noggin/MS
nohow
noise/DSMG
noiseless/PY
noiselessness/M
noisemaker/MS
noisily
noisiness/M
noisome
noisy/PTR
nomad/SM
nomadic
nomenclature/MS
nominal/Y
nominate/CASDXVNG
nomination/ACM
nominative/SM
nominator/CSM
nominee/MS
non
nonabrasive
nonabsorbent/SM
nonacademic
nonacceptance/M
nonacid
nonactive/MS
nonaddictive
nonadhesive
nonadjacent
nonadjustable
nonadministrative
nonage/MS
nonagenarian/MS
nonaggression/M
nonalcoholic
nonaligned
nonalignment/M
nonallergic
nonappearance/MS
nonassignable
nonathletic
nonattendance/M
nonautomotive
nonavailability/M
nonbasic
nonbeliever/MS
nonbelligerent/MS
nonbinary
nonbinding
nonbreakable
nonburnable
noncaloric
noncancerous
nonce/M
nonchalance/M
nonchalant/Y
nonchargeable
nonclerical/MS
nonclinical
noncollectable
noncom/MS
noncombat
noncombatant/MS
noncombustible
noncommercial/MS
noncommittal/Y
noncommunicable
noncompeting
noncompetitive
noncompliance/M
noncomplying
noncomprehending
nonconducting
nonconductor/MS
nonconforming
nonconformism
nonconformist/MS
nonconformity/M
nonconsecutive
nonconstructive
noncontagious
noncontinuous
noncontributing
noncontributory
noncontroversial
nonconvertible
noncooperation/M
noncorroding
noncorrosive
noncredit
noncriminal/SM
noncritical
noncrystalline
noncumulative
noncustodial
nondairy
nondeductible/M
nondelivery/SM
nondemocratic
nondenominational
nondepartmental
nondepreciating
nondescript
nondestructive
nondetachable
nondeterminism
nondeterministic
nondisciplinary
nondisclosure/M
nondiscrimination/M
nondiscriminatory
nondramatic
nondrinker/MS
nondrying
none
noneducational
noneffective
nonelastic
nonelectric
nonelectrical
nonempty
nonenforceable
nonentity/SM
nonequivalent/MS
nonessential
nonesuch/MS
nonetheless
nonevent/MS
nonexchangeable
nonexclusive
nonexempt/M
nonexistence/M
nonexistent
nonexplosive/MS
nonfactual
nonfading
nonfat
nonfatal
nonfattening
nonferrous
nonfiction/M
nonfictional
nonflammable
nonflowering
nonfluctuating
nonflying
nonfood/M
nonfreezing
nonfunctional
nongovernmental
nongranular
nonhazardous
nonhereditary
nonhuman
nonidentical
noninclusive
nonindependent
nonindustrial
noninfectious
noninflammatory
noninflationary
noninflected
nonintellectual/MS
noninterchangeable
noninterference/M
nonintervention/M
nonintoxicating
noninvasive
nonirritating
nonissue
nonjudgmental
nonjudicial
nonlegal
nonlethal
nonlinear
nonliterary
nonliving/M
nonmagnetic
nonmalignant
nonmember/MS
nonmetal/SM
nonmetallic
nonmigratory
nonmilitant
nonmilitary
nonnarcotic/SM
nonnative/MS
nonnegative
nonnegotiable
nonnuclear
nonnumerical
nonobjective
nonobligatory
nonobservance/M
nonobservant
nonoccupational
nonoccurence
nonofficial
nonoperational
nonoperative
nonparallel/MS
nonpareil/MS
nonparticipant/MS
nonparticipating
nonpartisan/SM
nonpaying
nonpayment/SM
nonperformance/M
nonperforming
nonperishable
nonperson/MS
nonphysical/Y
nonplus/S
nonplussed
nonplussing
nonpoisonous
nonpolitical
nonpolluting
nonporous
nonpracticing
nonprejudicial
nonprescription
nonproductive
nonprofessional/SM
nonprofit/SMB
nonproliferation/M
nonpublic
nonpunishable
nonracial
nonradioactive
nonrandom
nonreactive
nonreal
nonreciprocal/SM
nonreciprocating
nonrecognition/M
nonrecoverable
nonrecurring
nonredeemable
nonrefillable
nonrefundable
nonreligious
nonrenewable
nonrepresentational
nonresident/MS
nonresidential
nonresidual/M
nonresistance/M
nonresistant
nonrestrictive
nonreturnable/MS
nonrhythmic
nonrigid
nonsalaried
nonscheduled
nonscientific
nonscoring
nonseasonal
nonsectarian
nonsecular
nonsegregated
nonsense/M
nonsensical/Y
nonsensitive
nonsexist
nonsexual
nonskid
nonslip
nonsmoker/SM
nonsmoking
nonsocial
nonspeaking
nonspecialist/MS
nonspecializing
nonspecific
nonspiritual/SM
nonstaining
nonstandard
nonstarter/MS
nonstick
nonstop
nonstrategic
nonstriking
nonstructural
nonsuccessive
nonsupport/GM
nonsurgical
nonsustaining
nonsympathizer/M
nontarnishable
nontaxable
nontechnical
nontenured
nontheatrical
nonthinking
nonthreatening
nontoxic
nontraditional
nontransferable
nontransparent
nontrivial
nontropical
nonuniform
nonunion
nonuser/MS
nonvenomous
nonverbal
nonviable
nonviolence/M
nonviolent/Y
nonvirulent
nonvocal
nonvocational
nonvolatile
nonvoter/MS
nonvoting
nonwhite/MS
nonworking
nonyielding
nonzero
noodle/MGDS
nook/MS
nookie
nooky
noon/M
noonday/M
noontide/M
noontime/M
noose/SM
nope
nor
nor'easter
norm/MS
normal/MY
normalcy/M
normality/M
normalization/M
normalize/DSG
normative
north/ZMR
northbound
northeast/MRZ
northeaster/MY
northeastern
northeastward/S
norther/MY
northerly/SM
northern/ZR
northerner/M
northernmost
northward/S
northwest/ZMR
northwester/MY
northwestern
northwestward/S
nose/MGDSJ
nosebag/S
nosebleed/MS
nosecone/SM
nosedive/DSMG
nosegay/SM
nosh/MDRSZG
nosher/M
nosily
nosiness/M
nostalgia/M
nostalgic
nostalgically
nostril/MS
nostrum/MS
nosy/RPT
not/B
notability/SM
notable/SM
notably
notarial
notarization/M
notarize/GDS
notary/SM
notate/GDS
notation/FCSM
notch/GMDS
note's
note/FCSDG
notebook/MS
notelet/S
notepad/S
notepaper/M
noteworthiness/M
noteworthy/P
nothing/PSM
nothingness/M
notice/MGDS
noticeable/U
noticeably
noticeboard/S
noticed/U
notifiable
notification/M
notifier/M
notify/NDRSXZG
notion/MS
notional/Y
notoriety/M
notorious/Y
notwithstanding
notwork/S
nougat/MS
nought/MS
noun/KMS
nourish/DSLG
nourishment/M
nous
nova/MS
novae
novel/SM
novelette/SM
novelist/SM
novelization/MS
novelize/DSG
novella/MS
novelty/SM
novena/MS
novene
novice/MS
novitiate/MS
now/M
nowadays/M
noway/S
nowhere/M
nowise
nowt
noxious
nozzle/MS
nu/SM
nuance/MDS
nub/SM
nubbin/MS
nubby/TR
nubile
nuclear/K
nucleate/DSGN
nucleation/M
nuclei
nucleic
nucleoli
nucleolus/M
nucleon/SM
nucleoside
nucleotide
nucleus/M
nuclide/S
nude/MTRS
nudge/GDSM
nudism/M
nudist/SM
nudity/M
nugatory
nugget/SM
nuisance/MS
nuke/MGDS
null/S
nullification/M
nullify/NDSG
nullity/M
numb/ZTGPDRYS
number's
number/ASDG
numbered/U
numberless
numbing/Y
numbness/M
numbskull/SM
numerable/I
numeracy/IM
numeral/SM
numerate/XGNDS
numeration/M
numerator/MS
numeric
numerical/Y
numerologist/MS
numerology/M
numerous/Y
numinous
numismatic/S
numismatics/M
numismatist/SM
numskull/MS
nun/SM
nuncio/SM
nunnery/SM
nuptial/MS
nurse/MZGDRS
nurselings
nursemaid/MS
nurser/M
nursery/SM
nurseryman/M
nurserymen
nursing/M
nursling/SM
nurture/DRSMZG
nurturer/M
nut/SM
nutcase/S
nutcracker/MS
nuthatch/MS
nuthouse/S
nutjob/S
nutmeat/SM
nutmeg/SM
nutpick/SM
nutria/SM
nutrient/MS
nutriment/MS
nutrition/M
nutritional/Y
nutritionist/SM
nutritious/YP
nutritiousness/M
nutritive
nutshell/MS
nutted
nutter/S
nuttiness/M
nutting
nutty/RTP
nuzzle/DRSMZG
nuzzler/M
nybble/S
nylon/MS
nylons/M
nymph/M
nymphet/MS
nympho/S
nymphomania/M
nymphomaniac/SM
nymphs
nystagmus
n�e
o
o'clock
o'er
oaf/SM
oafish/PY
oafishness/M
oak/SMN
oakum/M
oar/SGMD
oarlock/SM
oarsman/M
oarsmen
oarswoman/M
oarswomen
oases
oasis/M
oat/SMN
oatcake/SM
oath/M
oaths
oatmeal/M
oats/M
ob/S
obbligato/MS
obduracy/M
obdurate/PY
obdurateness/M
obedience/EM
obedient/EY
obeisance/SM
obeisant
obelisk/MS
obese
obesity/M
obey/EDSG
obfuscate/GNXDS
obfuscation/M
obi/SM
obit/MS
obituary/SM
obj
object/SGVMD
objectify/NGDS
objection/SMB
objectionable/U
objectionably
objective/SMYP
objectiveness/M
objectivity/M
objector/MS
objurgate/XGNDS
objurgation/M
oblate/NX
oblation/M
obligate/DSXGN
obligation/M
obligatorily
obligatory
oblige/EGDS
obliging/Y
oblique/SMYP
obliqueness/M
obliquity/M
obliterate/DSGN
obliteration/M
oblivion/M
oblivious/YP
obliviousness/M
oblong/MS
obloquy/M
obnoxious/YP
obnoxiousness/M
oboe/MS
oboist/MS
obscene/RYT
obscenity/SM
obscurantism/M
obscurantist/SM
obscure/DRSYTG
obscurity/SM
obsequies
obsequious/PY
obsequiousness/M
obsequy/M
observable/U
observably
observance/MS
observant/Y
observation/SM
observational
observatory/SM
observe/DRSBZG
observed/U
observer/M
obsess/DSGV
obsession/SM
obsessional/Y
obsessive/PSMY
obsessiveness/M
obsidian/M
obsolesce/DSG
obsolescence/M
obsolescent
obsolete/GDS
obstacle/MS
obstetric/S
obstetrical
obstetrician/SM
obstetrics/M
obstinacy/M
obstinate/Y
obstreperous/YP
obstreperousness/M
obstruct/DGVS
obstructed/U
obstruction/SM
obstructionism/M
obstructionist/MS
obstructive/YP
obstructiveness/M
obtain/DBLGS
obtainable/U
obtainment/M
obtrude/DSG
obtrusion/M
obtrusive/UPY
obtrusiveness/UM
obtuse/YTRP
obtuseness/M
obverse/SM
obviate/DSGN
obviation/M
obvious/PY
obviousness/M
ocarina/MS
occasion/GMDS
occasional/Y
occidental/SM
occlude/GDS
occlusion/SM
occlusive
occult/M
occultism/M
occultist/SM
occupancy/M
occupant/SM
occupation/AM
occupational/Y
occupations
occupied/U
occupier/SM
occupy/ADSG
occur/AS
occurred/A
occurrence/SM
occurring/A
ocean/SM
oceanfront/SM
oceangoing
oceanic/M
oceanographer/SM
oceanographic
oceanography/M
oceanology/M
ocelot/MS
och/R
ocher/M
ocker/S
octagon/MS
octagonal
octal
octane/MS
octant/S
octantal
octave/MS
octavo/MS
octet/SM
octogenarian/SM
octopi
octopus/MS
octothorp
octothorpe
ocular/MS
oculist/SM
oculomotor
odalisque/SM
odd/STRYLP
oddball/SM
oddity/SM
oddment/SM
oddness/M
odds/M
ode/SM
odious/YP
odiousness/M
odium/M
odometer/MS
odor/MDS
odoriferous
odorless
odorous
odyssey/MS
oedipal
oenology/M
oenophile/SM
oeuvre/MS
of
off/SZGDRJ
offal/M
offbeat/MS
offend/ZGDRS
offender/M
offense/MS
offensive's
offensive/IPY
offensiveness/IM
offensives
offer/JGMD
offering/M
offertory/SM
offhand
offhanded/PY
offhandedness/M
office/MZRS
officeholder/SM
officer/M
official/MYS
officialdom/M
officialese
officialism/M
officiant/SM
officiate/DSG
officiator/MS
officious/PY
officiousness/M
offing/M
offish
offline
offload/SDG
offprint/SM
offset/MS
offsetting
offshoot/MS
offshore/G
offside
offsite
offspring/M
offstage/S
offtrack
oft
often/TR
oftentimes
ofttimes
ogle/MZGDRS
ogler/M
ogre/MS
ogreish
ogress/MS
oh/M
ohm/SM
ohmmeter/MS
oho
ohs
oi
oik/S
oil/SGMD
oilcan/S
oilcloth/M
oilcloths
oilfield/S
oiliness/M
oilman
oilmen
oilskin/MS
oilskins/M
oily/RPT
oink/MDSG
ointment/SM
okapi/SM
okay/MDSG
okra/MS
old/STMNRP
oldie/SM
oldish
oldness/M
oldster/MS
ole/SMV
oleaginous
oleander/MS
oleo/M
oleomargarine/M
olfactory/SM
oligarch/M
oligarchic
oligarchical
oligarchs
oligarchy/SM
oligo
oligonucleotide/S
oligopoly/SM
olive/SM
ol�/M
om/SMNX
ombudsman/M
ombudsmen
omega/SM
omelet/MS
omelette/MS
omen/M
omicron/MS
ominous/YP
ominousness/M
omission/MS
omit/S
omitted
omitting
omnibus/MS
omnidirectional
omnipotence/M
omnipotent
omnipresence/M
omnipresent
omniscience/M
omniscient
omnivore/MS
omnivorous/PY
omnivorousness/M
on/Y
onboard
once/M
oncogene/SM
oncologist/SM
oncology/M
oncoming
one/SXMNP
oneness/M
onerous/PY
onerousness/M
oneself
onetime
ongoing
onion/M
onionskin/M
online
onlooker/SM
onlooking
onomatopoeia/M
onomatopoeic
onomatopoetic
onrush/MSG
onscreen
onset/MS
onshore
onside
onsite
onslaught/MS
onstage
onto
ontogeny/M
ontological
ontology/M
onus/MS
onward
onyx/MS
oodles/M
ooh/GD
oohs
oomph
oops
ooze/MGDS
oozy/TR
op/SMDG
opacity/M
opal/MS
opalescence/M
opalescent
opaque/PYTGDRS
opaqueness/M
opcode/S
ope/S
open/ZTGJPMDRYS
opencast
opened/U
opener/M
openhanded/P
openhandedness/M
openhearted
opening/M
openness/M
opensource
openwork/M
opera/MS
operable/I
operand/S
operate/DSGNVX
operatic
operatically
operation/M
operational/Y
operationalize/GDS
operative/SM
operator/SM
operetta/SM
ophthalmic
ophthalmologist/SM
ophthalmology/M
opiate/SM
opine/GNXDS
opinion/M
opinionated
opioid/SM
opium/M
opossum/MS
opp
opponent/SM
opportune/IY
opportunism/M
opportunist/SM
opportunistic
opportunistically
opportunity/SM
oppose/DRSBG
opposed/U
opposite/SMYNX
opposition/M
oppositional
oppress/DSGV
oppression/M
oppressive/YP
oppressiveness/M
oppressor/MS
opprobrious/Y
opprobrium/M
opt/SGD
optic/MS
optical/Y
optician/SM
optics/M
optima
optimal/Y
optimism/SM
optimist/SM
optimistic
optimistically
optimization/MS
optimize/DRSG
optimum/SM
option/SMDG
optional/Y
optometrist/MS
optometry/M
opulence/M
opulent/Y
opus/MS
or
oracle/SM
oracular
oral/MYS
orality
orange/SMP
orangeade/MS
orangery/SM
orangutan/SM
orate/GNXDS
oration/M
orator/SM
oratorical/Y
oratorio/MS
oratory/SM
orb/SM
orbicular
orbit/MDRZGS
orbital/SM
orbiter/M
orc/SM
orchard/SM
orchestra/MS
orchestral
orchestrate/DSXGN
orchestration/M
orchid/SM
ordain/SDLG
ordainment/M
ordeal/SM
order/EAMDGS
ordered/U
orderings
orderliness/EM
orderly/PSM
ordinal/SM
ordinance/SM
ordinarily
ordinariness/M
ordinary/SMP
ordinate/MNSX
ordination/M
ordnance/M
ordure/M
ore/SM
oregano/M
org
organ/MS
organdy/M
organelle/MS
organic/SM
organically/I
organism/MS
organismic
organist/MS
organization/ASM
organizational/Y
organize/AESDG
organized/U
organizer/MS
organza/M
orgasm/SM
orgasmic
orgiastic
orgy/SM
oriel/MS
orient's
orient/AEDGS
oriental/MS
orientalist/S
orientate/EDSGN
orientation/AEM
orientations
orienteering
orifice/MS
orig
origami/M
origin/SM
original/MYS
originality/M
originate/DSGN
origination/M
originator/SM
oriole/SM
orison/SM
ormolu/M
ornament/SGMD
ornamental
ornamentation/M
ornate/YP
ornateness/M
orneriness/M
ornery/PRT
ornithological
ornithologist/MS
ornithology/M
orotund
orotundity/SM
orphan/SMDG
orphanage/MS
orris/MS
orthodontia/M
orthodontic/S
orthodontics/M
orthodontist/SM
orthodox/U
orthodoxy/SM
orthogonal
orthogonality
orthographic
orthographically
orthography/SM
orthopedic/S
orthopedics/M
orthopedist/MS
orzo/M
oscillate/GNDSX
oscillation/M
oscillator/SM
oscillatory
oscilloscope/MS
osculate/DSXGN
osculation/M
osier/MS
osmium/M
osmosis/M
osmotic
osprey/SM
ossicles
ossification/M
ossify/NGDS
ostensible
ostensibly
ostentation/M
ostentatious/Y
osteoarthritis/M
osteopath/M
osteopathic
osteopaths
osteopathy/M
osteoporosis/M
ostler/S
ostracism/M
ostracize/GDS
ostrich/MS
other/MSP
otherwise
otherworldly
otiose
otter/MS
ottoman/MS
oubliette/MS
ouch
ought
oughtn't
ounce/MS
our/S
ourselves
oust/ZGDRS
ouster/M
out/SJGMDR
outage/SM
outargue/GDS
outback/MS
outbalance/DSG
outbid/S
outbidding
outboard/MS
outboast/DSG
outbound
outbox/MS
outbreak/MS
outbuilding/MS
outburst/SM
outcast/MS
outclass/DSG
outcome/MS
outcrop/MS
outcropped
outcropping/SM
outcry/SM
outdated
outdid
outdistance/GDS
outdo/G
outdoes
outdone
outdoor/S
outdoors/M
outdoorsy
outdraw/GS
outdrawn
outdrew
outercourse
outermost
outerwear/M
outface/GDS
outfall/S
outfield/SMRZ
outfielder/M
outfight/SG
outfit/SM
outfitted
outfitter/MS
outfitting
outflank/GSD
outflow/MS
outfought
outfox/GDS
outgo/MJG
outgoes
outgrew
outgrow/HGS
outgrown
outgrowth/M
outgrowths
outguess/GDS
outgun/S
outgunned
outgunning
outhit/S
outhitting
outhouse/SM
outing/M
outlaid
outlandish/PY
outlandishness/M
outlast/DSG
outlaw/SGMD
outlay/SGM
outlet/SM
outlier/MS
outline/MGDS
outlive/GDS
outlook/MS
outlying
outmaneuver/GDS
outmatch/GDS
outmoded
outnumber/DSG
outpace/GDS
outpatient/MS
outperform/GSD
outplace/L
outplacement/M
outplay/GDS
outpoint/DGS
outpost/MS
outpouring/MS
outproduce/DSG
output/SM
outputted
outputting
outrace/GDS
outrage/MGDS
outrageous/Y
outran
outrank/GDS
outre
outreach/MDSG
outrider/MS
outrigger/SM
outright
outrun/S
outrunning
outr�
outscore/GDS
outsell/GS
outset/SM
outshine/GS
outshone
outshout/GDS
outside/MZRS
outsider/M
outsize/MDS
outskirt/MS
outsmart/GDS
outsold
outsource/DSG
outsourcing/M
outspend/SG
outspent
outspoken/YP
outspokenness/M
outspread/GS
outstanding/Y
outstation/MS
outstay/DGS
outstretch/DSG
outstrip/S
outstripped
outstripping
outta
outtake/MS
outvote/GDS
outward/YS
outwear/GS
outweigh/GD
outweighs
outwit/S
outwith
outwitted
outwitting
outwore
outwork/MDRSZG
outworn
ouzo/MS
ova
oval/MS
ovarian
ovary/SM
ovate/NX
ovation/M
oven/MS
ovenbird/SM
ovenproof
ovenware
over/MYS
overabundance/M
overabundant
overachieve/ZGDRS
overachiever/M
overact/GVSD
overage/SM
overaggressive
overall/SM
overalls/M
overambitious
overanxious
overarching
overarm/GSD
overate
overattentive
overawe/DSG
overbalance/MGDS
overbear/GS
overbearing/Y
overbid/SM
overbidding
overbite/MS
overblown
overboard
overbold
overbook/DGS
overbore
overborne
overbought
overbroad
overbuild/SG
overbuilt
overburden/GSD
overbuy/GS
overcame
overcapacity/M
overcapitalize/DSG
overcareful
overcast/MGS
overcautious
overcharge/DSMG
overclock/GD
overcloud/SGD
overcoat/MS
overcome/GS
overcompensate/DSGN
overcompensation/M
overconfidence/M
overconfident
overconscientious
overcook/DGS
overcritical
overcrowd/SDG
overcrowding/M
overdecorate/DSG
overdependent
overdevelop/SDG
overdid
overdo/G
overdoes
overdone
overdose/MGDS
overdraft/SM
overdraw/GS
overdrawn
overdress/GMDS
overdrew
overdrive/SM
overdub/SM
overdubbed
overdubbing
overdue
overeager
overeat/GSN
overemotional
overemphasis/M
overemphasize/GDS
overenthusiastic
overestimate/MGNDS
overestimation/M
overexcite/DSG
overexercise/GDS
overexert/SDG
overexertion/M
overexpose/GDS
overexposure/M
overextend/DGS
overfed
overfeed/GS
overfill/DGS
overflew
overflight/MS
overflow/MDSG
overflown
overfly/GS
overfond
overfull
overgeneralize/DSG
overgenerous
overgraze/DSG
overgrew
overground
overgrow/HSG
overgrown
overgrowth/M
overhand/MDS
overhang/MSG
overhasty
overhaul/MDSG
overhead/MS
overhear/SG
overheard
overheat/DSG
overhung
overinclusion
overinclusive
overindulge/GDS
overindulgence/M
overindulgent
overinflated
overjoy/GSD
overkill/M
overladen
overlaid
overlain
overland
overlap/SM
overlapped
overlapping
overlarge
overlay/GSM
overleaf
overlie
overload/GMDS
overlong
overlook/GMDS
overlord/MS
overly/SG
overmanned
overmanning
overmaster/SDG
overmodest
overmuch/S
overnice
overnight/MS
overoptimism/M
overoptimistic
overpaid
overparticular
overpass/MS
overpay/GS
overplay/GDS
overpopulate/GNDS
overpopulation/M
overpower/SDG
overpowering/Y
overpraise/DSG
overprecise
overprice/DSG
overprint/SMDG
overproduce/GDS
overproduction/M
overprotect/SDGV
overqualified
overran
overrate/GDS
overreach/GDS
overreact/SDG
overreaction/SM
overrefined
overrich/P
overridden
override/MGS
overripe/M
overrode
overrule/GDS
overrun/SM
overrunning
oversampling
oversaw
oversea/S
oversee/RSZ
overseeing
overseen
overseer/M
oversell/GS
oversensitive/P
oversensitiveness/M
oversexed
overshadow/DSG
overshare/DSG
overshoe/MS
overshoot/GS
overshot
oversight/SM
oversimple
oversimplification/M
oversimplify/DSNGX
oversize/D
oversleep/GS
overslept
oversold
overspecialization/M
overspecialize/GDS
overspend/SG
overspent
overspread/GS
overstaffed
overstate/DSLG
overstatement/MS
overstay/DSG
overstep/S
overstepped
overstepping
overstimulate/DSG
overstock/GSD
overstretch/GDS
overstrict
overstrung
overstuffed
oversubscribe/DSG
oversubtle
oversupply/GDS
oversuspicious
overt/Y
overtake/GS
overtaken
overtax/GDS
overthink/SG
overthought
overthrew
overthrow/SMG
overthrown
overtime/MS
overtire/GDS
overtone/MS
overtook
overture/MS
overturn/DSG
overuse/DSMG
overvaluation/S
overvalue/DSG
overview/MS
overweening/Y
overweight/M
overwhelm/SGD
overwhelming/Y
overwinter/SDG
overwork/GMDS
overwrite/GS
overwritten
overwrote
overwrought
overzealous
oviduct/SM
oviparous
ovoid/MS
ovular
ovulate/DSGN
ovulation/M
ovule/MS
ovum/M
ow
owe/DSG
owl/SM
owlet/MS
owlish/Y
own/ESGD
owner/MS
ownership/M
ox/MN
oxalate
oxblood/M
oxbow/MS
oxcart/SM
oxford/SM
oxidant/MS
oxidase
oxidation/M
oxidative
oxide/MS
oxidization/M
oxidize/ZGDRS
oxidizer/M
oxtail/S
oxyacetylene/M
oxygen/M
oxygenate/DSGN
oxygenation/M
oxymora
oxymoron/M
oxymoronic
oxymoronically
oy
oyes
oyez
oyster/SM
oz
ozone/M
p/NRXTGJ
pH
pa/SMH
pablum/M
pabulum/M
pace/MZGDRS
pacemaker/SM
pacer/M
pacesetter/SM
pacey
pachyderm/MS
pachysandra/MS
pacific
pacifically
pacification/M
pacifier/M
pacifism/M
pacifist/SM
pacifistic
pacify/ZGDRSN
pack's
pack/AUGSD
package's
package/AGDS
packager/SM
packaging/M
packer/MS
packet/MS
packing's
packinghouse/SM
packsaddle/MS
pact/MS
pacy/RT
pad/SM
padded
padding/M
paddle/MZGDRS
paddler/M
paddock/MDGS
paddy/SM
padlock/MDSG
padre/SM
paean/SM
paella/MS
pagan/SM
paganism/M
page/MZGDRS
pageant/MS
pageantry/M
pageboy/SM
pager/M
paginate/DSGN
pagination/M
pagoda/MS
pah
paid/AU
pail/MS
pailful/SM
pain/MDSG
painful/PY
painfuller
painfullest
painfulness/M
painkiller/MS
painkilling
painless/PY
painlessness/M
painstaking/MY
paint/SZGJMDR
paintball
paintbox/MS
paintbrush/MS
painted/U
painter/MY
painting/M
paintwork
pair/AMDSG
paired/U
pairing/S
pairwise
paisley/SM
pajama/S
pajamas/M
pal/SMY
palace/MS
paladin/SM
palanquin/SM
palatable/U
palatal/SM
palatalization/M
palatalize/GDS
palate/MBS
palatial/Y
palatinate/MS
palatine/MS
palaver/GSMD
palazzi
palazzo
pale/MYTGPDRSJ
paleface/MS
paleness/M
paleo
paleographer/MS
paleography/M
paleolithic
paleontologist/SM
paleontology/M
palette/SM
palfrey/SM
palimony/M
palimpsest/MS
palindrome/MS
palindromic
paling/M
palisade/SM
palish
pall/MDSG
palladium/M
pallbearer/MS
pallet/MS
palliate/DSGNV
palliation/M
palliative/SM
pallid/YP
pallidness/M
pallor/M
palm/MDSG
palmate
palmetto/SM
palmist/SM
palmistry/M
palmtop/SM
palmy/TR
palomino/MS
palpable
palpably
palpate/DSGN
palpation/M
palpitate/XGNDS
palpitation/M
palsy/GDSM
paltriness/M
paltry/RPT
pampas/M
pamper/DSG
pamphlet/MS
pamphleteer/MS
pan/SM
panacea/SM
panache/M
panama/MS
panatella/S
pancake/DSMG
panchromatic
pancreas/MS
pancreatic
pancreatitis
panda/SM
pandemic/SM
pandemonium/M
pander/MDRZGS
panderer/M
pane/KM
panegyric/SM
panel/SGJMD
paneling/M
panelist/MS
panes
pang/MS
panhandle/DRSMZG
panhandler/M
panic/SM
panicked
panicking
panicky
panned
pannier/SM
panning
panoply/SM
panorama/SM
panoramic
panpipes/M
pansy/SM
pant/MDSG
pantaloons/M
pantechnicon/S
pantheism/M
pantheist/SM
pantheistic
pantheon/SM
panther/MS
pantie/M
panto/S
pantomime/MGDS
pantomimic
pantomimist/SM
pantry/SM
pantsuit/SM
panty/SM
pantyhose/M
pantyliner/M
pantywaist/SM
pap/SM
papa/MS
papacy/SM
papal
paparazzi/M
paparazzo
papaw/SM
papaya/MS
paper/SZGMDR
paperback/SM
paperbark/S
paperboard/M
paperboy/SM
paperclip/S
paperer/M
papergirl/SM
paperhanger/SM
paperhanging/M
paperless
paperweight/MS
paperwork/M
papery
papilla/M
papillae
papillary
papist/MS
papoose/MS
pappy/SM
paprika/M
papyri
papyrus/M
par/SZGMDRBJ
para/MS
parable/MS
parabola/SM
parabolic
paracetamol/S
parachute/DSMG
parachutist/MS
paracord
parade/MZGDRS
parader/M
paradigm/SM
paradigmatic
paradisaical
paradise/SM
paradox/MS
paradoxical/Y
paraffin/M
paragliding
paragon/MS
paragraph/GMD
paragraphs
parakeet/SM
paralegal/MS
parallax/MS
parallel/SGMD
paralleled/U
parallelism/MS
parallelization/MS
parallelize/GDS
parallelogram/SM
paralyses
paralysis/M
paralytic/SM
paralyze/DSG
paralyzing/Y
paramagnetic
paramecia
paramecium/M
paramedic/MS
paramedical/MS
parameter/MS
parameterize/D
parametric
parametrize/D
paramilitary/SM
paramount
paramountcy
paramour/SM
paranoia/M
paranoiac/MS
paranoid/SM
paranormal
parapet/MS
paraphernalia/M
paraphrase/DSMG
paraplegia/M
paraplegic/SM
paraprofessional/MS
parapsychologist/MS
parapsychology/M
paraquat/M
parasailing
parascending
parasite/SM
parasitic
parasitical/Y
parasitism/M
parasol/MS
parasympathetic/S
parathion/M
parathyroid/MS
paratroop/RZS
paratrooper/M
paratroops/M
paratyphoid/M
parboil/DSG
parcel/GMDS
parch/LGDS
parchment/SM
pardner/S
pardon/ZGMDRBS
pardonable/U
pardonably/U
pardoner/M
pare/S
paregoric/M
parent/GMDS
parentage/M
parental
parentheses
parenthesis/M
parenthesize/DSG
parenthetic
parenthetical/Y
parenthood/M
parenting/M
parer/M
pares/S
paresis/M
parfait/MS
pariah/M
pariahs
paribus
parietal
parimutuel/MS
paring/M
parish/MS
parishioner/MS
parity/ESM
park/MDSG
parka/SM
parking/M
parkland
parkour
parkway/MS
parky
parlance/M
parlay/GMDS
parley/GMDS
parliament/SM
parliamentarian/SM
parliamentary
parlor/MS
parlous
parmigiana
parmigiano
parochial/Y
parochialism/M
parodist/SM
parody/GDSM
parole/MGDS
parolee/MS
paronychia
parotid
paroxysm/SM
paroxysmal
parquet/MDSG
parquetry/M
parred
parricidal
parricide/MS
parring
parrot/GMDS
parry/GDSM
parse/DRSZG
parsec/MS
parsimonious/Y
parsimony/M
parsley/M
parsnip/MS
parson/MS
parsonage/MS
part's
part/CDSG
partake/ZGRS
partaken
partaker/M
parterre/SM
parthenogenesis/M
partial/MYS
partiality/M
participant/SM
participate/DSGN
participation/M
participator/MS
participatory
participial/M
participle/MS
particle/SM
particleboard/M
particular/SMY
particularity/SM
particularization/M
particularize/DSG
particulate/SM
parting/MS
partisan/SM
partisanship/M
partition/GMDS
partitions/A
partitive/MS
partizan/SM
partly
partner/MDSG
partnership/MS
partook
partridge/SM
parturition/M
partway
party/GDSM
partygoers
parvenu/MS
pascal/MS
paschal
pasha/SM
pass/M
passably
passage/MS
passageway/MS
passbook/MS
passe/DRSBXZGNV
passel/MS
passenger/SM
passer/M
passerby/M
passersby
passim
passing/MY
passion/EM
passionate/EY
passionflower/SM
passionless
passive/PMYS
passiveness/M
passivity/M
passivization
passivize/DSG
passkey/MS
passphrase/S
passport/MS
password/MS
pass�
past/AMS
pasta/SM
paste/DSMG
pasteboard/M
pastel/MS
pastern/MS
pasteurization/M
pasteurize/ZGDRS
pasteurized/U
pasteurizer/M
pastiche/MS
pastie
pastille/MS
pastime/MS
pastiness/M
pastor/MS
pastoral/MS
pastorate/MS
pastrami/M
pastry/SM
pasturage/M
pasture/DSMG
pastureland/M
pasty/PTRSM
pat/SM
patch/EGMDS
patchily
patchiness/M
patchouli
patchwork/SM
patchy/TPR
pate/MS
patella/MS
patellae
patent/GMDYS
paterfamilias/MS
paternal/Y
paternalism/M
paternalist/S
paternalistic
paternity/M
paternoster/MS
path/M
pathetic
pathetically
pathfinder/SM
pathless
pathogen/SM
pathogenic
pathological/Y
pathologist/SM
pathology/M
pathos/M
paths
pathway/MS
patience/M
patient/IMST
patienter
patiently
patina/MS
patine
patio/SM
patisserie/S
patois/M
patresfamilias
patriarch/M
patriarchal
patriarchate/MS
patriarchs
patriarchy/SM
patrician/SM
patricidal
patricide/SM
patrimonial
patrimony/SM
patriot/SM
patriotic/U
patriotically
patriotism/M
patrol/MS
patrolled
patrolling
patrolman/M
patrolmen
patrolwoman/M
patrolwomen
patron/MS
patronage/MS
patroness/MS
patronize/ZGDRS
patronizer/M
patronizing/Y
patronymic/SM
patronymically
patroon/SM
patsy/SM
patted
patter/MDGS
pattern/SMDG
patting
patty/SM
paucity/M
paunch/MS
paunchy/RT
pauper/MS
pauperism/M
pauperize/DSG
pause/DSMG
pave/AZGDRS
paved/U
pavement/MS
pavilion/SM
paving/MS
pavlova/S
paw/SGMD
pawl/MS
pawn/MDSG
pawnbroker/MS
pawnbroking/M
pawnshop/MS
pawpaw/MS
pax
pay's
pay/ASGBL
payback/SM
paycheck/MS
payday/MS
payed
payee/SM
payer/SM
payload/SM
paymaster/SM
payment/ASM
payoff/MS
payola/M
payout/MS
payphone/S
payroll/SM
payslip/SM
paywall/SM
payware
pct
pd
pea/SM
peace/SM
peaceable
peaceably
peaceful/PY
peacefulness/M
peacekeeper/SM
peacekeeping/M
peacemaker/MS
peacemaking/M
peacetime/M
peach/MS
peachy/TR
peacock/MS
peafowl/MS
peahen/MS
peak/MDSG
peaky
peal/AMDSG
peanut/MS
pear/MYS
pearl/SGMD
pearly/RT
peasant/SM
peasantry/M
peashooter/SM
peat/M
peaty/TR
pebble/MGDS
pebbly
pebibyte/SM
pecan/SM
peccadillo/M
peccadilloes
peccary/SM
peck/MDRSZG
peckish
pecs
pectic
pectin/M
pectoral/MS
pectoralis
peculate/GNDS
peculation/M
peculator/SM
peculiar/Y
peculiarity/SM
pecuniary
pedagogic
pedagogical/Y
pedagogue/SM
pedagogy/M
pedal/SGMD
pedalo/S
pedant/MS
pedantic
pedantically
pedantry/M
peddle/ZGDRS
peddler/M
pederast/MS
pederasty/M
pedestal/MS
pedestrian/SM
pedestrianization
pedestrianize/GDS
pediatric/S
pediatrician/MS
pediatrics/M
pedicab/SM
pedicure/MGDS
pedicurist/MS
pedigree/MDS
pediment/MS
pedometer/MS
pedophile/S
pedophilia
peduncle/MS
pee/DRSMZ
peeing
peek/MDSG
peekaboo/M
peel/MDRSJZG
peeled/U
peeler/M
peeling/M
peen/MS
peep/MDRSZG
peepbo
peeper/M
peephole/MS
peepshow/MS
peer/MDG
peerage/SM
peeress/MS
peerless
peeve/DSMG
peevish/PY
peevishness/M
peewee/MS
peewit/S
peg/SM
pegboard/MS
pegged
pegging
peignoir/SM
pejoration/M
pejorative/SMY
peke/MS
pekinese/SM
pekingese/SM
pekoe/M
pelagic
pelf/M
pelican/MS
pellagra/M
pellet/GMDS
pellucid
pelmet/S
pelt/MDSG
pelvic
pelvis/MS
pemmican/M
pen/M
penal
penalization/M
penalize/DSG
penalty/SM
penance/MS
pence
penchant/SM
pencil/GMDJS
pend/CDSG
pendant/MS
pendency
pendent/MS
pendulous
pendulum/MS
penetrability/M
penetrable
penetrate/DSGNVX
penetrating/Y
penetration/M
penfriend/S
penguin/MS
penicillin/M
penile
peninsula/SM
peninsular
penis/MS
penitence/M
penitent/SMY
penitential
penitentiary/SM
penknife/M
penknives
penlight/SM
penman/M
penmanship/M
penmen
pennant/MS
penned
penniless
penning
pennon/MS
penny/SM
pennyweight/MS
pennyworth
penologist/MS
penology/M
pension/BZGMDRS
pensioner/M
pensive/PY
pensiveness/M
pent
pentacle/MS
pentagon/MS
pentagonal
pentagram/SM
pentameter/SM
pentathlete/MS
pentathlon/MS
penthouse/SM
penuche/M
penultimate/SM
penumbra/MS
penumbrae
penurious/PY
penuriousness/M
penury/M
peon/MS
peonage/M
peony/SM
people/MGDS
pep/SM
pepped
pepper/GMDS
peppercorn/SM
peppermint/SM
pepperoni/MS
peppery
peppiness/M
pepping
peppy/TPR
pepsin/M
peptic/MS
peptide/S
peradventure/M
perambulate/XGNDS
perambulation/M
perambulator/MS
percale/MS
perceive/BGDS
perceived/U
percent/MS
percentage/SM
percentile/SM
perceptible
perceptibly
perception/SM
perceptional
perceptive/PY
perceptiveness/M
perceptual/Y
perch/GMDS
perchance
percipience/M
percipient
percolate/GNDS
percolation/M
percolator/SM
percussion/AM
percussionist/MS
percussive
perdition/M
perdurable
peregrinate/DSXGN
peregrination/M
peregrine/MS
peremptorily
peremptory
perennial/SMY
perestroika/M
perfect/PTGMDRYS
perfecta/MS
perfectibility/M
perfectible
perfection/SM
perfectionism/M
perfectionist/SM
perfectness/M
perfidious/Y
perfidy/SM
perforate/GNXDS
perforation/M
perforce
perform/SDRZG
performance/SM
performant
performative
performed/U
performer/M
perfume/DRSMZG
perfumer/M
perfumery/SM
perfunctorily
perfunctory
perfusion
pergola/SM
perhaps
pericardia
pericardial
pericarditis
pericardium/M
perigee/SM
perihelia
perihelion/M
peril/SGMD
perilous/Y
perimeter/SM
perinatal
perinea
perineum/M
period/MS
periodic
periodical/SMY
periodicity/M
periodontal
periodontics/M
periodontist/SM
peripatetic/MS
peripheral/MYS
periphery/SM
periphrases
periphrasis/M
periphrastic
periscope/SM
perish/BDRSZG
perishable/MS
peristalses
peristalsis/M
peristaltic
peristyle/SM
peritoneal
peritoneum/MS
peritonitis/M
periwig/SM
periwinkle/SM
perjure/DRSZG
perjurer/M
perjury/SM
perk/MDSG
perkily
perkiness/M
perky/TPR
perm/MDSG
permafrost/M
permalink/SM
permanence/M
permanency/M
permanent/SMY
permeability/M
permeable
permeate/GNDS
permeation/M
permissibility
permissible
permissibly
permission/MS
permissive/PY
permissiveness/M
permit/MS
permitted
permittee
permitting
permittivity
permutation/SM
permute/DSG
pernicious/YP
perniciousness/M
peroration/MS
peroxide/MGDS
perpendicular/SMY
perpendicularity/M
perpetrate/DSGN
perpetration/M
perpetrator/MS
perpetual/SMY
perpetuate/DSGN
perpetuation/M
perpetuity/M
perplex/GDS
perplexed/Y
perplexing/Y
perplexity/SM
perquisite/SM
persecute/GNXDS
persecution/M
persecutor/SM
perseverance/M
persevere/DSG
persiflage/M
persimmon/SM
persist/SGD
persistence/M
persistent/Y
persnickety
person/UMS
persona/SM
personable
personae
personage/MS
personal/MYS
personality/SM
personalization
personalize/CDSG
personalty/M
personification/M
personify/GDSNX
personnel/M
perspective/MS
perspex
perspicacious/Y
perspicacity/M
perspicuity/M
perspicuous
perspiration/M
perspire/GDS
persuade/BZGDRS
persuaded/U
persuader/M
persuasion/SM
persuasive/PY
persuasiveness/M
pert/RYPT
pertain/GSD
pertinacious/Y
pertinacity/M
pertinence/M
pertinent/Y
pertness/M
perturb/DGS
perturbation/SM
perturbed/U
pertussis/M
peruke/MS
perusal/MS
peruse/GDS
perv/S
pervade/DSG
pervasive/PY
pervasiveness/M
perverse/PXYN
perverseness/M
perversion/M
perversity/M
pervert/SGMD
peseta/MS
peskily
peskiness/M
pesky/TPR
peso/MS
pessary/S
pessimal
pessimism/M
pessimist/SM
pessimistic
pessimistically
pest/MRSZ
pester/GD
pesticide/MS
pestiferous
pestilence/SM
pestilent
pestilential
pestle/MGDS
pesto/M
pet/SZMR
petabyte/MS
petajoule/S
petal/SMD
petard/MS
petawatt/S
petcock/SM
peter/GMD
petiole/SM
petite/MS
petition/ZGMDRS
petitionary
petitioner/M
petrel/MS
petrifaction/M
petrify/DSG
petrochemical/SM
petrodollar/MS
petrol/M
petrolatum/M
petroleum/M
petrologist/SM
petrology/M
petted
petticoat/MS
pettifog/S
pettifogged
pettifogger/SM
pettifoggery/M
pettifogging
pettily
pettiness/M
petting/M
pettish/Y
petty/PTR
petulance/M
petulant/Y
petunia/MS
pew/SM
pewee/SM
pewit/SM
pewter/MS
peyote/M
pf
pfennig/MS
pg
phaeton/MS
phage/S
phagocyte/SM
phalanger/SM
phalanges
phalanx/MS
phalli
phallic
phallocentric
phallocentrism
phallus/M
phantasm/MS
phantasmagoria/MS
phantasmagorical
phantasmal
phantom/SM
pharaoh/M
pharaohs
pharisaic
pharisee/SM
pharma/MS
pharmaceutic/MS
pharmaceutical/SM
pharmaceutics/M
pharmacist/MS
pharmacologic
pharmacological
pharmacologist/SM
pharmacology/M
pharmacopeia/SM
pharmacopoeia/MS
pharmacotherapy
pharmacy/SM
pharyngeal
pharynges
pharyngitis/M
pharynx/M
phase/DSMG
phaseout/SM
phat
pheasant/MS
phenacetin/M
phenobarbital/M
phenol/M
phenom/MS
phenomena
phenomenal/Y
phenomenological
phenomenology
phenomenon/MS
phenotype
phenytoin
pheromone/MS
phew
phi/SM
phial/SM
philander/ZGDRS
philanderer/M
philandering/M
philanthropic
philanthropically
philanthropist/MS
philanthropy/SM
philatelic
philatelist/MS
philately/M
philharmonic/SM
philippic/MS
philistine/MS
philistinism/M
philodendron/SM
philological
philologist/MS
philology/M
philosopher/MS
philosophic
philosophical/Y
philosophize/DRSZG
philosophizer/M
philosophy/SM
philter/MS
phimosis/S
phish/ZGDR
phisher/M
phlebitis/M
phlebotomist/MS
phlebotomize/GDS
phlebotomy
phlegm/M
phlegmatic
phlegmatically
phloem/M
phlogiston/S
phlox/M
pho
phobia/MS
phobic/MS
phoebe/MS
phoenix/MS
phone/DSMG
phonecard/S
phoneme/MS
phonemic
phonemically
phonetic/S
phonetically
phonetician/SM
phonetics/M
phoneyed
phoneying
phonic/S
phonically
phonics/M
phoniness/M
phonograph/M
phonographic
phonographs
phonological/Y
phonologist/MS
phonology/M
phonon
phony/PTGDRSM
phooey
phosphate/MS
phosphine/MS
phosphodiesterase
phosphor/MS
phosphorescence/M
phosphorescent/Y
phosphoric
phosphorous
phosphorus/M
phosphorylate/DSGN
photo/SGMD
photocell/MS
photocopier/M
photocopy/DRSMZG
photodetector/S
photoelectric
photoelectrically
photoengrave/DRSJZG
photoengraver/M
photoengraving/M
photofinishing/M
photogenic
photogenically
photograph/MDRZG
photographer/M
photographic
photographically
photographs/A
photography/M
photojournalism/M
photojournalist/SM
photometer/MS
photon/MS
photosensitive
photosensor/S
photosensory
photostat/SM
photostatic
photostatted
photostatting
photosynthesis/M
photosynthesize/GDS
photosynthetic
phototropic
phototropism
phototypesetter
phototypesetting
photovoltaic
phrasal
phrase's
phrase/AGDS
phrasebook/S
phraseology/M
phrasing/MS
phreaking
phrenologist/SM
phrenology/M
phyla
phylactery/SM
phyllo
phylogeny/M
phylum/M
phys
physic/SM
physical/MYS
physicality
physician/SM
physicist/SM
physicked
physicking
physics/M
physio/S
physiognomy/SM
physiography/M
physiologic
physiological/Y
physiologist/MS
physiology/M
physiotherapist/MS
physiotherapy/M
physique/MS
phytoplankton
pi/SMDRHZG
pianissimo/SM
pianist/MS
piano/SM
pianoforte/SM
pianola/S
piaster/MS
piazza/MS
pibroch/M
pibrochs
pic/SM
pica/M
picador/MS
picante
picaresque
picayune
piccalilli/M
piccolo/MS
pick/MDRSJZG
pickax/GMDS
picker/M
pickerel/MS
picket/ZGMDRS
pickings/M
pickle/MGDS
pickpocket/SM
pickup/MS
picky/PTR
picnic/MS
picnicked
picnicker/SM
picnicking
picot/SM
pictogram/S
pictograph/M
pictographs
pictorial/MYS
picture/MGDS
picturesque/PY
picturesqueness/M
piddle/MGDS
piddly
pidgin/MS
pie/SM
piebald/MS
piece/DSMG
piecemeal
piecewise
piecework/MRZ
pieceworker/M
piecrust/SM
pieing
pier/M
pierce/JGDS
piercing/MY
piety/M
piezoelectric
piffle/MG
pig/SML
pigeon/MS
pigeonhole/DSMG
pigged
piggery/S
pigging
piggish/PY
piggishness/M
piggy/TRSM
piggyback/MDSG
pigheaded/PY
pigheadedness/M
piglet/MS
pigment/MDS
pigmentation/M
pigpen/MS
pigskin/MS
pigsty/SM
pigswill
pigtail/MS
pike/MZGDRS
piker/M
pikestaff/SM
pikestaves
pilaf/SM
pilaster/MS
pilchard/MS
pile/MGDSJ
pileup/MS
pilfer/ZGDRS
pilferage/M
pilferer/M
pilgrim/MS
pilgrimage/MGDS
piling/M
pill/MDSG
pillage/MZGDRS
pillager/M
pillar/MDS
pillbox/MS
pillion/MS
pillock/S
pillory/GDSM
pillow/GMDS
pillowcase/MS
pillowslip/MS
pilot/DGSM
pilothouse/SM
pimento/MS
pimiento/MS
pimp/GMDYS
pimpernel/MS
pimple/DSM
pimply/RT
pin/SM
pinafore/MS
pinata/MS
pinball/M
pincer/MS
pinch/GMDS
pincushion/MS
pine's
pine/AGDS
pineapple/MS
pinewood/S
piney
pinfeather/SM
ping/GMD
pinhead/SM
pinhole/SM
pinion/SMDG
pink/TGPMDRS
pinkeye/M
pinkie/M
pinkish
pinkness/M
pinko/MS
pinky/SM
pinnacle/SM
pinnate
pinned/U
pinning/U
pinny/S
pinochle/M
pinon/MS
pinpoint/SGMD
pinprick/MS
pinsetter/SM
pinstripe/DSM
pint/MS
pinto/MS
pinup/MS
pinwheel/GSMD
piny/TR
pinyin/M
pinyon/SM
pioneer/SGMD
pious/YP
piousness/M
pip/SZGMDR
pipe/MS
pipeline/SM
piper/M
pipette/SM
pipework
piping/M
pipit/MS
pipped
pippin/SM
pipping
pipsqueak/SM
piquancy/M
piquant/Y
pique/MGDS
piracy/M
piranha/SM
pirate/DSMG
piratical/Y
pirogi/M
piroshki/M
pirouette/DSMG
pirozhki/M
piscatorial
pismire/SM
piss/ZGMDRS
pissoir/S
pistachio/SM
piste/S
pistil/SM
pistillate
pistol/SM
piston/SM
pit/SM
pita/MS
pitapat/SM
pitch/MDRSZG
pitchblende/M
pitcher/M
pitchfork/MDSG
pitchman/M
pitchmen
piteous/YP
piteousness/M
pitfall/SM
pith/M
pithead/S
pithily
pithiness/M
pithy/RTP
pitiable
pitiably
pitiful/Y
pitiless/PY
pitilessness/M
piton/MS
pitta/S
pittance/MS
pitted
pitting
pituitary/SM
pity/GDSM
pitying/Y
pivot/MDGS
pivotal
pix/M
pixel/MS
pixelate/DS
pixie/MS
pizazz/M
pizza/MS
pizzazz/M
pizzeria/SM
pizzicati
pizzicato/M
pi�ata/MS
pi�on/SM
pj's
pk
pkg
pkt
pkwy
pl
placard/SMDG
placate/DSGN
placation/M
placatory
place's
place/AESDLG
placebo/SM
placed/U
placeholder/MS
placekick/MDRZGS
placekicker/M
placement/EASM
placenta/SM
placental/S
placer/SM
placid/Y
placidity/M
placings
placket/SM
plagiarism/SM
plagiarist/SM
plagiarize/DRSZG
plagiarizer/M
plagiary/M
plague/DSMG
plaice
plaid/MS
plain/MRYTSP
plainchant
plainclothes
plainclothesman/M
plainclothesmen
plainness/M
plainsman/M
plainsmen
plainsong/M
plainspoken
plaint/SMV
plaintext
plaintiff/SM
plaintive/Y
plait/MDGS
plan/ZMRS
planar
plane's
plane/CGDS
planeload/MS
planer/M
planet/SM
planetarium/SM
planetary
plangency/M
plangent
plank/MDGS
planking/M
plankton/M
planned/U
planner/SM
planning/S
plant/MDRZGSJ
plantain/SM
plantar
plantation/MS
planter/M
planting/M
plantlike
plaque/SM
plash/MDSG
plasma/M
plasmon
plaster/SZGMDR
plasterboard/M
plasterer/M
plastic/SM
plasticity/M
plasticize/DSG
plastique
plat/XGMDNS
plate/MS
plateau/SMDG
plateful/SM
platelet/SM
platen/M
platform/SGMD
plating/M
platinum/M
platitude/SM
platitudinous
platonic
platoon/SGMD
platted
platter/SM
platting
platy/M
platypus/MS
platys
plaudit/SM
plausibility/M
plausible
plausibly
play/AEGMDS
playable/EU
playact/SGD
playacting/M
playback/MS
playbill/MS
playbook/MS
playboy/SM
player/SM
playfellow/SM
playful/PY
playfulness/M
playgirl/MS
playgoer/MS
playground/SM
playgroup/S
playhouse/MS
playlist/MS
playmate/MS
playoff/SM
playpen/SM
playroom/SM
playschool/S
plaything/SM
playtime/M
playwright/SM
plaza/MS
plea/MS
plead/ADRZGSJ
pleader's
pleading/MY
pleasant/UTYP
pleasanter
pleasantness/UM
pleasantry/SM
please/EDSG
pleasing/YS
pleasurably
pleasure/MGDSB
pleasureful
pleat/MDGS
pleb/S
plebby
plebe/MS
plebeian/MS
plebiscite/MS
plectra
plectrum/MS
pledge/DSMG
plenary/SM
plenipotentiary/SM
plenitude/SM
plenteous
plentiful/Y
plenty/M
plenum/S
pleonasm/MS
plethora/M
pleura/M
pleurae
pleurisy/M
plexiglass/M
plexus/MS
pliability/M
pliable
pliancy/M
pliant/Y
pliers/M
plight/SMDG
plimsoll/S
plinth/M
plinths
plod/S
plodded
plodder/MS
plodding/S
plonk/DRSZG
plop/MS
plopped
plopping
plosive/S
plot/MS
plotted
plotter/SM
plotting
plover/SM
plow/GMDS
plowman/M
plowmen
plowshare/MS
ploy's
ploy/S
pluck/MDSG
pluckily
pluckiness/M
plucky/RPT
plug's
plug/US
plugged/U
plugging/U
plughole/S
plugin/SM
plum/GMDS
plumage/M
plumb/MDRSZGJ
plumbed/U
plumber/M
plumbing/M
plume/MS
plummet/SGMD
plummy
plump/MDRYSTGP
plumpness/M
plumy/RT
plunder/SZGMDR
plunderer/M
plunge/DRSMZG
plunger/M
plunk/MDSG
pluperfect/SM
plural/SM
pluralism/M
pluralist/MS
pluralistic
plurality/SM
pluralization/M
pluralize/GDS
plus/MS
plush/MRYTP
plushness/M
plushy/RT
plusses
plutocracy/SM
plutocrat/SM
plutocratic
plutonium/M
pluvial
ply/AGDSM
plywood/M
pm
pneumatic
pneumatically
pneumococcal
pneumococci
pneumococcus
pneumonia/M
poach/DRSZG
poacher/M
poaching/M
pock/GMDS
pocket/SMDG
pocketbook/SM
pocketful/SM
pocketknife/M
pocketknives
pockmark/MDGS
pod/SM
podcast/SMG
podded
podding
podiatrist/SM
podiatry/M
podium/SM
poem/MS
poesy/M
poet/MS
poetaster/MS
poetess/MS
poetic/S
poetical/Y
poetry/M
pogrom/SM
poi/M
poignancy/M
poignant/Y
poinciana/SM
poinsettia/SM
point/MDRSZG
pointblank
pointed/Y
pointer/M
pointillism/M
pointillist/SM
pointless/PY
pointlessness/M
pointy/TR
poise/MGDS
poison/SJZGMDR
poisoner/M
poisoning/M
poisonous/Y
poke/MZGDRS
poker/M
pokey/MS
poky/TR
pol/SGMD
polar
polarity/SM
polarization/CM
polarize/CDSG
polarizer/S
pole/MS
poleaxe/GDS
polecat/MS
polemic/MS
polemical/Y
polemicist/SM
polemics/M
polestar/SM
police/DSMG
policeman/M
policemen
policewoman/M
policewomen
policy/SM
policyholder/MS
policymaker/S
polio/MS
poliomyelitis/M
polish/ZGMDRS
polished/U
polisher/M
politburo/MS
polite/RYTP
politeness/M
politesse/M
politic/S
political/Y
politician/SM
politicization/M
politicize/CDSG
politicking/M
politico/SM
politics/M
polity/SM
polka/MDSG
poll/GMDNS
pollack/MS
pollard/S
pollen/M
pollinate/GNDS
pollination/M
pollinator/SM
polling/M
polliwog/SM
pollock/M
pollster/SM
pollutant/MS
pollute/ZGNDRS
polluted/U
polluter/M
pollution/M
pollywog/MS
polo/M
polonaise/SM
polonium/M
poltergeist/MS
poltroon/SM
poly
polyacrylamide
polyamory/S
polyandrous
polyandry/M
polyclinic/SM
polyester/MS
polyethylene/M
polygamist/MS
polygamous
polygamy/M
polyglot/SM
polygon/SM
polygonal
polygraph/GMD
polygraphs
polyhedral
polyhedron/SM
polymath/M
polymaths
polymer/SM
polymeric
polymerization/M
polymerize/GDS
polymorphic
polymorphically
polymorphism
polymorphous
polynomial/MS
polynucleotide/SM
polyp/MS
polypeptide/SM
polyphonic
polyphony/M
polypropylene/M
polys
polysemous
polystyrene/M
polysyllabic
polysyllable/MS
polytechnic/MS
polytheism/M
polytheist/SM
polytheistic
polythene
polyunsaturate/DS
polyurethane/MS
polyvinyl
pom/S
pomade/DSMG
pomander/SM
pomegranate/MS
pommel/SGMD
pommy/S
pomp/M
pompadour/SMD
pompano/MS
pompom/SM
pomposity/M
pompous/YP
pompousness/M
ponce/GDS
poncho/SM
poncy
pond/MS
ponder/SZGDR
ponderer/M
ponderous/YP
ponderousness/M
pone/MS
pong/GDS
pongee/M
poniard/MS
pontiff/SM
pontifical/Y
pontificate/DSMG
pontoon/SM
pony/GDSM
ponytail/MS
poo/SGD
pooch/MDSG
poodle/SM
poof/MS
poofter/S
pooh/GMD
poohs
pool/GMDS
poolroom/MS
poolside/S
poop/GMDS
poor/TRYP
poorboy/M
poorhouse/SM
poorness/M
pop/SM
popcorn/M
pope/MS
popgun/SM
popinjay/MS
poplar/SM
poplin/M
popover/SM
poppa/MS
poppadom/S
popped
popper/SM
poppet/S
popping
poppy/SM
poppycock/M
populace/MS
popular/Y
popularity/UM
popularization/M
popularize/DSG
populate/ACGDS
populated/U
population/CM
populations
populism/M
populist/MS
populous/P
populousness/M
popup/MS
porcelain/SM
porch/MS
porcine
porcupine/SM
pore/MGDS
porgy/SM
pork/ZMR
porker/M
porky/RSMT
porn/M
porno/M
pornographer/MS
pornographic
pornographically
pornography/M
porosity/M
porous/P
porousness/M
porphyritic
porphyry/M
porpoise/MGDS
porridge/M
porringer/SM
port's/A
port/CAEGDS
portability/M
portable/MS
portage/DSMG
portal/SM
portcullis/MS
portend/SGD
portent/SM
portentous/YP
porter/ASM
porterhouse/SM
portfolio/MS
porthole/MS
portico/M
porticoes
portiere/MS
portion/KSGMD
porti�re/MS
portlet/SM
portliness/M
portly/RPT
portmanteau/MS
portrait/MS
portraitist/SM
portraiture/M
portray/SGD
portrayal/MS
portulaca/M
pose's/A
pose/CAKEGDS
poser/EKSM
poseur/SM
posh/TR
posit/DSG
position/CKEMS
positional/KE
positioned/K
positioning/AK
positive/EMYPS
positiveness/M
positivism
positivist/S
positivity/SM
positron/MS
poss
posse/MS
possess/AEVGSD
possession/ASM
possessive/SMYP
possessiveness/M
possessor/SM
possibility/SM
possible/SM
possibly
possum/SM
post-partum
post/ZGMDRSJ
postage/M
postal
postbag/S
postbox/S
postcard/SM
postcode/S
postcolonial
postconsonantal
postdate/DSG
postdoc/MS
postdoctoral
poster/M
posterior/SM
posterity/M
postgraduate/SM
posthaste
posthitides
posthitis
posthumous/Y
posthypnotic
postie/S
postilion/SM
postillion/MS
postindustrial
posting/M
postlude/SM
postman/M
postmark/SMDG
postmaster/MS
postmen
postmenopausal
postmeridian
postmistress/MS
postmodern
postmodernism/M
postmodernist/MS
postmortem/SM
postnasal
postnatal/Y
postoperative
postpaid
postpartum
postpone/DSGL
postponement/SM
postprandial
postscript/SM
postseason/SM
postsynaptic
postulate/XDSMGN
postulation/M
postural
posture/MGJDS
posturing/M
postwar
postwoman
postwomen
posy/SM
pot/CSM
potability/M
potable/SM
potash/M
potassium/M
potato/M
potatoes
potbelly/DSM
potboiler/SM
potency/M
potent/Y
potentate/MS
potential/MYS
potentiality/SM
potentiate/GDS
potful/SM
pothead/SM
pother/SMDG
potherb/SM
potholder/MS
pothole/DRSMZG
pothook/SM
potion/SM
potluck/MS
potpie/SM
potpourri/SM
potsherd/SM
potshot/MS
pottage/M
potted
potter/GSMD
pottery/SM
potting
potty/PRSMT
pouch/MDSG
pouf/S
pouffe/S
poulterer/MS
poultice/DSMG
poultry/M
pounce/DSMG
pound's
pound/KDRSZG
poundage/M
pounding/SM
pour/GDSJ
pout/ZGMDRS
pouter/M
poutine/S
poverty/M
pow
powder/GSMD
powdery
power/MDSG
powerboat/MS
powerful/Y
powerhouse/SM
powerless/PY
powerlessness/M
powwow/SGMD
pox/MS
pp
ppm
ppr
pr
practicability/M
practicably
practical/SMY
practicality/SM
practice/DSMGB
practiced/U
practicum/SM
practitioner/SM
praecipe
praetor/SM
praetorian
pragmatic/MS
pragmatical/Y
pragmatism/M
pragmatist/MS
prairie/SM
praise/EDSMG
praiseworthiness/M
praiseworthy/P
praline/SM
pram/MS
prance/DRSMZG
prancer/M
prancing/Y
prang/DSG
prank/MDSG
prankster/SM
praseodymium/M
prat/S
prate/MZGDRS
prater/M
pratfall/SM
prattle/DRSMZG
prattler/M
prawn/MDSG
pray/ZGDRS
prayer/M
prayerful/Y
pre-fill/GDS
pre-programmed
pre-programming
preach/DRSZGL
preacher/M
preachment/M
preachy/RT
preadolescence/SM
preadolescent
preamble/MGDS
prearrange/LGDS
prearrangement/M
preassigned
precancel/SMDG
precancerous
precarious/PY
precariousness/M
precast
precaution/MS
precautionary
precede/DSG
precedence/M
precedent/SM
precedential
precept/SM
preceptor/SM
precession
precessional
precinct/MS
preciosity/M
precious/YP
preciousness/M
precipice/SM
precipitant/MS
precipitate/XMYGNDS
precipitation/M
precipitous/Y
precis/M
precise/DRSYTGNP
preciseness/M
precision/M
preclude/GDS
preclusion/SM
precocious/YP
precociousness/M
precocity/M
precognition/M
precognitive
precolonial
preconceive/GDS
preconception/SM
precondition/MDGS
precook/GSD
precursor/SM
precursory
predate/DSG
predator/MS
predatory
predawn
predecease/GDS
predecessor/SM
predefined
predesignate/GDS
predestination/M
predestine/DSG
predetermination/M
predetermine/ZGDRS
predeterminer/M
predicable
predicament/MS
predicate/MGNVDS
predication/M
predicative/Y
predict/BGVSD
predictability/UM
predictable/U
predictably/U
prediction/SM
predictor/MS
predigest/GDS
predilection/SM
predispose/GDS
predisposition/MS
prednisone
predominance/M
predominant/Y
predominate/YGDS
preemie/SM
preeminence/M
preeminent/Y
preempt/GVSD
preemption/M
preemptive/Y
preen/DSG
preexist/DGS
preexistence/M
pref
prefab/SM
prefabbed
prefabbing
prefabricate/DSGN
prefabrication/M
preface/DSMG
prefatory
prefect/SM
prefecture/MS
prefer/SBL
preferably
preference/MS
preferential/Y
preferment/M
preferred
preferring
prefigure/GDS
prefill/GSD
prefix/MDSG
preform/GSD
prefrontal
pregame/SM
pregnancy/SM
pregnant
preheat/GSD
prehensile
prehistorian/S
prehistoric
prehistorical/Y
prehistory/M
prehuman
preinstall/D
prejudge/LGDS
prejudgement/MS
prejudgment/SM
prejudice/MGDS
prejudiced/U
prejudicial
prekindergarten/SM
prelacy/M
prelate/SM
prelim/SM
preliminarily
preliminary/SM
preliterate
preload/SGD
prelude/MS
premarital
premature/Y
premed/SM
premedical
premeditate/DSGN
premeditated/U
premeditation/M
premenstrual
premier/SGMD
premiere/MS
premiership/MS
premise/DSMG
premium/SM
premix/GDS
premolar/SM
premonition/MS
premonitory
prenatal/Y
prenup/SM
prenuptial
preoccupation/SM
preoccupy/DSG
preoperative
preordain/GDS
preowned
prep/MS
prepackage/DSG
prepacked
prepaid
preparation/SM
preparatory
prepare/ZGDRS
prepared/UP
preparedness/UM
prepay/GSL
prepayment/MS
prepend/DGS
preponderance/SM
preponderant/Y
preponderate/GDS
preposition/SM
prepositional/Y
prepossess/GDS
prepossessing/U
prepossession/SM
preposterous/Y
prepped
preppie/M
prepping
preppy/TRSM
preprocess/DSG
preprogrammed
preprogramming
prepubescence/M
prepubescent/SM
prepuce/MS
prequel/MS
prerecord/GSD
preregister/SGD
preregistration/M
prerequisite/MS
prerogative/SM
pres
presage/MGDS
presbyopia/M
presbyter/SM
presbytery/SM
preschool/SZMR
preschooler/M
prescience/M
prescient/Y
prescribe/DSG
prescript/SVM
prescription/SM
prescriptive/Y
preseason/SM
presence/SM
present/LMDRYZGSB
presentably
presentation/ASM
presenter/M
presentiment/SM
presentment/SM
preservation/M
preservationist/SM
preservative/SM
preserve/BDRSMZG
preserver/M
preset/S
presetting
preshrank
preshrink/GS
preshrunk
preside/GDS
presidency/SM
president/MS
presidential
presidia
presidium/M
presort/DGS
press's
press/ACGSD
pressed/U
presser/MS
pressie/S
pressing/SMY
pressman/M
pressmen
pressure/DSMG
pressured/U
pressurization/M
pressurize/CGDS
pressurized/U
pressurizer/SM
prestidigitation/M
prestige/M
prestigious
presto/SM
presumably
presume/GDSB
presumption/SM
presumptive
presumptuous/YP
presumptuousness/M
presuppose/DSG
presupposition/MS
pretax
preteen/MS
pretend/DRZGS
pretender/M
pretense/SXMN
pretension/M
pretentious/UY
pretentiousness/M
preterit/SM
preterite/MS
preterm
preternatural/Y
pretest/DGS
pretext/MS
pretrial/S
prettify/GDS
prettily
prettiness/M
pretty/TGDRSMP
pretzel/MS
prev
prevail/DGS
prevalence/M
prevalent
prevaricate/DSGNX
prevarication/M
prevaricator/SM
prevent/DBSGV
preventable/U
preventative/MS
prevention/M
preventive/SM
preview/MDRSZG
previous/Y
prevision/MS
prewar
prey/GMDS
prezzie/S
priapic
price's
price/AGDS
priceless
pricey
pricier
priciest
prick/MDRYSZG
pricker/M
prickle/MGDS
prickliness/M
prickly/PRT
pride/MGDS
prideful/Y
prier/M
priest/SMY
priestess/MS
priesthood/SM
priestliness/M
priestly/RTP
prig/MS
priggish/P
priggishness/M
prim/ZGDRYP
primacy/M
primal
primarily
primary/SM
primate/MS
prime/MS
primer/M
primeval
priming/M
primitive/SPMY
primitiveness/M
primmer
primmest
primness/M
primogenitor/SM
primogeniture/M
primordial/Y
primp/DSG
primrose/SM
primula/S
prince/SMY
princedom/SM
princeliness/M
princely/PRT
princess/MS
principal/SMY
principality/SM
principle/DSM
principled/U
print/AMDSG
printable/U
printer/MS
printing/SM
printmaking
printout/SM
prion/S
prior/MS
prioress/MS
prioritization
prioritize/DSG
priority/SM
priory/SM
prism/MS
prismatic
prison/SZMR
prisoner/M
prissily
prissiness/M
prissy/PTR
pristine
prithee
privacy/M
private/XMYTNRS
privateer/SM
privation/CSM
privatization/SM
privatize/DSG
privet/SM
privilege/DSMG
privileged/U
privily
privy/RSMT
prize/MGDS
prized/A
prizefight/ZGSMR
prizefighter/M
prizefighting/M
prizewinner/MS
prizewinning
pro/SM
proactive/Y
probabilistic
probability/SM
probable/SM
probably
probate/MN
probation/ZMR
probational
probationary
probationer/M
probe/MGDSBJ
probity/M
problem/MS
problematic/U
problematical/Y
probosces
proboscis/MS
procaine/M
procaryote/SM
procaryotic
procedural
procedure/SM
proceed/GJDS
proceeding/M
proceeds/M
process's
process/AGDS
processable
processed/U
procession/GD
processional/MS
processor/SM
proclamation/MS
proclivity/SM
procrastinate/DSGN
procrastination/M
procrastinator/MS
procreate/V
proctor/GMDS
procurement/M
prod/MS
prodigal/MYS
prodigality/M
prodigious/Y
prodigy/SM
produce's
produce/AZGDRS
producer/AM
producible/A
production/ASM
productive/UY
productiveness/M
productivity/M
prof/MS
profanation/MS
profane/PYGDS
profaneness/M
profanity/SM
professed/Y
profession/SM
professional/MYS
professionalism/M
professionalization
professionalize/DSG
professor/SM
professorial/Y
professorship/SM
proffer/GMDS
proficiency/M
proficient/MYS
profit/BGD
profitability/M
profitable/U
profitably/U
profiteer/MDGS
profiteering/M
profiterole/SM
profitless
profligacy/M
profligate/SMY
profound/RYTP
profoundness/M
profundity/SM
profuse/PY
profuseness/M
progenitor/SM
progeny/M
progesterone/M
progestin/S
prognathous
prognoses
prognosis/M
prognostic/MS
prognosticate/XGNDS
prognostication/M
prognosticator/MS
program/CAS
programed
programing
programmability
programmable/MS
programmatic
programmed/AC
programmer/MS
programming/SM
progress/MDSGV
progression/MS
progressive/PMYS
progressiveness/M
prohibit/DGVS
prohibition/SM
prohibitionist/MS
prohibitive/Y
prohibitory
project/GMDS
projectile/SM
projection/SM
projectionist/SM
projector/MS
prokaryote/MS
prokaryotic
prole/S
proletarian/MS
proletariat/M
proliferate/DSGN
proliferation/M
prolific
prolifically
prolix/Y
prolixity/M
prologue/SM
prolongation/SM
prom/M
promenade/MGDS
promethium/M
prominence/M
prominent/Y
promiscuity/M
promiscuous/Y
promise/DMG
promising/Y
promissory
promo/M
promontory/SM
promote/DRZG
promoter/M
promotional
prompt/JPSMDRYZTG
prompted/U
prompter/M
prompting/M
promptitude/M
promptness/M
promulgate/GNDS
promulgation/M
promulgator/MS
pronate/DSGN
pronator/SM
prone/P
proneness/M
prong/MDS
pronghorn/MS
pronominal/M
pronounce/DSLG
pronounceable/U
pronouncement/SM
pronto
pronunciation/MS
proof/ADGSM
proofread/SRZG
proofreader/M
prop/MS
propaganda/M
propagandist/MS
propagandize/GDS
propagate/DSGN
propagation/M
propagator/SM
propel/S
propellant/MS
propelled
propeller/SM
propelling
propensity/SM
proper/MRYT
property/DSM
prophecy/SM
prophesier/M
prophesy/DRSMZG
prophet/SM
prophetess/MS
prophetic
prophetical/Y
prophylactic/SM
prophylaxes
prophylaxis/M
propinquity/M
propitiate/DSGN
propitiation/M
propitiatory
propitious/Y
proponent/SM
proportion/ESM
proportional/YS
proportionality
proportionate/EY
proposal/MS
propped
propping
propranolol
proprietary/SM
proprieties/M
proprietor/SM
proprietorial/Y
proprietorship/SM
proprietress/MS
propriety/SM
propulsion/M
propulsive
propyl
prorate/DSGN
prorogation/M
prorogue/GD
prosaic
prosaically
proscenium/SM
prosciutto/M
proscribe/DG
proscription/MS
prose/M
prosecute/DSBXGN
prosecution/M
prosecutor/MS
prosecutorial
proselyte/DSMG
proselytism/M
proselytize/DRSZG
proselytizer/M
prosocial
prosody/SM
prospect/MDGVS
prospective/Y
prospector/SM
prospectus/MS
prosper/GSD
prosperity/M
prosperous/Y
prostate/MS
prostheses
prosthesis/M
prosthetic/S
prostitute/MGNDS
prostitution/M
prostrate/GNXDS
prostration/M
prosy/RT
protactinium/M
protagonist/SM
protean
protect/GVSD
protected/U
protection/SM
protectionism/M
protectionist/MS
protective/PY
protectiveness/M
protector/MS
protectorate/MS
protege/SM
protegee/S
protein/SM
protestant/S
protestation/MS
protestor/MS
prothonotarial
prothonotary/S
protocol/MS
proton/SM
protoplasm/M
protoplasmic
prototype/MGS
prototypical
protozoa
protozoan/MS
protozoic
protract/GD
protrude/GDS
protrusile
protrusion/MS
protuberance/MS
protuberant
prot�g�/MS
prot�g�e/S
proud/RYT
prov/NB
provability/M
provably
prove/EAGDS
proved/U
proven/U
provenance/SM
provender/M
provenience/M
proverbial/Y
provide/DRSZG
provided/U
providence/M
provident/Y
providential/Y
provider/M
province/MS
provincial/SMY
provincialism/M
provisional/Y
proviso/SM
provocateur/S
provocative/PY
provocativeness/M
provoke/DRSZG
provoked/U
provoker/M
provoking/Y
provolone/M
provost/SM
prow/MS
prowess/M
prowl/MDRSZG
prowler/M
proximal
proximate
proximity/M
proxy/SM
prude/MS
prudence/M
prudent/Y
prudential/Y
prudery/M
prudish/YP
prudishness/M
prune/MZGDRS
pruner/M
pruno
prurience/M
prurient/Y
pry/ZTGDRSM
pr�cis/MDG
psalm/MS
psalmist/SM
psaltery/SM
psephologist/S
psephology
pseud/S
pseudo/S
pseudonym/SM
pseudonymous
pseudorandom/Y
pseudoscience/MS
pseudy
pshaw/MS
psi/SM
psittacosis/M
psoriasis/M
psst
psych/MDSG
psyche/M
psychedelia
psychedelic/SM
psychedelically
psychiatric
psychiatrist/SM
psychiatry/M
psychic/MS
psychical/Y
psycho/SM
psychoactive
psychoanalyses
psychoanalysis/M
psychoanalyst/SM
psychoanalytic
psychoanalytical/Y
psychoanalyze/DSG
psychobabble/M
psychodrama/MS
psychogenic
psychokinesis
psychokinetic
psychological/Y
psychologist/MS
psychology/SM
psychometric
psychomotor
psychoneuroses
psychoneurosis/M
psychopath/M
psychopathic
psychopathology
psychopaths
psychopathy/M
psychopharmacology
psychophysiology
psychos/S
psychosis/M
psychosomatic
psychotherapist/MS
psychotherapy/SM
psychotic/SM
psychotically
psychotropic/MS
psychs
pt/C
ptarmigan/MS
pterodactyl/MS
ptomaine/SM
pub/SM
pubertal
puberty/M
pubes/M
pubescence/M
pubescent
pubic
pubis/M
public/AM
publican/AMS
publication/ASM
publicist/MS
publicity/M
publicize/GDS
publicly
publish/AGDS
publishable
published/U
publisher/MS
publishing/M
puce/M
puck/ZMRS
pucker/MDG
puckish/YP
puckishness/M
pud/S
pudding/SM
puddle/DSMG
puddling/M
pudenda
pudendum/M
pudginess/M
pudgy/PRT
pueblo/SM
puerile
puerility/M
puerperal
puff/ZGMDRS
puffball/SM
puffer/M
puffin/SM
puffiness/M
puffy/PRT
pug/SM
pugilism/M
pugilist/SM
pugilistic
pugnacious/YP
pugnaciousness/M
pugnacity/M
puke/MGDS
pukka
pulchritude/M
pulchritudinous
pule/GDS
pull/ZGMDRS
pullback/MS
puller/M
pullet/SM
pulley/SM
pullout/MS
pullover/SM
pulmonary
pulp/GMDS
pulpiness/M
pulpit/SM
pulpwood/M
pulpy/RPT
pulsar/SM
pulsate/XGNDS
pulsation/M
pulse/AMGDS
pulverization/M
pulverize/DSG
puma/MS
pumice/SM
pummel/SGD
pump/ZGMDRS
pumper/M
pumpernickel/M
pumpkin/MS
pun/SM
punch/MDRSZG
punchbag/S
puncheon/MS
puncher/M
punchline/S
punchy/TR
punctilio/M
punctilious/PY
punctiliousness/M
punctual/Y
punctuality/M
punctuate/GNDS
punctuation/M
puncture/DSMG
pundit/SM
punditry/M
pungency/M
pungent/Y
puniness/M
punish/BLGDS
punished/U
punishing/Y
punishment/MS
punitive/Y
punk/TMRS
punned
punnet/S
punning
punster/SM
punt/ZGMDRS
punter/M
puny/TRP
pup/SM
pupa/M
pupae
pupal
pupate/DSG
pupil/MS
pupped
puppet/MS
puppeteer/SM
puppetry/M
pupping
puppy/SM
purblind
purchase/DRSMZGB
purchaser/M
purdah/M
pure/PYTR
purebred/SM
puree/MDS
pureeing
pureness/M
purgative/SM
purgatorial
purgatory/SM
purge/MZGDRS
purger/M
purification/M
purifier/M
purify/NDRSZG
purine/MS
purism/M
purist/MS
puristic
puritan/SM
puritanical/Y
puritanism/M
purity/M
purl/GMDS
purlieu/SM
purloin/SGD
purple/MTRS
purplish
purport/SMDG
purported/Y
purpose's
purpose/ADSG
purposeful/YP
purposefulness/M
purposeless/PY
purposely
purr/GMDS
purse/MZGDRS
purser/M
pursuance/M
pursuant
pursue/ZGDRS
pursuer/M
pursuit/SM
purulence/M
purulent
purvey/DSG
purveyance/M
purveyor/SM
purview/M
pus/M
push/ZGMDRS
pushbike/S
pushcart/SM
pushchair/S
pusher/M
pushily
pushiness/M
pushover/MS
pushpin/S
pushup/MS
pushy/TRP
pusillanimity/M
pusillanimous/Y
puss/MS
pussy/TRSM
pussycat/MS
pussyfoot/DSG
pustular
pustule/SM
put/ISM
putative
putdown/SM
putout/MS
putrefaction/M
putrefactive
putrefy/GDS
putrescence/M
putrescent
putrid
putsch/MS
putt/ZGMDRS
putted/I
puttee/MS
putter/MDRZG
putterer/M
putting/I
putty/GDSM
putz/S
puzzle/MZGDRSL
puzzlement/M
puzzler/M
pvt
pwn/SGD
pyelonephritis
pygmy/SM
pylon/SM
pylori
pyloric
pylorus/M
pyorrhea/M
pyramid/GSMD
pyramidal
pyre/MS
pyrimidine/MS
pyrite/SM
pyrites/M
pyromania/M
pyromaniac/SM
pyrotechnic/S
pyrotechnical
pyrotechnics/M
pyruvate
python/SM
pyx/MS
pzazz
q
qr
qt/S
qty
qua
quack/GMDS
quackery/M
quad/MS
quadrangle/SM
quadrangular
quadrant/MS
quadraphonic
quadratic/MS
quadrature
quadrennial
quadrennium/MS
quadriceps/MS
quadrilateral/SM
quadrille/XMNS
quadrillion/M
quadriplegia/M
quadriplegic/SM
quadrivium/M
quadruped/MS
quadrupedal
quadruple/MGDS
quadruplet/MS
quadruplicate/MGNDS
quadruplication/M
quaff/GMDS
quagmire/SM
quahog/MS
quail/GMDS
quaint/PRYT
quaintness/M
quake/MGDS
quaky
qualification/EM
qualified/U
qualifier/SM
qualify/EGXNDS
qualitative/Y
quality/SM
qualm/MS
qualmish
quandary/SM
quango/S
quanta
quantifiable
quantification/M
quantifier/M
quantify/NDRSZG
quantitation
quantitative/Y
quantity/SM
quantization
quantize/D
quantum/M
quarantine/MGDS
quark/MS
quarrel/SZGMDR
quarreler/M
quarrelsome/P
quarrelsomeness/M
quarry/DSMG
quart/MS
quarter/SGMDY
quarterback/GMDS
quarterdeck/MS
quarterfinal/SM
quarterly/SM
quartermaster/MS
quarterstaff/M
quarterstaves
quartet/SM
quartile/S
quarto/MS
quartz/M
quasar/MS
quash/GDS
quasi
quatrain/MS
quaver/MDSG
quavery
quay/MS
quayside/S
queasily
queasiness/M
queasy/TPR
queen/GMDYS
queenly/RT
queer/PTGMDRYS
queerness/M
quell/GDS
quench/ZGDRSB
quenchable/U
quencher/M
quenchless
querulous/YP
querulousness/M
query/DSMG
ques
quesadilla/MS
quest/IFAMS
quested
questing
question/SMDRZGBJ
questionable/U
questionably/U
questioned/U
questioner/M
questioning/MY
questionnaire/SM
queue's
queue/CDSG
quibble/DRSMZG
quibbler/M
quiche/SM
quick/MNRYXTP
quicken/DG
quickfire
quickie/SM
quicklime/M
quickness/M
quicksand/MS
quicksilver/M
quickstep/MS
quid/MS
quiescence/M
quiescent/Y
quiet/SMDNRYXTGP
quieten/DG
quietism
quietness/M
quietude/IEM
quietus/MS
quiff/S
quill/SM
quilt/SMDRZG
quilter/M
quilting/M
quin/S
quince/SM
quine/S
quinidine
quinine/M
quinoa
quinsy/M
quint/SM
quintessence/SM
quintessential/Y
quintet/SM
quintuple/MGDS
quintuplet/MS
quip/MS
quipped
quipping
quipster/SM
quire's
quire/IAS
quirk/SMDG
quirkiness/M
quirky/RTP
quirt/SM
quisling/SM
quit/S
quitclaim/MS
quite
quittance/M
quitter/SM
quitting
quiver/SMDG
quivery
quixotic
quixotically
quiz/M
quizzed
quizzer/SM
quizzes
quizzical/Y
quizzing
quo/H
quoin/SM
quoit/SMDG
quondam
quorate/I
quorum/SM
quot/B
quota/SM
quotability/M
quotation/SM
quote's
quote/UDSG
quotidian
quotient/SM
qwerty
r/S
rabbet/GMDS
rabbi/SM
rabbinate/M
rabbinic
rabbinical
rabbit/GMDS
rabble/MS
rabid/PY
rabidness/M
rabies/M
raccoon/MS
race/MZGDRS
racecourse/SM
racegoer/S
racehorse/MS
raceme/MS
racer/M
racetrack/MS
raceway/MS
racial/Y
racialism/M
racialist/MS
racily
raciness/M
racing/M
racism/M
racist/SM
rack/GMDS
racket/SMDG
racketeer/SMDG
racketeering/M
raconteur/SM
racoon
racquet/SM
racquetball/SM
racy/PRT
rad/SM
radar/SM
radarscope/SM
raddled
radial/SMY
radian/S
radiance/M
radiant/Y
radiate/DSGNX
radiation/M
radiator/SM
radical/SMY
radicalism/M
radicalization/M
radicalize/DSG
radicchio/M
radii
radio/MDGS
radioactive/Y
radioactivity/M
radiocarbon/M
radiogram/MS
radiographer/SM
radiography/M
radioisotope/MS
radiologist/SM
radiology/M
radioman/M
radiomen
radiometer/MS
radiometric
radiometry/M
radiophone/SM
radioscopy/M
radiosonde/SM
radiosurgery
radiotelegraph/M
radiotelegraphs
radiotelegraphy/M
radiotelephone/MS
radiotherapist/MS
radiotherapy/M
radish/MS
radium/M
radius/M
radon/M
raffia/M
raffish/YP
raffishness/M
raffle/DSMG
raft/ZGMDRS
rafter/M
rafting/M
rag/SGMD
raga/MS
ragamuffin/MS
ragbag/M
rage/MS
ragga
ragged/RYTP
raggedness/M
raggedy/RT
ragging
raging/Y
raglan/SM
ragout/SM
ragtag/S
ragtime/M
ragweed/M
ragwort
rah
raid/ZGMDRS
raider/M
rail's
rail/CGDS
railcard/S
railing/SM
raillery/SM
railroad/SZGMDR
railroader/M
railroading/M
railway/SM
railwayman
railwaymen
raiment/M
rain/GMDS
rainbow/SM
raincoat/SM
raindrop/SM
rainfall/SM
rainmaker/SM
rainmaking/M
rainproof
rainstorm/MS
rainwater/M
rainy/RT
raise/MZGDRS
raiser/M
raisin/SM
raja/MS
rajah/M
rajahs
rake/MGDS
rakish/YP
rakishness/M
rally/DSMG
ram/SMN
ramble/DRSMZGJ
rambler/M
rambunctious/PY
rambunctiousness/M
rambutan/S
ramekin/SM
ramie/M
ramification/M
ramify/DSXNG
ramjet/SM
rammed
ramming
ramp/GMDS
rampage/DSMG
rampancy/M
rampant/Y
rampart/SM
ramrod/SM
ramrodded
ramrodding
ramshackle
ran/A
ranch/MDRSZG
rancher/M
ranching/M
rancid/P
rancidity/M
rancidness/M
rancor/M
rancorous/Y
rand/M
randiness/M
rando/S
random/PSY
randomization/M
randomize/DSG
randomness/MS
randy/RTP
ranee/MS
rang/ZR
range's
range/CGDS
rangefinder/S
ranger/M
ranginess/M
rangy/RTP
rani/MS
rank/TGJPMDRYS
ranking/M
rankle/DSG
rankness/M
ransack/SGD
ransom/SZGMDR
ransomer/M
ransomware
rant/ZGMDJRS
ranter/M
rap/SZGMDR
rapacious/PY
rapaciousness/M
rapacity/M
rape/MS
raper/M
rapeseed/M
rapid/PMRYTS
rapidity/M
rapidness/M
rapier/SM
rapine/M
rapist/SM
rapped
rappel/SM
rappelled
rappelling
rapper/SM
rapping
rapport/MS
rapporteur/S
rapprochement/SM
rapscallion/MS
rapt/YP
raptness/M
raptor/S
rapture/MS
rapturous/Y
rare/YTGPDRS
rarebit/MS
rarefaction/M
rarefy/GDS
rareness/M
rarity/SM
rascal/SMY
rash/ZTMRSYP
rasher/M
rashness/M
rasp/GMDS
raspberry/SM
raspy/RT
raster
rasterization/M
rasterize/DRSG
rat/SM
ratatouille/M
ratbag/S
ratchet/GMDS
rate/JXMZGNDRS
rated/U
ratepayer/S
rater/M
rather
rathskeller/SM
ratification/M
ratifier/M
ratify/NDRSZG
rating/M
ratio/MS
ratiocinate/GNDS
ratiocination/M
ration/MDG
rational/SMY
rationale/MS
rationalism/M
rationalist/SM
rationalistic
rationality/M
rationalization/MS
rationalize/DSG
ratlike
ratline/SM
rattan/SM
ratted
ratter/SM
ratting
rattle/DRSMZGJ
rattlebrain/SMD
rattler/M
rattlesnake/SM
rattletrap/SM
rattly
rattrap/SM
ratty/RT
raucous/YP
raucousness/M
raunchily
raunchiness/M
raunchy/TRP
ravage/DRSMZG
ravager/M
ravages/M
rave/JMZGDRS
ravel's
ravel/UDSG
raveling/S
raven/MDSG
ravenous/Y
ravine/SM
raving/M
ravioli/SM
ravish/DRSZGL
ravisher/M
ravishing/Y
ravishment/M
raw/PTMR
rawboned
rawhide/M
rawness/M
ray/SM
rayon/M
raze/GDS
razor/MS
razorback/MS
razz/GMDS
razzmatazz/M
rcpt
rd
re/DSMYTGVJ
reach/MDSGB
reachable/U
reacquire/DSG
react/V
reactance
reactant/SM
reactionary/SM
reactivity/M
read/ZGMRBJS
readability/SM
reader/M
readership/SM
readily
readiness/M
reading/M
readmitted
readout/SM
ready/DRSTGP
reafforestation
real/TMRYPS
realism/M
realist/SM
realistic/U
realistically/U
realities
reality/UM
realization/MS
realize/DSBG
realized/U
realm/MS
realness/M
realpolitik/M
realtor/SM
realty/M
ream/ZGMDRS
reamer/M
reap/ZGDRS
reaper/M
rear/GMDS
rearguard/MS
rearmost
rearward/S
reason/SMDRZGB
reasonable/UP
reasonableness/UM
reasonably/U
reasoner/M
reasoning/M
reassemble/DSG
reassuring/Y
rebate/M
rebel/MS
rebellion/MS
rebellious/YP
rebelliousness/M
rebid/S
rebidding
rebirth/M
reboil/SDG
rebrand/G
rebuild/SG
rebuke/DSMG
rebuking/Y
rebuttable
rebuttal/MS
rec'd
rec/M
recalcitrance/M
recalcitrant
recant/SDG
recantation/SM
recap/MS
recapitalization
recce/S
recd
receipt/SMDG
receivables/M
receive/DRSZGB
receiver/M
receivership/M
recency
recent/RYTP
recentness/M
receptacle/SM
reception/MS
receptionist/SM
receptive/PY
receptiveness/M
receptivity/M
receptor/SM
recess/MDSGV
recessional/SM
recessionary
recessive/SM
recherche
recherch�
recidivism/M
recidivist/SM
recipe/SM
recipient/SM
reciprocal/SMY
reciprocate/GNDS
reciprocation/M
reciprocity/M
recital/SM
recitalist/MS
recitative/MS
reciter/SM
reckless/YP
recklessness/M
reckon/SJDG
reckoning/M
reclamation/M
recline/DRSZG
recliner/M
recluse/SMV
recognizable/U
recognizably/U
recognize/DRSGB
recognized/U
recoilless
recombination
recommend/ZR
recompense/DSMG
recompile/GD
recon/S
reconcile/GDSB
reconciliation/S
recondite
reconfiguration
reconnaissance/MS
reconnoiter/DGS
reconstruct/V
reconstructed/U
recorded/U
recorder/MS
recording/MS
recoup/DG
recourse/M
recoverable/U
recovery/SM
recreant/MS
recreational
recriminate/DSGNX
recrimination/M
recriminatory
recrudesce/GDS
recrudescence/M
recrudescent
recruit/LSMDRZG
recruiter/M
recruitment/M
rectal/Y
rectangle/MS
rectangular
rectifiable
rectification/M
rectifier/M
rectify/XNDRSZG
rectilinear
rectitude/M
recto/MS
rector/SM
rectory/SM
rectum/SM
recumbent
recuperate/GNVDS
recuperation/M
recur/S
recurred
recurrence/SM
recurrent/Y
recurring
recurse/XNV
recusal/S
recuse/DSG
recyclable/SM
recycling/M
red/PSM
redact/SDG
redacted/U
redaction/SM
redactor/SM
redbird/SM
redbreast/MS
redbrick
redcap/SM
redcoat/SM
redcurrant/S
redden/SDG
redder
reddest
reddish
redeem/RZB
redeemer/M
redemption/M
redemptive
redesign/DSG
redhead/SMD
redirection
redistrict/GD
redivide/GDS
redlining/M
redneck/SM
redness/M
redo/G
redolence/M
redolent
redoubt/SBM
redoubtably
redound/SDG
redraw/SG
redskin/SM
reduce/DRSZG
reducer/M
reducible
reductase/M
reduction/SM
reductionist
reductive
redundancy/SM
redundant/Y
redwood/SM
redye/DS
reediness/M
reedy/RTP
reef/ZGMDRS
reefer/M
reek/GMDS
reel's
reel/UGDS
reeve/G
reexport/SDG
ref/SZM
refashion/DGS
refection/M
refectory/SM
refer/B
referee/DSM
refereeing
reference/MGDS
referendum/MS
referent/SM
referential
referral/SM
referred
referrer/SM
referring
reffed
reffing
refile/DSG
refill/BM
refined/U
refinement/SM
refiner/SM
refinery/S
refitting
reflate/XDSGN
reflationary
reflect/GVSD
reflection/MS
reflective/Y
reflectivity
reflector/MS
reflexive/SMY
reflexivity
reflexology
reforge/DSG
reform/MZ
reformat/V
reformatory/SM
reformatting
reformed/U
reformist/S
refortify/GDS
refract/SGVD
refraction/M
refractory/SM
refrain/SGMD
refresh/ZGLDRS
refresher/M
refreshing/Y
refreshment/SM
refreshments/M
refrigerant/SM
refrigerate/DSGN
refrigeration/M
refrigerator/MS
refuge/SM
refugee/SM
refulgence/M
refulgent
refund/B
refurbishment/MS
refusal/MS
refutation/MS
refute/BDRSZG
refuter/M
reg
regal/DYG
regalement/M
regalia/M
regard/ESMDG
regardless
regards/M
regather/DGS
regatta/SM
regency/SM
regeneracy/M
regenerate/V
regex/M
regexp/S
reggae/M
regicidal
regicide/MS
regime/SM
regimen/SM
regiment/MDGS
regimental
regimentation/M
region/SM
regional/Y
regionalism/MS
register/GMDS
registered/U
registrant/MS
registrar/MS
registration/SM
registry/SM
reglet
regnant
regress/MDSGV
regression/MS
regret/SM
regretful/Y
regrettable
regrettably
regretted
regretting
regrind/GS
reground
regroup/DGS
regular/MYS
regularity/SM
regularization/M
regularize/DSG
regulate/CDSGNV
regulated/U
regulation/CM
regulations
regulator/MS
regulatory
regurgitate/DSGN
regurgitation/M
rehab/MS
rehabbed
rehabbing
rehabilitate/GNVDS
rehabilitation/M
rehang/SDG
rehears/GD
rehearsal/MS
rehearsed/U
rehi
rehung
reify/NDSG
reign/MDSG
reignite/DSG
reimburse/BDSGL
reimbursement/MS
rein/GD
reindeer/M
reinforce/LGDS
reinforcement/SM
reinitialize
reinstall/DG
reinstatement/M
reinsurance
reiterate/V
reject/GSMD
rejection/SM
rejoice/JGDS
rejoicing/M
rejoinder/SM
rejuvenate/DSGN
rejuvenation/M
rel
relate/DRSBXZGNV
related/YP
relatedness/M
relater/M
relation/M
relational
relationship/MS
relative/MYS
relativism/M
relativist/S
relativistic
relativity/M
relax/DRSZG
relaxant/MS
relaxation/SM
relaxer/M
relay/D
release/B
released/U
relegate/GNDS
relent/SGD
relentless/PY
relentlessness/M
relevance/M
relevancy/M
relevant/Y
reliability/UM
reliable/U
reliably/U
reliance/M
reliant
relic/MS
relict/CSM
relief/SM
relieve/ZGDRS
reliever/M
religion/SM
religiosity
religious/MYP
religiousness/M
reline/DSG
relinquish/LDSG
relinquishment/M
reliquary/SM
relish/GMDS
relist/SGD
relocate/B
reluctance/M
reluctant/Y
rely/GDS
rem/M
remain/SGD
remainder/GMDS
remand/SGD
remapping
remark/B
remarkableness/M
remarkably
remarked/U
remeasure/GDS
remediable
remedy/GDSM
remember/DG
remembered/U
remembrance/MS
reminder/M
reminisce/GDS
reminiscence/MS
reminiscent/Y
remiss/PY
remissness/M
remit/S
remittance/SM
remitted
remitting/U
remix/DSG
remnant/MS
remodel/GDS
remold/SGD
remonstrant/SM
remonstrate/DSG
remorse/M
remorseful/Y
remorseless/PY
remorselessness/M
remote/RSMYTP
remoteness/M
removal/SM
remunerate/GNVXDS
remuneration/M
renaissance/MS
renal
renascence/S
rend/GS
render/SZGMDRJ
renderer/M
rendering/M
rendezvous/GMDS
rendition/MS
renegade/DSMG
renege/DRSZG
reneger/M
renew/DSBG
renewable/S
renewal/MS
rennet/M
rennin/M
renounce/LDSG
renouncement/M
renovate/DSXGN
renovation/M
renovator/MS
renown/MD
rent/ZGMDRS
rental/SM
renter/M
renunciation/SM
reopen/SDG
reorg/MDSG
rep/SM
repaint/GDS
repair/BZR
repairer/M
repairman/M
repairmen
reparable
reparation/MS
reparations/M
repartee/M
repatriate/XDSMGN
repatriation/M
repeat/SMDRZGB
repeatability
repeatable/U
repeatably
repeated/Y
repeater/M
repeating/M
repel/S
repelled
repellent/SM
repelling
repent/SDG
repentance/M
repentant/Y
repercussion/S
repertoire/MS
repertory/SM
repetition/MS
repetitious/YP
repetitiousness/M
repetitive/YP
repetitiveness/M
rephotograph/DG
replaceable
replant/GSD
replenish/LGDS
replenishment/M
replete/PDSGN
repleteness/M
repletion/M
replica/SM
replicate/DSGNX
replication/M
replicator/S
reportage/M
reported/Y
reportorial
reposeful
reposition
repository/SM
reprehend/DGS
reprehensibility/M
reprehensible
reprehensibly
reprehension/M
represent/GDS
representational
representative/MS
represented/U
repression/MS
repressive/PY
reprieve/DSMG
reprimand/GSMD
reprisal/SM
reprise/SMG
reproach/GMDSB
reproachful/Y
reprobate/MS
reproducibility
reproductive
reprogramming
reproving/Y
reptile/SM
reptilian/MS
republic/S
republicanism/M
repudiate/XGNDS
repudiation/M
repudiator/MS
repugnance/M
repugnant
repulsion/M
repulsive/YP
repulsiveness/M
repurchase/GDS
reputability/M
reputably/E
reputation/MS
reputational
repute/DSMGB
reputed/Y
request/GDR
requestor
requiem/SM
require/LDG
requirement/MS
requisite/XMNS
requisition/GMD
requital/M
requite/DRSZG
requited/U
requiter/M
reread/SG
rerecord/GDS
rerunning
resample/GDS
resat
rescind/SDG
rescission/M
rescue/DRSMZG
rescuer/M
reseal/B
resemble/DSG
resend
resent/LSDG
resentful/YP
resentfulness/M
resentment/MS
reserpine/M
reservation/MS
reserved/UY
reservedness/M
reservist/SM
reservoir/SM
resetting
reshipping
residence/SM
residency/SM
resident/MS
residential
residua
residual/MS
residue/SM
residuum/M
resignation/SM
resigned/Y
resilience/M
resiliency/M
resilient/Y
resinous
resist/SMDRZGV
resistance/SM
resistant/U
resistible
resistive/YP
resistivity
resistless
resistor/MS
resit/S
resitting
resold
resole/DSG
resolute/PY
resoluteness/M
resolve/RBM
resolved/U
resonance/SM
resonant/Y
resonate/GDS
resonator/SM
resorption/M
resound/SGD
resounding/Y
resourceful/YP
resourcefulness/M
resp
respect/ESGVMD
respectability/M
respectable
respectably
respectful/EY
respectfulness/M
respective/Y
respiration/M
respirator/SM
respiratory
respire/DG
resplendence/M
resplendent/Y
respond/SZGDR
respondent/SM
responder/M
response/MS
responsibility/SM
responsible
responsibly
responsive/UYP
responsiveness/UM
responsivity/M
rest/GVMDS
restate/GDS
restaurant/SM
restaurateur/MS
restful/YP
restfuller
restfullest
restfulness/M
restitution/M
restive/YP
restiveness/M
restless/PY
restlessness/M
restoration/SM
restorative/SM
restorer/SM
restrained/U
restraint/MS
restrict/SDGV
restricted/U
restriction/MS
restrictive/YP
restrictiveness/M
restring/SG
restroom/SM
restructuring/SM
result/GSMD
resultant/SM
resume/DSMG
resumption/MS
resupply/DSG
resurgence/MS
resurgent
resurrect/GSD
resurrection/MS
resuscitate/GNDS
resuscitation/M
resuscitator/SM
retailer/MS
retain/SDRZG
retainage/S
retainer/M
retake/G
retaliate/DSGNVX
retaliation/M
retaliatory
retard/SMDRZG
retardant/SM
retardation/M
retarder/M
retch/DSG
reteach/GS
retention/M
retentive/YP
retentiveness/M
rethink/SGM
rethought
reticence/M
reticent/Y
reticle/SM
reticulated
reticulation/MS
reticulum
retina/SM
retinal
retinoblastoma
retinue/SM
retiree/SM
retirement/MS
retort/GMD
retrace/GDS
retract/DBG
retractile
retraction/S
retrain/DGS
retread/D
retrenchment/MS
retribution/MS
retributive
retrieval/SM
retrieve/DRSMZGB
retriever/M
retro/MS
retroactive/Y
retrofire/GDS
retrofit/SM
retrofitted
retrofitting
retrograde/DSG
retrogress/GVDS
retrogression/M
retroreflector/S
retrorocket/MS
retrospect/MDSGV
retrospection/M
retrospective/MYS
retrovirus/MS
retsina/M
returnable/SM
returnee/SM
rev/ZVM
revamping/M
reveal/GJSD
revealed/U
revealing/Y
reveille/M
revel/JMDRSZG
revelation/SM
revelatory
reveler/M
revelry/SM
revenge/MGDS
revenuer/SM
reverb
reverberate/DSGNX
reverberation/M
revere/DSG
reverence/DSMG
reverend/SM
reverent/Y
reverential/Y
reverie/MS
revers/M
reversal/SM
reverse/Y
reversibility
reversible
reversibly
revert/GSD
revertible
revetment/SM
reviewability
revile/DRSLZG
revilement/M
reviler/M
reviser/MS
revision/SM
revisionism/M
revisionist/SM
revival/MS
revivalism/M
revivalist/SM
revive/DSG
revivification/M
revocable
revoke/DSG
revolt/GD
revolting/Y
revolution/SM
revolutionary/SM
revolutionist/SM
revolutionize/DSG
revolve/BZGDRS
revolver/M
revue/MS
revulsion/M
revved
revving
rewarded/U
rewarding/U
rewarm/GSD
rewash/GDS
reweave/GS
rewedding
rewind/MB
rewound
rewrite/MGS
rhapsodic
rhapsodical
rhapsodize/GDS
rhapsody/SM
rhea/MS
rhenium/M
rheostat/SM
rhesus/MS
rhetoric/M
rhetorical/Y
rhetorician/SM
rheum/M
rheumatic/MS
rheumatically
rheumatism/M
rheumatoid
rheumatological
rheumatologist/MS
rheumatology/M
rheumy
rhinestone/SM
rhinitis/M
rhino/MS
rhinoceros/MS
rhinoplasty
rhinovirus/MS
rhizome/MS
rho/SM
rhodium/M
rhododendron/SM
rhomboid/SM
rhomboidal
rhombus/MS
rhubarb/MS
rhyme/MZGDRS
rhymer/M
rhymester/MS
rhythm/SM
rhythmic
rhythmical/Y
rial/MS
rib/SM
ribald
ribaldry/M
ribbed
ribber/SM
ribbie/S
ribbing
ribbon/SM
riboflavin/M
rice/MZGDRS
ricer/M
rich/TMRSYP
richness/M
rick/GMDS
rickets/M
rickety/RT
rickrack/M
rickshaw/MS
ricochet/GMDS
ricotta/M
rid/S
riddance/M
ridden
ridding
riddle/DSMG
ride/MZGRS
rider/M
riderless
ridership/M
ridge/MGDS
ridgepole/SM
ridgy
ridicule/MGDS
ridiculous/YP
ridiculousness/M
riding/M
rife/TR
riff/GMDS
riffle/DSMG
riffraff/M
rifle/MZGDRS
rifleman/M
riflemen
rifler/M
rifling/M
rift/GMDS
rig/SM
rigamarole/SM
rigatoni/M
rigged
rigger/SM
rigging/M
right/MDRYSPTG
righteous/UP
righteously
righteousness/UM
rightful/PY
rightfulness/M
rightism/M
rightist/SM
rightmost
rightness/M
righto
rightsize/DSG
rightward/S
rigid/YP
rigidity/M
rigidness/M
rigmarole/MS
rigor/MS
rigorous/YP
rigorousness/M
rile/GDS
rill/MS
rim/SGMD
rime/MS
rimless
rimmed
rimming
rind/MS
ring/ZGMDRJ
ringer/M
ringgit/MS
ringleader/MS
ringlet/MS
ringlike
ringmaster/MS
ringside/M
ringtone/SM
ringworm/M
rink/MS
rinse/MGDS
riot/ZGMDRS
rioter/M
rioting/M
riotous/PY
rip/SXTMNR
riparian
ripcord/MS
ripe/YP
ripen/DG
ripened/U
ripeness/M
ripoff/SM
riposte/MGDS
ripped
ripper/SM
ripping
ripple/DSMG
ripply
ripsaw/SM
riptide/MS
rise/JMZGRS
risen
riser/M
risibility/M
risible
rising/M
risk/GMDS
riskily
riskiness/M
risky/RPT
risotto/MS
risque
risqu�
rissole/S
rite/MS
ritual/SMY
ritualism/M
ritualistic
ritualistically
ritualized
ritzy/RT
riv/ZNR
rival/MDSG
rivaled/U
rivalry/SM
rive/CGDS
river/M
riverbank/SM
riverbed/MS
riverboat/SM
riverfront
riverside/MS
rivet/MDRSZG
riveter/M
riviera/S
rivulet/MS
riyal/MS
rm
roach/GMDS
road/IMS
roadbed/SM
roadblock/MDSG
roadhouse/SM
roadie/MS
roadkill/M
roadmap/S
roadrunner/SM
roadshow/SM
roadside/SM
roadster/SM
roadway/SM
roadwork/SM
roadworthy
roam/ZGDRS
roamer/M
roaming/M
roan/MS
roar/ZGMDRS
roarer/M
roaring/M
roast/ZGMDRSJ
roaster/M
roasting/M
rob/S
robbed
robber/MS
robbery/SM
robbing
robe's
robe/EGDS
robin/MS
robocall/SGMD
robot/MS
robotic/S
robotics/M
robotize/GDS
robust/RYPT
robustness/M
rock/ZGMDRS
rockabilly/M
rockbound
rocker/M
rockery/S
rocket/MDSG
rocketry/M
rockfall/SM
rockiness/M
rocky/TRP
rococo/M
rod/SM
rode
rodent/MS
rodeo/MS
roe/SM
roebuck/SM
roentgen/MS
roger/GDS
rogue's
rogue/KS
roguery/M
roguish/YP
roguishness/M
roil/GDS
roister/ZGDRS
roisterer/M
role/MS
roll/MDRZGJS
rollback/SM
roller/M
rollerblading
rollerskating/M
rollick/SDG
rollicking/M
rollmop/S
rollout
rollover/SM
romaine/MS
roman/M
romance/MZGDRS
romancer/M
romantic/MS
romantically
romanticism/M
romanticist/SM
romanticize/DSG
romeo/MS
romp/MDRZGS
romper/M
rondo/SM
rood/MS
roof/MDRZGS
roofer/M
roofing/M
roofless
rooftop/SM
rook/MDGS
rookery/SM
rookie/SM
room/MDRZGS
roomer/M
roomette/SM
roomful/SM
roominess/M
roommate/SM
roomy/RTP
roost/SMDRZG
rooster/M
root/MDRZGS
rooter/M
rootkit/SM
rootless/P
rootlet/SM
rope/MZGDRS
roper/M
ropy/RT
rosary/SM
rose/MS
roseate
rosebud/SM
rosebush/MS
rosemary/M
rosette/SM
rosewater/M
rosewood/MS
rosily
rosin/SMDG
rosiness/M
roster/SM
rostrum/MS
rosy/RTP
rot/SM
rota/S
rotary/SM
rotatably
rotate/DSGNBX
rotation/M
rotational
rotator
rotatory
rote/M
rotgut/M
rotisserie/SM
rotogravure/MS
rotor/SM
rototiller/MS
rotted
rotten/TPRY
rottenness/M
rotter/S
rotting
rottweiler/S
rotund/P
rotunda/MS
rotundity/M
rotundness/M
roue/MS
rouge/DSMG
rough/MDNRYXTGP
roughage/M
roughcast
roughen/GD
roughhouse/MGDS
roughneck/GMDS
roughness/M
roughs
roughshod
roulette/M
round/PSMDRYZTG
roundabout/SM
roundel/S
roundelay/MS
roundhouse/SM
roundish
roundness/M
roundup/MS
roundworm/SM
rouse/DSG
roust/SDG
roustabout/SM
rout/MRZS
route's
route/ADSG
routeing
router/M
routine/MYS
routinize/GDS
roux
rou�/MS
rove/ZGDRS
rover/M
row/SZGMDR
rowan/S
rowboat/MS
rowdily
rowdiness/M
rowdy/PRSMT
rowdyism/M
rowel/SMDG
rower/M
rowing/M
rowlock/S
royal/SMY
royalist/SM
royalties/M
royalty/SM
rpm
rps
rt
rte
rub/SM
rubato/SM
rubbed
rubber/SM
rubberize/GDS
rubberneck/MDRSZG
rubbernecker/M
rubbery
rubbing/S
rubbish/MDSG
rubbishy
rubble/M
rubdown/SM
rube/MS
rubella/M
rubicund
rubidium/M
ruble/SM
rubric/SM
ruby/RSMT
ruched
ruck/DGS
rucksack/MS
ruckus/MS
ructions
rudder/SM
rudderless
ruddiness/M
ruddy/RTP
rude/YTRP
rudeness/M
rudiment/SM
rudimentary
rue/DSMG
rueful/PY
ruefulness/M
ruff/MDYGS
ruffian/MYS
ruffle/DSMG
ruffled/U
rug/SM
rugby/M
rugged/PTRY
ruggedness/M
rugger
rugrat/SM
ruin/MDGS
ruination/M
ruinous/Y
rule/MZGJDRS
ruler/M
ruling/M
rum/SM
rumba/SMDG
rumble/DSJMG
rumbling/M
rumbustious
ruminant/MS
ruminate/XGNVDS
rumination/M
ruminative/Y
rummage/DSMG
rummer
rummest
rummy/M
rumor/SMDG
rumormonger/SM
rump/MYS
rumple/DSMG
rumpus/MS
run/ASM
runabout/MS
runaround/SM
runaway/MS
rundown/SM
rune/MS
rung/MS
runic
runlet/SM
runnel/SM
runner/SM
running/M
runny/RT
runoff/SM
runt/MS
runtime
runty/RT
runway/SM
rupee/SM
rupiah/M
rupiahs
rupture/MGDS
rural
ruse/MS
rush/MDRSZG
rusher/M
rushy
rusk/MS
russet/SM
rust/MDGS
rustic/SM
rustically
rusticate/GDS
rustication/M
rusticity/M
rustiness/M
rustle/DRSJMZG
rustler/M
rustproof/SDG
rusty/RPNT
rut/SM
rutabaga/SM
ruthenium/M
rutherfordium/M
ruthless/YP
ruthlessness/M
rutted
rutting
rutty/RT
rye/M
s/NYXB
sabbath/M
sabbaths
sabbatical/SM
saber/MS
sable/MS
sabot/MS
sabotage/DSMG
saboteur/SM
sabra/MS
sabre/MS
sac/SM
saccharin/M
saccharine
sacerdotal
sachem/SM
sachet/SM
sack/ZGMDRJS
sackcloth/M
sacker/M
sackful/MS
sacking/M
sacra
sacrament/MS
sacramental
sacred/YP
sacredness/M
sacrifice/DSMG
sacrificial/Y
sacrilege/MS
sacrilegious/Y
sacristan/MS
sacristy/SM
sacroiliac/MS
sacrosanct/P
sacrosanctness/M
sacrum/M
sad/PY
sadden/SDG
sadder
saddest
saddle's
saddle/UDSG
saddlebag/MS
saddler/S
saddlery
sades
sadhu/S
sadism/M
sadist/SM
sadistic
sadistically
sadness/M
sadomasochism/M
sadomasochist/MS
sadomasochistic
safari/SGMD
safe/MYTPRS
safeguard/SMDG
safekeeping/M
safeness/M
safety/SM
safflower/MS
saffron/MS
sag/SM
saga/MS
sagacious/Y
sagacity/M
sage/MYTRS
sagebrush/M
sagged
sagging
saggy/RT
sago/M
saguaro/MS
sahib/MS
said/U
sail/GMDSJ
sailboard/MRZGS
sailboarder/M
sailboarding/M
sailboat/MS
sailcloth/M
sailfish/MS
sailing/M
sailor/SM
sailplane/MS
saint/MDYS
sainthood/M
saintlike
saintliness/M
saintly/PRT
saith
sake/M
saki/M
salaam/SMDG
salability
salable/U
salacious/PY
salaciousness/M
salacity/M
salad/MS
salamander/SM
salami/SM
salary/DSM
sale/ABMS
saleable/U
saleroom/S
salesclerk/SM
salesgirl/SM
saleslady/SM
salesman/M
salesmanship/M
salesmen
salespeople/M
salesperson/MS
salesroom/S
saleswoman/M
saleswomen
salience/M
salient/SMY
saline/SM
salinity/M
saliva/M
salivary
salivate/GNDS
salivation/M
sallow/RTP
sallowness/M
sally/DSMG
salmon/SM
salmonella/M
salmonellae
salon/MS
saloon/SM
salsa/MS
salt's
salt/CTGDS
saltbox/MS
saltcellar/SM
salted/U
salter
saltine/SM
saltiness/M
saltpeter/M
saltshaker/SM
saltwater/M
salty/RTP
salubrious/I
salutary
salutation/MS
salutatorian/MS
salutatory
salute/DSMG
salvage/DSMG
salvageable
salvation/M
salve/MZGDRS
salver/M
salvo/MS
samarium/M
samba/MDSG
same/SP
sameness/M
samey
samizdat/S
samosa/S
samovar/SM
sampan/SM
sample/DRSMZGJ
sampler/M
sampling/M
samurai/SM
sanatorium/SM
sanctification/M
sanctify/GDSN
sanctimonious/YP
sanctimoniousness/M
sanctimony/M
sanction/GSMD
sanctioned/U
sanctity/M
sanctuary/SM
sanctum/SM
sand/ZGMDRS
sandal/SM
sandalwood/M
sandbag/SM
sandbagged
sandbagger/SM
sandbagging
sandbank/MS
sandbar/SM
sandblast/ZGMDRS
sandblaster/M
sandbox/MS
sandcastle/MS
sander/M
sandhog/SM
sandiness/M
sandlot/SM
sandlotter/MS
sandman/M
sandmen
sandpaper/GMDS
sandpiper/MS
sandpit/S
sandstone/M
sandstorm/SM
sandwich/MDSG
sandy/RTP
sane/IYTR
saneness/M
sang/S
sangfroid/M
sangria/M
sanguinary
sanguine/Y
sanitarian/SM
sanitarium/SM
sanitary/IU
sanitation/M
sanitize/ZGDRS
sanity/IM
sank
sans
sanserif
sap/SM
sapience/M
sapiens
sapient
sapless
sapling/MS
sapped
sapper/S
sapphire/SM
sappiness/M
sapping
sappy/PRT
saprophyte/SM
saprophytic
sapsucker/SM
sapwood/M
saran/M
sarcasm/MS
sarcastic
sarcastically
sarcoma/MS
sarcophagi
sarcophagus/M
sardine/MS
sardonic
sardonically
sarge/MS
sari/MS
sarky
sarnie/S
sarong/SM
sarsaparilla/MS
sartorial/Y
sash/MS
sashay/SGMD
sass/GMDS
sassafras/MS
sassy/RT
sat
satanic
satanical/Y
satanism/M
satanist/MS
satay
satchel/MS
sate/GDS
sateen/M
satellite/DSMG
satiable/I
satiate/GNDS
satiation/M
satiety/M
satin/M
satinwood/SM
satiny
satire/SM
satiric
satirical/Y
satirist/SM
satirize/DSG
satisfaction/EM
satisfactions
satisfactorily/U
satisfactory/U
satisfied/U
satisfy/EDSG
satisfying/U
satisfyingly
sativa
satori/M
satrap/SM
satsuma/S
saturate/DSGN
saturated/U
saturation/M
saturnine
satyr/MS
satyriasis/M
satyric
sauce/MZGDRS
saucepan/SM
saucer/M
saucily
sauciness/M
saucy/RPT
sauerkraut/M
sauna/MDSG
saunter/MDGS
saurian
sauropod/SM
sausage/MS
saute/MS
sauteed
sauteing
sauternes/M
saut�/MDSG
savage/DRSMYTGP
savageness/M
savagery/SM
savanna/MS
savant/SM
save/BJMZGDRS
saveable
saved/U
saver/M
saving/M
savings/M
savior/SM
savoir
savor/MDSG
savoriness/M
savory/PTRSM
savoy/MS
savvy/DRSMTG
saw/SGMD
sawbones/M
sawbuck/MS
sawdust/M
sawfly/SM
sawhorse/SM
sawmill/MS
sawyer/SM
sax/MS
saxifrage/SM
saxophone/MS
saxophonist/SM
say's
say/USG
saying/SM
sayonara/S
scab/MS
scabbard/MS
scabbed
scabbiness/M
scabbing
scabby/PTR
scabies/M
scabrous
scad/MS
scaffold/SMG
scaffolding/M
scag/S
scagged
scalability
scalar/S
scalawag/MS
scald/MDSG
scale's
scale/CGDSB
scaleless
scalene
scaliness/M
scallion/MS
scallop/GSMD
scallywag/MS
scalp/MDRSZG
scalpel/SM
scalper/M
scaly/RTP
scam/MS
scammed
scammer/S
scamming
scamp/MRSZ
scamper/GMD
scampi/M
scan/MS
scandal/SM
scandalize/DSG
scandalmonger/SM
scandalous/Y
scandium/M
scanned
scanner/SM
scanning
scansion/M
scant/CDSTG
scanter
scantily
scantiness/M
scantly
scantness/M
scanty/RSPT
scapegoat/SGMD
scapegrace/MS
scapula/M
scapulae
scapular/SM
scar/GMDS
scarab/SM
scarce/RYTP
scarceness/M
scarcity/SM
scare/MS
scarecrow/MS
scaremonger/SMG
scarf/MDSG
scarification/M
scarify/NDSG
scarily
scariness/M
scarlatina/M
scarlet/M
scarp/MDRSZG
scarper/DG
scarred
scarring
scarves
scary/RTP
scat/MS
scathing/Y
scatological
scatology/M
scatted
scatter/GJSMD
scatterbrain/SMD
scattering/M
scattershot
scatting
scatty
scavenge/ZGDRS
scavenger/M
scenario/MS
scenarist/MS
scene/MS
scenery/M
scenic
scenically
scent/CMS
scented/U
scenting
scentless
scepter/MS
sch
schadenfreude
schedule's
schedule/ADSG
scheduled/U
scheduler/S
schema/S
schemata
schematic/SM
schematically
schematize/GDS
scheme/DRSMZG
schemer/M
scherzo/MS
schilling/MS
schism/SM
schismatic/SM
schist/M
schistosomiasis
schizo/SM
schizoid/MS
schizophrenia/M
schizophrenic/SM
schlemiel/SM
schlep/SM
schlepp/GMDS
schlock/M
schlocky
schmaltz/M
schmaltzy/TR
schmo/M
schmoe/SM
schmooze/DRSZG
schmuck/MS
schnapps/M
schnaps
schnauzer/SM
schnitzel/SM
schnook/MS
schnoz/MS
schnozzle/SM
scholar/MYS
scholarship/MS
scholastic
scholastically
scholasticism
school/SGMD
schoolbag/MS
schoolbook/SM
schoolboy/MS
schoolchild/M
schoolchildren/M
schooldays
schooled/U
schoolfellow/SM
schoolgirl/SM
schoolhouse/SM
schooling/M
schoolkid/S
schoolmarm/SM
schoolmarmish
schoolmaster/MS
schoolmate/SM
schoolmistress/MS
schoolroom/SM
schoolteacher/MS
schoolwork/M
schoolyard/SM
schooner/SM
schrod/S
schuss/GMDS
schussboomer/MS
schwa/MS
sci
sciatic
sciatica/M
science/FMS
scientific/U
scientifically/U
scientist/SM
scimitar/SM
scintilla/MS
scintillate/DSGN
scintillation/M
scion/MS
scissor/GDS
scleroses
sclerosis/M
sclerotic
scoff/MDRSZG
scoffer/M
scofflaw/MS
scold/MDSGJ
scolding/M
scoliosis/M
sconce/SM
scone/MS
scooch/DSG
scoop/MDSG
scoopful/MS
scoot/DRSZG
scooter/M
scope/MGDS
scorbutic
scorch/MDRSZG
scorcher/M
score/MZGDRS
scoreboard/SM
scorebook/SM
scorecard/MS
scorekeeper/MS
scoreless
scoreline/S
scorer/M
scoresheet/SM
scorn/MDRSZG
scorner/M
scornful/Y
scorpion/MS
scot-free
scotch/MDSG
scotchs
scoundrel/MS
scour/DRSZG
scourer/M
scourge/DSMG
scout/MDRSZG
scouting/M
scoutmaster/MS
scow/MS
scowl/MDSG
scrabble/MZGDRS
scrabbler/M
scrag/MS
scraggly/RT
scraggy/TR
scram/S
scramble's
scramble/UGDS
scrambler/MS
scrammed
scramming
scrap/MDRSZGJ
scrapbook/SM
scrape/SM
scraper/M
scrapheap/SM
scrapie
scrapped
scrapper/MS
scrapping
scrappy/TR
scrapyard/SM
scratch/GMDS
scratchcard/S
scratched/U
scratchily
scratchiness/M
scratchpad/S
scratchy/PRT
scrawl/SMDG
scrawly
scrawniness/M
scrawny/PTR
scream/SMDRZG
screamer/M
screaming/Y
scree/MDS
screech/GMDS
screechy/TR
screed/S
screen/SJMDRZG
screening/M
screenplay/SM
screensaver/SM
screenshot/SM
screenwriter/SM
screenwriting/M
screw's
screw/UDSG
screwball/MS
screwdriver/MS
screwiness/M
screwworm/SM
screwy/PRT
scribal
scribble/MZGDRS
scribbler/M
scribe's
scribe/CKIS
scrim/MS
scrimmage/MGDS
scrimp/SDG
scrimshaw/MDGS
scrip/MS
script/FSMDG
scripted/U
scriptural
scripture/MS
scriptwriter/SM
scrivener/SM
scrod/M
scrofula/M
scrofulous
scrog/S
scroll/GSMD
scrollbar/S
scrooge/MS
scrota
scrotal
scrotum/M
scrounge/DRSZG
scrounger/M
scroungy/TR
scrub/MS
scrubbed
scrubber/SM
scrubbing
scrubby/RT
scruff/SM
scruffily
scruffiness/M
scruffy/RPT
scrum/S
scrumhalf
scrumhalves
scrummage/S
scrummed
scrumming
scrump/SGD
scrumptious/Y
scrumpy
scrunch/MDSG
scrunchie/M
scrunchy/SM
scruple/MGDS
scrupulosity/M
scrupulous/UPY
scrupulousness/UM
scrutineer/S
scrutinize/GDS
scrutiny/M
scuba/MDSG
scud/MS
scudded
scudding
scuff/MDSG
scuffle/MGDS
scull/MDRSZG
sculler/M
scullery/SM
scullion/SM
sculpt/SGD
sculptor/SM
sculptress/MS
sculptural
sculpture/DSMG
scum/MS
scumbag/MS
scummed
scumming
scummy/TR
scupper/MDGS
scurf/M
scurfy
scurrility/M
scurrilous/PY
scurrilousness/M
scurry/GDSM
scurvily
scurvy/TRM
scutcheon/SM
scuttle/MGDS
scuttlebutt/M
scuzzy/TR
scythe/DSMG
se
sea/SM
seabed/SM
seabird/MS
seaboard/SM
seaborne
seacoast/SM
seafarer/SM
seafaring/M
seafloor/SM
seafood/M
seafront/SM
seagoing
seagull/MS
seahorse/MS
seal's
seal/AUSDG
sealant/MS
sealer/SM
sealskin/M
seam/GMDNS
seaman/M
seamanship/M
seamless/Y
seamount/MS
seamstress/MS
seamy/RT
seance/SM
seaplane/SM
seaport/MS
sear/GMDS
search/AZGMDRS
searchable/U
searcher/AM
searching/Y
searchlight/MS
searing/Y
seascape/SM
seashell/SM
seashore/SM
seasick/P
seasickness/M
seaside/MS
season/SGMDBJ
seasonable/U
seasonably/U
seasonal/Y
seasonality
seasoned/U
seasoning/M
seat's
seat/UGDS
seating/M
seatmate/SM
seawall/MS
seaward/MS
seawater/M
seaway/SM
seaweed/MS
seaworthiness/M
seaworthy/P
sebaceous
seborrhea/M
sebum
sec'y
sec/SM
secant/SM
secateurs
secede/DSG
secession/M
secessionist/MS
seclude/GDS
seclusion/M
seclusive
second/SLZGMDRY
secondarily
secondary/SM
seconder/M
secondhand
secondment/S
secrecy/M
secret/SGVMDY
secretarial
secretariat/MS
secretary/SM
secretaryship/M
secrete/XNS
secretion/M
secretive/PY
secretiveness/M
secretory
sect/IMS
sectarian/MS
sectarianism/M
sectary/SM
section/AESM
sectional/MS
sectionalism/M
sectioned
sectioning
sector/ESM
secular
secularism/M
secularist/SM
secularization/M
secularize/DSG
secure/DRSYTG
secured/U
security/ISM
secy
sedan/MS
sedate/DRSYTGNVP
sedateness/M
sedation/M
sedative/SM
sedentary
sedge/M
sedgy
sediment/MS
sedimentary
sedimentation/M
sedition/M
seditious
seduce/DRSZG
seducer/M
seduction/SM
seductive/YP
seductiveness/M
seductress/MS
sedulous/Y
see/RSMZ
seed's
seed/AGDS
seedbed/MS
seedcase/MS
seeded/U
seeder/SM
seediness/M
seedless
seedling/MS
seedpod/MS
seedy/RPT
seeing/S
seek/ZGRS
seeker/M
seem/GDS
seeming/Y
seemliness/UM
seemly/URTP
seen/U
seep/GDS
seepage/M
seer/M
seersucker/M
seesaw/SMDG
seethe/DSG
segfault/S
segment/GSMD
segmentation/M
segmented/U
segregable
segregate/CDSGN
segregated/U
segregation/CM
segregationist/MS
segue/MGDS
segueing
seigneur/SM
seignior/SM
seigniorial
seine/MZGDRS
seiner/M
seismic
seismically
seismograph/ZMR
seismographer/M
seismographic
seismographs
seismography/M
seismologic
seismological
seismologist/MS
seismology/M
seize/GDS
seizure/MS
seldom/Y
select/CSGVD
selection/SM
selective/Y
selectivity/M
selectman/M
selectmen
selectness/M
selector/MS
selenium/M
selenographer/MS
selenography/M
self/GM
selfie/SM
selfish/UYP
selfishness/UM
selfism
selfist/S
selfless/PY
selflessness/M
selfsame
sell's
sell/AZGRS
seller's
selloff/MS
sellotape/DSG
sellout/MS
seltzer/MS
selvage/MS
selvedge/MS
selves
semantic/S
semantically
semanticist/MS
semantics/M
semaphore/DSMG
semblance/ASM
semen/M
semester/SM
semi/MS
semiannual/Y
semiarid
semiautomatic/MS
semibreve/S
semicircle/SM
semicircular
semicolon/MS
semiconducting
semiconductor/MS
semiconscious
semidarkness/M
semidetached
semifinal/SM
semifinalist/MS
semigloss/S
semimonthly/SM
seminal
seminar/MS
seminarian/SM
seminary/SM
semiofficial
semiotic/S
semiotics/M
semipermeable
semiprecious
semiprivate
semipro/S
semiprofessional/SM
semiquaver/S
semiretired
semiskilled
semisolid
semisweet
semitone/SM
semitrailer/MS
semitransparent
semitropical
semivowel/SM
semiweekly/SM
semiyearly
semolina/M
sempstress/MS
senate/SM
senator/MS
senatorial
send/ZGRS
sender/M
sendoff/MS
senescence/M
senescent
senile
senility/M
senior/SM
seniority/M
senna/M
senor/MS
senora/SM
senorita/SM
sensation/MS
sensational/Y
sensationalism/M
sensationalist/MS
sensationalistic
sensationalize/GDS
sense/MGDS
senseless/PY
senselessness/M
sensibilities
sensibility/IM
sensible/P
sensibleness/M
sensibly/I
sensitive/SMYP
sensitiveness/M
sensitivities
sensitivity/IM
sensitization/CM
sensitize/CDSG
sensor/SM
sensory
sensual/Y
sensualist/SM
sensuality/M
sensuous/YP
sensuousness/M
sent/FAU
sentence/MGDS
sententious/Y
sentience/IM
sentient/Y
sentiment/SM
sentimental/Y
sentimentalism/M
sentimentalist/MS
sentimentality/M
sentimentalization/M
sentimentalize/GDS
sentinel/MS
sentry/SM
sepal/MS
separability/IM
separable
separably/I
separate/XMYGNVDSP
separateness/M
separation/M
separatism/M
separatist/MS
separator/MS
sepia/M
sepsis/M
septa
septal
septet/SM
septic
septicemia/M
septicemic
septuagenarian/MS
septum/M
sepulcher/GMDS
sepulchral
seq
sequel/SM
sequence/MZGDRS
sequencing/M
sequential/FY
sequester/SDG
sequestrate/XGNDS
sequestration/M
sequin/SMD
sequinned
sequitur
sequoia/MS
sera
seraglio/MS
serape/SM
seraph/M
seraphic
seraphim
seraphs
sere/TR
serenade/MGDS
serendipitous
serendipity/M
serene/RPYT
sereneness/M
serenity/M
serf/MS
serfdom/M
serge/M
sergeant/MS
serial/SMY
serialization/SM
serialize/GDSB
series/M
serif/MS
serigraph/M
serigraphs
serine
serious/PY
seriousness/M
sermon/SM
sermonize/GDS
serology/M
serotonin
serous
serpent/MS
serpentine/M
serrate/XND
serration/M
serried
serum/MS
servant/MS
serve's/AF
serve/FACGDS
server/SM
servery/S
service/EMS
serviceability/M
serviceable
serviced
serviceman/M
servicemen
servicewoman/M
servicewomen
servicing
serviette/MS
servile
servility/M
serving's
servings
servitor/MS
servitude/M
servo/MS
servomechanism/SM
servomotor/MS
sesame/SM
sesquicentennial/MS
session/MS
set/AISM
setback/MS
setscrew/SM
setsquare/S
sett/BJZGRS
settee/MS
setter/M
setting/M
settle's
settle/AUGDS
settlement/AM
settlements
settler/SM
setup/MS
seven/MHS
seventeen/SMH
seventeenth/M
seventeenths
seventh/M
sevenths
seventieth/M
seventieths
seventy/SMH
sever/ETGDS
severability
several/MY
severance/SM
severe/YPR
severeness/M
severity/M
sew/ASGD
sewage/M
sewer/MS
sewerage/M
sewing/M
sewn/A
sex/GMDS
sexagenarian/SM
sexily
sexiness/M
sexism/M
sexist/MS
sexless
sexologist/SM
sexology/M
sexpot/MS
sextant/SM
sextet/MS
sexting
sexton/MS
sextuplet/SM
sexual/Y
sexuality/M
sexy/PTR
sf
sh
shabbily
shabbiness/M
shabby/PTR
shack/MDSG
shackle's
shackle/UGDS
shad/GMDSJ
shade/MS
shadily
shadiness/M
shading/M
shadow/SGMD
shadowbox/GDS
shadowy/RT
shady/RPT
shaft/MDSG
shag/MS
shagged
shagginess/M
shagging
shaggy/TPR
shah/M
shahs
shake/MZGRS
shakedown/SM
shaken/U
shakeout/MS
shaker/M
shakeup/MS
shakily
shakiness/M
shaky/RPT
shale/M
shall
shallot/MS
shallow/TPMRYS
shallowness/M
shalom
shalt
sham/GMDS
shaman/SM
shamanic
shamanism
shamanistic
shamble/MGDS
shambles/M
shambolic
shame/MS
shamefaced/Y
shameful/PY
shamefulness/M
shameless/YP
shamelessness/M
shammed
shamming
shampoo/ZGMDRS
shampooer/M
shamrock/MS
shan't
shandy/S
shanghai/DSG
shank/MS
shantung/M
shanty/SM
shantytown/SM
shape's
shape/AGDS
shaped/U
shapeless/YP
shapelessness/M
shapeliness/M
shapely/PTR
shapeshift/ZGDR
shard/MS
share/MZGDRSB
shareable
sharecrop/S
sharecropped
sharecropper/MS
sharecropping
shareholder/SM
shareholding/S
sharer/M
shareware/M
sharia/M
shariah
shark/MDSG
sharkskin/M
sharp/MDNRYSPXZTG
sharpen/ADGS
sharpener/MS
sharper/M
sharpie/M
sharpish
sharpness/M
sharpshooter/SM
sharpshooting/M
sharpy/SM
shat
shatter/GMDS
shatterproof
shave/MZGDRSJ
shaven/U
shaver/M
shaving/M
shawl/MS
shay/MS
she'd
she'll
she/DSM
sheaf/M
shear/MDRSZG
shearer/M
sheath/JM
sheathe/UGDS
sheathing/M
sheaths
sheave/DSMG
shebang/MS
shebeen/S
shed/MS
shedding
sheen/M
sheeny/TR
sheep/M
sheepdog/MS
sheepfold/SM
sheepherder/MS
sheepish/YP
sheepishness/M
sheepskin/MS
sheer/MDRSPTG
sheerness/M
sheet/MSG
sheeting/M
sheetlike
sheik/MS
sheikdom/MS
sheikh/M
sheikhdom/MS
sheikhs
sheila/S
shekel/SM
shelf/M
shell/MDRSG
shellac/MS
shellacked
shellacking/MS
shellfire/M
shellfish/MS
shelter/GMDS
shelve/GDS
shelving/M
shemale/MS
shenanigan/SM
shepherd/SMDG
shepherdess/MS
sherbet/SM
sherd/MS
sheriff/SM
sherry/SM
shew/GDS
shewn
shh
shiatsu/M
shibboleth/M
shibboleths
shield/MDGS
shift/ZGMDRS
shiftily
shiftiness/M
shiftless/PY
shiftlessness/M
shifty/RPT
shiitake/SM
shill/GMDSJ
shillelagh/M
shillelaghs
shilling/M
shim/MS
shimmed
shimmer/SMDG
shimmery
shimming
shimmy/DSMG
shin/ZGMDRS
shinbone/SM
shindig/SM
shine/MS
shiner/M
shingle/DSMG
shinguard/M
shininess/M
shinned
shinning
shinny/DSG
shinsplints/M
shiny/TRP
ship's
ship/ALS
shipboard/MS
shipbuilder/SM
shipbuilding/M
shipload/SM
shipmate/SM
shipment/AM
shipments
shipowner/MS
shipped/A
shipper/SM
shipping/M
shipshape
shipwreck/GMDS
shipwright/MS
shipyard/SM
shire/MS
shirk/ZGDRS
shirker/M
shirr/GMDSJ
shirring/M
shirt/GMDS
shirtfront/SM
shirting/M
shirtless
shirtsleeve/SM
shirttail/SM
shirtwaist/MS
shirty
shit/SM!
shitfaced/!
shithead/MS!
shitload/MS!
shitted/!
shitting/!
shitty/RT!
shiv/ZMRS
shiver/MDG
shivery
shoal/GMDS
shoat/MS
shock/ZGMDRS
shocker/M
shocking/Y
shockproof
shod/U
shoddily
shoddiness/M
shoddy/PRMT
shoe/MS
shoehorn/GMDS
shoeing
shoelace/MS
shoemaker/SM
shoeshine/SM
shoestring/SM
shoetree/MS
shogun/MS
shogunate/M
shone
shoo/GDS
shook
shoot/ZGMRSJ
shooter/M
shooting/M
shootout/MS
shop/MS
shopaholic/MS
shopfitter/S
shopfitting
shopfront/S
shopkeeper/MS
shoplift/DRZGS
shoplifter/M
shoplifting/M
shoppe/MZGDRS
shopper/M
shopping/M
shoptalk/M
shopworn
shore/MGDS
shorebird/SM
shoreline/MS
shoring/M
short/XTGMDNRYSP
shortage/MS
shortbread/M
shortcake/MS
shortchange/DSG
shortcoming/MS
shortcrust
shortcut/MS
shorten/JGD
shortening/M
shortfall/MS
shorthand/MD
shorthorn/MS
shortie/M
shortish
shortlist/DGS
shortness/M
shortsighted/PY
shortsightedness/M
shortstop/MS
shortwave/MS
shorty/SM
shot/MS
shotgun/SM
shotgunned
shotgunning
should
should've
shoulder/MDGS
shouldn't
shout/ZGMDRS
shouter/M
shove/MGDS
shovel/MDSG
shovelful/SM
show/JZGMDRS
showbiz/M
showboat/MDGS
showcase/MGDS
showdown/MS
shower/MDG
showerproof
showery
showgirl/MS
showground/S
showily
showiness/M
showing/M
showjumping
showman/M
showmanship/M
showmen
shown
showoff/SM
showpiece/SM
showplace/SM
showroom/MS
showstopper/MS
showstopping
showtime/MS
showy/TRP
shpt
shrank
shrapnel/M
shred/MS
shredded
shredder/MS
shredding
shrew/MS
shrewd/RYPT
shrewdness/M
shrewish
shriek/MDSG
shrift/M
shrike/MS
shrill/DRSPTG
shrillness/M
shrilly
shrimp/MDRSZG
shrine/MS
shrink/MSBG
shrinkage/M
shrive/GDS
shrivel/SGD
shriven
shroom/S
shroud/GMDS
shrub/MS
shrubbery/SM
shrubby/RT
shrug/MS
shrugged
shrugging
shrunk/N
shtick/MS
shuck/GMDS
shucks/S
shudder/MDSG
shuffle/AMGDS
shuffleboard/SM
shuffler/SM
shun/S
shunned
shunning
shunt/MSDG
shush/DSG
shut/S
shutdown/SM
shuteye/M
shutoff/SM
shutout/SM
shutter/SMDG
shutterbug/MS
shutting
shuttle/DSMG
shuttlecock/GMDS
shy/TGDRSMY
shyer
shyest
shyness/M
shyster/SM
sibilant/SM
sibling/SM
sibyl/MS
sibylline
sic/S
sicced
siccing
sick/PXTGDNRYS
sickbay/S
sickbed/SM
sicken/DG
sickening/Y
sickie/MS
sickish
sickle/MS
sickly/RT
sickness/MS
sicko/MS
sickout/SM
sickroom/MS
side's
side/AGDS
sidearm/SM
sidebar/SM
sideboard/SM
sideburns/M
sidecar/SM
sidekick/SM
sidelight/MS
sideline/DSMG
sidelong
sideman/M
sidemen
sidepiece/MS
sidereal
sidesaddle/MS
sideshow/MS
sidesplitting
sidestep/MS
sidestepped
sidestepping
sidestroke/DSMG
sideswipe/DSMG
sidetrack/SMDG
sidewalk/MS
sidewall/MS
sideways
sidewinder/SM
siding/MS
sidle/MGDS
siege/MS
sienna/M
sierra/MS
siesta/MS
sieve/MGDS
sift/ZGDRS
sifted/U
sifter/M
sigh/GMD
sighs
sight/GMDYSJ
sighting/M
sightless
sightly/UTR
sightread
sightseeing/M
sightseer/MS
sigma/MS
sign's/C
sign/AFCGDS
signage/M
signal/MDRYSZG
signaler/M
signalization/M
signalize/GDS
signalling
signalman/M
signalmen
signatory/SM
signature/MS
signboard/MS
signed/U
signer/CMS
signet/MS
significance/IM
significant/IY
signification/M
signify/XDSNG
signing/CSM
signor/FMS
signora/SM
signore
signori
signorina/MS
signorine
signpost/GSMD
signup/MS
silage/M
silence/DRSMZG
silencer/M
silent/MRYST
silhouette/DSMG
silica/M
silicate/MS
siliceous
silicon/SM
silicone/M
silicosis/M
silk/MNS
silkily
silkiness/M
silkscreen/SM
silkworm/MS
silky/TRP
sill/MS
silliness/M
silly/TRSMP
silo/MDS
silt/GMDS
silty/TR
silvan
silver/GMDS
silverfish/MS
silversmith/M
silversmiths
silverware/M
silvery
sim/SM
simian/MS
similar/Y
similarity/ESM
simile/MS
similitude/EM
simmer/GMDS
simonize/DSG
simony/M
simpatico
simper/GMDS
simpering/Y
simple/TRP
simpleminded
simpleness/M
simpleton/SM
simplex
simplicity/M
simplification/M
simplify/DSXNG
simplistic
simplistically
simply
simulacra
simulacrum/S
simulate/EDSGN
simulation/EM
simulations
simulator/EMS
simulcast/GMDS
simultaneity/M
simultaneous/Y
sin/ASM
since
sincere/IYT
sincerer
sincerity/IM
sine/MS
sinecure/MS
sinew/MS
sinewy
sinful/PY
sinfulness/M
sing/BZGMDRYS
singalong/S
singe/MS
singeing
singer/M
singing/M
single-handedly
single/PMGDS
singleness/M
singles/M
singlet/S
singleton/SM
singletree/SM
singsong/SMDG
singular/SMY
singularity/SM
sinister
sink/BZGMRS
sinkable/U
sinker/M
sinkhole/SM
sinless
sinned
sinner/MS
sinning
sinology
sinuosity/M
sinuous/Y
sinus/MS
sinusitis/M
sinusoidal
sip/SM
siphon/GMDS
sipped
sipper/SM
sipping
sir/SXMN
sire/CMGDS
siree/M
siren/M
sirloin/SM
sirocco/SM
sirrah
sirree/M
sis/MS
sisal/M
sissified
sissy/RSMT
sister/ASM
sisterhood/MS
sisterliness/M
sisterly/P
sit/S
sitar/SM
sitarist/MS
sitcom/SM
site/MGDS
sitemap/SM
sitter/SM
sitting/SM
situ
situate/DSXGN
situation/M
situational
six/MSH
sixfold
sixpence/MS
sixshooter/M
sixteen/SMH
sixteenth/M
sixteenths
sixth/M
sixths
sixtieth/M
sixtieths
sixty/SMH
size's
size/AGBDRS
sizeable
sizing/M
sizzle/DRSMZG
ska/M
skate/MZGDRS
skateboard/MDRSZG
skateboarder/M
skateboarding/M
skater/M
skating/M
skedaddle/MGDS
skeet/ZMR
skein/MS
skeletal
skeleton/SM
skeptic/SM
skeptical/Y
skepticism/M
sketch/MDRSZG
sketchbook/S
sketcher/M
sketchily
sketchiness/M
sketchpad/S
sketchy/RTP
skeuomorph
skeuomorphic
skeuomorphism
skeuomorphs
skew/MDRZGS
skewbald/S
skewer/MDG
ski/SZGMDR
skibob/S
skid/MS
skidded
skidding
skidpan/S
skier/M
skiff/SM
skiffle
skiing/M
skill's
skill/CSD
skilled/U
skillet/SM
skillful/UY
skillfulness/M
skim/MS
skimmed
skimmer/SM
skimming
skimp/SDG
skimpily
skimpiness/M
skimpy/RTP
skin/AMS
skincare/M
skinflick/MS
skinflint/MS
skinful
skinhead/MS
skinless
skinned/A
skinniness/M
skinning/A
skinny/RMTP
skint
skintight
skip/MS
skipped
skipper/SMDG
skipping
skirmish/ZGMDRS
skirt/SMDG
skit/MS
skitter/GSD
skittish/YP
skittishness/M
skittle/S
skive/DRSZG
skivvy/DSMG
skoal/SM
skua/S
skulduggery/M
skulk/SDRZG
skulker/M
skull/SM
skullcap/MS
skullduggery/M
skunk/SMDG
sky/GSM
skycap/SM
skydive/DRSZG
skydiver/M
skydiving/M
skyjack/JZGSDR
skyjacker/M
skyjacking/M
skylark/SGMD
skylight/MS
skyline/SM
skyrocket/GSMD
skyscraper/SM
skyward/S
skywriter/SM
skywriting/M
slab/MS
slabbed
slabbing
slack/PXZTGMDNRYS
slacken/DG
slacker/M
slackness/M
slacks/M
slag/MS
slagged
slagging
slagheap/S
slain
slake/GDS
slalom/MSDG
slam/MS
slammed
slammer/SM
slamming
slander/MZGDRS
slanderer/M
slanderous
slang/M
slangy/RT
slant/MSDG
slanting/Y
slantwise
slap/MS
slapdash
slaphappy
slapped
slapper/S
slapping
slapstick/M
slash/MDRSZG
slasher/M
slat/MDGS
slate/SM
slather/SDG
slatted
slattern/SMY
slaughter/MDRZGS
slaughterer/M
slaughterhouse/MS
slave/DRSMZG
slaveholder/MS
slaver/MDG
slavery/M
slavish/PY
slavishness/M
slaw/M
slay/DRZGJS
slayer/M
slaying/M
sleaze/SM
sleazebag/S
sleazeball/S
sleazily
sleaziness/M
sleazy/PRT
sled/MS
sledded
sledder/SM
sledding
sledge/DSMG
sledgehammer/GSMD
sleek/SDRYTGP
sleekness/M
sleep/SMRZG
sleeper/M
sleepily
sleepiness/M
sleepless/PY
sleeplessness/M
sleepover/SM
sleepwalk/ZGSDR
sleepwalker/M
sleepwalking/M
sleepwear/M
sleepy/RPT
sleepyhead/MS
sleet/SMDG
sleety
sleeve/DSM
sleeveless
sleigh/MDG
sleighs
sleight/SM
slender/PRT
slenderize/DSG
slenderness/M
slept
sleuth/MG
sleuths
slew/MDGS
slice/DRSMZG
slicer/M
slick/SMDRYZTGP
slicker/M
slickness/M
slid
slide/RSMZG
slider/M
slideshow/MS
slight/SMDRYTGP
slightness/M
slim/PS
slime/M
sliminess/M
slimline
slimmed
slimmer/S
slimmest
slimming/M
slimness/M
slimy/RTP
sling/SMG
slingback/S
slingshot/SM
slink/SG
slinky/RT
slip/MS
slipcase/MS
slipcover/MS
slipknot/MS
slippage/MS
slipped
slipper/SM
slipperiness/M
slippery/PRT
slipping
slippy
slipshod
slipstream/SM
slipway/SM
slit/MS
slither/SGMD
slithery
slitter
slitting
sliver/GSMD
slob/MS
slobbed
slobber/MDSG
slobbery
slobbing
sloe/MS
slog/MS
slogan/SM
sloganeering
slogged
slogging
sloop/SM
slop/MDGS
slope/SM
slopped
sloppily
sloppiness/M
slopping
sloppy/PTR
slops/M
slosh/DSG
slot/MS
sloth/M
slothful/YP
slothfulness/M
sloths
slotted
slotting
slouch/ZGMDRS
sloucher/M
slouchy/TR
slough/GMD
sloughs
sloven/SMY
slovenliness/M
slovenly/PTR
slow/DRYTGSP
slowcoach/S
slowdown/SM
slowness/M
slowpoke/SM
sludge/M
sludgy/RT
slue/MGDS
slug/MS
sluggard/MS
slugged
slugger/SM
slugging
sluggish/PY
sluggishness/M
sluice/DSMG
slum/MS
slumber/GSMD
slumberous
slumbrous
slumdog/SM
slumlord/MS
slummed
slummer
slumming
slummy/RT
slump/SMDG
slung
slunk
slur/MS
slurp/SMDG
slurred
slurring
slurry/M
slush/MDSG
slushiness/M
slushy/RPT
slut/MS
sluttish
slutty/RT
sly/TRY
slyer
slyest
slyness/M
smack/SMDRZG
smacker/M
small/SMRTP
smallholder/S
smallholding/S
smallish
smallness/M
smallpox/M
smarmy/RT
smart/SMDNRYXTGP
smarten/DG
smartness/M
smartphone/SM
smarts/M
smartwatch/MS
smarty/SM
smartypants/M
smash/MDRSZG
smasher/M
smashup/SM
smattering/MS
smear/SMDG
smeary/RT
smell/SMDG
smelliness/M
smelly/RPT
smelt/SMDRZG
smelter/M
smidge/SM
smidgen/MS
smilax/M
smile/DSMG
smiley/SM
smiling/Y
smirch/GMDS
smirk/SMDG
smite/SG
smith/M
smithereens/M
smiths
smithy/SM
smitten
smock/SMDG
smocking/M
smog/MS
smoggy/RT
smoke/DRSMZG
smokehouse/MS
smokeless
smoker/M
smokescreen/SM
smokestack/SM
smokey
smokiness/M
smoking/M
smoky/RTP
smolder/SGMD
smooch/MDSG
smoochy
smooth/PDRYTG
smoothie/M
smoothness/M
smooths
smoothy/SM
smorgasbord/SM
smote
smother/GSMD
smoulder/GMDS
smudge/DSMG
smudgy/TR
smug/YP
smugger
smuggest
smuggle/ZGDRS
smuggler/M
smuggling/M
smugness/M
smurf/S
smut/MS
smuttiness/M
smutty/TRP
sm�rg�sbord/MS
snack/SMDG
snaffle/DSMG
snafu/SM
snag/MS
snagged
snagging
snail/SMDG
snake/DSMG
snakebite/MS
snakelike
snakeskin
snaky/RT
snap's
snap/US
snapdragon/SM
snapped/U
snapper/MS
snappily
snappiness/M
snapping/U
snappish/YP
snappishness/M
snappy/TRP
snapshot/SM
snare/DSMG
snarf/SDG
snark/S
snarkily
snarky/TR
snarl's
snarl/USDG
snarling/Y
snarly/TR
snatch/ZGMDRS
snatcher/M
snazzily
snazzy/TRP
sneak/SMDRZG
sneaker/M
sneakily
sneakiness/M
sneaking/Y
sneaky/TRP
sneer/SJMDG
sneering/Y
sneeze/DSMG
snick/SDRZG
snicker/MDG
snide/RYT
sniff/SMDRYZG
sniffer/M
sniffle/DSMG
sniffly/RT
sniffy/RT
snifter/SM
snigger/SMDG
snip/MDRZGS
snipe/SM
sniper/M
snipped
snippet/SM
snipping
snippy/RT
snips/M
snit/MS
snitch/MDSG
snivel/SMDRZG
sniveler/M
snob/MS
snobbery/M
snobbish/PY
snobbishness/M
snobby/RT
snog/S
snogged
snogging
snood/SM
snooker/MDSG
snoop/SMDRZG
snooper/M
snoopy/TR
snoot/SM
snootily
snootiness/M
snooty/PTR
snooze/DSMG
snore/DRSMZG
snorer/M
snorkel/ZGMDRS
snorkeler/M
snorkeling/M
snort/SMDRZG
snorter/M
snot/MS
snottily
snottiness/M
snotty/TPR
snout/SM
snow/MDGS
snowball/GSMD
snowbank/SM
snowbird/SM
snowblower/MS
snowboard/ZGMDRS
snowboarder/M
snowboarding/M
snowbound
snowcap/MS
snowcat/MS
snowdrift/SM
snowdrop/SM
snowfall/SM
snowfield/SM
snowflake/SM
snowiness/M
snowline
snowman/M
snowmen
snowmobile/DSMG
snowplow/SGMD
snowshed
snowshoe/SM
snowshoeing
snowstorm/SM
snowsuit/SM
snowy/PRT
snub/MS
snubbed
snubbing
snuff/SMDRYZG
snuffbox/MS
snuffer/M
snuffle/MGDS
snug/MYSP
snugged
snugger
snuggest
snugging
snuggle/MGDS
snugness/M
so
soak/MDGSJ
soaking/M
soap/MDGS
soapbox/MS
soapiness/M
soapstone/M
soapsuds/M
soapy/RPT
soar/MDGS
sob/SM
sobbed
sobbing/Y
sober/SDRYPTG
soberness/M
sobriety/IM
sobriquet/SM
soc
soccer/M
sociability/M
sociable/SM
sociably
social/SMY
socialism/M
socialist/SM
socialistic
socialite/SM
socialization/M
socialize/DSG
societal
society/SM
socioeconomic
socioeconomically
sociological/Y
sociologist/SM
sociology/M
sociopath/M
sociopaths
sociopolitical
sock/MDGS
socket/SM
sockeye/SM
sod/SM
soda/MS
sodded
sodden/Y
sodding
sodium/M
sodomite/MS
sodomize/GDS
sodomy/M
soever
sofa/MS
soffit/S
soft/NRYXTP
softback
softball/MS
softbound
softcover
soften/DRZG
softener/M
softhearted
softie/M
softness/M
software/M
softwood/SM
softy/SM
soggily
sogginess/M
soggy/RTP
soigne
soignee
soign�
soign�e
soil/MDGS
soiled/U
soiree/SM
soir�e/SM
sojourn/ZGMDRS
sojourner/M
sol/SM
solace/DSMG
solar
solaria
solarium/M
sold
solder/ZGSMDR
solderer/M
soldier/MDYSG
soldiery/M
sole/FSDGM
solecism/SM
solely
solemn/PTRY
solemness/M
solemnify/DSG
solemnity/SM
solemnization/M
solemnize/DSG
solemnness/M
solenoid/MS
solicit/GDS
solicitation/SM
solicited/U
solicitor/SM
solicitous/PY
solicitousness/M
solicitude/M
solid/PSMRYT
solidarity/M
solidi
solidification/M
solidify/DSNG
solidity/M
solidness/M
solidus/M
soliloquies
soliloquize/DSG
soliloquy/M
solipsism/M
solipsistic
solitaire/MS
solitariness/M
solitary/SMP
solitude/M
solo/MDGS
soloist/MS
solstice/MS
solubility/IM
soluble/MS
solute's
solute/AXN
solutes
solution's/AE
solvability
solvable/IU
solve/EADSG
solved/U
solvency/IM
solvent/IMS
solver/SM
somatic
somatosensory
somber/PY
somberness/M
sombre/PY
sombreness/M
sombrero/MS
some
somebody/SM
someday
somehow
someone/MS
someplace
somersault/MDGS
somerset/SM
somersetted
somersetting
something/SM
sometime/S
someway/S
somewhat/S
somewhere
sommelier/MS
somnambulism/M
somnambulist/SM
somnolence/M
somnolent
son/SM
sonar/SM
sonata/SM
sonatina/SM
song/MS
songbird/SM
songbook/SM
songfest/SM
songster/MS
songstress/MS
songwriter/SM
songwriting
sonic
sonnet/SM
sonny/SM
sonogram/SM
sonority/M
sonorous/YP
sonorousness/M
sonsofbitches
soon/RT
soot/M
sooth/MDRSZG
soothe
soother/M
soothing/Y
soothsayer/MS
soothsaying/M
sooty/RT
sop/SM
soph
sophism/M
sophist/MS
sophistic
sophistical
sophisticate/DSMGN
sophisticated/U
sophistication/M
sophistry/SM
sophomore/MS
sophomoric
soporific/MS
soporifically
sopped
sopping
soppy/RT
soprano/MS
sorbet/SM
sorcerer/MS
sorceress/MS
sorcery/M
sordid/PY
sordidness/M
sore/MYTRSP
sorehead/MS
soreness/M
sorghum/M
sorority/SM
sorrel/SM
sorrily
sorriness/M
sorrow/SMDG
sorrowful/YP
sorrowfulness/M
sorry/RTP
sort/FASGDM
sorta
sorted/U
sorter/SM
sortie/DSM
sortieing
sot/SM
sottish
sou'wester
sou/SMH
souffle/SM
souffl�/SM
sough/MDG
soughs
sought/U
souk/S
soul/MS
soulful/YP
soulfulness/M
soulless/YP
soulmate/SM
sound/JPSMDRYZTG
soundalike/S
soundbar/S
soundbite/S
soundboard/MS
soundcheck/S
sounder/M
sounding/M
soundless/Y
soundness/UM
soundproof/GDS
soundproofing/M
soundscape/S
soundtrack/SM
soup/MDGS
soupcon/MS
soupy/RT
soup�on/MS
sour/MDRYTGSP
source/ADSMG
sourdough/M
sourdoughs
sourish
sourness/M
sourpuss/MS
sousaphone/MS
souse/DSMG
south/M
southbound
southeast/ZMR
southeaster/MY
southeastern
southeastward/S
southerly/SM
southern/SZMR
southerner/M
southernmost
southpaw/SM
southward/MS
southwest/ZMR
southwester/MY
southwestern
southwestward/S
souvenir/SM
sovereign/SM
sovereignty/M
soviet/SM
sow's
sow/ASGD
sower/SM
sown/A
soy/M
soybean/MS
sozzled
spa/SM
space/DRSMZG
spacecraft/MS
spaceflight/MS
spaceman/M
spacemen
spaceport/SM
spacer/M
spaceship/SM
spacesuit/SM
spacetime
spacewalk/SGMD
spacewoman/M
spacewomen
spacey
spacial
spacier
spaciest
spaciness/M
spacing/M
spacious/YP
spaciousness/M
spade/DSMG
spadeful/MS
spadework/M
spadices
spadix/M
spaghetti/M
spake
spam/MS
spammed
spammer/SM
spamming
span/MS
spandex/M
spangle/DSMG
spangly
spaniel/SM
spank/SMDGJ
spanking/M
spanned
spanner/SM
spanning
spar/MS
spare/DRSMYTGP
spareness/M
spareribs/M
sparing/UY
spark/SMDYG
sparkle/DRSMZG
sparkler/M
sparky/RT
sparred
sparring
sparrow/SM
sparrowhawk/S
sparse/RYTP
sparseness/M
sparsity/M
spartan
spasm/SM
spasmodic
spasmodically
spastic/SM
spat/MS
spate/SM
spathe/SM
spatial/Y
spatted
spatter/SGMD
spatting
spatula/SM
spavin/MD
spawn/SMDG
spay/DGS
speak/SRZGJ
speakeasy/SM
speaker/M
speakerphone/S
spear/SMDG
spearfish/GMDS
speargun
spearhead/GMDS
spearmint/M
spec/MS
special/SMY
specialism/S
specialist/MS
specialization/MS
specialize/GDS
specialty/SM
speciation
specie/SM
species/M
specif
specifiable
specific/MS
specifically
specification/M
specificity/M
specified/U
specify/XNZDRSG
specimen/SM
specious/YP
speciousness/M
speck/SMDG
speckle/MGDS
specs/M
spectacle/SM
spectacles/M
spectacular/MYS
spectate/DSG
spectator/SM
specter/AMS
spectra
spectral
spectrogram/SM
spectrometer/MS
spectroscope/MS
spectroscopic
spectroscopy/M
spectrum/M
specula
specular/Y
specularity
speculate/DSXGNV
speculation/M
speculative/Y
speculator/MS
speculum/S
sped
speech/MS
speechify/DSG
speechless/YP
speechlessness/M
speechwriter/S
speed/SMRZG
speedboat/SM
speeder/M
speedily
speediness/M
speeding/M
speedometer/MS
speedster/SM
speedup/MS
speedway/SM
speedwell/M
speedy/TPR
speleological
speleologist/MS
speleology/M
spell's
spell/AJSDG
spellbind/ZGRS
spellbinder/M
spellbound
spellcheck/MDRZGS
spellchecker/M
spelldown/SM
speller/MS
spelling's
spelt
spelunker/MS
spelunking/M
spend/BSRZG
spender/M
spending/M
spendthrift/MS
spent/U
sperm/SM
spermatozoa
spermatozoon/M
spermicidal
spermicide/MS
spew/MDRZGS
spewer/M
sphagnum/MS
sphere/SM
spherical/Y
spheroid/SM
spheroidal
sphincter/MS
sphinx/MS
spic/S
spice/DSMG
spicily
spiciness/M
spick/S
spicule/MS
spicy/PRT
spider/SM
spiderweb/MS
spidery
spiel/SMDG
spiff/SDG
spiffy/TR
spigot/SM
spike/DSMG
spikiness/M
spiky/RPT
spill/SMDG
spillage/MS
spillover/SM
spillway/MS
spin/MS
spina
spinach/M
spinal/SMY
spindle/MGDS
spindly/TR
spine/SM
spineless/YP
spinet/SM
spinless
spinnaker/SM
spinner/MS
spinneret/SM
spinney/S
spinning/M
spinoff/MS
spinster/SM
spinsterhood/M
spinsterish
spiny/RT
spiracle/SM
spiraea/MS
spiral/SGMDY
spire's
spire/IFAS
spirea/SM
spirit's
spirit/ISGD
spirited/Y
spiritless
spiritual/MYS
spiritualism/M
spiritualist/MS
spiritualistic
spirituality/M
spirituous
spirochete/SM
spiry
spit/MDGS
spitball/ZGSMR
spite/ASM
spiteful/PY
spitefuller
spitefullest
spitefulness/M
spitfire/SM
spitted
spitting
spittle/M
spittoon/MS
spiv/S
splanchnic
splash/GMDS
splashdown/MS
splashily
splashiness/M
splashy/RTP
splat/SM
splatted
splatter/GSMD
splatting
splay/SMDG
splayfeet
splayfoot/MD
spleen/SM
splendid/RYT
splendor/MS
splendorous
splenectomy
splenetic
splice/DRSMZG
splicer/M
spliff/S
spline/S
splint/SZGMDR
splinter/MDG
splintery
split/SM
splitter/MS
splitting/MS
splodge/S
splosh/DSG
splotch/MDSG
splotchy/TR
splurge/DSMG
splutter/GMDS
spoil's
spoil/CSDRZG
spoilage/M
spoiled/U
spoiler/CM
spoilsport/MS
spoke/SM
spoken/U
spokesman/M
spokesmen
spokespeople
spokesperson/MS
spokeswoman/M
spokeswomen
spoliation/CM
sponge/DRSMZG
sponger/M
sponginess/M
spongy/RPT
sponsor/MDGS
sponsorship/SM
spontaneity/M
spontaneous/Y
spoof/SMDG
spook/SMDG
spookiness/M
spooky/RPT
spool/SMDG
spoon/SMDG
spoonbill/MS
spoonerism/MS
spoonful/SM
spoor/SMDG
sporadic
sporadically
spore/DSMG
sporran/S
sport/SMDGV
sportiness/M
sporting/Y
sportive/Y
sportscast/MRZGS
sportscaster/M
sportsman/M
sportsmanlike/U
sportsmanship/M
sportsmen
sportspeople
sportsperson
sportswear/M
sportswoman/M
sportswomen
sportswriter/SM
sporty/TPR
spot/CMS
spotless/PY
spotlessness/M
spotlight/GSMD
spotlit
spotted
spotter/MS
spottily
spottiness/M
spotting
spotty/TPR
spousal/MS
spouse/SM
spout/SMDG
sprain/GSMD
sprang
sprat/SM
sprawl/GSMD
spray's
spray/ASDG
sprayer/MS
spread/ZGBSMR
spreadeagled
spreader/M
spreadsheet/MS
spree/DSM
spreeing
sprig/SM
sprigged
sprightliness/M
sprightly/RTP
spring/GSM
springboard/MS
springbok/MS
springily
springiness/M
springlike
springtime/M
springy/RPT
sprinkle/DRSJMZG
sprinkler/M
sprinkling/M
sprint/ZGSMDR
sprinter/M
sprite/SM
spritz/ZGMDRS
spritzer/M
sprocket/MS
sprog/S
sprout/GSMD
spruce/DRSPMYTG
spruceness/M
sprung
spry/RYT
spryness/M
spud/MS
spume/DSMG
spumoni/M
spumy
spun
spunk/SM
spunky/TR
spur/MS
spurge/M
spurious/PY
spuriousness/M
spurn/SDG
spurred
spurring
spurt/SMDG
sputa
sputnik/MS
sputter/MDGS
sputum/M
spy/GDSM
spyglass/MS
spymaster/S
spyware/MS
sq
sqq
squab/SM
squabble/MZGDRS
squabbler/M
squad/SM
squadron/MS
squalid/PTRY
squalidness/M
squall/SGMD
squally
squalor/M
squamous
squander/GDS
square/PDRSMYTG
squareness/M
squarish
squash/GMDS
squashy/TR
squat/SMP
squatness/M
squatted
squatter/MS
squattest
squatting
squaw/SM
squawk/SZGMDR
squawker/M
squeak/SZGMDR
squeaker/M
squeakily
squeakiness/M
squeaky/TRP
squeal/SZGMDR
squealer/M
squeamish/PY
squeamishness/M
squeegee/MDS
squeegeeing
squeeze/BMZGDRS
squeezebox/S
squeezer/M
squelch/GMDS
squelchy
squib/SM
squid/SM
squidgy
squiffy
squiggle/DSMG
squiggly
squint/STGMDR
squire/DSMG
squirm/SGMD
squirmy/RT
squirrel/SGMD
squirt/SGMD
squish/GMDS
squishy/RT
sriracha
ssh
st
stab/MYS
stabbed
stabber/MS
stabbing/MS
stability/IM
stabilization/CM
stabilize/CDSG
stabilizer/MS
stable/DRSMTG
stableman/M
stablemate/S
stablemen
stably/U
staccato/MS
stack/SMDG
stadium/MS
staff's
staff/ASDG
staffer/MS
staffing/M
stag/MDGSJ
stage/SM
stagecoach/MS
stagecraft/M
stagehand/MS
stagestruck
stagey
stagflation/M
stagger/MDGS
staggering/Y
staging/M
stagnancy/M
stagnant/Y
stagnate/DSGN
stagnation/M
stagy/RT
staid/PRYT
staidness/M
stain/SMDG
stained/U
stainless/M
stair/SM
staircase/MS
stairway/MS
stairwell/SM
stake/DSMG
stakeholder/MS
stakeout/SM
stalactite/MS
stalagmite/MS
stale/DRSTGP
stalemate/DSMG
staleness/M
stalk/SMDRJZG
stalker/M
stalking/M
stall's
stall/SDG
stallholder/S
stallion/MS
stalwart/MYS
stamen/SM
stamina/M
stammer/ZGMDRS
stammerer/M
stammering/Y
stamp/SMDRZG
stampede/MGDS
stamper/M
stance/ISM
stanch/TGDRS
stanchion/SM
stand/SMRJZG
standalone
standard/MS
standardization/M
standardize/DSG
standby/M
standbys
standee/MS
stander/M
standing/M
standoff/MS
standoffish
standout/MS
standpipe/SM
standpoint/MS
standstill/MS
standup/M
stank
stanza/SM
staph/M
staphylococcal
staphylococci
staphylococcus/M
staple/DRSMZG
stapler/M
star/MDRZGS
starboard/M
starburst/S
starch/GMDS
starchily
starchiness/M
starchy/PTR
stardom/M
stardust/M
stare/SM
starer/M
starfish/MS
starfruit
stargaze/DRSZG
stargazer/M
stark/RYPZT
starkness/M
starless
starlet/MS
starlight/M
starling/SM
starlit
starred
starring
starry/TR
starstruck
start/ASMDG
starter/MS
startle/GDS
startling/Y
startup/MS
starvation/M
starve/DSJG
starveling/MS
stash/MDSG
stasis
stat/MS
state/DRSMYGNLX
statecraft/M
stated/U
statehood/M
statehouse/MS
stateless/P
statelessness/M
stateliness/M
stately/PRT
statement/AMS
statemented
statementing
stateroom/MS
stateside
statesman/M
statesmanlike
statesmanship/M
statesmen
stateswoman/M
stateswomen
statewide
static/SM
statically
station/MDRZG
stationary
stationer/M
stationery/M
stationmaster/S
statistic/MS
statistical/Y
statistician/SM
statuary/M
statue/SM
statuesque
statuette/MS
stature/MS
status/MS
statute/MS
statutorily
statutory
staunch/PDRSYTG
staunchness/M
stave/DSMG
stay/MDRZGS
std
stdio
stead/SM
steadfast/YP
steadfastness/M
steadily/U
steadiness/UM
steady/TGPDRSM
steak/SM
steakhouse/SM
steal/SMRHZG
stealth/M
stealthily
stealthiness/M
stealthy/TPR
steam/SMDRZG
steamboat/MS
steamer/M
steamfitter/SM
steamfitting/M
steaminess/M
steampunk
steamroll/ZGDRS
steamroller/MDG
steamship/MS
steamy/TPR
steed/SM
steel/SMDG
steeliness/M
steelmaker/S
steelworker/SM
steelworks/M
steely/PTR
steelyard/SM
steep/SMDNRYPXTG
steepen/GD
steeple/MS
steeplechase/MS
steeplejack/SM
steepness/M
steer/SMDBG
steerage/M
steering/M
steersman/M
steersmen
stegosauri
stegosaurus/MS
stein/SM
stellar
stem/MS
stemless
stemmed
stemming
stemware/M
stench/MS
stencil/GMDS
steno/SM
stenographer/SM
stenographic
stenography/M
stenosis
stent/SM
stentorian
step/IMS
stepbrother/SM
stepchild/M
stepchildren/M
stepdad/MS
stepdaughter/SM
stepfather/SM
stepladder/MS
stepmom/MS
stepmother/SM
stepparent/SM
steppe/DRSMZG
stepper/M
steppingstone/SM
stepsister/MS
stepson/MS
steradian
stereo/SM
stereogram/MS
stereophonic
stereoscope/MS
stereoscopic
stereotype/DSMG
stereotypical
sterile
sterility/M
sterilization/SM
sterilize/DRSZG
sterilizer/M
sterling/M
stern/SMRYPT
sternness/M
sternum/MS
steroid/MS
steroidal
stertorous
stet/S
stethoscope/MS
stetson/MS
stetted
stetting
stevedore/SM
stevia/M
stew/MDGS
steward/GMDS
stewardess/MS
stewardship/M
stick/SMRZG
sticker/M
stickily
stickiness/M
stickleback/SM
stickler/MS
stickpin/MS
stickup/MS
sticky/PTRSM
stiff/SMDNRYPXTG
stiffen/ZGDR
stiffener/M
stiffening/M
stiffness/M
stifle/DSJG
stifling/Y
stigma/SM
stigmata
stigmatic
stigmatization/M
stigmatize/GDS
stile/SM
stiletto/SM
still's
still/ITGSD
stillbirth/M
stillbirths
stillborn
stiller
stillness/M
stilt/SMD
stilted/Y
stimulant/SM
stimulate/DSGNV
stimulation/M
stimuli
stimulus/M
sting/ZGSMR
stinger/M
stingily
stinginess/M
stingray/SM
stingy/RTP
stink/ZGSMR
stinkbug/SM
stinker/M
stinky/RT
stint/GSMD
stipend/SM
stipendiary/S
stipple/DSMG
stippling/M
stipulate/XDSGN
stipulation/M
stir/MS
stirred
stirrer/SM
stirring/SY
stirrup/SM
stitch's
stitch/ADSG
stitchery/M
stitching/M
stoat/SM
stochastic
stock's
stock/AGSD
stockade/DSMG
stockbreeder/MS
stockbroker/SM
stockbroking/M
stockholder/SM
stockily
stockiness/M
stockinet/M
stockinette/M
stocking/SM
stockist/S
stockpile/MGDS
stockpot/SM
stockroom/MS
stocktaking/M
stocky/RTP
stockyard/MS
stodge
stodgily
stodginess/M
stodgy/RTP
stogie/M
stogy/SM
stoic/SM
stoical/Y
stoicism/M
stoke/DRSZG
stoker/M
stole/SM
stolen
stolid/RYTP
stolidity/M
stolidness/M
stolon/MS
stomach/MDRZG
stomachache/SM
stomacher/M
stomachs
stomp/GSMD
stone/DRSMZG
stonemason/MS
stoner/M
stonewall/GSD
stoneware/M
stonewashed
stonework/M
stonily
stoniness/M
stonkered
stonking
stony/TRP
stood
stooge/MS
stool/SM
stoolie/SM
stoop/GSMD
stop's
stop/US
stopcock/SM
stopgap/SM
stoplight/MS
stopover/MS
stoppable/U
stoppage/MS
stopped/U
stopper/GSMD
stopping/U
stopple/DSMG
stopwatch/MS
stopword/S
storage/M
store's
store/ADSG
storefront/MS
storehouse/MS
storekeeper/SM
storeroom/SM
stork/SM
storm/GSMD
stormily
storminess/M
stormy/RPT
story/DSM
storyboard/MS
storybook/SM
storyteller/MS
storytelling/M
stoup/SM
stout/TSMRYP
stouthearted
stoutness/M
stove/SM
stovepipe/SM
stow/DGS
stowage/M
stowaway/MS
straddle/DRSMZG
straddler/M
strafe/MGDS
straggle/DRSZG
straggler/M
straggly/TR
straight/SPXTMNRY
straightaway/SM
straightedge/SM
straighten/ZGDR
straightener/M
straightforward/YPS
straightforwardness/M
straightness/M
straightway
strain's
strain/FADSG
strainer/ASM
strait/MNSX
straiten/GD
straitjacket/SGMD
straitlaced
strand/MDSG
strange/PRYZT
strangeness/M
stranger/M
strangle/ZGDRS
stranglehold/SM
strangler/M
strangulate/GNDS
strangulation/M
strap's
strap/US
strapless/MS
strapped/U
strapping/M
strata
stratagem/SM
strategic/S
strategical/Y
strategics/M
strategist/SM
strategize/DG
strategy/SM
strati
stratification/M
stratify/DSGN
stratosphere/SM
stratospheric
stratum/M
stratus/M
straw/GSMD
strawberry/SM
stray/GSMD
streak/MDRSZG
streaker/M
streaky/TR
stream/MDRSZG
streamer/M
streamline/DSG
street/MS
streetcar/MS
streetlamp/S
streetlight/SM
streetwalker/SM
streetwalking
streetwise
strength/M
strengthen/AGDS
strengthener/MS
strengths
strenuous/PY
strenuousness/M
strep/M
streptococcal
streptococci
streptococcus/M
streptomycin/M
stress/MDSG
stressed/U
stressful
stressors
stretch/BZGMDRS
stretcher/MDG
stretchmarks
stretchy/TR
strew/GSDH
strewn
stria/M
striae
striated
striation/MS
stricken
strict/RYPT
strictness/M
stricture/SM
stridden
stride/MGS
stridency/M
strident/Y
strife/M
strike/MZGRSJ
strikebound
strikebreaker/SM
strikebreaking
strikeout/MS
striker/M
striking/Y
string/MDRSZG
stringency/M
stringent/Y
stringer/M
stringiness/M
stringy/PTR
strip/GSMD
stripe/MS
stripey
stripling/MS
stripped
stripper/MS
stripping
striptease/MZGDRS
stripteaser/M
stripy
strive/GS
striven
strobe/MS
stroboscope/MS
stroboscopic
strode
stroke/MGDS
stroll/MDRSZG
stroller/M
strong/RYT
strongbox/MS
stronghold/MS
strongman/M
strongmen
strongroom/S
strontium/M
strop/SM
strophe/SM
strophic
stropped
stroppily
stropping
stroppy/TRP
strove
struck
struct/CFSM
structural/Y
structuralism
structuralist/S
structure's
structure/AGDS
structured/U
strudel/SM
struggle/MGDS
strum/SM
strummed
strumming
strumpet/MS
strung/UA
strut/SM
strutted
strutting
strychnine/M
stub/MS
stubbed
stubbing
stubble/M
stubbly
stubborn/RYPT
stubbornness/M
stubby/RT
stucco/MDG
stuccoes
stuck/U
stud/MYS
studbook/MS
studded
studding/M
student/SM
studentship/S
studied/U
studiedly
studio/MS
studious/PY
studiousness/M
studly/RT
study's
study/AGDS
stuff/GSMDJ
stuffily
stuffiness/M
stuffing/M
stuffy/RPT
stultification/M
stultify/DSNG
stumble/DRSMZG
stumbler/M
stump/GSMD
stumpy/TR
stun/S
stung
stunk
stunned
stunner/S
stunning/Y
stunt/GSMD
stuntman
stuntmen
stupefaction/M
stupefy/DSG
stupefying/Y
stupendous/Y
stupid/TMRYS
stupidity/SM
stupor/MS
sturdily
sturdiness/M
sturdy/TRP
sturgeon/SM
stutter/MDRSZG
stutterer/M
sty/SM
stye/MS
style's
style/ADSG
styli
stylish/PY
stylishness/M
stylist/SM
stylistic/S
stylistically
stylize/DSG
stylometric
stylometry/S
stylus/MS
stymie/MDS
stymieing
styptic/SM
suasion/EM
suave/RYTP
suaveness/M
suavity/M
sub/SM
subaltern/MS
subaqua
subarctic
subarea/MS
subatomic
subbasement/SM
subbed
subbing
subbranch/MS
subcategory/SM
subclass
subcommittee/SM
subcompact/SM
subconscious/PMY
subconsciousness/M
subcontinent/SM
subcontinental
subcontract/MDSG
subcontractor/MS
subculture/MS
subcutaneous/Y
subdivide/GDS
subdivision/SM
subdomain/MS
subdominant
subdue/DSG
subeditor/S
subfamily/SM
subfreezing
subgroup/MS
subhead/GJMS
subheading/M
subhuman/MS
subj
subject/GVMDS
subjection/M
subjective/Y
subjectivity/M
subjoin/GDS
subjugate/GNDS
subjugation/M
subjunctive/SM
sublate/MGDS
sublease/MGDS
sublet/SM
subletting
sublieutenant/S
sublimate/GNDS
sublimation/M
sublime/YTGDRS
subliminal/Y
sublimity/M
sublingual
submarginal
submarine/MZRS
submariner/M
submerge/GDS
submergence/M
submerse/GNDS
submersible/MS
submersion/M
submicroscopic
submission's/A
submission/MS
submissive/PY
submissiveness/M
submit/AS
submitted/A
submitter
submitting/A
subnormal
suborbital
suborder/MS
subordinate/DSMGN
subordination/IM
suborn/SGD
subornation/M
subpar
subparagraph
subparagraphs
subpart
subplot/MS
subpoena/GMDS
subpoenable
subprime
subprofessional/SM
subprogram/S
subrogate/DSN
subroutine/SM
subscribe/UASDG
subscriber/MS
subscript/MS
subscription/MS
subsection/MS
subsequent/Y
subservience/M
subservient/Y
subset/SM
subside/GDS
subsidence/M
subsidiarity
subsidiary/SM
subsidization/M
subsidize/ZGDRS
subsidizer/M
subsidy/SM
subsist/SDG
subsistence/M
subsoil/M
subsonic
subspace
subspecies/M
substance/SM
substandard
substantial/IY
substantiate/GNDSX
substantiated/U
substantiation/FM
substantive/SMY
substation/MS
substituent/MS
substitute/XMGNDS
substitution/M
substrata
substrate/MS
substratum/M
substructure/SM
subsume/DSG
subsumption/S
subsurface/M
subsystem/SM
subteen/SM
subtenancy/M
subtenant/SM
subtend/SDG
subterfuge/SM
subterranean
subtext/SM
subtitle/DSMG
subtle/TR
subtlety/SM
subtly
subtopic/SM
subtotal/SGMD
subtract/GVSD
subtraction/SM
subtrahend/SM
subtropic/S
subtropical
subtropics/M
subtweet/S
suburb/MS
suburban/SM
suburbanite/SM
suburbia/M
subvention/SM
subversion/M
subversive/SPMY
subversiveness/M
subvert/SDG
subway/MS
subwoofer/S
subzero
succeed/GDS
success/VMS
successful/UY
succession/SM
successive/Y
successor/SM
succinct/RYTP
succinctness/M
succor/SGMD
succotash/M
succubi
succubus
succulence/M
succulency/M
succulent/SM
succumb/GDS
such
suchlike
suck/MDRZGS
sucker/GMD
suckle/DSJG
suckling/M
sucky
sucrose/M
suction/SMDG
sudden/PY
suddenness/M
suds/M
sudsy/TR
sue/DSG
suede/M
suet/M
suety
suffer/DRZGSJ
sufferance/M
sufferer/M
suffering/M
suffice/DSG
sufficiency/IM
sufficient/IY
suffix/MDSG
suffixation/M
suffocate/GNDS
suffocation/M
suffragan/MS
suffrage/M
suffragette/SM
suffragist/MS
suffuse/DSGN
suffusion/M
sugar/GSMD
sugarcane/M
sugarcoat/GDS
sugarless
sugarplum/MS
sugary/RT
suggest/GVSDR
suggestibility/M
suggestible
suggestion/SM
suggestive/YP
suggestiveness/M
suicidal
suicide/SM
suit/BMDGS
suitability/UM
suitableness/M
suitably/U
suitcase/SM
suite/SM
suited/U
suiting/M
suitor/MS
sukiyaki/M
sulfa/M
sulfate/SM
sulfide/SM
sulfonamides
sulfur/MDSG
sulfuric
sulfurous
sulk/MDGS
sulkily
sulkiness/M
sulky/TRSMP
sullen/RYPT
sullenness/M
sullied/U
sully/GDS
sultan/MS
sultana/SM
sultanate/MS
sultrily
sultriness/M
sultry/RPT
sum/SM
sumac/M
summarily
summarize/GDS
summary/SM
summat
summation/FMS
summed
summer/MDSG
summerhouse/SM
summertime/M
summery
summing
summit/MDS
summitry/M
summon/DRSZG
summoner/M
summons/GMDS
sumo/M
sump/MS
sumptuous/PY
sumptuousness/M
sun/SM
sunbath/ZGMDRS
sunbathe
sunbather/M
sunbathing/M
sunbaths
sunbeam/SM
sunbed/S
sunbelt/SM
sunblock/MS
sunbonnet/SM
sunburn/SGMD
sunburst/MS
sundae/MS
sundeck/S
sunder/DSG
sundial/SM
sundown/SM
sundress/S
sundries/M
sundry/S
sunfish/MS
sunflower/MS
sung/U
sunglasses/M
sunhat/S
sunk/N
sunlamp/SM
sunless
sunlight/M
sunlit
sunned
sunniness/M
sunning
sunny/TRP
sunrise/SM
sunroof/SM
sunscreen/MS
sunset/MS
sunshade/MS
sunshine/M
sunshiny
sunspot/SM
sunstroke/M
suntan/MS
suntanned
suntanning
suntrap/S
sunup/M
sup/SZMR
super/M
superabundance/MS
superabundant
superannuate/GNDS
superannuation/M
superb/RYT
supercargo/M
supercargoes
supercharge/ZGDRS
supercharger/M
supercilious/PY
superciliousness/M
supercity/SM
supercomputer/MS
superconducting
superconductive
superconductivity/M
superconductor/SM
supercritical
superego/MS
supererogation/M
supererogatory
superficial/Y
superficiality/M
superfine
superfluity/M
superfluous/YP
superfluousness/M
superglue
supergrass/S
superhero/MS
superheroes
superhighway/SM
superhuman
superimpose/GDS
superimposition/M
superintend/DSG
superintendence/M
superintendency/M
superintendent/SM
superior/MS
superiority/M
superlative/SMY
superman/M
supermarket/SM
supermassive
supermen
supermodel/SM
supermom/MS
supernal
supernatural/SY
supernova/MS
supernovae
supernumerary/SM
superpose/GDS
superposition/M
superpower/SM
supersaturate/GNDS
supersaturation/M
superscribe/GDS
superscript/MS
superscription/M
supersede/GDS
superset
supersize/GDS
supersonic
superspreader/SM
superstar/MS
superstardom
superstate/S
superstition/MS
superstitious/Y
superstore/MS
superstructure/MS
supertanker/MS
superuser/S
supervene/GDS
supervention/M
supervillain/MS
supervise/XGNDS
supervised/U
supervision/M
supervisor/MS
supervisory
superwoman/M
superwomen
supine/Y
supp/DRZG
supper/M
suppertime
suppl
supplant/SDG
supple/TLPR
supplement/MDGS
supplemental
supplementary
supplementation/M
suppleness/M
suppliant/SM
supplicant/MS
supplicate/GDS
supplication/M
supplier/M
supply/ZGDRSMXN
support/MDRSBZGV
supportable/UI
supported/U
supporter/M
suppose/GDS
supposed/Y
supposition/MS
suppository/SM
suppress/GVDS
suppressant/MS
suppressible
suppression/M
suppressor/SM
suppurate/DSGN
suppuration/M
supra
supranational
supremacist/MS
supremacy/M
supreme/Y
supremo/S
supt
surcease/DSMG
surcharge/DSMG
surcingle/SM
sure/PYTR
surefire
surefooted
sureness/M
surety/SM
surf/MDRZGS
surface's
surface/AGDS
surfboard/MDSG
surfeit/MDSG
surfer/M
surfing/M
surge/DSMG
surgeon/MS
surgery/SM
surgical/Y
surjection/S
surliness/M
surly/PTR
surmise/MGDS
surmount/DGSB
surmountable/I
surname/MS
surpass/GDS
surpassed/U
surplice/MS
surplus/MS
surplussed
surplussing
surprise/DSMGJ
surprising/UY
surreal
surrealism/M
surrealist/SM
surrealistic
surrealistically
surrender/MDSG
surreptitious/PY
surreptitiousness/M
surrey/MS
surrogacy/M
surrogate/SM
surround/GSDJ
surrounding/M
surroundings/M
surtax/MDSG
surtitle/S
surveil/S
surveillance/SM
surveillant/MS
surveilled
surveilling
survey's
survey/ADGS
surveying/M
surveyor/SM
survival/SM
survivalist/SM
survive/DSGB
survivor/SM
survivorship
susceptibility/SM
susceptible/I
sushi/M
suspect/SMDG
suspected/U
suspend/SDRZG
suspender/M
suspense/XMN
suspenseful
suspension/M
suspicion/SM
suspicious/Y
suss/DSG
sustain/SDBG
sustainability
sustainable/U
sustainably
sustenance/M
sutler/MS
suttee
suture/MGDS
suzerain/MS
suzerainty/M
svelte/TR
swab/MS
swabbed
swabbing
swaddle/DSG
swag/MS
swagged
swagger/SMDRG
swagging
swain/SM
swallow/GSMD
swallowtail/MS
swam
swami/SM
swamp/GSMD
swampland/M
swampy/RT
swan/MS
swank/TGSMDR
swankily
swankiness/M
swanky/RPT
swanned
swanning
swansdown/M
swansong/S
swap/MS
swapped
swapping
sward/SM
swarm/GSMD
swarthy/TR
swash/GMDS
swashbuckler/SM
swashbuckling/M
swastika/SM
swat/MS
swatch/MS
swath/GMDS
swathe/M
swaths
swatted
swatter/SMDG
swatting
sway/MDGS
swayback/MD
swayed/U
swear/ZGSR
swearer/M
swearword/MS
sweat/ZGSMDR
sweatband/MS
sweater/M
sweatpants/M
sweats/M
sweatshirt/SM
sweatshop/MS
sweatsuit/S
sweaty/RT
swede/SM
sweep/ZGSMRJ
sweeper/M
sweeping/MY
sweepings/M
sweepstakes/M
sweet/XTSMNRYP
sweetbread/SM
sweetbriar/SM
sweetbrier/SM
sweetcorn
sweetened/U
sweetener/MS
sweetening/M
sweetheart/SM
sweetie/SM
sweetish
sweetmeat/MS
sweetness/M
swell/TGSMDRJ
swellhead/MDS
swelling/M
swelter/SGMD
swept
sweptback
swerve/MGDS
swerving/U
swift/PTSMRY
swiftness/M
swig/MS
swigged
swigging
swill/GSMD
swim/MS
swimmer/SM
swimming/MY
swimsuit/SM
swimwear
swindle/DRSMZG
swindler/M
swine/SM
swineherd/SM
swing/ZGSMR
swingeing
swinger/M
swinish
swipe/DSMG
swirl/GSMD
swirly
swish/TGMDRS
switch/MDRSZGB
switchback/MS
switchblade/SM
switchboard/SM
switcher/M
switcheroo/S
switchover
swivel/MDGS
swiz
swizz
swizzle/DSG
swollen
swoon/SGMD
swoop/SGMD
swoosh/MDSG
sword/SM
swordfish/MS
swordplay/M
swordsman/M
swordsmanship/M
swordsmen
swore
sworn
swot/S
swotted
swotting
swum
swung
sybarite/SM
sybaritic
sycamore/MS
sycophancy/M
sycophant/SM
sycophantic
syllabi
syllabic
syllabicate/GNDS
syllabication/M
syllabification/M
syllabify/DSNG
syllable/MS
syllabub/S
syllabus/MS
syllogism/MS
syllogistic
sylph/M
sylphic
sylphlike
sylphs
sylvan
symbioses
symbiosis/M
symbiote/S
symbiotic
symbiotically
symbol/MS
symbolic
symbolical/Y
symbolism/M
symbolization/M
symbolize/DSG
symbology
symmetric/Y
symmetrical/Y
symmetry/SM
sympathetic/U
sympathetically/U
sympathies/M
sympathize/ZGDRS
sympathizer/M
sympathy/SM
symphonic
symphony/SM
symposium/MS
symptom/MS
symptomatic
symptomatically
syn/H
synagogal
synagogue/SM
synapse/MS
synaptic
sync/MDSG
synches
synchronicity
synchronism/M
synchronization/SM
synchronize/ZGDRS
synchronizer/M
synchronous/Y
synchrony
syncopate/DSGN
syncopation/M
syncope/M
syndicalism
syndicalist/S
syndicate/DSMGN
syndication/M
syndrome/SM
synergism/M
synergistic
synergy/SM
synesthesia
synesthete/S
synesthetic
synfuel/MS
synod/SM
synonym/SM
synonymous
synonymy/M
synopses
synopsis/M
synoptic
synovial
syntactic
syntactical/Y
syntax/M
synthase/SM
syntheses
synthesis/M
synthesize/ZGDRS
synthesizer/M
synthetic/SM
synthetically
synths
syphilis/M
syphilitic/SM
syringe/DSMG
syrup/SM
syrupy
sysadmin/MS
sysop/SM
system/SM
systematic/U
systematical/Y
systematization/M
systematize/GDS
systemic/MS
systemically
systole/SM
systolic
s�ance/SM
t/SDNXGBJ
tRNA/M
ta
tab/SM
tabbed
tabbing
tabbouleh/M
tabby/SM
tabernacle/SM
tabla/MS
table/MGDS
tableau/M
tableaux
tablecloth/M
tablecloths
tableland/SM
tablespoon/SM
tablespoonful/SM
tablet/SM
tabletop/MS
tableware/M
tabloid/SM
taboo/MDSG
tabor/MS
tabular
tabulate/DSGNX
tabulation/M
tabulator/SM
tachograph
tachographs
tachometer/SM
tachycardia/M
tachyon
tacit/PY
tacitness/M
taciturn/Y
taciturnity/M
tack/ZGMDRS
tacker/M
tackiness/M
tackle/DRSMZG
tackler/M
tacky/RTP
taco/MS
tact/FM
tactful/YP
tactfulness/M
tactic/SM
tactical/Y
tactician/MS
tactile
tactility/M
tactless/PY
tactlessness/M
tad/SM
tadpole/MS
taffeta/M
taffrail/SM
taffy/SM
tag/SM
tagged
tagger/SM
tagging
tagliatelle
tagline/MS
taiga/MS
tail/ACSDMG
tailback/MS
tailboard/S
tailbone/S
tailcoat/MS
tailgate/MZGDRS
tailgater/M
tailless
taillight/MS
tailor/SGMD
tailoring/M
tailpiece/S
tailpipe/SM
tailspin/SM
tailwind/SM
taint/MDSG
tainted/U
take/AIMS
takeaway/S
taken/A
takeoff/MS
takeout/MS
takeover/SM
taker/MS
taking/SM
takings/M
talc/M
talcum/M
tale/MS
talebearer/MS
talent/SMD
talented/U
tali
talisman/MS
talk/ZGMDRS
talkative/PY
talkativeness/M
talker/M
talkie/RSMT
talky
tall/TRP
tallboy/MS
tallier/M
tallish
tallness/M
tallow/M
tallowy
tally/DRSMZG
tallyho/MDGS
talon/MS
talus/MS
tam/SM
tamale/SM
tamarack/MS
tamarind/MS
tambourine/MS
tame/BYZTGDRSP
tameable
tamed/U
tameness/M
tamer/M
tamoxifen
tamp/ZGDRS
tamper/ZGDR
tamperer/M
tampon/SM
tan/SM
tanager/MS
tanbark/M
tandem/SM
tandoori/M
tang/MS
tangelo/MS
tangent/MS
tangential/Y
tangerine/MS
tangibility/IM
tangible/IMS
tangibleness/M
tangibly/I
tangle's
tangle/UDSG
tango/MDSG
tangy/RT
tank/ZGMDRS
tankard/MS
tanker/M
tankful/MS
tanned/U
tanner/SM
tannery/SM
tannest
tannin/M
tanning/M
tansy/M
tantalization/M
tantalize/ZGDRS
tantalizer/M
tantalizing/Y
tantalum/M
tantamount
tantra/M
tantrum/SM
tap/SZGMDR
tapas
tape/MS
tapeline/MS
taper/MDG
tapestry/SM
tapeworm/MS
tapioca/M
tapir/MS
tapped/U
tapper/MS
tappet/MS
tapping
taproom/SM
taproot/SM
tar/SGMD
taramasalata
tarantella/MS
tarantula/SM
tarball/S
tardily
tardiness/M
tardy/TPR
tare/MS
target/MDGS
tariff/MS
tarmac/MS
tarmacadam
tarmacked
tarmacking
tarn/MS
tarnish/GMDS
tarnished/U
taro/MS
tarot/MS
tarp/MS
tarpaulin/MS
tarpon/MS
tarragon/SM
tarred
tarring
tarry/TGDRS
tarsal/MS
tarsi
tarsus/M
tart/PTGMDRYS
tartan/MS
tartar/MS
tartaric
tartness/M
tarty/T
taser/GMDS
task/GMDS
taskbar
taskmaster/MS
taskmistress/MS
tassel/MDSG
taste/JMZGDRS
tasted/U
tasteful/EPY
tastefulness/EM
tasteless/PY
tastelessness/M
taster/M
tastily
tastiness/M
tasting/M
tasty/TRP
tat/SZR
tatami/MS
tater/M
tatted
tatter/MDSG
tatterdemalion/MS
tattie
tatting/M
tattle/MZGDRS
tattler/M
tattletale/MS
tattoo/MDRSZG
tattooer/M
tattooist/SM
tatty/TRS
tau/SM
taught/UA
taunt/ZGMDRS
taunter/M
taunting/Y
taupe/M
taut/PXTNRY
tauten/DG
tautness/M
tautological/Y
tautologous
tautology/SM
tavern/MS
tawdrily
tawdriness/M
tawdry/RTP
tawny/TRM
tax/BZGMDRS
taxa
taxation/M
taxer/M
taxi/GMDS
taxicab/SM
taxidermist/SM
taxidermy/M
taximeter/MS
taxiway/S
taxman
taxmen
taxon
taxonomic
taxonomical
taxonomist/MS
taxonomy/SM
taxpayer/MS
taxpaying
tb/S
tbsp
tea/SM
teabag/S
teacake/SM
teach/ZGRSBJ
teachable/U
teacher/M
teaching/M
teacup/MS
teacupful/MS
teak/MS
teakettle/SM
teal/MS
tealight/MS
team/GMDS
teammate/MS
teamster/MS
teamwork/M
teapot/MS
tear/GMDS
tearaway/S
teardrop/SM
tearful/Y
teargas/MS
teargassed
teargassing
tearjerker/MS
tearoom/SM
teary/TR
tease/MZGDRS
teasel/MS
teaser/M
teasing/Y
teaspoon/SM
teaspoonful/SM
teat/MS
teatime/S
tebibyte/MS
tech/M
techie/S
technetium/M
technical/Y
technicality/SM
technician/SM
technicolor
technique/SM
techno
technobabble
technocracy/SM
technocrat/MS
technocratic
technological/Y
technologist/MS
technology/SM
technophobe/S
techs
tectonic/S
tectonics/M
ted/S
teddy/S
tedious/PY
tediousness/M
tedium/M
tee/DSMH
teeing
teem/GDS
teen/MS
teenage/RZ
teenager/M
teeny/TR
teenybopper/MS
teepee/MS
teeter/MDSG
teethe/GDS
teething/M
teetotal/RZ
teetotaler/M
teetotalism/M
tektite/SM
tel
telecast/SZGMR
telecaster/M
telecom/M
telecommunication/MS
telecommunications/M
telecommute/ZGDRS
telecommuter/M
telecommuting/M
teleconference/MGDS
teleconferencing/M
telegenic
telegram/MS
telegraph/MDRZG
telegrapher/M
telegraphese
telegraphic
telegraphically
telegraphist/SM
telegraphs
telegraphy/M
telekinesis/M
telekinetic
telemarketer/SM
telemarketing/M
telemeter/SM
telemetry/SM
teleological
teleology
telepathic
telepathically
telepathy/M
telephone/DRSMZG
telephoner/M
telephonic
telephonist/S
telephony/M
telephoto/SM
telephotography/M
teleplay/MS
teleport/DSG
teleportation
teleprinter/MS
teleprocessing/M
teleprompter/SM
telesales
telescope/DSMG
telescopic
telescopically
teletext/MS
telethon/MS
teletype/S
teletypewriter/MS
televangelism/M
televangelist/MS
televise/XGNDS
television/M
teleworker/S
teleworking
telex/MDSG
tell/AGS
teller/SM
telling/Y
telltale/SM
tellurium/M
telly/SM
telnet
temblor/MS
temerity/M
temp/MDRZTGS
temper/MDG
tempera/LSM
temperament/MS
temperamental/Y
temperance/IM
temperate/IY
temperateness/M
temperature/SM
tempest/SM
tempestuous/YP
tempestuousness/M
tempi
template's
template/S
temple/SM
tempo/SM
temporal/Y
temporarily
temporariness/M
temporary/FSM
temporize/ZGDRS
temporizer/M
tempt/SDRZG
temptation/MS
tempter/M
tempting/Y
temptress/MS
tempura/M
ten/BMH
tenability/M
tenable/U
tenably
tenacious/YP
tenaciousness/M
tenacity/M
tenancy/SM
tenant/SMDG
tenanted/U
tenantry/M
tench
tend/IFEDGS
tended/U
tendency/SM
tendentious/YP
tendentiousness/M
tender/SMDRYTGP
tenderfoot/MS
tenderhearted/P
tenderheartedness/M
tenderize/ZGDRS
tenderizer/M
tenderloin/SM
tenderness/M
tendinitis/M
tendon/SM
tendonitis/M
tendril/SM
tenement/SM
tenet/SM
tenfold
tenner/S
tennis/M
tenon/SMDG
tenor/SM
tenpin/SM
tenpins/M
tense/DRSMYTGNXP
tenseness/M
tensile
tension/ESM
tensity/IM
tensor/S
tent/DGSM
tentacle/DSM
tentative/PY
tentativeness/M
tenterhook/MS
tenth/MY
tenths
tenuity/M
tenuous/PY
tenuousness/M
tenure/DSMG
tepee/SM
tepid/YP
tepidity/M
tepidness/M
tequila/SM
terabit/SM
terabyte/MS
terahertz/M
terajoule/S
terapixel/MS
terawatt/S
terbium/M
tercentenary/SM
tercentennial/SM
teriyaki
term/MDYGS
termagant/MS
terminable/IC
terminal/MYS
terminate/DSGNX
termination/CSM
terminator/S
termini
terminological/Y
terminology/SM
terminus/M
termite/SM
tern/IMS
ternary/SM
terr
terrace/DSMG
terracotta/M
terraforming/M
terrain/SM
terrapin/MS
terrarium/SM
terrazzo/MS
terrestrial/SMY
terrible/P
terribleness/M
terribly
terrier/M
terrific
terrifically
terrify/GDS
terrifying/Y
terrine/S
territorial/MS
territoriality
territory/SM
terror/SM
terrorism/M
terrorist/SM
terrorize/DSG
terry/RMZ
terrycloth/M
terse/RYTP
terseness/M
tertiary
tessellate/DSXGN
tessellation/M
test's/AFK
test/AKFCDGS
testable/CF
testament/MS
testamentary
testate/S
testator/MS
testatrices
testatrix/M
testcase/MS
tested/U
tester/KSM
testes
testicle/MS
testicular
testifier/M
testify/ZGDRS
testily
testimonial/MS
testimony/SM
testiness/M
testings
testis/M
testosterone/M
testsuite/SM
testy/PRT
tetanus/M
tetchily
tetchy/PRT
tether/SMDG
tetra/SM
tetracycline/M
tetrahedral
tetrahedron/MS
tetrameter/SM
text/FMS
textbook/SM
textbox/MS
texted
textile/MS
texting
textual/FY
textural
texture/MGDS
thalami
thalamus/M
thaliana
thalidomide/M
thallium/M
than
thane/SM
thank/SDG
thankful/YP
thankfulness/M
thankless/PY
thanklessness/M
thanksgiving/SM
that'd
that'll
that/M
thatch/MDRSZG
thatcher/M
thatching/M
thaw/MDGS
the/JG
theater/SM
theatergoer/SM
theatrical/YS
theatricality/M
theatricals/M
theatrics/M
thee/S
theft/SM
their/S
theism/M
theist/SM
theistic
them
thematic
thematically
theme/DSMG
themself
themselves
then/M
thence
thenceforth
thenceforward
theocracy/SM
theocratic
theodolite/S
theologian/SM
theological/Y
theology/SM
theorem/MS
theoretic
theoretical/Y
theoretician/SM
theorist/SM
theorize/DSG
theory/SM
theosophic
theosophical
theosophist/SM
theosophy/M
therapeutic/S
therapeutically
therapeutics/M
therapist/SM
therapy/SM
there/M
thereabout/S
thereafter
thereat
therebetween
thereby
therefor
therefore
therefrom
therein
theremin/SM
thereof
thereon
thereto
theretofore
thereunder
thereunto
thereupon
therewith
therm/SM
thermal/MYS
thermionic
thermobaric
thermodynamic/S
thermodynamics/M
thermometer/MS
thermometric
thermonuclear
thermoplastic/SM
thermos/MS
thermostat/MS
thermostatic
thermostatically
thesauri
thesaurus/MS
these/S
thesis/M
thespian/SM
theta/SM
thew/MS
they
they'd
they'll
they're
they've
thiamine/M
thick/PMNRYXT
thicken/DRJZG
thickener/M
thickening/M
thicket/MS
thickheaded/M
thickness/MS
thicko/S
thickset
thief/M
thieve/DSG
thievery/M
thieving/M
thievish
thigh/M
thighbone/MS
thighs
thimble/MS
thimbleful/SM
thin/YSP
thine
thing/M
thingamabob/SM
thingamajig/SM
thingumabob/S
thingummy/S
thingy/S
think/SRBZG
thinkable/U
thinker/M
thinking's
thinned
thinner/MS
thinness/M
thinnest
thinning
third/SMY
thirst/SGMD
thirstily
thirstiness/M
thirsty/TPR
thirteen/SMH
thirteenth/M
thirteenths
thirtieth/M
thirtieths
thirty/HSM
this
thistle/MS
thistledown/M
thither
tho
thole/SM
thong/SM
thoracic
thoracotomy
thorax/MS
thorium/M
thorn/SM
thorniness/M
thorny/PRT
thorough/RYPT
thoroughbred/MS
thoroughfare/MS
thoroughgoing
thoroughness/M
those
thou/MS
though
thought/SM
thoughtful/YP
thoughtfulness/M
thoughtless/PY
thoughtlessness/M
thousand/MHS
thousandfold
thousandth/M
thousandths
thraldom/M
thrall/SMDG
thralldom/M
thrash/JMDRSZG
thrasher/M
thrashing/M
thread/SMDRZG
threadbare
threader/M
threadlike
thready/TR
threat/SMNX
threaten/DG
threatening/Y
three/SM
threefold
threepence/M
threescore/MS
threesome/SM
threnody/SM
thresh/MDRSZG
thresher/M
threshold/SM
threw
thrice
thrift/SM
thriftily
thriftiness/M
thriftless
thrifty/PTR
thrill/SMDRZG
thriller/M
thrilling/Y
thrive/DSG
throat/SM
throatily
throatiness/M
throaty/RTP
throb/SM
throbbed
throbber
throbbing
throe/SM
thrombi
thrombolytic
thromboses
thrombosis/M
thrombotic
thrombus/M
throne's
throne/S
throng/GSMD
throttle/DRSMZG
throttler/M
through
throughout
throughput/M
throughway/MS
throw/SMRZG
throwaway/SM
throwback/SM
thrower/M
thrown
thru
thrum/SM
thrummed
thrumming
thrush/MS
thrust/ZGSMR
thruway/MS
thud/MS
thudded
thudding
thug/MS
thuggery/M
thuggish
thulium/M
thumb/SMDG
thumbnail/SM
thumbprint/SM
thumbscrew/SM
thumbtack/SM
thump/SMDG
thumping/M
thunder/ZGMDRS
thunderbolt/SM
thunderclap/SM
thundercloud/MS
thunderer/M
thunderhead/SM
thunderous/Y
thundershower/SM
thunderstorm/SM
thunderstruck
thundery
thunk/S
thus/Y
thwack/ZGSMDR
thwacker/M
thwart/GSMD
thy
thyme/M
thymine/M
thymus/MS
thyroid/MS
thyroidal
thyself
ti/MRZ
tiara/SM
tibia/M
tibiae
tibial
tic/SM
tick/MDRZGS
ticker/M
ticket/GSMD
ticking/M
tickle/DRSMZG
tickler/M
ticklish/YP
ticklishness/M
ticktacktoe/M
ticktock/MS
tidal/Y
tidbit/SM
tiddler/S
tiddly
tiddlywink/S
tiddlywinks/M
tide/MGJDS
tideland/SM
tidemark/S
tidewater/MS
tideway/MS
tidily/U
tidiness/UM
tidings/M
tidy/DRSMTGP
tie's
tie/AUSD
tieback/MS
tiebreak/RSZ
tiebreaker/M
tiepin/S
tier/MD
tiff/MDGS
tiger/SM
tigerish
tight/SNRYPXT
tighten/ZGDR
tightener/M
tightfisted
tightness/M
tightrope/MS
tights/M
tightwad/MS
tigress/MS
til
tilapia
tilde/SM
tile/MZGDRS
tiler/M
tiling/M
till's
till/EDRZGS
tillable
tillage/M
tiller/EM
tilt/MDGS
timber/SMDG
timberland/M
timberline/MS
timbre/SM
timbrel/SM
time/MYZGJDRS
timekeeper/MS
timekeeping/M
timeless/PY
timelessness/M
timeline/MS
timeliness/UM
timely/UPRT
timeout/SM
timepiece/MS
timer/M
timescale/S
timeserver/SM
timeserving/M
timeshare/S
timestamp/SMD
timetable/DSMG
timeworn
timezone
timid/RYTP
timidity/M
timidness/M
timing/M
timorous/PY
timorousness/M
timothy/M
timpani/M
timpanist/SM
tin/SM
tincture/MGDS
tinder/M
tinderbox/MS
tine/MS
tinfoil/M
ting/MDYG
tinge/SM
tingeing
tingle/DSMGJ
tingling/M
tininess/M
tinker/ZGSMDR
tinkerer/M
tinkle/DSMG
tinned
tinniness/M
tinning
tinnitus/M
tinny/PRT
tinplate/M
tinpot
tinsel/GSMD
tinsmith/M
tinsmiths
tint/MDGS
tintinnabulation/MS
tintype/MS
tinware/M
tiny/RTP
tip/SM
tipped
tipper/SM
tippet/SM
tippex/GDS
tipping
tipple/DRSMZG
tippler/M
tipsily
tipsiness/M
tipster/MS
tipsy/RPT
tiptoe/DSM
tiptoeing
tiptop/SM
tirade/SM
tiramisu/MS
tire's
tire/AGDS
tired/PRYT
tiredness/M
tireless/YP
tirelessness/M
tiresome/PY
tiresomeness/M
tissue/SM
tit/SM
titan/SM
titanic
titanium/M
titch/S
titchy
tithe/DRSMZG
tither/M
titian/M
titillate/DSGN
titillating/Y
titillation/M
titivate/DSGN
titivation/M
title/DSMG
titled/U
titleholder/MS
titlist/MS
titmice
titmouse/M
titter/SGMD
tittivate/DSGN
tittivation/M
tittle/SM
titty/S
titular
tizz
tizzy/SM
tn
tnpk
to/IU
toad/MS
toadstool/MS
toady/DSMG
toadyism/M
toast/SMDRZG
toaster/M
toastmaster/SM
toastmistress/MS
toasty/TRS
tobacco/MS
tobacconist/SM
toboggan/ZGSMDR
tobogganer/M
tobogganing/M
toccata/S
tocopherol
tocsin/SM
today/M
toddle/DRSMZG
toddler/M
toddy/SM
toe/DSM
toecap/SM
toehold/MS
toeing
toenail/MS
toerag/S
toff/S
toffee/SM
tofu/M
tog/SM
toga/MDS
together/P
togetherness/M
togged
togging
toggle/DSMG
togs/M
toil/MDRZGS
toiler/M
toilet/MDGS
toiletry/SM
toilette/M
toilsome
toke/MGDS
token/SM
tokenism/M
told/AU
tole/M
tolerable/I
tolerably/I
tolerance/IM
tolerances
tolerant/IY
tolerate/GNDS
toleration/M
toll/MDGS
tollbooth/M
tollbooths
tollgate/SM
tollway/SM
toluene/M
tom/SM
tomahawk/SGMD
tomato/M
tomatoes
tomb/MDGS
tombola/S
tomboy/MS
tomboyish
tombstone/MS
tomcat/MS
tome/MS
tomfoolery/SM
tomographic
tomography/M
tomorrow/MS
tomtit/MS
ton/SM
tonal/Y
tonality/SM
tone's
tone/IZGDRS
tonearm/SM
toneless/Y
toner/IM
tong/MDGS
tongue/MGDS
tongueless
tonic/SM
tonight/M
tonnage/SM
tonne/SM
tonsil/MS
tonsillectomy/SM
tonsillitis/M
tonsorial
tonsure/DSMG
tony/RT
too
toodles
took/A
tool's
tool/ADGS
toolbar/SM
toolbox/MS
toolkit
toolmaker/MS
toot/MDRZGS
tooter/M
tooth/MD
toothache/MS
toothbrush/MS
toothily
toothless
toothpaste/SM
toothpick/SM
toothsome
toothy/RT
tootle/GDS
tootsie/S
top/SM
topaz/MS
topcoat/SM
topdressing/SM
topee/S
topflight
topi/S
topiary/M
topic/SM
topical/Y
topicality/M
topknot/SM
topless
topmast/SM
topmost
topnotch
topographer/SM
topographic
topographical/Y
topography/SM
topological/Y
topology
topped
topper/MS
topping/SM
topple/GDS
topsail/SM
topside/SM
topsoil/M
topspin/M
toque/SM
tor/SM
torah/M
torahs
torch/GMDS
torchbearer/MS
torchlight/M
tore
toreador/MS
torment/SMDG
tormenting/Y
tormentor/MS
torn
tornado/M
tornadoes
torpedo/GMD
torpedoes
torpid/Y
torpidity/M
torpor/M
torque/MGDS
torrent/SM
torrential
torrid/YP
torridity/M
torridness/M
torsion/M
torsional
torso/SM
tort's
tort/EFAS
torte/SM
tortellini/M
tortilla/MS
tortoise/MS
tortoiseshell/SM
tortoni/M
tortuous/PY
tortuousness/M
torture/DRSMZG
torturer/M
torturous
torus
tosh
toss/MDRSZG
tossup/MS
tot/SGMD
total/GSMDY
totalitarian/SM
totalitarianism/M
totality/SM
totalizator/SM
tote/MS
totem/SM
totemic
totted
totter/ZGMDRS
totterer/M
totting
toucan/MS
touch/AGMDS
touchdown/SM
touche/BJ
touched/U
touchily
touchiness/M
touching/Y
touchline/S
touchpaper/S
touchscreen/MS
touchstone/MS
touchy/RPT
touch�
tough/XTGMDNRYP
toughen/ZGDR
toughener/M
toughie/SM
toughness/M
toughs
toupee/MS
tour/CFSGDM
tourism/M
tourist/MS
touristic
touristy
tourmaline/M
tournament/SM
tourney/MS
tourniquet/MS
tousle/GDS
tout/MDGS
tow/SZGMDR
toward/S
towboat/MS
towel/JGSMD
towelette/SM
toweling/M
tower/GMD
towhead/MDS
towhee/MS
towline/MS
town/MS
townee/S
townhouse/MS
townie/MS
townsfolk/M
township/MS
townsman/M
townsmen
townspeople/M
townswoman/M
townswomen
towpath/M
towpaths
towrope/SM
toxemia/M
toxic
toxicity/SM
toxicological
toxicologist/SM
toxicology/M
toxin/SM
toy/SGMD
toyboy/S
tr
trabecula
trabecular
trabecule
trace/JDRSMZG
traceability
traceable/U
tracer/M
tracery/SM
traceur/SM
trachea/M
tracheae
tracheal
tracheotomy/SM
tracing/M
track/ZGSMDR
trackback/SM
trackball/SM
tracker/M
trackless
tracksuit/S
tract's
tract/CKFEAS
tractability/IM
tractable/I
tractably/I
traction/FEACKM
tractor/FCKMS
trad
trade/BJDRSMZG
trademark/SGMD
tradeoff/S
trader/M
tradesman/M
tradesmen
tradespeople/M
tradeswoman/M
tradeswomen
trading/M
tradition/MS
traditional/Y
traditionalism/M
traditionalist/SM
traduce/DRSZG
traducer/M
traffic/SM
trafficked
trafficker/SM
trafficking/M
tragedian/SM
tragedienne/MS
tragedy/SM
tragic
tragically
tragicomedy/SM
tragicomic
trail/ZGSMDR
trailblazer/MS
trailblazing/M
trailer/M
trailhead/S
train/ZGSMDRBJ
trained/U
trainee/SM
trainer/M
training/M
trainload/MS
trainman/M
trainmen
trainspotter/S
trainspotting
traipse/DSMG
trait/SM
traitor/SM
traitorous/Y
trajectory/SM
tram/MS
tramcar/S
tramlines
trammed
trammel/SGMD
trammeled/U
tramming
tramp/ZGSMDR
tramper/M
trample/DRSMZG
trampler/M
trampoline/MGDS
tramway/S
trance/MS
tranche/S
tranquil/RYT
tranquility/M
tranquilize/ZGDRS
tranquilizer/M
tranquillity/M
trans/I
transact/DGS
transaction/SM
transactional
transactor/MS
transatlantic
transceiver/SM
transcend/GSD
transcendence/M
transcendent
transcendental/Y
transcendentalism/M
transcendentalist/SM
transcontinental
transcreation/M
transcribe/ZGDRS
transcriber/M
transcript/MS
transcription/SM
transcriptional
transducer/MS
transduction
transect/DSG
transept/MS
transfect/SGD
transfeminine
transfer/MBS
transferal/MS
transference/M
transferred
transferring
transfiguration/M
transfigure/GDS
transfinite
transfix/DSG
transform/BSZGMDR
transformation/SM
transformational
transformative
transformer/M
transfuse/DSXGN
transfusion/M
transgender/SD
transgenderism
transgene/S
transgenic
transgress/GVDS
transgression/SM
transgressor/SM
transience/M
transiency/M
transient/SMY
transistor/SM
transistorize/DSG
transit/SGMD
transition/GSMD
transitional/Y
transitive/ISMY
transitiveness/M
transitivity/M
transitory
transl
translatable/U
translate/DSGNBX
translated/U
translation/M
translator/SM
transliterate/DSGNX
transliteration/M
translocation
translucence/M
translucency/M
translucent/Y
transmasculine
transmigrate/GNDS
transmigration/M
transmissible
transmission/AMS
transmit/S
transmittable
transmittal/M
transmittance/M
transmitted
transmitter/SM
transmitting
transmogrification/M
transmogrify/DSNG
transmutation/SM
transmute/BDSG
transnational/MS
transoceanic
transom/SM
transpacific
transparency/SM
transparent/Y
transphobia/M
transphobic
transpiration/M
transpire/DSG
transplant/MDGS
transplantation/M
transpolar
transponder/SM
transport/BSZGMDR
transportation/M
transporter/M
transpose/DSG
transposition/MS
transsexual/SM
transsexualism/M
transship/SL
transshipment/M
transshipped
transshipping
transubstantiation/M
transversal
transverse/MYS
transvestism/M
transvestite/MS
trap/MS
trapdoor/MS
trapeze/SM
trapezium/SM
trapezoid/SM
trapezoidal
trappable
trapped
trapper/SM
trapping/S
trappings/M
trapshooting/M
trash/GMDS
trashcan/MS
trashiness/M
trashy/RPT
trauma/MS
traumatic
traumatically
traumatize/GDS
travail/SGMD
travel/MDRSZGJ
traveled/U
traveler/M
traveling/M
travelog/SM
travelogue/MS
traversal/SM
traverse/DSMG
travesty/GDSM
trawl/ZGSMDR
trawler/M
tray/MS
treacherous/PY
treacherousness/M
treachery/SM
treacle/M
treacly
tread/AGSM
treadle/DSMG
treadmill/MS
treas
treason/BM
treasonous
treasure/DRSMZG
treasurer/M
treasury/SM
treat/AGSMD
treatable
treated/U
treatise/SM
treatment/MS
treaty/SM
treble/MGDS
trebuchet/S
tree/MDS
treeing
treeless
treelike
treeline
treetop/SM
trefoil/SM
trek/MS
trekked
trekker/SM
trekking
trellis/GMDS
trematode/MS
tremble/DSMG
tremendous/Y
tremolo/SM
tremor/MS
tremulous/PY
tremulousness/M
trench's
trench/ADSG
trenchancy/M
trenchant/Y
trencher/MS
trencherman/M
trenchermen
trend/GSMD
trendily
trendiness/M
trendsetter/S
trendsetting
trendy/RSMPT
trepidation/M
trespass/MDRSZG
trespasser/M
tress/EMS
trestle/MS
trews
trey/MS
triad/SM
triage/MGDS
trial/ASM
trialed
trialing
triangle/SM
triangular/Y
triangulate/GNDS
triangulation/M
triathlete/S
triathlon/SM
tribal
tribalism/M
tribe/SM
tribesman/M
tribesmen
tribeswoman/M
tribeswomen
tribulation/SM
tribunal/SM
tribune/MS
tributary/SM
tribute's
tribute/FS
trice/M
tricentennial/MS
triceps/MS
triceratops/M
trichina/M
trichinae
trichinosis/M
trichotomy/S
trick/GSMD
trickery/M
trickily
trickiness/M
trickle/MGDS
trickster/SM
tricky/TRP
tricolor/SM
tricorn/MS
tricycle/SM
trident/MS
tried/U
triennial/MYS
trier/SM
trifecta/SM
trifle/MZGDRS
trifler/M
trifocals/M
trig/M
trigger/MDSG
triglyceride/MS
trigonometric
trigonometrical
trigonometry/M
trike/SM
trilateral/S
trilby/SM
trill/GSMD
trillion/SMH
trillionth/M
trillionths
trillium/M
trilobite/SM
trilogy/SM
trim/PMYS
trimaran/MS
trimester/SM
trimmed/U
trimmer/SM
trimmest
trimming/SM
trimmings/M
trimness/M
trimonthly
trinitrotoluene/M
trinity/SM
trinket/SM
trio/MS
trip/MYS
tripartite
tripe/M
triplane/SM
triple/MGDS
triplet/SM
triplex/MS
triplicate/MGDS
tripod/MS
tripodal
tripos
tripped
tripper/SM
tripping
triptych/M
triptychs
tripwire/S
trireme/SM
trisect/SDG
trisection/M
trite/FPYT
triteness/FM
triter
tritium/M
triumph/GMD
triumphal
triumphalism
triumphalist
triumphant/Y
triumphs
triumvir/MS
triumvirate/SM
trivalent
trivet/MS
trivia/M
trivial/Y
triviality/SM
trivialization/M
trivialize/GDS
trivium/M
trochaic
trochee/SM
trod/AU
trodden/A
troglodyte/SM
troika/MS
troll/SGMD
trolley/SM
trolleybus/MS
trollop/SM
trombone/MS
trombonist/MS
tromp/SGD
tron/S
troop/SZGMDR
trooper/M
troopship/MS
trope/SM
trophy/SM
tropic/MS
tropical/Y
tropics/M
tropism/SM
troposphere/SM
trot/MS
troth/M
trotted
trotter/SM
trotting
troubadour/MS
trouble/DSMG
troubled/U
troublemaker/MS
troubleshoot/DRZGS
troubleshooter/M
troubleshooting/M
troubleshot
troublesome/Y
trough/M
troughs
trounce/DRSZG
trouncer/M
troupe/MZGDRS
trouper/M
trouser/SM
trousers/M
trousseau/M
trousseaux
trout/SM
trove/SM
trow/DSG
trowel/MDSG
troy/S
truancy/M
truant/GMDS
truce/SM
truck/SZGMDR
trucker/M
trucking/M
truckle/MGDS
truckload/SM
truculence/M
truculent/Y
trudge/MGDS
true/MTGDRS
truelove/SM
truffle/MS
trug/S
truism/MS
truly/U
trump/SGMD
trumpery/M
trumpet/ZGMDRS
trumpeter/M
truncate/GNDS
truncation/M
truncheon/SM
trundle/MZGDRS
trundler/M
trunk/SGM
truss/GMDS
trust/ESGMD
trustee/MS
trusteeship/SM
trustful/EY
trustfulness/M
trusting/Y
trustworthiness/M
trustworthy/TPR
trusty/TRSM
truth/ZMR
truther/M
truthful/UYP
truthfulness/UM
truthiness
truths/U
try's
try/AGDS
trying/Y
tryout/SM
tryptophan
tryst/SMDG
tsarists
tsetse/MS
tsp
tsunami/SM
ttys
tub/SZGMDR
tuba/MS
tubal
tubby/TR
tube/MS
tubeless/M
tuber/M
tubercle/SM
tubercular
tuberculin/M
tuberculosis/M
tuberculous
tuberose/M
tuberous
tubful/MS
tubing/M
tubular
tubule/MS
tuck/MDRSZG
tucker/MDG
tuft/MDRSZG
tufter/M
tug/SM
tugboat/MS
tugged
tugging
tuition/IM
tularemia/M
tulip/SM
tulle/M
tum/S
tumble/DRSMZG
tumbledown
tumbler/M
tumbleweed/SM
tumbling/M
tumbrel/SM
tumbril/SM
tumescence/M
tumescent
tumid
tumidity/M
tummy/SM
tumor/SM
tumorous
tumult/SM
tumultuous/Y
tun/SZGMDRB
tuna/MS
tundra/SM
tune/MS
tuneful/YP
tunefulness/M
tuneless/Y
tuner/M
tuneup/SM
tung
tungsten/M
tunic/SM
tunnel/JSMDRZG
tunneler/M
tunny/SM
tuple/S
tuppence
tuppenny
tuque/SM
turban/SMD
turbid
turbidity/M
turbine/SM
turbo/SM
turbocharge/ZGDRS
turbocharger/M
turbofan/SM
turbojet/SM
turboprop/SM
turbot/SM
turbulence/M
turbulent/Y
turd/MS
turducken/SM
tureen/SM
turf/MDSG
turfy
turgid/Y
turgidity/M
turkey/SM
turmeric/SM
turmoil/MS
turn/AMDRSZG
turnabout/SM
turnaround/SM
turnbuckle/SM
turncoat/SM
turner/AM
turning/MS
turnip/SM
turnkey/MS
turnoff/MS
turnout/MS
turnover/MS
turnpike/MS
turnstile/SM
turntable/SM
turpentine/M
turpitude/M
turps
turquoise/SM
turret/SMD
turtle/SM
turtleback/S
turtledove/SM
turtleneck/SMD
tush/MS
tusk/MDS
tussle/DSMG
tussock/MS
tussocky
tut/SM
tutelage/M
tutelary
tutor/SMDG
tutored/U
tutorial/SM
tutorship/M
tutted
tutti/SM
tutting
tutu/MS
tux/MS
tuxedo/SM
twaddle/MZGDRS
twaddler/M
twain/M
twang/SMDG
twangy/RT
twas
twat/S
tweak/SMDG
twee
tweed/SM
tweedle/DSG
tweeds/M
tweedy/RT
tween
tweep/S
tweet's
tweet/ASDG
tweeter/SM
tweezers/M
twelfth/M
twelfths
twelve/SM
twelvemonth/M
twelvemonths
twentieth/M
twentieths
twenty/SMH
twerk/SDG
twerp/SM
twice
twiddle/MGDS
twiddly
twig/MS
twigged
twigging
twiggy/TR
twilight/M
twilit
twill/MD
twin/MDRSZG
twine/SM
twiner/M
twinge/DSMG
twinight
twink/SY
twinkle/MGJDS
twinkling/M
twinned
twinning
twinset/S
twirl/SMDRZG
twirler/M
twirly
twist's
twist/USDG
twister/MS
twisty/TR
twit/MS
twitch/GMDS
twitchy/RT
twitted
twitter/MDSG
twittery
twitting
twixt
two/SM
twofer/SM
twofold
twopence/SM
twopenny
twosome/SM
twp
tycoon/SM
tying/AU
tyke/MS
tympani/M
tympanic
tympanist/MS
tympanum/SM
type's
type/AGDS
typecast/GS
typeface/MS
typescript/MS
typeset/S
typesetter/MS
typesetting/M
typewrite/RSZG
typewriter/M
typewriting/M
typewritten
typewrote
typhoid/M
typhoon/MS
typhus/M
typical/UY
typicality/M
typification/M
typify/NGDS
typing/M
typist/SM
typo/MS
typographer/SM
typographic
typographical/Y
typography/M
typology/SM
tyrannic
tyrannical/Y
tyrannicidal
tyrannicide/S
tyrannize/GDS
tyrannosaur/MS
tyrannosaurus/MS
tyrannous
tyranny/SM
tyrant/SM
tyro/MS
tzatziki
u/S
ubiquitous/Y
ubiquity/M
udder/SM
udon
ufologist/SM
ufology/M
ugh
ugliness/M
ugly/RTP
uh
uhf
ukase/SM
ukulele/SM
ulcer/SM
ulcerate/XDSGNV
ulceration/M
ulcerous
ulna/M
ulnae
ulnar
ulster/MS
ult
ulterior
ultimate/MY
ultimatum/MS
ultimo
ultra/SM
ultraconservative/SM
ultrahigh
ultralight/SM
ultramarine/M
ultramodern
ultrasensitive
ultrashort
ultrasonic
ultrasonically
ultrasound/MS
ultraviolet/M
ululate/DSGNX
ululation/M
um
umbel/SM
umber/M
umbilical
umbilici
umbilicus/M
umbra/SM
umbrage/M
umbrella/SM
umiak/SM
umlaut/MS
ump/SGMD
umpire/MGDS
umpteen/H
unabridged/MS
unacceptability
unacceptable
unaccommodating
unaccountably
unadventurous
unaesthetic
unalterably
unambitious
unanimity/M
unanimous/Y
unapparent
unappetizing
unappreciative
unary
unassertive
unassimilable
unassuming/Y
unavailing/Y
unaware/S
unbeknown
unbeknownst
unbend/SG
unbent
unbid
unblinking/Y
unblushing/Y
unbosom/DG
unbound/D
unbox/JGDS
unbreakable
unbroken
uncanny/T
uncap/S
uncaring
unceasing/Y
unchangeable
uncharacteristic
uncharitable
unchaste/RT
uncial/M
uncle/SM
unclean/DRPT
uncleanly/T
unclear/DRT
uncomfortable
uncommon/T
uncompelling
uncomplaining/Y
uncomplicated
uncomprehending/Y
uncompromising/Y
unconditional/Y
uncongenial
unconscionable
unconscionably
unconscious/M
unconstitutional/Y
uncontrollably
uncontroversial
uncool
uncooperative
uncouth/Y
uncrushable
unction/SM
unctuous/YP
unctuousness/M
uncut
undaunted/Y
undebatably
undecided/SM
undemonstrative/Y
undeniably
under
underachieve/LZGDRS
underachiever/M
underact/SDG
underage
underappreciated
underarm/SM
underbelly/SM
underbid/S
underbidding
underbrush/M
undercarriage/MS
undercharge/MGDS
underclass/MS
underclassman/M
underclassmen
underclothes/M
underclothing/M
undercoat/GJSMD
undercoating/M
undercover
undercurrent/SM
undercut/SM
undercutting
underdeveloped
underdevelopment/M
underdog/SM
underdone
underemployed
underemployment/M
underestimate/DSMGNX
underestimation/M
underexpose/GDS
underexposure/MS
underfed
underfeed/GS
underfloor
underflow
underfoot
underfunded
underfur/M
undergarment/SM
undergo/G
undergoes
undergone
undergrad/S
undergraduate/SM
underground/MS
undergrowth/M
underhand
underhanded/PY
underhandedness/M
underinclusive
underinflated
underlain
underlay/ZSMR
underlayer/M
underlie/S
underline/MGDS
underling/MS
underlip/SM
underlying
undermanned
undermentioned
undermine/GDS
undermost
underneath/M
underneaths
undernourished
undernourishment/M
underpaid
underpants/M
underpart/MS
underpass/MS
underpay/GSL
underpayment/SM
underpin/S
underpinned
underpinning/MS
underplay/DGS
underpopulated
underprivileged
underproduction/M
underrate/GDS
underrepresented
underscore/DSMG
undersea/S
undersecretary/SM
undersell/GS
undersexed
undershirt/SM
undershoot/SG
undershorts/M
undershot
underside/MS
undersign/DGS
undersigned/M
undersized
underskirt/SM
undersold
understaffed
understand/SGBJ
understandably
understanding/MY
understate/DSLG
understatement/SM
understood
understudy/GDSM
undertake/ZGJRS
undertaken
undertaker/M
undertaking/M
underthings/M
undertone/MS
undertook
undertow/SM
underused
underutilized
undervaluation/M
undervalue/DSG
underwater
underway
underwear/M
underweight/M
underwent
underwhelm/DGS
underwing/MS
underwire/DS
underworld/MS
underwrite/ZGRS
underwriter/M
underwritten
underwrote
undesirable/MS
undies/M
undo
undoubted/Y
undramatic
undreamt
undue
undulant
undulate/DSXGN
undulation/M
undying
unearthliness/M
unease/M
uneasy/T
uneatable
uneconomic
unemployed/M
unending
unenterprising
unequal/DY
unerring/Y
unessential
uneven/Y
unexceptionably
unexcited
unexciting
unexpected/YP
unexpectedness/M
unexplainably
unfailing/Y
unfair/PTRY
unfaltering
unfamiliar
unfathomably
unfed
unfeeling/Y
unfeminine
unfit/S
unfitting
unfix/GDS
unflagging/Y
unflappability/M
unflappable
unflappably
unflattering
unflinching/Y
unforgettably
unforgivably
unfortunate/MS
unfriendly/T
unfrock/DG
unfruitful
unfunny
ungainliness/M
ungainly/RPT
ungenerous
ungentle
ungodly/T
ungraceful/Y
ungrudging
unguarded
unguent/SM
ungulate/MS
unhandy/T
unhappy/T
unhealthful
unhealthy/T
unhistorical
unholy/T
unhurt
unicameral
unicellular
unicorn/SM
unicycle/SM
unidirectional
unification/AM
uniform/SMDYG
uniformity/M
unify/AGDSN
unilateral/Y
unilateralism
unimportant
unimpressive
uninformative
uninhibited/Y
uninsured
unintelligent
unintended
uninteresting
uninterrupted/Y
uninviting
union/ASM
unionism/M
unionist/MS
unique/YTRP
uniqueness/M
unisex/M
unison/M
unitary
unite/AEGSD
unitedly
unities
unitize/DSG
unity/EM
univalent
univalve/SM
universal/MYS
universalism
universalist
universality/M
universalize/DSG
universe/SM
university/SM
univocal
unjust/Y
unkempt
unkind/T
unkindly/T
unknowable/M
unknown/SM
unleaded/M
unless
unlike/PB
unlikely/T
unlit
unlock/DSG
unlovable
unlovely/TR
unloving
unlucky/T
unman/S
unmanly/T
unmarried
unmeaning
unmentionable/MS
unmentionables/M
unmet
unmindful
unmissable
unmistakably
unmoral
unmovable
unmusical
unnecessary
unnerving/Y
unobservant
unoffensive
unofficial/Y
unoriginal
unpeople
unperceptive
unpersuasive
unpick/GDS
unpin/S
unpleasing
unpolitical
unpopular
unpractical
unprecedented/Y
unprofessional/Y
unpromising
unpropitious
unquestioning/Y
unquiet/TR
unread/B
unready
unreal
unreasoning
unregenerate
unrelated
unrelenting/Y
unrelieved/Y
unremarkable
unremitting/Y
unrepentant
unreported
unrepresentative
unrequest/D
unrest/M
unrevealing
unrideable
unripe/TR
unroll/GDS
unromantic
unruliness/M
unruly/RTP
unsafe/YTR
unsavory
unsaw
unscathed
unsee/S
unseeing/Y
unseemly/T
unseen/M
unsentimental
unset
unshakable
unshakably
unshakeable
unshapely
unsharp
unshockable
unshorn
unsightliness/M
unsightly/PT
unsmiling
unsociable
unsocial
unsold
unsound/PRYT
unspeakable
unspeakably
unspecific
unspectacular
unsporting
unstable
unsteady/TRP
unstinting/Y
unstrapping
unsubstantial
unsubtle
unsuitable
unsure
unsuspecting/Y
unsymmetrical
untactful
unthinkably
unthinking/Y
untidy/PTR
until
untimely/T
untiring/Y
untouchable/MS
untoward
untrue/RT
untrustworthy
untruth/M
unutterable
unutterably
unwarrantable
unwary/T
unwavering
unwed
unwelcome/G
unwell
unwieldiness/M
unwieldy/TRP
unwise/RYT
unworried
unworthy/T
unwound
unwrapping
unyielding
up/S
upbeat/MS
upbraid/SGD
upbringing/MS
upchuck/SGD
upcoming
upcountry/M
update/MGDRS
updraft/MS
upend/SGD
upfront
upgrade/MGDS
upheaval/MS
upheld
uphill/MS
uphold/ZGRS
upholder/M
upholster/ASGD
upholsterer/MS
upholstery/M
upkeep/M
upland/MS
uplift/JSMDG
uplink/SM
upload/SDG
upmarket
upmost
upon
upped
upper/SM
uppercase/M
upperclassman/M
upperclassmen
upperclasswoman
upperclasswomen
uppercut/MS
uppercutting
uppermost
upping
uppish
uppity
upraise/DSG
uprear/GSD
upright/MYPS
uprightness/M
uprising/SM
upriver
uproar/SM
uproarious/Y
uproot/GSD
upscale
upset/SM
upsetting
upshot/SM
upside/SM
upsilon/MS
upstage/GDS
upstairs
upstanding
upstart/MDSG
upstate/M
upstream
upstroke/SM
upsurge/MGDS
upswing/MS
uptake/SM
uptempo
upthrust/GSM
uptick/SM
uptight
uptime
uptown/M
uptrend
upturn/GSMD
upvote/DS
upward/SY
upwind
uracil/M
uranium/M
urban
urbane/RYT
urbanity/M
urbanization/M
urbanize/DSG
urbanologist/MS
urbanology/M
urchin/SM
urea/M
uremia/M
uremic
ureter/SM
urethane/M
urethra/M
urethrae
urethral
urge/MGDS
urgency/M
urgent/Y
uric
urinal/SM
urinalyses
urinalysis/M
urinary
urinate/GNDS
urination/M
urine/M
urn/SM
urogenital
urological
urologist/MS
urology/M
ursine
urticaria/M
usability/M
usable/UA
usage/SM
use/AEDSMG
used/U
useful/PY
usefulness/M
useless/YP
uselessness/M
user/MS
username/MS
usher/SMDG
usherette/SM
usu
usual's
usual/UY
usufruct/SM
usurer/SM
usurious
usurp/SDRZG
usurpation/M
usurper/M
usury/M
utensil/SM
uteri
uterine
utero
uterus/M
utilitarian/MS
utilitarianism/M
utility/SM
utilization/M
utilize/GBDS
utmost/M
utopia/SM
utopian/MS
utter/SDYG
utterance/SM
uttermost/M
uveitis
uvula/SM
uvular/MS
uxorious
v/AS
vac/S
vacancy/SM
vacant/Y
vacate/DSG
vacation/ZGMDRS
vacationer/M
vacationist/SM
vaccinate/GNDSX
vaccinated/U
vaccination/M
vaccinator/S
vaccine/SM
vacillate/XGNDS
vacillation/M
vacinal
vacuity/M
vacuole/MS
vacuous/YP
vacuousness/M
vacuum/GSMD
vagabond/SMDG
vagabondage/M
vagarious
vagary/SM
vagina/SM
vaginae
vaginal/Y
vaginitis
vagrancy/M
vagrant/MS
vague/RYTP
vagueness/M
vagus
vain/RYT
vainglorious/Y
vainglory/M
val
valance/MS
vale/MS
valediction/MS
valedictorian/SM
valedictory/SM
valence/MS
valency/SM
valentine/SM
valet/SMDG
valetudinarian/MS
valetudinarianism/M
valiance/M
valiant/Y
valid/Y
validate/IGNDS
validation/IM
validations
validator/S
validities
validity/IM
validness/M
valise/SM
valley/SM
valor/M
valorous/Y
valuable/MS
valuate/DSG
valuation/CAMS
value's
value/CAGSD
valueless
valuer/SM
valve/DSMG
valveless
valvular
vamoose/DSG
vamp/AMDGS
vampire/SM
van/SM
vanadium/M
vandal/SM
vandalism/M
vandalize/DSG
vane/MS
vanguard/MS
vanilla/SM
vanish/JDSG
vanishing/Y
vanity/SM
vanned
vanning
vanquish/ZGDRS
vanquisher/M
vantage/SM
vape/GDS
vapid/YP
vapidity/M
vapidness/M
vapor/SM
vaporization/M
vaporize/DRSZG
vaporizer/M
vaporous
vaporware
vapory
vaquero/MS
var/S
variability/IM
variable/ISM
variably/I
variance/SM
variant/MS
variate/NX
variation/M
varicolored
varicose
varied/U
variegate/DSGN
variegation/M
varietal/SM
variety/SM
various/Y
varlet/SM
varmint/MS
varnish/GMDS
varnished/U
varsity/SM
vary/DSG
varying/U
vascular
vase/MS
vasectomy/SM
vasoconstriction
vasomotor
vasopressor/SM
vassal/SM
vassalage/M
vast/MRYTSP
vastness/M
vat/SM
vatted
vatting
vaudeville/M
vaudevillian/MS
vault/SMDRZG
vaulter/M
vaulting/M
vaunt/SMDG
vb
veal/M
vector/SGMD
veejay/SM
veep/MS
veer/MDGS
veg/SM
vegan/SM
veganism
vegeburger/S
veges
vegetable/SM
vegetarian/SM
vegetarianism/M
vegetate/GNVDS
vegetation/M
vegged
vegges
veggie/SM
veggieburger/S
vegging
vehemence/M
vehemency/M
vehement/Y
vehicle/MS
vehicular
veil's
veil/UDGS
vein/MDGS
vela
velar/SM
veld/MS
vellum/M
velocipede/MS
velocity/SM
velodrome/S
velour/MS
velum/M
velvet/M
velveteen/M
velvety
venal/Y
venality/M
venation/M
vend/DGS
vendetta/SM
vendible
vendor/MS
veneer/MDGS
venerability/M
venerable
venerate/DSGN
veneration/M
venereal
vengeance/M
vengeful/AY
venial
venireman/M
veniremen
venison/M
venom/M
venomous/Y
venous
vent's
vent/DGS
ventilate/GNDS
ventilation/M
ventilator/SM
ventilatory
ventral
ventricle/SM
ventricular
ventriloquism/M
ventriloquist/SM
ventriloquy/M
venture/DSMG
venturesome/PY
venturesomeness/M
venturous/PY
venturousness/M
venue/ASM
veracious/Y
veracity/M
veranda/SM
verandah/M
verandahs
verapamil
verb/KMDGS
verbal/MYS
verbalization/M
verbalize/GDS
verbatim
verbena/SM
verbiage/MS
verbose/Y
verbosity/M
verboten
verdant/Y
verdict/SM
verdigris/GMDS
verdure/M
verge's
verge/FDSG
verger/MS
verifiability
verifiable/U
verifiably
verification/M
verified/U
verifier/M
verify/DRSNZG
verily
verisimilitude/M
veritable
veritably
verity/SM
vermicelli/M
vermiculite/M
vermiform
vermilion/M
vermin/M
verminous
vermouth/M
vernacular/MS
vernal
vernier/SM
veronica/M
verruca/SM
verrucae
versa
versatile
versatility/M
verse/AFNGMSDX
versed/U
versification/M
versifier/M
versify/ZGNDRS
version/AFIMS
versioned
versioning
verso/SM
versus
vert/A
vertebra/M
vertebrae
vertebral
vertebrata
vertebrate/IMS
vertebrobasilar
vertex/MS
vertical/MYS
vertices
vertiginous
vertigo/M
verve/M
very/RT
vesicle/SM
vesicular
vesiculate
vesper/MS
vessel/MS
vest's
vest/ILDGS
vestal/MS
vestibule/MS
vestige/SM
vestigial/Y
vesting/M
vestment/IMS
vestry/SM
vestryman/M
vestrymen
vet/SM
vetch/MS
veteran/SM
veterinarian/MS
veterinary/SM
veto/MDG
vetoes
vetted
vetting
vex/GDS
vexation/SM
vexatious/Y
vhf
vi
via
viability/M
viable
viably
viaduct/SM
vial/MS
viand/SM
vibe/MS
vibes/M
vibraharp/SM
vibrancy/M
vibrant/Y
vibraphone/MS
vibraphonist/MS
vibrate/GNDSX
vibration/M
vibrational
vibrationless
vibrato/MS
vibrator/SM
vibratory
viburnum/SM
vicar/SM
vicarage/SM
vicarious/YP
vicariousness/M
vice/CMS
viced
vicegerent/SM
vicennial
viceregal
viceroy/MS
vichyssoise/M
vicing
vicinity/M
vicious/YP
viciousness/M
vicissitude/SM
victim/MS
victimization/M
victimize/GDS
victimless
victor/MS
victorious/Y
victory/SM
victual/SMDG
vicuna/MS
vicu�a/MS
videlicet
video/GSMD
videocassette/SM
videoconferencing
videodisc/MS
videographer/MS
videography/SM
videophile/MS
videophone/MS
videotape/DSMG
videotex
vie/DS
view/AMDRBSZG
viewer/AM
viewership/M
viewfinder/SM
viewing/SM
viewpoint/MS
vigesimal
vigil/SM
vigilance/M
vigilant/Y
vigilante/SM
vigilantism/M
vigilantist/M
vignette/DSMG
vignettist/MS
vigor/M
vigorous/Y
vii
viii
viking/MS
vile/YTPR
vileness/M
vilification/M
vilify/DSNG
villa/SM
village/RSMZ
villager/M
villain/SM
villainous
villainy/SM
villein/SM
villeinage/M
villi
villus/M
vim/M
vinaigrette/M
vincible/I
vindicate/XDSGN
vindication/M
vindicator/MS
vindictive/PY
vindictiveness/M
vine/MS
vinegar/M
vinegary
vineyard/MS
vino/M
vinous
vintage/MS
vintner/MS
vinyl/SM
viol/MBS
viola/SM
violable/I
violate/GNDSX
violation/M
violator/SM
violence/M
violent/Y
violet/MS
violin/MS
violincello/S
violinist/SM
violist/MS
violoncellist/SM
violoncello/MS
viper/SM
viperous
virago/M
viragoes
viral
vireo/SM
virgin/MS
virginal/SM
virginity/M
virgule/MS
virile
virility/M
virologist/SM
virology/M
virtual/Y
virtualization
virtue/SM
virtuosity/M
virtuoso/SM
virtuous/YP
virtuousness/M
virulence/M
virulent/Y
virus/MS
visa/MDSG
visage/MS
viscera
visceral/Y
viscid
viscose/M
viscosity/M
viscount/SM
viscountcy/SM
viscountess/MS
viscous
viscus/M
vise/ACMGDS
visibility/IM
visible/I
visibly/I
vision/KGDSM
visionary/SM
visit's
visit/ASGD
visitant/MS
visitation/MS
visitor/MS
visor/SM
vista/SM
visual/SMY
visualization/SM
visualize/DRSZG
visualizer/M
vita/M
vitae
vital/SY
vitality/M
vitalization/AM
vitalize/CAGSD
vitals/M
vitamin/MS
vitiate/GNDS
vitiation/M
viticulture/M
viticulturist/MS
vitreous
vitrifaction/M
vitrification/M
vitrify/GNDS
vitrine/SM
vitriol/M
vitriolic
vitriolically
vittles/M
vituperate/GNVDS
vituperation/M
viva/MS
vivace
vivacious/PY
vivaciousness/M
vivacity/M
vivant/S
vivaria
vivarium/SM
vivid/RYTP
vividness/M
vivify/ADSG
viviparous
vivisect/DGS
vivisection/M
vivisectional
vivisectionist/SM
vixen/SM
vixenish/Y
viz
vizier/SM
vlf
vlogged
vlogger/M
vlogging
vocab
vocable/MS
vocabulary/SM
vocal/SMY
vocalic
vocalist/SM
vocalization/MS
vocalize/DSG
vocation/FIKASM
vocational/Y
vocative/MS
vociferate/DSGN
vociferation/M
vociferous/YP
vociferousness/M
vocoder/S
vodka/SM
vogue/SM
voguish
voice/IDSMG
voiced/U
voiceless/PY
voicelessness/M
voicemail/SM
void/MDSGB
voila
voile/M
voil�
vol/S
volatile/S
volatility/M
volatilize/DSG
volcanic
volcanism
volcano/M
volcanoes
volcanological
volcanologist/MS
volcanology/M
vole/MS
volition/M
volitional
volley/GSMD
volleyball/MS
volt/AMS
voltage/MS
voltaic
voltmeter/SM
volubility/M
voluble
volubly
volume/SM
volumetric
voluminous/YP
voluminousness/M
voluntarily/I
voluntarism/M
voluntary/SM
volunteer/SGMD
volunteerism/M
voluptuary/SM
voluptuous/PY
voluptuousness/M
volute/SM
vomit/SMDG
voodoo/GSMD
voodooism/M
voracious/PY
voraciousness/M
voracity/M
vortex/MS
votary/SM
vote's
vote/CGVDS
voter/SM
vouch/DRSZG
voucher/M
vouchsafe/DSG
vow/SGMD
vowel/SM
voyage/MZGDRS
voyager/M
voyageur/SM
voyeur/MS
voyeurism/M
voyeuristic
vulcanism
vulcanization/M
vulcanize/GDS
vulgar/RYT
vulgarian/MS
vulgarism/MS
vulgarity/SM
vulgarization/M
vulgarize/ZGDRS
vulgarizer/M
vulnerabilities
vulnerability/IM
vulnerable/I
vulnerably/I
vulpine
vulture/SM
vulturous
vulva/M
vulvae
vuvuzela/MS
vying
w/DNXTGVJ
wabbit/S
wack/MRTS
wackiness/M
wacko/SM
wacky/RPT
wad/SZGMDR
wadded
wadding/M
waddle/DSMG
wade/MS
wader/M
waders/M
wadge/S
wadi/MS
wafer/SM
waffle/MZGDRS
waffler/M
waft/MDGS
wag/SZGMDR
wage/MS
waged/U
wager/ZGMDR
wagerer/M
wagged
waggery/SM
wagging
waggish/YP
waggishness/M
waggle/MGDS
wagon/ZSMR
wagoner/M
wagtail/SM
waif/MS
wail/MDRZGS
wailer/M
wailing/M
wain/MS
wainscot/SJMDG
wainscoting/M
wainscotted
wainscotting/MS
wainwright/MS
waist/SM
waistband/MS
waistcoat/MS
waistline/MS
wait/MDRZGS
waiter/M
waiting/M
waitperson/MS
waitress/MS
waitstaff/M
waive/DRSZG
waiver/M
wake/MGJDS
wakeful/PY
wakefulness/M
waken/GSD
waldo/S
waldoes
wale/MGDS
walk/MDRZGS
walkabout/S
walkaway/MS
walker/M
walkies
walking/M
walkout/SM
walkover/MS
walkway/SM
wall/MDGS
wallaby/SM
wallah
wallahs
wallboard/M
wallet/MS
walleye/DSM
wallflower/MS
wallop/MDSJG
walloping/M
wallow/MDSG
wallpaper/SMDG
wally/S
walnut/MS
walrus/MS
waltz/ZGMDRS
waltzer/M
wampum/M
wan/GPDY
wand/MS
wander/DRSJZG
wanderer/M
wanderings/M
wanderlust/SM
wane/MS
wangle/MZGDRS
wangler/M
wank/DRZGS
wanna
wannabe/SM
wannabee/S
wanner
wanness/M
wannest
want/MDGS
wanted/U
wanton/MDYSPG
wantonness/M
wapiti/MS
war/SM
warble/MZGDRS
warbler/M
warbonnet/SM
ward/AMDGS
warden/MS
warder/MS
wardress/S
wardrobe/SM
wardroom/SM
ware/MS
warehouse/DSMG
warez
warfare/M
warfarin
warhead/MS
warhorse/SM
warily/U
wariness/UM
warlike
warlock/MS
warlord/MS
warm/PDRYHZTGS
warmblooded
warmer/M
warmhearted/P
warmheartedness/M
warmish
warmness/M
warmonger/SMG
warmongering/M
warmth/M
warmup/MS
warn/JDGS
warning/M
warp/MDGS
warpaint
warpath/M
warpaths
warplane/MS
warrant/GMDS
warranted/U
warranty/DSMG
warred
warren/MS
warring
warrior/SM
warship/SM
wart/MS
warthog/SM
wartime/M
warty/TR
wary/UPRT
was
wasabi
wash/BJMDRSZG
washable/SM
washbasin/SM
washboard/SM
washbowl/SM
washcloth/M
washcloths
washed/U
washer/M
washerwoman/M
washerwomen
washing/M
washout/MS
washrag/MS
washroom/MS
washstand/SM
washtub/MS
washy/TR
wasn't
wasp/MS
waspish/YP
waspishness/M
wassail/SMDG
wast
wastage/M
waste/DRSMZG
wastebasket/MS
wasteful/PY
wastefulness/M
wasteland/SM
wastepaper/M
waster/M
wastewater
wastrel/SM
watch/BZGMDRS
watchable/U
watchband/MS
watchdog/SM
watcher/M
watchful/YP
watchfulness/M
watchmaker/MS
watchmaking/M
watchman/M
watchmen
watchstrap/S
watchtower/SM
watchword/MS
water/GSMD
waterbed/MS
waterbird/SM
waterboard/MDJSG
waterboarding/M
waterborne
watercolor/MS
watercourse/SM
watercraft/M
watercress/M
waterfall/SM
waterfowl/SM
waterfront/MS
waterhole/SM
wateriness/M
waterlily/SM
waterline/MS
waterlogged
watermark/MDGS
watermelon/SM
watermill/MS
waterpower/M
waterproof/SMDG
waterproofing/M
waters/M
watershed/MS
waterside/MS
waterspout/SM
watertight
waterway/MS
waterwheel/SM
waterworks/M
watery/PTR
watt/MS
wattage/M
wattle/MGDS
wave/MZGDRS
waveband/S
waveform
wavefront
wavelength/M
wavelengths
wavelet/SM
wavelike
waver/ZGMDR
waverer/M
wavering/Y
waviness/M
wavy/PRT
wax/GMDNS
waxiness/M
waxwing/SM
waxwork/SM
waxy/RPT
way/SM
waybill/SM
wayfarer/MS
wayfaring/SM
waylaid
waylay/RSZG
waylayer/M
wayside/SM
wayward/PY
waywardness/M
wazoo/S
we
we'd
we'll
we're
we've
weak/PNRYXT
weaken/DRZG
weakener/M
weakfish/MS
weakish
weakling/SM
weakness/MS
weal/MHS
wealth/M
wealthiness/M
wealthy/TRP
wean/DGS
weapon/MS
weaponize/GDS
weaponless
weaponry/M
wear/MRBJSZG
wearable/U
wearer/M
wearied/U
wearily
weariness/M
wearisome/Y
weary/TGDRSP
weasel/MDYSG
weather/SMDG
weatherboard/SG
weathercock/MS
weathering/M
weatherization/M
weatherize/DSG
weatherman/M
weathermen
weatherperson/MS
weatherproof/GSD
weatherstrip/S
weatherstripped
weatherstripping/M
weave/DRSMZG
weaver/M
weaving/M
web/SM
webbed
webbing/M
webcam/MS
webcast/SMG
webdesign/MS
webfeet
webfoot/M
webinar/SM
webisode/MS
weblog/MS
webmail/SM
webmaster/SM
webmistress/MS
webpage/SM
website/SM
wed/AS
wedded/A
wedder
wedding/SM
wedge/DSMG
wedgie/MS
wedlock/M
wee/RSMT
weed/MDRSZG
weeder/M
weedkiller/S
weedless
weedy/TR
weeing
week/MYS
weekday/SM
weekend/SZGMDR
weekly/SM
weeknight/SM
ween/DSG
weenie/MTRS
weensy/RT
weeny
weep/MRJSZG
weeper/M
weepie
weepy/TRSM
weevil/MS
weft/MS
weigh's
weigh/AGD
weighbridge/S
weighs/A
weight/MDSJG
weighted/U
weightily
weightiness/M
weightless/YP
weightlessness/M
weightlifter/MS
weightlifting/M
weighty/PTR
weir/MS
weird/PTGDRY
weirdie/MS
weirdness/M
weirdo/MS
welcome/MGDS
weld/MDRBSZG
welder/M
welfare/M
welkin/M
well/MDPSG
wellhead/SM
wellie
wellington/MS
wellness/M
wellspring/MS
welly/S
welp
welsh/ZGDRS
welsher/M
welt/MDRSZG
welter/GMD
welterweight/SM
wen/M
wench/MS
wend/DSG
went
wept
were
weren't
werewolf/M
werewolves
west/M
westbound
westerly/SM
western/SZMR
westerner/M
westernization/M
westernize/GDS
westernmost
westward/S
wet/SMYP
wetback/SM
wetland/SM
wetness/M
wetter/SM
wettest
wetting
wetware/S
whack/SJZGMDR
whacker/M
whale/DRSMZG
whaleboat/MS
whalebone/M
whaler/M
whaling/M
wham/MS
whammed
whamming
whammy/SM
wharf/M
wharves
what/MS
whatchamacallit/MS
whatever
whatnot/M
whatshername
whatshisname
whatsit/S
whatsoever
wheal/SM
wheat/MN
wheatgerm
wheatmeal
whee
wheedle/DRSZG
wheedler/M
wheel/SMDRG
wheelbarrow/SM
wheelbase/SM
wheelchair/SM
wheelhouse/MS
wheelie/SM
wheelwright/MS
wheeze/DSMG
wheezily
wheeziness/M
wheezy/PRT
whelk/SMD
whelm/SDG
whelp/SMDG
when/MS
whence
whenever
whensoever
where/SM
whereabouts/M
whereas
whereat
whereby
wherefore/MS
wherein
whereof
whereon
wheresoever
whereto
whereupon
wherever
wherewith
wherewithal/M
wherry/SM
whet/S
whether
whetstone/SM
whetted
whetting
whew
whey/M
which
whichever
whiff/SMDG
whiffletree/MS
while/DSMG
whilom
whilst
whim/MS
whimper/MDGS
whimsical/Y
whimsicality/M
whimsy/SM
whine/DRSMZG
whiner/M
whinge/DRSZG
whingeing
whinny/GDSM
whiny/RT
whip/MS
whipcord/M
whiplash/MS
whipped
whipper/MS
whippersnapper/MS
whippet/MS
whipping/SM
whippletree/SM
whippoorwill/MS
whipsaw/MDGS
whir/MS
whirl/SMDG
whirligig/MS
whirlpool/MS
whirlwind/MS
whirlybird/SM
whirred
whirring
whisk/SMDRZG
whisker/MD
whiskery
whiskey/MS
whisky/SM
whiskys
whisper/MDRSZG
whisperer/M
whist/M
whistle/MZGDRS
whistleblower/GMS
whistler/M
whit/MDNRSXTGJ
white/SPM
whitebait
whiteboard/S
whitecap/SM
whitefish/MS
whitehead/MS
whitelist/GDS
whiten/ZGDRJ
whitener/M
whiteness/M
whitening/M
whiteout/SM
whitepaper/MS
whitetail/MS
whitewall/SM
whitewash/MDSG
whitewater/M
whitey/SM
whither
whiting/M
whitish
whittle/ZGDRS
whittler/M
whiz/M
whizkid/M
whizz/MDSG
whizzbang/MS
who'd
who'll
who're
who've
who/M
whoa
whodunit/MS
whoever
whole/SMP
wholefood/S
wholegrain
wholehearted/YP
wholeheartedness/M
wholemeal
wholeness/M
wholesale/MZGDRS
wholesaler/M
wholesome/UP
wholesomely
wholesomeness/UM
wholewheat
wholly
whom
whomever
whomsoever
whoop/SMDRZG
whoopee/S
whooper/M
whoosh/MDSG
whop/S
whopped
whopper/SM
whopping
whore/SMG
whorehouse/MS
whoreish
whorish
whorl/SMD
whose
whoso
whosoever
whup/S
whupped
whupping
why'd
why/M
whys
wick/MDRSZGJ
wicked/TPRY
wickedness/M
wicker/M
wickerwork/M
wicket/SM
wide/YTRP
widemouthed
widen/SDRZG
widener/M
wideness/M
widescreen/MS
widespread
widgeon/MS
widget/S
widow/SMDRZG
widower/M
widowhood/M
width/M
widths
wield/SDRZG
wielder/M
wiener/SM
wienie/SM
wife/MY
wifeless
wig/SM
wigeon/SM
wigged
wigging
wiggle/DRSMZG
wiggler/M
wiggly/TR
wight/SM
wiglet/SM
wigwag/SM
wigwagged
wigwagging
wigwam/SM
wiki/MS
wild/MRYSTP
wildcard/MS
wildcat/MS
wildcatted
wildcatter/MS
wildcatting
wildebeest/MS
wilderness/MS
wildfire/MS
wildflower/SM
wildfowl/M
wildlife/M
wildness/M
wilds/M
wile/MGDS
wilful/P
wilfulness/M
wiliness/M
will/MDS
willful/PY
willfulness/M
willies/M
willing/UPY
willingness/UM
williwaw/MS
willow/SM
willowy
willpower/M
willy-nilly
willy/S
wilt/MDSG
wily/RTP
wimp/MDSG
wimpish
wimple/DSMG
wimpy/RT
win/SGMD
wince/DSMG
winch/MDSG
wind's
wind/UASG
windbag/SM
windblown
windbreak/SZMR
windbreaker/M
windburn/MD
windcheater/S
windchill/M
winded
winder/SM
windfall/MS
windflower/MS
windily
windiness/M
winding/SM
windjammer/SM
windlass/MS
windless
windmill/MDGS
window/SMDG
windowless
windowpane/SM
windowsill/SM
windpipe/MS
windproof
windrow/SM
windscreen/SM
windshield/SM
windsock/MS
windstorm/MS
windsurf/ZGDRS
windsurfer/M
windsurfing/M
windswept
windup/SM
windward/M
windy/RTP
wine/MS
wineglass/MS
winegrower/MS
winemaker/MS
winery/SM
wineshop/MS
wing/MDRZG
wingding/MS
wingless
winglike
wingnut/SM
wingspan/MS
wingspread/SM
wingtip/SM
wink/MDRSZG
winker/M
winkle/DSMG
winnable/U
winner/SM
winning/MYS
winnow/ZGSDR
winnower/M
wino/MS
winsome/YTRP
winsomeness/M
winsorization
winsorize/GDS
winter/GSMD
wintergreen/M
winterize/GDS
wintertime/M
wintry/TR
winy/RT
wipe/MZGDRS
wiper/M
wire's
wire/AGDS
wired/S
wirehair/MS
wireless/MS
wiretap/MS
wiretapped
wiretapper/SM
wiretapping/M
wiriness/M
wiring/M
wiry/RTP
wisdom/M
wise/MYTGDRS
wiseacre/SM
wisecrack/MDSG
wiseguy/S
wish/MDRSZG
wishbone/SM
wisher/M
wishful/Y
wishlist's
wishy-washy
wisp/MS
wispy/RT
wist
wisteria/SM
wistful/YP
wistfulness/M
wit/SM
witch/MDSG
witchcraft/M
witchery/M
with
withal
withdraw/SG
withdrawal/MS
withdrawn
withdrew
withe/DRSMZG
wither/JGD
withering/Y
withers/M
withheld
withhold/SG
withholding/M
within/M
without
withstand/GS
withstood
witless/PY
witlessness/M
witness/MDSG
wits/M
witted
witter/SGD
witticism/SM
wittily
wittiness/M
witting/UY
witty/RPT
wive/GDS
wiz/S
wizard/SMY
wizardry/M
wizened
wk/Y
woad/M
woah
wobble/MGDS
wobbliness/M
wobbly/RTP
wodge/S
woe/SM
woebegone
woeful/YP
woefuller
woefullest
woefulness/M
wog/S
wok/SMN
woke
wold/MS
wolf/MDSG
wolfhound/SM
wolfish
wolfram/M
wolverine/SM
wolves
woman/M
womanhood/M
womanish
womanize/DRSZG
womanizer/M
womankind/M
womanlike/M
womanliness/M
womanly/RPT
womb/MS
wombat/MS
womble/S
women/M
womenfolk/SM
womenfolks/M
won't
won/M
wonder/MDGLS
wonderful/YP
wonderfulness/M
wondering/Y
wonderland/MS
wonderment/M
wondrous/Y
wonk/MS
wonky/TR
wont/MD
wonted/U
woo/SZGDR
wood/MDNSG
woodbine/M
woodblock/MS
woodcarver/MS
woodcarving/SM
woodchuck/MS
woodcock/SM
woodcraft/M
woodcut/SM
woodcutter/SM
woodcutting/M
wooden/RYTP
woodenness/M
woodiness/M
woodland/SM
woodlice
woodlot/SM
woodlouse
woodman/M
woodmen
woodpecker/MS
woodpile/SM
woods/M
woodshed/SM
woodsiness/M
woodsman/M
woodsmen
woodsy/RTP
woodwind/MS
woodwork/MRZG
woodworker/M
woodworking/M
woodworm/S
woody/TPRSM
wooer/M
woof/MDRSZG
woofer/M
wool/MNX
woolen/M
woolgathering/M
wooliness
woolliness/M
woolly/RSMPT
woozily
wooziness/M
woozy/TRP
wop/MS!
word's
word/ADSG
wordage/M
wordbook/SM
wordie
wordily
wordiness/M
wording/SM
wordless/Y
wordplay/M
wordsmith
wordsmiths
wordy/TPRS
wore
work's
work/ADJSG
workable/U
workaday
workaholic/SM
workaround/S
workbasket/S
workbench/MS
workbook/MS
workday/SM
worker/MS
workfare/M
workflow/MS
workforce/M
workhorse/SM
workhouse/SM
working's
workingman/M
workingmen
workings/M
workingwoman/M
workingwomen
workload/MS
workman/M
workmanlike
workmanship/M
workmate/S
workmen
workout/SM
workplace/MS
workroom/MS
works/M
worksheet/MS
workshop/MS
workshy
worksite/S
workspace
workstation/MS
worktable/MS
worktop/S
workup/MS
workweek/SM
world/SM
worldlier
worldliness/UM
worldly/UTP
worldview/SM
worldwide
worm/MDSG
wormhole/MS
wormwood/M
wormy/TR
worn/U
worried/Y
worrier/M
worriment/M
worrisome
worry/ZGDRSMJ
worrying/Y
worrywart/SM
worse/M
worsen/DSG
worship/ZGSMDR
worshiper/M
worshipful
worst/SGMD
worsted/M
wort/M
worth/M
worthies
worthily/U
worthiness/UM
worthless/PY
worthlessness/M
worthwhile
worthy's
worthy/UPRT
wot
wotcha
would've
would/S
wouldn't
wouldst
wound/SGMDR
wove/A
woven/AU
wow/SGMD
wpm
wrack/GSMD
wraith/M
wraiths
wrangle/DRSMZGJ
wrangler/M
wrap's
wrap/US
wraparound/SM
wrapped/U
wrapper/SM
wrapping/MS
wrasse/MS
wrath/M
wrathful/Y
wreak/SGD
wreath/MDSG
wreathe
wreaths
wreck/SZGMDR
wreckage/M
wrecker/M
wren/MS
wrench/MDSG
wrest/SGMD
wrestle/MZGDRS
wrestler/M
wrestling/M
wretch/MS
wretched/TPRY
wretchedness/M
wriggle/MZGDRS
wriggler/M
wriggly
wright/MS
wring/SZGMR
wringer/M
wrinkle/MGDS
wrinkled/U
wrinkly/TRSM
wrist/SM
wristband/MS
wristwatch/MS
writ/MRBJSZG
write/S
writer/M
writhe/MGDS
writing/M
written/AU
wrong/STGMPDRY
wrongdoer/SM
wrongdoing/SM
wrongful/PY
wrongfulness/M
wrongheaded/YP
wrongheadedness/M
wrongness/M
wrote/A
wroth
wrought
wrung
wry/Y
wryer
wryest
wryness/M
wt
wunderkind/S
wurst/SM
wuss/MS
wussy/RSMT
x
xci
xcii
xciv
xcix
xcvi
xcvii
xenon/M
xenophile/S
xenophobe/MS
xenophobia/M
xenophobic
xerographic
xerography/M
xerox/MDSG
xi/SM
xii
xiii
xiv
xix
xor
xref/S
xterm/M
xv
xvi
xvii
xviii
xx
xxi
xxii
xxiii
xxiv
xxix
xxv
xxvi
xxvii
xxviii
xxx
xxxi
xxxii
xxxiii
xxxiv
xxxix
xxxv
xxxvi
xxxvii
xxxviii
xylem/M
xylene
xylophone/SM
xylophonist/MS
y
y'all
ya
yacht/SMDG
yachting/M
yachtsman/M
yachtsmen
yachtswoman/M
yachtswomen
yahoo/SM
yak/SM
yakked
yakking
yam/SM
yammer/SZGMDR
yammerer/M
yang/M
yank/MDSG
yap/SM
yapped
yapping
yard/MS
yardage/MS
yardarm/MS
yardman/M
yardmaster/MS
yardmen
yardstick/MS
yarmulke/SM
yarn/MS
yarrow/M
yashmak/S
yaw/SGMD
yawl/MS
yawn/MDRSZG
yawner/M
yaws/M
yay
yd
ye/RST
yea/SM
yeah/M
yeahs
year/MYS
yearbook/MS
yearling/MS
yearlong
yearly/SM
yearn/GSJD
yearning/M
yeast/SM
yeasty/RT
yeet/DSG
yegg/MS
yell/MDSG
yellow/MDRTGPS
yellowhammer/S
yellowish
yellowness/M
yellowy
yelp/MDSG
yen/SM
yeoman/M
yeomanry/M
yeomen
yep/SM
yes/MS
yeshiva/SM
yeshivot
yessed
yessing
yesterday/MS
yesteryear/M
yet
yeti/MS
yew/SM
yid/S
yield/JSGMD
yikes
yin/M
yip/SM
yipe
yipped
yippee
yipping
yo
yob/S
yobbo/S
yobibyte/SM
yodel/SMDRZG
yodeler/M
yoga/M
yogi/MS
yogic
yogurt/SM
yoke's
yoke/UGDS
yokel/SM
yolk/MDS
yon
yonder
yonks
yore/M
you'd
you'll
you're
you've
you/SMH
young/TMR
youngish
youngster/MS
your/S
yourself
yourselves
youth/M
youthful/YP
youthfulness/M
youths
yow
yowl/MDSG
yowsa
yowsah
yowza
yowzah
yr/S
ytterbium/M
yttrium/M
yuan/M
yucca/SM
yuck/MDSG
yucky/TR
yuk/SM
yukked
yukking
yukky
yule/M
yuletide/M
yum
yummy/TR
yup/SM
yuppie/MS
yuppify/GDS
yurt/MS
z/DNXTGJ
zaniness/M
zany/RSMPT
zap/SM
zapped
zapper/MS
zapping
zappy
zeal/M
zealot/MS
zealotry/M
zealous/YP
zealousness/M
zebibyte/SM
zebra/SM
zebu/MS
zed/SM
zeitgeist/SM
zenith/M
zeniths
zenned
zeolite/S
zephyr/MS
zeppelin/MS
zero/MDHSG
zeroes
zest/MS
zestful/YP
zestfulness/M
zesty/RT
zeta/MS
zigzag/SM
zigzagged
zigzagging
zilch/M
zillion/MS
zinc/MS
zincked
zincking
zine/S
zinfandel/M
zing/MDRZG
zinger/M
zingy/RT
zinnia/MS
zip's
zip/US
zipped/U
zipper/MDGS
zipping/U
zippy/TR
zircon/MS
zirconium/M
zit/SM
zither/MS
zloty/SM
zlotys
zodiac/MS
zodiacal
zombie/MS
zonal/Y
zone's
zone/AGDS
zoning/M
zonked
zoo/SM
zookeeper/SM
zoological/Y
zoologist/SM
zoology/M
zoom/MDSG
zoomorphism/M
zoonosis
zoonotic
zoophilia/M
zoophyte/SM
zoophytic
zooplankton
zorch
zoster
zounds
zucchini/MS
zuke/S
zwieback/M
zydeco/M
zygote/SM
zygotic
zymurgy/M
�ngstr�m/M
�clair/SM
�clat/M
�lan/M
�migr�/SM
�p�e/MS
�tude/SM
PK
!<�Q�gg7chrome/toolkit/res/autofill/FormAutofillStorage.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Implements an interface of the storage of Form Autofill.
 */

// We expose a singleton from this module. Some tests may import the
// constructor via a backstage pass.
import {
  AddressesBase,
  CreditCardsBase,
  FormAutofillStorageBase,
} from "resource://autofill/FormAutofillStorageBase.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  CreditCard: "resource://gre/modules/CreditCard.sys.mjs",
  JSONFile: "resource://gre/modules/JSONFile.sys.mjs",
  OSKeyStore: "resource://gre/modules/OSKeyStore.sys.mjs",
});

const PROFILE_JSON_FILE_NAME = "autofill-profiles.json";

class Addresses extends AddressesBase {}

class CreditCards extends CreditCardsBase {
  constructor(store) {
    super(store);
  }

  async _encryptNumber(creditCard) {
    if (!("cc-number-encrypted" in creditCard)) {
      if ("cc-number" in creditCard) {
        let ccNumber = creditCard["cc-number"];
        if (lazy.CreditCard.isValidNumber(ccNumber)) {
          creditCard["cc-number"] =
            lazy.CreditCard.getLongMaskedNumber(ccNumber);
        } else {
          // Credit card numbers can be entered on versions of Firefox that don't validate
          // the number and then synced to this version of Firefox. Therefore, mask the
          // full number if the number is invalid on this version.
          creditCard["cc-number"] = "*".repeat(ccNumber.length);
        }
        creditCard["cc-number-encrypted"] = await lazy.OSKeyStore.encrypt(
          ccNumber
        );
      } else {
        creditCard["cc-number-encrypted"] = "";
      }
    }
  }
}

export class FormAutofillStorage extends FormAutofillStorageBase {
  constructor(path) {
    super(path);
  }

  getAddresses() {
    if (!this._addresses) {
      this._store.ensureDataReady();
      this._addresses = new Addresses(this._store);
    }
    return this._addresses;
  }

  getCreditCards() {
    if (!this._creditCards) {
      this._store.ensureDataReady();
      this._creditCards = new CreditCards(this._store);
    }
    return this._creditCards;
  }

  /**
   * Loads the profile data from file to memory.
   *
   * @returns {JSONFile}
   *          The JSONFile store.
   */
  _initializeStore() {
    return new lazy.JSONFile({
      path: this._path,
      dataPostProcessor: this._dataPostProcessor.bind(this),
    });
  }

  _dataPostProcessor(data) {
    data.version = this.version;
    if (!data.addresses) {
      data.addresses = [];
    }
    if (!data.creditCards) {
      data.creditCards = [];
    }
    return data;
  }
}

// The singleton exposed by this module.
export const formAutofillStorage = new FormAutofillStorage(
  PathUtils.join(PathUtils.profileDir, PROFILE_JSON_FILE_NAME)
);
PK
!<��9��;chrome/toolkit/res/autofill/FormAutofillStorageBase.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Interface for the storage of Form Autofill.
 *
 * The data is stored in JSON format, without indentation and the computed
 * fields, using UTF-8 encoding. With indentation and computed fields applied,
 * the schema would look like this:
 *
 * {
 *   version: 1,
 *   addresses: [
 *     {
 *       guid,                 // 12 characters
 *       version,              // schema version in integer
 *
 *       // address fields
 *       given-name,
 *       additional-name,
 *       family-name,
 *       name,
 *       organization,         // Company
 *       street-address,       // (Multiline)
 *       address-level3,       // Suburb/Sublocality
 *       address-level2,       // City/Town
 *       address-level1,       // Province (Standardized code if possible)
 *       postal-code,
 *       country,              // ISO 3166
 *       tel,                  // Stored in E.164 format
 *       email,
 *
 *       // computed fields (These fields are computed based on the above fields
 *       // and are not allowed to be modified directly.)
 *       given-name,
 *       additional-name,
 *       family-name,
 *       address-line1,
 *       address-line2,
 *       address-line3,
 *       country-name,
 *       tel-country-code,
 *       tel-national,
 *       tel-area-code,
 *       tel-local,
 *       tel-local-prefix,
 *       tel-local-suffix,
 *
 *       // metadata
 *       timeCreated,          // in ms
 *       timeLastUsed,         // in ms
 *       timeLastModified,     // in ms
 *       timesUsed,
 *       _sync: { ... optional sync metadata },
 *       ...unknown fields     // We keep fields we don't understand/expect from other clients
 *                             // to prevent data loss for other clients, we roundtrip them for sync
 *     }
 *   ],
 *   creditCards: [
 *     {
 *       guid,                 // 12 characters
 *       version,              // schema version in integer
 *
 *       // credit card fields
 *       billingAddressGUID,   // An optional GUID of an autofill address record
 *                                which may or may not exist locally.
 *
 *       cc-name,
 *       cc-number,            // will be stored in masked format (************1234)
 *                             // (see details below)
 *       cc-exp-month,
 *       cc-exp-year,          // 2-digit year will be converted to 4 digits
 *                             // upon saving
 *       cc-type,              // Optional card network id (instrument type)
 *
 *       // computed fields (These fields are computed based on the above fields
 *       // and are not allowed to be modified directly.)
 *       cc-given-name,
 *       cc-additional-name,
 *       cc-family-name,
 *       cc-number-encrypted,  // encrypted from the original unmasked "cc-number"
 *                             // (see details below)
 *       cc-exp,
 *
 *       // metadata
 *       timeCreated,          // in ms
 *       timeLastUsed,         // in ms
 *       timeLastModified,     // in ms
 *       timesUsed,
 *       _sync: { ... optional sync metadata },
 *       ...unknown fields     // We keep fields we don't understand/expect from other clients
 *                             // to prevent data loss for other clients, we roundtrip them for sync
 *     }
 *   ]
 * }
 *
 *
 * Encrypt-related Credit Card Fields (cc-number & cc-number-encrypted):
 *
 * When saving or updating a credit-card record, the storage will encrypt the
 * value of "cc-number", store the encrypted number in "cc-number-encrypted"
 * field, and replace "cc-number" field with the masked number. These all happen
 * in "computeFields". We do reverse actions in "_stripComputedFields", which
 * decrypts "cc-number-encrypted", restores it to "cc-number", and deletes
 * "cc-number-encrypted". Therefore, calling "_stripComputedFields" followed by
 * "computeFields" can make sure the encrypt-related fields are up-to-date.
 *
 * In general, you have to decrypt the number by your own outside FormAutofillStorage
 * when necessary. However, you will get the decrypted records when querying
 * data with "rawData=true" to ensure they're ready to sync.
 *
 *
 * Sync Metadata:
 *
 * Records may also have a _sync field, which consists of:
 * {
 *   changeCounter,    // integer - the number of changes made since the last
 *                     // sync.
 *   lastSyncedFields, // object - hashes of the original values for fields
 *                     // changed since the last sync.
 * }
 *
 * Records with such a field have previously been synced. Records without such
 * a field are yet to be synced, so are treated specially in some cases (eg,
 * they don't need a tombstone, de-duping logic treats them as special etc).
 * Records without the field are always considered "dirty" from Sync's POV
 * (meaning they will be synced on the next sync), at which time they will gain
 * this new field.
 */

import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs";
import { AddressRecord } from "resource://gre/modules/shared/AddressRecord.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AutofillTelemetry: "resource://gre/modules/shared/AutofillTelemetry.sys.mjs",
  CreditCard: "resource://gre/modules/CreditCard.sys.mjs",
  CreditCardRecord: "resource://gre/modules/shared/CreditCardRecord.sys.mjs",
  FormAutofillNameUtils:
    "resource://gre/modules/shared/FormAutofillNameUtils.sys.mjs",
  FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
  OSKeyStore: "resource://gre/modules/OSKeyStore.sys.mjs",
  PhoneNumber: "resource://gre/modules/shared/PhoneNumber.sys.mjs",
});

const CryptoHash = Components.Constructor(
  "@mozilla.org/security/hash;1",
  "nsICryptoHash",
  "initWithString"
);

const STORAGE_SCHEMA_VERSION = 1;

// NOTE: It's likely this number can never change.
// Please talk to the sync team before changing this!
// (And if it did ever change, it must never be "4" due to the reconcile hacks
// below which repairs credit-cards with version=4)
export const ADDRESS_SCHEMA_VERSION = 1;

// Version 2: Bug 1486954 - Encrypt `cc-number`
// Version 3: Bug 1639795 - Update keystore name
// Version 4: (deprecated!!! See Bug 1812235): Bug 1667257 - Do not store `cc-type` field
// Next version should be 5
// NOTE: It's likely this number can never change.
// Please talk to the sync team before changing this!
export const CREDIT_CARD_SCHEMA_VERSION = 3;

const VALID_ADDRESS_FIELDS = [
  "name",
  "organization",
  "street-address",
  "address-level3",
  "address-level2",
  "address-level1",
  "postal-code",
  "country",
  "tel",
  "email",
];

const VALID_ADDRESS_COMPUTED_FIELDS = [
  "country-name",
  ...AddressRecord.NAME_COMPONENTS,
  ...AddressRecord.STREET_ADDRESS_COMPONENTS,
  ...AddressRecord.TEL_COMPONENTS,
];

const VALID_CREDIT_CARD_FIELDS = [
  "billingAddressGUID",
  "cc-name",
  "cc-number",
  "cc-exp-month",
  "cc-exp-year",
  "cc-type",
];

const VALID_CREDIT_CARD_COMPUTED_FIELDS = [
  "cc-given-name",
  "cc-additional-name",
  "cc-family-name",
  "cc-number-encrypted",
  "cc-exp",
];

const INTERNAL_FIELDS = [
  "guid",
  "version",
  "timeCreated",
  "timeLastUsed",
  "timeLastModified",
  "timesUsed",
];

function sha512(string) {
  if (string == null) {
    return null;
  }
  let encoder = new TextEncoder();
  let bytes = encoder.encode(string);
  let hash = new CryptoHash("sha512");
  hash.update(bytes, bytes.length);
  return hash.finish(/* base64 */ true);
}

/**
 * Class that manipulates records in a specified collection.
 *
 * Note that it is responsible for converting incoming data to a consistent
 * format in the storage. For example, computed fields will be transformed to
 * the original fields and 2-digit years will be calculated into 4 digits.
 */
class AutofillRecords {
  /**
   * Creates an AutofillRecords.
   *
   * @param {JSONFile} store
   *        An instance of JSONFile.
   * @param {string} collectionName
   *        A key of "store.data".
   * @param {Array.<string>} validFields
   *        A list containing non-metadata field names.
   * @param {Array.<string>} validComputedFields
   *        A list containing computed field names.
   * @param {number} schemaVersion
   *        The schema version for the new record.
   */
  constructor(
    store,
    collectionName,
    validFields,
    validComputedFields,
    schemaVersion
  ) {
    this.log = FormAutofill.defineLogGetter(
      lazy,
      "AutofillRecords:" + collectionName
    );

    this.VALID_FIELDS = validFields;
    this.VALID_COMPUTED_FIELDS = validComputedFields;

    this._store = store;
    this._collectionName = collectionName;
    this._schemaVersion = schemaVersion;

    this._initialize();

    Services.obs.addObserver(this, "formautofill-storage-changed");
  }

  _initialize() {
    this._initializePromise = Promise.all(
      this._data.map(async (record, index) =>
        this._migrateRecord(record, index)
      )
    ).then(hasChangesArr => {
      let dataHasChanges = hasChangesArr.includes(true);
      if (dataHasChanges) {
        this._store.saveSoon();
      }
    });
  }

  observe(subject, topic, _data) {
    if (topic == "formautofill-storage-changed") {
      let collectionName = subject.wrappedJSObject.collectionName;
      if (collectionName != this._collectionName) {
        return;
      }
      const telemetryType =
        subject.wrappedJSObject.collectionName == "creditCards"
          ? lazy.AutofillTelemetry.CREDIT_CARD
          : lazy.AutofillTelemetry.ADDRESS;
      const count = this._data.filter(entry => !entry.deleted).length;
      lazy.AutofillTelemetry.recordAutofillProfileCount(telemetryType, count);
    }
  }

  /**
   * Gets the schema version number.
   *
   * @returns {number}
   *          The current schema version number.
   */
  get version() {
    return this._schemaVersion;
  }

  /**
   * Gets the data of this collection.
   *
   * @returns {Array}
   *          The data object.
   */
  get _data() {
    return this._getData();
  }

  _getData() {
    return this._store.data[this._collectionName];
  }

  // Ensures that we don't try to apply synced records with newer schema
  // versions. This is a temporary measure to ensure we don't accidentally
  // bump the schema version without a syncing strategy in place (bug 1377204).
  _ensureMatchingVersion(record) {
    if (record.version != this.version) {
      throw new Error(
        `Got unknown record version ${record.version}; want ${this.version}`
      );
    }
  }

  /**
   * Initialize the records in the collection, resolves when the migration completes.
   *
   * @returns {Promise}
   */
  initialize() {
    return this._initializePromise;
  }

  /**
   * Adds a new record.
   *
   * @param {object} record
   *        The new record for saving.
   * @param {object} options
   * @param {boolean} [options.sourceSync = false]
   *        Did sync generate this addition?
   * @returns {Promise<string>}
   *          The GUID of the newly added item..
   */
  async add(record, { sourceSync = false } = {}) {
    let recordToSave = this._clone(record);

    if (sourceSync) {
      // Remove tombstones for incoming items that were changed on another
      // device. Local deletions always lose to avoid data loss.
      let index = this._findIndexByGUID(recordToSave.guid, {
        includeDeleted: true,
      });
      if (index > -1) {
        let existing = this._data[index];
        if (existing.deleted) {
          this._data.splice(index, 1);
        } else {
          throw new Error(`Record ${recordToSave.guid} already exists`);
        }
      }
    } else if (!recordToSave.deleted) {
      this._normalizeRecord(recordToSave);
      // _normalizeRecord shouldn't do any validation (throw) because in the
      // `update` case it is called with partial records whereas
      // `_validateFields` is called with a complete one.
      this._validateFields(recordToSave);

      recordToSave.guid = this._generateGUID();
      recordToSave.version = this.version;

      // Metadata
      let now = Date.now();
      recordToSave.timeCreated = now;
      recordToSave.timeLastModified = now;
      recordToSave.timeLastUsed = 0;
      recordToSave.timesUsed = 0;
    }

    return this._saveRecord(recordToSave, { sourceSync });
  }

  async _saveRecord(record, { sourceSync = false } = {}) {
    if (!record.guid) {
      throw new Error("Record missing GUID");
    }

    let recordToSave;
    if (record.deleted) {
      if (this._findByGUID(record.guid, { includeDeleted: true })) {
        throw new Error("a record with this GUID already exists");
      }
      recordToSave = {
        guid: record.guid,
        timeLastModified: record.timeLastModified || Date.now(),
        deleted: true,
      };
    } else {
      this._ensureMatchingVersion(record);
      recordToSave = record;
      await this.computeFields(recordToSave);
    }

    if (sourceSync) {
      let sync = this._getSyncMetaData(recordToSave, true);
      sync.changeCounter = 0;
    }

    this._data.push(recordToSave);

    this.updateUseCountTelemetry();

    this._store.saveSoon();

    Services.obs.notifyObservers(
      {
        wrappedJSObject: {
          sourceSync,
          guid: record.guid,
          collectionName: this._collectionName,
        },
      },
      "formautofill-storage-changed",
      "add"
    );
    return recordToSave.guid;
  }

  _generateGUID() {
    let guid;
    while (!guid || this._findByGUID(guid)) {
      guid = Services.uuid
        .generateUUID()
        .toString()
        .replace(/[{}-]/g, "")
        .substring(0, 12);
    }
    return guid;
  }

  /**
   * Update the specified record.
   *
   * @param  {string} guid
   *         Indicates which record to update.
   * @param  {object} record
   *         The new record used to overwrite the old one.
   * @param  {Promise<boolean>} [preserveOldProperties = false]
   *         Preserve old record's properties if they don't exist in new record.
   */
  async update(guid, record, preserveOldProperties = false) {
    this.log.debug(`update: ${guid}`);

    let recordFoundIndex = this._findIndexByGUID(guid);
    if (recordFoundIndex == -1) {
      throw new Error("No matching record.");
    }

    // Clone the record before modifying it to avoid exposing incomplete changes.
    let recordFound = this._clone(this._data[recordFoundIndex]);
    await this._stripComputedFields(recordFound);

    let recordToUpdate = this._clone(record);
    this._normalizeRecord(recordToUpdate, true);

    let hasValidField = false;
    for (let field of this.VALID_FIELDS) {
      let oldValue = recordFound[field];
      let newValue = recordToUpdate[field];

      // Resume the old field value in the perserve case
      if (preserveOldProperties && newValue === undefined) {
        newValue = oldValue;
      }

      if (newValue === undefined || newValue === "") {
        delete recordFound[field];
      } else {
        hasValidField = true;
        recordFound[field] = newValue;
      }

      this._maybeStoreLastSyncedField(recordFound, field, oldValue);
    }

    if (!hasValidField) {
      throw new Error("Record contains no valid field.");
    }

    // _normalizeRecord above is called with the `record` argument provided to
    // `update` which may not contain all resulting fields when
    // `preserveOldProperties` is used. This means we need to validate for
    // missing fields after we compose the record (`recordFound`) with the stored
    // record like we do in the loop above.
    this._validateFields(recordFound);

    recordFound.timeLastModified = Date.now();
    let syncMetadata = this._getSyncMetaData(recordFound);
    if (syncMetadata) {
      syncMetadata.changeCounter += 1;
    }

    await this.computeFields(recordFound);
    this._data[recordFoundIndex] = recordFound;

    this._store.saveSoon();

    Services.obs.notifyObservers(
      {
        wrappedJSObject: {
          guid,
          collectionName: this._collectionName,
        },
      },
      "formautofill-storage-changed",
      "update"
    );
  }

  /**
   * Notifies the storage of the use of the specified record, so we can update
   * the metadata accordingly. This does not bump the Sync change counter, since
   * we don't sync `timesUsed` or `timeLastUsed`.
   *
   * @param  {string} guid
   *         Indicates which record to be notified.
   */
  notifyUsed(guid) {
    this.log.debug("notifyUsed:", guid);

    let recordFound = this._findByGUID(guid);
    if (!recordFound) {
      throw new Error("No matching record.");
    }

    recordFound.timesUsed++;
    recordFound.timeLastUsed = Date.now();

    this.updateUseCountTelemetry();

    this._store.saveSoon();
    Services.obs.notifyObservers(
      {
        wrappedJSObject: {
          guid,
          collectionName: this._collectionName,
        },
      },
      "formautofill-storage-changed",
      "notifyUsed"
    );
  }

  updateUseCountTelemetry() {
    const telemetryType =
      this._collectionName == "creditCards"
        ? lazy.AutofillTelemetry.CREDIT_CARD
        : lazy.AutofillTelemetry.ADDRESS;
    let records = this._data.filter(r => !r.deleted);
    lazy.AutofillTelemetry.recordNumberOfUse(telemetryType, records);
  }

  /**
   * Removes the specified record. No error occurs if the record isn't found.
   *
   * @param  {string} guid
   *         Indicates which record to remove.
   * @param  {object} options
   * @param  {boolean} [options.sourceSync = false]
   *         Did Sync generate this removal?
   */
  remove(guid, { sourceSync = false } = {}) {
    this.log.debug("remove:", guid);

    if (sourceSync) {
      this._removeSyncedRecord(guid);
    } else {
      let index = this._findIndexByGUID(guid, { includeDeleted: false });
      if (index == -1) {
        this.log.warn("attempting to remove non-existing entry", guid);
        return;
      }
      let existing = this._data[index];
      if (existing.deleted) {
        return; // already a tombstone - don't touch it.
      }
      let existingSync = this._getSyncMetaData(existing);
      if (existingSync) {
        // existing sync metadata means it has been synced. This means we must
        // leave a tombstone behind.
        this._data[index] = {
          guid,
          timeLastModified: Date.now(),
          deleted: true,
          _sync: existingSync,
        };
        existingSync.changeCounter++;
      } else {
        // If there's no sync meta-data, this record has never been synced, so
        // we can delete it.
        this._data.splice(index, 1);
      }
    }

    this.updateUseCountTelemetry();

    this._store.saveSoon();
    Services.obs.notifyObservers(
      {
        wrappedJSObject: {
          sourceSync,
          guid,
          collectionName: this._collectionName,
        },
      },
      "formautofill-storage-changed",
      "remove"
    );
  }

  /**
   * Returns the record with the specified GUID.
   *
   * @param   {string} guid
   *          Indicates which record to retrieve.
   * @param   {object} options
   * @param   {boolean} [options.rawData = false]
   *          Returns a raw record without modifications and the computed fields
   *          (this includes private fields)
   * @returns {Promise<object>}
   *          A clone of the record.
   */
  async get(guid, { rawData = false } = {}) {
    this.log.debug(`get: ${guid}`);

    let recordFound = this._findByGUID(guid);
    if (!recordFound) {
      return null;
    }

    // The record is cloned to avoid accidental modifications from outside.
    let clonedRecord = this._cloneAndCleanUp(recordFound);
    if (rawData) {
      // The *-name fields, previously listed in VALID_FIELDS, have been moved to
      // COMPUTED_FIELDS. By default, the sync payload includes only those fields in VALID_FIELDS.
      // Excluding *-name fields from the sync payload would prevent older devices from
      // synchronizing with newer devices. To maintain backward compatibility, keep those deprecated
      // ields in the payload, ensuring that older devices can still sync with newer devices.
      const fieldsToKeep = AddressRecord.NAME_COMPONENTS;
      await this._stripComputedFields(clonedRecord, fieldsToKeep);
    } else {
      this._recordReadProcessor(clonedRecord);
    }
    return clonedRecord;
  }

  /**
   * Returns all records.
   *
   * @param   {object} options
   * @param   {boolean} [options.rawData = false]
   *          Returns raw records without modifications and the computed fields.
   * @param   {boolean} [options.includeDeleted = false]
   *          Also return any tombstone records.
   * @returns {Promise<Array.<object>>}
   *          An array containing clones of all records.
   */
  async getAll({ rawData = false, includeDeleted = false } = {}) {
    this.log.debug(`getAll. includeDeleted = ${includeDeleted}`);

    let records = this._data.filter(r => !r.deleted || includeDeleted);
    // Records are cloned to avoid accidental modifications from outside.
    let clonedRecords = records.map(r => this._cloneAndCleanUp(r));
    await Promise.all(
      clonedRecords.map(async record => {
        if (rawData) {
          const fieldsToKeep = AddressRecord.NAME_COMPONENTS;
          await this._stripComputedFields(record, fieldsToKeep);
        } else {
          this._recordReadProcessor(record);
        }
      })
    );
    return clonedRecords;
  }

  /**
   * Returns true if the data set is empty. If the `includeDeleted` option is set to true,
   * it will also consider items that are marked as deleted.
   *
   * @param   {object}  [options={}] options
   * @param   {boolean} [options.includeDeleted = false]
   *                    Indicates whether to include deleted items in the check.
   * @returns {boolean} Returns `true` if the data set is empty, otherwise `false`.
   */
  isEmpty({ includeDeleted = false } = {}) {
    return !this._data.find(r => !r.deleted || includeDeleted);
  }

  /**
   * Return all saved field names in the collection.
   *
   * @returns {Promise<Set>} Set containing saved field names.
   */
  async getSavedFieldNames() {
    this.log.debug("getSavedFieldNames");

    let records = this._data.filter(r => !r.deleted);
    records
      .map(record => this._cloneAndCleanUp(record))
      .forEach(record => this._recordReadProcessor(record));

    let fieldNames = new Set();
    for (let record of records) {
      for (let fieldName of Object.keys(record)) {
        if (INTERNAL_FIELDS.includes(fieldName) || !record[fieldName]) {
          continue;
        }
        fieldNames.add(fieldName);
      }
    }

    return fieldNames;
  }

  /**
   * Functions intended to be used in the support of Sync.
   */

  /**
   * Stores a hash of the last synced value for a field in a locally updated
   * record. We use this value to rebuild the shared parent, or base, when
   * reconciling incoming records that may have changed on another device.
   *
   * Storing the hash of the values that we last wrote to the Sync server lets
   * us determine if a remote change conflicts with a local change. If the
   * hashes for the base, current local value, and remote value all differ, we
   * have a conflict.
   *
   * These fields are not themselves synced, and will be removed locally as
   * soon as we have successfully written the record to the Sync server - so
   * it is expected they will not remain for long, as changes which cause a
   * last synced field to be written will itself cause a sync.
   *
   * We also skip this for updates made by Sync, for internal fields, for
   * records that haven't been uploaded yet, and for fields which have already
   * been changed since the last sync.
   *
   * @param   {object} record
   *          The updated local record.
   * @param   {string} field
   *          The field name.
   * @param   {string} lastSyncedValue
   *          The last synced field value.
   */
  _maybeStoreLastSyncedField(record, field, lastSyncedValue) {
    let sync = this._getSyncMetaData(record);
    if (!sync) {
      // The record hasn't been uploaded yet, so we can't end up with merge
      // conflicts.
      return;
    }
    let alreadyChanged = field in sync.lastSyncedFields;
    if (alreadyChanged) {
      // This field was already changed multiple times since the last sync.
      return;
    }
    let newValue = record[field];
    if (lastSyncedValue != newValue) {
      sync.lastSyncedFields[field] = sha512(lastSyncedValue);
    }
  }

  /**
   * Attempts a three-way merge between a changed local record, an incoming
   * remote record, and the shared parent that we synthesize from the last
   * synced fields - see _maybeStoreLastSyncedField.
   *
   * @param   {object} strippedLocalRecord
   *          The changed local record, currently in storage. Computed fields
   *          are stripped.
   * @param   {object} remoteRecord
   *          The remote record.
   * @returns {object | null}
   *          The merged record, or `null` if there are conflicts and the
   *          records can't be merged.
   */
  _mergeSyncedRecords(strippedLocalRecord, remoteRecord) {
    let sync = this._getSyncMetaData(strippedLocalRecord, true);

    // Copy all internal fields from the remote record. We'll update their
    // values in `_replaceRecordAt`.
    let mergedRecord = {};
    for (let field of INTERNAL_FIELDS) {
      if (remoteRecord[field] != null) {
        mergedRecord[field] = remoteRecord[field];
      }
    }

    for (let field of this.VALID_FIELDS) {
      let isLocalSame = false;
      let isRemoteSame = false;
      if (field in sync.lastSyncedFields) {
        // If the field has changed since the last sync, compare hashes to
        // determine if the local and remote values are different. Hashing is
        // expensive, but we don't expect this to happen frequently.
        let lastSyncedValue = sync.lastSyncedFields[field];
        isLocalSame = lastSyncedValue == sha512(strippedLocalRecord[field]);
        isRemoteSame = lastSyncedValue == sha512(remoteRecord[field]);
      } else {
        // Otherwise, if the field hasn't changed since the last sync, we know
        // it's the same locally.
        isLocalSame = true;
        isRemoteSame = strippedLocalRecord[field] == remoteRecord[field];
      }

      let value;
      if (isLocalSame && isRemoteSame) {
        // Local and remote are the same; doesn't matter which one we pick.
        value = strippedLocalRecord[field];
      } else if (isLocalSame && !isRemoteSame) {
        value = remoteRecord[field];
      } else if (!isLocalSame && isRemoteSame) {
        // We don't need to bump the change counter when taking the local
        // change, because the counter must already be > 0 if we're attempting
        // a three-way merge.
        value = strippedLocalRecord[field];
      } else if (strippedLocalRecord[field] == remoteRecord[field]) {
        // Shared parent doesn't match either local or remote, but the values
        // are identical, so there's no conflict.
        value = strippedLocalRecord[field];
      } else {
        // Both local and remote changed to different values. We'll need to fork
        // the local record to resolve the conflict.
        return null;
      }

      if (value != null) {
        mergedRecord[field] = value;
      }
    }

    // When merging records, we shouldn't persist any unknown fields on the local and instead
    // rely on the remote for unknown fields, so we filter the fields we know and keep the rest
    Object.keys(remoteRecord)
      .filter(
        key =>
          !this.VALID_FIELDS.includes(key) && !INTERNAL_FIELDS.includes(key)
      )
      .forEach(key => (mergedRecord[key] = remoteRecord[key]));
    return mergedRecord;
  }

  /**
   * Replaces a local record with a remote or merged record, copying internal
   * fields and Sync metadata.
   *
   * @param   {number} index
   * @param   {object} remoteRecord
   * @param   {object} options
   * @param   {Promise<boolean>} [options.keepSyncMetadata = false]
   *          Should we copy Sync metadata? This is true if `remoteRecord` is a
   *          merged record with local changes that we need to upload. Passing
   *          `keepSyncMetadata` retains the record's change counter and
   *          last synced fields, so that we don't clobber the local change if
   *          the sync is interrupted after the record is merged, but before
   *          it's uploaded.
   */
  async _replaceRecordAt(
    index,
    remoteRecord,
    { keepSyncMetadata = false } = {}
  ) {
    let localRecord = this._data[index];
    let newRecord = this._clone(remoteRecord);

    await this._stripComputedFields(newRecord);

    this._data[index] = newRecord;

    if (keepSyncMetadata) {
      // It's safe to move the Sync metadata from the old record to the new
      // record, since we always clone records when we return them, and we
      // never hand out references to the metadata object via public methods.
      newRecord._sync = localRecord._sync;
    } else {
      // As a side effect, `_getSyncMetaData` marks the record as syncing if the
      // existing `localRecord` is a dupe of `remoteRecord`, and we're replacing
      // local with remote.
      let sync = this._getSyncMetaData(newRecord, true);
      sync.changeCounter = 0;
    }

    if (
      !newRecord.timeCreated ||
      localRecord.timeCreated < newRecord.timeCreated
    ) {
      newRecord.timeCreated = localRecord.timeCreated;
    }

    if (
      !newRecord.timeLastModified ||
      localRecord.timeLastModified > newRecord.timeLastModified
    ) {
      newRecord.timeLastModified = localRecord.timeLastModified;
    }

    // Copy local-only fields from the existing local record.
    for (let field of ["timeLastUsed", "timesUsed"]) {
      if (localRecord[field] != null) {
        newRecord[field] = localRecord[field];
      }
    }

    await this.computeFields(newRecord);
  }

  /**
   * Clones a local record, giving the clone a new GUID and Sync metadata. The
   * original record remains unchanged in storage.
   *
   * @param   {object} strippedLocalRecord
   *          The local record. Computed fields are stripped.
   * @returns {string}
   *          A clone of the local record with a new GUID.
   */
  async _forkLocalRecord(strippedLocalRecord) {
    let forkedLocalRecord = this._cloneAndCleanUp(strippedLocalRecord);
    forkedLocalRecord.guid = this._generateGUID();

    // Give the record fresh Sync metadata and bump its change counter as a
    // side effect. This also excludes the forked record from de-duping on the
    // next sync, if the current sync is interrupted before the record can be
    // uploaded.
    this._getSyncMetaData(forkedLocalRecord, true);

    await this.computeFields(forkedLocalRecord);
    this._data.push(forkedLocalRecord);

    return forkedLocalRecord;
  }

  /**
   * Reconciles an incoming remote record into the matching local record. This
   * method is only used by Sync; other callers should use `merge`.
   *
   * @param   {object} remoteRecord
   *          The incoming record. `remoteRecord` must not be a tombstone, and
   *          must have a matching local record with the same GUID. Use
   *          `add` to insert remote records that don't exist locally, and
   *          `remove` to apply remote tombstones.
   * @returns {Promise<object>}
   *          A `{forkedGUID}` tuple. `forkedGUID` is `null` if the merge
   *          succeeded without conflicts, or a new GUID referencing the
   *          existing locally modified record if the conflicts could not be
   *          resolved.
   */
  async reconcile(remoteRecord) {
    this._ensureMatchingVersion(remoteRecord);
    if (remoteRecord.deleted) {
      throw new Error(`Can't reconcile tombstone ${remoteRecord.guid}`);
    }

    let localIndex = this._findIndexByGUID(remoteRecord.guid);
    if (localIndex < 0) {
      throw new Error(`Record ${remoteRecord.guid} not found`);
    }

    let localRecord = this._data[localIndex];
    let sync = this._getSyncMetaData(localRecord, true);

    let forkedGUID = null;

    // NOTE: This implies a credit-card - so it's critical ADDRESS_SCHEMA_VERSION
    // never equals 4 while this code exists!
    let requiresForceUpdate =
      localRecord.version != remoteRecord.version && remoteRecord.version == 4;

    if (requiresForceUpdate) {
      // Another desktop device that is still using version=4 has created or
      // modified a remote record. Here we downgrade it to version=3 so we can
      // treat it normally, then cause it to be re-uploaded so other desktop
      // or mobile devices can still see it.
      // That device still using version=4 *will* again see it, and again
      // upgrade it, but thankfully that 3->4 migration doesn't force a reupload
      // of all records, or we'd be going back and forward on every sync.
      // Once that version=4 device gets updated to roll back to version=3, it
      // will then yet again re-upload it, this time with version=3, but the
      // content will be the same here, so everything should work out in the end.
      //
      // If we just ignored this incoming record, it would remain on the server
      // with version=4. If the device that wrote that went away (ie, never
      // synced again) nothing would ever repair it back to 3, which would
      // be bad because mobile would remain broken until the user edited the
      // card somewhere.
      remoteRecord = await this._computeMigratedRecord(remoteRecord);
    }
    if (sync.changeCounter === 0) {
      // Local not modified. Replace local with remote.
      await this._replaceRecordAt(localIndex, remoteRecord, {
        keepSyncMetadata: false,
      });
    } else {
      let strippedLocalRecord = this._clone(localRecord);
      await this._stripComputedFields(strippedLocalRecord);

      let mergedRecord = this._mergeSyncedRecords(
        strippedLocalRecord,
        remoteRecord
      );
      if (mergedRecord) {
        // Local and remote modified, but we were able to merge. Replace the
        // local record with the merged record.
        await this._replaceRecordAt(localIndex, mergedRecord, {
          keepSyncMetadata: true,
        });
      } else {
        // Merge conflict. Fork the local record, then replace the original
        // with the merged record.
        let forkedLocalRecord = await this._forkLocalRecord(
          strippedLocalRecord
        );
        forkedGUID = forkedLocalRecord.guid;
        await this._replaceRecordAt(localIndex, remoteRecord, {
          keepSyncMetadata: false,
        });
      }
    }

    if (requiresForceUpdate) {
      // The incoming record was version=4 and we want to re-upload it as version=3.
      // We need to reach directly into self._data[] so we can poke at the
      // sync metadata directly.
      let indexToUpdate = this._findIndexByGUID(remoteRecord.guid);
      let toUpdate = this._data[indexToUpdate];
      this._getSyncMetaData(toUpdate, true).changeCounter += 1;
      this.log.info(
        `Flagging record ${toUpdate.guid} for re-upload after record version downgrade`
      );
    }

    this._store.saveSoon();
    Services.obs.notifyObservers(
      {
        wrappedJSObject: {
          sourceSync: true,
          guid: remoteRecord.guid,
          forkedGUID,
          collectionName: this._collectionName,
        },
      },
      "formautofill-storage-changed",
      "reconcile"
    );

    return { forkedGUID };
  }

  _removeSyncedRecord(guid) {
    let index = this._findIndexByGUID(guid, { includeDeleted: true });
    if (index == -1) {
      // Removing a record we don't know about. It may have been synced and
      // removed by another device before we saw it. Store the tombstone in
      // case the server is later wiped and we need to reupload everything.
      let tombstone = {
        guid,
        timeLastModified: Date.now(),
        deleted: true,
      };

      let sync = this._getSyncMetaData(tombstone, true);
      sync.changeCounter = 0;
      this._data.push(tombstone);
      return;
    }

    let existing = this._data[index];
    let sync = this._getSyncMetaData(existing, true);
    if (sync.changeCounter > 0) {
      // Deleting a record with unsynced local changes. To avoid potential
      // data loss, we ignore the deletion in favor of the changed record.
      this.log.info(
        "Ignoring deletion for record with local changes",
        existing
      );
      return;
    }

    if (existing.deleted) {
      this.log.info("Ignoring deletion for tombstone", existing);
      return;
    }

    // Removing a record that's not changed locally, and that's not already
    // deleted. Replace the record with a synced tombstone.
    this._data[index] = {
      guid,
      timeLastModified: Date.now(),
      deleted: true,
      _sync: sync,
    };
  }

  /**
   * Provide an object that describes the changes to sync.
   *
   * This is called at the start of the sync process to determine what needs
   * to be updated on the server. As the server is updated, sync will update
   * entries in the returned object, and when sync is complete it will pass
   * the object to pushSyncChanges, which will apply the changes to the store.
   *
   * @returns {object}
   *          An object describing the changes to sync.
   */
  pullSyncChanges() {
    let changes = {};

    let profiles = this._data;
    for (let profile of profiles) {
      let sync = this._getSyncMetaData(profile, true);
      if (sync.changeCounter < 1) {
        if (sync.changeCounter != 0) {
          this.log.error("negative change counter", profile);
        }
        continue;
      }
      changes[profile.guid] = {
        profile,
        counter: sync.changeCounter,
        modified: profile.timeLastModified,
        synced: false,
      };
    }
    this._store.saveSoon();

    return changes;
  }

  /**
   * Apply the metadata changes made by Sync.
   *
   * This is called with metadata about what was synced - see pullSyncChanges.
   *
   * @param {object} changes
   *        The possibly modified object obtained via pullSyncChanges.
   */
  pushSyncChanges(changes) {
    for (let [guid, { counter, synced }] of Object.entries(changes)) {
      if (!synced) {
        continue;
      }
      let recordFound = this._findByGUID(guid, { includeDeleted: true });
      if (!recordFound) {
        this.log.warn("No profile found to persist changes for guid " + guid);
        continue;
      }
      let sync = this._getSyncMetaData(recordFound, true);
      sync.changeCounter = Math.max(0, sync.changeCounter - counter);
      if (sync.changeCounter === 0) {
        // Clear the shared parent fields once we've uploaded all pending
        // changes, since the server now matches what we have locally.
        sync.lastSyncedFields = {};
      }
    }
    this._store.saveSoon();
  }

  /**
   * Reset all sync metadata for all items.
   *
   * This is called when Sync is disconnected from this device. All sync
   * metadata for all items is removed.
   */
  resetSync() {
    for (let record of this._data) {
      delete record._sync;
    }
    // XXX - we should probably also delete all tombstones?
    this.log.info("All sync metadata was reset");
  }

  /**
   * Changes the GUID of an item. This should be called only by Sync. There
   * must be an existing record with oldID and it must never have been synced
   * or an error will be thrown. There must be no existing record with newID.
   *
   * No tombstone will be created for the old GUID - we check it hasn't
   * been synced, so no tombstone is necessary.
   *
   * @param   {string} oldID
   *          GUID of the existing item to change the GUID of.
   * @param   {string} newID
   *          The new GUID for the item.
   */
  changeGUID(oldID, newID) {
    this.log.debug("changeGUID: ", oldID, newID);
    if (oldID == newID) {
      throw new Error("changeGUID: old and new IDs are the same");
    }
    if (this._findIndexByGUID(newID) >= 0) {
      throw new Error("changeGUID: record with destination id exists already");
    }

    let index = this._findIndexByGUID(oldID);
    let profile = this._data[index];
    if (!profile) {
      throw new Error("changeGUID: no source record");
    }
    if (this._getSyncMetaData(profile)) {
      throw new Error("changeGUID: existing record has already been synced");
    }

    profile.guid = newID;

    this._store.saveSoon();
  }

  // Used to get, and optionally create, sync metadata. Brand new records will
  // *not* have sync meta-data - it will be created when they are first
  // synced.
  _getSyncMetaData(record, forceCreate = false) {
    if (!record._sync && forceCreate) {
      // create default metadata and indicate we need to save.
      record._sync = {
        changeCounter: 1,
        lastSyncedFields: {},
      };
      this._store.saveSoon();
    }
    return record._sync;
  }

  /**
   * Finds a local record with matching common fields and a different GUID.
   * Sync uses this method to find and update unsynced local records with
   * fields that match incoming remote records. This avoids creating
   * duplicate profiles with the same information.
   *
   * @param   {object} remoteRecord
   *          The remote record.
   * @returns {Promise<string|null>}
   *          The GUID of the matching local record, or `null` if no records
   *          match.
   */
  async findDuplicateGUID(remoteRecord) {
    if (!remoteRecord.guid) {
      throw new Error("Record missing GUID");
    }
    this._ensureMatchingVersion(remoteRecord);
    if (remoteRecord.deleted) {
      // Tombstones don't carry enough info to de-dupe, and we should have
      // handled them separately when applying the record.
      throw new Error("Tombstones can't have duplicates");
    }
    let localRecords = this._data;
    for (let localRecord of localRecords) {
      if (localRecord.deleted) {
        continue;
      }
      if (localRecord.guid == remoteRecord.guid) {
        throw new Error(`Record ${remoteRecord.guid} already exists`);
      }
      if (this._getSyncMetaData(localRecord)) {
        // This local record has already been uploaded, so it can't be a dupe of
        // another incoming item.
        continue;
      }

      // Ignore computed fields when matching records as they aren't synced at all.
      let strippedLocalRecord = this._clone(localRecord);
      await this._stripComputedFields(strippedLocalRecord);

      let keys = new Set(Object.keys(remoteRecord));
      for (let key of Object.keys(strippedLocalRecord)) {
        keys.add(key);
      }
      // Ignore internal fields when matching records. Internal fields are synced,
      // but almost certainly have different values than the local record, and
      // we'll update them in `reconcile`.
      for (let field of INTERNAL_FIELDS) {
        keys.delete(field);
      }
      if (!keys.size) {
        // This shouldn't ever happen; a valid record will always have fields
        // that aren't computed or internal. Sync can't do anything about that,
        // so we ignore the dubious local record instead of throwing.
        continue;
      }
      let same = true;
      for (let key of keys) {
        // For now, we ensure that both (or neither) records have the field
        // with matching values. This doesn't account for the version yet
        // (bug 1377204).
        same =
          key in strippedLocalRecord == key in remoteRecord &&
          strippedLocalRecord[key] == remoteRecord[key];
        if (!same) {
          break;
        }
      }
      if (same) {
        return strippedLocalRecord.guid;
      }
    }
    return null;
  }

  /**
   * Internal helper functions.
   */

  _clone(record) {
    return Object.assign({}, record);
  }

  _cloneAndCleanUp(record) {
    let result = {};
    for (let key in record) {
      // Do not expose hidden fields and fields with empty value (mainly used
      // as placeholders of the computed fields).
      if (!key.startsWith("_") && record[key] !== "") {
        result[key] = record[key];
      }
    }
    return result;
  }

  _findByGUID(guid, { includeDeleted = false } = {}) {
    let found = this._findIndexByGUID(guid, { includeDeleted });
    return found < 0 ? undefined : this._data[found];
  }

  _findIndexByGUID(guid, { includeDeleted = false } = {}) {
    return this._data.findIndex(record => {
      return record.guid == guid && (!record.deleted || includeDeleted);
    });
  }

  async _migrateRecord(record, index) {
    let hasChanges = false;

    if (record.deleted) {
      return hasChanges;
    }

    if (!record.version || isNaN(record.version) || record.version < 1) {
      this.log.warn("Invalid record version:", record.version);

      // Force to run the migration.
      record.version = 0;
    }

    if (this._isMigrationNeeded(record)) {
      hasChanges = true;

      record = await this._computeMigratedRecord(record);

      if (record.deleted) {
        // record is deleted by _computeMigratedRecord(),
        // go ahead and put it in the store.
        this._data[index] = record;
        return hasChanges;
      }

      // Compute the computed fields before putting it to store.
      await this.computeFields(record);
      this._data[index] = record;

      return hasChanges;
    }

    const originalNumFields = Object.keys(record).length;
    await this.computeFields(record);
    const hasNewComputedFields =
      Object.keys(record).length != originalNumFields;

    hasChanges |= hasNewComputedFields;
    return hasChanges;
  }

  _normalizeRecord(record, preserveEmptyFields = false) {
    this._normalizeFields(record);

    for (const key in record) {
      if (!this.VALID_FIELDS.includes(key)) {
        // Though we allow unknown fields, certain fields are still protected
        // from being changed
        if (INTERNAL_FIELDS.includes(key)) {
          throw new Error(`"${key}" is not a valid field.`);
        } else {
          // We shouldn't try to normalize unknown fields. We'll just roundtrip them
          this.log.warn(`${key} is not a known field. Skipping normalization.`);
          continue;
        }
      }
      if (typeof record[key] !== "string" && typeof record[key] !== "number") {
        throw new Error(
          `"${key}" contains invalid data type: ${typeof record[key]}`
        );
      }
      if (!preserveEmptyFields && record[key] === "") {
        delete record[key];
      }
    }

    const keys = Object.keys(record);
    // By default we ensure there is always a country field, so if this record
    // doesn't contain other fields, this is an empty record
    if (!keys.length || (keys.length == 1 && keys[0] == "country")) {
      throw new Error("Record contains no valid field.");
    }
  }

  /**
   * Unconditionally remove all data and tombstones for this collection.
   */
  removeAll({ sourceSync = false } = {}) {
    this._store.data[this._collectionName] = [];
    this._store.saveSoon();
    Services.obs.notifyObservers(
      {
        wrappedJSObject: {
          sourceSync,
          collectionName: this._collectionName,
        },
      },
      "formautofill-storage-changed",
      "removeAll"
    );
  }

  _isMigrationNeeded(record) {
    return record.version < this.version;
  }

  /**
   * Strip the computed fields based on the record version.
   *
   * @param   {object} record      The record to migrate
   * @returns {object}             Migrated record.
   *                               Record is always cloned, with version updated,
   *                               with computed fields stripped.
   *                               Could be a tombstone record, if the record
   *                               should be discorded.
   */
  async _computeMigratedRecord(record) {
    if (!record.deleted) {
      record = this._clone(record);
      await this._stripComputedFields(record);
      record.version = this.version;
    }
    return record;
  }

  async _stripComputedFields(record, fieldsToKeep = []) {
    for (const field of this.VALID_COMPUTED_FIELDS) {
      if (fieldsToKeep.includes(field)) {
        continue;
      }
      delete record[field];
    }
  }

  // An interface to be inherited.
  _recordReadProcessor(_record) {}

  // An interface to be inherited.
  async computeFields(_record) {}

  /**
   * An interface to be inherited to mutate the argument to normalize it.
   *
   * @param {object} _partialRecord containing the record passed by the consumer of
   *                               storage and in the case of `update` with
   *                               `preserveOldProperties` will only include the
   *                               properties that the user is changing so the
   *                               lack of a field doesn't mean that the record
   *                               won't have that field.
   */
  _normalizeFields(_partialRecord) {}

  /**
   * An interface to be inherited to validate that the complete record is
   * consistent and isn't missing required fields. Overrides should throw for
   * invalid records.
   *
   * @param {object} _record containing the complete record that would be stored
   *                        if this doesn't throw due to an error.
   * @throws
   */
  _validateFields(_record) {}

  // An interface to be inherited.
  migrateRemoteRecord(_remoteRecord) {}
}

export class AddressesBase extends AutofillRecords {
  constructor(store) {
    super(
      store,
      "addresses",
      VALID_ADDRESS_FIELDS,
      VALID_ADDRESS_COMPUTED_FIELDS,
      ADDRESS_SCHEMA_VERSION
    );
  }

  _recordReadProcessor(address) {
    if (address.country && !FormAutofill.countries.has(address.country)) {
      delete address.country;
      delete address["country-name"];
    }
  }

  _isMigrationNeeded(record) {
    if (super._isMigrationNeeded(record)) {
      return true;
    }

    if (
      !record.name &&
      (record["given-name"] ||
        record["additional-name"] ||
        record["family-name"])
    ) {
      return true;
    }
    return false;
  }

  async _computeMigratedRecord(address) {
    // Bug 1836438 - `name` field was moved from computed fields to valid fields.
    if (
      !address.name &&
      (address["given-name"] ||
        address["additional-name"] ||
        address["family-name"])
    ) {
      address.name = lazy.FormAutofillNameUtils.joinNameParts({
        given: address["given-name"] ?? "",
        middle: address["additional-name"] ?? "",
        family: address["family-name"] ?? "",
      });
    }

    return super._computeMigratedRecord(address);
  }

  async computeFields(address) {
    // NOTE: Remember to bump the schema version number if any of the existing
    //       computing algorithm changes. (No need to bump when just adding new
    //       computed fields.)

    // NOTE: Computed fields should be always present in the storage no matter
    //       it's empty or not.

    if (!address.deleted) {
      AddressRecord.computeFields(address);
    }
  }

  _normalizeFields(address) {
    this._normalizeCountryFields(address);
    this._normalizeNameFields(address);
    this._normalizeAddressFields(address);
    this._normalizeTelFields(address);
  }

  _normalizeNameFields(address) {
    if (
      !address.name &&
      (address["given-name"] ||
        address["additional-name"] ||
        address["family-name"])
    ) {
      address.name = lazy.FormAutofillNameUtils.joinNameParts({
        given: address["given-name"] ?? "",
        middle: address["additional-name"] ?? "",
        family: address["family-name"] ?? "",
      });
    }

    delete address["given-name"];
    delete address["additional-name"];
    delete address["family-name"];
  }

  _normalizeAddressFields(address) {
    if (AddressRecord.STREET_ADDRESS_COMPONENTS.some(c => !!address[c])) {
      // Treat "street-address" as "address-line1" if it contains only one line
      // and "address-line1" is omitted.
      if (
        !address["address-line1"] &&
        address["street-address"] &&
        !address["street-address"].includes("\n")
      ) {
        address["address-line1"] = address["street-address"];
        delete address["street-address"];
      }

      // Concatenate "address-line*" if "street-address" is omitted.
      if (!address["street-address"]) {
        address["street-address"] = AddressRecord.STREET_ADDRESS_COMPONENTS.map(
          c => address[c]
        )
          .join("\n")
          .replace(/\n+$/, "");
      }
    }
    AddressRecord.STREET_ADDRESS_COMPONENTS.forEach(c => delete address[c]);
  }

  _normalizeCountryFields(address) {
    // When we can't identify the country code, it is possible because that the region exists
    // in regionNames.properties but not in libaddressinput.
    const country =
      lazy.FormAutofillUtils.identifyCountryCode(
        address.country || address["country-name"]
      ) || address.country;

    // Only values included in the region list will be saved.
    let hasLocalizedName = false;
    try {
      if (country) {
        let localizedName = Services.intl.getRegionDisplayNames(undefined, [
          country,
        ]);
        hasLocalizedName = localizedName != country;
      }
    } catch (e) {}

    if (country && hasLocalizedName) {
      address.country = country;
    } else {
      address.country = FormAutofill.DEFAULT_REGION;
    }

    delete address["country-name"];
  }

  _normalizeTelFields(address) {
    if (address.tel || AddressRecord.TEL_COMPONENTS.some(c => !!address[c])) {
      lazy.FormAutofillUtils.compressTel(address);

      let possibleRegion = address.country || FormAutofill.DEFAULT_REGION;
      let tel = lazy.PhoneNumber.Parse(address.tel, possibleRegion);

      if (tel && tel.internationalNumber) {
        // Force to save numbers in E.164 format if parse success.
        address.tel = tel.internationalNumber;
      }
    }
    AddressRecord.TEL_COMPONENTS.forEach(c => delete address[c]);
  }

  /**
   * Migrate the remote record to the expected format.
   *
   * @param {object} remoteRecord The remote record.
   */
  migrateRemoteRecord(remoteRecord) {
    // When a remote record lacks the `name` field but includes any `*-name` fields, we can
    // assume that the record originates from an older device. This is because even if an older
    // device pulls the `name` field from a newer record from the sync server, the `name` field,
    // being a computed field for an older device, will always be stripped.

    // If the remote record comes from an older device, we compare the `*-name` fields in the
    // remote record with those in the corresponding local record. If the values of the `*-name`
    // fields differ, it indicates that the remote record has updated these fields. If the
    // values are the same, we replace the name field of the remote record with the local
    // name field to ensure the completeness of the name field when reconciling.
    //
    // Here is an example:
    // Assume the local record is {"name": "Mr. John Doe"}. If an updated remote record
    // has {"given-name": "John", "family-name": "Doe"}, we will NOT join the `*-name` fields
    // and replace the local `name` field with "John Doe". This allows us to retain the complete
    // name - "Mr. John Doe".
    // However, if the updated remote record has {"given-name": "Jane", "family-name": "Poe"},
    // we will rebuild it and replace the local `name` field with "Jane Poe".
    if (
      !("name" in remoteRecord) &&
      AddressRecord.NAME_COMPONENTS.some(c => c in remoteRecord)
    ) {
      const localRecord = this._findByGUID(remoteRecord.guid);
      if (
        localRecord &&
        AddressRecord.NAME_COMPONENTS.every(
          c => remoteRecord[c] == localRecord[c]
        )
      ) {
        remoteRecord.name = localRecord.name;
      } else {
        remoteRecord.name = lazy.FormAutofillNameUtils.joinNameParts({
          given: remoteRecord["given-name"],
          middle: remoteRecord["additional-name"],
          family: remoteRecord["family-name"],
        });
      }
    }

    // To enable new devices to sync name field changes with older devices, we still
    // include the computed *-name fields in the sync payload while uploading.
    // This also means that the incoming remote record will also contain *-name fields.
    // However, since the autofill storage does not expect remote records to contain
    // computed fields while merging, we remove them from the remote record.
    AddressRecord.NAME_COMPONENTS.forEach(f => delete remoteRecord[f]);
  }
}

export class CreditCardsBase extends AutofillRecords {
  constructor(store) {
    super(
      store,
      "creditCards",
      VALID_CREDIT_CARD_FIELDS,
      VALID_CREDIT_CARD_COMPUTED_FIELDS,
      CREDIT_CARD_SCHEMA_VERSION
    );
  }

  async computeFields(creditCard) {
    // NOTE: Remember to bump the schema version number if any of the existing
    //       computing algorithm changes. (No need to bump when just adding new
    //       computed fields.)

    // NOTE: Computed fields should be always present in the storage no matter
    //       it's empty or not.

    let hasNewComputedFields = false;

    if (creditCard.deleted) {
      return hasNewComputedFields;
    }

    let type = lazy.CreditCard.getType(creditCard["cc-number"]);
    if (type) {
      creditCard["cc-type"] = type;
    }

    // Compute split names
    if (!("cc-given-name" in creditCard)) {
      const nameParts = lazy.FormAutofillNameUtils.splitName(
        creditCard["cc-name"]
      );
      creditCard["cc-given-name"] = nameParts.given;
      creditCard["cc-additional-name"] = nameParts.middle;
      creditCard["cc-family-name"] = nameParts.family;
      hasNewComputedFields = true;
    }

    // Compute credit card expiration date
    if (!("cc-exp" in creditCard)) {
      if (creditCard["cc-exp-month"] && creditCard["cc-exp-year"]) {
        creditCard["cc-exp"] =
          String(creditCard["cc-exp-year"]) +
          "-" +
          String(creditCard["cc-exp-month"]).padStart(2, "0");
      } else {
        creditCard["cc-exp"] = "";
      }
      hasNewComputedFields = true;
    }

    // Encrypt credit card number
    await this._encryptNumber(creditCard);

    return hasNewComputedFields;
  }

  async _encryptNumber(_creditCard) {
    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
  }

  _isMigrationNeeded(record) {
    return (
      // version 4 is deprecated and is rolled back to version 3
      record.version == 4 || record.version < this.version
    );
  }

  async _computeMigratedRecord(creditCard) {
    if (creditCard.version <= 2) {
      if (creditCard["cc-number-encrypted"]) {
        // We cannot decrypt the data, so silently remove the record for
        // the user.
        if (!creditCard.deleted) {
          this.log.warn(
            "Removing version",
            creditCard.version,
            "credit card record to migrate to new encryption:",
            creditCard.guid
          );

          // Replace the record with a tombstone record here,
          // regardless of existence of sync metadata.
          let existingSync = this._getSyncMetaData(creditCard);
          creditCard = {
            guid: creditCard.guid,
            timeLastModified: Date.now(),
            deleted: true,
          };

          if (existingSync) {
            creditCard._sync = existingSync;
            existingSync.changeCounter++;
          }
        }
      }
    }

    // Do not remove the migration code until we're sure no users have version 4
    // credit card records (created in Fx110 or Fx111)
    if (creditCard.version == 4) {
      // Version 4 is deprecated, so downgrade or upgrade to the current version
      // Since the only change made in version 4 is deleting `cc-type` field, so
      // nothing else need to be done here expect flagging sync needed
      let existingSync = this._getSyncMetaData(creditCard);
      if (existingSync) {
        existingSync.changeCounter++;
      }
    }

    return super._computeMigratedRecord(creditCard);
  }

  async _stripComputedFields(creditCard) {
    if (creditCard["cc-number-encrypted"]) {
      try {
        creditCard["cc-number"] = await lazy.OSKeyStore.decrypt(
          creditCard["cc-number-encrypted"]
        );
      } catch (ex) {
        if (ex.result == Cr.NS_ERROR_ABORT) {
          throw ex;
        }
        // Quietly recover from encryption error,
        // so existing credit card entry with undecryptable number
        // can be updated.
      }
    }
    await super._stripComputedFields(creditCard);
  }

  _normalizeFields(creditCard) {
    lazy.CreditCardRecord.normalizeFields(creditCard);
  }

  _validateFields(creditCard) {
    if (!creditCard["cc-number"]) {
      throw new Error("Missing/invalid cc-number");
    }
  }

  _ensureMatchingVersion(record) {
    if (!record.version || isNaN(record.version) || record.version < 1) {
      throw new Error(
        `Got invalid record version ${record.version}; want ${this.version}`
      );
    }

    if (record.version == 4) {
      // Version 4 is deprecated, we need to force downloading it from sync
      // and let migration do the work to downgrade it back to the current version.
      return true;
    } else if (record.version < this.version) {
      switch (record.version) {
        case 1:
        case 2:
          // The difference between version 1 and 2 is only about the encryption
          // method used for the cc-number-encrypted field.
          // The difference between version 2 and 3 is the name of the OS
          // key encryption record.
          // As long as the record is already decrypted, it is safe to bump the
          // version directly.
          if (!record["cc-number-encrypted"]) {
            record.version = this.version;
          } else {
            throw new Error(
              "Could not migrate record version:",
              record.version,
              "->",
              this.version
            );
          }
          break;
        default:
          throw new Error(
            "Unknown credit card version to match: " + record.version
          );
      }
    }

    return super._ensureMatchingVersion(record);
  }

  /**
   * Find a match credit card record in storage that is either exactly the same
   * as the given record or a superset of the given record.
   *
   * See the comments in `getDuplicateRecords` to see the difference between
   * `getDuplicateRecords` and `getMatchRecords`
   *
   * @param {object} record
   *        The credit card for match checking. please make sure the
   *        record is normalized.
   * @returns {object}
   *          Return the first matched record found in storage, null otherwise.
   */
  async *getMatchRecords(record) {
    for await (const recordInStorage of this.getDuplicateRecords(record)) {
      const fields = this.VALID_FIELDS.filter(f => f != "cc-number");
      if (
        fields.every(
          field => !record[field] || record[field] == recordInStorage[field]
        )
      ) {
        yield recordInStorage;
      }
    }
    return null;
  }

  /**
   * Find a duplicate credit card record in the storage.
   *
   * A record is considered as a duplicate of another record when two records
   * are the "same". This might be true even when some of their fields are
   * different. For example, one record has the same credit card number but has
   * different expiration date as the other record are still considered as
   * "duplicate".
   * This is different from `getMatchRecords`, which ensures all the fields with
   * value in the the record is equal to the returned record.
   *
   * @param {object} record
   *        The credit card for duplication checking. please make sure the
   *        record is normalized.
   * @returns {object}
   *          Return the first duplicated record found in storage, null otherwise.
   */
  async *getDuplicateRecords(record) {
    if (!record["cc-number"]) {
      return null;
    }

    for (const recordInStorage of this._data) {
      if (recordInStorage.deleted) {
        continue;
      }

      const decrypted = await lazy.OSKeyStore.decrypt(
        recordInStorage["cc-number-encrypted"],
        false
      );

      if (decrypted == record["cc-number"]) {
        yield recordInStorage;
      }
    }
    return null;
  }
}

export class FormAutofillStorageBase {
  constructor(path) {
    this._path = path;
    this._initializePromise = null;
    this.INTERNAL_FIELDS = INTERNAL_FIELDS;
  }

  get version() {
    return STORAGE_SCHEMA_VERSION;
  }

  get addresses() {
    return this.getAddresses();
  }

  get creditCards() {
    return this.getCreditCards();
  }

  getAddresses() {
    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
  }

  getCreditCards() {
    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
  }

  /**
   * Initialize storage to memory.
   *
   * @returns {Promise} When the operation finished successfully.
   * @throws  JavaScript exception.
   */
  initialize() {
    if (!this._initializePromise) {
      this._store = this._initializeStore();
      this._initializePromise = this._store.load().then(() => {
        let initializeAutofillRecords = [
          this.addresses.initialize(),
          this.creditCards.initialize(),
        ];
        return Promise.all(initializeAutofillRecords);
      });
    }
    return this._initializePromise;
  }

  _initializeStore() {
    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
  }

  // For test only.
  _saveImmediately() {
    return this._store._save();
  }

  _finalize() {
    return this._store.finalize();
  }
}
PK
!<���B��$modules/shared/AddressRecord.sys.mjs/* eslint-disable no-useless-concat */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { FormAutofillNameUtils } from "resource://gre/modules/shared/FormAutofillNameUtils.sys.mjs";
import { FormAutofillUtils } from "resource://gre/modules/shared/FormAutofillUtils.sys.mjs";
import { PhoneNumber } from "resource://gre/modules/shared/PhoneNumber.sys.mjs";
import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs";

/**
 * The AddressRecord class serves to handle and normalize internal address records.
 * AddressRecord is used for processing and consistent data representation.
 */
export class AddressRecord {
  static NAME_COMPONENTS = ["given-name", "additional-name", "family-name"];

  static STREET_ADDRESS_COMPONENTS = [
    "address-line1",
    "address-line2",
    "address-line3",
  ];
  static TEL_COMPONENTS = [
    "tel-country-code",
    "tel-national",
    "tel-area-code",
    "tel-local",
    "tel-local-prefix",
    "tel-local-suffix",
  ];

  static computeFields(address) {
    this.#computeNameFields(address);
    this.#computeAddressLineFields(address);
    this.#computeCountryFields(address);
    this.#computeTelFields(address);
  }

  static #computeNameFields(address) {
    // Compute split names
    if (!("given-name" in address)) {
      const nameParts = FormAutofillNameUtils.splitName(address.name);
      address["given-name"] = nameParts.given;
      address["additional-name"] = nameParts.middle;
      address["family-name"] = nameParts.family;
    }
  }

  static #computeAddressLineFields(address) {
    // Compute address lines
    if (!("address-line1" in address)) {
      let streetAddress = [];
      if (address["street-address"]) {
        streetAddress = address["street-address"]
          .split("\n")
          .map(s => s.trim());
      }
      for (let i = 0; i < 3; i++) {
        address[`address-line${i + 1}`] = streetAddress[i] || "";
      }
      if (streetAddress.length > 3) {
        address["address-line3"] = FormAutofillUtils.toOneLineAddress(
          streetAddress.slice(2)
        );
      }
    }
  }

  static #computeCountryFields(address) {
    // Compute country name
    if (!("country-name" in address)) {
      address["country-name"] =
        FormAutofill.countries.get(address.country) ?? "";
    }
  }

  static #computeTelFields(address) {
    // Compute tel
    if (!("tel-national" in address)) {
      if (address.tel) {
        let tel = PhoneNumber.Parse(
          address.tel,
          address.country || FormAutofill.DEFAULT_REGION
        );
        if (tel) {
          if (tel.countryCode) {
            address["tel-country-code"] = tel.countryCode;
          }
          if (tel.nationalNumber) {
            address["tel-national"] = tel.nationalNumber;
          }

          // PhoneNumberUtils doesn't support parsing the components of a telephone
          // number so we hard coded the parser for US numbers only. We will need
          // to figure out how to parse numbers from other regions when we support
          // new countries in the future.
          if (tel.nationalNumber && tel.countryCode == "+1") {
            let telComponents = tel.nationalNumber.match(
              /(\d{3})((\d{3})(\d{4}))$/
            );
            if (telComponents) {
              address["tel-area-code"] = telComponents[1];
              address["tel-local"] = telComponents[2];
              address["tel-local-prefix"] = telComponents[3];
              address["tel-local-suffix"] = telComponents[4];
            }
          }
        } else {
          // Treat "tel" as "tel-national" directly if it can't be parsed.
          address["tel-national"] = address.tel;
        }
      }

      this.TEL_COMPONENTS.forEach(c => {
        address[c] = address[c] || "";
      });
    }
  }
}
PK
!</u��1*1*,modules/shared/FormAutofillNameUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// FormAutofillNameUtils is initially translated from
// https://cs.chromium.org/chromium/src/components/autofill/core/browser/autofill_data_util.cc?rcl=b861deff77abecff11ae6a9f6946e9cc844b9817
export var FormAutofillNameUtils = {
  NAME_PREFIXES: [
    "1lt",
    "1st",
    "2lt",
    "2nd",
    "3rd",
    "admiral",
    "capt",
    "captain",
    "col",
    "cpt",
    "dr",
    "gen",
    "general",
    "lcdr",
    "lt",
    "ltc",
    "ltg",
    "ltjg",
    "maj",
    "major",
    "mg",
    "mr",
    "mrs",
    "ms",
    "pastor",
    "prof",
    "rep",
    "reverend",
    "rev",
    "sen",
    "st",
  ],

  NAME_SUFFIXES: [
    "b.a",
    "ba",
    "d.d.s",
    "dds",
    "i",
    "ii",
    "iii",
    "iv",
    "ix",
    "jr",
    "m.a",
    "m.d",
    "ma",
    "md",
    "ms",
    "ph.d",
    "phd",
    "sr",
    "v",
    "vi",
    "vii",
    "viii",
    "x",
  ],

  FAMILY_NAME_PREFIXES: [
    "d'",
    "de",
    "del",
    "der",
    "di",
    "la",
    "le",
    "mc",
    "san",
    "st",
    "ter",
    "van",
    "von",
  ],

  // The common and non-ambiguous CJK surnames (last names) that have more than
  // one character.
  COMMON_CJK_MULTI_CHAR_SURNAMES: [
    // Korean, taken from the list of surnames:
    // https://ko.wikipedia.org/wiki/%ED%95%9C%EA%B5%AD%EC%9D%98_%EC%84%B1%EC%94%A8_%EB%AA%A9%EB%A1%9D
    "남궁",
    "사공",
    "서문",
    "선우",
    "제갈",
    "황보",
    "독고",
    "망절",

    // Chinese, taken from the top 10 Chinese 2-character surnames:
    // https://zh.wikipedia.org/wiki/%E8%A4%87%E5%A7%93#.E5.B8.B8.E8.A6.8B.E7.9A.84.E8.A4.87.E5.A7.93
    // Simplified Chinese (mostly mainland China)
    "欧阳",
    "令狐",
    "皇甫",
    "上官",
    "司徒",
    "诸葛",
    "司马",
    "宇文",
    "呼延",
    "端木",
    // Traditional Chinese (mostly Taiwan)
    "張簡",
    "歐陽",
    "諸葛",
    "申屠",
    "尉遲",
    "司馬",
    "軒轅",
    "夏侯",
  ],

  // All Korean surnames that have more than one character, even the
  // rare/ambiguous ones.
  KOREAN_MULTI_CHAR_SURNAMES: [
    "강전",
    "남궁",
    "독고",
    "동방",
    "망절",
    "사공",
    "서문",
    "선우",
    "소봉",
    "어금",
    "장곡",
    "제갈",
    "황목",
    "황보",
  ],

  // The whitespace definition based on
  // https://cs.chromium.org/chromium/src/base/strings/string_util_constants.cc?l=9&rcl=b861deff77abecff11ae6a9f6946e9cc844b9817
  WHITESPACE: [
    "\u0009", // CHARACTER TABULATION
    "\u000A", // LINE FEED (LF)
    "\u000B", // LINE TABULATION
    "\u000C", // FORM FEED (FF)
    "\u000D", // CARRIAGE RETURN (CR)
    "\u0020", // SPACE
    "\u0085", // NEXT LINE (NEL)
    "\u00A0", // NO-BREAK SPACE
    "\u1680", // OGHAM SPACE MARK
    "\u2000", // EN QUAD
    "\u2001", // EM QUAD
    "\u2002", // EN SPACE
    "\u2003", // EM SPACE
    "\u2004", // THREE-PER-EM SPACE
    "\u2005", // FOUR-PER-EM SPACE
    "\u2006", // SIX-PER-EM SPACE
    "\u2007", // FIGURE SPACE
    "\u2008", // PUNCTUATION SPACE
    "\u2009", // THIN SPACE
    "\u200A", // HAIR SPACE
    "\u2028", // LINE SEPARATOR
    "\u2029", // PARAGRAPH SEPARATOR
    "\u202F", // NARROW NO-BREAK SPACE
    "\u205F", // MEDIUM MATHEMATICAL SPACE
    "\u3000", // IDEOGRAPHIC SPACE
  ],

  // The middle dot is used as a separator for foreign names in Japanese.
  MIDDLE_DOT: [
    "\u30FB", // KATAKANA MIDDLE DOT
    "\u00B7", // A (common?) typo for "KATAKANA MIDDLE DOT"
  ],

  // The Unicode range is based on Wiki:
  // https://en.wikipedia.org/wiki/CJK_Unified_Ideographs
  // https://en.wikipedia.org/wiki/Hangul
  // https://en.wikipedia.org/wiki/Japanese_writing_system
  CJK_RANGE: [
    "\u1100-\u11FF", // Hangul Jamo
    "\u3040-\u309F", // Hiragana
    "\u30A0-\u30FF", // Katakana
    "\u3105-\u312C", // Bopomofo
    "\u3130-\u318F", // Hangul Compatibility Jamo
    "\u31F0-\u31FF", // Katakana Phonetic Extensions
    "\u3200-\u32FF", // Enclosed CJK Letters and Months
    "\u3400-\u4DBF", // CJK unified ideographs Extension A
    "\u4E00-\u9FFF", // CJK Unified Ideographs
    "\uA960-\uA97F", // Hangul Jamo Extended-A
    "\uAC00-\uD7AF", // Hangul Syllables
    "\uD7B0-\uD7FF", // Hangul Jamo Extended-B
    "\uFF00-\uFFEF", // Halfwidth and Fullwidth Forms
  ],

  HANGUL_RANGE: [
    "\u1100-\u11FF", // Hangul Jamo
    "\u3130-\u318F", // Hangul Compatibility Jamo
    "\uA960-\uA97F", // Hangul Jamo Extended-A
    "\uAC00-\uD7AF", // Hangul Syllables
    "\uD7B0-\uD7FF", // Hangul Jamo Extended-B
  ],

  _dataLoaded: false,

  // Returns true if |set| contains |token|, modulo a final period.
  _containsString(set, token) {
    let target = token.replace(/\.$/, "").toLowerCase();
    return set.includes(target);
  },

  // Removes common name prefixes from |name_tokens|.
  _stripPrefixes(nameTokens) {
    for (let i in nameTokens) {
      if (!this._containsString(this.NAME_PREFIXES, nameTokens[i])) {
        return nameTokens.slice(i);
      }
    }
    return [];
  },

  // Removes common name suffixes from |name_tokens|.
  _stripSuffixes(nameTokens) {
    for (let i = nameTokens.length - 1; i >= 0; i--) {
      if (!this._containsString(this.NAME_SUFFIXES, nameTokens[i])) {
        return nameTokens.slice(0, i + 1);
      }
    }
    return [];
  },

  _isCJKName(name) {
    // The name is considered to be a CJK name if it is only CJK characters,
    // spaces, and "middle dot" separators, with at least one CJK character, and
    // no more than 2 words.
    //
    // Chinese and Japanese names are usually spelled out using the Han
    // characters (logographs), which constitute the "CJK Unified Ideographs"
    // block in Unicode, also referred to as Unihan. Korean names are usually
    // spelled out in the Korean alphabet (Hangul), although they do have a Han
    // equivalent as well.

    if (!name) {
      return false;
    }

    let previousWasCJK = false;
    let wordCount = 0;

    for (let c of name) {
      let isMiddleDot = this.MIDDLE_DOT.includes(c);
      let isCJK = !isMiddleDot && this.reCJK.test(c);
      if (!isCJK && !isMiddleDot && !this.WHITESPACE.includes(c)) {
        return false;
      }
      if (isCJK && !previousWasCJK) {
        wordCount++;
      }
      previousWasCJK = isCJK;
    }

    return wordCount > 0 && wordCount < 3;
  },

  // Tries to split a Chinese, Japanese, or Korean name into its given name &
  // surname parts. If splitting did not work for whatever reason, returns null.
  _splitCJKName(nameTokens) {
    // The convention for CJK languages is to put the surname (last name) first,
    // and the given name (first name) second. In a continuous text, there is
    // normally no space between the two parts of the name. When entering their
    // name into a field, though, some people add a space to disambiguate. CJK
    // names (almost) never have a middle name.

    let reHangulName = new RegExp(
      "^[" + this.HANGUL_RANGE.join("") + this.WHITESPACE.join("") + "]+$",
      "u"
    );
    let nameParts = {
      given: "",
      middle: "",
      family: "",
    };

    if (nameTokens.length == 1) {
      // There is no space between the surname and given name. Try to infer
      // where to separate between the two. Most Chinese and Korean surnames
      // have only one character, but there are a few that have 2. If the name
      // does not start with a surname from a known list, default to one
      // character.
      let name = nameTokens[0];
      let isKorean = reHangulName.test(name);
      let surnameLength = 0;

      // 4-character Korean names are more likely to be 2/2 than 1/3, so use
      // the full list of Korean 2-char surnames. (instead of only the common
      // ones)
      let multiCharSurnames =
        isKorean && name.length > 3
          ? this.KOREAN_MULTI_CHAR_SURNAMES
          : this.COMMON_CJK_MULTI_CHAR_SURNAMES;

      // Default to 1 character if the surname is not in the list.
      surnameLength = multiCharSurnames.some(surname =>
        name.startsWith(surname)
      )
        ? 2
        : 1;

      nameParts.family = name.substr(0, surnameLength);
      nameParts.given = name.substr(surnameLength);
    } else if (nameTokens.length == 2) {
      // The user entered a space between the two name parts. This makes our job
      // easier. Family name first, given name second.
      nameParts.family = nameTokens[0];
      nameParts.given = nameTokens[1];
    } else {
      return null;
    }

    return nameParts;
  },

  init() {
    if (this._dataLoaded) {
      return;
    }
    this._dataLoaded = true;

    this.reCJK = new RegExp("[" + this.CJK_RANGE.join("") + "]", "u");
  },

  splitName(name) {
    let nameParts = {
      given: "",
      middle: "",
      family: "",
    };

    if (!name) {
      return nameParts;
    }

    let nameTokens = name.trim().split(/[ ,\u3000\u30FB\u00B7]+/);
    nameTokens = this._stripPrefixes(nameTokens);

    if (this._isCJKName(name)) {
      let parts = this._splitCJKName(nameTokens);
      if (parts) {
        return parts;
      }
    }

    // Don't assume "Ma" is a suffix in John Ma.
    if (nameTokens.length > 2) {
      nameTokens = this._stripSuffixes(nameTokens);
    }

    if (!nameTokens.length) {
      // Bad things have happened; just assume the whole thing is a given name.
      nameParts.given = name;
      return nameParts;
    }

    // Only one token, assume given name.
    if (nameTokens.length == 1) {
      nameParts.given = nameTokens[0];
      return nameParts;
    }

    // 2 or more tokens. Grab the family, which is the last word plus any
    // recognizable family prefixes.
    let familyTokens = [nameTokens.pop()];
    while (nameTokens.length) {
      let lastToken = nameTokens[nameTokens.length - 1];
      if (!this._containsString(this.FAMILY_NAME_PREFIXES, lastToken)) {
        break;
      }
      familyTokens.unshift(lastToken);
      nameTokens.pop();
    }
    nameParts.family = familyTokens.join(" ");

    // Take the last remaining token as the middle name (if there are at least 2
    // tokens).
    if (nameTokens.length >= 2) {
      nameParts.middle = nameTokens.pop();
    }

    // Remainder is given name.
    nameParts.given = nameTokens.join(" ");

    return nameParts;
  },

  joinNameParts({ given, middle, family }) {
    if (this._isCJKName(given) && this._isCJKName(family) && !middle) {
      return family + given;
    }
    return [given, middle, family]
      .filter(part => part && part.length)
      .join(" ");
  },
};

FormAutofillNameUtils.init();
PK
!<֪3a�>�>"modules/shared/PhoneNumber.sys.mjs/* This Source Code Form is subject to the terms of the Apache License, Version
 * 2.0. If a copy of the Apache License was not distributed with this file, You
 * can obtain one at https://www.apache.org/licenses/LICENSE-2.0 */

// This library came from https://github.com/andreasgal/PhoneNumber.js but will
// be further maintained by our own in Form Autofill codebase.

import { PHONE_NUMBER_META_DATA } from "resource://gre/modules/shared/PhoneNumberMetaData.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PhoneNumberNormalizer:
    "resource://gre/modules/shared/PhoneNumberNormalizer.sys.mjs",
});

export var PhoneNumber = (function (dataBase) {
  const MAX_PHONE_NUMBER_LENGTH = 50;
  const NON_ALPHA_CHARS = /[^a-zA-Z]/g;
  const NON_DIALABLE_CHARS = /[^,#+\*\d]/g;
  const NON_DIALABLE_CHARS_ONCE = new RegExp(NON_DIALABLE_CHARS.source);
  const SPLIT_FIRST_GROUP = /^(\d+)(.*)$/;
  const LEADING_PLUS_CHARS_PATTERN = /^[+\uFF0B]+/g;

  // Format of the string encoded meta data. If the name contains "^" or "$"
  // we will generate a regular expression from the value, with those special
  // characters as prefix/suffix.
  const META_DATA_ENCODING = [
    "region",
    "^(?:internationalPrefix)",
    "nationalPrefix",
    "^(?:nationalPrefixForParsing)",
    "nationalPrefixTransformRule",
    "nationalPrefixFormattingRule",
    "^possiblePattern$",
    "^nationalPattern$",
    "formats",
  ];

  const FORMAT_ENCODING = [
    "^pattern$",
    "nationalFormat",
    "^leadingDigits",
    "nationalPrefixFormattingRule",
    "internationalFormat",
  ];

  let regionCache = Object.create(null);

  // Parse an array of strings into a convenient object. We store meta
  // data as arrays since thats much more compact than JSON.
  function ParseArray(array, encoding, obj) {
    for (let n = 0; n < encoding.length; ++n) {
      let value = array[n];
      if (!value) {
        continue;
      }
      let field = encoding[n];
      let fieldAlpha = field.replace(NON_ALPHA_CHARS, "");
      if (field != fieldAlpha) {
        value = new RegExp(field.replace(fieldAlpha, value));
      }
      obj[fieldAlpha] = value;
    }
    return obj;
  }

  // Parse string encoded meta data into a convenient object
  // representation.
  function ParseMetaData(countryCode, md) {
    let array = JSON.parse(md);
    md = ParseArray(array, META_DATA_ENCODING, { countryCode });
    regionCache[md.region] = md;
    return md;
  }

  // Parse string encoded format data into a convenient object
  // representation.
  function ParseFormat(md) {
    let formats = md.formats;
    if (!formats) {
      return;
    }
    // Bail if we already parsed the format definitions.
    if (!Array.isArray(formats[0])) {
      return;
    }
    for (let n = 0; n < formats.length; ++n) {
      formats[n] = ParseArray(formats[n], FORMAT_ENCODING, {});
    }
  }

  // Search for the meta data associated with a region identifier ("US") in
  // our database, which is indexed by country code ("1"). Since we have
  // to walk the entire database for this, we cache the result of the lookup
  // for future reference.
  function FindMetaDataForRegion(region) {
    // Check in the region cache first. This will find all entries we have
    // already resolved (parsed from a string encoding).
    let md = regionCache[region];
    if (md) {
      return md;
    }
    for (let countryCode in dataBase) {
      let entry = dataBase[countryCode];
      // Each entry is a string encoded object of the form '["US..', or
      // an array of strings. We don't want to parse the string here
      // to save memory, so we just substring the region identifier
      // and compare it. For arrays, we compare against all region
      // identifiers with that country code. We skip entries that are
      // of type object, because they were already resolved (parsed into
      // an object), and their country code should have been in the cache.
      if (Array.isArray(entry)) {
        for (let n = 0; n < entry.length; n++) {
          if (typeof entry[n] == "string" && entry[n].substr(2, 2) == region) {
            if (n > 0) {
              // Only the first entry has the formats field set.
              // Parse the main country if we haven't already and use
              // the formats field from the main country.
              if (typeof entry[0] == "string") {
                entry[0] = ParseMetaData(countryCode, entry[0]);
              }
              let formats = entry[0].formats;
              let current = ParseMetaData(countryCode, entry[n]);
              current.formats = formats;
              entry[n] = current;
              return entry[n];
            }

            entry[n] = ParseMetaData(countryCode, entry[n]);
            return entry[n];
          }
        }
        continue;
      }
      if (typeof entry == "string" && entry.substr(2, 2) == region) {
        dataBase[countryCode] = ParseMetaData(countryCode, entry);
        return dataBase[countryCode];
      }
    }
  }

  // Format a national number for a given region. The boolean flag "intl"
  // indicates whether we want the national or international format.
  function FormatNumber(regionMetaData, number, intl) {
    // We lazily parse the format description in the meta data for the region,
    // so make sure to parse it now if we haven't already done so.
    ParseFormat(regionMetaData);
    let formats = regionMetaData.formats;
    if (!formats) {
      return null;
    }
    for (let n = 0; n < formats.length; ++n) {
      let format = formats[n];
      // The leading digits field is optional. If we don't have it, just
      // use the matching pattern to qualify numbers.
      if (format.leadingDigits && !format.leadingDigits.test(number)) {
        continue;
      }
      if (!format.pattern.test(number)) {
        continue;
      }
      if (intl) {
        // If there is no international format, just fall back to the national
        // format.
        let internationalFormat = format.internationalFormat;
        if (!internationalFormat) {
          internationalFormat = format.nationalFormat;
        }
        // Some regions have numbers that can't be dialed from outside the
        // country, indicated by "NA" for the international format of that
        // number format pattern.
        if (internationalFormat == "NA") {
          return null;
        }
        // Prepend "+" and the country code.
        number =
          "+" +
          regionMetaData.countryCode +
          " " +
          number.replace(format.pattern, internationalFormat);
      } else {
        number = number.replace(format.pattern, format.nationalFormat);
        // The region has a national prefix formatting rule, and it can be overwritten
        // by each actual number format rule.
        let nationalPrefixFormattingRule =
          regionMetaData.nationalPrefixFormattingRule;
        if (format.nationalPrefixFormattingRule) {
          nationalPrefixFormattingRule = format.nationalPrefixFormattingRule;
        }
        if (nationalPrefixFormattingRule) {
          // The prefix formatting rule contains two magic markers, "$NP" and "$FG".
          // "$NP" will be replaced by the national prefix, and "$FG" with the
          // first group of numbers.
          let match = number.match(SPLIT_FIRST_GROUP);
          if (match) {
            let firstGroup = match[1];
            let rest = match[2];
            let prefix = nationalPrefixFormattingRule;
            prefix = prefix.replace("$NP", regionMetaData.nationalPrefix);
            prefix = prefix.replace("$FG", firstGroup);
            number = prefix + rest;
          }
        }
      }
      return number == "NA" ? null : number;
    }
    return null;
  }

  function NationalNumber(regionMetaData, number) {
    this.region = regionMetaData.region;
    this.regionMetaData = regionMetaData;
    this.number = number;
  }

  // NationalNumber represents the result of parsing a phone number. We have
  // three getters on the prototype that format the number in national and
  // international format. Once called, the getters put a direct property
  // onto the object, caching the result.
  NationalNumber.prototype = {
    // +1 949-726-2896
    get internationalFormat() {
      let value = FormatNumber(this.regionMetaData, this.number, true);
      Object.defineProperty(this, "internationalFormat", {
        value,
        enumerable: true,
      });
      return value;
    },
    // (949) 726-2896
    get nationalFormat() {
      let value = FormatNumber(this.regionMetaData, this.number, false);
      Object.defineProperty(this, "nationalFormat", {
        value,
        enumerable: true,
      });
      return value;
    },
    // +19497262896
    get internationalNumber() {
      let value = this.internationalFormat
        ? this.internationalFormat.replace(NON_DIALABLE_CHARS, "")
        : null;
      Object.defineProperty(this, "internationalNumber", {
        value,
        enumerable: true,
      });
      return value;
    },
    // 9497262896
    get nationalNumber() {
      let value = this.nationalFormat
        ? this.nationalFormat.replace(NON_DIALABLE_CHARS, "")
        : null;
      Object.defineProperty(this, "nationalNumber", {
        value,
        enumerable: true,
      });
      return value;
    },
    // country name 'US'
    get countryName() {
      let value = this.region ? this.region : null;
      Object.defineProperty(this, "countryName", { value, enumerable: true });
      return value;
    },
    // country code '+1'
    get countryCode() {
      let value = this.regionMetaData.countryCode
        ? "+" + this.regionMetaData.countryCode
        : null;
      Object.defineProperty(this, "countryCode", { value, enumerable: true });
      return value;
    },
  };

  // Check whether the number is valid for the given region.
  function IsValidNumber(number, md) {
    return md.possiblePattern.test(number);
  }

  // Check whether the number is a valid national number for the given region.
  /* eslint-disable no-unused-vars */
  function IsNationalNumber(number, md) {
    return IsValidNumber(number, md) && md.nationalPattern.test(number);
  }

  // Determine the country code a number starts with, or return null if
  // its not a valid country code.
  function ParseCountryCode(number) {
    for (let n = 1; n <= 3; ++n) {
      let cc = number.substr(0, n);
      if (dataBase[cc]) {
        return cc;
      }
    }
    return null;
  }

  // Parse a national number for a specific region. Return null if the
  // number is not a valid national number (it might still be a possible
  // number for parts of that region).
  function ParseNationalNumber(number, md) {
    if (!md.possiblePattern.test(number) || !md.nationalPattern.test(number)) {
      return null;
    }
    // Success.
    return new NationalNumber(md, number);
  }

  function ParseNationalNumberAndCheckNationalPrefix(number, md) {
    let ret;

    // This is not an international number. See if its a national one for
    // the current region. National numbers can start with the national
    // prefix, or without.
    if (md.nationalPrefixForParsing) {
      // Some regions have specific national prefix parse rules. Apply those.
      let withoutPrefix = number.replace(
        md.nationalPrefixForParsing,
        md.nationalPrefixTransformRule || ""
      );
      ret = ParseNationalNumber(withoutPrefix, md);
      if (ret) {
        return ret;
      }
    } else {
      // If there is no specific national prefix rule, just strip off the
      // national prefix from the beginning of the number (if there is one).
      let nationalPrefix = md.nationalPrefix;
      if (
        nationalPrefix &&
        number.indexOf(nationalPrefix) == 0 &&
        (ret = ParseNationalNumber(number.substr(nationalPrefix.length), md))
      ) {
        return ret;
      }
    }
    ret = ParseNationalNumber(number, md);
    if (ret) {
      return ret;
    }
  }

  function ParseNumberByCountryCode(number, countryCode) {
    let ret;

    // Lookup the meta data for the region (or regions) and if the rest of
    // the number parses for that region, return the parsed number.
    let entry = dataBase[countryCode];
    if (Array.isArray(entry)) {
      for (let n = 0; n < entry.length; ++n) {
        if (typeof entry[n] == "string") {
          entry[n] = ParseMetaData(countryCode, entry[n]);
        }
        if (n > 0) {
          entry[n].formats = entry[0].formats;
        }
        ret = ParseNationalNumberAndCheckNationalPrefix(number, entry[n]);
        if (ret) {
          return ret;
        }
      }
      return null;
    }
    if (typeof entry == "string") {
      entry = dataBase[countryCode] = ParseMetaData(countryCode, entry);
    }
    return ParseNationalNumberAndCheckNationalPrefix(number, entry);
  }

  // Parse an international number that starts with the country code. Return
  // null if the number is not a valid international number.
  function ParseInternationalNumber(number) {
    // Parse and strip the country code.
    let countryCode = ParseCountryCode(number);
    if (!countryCode) {
      return null;
    }
    number = number.substr(countryCode.length);

    return ParseNumberByCountryCode(number, countryCode);
  }

  // Parse a number and transform it into the national format, removing any
  // international dial prefixes and country codes.
  function ParseNumber(number, defaultRegion) {
    let ret;

    // Remove formating characters and whitespace.
    number = lazy.PhoneNumberNormalizer.Normalize(number);

    // If there is no defaultRegion or the defaultRegion is the global region,
    // we can't parse international access codes.
    if ((!defaultRegion || defaultRegion === "001") && number[0] !== "+") {
      return null;
    }

    // Detect and strip leading '+'.
    if (number[0] === "+") {
      return ParseInternationalNumber(
        number.replace(LEADING_PLUS_CHARS_PATTERN, "")
      );
    }

    // If "defaultRegion" is a country code, use it to parse the number directly.
    let matches = String(defaultRegion).match(/^\+?(\d+)/);
    if (matches) {
      let countryCode = ParseCountryCode(matches[1]);
      if (!countryCode) {
        return null;
      }
      return ParseNumberByCountryCode(number, countryCode);
    }

    // Lookup the meta data for the given region.
    let md = FindMetaDataForRegion(defaultRegion.toUpperCase());
    if (!md) {
      dump("Couldn't find Meta Data for region: " + defaultRegion + "\n");
      return null;
    }

    // See if the number starts with an international prefix, and if the
    // number resulting from stripping the code is valid, then remove the
    // prefix and flag the number as international.
    if (md.internationalPrefix.test(number)) {
      let possibleNumber = number.replace(md.internationalPrefix, "");
      ret = ParseInternationalNumber(possibleNumber);
      if (ret) {
        return ret;
      }
    }

    ret = ParseNationalNumberAndCheckNationalPrefix(number, md);
    if (ret) {
      return ret;
    }

    // Now lets see if maybe its an international number after all, but
    // without '+' or the international prefix.
    ret = ParseInternationalNumber(number);
    if (ret) {
      return ret;
    }

    // If the number matches the possible numbers of the current region,
    // return it as a possible number.
    if (md.possiblePattern.test(number)) {
      return new NationalNumber(md, number);
    }

    // We couldn't parse the number at all.
    return null;
  }

  function IsPlainPhoneNumber(number) {
    if (typeof number !== "string") {
      return false;
    }

    let length = number.length;
    let isTooLong = length > MAX_PHONE_NUMBER_LENGTH;
    let isEmpty = length === 0;
    return !(isTooLong || isEmpty || NON_DIALABLE_CHARS_ONCE.test(number));
  }

  return {
    IsPlain: IsPlainPhoneNumber,
    IsValid: IsValidNumber,
    Parse: ParseNumber,
    FindMetaDataForRegion,
  };
})(PHONE_NUMBER_META_DATA);
PK
!<H���*modules/shared/PhoneNumberMetaData.sys.mjs/* This Source Code Form is subject to the terms of the Apache License, Version
 * 2.0. If a copy of the Apache License was not distributed with this file, You
 * can obtain one at https://www.apache.org/licenses/LICENSE-2.0 */

/*
 * This data was generated base on libphonenumber v8.4.1 via the script in
 * https://github.com/andreasgal/PhoneNumber.js
 *
 * The XML format of libphonenumber has changed since v8.4.2 so we can only stay
 * in this version for now.
 */

export var PHONE_NUMBER_META_DATA = {
  46: '["SE","00","0",null,null,"$NP$FG","\\\\d{6,12}","[1-35-9]\\\\d{5,11}|4\\\\d{6,8}",[["(8)(\\\\d{2,3})(\\\\d{2,3})(\\\\d{2})","$1-$2 $3 $4","8",null,"$1 $2 $3 $4"],["([1-69]\\\\d)(\\\\d{2,3})(\\\\d{2})(\\\\d{2})","$1-$2 $3 $4","1[013689]|2[0136]|3[1356]|4[0246]|54|6[03]|90",null,"$1 $2 $3 $4"],["([1-469]\\\\d)(\\\\d{3})(\\\\d{2})","$1-$2 $3","1[136]|2[136]|3[356]|4[0246]|6[03]|90",null,"$1 $2 $3"],["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1-$2 $3 $4","1[2457]|2(?:[247-9]|5[0138])|3[0247-9]|4[1357-9]|5[0-35-9]|6(?:[124-689]|7[0-2])|9(?:[125-8]|3[0-5]|4[0-3])",null,"$1 $2 $3 $4"],["(\\\\d{3})(\\\\d{2,3})(\\\\d{2})","$1-$2 $3","1[2457]|2(?:[247-9]|5[0138])|3[0247-9]|4[1357-9]|5[0-35-9]|6(?:[124-689]|7[0-2])|9(?:[125-8]|3[0-5]|4[0-3])",null,"$1 $2 $3"],["(7\\\\d)(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1-$2 $3 $4","7",null,"$1 $2 $3 $4"],["(77)(\\\\d{2})(\\\\d{2})","$1-$2$3","7",null,"$1 $2 $3"],["(20)(\\\\d{2,3})(\\\\d{2})","$1-$2 $3","20",null,"$1 $2 $3"],["(9[034]\\\\d)(\\\\d{2})(\\\\d{2})(\\\\d{3})","$1-$2 $3 $4","9[034]",null,"$1 $2 $3 $4"],["(9[034]\\\\d)(\\\\d{4})","$1-$2","9[034]",null,"$1 $2"],["(\\\\d{3})(\\\\d{2})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1-$2 $3 $4 $5","25[245]|67[3-6]",null,"$1 $2 $3 $4 $5"]]]',
  299: '["GL","00",null,null,null,null,"\\\\d{6}","[1-689]\\\\d{5}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3",null,null]]]',
  385: '["HR","00","0",null,null,"$NP$FG","\\\\d{6,9}","[1-7]\\\\d{5,8}|[89]\\\\d{6,8}",[["(1)(\\\\d{4})(\\\\d{3})","$1 $2 $3","1",null],["([2-5]\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[2-5]",null],["(9\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","9",null],["(6[01])(\\\\d{2})(\\\\d{2,3})","$1 $2 $3","6[01]",null],["([67]\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[67]",null],["(80[01])(\\\\d{2})(\\\\d{2,3})","$1 $2 $3","8",null],["(80[01])(\\\\d{3})(\\\\d{3})","$1 $2 $3","8",null]]]',
  670: '["TL","00",null,null,null,null,"\\\\d{7,8}","[2-489]\\\\d{6}|7\\\\d{6,7}",[["(\\\\d{3})(\\\\d{4})","$1 $2","[2-489]",null],["(\\\\d{4})(\\\\d{4})","$1 $2","7",null]]]',
  258: '["MZ","00",null,null,null,null,"\\\\d{8,9}","[28]\\\\d{7,8}",[["([28]\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","2|8[2-7]",null],["(80\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","80",null]]]',
  359: '["BG","00","0",null,null,"$NP$FG","\\\\d{5,9}","[23567]\\\\d{5,7}|[489]\\\\d{6,8}",[["(2)(\\\\d)(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","2",null],["(2)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","2",null],["(\\\\d{3})(\\\\d{4})","$1 $2","43[124-7]|70[1-9]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{2})","$1 $2 $3","43[124-7]|70[1-9]",null],["(\\\\d{3})(\\\\d{2})(\\\\d{3})","$1 $2 $3","[78]00",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","999",null],["(\\\\d{2})(\\\\d{3})(\\\\d{2,3})","$1 $2 $3","[356]|4[124-7]|7[1-9]|8[1-6]|9[1-7]",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","48|8[7-9]|9[08]",null]]]',
  682: '["CK","00",null,null,null,null,"\\\\d{5}","[2-8]\\\\d{4}",[["(\\\\d{2})(\\\\d{3})","$1 $2",null,null]]]',
  852: '["HK","00(?:[126-9]|30|5[09])?",null,null,null,null,"\\\\d{5,11}","[235-7]\\\\d{7}|8\\\\d{7,8}|9\\\\d{4,10}",[["(\\\\d{4})(\\\\d{4})","$1 $2","[235-7]|[89](?:0[1-9]|[1-9])",null],["(800)(\\\\d{3})(\\\\d{3})","$1 $2 $3","800",null],["(900)(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","900",null],["(900)(\\\\d{2,5})","$1 $2","900",null]]]',
  998: '["UZ","810","8",null,null,"$NP $FG","\\\\d{7,9}","[679]\\\\d{8}",[["([679]\\\\d)(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]',
  291: '["ER","00","0",null,null,"$NP$FG","\\\\d{6,7}","[178]\\\\d{6}",[["(\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3",null,null]]]',
  95: '["MM","00","0",null,null,"$NP$FG","\\\\d{5,10}","[1478]\\\\d{5,7}|[256]\\\\d{5,8}|9(?:[279]\\\\d{0,2}|[58]|[34]\\\\d{1,2}|6\\\\d?)\\\\d{6}",[["(\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","1|2[245]",null],["(2)(\\\\d{4})(\\\\d{4})","$1 $2 $3","251",null],["(\\\\d)(\\\\d{2})(\\\\d{3})","$1 $2 $3","16|2",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","67|81",null],["(\\\\d{2})(\\\\d{2})(\\\\d{3,4})","$1 $2 $3","[4-8]",null],["(9)(\\\\d{3})(\\\\d{4,6})","$1 $2 $3","9(?:2[0-4]|[35-9]|4[137-9])",null],["(9)([34]\\\\d{4})(\\\\d{4})","$1 $2 $3","9(?:3[0-36]|4[0-57-9])",null],["(9)(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","92[56]",null],["(9)(\\\\d{3})(\\\\d{3})(\\\\d{2})","$1 $2 $3 $4","93",null]]]',
  266: '["LS","00",null,null,null,null,"\\\\d{8}","[2568]\\\\d{7}",[["(\\\\d{4})(\\\\d{4})","$1 $2",null,null]]]',
  245: '["GW","00",null,null,null,null,"\\\\d{7,9}","(?:4(?:0\\\\d{5}|4\\\\d{7})|9\\\\d{8})",[["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","44|9[567]",null],["(\\\\d{3})(\\\\d{4})","$1 $2","40",null]]]',
  374: '["AM","00","0",null,null,"($NP$FG)","\\\\d{5,8}","[1-9]\\\\d{7}",[["(\\\\d{2})(\\\\d{6})","$1 $2","1|47",null],["(\\\\d{2})(\\\\d{6})","$1 $2","4[1349]|[5-7]|9[1-9]","$NP$FG"],["(\\\\d{3})(\\\\d{5})","$1 $2","[23]",null],["(\\\\d{3})(\\\\d{2})(\\\\d{3})","$1 $2 $3","8|90","$NP $FG"]]]',
  61: [
    '["AU","(?:14(?:1[14]|34|4[17]|[56]6|7[47]|88))?001[14-689]","0",null,null,null,"\\\\d{6,10}","1\\\\d{4,9}|[2-578]\\\\d{8}",[["([2378])(\\\\d{4})(\\\\d{4})","$1 $2 $3","[2378]","($NP$FG)"],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[45]|14","$NP$FG"],["(16)(\\\\d{3,4})","$1 $2","16","$NP$FG"],["(16)(\\\\d{3})(\\\\d{2,4})","$1 $2 $3","16","$NP$FG"],["(1[389]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","1(?:[38]0|90)","$FG"],["(180)(2\\\\d{3})","$1 $2","180","$FG"],["(19\\\\d)(\\\\d{3})","$1 $2","19[13]","$FG"],["(19\\\\d{2})(\\\\d{4})","$1 $2","19[679]","$FG"],["(13)(\\\\d{2})(\\\\d{2})","$1 $2 $3","13[1-9]","$FG"]]]',
    '["CC","(?:14(?:1[14]|34|4[17]|[56]6|7[47]|88))?001[14-689]","0",null,null,null,"\\\\d{6,10}","[1458]\\\\d{5,9}"]',
    '["CX","(?:14(?:1[14]|34|4[17]|[56]6|7[47]|88))?001[14-689]","0",null,null,null,"\\\\d{6,10}","[1458]\\\\d{5,9}"]',
  ],
  500: '["FK","00",null,null,null,null,"\\\\d{5}","[2-7]\\\\d{4}"]',
  261: '["MG","00","0",null,null,"$NP$FG","\\\\d{7,9}","[23]\\\\d{8}",[["([23]\\\\d)(\\\\d{2})(\\\\d{3})(\\\\d{2})","$1 $2 $3 $4",null,null]]]',
  92: '["PK","00","0",null,null,"($NP$FG)","\\\\d{6,12}","1\\\\d{8}|[2-8]\\\\d{5,11}|9(?:[013-9]\\\\d{4,9}|2\\\\d(?:111\\\\d{6}|\\\\d{3,7}))",[["(\\\\d{2})(111)(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","(?:2[125]|4[0-246-9]|5[1-35-7]|6[1-8]|7[14]|8[16]|91)1",null],["(\\\\d{3})(111)(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","2[349]|45|54|60|72|8[2-5]|9[2-9]",null],["(\\\\d{2})(\\\\d{7,8})","$1 $2","(?:2[125]|4[0-246-9]|5[1-35-7]|6[1-8]|7[14]|8[16]|91)[2-9]",null],["(\\\\d{3})(\\\\d{6,7})","$1 $2","2[349]|45|54|60|72|8[2-5]|9[2-9]",null],["(3\\\\d{2})(\\\\d{7})","$1 $2","3","$NP$FG"],["([15]\\\\d{3})(\\\\d{5,6})","$1 $2","58[12]|1",null],["(586\\\\d{2})(\\\\d{5})","$1 $2","586",null],["([89]00)(\\\\d{3})(\\\\d{2})","$1 $2 $3","[89]00","$NP$FG"]]]',
  234: '["NG","009","0",null,null,"$NP$FG","\\\\d{5,14}","[1-6]\\\\d{5,8}|9\\\\d{5,9}|[78]\\\\d{5,13}",[["(\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[12]|9(?:0[3-9]|[1-9])",null],["(\\\\d{2})(\\\\d{3})(\\\\d{2,3})","$1 $2 $3","[3-6]|7(?:[1-79]|0[1-9])|8[2-9]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","70|8[01]|90[235-9]",null],["([78]00)(\\\\d{4})(\\\\d{4,5})","$1 $2 $3","[78]00",null],["([78]00)(\\\\d{5})(\\\\d{5,6})","$1 $2 $3","[78]00",null],["(78)(\\\\d{2})(\\\\d{3})","$1 $2 $3","78",null]]]',
  350: '["GI","00",null,null,null,null,"\\\\d{8}","[2568]\\\\d{7}",[["(\\\\d{3})(\\\\d{5})","$1 $2","2",null]]]',
  45: '["DK","00",null,null,null,null,"\\\\d{8}","[2-9]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]',
  963: '["SY","00","0",null,null,"$NP$FG","\\\\d{6,9}","[1-59]\\\\d{7,8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[1-5]",null],["(9\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","9",null]]]',
  226: '["BF","00",null,null,null,null,"\\\\d{8}","[25-7]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]',
  974: '["QA","00",null,null,null,null,"\\\\d{7,8}","[2-8]\\\\d{6,7}",[["([28]\\\\d{2})(\\\\d{4})","$1 $2","[28]",null],["([3-7]\\\\d{3})(\\\\d{4})","$1 $2","[3-7]",null]]]',
  218: '["LY","00","0",null,null,"$NP$FG","\\\\d{7,9}","[25679]\\\\d{8}",[["([25679]\\\\d)(\\\\d{7})","$1-$2",null,null]]]',
  51: '["PE","19(?:1[124]|77|90)00","0",null,null,"($NP$FG)","\\\\d{6,9}","[14-9]\\\\d{7,8}",[["(1)(\\\\d{7})","$1 $2","1",null],["([4-8]\\\\d)(\\\\d{6})","$1 $2","[4-7]|8[2-4]",null],["(\\\\d{3})(\\\\d{5})","$1 $2","80",null],["(9\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","9","$FG"]]]',
  62: '["ID","0(?:0[1789]|10(?:00|1[67]))","0",null,null,"$NP$FG","\\\\d{5,12}","(?:[1-79]\\\\d{6,10}|8\\\\d{7,11})",[["(\\\\d{2})(\\\\d{5,8})","$1 $2","2[124]|[36]1","($NP$FG)"],["(\\\\d{3})(\\\\d{5,8})","$1 $2","[4579]|2[035-9]|[36][02-9]","($NP$FG)"],["(8\\\\d{2})(\\\\d{3,4})(\\\\d{3})","$1-$2-$3","8[1-35-9]",null],["(8\\\\d{2})(\\\\d{4})(\\\\d{4,5})","$1-$2-$3","8[1-35-9]",null],["(1)(500)(\\\\d{3})","$1 $2 $3","15","$FG"],["(177)(\\\\d{6,8})","$1 $2","17",null],["(800)(\\\\d{5,7})","$1 $2","800",null],["(804)(\\\\d{3})(\\\\d{4})","$1 $2 $3","804",null],["(80\\\\d)(\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","80[79]",null]]]',
  298: '["FO","00",null,"(10(?:01|[12]0|88))",null,null,"\\\\d{6}","[2-9]\\\\d{5}",[["(\\\\d{6})","$1",null,null]]]',
  381: '["RS","00","0",null,null,"$NP$FG","\\\\d{5,12}","[126-9]\\\\d{4,11}|3(?:[0-79]\\\\d{3,10}|8[2-9]\\\\d{2,9})",[["([23]\\\\d{2})(\\\\d{4,9})","$1 $2","(?:2[389]|39)0",null],["([1-3]\\\\d)(\\\\d{5,10})","$1 $2","1|2(?:[0-24-7]|[389][1-9])|3(?:[0-8]|9[1-9])",null],["(6\\\\d)(\\\\d{6,8})","$1 $2","6",null],["([89]\\\\d{2})(\\\\d{3,9})","$1 $2","[89]",null],["(7[26])(\\\\d{4,9})","$1 $2","7[26]",null],["(7[08]\\\\d)(\\\\d{4,9})","$1 $2","7[08]",null]]]',
  975: '["BT","00",null,null,null,null,"\\\\d{6,8}","[1-8]\\\\d{6,7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","1|77",null],["([2-8])(\\\\d{3})(\\\\d{3})","$1 $2 $3","[2-68]|7[246]",null]]]',
  34: '["ES","00",null,null,null,null,"\\\\d{9}","[5-9]\\\\d{8}",[["([89]00)(\\\\d{3})(\\\\d{3})","$1 $2 $3","[89]00",null],["([5-9]\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[568]|[79][0-8]",null]]]',
  881: '["001",null,null,null,null,null,"\\\\d{9}","[67]\\\\d{8}",[["(\\\\d)(\\\\d{3})(\\\\d{5})","$1 $2 $3","[67]",null]]]',
  855: '["KH","00[14-9]","0",null,null,null,"\\\\d{6,10}","[1-9]\\\\d{7,9}",[["(\\\\d{2})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","1\\\\d[1-9]|[2-9]","$NP$FG"],["(1[89]00)(\\\\d{3})(\\\\d{3})","$1 $2 $3","1[89]0",null]]]',
  420: '["CZ","00",null,null,null,null,"\\\\d{9,12}","[2-8]\\\\d{8}|9\\\\d{8,11}",[["([2-9]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[2-8]|9[015-7]",null],["(96\\\\d)(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","96",null],["(9\\\\d)(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","9[36]",null]]]',
  216: '["TN","00",null,null,null,null,"\\\\d{8}","[2-57-9]\\\\d{7}",[["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3",null,null]]]',
  673: '["BN","00",null,null,null,null,"\\\\d{7}","[2-578]\\\\d{6}",[["([2-578]\\\\d{2})(\\\\d{4})","$1 $2",null,null]]]',
  290: [
    '["SH","00",null,null,null,null,"\\\\d{4,5}","[256]\\\\d{4}"]',
    '["TA","00",null,null,null,null,"\\\\d{4}","8\\\\d{3}"]',
  ],
  882: '["001",null,null,null,null,null,"\\\\d{7,12}","[13]\\\\d{6,11}",[["(\\\\d{2})(\\\\d{4})(\\\\d{3})","$1 $2 $3","3[23]",null],["(\\\\d{2})(\\\\d{5})","$1 $2","16|342",null],["(\\\\d{2})(\\\\d{4})(\\\\d{4})","$1 $2 $3","34[57]",null],["(\\\\d{3})(\\\\d{4})(\\\\d{4})","$1 $2 $3","348",null],["(\\\\d{2})(\\\\d{2})(\\\\d{4})","$1 $2 $3","1",null],["(\\\\d{2})(\\\\d{3,4})(\\\\d{4})","$1 $2 $3","16",null],["(\\\\d{2})(\\\\d{4,5})(\\\\d{5})","$1 $2 $3","16|39",null]]]',
  267: '["BW","00",null,null,null,null,"\\\\d{7,8}","[2-79]\\\\d{6,7}",[["(\\\\d{3})(\\\\d{4})","$1 $2","[2-6]",null],["(7\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","7",null],["(90)(\\\\d{5})","$1 $2","9",null]]]',
  94: '["LK","00","0",null,null,"$NP$FG","\\\\d{7,9}","[1-9]\\\\d{8}",[["(\\\\d{2})(\\\\d{1})(\\\\d{6})","$1 $2 $3","[1-689]",null],["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","7",null]]]',
  356: '["MT","00",null,null,null,null,"\\\\d{8}","[2357-9]\\\\d{7}",[["(\\\\d{4})(\\\\d{4})","$1 $2",null,null]]]',
  375: '["BY","810","8","8?0?",null,null,"\\\\d{5,11}","[1-4]\\\\d{8}|800\\\\d{3,7}|[89]\\\\d{9,10}",[["(\\\\d{2})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2-$3-$4","17[0-3589]|2[4-9]|[34]","$NP 0$FG"],["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2-$3-$4","1(?:5[24]|6[235]|7[467])|2(?:1[246]|2[25]|3[26])","$NP 0$FG"],["(\\\\d{4})(\\\\d{2})(\\\\d{3})","$1 $2-$3","1(?:5[169]|6[3-5]|7[179])|2(?:1[35]|2[34]|3[3-5])","$NP 0$FG"],["([89]\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","8[01]|9","$NP $FG"],["(82\\\\d)(\\\\d{4})(\\\\d{4})","$1 $2 $3","82","$NP $FG"],["(800)(\\\\d{3})","$1 $2","800","$NP $FG"],["(800)(\\\\d{2})(\\\\d{2,4})","$1 $2 $3","800","$NP $FG"]]]',
  690: '["TK","00",null,null,null,null,"\\\\d{4,7}","[2-47]\\\\d{3,6}"]',
  507: '["PA","00",null,null,null,null,"\\\\d{7,8}","[1-9]\\\\d{6,7}",[["(\\\\d{3})(\\\\d{4})","$1-$2","[1-57-9]",null],["(\\\\d{4})(\\\\d{4})","$1-$2","6",null]]]',
  692: '["MH","011","1",null,null,null,"\\\\d{7}","[2-6]\\\\d{6}",[["(\\\\d{3})(\\\\d{4})","$1-$2",null,null]]]',
  250: '["RW","00","0",null,null,null,"\\\\d{8,9}","[027-9]\\\\d{7,8}",[["(2\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","2","$FG"],["([7-9]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[7-9]","$NP$FG"],["(0\\\\d)(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","0",null]]]',
  81: '["JP","010","0",null,null,"$NP$FG","\\\\d{8,17}","[1-9]\\\\d{8,9}|00(?:[36]\\\\d{7,14}|7\\\\d{5,7}|8\\\\d{7})",[["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1-$2-$3","(?:12|57|99)0",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1-$2-$3","800",null],["(\\\\d{4})(\\\\d{4})","$1-$2","0077","$FG","NA"],["(\\\\d{4})(\\\\d{2})(\\\\d{3,4})","$1-$2-$3","0077","$FG","NA"],["(\\\\d{4})(\\\\d{2})(\\\\d{4})","$1-$2-$3","0088","$FG","NA"],["(\\\\d{4})(\\\\d{3})(\\\\d{3,4})","$1-$2-$3","00(?:37|66)","$FG","NA"],["(\\\\d{4})(\\\\d{4})(\\\\d{4,5})","$1-$2-$3","00(?:37|66)","$FG","NA"],["(\\\\d{4})(\\\\d{5})(\\\\d{5,6})","$1-$2-$3","00(?:37|66)","$FG","NA"],["(\\\\d{4})(\\\\d{6})(\\\\d{6,7})","$1-$2-$3","00(?:37|66)","$FG","NA"],["(\\\\d{2})(\\\\d{4})(\\\\d{4})","$1-$2-$3","[2579]0|80[1-9]",null],["(\\\\d{4})(\\\\d)(\\\\d{4})","$1-$2-$3","1(?:26|3[79]|4[56]|5[4-68]|6[3-5])|5(?:76|97)|499|746|8(?:3[89]|63|47|51)|9(?:49|80|9[16])",null],["(\\\\d{3})(\\\\d{2})(\\\\d{4})","$1-$2-$3","1(?:2[3-6]|3[3-9]|4[2-6]|5[2-8]|[68][2-7]|7[2-689]|9[1-578])|2(?:2[03-689]|3[3-58]|4[0-468]|5[04-8]|6[013-8]|7[06-9]|8[02-57-9]|9[13])|4(?:2[28]|3[689]|6[035-7]|7[05689]|80|9[3-5])|5(?:3[1-36-9]|4[4578]|5[013-8]|6[1-9]|7[2-8]|8[14-7]|9[4-9])|7(?:2[15]|3[5-9]|4[02-9]|6[135-8]|7[0-4689]|9[014-9])|8(?:2[49]|3[3-8]|4[5-8]|5[2-9]|6[35-9]|7[579]|8[03-579]|9[2-8])|9(?:[23]0|4[02-46-9]|5[024-79]|6[4-9]|7[2-47-9]|8[02-7]|9[3-7])",null],["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1-$2-$3","1|2(?:2[37]|5[5-9]|64|78|8[39]|91)|4(?:2[2689]|64|7[347])|5(?:[2-589]|39)|60|8(?:[46-9]|3[279]|2[124589])|9(?:[235-8]|93)",null],["(\\\\d{3})(\\\\d{2})(\\\\d{4})","$1-$2-$3","2(?:9[14-79]|74|[34]7|[56]9)|82|993",null],["(\\\\d)(\\\\d{4})(\\\\d{4})","$1-$2-$3","3|4(?:2[09]|7[01])|6[1-9]",null],["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1-$2-$3","[2479][1-9]",null]]]',
  237: '["CM","00",null,null,null,null,"\\\\d{8,9}","[2368]\\\\d{7,8}",[["([26])(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4 $5","[26]",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[23]|88",null],["(800)(\\\\d{2})(\\\\d{3})","$1 $2 $3","80",null]]]',
  351: '["PT","00",null,null,null,null,"\\\\d{9}","[2-46-9]\\\\d{8}",[["(2\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","2[12]",null],["([2-46-9]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","2[3-9]|[346-9]",null]]]',
  246: '["IO","00",null,null,null,null,"\\\\d{7}","3\\\\d{6}",[["(\\\\d{3})(\\\\d{4})","$1 $2",null,null]]]',
  227: '["NE","00",null,null,null,null,"\\\\d{8}","[0289]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[289]|09",null],["(08)(\\\\d{3})(\\\\d{3})","$1 $2 $3","08",null]]]',
  27: '["ZA","00","0",null,null,"$NP$FG","\\\\d{5,9}","[1-79]\\\\d{8}|8\\\\d{4,8}",[["(860)(\\\\d{3})(\\\\d{3})","$1 $2 $3","860",null],["(\\\\d{2})(\\\\d{3,4})","$1 $2","8[1-4]",null],["(\\\\d{2})(\\\\d{3})(\\\\d{2,3})","$1 $2 $3","8[1-4]",null],["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","[1-79]|8(?:[0-57]|6[1-9])",null]]]',
  962: '["JO","00","0",null,null,"$NP$FG","\\\\d{8,9}","[235-9]\\\\d{7,8}",[["(\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","[2356]|87","($NP$FG)"],["(7)(\\\\d{4})(\\\\d{4})","$1 $2 $3","7[457-9]",null],["(\\\\d{3})(\\\\d{5,6})","$1 $2","70|8[0158]|9",null]]]',
  387: '["BA","00","0",null,null,"$NP$FG","\\\\d{6,9}","[3-9]\\\\d{7,8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2-$3","[3-5]",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","6[1-356]|[7-9]",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{3})","$1 $2 $3 $4","6[047]",null]]]',
  33: '["FR","00","0",null,null,"$NP$FG","\\\\d{9}","[1-9]\\\\d{8}",[["([1-79])(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4 $5","[1-79]",null],["(1\\\\d{2})(\\\\d{3})","$1 $2","11","$FG","NA"],["(8\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","8","$NP $FG"]]]',
  972: '["IL","0(?:0|1[2-9])","0",null,null,"$FG","\\\\d{4,12}","1\\\\d{6,11}|[2-589]\\\\d{3}(?:\\\\d{3,6})?|6\\\\d{3}|7\\\\d{6,9}",[["([2-489])(\\\\d{3})(\\\\d{4})","$1-$2-$3","[2-489]","$NP$FG"],["([57]\\\\d)(\\\\d{3})(\\\\d{4})","$1-$2-$3","[57]","$NP$FG"],["(153)(\\\\d{1,2})(\\\\d{3})(\\\\d{4})","$1 $2 $3 $4","153",null],["(1)([7-9]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1-$2-$3-$4","1[7-9]",null],["(1255)(\\\\d{3})","$1-$2","125",null],["(1200)(\\\\d{3})(\\\\d{3})","$1-$2-$3","120",null],["(1212)(\\\\d{2})(\\\\d{2})","$1-$2-$3","121",null],["(1599)(\\\\d{6})","$1-$2","15",null],["(\\\\d{4})","*$1","[2-689]",null]]]',
  248: '["SC","0(?:[02]|10?)",null,null,null,null,"\\\\d{6,7}","[24689]\\\\d{5,6}",[["(\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","[246]",null]]]',
  297: '["AW","00",null,null,null,null,"\\\\d{7}","[25-9]\\\\d{6}",[["(\\\\d{3})(\\\\d{4})","$1 $2",null,null]]]',
  421: '["SK","00","0",null,null,"$NP$FG","\\\\d{6,9}","(?:[2-68]\\\\d{5,8}|9\\\\d{6,8})",[["(2)(1[67])(\\\\d{3,4})","$1 $2 $3","21[67]",null],["([3-5]\\\\d)(1[67])(\\\\d{2,3})","$1 $2 $3","[3-5]",null],["(2)(\\\\d{3})(\\\\d{3})(\\\\d{2})","$1/$2 $3 $4","2",null],["([3-5]\\\\d)(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1/$2 $3 $4","[3-5]",null],["([689]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[689]",null],["(9090)(\\\\d{3})","$1 $2","9090",null]]]',
  672: '["NF","00",null,null,null,null,"\\\\d{5,6}","[13]\\\\d{5}",[["(\\\\d{2})(\\\\d{4})","$1 $2","1",null],["(\\\\d)(\\\\d{5})","$1 $2","3",null]]]',
  870: '["001",null,null,null,null,null,"\\\\d{9}","[35-7]\\\\d{8}",[["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3",null,null]]]',
  883: '["001",null,null,null,null,null,"\\\\d{9}(?:\\\\d{3})?","51\\\\d{7}(?:\\\\d{3})?",[["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","510",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","510",null],["(\\\\d{4})(\\\\d{4})(\\\\d{4})","$1 $2 $3","51[13]",null]]]',
  264: '["NA","00","0",null,null,"$NP$FG","\\\\d{8,9}","[68]\\\\d{7,8}",[["(8\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","8[1235]",null],["(6\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","6",null],["(88)(\\\\d{3})(\\\\d{3})","$1 $2 $3","88",null],["(870)(\\\\d{3})(\\\\d{3})","$1 $2 $3","870",null]]]',
  878: '["001",null,null,null,null,null,"\\\\d{12}","1\\\\d{11}",[["(\\\\d{2})(\\\\d{5})(\\\\d{5})","$1 $2 $3",null,null]]]',
  239: '["ST","00",null,null,null,null,"\\\\d{7}","[29]\\\\d{6}",[["(\\\\d{3})(\\\\d{4})","$1 $2",null,null]]]',
  357: '["CY","00",null,null,null,null,"\\\\d{8}","[257-9]\\\\d{7}",[["(\\\\d{2})(\\\\d{6})","$1 $2",null,null]]]',
  240: '["GQ","00",null,null,null,null,"\\\\d{9}","[23589]\\\\d{8}",[["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[235]",null],["(\\\\d{3})(\\\\d{6})","$1 $2","[89]",null]]]',
  506: '["CR","00",null,"(19(?:0[012468]|1[09]|20|66|77|99))",null,null,"\\\\d{8,10}","[24-9]\\\\d{7,9}",[["(\\\\d{4})(\\\\d{4})","$1 $2","[24-7]|8[3-9]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1-$2-$3","[89]0",null]]]',
  86: '["CN","(1(?:[129]\\\\d{3}|79\\\\d{2}))?00","0","(1(?:[129]\\\\d{3}|79\\\\d{2}))|0",null,null,"\\\\d{4,12}","[1-7]\\\\d{6,11}|8[0-357-9]\\\\d{6,9}|9\\\\d{7,10}",[["(80\\\\d{2})(\\\\d{4})","$1 $2","80[2678]","$NP$FG"],["([48]00)(\\\\d{3})(\\\\d{4})","$1 $2 $3","[48]00",null],["(\\\\d{5,6})","$1","100|95",null,"NA"],["(\\\\d{2})(\\\\d{5,6})","$1 $2","(?:10|2\\\\d)[19]","$NP$FG"],["(\\\\d{3})(\\\\d{5,6})","$1 $2","[3-9]","$NP$FG"],["(\\\\d{3,4})(\\\\d{4})","$1 $2","[2-9]",null,"NA"],["(21)(\\\\d{4})(\\\\d{4,6})","$1 $2 $3","21","$NP$FG"],["([12]\\\\d)(\\\\d{4})(\\\\d{4})","$1 $2 $3","10[1-9]|2[02-9]","$NP$FG"],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","3(?:1[02-9]|35|49|5|7[02-68]|9[1-68])|4(?:1[02-9]|2[179]|[35][2-9]|6[4789]|7\\\\d|8[23])|5(?:3[03-9]|4[36]|5[02-9]|6[1-46]|7[028]|80|9[2-46-9])|6(?:3[1-5]|6[0238]|9[12])|7(?:01|[1579]|2[248]|3[04-9]|4[3-6]|6[2368])|8(?:1[236-8]|2[5-7]|3|5[1-9]|7[02-9]|8[3678]|9[1-7])|9(?:0[1-3689]|1[1-79]|[379]|4[13]|5[1-5])","$NP$FG"],["(\\\\d{3})(\\\\d{4})(\\\\d{4})","$1 $2 $3","3(?:11|7[179])|4(?:[15]1|3[1-35])|5(?:1|2[37]|3[12]|51|7[13-79]|9[15])|7(?:31|5[457]|6[09]|91)|8(?:[57]1|98)","$NP$FG"],["(\\\\d{4})(\\\\d{3})(\\\\d{4})","$1 $2 $3","807","$NP$FG"],["(\\\\d{3})(\\\\d{4})(\\\\d{4})","$1 $2 $3","1[3-578]",null],["(10800)(\\\\d{3})(\\\\d{4})","$1 $2 $3","108",null],["(\\\\d{3})(\\\\d{7,8})","$1 $2","950",null]]]',
  257: '["BI","00",null,null,null,null,"\\\\d{8}","[267]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]',
  683: '["NU","00",null,null,null,null,"\\\\d{4}","[1-5]\\\\d{3}"]',
  43: '["AT","00","0",null,null,"$NP$FG","\\\\d{3,13}","[1-9]\\\\d{3,12}",[["(116\\\\d{3})","$1","116","$FG"],["(1)(\\\\d{3,12})","$1 $2","1",null],["(5\\\\d)(\\\\d{3,5})","$1 $2","5[079]",null],["(5\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","5[079]",null],["(5\\\\d)(\\\\d{4})(\\\\d{4,7})","$1 $2 $3","5[079]",null],["(\\\\d{3})(\\\\d{3,10})","$1 $2","316|46|51|732|6(?:5[0-3579]|[6-9])|7(?:[28]0)|[89]",null],["(\\\\d{4})(\\\\d{3,9})","$1 $2","2|3(?:1[1-578]|[3-8])|4[2378]|5[2-6]|6(?:[12]|4[1-9]|5[468])|7(?:2[1-8]|35|4[1-8]|[5-79])",null]]]',
  247: '["AC","00",null,null,null,null,"\\\\d{5,6}","[46]\\\\d{4}|[01589]\\\\d{5}"]',
  675: '["PG","00",null,null,null,null,"\\\\d{7,8}","[1-9]\\\\d{6,7}",[["(\\\\d{3})(\\\\d{4})","$1 $2","[13-689]|27",null],["(\\\\d{4})(\\\\d{4})","$1 $2","20|7",null]]]',
  376: '["AD","00",null,null,null,null,"\\\\d{6,9}","[16]\\\\d{5,8}|[37-9]\\\\d{5}",[["(\\\\d{3})(\\\\d{3})","$1 $2","[137-9]|6[0-8]",null],["(\\\\d{4})(\\\\d{4})","$1 $2","180",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","690",null]]]',
  63: '["PH","00","0",null,null,null,"\\\\d{5,13}","2\\\\d{5,7}|[3-9]\\\\d{7,9}|1800\\\\d{7,9}",[["(2)(\\\\d{3})(\\\\d{4})","$1 $2 $3","2","($NP$FG)"],["(2)(\\\\d{5})","$1 $2","2","($NP$FG)"],["(\\\\d{4})(\\\\d{4,6})","$1 $2","3(?:23|39|46)|4(?:2[3-6]|[35]9|4[26]|76)|5(?:22|44)|642|8(?:62|8[245])","($NP$FG)"],["(\\\\d{5})(\\\\d{4})","$1 $2","346|4(?:27|9[35])|883","($NP$FG)"],["([3-8]\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","[3-8]","($NP$FG)"],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","81|9","$NP$FG"],["(1800)(\\\\d{3})(\\\\d{4})","$1 $2 $3","1",null],["(1800)(\\\\d{1,2})(\\\\d{3})(\\\\d{4})","$1 $2 $3 $4","1",null]]]',
  236: '["CF","00",null,null,null,null,"\\\\d{8}","[278]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]',
  590: [
    '["GP","00","0",null,null,"$NP$FG","\\\\d{9}","[56]\\\\d{8}",[["([56]90)(\\\\d{2})(\\\\d{4})","$1 $2-$3",null,null]]]',
    '["BL","00","0",null,null,null,"\\\\d{9}","[56]\\\\d{8}"]',
    '["MF","00","0",null,null,null,"\\\\d{9}","[56]\\\\d{8}"]',
  ],
  53: '["CU","119","0",null,null,"($NP$FG)","\\\\d{4,8}","[2-57]\\\\d{5,7}",[["(\\\\d)(\\\\d{6,7})","$1 $2","7",null],["(\\\\d{2})(\\\\d{4,6})","$1 $2","[2-4]",null],["(\\\\d)(\\\\d{7})","$1 $2","5","$NP$FG"]]]',
  64: '["NZ","0(?:0|161)","0",null,null,"$NP$FG","\\\\d{7,11}","6[235-9]\\\\d{6}|[2-57-9]\\\\d{7,10}",[["([34679])(\\\\d{3})(\\\\d{4})","$1-$2 $3","[346]|7[2-57-9]|9[1-9]",null],["(24099)(\\\\d{3})","$1 $2","240",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","21",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3,5})","$1 $2 $3","2(?:1[1-9]|[69]|7[0-35-9])|70|86",null],["(2\\\\d)(\\\\d{3,4})(\\\\d{4})","$1 $2 $3","2[028]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","2(?:10|74)|5|[89]0",null]]]',
  965: '["KW","00",null,null,null,null,"\\\\d{7,8}","[12569]\\\\d{6,7}",[["(\\\\d{4})(\\\\d{3,4})","$1 $2","[16]|2(?:[0-35-9]|4[0-35-9])|9[024-9]|52[25]",null],["(\\\\d{3})(\\\\d{5})","$1 $2","244|5(?:[015]|66)",null]]]',
  224: '["GN","00",null,null,null,null,"\\\\d{8,9}","[367]\\\\d{7,8}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","3",null],["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[67]",null]]]',
  973: '["BH","00",null,null,null,null,"\\\\d{8}","[136-9]\\\\d{7}",[["(\\\\d{4})(\\\\d{4})","$1 $2",null,null]]]',
  32: '["BE","00","0",null,null,"$NP$FG","\\\\d{8,9}","[1-9]\\\\d{7,8}",[["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","4[6-9]",null],["(\\\\d)(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[23]|4[23]|9[2-4]",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[156]|7[018]|8(?:0[1-9]|[1-79])",null],["(\\\\d{3})(\\\\d{2})(\\\\d{3})","$1 $2 $3","(?:80|9)0",null]]]',
  249: '["SD","00","0",null,null,"$NP$FG","\\\\d{9}","[19]\\\\d{8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3",null,null]]]',
  678: '["VU","00",null,null,null,null,"\\\\d{5,7}","[2-57-9]\\\\d{4,6}",[["(\\\\d{3})(\\\\d{4})","$1 $2","[579]",null]]]',
  52: '["MX","0[09]","01","0[12]|04[45](\\\\d{10})","1$1","$NP $FG","\\\\d{7,11}","[1-9]\\\\d{9,10}",[["([358]\\\\d)(\\\\d{4})(\\\\d{4})","$1 $2 $3","33|55|81",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","[2467]|3[0-2457-9]|5[089]|8[02-9]|9[0-35-9]",null],["(1)([358]\\\\d)(\\\\d{4})(\\\\d{4})","044 $2 $3 $4","1(?:33|55|81)","$FG","$1 $2 $3 $4"],["(1)(\\\\d{3})(\\\\d{3})(\\\\d{4})","044 $2 $3 $4","1(?:[2467]|3[0-2457-9]|5[089]|8[2-9]|9[1-35-9])","$FG","$1 $2 $3 $4"]]]',
  968: '["OM","00",null,null,null,null,"\\\\d{7,9}","(?:5|[279]\\\\d)\\\\d{6}|800\\\\d{5,6}",[["(2\\\\d)(\\\\d{6})","$1 $2","2",null],["([79]\\\\d{3})(\\\\d{4})","$1 $2","[79]",null],["([58]00)(\\\\d{4,6})","$1 $2","[58]",null]]]',
  599: [
    '["CW","00",null,null,null,null,"\\\\d{7,8}","[169]\\\\d{6,7}",[["(\\\\d{3})(\\\\d{4})","$1 $2","[13-7]",null],["(9)(\\\\d{3})(\\\\d{4})","$1 $2 $3","9",null]]]',
    '["BQ","00",null,null,null,null,"\\\\d{7}","[347]\\\\d{6}"]',
  ],
  800: '["001",null,null,null,null,null,"\\\\d{8}","\\\\d{8}",[["(\\\\d{4})(\\\\d{4})","$1 $2",null,null]]]',
  386: '["SI","00","0",null,null,"$NP$FG","\\\\d{5,8}","[1-7]\\\\d{6,7}|[89]\\\\d{4,7}",[["(\\\\d)(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[12]|3[24-8]|4[24-8]|5[2-8]|7[3-8]","($NP$FG)"],["([3-7]\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","[37][01]|4[0139]|51|6",null],["([89][09])(\\\\d{3,6})","$1 $2","[89][09]",null],["([58]\\\\d{2})(\\\\d{5})","$1 $2","59|8[1-3]",null]]]',
  679: '["FJ","0(?:0|52)",null,null,null,null,"\\\\d{7}(?:\\\\d{4})?","[35-9]\\\\d{6}|0\\\\d{10}",[["(\\\\d{3})(\\\\d{4})","$1 $2","[35-9]",null],["(\\\\d{4})(\\\\d{3})(\\\\d{4})","$1 $2 $3","0",null]]]',
  238: '["CV","0",null,null,null,null,"\\\\d{7}","[259]\\\\d{6}",[["(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3",null,null]]]',
  691: '["FM","00",null,null,null,null,"\\\\d{7}","[39]\\\\d{6}",[["(\\\\d{3})(\\\\d{4})","$1 $2",null,null]]]',
  262: [
    '["RE","00","0",null,null,"$NP$FG","\\\\d{9}","[268]\\\\d{8}",[["([268]\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]',
    '["YT","00","0",null,null,"$NP$FG","\\\\d{9}","[268]\\\\d{8}"]',
  ],
  241: '["GA","00",null,null,null,null,"\\\\d{7,8}","0?\\\\d{7}",[["(\\\\d)(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[2-7]","0$FG"],["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","0",null]]]',
  370: '["LT","00","8","[08]",null,"($NP-$FG)","\\\\d{8}","[3-9]\\\\d{7}",[["([34]\\\\d)(\\\\d{6})","$1 $2","37|4(?:1|5[45]|6[2-4])",null],["([3-6]\\\\d{2})(\\\\d{5})","$1 $2","3[148]|4(?:[24]|6[09])|528|6",null],["([7-9]\\\\d{2})(\\\\d{2})(\\\\d{3})","$1 $2 $3","[7-9]","$NP $FG"],["(5)(2\\\\d{2})(\\\\d{4})","$1 $2 $3","52[0-79]",null]]]',
  256: '["UG","00[057]","0",null,null,"$NP$FG","\\\\d{5,9}","\\\\d{9}",[["(\\\\d{3})(\\\\d{6})","$1 $2","[7-9]|20(?:[013-8]|2[5-9])|4(?:6[45]|[7-9])",null],["(\\\\d{2})(\\\\d{7})","$1 $2","3|4(?:[1-5]|6[0-36-9])",null],["(2024)(\\\\d{5})","$1 $2","2024",null]]]',
  677: '["SB","0[01]",null,null,null,null,"\\\\d{5,7}","[1-9]\\\\d{4,6}",[["(\\\\d{2})(\\\\d{5})","$1 $2","[7-9]",null]]]',
  377: '["MC","00","0",null,null,"$NP$FG","\\\\d{8,9}","[34689]\\\\d{7,8}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[39]","$FG"],["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","4",null],["(6)(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4 $5","6",null],["(\\\\d{3})(\\\\d{3})(\\\\d{2})","$1 $2 $3","8","$FG"]]]',
  382: '["ME","00","0",null,null,"$NP$FG","\\\\d{6,9}","[2-9]\\\\d{7,8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[2-57-9]|6[036-9]",null]]]',
  231: '["LR","00","0",null,null,"$NP$FG","\\\\d{7,9}","2\\\\d{7,8}|[378]\\\\d{8}|4\\\\d{6}|5\\\\d{6,8}",[["(2\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","2",null],["([4-5])(\\\\d{3})(\\\\d{3})","$1 $2 $3","[45]",null],["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","[23578]",null]]]',
  591: '["BO","00(1\\\\d)?","0","0(1\\\\d)?",null,null,"\\\\d{7,8}","[23467]\\\\d{7}",[["([234])(\\\\d{7})","$1 $2","[234]",null],["([67]\\\\d{7})","$1","[67]",null]]]',
  808: '["001",null,null,null,null,null,"\\\\d{8}","\\\\d{8}",[["(\\\\d{4})(\\\\d{4})","$1 $2",null,null]]]',
  964: '["IQ","00","0",null,null,"$NP$FG","\\\\d{6,10}","[1-7]\\\\d{7,9}",[["(1)(\\\\d{3})(\\\\d{4})","$1 $2 $3","1",null],["([2-6]\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[2-6]",null],["(7\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","7",null]]]',
  225: '["CI","00",null,null,null,null,"\\\\d{8}","[02-8]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]',
  992: '["TJ","810","8",null,null,"$FG","\\\\d{3,9}","[3-57-9]\\\\d{8}",[["([349]\\\\d{2})(\\\\d{2})(\\\\d{4})","$1 $2 $3","[34]7|91[78]",null],["([457-9]\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","4[148]|[578]|9(?:1[59]|[0235-9])",null],["(331700)(\\\\d)(\\\\d{2})","$1 $2 $3","331",null],["(\\\\d{4})(\\\\d)(\\\\d{4})","$1 $2 $3","3[1-5]",null]]]',
  55: '["BR","00(?:1[245]|2[1-35]|31|4[13]|[56]5|99)","0","(?:0|90)(?:(1[245]|2[135]|[34]1)(\\\\d{10,11}))?","$2",null,"\\\\d{8,11}","[1-46-9]\\\\d{7,10}|5(?:[0-4]\\\\d{7,9}|5(?:[2-8]\\\\d{7}|9\\\\d{7,8}))",[["(\\\\d{4})(\\\\d{4})","$1-$2","[2-9](?:[1-9]|0[1-9])","$FG","NA"],["(\\\\d{5})(\\\\d{4})","$1-$2","9(?:[1-9]|0[1-9])","$FG","NA"],["(\\\\d{3,5})","$1","1[125689]","$FG","NA"],["(\\\\d{2})(\\\\d{4})(\\\\d{4})","$1 $2-$3","[1-9][1-9]","($FG)"],["(\\\\d{2})(\\\\d{5})(\\\\d{4})","$1 $2-$3","(?:[14689][1-9]|2[12478]|3[1-578]|5[1-5]|7[13-579])9","($FG)"],["(\\\\d{4})(\\\\d{4})","$1-$2","(?:300|40(?:0|20))",null],["([3589]00)(\\\\d{2,3})(\\\\d{4})","$1 $2 $3","[3589]00","$NP$FG"]]]',
  674: '["NR","00",null,null,null,null,"\\\\d{7}","[458]\\\\d{6}",[["(\\\\d{3})(\\\\d{4})","$1 $2",null,null]]]',
  967: '["YE","00","0",null,null,"$NP$FG","\\\\d{6,9}","[1-7]\\\\d{6,8}",[["([1-7])(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[1-6]|7[24-68]",null],["(7\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","7[0137]",null]]]',
  49: '["DE","00","0",null,null,"$NP$FG","\\\\d{2,15}","[1-35-9]\\\\d{3,14}|4(?:[0-8]\\\\d{3,12}|9(?:[0-37]\\\\d|4(?:[1-35-8]|4\\\\d?)|5\\\\d{1,2}|6[1-8]\\\\d?)\\\\d{2,8})",[["(1\\\\d{2})(\\\\d{7,8})","$1 $2","1[67]",null],["(15\\\\d{3})(\\\\d{6})","$1 $2","15[0568]",null],["(1\\\\d{3})(\\\\d{7})","$1 $2","15",null],["(\\\\d{2})(\\\\d{3,11})","$1 $2","3[02]|40|[68]9",null],["(\\\\d{3})(\\\\d{3,11})","$1 $2","2(?:\\\\d1|0[2389]|1[24]|28|34)|3(?:[3-9][15]|40)|[4-8][1-9]1|9(?:06|[1-9]1)",null],["(\\\\d{4})(\\\\d{2,11})","$1 $2","[24-6]|[7-9](?:\\\\d[1-9]|[1-9]\\\\d)|3(?:[3569][02-46-9]|4[2-4679]|7[2-467]|8[2-46-8])",null],["(3\\\\d{4})(\\\\d{1,10})","$1 $2","3",null],["(800)(\\\\d{7,12})","$1 $2","800",null],["(\\\\d{3})(\\\\d)(\\\\d{4,10})","$1 $2 $3","(?:18|90)0|137",null],["(1\\\\d{2})(\\\\d{5,11})","$1 $2","181",null],["(18\\\\d{3})(\\\\d{6})","$1 $2","185",null],["(18\\\\d{2})(\\\\d{7})","$1 $2","18[68]",null],["(18\\\\d)(\\\\d{8})","$1 $2","18[2-579]",null],["(700)(\\\\d{4})(\\\\d{4})","$1 $2 $3","700",null],["(138)(\\\\d{4})","$1 $2","138",null],["(15[013-68])(\\\\d{2})(\\\\d{8})","$1 $2 $3","15[013-68]",null],["(15[279]\\\\d)(\\\\d{2})(\\\\d{7})","$1 $2 $3","15[279]",null],["(1[67]\\\\d)(\\\\d{2})(\\\\d{7,8})","$1 $2 $3","1(?:6[023]|7)",null]]]',
  31: '["NL","00","0",null,null,"$NP$FG","\\\\d{5,10}","1\\\\d{4,8}|[2-7]\\\\d{8}|[89]\\\\d{6,9}",[["([1-578]\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","1[035]|2[0346]|3[03568]|4[0356]|5[0358]|7|8[4578]",null],["([1-5]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","1[16-8]|2[259]|3[124]|4[17-9]|5[124679]",null],["(6)(\\\\d{8})","$1 $2","6[0-57-9]",null],["(66)(\\\\d{7})","$1 $2","66",null],["(14)(\\\\d{3,4})","$1 $2","14","$FG"],["([89]0\\\\d)(\\\\d{4,7})","$1 $2","80|9",null]]]',
  970: '["PS","00","0",null,null,"$NP$FG","\\\\d{4,10}","[24589]\\\\d{7,8}|1(?:[78]\\\\d{8}|[49]\\\\d{2,3})",[["([2489])(2\\\\d{2})(\\\\d{4})","$1 $2 $3","[2489]",null],["(5[69]\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","5",null],["(1[78]00)(\\\\d{3})(\\\\d{3})","$1 $2 $3","1[78]","$FG"]]]',
  58: '["VE","00","0",null,null,"$NP$FG","\\\\d{7,10}","[24589]\\\\d{9}",[["(\\\\d{3})(\\\\d{7})","$1-$2",null,null]]]',
  856: '["LA","00","0",null,null,"$NP$FG","\\\\d{6,10}","[2-8]\\\\d{7,9}",[["(20)(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","20",null],["([2-8]\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","2[13]|3[14]|[4-8]",null],["(30)(\\\\d{2})(\\\\d{2})(\\\\d{3})","$1 $2 $3 $4","30",null]]]',
  354: '["IS","1(?:0(?:01|10|20)|100)|00",null,null,null,null,"\\\\d{7,9}","[4-9]\\\\d{6}|38\\\\d{7}",[["(\\\\d{3})(\\\\d{4})","$1 $2","[4-9]",null],["(3\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","3",null]]]',
  242: '["CG","00",null,null,null,null,"\\\\d{9}","[028]\\\\d{8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","[02]",null],["(\\\\d)(\\\\d{4})(\\\\d{4})","$1 $2 $3","8",null]]]',
  423: '["LI","00","0","0|10(?:01|20|66)",null,null,"\\\\d{7,9}","6\\\\d{8}|[23789]\\\\d{6}",[["(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3","[23789]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","6[56]",null],["(69)(7\\\\d{2})(\\\\d{4})","$1 $2 $3","697",null]]]',
  213: '["DZ","00","0",null,null,"$NP$FG","\\\\d{8,9}","(?:[1-4]|[5-9]\\\\d)\\\\d{7}",[["([1-4]\\\\d)(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[1-4]",null],["([5-8]\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[5-8]",null],["(9\\\\d)(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","9",null]]]',
  371: '["LV","00",null,null,null,null,"\\\\d{8}","[2689]\\\\d{7}",[["([2689]\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3",null,null]]]',
  503: '["SV","00",null,null,null,null,"\\\\d{7,8}|\\\\d{11}","[267]\\\\d{7}|[89]\\\\d{6}(?:\\\\d{4})?",[["(\\\\d{4})(\\\\d{4})","$1 $2","[267]",null],["(\\\\d{3})(\\\\d{4})","$1 $2","[89]",null],["(\\\\d{3})(\\\\d{4})(\\\\d{4})","$1 $2 $3","[89]",null]]]',
  685: '["WS","0",null,null,null,null,"\\\\d{5,7}","[2-8]\\\\d{4,6}",[["(8\\\\d{2})(\\\\d{3,4})","$1 $2","8",null],["(7\\\\d)(\\\\d{5})","$1 $2","7",null],["(\\\\d{5})","$1","[2-6]",null]]]',
  880: '["BD","00","0",null,null,"$NP$FG","\\\\d{6,10}","[2-79]\\\\d{5,9}|1\\\\d{9}|8[0-7]\\\\d{4,8}",[["(2)(\\\\d{7,8})","$1-$2","2",null],["(\\\\d{2})(\\\\d{4,6})","$1-$2","[3-79]1",null],["(\\\\d{4})(\\\\d{3,6})","$1-$2","1|3(?:0|[2-58]2)|4(?:0|[25]2|3[23]|[4689][25])|5(?:[02-578]2|6[25])|6(?:[0347-9]2|[26][25])|7[02-9]2|8(?:[023][23]|[4-7]2)|9(?:[02][23]|[458]2|6[016])",null],["(\\\\d{3})(\\\\d{3,7})","$1-$2","[3-79][2-9]|8",null]]]',
  265: '["MW","00","0",null,null,"$NP$FG","\\\\d{7,9}","(?:1(?:\\\\d{2})?|[2789]\\\\d{2})\\\\d{6}",[["(\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","1",null],["(2\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","2",null],["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[1789]",null]]]',
  65: '["SG","0[0-3]\\\\d",null,null,null,null,"\\\\d{8,11}","[36]\\\\d{7}|[17-9]\\\\d{7,10}",[["([3689]\\\\d{3})(\\\\d{4})","$1 $2","[369]|8[1-9]",null],["(1[89]00)(\\\\d{3})(\\\\d{4})","$1 $2 $3","1[89]",null],["(7000)(\\\\d{4})(\\\\d{3})","$1 $2 $3","70",null],["(800)(\\\\d{3})(\\\\d{4})","$1 $2 $3","80",null]]]',
  504: '["HN","00",null,null,null,null,"\\\\d{8}","[237-9]\\\\d{7}",[["(\\\\d{4})(\\\\d{4})","$1-$2",null,null]]]',
  688: '["TV","00",null,null,null,null,"\\\\d{5,7}","[279]\\\\d{4,6}"]',
  84: '["VN","00","0",null,null,"$NP$FG","\\\\d{7,10}","[167]\\\\d{6,9}|[2-59]\\\\d{7,9}|8\\\\d{6,8}",[["([17]99)(\\\\d{4})","$1 $2","[17]99",null],["([48])(\\\\d{4})(\\\\d{4})","$1 $2 $3","4|8(?:[1-57]|6[0-79]|9[0-7])",null],["([235-7]\\\\d)(\\\\d{4})(\\\\d{3})","$1 $2 $3","2[025-79]|3[0136-9]|5[2-9]|6[0-46-8]|7[02-79]",null],["(80)(\\\\d{5})","$1 $2","80",null],["(69\\\\d)(\\\\d{4,5})","$1 $2","69",null],["([235-7]\\\\d{2})(\\\\d{4})(\\\\d{3})","$1 $2 $3","2[0-489]|3[25]|5[01]|65|7[18]",null],["([89]\\\\d)(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","8(?:68|8|9[89])|9",null],["(1[2689]\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","1(?:[26]|8[68]|99)",null],["(1[89]00)(\\\\d{4,6})","$1 $2","1[89]0","$FG"]]]',
  255: '["TZ","00[056]","0",null,null,"$NP$FG","\\\\d{7,9}","\\\\d{9}",[["([24]\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","[24]",null],["([67]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[67]",null],["([89]\\\\d{2})(\\\\d{2})(\\\\d{4})","$1 $2 $3","[89]",null]]]',
  222: '["MR","00",null,null,null,null,"\\\\d{8}","[2-48]\\\\d{7}",[["([2-48]\\\\d)(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]',
  230: '["MU","0(?:0|[2-7]0|33)",null,null,null,null,"\\\\d{7,8}","[2-9]\\\\d{6,7}",[["([2-46-9]\\\\d{2})(\\\\d{4})","$1 $2","[2-46-9]",null],["(5\\\\d{3})(\\\\d{4})","$1 $2","5",null]]]',
  592: '["GY","001",null,null,null,null,"\\\\d{7}","[2-46-9]\\\\d{6}",[["(\\\\d{3})(\\\\d{4})","$1 $2",null,null]]]',
  41: '["CH","00","0",null,null,"$NP$FG","\\\\d{9}(?:\\\\d{3})?","[2-9]\\\\d{8}|860\\\\d{9}",[["([2-9]\\\\d)(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[2-7]|[89]1",null],["([89]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","8[047]|90",null],["(\\\\d{3})(\\\\d{2})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4 $5","860",null]]]',
  39: [
    '["IT","00",null,null,null,null,"\\\\d{6,11}","[01589]\\\\d{5,10}|3(?:[12457-9]\\\\d{8}|[36]\\\\d{7,9})",[["(\\\\d{2})(\\\\d{3,4})(\\\\d{4})","$1 $2 $3","0[26]|55",null],["(0[26])(\\\\d{4})(\\\\d{5})","$1 $2 $3","0[26]",null],["(0[26])(\\\\d{4,6})","$1 $2","0[26]",null],["(0\\\\d{2})(\\\\d{3,4})(\\\\d{4})","$1 $2 $3","0[13-57-9][0159]",null],["(\\\\d{3})(\\\\d{3,6})","$1 $2","0[13-57-9][0159]|8(?:03|4[17]|9[245])",null],["(0\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","0[13-57-9][2-46-8]",null],["(0\\\\d{3})(\\\\d{2,6})","$1 $2","0[13-57-9][2-46-8]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[13]|8(?:00|4[08]|9[59])",null],["(\\\\d{4})(\\\\d{4})","$1 $2","894",null],["(\\\\d{3})(\\\\d{4})(\\\\d{4})","$1 $2 $3","3",null]]]',
    '["VA","00",null,null,null,null,"\\\\d{6,11}","(?:0(?:878\\\\d{5}|6698\\\\d{5})|[1589]\\\\d{5,10}|3(?:[12457-9]\\\\d{8}|[36]\\\\d{7,9}))"]',
  ],
  993: '["TM","810","8",null,null,"($NP $FG)","\\\\d{8}","[1-6]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2-$3-$4","12",null],["(\\\\d{2})(\\\\d{6})","$1 $2","6","$NP $FG"],["(\\\\d{3})(\\\\d)(\\\\d{2})(\\\\d{2})","$1 $2-$3-$4","13|[2-5]",null]]]',
  888: '["001",null,null,null,null,null,"\\\\d{11}","\\\\d{11}",[["(\\\\d{3})(\\\\d{3})(\\\\d{5})","$1 $2 $3",null,null]]]',
  353: '["IE","00","0",null,null,"($NP$FG)","\\\\d{5,10}","[124-9]\\\\d{6,9}",[["(1)(\\\\d{3,4})(\\\\d{4})","$1 $2 $3","1",null],["(\\\\d{2})(\\\\d{5})","$1 $2","2[24-9]|47|58|6[237-9]|9[35-9]",null],["(\\\\d{3})(\\\\d{5})","$1 $2","40[24]|50[45]",null],["(48)(\\\\d{4})(\\\\d{4})","$1 $2 $3","48",null],["(818)(\\\\d{3})(\\\\d{3})","$1 $2 $3","81",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[24-69]|7[14]",null],["([78]\\\\d)(\\\\d{3,4})(\\\\d{4})","$1 $2 $3","76|8[35-9]","$NP$FG"],["(700)(\\\\d{3})(\\\\d{3})","$1 $2 $3","70","$NP$FG"],["(\\\\d{4})(\\\\d{3})(\\\\d{3})","$1 $2 $3","1(?:8[059]|5)","$FG"]]]',
  966: '["SA","00","0",null,null,"$NP$FG","\\\\d{7,10}","1\\\\d{7,8}|(?:[2-467]|92)\\\\d{7}|5\\\\d{8}|8\\\\d{9}",[["([1-467])(\\\\d{3})(\\\\d{4})","$1 $2 $3","[1-467]",null],["(1\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","1[1-467]",null],["(5\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","5",null],["(92\\\\d{2})(\\\\d{5})","$1 $2","92","$FG"],["(800)(\\\\d{3})(\\\\d{4})","$1 $2 $3","80","$FG"],["(811)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","81",null]]]',
  380: '["UA","00","0",null,null,"$NP$FG","\\\\d{5,9}","[3-9]\\\\d{8}",[["([3-9]\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","[38]9|4(?:[45][0-5]|87)|5(?:0|6[37]|7[37])|6[36-8]|7|9[1-9]",null],["([3-689]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","3[1-8]2|4[13678]2|5(?:[12457]2|6[24])|6(?:[49]2|[12][29]|5[24])|8[0-8]|90",null],["([3-6]\\\\d{3})(\\\\d{5})","$1 $2","3(?:5[013-9]|[1-46-8])|4(?:[137][013-9]|6|[45][6-9]|8[4-6])|5(?:[1245][013-9]|6[0135-9]|3|7[4-6])|6(?:[49][013-9]|5[0135-9]|[12][13-8])",null]]]',
  98: '["IR","00","0",null,null,"$NP$FG","\\\\d{4,10}","[1-8]\\\\d{9}|9(?:[0-4]\\\\d{8}|9\\\\d{2,8})",[["(21)(\\\\d{3,5})","$1 $2","21",null],["(\\\\d{2})(\\\\d{4})(\\\\d{4})","$1 $2 $3","[1-8]",null],["(\\\\d{3})(\\\\d{3})","$1 $2","9",null],["(\\\\d{3})(\\\\d{2})(\\\\d{2,3})","$1 $2 $3","9",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","9",null]]]',
  971: '["AE","00","0",null,null,"$NP$FG","\\\\d{5,12}","[2-79]\\\\d{7,8}|800\\\\d{2,9}",[["([2-4679])(\\\\d{3})(\\\\d{4})","$1 $2 $3","[2-4679][2-8]",null],["(5\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","5",null],["([479]00)(\\\\d)(\\\\d{5})","$1 $2 $3","[479]0","$FG"],["([68]00)(\\\\d{2,9})","$1 $2","60|8","$FG"]]]',
  30: '["GR","00",null,null,null,null,"\\\\d{10}","[26-9]\\\\d{9}",[["([27]\\\\d)(\\\\d{4})(\\\\d{4})","$1 $2 $3","21|7",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","2[2-9]1|[689]",null],["(2\\\\d{3})(\\\\d{6})","$1 $2","2[2-9][02-9]",null]]]',
  228: '["TG","00",null,null,null,null,"\\\\d{8}","[29]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[29]",null]]]',
  48: '["PL","00",null,null,null,null,"\\\\d{6,9}","[12]\\\\d{6,8}|[3-57-9]\\\\d{8}|6\\\\d{5,8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[14]|2[0-57-9]|3[2-4]|5[24-689]|6[1-3578]|7[14-7]|8[1-79]|9[145]",null],["(\\\\d{2})(\\\\d{1})(\\\\d{4})","$1 $2 $3","[12]2",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","26|39|5[0137]|6[0469]|7[02389]|8[08]",null],["(\\\\d{3})(\\\\d{2})(\\\\d{2,3})","$1 $2 $3","64",null],["(\\\\d{3})(\\\\d{3})","$1 $2","64",null]]]',
  886: '["TW","0(?:0[25679]|19)","0",null,null,"$NP$FG","\\\\d{7,10}","2\\\\d{6,8}|[3-689]\\\\d{7,8}|7\\\\d{7,9}",[["(20)(\\\\d)(\\\\d{4})","$1 $2 $3","202",null],["(20)(\\\\d{3})(\\\\d{4})","$1 $2 $3","20[013-9]",null],["([2-8])(\\\\d{3,4})(\\\\d{4})","$1 $2 $3","2[23-8]|[3-6]|[78][1-9]",null],["([89]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","80|9",null],["(70)(\\\\d{4})(\\\\d{4})","$1 $2 $3","70",null]]]',
  212: [
    '["MA","00","0",null,null,"$NP$FG","\\\\d{9}","[5-9]\\\\d{8}",[["([5-7]\\\\d{2})(\\\\d{6})","$1-$2","5(?:2[015-7]|3[0-4])|[67]",null],["([58]\\\\d{3})(\\\\d{5})","$1-$2","5(?:2[2-489]|3[5-9]|92)|892",null],["(5\\\\d{4})(\\\\d{4})","$1-$2","5(?:29|38)",null],["([5]\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","5(?:4[067]|5[03])",null],["(8[09])(\\\\d{7})","$1-$2","8(?:0|9[013-9])",null]]]',
    '["EH","00","0",null,null,"$NP$FG","\\\\d{9}","[5-9]\\\\d{8}"]',
  ],
  372: '["EE","00",null,null,null,null,"\\\\d{4,10}","1\\\\d{3,4}|[3-9]\\\\d{6,7}|800\\\\d{6,7}",[["([3-79]\\\\d{2})(\\\\d{4})","$1 $2","[369]|4[3-8]|5(?:[0-2]|5[0-478]|6[45])|7[1-9]",null],["(70)(\\\\d{2})(\\\\d{4})","$1 $2 $3","70",null],["(8000)(\\\\d{3})(\\\\d{3})","$1 $2 $3","800",null],["([458]\\\\d{3})(\\\\d{3,4})","$1 $2","40|5|8(?:00|[1-5])",null]]]',
  598: '["UY","0(?:1[3-9]\\\\d|0)","0",null,null,null,"\\\\d{7,8}","[2489]\\\\d{6,7}",[["(\\\\d{4})(\\\\d{4})","$1 $2","[24]",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","9[1-9]","$NP$FG"],["(\\\\d{3})(\\\\d{4})","$1 $2","[89]0","$NP$FG"]]]',
  502: '["GT","00",null,null,null,null,"\\\\d{8}(?:\\\\d{3})?","[2-7]\\\\d{7}|1[89]\\\\d{9}",[["(\\\\d{4})(\\\\d{4})","$1 $2","[2-7]",null],["(\\\\d{4})(\\\\d{3})(\\\\d{4})","$1 $2 $3","1",null]]]',
  82: '["KR","00(?:[124-68]|3\\\\d{2}|7(?:[0-8]\\\\d|9[0-79]))","0","0(8[1-46-8]|85\\\\d{2})?",null,"$NP$FG","\\\\d{3,14}","007\\\\d{9,11}|[1-7]\\\\d{3,9}|8\\\\d{8}",[["(\\\\d{5})(\\\\d{3,4})(\\\\d{4})","$1 $2 $3","00798","$FG","NA"],["(\\\\d{5})(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3 $4","00798","$FG","NA"],["(\\\\d{2})(\\\\d{4})(\\\\d{4})","$1-$2-$3","1(?:0|1[19]|[69]9|5[458])|[57]0",null],["(\\\\d{2})(\\\\d{3,4})(\\\\d{4})","$1-$2-$3","1(?:[01]|5[1-4]|6[2-8]|[7-9])|[68]0|[3-6][1-9][1-9]",null],["(\\\\d{3})(\\\\d)(\\\\d{4})","$1-$2-$3","131",null],["(\\\\d{3})(\\\\d{2})(\\\\d{4})","$1-$2-$3","131",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1-$2-$3","13[2-9]",null],["(\\\\d{2})(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1-$2-$3-$4","30",null],["(\\\\d)(\\\\d{3,4})(\\\\d{4})","$1-$2-$3","2[1-9]",null],["(\\\\d)(\\\\d{3,4})","$1-$2","21[0-46-9]",null],["(\\\\d{2})(\\\\d{3,4})","$1-$2","[3-6][1-9]1",null],["(\\\\d{4})(\\\\d{4})","$1-$2","1(?:5[246-9]|6[04678]|8[03579])","$FG"]]]',
  253: '["DJ","00",null,null,null,null,"\\\\d{8}","[27]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]',
  91: '["IN","00","0",null,null,"$NP$FG","\\\\d{6,13}","008\\\\d{9}|1\\\\d{7,12}|[2-9]\\\\d{9,10}",[["(\\\\d{5})(\\\\d{5})","$1 $2","600|7(?:[02-8]|19|9[037-9])|8(?:0[015-9]|[1-9]|20)|9",null],["(\\\\d{2})(\\\\d{4})(\\\\d{4})","$1 $2 $3","11|2[02]|33|4[04]|79[1-9]|80[2-46]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","1(?:2[0-249]|3[0-25]|4[145]|[59][14]|7[1257]|[68][1-9])|2(?:1[257]|3[013]|4[01]|5[0137]|6[0158]|78|8[1568]|9[14])|3(?:26|4[1-3]|5[34]|6[01489]|7[02-46]|8[159])|4(?:1[36]|2[1-47]|3[15]|5[12]|6[0-26-9]|7[0-24-9]|8[013-57]|9[014-7])|5(?:1[025]|[36][25]|22|4[28]|5[12]|[78]1|9[15])|6(?:12|[2-4]1|5[17]|6[13]|7[14]|80)|7(?:12|2[14]|3[134]|4[47]|5[15]|[67]1|88)|8(?:16|2[014]|3[126]|6[136]|7[078]|8[34]|91)",null],["(\\\\d{4})(\\\\d{3})(\\\\d{3})","$1 $2 $3","1(?:[23579]|[468][1-9])|[2-8]",null],["(\\\\d{2})(\\\\d{3})(\\\\d{4})(\\\\d{3})","$1 $2 $3 $4","008",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","140","$FG"],["(\\\\d{4})(\\\\d{2})(\\\\d{4})","$1 $2 $3","160","$FG"],["(\\\\d{4})(\\\\d{4,5})","$1 $2","180","$FG"],["(\\\\d{4})(\\\\d{2,4})(\\\\d{4})","$1 $2 $3","180","$FG"],["(\\\\d{4})(\\\\d{3,4})(\\\\d{4})","$1 $2 $3","186","$FG"],["(\\\\d{4})(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","18[06]","$FG"]]]',
  389: '["MK","00","0",null,null,"$NP$FG","\\\\d{6,8}","[2-578]\\\\d{7}",[["(2)(\\\\d{3})(\\\\d{4})","$1 $2 $3","2",null],["([347]\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","[347]",null],["([58]\\\\d{2})(\\\\d)(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[58]",null]]]',
  1: [
    '["US","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[2-9]\\\\d{9}",[["(\\\\d{3})(\\\\d{4})","$1-$2",null,null,"NA"],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","($1) $2-$3",null,null,"$1-$2-$3"]]]',
    '["AI","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[2589]\\\\d{9}"]',
    '["AS","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[5689]\\\\d{9}"]',
    '["BB","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[2589]\\\\d{9}"]',
    '["BM","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[4589]\\\\d{9}"]',
    '["BS","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[2589]\\\\d{9}"]',
    '["CA","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[2-9]\\\\d{9}|3\\\\d{6}"]',
    '["DM","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[57-9]\\\\d{9}"]',
    '["DO","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[589]\\\\d{9}"]',
    '["GD","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[4589]\\\\d{9}"]',
    '["GU","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[5689]\\\\d{9}"]',
    '["JM","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[589]\\\\d{9}"]',
    '["KN","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[589]\\\\d{9}"]',
    '["KY","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[3589]\\\\d{9}"]',
    '["LC","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[5789]\\\\d{9}"]',
    '["MP","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[5689]\\\\d{9}"]',
    '["MS","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[5689]\\\\d{9}"]',
    '["PR","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[5789]\\\\d{9}"]',
    '["SX","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[5789]\\\\d{9}"]',
    '["TC","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[5689]\\\\d{9}"]',
    '["TT","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[589]\\\\d{9}"]',
    '["AG","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[2589]\\\\d{9}"]',
    '["VC","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[5789]\\\\d{9}"]',
    '["VG","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[2589]\\\\d{9}"]',
    '["VI","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[3589]\\\\d{9}"]',
  ],
  60: '["MY","00","0",null,null,null,"\\\\d{6,10}","[13-9]\\\\d{7,9}",[["([4-79])(\\\\d{3})(\\\\d{4})","$1-$2 $3","[4-79]","$NP$FG"],["(3)(\\\\d{4})(\\\\d{4})","$1-$2 $3","3","$NP$FG"],["([18]\\\\d)(\\\\d{3})(\\\\d{3,4})","$1-$2 $3","1[02-46-9][1-9]|8","$NP$FG"],["(1)([36-8]00)(\\\\d{2})(\\\\d{4})","$1-$2-$3-$4","1[36-8]0",null],["(11)(\\\\d{4})(\\\\d{4})","$1-$2 $3","11","$NP$FG"],["(15[49])(\\\\d{3})(\\\\d{4})","$1-$2 $3","15","$NP$FG"]]]',
  355: '["AL","00","0",null,null,"$NP$FG","\\\\d{5,9}","[2-57]\\\\d{7}|6\\\\d{8}|8\\\\d{5,7}|9\\\\d{5}",[["(4)(\\\\d{3})(\\\\d{4})","$1 $2 $3","4[0-6]",null],["(6\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","6",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[2358][2-5]|4[7-9]",null],["(\\\\d{3})(\\\\d{3,5})","$1 $2","[235][16-9]|8[016-9]|[79]",null]]]',
  254: '["KE","000","0","005|0",null,"$NP$FG","\\\\d{7,10}","20\\\\d{6,7}|[4-9]\\\\d{6,9}",[["(\\\\d{2})(\\\\d{5,7})","$1 $2","[24-6]",null],["(\\\\d{3})(\\\\d{6})","$1 $2","7",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[89]",null]]]',
  223: '["ML","00",null,null,null,null,"\\\\d{8}","[246-9]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[246-9]",null],["(\\\\d{4})","$1","67|74",null,"NA"]]]',
  686: '["KI","00",null,"0",null,null,"\\\\d{5,8}","[2458]\\\\d{4}|3\\\\d{4,7}|7\\\\d{7}"]',
  994: '["AZ","00","0",null,null,"($NP$FG)","\\\\d{7,9}","[1-9]\\\\d{8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","(?:1[28]|2(?:[45]2|[0-36])|365)",null],["(\\\\d{2})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[4-8]","$NP$FG"],["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","9","$NP$FG"]]]',
  979: '["001",null,null,null,null,null,"\\\\d{9}","\\\\d{9}",[["(\\\\d)(\\\\d{4})(\\\\d{4})","$1 $2 $3",null,null]]]',
  66: '["TH","00","0",null,null,"$NP$FG","\\\\d{4}|\\\\d{8,10}","[2-9]\\\\d{7,8}|1\\\\d{3}(?:\\\\d{5,6})?",[["(2)(\\\\d{3})(\\\\d{4})","$1 $2 $3","2",null],["([13-9]\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","14|[3-9]",null],["(1[89]00)(\\\\d{3})(\\\\d{3})","$1 $2 $3","1","$FG"]]]',
  233: '["GH","00","0",null,null,"$NP$FG","\\\\d{7,9}","[235]\\\\d{8}|8\\\\d{7}",[["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","[235]",null],["(\\\\d{3})(\\\\d{5})","$1 $2","8",null]]]',
  593: '["EC","00","0",null,null,"($NP$FG)","\\\\d{7,11}","1\\\\d{9,10}|[2-8]\\\\d{7}|9\\\\d{8}",[["(\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2-$3","[247]|[356][2-8]",null,"$1-$2-$3"],["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","9","$NP$FG"],["(1800)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","1","$FG"]]]',
  509: '["HT","00",null,null,null,null,"\\\\d{8}","[2-489]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{4})","$1 $2 $3",null,null]]]',
  54: '["AR","00","0","0?(?:(11|2(?:2(?:02?|[13]|2[13-79]|4[1-6]|5[2457]|6[124-8]|7[1-4]|8[13-6]|9[1267])|3(?:02?|1[467]|2[03-6]|3[13-8]|[49][2-6]|5[2-8]|[67])|4(?:7[3-578]|9)|6(?:[0136]|2[24-6]|4[6-8]?|5[15-8])|80|9(?:0[1-3]|[19]|2\\\\d|3[1-6]|4[02568]?|5[2-4]|6[2-46]|72?|8[23]?))|3(?:3(?:2[79]|6|8[2578])|4(?:0[0-24-9]|[12]|3[5-8]?|4[24-7]|5[4-68]?|6[02-9]|7[126]|8[2379]?|9[1-36-8])|5(?:1|2[1245]|3[237]?|4[1-46-9]|6[2-4]|7[1-6]|8[2-5]?)|6[24]|7(?:[069]|1[1568]|2[15]|3[145]|4[13]|5[14-8]|7[2-57]|8[126])|8(?:[01]|2[15-7]|3[2578]?|4[13-6]|5[4-8]?|6[1-357-9]|7[36-8]?|8[5-8]?|9[124])))?15)?","9$1","$NP$FG","\\\\d{6,11}","11\\\\d{8}|[2368]\\\\d{9}|9\\\\d{10}",[["([68]\\\\d{2})(\\\\d{3})(\\\\d{4})","$1-$2-$3","[68]",null],["(\\\\d{2})(\\\\d{4})","$1-$2","[2-9]","$FG","NA"],["(\\\\d{3})(\\\\d{4})","$1-$2","[2-9]","$FG","NA"],["(\\\\d{4})(\\\\d{4})","$1-$2","[2-9]","$FG","NA"],["(9)(11)(\\\\d{4})(\\\\d{4})","$2 15-$3-$4","911",null,"$1 $2 $3-$4"],["(9)(\\\\d{3})(\\\\d{3})(\\\\d{4})","$2 15-$3-$4","9(?:2[234689]|3[3-8])",null,"$1 $2 $3-$4"],["(9)(\\\\d{4})(\\\\d{2})(\\\\d{4})","$2 15-$3-$4","9[23]",null,"$1 $2 $3-$4"],["(11)(\\\\d{4})(\\\\d{4})","$1 $2-$3","1",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2-$3","2(?:2[013]|3[067]|49|6[01346]|80|9[147-9])|3(?:36|4[1-358]|5[138]|6[24]|7[069]|8[013578])",null],["(\\\\d{4})(\\\\d{2})(\\\\d{4})","$1 $2-$3","[23]",null],["(\\\\d{3})","$1","1[012]|911","$FG","NA"]]]',
  57: '["CO","00(?:4(?:[14]4|56)|[579])","0","0([3579]|4(?:44|56))?",null,null,"\\\\d{7,11}","(?:[13]\\\\d{0,3}|[24-8])\\\\d{7}",[["(\\\\d)(\\\\d{7})","$1 $2","1(?:8[2-9]|9[0-3]|[2-7])|[24-8]","($FG)"],["(\\\\d{3})(\\\\d{7})","$1 $2","3",null],["(1)(\\\\d{3})(\\\\d{7})","$1-$2-$3","1(?:80|9[04])","$NP$FG","$1 $2 $3"]]]',
  597: '["SR","00",null,null,null,null,"\\\\d{6,7}","[2-8]\\\\d{5,6}",[["(\\\\d{3})(\\\\d{3})","$1-$2","[2-4]|5[2-58]",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1-$2-$3","56",null],["(\\\\d{3})(\\\\d{4})","$1-$2","[6-8]",null]]]',
  676: '["TO","00",null,null,null,null,"\\\\d{5,7}","[02-8]\\\\d{4,6}",[["(\\\\d{2})(\\\\d{3})","$1-$2","[1-6]|7[0-4]|8[05]",null],["(\\\\d{3})(\\\\d{4})","$1 $2","7[5-9]|8[47-9]",null],["(\\\\d{4})(\\\\d{3})","$1 $2","0",null]]]',
  505: '["NI","00",null,null,null,null,"\\\\d{8}","[12578]\\\\d{7}",[["(\\\\d{4})(\\\\d{4})","$1 $2",null,null]]]',
  850: '["KP","00|99","0",null,null,"$NP$FG","\\\\d{6,8}|\\\\d{10}","1\\\\d{9}|[28]\\\\d{7}",[["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","1",null],["(\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","2",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","8",null]]]',
  7: [
    '["RU","810","8",null,null,"$NP ($FG)","\\\\d{10}","[3489]\\\\d{9}",[["(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1-$2-$3","[1-79]","$FG","NA"],["([3489]\\\\d{2})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2-$3-$4","[34689]",null],["(7\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","7",null]]]',
    '["KZ","810","8",null,null,null,"\\\\d{10}","(?:33\\\\d|7\\\\d{2}|80[09])\\\\d{7}"]',
  ],
  268: '["SZ","00",null,null,null,null,"\\\\d{8}","[027]\\\\d{7}",[["(\\\\d{4})(\\\\d{4})","$1 $2","[027]",null]]]',
  501: '["BZ","00",null,null,null,null,"\\\\d{7}(?:\\\\d{4})?","[2-8]\\\\d{6}|0\\\\d{10}",[["(\\\\d{3})(\\\\d{4})","$1-$2","[2-8]",null],["(0)(800)(\\\\d{4})(\\\\d{3})","$1-$2-$3-$4","0",null]]]',
  252: '["SO","00","0",null,null,null,"\\\\d{6,9}","[1-9]\\\\d{5,8}",[["(\\\\d{6})","$1","[134]",null],["(\\\\d)(\\\\d{6})","$1 $2","2[0-79]|[13-5]",null],["(\\\\d)(\\\\d{7})","$1 $2","24|[67]",null],["(\\\\d{2})(\\\\d{4})","$1 $2","8[125]",null],["(\\\\d{2})(\\\\d{5,7})","$1 $2","15|28|6[1-35-9]|799|9[2-9]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","3[59]|4[89]|6[24-6]|79|8[08]|90",null]]]',
  229: '["BJ","00",null,null,null,null,"\\\\d{4,8}","[2689]\\\\d{7}|7\\\\d{3}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]',
  680: '["PW","01[12]",null,null,null,null,"\\\\d{7}","[2-8]\\\\d{6}",[["(\\\\d{3})(\\\\d{4})","$1 $2",null,null]]]',
  263: '["ZW","00","0",null,null,"$NP$FG","\\\\d{3,10}","2(?:[012457-9]\\\\d{3,8}|6(?:[14]\\\\d{7}|\\\\d{4}))|[13-79]\\\\d{4,9}|8[06]\\\\d{8}",[["([49])(\\\\d{3})(\\\\d{2,4})","$1 $2 $3","4|9[2-9]",null],["(7\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","7",null],["(86\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","86[24]",null],["([2356]\\\\d{2})(\\\\d{3,5})","$1 $2","2(?:0[45]|2[278]|[49]8|[78])|3(?:08|17|3[78]|7[1569]|8[37]|98)|5[15][78]|6(?:[29]8|[38]7|6[78]|75|[89]8)",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","2(?:1[39]|2[0157]|6[14]|7[35]|84)|329",null],["([1-356]\\\\d)(\\\\d{3,5})","$1 $2","1[3-9]|2[0569]|3[0-69]|5[05689]|6[0-46-9]",null],["([235]\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[23]9|54",null],["([25]\\\\d{3})(\\\\d{3,5})","$1 $2","(?:25|54)8",null],["(8\\\\d{3})(\\\\d{6})","$1 $2","86",null],["(80\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","80",null]]]',
  90: '["TR","00","0",null,null,null,"\\\\d{7,10}","[2-589]\\\\d{9}|444\\\\d{4}",[["(\\\\d{3})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[23]|4(?:[0-35-9]|4[0-35-9])","($NP$FG)"],["(\\\\d{3})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","5[02-69]","$NP$FG"],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","51|[89]","$NP$FG"],["(444)(\\\\d{1})(\\\\d{3})","$1 $2 $3","444",null]]]',
  352: '["LU","00",null,"(15(?:0[06]|1[12]|35|4[04]|55|6[26]|77|88|99)\\\\d)",null,null,"\\\\d{4,11}","[24-9]\\\\d{3,10}|3(?:[0-46-9]\\\\d{2,9}|5[013-9]\\\\d{1,8})",[["(\\\\d{2})(\\\\d{3})","$1 $2","[2-5]|7[1-9]|[89](?:[1-9]|0[2-9])",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3","[2-5]|7[1-9]|[89](?:[1-9]|0[2-9])",null],["(\\\\d{2})(\\\\d{2})(\\\\d{3})","$1 $2 $3","20",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{1,2})","$1 $2 $3 $4","2(?:[0367]|4[3-8])",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{3})","$1 $2 $3 $4","20",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{1,2})","$1 $2 $3 $4 $5","2(?:[0367]|4[3-8])",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{1,4})","$1 $2 $3 $4","2(?:[12589]|4[12])|[3-5]|7[1-9]|8(?:[1-9]|0[2-9])|9(?:[1-9]|0[2-46-9])",null],["(\\\\d{3})(\\\\d{2})(\\\\d{3})","$1 $2 $3","70|80[01]|90[015]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","6",null]]]',
  47: [
    '["NO","00",null,null,null,null,"\\\\d{5}(?:\\\\d{3})?","0\\\\d{4}|[2-9]\\\\d{7}",[["([489]\\\\d{2})(\\\\d{2})(\\\\d{3})","$1 $2 $3","[489]",null],["([235-7]\\\\d)(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[235-7]",null]]]',
    '["SJ","00",null,null,null,null,"\\\\d{5}(?:\\\\d{3})?","0\\\\d{4}|[45789]\\\\d{7}"]',
  ],
  243: '["CD","00","0",null,null,"$NP$FG","\\\\d{7,9}","[2-6]\\\\d{6}|[18]\\\\d{6,8}|9\\\\d{8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","12",null],["([89]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","8[0-2459]|9",null],["(\\\\d{2})(\\\\d{2})(\\\\d{3})","$1 $2 $3","88",null],["(\\\\d{2})(\\\\d{5})","$1 $2","[1-6]",null]]]',
  220: '["GM","00",null,null,null,null,"\\\\d{7}","[2-9]\\\\d{6}",[["(\\\\d{3})(\\\\d{4})","$1 $2",null,null]]]',
  687: '["NC","00",null,null,null,null,"\\\\d{6}","[2-57-9]\\\\d{5}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1.$2.$3","[2-46-9]|5[0-4]",null]]]',
  995: '["GE","00","0",null,null,null,"\\\\d{6,9}","[34578]\\\\d{8}",[["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[348]","$NP$FG"],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","7","$NP$FG"],["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","5","$FG"]]]',
  961: '["LB","00","0",null,null,null,"\\\\d{7,8}","[13-9]\\\\d{6,7}",[["(\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","[13-6]|7(?:[2-57]|62|8[0-7]|9[04-9])|8[02-9]|9","$NP$FG"],["([7-9]\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","[89][01]|7(?:[01]|6[013-9]|8[89]|9[1-3])",null]]]',
  40: '["RO","00","0",null,null,"$NP$FG","\\\\d{6,9}","[23]\\\\d{5,8}|[7-9]\\\\d{8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","[23]1",null],["(\\\\d{2})(\\\\d{4})","$1 $2","[23]1",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[23][3-7]|[7-9]",null],["(2\\\\d{2})(\\\\d{3})","$1 $2","2[3-6]",null]]]',
  232: '["SL","00","0",null,null,"($NP$FG)","\\\\d{6,8}","[2-9]\\\\d{7}",[["(\\\\d{2})(\\\\d{6})","$1 $2",null,null]]]',
  594: '["GF","00","0",null,null,"$NP$FG","\\\\d{9}","[56]\\\\d{8}",[["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]',
  976: '["MN","001","0",null,null,"$NP$FG","\\\\d{6,10}","[12]\\\\d{7,9}|[57-9]\\\\d{7}",[["([12]\\\\d)(\\\\d{2})(\\\\d{4})","$1 $2 $3","[12]1",null],["([12]2\\\\d)(\\\\d{5,6})","$1 $2","[12]2[1-3]",null],["([12]\\\\d{3})(\\\\d{5})","$1 $2","[12](?:27|[3-5])",null],["(\\\\d{4})(\\\\d{4})","$1 $2","[57-9]","$FG"],["([12]\\\\d{4})(\\\\d{4,5})","$1 $2","[12](?:27|[3-5])",null]]]',
  20: '["EG","00","0",null,null,"$NP$FG","\\\\d{5,10}","1\\\\d{4,9}|[2456]\\\\d{8}|3\\\\d{7}|[89]\\\\d{8,9}",[["(\\\\d)(\\\\d{7,8})","$1 $2","[23]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","1[012]|[89]00",null],["(\\\\d{2})(\\\\d{6,7})","$1 $2","1[35]|[4-6]|[89][2-9]",null]]]',
  689: '["PF","00",null,null,null,null,"\\\\d{6}(?:\\\\d{2})?","4\\\\d{5,7}|8\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","4[09]|8[79]",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3","44",null]]]',
  56: '["CL","(?:0|1(?:1[0-69]|2[0-57]|5[13-58]|69|7[0167]|8[018]))0","0","0|(1(?:1[0-69]|2[0-57]|5[13-58]|69|7[0167]|8[018]))",null,"$NP$FG","\\\\d{7,11}","(?:[2-9]|600|123)\\\\d{7,8}",[["(\\\\d)(\\\\d{4})(\\\\d{4})","$1 $2 $3","2[23]","($FG)"],["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","[357]|4[1-35]|6[13-57]","($FG)"],["(9)(\\\\d{4})(\\\\d{4})","$1 $2 $3","9",null],["(44)(\\\\d{3})(\\\\d{4})","$1 $2 $3","44",null],["([68]00)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","60|8","$FG"],["(600)(\\\\d{3})(\\\\d{2})(\\\\d{3})","$1 $2 $3 $4","60","$FG"],["(1230)(\\\\d{3})(\\\\d{4})","$1 $2 $3","1","$FG"],["(\\\\d{5})(\\\\d{4})","$1 $2","219","($FG)"],["(\\\\d{4,5})","$1","[1-9]","$FG","NA"]]]',
  596: '["MQ","00","0",null,null,"$NP$FG","\\\\d{9}","[56]\\\\d{8}",[["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]',
  508: '["PM","00","0",null,null,"$NP$FG","\\\\d{6}","[45]\\\\d{5}",[["([45]\\\\d)(\\\\d{2})(\\\\d{2})","$1 $2 $3",null,null]]]',
  269: '["KM","00",null,null,null,null,"\\\\d{7}","[3478]\\\\d{6}",[["(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3",null,null]]]',
  358: [
    '["FI","00|99(?:[02469]|5(?:11|33|5[59]|88|9[09]))","0",null,null,"$NP$FG","\\\\d{5,12}","1\\\\d{4,11}|[2-9]\\\\d{4,10}",[["(\\\\d{3})(\\\\d{3,7})","$1 $2","(?:[1-3]00|[6-8]0)",null],["(116\\\\d{3})","$1","116","$FG"],["(\\\\d{2})(\\\\d{4,10})","$1 $2","[14]|2[09]|50|7[135]",null],["(\\\\d)(\\\\d{4,11})","$1 $2","[25689][1-8]|3",null]]]',
    '["AX","00|99(?:[02469]|5(?:11|33|5[59]|88|9[09]))","0",null,null,"$NP$FG","\\\\d{5,12}","1\\\\d{5,11}|[35]\\\\d{5,9}|[27]\\\\d{4,9}|4\\\\d{5,10}|6\\\\d{7,9}|8\\\\d{6,9}"]',
  ],
  251: '["ET","00","0",null,null,"$NP$FG","\\\\d{7,9}","[1-59]\\\\d{8}",[["([1-59]\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3",null,null]]]',
  681: '["WF","00",null,null,null,null,"\\\\d{6}","[4-8]\\\\d{5}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3",null,null]]]',
  853: '["MO","00",null,null,null,null,"\\\\d{8}","[268]\\\\d{7}",[["([268]\\\\d{3})(\\\\d{4})","$1 $2",null,null]]]',
  44: [
    '["GB","00","0",null,null,"$NP$FG","\\\\d{4,10}","\\\\d{7,10}",[["(7\\\\d{3})(\\\\d{6})","$1 $2","7(?:[1-5789]|62)",null],["(\\\\d{2})(\\\\d{4})(\\\\d{4})","$1 $2 $3","2|5[56]|7[06]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","1(?:1|\\\\d1)|3|9[018]",null],["(\\\\d{5})(\\\\d{4,5})","$1 $2","1(?:38|5[23]|69|76|94)",null],["(1\\\\d{3})(\\\\d{5,6})","$1 $2","1",null],["(800)(\\\\d{4})","$1 $2","800",null],["(845)(46)(4\\\\d)","$1 $2 $3","845",null],["(8\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","8(?:4[2-5]|7[0-3])",null],["(80\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","80",null],["([58]00)(\\\\d{6})","$1 $2","[58]00",null]]]',
    '["GG","00","0",null,null,"$NP$FG","\\\\d{6,10}","[135789]\\\\d{6,9}"]',
    '["IM","00","0",null,null,"$NP$FG","\\\\d{6,10}","[135789]\\\\d{6,9}"]',
    '["JE","00","0",null,null,"$NP$FG","\\\\d{6,10}","[135789]\\\\d{6,9}"]',
  ],
  244: '["AO","00",null,null,null,null,"\\\\d{9}","[29]\\\\d{8}",[["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3",null,null]]]',
  211: '["SS","00","0",null,null,null,"\\\\d{9}","[19]\\\\d{8}",[["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3",null,"$NP$FG"]]]',
  373: '["MD","00","0",null,null,"$NP$FG","\\\\d{8}","[235-9]\\\\d{7}",[["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","22|3",null],["([25-7]\\\\d{2})(\\\\d{2})(\\\\d{3})","$1 $2 $3","2[13-9]|[5-7]",null],["([89]\\\\d{2})(\\\\d{5})","$1 $2","[89]",null]]]',
  996: '["KG","00","0",null,null,"$NP$FG","\\\\d{5,10}","[235-8]\\\\d{8,9}",[["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[25-7]|31[25]",null],["(\\\\d{4})(\\\\d{5})","$1 $2","3(?:1[36]|[2-9])",null],["(\\\\d{3})(\\\\d{3})(\\\\d)(\\\\d{3})","$1 $2 $3 $4","8",null]]]',
  93: '["AF","00","0",null,null,"$NP$FG","\\\\d{7,9}","[2-7]\\\\d{8}",[["([2-7]\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","[2-7]",null]]]',
  260: '["ZM","00","0",null,null,"$NP$FG","\\\\d{9}","[289]\\\\d{8}",[["([29]\\\\d)(\\\\d{7})","$1 $2","[29]",null],["(800)(\\\\d{3})(\\\\d{3})","$1 $2 $3","8",null]]]',
  378: '["SM","00",null,"(?:0549)?([89]\\\\d{5})","0549$1",null,"\\\\d{6,10}","[05-7]\\\\d{7,9}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[5-7]",null],["(0549)(\\\\d{6})","$1 $2","0",null,"($1) $2"],["(\\\\d{6})","0549 $1","[89]",null,"(0549) $1"]]]',
  235: '["TD","00|16",null,null,null,null,"\\\\d{8}","[2679]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]',
  960: '["MV","0(?:0|19)",null,null,null,null,"\\\\d{7,10}","[346-8]\\\\d{6,9}|9(?:00\\\\d{7}|\\\\d{6})",[["(\\\\d{3})(\\\\d{4})","$1-$2","[3467]|9(?:[1-9]|0[1-9])",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","[89]00",null]]]',
  221: '["SN","00",null,null,null,null,"\\\\d{9}","[3789]\\\\d{8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[379]",null],["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","8",null]]]',
  595: '["PY","00","0",null,null,null,"\\\\d{5,9}","5[0-5]\\\\d{4,7}|[2-46-9]\\\\d{5,8}",[["(\\\\d{2})(\\\\d{5})","$1 $2","(?:[26]1|3[289]|4[124678]|7[123]|8[1236])","($NP$FG)"],["(\\\\d{2})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","(?:[26]1|3[289]|4[124678]|7[123]|8[1236])","($NP$FG)"],["(\\\\d{3})(\\\\d{3,6})","$1 $2","[2-9]0","$NP$FG"],["(\\\\d{3})(\\\\d{6})","$1 $2","9[1-9]","$NP$FG"],["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","8700",null],["(\\\\d{3})(\\\\d{4,5})","$1 $2","[2-8][1-9]","($NP$FG)"],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[2-8][1-9]","$NP$FG"]]]',
  977: '["NP","00","0",null,null,"$NP$FG","\\\\d{6,10}","[1-8]\\\\d{7}|9(?:[1-69]\\\\d{6,8}|7[2-6]\\\\d{5,7}|8\\\\d{8})",[["(1)(\\\\d{7})","$1-$2","1[2-6]",null],["(\\\\d{2})(\\\\d{6})","$1-$2","1[01]|[2-8]|9(?:[1-69]|7[15-9])",null],["(9\\\\d{2})(\\\\d{7})","$1-$2","9(?:6[013]|7[245]|8)","$FG"]]]',
  36: '["HU","00","06",null,null,"($FG)","\\\\d{6,9}","[1-9]\\\\d{7,8}",[["(1)(\\\\d{3})(\\\\d{4})","$1 $2 $3","1",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[2-9]",null]]]',
};
PK
!<��=w�G�G*modules/shared/FormAutofillSection.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  AutofillTelemetry: "resource://gre/modules/shared/AutofillTelemetry.sys.mjs",
  FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
  FormAutofill: "resource://autofill/FormAutofill.sys.mjs",
  OSKeyStore: "resource://gre/modules/OSKeyStore.sys.mjs",
});

/**
 * To help us classify sections, we want to know what fields can appear
 * multiple times in a row.
 * Such fields, like `address-line{X}`, should not break sections.
 */
const MULTI_FIELD_NAMES = [
  "address-level3",
  "address-level2",
  "address-level1",
  "tel",
  "postal-code",
  "email",
  "street-address",
];

class FormSection {
  static ADDRESS = "address";
  static CREDIT_CARD = "creditCard";

  #fieldDetails = [];

  #name = "";

  constructor(fieldDetails) {
    if (!fieldDetails.length) {
      throw new TypeError("A section should contain at least one field");
    }

    fieldDetails.forEach(field => this.addField(field));

    const fieldName = fieldDetails[0].fieldName;
    if (lazy.FormAutofillUtils.isAddressField(fieldName)) {
      this.type = FormSection.ADDRESS;
    } else if (lazy.FormAutofillUtils.isCreditCardField(fieldName)) {
      this.type = FormSection.CREDIT_CARD;
    } else {
      throw new Error("Unknown field type to create a section.");
    }
  }

  get fieldDetails() {
    return this.#fieldDetails;
  }

  get name() {
    return this.#name;
  }

  addField(fieldDetail) {
    this.#name ||= fieldDetail.sectionName;
    this.#fieldDetails.push(fieldDetail);
  }
}

export class FormAutofillSection {
  /**
   * Record information for fields that are in this section
   */
  #fieldDetails = [];

  constructor(fieldDetails) {
    this.#fieldDetails = fieldDetails;

    ChromeUtils.defineLazyGetter(this, "log", () =>
      lazy.FormAutofill.defineLogGetter(this, "FormAutofillSection")
    );

    // Identifier used to correlate events relating to the same form
    this.flowId = Services.uuid.generateUUID().toString();
    this.log.debug(
      "Creating new credit card section with flowId =",
      this.flowId
    );
  }

  get fieldDetails() {
    return this.#fieldDetails;
  }

  get allFieldNames() {
    return this.fieldDetails.map(field => field.fieldName);
  }

  /*
   * Examine the section is a valid section or not based on its fieldDetails or
   * other information. This method must be overrided.
   *
   * @returns {boolean} True for a valid section, otherwise false
   *
   */
  isValidSection() {
    throw new TypeError("isValidSection method must be overrided");
  }

  /*
   * Examine the section is an enabled section type or not based on its
   * preferences. This method must be overrided.
   *
   * @returns {boolean} True for an enabled section type, otherwise false
   *
   */
  isEnabled() {
    throw new TypeError("isEnabled method must be overrided");
  }

  /*
   * Examine the section is createable for storing the profile. This method
   * must be overrided.
   *
   * @param {Object} _record The record for examining createable
   * @returns {boolean} True for the record is createable, otherwise false
   *
   */
  isRecordCreatable(_record) {
    throw new TypeError("isRecordCreatable method must be overridden");
  }

  /**
   * Override this method if the profile is needed to be customized for
   * previewing values.
   *
   * @param {object} _profile
   *        A profile for pre-processing before previewing values.
   * @returns {boolean} Whether the profile should be previewed.
   */
  preparePreviewProfile(_profile) {
    return true;
  }

  /**
   * Override this method if the profile is needed to be customized for filling
   * values.
   *
   * @param {object} _profile
   *        A profile for pre-processing before filling values.
   * @returns {boolean} Whether the profile should be filled.
   */
  async prepareFillingProfile(_profile) {
    return true;
  }

  /**
   * The result is an array contains the sections with its belonging field details.
   *
   * @param   {Array<FieldDetails>} fieldDetails field detail array to be classified
   * @param   {boolean} ignoreInvalid
   *          True to keep invalid section in the return array. Only used by tests now.
   * @returns {Array<FormSection>} The array with the sections.
   */
  static classifySections(fieldDetails, ignoreInvalid = false) {
    const addressSections = FormAutofillSection.groupFields(
      fieldDetails.filter(f =>
        lazy.FormAutofillUtils.isAddressField(f.fieldName)
      )
    );
    const creditCardSections = FormAutofillSection.groupFields(
      fieldDetails.filter(f =>
        lazy.FormAutofillUtils.isCreditCardField(f.fieldName)
      )
    );

    const sections = [...addressSections, ...creditCardSections].sort(
      (a, b) =>
        fieldDetails.indexOf(a.fieldDetails[0]) -
        fieldDetails.indexOf(b.fieldDetails[0])
    );

    const autofillableSections = [];
    for (const section of sections) {
      // We don't support csc field, so remove csc fields from section
      const fieldDetails = section.fieldDetails.filter(
        f => !["cc-csc"].includes(f.fieldName)
      );
      if (!fieldDetails.length) {
        continue;
      }

      const autofillableSection =
        section.type == FormSection.ADDRESS
          ? new FormAutofillAddressSection(fieldDetails)
          : new FormAutofillCreditCardSection(fieldDetails);

      if (ignoreInvalid && !autofillableSection.isValidSection()) {
        continue;
      }

      autofillableSections.push(autofillableSection);
    }
    return autofillableSections;
  }

  /**
   * Groups fields into sections based on:
   * 1. Their `sectionName` attribute.
   * 2. Whether the section already contains a field with the same `fieldName`,
   *    If so, a new section is created.
   *
   * @param {Array} fieldDetails An array of field detail objects.
   * @returns {Array} An array of FormSection objects.
   */
  static groupFields(fieldDetails) {
    let sections = [];
    for (let i = 0; i < fieldDetails.length; i++) {
      const cur = fieldDetails[i];
      const [currentSection] = sections.slice(-1);

      // The section this field might be placed into.
      let candidateSection = null;

      // Use name group from autocomplete attribute (ex, section-xxx) to look for the section
      // we might place this field into.
      // If the field doesn't have a section name, the candidate section is the previous section.
      if (!currentSection || !cur.sectionName) {
        candidateSection = currentSection;
      } else if (cur.sectionName) {
        // If the field has a section name, the candidate section is the nearest section that
        // either shares the same name or lacks a name.
        for (let idx = sections.length - 1; idx >= 0; idx--) {
          if (!sections[idx].name || sections[idx].name == cur.sectionName) {
            candidateSection = sections[idx];
            break;
          }
        }
      }

      if (candidateSection) {
        let createNewSection = true;

        // We might create a new section instead of placing the field in the candidate section if
        // the section already has a field with the same field name.
        // We also check visibility for both the fields with the same field name because we don't
        // want to create a new section for an invisible field.
        if (
          candidateSection.fieldDetails.find(
            f => f.fieldName == cur.fieldName && f.isVisible && cur.isVisible
          )
        ) {
          // For some field type, it is common to have multiple fields in one section, for example,
          // email. In that case, we will not create a new section even when the candidate section
          // already has a field with the same field name.
          const [last] = candidateSection.fieldDetails.slice(-1);
          if (last.fieldName == cur.fieldName) {
            if (
              MULTI_FIELD_NAMES.includes(cur.fieldName) ||
              (last.part && last.part + 1 == cur.part)
            ) {
              createNewSection = false;
            }
          }
        } else {
          // The field doesn't exist in the candidate section, add it.
          createNewSection = false;
        }

        if (!createNewSection) {
          candidateSection.addField(fieldDetails[i]);
          continue;
        }
      }

      // Create a new section
      sections.push(new FormSection([fieldDetails[i]]));
    }

    return sections;
  }

  /**
   * Return the record that is converted from the element's value.
   * The `valueByElementId` is passed by the child process.
   *
   * @returns {object} object keyed by field name, and values are field values.
   */
  createRecord(formFilledData) {
    if (!this.fieldDetails.length) {
      return {};
    }

    const data = {
      flowId: this.flowId,
      record: {},
    };

    for (const detail of this.fieldDetails) {
      const { filledValue } = formFilledData.get(detail.elementId);

      if (
        !filledValue ||
        filledValue.length > lazy.FormAutofillUtils.MAX_FIELD_VALUE_LENGTH
      ) {
        // Keep the property and preserve more information for updating
        data.record[detail.fieldName] = "";
      } else if (detail.part > 1) {
        // If there are multiple parts for the same field, concatenate the values.
        // This is now used in cases where the credit card number field
        // is split into multiple fields.
        data.record[detail.fieldName] += filledValue;
      } else {
        data.record[detail.fieldName] = filledValue;
      }
    }

    if (!this.isRecordCreatable(data.record)) {
      return null;
    }

    return data;
  }

  /*
   * For telemetry
   */
  onDetected() {
    if (!this.isValidSection()) {
      return;
    }

    lazy.AutofillTelemetry.recordDetectedSectionCount(this.fieldDetails);
    lazy.AutofillTelemetry.recordFormInteractionEvent(
      "detected",
      this.flowId,
      this.fieldDetails
    );
  }

  onPopupOpened(elementId) {
    const fieldDetail = this.getFieldDetailByElementId(elementId);
    lazy.AutofillTelemetry.recordFormInteractionEvent(
      "popup_shown",
      this.flowId,
      [fieldDetail]
    );
  }

  onFilled(filledResult) {
    lazy.AutofillTelemetry.recordFormInteractionEvent(
      "filled",
      this.flowId,
      this.fieldDetails,
      filledResult
    );
  }

  onFilledModified(elementId) {
    const fieldDetail = this.getFieldDetailByElementId(elementId);
    lazy.AutofillTelemetry.recordFormInteractionEvent(
      "filled_modified",
      this.flowId,
      [fieldDetail]
    );
  }

  onSubmitted(formFilledData) {
    lazy.AutofillTelemetry.recordSubmittedSectionCount(this.fieldDetails, 1);
    lazy.AutofillTelemetry.recordFormInteractionEvent(
      "submitted",
      this.flowId,
      this.fieldDetails,
      formFilledData
    );
  }

  onCleared(elementId) {
    const fieldDetail = this.getFieldDetailByElementId(elementId);
    lazy.AutofillTelemetry.recordFormInteractionEvent("cleared", this.flowId, [
      fieldDetail,
    ]);
  }

  /**
   * Utility functions
   */
  getFieldDetailByElementId(elementId) {
    return this.fieldDetails.find(detail => detail.elementId == elementId);
  }
}

export class FormAutofillAddressSection extends FormAutofillSection {
  isValidSection() {
    const fields = new Set(this.fieldDetails.map(f => f.fieldName));
    return fields.size >= lazy.FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD;
  }

  isEnabled() {
    return lazy.FormAutofill.isAutofillAddressesEnabled;
  }

  isRecordCreatable(record) {
    const country = lazy.FormAutofillUtils.identifyCountryCode(
      record.country || record["country-name"]
    );
    if (
      country &&
      !lazy.FormAutofill.isAutofillAddressesAvailableInCountry(country)
    ) {
      // We don't want to save data in the wrong fields due to not having proper
      // heuristic regexes in countries we don't yet support.
      this.log.warn(
        "isRecordCreatable: Country not supported:",
        record.country
      );
      return false;
    }

    // Multiple name or tel fields are treat as 1 field while countng whether
    // the number of fields exceed the valid address secton threshold
    const categories = Object.entries(record)
      .filter(e => !!e[1])
      .map(e => lazy.FormAutofillUtils.getCategoryFromFieldName(e[0]));

    return (
      categories.reduce(
        (acc, category) =>
          ["name", "tel"].includes(category) && acc.includes(category)
            ? acc
            : [...acc, category],
        []
      ).length >= lazy.FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD
    );
  }
}

export class FormAutofillCreditCardSection extends FormAutofillSection {
  /**
   * Determine whether a set of cc fields identified by our heuristics form a
   * valid credit card section.
   * There are 4 different cases when a field is considered a credit card field
   * 1. Identified by autocomplete attribute. ex <input autocomplete="cc-number">
   * 2. Identified by fathom and fathom is pretty confident (when confidence
   *    value is higher than `highConfidenceThreshold`)
   * 3. Identified by fathom. Confidence value is between `fathom.confidenceThreshold`
   *    and `fathom.highConfidenceThreshold`
   * 4. Identified by regex-based heurstic. There is no confidence value in thise case.
   *
   * A form is considered a valid credit card form when one of the following condition
   * is met:
   * A. One of the cc field is identified by autocomplete (case 1)
   * B. One of the cc field is identified by fathom (case 2 or 3), and there is also
   *    another cc field found by any of our heuristic (case 2, 3, or 4)
   * C. Only one cc field is found in the section, but fathom is very confident (Case 2).
   *    Currently we add an extra restriction to this rule to decrease the false-positive
   *    rate. See comments below for details.
   *
   * @returns {boolean} True for a valid section, otherwise false
   */
  isValidSection() {
    let ccNumberDetail = null;
    let ccNameDetail = null;
    let ccExpiryDetail = null;

    for (let detail of this.fieldDetails) {
      switch (detail.fieldName) {
        case "cc-number":
          ccNumberDetail = detail;
          break;
        case "cc-name":
        case "cc-given-name":
        case "cc-additional-name":
        case "cc-family-name":
          ccNameDetail = detail;
          break;
        case "cc-exp":
        case "cc-exp-month":
        case "cc-exp-year":
          ccExpiryDetail = detail;
          break;
      }
    }

    // Condition A. Always trust autocomplete attribute. A section is considered a valid
    // cc section as long as a field has autocomplete=cc-number, cc-name or cc-exp*
    if (
      ccNumberDetail?.reason == "autocomplete" ||
      ccNameDetail?.reason == "autocomplete" ||
      ccExpiryDetail?.reason == "autocomplete"
    ) {
      return true;
    }

    // Condition B. One of the field is identified by fathom, if this section also
    // contains another cc field found by our heuristic (Case 2, 3, or 4), we consider
    // this section a valid credit card seciton
    if (ccNumberDetail?.reason == "fathom") {
      if (ccNameDetail || ccExpiryDetail) {
        return true;
      }
    } else if (ccNameDetail?.reason == "fathom") {
      if (ccNumberDetail || ccExpiryDetail) {
        return true;
      }
    }

    // Condition C.
    if (
      ccNumberDetail?.isOnlyVisibleFieldWithHighConfidence ||
      ccNameDetail?.isOnlyVisibleFieldWithHighConfidence
    ) {
      return true;
    }

    return false;
  }

  isEnabled() {
    return lazy.FormAutofill.isAutofillCreditCardsEnabled;
  }

  isRecordCreatable(record) {
    return (
      record["cc-number"] &&
      lazy.FormAutofillUtils.isCCNumber(record["cc-number"])
    );
  }

  /**
   * Customize for previewing profile
   *
   * @param {object} profile
   *        A profile for pre-processing before previewing values.
   * @returns {boolean} Whether the profile should be filled.
   * @override
   */
  preparePreviewProfile(profile) {
    if (!profile) {
      return true;
    }

    // Always show the decrypted credit card number when Master Password is
    // disabled.
    if (profile["cc-number-decrypted"]) {
      profile["cc-number"] = profile["cc-number-decrypted"];
    } else if (!profile["cc-number"].startsWith("****")) {
      // Show the previewed credit card as "**** 4444" which is
      // needed when a credit card number field has a maxlength of four.
      profile["cc-number"] = "****" + profile["cc-number"];
    }

    return true;
  }

  /**
   * Customize for filling profile
   *
   * @param {object} profile
   *        A profile for pre-processing before filling values.
   * @returns {boolean} Whether the profile should be filled.
   * @override
   */
  async prepareFillingProfile(profile) {
    // Prompt the OS login dialog to get the decrypted credit card number.
    if (profile["cc-number-encrypted"]) {
      const promptMessage = lazy.FormAutofillUtils.reauthOSPromptMessage(
        "autofill-use-payment-method-os-prompt-macos",
        "autofill-use-payment-method-os-prompt-windows",
        "autofill-use-payment-method-os-prompt-other"
      );
      const decrypted = await this.getDecryptedString(
        profile["cc-number-encrypted"],
        promptMessage
      );

      if (!decrypted) {
        // Early return if the decrypted is empty or undefined
        return false;
      }

      profile["cc-number"] = decrypted;
    }
    return true;
  }

  async getDecryptedString(cipherText, reauth) {
    if (
      !lazy.FormAutofillUtils.getOSAuthEnabled(
        lazy.FormAutofill.AUTOFILL_CREDITCARDS_REAUTH_PREF
      )
    ) {
      this.log.debug("Reauth is disabled");
      reauth = false;
    }
    let string;
    try {
      string = await lazy.OSKeyStore.decrypt(cipherText, reauth);
    } catch (e) {
      if (e.result != Cr.NS_ERROR_ABORT) {
        throw e;
      }
      this.log.warn("User canceled encryption login");
    }
    return string;
  }
}
PK
!<�SV�N:N:(modules/shared/AutofillTelemetry.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { FormAutofillUtils } from "resource://gre/modules/shared/FormAutofillUtils.sys.mjs";

const { FIELD_STATES } = FormAutofillUtils;

class AutofillTelemetryBase {
  SUPPORTED_FIELDS = {};

  EVENT_CATEGORY = null;
  EVENT_OBJECT_FORM_INTERACTION = null;

  SCALAR_DETECTED_SECTION_COUNT = null;
  SCALAR_SUBMITTED_SECTION_COUNT = null;

  HISTOGRAM_NUM_USES = null;
  HISTOGRAM_PROFILE_NUM_USES = null;
  HISTOGRAM_PROFILE_NUM_USES_KEY = null;

  #initFormEventExtra(value) {
    let extra = {};
    for (const field of Object.values(this.SUPPORTED_FIELDS)) {
      extra[field] = value;
    }
    return extra;
  }

  #setFormEventExtra(extra, key, value) {
    if (!this.SUPPORTED_FIELDS[key]) {
      return;
    }

    extra[this.SUPPORTED_FIELDS[key]] = value;
  }

  /**
   * Building the extra keys object that is included in the Legacy Telemetry event `cc_form_v2`
   * or `address_form` event and the Glean event `cc_form`, and `address_form`.
   * It indicates the detected credit card or address fields and which method (autocomplete property, regular expression heuristics or fathom) identified them.
   *
   * @param {Array<object>} fieldDetails fieldDetails to extract which fields were identified and how
   * @param {string} undetected Default value when a field is not detected: 'undetected' (Glean) and 'false' in (Legacy)
   * @param {string} autocomplete Value when a field is identified with autocomplete property: 'autocomplete' (Glean), 'true' (Legacy)
   * @param {string} regexp Value when a field is identified with regex expression heuristics: 'regexp' (Glean), '0' (Legacy)
   * @param {boolean} includeMultiPart Include multi part data or not
   * @returns {object} Extra keys to include in the form event
   */
  #buildFormDetectedEventExtra(
    fieldDetails,
    undetected,
    autocomplete,
    regexp,
    includeMultiPart
  ) {
    let extra = this.#initFormEventExtra(undetected);

    let identified = new Set();
    fieldDetails.forEach(detail => {
      identified.add(detail.fieldName);

      if (detail.reason == "autocomplete") {
        this.#setFormEventExtra(extra, detail.fieldName, autocomplete);
      } else {
        // confidence exists only when a field is identified by fathom.
        let confidence =
          detail.confidence > 0 ? Math.floor(100 * detail.confidence) / 100 : 0;

        this.#setFormEventExtra(
          extra,
          detail.fieldName,
          confidence ? confidence.toString() : regexp
        );
      }

      if (
        detail.fieldName === "cc-number" &&
        this.SUPPORTED_FIELDS[detail.fieldName] &&
        includeMultiPart
      ) {
        extra.cc_number_multi_parts = detail.part ?? 1;
      }
    });
    return extra;
  }

  recordFormDetected(flowId, fieldDetails) {
    this.recordFormEvent(
      "detected",
      flowId,
      this.#buildFormDetectedEventExtra(
        fieldDetails,
        "false",
        "true",
        "0",
        false
      )
    );

    this.recordGleanFormEvent(
      "formDetected",
      flowId,
      this.#buildFormDetectedEventExtra(
        fieldDetails,
        "undetected",
        "autocomplete",
        "regexp",
        true
      )
    );
  }

  recordPopupShown(flowId, fieldDetails) {
    const extra = { field_name: fieldDetails[0].fieldName };
    this.recordFormEvent("popup_shown", flowId, extra);
    this.recordGleanFormEvent("formPopupShown", flowId, extra);
  }

  recordFormFilled(flowId, fieldDetails, data) {
    // Calculate values for telemetry
    const extra = this.#initFormEventExtra("unavailable");

    for (const fieldDetail of fieldDetails) {
      let { filledState, value } = data.get(fieldDetail.elementId);
      switch (filledState) {
        case FIELD_STATES.AUTO_FILLED:
          filledState = "filled";
          break;
        case FIELD_STATES.NORMAL:
        default:
          filledState =
            fieldDetail.tagName == "SELECT" || value.length
              ? "user_filled"
              : "not_filled";
          break;
      }
      this.#setFormEventExtra(extra, fieldDetail.fieldName, filledState);
    }

    this.recordFormEvent("filled", flowId, extra);
    this.recordGleanFormEvent("formFilled", flowId, extra);
  }

  recordFilledModified(flowId, fieldDetails) {
    const extra = { field_name: fieldDetails[0].fieldName };
    this.recordFormEvent("filled_modified", flowId, extra);
    this.recordGleanFormEvent("formFilledModified", flowId, extra);
  }

  recordFormSubmitted(flowId, fieldDetails, data) {
    const extra = this.#initFormEventExtra("unavailable");

    for (const fieldDetail of fieldDetails) {
      let { filledState, value } = data.get(fieldDetail.elementId);
      switch (filledState) {
        case FIELD_STATES.AUTO_FILLED:
          filledState = "autofilled";
          break;
        case FIELD_STATES.NORMAL:
        default:
          filledState =
            fieldDetail.tagName == "SELECT" || value.length
              ? "user_filled"
              : "not_filled";
          break;
      }
      this.#setFormEventExtra(extra, fieldDetail.fieldName, filledState);
    }

    this.recordFormEvent("submitted", flowId, extra);
    this.recordGleanFormEvent("formSubmitted", flowId, extra);
  }

  recordFormCleared(flowId, fieldDetails) {
    const extra = { field_name: fieldDetails[0].fieldName };

    // Note that when a form is cleared, we also record `filled_modified` events
    // for all the fields that have been cleared.
    this.recordFormEvent("cleared", flowId, extra);
    this.recordGleanFormEvent("formCleared", flowId, extra);
  }

  recordFormEvent(method, flowId, extra) {
    Services.telemetry.recordEvent(
      this.EVENT_CATEGORY,
      method,
      this.EVENT_OBJECT_FORM_INTERACTION,
      flowId,
      extra
    );
  }

  recordGleanFormEvent(_eventName, _flowId, _extra) {
    throw new Error("Not implemented.");
  }

  recordFormInteractionEvent(method, flowId, fieldDetails, data) {
    if (!this.EVENT_OBJECT_FORM_INTERACTION) {
      return undefined;
    }
    switch (method) {
      case "detected":
        return this.recordFormDetected(flowId, fieldDetails);
      case "popup_shown":
        return this.recordPopupShown(flowId, fieldDetails);
      case "filled":
        return this.recordFormFilled(flowId, fieldDetails, data);
      case "filled_modified":
        return this.recordFilledModified(flowId, fieldDetails);
      case "submitted":
        return this.recordFormSubmitted(flowId, fieldDetails, data);
      case "cleared":
        return this.recordFormCleared(flowId, fieldDetails);
    }
    return undefined;
  }

  recordDoorhangerEvent(method, object, flowId) {
    Services.telemetry.recordEvent(this.EVENT_CATEGORY, method, object, flowId);
  }

  recordManageEvent(method) {
    Services.telemetry.recordEvent(this.EVENT_CATEGORY, method, "manage");
  }

  recordAutofillProfileCount(_count) {
    throw new Error("Not implemented.");
  }

  recordDetectedSectionCount() {
    if (!this.SCALAR_DETECTED_SECTION_COUNT) {
      return;
    }

    Services.telemetry.scalarAdd(this.SCALAR_DETECTED_SECTION_COUNT, 1);
  }

  recordSubmittedSectionCount(count) {
    if (!this.SCALAR_SUBMITTED_SECTION_COUNT || !count) {
      return;
    }

    Services.telemetry.scalarAdd(this.SCALAR_SUBMITTED_SECTION_COUNT, count);
  }

  recordNumberOfUse(records) {
    let histogram = Services.telemetry.getKeyedHistogramById(
      this.HISTOGRAM_PROFILE_NUM_USES
    );
    histogram.clear();

    for (let record of records) {
      histogram.add(this.HISTOGRAM_PROFILE_NUM_USES_KEY, record.timesUsed);
    }
  }
}

export class AddressTelemetry extends AutofillTelemetryBase {
  EVENT_CATEGORY = "address";
  EVENT_OBJECT_FORM_INTERACTION = "address_form";
  EVENT_OBJECT_FORM_INTERACTION_EXT = "address_form_ext";

  SCALAR_DETECTED_SECTION_COUNT =
    "formautofill.addresses.detected_sections_count";
  SCALAR_SUBMITTED_SECTION_COUNT =
    "formautofill.addresses.submitted_sections_count";
  SCALAR_AUTOFILL_PROFILE_COUNT =
    "formautofill.addresses.autofill_profiles_count";

  HISTOGRAM_PROFILE_NUM_USES = "AUTOFILL_PROFILE_NUM_USES";
  HISTOGRAM_PROFILE_NUM_USES_KEY = "address";

  // Fields that are record in `address_form` and `address_form_ext` telemetry
  SUPPORTED_FIELDS = {
    "street-address": "street_address",
    "address-line1": "address_line1",
    "address-line2": "address_line2",
    "address-line3": "address_line3",
    "address-level1": "address_level1",
    "address-level2": "address_level2",
    "postal-code": "postal_code",
    country: "country",
    name: "name",
    "given-name": "given_name",
    "additional-name": "additional_name",
    "family-name": "family_name",
    email: "email",
    organization: "organization",
    tel: "tel",
  };

  // Fields that are record in `address_form` event telemetry extra_keys
  static SUPPORTED_FIELDS_IN_FORM = [
    "street_address",
    "address_line1",
    "address_line2",
    "address_line3",
    "address_level2",
    "address_level1",
    "postal_code",
    "country",
  ];

  // Fields that are record in `address_form_ext` event telemetry extra_keys
  static SUPPORTED_FIELDS_IN_FORM_EXT = [
    "name",
    "given_name",
    "additional_name",
    "family_name",
    "email",
    "organization",
    "tel",
  ];

  recordGleanFormEvent(_eventName, _flowId, _extra) {
    // To be implemented when migrating the legacy event address.address_form to Glean
  }

  recordFormEvent(method, flowId, extra) {
    let extExtra = {};
    if (["detected", "filled", "submitted"].includes(method)) {
      for (const [key, value] of Object.entries(extra)) {
        if (AddressTelemetry.SUPPORTED_FIELDS_IN_FORM_EXT.includes(key)) {
          extExtra[key] = value;
          delete extra[key];
        }
      }
    }

    Services.telemetry.recordEvent(
      this.EVENT_CATEGORY,
      method,
      this.EVENT_OBJECT_FORM_INTERACTION,
      flowId,
      extra
    );

    if (Object.keys(extExtra).length) {
      Services.telemetry.recordEvent(
        this.EVENT_CATEGORY,
        method,
        this.EVENT_OBJECT_FORM_INTERACTION_EXT,
        flowId,
        extExtra
      );
    }
  }

  recordAutofillProfileCount(count) {
    Services.telemetry.scalarSet(this.SCALAR_AUTOFILL_PROFILE_COUNT, count);
  }
}

class CreditCardTelemetry extends AutofillTelemetryBase {
  EVENT_CATEGORY = "creditcard";
  EVENT_OBJECT_FORM_INTERACTION = "cc_form_v2";

  SCALAR_DETECTED_SECTION_COUNT =
    "formautofill.creditCards.detected_sections_count";
  SCALAR_SUBMITTED_SECTION_COUNT =
    "formautofill.creditCards.submitted_sections_count";

  HISTOGRAM_NUM_USES = "CREDITCARD_NUM_USES";
  HISTOGRAM_PROFILE_NUM_USES = "AUTOFILL_PROFILE_NUM_USES";
  HISTOGRAM_PROFILE_NUM_USES_KEY = "credit_card";

  // Mapping of field name used in formautofill code to the field name
  // used in the telemetry.
  SUPPORTED_FIELDS = {
    "cc-name": "cc_name",
    "cc-number": "cc_number",
    "cc-type": "cc_type",
    "cc-exp": "cc_exp",
    "cc-exp-month": "cc_exp_month",
    "cc-exp-year": "cc_exp_year",
  };

  recordGleanFormEvent(eventName, flowId, extra) {
    extra.flow_id = flowId;
    Glean.formautofillCreditcards[eventName].record(extra);
  }

  recordNumberOfUse(records) {
    super.recordNumberOfUse(records);

    if (!this.HISTOGRAM_NUM_USES) {
      return;
    }

    let histogram = Services.telemetry.getHistogramById(
      this.HISTOGRAM_NUM_USES
    );
    histogram.clear();

    for (let record of records) {
      histogram.add(record.timesUsed);
    }
  }

  recordAutofillProfileCount(count) {
    Glean.formautofillCreditcards.autofillProfilesCount.set(count);
  }
}

export class AutofillTelemetry {
  static #creditCardTelemetry = new CreditCardTelemetry();
  static #addressTelemetry = new AddressTelemetry();

  // const for `type` parameter used in the utility functions
  static ADDRESS = "address";
  static CREDIT_CARD = "creditcard";

  static #getTelemetryByFieldDetail(fieldDetail) {
    return FormAutofillUtils.isAddressField(fieldDetail.fieldName)
      ? this.#addressTelemetry
      : this.#creditCardTelemetry;
  }

  static #getTelemetryByType(type) {
    return type == AutofillTelemetry.CREDIT_CARD
      ? this.#creditCardTelemetry
      : this.#addressTelemetry;
  }

  /**
   * Utility functions for `doorhanger` event (defined in Events.yaml)
   *
   * Category: address or creditcard
   * Event name: doorhanger
   */
  static recordDoorhangerShown(type, object, flowId) {
    const telemetry = this.#getTelemetryByType(type);
    telemetry.recordDoorhangerEvent("show", object, flowId);
  }

  static recordDoorhangerClicked(type, method, object, flowId) {
    const telemetry = this.#getTelemetryByType(type);

    // We don't have `create` method in telemetry, we treat `create` as `save`
    switch (method) {
      case "create":
        method = "save";
        break;
      case "open-pref":
        method = "pref";
        break;
      case "learn-more":
        method = "learn_more";
        break;
    }

    telemetry.recordDoorhangerEvent(method, object, flowId);
  }

  /**
   * Utility functions for form event (defined in Events.yaml)
   *
   * Category: address or creditcard
   * Event name: cc_form_v2, or address_form
   */

  static recordFormInteractionEvent(method, flowId, fieldDetails, data) {
    const telemetry = this.#getTelemetryByFieldDetail(fieldDetails[0]);
    telemetry.recordFormInteractionEvent(method, flowId, fieldDetails, data);
  }

  /**
   * Utility functions for submitted section count scalar (defined in Scalars.yaml)
   *
   * Category: formautofill.creditCards or formautofill.addresses
   * Scalar name: submitted_sections_count
   */
  static recordDetectedSectionCount(fieldDetails) {
    const telemetry = this.#getTelemetryByFieldDetail(fieldDetails[0]);
    telemetry.recordDetectedSectionCount();
  }

  static recordSubmittedSectionCount(fieldDetails, count) {
    const telemetry = this.#getTelemetryByFieldDetail(fieldDetails[0]);
    telemetry.recordSubmittedSectionCount(count);
  }

  static recordManageEvent(type, method) {
    const telemetry = this.#getTelemetryByType(type);
    telemetry.recordManageEvent(method);
  }

  static recordAutofillProfileCount(type, count) {
    const telemetry = this.#getTelemetryByType(type);
    telemetry.recordAutofillProfileCount(count);
  }

  /**
   * Utility functions for address/credit card number of use
   */
  static recordNumberOfUse(type, records) {
    const telemetry = this.#getTelemetryByType(type);
    telemetry.recordNumberOfUse(records);
  }

  static recordFormSubmissionHeuristicCount(label) {
    Glean.formautofill.formSubmissionHeuristic[label].add(1);
  }
}
PK
!<޺��II actors/FormHandlerParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class FormHandlerParent extends JSWindowActorParent {
  receiveMessage(message) {
    switch (message.name) {
      case "FormHandler:EnsureParentExists": {
        // This dummy message is sent to make sure that the parent exists,
        // because we use the existence of the parent to determine whether to
        // notify the corresponding child when a page navigation occurs.
        break;
      }
      case "FormHandler:NotifyNavigatedSubtree": {
        this.onPageNavigated(message.data);
        break;
      }
      case "FormHandler:RegisterProgressListenerAtTopLevel": {
        this.registerProgressListenerAtTopLevel();
        break;
      }
    }
  }

  /**
   * Go through the subtree of the navigated browsing context and
   * let the existing FormHandler parents (same-origin and cross-origin)
   * notify their corresponding children of the detected page navigation
   * If the current browsing context is the navigated one, skip it,
   * because the page navigation was processed directly in the child.
   *
   * @param {BrowsingContext} navigatedBrowsingContext
   */
  onPageNavigated(navigatedBrowsingContext) {
    const browsingContexts =
      navigatedBrowsingContext.getAllBrowsingContextsInSubtree();

    if (this.browsingContext === navigatedBrowsingContext) {
      // Don't notify the child of the navigated process root,
      // since the page navigation was already processed in that child
      browsingContexts.shift();
    }

    for (const context of browsingContexts) {
      const windowGlobal = context.currentWindowGlobal;
      if (!windowGlobal) {
        continue;
      }

      // This next step doesn't create the FormHandler actor pair. We only
      // check whether the FormHandlerParent already exists.
      // If it exists, somebody in that window context registered an interest
      // in form submissions, so we send a message.
      // If it doesn't exist, then nobody in that window context is interested
      // in the form submissions, so we don't need to send a message.
      const formHandlerActor = windowGlobal.getExistingActor("FormHandler");
      formHandlerActor?.sendAsyncMessage(
        "FormHandler:FormSubmissionByNavigation"
      );
    }
  }

  /**
   * Send a dummy message to the FormHandlerChild of the top.
   * This is to make sure that the top child is being created and has
   * registered the progress listener that listens for page navigations.
   */
  registerProgressListenerAtTopLevel() {
    const topLevelFormHandler =
      this.browsingContext.top.currentWindowGlobal.getActor("FormHandler");
    topLevelFormHandler.sendAsyncMessage("FormHandler:EnsureChildExists");
  }
}
PK
!<��#�	�	$modules/ServiceWorkerCleanUp.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "serviceWorkerManager",
  "@mozilla.org/serviceworkers/manager;1",
  "nsIServiceWorkerManager"
);

if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
  throw new Error(
    "ServiceWorkerCleanUp.sys.mjs can only be used in the parent process"
  );
}

function unregisterServiceWorker(aSW) {
  return new Promise(resolve => {
    let unregisterCallback = {
      unregisterSucceeded: resolve,
      unregisterFailed: resolve, // We don't care about failures.
      QueryInterface: ChromeUtils.generateQI([
        "nsIServiceWorkerUnregisterCallback",
      ]),
    };
    lazy.serviceWorkerManager.propagateUnregister(
      aSW.principal,
      unregisterCallback,
      aSW.scope
    );
  });
}

function unregisterServiceWorkersMatching(filterFn) {
  let promises = [];
  let serviceWorkers = lazy.serviceWorkerManager.getAllRegistrations();
  for (let i = 0; i < serviceWorkers.length; i++) {
    let sw = serviceWorkers.queryElementAt(
      i,
      Ci.nsIServiceWorkerRegistrationInfo
    );
    if (filterFn(sw)) {
      promises.push(unregisterServiceWorker(sw));
    }
  }
  return Promise.all(promises);
}

export const ServiceWorkerCleanUp = {
  removeFromHost(aHost) {
    return unregisterServiceWorkersMatching(sw =>
      Services.eTLD.hasRootDomain(sw.principal.host, aHost)
    );
  },

  removeFromBaseDomain(aBaseDomain) {
    // Service workers are disabled in partitioned contexts. This means we don't
    // have to check for a partitionKey, but only look at the top level base
    // domain. If this ever changes we need to update this method to account for
    // partitions. See Bug 1495241.
    return unregisterServiceWorkersMatching(
      sw => sw.principal.baseDomain == aBaseDomain
    );
  },

  removeFromPrincipal(aPrincipal) {
    return unregisterServiceWorkersMatching(sw =>
      sw.principal.equals(aPrincipal)
    );
  },

  removeFromOriginAttributes(aOriginAttributesString) {
    lazy.serviceWorkerManager.removeRegistrationsByOriginAttributes(
      aOriginAttributesString
    );
    return Promise.resolve();
  },

  removeAll() {
    return unregisterServiceWorkersMatching(() => true);
  },
};
PK
!<0�s�	�	 modules/TelemetryStorage.sys.mjs/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { Log } from "resource://gre/modules/Log.sys.mjs";
import { TelemetryUtils } from "resource://gre/modules/TelemetryUtils.sys.mjs";

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetryStorage::";

const Telemetry = Services.telemetry;
const Utils = TelemetryUtils;

// Compute the path of the pings archive on the first use.
const DATAREPORTING_DIR = "datareporting";
const PINGS_ARCHIVE_DIR = "archived";
const ABORTED_SESSION_FILE_NAME = "aborted-session-ping";
const SESSION_STATE_FILE_NAME = "session-state.json";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "gDataReportingDir", function () {
  return PathUtils.join(PathUtils.profileDir, DATAREPORTING_DIR);
});
ChromeUtils.defineLazyGetter(lazy, "gPingsArchivePath", function () {
  return PathUtils.join(lazy.gDataReportingDir, PINGS_ARCHIVE_DIR);
});
ChromeUtils.defineLazyGetter(lazy, "gAbortedSessionFilePath", function () {
  return PathUtils.join(lazy.gDataReportingDir, ABORTED_SESSION_FILE_NAME);
});
ChromeUtils.defineESModuleGetters(lazy, {
  TelemetryHealthPing: "resource://gre/modules/HealthPing.sys.mjs",
});
// Maxmimum time, in milliseconds, archive pings should be retained.
const MAX_ARCHIVED_PINGS_RETENTION_MS = 60 * 24 * 60 * 60 * 1000; // 60 days

// Maximum space the archive can take on disk (in Bytes).
const ARCHIVE_QUOTA_BYTES = 120 * 1024 * 1024; // 120 MB
// Maximum space the outgoing pings can take on disk, for Desktop (in Bytes).
const PENDING_PINGS_QUOTA_BYTES_DESKTOP = 15 * 1024 * 1024; // 15 MB
// Maximum space the outgoing pings can take on disk, for Mobile (in Bytes).
const PENDING_PINGS_QUOTA_BYTES_MOBILE = 1024 * 1024; // 1 MB

// The maximum size a pending/archived ping can take on disk.
const PING_FILE_MAXIMUM_SIZE_BYTES = 1024 * 1024; // 1 MB

// This special value is submitted when the archive is outside of the quota.
const ARCHIVE_SIZE_PROBE_SPECIAL_VALUE = 300;

// This special value is submitted when the pending pings is outside of the quota, as
// we don't know the size of the pings above the quota.
const PENDING_PINGS_SIZE_PROBE_SPECIAL_VALUE = 17;

const UUID_REGEX =
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

/**
 * This is thrown by |TelemetryStorage.loadPingFile| when reading the ping
 * from the disk fails.
 */
function PingReadError(
  message = "Error reading the ping file",
  becauseNoSuchFile = false
) {
  Error.call(this, message);
  let error = new Error();
  this.name = "PingReadError";
  this.message = message;
  this.stack = error.stack;
  this.becauseNoSuchFile = becauseNoSuchFile;
}
PingReadError.prototype = Object.create(Error.prototype);
PingReadError.prototype.constructor = PingReadError;

/**
 * This is thrown by |TelemetryStorage.loadPingFile| when parsing the ping JSON
 * content fails.
 */
function PingParseError(message = "Error parsing ping content") {
  Error.call(this, message);
  let error = new Error();
  this.name = "PingParseError";
  this.message = message;
  this.stack = error.stack;
}
PingParseError.prototype = Object.create(Error.prototype);
PingParseError.prototype.constructor = PingParseError;

/**
 * This is a policy object used to override behavior for testing.
 */
export var Policy = {
  now: () => new Date(),
  getArchiveQuota: () => ARCHIVE_QUOTA_BYTES,
  getPendingPingsQuota: () =>
    AppConstants.platform == "android"
      ? PENDING_PINGS_QUOTA_BYTES_MOBILE
      : PENDING_PINGS_QUOTA_BYTES_DESKTOP,
  /**
   * @param {string} id The ID of the ping that will be written into the file. Can be "*" to
   *                    make a pattern to find all pings for this installation.
   * @return
   *         {
   *           directory: <nsIFile>, // Directory to save pings
   *           file: <string>, // File name for this ping (or pattern for all pings)
   *         }
   */
  getUninstallPingPath: id => {
    // UpdRootD is e.g. C:\ProgramData\Mozilla\updates\<PATH HASH>
    const updateDirectory = Services.dirsvc.get("UpdRootD", Ci.nsIFile);
    const installPathHash = updateDirectory.leafName;

    return {
      // e.g. C:\ProgramData\Mozilla
      directory: updateDirectory.parent.parent.clone(),
      file: `uninstall_ping_${installPathHash}_${id}.json`,
    };
  },
};

/**
 * Wait for all promises in iterable to resolve or reject. This function
 * always resolves its promise with undefined, and never rejects.
 */
function waitForAll(it) {
  let dummy = () => {};
  let promises = Array.from(it, p => p.catch(dummy));
  return Promise.all(promises);
}

/**
 * Permanently intern the given string. This is mainly used for the ping.type
 * strings that can be excessively duplicated in the _archivedPings map. Do not
 * pass large or temporary strings to this function.
 */
function internString(str) {
  return Symbol.keyFor(Symbol.for(str));
}

export var TelemetryStorage = {
  get pingDirectoryPath() {
    return PathUtils.join(PathUtils.profileDir, "saved-telemetry-pings");
  },

  /**
   * The maximum size a ping can have, in bytes.
   */
  get MAXIMUM_PING_SIZE() {
    return PING_FILE_MAXIMUM_SIZE_BYTES;
  },
  /**
   * Shutdown & block on any outstanding async activity in this module.
   *
   * @return {Promise} Promise that is resolved when shutdown is complete.
   */
  shutdown() {
    return TelemetryStorageImpl.shutdown();
  },

  /**
   * Save an archived ping to disk.
   *
   * @param {object} ping The ping data to archive.
   * @return {promise} Promise that is resolved when the ping is successfully archived.
   */
  saveArchivedPing(ping) {
    return TelemetryStorageImpl.saveArchivedPing(ping);
  },

  /**
   * Load an archived ping from disk.
   *
   * @param {string} id The pings id.
   * @return {promise<object>} Promise that is resolved with the ping data.
   */
  loadArchivedPing(id) {
    return TelemetryStorageImpl.loadArchivedPing(id);
  },

  /**
   * Get a list of info on the archived pings.
   * This will scan the archive directory and grab basic data about the existing
   * pings out of their filename.
   *
   * @return {promise<sequence<object>>}
   */
  loadArchivedPingList() {
    return TelemetryStorageImpl.loadArchivedPingList();
  },

  /**
   * Clean the pings archive by removing old pings.
   * This will scan the archive directory.
   *
   * @return {Promise} Resolved when the cleanup task completes.
   */
  runCleanPingArchiveTask() {
    return TelemetryStorageImpl.runCleanPingArchiveTask();
  },

  /**
   * Run the task to enforce the pending pings quota.
   *
   * @return {Promise} Resolved when the cleanup task completes.
   */
  runEnforcePendingPingsQuotaTask() {
    return TelemetryStorageImpl.runEnforcePendingPingsQuotaTask();
  },

  /**
   * Run the task to remove all the pending pings
   *
   * @return {Promise} Resolved when the pings are removed.
   */
  runRemovePendingPingsTask() {
    return TelemetryStorageImpl.runRemovePendingPingsTask();
  },

  /**
   * Remove all pings that are stored in the userApplicationDataDir
   * under the "Pending Pings" sub-directory.
   */
  removeAppDataPings() {
    return TelemetryStorageImpl.removeAppDataPings();
  },

  /**
   * Reset the storage state in tests.
   */
  reset() {
    return TelemetryStorageImpl.reset();
  },

  /**
   * Test method that allows waiting on the archive clean task to finish.
   */
  testCleanupTaskPromise() {
    return TelemetryStorageImpl._cleanArchiveTask || Promise.resolve();
  },

  /**
   * Test method that allows waiting on the pending pings quota task to finish.
   */
  testPendingQuotaTaskPromise() {
    return (
      TelemetryStorageImpl._enforcePendingPingsQuotaTask || Promise.resolve()
    );
  },

  /**
   * Save a pending - outgoing - ping to disk and track it.
   *
   * @param {Object} ping The ping data.
   * @return {Promise} Resolved when the ping was saved.
   */
  savePendingPing(ping) {
    return TelemetryStorageImpl.savePendingPing(ping);
  },

  /**
   * Saves session data to disk.
   * @param {Object}  sessionData The session data.
   * @return {Promise} Resolved when the data was saved.
   */
  saveSessionData(sessionData) {
    return TelemetryStorageImpl.saveSessionData(sessionData);
  },

  /**
   * Loads session data from a session data file.
   * @return {Promise<object>} Resolved with the session data in object form.
   */
  loadSessionData() {
    return TelemetryStorageImpl.loadSessionData();
  },

  /**
   * Load a pending ping from disk by id.
   *
   * @param {String} id The pings id.
   * @return {Promise} Resolved with the loaded ping data.
   */
  loadPendingPing(id) {
    return TelemetryStorageImpl.loadPendingPing(id);
  },

  /**
   * Remove a pending ping from disk by id.
   *
   * @param {String} id The pings id.
   * @return {Promise} Resolved when the ping was removed.
   */
  removePendingPing(id) {
    return TelemetryStorageImpl.removePendingPing(id);
  },

  /**
   * Returns a list of the currently pending pings in the format:
   * {
   *   id: <string>, // The pings UUID.
   *   lastModified: <number>, // Timestamp of the pings last modification.
   * }
   * This populates the list by scanning the disk.
   *
   * @return {Promise<sequence>} Resolved with the ping list.
   */
  loadPendingPingList() {
    return TelemetryStorageImpl.loadPendingPingList();
  },

  /**
   * Returns a list of the currently pending pings in the format:
   * {
   *   id: <string>, // The pings UUID.
   *   lastModified: <number>, // Timestamp of the pings last modification.
   * }
   * This does not scan pending pings on disk.
   *
   * @return {sequence} The current pending ping list.
   */
  getPendingPingList() {
    return TelemetryStorageImpl.getPendingPingList();
  },

  /**
   * Save an aborted-session ping to disk. This goes to a special location so
   * it is not picked up as a pending ping.
   *
   * @param {object} ping The ping data to save.
   * @return {promise} Promise that is resolved when the ping is successfully saved.
   */
  saveAbortedSessionPing(ping) {
    return TelemetryStorageImpl.saveAbortedSessionPing(ping);
  },

  /**
   * Load the aborted-session ping from disk if present.
   *
   * @return {promise<object>} Promise that is resolved with the ping data if found.
   *                           Otherwise returns null.
   */
  loadAbortedSessionPing() {
    return TelemetryStorageImpl.loadAbortedSessionPing();
  },

  /**
   * Remove the aborted-session ping if present.
   *
   * @return {promise} Promise that is resolved once the ping is removed.
   */
  removeAbortedSessionPing() {
    return TelemetryStorageImpl.removeAbortedSessionPing();
  },

  /**
   * Save an uninstall ping to disk, removing any old ones from this
   * installation first.
   * This is stored independently from other pings, and only read by
   * the Windows uninstaller.
   *
   * WINDOWS ONLY, does nothing and resolves immediately on other platforms.
   *
   * @return {promise} Promise that is resolved when the ping has been saved.
   */
  saveUninstallPing(ping) {
    return TelemetryStorageImpl.saveUninstallPing(ping);
  },

  /**
   * Remove all uninstall pings from this installation.
   *
   * WINDOWS ONLY, does nothing and resolves immediately on other platforms.
   *
   * @return {promise} Promise that is resolved when the pings have been removed.
   */
  removeUninstallPings() {
    return TelemetryStorageImpl.removeUninstallPings();
  },

  /**
   * Save a single ping to a file.
   *
   * @param {object} ping The content of the ping to save.
   * @param {string} file The destination file.
   * @param {bool} overwrite If |true|, the file will be overwritten if it exists,
   * if |false| the file will not be overwritten and no error will be reported if
   * the file exists.
   * @returns {promise}
   */
  savePingToFile(ping, file, overwrite) {
    return TelemetryStorageImpl.savePingToFile(ping, file, overwrite);
  },

  /**
   * Save a ping to its file.
   *
   * @param {object} ping The content of the ping to save.
   * @param {bool} overwrite If |true|, the file will be overwritten
   * if it exists.
   * @returns {promise}
   */
  savePing(ping, overwrite) {
    return TelemetryStorageImpl.savePing(ping, overwrite);
  },

  /**
   * Remove the file for a ping
   *
   * @param {object} ping The ping.
   * @returns {promise}
   */
  cleanupPingFile(ping) {
    return TelemetryStorageImpl.cleanupPingFile(ping);
  },

  /**
   * Loads a ping file.
   * @param {String} aFilePath The path of the ping file.
   * @return {Promise<Object>} A promise resolved with the ping content or rejected if the
   *                           ping contains invalid data.
   */
  async loadPingFile(aFilePath) {
    return TelemetryStorageImpl.loadPingFile(aFilePath);
  },

  /**
   * Remove FHR database files. This is temporary and will be dropped in
   * the future.
   * @return {Promise} Resolved when the database files are deleted.
   */
  removeFHRDatabase() {
    return TelemetryStorageImpl.removeFHRDatabase();
  },

  /**
   * Only used in tests, builds an archived ping path from the ping metadata.
   * @param {String} aPingId The ping id.
   * @param {Object} aDate The ping creation date.
   * @param {String} aType The ping type.
   * @return {String} The full path to the archived ping.
   */
  _testGetArchivedPingPath(aPingId, aDate, aType) {
    return getArchivedPingPath(aPingId, aDate, aType);
  },

  /**
   * Only used in tests, this helper extracts ping metadata from a given filename.
   *
   * @param fileName {String} The filename.
   * @return {Object} Null if the filename didn't match the expected form.
   *                  Otherwise an object with the extracted data in the form:
   *                  { timestamp: <number>,
   *                    id: <string>,
   *                    type: <string> }
   */
  _testGetArchivedPingDataFromFileName(aFileName) {
    return TelemetryStorageImpl._getArchivedPingDataFromFileName(aFileName);
  },

  /**
   * Only used in tests, this helper allows cleaning up the pending ping storage.
   */
  testClearPendingPings() {
    return TelemetryStorageImpl.runRemovePendingPingsTask();
  },
};

/**
 * This object allows the serialisation of asynchronous tasks. This is particularly
 * useful to serialise write access to the disk in order to prevent race conditions
 * to corrupt the data being written.
 * We are using this to synchronize saving to the file that TelemetrySession persists
 * its state in.
 */
function SaveSerializer() {
  this._queuedOperations = [];
  this._queuedInProgress = false;
  this._log = Log.repository.getLoggerWithMessagePrefix(
    LOGGER_NAME,
    LOGGER_PREFIX
  );
}

SaveSerializer.prototype = {
  /**
   * Enqueues an operation to a list to serialise their execution in order to prevent race
   * conditions. Useful to serialise access to disk.
   *
   * @param {Function} aFunction The task function to enqueue. It must return a promise.
   * @return {Promise} A promise resolved when the enqueued task completes.
   */
  enqueueTask(aFunction) {
    let promise = new Promise((resolve, reject) =>
      this._queuedOperations.push([aFunction, resolve, reject])
    );

    if (this._queuedOperations.length == 1) {
      this._popAndPerformQueuedOperation();
    }
    return promise;
  },

  /**
   * Make sure to flush all the pending operations.
   * @return {Promise} A promise resolved when all the pending operations have completed.
   */
  flushTasks() {
    let dummyTask = () => new Promise(resolve => resolve());
    return this.enqueueTask(dummyTask);
  },

  /**
   * Pop a task from the queue, executes it and continue to the next one.
   * This function recursively pops all the tasks.
   */
  _popAndPerformQueuedOperation() {
    if (!this._queuedOperations.length || this._queuedInProgress) {
      return;
    }

    this._log.trace(
      "_popAndPerformQueuedOperation - Performing queued operation."
    );
    let [func, resolve, reject] = this._queuedOperations.shift();
    let promise;

    try {
      this._queuedInProgress = true;
      promise = func();
    } catch (ex) {
      this._log.warn(
        "_popAndPerformQueuedOperation - Queued operation threw during execution. ",
        ex
      );
      this._queuedInProgress = false;
      reject(ex);
      this._popAndPerformQueuedOperation();
      return;
    }

    if (!promise || typeof promise.then != "function") {
      let msg = "Queued operation did not return a promise: " + func;
      this._log.warn("_popAndPerformQueuedOperation - " + msg);

      this._queuedInProgress = false;
      reject(new Error(msg));
      this._popAndPerformQueuedOperation();
      return;
    }

    promise.then(
      result => {
        this._queuedInProgress = false;
        resolve(result);
        this._popAndPerformQueuedOperation();
      },
      error => {
        this._log.warn(
          "_popAndPerformQueuedOperation - Failure when performing queued operation.",
          error
        );
        this._queuedInProgress = false;
        reject(error);
        this._popAndPerformQueuedOperation();
      }
    );
  },
};

var TelemetryStorageImpl = {
  _logger: null,
  // Used to serialize aborted session ping writes to disk.
  _abortedSessionSerializer: new SaveSerializer(),
  // Used to serialize session state writes to disk.
  _stateSaveSerializer: new SaveSerializer(),

  // Tracks the archived pings in a Map of (id -> {timestampCreated, type}).
  // We use this to cache info on archived pings to avoid scanning the disk more than once.
  _archivedPings: new Map(),
  // A set of promises for pings currently being archived
  _activelyArchiving: new Set(),
  // Track the archive loading task to prevent multiple tasks from being executed.
  _scanArchiveTask: null,
  // Track the archive cleanup task.
  _cleanArchiveTask: null,
  // Whether we already scanned the archived pings on disk.
  _scannedArchiveDirectory: false,

  // Track the pending ping removal task.
  _removePendingPingsTask: null,

  // This tracks all the pending async ping save activity.
  _activePendingPingSaves: new Set(),

  // Tracks the pending pings in a Map of (id -> {timestampCreated, type}).
  // We use this to cache info on pending pings to avoid scanning the disk more than once.
  _pendingPings: new Map(),

  // Track the pending pings enforce quota task.
  _enforcePendingPingsQuotaTask: null,

  // Track the shutdown process to bail out of the clean up task quickly.
  _shutdown: false,

  get _log() {
    if (!this._logger) {
      this._logger = Log.repository.getLoggerWithMessagePrefix(
        LOGGER_NAME,
        LOGGER_PREFIX
      );
    }

    return this._logger;
  },

  /**
   * Shutdown & block on any outstanding async activity in this module.
   *
   * @return {Promise} Promise that is resolved when shutdown is complete.
   */
  async shutdown() {
    this._shutdown = true;

    // If the following tasks are still running, block on them. They will bail out as soon
    // as possible.
    await this._abortedSessionSerializer.flushTasks().catch(ex => {
      this._log.error("shutdown - failed to flush aborted-session writes", ex);
    });

    if (this._cleanArchiveTask) {
      await this._cleanArchiveTask.catch(ex => {
        this._log.error("shutdown - the archive cleaning task failed", ex);
      });
    }

    if (this._enforcePendingPingsQuotaTask) {
      await this._enforcePendingPingsQuotaTask.catch(ex => {
        this._log.error("shutdown - the pending pings quota task failed", ex);
      });
    }

    if (this._removePendingPingsTask) {
      await this._removePendingPingsTask.catch(ex => {
        this._log.error("shutdown - the pending pings removal task failed", ex);
      });
    }

    // Wait on pending pings still being saved. While IOUtils should have shutdown
    // blockers in place, we a) have seen weird errors being reported that might
    // indicate a bad shutdown path and b) might have completion handlers hanging
    // off the save operations that don't expect to be late in shutdown.
    await this.promisePendingPingSaves();
  },

  /**
   * Save an archived ping to disk.
   *
   * @param {object} ping The ping data to archive.
   * @return {promise} Promise that is resolved when the ping is successfully archived.
   */
  saveArchivedPing(ping) {
    let promise = this._saveArchivedPingTask(ping);
    this._activelyArchiving.add(promise);
    promise.then(
      () => {
        this._activelyArchiving.delete(promise);
      },
      () => {
        this._activelyArchiving.delete(promise);
      }
    );
    return promise;
  },

  async _saveArchivedPingTask(ping) {
    const creationDate = new Date(ping.creationDate);
    if (this._archivedPings.has(ping.id)) {
      const data = this._archivedPings.get(ping.id);
      if (data.timestampCreated > creationDate.getTime()) {
        this._log.error(
          "saveArchivedPing - trying to overwrite newer ping with the same id"
        );
        return Promise.reject(
          new Error("trying to overwrite newer ping with the same id")
        );
      }
      this._log.warn(
        "saveArchivedPing - overwriting older ping with the same id"
      );
    }

    // Get the archived ping path and append the lz4 suffix to it (so we have 'jsonlz4').
    const filePath =
      getArchivedPingPath(ping.id, creationDate, ping.type) + "lz4";
    await IOUtils.makeDirectory(PathUtils.parent(filePath));
    await this.savePingToFile(
      ping,
      filePath,
      /* overwrite*/ true,
      /* compressed*/ true
    );

    this._archivedPings.set(ping.id, {
      timestampCreated: creationDate.getTime(),
      type: internString(ping.type),
    });

    Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SESSION_PING_COUNT").add();
    return undefined;
  },

  /**
   * Load an archived ping from disk.
   *
   * @param {string} id The pings id.
   * @return {promise<object>} Promise that is resolved with the ping data.
   */
  async loadArchivedPing(id) {
    const data = this._archivedPings.get(id);
    if (!data) {
      this._log.trace("loadArchivedPing - no ping with id: " + id);
      return Promise.reject(
        new Error("TelemetryStorage.loadArchivedPing - no ping with id " + id)
      );
    }

    const path = getArchivedPingPath(
      id,
      new Date(data.timestampCreated),
      data.type
    );
    const pathCompressed = path + "lz4";

    // Purge pings which are too big.
    let checkSize = async function (path) {
      const fileSize = await IOUtils.stat(path).then(info => info.size);
      if (fileSize > PING_FILE_MAXIMUM_SIZE_BYTES) {
        Telemetry.getHistogramById(
          "TELEMETRY_DISCARDED_ARCHIVED_PINGS_SIZE_MB"
        ).add(Math.floor(fileSize / 1024 / 1024));
        Telemetry.getHistogramById(
          "TELEMETRY_PING_SIZE_EXCEEDED_ARCHIVED"
        ).add();
        await IOUtils.remove(path, { ignoreAbsent: true });
        throw new Error(
          `loadArchivedPing - exceeded the maximum ping size: ${fileSize}`
        );
      }
    };

    let ping;
    try {
      // Try to load a compressed version of the archived ping first.
      this._log.trace(
        "loadArchivedPing - loading ping from: " + pathCompressed
      );
      await checkSize(pathCompressed);
      ping = await this.loadPingFile(pathCompressed, /* compressed*/ true);
    } catch (ex) {
      if (!ex.becauseNoSuchFile) {
        throw ex;
      }
      // If that fails, look for the uncompressed version.
      this._log.trace(
        "loadArchivedPing - compressed ping not found, loading: " + path
      );
      await checkSize(path);
      ping = await this.loadPingFile(path, /* compressed*/ false);
    }

    return ping;
  },

  /**
   * Saves session data to disk.
   */
  saveSessionData(sessionData) {
    return this._stateSaveSerializer.enqueueTask(() =>
      this._saveSessionData(sessionData)
    );
  },

  async _saveSessionData(sessionData) {
    await IOUtils.makeDirectory(lazy.gDataReportingDir, {
      createAncestors: false,
    });

    let filePath = PathUtils.join(
      lazy.gDataReportingDir,
      SESSION_STATE_FILE_NAME
    );
    try {
      await IOUtils.writeJSON(filePath, sessionData);
    } catch (e) {
      this._log.error(
        `_saveSessionData - Failed to write session data to ${filePath}`,
        e
      );
      Telemetry.getHistogramById("TELEMETRY_SESSIONDATA_FAILED_SAVE").add(1);
    }
  },

  /**
   * Loads session data from the session data file.
   * @return {Promise<Object>} A promise resolved with an object on success,
   *                           with null otherwise.
   */
  loadSessionData() {
    return this._stateSaveSerializer.enqueueTask(() => this._loadSessionData());
  },

  async _loadSessionData() {
    const dataFile = PathUtils.join(
      PathUtils.profileDir,
      DATAREPORTING_DIR,
      SESSION_STATE_FILE_NAME
    );
    let content;
    try {
      content = await IOUtils.readUTF8(dataFile);
    } catch (ex) {
      this._log.info("_loadSessionData - can not load session data file", ex);
      Telemetry.getHistogramById("TELEMETRY_SESSIONDATA_FAILED_LOAD").add(1);
      return null;
    }

    let data;
    try {
      data = JSON.parse(content);
    } catch (ex) {
      this._log.error("_loadSessionData - failed to parse session data", ex);
      Telemetry.getHistogramById("TELEMETRY_SESSIONDATA_FAILED_PARSE").add(1);
      return null;
    }

    return data;
  },

  /**
   * Remove an archived ping from disk.
   *
   * @param {string} id The pings id.
   * @param {number} timestampCreated The pings creation timestamp.
   * @param {string} type The pings type.
   * @return {promise<object>} Promise that is resolved when the pings is removed.
   */
  async _removeArchivedPing(id, timestampCreated, type) {
    this._log.trace(
      "_removeArchivedPing - id: " +
        id +
        ", timestampCreated: " +
        timestampCreated +
        ", type: " +
        type
    );
    const path = getArchivedPingPath(id, new Date(timestampCreated), type);
    const pathCompressed = path + "lz4";

    this._log.trace("_removeArchivedPing - removing ping from: " + path);
    await IOUtils.remove(path);
    await IOUtils.remove(pathCompressed);
    // Remove the ping from the cache.
    this._archivedPings.delete(id);
  },

  /**
   * Clean the pings archive by removing old pings.
   *
   * @return {Promise} Resolved when the cleanup task completes.
   */
  runCleanPingArchiveTask() {
    // If there's an archive cleaning task already running, return it.
    if (this._cleanArchiveTask) {
      return this._cleanArchiveTask;
    }

    // Make sure to clear |_cleanArchiveTask| once done.
    let clear = () => (this._cleanArchiveTask = null);
    // Since there's no archive cleaning task running, start it.
    this._cleanArchiveTask = this._cleanArchive().then(clear, clear);
    return this._cleanArchiveTask;
  },

  /**
   * Removes pings which are too old from the pings archive.
   * @return {Promise} Resolved when the ping age check is complete.
   */
  async _purgeOldPings() {
    this._log.trace("_purgeOldPings");

    const nowDate = Policy.now();
    const startTimeStamp = nowDate.getTime();

    // Keep track of the newest removed month to update the cache, if needed.
    let newestRemovedMonthTimestamp = null;
    let evictedDirsCount = 0;
    let maxDirAgeInMonths = 0;

    // Walk through the monthly subdirs of the form <YYYY-MM>/
    for (const path of await IOUtils.getChildren(lazy.gPingsArchivePath)) {
      const info = await IOUtils.stat(path);
      if (info.type !== "directory") {
        continue;
      }

      const name = PathUtils.filename(path);

      if (this._shutdown) {
        this._log.trace(
          "_purgeOldPings - Terminating the clean up task due to shutdown"
        );
        return;
      }

      if (!isValidArchiveDir(name)) {
        this._log.warn(
          `_purgeOldPings - skipping invalidly named subdirectory ${path}`
        );
        continue;
      }

      const archiveDate = getDateFromArchiveDir(name);
      if (!archiveDate) {
        this._log.warn(
          `_purgeOldPings - skipping invalid subdirectory date ${path}`
        );
        continue;
      }

      // If this archive directory is older than allowed, remove it.
      if (
        startTimeStamp - archiveDate.getTime() >
        MAX_ARCHIVED_PINGS_RETENTION_MS
      ) {
        try {
          await IOUtils.remove(path, { recursive: true });
          evictedDirsCount++;

          // Update the newest removed month.
          newestRemovedMonthTimestamp = Math.max(
            archiveDate,
            newestRemovedMonthTimestamp
          );
        } catch (ex) {
          this._log.error(`_purgeOldPings - Unable to remove ${path}`, ex);
        }
      } else {
        // We're not removing this directory, so record the age for the oldest directory.
        const dirAgeInMonths = Utils.getElapsedTimeInMonths(
          archiveDate,
          nowDate
        );
        maxDirAgeInMonths = Math.max(dirAgeInMonths, maxDirAgeInMonths);
      }
    }

    // Trigger scanning of the archived pings.
    await this.loadArchivedPingList();

    // Refresh the cache: we could still skip this, but it's cheap enough to keep it
    // to avoid introducing task dependencies.
    if (newestRemovedMonthTimestamp) {
      // Scan the archive cache for pings older than the newest directory pruned above.
      for (let [id, info] of this._archivedPings) {
        const timestampCreated = new Date(info.timestampCreated);
        if (timestampCreated.getTime() > newestRemovedMonthTimestamp) {
          continue;
        }
        // Remove outdated pings from the cache.
        this._archivedPings.delete(id);
      }
    }

    const endTimeStamp = Policy.now().getTime();

    // Save the time it takes to evict old directories and the eviction count.
    Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTED_OLD_DIRS").add(
      evictedDirsCount
    );
    Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTING_DIRS_MS").add(
      Math.ceil(endTimeStamp - startTimeStamp)
    );
    Telemetry.getHistogramById("TELEMETRY_ARCHIVE_OLDEST_DIRECTORY_AGE").add(
      maxDirAgeInMonths
    );
  },

  /**
   * Enforce a disk quota for the pings archive.
   * @return {Promise} Resolved when the quota check is complete.
   */
  async _enforceArchiveQuota() {
    this._log.trace("_enforceArchiveQuota");
    let startTimeStamp = Policy.now().getTime();

    // Build an ordered list, from newer to older, of archived pings.
    let pingList = Array.from(this._archivedPings, p => ({
      id: p[0],
      timestampCreated: p[1].timestampCreated,
      type: p[1].type,
    }));

    pingList.sort((a, b) => b.timestampCreated - a.timestampCreated);

    // If our archive is too big, we should reduce it to reach 90% of the quota.
    const SAFE_QUOTA = Policy.getArchiveQuota() * 0.9;
    // The index of the last ping to keep. Pings older than this one will be deleted if
    // the archive exceeds the quota.
    let lastPingIndexToKeep = null;
    let archiveSizeInBytes = 0;

    // Find the disk size of the archive.
    for (let i = 0; i < pingList.length; i++) {
      if (this._shutdown) {
        this._log.trace(
          "_enforceArchiveQuota - Terminating the clean up task due to shutdown"
        );
        return;
      }

      let ping = pingList[i];

      // Get the size for this ping.
      const fileSize = await getArchivedPingSize(
        ping.id,
        new Date(ping.timestampCreated),
        ping.type
      );
      if (!fileSize) {
        this._log.warn(
          "_enforceArchiveQuota - Unable to find the size of ping " + ping.id
        );
        continue;
      }

      // Enforce a maximum file size limit on archived pings.
      if (fileSize > PING_FILE_MAXIMUM_SIZE_BYTES) {
        this._log.error(
          "_enforceArchiveQuota - removing file exceeding size limit, size: " +
            fileSize
        );
        // We just remove the ping from the disk, we don't bother removing it from pingList
        // since it won't contribute to the quota.
        await this._removeArchivedPing(
          ping.id,
          ping.timestampCreated,
          ping.type
        ).catch(() =>
          this._log.error(
            "_enforceArchiveQuota - failed to remove archived ping" + ping.id
          )
        );
        Telemetry.getHistogramById(
          "TELEMETRY_DISCARDED_ARCHIVED_PINGS_SIZE_MB"
        ).add(Math.floor(fileSize / 1024 / 1024));
        Telemetry.getHistogramById(
          "TELEMETRY_PING_SIZE_EXCEEDED_ARCHIVED"
        ).add();
        continue;
      }

      archiveSizeInBytes += fileSize;

      if (archiveSizeInBytes < SAFE_QUOTA) {
        // We save the index of the last ping which is ok to keep in order to speed up ping
        // pruning.
        lastPingIndexToKeep = i;
      } else if (archiveSizeInBytes > Policy.getArchiveQuota()) {
        // Ouch, our ping archive is too big. Bail out and start pruning!
        break;
      }
    }

    // Save the time it takes to check if the archive is over-quota.
    Telemetry.getHistogramById("TELEMETRY_ARCHIVE_CHECKING_OVER_QUOTA_MS").add(
      Math.round(Policy.now().getTime() - startTimeStamp)
    );

    let submitProbes = (sizeInMB, evictedPings, elapsedMs) => {
      Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SIZE_MB").add(sizeInMB);
      Telemetry.getHistogramById("TELEMETRY_ARCHIVE_EVICTED_OVER_QUOTA").add(
        evictedPings
      );
      Telemetry.getHistogramById(
        "TELEMETRY_ARCHIVE_EVICTING_OVER_QUOTA_MS"
      ).add(elapsedMs);
    };

    // Check if we're using too much space. If not, submit the archive size and bail out.
    if (archiveSizeInBytes < Policy.getArchiveQuota()) {
      submitProbes(Math.round(archiveSizeInBytes / 1024 / 1024), 0, 0);
      return;
    }

    this._log.info(
      "_enforceArchiveQuota - archive size: " +
        archiveSizeInBytes +
        "bytes" +
        ", safety quota: " +
        SAFE_QUOTA +
        "bytes"
    );

    startTimeStamp = Policy.now().getTime();
    let pingsToPurge = pingList.slice(lastPingIndexToKeep + 1);

    // Remove all the pings older than the last one which we are safe to keep.
    for (let ping of pingsToPurge) {
      if (this._shutdown) {
        this._log.trace(
          "_enforceArchiveQuota - Terminating the clean up task due to shutdown"
        );
        return;
      }

      // This list is guaranteed to be in order, so remove the pings at its
      // beginning (oldest).
      await this._removeArchivedPing(ping.id, ping.timestampCreated, ping.type);
    }

    const endTimeStamp = Policy.now().getTime();
    submitProbes(
      ARCHIVE_SIZE_PROBE_SPECIAL_VALUE,
      pingsToPurge.length,
      Math.ceil(endTimeStamp - startTimeStamp)
    );
  },

  async _cleanArchive() {
    this._log.trace("cleanArchiveTask");

    if (!(await IOUtils.exists(lazy.gPingsArchivePath))) {
      return;
    }

    // Remove pings older than allowed.
    try {
      await this._purgeOldPings();
    } catch (ex) {
      this._log.error(
        "_cleanArchive - There was an error removing old directories",
        ex
      );
    }

    // Make sure we respect the archive disk quota.
    await this._enforceArchiveQuota();
  },

  /**
   * Run the task to enforce the pending pings quota.
   *
   * @return {Promise} Resolved when the cleanup task completes.
   */
  async runEnforcePendingPingsQuotaTask() {
    // If there's a cleaning task already running, return it.
    if (this._enforcePendingPingsQuotaTask) {
      return this._enforcePendingPingsQuotaTask;
    }

    // Since there's no quota enforcing task running, start it.
    try {
      this._enforcePendingPingsQuotaTask = this._enforcePendingPingsQuota();
      await this._enforcePendingPingsQuotaTask;
    } finally {
      this._enforcePendingPingsQuotaTask = null;
    }
    return undefined;
  },

  /**
   * Enforce a disk quota for the pending pings.
   * @return {Promise} Resolved when the quota check is complete.
   */
  async _enforcePendingPingsQuota() {
    this._log.trace("_enforcePendingPingsQuota");
    let startTimeStamp = Policy.now().getTime();

    // Build an ordered list, from newer to older, of pending pings.
    let pingList = Array.from(this._pendingPings, p => ({
      id: p[0],
      lastModified: p[1].lastModified,
    }));

    pingList.sort((a, b) => b.lastModified - a.lastModified);

    // If our pending pings directory is too big, we should reduce it to reach 90% of the quota.
    const SAFE_QUOTA = Policy.getPendingPingsQuota() * 0.9;
    // The index of the last ping to keep. Pings older than this one will be deleted if
    // the pending pings directory size exceeds the quota.
    let lastPingIndexToKeep = null;
    let pendingPingsSizeInBytes = 0;

    // Find the disk size of the pending pings directory.
    for (let i = 0; i < pingList.length; i++) {
      if (this._shutdown) {
        this._log.trace(
          "_enforcePendingPingsQuota - Terminating the clean up task due to shutdown"
        );
        return;
      }

      let ping = pingList[i];

      // Get the size for this ping.
      const fileSize = await getPendingPingSize(ping.id);
      if (!fileSize) {
        this._log.warn(
          "_enforcePendingPingsQuota - Unable to find the size of ping " +
            ping.id
        );
        continue;
      }

      pendingPingsSizeInBytes += fileSize;
      if (pendingPingsSizeInBytes < SAFE_QUOTA) {
        // We save the index of the last ping which is ok to keep in order to speed up ping
        // pruning.
        lastPingIndexToKeep = i;
      } else if (pendingPingsSizeInBytes > Policy.getPendingPingsQuota()) {
        // Ouch, our pending pings directory size is too big. Bail out and start pruning!
        break;
      }
    }

    // Save the time it takes to check if the pending pings are over-quota.
    Telemetry.getHistogramById("TELEMETRY_PENDING_CHECKING_OVER_QUOTA_MS").add(
      Math.round(Policy.now().getTime() - startTimeStamp)
    );

    let recordHistograms = (sizeInMB, evictedPings, elapsedMs) => {
      Telemetry.getHistogramById("TELEMETRY_PENDING_PINGS_SIZE_MB").add(
        sizeInMB
      );
      Telemetry.getHistogramById(
        "TELEMETRY_PENDING_PINGS_EVICTED_OVER_QUOTA"
      ).add(evictedPings);
      Telemetry.getHistogramById(
        "TELEMETRY_PENDING_EVICTING_OVER_QUOTA_MS"
      ).add(elapsedMs);
    };

    // Check if we're using too much space. If not, bail out.
    if (pendingPingsSizeInBytes < Policy.getPendingPingsQuota()) {
      recordHistograms(Math.round(pendingPingsSizeInBytes / 1024 / 1024), 0, 0);
      return;
    }

    this._log.info(
      "_enforcePendingPingsQuota - size: " +
        pendingPingsSizeInBytes +
        "bytes" +
        ", safety quota: " +
        SAFE_QUOTA +
        "bytes"
    );

    startTimeStamp = Policy.now().getTime();
    let pingsToPurge = pingList.slice(lastPingIndexToKeep + 1);

    // Remove all the pings older than the last one which we are safe to keep.
    for (let ping of pingsToPurge) {
      if (this._shutdown) {
        this._log.trace(
          "_enforcePendingPingsQuota - Terminating the clean up task due to shutdown"
        );
        return;
      }

      // This list is guaranteed to be in order, so remove the pings at its
      // beginning (oldest).
      await this.removePendingPing(ping.id);
    }

    const endTimeStamp = Policy.now().getTime();
    // We don't know the size of the pending pings directory if we are above the quota,
    // since we stop scanning once we reach the quota. We use a special value to show
    // this condition.
    recordHistograms(
      PENDING_PINGS_SIZE_PROBE_SPECIAL_VALUE,
      pingsToPurge.length,
      Math.ceil(endTimeStamp - startTimeStamp)
    );
  },

  /**
   * Reset the storage state in tests.
   */
  reset() {
    this._shutdown = false;
    this._scannedArchiveDirectory = false;
    this._archivedPings = new Map();
    this._scannedPendingDirectory = false;
    this._pendingPings = new Map();
  },

  /**
   * Get a list of info on the archived pings.
   * This will scan the archive directory and grab basic data about the existing
   * pings out of their filename.
   *
   * @return {promise<sequence<object>>}
   */
  async loadArchivedPingList() {
    // If there's an archive loading task already running, return it.
    if (this._scanArchiveTask) {
      return this._scanArchiveTask;
    }

    await waitForAll(this._activelyArchiving);

    if (this._scannedArchiveDirectory) {
      this._log.trace(
        "loadArchivedPingList - Archive already scanned, hitting cache."
      );
      return this._archivedPings;
    }

    // Since there's no archive loading task running, start it.
    let result;
    try {
      this._scanArchiveTask = this._scanArchive();
      result = await this._scanArchiveTask;
    } finally {
      this._scanArchiveTask = null;
    }
    return result;
  },

  async _scanArchive() {
    this._log.trace("_scanArchive");

    let submitProbes = (pingCount, dirCount) => {
      Telemetry.getHistogramById("TELEMETRY_ARCHIVE_SCAN_PING_COUNT").add(
        pingCount
      );
      Telemetry.getHistogramById("TELEMETRY_ARCHIVE_DIRECTORIES_COUNT").add(
        dirCount
      );
    };

    if (!(await IOUtils.exists(lazy.gPingsArchivePath))) {
      submitProbes(0, 0);
      return new Map();
    }

    let subDirCount = 0;
    // Walk through the monthly subdirs of the form <YYYY-MM>/
    for (const path of await IOUtils.getChildren(lazy.gPingsArchivePath)) {
      const info = await IOUtils.stat(path);

      if (info.type !== "directory") {
        continue;
      }

      const name = PathUtils.filename(path);
      if (!isValidArchiveDir(name)) {
        continue;
      }

      subDirCount++;

      this._log.trace(`_scanArchive - checking in subdir: ${path}`);
      const pingPaths = [];
      for (const ping of await IOUtils.getChildren(path)) {
        const info = await IOUtils.stat(ping);
        if (info.type !== "directory") {
          pingPaths.push(ping);
        }
      }

      // Now process any ping files of the form "<timestamp>.<uuid>.<type>.[json|jsonlz4]".
      for (const path of pingPaths) {
        const filename = PathUtils.filename(path);
        // data may be null if the filename doesn't match the above format.
        let data = this._getArchivedPingDataFromFileName(filename);
        if (!data) {
          continue;
        }

        // In case of conflicts, overwrite only with newer pings.
        if (this._archivedPings.has(data.id)) {
          const overwrite =
            data.timestamp > this._archivedPings.get(data.id).timestampCreated;
          this._log.warn(
            `_scanArchive - have seen this id before: ${data.id}, overwrite: ${overwrite}`
          );
          if (!overwrite) {
            continue;
          }

          await this._removeArchivedPing(
            data.id,
            data.timestampCreated,
            data.type
          ).catch(e =>
            this._log.warn("_scanArchive - failed to remove ping", e)
          );
        }

        this._archivedPings.set(data.id, {
          timestampCreated: data.timestamp,
          type: internString(data.type),
        });
      }
    }

    // Mark the archive as scanned, so we no longer hit the disk.
    this._scannedArchiveDirectory = true;
    // Update the ping and directories count histograms.
    submitProbes(this._archivedPings.size, subDirCount);
    return this._archivedPings;
  },

  /**
   * Save a single ping to a file.
   *
   * @param {object} ping The content of the ping to save.
   * @param {string} file The destination file.
   * @param {bool} overwrite If |true|, the file will be overwritten if it exists,
   * if |false| the file will not be overwritten and no error will be reported if
   * the file exists.
   * @param {bool} [compress=false] If |true|, the file will use lz4 compression. Otherwise no
   * compression will be used.
   * @returns {promise}
   */
  async savePingToFile(ping, filePath, overwrite, compress = false) {
    try {
      this._log.trace("savePingToFile - path: " + filePath);
      await IOUtils.writeJSON(filePath, ping, {
        compress,
        mode: overwrite ? "overwrite" : "create",
        tmpPath: `${filePath}.tmp`,
      });
    } catch (e) {
      if (
        !DOMException.isInstance(e) ||
        e.name !== "NoModificationAllowedError"
      ) {
        throw e;
      }
    }
  },

  /**
   * Save a ping to its file.
   *
   * @param {object} ping The content of the ping to save.
   * @param {bool} overwrite If |true|, the file will be overwritten
   * if it exists.
   * @returns {promise}
   */
  async savePing(ping, overwrite) {
    await getPingDirectory();
    let file = pingFilePath(ping);
    await this.savePingToFile(ping, file, overwrite);
    return file;
  },

  /**
   * Remove the file for a ping
   *
   * @param {object} ping The ping.
   * @returns {promise}
   */
  cleanupPingFile(ping) {
    return IOUtils.remove(pingFilePath(ping));
  },

  savePendingPing(ping) {
    let p = this.savePing(ping, true).then(path => {
      this._pendingPings.set(ping.id, {
        path,
        lastModified: Policy.now().getTime(),
      });
      this._log.trace("savePendingPing - saved ping with id " + ping.id);
    });
    this._trackPendingPingSaveTask(p);
    return p;
  },

  async loadPendingPing(id) {
    this._log.trace("loadPendingPing - id: " + id);
    let info = this._pendingPings.get(id);
    if (!info) {
      this._log.trace("loadPendingPing - unknown id " + id);
      throw new Error(
        "TelemetryStorage.loadPendingPing - no ping with id " + id
      );
    }

    // Try to get the dimension of the ping. If that fails, update the histograms.
    let fileSize = 0;
    try {
      fileSize = await IOUtils.stat(info.path).then(stat => stat.size);
    } catch (e) {
      if (!DOMException.isInstance(e) || e.name !== "NotFoundError") {
        throw e;
      }
      // Fall through and let |loadPingFile| report the error.
    }

    // Purge pings which are too big.
    if (fileSize > PING_FILE_MAXIMUM_SIZE_BYTES) {
      await this.removePendingPing(id);
      Telemetry.getHistogramById(
        "TELEMETRY_DISCARDED_PENDING_PINGS_SIZE_MB"
      ).add(Math.floor(fileSize / 1024 / 1024));
      Telemetry.getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_PENDING").add();

      // Currently we don't have the ping type available without loading the ping from disk.
      // Bug 1384903 will fix that.
      lazy.TelemetryHealthPing.recordDiscardedPing("<unknown>");
      throw new Error(
        "loadPendingPing - exceeded the maximum ping size: " + fileSize
      );
    }

    // Try to load the ping file. Update the related histograms on failure.
    let ping;
    try {
      ping = await this.loadPingFile(info.path, false);
    } catch (e) {
      // If we failed to load the ping, check what happened and update the histogram.
      if (e instanceof PingReadError) {
        Telemetry.getHistogramById("TELEMETRY_PENDING_LOAD_FAILURE_READ").add();
      } else if (e instanceof PingParseError) {
        Telemetry.getHistogramById(
          "TELEMETRY_PENDING_LOAD_FAILURE_PARSE"
        ).add();
      }

      // Remove the ping from the cache, so we don't try to load it again.
      this._pendingPings.delete(id);
      // Then propagate the rejection.
      throw e;
    }

    return ping;
  },

  removePendingPing(id) {
    let info = this._pendingPings.get(id);
    if (!info) {
      this._log.trace("removePendingPing - unknown id " + id);
      return Promise.resolve();
    }

    this._log.trace(
      "removePendingPing - deleting ping with id: " +
        id +
        ", path: " +
        info.path
    );
    this._pendingPings.delete(id);
    return IOUtils.remove(info.path).catch(ex =>
      this._log.error("removePendingPing - failed to remove ping", ex)
    );
  },

  /**
   * Track any pending ping save tasks through the promise passed here.
   * This is needed to block on any outstanding ping save activity.
   *
   * @param {Object<Promise>} The save promise to track.
   */
  _trackPendingPingSaveTask(promise) {
    let clear = () => this._activePendingPingSaves.delete(promise);
    promise.then(clear, clear);
    this._activePendingPingSaves.add(promise);
  },

  /**
   * Return a promise that allows to wait on pending pings being saved.
   * @return {Object<Promise>} A promise resolved when all the pending pings save promises
   *         are resolved.
   */
  promisePendingPingSaves() {
    // Make sure to wait for all the promises, even if they reject. We don't need to log
    // the failures here, as they are already logged elsewhere.
    return waitForAll(this._activePendingPingSaves);
  },

  /**
   * Run the task to remove all the pending pings
   *
   * @return {Promise} Resolved when the pings are removed.
   */
  async runRemovePendingPingsTask() {
    // If we already have a pending pings removal task active, return that.
    if (this._removePendingPingsTask) {
      return this._removePendingPingsTask;
    }

    // Start the task to remove all pending pings. Also make sure to clear the task once done.
    try {
      this._removePendingPingsTask = this.removePendingPings();
      await this._removePendingPingsTask;
    } finally {
      this._removePendingPingsTask = null;
    }
    return undefined;
  },

  async removePendingPings() {
    this._log.trace("removePendingPings - removing all pending pings");

    // Wait on pending pings still being saved, so so we don't miss removing them.
    await this.promisePendingPingSaves();

    // Individually remove existing pings, so we don't interfere with operations expecting
    // the pending pings directory to exist.
    const directory = TelemetryStorage.pingDirectoryPath;

    if (!(await IOUtils.exists(directory))) {
      this._log.trace(
        "removePendingPings - the pending pings directory doesn't exist"
      );
      return;
    }

    for (const path of await IOUtils.getChildren(directory)) {
      let info;
      try {
        info = await IOUtils.stat(path);
      } catch (ex) {
        // It is possible there is another task removing a ping in between
        // reading the directory and calling stat.
        //
        // On Windows, attempting to call GetFileAttributesEx() on a file
        // pending deletion will result in ERROR_ACCESS_DENIED, which will
        // propagate to here as a NotAllowedError.
        if (
          DOMException.isInstance(ex) &&
          (ex.name === "NotFoundError" || ex.name === "NotAllowedError")
        ) {
          continue;
        }

        throw ex;
      }

      if (info.type === "directory") {
        continue;
      }

      try {
        await IOUtils.remove(path);
      } catch (ex) {
        this._log.error(
          `removePendingPings - failed to remove file ${path}`,
          ex
        );
        continue;
      }
    }
  },

  /**
   * Iterate through all pings in the userApplicationDataDir under the "Pending Pings" sub-directory
   * and yield each file.
   */
  async *_iterateAppDataPings() {
    this._log.trace("_iterateAppDataPings");

    let uAppDataDir;
    try {
      uAppDataDir = Services.dirsvc.get("UAppData", Ci.nsIFile);
    } catch (ex) {
      // The test suites might not create and define the "UAppData" directory.
      // We account for that here instead of manually going through each test using
      // telemetry to manually create the directory and define the constant.
      this._log.trace(
        "_iterateAppDataPings - userApplicationDataDir is not defined. Is this a test?"
      );
      return;
    }

    const appDataPendingPings = PathUtils.join(
      uAppDataDir.path,
      "Pending Pings"
    );

    // Check if appDataPendingPings exists and bail out if it doesn't.
    if (!(await IOUtils.exists(appDataPendingPings))) {
      this._log.trace(
        "_iterateAppDataPings - the AppData pending pings directory doesn't exist."
      );
      return;
    }

    // Iterate through the pending ping files.
    for (const path of await IOUtils.getChildren(appDataPendingPings)) {
      const info = await IOUtils.stat(path);
      if (info.type !== "directory") {
        yield path;
      }
    }
  },

  /**
   * Remove all pings that are stored in the userApplicationDataDir
   * under the "Pending Pings" sub-directory.
   */
  async removeAppDataPings() {
    this._log.trace("removeAppDataPings");

    for await (const path of this._iterateAppDataPings()) {
      try {
        await IOUtils.remove(path);
      } catch (ex) {
        this._log.error(
          `removeAppDataPings - failed to remove file ${path}`,
          ex
        );
      }
    }
  },

  /**
   * Migrate pings that are stored in the userApplicationDataDir
   * under the "Pending Pings" sub-directory.
   */
  async _migrateAppDataPings() {
    this._log.trace("_migrateAppDataPings");

    for await (const path of this._iterateAppDataPings()) {
      try {
        // Load the ping data from the original file.
        const pingData = await this.loadPingFile(path);

        // Save it among the pending pings in the user profile, overwrite on
        // ping id collision.
        await TelemetryStorage.savePing(pingData, true);
      } catch (ex) {
        this._log.error(
          `_migrateAppDataPings - failed to load or migrate file. Removing ${path}`,
          ex
        );
      }

      try {
        // Finally remove the file.
        await IOUtils.remove(path);
      } catch (ex) {
        this._log.error(
          `_migrateAppDataPings - failed to remove file ${path}`,
          ex
        );
      }
    }
  },

  loadPendingPingList() {
    // If we already have a pending scanning task active, return that.
    if (this._scanPendingPingsTask) {
      return this._scanPendingPingsTask;
    }

    if (this._scannedPendingDirectory) {
      this._log.trace(
        "loadPendingPingList - Pending already scanned, hitting cache."
      );
      return Promise.resolve(this._buildPingList());
    }

    // Since there's no pending pings scan task running, start it.
    // Also make sure to clear the task once done.
    this._scanPendingPingsTask = this._scanPendingPings().then(
      pings => {
        this._scanPendingPingsTask = null;
        return pings;
      },
      ex => {
        this._scanPendingPingsTask = null;
        throw ex;
      }
    );
    return this._scanPendingPingsTask;
  },

  getPendingPingList() {
    return this._buildPingList();
  },

  async _scanPendingPings() {
    this._log.trace("_scanPendingPings");

    // Before pruning the pending pings, migrate over the ones from the user
    // application data directory (mainly crash pings that failed to be sent).
    await this._migrateAppDataPings();

    const directory = TelemetryStorage.pingDirectoryPath;
    if (!(await IOUtils.exists(directory))) {
      return [];
    }

    const files = [];
    for (const path of await IOUtils.getChildren(directory)) {
      if (this._shutdown) {
        return [];
      }

      try {
        const info = await IOUtils.stat(path);
        if (info.type !== "directory") {
          files.push({ path, info });
        }
      } catch (ex) {
        this._log.error(`_scanPendingPings - failed to stat file ${path}`, ex);
        continue;
      }
    }

    for (const { path, info } of files) {
      if (this._shutdown) {
        return [];
      }

      // Enforce a maximum file size limit on pending pings.
      if (info.size > PING_FILE_MAXIMUM_SIZE_BYTES) {
        this._log.error(
          `_scanPendingPings - removing file exceeding size limit ${path}`
        );
        try {
          await IOUtils.remove(path);
        } catch (ex) {
          this._log.error(
            `_scanPendingPings - failed to remove file ${path}`,
            ex
          );
        } finally {
          Telemetry.getHistogramById(
            "TELEMETRY_DISCARDED_PENDING_PINGS_SIZE_MB"
          ).add(Math.floor(info.size / 1024 / 1024));
          Telemetry.getHistogramById(
            "TELEMETRY_PING_SIZE_EXCEEDED_PENDING"
          ).add();

          // Currently we don't have the ping type available without loading the ping from disk.
          // Bug 1384903 will fix that.
          lazy.TelemetryHealthPing.recordDiscardedPing("<unknown>");
        }
        continue;
      }

      let id = PathUtils.filename(path);
      if (!UUID_REGEX.test(id)) {
        this._log.trace(`_scanPendingPings - filename is not a UUID: ${id}`);
        id = Utils.generateUUID();
      }

      this._pendingPings.set(id, {
        path,
        lastModified: info.lastModified,
      });
    }

    this._scannedPendingDirectory = true;
    return this._buildPingList();
  },

  _buildPingList() {
    const list = Array.from(this._pendingPings, p => ({
      id: p[0],
      lastModified: p[1].lastModified,
    }));

    list.sort((a, b) => b.lastModified - a.lastModified);
    return list;
  },

  /**
   * Loads a ping file.
   * @param {String} aFilePath The path of the ping file.
   * @param {Boolean} [aCompressed=false] If |true|, expects the file to be compressed using lz4.
   * @return {Promise<Object>} A promise resolved with the ping content or rejected if the
   *                           ping contains invalid data.
   * @throws {PingReadError} There was an error while reading the ping file from the disk.
   * @throws {PingParseError} There was an error while parsing the JSON content of the ping file.
   */
  async loadPingFile(aFilePath, aCompressed = false) {
    let rawPing;
    try {
      rawPing = await IOUtils.readUTF8(aFilePath, { decompress: aCompressed });
    } catch (e) {
      this._log.trace(`loadPingfile - unreadable ping ${aFilePath}`, e);
      throw new PingReadError(
        e.message,
        DOMException.isInstance(e) && e.name === "NotFoundError"
      );
    }

    let ping;
    try {
      ping = JSON.parse(rawPing);
    } catch (e) {
      this._log.trace(`loadPingfile - unparseable ping ${aFilePath}`, e);
      await IOUtils.remove(aFilePath).catch(ex => {
        this._log.error(
          `loadPingFile - failed removing unparseable ping file ${aFilePath}`,
          ex
        );
      });
      throw new PingParseError(e.message);
    }

    return ping;
  },

  /**
   * Archived pings are saved with file names of the form:
   * "<timestamp>.<uuid>.<type>.[json|jsonlz4]"
   * This helper extracts that data from a given filename.
   *
   * @param fileName {String} The filename.
   * @return {Object} Null if the filename didn't match the expected form.
   *                  Otherwise an object with the extracted data in the form:
   *                  { timestamp: <number>,
   *                    id: <string>,
   *                    type: <string> }
   */
  _getArchivedPingDataFromFileName(fileName) {
    // Extract the parts.
    let parts = fileName.split(".");
    if (parts.length != 4) {
      this._log.trace("_getArchivedPingDataFromFileName - should have 4 parts");
      return null;
    }

    let [timestamp, uuid, type, extension] = parts;
    if (extension != "json" && extension != "jsonlz4") {
      this._log.trace(
        "_getArchivedPingDataFromFileName - should have 'json' or 'jsonlz4' extension"
      );
      return null;
    }

    // Check for a valid timestamp.
    timestamp = parseInt(timestamp);
    if (Number.isNaN(timestamp)) {
      this._log.trace(
        "_getArchivedPingDataFromFileName - should have a valid timestamp"
      );
      return null;
    }

    // Check for a valid UUID.
    if (!UUID_REGEX.test(uuid)) {
      this._log.trace(
        "_getArchivedPingDataFromFileName - should have a valid id"
      );
      return null;
    }

    // Check for a valid type string.
    const typeRegex = /^[a-z0-9][a-z0-9-]+[a-z0-9]$/i;
    if (!typeRegex.test(type)) {
      this._log.trace(
        "_getArchivedPingDataFromFileName - should have a valid type"
      );
      return null;
    }

    return {
      timestamp,
      id: uuid,
      type,
    };
  },

  async saveAbortedSessionPing(ping) {
    this._log.trace(
      "saveAbortedSessionPing - ping path: " + lazy.gAbortedSessionFilePath
    );
    await IOUtils.makeDirectory(lazy.gDataReportingDir);

    return this._abortedSessionSerializer.enqueueTask(() =>
      this.savePingToFile(ping, lazy.gAbortedSessionFilePath, true)
    );
  },

  async loadAbortedSessionPing() {
    let ping = null;
    try {
      ping = await this.loadPingFile(lazy.gAbortedSessionFilePath);
    } catch (ex) {
      if (ex.becauseNoSuchFile) {
        this._log.trace("loadAbortedSessionPing - no such file");
      } else {
        this._log.error("loadAbortedSessionPing - error loading ping", ex);
      }
    }
    return ping;
  },

  removeAbortedSessionPing() {
    return this._abortedSessionSerializer.enqueueTask(async () => {
      try {
        await IOUtils.remove(lazy.gAbortedSessionFilePath, {
          ignoreAbsent: false,
        });
        this._log.trace("removeAbortedSessionPing - success");
      } catch (ex) {
        if (DOMException.isInstance(ex) && ex.name === "NotFoundError") {
          this._log.trace("removeAbortedSessionPing - no such file");
        } else {
          this._log.error("removeAbortedSessionPing - error removing ping", ex);
        }
      }
    });
  },

  async saveUninstallPing(ping) {
    if (AppConstants.platform != "win") {
      return;
    }

    // Remove any old pings from this install first.
    await this.removeUninstallPings();

    let { directory: pingFile, file } = Policy.getUninstallPingPath(ping.id);
    pingFile.append(file);

    await this.savePingToFile(ping, pingFile.path, /* overwrite */ true);
  },

  async removeUninstallPings() {
    if (AppConstants.platform != "win") {
      return;
    }

    const { directory, file } = Policy.getUninstallPingPath("*");
    const [prefix, suffix] = file.split("*");

    for (const path of await IOUtils.getChildren(directory.path)) {
      const filename = PathUtils.filename(path);
      if (!filename.startsWith(prefix) || !filename.endsWith(suffix)) {
        continue;
      }

      this._log.trace("removeUninstallPings - removing", path);
      try {
        await IOUtils.remove(path);
        this._log.trace("removeUninstallPings - success");
      } catch (ex) {
        if (DOMException.isInstance(ex) && ex.name === "NotFoundError") {
          this._log.trace("removeUninstallPings - no such file");
        } else {
          this._log.error("removeUninstallPings - error removing ping", ex);
        }
      }
    }
  },

  /**
   * Remove FHR database files. This is temporary and will be dropped in
   * the future.
   * @return {Promise} Resolved when the database files are deleted.
   */
  async removeFHRDatabase() {
    this._log.trace("removeFHRDatabase");

    // Let's try to remove the FHR DB with the default filename first.
    const FHR_DB_DEFAULT_FILENAME = "healthreport.sqlite";

    // Even if it's uncommon, there may be 2 additional files: - a "write ahead log"
    // (-wal) file and a "shared memory file" (-shm). We need to remove them as well.
    let FILES_TO_REMOVE = [
      PathUtils.join(PathUtils.profileDir, FHR_DB_DEFAULT_FILENAME),
      PathUtils.join(PathUtils.profileDir, FHR_DB_DEFAULT_FILENAME + "-wal"),
      PathUtils.join(PathUtils.profileDir, FHR_DB_DEFAULT_FILENAME + "-shm"),
    ];

    // FHR could have used either the default DB file name or a custom one
    // through this preference.
    const FHR_DB_CUSTOM_FILENAME = Services.prefs.getStringPref(
      "datareporting.healthreport.dbName",
      undefined
    );
    if (FHR_DB_CUSTOM_FILENAME) {
      FILES_TO_REMOVE.push(
        PathUtils.join(PathUtils.profileDir, FHR_DB_CUSTOM_FILENAME),
        PathUtils.join(PathUtils.profileDir, FHR_DB_CUSTOM_FILENAME + "-wal"),
        PathUtils.join(PathUtils.profileDir, FHR_DB_CUSTOM_FILENAME + "-shm")
      );
    }

    for (let f of FILES_TO_REMOVE) {
      await IOUtils.remove(f).catch(e =>
        this._log.error(`removeFHRDatabase - failed to remove ${f}`, e)
      );
    }
  },
};

// Utility functions

function pingFilePath(ping) {
  // Support legacy ping formats, who don't have an "id" field, but a "slug" field.
  let pingIdentifier = ping.slug ? ping.slug : ping.id;

  if (typeof pingIdentifier === "undefined" || pingIdentifier === null) {
    throw new Error(
      "Incompatible ping format -- ping has no slug or id attribute"
    );
  }

  return PathUtils.join(TelemetryStorage.pingDirectoryPath, pingIdentifier);
}

function getPingDirectory() {
  return (async function () {
    let directory = TelemetryStorage.pingDirectoryPath;

    if (!(await IOUtils.exists(directory))) {
      await IOUtils.makeDirectory(directory, { permissions: 0o700 });
    }

    return directory;
  })();
}

/**
 * Build the path to the archived ping.
 * @param {String} aPingId The ping id.
 * @param {Object} aDate The ping creation date.
 * @param {String} aType The ping type.
 * @return {String} The full path to the archived ping.
 */
function getArchivedPingPath(aPingId, aDate, aType) {
  // Get the ping creation date and generate the archive directory to hold it. Note
  // that getMonth returns a 0-based month, so we need to add an offset.
  let month = String(aDate.getMonth() + 1);
  let archivedPingDir = PathUtils.join(
    lazy.gPingsArchivePath,
    aDate.getFullYear() + "-" + month.padStart(2, "0")
  );
  // Generate the archived ping file path as YYYY-MM/<TIMESTAMP>.UUID.type.json
  let fileName = [aDate.getTime(), aPingId, aType, "json"].join(".");
  return PathUtils.join(archivedPingDir, fileName);
}

/**
 * Get the size of the ping file on the disk.
 * @return {Integer} The file size, in bytes, of the ping file or 0 on errors.
 */
var getArchivedPingSize = async function (aPingId, aDate, aType) {
  const path = getArchivedPingPath(aPingId, aDate, aType);
  let filePaths = [path + "lz4", path];

  for (let path of filePaths) {
    try {
      return (await IOUtils.stat(path)).size;
    } catch (e) {}
  }

  // That's odd, this ping doesn't seem to exist.
  return 0;
};

/**
 * Get the size of the pending ping file on the disk.
 * @return {Integer} The file size, in bytes, of the ping file or 0 on errors.
 */
var getPendingPingSize = async function (aPingId) {
  const path = PathUtils.join(TelemetryStorage.pingDirectoryPath, aPingId);
  try {
    return (await IOUtils.stat(path)).size;
  } catch (e) {}

  // That's odd, this ping doesn't seem to exist.
  return 0;
};

/**
 * Check if a directory name is in the "YYYY-MM" format.
 * @param {String} aDirName The name of the pings archive directory.
 * @return {Boolean} True if the directory name is in the right format, false otherwise.
 */
function isValidArchiveDir(aDirName) {
  const dirRegEx = /^[0-9]{4}-[0-9]{2}$/;
  return dirRegEx.test(aDirName);
}

/**
 * Gets a date object from an archive directory name.
 * @param {String} aDirName The name of the pings archive directory. Must be in the YYYY-MM
 *        format.
 * @return {Object} A Date object or null if the dir name is not valid.
 */
function getDateFromArchiveDir(aDirName) {
  let [year, month] = aDirName.split("-");
  year = parseInt(year);
  month = parseInt(month);
  // Make sure to have sane numbers.
  if (
    !Number.isFinite(month) ||
    !Number.isFinite(year) ||
    month < 1 ||
    month > 12
  ) {
    return null;
  }
  return new Date(year, month - 1, 1, 0, 0, 0);
}
PK
!<H��6�6"modules/TelemetryScheduler.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Log } from "resource://gre/modules/Log.sys.mjs";

import { TelemetrySession } from "resource://gre/modules/TelemetrySession.sys.mjs";
import { TelemetryUtils } from "resource://gre/modules/TelemetryUtils.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetters(lazy, {
  idleService: ["@mozilla.org/widget/useridleservice;1", "nsIUserIdleService"],
});

const MIN_SUBSESSION_LENGTH_MS =
  Services.prefs.getIntPref("toolkit.telemetry.minSubsessionLength", 5 * 60) *
  1000;

const LOGGER_NAME = "Toolkit.Telemetry";

// Seconds of idle time before pinging.
// On idle-daily a gather-telemetry notification is fired, during it probes can
// start asynchronous tasks to gather data.
const IDLE_TIMEOUT_SECONDS = Services.prefs.getIntPref(
  "toolkit.telemetry.idleTimeout",
  5 * 60
);

// Execute a scheduler tick every 5 minutes.
const SCHEDULER_TICK_INTERVAL_MS =
  Services.prefs.getIntPref(
    "toolkit.telemetry.scheduler.tickInterval",
    5 * 60
  ) * 1000;
// When user is idle, execute a scheduler tick every 60 minutes.
const SCHEDULER_TICK_IDLE_INTERVAL_MS =
  Services.prefs.getIntPref(
    "toolkit.telemetry.scheduler.idleTickInterval",
    60 * 60
  ) * 1000;

// The maximum time (ms) until the tick should moved from the idle
// queue to the regular queue if it hasn't been executed yet.
const SCHEDULER_TICK_MAX_IDLE_DELAY_MS = 60 * 1000;

// The frequency at which we persist session data to the disk to prevent data loss
// in case of aborted sessions (currently 5 minutes).
const ABORTED_SESSION_UPDATE_INTERVAL_MS = 5 * 60 * 1000;

// The tolerance we have when checking if it's midnight (15 minutes).
const SCHEDULER_MIDNIGHT_TOLERANCE_MS = 15 * 60 * 1000;

/**
 * This is a policy object used to override behavior for testing.
 */
export var Policy = {
  now: () => new Date(),
  setSchedulerTickTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
  clearSchedulerTickTimeout: id => clearTimeout(id),
  prioEncode: (batchID, prioParams) => PrioEncoder.encode(batchID, prioParams),
};

/**
 * TelemetryScheduler contains a single timer driving all regularly-scheduled
 * Telemetry related jobs. Having a single place with this logic simplifies
 * reasoning about scheduling actions in a single place, making it easier to
 * coordinate jobs and coalesce them.
 */
export var TelemetryScheduler = {
  // Tracks the main ping
  _lastDailyPingTime: 0,
  // Tracks the aborted session ping
  _lastSessionCheckpointTime: 0,
  // Tracks all other pings at regular intervals
  _lastPeriodicPingTime: 0,

  _log: null,

  // The timer which drives the scheduler.
  _schedulerTimer: null,
  // The interval used by the scheduler timer.
  _schedulerInterval: 0,
  _shuttingDown: true,
  _isUserIdle: false,

  /**
   * Initialises the scheduler and schedules the first daily/aborted session pings.
   */
  init() {
    this._log = Log.repository.getLoggerWithMessagePrefix(
      LOGGER_NAME,
      "TelemetryScheduler::"
    );
    this._log.trace("init");
    this._shuttingDown = false;
    this._isUserIdle = false;

    // Initialize the last daily ping and aborted session last due times to the current time.
    // Otherwise, we might end up sending daily pings even if the subsession is not long enough.
    let now = Policy.now();
    this._lastDailyPingTime = now.getTime();
    this._lastPeriodicPingTime = now.getTime();
    this._lastSessionCheckpointTime = now.getTime();
    this._rescheduleTimeout();

    lazy.idleService.addIdleObserver(this, IDLE_TIMEOUT_SECONDS);
    Services.obs.addObserver(this, "wake_notification");
  },

  /**
   * Stops the scheduler.
   */
  shutdown() {
    if (this._shuttingDown) {
      if (this._log) {
        this._log.error("shutdown - Already shut down");
      } else {
        console.error("TelemetryScheduler.shutdown - Already shut down");
      }
      return;
    }

    this._log.trace("shutdown");
    if (this._schedulerTimer) {
      Policy.clearSchedulerTickTimeout(this._schedulerTimer);
      this._schedulerTimer = null;
    }

    lazy.idleService.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS);
    Services.obs.removeObserver(this, "wake_notification");

    this._shuttingDown = true;
  },

  // Reset some specific innards without shutting down and re-init'ing.
  // Test-only method.
  testReset() {
    this._idleDispatch?.cancel();
    this._idleDispatch = undefined;
  },

  _clearTimeout() {
    if (this._schedulerTimer) {
      Policy.clearSchedulerTickTimeout(this._schedulerTimer);
    }
  },

  /**
   * Reschedules the tick timer.
   */
  _rescheduleTimeout() {
    this._log.trace("_rescheduleTimeout - isUserIdle: " + this._isUserIdle);
    if (this._shuttingDown) {
      this._log.warn("_rescheduleTimeout - already shutdown");
      return;
    }

    this._clearTimeout();

    const now = Policy.now();
    let timeout = SCHEDULER_TICK_INTERVAL_MS;

    // When the user is idle we want to fire the timer less often.
    if (this._isUserIdle) {
      timeout = SCHEDULER_TICK_IDLE_INTERVAL_MS;
      // We need to make sure though that we don't miss sending pings around
      // midnight when we use the longer idle intervals.
      const nextMidnight = TelemetryUtils.getNextMidnight(now);
      timeout = Math.min(timeout, nextMidnight.getTime() - now.getTime());
    }

    this._log.trace(
      "_rescheduleTimeout - scheduling next tick for " +
        new Date(now.getTime() + timeout)
    );
    this._schedulerTimer = Policy.setSchedulerTickTimeout(
      () => this._onSchedulerTick(),
      timeout
    );
  },

  _sentPingToday(pingTime, nowDate) {
    // This is today's date and also the previous midnight (0:00).
    const todayDate = TelemetryUtils.truncateToDays(nowDate);
    // We consider a ping sent for today if it occured after or at 00:00 today.
    return pingTime >= todayDate.getTime();
  },

  /**
   * Checks if we can send a daily ping or not.
   * @param {Object} nowDate A date object.
   * @return {Boolean} True if we can send the daily ping, false otherwise.
   */
  _isDailyPingDue(nowDate) {
    // The daily ping is not due if we already sent one today.
    if (this._sentPingToday(this._lastDailyPingTime, nowDate)) {
      this._log.trace("_isDailyPingDue - already sent one today");
      return false;
    }

    // Avoid overly short sessions.
    const timeSinceLastDaily = nowDate.getTime() - this._lastDailyPingTime;
    if (timeSinceLastDaily < MIN_SUBSESSION_LENGTH_MS) {
      this._log.trace(
        "_isDailyPingDue - delaying daily to keep minimum session length"
      );
      return false;
    }

    this._log.trace("_isDailyPingDue - is due");
    return true;
  },

  /**
   * Checks if we can send a regular ping or not.
   * @param {Object} nowDate A date object.
   * @return {Boolean} True if we can send the regular pings, false otherwise.
   */
  _isPeriodicPingDue(nowDate) {
    // The periodic ping is not due if we already sent one today.
    if (this._sentPingToday(this._lastPeriodicPingTime, nowDate)) {
      this._log.trace("_isPeriodicPingDue - already sent one today");
      return false;
    }

    this._log.trace("_isPeriodicPingDue - is due");
    return true;
  },

  /**
   * An helper function to save an aborted-session ping.
   * @param {Number} now The current time, in milliseconds.
   * @param {Object} [competingPayload=null] If we are coalescing the daily and the
   *                 aborted-session pings, this is the payload for the former. Note
   *                 that the reason field of this payload will be changed.
   * @return {Promise} A promise resolved when the ping is saved.
   */
  _saveAbortedPing(now, competingPayload = null) {
    this._lastSessionCheckpointTime = now;
    return TelemetrySession.saveAbortedSessionPing(competingPayload).catch(e =>
      this._log.error("_saveAbortedPing - Failed", e)
    );
  },

  /**
   * The notifications handler.
   */
  observe(aSubject, aTopic) {
    this._log.trace("observe - aTopic: " + aTopic);
    switch (aTopic) {
      case "idle":
        // If the user is idle, increase the tick interval.
        this._isUserIdle = true;
        return this._onSchedulerTick();
      case "active":
        // User is back to work, restore the original tick interval.
        this._isUserIdle = false;
        return this._onSchedulerTick(true);
      case "wake_notification":
        // The machine woke up from sleep, trigger a tick to avoid sessions
        // spanning more than a day.
        // This is needed because sleep time does not count towards timeouts
        // on Mac & Linux - see bug 1262386, bug 1204823 et al.
        return this._onSchedulerTick(true);
    }
    return undefined;
  },

  /**
   * Creates an object with a method `dispatch` that will call `dispatchFn` unless
   * the method `cancel` is called beforehand.
   *
   * This is used to wrap main thread idle dispatch since it does not provide a
   * cancel mechanism.
   */
  _makeIdleDispatch(dispatchFn) {
    this._log.trace("_makeIdleDispatch");
    let fn = dispatchFn;
    let l = msg => this._log.trace(msg); // need to bind `this`
    return {
      cancel() {
        fn = undefined;
      },
      dispatch(resolve, reject) {
        l("_makeIdleDispatch.dispatch - !!fn: " + !!fn);
        if (!fn) {
          return Promise.resolve().then(resolve, reject);
        }
        return fn(resolve, reject);
      },
    };
  },

  /**
   * Performs a scheduler tick. This function manages Telemetry recurring operations.
   * @param {Boolean} [dispatchOnIdle=false] If true, the tick is dispatched in the
   *                  next idle cycle of the main thread.
   * @return {Promise} A promise, only used when testing, resolved when the scheduled
   *                   operation completes.
   */
  _onSchedulerTick(dispatchOnIdle = false) {
    this._log.trace("_onSchedulerTick - dispatchOnIdle: " + dispatchOnIdle);
    // This call might not be triggered from a timeout. In that case we don't want to
    // leave any previously scheduled timeouts pending.
    this._clearTimeout();

    if (this._idleDispatch) {
      this._idleDispatch.cancel();
    }

    if (this._shuttingDown) {
      this._log.warn("_onSchedulerTick - already shutdown.");
      return Promise.reject(new Error("Already shutdown."));
    }

    let promise = Promise.resolve();
    try {
      if (dispatchOnIdle) {
        this._idleDispatch = this._makeIdleDispatch((resolve, reject) => {
          this._log.trace(
            "_onSchedulerTick - ildeDispatchToMainThread dispatch"
          );
          return this._schedulerTickLogic().then(resolve, reject);
        });
        promise = new Promise((resolve, reject) =>
          Services.tm.idleDispatchToMainThread(() => {
            return this._idleDispatch
              ? this._idleDispatch.dispatch(resolve, reject)
              : Promise.resolve().then(resolve, reject);
          }, SCHEDULER_TICK_MAX_IDLE_DELAY_MS)
        );
      } else {
        promise = this._schedulerTickLogic();
      }
    } catch (e) {
      this._log.error("_onSchedulerTick - There was an exception", e);
    } finally {
      this._rescheduleTimeout();
    }

    // This promise is returned to make testing easier.
    return promise;
  },

  /**
   * Implements the scheduler logic.
   * @return {Promise} Resolved when the scheduled task completes. Only used in tests.
   */
  _schedulerTickLogic() {
    this._log.trace("_schedulerTickLogic");

    let nowDate = Policy.now();
    let now = nowDate.getTime();

    // Check if the daily ping is due.
    const shouldSendDaily = this._isDailyPingDue(nowDate);
    // Check if other regular pings are due.
    const shouldSendPeriodic = this._isPeriodicPingDue(nowDate);

    if (shouldSendPeriodic) {
      this._log.trace("_schedulerTickLogic - Periodic ping due.");
      this._lastPeriodicPingTime = now;
      // Send other pings.
      // ...currently no other pings exist
    }

    if (shouldSendDaily) {
      this._log.trace("_schedulerTickLogic - Daily ping due.");
      this._lastDailyPingTime = now;
      return TelemetrySession.sendDailyPing();
    }

    // Check if the aborted-session ping is due. If a daily ping was saved above, it was
    // already duplicated as an aborted-session ping.
    const isAbortedPingDue =
      now - this._lastSessionCheckpointTime >=
      ABORTED_SESSION_UPDATE_INTERVAL_MS;
    if (isAbortedPingDue) {
      this._log.trace("_schedulerTickLogic - Aborted session ping due.");
      return this._saveAbortedPing(now);
    }

    // No ping is due.
    this._log.trace("_schedulerTickLogic - No ping due.");
    return Promise.resolve();
  },

  /**
   * Re-schedule the daily ping if some other equivalent ping was sent.
   *
   * This is only called from TelemetrySession when a main ping with reason 'environment-change'
   * is sent.
   *
   * @param {Object} [payload] The payload of the ping that was sent,
   *                           to be stored as an aborted-session ping.
   */
  rescheduleDailyPing(payload) {
    if (this._shuttingDown) {
      this._log.error("rescheduleDailyPing - already shutdown");
      return;
    }

    this._log.trace("rescheduleDailyPing");
    let now = Policy.now();

    // We just generated an environment-changed ping, save it as an aborted session and
    // update the schedules.
    this._saveAbortedPing(now.getTime(), payload);

    // If we're close to midnight, skip today's daily ping and reschedule it for tomorrow.
    let nearestMidnight = TelemetryUtils.getNearestMidnight(
      now,
      SCHEDULER_MIDNIGHT_TOLERANCE_MS
    );
    if (nearestMidnight) {
      this._lastDailyPingTime = now.getTime();
    }
  },
};
PK
!<�YYmodules/EventPing.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This module sends Telemetry Events periodically:
 * https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/data/event-ping.html
 */

import { TelemetryUtils } from "resource://gre/modules/TelemetryUtils.sys.mjs";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "resource://gre/modules/Log.sys.mjs",
  TelemetryController: "resource://gre/modules/TelemetryController.sys.mjs",
  TelemetrySession: "resource://gre/modules/TelemetrySession.sys.mjs",
  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

const Utils = TelemetryUtils;

const MS_IN_A_MINUTE = 60 * 1000;

const DEFAULT_EVENT_LIMIT = 1000;
const DEFAULT_MIN_FREQUENCY_MS = 60 * MS_IN_A_MINUTE;
const DEFAULT_MAX_FREQUENCY_MS = 10 * MS_IN_A_MINUTE;

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetryEventPing::";

const EVENT_LIMIT_REACHED_TOPIC = "event-telemetry-storage-limit-reached";

export var Policy = {
  setTimeout: (callback, delayMs) => lazy.setTimeout(callback, delayMs),
  clearTimeout: id => lazy.clearTimeout(id),
  sendPing: (type, payload, options) =>
    lazy.TelemetryController.submitExternalPing(type, payload, options),
};

export var TelemetryEventPing = {
  Reason: Object.freeze({
    PERIODIC: "periodic", // Sent the ping containing events from the past periodic interval (default one hour).
    MAX: "max", // Sent the ping containing the maximum number (default 1000) of event records, earlier than the periodic interval.
    SHUTDOWN: "shutdown", // Recorded data was sent on shutdown.
  }),

  EVENT_PING_TYPE: "event",

  _logger: null,

  _testing: false,

  // So that if we quickly reach the max limit we can immediately send.
  _lastSendTime: -DEFAULT_MIN_FREQUENCY_MS,

  _processStartTimestamp: 0,

  get dataset() {
    return Services.telemetry.canRecordPrereleaseData
      ? Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS
      : Ci.nsITelemetry.DATASET_ALL_CHANNELS;
  },

  startup() {
    this._log.trace("Starting up.");

    // Calculate process creation once.
    this._processStartTimestamp =
      Math.round(
        (Date.now() - TelemetryUtils.monotonicNow()) / MS_IN_A_MINUTE
      ) * MS_IN_A_MINUTE;

    Services.obs.addObserver(this, EVENT_LIMIT_REACHED_TOPIC);

    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "maxFrequency",
      Utils.Preferences.EventPingMaximumFrequency,
      DEFAULT_MAX_FREQUENCY_MS
    );
    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "minFrequency",
      Utils.Preferences.EventPingMinimumFrequency,
      DEFAULT_MIN_FREQUENCY_MS
    );

    this._startTimer();
  },

  shutdown() {
    this._log.trace("Shutting down.");
    // removeObserver may throw, which could interrupt shutdown.
    try {
      Services.obs.removeObserver(this, EVENT_LIMIT_REACHED_TOPIC);
    } catch (ex) {}

    this._submitPing(this.Reason.SHUTDOWN, true /* discardLeftovers */);
    this._clearTimer();
  },

  observe(aSubject, aTopic) {
    switch (aTopic) {
      case EVENT_LIMIT_REACHED_TOPIC:
        this._log.trace("event limit reached");
        let now = Utils.monotonicNow();
        if (now - this._lastSendTime < this.maxFrequency) {
          this._log.trace("can't submit ping immediately as it's too soon");
          this._startTimer(
            this.maxFrequency - this._lastSendTime,
            this.Reason.MAX,
            true /* discardLeftovers*/
          );
        } else {
          this._log.trace("submitting ping immediately");
          this._submitPing(this.Reason.MAX);
        }
        break;
    }
  },

  _startTimer(
    delay = this.minFrequency,
    reason = this.Reason.PERIODIC,
    discardLeftovers = false
  ) {
    this._clearTimer();
    this._timeoutId = Policy.setTimeout(
      () => TelemetryEventPing._submitPing(reason, discardLeftovers),
      delay
    );
  },

  _clearTimer() {
    if (this._timeoutId) {
      Policy.clearTimeout(this._timeoutId);
      this._timeoutId = null;
    }
  },

  /**
   * Submits an "event" ping and restarts the timer for the next interval.
   *
   * @param {String} reason The reason we're sending the ping. One of TelemetryEventPing.Reason.
   * @param {bool} discardLeftovers Whether to discard event records left over from a previous ping.
   */
  _submitPing(reason, discardLeftovers = false) {
    this._log.trace("_submitPing");

    if (reason !== this.Reason.SHUTDOWN) {
      this._startTimer();
    }

    let snapshot = Services.telemetry.snapshotEvents(
      this.dataset,
      true /* clear */,
      DEFAULT_EVENT_LIMIT
    );

    if (!this._testing) {
      for (let process of Object.keys(snapshot)) {
        snapshot[process] = snapshot[process].filter(
          ([, category]) => !category.startsWith("telemetry.test")
        );
      }
    }

    let eventCount = Object.values(snapshot).reduce(
      (acc, val) => acc + val.length,
      0
    );
    if (eventCount === 0) {
      // Don't send a ping if we haven't any events.
      this._log.trace("not sending event ping due to lack of events");
      return;
    }

    // The reason doesn't matter as it will just be echo'd back.
    let sessionMeta = lazy.TelemetrySession.getMetadata(reason);

    let payload = {
      reason,
      processStartTimestamp: this._processStartTimestamp,
      sessionId: sessionMeta.sessionId,
      subsessionId: sessionMeta.subsessionId,
      lostEventsCount: 0,
      events: snapshot,
    };

    if (discardLeftovers) {
      // Any leftovers must be discarded, the count submitted in the ping.
      // This can happen on shutdown or if our max was reached before faster
      // than our maxFrequency.
      let leftovers = Services.telemetry.snapshotEvents(
        this.dataset,
        true /* clear */
      );
      let leftoverCount = Object.values(leftovers).reduce(
        (acc, val) => acc + val.length,
        0
      );
      payload.lostEventsCount = leftoverCount;
    }

    const options = {
      addClientId: true,
      addEnvironment: true,
      usePingSender: reason == this.Reason.SHUTDOWN,
    };

    this._lastSendTime = Utils.monotonicNow();
    Services.telemetry
      .getHistogramById("TELEMETRY_EVENT_PING_SENT")
      .add(reason);
    Policy.sendPing(this.EVENT_PING_TYPE, payload, options);
  },

  /**
   * Test-only, restore to initial state.
   */
  testReset() {
    this._lastSendTime = -DEFAULT_MIN_FREQUENCY_MS;
    this._clearTimer();
    this._testing = true;
  },

  get _log() {
    if (!this._logger) {
      this._logger = lazy.Log.repository.getLoggerWithMessagePrefix(
        LOGGER_NAME,
        LOGGER_PREFIX + "::"
      );
    }

    return this._logger;
  },
};
PK
!<��y
y
 modules/TelemetryArchive.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Log } from "resource://gre/modules/Log.sys.mjs";

import { TelemetryUtils } from "resource://gre/modules/TelemetryUtils.sys.mjs";

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetryArchive::";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  TelemetryStorage: "resource://gre/modules/TelemetryStorage.sys.mjs",
});

export var TelemetryArchive = {
  /**
   * Get a list of the archived pings, sorted by the creation date.
   * Note that scanning the archived pings on disk is delayed on startup,
   * use promizeInitialized() to access this after scanning.
   *
   * @return {Promise<sequence<Object>>}
   *                    A list of the archived ping info in the form:
   *                    { id: <string>,
   *                      timestampCreated: <number>,
   *                      type: <string> }
   */
  promiseArchivedPingList() {
    return TelemetryArchiveImpl.promiseArchivedPingList();
  },

  /**
   * Load an archived ping from disk by id, asynchronously.
   *
   * @param id {String} The pings UUID.
   * @return {Promise<PingData>} A promise resolved with the pings data on success.
   */
  promiseArchivedPingById(id) {
    return TelemetryArchiveImpl.promiseArchivedPingById(id);
  },

  /**
   * Archive a ping and persist it to disk.
   *
   * @param {object} ping The ping data to archive.
   * @return {promise} Promise that is resolved when the ping is successfully archived.
   */
  promiseArchivePing(ping) {
    return TelemetryArchiveImpl.promiseArchivePing(ping);
  },
};

/**
 * Checks if pings can be archived. Some products (e.g. Thunderbird) might not want
 * to do that.
 * @return {Boolean} True if pings should be archived, false otherwise.
 */
function shouldArchivePings() {
  return Services.prefs.getBoolPref(
    TelemetryUtils.Preferences.ArchiveEnabled,
    false
  );
}

var TelemetryArchiveImpl = {
  _logger: null,

  get _log() {
    if (!this._logger) {
      this._logger = Log.repository.getLoggerWithMessagePrefix(
        LOGGER_NAME,
        LOGGER_PREFIX
      );
    }

    return this._logger;
  },

  promiseArchivePing(ping) {
    if (!shouldArchivePings()) {
      this._log.trace("promiseArchivePing - archiving is disabled");
      return Promise.resolve();
    }

    for (let field of ["creationDate", "id", "type"]) {
      if (!(field in ping)) {
        this._log.warn("promiseArchivePing - missing field " + field);
        return Promise.reject(new Error("missing field " + field));
      }
    }

    return lazy.TelemetryStorage.saveArchivedPing(ping);
  },

  _buildArchivedPingList(archivedPingsMap) {
    let list = Array.from(archivedPingsMap, p => ({
      id: p[0],
      timestampCreated: p[1].timestampCreated,
      type: p[1].type,
    }));

    list.sort((a, b) => a.timestampCreated - b.timestampCreated);

    return list;
  },

  promiseArchivedPingList() {
    this._log.trace("promiseArchivedPingList");

    return lazy.TelemetryStorage.loadArchivedPingList().then(loadedInfo => {
      return this._buildArchivedPingList(loadedInfo);
    });
  },

  promiseArchivedPingById(id) {
    this._log.trace("promiseArchivedPingById - id: " + id);
    return lazy.TelemetryStorage.loadArchivedPing(id);
  },
};
PK
!<�`���modules/HealthPing.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This module collects data on send failures and other critical issues with Telemetry submissions.
 */

import { TelemetryUtils } from "resource://gre/modules/TelemetryUtils.sys.mjs";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "resource://gre/modules/Log.sys.mjs",
  TelemetryController: "resource://gre/modules/TelemetryController.sys.mjs",
  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

const Utils = TelemetryUtils;

const MS_IN_A_MINUTE = 60 * 1000;
// Send health ping every hour
const SEND_TICK_DELAY = 60 * MS_IN_A_MINUTE;

// Send top 10 discarded pings only to minimize health ping size
const MAX_SEND_DISCARDED_PINGS = 10;

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetryHealthPing::";

export var Policy = {
  setSchedulerTickTimeout: (callback, delayMs) =>
    lazy.setTimeout(callback, delayMs),
  clearSchedulerTickTimeout: id => lazy.clearTimeout(id),
};

export var TelemetryHealthPing = {
  Reason: Object.freeze({
    IMMEDIATE: "immediate", // Ping was sent immediately after recording with no delay.
    DELAYED: "delayed", // Recorded data was sent after a delay.
    SHUT_DOWN: "shutdown", // Recorded data was sent on shutdown.
  }),

  FailureType: Object.freeze({
    DISCARDED_FOR_SIZE: "pingDiscardedForSize",
    SEND_FAILURE: "sendFailure",
  }),

  OsInfo: Object.freeze({
    name: Services.appinfo.OS,
    version:
      Services.sysinfo.get("kernel_version") || Services.sysinfo.get("version"),
  }),

  HEALTH_PING_TYPE: "health",

  _logger: null,

  // The health ping is sent every every SEND_TICK_DELAY.
  // Initialize this so that first failures are sent immediately.
  _lastSendTime: -SEND_TICK_DELAY,

  /**
   * This stores reported send failures with the following structure:
   * {
   *  type1: {
   *    subtype1: value,
   *    ...
   *    subtypeN: value
   *  },
   *  ...
   * }
   */
  _failures: {},
  _timeoutId: null,

  /**
   * Record a failure to send a ping out.
   * @param {String} failureType The type of failure (e.g. "timeout", ...).
   * @returns {Promise} Test-only, resolved when the ping is stored or sent.
   */
  recordSendFailure(failureType) {
    return this._addToFailure(this.FailureType.SEND_FAILURE, failureType);
  },

  /**
   * Record that a ping was discarded and its type.
   * @param {String} pingType The type of discarded ping (e.g. "main", ...).
   * @returns {Promise} Test-only, resolved when the ping is stored or sent.
   */
  recordDiscardedPing(pingType) {
    return this._addToFailure(this.FailureType.DISCARDED_FOR_SIZE, pingType);
  },

  /**
   * Assemble payload.
   * @param {String} reason A string indicating the triggering reason (e.g. "immediate", "delayed", "shutdown").
   * @returns {Object} The assembled payload.
   */
  _assemblePayload(reason) {
    this._log.trace("_assemblePayload()");
    let payload = {
      os: this.OsInfo,
      reason,
    };

    for (let key of Object.keys(this._failures)) {
      if (key === this.FailureType.DISCARDED_FOR_SIZE) {
        payload[key] = this._getTopDiscardFailures(this._failures[key]);
      } else {
        payload[key] = this._failures[key];
      }
    }

    return payload;
  },

  /**
   * Sort input dictionary descending by value.
   * @param {Object} failures A dictionary of failures subtype and count.
   * @returns {Object} Sorted failures by value.
   */
  _getTopDiscardFailures(failures) {
    this._log.trace("_getTopDiscardFailures()");
    let sortedItems = Object.entries(failures).sort((first, second) => {
      return second[1] - first[1];
    });

    let result = {};
    sortedItems.slice(0, MAX_SEND_DISCARDED_PINGS).forEach(([key, value]) => {
      result[key] = value;
    });

    return result;
  },

  /**
   * Assemble the failure information and submit it.
   * @param {String} reason A string indicating the triggering reason (e.g. "immediate", "delayed", "shutdown").
   * @returns {Promise} Test-only promise that resolves when the ping was stored or sent (if any).
   */
  _submitPing(reason) {
    if (!TelemetryHealthPing.enabled || !this._hasDataToSend()) {
      return Promise.resolve();
    }

    this._log.trace("_submitPing(" + reason + ")");
    let payload = this._assemblePayload(reason);
    this._clearData();
    this._lastSendTime = Utils.monotonicNow();

    let options = {
      addClientId: true,
      usePingSender: reason === this.Reason.SHUT_DOWN,
    };

    return new Promise(r =>
      // If we submit the health ping immediately, the send task would be triggered again
      // before discarding oversized pings from the queue.
      // To work around this, we send the ping on the next tick.
      Services.tm.dispatchToMainThread(() =>
        r(
          lazy.TelemetryController.submitExternalPing(
            this.HEALTH_PING_TYPE,
            payload,
            options
          )
        )
      )
    );
  },

  /**
   * Accumulate failure information and trigger a ping immediately or on timeout.
   * @param {String} failureType The type of failure (e.g. "timeout", ...).
   * @param {String} failureSubType The subtype of failure (e.g. ping type, ...).
   * @returns {Promise} Test-only, resolved when the ping is stored or sent.
   */
  _addToFailure(failureType, failureSubType) {
    this._log.trace(
      "_addToFailure() - with type and subtype: " +
        failureType +
        " : " +
        failureSubType
    );

    if (!(failureType in this._failures)) {
      this._failures[failureType] = {};
    }

    let current = this._failures[failureType][failureSubType] || 0;
    this._failures[failureType][failureSubType] = current + 1;

    const now = Utils.monotonicNow();
    if (now - this._lastSendTime >= SEND_TICK_DELAY) {
      return this._submitPing(this.Reason.IMMEDIATE);
    }

    let submissionDelay = SEND_TICK_DELAY - now - this._lastSendTime;
    this._timeoutId = Policy.setSchedulerTickTimeout(
      () => TelemetryHealthPing._submitPing(this.Reason.DELAYED),
      submissionDelay
    );
    return Promise.resolve();
  },

  /**
   * @returns {boolean} Check the availability of recorded failures data.
   */
  _hasDataToSend() {
    return Object.keys(this._failures).length !== 0;
  },

  /**
   * Clear recorded failures data.
   */
  _clearData() {
    this._log.trace("_clearData()");
    this._failures = {};
  },

  /**
   * Clear and reset timeout.
   */
  _resetTimeout() {
    if (this._timeoutId) {
      Policy.clearSchedulerTickTimeout(this._timeoutId);
      this._timeoutId = null;
    }
  },

  /**
   * Submit latest ping on shutdown.
   * @returns {Promise} Test-only, resolved when the ping is stored or sent.
   */
  shutdown() {
    this._log.trace("shutdown()");
    this._resetTimeout();
    return this._submitPing(this.Reason.SHUT_DOWN);
  },

  /**
   * Test-only, restore to initial state.
   */
  testReset() {
    this._lastSendTime = -SEND_TICK_DELAY;
    this._clearData();
    this._resetTimeout();
  },

  get _log() {
    if (!this._logger) {
      this._logger = lazy.Log.repository.getLoggerWithMessagePrefix(
        LOGGER_NAME,
        LOGGER_PREFIX + "::"
      );
    }

    return this._logger;
  },
};

XPCOMUtils.defineLazyPreferenceGetter(
  TelemetryHealthPing,
  "enabled",
  TelemetryUtils.Preferences.HealthPingEnabled,
  true
);
PK
!<
2B��1localization/en-US/crashreporter/aboutcrashes.ftl
crash-reports-title = Crash Reports
submit-all-button-label = Submit All
delete-button-label = Clear All
delete-confirm-title = Are you sure?
delete-unsubmitted-description = This will delete all unsubmitted crash reports and cannot be undone.
delete-submitted-description = This will remove the list of submitted crash reports but will not delete the submitted data. This cannot be undone.

crashes-unsubmitted-label = Unsubmitted Crash Reports
id-heading = Report ID
date-crashed-heading = Date Crashed
submit-crash-button-label = Submit
submit-crash-button-failure-label = Failed

crashes-submitted-label = Submitted Crash Reports
date-submitted-heading = Date Submitted
view-crash-button-label = View

no-reports-label = No crash reports have been submitted.
no-config-label = This application has not been configured to display crash reports. The preference <code>breakpad.reportURL</code> must be set.
PK
!<(�Z�	�	2localization/en-US/crashreporter/crashreporter.ftl
crashreporter-branded-title = { -brand-short-name } Crash Reporter

crashreporter-apology = We’re Sorry
crashreporter-crashed-and-restore = { -brand-short-name } had a problem and crashed. We’ll try to restore your tabs and windows when it restarts.
crashreporter-plea = To help us diagnose and fix the problem, you can send us a crash report.

crashreporter-information = This application is run after a crash to report the problem to { -vendor-short-name }. It should not be run directly.

crashreporter-error = { -brand-short-name } had a problem and crashed. Unfortunately, the crash reporter is unable to submit a report for this crash.
crashreporter-error-details = Details: { $details }

crashreporter-no-run-message = This application is run after a crash to report the problem to the application vendor. It should not be run directly.

crashreporter-button-details = Details…
crashreporter-loading-details = Loading…

crashreporter-view-report-title = Report Contents

crashreporter-comment-prompt = Add a comment (comments are publicly visible)

crashreporter-report-info = This report also contains technical information about the state of the application when it crashed.

crashreporter-send-report = Tell { -vendor-short-name } about this crash so they can fix it.

crashreporter-include-url = Include the address of the page I was on.

crashreporter-submit-status = Your crash report will be submitted before you quit or restart.
crashreporter-submit-in-progress = Submitting your report…
crashreporter-submit-success = Report submitted successfully!
crashreporter-submit-failure = There was a problem submitting your report.

crashreporter-resubmit-status = Resending reports that previously failed to send…

crashreporter-button-quit = Quit { -brand-short-name }

crashreporter-button-restart = Restart { -brand-short-name }

crashreporter-button-ok = OK
crashreporter-button-close = Close

crashreporter-crash-identifier = Crash ID: { $id }

crashreporter-crash-details = You can view details of this crash at { $url }.


crashreporter-error-minidump-analyzer = Failed to run minidump-analyzer
crashreporter-error-opening-file = Failed to open file ({ $path })
crashreporter-error-loading-file = Failed to load file ({ $path })
crashreporter-error-creating-dir = Failed to create directory ({ $path })
crashreporter-error-no-home-dir = Missing home directory
crashreporter-error-moving-path = Failed to move { $from } to { $to }
crashreporter-error-version-eol = Version end of life: crash reports are no longer accepted.
PK
!<&�'��)localization/en-US/dom/XMLPrettyPrint.ftl
xml-nostylesheet = This XML file does not appear to have any style information associated with it. The document tree is shown below.
PK
!<�B��� localization/en-US/dom/media.ftl
default-audio-output-device-label = Default audio output device
mediastatus-fallback-title = { -brand-short-name } is playing media
PK
!<��vv8localization/en-US/locales-preview/aboutTranslations.ftl
about-translations-title = Translations
about-translations-header = { -translations-brand-name }
about-translations-results-placeholder = Translation
about-translations-translating-message = Translating…
about-translations-detect = Detect language
about-translations-detect-lang = Detect language ({ $language })
about-translations-select = Select language
about-translations-textarea =
  .placeholder = Add text to translate
about-translations-no-support = Your device does not meet the minimum requirements to use this feature. Try on another device.
about-translations-engine-error = The translations engine failed to load.
PK
!<h����8localization/en-US/security/certificates/certManager.ftl
certmgr-title =
    .title = Certificate Manager

certmgr-tab-mine =
    .label = Your Certificates

certmgr-tab-remembered =
    .label = Authentication Decisions

certmgr-tab-people =
    .label = People

certmgr-tab-servers =
    .label = Servers

certmgr-tab-ca =
    .label = Authorities

certmgr-mine = You have certificates from these organizations that identify you
certmgr-remembered = These certificates are used to identify you to websites
certmgr-people = You have certificates on file that identify these people
certmgr-server = These entries identify server certificate error exceptions
certmgr-ca = You have certificates on file that identify these certificate authorities

certmgr-edit-ca-cert2 =
    .title = Edit CA certificate trust settings
    .style = min-width: 48em;

certmgr-edit-cert-edit-trust = Edit trust settings:

certmgr-edit-cert-trust-ssl =
    .label = This certificate can identify websites.

certmgr-edit-cert-trust-email =
    .label = This certificate can identify mail users.

certmgr-delete-cert2 =
    .title = Delete Certificate
    .style = min-width: 48em; min-height: 24em;

certmgr-cert-host =
    .label = Host

certmgr-cert-name =
    .label = Certificate Name

certmgr-cert-server =
    .label = Server

certmgr-token-name =
    .label = Security Device

certmgr-begins-label =
    .label = Begins On

certmgr-expires-label =
    .label = Expires On

certmgr-email =
    .label = E-Mail Address

certmgr-serial =
    .label = Serial Number

certmgr-fingerprint-sha-256 =
    .label = SHA-256 Fingerprint

certmgr-view =
    .label = View…
    .accesskey = V

certmgr-edit =
    .label = Edit Trust…
    .accesskey = E

certmgr-export =
    .label = Export…
    .accesskey = x

certmgr-delete =
    .label = Delete…
    .accesskey = D

certmgr-delete-builtin =
    .label = Delete or Distrust…
    .accesskey = D

certmgr-backup =
    .label = Backup…
    .accesskey = B

certmgr-backup-all =
    .label = Backup All…
    .accesskey = k

certmgr-restore =
    .label = Import…
    .accesskey = m

certmgr-add-exception =
    .label = Add Exception…
    .accesskey = x

exception-mgr =
    .title = Add Security Exception

exception-mgr-extra-button =
    .label = Confirm Security Exception
    .accesskey = C

exception-mgr-supplemental-warning = Legitimate banks, stores, and other public sites will not ask you to do this.

exception-mgr-cert-location-url =
    .value = Location:

exception-mgr-cert-location-download =
    .label = Get Certificate
    .accesskey = G

exception-mgr-cert-status-view-cert =
    .label = View…
    .accesskey = V

exception-mgr-permanent =
    .label = Permanently store this exception
    .accesskey = P

pk11-bad-password = The password entered was incorrect.
pkcs12-decode-err = Failed to decode the file. Either it is not in PKCS #12 format, has been corrupted, or the password you entered was incorrect.
pkcs12-unknown-err-restore = Failed to restore the PKCS #12 file for unknown reasons.
pkcs12-unknown-err-backup = Failed to create the PKCS #12 backup file for unknown reasons.
pkcs12-unknown-err = The PKCS #12 operation failed for unknown reasons.
pkcs12-info-no-smartcard-backup = It is not possible to back up certificates from a hardware security device such as a smart card.
pkcs12-dup-data = The certificate and private key already exist on the security device.


choose-p12-backup-file-dialog = File Name to Backup
file-browse-pkcs12-spec = PKCS12 Files
choose-p12-restore-file-dialog = Certificate File to Import


file-browse-certificate-spec = Certificate Files
import-ca-certs-prompt = Select File containing CA certificate(s) to import
import-email-cert-prompt = Select File containing somebody’s Email certificate to import


edit-trust-ca = The certificate “{ $certName }” represents a Certificate Authority.


delete-user-cert-title =
    .title = Delete your Certificates
delete-user-cert-confirm = Are you sure you want to delete these certificates?
delete-user-cert-impact = If you delete one of your own certificates, you can no longer use it to identify yourself.


delete-ssl-override-title =
    .title = Delete Server Certificate Exception
delete-ssl-override-confirm = Are you sure you want to delete this server exception?
delete-ssl-override-impact = If you delete a server exception, you restore the usual security checks for that server and require it uses a valid certificate.

delete-ca-cert-title =
    .title = Delete or Distrust CA Certificates
delete-ca-cert-confirm = You have requested to delete these CA certificates. For built-in certificates all trust will be removed, which has the same effect. Are you sure you want to delete or distrust?
delete-ca-cert-impact = If you delete or distrust a certificate authority (CA) certificate, this application will no longer trust any certificates issued by that CA.


delete-email-cert-title =
    .title = Delete E-Mail Certificates
delete-email-cert-confirm = Are you sure you want to delete these people’s e-mail certificates?
delete-email-cert-impact = If you delete a person’s e-mail certificate, you will no longer be able to send encrypted e-mail to that person.

cert-with-serial =
    .value = Certificate with serial number: { $serialNumber }

send-no-client-certificate = Send no client certificate

no-cert-stored-for-override = (Not Stored)

certificate-not-available = (Unavailable)


permanent-override = Permanent
temporary-override = Temporary


add-exception-branded-warning = You are about to override how { -brand-short-name } identifies this site.
add-exception-invalid-header = This site attempts to identify itself with invalid information.
add-exception-domain-mismatch-short = Wrong Site
add-exception-domain-mismatch-long = The certificate belongs to a different site, which could mean that someone is trying to impersonate this site.
add-exception-expired-short = Outdated Information
add-exception-expired-long = The certificate is not currently valid. It may have been stolen or lost, and could be used by someone to impersonate this site.
add-exception-unverified-or-bad-signature-short = Unknown Identity
add-exception-unverified-or-bad-signature-long = The certificate is not trusted because it hasn’t been verified as issued by a trusted authority using a secure signature.
add-exception-valid-short = Valid Certificate
add-exception-valid-long = This site provides valid, verified identification.  There is no need to add an exception.
add-exception-checking-short = Checking Information
add-exception-checking-long = Attempting to identify this site…
add-exception-no-cert-short = No Information Available
add-exception-no-cert-long = Unable to obtain identification status for this site.


save-cert-as = Save Certificate To File
cert-format-base64 = X.509 Certificate (PEM)
cert-format-base64-chain = X.509 Certificate with chain (PEM)
cert-format-der = X.509 Certificate (DER)
cert-format-pkcs7 = X.509 Certificate (PKCS#7)
cert-format-pkcs7-chain = X.509 Certificate with chain (PKCS#7)
write-file-failure = File Error
PK
!<�(
(
:localization/en-US/security/certificates/deviceManager.ftl

devmgr-window =
    .title = Device Manager
    .style = min-width: 67em; min-height: 32em;

devmgr-devlist =
    .label = Security Modules and Devices

devmgr-header-details =
    .label = Details

devmgr-header-value =
    .label = Value

devmgr-button-login =
    .label = Log In
    .accesskey = n

devmgr-button-logout =
    .label = Log Out
    .accesskey = O

devmgr-button-changepw =
    .label = Change Password
    .accesskey = P

devmgr-button-load =
    .label = Load
    .accesskey = L

devmgr-button-unload =
    .label = Unload
    .accesskey = U

devmgr-button-enable-fips =
    .label = Enable FIPS
    .accesskey = F

devmgr-button-disable-fips =
    .label = Disable FIPS
    .accesskey = F


load-device =
    .title = Load PKCS#11 Device Driver

load-device-info = Enter the information for the module you want to add.

load-device-modname =
    .value = Module Name
    .accesskey = M

load-device-modname-default =
    .value = New PKCS#11 Module

load-device-filename =
    .value = Module filename
    .accesskey = f

load-device-browse =
    .label = Browse…
    .accesskey = B


devinfo-status =
    .label = Status

devinfo-status-disabled =
    .label = Disabled

devinfo-status-not-present =
    .label = Not Present

devinfo-status-uninitialized =
    .label = Uninitialized

devinfo-status-not-logged-in =
    .label = Not Logged In

devinfo-status-logged-in =
    .label = Logged In

devinfo-status-ready =
    .label = Ready

devinfo-desc =
    .label = Description

devinfo-man-id =
    .label = Manufacturer

devinfo-hwversion =
    .label = HW Version
devinfo-fwversion =
    .label = FW Version

devinfo-modname =
    .label = Module

devinfo-modpath =
    .label = Path

login-failed = Failed to Login

devinfo-label =
    .label = Label

devinfo-serialnum =
    .label = Serial Number

fips-nonempty-primary-password-required = FIPS mode requires that you have a Primary Password set for each security device. Please set the password before trying to enable FIPS mode.
unable-to-toggle-fips = Unable to change the FIPS mode for the security device. It is recommended that you exit and restart this application.
load-pk11-module-file-picker-title = Choose a PKCS#11 device driver to load

load-module-help-empty-module-name =
    .value = The module name cannot be empty.

load-module-help-root-certs-module-name =
    .value = ‘Root Certs‘ is reserved and cannot be used as the module name.

add-module-failure = Unable to add module
del-module-warning = Are you sure you want to delete this security module?
del-module-error = Unable to delete module
PK
!</����
�
-localization/en-US/security/pippki/pippki.ftl
password-quality-meter = Password quality meter


change-device-password-window =
  .title = Change Password

change-password-token = Security Device: { $tokenName }
change-password-old = Current password:
change-password-new = New password:
change-password-reenter = New password (again):

pippki-failed-pw-change = Unable to change password.
pippki-incorrect-pw = You did not enter the correct current password. Please try again.
pippki-pw-change-ok = Password successfully changed.

pippki-pw-empty-warning = Your stored passwords and private keys will not be protected.
pippki-pw-erased-ok = You have deleted your password. { pippki-pw-empty-warning }
pippki-pw-not-wanted = Warning! You have decided not to use a password. { pippki-pw-empty-warning }

pippki-pw-change2empty-in-fips-mode = You are currently in FIPS mode. FIPS requires a non-empty password.


reset-primary-password-window2 =
  .title = Reset Primary Password
  .style = min-width: 40em
reset-password-button-label =
  .label = Reset
reset-primary-password-text = If you reset your Primary Password, all your stored web and e-mail passwords, personal certificates, and private keys will be forgotten. Are you sure you want to reset your Primary Password?

pippki-reset-password-confirmation-title = Reset Primary Password
pippki-reset-password-confirmation-message = Your Primary Password has been reset.


download-cert-window2 =
  .title = Downloading Certificate
  .style = min-width: 46em
download-cert-message = You have been asked to trust a new Certificate Authority (CA).
download-cert-trust-ssl =
  .label = Trust this CA to identify websites.
download-cert-trust-email =
  .label = Trust this CA to identify email users.
download-cert-message-desc = Before trusting this CA for any purpose, you should examine its certificate and its policy and procedures (if available).
download-cert-view-cert =
  .label = View
download-cert-view-text = Examine CA certificate


client-auth-window =
  .title = User Identification Request
client-auth-send-no-certificate =
  .label = Don’t send a certificate

client-auth-site-identification = “{ $hostname }” has requested that you identify yourself with a certificate:
client-auth-cert-details = Details of selected certificate:
client-auth-cert-details-issued-to = Issued to: { $issuedTo }
client-auth-cert-details-serial-number = Serial number: { $serialNumber }
client-auth-cert-details-validity-period = Valid from { $notBefore } to { $notAfter }
client-auth-cert-details-key-usages = Key usages: { $keyUsages }
client-auth-cert-details-email-addresses = Email addresses: { $emailAddresses }
client-auth-cert-details-issued-by = Issued by: { $issuedBy }
client-auth-cert-details-stored-on = Stored on: { $storedOn }
client-auth-cert-remember-box =
  .label = Remember this decision


set-password-window =
  .title = Choose a Certificate Backup Password
set-password-message = The certificate backup password you set here protects the backup file that you are about to create. You must set this password to proceed with the backup.
set-password-backup-pw =
  .value = Certificate backup password:
set-password-repeat-backup-pw =
  .value = Certificate backup password (again):
set-password-reminder = Important: If you forget your certificate backup password, you will not be able to restore this backup later. Please record it in a safe location.


protected-auth-alert = Please authenticate to the token “{ $tokenName }”. How to do so depends on the token (for example, using a fingerprint reader or entering a code with a keypad).
PK
!<~7>�JJ(localization/en-US/services/accounts.ftl
account-client-name = { $user }’s { -brand-short-name } on { $system }
PK
!<*$�`/localization/en-US/toolkit/about/aboutAbout.ftl
about-about-title = About About
about-about-note =
    This is a list of “about” pages for your convenience.<br/>
    Some of them might be confusing. Some are for diagnostic purposes only.<br/>
    And some are omitted because they require query strings.
PK
!<�����0localization/en-US/toolkit/about/aboutCompat.ftl
label-disable = Disable
label-enable = Enable
label-interventions = Interventions
label-more-information = More Information: Bug { $bug }
label-overrides = User Agent Overrides
text-disabled-in-about-config = This feature has been disabled in about:config
text-no-interventions = No interventions are being used
text-no-overrides = No UA overrides are being used
text-title = about:compat


label-smartblock = SmartBlock Fixes
text-no-smartblock = No SmartBlock fixes are being used
PK
!<�[�##/localization/en-US/toolkit/about/aboutGlean.ftl

-fog-brand-name = FOG
-glean-brand-name = Glean
glean-sdk-brand-name = { -glean-brand-name } SDK
glean-debug-ping-viewer-brand-name = { -glean-brand-name } Debug Ping Viewer

about-glean-page-title2 = About { -glean-brand-name }
about-glean-header = About { -glean-brand-name }
about-glean-interface-description =
  The <a data-l10n-name="glean-sdk-doc-link">{ glean-sdk-brand-name }</a>
  is a data collection library used in { -vendor-short-name } projects.
  This interface is designed to be used by developers and testers to manually
  <a data-l10n-name="fog-link">test instrumentation</a>.

about-glean-upload-enabled = Data upload is enabled.
about-glean-upload-disabled = Data upload is disabled.
about-glean-upload-enabled-local = Data upload is enabled only for sending to a local server.
about-glean-upload-fake-enabled =
  Data upload is disabled,
  but we’re lying and telling the { glean-sdk-brand-name } it is enabled
  so that data is still recorded locally.
  Note: If you set a debug tag, pings will be uploaded to the
  <a data-l10n-name="glean-debug-ping-viewer">{ glean-debug-ping-viewer-brand-name }</a> regardless of settings.

about-glean-prefs-and-defines = Relevant <a data-l10n-name="fog-prefs-and-defines-doc-link">preferences and defines</a> include:
about-glean-data-upload = <code>datareporting.healthreport.uploadEnabled</code>: { $data-upload-pref-value }
about-glean-local-port = <code>telemetry.fog.test.localhost_port</code>: { $local-port-pref-value }
about-glean-glean-android = <code>MOZ_GLEAN_ANDROID</code>: { $glean-android-define-value }
about-glean-moz-official =<code>MOZILLA_OFFICIAL</code>: { $moz-official-define-value }

about-glean-about-testing-header = About Testing
about-glean-manual-testing =
  Full instructions are documented in the
  <a data-l10n-name="fog-instrumentation-test-doc-link">{ -fog-brand-name } instrumentation testing docs</a>
  and in the <a data-l10n-name="glean-sdk-doc-link">{ glean-sdk-brand-name } documentation</a>,
  but, in short, to manually test that your instrumentation works, you should:

about-glean-no-ping-label = (don’t submit any ping)
about-glean-label-for-tag-pings-with-requirements =
  Set a memorable debug tag <span>(20 characters or fewer, alphanumerics and - only)</span> so you can recognize your pings later.
about-glean-label-for-ping-names =
  Select from the preceding list the ping your instrumentation is in.
  If it’s in a <a data-l10n-name="custom-ping-link">custom ping</a>, choose that one.
  Otherwise, the default for <code>event</code> metrics is
  the <code>events</code> ping
  and the default for all other metrics is
  the <code>metrics</code> ping.
about-glean-label-for-log-pings =
  (Optional. Check the preceding box if you want pings to also be logged when they are submitted.
  You will additionally need to <a data-l10n-name="enable-logging-link">enable logging</a>.)
about-glean-label-for-controls-submit =
  Press the preceding button to tag all { -glean-brand-name } pings with your tag and submit the selected ping.
  (All pings submitted from then until you restart the application will be tagged with
  <code>{ $debug-tag }</code>.)
about-glean-li-for-visit-gdpv =
  <a data-l10n-name="gdpv-tagged-pings-link">Visit the { glean-debug-ping-viewer-brand-name } page for pings with your tag</a>.
  It shouldn’t take more than a few seconds from pushing the button to your ping arriving.
  Sometimes it may take a small handful of minutes.

about-glean-adhoc-explanation2 =
  For more <i>ad hoc</i> testing,
  you can also determine the current value of a particular piece of instrumentation
  by opening a devtools console here on <code>about:glean</code>
  and using the <code>testGetValue()</code> API like
  <code>Glean.metricCategory.metricName.testGetValue()</code>
  for a metric named <code>metric.category.metric_name</code>.

about-glean-adhoc-note =
  Please note that you are using the Glean JS API by using the devtools console.
  This means the metric category and metric name are formatted in
  <code>camelCase</code> unlike in the Rust and C++ APIs.

controls-button-label-verbose = Apply settings and submit ping

about-glean-about-data-header = About Data
about-glean-about-data-explanation =
  To browse the list of collected data, please consult the
  <a data-l10n-name="glean-dictionary-link">{ -glean-brand-name } Dictionary</a>.
PK
!<?wr���8localization/en-US/toolkit/about/aboutHttpsOnlyError.ftl
about-httpsonly-title-alert = HTTPS-Only Mode Alert
about-httpsonly-title-site-not-available = Secure Site Not Available

about-httpsonly-explanation-unavailable2 = You’ve enabled HTTPS-Only Mode for enhanced security, and a HTTPS version of <em>{ $websiteUrl }</em> is not available.
about-httpsonly-explanation-question = What could be causing this?
about-httpsonly-explanation-nosupport = Most likely, the website simply does not support HTTPS.
about-httpsonly-explanation-risk = It’s also possible that an attacker is involved. If you decide to visit the website, you should not enter any sensitive information like passwords, emails, or credit card details.
about-httpsonly-explanation-continue = If you continue, HTTPS-Only Mode will be turned off temporarily for this site.

about-httpsonly-button-continue-to-site = Continue to HTTP Site
about-httpsonly-button-go-back = Go Back
about-httpsonly-link-learn-more = Learn More…


about-httpsonly-suggestion-box-header = Possible Alternative
about-httpsonly-suggestion-box-www-text = There is a secure version of <em>www.{ $websiteUrl }</em>. You can visit this page instead of <em>{ $websiteUrl }</em>.
about-httpsonly-suggestion-box-www-button = Go to www.{ $websiteUrl }
PK
!<��X�
�
1localization/en-US/toolkit/about/aboutLogging.ftl
about-logging-title = About Logging
about-logging-page-title = Logging manager
about-logging-current-log-file = Current log file:
about-logging-new-log-file = New log file:
about-logging-currently-enabled-log-modules = Currently enabled log modules:
about-logging-log-tutorial =
    See <a data-l10n-name="logging">HTTP Logging</a>
    for instructions on how to use this tool.
about-logging-open-log-file-dir = Open directory
about-logging-set-log-file = Set Log File
about-logging-set-log-modules = Set Log Modules
about-logging-start-logging = Start Logging
about-logging-stop-logging = Stop Logging
about-logging-buttons-disabled = Logging configured via environment variables, dynamic configuration unavailable.
about-logging-some-elements-disabled = Logging configured via URL, some configuration options are unavailable
about-logging-info = Info:
about-logging-log-modules-selection = Log module selection
about-logging-new-log-modules = New log modules:
about-logging-logging-output-selection = Logging output
about-logging-logging-to-file = Logging to a file
about-logging-logging-to-profiler = Logging to the { -profiler-brand-name }
about-logging-no-log-modules = None
about-logging-no-log-file = None
about-logging-logging-preset-selector-text = Logging preset:
about-logging-with-profiler-stacks-checkbox = Enable stack traces for log messages


about-logging-preset-networking-label = Networking
about-logging-preset-networking-description = Log modules to diagnose networking issues
about-logging-preset-networking-cookie-label = Cookies
about-logging-preset-networking-cookie-description = Log modules to diagnose cookie issues
about-logging-preset-networking-websocket-label = WebSockets
about-logging-preset-networking-websocket-description = Log modules to diagnose WebSocket issues
about-logging-preset-networking-http3-label = HTTP/3
about-logging-preset-networking-http3-description = Log modules to diagnose HTTP/3 and QUIC issues
about-logging-preset-networking-http3-upload-speed-label = HTTP/3 upload speed
about-logging-preset-networking-http3-upload-speed-description = Log modules to diagnose HTTP/3 upload speed issues
about-logging-preset-media-playback-label = Media playback
about-logging-preset-media-playback-description = Log modules to diagnose media playback issues (not video-conferencing issues)
about-logging-preset-webrtc-label = WebRTC
about-logging-preset-webrtc-description = Log modules to diagnose WebRTC calls
about-logging-preset-webgpu-label = WebGPU
about-logging-preset-webgpu-description = Log modules to diagnose WebGPU issues
about-logging-preset-gfx-label = Graphics
about-logging-preset-gfx-description = Log modules to diagnose graphics issues
about-logging-preset-windows-label = Windows
about-logging-preset-windows-description = Log modules to diagnose issues specific to Microsoft Windows
about-logging-preset-custom-label = Custom
about-logging-preset-custom-description = Log modules manually selected

about-logging-error = Error:


about-logging-invalid-output = Invalid value “{ $v }“ for key “{ $k }“
about-logging-unknown-logging-preset = Unknown logging preset “{ $v }“
about-logging-unknown-profiler-preset = Unknown profiler preset “{ $v }“
about-logging-unknown-option = Unknown about:logging option “{ $k }“
about-logging-configuration-url-ignored = Configuration URL ignored
about-logging-file-and-profiler-override = Can’t force file output and override profiler options at the same time

about-logging-configured-via-url = Option configured via URL
PK
!<����1localization/en-US/toolkit/about/aboutMozilla.ftl
about-mozilla-title-6-27 = The Book of Mozilla, 6:27
about-mozilla-quote-6-27 =
    The Beast continued its studies with renewed <em>Focus</em>, building great <em>Reference</em>
    works and contemplating new <em>Realities</em>. The Beast brought forth its followers and
    acolytes to create a renewed smaller form of itself and, through <em>Mischievous</em> means,
    sent it out across the world.
about-mozilla-from-6-27 = from <strong>The Book of Mozilla,</strong> 6:27
PK
!<���

4localization/en-US/toolkit/about/aboutNetworking.ftl
about-networking-title = About Networking
about-networking-http = HTTP
about-networking-http-clear-cache-button = Clear HTTP Cache
about-networking-sockets = Sockets
about-networking-dns = DNS
about-networking-dns-clear-cache-button = Clear DNS Cache
about-networking-dns-trr-url = DoH URL
about-networking-dns-trr-mode = DoH Mode
about-networking-dns-suffix = DNS suffix
about-networking-websockets = WebSockets
about-networking-refresh = Refresh
about-networking-auto-refresh = Autorefresh every 3 seconds
about-networking-hostname = Hostname
about-networking-port = Port
about-networking-http-version = HTTP Version
about-networking-ssl = SSL
about-networking-active = Active
about-networking-idle = Idle
about-networking-host = Host
about-networking-type = Type
about-networking-sent = Sent
about-networking-received = Received
about-networking-family = Family
about-networking-trr = TRR
about-networking-addresses = Addresses
about-networking-expires = Expires (Seconds)
about-networking-originAttributesSuffix = Isolation Key
about-networking-flags = Extra flags
about-networking-messages-sent = Messages Sent
about-networking-messages-received = Messages Received
about-networking-bytes-sent = Bytes Sent
about-networking-bytes-received = Bytes Received
about-networking-logging = Logging
about-networking-dns-lookup = DNS Lookup
about-networking-dns-lookup-button = Resolve
about-networking-dns-domain = Domain:
about-networking-dns-lookup-table-column = IPs
about-networking-dns-https-rr-lookup-table-column = HTTP RRs
about-networking-rcwn = RCWN Stats
about-networking-rcwn-status = RCWN Status
about-networking-rcwn-cache-won-count = Cache won count
about-networking-rcwn-net-won-count = Net won count
about-networking-total-network-requests = Total network request count
about-networking-rcwn-operation = Cache Operation
about-networking-rcwn-perf-open = Open
about-networking-rcwn-perf-read = Read
about-networking-rcwn-perf-write = Write
about-networking-rcwn-perf-entry-open = Entry Open
about-networking-rcwn-avg-short = Short Average
about-networking-rcwn-avg-long = Long Average
about-networking-rcwn-std-dev-long = Long Standard Deviation
about-networking-rcwn-cache-slow = Cache slow count
about-networking-rcwn-cache-not-slow = Cache not slow count
about-networking-networkid = Network ID
about-networking-networkid-id = Network ID
about-networking-moved-about-logging = This page has been moved to <a data-l10n-name="about-logging-url">about:logging</a>.


about-networking-networkid-is-up = Link is up
about-networking-networkid-status-known = Link status is known

PK
!<���3localization/en-US/toolkit/about/aboutProcesses.ftl
about-processes-title = Process Manager

about-processes-column-action =
    .title = Actions


about-processes-shutdown-process =
    .title = Unload tabs and kill process
about-processes-kill-process =
    .title = Kill process
about-processes-shutdown-tab =
    .title = Close tab

about-processes-profile-process =
    .title = { $duration ->
   [one] Profile all threads of this process for { $duration } second
  *[other] Profile all threads of this process for { $duration } seconds
}


about-processes-column-name = Name
about-processes-column-memory-resident = Memory
about-processes-column-cpu-total = CPU


about-processes-browser-process = { -brand-short-name } ({ $pid })
about-processes-web-process = Shared Web Process ({ $pid })
about-processes-file-process = Files ({ $pid })
about-processes-extension-process = Extensions ({ $pid })
about-processes-privilegedabout-process = About pages ({ $pid })
about-processes-plugin-process = Plugins ({ $pid })
about-processes-privilegedmozilla-process = { -vendor-short-name } sites ({ $pid })
about-processes-gmp-plugin-process = Gecko Media Plugins ({ $pid })
about-processes-gpu-process = GPU ({ $pid })
about-processes-vr-process = VR ({ $pid })
about-processes-rdd-process = Data Decoder ({ $pid })
about-processes-socket-process = Network ({ $pid })
about-processes-remote-sandbox-broker-process = Remote Sandbox Broker ({ $pid })
about-processes-fork-server-process = Fork Server ({ $pid })
about-processes-preallocated-process = Preallocated ({ $pid })
about-processes-utility-process = Utility ({ $pid })
about-processes-inference-process = Inference ({ $pid })

about-processes-unknown-process = Other: { $type } ({ $pid })


about-processes-web-isolated-process = { $origin } ({ $pid })
about-processes-web-serviceworker = { $origin } ({ $pid }, serviceworker)
about-processes-with-coop-coep-process = { $origin } ({ $pid }, cross-origin isolated)
about-processes-web-isolated-process-private = { $origin } — Private ({ $pid })
about-processes-with-coop-coep-process-private = { $origin } — Private ({ $pid }, cross-origin isolated)


about-processes-active-threads = { $active ->
   [one] { $active } active thread out of { $number }: { $list }
  *[other] { $active } active threads out of { $number }: { $list }
}

about-processes-inactive-threads = { $number ->
   [one] { $number } inactive thread
  *[other] { $number } inactive threads
}

about-processes-thread-name-and-id = { $name }
    .title = Thread id: { $tid }

about-processes-tab-name = Tab: { $name }
about-processes-preloaded-tab = Preloaded New Tab

about-processes-frame-name-one = Subframe: { $url }

about-processes-frame-name-many = Subframes ({ $number }): { $shortUrl }


about-processes-utility-actor-unknown = Unknown actor
about-processes-utility-actor-audio-decoder-generic = Generic Audio Decoder
about-processes-utility-actor-audio-decoder-applemedia = Apple Media Audio Decoder
about-processes-utility-actor-audio-decoder-wmf = Windows Media Framework Audio Decoder
about-processes-utility-actor-mf-media-engine = Windows Media Foundation Media Engine CDM
about-processes-utility-actor-js-oracle = JavaScript Oracle
about-processes-utility-actor-windows-utils = Windows Utils
about-processes-utility-actor-windows-file-dialog = Windows File Dialog


about-processes-cpu = { NUMBER($percent, maximumSignificantDigits: 2, style: "percent") }
    .title = Total CPU time: { NUMBER($total, maximumFractionDigits: 0) }{ $unit }

about-processes-cpu-user-and-kernel-not-ready = (measuring)

about-processes-cpu-almost-idle = < 0.1%
    .title = Total CPU time: { NUMBER($total, maximumFractionDigits: 0) }{ $unit }

about-processes-cpu-fully-idle = idle
    .title = Total CPU time: { NUMBER($total, maximumFractionDigits: 0) }{ $unit }


about-processes-total-memory-size-changed = { NUMBER($total, maximumFractionDigits:0) }{ $totalUnit }
   .title = Evolution: { $deltaSign }{ NUMBER($delta, maximumFractionDigits:0) }{ $deltaUnit }

about-processes-total-memory-size-no-change = { NUMBER($total, maximumFractionDigits:0) }{ $totalUnit }


duration-unit-ns = ns
duration-unit-us = µs
duration-unit-ms = ms
duration-unit-s = s
duration-unit-m = m
duration-unit-h = h
duration-unit-d = d


memory-unit-B = B
memory-unit-KB = KB
memory-unit-MB = MB
memory-unit-GB = GB
memory-unit-TB = TB
memory-unit-PB = PB
memory-unit-EB = EB
PK
!<T���	�	2localization/en-US/toolkit/about/aboutProfiles.ftl

profiles-title = About Profiles
profiles-subtitle = This page helps you to manage your profiles. Each profile is a separate world which contains separate history, bookmarks, settings and add-ons.
profiles-create = Create a New Profile
profiles-restart-title = Restart
profiles-restart-in-safe-mode = Restart with Add-ons Disabled…
profiles-restart-normal = Restart normally…
profiles-conflict = Another copy of { -brand-product-name } has made changes to profiles. You must restart { -brand-short-name } before making more changes.
profiles-flush-fail-title = Changes not saved
profiles-flush-conflict = { profiles-conflict }
profiles-flush-failed = An unexpected error has prevented your changes from being saved.
profiles-flush-restart-button = Restart { -brand-short-name }

profiles-name = Profile: { $name }
profiles-is-default = Default Profile
profiles-rootdir = Root Directory

profiles-localdir = Local Directory
profiles-current-profile = This is the profile in use and it cannot be deleted.
profiles-in-use-profile = This profile is in use in another application and it cannot be deleted.

profiles-rename = Rename
profiles-remove = Remove
profiles-set-as-default = Set as default profile
profiles-launch-profile = Launch profile in new browser

profiles-cannot-set-as-default-title = Unable to set default
profiles-cannot-set-as-default-message = The default profile cannot be changed for { -brand-short-name }.

profiles-yes = yes
profiles-no = no

profiles-rename-profile-title = Rename Profile
profiles-rename-profile = Rename profile { $name }

profiles-invalid-profile-name-title = Invalid profile name
profiles-invalid-profile-name = The profile name “{ $name }” is not allowed.

profiles-delete-profile-title = Delete Profile
profiles-delete-profile-confirm =
    Deleting a profile will remove the profile from the list of available profiles and cannot be undone.
    You may also choose to delete the profile data files, including your settings, certificates and other user-related data. This option will delete the folder “{ $dir }” and cannot be undone.
    Would you like to delete the profile data files?
profiles-delete-files = Delete Files
profiles-dont-delete-files = Don’t Delete Files

profiles-delete-profile-failed-title = Error
profiles-delete-profile-failed-message = There was an error while attempting to delete this profile.


profiles-opendir =
    { PLATFORM() ->
        [macos] Show in Finder
        [windows] Open Folder
       *[other] Open Directory
    }
PK
!<U��V��0localization/en-US/toolkit/about/aboutReader.ftl
about-reader-loading = Loading…
about-reader-load-error = Failed to load article from page

about-reader-color-light-theme = Light
    .title = Light theme
about-reader-color-dark-theme = Dark
    .title = Dark theme
about-reader-color-sepia-theme = Sepia
    .title = Sepia theme
about-reader-color-auto-theme = Auto
    .title = Auto theme
about-reader-color-gray-theme = Gray
    .title = Gray theme
about-reader-color-contrast-theme = Contrast
    .title = Contrast theme

about-reader-estimated-read-time =
    { $rangePlural ->
        [one] { $range } minute
       *[other] { $range } minutes
    }


about-reader-toolbar-minus =
    .title = Decrease font size
about-reader-toolbar-plus =
    .title = Increase font size
about-reader-toolbar-contentwidthminus =
    .title = Decrease Content Width
about-reader-toolbar-contentwidthplus =
    .title = Increase Content Width
about-reader-toolbar-lineheightminus =
    .title = Decrease Line Height
about-reader-toolbar-lineheightplus =
    .title = Increase Line Height


about-reader-font-type-serif = Serif
about-reader-font-type-sans-serif = Sans-serif
about-reader-font-type-monospace = Monospace


about-reader-toolbar-close = Close Reader View
about-reader-toolbar-type-controls = Type controls
about-reader-toolbar-text-layout-controls = Text and layout
about-reader-toolbar-theme-controls = Theme
about-reader-toolbar-savetopocket = Save to { -pocket-brand-name }


about-reader-colors-menu-header = Theme

about-reader-fxtheme-tab = Default
about-reader-customtheme-tab = Custom


about-reader-custom-colors-foreground = Text
    .title = Edit color
about-reader-custom-colors-background = Background
    .title = Edit color

about-reader-custom-colors-unvisited-links = Unvisited links
    .title = Edit color
about-reader-custom-colors-visited-links = Visited links
    .title = Edit color

about-reader-custom-colors-selection-highlight = Highlighter for read aloud
    .title = Edit color

about-reader-reset-button = Reset defaults


about-reader-text-header = Text

about-reader-text-size-label = Text size
about-reader-font-type-selector-label = Font
about-reader-font-weight-selector-label = Font weight

about-reader-font-weight-light = Light
about-reader-font-weight-regular = Regular
about-reader-font-weight-bold = Bold

about-reader-layout-header = Layout

about-reader-slider-label-spacing-standard = Standard
about-reader-slider-label-spacing-wide = Wide

about-reader-content-width-label =
  .label = Content width
about-reader-line-spacing-label =
  .label = Line spacing

about-reader-advanced-layout-header = Advanced

about-reader-character-spacing-label =
  .label = Character spacing
about-reader-word-spacing-label =
  .label = Word spacing
about-reader-text-alignment-label = Text alignment
about-reader-text-alignment-left =
    .title = Align text left
about-reader-text-alignment-center =
    .title = Align text center
about-reader-text-alignment-right =
    .title = Align text right
PK
!<��Ĕ��0localization/en-US/toolkit/about/aboutRights.ftl
rights-title = About Your Rights
rights-intro =
    { -brand-full-name } is free and open source software, built by a community
    of thousands from all over the world. There are a few things you should
    know:
rights-intro-point-1 =
    { -brand-short-name } is made available to you under the terms of the
    <a data-l10n-name="mozilla-public-license-link">Mozilla Public License</a>.
    This means you may use, copy and distribute { -brand-short-name } to
    others. You are also welcome to modify the source code of
    { -brand-short-name } as you want to meet your needs. The Mozilla Public
    License also gives you the right to distribute your modified versions.
rights-intro-point-2 =
    You are not granted any trademark rights or licenses to the trademarks of
    the Mozilla Foundation or any party, including without limitation the
    Firefox name or logo. Additional information on trademarks may be found
    <a data-l10n-name="mozilla-trademarks-link">here</a>.
rights-intro-point-3 =
    Some features in { -brand-short-name }, such as the Crash Reporter, give
    you the option to provide feedback to { -vendor-short-name }. By choosing
    to submit feedback, you give { -vendor-short-name } permission to use the
    feedback to improve its products, to publish the feedback on its websites,
    and to distribute the feedback.
rights-intro-point-4 =
    How we use your personal information and feedback submitted to
    { -vendor-short-name } through { -brand-short-name } is described in the
    <a data-l10n-name="mozilla-privacy-policy-link">{ -brand-short-name }
    Privacy Policy</a>.
rights-intro-point-4-unbranded =
    Any applicable privacy policies for this product should be listed here.
rights-intro-point-5 =
    Some { -brand-short-name } features make use of web-based information
    services, however, we cannot guarantee they are 100% accurate or
    error-free. More details, including information on how to disable the
    features that use these services, can be found in the
    <a data-l10n-name="mozilla-service-terms-link">service terms</a>.
rights-intro-point-5-unbranded =
    If this product incorporates web services, any applicable service terms for
    the service(s) should be linked to the
    <a data-l10n-name="mozilla-website-services-link"> Website Services</a>
    section.
rights-intro-point-6 =
    In order to play back certain types of video content, { -brand-short-name }
    downloads certain content decryption modules from third parties.
rights-webservices-header = { -brand-full-name } Web-Based Information Services
rights-webservices =
    { -brand-full-name } uses web-based information services (“Services”) to
    provide some of the features provided for your use with this binary version
    of { -brand-short-name } under the terms described below. If you do not
    want to use one or more of the Services or the terms below are
    unacceptable, you may disable the feature or Service(s). Instructions on
    how to disable a particular feature or Service may be found
    <a data-l10n-name="mozilla-disable-service-link">here</a>. Other features
    and Services can be disabled in the application preferences.
rights-safebrowsing =
    <strong>SafeBrowsing: </strong>Disabling the Safe Browsing feature is not
    recommended as it may result in you going to unsafe sites. If you wish to
    disable the feature completely, follow these steps:
rights-safebrowsing-term-1 = Open the application preferences
rights-safebrowsing-term-2 = Select the Security selection
rights-safebrowsing-term-3 =
    Uncheck the option to “{ enableSafeBrowsing-label }”
enableSafeBrowsing-label = Block dangerous and deceptive content
rights-safebrowsing-term-4 = Safe Browsing is now disabled
rights-locationawarebrowsing =
    <strong>Location Aware Browsing: </strong>is always opt-in. No location
    information is ever sent without your permission. If you wish to disable
    the feature completely, follow these steps:
rights-locationawarebrowsing-term-1 =
    In the URL bar, type <code>about:config</code>
rights-locationawarebrowsing-term-2 = Type geo.enabled
rights-locationawarebrowsing-term-3 =
    Double click on the geo.enabled preference
rights-locationawarebrowsing-term-4 = Location-Aware Browsing is now disabled
rights-webservices-unbranded =
    An overview of the website services the product incorporates, along with
    instructions on how to disable them, if applicable, should be included
    here.
rights-webservices-term-unbranded =
    Any applicable service terms for this product should be listed here.
rights-webservices-term-1 =
    { -vendor-short-name } and its contributors, licensors and partners work to
    provide the most accurate and up-to-date Services. However, we cannot
    guarantee that this information is comprehensive and error-free. For
    example, the Safe Browsing Service may not identify some risky sites and
    may identify some safe sites in error and the Location Aware Service all
    locations returned by our service providers are estimates only and neither
    we nor our service providers guarantee the accuracy of the locations
    provided.
rights-webservices-term-2 =
    { -vendor-short-name } may discontinue or change the Services at its
    discretion.
rights-webservices-term-3 =
    You are welcome to use these Services with the accompanying version of
    { -brand-short-name }, and { -vendor-short-name } grants you its rights to
    do so. { -vendor-short-name } and its licensors reserve all other rights in
    the Services. These terms are not intended to limit any rights granted
    under open source licenses applicable to { -brand-short-name } and to
    corresponding source code versions of { -brand-short-name }.
rights-webservices-term-4 =
    <strong>The Services are provided “as-is.” { -vendor-short-name }, its
    contributors, licensors, and distributors, disclaim all warranties, whether
    express or implied, including without limitation, warranties that the
    Services are merchantable and fit for your particular purposes. You bear
    the entire risk as to selecting the Services for your purposes and as to
    the quality and performance of the Services. Some jurisdictions do not
    allow the exclusion or limitation of implied warranties, so this disclaimer
    may not apply to you.</strong>
rights-webservices-term-5 =
    <strong>Except as required by law, { -vendor-short-name }, its
    contributors, licensors, and distributors will not be liable for any
    indirect, special, incidental, consequential, punitive, or exemplary
    damages arising out of or in any way relating to the use of
    { -brand-short-name } and the Services. The collective liability under
    these terms will not exceed $500 (five hundred dollars). Some jurisdictions
    do not allow the exclusion or limitation of certain damages, so this
    exclusion and limitation may not apply to you.</strong>
rights-webservices-term-6 =
    { -vendor-short-name } may update these terms as necessary from time to
    time. These terms may not be modified or canceled without
    { -vendor-short-name }’s written agreement.
rights-webservices-term-7 =
    These terms are governed by the laws of the state of California, U.S.A.,
    excluding its conflict of law provisions. If any portion of these terms is
    held to be invalid or unenforceable, the remaining portions will remain in
    full force and effect. In the event of a conflict between a translated
    version of these terms and the English language version, the English
    language version shall control.
PK
!<̚W���8localization/en-US/toolkit/about/aboutServiceWorkers.ftl

about-service-workers-title = About Service Workers
about-service-workers-main-title = Registered Service Workers
about-service-workers-warning-not-enabled = Service Workers are not enabled.
about-service-workers-warning-no-service-workers = No Service Workers registered.

origin-title = Origin: { $originTitle }


scope = <strong>Scope:</strong> { $name }
script-spec = <strong>Script Spec:</strong> <a data-l10n-name="link">{ $url }</a>
current-worker-url = <strong>Current Worker URL:</strong> <a data-l10n-name="link">{ $url }</a>
active-cache-name = <strong>Active Cache Name:</strong> { $name }
waiting-cache-name = <strong>Waiting Cache Name:</strong> { $name }
push-end-point-waiting = <strong>Push Endpoint:</strong> { waiting }
push-end-point-result = <strong>Push Endpoint:</strong> { $name }

update-button = Update

unregister-button = Unregister

unregister-error = Failed to unregister this Service Worker.

waiting = Waiting…
PK
!<�A�z<z<1localization/en-US/toolkit/about/aboutSupport.ftl
page-title = Troubleshooting Information
page-subtitle =
    This page contains technical information that might be useful when you’re
    trying to solve a problem. If you are looking for answers to common questions
    about { -brand-short-name }, check out our <a data-l10n-name="support-link">support website</a>.

crashes-title = Crash Reports
crashes-id = Report ID
crashes-send-date = Submitted
crashes-all-reports = All Crash Reports
crashes-no-config = This application has not been configured to display crash reports.
support-addons-title = Add-ons
support-addons-name = Name
support-addons-type = Type
support-addons-enabled = Enabled
support-addons-version = Version
support-addons-id = ID
legacy-user-stylesheets-title = Legacy User Stylesheets
legacy-user-stylesheets-enabled = Active
legacy-user-stylesheets-stylesheet-types = Stylesheets
legacy-user-stylesheets-no-stylesheets-found = No stylesheets found
security-software-title = Security Software
security-software-type = Type
security-software-name = Name
security-software-antivirus = Antivirus
security-software-antispyware = Antispyware
security-software-firewall = Firewall
features-title = { -brand-short-name } Features
features-name = Name
features-version = Version
features-id = ID
processes-title = Remote Processes
processes-type = Type
processes-count = Count
app-basics-title = Application Basics
app-basics-name = Name
app-basics-version = Version
app-basics-build-id = Build ID
app-basics-distribution-id = Distribution ID
app-basics-update-channel = Update Channel
app-basics-update-dir =
    { PLATFORM() ->
        [linux] Update Directory
       *[other] Update Folder
    }
app-basics-update-history = Update History
app-basics-show-update-history = Show Update History
app-basics-binary = Application Binary
app-basics-profile-dir =
    { PLATFORM() ->
        [linux] Profile Directory
       *[other] Profile Folder
    }
app-basics-build-config = Build Configuration
app-basics-user-agent = User Agent
app-basics-os = OS
app-basics-os-theme = OS Theme
app-basics-rosetta = Rosetta Translated
app-basics-memory-use = Memory Use
app-basics-performance = Performance
app-basics-service-workers = Registered Service Workers
app-basics-third-party = Third-party Modules
app-basics-profiles = Profiles
app-basics-launcher-process-status = Launcher Process
app-basics-multi-process-support = Multiprocess Windows
app-basics-fission-support = Fission Windows
app-basics-remote-processes-count = Remote Processes
app-basics-enterprise-policies = Enterprise Policies
app-basics-location-service-key-google = Google Location Service Key
app-basics-safebrowsing-key-google = Google Safebrowsing Key
app-basics-key-mozilla = Mozilla Location Service Key
app-basics-safe-mode = Safe Mode
app-basics-memory-size = Memory Size (RAM)
app-basics-disk-available = Disk Space Available
app-basics-pointing-devices = Pointing Devices

app-basics-data-size = { $value } { $unit }

show-dir-label =
    { PLATFORM() ->
        [macos] Show in Finder
        [windows] Open Folder
       *[other] Open Directory
    }
environment-variables-title = Environment Variables
environment-variables-name = Name
environment-variables-value = Value
experimental-features-title = Experimental Features
experimental-features-name = Name
experimental-features-value = Value
modified-key-prefs-title = Important Modified Preferences
modified-prefs-name = Name
modified-prefs-value = Value
user-js-title = user.js Preferences
user-js-description = Your profile folder contains a <a data-l10n-name="user-js-link">user.js file</a>, which includes preferences that were not created by { -brand-short-name }.
locked-key-prefs-title = Important Locked Preferences
locked-prefs-name = Name
locked-prefs-value = Value
graphics-title = Graphics
graphics-features-title = Features
graphics-diagnostics-title = Diagnostics
graphics-failure-log-title = Failure Log
graphics-gpu1-title = GPU #1
graphics-gpu2-title = GPU #2
graphics-decision-log-title = Decision Log
graphics-crash-guards-title = Crash Guard Disabled Features
graphics-workarounds-title = Workarounds
graphics-device-pixel-ratios = Window Device Pixel Ratios
graphics-window-protocol = Window Protocol
graphics-desktop-environment = Desktop Environment
place-database-title = Places Database
place-database-stats = Statistics
place-database-stats-show = Show Statistics
place-database-stats-hide = Hide Statistics
place-database-stats-entity = Entity
place-database-stats-count = Count
place-database-stats-size-kib = Size (KiB)
place-database-stats-size-perc = Size (%)
place-database-stats-efficiency-perc = Efficiency (%)
place-database-stats-sequentiality-perc = Sequentiality (%)
place-database-integrity = Integrity
place-database-verify-integrity = Verify Integrity
a11y-title = Accessibility
a11y-activated = Activated
a11y-force-disabled = Prevent Accessibility
a11y-handler-used = Accessible Handler Used
a11y-instantiator = Accessibility Instantiator
library-version-title = Library Versions
copy-text-to-clipboard-label = Copy text to clipboard
copy-raw-data-to-clipboard-label = Copy raw data to clipboard
sandbox-title = Sandbox
sandbox-sys-call-log-title = Rejected System Calls
sandbox-sys-call-index = #
sandbox-sys-call-age = Seconds Ago
sandbox-sys-call-pid = PID
sandbox-sys-call-tid = TID
sandbox-sys-call-proc-type = Process Type
sandbox-sys-call-number = Syscall
sandbox-sys-call-args = Arguments
troubleshoot-mode-title = Diagnose issues
restart-in-troubleshoot-mode-label = Troubleshoot Mode…
clear-startup-cache-title = Try clearing the startup cache
clear-startup-cache-label = Clear startup cache…
startup-cache-dialog-title2 = Restart { -brand-short-name } to clear startup cache?
startup-cache-dialog-body2 = This will not change your settings or remove extensions.
restart-button-label = Restart


audio-backend = Audio Backend
max-audio-channels = Max Channels
sample-rate = Preferred Sample Rate
roundtrip-latency = Roundtrip latency (standard deviation)
media-title = Media
media-output-devices-title = Output Devices
media-input-devices-title = Input Devices
media-device-name = Name
media-device-group = Group
media-device-vendor = Vendor
media-device-state = State
media-device-preferred = Preferred
media-device-format = Format
media-device-channels = Channels
media-device-rate = Rate
media-device-latency = Latency
media-capabilities-title = Media Capabilities
media-codec-support-info = Codec Support Information
media-capabilities-enumerate = Enumerate database


media-codec-support-sw-decoding = Software Decoding
media-codec-support-hw-decoding = Hardware Decoding
media-codec-support-codec-name = Codec Name
media-codec-support-supported = Supported
media-codec-support-unsupported = Unsupported
media-codec-support-error = Codec support information unavailable. Try again after playing back a media file.
media-codec-support-lack-of-extension = Install extension


media-content-decryption-modules-title = Content Decryption Modules Information
media-key-system-name = Key System Name
media-video-robustness = Video Robustness
media-audio-robustness = Audio Robustness
media-cdm-capabilities = Capabilities
media-cdm-clear-lead = Clear Lead
media-hdcp-22-compatible = HDCP 2.2 Compatible


intl-title = Internationalization & Localization
intl-app-title = Application Settings
intl-locales-requested = Requested Locales
intl-locales-available = Available Locales
intl-locales-supported = App Locales
intl-locales-default = Default Locale
intl-os-title = Operating System
intl-os-prefs-system-locales = System Locales
intl-regional-prefs = Regional Preferences


remote-debugging-title = Remote Debugging (Chromium Protocol)
remote-debugging-accepting-connections = Accepting Connections
remote-debugging-url = URL


report-crash-for-days =
    { $days ->
        [one] Crash Reports for the Last { $days } Day
       *[other] Crash Reports for the Last { $days } Days
    }

crashes-time-minutes =
    { $minutes ->
        [one] { $minutes } minute ago
       *[other] { $minutes } minutes ago
    }

crashes-time-hours =
    { $hours ->
        [one] { $hours } hour ago
       *[other] { $hours } hours ago
    }

crashes-time-days =
    { $days ->
        [one] { $days } day ago
       *[other] { $days } days ago
    }

pending-reports =
    { $reports ->
        [one] All Crash Reports (including { $reports } pending crash in the given time range)
       *[other] All Crash Reports (including { $reports } pending crashes in the given time range)
    }

raw-data-copied = Raw data copied to clipboard
text-copied = Text copied to clipboard


blocked-driver = Blocked for your graphics driver version.
blocked-gfx-card = Blocked for your graphics card because of unresolved driver issues.
blocked-os-version = Blocked for your operating system version.
blocked-mismatched-version = Blocked for your graphics driver version mismatch between registry and DLL.
try-newer-driver = Blocked for your graphics driver version. Try updating your graphics driver to version { $driverVersion } or newer.

clear-type-parameters = ClearType Parameters

compositing = Compositing
support-font-determination = Font Visibility Debug Info
hardware-h264 = Hardware H264 Decoding
main-thread-no-omtc = main thread, no OMTC
yes = Yes
no = No
unknown = Unknown
virtual-monitor-disp = Virtual Monitor Display


found = Found
missing = Missing

gpu-process-pid = GPUProcessPid
gpu-process = GPUProcess
gpu-description = Description
gpu-vendor-id = Vendor ID
gpu-device-id = Device ID
gpu-subsys-id = Subsys ID
gpu-drivers = Drivers
gpu-ram = RAM
gpu-driver-vendor = Driver Vendor
gpu-driver-version = Driver Version
gpu-driver-date = Driver Date
gpu-active = Active
webgl1-wsiinfo = WebGL 1 Driver WSI Info
webgl1-renderer = WebGL 1 Driver Renderer
webgl1-version = WebGL 1 Driver Version
webgl1-driver-extensions = WebGL 1 Driver Extensions
webgl1-extensions = WebGL 1 Extensions
webgl2-wsiinfo = WebGL 2 Driver WSI Info
webgl2-renderer = WebGL 2 Driver Renderer
webgl2-version = WebGL 2 Driver Version
webgl2-driver-extensions = WebGL 2 Driver Extensions
webgl2-extensions = WebGL 2 Extensions
webgpu-default-adapter = WebGPU Default Adapter
webgpu-fallback-adapter = WebGPU Fallback Adapter

support-blocklisted-bug = Blocklisted due to known issues: <a data-l10n-name="bug-link">bug { $bugNumber }</a>

unknown-failure = Blocklisted; failure code { $failureCode }

d3d11layers-crash-guard = D3D11 Compositor
glcontext-crash-guard = OpenGL
wmfvpxvideo-crash-guard = WMF VPX Video Decoder

reset-on-next-restart = Reset on Next Restart
gpu-process-kill-button = Terminate GPU Process
gpu-device-reset = Device Reset
gpu-device-reset-button = Trigger Device Reset
uses-tiling = Uses Tiling
content-uses-tiling = Uses Tiling (Content)
off-main-thread-paint-enabled = Off Main Thread Painting Enabled
off-main-thread-paint-worker-count = Off Main Thread Painting Worker Count
target-frame-rate = Target Frame Rate

min-lib-versions = Expected minimum version
loaded-lib-versions = Version in use

has-seccomp-bpf = Seccomp-BPF (System Call Filtering)
has-seccomp-tsync = Seccomp Thread Synchronization
has-user-namespaces = User Namespaces
has-privileged-user-namespaces = User Namespaces for privileged processes
support-user-namespaces-unavailable =
    { $status } — This feature is not allowed by your system. This can restrict security features of { -brand-short-name }.
can-sandbox-content = Content Process Sandboxing
can-sandbox-media = Media Plugin Sandboxing
content-sandbox-level = Content Process Sandbox Level
effective-content-sandbox-level = Effective Content Process Sandbox Level
content-win32k-lockdown-state = Win32k Lockdown State for Content Process
support-sandbox-gpu-level = GPU Process Sandbox Level
sandbox-proc-type-content = content
sandbox-proc-type-file = file content
sandbox-proc-type-media-plugin = media plugin
sandbox-proc-type-data-decoder = data decoder

startup-cache-title = Startup Cache
startup-cache-disk-cache-path = Disk Cache Path
startup-cache-ignore-disk-cache = Ignore Disk Cache
startup-cache-found-disk-cache-on-init = Found Disk Cache on Init
startup-cache-wrote-to-disk-cache = Wrote to Disk Cache

launcher-process-status-0 = Enabled
launcher-process-status-1 = Disabled due to failure
launcher-process-status-2 = Disabled forcibly
launcher-process-status-unknown = Unknown status

multi-process-windows = { $remoteWindows }/{ $totalWindows }
fission-windows = { $fissionWindows }/{ $totalWindows }
fission-status-experiment-control = Disabled by experiment
fission-status-experiment-treatment = Enabled by experiment
fission-status-disabled-by-e10s-env = Disabled by environment
fission-status-enabled-by-env = Enabled by environment
fission-status-disabled-by-env = Disabled by environment
fission-status-enabled-by-default = Enabled by default
fission-status-disabled-by-default = Disabled by default
fission-status-enabled-by-user-pref = Enabled by user
fission-status-disabled-by-user-pref = Disabled by user
fission-status-disabled-by-e10s-other = E10s disabled
fission-status-enabled-by-rollout = Enabled by phased rollout

async-pan-zoom = Asynchronous Pan/Zoom
apz-none = none
wheel-enabled = wheel input enabled
touch-enabled = touch input enabled
drag-enabled = scrollbar drag enabled
keyboard-enabled = keyboard enabled
autoscroll-enabled = autoscroll enabled
zooming-enabled = smooth pinch-zoom enabled


wheel-warning = async wheel input disabled due to unsupported pref: { $preferenceKey }
touch-warning = async touch input disabled due to unsupported pref: { $preferenceKey }


policies-inactive = Inactive
policies-active = Active
policies-error = Error


support-printing-title = Printing
support-printing-troubleshoot = Troubleshooting
support-printing-clear-settings-button = Clear saved print settings
support-printing-modified-settings = Modified print settings
support-printing-prefs-name = Name
support-printing-prefs-value = Value


support-remote-settings-title = Remote Settings
support-remote-settings-status = Status
support-remote-settings-status-ok = OK
support-remote-settings-status-broken = Not working
support-remote-settings-last-check = Last check
support-remote-settings-local-timestamp = Local timestamp
support-remote-settings-sync-history = History
support-remote-settings-sync-history-status = Status
support-remote-settings-sync-history-datetime = Date
support-remote-settings-sync-history-infos = Infos


support-remote-experiments-title = Remote Experiments
support-remote-experiments-name = Name
support-remote-experiments-branch = Experiment Branch
support-remote-experiments-see-about-studies = See <a data-l10n-name="support-about-studies-link">about:studies</a> for more information, including how to disable individual experiments or to disable { -brand-short-name } from running this type of experiment in the future.

support-remote-features-title = Remote Features
support-remote-features-name = Name
support-remote-features-status = Status


pointing-device-mouse = Mouse
pointing-device-touchscreen = Touchscreen
pointing-device-pen-digitizer = Pen Digitizer
pointing-device-none = No pointing devices


content-analysis-title = Content Analysis (DLP)
content-analysis-active = Active
content-analysis-connected-to-agent = Connected to Agent
content-analysis-agent-path = Agent Path
content-analysis-agent-failed-signature-verification = Agent Failed Signature Verification
content-analysis-request-count = Request Count
PK
!<�ϼ��3localization/en-US/toolkit/about/aboutTelemetry.ftl
about-telemetry-ping-data-source = Ping data source:
about-telemetry-show-current-data = Current data
about-telemetry-show-archived-ping-data = Archived ping data
about-telemetry-show-subsession-data = Show subsession data
about-telemetry-choose-ping = Choose ping:
about-telemetry-archive-ping-type = Ping Type
about-telemetry-archive-ping-header = Ping
about-telemetry-option-group-today = Today
about-telemetry-option-group-yesterday = Yesterday
about-telemetry-option-group-older = Older
about-telemetry-previous-ping = <<
about-telemetry-next-ping = >>
about-telemetry-page-title = Telemetry Data
about-telemetry-current-store = Current Store:
about-telemetry-more-information = Looking for more information?
about-telemetry-firefox-data-doc = The <a data-l10n-name="data-doc-link">Firefox Data Documentation</a> contains guides about how to work with our data tools.
about-telemetry-telemetry-client-doc = The <a data-l10n-name="client-doc-link">Firefox Telemetry client documentation</a> includes definitions for concepts, API documentation and data references.
about-telemetry-telemetry-dashboard = The <a data-l10n-name="dashboard-link">Telemetry dashboards</a> allow you to visualize the data Mozilla receives via Telemetry.
about-telemetry-telemetry-probe-dictionary = The <a data-l10n-name="probe-dictionary-link">Probe Dictionary</a> provides details and descriptions for the probes collected by Telemetry.
about-telemetry-show-in-Firefox-json-viewer = Open in the JSON viewer
about-telemetry-home-section = Home
about-telemetry-general-data-section = General Data
about-telemetry-environment-data-section = Environment Data
about-telemetry-session-info-section = Session Information
about-telemetry-scalar-section = Scalars
about-telemetry-keyed-scalar-section = Keyed Scalars
about-telemetry-histograms-section = Histograms
about-telemetry-keyed-histogram-section = Keyed Histograms
about-telemetry-events-section = Events
about-telemetry-simple-measurements-section = Simple Measurements
about-telemetry-slow-sql-section = Slow SQL Statements
about-telemetry-addon-details-section = Add-on Details
about-telemetry-late-writes-section = Late Writes
about-telemetry-raw-payload-section = Raw Payload
about-telemetry-raw = Raw JSON
about-telemetry-full-sql-warning = NOTE: Slow SQL debugging is enabled. Full SQL strings may be displayed below but they will not be submitted to Telemetry.
about-telemetry-fetch-stack-symbols = Fetch function names for stacks
about-telemetry-hide-stack-symbols = Show raw stack data
about-telemetry-data-type =
    { $channel ->
        [release] release data
       *[prerelease] pre-release data
    }
about-telemetry-upload-type =
    { $uploadcase ->
        [enabled] enabled
       *[disabled] disabled
    }
about-telemetry-histogram-stats =
    { $sampleCount ->
        [one] { $sampleCount } sample, average = { $prettyAverage }, sum = { $sum }
       *[other] { $sampleCount } samples, average = { $prettyAverage }, sum = { $sum }
    }
about-telemetry-page-subtitle = This page shows the information about performance, hardware, usage and customizations collected by Telemetry. This information is submitted to { $telemetryServerOwner } to help improve { -brand-full-name }.
about-telemetry-settings-explanation = Telemetry is collecting { about-telemetry-data-type } and upload is <a data-l10n-name="upload-link">{ about-telemetry-upload-type }</a>.
about-telemetry-ping-details = Each piece of information is sent bundled into “<a data-l10n-name="ping-link">pings</a>”. You are looking at the { $name }, { $timestamp } ping.
about-telemetry-data-details-current = Each piece of information is sent bundled into “<a data-l10n-name="ping-link">pings</a>“. You are looking at the current data.
about-telemetry-filter-placeholder =
    .placeholder = Find in { $selectedTitle }
about-telemetry-filter-all-placeholder =
    .placeholder = Find in all sections
about-telemetry-results-for-search = Results for “{ $searchTerms }”
about-telemetry-no-search-results = Sorry! There are no results in { $sectionName } for “{ $currentSearchText }”
about-telemetry-no-search-results-all = Sorry! There are no results in any sections for “{ $searchTerms }”
about-telemetry-no-data-to-display = Sorry! There is currently no data available in “{ $sectionName }”
about-telemetry-current-data-sidebar = current data
about-telemetry-telemetry-ping-type-all = all
about-telemetry-histogram-copy = Copy
about-telemetry-slow-sql-main = Slow SQL Statements on Main Thread
about-telemetry-slow-sql-other = Slow SQL Statements on Helper Threads
about-telemetry-slow-sql-hits = Hits
about-telemetry-slow-sql-average = Avg. Time (ms)
about-telemetry-slow-sql-statement = Statement
about-telemetry-addon-table-id = Add-on ID
about-telemetry-addon-table-details = Details
about-telemetry-addon-provider = { $addonProvider } Provider
about-telemetry-keys-header = Property
about-telemetry-names-header = Name
about-telemetry-values-header = Value
about-telemetry-late-writes-title = Late Write #{ $lateWriteCount }
about-telemetry-stack-title = Stack:
about-telemetry-memory-map-title = Memory map:
about-telemetry-error-fetching-symbols = An error occurred while fetching symbols. Check that you are connected to the Internet and try again.
about-telemetry-time-stamp-header = timestamp
about-telemetry-category-header = category
about-telemetry-method-header = method
about-telemetry-object-header = object
about-telemetry-extra-header = extra
about-telemetry-process = { $process } process
PK
!<3�����4localization/en-US/toolkit/about/aboutThirdParty.ftl
third-party-page-title = Third-party Module Information
third-party-section-title = List of third-party modules in { -brand-short-name }

third-party-intro =
    This page shows the third-party modules which were injected into your
    { -brand-short-name }. Any module that is not signed by Microsoft or
    { -vendor-short-name } is considered to be a third-party module.

third-party-message-empty = No third-party modules were detected.
third-party-message-no-duration = Not recorded

third-party-detail-version = File version
third-party-detail-vendor = Vendor info
third-party-detail-occurrences = Occurrences
    .title = How many times this module was loaded.
third-party-detail-duration = Avg. Blocking time (ms)
    .title = How long this module blocked the application.
third-party-detail-app = Application
third-party-detail-publisher = Publisher

third-party-th-process = Process
third-party-th-duration = Loading Duration (ms)
third-party-th-status = Status

third-party-tag-ime = IME
    .title =
        This type of module is loaded when you use a third-party IME.
third-party-tag-shellex = Shell Extension
    .title =
        This type of module is loaded when you open the system file dialog.
third-party-tag-background = Background
    .title =
        This module did not block the application because it was loaded
        in the background.
third-party-icon-unsigned =
    .title = This module is not signed
    .alt = This module is not signed
third-party-icon-warning =
    .title = { -brand-short-name } crashed in code from this module
    .alt = { -brand-short-name } crashed in code from this module

third-party-status-loaded = Loaded
third-party-status-blocked = Blocked
third-party-status-redirected = Redirected

third-party-button-copy-to-clipboard = Copy raw data to clipboard
third-party-loading-data =
    .alt = Loading system information…
    .title = Loading system information…
third-party-button-reload = Reload with system info
    .title = Reload with system information
third-party-button-open =
    .title = Open file location…
third-party-button-to-block-module = Block this module
    .title = Block this module
    .aria-label = Block this module
third-party-button-to-unblock-module = Unblock this module
    .title = Currently blocked. Click to unblock it.
    .aria-label = Currently blocked. Click to unblock it.
third-party-button-to-unblock-module-disabled = Unblock this module (blocklist currently disabled)
    .title =
        Currently marked as blocked, although the blocklist is disabled for this run
        of { -brand-short-name }. Click to unblock it.
    .aria-label =
        Currently marked as blocked, although the blocklist is disabled for this run
        of { -brand-short-name }. Click to unblock it.
third-party-button-expand =
    .title = Show detailed information
third-party-button-collapse =
    .title = Collapse detailed information
third-party-blocking-requires-restart =
    To block a third-party module, { -brand-short-name } must restart.
third-party-should-restart-title = Restart { -brand-short-name }
third-party-restart-now = Restart now
third-party-restart-later = Restart later

third-party-blocked-by-builtin =
    .title = Blocked by { -brand-short-name }
    .alt = Blocked by { -brand-short-name }
PK
!<r�W��2localization/en-US/toolkit/about/aboutWebauthn.ftl

about-webauthn-page-title = About WebAuthn


about-webauthn-info-section-title = Device info
about-webauthn-info-subsection-title = Authenticator info
about-webauthn-options-subsection-title = Authenticator options
about-webauthn-pin-section-title = PIN Management
about-webauthn-credential-management-section-title = Manage credentials
about-webauthn-pin-required-section-title = PIN required
about-webauthn-confirm-deletion-section-title = Confirm deletion
about-webauthn-bio-enrollment-section-title = Biometric enrollments


about-webauthn-text-connect-device = Please connect a security token.
about-webauthn-text-select-device = Please select your desired security token by touching the device.
about-webauthn-text-non-ctap2-device = Unable to manage options because your security token does not support CTAP2.
about-webauthn-text-not-available = Not available on this platform.
about-webauthn-bio-enrollment-list-subsection-title = Enrollments:
about-webauthn-add-bio-enrollment-section-title = Add new enrollment


about-webauthn-results-success = Success!
about-webauthn-results-general-error = Error!
about-webauthn-results-pin-invalid-error =
    { $retriesLeft ->
        [0] Error: Incorrect PIN. Try again.
        [one] Error: Incorrect PIN. Try again. You have one attempt left.
       *[other] Error: Incorrect PIN. Try again. You have { $retriesLeft } attempts left.
    }
about-webauthn-results-pin-blocked-error = Error: There are no attempts left and your device has been locked, because the wrong PIN was provided too many times. The device needs a reset.
about-webauthn-results-pin-not-set-error = Error: PIN not set. This operation needs PIN protection.
about-webauthn-results-pin-too-short-error = Error: The given PIN is too short.
about-webauthn-results-pin-too-long-error = Error: The given PIN is too long.
about-webauthn-results-pin-auth-blocked-error = Error: There were too many failed attempts in a row and PIN authentication has been temporarily blocked. Your device needs a power cycle (unplug and re-insert).
about-webauthn-results-cancelled-by-user-error = Error: Operation has been canceled by the user.


about-webauthn-new-pin-label = New PIN:
about-webauthn-repeat-pin-label = Repeat new PIN:
about-webauthn-current-pin-label = Current PIN:
about-webauthn-pin-required-label = Please enter your PIN:
about-webauthn-credential-list-subsection-title = Credentials:
about-webauthn-enrollment-name-label = Enrollment name (optional):
about-webauthn-enrollment-list-empty = No enrollments found on device.
about-webauthn-credential-list-empty = No credentials found on device.
about-webauthn-confirm-deletion-label = You are about to delete:


about-webauthn-current-set-pin-button = Set PIN
about-webauthn-current-change-pin-button = Change PIN
about-webauthn-list-credentials-button = List credentials
about-webauthn-list-bio-enrollments-button = List enrollments
about-webauthn-add-bio-enrollment-button = Add enrollment
about-webauthn-cancel-button = Cancel
about-webauthn-send-pin-button = OK
about-webauthn-delete-button = Delete
about-webauthn-start-enrollment-button = Start enrollment
about-webauthn-update-button = Update


about-webauthn-auth-option-uv = User verification
about-webauthn-auth-option-up = User presence
about-webauthn-auth-option-clientpin = Client PIN
about-webauthn-auth-option-rk = Resident key
about-webauthn-auth-option-plat = Platform device
about-webauthn-auth-option-pinuvauthtoken = Command permissions (pinUvAuthToken)
about-webauthn-auth-option-nomcgapermissionswithclientpin = No MakeCredential / GetAssertion permissions with client PIN
about-webauthn-auth-option-largeblobs = Large blobs
about-webauthn-auth-option-ep = Enterprise attestation
about-webauthn-auth-option-bioenroll = Biometric enrollment
about-webauthn-auth-option-userverificationmgmtpreview = Prototype of biometric enrollment (FIDO_2_1_PRE)
about-webauthn-auth-option-uvbioenroll = Biometric enrollment permission
about-webauthn-auth-option-authnrcfg = Authenticator config
about-webauthn-auth-option-uvacfg = Authenticator config permission
about-webauthn-auth-option-credmgmt = Credential management
about-webauthn-auth-option-credentialmgmtpreview = Prototype credential management
about-webauthn-auth-option-setminpinlength = Set minimum PIN length
about-webauthn-auth-option-makecreduvnotrqd = MakeCredential without user verification
about-webauthn-auth-option-alwaysuv = Always require user verification
about-webauthn-auth-option-true = True
about-webauthn-auth-option-false = False
about-webauthn-auth-option-null = Not supported


about-webauthn-auth-info-vendor-prototype-config-commands = Vendor prototype config commands
about-webauthn-auth-info-remaining-discoverable-credentials = Remaining discoverable credentials
about-webauthn-auth-info-certifications = Certifications
about-webauthn-auth-info-uv-modality = User verification modality
about-webauthn-auth-info-preferred-platform-uv-attempts = Preferred platform user verification attempts
about-webauthn-auth-info-max-rpids-for-set-min-pin-length = Max relying party IDs for set minimum PIN length
about-webauthn-auth-info-max-cred-blob-length = Max credential blob length
about-webauthn-auth-info-firmware-version = Firmware version
about-webauthn-auth-info-min-pin-length = Minimum PIN length
about-webauthn-auth-info-force-pin-change = Force PIN change
about-webauthn-auth-info-max-ser-large-blob-array = Max size of large blob array
about-webauthn-auth-info-algorithms = Algorithms
about-webauthn-auth-info-transports = Transports
about-webauthn-auth-info-max-credential-id-length = Max credential ID length
about-webauthn-auth-info-max-credential-count-in-list = Max credential count in list
about-webauthn-auth-info-pin-protocols = PIN protocols
about-webauthn-auth-info-max-msg-size = Max message size
about-webauthn-auth-info-aaguid = AAGUID
about-webauthn-auth-info-extensions = Extensions
about-webauthn-auth-info-versions = Versions
about-webauthn-auth-info-true = True
about-webauthn-auth-info-false = False
about-webauthn-auth-info-null = Not supported


about-webauthn-samples-still-needed =
    { $repeatCount ->
        [one] { $repeatCount } sample still needed.
       *[other] { $repeatCount } samples still needed.
    }

about-webauthn-ctap2-enroll-feedback-good = Sample was good.


about-webauthn-ctap2-enroll-feedback-too-high = Sample was too high.
about-webauthn-ctap2-enroll-feedback-too-low = Sample was too low.
about-webauthn-ctap2-enroll-feedback-too-left = Sample was too left.
about-webauthn-ctap2-enroll-feedback-too-right = Sample was too right.


about-webauthn-ctap2-enroll-feedback-too-fast = Sample was too fast.
about-webauthn-ctap2-enroll-feedback-too-slow = Sample was too slow.
about-webauthn-ctap2-enroll-feedback-poor-quality = Sample had poor quality.
about-webauthn-ctap2-enroll-feedback-too-skewed = Sample was too skewed.
about-webauthn-ctap2-enroll-feedback-too-short = Sample was too short.
about-webauthn-ctap2-enroll-feedback-merge-failure = Sample merge failure.
about-webauthn-ctap2-enroll-feedback-exists = Sample already exists.
about-webauthn-ctap2-enroll-feedback-no-user-activity = No activity from user.
about-webauthn-ctap2-enroll-feedback-no-user-presence-transition = User did not complete the sampling as expected.
about-webauthn-ctap2-enroll-feedback-other = Sample error.
PK
!<��1�0localization/en-US/toolkit/about/aboutWebrtc.ftl

about-webrtc-document-title = WebRTC Internals

about-webrtc-save-page-dialog-title = save about:webrtc as


about-webrtc-closed-peerconnection-disclosure-show-msg = Show Closed PeerConnections
about-webrtc-closed-peerconnection-disclosure-hide-msg = Hide Closed PeerConnections


about-webrtc-aec-logging-msg-label = AEC Logging
about-webrtc-aec-logging-off-state-label = Start AEC Logging
about-webrtc-aec-logging-on-state-label = Stop AEC Logging
about-webrtc-aec-logging-toggled-on-state-msg = AEC logging active (speak with the caller for a few minutes and then stop the capture)
about-webrtc-aec-logging-unavailable-sandbox = The environment variable MOZ_DISABLE_CONTENT_SANDBOX=1 is required to export AEC logs. Only set this variable if you understand the possible risks.
about-webrtc-aec-logging-toggled-off-state-msg = Captured log files can be found in: { $path }


about-webrtc-auto-refresh-label = Auto Refresh

about-webrtc-force-refresh-button = Refresh


about-webrtc-peerconnection-id-label = PeerConnection ID:

about-webrtc-data-channels-opened-label = Data Channels Opened:

about-webrtc-data-channels-closed-label = Data Channels Closed:


about-webrtc-sdp-heading = SDP
about-webrtc-local-sdp-heading = Local SDP
about-webrtc-local-sdp-heading-offer = Local SDP (Offer)
about-webrtc-local-sdp-heading-answer = Local SDP (Answer)
about-webrtc-remote-sdp-heading = Remote SDP
about-webrtc-remote-sdp-heading-offer = Remote SDP (Offer)
about-webrtc-remote-sdp-heading-answer = Remote SDP (Answer)
about-webrtc-sdp-history-heading = SDP History
about-webrtc-sdp-parsing-errors-heading = SDP Parsing Errors


about-webrtc-rtp-stats-heading = RTP Stats


about-webrtc-ice-state = ICE State
about-webrtc-ice-stats-heading = ICE Stats
about-webrtc-ice-restart-count-label = ICE restarts:
about-webrtc-ice-rollback-count-label = ICE rollbacks:
about-webrtc-ice-pair-bytes-sent = Bytes sent:
about-webrtc-ice-pair-bytes-received = Bytes received:
about-webrtc-ice-component-id = Component ID


about-webrtc-type-local = Local
about-webrtc-type-remote = Remote


about-webrtc-nominated = Nominated

about-webrtc-selected = Selected

about-webrtc-save-page-label = Save Page
about-webrtc-enable-logging-label = Enable WebRTC Log Preset
about-webrtc-peerconnections-section-heading = RTCPeerConnection Statistics
about-webrtc-peerconnections-section-show-msg = Show RTCPeerConnection Statistics
about-webrtc-peerconnections-section-hide-msg = Hide RTCPeerConnection Statistics
about-webrtc-stats-clear = Clear History
about-webrtc-log-heading = Connection Log
about-webrtc-log-clear = Clear Log
about-webrtc-log-section-show-msg = Show Log
    .title = Click to expand this section
about-webrtc-log-section-hide-msg = Hide Log
    .title = Click to collapse this section
about-webrtc-copy-report-button = Copy Report
about-webrtc-copy-report-history-button = Copy Report History


about-webrtc-connection-open = [ { $browser-id } | { $id } ] { $url } { $now }
about-webrtc-connection-closed = [ { $browser-id } | { $id } ] { $url } (closed) { $now }


about-webrtc-short-send-receive-direction = Send / Receive: { $codecs }
about-webrtc-short-send-direction = Send: { $codecs }
about-webrtc-short-receive-direction = Receive: { $codecs }


about-webrtc-local-candidate = Local Candidate
about-webrtc-remote-candidate = Remote Candidate
about-webrtc-raw-candidates-heading = All Raw Candidates
about-webrtc-raw-local-candidate = Raw Local Candidate
about-webrtc-raw-remote-candidate = Raw Remote Candidate
about-webrtc-raw-cand-section-show-msg = Show Raw Candidates
    .title = Click to expand this section
about-webrtc-raw-cand-section-hide-msg = Hide Raw Candidates
    .title = Click to collapse this section
about-webrtc-priority = Priority
about-webrtc-fold-default-show-msg = Show Details
    .title = Click to expand this section
about-webrtc-fold-default-hide-msg = Hide Details
    .title = Click to collapse this section
about-webrtc-dropped-frames-label = Dropped frames:
about-webrtc-discarded-packets-label = Discarded packets:
about-webrtc-decoder-label = Decoder
about-webrtc-encoder-label = Encoder
about-webrtc-show-tab-label = Show tab
about-webrtc-current-framerate-label = Framerate
about-webrtc-width-px = Width (px)
about-webrtc-height-px = Height (px)
about-webrtc-consecutive-frames = Consecutive Frames
about-webrtc-time-elapsed = Time Elapsed (s)
about-webrtc-estimated-framerate = Estimated Framerate
about-webrtc-rotation-degrees = Rotation (degrees)
about-webrtc-first-frame-timestamp = First Frame Reception Timestamp
about-webrtc-last-frame-timestamp = Last Frame Reception Timestamp


about-webrtc-local-receive-ssrc = Local Receiving SSRC
about-webrtc-remote-send-ssrc = Remote Sending SSRC


about-webrtc-pc-configuration-show-msg = Show Configuration
about-webrtc-pc-configuration-hide-msg = Hide Configuration


about-webrtc-configuration-element-provided = Provided

about-webrtc-configuration-element-not-provided = Not Provided

about-webrtc-user-modified-configuration-heading = User Modified WebRTC Configuration


about-webrtc-user-modified-configuration-show-msg = Show User Modified Configuration
about-webrtc-user-modified-configuration-hide-msg = Hide User Modified Configuration


about-webrtc-bandwidth-stats-heading = Estimated Bandwidth

about-webrtc-track-identifier = Track Identifier

about-webrtc-send-bandwidth-bytes-sec = Send Bandwidth (bytes/sec)

about-webrtc-receive-bandwidth-bytes-sec = Receive Bandwidth (bytes/sec)

about-webrtc-max-padding-bytes-sec = Maximum Padding (bytes/sec)

about-webrtc-pacer-delay-ms = Pacer Delay ms

about-webrtc-round-trip-time-ms = RTT ms

about-webrtc-frame-stats-heading = Video Frame Statistics - MediaStreamTrack ID: { $track-identifier }

about-webrtc-save-page-complete-msg = Page saved to: { $path }

about-webrtc-frames =
  { $frames ->
      [one] { $frames } frame
     *[other] { $frames } frames
  }

about-webrtc-channels =
  { $channels ->
      [one] { $channels } channel
     *[other] { $channels } channels
  }

about-webrtc-received-label =
  { $packets ->
      [one] Received { $packets } packet
     *[other] Received { $packets } packets
  }

about-webrtc-lost-label =
  { $packets ->
      [one] Lost { $packets } packet
     *[other] Lost { $packets } packets
  }

about-webrtc-sent-label =
  { $packets ->
      [one] Sent { $packets } packet
     *[other] Sent { $packets } packets
  }

about-webrtc-jitter-label = Jitter { $jitter }

about-webrtc-trickle-caption-msg = Trickled candidates (arriving after answer) are highlighted in blue


about-webrtc-sdp-set-at-timestamp-local = Set Local SDP at timestamp { NUMBER($timestamp, useGrouping: "false") }

about-webrtc-sdp-set-at-timestamp-remote = Set Remote SDP at timestamp { NUMBER($timestamp, useGrouping: "false") }

about-webrtc-sdp-set-timestamp = Timestamp { NUMBER($timestamp, useGrouping: "false") } (+ { $relative-timestamp } ms)


about-webrtc-show-msg-sdp = Show SDP
about-webrtc-hide-msg-sdp = Hide SDP


about-webrtc-media-context-show-msg = Show Media Context
about-webrtc-media-context-hide-msg = Hide Media Context
about-webrtc-media-context-heading = Media Context

PK
!<���;��9localization/en-US/toolkit/about/aboutWindowsMessages.ftl

windows-messages-page-title = Windows Messages Information
windows-messages-intro =
    This page shows the most recent messages sent by Windows
    to the { -brand-short-name } browser windows. The
    bolded entry represents this window. Note that this page shows
    the most recent messages at the time the page was loaded;
    to see current ones you will need to refresh the page.
windows-messages-copy-to-clipboard = Copy to clipboard
PK
!<�@���/localization/en-US/toolkit/about/certviewer.ftl
certificate-viewer-certificate-section-title = Certificate


certificate-viewer-error-message = We were unable to find the certificate information, or the certificate is corrupted. Please try again.
certificate-viewer-error-title = Something went wrong.


certificate-viewer-algorithm = Algorithm
certificate-viewer-certificate-authority = Certificate Authority
certificate-viewer-cipher-suite = Cipher Suite
certificate-viewer-common-name = Common Name
certificate-viewer-email-address = Email Address
certificate-viewer-tab-title = Certificate for { $firstCertName }
certificate-viewer-inc-country = Inc. Country
certificate-viewer-country = Country
certificate-viewer-curve = Curve
certificate-viewer-distribution-point = Distribution Point
certificate-viewer-dns-name = DNS Name
certificate-viewer-ip-address = IP Address
certificate-viewer-other-name = Other Name
certificate-viewer-exponent = Exponent
certificate-viewer-id = ID
certificate-viewer-key-exchange-group = Key Exchange Group
certificate-viewer-key-id = Key ID
certificate-viewer-key-size = Key Size
certificate-viewer-inc-locality = Inc. Locality
certificate-viewer-locality = Locality
certificate-viewer-location = Location
certificate-viewer-logid = Log ID
certificate-viewer-method = Method
certificate-viewer-modulus = Modulus
certificate-viewer-name = Name
certificate-viewer-not-after = Not After
certificate-viewer-not-before = Not Before
certificate-viewer-organization = Organization
certificate-viewer-organizational-unit = Organizational Unit
certificate-viewer-policy = Policy
certificate-viewer-protocol = Protocol
certificate-viewer-public-value = Public Value
certificate-viewer-purposes = Purposes
certificate-viewer-qualifier = Qualifier
certificate-viewer-qualifiers = Qualifiers
certificate-viewer-required = Required
certificate-viewer-unsupported = &lt;unsupported&gt;
certificate-viewer-inc-state-province = Inc. State/Province
certificate-viewer-state-province = State/Province
certificate-viewer-sha-1 = SHA-1
certificate-viewer-sha-256 = SHA-256
certificate-viewer-serial-number = Serial Number
certificate-viewer-signature-algorithm = Signature Algorithm
certificate-viewer-signature-scheme = Signature Scheme
certificate-viewer-timestamp = Timestamp
certificate-viewer-value = Value
certificate-viewer-version = Version
certificate-viewer-business-category = Business Category
certificate-viewer-subject-name = Subject Name
certificate-viewer-issuer-name = Issuer Name
certificate-viewer-validity = Validity
certificate-viewer-subject-alt-names = Subject Alt Names
certificate-viewer-public-key-info = Public Key Info
certificate-viewer-miscellaneous = Miscellaneous
certificate-viewer-fingerprints = Fingerprints
certificate-viewer-basic-constraints = Basic Constraints
certificate-viewer-key-usages = Key Usages
certificate-viewer-extended-key-usages = Extended Key Usages
certificate-viewer-ocsp-stapling = OCSP Stapling
certificate-viewer-subject-key-id = Subject Key ID
certificate-viewer-authority-key-id = Authority Key ID
certificate-viewer-authority-info-aia = Authority Info (AIA)
certificate-viewer-certificate-policies = Certificate Policies
certificate-viewer-embedded-scts = Embedded SCTs
certificate-viewer-crl-endpoints = CRL Endpoints

certificate-viewer-download = Download
certificate-viewer-boolean = { $boolean ->
  [true] Yes
 *[false] No
}


certificate-viewer-download-pem = PEM (cert)
  .download = { $fileName }.pem
certificate-viewer-download-pem-chain = PEM (chain)
  .download = { $fileName }-chain.pem

certificate-viewer-critical-extension =
  .title = This extension has been marked as critical, meaning that clients must reject the certificate if they do not understand it.
certificate-viewer-export = Export
  .download = { $fileName }.pem


certificate-viewer-unknown-group-label = (unknown)


certificate-viewer-tab-mine = Your Certificates
certificate-viewer-tab-people = People
certificate-viewer-tab-servers = Servers
certificate-viewer-tab-ca = Authorities
certificate-viewer-tab-unkonwn = Unknown
PK
!<
��|+localization/en-US/toolkit/about/config.ftl

about-config-intro-warning-title = Proceed with Caution
about-config-intro-warning-text = Changing advanced configuration preferences can impact { -brand-short-name } performance or security.
about-config-intro-warning-checkbox = Warn me when I attempt to access these preferences
about-config-intro-warning-button = Accept the Risk and Continue


about-config-caution-text = Changing these preferences can impact { -brand-short-name } performance or security.

about-config-page-title = Advanced Preferences

about-config-search-input1 =
    .placeholder = Search preference name
about-config-show-all = Show All

about-config-show-only-modified = Show only modified preferences

about-config-pref-add-button =
    .title = Add
about-config-pref-toggle-button =
    .title = Toggle
about-config-pref-edit-button =
    .title = Edit
about-config-pref-save-button =
    .title = Save
about-config-pref-reset-button =
    .title = Reset
about-config-pref-delete-button =
    .title = Delete


about-config-pref-add-type-boolean = Boolean
about-config-pref-add-type-number = Number
about-config-pref-add-type-string = String


about-config-pref-accessible-value-default =
    .aria-label = { $value } (default)
about-config-pref-accessible-value-custom =
    .aria-label = { $value } (custom)
PK
!<�C	C	3localization/en-US/toolkit/about/url-classifier.ftl
url-classifier-title = URL Classifier Information
url-classifier-search-title = Search
url-classifier-search-result-title = Results
url-classifier-search-result-uri = URI: { $uri }
url-classifier-search-result-list = List of tables: { $list }
url-classifier-search-input = URL
url-classifier-search-error-invalid-url = Invalid URL
url-classifier-search-error-no-features = No features selected
url-classifier-search-btn = Start searching
url-classifier-search-features = Features
url-classifier-search-listType = List type
url-classifier-provider-title = Provider
url-classifier-provider = Provider
url-classifier-provider-last-update-time = Last update time
url-classifier-provider-next-update-time = Next update time
url-classifier-provider-back-off-time = Back-off time
url-classifier-provider-last-update-status = Last update status
url-classifier-provider-update-btn = Update
url-classifier-cache-title = Cache
url-classifier-cache-refresh-btn = Refresh
url-classifier-cache-clear-btn = Clear
url-classifier-cache-table-name = Table name
url-classifier-cache-ncache-entries = Number of negative cache entries
url-classifier-cache-pcache-entries = Number of positive cache entries
url-classifier-cache-show-entries = Show entries
url-classifier-cache-entries = Cache Entries
url-classifier-cache-prefix = Prefix
url-classifier-cache-ncache-expiry = Negative cache expiry
url-classifier-cache-fullhash = Full hash
url-classifier-cache-pcache-expiry = Positive cache expiry
url-classifier-debug-title = Debug
url-classifier-debug-module-btn = Set Log Modules
url-classifier-debug-file-btn = Set Log File
url-classifier-debug-js-log-chk = Set JS Log
url-classifier-debug-sb-modules = Safe Browsing log modules
url-classifier-debug-modules = Current log modules
url-classifier-debug-sbjs-modules = Safe Browsing JS log
url-classifier-debug-file = Current log file

url-classifier-trigger-update = Trigger Update
url-classifier-not-available = N/A
url-classifier-disable-sbjs-log = Disable Safe Browsing JS Log
url-classifier-enable-sbjs-log = Enable Safe Browsing JS Log
url-classifier-enabled = Enabled
url-classifier-disabled = Disabled
url-classifier-updating = updating
url-classifier-cannot-update = cannot update
url-classifier-success = success


url-classifier-update-error = update error ({ $error })
url-classifier-download-error = download error ({ $error })
PK
!<xq����>localization/en-US/toolkit/contentanalysis/contentanalysis.ftl
contentanalysis-alert-title = Content Analysis

contentanalysis-slow-agent-notification = The Content Analysis tool is taking a long time to respond for resource “{ $content }”
contentanalysis-slow-agent-dialog-header = Scan in progress

contentanalysis-slow-agent-dialog-body-file = { $agent } is reviewing “{ $filename }” against your organization’s data policies. This may take a moment.
contentanalysis-slow-agent-dialog-body-clipboard = { $agent } is reviewing what you pasted against your organization’s data policies. This may take a moment.
contentanalysis-slow-agent-dialog-body-dropped-text = { $agent } is reviewing the text you dropped against your organization’s data policies. This may take a moment.
contentanalysis-slow-agent-dialog-body-print = { $agent } is reviewing what you printed against your organization’s data policies. This may take a moment.
contentanalysis-operationtype-clipboard = clipboard
contentanalysis-operationtype-dropped-text = dropped text
contentanalysis-operationtype-print = print
contentanalysis-customdisplaystring-description = upload of “{ $filename }”

contentanalysis-warndialogtitle = This content may be unsafe

contentanalysis-warndialogtext = Your organization uses data-loss prevention software that has flagged this content as unsafe: { $content }. Use it anyway?
contentanalysis-warndialog-response-allow = Use content
contentanalysis-warndialog-response-deny = Cancel

contentanalysis-notification-title = Content Analysis
contentanalysis-genericresponse-message = Content Analysis responded with { $response } for resource: { $content }
contentanalysis-block-message = Your organization uses data-loss prevention software that has blocked this content: { $content }.
contentanalysis-unspecified-error-message-content = An error occurred in communicating with { $agent }. { $content }
contentanalysis-no-agent-connected-message-content = Unable to connect to { $agent }. { $content }
contentanalysis-invalid-agent-signature-message-content = Failed signature verification for { $agent }. { $content }
contentanalysis-error-message-upload-file = Upload of “{ $filename }” denied.
contentanalysis-error-message-dropped-text = Drag and drop denied.
contentanalysis-error-message-clipboard = Paste denied.
contentanalysis-error-message-print = Print denied.

contentanalysis-block-dialog-title-upload-file = You’re not permitted to upload this file
contentanalysis-block-dialog-body-upload-file = Under your organization’s data protection policies, you’re not permitted to upload the file “{ $filename }”. Contact your administrator for more info.
contentanalysis-block-dialog-title-clipboard = You’re not permitted to paste this content
contentanalysis-block-dialog-body-clipboard = Under your organization’s data protection policies, you’re not permitted to paste this content. Contact your administrator for more info.
contentanalysis-block-dialog-title-dropped-text = You’re not permitted to drop this content
contentanalysis-block-dialog-body-dropped-text = Under your organization’s data protection policies, you’re not permitted to drag and drop this content. Contact your administrator for more info.
contentanalysis-block-dialog-title-print = You’re not permitted to print this document
contentanalysis-block-dialog-body-print = Under your organization’s data protection policies, you’re not permitted to print this document. Contact your administrator for more info.

contentanalysis-inprogress-quit-title = Quit { -brand-shorter-name }?
contentanalysis-inprogress-quit-message = Several actions are in progress. If you quit { -brand-shorter-name }, these actions will not be completed.
contentanalysis-inprogress-quit-yesbutton = Yes, quit
PK
!<��^3localization/en-US/toolkit/downloads/downloadUI.ftl
download-ui-confirm-title = Cancel All Downloads?


download-ui-confirm-quit-cancel-downloads =
    { $downloadsCount ->
        [1] If you exit now, 1 download will be canceled. Are you sure you want to exit?
       *[other] If you exit now, { $downloadsCount } downloads will be canceled. Are you sure you want to exit?
    }
download-ui-confirm-quit-cancel-downloads-mac =
    { $downloadsCount ->
        [1] If you quit now, 1 download will be canceled. Are you sure you want to quit?
       *[other] If you quit now, { $downloadsCount } downloads will be canceled. Are you sure you want to quit?
    }
download-ui-dont-quit-button =
    { PLATFORM() ->
        [mac] Don’t Quit
       *[other] Don’t Exit
    }

download-ui-confirm-offline-cancel-downloads =
    { $downloadsCount ->
        [1] If you go offline now, 1 download will be canceled. Are you sure you want to go offline?
       *[other] If you go offline now, { $downloadsCount } downloads will be canceled. Are you sure you want to go offline?
    }
download-ui-dont-go-offline-button = Stay Online

download-ui-confirm-leave-private-browsing-windows-cancel-downloads =
    { $downloadsCount ->
        [1] If you close all Private Browsing windows now, 1 download will be canceled. Are you sure you want to leave Private Browsing?
       *[other] If you close all Private Browsing windows now, { $downloadsCount } downloads will be canceled. Are you sure you want to leave Private Browsing?
    }
download-ui-dont-leave-private-browsing-button = Stay in Private Browsing

download-ui-cancel-downloads-ok =
    { $downloadsCount ->
        [1] Cancel 1 Download
       *[other] Cancel { $downloadsCount } Downloads
    }


download-ui-file-executable-security-warning-title = Open Executable File?
download-ui-file-executable-security-warning = “{ $executable }” is an executable file. Executable files may contain viruses or other malicious code that could harm your computer. Use caution when opening this file. Are you sure you want to launch “{ $executable }”?
PK
!<È2?HH6localization/en-US/toolkit/downloads/downloadUtils.ftl

download-utils-short-seconds =
    { $timeValue ->
        [one] s
       *[other] s
    }
download-utils-short-minutes =
    { $timeValue ->
        [one] m
       *[other] m
    }
download-utils-short-hours =
    { $timeValue ->
        [one] h
       *[other] h
    }
download-utils-short-days =
    { $timeValue ->
        [one] d
       *[other] d
    }


download-utils-status = { $timeLeft } — { $transfer } ({ $rate } { $unit }/sec)
download-utils-status-infinite-rate = { $timeLeft } — { $transfer } (Really fast)
download-utils-status-no-rate = { $timeLeft } — { $transfer }

download-utils-bytes = bytes
download-utils-kilobyte = KB
download-utils-megabyte = MB
download-utils-gigabyte = GB

download-utils-transfer-same-units = { $progress } of { $total } { $totalUnits }
download-utils-transfer-diff-units = { $progress } { $progressUnits } of { $total } { $totalUnits }
download-utils-transfer-no-total = { $progress } { $progressUnits }

download-utils-time-pair = { $time }{ $unit }
download-utils-time-left-single = { $time } left
download-utils-time-left-double = { $time1 } { $time2 } left
download-utils-time-few-seconds = A few seconds left
download-utils-time-unknown = Unknown time left

download-utils-done-scheme = { $scheme } resource
download-utils-done-file-scheme = local file

download-utils-yesterday = Yesterday
PK
!<|�X��4localization/en-US/toolkit/featuregates/features.ftl
experimental-features-css-masonry2 =
    .label = CSS: Masonry Layout
experimental-features-css-masonry-description = Enables support for the experimental CSS Masonry Layout feature. See the <a data-l10n-name="explainer">explainer</a> for a high level description of the feature. To provide feedback, please comment in <a data-l10n-name="w3c-issue">this GitHub issue</a> or <a data-l10n-name="bug">this bug</a>.

experimental-features-web-gpu2 =
    .label = Web API: WebGPU
experimental-features-web-gpu-description3 = The <a data-l10n-name="wikipedia-webgpu">WebGPU API</a> provides low-level support for performing computation and graphics rendering using the <a data-l10n-name="wikipedia-gpu">Graphics Processing Unit (GPU)</a> of the user’s device or computer. The first version of the <a data-l10n-name="spec">specification</a> is nearing finalization. See <a data-l10n-name="bugzilla">bug 1616739</a> for more details.

experimental-features-media-jxl =
    .label = Media: JPEG XL
experimental-features-media-jxl-description = With this feature enabled, { -brand-short-name } supports the JPEG XL (JXL) format. This is an enhanced image file format that supports lossless transition from traditional JPEG files. See <a data-l10n-name="bugzilla">bug 1539075</a> for more details.

experimental-features-devtools-serviceworker-debugger-support =
    .label = Developer Tools: Service Worker debugging
experimental-features-devtools-serviceworker-debugger-support-description = Enables experimental support for Service Workers in the Debugger panel. This feature may slow the Developer Tools down and increase memory consumption.

experimental-features-webrtc-global-mute-toggles =
    .label = WebRTC Global Mute Toggles
experimental-features-webrtc-global-mute-toggles-description = Add controls to the WebRTC global sharing indicator that allow users to globally mute their microphone and camera feeds.

experimental-features-js-warp =
    .label = JavaScript JIT: Warp
experimental-features-js-warp-description = Enable Warp, a project to improve JavaScript performance and memory usage.

experimental-features-ime-search =
    .label = Address Bar: show results during IME composition
experimental-features-ime-search-description = An IME (Input Method Editor) is a tool that allows you to enter complex symbols, such as those used in East Asian or Indic written languages, using a standard keyboard. Enabling this experiment will keep the address bar panel open, showing search results and suggestions, while using IME to input text. Note that the IME might display a panel that covers the address bar results, therefore this preference is only suggested for IME not using this type of panel.

experimental-features-auto-pip =
    .label = Picture-in-Picture: auto-open on tab switch
experimental-features-auto-pip-description = Enable Picture-in-Picture on active videos when switching tabs.

genai-settings-chat-title =
    .label = AI chatbot

sidebar-title =
    .label = Sidebar
sidebar-description = Easily access your history, tabs from other devices, sidebar extensions, and more as you browse. <a data-l10n-name="connect">Share feedback</a>
vertical-tabs-title =
    .label = Vertical tabs
vertical-tabs-description = Move your tabs from the top of the browser to the side. Requires you also try the sidebar. <a data-l10n-name="connect">Share feedback</a>

experimental-features-group-developer-tools =
  .label = Developer Tools
experimental-features-group-webpage-display =
  .label = Webpage Display
experimental-features-group-customize-browsing =
  .label = Customize your browsing
experimental-features-group-customize-browsing-description = If you try these features, <a data-l10n-name="connect">share your thoughts on Connect</a>
PK
!<�
��	�	8localization/en-US/toolkit/formautofill/formAutofill.ftl

autofill-use-payment-method-os-prompt-macos = use stored payment method information
autofill-use-payment-method-os-prompt-windows = { -brand-short-name } is trying to use stored payment method information. Confirm access to this Windows account below.
autofill-use-payment-method-os-prompt-other = { -brand-short-name } is trying to use stored payment method information.


autofill-edit-payment-method-os-prompt-macos = show stored payment method information
autofill-edit-payment-method-os-prompt-windows = { -brand-short-name } is trying to show stored payment method information. Confirm access to this Windows account below.
autofill-edit-payment-method-os-prompt-other = { -brand-short-name } is trying to show stored payment method information.

autofill-options-link = Form Autofill Options
autofill-options-link-osx = Form Autofill Preferences


credit-card-doorhanger-credit-cards-sync-checkbox = Sync all saved cards across my devices

credit-card-save-doorhanger-header = Securely save this card?
credit-card-save-doorhanger-description = { -brand-short-name } encrypts your card number. Your security code won’t be saved.

credit-card-capture-save-button =
    .label = Save
    .accessKey = S
credit-card-capture-cancel-button =
    .label = Not now
    .accessKey = W
credit-card-capture-never-save-button =
    .label = Never save cards
    .accessKey = N


credit-card-update-doorhanger-header = Update card?
credit-card-update-doorhanger-description = Card to update:

credit-card-capture-save-new-button =
    .label = Save as new card
    .accessKey = C
credit-card-capture-update-button =
    .label = Update existing card
    .accessKey = U

autofill-clear-form-label = Clear Autofill Form

autofill-manage-addresses-label = Manage addresses

autofill-manage-payment-methods-label = Manage payment methods


autofill-card-network-amex = American Express
autofill-card-network-cartebancaire = Carte Bancaire
autofill-card-network-diners = Diners Club
autofill-card-network-discover = Discover
autofill-card-network-jcb = JCB
autofill-card-network-mastercard = MasterCard
autofill-card-network-mir = MIR
autofill-card-network-unionpay = Union Pay
autofill-card-network-visa = Visa

autofill-phishing-warningmessage-extracategory = Also autofills { $categories }

autofill-phishing-warningmessage = Autofills { $categories }

autofill-category-address = address
autofill-category-name = name
autofill-category-organization = organization
autofill-category-tel = phone
autofill-category-email = email
PK
!<�I䥖�+localization/en-US/toolkit/global/alert.ftl
alert-close =
    .tooltiptext = Close this notification
alert-settings-title =
    .tooltiptext = Settings


notification-default-dismiss = Dismiss
PK
!<��,		2localization/en-US/toolkit/global/antiTracking.ftl
btp-warning-tracker-classified =
    { $gracePeriodSeconds ->
        *[other] “{ $siteHost }” has been classified as a bounce tracker. If it does not receive user activation within the next { $gracePeriodSeconds } seconds it will have its state purged.
    }
PK
!<C�N��/localization/en-US/toolkit/global/appPicker.ftl
app-picker-browse-button =
    .buttonlabelextra2 = Browse…
app-picker-send-msg =
    .value = Send this item to:
app-picker-no-app-found =
    .value = No applications were found for this file type.
PK
!<�
�[[3localization/en-US/toolkit/global/browser-utils.ftl
browser-utils-url-data = (data)

browser-utils-url-extension = Extension ({ $extension })
PK
!<�,Ǐ�2localization/en-US/toolkit/global/commonDialog.ftl
common-dialog-title-null = This page says
common-dialog-title-system = { -brand-short-name }
common-dialog-title-unknown = Unknown

common-dialog-spinner =
  .alt = Busy

common-dialog-username =
  .value = Username
common-dialog-password =
  .value = Password

common-dialog-copy-cmd =
    .label = Copy
    .accesskey = C
common-dialog-select-all-cmd =
    .label = Select All
    .accesskey = A
PK
!<=.�kk:localization/en-US/toolkit/global/cookieBannerHandling.ftl
cookie-banner-handled-webconsole = { -brand-shorter-name } handled a cookie banner on behalf of the user.
PK
!<���39localization/en-US/toolkit/global/createProfileWizard.ftl
create-profile-window2 =
  .title = Create Profile Wizard
  .style = min-width: 45em; min-height: 32em;


create-profile-first-page-header2 =
  { PLATFORM() ->
    [macos] Introduction
   *[other] Welcome to the { create-profile-window2.title }
  }

profile-creation-explanation-1 = { -brand-short-name } stores information about your settings and preferences in your personal profile.

profile-creation-explanation-2 = If you are sharing this copy of { -brand-short-name } with other users, you can use profiles to keep each user’s information separate. To do this, each user should create his or her own profile.

profile-creation-explanation-3 = If you are the only person using this copy of { -brand-short-name }, you must have at least one profile. If you would like, you can create multiple profiles for yourself to store different sets of settings and preferences. For example, you may want to have separate profiles for business and personal use.

profile-creation-explanation-4 =
  { PLATFORM() ->
    [macos] To begin creating your profile, click Continue.
   *[other] To begin creating your profile, click Next.
  }


create-profile-last-page-header2 =
  { PLATFORM() ->
    [macos] Conclusion
   *[other] Completing the { create-profile-window2.title }
  }

profile-creation-intro = If you create several profiles you can tell them apart by the profile names. You may use the name provided here or use one of your own.

profile-prompt = Enter new profile name:
  .accesskey = E

profile-default-name =
  .value = Default User

profile-directory-explanation = Your user settings, preferences and other user-related data will be stored in:

create-profile-choose-folder =
  .label = Choose Folder…
  .accesskey = C

create-profile-use-default =
  .label = Use Default Folder
  .accesskey = U
PK
!<e��U��/localization/en-US/toolkit/global/cspErrors.ftl
csp-error-missing-directive = Policy is missing a required ‘{ $directive }’ directive

csp-error-illegal-keyword = ‘{ $directive }’ directive contains a forbidden { $keyword } keyword

csp-error-illegal-protocol = ‘{ $directive }’ directive contains a forbidden { $scheme }: protocol source

csp-error-missing-host = { $scheme }: protocol requires a host in ‘{ $directive }’ directives

csp-error-missing-source = ‘{ $directive }’ must include the source { $source }

csp-error-illegal-host-wildcard = { $scheme }: wildcard sources in ‘{ $directive }’ directives must include at least one non-generic sub-domain (e.g., *.example.com rather than *.com)
PK
!<S$L�II0localization/en-US/toolkit/global/datepicker.ftl


date-picker-label =
    .aria-label = Choose a date
date-spinner-label =
    .aria-label = Choose a month and a year


date-picker-clear-button = Clear


date-picker-previous =
    .aria-label = Previous month
date-picker-next =
    .aria-label = Next month


date-spinner-month =
    .aria-label = Month
date-spinner-year =
    .aria-label = Year


date-spinner-month-previous =
    .aria-label = Previous month
date-spinner-month-next =
    .aria-label = Next month
date-spinner-year-previous =
    .aria-label = Previous year
date-spinner-year-next =
    .aria-label = Next year
PK
!<7��1localization/en-US/toolkit/global/datetimebox.ftl

datetime-year-placeholder = yyyy
datetime-month-placeholder = mm
datetime-day-placeholder = dd
datetime-time-placeholder = --


datetime-year =
    .aria-label = Year
datetime-month =
    .aria-label = Month
datetime-day =
    .aria-label = Day


datetime-hour =
    .aria-label = Hours
datetime-minute =
    .aria-label = Minutes
datetime-second =
    .aria-label = Seconds
datetime-millisecond =
    .aria-label = Milliseconds
datetime-dayperiod =
    .aria-label = AM/PM



datetime-calendar =
    .aria-label = Calendar
PK
!<�Y���:localization/en-US/toolkit/global/extensionPermissions.ftl

webext-perms-description-bookmarks = Read and modify bookmarks
webext-perms-description-browserSettings = Read and modify browser settings
webext-perms-description-browsingData = Clear recent browsing history, cookies, and related data
webext-perms-description-clipboardRead = Get data from the clipboard
webext-perms-description-clipboardWrite = Input data to the clipboard
webext-perms-description-declarativeNetRequest = Block content on any page
webext-perms-description-declarativeNetRequestFeedback = Read your browsing history
webext-perms-description-devtools = Extend developer tools to access your data in open tabs
webext-perms-description-downloads = Download files and read and modify the browser’s download history
webext-perms-description-downloads-open = Open files downloaded to your computer
webext-perms-description-find = Read the text of all open tabs
webext-perms-description-geolocation = Access your location
webext-perms-description-history = Access browsing history
webext-perms-description-management = Monitor extension usage and manage themes
webext-perms-description-nativeMessaging = Exchange messages with programs other than { -brand-short-name }
webext-perms-description-notifications = Display notifications to you
webext-perms-description-pkcs11 = Provide cryptographic authentication services
webext-perms-description-privacy = Read and modify privacy settings
webext-perms-description-proxy = Control browser proxy settings
webext-perms-description-sessions = Access recently closed tabs
webext-perms-description-tabs = Access browser tabs
webext-perms-description-tabHide = Hide and show browser tabs
webext-perms-description-topSites = Access browsing history
webext-perms-description-webNavigation = Access browser activity during navigation
PK
!<��a�  0localization/en-US/toolkit/global/extensions.ftl

webext-perms-header = Add { $extension }?
webext-perms-header-with-perms = Add { $extension }? This extension will have permission to:
webext-perms-header-unsigned = Add { $extension }? This extension is unverified. Malicious extensions can steal your private information or compromise your computer. Only add it if you trust the source.
webext-perms-header-unsigned-with-perms = Add { $extension }? This extension is unverified. Malicious extensions can steal your private information or compromise your computer. Only add it if you trust the source. This extension will have permission to:
webext-perms-sideload-header = { $extension } added
webext-perms-optional-perms-header = { $extension } requests additional permissions.


webext-perms-add =
    .label = Add
    .accesskey = A
webext-perms-cancel =
    .label = Cancel
    .accesskey = C

webext-perms-sideload-text = Another program on your computer installed an add-on that may affect your browser. Please review this add-on’s permissions requests and choose to Enable or Cancel (to leave it disabled).
webext-perms-sideload-text-no-perms = Another program on your computer installed an add-on that may affect your browser. Please choose to Enable or Cancel (to leave it disabled).
webext-perms-sideload-enable =
    .label = Enable
    .accesskey = E
webext-perms-sideload-cancel =
    .label = Cancel
    .accesskey = C

webext-perms-update-text = { $extension } has been updated. You must approve new permissions before the updated version will install. Choosing “Cancel” will maintain your current extension version. This extension will have permission to:
webext-perms-update-accept =
    .label = Update
    .accesskey = U

webext-perms-optional-perms-list-intro = It wants to:
webext-perms-optional-perms-allow =
    .label = Allow
    .accesskey = A
webext-perms-optional-perms-deny =
    .label = Deny
    .accesskey = D

webext-perms-host-description-all-urls = Access your data for all websites

webext-perms-host-description-wildcard = Access your data for sites in the { $domain } domain

webext-perms-host-description-too-many-wildcards =
    { $domainCount ->
        [one] Access your data in { $domainCount } other domain
       *[other] Access your data in { $domainCount } other domains
    }
webext-perms-host-description-one-site = Access your data for { $domain }

webext-perms-host-description-too-many-sites =
    { $domainCount ->
        [one] Access your data on { $domainCount } other site
       *[other] Access your data on { $domainCount } other sites
    }


webext-site-perms-header-with-gated-perms-midi = This add-on gives { $hostname } access to your MIDI devices.
webext-site-perms-header-with-gated-perms-midi-sysex = This add-on gives { $hostname } access to your MIDI devices (with SysEx support).


webext-site-perms-description-gated-perms-midi =
    These are usually plug-in devices like audio synthesizers, but might also be built into your computer.

    Websites are normally not allowed to access MIDI devices. Improper usage could cause damage or compromise security.


webext-site-perms-header-with-perms = Add { $extension }? This extension grants the following capabilities to { $hostname }:
webext-site-perms-header-unsigned-with-perms = Add { $extension }? This extension is unverified. Malicious extensions can steal your private information or compromise your computer. Only add it if you trust the source. This extension grants the following capabilities to { $hostname }:


webext-site-perms-midi = Access MIDI devices
webext-site-perms-midi-sysex = Access MIDI devices with SysEx support
PK
!<��w��	�	3localization/en-US/toolkit/global/handlerDialog.ftl

permission-dialog-description =
  Allow this site to open the { $scheme } link?

permission-dialog-description-file =
  Allow this file to open the { $scheme } link?

permission-dialog-description-host =
  Allow { $host } to open the { $scheme } link?

permission-dialog-description-extension =
  Allow the extension { $extension } to open the { $scheme } link?

permission-dialog-description-app =
  Allow this site to open the { $scheme } link with { $appName }?

permission-dialog-description-host-app =
  Allow { $host } to open the { $scheme } link with { $appName }?

permission-dialog-description-file-app =
  Allow this file to open the { $scheme } link with { $appName }?

permission-dialog-description-extension-app =
  Allow the extension { $extension } to open the { $scheme } link with { $appName }?

permission-dialog-description-system-app =
  Open the { $scheme } link with { $appName }?

permission-dialog-description-system-noapp =
  Open the { $scheme } link?


permission-dialog-remember =
  Always allow <strong>{ $host }</strong> to open <strong>{ $scheme }</strong> links

permission-dialog-remember-file =
  Always allow this file to open <strong>{ $scheme }</strong> links

permission-dialog-remember-extension =
  Always allow this extension to open <strong>{ $scheme }</strong> links


permission-dialog-btn-open-link =
      .label = Open Link
      .accessKey = O

permission-dialog-btn-choose-app =
      .label = Choose Application
      .accessKey = A

permission-dialog-unset-description = You’ll need to choose an application.

permission-dialog-set-change-app-link = Choose a different application.


chooser-window =
      .title = Choose Application
      .style = min-width: 26em; min-height: 26em;

chooser-dialog =
      .buttonlabelaccept = Open Link
      .buttonaccesskeyaccept = O

chooser-dialog-description = Choose an application to open the { $scheme } link.

chooser-dialog-remember =
  Always use this application to open <strong>{ $scheme }</strong> links

chooser-dialog-remember-extra = {
  PLATFORM() ->
      [windows] This can be changed in { -brand-short-name }’s options.
     *[other] This can be changed in { -brand-short-name }’s preferences.
  }

choose-other-app-description = Choose other Application
choose-app-btn =
      .label = Choose…
      .accessKey = C
choose-other-app-window-title = Another Application…

choose-dialog-privatebrowsing-disabled = Disabled in Private Windows
PK
!<�q�~zz.localization/en-US/toolkit/global/htmlForm.ftl
input-file-and-more-files =
    { $fileCount ->
        [one] and one more
       *[other] and { $fileCount } more
    }
PK
!<�u �``1localization/en-US/toolkit/global/mozFiveStar.ftl
moz-five-star-rating =
  .title = Rated { NUMBER($rating, maximumFractionDigits: 1) } out of 5
PK
!<�{ڼ��7localization/en-US/toolkit/global/popupnotification.ftl
popup-notification-learn-more = Learn more
popup-notification-more-actions-button =
    .aria-label = More actions
popup-notification-default-button =
    .label = OK!
    .accesskey = O
PK
!<�V����2localization/en-US/toolkit/global/processTypes.ftl

process-type-web = Web Content

process-type-privilegedabout = Privileged About

process-type-privilegedmozilla = Privileged Mozilla Content

process-type-extension = Extension

process-type-file = Local File

process-type-forkserver = Fork Server

process-type-webisolated = Isolated Web Content

process-type-webserviceworker = Isolated Service Worker

process-type-prealloc = Preallocated


process-type-default = Main
process-type-tab = Tab

process-type-gpu = GPU

process-type-socket = Socket

process-type-rdd = RDD

process-type-inference = Inference

process-type-utility = Sandboxed IPC Actor
process-type-utility-actor-audio-decoder-generic = Utility Generic Audio Decoder
process-type-utility-actor-audio-decoder-applemedia = Utility AppleMedia
process-type-utility-actor-audio-decoder-wmf = Utility Windows Media Foundation
process-type-utility-actor-mf-media-engine = Utility Media Foundation Engine
process-type-utility-actor-js-oracle = Utility JavaScript Oracle
process-type-utility-actor-windows-utils = Utility Windows Utils
process-type-utility-actor-windows-file-dialog = Utility Windows File Dialog


process-type-unknown = Unknown
PK
!<�8���6localization/en-US/toolkit/global/profileDowngrade.ftl
profiledowngrade-window2 =
    .title = You’ve launched an older version of { -brand-product-name }
    .style = min-width: 490px;

profiledowngrade-window-create =
    .label = Create New Profile

profiledowngrade-sync2 = Using an older version of { -brand-product-name } can corrupt bookmarks and browsing history already saved to an existing { -brand-product-name } profile. To protect your information, create a new profile for this installation of { -brand-short-name }. You can always sign in with an account to sync your bookmarks and browsing history between profiles.
profiledowngrade-nosync = Using an older version of { -brand-product-name } can corrupt bookmarks and browsing history already saved to an existing { -brand-product-name } profile. To protect your information, create a new profile for this installation of { -brand-short-name }.

profiledowngrade-quit =
    .label = { PLATFORM() ->
                 [windows] Exit
                *[other] Quit
             }
PK
!<!J����6localization/en-US/toolkit/global/profileSelection.ftl
profile-selection-window =
  .title = { -brand-short-name } - Choose User Profile

profile-selection-button-accept =
  .label = Start { -brand-short-name }

profile-selection-button-cancel =
  .label = Exit

profile-selection-new-button =
  .label = Create Profile…
  .accesskey = C

profile-selection-rename-button =
  .label = Rename Profile…
  .accesskey = R

profile-selection-delete-button =
  .label = Delete Profile…
  .accesskey = D

profile-selection-conflict-message = Another copy of { -brand-product-name } has made changes to profiles. You must restart { -brand-short-name } before making more changes.


profile-manager-description = { -brand-short-name } stores information about your settings, preferences, and other user items in your user profile.

profile-manager-work-offline =
  .label = Work offline
  .accesskey = o

profile-manager-use-selected =
  .label = Use the selected profile without asking at startup
  .accesskey = s
PK
!<1��~~2localization/en-US/toolkit/global/resetProfile.ftl
refresh-profile-dialog-title = Refresh { -brand-short-name } to its default settings?
refresh-profile-dialog-button =
    .label = Refresh { -brand-short-name }
refresh-profile-dialog-description = Start fresh to fix performance issues. This will remove your extensions and customizations. You won’t lose essential information like bookmarks and passwords.
refresh-profile = Give { -brand-short-name } a tune up
refresh-profile-button = Refresh { -brand-short-name }…
refresh-profile-learn-more = Learn more

refresh-profile-progress =
    .title = Refresh { -brand-short-name }
refresh-profile-progress-description = Almost done…
PK
!<y�@b��:localization/en-US/toolkit/global/resistFingerPrinting.ftl
privacy-spoof-english = Changing your language setting to English will make you more difficult to identify and enhance your privacy. Do you want to request English language versions of web pages?
PK
!<���pp2localization/en-US/toolkit/global/run-from-dmg.ftl

prompt-to-install-title = Finish installing { -brand-short-name }?
prompt-to-install-message = Complete this one-step installation to help keep { -brand-short-name } up to date and prevent data loss. { -brand-short-name } will be added to your Applications folder and Dock.
prompt-to-install-yes-button = Install
prompt-to-install-no-button = Don’t Install


install-failed-title = { -brand-short-name } installation failed.
install-failed-message = { -brand-short-name } failed to install but will continue to run.


prompt-to-launch-existing-app-title = Open existing { -brand-short-name } application?
prompt-to-launch-existing-app-message = You already have { -brand-short-name } installed. Use the installed application to stay up to date and prevent data loss.
prompt-to-launch-existing-app-yes-button = Open existing
prompt-to-launch-existing-app-no-button = No thanks
PK
!<vp�EE*localization/en-US/toolkit/global/tree.ftl
tree-columnpicker-restore-order =
    .label = Restore Column Order
PK
!<`"e��8localization/en-US/toolkit/global/unknownContentType.ftl
unknowncontenttype-handleinternally =
    .label = Open with { -brand-short-name }
    .accesskey = e

unknowncontenttype-settingschange =
    .value =
        { PLATFORM() ->
            [windows] Settings can be changed in { -brand-short-name }’s Options.
           *[other] Settings can be changed in { -brand-short-name }’s Preferences.
        }

unknowncontenttype-intro = You have chosen to open:
unknowncontenttype-which-is = which is:
unknowncontenttype-from = from:
unknowncontenttype-prompt = Would you like to save this file?
unknowncontenttype-action-question = What should { -brand-short-name } do with this file?
unknowncontenttype-open-with =
    .label = Open with
    .accesskey = O
unknowncontenttype-other =
    .label = Other…
unknowncontenttype-choose-handler =
    .label =
        { PLATFORM() ->
            [macos] Choose…
           *[other] Browse…
        }
    .accesskey =
        { PLATFORM() ->
            [macos] C
           *[other] B
        }
unknowncontenttype-save-file =
    .label = Save File
    .accesskey = S
unknowncontenttype-remember-choice =
    .label = Do this automatically for files like this from now on.
    .accesskey = a
PK
!<�G���3localization/en-US/toolkit/global/videocontrols.ftl
videocontrols-buffer-bar-label = Loading:
videocontrols-volume-control =
    .aria-label = Volume
videocontrols-closed-caption-button =
    .aria-label = Closed Captions

videocontrols-play-button =
    .aria-label = Play
videocontrols-pause-button =
    .aria-label = Pause
videocontrols-mute-button =
    .aria-label = Mute
videocontrols-unmute-button =
    .aria-label = Unmute
videocontrols-enterfullscreen-button =
    .aria-label = Full Screen
videocontrols-exitfullscreen-button =
    .aria-label = Exit Full Screen
videocontrols-casting-button-label =
    .aria-label = Cast to Screen
videocontrols-closed-caption-off =
    .offlabel = Off

videocontrols-picture-in-picture-label = Picture-in-Picture

videocontrols-picture-in-picture-toggle-label2 = Pop out this video

videocontrols-picture-in-picture-explainer3 = More screens are more fun. Play this video while you do other things.

videocontrols-error-aborted = Video loading stopped.
videocontrols-error-network = Video playback aborted due to a network error.
videocontrols-error-decode = Video can’t be played because the file is corrupt.
videocontrols-error-src-not-supported = Video format or MIME type is not supported.
videocontrols-error-no-source = No video with supported format and MIME type found.
videocontrols-error-generic = Video playback aborted due to an unknown error.
videocontrols-status-picture-in-picture = This video is playing in Picture-in-Picture mode.

videocontrols-position-and-duration-labels = { $position }<span data-l10n-name="position-duration-format"> / { $duration }</span>

videocontrols-scrubber-position-and-duration =
    .aria-label = Position
    .aria-valuetext = { $position } / { $duration }
PK
!<�̕��,localization/en-US/toolkit/global/wizard.ftl
wizard-macos-button-back =
  .label = Go Back
  .accesskey = B
wizard-linux-button-back =
  .label = Back
  .accesskey = B
wizard-win-button-back =
  .label = < Back
  .accesskey = B

wizard-macos-button-next =
  .label = Continue
  .accesskey = C
wizard-linux-button-next =
  .label = Next
  .accesskey = N
wizard-win-button-next =
  .label = Next >
  .accesskey = N

wizard-macos-button-finish =
  .label = Done
wizard-linux-button-finish =
  .label = Finish
wizard-win-button-finish =
  .label = Finish

wizard-macos-button-cancel =
  .label = Cancel
wizard-linux-button-cancel =
  .label = Cancel
wizard-win-button-cancel =
  .label = Cancel
PK
!<�M�z��1localization/en-US/toolkit/intl/languageNames.ftl
language-name-aa = Afar
language-name-ab = Abkhazian
language-name-ach = Acholi
language-name-ae = Avestan
language-name-af = Afrikaans
language-name-ak = Akan
language-name-am = Amharic
language-name-an = Aragonese
language-name-ar = Arabic
language-name-as = Assamese
language-name-ast = Asturian
language-name-av = Avaric
language-name-ay = Aymara
language-name-az = Azerbaijani
language-name-ba = Bashkir
language-name-be = Belarusian
language-name-bg = Bulgarian
language-name-bh = Bihari
language-name-bi = Bislama
language-name-bm = Bambara
language-name-bn = Bengali
language-name-bo = Tibetan
language-name-br = Breton
language-name-bs = Bosnian
language-name-ca = Catalan
language-name-cak = Kaqchikel
language-name-ce = Chechen
language-name-ch = Chamorro
language-name-co = Corsican
language-name-cr = Cree
language-name-crh = Crimean Tatar
language-name-cs = Czech
language-name-csb = Kashubian
language-name-cu = Church Slavic
language-name-cv = Chuvash
language-name-cy = Welsh
language-name-da = Danish
language-name-de = German
language-name-dsb = Lower Sorbian
language-name-dv = Divehi
language-name-dz = Dzongkha
language-name-ee = Ewe
language-name-el = Greek
language-name-en = English
language-name-eo = Esperanto
language-name-es = Spanish
language-name-et = Estonian
language-name-eu = Basque
language-name-fa = Persian
language-name-ff = Fulah
language-name-fi = Finnish
language-name-fj = Fijian
language-name-fo = Faroese
language-name-fr = French
language-name-fur = Friulian
language-name-fy = Frisian
language-name-ga = Irish
language-name-gd = Scottish Gaelic
language-name-gl = Galician
language-name-gn = Guarani
language-name-gu = Gujarati
language-name-gv = Manx
language-name-ha = Hausa
language-name-haw = Hawaiian
language-name-he = Hebrew
language-name-hi = Hindi
language-name-hil = Hiligaynon
language-name-ho = Hiri Motu
language-name-hr = Croatian
language-name-hsb = Upper Sorbian
language-name-ht = Haitian
language-name-hu = Hungarian
language-name-hy = Armenian
language-name-hz = Herero
language-name-ia = Interlingua
language-name-id = Indonesian
language-name-ie = Interlingue
language-name-ig = Igbo
language-name-ii = Sichuan Yi
language-name-ik = Inupiaq
language-name-io = Ido
language-name-is = Icelandic
language-name-it = Italian
language-name-iu = Inuktitut
language-name-ja = Japanese
language-name-jv = Javanese
language-name-ka = Georgian
language-name-kab = Kabyle
language-name-kg = Kongo
language-name-ki = Kikuyu
language-name-kj = Kuanyama
language-name-kk = Kazakh
language-name-kl = Greenlandic
language-name-km = Khmer
language-name-kn = Kannada
language-name-ko = Korean
language-name-kok = Konkani
language-name-kr = Kanuri
language-name-ks = Kashmiri
language-name-ku = Kurdish
language-name-kv = Komi
language-name-kw = Cornish
language-name-ky = Kirghiz
language-name-la = Latin
language-name-lb = Luxembourgish
language-name-lg = Ganda
language-name-li = Limburgan
language-name-lij = Ligurian
language-name-ln = Lingala
language-name-lo = Lao
language-name-lt = Lithuanian
language-name-ltg = Latgalian
language-name-lu = Luba-Katanga
language-name-lv = Latvian
language-name-mai = Maithili
language-name-meh = Southwestern Tlaxiaco Mixtec
language-name-mg = Malagasy
language-name-mh = Marshallese
language-name-mi = Maori
language-name-mix = Mixtepec Mixtec
language-name-mk = Macedonian
language-name-ml = Malayalam
language-name-mn = Mongolian
language-name-mr = Marathi
language-name-ms = Malay
language-name-mt = Maltese
language-name-my = Burmese
language-name-na = Nauru
language-name-nb = Norwegian Bokmål
language-name-nd = Ndebele, North
language-name-ne = Nepali
language-name-ng = Ndonga
language-name-nl = Dutch
language-name-nn = Norwegian Nynorsk
language-name-no = Norwegian
language-name-nr = Ndebele, South
language-name-nso = Sotho, Northern
language-name-nv = Navajo
language-name-ny = Chichewa
language-name-oc = Occitan
language-name-oj = Ojibwa
language-name-om = Oromo
language-name-or = Odia
language-name-os = Ossetian
language-name-pa = Punjabi
language-name-pi = Pali
language-name-pl = Polish
language-name-ps = Pashto
language-name-pt = Portuguese
language-name-qu = Quechua
language-name-rm = Rhaeto-Romanic
language-name-rn = Kirundi
language-name-ro = Romanian
language-name-ru = Russian
language-name-rw = Kinyarwanda
language-name-sa = Sanskrit
language-name-sat = Santali
language-name-sc = Sardinian
language-name-sco = Scots
language-name-sd = Sindhi
language-name-se = Northern Sami
language-name-sg = Sango
language-name-si = Sinhala
language-name-sk = Slovak
language-name-skr = Saraiki
language-name-sl = Slovenian
language-name-sm = Samoan
language-name-sn = Shona
language-name-so = Somali
language-name-son = Songhay
language-name-sq = Albanian
language-name-sr = Serbian
language-name-ss = Siswati
language-name-st = Sotho, Southern
language-name-su = Sundanese
language-name-sv = Swedish
language-name-sw = Swahili
language-name-szl = Silesian
language-name-ta = Tamil
language-name-te = Telugu
language-name-tg = Tajik
language-name-th = Thai
language-name-ti = Tigrinya
language-name-tig = Tigre
language-name-tk = Turkmen
language-name-tl = Tagalog
language-name-tlh = Klingon
language-name-tn = Tswana
language-name-to = Tonga
language-name-tr = Turkish
language-name-trs = Triqui
language-name-ts = Tsonga
language-name-tt = Tatar
language-name-tw = Twi
language-name-ty = Tahitian
language-name-ug = Uighur
language-name-uk = Ukrainian
language-name-ur = Urdu
language-name-uz = Uzbek
language-name-ve = Venda
language-name-vi = Vietnamese
language-name-vo = Volapük
language-name-wa = Walloon
language-name-wen = Sorbian
language-name-wo = Wolof
language-name-xh = Xhosa
language-name-yi = Yiddish
language-name-yo = Yoruba
language-name-za = Zhuang
language-name-zam = Miahuatlán Zapotec
language-name-zh = Chinese
language-name-zu = Zulu
PK
!<�׺77/localization/en-US/toolkit/intl/regionNames.ftl

region-name-ad = Andorra
region-name-ae = United Arab Emirates
region-name-af = Afghanistan
region-name-ag = Antigua and Barbuda
region-name-ai = Anguilla
region-name-al = Albania
region-name-am = Armenia
region-name-ao = Angola
region-name-aq = Antarctica
region-name-ar = Argentina
region-name-as = American Samoa
region-name-at = Austria
region-name-au = Australia
region-name-aw = Aruba
region-name-az = Azerbaijan
region-name-ba = Bosnia and Herzegovina
region-name-bb = Barbados
region-name-bd = Bangladesh
region-name-be = Belgium
region-name-bf = Burkina Faso
region-name-bg = Bulgaria
region-name-bh = Bahrain
region-name-bi = Burundi
region-name-bj = Benin
region-name-bl = Saint Barthélemy
region-name-bm = Bermuda
region-name-bn = Brunei
region-name-bo = Bolivia
region-name-bq-2018 = Caribbean Netherlands
region-name-br = Brazil
region-name-bs = Bahamas, The
region-name-bt = Bhutan
region-name-bv = Bouvet Island
region-name-bw = Botswana
region-name-by = Belarus
region-name-bz = Belize
region-name-ca = Canada
region-name-cc = Cocos (Keeling) Islands
region-name-cd = Congo (Kinshasa)
region-name-cf = Central African Republic
region-name-cg = Congo (Brazzaville)
region-name-ch = Switzerland
region-name-ci = Côte d’Ivoire
region-name-ck = Cook Islands
region-name-cl = Chile
region-name-cm = Cameroon
region-name-cn = China
region-name-co = Colombia
region-name-cp = Clipperton Island
region-name-cr = Costa Rica
region-name-cu = Cuba
region-name-cv-2020 = Cape Verde
region-name-cw = Curaçao
region-name-cx = Christmas Island
region-name-cy = Cyprus
region-name-cz-2019 = Czechia
region-name-de = Germany
region-name-dg = Diego Garcia
region-name-dj = Djibouti
region-name-dk = Denmark
region-name-dm = Dominica
region-name-do = Dominican Republic
region-name-dz = Algeria
region-name-ec = Ecuador
region-name-ee = Estonia
region-name-eg = Egypt
region-name-eh = Western Sahara
region-name-er = Eritrea
region-name-es = Spain
region-name-et = Ethiopia
region-name-fi = Finland
region-name-fj = Fiji
region-name-fk = Falkland Islands (Islas Malvinas)
region-name-fm = Micronesia, Federated States of
region-name-fo = Faroe Islands
region-name-fr = France
region-name-ga = Gabon
region-name-gb = United Kingdom
region-name-gd = Grenada
region-name-ge = Georgia
region-name-gf = French Guiana
region-name-gg = Guernsey
region-name-gh = Ghana
region-name-gi = Gibraltar
region-name-gl = Greenland
region-name-gm = Gambia, The
region-name-gn = Guinea
region-name-gp = Guadeloupe
region-name-gq = Equatorial Guinea
region-name-gr = Greece
region-name-gs = South Georgia and South Sandwich Islands
region-name-gt = Guatemala
region-name-gu = Guam
region-name-gw = Guinea-Bissau
region-name-gy = Guyana
region-name-hk = Hong Kong
region-name-hm = Heard Island and McDonald Islands
region-name-hn = Honduras
region-name-hr = Croatia
region-name-ht = Haiti
region-name-hu = Hungary
region-name-id = Indonesia
region-name-ie = Ireland
region-name-il = Israel
region-name-im = Isle of Man
region-name-in = India
region-name-io = British Indian Ocean Territory
region-name-iq = Iraq
region-name-ir = Iran
region-name-is = Iceland
region-name-it = Italy
region-name-je = Jersey
region-name-jm = Jamaica
region-name-jo = Jordan
region-name-jp = Japan
region-name-ke = Kenya
region-name-kg = Kyrgyzstan
region-name-kh = Cambodia
region-name-ki = Kiribati
region-name-km = Comoros
region-name-kn = Saint Kitts and Nevis
region-name-kp = Korea, North
region-name-kr = Korea, South
region-name-kw = Kuwait
region-name-ky = Cayman Islands
region-name-kz = Kazakhstan
region-name-la = Laos
region-name-lb = Lebanon
region-name-lc = Saint Lucia
region-name-li = Liechtenstein
region-name-lk = Sri Lanka
region-name-lr = Liberia
region-name-ls = Lesotho
region-name-lt = Lithuania
region-name-lu = Luxembourg
region-name-lv = Latvia
region-name-ly = Libya
region-name-ma = Morocco
region-name-mc = Monaco
region-name-md = Moldova
region-name-me = Montenegro
region-name-mf = Saint Martin
region-name-mg = Madagascar
region-name-mh = Marshall Islands
region-name-mk-2019 = North Macedonia
region-name-ml = Mali
region-name-mm = Burma
region-name-mn = Mongolia
region-name-mo = Macau
region-name-mp = Northern Mariana Islands
region-name-mq = Martinique
region-name-mr = Mauritania
region-name-ms = Montserrat
region-name-mt = Malta
region-name-mu = Mauritius
region-name-mv = Maldives
region-name-mw = Malawi
region-name-mx = Mexico
region-name-my = Malaysia
region-name-mz = Mozambique
region-name-na = Namibia
region-name-nc = New Caledonia
region-name-ne = Niger
region-name-nf = Norfolk Island
region-name-ng = Nigeria
region-name-ni = Nicaragua
region-name-nl = Netherlands
region-name-no = Norway
region-name-np = Nepal
region-name-nr = Nauru
region-name-nu = Niue
region-name-nz = New Zealand
region-name-om = Oman
region-name-pa = Panama
region-name-pe = Peru
region-name-pf = French Polynesia
region-name-pg = Papua New Guinea
region-name-ph = Philippines
region-name-pk = Pakistan
region-name-pl = Poland
region-name-pm = Saint Pierre and Miquelon
region-name-pn = Pitcairn Islands
region-name-pr = Puerto Rico
region-name-pt = Portugal
region-name-pw = Palau
region-name-py = Paraguay
region-name-qa = Qatar
region-name-qm = Midway Islands
region-name-qs = Bassas da India
region-name-qu = Juan de Nova Island
region-name-qw = Wake Island
region-name-qx = Glorioso Islands
region-name-qz = Akrotiri
region-name-re = Réunion
region-name-ro = Romania
region-name-rs = Serbia
region-name-ru = Russia
region-name-rw = Rwanda
region-name-sa = Saudi Arabia
region-name-sb = Solomon Islands
region-name-sc = Seychelles
region-name-sd = Sudan
region-name-se = Sweden
region-name-sg = Singapore
region-name-sh = Saint Helena, Ascension, and Tristan da Cunha
region-name-si = Slovenia
region-name-sk = Slovakia
region-name-sl = Sierra Leone
region-name-sm = San Marino
region-name-sn = Senegal
region-name-so = Somalia
region-name-sr = Suriname
region-name-ss = South Sudan
region-name-st = São Tomé and Príncipe
region-name-sv = El Salvador
region-name-sx = Sint Maarten
region-name-sy = Syria
region-name-sz-2019 = Eswatini
region-name-tc = Turks and Caicos Islands
region-name-td = Chad
region-name-tf = French Southern and Antarctic Lands
region-name-tg = Togo
region-name-th = Thailand
region-name-tj = Tajikistan
region-name-tk = Tokelau
region-name-tl = Timor-Leste
region-name-tm = Turkmenistan
region-name-tn = Tunisia
region-name-to = Tonga
region-name-tr = Turkey
region-name-tt = Trinidad and Tobago
region-name-tv = Tuvalu
region-name-tw = Taiwan
region-name-tz = Tanzania
region-name-ua = Ukraine
region-name-ug = Uganda
region-name-us = United States
region-name-uy = Uruguay
region-name-uz = Uzbekistan
region-name-va = Vatican City
region-name-vc = Saint Vincent and the Grenadines
region-name-ve = Venezuela
region-name-vg = Virgin Islands, British
region-name-vi = Virgin Islands, U.S.
region-name-vn = Vietnam
region-name-vu = Vanuatu
region-name-wf = Wallis and Futuna
region-name-ws = Samoa
region-name-xa = Ashmore and Cartier Islands
region-name-xb = Baker Island
region-name-xc = Coral Sea Islands
region-name-xd = Dhekelia
region-name-xe = Europa Island
region-name-xg = Gaza Strip
region-name-xh = Howland Island
region-name-xj = Jan Mayen
region-name-xk = Kosovo
region-name-xl = Palmyra Atoll
region-name-xm = Kingman Reef
region-name-xp = Paracel Islands
region-name-xq = Jarvis Island
region-name-xr = Svalbard
region-name-xs = Spratly Islands
region-name-xt = Tromelin Island
region-name-xu = Johnston Atoll
region-name-xv = Navassa Island
region-name-xw = West Bank
region-name-ye = Yemen
region-name-yt = Mayotte
region-name-za = South Africa
region-name-zm = Zambia
region-name-zw = Zimbabwe
PK
!<bk�eOO7localization/en-US/toolkit/main-window/autocomplete.ftl


autocomplete-import-logins-chrome =
    <div data-l10n-name="line1">Import your login from Google Chrome</div>
    <div data-l10n-name="line2">for { $host } and other sites</div>
autocomplete-import-logins-chromium =
    <div data-l10n-name="line1">Import your login from Chromium</div>
    <div data-l10n-name="line2">for { $host } and other sites</div>
autocomplete-import-logins-chromium-edge =
    <div data-l10n-name="line1">Import your login from Microsoft Edge</div>
    <div data-l10n-name="line2">for { $host } and other sites</div>


autocomplete-import-learn-more = Learn more
PK
!<�Po��2localization/en-US/toolkit/main-window/findbar.ftl

findbar-next =
    .tooltiptext = Find the next occurrence of the phrase
findbar-previous =
    .tooltiptext = Find the previous occurrence of the phrase

findbar-find-button-close =
    .tooltiptext = Close find bar

findbar-highlight-all2 =
    .label = Highlight All
    .accesskey = { PLATFORM() ->
        [macos] l
       *[other] a
    }
    .tooltiptext = Highlight all occurrences of the phrase

findbar-case-sensitive =
    .label = Match Case
    .accesskey = C
    .tooltiptext = Search with case sensitivity

findbar-match-diacritics =
    .label = Match Diacritics
    .accesskey = i
    .tooltiptext = Distinguish between accented letters and their base letters (for example, when searching for “resume”, “résumé” will not be matched)

findbar-entire-word =
    .label = Whole Words
    .accesskey = W
    .tooltiptext = Search whole words only

findbar-not-found = Phrase not found

findbar-wrapped-to-top = Reached end of page, continued from top
findbar-wrapped-to-bottom = Reached top of page, continued from bottom

findbar-normal-find =
    .placeholder = Find in page
findbar-fast-find =
    .placeholder = Quick find
findbar-fast-find-links =
    .placeholder = Quick find (links only)

findbar-case-sensitive-status =
    .value = (Case sensitive)
findbar-match-diacritics-status =
    .value = (Matching diacritics)
findbar-entire-word-status =
    .value = (Whole words only)

findbar-found-matches =
    .value =
        { $total ->
            [one] { $current } of { $total } match
           *[other] { $current } of { $total } matches
        }

findbar-found-matches-count-limit =
    .value =
        { $limit ->
            [one] More than { $limit } match
           *[other] More than { $limit } matches
        }
PK
!<�/nn1localization/en-US/toolkit/neterror/certError.ftl
cert-error-intro = { $hostname } uses an invalid security certificate.

cert-error-mitm-intro = Websites prove their identity via certificates, which are issued by certificate authorities.

cert-error-mitm-mozilla = { -brand-short-name } is backed by the non-profit Mozilla, which administers a completely open certificate authority (CA) store. The CA store helps ensure that certificate authorities are following best practices for user security.

cert-error-mitm-connection = { -brand-short-name } uses the Mozilla CA store to verify that a connection is secure, rather than certificates supplied by the user’s operating system. So, if an antivirus program or a network is intercepting a connection with a security certificate issued by a CA that is not in the Mozilla CA store, the connection is considered unsafe.

cert-error-trust-unknown-issuer-intro = Someone could be trying to impersonate the site and you should not continue.

cert-error-trust-unknown-issuer = Websites prove their identity via certificates. { -brand-short-name } does not trust { $hostname } because its certificate issuer is unknown, the certificate is self-signed, or the server is not sending the correct intermediate certificates.

cert-error-trust-cert-invalid = The certificate is not trusted because it was issued by an invalid CA certificate.

cert-error-trust-untrusted-issuer = The certificate is not trusted because the issuer certificate is not trusted.

cert-error-trust-signature-algorithm-disabled = The certificate is not trusted because it was signed using a signature algorithm that was disabled because that algorithm is not secure.

cert-error-trust-expired-issuer = The certificate is not trusted because the issuer certificate has expired.

cert-error-trust-self-signed = The certificate is not trusted because it is self-signed.

cert-error-trust-symantec = Certificates issued by GeoTrust, RapidSSL, Symantec, Thawte, and VeriSign are no longer considered safe because these certificate authorities failed to follow security practices in the past.

cert-error-untrusted-default = The certificate does not come from a trusted source.

cert-error-domain-mismatch = Websites prove their identity via certificates. { -brand-short-name } does not trust this site because it uses a certificate that is not valid for { $hostname }.

cert-error-domain-mismatch-single = Websites prove their identity via certificates. { -brand-short-name } does not trust this site because it uses a certificate that is not valid for { $hostname }. The certificate is only valid for <a data-l10n-name="domain-mismatch-link">{ $alt-name }</a>.

cert-error-domain-mismatch-single-nolink = Websites prove their identity via certificates. { -brand-short-name } does not trust this site because it uses a certificate that is not valid for { $hostname }. The certificate is only valid for { $alt-name }.

cert-error-domain-mismatch-multiple = Websites prove their identity via certificates. { -brand-short-name } does not trust this site because it uses a certificate that is not valid for { $hostname }. The certificate is only valid for the following names: { $subject-alt-names }

cert-error-expired-now = Websites prove their identity via certificates, which are valid for a set time period. The certificate for { $hostname } expired on { $not-after-local-time }.

cert-error-not-yet-valid-now = Websites prove their identity via certificates, which are valid for a set time period. The certificate for { $hostname } will not be valid until { $not-before-local-time }.

cert-error-code-prefix = Error code: { $error }

cert-error-code-prefix-link = Error code: <a data-l10n-name="error-code-link">{ $error }</a>

cert-error-ssl-connection-error = An error occurred during a connection to { $hostname }. { $errorMessage }

cert-error-symantec-distrust-description = Websites prove their identity via certificates, which are issued by certificate authorities. Most browsers no longer trust certificates issued by GeoTrust, RapidSSL, Symantec, Thawte, and VeriSign. { $hostname } uses a certificate from one of these authorities and so the website’s identity cannot be proven.

cert-error-symantec-distrust-admin = You may notify the website’s administrator about this problem.

cert-error-old-tls-version = This website might not support the TLS 1.2 protocol, which is the minimum version supported by { -brand-short-name }.

cert-error-details-hsts-label = HTTP Strict Transport Security: { $hasHSTS }

cert-error-details-key-pinning-label = HTTP Public Key Pinning: { $hasHPKP }

cert-error-details-cert-chain-label = Certificate chain:

open-in-new-window-for-csp-or-xfo-error = Open Site in New Window

csp-xfo-blocked-long-desc = To protect your security, { $hostname } will not allow { -brand-short-name } to display the page if another site has embedded it. To see this page, you need to open it in a new window.


connectionFailure-title = Unable to connect
deniedPortAccess-title = This address is restricted
dnsNotFound-title = Hmm. We’re having trouble finding that site.

dns-not-found-trr-only-title2 =
  Possible security risk looking up this domain
dns-not-found-native-fallback-title2 =
  Possible security risk looking up this domain

fileNotFound-title = File not found
fileAccessDenied-title = Access to the file was denied
generic-title = Oops.
captivePortal-title = Log in to network
malformedURI-title = Hmm. That address doesn’t look right.
netInterrupt-title = The connection was interrupted
notCached-title = Document Expired
netOffline-title = Offline mode
contentEncodingError-title = Content Encoding Error
unsafeContentType-title = Unsafe File Type
netReset-title = The connection was reset
netTimeout-title = The connection has timed out
serverError-title = Looks like there’s a problem with this site
unknownProtocolFound-title = The address wasn’t understood
proxyConnectFailure-title = The proxy server is refusing connections
proxyResolveFailure-title = Unable to find the proxy server
redirectLoop-title = The page isn’t redirecting properly
unknownSocketType-title = Unexpected response from server
nssFailure2-title = Secure Connection Failed
csp-xfo-error-title = { -brand-short-name } Can’t Open This Page
corruptedContentError-title = Corrupted Content Error
sslv3Used-title = Unable to Connect Securely
inadequateSecurityError-title = Your connection is not secure
blockedByPolicy-title = Blocked Page
clockSkewError-title = Your Computer Clock is Wrong
networkProtocolError-title = Network Protocol Error
nssBadCert-title = Warning: Potential Security Risk Ahead
nssBadCert-sts-title = Did Not Connect: Potential Security Issue
certerror-mitm-title = Software is Preventing { -brand-short-name } From Safely Connecting to This Site
PK
!<��<�*�*0localization/en-US/toolkit/neterror/netError.ftl

neterror-page-title = Problem loading page
certerror-page-title = Warning: Potential Security Risk Ahead
certerror-sts-page-title = Did Not Connect: Potential Security Issue
neterror-blocked-by-policy-page-title = Blocked Page
neterror-captive-portal-page-title = Log in to network
neterror-dns-not-found-title = Server Not Found
neterror-malformed-uri-page-title = Invalid URL


neterror-advanced-button = Advanced…
neterror-copy-to-clipboard-button = Copy text to clipboard
neterror-learn-more-link = Learn more…
neterror-open-portal-login-page-button = Open Network Login Page
neterror-override-exception-button = Accept the Risk and Continue
neterror-pref-reset-button = Restore default settings
neterror-return-to-previous-page-button = Go Back
neterror-return-to-previous-page-recommended-button = Go Back (Recommended)
neterror-try-again-button = Try Again
neterror-add-exception-button = Always continue for this site
neterror-settings-button = Change DNS settings
neterror-view-certificate-link = View Certificate
neterror-trr-continue-this-time = Continue this time
neterror-disable-native-feedback-warning = Always continue


neterror-pref-reset = It looks like your network security settings might be causing this. Do you want the default settings to be restored?
neterror-error-reporting-automatic = Report errors like this to help { -vendor-short-name } identify and block malicious sites


neterror-generic-error = { -brand-short-name } can’t load this page for some reason.

neterror-load-error-try-again = The site could be temporarily unavailable or too busy. Try again in a few moments.
neterror-load-error-connection = If you are unable to load any pages, check your computer’s network connection.
neterror-load-error-firewall = If your computer or network is protected by a firewall or proxy, make sure that { -brand-short-name } is permitted to access the web.

neterror-captive-portal = You must log in to this network before you can access the internet.

neterror-dns-not-found-with-suggestion = Did you mean to go to <a data-l10n-name="website">{ $hostAndPath }</a>?
neterror-dns-not-found-hint-header = <strong>If you entered the right address, you can:</strong>
neterror-dns-not-found-hint-try-again = Try again later
neterror-dns-not-found-hint-check-network = Check your network connection
neterror-dns-not-found-hint-firewall = Check that { -brand-short-name } has permission to access the web (you might be connected but behind a firewall)


neterror-dns-not-found-trr-only-reason2 = { -brand-short-name } can’t protect your request for this site’s address through our secure DNS provider. Here’s why:
neterror-dns-not-found-trr-third-party-warning2 = You can continue with your default DNS resolver. However, a third-party might be able to see what websites you visit.

neterror-dns-not-found-trr-only-could-not-connect = { -brand-short-name } wasn’t able to connect to { $trrDomain }.
neterror-dns-not-found-trr-only-timeout = The connection to { $trrDomain } took longer than expected.
neterror-dns-not-found-trr-offline = You are not connected to the internet.
neterror-dns-not-found-trr-unknown-host2 = This website wasn’t found by { $trrDomain }.
neterror-dns-not-found-trr-server-problem = There was a problem with { $trrDomain }.
neterror-dns-not-found-bad-trr-url = Invalid URL.
neterror-dns-not-found-system-sleep = System is in sleep mode.
neterror-dns-not-found-trr-unknown-problem = Unexpected problem.


neterror-dns-not-found-native-fallback-reason2 = { -brand-short-name } can’t protect your request for this site’s address through our secure DNS provider. Here’s why:
neterror-dns-not-found-native-fallback-heuristic = DNS over HTTPS has been disabled on your network.
neterror-dns-not-found-native-fallback-not-confirmed2 = { -brand-short-name } wasn’t able to connect to { $trrDomain }.


neterror-file-not-found-filename = Check the file name for capitalization or other typing errors.
neterror-file-not-found-moved = Check to see if the file was moved, renamed or deleted.

neterror-access-denied = It may have been removed, moved, or file permissions may be preventing access.

neterror-unknown-protocol = You might need to install other software to open this address.

neterror-redirect-loop = This problem can sometimes be caused by disabling or refusing to accept cookies.

neterror-unknown-socket-type-psm-installed = Check to make sure your system has the Personal Security Manager installed.
neterror-unknown-socket-type-server-config = This might be due to a non-standard configuration on the server.

neterror-not-cached-intro = The requested document is not available in { -brand-short-name }’s cache.
neterror-not-cached-sensitive = As a security precaution, { -brand-short-name } does not automatically re-request sensitive documents.
neterror-not-cached-try-again = Click Try Again to re-request the document from the website.

neterror-net-offline = Press “Try Again” to switch to online mode and reload the page.

neterror-proxy-resolve-failure-settings = Check the proxy settings to make sure that they are correct.
neterror-proxy-resolve-failure-connection = Check to make sure your computer has a working network connection.
neterror-proxy-resolve-failure-firewall = If your computer or network is protected by a firewall or proxy, make sure that { -brand-short-name } is permitted to access the web.

neterror-proxy-connect-failure-settings = Check the proxy settings to make sure that they are correct.
neterror-proxy-connect-failure-contact-admin = Contact your network administrator to make sure the proxy server is working.

neterror-content-encoding-error = Please contact the website owners to inform them of this problem.

neterror-unsafe-content-type = Please contact the website owners to inform them of this problem.

neterror-nss-failure-not-verified = The page you are trying to view cannot be shown because the authenticity of the received data could not be verified.
neterror-nss-failure-contact-website = Please contact the website owners to inform them of this problem.

certerror-intro = { -brand-short-name } detected a potential security threat and did not continue to <b>{ $hostname }</b>. If you visit this site, attackers could try to steal information like your passwords, emails, or credit card details.
certerror-sts-intro = { -brand-short-name } detected a potential security threat and did not continue to <b>{ $hostname }</b> because this website requires a secure connection.
certerror-expired-cert-intro = { -brand-short-name } detected an issue and did not continue to <b>{ $hostname }</b>. The website is either misconfigured or your computer clock is set to the wrong time.
certerror-mitm = <b>{ $hostname }</b> is most likely a safe site, but a secure connection could not be established. This issue is caused by <b>{ $mitm }</b>, which is either software on your computer or your network.

neterror-corrupted-content-intro = The page you are trying to view cannot be shown because an error in the data transmission was detected.
neterror-corrupted-content-contact-website = Please contact the website owners to inform them of this problem.

neterror-sslv3-used = Advanced info: SSL_ERROR_UNSUPPORTED_VERSION

neterror-inadequate-security-intro = <b>{ $hostname }</b> uses security technology that is outdated and vulnerable to attack. An attacker could easily reveal information which you thought to be safe. The website administrator will need to fix the server first before you can visit the site.
neterror-inadequate-security-code = Error code: NS_ERROR_NET_INADEQUATE_SECURITY

neterror-clock-skew-error = Your computer thinks it is { DATETIME($now, dateStyle: "medium") }, which prevents { -brand-short-name } from connecting securely. To visit <b>{ $hostname }</b>, update your computer clock in your system settings to the current date, time, and time zone, and then refresh <b>{ $hostname }</b>.

neterror-network-protocol-error-intro = The page you are trying to view cannot be shown because an error in the network protocol was detected.
neterror-network-protocol-error-contact-website = Please contact the website owners to inform them of this problem.

certerror-expired-cert-second-para = It’s likely the website’s certificate is expired, which prevents { -brand-short-name } from connecting securely. If you visit this site, attackers could try to steal information like your passwords, emails, or credit card details.
certerror-expired-cert-sts-second-para = It’s likely the website’s certificate is expired, which prevents { -brand-short-name } from connecting securely.

certerror-what-can-you-do-about-it-title = What can you do about it?

certerror-unknown-issuer-what-can-you-do-about-it-website = The issue is most likely with the website, and there is nothing you can do to resolve it.
certerror-unknown-issuer-what-can-you-do-about-it-contact-admin = If you are on a corporate network or using antivirus software, you can reach out to the support teams for assistance. You can also notify the website’s administrator about the problem.

certerror-expired-cert-what-can-you-do-about-it-clock = Your computer clock is set to { DATETIME($now, dateStyle: "medium") }. Make sure your computer is set to the correct date, time, and time zone in your system settings, and then refresh <b>{ $hostname }</b>.
certerror-expired-cert-what-can-you-do-about-it-contact-website = If your clock is already set to the right time, the website is likely misconfigured, and there is nothing you can do to resolve the issue. You can notify the website’s administrator about the problem.

certerror-bad-cert-domain-what-can-you-do-about-it = The issue is most likely with the website, and there is nothing you can do to resolve it. You can notify the website’s administrator about the problem.

certerror-mitm-what-can-you-do-about-it-antivirus = If your antivirus software includes a feature that scans encrypted connections (often called “web scanning” or “https scanning”), you can disable that feature. If that doesn’t work, you can remove and reinstall the antivirus software.
certerror-mitm-what-can-you-do-about-it-corporate = If you are on a corporate network, you can contact your IT department.
certerror-mitm-what-can-you-do-about-it-attack = If you are not familiar with <b>{ $mitm }</b>, then this could be an attack and you should not continue to the site.

certerror-mitm-what-can-you-do-about-it-attack-sts = If you are not familiar with <b>{ $mitm }</b>, then this could be an attack, and there is nothing you can do to access the site.

certerror-what-should-i-do-bad-sts-cert-explanation = <b>{ $hostname }</b> has a security policy called HTTP Strict Transport Security (HSTS), which means that { -brand-short-name } can only connect to it securely. You can’t add an exception to visit this site.
PK
!<	&�vv1localization/en-US/toolkit/neterror/nsserrors.ftl

psmerr-ssl-disabled = Can’t connect securely because the SSL protocol has been disabled.
psmerr-ssl2-disabled = Can’t connect securely because the site uses an older, insecure version of the SSL protocol.
psmerr-hostreusedissuerandserial =
    You have received an invalid certificate. Please contact the server administrator or email correspondent and give them the following information:

    Your certificate contains the same serial number as another certificate issued by the certificate authority. Please get a new certificate containing a unique serial number.

ssl-error-export-only-server = Unable to communicate securely. Peer does not support high-grade encryption.
ssl-error-us-only-server = Unable to communicate securely. Peer requires high-grade encryption which is not supported.
ssl-error-no-cypher-overlap = Cannot communicate securely with peer: no common encryption algorithm(s).
ssl-error-no-certificate = Unable to find the certificate or key necessary for authentication.
ssl-error-bad-certificate = Unable to communicate securely with peer: peers’s certificate was rejected.
ssl-error-bad-client = The server has encountered bad data from the client.
ssl-error-bad-server = The client has encountered bad data from the server.
ssl-error-unsupported-certificate-type = Unsupported certificate type.
ssl-error-unsupported-version = Peer using unsupported version of security protocol.
ssl-error-wrong-certificate = Client authentication failed: private key in key database does not match public key in certificate database.
ssl-error-bad-cert-domain = Unable to communicate securely with peer: requested domain name does not match the server’s certificate.
ssl-error-post-warning = Unrecognized SSL error code.
ssl-error-ssl2-disabled = Peer only supports SSL version 2, which is locally disabled.
ssl-error-bad-mac-read = SSL received a record with an incorrect Message Authentication Code.
ssl-error-bad-mac-alert = SSL peer reports incorrect Message Authentication Code.
ssl-error-bad-cert-alert = SSL peer cannot verify your certificate.
ssl-error-revoked-cert-alert = SSL peer rejected your certificate as revoked.
ssl-error-expired-cert-alert = SSL peer rejected your certificate as expired.
ssl-error-ssl-disabled = Cannot connect: SSL is disabled.
ssl-error-fortezza-pqg = Cannot connect: SSL peer is in another FORTEZZA domain.
ssl-error-unknown-cipher-suite = An unknown SSL cipher suite has been requested.
ssl-error-no-ciphers-supported = No cipher suites are present and enabled in this program.
ssl-error-bad-block-padding = SSL received a record with bad block padding.
ssl-error-rx-record-too-long = SSL received a record that exceeded the maximum permissible length.
ssl-error-tx-record-too-long = SSL attempted to send a record that exceeded the maximum permissible length.
ssl-error-rx-malformed-hello-request = SSL received a malformed Hello Request handshake message.
ssl-error-rx-malformed-client-hello = SSL received a malformed Client Hello handshake message.
ssl-error-rx-malformed-server-hello = SSL received a malformed Server Hello handshake message.
ssl-error-rx-malformed-certificate = SSL received a malformed Certificate handshake message.
ssl-error-rx-malformed-server-key-exch = SSL received a malformed Server Key Exchange handshake message.
ssl-error-rx-malformed-cert-request = SSL received a malformed Certificate Request handshake message.
ssl-error-rx-malformed-hello-done = SSL received a malformed Server Hello Done handshake message.
ssl-error-rx-malformed-cert-verify = SSL received a malformed Certificate Verify handshake message.
ssl-error-rx-malformed-client-key-exch = SSL received a malformed Client Key Exchange handshake message.
ssl-error-rx-malformed-finished = SSL received a malformed Finished handshake message.
ssl-error-rx-malformed-change-cipher = SSL received a malformed Change Cipher Spec record.
ssl-error-rx-malformed-alert = SSL received a malformed Alert record.
ssl-error-rx-malformed-handshake = SSL received a malformed Handshake record.
ssl-error-rx-malformed-application-data = SSL received a malformed Application Data record.
ssl-error-rx-unexpected-hello-request = SSL received an unexpected Hello Request handshake message.
ssl-error-rx-unexpected-client-hello = SSL received an unexpected Client Hello handshake message.
ssl-error-rx-unexpected-server-hello = SSL received an unexpected Server Hello handshake message.
ssl-error-rx-unexpected-certificate = SSL received an unexpected Certificate handshake message.
ssl-error-rx-unexpected-server-key-exch = SSL received an unexpected Server Key Exchange handshake message.
ssl-error-rx-unexpected-cert-request = SSL received an unexpected Certificate Request handshake message.
ssl-error-rx-unexpected-hello-done = SSL received an unexpected Server Hello Done handshake message.
ssl-error-rx-unexpected-cert-verify = SSL received an unexpected Certificate Verify handshake message.
ssl-error-rx-unexpected-client-key-exch = SSL received an unexpected Client Key Exchange handshake message.
ssl-error-rx-unexpected-finished = SSL received an unexpected Finished handshake message.
ssl-error-rx-unexpected-change-cipher = SSL received an unexpected Change Cipher Spec record.
ssl-error-rx-unexpected-alert = SSL received an unexpected Alert record.
ssl-error-rx-unexpected-handshake = SSL received an unexpected Handshake record.
ssl-error-rx-unexpected-application-data = SSL received an unexpected Application Data record.
ssl-error-rx-unknown-record-type = SSL received a record with an unknown content type.
ssl-error-rx-unknown-handshake = SSL received a handshake message with an unknown message type.
ssl-error-rx-unknown-alert = SSL received an alert record with an unknown alert description.
ssl-error-close-notify-alert = SSL peer has closed this connection.
ssl-error-handshake-unexpected-alert = SSL peer was not expecting a handshake message it received.
ssl-error-decompression-failure-alert = SSL peer was unable to successfully decompress an SSL record it received.
ssl-error-handshake-failure-alert = SSL peer was unable to negotiate an acceptable set of security parameters.
ssl-error-illegal-parameter-alert = SSL peer rejected a handshake message for unacceptable content.
ssl-error-unsupported-cert-alert = SSL peer does not support certificates of the type it received.
ssl-error-certificate-unknown-alert = SSL peer had some unspecified issue with the certificate it received.
ssl-error-generate-random-failure = SSL experienced a failure of its random number generator.
ssl-error-sign-hashes-failure = Unable to digitally sign data required to verify your certificate.
ssl-error-extract-public-key-failure = SSL was unable to extract the public key from the peer’s certificate.
ssl-error-server-key-exchange-failure = Unspecified failure while processing SSL Server Key Exchange handshake.
ssl-error-client-key-exchange-failure = Unspecified failure while processing SSL Client Key Exchange handshake.
ssl-error-encryption-failure = Bulk data encryption algorithm failed in selected cipher suite.
ssl-error-decryption-failure = Bulk data decryption algorithm failed in selected cipher suite.
ssl-error-socket-write-failure = Attempt to write encrypted data to underlying socket failed.
ssl-error-md5-digest-failure = MD5 digest function failed.
ssl-error-sha-digest-failure = SHA-1 digest function failed.
ssl-error-mac-computation-failure = MAC computation failed.
ssl-error-sym-key-context-failure = Failure to create Symmetric Key context.
ssl-error-sym-key-unwrap-failure = Failure to unwrap the Symmetric key in Client Key Exchange message.
ssl-error-pub-key-size-limit-exceeded = SSL Server attempted to use domestic-grade public key with export cipher suite.
ssl-error-iv-param-failure = PKCS11 code failed to translate an IV into a param.
ssl-error-init-cipher-suite-failure = Failed to initialize the selected cipher suite.
ssl-error-session-key-gen-failure = Client failed to generate session keys for SSL session.
ssl-error-no-server-key-for-alg = Server has no key for the attempted key exchange algorithm.
ssl-error-token-insertion-removal = PKCS#11 token was inserted or removed while operation was in progress.
ssl-error-token-slot-not-found = No PKCS#11 token could be found to do a required operation.
ssl-error-no-compression-overlap = Cannot communicate securely with peer: no common compression algorithm(s).
ssl-error-handshake-not-completed = Cannot initiate another SSL handshake until current handshake is complete.
ssl-error-bad-handshake-hash-value = Received incorrect handshakes hash values from peer.
ssl-error-cert-kea-mismatch = The certificate provided cannot be used with the selected key exchange algorithm.
ssl-error-no-trusted-ssl-client-ca = No certificate authority is trusted for SSL client authentication.
ssl-error-session-not-found = Client’s SSL session ID not found in server’s session cache.
ssl-error-decryption-failed-alert = Peer was unable to decrypt an SSL record it received.
ssl-error-record-overflow-alert = Peer received an SSL record that was longer than is permitted.
ssl-error-unknown-ca-alert = Peer does not recognize and trust the CA that issued your certificate.
ssl-error-access-denied-alert = Peer received a valid certificate, but access was denied.
ssl-error-decode-error-alert = Peer could not decode an SSL handshake message.
ssl-error-decrypt-error-alert = Peer reports failure of signature verification or key exchange.
ssl-error-export-restriction-alert = Peer reports negotiation not in compliance with export regulations.
ssl-error-protocol-version-alert = Peer reports incompatible or unsupported protocol version.
ssl-error-insufficient-security-alert = Server requires ciphers more secure than those supported by client.
ssl-error-internal-error-alert = Peer reports it experienced an internal error.
ssl-error-user-canceled-alert = Peer user canceled handshake.
ssl-error-no-renegotiation-alert = Peer does not permit renegotiation of SSL security parameters.
ssl-error-server-cache-not-configured = SSL server cache not configured and not disabled for this socket.
ssl-error-unsupported-extension-alert = SSL peer does not support requested TLS hello extension.
ssl-error-certificate-unobtainable-alert = SSL peer could not obtain your certificate from the supplied URL.
ssl-error-unrecognized-name-alert = SSL peer has no certificate for the requested DNS name.
ssl-error-bad-cert-status-response-alert = SSL peer was unable to get an OCSP response for its certificate.
ssl-error-bad-cert-hash-value-alert = SSL peer reported bad certificate hash value.
ssl-error-rx-unexpected-new-session-ticket = SSL received an unexpected New Session Ticket handshake message.
ssl-error-rx-malformed-new-session-ticket = SSL received a malformed New Session Ticket handshake message.
ssl-error-decompression-failure = SSL received a compressed record that could not be decompressed.
ssl-error-renegotiation-not-allowed = Renegotiation is not allowed on this SSL socket.
ssl-error-unsafe-negotiation = Peer attempted old style (potentially vulnerable) handshake.
ssl-error-rx-unexpected-uncompressed-record = SSL received an unexpected uncompressed record.
ssl-error-weak-server-ephemeral-dh-key = SSL received a weak ephemeral Diffie-Hellman key in Server Key Exchange handshake message.
ssl-error-next-protocol-data-invalid = SSL received invalid NPN extension data.
ssl-error-feature-not-supported-for-ssl2 = SSL feature not supported for SSL 2.0 connections.
ssl-error-feature-not-supported-for-servers = SSL feature not supported for servers.
ssl-error-feature-not-supported-for-clients = SSL feature not supported for clients.
ssl-error-invalid-version-range = SSL version range is not valid.
ssl-error-cipher-disallowed-for-version = SSL peer selected a cipher suite disallowed for the selected protocol version.
ssl-error-rx-malformed-hello-verify-request = SSL received a malformed Hello Verify Request handshake message.
ssl-error-rx-unexpected-hello-verify-request = SSL received an unexpected Hello Verify Request handshake message.
ssl-error-feature-not-supported-for-version = SSL feature not supported for the protocol version.
ssl-error-rx-unexpected-cert-status = SSL received an unexpected Certificate Status handshake message.
ssl-error-unsupported-hash-algorithm = Unsupported hash algorithm used by TLS peer.
ssl-error-digest-failure = Digest function failed.
ssl-error-incorrect-signature-algorithm = Incorrect signature algorithm specified in a digitally-signed element.
ssl-error-next-protocol-no-callback = The next protocol negotiation extension was enabled, but the callback was cleared prior to being needed.
ssl-error-next-protocol-no-protocol = The server supports no protocols that the client advertises in the ALPN extension.
ssl-error-inappropriate-fallback-alert = The server rejected the handshake because the client downgraded to a lower TLS version than the server supports.
ssl-error-weak-server-cert-key = The server certificate included a public key that was too weak.
ssl-error-rx-short-dtls-read = Not enough room in buffer for DTLS record.
ssl-error-no-supported-signature-algorithm = No supported TLS signature algorithm was configured.
ssl-error-unsupported-signature-algorithm = The peer used an unsupported combination of signature and hash algorithm.
ssl-error-missing-extended-master-secret = The peer tried to resume without a correct extended_master_secret extension.
ssl-error-unexpected-extended-master-secret = The peer tried to resume with an unexpected extended_master_secret extension.

sec-error-io = An I/O error occurred during security authorization.
sec-error-library-failure = security library failure.
sec-error-bad-data = security library: received bad data.
sec-error-output-len = security library: output length error.
sec-error-input-len = security library has experienced an input length error.
sec-error-invalid-args = security library: invalid arguments.
sec-error-invalid-algorithm = security library: invalid algorithm.
sec-error-invalid-ava = security library: invalid AVA.
sec-error-invalid-time = Improperly formatted time string.
sec-error-bad-der = security library: improperly formatted DER-encoded message.
sec-error-bad-signature = Peer’s certificate has an invalid signature.
sec-error-expired-certificate = Peer’s Certificate has expired.
sec-error-revoked-certificate = Peer’s Certificate has been revoked.
sec-error-unknown-issuer = Peer’s Certificate issuer is not recognized.
sec-error-bad-key = Peer’s public key is invalid.
sec-error-bad-password = The security password entered is incorrect.
sec-error-retry-password = New password entered incorrectly. Please try again.
sec-error-no-nodelock = security library: no nodelock.
sec-error-bad-database = security library: bad database.
sec-error-no-memory = security library: memory allocation failure.
sec-error-untrusted-issuer = Peer’s certificate issuer has been marked as not trusted by the user.
sec-error-untrusted-cert = Peer’s certificate has been marked as not trusted by the user.
sec-error-duplicate-cert = Certificate already exists in your database.
sec-error-duplicate-cert-name = Downloaded certificate’s name duplicates one already in your database.
sec-error-adding-cert = Error adding certificate to database.
sec-error-filing-key = Error refiling the key for this certificate.
sec-error-no-key = The private key for this certificate cannot be found in key database
sec-error-cert-valid = This certificate is valid.
sec-error-cert-not-valid = This certificate is not valid.
sec-error-cert-no-response = Cert Library: No Response
sec-error-expired-issuer-certificate = The certificate issuer’s certificate has expired. Check your system date and time.
sec-error-crl-expired = The CRL for the certificate’s issuer has expired. Update it or check your system date and time.
sec-error-crl-bad-signature = The CRL for the certificate’s issuer has an invalid signature.
sec-error-crl-invalid = New CRL has an invalid format.
sec-error-extension-value-invalid = Certificate extension value is invalid.
sec-error-extension-not-found = Certificate extension not found.
sec-error-ca-cert-invalid = Issuer certificate is invalid.
sec-error-path-len-constraint-invalid = Certificate path length constraint is invalid.
sec-error-cert-usages-invalid = Certificate usages field is invalid.
sec-internal-only = **Internal ONLY module**
sec-error-invalid-key = The key does not support the requested operation.
sec-error-unknown-critical-extension = Certificate contains unknown critical extension.
sec-error-old-crl = New CRL is not later than the current one.
sec-error-no-email-cert = Not encrypted or signed: you do not yet have an email certificate.
sec-error-no-recipient-certs-query = Not encrypted: you do not have certificates for each of the recipients.
sec-error-not-a-recipient = Cannot decrypt: you are not a recipient, or matching certificate and private key not found.
sec-error-pkcs7-keyalg-mismatch = Cannot decrypt: key encryption algorithm does not match your certificate.
sec-error-pkcs7-bad-signature = Signature verification failed: no signer found, too many signers found, or improper or corrupted data.
sec-error-unsupported-keyalg = Unsupported or unknown key algorithm.
sec-error-decryption-disallowed = Cannot decrypt: encrypted using a disallowed algorithm or key size.
sec-error-no-krl = No KRL for this site’s certificate has been found.
sec-error-krl-expired = The KRL for this site’s certificate has expired.
sec-error-krl-bad-signature = The KRL for this site’s certificate has an invalid signature.
sec-error-revoked-key = The key for this site’s certificate has been revoked.
sec-error-krl-invalid = New KRL has an invalid format.
sec-error-need-random = security library: need random data.
sec-error-no-module = security library: no security module can perform the requested operation.
sec-error-no-token = The security card or token does not exist, needs to be initialized, or has been removed.
sec-error-read-only = security library: read-only database.
sec-error-no-slot-selected = No slot or token was selected.
sec-error-cert-nickname-collision = A certificate with the same nickname already exists.
sec-error-key-nickname-collision = A key with the same nickname already exists.
sec-error-safe-not-created = error while creating safe object
sec-error-baggage-not-created = error while creating baggage object
sec-error-bad-export-algorithm = Required algorithm is not allowed.
sec-error-exporting-certificates = Error attempting to export certificates.
sec-error-importing-certificates = Error attempting to import certificates.
sec-error-pkcs12-decoding-pfx = Unable to import. Decoding error. File not valid.
sec-error-pkcs12-invalid-mac = Unable to import. Invalid MAC. Incorrect password or corrupt file.
sec-error-pkcs12-unsupported-mac-algorithm = Unable to import. MAC algorithm not supported.
sec-error-pkcs12-unsupported-transport-mode = Unable to import. Only password integrity and privacy modes supported.
sec-error-pkcs12-corrupt-pfx-structure = Unable to import. File structure is corrupt.
sec-error-pkcs12-unsupported-pbe-algorithm = Unable to import. Encryption algorithm not supported.
sec-error-pkcs12-unsupported-version = Unable to import. File version not supported.
sec-error-pkcs12-privacy-password-incorrect = Unable to import. Incorrect privacy password.
sec-error-pkcs12-cert-collision = Unable to import. Same nickname already exists in database.
sec-error-user-cancelled = The user pressed cancel.
sec-error-pkcs12-duplicate-data = Not imported, already in database.
sec-error-message-send-aborted = Message not sent.
sec-error-inadequate-key-usage = Certificate key usage inadequate for attempted operation.
sec-error-inadequate-cert-type = Certificate type not approved for application.
sec-error-cert-addr-mismatch = Address in signing certificate does not match address in message headers.
sec-error-pkcs12-unable-to-import-key = Unable to import. Error attempting to import private key.
sec-error-pkcs12-importing-cert-chain = Unable to import. Error attempting to import certificate chain.
sec-error-pkcs12-unable-to-locate-object-by-name = Unable to export. Unable to locate certificate or key by nickname.
sec-error-pkcs12-unable-to-export-key = Unable to export. Private Key could not be located and exported.
sec-error-pkcs12-unable-to-write = Unable to export. Unable to write the export file.
sec-error-pkcs12-unable-to-read = Unable to import. Unable to read the import file.
sec-error-pkcs12-key-database-not-initialized = Unable to export. Key database corrupt or deleted.
sec-error-keygen-fail = Unable to generate public/private key pair.
sec-error-invalid-password = Password entered is invalid. Please pick a different one.
sec-error-retry-old-password = Old password entered incorrectly. Please try again.
sec-error-bad-nickname = Certificate nickname already in use.
sec-error-not-fortezza-issuer = Peer FORTEZZA chain has a non-FORTEZZA Certificate.
sec-error-cannot-move-sensitive-key = A sensitive key cannot be moved to the slot where it is needed.
sec-error-js-invalid-module-name = Invalid module name.
sec-error-js-invalid-dll = Invalid module path/filename
sec-error-js-add-mod-failure = Unable to add module
sec-error-js-del-mod-failure = Unable to delete module
sec-error-old-krl = New KRL is not later than the current one.
sec-error-ckl-conflict = New CKL has different issuer than current CKL. Delete current CKL.
sec-error-cert-not-in-name-space = The Certifying Authority for this certificate is not permitted to issue a certificate with this name.
sec-error-krl-not-yet-valid = The key revocation list for this certificate is not yet valid.
sec-error-crl-not-yet-valid = The certificate revocation list for this certificate is not yet valid.
sec-error-unknown-cert = The requested certificate could not be found.
sec-error-unknown-signer = The signer’s certificate could not be found.
sec-error-cert-bad-access-location = The location for the certificate status server has invalid format.
sec-error-ocsp-unknown-response-type = The OCSP response cannot be fully decoded; it is of an unknown type.
sec-error-ocsp-bad-http-response = The OCSP server returned unexpected/invalid HTTP data.
sec-error-ocsp-malformed-request = The OCSP server found the request to be corrupted or improperly formed.
sec-error-ocsp-server-error = The OCSP server experienced an internal error.
sec-error-ocsp-try-server-later = The OCSP server suggests trying again later.
sec-error-ocsp-request-needs-sig = The OCSP server requires a signature on this request.
sec-error-ocsp-unauthorized-request = The OCSP server has refused this request as unauthorized.
sec-error-ocsp-unknown-response-status = The OCSP server returned an unrecognizable status.
sec-error-ocsp-unknown-cert = The OCSP server has no status for the certificate.
sec-error-ocsp-not-enabled = You must enable OCSP before performing this operation.
sec-error-ocsp-no-default-responder = You must set the OCSP default responder before performing this operation.
sec-error-ocsp-malformed-response = The response from the OCSP server was corrupted or improperly formed.
sec-error-ocsp-unauthorized-response = The signer of the OCSP response is not authorized to give status for this certificate.
sec-error-ocsp-future-response = The OCSP response is not yet valid (contains a date in the future).
sec-error-ocsp-old-response = The OCSP response contains out-of-date information.
sec-error-digest-not-found = The CMS or PKCS #7 Digest was not found in signed message.
sec-error-unsupported-message-type = The CMS or PKCS #7 Message type is unsupported.
sec-error-module-stuck = PKCS #11 module could not be removed because it is still in use.
sec-error-bad-template = Could not decode ASN.1 data. Specified template was invalid.
sec-error-crl-not-found = No matching CRL was found.
sec-error-reused-issuer-and-serial = You are attempting to import a cert with the same issuer/serial as an existing cert, but that is not the same cert.
sec-error-busy = NSS could not shutdown. Objects are still in use.
sec-error-extra-input = DER-encoded message contained extra unused data.
sec-error-unsupported-elliptic-curve = Unsupported elliptic curve.
sec-error-unsupported-ec-point-form = Unsupported elliptic curve point form.
sec-error-unrecognized-oid = Unrecognized Object Identifier.
sec-error-ocsp-invalid-signing-cert = Invalid OCSP signing certificate in OCSP response.
sec-error-revoked-certificate-crl = Certificate is revoked in issuer’s certificate revocation list.
sec-error-revoked-certificate-ocsp = Issuer’s OCSP responder reports certificate is revoked.
sec-error-crl-invalid-version = Issuer’s Certificate Revocation List has an unknown version number.
sec-error-crl-v1-critical-extension = Issuer’s V1 Certificate Revocation List has a critical extension.
sec-error-crl-unknown-critical-extension = Issuer’s V2 Certificate Revocation List has an unknown critical extension.
sec-error-unknown-object-type = Unknown object type specified.
sec-error-incompatible-pkcs11 = PKCS #11 driver violates the spec in an incompatible way.
sec-error-no-event = No new slot event is available at this time.
sec-error-crl-already-exists = CRL already exists.
sec-error-not-initialized = NSS is not initialized.
sec-error-token-not-logged-in = The operation failed because the PKCS#11 token is not logged in.
sec-error-ocsp-responder-cert-invalid = Configured OCSP responder’s certificate is invalid.
sec-error-ocsp-bad-signature = OCSP response has an invalid signature.
sec-error-out-of-search-limits = Cert validation search is out of search limits
sec-error-invalid-policy-mapping = Policy mapping contains anypolicy
sec-error-policy-validation-failed = Cert chain fails policy validation
sec-error-unknown-aia-location-type = Unknown location type in cert AIA extension
sec-error-bad-http-response = Server returned bad HTTP response
sec-error-bad-ldap-response = Server returned bad LDAP response
sec-error-failed-to-encode-data = Failed to encode data with ASN1 encoder
sec-error-bad-info-access-location = Bad information access location in cert extension
sec-error-libpkix-internal = Libpkix internal error occurred during cert validation.
sec-error-pkcs11-general-error = A PKCS #11 module returned CKR_GENERAL_ERROR, indicating that an unrecoverable error has occurred.
sec-error-pkcs11-function-failed = A PKCS #11 module returned CKR_FUNCTION_FAILED, indicating that the requested function could not be performed. Trying the same operation again might succeed.
sec-error-pkcs11-device-error = A PKCS #11 module returned CKR_DEVICE_ERROR, indicating that a problem has occurred with the token or slot.
sec-error-bad-info-access-method = Unknown information access method in certificate extension.
sec-error-crl-import-failed = Error attempting to import a CRL.
sec-error-expired-password = The password expired.
sec-error-locked-password = The password is locked.
sec-error-unknown-pkcs11-error = Unknown PKCS #11 error.
sec-error-bad-crl-dp-url = Invalid or unsupported URL in CRL distribution point name.
sec-error-cert-signature-algorithm-disabled = The certificate was signed using a signature algorithm that is disabled because it is not secure.

mozilla-pkix-error-key-pinning-failure = The server uses key pinning (HPKP) but no trusted certificate chain could be constructed that matches the pinset. Key pinning violations cannot be overridden.
mozilla-pkix-error-ca-cert-used-as-end-entity = The server uses a certificate with a basic constraints extension identifying it as a certificate authority. For a properly-issued certificate, this should not be the case.
mozilla-pkix-error-inadequate-key-size = The server presented a certificate with a key size that is too small to establish a secure connection.
mozilla-pkix-error-v1-cert-used-as-ca = An X.509 version 1 certificate that is not a trust anchor was used to issue the server’s certificate. X.509 version 1 certificates are deprecated and should not be used to sign other certificates.
mozilla-pkix-error-not-yet-valid-certificate = The server presented a certificate that is not yet valid.
mozilla-pkix-error-not-yet-valid-issuer-certificate = A certificate that is not yet valid was used to issue the server’s certificate.
mozilla-pkix-error-signature-algorithm-mismatch = The signature algorithm in the signature field of the certificate does not match the algorithm in its signatureAlgorithm field.
mozilla-pkix-error-ocsp-response-for-cert-missing = The OCSP response does not include a status for the certificate being verified.
mozilla-pkix-error-validity-too-long = The server presented a certificate that is valid for too long.
mozilla-pkix-error-required-tls-feature-missing = A required TLS feature is missing.
mozilla-pkix-error-invalid-integer-encoding = The server presented a certificate that contains an invalid encoding of an integer. Common causes include negative serial numbers, negative RSA moduli, and encodings that are longer than necessary.
mozilla-pkix-error-empty-issuer-name = The server presented a certificate with an empty issuer distinguished name.
mozilla-pkix-error-additional-policy-constraint-failed = An additional policy constraint failed when validating this certificate.
mozilla-pkix-error-self-signed-cert = The certificate is not trusted because it is self-signed.

xp-java-remove-principal-error = Couldn’t remove the principal
xp-java-delete-privilege-error = Couldn’t delete the privilege
xp-java-cert-not-exists-error = This principal doesn’t have a certificate

xp-sec-fortezza-bad-card = Fortezza card has not been properly initialized. Please remove it and return it to your issuer.
xp-sec-fortezza-no-card = No Fortezza cards Found
xp-sec-fortezza-none-selected = No Fortezza card selected
xp-sec-fortezza-more-info = Please select a personality to get more info on
xp-sec-fortezza-person-not-found = Personality not found
xp-sec-fortezza-no-more-info = No more information on that personality
xp-sec-fortezza-bad-pin = Invalid Pin
xp-sec-fortezza-person-error = Couldn’t initialize Fortezza personalities.
PK
!<p�F�6localization/en-US/toolkit/passwordmgr/passwordmgr.ftl

password-manager-save-password-message = Save password for { $host }?
password-manager-save-password-button-deny =
    .label = Not now
    .accesskey = N
password-manager-save-password-button-allow =
    .label = Save
    .accesskey = S
password-manager-save-password-button-never =
    .label = Never save
    .accesskey = e

password-manager-update-password-message = Update password for { $host }?
password-manager-update-password-button-delete =
    .label = Remove saved password
    .accesskey = R
password-manager-update-login-add-username = Add username to saved password?
password-manager-password-password-button-allow =
    .label = Update
    .accesskey = U
password-manager-update-password-button-deny =
    .label = Don’t update
    .accesskey = D

password-manager-no-username-placeholder = No username
password-manager-toggle-password =
    .label = Show password
    .accesskey = h
password-manager-confirm-password-change = Confirm Password Change
password-manager-select-username = Select which login to update:
PK
!<���``0localization/en-US/toolkit/payments/payments.ftl

credit-card-expiration = Expires on { $month }/{ $year }


credit-card-label-number-2 = { $number }
    .aria-label = { $type } { credit-card-label-number-2 }

credit-card-label-number-name-2 = { $number }, { $name }
    .aria-label = { $type } { credit-card-label-number-name-2 }

credit-card-label-number-expiration-2 = { $number }, { credit-card-expiration }
    .aria-label = { $type } { credit-card-label-number-expiration-2 }

credit-card-label-number-name-expiration-2 =
  { $number }, { $name }, { credit-card-expiration }
    .aria-label = { $type } { credit-card-label-number-name-expiration-2 }
PK
!<3�7�8�8/localization/en-US/toolkit/pdfviewer/viewer.ftl

pdfjs-previous-button =
    .title = Previous Page
pdfjs-previous-button-label = Previous
pdfjs-next-button =
    .title = Next Page
pdfjs-next-button-label = Next

pdfjs-page-input =
    .title = Page

pdfjs-of-pages = of { $pagesCount }

pdfjs-page-of-pages = ({ $pageNumber } of { $pagesCount })

pdfjs-zoom-out-button =
    .title = Zoom Out
pdfjs-zoom-out-button-label = Zoom Out
pdfjs-zoom-in-button =
    .title = Zoom In
pdfjs-zoom-in-button-label = Zoom In
pdfjs-zoom-select =
    .title = Zoom
pdfjs-presentation-mode-button =
    .title = Switch to Presentation Mode
pdfjs-presentation-mode-button-label = Presentation Mode
pdfjs-open-file-button =
    .title = Open File
pdfjs-open-file-button-label = Open
pdfjs-print-button =
    .title = Print
pdfjs-print-button-label = Print
pdfjs-save-button =
    .title = Save
pdfjs-save-button-label = Save

pdfjs-download-button =
    .title = Download

pdfjs-download-button-label = Download

pdfjs-bookmark-button =
    .title = Current Page (View URL from Current Page)
pdfjs-bookmark-button-label = Current Page


pdfjs-tools-button =
    .title = Tools

pdfjs-tools-button-label = Tools
pdfjs-first-page-button =
    .title = Go to First Page
pdfjs-first-page-button-label = Go to First Page
pdfjs-last-page-button =
    .title = Go to Last Page
pdfjs-last-page-button-label = Go to Last Page
pdfjs-page-rotate-cw-button =
    .title = Rotate Clockwise
pdfjs-page-rotate-cw-button-label = Rotate Clockwise
pdfjs-page-rotate-ccw-button =
    .title = Rotate Counterclockwise
pdfjs-page-rotate-ccw-button-label = Rotate Counterclockwise
pdfjs-cursor-text-select-tool-button =
    .title = Enable Text Selection Tool
pdfjs-cursor-text-select-tool-button-label = Text Selection Tool
pdfjs-cursor-hand-tool-button =
    .title = Enable Hand Tool
pdfjs-cursor-hand-tool-button-label = Hand Tool
pdfjs-scroll-page-button =
    .title = Use Page Scrolling
pdfjs-scroll-page-button-label = Page Scrolling
pdfjs-scroll-vertical-button =
    .title = Use Vertical Scrolling
pdfjs-scroll-vertical-button-label = Vertical Scrolling
pdfjs-scroll-horizontal-button =
    .title = Use Horizontal Scrolling
pdfjs-scroll-horizontal-button-label = Horizontal Scrolling
pdfjs-scroll-wrapped-button =
    .title = Use Wrapped Scrolling
pdfjs-scroll-wrapped-button-label = Wrapped Scrolling
pdfjs-spread-none-button =
    .title = Do not join page spreads
pdfjs-spread-none-button-label = No Spreads
pdfjs-spread-odd-button =
    .title = Join page spreads starting with odd-numbered pages
pdfjs-spread-odd-button-label = Odd Spreads
pdfjs-spread-even-button =
    .title = Join page spreads starting with even-numbered pages
pdfjs-spread-even-button-label = Even Spreads


pdfjs-document-properties-button =
    .title = Document Properties…
pdfjs-document-properties-button-label = Document Properties…
pdfjs-document-properties-file-name = File name:
pdfjs-document-properties-file-size = File size:

pdfjs-document-properties-size-kb = { NUMBER($kb, maximumSignificantDigits: 3) } KB ({ $b } bytes)

pdfjs-document-properties-size-mb = { NUMBER($mb, maximumSignificantDigits: 3) } MB ({ $b } bytes)

pdfjs-document-properties-title = Title:
pdfjs-document-properties-author = Author:
pdfjs-document-properties-subject = Subject:
pdfjs-document-properties-keywords = Keywords:
pdfjs-document-properties-creation-date = Creation Date:
pdfjs-document-properties-modification-date = Modification Date:

pdfjs-document-properties-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }

pdfjs-document-properties-creator = Creator:
pdfjs-document-properties-producer = PDF Producer:
pdfjs-document-properties-version = PDF Version:
pdfjs-document-properties-page-count = Page Count:
pdfjs-document-properties-page-size = Page Size:
pdfjs-document-properties-page-size-unit-inches = in
pdfjs-document-properties-page-size-unit-millimeters = mm
pdfjs-document-properties-page-size-orientation-portrait = portrait
pdfjs-document-properties-page-size-orientation-landscape = landscape
pdfjs-document-properties-page-size-name-a-three = A3
pdfjs-document-properties-page-size-name-a-four = A4
pdfjs-document-properties-page-size-name-letter = Letter
pdfjs-document-properties-page-size-name-legal = Legal


pdfjs-document-properties-page-size-dimension-string = { $width } × { $height } { $unit } ({ $orientation })
pdfjs-document-properties-page-size-dimension-name-string = { $width } × { $height } { $unit } ({ $name }, { $orientation })


pdfjs-document-properties-linearized = Fast Web View:
pdfjs-document-properties-linearized-yes = Yes
pdfjs-document-properties-linearized-no = No
pdfjs-document-properties-close-button = Close


pdfjs-print-progress-message = Preparing document for printing…

pdfjs-print-progress-percent = { $progress }%

pdfjs-print-progress-close-button = Cancel
pdfjs-printing-not-supported = Warning: Printing is not fully supported by this browser.
pdfjs-printing-not-ready = Warning: The PDF is not fully loaded for printing.


pdfjs-toggle-sidebar-button =
    .title = Toggle Sidebar
pdfjs-toggle-sidebar-notification-button =
    .title = Toggle Sidebar (document contains outline/attachments/layers)
pdfjs-toggle-sidebar-button-label = Toggle Sidebar
pdfjs-document-outline-button =
    .title = Show Document Outline (double-click to expand/collapse all items)
pdfjs-document-outline-button-label = Document Outline
pdfjs-attachments-button =
    .title = Show Attachments
pdfjs-attachments-button-label = Attachments
pdfjs-layers-button =
    .title = Show Layers (double-click to reset all layers to the default state)
pdfjs-layers-button-label = Layers
pdfjs-thumbs-button =
    .title = Show Thumbnails
pdfjs-thumbs-button-label = Thumbnails
pdfjs-current-outline-item-button =
    .title = Find Current Outline Item
pdfjs-current-outline-item-button-label = Current Outline Item
pdfjs-findbar-button =
    .title = Find in Document
pdfjs-findbar-button-label = Find
pdfjs-additional-layers = Additional Layers


pdfjs-thumb-page-title =
    .title = Page { $page }

pdfjs-thumb-page-canvas =
    .aria-label = Thumbnail of Page { $page }


pdfjs-find-input =
    .title = Find
    .placeholder = Find in document…
pdfjs-find-previous-button =
    .title = Find the previous occurrence of the phrase
pdfjs-find-previous-button-label = Previous
pdfjs-find-next-button =
    .title = Find the next occurrence of the phrase
pdfjs-find-next-button-label = Next
pdfjs-find-highlight-checkbox = Highlight All
pdfjs-find-match-case-checkbox-label = Match Case
pdfjs-find-match-diacritics-checkbox-label = Match Diacritics
pdfjs-find-entire-word-checkbox-label = Whole Words
pdfjs-find-reached-top = Reached top of document, continued from bottom
pdfjs-find-reached-bottom = Reached end of document, continued from top

pdfjs-find-match-count =
    { $total ->
        [one] { $current } of { $total } match
       *[other] { $current } of { $total } matches
    }

pdfjs-find-match-count-limit =
    { $limit ->
        [one] More than { $limit } match
       *[other] More than { $limit } matches
    }

pdfjs-find-not-found = Phrase not found


pdfjs-page-scale-width = Page Width
pdfjs-page-scale-fit = Page Fit
pdfjs-page-scale-auto = Automatic Zoom
pdfjs-page-scale-actual = Actual Size

pdfjs-page-scale-percent = { $scale }%


pdfjs-page-landmark =
    .aria-label = Page { $page }


pdfjs-loading-error = An error occurred while loading the PDF.
pdfjs-invalid-file-error = Invalid or corrupted PDF file.
pdfjs-missing-file-error = Missing PDF file.
pdfjs-unexpected-response-error = Unexpected server response.
pdfjs-rendering-error = An error occurred while rendering the page.


pdfjs-annotation-date-time-string = { DATETIME($dateObj, dateStyle: "short", timeStyle: "medium") }

pdfjs-text-annotation-type =
    .alt = [{ $type } Annotation]


pdfjs-password-label = Enter the password to open this PDF file.
pdfjs-password-invalid = Invalid password. Please try again.
pdfjs-password-ok-button = OK
pdfjs-password-cancel-button = Cancel
pdfjs-web-fonts-disabled = Web fonts are disabled: unable to use embedded PDF fonts.


pdfjs-editor-free-text-button =
    .title = Text
pdfjs-editor-free-text-button-label = Text
pdfjs-editor-ink-button =
    .title = Draw
pdfjs-editor-ink-button-label = Draw
pdfjs-editor-stamp-button =
    .title = Add or edit images
pdfjs-editor-stamp-button-label = Add or edit images
pdfjs-editor-highlight-button =
    .title = Highlight
pdfjs-editor-highlight-button-label = Highlight
pdfjs-highlight-floating-button1 =
    .title = Highlight
    .aria-label = Highlight
pdfjs-highlight-floating-button-label = Highlight


pdfjs-editor-remove-ink-button =
    .title = Remove drawing
pdfjs-editor-remove-freetext-button =
    .title = Remove text
pdfjs-editor-remove-stamp-button =
    .title = Remove image
pdfjs-editor-remove-highlight-button =
    .title = Remove highlight


pdfjs-editor-free-text-color-input = Color
pdfjs-editor-free-text-size-input = Size
pdfjs-editor-ink-color-input = Color
pdfjs-editor-ink-thickness-input = Thickness
pdfjs-editor-ink-opacity-input = Opacity
pdfjs-editor-stamp-add-image-button =
    .title = Add image
pdfjs-editor-stamp-add-image-button-label = Add image
pdfjs-editor-free-highlight-thickness-input = Thickness
pdfjs-editor-free-highlight-thickness-title =
    .title = Change thickness when highlighting items other than text

pdfjs-free-text =
    .aria-label = Text Editor
pdfjs-free-text-default-content = Start typing…
pdfjs-ink =
    .aria-label = Draw Editor
pdfjs-ink-canvas =
    .aria-label = User-created image


pdfjs-editor-alt-text-button-label = Alt text

pdfjs-editor-alt-text-edit-button-label = Edit alt text
pdfjs-editor-alt-text-dialog-label = Choose an option
pdfjs-editor-alt-text-dialog-description = Alt text (alternative text) helps when people can’t see the image or when it doesn’t load.
pdfjs-editor-alt-text-add-description-label = Add a description
pdfjs-editor-alt-text-add-description-description = Aim for 1-2 sentences that describe the subject, setting, or actions.
pdfjs-editor-alt-text-mark-decorative-label = Mark as decorative
pdfjs-editor-alt-text-mark-decorative-description = This is used for ornamental images, like borders or watermarks.
pdfjs-editor-alt-text-cancel-button = Cancel
pdfjs-editor-alt-text-save-button = Save
pdfjs-editor-alt-text-decorative-tooltip = Marked as decorative

pdfjs-editor-alt-text-textarea =
    .placeholder = For example, “A young man sits down at a table to eat a meal”


pdfjs-editor-resizer-top-left =
    .aria-label = Top left corner — resize
pdfjs-editor-resizer-top-middle =
    .aria-label = Top middle — resize
pdfjs-editor-resizer-top-right =
    .aria-label = Top right corner — resize
pdfjs-editor-resizer-middle-right =
    .aria-label = Middle right — resize
pdfjs-editor-resizer-bottom-right =
    .aria-label = Bottom right corner — resize
pdfjs-editor-resizer-bottom-middle =
    .aria-label = Bottom middle — resize
pdfjs-editor-resizer-bottom-left =
    .aria-label = Bottom left corner — resize
pdfjs-editor-resizer-middle-left =
    .aria-label = Middle left — resize


pdfjs-editor-highlight-colorpicker-label = Highlight color

pdfjs-editor-colorpicker-button =
    .title = Change color
pdfjs-editor-colorpicker-dropdown =
    .aria-label = Color choices
pdfjs-editor-colorpicker-yellow =
    .title = Yellow
pdfjs-editor-colorpicker-green =
    .title = Green
pdfjs-editor-colorpicker-blue =
    .title = Blue
pdfjs-editor-colorpicker-pink =
    .title = Pink
pdfjs-editor-colorpicker-red =
    .title = Red


pdfjs-editor-highlight-show-all-button-label = Show all
pdfjs-editor-highlight-show-all-button =
    .title = Show all


pdfjs-editor-new-alt-text-dialog-edit-label = Edit alt text (image description)

pdfjs-editor-new-alt-text-dialog-add-label = Add alt text (image description)

pdfjs-editor-new-alt-text-textarea =
    .placeholder = Write your description here…

pdfjs-editor-new-alt-text-description = Short description for people who can’t see the image or when the image doesn’t load.

pdfjs-editor-new-alt-text-disclaimer1 = This alt text was created automatically and may be inaccurate.
pdfjs-editor-new-alt-text-disclaimer-learn-more-url = Learn more

pdfjs-editor-new-alt-text-create-automatically-button-label = Create alt text automatically
pdfjs-editor-new-alt-text-not-now-button = Not now
pdfjs-editor-new-alt-text-error-title = Couldn’t create alt text automatically
pdfjs-editor-new-alt-text-error-description = Please write your own alt text or try again later.
pdfjs-editor-new-alt-text-error-close-button = Close

pdfjs-editor-new-alt-text-ai-model-downloading-progress = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB)
    .aria-valuetext = Downloading alt text AI model ({ $downloadedSize } of { $totalSize } MB)

pdfjs-editor-new-alt-text-added-button-label = Alt text added

pdfjs-editor-new-alt-text-missing-button-label = Missing alt text

pdfjs-editor-new-alt-text-to-review-button-label = Review alt text

pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer = Created automatically: { $generatedAltText }


pdfjs-image-alt-text-settings-button =
    .title = Image alt text settings
pdfjs-image-alt-text-settings-button-label = Image alt text settings

pdfjs-editor-alt-text-settings-dialog-label = Image alt text settings
pdfjs-editor-alt-text-settings-automatic-title = Automatic alt text
pdfjs-editor-alt-text-settings-create-model-button-label = Create alt text automatically
pdfjs-editor-alt-text-settings-create-model-description = Suggests descriptions to help people who can’t see the image or when the image doesn’t load.

pdfjs-editor-alt-text-settings-download-model-label = Alt text AI model ({ $totalSize } MB)

pdfjs-editor-alt-text-settings-ai-model-description = Runs locally on your device so your data stays private. Required for automatic alt text.
pdfjs-editor-alt-text-settings-delete-model-button = Delete
pdfjs-editor-alt-text-settings-download-model-button = Download
pdfjs-editor-alt-text-settings-downloading-model-button = Downloading…

pdfjs-editor-alt-text-settings-editor-title = Alt text editor
pdfjs-editor-alt-text-settings-show-dialog-button-label = Show alt text editor right away when adding an image
pdfjs-editor-alt-text-settings-show-dialog-description = Helps you make sure all your images have alt text.
pdfjs-editor-alt-text-settings-close-button = Close
PK
!<�De��@localization/en-US/toolkit/pictureinpicture/pictureinpicture.ftl
pictureinpicture-player-title = Picture-in-Picture


pictureinpicture-pause-btn =
  .aria-label = Pause
  .tooltip = Pause (Spacebar)
pictureinpicture-play-btn =
  .aria-label = Play
  .tooltip = Play (Spacebar)

pictureinpicture-mute-btn =
  .aria-label = Mute
  .tooltip = Mute ({ $shortcut })
pictureinpicture-unmute-btn =
  .aria-label = Unmute
  .tooltip = Unmute ({ $shortcut })

pictureinpicture-unpip-btn =
  .aria-label = Send back to tab
  .tooltip = Back to tab

pictureinpicture-close-btn =
  .aria-label = Close
  .tooltip = Close ({ $shortcut })

pictureinpicture-subtitles-btn =
  .aria-label = Subtitles
  .tooltip = Subtitles

pictureinpicture-fullscreen-btn2 =
  .aria-label = Fullscreen
  .tooltip = Fullscreen (double-click or { $shortcut })

pictureinpicture-exit-fullscreen-btn2 =
  .aria-label = Exit fullscreen
  .tooltip = Exit fullscreen (double-click or { $shortcut })


pictureinpicture-toggle-fullscreen-shortcut =
  .key = F


pictureinpicture-seekbackward-btn =
  .aria-label = Backward
  .tooltip = Backward (←)

pictureinpicture-seekforward-btn =
  .aria-label = Forward
  .tooltip = Forward (→)


pictureinpicture-subtitles-panel-accessible = Subtitles settings

pictureinpicture-subtitles-label = Subtitles

pictureinpicture-font-size-label = Font size

pictureinpicture-font-size-small = Small

pictureinpicture-font-size-medium = Medium

pictureinpicture-font-size-large = Large
PK
!<�KDK��6localization/en-US/toolkit/preferences/preferences.ftl
password-not-set =
    .value = (not set)

failed-pp-change = Unable to change Primary Password.
incorrect-pp = You did not enter the correct current Primary Password. Please try again.
pp-change-ok = Primary Password successfully changed.

settings-pp-erased-ok = You have deleted your Primary Password. Stored passwords and certificate private keys managed by { -brand-short-name } will not be protected.
settings-pp-not-wanted = Warning! You have decided not to use a Primary Password. Stored passwords and certificate private keys managed by { -brand-short-name } will not be protected.

pp-change2empty-in-fips-mode = You are currently in FIPS mode. FIPS requires a non-empty Primary Password.
pw-change-success-title = Password Change Succeeded
pw-change-failed-title = Password Change Failed
pw-remove-button =
    .label = Remove

primary-password-dialog =
    .title = Primary Password
set-password-old-password = Current password:
set-password-new-password = Enter new password:
set-password-reenter-password = Re-enter password:
set-password-meter = Password quality meter
set-password-meter-loading = Loading
primary-password-required-by-policy = Your organization requires that you have a Primary Password set in order to save logins and passwords.
primary-password-description = A Primary Password is used to protect some sensitive information, like logins and passwords, on this device. If you create a Primary Password you will be asked to enter it once per session when { -brand-short-name } retrieves saved information protected by the password.
primary-password-warning = Please make sure you remember the Primary Password you have set. If you forget your Primary Password, you will be unable to access any of the information protected by it on this device.

remove-primary-password =
    .title = Remove Primary Password
remove-info =
    .value = You must enter your current password to proceed:
remove-primary-password-warning1 = Your Primary Password is used to protect sensitive information like logins and passwords.
remove-primary-password-warning2 = If you remove your Primary Password your information will not be protected if your computer is compromised.
remove-password-old-password =
    .value = Current password:
PK
!<ː?���4localization/en-US/toolkit/printing/printDialogs.ftl
print-setup =
    .title = Page Setup
custom-prompt-title = Custom…
custom-prompt-prompt = Enter your custom header/footer text
basic-tab =
    .label = Format & Options
advanced-tab =
    .label = Margins & Header/Footer
format-group-label =
    .value = Format
orientation-label =
    .value = Orientation:
portrait =
    .label = Portrait
    .accesskey = P
landscape =
    .label = Landscape
    .accesskey = L
scale =
    .label = Scale:
    .accesskey = S
scale-percent =
    .value = %
shrink-to-fit =
    .label = Shrink to fit Page Width
    .accesskey = W
options-group-label =
    .value = Options
print-bg =
    .label = Print Background (colors & images)
    .accesskey = B
margin-group-label-inches =
    .value = Margins (inches)
margin-group-label-metric =
    .value = Margins (millimeters)
margin-top =
    .value = Top:
    .accesskey = T
margin-top-invisible =
    .value = Top:
margin-bottom =
    .value = Bottom:
    .accesskey = B
margin-bottom-invisible =
    .value = Bottom:
margin-left =
    .value = Left:
    .accesskey = L
margin-left-invisible =
    .value = Left:
margin-right =
    .value = Right:
    .accesskey = R
margin-right-invisible =
    .value = Right:
header-footer-label =
    .value = Headers & Footers
hf-left-label =
    .value = Left:
hf-center-label =
    .value = Center:
hf-right-label =
    .value = Right:
header-left-tip =
    .tooltiptext = Left header
header-center-tip =
    .tooltiptext = Center header
header-right-tip =
    .tooltiptext = Right header
footer-left-tip =
    .tooltiptext = Left footer
footer-center-tip =
    .tooltiptext = Center footer
footer-right-tip =
    .tooltiptext = Right footer
hf-blank =
    .label = --blank--
hf-title =
    .label = Title
hf-url =
    .label = URL
hf-date-and-time =
    .label = Date/Time
hf-page =
    .label = Page #
hf-page-and-total =
    .label = Page # of #
hf-custom =
    .label = Custom…
print-preview-window =
    .title = Print Preview
print-title =
    .value = Title:
print-preparing =
    .value = Preparing…
print-progress =
    .value = Progress:
print-window =
    .title = Printing
print-complete =
    .value = Printing is Completed.

print-percent =
    .value = { $percent }%
dialog-cancel-label = Cancel
dialog-close-label = Close
PK
!<(�p���4localization/en-US/toolkit/printing/printPreview.ftl
printpreview-simplify-page-checkbox =
    .label = Simplify Page
    .accesskey = i
    .tooltiptext = This page cannot be automatically simplified
printpreview-simplify-page-checkbox-enabled =
    .label = { printpreview-simplify-page-checkbox.label }
    .accesskey = { printpreview-simplify-page-checkbox.accesskey }
    .tooltiptext = Change layout for easier reading
printpreview-close =
    .label = Close
    .accesskey = C
printpreview-portrait =
    .label = Portrait
    .accesskey = o
printpreview-landscape =
    .label = Landscape
    .accesskey = L
printpreview-scale =
    .value = Scale:
    .accesskey = S
printpreview-shrink-to-fit =
    .label = Shrink To Fit
printpreview-custom =
    .label = Custom…
printpreview-print =
    .label = Print…
    .accesskey = P
printpreview-of =
    .value = of
printpreview-custom-scale-prompt-title = Custom Scale
printpreview-page-setup =
    .label = Page Setup…
    .accesskey = u
printpreview-page =
    .value = Page:
    .accesskey = a

printpreview-sheet-of-sheets = { $sheetNum } of { $sheetCount }


printpreview-percentage-value =
    .label = { $percent }%
printpreview-homearrow =
    .label = { $arrow }
    .tooltiptext = First page
printpreview-previousarrow =
    .label = { $arrow }
    .tooltiptext = Previous page
printpreview-nextarrow =
    .label = { $arrow }
    .tooltiptext = Next page
printpreview-endarrow =
    .label = { $arrow }
    .tooltiptext = Last page
printpreview-homearrow-button =
    .title = First page
printpreview-previousarrow-button =
    .title = Previous page
printpreview-nextarrow-button =
    .title = Next page
printpreview-endarrow-button =
    .title = Last page
PK
!<�B�S��7localization/en-US/toolkit/updates/backgroundupdate.ftl
backgroundupdate-task-description = The Background Update task checks for updates to { -brand-short-name } when { -brand-short-name } is not running. This task is installed automatically by { -brand-short-name }, and is reinstalled when { -brand-short-name } runs. To disable this task, update the browser settings or the { -brand-short-name } enterprise policy setting “BackgroundAppUpdate”.
PK
!<+�L0localization/en-US/toolkit/updates/elevation.ftl
elevation-update-wizard =
  .title = Software Update
elevation-details-link-label =
  .value = Details
elevation-error-manual =
  You can update { -brand-short-name } manually by visiting this link
  and downloading the latest version:
elevation-finished-page = Update Ready to Install
elevation-finished-background-page =
  A security and stability update for { -brand-short-name } has been
  downloaded and is ready to be installed.
elevation-finished-background = Update:
elevation-more-elevated =
  This update requires administrator privileges. The update will be
  installed the next time { -brand-short-name } starts. You can restart
  { -brand-short-name } now, continue working and restart later, or decline this
  update.

sandbox-missing-unprivileged-namespaces = Some of { -brand-short-name }’s security features may offer less protection on your current operating system.
sandbox-unprivileged-namespaces-dismiss-button =
  .label = Don’t show again
  .accesskey = D
sandbox-unprivileged-namespaces-howtofix = How to fix this issue
PK
!<�T,���.localization/en-US/toolkit/updates/history.ftl
history-title = Update History
history-intro = The following updates have been installed

close-button-label =
    .buttonlabelcancel = Close
    .title = Update History

no-updates-label = No updates installed yet
name-header = Update Name
date-header = Install Date
type-header = Type
state-header = State

update-full-build-name = { $name } ({ $buildID })

update-details = Details

update-installed-on = Installed on: { $date }

update-status = Status: { $status }
PK
!<8U8�@�@�hyphenation/hyph_af.hyfHyf0tP�����'-4�������$�'–’-111001���������\�-�.Za�bh�c��dT�e@Pf`_g�h�it�j��k�l(+m,Hn(woȷpD�q��r�s�Nt(}u@�v �w��x`�y�zܧ��^�]|s�Y�nZ���a�bXc�f�gPh�k�l mdn(p|rs�tD	u�����a�	b�c�d�e<f�g�h�!iP$jD&k�+l�/m�2n�6o�<p�?r`CshLt|Pu�QvUwDXx�XypYz ,Pah���Xw���da(dpl4d��|��\��bh��-�d -�c�lsDe!Pe���ay&����oh�l�e,�a��0t�a\�j�����,.����4al�6@rfLcD�:�.�Q��dm�Ppr�i��|a�eo0r�sd�@�F�.�i���t�i�e�n�t$!�CL�.�T�s�R�.{���s�je otZX�j!�j](aPo�ya�skfD'�-|�k�s��d���p��r�y\���h����0�\��.@k�<`�lk��m|k�a�k���at0ux~� ~���r�o�e8p�@prp$u�E����<.�pDa�l�5�,	��`s�hrh��text�Tr���wH��.�r�u�e�t���e�f8lDo|p�s�t�zv�ar�m��P��r�gإ��a�$a�x�,dy�h�T�0tn��Pe����\�$��!��$�(y,�tTz�\z���ahz,�exz�h�w�z��z���e{��eH�a��e��h~�,w$}�s8{�p����Ok�o�i�4r4d��@��L����Xa�c�d�g�it�O��ȷ�l���p��r4����l�sL��4�Ļ���o4�����oDr,�r���h��t�� rXsЏ ܏&8e�@d��,Li�+��dg�p����la�t�zd�0\�4�.D��L����r4�,�ut���h �aL��e��b���.,$7�sP�<�a�j@n`ott�hF��,g�4o�@ ���L.,�TfXCF���li�r�EK����a�l$���a��Rԋa�.�b���l��,�o���h�s	td�Y�`��,�jx���	e�����f	y��� 	e��,	r\���8	d�_L�]P	v�YX	n����d	a�	d�	l(
rl
s�
t�p	a�
e�i�lPor0u�y��a�f��	n���	i�	oXge��,.ȩi�	i�����	o�m�7
w��
g8���
aL
i��s����8
sT�]@
n|�a��X
k��`
eH�y�b��x
.��,�
oL��
h$�s@����
s���
k��
e ����
dk lhs�t��a.,�
fĭ���
ad��� ���a<o���.x0l0)�>Hs���Pox�\kL�����tl\��|e�ul��h(�,����l�p�����.�,�o��h�����s��IJ���iu|������s���Ķ��fж$rT�0e�i�o���<k�m�n
p 
rD
s�
t�
uܶ���xs��eH��P����s4��r�����X�t����a����a�i`��h����at���a���
l������
o����.p�,
rH��8
o\
t�����T
a���dr]h
tȯ�p
iX��|
r�
spw�w��
a|�,�
w�9�����
.���
t�����
i�
tg�̴��
j,�i�����
e��$��o�n���$eXl�ta��Dl����Lo|������dl��,le���xh(��rL����d�h�np��l0p<sXtȉa��o��e��a�!�i��m,���t8���r���$ox�4�a(��Dl4��LaHc��x
.T��dy`�ph����|t�yh��ah8i�o�yH��P����n0��n�����e������.X���e���o�l��a8�a� c���,rht,t��s��Hs���Pu��\rD9��it.ج�|a���t�����s�������i����r�����p��$a e�i�o�ru|y$r���.xm�n���o<s\���gXk�t�wl��H��DrDt����Lapo�����ho�P�؝�|e�����g���a�j��a�e\���il�|�������i���kT�|����a��]�a8o@���lHw0���k\l�r�s�	a��a@
]@eh��t���Ta�&3hal�ipgP���|o��a���l���a�o�p��a���k��e$��l�����p�����e$j8sdt������st�k�i �0i�������Dk��,La���XhP�e���pa�o����xm�r4�e���,.H��e8��t�����s��a�����f����e����o�������s|���n8pDt��l��$eԏ,r���
j�:�:��Pe�Xi��dp����ps�������.���u\��cT����bdTe�f�g�i4k�n�p�r�slt�ux0z8����#�a(i��wX����.@���n��'��4k���<s$���Hrdt�+��0�.�\��ln�[]xo@����rH�&�q��oTp�fk,�od����g��0���u����d\���nsx�$t(�.�,kTo�tD�� s@� �2@a�Hplr�67�6��ddTF=��xi�o��r�FhQ�TQf�sP��e���r$���d�ko0t@�lf^: a�����t�fO�re!�(jTo�mXs&@t�r��Hp�&B�&�`s�&hm�%,to�#�s��e`)�a�wP'���d�f,i8n@tl�a�)H�o,,L,���e�+���lo�,��,o��.��v4.] n2X9QTe\jxs�9�(:�H��td.�:,le�w�w�0<��a�k�o�p�t\�U<�u�=Z,?��?^�.�md�b�+a����aD@^c,n8r��ah�o�a�$h�$i8ADlf�Pm@.�AfHe�A��Tg�A`a�spvi�C,|ntGe�G���a�E���r�s�Hr�H���c�H��a�H�t���z���sXx�u(w���d�K��otL���.�L��adL$r@Ppo�r�y\�x�Pv,xXh\Y��dc�w������e���l�\}�[��iH_aT_��i,_��j�^���t`_<a�e�idl�o�rPuLl&,u7ePo8agXpHa��aho�_��,l�r�s(��\�|a��a`oP�00b�tob��|b�a���n�b�e��mt����t�����rHd��a�c���dgpk�l�mn@rLse�,eLu�es�e��s4e� k@e�Hef8lTe@iX��|s 
Xn�e��du����e�f���a�f��f��p�f���p�f�o�u��PF���t�E���iĜr@��e�L���v�Y�����f���|hB�h glh(g�g��4o�h��j�k�o�p�t�d�,i7hb�r8i�pa��Di�,?��i��t.�r�iH�e�i��.A�@���g�iH�e�l�dl���f(gHs�`��`�r�g���atma�m4n�m<e�o�pk�n�m��Ty�)��*:�o�xatr�Tp���o�uX��`s���.@s��aTs���d �0��eԧ�b������t� u��}04��a8v��b@��.L�,oX��8d��Di������\s0���dg���pn�|a�e� i� o�!y$�a0����u<��lH����b�g0 iD kp r� w�a��r$����o rL�aX�] a�:��� d����$ n؄e���< oą�̅��P l��^X k���d a� e�*���� n��l�b\�� .<���� v�T���� t����� eȉ��� f!l8!od!t���ȋ� aԋ��!a$!o ��,�!o���h�0!i��X�D!e,�iL!z4��X!at!oH�e|�a��]|!o���!r�����!g�_&X\���!uU���!r(���!e��!t����!c"g8"lD"n�#r�#s����"l�H.���"sܬ "u����,"eL�t"a�"d�"g#i#k`#ot#s�#t��0�l".�"rT���"e�����"iH�����"w��8����"rı��"aP��"r�"w���^�\��0d��"iP���"t�0#y��#l8#n@#w ��\�$�x��l#eܸH#i��T#s$ �<���#n���nm�#e�n�#ed��l����#fx���#y���#wt�������#a��#aĽ���#a$k$oX�Z,���n�\m$i��� $l����,$g�$h�$s�$tt���8$aP%e�%o�%u8&y%��@���p$.�^�x$e�p�$w��a����$a���$p|k&؝��$a�����$g���$a�g��c�$r`_���$eԧ�$gܧ��%�&���m62$%p,?,%p��8%o\���D%sD�7�oy��\%fXxh%oP���t%d�%s�������%a��%f��%a���.Ј�%s�����%ot����%k����%e�����%g8��&r�a,&lP� &e`���,&s��
�&a(e�(i8)l�)n�)o�*r8+uL+w�+y����t&rL���|&a�&d�&f('nL'pt'r�'t�&�X��&th���&e,���i4���p$.�����&o���&r4d���&�`��h���'tt�'n���'oD�aX'kT��4'a���@'l<��<�0D�`'e���h'b��������'r��,�'a�'uH��'h�'o�'u�����.�j,.�j���'������'��a����'i������'e��(l����(pL(r�(s�����0(a\(ld(rX���8(kt(s0,�e�50@�l(a�(l�(p��0��D@e8��(t��8��(t����(s|����(e�(p)r$)t�������(i����(lԱ+@�d.H��)eT��)sT�i��0)a`)el)iL0��L)n��T)y��ix)pH������)kl���)i�)o� ��&�)n0��)p�������)a�)f����)a�����)m*pl*rD,���}*e@*lL*o��,*r�%��i$*a��a���8*a�Ze�7�.��T*g����`*a�*sX��*e4�|*tL���p$.3+Lt���*.�����*k����*a+y�*��c`_���*e8���*g�����*���1����+t�����.����+n�� +o<���,+m��i���D+e\+oD�id��H��d+p��l+o����x+tt����+o�B�B��+i�A�+jT����+t���+e���	�+'8,a�,e�-i0.l\.o/ux/�,����+���,�@Ba�A,ii ,th���,,e`,kt,s`�a�4L,a<���T,w���l,a<8����,s����,i�,k�,p0-s��;��B�,.�����,��	�,ü���,o������,o��,o-r��	r�	�-b�	,-a�	$-hT-o`-ph-tx-u�ap
L-n�?�
H�o��
p-u��L`���-e�-i�-o�-r����-g.pTi���-m��]�-e��&���-e�i�-s���-a�� ���.dd��.yh$.o0�L�<.n,xD.hX��P.c�.f�.g�.n�.s��p$. �x.i����.u�P����.�\m�.��.l��:���.dH���.t.��.k�fax&�.n\&/e�%��/g@/i'�',/l�&��4/sP�k0���L/r��T/e8��`/d���l/�(+	�+'�/a�0e`1i�1n2ot2u�2y,�+a�+�/o�+�/d�+���/c0nD0sp0t�0z��a��/l�-���/a0u��a�.0r���0��$0k0�,0e�/80t(0�00��P0s<0,X0eH0d0h\�a��|0aX��0d�3�3�0a�0lP3���0kX1���0l�0r1s41t��6�6��0l�5���0k�8��71m1w 9h9�49� 1mH9(1e�:��:�@1s�:�H1o�:��T1d�1r�1t��a\�t1a0�|1bXo�ho���1a����1a�=��1r�Ti���1e?V?���1d ?��1a�>�1j�>���1d42fT2s�?V�?��2m�?�2a�?��(2ltAa�A�@2nAH2id2k�A\�E+�E��l2e�Ga�G�2n�G���2e�2itG���2n�2u H��G�2n�c����2s8����2i(}���2n,H83a04eT5i�5k6op6u�6y�]:I`3d�H 3n�H��,3a\3f�3g�3s4u�I��IT3l�Ja�3e�J]h3oJ��t3r���������3a�4�3a�K�3m�3t��\L���3r,L��3o���p��3s\��3r\���3u,e�V��4aH4o\4rx4y�T��4k�4s�4t5w�	e���h4n(]P4i<�e��kh
p4s8X��4e��=i�4h,X��4rXB0@B���4kxX�4i�4j�4r�Be�sa�X��4uh�a|Y�4c 5f45l@5m�����5oh�a���,5a(+�a�a+�a��L5el5j�5r�5xt��h��a@�x5a��5v����.(w���5n�����5oh���p�5aTf�5s�d�5oh�u6j�h���5tth���5e46g@6k\6n����hai 6e�h(6e$i+�T0�ixH6u�i��P6d v+�u��h6l�mH��|6t��6n����6o�v���6l�~~���6r{���6k7rx7s(w��
�6e�7g�7h�7k(8lD8mP8n�9ox:pD;r�;s<t8<u|�+<7al7o$w7�(��(7gp�07lT7n0�@�(��L7g$4.��`7s��\m,��7l��(����7s؍]�7g����7i��7r�p}�7o�����7h��]�7a����7lȉ@�48i\���8ul�8e���8i$����$��<8s���8d�8e�8i�8k9p9sx9t�9u�0�8t�T��x8iD���8u��d0W��8o���8m0���.��8s��8a\�4L�m����8md����8r���9a0��49hH9sp�,,9e����6@9eV�|U��T9r����\9e�9rd��h9e�9r�q��}�9a�9i���ؗ���9s�X�Xv��9.�u�9sd�LT��:dh��9n:p(:r@:sē��p$.D��:e���ħ�8� :n8:sx���(���.РH:t���T:e�:g�:i�:r��`:e�:o�:r�:sܠ��L.�00���:n����i�:dL�����:f����:a����;m|��/��:k�4;u�����;e�7 ;i��,;g����8;ap;k|;o�;tH��T�]\;e��d;n��̩�����;o��\�;o���;k�;o��l5��.���;gp��;oL�a<l���;l�,�;e���;hH�X��d���<o̱ <o|���,<dx<n�<t��T��L<.D�T<i�Y��`<�����l<�g̴��<jȷ�<a,=e�=i>l4>o�>r�>u$?yܸ��<id����<d=v�����.���<e��aļ���<oм�<l�������=aH=sh���=lh=r|=s�'�,@=k0���T=tD���\=s�=���t=k�=t���B����=j�����=t�����=e�=l�=t4�7�.��=f����=a|��`���=sX�i��>aH�a�>oXx>zL���(>d`>e�>n�>st|P|��L>e4���T>fp>nx��(�:���x>wt����>th��>d���>a�il���>e������>d����>a<����>t�������>a|��?a<?lP?o\?rd���?p�?t��a���4?aP�a���H?l��e0��8���d?n�,l?o$��x?h���+'�?a`@eAilAoxBu�By,���F�����?dt��?r�����?a@d(@s�f+�tHf��?r,���?a|�a.��@g��@e� 8��4@uD��<@mt�H@u���T@a�@g�@k�@m�@s�a�]|@u�����@r���p����@sx�.D���@sT�@acl�i ��@l��+Aj Ar����@tl���@e<Ap�B�D��������(AeX��0Alt���p$. x�HAy,xTAk4���`Ac�Ag�An�Ap�AsBt<Bw��a��Ak��Aa�e�.����Ao�As�����Ad �d������Ay�����At���#�Be$��Br�����(Bi�0Bl K�|HBp�]PBiP�\Bl�BoH
��hBk�Bs���Bp�� ���Be|?�����Ba���Ba��Btx-u�me0���Ba$���BkCsT�a|��BeCt�^LI`Cs�$Cnh��0Ca�Cf�Cg�CkDm��n$Dv�<Ca8Dc\De�EfFg FhhFi�Fj�FkPGldGm�Gn�Go\Ip�Ir�ItKu�Kw�Ky,i4i�Co���2�Ca�m�Cp�{0Tx�Cy����Ct(}�a���Do�Dl��i�0Da�����9.<�DDn,��PDa�De�DkElDErdEs�Et�Ex����Da|���Dr�DyL�.X����Ds`�h���Dkt,�Du����Dh���Da����DaEo���Ep�* �*��$Ei�),En`��8Eex��PEe4XElxEt�Eu�e�p-uXS�A���Ep��Ea�Er8C}�K��K���Ey�u�t���Eet��ElT�Ea�Ei|i}�Fr4�Fa<FeDFiLFoTFupixi�i�i$!i�Fe���\Fn�Fp�Fr�!�!|Fs�"���.#,�Fo#��Fh��7�.�����Fg��Fa,$i$�Fa�$i�$�FaGeGl$Go,Gr4Gu`&i�'}�'i�)}l*i�+�+��<Gg\+��DGa�.i�.\Ga�Ge�Gi�Go�Gu$/iP/i�/i�/i�0p0�Gs80�Gg�/���Ga�Ge�0iXxu Ha@Hw2���GdlHi�Hk�Hm�Hn�HpHIr�P&���HaLx��Hf`�8���,Hn��4Hah3p3LHs|3��THt�3`He�3�3xHp�3���Ho044��He�Hsd�+@4#�H.P4i�He�4��4m�Hg6}�HeIhL6aX6��Hk��t6��Ikd6,Io̬(��(IsT7��0Is�6��<IoL9i�8TIexIl�Io�Ir�Iy�:}��+;���Ig��0D<�In<��Ie0=i�/�=��I.H=�IiP?i?�IaJelJitJo�Jr�Ju�Z:�AJp�@��Jm<JrD3�B(JlLB��0JoTJt�_0�_4LJadJj(::XCiDDi�o>�E��|Jf�E��Ja�Je,FD4F�Ji��0�u��Jr@G���Ja�Jc8G��7X����Jtt~���Je�H��Ji0Km8KpXH��Kb@KeLKi\Kk�Kt(+������ .tIi��|���TKr�@V�@��hKr�J,pKe�J�|Kh�Krho��,.���Ka<Li<K�Ke�Ki�Ko�LiMi�MLl0Ln\Ls�at(�Ki����Kv�MJ�M��Lr�M�Lg�M��$La�+P�+��<Lg\�4DLaHNPLl�N�La�Le�Mi<Nj�NoXOr�OsPy�M��#�LypP���LsO���Lf�Lg�PipU�|U���LkU���LeMf8Mr�Ms�Va�V�Lo�V���LlW��\!Mi0\MfPMgX\��(MahMt0(��\�HMr(:�tMi�_�\Mj���0ae�Mu`|MtDa�La���Md������Me�c���Mn�Mt�c���Me�MkNn�B���Mj�d+�ea�e�Mr�e��Ne<\V��pNi,$��$Nag�0NaTNoXg`g��LNkDL.L��`Ns�h��lNy�h��xNe�Nk�Nl$Or0Oudkapk]�Na�k���Nl�Ny�\�����N.t����No�'a{�Nn���Nu(�mЧ��Ot���On�l��Oo�m�$��8p�8Orho��@Op0o�LOatOe�Oi�q��plOk�Os�q��r��Osdr]�Oe�r��tiLt�Oa�Oh�Oj�����u7�Oa�u<�Oa�{��{��Oax{��Oa,Po@Pr�{��Pd|a|��$Pr��a(|�8Pad��p�LPa|��TPa����`Pl�Pt(}��lPi�PmQn0QrPQt؇��Pi�Pj\����b@���Pi�h����Pnh���Pal:,�Pl���Ph�gQt8����PsȎ�l���Qa0���$Qaho+��<Qa��DQrh���\Qdș��dQa�Qe����pQk�QnRr0Rs@��Qa�ReXTl�To�a��Qi�I���Qf�����Qa�Qo�e��kt��Qn���Qa���RaL�aX�Rnd�$ReLRwdRy��a��4DRaX�q.d�XRs��&L���pRo���xRn@��RiĜ�Rc�Rl�Sm�SrTs8Tt�
�Rrp
�Ra�Rr�����Ra@So S�4�&����Re����.�vSr���S�h���,Sr��4SeXSm (�PSel@�4��dSg���lSnTxSeL��T��Sa(��Sm�Sn�Sv���Se`��h����Sa������Se4%�$�Sf�={�SaD�Tk�l&C��Te���� Tr��,To���(���DTs<���LTat�a��dTn��lTi���xTg�Tl�Ts<1�H��Tw�����Ty�����To���Tk؍�����Ti�k,�Tr�����Thx����Tg8Un`Us ���Ua�Ue,Wh\Wi�Wy�W��+�������@Uu��4HUo4�TUmxUpP�a���pUa`2e$��U-�Um�Uo@����UbVe8VkLVl�Vn�Vr�Vs��i(wa\��Uop�����Un�]�Ua����Ul ���VkVt������$Vu����,Vu�e����DVahVi�atVg`�\Vn��a�|Vn����Va�e����Vl�Vr����Vk���=.���Vk�VoWwIJaв�Ve4�a@�H�Va���LW.ܧ��W��<Wi W�<�i���9.\�DWdT���PWi|Wn�Wp�Wt$������tWs��+ض+��T����W.���We����W����ȹ���WsԹ4�Wa����Wwx����Wk Xn�����Xd��Xn���Xa8Xe���p�0Xt���<0t|�LXn���TXiT�`Xm�Xn�Xs`���lXaYkYshh�g�Xi����XnHFXH�Xd��XuT�k���Xn�����Xo�#�Xo�����XlP��0YeDYl�)�`��Ye,$Yr�+�\�4<Ya�H��E��PYs����XYu���dYe�Yw�Y���l�]�Yc�����Yi8���Yrܧ���Y�x�i ��Ya�YiT�iZ
�Zd�Zf0[g<\iH\k�\lL]m�]n�_p`r�bs�bt�cu���Ya�dbfcfdie�if�kg�ph�piTrk�tl8{m�nl�ot�p��r�s��t\�u�v��wt�x��yX�z4d�f�Za�Ze�Zo�ZrHfi�fiXgilg�@>�=��ZeT�Zm�i�Zm�i���Ze[sD�(]�Zt@k,[al�|k[l\[s�k�� [a�[e�[r�[sL7��H[m�bP[e���eh[sf��p[aLl|[l�m^�[o��m�u��[d�u���[n�n��m�[l�[p�[t\wDo�ho��o��o���[e�o4�[e�p�PN7\.�\gDq�$\a�p0\lTrd\al\ot\r�\s�\wDri�sit^h�$���|\r���\e4t,�\txt4�t�\d�\f]s���.�����\s�u�\yv�v���\e]pL�a�[]oy�(y,]a@]fLS#,]e #,4]e8{d]al]et]o�]sh{i|i�|i0�8�|]l��]u�~,�]m��]dH^gT^k�^l�^s�_t�_v�a����]u���]o�]rTQ�^n4���]e^o����D��^s��B��f(^s��0^n���<^o|���lp^o�^r���Tfh^n���,.���|^y`���^gXg��^et�ؙ��^eĈ�^g؈,�^a_e0_iP_k\_ld_nt_o�_p|a_r`��^e _k��am����(_gX&7�m<_m�{D_ex�����4؊l_m�_nHoa��Ty<���_gD����_i����_u�v��v,�_at��_a�_o�_r`u��i��i��x�i��^����
`a``d�`e�`i�`kallao�as$bt�but�ax`g����D`n�`s��P`a�`e�`i�`u��������`.����@�����`g�����`n��i4�^T�^�������`k��}�`e����`w�adae���U-ab8akLav`azp�H0aah(at(Daa��a�Xai�^`���.�ai�tar̕�ae�ai�at4.� <��ad�@�����ak����ae�al��<�aebi�Y�xA���al�0XS�`���bpt��baDbidbo�br�al��<bnX��`��Pbol���Xbm��r�fpbbȘ�xby��^���bo,�i�bmP304f�bl4��be���cncocr8cs�J�J���bt�H�bg$h��ba4�iL��@�a�t$cf��,,ce`cf�cl�co�ct$S,]eT,Tce����,�lcm\�tci�co�cyd-a����7���cn��cn�cwp��lJ����ci��e��cb\���cw��+4���dn����doԧdrܧ��(d��i����.DrDds<���Pda�do����\dk�dr�ds�ldaed0eePelper�es�eu�ew ��s�db����dm4��8����deLB�@���dr����de���dt��4��ep����es ���$er�
����<euIJ��Dea<�,���\ep����deu��\�,|eo���ea��eh@��ec����es��+T����ee���ei��P��eeD��euh��eq���Hfa�fe�fi,gjLgmXgolgr�gsHhu�hv�hy\��\fadfm�frL�$D��|fp\C��d.h��pfet�+0���fs�+0>��fn���fa���fl�fm�1�>i�fet>��fe���gex�����fs����guH)t���gd��� gu�<��:��8gn���@gi���dgw��0����ged�5 �xg.T��gs$
4
�ge�����gw���gr,��ge�gn�go4ht@hu��i��=`���g.,���gphr�6+,?&�Bho����hs`�<(heXHi|��dhi�hs�ht؇C����\ht@��H���phlT��xhe��hp���<j$�H��`�ht���hr`�,�he���is��V����ht����hi���hmT�Hil�is(��4���io$iop
0it�
��<iadiepio!��\iixiA�D@<xio8r�iu0<�itp�k\��irXA���iu�M�LS�$S�iejk8jl@P��	�ieXjfxjg�ji�jl�jo�jr@ksXkv�PD��js�
��S$ja�S��,ja�Um�U]Dje�TLjr,V�4V,dja�Uljh�W�W���jrxV�jnXX��Yf\Y�jf�[]�jakekiko0ku4\���#a�jm�jnH\U��+`\f�\[kt�\U�\f(kd�\a�\f<K��\,8kw�^gdkl�^,Lka�^`��kn�kr�_lka�kdlkll$lp8lr`_��pp-|kaLlemgmi\mlxmo�mr�ms�otpu�_mH`�kv`��hP�`��kvaf$wHa��lgP�Fb�li4�tb��0le���cDl.tlb|ld�li�lm�lr�ls�lw\�yHd�ld��a@���lolS��f�@���g��lv�h��j��j���ln�j�leHj��"s�k�luk,�ledl�$mnPmsm� m��mg4ms<���E�T��<mr�mDmt�m��P�qdmgTplmg�mr�mux��r���md�mk��@s�Ts���md�tf�mit]�me�mu��+8vf�vH8nbhv	�maXnilnk�nl�nn�noDophot�owܴ д�$no�d,nny��x��Dns�xLnn@yty,dna�no�nr�ny�I�(���nvz�ne�nl���dz�@++�zilz4�no�zi���T2���np�z4�neom$oo0op�p�4�o.(��t5om�z2�g.{+�z�<oe\ooh{�P{��Tor�{��oa�oi�\k�{�xol�{���oa�|FlK�xK���oa�}4�oa�oe$La�}���oe�~i�ou�}��oup��.\��orXfpi,pr@puĐt0���$pep��\�8prL�7\.h���Lpg���Xpa��dpl����piH���|pr��pe�psX��d����p.�qgDqlpqn�qp�qsrt0q��ap�p.�o��pe����pnP'�T���qr��qe����$q��!���<qpdpax�+Pqe<�,XqtL���dqsT��0��|qs����qiL���qeX����qk�ql�qprtؿ+Ŀ4�qaH�+�qr����<��qs<��qyT�+g���rj(rr��������0rnL�8ra�rr����Dra�resiXskdsl�sn�sotr4tsltuxtw�tyD������rb������re�����re�ro���rl�rs�ap��rf8@8��rs\�P����rs|�sn ss���8st0���p$.<��,se8�	��Dsoh��Lso�4�sa�se��p���tskT���|sn�F���st���sel�4������se(��sr���sb�sm��P���ss��]te ti`�f�u��]tgx�\��,,tcPtm\tp$/F���Hte(�H��f<�dti��#�te�ti�����+����tnt���ts��h�,ugpuk�un����ta�ud�uevf$wgPwi�wk�xl�xmyo(ysxzt{u,{v�f �k�uip�iudL�7 ua@umHut�o"�o�X��`�Pudl�4Xue<���dulL�T|ua`��ua����ug�+X���ui��ue(���uf�us�
�
H�ur�	�utd
�<vn
���v-veHvhXvidvj�vo�vs�vu��|
�4vi�\�
��
Pvet�\�l�lve�Y��tvi�vl��L{�vo`,�vk�vn�vt����vi�]a�vn�]�va$@�i\�(�r+���wilwrwoD������0ws0��8wn�DwetwgHaa�`wl`��hwa�wl������ws��wa``7����wd���wrp�waH�waxl<xoPxsdxu�xw0[T��wgT����wa�]xaD�mT�xd8�$xn��0xo|��,Hxp�\xi����.T�pxk,]|xyl�3�xn��xeh��xeܝ��xr$3���xeX1���xi��xeylX���6nt���xoXX"�`yg",ya�yg�yk�yl�yozp$zw�c��Lyi47Tyt,#���3a8#,lyaD#�xyh��0�yk`&�yl�#{�ye7�,;\+�yit5XD$�yo�7D<�yt<��yet$�yr�$��$��za�$�zaHze4
7�L��4zw�$��<zr<��Lb��Tzab,\ze�zu,%hzh�zr�zs{w@��,����zsP�s$�@�zs�r���zt0o��zoF�t�zsLt,�za�za{���ze�%��ze�%�"��({ot( {e(+�
h{a�{b|et|i�|m�|o$}p�~s�u�y�+��{k�{n�{r�{t�,4l�����{j�-���{t�.��L
i(�K4����{oH0�{o�k������{g|s�0,�{a�=Q���{sX1�$|gD|s�2Y�2��|t�=a=�0|n�78|iX|mh|p�8_�8a�8<`|o�:��|s$��@+���|wX���|y�<�|k4^�D,�|u�=���|f�=��|a�|u�F��E���|n�>���&��,�|e���}r C��}r�B�}aH}l�}o~sX���Lpg��<}at}e�}i�}uH����`}r��h}eH�+�.P��}g\&�.�����}g�:d�e�}eX�k�}h�C���}m����}l����}�,�}��C,�}e`cf,~iP~t$ ��xrh� ~e�?�C��8~f\~wD@~a��7�q�<wd~l4l~e,x~sD,�~e�~k�~lmDolp�w��}`]�~p`D{�~o�m�,���~d,�~n�D�~eH9F(i�D�t�D�e8u@B`Fa�/�0l�D��s�9��Ls�D��Tl�D`e�K0xK��xn�rdAH�a�y�K0����.xM���n�E��iPF��t�UtG��JYJ���t�H�gl�k��l��s,H���a��c�dl�e��g��i|�k�o��r؈s��td�y�,���L�lxt#T�aK��`�w(K0K��x�f�:aL���i�K��p\��hM��-��s4���sL�Ȁn�M��Ԁa@�dT�k`�n��p��s��ttM��aԁe��iĂj�o4�r`�s$�wt��f�,�lf�4�ep(��M��L�tt�+t�a���l�l�M&��(N��e�:��r����t0o���:�e�N����tlN��ȁg�k$�m<�rT�t|�v���NP�N����s-D���l0O�a�0�.XO��0�f��`�s,�H�e`��I|I��h�l�Ip�a�^:��a�����wlP����nx�0����p.Ђr�����a��a�P��P��؂m��n�p�u�P��P�̱�.�����dQ� Q� �k0Q�(�aD�o�Q�RaR�L�rRT�e��k��p�t��w����R|�p�R{��oH8�X8����a�S��aȃr�S&�S���e�S���g�S<ԃe<T���[eHT4�eP��X����g@����n���i@�yx���.����4�n�3a�WL�ntW��T�o�T`�r�\�.`x�lZ��a�d�Y�	��a�g,�hL�l��n��o�r4�s̆u��7�f�̄mZ&Ԅe��k���r�k,�ok,�h�p�����i�k, �a��[��8�d�[@�a\�i�n�l�gx�p�n0�.`>&�.�ath��l�o���oLr�[f��s�[��n܅rd��̥ȅ.�r��Ѕe'�#�u�t�pp\]��e(�mw���m�\��k�\(�aX�kp�n��t��u^�^,P�a�z�#eH_id�e��``��|�.��m8`<��e�`7`a0rĐȅ.0�����e�a��rD���؆r�a���t�a�e�n(�sXg�$c���l���c �fL�l,�ؿ��8�wĿ4@�a��H[m��pX�e$ed�s�d�p�a��rȇwl�&�����e`���f�f���e��y�f�4g4�PHj�Їs�t�vth؇p�rl�s���P�j���md$�g@�o�j�}k����,�i���4�e�DDD��L�eج�T�ohk`�tD�a����x�n���y�k��h�(7�H���wL"��ll��a�m$�s�k,Ĉa8�c`�e��ỉj�kx�l��m��n؊o�p,�t(��=�0�e�sD> �\$�,�@�a���H�l<lT�i��utG��G��p�a�E��x�rd":�.Hm����k�l���nL�0܉kT$����k�m<��e��o�	�x�7�����dt$���r�mm$�i�m{�e4�i@�o0�+<��D�,�nn�~pX�u�m�n��P�s�+�Hn�d�t,n�l�aHf /&��a�.���d�n���a�ni������n�6����ao̊rlo!|o�a�e$�o�o��o���r�th:��.�o�DplJiP�o�@��<�lDD��D�i\O�xO\�n��r�q��d�aċsDq�	t�a�eX�il�j��oЌr(�sD�uX�w`+�=�Ћo�S��s�� B��q��؋i�k �n8�t�XL�thW��s�X��g��Z���n�A�,.ha,�e<�L�r��D�s|r��L�n�r!�r�d�o(s��r��x�l��p̢y�����uXs&��r�q��q��t�s��s�s�Če�i�o�r+�C]�n�r�.t����b '��t{�i4�o�t,�k�u�Xu�du��<�i����z�P�o�v(wȷ�
��a$�eP�i|�l��o�p�r��sx�u��yd��ča؍g�k�r���P���n�oY8���Ѝt4tPD���sL�Y�����m�t4�����hh��D�n����0�px���8�o���p�r�������\�a�d�f��L����k��n�s �(���a4�����aD��t�����d����̎a��pԎt��d����r����al��(�o4�u<�y��+���� �l������L�kT�s0+��K�x:��i8���\�l��h�l��,t�a��e�i�k(�tl�w�&�	�����o,��k���ȏn`�&Џeh�܏d�����f��{��o|?P?���a���a<�iX�olJ�������D�t����L�e�����d�e<����ip���td�����g8�k\�p������a��d4�eؒf�gT�i��kؔn�o��r̕st�t��u��y������n ���a��7�w�s�D��eP�4 �e\���,�lP�o|���H�p8�4p�p��a��h�ap��|��|�t������g��sԑt����a�e �o\�D����.�:��̑mP�+�����r�t@B0,���i�������pt���pt�$�,�aX�e��g��i��k��*����P�s�}�}4d�e8�l�w����x�s����-��3��p`�}��ip�����n�+���Ēa����̒lt���a���h$�i0�l8�o�+h�8�m��edl�t��i�����+���@�fl�]H�ex�j�������d�et�l�k�@�����a����n�����aؓi�lH�o��s��wP��d���ēit�̓n������r���e��]��e�o��+ �s�����v,(�o����0�v �<�n`�v�0�X�.�1y�1l�i��4t�u��,��n��p���t�}��ad����a�L���o@���v��̔a4��e0�o`�ph�w��F �i����j�����t�B�J��(�hH�mP�pX�s ��p�M�J�����5v��+����t�g��,|�a��ee��t���s4;���g�<�-��a�e<�i\�kȖp��t�$��k����eX�l(�nP��� �a<=��4�e\z��H�u`{P�o��r8���)��l�pX�t�a��e��o�)D��e�)P�0�8X�.�����n�<��aؖr�&0�l���l�<�a�e�xm�@��,.D���eL�a���(�n��0�i�b^��vxOH�s���T�a��l��s��`�aȗcؗe4�hl�oȘr0�s�Jsd}��u�Q����b��SXh�\�+(��З.�e�h�id(�d����nDW�\W�ԋa�b�� �lb,(�oL�u0�a,���D�r4����X�o���`�m��o��r��kl��g��r8��	�@qp0q���k(	��e<	���e�o�y4�7 s��ܘp�	���eܻx�	f��bHs�t&�b�	�m�	,$�aL�l`�p.aP�D�u�v�	X�rx�y�	��	��p�nH
��s��4X�.���o$̙txOxol�i��a����a4�hؙgH�p��aT�cd�d��eКf��g�h<�iP�jh�k��l�m�n,�oh�p��tP�wd�y��|�@�p��d�i��\�.t�h��\���|t,|�g��rĚt\(aT��k`����a�AF���eT��<���ؚt�k,�i���h,DFi� �� �u� 4�e� ��$�r�0�f��$<H�.`�m(+0�$���o��r̛w)x�l�'��o�)~�)���i��u,*+\+4؛a0[��+�ěg�+��̛a�.4�y�/�80m�/��a�ol1�d�U43���.2 �fL�gX�o`�p�t\3D�it5$6��8�|�o�;�;��t�r?���.��aĜh�l�o$�r��|?�P?����aPC��	�̜s�g�Ԝe�D�DD���f�.� F���.�E���k4�n�E��aD�op��F��F��<�o<K4HNqp�t�MX�sXN�xO��rO��x�a؝g�s�w�N
��a0�e��h��i��jПk��l�m4�oL�r��s�tP�u�P��S4��sD>�d.�=6�e�t1a���b��\W���tU��$�iP�lp�r��t<�Y��H�jL]�0\\�rX\��d�a��e��o�*a�]��rh^aL^��n`��A���sha��et������̞t����Ԟrh��ab,�c�eD�kp�l���Lb���e��m���$�p|�,�n����8�i��&H��P�eX��X�n�b�d�o̢0��|�e�c����t�c����eg4,��\���s��,��-lg�ğv��ܟt��e�g��eA	�>���s�g��ol��g�h��(�o@t��З.0o�@�y���X�a���`�m�tl�lLt,
x�a̠i�j��k\�l��m̡n�o��t�w��ؠe<u����tPu��n��u�0 '�n�u{�i�oH�r�(�u�m(�p����.v��u]4�pv�<�i,!P�T�e��y��s�)��l�hh.t�k v��at�������a�i7�1���dl1��opv��o�^:�e4�ءw�v�m�nT�pp�w@�P4�$�d4�kH�l�4���&5�,�o�gXg�@�u6������\�r��d�e$wd,w��|�d�v����nw���eĢr�qԢe4F��kdw���eܢo��Xw0�w4�x��x,�e�y�w��h(�u���Hc���syi�y��y�0�a�y�8�a$y��D�m(}���d��g�l�p8�rp�sԤt��a8f��y����eH��r4t+Ё4��aX�����r�s���,Уu؁�أb;at$�o��,��p����sԏ���Đ$�u0���,�eP�ix�8h�]H�s4�+��i��r��u@�\�t�����.���|�n̖C�G�.@G����s�vaw��e8�,��t��Ȥs���pH����o�����l@��aP�et�l��o�m��$�t��,�t����8�oĜD�l(�X�.X��\�v<���h�o��g�����npJ�wa������lx�����gХs ���a�eH�s4������إa@����g�r'e�����a�up�0|��i0�a4�(�n,0�aĸ<�hH�t����T�o(w��\�f����h�oT�H��.`�����aĦi�n�o �w�iT�����t�����s����K��Цd8��ئrD����a�o�j���9.���r��� �H$�i�.0��(�a<�4�kH���@�o�L�o��z�o�Lyi�k��h�tZ��t�gT����a��lȧo�i�&����e������kH�����r��	ԧ��L��L��M����D�����0��8��d�.��ad�b��d �eİf̰g��iT�jh�kIJlдn�o��p��r\�s��t�u�v�w�y��\d�ld��l�k�d�t�a�wZ����b�d<�k��lH�n8�r��sL�t�O�D���r��������e��̨��e�بi��!lg��af���r�s����g�ps�s(�nTr��0�iX�l��o��r�\wds�h�ax�e�s�p�0�sp�u�s�t�xax]��a�w��l�t����kȩo �tyi�n��r�s�ةsH���t���z��z�s�z,�axz�s�i��,�ad�ep�r���4�d��gܪk�aԁ��\�k4�e8�V@���x�dL���a�����l��s��̪.��<��e4���t|�	p�e|��Ԫa��r�w�������eȇ���k���n���a��� �a����,�kL�s\�t̕4��t��T�h��h�t���p�s��|�k���eЫg�j�n�pؚV�����t�,��i���īhD����.,$7ܫnP�<�a�iL9+(�eh���e���x9 �rx�\�n����4�a��@�a\�j����6��a��e��i��l��o��y��� �����IJ����$���ȬeЬiجo0���������+������a`�i�a��lP�t���sd�wT����d��eĭk �l��rx�sl�t��a\�a`�<D�a������(��l�r����t�d$�����l$Y��X����s����ul�]��eD����n�w|�m����ԭdT
��ܭn@
]�ip��H��ap
�g�
���aL�kt�l��0X�k�]4�i@�l��q�a�`�oH�h�d`�Įr-��a -��a̮lԮr`azP'����g�sL�t`T-i�-e�y�-��-���s�8e����nl8<��a$�r�5�p�0�eD<(�u'�:,8�kX9�@�s�;-<X�n<`�a0<l�a��k��l��m��o�t���=���a�$t8>�t>H,?�̯nP4+���D�ԯr�Y��ܯ�D@��e�iP�rLB��@���r�@F(�a�F4�l�t8s�F��<�l8A�D�ob���a��e�A\�h��o��w,b+�<m8���dLb����s�B+�B����nlEH@P�`_�ذe�c������s �u����dX�e|�j��n��oԱr�sH�t(�i,�r������edha����8�il�@�u����L�dh�gD�+t�r��Lt���l�Q�0Q���i�����rL�����d�`cfĻ��s8�\�̱cĽ���a�h4�s���p.�,��oh���o��h�=6(�c�����@�rt��`�e\�����|�l��o��u�����<��Hfe����a�o��rh�����d�s�u�����a4�e�i0�o��u��yгäa���nlge�b���o\�3X� �m�(�eH�s��t�	�d�ep�k=i�.�	�X�r4
9�Ba��e�B�x�i$��jxB�a������l�����f�v��r���ij�DL���ܳo��k�x$��.Ժ�n�����e��T�aX��$�k`�m��=�@�l�H�a(�L���h�m�'p�e�%��|�s�.���ReL*i��rX*��a�(��mȴs�*4,H�ܴoth�H,�,�e\+�e\�,��l�w�s(w���bx�e�gT�k��m�nT�o��p��rH�sX�t��u��Lt	T�l~��\�i��o{��h�kĵr�s(
�~��m��o�~��8еê��t|�����s�8C\�.�صk���H����r�k,�e��,�h,��gH�s+�l*(�t��,0�uh�<�k�t�aжe�i��l4�op�r����a��l��sH\����k�u�l�����e��p�9.��aX�����fh�Ķr8��t��ܶs|��e��^�e��H���dċ�e���9.�(�mP�rd�s4�F����H�sl�O�p\�t,�^��a��T����|�g�YX��tP����a$����a��o�bZ4����0���ȷmD��зa�r����ܷd0�t Qa�a����at����a�����ld��$�e@�r�e���.h�H�pT��С��`�a���h�a��t�l\�ah�����kx���a̸e������d�g�s<��Ȥ��ĸs��e�ظa��r\��h�]�ix�q$�a0�k8�l@�t\��h��a`.|e�e�����a��jܹp�t8�uW_l�d�m���l�eh�x�n�{e8{���b,$7��m��<��a$ ���eTIiĹp���йe<\k|?��i�i�aج���a�r��n�I��.tI�� �lXH,�i,b��,D�a��L�h|�sذa��h�o�,p�t؇��9.����t|�����i��sܳ4l��ȷ��r��s���̺a����Ժd�����n�����a,�e��i��o,�uP�ø������k� �e@�it�n��iL�s�88`y�\T�tl�\�s���h�gl����aP�����w�����kl�]��e`��4��лn�s�v�w��+�\��ػk������i��n,H��$i��n�+H
��$�l<�pd�8��`�m����D���,��e��i��k��o��p��t��u,����$�2��8H?HlJiXH��N�̼eԼiU��c���ܼu����el�6�l(��c(}���cL�i��l�n@�s��tԾu�����8�s����@�kd�t@��؇�\�j�}pP��p�.%��x�f���a��jȽoܽu�����tge8l.�%����p�a���Խi������b��n����e8����sf4� �d�,(�a@�4�hp�t�D;�D��P�rtJiX�e4��d�oL�aLb��|�lb,��e����h �������dĐ��np���e\�Ⱦr@�,�e��iĜ��� ���e@��`�4�d<�kL�ll�nx�s4�t�����d����D�b\�t,%8��D���d�aP����k��lпt�wX)�����t����o\��PA�T�����k�r ����e�od���.�laDD���rP��R& R�a�Q���k����l���(�aT�e`�r\W���xe̼�H�i0o�
d�.��al�c��e�h��k��o(�tt�u��y�i�i��sZ����e�m�p��r`�t�C�$}��h8{��pt�� r��������a$�lH�u�%a,%�o���t��4�.��0�o��<�s�����a��T�hh�6��a��h��i��o����������D@�e0<��tT�����s�@�d����. �e��h�l0�nh�o��s��t4�a@��nL����aH����lD�md�r��s��v�:�T<�i(�a��P�g���X�ex�i��yX������. ���l8HyH7��lh����y8���t@��Kiԇ���v8�+ȉ����e���o�+���ih���e�b+�a�k���$�i�&�7<�e��D�g���P�aȉ��\�r��t�a�|�i�������e�����mp����dho����nȯ���a����r(w����t�v<��.�`��y��r(��e�N�<�aD�oL�uO��h�$y�tJ�4��T�o@�\�t(}��h�s�0����n8�]��a�����i�����r`�����p�-d�.\�a�b@�d0�eİf��g��h��i��jT�kt�l��m��n��o��p��r,�s@�t|�u`�v�w��yM��],�b��d��k��s�Y4�n��rZ��L�a��b��f��g��k0�lD�mt�nH�pt�r��s��t�]�T^�\_��^,��l�`e``��u`����d�ee�d��s�i��k��a,�eD�hX�rl�s�b�|k�s0j�Ll$�t�+P�m�k,8�aȀ�m]��aln\�md�k��o��t�n�T| |����rho���e ��,.�s��ids4��eTr����l��o��r�wT��4h8���r�s��o,��xt#�a���t�r�t��$�a�{e8{�<�bh�p�}aH}�T�u$}�\�l����k��s�t����e����kTe��r|����e�aĈ��k؈,��a��k��m�o�t@���{��o���؊i,�eЌe����r�Tp$.���$�eT�,0�nt��<�hX�l|�+d�aX�+D������l�m��tt���il��\�.��l��t�+�\.؛����g��4��a�E������a$����a�����r��\�j�,4�y�at(�oL����v�(�l���h�a��e�h�������T�l\���\�kx�s��4��������et�����e��s0�����l��r����,��o�\��'��lD���a��sP�����a8��.���<WiL�+P���t\��aT���$�b��e �g,�i@�kt�l��m�n�o(�pP�r��s,�tt�u��T�+��r$���t�g��l��np�L��+��i`���.�����g���l���h���'��u����o���k����sx�����d�3d����.\7,�h�aD��4�a��l��n��o�r$�s�`�nH��T���t�d��t�	]|�a��e� ��,.�	��i��ml�]��aX
	`
����o�	��n�0�t��]��e(]��i��7�	�,�a<�k��(�{4�a����H�e��P�pp
\�p�
��h�a��e\�f��o��sx�t��w�+��u����e��g��k�l�m4�nkP`����gt�	�����t<	Dw��kP����e� 	T�e+@�e��(�gtZ&da(H�r���P�ep�i��o�
�\[�.�x�s�����rx��o��p������r����e#a�"��rh,��e�o8�pL�t�i�l,�n�(	�3��d�4����$�et$LX�e(H@�r4F&�A���t���`�g�l�a�P1	x��������s�(����i�(��e�=T��m��p�a����a����l�alP����n�4��i$���d0�g8�jT�k��s��tl7	��a$e@�r@�H�at�j��l��r��at�l�afe�f}p(�o���eh��r�,��p!�(j��r�!��!���i@#=	H#��kT#���e`#���l�"�p�sx#`cf�#�����(0�t'8�sP'��D�a��e�f �iL�m`�nl�o��p��sX�tx�u *C	�.,*����n�)��e��m��n�;�:����g�*��i����.�*����t@XI	�+����p,����a�+����l�.���H.4.]�n�<O	�<��,�s|���4�n�1�@�i@��2�X�a�2i��b��e��f��l��n��w�w��2+���D3+�3���@5��4��st$aP6<��o�5��j�k�l,�pX��6{��r�7a|7�u�8al8<$�u�9��9��8�l�9,@�aX9�L�h$;;��d�t�:l�i�<+<��g��l��p0<
��a�e$�iD�k��l��n��o�p��t��w�a&��i�<����n|�+@�e�����g�<��n�=!=��n&�$0�t�={8�a\�o�=0>T�r��(	H,h�dT>p�e8>|�e�>i,U&�Y���eh?����f,?��f��k��n��rp� @���i�3����sP4!�6�X8�,�r�?<�aX�ox�rT���8��$�i8P���8�s����@��;��L��T�8�]d�k@�l�i|?�xolt@����aD@<��a��e��ihA �@����e�@����l�@f��g�C�dA�e��L<.x������<L��������A��.P�n�A�e��wD!0�[4<�e�Z��D�tđ	�G��\�o��s�E��d�r��T	pH��w`_���e��l�i��h��p�c��s�ni�m����i�,0��<�t���sH�z���	��ax�c��e|�g�k��l��n �s��t���(�l`��0�oX���v���,.|�T�eؒ�`�i�l�k��tĻ,.�c����o(����i��a����l������a������f��k�n$�pD�tL������s�����eD�X	�����s�$}x��l����0�m��8�oT�u��e����q�\�a�d�f���p�o��r��sT�m`�]��e����k��o��dz���o��,��r4�\.x�4��m��:�.����pL���m�����a(�l0�r\�w���}P�i\�s��<�s��]D�b��}h�y����n�t!�t�l���|�aPZ�@���sP����a��rL�����g��l��s��t��v��}Xg�<�e����_	�=�8�i����sĽ�s��a@�oh�p��q�> ,�iL�n��+�����T�eH��\�n�������t�sP�|�iD���uTra,b����kb,��a����h��j��l�����s������eg���it��0�a<�i<���x
.,g4�if@�j����$�d����H�.L�a�0���h�l����]`�o����ah��(+�,H��~e~����r{����k�l0�s(w����eD�fT�gt�l��ml�n��o��p��rp�s��u��y�a���n@���o>�<�p\�{�o�$�k�����7�e	,�L�i|�X8�`�s���h�o��s�7ap$.D$��ȕ,��o$���p��sP�tx�������i�����lH����p��,��a�e$�lD�w,k	��r	�D��m ��i�Ly	�L��0��dAH8��4E���0�,X�k����`�s��	8�x�dh���r��	����n��r��	������f�����o��y��������s�����g$�o8�s��	d�,�y����h����t|ix�0�lH�t�xAa�@��P�lج�X�e��d�t̴�|���|�t ����e8�+ȷ������aT�e��i�o��u��y�����g�n4�s���	����a��7��a���	�����d��������n����a��(�t���	����@���l�g|�l��wH�����p
e	 ���t�ax�0�T�	������fl�]��f��g��s��v�}"(�����t�=�	x���sX�yP�.`�����s����y������4���@�nT�ol�p��s�ø��	����8�d���	��L�id�s��	��}��a��n�a��f|�n��	��`cf�
�H
����g��i��s�
�	�
����m��tD�	��	��$����.�e�k�m$�w0��	0�T� �0���a�e�f�it�j��kd�l��m��n,�o��p`�tP�w8�y�{�	L]l�b�t�m��rh��a��d��k��n�r�s��+�	(~�����r��sL�	P�	�	��s�����g�f�,�8�eX�il�kx�l��n��r��	|��0�n|��	TD�.���L�s�	
���d�o���e`}r$���e�
�����gT
`����a��t��v�\Mj(���
LS����r$S��eT,��e 
��d0�mP�n!�!�(�p|!��!
<�e$!��D�dd�sl�t�"���$40�K`&|�e�${��e��i��o �r����d '��n��s��
�'"
�(
\(����e�'��l��m�o�(/
)Kt)��)���a�)��a<�iP�o��5
�)]4�t�������H�o,f\+\�ex�i��u�,��&F0.����e.����i�.4��e��o�1�$/���e�@Vh@��r�/����o�/��a�e�o1�0��u�1<
l1�o$�t�kC2�L�e\�mh�n��o`�p��wT2�04B
4�T�eP4���d��t�4H
�4��x�.��s�4
��e��P
�iX
l5^
t5��g��H�9�L9����l��s�8��e�i�o8�rP:d
�:�X�C;���pp�0L�eX�iD<�k<�,�eX�j
��D�rt	s
?<��a��elJi(�r0�uH\�	|?�|�kP?����a��f��n�?�T@z
$@����d�@�@����a��l��n�rxA� B�	B����td��
LB����.�r �s�^�	�B�
�E@G��M��N�d�e�_4\MjX\��L�tU��X�r���-(}��p�e��n��r�sdp0Ȏ��e����t8�����s,�a8���eD�,��h0�����r ��,����k8���e@��i4�kT�p�%�
�$ �p\��(�a�9aL9��@�lؕ�H�e@�,��e��i��
��t����p�s���|�n�����e��
��i��`��dĜ��r��sĜ�
 �
=���dD���iL�������e ��8�a��e@�il�o��y�\�
�Y �lP�rx���(�a`�n��r��t`�0��
���X�gx�o�a�p�r���
������m��s؃�����.����e���3������l@�����i��r,�t|�������g�kx�L���
�odSa�A���rL� �a ��
T���8�lX�nP�3����P�g$������d�o��x���x�nD���gP���a��m��t`�����s�/P/���t�.���ipypG����nt�i��i ����u\��0�r���
ȉ���d��o����$�hZ��m��n�tD�u��<�a\�b@�c��d$�e@�fd�g�h\ijDk�
lTm$n�"o�#pP'r0<s�At�Eu�Iv4Jw�KxLydLzx��x~F�~��s�~,��e8{���s�������g0�����eT@m�����t���� �n��,�o\��8�m�����d�P�a��c��o��r�t�����x�r��t�����.h���u̴��.������t�����u�����
e��o��л+��s�����n\y+$y���i����u�t��� �el�6(�lh�4�cX�h0���P�n��t����h�a�c��p�a���|�i�`�
lk���t���a�����a��o\�����g����a�e��o��r�s��u��w��yxm�0���i@�r��s$�,�7�s8�.D�(�sP���4�ad�i�@���P�g �]X�n�����p�p��x�a�D�7��f������f��r8������s��������a��e�#T���p���
������s����k,��aT�k`�l��o��p��w�x7l%��4�m�$<�l��{H�ad�i��a�i&4�7l�e�+��t�f\�,����m��H<L�P�4��e|���dhi�#��od�<
l�����o�������t������s �w�k��Hut<��gT����a��d��fT�g��i��k��lX�ml�n��p��r$�s�t�uX�yP�a��h�u����p�t��|�a��w��y�����#��e��i@����l$�rLX���wa����a��o\� ����i�����p��m�����t4\���n�[]�a@�ep����.`\4�k�m�d���L�lp�r|�s8v�t�]h�u�����d�����i��
����a\����lD���a�l4�nD�o��r��w�=�p��s,��sT�KT�����a$�g�	]�a,�oL��
�	�l�^T���	<�gh�ox��T�T�st
\�r��k��t�iX��|�o(]��o@
^��a�a,�����l�p
��p�
����a�d8�e��i��o��s���s�����o��o��l��
 �nH�r��(�eT�iH��.�0Q����\�r���d�d��g��s��t`�p�n��
����s�L��x���b��o��
��
���d��nT��\�h,��a�n�=	��g��aē @�(�e0@��0�dd��<�nd�uTH�oHB�$��d��e��j�k��sd�tp�y��	����j$WaW���a��m��n�$�n�|nf��t����o�,��l�m �p@�w��lo0h�aqq��,�e�p44�eXs&$%p�r��L�p!�X�ol"i�<�d~l��x�e$����s�#��e��l��o�r<�s�$�t}e��o������g������od%&(%����pX���i��s�����e�%��o�%�%(�m�%,0�ad�k��t�&	��P�p$&�X�ex�o��ut&��*l*��i���ciP'��a�e�i(�o��p��s�u�y�('�p���)�d��e�n,*� ���*���d�.�4.]�n�2��bP�e\�m��nd�wX��.�2��D�sX3�hpl�r�6{t�u�5��k��l�m�o�+�+����m|7��a`7l��d�7��e8�cw�:^�s�O���t4;��s�;�0<�
\�.h�a��l��m�n�o(�pD�t|�w��yx��<��T�a<\�lx�p|��9.��T>��p8>��e�>�t>���e�uDF@PF����l�Gi�i80t�>�a�y?62�g.,?��pD<�@��e�?<�rt@����aX�lD@<4�a`�rp�y�?a8A}$HH7h�ddAlAntA��d��f�M��M����eXS�P�p�A����p�A��a�e�i<�r��w��!V(�h|U���n�A���e@Bi�n�e�TC���.dC$�k8C�0�e\�o��u s0h�d�r��P�e�z7��s��p�k�s��x�i��������l`Ei��elE���iF4�E����g�i�s �t8�u@��F���n�H��p;����eؕ��o I��<jp��9.@I{,�r8L�DLD�tL��L�s,M��Ld�rܧ��l�����0��]��Y��nZ����a�M��a�k�lX�nd�r�s�tNe�M,�t�M���st�xr|N��e�N��i4N���eD�yT� ��$�e�*,�t�(8�s Oi,O��P�a�O���a��o�s��7h�t|O��x�g�jn��rL��.T�����m(��O����dP�P{�e$P�k�$�8P�k�vap$.w���eLt,�t�N�s�i�d�s�P(�f@P��4�al�e��f��i��l��o(�s@kP$SjkD�,Ux�k�T��e��P�W����hxV��nXX�o�y��X���o�(m�Y��Y��s\Y�m�uȲh[���e�]H4�a�\,�t�]��X�l$}�8{�@�p�_H�m��s`_��T�a��eP�i\�ot�r��s��t��uh���b��k�c��l��s$�v��.�e��sf����a�i��i���r�iH�e�h�p�t�i�xja�j�o�j�w��(��0�o m��8�kdlD�nTplmg�u]tgt]h�ih!!���i(x����t�w��nhv��e�i�k�l(�pt�t�x��Dns�t�x�n��y��-�z����tlz4�o�{��e8�i�z��rH�y8����a0=��@�k�\/�{�T�l�{��\�a�{<h�a��0x~����e ~����r�}���epi�.0��nX�^��n����iH����r��e o�.7��a��]a����rȉ��k��.�a,zд�8i��Dn���	Pb�d�eg`k�l�n$s�t4����.����k����o���r�st�6���m���a�l�6�ai,kTp�t�uP�m�k`�r����ut�{ o<ul*,. ����.��Hale,��4���dk����,.��<xe�o�E�����oX����.��g���nI<�H�n���a�����a�a\��n�k,-���hHo�����(o�B0r�<pL��wa����Ta|n�w0�t�s4to@�mh���t����d��]�yX8aH��a�,�p����s���
L����deTgho�s�U�d(e���.�d�[��[��4s8�<aP��Hl�����`n�o�p�t(j74j�dj�rHj�����e�k��tl�>d<�,�a�e ��,��pH�GP��	�s\���gĽn�	aXexi�j�kloXpxt��F��Pi�!����dd�ln8�+���a�|mx%/
�mp�m�m�wX���a�o��L&���N�������'��tп�ؿ���aĿ4a,�e	8u��Ȳ$r�7�,e���:��DrH��LixA��@��dlT��le�r4FT�E��e���hrjk8n\ohr�s�m0����d,b���nb,�a(�y���n�]�ilg�lH�Z�cp�a s$h�,i@(�e�k��Ddt��Pn���<Qa�u��u{to�,|k�l�o�p�t�wP�L��L�;�v���r���oXQP?���k����ar�Edw��a�=C�:��t���i��$mPn�o�s��i����Ha�k�o�u�ef�nf]hi�d�tl��0�i�t�����i(w���nP�i\��l��������s���a���t�����st��a�����n�rL�a�k�n�s����,a�et	i�	k�	l�	o(r�s 
u@
wh
yT	��b�<|e���t����s8��@����s����Y��p�g����e 	n@	tDS�	0��n�����	r��	a���	apR�A��,	mh�4	ax�`����L	�\�����`	s|�h	nh�4�	<
���	s�	u�]�	e�	o�	�p��|�`�����	�|������	g<�����	e(
m`
nt
o�
p�
ruw�?a4��
l��
o8
s���H
lP
t ��
T���(
����X
o���8�l
i���
a�
m�
n��p���
p�Y�Y|�0���
a�����
d�
u#�.4��
m��f���
t�����
s@�+L�e��]DaXoxu,�C����<d4�����PkhmT��	�
+����pg�:|��pP��p��,�a�e$iTl�o�p�t
u�HL�p���m�s`j4�k��o���e����g���n�&0id-��8ghk\�Do�v�-�`s,8~8�to�7�|t���up�d���r(���i�o;��e;a$���P�l�n���e
i\�0��f��ou�����t<�
i��
���,
���]T
i`
u4
�����eeX�ft�h��
a(d@fHgtk�l�r�s���p
a�d�e�f`ikHllmxo0phs�t�uy�]�D��
dh�kvT��
nr�����	����dLg��� m8v,��4��L��o.H����Tnl�4\o<���hl�tfT�~����i�mD�Y�+؛���n��4�a��l�p�����@�dp��ae�o�s8&X��З.0lPrpv���t���(e$S&���<e���Df��|I��\n�Idat���tvi���|l�nD�4(�el����dd/r	P/��d`4�iH�m��iPlpr�s�tl�t�n���e(sx�4a<tP?�k�w<k���De������\s,	��dtEm�	0|t�	��t�	�ed�p���o$�o����ab
���a(e�l�o(rTtpea�dr��\
�td
iPndr��7�"<d|
�D-8/��T��\k�a�
��pn�
�xa�e�X�tX��s�
��t�n�oDrs ��h�p@nm��C	stZ�pr,�shZVT�s��a�\��nإ4o��x���4f��<y��Hw���n�������ll����tl��<�eس�t���s����g�h�k�v<�L�0�o��dJ&J���e���gl�]�aHn$s4w�H�,],it(\h�@v�,��Hut���Tg�`aX�b�l�n�o�prL\��i�t@YH���d�t�Y,�
��r��Y���deH����.�f����!,i�!$hP��	<s���DgX"Pn",
\a�f�i�k\lpm�n�o(tpw#t #,�e�!:�r$!���dp#�n�!��%0�$�n�#{�ao8r�(�#m r�#��#��r�)]h
t�#�,iHo��.d-��Df\+PoP/F$�hi�o��&�@���a�/���rp0�$�g$$�a��o�4�4��eP4���dD$�no0�m�5�kt5�rPAR�@��k�$HeLrp	�E��8n�E�@a�K���Re�$��Xr�$�da�e$L��$���e�� Xs��eXQ���k%���k,%�a�%�i�U�j�&���t@���i���(�$kl)�x)a�)��a���.�0p�+��8pxs(+Da�edo�p�sP���/pj�3a�3��k�wP3���eX1���l�r�s0tDvp��7ax7�n�5���o�7�im t(#�.=�s�8��8L�a"H9(f|I+�I<aP|�,?�Pf�>��Xexp�r�@��@�@���gX����wa���a�B��l�s�C�C{�r�C,�k�D+�lD,�p�:��Im�H�f`gxk�l�r�s,H��
�"-a�delg�ij@k�o�s!t"ul"yJ����l�tXs�K��pk@K0K���tt�Y�K���tH�aL��e�K�p�o��M��t�M���gtM��a$ePr�s�u�wPip$.XO��olN��rH\kt�0k Q�8a0Q�Dali����.�Q]`t��,.����x�xS��n��R�odSBlS��s�T��,�r��a�����r���e��
�V���l�T�in0rDWi t!Y -�tW��(g@tX9�\Mj�a�k��Lr�k,To�Y�`h�l�[i�[|a�o�ao���r�a��g�m�ns�bi��}$c���h�kd�����.dhm�c�o�dg�diD����� h|�(n�d�4i`oȇw���lsTfTm����i�xi�tmth|m�r�ut���.P����n�j���y̴C|����t�Ԅel�d<lTr�k,�a�ehiTjxk�l�mn ohp�t� u� y�x:���4l�Kȅ.���HeX�T`e���hs<lti�l�n�r�sTl���e�i����k�+(��l���vD>�8@��e4�s���4��w,a�l�4�e@n"&<m�,aHm��4g��+�m<Li(��,�in`e�m{lo�y�&�����e@+���k0.��p�k�n��i,n��uL1��n��d�n��e�u���.�1�k�n�uHo�4rHvonH
�k,u�vd5�@a0���o��Ti|o\e�i�oT�ZT�|t�:���s�o�.�o���t�u��0�s��� p/(p��k4p���a k mDp�a$ e� o� rXQ0@�dp�� hH iP ld rx s� t� u4A�xpa��e�p
�p��\ sd��Bp .ha��aa�E�� mDD��� o(�6�q4F� k�p�� eXH,�i��r0qЗ.�QXQ��� t�q��� kDq�!aD!eh!i�!r�!s"w�XLhW0!s�q��8!k�4ؠe�r��P!t|r��\!n�����.�C]t!f�s��!i�!ot���k�ta�t�!n�t,�!i�!o�!puL-n0ua<u�!a�a{���!n�z��!e�u��u"i@"lT"s�a�,"n v��4"iLv!XvL"kd"tpv��v��@���g �t"e,��"t\��"s(w�"e�"f#g4#i`#pl#rx#s�#t��{���"s�Pt���"g�����"a��,�#n��a\�+#e�#t��(#s�%�@#k���H#e��T#l����|;o���#tج+ȯ����#r0�md����#f�#gȷ�#a$e�$i�$l(%o�%r�%s'u8�f����#o�����#oh����#l($m<$ph$r��YL� $m� �4$i$�C0�<H$e��*P$tD���\$s������t$m��|$e�����$d�$n�Y�����$tX�i�$p���$a�$i%u��Uk�	H�+�$gP��$g�������%s��u4���%tL���%eP%nd%p$�a�<%nt���D%ix��X��\%s���p%il��x%e�9\��%u(�%e���%m��,	�%a�%e&i&j$&k�&l�&o�&p�&wT����%s,�%i`��h�&d������L&aX&it&ot�&�%��8&o��@&n 'tH��`&e�h&h.��i$��&u@404��&sP��&m�&nD�!<�x��&r`lK�&r�����&a����&ap����t<���'i����.�'a�'f(g\(k�(l�(n�(p�(r�(s()u����'a`)d�)e�+f -g4.i8/kh1l�1m2n�2o�4pl5r�5sX9t�:u4;y�+����t��'n\,Y<����'d�'h�'s�'t(v(wp��l]6@k,�'k��Y�����Y���0(r�t<�mf(eH�B$(e̚�D�<(sP�4D(e\���P(l�sp(tt���������x(b�(d�t�u�H.�t�����(a�(d��G����(a�8���(pT����a���(r���(e�zs؇�)v����)t\��)i�&���4)e����<)l(��H)y��T)r�)s�)t�)w��V���x)pX��)id��)l������)e����)e����)r��H\�\��)b,*eX*fl*i|*k�*l�*m�*n�*r+s�+t�+w�N�*k���� *nD*rx�T���<*s�����P*f������d*e�lp�i�*s��	��,�*tl ����*m�=YT�*m�}[l��*t����*g�*i8�j4�b�������*g+v@�� �h0+eP+pd+t�m��(+l�:����<+o��<D+it@0��<\+at+p@n ��mXZ|+m�A��+m��+ep��x��+a��v�����+�����Hvh,l�,o�,p�,r�����+a����+a,,eH,oP,ul�6�X,n4��
 ,e<,t�Xt|tX+X����}g`,s�'�.l:,�}e�Y�l,hp�x,m�,o(��.tZ�,m�[(�<����,k|�m�,u��]�,e�,u�
��p�k�\���,i4�^nb���,e��-r��-aT-l�-r�-s .u�o��.PVi<-n����H-y(+7�t�`-m�th-n�]t-e�-y�Bm���-pTv���-m�-sC���,�-o0���-h�-t}(}��-a�{<�-r�zH�.jT���.t`�.i�}l�],.dX.fd.g�.n�.t�������P.r(��p.a�e��Y����x.d�.k�.n�.s�.td������..8�(D��.a�����Y�Q�(���.l����.a/r������.a��/e�����/n�� /a���,/a�/e�/h�/idvj0lL0o�0s�0u1wH1y@�qL��l/l4��t/e���/s��$����/s��,�/a�.�X���/.d����/k�/s�/wt��/n\�Lx��^��/yFT���0t��]0a,0e��tD��(�e����40d �@0nd0p��P��\0n����p0n��,x0o�0p�0t9�X8���0s����0aDDa���0oD�sp��0r���0r@��0u��(L<.x����0�����1�t�]1e�@���(1r �01et���<1w�+��T1k��\1i�1u�&+�%��x1i|�+�1a�1fD���1ax`�d��1hp��1n�]&x,e�1lD,�1sT�T��1i�V���1w|��1i��2eT2k`2mh2n�2o�2r���Lg��42s4g#<2e�d�H2w(+�g�h��.�h��p2s��|2e�k4��2e3g$3kD3lX3m�3n�3o�3p4r$4sh4u�2ø������2w��3�����2�3����3g��Bd��4���3.43s`����
����<3id��T��P3.l3h�sl:P|��.����t3a�^
���3g�3m�3n�3s ��T��	D��3d�K����3a�3l4o��B
С0����3aD�B
L�m4a\��x����4i84sT4t̬������@4l��H4i\�d���`4d�e|4t|���.Te&|���4e(����4kd����4n\��4a�4i5l 5r@5s��L�7o�����4h�����4n��aX����4n����4a�����,i��5u��a�,5g �,45i��a����L5n�T5i��,`5e�=�8�ix5s��5s��5a�5e$6iP6j�6k|7l�7m8ol8p�8s�8t,9w�����5iX�5l�5t�,�e�	��6a���6d�6n<$H$06nT$��86a0<D6e�7�p\6aPd6i�6j�6k`{p6a�6e7i(7o�nyt�F�6ut�X%s
Dg�6e�6i<o�A �&�6e��6t�=��'�6s��6s����7t�7eD7oP7p�(k)<7r����.8@��	\7s,d7s|p7e�7o�7u���X�n���0.���7d.���7i-a���7l�7r�7t��7aD�L���7aH0F4��8m48n@8pp�w��d5� 8r(��(8vd��)n�	���L8n���T8e�<`8i�8u����.=��|8t$�����8n��8e�=6�8cD+�8l�8o�<�8e�8i9rxA����e0B��i�	�9g��9e ���&a0� 9axO\�n�9p���89a�9p��	H9a�9e�9h(:jT:o�:r�:s�:u�:w�_C	�.XSC�9eH�q$���9.[:�9.�Z���9d(���9n�9s���`�0�a,b���9lb,�9aXsmTr��:k,$7:kg�:a��\��4:n��<:i���H:px:r�&��,d:e	��l:r�qR(	�:s<	��:e�:o�	���k�	,�l
���oux�����k$
��:a�:i
+H
;g;i,;u�
�0+�
��;n$;tDC
�$h;d�;s�;t�����H;l|���P;e��\;o0�8��t;f�|;l�;a�;m�;u�.0�;a�.��;a�Y��i�iu�p� k���;e���;rI`��.��;nh<a�<g�<k�<l�<m�<n�<aT�c�<d�<e=i�=j�=k8>lt>m�>n,?o�?p8@sD@tdAwtAyt�47x<e�<t���i�x�����<l�|�
l(�<o�i��t�h,�8�e�<p=r�$(�<i`i��4=a<=eD=f`=gt=l�=n�=t�$ �� ilSa��L=i� ��T=eT����l=l�"��"�=i$!���=sT#}$��`&�=.�${�=e�=l>o$>u��t�'��=e4��>t�����=s�'�=rX�y+�l*>t�+�\+0>aT>eh>iT,L>m�&�,�`>p�.��>e�>o�Gu�>�$/iЗ.�>n�4+���L���>.�B���>�HB��/���>u�a+<1�>e�/�>i?o?yl1�o�1��2��jT2��?t2 ?eh?ft?l|?o�?u3�3�H?u(3P?g43��\?a�3�t5h�7��8<�?e�?i�?l@r�9�L9���?l�?n�?r�?t::�h:��:���?r�:m�:��?i�;�<�@a�662@v��� @p�=6,@o?<��.t@a�@e�@h�@iAo8ArXAu�?�P?��l@d�@s�S��@���=.��a�@l�@s�Y�xA���@i�B<
�@o,?L�bLb���@rPC�@e�C���H.XC��@k���DD��AiAs�E� Fk�E��$Ak�E�,AaHAe4Fm|G�@G��PAk<KH�}�MlAd�PfO���Ag�As�N�Aa�Ae@Bi�Bj�Bk�Bo8Cr�CsEt0EulEw�Ey�S��a�S�AtU�Bi Br�\W���Ae�'
0\BaX\��Ba��c��,Bl�c��4BeXBk�d
�d��PBeФ�ܤdBkl���lB-����xBeg��Bi�����{�Bit����Bslg��ByHj��Bp�k���Bo�h���BnCpCr�l�8l��Bvx��l��CdH�Uho��$Cm0o�,CadCe�Ci�

�q��LCepCo�pTCk؄
|Cr�
��Cdr]�Cm_���.�^�CnT,�CyLt,�Cf�CkhDltDm�Do�Dp�Dt&
Do�u�Ct�u{�Ca(DoHDr���������Ds�uDu�u	�u]4Dev�<Di�}D���TDpP�\Da v���a�4"
P4���Dg�v�DnH8R�v���Da�v�Da�Dr�v&�v��kw�Ded�L�p�D.0o��De�w��Drxy��y@Es\y��Et$y��$EiTEn�a���@Ee8���HEil{i�tn�z�`Ei|Eyx�i�|(
�|�Et�{���Es�7(}��
HI-�E.FgFiLFk�Fl�FmGnhGp�Gr�Hs It@Iu�ua�u]�EeЁ4�EiX����Er؇����Ft��ؠe���(Ft|�4Fn|���@Fi\Fl���l;tdFn���lFel�xFi����Fe�Fo�����Fo��+�����Fn���Fo�Fu�am��Fg���Ft���Fi,�i8���Ga0Gs��a���Ga��$Gl�]m��<GdP�DGnd���PGaԏ\Ga�i�Ga�Gl0���tGa�GepHsl���Gl�\���+����GtĐ�Ge�Gg$HkDHl��.��+�����G.�Gt��:Hhbs��.
��,Htp���HsT7��0Hm ���8He���d���PHrt�<XHa|�dHp,�i�Hp@�|Ha�Hj�Hk�Ht��&�9e����.�����Hg$<�Hix���t\���Hofxd����Hc4���Ha��+h��Il0Im��Ie8IrXZ��\�{\�5
 ��,���PIaxt#XIaș��dIw����pIk�Il@�|Ia�IrJu0��<��IsH����Ie̥�L��Ie4����Ir�����Iò�9.���Is�����Ie�:
���Jsx���JlHJr �$JaXJe�Ki��i ��@���PJe�Jg�JiKn\Kr�KsH`k`xJn�i�Ja���Ja�Jo�p��h�dD�i�Je���
�Je����Jd�����Jn�Jsx�K(��4��Je���Ke<Ks�>
�aKs��$Ke\�,0Kt�6D
�HKs���PKe�8yX8��hKnܲpKa��|Kp���Kt�����KsT����Kkܴ���Ke$����.0���Ke<��KtH����Ks�����Ky��D���Lo`���LnDLs �wT�� ��0LeP�8LtL�m����PLa�XLk�Lr������.����tLa�����Lr�LeMl,Mr�2iP'���LoT����Lr�&�%���Li�j��Lu4�7�Llh����Lf���Ma�����Me���� Md`MetMh��a��@Me ���HMo�TMlH���lMe�Mg�Mi�Mk4Nl,OnpOp�Or8Ps`_�������Me(����,�Mk�MpNt�����Ms(�4Ne@��H����Mk��T�ph���NaPNgdNlpNs���Na�Ne�Nf�NoOsL�7Hutd��t��\Nb�P3.���|Ni�Nnd����Nt�
+�Ne
���NltX�I
��NpX���No|�@�*���Nll*�Ni�#{�Nu",Ok�H�g,H�� OaHOt�s}TOeDq�<Or�s���l��\OeȷdOr�����'a�Of����|Oa�Oo$Ps0Py<����'d������f4����Of�Og�Os�Ow�u�����+��Oe!m�&���Ot�Pn`{Pe�Pk$���d�.�PaRbRd$Se�Tf�UgxVi�WkXXlYm<Yn\Yo�[p�[r�\s^t4^u�^v�^w�^yZ	�Pa�Pd�Pg<QhTQi�Qk�Qn�Qp�QrM�	�]�Pb�Y�Pn�Pr`�f��k�Qimf��O
���Qh��Qn���$Qe�p0Qr�-�pHQr$�ap$.��`Qe4t,lQtTr��xQsl�e����Qj�Qr�Qs����QtЌ}HuV
(�,�Qtt��t�x�����QtRuD�X�.���Qq�,���8ReSw��,�7$Rn0���,RiHRs���xRa�Re�Rk�RtSu&�<idRe��lRk8@[
4�Rs���Rs0��=�RiD�{�Re�lA���Rr��<�Ro�Rr,A8A��Ra4�Z�J�RtXHSs��T�LSelSi�Sl�Tr�Ts�Ttl�a
$���DSn\Ss$�g
��\��dSe��p
xSp�Ss�
���Sa�SeToTsHTuPTy�H[m��Sed���i���St���SnT�Vxd��Snx�Sop�h,Tm<Tpx;R;��(Tot$0To�ii\Ts�*�X�Ta�6{dTr�Tu�5pTkP'���Ts�Tt�)p��Ti�*�:�	X9��Ts0<��TtD@^�A�8�t�A�Te@P,Ue�Ui�Ur�S��SUs�S��Ua$S UlTUrtUt�+��)@Ut�T��HUe8���T�`Ut�ThUe�V�V�Uk�V���Ue�UsxV�Ue@Wm
LW�Um`\�mi�[]�Ue`_ Ve4Vh@VllVo�i�i���Ur�iH�Ue�hVp�cVs�i�k,,Va�m����iPVy�oi�p:
�pXVsTp`Vd��Vd�Ve�Wl�Wn�Ws�Wt���Vj\��<����V-���Vi�7�,�Vt����Vs��Vk�����Ve(WlLWspWt����Wo�Wf���Wa�.��;a���4Wa�@Wm\Wo̠e���Dl��dWoPN+�|Wg����Wa�Wmt�e���Wa��L����Wr��+T���Wo��Wt������Wz���,XoT�8Xe��,Xt��Xs�� XmE��+lXoh���@Xp���LXatXe�Xi��H���Xe�Xs�Xt�Xx�q
�Xs�	��	�Xe$+Aj����.(w���Xr�K���Xo���XeYt�G0���Xk|w
(+�0Yo�i0@��Yo�>��$Yn,HPYi�c�aHYs(w�Yf�Yk�Yl�YmZntZo�Zp�Zr\[sh[u�[x����Yf����e@�,�Yt����Ys������Yk��e$���Ys4�}
Zk ZtD���Ys�����Yd<Zt�R'�S,Ze�S�`T�	d��4ZbPZeXZl����g"P��
8�`Zlh�hZr��
�Zo�Zs�i:L9���Znx��Ze�,�ZpȤ�x��Ze�����Zd�Zg[o([u��`�,��i4����Zs���[k [mT�C4�HDS6LS��0[n$S8[eT,D[e��P[f|����
tЈm܈�t[l<���|[l���[i�����[h�[s�E��a?�[r��[t��i��,�[aȷ�[s��]4\a`\e�\i�\o�\u�\y��'��\a��7\a����(\gH\mP\sH�������X\nx\sH�. �p\k(��l�]�\g�\t�������\o����\o(�04����\d|
��
���\aH
���\g$��,(]aL]el]k�]l�]m�]p�]t47�<th]g@]n�aY���8]k,l�k�(��'X]n�${`]od-��x]g\+�]o�.H�]a�Ge�Gi�.f�8H�]l�]r�:B|<�<��]oXQP?���]k?�]alJi�N� ^r�p�,^dlOk0o�^eq�(}T^cl^r�^s�^u�\(L^hđ�Ir0���`^o���@�x^op��
3.\��^r@�,�^aH�+�����^l ��`�_n_s,_t@s-\��^d�[�^u��,�^oD���_gP��
�����p��� _aT_jl���,.����<_er�H_i���-d�.�_aLcbdcd�ce�jfkg�khdli�mk�ml�om�onTpo��ptrhvs�}tu�v�^w�yZ`a�`d�`f�`gakHal�anbpbr�bsct@cy<\�
�Y�_i<`mH`n�`tE�
E`e�],$`tL]0`s�]�.l`b�`d�`l�`w���M,d`i��m�]�x`h�^"�^"�b�
@��f��`d�i��`o�jm�k��`r�mB�`e�mF�`p�#�ds�aaTr���`l�s+l]�
@k,ak4�7 as�t,af\�p�t��8aadae�ao���u\ai����.�pagy|ao\\a�]�aa����ar����ag�ak�asTek	|���ae�mp$.�{�ae؈,�ak�xt��bp�����a0bbl�m8bsdbt\��
̕Pbt�0\bg��<Dbi|t����i���dm�pbr�|ba�bm�bo�bp�bt�/�	�/���bk�4�bo,�i�bl�3+h��H�e4F�$���be����brL�}��u��cr����
.��cr���(co����4cn�,Xcy�<s����ce�cu�cw�k�3xcfl�i�coP����co0����cr�����eg��ci���cj|����ct�H�ca8�iT�Hdd|de�dfeglSi�ekfl�fm�fnPgohgp�gr�hs0jtHju�jv�jw�jy�d����\di ������Tds���	l�hdh$���pdn�dt�Qa.�����dl��da���dt���dsx����d��e�XC�?�di@���dfel���
���d��.4eeTeu�H�� ep��w(es|�Lpf@el��Hei�;��;��`er��heg$�tei0�em���p .���ek�	]�eiD���el�es�ey�eh
��m H��ekp
�eg�
���ea,fdLfo�fs@����fd� fa<fo�i�+xDfl\fo���Sn��|�.(Hhfeh,tft\��p$.��fu�:���faT�fi�fo�� x��fpd���fp$��fd4ge@ggP}���frgu,�a���grHXL8X gt(gsl34#�
�"Hgi�#+|gg�#\ga�ge�go�#FP$\$*�gth$���gs$���gr���
P%���gs(%���gnP'��ga(heLhilho�hsx�u�(C'�gphr�(�Цd�+F�+he�)ht���ȏn,.}4he4.]@hd��0�2��Xhf�2`he�hg303|hgl8<�o�5�hp�hw,9�<�0<�ha�hfii8ijDik��lpim�in�ip�itjw$StT,�he�=+=�in�om��it,$7 ig�=�,ia�=�diaX� h��Pie�$Xidt>��0t�>xie�?H�ie\���?��ib�is�?���ir�isH:�
�%�
P:�i.D@H�iaji�@�t@���is�Ca�@�jgdA�,$+�B�ja�A$jj��wF��E��@jiXjm�F�P3.�"�
|���djs����lje �xjo�I�jw$K�
0K�je<K,�jtK���jsXJ���jn4J�jeL@P��jlXX�ki�X�`_,ka�ke�_�Lkkxks�kt���t�,kext#4kea��@kw�b��b�Xke�b�`kr�blkt���c�kjj��iH�ki�h�kt�c�ks�,�ko�psXlu|�G8����krȉ���kelg$ll$���Ik0��
lo<�lhԋ+0lf�G��a���8le���@lo,���Llm��ld�le�lf�lm mnPmp�ms�mt������ln���le�Qa���lr�����la�����lf�Pi�����laد����lp4��@�maP��maL���mg8mr��
�0miԼ�hmiL��Dms$ ��Jth�\me�����t��tmn��me��hr�ms�w��,�mw����maL�|&a�
����msh����mn ns����mahne�niooxou�oy�i�na@neTnk`no�a�8nl�$�h��Lna�be�H�.�nnd��
��xn.a��nn��no��nd�nf�ng�nk�nm`>pos����0r`���nu����&X�
L�
X��ho-ob8od@oeHofPomXop`otXx��X��(w
��w
\��
�'p�%��pos�ou���(H�.�on�*�(+��oa�/�0�oi�/��ou�/�ok�+���os,H�peHpi�U�
�U�om pt�TpeV��0�a��(po�a��0pk�a<pe(w�pd�pe�qf�qg�qlrm$rntro�rp�rr�rsTsu�swty�q�4ya@y�pkLy�pa�piXx�ps��
��pdP|�{���pfqk(qs<qt�x���pi�~}qu~��qr��3.X��h�4qaXqh,����.��,Lqe��&���dqyN��lqr4N��xqa���ql�����q�����B,��qie`����qo����qf�qi\��l��qeT�P���qg$��raȔ7	����rgLrsXrt8�a�+8ro0�,@rtd�x�����`rlh�hrg�rr8�`�2�rl�roС+����ra�(����]�rg�����ri\�����rlsp,ss8st�L7�9���rrTIisl���seD>�̬6$seج�̱�`sa�si�ss|���@sd����X�.�����lsf���tse�sn$��,����si�sk<�����.���,.��]�set���si���sr����s. ����sa��]4ta�te�ui�uo8vuTvy���
����,tbLtkdtm�ts\���3.<�i|tdH��Xta�toXg $,�tto�|e����ta�te�e��i���	��td�te�tn$usLutXuuH���tk�tp��D���us��`�,ut�� �ue8up@uu���0r���j<���:�,`utP���hus���tuk�utl�]�ue�ug�up�ut���Mj(��X�+���3.4��4����uk�unvo$vw��������uivk�3m������ve�
�H
��0vgHvi�
����m$���k�mCs��va�we\xf�xit�jyklzl�zm�zn�zo�zp�{t�}u�}wh�
�va�vdwf]gwk wl@wndwpxwr�sL]���vm��ve�f��.,f�����sx�
���wa0we�u�
�
���8waPwt�����pwe|�Xwp�0��,8�e�wf�wi�wkxl(xn<xsHxw@�fT#D�.�we����wsX(�	t����wo�ws�wt�Pt�"$(��xexi�(
��� xe�.44xt0Txe�+T�|xe��4LS��hxr$Spxe &�xe��xd�xm�xn�F Ck!��xa!��xp�!�<�e$!���xd�xi�xlysP�F�"��"P�$,@ya�ye�yi�ylzodzr��;�$,yl�$4ya\ybdydtyp�yt�d�h��	��

�%Blya�	&�yt$��
`&�yd�yr�yu�&�d�H�����y.�yd�����yr��P '3�yn��'����'zf��l��m,zoHzrTzt\zu)K8zr�(W���	����@zrX)�	����)�\+4|za�zo�+��-�d-���zp�.4�0��0�zl�/�ze24t?l�zp�?u��a�6��zo62�zr�8�{e({iP{o�{rL9����l {r��s:+�:�<{lT�[���4{l;�;��H{eh{r�;^�;��`{e�{t��e�;�x{s�<�<��{u?<�{a |e�|i�|o(}rH\W|?��{kP?���{a�{b|k|l|r�O��{i��0XQ��?�h@�PA<
�@��|k@|lT|r�|smxA��8|g@B(LB��L|al|it|r||s�]~�^m�B��B

�B�|eXCf�|g�C�C���|t�D��|l�|r�|tDD���|e}n�it�D���|e�D�h��
xEF|�E��}d$Ak@}nH}p�E�}aX}il}o�}upk8p5
Xr�TF]P}bxF��F��d}e�}o�F��F�|}n
�G��}utIfXH�}i<K4�}e4
F�L���}w<L���}r�N� ~e�~u��sl��}h$����}n|U��~eU��~eX~lx~r�~s���H�8~i�X@~gY��L~a�(�0\d~sX\��l~a��e�~oL^i�~e�~l|��2���~rD3���.`�`cf�~m�`i�fe�`��~eh���y���~i$y���~r(}a eXi}���4lp
t�
��,a���X�.���@o����Ld�r�t�����la�tl؇@�, ��P��t`����s\��d�.�aH�e<�ih�l|�m��nȉo��r�t,�u\�v�^w��yZ���E.0�lȀm0�n�r��w�yv��t��(�f\�mh�ox�s�,����H�g�xP�aye	�y.(y,p�k��t�E��$H��r8CmC���n}����g$}���a8{���p�s�D��a�~{؀r�~,�k�D`�a����n���aP�j\�rd�u����d��gԁs�����.Ă�D�a4�}�Ti\��L�l�i���t�l��o��s܅a����r�^m
4���l��w|aT	�.�a؈,ȁk�m��. ���p���o�����d,�s|�t�8nl̕ �eH�o\�p�3a�@�l��aȖ<T�aT�`���h�at��p�a��j��ol�i��e�h��0Hm��i����k�s(��0����n<��ȂaH�Ԃh`��
���i0��l�����e|��.�����sIJ��$�u\�0�lT���
<�b��e$�g��i�kL�l`�n��r8�s��t��uԇv�w��+$�����l̃r�s���8+��.�8<��e����t�����sH���<�؃.$���e��&���a�r���g\��rd����oX�r��sD�m����8�k4t��@�nt�]L�a@��H���d�k(��l�y��x�p����.̄s\����d�A7x����m�<��e�6��t�	iD��؄o�s �w8�<D��n���a�,�a@
�`�@�e���(�gp
4�n�
��@�a�$��X�dx�s i�,p�oP'���a�eD�fX�i|�o�s�u �y'^�s.̅k�l��nP(�\(��ąl�H�(؅.�(���d�(��)�(�g4�k<�vd�����a�����p|*!�I��+�P�s���4.^h�bt�d���g.,.n�2��d��l��nȆs܆u��w����3����d��t�gm(����m$4�Ԇs84�h4+ � �e,�h�5��c�:^0�4;�k�?\�e0<,�ph�t��&�?�H�u�?��P�rD@�L^T	 B��p�o�A�x�r�A��ePG�\G����ahG��a�E����ṗs�H��I��r�Ii0K�<K,�tK���sXJ����n4J�eܝ+p�i��o���� �r���0�e܈l�n�p0�rH�s\�v��À.��.4.]d�n�����!|�i��y����n���D���������ad��rT��Ĉb���Јlس}
�tP���sL����g����L���e���(�lH��T�a�<�pX8�@���-��t�i��(+���a�+�,H���0�a�x��l�x����sXx��e(w����d8�e��f<�gt�i��kԋl8�n��p�r �s4�t\�u���0>��}��i�}�a~���ax4y{��(�k\�s؃a�<H�i�P�pl�t�e���.�O��t�p������o����r���������iЊa�m������a�u�P�$}��.8{�؊p@�s؇��s����t ��i��a�k���k�k�
$�o,�0�h�������H�r��4P�e�\�w��h�sLt��:k������k,�]��a�����r�����.T���r���a���ȋa��e�f�i,�o�	���s`����l��nh�pag8� �o�����.`�d$��0���H�ap�gD��P�a��rl��a��x�o�mȡ���dС����n�����a����lԌs�4�3a�,Ȍm����|;o�s��tx�������j�� �l�
�o���hD�i��,�a��e̴�@�a�
jl�o|���H�t��wX��Dl|�mH����h�����e؍i��a��<��o ���t���s���sl�]̍e��s���T��x��t�N̼eD�o�����.X����o(}�� �gp�i��k(�l��m��tL��x��L�a���T�a��u����`�d��sȎt�a����i��$cf����e��0Ԏe@����i؇���jl�8�ap���܎n���
�u|����h�!e@��a@�eL�l`�o����p��s(�a4���8�k��(Ae��ap���X�r�@ap$.�$Hl�e��,x�t�����e����ȍut?�.,?���s����ďs�^�
ė��؏sh���r���e�j,�sg�H_i�vaH��o8�, �p�tT�"���<�l���D�l@�P�iP� T��h�a�u��p�mt]|�o���r`�����g��s���P���k�tS@$@��Ȑg8�iАn ��ܐaZ��a�m�n0�s���a��b�c�d��e��f�gP�h\�i��k��l��mL�nĻoL�p�r�s�t��uT�w���t_0�^,h�o��p��t�]p�s�Y��n�_,���edp�X�P4����.D���n�~,̑o8{�ؑs؇&��p�����o������oh���o�$�kL�p`�t|�+h��D�o��+���X�ap�r$�n�i�����x�f��g���a��o�k���4�.������s�����ut���З.���̒yh�ؒk���
 �at�d��e��i�jجo��r�s(�u���\��D�g�u���3eX�],�o���8�rHut��0����T�a����\�r@��h�e0����s��it�u����n�L`ܧ��������̓a��8��ēs0�i�n���ؓaD�����,�6	4�e`�it�k��l�n�o�p��t�u���,�t�h��@�eP���H�o�T�n�$m��{l�a��e��r��m�&����m����r �d�^Дa���+���s�+��Ĕg�10,.�1�ܔe���y,�4��� �a4�eH�i`�rX8���p.�9a����,�k�:a��@�eX�l��+8��8�ip�y�<����xol����x�a`�<��a��e�i�o�r�����eȕkԕl�XsPA��s�����elJ��D+DD���e�q	4F��k(��eXH��r|��4�u\��س����<�s����D�g����P�nԖo��\�i�r��wT���ܤ-l�d�e��fD�g�k�l��nx�pܝr�s��t��u��wX�yL�Ð���P�eĻȖn��� �l����o��4$��e$�� �n`���wd���r����$�a��0�al�lx���<��8��D�X�s���`�y��tx�r�t����a4���l@�����aȗiԗr��s �u�V+����e�[�X��4���ܗl�H�a(�,�t؇��Fg�����t4^�i�m+�m��,�al�od���4�l��r��s�&���X�ao��`�kt�7�u��x�mh�]��e��tt�]��i�u�t������t�Md�Ęn�p̘y,ؘsD���a�e`�i��l��n��o�rl�w��З.4�rL�s�X���,�v8@60�e8�@�s��Ct	X�eT�R�iT���l�a�	]x�a��ml�]��iD��`
����d�	��nܙp�&���șe�
Йl�s�v�L��e,&`�m(�v(]�eH�iJ�I �u�}m�u�4�t��]<�gX�X�Re,���T�r@
]`�a��e��i��oКy���������s������eT
!���D����sh���.x���Ěd�
�p
ܚa�f�
���a4�eX�ox�s�j�@��o�� �e��(�n��
�.�@�rxL�oPa\d�nh,l�a��o�����n���t�a��n$����a�g�k,�oD�sH�u�����ԛr�ܛol��l�fa�f��o@��r���tvi� �l<�ojX��t�a��o��pĜt4�um���`�k�h�k�6� ��r��u8��7���tlo��hKnh��a���a��e �o V���؜mP ��e$ ���l�t���� �� �� ���o\��� ,�u0�!"@�r����.�T�g(%��`�o�#l�o��r��Rt�i������o�%���o�+Y���a�'����m��s'��fP'��Нa �ep�n��o�s�'L�*d�*��t�*���g�)�nD�t B�+�0�r�+8�e��7���P�f2X�f2�d�e4E���rX3�|�t�2��m��n�3!��i��y��0��$LL�Ğe,9�̞e�5؞wh��<�p<��m0<�aL�e��fȟk8�l��m̠oh�p̡t<�uH�wH�+�<�D�.h�n@:�4e���\�k��t�!s!�x�s�T��4zw$S��rT,��e4%���.�$��f�={��a�o�r�(�>ܟnP7pt)��)���a�)���a�y8*�	�+��wg0>� �a8>,�a\�ep�ix�u�Fh�eT>P�pxh>;.���i�}��9����b�>i��ut>���e��oh@t�i�>����o,?��e��n�o4�p�2"
�. ?���sP4��$�d�7�5�p|?h�r��@�e62(�n�&l�&�8XH�eX8��P�n�?<\�a��e��l�?�?��|�rH�I	�?m��g�?���i|?�T�lt@����a�isD@<��a�e�i�@���e��h��l�nDW} B�B���t�Ca�@��n�I�tI��(�lXH0�idA��Os\OP�sxOX�n�r�A��d�a��l�At�a̢e�oD�r`�s��u��w��yXd��a�Q����b�A�З.�r�:s�_4ܢs B���t��,��e�k���s�B���mLC�TC�� �eP�odC(�k8C�8�epC�hDi�C,X�l�,.$E��l�e0E��x�i`E�lE���i�E��E����s�E
�b�d��g$�iH�lP�mx�s��u��v��w�}�
H0F��r�E��E]�e�E4�iFf<�nP���.@���0�g�F��F&��V�HX�t�H�`�o�Hl�k@I@��
�0�K���ee4J��-��i�iX�k�]��i����Ĥl\�Фk�L��L���rܧ������D���L��N�O{�uO, �k4N��,�s�M8�lp�n��s�r��iHO�X�j,O��d�t?�8P|�t$S�li@P����e��iХlإr�uxV�nmȥn�W�XX�[4��f4^�iH`�`�n�_��aD�pX�r`_���a��eԦi�l�o`�r��s�ub&4��d�tb��L�e��d.�d�l�f�ct�f��n��t�H���a�f���a0j��e<�P m����sdlȦn�m�o@o�o���e�rB�tTp�p$�r��r���g@�o���s���4�o$u��tL�st]T�e|�o��yv��u��t�oTvf�vHhv��aЧi��kX�mx�o��p��ty�x����s�xħn��styBܧs@y�py,�a(�e4�o<�u@���&���d�y�nz�l*t�1a$/�D�e�z4L�e|��P4��d�a�z4l�n��ot5�;H{���iP{����e�z���o�{<�oi�o�}�|�̨i�|��Ԩe���'�.l����a ��r�e�}a�}$�u},�h,���8�a�D�u�P�a��d�eX���l�o��x�r��t������eةk�sn�o�r<�s��wh�Aj���]ĩih��̩l������t�����a����n��]T`�e�<i�tP�$�k��,0�at�i��jԪk�p<�tt�w�� �Џe���h�d��n�����vĜ��e��e�����.�$����k�<��u`]m(�{ȪoиH8�rX8���a�d�r(���a�8�8���Re�Ca���(�k��0�iT�u,������L�u�LaT���`�l@�4h�eP�+,�����n��]��a��e��y��fx����
�aD�bh�dܬe�f�g4�iT�l��m��o�sx�t{uh�PNg�t(��4����o�o�fx�$�t���,�sd8�e���h�t���P�g�\�a��s��t`4�3aH|�m��tr`�<��jd����+m@����e�$Ĭa��j�̬t�
+�i
���l�Xtljh�$����e����d�(�n!+d��@�wh�H�od�u�%gH0���l�t�t�a��o�����r�@�
��e�����pXȭs����kH�7�"ԭp$!��ܭsp#�n",�i4�oH�pȤ�6��e�6���dD$(�r<&t$@�r`���.xOT�r%��`�a,%l�a(+���a��e@�ih�mt�oدp�u4���+�TgX1�Юk��l�n(�sD�js03�83ܮrD3�aP3���a�!�
�5��s�4���t�7� t�B������:��;+�;��H�r�>��P�g�=�\�i�>���p��t�@&�A��A���uB,��e�A��h��P���k����i�B�̯l�E��mtF��F��a�F���aI�
$I���d�H�r�H$�a�f��gxk��r,H��0�a��d�e�fP�gP�i�kD�n��o�r<�s�t��u��vJf��K����gL�X�M����a�stM���a�e �i@�o��s��uıw(N�8�+DO�sXO���alN���r@�/lP���vP|a����,�f�P��4�e\�o���l�m��P�gt�r�om����,y8Sf|�u S��eR��l��wHT��Tg@�+����i�B� �iVбj�Uܱt�T�e�r'�tW���a�Y�a�`�� �s`(�rZ4�a��n�Y�
@�a��eȲh�i8�l\�o��p��rسsD�u�a+<[	 [���r0[��etZ��td�aȷ��a�k,��p��:p[��Բd��f�sX[ܲn�6�r�[��[��[,�t\�xhn$�b�[,�e����r2D�el�l�[L�pt�u�rO\!\�$\|�u0\,��e<\���s8v!p\]��u�v�wg�\���a�p�\ȳa$�eh�f��i��k�lH�o��p��t��u �w(]��.�	�,.4]�e<�k(�l]�4�rL�u 
&�]��]T�r�]�\�e��@�]�t�.��j�]|�n�d7l%^��l^,��a�uܴ�l��x�ȴr����д�`*7�^�d���_��p�^4�e(�y4_�<_ �m�_��_��4�ap_4<�fX�m�_�\.h�m�_��_<�_�p�l�_�x�i�B�Y���e�`����l``����l8`<��eԵi���`f̵nLa�Taf�e�l`a�i�I�`��pa���l|a4�e|���.�a��,�l�a8�i�a�|�g��k��n��shb�tbh�a�b��p�a�biHc�$c����f���c��a�c��i��!���Ķs�e��̶tTeضr�d��e�l\�n��oԷr�s$�wf]xa0�e\���.��$�r�WtW��<�f0fD�rf#P�el�u�u��lL�e	t�eT�|�iTf��g��l��mh��tvi��I<�gP�g t]��n�f�ȷi�o�f��f���l$g��f,��tض��.����t4g#�iHKI�g0�m�g8�ah�e�:�W�T�i0h\�m��sHXP��eThx�tXXȔ��i����gth��nܸsHk�Pkf��a\kĸahkиk�k|�a�i(��l�]��g��(%���udl�o<l �pl�t�k,,�e��g��kȹl�m(�oH�px�t�A��d�e|�s�CT�l�m�	ܧs�m��p�m{��a��i<�aD���n,n���7�/��йt�/��عl�o�n��o�@kh@�r$� Ho���do�n�9��o��4�k|o<�el�r\�<8�]X�e�o�`�iDp+��i��oԺwXC���td��8D���.xEaDD����n`{�(�i��s�p\Ⱥi�q����pDq��a�e8�hX�ux�wr�rs$�t�q���s$raLb���p.Dr,,�ePugXu��D�tdu��L�i��ax���d�f�z�l�y����u��.�u��eX�<�����o�v,��l(w�p�r�s�#t�����ܻr��a4���r4���b�����u��0�kD�p�#t���<�l�'���+ȷ�	x�a��e��i��l��oȼrԼs�u��yd���#gh���������o��iL����s�e��p��tl��\Oe��,\�.��k��m��i��p��{�o<���ip����8�cP�k|�o��u��wh�L^h���d�l���D�ep�L(���\�o�����r4���p�p�����H
����sx��4
��a4�,�ih��g<�k\�nh�r��s��v�ĽaT�c��e�f�h�i�jX�kĿl�m�n,�oH�pT�t��uP�wQB
Lt���4�sPz�	H�s���P�g��t�g��,�|�p��
�f,��i��tľu�\�F�E����m�a��,.�aоe���ܾi,�n$!���$t�v��n$�H�.8�aL�oD�+,$70�n��+t$��D�e�$���.p�a��u����$m��a��n�$��$��f�%+x��
��������*+l*��i\+4ؿa�+��+��пa�g�+��.4�i�/�P/���t�/4�2<
T2���s2 �e��n@�r�6��8�\�.?���.Ĝh��o��yD�\l,l�o�E}t�hDD����p�|�H7��ntIf�kXH��i�m�I�@J�LJ��aXJ��a�N�(�a`�bh�e\�it�o��r�s�u<�wH�y��z���O�8�gT�t�P�T� T��@�s,TH�i`T�U���d�g�k�m(�n4�rP�t8���L�l��#��a\U��w n��m����s�m����a�V���l����p$.t�4�ehW�t@Z�LZ���sXZ�a[��Z�� �hX\i@�u�_��A(haH�e�c�h�n�ei4ms�h���f��p��r����j����aȡyС����n�����a8l���l�����e�����w�l����o0o��e�y�p�@t�Lt,8�c@�ep�j��o��p��t��uu+�t8�rĚt��7h�P�dt$��X�o�u<d�o�vX�v|�o4:�:����k�D����r�v��e�v����kw���e��o�l��l����iDw����r����ou$y�$�s$z�0z�a<z�al{���tn�z�0�i�{�t�sh�m��iT�k��4\�k�|h�oD�kT���nh�����a�����a�|���l(}��m���a��e��i�u@�i\�i��i��e���p��\��r���u�����, �k����(�s@���4�lx�m��s �@�eܮ�
.Į�`�sЮl�o�����p��t;���rܲ��o����m��n\�s(+�K��p(t�H��k,H����a�o�s0�uth^Dp+�k,�ttM�4v���d�u$�n�+U�+��<�m\+D�a�P�ll�t?���a\�e��iP�ot�u�Yp��rZ����a��c�gD�n��p��r��s �z���\�{��r�a��k`����sf���q�e�k��a|ki�� ����i,�, �a���,�h���8�gt�k��� f�T�pf#\�a|��h�nt��X�l4�B������e��iT�[�����m��p�tP�m�8X��tX8����nh����a�S�������s�����a���8�eX��zP'���ai����,�rl�}D��D�nx�rT���L�k��r��s�tL�u(}��a��&����iD����s�3��2��pP'����oD�L<.��������>����>��i0<��n�tD@�8C��A�r8�s,Ha�u�$�n�C,,�jF+�E��D�g������X�n����`�k��n��o�s��u���l�e�n0�r��a����n������a��o0ws �a,���l����"��n��p`#
���<�pD�tȟ{��a�iT�o��kh�l��o��p��t��u�%�&�P7��L�pd-�8�`�ox�ux�at?�̠���l��n��a\�ah�<��a8A�̡<��r0�a��k<���i�Iy��iD��L�2��e\���pP���oL����g\��(�p\��w<�s(w��D�b��e��n�s<�u����,ke ��l�e~��x�w{����k��n��r�'$�,��kx�����s2	|�����nȔ�	������g�P�,���ah���f���a�����t̉�i4�$�u|���0�k��@�,H�p؇�P�s����\�t(}��h�i��k��n���x�m��]��e�����i|�����r8����kȍ�ԍ���e����r����-d�.L�aH�b��d��eİf��g��h|�ih�k�l(�ml�n��oX�p��r��st�t<�u��v��wt�y���Z
��ah�d��f��g@�kl�l��m�n��p�r��sH�t<�y�\�
�Y��l��n�r�]"�]��d��s�`wdp����.,���e�^,��t�ad.�a��e�t`����s$�t�a��a<�e$b	4�i<�jDb0gh�0t���D�a�f�L�l��sf�X�e��v�f0�fx�l�h_	�iG�i��e�i����e��o��r��s{��,�f�j��e�j�@k_	�k��e�g(�o0�r�l�Ll�mm� �e�l(
xmm�mB4teTr��8�s�w��wL�a|�l��o��w�t��T�k��sxe<x(
��o0x��x}ze(y,��p��t�$8{��p�s}a}����r$}���a��lt}�H}���e�~�B����aL�eX�kt�o��s��tT�L`���8�ol�@�r|��
�i��i��`�t�h�n؈,��k��tdpap$.,���e���
�Qj��o�r�k�p�.������m�������k�st����a$�iT�ll�o|�r��s��t��	����(FtP���n4�t`�
H�aX���<�k`�t|��D�a����.��8����t�iT������s����i��,��e��p��t<x���r����k(���eU�����ed��	������.0�aD�bP�mh�o��s��t8����(�k ��\�<�eD��3.0��t�r�\�o�r-��̕|�tt����i��ol���p��g��l�m�t��̛��i؛����a��4��a�Y��.���a�4��a$����a(�o����rD���V�0���0�g|�t��8�e��h��o��r��s�u������h�s��p�e�a,b����r��,��a��u,����p.(��4�����o8t��.@�����kL����y����w��\y��,�lP����ilt�Tr���u��H$�k����0�a�,x�e��o�a�]X�iĭ��`�w ���l�kg�tMi̴���j������t�����u���ȬeT�$�d0�e�f0�i��k��l��m�n��pX�r8�sh�t��u0�y��ä�0t����o���l���e$�P�lDSnp�p��r��L����H�a��<�,\�t��d�s�0��|�o��t�����s�8a�8<��eN���rN����a�����a����lx�����������@�����f�a���o���rX�s\�� �dd�kl�s`��T�D�r�6L�p`�$�t�_	D��x�st!p
��k��n��s�
����a�e4�i\�mp�o��sl�d�m�����e�=68�i���s�}n����e�mT	ML@�r����b`�(�n��Ta`��H�glP�a� !xh�u����|�e�����d���nh,��oD,��aD����fT��a��m�=�$Ia�H��r��a$����a@�dd�ep�jx�m��n��o��sDaP�,�a��4�r�U+�.�UL�lX�e�	(+$�g$�����u���. ��n�,��oT��i�$i��a�$���a�e�o�#��l$�s,	���.����r��!$&��%,�k *�.,*��0�n�)<�e��lP'��
H�e��f��k��m��n��o �p@�s��t�v�*���sh,�.�+���Pn,����a�+����l8/�
��a�i�o,�rX�ut�wl�a,/��l�/a�/��n���L0�r$�wL����}@�ip����]8�s
ad�l�0L�i|���!1]l�yl<�4����gP�����n�1���e@��Qf2���a��e��o�1a2��i��nDWa�2e�2P�e�n�3!5a 5��u�4�r�5��5,�t�54�ed�k��l��n��o��p��w�6�|�i��oh'�7t�l�(7��m�7a|7��o�i8i��n48����.`8�l8<��i,9��:�X9���s� 	�����r@��a���9.�<� �l0<,�e��n8s0�r��H�l8C�P�o�A\�r��s��t��u�C��C{��a�C,��ky�E���i0EiL��đ��r�G����o�E����r��s��t�H�
 I��cjp����u�#�e�����lL��$�kX�njt�iL�@�oL��L�o\�\,M��d�b�Ll�rܧ��x��`_���a��e u�,u7��a8a��gHa����a�_��l�c�l�,$�a8�op�u�������i��l����e����yt��
ȉ��0�i܎����D�n��
L�u����X�h,���d�k�	��e��k��m�nt�o��pԱr��sT�t�����d�l�m8�s��t��u�a����ol���rܚ��i���a�����aT�@a��:h����tL�� �n�,�eP�kp�l��tȟ.d�r����\�y8�L�co�����vn̡<|�at�i����ax�J����s�}������l��i�����a��mh������.(�e4�r<�uL�����dD�hL�l\�sO 	��� �l0Q�����#Xgx��<�,T�t����kĻh�s�p$.0����e�@�����gP���n�����iL����l���.�o�p<�t�&IJ����i�w��l,���b;�H���o4J��@��(�wT��0�e��`�k��H�s�u9�����a��e�l���L�|�g��s��p�3m��t�C����r�����n��rX��d���e�����e,����
��rX�����k��i�]�i�]T�a��e��i��o��u �y����f���@�ih���H�dp�n��s��
���h�d��g��k`������e��i�؃.$!a���n`)�H���d��r���e�g �i(�l0�n8�p\�rp�u`��e����������P�a��.�#��D�s,	�h�e�)(
���PYs�`���|��(�0����d��r���e��g��k�m(�nH�pP�s��`�����k��o8D��p��&�o,?at���e��8�k@�nL	D��
��X�p�j$�T$��\�e��d�e�X��|�g��k��o��u@0a���n�����o@��.���s� �Hv.�%��h�r�%����b�g,�������e\&a
�(�(+�`�a$�H.4�o(.,<�p�-��H�s�+��T�n,H]��a��e��i0�oXK&�H��p��v�L�
��
lV����.�T��g��i��m��t�V�W�	xX+Aj�a3�k�pT�a�]��a�b���lL���h�X�tth$�ed�mt�o��p��r�A�p$.h�L�exi/
X��jl�tHj&��.�����s�j����o(w(�b4�d<�e�fT�g4�kh�l��m��n8�o��p��r��s��t��u�vL�w���\�&������o,��g�����e�w�rXx�{���pf\�k��l��p��r��s~gp�e(~a4~�h�i�e@��|�o�����l|�g����i #�(#��t<����s��B��.�������`���0�m������i���h�����t��n���(�oP�s��{�r@�,D�k�|Wg���\�a��f��l��o��w�Za��r`�����oh�
8�B�($$���a �b,�g`�h��m��s�]�.�Y��nP����a0�&�����a�����n�0,�iܐL�h �VĐ��8�dА,@�a,��l:,X�u=��>l�s�>��t�s�=���iܑ����l���a��,��a��p4��L��io�L��o|���t������a�t�g�	d���l �r(�s�����
h�0�g:pT�r���8�L�d��BX��@��h�g��p��p�a��e��l�m�n�Zo8�p\�u\��9e��p$.������a�����l���.����s�����a�o������
��	�o�������$�r���,�e��sp���D�t���P�i4��L�h�nh���p�ax�|�a��o����	��d��f�Zg�i$�k|;o,�p4�sl�t$�a0����e������o�����o�������sĦ���g��]�n�x�$x�8�lX�tB���D�n�<L�et�e���d�a|�jg��i��p��e��i�j$�kL�pl�t��p|����t(����e�"#d"���o������kx����n�T7����f�$���f��<�u��\4�i<�o��t�,TI����D�e|?��iX�aج�`�aX�!D��x�k����a��eY������l����p$.\����eܳ��t|�����s�<tH��.T�����v`�����e(���i�k$�s��i���/���,�h����4�r��@�eȷ���r��s���8�w
h�a����p�pl��|�oh�i��,��i�����a`�e��i��o��u��y�Z�t���d������a��gLtk4�n��3�e$�o�h��l��s��rxmL��9����,�dD�kD�	�����L�i�T�d��el�g��k��l��s��t����xg.������tT���B��e�������p�� �� �ue�$��@l�]��f�ug�k0�m<�nD�pp�sx�vT��h@�t���o���$�o��gX�+d�l��F��P�e���X�ex��
X��
���
������t4�����e��l��o��r��w���
������pD�w����nL�i��	4��
H
����b�i4�k$�lX�s�
g�m�
K���p �sP�(�aD�s(�,�.xR�P�pXadd�n��l�i$��x�f��g�k��s��t�L���t���s�&7`&��t����e��k�B�,P�ax�c��e��i�j(�k\�l��m��n��o(�p��t�u@�wh�yI`�H.�8�nhD�a]g�<k<�+�d�i�l�h,8�e��r��t`����v@B���i����n "(�!���e$!����gys��X������i,$7��r$<��a�%��$�n�${�aD�i`]o�+ '<�n�,���	�\+x�iP��H\^�Yl�k�.�t�a�.���a��oh@��/����o�>u�/4��od1el1��b�w�
��j2��b�n�pT���4YP4����d�l���62�k�8�H�ed�i��r��u�9�L9��@�k�?l�?n {r��s�:�p�r���;�<�x�a��i8�~0�+=����r�\W|?���lP?����a��n?��a$�e��i�r��u�y��"$@����t�W 	�W��nPA�e�@���kH�m\�nh�r��t�Z[�A@�ph[�	B��T�sLB��L|a��it|r��t�]k�_4dJjha DCXC���pXgrFK��o�E����d�h�k�E��a�pz F��y+@G���r,�uH�tI�XH�i4�rĐ
0���,�e<K4T�e�L�<L��L�i�M��M`�n�N���e�hԼi��oL�u0�w$\0\��dX\����a��o�sU����r�sl8�^�p`�Lb�b,�e�h��r|�x�l�v�l���el{i�z�(�i(}��d��i�lX�n��r��s`�t��u�� 4t,d�i����l�s<��x�kH��a�g������k��m�n�t�@���sx���,�t(�a4����r@��e�����d,�pP�t��a���o@� �lD�od��p���<�o�$8��h�i��s���Hma(��p�n��x�i|�*0�����s@�p�e��l(�nL�t���������n���e�+@�+���g�+���n��4�a�xXx�e��m�d���o|?�0kd���4�a4��@�a����X�r��s �a,�p�u8�,x�l�@0,.4����e�����t\���s@�,�a4��
���s�����g�����n �],�a��e�iD�oX�u��`��Y�rx��� �aH�eP�nX�rx�sh������XT���a`��`�a4�l�k�� �����k��s@�����e��g��r|�������LZ�L7�lM���fM���a��l��������0�wT����k<�s��a���(�a��
���P�t�w
����i����`�a`���h�f��k��s��.������t<���i������uP�{\���k����̩l��d�.h�adb�d�e
fg�i<jHkhl�mnXo�!p�Lr"s,%t�%ut(v�(w�(y��M��]<�b��h�YD�n��r�tZ��
T�a�d4�fL�g�i<�k�m�n�p�r�st,w���``�	`����d�c�c��o8c,�l��t�b��sĢ�c�rXg��nf��o$�u,�vHhi�he�i7�laD�dRx�k7�p.p�a��i��l(�o��r�s|kiЗ.�smf��e�l+\m0��a�m��Lpg�u�m]��i04�o���e�n4�m�m�o������o��g�p�rd��ul����bDr$�lTr��0�al�l��o�s�w����sX�dds4`�e�<�x�l4�����u�s��k��o̥&T���e8���r���4t,�ixt4`�a\���p$.����a�{,�d8{��b,�oL�p��sh@X�|�$�o�}�H}�8�i$}�@�lh�oP�at�i�}��\�ll�(�~��~{|�o�~,��k��l��p�~��~��e`al��e��
ap�r8s����d`g�ksDtԀ�r�����afd����a�< aDe`�(tԃ�
�����La���Ta�n�o��s�u�����|l����o��ĭ0r|��Ԫa��l�o�r�w���eTf�p�����e�aȇ4�i�.؈,k$p�t�d��\tt���,a���8a�Qjlrxw�b�Č�Ќ�deX�9t���_rt�������t���l��e�i�n�bo�p$ ��p.<��eh��T�ad�T	p����a0����r��e�Y	����a�� a�a��8e��@p����Ls�Xa�e�or�����]|eĭ���n ����k����v����w<��D���b\����m,n8p���aXe�i�o(rHs�u�E���.��,at�� hH���t���	����Dn0���Le�i�r���D�ltP���ta�ol�i`7s��������s�����nD�������f�o�rw����H.���rx������d��0�Oet�K���a���a8eT�f���,�@k`mlo��p��4��a,�H�n���h��xd؇U�����t|����iT�ce(f`g�i�k�ln�p,	r�	s$t�upw�y0o(���r@��t$�0g8pHr�s�tvT������L���@axe�l�o�s��l*��ds�lih1�(�L���$���t,A`�}�aD�<�rxB�B���e�B��i��j|����r|I���a�Ia���	@��� f��V���4t�4<o��Hld���Ts��ln\��te�g�k�n<s�thnܼu��e���l�w�T�a`���a$��0��a����ao�ht)��o��$o$0kXpX8a�nX�LapiD�L��hr����|sԼ ���i�	���t�	]�oD���lD�n�s
p-u�,�u`�
���i�}	l��t$��g8�j<sdt���(l�,0eXt$ ap$.�Le!�X�j�#+$��pl�#xe�l�o�r�s�$��$��i�pag(%���o�zm���w�����t�%��a�%,�k	z`�ap$.�	y'�@	aP'�� 	a`	k��t�'	��p	p,/H	m8/�T	a�	s����0��0,x	p8���<��	e�	t0<	�	e�	h
i4
kd
lp
o�
t�
uw�0��e�dx4��	b,�	a�" �=�	l�=��
s=�
n�=m@
s�={(
e�&�.-��,.h>�L
e8>X
i,?�n�
o|?XD@H�
a�
e�i�
r�
yt@��4�a�@��
l�@�,A+8A��
aH�XH�ou�L�6e<L���
tdAH�
e�R��A��n�AaȗcLepo�r�sXZ��A�Dm8��iblXr�B��do8s���.�r��|l8C��o(D��C{�o�C,�k���E���f�m�n r@t�F�0GG���s�G<�G��.�Gg�G��e0ođeI I�8e�Ja�J��LnXJ��Ti�n4J`e�i�	K���a����K���gL��Lv�rܧ����`!�4
,M���w�P��P�n�P�aP
s@P��	�ad
e�
i�
loإr`s�t�u�$��0
a���8
r�D
tlS�$S\
iЮk|
n�
v$��I��VixV�
e�
n�W��
gP��XX��
a@XLX���
pē��Y��
eZ���
de t\Y�
n,p���
��	<Z�s�Z��%0xm�$4rl]{@a�o�Nu�\,Lk�n�o�pP�m`]P7p0b�a���r<1�e�/�it5�;g2�oL9����r�]H�e�z�^��w4^�iu�^$`_4Vh4llo�s�nm�m��,i�omPN7@n�Hg�q��TaTp`l�r�0x�|y�r���dhvX�m�	�a�d0e`g�k�n�pXs|t��4��i���j��t�od��ةsl���tج�nl�$����(dhg�k�l�r�s@u�n�4���TiD���\lКal�]ty���|w������a؞���wܝ���s�������e��m�t4w����g̡<�ey�|�H7s�KyxK�� kH��(a���Ȧ��(FtԦHn���Ti�l�n�ords�u n��m���s��a�v���p.�o��yP:&ܐ��er��g��m�	�p����lT��k`�]�e0i<o�u7�u��t�u]$e|������Dg��,La��Xk�o�p�wd��l���|ax�4�n����}����rX8h���a��p�pL��s�����ak��lDods�wةe4J����wh�&�,$i���,s�8p8�a��Pe<�,Xe�j�o��a��<xut5����o��!��]�a�l��dnP���il(oL����g<hLkllxr�s�t�vܲa8�Ti\�4i\�o�����e�e��Dl\rԷ}�g�Xg�di��	H��
<�,�p��(j��ex�iL���a�lor s����a�oX�����(�:yX��l����m�(&ȼ�Լ8oX�$� mP�,o����Dn�Le��
h��dm�r��le�j�r�sz4�i��tg��il�$�����e���Dy4��.,�a�,�hpt��������lo���d@��t���(st�0u��pa�e�s�tu,w���L�hf�tĻ��n�����oH��i�����eA�d���sL�o���mp��,�e<kTldo|p�t���(%��s(o�%��(p(�{0aLul*m\�i����\np�wH�+(��te�r��CxA0�l$����l���e�Ym�_��j�����t�����rt���e(:�|�������l<�i�,���t��] aHiTyضC���@tx��`f��+���a�edo�sh���t�j<J����s��i�kr(sXv���0��pD�}�o����n;a�:�i,	���u�
aHe�	w��p$.x���4�Ĝ@ÌILeX��ȭs�/���o$�po",|mD,m�+���f(+�a�o�s�?a�?�l�?���a�>���k$Yn�D�D,�m,He8X��Ts��ml� d����(n ���4e�w@e(w��Lb�e�fg�kl(mHn�o�p�r�s�t� u!w<!x0�a�z���l�z�a�ejo$r{���dPfDghr�sx{�H{��t�{���.�{�a�{���n�{e�r�.T},s�|��8o�8<еêPt|���\s\�{�r�tk�t�8������i�Z�
�rH��p�����o�sza�H�a��,�p����g,��ioHsD��(p�r����
.�40th�<oXt�{lo,E�|��dk������xl�o�s�w�@0nȌ�Ќ��n܌4�i@�,�w��+�4�i��\����.l��e����i�5�8t���r$��e<8s�7�D�����@dxg�s�t���Įr��`aȔ�laЖaܖ�a0�,�p�t$�ap$.�+�ed�(j��$h��dfp,r@s<K�l�,�wL��s���
bܼ�̥�
8�$e��8hdt�����Paج�Xr���ZeL�pm���|m���e�l�r�������y����|;o�yt+P����nH��(���.���ejkDlXt�w����\���+�$t�+��,a\�48a��ج�Pa|r�G�hm���puةmM��k�L��kP�4�iX��D���k���a r\ sp u���r���tdr]�eȯ��i0 u����� nܧ�� ��s��$ ��Kpw��< n�w�D a�,P w(�!��h i����.(�| a��� s|���� i|�t� w�H�,H��� a��� n� r� tе�ܵ�� eg�N� jt�}!y��!r$�����aԺ$!r����0!e��l!o��H!d��T!j�����n���P�x!nd����!aȷ�!a�!e�!h�!o�a���!nx����!ih����!n��iT�,�!et����dL����!n�,
X"a�"e #fD#gp#i�#j�#k$m$$nD$ot$p�$t�$w���D"rhL"ap"d�"r�"s&D�����x"m,��"p,l�k�"l�"m#r��"e$�pa|���"kL�"e�(�"a`���"vlS+$S#iT,#e�����a�k,,#a��8#h���P#a� ��X#a�d#g���,$7|#s$<�#a '�yn�${�#i�#o�#r�'X]n�#r��m�����#r�)�)��#e�#u,*��.���a��e$0�80$a�/$a�^�620$w28$p$F�:P$t�:�X$e�$i�8d$l�$u�:�=�?HlJi�$o|D��D���$gDD���$elKP�rxK���$a<K��$a�$e%y�L<L���$t�+xM��%nO���AgL%md%s�N%a�%e�%o�%w$}YpR�D%p�S+Xi�SX%p<_'�^p%kX\��x%sU���%rD�"`@��%dl�%n�h���%oTr�x����%k�z��%a�}Hvh$&l(}��	�%b\&g�&i<'k�'n�'r�'s(t��u�~i4&eP&o4��T�&$��<&a0���D&kX�x&e�&u�g��9.|�l&r\��
d��&b�g�����&.�&e�&m's�t��.<�&�/�&i��i�&s���&aؿ���nĿ4�&a��'l������'aT'k��� 'a|���0'rt's�r&Lt��L'eX8�x��`'a(�,h'p�a���.���'e8����'i�'sl؃.���'a��a����'g��'a0����'p�.�po@��'mb���(h0(z<�aȷ(u�|�$(px����$<(r��D(od�P(o����\(s@�h(a�(ex�����(iĜ�(t�����a�(u@����(r ��(ep�a|��(i`�,)c@)f�)kX*m�*n�*p�*s+t�F�.�E��)m����)uh� )e$Si����8)eP)o\Yi�_m��X)t|&`)n�ml)a����x)a�)lD�n�)o,*r@*s� ���)e�����)p�#�)o�)u�a
�.$�i�)gP��*n� �����)t� T�*e`�*dh�� *e�����,8*k�+ih*g(+L*a�*u�,��m@���p*tPF��x*n�E���*iD���*a�Za�H�*f8���*a������*l�HP��*p�r�
�r���*gdr]�*n0o�+i���+r�-d�.�+a�0b�0dX1e�9f�9gl:h�:i�=kt�l�=m��n�>o�BpDrDs4Et�EuGv�^wtGy�B�|�&l����+a�����+n���+of�+dZ���+c$,d8,eD,f�,g�,h�,k-lp-m�-n�.p�.r�/sH0t�0yt���"s�f�,uf�,e�i�i0,s�i\,d�`ol,rx,sRkF�j]d,o@ke�oaho��,a�m�,t�k���,s���,i�p�,d���������,tds4�,oTr���,l�w,u+�t�,g�t��-a@-t�z��z�� -shz,(-uxz4-h��m
�i�{L-jh{�X-t8{�d-aЂ�����|-rĂ��-a���-j����-d�-g�-n(.s�.u����-o��f�+a���-nȷ�\��-p�g�--�(	���.mĈ.l؈,.aH.pt.t����Zn�<.eX.r�o(p4p��`.a,�h.a�.oP�a�u��r|�+t���.l�����a�.kH/l\/s�/t�/y��a���.k����.e/l/o,/r����]�.e��
H�/m$/n<�a��el��!4/r��</p\�.̕T/kl/t��e�aȘ�t/ot��|/r��8���/.t���/a���/nh���o�/r0u��/k0t������/i�*al*�/i�@����k���0ep�r8�����(0s��,00e��<0hx0t�����X0s�,`0y��l0hЦ�ئ���0d���0r����0a�����0n�,r���1o,1s�cw��,.�����0����0à���0o����1p���,��1o,� 1o@���81r��@1eT���L1d�1e�2g$3iP3l�4m�4n�5p�5r�7sH9t�9uh���4)eD��1l����1o�1r$����1k�1l2n02rX2s�2t��������.�1o������l��1e��l*��2s�2iD2k��� 2e� |*i<2e���$��P2l�2p�2t����l2e(�<t2rX��
4����2lD�<�2a0��<���2e��2rT���2sd����2a�2t����2t�b���(�3e���3e\��3na�
03rp
83a�
��D3a�3e�3k(4sH4t,�@
]l3a���t3w���3kp��l�3a�3j�l�3n�3r�3s4wP�at��3o��]�3a��e$4e=y�$�4lt$4uh,4p�%��%��44o�<4o������T4s�C,\4i��h4hTt4p4������4s����4g�4n$���4a�4e 5o@5s�5tX&$�4e����4s�4i�_&��5aHj�5g�5p�ax{,5y�,45k`5nl5p|5t��X5uhe���t5a\!ah!���5n!��5i(j�5r�!eT�,i�#�5h\(�'�5k6sP'���5aH6d\6e�6i�6kx7o�7t�=�0�e�(6s0�t���7(6n����06i`)<6el*��)T6i|6n�6s�*+@�e�*��p6g+h@�s�{(`���6a��<�6aس�6t���6s�.���6g4.]�6n���0]�6i8/��6l7n87oD7w�����]7a\0�d0�$7nL0,7p1}X7aX�yd���P7rX���2��d7s�2l7e�7n(���h�3���7t@�#H����7��:�7Ø:��7eX9��7r<�8l0<�7a8k|8l�8m�8p�8t 9w�<��$!�={8a@8oh8r�(/),8r>48oP8r�=0��e��0t8e�)�\8o��8>i�8u.+̬�A�8s�>���8st>4�8o�;m�;���8t;���8r�?<�8o�@t@���8sD@<�8a9oA!xK��XrdAH9a|U!�A�,9eh9m�9r�A49e�s�Z�T9sXZ\9p\_0�^t9o B��|9s��.��9s�F���9a�E���9l@P��9lh$����9rX���9eXX���9o`_�$:aP:e�a�s�a��:g�_:n�g$�g��0:rhg8:e�cD:p`:s�h�p�,�:e��iH���x:i���.�:o�:s���
�:d4;e�;g8<hd<l�<n0�r�<s�=t\�v<�7(���:ap��:eج��:s�*;t��Lؘ��;s���;aL;w����$;kl;r�;s�;tl�]L,aD�� �X;tܝ��`;e|;y4;e�ȟ{�;r��;kd��t����;a���;a��jT��D.`�]�;e����;r<u4��.���;l��;e���.����<b��� <aP�,<r`��l���D<ax�L<a���X<tl�0L�2p<l\�x<pP���<oL����<g�<sH�	<�,�<p�<t�p6x�+�<u����<f=s8=t,M���.�Yv�<r�Y��=��=6=è�lg�$=rT��,=kP=o,Ea����H=kpw��,.�w�\=a�,h=w��t=s����ma�=o��kXs���=m(+��=a@>e�>ih���/�=p�=t�+���=s>t�0�Rr0��=oH0�j�k�>rx>oP3��(>oX1��4>l\>r\6HKs�5��P>el>ox7eH=�P=��t>k8=�|>o�<�>t�:���>sXx�?e ?j(w��
�>d,?e�?f�?k�?l0@nh@o�@p�@rAs�AtHButy�x+�x���>l$�g0���?d���?a{�H?dh?nt?s|��z@?s���d�T?sx���\?e��t��
LX���?m�j��?a�����?ll�����b��?l����?a�	��?s����?e�i@m�e��?a�xh�@hD��@cH@o���� @dT@t�P���@@pd��(�sT��h�`@n�@rl�0��x@a8��@d��
�@l����������@g|;o�@s��a��<�@ux��@j�@p@�t�a�<�@o��h�Aa��Aa4Af�Ai�Ak�AtT�TAl�X�tX�@As�Y�HAe�����`An����hAex��tAn�m�Aø��Ae��L<.�����A�ج�̎a��AeBh(Bo�a��"s����Au��a���Au�,�Aex�L����BaH��BrXX����4Bl|���<BftBs�Bt��x)p�4\BiܳhBl�Bt��ap$.\���Be��$���Bk̴��Bed�>t��B.�����BeD��Brܧ���B�ȷ� Ca\Ce�Ch�Co�Cs8��8CnHutd���Cg�p�.�p�,Ce@��X�s����DCyh���PCl�Cn�r0!�lCox���tCt��&�����CrT�,�CeL���d%p�)���a��{�Cr��,�Ck�ClDt�,t$��CiP?����a���Ca����,@Da`Dk�Dl�Dm�Do�DpEtdAwhH�p&�$LDt�${TDa`]o�Dr�\t)tDl�)��|Da�)��Da8-��,��Dn\+�Di$/��.��De2`�pL9���?n�8�DeP?����a?�DaEe�@i$Eg,EoA�0BF�N�`Ee(:�_4@EjX\��HEtU��TEr�~�lEn]tEe��El(�Ek(}��	�Ec�Ee$FfPFi`Fl�Fm�Fn�Fr�Fsh>�p$.8>�Eì�El��Es(�f�3a4�LFa@�,Fm���Fsą���0Fs|��8Fe����DFl�t���]X)t�YhFn@��tFa���Fa8����Ps�Ft�ma�r���Fu���Fo4�+�Fi0����Fg�l��,.dl�Fe@�p@�,,Gi�aܢ�Ga��Gr��� Gt�]7@k,8Gt4�7@Gs�LGf���XGa�Gs`���dGl�Gn��s�a ��Ga,�,�GlD���Ge�Gi!��.DW���Gtp��Gn Cد��Ga��m�Gp�a�Gm Hndc&tc�He$c��Hg��d�.�HaMbhMctMd�Te�Yf�Yg��h�ai�dj�dkXgl�gntho��p�kr�ksDqt�uu�vv�^w�vywz�Y�Z�HahId�Ie�IfJg�JiKk0KlHKmXKp�Kr�Ks�Lt�Lv�LwMy�\��Y�HlImIn$IrL]S�]`�` 	``Ii`��IdHIs��\�{4Ir�a<Ik@g�Lg�TIif�\Im��v�*��It�xIsHi���Iyi�Il �Y�i�Ii�Il�jmLX��8�d�j��Ia�`:�k�Idlk��In<Jp|k�IaDJp�k��JadJepJl(�o�Jr�Js�Jt�Ju�_C	$l��=:�Ze�l�LJmLlXJm\m�a�u+�u��|Jn�m]�Jo�m��o�Ji�d��c���Jgpi��\\.���Jt�q�Je�p�Js��t Kpds4�JiTr��KlH�^v��t��(Kf@Ktxz�8{|��t��PKlhKr���Ko��7�itKt(���|Ke����0bb�Ke,��4��Kah���Kl��KkLl�mLp,Lt|LwH�-���Kp�'��Ki���h��H�eLr<}������$LaPLe\Lop�rhLuBa�@��HLn����r@G+tLu\��P���Le<L+��,D�a���Lh�Lj�Lol�a�����Le��4�Li4���Pn�� ������Le���Le����.����Ml�,HMa����(Msf0Mh����<Mc ���TMeh�\Mh���	�MalNelPi�Po0QrRstTt�Tu�TyDic�l��Mk,��Ms����Me\����Mg�Mk�MpNr(Ns����p(tH��0p\��t���NbNt��Y�=��� NsDNt���L�e���8No�����xe����PNiDn0���	\Ne�Ng�Ni�NkOl0OmXOr4PsLPz�
 ����N.,���k	$�,�Na@����Ns ��Nr�{�No��,�Nk$Ott����Ns|?>
P?��OaL�HOa�� $m,�D�<OdpOf|OrP���DOa�Oe�OiPo�'a.�(�������Om�On�Os��������Ot8@i+h�Os�� �]�Om�On�H<����O.$4�Pil�i�Os4ol=mt=�Pl$��Pl��(Pi@��odL@Pv��F��XPe����`Pd�Pg�np��|����Po�����f�Pm�Pn�PpQs������Pe�PtP�YD�vl����Pd�Ptd��ܼ�����Pbd��p�Qt\�+���QkLQt��� QaTQe�Qi�Qo�Qu��T�fhQs�<� �`Qd��	����tQf��]|Qf�Qv�����+����Qe�Qk�Qt4�+$�C��}�����Qm�����Qi\M�����Qj�����Qt��Rr,�
RehRg�Ri�Rk SlXSmxSo�Sp�StHTwx�&�g�HRu��PRr��\Re�":P���tRl�|Rn�&.
�&���Rn���Rn��{�Re�Ro�Rr�K<7r���Ro������Ra ���Ra0[	�+�Sg�+��Sad�Sa8Se`a\�f0Sg�Y��.�DSa��4LSa45h��dSs,��lSn�Sohrħm�5�Sn���Sr,�8���Se���Sr�Su=+`�<��a�SeTo0Tr����kh�7�D���StDD��TexF;�F��Te(�$To<L���oeP�4<Te`{�l{��TTs�z�\Ti@��hTwp�+|����Te�i�Tk�������Ta����Ta|����Tr��$�����TsT�8UbpUc�Ud�Ue�flVg�Vi�Vk�VlWmDWnhWptWr8XsxXt�Xu|Yw�Yz ���,�s\�,Uet��DUu�c��LUc(��XUi@�dUt@����|Ur���Ue$��Ul�Um�Un�UrVtVu`Vw��+H�aX�0���	l��Ud��h����o�s4J����Uw�Ve��4Vu,��
.8�{(Vr�k$�@Vr����HVo4JTVod����.|Vl�m��,�a\��dSe��l�9�Ve�,�VpD���VsH���a��VaH��Vm�
���VlTo�Vsh,��pT�$Wa�j�D,WiD��Wf�i�,0Wl$��8WsP��tvi(%��PWl�#\WoP'����.�Wf�Wo�WpXs�ia.���Wf�+���Wa$4��2�Ws,5�45�Wg@5,�Wi�4�Wsp7�|7�We�5�WlXw,9�$XeL0�<�pv0<,XeHXtD@<�iaXXe�@��k��g�B��`Xm�AlXo�Xr�CB8C��Xi�F�P3.�Xo�E���XmYr$YshYt�&�?���Xo�F���Xk�G��G��XltGi�Xa�G���Xa�Ha�H<Yi�HYj8YkPYt�H�LnatJi�o�H�DYoЗ��'� I�\Yo�N4JtYtdL��Lvܧ���Y�@P,�Yl�YyXX��� _��Yp,_��Ya�^�Yt`_�	ZatZeX[i�[l�[o<\pp\r�\s�au�`&�_Zdlk<ZpPZs�Yb�4Zt��
�bHZe��spd�	|d��`Zn�chZe�Zi�Zk�Zr`:s0[tlSf�e��e���Zs�_��'�Zp�g�Za�Zl�g��Za�Zd[e�(�T)`)�Zrh�(h[t�Z��A�[n<[r0j [e Bk�lY�l�D[pdlL[mp[n�Y m��h[k�[sx��<�,�[t�m�[aXH n�[u�m���[s�qe	�[.�q���[�Tp�[n\o\u�[���$rfhr^
tr\gTs��Ea,\u��,$\e���0\s�Z�t�H\d4t��P\a�jnLQtt]\\a�\i�u��R�v��\.�v��\e�v��\d]g(]phv�\a4]e�]f�]i^k�^lH_np_o�_p8`t`au|aw�m�Qf�\t]7�\ipwRXw�]edw�]p�w�	e`]il]k�]r�wo�wL]e�w��T]s�w�8/:�]e`��t]k�]o�]v�/&�2(}�Ta|x�]r\x��]e�x	�x��]a�x��]p�x�]m�]n�x�l�t@y��n,^ty,^aP^o�^u�y�.���(��8^ld^pz@^eP7pt^r��74��Hz��l^s�^tl�m�*��4zwl*�^r�^u���ir�+|z��^a�^b�^dlz4�^a_e(_o<_y�d}�^o��+/
ui,��p��-_p�z��_o�.�h.4_m�ziT_i<1t�2�T2��\_t�z4d_e�_f�_m(3�43���_a�= 4��_m�9&����_o{���_l�z��_e�_i`o4{d<{��_l({��_lH{�P{��`e�{R�{�`i�{��`bT`l�{<(`a``e�`iao(ar|��. |����h�`i�`l�`m�`n�`s4AD�`k`8|@|���`g�AF�`a�`m�`pLZ�A��ZmB��|��|�`e�|f�n�m0,.�|���`uH}�}��ap(}�aa@au�}��}�8au���}fLae�}Tai��r�}��`�l�}4paeX���t�ai��ae�bg�bk$cn�cs0dt���aa����aabn�����ak0br\bu�����ak��]bi�'�@brНbaܝ��$ba�s��<�a$�fHbn��Pbi���r�hba���tba�be�bi|d!���be�l!Ԧ�be0=��p$.���by<�,�bp�����bscw��mX�X�bm�����br��]ca���L���cdHcftcg<h�cs��t�cv����f��Pce��Xcl�ctP��dce���<�_	��e�co����ca�ce�cidoĽl�l�cr���h���ca|+���ce ��cddn���ܭs,��&m(dn�����@�r`ds�K&�t�@de�tHdr�,Tdad����l\���ldg�dkt�xde�dix�}quL����dr����dtl����ds�����de���
$eaTeeflfnTfo|fp�fr�fs 
u4gw��8ed��erL�ea@efHek``�
��f@���s��X*flei�el�er0��xee�������es�����ea�ei|4���et<�aH��eeX����ee�es��m
�ee@��ewL���f�]�eil�# fa0fe���8X���(fs@fu�X+�f`fi��Hff��������,hfiX��pfs��� ti�fo�fy��+�����fl�fo�fs��<
���0������fe�1a���fe��4�fo��,�fngo\tp$gt,gw��8m��+@����# aLge������Dgn����a�gi�gu�i`��lga�tgg�'��%���gs,H�ga0hehhi|�aL��go�K�gp�H�gsht�L��L���ge�L4�gi�L�gjPWa\W��hlhWho�T$hpThspx,X�@hv8XHhe�c��a`hs(w
�he�hg$ikxim�injoHjp�jrhks�kt{��Pf�hs���tHa!�_�hl,��haie��spd�hs|d���hnx��he����is���ia8iodir���.�mT�Diw��]Liw,�]XiiP�$��pia�im�io�=����p$.t���ii4���iy|�������ia�id�itD��d��t�a����in���iah��idjg4jr����0�� ji8�(jdܼe��@jbljl�jo�jrС+`�a���`ja�o,�d�e|juX��jh���jm���
�����jd����jo���ja�����jgkkkm<kt���
�a �]�ja���jl���
P�V����km��,$ka���0kh�$�ثfHka��Pka��\kkxkp���D����ka�ke�kr��ȯ+�ke�F���ko4����e�,la<le�lf�lg�li�mj�mk,nl�nm�nnoo|opDpt�pw0qyhL"aؙg(ln�i��gH��,4l.Tlldlp�lr���%}(\lrT:0�plo�lr`��xlt�:�T��le���LS���lr$S�lef+���ll���le��4=a<=emgHmnhmttmu� imaX�a�mr"� "(ms�!�0me$!��<mg�#��#�TmiT#�\mr$,$�$<|ma,%�4%���me�$�mf�mp�mr�${�ma�meD�ino nr�%�	�%���`&�ml��������ms�'nu�)�)�na\+�Hnalnetni|no�nu�+�<�m`nn�+��+��Xnd,��,fd-f�nt�a:�-��ng�nt�:.��1�$/��ne�.��ne�0�80�nr�/�na�ne?o�0m�zlp2�T2��ok2oeHon`�p<oÐ�e	�[.����0o�P4��$�d\ot(w�i�8+X8��dor�os�8loa�oe�oo�or9�:L9���or�;�;��or�;���ot;���or�ot�;<<�;���ot<��oapep�0D<pkH\|?� pkP?��(pa��f?4padpe�pr�pu�pw�py�@���hxpl�prxA���H.�ps�ALd�LB���p.�ps�B# F��E���pk�E��pa@G�0�\l�y$HUH7�pd<K4qeh��$L��qr<L��qe�N0<qe�M$qwxN�N�
�qa�qeDrh|ri�rj�ro�sr�tsduu�uy�P)�P��tqsO��|qg�qr�qt�SYdS���qk�qt�SY@T�,T�qjU��Bi�qrrs�\0\�qmX\���qa�`�`rm$rt�@�0a/ric��b��0rnb,8roDe�Le��PraXe]Xra�d��drw�c��prk�rn�e���vg�|ma�ro`g�t���Q���rt��i�rk�j���ra�h���rfsk(slHsnXsplsrp��l#e@�,�ri�k��ss�
�� sihI|�4sd�k��<sa�d8l&Їs��l��dsg����o�xsn�o��siho���sd�sf0o��sa�se�Cito`tutty�o���p�sf�sk؄i|Cr�q���so s+�r��te [m(tn8toPtud�whs+�s5ts�0tmHts�	�s��s+�s��Xti8t@t��ltk�t�Lt,�ta�ti�tkulumuo<upHut "&0u��te<u���tgPu�tn�%0ud�u�tn�u{�ta�mP�T�e v��v�nH8��v��(ua�v0uaw��y<\y��Put$y��Xuitum�y��|$�{��|us(}	�ua�ue�ui�uk vl4vnXvsxvt�vu}����.�us̀�p.��7؉�va|����uw,�+��hm��,vo���vs��e	8���,vi��Chva\��@vr@�Lvkpvt�)04�+��8Irp�`\��vr@�,�va̚�ؚ�vsH����vi�����vl���P��vo`����vs��9.f�vsZ���vc�waZ�� wa�wb,xcXxd{e��f,�g��i؊j�k�l$�m��nh�o�p��r��s�t|�u(�v��w �y��Ük�Hut�����wg��wa��o�wv�wy�+����wr�,�wo�ka�k�wol����wr��wnx����n��]xy���xwh� xkX����8xr\���@xg��Lxa�xe�xlyoyrLys�zu���
����|xn0����xe�xl�xs7���xe����xit����xe����a�<�xa���xpt���1u�������ypT�����ye�
�e����(yk��4yk,�@ya�yk�yl�yozpHzrhzu�zw�s��"
tyh��|ys��{�yi<�0L'e�+���ykd��ya�yo�-��id-���yp���,���yo�gpzr�6��6���ygX8���a��za�s�(zg�=�0zgH=�<zo�ItI��TztXH\zixK���oa�zrP�4tza�zy�KapM�
xM���zg|�+|����zk������a4{g��	�zaH{e�{i�{j�{o�{r|s$|u,|wT����zdP|f�|g�}i~k@lx�n��p|�r�sh�t�����X{e��<{ix{t�,.xE�d{e,�l{t����Բd�����{n0����p.����{a�����n�{o�{p�{r��X�����!�!����{o0Gy(��{y`�<|r�|t��i��4@�y�PX4�8|al|n@���@|at|e�|l�|o�|r�Q!l�B�|kj��!���|o����\��.�\]�|t�[]�|i�`mH`�|d`�|nT��|ad����|a4}lT}o`}s�}yP'i}r�m��}e@}m�m��$}a���.lm0\�L}g�����&��h}a�p}k���|}s\�}e�}s�P����}a��}n$��}k�&`&�}r���}e�,�}aD���}a4~e`~l�~o�~rs �w0����xe��(~iT~r|�v��0�.X���H~f�	�t~a�T���l~ph���tvi�	�~l�~o��mT��~tt
�~r(}�~a�~e�~oLt+D���~k`��X������~n��~e�,t�Xe�� i`mxt�
��,e�i�o�s�
TLJmL�0�r�le�B�s����g�s`��n�F�L�tx�I	 �M,�e����bx�n�p���i���rh,��e,�kL�l8�{$�r��,�8�k\@�i����kX�i��r��s$��d�èk�o$�s`�y��&`����e@:�Qj0����t8X@�s@�l�n�wt��0��of#�oȇ���l�x{�o�,�k�m8�o ��� �,@�e�vH�bl"�T�sH^@��l�gP�t�n�#����a�#��a؁i�l$�sT�u �p(�,��t�$����s�v�$��ȁn��L�$}�a�i�o�$���+|��%�e�%,�e@�iL�l���&8�n�&i'i�(L�(\�e'd�sP'��p�a��k��ôs�t8/4�2�n��uh4��6��6{��e�5��k�:��Dk�:�؂aX9��w=��<���r0<	�e<�iP�j\�k��l��m�p�t4�w=�4�eT$+�=�H�e�={�Cr4Em���h�th>�p�m8>|�i��od-!��L1��i�>i��dt>���e����.�?��̃l�?<؃i�o;!LB+�@����rD@<�e �r8A�$To<L�dAH,�e\O$�pxO@�n�A��L�a��m�AX�a��h��j��o܄r�sl�wpR�b,,�e�B��@����i�B����l̄olX�C�8C�Ԅi�u�s!HD��Ta�C{�r�C,�k,�n@�pT�tdv�pv$�a�Da�D8�e�D��D�L�e�ci|E�lE�d�yH
��pg�O��x�u�M��rܧ����������H�P��t@P��
��a��e�f0�i�jlH�o|�pإr��s �uLS���n$S�e�W	�U�s�T�i�WfxV(�n�Y�X�s\Y<�k`�m�YP�Y��[��[,h�a�[p�s��:@]����t(]��n�\,��aĆi�l�o�p�h�d�n�"6�i$!��Ԇs�"��]T�e2�yo�]Hza���4^�i`_	X�dx�e��g�i\ml�nD�o\�rh�sdc�\�.�ie�hd�t�cl�sLf0��of����o�k��lk,��e\fJ�Pxol�li��a�l��ȇadlԇf�g�g�L0�H��t�o��a0B_�@���o8s� �e�r,�tTp8�sTv��fet]P�yhv	\�.��g��i��k�l�o�ph�u��w���0�x����f�x��ny,dna��Z�+�	Ȉt|z�Јslz4܈a�7��9.�z4�t�z+(�aH�r�8t�.X8���dx��8�]4�s�{�<�i�I�}fT�p�}\�i�KaxK��t�n�}4|�a�Љg܉k�l�s��tXj��`���f���f���ĉa���ܬ�����eĽ�$�g���a,�k�qlT�p\�t��w��FX��X&iH�u�ny�����@�uH�+T�+��e�r0h�sx���p���@��|�øLa<L����rP�4��e��rjL�+�l\�����kt�̊e�	L�bO���sL��aD�gL�mT�n����	�ah�e|�i��l4�n�o,�r@�s�w��m��U�ih�+Aj��\�t���|�t�eh	nT�����t�]��aċe��F����d����e,@���Ћl���؋i��p�e�o���s$�ud�ap��l�����]8�o��f��,\�.p�i��j��k��l\tp��t܌w� +���h�g��n����Dns�(�{�Cr�x����p\���i��H��a�L�Ȍn@�4Ќi��4��i�������nh�PNg����a`�f�gl�i�k8�o��p̏s��t{u�y|
��Gtd
H�n
��T�e��lčs���`_��L��x�gܧ������
Z�
����p�
���aL{�u`,��k܍m�G��]Hԍe4i�l�oH�s$r��40dl�n �r�!D��L��(�eX�40�e�<�m\�/0��T�b��s�\�e��f̎g�m
��a���m�.��P(
������a����.����y`����n���؎m�p�,�e�o,�tH�s�d�cw����$�iXT�kh�o|�s�����L�w,��`�r����t�pOa���a�!,��t�!��h�"�HeX"��r",��a�a�؏a�|��h,%�zP)e@)���o�(�f(+�	P�a��eܐg@�i��k(�l4�o��p��s�+�Tg��p��r>t`��h�g�.+p�e�.�|�l�.���mX1���s��u�7��9��PYs��� �d�k,Đa�9�Аh��`�m�s�=��ae��pX���ot���o�>�X�s$S0,]e4A�@�eAL�fd�H�+d�sP�l�g��x�i�B���l��o|��
����.���r�C����oБpd%��H.�Hm�ܑl@D�aD,�a �l4�pT�t�++,�g�D�a�+%�D��e$@��D��@�nEH�ad�iXC��nJfĒe�Hp�g̒n�s,H��|�aD�d��eȔg�i\�k�n�o0�sd�t0�ydJ����K�K�Ԓl�Kܒk�tHL�PL����n,L��e�_�.L��p�M��$�al�g��mtM�0�a��dēeܓi�o�r4�s��t�M�Huth��@tD��x�p(��8�����a����a@����w4PslN����s,u`P&lP��ԓd�P&�P���m�����.�Q����l0Q��o8SF S �eR(�lH�oP�pxS��S�\�l�:TT�\T��d�shT�l�itT�x�w�TX*f��i�VfH`�`��nZ��a�Y���a�o�rX�[ؔs�b��a�k�l$�n������tc��r$c���g(:z���0�j�e��8�tTeD�r�d�P�edvjx�n��oȕrf#to,��e����gTf��m�ks�
����h������g�f���uHX�Thԕt0hܕs�g�e@��$i���sth�k$�shk`cf�k,\�.\�eh�i��kܖp�s�tdl<lT�p�l�h�d�&���e�mt�p�m{��e��o��������in��r$}m8{���plo��Ėm|oЖa<���fD>��l�=6��eDp+$�e8�odp+���.DD��,�l`�xOD�r�q��L�aDq�X�a��d��eėg�r�s(�w�T�
�&(Z,��iY����s�q����ldl(
�a���iXo��s��Зa�s�ؗa�Xi��o�ut��|l`t!u@�i�t,�l�z�
�v\�oh��<�l���D�o����P�k(w��a��dL�f��g��i��k̛l(�mT�n��p8�r�sX�tПv w�gLx�Xx��a�e(�oP�rl�s@�u$���x��ܘb�e�k�r�t�x�@�+P��,����y�� �kD�nȔ�@�el���8�gy���a@yX�nLy`�a��m��o�p�u$�w���������o��4��o�y�z����g�y���rp�=��̙izԙuTzV\z���thz�i�o�tz���a0�r�z4�a�z�����z��8�i��l�s`aL]X�r��,`�e��ih�Ć|�d,��d�-��e�l�p �s���f���hx���n�	�hn̚s\mԚe<������u��B�����f���nh��iD�uT�V\�f0�ph�8�iXa���P�o��X�lT�+�a�p�p��|�s����a��s�i����=6��t@�,��s��a�s�����ܛd��n$�$��a$$�ȁ,�n$�<�aD�eL�oP�i��i4�i��t�a��g��sp�f�9.|�h�gȔ��0��{��o0�,��k��l,n��Ԝaܜe�k�n�Zo,�s@�i��i������k��]�i�����l����al�H�e�, �w����d�f�i0�kP�lp�o��t��vL����dh���`�a��p��rx�l�a�4c��o�Z6H����������m���0����m؝oP��D��Нpp�|�m�k��]�e�����r��^0��k �]�i��$�l�������<�g��D�o3����\�.���d�td�����|�.��a��iԞj�yd��t�����a��l����n,$7p$.g�Ȟa||a.�{���l@�Leh�^���a$�k,�oL�p���p�i:���isTIi4�r���@�e�x�a��e��i��o��rȟuD�i��a<\�xO��i�Z���9.�����nԼiH�iȯ��i(��;+�8؟o��p���sȷ�
@�a��d��eP�h\�i��k��l�n�o��p��r�s��ud���#fX�gx�l��m8���Hut��ĹBd�sй��l�e8{���������g�����aD����rh��Рeܠg�n��r���nd����.��x����iD�i�ax�@br,�B�a$�g(�<�e4�F���0�uȉ��8�rT�,D�o���|�e4�0���h�e����p�l�������k�]��i�����l��Сa�e��o���X���ȡn�0����ܡn���i��g����othe	L��L�fd�np�sQ��P�,�i��i4�g����@�a��f��Yt���\�t��p`cf@������|�v������r�����el����o̢u$��������t����)D�)�Ԣe��{ܢr��,�k�m8�nL�od�tl�w0�4��a1���s�0 �u�/,�eD�+P�D�n\�r��!��H��H��e��s�L��x�s�����l<�����Of�g �lD�t������ax�d̥e�f�g��i�k��mħn�o�px�s��t4�uP�y����7Hut������e�����e8�o���0�o���jt���i����P�t����\�k��r��h�aȤe�i0�ox�rl��t�����m��$�,��a@�����s������kh17ܝ��Ԥl����ܤr�����e�n��e�t�����s0�������st��$�eD�od�r��XP�p���x�F�e���X�d���(��p�i��o8��@����aL�����m�
��dr��<��e ���t���s��`����إ�tZ$xnp��o������o���.,�h<�lH�ph�rt���������4�o������P�g�]P�n�]\�iD������t��l�]Ħn|���b��,��uس��k����s������g�����Цa���ئa�e �lT�nx�o4����sT���Lpg��]�a0�i����kL��,.��<�y��]H�eD��
��t�`�s �l�m8��d�Vp���n|�����aD����advj�d����j������d�g4���Чn��t@��$��3.�h�,dyT��\��h8�s��{�i �,,�kH�nL�i�����.�P�r�\�a��g�h�a��jȨm�p�t��m0�����k�$����r0<��u�/����o�;��.;��Ԩt�<�o�]�\����i0�yD���r�<�e8�iL�r�B&�f�� k��@�e\O�.xOX�n��r���d�a� k��r��t�a��h̩o�r(�s��,b��P�mb,��aXx����ĩdܩn���Dd�	���/.�	���l<	��o�u�s��.�s���k�	,�mwH
@�s�h��$��H�.`�s�hؙg�p�s��t��-h�a�c(�ex�i��j��k\�l�zm�np�o��p̬sجtP�w\�d��Ītp,̪a|�تhliD�gj$+�����n��e,�X�p����4�n,<�il�u(H�h���d�s����n�!a�!��n$!����et$�$<��o�$ثa�e��i�oP�rLu�$f0��`&�e�'� '�s(+�'�e,�p8�r@�uH�w���3.��+��+d)�)���i\+4�3��i2d�l��o��r�5�t5��r�6���d�6�8�TIe�@+��H��e�=6��t?�	ԭ-��.�a<�dĜhd�ip�m��r̭wP?i�f$�k,�l4�r�?+XQ+�?+h@+�T\Cد�D�e�C�L�pXC�X�m�g�p��E��x�t�E���a��e��o�I}(�o4F��v�F��F����o0���@mH��ܭdX���n����o����l�N�
D�a��e�hԼi@�jH�oȯr�s�t�uO�X�kd�s��tXQ��pk�S�p�t�S�������x�e@T���i,T��jU���eЮspU1|U����k<e`Ȯa�n�p�t?��>�oaa<��r0a�b,�e4�n�H�g���(�ag��h�d�p��r��shlY8l�\�m��a��p�n�l��x�a��e��o�a�l��n��<mu��k���0o��eܯotty�r��tT�iLt,�e0�kH�ld�p��r��t��s�m��r�u�r�u{$�a@�o�u!P�iT�aD���v!�v\�oT���L<.0�p�e����|��H=�����v���ekw���eذo�uh��8w��İtDw��̰e0��xx���ox���l�w��e$y�(�iD�s\y�\z�hz0�t<z8�e �a�P�efX�h}d�c(}����-p�a̱dȲeԲg��i4�kh�lt�m��n��rܳs̴t��w<��P�g�kH��a�id�o��s��ut�:�i�����t��a�����f����e,�nP������$�i<�k� �7��D�g��L�r����X�o��7p�d�<x�y���t|�i�iT��`�]��ol�����i���rX���h�7l�r�k\Ё����l(�sT��|���l���؃.���a|��gm�<�lH��D�t��P�n���\�o�� ��v��|�w8�����yĐ�mi0�����e@%�	�$��k\����a�eX&i@�ȳk�l8�oD�p\�t�&a`&��n�,���p��4�i����7�$�w��4,�uؕ�P�aX8����n4����e��i�_mLB��l�t�@��t�r(d��C���k��s�����e�d���`�a��k�s����a$�eX�oh�rh�&4t,�yXQ���s\�X�X,�thW�sh���k4�mXZpmP�8�k�@�al,hЗ�H�m̄o����a��e8pho��x�phq����i�\���bܵr�v`�s�����s��ĵnt��еe@�\(wi\��o(����`�e<�yĜ�r@��e`�i��k4;X�.�IX�.L���H�v���T�eh���l�t����t�e�a.���l�����a@�����n�r ���e�(a.'̶l���ضa�k��x��n���
��oT���.`����a8�et��0�0�uT�i��r���Me��� �`�t�h�s����t�e��j��l�����n����ot���d��-d�.d�aܼbD�dh�e�jf�gT�h��i��j��k�l��m��nL�o��pl�r��s�t<�u�v�^wd�y������0�.<\8�e�YD�i��nиrZ��P�aܸd0�f8�gD�kйl(�n\�p�r��s4�tмv�_��]��v���`i��i``��u`��ĸdf�$�-��.�Za�i�Zrg���p.�f���ed�a�����g���e�i�k���pnTr�`�et�i��k��o��t�ril�s�r�s�h����|�tXs���e�si��s���Լ�t����i�uB�s�t��Ĺe�f�m�u@�sv��r���
l�x��o؇��p����oD�sP�t�؈,<�p����Qjt�l�r��s��t�i��y��.<��|�p����aȺm,��.t���f$/��fe0�4��e���m����Ժa�kL�m\�s��t��:�eH����n�T&���
0�a<�oD�r��@�rH�i���D�\��̕T�kx�t�ap$.��<l�e�{�t����yP�<ja���j��oлpܻs�t,�i�:�h��Ȼi�=�@�����e�i�@�C���.XC��lx�������a\�r��$�ap�e|�j��r��s4�ȅ.dS��P�eP�0���h�l���LiL�B����j��X�#\���.h�����vt�����o�ļl�,�e8�rt
��	�oĭ���o ����kh�Bt����g,� �n����,�e���P�w�H�ca`�i@�iT���
H�.��aĽd��e0�i��k��lL�mx�n �pD�r��s�uTri<���k��r��i���ܽe�o@�+��Խr��+�����f$��lDSn(�t����H�a �u�L��\�@�eT�s��L�n��$�	�V���.��\�kl�]h�eD��t�n��r(�t����rt����tp
��k�
����a�o�s4�t@�y�!xؾe�o�$xn8��Ta�{��rh,�k�a��� �k�(�a�8�sT��m̕#���X�s`�r$��l�a��n��oпs$�&�,��lHjm��s���p�a����u�,Ŀl��m�o�����d���e`�) �p�$��#�i'Bx�a��d��r�sP'��,�a�dP�et�i��o��sD�t��u�_1�.�'l�p@gLg���i,����m����(���y �����d�(��i�t��T	`)��.�a�r,�w|�������gH)aT)��y��a�)H$�y@��4e�*��8�k�)D�nh�sd+9+h`�t4.[$3��2|�k�5*��a4�e�j�m�o0�t�5��gD6aP6<��e`�l��d�7��e8(
�n�48����dd�>�8���.�8+�l�8<$�e(:
X9�<�j`�o��sH:�
T:��X�p�uD�u<l�a�:,t�j���n0�����a�:��r0<��.��i��l��t=b8>i�@!D@<��eDHa�G��l�G����e�E���r`_�4�eH�l�h�@�p�c(�s�i�m��PVy�,��e��i̕F����d�s<�l�rH���x�a<����s��tT��H���t���L�����n������k�rh�s�����e��k��n@�pT�s`�t��aН�fܝ���a4�o@�u����,�m�:i�J��H�p�P�m�\�a��k���ȟ{x�ax���̶t����r������e��s<��`:S����sP����e�lL�����g�k(�s�[04s8���a����lx�[<�, �t��L�tL��4�e�A��qk��@�j|�s��z�u.�,t�k��t��u��b��e�����k������p-u���������o�����kt������o��:
����s����m��X�a��eP�i��o��u��y���$�a0�4�l<���<�a|�oh���H�k��n��p��s��t��a��t�k�����.��tD�/
����rL�e���t���e��i�k�p<�u�t�
�����e�+�s����	tGip$. ��$�a���0�r`+�H�gh�tle|�`�ex�s��@K���sX����o��s�����e\��Lna�'��k�%����s�(�on,H��e�X�X����m�T��u��a�{���e�z�o{��(�d��i��s��t(w��
4�e��f��g4�kP�lX�mt�n�oX�p��r��sd�t��u�}��l�t��a������lh���o��i������a��g$u��t�t��s\�]��e,���r�s���h��k��� �a���(�aD�o��~l��$�d�hl:e��B����l�e��o��s��t8�@je��rHjm��b���p��.0�,��k��t$�+p$.�+��ed���.s8�|�. �th���r,�t|�m����.X�u@�eYa���8�l@��h�g��L�ap�dT#lx�s���e0��x���o������d|;o��t� at�����k�����a��s��u�	{(�,��o$E�
����i��pk�tX�d���mج��i8�kD�n��lg�0�o$h�� ad��D��P�s��X�a�ke��a\��t�eܳ|�t|�����sȷ���e(�iX�o����s������ah�����l��r��,�B��dD�����a�o��i8�0h��l�����s�����e �m
d�j��4�t�@�rL���L�oԞ������a�e8�i��o��u��yt����k��m��t������a��e��kH\�h���b�=��i��ii��s\�����t�g���n�s(�u ��
 �t��e<����l�]0�aT�b\�e��n��o������s��t���l����.<�p�lL���|�������Aj���
������s8�$�w�4���
��b��d�e�f�g�j,�k8�p��rd�s(�$��i��g��؊$d��	4���$�.��w
P�o\�rX��
D�fH�p������
t�a��ox��6�@ap�|�p�+H
����t$���s|���t�,
��ah�i��j��k$�l0�mP�ox�p��t��whp"d�k8�lD�sX�t���4�s$�tT���xg.���,�mqP�p,�D0 &�`�d��g��nԦ� ��|�i��o� ��!
$!����g�$t$^�$K�$��a�${��a��e�o�&`&��t�+�'��fX]n�-d-���j\+�o�.4<�y�/�P4�d�.2D�nX�o��rX�;���or�8l�oLB��L|a�@����r?��elJi��oh���D����tDD����e��nxE��K�xK����r<K���a�e�L+<L���r�N�(�o<mu�h�� �s�}�(}��4�bd�ep�i|�n��s��tH���\�b�����t8����t��(j��  D���o�����p4����i@���t��@����e0����d����d����a�e$�jP�rh��4�m���g��ih��8�0�odr]8�o��D�iD��`���\�n|�p��s$�t����a��l��o��rĸ7и����dP���r�����a����a�����s8����}P���k�tT�� ����e�����,�a8�o����hca�b��0�nP�u(}��a��e��i�����]d�vlg�l�if�x�r}��d����<��r̀��t���sp��.,�i��s@�����a������n��d�.��a\�b��d�e��f��gl�i��k��lD�m�n4�o\�p��r�s�tH
u4
w$y��Ü<at|�T�nL]\�i�Yh�m��n��rZ��t�a��b�c,�d<�f��g8�i\�k��lH�m��n8�pT�r��s��t�]�
��s�^,�..``	`����dU�(����ef�tĻ��f��of� �i��v�i��\,dd�ep�h��o��t��u��v��w�i�|k�y{��jx�e^4^i��r���.l^����nXke�^�k7��a�e�iH�r��s�u �wlk��Gl|k��atl�Ll�bm���e4�nXg:@�i$m��(�l�g&�mBd�a��yt���d4t��X�a\��0�p�rTv��x�e8nx�mH��b��k�m��a��i��owaLnaXn��n�n��n����p�n4��e �p��e�ax����n�^��aq��8Cn�p,�g�s̚sds4D�eTr��P�l��o��w�����r���p�e�s|�pت���e,�����txt#��ady�t����b��e�o@KtH�r�u��e,�.���ry�o$�&��i�{��$�gh{�0�r8{�<�ad�e��p�9�|�\�u<}aH}�p�a$}�x�l����a��f��k����a�e�o\�r�����d0�gD�kh�s��t�yԀ����l�eXs�T�����k$��ԁ���m�v����e��e���(�ofL|��<�l�r���ĈT�a؈,\�a��e��j��k��o��p�t��a`���u��ả<��o̊a؊��r�e�Ot�����d�����a��e�Qj�Qr���`Utd���Zip$.ܸ��a����dX�st��(�al�e��l��o�p$�r���H.���$��d�s��wKXJ��|�n4J��e����������������|����o���8�g�����nԺ ����a�����r����aT�.��m���e@�i(�����h8�]4�gT�B����L�iL�ml�rt�t��et���
<�|�a���i��t��u$@a������n�����a��op�r��XHi��itI��b���,��o����h�o,�s�k|4����m�L��,$�o8�Dl���8�o���D�l�P�o�c��o�h�i���p�t\���|�g��m��
��a��et�o(�rd�s��t��u��w�y���|(D����e��,�7��l0�����i�l<�sh�u��k����kt����e����(�l��0�eL�mt>�\�k	d���T�ot���\�r�����l��n��ol�
t�����id�Yl�����tT������n0h��g��e,H����nD���nܧ������qr<\��st��i����a����aL�oP�

T��8�a���@�md���i,�X�lx�o��p,�H0qa�p��e0o���e@����r��4��l���o|�����s8�i��r����a��u������������t�����sT�d�.t�a��e�f��g��ip�k �l�n��r �s�t<�ux�w��y�����
Trg<�l�k|������a$�����d��k�l�nD�p��r��s��+��e��i�H����xrt	��e�������n�����o������ol��Ud��h(�v�v��������0�s��8�eX�ll�r�����e�a��d�o������x�sT��\�����ah���l$����a��t`�}�RaD�<��r��B��.x������d�����.�r8�sP��.8v���kt�]�u��a��$�n��,�eX�kx�o��p+��P�w�p4�d�.�z4l�m������e(����r��\����e��l��n�sT�t��a0���r�����a��d�o��
��h��oXX�r$�e0�o8ae	(�u}��p$.���<�e��H�zl�}��a��eD��`�n��r��s��w$I�H��r����a���(�������e����o�,��p��t��	,��@
]��a�+<�a\�it�o��r��u�
���d��oi,.���0�e�k��H�g���P�a���$%p���h�p����n(���a���p�k�����i����t�����sx��e��`����t��g$����a �d4�i`�s��t����.����,�g���@�w,H�a�,T�hp�ox�p��th$ ��p$.���e��o��r� a� !����k!���a��c(j�5r�ah���l -�P'����g�tX9X
\j���<��l0<�eH�kl�lpim��o��p��t�u�={8a���.0>�T�p8>`�a|�od-��6��Ѕe,?��r�?��?<��iD@<�
e��i��o�@fA��Te��mhEw
h�a�S�dS����t�A����r�A��a4Fa@F�nLF���iL�lT�o�E��(�kd�s\F�Јe���H\�g$J�4Jp�aL�L����oL����ntM\,M����h�L��rܧ�����@���P��P��a@P����a�lp�o��r��sT��LX���a��tXX���a4�eD�iX�utX�
�X��X�<�e�&��%��P�i�Z�
�v\Yd�p`\m��g�[]|�e��+�]i�\,��l`_��a�et�h��l��o�r0�s`�ua�p(t�_��k�e0,�k�e]��i�e����l�c�k<�nD:p\�r�eq@g
�f�4�g@hLh]H�d�g�P�iXli��t�k,h�u��C�n�hn��n��t�m����e��o��uPVy$0o���oxof�s��s���i�s��rTp��w�u��u]�et]�i$�o�u���fshv�\�.@�p�z+��jX��H�tT�i�]
��b�e��f(�gT�k��m��n8�oX�px�s��tX�v��x����.��fܬ&�W���exV��lİ���iԖ�\�����ol���i0�o������dP�kt�m��p��s��t������(�p@	��<�t���D�e`�l��]8o��Tl�e8������ix���rD�bL����.���e�;k���;a�����la��r��a`\��eإ4��e����,�a���k<�mH�t����s,HH74�f��<<�y������lt�s��wt��|�d<�,h�i��j��p����V�����d����a��}D������s�_	L���	��f�g��h��k��lxr��s��t�v4��xJn8�r@��aP���aT�et�l��r��u(��i`:@�t��H�s$�r,�`�b8�h�e��e8�aD���i��_	��H�a��o��w��k	$����a a<�e��tx�8�,Lqe����h��#�eĜs0�,.P����e��� �yĻ,�r��eԼ,D�tL��L�s�'�X��d�o�l�k��o��p,��L�nT�p:aL9����rH����e��$�j��o�r8�z`@�xdl��nt����o�p�����a����e�o�r+l�����$�i���,�rL�����D�e@�L�ilm�k��d�t�h��l�n�Nx�o������t�����a��et�i��l��n �o��r��s4�t@�ut�w���
����nL���a���e�i(�l4�sd�0����k��l~p�����a8��L�e ���
.,��@�l���..���X�k��l|�d�nL�Y ����e����i�]��e��i��o�/
����d��pH�q
�<
������s������il�]��e,�Y���gD�s���mP�pd��T�0�i��,8�t����d`�s��,��	4���h�d����p�n���|�a��e��i��oT�`���d�����]��b�����Qk�����a��,��m��p�u(��؟odI�
���d��i(��0�� �it��(�w<�
iP�r����,�e��+,���\�g��t��]d�a��e��i��y���+������e��tL� �������l�tn���a��i��o�H�gt�jX����g,�o8�w�(����e�
�l� �p!��p(+�|�aP�e|�i��o��u��y�]x`h�Yd�n�+��p�a��g��k�p,�rD��ܧ������,����u���p܎n�,V�,����t�,4��o�,����lT��X�����a�.+��a�.��l���Qa�.�� �a�5ba�5��8�aX1��D�rh�s�7��7`�ad<��:��t�lA���t�>����s�������o�A���rPF���t�E����i�G�G��m�G���itG����n,H�@�a|�e��o�s�u ~��J �eJ��(�t�H4�g\�sL�\�l�KP�p�(�V��h�e�Tp�ls��t�AexX��a��o`XlX����m4j�j��rth��o�C0XC���g�nDp��i�k,��t�u��z�z���kXx�u(w��(�d��e�g4�k��lT�m��n��o��pL�r��s$�td�u�v�w4�x��.@||�sP|����a�l�r{����f�g �n4�pX�s��t��w�|e�|��|]�i�|i�|���a�l$}����s4}���aex����a��i��,�aH�lP�o�e(%e�صk|�ll�t��w0>a��g��p�a�+4�HL��X�����ah���a4J�a���k�_��k,��a�g�l �n��eo�n��s\m�i�t���a4�����,�nL�r`�s��y,�����)i@�,T�lp�p\teD�+t���x�nT�X���a�����a�g��i$�m�7 �!��d���r��o��k\����nl��e�s�@�
�s����p��oБ��O.����0�p���<�o$��H�p�Ys$I���.�Hd�r|�p�a����|�a��d@�g`�k�s(�t0���p�g�kD����a�e�o�r �sd�u�MaX�#XO���tē���r�t,�)�eX�.�e4���w�����,�aȔ�4�ae�$eL�a\��T�a��e��l��o��r��wD�$��tP���r8�:��$�wȕ} ��p$.4g#��a�����{�e0�,�k�t$�ap$.�l�+�e�ixpXC�@n"d�� �p@�rH�uT�wXuadu��@�i(��
$J4J\�a���d�w��p�e��ih�|�d�f�g �mD�np�p�rr��t �|t������sL������r�uy�u���n\�]�o@5s�4���sD�i�n(��eh�f�
.t�,�gT�8�aT�dD�9С0���\�a��d�l�����|�ix�i��aX���a@�f�a�n����a�e<�iD�o�^s���sP��n�v�d��s(�����yР�,�n����e4�n�.��\�f�f��\�a|;o��m�j�,�d�rh�l�f��x�a��e�k�p�t<�	(����a'�,.����e����i���ج�̎a��i�CLd���l0cx<c�c�,�s���hD�o�#rP�s�k+H��<�n�,�Oa̱�|���\�d��f��s|�t�X��|�eXX����o�����lȳ!ܳ��k����.��h`����c(��i�n�b���.�a��k,H���i����-H����W.����(�y������h���H�sȷP�e��i��l�o�r �s�&XPe����|�d�P���k����ip�YX����dL�����p����gl���eĻ7����o`�&��ih��d��,�i<�lL�nl�p��t$�il1��/D�o8�X<�X�ix�`�r@G���x�u��,�e\i�oo4o����b�m����o������l���gns��������t���g����a��0a8r �t\�@t@��(uȦaԦ<n(���Dil�]PgL��T�ha`��pa����|k4����n��a,cXe\xf�i0j`k|lm�n�o�p�tu0w���$�th�kl�r����m���a� h$k	,�8a���@l,Li|u0G:G��hs�E��pn�<=e�f�nL�DLX���g� �a� ���l��k�!��a$!���d�ks�"k	d"��oH�m�"p�[t8{�	,$7m$<$as(@%g<i�$Dk�p�${Pa�e�i�oXrpu�%�	lya�i�%

��@�����s`&�u��k '�n�:h�m(���t�'�em$n0r\zu��:�(m��(�i���<s4�G��O�)]Db�)�LiP�o�*�l*hr\+�i�o�yP��X��,��sd-����s�.h.�f�ZC	�Y�dNil�kt�.��a g,kLr�.��ale�bU�o:�,��t�,���H.<e�r(��/��Da@1�L1Xe$/�`d�e�l�1��r02
8/��U\��m�0�e�/�e�o�1Kl1�o�t�k\3\2�g(n@odpxr�4$8r�4�eP4��d�Dg�4at5��gXr�5c�5Ps6�p.��k�6����d�8<�a�i�rиkH8�rX8���a�r�8�:���n�:���e<���i?<a(bDe�i�j�o�r�y�?���.P?��l`T��VqA�0n�@��8g\r�4FLB��Tpls�Bx�F�C��tlXC�|g�k�s�t�C��f�8DUr�h��D���tDD���ePqk4F�g�E��edJ�XHns�J�xK��З.Dp<K� aLety|�C<L�`r\�0�L��Xf�+xM��lk�N��a(eli�o<	r�	s
u$
wpP���'dO���f�n�rf�tZ�lS��e�R���g�SvdS���t��[V(d|U��nU��eLl�Ya�Y8eY��@o�d��c��Xs�c��`e�n�sf�e���g�f��f�n�f�a�f�a�k��h���m�n�p	rlY�k���t���8l��r�l���gq�q	a�p	d\	ih	n0o�(	ex	i�	o�	y�q�	hqiT	s�+�rdr]p	e8s���..�r���	l@tf�����	r�t�	aLt,�	a�	o�	p�v��n(=�0=���	n�v�	y$y��$Ei`{�l{��
s�z�
i<
y���x���4
f(}��4�b�
g�
iPk�lm4vndp�s�t
uL�iX����
-|
a�$k,��
k���
s��a���
k|���
e�����
l�
m0nDt ��8le���
ot`E�4E�e��x���v@���$i@��؇�<s|���-pa�l�od��p�ha�� Kp��]|i8� ����pЈ�p$a�����n����eT����a�p����aD�y���9e�#4gd��l���kx�]o���(r�*�.�m@s��LyԏXl�+ؕ�pi@�xp�t�
ud�����k4���a�k�m�vL��lg��a�g��a�z,�o���o���o\�#��s�e��
aL
e@���
gl
s �$
e�
i�
o�
y���L=i�)�={X
r��`
kT$��,.��x
e��
jT����
s�"|����
s�����
eL�aX��
k(�p�
aex����
s|a,�
e����`�adipo�s`���f�g0kTmtn�ps�t���.�W��LkxVXn\Yi�d�x��,.Xx|e�@h�,�c�i�k���nl]{Lna8�������k��<�er4��t��s(}�Fh	��rt	n����$i@l�4Lu$��(+��.P�e�,`lD���hs�����m����r����a�l�n��t���e�u���.�����s�JmJ���t�H�g����aD��8lP�aPidp|t�u��	���0f����tRl��Dn�:��\ito�� ��lJi|�i��i����a�r0o�<QaD'�-d�.ha�b�c�d,eTf�gh�i$j�$k\+l�.m�/n2o�8pH=r�=s?tXHu�Jv<Kw�My�Y�\‖����.���,e��8i��D���P�ZH�.�ad,f4g�k�l(m�n|p�rsDt�v�Z�
�u�Y�d�k�HlIn�p��r�sHh�8�iH\��_��b�Lg�f��m r��v�g�
lg�e�i��k7X.hateQi|n�o�td�kl�|k`lLl3�o��eptxm��p@���r2�a ~��0�.�w�o��e4J�Tr����.�aek(rLsTtDrdyd�rBXs�
4������kt]o@uX������8s4t�t�x��i�|:�o��`m�tlm�t��xa�b�f�g�mt�>dy�u��vg�o�va�v�l$w�	�x��e�xa�x���i�za�z�oxzr8{Hb\ehp�s�{�{,@a�4+|�Tn$}��a�Hpy�~xw�e	�l����a�dg�ak����M�����g�k���arT�a�kXs��Qa4���u���	 a0rPs��e����](e����<<e4��Dt������\tT�,dat��ph�p r�����a�e��������l����0bb�Ke�o�t����e�����i��l��e	m�e,pL�
L9�h��$e<r<C���LjXlht����w���`aH���.h���tnt����o��l�,��a��e��i��l��o�r�u��y�����h���a��i���aȬeЬiجo��ru�cw$y\��|�����T�H�.|e�g�i�k�lLm�n(p`r4s�twHy�È�3.$���pd�f�k�n�prTs������H����,�el��h��a����o���lH�C��ms����a0eHo�(.l*+�(iP���tKt(�<e$��xo�wl5|?hdg�lo|��4������n����a����r���kx�����d������ad����p�2t��	\���ek,lDnTs`�3.�	��$a0�|Vn���8a��d$|�.d�ap���`k��,huD��th�s�	�m��ak�p�t(I�e��
�a$e<f�i�o�s�
��
�np
�an���t�aP!��l�eXb���0aP�edi�Pr�
Lpd�VOd����xs`��k��x�fL�o�p�@%e��k�{�ah�k�lp(tPa\�ot$�Ze|?P?��a(Ha�,!D��4g\lT<a|e-����VtD��ds���pk����in���a$���d�g�sL�T���r`,�ol��h lr��o�t���r@��,���sT�,u�#hL]��'�4e�'��<s'HfP'��Ta|f�st(v�+�3.6�$6�n�5��i�k�tX�6{�ra�a�d�l�8<�a|?��?��:��p-uX9��u������r@�a0<l�a`k�l�p�t�u�w�$��={Xa��|tT>lt8>xe�?<�oD@XH@�r�oudA�e����<2e<L���k�A�p.�Fe�K��tXJ���s4J�e�@�DyDD��f8L�$oDL0tL��<s@P,ta|i�Yl�o�r�u�P�xV�\Y��[�4^�`_��a�e�i�loru�_��s��e�b�e�c�`:sdl��m�PVyTp�t���,4apexi�o�u��D.dr�E������.�Ln���XoH��<��ȉ��s �����l��
�o ��h,���
 d$ e� f� g!m$!n#p(#sT#t$u�����a e o���e�����.�	���T kt s������4 sl�]< o���H wp��8�` i�#h l� m�1��i� e���� eХ�Ne����� l� r<�a`\� uإ4� e�b���� o� r`�]<o̯Kد�!l���!pL���
��.h!a�!d�!e��f�!g��hd"k�"l�"mxr�"s�v��F0�`!g�N����t!k���|!e�!r<�u0Q
DW����t��!n�!sXXyHX<�!e8X�!tP��"a "e8"lL"r4��"n@��!aH`�`:��"s,�x
.8�,"e�t���]D"e\"yTv���@cx"l�"o�}��	tg��g�"gXg��"i(+�	,��
e<��"e�"i�"l�"n�lp�n�nȹ��"u�n�ȉ���p.T�,�"oL��#hT�� #t4��dS��4#e(��<#r��H#ax#e�#r�#s�#u�	h��p#m����#e�#i�f�r��x�mdr]�#e��aPu�#k��#i�#lP����Fa���#r���#u���#mt�<,$aT$et$o�$u��7؈����4$s<�<$n\���H$a�(
���`$oP���h$kt����f��{�.�$a`&e 'i�'l�'n�'o�)rl*u+w@+y�Z����$f%rL��$a4%f@%kl%lx%m�%n�%p�%r�%s&tL&w��#���%s$%t$�*���
����,%e@�gX%e�wt\�2�rP%bv�l���d%f��/
���
����%t��B�%i�%o�%r�a$���%n��al��%n|����:����%.�%a0�	���	���H�&a$&e8&iP�a8���&l�f����0&t����D&e��$��X&m�&n�&p�&r�&s�&t�������&n����&l�&r�������&a�%e���
X����&k�&n���	8��fm��Ah��&s't��9��
���'m|�'e\'kh'l�'p�'s�������<'b��}D'u����P'l���x'j�'o<	��Dl��&��.���%.��l��4��	���'d(e<(k\(l�(m�(n)o0)sX)td)w$)ì�Hx����'�<���(n (r�'�x�G������((a4���0(aL(o(�P���
h���T(lp(ox(w���	��� �����(b�(i�(p@�V��9����(e�(f�(g�(s�(t��e	�Y�Ȕ�0�]�9L�dT��(d8��(r)tX�a
`��	����)����	D)e��a���<)e�	���P)tL����l��l)n����t)a�)g�)n����)a�)e�)i,*u8*y��t4�`�i�)e�)s��{�����)t��K�����]�)f*n*p*s<��D��p��������$*i��XD*t��|x�a����L*k��T*a<�`*d�*i�*l�*n�*r�*s+t��������*k�*pH��	�������*d�*tP��X���������*f���	���r`���*a�������]+n���+i��(+rt���4+dT+f$�wh����+a,e�,id-o.uh.y�,�T��h����+a�+d�+g�+m�+n�+s�+t,w�/
L�����	����+d�+g�+t`3D"��	�	,0��tdH,ex,i�,k(�l�,n��t�,u�,w�,yl,�8�@,pX,rH|���	��.x���`,���������,g���,e�,s�,t�	�HK@
p�	�����|��.���-b-e-g8-n�����0��-r`��P��$-e���,-g4a@��D-eLL-eX��X-bHof�-j�-k�-o�-p�-t�-w؊���T���-n�-s@�	��e���-a�-eX\�����-r!0T!0`!�-j\&��%��.g0.i`.s�&��&��(..H.aP.hX.t�	P�O���'�	�(�e�.f�.m�on�.p�.s@)��*a�*���.iX*�.u0������.f�*��.a�*�	(+��.a$/eP/i�/o�/u�/y�+�/d/r$,&�/�.��/tX1�8/l�3�
P3��0/kH/tH4��:�d/dp/s�/t�:���.�<�b:
�=�x/h�?g�>���/k�/l�/n�?��3.0@��E�tG��/n�/t Ha�G�/n�G��/i��&,H80a�0e<1il1o�1u�1y<\�	�H0i`0mh0t�H$0ap0g�0m�0p�0r�0s�0t��vI��b�J��0e�0sdJL�J��.HK/
�KahK��0oXK��0r�K��Kp�L��T�e�0k�0l1s1t1u�V��VX8X�	xX�	�X�cac]1a�b��$1w�a01kX1t@��Be0d�L1r�w�
thd1b�1ed�m�1o�1rh���h���1tj�k�
�j���1m�u��u�1i�v�2-�1e0�0�1r\�	P��`����1s\��1y(wT2e43f\3g�3i�3k�3l4mP4nt5o6p�6r�7s�7t�7uty{��Pfp2k�2n�2s�2t~X�2a�2e�2o�2u�}��l4~��v�~a�~�2l 
ex���2yT��`���2s�"
h���.3sT��3e���2tL��p��P�3u��3g����(3a�YfH3rإ���e\m+,�T3l`���h3s����p3t��|3eD�P�,�3o����3h��3p����3o���i�3l4oX1a��3eh��3m|��8�4s$���p.04e84g@4sH4v��fܐ���_	G_	����X.�4d�4e�4g5k 5o45s�itX5ud5v��w�kē���4.�4rD���4e`��XO���4n����4e�4g���l�h�U�4klV�Ȕ�x�� ���4s4g#�4a\��5w��po�0�,5o؇��.�u7@5t�uL5i�v����h�l5g�5l�5n�5r̛�D��	T��5d�5s���8�$�.�5bx�d�5e�5g�5l�5s6t\�̥k	��
P�	x��	����.6r�
�2
��.X6ed6h�6i�@l�6o�6r�6s�6t�6v����ds���L6kP�,it6oD����k\�f�6e|�������6a��
�#�x�#x�������6d�6g@7kT7oh7t���.7a7e,7r��k	|da�7e4ta�ah�] 7a��
��87eL�t���L7r̩���`7o��7a��t7h4F���7o���7h�#r�bi|���7d�7s8ṯ��7e0��D��pUaܳ#�7p�7t\�e���D<a$8k̴�8a,8o��aX�V88mH�
�\�	P�@8l�8nd���H8a�8d�8k�8n�8p�8r9s,9tȷ	X8aL9e�:i�:l;o<r�<s=u0=y��wܸD��(�X�8o�L�8oj�\�/
��
����8k��#$�.9t�\�a$���9r4� 9a<9r��H��h���	D9.x9e�9k�9l:n:rP:sh:tp:u���	e�����9l�9nh��t�]�9e��a���9k�����9a�9e�9l�9op����9rH���9tx��3.��	D���:d4:k<:mH:s8/9�1�xg.��'��#�%.`:i����A����������x:k�����:e�:oĻ0���:e�:i�:y��.���:e�GP��:n�:tx�5h���:s�����=4���;iL���;e<;lD;m`;nx;o�;r�;s�;tP��X�/
��f���L;tt���T;s���.�l;r�;t,�D̥(
�����;e�;t�����;r���	�kKd���;e�;iԼ���R����;k�����;a0<k<<tl���;aD<e|<o�<u�<y��*����(<t��	� �e\<md<sl<uT�
��(�G��Z����t<b�<e�<f�<g�<n�<s�}a�i�<i���i��Gd��D��
���<t�����<i��������i<�����b=l =n�X|��\��d���(=n@=p|�/
���h=a|=e�=i�=o�=u�\y���t=ei+��l��4��H
��6
�=aD>e�>i�>k�>l�>m?n��o?p��t��udAw8�yh��=f�=g>r8>y,�4��a�>l���>o��.Ħ�� >s����,>i,�l>n�>r�>s�pX>s���`>a|>i�e�w`���>s��4�>t���>e�
4���>i����>e$ ��>l�$�$Go\+��.H\Ga�Ge�/��8H�]l�N�.P?a�@ePChXCirjDDo�Er@Gu0�wHyO��	D9.|?a�?d�?f�?l@m$@nh@rt@txO��?r�?t`��8ed�b}�Ow
�P�	pP���?e��.�t�?s�Q���?a�?e@i@t�Q�	�re�?m�Z�R	xz�pR�xn.�to�R��xn.T@d����R�4@a�R�<@t�R�H@s���dS��`@i,T��@rTT�	<��U���@a�@d�@eAg4AiPAkxAl�AmBn0BoLBr�Bs@�\U�@d�@e��pU�|U���@kAnArV�V�V�(ArW�$W] Ae\Wi8X�DX]<AihWDAl�a�As$�\Ak�AvY��hAe�As�At���I2�"�(Z��Aeg�tMi���AjXZxn.�Am�Ao���Z�Z���Am<[aH[�Au�Z��Bo Bt(By�[�l"�\�	0]�0\8BpX\��@BaxBe�Bk�Bm�Bo�Bs�Byl*��]pBi4��^��Bt$^�
@^L^�Bm�7��^��Bm�_�.�_�Bs=��<��Br`#�BeCi$Ck4ClDCmH`a`Cn�={Lna�il`i�`��`�<Cab�c�	�Ce�Cg�Ck�Cl�Cm�Cn Dp(Dr8Dt�c��}�d���Ct<�k	�d���Cu�e��e�Cade��Ca�b����Cufef��Ca�e���CgDklf5tf�Dh�f&0��h���f�0De�h��D9.�De�Df$Eg,EkhEmxEn�Eo�Ep�Es�Et�EwDi��h��|Dg�Dk�Dl�Dn�Dr�Ds�Dvpi��i���.�Da�i�x��j4j��IO�j�EeEo�ja�j�DkEm�nk�@kiLk�	�k����aHEl\Er�k�pk]@Ee`��,�]TEe�kw
l��	�k��pEe`@=l�En8l}�Eo�l��l��Eo<m��Ethm�tmw
n0Xo�ho���EaFd Fk0o��Ea4FeTFi�FoGu0Gy�oK�o���..hq��p,FiDFwDr����	dr]LFllFn����r��dFs s�r��xFe�Fg�Fn�Fo�Fs�FtGuGv��
(��hs���FtD��ts��Fn��s�Fp$��FsP���s��sZ�s� Gk(Gs�s,t�	@tX(�$y��8Gc`GdpGi|Gk�Gs�GtH�Ty�\y��hGg�yX�Gi�Gl�a|��Gn8�I	���Gp�y}�Ge̖��a4���Gr<z�GtЗi�z��GoHy�|�T���Gs�{7$Hd,Hf8Hlx{/
������.||XLHod|�p|DHo(}�HbtIi JlXJmdJn�Js�Jt�Jy�}��Ha�Hg�HhIn,IoXIr�}���l��Hsdl�Hd\~��HiL�kh��Hfȉ���Ho��Ho�K�&i�HIsд�IaT���~��$Io�	&p���8Io,�@Ik����LIe��B�Ia�Ie����dId�Ik�Il�Ip�It�Iw���x���If,�a����Ii��|��@�eH���Il�Ir���d�o؇/
T�0Ћa܋��Jn�Ji���Jt�^"�],Js�Y4Jn@��@Ja��LJa8�7pJi��3XHP@�xJub,�@e���Jh������.����Jo@�,�Ja�e��i�Jl�Jo�JrKuKy���<������������0Kk�����Tah��Ka����$Kr ��xKa<Le�LiMohMrX�uxMy�L�<\*�YdKix���lKa�Ke�Kk�Km�Kn�Kr��tLw\�Gh��Kl�Kn$�TrX8{1�Kah{H�"����Kd�������Kt�������Ls`Lt ���LfhLkpLpxLt@���$Le��g�Li�Ll�Lm�Lr�Lt^[���	�����x�7�����Ls������LgЮ1�1����Lmx������LeL��Lr�`����L�T��MkMl��� �3���0MePMrt�G|���(Mg�x��<MtD���DMs`Mt��9t���$x���pMg`��Mf<�k�Mn4NpHNs�Nw�T�$S�Mr�����Me�J�J���Mr8���MgD����MaNs0�a<���Mn��MiNm\���+�� Na���(Nl���P�@NkXNt �lNed�GT���dNr@���Nr �xNe���-d�.Oa`Tb�TdUe�af�agbh�cigjlgk�gl�gm$hn�ho@np0orLts�wt$yu�zv�zw�{y�|zZ��H�.xOa�Ob�OdpPf�Pg�PiXQk�QlpRm�RnXSpdSr�Ss,TtHK�HOm�]POa�Od�Os�Ot�Ov�Y\On�]9�l>�^,�Of�Oi0_B�_9�_�d��d�ObLgf��Om�OrPslg��{o�ga�g��Or�gPe,Pi4Pk@PnHPpXPt�e��{Lna�ge���(ha4h�PPehPoDD��i���.\,d�Pe�Pr��s��v�Pw�iB�Pl8j+�jJ�^�Llm�Pn�k���Pe�Ps�Pt�fm�mP�o�Dq��p�PlQn@Qp��Ppq��Qv|q��q� Qs�q�(Qi�q�4Qe�r�.Tr��LQe�Ql�Qr�Qt�Qw8�T�stQpds4|Qe�Qi��tt}�Qo�����.�����Qlt��
xt#xy�uB��e�Zm�t���QeRi Rk@Rstw�PwRg�w�a�wRa�6a�y,Rr(y,4Ro��:�Je�<��LRdt|�XRn8{�dRi���
���|Rt���Ra�Rr�Rs����RdSg$Sn8Ss4�}�Ri���Q]�RnTa���Ro`��Rt��V4��Rt���Ss�gk	�gSa��؈,0Sk(����DSot��LSr������i�Sk�Sm�So�St��D�3.0����So�d�l���Skt�4�Si�\�.�Se�Sp�St��mh�+�����a�3q,�Tl�fTo����Ts��� Ti@TjTTr�����iho!L��LTa�,|Ta�Ty�����Nu����pTlH�aT���Ti4�@�Te��Tt����TyL���0�g����Tn�����Tix���.��Ts\��TyT����Tb\Ud|Ue�Vf�VgDWh\WihWkYlXZm�Zn\o\pX\r`shat�au��g��TUo��L���hUr$���pUk�Ul�UmVnVrdVs�Vu(��8���Ue�����Ue�Uo������Ud����UoDx����UvX��UeVoH�il�(��h��B8VaXVe�'��d�,Va�vs���DVv�LVn$��|VkP2l�Vt�Vu�=�D�tI����kXH�Vi�����tn���Vo@����Vl�o�
d����VnWo$Wr�r��r���Vi\��Vr�t��.�tWnt�]Wep��,���0Wi�8Wu����o\��PWnD�Wa�Wb�WeXhDXlXXo�Xs�Xv��I,�Wm<����.�����WkH�,�Wa 	��Wa���Wn�_^	�Wp	�Wa�Y0k$���Xa��,Xa@� �&$Xi��,Xm�	]8Xi�
��	PXr�d�dXp�lXm�,xXe�Xi�Xk�Xta$��Xn(�9�l�XeP������Xl����,�Xo�
�9pp
�Xa��kxSp�
���XaTYd$e�Yf�Yi�Yl�Yo(Zs������4Yi��<Ye�HYw����`Ys��hYe���tYl������Ye��Ye`��Yd�Yk��H��h��.����Ysx�Ye�Yo�pag�SnL17���Zdp�Zeh,Zm�=60�ex4ZsD��@ZsTLZa��m�Zo�Zp��G,pZa$��xZvd���Zm�Zoh@T�g�C����ZodJ0`���Ze�Zg$���Za[d[h[kH[oh[s�[t����.���@�T�a0[o8��`([o���,e�<[u(l��T[n�,\[a�[l�[o�[u�ns|nf�[t���[o (
� e!4(j�[r\w�!}�[ePq�s�[g�[u0r�!��!���[n"��[e�"��#�h,��4�e'$\d�\f�\g�\k�\l�\m0]pL]rP'���_-0\ap]d�]e�]f�]i^k$^mL^o�^r�^s�_t�_u�_y�'|Wixm�(��\o\(��\rt]��o(y,�.�(���\s�{<���\nH���\a]e$]p�5d��]r��:�.�(
<]p�(Y���(�D]gL�a|]oT)�X]o`)d]rT�� *�
,*���]n�)�]e�]l�]m�*!�*0^9�+���]t�.���H.�]k4.]�]n�.��/.�����]n1]^y8/�^w�1�
��D�,^aX3�4^r�2@^mh^n�^r�^s�^w�3����dx^t(��4�$4P3.������Qal5,�^a�^e�^iT5��^n`5�^iL5�\��5F�5�^s�5�^a_e<_k\_ot_w�_y�5$cf(_g�a(7L�p�6{0_o�)�@8�H_n8P_pL0Ğe,9�h_eXN�HN�_t�M�_s�9H9���_pX94�_a;�l�e�:�_i|�$�e�;�_t4;�_s\�0�i=H`n0<`il`l�`map0atH�wx���=4`t�=��<`s���,.d-��T`e8>``o�`yЬ ��|`ih.�`d/�/���`t�.��`rt>��`a�`e�BH9�`o�>i�`t�<�|<���`e@��`o�?<arLB����i�@��arD@/$aeLauXay`G�
XA��DadH+@Bg�A`ai0GG��tas�E��|an@P��alXX��ki`_��aa�ae��l�`��_�afXK�ap�f��aa�c�an�,,baLbe�bi�bl�bo<csHcy�E����$b.<bs�����H���Dbi`bn�br`�X�''�bn��^hba���tba�bo�'�|��<��
�bt��bt�w�����bhh���beȉ��H�.cn�L�
|��bt8����ba܈@<���cl�ci�$ch�0cc����H�.��(	�a��Tct�a\ceд�hci��tcn����cb�ce�df�dg�dkdel�en�fp0�r�fs�ftgv��+�co�����cfdg(dk�dr�ds��L�����]�ciD����cr���dp���daDdlLdoXdr`dw��e����m�el�e�2��2��hd���pd�ܝ��|do��������do����do��dm�����.�df�T��o����dm��������deel8erXewT���]ea$eu�����)g����]0eo ������Dea��]Lea����.�ea�ed�es�etD��.T��en��eah����x��L���cd�eefgtfk��l�vX9�ܢs����et��er@�i$faP��fa@fe��rXfs4�xol"nep�.��4fg��0dfuسLfu\�+�����lfh�fw$�#��a����.�fsL���fe��	I-��fnĽ�fa��fa�fj�H��@�r���Ĝ�fr@�get��,$aLge`go�����,gi��4gn\���@gs�+P���Xgk����gi�o��x�|gb���go|��gs���gat�i�goh���gs���iX��gn��H���gt(+�he�8�ri�7htX1��hs,H�Thetho@�a �H4heL��<hw�THhy�:
�k@`hsthhht(w��H�.�he�jfLkg�kkl�km�knlo8lp�lr<mstmt�munw4ny�j����hn�����he$����hi{���he ifDigpik�il�ipjr4jsP|���.�ox
.4}��,iy�|��8ill~Vt~��Pip`~�Xia~��dil�6r@��|�.�ia�ie�io�itp
�,V�itx�
���ip��	��|�.�il�����ii�}�ie|�����n jo,ju�����ju�:(
��
HjlPjmljw�������L,��Xjl4�H`je$���i�O��xjt�����jo���jr�����j�������a�jeki@ko�(	�,�jtD���js���jkkm�<��:���jsTki0��o�Y�(�eZ��(kdH�4kn,���7l����.����Xks��]dka�ke���pkl�koE�$�ktċ�kt$�!��ku$���.����@dl�e�kgltp\}Ȕ��krls�\�d�XZlsh�`@n������$lr��,le\lhhlm�lo�lv���P�,Tle��P�7��pll�xlr���lo�e����hn�����le̥�li|�v�����le�limm���ؠeĦ���lt��]�ln�������mg���ma0mo$�����(mm��LmfhmtT���a���Tmoج�\mr���#r�ms��a���me�,�mt|�����s�mt�mw����Ԏeg��mi̴@�mjH= ĸ�mr���ms,Hi���mnD���9.�mna����nt ���(noȷ�|ne�ni�nlooosx'п,\nkx���dnsh���pnn�B��ni���nj�����nt�����ne�B�����nk���ne �d�j���nt��nrL����no��i(ol��,oa8�����hoa�pedri�ro�su@ty�Z5t�Pod��m����Xoa�od�of�okpn8pp�pt � ���os,���oi<���oa�oo�P�/s��L�ootZ�O�t�
�oa\����ot\�Ph�,�oa pf����ps�l(���.fPps8��(padpe�pl�ppX��O.���l��\pw�� ����pp�����xpü���po�	��'�pj�pu���P�t���
�paqcqd0qePqghqi�qk�qr�qs�qt0ruDrwH��@�������qa(qd@�"���@qkHqn��[�5�����%.`qiP��
��i�qn�qsP������xqi<�k�s,ip����qa�9l؄o�qr�qw�����L�qiT
a��� �ue�qt��-�4raAjd��Q��rb����rl(��<���(rk���x�<ro����Prul�]Xrb�re�rg�rn������|rf�rk�rs`��P����rl��	��(����rl�������rg��:�w�ro4����rb se8slhsntso�ss�su�sv|������sr���9s����,saPsi�a��Hsn|��Qf����\sa����sm �>�����si���skd�g��H
����b��g�si�sk,tsDm�j�
���stP�to�����sp�tp̖E�Ltr� tt0�$��8tk�,
�ta�tePui�uj�ukP�l vmpvn�vo�vpwt��u�ww�S47�tth�tg�tm�tr(&����g������ts,�tk��nurHaT�tf`���tauy�;.4;usL"Z�!�(ur$!��0ugd�s�v�<un�ut�#��#`uk�#huiT#@tusi,$7�ue$<�ua�$�mr�${�ua�uovr<(+�'�ukX]n�ur����k�)]�uevp�)��ui�#u*a�.���a4veDvo$/���e�l�/�Tvt�>u�Ah�0o80\vt�/dva�e�5�t5�vn2�vo9<
X8���vs�8�va�De�vo�vr�;h;���vt|<<��vo�@����a��l,wn?��velJiDwodwr0�u[[B��$wd�D���|rDD��8we�F��F��Pwn�E�XwoxK���$a��r<K�pwa�we�wi%y<L��DgnP�+�����wg�L��wn�N��waxe�xhyiyuO���qt�������w��Y�w�Y��xoU��xlHxr�xs�xu�\a0\0xl�qmX\��8xatxi�xo�xu��m,.}`xg�]]hxdL^i�
@�_��xl�_�xi``al`�xo`�xl�xtt@�0av�xa�a��PYsLbib,�xe�e���Cg�c��yn$y��@pu(}���uaLye\yi�yk�ym�yr<zs�zt����g����Tygpyn�yt@�g@��؇@xys��}|����yl�^m�]�yw�Y�yn@���ya���ya�����]�ya����yn0����ykzt�$b�����zt�zr,�$za@�0zahze�zs�A�0�r�Pze��\ztl>���tzn(�6|ze,M��L�zrܧ���z����z�@�,�zl��i<����zi ��{el{i��� ����zk{l${s@����ze@{r���|��4���,{o���4{p|ĽL{p�T{aT���`{s��{a�{l|o(|rD|s`���x{dP|e||l�|n�|o�|s4��ywL��{n��{a��H���{tX���{nt���{ox��e���|d|���|r���D�
��0|t��8|a0��,4zw�X|r�d|o���p|oD���|sp&�o��|e���|r�,�|p��� ��lNeP��|t��}e��a���|l�����|e����}nZ�}e�}h��}a�}b(cHd�e�fX�g��i|�k�l�m8�nl�oԏp0�r@�s�t\�u�w�y�i}i�}s}X�.,����}a�p�}u�kf�����}g~s��}a4~e@~f\~gt~i�~l�~o�~s�~t�~v�~w���~l؛��<Lg��4~aĭ���wo ���(~kİ4�u+t]H~o̰�P~r����l�t����h~nIJ��~u����~n�'+�������~r����~h\�,�~c��4��x���lk���~y0�����~n�]e���lh�kt�f\���4r��<a�i�r�s��t��`p���ho����tt���8e�i��BL1�����d��4�e,��m�o�t,��hr�D0DD���e`�<�o�y$H�H7�dT�H�bl�r̀s�u0��IJ��4�o\�<�l8�Ȗn4.]T�oP'��`�i��o�3���d�2|�n������eTF]\6a8A���iD@<��r0<��t��tG��؀i�E���n�\B �n�[]��i@P���r@�s��+�]f��a�]H(�a�\,4�m�_��a`_��L�a|�e\ml��oЁr؁sd�u�c�li��sdi�Di���a�h��kTp���mȁrr�
o�r�t4hv���k�l�o@�p(�tP�uy�lz�t5��z4�o |+8�k�{<�e@�o|��|+�}F�}H�i�}\\�bD�& ��p�g�r��x�a��o�rL�s�u�����d�el�f��g�k|�l�m@�nH�p��s؇t0�s��؂st����tج���} �e��I	�����kT��e�0s0��,�b`�4�m�6@�i|�kăl؃p�t&7l�mh�tt�{p�a��e��i������l��t�l��������i��^��u`�C��Ѓr�0��e��<�o��������t(���i��@�rX�s؞m
�,�wܝ��4�s����;a�L�m�P�����d�a��e��lDSa
 dLS����n����etX�Х��e�i�����oԄr܄s`�����L�������a(�l�r�sX�u��w��<�,`cf,�t��a<�$�algm�8�k����@�t<�L�iT��p�d�a���p�a��e؅o�t�P�����sܬ��kąs�	�d~l�	��e�X��ЅoD�,$7�ng��ax���j�����a �ot����e0�i`�eh�rp�sL���,�a|�d��i�k�o�s�I����K̘y����rTQf�k0Q���eԥ������o@���oP����v���4)e��̆l��؆o����l���.,���r<�,�e$�i4�k�l������{,�a��}L��@�l\�o��r��il�ot�r������Ѕeȼ��oĽ����a��j��oćt�4,��P?�T����a(����X�-Їa�d �e@�j�r@�s,�7(6n0�����i�T��eh��,9e0�r4��p��,8�jZ���/s��L�a���t&rL�d�a����p�a��lЈo��r(�s��t̉u؉w���������o�]��o����f��Ĉf�r������
�����t����a�y���4����s��,�eL�kX�mx�p��vD�m(�{D�i���X���.;��`�p(��l�o�J\�\t����h��������aT���m�����a<���y��4h� �g����a@�dl�eЊf�i��o@�p��s�t{u���L�����\kX��(�r�4�e�
a�
HL�a�	T�t�`�s ��\�x�w�����-�v��r������P���Q���i���p
��Ċa�P���܊s��nX�o���g�&�,�a�����s�!��(�k�!4�ep�o �m��P�t�X�r�!��d�o��r�����gL"�X"��a",��a��oD$�&m@f&f���e�e��ċg�c��Ћn,%܋i�u�y@�.\y����t$y���i(+�@�a\�e��g��i��oČs�u�+�TgP�tH0u�pjX1��u��u��d�kt]l�o�9�x�r�:���e4;i @�0@����d�>����nD,\�.�f�7�S��Ԍd$S܌lT,�e�E��i�sPF��F��K��x"m�H �r,H��,�a|�e��i�k�s�t��u�8X\�ttW��d�s�Tp�r\Y(
������o�a��f��v@����T���a`���d�f�ȍe�d�ԍr�k��e(�iH�k��lȎtM����b<l�n�l�`�d�m��m4�p�m{<�a`�o\(�nX�lx�ơ�)p�lms�+���gHn���g,n���a(p�xol4p����aDp��a؎r�pC�q����eDq��e�i<�rt�sD�F�r���n|r���n�p�4�l�s$�a�s�0�eT�otat��L�e�u0�t{`�o�t,h�k�mw؁��.X�����s�u��g�����p�����o������lȷȏi�l�o��m���yt��L�����n��l�ax�f�g��k��s��t����
�aĐe�f4�gh�i��kđo�pD�r|�s�t$�u0Pyt���r<��\���������t�������l��
������n���eؐf�u��P*f<�����m���pX����o����o�����lt�,��-��(�hP�l�m���wa����D�a�����.tl�]\�n4�*��t�s���|�e��n�I�I��l����e��]��a4��p�� ��Бi��ؑs����e8���h�td�����g\��a����lcmH��� �i�,�e��,8�h���P�e47X�t�d�g�	p�a��eВf�l�m0�nD�ot�p��w�!X��kpxa|x��e\x�Ēe�-R���ܒo|�otB�HB����s�/���u��o�m�(�o@X�<�o�	�P�r���X�a��r�<d�aؖr��L��oe0���e��i��a�L���e8��l��r���ēo�p��Гo�r�&�s�ts���m�	���o<	��oH
hؙg��l��p�,�a��e�h8�i\�k��l��n��oؕp(�s4�tP�w,u7\.xx�g�����a|&��
|����n,��e�>s����,.����Ȕe��Ԕi,�d�k w4�l����a�����oT +H�l$ � �k�,�eP�n��L$!��d�s�$���.x�o��r<(��'p�k�)B�)���i\+4��u.�l1m�/��o24t?lЕr�6i�8��i��o�:��:���e;��t�;C�E��"sD>��u�=6�e?�
��.d�a��b��f��gĜh��itJo̖r�sP?��� kt�v�X�.`T\�a�aD:�C����kXC���n�q04F��k�E���eܖuG+Lt\�N��ah�eԼiЗo�r8�sL�uT�yO�0�d�Ag\�s�OY�O�(�m�9L9��<�k�S+D�e�SP�pU���lėr�@���x�g�Y���nY����i�\��\���o0\��gX\����a�h��p	r8l��l�q��p�k0o��e'�0.���s.���iP� �uLt,,�lH�p�v�Dr�{�(}p�r��s̘t0���i��sh�^�����n|���i@����k��,�e\����h��@�j ���${s@���ؘe�s ��e����hi���m`���DLs(�u(}����aĜe�i<�l�o��r��u�y�a�ah�t`��p�s�Y|�rZ����așkH�l��n�rd�s�r�Dr��rTr����a�e�i��o�r0���X{e�r�i�s�r�siX�et}�
y�uH�e�u$�t�u0�s�t��<�e��fؚi�k�o0�s\S�LS��h�sv�p�ev��|�e��u�m8�����k�vi��n\37,���gX��oPw̚s<x�,��w�o(�����o���ty�py�(y,(�aD�ph�tzLa�oX�r�y�@��$H`�ex�hPC�UXl���e��f�����e�gX5u��&�����e�����f<Z��̛p���ԛa0r���H����tT�]��e�����i �k���t�j/l<�nD�o,/r��eH�e����Gt��L�n�X�e��o��w�5�X�$x�r,���odK�lK��ixK����aP�4��aT�
�d,�ed�gp�i��l,�n��p��rD�s��t���Ȕ@`
����gD��n����o�1r$����k��lD�rP�s���@a$�4T���.d���X�a\��l�s����n�|�o��r�
����d��o�s�u(e��x��oНp�Cp�h,؝m�pX$&d$��et$��l�C��m�$��$�aH�eX�od�tp�u��+�P�r!�(j"�P�&`��x�i����g�$���e�#��lP'`	ܞa�d(�e��f̟i�k$�o8�s$�t'�s.`)u�i������s������n�����e�)�
�) �bD�kT�mh�n��v|*�Da�*L�a����*��`�ax�d ��Ĝ�I��eP,a,����u�+����l��r�,a�,]��u4.��n�.��.��؟n8/��l�w01t3�\.�3���a�2�n�5��50�ah�k��l��mȠp�t�w70x�n�6{\�i��o���>P7���.(7��p|7��7���a a�7���gl8���Рl�8<ؠa,9��a�&��&��r 9���a(:eX9��j4�w�:��?^0<<�pX�tl�uD@^��a@��XHd�v@B���n�Ax�i��j��o̡rԡs�B��B���iC�5g�B����p8C��C�mtD����6�o���s�����dL�e`�gl�r��s�t�.��P�g4.](�nܝ��4�i����@�r������X�s���-|�a��g��h�fĽ��r���a��oТt,�i��l�o�3��ET��Ȣr����a��ܢr0��p'�eho����t��L�7�sh����gP�k��s���(�a��e��i<�+p�o\�<���\�k��d�e���k�|�a�+��l���k�L0�ԣs���e̠0�̣o|*��)�k|����e�u{���r(w���e��g��l��nԥo�rT�uP��4N+\�o��8�l����D���N������,�7o m��h�h�t�n,���i��r\�}�|Ni�����eؤg��o$�s<�uH�y�_e�Фa�o�a��n8���4�P4���gD$�n̏,�o�&����t{0�i�<1w�S�
�S<T�e4�\�tD��h�s����t�d��k��af]��o\����l��,.(�����o����oh�ȥd�r8�8:s�e�ex��t������s�����rx�sX��(�s���0�pԏ<�o|���H�p�<t��aH�Bd�y��7l�r����x�g������a��e��iL�yd�����.̦e��nj������th���ئp����sl�]�e�j��a��et��z����p�k|���(�i��4�u$��@�d|�k��s��u�����.t���d�k0��p�y4��t|�����@5t(}����i���.(}����e�Jy|�D��Чi���اm���r�����a`�����f�sP��
d�.x�a@�e�fT�i<�j��ot�rĸs�u�wx�y��Hi���ai\�l��oZ��h�e��g�l�n|�p��r4�s�t0i�<i��t�"�
�k����.pJl�@a�$H̨e(y,Ԩt�t���s��'`���k���s����d0�gH�iT�s���Ta���Hg��<�n؈`�m���D��$��h�nt��p�eD��
������m��s��t̕��e�؃.t��h��j�o�r�y�bL4�,ةil�����o�'È��Șe�|��{���sDF��, �i�(�h`�kt�l��m��p��t�$Hkah��T�a؛���4l�aHBa�/����u�4��oh��P�a$�C�����rd�0�ap�����a0���̪r��تe�s�'+����,��o��&P���d\��a`2m��r��tīvT���$�bЫd �e�g��i��k��lЮm�n�r��sL�t,�!����x�uO����a����a��y����e<�����i�,��l����r��y����a��y�����+L]P(�,�e����s$����fH�gh�r|�s�t|���.T���<�s�(+�T�n���\�ah��$��t�a��kP2lĬp̬t�$��$��a�={��a��o>t(��D����a<��Ԭu�ܬrd���a8�dL�lD�o\�rd�sp�uT�iD�7�.���� �fdc�,�o\�iP�r�r��|dt�������t��i$�\��x�sX!(]��oD����r��u���or 
��u<�!�
ȭb�
Эnp
ܭa�r�
���a0�e\�f��o��s�F����gX��(�ep�x��<�n��D�a���P�l��r���l�n(4t�ox��o��p��`&��{��eh,��kd�ܮsTĮoA�9.x��k�s$���a4�e\�s�X�U�hs�U�n(�e��@�dT[n��r�,H�a��e��kܯlp�o�p��t�[uTata���i��r��s�a�Fx�ȯaЯu�m�l*�d�ehX.r$ a�s��ex ��)�6sP'��	�e\�f|�g��i��k�o�pX�s|�ux,�,H�m�+��P�o-a-h�r -p�a��rt-��-]��e�.��.����t4.]��n8/�
�a�e�lx�o��rıuбw�y,/\Qd��f����/^�/�s0},�aX�oT��0��$�a@�nH�pp���������P�kD�L�d�sL0l�m��n@0a��y0�&��������e��o��a�0��0��u1�ܱad�+H1i�2��4���.f�n�4�a �l4�o�4a5��ot�a���,�nH�oP�r����a�5�m�
��.;��d�l�:p�it>���i0<��mвoܲp�t@�wXJ4J��e ?����w,?IJe�?��e�:L��i�?���mD@��a$�h,�rt@+�@�8AnxK���&adA�4�a�A��.��r��sгw��&Pq��d�udCl�g8C�x�e�De�C,��o��t�D���&(�p��i|Ei��slE4ijy,Mg�Lܳrܧ����Z�\Y��n@P���o�l����r��<$�o�60�t��<�s���H�d��e��g��k �l��n��pضt�L���|�a������l��t���4j`�}�����r������lԴo�s�i$���p$.<�ܴe<�,�tP�\����gh��a����dD�s�a�0�n�,8�iT�k�#�\���\�.����d�s���p�a��e̵oصrL���|�d�k$�sl�tOa�����l�P.@�����p0Q}�uP��Q���k0���]��e���l���4�y<�,�k@�t@+��,.x�eT�o�������L�n����Yp��`�a��e�����|�s����}iL����l��r����4�oȼ���oY�h��Ķl��̶e�i$�j �o0�rH�stf��bh�i��k\���nt���r����(�ot��h�eTEa0E��H�n�P�u\���\�t�|�{��t�g�"s(w��|�e��l$�oD�rh�u�������a�i��k��o�e(�o���ȷvl�Էn�]��o��lx�����r8��dh��r�4�0�m����8�u�����g̱P�a|���\�d�����e��i������e$ra���n(�����ol�]��g��}4�̸r0���Ըg��m(}���rD���a|�� ��H�e��������t��]$�n���0�i@���<�r���|���T�o��\�o��u`���h�d�k�n(�s��t � ؇���e������t|�����ix��,�����s؉�ȹa����Թw� ����d8���nD����a�s�,NmP�pX�a��kd�pt�tL�d�i���@�sD�L�k��8�� ��l�aXo0kho����a0o���a�����r��D' waԺe�h\�iH�y\�T��m�rTHԺ�.4����e�2��xP'���o��T����p. ��$�eP�0�t`���<�sZ��m��T�aܻb�d0�e��f�g��k�lD�n��o��p�LrP�s��t�vh{f8{���a`�T�l �����i ���Ļl�лe���a�eL�gЬi|�o��r��s0�� �n�������s��f��,�e��4�l���@�ep�l�ahn\�u����d�e�����f���ye��o��P�����n,���iܼk$�o&F�$ȼt��{мa�r4��<�]�t ���i�6��e�6���d,���rT�P�n\�rh�st�u�g0$��H�nP'��h��m��s�t�v�����.ȉ��x�f���o�1\t�1+��a�1���a�5�����.T:��ȽnX9�ԽoT�m����l����l@��i<�o8��D��lP���$�����0�Ð< �<7H�t<P�g0<\�a�E��P��P|�a��r��s@P����a�jlľr�s�Q��P3.�[4ؾeX\+`\оn�\,��l@hu`_�l4�shni�m���e��iPVy�v��v� �ahv(�al�k��l��t��s�%��L�s@yT�ny,`�a|�ozt,�lz4��e |��8�k�{<��e��ox�u�|������ma�et	i�lP�oh�r��s؉w@�\�tX����s���r�#�i$�u�����ix1i4�s�'�����<�s��D�m�r������`�i�P�t�d��,|�a��k\tp$gt�u(����a��L�s���n�ih�����a�o,�sX��e�����+��r�+���a\+�a", �l�H���.,H��8�ap�e��g��k�o�s��u�V��Th�i��txXH�e�Y,�eFf]��i�d���l��w +4g#��ath��mxiU� ؙ��tl�g�m�k,�a<�iP�l\�m��o��p�t�"Hm��(�s�l�0�nh.t,n�H�y�n�|�a�]k�Yh�n�.�p�a�6!o��r|o��alo���a�u^ ����Jo(w����yȷ���a��e��i�l8�o��r��ud�����X����n���a$�iP�������gL���,�rh�tm�
D�a��i��k\�l�n��o�p �t|�uP�w� i���g��n�!Y$!����dd�s�$��a��o�%+�$��r�'���o)�24��k�?u�3i;+;���e�8�o?�8�aT�el�rt�uP?iD�f�?xA��@��L�ld�rLB��EU@GiXH�i�N���a̼e��hԼirjD�oL�uT�yO�8�g,bib,��a��m�<i��t<��nD���aĜ��s@���ed�.L�b��e��i�mH�oT�z0�����8�r�@�u��0�X�t�^`�s���l�u$���x�rT�����e��n�&�V���e�V����l��l$����e����a�����c`�������t�-��+���n(+�a��iX�.���$�ah�0�k(w��<�o����e��h��k�n,�sP�tX9�\jP'��|�tT�����r����.����a\���h�����-���a�����l�l�dJ��wJ����e�H��g,H���a?lJi� �t{i\�e�z�8�e�ND�w�z�d�	�\d�.Ԁl�l���x�a����a�����d2541270000101006201221124500701002126000022260210702006001060212600024502120600043006000007000027000000260217000123002323026521065001900070202200320600204701202100530430700000030510760107600024220250101700000602021452020420545107030040205000600342000110692005300740002760000700200541045040070210702127400072005004000730021421045760022476202760112254007002076200506100740107007000247000612025260106002741003407000670024106212202253690002206024045221207001071207621231062002449200432130800740000410076012601242030123230052267621023207001254240000374120103300040276004376230650098006300703000234500260300076100230060610030415601006506104562103017060200252305447000030100350150610017602041230013700061023002143210600124640070430075020301067020700245209002421426001365010705000400137002106207604323140740023307630065204042600240456023230402762124304321227022280001021067040005604007612074040600017070056002247000142021001102112800002159005026000125407420012104600500000073100702050004005000006740080040802105000020432011170210640012702123020452004501030300501043025100104010500020036200502050002105212450203012054010300006002125100400124003040010450124000265000050200452125620521050436521250000532054200560004002500122142004600234243000450015012522250000254021045000410250210415000120500002125001002062025000250002224521010510402121302073000103006502034001020652106030065000740200600173200670005123000162222470100704007023067000263010602221700505000010170410500021210602065026500267001030010537004331005000300651004605251500010034000600036702105013470040500001501202400540022412540007600200560000560004450301052250001060000210702105100010160210670040252010108003005030121240210680002437041022123400230171004600140200152740015700760010603036040601173010120702125420400501704720012076212223742101241224031222001320103401045021215021045023450050044004500006721043210404070011060020644006500100400100500210670000650000010070010732104320076040600217004072125605002424321240100450100170020630014500104121036000430106302450210430100400010502225000145000215105002043002502121432052145020230200450214502004510050002010320100210105021042002500100430206000204500254300760000074000024324045013520027000000070010015002105002005202023602050027910001485010060204000154020043021030210320010060107401165214501005200050212765412360107001317401047020540363020470212600100670001700217300124206702025400156100700304300164051026701002170212470300650210543065300606022800210630005200570020670212460405011010250650200632123024542101701001703002222421240020650127602027602003521053260403020246212501027400441205400006021445020260021040300340245300060040700606002006702004621072002500215410050030045222702227603004601057020023025310010065020262540209000040350300100305602500104600300600400160010452301503047001020212458700264025402117021216507222240021340232605004334020500202500102020210240016700401170102430124041040126247020247020070042122124501205402123000240124021401516320343009400400450040450001500032002453004541056002050400130020700020070001065000156010072002126000107400300450020670000160200274300512030301202128700036006230451246222710370030452674700238703006121047021067030067040035000470001700001632104602063206434070250001262212672009001005030201605002212431010650160201000204521462020254040542126526720760017402010011050052070104012054001010045072040021210704004300004500405601030040430010740304000004301354010060000002721047010010050032040400210400437004400020500306012056000154012650120700030065001243100650214000231012045030073007001115010106210721043121006100434007210015010014501026070131050021250300070220170010404007400561208900273010040222760306003400500320053010100310340217030604370142672225703002152106500207062056002670216000406500217202027640217402106700206720021004100422126730035024210301367000026501006200024324567000206500106000015602006020245202101104500211502020620757024500334205604030545004021360014000027007027000263103621076021245025620040020013250020004600050000123001004002327600251001460041053010450107001010601017602010402010051010100203022270600100632010600215110802007200010076210650000240004014107011054002003002160001004504400001500011500005300122053010021201050305403006100101004020540012530004002021007610050400403000152104406420120612067000310704036002672676510460217401220226630002252020250012600011405072650202342006302102560202400252540243021430034240341005600010260002221420202203462002122002122060021007010022024045002000220760022600620000210030060030004030206212200262650021060000124302005202124500100104004003004202126500010604000143204060000105400435400404300025400135400202042434021500114304020232106020212750020052700021040230207000210040034001202424500125404004000304300206000020430202540004540021243000143002143030054000014300125000001434010226002222425100118700100011020020243012040040540041200721256001001001041021065000205100210650050042012104304504006240212600000160003404100205400232070222207021254000134040540002562600030040420402021000510870215070210100050100700205045001005210504100500120100700301060330100700401057021032000750011501263100402321000210305110000020100702101060210100010100026050000025000232760100100141004101004030540002120005010003010521060012064004010502014501011011044214602075000304256020760001505407402147002174023570001500278125012350010042010310702352201023030614001070004021521288888800PK
!<Nki�(k(khyphenation/hyph_bg.hyfHyf0tP�����'-4�������$�'–’-111001`j��������.0�T�x�����������������������������0��P���D���D���d���t�<����`���X�� � � P!""��	��(�0�8�@�0�0�H�P��"0#�#h$�$%�%��є%��X�<�<�������\�D�D�D	�D
��%��h���%�%�������������������������%&&&��	��(�,�4�@�,�,�H�P�&4&<&T���P&X&��L����������������������&�&�&�&�&�&�&�&�&�&�&�&��	�����$���,�4�'' '('0'8'@'Tиь,T'��L�����������������������-.4.h.�.l'��	��(�,�4�@�,�,�H����.�'�xј'�'������8�@�H�P���X�`�h������0H1�102l2�2�'��	p�(�,�4�@�,�,�H���3�'��P�D3�'���������� �(�0�8�@��'d4�4P 6d6�647�7�788(��	H�|���������������h�9(���;$(��(�@(����������� ����@�8(�<��=�=>4>P(��	 �|�������������T��>h(l���|(������8�@����������������(@ @�(�@�@A<AhA�(��	����$�,���4�<��A�A�(8BLB`B�(\��јB�(��T����������������������(\C�C�C8D|DT �E�EF8F�(��	����$�,���4�<��F�F�(<GxG�G�(\����G)��T���������������������)�H�H�HIHI|I� �J�JK0)��	��	�	�$	�,	�	�	�4	�<	�XK�K()�K LPL<)\	��	�LX)��T	��	��	��	��	��	��	��	��	��	��	�P)XM�M�M�M NTN|N$!�O�Op)��	�	�
�
�$
�,
�
�
�4
�<
�0P`Ph)�P�P(Q|)T
и
ѐ)������8�@������
��
��
��
��
��Q�Q�Q R�)�R�)��	�
���
�$�,��
��
�4��
�hS�S�)8����)��	�
� � 
��
��� 
� 
�P���S*��0�l�t�|���������������*�TU\U�U�U$VXV�V�Vp"*��	��������������0XlX$*�X0YlY,*8��єYH*��0�l�t�|���������������@*(Z<Z�"�"�Z�Z([\[�[�[\*��	������
�
�����
�
�#\\l*�\�\]t*0
�d
ш*������8�@�������X�`�h����*��	p��,�$�,�,�,�4��
��]�*�
�<��*�*���
��
��
��
��
�����$�,�++ +(+0+8+@+H+P+X+`+h+��	4�h�p��x�p�p������+�+�+�+�+�+����+��0���t�|����������������+ b@b`b�b�+��	��(���0�8�����@�H��b�+�$�c�c,`м� ,��0���t�|���������������,4dTdtd�d<,��	�����������������d4,,e%�eL,(д��eh,�� �\�d�l�t�|�����������`,tf�f�fgLgtg�g�g�gh|,��	����������������hh�ht,�h ix%�,��L���T������������������,���\�-�.��	��(�0�8�@�0�0�H���(/</����������H�P���X�`�h���t/������/0�2��	p�(�0�8�@�0�0�H���X3����T���\�� �(�0�8�@��3���d�4$5L8��	H������������������8:p:,;D<������������ ����@�|<����ь<�<H>��	 ���������������T��?����t���������|����������?��0Є�?4@|A��	������,���4�<�$B�B��T���������������������B�����$C�DLF��	����8�,���4�<��F�G��T���������������������$H��@Д�TH�IK��	��	�	���,	�	�	�4	�<	��K�0pЀL�����L���ИL�����L���аL����T	����	��	��	��	��	��	��	����	��L��Р��%0��L�(��TЄѰ0��`��YT��|�,M�N�O��	�	�
�
���,
�
�
�4
�<
��PPQ����t����������
��
��
��
�P����Q���Шќ0hДрL����L��,ИL��8��L��D�(��Tа0��\�TT��t�(��|�1����LR�R��	�
���
���,��
��
�4��
��S��0�D�t�|���������������T����T�T��	���T�����T�T�L����TWW��	L���������������X�Y��0���t�|����������������Y�������YPZ�Z�[��	������
�����
�
�\p\D]����t�����������X�`�h���|]�� �dь]��	p��0���,�0�0�4��
��]�]������������������$�^����4�H^�^�^_H_�_�_�_ `L`�`�`��	,�`�h�p�x�h�h������`a�*<ada�a�a��0���t�|����������������a������a�b��	��(���������@�H�c(c�c��0�\�t�|���������������d���d�d�d��	�����������������ePe�e�� ���d�l�t�|�����������f������Hf$h��	�������������� ��hLi��(����%��,�t/�(��3�|<�(��>��?��B�$H��L�(��Q���.��p�0��x���X�������� �� �� �P!�����"єx!������ �(�0�8�@�H�P�X�`��)X)�))�)! )� )� )P!!")"��	h������������������")0#!�#)h$)�$)%)�%�.��8р%!�%������� �(�<&+@'!�'!h(!|)!t*!�)��0�P�0�0��*+�,�.tДь,!�,��l���@�`�-+�.��������t/�.����(/!</��������@�`��/+0+�2���������3�. �p�D3!X3���@�H�P�X�`�d4!�4!$5+d6!�7!�8+L8��h��������������9!:+p:+,;+|<�.����D<���@�H���X�`��<+H>��h��������������>)�?�.0мѰ?����d�l�t�|��������������?)�/!0!@) @)4@!�@)�@)A)<A!hA)|A��	�������� ����� � ��A)�A!$B)8B)LB)`B)�B3�.4 �l јB!�B��, �L �T �\ �|D!�D+F!�F!LF��d �d �d �$H3�.� �� ��G!�G��� �� �� �� �HI!�I+�J!�K!K��� �� �� ��L3�.!�<!�L!�L��� �!�$!�,!� N!�N+�O!`P!�O��4!�4!�4!��Q�.d!��!�PQ����d�l�t�|����!��!��!��!��!��Q!�Q)�Q) R)LR!�R)�R��	�!����!��� ��!��!� ��!�hS!�S)T3�.L"�x"�T��	"��"�0#��#�h$�0#�0#��$�%��S!�S��D"�`"�h"��U!�V!W+W��p"��"��"��"�lX!�Y9�.�"�#єY!�Y���"��"��"��"��"��"�#�(Z!<Z!PZ+�Z+�Z!�[!\+�[��#�(#�(#�(#�\\!|]�.D#И#�D]����d�l�t�|���@�x#��#��#�`��1)02)l2)�2)�]��	�#������� ����� ��#��])^9�.�#�P$��]!�]���#�$�$� $�($�0$�8$�@$�H^+�^!�^!_+H_+�_!L`!a!�`��H$�P�H$�H$��a9�.|$Р$Ѵa���"��"��"��"��"��"�#��b��(#��$�(#�(#�(c+d9�.�$�$��c���"��"��"��"��"��"�#��d��(#�(#�(#�%�Pe+f9�.4%�`%��e!�e��,%�H%�P%�Lg!�g!�h!$h��X%�X%�X%�x%�Li+��.�%�&�0���%�<&�@'��'��'�$(��h(��(��(�<)�|)����%��)�x+����%��%�&�&��!+P!+0#+"��&�4&�&�&��#!�%�.X&��&р%)�%��P&��&��&��&��&��&��&��&��&��&��&�<&!@')�')�')$()h()�()�()<))|))�))�)��	�&�'�'� '�('�'�'�0'�8'�,*)t*)�*!�+),)L,)�,�.T'�l'Ѡ,���%���&�&��.��&�4&�&�&�t/�.�'�'�(/+</���'��%�����&�&��2��&�4&�&�&��3�.�'�(�4!X3���'�P�L8��h�(�����:!|<�.@(�P(ь<!D<��8(���H>��h�(������?�.|(а(Ѱ?���'��(������(��?!4@+$B!|A���(��B3�.�(��(�$C!�B���(�T ��F!LF���(�$H3�.)�0)�TH!�G��)�� ��K!K��()��L3�.X)�p)�,M!�L��P)�$!��P!�O��h)��Q�.�)д)�PQ���'��(������)�LR+�R���(�T3�.*�*�T��	�)�,*�t*��*��+�t*�t*�,�L,��T!�S��*�W��p"�$*��X!�Y�.H*�\*��Y!�Y��@*��"��"��[��#�l*�p\!|]�.�*Ш*�D]���'��(�����&�&��]��&��(�&�&�^�.�*�h+��])�]���*�+�+� +�(+�0+�8+�@+�H+�P+�X+�H^!�^)�^)_!H_!�_)�_)�_) `)L`)�`)�`��	`+��+��+� '��+��+��+��+��+��`)a)<a)da)�a)�a�.�+��+�a!�a���+��"��"�c!�b���+��$�d�. ,�<,�d!�c��,��"��"�e!�d��4,�%�f�.h,�|,�Hf!�e��`,��h!$h��t,�x%���.�,�-�0���,���-��-������.�4.�h.�P!����,а.ь���%�-�@�`�X!"���������,�.8-М-ь,)�,��0-��l-�t-�0�8�@�|-��-��-�`�-!�-).)4.)h.)�.)�.��	�-����������������-��.)t/�.�-���</���'�l���@�`��B3�..�l �\C!�B��.�L �T �\ �$H3�.P.�� рH!�G��H.�� �� �� ��L3�.�.�<!�XM!�L��|.�!�$!�,!�T3�.�.�x"�T��	�.��"�0#��#�h$�0#�0#��$��.��T!�S���.�`"�h"�f9�./�`%�tf!�e��/�H%�P%���.�/�p�0��(/����/�0��0�H1����1�02�l2�P!���</м2ь�����-��/�0�8�@�H�P�X�`��!�,�.�/М-Ѡ,��l��l-�0�0�8�@�|-��-��-�`��-!t/�.$0�h0�</�����l�t�X0�`0�@�x#��#��#�`��0)H1)�2��	�#����������������0�3)�3���.�0�1�X3����0�@�H�P��0�X��0��0�1�`�4? 6?�6?47?�7?88?L8��	1�h�������������@1��;?|<���.\1�1�D<����1�@�H��1���X��1��1��1�`��<?�<?�=?�=?>?4>?H>��	�1�h��������������1��>?�B3�.2�l ѬB��, �.�(2�L �T �\ ��C!$H3�.D2�� ��G��� �H.�d2�� �� �� ��H!�L3�.�2�<!ѰL��� �|.��2�!�$!�,!��M!T3�.�2�x"�T��	�2��"�0#��#�h$�0#�0#��$�3��S��D"��.�3�`"�h"�U!f9�. 3�`%��e��,%�/�<3�H%�P%��f!��.�3��3�0��D3�4�d4��4�$5� 6��d6��6�47��7��7���X3�L8ь���%��3��3�&�&�!�!�"!"���3�&�4&��3�&�&�4�h$!�$!�%�.4�<4є%���,4��'!,*!�)��44� '�T4�\4��+!,!�,�.x4Д4Ѡ,�����3��3�&�&��.���3�&�4&��3�&�&�4�t/�.�4�5�</���'������4��4�&�&��0!H1!�2���3�&�4&��3�&�&�4��3�.@5��5�D3)X3��85�t5�|5��5��5��5��5��5��5��5��5�4)d4)�4)$5! 6)d6)�6)47)�7)�7)88)L8��	�5��5�6�(�6�6�6�6�6��8!�9)p:!,;!�;)|<�.<6�L6Ѹ<!D<��46���H>���5�(�6�6��?9�.x6а6Ѱ?���'������6��6��(�@! @!�A!|A���6��(��6��6�8B!LB!�B3�.�6�7��C!�B���6�T ��F!LF��7��(�$7�,7�<G!xG!$H3�.P7�h7��H!�G��H7�� �XK!K��`7�()��7��7��K! L!�L3�.�7��7��M!�L���7�$!�0P!�O���7�h)��7��7��P!�P!�Q9�.8� 8�PQ���'������6��6��)��R���6��(��6��6�T3�.�8Ќ8�T��	88��8��9�:�p:��9��9�,;��;�\U!�S��x8�W��p"��8�$*��8��8�0X!�X!0Y!�Y�.�8�h9єY)�Y���8�9�9� 9�(9�09�89�@9�H9�P9�X9��Y)(Z)<Z)PZ!�Z!�Z)�Z)([)\[)�[)�[)�[��	`9��9��9�l*��9��9��9��9��9�\!\\)�\!�\!])|]9�.�9��9�D]���'������6��6�&�&��]���6�&��(��6�&�&��6�^�.�*�,:ј`��	`+�X:��+� '�`:��+��+�h:��+��`!<a!da!�a�.�:��:Ѵa���8��:�9� 9�(9�09�89��:��:��:�X9��a) b)@b)`b)�b)�b��	�:�;��9��+�;��9��9�;�$;��b!(c!�c!�c)d�.@;М;��c���8�t;�9� 9�(9�09�89�|;��;��;�X9�d)4d)Td)td)�d)�d��	�;��;��9�4,��;��9��9��;��;��d!,e!Pe!�e)f9�.<�<��f!�e���;�hh!$h��<�t,�4<�<<�x%��h! i!0��D3��<�d4��4��<��<��d6��=��=�>��7���D<�H>��%�.�<�<4є%����<�$(!�3�.�<��<�X3��P��<� 6!L8���5�(�6�6�|<�.=�|=�D<��85�D=�|5��5�L=�T=��5�\=�d=�l=��5��<)�<)�<!�=)�=)>)4>)H>��	t=��5�6�(�6�6�6�6��=��>)�B3�.�=�7�8D!�B���=�T �$H3�.�=�h7�I!�G���=�� ��L3�.$>��7��M!�L��>�$!�T3�.|>Ќ8�T��	4>��8��9�:�p:��9��9�,;��>��U!�S��t>�f9�.�>�<�g!�e���>��G0���>��>�?�?�?�?�$?�,?�4?�<?�D?����>�T?��%G�,Gt/G�3G|<G�?G�BG$HG�LG�QGTGT��	L?��?��?��?��?��?��?��?��?��YG|]G^G�aGdGfG0��(/��?��/�0�@� @��4@��@��@�A�<A����?�|A��%3�.��8ѐ33�. �p�|<3�.�����?�.H@Є@Ѱ?����d�l�t�|�����������|@�<A)|A��	�����@��� ��@��@� � ��A)�B3�.�@ЬB��, �.�(2�L �T �$H3�.�@��G��� �H.�d2�� �� ��L3�. AаL��� �|.��2�!�$!��Q�.LA�PQ����l�t��!��)�T3�.�A�A�T��	hA��A��A�$B�8B��A��A�LB�`B��S��D"��.�3�`"�W��p"��Y�.�"�#�|]�.�A�B�D]����l�t�@�&��]��&�&�&�^�.�#�P$��a�.|$Р$�d�.�$�$�f�.tBЌB��e��,%�/�<3�H%�$h��x%��9�.�B�C�0���B�$C�\C��C��C�8D�|D��D��E��E�F����B�LFь���%�&�C�&� !"��&�&�&��%9�.8C�PCє%���HC��(!�)��P��,9�.pCАCѠ,����&��C�&�.!�.��&�&�&�t/9�.�C��C�</���'�����&��C�&��1!�2��&�&�&��39�.D� D�X3��P�D��6!L8��h�������|<9�.LD�dD�D<����\D��=!H>��h��������?9�.�Dа?���'������(��D��@!�B�.�D�XEјB)�B���D�E�E�E�E� E�(E�0E�8E�@E�HE�$C)\C)�C)�C)8D)|D)�D!�E)�E)F)8F)LF��	PE��E��E��E��E��E��E��E��E��F)�F)�F)<G)xG)�G)$H9�.�E�|I!�G���E�� ��L9�.�E�TN!�L���E�$!��Q9�.F�PQ���'�����0F��)��Q!T3�.�F�A�T��	8F��F��F��F�<G��F��F�xG��G�$V!�S��xF��Y9�.�FмFѨY���"��"��F��Z!�[��#�|]9�.�F�B�D]���'�����&��C�&�^9�.G�0G��]��$�($�0$�(G��_!�`��P��a9�.PG�lGѴa���"��"�dG� b!�b���$�d9�.�GШG��c���"��"��G�4d!�d��%�f9�.�GЌB�tg!�e���G��9�.4H�C�0���G�TH��H��H��H�I�HI�|I��I��J��J����G�Kь���%�&�LH�&�� !�%9�.hH�PCє%���xH��(!�,9�.�HАCѠ,����&��H�&�4.!t/9�.�H��C�</���'�����&��H�&�02!�39�.I� D�X3��P�I�47!|<9�.0I�dD�D<����@I��=!�?9�.XIа?���'������(�tI��@!�B3�.�IЬB��T ��I��E!$H�.�I�LJ��G)�G���I��I��I�J�J�J�J�$J�,J�4J�<J�TH)�H)�H)�H)I)HI)|I)�I!�J)�J)K)K��	DJ�xJ��J��J��J��J��J��J��J�XK)�K)�K)�K) L)PL)�L3�.�J�|N!�L���J�$!��Q9�.�J�PQ���'������J��)��Q!T3�.LK�A�T��	K�XK��K��K��K��K��K� L�PL�XV!�S��DK��Y9�.lKмFѨY���"��"��K�([!|]9�.�K�B�D]���'�����&��H�&�^9�.�K�0G��]��$�($�0$��K��_!�a9�.L�lGѴa���"��"�L�@b!d9�.4LШG��c���"��"�HL�Td!f9�.lLЌBќg!�e��dL�H1</��xL�t/���L�0���L�(���L�0���L��L�,M�XM��M��M��M� N�TN�|N��N��O����L��O��9�.M�Cь���%�&�$M�&�� !�%9�.@M�PCє%���PM�<)!�,9�.lMАCѠ,����&��M�&�h.!t/9�.�M��C�</���'�����&��M�&�l2!�39�.�M� D�X3��P��M��7!|<9�.N�dD�D<����N�>!�?9�.0Nа?���'������(�LN�A!�B3�.dNЬB��T �tN��E!$H9�.�N��G��� ��N��J!�L�.�N�LO�L)�L���N��N��N�O�O�O�O�$O�,O�4O�<O�,M)XM)�M)�M)�M) N)TN)|N)�N!�O)�O)�O��	DO�xO��O��O��O��O��O��O��O�0P)`P)�P)�P)�P)(Q)�Q9�.�O�PQ���'������O��)� R!T3�.$P�A�T��	�O�0P�`P��P��P�`P�`P��P�(Q��V!�S��P��Y9�.DPмFѨY���"��"�XP�\[!|]9�.tP�B�D]���'�����&��M�&�^9�.�P�0G��]��$�($�0$��P� `!�a9�.�P�lGѴa���"��"��P�`b!d9�.QШG��c���"��"� Q�td!f9�.DQЌB��g!�e��<Q�0��
(/��?��/�0�@� @���Q��Q��Q� R��L�LR���PQ��R��?�.�Qа?����l�t��(����B3�.�QЬB��, �.�(2�T �\ �$H3�.R��G��� �H.�d2�� �� ��L3�.0RаL��� �|.��2�$!�,!��Q�.`RМR�PQ����d�l�t�|����R��!��!��!��!��Q)�R��	�!����R��� ��R��R� ��!�hS)T3�.S�A�T��	�R��A�hS�$B�8B�hS�hS�LB��S��S��D"��.�3�\S�h"��0M</��,S�xL�t/��4S�0��DS�(��PS�|]�.|S�B�D]����l�t�&�`�f�.�SЌB��e��,%�/�<3�P%��9�.XT�tT�0���S��T��T�U�\U��U��U�$V�XV��V��V����S�W���
T��Y�|]�(�^��a�|]�|]�d�f�(�(�(�����%�&�&�"!"��lT�&�&�&��%9�.�TдTє%����)!�)���T�P��,9�.�T��TѠ,����&�&��.!�.���T�&�&�&�t/9�. U�DU�</���'�����&�&��2!�2��<U�&�&�&��39�.pUЄU�X3��P�88!L8��|U�h�������|<9�.�U��U�D<����4>!H>���U�h��������?9�.�U�VѰ?���'������(�hA!|A��V��B9�.8V�LVѬB��T �8F!LF��DV�$H9�.lVЀV��G��� �K!K��xV��L9�.�VдVѰL��$!��O!�O���V��Q9�.�V��V�PQ���'������)��R!�R���V�T�.HW��W�T��	W�0X�lX��X��X�lX�lX�0Y�lY��S)�S��@W�|W��W��W��W��W��W��W��W��W��W��T)�T)U)\U)�U)�U)$V)XV)�V)�V)W!W��	�W�X�X�X�X�X�X� X�(X�0X)lX)�X)�X)0Y)lY)�Y9�.DX�\XѨY���"��"��[!�[��TX�#�|]9�.�XМX�D]���'�����&�&��]��<U�&�&�&�^9�.�X��X��]��$�($�0$��`!�`���X�P��a9�.Y� YѴa���"��"��b!�b��Y��$�d9�.DY�\Y��c���"��"��d!�d��TY�%�f9�.�Y�h!$h��|Y�x%��3�.�3��3�0���Y��Y�(Z�<Z�PZ��Z���Z��Z�([�\[��[����Yи[��%3�.Z�Zє%���,4��<��)��44�P��,3�.x4Д4�t/3�.�4�5ѐ3�.dZ��5�X3��85�t5�|5��5��5��<��5��5��5��5��5�|<�.�Z�|=�D<��85�D=�|5��5�46�T=��5�\=�d=�l=��5��?�.x6а6��B3�.[�[ѬB���6��=�T �LF��7�$H3�.<[�P[��G��H7��=�� �K��`7��L3�.p[Є[ѰL���7�>�$!��O���7��Q�.8� 8�T3�.�[��[�T��	�[�\�\\�p\��\�\\�\\��\�]��S��x8�t>�W��p"��8��Y�.�8�\Ѹ[��	`9��9��9�D\�L\��9��9�T\��9�p\)�\)�\)|]�.�9��9�^�.�\И\��]��$�(+�0+��`��X:�P��a�.�\��\Ѵa��(9�09��b��;��$�d�.�\�]��c��(9�09��d���;�%�f�.$]�4]��e���;��>�$h��<�x%�0��(/��?��/�0�@� @����1�02�l2�P!���D]Ќ]�T��	�2��A�0#�$B�8B�0#�0#�LB��]�f�. 3�`%��3�.(^��3�0���]�H^��^��^�_�H_���_��_��_� `�L`����]И`ь���%��%��3��3�&�&��%�.\^��&є%��P&��&��&��&�,4��<��&��&��&��&��&��,3�.�^Д4Ѡ,���%����3��3�&�&�t/3�.�^�5�</���'��%������4��4�&�&��3�._��5�X3��85��'�|5��5��5��<��5��5��5��5��5�|<�.\_�|=�D<��85�8(�|5��5�46�T=��5�\=�d=�l=��5��?�.�_а6Ѱ?���'��(������6��6��(��B3�.�_��(ѬB���(��6��=�T �$H3�.`�0)��G��)�H7��=�� ��L3�.4`�p)ѰL��P)��7�>�$!��Q�.``� 8�PQ���'��(������6��6��)�T3�.�`�*�T��	�`��`�a��*�<a�a�a�da��a��S��*�x8�t>��Y�.�`�\*ѨY��@*�(9�09�|]�.a��9�D]���'��(������6��6�&�&��a�.Pa��+Ѵa���+�(9�09�d�.xa�<,��c��,�(9�09�f�.�a�|,��e��`,��;��>�0���Y��a�(Z�<Z�PZ��Z���Z� b�@b�`b��[����aДb��%3�.Z�b��)��P�T4��B3�.[�4b�LF��$7�$H3�.<[�Tb�K���7��L3�.p[�tb��O���7�T3�.�[�b�T��	�b��b�\\�c�(c�\\�\\��c��c�W��p"��8��Y�.�b��bѨY��(9�09��[��#��9�^�.�\�cј`��P�`:��a�.�:�<cєb��	�:�hc��9�pc�;��9��9�xc�$;��b)c)�c)d�.�\ДcѨd���;�%�f�.$]иc�$h��4<�x%�0���Y�d�(Z�<Z�PZ��Z���Z�4d�Td�td��[����cШd��%3�.Z�$d��)��P�\4��B3�.[�Hd�LF��,7�$H3�.<[�hd�K���7��L3�.p[Јd��O���7�T3�.�[��d�T��	�d��d�\\�e�,e�\\�\\�Pe��e�W��p"��8��Y�.�b�dѸ[��#��9�^�.�\�eј`��P�h:��a�.�\�@eєb���$�;�d�.@;�deѨd��	�;��e��9��e��e��9��9��;��;��d)e),e)f�.$]мe�$h��<<�x%��3�.XT�(f�0���e�Hf�tf��f��f�g��Lg�tg��g��g��g����e�$h�"��&�&�&�@f�%!�%3�.�T�\f��)��P�lf�L,!�,3�.�TЈfѰ.��&�&�&��f��.!t/3�. UмfѼ2��&�&�&��f�3!�33�.pU��f�L8��h�������g��;!|<3�.�U�(g�H>��h�������Dg��>!�?�.�U�hg�`B!|A��`g��B3�.8VАgѴG!LF���g�$H3�.lVиg�PL!K���g��L3�.�V��g�(Q!�O���g��Q�.�V�hјS!�R��h�T3�.Ph�T��	h�hh��h��h��h��h��h� i�Li�W��p"�`h�lY!�Y�.DX�|hѸ[��#��h�]!|]�.�XШhь]��&�&�&��h��]!^�.�X��hј`��P��h��a!�a�.Y�iєb���$�i��c!d�.DY�4iѨd��%�Di��e!f�.hi��i��e)�e��`i��i��i��i��i��i��i��i��i��i��i�Hf)tf)�f)�f)g)Lg)tg)�g)�g)�g)h)$h��	�i� j�(j�0j�8j�(j�(j�@j�Hj�hh)�h)�h)�h) i)Li!820300	304000000140304800000400000444000000203044030020000004010020200PK
!<��o�((hyphenation/hyph_bn.hyfHyf0tP�����'-4�������$�'–’-111001���������(���4�����
��3������<�<�<�<�<�<�<�<�<�<�<�<�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|����<�<���D�(���<�<�<�<�<�<�<�<�<��<�<�|�|�|�<�<�<�<�20021001110002001PK
!<���((hyphenation/hyph_ca.hyfHyf0tP�����'-4�������$�'–’-111001���������.Xatb�c�d�e�f(gph�ipj�lpm�n�o�p\qlr�s�tupvpxpzH�X��la�e�i@l�ohr�uÐ�s���la�e�i@l�ohr�u���lae�i�ohr uÐ8s��	la�e�i@lHn�ohrTu��|a	eD	i	o�	���la�e�i�o�u���la�e�i�l�o�u���la�e�i�o`s�u�yÐ�f�i��e��
la(e0i@l�n8o�r`s�u��|a	e�	i	o�	���@u����la�e�i�o�u�Xt��la�e�i�o�u�m�r�s�b��la�e�i�ohr�u�tH�������������������xa(o�u����
t�0�t�t�t���t�t�t��Xt
H����������H������������������	e�	i�	�H����l����ae i(o0uÜ���l���8ae i`o0uðl��8ae i`o�uÌl8ae i`o�u�X���a�e�i�o�u,Ð�������������������tH��$�$�$�$�$�$�$�X#��Pa�e,i�n�o�rdu8Ð#�#��
Pa�e�ixl�o�r�sdu�x8���Pa�e4g,iTn�odu8���Pa�e�g,i�otrdu8���Pa�e�i�odu8�tH��	0�0�0�0�d�0�0�0�l����Pa�ei�o$u��H��d�l���Pa�e�i�o@u��H��0�0�0�0�0�0�0��#$aLeTo\u����Pa�e`i�o@u��P&���$aLeTo
s\u�À&�&d&��$aLe�iT
mTo\u�ô&���$aLe�nTo$
s\u����$a�eTo
s\u�À&�t08�������������������$aLeTo\u��$aLe�iTo\u��$aLe�iTo\u����$aLeTo\u��P*���|a	e	o�p 	u�	�p���i\o(	u���
a�
b�
c�d�elg�hd	i�j\k�l�m�n�p�
r�
s0tt	u0v�w�*�*d*���|a	eD	i	o�	ô*0-8��L	�L	����(nT	���T	�8��L	�L	�L	�L	�L	�L	�8��L	�L	�L	�L	�L	�L	��*�e���L	�L	�L	�`*�1��	.p���	e����	m@
tp8�	e���
m@
tl�	.���4
a�p��L
.�=p��`
i�h
h���t
i����
tX���
n�*����
n�
st���
e�
i�**����
s�*l���
p����
a$e�hDldoux*���l�*8-0r@��8a�-���Pl���Xl�
nxrt*8*���s����e�i�*����slA����a����n�*m(�r����epp���
n�lc@dHpT��aXedo�t���HPs�A�*�lr���te�*�tl���lp���a���sp���u����h�*l���n����a
e<
o\
s�* 
i(��
r��*���(
t8��0
s�*lH
l`��P
a��
st��h
e���t
b�
dl���
eo�*�*�
ddo����
b�
p����
u�s����
e(lr����
e`*���s8-nh��a����h$r�-���@o���-���teXi�ø-dE�|u����e�E��������pJ�L����n��i����e`���tT���s�St��d�r���o���(f�4rp��@e���Lh�]p��de���lh���xcp���s����z�#�t�8p���p�8�8�8�����e�l���i����e(�gHrPv���$ade�p����c�Xn����pe�f��o�sl���i�8�k����ol�cfHp�rs����a il8����r��8T��t�nHp(��,n��@x���He����i�l��lo�A����pt���a��s�����H����(���n�y�#�.s��el���i����r�����.��l$.p��,a�e$
��8h���Hs���Ti(��`e�o�����|.��s����.�sp���e����h$&�cd���a����u������.����.���4iy�#�s0(et��hiyt��@b���Pb�(e�����t.�s�#|p����ep���e�o��`���.��.s����ed���l����u������.����.hs�x��� e�#,n�8e���De��Pl����.���t.���|i�o����t��tp���e(���h����gl���r����ai�t.���t.0e�#�d���e����.$r�����<.Dsh��Pe���\r���htltl����a����s����o������.�#�s��e����i$y����tX���l���a���y����.p���o����<.���iDy���Pk�`sp��li��xh�#�s����e10030010001000032001200120001012020200100000300010213020002120000	12000000020010200301031003032000898800898880088988008898880088898800	888988800889800888008888008888800
8898889800
8898988800
8988988800898898008889800PK
!<�:�;H�H�hyphenation/hyph_cs.hyfHyf0tP�����'-4�������$�'–’-111001���������.<
aH8b�Hct\d@te��f��gt�h8�i��jh�k�ltmnP*o�Hp|Vq0Wr@vst�t��u��v,�w��x��yL�zt.��W�)��l����s<
���d,fLilk�l�s�t���a�b`c�d\e�f�gh,i�j�l�m�n�o$	p\	r<
shu4v\x|y�zlè�4�H��$r@
L��8�X��@�n�Xy���`r�lxsp%�$�k�t&���.����tL'�a���<���hh;���lH8�e$l8sLy�A��u�?��o�E!\E%0k`��G��DltL)�H��Xhpt<T)�+0���xsY/�rlW���e�u�W����H��[�[/�c�`p`4�t_�pt\���eo4s�g�g���\f7$v�<i�m;�l>,m�rB�r��@at)@t��	Ta�f�g�i�k�l$n8pLs��d|�l�|�|�r�~Gl~���g��Jl����l�r�Nd�`��r����es�R0�%h(�+���s4QV��0p���Dt�HZ�]Xpt.��`�����+ܢ|t�����l���i�;`����s���e�o�CH����z�>t��lL����st����oyD�_̻��zD�Z8���$hXnps�t�@adl����He����.��;��b4���xi ����l�s��Z$�x��a�����a�s@���%�m����g����e0i<sluL�fx�.��t�������7$Ť>dl����D�,�q�Xt\t`sh
4txd�i�o�s�w����a�g\
;�{���u>t��mv���a<eì(�/h�������(�DPi`t0�T<�~h�Xk,,��ll�+te�l��P*���b�ddn�s�t��p���@7�t,,�iE��A�����_�.�j�.���ei,r0bJ�X�/n(/ aD�x/�/��<�,48Pi@8��Xa�?Nh?��pr�=xtС������l�@���e�o�u@�rd-�<A���iA�cXA�D)h9�����O�O��	�$O��4	v	ĸH	o<	sQ�S�^H]��D	e|	o0W��L	e�	o�	u
�:R�_��t	p8f��	kTe���	c�	ļ- f��	o�	u�P�TZ�$Z�	o0.���	kH.���	�X��l���	b�o]�Z��
���R�z
n`y�� 
m\
t@v,
e�
i�
k�
t�z~�zT
kH�,���h
u���p
nT|��|
a�|)��)�
r�`����
v����
a�
ei�L����
l�
�������
��b���
n@�hЏ)TĤ�t)��(�H�� 7/@d(7��H�h�)����`c�k�s�t�v�z���)t�4�p���`���������4��w���n$��Լ���m�r,�D��e�O0GGk��c�����@���$ì�]��h�Hc����Pa�������ha����pi���L����a�d��D�)����p����������
j�
l�
s<
���a�
d�
f�
h�
ik<mLn�o�p�q�rs(t0u8v@wHyPz�
�����axb�c�d�e�f�g�hXij�kll�mDn�o!p"r�$sL'tP)u�*v�+w�+x�+y�,zT*�0Ġ�lRR/0���
�������.X�e$i,o4yD�
��
X
\
��8
D��Dapdxh�i�u�x�y��H �h�H
�'\
�!�|V"�a�e�uy��"
T
�#
�#�P#�����#
�$�t�&�����L'�P)�*�+��+�,��&����X��8��`�H8	la�e�lr<stt�y�zh�8v�h;���b�@D@�����?���o�u��A0A	Db�a�c,C���im|dR �D}����E$�\E%0kPoXt���EDFl�A��`��FH�G��|j$H�H��c�de$h�ijk ltr�s�t�wz�ÐI�I������I�XJ�LK�Jc���tL��.Ldxl�m�t\ÈĤ�����L��T�p����� M��X����D�#\M(dM��������и.�M#X���84#�����n������TN��lO��P4�OtQ���.@a\odulyP��.��Q9TQ��H��Q�3?�HS�oDh#�S���m�S��6�<T���a�b�e�f�m�r�s����TD���,�RUI���,�W��NlW��.DkLm\rps|t�W�������$ZT�ZZH]$[/Te�8[_h.x[d�i��_�����eP\7�d�pt\��
�a�bj$m4oHrtu�v���!��]%�cf�]����Pdi�/T^���Te\g\f7,cj�i@aXul�����`��nhŔpn�rT>8v�i@t���b�c�ghl0n<oDr\slvtxdw�`����l�|�e$}t`�I���e k���#���(gԅ0�t�.mT|
��TiDt4�7$�D�t��|j�l�t�x�������a�o���������������T�;`����b���eh`l|r�u�w��0��HePlXo<�@�����4�h�2R����X����hn����pa�e�o��rl5��{��f<�����ܭ%t��lr(t0v��f`���������Øx�X�����|l���eP�%�G�Z����8o����@�8����e�g�l�n�p�vxL��t��G�������o0��lT�������d�t��{X�%�r ��������s��i�VZ���e����dXidmlo|uDĐ��r/����<� �R|�Pp������4�{��tn(��������4��h���
�adh lXo�rs(t<uPv\y��H��/��������d������<�� �/�4���x���,� ��4���@���x�lu�BP���dzD��xa�i�o���a�����a�s��#��Dc���nh�������4���eT��/��w������%�ø�4�����%�<��T�4t�����HaX�T�/�	�a�h�i$lPo\rluxyL��v`��a�����s�t�(#���td�̪��7�g�s(�D��l�e����mH�a4y���h?`Z<t���Ds��\���G)dmla�oT�H�T�����]��������vt	�i�m�n4ohp�r�s�t�`%�%�A�������B���cH���$u����@����J��T��%|l�t\�<�TRx�a��0����d����P$�%�k�t�Џ��tĜ��%�k������������t$i�,m��
8a�d,ehi�n�o�s4tHu\ypz�� �^�T%�c�ru�� a�T^����k�����c�m���o�u�fDh�.�l����cs�)��oR�np�{D$jHn\r������@d���@��To� 4"�H"ps"%xid"����?�h9�����#/������x�l$���r�#%�c�ku�$����$����L%<�Gt%p�#����&�%%,r��'@d��R�'Tv|(�@(hs�)��(|e�j�m�t�u)�����#��&�)�*�.��)�t_�s_����+�( lP*��
�b0 cL dT hd i| k� p� s� t� w� z@ Ġ �,,��,t0./H.��8 ��.�27�3`3��\ nt z ]�4��:�� cP;R�=�h9��� ��=�� th?� ��I����� �Ԗ�@� c4C��Ct����L��� alL��!r�H	!iP!j\!ld!m�!o�!p�!r�!yt!��MtH!ePM��NR�A�O��l!��!��O�$O���!k4Q%<RLR�!o�Q���!i�!o�!�e#hR���!b�m�j���!��T�<V]�VT"g0W��"a�"dTe�"f�"j�"m�"o8#ph#t�#u�#v�#yl"�P#�p��L"ahZ/�"ŌZ��`"��"��"��#�[�Z���"�] ]���"�`\���"Ŵ`�`Dc/xc	�	R|d�"bTe#c#u f8f���"kPiR@i��#sV�O��$#��i,#Űk�t�j��D#��#�(l��k�`#h|#l�#r�'R@��<l�#i�l�m/�#s�m;dnpn���#n|n�#a�n�o/�p/�p���#a<$eD$ih$kt$v$�0$�\q�hq��$�L$�|q�q��($��q/0s/,t�����%T$o�t\$n�uZM�hx)|$iLx���$h@v�$c%e%f %h4%ip%k�%m�%o�%r&t�&u�&v�&y�$Ęxl�x���$��z�z�$s`y���$pD{>|�{��%m��T|��,%jL%nX������D%t���D}��X%��%��|���%k�%r�%u`%��R�d
�}�%jT��
H�%l��hZh����%�����%d&o�%�`\�0� ���&n����&aH&m\&r<&Ā����4&�l�#L����l���P&e|&i���&l�&o@�l&h�fL�f�����&c�
��&i��8����&�����&�<�t���&i'p$'t��p�4�&l'u�U
$�Џ��'a@'�d���h.0���4'�t�
�'e�'h�'j�'k�'l�'r�(s�(t�'Ġ(�l~���|'i�'l�<
(�����Ĝ)���.�'o ����'�P*����`����'m�
v����'a4(eL(o\(u$(�84�$���(n����(��������lD(m�K#p�h�#$���T(mt(�Lm�Xm��l(��<����(�����(Ô����(��(��-��%�(k �l �� ����(�,��(l�(r�(Đ����L(ols_4�)c,)k�m�t<)��_��NP���4)�|�����H)a�)i�)j�)l�)p�)s�)t*Ű�8���x)nв���4��)a�����)l��Z��3t��)p�)t���)r�ĺ
���)h*r���/���*����Ŀ��$*h`*kp*l|*p�*r�*s�*t�*�t.��,*��Bp�P���h*e�Z �ZL��\����*�t�8�/��%	�*d+e+h +m<+oP+rd+s�*Đ+��[=0����*uL����*�����|R����+g���>����(+hx���0+u�l����H+u����>\+t��C����p+e����x+r$����+�,��+a�+rxR����+b������+.��|�,a�����+a,c4,e@,lP,op,s�,t�,�t�G���(�] ,r|���(,u`���,T���H,c`,t��JP���h,kX��8���|,����,h��L��,a-d4-eT-i`-jh-k�-l�-m�-p�-t�-v�-�������,k�,z��O�.P9�����-�D���$-v-�_T�<X��,-pH7RL���@-m�H-o�����^��f����p-c�-m�-t����x-�L��-�d�b`�b��>P�h�k ����-��^��h�|��$�,�_�-k�����-s.�8�pD���.��c/�]��$.�t\��,.���
8.d�.g�.h�.j�.k�.l/n /tp/v\/����/�D.���`�� ��E�HG�F�Ŀ��������`lt�)��v�h����.l�.uT�
������.a/l/oH���)ب7t���/u8/à�y����0/��Vt4�D/j,)k)��L/�8���h/l<
��|/a00b�0c�0dX1eh1f�1g�1hh2i�2j�2k@3l4m84n�4o�4p5r�5s�6t�7u�7v8w(8zx2�H1Ĥ4�8@fD@���/�@0�P0����?��H0iX0o�u`0y�/�H80lh0s�@~�@	�@fA|D\E> MtL��p0l�H��x0h�0k�0t�O%<T��0�d��T���0�k�i�0ot\���0r1v41y�0�\l�<f���0�1�@1�nl�t�p������m����1hLq(1c�q/�W����4�@t����`1l|1r�1tH�%����%���1n�1r��4�t���
�1a�1e2i2l2o$2r@2uH2v�1�42�@�/T����1��H��L�2st�;l�иB���,2����X2i`2oX�d��8�] ��t.��p2����2c�2d�2o4����%����2k�2s���h��2h�2l3m$3r83v3�<��������2e��� ��4���3�03��xa4����t����.a�3d�3e�3n�3o�3up3Ø3��/���h3��3��������t����3���/̘�/�3e�3z8���3�4f%�3Ĥ����3k�\7,��]4ut�n,4Ŭl��$4�L4rh4v\4�#%t%l�#��T4��'%�/N�)��p4��(�m�4s�t�ux4�4�)���4��5��6��)��)��4)�P*O��l!��H�4s�4ŔS%�V05p0W��	�4aL5dT5ed5ml5o�#u�#y85�|5�@YR�Z��`"�\5��#�`\H]Dc�|d7Te�k.�j��t5��5��m/�p���#a<$eD$i�5k�5�hq��L$��t_Pz
`y���5k@v�5e6k46lT6o`6pp6t�6�X��|���5y6�D}���%��~��~�� 6�\~��D6o(6�~I�������L6v$�h�{����h6r����6od���|6��\������6e���6l�6tĐ�Џ���6r�t���	�6aH7lp7o|7r/u�7v7�(7Ĩ7�x�/����7�47� �/ ��� 7�d�/AT7u���<7o����R�B\7n��d7v���7�$����"�����7���������7�p�%��40��L����7����7d�7�7Ŝ�t��^$����7��,���8a��788oL���8r@8vD���n�8ah:b�:c;dh;e>f>g >hT>i�?j�?k�?ltAm|An�Ao�Bp,Cr\Es�Ft�FupGv�Gw�Gy$Hz:��:��A��
�����8d<
���8a9bD9cd9dx9e�9l�9n�9o�s�9t�9�h:�xZ9b$9yP�����9d�P09o��89kp���P9����X9�D����p9r��l�9m�x0���9��RD���9n�t,�L'�9m�9t�+�(�9.L/�\/���9�D.���9�t.��\:�:��=�8G�dG�8}6��0:�6��8:��5D:k�/��P:sH8%�:a�:l�:r�:Ô#�8���:s�=�:���:��?���o�cR,C���:k�H%�:f�:r>RHS]lW�W���:����=�t\ ;e(;iD;oL;r8;�_0b�c��]��0;�\f��i�0ot��;c�;n@t��X;a�;d<f<h<i�<j�<l�<m�<o�<p�<r�<s(=ud=vl=y�=z=�swdu�z�;èz���;�Py���;��l�|l���;�X|d|�;a$}4l~�	D<dL<eT<g\<jd<ll<mt<n|<r�<sh�R��~~��0�R�R�~���~R��R���<d�<m�RH�R����<lD�ԅ4��GLj,j�<a0����<r��%\�<�=t����=��	���] =b@=k\=tP=Ō�`��*��H=�ԑR4�|��p�����t=�D���|=�����=d�=o�=r�=th�R(��=s��ؖG��N�����=a�����=tО]�]�=o��%0����>ht�%0>e8>f�]�%!R���@>p8���H>a�>b�>g�>k�>l�>m?o0?r`?s�?t�>�ľ��>l�������>i�>n8�(�����>r0�%�l���������>�(7��5���>y8���?l?mH7�.a#����(?g��
��N<?et?oh���D?r��T?t\������l?mxn�p��?l��%�?a�?o@��x��?d��R���?sh�>���
�.@al@e�@iAo(At0AulAvD@�XA����@c$@�0��@��2�����0@k���8@��@��@�A� A�dA�t���d@a�@d�@m�@t�@��xy���@�x��#��h.�@a�@o�������������@i��7�@n�����n#���AvD��</�R\7DAe|�R���<At������PA��/qRt%%�Ao�A��"�d"�Adt�;T�/�As�#���A��()���A�E�DF��F�G��H�*tP*��
�Aa Bb4BcHBdxBe�Bo�Br�Bu�Bx�Bz�+( l�-�,��,Bl�.~�.��@Bm\BrlB�(/J�/��/��dB��0]H:�9���Bgh=D�<���Bo�Br�<R,��A���Bw��RdC���Bt�By�C��C�H%�4s�V��
TCg\ChdCilCktCl�Ct�Cv�Cx�Cz$@�0W���BaT5e Do|Ds�Dt�Du�C��D��0X#DXpX�X#��Y|Ca�Cn�Co������ Z��+���#d"�84�CohZ���Cn�Z���C��"��C��D�Dc���.�nDt`��8f���	kTe��Dc@Dn\DodDslDzPD�@8T9��h��HD�hh�h#�i�j�j%tDk�Dl\~�lN�k��Dv�l�l���Da�Db�Dd�DjX��H�в#�j���#��m�n���Dc�p)Ea EeD$i$ôp��q��h.8EjHElTEnLr`��Xr��@Eexr@v%�Eb�Ec�Ek�El�En`6p�Es�Et�w�Lxt�|�Eae4����~���E�\~���E��t|�7�+������E.FiFo$Fr8F�D��$�Fp��
L����Fe���H���0F���XFl`FtxF���Џ��'a�6r,�����pF�t�%�Fr������Fe4���{�Fl�FsGt(��4��Fe�����Flt��)p�Fq́к���Fl����Te����Gr(Gs�t,�GĿ��0GcHGz��/�OR�H��PGk����XGc��%|Gy�),���Ga�Ge"�����Gr��|�)�����GaHjHkHrHs�G�$�8����G�����%�Gt���Gs��|�4��ZL��DHcLHdlHrtHu|Hv�Hz��D�`H�P9
����XH���tP�����L�{��<
,Ia<IcPIdpIms�It���Ha�Ib�IcXJd�Je>fLLgtLhTNilOj�OkQlHRmhRn|Ro�Rp8SqHSr�Ss<Tt�UuxVv�Vx�VyWz�I�DJ��U�� ��4Il�R���HIb`Id�^Rh
�hId�'{L'|Ir�//t.���I�LO��R�TV�W�H8%�Is�x\E%�Id�HJaJe(Jl4Js�+D���In�H�In�K��JJm�QQ�� Je�SdlW�W��<J�DL�4R�t\pJi�Jo�Jr�Ju0b��<�ph��xJa\f7�Jr�i��n�@t�Ja,KbLKc|Kj�<l�Km�Kn�KoLr8LzK�L�t�Jndu�Jo�|�uNv��K�(L��B��A��Ko8v Ko�w�w��8Kldw��@Kh`KlhKu\xR�U�~.��pKa D��Kj���%�Ki����Kc�Knx��%�Ki �Rԅ���Kb�Kk�KsȆ~�=R\��0���Li4�����L���D����0Ld��)��%`Lr������XLe4�t���lLa�Lb�LcMeMhMiMk MlTMnpMo�Mp�Mr�Ms�MtNu NvDNy�L�dM�@��T����L��M�LN���_tL�����Lh�/,��H�/в���h.<MaHMe�*����4Mv�����.���A$���\M�L���Mb�G B|MyE���l��MaP��tCl,�)�[)�����M.�����M�P�N��M��c1d����Mc�����M��/X��hNi8N����$���0N�̻/��8��Ne�Nf�Ni�Nk�Nn Os@O��R����xNn����Nn��������N������N�L��Nr����R���NnL!���Nu�����NiOo����Oi�v��Oa0Ol(�(�����8O� �/`OdTeR����XOm���?a�Oe(�;�|Osh�.�Oa�Od�OePhPi<PlDPmTPn�Po�Pt�Pu�PvQz�P�4�7�
j��td|�D��OfPr0�<����(Pn4Pt������� Pup������%�#���%LPr9d	��`P�l�hP�x���tPm�Pn�Pu�Pv�!������Pl�B#P����Pt��9L����P�l���P��AT����Z�����%���F����.apQc�Qe�Qi�Qo$Rt�3uTQ�lW�H1��4Q�����<Q����HQ��Q��Q�R�,R�8����#���xQh�Ql�Qm���K�QL���7�Qf�Qp �;��/�-t@8�����Qn�QpRvp;f�QeRa<����/�W@RoP*�t%XRd`Roh
%)%tRa�P*�Rc�Rg�Rn�Bo�Rtx-\�,���Rk@24"�"%�Ra@8���Rn�@@�RlHG/�H7SdSe Sl(So0Ss<K��XOmlK
PM$O�S|V��V�0W��@SapSi�So�#ydSÌZ���"��a��e#�SiTe��xSb�Si�Sn�`�g�@8��h.�Se�8��/��w���S�@v%fTkTs4Ty�S�(T�����}�Sp�|���Su|�A Tt��d����7���t����+.�6a�Te�Tl�Tn�ToUpUr�Ut�Uu�Uv�Uz�T��TĐU�\�x����Tr�Tv�����T��7����Tr��f�����Tn �� ����T���Ti�	��������$Ua<UeLUoxUu`�4Us�
v�Ym���|ksp���DUd`UmhUw\�b�if��=$���pU.T(m�/�����U��U�,�tب��Uul����/p��Ы)���F�@V���Vc4Vk�FlGtX�h����Uh`KlX�h���V���V����(Vr��LVz��]Ŀ/lVc\�.,�h`Vh��7�Va�Vk�Vo�V�D����Vn�������V� �Rx�������Vk�VpHs��]�Vo؁
��Z�)L�(Wa4Wc<WnDWs�{�� Wm�Rh���y<
/�WcXfXi0Xk\Xp�Xs��LWa�Xb�Xc�XdYe�Yf�Yg�Yh�YiZj$Zk\Zl�Zm�Zn�Zo[p$[r8[sx[t�[u,\v8\y@\z�X��X�d[���lW��r���������%Xk�Z�RX��Xv J���(Xl�,tO4<Xh�!��DXc!PXo��<&��hX�&��pXĜ$|Xt�/�t.���X��Y�\�H8_�H�XhtL�|$i�XuN��W��<J�t\���Xp,.äi}@t/,YcHYh\Ys|Yv=��w��8Kl8Yrdw��Yh�M��}
$}@Yl4���TYk�������hY�4�pYÈ����4L�t����Yo@2u8�/�Yb�Ysľ���0Ol�Yt����hX�h����Y� ��ZnZs�������Zi��th�4ZlTZo�HZ���x���@Z�x��/�.alZi����	�d	��tZ��Z�t�Zy|ZèZ�p�T
���Z�D�P*/�Zk�Zn[s�4�9;@8���ZgL>{�=�Zk�H�[a(I0W/0[a�V@v_L[h�-kT[s�{�|�%���)��\[��[�H\�t�4�[.�[a�[l�[r�[v�[y�[Ÿ�������������[��7�p������/�[ch����Ŀ��0Gc\ŤA�\���\�����$\a��]L�/��<
7�\a�\c]i0]l<]mP]rt]z��P\a�]b^c�^d_e(af�ag�ah0biPdjpdk�dlTem4fn\fo�ip�ir�lsPnt�nu�pv<qwDqxLqy�qz�]�T^�<f������\t��RX��]k ]zX�RH�l(]p��Kj,jR"��H]r`]th#�h.T-��,l]iD.]t.��	�I��]���c��i��i�p��p�|q�\:�:���]�H8�]b�]e�]h^u�]�h:Rh;� >R0�f�F{�]y�H%$^d,^h@^tXJtL)8^nTM�<T�lW�^��W��H^��� a��~/�-m���h^�����t^�Z�^l�Y��^s�X���^�t\�^a�^l_s_v�^�`]�P]���^tP\7�^r�d7\l�<f���^��l+�ph@th_ax_e�_f�_h�_i�_j`k `mL`op`p�`r�`s�`t�`uavaz8uRt��`_m�;n{4Pr�d|�_tP�$}�_t��l~���_aD<d�g0b����_i���_d�_md���_i`o����l���`dD�@`o`y,,`e��4`sԅ���Kbd`n�'�@8��\`y��4�`n�`t�NRUIU�`c�%�`e0��܌
����`a���`r|�h��`v���0�L����`�4��`��ao(�J��/��%Hafpai|al�ao�as�1t\�7�����%PaeܢXat����dal��t��)���`�����ae�ai�1n�w���t�4�ac�adbkbo(bzb�TN�����ai����T����a�вRL�� bcص�8�tba�bc�be�bg�bh�bnco cp\cs�cu�b�,����.�be���dbn,��2h.�����bk�����b�������bh����l8�#���bnl�D��br�����d����Bcs8���cv �,cs�S����.lcc��N4ceh���Dcr��Pct|c��]����d���tc��������cr ����[.�cc�ch�ckdldmdndr8dt@dv�c�(d���H��T����c����(��4��d�T���<��D������ d�Hd�`�����h�����`dehdp��T�%h�>�dm�dr�dì�$��h����d���dÀ�������d����de�dhei�z����deT�.d����da������e���7e�0�� e����ted@>p�es�et(eĤe�t4ea�ed�ei�eofr(fu�%�$|ee��	�ee	���e��(�(���e�	�e�h
�0�����el����eu�k����f�x��fŘ����� ft%)���A�\l�n��F��o��q�P*7�fa�fb\gc�gd�ghhihj(hk<hoThpphr�hs�ht�hu<ivhixtiz�g�*4,,��+�fl�fo�fr(gy�3�A���fj�Brg�h,���fa)��X��g��G��G��g��G��g�p-�x-4gapgexgl�	o�gs�	u�,��<gk�gr�gt�-<P����-J�U��-���gv�Z�0.���gn�gtH.���g�x[J�^#�.���gd�gn�gr/R(/��24`3��R�3��hmD���4 he0:��9��4hdLhkX:R�:#`hn�;R8d#�<��hhl�>�=|ho�hu�&@�hr�@{�A���	b(+h�hk�hmip�h�,i������h�<B��R8�����hr ������i�`�i���� i�`��BLip�`� �~���C��Ti�dC��\i�C4�ii��F/�GHG���il�H��io�4s$O��	�0Wja�jb�jc�je�jkklkolu�#yHlz�j��DŤ��V��	�i.,jd4jhtCl<jpDjsdjy�CzTj��0X�@Y��Y#�&��X��Lj�lj��+�.f�0hZ��tjc�jd�jl<QČZ��|j��"��k�8l�@l��0�Z�AL[�jop[H]�jo�_X��c%�jy8d%8fPkuTe��kc|kd�kf�kk\Do�kpdDs�kt�ku�kzlkĸk��U�Y�0.��XkeH.��`k��k��f�f#h#<7�lL�:�ki�?��h���k��k�(i#@i�i�klL�R�DF������k��l��(lf0lml�t(�(m���#n��op�Tlb0p��p���.�#a�le�li�lo|l�hq��$��l����q���l.�lp�lĈ�������l��r�0s/�lm�s�,t��lt`��u�@v>Hmbpme%f�mk�mm�mp�mt�munz<m�Xm�(8�/��(mz�w��0m��w%�x��x��Pm��m���`y��hms��#�}|mp�|���mo���H�ml$����)�mrL����
l����me�mi@���R����mp<�v��l$n�Dn� �
����n�\�,�0nu���8n�t�%xnl�(r�ntl�s����dn����ln�,�%��
�nbod(of8olXom�op�or�os�n�nĄ/���n�����n�̮X��nl�Y9����ni����n��I�]��o�H���oÐ�44�����0old	��Do���Lo�A�xN��dov����loo��xol8�������oi���omt��otl����ob�orH8ThZ��Z���o�����pe�o�H]
���Ŀ��pb4pcLpk`pmhpn�pzxp�,�G`VhDpk�O�4Xpu0�
��Z���L��\���pp������/��4�paqlqsquqv�p��`�0q�<I����pc�pk�pn�D��N�����p� q�8����~L����%��N�$���(q�,�%����hqkDltqs�R����`qt��_�/L�G�qg�qi�qn��{�D ��qrL����qk��qo��h����qy�����.raLrbTredri�rr�rt�ru�rv<rär���� rsh�;�0 ���(rc����0r�|r���(\�-����/\r.tro�<�N��P�3X�����D����r���8<
/sns�0s����ralsctsh|sk�sl�Zn�so�st�szHsüsň�0��s�8JD��sa�&����(s��/�`sbt.��<s��s�0
00Xsl�Ht�/h�_�P*/�sv��B�sit���])���s�H\�Ŀ]L�p�RM/�st$���si���sh�tt<
���sc�tdueuguk ul8umdunpup�us�ut��ta8vbdwcPyd{ed|f�|g$}hl~ijl�k�lD�m�nԅo��p�q0�r��sh�t��u4�v��w$�x|�y�zv�yĴ��������ti�?����ta�tm�tuuv$�tJ�>����l k(]p��0uaLubTup�	�h�h�D��\ui!4\&&��xur�$�ut�uu�&D�9�����u�L'�ur�ut�u��'�'���ua�(�6���k�/���utt.���u��~���89;D9�vk�8��vclvuH8	(va�vb�vd�e�vl�vrws�v�v��P)��dve::��xv��v�h:�;%�vaP\?�=)l@�?���ve�u�Ìp�D���v�,C���v�E��A���v�<w�\E%0k0wt�����.��wr�E��$weDF.�!!Dwr�HLwp�H��Xwa�wc�whxk\xldxqlxrtxt�xv�xy�w�(J
�I�wl�I����x�� M���wotL���wl�woxvPn��.���wtpM��wd NC�OG$xe@xiLxmTxr�O�t��(P��,xeP4xnDPw�JQ8S%HS<T)�xu�0Ðx��T��T���x�����U��xmTV��xclV���xV7�xr�V��Y;�Y/�xslW���xi0ykDyt�W���x��z�<|�������y�$Z$yÄ��x[4<yet\��
�^a�yi�yj�ymzn zopzr�zvPzèz�@8c���yn0b�yo�`d��yzPd��ye4eTe�ya�yn��\��/�yp8���y�4f%�y��:��fzc\f7zb?l<zpp;Th#4ze�i/�]��Hz�0l�l��\zm�idzu(�H���|z��l/�z�\l���zi<f���z��z��p>�q/�zo�=<sNHs���z��r���zÈ{R{���zm@t��
�zeL{f`{gt{j�{k�{m�{p�{q�{s|t|v|x|z��d|D{u�|I�|X{o�~
��l{al�7D��.e�{i�{o�{p�{s�{u��܃����LL����{h��������{sh�%4�t$�7�0|zH�DH�4(|o��]8�D��D|a ���L|n��X|a�|r�`�p���t|�H���||���1n�|o�|r}u�wH�h.�W������|chn�����|a�|e}�h^������|gDc����|�t�;����}st�X}a`}b�}e�}l�}m�}r�}u0~y~�p}��}�4���%<J�X��h}��}��}�������}o����D�{`��_���}�����}�l����}e ������}��ȺP����~d(~tT���~�@~�غ̻��#���8~l$Z����L~k����T~�8����~g�~n�~s�~t`~���4�~h�1��x�~a�~o��X������~t���.�lp�%��t ����~s���x��~bDgLmTr����
�~a|c�e�i�k�m �o�t�u`��`���Rx�{`o�"Ip�|���hl4���ph��{��k�m�n�s��s���o��V����e(�\���|��d�n��|t�%`qt�Z���y�!���bT�cp�dx�f��h�2k��p��s��td�Ġ��4�a\��d���\��p�!�����!L�R��!��������������!�DL�f\��������Ȁ�L�Ԁv��e���b�k$�m,�n4�p<�sP�vD��L�`�	4�!��R}j����H=�����l����X��h�����ĺn؁o�r\y�����h�����4�x������98�������%���x���aD����|c��<�v���
��a`�e��h܂n�o�r0�s�3u<�yH����*%4�i���h3��@��3�dA��p�ax�lt�IT�.��l��md�����âo��Ø�����@�T������L�� �4f%Ԃi��X!n�������f�1���y`y���d�%$�etxdl�h��i��m��n܃p�s�Ÿ���u����t�m���|�u`�%��o��u�#d"��u'�xN
<��ȃo�%Ѓl������D�nX�v��
��a��d��e؄i�k�n�o(�sx�t��v�!�D��<�md+�%P�s����c��m��d�o��uT%t�rDh����cT{D��iĄs�q����t� � Єc�!%`qt��D�a"%��exi�#Nd"�x�#%�k<�sL�t�$%%tD�a\�r(%N� ���%d�i�%%l�n,r��z������7�����'{�'�)����\��<��d��*P*��̅a �b@�c\�d��f�Rg��h��iȆk؆m�o�t0�up�v��x�BzP��+zc�fl8�r|Myh,�,�0.yH.��H���.t�n��r�.�/%l�ex/��/�����(/������1��t�2�`3��\ n�44(8H7Іy�9И����s@�e(�t����������A#�A)@�k`�r<BT�������L��dB8��Bh�l��rCdC7<K�H	��dći�l,�oL�rЈs�t�u���lL���bcPN��M��Ї����� ��PM���o؇�dNxN<����t$O�	��^,R��8�l�Q��@�el�ix�l��o\R#LRd�t8dRhR���kf\Do�t�\S����nhS������R�����O������S��SȈh�����܈k�T%�a�U��U����p|V�V|�c��e��g��k0W���a�b(�eH�h\�ih�k��n��o8#p�s�u�#y�����D��t�W�t�t�W�����e��u��t��pX����c�tԉĘ������̉�(�hZ�"ŌZ�����"��"��#�L[%�[l\����H]<�l`���^��4�eLa%�b��D%t�aP�n�c%Lxm��v��
,dx�e"Re%��nTe��pԊr��:4ze��9=������h)Ȋüh��h�����}�j���o�j%��k�l$�pT�r���.8�oH���VO��@��Dm��p�5k��ux��H�p,u��p��4)�`u�@v�$c�d��e�g�h4�k��l�%mȌpԌq�r��s�t�&u�w�y �z�i�x��܋p(z;`y���g�{�|I�{���rD}��X%���%��|��X�o�%rt�u��v�5y ��h�Š}I4��4~��`���}�%j�Sp��t<��~R�~\~����e�E�|��Dth������$�4���́%����܌a(��|��k��# ����gP�n����	�a��e��k��lȍr�sp�è����H�0���H�uH�b<���\�nH���d��0F�8�����n���������n�������8��`���ln����P&e؍i@��h ������a�������������Tv<��83�t���(�v��0�kP�p\�tp�4'uЏ��@'�t���a�c�h<�kT�l��o܏rh�t|�v��|�Ŭ���a�����l̎r�t�������Ďt���؎h<TԖ%�t� �������(����l�����(��Ĝ>L�v0��������oln��)�����Ad�Ô���p��4��L���R����b��z���T9�h9�����П~$�����ȏ�����Fe�iL(o�uЏ� ���b@���o$�|�������Ħ���,�iD�n�)# ���7��7��T��,�\���
p�t�a��o���L��\������Ъ
��]�	b��c0�h8�i�kT�m`�pp�r��sԑt�v��z$��*�X���.h����h�k�t��Я�y�������8�)pT
��@����H�ļ�J�j�����h�s��vP��t�4��k��t�<��H������������� � ��������*r�uȑ�ب����Լ�Ŀ�r �s ���yt��p���,�a�7d +m\�rd+s|��������T�e�����h�u$���p�����g,�����aȒeԒhܒn�o��u��t��e���������u �p��<|��r$�4%<yeD���th��n�����aT�i`�ol�pt�y\f?h�@�o|�H�d���Ks��F`������,c��e��oHsēzԅ|�����o��w��T����.�;\f	��R̓o`�u����ԓd���j��m��p��r��tԔzp��L����a�b�e�f0�hD�iP�j`�k�l(�o��rĖsؖt<�vH�z(�ä�ŌnD������h���:DT
��|�������N �G��n(����<���o�-���̔mT>�x��i��l���,����d��
���a�
���(�n0��<�a��7D���)X�ex�l��rĕu�
D���d��a��o���X�f�d��������Dcf���$����t�������Еh��n�tL�ؕe�o������P�����b�.H� �dH�l\�rh�s/��5��@�o�a�����T�i���.x�p?J,���8�l������e88o`�/ ������$F������r��>��t��)Жa�r�v`�����a��$�f������8�R������� ��x���44�oL�4T�a���X�/����\�l��n�-s�������|�o8��D�������<
����d����a�c`�d̘eԘfܘh<�iT�k��l��m�n4�o\�ph�rؚs��tL�ul�v��z`��H�Ĵ�ŴH(�h�0ktL��.\Z�lW��4�l�W��<��Ę��^�t\��X�d��m,.Ü���eTex�o\l��e��i<f�����xr��l����n�l9�r�@t4��tt���	�1a�1e2i�l�Yo$2r@2u4�y�1Ü�����e(��г`��� ��̻8�4������d�h���t�d|�l؁o��t\yD�ü�7����.�����,��4�������L�tl�h�Zyԙ���)d	��̙����7����a�e(����dwD�ct%��#�� ��P*4@�v�B%�RO��H���HP��0W��0[aT5e��o�#u�#y���hZ/�Z�����Te�lDz�pȚt)�����X��М�Du_Lx@v	Кc�e�k�m`6p�t@�vH�y(T�`y{�|H�������4�������,���t��t���P�kl�m��t��_d�0���t��Џ��|���Лn؛zt���	��a�i�'l�r<�uD�v7�(7� ��0�
l��H����b��aL(o�uЏ�`��/�(Ô�����0�� ��tب�p���]*�Ŀ)t.��X����qu��Ŵ��$���|���i�D�����rL�����d(���������/������Ĝi\�l],�a@�b`�c�d�e(�fh�ht�k��mОo�r|�s�t�u�z��İ��<
t�?�uH84�l���I��L���H��|�h�0k��s@^tT��tL��S%�X_lW����c��kĝn̝t�W�������$Z_�Z_x[��i@at\��ԝr�s_v�r���u��@t���n�s��Z��%�� �e@�iL�l|1r���x)n����.L���T�dt���\�oh����.lt�n��Ŭ.������(�u)�����\�����@���:%P*��Ȟph^RH]��ܞg0W���e�i �m8�t(�ń�q�a�s|d%�j��t5�@���k%�m�,tfhq��H���p���5kP��p��,u��4)�@vL[h��kT[s�|RЏ����tȟv���,���������t����<��Пadu��؟lt����e�'l�m�7�,�%��tL���h-k0�n�-th�L�_����8�kH�%���L�l��T�l�Zn|�s��@vt�t �a4�bH�cx�d��e\�ft�g��iD�jT�k��lD�m4fn��o �pH�r��s�t��uqv�x�szH��h��l��,K��b����e,�h4Il�tt<
���c$H8@�eh;�H%X�r`�sHS��St�W��<J�8��t\���mTe�t���;n@t����a��hԡj�<l�m�s$}(�n�~.�m��ġaL�xD��d������a���t@�(��������$���]0�rt.��<��$��ܦ����|1rl�y�����%h�R���|�s��v8�����aĢf�Ni̢kܢl�p��r�u�*RL�������0�Ԣl �tLa�����h�������|���)� ����zp���0�d����8�o`��h�%t�o9+����`�sx���h�nP&�D���eT�����r`���t������s�����aԣe�o<�y������@m��x��RT��s$����i����Q��3�(��dA����@DnD��P*]�]0�ot%T�a\�j� >�(�)��d��������F��s�x-R�,����kP*����c��dԤiȆk�l�shix�.��\Br�3R`3��̤d4��6���s�67�e�5���i�=�kL>�H%.�X��(���V��0��0W��<�a|�d��o��yp��hZ��(n�Z��d��`\�g�Te����h�n�p@v%̥aHmbԥeܥf�k`6p�t�v�`yD{F�}
�|���u �
������a��lt��	�6aH�e\�hh�lx�o��r/u7�(7�4�R��@�v��(���T�e�@8��p�n�(�/�.4���e������l������l��st�t<����Ȧl����Цlls������cp�a�b�c4�d`�e��f�g0�h��i��j�k�l��m��nH�o�Bp��rP�sd�t��uЭvܭw�Vx��y����<��<
��,Ia��b<Icħd �n<�rL�st�t��u��v��yШz��Ĉ��x��r���`Idاz�ńq�q<f�����0�����^��%�d,�pD���d�i��"�"��4�oH]r�$;`�t\&&��X�r�'L'l�r)��������P)����e�*%�siP,�+����o�-~�,Ȩm�5T�/��ܨst.����ĭ�H8%�H%lW(�k�W����$Z�Eat\%�Jr�R(v��@�s8vH�a@t��T�b��d��e��ht{j̩o�stxPyl~{����i�zm$}@Yl��m�_t�}�\�
ԅ��ĩd��r��ةtȍ��>�l�����o��)����f(�sP�At���	�+.�1ah�e2i2l��or��w�����t�d���\�n��+��/T���|���<L�����r��ľZ8�����b̪g�o�s���>n�2R8���تh��Z�m��%h�%0������r����aX�ex�o��y8�����h3��Q��3�dA���#���P�sh�t��#����p�d����kt%��l���b��e�j�m$�s@�wܫ�4�������ԫ�����(��pADDQX!=|!��!�#�T���#��,��<qAP*��̅al�cx�i��s�Rt.��,��d�y`3��s��L>�=��k�V���i.Ԭr0W����a��el5o�t(�u�#y��`YhZ��tjc�Z��ܬ��"�4��H]��8�l�}��k��fXm�����l����n�)�������H\�@v%Hmb�k`6pt�>t�a�nt�)܈k8������l��s������e��m�orh������]������r��\7n,��a��|�������a�l�uēz`��L�t����
l�n�
s<
�� �a��b�c@�dl�j��k��l��m̯n�9o��u �z������4�a��b��c�d�e�f�g,�hH�iZjвk�lD�m�nL�o��pl�r,�sP�t�u�v��w̻y�zT���X���L���.�x�l��r$���h(�k0�mRHRRHJ���8�rX��\l<f��P��d���d�z`�R���x�b(Xl$l��l����lD��%��eD����nܯs�)������P)/,*T*�����->0ua�,�m6�5,�k�/��4�s��t��wt.��@������������7��l���6��t��8�T>�H8��i���:���k��H��H����aذ�LO��I��а���TV�t\%��r�i��yRPy���mP��@t��	�d�ze\�i�<l��o��s��tбv�y�z��z��H��l~���gt�n���~��l�sԅt�Kb��p�:�:��a������lةth�%ȱlT�4�#H�|���رs������eX�����e���l$�r���t�%T�����4�l8���<�cl�e��k�q����@d���`�n|�r������l��T������ �����oȲr�����h<�h�%�2l�o<7�H.����x����ĸ�#�����b<�nD�t����a��d��e�o<�y`�����#����0@kx�r������L��г�(��dA�5�5��4�������A���d@a��j��nȳrP�s��(�d������cc��k�n��H��T������c�P#����b<�h@DnAv0��0.H.��(���2#t	XRd��l��m��oȴu�Zyx�èZ�дň�d	��p������k�ܴ�N�����o`����v�B#�����7���TA���d�e�lD��i�!A)���A� ��и�H��X��*4��d��g��h��r��sP*��0�a Bbصc�Rg�i�k(�lT�oD�pt�s@�v��z�*��*��"
\+�O�x-��dpgexgl�n�	o�	u�w�,����kTP�<q�`3��̤d\ n|<r�t�3R�5~�4�t�5�<�a 6R64�mL�r46�9)H,c��dܶf�jLhk�l�yn�o$�t,�v4�y�.?0:t��a��e�_i��nĶo̶sԶy�.�/�@:��l�Lq��1R�3t6��5���e�i�n@�o�6?7��9�:R�BR�C�;��:<�kX�t�u@<R\?h?��`�a�=h�t�C��4Wc��j��n��pķv��h���è�����P�~�D~���W��̷�HG���tԷĨHN�H%[a �cSe(�h0�i8�m@�nH�s�J�LL^lLt�N��N��S��V���i��m��s��t�Cv0W��P�a��e��i���DX��X��s�Y;�Y#H]��a�Z����(���p)�e�i�q��h.�l��sXr��;ls0s���c@vt�u���s@v>@�d%fT[s�x���t�x�a�'l�m��r��Ĩ7ń�����p�fX�� �����n �������������'o��u,��(�GX���ȹvh���йh����ܹc�h�k$�l8�p�FsL�z��]�4�R�����lDM���0�k8�Լ��D�n����GrD�,�hd�a��e�ai��uĿ��l�cȺdкl�rغt��ĨJ�U������������NP����|�ä�h�a<�eX�id�oqs��y��z$������.��N0�Ÿ������	�������������D�����L��x��t�d|�l�������T0
������n,�����e��u��
����l����	��b��d�h�k$�lHs8�tD�z���P�Zx�;������l��`��a��J����0�r�hd�d aT^��P��P���X�������p�a��e��iȼoмu���8���x��\�����<�f���������X��H��L���ؼo�zL�<
�,Ia<Ic��d��g�
iнl�m,�n�oh�s��t0ut]z�����aľb��ch�d�eL�f��gD�h��i��j��k0�l�m��n8�o �p��r��sp�t��u�vP�w�VxX�z�������4?�����o�G ��lȽb�d��e�u���������sl�Tup�9�����D��T�d`�k �ü
��@�o�%H�r�!��$�k����'t�eL'|�l00t�/����bt.����������h;�H8	��e�i��l�o�p$�rh0s��t���T>�?���u�A�B��BnL��,C���a`�i�:k��o��ut��)��X��D�����aX�u�C�C��l�� D�D�A���v�����FRG]�H��
$^d�hH�iT�l��p��r��t�v�yt���M��M�����M��tL���t(�v��dM����d�� Nh �o�R�N��4�sTN<�eQ���.d�e�Q4R.DJ��l���R7[a�S�S����nHS����o�T��t��<T�����xVJ�V��X�X�hlW���c@�u\���W���������<Mf M���aL�iX����l�[(�h�[/4�c��\��X��T��t\��g�i�m�s��Ŝa�L"a��i��s��ôa� �������P��\c;0b��sTe%�m�l>�kn�<f����@t��
��a@�bP�c��ht{j��k��n�r�s �t,�wtx���8vZ�wdw��H�hd�kx�zxp�i@xDWRDyJ�x����ty�����$}@Yl��m(�nl�t��o���؁��r��|�����a��z@(#����u�0�����Ð���l��h��y��Py���.����4�d��@�ep�f|�l��r��t��\�h�s�����a��eԣd�p������H���H+u���\�
����h�&��������p���������a�h$�l8�n}u0�x�u���
����y� D��0�it��l��n(tt�����4�;@���`�sT���h����.��m��t�����e��i����.h�b� �.�и)��i������h��8���	��d �i(�k0�m@�nX�o`�sh�vp�z����%�7������8�cP�s�+8���7��X�7��7����x�d��e��m��o��u`����|Os��7������b0�����dh���a�h �kDPm(�r4��x�P������<������%�����a\�e�h��i��l��tL�X����T�c�@m���p�l�i��7t�tH����o�R�/��t�J�%��e��r0����e4�]��s��	��y�����tt�aXRd\�j<�l�nh�r|�s�������4�eT����L��dx��`�aLx{%t�c�k����gd�e���ddun��s����a�cT�dt�e��f��i��o�sX�t��u��v��y���@��(����uu��%�k�l��d"�Z �o$l(�n8��4����T%L�i��<`�iDh�f��x$� F�� ��eX!�������"��"��ld"��b��i��n��p#��]k�8�@8����e�#>�e �l`y�D{�#��T4����������8�i�%��@�r�%%L�a|�e��l��r��ti��p�t@&�(&����o�&.Jm�&����ed'�.'��p�'Z (�'��i�(��)#�(��s)�����h��(�������*t�c|�g��sP*��
$�a��b��c��d��e��g��i8�k@�o`�r��t��u��x�w�*{\+{�+( l��rh,�,��l�Q��-����e(/��.����r�04@24��oH���`3����c�d\ n�3@�oԶy,���o!����$���4G�94D�e%H�et��<��P�n�8��l���@��Џ�@��r�A�A����a��f��o��p`�r��x��RHB~��RлR/E����n��z�E]H�����r(I���d�H�a@�hH�lh�o��t|��LL>PM��loo0P�<P��T�t$O��\�r�T.O��t��U��T%��e�VD��f0W��	��a��e�j0�k<�o�#u�#y���X��X�Z������"��#�H]�f�u\`���
xc)�u��
�c�a�c%$�lTeH�u@i�kl�j��P���#��pt�k�t��w@v
|�bКc��d��eL[h��j��k(�l\�p�rh�t�y��İx��Pm��x�hz
`y����l�|��|����4�'4~�����d,D}����\~����eD6o<�Ô~��h^��Sh���H��$�4P�ì���&a��r��v���<����TrH������0F��N��ei��o���x�D�������L���Jm8����.������j8�D���e������p�/)��������P�kd�ll�m��t��P��`�p���H�������\�at���a��h��iD�jxnl�m��rp�t��v��y��z�����d�ŤRГ
��l�����ix�N�������0���(�����u��u��1�����m�c5�c����d���$��|�
����<�aT����P�e����X��,�%�(l��\���|��p����p�����oЫt4�������g��m��n��t�z$�Ĝ�t��v4�7��7ԼĿ����*d8�e +mD�r0q�l~��t<n����,�i��)T�e,�L���b��eh-k��l�-m��o��r��zx���i��DL�%��e��o��ø�
������������T7u$��H���t������eL�tL�G������k�lX�<
4���a��b��c��dX1e��g��h�j(�k4�ld�m��n��o��p<�r��s`�t�7u��v�zT�Đ���?H8��l��r���Bn,C����a��o�A���v��H���c��e��h$�t8�v,��8L�J��ztL��.xl��dM��������<T�I����xV�x[dlW��@�t�W��H����<������d��_l��t\��x�e�Jr1v���<f���^�@1���>h`l�1rt�bk��l��n�u�y����%��иl��������̻��� �m��th��Od����.aP�e/o�3uH���\�dx�tl�h�n�Zy��Ô���d	�����������7����@@��rd"��t����o\4�P*4�J�����H�l�4s4�y���(��4NPM���elooV�O�� ��<V�0W��0[aT5el5o�#u�#y`��l�ŌZ��`"��j���5��p���#a<$eD$i$�)��x��D�����h��@vКc��i��k��l4�t@�v(��H|T|����e�|��X�o�5yL~��@c\~����a��eD6o��|~�~�����x���������$Fr�YĤ�P�kX�l��t��t���e��h��m�Ġ����|�k(��,�t����X���7�4�)c,)k���7����d�$�������I9�������D������L�����d(�k8�nD�r|Hv�zT����>\��h���0�a��`� ���L���Z����`�r���<

$�a��b0�c@�dT�e`�gh�k�Vnx�r��s��t��u�����x�a(�b4�c��d�e�.gh�h|�ih�jt�k��l��m��n��oT�p��r��sL�t��u4�v@�wP�yd�z����������
j��,�h4Il�L�rHV�p9r�R���(Xl�t"��x�lH]r&q�$��t�'L'��rP).������D./t.���������0��D���i�������//�k�2�2�l�?���veH8�l�H���d|�h\xl��sP�ØI�����n<M��\�. M��d�atL��p�l�m�SZlW��k��n��t�W���������Zt�
x[4��it\��r�^�Dc��j������i���@t<�c�zeH�fX�h��k��l��m��o��r(�sT�t`�xdw��H�hd|��}�$}P�l(�nt��~�~��l��؁Vl�����o`�V�����ex�����c��tD���oT�ԅt(i�����t0�����o�u�V�I����������Ő��t�<����4���<��h�H�i$��t�%t�n�t8���d��h��m��n��s$��_
h���e�D���l��n��
�D���p��D������a���Yt�u��(��a����� �/\�s47=H���<��4���D�è�P�t��%�?ah�%��l��r��s83v����2e��i��o������P�����������7��J��t�����t�+.4�ed�i��o��u�ZyP��д�������o@�u��$�n'��Nd	��H��(
T���\�cC�Bp�o��x�v�/%��o����3d"��j���(��P*�a�b4�cp�d��e��f��h��j�k8�mL�p|�r��s��t0�wd�����*{�+( l,�rh,��,�D�eL�k�J
x-4gapge0.�H.��\���.����e��l�gr��u�d�n��0t��a��m��s�0
p1RLR�1t�2
�2��l��r��tl�
L3�3��4_�Ea(�l����������5
H70�e�:
�:D�ad�ol�r'u�;
�;��k��<��t�t�=.��l��o��t��v�>
�>
h?
��u���?<���?��i��kh9�����0�@.�r(�u���@
�������ب�4C<�e��
E/PMt�HL�lh�ox�r�4s��t��u��ŰQ{��o�f;hR����g�R�O������T��U)0W
�)���������F���(��@v%t�c�k8�l@�mT�n`6pp�t����|�� �o�%r$à}��r0�s�=�\~tHtL�f�R�{�Y��\�.����d�e��r��y� ń������o��
����ad�����������k(�l4�n<�pD�txF���������c�m�ut�����ol�#P�\����Ql�����e�_p�hЏ{t�p�e��l�m��o��r��v�7ń�����.���x�e��;����f@�
�����ip���o����b�c0�dL�kX�l`�m��p}s��v��z�����X�_h�D�e�`�����<�?<�tH���$�aL'�4����D�l����tl�yDs�R�O��t����|�Ŭ�_ȼf��pԼ����a ������i�����������9<�k�������Ŀ��c,����/�nd�����n����] �s��(�y,�4������H�t��L���	\�e��k��l��m��n�p�r@8v �z$�
��)��ux���������L���i�����?��n��9�������h�����o�����P�����uP�L���8�k<
7��a�d,�e@�lX�ol�p�st�t��u��v��z��4�a`�bt�c��dD�e�f,�g<�h��i��j��k�l��m��nx�o��p�r��s�tT�u��v<qwP�xX�y��z���4���
�����a��k
���`Idاz ��X��p�������4���x�ll4�eP�n4fr����yn\!�!d�l��r��Ŵ!�Rt!�����P)��(+h�*%P�sx��,��b /D.����tt.��	��������D��������������T���/��`sb,�d4�k<�nX�v<Q�L���0��2T84��5��4��D���7�H8%l�p�BR�H%��h,���M{tL����tlWl�W����������P\	t\��	��a�e(;i�l�r �s(�u8�y��ÐI��]�����`_��k�d��i�lx�n�tqqLq0�s@th�cHYht{j|�m��p��s��tdw��x�zxD�t�r��_����r����t|���%��oh���w������]��r�t�z���\���������������> ��h���h}���>h`lt����+.p�a��e��i��l��m��o��s��u������4�/@�NT���x���H����}oD�3��o��L���,�>��t��X�N������8�$�aH�bd�c�ep�f�Ni��n���нR����l8�r@�t"��Rľ4�OJ�OP�a����X�kL�_���;n����x�a��n(�x��������%��b`��(�%h���d(�r��s����������0�d8�j@�lH�s4Mv$@����	��a��e��h��i@�jP�o��p<�yx�ô�l#`��\�tD�NT���T�r����tjc0@k���h�����4������.�Ql�@m��s��z�������]��zT�
d�����ap���7��c�Qf�o�v���i$�k�P��� �fd������,�m���@2#���H�gh�rp�t��vT�#�p}�B��x�����HR����st��a��o�ZĜ$�]%��i�o�s|RJ� ����o� ��c4�;d"�k�#��(@Ro�ux4�)�� ��4����H��T���H��*�*��T�d��n8�r��t��xP*��\�a Bb��c��e�h�i,�j4�kT�ll�m��n��o��p��r��tP�ul�v��w��z0+L'Rl+��,��,Bld�y�0���s���x)n�2�i`3��\ n|<r�s�3�4�Nn�6�67@�e�5��H�i���nH7`�a��o4��|�t�7�!�@8����k��tH9��9��;;�;����t�:��o��p4Q}�<�h@h.|CaD�r$���7�P@���v\@����@i�A0�u�@��8�o�A��(+hd�k`�r<B�B���j���%x�e4C/D�C����d��n��rh�\DdFF����c�HtHG����s�H%�a�dqvpI(I����c<K�0W	D�a��c��e��i �m��o�s,�zh���V���Ĵ1hZ��P�h�"ŌZ��X��(��$��(���p0lp[%|�hH]��d8�l�}��]��]����b�a���b��d$b#h�# f��	u8f����kTe����c�h�pg#�:�j%tDknp��pB�#aT�e`�i��k��o��üq��TEn0st��(�����l�����,t����c��khq�����Tt��t�u�@v%��k��l��t��v�|���%r\~%��t������zt�%�i �m��r�7v4��,�%0ua�����,����)4�@�a��	��d��e��h��k��p�r �s<�tH�u�p~H�����v�)�����o�p���o��ì�����rTe
X�4�����o�����l������l���RO�������t�,�t��Ĝ���4�kl���(+h��/t�sԖ,���`�c�h�tĿ]0Gc��d��p���#����lsc�/����a�i(�o4�r|Gy�����D�Ť���������L�����L�������T9�������x����Ř���u�$���<��������
��b��d��j��k��m��n��p��t@�z���P�Z��Z�����Vo��r�.u�
P�Zp�y$��8�����������Z��rL��������h.�,h�i0�y$�������Tu���������48�m������L�d��p��tt��(�|���l��8����L�4��lL�%�/<
����.h�a��b��c��g�m�n�9o0�r`�s��t��u��z������a �b8�c��d�e�fD�gd�h��i��j�kH�l��m4fn�oHpxq�r�s�t\uqv<qw�Vxy z��t�ļ������
sh��t�t��|�e��l��t N�
|���r�+���sD���Kc�nxD�%�i�)����s�����<���W�"�b"��$�a�#�\&t�a��o&��D�r�$T�t��b`���l�m���'��aL'��r�'��,��b��h��i�/���[.`sb�m4�s�zt.����������D��<�����4����(8���e<WnH8%,�r,C����u�H%(�hT�i�0k��TNL�alW��kĝn̝t�W��`��Ę����t\%��a�b�h�m�zv�^�HD����uP\7��n�a>�u�v�����M�@t��@�bX�cx�d�ze��f��h�i��j�l�m(�n4�oD�pd�r��t��z���ŘvR8v8�dP�r�vdw��h.4��pd>d�aPy��l�kd|Z��
$}��c@Yl��m(�n�_t���X���}�����P�#�~x�t�~4�hl~���g�����e�D�h.؄�������iԅ/L���<�r0e���%P�o0���X�nx�t��v�k�`#h|nR��Rh���s���<Wn��r��d�����������]�n��r�s�t���|�t�������e��% �d0�o<�rx�hi����(�xH�t��%�e`l4���i��nt���T�a��b��e��n��t42�X�̯�����P��8�7
�aD�bp�c̪g��h�Ni�l�n�oD�s��z��Č���Z#���n0��������s0�t���h��.���(�a$�ľ<�r<�H�P�e��n��z��ð���X�i �����|���NX�4��$Z��a�����k������������aX�u0��i�����n������o8���تh �mH70uaІyh���d�rp�v�YĄ�0�t|�����P����NX������(�x�t������X�<Wn ����[.�d�ckdr�s�ń�Nx����������������kD��h�%�lDPm(�o<�r�%P�x��� �u����4�o��+.��a��b��e��g�i�l�o,�p4�rD�sh�u��y ��X���������c������c �A�/D��,����n��7�a��nH7����m����oH���/D�������H�� �%<�td�����P��p��\������%x�eT���v��ot%XRd�(�c)�����d���x�\)#P*���.,aPbdc�d�e�j�k�m�opTr`s�t�uv�Bz��*����n8�r@s\+R�Fr�+Htx-�,��\ktt�-(/��.��|r�v�Ű/��dB����/~�/�0�lD1��3/�<d����4�vH7#�9Z�:�:�a i,s��lL��c�S��arb��4��a<�<��Hi�=Zxa�m�=��=��pn�p@�o@�i�A�h9����(�#����n������A���i�n��8�4�t��R<I����c�Ba,k ��<GtHG��4c�H%`e�4sl�|��lK����uK���3�|V%0W��0[apSil5o�p�#y�i�@v%�aHmb�h�k�Dlr tX� w�v���i�{%�|���o$à}��r�������������Hr<��47�H���4����d���P����k�6t��`��p���x�t�tt�%�a�e�l�mrHz�(�(������mdw
���c�mD��%�W#`����b(f$@�����a0eL(o ��X#�����.@c�]Ы�Te��J��t��.�c�Dj�k�m�o�sTN�h����i��J����r̴���l,��� �dt��)p,�t��/(GsĿ���[.pbch|*p,��`VhDpk�����.�Ga@cLe�kTo�s������`Kl|�4T��dn�v@8���.�B��T���@�%�op�C���$����tX��t8���������[.�c�h�mt�x�f���������|������tL��	LaXb`ghk��l��m<Wnpp�z���,kx��������P�n�����t�k�u��]]�a�d�e�ikls,u`zT�h�<
/�Wc�s�$Tt\��pJi@t�8��h���t@vZx���o���� v�z,�hd�aĿ��<ct.��H�L��)��@���a�	bH
ch
de�ft�g�h�i j�k�l`m�no�pxrs�t�u�vDy�zd	�T
�������a��k8m@t<
��
�a`c�d�i�j�m�n�o�q�r	tD	v	�<�(�.�L$��Lc��Th�\t|�TV����t����8�rXtc4����c�Z��l�m��Dܯs��"��H]r�v�#R���&�l���	�L'l�r(	t�(����+��0	��*%8	�8.ND.��P	dt.���	�X	���������4��4���	��/���	�H8%	�	a�	d�	i4�l
rh0s 
u<
ø���8;%�>bT>���	k�C�C���	�,C��
u�	ðDr�F!,
i8�8G�:��4
��H%8�v�W���:�p�@�t\�+.�
a�
d�
i�
l�
m�
ps�
�
��
�P\��I��]���
�H^$T^���
��^t0b��d�Te�<f��\M��i	�l��uRt��s@t��a\bxd�e�h�it{j�kmo��pLsDt\v8v_zR z7dbPy��lo�Ŝz�z����`{R{���g�m�{�X}N$}�a@Yl�m�_tt�ð}���l~���ml����lD���lԅ���Kb$u0��X�������,�h�8iTr܏4�{`�����v����dd�sؚ��]�s��%�e<�rPy�����dt����1a�l�����
����o
�8����a�Yb(
cH
d\
gl
hx
n�
sD��
������@d_0
nh�<
e`���T
e�>nD���n���
g�
s��>i%�>�
th��
rń��
t��N<?e�
yX��$��8����
�����
�������
��� ���c��4\dde|m�o�rH��=����@�l�t���_�Q0���������%h�>�l�m�r`qt����������Ql�m����e�h�io0t�����?�����cc�������@DnT7uAv���]8a�	�d	��L�t�\�j�l�oTÐ�\����|u��l!��tT;�a�i������z�������� �Nu|q�)���A������P*���.`albxc�d�g��i�jkr,sTt�u�x�z*����n�+|My�,��\k�]R�.���b`Id܋p\Br̶s�wtاzlBŠ��2���a@2�r�4��4�����3������o�4�r�<���Brt>L>��y�= k@sHt@?Rh?���Y�@D�rl������d��Yk�X��x������������A���hk�m�Ĩ��dC�C����nķv�ňD�D�����H% e<lHr�4s|�Š���K��olK��r�M��@cD�tPM��,a�QtTu�R\X�V��\jtCl��t0W��	da�d�e�hl5ov�#y����4hZ���m�Z����`\)H]��8�l�Y�La��o�m]�j����|n_�p�@v%
@eHfPk\lhmpo`6pxs�t�w`y)D{h�|�%u\~��(6�H���t|������Hr�y������i܉���a��t�%�c�(r��Ԗ� �) ������������f$hL�k0pDstz`������e���.�t�<h,�t`��l���T�$�Լ��lm,�hĿ���c�k�r����4�V ����aTe�m�o|dTe��%�7dkn p(r�7�0��L����� ����� ����t���|���0s����8a`c�kxz�,�����`Klx�z���4pn��4����sL�>��8�k�o������|r���<
�.�a�c�d�eh,iLktm�o�p�r�s�t�u�v�w�z�����apb�cTdDe f�gH h� i|!j�!k�!l�!m"nd"o�#p�q�#r�#s�%t'u�'v<qw�'x�'y@(z��8��#��)�a�h�
l�n�
s���
RL�^Th�\tp���������ä)rsD\R�4�X��$n��8i���@r�t`vPR��Pr�he����s�����Lj��4!�`n��t!��@��"�"���aHip%���o�$�kpr<t|�h�����$�4��%l��\&��$cLi&��,r8F�|&��(f�(��T��(��\à(��h�L't�P)���k�h�� +~�*%�mLip�+4�,4�kȨm�qnh-��//(h4sPt\vhzt.�����X!�D���#��'�|q��R�1�� d�54@tp6�H7�6��Hl�7\7n(8H8%�o�=��A��|s�H%e(�h�i�k�lo@^t8�v5�����r�t������TN���6�O4Q��eH9�R��t|RnlWl��kLm\���W��$�����Z_t\%|a�e�i�o�r8s�P\7tk`_�k���a0b�m�h\f7�u�i��e�ou�j��e�k���bg�Qnp�f��k�l���%j\zm���yn�l>,o@t�a�b��d(e<fHhTi�j�k�l�m�no@rTu�v�y�z���t���+b�rs�t�""���d�u�u�r8v8�dDyJ��x���ty����d�������{R{�� s��d|4e$} dl~�g�~.�k�m�r�t�v��`a�demdolstt|uh��T����*�h�\f7�u���o�u���n�.��|�r�<eX��!$���$oHu4���,n@�!H�(P���P� �.�����
��Rl����v��{����h�<l�z D�
n���������,���.���i����a�:��a$tԅ��p,u@<�0�)�Pt��0���8aH���]Ld��2����`��h�v��t�4��Vk�r\��B��T�@�%�ø����v|����o�4Wc�l��nؕ��.��e��/�=t�f(���>pi8�<��H���0��
����Da��%LrܢXt����dl���`���|e���e�l�o r0 u�����a�o|ux���p�tAv�A���PtH����uܬ��������� e��4]���� a��
����( e@ n4�t�%d cl ibkt o� s���H�L���<�h,�~8�	� c̪g�Ni!kL%n!o,!s@!tL!u������� �H�� ð���� i� k�O�����%� e��� t$��8���!a$!d�����l��p�8!h�����k �/�dl!kt!v(����T��G�!a��Rx��!th�%�!a �k�!lDPmh#�"���!t4�7�!r�t�2e��o�%�!o�]t%\�j%"aH"i��"d�pn�v,!,"aT"e\"i� 4"s��T|P*�"a�"b�"d#f#i4�k�oH#s�#u�#x�Bz*)�"h�"l8�r�"z�R�*R�,Rl@�,,�"e�+�"l�.���b#g�v#ŜaR�/�����1`3��\ n0#v8#z�R ]!�>{�=@#ll#t�����X#ph?��`#oX�B��x#h�A���#c(+hdCHG��4c��s�H4�4s0W��.�#u�l/�p���5�)���#�t%�T���(�@v%@$aX$bl$c%f�$k�Dl�$m�$n�$o�$p�$r�$s%tL%uh%v�v��tkP$tt�R�w%�	ahx�Lx��d$h�}�$j�m��r�u0���|��x$o�%r�$�,��T��D}���$�H�%����H,c�S�$�4�$s�%|���t%a(%rL� ���%d���P&e<%i��o@�D�8���D%b�&c`%i8��%�&i���%k�%l<�p���t���tt�%�%a&i�'j(&l�m|&o�&r�nt'z&è7��������%o����%k��%t�����%��k��X�uL����� &a@&oL&uT&yA��dov\<<�|�b�a\&o����d&i��p&r����&e�:k�&o�u�&yЏÐ��&r�&u�_�\`Ap��&d�&i�&s|k�$�������F����&oЫ	��4'b��dH'e\'hd'pp's�'tX�_@'i����T't|������:ft��)p<�K��|'z��TĿ���'b�'c�'n,�G�'h\�
��/��y���'s����t�y�� (iHkH�t(�4(ń/P���(�������(nX��8���,(�L�`(el(nt(p|(sl�;��X(mh��P�>��~Ti���(h�(i�(m�(v��d��B��_���8)a�Xb\)c|)d�)e�)h�i�)j|sk�)l�)n�)s�st�)z�)�l)��)����(��u��p����4���������<
��0)dP)��#O���H)��H_lW�W��d)���t\�@t]t��x�������)a�__@v_���)���)�L�*�ĿVt.���)��)��NL�_���<
��
*a�*b�*d�*f�*g�*k�*l +m0+nL+p\+s0ul+x��*a�+b�,c�.d�0e�1f@2g�2h`3i�3j�4k�5lH7m@8n�9o�:p�q�<r�=s@t�Au�Bv4CwdCx�Cy�Cz�+�H.�h9�x1�t�ta�e�7��7lȽb+l+t$#���7,
D��(+e�i�Jo�u�y!74%�$T+i�t�+t�E.�/]�+s�+�t.��x+��1��1��3���lW�H1���+��5ZH8�+.�	a�+d,e�i$,j,,lh,r�,s�,v�,z;��a�<���h,sh;��,l0�R�?��?\,oH,�@���-tD@��<,�AKA��T,v,C����oXy�	ÔE�5y\E%|,kpG�$H^�H��
�c�d�,hd-ijx-k�-l�-r�-t.x.yzP��tL��.LcLd(-fxl�m�t@-vT-w\-z-�0-��L��T����#dM������ NCL-iNr���TNc�O��Op-a�-e�-f�-j�-o�-v�-y�O
���������P���-t�P\X��Q��@aHSTe�6I<T���-a�r�y�V��V��V.k�YlW��(.h|.p�.vp.��W��0.�0�<7�Z;�Y�\.s�X��d.�[3,\Zt\��	�+.�.a�.e�.m/n(/r�/v�.ð/�P\7�+b���@�u_�.n�]��l�D��Te4D��s4f%�.ej8/cT/m\/tH/�d/�i/a�/��Wa��0��@/��X��Y�Tj��D��4�|j��p/m�/s�/t�j��x/��5� [\l<f���/��/�x����fj�p4�/odr9�q���/i0s�/�|rf<r���/�,�mlsZ�r��0c@0iH0ld0ml0nt0t|0uX0�8�/�sZ�s�W��P0�tZ�ZZ�sZ��/t@t���0a�0d�0f�0g��h�0i<1j�{kD1lp1m�1n�1p�1rLs�1tlv�1w|xPy7d|7�|7l~��d<l1r01Ř#�����1u 1y�#�h�����(1�t�X1a`1ih1u��
����3�D��.�{i�7��70�th��Tn���/�]�1t`�]H����	�1c�1d2f2h�i 2j,2r82s8\yx�y\�yt�/̂ox�
D���2aH��������.`2hl2it2l�2r�2v0��He����t�a
����|2i�2o�2ul5
(�
Эtt��2d��ibk�2l,3m@3oL3tTe�%�2m�����7�2k����2i 3Ü[fг��3.`���3�D�t@� eL���43tP�tX3i�8���ha�3d0�m�3nX�o�3t ]zh���a�e�����dl�s��p��3a ��3r<�tx��
l����	�3a4c4k(4oP4s�4u�4z�4Ð4�4�t�>�	I�4a��4b�2kD4s����<4o��>p4k�$��\4����d4ð���|4e�����4���R���4t��l�4r�����4� �Fd�h��4d5lx5o�5t�5u@5�T5ż�����v����<�n���5a05�4����ccx���$5��E������ ��4���L5��5����l��d5sx���l5v\q�����5�4�B�5��>lK���5eT��5p����+.6at6e�6h�6i�6l7n/o7r7s�3u.x(7yT6ì� 6m46rH6u�Loôj�0���,6s�)#��/@6t���h3��@��3� A�dA�,R���6i���t<nd�7X��p����6i��7�6c�6eP�����6c�d�6n4�s��H���6o����Pt�/�6u4f%��u��A@ 7c��]47st	l�h�7i\�j�7l�7o�s8u(8y�7�x
D%t���t7nX�u��z����.����7e�7����E��/������7�\BJ�7���7r���7d8l���$4���L����8k�cr|�D�� 8e�l8m��	48a�8c�8d�8e9g9sH9t�8�\4�tR�~@3��t8k�/|8l����8��J��8a�%�8e�JT%�8r(D�8e�8n�8o�f���yn��9l�#>(9c �ll${�&.�&��09e�%%<9r�(�m�t�u)��T9��=��?��A��D��+��9e( l�9o:r:y�9�P*���9b0:dH:g��hX:k�:m�:p�:r�:s�:t�:x:�,�=��:���9��A
h,
�G
0.y(:eH.��:�Y�.t@:o�wt\fJ@2R����4P:n�:�t:Ř5'T5��l:�T������:�H7Lub�:��<�=��:th?�@@�:rdCJ���!t(I���:r�H�:aP;cp;e(�h|;j�;k�;l�;n�;o�;r@<t(<��Lf�L��0;��J��8;ðJ%D;h�K��K��\;alK��d;rM�H!eDM4N��бvPM���;e�N%��e�O+�O���;s$O���;d�;m�;t�O��P��d��P�;ŰQt<eP`#,R��<t�R�8<iO��<�t��PS�T%(�a��e�<i�u�yl<ä<�x��U��d<��<��0�!ܛ@U|�<n��pU���<��V�<r�Cv0W��
�<a(=b�"d4=e@=gP=jh=o�<r�#u�=v�#y=�P#�`Y�Z������"��"��=��#��AL[ =oH]�faZ�c
xc)H=a`=i|�
Te#cx=t(i�:rn/�nR|n�=n�=o�n�p�5kȚt�=v�=�|q�q���=��ut4w��(g�v���=n@v�=a�$c<>dD>gL>k�>l�>o?p�r@?sh?t�&u�?v�$��>�x�{R�|���o�%rh>ut>y`%ð}��tX����.H�T����>��~/�>Ĕ~���>��E�\~���>�X�d����>�T?��7� B������>b�>mH7b���3��>cl��?�$�44?r���?�LR��Q��,?i|�%L?o\���� ����g����\?a��l�?r������P&e�?i�?u�?�8�������?��?�@��������&i��z��0�k�?m<�p@t��tԖRЏ���?c@k@'�p�~t�	p@iD�j�@k�@l�@n�@rlAs�At\@è��x���H@z����P@����h@et��4�7|@tĜ>�@a0��L���<�n����@alnÀ�%��u��`����@dAv����@aAoXAu�M�M���@�Р��@�p�<AcHAfl?mLOnP���(A�����0A�f��$���PAk�${��%dAc|Ae���D���Ae,��Al4�,)k�|����An�����AaBc��dBe<Bk8olHBodBr�Bs�Bt�Bz����(�����A�h���Bk�,Bn4Bt�{|�{���`qt,�4�����PB�����xBiH]rXB�8�pst�%�)p����Br���8����Bi�B�Լ���BnX!9�����B������p���CoCr(Cu�B��4x���Ck�+����CsL�t�k,���8aԒhPCi`��P���HCl8������\CbH0l�Cv�Cy�C�������������`�]�Cs����Hsx��iL����CbDdDe$Dk8Do\Dr�Dt�Du�Dv�Dz�D��A���D����Cn���dDr0���4����xJaH�,DrLDsTDt��j$�j����88o�uXy�!�,thq��tD�`���|D� ����D��4P��4t��L����D���4�D�L�4X�/�Da�����DlEs.Ŭ�,���H��lxr��Ec\EdԘftEg�Ek�sl�Ep�Er�Es�Et�Ev�Ez�E�hE�F�t\�h�W�������1rh��EuT��E)t.���E��H�4s0W���@v�Et@�vt����6a/u7�(7Ĥ�ZL���<Wn)��H\�H8��l��FbdFcpFd�Fg�Fh�Fk�FnGpGrGs�st�sz<P��OPFl�H��XFkt\�FÈi��]��|F����1nt���2l�FmD�h���؁o<RD�Ff���Fe�Fg��LQw4Q�Fl�H�Fp0W���#u�#y@vZ(Ge`yttL.�H��0Gh��<Gc�Gd�Gh�Gj�Gl�ZnXHpGr�Hs�Ht�Ez<H�\f�t\���Got���2l��������Gs����.a�Ge�3u H����.Hz�GĘ�y���G���Hp��GeD��Gt����Hi����3��(|e�u)��,H��H��QdHo�HLHrhR����m�������pHo@vxHtX�Џ����Htt����6a/u(Ia�Jb�Jc<KdlKe>f8LgLLhlLiMjDMkPMl�Nm�Nn$Oo4Qp�Qr�Ss�Tt�Uu$Vw<VyhVz�J�K�O�<
��
h�apIc�Ie�Il�Im�InJpJr0JtDJvpJy��z�I�$��(-f��dIh4Il�$R�Iil�Il�b�0���I��R��Im��R,�IsD���Ie�I�� ���I�!3"��H]r�(�(��J�L'$J�8��*%<Jl\Jo0+�<+��TJu�,��+��hJtD.��lsct.��|J�M�V�H8%�Jeh;]�H%�Je�JhKiX�r�JD�LtL���Jc�JeKu�<M/�JlNTNDlWl(�n�W��K��I��]��(K�t\��PKfXKp0K�(a�i�tts@t��`Ka�ze�Kh�Kit{j�Kn�Kp�{q�KrLs,Lz���$}���l~���#|�
��Kc0����Ka�Ki\�X�u�LŐ��Kt�����L��J���$La��%DLl��t�)XLiH�)h�#�Lg8���	`Ld̪g��k�Lo�Lp�Lr�Ls�LtMv���8�����n� �Z�La�LlH�������h�����Lt��p��Lv�# ����n��t$Mo<Mu���?s4Mt��R���h�%�l���
�.�Ma4Ne�hL�lxNo�Ns�Nu�MüNŬ���8�j@�l�Mm�Mv�Mw$@��#����*���M��*%�MðMĬ�������M��+�����0@k(N�����M�PN�dN�(��dA�<u��5��Ns�4��N�����.xQhȳr���]HNs�����\Ns`����pNsAv�R�\t�Nc�Nsl����������N�t%XRd�Zy%�N��=/�Nt8���N�)���A��R��T��F�V�H\�P*���fatOc�Od�ghhj�Ok�Ol�OmPn�oPp<PrHPs�Pt�PuQvQz�OĤ,4$Z#0.��|OkH.���O��m��l>�Ot�.���Os#��44�dr�d�6{�67�Oe�5���Oi�~H7�Os@8����n�;N�:ZPkX�tl��k�(Pa�<��0Pt�=.hPatPe|Pi�Pt�Pu�=�8�r`y�T|�h?��&�A��@���Pv�@���Pa�e@�Pr�P� �������P��A���hkip�Pv�h�hŬ�R�B,�a�C#4Wc��n Qr\D����e�JJ�H�,QbLQl|Qs�QyPM%\QipQo��7.d�xN��hQc�S_�Qa�v
<V��V���i.Ԭr���Q�0W���Qa,ReLRihRo�RuR�R��#�X���Q��[hZ���Q.�jl(n�Z���Q��[�\��R�4]H]��$Ra�}��b��@-m,�v�a<Ro\Rtp��CoTe��#c�Rd��j�Rl�Rp�RtlDz�fh��Ro/��:�(i�l��0lm�Rn4��p��.�RePSihSÈSŰq�q���Rc(Sj0Sp8St@SzSĈ�����S�Lr�r�h��rls0s��HSc,t����chq��\S�t�����tSm,u��|S�@v�+.�Se�ShTi$Tk,Tl4Tn<ToXTp`Ts��ttTuH�yT�(T�`y��Sl<�Ihz���Sy�{�T|n��w��T�PT�|T��|�\~��_���HTu�EN$�(|��lTu����]���Tk�Tt�T�t��ď�����T�Џ�To|�ð�
t�%UbUe8Uh@UiHUk�'l\UohUr�ntD�v�U�pUŴ�Rdw��Uc(Uk0Um�ID�#(��|Ĝ~�����Pt��PUu�������(���x����|U�l�����Us�����Up�Ux�Uz�U�`���Ut����U���л���Լ���UdD������UjVl\�Ŀ��c,����Ge|�PVl����0VeHsXVt��
�����,hh�lL���ؼo<
��tVa�Ve�Vj�Zn�Vp�q�Vs@t���H4T|)@v�Vi<
����.�Wa�Wb�Wc�d�WeXf0XhDXi\XjpXk�Xl�Xm�XnYo@Yp`Yr�Ys�YtZu Zv��z�X����VaL[bp[c`\dH]e�`fagLah�aixcj�ck8dl|dmenTeo�ip,jr�js�kt�lu|nvP�w�Vx�nypz�Z�\ĘjŤ����We�
l�n�
stA�x�Wm��Th89k�tt�/p9r��������Wa�Xi���X��$X�ľRX��<Xb��%\r.��PXn������hXi_���%|Xel�XdH
���Xc�Xk��l�Xn�Xo����l�4���Xbh�NuD���Xi�����0 ����Yc�r$Yt� R�M\!�,Ya!4Yl`"�l"��LY�"��pYcTY�p[��%�Spp%��xYu�$�Yk�Yy�&D���'�YmL'�Ye�Yi�Yl$J�(��X����Y���YŴ'��)� eP)���Yt���X�u�*%Zi@ZoLZs\Z�0+q<+��8Zud+�����+��TZ��/���[.�Zb�Zk�Zl�Zr [t�Z�t.��hZ��`�Dc�D���i�n�Dn��o�00\r.�Zl0�2��.�@3���Za5��4��D��[�@[�t�9�6[k��f�6��[sh��\�/,[t����4[eH8��lh[rh0s��th�,Ct�H%�[a(�h�[i�0k�[l�[vD��pd�H�[n8���t	pTN�[oQ��[ip��Q7�[cxVlW\e(\i��kLmT\��W���[���hd�Y=p���Y/ \t@\v��8\a�Y��X��L\�t\��
�^a�\h�\i�\j�Xp�\r]v]z�\� ]żanT4�(����\��b���\�0b�\nPd}�]��D���\��i��\ok�p/x��p4�\o�q��q���/i<f��]��Rt��,]f�"h�]l@t��4]a�]b�]c�]d^eh^gt^h�^i�^k�^l�Km|_n�_o�_p�_r�_sP`t\`up`x�`y�`z�_�P u�]o8vZ�]a�]�(v{�A��v���]�xXdw���]kPy��P��{���d^p�{�.8^a@^eH^iP^uX^y(IlKć�<V�|r�|`^o$}@Yl��m(�nt��l~��D<d�gl<m�^n�~P�ś�l����^n�@�H����^�X_�`_����_i$_k<_lL_u�^�����7�^l�9����_��%4_e_�D��H���D��37D_jdA���D�����h_�����wp_Ä������_�`�ԅ���Kb�_l�r�_s�5���9.�=�:t��d!m0���t�t����l`t�����_s<�\r.0`e8`k�l@`oH`u���0��(��L��h��Cn��v@ n��f$���h`x,R|���|`c��`o�������`l�
�]h.�`c�`e�`n`������\r.h������`c���`u��>h$al8an@ar�u����e|u�y��������!�t���1a�1e�ai2l�Yo$2r��t@2u�av4�y�a�T����1��D��l�H����ae��8�
�aa$bbDbctbe�bf̪g�bh�bi�bm�bn�bo�qb���tk�������a��T/m$���baľbrpI��H0bm����8baXbc`bh�IR���xlԅ���lbo|�L��bl���boD��bnd"DX�
�����bo����bn����9g@8��d�i8����bn��pcrcz`���Ȋ�C��T�a,cu4cy�D�$��� ���<cadldcr�s@dvlcz<�N�\r.��)�ca�co�c�@�Rx��cd��������c�8�R���cmh�%�clDPm�r,dv�do�c�x���@Z�d�4����>�P���T7u|����/dl�� da�Ldhlu\d�d��d����Td���tdoP*3tXRd�de�dh�di�dl�dn�doeu����dz�%����g��%�d�������0����de0R���ep%tRa0eoHesh4v\4�d"�Ad@ee�0��#%�kP*����.�ea�eb8fc�fd�fe�ff�fggh�gihkhlDhmhho�hr�hs(it@iupiv�iw�x�izdi��h�*/�ec�eg��n8�r@s��t��x���tt�*N�	r�+�eaf�E.�A��f�x-�4gapge�gs�w�,�� fk,BlXfolfy�B�|RPfv..dfk\f�fb�.��xfolBżfR�0B�fr�1�H������f��1P�n�_t�fy�f�E�8\p�@2�fag�����f��2	h.8ga@gdHgeXgl|gn�gr�gu�gy4��2w��
�2��Pgapg�L�� 3��hg��{l��gu�gy�l
�n
�̻`3/�gn|<r�gt�gz�3��D%t�3�u�- ]R�gmX��4�gy�5/$hl�6/�yn�6�hox���0hdH78he\hÈ#d	��Th��9��H,c4hd�Bg�ho�9�<)�hex�lm�hv|_4=�hn�=R�=�h9���h�h>�SpL>���hu�=�hkil�hui�L~��>��ia����>��i�@0�y�����}e�A��4ihPis�B%,�t�*�+��\i��BZ,k���4C��|isD�C���id<Wn�ir\D���HG���if�ih�G�(I���ij�H�iajl$jr�4sj�K�K��j�PM��ȃo�Qt0WLja�js�jy`jÐX��V��DjlhZ��Z��Xj�
��$��$��pj��j%xj�n�)������k��A��m��p�@v%�jkkt`kv�k��$�X��|���jl�ju$�t}�}�Sp��t�# ���kt����ka<kv�Y��L���(k�D�Lk�0kĀ��������Xka�k�|k��l���tk�L��H����k����w���k���P�kX�l<�p�kr0W�t��
la(lh�'l4lp<lrllu�lv��zl�(7��lb�;����7�47�0/�(�)̂o��Tla`lo��#`���Llmp����ktب7xlpx�Rp�8\a������.�la�lc�le(mf0mi<mlDmr�)slmtXmŘ|����lc(eĨ�09oh����lk�R����lcmhmp my$}R��R|���#8����s����/`��Ut���Lm�p�R��dmt�����xm������ml�mr�ms�mv�mz�m�\���� �,����ma��mt�����������mk,�G`VhĿ���mc�'n|*p ns0n�t�Z��0\���(n�t\�����<nd|q7���Pn�D��Xn����dnn��pna�nc�nd�nn�no��R������%�na��e�no�nu�������?�nkx����nu�������.8oaDoeLoh�k\ol��m�on�oo�u�oy�ozP�R|���0ol|�/x�d��`�Toa�oe�ou|o������to����p����~T����op��/���4�on����[.�oc�ozd���.x����oh4�h����rL���pa0pbhpe$Dk<Wn�-tx�tHpaPpe�joXprh��h;���8v���`pb��E����tp��p������pi|p���=<��x�<
���pb4qn@qsLqv���pa\)c�qd�qesh0si�Vj�tk�)l�m�)nuo up�Er<usDut`uu�uv�szhqÈq�,u�\�D��,qy�$�Dl�*�00L�/��Tqbt.��\q�,t�tu�lW�q��W��|q��Y�X���q�t\4dw��\r.@t���qc0rd<rhDriLrjXrldrmxrn�<o�rp�rr�ru�rv�rz���r�0r�<r��r��z/rèz��r�Py��$r�$}l~���<d���,sD�
n؄����pri�R���rt0�4��Z4�T���4Wc0Ld�rk�rl��p��r`����ro������T7ud������r�t�s�и����s���%8���(salsc�sh�sl�sm�sn�sotptttv$tz�O�����dsk4�l��D����|se�siD��sn�so��
H7�L����sm0���sn4fR�~��sk���8�4�st��; ��`np���qsX�G ���\r.Ttk�tn�tp�ts�t�x�9(�Ltodt�4���`���S���pt�����xt�������<�\�����t�h����tÐ����t�h��tl�to�tuuy���2ex�NT�X�NP*/Ckuu�AN�H4�y)�����H��@v�t���6aTuŔ����U���]lust�ZĿ����|uaqu��lW��kLm�W���u���t�k�sl�Zn�up,vt�u��u�(�m)���u�H\�PM�H�ul�8vi���v����v�t��� va�Yn�va�wbLxc�xd`yeD{f�{g�{hT|i�|j�|k\~lHm�n��o$�ṕq�r|�s��t�u�v��w�y<�z�wðx�d������
l�vs<
���va�\cwg wi4wn@wph�st�ttwu��z`w����wy����X��wr8DD��,wa!��r$'�&Lwt���Tw���P)��lwrD.��|0ut.���w�4{�n���Ȉ���H8@�exjxlxn$xoh[r,xs4xt<xuDxvx��w��=��:���w� ��:���w��?��?t|A��At\Ef�F��FkpG��H��hxh�xm�xrzT��tL)|x�LNf�L��tx�HRAHStlW��xa��kLm�x��W���x�LW�`yZ�xe�Y��xs�X���x��a4�.t\���xh$yk0yl@yoTys,.�T��pd>yu�dJ�hp\f78ys�m��lFLyp@t��d@a�yb�yd�ye(zg<h4zjPzkhzl�zm�zo�zp�zt{v${z�yÔ��v��K��8v�yePy����8v�jo{���ybznzv������yo|��B��|zv�|zo��l{aDzm�`ol���r��xza���\zh�<l��
D��d�Kj�zk�yԅ)��P!j�`n�zsЈ�<�> heh��zk�zn{o{v{zd"���%�zo��u���|�Ы4��r�4�/�],{l�1t��4�+.t{i|{r�{uh{�<�vH���`{���H������%�{m�{s���P�yt���	�+.�{a�Lb�{l|mt�n|r,|u8|z4��{k�������{aD�_l�|oTe��\Do8��.��� |p�D�|���@|m8���H|e||g�|k�|s�|t8��|o��p|nd"�yn���|h�
�����|ep�dmt�������|e�|�0�������|�h����+.��dl}gt}l�}m�}n�}o�dr�Pt�}u~v$~zD}�\}�4~����x�r����8}��d��������T}�|}�,�t�����������}a�
x�X��T��}b�}d�}o�}s~u~w�}ň��,�� ��`������}�H��,���I��~i��h�H�4���,~����h.����@~n�r���L~a�~e�~n�~o8s�~����0@kx�r�~t������|~��~��6����.�~d�~e�@mLq�x����~y�z��/�~b��b4f�t,v�J�i�B���M� ä\h
At@dxe�n�r�Zy�èZ��#��pm�sL��Vd	����PT���x�fŸ���������a�c$�e8�kD�ph4v!��i��p���D�yD�J�!%0�e�#>8)N��n�(L�a)��X�������������D��|�sP*��	 BbЀd�Rg܀k�o��s�u��vĀ�0.H.������.��lB��4(�lTxr�9tD{J�=�f���A���hHG)�H4P�a\!l��n��o4�yh��l�X(I��H�i`�s�$��J���S�L��|��M��NR��s�#�$O����s��t	�HP����yn�P��o|V��Cv���V��؁d0W��	�aT5e�o(�p4�ut�v�#yh��T��Te��kk��t�i#,#Ōl�h�m��m��@�s�j��H��n�Z��`��|n�@v��+.̂c�d�e�i(�kL�m4Tn\�o|�r�&vH�y �z���(TŌw��p4�Lx�؂hhx��x�l~`y���i�j�p4z��z{�|�T|���s�|�D�l8��D}��0:�t}HA4C����T�w�h���h�s���p�o���kr��t��uЏL�t����+. �ad�bt�d��fĄḡh$�i0�j8�k`�ll�m��n��o̅p�r�s�t�uD�v��yH�Ø�Ę��Ԕ|ee����s������.\�ux���,�t����<���<�47�/���At�?d�%l�a��� �����l��n ���������Pn�X�����At�A(���a�i�m�u���"d�/�r����T�H�|#����o��g���Ĝ)L�À�����D��\����X�u,�_�.�{u��y�Z���A�A���������������T�����s����i�Ax0�0��ԅh0��$���܅c<�n������l�����L�e��o��uԆv���LO��I��(��84b�^����D�k\�s�_�(�bDc��d�k�n��p���|�c��s�����$�����a��nĆs̆u4���)�t�����L�����܆c��A��e��,�_بt�Rn$��`�������t{����0�jp�8�e`�y��À�����X�m���ܪ)l�h\���t��x������������H\4�.Їf,)k؇m�t��_tT�sTX�_�����b�c\�hl�kx�l��p��r��sh���Xbc,�hT�sX���Ld@����L���T�p����S���4����d�l����0ol��td������l��r8�G8�\r.������it��Ŀ���mc|*p�r�t ��]�����8�k����a|�e��l��o4�rquqv��yH��l�Ĩ�ŤD.�����d��@��L������k����L����7��������t��;���8��x��$����7�|���x,����Ge܉i�r��;P���ԉg������,c�j�k(�n4�s��_�����sp���a���L�tX�ax�k��z����P�j�����d����)x�ll��L�
��3������v�a�XbL�c��d��e�f�h<�i�Vjt�k��l��m�n(�op�p��sЏtL�u��v��z���p����x�l8�o<
���b@�cP�jp�r|�s��v�A�L�{��H�t�#�#\�a"��d�v�$tLW9����a0���������
jLm�n����Ű*%��a�e,�i4�o<�r�ø���B�D���À���������+��������D���<+P+���HX�iTN�lW��.��e�W��`������O��{Y/��pt\dw��@Kh@t����c<ht{j�<o�t�ru�v�z4�|���،�h����R4���j���4t���$2r��L�(�r8���0�fl
h(�kT�r������v���������h�����d��o��r��sȍu83v\yd�ü�x����.��h��r�#��T��}b�%j�xm������G�����D�t����a\�el�o�Ns�3u<�yD��|�ń4��4�� �������Tv(�����4���3�dA���������jȳr�d�����t������@Ro4u���HNT�����tXRdԎe܎oȴuT������8)O�(�a�4�)�����ď�,���s�*P*�� �aP�dd�uD���.��<���.��D�ÄB+�A��\�s�H4��i��l��o��r��ylL�PM��e$O��Q�<VN@v��-k����tt���.$�a<�bD�ip�k|�m��nĐr�s��u�v0��(7İ������������Ĭ���7������bT�l0�#�P:�\�oĜ>d�n,�T�������������!�A��������,�����t�?iܐu��$���Ԑa��� ��ب��lH\�/�pp�t$�e��4���.)c,)kD�s0�������k`�pXmż���lĿ0Gc|*p��r��vt.��l�� ���pe��Z��t�ȃe�i4�o4�rquqvԑ��������L������������L�t<
����.,Ia�b��cL�dd�e��f��gēhГiܓj��k�l(�m0�n�ot�p��rԔs�t��uD�v\�wd�yl�z�ļ�����a��bԖcd�d��e��ft�g(�h�i��jĜk�l,�m��n��o�p�r��s,�tبup�v�w�yЫz��� �Ĕ��lx�a���t��.(�k0�t0����D�|��Y���8�����uv@���{���\�gt�n0{ ��|�a��l�`����l��n�������lX
�s/l�X�u����olh.�g �lD��$R�#D��h.L�e��k\��t��,�|q����T���J����!��uh�äUL[#"����b��i��t�a�h#�\r.���<����4%�$̔i�r�t�%&X�r���&��a�ee�'�L'�h8�n�eň ���%0�i�*T�s�M�d+��+Z�+/�,��b�/���[.ԕc,�d�f�k�lH�n\�r��t��zL��t.��x�����d��D�����,������0�`1h1ܕl�2�`qt�f@3����k�n(���3fd����� ��dw9D4�c84<�eT5�5��T�et�i|�k��s�a��cf�jfp7��6����o��(8����iH8%��r,C̖ne�H%��e�htL��.��a��RlL�llWL�i|Ok\�t<���W���� ������XN�X��4��T���Y��Yx[ht\%t�a|�oP\\f�@t̗aؗd�ze�ht{j�k�<l8�nD�od�p��rИs�t�x�z��tt`_mPy��`Id#g��$}_P�l��m(�n���t��]�ty��������%$�e���,�nԅ���KbT�m؆R���\�l����g��x0���p�a��e��R�+Rh^L"a(���g��tP`R���r��ĘeDt0�h�Tr��H��$�����uH�~����z�����[.H�dP�jX�nx�r��sԙt���`�]����0�i��7����d��h���l��ؚ;d"���oș�X���n���������|q����������\�7М��ܙe`����%�o�_t�b����i�����lt���
�+.d�a��c��e��j��l|mTMnȚqКr�Msؚt���4�/x�j@>p4Mvl�T����L����|����/�/��kl�Z����{a�}o|V/l�$P�Z8�4�aH�b��c��e��gl
h�NiЛmܛn�oD�pX�����h.��G�eX�i���$�gtk�l`�m8�rp�s@�t���R�{h�h�k�����|�y��������kmp��t@�v �R���>nl�p���țp�����d�t&)X�%�i��R8����bܶf �k(�t8���R�/�J��0�� �8�ø�0���P�� ����cc�ckdn�c�x�.��m��Ŵ���|�a��o��������������%h�>P:n�o�u�v�5yx���n�w9R�����g���T���%���
��.L�a��d��e�i��nAo<�y����Ĭ���8�j�Mmd�s4Mv`�#��ftjc0@k(n�Tv���l���Q���dA��������mĝrԝ���d�\������̝�t��� a�������4fA�N�	fd	������h��x��t	X�cXRd`�i��lp�o�Zy�èZ�д�H
������N%��f 6)���A������ ��H\�P*��a�b �c4�d��e��f@�hT�i�k@�o��p��r��uȟwПzX*���i��n8�r��t�+zc�,��\kttd�y�.���v�24L�m,3�`3��t zh� �]`�h�J��h���:X�t�ut�ø^4=��k�<����edB�A����r4Ct�C4�e�qnDD�H% �c�i0�k$jrH�slL0W���.`�a\�bl�c��e@�i4�jD�m̖np�o$�u��v��yإz��Ä���Ť���V��	X�.��d��k��l��mĠnРv�x�Cz����tmpX�X;�X�X��o�f�X����c Z��ðMĤ���������+#�Z��$�����8���u���ĥ��[khZ���.@�k�jlP�t<Q��ZA[H [��H�sL[Z�[p[%d�l�[\r.\��x��H]�� a��d��jСl�p,�t��]�ȡm���^ćf�_ءi<��`�i�Ô_���� �9������h�bP`$�t�`s�ah.��a��cȢdТf�h��i�k�n�s4Pt,�u��ü�� ���a�a�b�����|R�Db����o���������h���b�h�t���آ��b�üb����b�����C�������xc��|d<�a\�Ø	�d	��T����9Te��h�.Уb��c|kd�g�h$�i8�j�kk@�l\�mh�n\Dox�p��r��sФvؤz0�����r�eȣi�o�Af f��	u��8f���kPku�P��D���f�g��g90�z�g�3�h9p�T
��H��DhP��@8��;�:p�o��r��u�;�Uf�hP�$�h��c�?V�h�������pi]�ic�D��i���<H����ki�j����|�����l�l���aX�e`�ih�mt�uL�Ä/�����D���l0m���l.l��m�|nZ�n���o��p��s��w�o������@�o�o�Хh���p��Tlb�p��p��. �aL�cT�eĦi|�m�lo��t��u��v<�ôp�Tqt\q��(�bhq��0����\)|�q��\r.8Ejx�n��t���pr�xr��p�i��k���X!7�������~h���r��r�����0s���h��m�t�v���t�ńs
�s�n�s�tAt�\r�,t���.L�m��nl�z@��\��H�9T���8��d���(��t��T��t��� �t��3Du�`u�u��u/@v%
Hmb�$c�e%f�h|Pi�-k�%m��p��t�u�&v�$�`y��h.�{�� d$�o���r��������i��P�kt�
�+.d�b��h��l��m��n��s�nt��vȨwШx|�è7�00bx���h�b����p��(�A� &a��oA�,�A����Ap�3�A�����t��.�c,�d(ofH�hT�j\�lx�pDmr��s��tX����th����h<�H���$�a<�n4f%��ì��iвZ��/��lL��d�m��l�it�Zк����lĿ�
�[.Щcةd�f�h�l��n�r�����,���������P����\���l��$�� ���aTe�oL�����lsc<�t����2�����D�k�Tv����L�����ܪ�����d�iЪo4�rqu��y\�Ä�Ŝ�%�����n��r������|�k���Ptx���Īu��),�%�.�iP����Ls��4�c@�dd�np�o��txzP�Ā�����|$i����(�hP�_�rN8���H���+p��\�sT�]��N8���x�������,h�/��bīl@�t�� �����������L����+.�a�k�o��(�����,k������������H��x�� ��� ����a�Xblsc�d�)hجi�Vj�k�sl�)n�o�p0�s�st\�ut�z����X�P�ŴN<
����d��f|�s��14�/����ht.�����h����8���Ьnh��,{P*���c�H3 Sl$�� /|J���t�J����@v�D�e�z`y��<�t)���)������[cĿ��|*pL�4<
��,Iauk�n�p8�r0uL�y��|�aX�bh�cH�d�e��f��g��hвj�k��l��m4�n,�o��p��rt�s��tl�u��vлx0�yԼz�������,�D���e(�hH �T�"��0�ep,��+��D�sH8�	d��e��g��i��jĮk̮lh0s4�t<�w(��h;���Kn>%T>X�u� �?
�?%��o�?�?��a�o����@8@D@����PA9XA����`FDF�t�A�����F�G%��L��D��tL��(�vL�ôH��X�h��k\xl��pȯrЯtt��������P���������Ot��ì%s�R�HS<T
�Fi�U��د��Z|lW���m(�n�W����а���������Z��]P\74�it\��<�ap�d��j��o��r�0��^?�^h�a��
Pd�|�o�g�g�����\f7���i@a�0o�r/�
lܰn�r����as@t��	�c�0d$�e8�lD�mT�rh�s|�t��u��|�e{���z0�����0�sD�%H��0���L�hȌJ��`�p܏h�t�r�����`1l���e`lt���
�1a�lH�md�n�Yop�r��v4�yܱÔ��T����1�����|q��������d���.,�a�e�}o4�sL&u<�����`���X_�D���B�d"P�v���X�ol�|�aP����иB��e����������X�/��h��aX2i0kĴ��2c�m�o�������b�2k�������d�h���4�ld�m|�o��r\y���l������.�2eX�oX�u4Ì�#P���P�v��74���L5����x���j,��D����a��e�!Ŕ�I4�B�e����.a�3e�h�k4�l/o<sd�t�3u��L����h3��@���dA�<�J�%�r(�f��/ �nH�(�eH�rP�u4��h����p�R�%\�l��r@f$@��t�����|������ah
>t��d�dh̴l�m�n�r�����Da��o����ܴ�x����������7��N����aD�n��(�e9gh�hp�i�|o��p��rHes��t��uĵyH �� ,%j�v�Q�#4��r�#�'��%%��z'��pd'R�'Dеc���(|e�m�t�u��)��ص�d��`��L�������Nl)�����+7P*��$�bP�cX�kh�m�_st�t��Ť,7�47�7H7`�i@7�Dyh9��|�� �
(���oKl��nK������H�g��l�o�p8�rl�s��t���8L��M����PM����g;$O���h4Q%,�l\Q�LQ%$�i�Q)P�a\�b@�ed�i�Q����tL[RLR
�S%x�t��t�ma�T�VԷaܷs0W����aT5e$�g8�i�jl5o�#uP�v�#y����D�ŰW�YT+i�Z��`"����"��#��[i\�����`~@aa�rtb�a0�e�j��D#�|n%\�y���p�c�5k@vHmb�dȸe%f�h�k0�l\�mx�o��p��t�&vD�y �z�d��y����l`y����dظo�zG�{��r|�D}����%��|��X�o$�r�5y�ü�a�d�i\~����eP���~/�-m�-t�~��@��Ht�yl�Ø��T������؆m$����e��ulK����k�U��������������i�p�r �s(�v8�y����R$��g̅���@���c����i ���D�����0�k�83Nt���L�v��T�k|�m��t�
��t�iЏ���6r@'�t�ĺhкl�o�rp�t<�z,��d��(�����a������غa����a�e�u�ü�������j�����|��������$���(�Ы����4�D�a,)k�m�t������d�rlus��t��Ř����y������t.�������7�Ļ���l$���������a��b�l�t �y��h�8�r8�%�s%��%�����7�`�x|�����
(�ax�b��e��f��g��i��k@,l��r��s��t��ul��P����I����|����l�������|�������L�������mL���ȼa��d$�m8�nD�r|Hv(;DD����i��r������������G@�a0�el�ot�uT����.�	�d	��L��d����
0�
��{����|�m�-s���8��tD������<
/����a��c�Gd,�hD�j�k\�l��r�sL�t��v��z�Ĥ�ŴH�0k�XZlW���c̝t�W������t����l�Yo0v4�y�����)aT�m��%���	�.a��d�3e�hL�l��o<s�3u�3Ĉ�y���bPj)�����ܾ�@��0W��0[al5o�#uԾv�#y|n7�p�5k��d��,t���mhq�����w��T�@v,�t@�v�ì���� et�_��8�kt����6a��r/uh��(7Ĭ���47��������t����qu|���7�L�����kD�r@8v,CH8��r����b,�c��dX1e��f>g�h8�i�j�kP�l��m��n�o�p �rt�s�t|�u��v��z��Ø��\�ŴHhD�a\�h|�n��s�H�rT�s#tLl�lt�n M�TM�hR�SRlW���s�W��������8[#t\����o�Xp�r�z,.� ]�k��m�i�o�q�t����}l���/�������)h�)t}l`qt0�uT��Sp�������<�u���D�ap�e|�h<�y��;���h�hd��]@Rot��r�Z�x�����u����a�=ZP*���s(I��H�a�r�t�u��lĈJ��|���Q��U�0W��D�c̖n�p�#ut�v�#y���p[��p���#a�5�)��L������A����@v�Ec��k��pT[s��t@�v���l���}��m�|����o$��������r�Y�����?id���P���������k����t����rt����6a@�i�mH�oP�r/ut�v0��������h�o�ux�#p���`�pp�t��t.��h����h/l�7�L���<Wn��r��t��u@8v����P�������
FbԘf�Fg�h�ld�np�p��r��t�szt����1a����.a0�e<�lP������@mH��H�n4f����i��#J��\�s�Hp40W��x�zt����6a��h(�]�a��b��c��d��e��g�h�i�j �k8�l��m��nx�o �p��r��s(�tL�u�y��z���L��$��<
��
,Ia<IcD�dT�fh�i|�l��ps��z���H����<�rX�ńv����PlX��\�n8��lt�c!_2{�1����l�/����h��s�	�t.���������������5;H8%��u�F{�F{��l�H%�eX�r �t$};�J�h<T�LW�lW��(�at�eL�i|�od���W��0������X�X��\��T��Y��Z�<]#P\7��m��ut\����a ;e(;i��m��r���P)�I��]������iJ�z�Py����v@t��
��d4�ht{j�<l`�p�s��t��v�zx�Ŕ}PgaD�u$}$�l|gn\�������L����T��<���k����l��h�Xkȱl��Ø���������4�\7nܘ������h��s��tؚ�Dl���������a��>4�t�����a$2r8���
��b�bcD�gl
h��k`�ll�p��s��t�����P�a��{4f�0�X�n ���r|�t��R��R����k��#p���r�N������4�N �����l�n��r���X90[��n<�����ax����������%�?oh�%�l0�r������a\�ep�h��j/o�3ux������.xQh�@md�t����3��������%��ut4���p�T
�����%��o�u�9�������#�����d"��u��������'��(H��)����d�����������/�)��@���M��,��T�r�,��\�h��kP*��h�c��d��e�gh��i�l@�oD�p\�r��s�:t��u�v�z���x-4ga�	o�	u�0�0����i`3��̤d\ n8#z������������5
 �a(�c6�8��/�J��0���:T�l8�Ü;{�<��pYch�f�?�h�h�?��p���?��|��h?����r�=��t@�?��th9������A�k���(�������n������\�<B����y�B\�C�H>8�dH�lh�r�4s<K���PM��@�iX�y
LR�Q��`�ix�ohR���i.�V��hn��y���0W����a��el5o�s(�t�#y��@�����X�����X��+H]��8�l���`��_������Z�����#��j%�xe<lJ�k� �r�m���#s�j��4���r��d���q��L���p��X�eD$i��o���,t����nhq��|��@v>,`e��r��t���n��w����������o��Ĭ�����r��
����a�e�i�u���<��H�fL����dt��@�exnlИ#��8�s��tl�o��pp's���h�R,���d�m|�v�B���4`��������H]�������e������rĿ	0Gc��d�h��i�k|*p��r�s��İ�8�4 ���Te�ot�t��]P�cp�dx�e��m��o��p��s��t��zd�Đ��\x����H�l$�)8���\��P�4|�)P��$�8������T�)��GpM<hx)��oLx����h����c��k,�lT�n\�sl�tD��P�����a�e�o�uD�.|�.L~
����$�a`1iH��|~�����@����{���P�
$���d�e|�r����<%i��GP�{�4��d��G�����tL�h��d��e��l��rD�{������L���i��������a��)<
��,Ia��c��f��g��h���a��c��d��e�f>g �hP�iZj�slp�n|�o��r��s�t$�uH�y���<��,�h�t�t���/)��dt.������p��0�Ht{/�zm@t����eh��i0
n�sl~���t�k4����ast����1a@�f2i�lH�m�Yo����8���H�e`�l0�t�%��h�tP*��l�5t�V/0W����a��el5o��u�#yH]��s�_;�l�@vHmbКc��l`6p��t��t�a�� �����rt��o<�z���������.4�p���)���U���\�c��
����T�h<
��c��k��p�s�z��h�a8�blscD�dL�e>ft�gt�h|�i�Vj��k�sltAm4fn��o��p�Er��s��tH�u<qw`�y�z����Ťt!��$Z4-��,�et.���I����A]H80�ot\%@td�dt{jl�ktxPyl�4t�>8���e�����th�%��lx����E�������P*��t@�hr�H>)�����@v>t� �a�m,�r8�u@�y�U��?��i����l�����ب3�����[cX�p��_��]l�l`�Z�)<
��t�a(�c��g0�kP�l\�md�nX�o��r��s��v��z��|�a��b��cP�d|�el�gx�h��i��j��k`�lP�mp�nT�o��p�q|�r��s��tL�u��v@�w��x�zP��8��8�Ř%��@�iH�u��J<Jl�]o��D��pd`�kx�ø���u�"�$t�a�*%\�y�,�H8�	d��l��r�?����,C���	ôH����h�k\xllxr�tP��tL��.���L��p��O<TlW���xiH�m\�t�W��$��<���Ztt\�hh�rk�i`�odw/@t��	t�c�Ki��k��l�n�s�t(�u<�vl�_��o�pf�B�����l�����؁��vH����u�������؄������i��7h��p���h.��]�r,�I4�4�a�Bt.��H�����p�H�]��d�ot�4��������g8�����n��tp�����2c �m��sp�
��%��th���	��d�k�lP�m؁oX�v\yd��D��������.$�i8���f��7�nD�=x���0������������������a��e��gp�h��o��s�u�����
l��m�$i����Is��t���yD�>T��xJa�����rH#�%��m`��������\t������D}�����|����Z$�k�]0�s�ttH�h�sk�nh�rx{���a��d��e�s ��y���c��r��
����oT%��rD��i��s�����tx$I�$����o�#%��k�l)�����(|e�u��)��$�����X��<�����P*��̅a|�b��p��s��t��u@�vd��+v��l,,N�:4X�t�=4��t�?
h?����r@4��a��A�
(I����d�H��a�c�d(�hL�lL�rp�ŰJR<Kt�LL) �lR��t|��|�$��@l��Q��4���R�R��X�e8<iO��`��0W��g �m��o��r��v@��a%�e��tTe����b,j%|nt�p��D$i��k�t4��l�tD{y@v
��f�h4�iP�k��l��m��n`6p��s$�t�&vD�yL��T|��4�7<�k�|��D�a$iD�lt�m�%r|�u6Ä}��}�%j��t\~����eD6o|u����~�~�����H{�8��������i��Ĉ 
�
|����i�o��n������\�
�����maP�ed�i��o��r���$�Ŵ<���H�l���$�\�e|�h��z���l
�X���fX������df47/��n��sH������8�������8�jX#p��u������P&e�����nĦ���h����i������īb��/0�l���d���D����0�kP�pt�������l��pF�t���	�6a��h��o��r/ut�vl�(7�Tu�H�(�����i��o�uL�ȟ����w��a�eh�o(�y�Ð�����s�����|�4�������� �cĥ�4��m�t��
��d��e��i��l��m��n��rp's��z��������|��H�t��8�������t4�����n��tԼt���7d��s��TL��������eL�4	�Cb@�cP�dh�hp�l��m��n��o4�ä������,���_D���H�e`�i(;����L�%�Dal�o��yt�iD�.��m��th�����e��i��o��u�#h�#@!#����t���'H�����d�l,�tX�_��@�bx�c��d�.g��h�Vj�k��l��m��n8�pD�r��s��t�7u4�z���|��tL��xl�t��ŴH��d�hdM����lW���c��n��t�W���������Z�x[�t�bk�L�lt�n��r�#>����s�[fx[4�a�O���t�O����$O�� �ĸH,�o0Wkl �md��hZ���"ŌZ��X���p�5k)��p���������|��X�o�5y6�@v��k��tXŬ�	���%kX�l��t���t���
�6a�?c�h�k�l �r/u7�(7�(��(�Ĝy��7�����U�L�4\�eT�gd�kp�l�-m��n��%���)\�oL�Dh���x�e��i��à�������������������L�Z������k���(�D�������[�<
��a(�c��d��e��f��gh��j��k�m�o �p(�r4�s<�tt�u��y��z�����d������ax�b�cD�d��e��f��g��h�i��j��kL�l��mh�nH�oP�p��r��s�tP�u��vP�w$�yL�z���4�� �������_R����d�N��0�������!G"���a�$4L'P�Ŕ(
�(��H��)���\�����P),*�cT*��|���+�,��/�d�j,�pL�s`�z�����t.������h��D�����|q�$Z�lW����kH1������0)�24�4���	����X���4�h6{�?ip6��4�r�5@�t�6�(8)�8���b��nH8h�a��l��r��y������9��r�!+�9����k�?	,C)G��A������G���s$�HZ��t:���k��H%�ai�u$�y�U��V�lW��W��,�����t\��	�+.(;i��m��n�
p��s_v��ð�Ŝc���Q.��t��Ĉ]��p��P9��c��c�����8dA<f��������\l���u`u��l)�r�|0u@tTa�c,�d�ze<h<�jL�kXrll�m�<px�s��t��v${z���Py���m����l{a�<ml�x�b�
�����X�sD�`�i��4��r�{T�h���l��n��R �����a��o���p�����a��i��/t��bk�u�y8�
0�a�bc��e̪gl
h�Ni�Nk@�lL%nL�o���`�m8�r0�X�u8����b$!d �k��nt	p �/|�t��J`�t�ox���!t������a��i��u��v��;|���s���4��h�)�lP�m�o�dr$�uX�v\y��������d��x��r���T��xm<�rD�s�}�� �����.��b��e��i,�p��u����2b����p�k���x�� ��78�����(�����������n��v4����7�.��n�\3t4�o0�uD�vP�y,;���sX�h����h���$�c T�%<�pD����k����d��\�a��i��o�t�y�������4��/��m������������/h.8�����,!#� ��s��v�$�kd"�%R�'��(@�o)����`�����F�x��H\�P*P*̅a|�b��c��e��h��k�o��r��s$�t��+�fl��r��sh,��,�x-4ga�	u�,����k�0)�2��4(�l�<����zȊ�p�=�Zk�th?��� ��?x��kh9����@4D�r<A�A0�c�@��8�o�H4l�i�4s�tx�y(��lL��Ьn<Vy4�����o�V����dDjs��z�����0W����a,�e<�nD�oP�u�����X�����T-�����ihZ��p/m �ŌZ������#�@[��Z����H]�� a�}�e�Te��Dc�l��PAkl��p���#a��e��j��o0r�q��x�d��z���<���r������r�V�u-@v>��c%f��t��Àw]�w�����Lx�����$Fr���t�)�h��r(�u8�v(��TuŬ���47�0/�p�8\aH�r4�a���[c\�hh�sp�zt�4Լ��Ŀ��c��d��p���,�h�'h��t\���d������t��N��4��u�y��Ä������Dt�������L�y��{���k�]�s��Hk@��$���s8���4��L���	�.x�e��i��l��m��r�Dt��y������������������L�_��e��ä�������������_��y$�y<
/X�hh�s�������a��blsc,�d\�e��g��h��iD�jL�kX�l��m��n�o�r,�sP�tX�u��v����X�D���T%�$`�e��l��o���~��|��\~������%@-m./�������/���b��ht.��������<�����00Z�1OH8� ���=��=���a�=���t�:����t\<�uP�Ōn�`c�q�<f��H��@t/@�b��ht{j��m��o��r��z$}t��l̂o��r�}�}D�Zԅ�k0�����t���m��~�s���t�B8�/�h�o0�rD���l��8����u������(�j ����]h�t}l����3et�i��o��u��ì��������|����\��	�t��b��È]d	�������i��oL%�� ��n�#�d"��uP*/�n@8)0W/@SaT5e�#y@v_�Es��xF�)��8��t�_��/h�d���H���{`�p�t���x��Ŀ�0Gc��r ����aTe�o����i214301040030010023030003340210221310001212013212100232130330310113111231234000021023320240403124010302400033000401001443001031103040031002003421034000404403110421024220310310111021103210240011004102100412030000314100130001301032200300022214310003120410033002000020010101020101012301411230300201430010301403203001320011002300000130020301000030000130100302122000302103010002300300100303122001430304100300124001002430410102001041023103201022122101103210214100030030030121034322300322024001022420220102211200410011222202221102102103000001230000000201234104021031010100320002120410000103301020431021000301130101210420201003400300444001044124210220030100003030000300000022101212430102100111014134030030021044010430002321143030202140012012010200010001003212100000100101002004012103343040401003201003002400013300003000103000010110001014100324103020020023324000000202212000202021020311230224000100230004310043403442340313401000103213243002013103040321021310040024221213000210430200030203442431032010122013023300143034402032222031303023020000142011040040120400000331003100113024401321100300430000043210211120013003340000212003440310000402102213320000210104224021210010020134004300120300012012120140003041110231003002003030003310100321241010044001024101104401300104110042100310404034010042001434103003234020402140302042000002000114104200420000420101000112203000210221130014130112143111430020124002010133210010030100030202004000103000100300102300000101324021003010013030104004010012121023012042124000011010242010030010003000311000010001030001000	30001000013112040100133200134010220222301341022100100021410001311013113010400120400100010401010301030034103100202102021002020113200103101230232210000112030000100	30000100010401301002030032133301031001033002103300233010031010300301210040123100010120103300100440000033000002003020113001101022113004013010312300030211223301400103100000441041023000033020100010020PK
!<�q%p�p�hyphenation/hyph_cy.hyfHyf0tP�����'-4�������$�'–’-111001|���������. #aGb0Pc_d�ye��f�gD�h��i��lpmHn\*o,?p�BrXcs�ot�|uЃw�y�F��%8$��`t #��
hcd�e�f$gDmpn�rls�w��ta�b�c�d0
e�f�g�h�i�l�mHnholp�r�sLth u� w"y�_�%�e&��aDdde�f�l�r\b
�&0sp&��8aTo�&D'0'\g|nH�`'ty�h�h���r<h�e�'$�(�)&�)���ah+�n�+���a�fl�+*(,.�,1�,5�uw �7�.;�->w`4A@450e3E8lTs�
;�57�5\dX5>da�g�or8sLtXwD7G�h�J$����a�r�7���n�75�e�$M`9>��$�Q���d�P�y�9�h�bȣPe�j��d�j�� d:,o�%7H:��De�:Rde�:7GA�<Ulo�;��tb�c�d�ef0g<iPw�G=Y�a�<���h|I]�I���l@IY�e8=c�d�K$�=�dnMA>g�=kfXPA4Po(e >s0@\d�?��De(@HAx@Edg|t$B�EE���e6v��s�Gz�a�e�F���nrG���a,ePldr�6A�G�d�<~0<���t�GYa��4�ky�H�� lDt̓���><aK5\e�KtLspe�L�1�QYxl�O���l0P���a�e�hXl`r|y�R�xR�n\S5�e�l�o0rLw`T�UM�V$l<�$P���d�4�a���l�Vs���WY8nHW��@y�X5LZ5��7�^Uhe�\��pw�_Y�r�^�d_���a�d�e<	i�	r�	w�	y�a5�e�i,wpyԀ�Xf���n�b���f4c�$a8dDe�l�nw0��4k�nh��dk0d�k��a�,*�Ls<���Ta�e�o�wLm`dTm.xa�c*�l0'�@�����c�\b�8}�s�����aeow�����d�n�d�nc�e|}��}��d�����d$lH0���Y8a�e�o�w�w��@lPe�Xae>df�l$1�����2��x��x���d�xY�a�1d�1>�a�/Y�l�yY�l�e���a	f	r0	tԀ.Xf��	n���g��	w�g��gz(	h�j$a8dDeh	gt	l�	nw�l�hl��`	ymk�l�m��s�r���	e�u$l�w�Xa�	olw���	f�l
r4x�\+$���	d�S���	a�G���	f$�A�	a<���
hPy��
c{5X
r�y��$
bl
ct
d�
g�
l�
nrtwx{d
whMA�{5}5�
l�
r$$��s�
n$�.@�A��
r4�>�
o�
s�$@�����
s<����
ayd�7*���
aH���np�5@fLnPY�
a����(n�k4y<���7���Ta$���\nl���hy�P ����d`����d�l�n<rTs�����ahfD
i�
o�
r y�����>�h��Y�c�5И>�a�����.�W>s��i���$aP�Y0nHA`e��Hg�f7؜s�a�e�o�y��	�sȝ���a�eP����r�EP�x��r�;J��Y�t���d
n�����5�dX��d���
d(
s�Y
a�t��D���0
n��8
g\
s�J�J�T
g�"�7��h
i�7'p
dl�E|
e�����
n�
r�9,��0�
d�����
d�
o�
w�
y0;<h<4�M����
iy�b5<�;d0�Ynl����dHnTw�@d���4s�Y<a4�����c\e�oĩ��dl�n���ta$exh�l$o�r@wTy2Jl!;��>�l�o`�D44���s�9J�a�e`9���l$P@U���s���a(���n@rTu�FYD���8bX�]S>Ld��Y\n0���`yȮ5lw̰s�a�e�y�-J,�5�y�����ft��i��$�>�d��nw��PD�$`�Y�d�v�a�b���y����d@f�l@�L���8a|f�)Al)Py�Xwx��dd��>pa$�ga�E�l��s�y0Iv���m���e�wXo�o$�A�h����n�>vD�Y�n�bA|}rЇ��e����d����(d��54e|�P���Ld�f8mLn�t�w��7�w��tl�r���|a�e$f��PԙY�c�Dl\O���ain`GY�n�FU�yp����b̺A�r$��l�r��x���l���e�@��0od�`���Da\eX�Jhf0|H�7T�>pu�>xrȩ���hػ7��Y�e8$����	�cdf<gHmhn�r�swD����ae�o�w`yܼ�����l�1 ���a,l8���..\�k4r���8l6U��Tsн>\a�g�t�w0���h@����aL����nX�5�eH:J�:D�Y�d�e�n�wH>��a�>�?HAb���g(��p�c����bl
c8dLo`rxu�5Dl�,����4y@���Tf	wX����>pr�1G�Y���rP����f�l�5�n����y|5�<�5�n�y��$��J�'�u��Y�ln rHt�J���a�!�|H*�C(u�>0a�'<h��$�rD���Td�f�g�l�m<nXr�$X�����f�o�r�A��dP��,����y����$t�8�E�e�uyP����n0s`7���a����U(g��5Hot��X�����Pw<�$�a�e�wD�Ydh����xc�����a�rT %���s@%7x%7@�PP�P����r�����d�����a0e�i�l`o�u�y,8�e����rL���$dPn\ols�,���Ha`�&�����Edt����xn�s�a�eo<y���Y�eP����w�7,����a�����n@�Dr>�s�E�ae����n,��T���>rȩ��$hp���0tDr��EHae0���Pn�P�Yld����td��.(���r�����g�n0t|������a�>�et����n rpE�a8i\o�w�y<:1H:��a�=��ft	��0
n	��,gHs�	cT
g4PP��Tlln�E|
e�J>xa�w,'�h����tp���y�J`��4sl���a�>�n&A�|U�d���a�N��u8��t؄kl�>llsd��$i�n�rH��4e�g(i�o�wdh���di����xa���	w�5�e�hlwy,��n�J�����u<����t�wP5�yP�7\�Y�ad��@$��n�aHedfpg�r�wd�������Pi�YXe�c�a�e�w�y�7�����P �4 E�e� �� 5�a�l� ��#�P#���e�wl><'��$ly�(��(��n�$Pf dXf��(a�b��4f-��@e�,��Ld�l\*��Xd�f�g�lr.d000���a�o�u���25�l�2�45�l�y�4.��(���ae�5���gnL�P|5P�8EHd�&�dI(d�9A0a�9c<dTA@A>Taxe,?E\lpAD�C���r�B���d�g�n�B���ae$h8ipu�y8.�D��l�5��E>�a�d�E�M��J���w��4Qa�Pi,R��Q50aPn���SYHt��D��\l�]��dgD�db��|a�ea���n��x@��c���sXcE�a�e�ty��4e5�s�ks�wPe�n�h�n>�cP�Jp0�H0��d�pY$a�o��0lln�o��<axh o r�G�pzde�ss�a�i�o�r�u\�x�s���g��M��J�t�e,u��a��$ u���d�u�C$hr>�d�x���a�w�� ry5( a4 o�x�d�yH e�/�(Y��@ n�}5t}��T h�|��\ c�E�0<��t s�Y| a� e����� rЃ��� a� e�!n�!o�!y�Mv$�� sh���(d(!iH!l\!np!sP�$��� d@�;� a�!i�'!h�>!t�$p�4!dH�k<!a������T!aH���Eh!t�mA��|!o�E�!i�+.�*k�!r؍�!b����Y�!r�!y�����!b�!r�!w�N�����$����"d4"l���"dP"f|"m�"n�"s8~ԙA����<"rd�ED"ad0��\"w����d"dԟEp"a�"e�"y\�(A��&�"l�"s��(gD�M�����"d��M��E�"b�"g�"i#n#t<������4� #t#r��#a|#b8$c&d�)e�+f�-g@/h�/i�/l3mX5n�:o;p�;rx@s,Ct�DuEw�;GY�#a�#e�#i�#l�#o$r$u�F�#dpGA�H�H���#d�#l4���J��JY�#e JU�#nKELU$r�8AtL.�M�($.0$m�5p0PY\$a�$e�$h�%i�%l�%o�%t�Op$d|$n�$o|P��ePQ.4�:���$lH��xR�$i�$n4��RY�$e�$n��"�$."�$i\SE%. %a@%eL%oT%rd%ux%w�%y�8$ @S��%c0%w(�7|�'`T��8%f�V�V W-W>\%s<W3HW��p%a@y�W$p7x�c�%w<X�%m�XPtY�[��[���%a�%e�[h_9�^�%c4&dD&fX&r_���%ap&d0'e�'i�'l�'nD(o�(r)ul)w�)y�_h+P,`��<&a\a? aYP&fh&g4P��a��	%.�&a�&d�&e�&f�&i�&o�&u'y��a�&d�&l�`Dlb�b��b���&u�&wc7c;4c&�c>hdA'nxdI4e9e>'c('s�y��eOD'gL'l`'ndf��f�%.�\gX'a|'e�'i�'o�'w4�@�X�T��j&(o'�'e�'y�o;`o���'n���o���'s�pY((b�p�'a<(oP�14(d$OP�'e0O��(d�NY(d�Y(y,f� qYHqO\(cd(dl(ft(n�q�q�qLr��5^�r��|(.�(e�(i�(o�(u�sd�s���(dt�XADt���(d�(n�t��]ALui)dTu�(a0uE�(iu���(n()r�q�P`u )o�6Xo4).��<)dH�kH)etv��T)l�u`)e�)i�)r�)y�v�Pw���).ؓx w���)s�)t�Plw�)n�)ry(�Py���)n�y�4*dH*n�y��
�)a\*dh*ip*l�*n�*o�*r�*s +t4+w�%�Hz�,*a�5P�z�@*.L}P}��T*dH�4���*e8}�����|*aXo�*d���*d<�P؍��Y�*c�*g2�p�E�*w��$�*e�*y��@���Pdt�sJ��+a,+r��>+hp��l�UH+y�P$���@+dl)� ���T+w`���\+d�+i�+l�+n�+r,t,u����h+a ,e(,f�,i�,l�,n-o4-r�-u�-w�-y(���Gz�+d��AИ��+e�+n�JL9�P��,n����*�D���b؜�H,aX,ep,ix,l�,w�,y�@�P���P,dh,i|����b�.l����,r@�A������5�,a�,e�,u�~������,i@����,d�����ȣ�У���,d�����,d-l(-nH��@8�l�E -t�'P-a`-et-i�-u�-w����M���5X-s�S�4�5l-f�AP`��>�-y��̧Ol��k	�-a.e8.lh.nx.o�.r�.u�.w(/yĩ�-d�-lt�������Ha.e(���.n4�7��D����$.w̰>,.aP.oX.u`.wX�����$������p.d�.f�.rL��:�ȵY�.i��>�.i�.y���T���s�.a�.e/n/r��������.lh�����]P@��/a���/m8/w���D�\/a�/eн.p/a���P/n�����h/dh�$���|/r�����/d��>�/g�/l�/n�/tL�'H���/e��P�����/n����/c4����'��Y
H0a�0c1d$1e�1f�1i�1l2n2o�2p�2u�2w�2y$�|#Y40r����<0bp0dx0e�0f�0i�0n�0r�0s�d�9p�P(����z�0d����<P0�Y�0cBP`��0oxR�x�>�0e�0h1w����E�0w���D\�0l����o1iL�P1d\1id1np1s�a�|
<1u����D1a���,�X'a��E%.$�Y��5|1o�1yT��;�1s�vP��>�1a�1e�1i�1o�1w2y���;������1c<�;p�(��0����1c02dL2gX2n|2r�����282a��@2i�EHal2et2o,�(8;@�$@��E�2e����\����2c�2e�2r�2s�2y��(�$���X�E�2t<��2.����p�@3bL3d�3e�3g�3h@4l�4n�4o�4p�4wL5yKP���83l�;`3a���X3n�J$kl3ox��t3l�3r|p�*�-�3r����3m`5�3yX$@���3n�5�3e4o 4r�2�k�3l����3b�a����4d�>4y0��	��,4s�	544a`4el4ft4g�	�	��X4d��6d�:��|4f�
>�4i�AP���4f�4r�c�4w<Jd?*8��4ad>p���4a5e5r,5y��Ph����4d�C�55i4�P���$5d<5t,;lP�kD5nH>�5.�5a86cL6d�6e07fD7g9iL9n`9o�9r:sH:t�:w�:y�'	�5b�5d�5e�5f6i6s6t 6u(6w�H���Y�5l M`6i�,T�R@�pWxgE\tY��>06oho<D6al6d�6e�6o�a�5d6a|6yeR�YUd�6d�6g�6l7r$7w�Y�6.�6a�6d�6o�;�|C������6.���Hf�k7ip�p����).ؖ��D7iu<7y\�zp7a|7d�7e�7f�7hl9o�����h7d:,d�:(, ����7f�����7fP5�7a�7e8rH8y�$�k�7f0����7lЯ�ܯ
�7d����7a����8d��s8e<8i�U����(8t4�s08s<�$`8d�8f�8m�8n�W$t8n��$��l8a���8a�8f�8iL�����8rX��8�$D�Y�8n����8w���8a�W$^�����8eȵ7����8r��9f,9g@�Y$9ix�H"��89u"@9a�+P#��X9c�9d�9g�9l�9m�9n�9r�5;�#���9.�9d-;@��).�9aЪ�82���9e$J�6.$�@$E -tL$�$�\c�%&�9c :e(:w�$:iiA�iA4:n���l%A\:c�%��<:al:h�:r�o�(t�,&sd:eHz�&�x:w<'�:e�:l�:y���'���:s����($�:dn4�;)�:l�)�</�\*���:ed?,?E�:a0;cP;e\;l|;o�;r�;w�\A0P��(;yT�P�@k<;w$@��D;l@ATa�=�t<�h;t�A��p;s�_�$^���;n�As�;w�;y�AP�A���;n0����Y�;lPB5�;l�BY0<a�<b�<c8=d�=e�=f >i@>mH>n�>o8?pX?sl?t�?u�?wp@y�B�B��(<b\<dh<ep<f�<n�<t�<wDP�C��T<rDD��D���6.�<f�D��E�($.�a�<e�E8F�\F����F���<r�FU�<eR�lG>�<s�G���<a=h$=lU7(H���<n�GY=e�XP�X��=oIo�Hc0=aP=ed=rt=yTJY\=s�g��JA�JG�J�l=n�J�=b�=i@KY`Lk�=d�=t�LP�L;TN�=n$Nk�=a�=e>f>oИ��N7�N���=d�=l�N�O*|O;�O��>d�Q4>a�R�,R�,>n<Ws�W�.�>a�>d�>h�>o�>w�>y�)���$.l>y�Yx>w�W���>d<:X�\XA<'AhX��X�>d�>g?r?s�X��%.�>i�'��2��YE�>l�8�0Z�?.%�@��Zz$?s�Z5,?aH?w<[;\�|[P?yL\\��d?e|?h�\�D�]���?g�?t�?w����t�U�?cv�^k�?i$^���?a�?c@d0@e<@iD@n\@yL��HNz�?h�^c@e�gP�^>@r�^;�^��(@d\_��_��6.T@i�_P`�h@nDwaXcE�5.�@a�@bAdAe@AfHAg�Ai�Al�AnBoBr$Bt�Bu�BwCyx@
�c���@s�@wLF�EY�@r|dAo</PL���@e(e'\*4e5Ad4An"��e��,An�e'Df�hAatAl�Ao�Aw��f��`Al\g.�,��g|Aa�g���Ad��;h5�Ar�Ayй�h#dhY�Aa�Aei9�i)�j'�Ay)A�j��%.�j���AdBek.�k'�k�LBapBexBl�Br�Bw�By�p\�k��DBl`BnhBt(l�qxLl�lAmA�BuhuD�]���Bs�ms�m���).�Bl�BnT|;t|P�m��Br�m��Pn��Br�m���BiCr���n� Cnl�CTo��Cn�ok\Ca�Ce�Ch(DohDr�Dw�DyHD�Pp�o��TCdtCl�14�pYlCf{PDq���Cb�Cm�q��s�s>�Ca�Cl�Co�CrDwDyttP�tTu�,u>�Ce�CwDy�u�9�u���Ccvdv�x>�w�� Di8Dl�x� ?;pw��@D�8y��x��TDny��\Da�Do�Dy�X7�y��|Dc�Dd�De�;(YJ�a7�z���Dc�{`{5�Dyd�t|���Da�De�{���DnX�7�|>El�'ЃYDEaxEc�Ed�Ee�EfFgFi,Fl@FnLFrhFy��UXErl�P�YPEtHWPL�EdEw�>lEh d4�����Er��Y�Ed�ErP�>@�E�Ey��h����Ed�Ei�El�Es��H���C��Hĩ�E�Ea\�U��7L���Fd��Y Fe��E8Fa��EXFd0�L��G����`Fr���|��tF.��|Fu���F��F��F�`�� ?���#�|#Y�FiHGy #���FbTGcpGd�Ge�Gf�Gi�Gn�Gr<HsdHt|Hu�Hw���Fa�Hd�HeJg JiKlLotLr�Ms�Mu(Nw�Ny0O7�NY@Gd8$`Gh�$Pp&P&��hGdT+w�)Q�Gt +V�+��/zX5z�a�Ge�+n�Go�6`9=\�<���Gh�;Y�GcfHl Hn0Hw�E. W���GsDVHyH>c\@*�?��(Hyx@LHgTHtHA
$B\C,Ck\HatHh�CR�Dz�^M�EY�HaEY�Hd�He��7�����Ha�E���Hn_�{��y���Hc�HdIi0ImPIn�Ir�Is}'x���EIaH�>Ir(Iw$�gX��5����8IepIs<���@Ia�Ie�Ig�Is�It�Iw@�`�P4���xId������Il�$k��$tF.��Iy�('Ta,����In�;p����ItdhD�E�Ii�-ĩ��Jl���Ja��U
PJa|Jb�Jd�Je�Jg�Jn�Jo�Jr�Js�Jt��pJih�n(���\Jd�/>dJdP�t(��L�L�t4�R��Y�Jc�Je|�>��@���������Jg��p������Jf,Ki8KntKu�Kw�����Ja�Ke�Kw�Ky�����z$Kr��>�a\Ke�y�6YHKi�OPKdlKs���������Y|Kd��L����Kc�Kn�Kw�"�2."�Ki,����KnX��\����2r�������Kc�Kn����).<+1�*kLl\*��Lb<LcLLdTlXLm`Ln�+�-��,��DLd�5�6�lLdl7��B>�La�Le�LiHMohMw|My�E>�a�B���Ln�K�J���Ld�Lg�Lm�Ln�<L��Ly�L�HM�M���Ln�QsMlMrH�*�3���4k�Me�'&$Ma�X��0Mi�X��<MdXMg�Y$�^A$^��`Md�a�a��tMf�Mndb��dXc�Me�e��Ha.e4e5�Mn�|���5.�Ma�MdNlNo�|��cM�}E�Ml�}���Mdx�A���No@����&Ѓ�� NaHNcXNldNnxNr�Ny�z0����YPNl�pNs�$���E4���N.�����Nd�Nf�Nl�Nn`Fr�5����0���>�Nt̒'�Nl���YOc0Od<Ol\On�Or�OsTw�EtF.���Oh`���POi����$Od8�YPOl����>HOa�����DatOe��;X���lOd�Os���Ц�إ���Of�OwP���4�>�O.��E�Ot�KA�K���On�#E�Oe|#Y�Ol #���Ob|Pd�Pe�Gf�PiQl8QmPQn�QrRs,Rt@Ru���OahRbpRdxRe,Sf\Sh<Xi�Xl,YntYoLZr�[s�[t0\uD\w�\y\R�&��Pa�Pl�%;�'b�)��)���Pa�Pt��A�����Pd\����Pe@�;�Pw+��Pi +>�Ph�/�$1�/YQeQo20��,An�3�� Qn3�,QeHQn�4�X5>�axQe�Qg�l�+n�o�Qt�Qy�6g$����a�7���Qn�75�QeD7z�QhH:;��A�:�Qd�=n�;Y�QeRpd@��.�Z��Qd8?5�Qex@ Rg�fHA�Re,C�8RrhD��D�|F;�F��HRu�F��PR�G_5�y�Rd�Rg�#l�Rn�RrSu}�Ri�~l�~U�Rg��.<�YHa�Rt��;`G7�FU�Ryp����RbSfSy��l����>Sl؜�����$Sf�����8Se�Sf�SnTrTsD���@Sa0Td`Te�Ui�Ul4VmtVn�Vo�VrWuHWw�Wy ����6.�Sb6iD����н>�Sa�Sg@����aL����SnX�5�Se0�z�Sh�=�LnD�Y�Se�� Rg�tJ���Tyx�$Tr�z;p�Y<Ta�Te�Ti�To�Tw����	DTb�Tc�Td�TiUlUn$Ur<Us�Ut0{� J�@{��{�|��{���Th����6.�Ti@��H�\����Tn8��@��Td�k�Ta�����RtUy@����6.Sf<���IE4UgLUt���hUi|Uo�Uw8w��lU`Uo����>tUl�{��m5�Ua�Ueh��̓;X�k�Ua4���).���Ugt�>�d,����UiVn��5�Ue�RP��A�Ut�x���ViTVnT�kVn<�(Vy$��"��@Vll�;HVo������`Vg��5hVe��c�VlP����Vb�Vd�Vn��,�����9d7��6���Vd�6E�Va -t�Vy)��$T�>�Va�Vo��*8�>.Wb Wsl�v�JH:	؄>(Wt0�k0Wn<���<WapWc|Wf�Wi�Wn�WyL���>hWh���`�*��E�6.��t��Y�Wf��PD����Wd�Wm�WnXs0Xt�w`�*(����Wn8�E�Wal�������Wn��.h�EXg Xo�������(Xu��TXb\XgdXn�XoP��L�E��,c,���lX.���tXdL�>�Xi�����Xe�Xi�Xl�XoYw Yy��>�d�s�z�Xo�P����Xn��$0����Xe\�>Ymh�vh�a����YwH5HYo\Yy�#Y@ nP#��<YeL*A)��TYw�*��Yl\*��	hYb<Lc�Yd�Yf�YlXLm�YnZr$Zs<+,�,��l�0�dd1>�Ya00���Yf4El�6�Vy�*;�Yn;��Ya�8��Zn�<.t<�ZbDD�B��0ZehZmpZt�B��8Za�Ze�Zi�Zo0[ydE8F3�_��|
xZd�K���Za�J���Zd�Zl�L*�S�Q5�Ze�ZnT$�SY�ZgPY��X���Zf�Zg[n�YP�Y�s�YE[a[e�7���PH[la��$[gP[lX[m|[nd�� bAXbv���e�[iXo`[odb��p[h�3�c�[wXcE�[a�[yE To�n>�[n�o>�o���[a�[e\i\o(\r�q%Dq���[gw��vU\d�w� \r�x�y�|�<\sT�zЃ`\a�\f�\m�\n�\y��t\s�*��El\tĉA��c�\ah���AP����\d�E�\e���\n�!r�/�N&p�Y�\y���
�\b]c,]dL]ft]l�]m�]n$^s�^t�^w��$]h�P��P<]l�r8~k���d��D]ah]e$fp�P(���`]r8���]l�J��>�]aԟP�]a�]e�*�����]nDx�Y�]o\����]r����]a^e^yd��]u�4X�^f0D�O��:��E^e@^gP^o\^td^w<�.<k@�'H^n4���b@��H���l^dT�>t^u�>�^rȩ���^h4�U�^a�^i�^y`��@� #h_c�_d�_e,`fD`gX`i�`l�`n ar�as�au�aw���^ahRb�ad�ee<hf�ig�jh�ji(ol@pm�pnHqo�rrusuu�uwlwy�n��$Pt_.�_w8$YX_h%?��x%��|_r&Y�_u�_w)l)�_y�)�0<P�z��_a`e`i`w�)��_r�)���_a`l�K��=�_d >E�?Pp*� ,T�+��$`e<`o-�-�w�/J�/>P`lt`o�`t������l`n�/�/Y%.�`d�`n�`r1P2����5bX5>�`a�`e�`f�`o�`uaway�6b07
`9'�&O�:O�:O0<Han�;YaaTad\aflan�w�ay�T�<�@aa8=��=��W�H>�dae�a'p@xadx@z�D�E��ad�E��6.�^�dbfbn8br\bsdbw_���aalbd�becf4ci�cl�co drhdu�dwey,`�D7U�`>bg(bi9a aY0baLbiTbw >p��a\�ab�a5�bae�bi�br�bu�by�a�4c2 dhdAe*�b.�6P�g���6.�e���br0	t�bucw&$<�&�bdhg�bacg��l�A<hs$cg��g��jU,c.\ccdcfxcg�cl�cm�cn�ctd�A�kApca�khl��%.`	ym*x�6�m�mY�ci�n�(o�ce�oP`o���cnHq,c.�cc�cddmdndr�q��q���9d<rkLr�pr��r5@da�	eHdiPdoXdw`dy�rtMDt�t�tAuYxdn�ds0uk��Phu�d.E��uk�dw�u�da�dc�de�dn�dr�dyPv�tvY�Jwqw���dnDw/ w���dnerdwxlw>4ecDedPefdempen�er�w�w�w�<ed�w��).�M�x\edyA�ea�ei�etX��y��ed$��Ԥ�Py�{��tF.fr�y���ebfc,fdXffdfg�fh�fi�flPgm\gntgo�gr�gs�gthux{.�{� fh@S|Efa}�8fdL}���6.Hff�}�����Pfa��g�fo�6��Epf.��'xfl0��fe��������fu؄�H�>�fl�fmx�0���5�ff4�k�ffglHgo�������fa ge(gi0go8gt@gw�������ȇ�܇��X��6.<�lgg�Rt���k���gyp���|gfP������gn��Ii�gt����>�gu��z�ga�gh�p�̓���gnd����go�ghl(hn4ht��$�A��� hyL���	pha�heif(iiDiodirpiu�iw�iyX� `���hhe�hn�ho,t�hwИ>�+n<�A�hl4-,�AК�����hr�����hd�hi�hn�hr������.e������hfiy�����؜>iy��U4id �AУA����<idTilH��0�A���\iy��Y�5̧��xir���l����id�in�YDa�ie����4).X����id��iaje(jl\jr�jy8Qcĩ���imjnxQ���>jeЭ*(���jḭs�Xo0jo$�A8jh���Djn��>Pjyԙ7�w��hjr��tpja���|jfD��ja�je4U�:���jl����jol������jl�����jg��U
4ka@kbdkd�ke�kfhlgml�mn�mo�mrLns�nt�nw<�4D�Y kh���(kcP�oXkr|M��4d �>Lky(�tke�ko����kn\gH�k�ks�r���*L���kt�ku����*�����kn�ktx�Y�ka�kelf ll@lrLlw ��\�ܞ;�>lr��Ep�5li0lo�P�������8la���O�0���TlbL���\la�le�ll�lo�lr�w�ly��>�ll�ln���;�a.e��L�P|�'�lf�;����ly`���lm�ls@�������H�kma,meTml`muhmwxmy��<mcDms��P���P��.Lma����>X�����pms��Y�6.�mi�mo4�>���mw`�;H��md�me|��������mr@���6.naneni no(nw8nyx���J>��l-f������Xb���>0nm�����Dnghnopntxnw�ny��D�;p�������������n.0�c($.�na�ne�ny������nn��P����Q�nf`���F���n�0������nd�r����od�JfHomPou����oa`oe�oo�ou�ow�oy����>��L���Xod|oi�on�or�ow��,���.Ha.e�ot�R�|�X�D0�Y�1c02d�on���oe,����td\�Y�������ocpd(pn0pr8pw($?�5p.��;��Ahtp`patpe�po�py�A���Xpr|Ax��lpr<AP���pd��H�pa�peqi qo8qw@qy�����pp�
s�pwED��d���pb�pdqs�8��U�6.�3>P#��qi0qsp$<'U)Y\*
�qc�qd�qe�qfrl<rmLrnprr�rs�rw�+;�+��xqh�,���5.�qi�qr�'��qa4kl.b</'@ n00Yry�m`��qo��P�qi��5�qd2���qd4E�6. rl0rw�4%��v85��(rg�5��6>�6EDra`re -t�7��8>�8��hra�rd�rwI:�9c�ra@<�<�ret<�l>>�B��	�5.�ra�sc�se�sftiDto�tw�ty�B��tF.sd<sfLsilsmxsn�su�C���6.$sa,sd4sw�Cp�C/8D��D�@EP,EzDsd\sn�/��3MdE�dsg�E>($.�a�se�+n�Qt�E����).�G�K�so�J���sd�sn�su�w�KM�MR$N&�sfO��k,R�tu�Q5ta(td0tr@Sd�3(Y\ts�X��8tedtgxtl�tn�/��YJpte�Y1T��).�YE�6.�ta -t�Y4_ $^���tg�tn�ty�_�P`>�Wf�aa���td�tn�twdb���6.@cDXcD�|��ug0un`urhusput�uw��E%.DuaTui��LuaX�T��Q����kxuht�U�ucЃ
�ua<vbPvcXvdtve�vi�vm�vowr wy��k�udvevfvi$vl,vn4vs�%D����uwt�;�+�`?��zvt̄D؄������Hvl�$����c�+����`vih���hvd�vn�vr��'�JЈ���vy(�d\�Y�vd�vr�vt���3Mt	P��k�vgh���vi؍&P������vewnЏ��&����wb<wcDwnXwodwr(�M�P�6E%.p�OLwn��'���
�6.�wc�wd�wfhxg�xl�xmynPyr�ys���wh�E�6.����9.�wd�ww������wy wJ��������wnd���waxfxn4xoPxr�����G�A@xdHxi�$xdȣ�'1,�.\xa�>�d��E�6.|xl�xyd����8�Y�xa�xlX.u�7 ����xd�xnp�7��.ԟ�xa�xo�<�����xg<7����xdd�� ys����ya(ye<yo��X����Os�� t�4yd���إ��Hyahyepyi�yy��>���a�p�xyd��E,c.�ya�yg�yox�^<��ya0�J�^ #YHzdhzf�Gi�zn�zr�zs|Hu�zw���ya{b�{c}d�e�f��g0�hH�i4�lX�m<�n��oL�pp�r�s��t�ul�w4�y&�Xzf`zl<h��'�+E�6.|za�zgh+A��X5��zgD7�;��zf�zn�=��zo>7XH>��zix@EpG�F���zd${rG���za0{e8{l@{ox{r�{w�{y�GY�).�HK5LL{n`L� -t�E��L>X{e�L��`{ntL>l{a�{e�{i�{o�L�L@HM(N�{y�NPO�<OY�{l�NY�{l�OO0P���{a|e|h�|o�|r�|t�|w�|yxRb\SE0|o<|rX|w�V7�V��(|d�V�H|y�����HW��P|eh|y�WY$5dx|n���Ta�|eD�7tY'�|n�|r�YD�|o(87�9Z���|dLZE�['�|o\AD\O�\�^
0}l_��
�|aL}dD~et~f�~ilrPw�y�~È`A�a�&dl}lt}r�a��8}a|}e�}f�}g�}o�}y�`8b�bcH�}a�}i�}o�}w�}yph(iYDi�i�i�i��ce>'c�}g~l<~nhx��xP�xY�}a ~e,~o4~w�����~dd�P��Ppe7�eD'gX~i`~n�fk\g�aph<hl~a�~e�~i�~w�h(i��i�jU�qa�~f�~g�~n�~r�~w�k�hl�m��m��n�nN�n���~�(ozo�oA�r��|(.,e4oDy�sDt�tA�t��<d�uOde�l�ytvYpn�vH�7��Yxw w�lw�m�x��yUl)l>y ����w`����d�n(�r@Ru�����a0�eL�f��i��lԀn�o�r@�uL�w��yИ� �.($PP����<�r�5P�؜>D�e`�r��yܞRp�e��o��T�#���>x�r�>��e�� �����d��gD���.̀o�A����op$�L����s���dP.У���o�>(�e0�i\iy��;4�6T�v����8�ş�����T�d��P\�a��5h�dl���t�d��n�Y�Da�De��	�a�eD�fX�il�l��o��r�u�y�5���>́.@aaĩ��ԁn�r����$Э>��r(����i,�l8�n�X�k$�l����2.d�u����).\�L�g��̰>d�a��eP.ot���'��n��s��E%.D�]�).̶��>��a؂e�i�o�w��y��;4�����c�dT�>x�����rP��D���(�o@�yD����>
��a��b̃c�dX�e`�g؄lH�n؅o��r�t$�w,�y��c��f��P��r4-P�L; �>��eP�Y��r��Pd�Yăh`���u؃l(����a�d$�e0�lD�rh���yeJ�>��'�r(�5<�ao7��hP�oPdJL�nL�'��l��r �7���p�d�>x�a��e`.w"r,�����n@�>��n��x���w����dH�k̄a�e8�l�}�)���.l)�y��7�����c(�g0�sl���������>��Y@�at�d��f��g��iȅlЅy�Yp�l�i��r�t�r����w����������a�m7����ol!������c�n��� -t��@�E�y��'�h�J���e0�����k
p�a��c��e�fH�gT�hp�i��l�m�o@�rT�w��y����i��n�����z��n���>��e��Ax�>��oL�XodІn�r,����'o��|���܆a�e�P��������r��5�a(�e��P��� �d����>4�id�<�r�����d��\�f�d�f��gL������aȇt܇w2yx�7P�����c@�v\�U��r0�>��i<�7<�5ԇy��.��y���k�n0��d$�g����,d���0�a82�Qc��8�i�g\���L�id�y<�PJl JUl�a���t�i��r������b�oc��dЈn�r�s�X�� �.Ȉd����P�a��e�g�n|�;���X����d���l������,�g4�t$R4�R��\sn���<�i|�tpEH�a��e��o��pȉrԉt�y *xbP'��s68��rP.x
P�
����n�5��y�
��a�>�s���%E�Y��ax�u���d�5e��f��g��i��n��r��wH��
�aԊb�c�d4�e܋f�g@�iX�o��t�u�wH�y�_�`�-k��ox.P������h&gEd<O��Y��l�Ȋy��t��yvA<u�i$�r���U�g�r$�{d��	,�c`�dx�l��m��n��r��s��u��w����6.p�d�P��X������8E��t���\��y�Jh4\YȋdЋyP;����h�i�l\gdc �o���X����gX�4` �,�e�4�sP�t� P#l�l�4�$Ed�i4&���r�%��x�i��r��w��y�&�{v����r�&�Ča����}�t}Y̌h�&ԌcD� 'Y�d<'���aPvc�n$�r,�y���(�(���:d�Wf)X�h)��<�c��g$]P�6.��a��iA�Tl�e(���t�s��U��a�)v\*Y؍c�d�:e�lXLm�n$�r�+���6.�+��̍h�,�d��44���e�0i�u4E�atK��6�8P�$�8$Y,�hd?>4�c,?E@�a\�l@A�B��i�B��d�a��c�d�e�f��g��i�l�m<�n��oؐs�t��wl�y,Ez\sn�G>Ԏa܎h06olG�GA�H��e��yTJ�J��J$N�4�aT�e`�fh�lp�u��yИ�TN�,�nD�u����N;�N��L�dO�\O6�OA���P��x�d̰�4Po��l��y�Y�Q;ȏcMlЏn؏r�t4SA�S��3�).V���d.DVs��y W���'s�6<W�d$�w,�ypA�p*�W�W�4�aP�ix�yXd�a���X�\�sTY�hXp�w�X��bXMg��l��n�X�T���i�4C)�YE��y�[c�i|[̐e�e�\'^&$^���a�e$�l�tn4�r@�y�^�p_A`�_�,�eP`�<wcX�d�`��`��P�d�aa��d�c��d��g��lđn�a��aY��d��� b��e��l4b��Rdbԑa�eD�cXcEܑa�b �e<�g��n��tP�u��y�d|ds�a4e;�f��f��(�rDf>0�aX�e`�nh�wx�y�fApg*h@@h$h��p�n�j��e�j*�k�Ȓm�k>��aؒe�f�l��o�u0�y l�rLl��Вr|l��l�xU�l>�l�m��5���>�.T|���l�m��$�l@�nt|���
a�De�m�h�r�B���d�mP\�a�n($.�n>t�d��nTo��o>	̓a��e�h��i��o��r̔sؔw�y�o�o��ēcTCd�e�);
�q��.Dq���g�rr$�6.�s�8�e@�iP�ld�op�rDw(t��;�tAtt5H�e0���t\�s,uG���@�Ex�o�vU��r�wTy���).y����elnP�m����y�z5��w`{�r�{$@�7�|Y�y�{���w�|>
<�aP�bX�d��f��g��l��n,�sL�td�w�|&H�u�D�l}u�}�l�at�e|�i�}(~��'�ODp�������l��y�����'s���ԕa�o�y����̕n$����s`��P�i�����d)���dti���Y�lT�E �i<�t�@�*���D�h\�u,{At�&ЃY	��a��d��e��gؖi�l�vo�p$�y��U��&h�����lȖwD���M��\�YЖr�s�������?�d?>��r(���a4����N.�����d�Wf�YD�cT�e��<�A4�L�n #���6.�b�c �dX�e�Gfd�g�Gi��lȘmИn<�oP�r��s�t,�w��`�aD�bL�d��e؜f��g��h��i��l��n��o�rL�t��u̧wl�y���|#��Fi8$Y`Gh�wD\p&5&���d<�eH�r�f*0'O4�i�(Gp*c�)��P�l�-�t�d|�lȬ�8..�0$�0>��h�/Y��c��o��uX2-2����n�23�X5>($.�d�e$�f�Go,�tL6;�6P�6Y��d�6�d�s8v07pH:5�:N�:��4�e0<�;Y	H�a��c��e��f��l��nșpЙt�w=��<��|�h�=��=���a�=ADVMH>��a�>8?�l?;0@��?��ؙe�y\@!x@�i�iA�AY�w,C�$�o(DEY�Hd<�rLF'G_\�dd�w�a5�uU�)r{��%.fr�y��p�b�Hc��d�i\�l0Im��n�r��s��tȜuМy�p}����nКr'��-H�>ؚa�e�o<�rP�t(IwX�3؅9��P�P�u,�M�d��$�a��E0�i���'H�h4�k%.p�l��y��>t��ЈPx�o������n<���Ha��g��tЛw؛y�P��$țr��C�PH�d�Gp����a0�cD�dP�eX�il�ot�w��y$UA(H���r܎A�e��>$�h@I���<�d�M��Sd�nЏ���M��'��y<w9@����cl�T��..��a��>��h�s���4�D��>�aP�e��i�l`�nx�oܞr0�tL�ul�w��y �`����d4�n,t@Ru,��И>,�t�����6.�Ri����@�d|�i��l��nȝr�s�t�Y�>t�o%P\�k��.`�74�����d������e��t��^����6.؝t�xP�����u�����O�d(�� �Y�l��s,�a@�o��A8�cx�A�AP�gX�s��ct<���У��e�>i����h�d��n��r̞s��Jl�E�O.��a��e -t�6��7��Y?.Ĥ6��k���Ԟa��i�o�w�y4�c<MX�>�d�k0��(�n<�hL�>��m��E8�f����@�r���<���X�f̧��`�y����.l���x�l���w��A��5��rD��ja���a�c �dD�gX�ld�n�Js���d���eP�P�a����dd���e��(�Y�6.���%.��,�nL���8�e�H�kP�y���%.x�dHtp�p`�Y�F�������5��a@�e��i�o�r �w,�y����p0dܠf�i�m�n0�s8�wp�$���0$����d����a��>�a$�e�67�O�d`����L����Kcd�ip�mx�n��o�����>\�s�C,���Ha.e`�tP����d��gġnСrءwL�A|���Y��e�C�A�C0����m��n��$^���-y���w(��\����r���X�cd�dl�ft�nd�OED�w��'L�h�y�v�����a��eX�H>Dua��dĢe��i8�lL�op�wx�y<;�{dd����cܢd�m�n�dX����D��f�g �t�� ~� ���h����@*.l!,�u�+dP#��D�c`�mh�n$�@$�<')>��c��n)d*\*��
<LcУd4�e@�gH�lXLml�n��rĤs�t-P�,��ȣd��eXzf�r�u\g,An�-���nl.��w�.uh/�p/�� �d</��(�d24P`�e,���,An�4��T�n�6ElLd��o��y88�(8����g�8��%.��c��d��f�9�90�:&t<Ԥiܤo�=��=>��ry�B>D�fL�iT�n��s��u�B��
��a��d��e�f �h4�iX�o�u�w0�y�D',E�E>�al�e�6��E>d�d@R*�@��x�u�E��a��>�HH��d@Ic�J5إc�d�g��m�n�o�rlK'�K��.<L%�Ly�LzMTM�\M�$N&�Po@S'�6.�Q5(�dH�rP�t�3V�X>
��a��c<Md��eԦiܦm�n�o�t��wtX���dl)�����w�X'(Y>@ nȦsdh��/P��i�3��Y��YzT8����l>;�c�nHN*�]�$^>(�cHNaY<�ndb;�o��o��D�ad�er;Dq��\�r�}���Md��w�|��p�d��l��n��rd~�>���kX�E��f��Ѓ��ħaHNc�d��e�l�n�r<�y���h����Y.�D�����6.0�n�7Џ�(�e����%.�:d�NlX�nd�s�P.ؓ���D�c��d�f�l�n@�r��s��5��dبl�r�y��D���d�P��a�MlШy��J8~G� �d�t��y��8��%.��Y.$�g,�o��t�8�d��7إYHya\�dhyel�fx�yt���Цkd�yp�4���y��E��t��w0��m���o�m����r��� #���Obh�ct�dЪe�f�Gi�l8Qm��nЫo�rT�sx�t��u��w��ĩa��b��cȬd(�ed�f��gȮh\�ḭl$�n��o��r,�sT�u��w�y%��$ET�.8$Y\�h&E��a��uĪw�%;��rX&\)R��r�Q�()��i�)�l)��r�)�ܪa�)��+���6.��a�rh+4-��/�@�a��e��lH���0Y�aL�eT�i\�ol�wH0��$�r�=�L�E�>�\@��?��d�yP1�$1x�d�1�X5>�a��dxQe�Qg�QtīwL6M�(��:��r�:<L��=ثg�Ln�;Y�e�f0g�tH�w�=�l?�4�h���\ �a|?�(�i0@A�?��@�ex@l�e RgA�A5d�d,Ck8Rr�D�E�EY��eGU��e�IA�H����r0P_oܬd�od�w�a5�ixc�4cU�ghr�pr����a �eHq�r0:��y��
�HcX�d��gЭi�l0Im�nD�r��sSu}�T*dx�e��i��o�u��yD~O�~U.��d��r�ctdk��~kHqO�4ec��.ȭy�'H�>�5.�d�;4�k%.�lHgo��J�����a��<����d�$n4�r�Rt<�u�$s�p����6.\�dSfSy������a��e��yИ>�a��e`���x�n�������hn��l�����n����EaD�5��a0�eX�ll�nx�o��r0�w<�y�$����e�Sn�rTsD�Y�Qe��J�����i����$�n@�r@���Sf,�A�Ui��5L�e��A��5d�eP����Vb��f��g��n�1J�Y����r��v�6E�VyT�s�Va��e�_T�_Yȯu�|
Яd���ܯa$�i����d��9�m�a�~��i�U�n<���|WfD����Wd�WmP�sh�EXg��TXb��d��e��g��l��pİw��(�����a����.L����nL��H�����0���>��at�e�f,�iX�o��u��w�yx������c �d,�f4�nL�s`�th�u���T+wp�5��>��eD�t(��`�X�.($��������).L�>��.��c��d�i�o0�s�u��;���������h�~�������iB���Eȱo�h�бsdhYܱa����i��>�s`�D$����;$��$�a��d@�w�;��'�>it�y0���H�d|�e��f��gqi /��00A82�����a����($.���\�����c̲mԲn�yh�������<���ܲf�����ocpd�m�s�wl���vhvH>D�i��o��u��w\Yyl�è>T�o\�w�m� �8!�H!��d���#&P#��x�e��f�#��&><'<+$�*k��l\*����b<Lc�d0�eL�f��g�lh�m��nȵrD�sl�t-��@e�r�,���d�yX-C /�(�n0/7</Y�D�d0��8�u00��@�al�e��f��u�0;�0��d�r�0�x�u1>x�a���������n25Ĵaдeشl�yL2�82����n�2�2�`�Ph3���n4E�6.�e$�l,�wH�yh4��4���u�4�85A(���ae\�w�5��4�g�����5T�y�5E�6.x�e�5�77�6����d�6E��a��e -t��y�7*A)����n�8Y��c�d �fZn0�o<�t�HP�9Y�y�9>�h�9c<d�i:Y�:kT�0;(�l�;t<�X�b�<�<sP�a�o;>�d�a��e��i��o��w>�v��w;`{��B>̶a�e�f�i4�o��w�y�B��hZmܶn�E>�a�J���Zl�Qs�d�Ze�Zn�r(�t@S�3���V�� �.�X>P�dX�e�Zf�Zgl�n�X(YAd�d@Y�YE[a��e��o�7A�7'��d��s�7v(8�J�_E��a$^����nԷoܷt�y�_��AP`k��d�`
�`���d�aa���c$[gX[m$�ndbAXc@�yo$�n>8�ml}P�|��L�bt�d��e��t@~$�}��l�rT*\*����d�~��dd�������oЃ5	�aPvc��e�\mh�n��o��r��tйy��Y� r�s��vh���(!i�l\!nT�rH�k<!a �e��J��$�C(�d�\>0�a�'<�hЈ��H�t��E`�ax�eP�1؍U�!b�����6.��d0�0�h����.�Y��h�Y�!y����Ĺb�d�Wf�!r�w4����N.��r����p�D�\y���
�bH�c|�d��f�l@�m`�nx�r��s��w��T�h�P`�o�V�X��Ph�f����p�d��i<]l��r�u��y�'��R �d�t�wa̺eԺlܺr(�A��6,�A �8����a�c�e$�i0�o8�w����������od�;��;ԟ��]aP�u̡������X�gp�ot�OإY��o0����^e@^gP^o��t4���o�l�4�D�^aػe�^i�^y|�;����лd0O�NY�d|#Y�y #����b��d�e �f\�gh�i|�l��mнnD�r��s�t|Hu(�w���aX�bx�d��e�f��g��i��l<�m��nP�oL�pT�r�s0�t8�u<�wD�yH��&��Dd��eȼfܼl�n�r0'O\g|n<h��eo�''Լa�'�(R�#�*Y��l�)���oh+��+���a8�lH�oT�s�,�-)-��@�lXc�-kw�/zt�fx�\�/YQe��lQo�1�1>��o3�,QeHQnTs�5�5��d�uX5>
��a�c�d�e0�gl�i�lx�oԾr�s 6/86��6g�d �r7A�75D7z(�eX�h$����r�7��@�n�75L�e,99d�g`9>��d��l�9��|Aa��;44����i�s�9J��a�eȾwH5A85����y�9��h�b��4�iȣP�e�j���d�j����d:�o8Dl>y\<���w0<��(�d\�t�;Y4�a��b�c��f�<Gt�o@x��@ n(D��h�exN4G����r�<U��w>A�=k��ox@ĿfԿt@A;LB$B�̿ahDA��e,Ck�rTy�����s�����a.e�E���nEY�e8�l,F��FY�F��@��h��Gl�l�JK��d�a_��d��i�o�rd�w���a��d�a����a�j���r�mvLrEHa�eHq�n`r��rA�r���a,e�oP�wDt��|Dc �d0�e�X7@Y@8t(�d�`P�t><�d�t��D�yw�u\�r{Y�6.�r�y��p�b�d|�f��g��h��i�l0Im��n,�o@�r�IsX�t��u��wl{x{>�aL}P�f}���dx�e(�f@�i��oh�r�ut�y�}��}H�oDi7t~ �o8�w�~��~U��d��r�ct�t��t��T�y��\�w�T�Y����
n��y�(�A0�����oH����a��l�m��K؄kl)�����w�	����d��P��ax�c��l4�k	%.@�a|�d��e��i��o�
s��w��y��p�8�d\�fd�il�nt�sp����P��W`�^�����Gp����T������d��e��7���y<�����c��i�Is�wy8g@���d,��n�����y�('Ta�|e����Y$�l�F&p���8�bP�o��O��kh�ep�o��;����)��t<�&x�e�>��a��d��l+k+���a +>��hX�
��>ؖAl�Y��i�y���(
s�����a$�����n��,�ip�o��wX����$�l�����8�d���@�dXoL�el�EX�h����d�n̞s<�A̧����y���a��i��.ĩ����n��p;5�/������i\���a���aTXb(�d4�gH�n�r\�w���xc�n0�1(�P(��� �lL��@�y`�b��YHt��A0���T�a`��t�e�y��L�n��5��a,�e��i��o��u��w��y�A��w������d��m��n��s�wl)������>�a`�X�.��i�o�ADB;��A��P�6.�����hL��� �cL�dd�ft�i��n��t�����Ri\�r8�r@����w��>l�t,�A��*���dDr�E��a�oe0�����n@�������sh� \�����m�5X�.Ȉd������d�m$�n0�w̡l�P�u���6.h��apT�y���l�a�kH�gx�n�$�d�dl��tF.��a�Dei`H5��a��e��i�o\Yy���D����dH~d����g��w�\dA���e8!�H!�����#PP#���d,�l$z�e&��%.T*U8�d\*��D�a��b��d��e�Yf��g��h��lXLm�r0�sD�w<+A�*k��l�,���l��y /A</��@ n2E�6.��a��l82 ��7�2���u�3s4��6.�u�7(5'�d�8Y�6.��cZnt<�%.Zbܤol>Y,?5�B>��a�e�f4�i��oH�u`�w��y�Cd�C��|�d4sw�B����d��f��g��i��n��u�D���6.�D�@E��.,Ez��d�E>�QtEn����l�KU�Rg�K���i�J���d(�m�LE�6.�Qs\�ad�d�Ze�ZnMr��s��t��w,R@S���6.t�a����h�� ddhY|�a�U���iV��;V'��y�X>��a��c�d�Zg�m �n��tX���d�XP�6.�X����h�X���,d�Y�6.�YE[a[e8�g -tZ�^A�]��@�d�_E�6.$^��T�n��y�5n�`��p�.��dP`>x�d�Wf�	�`���.a���c$[g��lX[m��n�s b����e�[i�wXo��odb����hcXc$�exe^4e5�f�o>�|>l�b��d��g��n��r�s`LUL��X�nl}E`�o�}P�y�}��x�dl�r��w��y�x7�~����lxD����e��l������d��E��a,AnX�E�6.T�E��.�e �t(�w����R�m��k�sЃ��	0�a|�b�d��e`�i�\m��n��r��y�J���h�e�Yp�lh�>��d��i��l��n@�rT�u�������l��H�k.��P��y؛�cB�t���o������sX�P��a,�e<��h�T	P���$�d���Ј��8�w������L�g\�kt�w������l�gP��E��e�����6.��Y%.��a��b��c�l�m�o�!r��"����Y��r(�A0�J�>��t̒'��lԟvp� �l4#̔�p�Y(�l��w�\y���0�b��d�f��g�l8�m��n�rh�s��t\�w�N�N��x�l�<��ydkg`���d�P��i������d��f��i<]l��nDiA4���o�p\�p����w����a���4�d<�eL�rd���ah]eX�f��i �$X�A���ԙYD�f���e��r�~�4).�KUh�n���t�i��5��d�����e�/R����i�,��a����E��a��l�rP.d�>��o��w`.������>�a8����l����d"d`�nԟE(�at�e0o��pP�u��y���>X�t$�\���l�l��n��r0��%.�����dx�Y��a(��$�7��&��r��s�U����Da^e��yT�D���c��إ���a0�d<�fL�nX�wh��t�c(�dЦc\X�(�cD�oP�>�����E`�b^e��g��i#n��o�g7�g����d<�>��oiA����g8�<kP��i�'��n(NAhR��wȩ����b�h�u,{><�n�z��e���a����b<$�e��0�d���`�YH�s4�YP�at�e�^i��y����d|�P@����:d #�0�bD�cP�d��e��f\�g��l�m0�n��r��s�t�uD�w����aP�bd�c(�dL�ex�fL�g��h<�iH�lx�m��n��o��p@�r��s��t0�w�y�#|#Y(�a�$58$Y<�h&Pd�a��l��u�%���e��+�p�. +>x�h�_����t�''o )�()��o)����rp*��)����l��t +�-��+����o�/��ax0�H0���e3�(�h�3;X5>�aP�dd�el�g�Go��wL6�\�aD6��6�D7�|�e��h�7$���7����n�75��e�:�;���d�f��l��s8=�DVM��l�V<X?MHA�x@E��gr7�C����r,Ck�e�Dk�>�8F$�d@FE,�aEY8�nGYp�e��i��l�o �rX�y�H���#d�#l��n��s��tH�JPI����y�I���$��>��r J&Ml�KK����e��o��w0��K2E�).L����g�n`L��LtL>�a0�whMX���D8�d\O��@�a�NYL�n0PY��e��h��i��o��r�s�t�RxR��n�U$\S����lt�PL����d<X��e�YD��itY����nLZ���wdh��w�[E��i�iP�[;�^u`�l_���ah�d��e�f�i(�l@�mH�o��r��u��w,�y�`��a'��a��e��f��i��o��rX`��a��i��u�ax�b'cR4c&��r�m��c��c��d d;�e'��l�r�f��g$<hu�~w�j� �a4k�(o��o8�w�o&@puHq��*gh�l��n��r44�rE`�a��e��o��w�4�5�H5�0r����yLrEHa��e`r�pr;�r'@daPdo��w�t��t����yu'�uO�a$�r�u&�d�u��u���wwAlw'<�lD�m�x�x��y�t�d��f��g��i�nl�sL}�}��l�d��i��y�~U�2.��f�~	�xA���l������wL�	�����dй����d��5��yH���d�s���<���.4�a<�cD�dL�eT�n\�wd�y���	4�;"	�$H�;�E4Ug��Y��aH�b\�e�f�,ip�l��o��r��u��w��y�� �����d`�����d<�e�+l��n��o�r �t�hwИ>�a�+e<�AP�A�d�e8=��5�*,�h�C��J� JU4�nD�<�i�HM����T�c��d��i��m��n��r��s�t����.��i�~��	�>��o��w(I 	0I����t��&	���.��y���dh�����i���؜>x,l,�o8�uP�y��7x���$�rL���Y�
a�De����@�n0�,	����\�s��5d�al�E%.����|�n��������e��y0�D���HN�̧����c��n��y�2	�:�<�����dܲfl���c�l$�rD�M��hy�@�Y�eĩ���-d|�f��i��n��r���
0�a��e��h��i�lt�n|�o��r�w`�y��/\�Gz��n��V�7	(���d��sX����|�=	������lȮ5��a\�b4�>X�.(�n������n0�w̰>�a@�eL�oT�y�+B	������t�>8�tX���Ad�dl�np4��$����'��d��e��m�;�����d��e�-0�h�������e��o��w�y�;��fL$4����La������b���0�a<�cD�nT�r��H	�Y(�iPv>h��ЏM	����L�n�O��f��n��r��s��t8/w��txf`����).��ad�x�A�����o��yP^Po��n>��t�n����l��b��lȩ$D�o�a0�eн�����n �t���������(�w��Uİw��k
��a��c��d��e��f�i�l8�ol�s��t��u��w�y����u����$.x�s��U�&��r��o��iL���Xod��n��w,����).X������yT��;�p��v�.,�i�Nt�7�$�w0����1c02dX�fd�n2P00��P�y���.x�y�*\�g�>��i�$�Y��d������d��n��\���a��e��sL�U(�i��P������nX�v�������d �g(�nX�sl�w(�������6.<�a�De|��hR	��D�a�EL�i`��h�d�apc��b��i��l�g�����i	&�	P��e�	AHY��a �b4�cp�d|�e��f��g��iH�o��t��w��y�c��d��f��i��n��HY��e�>,�eL�iT�o��y�gtY>X`Rh\�i<d�ad��b��;�7,����n�����e��o��w��y��H�7@����l��nPP����a�d(�e�g4�w8�x�d �w� $@�e� P#`�d|�e��n��s�#����e�>it�yp/;�#Yt�d@$�p$Y	l%�%����a��e��r��y�%A�&&�&A<';��y<w^	�(����c)\*��6.,�d��e��f��g��l��m��n��r��s��t�,��,c.L�d`�lt�w�b�-��D�e .7.��X�e w7�.Ol�y</'00�2PH4�4E��c�5P�6���a�6�8����c��f��w�:�<t<�>�,?� �e4�o��X�@k�l$@���l�Av�A��,�g�BEx�a8�b��c��d�f�iL�lp�n��o��w��y��ôB>��g��n8.��D���l�E����F����f|Z������G��G����hyc	�J��a�e�J���n�Hc��y(y7$N��Q�,�a4�o<�s,RM0T�Ut�V:DVD�i\�l�V*�W��d�W�d�a��o��y�)��%.��y�Y��w\XhX�Xb$^��i��o\_k�_ga>��n�twdbXc�
0�a<�bX�e��g��l�m�oD�tp�w��y�c$�c��(�f|d;��i	4�kD�d4e5L�lh�r�eY�).� �f��t�mDf>|�aX�e��o��r��w��y�g�g��Pй����dh5��y$h�iA�ic��alP�k��nj5��y�j>(�d4�l<�n�j��@xd4�<k��ksT�id�o�lg�x��l>\�l�m>�n��y4���p�.ln����d�nA�n>��c��rxoA�Q�o����.��a��h�o��s��a�e@�i$�oDwDy�s(u���(t�gu7�t�rЃ���5.��a��c��d��e�g$�i,�ll�m��n�vo��r��t��y��D���p�w��Yx�dvf��i$vl,vn4vs��n	��z��r����hL�v��t	��i��Ї���6.������d`vih�����d�i�>�d�EȖw\����oD�eL�iT�o\�wL��M0�H�xh��d�e|�p8�����i��l����P�;������e��t��\�PD�����r�Y��e��Q��d�fDwn�r4�A��v�����,�d4�m<�nD�s��'ԟ��P���) #��L�e��T�ap�i��>8$Y`Gh #��x�c�dd�ep�f��i��m��n0�r`�s��t��u��w����aX�bx�c��dL�e��fd�g��h�i�l��m(�n0�o��p��r�s�t��u\�w��y&��,c.0�d8�m@�oP�rp&*@psD(��t��(��H�w�*��)��\�n�+���6.��a�+y	h+����r�/z��n��r�`t@�3�X5>($.��c�d�e�+n�Go(�t���d86>��e�67L6��e�r�rP�6O�sH:��;YH�aL�iX�n4>b >D�aH>�x@($.t�g|�tHA$B�	��aLB7,Ck.��h��s�C.�C>��r{Y�z5��i�D���P�E����e�EY��dEY��d�e �l0�n<�rP�y�E����n,F&���@FE(�oLF5H�o���hFGUl�a�G*�F��d�n0P>��h��o��y\SE��iT%r��w��7�U��o��w\�7�WY��dHW����ytY�]��6.�\����n_o\�d�i$�r@�y�j&��rDtP�r���oy����a�elw��0�n�y����.��a��b��c��d@�fl�g��i�#l�m,�n`�o|�r��s��t$�uX�w�y&��dHz�%.,*a{��fr|P�{����h}���6.��d$�f0�l8�r�}�	L}����f�o2��}�g�i7t~�y�	�EP�eX�y0����'��r���`�a��w��y���).����7�����r����c��uH�>��a��d��l��r��t(Iw����6.؄G��E�6.�'��.X��6.�aH�'��	D�i<��� �dT�i�Rt؛y���7@�L�g���	�*cp�n�%.p����6.��a��i���	d���u�S@��;��gX��<�>��e�E��g��n��T�qv������m��>��e�r��A&�<�&�d�>�a8�hL�o���6��$.@�U@�nl�YpWc�nt�y�$���l�n��5��a�e�f�i$�oD�r��wT�y@���Gz��r`�����i�nИ������$e�����n�A؜>�aiy��n�c�\P���y���8�d�,CУ��0�a����l���L�d$�r�|�a��o��*ĩ��t�nL�G������fD�o��a��e�i�o�w�����P�<���
$�aD�bP�dd�f��o��p�r�s�t�w<�PD�Y�h����c4�n0�� �.P�Y<�r(�'\�e��x��p�r��A�6����x�.��ep/P��'��d�/�d?>��l�����a@�z�6.��G����g������.�h��p�r0���($.��>
�5.P�a��e��f�i�o0�t�u<�wp�y���,*a�r�w����<�dd�nx�r��>D�tX��	0�Yp�n����rt�yL�����d��ols��u`��	��$�>��a�ġn��'�,d0����d�sxf$$=�d,=>��at<��g�|P�{���r�>$�y\�5L�aT�eL����($��5\�.����d�d�g��n��r�����Wn��6��o0��p5��a�o�w�y���d�f�i�s��u0���C�DRPplA�k�nH\*���5.|�b��c��d��e��g�l�m�n@�r��t��y\O7�Yh�n�*kp�y�+'��h�+P�,'ȣdp/���6.</����d2�l�2�	4�w�y85>(���5���g�5�6.�6EDra$�d,�e8�tl7
�7'��s@8�	�8���6.`�al�gt�y���	�8X�u�:Hh<����Cn�>��|�n,?E�:a��u,B�B��h�PdhY��gXcE��i�yTo�n>�n�o>�a �e\�i��o��r��u��w�o��TCdDq���g�Cm<�rd��r��4�aL�o���Ad�vUT�a��gp�o8wM�x;y��x�a��eTy,{%.`{�|���5.�d��e�n �r@�s�}Y�d��w�}E�yT*/\*���d�~�d���%.�Q5��gX�E�i�lJ�>,�lT�E4�t��U��d��eЃ��
L�a��c��e�f��g�i�n�vo(�r<�yhG$D�����dt�A�zhWhh����Ed�r�EsЈ��c�\a�r��*����e��R�E�r\�U�g��	��6.��4�eP������<wcd�d�\np�rx�t�'�	|!o4���X�i���?p�����
��b��c�d�f(�gl�m��n��r�shw�'Oh��5($.�d�n�r�����	�P���nd���axn�r���	,�G�$.���L�e`�iT*�	X��8�d ���@�d��R	D�X�aԟP��	d���t�u����|�a��g��y��z��aD�Tإ���a@R�	x�'�u��E�ae$g<lDnLr\y��	�e����.���n0�<�>a4e����i0����k�To���bTn4��E�a�b�c�dxef`g�h	i�	l�
nPo8p�r�
s�
tupw�y #��
�bc0dX�e�f�g�iȘm�n�rs tDw8$�w4&9�%dLf��iTn\s&��a��iH�rdwtyD&��`��an	l)�dc�dn�)�,G�+��|l�-��/�X5>�a�Ge�f�o07��;%.��c�e�i��l Hn�u0Hw�= >��?HA�	ex@Eg�f,C�8e$�o�q��C��0gE�Hd(D��<�Po0<��Xt�GYda�F��prG��|a�i�!y J��O��|$n0P���a_5�ad�e�orw8y�^\�i�n�`�s�`>�ae6��`�	�a5DaXeli�'��Y0l�a8dXfP�b��Pfdk4cUdd�g���6.�a�e��xrd��d��	+��o +>�hЪ��t82���e2E�aHq�g��n8tTDt���e�r���o w$�uy��y��$glw��,nHrPy���e�|�	}��Ta�d��i�r���y��\dgi$l0n|r�sȜu4k�	4cU�aL}���i�w�d�	�d�r�~�	�~����P$�Ri����nD�.H�>P�t(Iw4�k��y<����IsLtpy�%�����Dețr`y��
��7H���hdp�Y	�6.�a0�cD�d�e0iDohwpy�C��wd��d�f�ins���)���.8D�y�D����E��E
d�
P�Pa,R�d��;$a�X���<cXm`n�Y���J��l�dhYD�a�Exi�odt�w�y4��j���l����m���r�����a$e��$�i��>�h��Y�c`����l����a4fTyL����؜>,l��$��5@dl���Hd�5�y����r��tlf���xf�m�s@�J����o@�-�e$
p���ax�Y�i\����r��cD�5(a@e�o�r	y(�74���dD�Ya���r���Ir����4iXn�t�u����Hape�t�w4���xId�s�����R�JX���a̓�����1��k�lP����b��T�>�a�y�a������fD����d	f����YT	a�Jd�Jet	g�	n�Jo�Jr�	s�Jtİw�	y������L	sЭ���`	iL���h	e���Ht��c�Jgdi���5�	a�	b�	e
i�
o�
y����0�s�KPK���	wX�U�	l��;L����	d�Kw��D�
nT
w��
�/> 
s���(
ih
s$��4
a|
w���D
iA%
��E`
e��.
����t
y�EHa�
e0����
n,�'��d�����nH>�
a�
ei0��
s@vd�
d�i���]��u�a8!�H!��(��,Y�9dxi�qr\*��<d�f�*g�n�rshlA�'&pg��T2���d00���y�6E%.��alLd��8!�H!�����85�c�d�
o�9�9,i�9c�d�I��IUwt<$i�=�0y�i^	,?�Pr�w�A�AsHadolw�X$^��Y(�iPB5ta�B5�a�e�i
o�
y,E��B���i�Ln�Kb�J���d�Lg�Q5,�a�e�g�SM�S/��tX��d�X��
aD
dP
eX
i`
oh
wp
y�'&$Ma��r�X��4
i(Y'�3'T8�l>'�>dbda��x
n�
s��cE�
oXc5�
a�
e�
o�
t�c4ec�Mn�jYm$�k>�
r�o>ah�o>s�z��s��|>@ap�dLlTn\sL}*�|U8n�.��PT�����4vsЃ��dapWc�i�r�t�y\���5�i��;�����wb�NlDwn�s,t��6
ؓz�el)�����w�C�d8fTs�>�a�e�i�o�w�y�'h �A�c��@��@n��HahopwB�B�{A8�xc�m�r0I<
`tA(�M�+A�C�cD���k	�cdf(lLmlnxr�s�w�$]h��$Od�r�yd����y8���%.<aDn �X�ԟ����%.d���Ti����`aإ�a�e�Of�E��a�����s�@P�K"�K���i���d����g��w<�>.@�4�Y�y|#Y�l�o #��
�b�c�d e`f�i�l�n�p�r@sptxu��a�b�c<ddef�gXh�il!l�!m"nP#o�$p�$r�$s�%t�&u<'w)yH!��#v�#g�$�8$Y�h&Y�l�no�r�_uw�';�'�a�'�D(l)r�)��)�@aLl�z��_a`w�)�0rp*�h+'tu�+��Ta|o�y,�-b�-d�\�/>�c�g��n��r�/�/X5>�a�Ge�`f�5v|;���m;E�o�;4l������d�VPa�Vn�V�eDV(lx@E��g\oht4JB��Tl$BD
,CI
�D.G�y�J0O���r�NY�d0P>	�ade<h�i�l�|rtyPQ��O���n�g�e���spR5�e�RAxRd(i0nH�k�RY�).\Sxw$��� d�;Ha�cTi�W*`lHW��li��M<X�a�d�e�o��r�t�w�y(�L�M�XM�dlXN
tX���.��0��M�X�[E,]Pp�.�\��d0n^U
�]�(y_ha�d�e�io@wLy�aC�^`sxw�a�a5�a�a:\g[
�e���n4kd�jU�a�e�f�g�lT�o��rst�~w�	y�k�k`
hlD
m�Lnt�n;�q;Hqd0e�r�qw'�u8rlw��0�n\s�y�y���a�b�d0fHghh�i�l�n�r8s�t�u�w{��o�r@{|M�x{>�yL}����f}���dx�e�i��o�u(y�~U��dg��r�ct\l�~a���	n@y����g\i��7X�Tn0��e
���pdH�>xd�s���4�ktF.�yЈ@�����n��k
�����y<����td�p����acg l(t܎���>h��P�r
�� �w
�E0e\itm|o�t�w�ydhYD�alo�J9jM�j�����m����d�J���o��>�h�k��d�fhl��l�D�y�>p�.̒'�l$����lDwn��Ha�e�f�ioPr��w\y�$ ���4d`���<ddmln�o,tȘvИ.�+e|t,�M<����jl\�������l؜s�a�o�yx����a�����nd���
d��4id�lX�k���l�����dnl�z(aDe�6J�}
�7��0i�7'8d���0�il�Yhd��5p�.xa��
t���wĩ���d�iJl�m�ru����ad,ePh\idl�n�orw@yĪl>y�G�8Qt�D���ܬ5|6yȬod��(���$l��Y8n0���8yȮ5Dw\�Ṵ�|w�y̲�
����tm��m�n��$�s������e�f�l�n�pL���8a��A����d�E�e���\8c��za̶��c$i,y\�Aй����n���
���8fPn`�D�o|a�eo`u�y�:���to�r�wD��X�$(�Y�s�Jp�Y�r�����b�n�p�s����Ha�$eL�5�Ivp/�����dȦsP���e n�6E��a[e,f�%.~��0dx�P<e����Hd8�>Tdpe�~�$D���xc�d�l�m��58�$���ab8dde�f�g�l n�mo4 r` s� t� w$!yP��
����dl1�
���dP�U(a0eXkr�Fp�(�Hd\eh��Te��P���L��kt�kuxwl�\��;\����d�n�rx�Y�elfLlw��D
d��
����c�����yL�Y�ae@i�ll�y8Q�
0����m����a��nP��	��.aTw��� d��b4alo�wl)�
���
��.xt���\n@8�
���
�r`��
�lm�����&�d�����aH�k�e�l`muxmy<���TaP����d�.�a i��,� o��c$ o, wH����M@�EnaP eX i(nw8ny�J�����Dnghno� txnw�ny�mD�s| y���� e����� h0�5� a� c� e� l����������d,��
� e!o!wD�JT�J\�J�,�'!d��!dp��`���0!i`!y�F��8!��$��A���X!c���a�i�!l�!y�s�!o�!w�!y�$����!n<�6���p����!n��Y�!n����<�a�!eX�p"o44��4!d4E�!aP��"lHH"ah"e�"h�"i�"o#w(#y�A��@"lX"r�A�$d��`"d�"t���>x"hX��"f�"l�"o�"s�"w�����"l���m�` �� M�<�
p$�"bP#���"s�(A<'��#y*���Da�De)��#nTYw&��,*aT*U8#d\*��D#a�#b�#d�#e�#f@�g$l$m@$nL$rp$s�*��,���5.�#o�qrP.O</Y�#o�#r�#t����/�0�
00�#e�#y�0*�0���#l2'�4��(�g4E$e�5P$$iX6\�7#�7',$s�6E4$e�8�d$c��f�9��9>\$ht<�E�).�y��x$s���$e�@�
$@���$t,?E�$e�B�$eTM��J���$oXc�$e%i`%y4ec%i%n�e��edh&\cc��g(%l0%yti�iA@%cH%n�P�ie
To���Da�!e�n>P%n�o��TCd�%f�%i�%n�%w�o��l%a�%e,&h4&ih&o�&r��w�&y`p��).�pP�pz�%d�5�pz�%a$qDqY&i&l�Cm&r$&w�q��q�r���6.Pr�ss�vUT�aL&lX&n`&sH�k�).wA��t�w��\(c�&d�&e�&r0x��.@x�xdy�t|�{���&n�&r�o��|Q�&a�&e�|��|�&d'n'tHq>�}���&o���E'i�E��Yvfvi,vnd'r4vsЃ�� 'a�'b�'d�'eL(i�vo�(r�(y�D|'e8���%.$�p'd�$�'e�Y�'l�7Ȇ��Y�'eЇ������'d`vih����'d�'iH!l8(n��$(t�����'u@�;(a�(i�'(h�]����0(d��A\�YD(ad(dl(rx(s(��$�6.��tl��P����(i�����(e����%.�(b<wc�(l�(n)t�,�LL���(aL���(d�Y�(o̒;�'.�;���$]h���	)c@)f�)g�)l�)m*n *r4*sL*wd�cp)axf|)n�)o�)r��Y8a�e�o����\)l����)d�P,�P����xy8���)aX.u ����xn��ԟE�)a�)y���x�
H"���)ul�;�)a�����)n<yo��إ��*a
cpyi��%.D*g<�4� #U��T*a�*b�+c�,d</e00f2g�3h�3i4l�5m�6nT8o\8p�8rt<s>td>ul>w�>yGk�*a+e<+l�+o�+r�y�G�F���*e�*n�G*�*tH:P�H���#d�#l+n(+rPI���).��y�I���).�JK��4+aX+e��o��w|+y�K��Fdl+rt+s|�;��U�K LtL��O0P���+a�+b�+e�+h�,i�,r�,t\OP�NY�+nhR�+yxR@S,n\S���+a$,e8,ll,nL%o�,r�,w�,y�S�`T���<n���U50,aP,e\,od,w�U��t+s����tV;x,i��g�V.HW�W<XOLZ'�['�^
�,o�,r_��	�,a-d�-e�'i.lP.ol.r�.w /y�:A a��a;�a���,a,-f@-iX-rl-ut-y�iJcs$-y�c�4cU8-lP-w�n� d;d-iHd�
hd�e�-d<~nDe7�eP�-a�-u�e���-b�-d�-i�-l�-r|H�
�z���-u�M�,f��,i�P�f>�-d�f��go(o��.a .e8�w`o���Kc0.s��E��i1��qY<.fHqD.f`.g2�
0�a�r'�.a�.e4o�.w�.y�rlK7�s���.c�.m�LU�`��p�.�t>�.d�t���.y�tA�Dc�uO�.a/e�u�hvPtv���.d/n�vP�wlw��/c0/ny�Wn�y��	�Hcp/d�Rg�/l0Im�/n�/r�/s0tL}/}��h/d�/i�/l�/r�~&�/g�~�
o�/nz�/a����r7���/a��4�k�/c<�P�Rtp�P�P0g�g<�>0o��>�ga(0r��P�����5.d0a�0e1fT1i`1lh1n�o�1rl�w2y`����d|0i�0n�o�G�И.�+e,�t J�p����0i�����0b�0d�0l�0n�0r��\����,An����6.�0liy���؜>�0a1e01i�,wP���P,d�7��O(1d�P��w��<1d��H1a��P���1a�1d�1e�1fDu���@ĢA:������1a�1e���1c�1d�1m�1rإ����M�A�Da�!el����1n�E	82a�2eD�f�2i�2l3n3oH3rh3yĩ��|0iL2nx2s��cd2ap2u 6	�\2u�&�T����TX���2y(����2d�2l�2r��sЈ�����2n�k�2yD��2d\�\�̰�`.w�2y�$��L�����3f(3r<PȵY 3u������43y��><3wX3y�;|����`3d�3f�3l�3r����A(��x�Y�3nD��3ep�������3b�3r@�$(�������3d\Xg�3r�3t@������E44aH4cT4d�4e�4i�4l5o5s(5u85w�5y����tKuxRgx�>@4e��H`4a�^:$���4a�4e�4o�4wL���h4u�Kw�`
\*��~�4dL�����d�&�4r�;���4e�4t܇w���4d��70�*0�55st<t�� 5b|d�
��'(�
\���05rH5y<�X5dh5nd�;`���\/`5oX�����t5e�5i�5o�5w����|5n$�t���'|�tpE�5a�5b�5eX6id6lt6o�6p�'x>6d(6g46i<6nT"\��6a 6i��(��).�0d.�	cD6i	YL6s�	��*,P��l6g8�6r�JPPs�6eHE�6aD7b`7cl7d�7e�7f�7g8i8n(8o@8tp�w��T�.�Y�6h���6c7d��f$7i��n87u�_��Y7u�>07r��3x:�).�uL]���L7f�>T7y<u�7i�6odk��U|7dd'�7dx�l�7s����6d��8E�7tudc����7l�7o��w�7y�>x�\@���7r�O"up2u�#@*.P#��8f88g@�
�%>L8oh&�\*U,?�p8a�8l�8rd?|8o�?A�8n�6�@A.,E��A���8i�As�8a�8eMP�J���8n�B@9cp9e�9f�9i��n��u�9w�B���8a8�b�9c�9d0:e�:f�:g�:i�:l�:m;n0;o�;p�;t<u<wh<y�C�L9h�C�+��$.|9a +>T9hDDYd9t+?�D,E�\F��G>�9h�H;�GY�9w@IA�9y�Hc�9d�9e:i$:ype�J>�9n\gCTJ��9nlJ�\J&:n�JF�J�:n�J�=bT:dh:g�kt�KK`:y�K��H:d�}�<LQt:w���TN�:n$Nk|:a�:f�:o�:y�5�И>�:a��eO>D�e�OP�4PL�Q;DV;*<W�:u,�y�W�W�;a$;e���W��;l�XD;fL;h\;nPY�3:�YJ�YET;ap;ex;i�7;(Zb�6.�;d�;r�;t8�4 #� �X��<[5�;s�Z5�;w�\>�2.\���;h�;i�;y|]��]����t�U�;c�]��<w$^4<a@<e\<g@/E^k,<h�^L<l$_kD��4_�T<la��dXc�	�<a�<b=e,=g�=i�=o�=t�=w�=y�c�<n�c>@aa<HW�d���<s|ds�<a�<e�+o�<w�\y�H��d���<d(N�GH	�yY=i4e5=a�fDf>$=aD=lT=ol=w\g�0�Y�g��L=ed=rȵDh5�Ay��P�h�x=edhY�=a�=b��\P�Y�=i�j>�=d4�l<�n�j���,d�k>pBe�=r�=um��m*�m�n�o��6.>e<>hP>sDq0>s�W�r(>a�s.{�\>a�z5D>i�h^	�|>Ѓ��HNc�>g�>l�>n�>t�>y�P`�U����>s��Y�>a�>eL��\�7D����>r�Y�>e������D�c?w4��?e��A�B���f��?rEd?a$@e�@hAi@Al�Ao�ArBsBt,BuPBw|By #>
�?b�?d�?f�?i�?n�?o�?r@s@Ru@w|#YHGy&�+�/�X5��,�:���?d�<G0<���?t�;Y�?a�?c�w�<x@ETHtEY�He�{'�y��	@cd@dp@i�@l�@m�@n�@r�@s�@t�~a�~UP@g}��X@iH�kIrp��6.4�k|@iyX�z<����Ig�Is�@t�Iw��;p�'�z��s�aD�s�@e�o�r����4iAn�t�����t�w������Aa8Ab�Je�JoİwP����>TAapAe|Ay������nPou�����6aL���dAd�����Kc�Ag(�h\*��Lb�AgTlh32E�Ay�Bs�Aa�Ay�EC�B���Asa��tMf�An�b"db���AnXc�BidhY�w�o5�}Y�|��$BdDBsЂT�E<BrЃ5�dxNrdBy����%.�����r���pBd�BrإY�#|#Y�BeXCitCl�\y #���Bb�Cc�CdDDe�Df�DgEh,EidEm�En�Ep�Er�Es8Ft��u\Fw���Ba�Fb�Gc�Hd�Je$Nf4Pg�Ph�QiDVl<Wm�Wn�Xo�ZpP[r|[s\t�]u$^way|Z��#"�4�K��`Cd�#EhCy@7x%���Cy�$E�Cw8$Y�Ch�Ci�Cs�Ct�%n�[$�%��%u&���Ca�CdDr8Dwp&DorU�&>�Cl�(6�(��Di��M�)��$Dil),Dr�)YTDdxDn\*$lDd8}(dT*P`Da�*/�+�(�nh+���Dn�+���Da�Df�D�H��F���Dn�����D�(,R�-��De�DoEr.Ax.̶;�.>�Da\/A$Eu@/Ea|HH	�/z@Edh�w(���8Ed<�4��LEdtEr3�TEo|Ew�4�4�L6GX5>�Ed�Ee�f�Eo�6>`9�Ed�9s�9���E.�@P�@s�Ee;E�Eh�;��?cx@FiFt�A��AYFe$B
Ы�
\C��$Fo,Ck,FaHFe�C�EpFsEYPFexFi�Fn�EzF�@FPGU�Fa�Fe�FlGo4Gw`Gy�FY�Fr�GD�H��T�c��m�Fn��r�Fs�I&	PI���Ft�IKY<LML���FcGm(GnXL@8�`L� Gt(N����cHGdPGn��\dN0OP�NYXGd�O>�Gd�Gf�Gr0P��lGa�Ge�Gh�Hy|P���w�G��).�QDxR�Gr�RA\SY�Ga(HeLHlTHm\Ho�Hu�Hw�Hy��@S���GdHnHr�S�TCDT7`T�� Hb<HiDHt�T��U��U;4Vp�V��X9cpHexHg�����W�HW��p%a�Hi�Hy�W�
�WY$5d�Hn���W���Hc�Hf�8m���\�_cIa@IdTJe\JitJo�Jr�Jw�Jy�^�$Ii,In4IuX`��`.�a��).�aYdIa�Ie�Ii�Io�Iu�IwJy�a�&d0sgX�fktIl�b��|Il�c74cU�In�Iw�nP�nc�Iy�c�Id�c��($.�Ii�q�hd�.�dk4e9 Jhe>�Ic@JrLJwd�	�wEJw$��<���,Jh�e�4Jc4�'�e��j&lJn��r�mAHqn�Jd�q��p�.�r��JoDtP�Ja��,*atX��Jd�u�lw��Jny�6.�y&�y���Ja@KblKc�KdLf<Lg`Li�Ll�LmMnTMo\Mr�Ms�Mu�Mw�G��z��(Ki\Ks{��0Ka�Te�TidKl�To<H�8{P�{����h|Ki<XL}��6.}���Kd�Ki�Ko�Kw�Ky�m�~U�KorJHq�Klw�PO�Kry��%.��Kn�A���La$LnX�yx�JԀ�Lyl�R�6.���0LlTLo�����'LLgH�>	��a�Lc�Ld�Lg��l�Ln�Lr�Lt(Iw̃\����).�Ld��`�|��H�Y�Le��E�).�'�d.4�kp�l	\X�E�Li<����dMg MiHMn�/@�,Mn ���(#��4Md"<My��Up��pMi@S���;hMd�MnЏ@mP��>�Mr�E�Mt(�w<���>�Ma�Mo�My@�"��OؖA�Mdl�Y�MiNnNy(�7�E�).���Cn$���Nn��k
TNa�NeOf@Oi\Ol�Oo�Or�Ou�OwPy`��|Nd�Ne�Nf�i��o�Ns,t��u �d+���. +>�NhX����Nt�GM��P������Nw�����Nd�Nl�NnOy\�c���Ny؛TМ ؜>%.p,i(Ol0Oo8Ow��y�;x�l�;����dPOlX�k�6.��hOu���,
xZdУ��pOa����|Od�Ol�On�OrH�P�).l����5�Od�����̧�Oi���\�Y�OsT�7D��Oc�YPyl���Pn�,r,Ps����oHPaXPe�Poĩ��Jl��n(�pPl.n���khPyL�����|PfD��PaQe4QiTQo�Qu�Qy.J\�k�Pe�l�Do����Pg�PnQw���н>�Pa�Pe�1(�\�����s Qw�J��YQyx����,Qf�HA����@QcP���HQehQn�Qs�6E(a�lv�lUtQr�=>|Qi0���Qt��������Ql8�>�Qg�cD����Qf�Qn����|a�8e��5,Ra�Rb4Sc@Sd�Se�Sf�Sg�Si�Sn0To�3r�UsVtVw<Vy���tRe�Rf�Rg�Ri�Rl�RnL	s�Rw�u�+�TRu���\Rh����hRt��P\�k��l�/��Rdh��(����Rd�1�����Rl0�*�RtH:�D�\�Rd�E�p�P�Y�ReSl�<wSy��PX�&PQ��O�� Snd�Y(Sa(�����a�dXSi�Sy��������`Su�x.hSa<�tSl,�'�SlL���Si���x��L�P�So|�<�&����U�Srp��Si��Y�Sd��eTg Ti(Tldx����Tl��l!����LTcxTd�Ue�Um�Un�+;�_'�,
TTd�Tf�Ti�Tn�Ts,���\Ta(Ud0Ue8UiHUo�Uw�Uy,`�X`��`��/��@���Ti�Tn�az�Ta�Te�TiUo Uw�c�A��A\X9�B��UcUm4k��BL���-;�'`
�q�P.@Uc`UdhUmpUn�q�<r�Lr#Pv�t�OxUc�Un�Uy�#l�� /P������7;����Ug�Ut@8���tAl����Ug�UtD�.�������Vh0�'T�a(Vl0Vr,�G�����6.���|Va�Ve�Vi�Vl�VoWuWw Wy��>�a�Ve����lVn��L����ow�$�a�Je�Vo����($.�Ve�Vw���Vn,�P<�����).0����Vg��n������d\�l������Wm0Wn��P�).pTWahWitWo�py�Y`WcA	&MlPY�pdH��Wa�We�WgXi\XohXyHXÔ>.���Wi 6u8E%.�Wa�Wtd���WsܑT�,����We�Xa$Xi4Xo��x=e<�g����m�,Xl8!tH!��@X�p$�P#��TXs)TYwT*��dH�u\*��tXa�Xb�Xc�Xd(YePYf�YgTl�Ym�Yn0Zs��t<+R�*k�Xl�+P�Xl�+���Xh�Xr8,$�,�,��ȣdYrl.Yw�.7�yA</�� Ya@YdHYo�#rp/P��A00YdYl�Yu�,w`1R̀��%.؀��lYd��xYe�����Yn2E�6.�Ya�YeЪ82���Ye�2A�587�6���Yu�6E�YaZg(Zi�7�Zw�7���Yl�P|��Zy8bt<�%.HZfPZgܤo�e,=�}���,i�y��XZd��dZe�F��pZ�,?5�Za�Ze$[i,[o<[wH[yd?z�Zr�?�$@��Zc�Zm�Zn[r[s|�@'�Zh�@z�).�@'[t�@C�@'�).�@z��iA�AdBPB54[y|B�B`[ap[o�B�Yv�X��h[gXc�[a�[e�[i�[l�[t\y�/*�c���[i�[u@R>�e4e5�[ndh&�[n�iA��).�ic�[i�kH�nk�oz<\u�o��\aL\e�\h|]i�]r�]u�]y|H�xqMDq��D\dl\ix\n�\s��h�q>d\s�qMr�s>�Ca�\e�\i�\l�\o]r$]wh]y��(t�\gH����\n0,�tt5�\a�t�\l�tU,u>pu]y���u��]cvH]y�6E�$.�0]n0vY<]o�v�v��T]ddv��\]d�A�vUt]yy��]a�]w�xlz�Hz'�]y,{>�{l}E(a�|���]b�]c^d'nhust}�}P�O.�}���]d��k�dvf`^gviЃ��
^a|^bHNc�^d�^e4_gP_h\_ip_l�_n�_o�_rP`y���G��F��h^n�Yp^a�^l�P"��^nȆ>�^n��Y�^eЇP�^o�����^dh����^d_i$_l,_r2�}�^g_lr���>_dH��Ј;������<_lL�oD_e\�Uh_nP����R�Ex_c�_i�_o��g����+��*k�_r؍U�_b�_d�_l�T4%����_d���`eD`ix�UP����_sd�*\R�`ohR�`htR�� `t,R�,`e��58`a��>l`b�`damDwnd�s�\��\gt`.P�A|`nL���`e�`i4����`d�`e�`o�`ray��2P�P�`u4k��`d`��`a��^��8��P���ԟ'���
�6.Lab�ac�ad�af blXbmdbncs@cw̔'p�YDal\aoL<Wk(�id���daa�Epaw���|ah��Y($.�aa�ad<]l�`nܕc�ao��u�and�%.���an�P�ao���d���aabe�o(�>brp�;8���<a4beDbi��>О9��<btp�.ԟEPbr����	Da�bc�bd��e�bf�bg�bn�bo��y$��D��ģ
�J�z�bal����$�bet��bd�-^	�-���bd�br�->��
ca��E�bb(cg�d7h*<�> cw8cy��4�Y�^aPci�&E�ca|db(ed4ee�efDfgdhi�iljm�jn�jo�kr�kt�mu�mw�nyld� #��x�c�cf�cndrdt@Ru�+����a�co-X5>�a�Ge�Go�=C�=k�ca�;Y�cf,Ck\Ha dh�C�@dr\F� u��,dw�C>4da��A����Ldt�F��Tdi�F��`d��i�Gs�da�de�di�dlerT+D
pG���dw�F���dd�i����H���dt JUİw�dy��@%cK0T��Ls�dotL>�diprAHqer_��eo�y5lec\*dxef�ei�en�er�etPQ*�{OXen�{��`ea�;��؅c�enH�>�eol�UX��el<����eoЛw��Rhp����ec�ei�����k`���dm�����ea,feif��w8fyL}$����fd���� fdl����,r�>	�fa�fePgi\glpgn�go�grhw$hy�(�t�Epfr�fwĩ��xfd�m�fn�fr�fwĪ����0<J�Y�fa��D�|GX���fagegi$go,gw(����fd$l4gnx���~�
��Ugg��U
PU
����).Hga.e��
\��a̰>%.d�aP.o$��|giD���%.�����gd�gg�gl��m�n�gt��G�v�{Pl���gy�������gy��>�gwhy@c�����gw��5<�c/nH�M���hc8hf@hn��M`����DaXhe��RX���Phs��Y�ha�hdieigtil�in�Jo�Jr�Jt�iw�iy����6.�hu����hl�hm�hn�hr�2��:P�Y0�>�hd��\(���ha��L�it��RL�Y�6.4ia<ieDilPir`iy0�A����.�$.������`�OXin��MH�klif�$`��Y�is��A0����ir<�����in`����c�ia�iojy��x�����iu�EHal2e0����in��jwh�p5`patpetjwh�Te0jo�8js>Dja,'Pjh���\jtp��hjy�� 6uH���ja�je�ji�jod>�jgHn�&�#*P#���jb\*��X9c�jdke kf,kg4km<kn�,��ȣdkl.G</`1G00��kl2Y	�5��6P%.�Yahkdp;e|kg����btkrl7uXke�g�7�E>�aX{e�B���kn�B���ka�o>
�kaLle|lf�li�ll�lomr�mu�mw�my�o��ēcldlf lm(ln0ls<\u@lw0'�
Pp��le`pM3v�pk�zWxE�$qY8lcDq��T�cD\dllitlmx\n�\s�qR�q�xr��r@�E�lo�vU�lr�w�w���lo�lyȩP�w���lt�w>�ld�lr0x��t_.�x;��a�le0:��xAtF.y��ma4meTmolmwtmylKTy��,mcDms�Mx�X�y��Lmcdmlz�HzT�z��d�c,{>�mr�msL{WT�E�d.ĒU
�{���mi`{5�my�|��{���mr�|>�mr\sX�P��kЃ���manc,nePni`nmlny��$nrLZPh�<nd��n��HndЇP\��8.h��Xnp�����WfDwn�!r�>�nc�nd�nf�ng�nlomTonxor�os�ot�ow��wh����Gd���nl��z��>�aot8����nl0�.ȠGԟEol@ou4&��} odp���(oa̡>4od�����Lo.lof�Aģudoeإ�oahye��>���ȩ;`�\4�Y�oa8$�	`Gh #��
�ocPpd`pfxpg�pi�pl�pnЫoqr�zsqt|Hu$qw���oahRb8qcDqexrf�rg�sh�vi�wl�wn�woyr�zs,{u`{w�{ypwä'~&��Hpl�r�+�|zappl�,M�-c�/z�pd(�;��$���pe�1;�pa�/Y�pi�pm��.X5z�pc�Ge�pg�Go��w86��*D7z�pn8=��;Yqdf,C�EY8�l\S0P��0qh�y���Hcxqd�qg�qi�ql�qm�qnrrrsDrtPrw}�T*d���.�qi��R	X��qo������qiH�>�qd�m4�khPyX�E�6.�Li<���d�qe4�A���gyp���rf��Wa0ri�JAdhY(ro̓��><ral�D.drclri��ؖY�Md���rf�roTy؜sD�eУ$�����rd��ra�resl�n0so`sw�sy�Aĩ���rft�n�A(����rnsrD�A��̰>saȵ��6.DsiPsn����sr,R��:;<saZ�Pv*��5XscpsyйA��`
���xswD�>	�sa(tettl�tn�to,ur�uuvwdvy|�Y$a�sm����sl�sntrtt��G0�z�pnн>�sgD�Atf����* ty�DJ��@tfHtl`trltt|�A�k�l���gy@���TtfX����50,a�te,���Vn�tt�����;�to�����tdP��tl�tnur����_d�tl�4g�4��tt�6E[e�8�d�Yua�C����ucT�> uaTue�ui�uo�uw�uy�;���Ludlun��PM��dutd�$4�sxudPY&��>�uf��`����uf�a��ua�����ud�a���8�>�ug�um�unvsp������<� va(vl0vy0����A��Y�:d�WfDvsؓz�"g�����PvhD���Xvc�vd�vm�vr�vw��������vd(�P8�E�va���TN��vu<�c�va����vf�vwX�\����UAawd�Jewn8wo(�P��wg$wo��PH����Dwo���,wn(8�4E�).\*��Lwl`���Xwo�F��dw����we����|wa�we�wo�ww�wyd�AL��wn,���%.0���d�n\���d�y�������wdH\*���qc0xd@xexxf�xg�xi�xl�xm�xn�xr�xs�xw�,��%.�9d</��@Qc`xd�mlxs�/`
p/��Xxi�/P�Ii00P2��3��d\s4��tl�xy�5�5�6.�6[e -t�8;hra��ct<�l>���c�n�C&�B���xc��d��f��g8ynLyw�B���xaTye�yi�yoHzw�zy�E>�aX{e�Qt\F�J���Ldtyf�yg(�m�yn�ywL��ya�ylL����<LQM���Rt�M@S��Q5�yd�yg�SP�).�X���Ja�d�yfzl8znd07PYY�ya44;TPza zo(zw5�85;(8�YE0zo -t$^'Ym�tnlzy�`��p�.|zdP`>\zd�Wf�`��a��aY�za�zya���zd�zf��l�zn�zw ��a�db���).@cXc5{a{e{i`Gx�Y�zh�c���zc4eDdh&�};l�r�|�� {dD{m��nL{rp�X��X{m<W�Ѓ5�{aHNc�>n�{r�{y�����Y|{i���{nЏ��������{a�:d�{e�Wf�m�{oDvs�{w���p�������D�c|d@|gT|l`|mt|n�|r�|w��� |d�PШy���,|r��E4|a��8���L|iԟE��p������l|.�|g�U����|d�z�|aإQ�oa�|e��� s(��4�Y�|r #U<}lL}nd}r�zw���|al}bt}c�}d�~efDg�h�i�l��n@�oL�pX�rT�s�tt�w��y�/�L6uX5>D}d\}e�6�;GE0PY�}h\SE�}e`T�}d�T7�^4&d�}l_���}a�}d(~e�'i8~l@~rd~w�~y�`Y�e�aE�,a~e�&f�&idf�b���}g�-l����f>~r�e��~i�-r(o�rPX~w�t>��d�t��L~y�u>�nt~rwylw��|~n�y\*d�~s�pz��.����~i�~u��>�~a�~w�~y�E�~t<\��{^�m5�~r0���D>�).`���u����aif,yl�ĩ���-d|�f���	4axe�2i�l�
n�o�.u�w�y�(���pn̰>d�a��e�w�������D�������p0d�Jf�n,�t����
�a8�d@�eH�fd�ip�lx�o�2u��w��y��>X�.(�nD�t�����k$�h��HL�U�����L���P�r�X�g��0���1c02d��n�P�oe\������oc�wdHE؀e�i$�o(T���Āyd��̀d�l��s�k�l8E�).��g���#��@*.P#���d4�l$��).\*U��gP�st<��BE��b��dāe�f�g��n�o�s�tH�u4�w@�y4G�FU��w�I.@IY��u�Hc��d�J���TN�́u$Nkԁah�l4Po��y�W�.�X���Zf|[.\���;yP`>��d$^��(�yaL�w@cAXcE��.��e��g��iȂoЂr�tT*P|*a\*��x�d4e5��d��n�E�e����dDf�dhY.�j�k;Ll��\�r�k>؂e�o�r�l;�l>��dm�o�o���a8�e@�hd�ol�rDq�s>p�rP�u�u\�n�uv�wyPЃU��c�k��hL�P� #Y8�cD�dt�e��g��īl؄n�r��s�t|Hu����a�b�c��dh�e��f�gL�h\�i��lh�m�n؍o(�p��rX�s�t��w��yl�7�$$�w8$Y,�e&��hGdX�n�w�'.@��*P`�i�)��h�n��r�*5�-��/z�6.�pd��r`&s�`t@�z0���1>��t�/Y��lX5>($.�a�Ge�Go@IY�Er8=c�d�;Y��d$�eL�il�t�w�=8�d�KK�K��0�d4>� >D�a|?g��a��i��wl?��X�hh�H	�Cx�i��s���\�$]��5?x@E��.ԅe܅g��i�o�t�wA�HA�$B��B;GY�l�K��t+sK���e0P>8�a@�eL�h�,i`�wt�y�O'xRd\SEX�uW�D\�;l�]A�\��l�n_Y��dȆe�i�l4�o@�rX�u�b��D'g��w�a����ec��e>؆d�n,f��\g�a��e4���j&�h��:�o(o���y�q���N.Hq(�d�rE�aP�y�t$u>{�y��`�b��c��d��g�iH�lp�m��nЈr�sl�t��u�{�}��Lo.��aЇd�i�|��}�L}��ȇf�i4c��~��/gX�g����i�r���H��H�>�n���ȇ>(�i���0�t4�k<�lHgod�w��YT���\�lX�x����x�u<�����a�d��tĈw؛y���(�,�����n�����yp���Lo.�i��s�y��M�d@S7ؐ���Al����d�z(�iH�tdh7	8�r@�t�Jk�J9��>�$.`�a�p*���X�i��zx�h�%����g��n��>�����cĉảeԉf�i�o�w�y`�����؜>H,a��n4id�����r��5̧���yl��E�5.8�iD�lh.n����6.\�,�g̰�D�o��e�w��Y��a��b(�d�f�g�lP�n��o�r��s�3t��w0�*�����nP�Yp^aĊl؊o�JJ������aT@���Њlx�Yp�rL���l@�J�>��eH�c��r���a\�eHxid�o��� �d����8�a��YD�g0'U
D(U
�7^���l�e���t�n��.�����g0����N.��e����Y�a��c�i0�lH�w\�y,c����؋.�����d��n��.�dXn�p�� ��7p����n�>$�yX�E�).\���<�s��b����T�cp���a��b��iԌw���n�.tL*�����r	k��a�/�T	���ǐn0��p��(vlHE	�a$�dP�e|�g��i�l��oЍwp�����>d�e Y<u8�e@�i�r����*d��H�i����\�lH!��d���bl�,MnP#>D�c��d��gȍl`�mh�n�#���9d@��$�<''\*U�d��l �r�,Y�6.�9d4��6.�ad��44���e�8Y,?�<�eD�l`�w$@@A�0�xNEL�dPB5T�r +�DDYl�t�B��t�e�B��
��aԎb�c0�dP�e��f��i��lЏn��o�t�w,�y`GYh�n�FUȎy�G5��a�e�h�i$�wlG��G��G�<Xg���D\�d�H)D�e�f�TJ�<�i�J���Ldl�ix�s�L/`L>d�g�ME��i$N&ԁa��i@Og�Q5.��d�Ze@SP�6.DV;\�l�W��W�ȏa�e��o�Wv�W���s�X\>�h�\.$^��s(�y@c�a��$�w�LPe>8�e|ds@�rXcEL�b �e��g��i��o��t�u�wDf��l\g*,k��j����g�kAȐn�k>��aܐe�Mr(l�r���6.Ll��Аr\v�m>�s�m��m����a�oY0�aD�eh�h��o�Dw�]y�o<�n�p�Dq���Cm\�rl�Pr��T�y�s�.Dw0x�w��x�dЃU����l�����a�b(�c4�d��e��f��hĒi̒l�np�o��rؓs�t�w�My�0��/Y�c�Q�yp�Y��r �u|MJ�M��$]h����xaL�d��r��y��d�lx�y�oA�c\�yDeA��>p�d\������o �G4�O,�d����r؝;�O8�'�d�l�e����ܒb��o�eX�Y�).d�����d�����a�bdD�e�bfP�gX�i`�oh�y���X���<�g��	$�gt�D�p�O�C����x�d��nإ����a��d̓l��h����.t�c��d�V;�sēi��zȩ'4���|r�^y #UX�r����ap�b�c��d4�ed�f��g؝h�i8�lԟm��np�o��pإr��sȩt4�w��y�;�%.h�nH>M	GY��a��e̔l�r�<wTGA�F����cL}P�H'��d�H����dĔr�I$KEؔy�KtL��i�L;\SE(�aP�eT%rd�w0P���h�%l�S�<�w@S���n�(��:4�r�T�`T��H�i��WHW��\�et�y�Wx|n_��
�5.��a�d��e4�f�'i8~lx�m��n��o�r��w �y�^u4&d0}lܕn�r`s�u�`c�a��e�Qt�w�`��`�aU
 aD�a��aPD�aP�eX�f`�i��o��yb��a<�n�bAcg4cl�n�cY4).�qA�cx�fpeA�).�ee>��n��s�y��yE��g�e'ܖb�d�i�-r�u�e;,f�d�a
8}�u8f����a�f�hL�Pi> �u<h(�fP�wd�yxiP�i��H�r�i��i��\�n`pA@pp�a�pY��b�p��e�p~Hqb��dėl̗s�q��($.rP�r��s
�r��ԗu�r��ܗa�eH�i\�o��w��y�s��,mc�Lm4�s@�w�h
dhY �d�ME(�i�t6T�at
Dt��Lmcl�lxt
�`��p�.�d�t>t�d�t����y�t�d�c�uO�.a��et~rtv���.d�i�l��U���Иl@�;ؘo��i�'�h�>��tH�k �elw,�l�x��y�#lL�rd�p���D�a\�n<�M	�����a(�e��f�,i��l��n�o,�r\�ux�w��yd�>`�����g|0i��nԙr,�;И>��t���	P�Y̙e�h��w�Pp�?�����a�|������aH�e��iP�oX�w�����d`�i�Nlp�r��s��wD~�Hq�P��������h�f��n<������il�Y7i؜���a�e�i�l�r�AКi�/n�GzȚt|��P���ܚi��T��nd�@�r
Ԟ&ܞ���a@�ot�w|�y��>(�dP�mX�n�>,�eh�w0IU@ @����;`�c�&�9@����d��5��e��o�������eĢ��>��eЛo؛yL�x�����d�gУ���).�2@���lL�Țt��>�iD�n����aP�yl��T�><�e0�Y�gw��|���Dd�ģ��l�nl�����g��n��t�Y�.�Da�De��g$�#
ȩA�E	�a �eD�id�l�
n|�o��r��w��yĩ�d�r���t�E��u�A���(����g8�i�Э>0�d\�T�l\�n����̰>d�aP.o`.w�2y����e��f0�AL�A�����a̶���d��5��y���hcНd|��D��e��Y�i��w��H��&���Je���lX.�w�����d�Jfp�n|�w���� �a��c��d��eH�f��i��lX�nd�o��u��w�2y��.D�t��Y�6.x���0h�g���a�e����r��o��eL��
nОt�,c(
<���؞.P����d�>�a�1e�f8�iD�o�1w2y����EP�'$�a�,�d�P#���s(�L�o0����1c02dd�n��r0:�@���|�e���\�����ed�y�/P����t�����i�l�n0�rpE
��a<�b\�e��g��hȠl�o(�pp�r��s̡u�w��y�/A�>@*.�t0HJ�(�w���8{l�\��H�rx��P�dx�r��s0�|Yp�iP���E��u`���@����u�5��e�	Y�	5��eܠi��o
o�
��
E�e�
���n4Ed�iP���l �n��8��:a@�iP�o\�rAA�,7�A��H�dP����`{n�5d�a��i��o�(u�.
�5��gP

����e�
�
5��epv>ġmw3
���ءbp���y�&�n$�rD�Jl���y�Px�a@�eH�i��oP�w����4P���Y,*a��X�dТe��f��iآn�s�u�wH��d�a�b$�cD�dX�eģf�g$�il�nt�o̤rԤt�u�wD�y ��@Px:
EA�Y�+n���y7�d�>�e���	�U0�r<8�i�vd��	P�b,�c��d��g��i��m��n��r��s����6.��d�;H���ܑ@
�u8E��auأyhP\YУd�z�e�l,A�rD�7d�D�����w��a@�dH�fP�gd�l8���D\�y�F
��";P#��a��d��eĤs8#L
D#U��d-P�#����d�>it�y�#p$Q
�$u�%E��a��e�i��o\�u4&gh&A�&A�&�d<'(�a0�e8�y '�'��(p�r)T�cd�d)Y������\�d\*TA@A>x�a��e��o��w��y,?E��lpA\�|A�B��0Ze�9f�n��u�B����a<�ct�d��eЦf��i�l(�n0�oH�sP�wp�y�E>�a�se�Qt�GAH�eP�oX�w`�y�G��$�h(H\H�H�H@I���y�Hch�dDeV
J>��d�J��i��l��n`Lk�Lk�).M��0(d�N$NkȦe�fp�u�yO\
P���d�Q5,�a�d@SDVs �y W�Wc�X<�lTP�).|[s$^d�a�P^k\�ya�c'�zwXcEx�a��b��e@Af<�g�i��n�o4�t��u��w��y|d�	�l�y��ЃP`���Чw�F��ا��dA4e��t�wP�ek�l�fY��.d�a��i��w�f���rDf>0�a��e��l`�nبr�y,E�
�fJ\�i|�s�@	�E`
t�a4>� >��a\@g
H�����y�f$t�\g>��eШw��7�g�$hAdh��j&�).�j���i�j'�d �l�j4J�).�k�k>,�aL�ex�r��wLl�`�bh�d\�r{7D\^	tmm��p�y�mM�m�m'��e��i,nAPnb�nb��g�n��o��	�a8�e�h��id�oȪr�s�w(�y�s>	�Ca8�e(�i4�l�CoT�r��uDw��yL���� �ett;TuA�.cl�dt�s|�u,u><�e��o��wDyLun
�MU�M��u|^��u����b��U�u>��r��dv����ny5ܪa�Es
�x��Ԫs�z;D�7�{Y�d`{5��a�Dyx�P��E�a�{���sЃY	`�aHNc��eثg�i�>n�o(�r@�y��Y��eviЇ���i����p�dh���|�d��gīl̫n�Es4kx
����a����).H�k�����).���M\�Y�ad(d�o�r��M�P.���؍U�l���4�a����i����<wc�Wf\�n����T�a�|e4030012225013042110400021014230420234402500322040014350423342102005022020242422021222401120254340252402120155201002500130520014012501050202045220015003050010203500005042320530430301224400002212024101200405101220405003544210200040442024004230005035010140001500050125020522030402350300500322251052004403420452312220453215200312431032050012040423220124233023022310540215200344503030415010440354012021401030223004001500000200500502004050130220403003222320040304201040303232420004300404010054040330300142232200123400123010405000402033522140320202052103003400033020300152011302403232232203240222140020502340000052020203240100103234040120401015212044444024124320503242324100402013244200541243214220301220122001303040002350400303101254005300115004020014345204400250152234420210030212423020233000050400043013001203031034104000032010110222000052022010022022030050024500500423012322232100020020500200223410145200032015021022320430002042121032103002032000432014543000002240222400004300020400001004000050140000200400000004050010	40000000125215000000205023000241340040214043400350323240234000501242221421230200122001240012140301502221302000440035000104000012002350300030012300022230205540020200252220440304303400245052035420522032043403030401340433203342050010041421204101420113010432105023030233254321022002230100502502141201211022000110500122420	40000000052140032400103212120140103400022300330325104430303043504034021050000024232022223002505014022020030502000130045000312500414302213032030001321103011024103021305200102201022032541030045103330050422314004402320213400352010121032401320210035400020431202503205200003422251043003	5000000004120440140201322320444002534023400441000520003220344532020500014050344023400305012050040234431040012040002040010140302400050400540200021211001404240404221020003200550000000514002441325040503000440550050003221404005124300142041405000402520130402452000442012200125241050410400043210141024320052045401251204221400100230351003050052124200122125402032022200252034120042120523112304302044320340140230135254000003403234003204000000254040001042032400010043010054000042101500401252100500002015000000250010005200210004013040122052300052010210232303200334000040223003250002103440132314040350212400300502015000001040002015000002045000004201021350042304204225000235425001202000130300400224040235000335222302404503245210524040400143235000504540440004002113015230450300302014410403000502301302105022211250223030120301013201040000204520004023205002050000205000025000300500030350301505003402144224034432330200500534215000124000001523240000104004042030530035032032324310500154001210141000010112252030103013204330242322302224502100502212512304003354000323123120322103220040412045104040420022400210323320000522320304020204021205030340210040030330052500045035002302032330030450105500230450100324102101500320350031015000100PK
!<Z'=HIHIhyphenation/hyph_da.hyfHyf0tP�����'-4�������$�'–’-111001�F��������.�ab�c\	d�efDg�h�ilj�kh l�#m�%n�(o�,pl.q�.r3s�7th:u\<v�=y�>z`D�X���pe�n��xabXd�hnoDp�>sdt�y$?�X8���k�s�'P
h���ax���lt���e,i����r��� tt
\
��8i�	��@r\	��Le�i�'�%��ho���pn�|g�
���a(���r����e�o�<�
�d����e����v$p&���t�%���e�*'�(��m0v�<)d,��(e�-'�,��<o��8��Pl�7��Xi�=+�=��pr�=��xdx!
h ���e���ls���b c@dXe`f�gh,i8jHk�lm8nto�p�rs�t�vzp/Tt�0
���a�	5(l\	��4ePg�
9���Pgxl�r�sH
>�.�i0�C<H�5�fD���e�i�s�ty$�nH
4
����id/����i���M|��t��� sl�
���@a`ehrpu(P
 TL Ph ��xa�e�i�k�o�s�u�yx!5D"5�"�v, V�"5�
#�i�#P�=5p,%���a�#��p�\4&cy�%P l�%��,aXkdulv|'h\'��Prh:P�(l�(M�,
�,��|e�i�o�P�-
x.5�f�.���a�b�e�g�i�o`#q�	vT/50{05�0P�2P3��a0c8i@kHoPp`t�P45x4P<5P�5��6�$6��Xepi�8�|7��7��xa�e�i�o�r�u\?�D85�85�nHqP95�vd,M�9
,:5L<
\<���aep?ø<5�>M�5dt��apdxe�i�j�noHrTsxt�u�w�y�?ì���\i\	�5�k�r�s�t��P���o�u�
3�|P���r�5�sx4
|���kl�%�(5g0r*�)��rL+P+��(a@ex+
�.��?�3hkHoptx4M$65�7Mh:P�s47q$6���r�;���tM�=5�s�>M����c�e	h,	i8	k@	y�5	r�
���o�P	.�H�����$	o��=
�P�	f�	n�	s��H	a�	b�	d�	e�
f�
gh�
i8j�
k�
l�
m�n�
o�
p r�s$tPudvly�?�`�(�((���	a8���	t�\	��5
d0
l\
r�
s�
M���P
mh��$
e����<
i`5D
r��P
et
ihH�5l
g3���kD�5�
e�
l��M�h �#�(P�
p�
v�*Hd,
�,�/��/���
tT/��tDv�.��eXixo�u�?������<e��0��PfhvD�4,{�0��ps�2
8
�2���n3���a�i�k�m�?t�uv@4
4���npcx4���i�4<7lT7�0=
\7��i|7P�7��a�e@o�rHuP9M,:Mh:5\bT\<�=P@
���td
f
g
k
l0
n8
p@
t��|aP
bp
c�
d,e<fhgth�i�j�khlm`n�o�prt�u�v�xt@�`P�
HP�5(
ax8P�
�Ph P��H
l`
sT�,	5���h
i�
H	P|
r\	���
a�
d�
o�
r s�	��
l�	��
e0
T�
x.
 ���
a�
eiPX0�X���
n47H�?��r���t��
���4fPrXt�.P���PD��`u�HP���|n�s|M�P�ll���ehq�5���	�a`e�l�ohrs8u@v@	y�P��,
8��a$e0p��35m@� P, 5� �L ��Hd
k�r�sh ��Pa�e�i�o�s�uly@ôPcx!P�k�m�H�!�D"5�m(��"P�2
#�a�#
@H�#5�d�#��a4p@s`-q%P e%��(lP%Xq�%PHk|n�%��Pa�n�o�s�u8H�$��'�'Hoh:
h*
�(���l�p�r�v�*P+5d,P��,���i�r�-5x.PHfPgXk�.��a`e�h�i�k�o�s�t�uy,@�`#��HHHT/5�fxg��h��pe��t�v05�bT��0l)M�0���d�
vX1P2
�1���r�#��2���mn�2
�2
|75�7��	aDediljto|r�u�y@@�D85Xk�p8��Ps�85lPP95�9P,:P\:5�#Ph:���m�n�r�%P�;�L<5\<���a�ei`@ø<P�r�
��<���f0=5�5pc�g��a�	b�d�e�
f�
gth�i�
kH
l�o�
p�s�t0udvly�@�� he����|s\	�5�j�)�l('�5�(5�r�/5x+���n+���e�i�+�7a�edio$vP9
0={D:��ih:5�5�r��8a�	b�d�ef�
gth$iTj\k�
l�
mdo|p�r�s�t�u�v�y�@��1�����t�5�nrs`��	5�n<&�e`���dX0P�5�n���i3�58bHs�@�T��P|��@tl
��(Ptm�
v�*q�,�.5�23��
�a�d�eh l4o@phtxv�@��!H$
P�n0
���e�	5�l�3�e�3�)�3aL {�4��a+P<5��,r�5��	
�8�He�8��Pd$6��\ir\7�7��a�edio|r�s�9�:Ph:���d�n�%T\<�=5�d�=
���	.���a(eTi�o�t�u�v �
��s���d<sDt3MH�����LehnpsH$|�
T*��xo�(���k�v�<
d,���e�7�%)�dh:���n<&�l=�\<���o����aTbtc�d�e�fhghijk�l(mHn�o(pXr|s�t<uDv(A�hlx!Hh ��`e� P\	��|r�s�M����kh5����l�n�r�t`5P�����.�.���r�MlM���	�a<eDlxoTrts�t8u�v(5�T/P��Ledi0
`M8��lt,:��u, � �L ���gh ���a�e�ik�ouhPx!���g�j�l�
�!
D"Pd�q�"�#M�%P�#�� u$<&4t�%��<dhfxg�s�tdu�
9�','��ps�'�v\7�8'�5�r((���el)P�(���d�g�k�l�nrt�)PT*
h*P�*)s�'++D,�
�,�� iDlPrD"�H-��<i�-
�.��Lelitt0P�1M3��0c�i�m�p�t�u4P�5�6 �6���r$6���epi�9<7���a�7��a�edior�u4y�?�P9P�9P(e�{�9�� .\:Ph:\<���aei�P���Xg��`a�d�ek(l|r�uD
HP
5�r\
���e�	5�r\	���e�s�'P9��o����t����kn�&`��.�Mx!Ph �� e<ihmD"PH!Ttih��Dd8$5Pl�"��\e<!�.�eT/T�;h:���s�{����p�uv���ab(ehpi�
k�l�n�or8s�t u, v4A��{M�hh��s���l<s\t3��@kLt$6
h��Ta����heHsH{L ���kh ���a�e�u�#�l�%���yl){�(���d�n�rv�*c+��(a�t�1�d,x.c�.��a$y�2{�2��0
n3��,a8iXk@p`txvx4$6��Xepu,:{\7M�7�a�e�i�r�s�H|7���r�,�8���hD8���r�8��8���e�9
�9���e�9hh:���r s t�;c<�l=\<��$ o< ud�{���D b� d� g� m� t��L a�	bH!dx!e�!f$"ghD"i8j�"k�
l�"m�"o�"p�r#s@#t�#u�#v�A� @��� r�2���� d� r�{�c4,T�'5!s�%��!o���!n�$!g�
50!a\	��<!i|rd!s�?P���\!t�c���p!.�!d�!l�!m�!n�!r�!s�!u�
{�8h���!e�$���!o`�c3c�H"iH)"d�5�!n<>dBD��"o8h��0"a���8"g`"n|"o�FH��hAgX"st"t�L�M���<e�"oly�5�%
�#���"u�)l)���"u�(���"d�p�"r�"v+qd,��,�"s�-3 #e�i,#j�3'�35#sl4
|75`#f�7��4#ah#e�#i�#o|r�u`HD85|#r�
R�8��t#f�8PP9l;Mh:���#l�<P\<���#e�#i|A�0=P�5���#a�	b0$d8$ef�
ghL$i\k�
l�$m�$n�$o%p4%rP%s|%t�%uly�A�\	�5D$j�q�5d$kt$n$	o�$sx{H��l$g\:{����$y|���$t�#�$e`T8$5�$n�%�(P�$d�
p�$vH	l)���$ad,q�,5�,���$e�i%l$%o,%rH-P�-5�-P�.�3M�35<%s3��D%e�i�k�pp%txv$6��Xe�7��a�%e�#i|r�A�D8P�:5h:���%d�%ll;c�i�PXk4&l���%a�	btc<&dp&e�
f,'g8'h@'i\'k�'l�
m�$n�'o�'p�r�'s((t�(vly�(z�AÀc\	|rL&s����i`&kh&p�
�5X�5�&a�&d�&m�&o�&r's|M�
�
���&a`)�&t8$5�&n���&e�\((���&e�)�����&k'l�
9�4
3��'lLtdD��$'o��5T'm|"oHs(H���<e�"o|'r8u�'v�A�P, 
h �(5�'r`+�+���'d�,M3�i�'k�'p(t cx4���'u�-P�5)�'oP6c$6���'a(i�8{|75\(l�7��	(a�eh(io�(r�(s�u4y�?��T(e�85�(a�8��8��t(i�8|(l�95 .h�9�(t�(u:\<�>�8)s���(aL)btcl)dXe�)f�)g *h(*i�jT*kh*l�*m�*n�*o�*p+r4,sD,tP,ud,v(B�h ��<i��@)lHH�
5X)n\	��`)i�)r�)s�)uX
 ���)i�M�%
P5�)n�.M���)r�5�k�)lD���)e�)o*r*shHdhT/
�5*e����k�M����n@*s�3`|��8*e�P���L*a<e8uh ��xa e�i�o�u@	yB�4%v�#���*r\'�%���*kT*T�(���*k,r�
v�,���i�*l�*r�*s+tH-�-�-|7�(.+a�.��		.L+a`+dx+e�+i,k,o,sB�85x.��D+n�d$/��X+np+s8/jT/���.�+g�+k�+r�+s�+th�H/���/m�/�0P�+e�+m�+n((r�5�+t�
�+n@0
X0q�0�0M�4PX1��,l,,t�1P3���iHo�7�eh:���)n�=�\<��\,s�P�,g�,n��p,a0$d�,ef4-hH-l�-m�-n�-o�-p�-r�-s(.t0.uT.v\.y\B�tz����,h�'>8���,lp
H����,c�!n-r<s,-uc$-aP��hL c|nh ��<-a`-ep!~x!��X-.t-r|-s�!~�!~�#�%T*{�(���-k�-r�-tx+8+���-eD,m�,��0�-c�.���-o<B�t�3��@k.o.p .t<5
�5�$6P�7h:5bH.l��l;��@.e\<M�=�h:)��d.u���`#f�.i/r��x.a�	b$/dT/e�
f0gth0i�0k�
l�0m�$n�0o|p1rX1s�1t�2u�2v�2y�B� 
,���.s������.b�qH	P/r\	��/a|r8/s��V���@/s���H/kp/l�/n�/s�/thc�a���|/s`���/s�3�3���/e�/p�/s .t�/u�-{�5)�/o��<7�:����/t�5D���/u����
e,0l@0mX0n�
���$0a�$8(��80oxHt0sH��L0g�0p�0t��p'l0e�0o,�4���0r�'��V���L*a<e�0i�0s8utB�p58��Ho�$��#���0o�"uL)�(���0b1p,r�*�.01eD1o�/MT/��(1s�*$�0��<1n�253��P1a8i�1k�1n�1p�1t�1u�1v{x4���1r�1v, �5��5�$6��pu<7
\7P�{|7���1l�7���1a2edi(2oP2r�2s�2y@@�D852l��h��2iP9P42r89q�{|9<2t�9��D2ah2ep2ix2o�9P0{�9{�9\:
h:���d�2n�2t<&�$a�%���2d<{L<P\<���2ae�#i|AØ>��=���2s���
ft3m�3p�3r� t���2a�3b�3d�3e�
f�3g�3h4il4jx4k�4l�4m5n<5o�5p6r�s$6t<7u\7vh7yPC�c�3a�#�P�5\	�3yl��5�3d�3r�3s�
��3�3���3eD��hT�3l���4b04g@4nL4sT4t\4uhc�{H��84t|{�{<9�
l��d4u���	.�4a�4e�l�4s�4y�Bä ����4b(c8��={h ���e�i�4o�4u@	yC��"�#{�#�yHV@'55n,5t�%��5iC��HT*M�(��45kX5lh5m�5n�5ph*{���*��`5.�5m�5s�5t�$�P%{|%��*��*��,)�5e�5i�5l6r�,��,���5c�5r-~��H-5�r�)���5.�-�5g�-��6o�.��7��	.P6a�6e7j 7o47r,C��|7��H6ml6nt6s|6t�6v8 
��� � D8��	�6.�6d�6l�6m�6n�6p�6r7s7t48 h��$����6o` �����8���6.3��l{P9�,7m�*
�9ch:���%dT7l�n�rl;�\<���#e�=�t7s�>v��� .�7g8n��|7a8bD8e�8f�8gth�8i9k9l�
m�$nP9ot9p�9r�9s:t,:uD:v\:y�cL&<&�7s8���7d8vl�0
��	5 8l�
��(8e`8s���48d�+gp8k|8o�8r T�����h8n�'�0����8m�oD�5 s����8i��8l����8a�8d�8eHs�C������8n�h $9i|$D"��9sL+
+��09a\9ed9il9m�(��89rx+5�+P�0��,x.�.��|9a�9e�9o�9y�Cä/VT/���9s�0���v�2c3�i�9p .t:u�@è5)�a�9r6
T7<7���9l�7�:�:��:sh:�� :d<:r�;{\<���e|A�d>c�=��T:p��
�a�:d�:e;g$;i4;kl;l�;p�;q�;r�;s<t<<v\	��'l�:r�:s�:v M�d���t����:i�5�:rD���:e;s;u��
��
���,;lL;t��D;a\;r�L 5h ��d;a�e@	y�CÀ,��|e�;lH-Ml.Mx.P�.���;a�;e�oT/P�;g�!rh��2M3���;a�i<k.oxv�4{x4���;a�7���edito,<r4<s�9M�9�\<+@M���D<d�<r��L<a�<e *h0=ijkH=ll=o�'px=r�=sxtdu�C��0�����<m�
c����<d�<l�<rs=t�hh���<exH`5�<g���<e{`"��P=n���=i���$=l@=s|ch ) e\=id=sD"
#�(5tm�.��*e3�� .t�1u���
�=a�=d,e�=k8>l�"mX>ntod>p�>r�>s�>t�>v 
\	���=r���`e>i>l >o(>s0>vp
���<i�P8$, Ph ��\=iH>o�"
�'>�%��P>s�,���$e�iT/P�>k�.��t>eliH/P3���i�7���#i�>r�9\<���ep?����|"o���>i�CPC���>�3���>�l�D��?i(D��?r`D��?�H?�x=0F��4?rlE��<?vlE�`D��T?��C{�C��h?�`E�B��|?d�B���?�(D{`D���?�0F�B���?v�B���?�<C,C���?�$6���?�(D�`D���?�T?��A���?�@��A��B�8@n�B�� @��B&`D���?�P@�lE{�Cc�C��X@�(DP`D���@�l@�<F
`D���?��@�lE��@r�/q�5�@nF���@e A�(A���@�lCHPC���@��@��C{`D��A��?�A�lE%<F�lEP`D�� A�`D��A�T?�@'5�@����DAi�5PAnx��\Ae�C��C��tA�3�lE���As`D���A�<F{`D���A�(Dc`D��A��A�T?�4A���?�(D�Bb`D���A�P@�D&�A���?��BP�B��B�`D���@��B��B��4B�\	�(D��HBd`D��lB�PB�<F4A���?��D{(D���Bl`D���B��B��B��EVlE���Bn�BrF���<F���Bd�Br`F{AH4A���B�<F��A���B��AH�A��C�lE�`D��<C�$C�<F (DcdCn`D���C�DC�lC��D&lEcxCk�E{`F$<F���Cr A(A���C��D���5.�B���Ck�B���C��A���?�$/��D���CdDk(D���Cr`D���A��C��0*h ��Dl��Db c,e|Dgi�Dk�Dl�Dnto�DrDEs\7v��<F�(D�lE�8\D��tDa�De�Dr�Ds�5�k�h�T����'vx!)�
�Deh ���Dl|P<&�Dr�%���Dd�.���eEgli$Em<Es0���Dr�0���3a4Eo�0�X1M3���iHop%t�	\	��XEe��	`Ed,e�Ej�Ek�El�Em�EnFr0Fv�5l���Ee���`eh �� eP%T�#���Es(P�'�Et�%���Es�Et((��.���eli$Fn,op&1�$Fe\<��e���dXe *hDl`Fr�
s�Ft�.��LexFs�1MX1��pFt�713304105001040013010531003451455030100100501054105014400050300540000300100100045034104500101050003304300010114104404300501004340005100450410540300300003050403301034004100300000300145000040104030430104500000430540100440401330341000045000400105001040300405301454304300045010504150650304105330303000000510410004541001044503410104350014400145405041040434040050304405040100540010400414543001034000540005003004341063035005030530350500000100043001100000103PK
!<����|�|�hyphenation/hyph_de-1901.hyfHyf0tP�����'-4�������$�'–’-111001�{��������.p!a\pb�c�d<�e`f�g�:h$fi�j��k�lp�m�n6o�cpTzq�zr��s��th%u�PvPXw�cx�gy�lz�c��"�"xaDlLop!���btc�g�i�k�ltm�n�p�r\s|t�ux���a�b�c�d�e�fDg�hXi,jdk�l�mn�o,p�r�sLt4u�v�w� x� y!z,Ä$$%\ldr���)@&��lk�0�.�n�r�u�0�1�4�3���s�6�6!�a�5���t�9%x9)�r�7���be(lHt8:�<-�<n�<!e�>2�>��4i�>!<eXsX?7`h;B)`e@��hp�tP>dB!�s<DB�e�rC���d�g,sltx�Fh
���k�D�n(�J�D;�iFNlslR�Fi�FW$t�6[I_DiPp�2�)<.�I;D�c�I��Xg�I)`a|hHJhwl�K�s@P)P�e�L���i�k�ms$t�P�P)�a<�p���c�Q!�a�Ru�R!�c�pSyiS!e@�}��)0t�V8s|V)DeLT��Pslt,W�X�HXta�h�YH]�|\�d�f�s�]��s�.N�_��c�t`�`�h ��a��s�`�e�b��g�dc��mDtPu�c���|�Tm��l��<s,n�l���u��Xb�uJ`r�t��le�r\p��xeoHu@yhTy���a�e�g�yyz��bz!�a�r$�����=�x=)�eЂ���gs�T���k�����$r(�u,elsD���8s�C���Tn�rȌ)\e<��C�x.t������hl�������b�rt����aDe�io\Ü���i�m�P2P��n��F�8��.(sH�)e�Y�H�uH�)0l���8a`ixo�r�s��ln���<.�l������n,����e�m��[�ș��a�������k8���n�����e�Fu��u�o���m(p<r���� eT�
���4f������Hbܟ��P����@���hr��pe<���|b�h�i�	k�	l�	m�	nD
p�
rsTt`u�v�x8B���i$����ex�T����e	nl	s�	t��!����	a8	bX	e<D��$	s�,	uL�T�%D	g`��L	n��*��d	p|	t��)H�/��u�	r��x��	e��u��)�	i�����	b��Bl��	m��
d,
n4
t��p
.��	r��)
e$
r<3\�d"N��9�����<
i4�=@�EP
tL���X
h��d
c�%p
e��)|
r�����
b�
def$i,s�zD��
n�
rL�)�
a�
e�2��2D��h��
ir��2���c�����J�����N\t�N�8np�U@itrx�Le�r�Z���lb�a�!�.�e��u�t���tt�/�i�jT����.ԗ���n0�uD����nȴ)�e����p s,t�zx�)t@�<eDh��x�����Ls|��pgxt��P�(��r�e������t\	����s�����`���et
i�
ls$u�ô
�	���i$
rD
s��J�J
o4��
n8
�
0
a,
8
t842�P
.�
nd��X
e���h
l�
s�}2���b�
.�
ns�by�
e b�
s%�
s����
����
�8%�P����
.,��H��Au4�c���s�!�t��0t���8a`d�e r@u�"`&�D&hhr&��pa�eD#��|n�r�s�t�u�&|'y�'���a�e�'y�(�d)��)�8u�Du�su)�e�2u�f�2f1;i:��9u,sL8��4s<�<!Ls<��Tt�:��`f�l�u�:��la�eDol>�T<���s<?��>�tT@�f�r�A�pA�e���D�nE���aiF�nF��E��n$��4S$tDSu,e�Q��8m�e�$f��Pa�m�n�rs��g���ta�m��J���e(�����e�k�n�u���4�JĆ��!�e����������eĎ��a���l���r��� oȞ���8l����@b�i�m�t����La�e i@l`n�o�s�uP���lT����p�
��Yp�o��)�i2�$���md"!�u����n$����nr
70���s(�����e���u,iL�4e���LeX��Ti0������lf���tp����������s�JH�)�r�����a����e�i4o�u��`t�@����fv��<.И)�e(+ph>��n\�)i����(gXv\S�x��Drl��Le�b7 bds����ls����x���|��r�u�t ����s\������d�il(sp����a4dDe�i�m@�l�����a|Wlr����t�����X���<e`l�n������Xa,���ll�w��th���cp����s�t�(��P��l����t�������cD���sp�������i0otu�����Le�$�<$��(bHcPs`t@���%l& &)Xh�3�01��ll�s�2%�7p7�a�e6���b�h�mp,r�s�z�7��W\@u�s�>���rhE8E���a,j)�Kr,K��eLM�L��$a<t$RNPs̰���JHe`t�+�;/8V��hgV��pl�U|a�e�r�S�t�V5�n�r0�:�e<����d�?�V_pV���e�V��\)X\�o\fCf��ePt�cr�c�� a\fdh�o�s8SHg!Hhk)�l)lA�8r)la�qtk�s�s�sL�r�s�tw���74�B�s�z���s�z���ae�i�o\u �D����s���b8cXiplL�%,�E$a<���,hT��4�PDt����Lnl�U|e���di��Z�h_���s�����a�cD]d�lC�e@��t(�u�h�5|����a�m��H��u�apb)��c���s�����p���/h�0sX�)8eH�uDs���Ps̳����hb�c�s�IgJ���r�m�e���k,��8����s��Up����i\����ls����
�alc�e�h�ik<p�tuP�Ժ,sDqP)$eT��\s���8s���D�(lv8�̼��dh��zH���xsT����n�r�t\�x����u���������a�������p�����m�t�u��u�e(�����e���i��)��(s��0a�id��g����Ps���X��n��npiouxeo�lo�e �E�g����ed�)�o�������e|����r )��(P�rD��e0�)h����c������$e����,l�n�t����8a�e�h�ito�r�su����tl����|kh�u�)�o4��p����e�f�n`���T����o\��	�	���ad$m8sHt�
�p�<.0s���e�@��
�D�L��@e�H��HpTn���\i���hn�p�w�L����l�[����s���s�;�a�i��!0��u�s��e@"�!���i���r�-�Trd-��eh%��(f|l�m�n�rPt�-�T52`5\il5)de�3pmP6��o���8��8��a�d�e�g�i@9�0:��:7x;U;�t>�i(o@s�?n@��?��s��y0@ m8p\�d@�pE8EHa`r0G��I)I��he$I��pb Sv�R���e�Q�n�P���e�o�V��V���r�Y�XY���lX�h sPX���a$ e\ it o�Z0\�\ s`[�� gH i��J�\u4 a@\< t�_�_��T e�a� tda��h r�R��R��� nb!� etd��c��� e`h�g��� l�g��� a��8pu� s�o��� t\o��� i�l��� e!iu�t��!e<!n$�J�u�(!tpu��0!sp!u�!b�!c"g"n"rP"sd"tp"u��H!a�"b@&c�)d,e�,f�.g�1h�3il5j�5k�7l@mCn|Ko�Kp�Lq�LrLTsHXt|\u8bv|bw�bx�by0cz�"Ü"��!eH#u@&�0/�."rC��Lz0"a8"f@"tDM)�O�SN,W�LT��H"t�Z�HX)\"s|\ydc�c��4X�x"��)��K�0b�\p�"a8#dH#e $f4$g<$hD$i|$k�$l%m%n$%oP%r�%s�%t&u &w(&y0&z #�p�#u�]P�s#ft0#u,t��#�&�,n��t��u��t��	@#bt#e�#i�#l�#r�#s$t$w$��u!�v�Hv��|#lw��#a0wuTy��#k�#r�#z������ȴ�|�|)�#e|{�#s�#t`|�P����#��|�|},$i�X}�}$�}�`$nl$rt$t��)�~��X$s0�P�.X���$a�$e�$i�$o�$u�$Ä�3�$g��9|�>L����$��$�؀EP
.���J@�Kd����R���$s��)%a��WЂ�8%.@%fH%n��h�_��\\��`%ap%È�%ԅb���h%��%�H�h��T�!�%a�%c�%i�%p�%t�%z�o�yP�up�{�%a���h������%i��!�%eD��&r���l�������������	8&al&c|&e�&h)i)k�)l�)o�)uX��l�����t&m<�t��&.�&a�&b'el'ft'i|'l�'m�'n�'o�'r�'s�'t�(u�(v�(w�'���&l�&uT<��>y$��\�P'c 'i0'rA�����E�4���('fH'kd'wX'�PF��F��F��P'���ؓ���P���Ĕ�̔��P�'.�'b�'r����T��T������'��(�`�\h����'uЕ�(e((oP(r��^��(gD]��(r`l�`��`0(m\`��8(ut`;D(a�(i�(����6d(.�(n�n�l(e�|(m`��(ua���(�8%�6u�(.���`J�(t�P�(fؕ��$�,�!L�)n���d�m).<)eT)mp)s�u���4)n�'`)s��)H)u�+,�uԗ)h)e�)l�)t����/ȗ'�)a�4T��)nH��T����)nl���(*a�*c�*d�*eX+f`+hh+i�+j�+o�+p�+q�+r�+s,t,u�*�84�����	 *.T*b\*gd*il*m�*n�*p�*r�*vl��h�)��80�x*a4@=���t�B��H8bM��3ܟ���*���)���).8a�*f+i+n +r8+st�S���*i����*n��x�,���+a0+i|�X��^H+pP+s|�d��i�mp�r��p|+e8���t+n��:���+b�)P�wT����|`��+c�+sX�;�+e�+u��p�سr�+z�h�����<��D,bL,dT,i\,kl,l�,o�,p�,r�,t�,w�,x����T�x�)�������d,a|,eD���������������,o�)l)��-k-n-r$-t,-u`��
�,a4-e�-f�-i�-l�-r.s4.t�.u�-�����h!���	�L-c\-np-r|-x<
�,����T-t��4��h-l�
��B�-u�
)�-l2����`�����-�����-a�-e�-�% ���-�.�0�%�yH!.a$.p��L)!,.aT.ex.r�.u�����L.il.r�����d.l0�.e�d�T���.r���.r��.a(/dT/eX0gx0i�0l�0n�0r1sh1t�1ut�/b/d/r/u$�ph �!�"u�"u /i</rD/u$#<#)$$D#L/it/n�/r�/sD&!&%l/a�/e�/t�&��&�/b8'��/u�0��'u�/a��z|'��/l�(��/a0e0i0p0sD0tT�u)2)�/l )�()wD)�(0e������U00mP)�80e�)��)!P0lh0r�)��+\x*��p0t�+_�0a�0��+�����,���0��.�0e�0u/P
.�/��1�1;�0e�0i�0o�2�3J�4�$1mX4J1a,1c41pP1t52H56�@1oX6�l72�6H1i`1r�7�8Ct1h\)@9�49�|1dL8�1n�:��8%.�1a�1e2i,2l�2n�2o(3r�3s�3t�3u�3w�3y3ð:��1tX>!T@�2r���E���1h�G2n8I��J�JJ$2aL2e�2sx2Ü���KD2id2r����K%\2hXM_tK��p2��M!�+z�O!�Ou�2a�2e�2t�2�D�p���2��P4�P���2eP�2rDQl�S��Q���2r3s�S�T��?��3�@U�Tu 3a@3eT3id3t�UyL3s�V��V�X)�X!\3a|3r�Xp�X;t3i�Y�,a!8\!�3sbe)leDh�e�3n$f��
�3a4d 4e(4f04g@4kl4l�4n�4s<5v�mm4t�l4s���m�`t�8v�%y)84.T4e\4uzp�{0_|��d4o������x4d�4e�4g�4s�����h*�����4p���4a�4c5e5o5sĎu0�@��4.��4h��Бl(5e45t,�%8� 5n��И)84.T5e\5ld5s�[pV"�W���5e���Cx5ơ�5th����5k����8%.�5a86b@6cH6dP6e�6i�6l�6o�6r�6s�6tl7u�7z�7��'���5d6l6m 6r(6t06z���T�������!0c-����3�w$��d6ft6n�6uh�d"����l6n8�����L���6iD�J�����6a�6o���@����6u��8����6h��h���H�!�6a7b7e87iD7r`7s��>�(7i0���D����07kX�P7i�K��P��BX7t���x7nP�Wh�\<����7������ 8.08c<8fD8gX8l`8m�8n�8r�8s9t9u����7ax9b:d8:e�;f�;g�;i\<k�<l�=m�=o@>pl>s�>t�?u�?y�?z89�P=b�&h4���(8h�����P8eT/lx�R��p8ix8p��q�� �l0����8a�8g�8s�8z��� �v\������8.�8e�8m�8r8%\ؼ|�QC\R�L�9i9s������0���$9g(��|�PH9m����,9�>��gP�v%Hv��P9n�9s����X9i�9rl�)h9e�9l�9o�9r�9s�vR���Ty���9h�9w���l�9i,j��>�Ђ���9hl�/�9u���7x������:���!(:r0:u:�<������p:bx:f�:h�:i�:l�:n�:p�:r4;s@;t`;ut;x\����d������:n��\D������:d�:gp���)�:sh������:o ���������:f�1h;l;m;t$;z���X���������,�,;k��P;aX;h��P������l;r��b �\��<�)|;rx*�H�)�;i�;l�;o`-��+���;i�w�@���;e<m$<n�������;e`�u�;n�?r����;b��p��y<l����<a8<s@<v��)�����t�H<r@�)P<ax<o�<s���p<h<�B��������<b�<r�<uh�!�<a�<eD=gt=i�=od��p����2���<c�<n=r(=s���|����<d,����)=a����=fT�� "�8"��0=��J8=���r��P=.�=sl�uX=rx�h=e��r!�=.������=b���)�=b����=b�=g�=p>r���P�u\�)�=a�K�����=e<�����>c���$>s b�f4�,>.�)4>eP>hP����lX>l�!`>k�>u`��h���|>m�>nȷ#��������>k��!�>a�>e ?rX?s?���������>gX����>i�>r��%�>f���������?��Z��;?e4?iH?oL��@?c(��`��0ux�JP?e|?tT�b��h?���'p?� ��?f�?m�?r��(t���?b��������ls-x����?z�����?r(�)�?e�?w��lp���8%.4@ax@e�@iPAlpAmBp0BsdBt�Buh@���H@bP@dX@g���\�������(���`@��A�X�L).�@b�@n�@r�@s�@t�@w��3p����h����@a�@i�@u�X@����8<��@h$�ll�84.Ae Ar(As8At��%�������yP��0AaHAi���r<��$�XAc�Al��)`Aa�Ae�Ai�Al�Ao�AuH�>������AiL��h��An�ET�c��Ad�_���As�Ath���Dk��uBa�)Bf(BrXuK���PBc,�O<��<Bl,DBh��!\B.�Ba�Be�Br�Bu�B�8)d��$���B��B�l�x���Bl�Br�U����Bn�D�u��D	 *.|Cb�Cc�Cd�Ck�Cl�Cm�Cn�Cs����BaDb0Dc<DdPEe�EfFg(Gh0Gi�Gj�GkTHlpHm�Hn�HoIrIs�It�Ju�Jw�JzD�[lZh+a��CihR�;�����Cg�Ci$<2�;��CnT����	��fDP�Csp���C��H��J��� DuD�tHP)(Dh�)).dDa�De�Dr�Ds<Eu؛R���\Dc|Dr������tDt�J�
�Dc
�Ds�Dxԩ�̳�`���D�;�D�p�2$��Ds�)�DaEp4Eu��k���Ds(EôEa��q���� E���u��xHEr$��pEcxEe�Ei�Ek�Er�Et���~X�8%.�Efp��L@�����Eb����Eh`��Ei�Et�Eu�P�@!�Esdy�bl!
F.8FaHFe�Ff�Fh�Fi�Fl�Fo�Fr�Fs���#�8@Fb\FilFr`�����%dFf�Fl�Fw�Fz(���(Mȴ�����*�(���FeX�Fa�+����;�Fa�������F.GcGp\ohX6� �GoT��HGdpGe|Gm�Gn�����r!PG.���XGs�JdGr��0����Gs�f�)).�Ga�Ge�GlHn HrLHt  ���Gn��� ���Gi� �H�������G����<!�Ho�!;0Ha@H�@���������8H��!�8"���R�\Hs@"JdHa�Hu�xd"C�Ha�He�Hä"��"��"���H�$#y�$�<$�Hd�Hl�Hr�Hs�$� %��%�&P'�$(8(��I��'�DIcTIelIhtIk�Ip�It�IzI�X( )u�(LIndIu����)�L*E�Ir���*h*)�Ia�Ir�*�t+U(+�Ie�,y�,)).�IaJeHJh`JotJr�JwJÐ-�,-���Ir�o�-��J�T.�-��Ji4Jn�x�`.�,Jn�.��.@JeXJr����/lJn�/�0��Ji�Jo�P0�Jn\0�4#01��Js�2Z3P`3��Jed_�3���Jt�n�3)�JbKe0Kg@KiLKshKw\K��ot4��Ki(Ks�sHtE5�4��8KnPwzT4��TK��5"tKiH{�6u�Ki�Kp�Kr�Ks�Kt�@J,K�L�S��WB84.�\�c8%.LaLe4LfhLh�Ll�Ln�Lo�Lp�Lr�Lu(Läc`h�Lf$3d���g�� L�kuDLaLLlDku8l58m�@m��TL��lxLt\L� n��p(�q��s��q�Lt�t-,t)�Ll�LuxyXu3xy�Tzu�z��8%.DMaNb�Nc�Nd�Ne�Of�OgPhPi�Pk�Ql�QmRn$Ro\Rr�RsSt�Su�Sv�Sw�Sy�Sz�MÈzlM.tMb�Md�Ml�Mn�Mp�Mr�Mu�f%�{)�Mt��8�|�~��Mi�~>`������Mg�Ms�Mz��v�J��LȀ)�Mp܀X,��x�P�����M��S�؄�	).8NaTNelNitNl|Nr�Ns�Nt�Nu�bDNu#��������LNcdNnЅ��}x����D�Ne�����J����P�Nn܋�������;�No��)�Nr��
OaOf(Og0OhLOiXOk`On�Or�Ou�Ow����VȋOf���H��<Or4�(4�\����DOn����%tOsX�b`��lOe��y܏���Oa�Of�1h�Oi�OlH����X���H����)ؒ��Or���Oa���u��!�OlPn��)(�3���0Pa@PetPm�Pn�Pu��)����u8PnXPrL�����PPdlPgXGsP�h4��m�|PtX����Pi�Pt�����t���4��Pl�PmQrQu�)�Pa0QlDQoXQrdQsdBh u�Pt���T]��Qe��`���Qgl�$QaL���<Qr��ܝ;PQi$�JtQa�6h����|Qs��!�Qe,��8����Q�d�!�Qe�Qo�Qð�Mh����QkL��Qr������Qrh�EP��Qn�JRaRe�y|��<RbDRdLRpTRrd����\�����P��xRa�Re�Rh�|y����pRd���̩y�RňJ�ReČ��x|�!�Ra�Rc�Re�RhSiH�~��Rl(��RhP�[`y��x��yԭ!Se8ShDSiXSotSr�Ss�̯0Se�PSn0�D���\h���`Smx�;hSa�Se��������Sh�Smȱ�������D��X���)�Sa����n�H���S�ȴ)TeTtTw�S�D��ܵ�8�l�7lp���$Ta\�,Tl�Ts�Tu����8Ta�TcPUe|Uh�Ui�Uk�Ul�UnVo0Vp|Vs,Wt�Wu�WwXy�T�Ժ�TsD=P)�TeD�H�Ts��������T���̼�TaUhD��Uc8��Te$Ui4Ul@UmHUuA�����J,��,Uax��H��T��dUblUmtUx��8�y����c�Ua�Ui����J��������Ug���Un�Us4���Utx�J���,��Up��l�Uo���h�����Vf Vl(Vrd���������LVhTVi\VldVolVu��z�(\�����<�l|���)tVa�Ve�Vi�Vp�Vt���Vi�Vl�Vr��l�%��%�Vm���X�!�Va0�y$��p��WiWoWrWu���t�������d�$WadWe|Wr�WuPW�8��`���HW�������\WptWr���t�D�Wa�We�Wo�W����H����Wu������W�H�����L����Wl��\��Wm����!Xa�ZXXs�����Xl���@Xn����(Xe�����)
�Xa Yc(Ye�Yh�YiZmTZo�Zr�ZsX[t\u0\zY�H�������Xb�Xf�Xg�Xk�Xl�Xm�Xp�Xu�)D�hd��0�P�Xt���P�%Ԁ���Xf��Ys�E�����B�xZ���p�	F.TYb\YipYl|Yn�Yp�Yr�Ys�Yx������>gT��T���hYiT�b��*����YuL��\�Ya�Ye�Yl�		�	pZn,�8�B�Ya,
!�Ym����Yg"(Zu��� Zs�E2hE4Zn�u<Za���HZmdZrDbpZt��|���Za�Ze�Zo�Z�ԁ���Zt`�����Z�������Zm<uu�Za[c [e4[i<[p�� �[n�[h0u,[t���uL�<x[k�[n�[u!D[a�[e�[h�[r�[s�[u����8����[g���������[i������[���[�`����[��;�[�,p!����\np$L\kT\w�#��\r�#!$\e\\id\ol\tt\wXr&`sM�$)(5u�$B%lh%8%.�\a�\b]cH]dT]e�]f^g<^h\^i�Gj�^l�^m�^n<_ph_r�_s�atbubwbxbz�a�0%r4'��\l�'��'��\i]o�_`(z4]ht\h�)C ]a�((]t|*/�))@]r�*�D,bh]n�]r+�84.�+��]i�+��t]eDO,�+���]nH-u�]nd-���]a�]e^f^n^t����-�].�]h�]r�
��-�Qk<.m�3/!�].@/�0^e$���/(^h\1L^aT^u�: 2u,2ch^s�2)�4`4p^s�3x^e�^�6%,4���^�P62�^a�^o�^p�^s�6�46�^l�)�7B�7B�8�_a_e _i,_u4_z�8H0:yX�;_o01�/c\=�\_t�=2�=H_r�=)P_e>��1�Ap_h�_n�_u�@ux_a`c,`dL`eX`f``gh`lp`o�`p�`r�`s�`tpazda�849�Hy�_.t=��_e0A�_nD�>�Ax`hx�D�A`m$`o��b�JBPB��4`p�9w�A<`r4�V��J��\�Bu(�3�B�x`r|�b�Cl�`a�`e�h|���`ghCR<.�`n�Cp�C)�`e�C��`d0D�DD�`aae<aiParT�b�D�ac(ar�x�D�� am��~�D��4ae�DR�D;Hai�XA��\a���$E� '��xa�8E!�ae�ao�as�a�h�\,]���au8]���a���F��ag�E���an�ar$F��\2h�F�HGEXHr|c�H��H�(bw�H�$I��P)PbadbilbrtbshPH\btQ�,Up�W��W�PX)�bi�_��be�_�c�ba�be�bi d�lc�bmtd�e�e���bs�g��g���bacecs(ct|�P�hcu����kci<l�l)DcaLcoTcu�l�dvHxp!��\cadbddc�dd$ee@ef�egfh�fi�fjgk,gl�gm,hnTiohip�iq�irHks�lt,nu�ov�ox�oz�l�������o��o�dc���<������5��\��\�$I�\pde$dl,ds�t�X�BT��\�Ptde�dnt�4det'i|'l�dr�ds�du���Ddh�dkDA!��ds@"J|da����dm���`���Z�����dp�P�d�m�de��daeieres��uܪ���eaX�/س�<��B�
),el`��4ef\elderlestet��H�N�es`���ee�el�en�er�es�eyD#�L/i�+��.1�eaL1JX4�,1c�et�7��6�er�g��:�:��fa@feHfiXfl�fm�fn�fr�fs�ft�3u�3wT@�G�J�JJPfahfe�K|fb��U�tfe@N�Py�Ou�fe�TuT3i�Y�8\�$f���fm�fs�P���f.�4c�fk�����glgrL�B������$gaXgbpge�gl�gp�gs�gu��l�)Pgehgl�������)h�!xga����<Bl���gh�!�gc �,p������gnl��gep����gi�Alhmhs ht$�J��)�ga���x!he���	).`hdxhe�hf�hg�hi�hkin is
��)Xhephr/x�hn�#`�8�l!�he�hl�hr�hsXu�/���he�� �)�he�hlir� B�!/$#�d"!ie�'�0ic@ieX())Z�(8ihG6��Lin�c�c��`ia�ip�is�t,t)|il�ir u%̼)w)�icTzP�zB�if�z���ia jc(je\jgtji�jk�jm�jo�jskt4kzj��|Jx�����j��j��S��)��8ji@jn�����Lje����/��!TjrX����Pt��hjnl�B�)�jl��p����jtL��jn�jsd�!�je0FZ|���PP���jb|�!�jeki|���u��ԭ!ke$kh,ks̯)���ȴ�@kw8�����	tkc|ke�ki�kkll lp(lsllt�lw̼LT�L�kg�kr�kt|�R�J���ki�knx����ke�ki �����������,���kp��l�kolr��lf0���/��N��N��<lcDledlt�����Xlr�������Plkp��d��|le�lr��Ut���!���le�c���l�����#k��P�lr��l����
�la�le(mh0mi8moLmrTms�mt�munzp�mi mn��)mn��T�\��	�Dmb���u�Za�mc�mi�ml<[p�mt�m���xm�O��ml�mr��mh�(��)��B�J�me�mi �T2�/!�mr�%��#!nw�'/4')nrh%��
 nbhncxnd�ne�nf�ng�nl�nm�nn�nros�ot�o�`()0*[�))pne�*�*)�nld-u�ne�-@/��nl007�3�P6��na�ns46u�7��8�_e,_u>��@)(o.Toc`oehogpokxon�op�os�|�� ���0o��A`m8oØADoh�A���`B"h���B��Cy�oc���$E� '���o�0G/8E)�or�P��cP�l�����ot����on�$l�"pl�psp!��pb�pc qd,qfHqh\qipqk�qlhrmxrnsr�ss�suty��paXtb�tc�td�te}fX}g�}h�}i�jP�kX�l��m��nЂo4�pT�q\�rT�s��tD�ud�vl�w��y��z,tÐ%�(��'��pe�&�pt@&���phqkp)J)mqs�+��)qr�,�\Qu�2u4qu�1��<qn�4��3��Tqs�5u�5��hqa�qe�qi�ql�qr0��P6��qr�6��6��6��qa�6��7%�qa�ql\rt�7��������q��<!reHri�q�\���<rb$rh4rrd�2�	=��,rg��ct=@rg@�h�>!Trh@��BC��	pra�rd�re�rg�rk�rl�rsstsz<D		�rr�D/PEF��G)�G)�ra�rl�rr�G	 H/TH	Iv�I��J�N	�L��sb@sdTselsn|szP��N)8seLO��N�Lsidsn`O�R7T�S)tswLT%�sa�sc�st8Ty�Tu|W	,W�sr^3|\�sg�ss`BK�_u�sk�sp�`��j�b���sodd\@thdc��tcHtrPts�c��t�����DdL�i�Hk$	\p!tte�tl�tr,ds�tu�t�tp���%؀u�trX��te�t�@��L����t��)	��tc\�;�tuD���)�<���84.Tua�ub�uc�ud�ue$vf,vgHviwkwl�wm�wn8yoTyr|{s�|t�|u�|w�|x}z��hunpurxusм�<����ue@��P�P�uh��LH����ul���ue�ui|�'$�8�J�uh�urvt�#�4����ukvl(����$�vat�.	��r3	x�4vrT���	<vetvf�vg�vk�vl�vm�vn�vs�vt��J�<.��)|vet�J��	�va��p�:	|����ve����vd�vh4����A	������u�vsx���%0waDwetwi�wl�ws�wt��%��(ws��%D�<wc\widwnlwt��������l������!|wa�wid� �!�+z@��l��wa�q�wn�)�we �%�ws����ws��9���\B.0xaPxddxepxgxxi�xn�xs�xt$yu0yz����)(xr0�R<���<xr��)Dxo�R��\xih�F	��yd"7��ul���xe�xp�xt�xu�*��)�xa�xr�*L	 �'�,u��Q	��!�xb�xiyrysyw(/�����X	��^	@2e	�yn`�B��)����ym�yn�ys����
@ya�yezg�zi�zm{n({o\{sp{u<{�$�����\�i	8����y.�yw���yi�yr�ysx��p�V����ykt�p	�v	�u�y.Ĕ���ynP�!zaXze�S|	lzs����zs<��(zo�(y4zh�(@zc<�Lzs�S	�S)dzeX�i	`���xz.����zn�zsP��	ԛ)�zs��	�z.�zn�)�ze�%�zs���zsX�!�za�9��	P���J{a{t@��4������ {pd�'����4{����	x�H{a��!P{tl��	@��h{m��{a�{e�{l�{p|s`|t�|z�{�T���	����{s����{�x��T��{r������{o<�����{o��Lx�)|e,|t8|z��	��$|.\�����	|�@|n��UH|i@�T|ex|o��4���p|l�|rL�b<���)�|e��|a�|hT��	$����|p4����|a\|����|rl����`�	 }aD}l��}l/P}s���,}���8}� b�	�lt}a�}e� ��}tt��h}sl!JD#[�}s�(��:��}u(dlb���}t$f�}a~b8~eL~kt~l�~no0rDs�t�z�f�e�}k�il~e\jd��s~rs ~t�m��,~sTyy)D~a\~ez28%.l~s�|%�~a�~l�~u�{�~ut|����	h~!�~uX�������~b�~e�~f�~gt���Ȅ�	�h��!�~lT����d nH�����،�	���(i����<e`otp�s�t�Pll��_h�*��Б)|c�i0�\����u��p��|���������8%.�a�e�uԓ����u�m�s�R\"�	ܙl�!D�e����C$�ơ,�th���8�k��J�8%.��a؀e��i�od�uL��|��	����|�b��d��n��tЀw@�0D�0�����c0��Ȁt���h���u	�a�b�g$�i<�mD�nT�sd�tl�uH��	\�(����	�����d4�n������	���	X��	,�L�e�%z���������	��)t�h��k@�|�c��e��ǵn؁s�tW�	`�_��X������āg(������zp�
��u�c�s@9z\�
 �s��=��)�e�bH b,�s��P4�s����@��|����
 ���\�nt�t���L��p�)��a��
���s����i����s�
�5�s6��Ăa�b@�cL�dT�eh�f��i��l��n�Kp�r��sĄt��x47!
p7%
(�l0�r�8l�8/p94@9u8�hx:\;�`�iT�u�;�t�e<A��@u|�s0��,B��n�Au��aG���'.ȃd܃e�s�G�ԃe�Gu�G_�I)
LM�L���a0�dP�ed�ip�t���M2�M�����N)�N)(�i@�rO/
�O�DO�H�i��cpP\�g$R�|�r0S0Tu�S��c��p�T�8HT�����e8X����n�W)��e؄h�s�X��Y�Yu�t0\u\�i�\��\���b$�f,�l�]\�^P�clH�a�d;�c@�gTz)�z;8%.��a��e��i؆o�u���f2�z����.��d��h��k��s�|T}2�}5
4�B�s̅t,W'x���u����ԅ�H�����	 *.$�aL�cX�f`�gp�i|�m��p��r��� �:
�C,�e,�E4�t<���@�hȋ����C
����h�ft��	��܏2l�I
��u��m��r����eȆgІo�u���c|���h�l��n�%H��	�z`(z��c �s<�tH�m,�tDD[h:�t��4�h��P
�s��!��a�c\�e�ill�oP�ph�sp�t��u�Ø��\���d��n��r�TsЇt��		\���dh��8����ȇz�%��r���܇�@��,��̼��a0�hT�u�u�V
�����n8�$�aH�e��]
D���@�f�)�T�84.��b��l��n��r�s��t�l����8.��e��lH���8.��t���x�̈f܈i�s,�M`�>���Ԉn8��������U���td�������f0�p8�r����p��\����H�l`�u<���Nd���a�e�i�o<�rȉ�0�������b��c��kl�����8��܉t`������,��x����\����n����r��d
�
\�����p|�%$�d��k
��H��t�;4�al�eX���q
���P���x
H�d�uT����b��~
h��
������n��!��a��et1h�r�s�Ð�Њl�s���|W�
��
؊r�����tp�����"�������4)��($�i`(u,�hh%��8�cl�et�f��l��m܋n�r(�s�*�d-�4�3|�i��l5�5!��at7�
P6����k�
�9��s@9)ċe�8�Ћd�?C>���g�i�t�?H����@N�sAu�@u �ap�c��e��i��pȌs�t�u��\�AP�a<Bl`m|�w�AX�h�A�
B�A��r���LB��n�Bl��MhC��s�C)��e��
�D��Ԍm�D�܌rDD�e�r�D�E)�I$I���cP�g\�s�K��Ku0�e�K8�lhKD�edO��P)PX)��e�\�`[��x�l�g�
��p��s�j��k��lBЍe܍u8p��o����t\o��čiHx�p!x�b��c��e��g��h��l��nЎp܎r�s �t,�y���aX�cx�d��el�ft�hL�i�fjd�k�lL�mT�o��pęq̙r�s�tl�u��y�oz@�Ü"��&m@&����h,��.��1�>7�7����tCJL�
�KȎe�L%lsn�r$��\R��i8T�
LT���alt�YHHX)�h�b�dc�P�s�c��4��d��Hk�uh�ep�h��t�4����o�P��ȏo<���
��cЏd܏f�i�k�n�r<�sH�t\�u��S��ert�T�x��P�g�uh�7����P$�e,�o��[�����4�h�T�a$��|�<P`�:<.�a$�b,�c4�d\�eؓf�g�h��i�Gj��k��lĔm̔n�o�+p`�r��sЕt�u$�v,�w4�y<�z���t;��i�:���b�k,�nl�o|�r��s��tD$�@<xD�P=��$�bH�c�rgX�sd�z0D�
�II�P�t�JD�=�8%�
�=��t�.��a��t�=�
��
>!��a>u�scTZX>)��oБu\��jX	�i��ؑml?���r�s�?������Г����?�
�o�@w�t�
$@�p��
�@<�n�@D�eT@��	P�b��f��i�l�n4�r��s��tȓxpA�
��.��e��s8%�
�Az�A���r<��
8Bb̒mlB(���
(CԒm��rC��ܒePC2y�
�D!�s$�w�C���t�D�D�E��,�aT�et�g��z�E%hEL�i0�<�`�b�ENh�eu�������e�F)��ix�����s�|��F��a@G<�dG��Gw�rH�G���a�n��r�8I�a<�dL�eX�f`�hX$sh�tp�vhI���$|I�D�s�I,�I3�I�Ĉ3��:J�x�uP�m�J���e��u�Ku��i����4�nNu@N)	�OA�e\�F|Pؔ.�P��i�P���eP��r�Q��Q���b0�fD�hT�rXR<�f<<��>XN��S��L�c�T/t�e��h��o�Ux��s�VM(�R��VhW����n�Y�ĕt[b[����l4[��a8\Cd-�b��ؕf�h�n�b�`]�c���f�tp<]�dE�d�e_le��epk)$f��D�c\�s����m��a�ep�i��l��n��o��rԗs�t��u��ì����n��r������l<��������l����̖e$���Ԗh��i�n�r\�s�Ԧ�����t0�b4�a<�eD�hL�n�#r��)���ܧ�����l�T�e������h�d��m��n����L�X��d��L�b4�;��oȲ'��r��)ȗt�H�!�e0�[�����m�p����0�e<�oD�u�����(�t��� �Pp��6��c��d��fȘi�k��l,�mT�nh�p|�r��s��tp9L@9u��hx:���i;<<J�;����f�@�Ԙt���Au,A)ܘe�Bu�Au�e�o�Dyx��<.8�s�F!�e8Eu �tL;�Hy@I!@�eG��H�n�K�,K��`�eLM��L��t�a��d��e�N�DOp�S�8Xu�W)��e�cuTz�z4ܙe�y����m<��L��st��e�����lki��!8�eD�i`�u4�hp���0�e�	P�o�
�!����X�rh%|�p��s\=�D'�C)��i�@u��s�gp!��84.d�al�b؛c`�fh�g��h��iԜl0�m��nt�p��r,�sH�t��u����aT�b��c��d�e�f�gp�h��i��jP�k��l�m�n�oP�pT�qX�rسs�t��u��v��w�y�zܟ�H!)�"���e��l��r���#x #������w�H#����n�$mP%��Ne̛À%sp%��ě�@&�8%.�h�&��&�a8�gD�oT�s<�z(zo�(y�h�(�c�G �s�w,�e�'�,Z��'�L�e�,�.�|�i��x0��t�o,27�1����l��o�2�3���n��s�4�4��7�r�7��Ȝa�b�t�ø8�x9B>R89������>�`ApA)�aP��@�� �m\�od�p��t�����H���[B���f��,���p�fD�x�rBu��edB�C��
8%.ԝa�c�d��g�k�rsH�tT�w\�z�B�h��<.0D)ܝe<D	F��eHF��G	0�a�G)�l8�o�rr|��h!)�J^	�I)@�w�J��J)).l�i@K��K���h��phL��LMDM��L��
��a̞bԞl�m��o�8r�s�t�u�wN��Q���u�Q!ܞa�i��)$R_�RCS��S_�S�|U�LT��$�h(Y�\�id�nHX)8�el�lt�m\Y%|Y���Z�T]�|\|�e��f��s�]��_���h���f\dc����h��m�n$�p4�r@�u�c����� ����� h��g���t`h�,h���d�g�h�hi\4k��i��,�z,nL�soh\pJh�u8��D���`�cx�s(�B�u��h��ot�L|�T���r�lԠa�h�o��������m����Ƞrp���<�H�ad�b��c��e��f��gȡh�iH�lأm�n�o �p,�r��sp�t��uԩxл)�)@�dxus\�t\�y���x�ep��@���p�nP�%�uh8�NX��t���f�����l<����$�����eܡo�r�4��T��c�m�n�_|�|4�������d(�sH������0�gx�u����8�a��e�f�lL�o��s��t���(���p�g�P��\��n�������p�����D���cТi�k�m4�����Ȣg�n��P
(����� ����)��m ��(�i��!�e8��<�24������0��������D�b`�ph�r�=���R�����@�x�n �!��a��e��o��p�����u��l@�78�u���ģrl�̣a�pL�����	8%.0�e�gT�h\�ix�k��sܤt�u��<���d��$�nH�r���d���@�gTr��u!p� d�i��)l�l������m��nl����eȤt����������u ���a\�h��!Ԥhyw����u�n�����o�����s���i����<.x�a��b��d��eh�h|�i��k̦m�n�o�s<�t��u<{È{��p�b��p�����)tNl`�L�)��b�������bܥc�d�i�r������ԥk�R$��iX��8�����s��(�b0�f8�o@�t �<�T�%d�+`^1n�H�h����P����\��,�:���t�e��nܚ-`�����f��A�����������ð�)��lX��8J�Ԧu��Jܦe���p4���R���t��!�a�������(�u��!0�ad�ex�r�Z��P�d����X�nh�Jx�;p�a@����h��mȱ�l�`T�)�c�g��m�n�	��a0�e\�ih�o|�p��s��tH�u��|�%�e�2x�H�P����n�������zT�(�hD�nL�tH�����\�,�T�n�t�r���rx��ej������t �����l̨mܨn@���a�e�i$�r�p�Ivl��Ԩt|����U�iLb�%��tԁ:H����td�;�a8�i@�o��X�q
��) ���uP�t��X�n�d�e��w���U|�����n��r��s$@�\����ltRh-P��s���ȩi�px8`J�l(�a4�eh�l�!l!�r� �tt���sD#8%.LzsP�tX)yd)H�a`�e��!�+�:l��ipJ�G��|�s$fܪa�c�e��g��k��lЫm�n`�o��p��rԬs�t|�v�z�f!�eԪb�d��m�))4h+pk�h�e�m$�d8�nh�st�tЏy�p��/b,p�,�eL�iT�s��Rpq��'.�7s`�c�s�	��h�s��(�v��s8v)��ey)D~a�9|����s������ȫb����ܫa�d�~f�h$�iD�nP�st���t���}���t���Ć0�r��!8�e��vp7)�)X�bx�n����p�s���P
.��n�����e��i̬l���ԛ�،��s����tp�r�t|����ԓu������a0�ed�rl�st�u��P��g�$�n@�r���d.lT�m\�s��d�\��)�uИ��)�N��eĭiЭmحs����r`�������a@��f���7p�B����ip7�D�aL�eT�l\�r6���bp�c|�f��l��m��nخo�p�rȯs�t�u�7��7P�8��8�p9t'i@9ud�h�;��CN�Au��l�E�hE��r8Eu��a�F�G����aЮi�H��J�KP,K���e��f�p�K\�L�LM��L���a<�cD�dT�f��g��i��p��tXN��N�,�)�O�L�a��l��rt��L��`���l���O%�%P��P_pP��e��r$R���aRu�S8%.ܯs�t,U��U�	�aV2�X��W)��h�Z�
0Z�n�\4�l<�rD�s�^��`\ bc�cB�z;��a`�e �h(�i�op�u,���f��z��|�.��d��h��iȰmذn�s�uL}�	T}����ml}�$������аd,W�4�B�t]�,���c̃�x����d@�hH�tP�u������T��̳��b��q
���84\�X�.��a��c`�g��hбi�m�n��p��r�s��)F.��sxu�`��	<�����kH��������ȱc�vИMt����\܏\�Si	���.�c��(����<.`�ah�cp�dx�e��f��k��l��n��sزt̲���(��L����z��go�L�z�c|2X���F.��ԛ)��c<�
D���IJ����
�u�

d�|����b�c$�d,�i4�m<�tD�uL�v��@�z���T����[�%`�s���p�:�h�b��c��h��n��t`(�
ȱ�:[��!��e8����gt��̳
��Pijb��)$�a��bдc�e̵h�i$�kH�lX�o��p�s�t��u�z����y\��bH�mT�np�s|�uP�
�%@�t\���V
P)\�iԺ��d�sD�#
����n������@����(
��-
D�����f8���e�i�r̼��h�4
����ܴnԿ;
��T���b �f(�i\�n|�r��t���d�%8�gH�n�(�?
��P@�s���H���T�gl�t���,��x���t�f��h��k�#r��z�t����ȴ�����a��\��U��)ĵaܵoD�%������m�n�~F
������fT�\(��n��l�a<�u|��	����4�l����������P�ph�r��t�i�l|�\��|�r��s����adVoԶr�u������
��L
��������R
(��̶o<�u�l�id�'�aX�el�i��oD��0������b0m����$�i8���,�t`���8��4�X
���P�a�����d�l��p��s�
�8����^
|�����dP��������s������mȷnh�d
`��<���зn�)طe��)�a,�e8�hd�ol�r��s�������d`�i
p���$�a\)P�e\�oh\H�i$	�����a���x�n�h%
ظa��b�f�h$�i,�m��n�o$�rt�sd��&��ĸv0%u̸l�r�&�'�\i4')�ld-�e�-�\1�,2)P6��ȫb`�dh�ep�fx�g��k��l��p��r��s��v�6	�6�@7n
H7t
|7	�7��7	�7�).��@9�	Թa�8���dܹf�k�r�t49z
`��G�;��e��l� �I	p<��<)(?�>���c8�lH�s$@�T@�d@!@�a�A[n��r|�w�AT�h�@uh�cԿ�I\$I����b��f��nK��M��PlPX)�Sa�e_��^̺t`[��Ժs�g���s�k��l)p!)��bлd�g4�kH�l��mмn�r<�s\�t��u���a��bP�c��d8�et�f��g$�hT�i��jx�k��ll�m�n��o��pęq��r�s�t|�uvlw�x�y�z�Ü"yDl +%�*��r�)Ļe�l��rĭ'��N�i�+/�.;�a�e$�l�.�T/J�0��6N�5��,�t�7u�7��@�ad�e��t�:|8:�\�i|�r8%�
�:��t�.p��>!��ix@@����e��o��t��dB7�BuC��ȼa�o�H�8%%�L���.�a(�e�8r4�vDMu��`O%�e�N��n�S�LTuH�s|V�(Y�t�rHX)P�e|�h��s��t�Y��Y��ZBX[�
�]�|\��f��g��s^��`B�_u��sнt�`dc'�c����ؽ���������\p$�a@�e��l�o�rD�s$�u��p�0�kpq��u��t��8�d`�ih�lp�n��r$tHvw��wu��s�x��x�|�e���Ty����tX����eܾi�o�t�؀u�tr̾ul�����
��Ծe�Ђ� b�����s,t����\��0�e<�up�%��(�i�JT��|�c��e��i��o��pԿt�uH�v	0�h�e��p�h\�����P����a��y܉�
�����tȉ�����p��h��i�o�rȿ�x���pD��%�t<����D�<�sH�tȌ�(�u4�s8E����8&ah�e��hH�ip�k��l��o��t�����x��t���et'i|'l�'m�'n�o�r�t,�u@�w���\�� 'i�r���
��)�i4����z���).�'b`��ЕC ]a �e�]KD]���i�P8�h��
,��L�T�a�ed,�ԗ)\�ed�md�s��t���#
T����d���C�
�a�d�e|�i��m��o��s$�u0�y��ì�y��ler�8a�c�i$�nT�r���	�R0�y����e<�s��J���4�eL�p��,�`�t<��g�ܪh�l��p�a�BdHa�y �uܟ�������u�������س)��e��o�p�t4Eu��Ô�����sX�)��lt��
�'�rt�����s��
<�J��a��b��c��d��e�f�g �i��l��m��n �p4�r�s$�tD�u`�wl�x�B��3����lh�uP�����e��h��L��o��������e��s��B8��X��t���f�l���
���T�4�mP�nd�s|�ȇJH��<�e����D�s|��
��\�eD���k��n����p�e(��	��%L��l���p���a��c�i�o�s�����l��gc4�\D��������������l�_��(���i�������
,�al�bx�d��e�#k,�o��t��u�#z��à�X	��)d�tL����eh��
��y��n��s��t ��
�����gt��
���d�l��s�����������N��rx��
@���4�h�k��B$�4�t��a<�hp����|��`Z��L�rlT�a�`
8%.��a$�eX�ft�i��k��l��o��r<�sp�t|�u�����d��n��r��th��Bl�����a�uhy\	��P��s�u����������	\�	�84.@�bH�m\-nP�r�����4u�
)).l�i,el�����d��n��s�l����P�s��������u�3��uK��%����a��e�o0�À��0��aH�����l �m���@2 ��(��HNL�oT�p�����L��\�n!d�a����m���\���d��e<�lT�n��r��s��t�u�"�
D#��n��r �s,�uD&�&%��a|')�'����a4�pP)��o~r�(�t�)�+�+��4�aL�o�-��.)`�iT/T3�l3)h�eH3�p�s3��|�s1;��o�42�4u��lX4J��a��e��t�5��i��r�52�5�
�72�6��o8!t1hL8��:�d�cp�l��u�:���a��e$�i\�l�m�o4�r��s �u�3w�3y��Ì;�
�;��\�hT<��>_�>x�sl?\��s�?�����8���?�T@��c��f��l��n�p�rAu���pA��fCu�D9�C����t�D�	E%�a�D��G4�n@�s8Ih�tpJ���P�J��H�m�JJP�a��e��i��o��s���\KPtK��|���K���i��n��r��%������np���K����t�K�
�L�|L��n�L�Mu�M!��e@N��u�O�Q$�l�R�@Ul�Tu,�al�eT3i��oX�ÐU�U��P���U��Ud�c��i��r��sV��Vh��e`V����l�V�
hW���b��f`W��W��Y���e�6h��i��o�p�t,ZtZ�Z�Z�[%4[�ab,�n�c��t�d(D�b�d%�e�$f��L�a��b�c�dx�e��f��gt�k��l|�m��n��o��p��r��s��tp�u�ic��a��l��us� j����r�j�8E���eHk����t�E\xl��k��opku��h�l)(�a<�e\�nl�r�l)4�nm�mP�r���,���H�r�m�
��%�m�d�a�m��n��s,p���gq'�p�
��es��u�
`tu��rhvy8v)��a�e(�l0�n@�rL�tT�u���
�v%��o�v��n�r �wwul�
(wPw�3��w;8�u8�L8x�yuTy\�r��uy)d�a��l�y=�zK�zB��a|J	).��a��b��d�e �f<�iH�nX�z����{��r�~u�|�|7t}�d}����n(}��i�n�}��}��D~��(�s~0�n4�����)P�u���d�g��l���l�a��o��p��h������ll��`����r�����l�p/�)�s�����a4�d`�e��f��g��h��i��k$�oH�s��v �ü�e�C�؂����\���),�rD�u����
�%L�g���T�np�u���Ԅ��.��sȄ�x�o�m�%�����%`�����bt�l��a�3����t4���a�o�,�����n(���r��4h!�m �rԺ)d�s��,�sp�t���8�a��t��zD�	P)\�e�$��v	�x�l$���a�I�D<T3���rĈl��e�B���fl������e��r��y����8%.$�aP�c|�e��p��s��t��uĎ�
�C����,�n@�4�id�w�@�h�J؏_\�u�����9w��p�r���h����eБy$�����r���������m��u��a0�e8�hH�rT�td�u$��@�ԓ��b�n�r������ȔQܔ�������)��\�X@�o�����\�m��w��
��a��d��e��l��n��o�r�t|�u��w����$���84.��n��s��y�%��%L�KX����H�C0�aX�et�o�Ic��t����$�nT��`���<�fd�gl�z���D�r�4�����y��
 �b<8fD�hL�kT�m\�nl�ot�p|�r��s�����a��b��dD�e��fX�gl�i��k��l��m��n��o �s@�t��u4�y@�z���ĸi	̸���n|���e4�i<�tD$R�%C �X�����0����8s�8zh��	x�������i�RL���9i��p��t���н�|�\���������$��D����sl�)��i��u��!��a0�e<�sx���!�t�����s�9w�� �r��B��
 *.��h��i�:l��m��n(�p8�r��s��tl�u��v��xd�������e��n�\r����.x���r��\��e��g8�d��k���	�'.��p��V8%r������.�s �tT�y`���ep����L�����0�d\�fp�gx�k��l��n��r���h�a,����l������a���������������J��ô��,����st�	�������a� �<�)\B.�e�l �m(�t�����x� �X	r��P=.d�s�*��0�rx*��@�eH�)L�i�r�@����d��e��n��t$��`���_������e��l������a��K@�)��l��s<�%8��������n�uh�!��a �ed�i��sp��\�4�e���b<�iT�r��%h����:nL�s����%���x�\�ct�n���x��`�!|�p8��)��a4����a\�������f��l��n�=p��r,����_������ipPu�����P�fh���|>m�!�u��(����,�k��!4�ad�e��o��r��s4�X���\�k��n��r��s�����|�g��t ���������n�������iL�Rx�N��e��k0 ���m�?r�st�����u�eL�P�����,�a(��P�e`�w��Lz����X�ap���a4�dL�e��i��m4�oL�p��s��t��u�������d��k��n �s\��T���0�����f��s��z��v�����P�����s���n�)�e��%�s�B�),�ap��h�tX�@�n��r��t\�7�!`�h��s�����!x�p��h�����wT��$���i��l���m��n��t��8�����a��tT��P��HAi����a�Ae$�$��p$�ut�����,�p�C
\�l�)@�fl�l`��0q��pXd�e��u��)x�a��pԶ3���r���
���l�\��s(��������D)
X�.l�ct�d|�f��h��k��l��m��n��r��s�t �u�����at�b��c��d��e0�fh�g��i��k��oP�rl�s��t�u(�w4�y`�zD��l����(�h��;�����iTb��eD��������d��g	��NRL	��e�	��	%��c
��tH
�T]		P
�e�i\DP,�rX�up��4��<���J��\<�D��`�s�h�uh��8%.P)��e؛������c��l�)��a�e<�o\�rp�s��uԜ�������ș���a�
����m
��r�s����
�s���
(�t�
��0�r4�	D��H�m;P�o��)h�i��p�+z�K,�u����m
 *.��b��c�f�i �l4�m<�nd�r�sp�z@�����n���e��X�����f�bX��n��,�eL���H�t�
T�r08%�
���\�.��d��f��g��h��k��l��m��n��r��s�t�u��w�zL��t���"�#V����( !��od�����������V(�sx�`�@�aH�u�d��/���P�d��gl!X�a��e��i��l��p��s�.��h�%��a8��r(yXu��oxR1\��Gc�he�
84.�c�d$�e@�mH�nP�oX�s`�th�vt����K0�r���84.XGs��UX��%�6h��X ��p���)x�Ð$���l<$��b��fD�h�Hl��m�n�p�r�s,�t4�w�$��$<��e�$�hEu8Eu��a%%� %��%$�t�%�&��&U�&\H�d���'��'�cs��u�'�X�a��c��e��k�p �t(H�Bh(��eX(��h�(��(��b��m��n8�H )���g��h@*RL*l��a�*Uh*)�o+����l(+�ap�e|�o��rP�ô��
8���<�t�,��D��8�O�+\�tt+�d�s0,�V����e\,;��i�,!
��a��d��e4�fH�g\�hp�l|�n��r��s��w��z���,-���Xg@�](.��-����b�r$�sDM�.%�b�.�:J,�atc8l@�a���.T�i��c�/Bh�a�jH��\0����l0;��o(�p�0���rt0!��p�"��-�����4#��ed_x`[����t�J01�t�2�Tc��Z����`3!�Ø3�4��dFfl�gt�k|�tt4��<�r�3)T�e�q�Xr��rh�56u
��b��fD�h��l��n$�pH�rx�s��u��vp7�	�;��e<u�AJG���f.�d�f(Gh�lIr�s�G�xH�I��I),K���=e8�f@�t�KP�L��L�.`�bh�sp�w@N��Q�����S�84.�Zh0Z��l�[��\��c��a��f��i�l �o4�p\�tp�u�cy@�gk%hp�Xp��ohn����s0q�pX�e@�-�q�c\t�,t),�aH�l�ir�t�xh�l�w)P�a,x�xy|�s�@��zlM.0�dP�fh�gp�i��k��l��n��p��r�s@�tl�u��w��z,���z����a��b$�cL�d��e��fP�g��h��i��k(�lX�m��n��o��p��r��s��t@�u�w���<��|�(�.@�m�t�|L�|uH�a`�r�-�$}l}(|�c�}�	�}�~(��l�~h�������d��e��f��h��m@�|�(G����
Ȁ���fԀ�	܀��i(�|4���i�s�����Eā��$���YPԁ�8�hT�i\�m�Y>Z�X�-,�d�b��e��f��gt����P��|b�0c�x�P��h��m��s�������d������\�\@���؄)��e�r�s��%�	����p�)8�h|'�<�0�l���)D�a`�bh�eT��P�x�c��r��|��������w�X�.��b��c$�dOf0�h8�id�lx�m��n�rt�s��t��u��v�����,���s<�����h�kX��`��������tx��iH������\�.P�gDOnX�s��Ѝ\�.�p�e0��t����(�.��a��s��t��z�		X��`����e4��t�!��f��n|����		��e����4����dT�����n�Ss܏��
�.<�f�1hD�lL�nT�o�#r\�sd�tl�wH���
Ԑ��|��H���������sȑ� ��Ȋ�����T�K<���i�n
H���l���������d��)��it�ؒ)��e�r�����J�P�r`��������X�(�n8�0�bt�h��l��!<�e��o�������`��$���h�è%hx�u��sX0�������l(���a���t;HH����b�dT����������a�b$�c,�eP�k`�n��o��t�
���t��(�����<�nD�r����XGs���X���X�.|�b��i��k�Pt��������Ĝl��u��a�)).��l��r�t|��������l���i��Ì�����ܝ;��e��7\�(�n���b��!�e��p���4�sL�<�nl�rd�!H�e�!h���d�s�%��x�s�J��o|����f.��a��b��f��g�h�k�l�m �n4�p<�rD�sL�uT�w\�z�5'd��;�x=��l,A�H�(��(��,�yT�%\����Ԧ��[�X\��t�d|�kL�`�+����%����������P�)��i������u��r$���ep�2��|�!	��a�e$�i,�kH�m`�nh�px�t�%zP��nl�9��yܫ��>�4�s`��<�aX�o��D�CP�%��U��p�e0�(�����kԭ!��a��e�h�i4�s�xG�x��i(�����e��f��s������rX����
�_̯��o�b �np
0����s̰y��J,�e��`�fl�m|�n��z��>��%X�s��l��8��t�d��s,<N���̳S��P��bp�YP�����k`[����c�i��)�ex\a@\�s��T�a��b�cT�e�f�h,�i��k��l�m�Un�o�p\�rx�s@�t��u�lw��y����\�L�bp�dx�n��s��t���\�u��t<��Ժ���sD=��nP)��eP��TZ\����oT������s�u�������d�3��i̼ �a4�e<�hD�oL�u�u,�p�(�_8������)pT�84.|�b��h��i�l�v�P�t�e0�������r��\d�����n��2����rl�����ehP%)�a4�n��c��a�u��y�r�=2bu��d�K�r�� �eD�gL�l\�n������$�s����T�ip�sx�tP�x8|��(���t��l��a��e��y��p�g2����o\�c(��g`��$RC�����t�����rp�u���(�e<�oP�r�u�h~ � �k�\��4�rP��(��H�a|�iD�|��d�u�)l�a��e�i�o�p�t8������m��r��p������e�g`��
����f�����$���a���p����e(��0��b�����b��kl�n|�r��s��ud�	 �a��e�i(�m4�od�rx�u��y���0��` �����t�b��>�����U��n�r�s�v\��<�����g��������h�� �z
�sX����%�p�
�p�|��e��dT���o|�����dL�r�R����D�iL��p�pt�;X�o����T�\`��������u���������m��r����y��y����e��P�r�����e��$�a��e�hT�ix�o��r�s@�t�w �z�����84.D�bL�mT�pd�rp�tH��P����
p�����\�i�!d�����h����x��p���i��n�o��r��������nT�u�d�B���n�����������h�tx���!�r\4�aH���%��,�l��	��@���	d�ml�t�L����a����b��f�;�����a�e@i	H��m����ux��c�s����3��w��hu��c�p$�uL���<d�bt�n��u!,�a��e��h��r�s�u���0�|�M���l�z��d���������������[i��nT���)��o�2,u�%zx �p���m��r�!��"��S�L�r4#7�a�#C\�enw<���#�#��4��\$�<$�H�t�#��P�nl�s�$�	0%lh%��t�a�eg|i�k�m�nDoLp\rts�tbx�z���+����f�+�i�+���e�*)�rs��
�,��c�/)@/)a@e`lls8'�0%,t�/4nPr0�P0�00)Xa1N|),2utl�n|2�2P��3�$3����P684.�b�l�s��"|7t�7U�p�t88�X8�8We(g _i8k0:y i\:��:��;�;�0a�<l\=H?�>��Telo0@>�@J�p�s�t�B��ChDD'8E��h�s|F���tG�n�G�hHGu�c�H��H)�w$I�P)(edotbs�R�R��a�Q�lDnPr����R��<t\S�\iT��VPX�a�e�i�s��X��Z��s�Z����,[�`[�8%.�`�	�_���r�s�t�`a%|b��c��	8%.$a8ePiplxp�t�u�yHXhlct�dtd0mHr�de`nhs�e��e� fBXfh�f!).�i�u��`g���n�g)�g��g��j�g���n��s�l�aei,w�lK�x�q���n\o��n�t$s$v$z�\p��
4bxc�g�h�l�o�p�r�s�t�u�v�z���ix*�:��u �6�c�z�����h%��P�l���xcm�on�rp����pp!�bchdxh�i�k�l�n�p�rPsht�u�v�x��a�	b�tc�	d�	e�
flg�h�i��j�k�lxm�n�o�pT�q�rHst�ud�v�w��y�z�Ü"���e�f�r�s $�P%��%@&%$hP��'Pb@r�&eLiD�o('�0'��8ft' +��*Tr�)\e��r�1J�iJ�3���bP6z�5���e�7u�a�klTspt�7y��%\<)�l(���<���k�<�nr�<!�e(il��=��kt=u��v	��0a`m��8hl>!Hc��X?��>!hsD�C��
|b�f�g�k�rl�p�r�sT�w�z�E��F/F!�r�G��&�I_�J	�K��Lub(c0i8t@z|NN�r�NP|S�S8THLT��Ha`h|U�HXhtoTZ+�]\|\|f��g�s�_��b��b8bh�b)�b�adcP	c	h0	r\	s�	u�	��c����d��`���ddHY)�fu	uf��	r�j��i��(	mXl\Dl<	r(l�D	eHk�P	s�l��lPh	r�lp	e�l��|	�,nP\pl��	i�����	e<�(
a<
cЏd\
et
f�
h�
ik0l�m�n4r,
sh
t�
u�
w�
x�
z\)\�)
u�)
t��EP���4
hL
kp�- �u8�JT
il
m���t��l��2����
i\�J�
e$����
lx��T����
e�
ns�������
g�
h�
i�
w����l`3���f0�u�Cax�t��)����(aPdde�o�s��t��!(:r(��D�\kxr�s8����"�����2�����h �!�ol�8%.�m�p��(L��4��D���������gos,t��uX�l��a$t ��������8%.la�d�e�f�l�n,�o�p
s
tl�w���|n�u��u�g�z`���J�l��L�)��e����r�u�zh�,���)�a(�!).Ԩ-���a��!).��C@�8
aH
eT
r�
t �|�3��U@
id��$�\Xg�\
a|
e�
t���,��@�!�
a������
e�)�
r|����
e�
r8J\���
ul��p�`) aPe�f�l,rLs�Jb4rHu�@bc��	pexf�i�m�t|-x\
�t
y�
�n�
��i��� �9h
�z�
m��a�ou���J�?�������J%p�� ���.���<o��uH!Da`h$.p��)ܵo���e�(�D#xs�:l$f84.�a�ekdl�n�orsxt�zih�e�t���r���f�m���r�z��z���ny)i(l0oDr�z'{d<bP�,{/\|��{Ln�u|��Ta��d�e�g�i�|2t|�f,�(}�sH�J~�n�pD~2P~_������a�it����l���a܋y��4aPc\odpĎL���@�<o�Dh�h�3<�d�r����lo�uD��ܙ������8%.�a<exi�ou�ð�����d�px�|��cd$n,u������p��p�	E�dL��4�q
\���4bTedi�tr��S��q
����\n<2@�84.�m�n��X�������e��b���n�pst�w��?�^��)�.���s��)�e\��s,�e��% ����c0gds��eDe$�2�<rh�8�)Pe�uXsL�2p�B�a\���d�s���e��%�s�=�B�i�U��s6b��fl0nTpdrXx�8�p7l<C��Aui�F_G��(aDeLu�G�|Ju,K�P��L��\g�i�m�ns0t�PkpP�nQ��a��q$Q�g�v����.�J�rԣ�ePQJ�i��2���ai�Q{�tP�2�R2�R��i$R!$eHh|�rPu8S)hS\��\P$�ftk|l�^\�^\�cN�z��a0e�io8u ��{2�h�z���c�d�mذnpH|�T|��r|�t�|:$Ȁ:���x���n�����@�� *.Tc\d`�gli�p�u<�%x�t�l����dk|n4����H��(��	���c�d�eȆg�sL����ԛ��c�t��d��p���r���e|����cn$s0t��(aԦ��cT�����%��!�a�c8e�i�k�o�p8sTt��u��y��$���%�m\��mT�n��r�s�uԺ�t��D�L�f�s�t��E�a��)������̼e(h(��������n8�a��eT���b`hlmtn�{r��t0�����Xr8��H���l�tL����d(�\�����e��l�i��lT�����n�s���a�hl<�or��\���\�i
(�� e(i̶o0u��xlv���|��������@sd�Ha�e�i�ryt�`���8���H��T����h����c�m����p�����p��st��e��H��s#�#��eT����t�������!
La�e8�ho0r`s@tTuxw�z����8%.pb�f�l�n�r�tH�|et�(������������p����h�i�nt��������g�s� �T����tT�<i���p���;(oXuL������D�$K�|a�c�e4[i�t<�
�g�m���2��h����e0�
�h�lXpl�������'e$i��<b �rs ��Tu�/)�;,a!4rx ����Lmdr�!����l�S;4#7pa�#�h%�g�h�m�nr4s�t��@/�\1�P6�8�ܹf�i�km�t;��;���lorh!u�!@"n
>u(eH?P
.�@�PshCRdnlr�C)@etpdlt�C��Cp$���x��P|r$E��e '����8E�$IP��b�r�s�NdOPX)�l)�a,eTuHÔl�Tp�8d`p�n8pue�o��t\o�� iLp�,wu�n��@�Hx�hg�xJ�x)`ap!��
84.$b\cpd�f�k�l�m�nh r� s�!t�!u��taP"b�tc�"dD#e�f�)g�)hx*i��j�+k�+l�.m�.n�/o1pT�q1rX4s8tL8ud�v�w�:z8"� $��"f<lDrLz�$�P%0&	�&L@&��Th���)hl�r�+�-��,���l�5hqa�7���qa��@���o��tC��	|b�d g��h8 kD lX oT�w4�y<D�8F�F! a$ e�r@F�HF b��q
�G)0 uTH	P a����H|N��L��` b� c� d� m� o� t� u� z�N��N��Q�$RDSz
S!� i�S��S�LT� c!e!p8!sl!t�Td�pPU�� i!mlU�0V(!e0!r ��(�L	|V�L!e����VD!s0�%$W�X!n,W`!a�!e�!r�!u���dW��!l|W��!a�!e�Wb�W��W�XHX)�!a\�m�!r�Z�]c|\�!c|f"k"s"t�2��_(�a�dc��$�p4�rP�sH"u�c�� "�p:�,n\pBx"a�"e�"i�s��sd"sp��l"uTy�t���"r�}��u	�"a�"e�"i#o$#r,#s4#t<#u#ì��"u���,����"r����"n� �lܟ��#�X��سB�J���<���a�#b�#c�#d�#e$f$g$$i�$l&m&n\'ot'p�'r�(sd)t�)u�|x$�����#a�#e�#r��JTu��#m@����#a��P����8�N�#c$s���lt��<�y��$lT���4�m<$nd$rl$s����8%.(�sT$t\$vT�C��%�����J�$e�6h�$t|��Ԓ2���$a��u�$a�$b0%d<%e�%l�%o�%s�%t�%u�%z�%ð8V\����$z���$nl�;%a��)�$r��p�	 ���%s0�%r��!$%eD�P%h`%rt%s��b���8���X%s������l%s����!�%a���h�r� �!�%t�+z4�y@�!�%a�R����%m$�%�����%�@�r��l��%i&u���%D&a�&d�&e�g'm�xn$'s8't��hX&m`&rp&u��������c �h&g\�/��)|&r������&b�&c�&i�&r��R������&d���d����&n��=.�)�&e��%�&s��&s@"J'a�,hl��'z4���!0'fԤhL'w���H���uT'rl'u���������'b����
|'a�'e0(iL(m�(n,�o�(s��u�(w�(È{�P��8����'g���'i�'n(r ������'g(s(t�������u`�����f<(nD(t���(n$��P+X��l(a�F�%X(s��`(s����Jx(od�(�����(�h�2x��(o��!�(tXz
�)�(a�y��(c)e )i()pD)sP)t$�h����(tT�)l�{r,�y�34)i���x�)<)t@��$�yt)p�X)a|)uT���H�3�|����)l�!8a�)e�)l�)n�)rD#�)n�&u&%�)e�+��)o�-J�.�1�:)<.*e8*iD*lL*r�u�3wT@(*t�F~�H��G��0*e�J!�TUhRg�T*o�e\*l�*s$f��h*a�*e�*g+m(+np+s�+t�+u�h�o��m���*g�*i�*l�*nto)�ou�p�
,p��*eL8u8v)�*up�8%.0s +t���+e�����d@+eP+gP�s����E��!H+al�q
��\+l��d+eԓy����|+a��J��B�+s������	<.�+a�,e`-i�-od.sl.u�.y�,ð��+b,d,g$,nH,sd,u|��@�,e�*x��
\��
0���,z��pн0,iT,uL���8,t�����\,bt,f�3	�l:�l��|,�|����,t�,u�,ä����,�|��$�\�4����,f��m
 *.-a4b<wc-g��h-i<-nD-rL-sH��(�������-n0-tH�$��_(-r��%�(,��L�e���@�X-a�-b�-d�-e�-f�-k�-n�-o�-s�-t�-z��/
$�~`��
`tE ����b���
(���-ch�(��\���5.���-a .b0.c<.gD.kL.mT.p\.t��~p9�	@9u(.h\������_��\,�		�C ��.t���)x.e��up�l�
8%.�.a/eD/gT/i�/o�/p�/s�/t�/u�/y�/z�.�D�P
.l�c�.t
cD\p���.��/� /h0/t</u,%����(/r8PlC���L/el/ft/n|/s`t_���4<$�,�t�&��'�,�01��/m8%:2���/.3��3u�3��5,0l6���/a��f80g@0hH0iX0lx0n�0p�0r�0s�0t1y�;�7��$0ix=��>��@l,B�AuP0a�A�Hpd0sG��l0i�K�,K���0e��fLM��L���0aD�d�0gP��S�W)؄h�0tXH�Y%�0gZ���0rZ!�0eL\�cl�z;L1a�1e�2i3o�3u�1��{��{�41ipl�z��<1bp1d|1h�1k�1m��N�|�h1lT}��}�����d��1nP��1e$���1mx���P�u�����1��3�@4�84b��1.2b 2c02d<2eD2ih2mx2n��r�2t�2v��<���Ux�(2e��S����ȱcX2n`2t4�h��C
t��L�\����p2n�2z�ocT����2i����2e<�������c�2e�2fȆg3n����2sT�h0��2eL�C
��\X����2gФE|���3c83h@3nH3s�3u�3�����
Ԧ�l3s�UPG.�3n�3r�3s�)T3e8%o�U��x3.����x.��v�b����3��X�r����3h��2��3f�3i4m4p4s04t��	�����X��(4eH�u4sh�Rt��r��P84cP4n�M��J�4aH5c�5e�5i6o6px6r�6s�6t8u��y�T�\�u�4cp�d�4f�4g�4h�4k�4l5m5n��r$5s45u|����`�1%@��p�%�4tظ
�%x*a5b���\�Ժ���D�L,5g��̼@5ae`5hx5r8���ep5i�����T�l�5i�5l�5n�5r�5t�5u��|d����5ll����8.�5i�5n��R��|H��x�p����2��)�dD�g6l��%������(�e<6iH6lX6o ���P46e\��
����P6r��.����d6t|��l6a�B��������6r�6ud��6a�6e`7hl7i�7o�7r�7u�6���8�P`����6��7��������6l�m7n,7rL7sx�h<���7t������$7m<7r��8�� �D7t�x��X7e��%x7s8��|�%�7n�7r�7t�����D�t�X�7a�7i�7oH�J��J��L����7s�����7nT�P8cD�L
����!,8e48i�r@8sp��	��d�muP?eh%�8a�8d�8e�8f�h9i(9m49n�9r�9s@:t4:�<0%u�8m�8n�8s�&���f.�8t�I��&|*��))�8r�*��8d�8t�*�$-�d-�|2��X$s,2u�8n9s�2L�7P6�� 9m�8�ܹfL9g|9s�9t�:!).`9et9l�:�l9w�:��:3	,<�p<�>%�9l�9t$@
��7�@N�9sA��@u�9a�9c:s :t����A�9i�A�9h$���C)�9p:tp���D%DD:i$E� '��,:�8E�T:a\:eh:hpE�E�ar|F�$I����b�:r�:s�N��OHdO���:t�lBH!�p!���:at;b�;c�;d<f0<h@<kT<lD=mP=n�=o�=p�=r>sX>t�>uD>����:a@b�tc$@dT@edGf�Gg�h�Gi�fjP�k�Jl@Nm�On�QoP�pęq�Tr�Ys8\tbu�dvewley�ez�?��"��"l;a�;eH#�&D�o@&���;h�*�ul�)�;e�;i�;l�2h+p�;n�����N�;e�-��-)�;l�,���;f<t`�(<p4.!<sL�1�6��5��8<l�L
�7��L<.�<a�<b�<e�<l =p0=t0Dm�8��x<c�7�<n�<u9���ux9)�<a�<e�<rX92h9���<i�98:X�<u�<!�<a�<e�<$rh=r=�=��=f@>� ?��>!(=rdB�@��<=tC��8%.l=dt=n�r|=t<D��H��I��K|Ku�=s�K��=l�=r�L��L�DMp�L���=a` b�=d�=m>t�N��Q!\B.�=e�QP
.>s0FcS!Hh� iLTu,>a4>i8T�U�4X��"��<>�X[NHX)P>txK\ed>i�]��l>l�>m|\x>f�>k�>s<?tx�|3X	�2���>tx_u�_u�>a�>c�>p�>s$?t`EC��`��>a�`N?e�`��`R?n<ap�`?i4?o�[2�a!).P?o�or�aq
4e�@e��X?fdc��`?f�?s�?u�c��l?�\G��T��T��d�Hk�?ctk��?h��VD����?n8��?eTo)o)�?c�?p,n���?s�o	\pB@as����ap��@r�8@a������0@n��@�d�@t<���D@a�@bAc$AdDAepAf$Bg8BiCl�Cm�Cn�Do�DpEr�Ft,Gu@GxTGy\��@��@i���@e�@l�@r`����*��
������@eAoAtP����@h�P�'�%��Ag4Ai|����8�J<AlTAs��������\Ant�dAa�Ae��f�Ai�AlBrBu�A�������A��
�$���Ai�ArX�hP�u�Am��������Agt���An��w��%Bi�R|�_����Bux�T���	0BedBf�glBm�Bn�Bo�Bs�Bt�Bw��(|�u|Bp�Bu��	�B�`������Be�Bk�����`�W���Bm�Bt������u�Bsx������Bu�����Ba(CetwipCl�Cm�Co�C�D���c@Ck��nPCr(�~��h8���HCs������\Cu��!dCa��2��|Ci��)�Ce����Cf��2����p��4��l��Cd�%i�Cm�Cp��		L�V���
8%.Da,DetDg|Dio�Ds�Dt�Dz����������$DbLDn`DrlDt��<��DDd���d���XDg�h�N��x ��l���Dt��!ys�Dw���`����)	�Dn�Dp��$��l���Dh�{y���DbHElPEpXEs`Eu�����DahEe�Ef�EgFiPFkpFl|Fm�Fo�Ft�Fz�FÌ�_��]�tl����ܥc�Ei�El�ErP��8����Eg��sd��l�V����Ew����)�Eo�E�������E�P�N`�����fFn Fs<Ft����Enp��$��Fux��.9����(Fr�P�0Fe$����)HFe���y\Fu(�!dFaX���Fa�
���Fs���Fb��qd������F���N�Fh�hȴ)@kw��|aGeGhGi$Gs����Gr�yT�y�H|��8Gg���LGa$���P`�Ge�Gi����Ge0��tGl�	�GlT�� ��Gr���s�B�GeD#�t$f��8%.Ha@Hc|Hd�He�HfIkIl8In�Io�IpJrpJs�Jtp�u�e$Hc4Hn�f_FCDh�,Hgpkuh�epHh�^�D]��PHr�lCXHe�kdHt�l�He�Him�xm�m�Hn�Hrpq,p��Hs�r��Hnr���Hi�r,�u)�Hr`tu�Hfy)Dr�{|��Ia,If�}%�}l$Ir�hId|Ie�If�Ih�Ii�In�Io�It����)`Iu�����tIl�In��Ȅ	t���y��� ��,-)T�!�Ia���In�Ir��	L��WJhJi�����_��a0Je8Ji@Jn`JohJs��،y��"TJet�2�LJr��@����a�Je�Jp�_���h���Ja����HAi�J
�Ja�Kb�Kd�Ke�g|Li�Ll�Lm�Lo�MsNtNutK�4�2�����JcKnKs8KtHKu,K�0��8zL�P�&$���$K�0��\�X	�@Kt�d�	|���TKd�Ks�Ku�,ä���\K�XM�4N��,4��l�J������b�Ke�Kn�Kr`LstLx��78%������K.�g�Kn8�C�%��aLg0LnDLuPLwXLz����2����L���J$L�|��@��<Ln���V,�lLil�: �%@���el/f�Ln�Lp�Ls�Lt�������Ld��(��Lt����h�N�l�La\�R8��Ls��uMc Mf(Mp0MrLMs@9�,����2<Me@�z�T|\�DMi��(hMcpMs�]\ b@|Ms�b����@��Mn�!�Ma�Me�Mk�Ml�Mt�����Mr��%��l�Mu��R��l�Mo|����Mo�����% ���Nf$Nk�2%K�L���,Nfp�)lNaOexOo�Op�Os�Ou�N�����dNb�Ng�Nn�Nr�Ns��0�%8�%�)�e��%�Nsddb�%�Nc�Nh�Nl�Nu(����N��O�H�b,gb��bX�84.Oe,OiPOnlOr��!h�0���$OnDOs��Ft��<Otp�%\Os��M��)h���dOa�u�On�z�%��l��)�Op�u�u	�OaPdPe�Pi�PkQo QsDQt\QuDu�OdPmPn��T%��@PiXPl`PnpPp�Pr�Px��X�8PgPPn�%���RpWxhPf8�`�|Pi����Pe�Pl�Pz����]�|�t_��Pc�dL/e�Pm�Pn�Pp���t_� %�<$QrL*K�'�Qk0Qt(+0;,a�,)8QrTQst0N01pQcxQl�Qn�1_�3`�@2��Qf�Qg�:h`2'�Qep7(�l6���QbRc4Rd<ReXRfpRi�RlDSm`SntSo|Sp�Sr�Ss<TtpTvxTw�Tx�Ty@9�Rh(Rkp9��7<:m Rtx:\;JHRft�)�;��;��PRahRr�<w�@_\B,BxRu�Au�Ra�Rd�Re�Rg�Ro�RsSySz��c�B!�Ry�B�Ri�B�$C�D��Rr�D2�D��D��D�(Se��}q��SeE��Sn�EPScXSd8Eu4Se�E3��GulSoPI[�J�,K��L��t�a��d�SgP�PT�<T��Si�S�Se�Sl�Sp�Ss$Tt�T�Tr���U�S.Ts,U)�SeTi�j��)T.0�R�U�a4Tr�V��W)).؄h\TlhTs8Yq,Y�TTi�YE�[�[v\L\���\(�Tc�Tr�Ts�]��`2 b�Tc�Ts�Ttpb��b�by�Te�b��zu@Ua jc�Ud�Ue�Vf�Vg�VihWo�Wr�Ws�XtHYu�Yy�Yz�U��{�z��8Uc\UddUitUn�Ut�|{l}(@�����lUeԁ����x����Uu�����U�lY����<���Uh��Uc\d�UfViHVlPVn�p`Vr�Vs�Vt�2v,��ȋ8%p����V.,Vb8Vc@Vg��J̌c$Va�C
���%���P�C܏��XVg|Vk�Vl�Vm�Vzp���a��Vb����Ve��9h���#<��Va$�\ؒl��N��u�VcWe,WnTWs(��Wk�l���m Ws���0�WlX�%<WeDWhȚ������ԛ)LWtd�|���`Wb�Wf�h�l�Wm��nTRrD�u�;�̥���u�We�WiLF�P�J|�����WcXnd�u|�!	�WaXc,XeXXidXklXo�Xt�Xu�Xw����	(�Xhl��
P�$Xn@XrHXt����������PXnܫBX*���f���ac��xXe��uȭ!|�����Xb�Xnԭ!�Xa$kh�Xo�XrYsH��T��D����Xr�x�;�XiYo�����J�Za$Ye4Yt̰u ��',Yeȱ�@Yh`Ym$�����XYs��%xYb̳���uȴl��!�YaZc,ZetZi�Zo�Zp[s4[t\u��yZ�l�S|����Yh\��YcT�nd�uT������Ys����Y�̼Zh8�a��T�$Zc�lLZrXZx���x���DZl���������`Zg��hZn�����f��|�r���Za�Ze�h�Zo�Zr|�� ��Zr�3���Zr|�������Z�(��̶o�Z��le8����[t����[l\[n|�rd[ud�[a�[e`7h�[i�[o�[r\u�[�0�u��P���8���l[l`���t[�,��\����P�a�6l��������[e�[n����\|����[rt���[a�[i��%H����[n�����7nT����),\n�:Ch��$\g��!
t\aD]e�_g�_h�_i`n`ot`r,as�at�au�z8]�)�\k�\l�\m�\n�\s]t�d�!�\.�\s0�P�\t`7�����\te�P�����\e�\k4���		���]s]y|V�X��y,��d���$]r����,]�p���
84.�]c�]f�]h�]i ^lX^md^n�^r|_s�_t�_u�_x�`������]a�]e�]��|��K�����]���u��E�����]f�]l�]m^sX����b�]z|�\�
d��
T��^tT���^i<^m��XH^s��)0^a\��4���P^pT�%x^t4�� !p^f�^s�����)�^o�^ô���^f�1h�^k_r$_sl_z���^���$����^n��)�^e_l���P�<)_e�d!_c@_pL_th��h�%8_a��\_ad_e���p��4���Lt_e�_s��<x���PLzs8l�_e\)�_e�_oH�i�_u�3$	z���	���_n����	`i�4`lH`r�D��u,`y��D��@`g�}%��T`k�`n�`t�`u�;\`a�`i�`oaua�������`dԁ�6@
�`eH�`m�J�`nb����l�`sԦ�%as����`�$a��$���JPappatlv(��<aiLDar0�����\ab�'daa$i�au�a��\��art�T����ar���a���ai�	������ae�arT"��!���as0%�h%���aa@bb�bh�bk�bllcm�cn�cp�cr�cs(dtp)4')8badbe|bl�br�bu8�d'��\bitbn�w��'B�'���u�:)\1�ba�bi�G)|3��2���bt4��3�bacePci\co�b� 4�,4���b����`4ci(cn8cr@cth���4 cg��t�4��4��4��4Hcn�5u46P6��dca|cs�7��8��ca�ci�8l;�
\=���f.�csw�>��cg�?NA�@u�cap`o�cs|���C)�cade�9p:thCH�pE��db8E)daDdhLdiTdttdz|Fy�	u�G�H H��\dn�dr,H!ddep$p$Iu�db�dh�dsT��IP�ds�duD���f%�fu�de�K���dn�O�dO���ds�P)�di,Uel�U)PXu4ea@ee�<��YP elX(el`[���#cXeǐe@\PebTi�g��deg�ep�et�j�ee�f�,k��e.<l3�lLco�eu�xJHx���egp!8%.�fa�fb�fc�ff�fg�fh�fi�fj�fkgl4hmDhn�hp�hq�hr�hsitPiupiv���ea�ibpkc�ld�me`tf8vg,xh�xi�fjyk|l�m�n�o�pT�q�r��s��t��uИvx�w��xܙz�i�H!J�")�fl�$�@&��,u�fl�-�
�.��1u�3l5)�5u�.�ft�6h�7�P=.Dga`gbhgdpge�gi�gk�glhohsht$hu,hzTgÜ7J,9�89��Lg�x9�:78:�\�i�gn�gr�gtp���:��gt�:��:���gf�gh�gm�1;@;�����;��ga\<l�<h�ga�<>��=�hrl>7�>��?u�?B@�
�.�oC�pha|hd�he�hn�o�ht�hz�C��Bhhl<D"PE��Hh�I��J��K)�hf4Ly�L)�L���.�aLT��hc`h�hiipH�s̅t�T��U�0V$HX)84.(ia0ie8ih@irHis�XK(Y��Y�	�Z)�Z�|\y`inhis�^��_�8b�dc�H9m�ir�it�c�����xi������Ș��i��\B.�is�jX	�l��\B.�ia�is�l\Tm]\p)
 ja@jb\je�ji�jl�jo�jr$ksHku�j��s|fp��ju����t,jiXt!4jlHvljgtjs�t��Lji|jn�v��v/�w�ja0x�L~��}�jk��%X��ja�je؀�Ђ��,t���j����h%�\�;ke�jð�%���jn��p�keT�!kt�;܋�0kk�tD���8kn`ksp��(�uXkc4�s�u�kc�kh�li�lk�)lX�)�t��ka�ke$lg0liDllXlmxlo�r�lt�du@�w���\�P 'ilr�F5�F�ks|F��ka4����km3�1;lo�wlr��P<ln�P���Plo�LC
Ĕ�lla�N/lNdls�Pt`ЕC�lrL�d�m�de�lsԗl�	�lamexmi�mn�mo�mr�ms4#t�myl�m�����lbmmmn�"u0�%��u��:�me0miLml`ms���������8m�H���@m�h�h��XmoH+p`�-��pmo��[���mi��_�m.����ml�X��д�س)�mc�p���<����maDnb�ncЏd��e�nfogtoi�ol,pn�qorrss�stHtuXtxt����0n����@ldnr�ns8n�0����\ne�2Կpno�nrD��xnt�P��nh����o������nk�nn,-ut��na�nf�Alor���X�������no<�7��ol,on4orHosT�H��@oaL1K���\ocdoelotH5���N��JT����(ws�ou����|oa�od�oe�olpo�ws pt�y��7D���c�or0�����obp���oe8����og��������pb4�@�!pa��84.�pa�pd�pe�pf�pgqh$qj,qk4qm@qnHqoXqp`qrpqs�u�qv�qw�qz�p�����pb�pg�q4�\D����p�Pq���Z����&b�prd�%�pf�pg����@0�Zh��
qe0G��qfT"�f���@"L<�ad"h���<�P
�&�P����yl��hqe�qk�qt'z��%�;v	|���qf�qp ��qo�qr$,v	����q��Q����q�H3"(�h`����l��8rd��p����ra@re�ri�rk�rn�rsst�r�0�����Trrtrs|ru��drflrz<��h��t��������rn`�������'.�riԣWd�������r��y�rh��!�re�rt��p��%x��ra����!se�0sh8sk@ss�st����Nx�mXsedst�����Pst����� ���lss@�tsa$�)��sa�se�sh�so tr,ts8ttt��������sh�st���sr��$	���sox�C|����t������ti�uP?e@����|���@te��n�`u�ta�teufPul�ur�ut���tb�tr$-tHu�u�tm�Q8<
��	�tc�i�tn�tr�uP��4���tg�th���x� �th�
)uaDue,elH
�
$ue,
,utP8us�`uahu�������xu�p?��ua�ue0�øJ�uu,�z
0l�uil2Lu!�ua�uevovr(vs�������uk����ur���vp��0;vi`��p�%z�)hva�vewi(wlPwn�wo�wr�ws�3t��`vi�vn�vr�vs �����vg� �h ���vt� ��������vr$��vaD#�vf�vnwr&%l/al([L(��va�'���vm�#z+�x*��wm�+u<wÐ,P�,��4w��.��.Hwa�we�wopw��.p�.��hw�</x
/|wu�/"�/1;�ea�weloD2%�1�wiX4J��a�wo�wt6d�6H1i�wo xrxÀ7����6��x�H���7Xxe�:)\xatxe�xm�xn�xr�xs�xu�3wD=��:��Txmlxr�=�T@tde�xn�C@N��O��T��Y��plc�b���xm$f�xa�xe�xg�fm)nysyt�e��m8vH��L8%.�����)Tyazbze�zi�zl�zn{o,{rp{s�{t�{u�y�d���Lyktym�yn�yr�yt�yuT���<=t�H������yo�yzx���yeK�$�h�����yt��8��)�yt��t�"�P�yr<����y� {�D���zu$�2,zi4zlDzr�zt��P����]0���<ze\zf|zh�zl���pzap�cx�hzhܧ�H�k����za����84.�zn���zd���L�B�za�z�|�%|������z�X�%�zo�����{p,�\�P{l�;0HaX{e`{oL{Ô�%����D{�Ȱ�������)h{o�{z�y��{rH�!�{e�{r`����ukX���{e�����x7n�{s����D|bL|dL�kT|m\|nh|st|u����{a�|b�|c�|d(}e�}f~ih~l�~m0oLtXuxv�z�|�|�y@����0����8sL�����p��|f�|s�|t�	&����|b8��\��|�q�����|�l�c$����!��a�|e}o(:rܤ�
����|t���|n}r��u� }r������cH}hd}i|}l�}n�}rd��T}e��l��m����\}dt}n��PD�y�p�!�}s�����}t����:fp�g��r<�l�}l�}r�}s��B0����}eH>��`�u�}n@��}e(~gD~nP~phv�4~b���~a$�������<~d���8.`~s̋		h�!).�~a�~e�~i�~s��y�~d�~n@��8��������~rx�y`��T��8��~kntu�)�~a$i���0����~gh�yp�������n���<r��@�y��!Dh ��hmpst����d�J�m�X��r(�)�a�ep�"x����k�����rp���8%.�ap�e�i(�mT�n`�o��pāsЁt�Bu0���T�w��n�r$�t8���� mh�!8�rH�s\�u�Zv[���u@�c�!2\��T�r�X�h�g��j�k��lȀr�@s�t�@w������������e��f�����h�����f؀z0��T�$��i8����~fP�sl��n��S���i@�n��)�eL�o�����8�t��B`i��l�r8����Sg���
�)x�o��s��uwm��ePw��L�t�����r��)��tC܁u�l{p�kD�cx�d��f��m��p��r��sĂu����a��d��eȄf��gt�h��i4�k��m��n �o�/pt�r��sT�t��uĈv4�y؈z؂�)����uT@	0"�L	��a�s��t�	��	�	P
�Ђs�
�p���C�h�������l�n �u�)�a<�eT�o\�r��u���"=��L�
(�t
0�sL�xԩ�
D�it��̳P
`��l��t������s�ăb؃h�i�n<�r��s��u��x@�����el,��ЃeX����%�g�m�[�s@"J�a���0�@)$�eT�i���,�b\�hx�l��tX������d���!l������!��rd&���k8��n���|�`�Ԅo(�m.�s�SH���f�gl!�a�hl8�mP�s��.��/�$�s�B,�a�4u$1m��D�aGct����b��r��uTl`�a��e�|�����HGd��eȅg؅k�r�s�t�K��D%$�Ѕr|u���e�yp����zR������n��r�)(�aX�sX��Ȳ'D�e�!)L�t�������d�e�l�l@"Jx�ad"!).ĆeІl�o�s�t �H#����m$#��r8"��#��#؆r+2(+�a�#��t,-$)�a<$��HdD�lP�rX�s`�t�Dh�$u<�s %�%�&��&PH�d'w���'|�m�'���a��cȇe�k$�t4�u�Izh(�X(��h�(��b܇n�r )�P�h\)���tT��@*��nL*l�a�Ir+(+�aP�ä,D�fL�m�8��,p�,!p�e|�h��rD.��-��h�g�.0�01��m��n2��/�@2���z,U��elH3l��i�5��3)Јw6)8�a@�cH�d\�el�fx�h��i��k��l��m��n �pL�r��s��tĊůvԊx܊z�5@9)x:T�a�\;B�<��;��d�l�>��@��@u��d,A�Au84.8Eu84.��s��"G84.�a�d�s�y�BdG%܉a�F��l�u�G�
�G��I��J�,K)4�f<�sD�t�KJ�L��L��Lu�.l�c|�e��ph�s��tXN���DO�t�n��"$Rh�S���t�U��W)84.��s�Y�0Z��[)\X\)84.�^�\���k�n�s<`% b\B.�c��8%.`iaL�el�f��h��i��l��pċr̋sԋu`h\�nd�r�i�,j%kx�aDk�l�hn���e<o(o����l�gn�p{,t)|il�LuXu�w�xy��zD�dL�kT�s\�t�z��܋ax�b jc��e��ǧh،i�k�l �m��n��o�p�r@�sp�t��ud�Ä|��}34�ԁ�����M�����j�؄)tNl���e��k��K�d�%��N�Ol��s��d�u(��Če��Ȇg���jl����y�n��!��i���~k8�ud�!�a\�ep�uH�����,�28���@��0��L�T�i����h�m��nt����t���
P���r�J��a��o�u|�̍n���p8�Xԍa��l܍l��2̩y�iP�)��e�ȟ;���� �w(�(�h|�!4�cT�e\�hP�`h��J`tԭ!d�sH���|�s��Ďa��b�c�e�i�oh�pБs�t��u��\��cH�m�p�rd�uH��h����eH�\����n�������̼�Ta@�h�r|�����,�r8�4�ad�e��l@Um��o��r؏wD�\��fx�h��i$��d��,����e��
������bȿԿ/��eȏuHY4e4
��_Џa��i�o�u�`������rda��by��T��@�eP�h��i��n��rАs�t��
������H�a`�i���Z��Ph�f��td���p�nT��H����dxs���x�����h��m�uX��x����Ȑs�������ܐt���a�����a�dL�������fT�n0�p`�tH��T�#4�d4�<�n��H�e�W		�����a��e��i�ZoԶr����r|�� ��n(�P��c��txp�|�p�c��u�)��a�c8�e`�op�p��tĒu���D�`����hD�\8��e���P���$�k��,�nT�rX�!��a����H�m��p���$��h�a��i��o�%dVUp�c��a��e��o��u����%��0�u0������̒b̨m\[nd�Ԓa�e$�r\�uy|�����P�a�ntWr<�t�;�7a8�eH��H������@�����T�r�M�T���h�m`���p���7�������p������m��ȓe��������s��P��r��@�b`�lL�m��n��r�t��u��x����
ԓa�e��h��i�l0�m<�o\�r�s�t�up�zܔ�)\H�8�.P�s\�
��)����X�ap�te�������x�gL�K�����e��t�������c]�
D��d���r��s�t����Ȕ�T��h��,��D�SL��p�$�i<�lP�nl�px�x���4�g��n��VT��H�a@��
T�\�s� ���d�o���	�\��r�	��d��iؕk�n�s�v�
��x��y2�ĕn�)̕a�ez��U��n��C�
�����P\�nl$�e�L�c��f8�|P�X��a�Ze�i�o��uԖ��fP
��|�.��f��n��s��u�����tH���`���u���Ȗ��@?c����Zm��n$R�nl�<u�4gu�a4�eT�pt�t0JD�r,[t�{��LL�e��b���`�f�'h�a��e��iėo �ac��m��s�� �T���e�[
p_����̗b��m!ԗe�r4�M��p�;ti�0�h�m@�nT�r`�tD ��!!�8�s�?��!��L�gx"J�"P�#C��et\w<��p$��l�g�#����rh%B��m��sP6�DD��@u��t$I�P)�a�el�u�5�hP���k��n�P��x�gTR�Q��b(�i8�lD�n|R@���R��0�t�RT�e\�t S���>��W��d�rPX)��uc�b��r�c)��a��e̙iԙtlc)�d)td��me�fN�l)�a0�eh�i|�o��w����K��l��p�utn���o���c\o��$�iD�nX�rqP�e����qJv��t��`�r���dv��t�b,w��n�����$zl�����lVp@3y��i(3u��e �s�1��̚rp!��ܚh(�l<�nT�t���ah�eL�il�o�ud�v�W��7���aPE�C��4�eL�i0G�HX�����n<���\�a��g̛k��n�,p�tI�м����s��D�2X�����r�C��e�o�rx���t�)t�؛rX�/��h����a��sa$�h,�r4�t<�u�����@�7�l�e$f��D�a\�vИ��5�6��d�a��b��i��n��rМs�up7���r�8��@�G��Юi�L��t�aȜd�Nl�S��c�Z0Zܜlh%�b<�gP�kl�n��r��t�'%4')�l0
H�d�/$�n@/)0�e�&�2; �:!X�s�8�`�g|�i;0@�>����o�E�
8E)��ep!84.h�a��b�!c�d,�fH�gP�id�k��lT�m��n��o��p��rL�s��t�u����a��b�c�d$�e|�f��g��h��iD�jP�kL�l��mX�n�o1pęq�r��sH�t��ud�v�w��z<��H!yt�r"p�t�Xt!|�l�"��b��e��hȞl�o�r�ps��t<$$�$��$���a�ø$*�$��؞�$%�	P%��%��))�a�r(*��+2�-#
�,��$�l`�r@�t4.��.)�3����n�5��5��\�a|�et6)P6�t�n 88�7��.��n�7����aПe�k0�l8�oH�t�8��:R8:�ȟi�n�r�:��8.�s`�?`�R�:����iP<�\<)�a(�r�/�<��=q ?��>!@�r@��x*a��p�E,���d�fD�l�rBux�eB)��f�Cu�B��l�s��uC����a�d�he�g�k�rl,�n8�sd�tp�uT�wx�z�CKP
�<DJ�adD�F��G�HQ�HC$�aI)).,JX4J�D�nJ��L�n�I)X�e�J��J)).|K�4L��K��fDM�L����a` b�=d�g�i�k� m�p�s$�tD�u�w�OCP��e0�'@Pu�s�P���a�f\�
S74�a<�i��DSf�SLT��4>i`�ph�s0Vl|V�����X��p�nHX)x�a�h��r��s��t�Zm [�Zu��eX[�p.)�]��Ģo�r�s|\̢f"s�tder�.	�.!��p�a�P?odcPP�h`�ml�nt�r��s��t�c������h��fP�g��g��X�i,hP�itkHk�|�c��e|ky(my�l����h\pģeԣo�y�t���Ђ��̣n������B�ad�r0�����p�����mP��<����ch�f��g��h�iP�l��m�n�o$�p0�r�s�t8�ut�xt���f�*i�����x�e��l<�u��8%.$�����eĤr�W�Фoؤt4�u��slX��XJ��T����c�g�n0�s�����P�d �h4����}|�}��(�e��ut�cQg|�u����<�a��b��eХl�s�t���4����L
���������D�\k��nxr��t������ �L��!ȥe��K �!ܥk@��l��p���8%.4�aT�bt�g��lo��sԦt �y��),�u`��	h���@�st�H�u���X���`�gh�!h�a|���������8"B���l���qk��t'zp�� ���e����n��!ȦeԤh��r�s�uyw`.���D����0���*�/���l����8%.h�c��f��gܧh��i0�kH�l��n��o
s��z$��x�,�p�h��m��)x�a@�0����n<���bP�!��e(��n�����ȧ���Ч�`���Fn�sD(t����n$��x�v�t�2$����n��)$�e@�o�x(�C`�ax�e����yX�u����e�l�b���8����u��J��e��Pȴl̨eبi|�2D���Ĩu�������.�e��T���l$�!��a$�h(�s��L�|���0�ph�s,���AH�l�AP�htJ\�c��`�l�:J��oDS��Q����m$f��
�xa�c�d(�eD�f\�kl�l��m��n�o�r �s0�z�kLpku�hm��l��e�r�m�
�ou4�o�m���lp3Pu4`tu<�lT�r�u4y�{�|��d�a��i��o~0��������i���8%.̪gԪhܪi��n�o�st��Ct�E��� ����_��eȇ:	�)�Ir�s������dp�tܙ�<�ih����<.|�a��eD�iȬo(�uP�y��P=(����t�.��r��s��2н�L�����t��uX�.�a�h�i�m$�r0�uPR���ЫiH�)ثrd�\��!������d�t������'.����a�����s���@�<�c��gl�nt�p|�r��s��t��z����%��h�x(���c��t����%������s�M@9�	����c�i�p�s�����2\���t��X(c���P�t������D�� �4�f����I�L���<�c�����|�a��e��oԭu���Dt�b��i��rX��b�$��$��l<$��b̭r %b013�p��ܭ�6<�aP�bd�f��hH0i��l��m̮n��pL�r��s��t�u�w�Tx7u�5B4�l�8�p7H�j<��;��\�e��f��l?Jt�u�>��|�l�AuP0a��e��k�BhC�8E�Įu�FJGu�e�s�u�G�I�|J,K��).�e0�f<�sD�z�K�k2�K�n�K��$�e�L����L���ad�c��d��iXN�P�	����l�g�Nt�r�N)��epP�S)�Sp�W�W)��aȯsܯt�Yl<��x[kZ!Яa0Z��[���e�[u�\P�f,�l�]�z;@�aȰe �h8�i��o��u��Èz����d\�sl�t��u��z4���C
ԁ�d�s��s,�x�m0ccx���H�t��u�������̳��n�����m� *. 2c�dX�f`�g�i�'q
x��.(2e����8Vc�e(�h،� �rr),x�����0�b�c\�eh�pp�s0����uT�s��ԛ���
|���x�n��s�!Ԧ��s������f��)�a �c,�e|�i��o��p[sȲt@�u�z��\�H�mT�nd�uH������n�����̼dhT���blmL�n`�r���H���D�t���x���X�lt�s��w8�����dPXn��l��fd�%������a��e�Zo�u ��d�'��aX�e�i�[o4�r�Wu��0����z�����n�t��������s�3	��� ��t�;(����)��!��a�e|�h��i�oX�r��s��t��u��z��H�P�s����|�b��d��k̳lL�m�nԳr�u���d�0�P��t��%��eL����D�d����s�������p���0�e0�iX^m@�n`�rx�x�� �T���8�t�(�)L�o���T�f�1ht�u�\)\�o�	����d��m��nܴt�,
������g��sp
)�2D�ȴrL��дe�����f��p�r%�=e� ���aD���g4�i�PC
�,�e�2��@�n��s�;H�al�ot�u�J$�nB��t�{N���@�n�#7h%��	hncܵh��l0�mP�n��p��r�s\��\1�	�r�1�4�3�a�e�i �p`4y�4y�5�|7'P6��(�l@�s�7��8�8�H�ad�s�t,<-p�td<[�\\=��x�.?)tNl>����b��eȶiԶo�s���H?��i�?�e0@zP�/d@!ܶp�t�@J�A<Bl�A��h�@u�c�op@�sL�u���hC,�s�C)4�eEu$E_ '��T��$IP��b�c��r<O��N��|�s�lB��e�p�\o����lp!��P=.|�b4�c@�d��f��g �h,�iX�kx�l��m0�nh�ox�p��rL�s0�t�uh�w$������al�b$�c��d��e<�fH�g(�h@�i8�j@�kh�l�m4�n��o�pT�qx�r�s��t �ud�v��w��x��y(�z��Ü"���a��b̸efظg��h�l�o�r�s�u �w�"�Xt��wH#��ĸn4$��$&$%�P%��a�i`%K��J�%�&\ &�|&8%.@&��(�e�)`�ih�lp�mx�r��u��vh+���1���+6,|���,���a��t�,)4.7�._�.��aܹi�n�o�s��x0��Թo�0��/��bp7��5<1J�e�2x�1���o�3P@�s�!�48�tP6�l~s�5��L�e�qi��kp�l�6#
�7���l��p�<�@>M�@���n@����iкm�p�t�u8�%`AJpA)Ⱥa�f<�@B�dB���sP���B��n�C��B�l��uC�� �a|bh�d�f��g�h�l�n�p �sH�t\�z<D		|�a��e��rdDB�G�
����w��z�D��rȴG�D�F)F!��lлs�F��M���ػ�(G��e�ìTH	�i@�y�H8I�4��IUI��,��tJ]�I)@�rhKd�J)T�w|K��L��Kp�o��p�Lj�L)��l�M�DM��n�L����aؼe��g�i$�t�uLO��N�мi�n��`O%�e�Op�PR�P���iP��nS�0�iDS��T�8T8�uLT��	@�ax�d��e��h��i��o��p��sнt�vPU||U�UxV�0VE�V��V��r|V)��e$WL
,WȽa�o�sr��u��|��%�W�rT��PW����4X+�"����HX)�!aP�ed�ht�r��s��t(Yy\�lpY2�Y��Z��Zl�a�Z�D[���nX[!��a��iľr�[2�p�	����n�[\�J(!̾e�\�Ծs|\�b�f(�g8�s\�tp.u�]���o �z��^-h`��_u0�lL�rT�s�`��`��a!P?o|b�t�aX;dc��	c��f̿h�n�r�s$�t4�u�c��d��|�����L��@e��f�f��Ŀn`h�,h��ؿd��)�j!�a�i���mHk�|�c�s(l7�lb nD,n��,�bL�cT�e\�fhnD�nD�n�P\p)��a��e�i�ll�r��s�u�z�p%p����c�u��u��e�t����d�t�|b�|�a�l��}�d��n�~��P�s��		�����tX��aH�e,jiP�o<��|��L���4��؀�� *\��X�.\�;`�e��i�����|�tT����a��e��k�p�t��\���NP���5
p��e�o�2D���f<�st�čЍ���i��B�e�)D�h��l��o\�%lrt�8�eh�i|'l�'rp�s@�wx���%�� �����(���T��!
��a��e��i�o<�r��s��t��u��y���l��������b�c�d �g�k(�l0�mD�nL�rX�s`�th�u؛��k)�)�h�LԜ0�%<�m �������Ыi,�%H����4�P����p�rܟ��x��4���0mi��l��r��s�Dx��xH�����e,���(�.��p���0������e�U����c�d�mЫ������r,�sȯ) ��X��\�ah�ep�u���ذ�����T�n`��p�K̳2,���x��$�س)��a��t�X�J��h8�h,������m����s���<�84.H�a\�b��c��d��e�f(�gd�h��i(�kD�l��m��n��o��p�r,�s��t��u�v �x�)@�d$�\��T�a��e�@l���p�up�s@���x�nP����ht'�����i��t ��C��e����d��e�&�y8����i ����t���a� �t���s@�u���a��l\�rP���!� "�8"��H����$�%��r��s��tl�y4�ux�e���8\�T���	�'.��b�c��d4�g��n��s��t����j/��c��r������).�b$�d8�eX�k`�tD)�s��u<�4��0�uD��`�XL�r,��<���D�b���T��T��8�h�r��yp�e��|�s��t�����bȓP��r����e�i�������_��a�e��u��d!��c�����s0���r0��C�a4�rx��tX����|����<�aX�ed�sD�\k �7<��L�l�sl�t�e��o��p4�lL�M���
8%.��a�d0�e �f�i(�k8�nD�o`�sp�t��z��t����)��dp�����t��)��e�u���0�3���p��d"!0�i��_��%��L�ml��T�e'z��!ys��w��z�����e���H#�P#��������������t���4u`�)��ix�d��u��s���a�f�r��
��Xu����8%.`�ax�b��e��f�g`�il�k��l,�o��r��s��t�w�z����p�g�uh�E��J8����'g��m����i��r(�\\�
�����s���)��o��r�����+������t��
<���h(�nLzs0�wP�!��e8�iH�r���l�3	�����;@�o`��� Fs���T�n��l|�a@�o4�x�%(�!��ed�����������,��)��a��!F.��k,�K��7�]3L]����b�����r�)��eȴ��a��"T�|�m�	 �aX�e4�hl�i�kt�s��t��w��yT�d�l�3	,��x����<@�|�e�l���p�C$�����t���a��h��o�x��|���d��r�t�)MlA\����o�		�8x����eȩi4�z�o�`)h�a��e��l�r �t,�u���xK`�h\	��Pt�s���|��@���	L-c��e��i\
!k
�
����s������������o��u��ð��	�����m���a0/!�r�L8�r��%�)p�a��d��e�wo��r�s�!�t��h�tD&y&%|�aD#��n��r��t|'h�'����a��Ô����P��u�(�����d)�i�11;��e�o3%�sH3HX4� �t�6!�:l4�i�G���8.$f��a��b��c$�d`�e��g �kl�l��m��n��o�p(�s��t�Pu�x�z����e��c�}k��m��r�f�4h��h�i��i�j[pk)��hWk�CM�kP��m�r�k��e�il�
0l%�ly�l�aD�eP�oX�s�_m<�o�m\�m7�mu�'.��b��n��s|{�	@���x�sDn��e�pR,p���es`�c��s��tXs�@sm��ets2�s��a8v���n�r�sPw�w��w;�e�wNy)T4eDr@�s`�t���p{)8�p�{2�{L�r�{!T�e|�x�a�{����a�?b��o�y`�|�y��l�����a�d�e�~fX�g�h$�id�jt�k��o��st��v��z���:	��)��u������d0�f8�h@�pH�s�؃�x���P�9��!P�s�f
(�)4�)l�a��sX��D�_ ����l���).��a��c��p��t��x��xh*U$��Ĉ؈����mx�n�s��J���84.`i�����a���'.L�ah�ct�l|�otp��sĎyH��@�T�u�\�h�����БJ��x�i�����`��ԓ��l������a0�e��h��s��u���l�%zT�����r̙��)��i��ܙ)�a,�e$��0���$�i�7��)t�a��e��l��n�o�r<�s\�à��� =p��h�l��n��r��������8.Ԧ7�����t$�����nL����o��uȬ(�K��JX����e@N�L�����b���r@�J�;�a,�o4�u���������P���%���H��<���p���!��a�Kb��c��d��e�gx�i��k��m��n��o`�s��t��u�z���̸u|����e��t����	��b�f�k$�l,�m8�n�pd�rp�u�%��-������f�6�X����tx�����x*a0�L�gT�w�8z��C�JC�Q�����\�m���f$9g��s�]������e8��	�g\|�����m�������4��D�$�)��h�����%�h��
��c,�e8�fH�gh�it�m|�n��rT�s\�x���
�����$�n�����(�@�tX�u�X|�P����`�m��)�����8.��a��d�g�}t��7����)��l��u������8.��a��f�g,�n@�tL�z�����)��o(�\����n�!�e�o���@����J$�tx����78�r��,�) ��o�`�ud�g@�l�e��m��n��������p����<~d8<s@�J��4�	�����e����b��f�p�r�u��P,����\�����f���8.$�e@�d�����P,�fL�s�b� bD�eH�h�!X�hܥkx�p��t����r��\|�����r���o�����i��sd�p�����mx���� �����f�?r��s|�j�u��t(�Bp�)8�a|�c��e��i�p �s,�t������0�bP�r\�sp�u8���� c��
�Ns��(��h�s��)X�<e��i��p��rH�)
h�����s0�����n���h���؀z8���x4dX$sl���n�P{l(���������f��
�y��)�z7�J\�ah�ep�i,_ux��L	L��eDP�rH�p���J�6��b,�f\�gp�h��i��k��l��m��nخo��p�r\�s,�tl�v��x���\p7��.��eH�j�l�rl8��7��r�8��8��i�8K<(�;��$�e@�lH�r�<��<#
�=��@�ux=)P�a ?y�>��h�e��r\@��@�@u��r,A���r�Am�AuP0aSy\F�LF��n8Eu��iPI�G����o�K�,K����f�t�L��L��t�a<�c8�d@�eL�gT�q�ÀN�DO��jnPn�Q~�S�'.��a��e��k��p��s��t�S�<Tt��2�T��e�T�
�T���e�U,U)��eD�p�V���u�U��e��o��r�V��Vz��TW���uX:�����W)�W)$�a@�h`�i�XeP�aX�i���X�Y��P�o�[�x�e�Q�\�\P�b��d��f��g��h��l����]��]��e�	�(^b�f�`^����r�^Scyc������c)$�a4�e@�fP�hd�ip�t�Lu�cy�i�`h,�nk�m��lH�op�	hn��\�p�w�zB��a��o��u���4�B�s�z����s��t�mԁ���s|���4�m�)��s�������̳����	t�L���t��!
@�a��c��eH�ht�i��l��o��p�6s�th�u�zZ�\��cd�dl�lt�m��n��r|�u���p���%5b\��f��n�H9�N|h����e8��i��m̼��h�l������x�)	���T�84.��b$Zclm�r�s8�x��lx����e0�gX�lt�s��w`�h�R�����)@�aX�oD��������`�p��h�m\��(���g��l��oP�	��u��e������n������h��r$R!X�.������t������idVoԶr`�u��0�b8�fd��a@�iX�rD��0�����������sP�tL�t�/������`�mȷn|�s�����!
��aX�e@�hp�i��o�rx�s��t��u���H�P�s������b��g��k��m�n,�rD�tP�uD�L0�lP���it�C
l=������dx�g` ]���$�b��th���)<�o���p���	84.��h��i��l��n��r�s0�t�_u�������^s������mT�����e��iT�RT��������a��g�������������L�k$�td�����r<�8.<�h\)P�e\�oT�u�	%`�S���\�o�	��d�m���|�b��fp��r�w��D����i��
|����l��s��t�����������}2����k �l�;��a�ZeL�i`�o4��~�`���u���(��D�z�D�ex����X�cp�sԦJ��c��p��t�4L��a�'daa��o��pu��c���0�h�m��r�>2�!��n�!����a�i@"u�&0%u�nh%���al�bH]d��e��f��g�h`�it�m��n��o��p��r�s��tD�x���4'�|�a��sp(N�,m�*)��sd-P�e��f��t<./!,.a��e��r/)0�	@/)a�e�nl0�o8�rH�s�*u�#��/�b$�r���0���p�0+�0�41J@�a�l�f.,2uT�d�6�P6��l�ep�f��l��p|cs��w �8���aܹf��i�t��w�8�;��<��<)	�=u\=����f>���n�t��@1�Ax�@u�e�op8�s|�t�u|� �C)0�aX�ch�et�o�Vpdlt���
�C%hC`�nDcDD��a��o��r���0D8��`�������[��D�8�e$E� '�����pE)8E)��a�e$�o<�r�E$�E����g�r$F��d�g�;�F���f4�pG�0G�H�$I����bd�dt�h��sTJ�`L!�K��l�ldO�PX��cu�g��a��c��m��n�o��s�t �u�L�g����r�hhj�Xj����p�j%��o|jh�jLl<l��exl�l)X�a��e��i��o��t��u��w���\m��lP�ch�n�m�Xrr�q��p�k\o��x�rpu��x4d�t����n���dv����f�wB�-�x���eHx����f��gy��x)��s$z��eP���z���cp!��a��b<�c\�d��f��g@�iT�k��l�m0�n�o$�p8�r��sh�t��u��yD�����a��b��c�dX�e<�f\�gd�hl�i��j��k�l��m�n�o�pT�q��r��st�u�v w��y8z(��H!��"��e�k�r�s��tH#�|$n
P%�P(��'��r�&$�t@&��0�hL�i)u(*�)T�ap�rx�s�+;
�+��,T/
8%.��bLf��g�k��p��s��t�v�@w�.��e�g�i�n$�o�#$t'��/�F.d)�X0�x0��@�e�0H�/\5��4,�e�3��4�s�6��5��L�tX���t�7`�k��n��t�<u�7��l�ahgd��e��i��l��u�6�9��:%8:���r�;U�=�<!��o��t����?���t��ypA�@���m�C3	�B�l��uC��
 �a|b�cl�d��e��f��g��h�rlt=n��sH�t�z<DB�h�E��t�sPE�|�r��t�E�E��F�F!��r(GrI)).��a��c���'�I�I�����DI:	�J��Ku|Ku�r�L��K�pN�L��0�b\�gd�ox�r��s��u��z�O�$R�p�dDR�\R!x��R��m��r�R��h�R!��cԿ��S�S�LT%��p�slt���0V���a��e ������y.0t�V��s,�t|V)�e4�u���0��4X��"��<���X��X��P�b��nHX)X�a��c��e��s��� YmpY�(Y��l��rh��Y����d�Zu��e��p<[d|\��f`in��s``�_u��g�bJ�sodc%H�hT�id�nx�r��t��u�c����d�������f�f��@�n�f�`h�,h��\�d\jX	�i��p�gLm{�l����ro)�?c,n����s\pl��e��l��r�y�u��t����eX��\���u��ht�H�)�a8�eL�s��u,�ì����ܟ��$���0mi�س)D�e<���b��c��d��e�g �h0�i��ld�mp�n<�oh�r<�s$�t4�u�|xX����P�)|�[��e����i��y�;��n8��0����8�J��f�n���	t����a$�%���T���(�f�gL�lh�nt�s����J4�)T�a����\�d�����o��t�(��xT�=����m��u������a��d��e�f$�s0�t�����(�D���k��r�t8�����\B3	��)�.��u �!�e@��P�e�������<�nd���D�r���l�\�i���8%.��a��g��o��s�t��)�pb��l(xr,�u��%��h�!��e��l���=����rl���k��o��NX*u����!�a �n0�w|�\ ���(�i��)H�u��P��	�����P�������'.��a��e�g�i�s0�z�����p��y��?�����s����n(r,h1 "���n8"����P�!��`�c�dD(t����n��-���0�JD����nȴ)$�e�h�a��e��h��s��t�u|��p��T�`�l�2���t��T���rx����l�x�)��a�e�o�p������g���r������@��eT
r��b�$�y��aGh|��`JP�id�H�l�N�:J$f��a�b�c�d��eP�g��k�l�m8�n��r��sP�tD���e�d�}k�j��i)��i�l�l�ae%Џ�r�m���d�*i�l�r<�t�o�ru0�eTr�@r�(�r�s)H�iT�J8v���e��"��s<��\�o�(yh�h�(t�c�v��s�S|	�y�Ty��ry)��a�i�u�zu�{u|%�a�c�z�{yD���e�|��h8�2�M���`�p����InT�u���(�e\�g�it�o|�u�����!H+al�sP�� ���������'.a��i��s��w،�	@��������4a�c�e�s4�b@��a[n��w��h؏�
�UБ)|c(�e�i4�tT�28� �r�������i��<�������a8�hp�rx�st�u��z\�/�B��a��u���p�$��J��a�r�� *bȰ��.�;��e�l�c�lحs$��h�7p�)$�a|�d��eh�i��p��s��u���dNb@�kH�lX�nt�uT��������0���P�g��sl�z������l0���sX���i�l�n�r@�s��*t����t��2l����n�����i��!p����a�t0���!��w��lh����a,�e��2��$�ch��<�8�a8���|�b�If�IhX$s��tl�L�n��s���x�c����lP6����m��n0���P��l(������B��e,����i�s6d�aP�bT�d��g��h��i��k�l�m�n�o�p8�r��s�t�Tx1yx:�l�ex�r����:d�sH;�x=)F.��a�=��=����l�>%�@��Al,A)��l�Au).8E��e�EG���e�s�G��r0H��I_�J�,K���0e�tLM�T�r�L��,�ad�cd�d��e��k�M�pN6�N)\�at�rO�O�DO�|�r�PJ�S��tpV)�V���r�U��e�o�V��W��\P�Tc�k,�l�^b�c)�f��i��o��t�LukuD�e`�l|�oP���k��$�gP�pX�r�Fz�k,�r�����8l%l�ihl|l��lt�rhn�r%�q��nxh�w)��a�zB��)
��a,c8elixo�p�6s�t�u�\��d\��n$5s����%�����̼<�hT� �fPr,��x���Hfl�w����T�i��`n����h�r���e�oԶr�u��ôh
 ��d���Zr�t����������g�ld��a�������`�m��!8axe|�h�i0�mDrPs��t�u��z$����Xb��k�XmPr�\eL�R���T���dtp���ll�r�t�_u������f�g�l�s�tn��d�����\<�a���	���mn8sp
v���s|����s���������0s�;8aJ�Zahexk�p0)���lpaL���ax l����m�r�!���th%�a�h�ln�r�shtP�0%)\1y4�3�as�5��8�8dDf`g|�ilktt|z49�@9)0a`��#��:Lb�:!Te�;cp<�/!P>>���a�u���8�fA!�@u�a�i�o�op�stL�uLB�B)hC�S.s�C)�e���DUDDe,o4r�[��D���$E�<e '��D�pE��P�u8E)\axsHG+�t��
$IP��b�l��n�s�t|�2\M�nDM!�eM��l�O)TidO���s(P\�P)o�V��V�l�V��lPXl,aX�L�r�l)�eup!84.blc�d�fg(hHihk�lTm�n@	pL	r�	s
tP
u8vD����Da�bPc�de`flgTh�i�fj�k8"l@"md"n<$o�&pT�q'r�'s�,t01uH3v`3w��x�3y�3zpÜ"�DgLhplTr\sdt8�#� #��0�4$�<$5P%�%��%�@&Uxh�&��e�s�t��'P�n�' D(�P(;�a�'��r�*��)�d�e�r�*��+��,r�-4�.�e&T/m�1�8a@n�1��2��3%Xe`g 4�04��5�hqaxo�6d�7��
8%.�a�d�e�lmo$p0sht$hu<yTgÜ7l:�8:��n�t�:���t@;E�<y�<!�a�=)�o�=�`�pP>�@>)hl>!).�?;x@�pnxr@��De�n�o�s�t�@A�@G�����0B��pC���f.�a�b�d	g��h	i	k�rl	n$	o�p�r�rsH�tT�w�BND"<DF
0G��GM�H��Hhwuw),	i�K4	s�L�K.0"a� c�=d�	g0i�k�	l�	m��p�	t��u�O��Q��Q!�	e�Q�S�LT%8%.�	c�	p�	y���
U�	w�T�	h0V^X
�	lXmHX� 
a�ht�m(
sH
t�X2�Z�8
a@
c�ZJ[JX[�|\p
f��g�
i�
l�
st�]b�
f\.�^m|
r\^T�^~��Z�_u�
b�
g�
o�
s�
t�
w``p`E�`aah�`�
e����a�a!�
e�UuP=.d�stU��rdbp e8b),idcP	�b�c�eH�h�m�n�p�s�u�c��D�d���&�3�d�dd\$e~�g�,h\hi�tk�Hk��c�s(lo,n��m�nF\peDu�y�v_Hv��n�t��i(n0r�w�Ty���e(��D���<s�)heth�u����`nt��'m�)
�a�*c
e�
i�
or�s�ty�
��\*g�k�n�t�"u�����rl�n�z�HC\��H�lH���<.H�)�l�
a0miP
lh
n�
r�
s�����\0
n����8
�H���D
�ܤ!�^s���\
t����t
b,���|
o���
e0��ܪ����
a���
e����
b�
p�
rL�z�� ��ܟ���
�������`tX�;a4eDotu`�`������<b�lp�sT��,���X��Ep�li�n��kh��X����rس)�o�p�t����r������b�u�'�a����)��h�rl������<�84.�a�b�c�d�efg,hXi�k�lLm�n`oxp�rs�t8u�,w�|x�
z�K)�)xp�s�t<�H\�h���@l�n��nP���a8&|���e��8���i �/�nP�%t�������
��.@n$���eHmPr����		4�NT����dpf�g�n���L����)xt��������g�k�����ux��e�t�i���a�betwi,l<oDu�����D��km��n(�������J��!$a�qø����tl�8%.ȫbhippxs��(L�����%8%.�a�b��c�dPe�gT�h\�i�Gj�ko�s�t@uHvPw��Bt��T����)	�bdf g(h0k8p@tHw�����\p�P��P���������$Dbdiln��<��xeP�P"�h�!�b�e�s�t��<.�nlz���4����l��e�k��L�������!�a b(l��n0rys8w��z��Jp����������H3r(�C�����uXb��s����pf�o ���s�s����8%.�a@b`etf�g�h�i�#k�l mdn�p�s�tu�Sv��z�Ä��bl m�yn��p�u�{���K$�84���(.Pn��)0eXiЅ�}���lb�������o���

�r��/��������P�!�r�����n��������|��(�!�i��������[4sX�!aXiD��/,�'8���<�P����Pt��J).tt@�����
d�|s����������,��!�.�a�c��U�.H���e��l(��h��!).��r��@��p��<.�(cDedkh�o|�pxs���T�<iTv����R��l\a�5x�)pi��$����d�k�n�|p�t�u��a�htu,z0�����p�X	������a4��J�u�c@�!s�(�$u �C$i|�hnc8GgPpXrL
\`)	�a�f�i�l(o0r@tdu�Ü�k�(�s������
J��l�P
.d���e���iÜ�x�n b�%s�������!8oPs`�\t�+�)�!�a(d8e�gh(iXl�m�n�o�r�s4tHu�\�t���c�d�k�m�np�������	����d��fsz,g\ "��l8"����"$.	D#0f`iln�r�s�z<$\$$��Xn&�|�a|s$'<�'%�a�s$2|'��m�5�(!�e�(��tP)�,hb�n�n�n������Ø)�e�)Lzs��))ux*����d�@�,��4s�,��<����+��lixoH�`-<�c�-��c0.�
��\�.B�.)�e/y�0�/���rL1���Ut1;�a�o3�	3���c�sX4�\oce p��q�5lh6���a@88!,s(9�L8@m�:lta�e�u��P=%�:��ln�r�u�=%�>�l?%�?����T@�rEb$fa$btc�d�e�g$k�l�m�nXotp|r�st�v�x�e�j��i)l4uHk$_G�^��<sD]��Dr�lCPe�k\tpkuhh�l���.�e�rmu�mJ�b�l�n�rDn��o�a|o,p��er����ohv8v)�aer�$M�vl�w��ey�Da\e|i(l�mDrTy�Pl��rDz%z2Tr̪��zhg�z��pn��[��)�a|�`������o�p��V��
��.�a�dfgh$o0sHtPv����Ȅ6��Tt��P�� ��r��v).@eȇyT�
Ĉ��)X�blkl��C�u���a����c�e�p�s�tu@���w��h��84.h�r�i��%Б�\���7n��u��)���(h0i8r@sht�u�����u\����Lc�R�2ԗ��Tc�!\e�s<J,uxaИ������)
�a� e� i� l<!nh!o�!r�!s�!tX ��y���d�k�l m  n4 ud����� g�;�T�u8�v���� s"-�, sP�\�P@ hh r<���H �|!�t��x�(��)p a0���x f� g$���� r���Ԫ������ h� s����� n�Z��u<-n� sL�� e!i!o !u,�`�ED�!eȬu(�u,!n��#|�JX��4!aL!e���$R�L���T!t�\!r���Pt!f{l�;,�o�`�l��)�!a�!e�{zd�u,��!i��������!k\�nH�!�!a"i"o,"rܴ�����!t��2���"pt��X�;$"u�Bp�JX"ep���\OsX�L"n�!�"a#d$#e�#f�#gp�i�#o�#s$t$u�"���!eD�"b�"d�"l�"n��u(*���"a�
����"l��D%p���"����7�%#g<#t#nH#r�#s�#x�!�}s���\�h�uk�#w�#zl#ÄS���d#�X`�)x#a��]D)��#e�|_`�l�<$��f�#r %�(�'��#e�#ph*%�.)�,)�#h�101��$f($n4$r@2�ܹf�2�684.�$b@�c�$d�$f�$l%n%p %r�%s&tpTv�&wXx�&z�8<�$a�$ep7�$l�$s���؀�	�8t
x:��$rH;�
�;�$e<�	�BR�$u�Au�$e���G��8%.K�,K��%a�L���8.H%a<�c|%d�%h�%m�%s�%tLM\%dd%kl%l�|�tM\|M�	Og�N)t%rPmQ��Qs$R��S��.�%h�%p�%s�%txTy�Tw,U!�V��U�%e�%r&v�V;��yH��W��&b�W)&aT&el&h�&i�&o�&r@&ä������TX�8X��L&id&lpX��X�|&a�&i���Xp$Y�Y���&n\Y�HY���&p�Y/�[�X\��\����d�&f�&lls�]��^��cH�a's�w<w)'y�zB�ah'eL'ä����,'sx���4'u����@'�t'����%z�\'s���`�s����'aX(c�(e�)f�)h�)iL*kX*oh*p�6s(+t�,u�,z8(�\�d�d�'l�'n(u(v0��p����'l��\��'g(t<��D�L�sл�����n�YsL(u���$(�o�d��D(s̼�ah(h8�H�e�(r�(w��Կ/|(o�]�L]���(d|����(r��_�(e��T��(b�(d)h )nH)p\)r�)t8�x��hE��%�(e���(r���)eH���$�k<)s��t�+���4)pd��,����ox���	P)f0�g��h�)i��k�)s��w��z�)Ô�| �:	,����)�8������a4��P�i<��D����)f��)�)o��������)p���)m*n *t$������*i����e0*r����(�8*l��l@*a����P�p��r��)�*a�*e�hTVi�*o�*r�����*s�*tXg�t��|����*i ��*r����*n���(���*i+o�Z�dv�lv�*e�����X����+kT+t��ud�+at+e0,o\,rp,uy�,�\B�	���L+.d+e ���3���l+.�+i�+l�+m�+n�+r�+s,u���|��+f��3�F.��<����+.�+tx������+.���<� �F.D���K��K��,e����,f|���$,p@,r����	H���H,ct�;P,a����h,mT�u�,b`���|,���
��u�����,m�,nh��
$\g�,rIw��p�)�,i��),-a�-e�.h(/i�/l�/m�/o0rt0s�0u�z�-�\��H�-s���� -bP-cX-l`-m|-n�-rp�t��Z���P����|�)h-a����p-z` ����-b�-k�-mx��� 9�\d����-m�au�����-�p���
84..a(.b<.eD.g��hT.i`.n�.r�.s�_t�.u�.v��y`��.u�4.e�P4��l���������L.gT��p.e�.t8�84�Tbx.. ��.e�%�.n�.s�.t��d���L�_sx������.r\�.e�so��r/u�	K�z��)/l�	��/kD/nl/t�I�	���</f�h\/id/sP
�p
L%<���Lt/m�B|/el$�e�/o����/m�/n��u�/e�I��P�/s����B�/s���/s�;�/a 0eP0i\0oh0��c<0iH0p�����40fX�	�\�g���p�p���$a�!�0a�0o�0p�0t<�0ud�` L��0al\4�0r�X�0opz����S.�0m1rx ��!u�!���0a1e�!2"1.(1s("�h%���f.�1a�1b�1c�1d�1e�1f�1g�h�1i�1k2m@2n�2o�2p�2r�2s�2t�2u�2x3z�2�0%��1r�&t'�4')�1i`((�)��*���sd-�1e�-u@/�,2(�2�P6��8%.(2bp�fx�g 9m��r02s82z����7��8�8�X2a`2g��i�t�8x�:'p2.x2l�c�:��<(\=�>��@U�2c`oe�)l�2t�ADD!$E� '���2�8E���a�2e<�r�EXH�H��H�$I���'.$3b,3r83s@3tIS�N�� jcdO�(P3�PlT3e�Q�DrPX!�3e��Jx\l3t@\t3s`[���3i�g���f.�3h�3m�so�3r�3s�3w�iXj���Hu`k�k|c�l)4at4e�4i(5o\5u�5wT4�|m��l4d,4g44n<4u�m�m�tn��n�nd4r�n��D4�<5��n��o\o��l4c�4n�4r8q�Dq��4eq���4s�4tPq�L'w��zTs�`s)�4e�q���4whvu0u��4a�t���4g5n�I�pu��5h5i�ul�v�dv�� 5r,��,w��45ly��x)H5sHx��P5gt5r�y��y��l5kLz�$z�5a�5u�5èz��z���5��5�p{��{�6����5op!B
�6b�6c�6d�6gxh�6i�6k7l 7m,7r47sT7th7u���5ap7b@9cx:d\;e�;fx=g�>h�@i��j,Ak�Al8EmGn�Jo,Kpęq�Lr�Ss�Wt0Zu�[v�[w\xL\yX\zX:�D$��"�6i@&J�6h'%�&�6e�6it'%�)!�6e�*�.�37P6p�5���6e�ql�7p�7��7a�@p@��7i�LuLT�D7eL7iPU��UJHX!`7e(Yy|\v\p�7a�7b�7e�8i�8j�8l�8o�8r�8s$9u89y�7��q�p���7l�7n�7rxr�s%t�	,t���7�09�XtC�t *.8a8i(8nl8r$wTuHv��P9n�w�88dH8sPx�x�x�@8e�z��z�T8nx8sTy��\8i�zc�}�8t��8e�R��X���8i�8o�����	Ђ����\�;�8e�8i��RT�N�6h9k9t�%z�����p�9aD��<�s�����u�kch�ep9h�li<:k�)l��o��9bt�
d9a�9e|'l�'m�9o�'r:t:u@�w�9��P\�P�9b�9c 'i�9rP�AxPFm4����9k��T������9�Е{:eD]�P :fؕP�����(:rd�m0:eL:sԗ)�+z�c��TW��)��)����K�0b���a�d�:e;i0;oH;r4#tT;ud�>��:b�:c�:i�:n�:x������:n�����:e0��ԩ%ܪ���:a;r ;v��%|��������(;n@;sȯX��h�e���<�)	D,b�;cL,d�;h�;l�;n�;s�;t�,xP�u$������!�)84.�;s���<c<mHu`��	�;a<e<<f�-i�<l�<o�<r=s\=tR@�	�Ai�tn,<r4% )�
)4<ah<i�<l�<o�<r�<s�<u���`<n|<r�<z������u,BLN�u��<a�<��������<�2����-a�<� ���-�(����,=mH!=a<=pT=u�2 ���4=eL=r��)Cl=e��h���L.i�)�=a�=eL>hh>i|>l�>n�>o�>s$�t���=b�=d�=l�=np��q�����=a �����=g$$�D#�=i>l@>nl���$u>i�&�' >s'J(>a&%4>m�)hru�*��T>rx*��\>e�-u�+��t>o�.HH0�/���>iX4�,1c�>i�>p�5h6R�:�:���>a ?ed?il?l@n0@o\@r�@t�3u�3w�@y?�l?P�?��?�3�T@8?iL?r�B
8B��0?s�Ft
E��D?t\?z�F��G�JJPfa�?e��o�?s��u�K<wc�?i�?m�?n(�p�?r�����K%L
�K%�?g\2h�?wPLV�M�M!�?el��Ou�?g@i @m(@o�Pu@"�Q)�QH@lT@p�Ru�Ru@@a|S��Tu 3ax@i�@o,W2�Vup@nhW8\7le�$fu�@d�@e�@m)n�@rAs�l�m��@m��'(�)�@u����4h���@c<e�BmAs$AtБl�J��)lAa�Ae�Ai�Al��n�Ar�As�At��u����TAaxAe��\AlПSP�\$����Al�������Ao|�P����A�L��A����l��)�ApH���u,Ba�Bd�Be�Bf$Cg<CihCk�Cl�Do�Ds�gu�Dy�DzhBð�DBbL�kLBr\Bu|����u�P�TBf�����A�d��0�2����xBm��!�Ba�Br<�����Ke�Bi�Br�Bx����0Be�Bs��� �h�u<�)�BaClCr��%��Ca���+uH�)Cl4Cr��/@�LCk\Ct �������TCu@�)�lxCr���CeȰ�@������Cd�Ck�Cn�Cs�<uh�!�CaDe�C��� �8��CsL���d������Cd�����C������Dc,Di�:lHDn\Drh��p�v��)4Ds|���<Ddl�&����TDktDw����)lDe���Df�Dp�Dr,�u�������%t��Dr�!�DtX����R�Dm���X�l(�)�DaEe,Ei�?wprUx���En����Er�������$Enp�u
hEa�EeLFi�Fm�Fo�Fp��s�Ft�Fu�Fy�dNb�En�Ert�u|�2����Er0����EeT�w8����tX��@b�Ec�Ei�El�EnFpFr0Fs���0���Est�m���p����8.��@�Xh���Fu(Fz0��<��<������8Ftl�@Fe\Fn8����dpFgt\��$���)xFa�l�r��)�Ff��!�Fut���Ft��Fn���D��pb\GedGllGptGr�Gu����Fa�Gb�Gc�Gd�GexHf�Hg�Hi�HkIl@InPIo�IsJt|Ju�Jv�Jy�Jz�G�,
�%@	�L	` bP
��Gs�
�D\p���G����GeP������Gr�
���Ge
�Gr�)�Ge�HiHn0HrlHtX��##g@����(Hb��dHHgXH���PH���dHt`BXyl!�Hl�Hr�Hs��7�pHGd�Hk�Hm�HnD�$��Hr�����Hg�B@��n8"B�Hi Io@9���Ic�"��",Ind"!4Ia�He<$�hIdxIk�Ir�Is�$��A�,A)pIe %��>c�%��'��Ig�'��Ia�Ie�Ih�Ii�ml�IpJt��	�(��b�Ill��)!�)�dh*�(+Dh,-y�,)JaDJe|�hpJr��`.�0Jd�-��8Jn\Jr��.%TJwP0�0;hJi01�Jk�1
H3B�3P�3��AB,A)�Jl6���Jk�Kp�Jr�JsKt�L�Jf�O3�TN�S�Jk�W)�&r�\�c`KbhKdxKk�Kn�c��Ka�Ke�Kf,Lh@Li�Ll�Lo�Lp�Lr�Ls�Lt�"�hdy�dh�dPpKte%P�P`h�Kc�Ki�Kn�hq�i�
k���&.�Ka�KeLlDk�`�h�MLe�k���Kd�k�Kr��8l�La L��,l��xu��l8Ledm%hnTLalLexLn\n�	Po�84.XGs(o��\Lr�o�pK�$g�pX�Lad�e�Lilq�q,t)H�l�irXuhww)�Li�Lz�wh�w��z��8%.LMa@NbXNc�NdDOe�OfPgPhpPi�PkQmPQnhQo�Qq�Qr�Qs$RttSu�Sy�MÈz�DbtMk|Ml�Mm�Mn�Mr�Ms�Mt�}�~E�Mm�Mt�~�~ $C
������Md��h` ܀�Mb4�� �hԁ��Mtx�%Nn$Nt�����M��Q��S�`h�����Nd��Pd�����,Nr؄)4NaPNl���)hNa��e�x���Nm�Nu��)	pNa�Ne�NiOnOoOr Os0Ou<Ow0����������%�NgP��Nn�Nu��������NrOtH���\�u��Dس�,����(Om����pOa�Oc�Of�Oh�Oi�Or�OtxuJ��)hOs`�-<���|Okȋ�H�����P�gDOn܏�OfH��0�<��Ohؒ��Ol<�ԓ��OeĔH��!�Oa(Pe4PhDPi\PlPnĕ�
8� Pt�)yh*�
���<PaTPe�*�uhPeT����c�Pe�Pl�Pn�Pt��u�F.�Pn�Pr������Pt��|bX����E4��Pr�)�Pa�Ps���$��d�C$Qa0Qe���Ԡ��Qs��QnL��jn84��<Q.�JDQe`Qo�y|��|Qn�F�utQa�\ęP���Qa�Qh��̌R|�{�Qa�Qc\�hRi�%z��HX��H���Qi��Ql(��Qh������kLRnP�uԭ!Ra�Re8Sh�RiSo0SrhSuhR�H����\����TRr����\R�S�`�����xRf�Ri�Rn�Rr�Rv����>g ����Rt�x%(����Re�RfX���y��� �n<<��;���RfD����Rf�Xr|�H�h���Sux�;$Sa�Se�i\S���`�HSu���PS�����m��Sf�Sn�Ss���8��H�+����g����Sa���Sa0Tc<TexTh�Ti�Tk�Tl�To�Tp,Us�Ut(Wu0Wy�|z\�TdTn��h\�JD��8�Te̼$ThT��@�ePTiXTnd��H�lTt����!dTs����Ti��������Ta�Ti�Tl�To(�p��2�$,�E(�u��l�To�������TeUoUr ��UcP�%��U(�%|�zXUn�) Ua�Ue�Uo�Up�UtĒulU��*�PUdt�0|���dU�l�v	��xUl�Um�Un8��	��P����Uk�Ut�qz�������Un�z$�6p� d�Va�Ve�Vh�Vi�Vo�VrWu0������Vb8VlDVmLVnXVs`Vu��;����0V.��0����[g����h�����hVd�Ve�Vw���pVr�����B)�Vex�5
�����[n|���Dmb�Vn�PX�.t�;�[a8�eWo�V��H����V�L���0th�t�Wf�������x��P8WllWn�Wr����@We�Wt��O����dWk|Wz`�]��V����We�Wf��]����)	�Wa8Xe�XhYi,YlHYo�Yr�YsZt���D���Wo�����WgXrXu0Xxx�����Xk���$9g(Xs�]D��p���TYbTXipXl�Xm�Xr���`Xn�S@�JT���hXa�Xe�����Xi�c�Xp4�l�����Xw\��Xe�Xi�XoYrg�XlC����S$	u�Xs�	����	��Ym$Yn�u��8Yil�J���@Yl\YphYr��8�fDutYa8)%�;|Ya�Ye�Yi���Jnu�Za�Yp�YtL��Ya4��X�Yr���;�Yi��MH%�Yk����Yr!Ze��h(Zr�h%
lZb|Zc�Zf�Zg�Zh�Zl�Zm�Zn,[p4[rd[s�[t�[v4'J�(�
`(utZe\e4d-���Zl@/l�Ze�Zl�/u00h\1h`4P
.�3�ZeP6:�@9)�Zs�8��Zd[g�:q[e$[s�:<.;W\=J>�ȶiP[n�P
.�JD[eLBy�@u\[it[tDD�G)8E)|[uhH3�P�[i�[o,U��[sXV�
4V�[o�V�PX)�[e�[i`[��Uc�[rL]��_�c \a�be0\ipl<\uD\ylc\e�	e��(\d�gu�g(�g���so�l)�ap\e�\i�\o\o�\n��JPq�|\aq���\t�t�v�dv���\n��l�z���\d���\r\pX]el]l�]r���\b�]c�]d�]e�]f(^g`^h�^i�^k�^l0`m<`n�5o�`p�`r bs(ct�v|cw�czc�w%�#ad]i�t��H]ltw�X��x]e؀_\����]ht�L�]l�dr�]s�]t��{ĕ�]r����]t�[Еu��]i��[<�`P4ef\el$'z&%^sD#^n���^e@^lH^rP^s�+�1�X4�T@�:��X^e�^l�fr�^s�3u�Ky�JJ|^e�Y�8v)84.$f���^g$������^e�^o��r�^s�u�������'._a _eL_f`_i�_k�_l�_n�_o�_s`u$`z������?i0_m��������8_i<�)@_e���@�X_mp_n����B@�)x_l��h�!�_a�_m8�'�_s����_a\�~P�\��_r4�J�_a��d�N�_a�+z@� �)`n���$\g���(�)`wp���hs��T`e\`i�`n,_u�p`zh��ܙ)h`i$#d"!|`e`h�c���`e�`f8lk���`l�z�z��
�`a jcadae`aflag�ai�ak�an�jo�as�atbu��/��)ar<���ac8ji4al<ar��܏��XVg�OlPazh�		ԓBؒ)Xal��!�Ol4����xaml���)�alt���ar�J�aeP�|�!�ae�akܫK��ԭ!�ae�arx�/�'bn0:8��be��Dbapbc�be�bp�bs�btcu\�u���8�Lba|be�bl�bm�bw̼TbhD�����i,��x�����T�PTi�bn�bsH�J��!����y<lc�be:t��,�s�bt���d�Lca��������c��c���DcaTce`chpcs�mt���T��p���Lcn\�uuhccPX�l�ce\o��cs�s;p!�f.Xda`dchddxdf�dg�dh�dk�dlen�epfr4gsXgttguLg����ca@hb�cLhd`hekf�lg�lhhni�fj�pk�pl�qm�qn�qo,tpXurws�wtxyud�v zw8zy�z�g�H!(@&P�)T�a��r�,��r�.�L>h�1����o�5Pp�l�dt�u�6!�do�7�dt�7���da$0iet�d�96,9�89���d��>�L	��BerC��
eaLedhee�k�rl��n�eo�es�et�ez<D�Xes�D!�ERPE�`eixeu8��H�I��)l�Il�JJ�K�eihn�ee����ed@r��enPo��ee(o���er���DM0ff@fk�L��	�eaLfc\fe�fg�fk�fo�fpgt$gz�|��}h�}��8ft�N�(O9�N�TfgpfkxfrXO9�O�O��fe0%�x�u�fd8��fl�P)�m.�fa�fl�fr�Pz�P�fm�fuQ�0Q�XQ/$R���E��S!gngy\#:�SB0V�LT��,gp4X���u�"��@g�HX���clge�!r(Y�|\�gf�gn�]% _R�^��gidcP�gcTKd�gr�gsht�c���g�d��t��y�dd��i�Hk�����l�ghhn�l���ge(hh0ho8hs m�Rt(m�8muTm�\p)ģe�lXha��<��f.�ha�hd�he$f�hi�hkilt&m�injp,jr�jskt���hr�����u8�J�hnT���hc4�m�E���C�hsx��htiu|�P��%0ia�od8ie\iixil��JD�lwtHix������	hikl��Pint�� �H��!pie�wi��J�il����ia�id�ie�in�io�is0yz�������)�ia<�����ind"X	��)l�<jiju�)h�,�����Pjn���� jaXjlhjntjo�jr�s�jw��u(�X	P�J��J`ja�����u�jn��)|ja�ji�jô��2�����j�o���u�jg���je�)t�aT���ja�jsx�B�`��
<.Dka�ke�fli8ll�lo�lr�ls�lt��<kblkdtkf|ki�nk�kn�kr�kuh�,��������ks�(��e���\�	�k.�ki�kn�krls�
�`�m�kn�
P����'.T-t4���8.lwl��,
����n�����$l���\lehli��otlu,l�d%<Tlix��ed��|lr��la�HN�)l�%z7�)�lrL11;�la�:<.0ma$�bTmddmeؓf�mg�mi�mk�ml�mm�mn�monrns ntHnu<�z@mð:l?��?��8m�n�Tn�$@m8B\T@��\mi�mn�mr��B�C��xmd�ms�D_���E���ms�G6TyuI)�ma�G���mkP���J�@N��O_|S%�Q���mp�T��Tm�YC8\�4nh�_��_),ne�cmb��@ns�d��e��ns$f��
\na�nc�nd(oeXok`ol�on�ooppDprXpsxpt�pz�h�84.Dl��k�nlpku�nh�l!������nn����ne<%�ni�$u�ne��olooe�m��og<olPor�ouHoa|orr�y�|Ppoe�oz(}�2���xor�)�oe����~e�og�osl�8�on��!�oe�osP�7ȇh����oe���oipn��|JR��pu�%$peL��0pli2،yPpn���8piX�c��hpoppt�b�����|+a�vs�pzp�9pu�h����pnܙ)�pi��l�pu���pr����X8%.�pa0qelqi�qo�qu$qð�Kqdqn@�9��70���qg����4���� *.LqcTqg\qmdqn��(��������@���g�qk�qz �c�c��J ��p�l�qa���)6rb@�crdrhri8rk@rl�rn�ro�rpsr�ss�st�wtxp7�x:�>��@�(rd0rn�@y),A��AK\rahripro\B�,BTru<Cy�D�Rr�G�G��|rd�rn@I�p7�J���rbxK�K�rk�rr,K���ra�e�rl�rtf��L/�L�|M�LM�rl$su�L��sa,snHst,�PQ� �2�R��4sn$R!<si`sr�Sp0S;Xsepsi��<T�Sxse�st�UL�sa�se�sr���V��sg����V�@
i�[�V;�sa�W)��a�se�su8X��)\�l]%�\tl�\��tb�Tc�c)\ta�te�tf�th�tl urDusPut�"�cTtbttn�r'e��ltl8�28i�tri%�te`h�tl�tn�i�kJ�l)�to�m��p��taueu�q��pK�tn4�P
$q���t�0q�|u�Xu�ua4ue(i�u�Dww)<ua�wl�z�|ua�uelvi�vo�uì}��z��tuk\�s�ux8��x�2�ud�ug�um�us�����u��v�̃�܃��,@�@� *.vcvdve vi@vrHvsXv�<��x��
������,vsЍ�8vs���܏���� ��Ȋ��Pv���m��dveȆg�vn؈�X����vz|���vb3c�vd�vg�vj�vs�vtd��	��	x=������Ԧ�vsT��K��P�vfwn�M���)
<.DwaPweXwhhwi�wp�wt�wy�wztw�\�TnT���yL����`wd���@����dVod�|le�wr�wut������U<�u�)�we��)
xa@xet1h�xi�xoypyrys��t yupyy��zdy�xb,xl8xtH�)�������$xb�hp�\xcdxilxl�xn�xr�p���T�xxe��� 
T����xt��t�xi����xe�xw�xzV4V���	���xn����/m�xs�wH�qX���xo�l����m<ynPyrD!�!�4ygT"h�!��Hys�"�����\y�\#�h%	�alZb�ycH]d�h�yl�yn�2r�yt`(_����5!�yp�3�ys�8�|1d�ys�t,<�8E��ysHG�$I��,NfzlznML
�MPX),zaXL�r�g�
(cth%)hze|zi��Dzu|���*)`zu�2�,2utzsp!���f.`{a�{b�{c�|d�|e�|f$}gT}hl}i�}k~l$m�nȀp܀r4�sԁt,�u8�xā����za؄b�c��d�eؒf��g(�h��i��j�k��ld�m�n|�o��pęqP�r|�sԭt�uD�v��w��x��yȴz����!)H!uX{bx{c�{lt�r�!�7%�"��{a��b�{dfظg�{iTr�s��t�{w�{y�"���r8#�D$� &(&|&|t@&���{e|h||kH�
'Pb�&|e<|iT|tt|u��t'P4|nP(�h|��'�H|r�(v	�(��`|��(�)(�)�<.�|a�|f��l�|r�|tl*�(*���|m�|p�*X+�+�,�,�,u�|a}e-��,��|rp-�4-�}rT/�.}e@}l�n�,�0_8}e@N		�1��L}md}t�3��3u�}c�}l�}rpkEh~�l4���}l��P6��5���}e�}l�}r�}t~Ð6#�6��Ce�}o�6)87��6!�}i�7/�7���}��7u<.T~a`gbt~d|~e�~g�~i�gk�~l�~m�~p�~s�~t$huy�7�
d~bl~r|�h�8\:8:��;��;��~e�;u0�r�<!�ga�~e�~g�<D=6�q
�=)�~.@>)�'.�~e4>l>��>�
h@�=�?dx@@r@��eHiPm�p�t�@��@�pA�la�e�uX�2`Adn�����xs�A�r�A7�pB)�lC��8%.�c�d@�e�f`�gt�i��k�rl��m��p�r��sH�t��w0DMdDb<D)�a�e�D�(�p0�r ���
p�ECPE�8�iP�r�Eb�FF!X�ipG!0G�l�e�G)�rrpHn
�&�I�).��p�IJ��a�I��J8�K)Ԁf4LE�L�a` b�e8"f(�i�k��p@zDM�LOc�N��i�P�P� �nLTB<.\�ax�c��e��i��k��p��s8TL4UOUd�l�Tl�h�U��UNLV�0V���h�V�|V)��i4X��"�����0��HX�<.��a`7e�l�!r�s �t�X)84.�t�Zu�tD[QX[!�a|\	V.X�bd�dt�e��f��g��mЂs�t�\�p2.H]Mh]e	T]�l�n�]�^�^���a��i����^��g��n�6z\7u`F�_uȂc��g�p�s�t�`��`}<a2�`��i�rPa%�a�(�s�aP0b��b)L�i�b��b��D�s�dVDdX�sdd��`�hdc��l�c̃dԃf܃g�h�m��n �r@�s��t��u�c��x���)�d����������d~@eb�e�f��g�),,h���.�i�s�h[ ib�i\).0�a�i'\�JHk�8�a|�c`�sl�tDlq(l�X�ell�0iTmut�e�l��|�s,n��sЄto�Ąc|A�Do��eTo��h�o�\p)	�at�b��e�l��m�jo��r��s�up�p���b4�d@�kP�ld�r#u�*� q,�epq��q��q%H�aTs�s��\�e�tXtN�uq�uP|�h�t����c��lЅn0r��~
�������w%����x��w��ȅt����d,�sX�܅aT�el�iD�à����v���r��)�eL��� �s<�tн(L���4���t�؀u *.�tŕ���d�n��~��x�s��)��a\�;�iT����e܆k��o�p��t\�̆id�2(�R��lԆa��bȉ����p��r��D��uh�e<�h�li�)l\���\B.d�nt�,�et'i|'l�'m�'rl�s��t@�w�����p��t�[Gĕ|�rЕC ]a��e��^����wD]����r�)�aP�e�i\�o��r̉t�w��ì�����c�f�k�l�n<�p`��Ԝ�����L�i,�t4�zH��\��t����8%.�D�it�l��n��r�s���H���l�b�%xmd��g��J�7��l��Ĉr,�����e�i�n��x������̈s|��Ԉn@��J�t��H+pܪ,�l���a<�nH�tgED�����4�n����
e���P�bp�nx�s��%ȯ���t��ܟ���
������UtX�;��a�i���̳�,�������J܉a��h���)pa<�84.��a�b<�cx�d��eȋf��gH�h��i�k�lt�m��n��o��p܏r��s<�tH�u��v��w��xĒzȊ�|��d��\�rH���d�e��t�)p�l��mxus��t��C��u\�)84.��s��tؽ���� �����|��$����܊a��l�r �ä�r���a�����������EL�aX�sP���,�h`�k���,�r���p�m�].p�ip�����'.��d��i��|���t��8�J��l��r4�$�|t���e�f�l�oX�M��������PG.(�e����%�����$u�����ld����4�c$���<�ax�e��i��l��o�������d�t��l�n$�)\�+�)T���8%.̌b،e�g�l(�m4�nЍs��	��cČlx�����e ����w��y��J�a�i<�y���|��� �p��P\�e��gh�kp�r��s��v�%`��T�c���t��H��\,;x�e����rH����t��A	tW�����r����e�r����tL�����܍ox���e����������al�b0�eT�f\�od�tl�uD�D�a\wiL�kH�J(��������@����l�<.��i��p��u���@��L�)��f������<.�a��d�e �g\�iL�n`�st�t��z������pb��l�
h��)�i�r\�%��$�nh�!��l0�p����#c$#8�sd"!@�e��l��X�ep����!l�l�syw��z���`��H���u��r`h�����ȅoԏp ��	4�M�����S. �b��d<�eH�fP�gp�k��l��mԐn�o�sH�th�z�F����,�r�����4�r���P��d�e���<�\�n�����e��i��$�����n���(����aȐ�����y��u�������������s�t�ì��������$������b�����!�.0�a8�e@�p���h����X�e`�r���x��ȴ�x�e��yD���g���S.��a��e��oȑs�t�uT�u�nT���x��ԑe���r������w@��e�u���U��mx��
�����	_�(�y�0�h|�`�g�nlh�nt�rB�W�gi\\��lI
,U����d)��il�T�a��i�u���Вi�`),�at�e��g��iԓl�o�r �st�t��u`��`��!�s�u�l �l��X	,g��8�t�P@�lt�sl�u���L���	��	\-n��s,
)x�@zc���sl���e��l���x��c�n��ēi�Ü%����<��d%���r���
u0��H!DaT�ih�p@���S���8�����L�d��`�r!�ua��r0���!Ĕa8�e�i�l��n��o��r�sp�t����dp_h�k�n�s�uP��L!�n8!��e� ��s�tl!�rмJ��B$�nD#,�aX�bx�l��n��rĕt�#�@l`%p<%d�r�$ul�e�5�`�)��w&%��z�'���/at)�X)y��pd)��a��o\+sd+�ԕlp+ܕex*���s$,��+�n�+���aT�el�i����(�s�_0�h-��<�c�,mH�id�u��P`-��g�/��.x�o�/����b~�	L1����l1;��aĖe�i��o�1`�gؖs�t����23%�2�n6�X4J��p�t�7/�6�r�:�8%.H�ad�e��onsT�ð:�8%.�?��8m�n�T@���S.x�n��r�C�E%<Rm�Q����e��i��lpR�RE$f��a�b(�cL�d��eL�f��g�k4�mX�n̛rԛs��t��xD���e��m�t4j�@j!�l�i)�bpkuh�e@�h�k�k8�a�l�d�ax�om_�l��\�n�m#�m��p�l�mu��bИfܘl�n�r0�s<�uxn7�n���tDn��so%�nȘr�o�pS,p��e��u�)r���f.�e@r����s�is$�tHt�t)`tuD�ap�e��o��r��t���th�i��r�t��)�uB�u2�u����r�u!��e8v�PG.�e�j��l�r�&��v%ԙe�vܙn��v(w)�w*�zB$�ay)�l�z2��r���,�b�)pH�sā������P�a��dȚeܚf��g�h�i�k$�nx�s��tPv�����e��iL�<���x���
��z���Ԛi�yȄ�	�oԄ��h���!�l�hr��
���t4�����L�ed�tp�u@"0#%8�mĆ@�n0�r�U�)\�a$���v�\.��o��ph*�
��iTV9T����eěrh�@p�����g��$����)4a�c(�i<�m\oH�pP�s��t��b@���o��w��h��
� �k��`��4�uh�{Б��i�jtW�\�s��d�r��s�p�e �;��s�G��uĜaМi؜r�t�u`�\ԓ��l��y\�{�/�!�r�������P��)4�al�l��nܝr$�s��t8�uL�ì� m�P@ h<���@����|���X�uL�`�a��i��o��u���D���sȬJ(�uX����uԭ�
���������u����ĝ��;��e�iНÀ�Ȱ�a8�R�nX�%,�)8�n��)�e8�p`�tL�%�YN�@�i����H�tȲ'T�at�e��iX��ac����uH�!��a��e�i�o�r@���|�g�����nОrT��`���Ȟf�s��wdh�c����������n�lX�(�aH�%ܵ%
����0�hP�mX�nt�s0�uP��d�i;l@���ul�s�2t�!��a��e��i�o8�s\�t�u�ð�y��Пa<wc؟i�s�tH������u,��t��@�y��y��(,�s�������bS b$�s�!��pP�t|���H�o��7p�!	��aD�dL�e��i��o��s�t��u8�ø�����g��lԠn�p�sl�u������a̠dhg�0���l�z$��hhLxP����s��n�)�e��%�sx���%$�r(���,���BX�84.d�fl�o0Fst��<��mx�t�el�|�d��mpP�8�����i���r����)��aСt�ܡa�%8!�a��m�7�P6���s�JP�aԢd�e�#f̣gԣi�o$�s@�tL�u4�y���D�pbh�n��r��u�|�d��i�Mz�r	WL	���e��i0]��DP��pp���������J���)̢r�b �f(�i@�nt�r��s��tģx@���<�n��e�X_�Ef8�s�����P�e`�sPc���X�etV���l�f��g��h��k��t�
�Gb����i�
:�)�|�lN��n�i<$hId�p�Hr�&�t(c�	$(�8(�����'��#p�Iz���-y�,)8�e014$r�7\bip7X�eH�j��oȤs6��d�bФc�d�e�h@�iH�l��m�n\�p��rԦsT�t�u���8n�8@9u��h�k<:mp2.�:�x:�e\;l?!�>���l,�n8�r�O@u$�a\@�
�@��Au��ep�l,`y�C��C��\�n�C!d�a��eHDKD��n8EuF.��ḁeإm�t\�hE��dF��Eĥr�FM�F�Gu�e$�n,�sH�tT�y(HV0H���b�G��r@I��I<|-2Jy4�nJ)<�a�Jc,K���ep�fx�h�KE,Lt�L��aP�e��oȦtLM��l��t|M�M�hQ�8Sh$R!��h�S�%h�i��m�s(�t�T`��X�oXUy Uz�n,U)�a�oc�UL8�a4TrV%�Wc�W��@�g�W)H�a��e��h��rȯs��tȧuTXZ8X��x�i�X_�X���o�Y��Y;��i<uZ!��a�yp<��Z�Чt0Zاn�t�[�l]B�\��l�\���bL�d$�f��gX�h`�kh�lx�m��p��r��s��u�]D�u`^3�^P�^3l�h0`��p�i�`b�`P b).��e�bx�clԨaܨe\f�l(�o<�sH�t�Lu�cH`h�r��u,j���e�i`�p����nlq��pX�i�qy�s�w�w)4�t�w��z)��aĩb jc̩ěh$�i8�nD�od�t8u��È{)�z����b��r�s܀ux�����m�������p��؄l�y�a�r�wd��p����e��)�l��܏���s����l���kP���J0�a|�T�b\�md�y��Bԭ!$kh���ijb��!
��a(�cP�e`hܫk�mlD�nX*oP�p|�s��tȭw���\��bd�dܪm�n��r@�V�%Ԫp@�t\�u��g�p�8�PD��<�r8��eH�l̼�hpF~4���4�l,��T�|�b��il�n��r��tH����d|�e��Jx����e��s�#z��� �,������8���P����a(�8*l��n��l̫a�i$�l,�o�Ar4�uT��(�������e�s ��,����2��Jh�<�o��;h�at�h����`�s�����t��������d̬n��ud���a��e4�hP�ih�ot�rx�u�I�0���Ĭt����\جg|��n����i�rT�D)�t�����b,�w�x�c,
h���<�g����D�n�pD�|���`�tt�/��a��%��gH�����n`�~T�������s��������%��)��i��!�a��e̯h�iD�ox�r��s�t�u��z��Ð���|�b0�lL�mH�np�r��t�=�����(�m@�s̸�����x�g`�n�8th�z�C|������eX[8�)|�t,�Pd�����r�������p���84.Ԯe�i�l�n(�r��s4�1��z����ܮf�l��b�va@��T����fT�� �.�X����xeX�f\2hp�k��l��n��r��s,�y�)P�ah�o�y�����`�!x�a��e���J���P�<)��ed�L5�k\T�i�o��u`	�$	u�llc��	���m�	����d$�m0�n������a���</f���<�pT�rD`�i�u���1k0s�;h�a��e��o�c��i������s���p�sJ̰e�p0�h�Lذe��@��!���t�����rh%T�ap�b��d��f��gȱh�i��m8�n�p�rH�st�t��z0%)`�r�&�
d'4')h�eH��0*|�a�))��e@]rd-%��a��sH-u�.�@/c\1�r|2�X$s,2uԱn�s�2�P6��f��i�l$�s0�z@7�|7��7)).�8V�8���al�dܹf��gȲiܲk�lm�n��t��w@9�0a|�e�9x��r�9��l��s��w(�	�	���:��:!��l;�ԲoX�;�ir8"	�;�p<�ċK\=���r>p(�a0�e8�oP>uH?)0@�LBu�@u@�iX�s�C)h�e�Vpl�tp��8E���eh:h��o<�r�E��E����i�Fl�Hc�w$IP̳b�c �m8�sI\�e�wP�Iسn�IL�I��h�k@JlJm�s����M���m�Oy�idO��,�s�P)h�etbs S!�R��T�e�Q�\�nx�s�TPX)��u�y��b���s�b��n�c!�g��c�h��l)��aD�e��i��oܵt�u8�wH�\m�l�c�n�r �s�mu�m�Xn����q��(�eT�gd�t\o��0�np�rh��\q�Pq�\�s�q����f��g��k��w�q�Xr�`s��t��t����d���dv����f̵p�v�w�w)Եe�h��r�w),x;(o0y�x)�uHx���g�zC
�z��$��$zP�e,��P�\�z��H�cp!84.�:a�b|�c��d��f�g �i@�kp�l�m\�nH�ph�rԺs�tD�uлv��\�a��b̼c�dT�e4�f��g��h��i8�j��k��l`�mh�n��o��pT�q|�r�sd�t��ux�v��w��y�z�Ü"�4�eH�lTr\�sH#u@�t$��$lT�e�$�	�%��'��&d�o��t@&��l�h�' (*\�)��a��mx�r��4-_�,����eԷfܷir�-�-|�.�e�n�r�/�t/%��tT/�n�0X
�3�0�k8�l@4e	l4�5�hqaX�i`�rh�t�6|�6��6��7���'.��e��i0�lĸo̸sظt�zx���:����b8:���r�;���d�=|l>!�e�>��?		@%�e$�m8�n@�pP�t�@d�@���ix@L�rpAv0�a`A���B��udB!H�oC8%.��a��b��c��d�g�h�i�l�p �s<�t��w�hz�B�	DP0D�
<D�Ĺ.Թr)b(�p�D;̹iFF!�.��s�F�(G�0G>TH'�&/
I,�ktItJ��I)4�r�K�X�o`�r�L��L��L���.��a��b��d0i�8r��s��t8%�DM|N��N		�R�DS4
S!��i0VLT��̺pPs�yX�HX��!a�hl�lt�m8�r�s8�z�Y�Z<$�
$\��$�n0\!,�e|\L�8.��c�et�f��g��m��r��s�	��]��l�b�0/^���r�^�h_���i�?����_u��bȻeL`F8byܻo�Vudc��	�gc0�h<�l��mH�n,�rT�s\�td�u�c������p�����fC
f��(�n,g�8�t,h���dHk�l\,n�x�Üo
�o��p��\p���a��e��uxrp����n�t��0r(�+D�����s�
8%.�a(�e8�h�Gj��l��o�r�s�)u@����m�p �rЎ܎\���<t�0�.��a�&b4�c<�dD�el'f��g��h��i�k,�lx�m��n��o��p��qԿr�s(�tH�u�(v��w��z ��<c�����f̽kԽl�n�o�u�T<EH�,���ܽc��g�z�r'd�/l�7�>P�,���������t��,�>4�D\���p2.d�ix�m��n��s��x��b84.�CJ�Cp�p�u�����s��RȓZ���a�H������eܾm�n�r�s�b��g��gx�o�����upJ���w��u(.D�i����eX�i4�}����<�n�L�|LP�t�OM�N��d��Ĕ)	l��̔A�].�T�����x�T���l�^�+�ę�t�x(.`�/ȿe�i�o,W��Vu�n�W�������m�uD�����9kЕ�8�a@�rt\�t`�2`�nl�p����X�t�cu��@e����t,�_|�e��i`2�_����l<����T���p�rh�\|��	̙�r���a@�iL�rx@H0����e�����m,�8�� �e�(�n��4�eX�<���a�b�c�e��f|�g��hd�id�kl�l8�mH�nD�od�pp�qx�r��s��t��u�x\�)84.�)��t�u��;p��@����n���e�#r�3	���oAtP�����hL
k8�K0�c8�iX�r�� �JH�gP�n��hP�4����f.p���������h������a��l\�r��t����l$����a��e�i �l0�r<�s�@tX���m����i��l�r8B���zPFM�%��k4��$��n\�!l�4�u(�e���D��8�(D�b����L��T���x�.��e�g��nT�s\�tx�
��P
��.��b��d��e��f�g �h�k�l�n �r(�sL�w����4�)0�u`�x�������o��Q	���8"����t��H���F.<�pD�th*����
`3�������x������	8%.��a��b��e�f��i�s$�t��)��d��g��m�����D���c�m��r���8�����lHCs��l����x��������� �C�+z��C@�!�r��pl�0�a�p���8%.��a��d��g��i��k��o��s��t@u8�v����)��gp�M���t��)��e��l��h�!��l����c��)��u��u��b���l���!).0'f �g�xi�syw(�zH�
��hPcH3l0�a��uT'r�s� �P�s��X�o���<.��a��e,�f`�g�th��i��k��l��n�o8�sP�t\�u��v,��0�����d��l���O9����f�i �n(rP��8����g��m�������k��)��o��rL���X�l���@��MP�7��rx�� "8"��p����D,������e�c$�����n��)��e��l���(�!p2.������������J��t���@��T�������t$�Kd��f���� ����!).H�a����!F.@�p�fh{m��n`��t�]|��x�dD���e��h��u���T��$�����p��t���a��h��z�� ����
|�����n���e$�p,�t8ux��7`�p�a��lc�D�r!L�e�uX�ld�l�'��|�o����elrD#�}s�:)<.��a�c�e�i�mk�xnD�onr��s��t��ð:��.��k�n@<P=\�tcT@y�G���8.,�d4�r|H$JmXRb�Q��<�f\�pl�rx�w|S\DO��S��d�exT*�Y�8\��d��?�����$f�a�b0�cL�dd�e��f��g�k��l��m��n��o4�s��t��v�&E�.�f���h�e��c�d �s(�t�&��hEi�pk:��m8�o�l@�e\�o�m��mK��g��n��r ���x�so��er���#r`tJhv)8v)��a��h��n��r�)�Pw��u�/%�w���Ty��by)	��aX�e�id�lDrl�st�t�u8���y��y��0���z0Dz��D�lz2L�r�z�p{B�{�0||��|�o(�������m�H���
��a�d�~f��g�h$�i,�qP�stPv����!��a�h�l�hr�sh�hHD�P���a��6T������4ah�cX�e`�oh�pp�sx�t���)h�rБB��u�����a��r�u���ԓ��u\����a���И���aT5elbr���l<.(�a��b��e��i�l��n,�od�sl�t��uX�Ð�:�����a���lD�mT�n`�s|�tT�bx������L�zL�b(Y.��g(s��)h�el�:��mP���<^m$�����l��p$�������.��f��g��k��r��zD�_8v�\�B�(0�		L���a��|����s����$�m@�rH�wL�\����<���P����cH�  ����t�p����|�l��s�+�l<.��a�bpge�i �n(�oX�ux�|������l��n��v��w0�b8b)x�eh��l��@��p��4����84.D�bP�e��y��<�e\;� �p�����a��i��o���D���b����84.��s�J��r���e�rh&H<$��t�&bp�����6��84.�a@�bP�ed�fp�g��h��l�m�n��o��p��r(�s@�uL�v�&w`�xh�zp7��;h\;)H�t\=��;��\�tx=� ?(�>��x�e��n��r�?N@u��g�A(��a��e��l��y,B�B��B��i\D2D��r�C!��e�D�	8E�G�(�a4�et�s�Gy�F� �u�GT�n���)@�eH#H�d$(%8(��`���I���oh��X*)�J�,K����f�L��
�8.��aL�c��dP�e��g�i�o�%s �uLM�rl�N��OUP!��a��e(P\�P�pP�ehQrtSJ�SR<.8�t�Uc0Zاn�[X�e�Qi\�X\:�\���ftk��l��s�^� b�
�c8%.��a �e��f��h�i\�l��n��o(�r$�s,�t<�ut�y��Xd��c
��a�k$�lT�nl�p|�r��s��u��z��Üd\�d��d���adG���.pGJ0�r0G�<�ee��H�id�o�e��e��e�f��t�a��e��o��u\fo�f}�Sv�V=|V)��e��i4g����s�V{@g,Lg�����tg�0c��g��r�g�����l���gq
`hlM.D�l`�nl�p|�r��t��zi\j��i<L�i�i��T�sj}���,j��t�i��l��o��r��sXj�tjq
�j��\k��	k�0mP�l��a8Le���8mb@m�����hnP �e4�k<�lD�oL�pT�s(o�,�rPo�XoU`o\�o�	p�Xp��p�t�a��i��u���pU4�$q��|��lq|�qK�qhr�q	��d��g��i��k��l��n�r�s�tx=�r�
8r@rU�r�s��s�Z��s)�tXu�F.P�a��e��o��u����{�|u��H�cd�nl�x���u��u��u2t�m��s�u��|�����u�v�u��c��d��sv�Hv��vI
�v���b��s�v��v��s��v��vP�f�nw�wy�wC�y�xy4�nT�p\�rd�t\=��2\�y��y�8z��z���a��e��o�����@
cԁ���s�z����t<��	���t���Tc�����s������������P�c�dTJ��)�.|�a��b��c��d��e�f�g �h0�i��k��l��m��n��o$�pT�q`�rh�sp�t0�uD�vL�wT�y\�z|��\����b��c��j��l��m�nX�sh�t$%�����o)�|�����kl5�p�%��a��b�7cx9VP���%��t���\��d0�g8�o@�sH�tP�z���Hx �x<���h�Ժ%X[h��`�t�����t��<��̼y����T���c��e��g��h�i$�kP�n��r��s��t�z�U|����a������H�ad�b�n��Ph�f�tT��d�������,�t��)4�e`�uH���@�dt�m�����s@"Jh�a ��@����t����tx�����a��s�)���8�!��e��u��c��)��c��h���������a����4�������c0�H��(�cP�eX�gh�n��o��td�U����~�����`�f��s��t$�yP�v|�t
�����������a�h�����a(���l���`��h�m��2�f�iP�p�rd���<�f�@�
����t�i�����edVo$-�hz)4�tDz)<�eT�)H�u|�(
�6d����a��e4�h��i�o�u�������d��r���`���HW������=F.�6l��ns��t,u<�b<�������px7s���|�����p����r��b��s����$�m�bx� ��c����c���.��a��b��c��d��eh�fp�gx�h��i��jD�kL�lT�ml�n|�o8�p�+qt�r��s�t�u��v��w��y��z`��H!C
������a0�bl�ct�d��f��g��h��k��l�m0�n��r��s��t��u(�xd��H�(�bL�hT�i\�td�z���D$X���L���_��~��a��r���x��,�������fD���0�\��r������.��e��i��k�m =p��;\<D�=�P�\�i �tt�(<=������(�aT�d\�fd�ll�nt�w��zl= �'�r.�5X����<|�)|�a8%L
�����.��s��t�R�����Ul������i��8%D����.��f�m�r�s��K��sh_R�� �.��
D�
d�d���0�bt�d|�g��l��m$]r��t��u����8����T���CY����`�����t�h����tX�pL����t`�xh�����s�����@�mp��4�aT�c`�ep�g|�i��l�m<�n��p��r �sD�uX�v`�xhu`��,�n �����@�n���H�h4��x�/l��h�r����g��l��n�������\	aT����e��l������m��p�������t�!���8�2�����r���a�p@��4�)�fp���)(�sT���0�d\�gh�sx�t�C
�7T�l�4�� !p�f��w8������i\�������st��r�����e��l��n�r�s����(�0����s<)��ad���L�e8�t4���0�sx�P�f������8r\�\�o�
�	��f��r�	����e��f��m�n �o(�r8�s�
��
��.$!��a�
u��m`C
��|�������bX�f�sp
��
c�
���
)0�o�����`�a<\Hs� 8C
���t�c��d��m��p��r��s��uXf��el	�0 ����o8�]D����d��g���T � p��h|���c(�r0�t�`������ ��	@�f��g��h��i��l��n��s��u����;H�aH�e��iL�o��u�Ü���c�# ~ �* �����s�2 ,9 4�����@A H��mԃI `���f$�g,�n���������P ��8�e�X a 
@�.��c��d��f��g��i��n��p��t��v�Uh x�x�h�p �-
`�p �w �����f��s L��4����nX����2A��].�a�b �g(�k8�s��E�������(��
ԛ)0�ix����D�cl�ft�k��l��m��s�;,��p��H�u|�l��J�u��aԦ���H,$��k��m��n��pP� l<4sm�a��<��si�H�bP�cX�d`�eh�fp�hx�k��m��n�r���4'		�N�)��� \D ~` �� � x P��r��s��z� �� A!���.��f��i��s��t��4!� d!� �!� �!� �\c '�����"�!���e$�l,�n4�t$@hL"� �@� �"� �"�<�hd�k�"��D�ct�r�"� �m��"��l�.��e��g��s�"� �"� �"� ,#34#� \# *.��s�#�# h%��
�f.�a�b0�c�8f�hh�i��l��mh�n��p��r��s4'< �a(�ip	t'-`(:D�h�(�(<�a\�s�)+�)�T�t,2u�sx�t�����f.�3�a��i��t�4�5N�6�46��r��uP6����a��e(�f 9m4�oP�s�6��6|��l�n���7� 7!�s�6u�t�	@7. �e�d@�r8���7�7)H�a`�tX8�8���d��e�Qf��i��t�9� �9����h�9��r@9)��e0:Jp<V\=���p�i�>��(�a8�l�t�@��ca�6h�i�sLB�C�$I��$3b,�c4�d��nT�s�I\TJ�H�e|J�J@�ndO)h�s�O=�O)`�eTi�P7PX)��e��i�u���`[��h8\��_��_����e��l`�@b%�Z������g��l��n�s�i��j��k�l),�a<�e��iLco��u��w�lt�o\\o��4�i`�n��s��t���,q�q��X�at�e|�t��� Pq��s�tt�����t$v�t����sHxp$ztp!�����aT�b`�ch�d��e0�f8�gP�hX�i�fj|�k��l��m��n��o��p��q��r��s��t�u0�v8�w@�z@���c��x"��K�Ș�\p���u��3|�i����t�e<�P	��c��g��i��l��n�r �s(�tXtxP�x���T�x����(a��eD�u����g\�io��!).�e������s��t�� ��`��D�eD#X�b�:�$fPh�gt�n8v)��a�x��3��p���6��c�Tz��uhz)�8tDz)��e�zH��u��Ss�����t1h�sh%��f�m$�nd-P6x�8�d�i�PPX�l�").X�ad�bt�efظg��h�k��l��nTr\�s��t �wLz���p!��H�b��c��d�fD�g��h��i0�k��lP�m��n�o�p�r��s�t��uD�x����a��b�c@�dp�ef8g\h�	i��j�k�lm�n�o�pT�q�rst�u,#v4#w\#y�#z���xr��"�P�nXt��#� H#��l�l��n�w���dPxM�$� �$���e%!�%8&L
 #�����@&���u�)t(*P�)��a�Cix�r�s�+�4-!�,���e,�f4�g<�r�-�lW�-!�.�`�ap�e$�l��o��s�.*L/�T/h�i��n�/!t/%��t�/%1���tP1J�1��lL2,2J��e��s�M�2!��k�3u�k�l�n$�r@4yl4�4)�4���s��f.�5PH�aP�rd�t��z�5��6��}o�6E�6!\�ax�b��u7!��,�7	�7��	��.��a��bhgd�e,�lD�o̸set�7u��g��kD8�X�%x9)\B.��k��uP���%!8:��n�:��sH���<�g�<! �e8��=��=�<�p@�h�et�i��p<=tx@L@r�@u�p,!B)|�l�B(C����a|bl=d4�e�f�g�k�rl�m�n$�sH�uX�w|�z�EVF��G��l�G2!X":	pHJ��e�H5�HC�aI)).8�i@�p�)(�I8!�J_X�J�P�ap���Z?!�Z��h���J)).��a��e�u4�KUK����i��rr� �4����h\5��K|Ku�rL��e.�s�K�eԀf�=l�j�L��` bL�ep�ix�k� m��t�u� ztO?`O%8�s�N�@�nd�r<�%�O��\�eP>�P�S�� i��oXSE8TLT����a��c��p�t�T0VG!,W�
HX)	\B.��a �eD�h�lX�mh�ot�s|�u�X��P�b��n�u�X�\Y(Y�i4�m<�r��Y��Y�P�e�YL!Z�HZ_TZ��`�m�Z�\���m�]\��gl>l��n|\��f(�g�k��m�slk^R!�2P�_��b�c�e$�g,�k0�l4�s<�w`��r�w`x��h�AY!L`x``�`B\�`
����bX�i�b��b��P�sdc��	c�Cd�e��g�h�l�m�n$�p,�rD�sL�th�ux�x�c��d��d��|��"��e�
�y�e����,g���t�g��t hX	�h_!�h!��s,h���g�z�3�hiP�i��).<�u�uHk�l�X�t�m�o�,n��`�sx���o\\p���e�l��u0:�n<:m��e�z��k�u��cX���o�f!�)��h0�k��l8�r�%t��a�e �i|'l(�u@�w\�%����d��̙��T�u����L�n���lhun<���`�a�b�c4�e`�fl�g��h��i4�kT�l�mT�n�p�rLs<txu�v�xH�Xt���b�ep�h!@����n����a �nP�����h(�k�P�'o!p�v!�de8�l
mL�nT�r$s��+4���l�wt���f���x�r��;��e$�������u��rT�����f�vk��l�n�s��b��\8	b�e�g��l`��pEc8h����e8"Bh�a���8.�b$�t��|!����x�,�t��'��b��c��u����
@�a��b��e@�fT�it�l��o�s��t��� �y4������JD�
�b�c�f��g�h�i�m�n,�r̾u\���e��J�����(����;�������n��0�V8���$�d\B��)8�.���l��L�nd�t��� ���!l�e���\���t�����s���������@�!paTrh�r���L�T�il��e�i�o4�pL�u�������n4�x�r8��,j�
`h �rL�)(�eD�o���!��!����.��a�b�d8e�g�h�i�j�l�m�xn�os t�z������pb��d�g�s�ut���!��2 ��t�m��)�iafil o(p0r���
}���!<�v	P�R!\����$DbXf`ixnH�rlDt�����S.�&d�<��psh�7\B.�e�l��@zc���s�+���u�aT���u�nH���f�8"w@"���ml���e�����b��!aDbLdTedr8wpz������!��bP0p��;\i���<�����hT���xr`�)�eЈw������i����<.aDbhdte�fg|i��k�l�m�no<rds�t�uw4zÄ�p�b8rd(f�ym0nP�(�����8.����)<sTt���h�p2.L�)\e����b�i�El�n�r���8����f�������d�'g����yk�zh�-�!��l��)�o�r���!�PX�l����P��XzeHr`up�D2�!Ė4i��;<eXu�3L8:	p:�8"��h�����a�(�!�X�X	�a�e�>���sl�2H��r���P��r��J�ae8���u��2d��������!����(s��)0aPe\o̩yP
.D�%!��!F.�c�e�t����|a(��h��2��rx�'�.�i�u��!P��x��!����!�a@�p�f[b�Zu�h�Z����)��e���ȴ),aDb�n�!�pa�e�i�o�p�s�tT��4cx�%T�|r,����rP�L	x�l�e�� �.@���e4s��8���܉t��������U@
ir,s�!���g$k��v�D���!�8%.��aPl��`,R���Xi�)`r|���le�f��n�rd-�P>\���a,Uu)�i�P�a�e�i$�p�tz$�8�P����o
`J,i����|��d�$l�lPelr�'D#Hr�:<.�a�ce�i��k	l	m	n$	o�+p�	rns�	u$�v�	yð:��S.�g�i�k�l�u�.�3%@<�L<:T<���.�>l?��?�����	���T@8%.PaXb`chi�k|m�n�o�r�s�uD@		�@�A�8B�tn�Bb�C,D��C���e�i|D��D�!��DE���r��2e,G�
8%3�G���.�k�l	mI�I����J @N��O3�QuH	cP	fX	h`	ll	rt	t|	uR�XR%�>��RuF.�S%<T0Z��T�	�TA	�	i�,W%�	.�	s�Vu�	nx��!@b�b���	b�	n�c�!leu�e�dX
m$f���	a�b`
c�
d�
e`f�g�h�kl�m�n�
o�
p�
q�
r�
sLt`u�v�z4h%pkuh�ex
h���kp
r��
dm�
n<�o�l�
e��M�mu�
fܘl�
n�
r8sXt<�uL�8%��n�
.,p�!rue$m0r@r�$�c���sX�!a���s��s�����D��s�`tu\B.pr�u��#Vw��xz�v�r8v)�e�G),x)�ity�Ty�m��ry)�ai�rsD{�L{����,{;��p{l|����a<eTgll�t�~u��d}�(}4iLl|}�H���~rb�gh~!\e����|��x�L������g����a��i�m�p$�)(�)�a���8%.�a|�b
d
e�If,
gP
id�jd
kp
st|�u�
v��V������!$
a�l@
sP������H
t� l4�)\
l���).�
eȇ�Ĉ����Ir�P�8.�
l�
p������hz)F.Dz)�
eT�)�
ǔ�����
h��)c8etp@��a(w�h؏3�����0i������De����BXm������lnИ)ta�e�o�r�[�l�r8��\S��Fl�V)�W�ܙ)�a���B�ei��L<�mr`�3���a@��!p�l<a\e�o�u���4l��p���HtX�Pnd�5
l�hsT��pe�|d���	�eL�s477�5B�st6���ab8cXd��i�l�m�n�opDrsDtTuT7�p7H�jlp9LHt@9u,hPk:�!<:v!x:�'.le�u�:��"r|s���T;C�Au��a�Ed�n8Eu�e�g�E�\�GP�a�e�F��u,��G��h�J%xK�K�k(t,K��a0o�tXg��L"LM�tu�L��
8a<�c8�d�e�g�i�tuw��,��Nb�M%|n�M������84'DO��.�l��P��a�O	"��8�P��tpP�n�QL
$RX	\B.tS����S�Jk(t�UXT�8X��0n�W)8e��h0Z�di�n,2utl�]��h�\��pc$�ftk�l�n�s�t�]"�^<`\�b" b�t�c�\f�z;	8%.ae �h�i�o$u�y���{�Lh�z��cXd�f�g��h�i�m�n�sHu4��&|Da�|�(�.peأ��*hm�|p�|�|r�|u�a$}�
l}�
$��s0B�	�����.�d�k	n�s�D�����"��#"4�B�ei t��)"|W/",Wr��6"ā��,���
,�@mXsЂ�x���g�h�n�s�u�ì���`�������܃E�6"xh="�����e@�E"�lL"�l������T"���s�Üo\"�o����84(�.�adbxc�d�e�f`�g�i��k,lh2m4nXp��r�s�t�u�2v�z��pr��<���Uh�k`��x���e"ȋ�
�e�f�o��k"�r"�y"����V.�b�fghns$ž�"����,x�4�P
Ѝ�	ܙ��������+.HdPt���t�����
leto|r���̏UXu�"�S����.<��"�r���"Ēd������	�benDoLphupxp`z\ü� �.s��u�b��r���"X��,d4g<i��]�������2<�D���T�������Ф�|���xc,�i�k�m�o�p��LFu��u�i�J�\�2�fp� �q
��������hMchstX����ds(c���e�gHkPmln4p<�t���2����\s$��l��8��dd|kܲ�"$E\ '����̳��e�u��P�b�c�g�m��D��r�khK���" ����m�����u<a�c0e�ik oLp�6s�t�u���q\�
4blcp�dp_htl�mT�n�r�sd�u|��p�P��"�%|th�pԺ�Ts�P�n������|�������r8��a�el�(r̼�h$oD����fX�J,��i�����rT���bXhhi�m�n�{r�s�t������"d���`l�n�����Pxd8��H��T�g�sl�t���"��(x�)�e���s���h��
L�����d`nr����l��ar�%��8�r$����,ldnlr�s��4a�eTVi�oT��|�Pxe���"����h ��d�k�h�
��3���n�Zrd�'�a eTipo�r���		�����t0mh����i8����t`�������P�a4p<r�������$7mLr������[ehsP�t8�|�u�c�rt��"��%X�"H����dt�;�a�i�o����� �������L���p��"�h���b��"�P��!<a�e�h�r,s`tpuH��he����	0bl�c��d�g��k�l�n�r�st�u0�"�/�psp�xsD���e��u�I������t�����t����4.ep����b�ilHr�s��(�����f�sT���H�a(e@o���� b8n������%Xg�m���"�>`s�hs�X	ta�������L���?����u����\�so���;�/a�o�uÀ%$�%�����d
0nu e<[pPt ���mr�'@e�mi�"�_���hc�n!�&���m0%u�l�nh%���a�c�e fD hL i` kl lx m!n�!p�!r\"sx"t�&`((,�h�*� r�+��+�� ed-�$ e�-4 n< r�k%�-\\1,2uh^st3�"�2��X r�3�aP6P
8%.� e� f� g��k� p� r� s� t� z�6�@7�
H7��7��7t$8u�7)� i� oxu��!�8��8�	�.��a,!d4!fD!gd!i|!n�!s�!t@9�`���:!<!aT!s;�"D;�;\!ft!oX�"�;X	,<#p<\=���f.$}P>�!g>��	�!a�!c"e@"iL"nԶoT"s�
u�!��>L
�>���!�(?)84%H?�!.��i "r("s0"t�?���<��?��?8"n�bd@7�@u �a�cp`opE8E)p"a$I����b�"c,Nf�"r#t�I#�I��"h�"k@J�Jm�"s�N���m. jc�"e�"g�"s�N:O#<O#T��XP#n(P�#e$#z�#��P�PX7�Sa\ iP#��Z�
�Z��H#��g84.p#p�#s�j|#ak%�k�l!�#a�#e�$g�$i(5o�$t%w�#Ôl�
�#g�#l�#r�u�m��m�m�n��n���#�@�\o��84.$c$$i<$np$r�$s�o��o����e4$sp#q�L$e\$t���\qF	Pq�T$s`r��q��h$l�$s�r{�s��$t�s��$�ht�$lHtl�$epu����t�t���$n�w)�h�$ix�x���$n�z(�z���$�$z %i(%u%�H{�{p!u�%b�%c�%d0<h&l<m�&n�&r�&s�&t�&u'y��0%a4'b`(c�)d�*ed-f@/g\1h,2i�fj�2k�3lP6m�8n�<o\=pęq>r�@s8EtXHuhHv|cw�Hx�Hy�Hz 'Ü"y@&�+�)�%r�7��
�f.0&a<&bL&eX&f`&oh&rp&sx&t$hu�7�<ux9�@;�	8:�D&t�;��=�x��l>��>hI)C���&s�L���.�&a�&stM�DM�&b�R�LT����a�Y�HX)�&h�&i�Y�|\y�&s�_��bdc��H9ml�n�	u�c��$E�'�`H�\p)d'et'i�'l�'o�'r(s��uT(Ôu��t��\'c8i�}H�'o��sX���'i�'u|�����'cd���K�Ђ���'p\�;�'i�'�ԅ#����'����	���'t�����(nT�!(a,(o<(p�)���P�4(a�����b,t��H(��u�kc�(e�(h�li�)k�)l��'�(s<��	�\B.t��(a�(e4)i|'lT)m�'n�r�)s�)t�)u@�w�(����x���(�\�P�9c 'i )r,)s�F"#�F)s|F�)a4���)m���	��PD)lL)nI�Ĕ�t)a�N��N%`)slNh)s�Zu����)o�)p�)t�ZL	�Z�)rĕ'H`2`�)rЕC�)o�lr�P�)mlc�d�m0:e�)�a0*eD*ih*o|*r��,���*e<*t� *r<����|+e\*t��'���T*i���(;n��X�;t*a�*up�R<�)
�*d�he�*g�*l�*m+nx+p�+r�,s$-t��)��)����d,a��e�*il�S��ul��*i��0+eD+g\�ioX+sl+z��d����(+r��'h�!<+eX�ul��P+a\5u`�)d+u��)��������+a�+e@,g`,i�,m�,n�,oX�r�,s�,t�,u�+������+�P�]8����+g���+i,m,n0,r���
x�,p������,tP�
���(,g�ykP��$��`���H,nl,t���P,n�P'#�.#��t,sX�!|,a��JTJe���(���!�,c���l�3#@���,m�,nܹ�|���,f�,t��9#�-e-pT���@#$�u�-a<-e4����4-k�tb`�h�k�-rHu`��
H-a�-e<.f\elp.oder�.s/t4/u�-�\	S�P�-s�-�����-�|	��	���-��	�Ai�-m�kn�-r0.ð�4���1h.l .n���!.e���\���(.��
�L.l\.r��<�,�T.o�h.b�.rd�.i�l�/����.�H!�.p�.t�.�<����.o�����.m�.rT�.e��b������.b$/n!/e,/s�%`B���m�)
�/a�/d�/eh00l�)n�0o�0r1s�0��%E#�%!p/e$x/tt���/b�f�/k�/n�p�/s/u�� �����/g� �"BD#�=i0n0r'[ >s'J�/a&%0m�'����f(0l(���+)P0a\0ox0ul0�,L#�+H0d�-��\�,��d0�l.�/��r�\�8"���0�T1�Ѝ�D2���0s�1�0i1;�0e�0o�0�3�s8��@4P�0s�1���0�X4J,1c 1e�>i41mH1p�5�[$�s`��(1a��6�@1ap:��:8%.�1l�1m�1r 2u�3w�J�JJ|1a�1i���|LuO'@N)�1e�Tu 3a�1e�1i�1t�1u2�`V��U�1r�Vp@n�X�HYulY2�U��2��c�	b��2n$fuL2cT2eh2g�fm|2n�2spk��m`2ml��8vht2e�v�����f.���f.�2c�2i�2t@��4.�2s��2h�R#����2n�u����
��a�^e43i<3l\3nd3ot3r|3t�3u$3�,n��P3u<���3�l3���L��P3e���uH3iX�4��(�{H�!��a�3i�3r��������3nX����3s���4a�Kb<4d`4e�4f�g�4i�4k5ll5m�5o�5p�5s�5t 6v$`z,4ä�����4m|���H9m���� 4�6���!(:rT4s�u��)L4e���:l�4n�4r�4s�4t\�x���������4f�thPLw �u,��4a��P;a<�J ��@��4k�4n����8<st�@�)�4a5n��%��h�!5a45e8��|�%��,5nD5sT����%����L5n��T5i�)`5e�����x5i�5r�P�)�)�5h@�u�5m�!�5a�%t�5zt�2�������!�5aTrh6r6sL���;�5ix�7L��d�)lbrT��,6k�6l�6n�6r�6up���46a�6d�6e@7fH7g\7it7k|7l�7m�7p�7r�7s��tl8u�8z��������6l0�u�6z�S�8����6t������6s�t��p�u7tX��6n 7r����!7sh����@a87g(0l��w��<�.\�18���X$sl�P7nl7r����.����c�7a$�l�J`�%�7i�u�7l�)�7fl�l����Y#���7td�u��)�7a8e$8i88pX8td8uP�88r���l8m���08e�����D8m�L8e�u���n|8r��8���8%.�8a@9d0:e�:g;i�;k�;n<oIr,<sp<t�<v�<w�/zx��D *.�8b9c9l9m9n$9s,9tl{�JT����	�	
���|9b�)
49a�9d�9e�9f�9g�9iOn�9o:r:s��u :v(:zl����\0m�
�9i�9r�
��dFf�9h���h��9a���E�l�
�9d�
��
���9r�)�F.:p����\��L:bT:h\:it:n�:s�,X�8%.PPn���%l:t�d)H8�:t�:wl!�:e�:l�:r;slH|�`#H���:�X���:��/�:a�:i��2�>�)<;e�;dD;fL;kT;m\;rd;sx;t�;v��`t�$L�u|�p;l��"������;a�Ge�;s�;t�;u�l�!l�!�"���;t�!!�;i�;r,"����"�"�;dd"!�;a�He %<$<r$<s�%)�'<).�#eL<iT<k�4pd<t�)L*\,;(+\<r�,��<a�<e�<r�<s�u,-|��.%�<i�-���<r0%t07�V	�<l�V���<lH3l�<o�Vc`3�6u�rb=f�Kp =r<=sP=t�;�L�.4=ch�sXNZ�S�f.8X�W)H=e�c��`ia�=e�=f��i�ipċr�=t`h��=r,j�k��DLa�=e�=i�=l�kl�8lh#x�w)�=a�=e�=o�x��d�g@x�=r�x�z��8%.P>a?b(?c<?dH?e�?f�?g�?i$@l0@o<@pD@rd@s�@t8u�@z�>Èzp>b�>m�>n�>r�>t�Mu�{��>a�!e�{\$���ԝa$�b�Mg��h�>s��m#܀��>e`O����>n �8ԁ��>tx�\?n�����>��j���P�y؄)?a<���u ?h�y��)4?i�Ofl?lt�nx?p�?r0�����d?e���܏���thl�wؒ��?tt�7����;�?i��!�?r�?s�	(�����?ctPm�?n�lX����?i@s�Ptx��%!��y@u��!@a|���l��BP���_��L@nd�u|�!T@a�@e�@t�Xw�wzP�@Xrt�;
���@rԭN���ȴ)�@a�@i�@oTw�Sè�u��u��uAa�Ac�AeLBi`Bk�Bm�Bo�Bp�CsDDtEuXAü�2\�Af0AnDAr\�t=n���h��<At�P���PA��B��~��uhAtD���pAn8�|Ae�Aw̼�AhL]�|����Ar��_�AeT�$Zc�Ai�ApBr$Bsd���Ad��d����x����AaBp��/
����Be����84.XGsd�K0Br��@Be����lXBl,�o��
�Bs�pBs`��|Ba�q�)�Be���Bc�Bh VlP�p�Bu@9��%@��p�P���Ca(Ce4CiLCoXCug�|���Ct��Cr�s�*t � �k�P��cDCz�p		����Zr<��l�\��`Cl�Cn�Cr�Cz�)hCeDiDoP���Cd�Ci�Cm4�s#@�)�Ce��z#��#h��Cst�J�Ca����Cf,�M=a��#X�		0�Dg��2�f0��lDe����$DbtDl�td�0Da�De4�h�Di�[o�DrEuyt�%���<�����|Dn�Dr�����+.�u�����Dn�Ds8��t�;�Di�Do(�����Dt��#L����Dp�\��Dr��Er��u���0Eu����)pEa�EepFf|Fh�Fm�Fo0GrHGs�Gt�Gu,Hz8]���84.�El�Em�Ep�Er����p�tP�����p���	84.�Eg�EiFn$FrHFsPFtXFv`Fxl�d�Ee��J�����f.0BeT�� �.8�t�JtFr���Fe<Fs���d!4FaLU<�����,'JhFi\X7eT�i�so�Fu�	�<pBsl�Fa���84.�Fb�Fc�FmGpGrl82�7�Fr�Fe8�����8�f<�sDJ��Ga�;$Ge�<yu@Ga�Gc�GphG���`G���ml`m�G��tGh���# ����G�L��Ya�!�Ge�Gl�Gs��,l"J�!���GeHu����Gr�
�h�h<$�Hg�#��Hn�#! HeLHid\ot\w�$��$��DHnh%l$I��P�tHe�Q�8%.�Hr��%�����H�\S��HÜc)�be�Ho�HtLf�f7�g�g���Ha�l)�HwIz�z$z�He�{�\pPpIcxId�Ie�Il�Ir��Ib�IcTJdKfhKg�KhMkMl�Mm�MnpNp�NrdOs(Pt�v�ozc��t��t/
�t�IrTy�X�\�����IhJk\�Pt��Ie|'l�Is�ItZ!����IcD]ЕC�Ie��$Ji����Ird�mJe@Js��R�����,Jeԗ)4Jp������LJa�Je�Jo�Jr�Js�Jt�Jw�J�����8.�Jg�Js�|Jn�����"�� �'ܟ���J�X��سB�Ja$������)�Je)`��Ka(Ke4efLKi\el\Kt�	�Ai8Kr4DKgP���)u!TKo��Ke�el�Kn�Ks�������Ks<%�Ki�$u�KeD#�Kl�.)X47�:��fa�KeDLi`Ll�Lm�fn�Lr�Ls�Lt�3u�3wT@Li Ln4Lr8B�tDt
�C��LgPF�E��,Lk\?z�G�J��J��LLc�JJTLapLe�KxO@N)xLo�Uy�Li�Tu�Le�@o�LtV\V���L.�X�X!�La�YN�Lt\�4[�Lu8\7Mh�_!�����,Ma�|c4MeDMl�gu����L��uh�!<Ma\MehMo8����,Di��u��p���pMtX�xMnp����MeD)����Ma�Md�MeNfHNgTNt,_udNz�u�)�Maphr�~�Mn�u`)�MaNe,Nl8Nr�	�Ai����$Ni0X�l!@Nl�,l4��3)\Na�c���`e�Ni�Nphn,t)|il�z�z���Na�Ne�NfOgOo<OsPOt�8jiԓ%ؒ)�Nl��r��
��%�Ng8��Nn��!�Ne|��	�b���Or��$Oe|�!0Ot̯ԭ!HOh\�����\Oa�Oc�Oe�Op�Os�Ot Pu̼L�Oh8��blT���Oh�Ol��Zl������)<lc�Oe:tĒu��`Cld�LcaPe��r8<���Pe���Pn�����LPaXPe0miLmr`Ps�mt�mu���lp��p!��x�b�!c�Pl�PnQrQsQt�Qu��hPa�Qb�Qd�Qe�fUgUh,UihVkpVlxVm�qn�Vo�Wp�Wr�Ws�Wt�Wud�v�w�z�Q��7���sC���g�L��` bLTHX!DQaTQe`QhpQi��r�Qs�Qu�Qz�X\Y�(YLQi�Y��l�YphQk�Qn�QoZ���	���Q.���Qs�
��Qn�Z\)0\7|\�dc��c���Q�\p)�)<��HRaTRb\RcdRdlRgtRh|Ri�Rl�Rm�RnTSo\Sr�Ts�Tt�TvUx�T��)pur���P��������$��T��Rt����Rs�����Ru�����Ra�Retwi\�o��D����l��Ra�Ru����)��l����RaSc Se�iHSÌ����4Sn�m<��,Sd<��D���@S��������Sa�Sb�Sd�Sef�SgTiTkTTm`TslTt�Tu����Sd�SnXEs0����������Sd��BL�l���XOkP���Sa��#Ĕ���Ss���Tn`�2�����#�Tn�)$Te�%0Ts��<TsX�!HTa��!�rt��NxTe���@�����Tc�Te0sh�Tp�Tt�T�x�{@�L$���Ta�Te�Tr���
���
���|�����)�:)�e��rXUt$f��	UaL2ctUe�Ug�Ui�Ul�UmVn4Vsi7�3$���`Ua�m��hUh�*l�Ur�Uwruel�8vl�x��{)|���Ua�Ue�UiH}�(}�UhD~�~�Un��Va���J���Va$Vst���Ď��,VaPVeXVotp`Vu�S�y����)�lp�)6����b�Vg�VlTp�Vr�Wy�=x=)�Va�*u�A��VlHD)�VdD�Vn�C!�Ve�Vi<DGx�u�L� Wa��d(We0Wg8Wi`WmtW�LM�DO�#P7pPDWg��:�j)0QLWnQCTWe�Q�#�M��lW�L\%�c)�z�Wa�We�Wo�z���We���|����)�We�zT���)$-)�*)�Wth%���WeXmP6��p�fp!Xda�Xb�Xc Yf4YgXYh�Yi�Yl0ZnLZp`Zr�Zs��XaL[b�tc�Qd`[eUg�_h�_ihVk<alxVmDandao�Wphbr|bs�bt�bu�w�ZÜ"��b�&�6e�Xs�Xt@&���Xh\�#ĕ�Xu�'��XtP(�#�'��XrP�,Yl�-)Ye�,��Yf0��.HYePYn�woT/��0��1P�Ylp���K��dYtL2lYn�Yr,2JxYe�1id22�i��3���Yb�7P�Yb�Yd�Ym�Ytx9���J:!�Ya�=��>u�>!�YaZoZs��u|?+X?JZt�BC��(Za@ZgF!��s�K�N!pZi�L��TZe�ZtLOSHS!xZeLTP��a�Zc`h�Zs��bU�Za�T�Zh�VL|V)�Zedcu[h[lm [n,[s�c���Z�@b�Tc�f�,g��h�,h��[gHk�#<[cD[stk�(l�\plzu��<���
X[a�[b�[e�[f\g8\h@\i�\l]nL]r�^sd_t�_y$�_���[a�[e�@l�[s�u�#@����[bD�78���[d���	���t��[l��\a \l(\r0\st)<������N$�PT�̌b\\e�vkx\s�\tx��p����yd\p��l\sH����u�\rl�s��H�l��r���\h �!�\c�����\s�\t@���\a�\e]r4�����P�dd����\n��������]a�i@]k8�p�!;,]i��)4]r�����+a�]b�]d�]e�]f^g^i,^kt^s�^t�r���)tNl�]u�%!L�l�]e�]u���#��%�]sh��]n��:����]r��)�Nl��z<��]lP�!�]e������^o)>��)$^.H^aP^eX^l`^u4�u$����8�ux��a��!h^t��u��!�^a�^e�^o�^s�������^m�����^rD�u4��#T��^n��^e|�p_t���#H����^s@�8
a(_e8_i@_oT_r\_uH_�U@
i��u�24������x�d��x�)	�Ls|_t��@�!t_s���:��l$f��	�_d�_e`k`l0`mh`n�`r�`sat�m�l�_n�_s�p�,p��_e�sJs�_tyl|P$�u(�)`a<`u��� `m��7��p<�D`c��)L`e�`r���X`d�~e�`g�`n\������#�!�`c�����`sĆ�`r��!�`e�`u22$���`m��
��5etpasatБ����h����ah0azpV�#p�C(al�)�uPas�'_@9��ah6��Xac�ah�al�ar0btd9p9�aa�ae�9��>P�al�?ul?J�ae�B��as�Au�af�alH��CJ�C!�aa��e�L�� Wabibt(bupP�$R!|�rtS7�Wl�]3�\��8bc\br�a!HOh�`��Pbt�ztbo|���)ܥk��)�aih%�bc�bl�bncr<csDct`((�bhZJ�)��bc�(�bs�5N�3�bs�8��ys>��V.cf0cs,�u�?�ca�@:d@!(ct�@�
8E�#�K�$I��LchdcsdO)p!
�f.dbdd�|e�fdg dm0dn<dsDdu��lca�QbXdclddtdeefUgUhei�fjfk flxVm�qnLfoXfpęq�fr�fs�ft�gu�gv�w�gy�oz�"��)��.�@�J�C��(dzLT(|\�Pds�_��uh�e��h�)l�!<��dg�k�dl�dm�dn�dr�dseu��L���deD�\wil����.�u�dsl������8%.<�e�ds��N�|��`)$f��	�b<ec\ed|ee�eg�el�en�esftpk)Heh�k�m�lem�lPeeteoأ��m(�m���l8v�|�x�ad4o�~u�������es��)X�e�eo�es�et�eu�lБ�|���HW���eÜ�u�(����fu��l�%Dfe8f�|�%����0f���6���Kr�c!�fo�R��R��dfrHs!lfes��xft�q�fr�z)��B��!�fagepFf`gi�grys�gu�fÐ�)84.�fs���d�gn�����f������p���gd0gi@�n@gr�Rv����]���8gfl�#��Lgl�	��Tgl�{���lgb��n�;tga��gr�!�h%�a�gn|�s�8�	�Pu�gu84.�#sp!��x�b`dc`hllhm|hn���ga4b�hc�hd�heLifTig�ih�ii�fj�ik�ilXjm�jn�jo�jp`kr�ks<ltxlu�v|cw�ly�z�h��7���f.@F�C��thg�hk�G��lPdc���ht�c���h����hh����hst��hi�'npJ�	��<�HRfinir(is@itd"����in����<�e��8%.8it@�J�<�h`��diixilx*pie�*��+�:�ir�T�$f�����ie�is��$����in��B��ia�cje�Hi0jo<ju����T|mjnJbH�)�ie0���jt��P
.����Юi��$jn �)h���Djtp���Ljahjp�Btji���
<$)�jd���|joTNt�$E�;6���jf�jm�jn�Ks�ju8E��HqG���ji�jtJ�0Z�cka,ke4kf�DhDkiPkoXks�c�$kne`h�ku�o�hn��<kn�q�w7�zxke�ki�ko�kr�y���ka�ke����)|�HP�l��
�ka�kc�keXwh�Ullm lp,lt4lu�z\�u\��kn̼T��X�[`��le�����lad�'��l���Lledlop���8%.\lsL��>�leh%��llrH?[�gp!@mb\mc|md,qf�mg�4h�mk�mlhrm�mn�mrXnsdnttnu���la�nb�c od\oe<tfHtgtth�ti��j�kpVlDvm\vndvoHwpT�q��rPws�wtHxud�v$zw�{z�nÜ"yDl�&Dapm�@&��Lmh���'��hm��)��.}e�mr�0%�5�8:��7���me$0i�ml�<�C4�e�mf�mi�l$	o�E0GtDM�L��	�ma` b� c(ne0ni\�m8no@nt� z�N�P�$R�S!� iPnrtS/LT̅tHX�#�\		|\lnb�nf��g�nn�]�^�#dc�nc�nh��m�nr�c���n�,w�z�dd\f\�i��p�gؑm\p�o�Ioe����nb,t��o��I��B8@a8oü��ܟ��0o�����os�)Dou<���Poa�ob�oc�oe��f�oi�pl��mqn�qr�sstt�|xt���
���e�����ooP����ohL
k8�!T����vk�olpnps8pt��J�vape�'��\8	b��(pt��'��)��u0pa`pe�	r�:P�Lpd0�Tpnppr�p��l����xpa�pe�pi�pl�ws�ptl�uD���nxrl��L�n��!�%a�pe�pi �$rht�zd��pn@�!Dh���PG.,qa�xnoDqsPqtxqzl��#��)$qc��L�ml��8qe���\qs��7<�2T���dqr`�)lqe����8%.�+a�qe�qgrhLriXrk`rlprn�rq�rs�rt`swlsz����b "�8"���q�P�!�qe�q�0��<��qbT<c���rl��ra4r�n$����,r�`���D(t���@rn��l(�!).ȐÌ���re���|rb�ri(�pę
��N0�a�rtp�
$x��re����rg�rn��!�rase si@srD��H�J��}����se����sr�b4snH�h���,sux�;4sa�J���Lsi�)Tseȴ�xsa��$��se�si�sk�ss�stT��,����l�MuP�)�sd���snx�)�se�sp@�G��d�X@��sr�����t��(/r0tt�@�!(ts`Jðlhte|'�'��TtaD#\tr�:)�te�ti�C�T@���tnJ��G���tr$f��84.�ta�tdue0ug@ulTumpun�uovr$vs8vt�e\*l��r�m��l�trr���th�m��ur$uss�f.8v�(}|��8ue(�����Lum`�p����ur���due�~f�Ih�ui�us��v<�J�����ut��u����ua�ue�uu�2ȇ�urD�$4��uf���up �h�rvs���vk$�F���z������0vhp�lPveX�<e�6����b��f�vg@0h�vl�vnخo�vp�vrws$wt|>ux=)�vl�A�\J2DJ���vrJ)�veG���vt,K���e�L��iwnDQJPQJ�ve�|��Swz�Wy�\���&f,�l@wn<`L
�c��Nlwa�6h9k|wt�%z\�t��d�twr��)�wa�we�whxi,xr8xs����P�u�������wep����wh�wsL$�t�\�wr\�we\�o�����s�	��xn�c�; xeB0%�h%��@xalZb�xc�xd�xe�xf�xg<yh$�iTyk\ym�yn�yp�yr�ysztzz`(m�)J�xiD*'�*u�*)�xld-�h ��/���xr@/)�xa�xe�Zlys0yu�/4n�6� ye1Jyt�6�49�L8(yn\1�rLyu 2�2�P6��x�.�yep�fx�g��l02s�6��6|yn�8�_e�t�=)�=���yi\=���yfP>y>���ya�yk8�l�ys�yt��d@��@t
�@B8EB�Hl$I����b,3rPXLza�zeH{i`{o�{u�z��XPXDzchzhxzn�zr�zsXY�<D�0Z��pzd�zg@Z�`ZP�Z\[�	�Zu�zl�Z���z�p{��{�\
`[���zg�zi{l{n{r0{s8{t@\L.g{sx\,�\\]\L]\({g^��^
d_
�`��_��@{rX{ta�da�\b�@b��h{rcP�b|{rTc\�l!�{a�{e�{oHÔl��s�\o���{sdvy1032230010024240441202041032421054004000430244412322604004123100442103630421031021243002003206010012302322143600203020123220321030601043410243241005000704000200042114212100411440004402011023400300631221022211101023623441460300022022245400042380024102040214821000322080021080000000
8000006000400442003240402041414401087000228720008321014202510034801021123142042123006541025013011123252431060004403001402424024824022610822004504430222440456210302252108002410340140100021013230220060401062405120520263021223005428000440064120304322210304400142423334212213233023640452526610223424112343110140223000121030140301023010232101254301001212125216101214120005010120000202003210003032143232000400002310001050010302144100000500200002301123224000100002223220120401004002104010100403210422342432423030000130044105432834000	800000000
8000002000602002030022210422101042033004142340431203411032034021021042210040012231226222142212223220300022332020320020202141014102032434104110204104000002001060010100444100024006120212420210212344323430432420200321402010364000232045103212124101221022303000320432200210340020014420014300025000542110021052161032100211001321040234432103212032110302101120212103004102143032121021312213202120103002121221102340501001000000700003021210203010001010030423021120021240201444310202100202123224240243400221104303103061040650000201003403432003412101220300010202142000006320500003400041610302303003030003024100022300043034201032221004300241300010430201010204342000202041220002213430134202223410343002002420422200204121021000104003410202300323223104242220400200210210240224220432025210101412442300106421250245020430104002042300022300400200320111002212004210010034102134430105211240422044021252100030232800022140442201043223234106341021210434300301085001001046033220300822244223124102002042022520010020610404104436410210021110402242014200003432323461101020007401224201260410322104022123212302202420434220301421000021010012223043040222004321041021043032143400020040600001871003321071208000030403140543060006210001201025413410000212301212320013630220232210300103004030120200040340240302410301221021300232115301133061020234403065000000302002300103100060000061001042040041030230000340204210002121240341012023214422006104103012220323300210202000104300300000010406210342001001030200030041240120104010410423043060000010201401211030032102012302012121001002204438000303131410420020121011214303201223400040000424300100340221200021030008210324104004043232432123223021020210410210210214200620412402100102120102102324120410410401065020140400210214342104310021440430000003432004100000040000204303210652604140404025410420006002102121004300000365321434404302024254043014060230100234200232010230020403002302004001023143401025200401250010340100200201450100210104141030320214244344100104000202444006102223022000214014122421030202132242121421412100204020003434322004320006050000201102324303204225004344020041100242044341520060120204523430100324000100200010102302421036102032420012010400010401030604003003200403001600020600320241000124650000032210040043223200040010040040040042043010002001241400102325410434303000400231003202108261060000221203002130322223220341404121224343252244103202214005400241204104123443010010011020230300125004010001065212065020010234300000102543000022400101012443412140000236002321210214320404344101240204000410100430214410254872102004202120123401030004324002100010020020020020310234343000026403341033220100020320050213034334630000161221040300024060000102434263411114361032010400010400222144042121040343040323440321250310600343012420062204400012520320410063325420344002506221065212642202022434410210432410212220122240403404032022410140003400102500104420360007800001421043001434070002412634620001420052210412040430010468521021254213243000103621023024404413420442121024123040340041043043212104064014003101032221034000021002004544500032232232213220002032106543223041223012023041065003001007020005021032400340405200041041043212030021410001026125420222016021022225402502040232123410230430402121010100061060030410216403000203030203040032032040110203100306301000401012001332010026203041204243121040000356000045042221131012100300204024030043242340312203002323300401203005400214430410500000400002430320650002030104600000060322023021060010013400040413212343434102112140242003230381241214341030443214150002050021220400640022420100142243320444102322210420041042420302020232102120100036344364342230400340440300224126001040234240041043241063001100010002430204122020612213002134512125221200002322422500040610802102414023030063000430221321002042344000321432002122030104304010200000054203032004000400306043232420030402030401021020062402432200230202201030412614232000030032430210302144114105023024303402323264214122122451210021465210200300041000002300242020230434324304043245203003000200310210212210434100210211202321640404222352004002002306050087080020002430301040000102103004204540010104104323032430022200423400010120120320000600002103223234300403230000102000046303000341001262143232300234440010001621401032432130101320042431021422012422243102004342100242100000423206022441002041021021032030363060400210254201024141006343030201742410201303201412522410020021412000412005421040043440021240302020430302320220410102004240320000240200320410430432430230412010323204102144102323230011040100400232520320500024430430400300040000002001000300024232120021012001030008002004303006123000308041023220032414216304304202025432014324122022304330410403310702126126102111141210043024041401430020403200460004100023662134112200400120343201302325010321023610334142003450303410210452202425321044200423233200050600213000201220201100102404131420450241524146413003224022423022200200081121046010025430043211416302014104214201020428001334233101320101400101202011401622141001230121101023411032214121132142320033430002002144022043210020004202504440320300320610300010011408300300044124032166020104404410320222430043423320001030230402232	10008710020062020104302004030004300020200120021010004306102101202003242020144872223202200400246500021440120010403660021243063502330033100411004101123004302610060004105201060504326400410403240430120421412412144123254100463063232301032124432146101004300244322023021232032230434332102000640002201004300210400141052002104102002621022234404121040012104201402221230004104100100222146003000032120042232400302064412624304201210520200614221220011100520000200234400230200232200410202232400000452200410412521004302124000200054320032030322212342011140000123223445210630410210340520100411204120210210030210010344300044304043000305200230320000020001443234406300001041303004003003242104634221410221252223410221232401000421224300022141003023004014103022104412104212324024104010002342000221000502032320010221041020104103200410210021043022341204052021043040031201040302004030210461040302144430430614106300023240310041042344031041020002012342016102254212320434202100140320200434200410061032023432200004134000240000234001043020100245000403204042301032402004030010220022032322232102031002106122304002034124100040200240210010021004102430650440602345401002000300123000120200234321212010424340410320432100020101243030223000420002001012413214302326220010042084122003022342024032434100120424021041241023440002004004260000000300023240600040022043043460046210423434220402161040021234145003024121232030340100140104401006103452030500244024220000302010020003020001002000220201020003440023210034021023232014421040022140102423430004201422103400210414143040032610142100332004312104040001322360250870441432444612122101061210004010041021032330003080321440306032140343210250240050033010364412303210103000010222165050010062100307642010344000421200265800041302100460204441003340301050000610100400264387010010021212212142023133400124320202120043032203200024102120PK
!<.;�\�\�hyphenation/hyph_de-1996.hyfHyf0tP�����'-4�������$�'–’-111001`h��������.Ha�lb��c��d̶ed�f�g2hT\ix�j�k�l��m��n *o�Wp�mqxnrT�s�tTu�=v�EwQx�Uy�Zz�`�� t xaHiPlXoH���b�c�g�i�kl�m�n�p�r`s|t�u�x���aDb,cTd8e|f�
gth<ijHkXlTmpn�odpr�s�t�u�v�w�x�y�z���!4"�"
hlpr�}�"�&�#��xk�.�,�n�r�u�.�/�2!�1���s�4�4$�a�3���t�7(p7,�r�5���be4lTt@8�:0�: n�:$(e�<5�<@i�<$HedsT=:T\>�?,le�=��tp�t��Ah@$�s\BE�e�r8A���dg8s�t�Il���k�B�n��M�B>�i,DQl$s|U�DiEZ0tX^0G:Ti`pht�5L.�Ga`!��e�Gpg�G,xa�h8HjpJn�I�o�s�jrN,�M�e�J���i�km(sHt�N�N,�a��v8�c�O$a�P{�P$ c�v Q4i(Q$<e�T�la`R��Tt�TMW�Uth�Z�Z!�d�f�s([�]��c�]�t]�h�_��d�P`���mt�`����4�T��i�xi��s���,r��brMr8q��(edr�l��4e�ouyHujXu��\axe�g�u�u��bv$�a�r��l��2��1,�e�|���g�s�H��~���kl��t��r�{�ed����sZ���tԉ����$h���<���8btr�t����@a�e�i�oy�����i�mdN5�M��n(���V�����.�s��,�eW��{��,�l@����a�io0r<s`��n����L.��rT��H�n����$e ��PeXk`t��$��<�!l��x��heL�!pnȥ��|ed:{�{�ox����m�p�rH�P����e���|����f��$����bD������������s������rt� e̶��
,b�h�i(	kL	lh	mt	nP
r�stuDvXxX9��ti<���|ex�T����e�n�s	t��$�����a�e@��H���gT���nD�������p	tX���Z��{	rl�
(� 	e��{��,4	iX	s����<	b���E0�`	m����	d�	n�	t�v�	.D��	r�,�	e�	r�	sL|�
����Q�	sp��	e
r�"5���	iX�Zd�p�!
t|���
h(� 
c��(,
e��,8
r����D
b�
d�
e�
f�
is�z��
n�
r$�,t
a�
e{5�5@{�@��
i�
r`�5l���
c�����&����*�1�
n��
i���
eTr,�$t�6��$$.teT�{,t��U<t��?Hi��D`��`.��hnاO����np�,�e��T4����p�t����a�e�h�o�rh�X\�\�$�a������s(�� g(t��
p���f��0r�8ep�i���Pt|!���di�rd���le�i8
lX
od
s�
u<�Mx�M�o����n$im�h���t����s,���e
l
s\25<	�.
n	���es5�	!�r���$
gH
w,
uP
z���D
t��L
rP��/{t{l
c���t
s�~\���
t����
a�
d�
e,sHuL
��j�
r����
a�
e����
nr$u<���aeH!�l+>D1��0{4s�/��<sP6��5!Tt�1��\u2��ha�e�i(o`7
�f�r�8��8�e��8<�nP<���a�iP=��nd=�8=���n�A!<?���s���dJttJ{e�H��m�[�T\��4adm�n�r�s|u|g@u��Xa�m��M �te�v��w��e�k�n�u�x�,{&�{d{$�e|}��������e�������a���h����rx���oؓ���l����$bli|m�t���0a�i�l�nos,u`�!��r\���tp���@Wv�o��,�i�|����eȠ!t�{�i�
�e���e(�w�i��أ���f����p��
(������$sh�Md�,8r���@a���Lexi�o(uj�l���pf�t�v0
M�����u���L.t�,�e�"vX2���n�,�i0����g�v�@�����r����eL�Z4�$r��{t����s���|���4dpi�l�s����<a�d�e0i<m��!������xa��,0����t���x�z�����e�ln �0����a������lw���hl���c�����s t�������f���(tp���,����DcD���Lsl���X������e�i�o�ud�0
~@���s���e��p���b�t��D,�h�&�|%���s�)� *���abHp\r�s z�+l+a,e�+w0^��?!4r8?��<e`A!A��Talt4FQ�s<�� �Mxe�9������g�I{�lJ�a�er�G���thJ��nr0���e���dD��xJ:�J��e�J
�P�DPo�Q��P��,d$Z�Y��@e�t�W!Hr�W��Xa�f�h�o�r�sPQ�Z$�h�^��`��5��e,�a�e!�k<j
0iw�o�j�@�r���sn���mxn���a4e�i(o`�Ⱥ��} s�|��(bdc�i�l�s~��}!Pa~��XhL}~�ptt��xn���è���i��$�"P��r����t��'̉�s�����ac�S,db�e��t�{h�)0��� a@m@��{8a������Ls�v��T�x��������pb�c`74@7�rP7�eܦ:�k�9�\���e����i����lT���	�ac4e�h�i�k8pDt�u�l���h��?p��� kij��(cTnhrtt$�D����Lsp�I����`u�����w��|a�v
�����p|����m�t(�M��{�e������eܿ��i�bN�b�i�b{�e�b�l�be��!g���� e��
,i���do��Th���Pe��{XrČZ�prL�xe8�,�h�����c�-\���s��,�e�����gl0nDt����aXe�h�i@o�r�s�u|�b���e��gt�'l���$k��{T�,<o��z����Pepf�n�s��!��`���xo��!�t`����!�����a�d�m�st$���uL.�s�����e�>5�!�mP���aeH�(�<q�<v n8���(i���4n\phw�@Z����Tl������{ps�xe�>�i�oP�!d���s�
X����i����r �r����eT���f<lHmdn�r(t@��#5�#i�#,$e�!0m�$�To����&v�&�\a�d�e�g�i�s`'�\(z�(�)~X)�tp*��t�*8,��ios4.�nd.�P.���s��.�mp�!�.��2d2 a8rD4��6� 6��@e@6��Hbp@�L@��`eL?
hn�=��te�oDf�C���r�F~�F���lTE!�h�s�E���aei4oH!��M	{�aJ!�t�H���i|MDM��e`O
Xt�N��(r�F�hd�F@n�O$Lepu����GM�Q�Q��xe�UhU���l�U���a�[dZ���s�Z���a�iw�a!�a���en�|$�b��t<b���sLg
�f
eH{�b�c�g�n�r( s< tH u�� at b�#c�'d�)e�*f�,g�/h�1i`3j�3k�5l�=m8AnTIo�Ip�Jq�Jr`Rs�UtZuH_v�_w�_x�_y`zX �t ��e!{�#�.��,�r8Ab�J� a f tK,�M�(QQ�T�`R��  tHX��U,4 sZP`�`���U�P ��'��I�@_��l� a!d!e�!f�!g�!h�!i,"k4"l�"n�"o�"r$#s�#t�#u�#w�#y�#z� Àl:� u([�o!� f0p!uDp��� ��#��j:q��q�8q��!bD!eL!i`!lt!r�!s�!t�!wr$|r�X!l�r�`s�l!a�s{Xu��!k�!r�!zx����p�� w�(w��!e�vm�!s�w��w�x��!i,�,x�Lx�lx�"n"r$"td|�py��"s�yHz�z��z�T"a�"e�"i�"o�"up"�z`"g����|��h"��"�L{�.|p|M��|:�|�:�|���".�"f�"n�"r�}$�}~��)�"a#ü��,���#�#�x�2��9D#.L#a`#ih#pp#t�#z����X#r�>�\�E|�>�#.�#e��I��P��j<�U��m�#i���#ed�:�#r���������\t�aD������	�#a$$c4$eH$h�&i�&kX'lt'o�'uЈ'0�����,$mLfԉ@$.�$a�$b�$e$%f,%i4%l<%mD%nL%o�%r�%s�%t�&u�&v�&w�%�P��$l�$ut3l�5��r��$c�$i�$r8xP�~=�Č���$f%k%w%à=��=��=��%����h����8��h��p����`%.h%bp%r����x��hK�����x%��&��
`RjH���%ut���%e�%o&r���T���%g�S�%rVr�V��V!�%mpV���%u�Va�%a�&iX&���H%&.l&nDk�$&e�w!4&m`��@&u0W��L&��"�<%{d&.T���V�x&t���&f|�x̏�ԏ�܏����&n�wĐ�����&r��&a'e'i('sP�{��&n��d�'n�{̒� 'e<'lL't����
��D'ad���������`'n��h'n�����'a`(ch(dp(e)f$)h,)iL)jT)oh)pp)q�)r�)s�)t�)uT(�\2�<����'.(b(g(m,(n4(p<(rD(v�����,t�!$(a8>P�����H_$�D���L(�ܛ,��@�D#.�a�(f�(i�(n�(r�(s�� ��(i���`��(n����������(a�(i�% �+�(p)s)t�1�6p�:<�)r@�?��Dȥv@)ex��L�!8)n��x�:`)b`�,����'�IȬx)c�)s��>�)e�)up�5��8�D�)t�)z(�PT�j`���)e�)r��M��� ��̶,0*b8*c@*dH*kX*lp*ox*p�*r�*s�*t�*w�*xt�!��!�!(�,l������P*ah*eH��l�T��!T�X�����*o��4����*t��,8�,�! ��+k+n +r(+t0+ud����*a8+e�+f�+i�+l�+o�+r,,sL,t�,u�+Ô�:d:�^`$|1��P+c`+nt+r�+x�c�h���X+t�9���l+l��\E�+u�,�+l�5,�H
n�G�+s�
:����+��
�
w�+a,e,�L�X���+�,�h����P$$,a<,p��,h$D,al,e�,r�,u :d,i�,r\�s����|,l|
�,e�y�:t�,r@���,r�-a@-dl-ed.g�.i�.l�.n�.r/s�/t�/u\:-b -d(-r0-u�L`��{L{8-iT-r\-u�
�,��d-i�-n�-r�-s�$���-a�-e�-t4~<�-b���-u %�{�-at�����-l$ �.a.e$.i,.p<.sP.t\ {У�p .l� M� �h��� �4.e��v� �H.ih!z,!$\.lt.r�!�$#�"��|.tP#$�.a�.Ä#M��$���.�&!�.e�.up&�. '�`)��(>�.e�.i/o4*��*M�+�0/ml+�/a@/eL/ph/t�+5�,�l,�8/t$-�X/o�b�-��-`/a|/e�/o,.P�.e�.^�/cH/'�/h�,`'�t0��/d�/�/n2���".0a 0eL0ih0l�0n1oL1r�1s�1t�1u�1w�1y81��1�0tp5$`7�80rD0s���P<��00h4><?X0n�@�tB0B{`0a�0e�0s�0ä��C�0i�0r���\C��0h�D$�B���0��D$�)z�F$�F{�0a�0e1t�G��G���0e4G�0rPHr�Jl�H��1r(1s�JhK��6��01��K�K{D1ad1ex1i�1tlLp1s`M��M@OPO$�1a�1r�O��Oa�1iP�XW�R$�1sXX8[,�[h^��[�1nT\��
�1a,2d82eL2fT2gd2k�2l�2n�2s03v�c��b$2s�c�D2nf�j��k
���n,\2.x2e�2u�ovDq�q{�2o�t$`x��w���2d�2e�2g�2s�x�z9��d|��2p��:�2a3c 3e(3o��{ܯ�<�3.�3h<��d��t�,\2.H3eP3lX3s����C��D^x�l3e$�
�3k����x3o���3t����".�3a,4b44c<4dD4e|4i�4l�4o�4r�4s�4tT5u|5zp5�����3d4l4m4r4t$4z���\�������$`
|����Ș����X4fh4nt4u@��zܚ��`4n�:|���n�4i�M�����4a�4o,�,�4u`�`����$�4hX�j��5nP�$�4a5b 5i,5rH5s����%��5kT�
85i�0�
,��
@5t���`5nL�3H��$���h5�p�a���'.6b6c(6f06gD6lL6m�6n�6r�6s7t7u����5ap7b8d@8e�9f�9g�9i@:k�:l�;m�;o$<pP<s�<t�=u�=y�=z07�d��H$8 ���6h|����!<6el-<@�Ul�`6eh6ip6p|>A`�F���ܯ<���x6a�6g�6s�6z|�hܰK��l�:�6.�6e�6m�6r�"���Q�O\P���6i7sP�W|�����X�7g��]�@7m ���$7��;��d�r�|r��H7n�7s4���P7i�7r�,`7e�7l�7o�7rX	s s���cXu���7h�7w��i��r�7iD`��2:�|���7h���7u<���:ȷ���7�ܶ$8r,8s88u�7�t�
�)(̸,$8t����x8b�8f�8h�8i�8l�8n�8p�8r<9sH9th9u|9x��o�ul������8n��<������8d�8g��{�,�8s����1�8oD����X����8f00h9l9m$9t,9z��9����8��X�X���49k�X9a`9h�<��(�ct9rL�,p��P���,�9r"���,�9i�9l�$�P#���9il���9e:n�������9e��{�9n�v���9l�����9a:s$:v������l�P�,:r�,4:a\:oh:sX�U��T:h�Et:t<�������|:b�:r�:uh�$�:a�:e(;gX;i�;oh��t�cp�{D����:d���:n;r;s ����,�:a|����:f�^�����;�H	&;����g��4;.l;s��{<;r0�L;e���h$d;.x����x;b4����,�;b0���;b�;g�;p�;r��c��{�,�;a�?������;e��������;c��<s8V:�c���!<.|�,<e4<h�����ܿr<<l`�$D<k�<t�<u(�����d<b��cl<rp�x<e8����<n���d��<k�<m4�$�<a�<e=rT=s��P���$����<g���<i=r��ix���=f$X���a=e0=iD=o��U<=c����:	{��L=ex=tH� ��d=���l=Ð��=f�=m�=r���:�=b4��p��,��X`�����=z�����=r��,�=e�=w\�r����
�".8>a|>e?f?id?kl?l�?m�?p@sh@tAul>�|�cL>bT>d\>g@����� ���������d>��?�����>.�>b�>n�>r�>s�>t?w�,(����,�����>a�>i�>u��%L��D�����>h��m8���?�\2.,?e4?r<?sL?t���(��\��D����D?a\?i���$L�)��.��t?lp�,|?a�?e�?i�?l�?u�����?i��:���?nL�5D����:_���{�?a��,�?f@r0i;��?$@a@@cT@t����D8�,@l(�4@h$�?��L@r�lp�$`@.�@a�@e�@r�@u�@à�,��������@��@��5��@l�@m�@r��J��Q����@n��5�
�@e���{��l�	�'.�Ab�Ac�Ad�Ak�Al�Am�AnBs����Aa4BbPBc\Bd|Ce�Cf,Dg4Eh<Ei�Ej�Ek`FlpFm|Fn�FoGpGr0Gs�Gt�Hu�Hw�Hz B�,�W��,)]���Ai��U�9������Ag�Ai:5�9��Anp������b��hD�Bsl���B��F��H���@Bu���pH,HBh�,D#.�Ba�Be�BrCshCuh�U���|Bc�BrP������Bt��T���Bs�Bl�Bs�Bx��(�:������B�D>�B�į5��Cs�,Ca@CpTCt`Cu�n��!,Cs��4Ca��P�LCe(�{ �tCr���@��Cc�Ce�Ci�Ck�Cr�Ct��t���".�Cf���py����Cb(��
�Ch��DiDt$Du�0b��$Ds��$	�>.`De�Df�Dh�Di�Dl�Do�DrEsH�t
XDbtDi�Dr�
���i�
�|Df�Dl�Dw�Dz��s p�i@��$�8"�8���Deh�Da�#:���>Ea�����E.(Ep��8 Eo���TEd|Ee�Em�En�����h$\E. ��dEs�MpEr���(��Es�c�,D#.�Ea�Ee�El Fn,FrXFtD���En��:����EiEF�̡�ܡ��F����lwFo�><FaLF�,��t������DF�$ixa��&hFu��Fa�Fe�F����Fb�'D��l����F�@�cp�Fd�Fl�Fr�Fs ^T����4�
GrTaX�h��G���PGc`GexGk�Gp�Gz$GÈ�\{�XGnpGu��:l��Gr�����Ga�Gr�(!4!,D#.�GaHe8HhPHoXHr�Hw�G��!��!�Gr\lU$"���G��"UL"Hi$Hn�t��"^Hn@#�L#0HeHHrd#�$�l$�pHi|Ho��UhHn�$:P�|%��Hs�&,'�'9�He�L��H���Ht<\�',�Hb�HeIgIi$Is@Iw4I�]U�(���Hi0a�0)�)��Ind)�fUP(��,I��)LIi�g: *{pIixIp�Ir�Is�It�4,8?A�G��KE\2.�P�W�".�Ia�IeJf@JhXJl`JnpJo�Jp�Jr�JuJÄW1T\1�If�ĉ:�[���I��^{Ja$Jl_{$`
$a�,a��,J��`PJt4J�a�dxe��g'�ehJt�hh,|Jl�Jum0i"m��m{xn���".Ka�Kb�Lc�Ld�Le�Mf�Mg�Mh�Mi�Nk�Ol�OmPn$Po\Pr�Ps(Qt�Qu�Qv�Qw�QyRz�K�nDK.LKb`KdhKl�Kn�Kp�Kr�Ku�c�o,XKtt�'0pE�q�tKiDr-4th�s��|Kg�Ks�KzptK�H��J��t,�Kp�t%�uc�v�v���K��Q��w�	D#.La,LeDLiLLlTLrhLspLtxLux,Lu� 1xx�x��$Lc<Ln�x�lx�x�ly3`Le(��xy9��?�yE�Ln�\�y�D�:�|>�Lo�z,�Lr�|�
�La�LfMgMh$Mi0Mk8Mn`Mr�Mu�MwT}'�~K�~�Lf�~� :Mr\��Qt��Mn���\��LMs�W��DMel�d���XMa|Mf00h�Mi�Ml��'��%�h����,(�'�Mr(�w�Ma�
��{��$�Ml�Mn@�,����NaNeHNmdNn|Nủ,���\�{Nn0Nr$������(Nd@Ng�]��>�PNt0���XNitNt���X�b��:��Nl�Nm�Nr�Nu��,�NaOlOo0Or<OspOth@]4{�Nt����Z�̗!�Ne��m����Ng �
�Na,�:��Or�����>(Oiԏ&LOaTOhĦ�j�V��
\OeL�$dOrX�:\�|Os0�$�Oe��^�����O��$�Oe�Oo�O�x�����OkĒ�Or�^$��Orܓ!ē�On��MPaPe,���<PbDPdLPpTPrȕ�d�c�����Ԛ'xPa�Pe�Ph0pg���pPd\��P��Pn܁&�Peԁx�8�$�Pa�Pc�Pe�PhQiQt�l��Pl���Ph@��������,Qa��^ؠ�$ QePQh\QipQo�Qr�Qs��1�HQeH�hQnp�
��������xQm��a�Qa�QeТ �r����Qh�Qm���Ȥ���:��'TEw(�,�Qa\���[x0���Q�p�,$Re,Rt4RwR�����}�r�5<���<Ra�DRl�RuT���
PRa�Rc4Se`Sh�Si�Sk�SoTp@Ts�Tt`UwtUy�R�Э|�������R��S�DU����l��Ra�Rh԰�Sc��ReSiSl$Sm,Su8�d��tB���Sa��Բ�ijbHSbPSmXSsH���������elSi�^̾z����tSg|�|Sn�SsD��l��(��Ssܿr�Sa�Soأ�4��Sp��c��Sf�Sr����:��rTh Ti(To0Tu��{��{����U ����8Ta\TepTixTp�Tt4�hTi��v|�0�rt�,�Ti�Tr�Tu��8����P4���Ts�Tu���Ta�Te�Th�TiUoUr�����\��\�e����Uf ��8��<Ue0U������(U�<���:H�x�LUsd�,TUa�����lUl���Ue���Un`���,
�Ua|Vc�VeWh@WixWm�Wo�WrHXs�XtlYu�YzhV�VbVfVg$Vk0Vl8VmHVpTVu�����{��]��h����Vt������t�|�@Vf�`Vs8� ����@��W��Y�T�z���V.�Vb�Vi�Vl�Vn�Vp�VrWs\2�P�<����<g(�%����Vi`�����������VsWu�������!(Wa0We8Wl4����������vXWllWsD��@���PWs<����dWtD���Wu$����Wsd9589�Wn�{�Wa��Wm�Wr��,�Wtth<���Xa$Xe4XoX�lu�����Wt`�|��X���^d��,Xm,{�{@Xa|Xc�Xe�Xi�Xo�Xp���hXn�pXh	{�Xt����	{�	,(
rh�Xk�XnYu,$�XaYe0YhPYr\Ys���l������Xg�\$
:
Yi�
��
��Y��
$Y�`���<Y��
aDY�,���dYb|Yn(���h�Yk�Yw����Yr�$�Ye�Yi�Yo�Yt�YwD_�L`������YnD){�E�rT!�".`ZahZb�Zc�Zd�Ze([f�[g�[i�Ej�[l\mX\n�\o�\p�\r]s�^t_u_w$_x,_z D'|Zlh�PwtZi�Zop|$$��Zh�Rj(�ZaP�Zt��,�Zr0'0*b[r[sM�����Znh��Zi ���Ze4�!�{H[n���[aX[e�[f�[n�[s�[td�� P[.l[ht[rh�@�Okp?��$P[.X$P[.|'�[e<���[ht e�[s� {�"!�"�[s�!�[e\�l$�x"��\��$54\a@\oH\pP\s�$:�$,\l��,&E0&�&qt\a|\e�\i�\u�\z�&\(�'X)�\o|%P'e4+,�+x�\t,5,�\r�+,�\e8,n�/��/�\h`]nl]u\/{�\at]c�]d�]e�]f�]g�]l�]o�]p^r^sX^t�^z�^�\2�F@].�4�H]e�/T]nЭ�/�]h��/�]m�]o$�,x�`0#P0���]p�7w0�]r0�)����/�0{��"1��]r8�5t1� ^a,^el�; �^gh1L.��Cd��8^m��@^r�1L^ep^r2��1(h^iDUJ\��|^���}d2,D#.�^e_o_s�^���QlS�^u�2���^��2��^n�^r��Q<3��^g\3���0h�3W\4]X5D\W�5'�5'8_w6w@6��=,\_ap_ix_r�=h_t�>�B5�D��E,�_i|M�DM���_eQ�_a�_e�Qz�P�_m�QhU��U���_a�_e`s`t(�|V�_upY�`i|��Z�Z,0`a8`o@`udZ!8c!�d�H��H`a�`b@ac�ad�ae�afDbg�bh�ci�cj�ck�cl�dm�dnfo,fp�fq�fr8hsxit�ju(lv0lx8lzLi������\l�Hl�P`���4����L���)��P��P�@6��lalas�zE��z�Paeԉae,%i4%lXarhas�au���� ah�akd8$�ch��H��`apxat`Q$����ae�����aa�ai�ar�as<�{��ȥ�aa���8��̶\E�,�ald����afbl br(bs8btH
�
Pz��h$0bs�dbe|bl�bn�br�bs�by�d-itbs$ EP#�&!�(
�ba�(M�.h�-�brl+��bt�Um�12���baceciclLcm\cnhcrtcs|ct�1u�1w`7�<?tB0B{ca,ceC@cb��Q�{m8cetE�4G�F{Tce�K{x1iPr�R@uT\���cm�cs������c.3c�ckܿ�x���cl�cr�E�
������cadb4deDdlPdppds|du4�
�,de,dl������,h�$<da|�T�,@l�Xdh`�$ddc���x������dn��de�����di�?l�ds�dt����p�$�de����	D#.ed$eeDefTeg�ei�ek�en�esm�,eeerD�@4en<es��0
��st
w�$Leelelter�esh{��,E�|ee��!�,�ee�el�er�esE����@~�$�ee���ecfe�,D���eh�: *��fn�W�W��$faHfphfs�h
h,@flXfr�h�l�,�j,`fcxft8k$�mnE�ffxn���fa�fc�fe,ggDgiXgk�gm�go�gs�gthz�fØpM�v�v���f��g��Q��y,�|~gignt�\�!ge��Wl����$$gr0���tNt��8gn �E��,Pglhgsԏ��������pgtĒxgn�$�ge�gs<������@��8�$�ge�gi�gt��M��,ؠ�$�gehhhs�, �p�}hw��ijXXhgxhr�htT���$he�hk�hp�hs$it8iu��,�M�`hi�hn����hhe�hiL�f	o���أ
�hf4��hpܿr�ho�hr������r������hc�heit4�irܹi����ikt�,���0ir8�Z����die�`��@i�t����!k�Xir��Q���pia�ie�ih�io�ir�istjt�jz���ii�in<�,�in���`��!\����ib�
�{@Xa@jcTji\jl�Xpdjtj��:���j��D�(jlLjr�0jh���	,�	E�

�
Z,$ljr���$�jw��,�jrT��
�jb�jc�jdkekf(kg<klDkmdkntkr�ksltl�$,���,�je|0,kl�{ ke |}4klT��!��$�Tka\ks�${0&��&�|\e�\u8,��I\/{|k.�kc�ke�kg�kk�kn�kp�ks�/�]m�/�kh0����0����1��,t1��kc\2����l�D4�d2,lr�=�Q�Z���@lt����Tln4"rt hll0msH��plbXmc�md�mf�mh�mi�mk(nl�nm�nnDor�os�oupy���lahpbqcqd8qexf,xgLxhlxi�zj�zk�zl�|m�|n�|ohp�q�r��s��td�u�v�w��y�zDp�$#h�%��%�8meH$@mt�#��Lmhpmk('��&hms�)��'|mr�*��/��mn\H{�0{�mu�2x�1���ms�3{�3���ma�meninlnr��D4��mr|4'�4n�4 na�4�5�<na\nl�nt�5l�v�:Dnhpnr�:$Lne���;��hng��j�<$|nh�=�A8A��	�na�nd�ne�ng�nkol$os,ot4oz\B��nr�B�|C�,D9�E,�E,�naolor�E�,F�`F�0GK�G��H��K��J��<obhod|oe�on�ozL{�L,`oe$M:�L�toi�on8M^P4R�R,�ow`R��oa�oc�otPR�R{�T,�[Z!�og�os],pkpp�0;�]ETX�_��po@a�P`��(pcXpr`ps�`��0p�D�����f:8h:�l$�pe�pl�prasqu8q�pn�pp�s��ps�t���X��L{{�pr�z
�pe�p��:�|���p�4��<��pc�>�pud����,��
,qe ��@�$qs̶��\2.�qa�qb�qc�qdreXrf`rg|riXsk`sl,$m�sn@uoXur�vs�wt�wu�ww�wxxz����qn�qr�qsH�h���t�m�qe��:�����:T��ql��qeriԽ<���Mrh,rrLrt�!'����$rk<rl��9P��\�Dra������g�x�hrrT���	pre�rf�rg�rk�rl�rm�rn ssLst��&��L.��,�reh�&����ra���u�p����re��:sdsh(����������sc4se<st0�cX������{Dss(�m����sa�se�si�sl�ss�st��	l��ss��H��sc�si�sn�st��� �����������$�sa �$�)zH������`@.8taXtdltextg�ti�tn�ts�tt,uu8uzh�h�,0trX�Ud���Dtr�,Lto4�U��dti��		�����{����te�tp�tt�tu�\���ta�tr	4��� {�	p�$�tbuiurusuw|#X�	x�W��	�&&	��$un@�El�,l���um�un����Hua�uevg viLvnlvo�vs�vu�r��:|�*	����u.�uwl��ui�ur�us$�i�K����ukH�1	�7	�{�u.����un�$�ua �*	(���v.���vn8vs �=	��{0vsēH�MDva\vt��}��cT�dvb|vp��P��~���va,�$�vtx�AL���vm4�m�va�vewlwp(wspwt�wz����8��vrt�U`�E�vo����C	woh�@�� weDwt�#z��H	��<w.��O	\�1Pwnh�Xwi�dwe�wo���{�wl������we��m�wa�wh$����wp��	�!�wa�(����wr8������d�V	 xa��� �xl�r8xe��Dxs$ &2�`xudZrXX��XxtT\�xa�xb�xe�xk$ylpyn�yo�yr�ysHzt�zz]�[�xk�_r�xet`
8g:f��xs�c���xn�xs�h!�n�n,�xaye�o5�".ys��:�q�8yaTyl`yu\qDyu�qch�Z	�s$Lyu�t��a	�w��hyb�ye�yf�yg�yt�xc�yg	lez$�ylL}��}��yd�yn,~��~E�n����yi<�������yezo$zp4zs@ztd�<zl��^Ć�\�, ��,zc���(����".`zahzepzu�c(��،{|zsl�|�rx�$�ze��z���zo���zt$����zk�&�
�".�zaL{e|ip|o�|u�|�d�)�����zb{d${n0{tD{w,�mPB����{c��{<{t��س��{	x{a�{b�{g�{i�{m�{n�{s�{t�{ud�m	��m8���s	�����{d�{n��x�z	���	���	X����{e�{z��z��(�0��	�,�{h(|kl��{c0|e8|gH|nT|s\|tP��	��$��h�i����@|g,�5��:h|z,��	0�{�|c�|sP-�����	�����|n�|t`�{��� ����|���,����|i��|s��	�)08}s *���|a@}b`}ct}d|}e�}f�}i�}l�}nxIp~r�~s�~t0xH+ml+�	P}lX}r�,r�,�P-�l}h�-~�.�/r�}iT��0��}eL0 5�4{�}s��@6�}n6{�}a�:��`%.�}d~e~s�;�	�}e�;{�;$�=�	`AA��~a\~d|~e�~i�~s�~tH~�B5B��@~�C,�B,T~il~r8C�	�C:pC�t~i��e�D�~g�E�	4F9�~r@G
LH{�G���~c�~pI<�`���~eL�~n�K,�~ehs�L��
&�M{tP{�O(i�P��P��<bXf`lRQ�R�Wr|adX>�W!tg�m,xn>�".�a(�e܀i�o<�u�Üc5n���.�d�h�k�s0p�q5Xq�	�T�u{�t�v� �u�v����x���w�|	�'.T�a|�c��f��g��i��m��p��rT}{x��	`�\�e�}!d�t~��p�h�~��~����	t����f �z	@��d�50��	\�{Ām�r��̀e��g�o��{����}e�������h$�l,�nĖ�p������4�cP�sl�t�~\�tD��	�1�X��d�h��hT�$��a,�c��e�i �k,�l<�o\�pt�s|�t��u��@�����d�n�r�tP���؁d��t��|���r$�u��������S����:l�m@�a\�h��u��{���	X���H�n�P�at�e���	԰��l�f�'�ij\2.��b��l��n̂r��sH�!ж���6.���`�K����Ăf�i�s(�-	Q�n(��	��^��~|��t��{ܿr�u��Q����4�fL�r��!8�c���T�ll�u�^�����!��a��e�i�oH�r܃Ð�4�{��b��c��k�����x����ȃt,���Ѓ�,��x��d�����r������p��^$�d������	���4��8��d�ep�i<�Ô�	<�\�u���H����b�
h�
����n�̄a��e�h�i �r(�s8������Ąl�sU�	�T܄r ���t��m�$@�����l�
���3 ���0���:PD�i${L�hT��X�c��e��f��lЅm�n��r�s0T�! #�!��i��l\#�d#$��a�%
�$��ȅk�(i�&�܅g.8,���g�i4.�/{\/{�aX�ct�e��i��p��uX���/8�a,@l�]md�w�/@�h0
P00l�r��:�0��n1rP2,�6@6����c܆g�8z�8{��e�8Ćl�8Іe�=,�E,�U!
�p�s�X�pY
�ZE8�e`]�]��$�t�\��,�iH
܇b�c��e�g�h�l$�m,�n<�pH�rt�s��t��y��D�aЈc�d�ẻfԉh��i�cj�kd�l��m��o �p�q(�rl�s��t�u,�y4�z���t �H$~�#���h�)T�,)�/�<�5���t�=�8AM�I?�I4�e�J��on`�r���\P'X�iPR%
`R��l�a��t�T�W�U,��h�_�P`�Ȉs�`�����ĉ���8h
��{�e�h�ԉ���w��ox���:@�o̶��
�cH�dT�f\�id�kl�n��r��s��t��u�)
��ar��T�(���x�g������el�����4�����h�����a�(�4�d�,2L.P�a��b��c��d�eh�fp�gx�h��i�Ej0�k8�lh�mp�n��oh)p�rH�st�t��uԏv܏w�y�z��È2.
|�i�1��	D�b��c��f��k��n�o�r0�sD�t�!^�&~�2����k 3�`34B��4����b̊c�ng܊s�zPB4
`z	0G�Ԋt�H�TIm�";
�4����.�a$�t�4B
4�I
85$�aH5{�oc�WQ
p5,<�oT�ulYX
�g_
�f��\�m�6��d�r��s�6�����p��`��̏��6Hll7�q'47��e
�7��n�7ȋe`7��	ԋb�fP�i|�l��nČr8�sL�tX�x�8k
$�.4�e@�iH�s�"q
�8��8�,�r9x
p�
X9,\�m�9��
H:d�m��r$:��l�ep:5u�
�;$��s��w;����t�;�
8<�P<����a�e�g$�z�<��<܌i���
���b0=Q��e�a�
P����e>,�i@��4>��0�s�wwH>D�a�>�
4�l�>�?�$D�?�
<?����a��c��n$�r�?\w�@!��a؍d�e�f��h�s�t�v�@��y�
�@��sA�
A�
8A�
HA��}�
|��
�A��u�z?0B�H�e`�uC{T�i�����{nHE{tE��F�
��e|��
�G|�.�G���i�G����e4G��r�H.
�H����bԎf�h��r�I!�fx0��2xB��J���c�K��e,�h<�olL~$�s`M�
�P,��
N��4�nP�h�pX�,��!T�r�P�\�e�R��XX��|�f��h��m��n�X.
�Y���
�Y���fďt�*�
�Z['8[��[�[�a,T\���c�s��!�L.��a,4b44c<4d�eL�fT�g\�hd�i0�k��l��m��n��o��pĒr̒s��t�uD�vL�wT�y\�zܐü����.��c��g4l��nĐr̐u\2�.
X�}�����f̗E�<$���Ԑ����f �h4�iP�n|�r8�sD�xP�@��f|��,�ep�<��~��T��<�eܚ��D�s`�t��9l�wu,�,���	t�a��eđgБh��k�n�o�r$�zl��l�#x���b��$��e��,�Ø�1����ܑ��:�,�ep��|�B��
��:��r�eНI����0�eD�PL��T�D\�?|�|�d��m��n��s��VT�`�ܟ��n�|'(�n��T�(D�\����t��,��ؒo����rP��e��� �m0�n<�p,�a��gL��(�t���'�'�Up�����e��o��u���x�t0������w *ܓc�d�f�i4�kH�l|�m��n��p̔r��s�tP-��h�-�.w��i\/x0&0���f�4�$�t45�5{@5,,�e�6{6{@�eX�oP8��.��s�:$`�e9{p�t��n�F$=$��e�:����n�?�8?����e`A�A��Ĕa�d�e�B�pCvJ��G���tLM�K,�e��W{xn�8�ed�y�|�X�m,�\��D�s �L�e\��T�{��a��i��t�|�{8�s����r�$��e̕i�u������e���ؕo��~�Z��rT�p �s�+�|�t1��i\/{�s�U�Z,H��\2.�a��bh�c��f��gԗh�i�lt�m�n��p�rh�s��t�u��<�a��bܛc�d@�e@�fH�g��hȥi�j�zk�lL�mT�nx�o��p�q��r8�s`�t �uH�vP�w��y4�zD�� ,t �0�e<�lD�r��� x� �����s:!��(�n4"�")`Le\��#�#��T���#��".��h�$�H$x�a��o��sL%:�%���e�P��*�,�ėi�.
h0��/��̗l�o1�1���n�s�2�2��50�r�5���a8�bT�tH�ð6�p7E�;U07��@���<9|?��?,\�a���=��d�m��o��p�t���t����������?eԘf�������f,���r�?{Șeh@h8A��
�".�a,�c8�d@�g`�k$os��t��w��zAWd��L.PB, �e\B�,D9L�e`Dw�E�t�a�E,T�l|�oorD���,�H	�G,��w�Hh�H,D#.��iI��I�șhЙp@J��JK��J��
ؙa�b�l(�m@�o�6rH�sP�tX�u`�w�K��O�8�{�O$ �a8�i�,$P$�P�(Q9�Q$�Q'`Rx�h��t`S��T�Un�U,��a��eȚlКm�V���i��n�V��V���xW��ZZ!ؚe��f�k�s([�0!�]��h����bQP`���h`�mt�n��p��r��u�`��$��|��$���d��d��X�te��d��l�d��gTe�,fQh��f����z�j!��s�k�l&ЛuX��d���țc��{�h��oԉ̔���r��r$�a0�h8�o�
<����r���x�m̶��a��bԜc�e�f �g<�h`�i��lh�m��n��o��p��r �s��t�u(�xh�,��,��d�qs��tԸt�)Ȝe��������n��m�a��k���L.���ȼ�a��i������f���,�l�����<���4�eP�oX�r@�\��T�t�c|�m��n�$p�Q(��������d��s4�����l���g�u������a$�et�f��l��o�s�st�Ø�iX�!�gd���Q��n������؞���cH��cD�i\�kd�m�����<�gT�n��h�����8���,l�m�E��b��i��$��e8��D�5��������bȞpОr�;:��U��:������n �$�a�c�e$�o,�pH�t�mD���{H�r�����c4�rp�<�e��{d��T�r0�\�ax�p�������
�".��d̟ex�g�h��i�k8�sx�t��u�ri\����d����n�r������ܟg�?��{<v
�i��,�l��
�� �mH�n���(�ed�t��q��(�{P�u4�X�a�jp�$p�huw�����l�{��n��
����i����
L.��a �b4�dH�e��h�iP�k\�mx�n��s��t�uol��b�d�p����'��,LLl8�$�,,�b��cl�@�bl�c��d��i��r�������d�k��U��x�i,�������s��"��b��fȡoСt��(�((�.8�4dR:�a�ءh��������)���C����e$�n��(����f��J����0����
8��x�,D�l��xS,�d�uH�Ml�et�[$���t,�$��a�����u��$��a�e�r��cT��̢d��Ԣn��M��a�aL���h�m���x�i4��	L�a��e�i�o�p�s<�t��u����{`�ch�gp�m�������5@a:|�x�c�������8�HSb��h��iУlܣn�Xt�5,�:|�]L�{ȣt��3��:���n��:����r���@���Xn�����t��{$�l�0�aT�op�r��u�$lu�`���\�t$��d�a��e<�~��5H����mp�{���ȤeԤw����^��tT���n�����(���ܤn�r�s�.9�����l4�X�t@��s����i8�ph��d�&�r\�ad�e��l\
��".|�tT�$� t�eP#2r��a��iH5&�1����s�A:<?����sT\�a8�cL�eܦg�k��l�m$�n��oħpܧr�sL�tԨvܨz�\$�[�b(�d0�m�',X^M�a^D�e�a��c!d�dx�n��s��tH��f��-bf�l�e��i��s��U8g�`%.����h����c@i^��h�i�$ 0HlȦs�k,Цe�n,�xa`�z�q���s4��@u���b\w�w���aL�d�yfT�hd�i��n��s�yt`x�lz~{��z\�t�{��{p�rd{$x�ed|Kl+,�},��b��n�~��~
��s ����.�n���̧e�i�l\�:��$���s�h����$zp(�r0�t8����<�e���{(���D�a��e��h��rĨs̨u���h�	l�g(�t�n��r����|,l��m��s|�s�s��,t����،{t��|�Ex�,�Q�a�e0�i<�mD�s���$�r��'X����al�pf���`���E����|il+���a��e��lĩr *��`�b̩fܩl�m�n@�oP�p|�r0�s\�tp�u�+:�+E�,�,�0c�66{ԩe�l�7Qt9:89�r9{��a�:��:���a4�i�$�<v,�e�>!�?E8?��H�ed�fl�p�?Q�@'`A�A��t�a��c��d��f��g�i�p�txB��B� �,D'��a�l�rܪ�h�:x���Ԫ�D�(��8Dh�D$�D�e0�D4F�(�a F{�G���".D�sL�tHI:J��L��K,T�h�N!
N!h�n�P��l��r��s�R��T��V,8V����c�WExn>�aȬe��h��iD�o��u��Üc�n��	�.�d�g$�h0�i8�mH�n\�sp�u�p��ps	q���mq^�r��s{�s��@�d�T�	u{T�t�Z��u!h�c�v��v��|�d��h��t��u�v����������w,|w�	�w��|�V.��a�c��g(�h8�iP�mX�n��p`�rp�sT},�>.�s�q�8~�	~���k ���t��0�cH�vt� ��\��d��4%*	���h�.��c�������L.ĭa̭cԭdܭe�f��k�l�n�s(�t̉���$��\���ge����e�q50����>.����{�cT�e
4�u��
ȕm���<�bh�cp�dx�i��t��u��v<��d�����̘�h�XO����Ls������bȮcЮh�n��t�e
����(���$خe���gX�
��
���b,�c@7�P7�eܦ: �kT�,��a��b$�c8�e�h$�i\�k��l��o̱p �s(�t(�uT�z����	���|�t���m��nįsЯu��pT
@T���i\�����sЭ
ܮ|�دn�������S�<�
��$
d����n��il��hH�cij0�bX�f`�i��n��r�t��ص�p�g��nЙ���+
�x�s�h������g��t,�h`��������fаhذk�!r�z���ܹ�p����	�aغ2
��8
��,��a�o,�����|��m<�n�y=
����4�fX��(�H�nܿrP�at�u��z	����l�l��C
������p��r��!��i,�<��Q�!��r�s�����a(To�r�u���Z��H
��������N
��w�o�{��T
���L�a��e�i�u��Ð�m4�{D�b\�g��i���x�:d�i��l�t,���x����c��a��iIJlвm�r�X
\�^
�������l�����d
d��زn��{��l�p<�5��5<��P�������s���m8�nh�k
(�:���@�n��,H�e�,��a��e��hسo��r�s��l�!����d$�p
����a�,ijeгo�����!��i�����<� ������Y����a�
����n��T
d�a��b��f��h��i��mD�n�\o��r��s�����P�v {X�lt�rxPtZi,|�l�!��e W��t ,�$���b�d�e��f�gȅk�l�p�r$�s0�v4%�H%��%~�%u
�%{
&'&�0&�D#.���`'g	h�a�&�8�dp�f܅g��k��r��s��tT'�
���E�)'x�e��l�G�p*�
�*�8,
��lеs�.h�.w�.$ȵa�/hXn�rd�w�/ܵh\/{�c�tT���1� 6Q@6���b8�f@�nH8��:��=r�E,�Qat�e�L�
�L��`�t�H��h�spY!�U����sH,@�bL�ch�d��gķk�l,�mH�nh�r��sԸt �u����at�b��c�d��e��f��g<�hT�i�j(�k��l0�m��nl�o��pd�q��r4�s��t(�u�v8�w�x��y�zX��t Pl�#!�(�p(T�r�'\�e��l��r0��Q|�i�)��,>��a��e��l-�l-M�.��3�зt�4Q�5{�5��طa��e �t�8Q@8��i�r�8����.���<$�i�=�8�o���A{8A��@�aX�o�F��"��J��`�.��a��e�6r��vK{���8M���e�L���n�Q�`R{��s@T:�V��r�U,ȸe��h�r�s�t�V�W�W�
HXE�X�
([Z!�f4�gD�s�[�X^�]{<�tP`��`�����P��d��������l��a��e�lh�o��rȺs��u��Àl:��k�m^�q.
8q����dعi�l�n�r�!t|r!`s��s{��s�t��t��e��iXu���t�zx4�eT�i`�o�p�L{{�prD�u�{\0|�
|L�ep|�|1t�t�~r8V!D��|�sDp���������e��u���(���i<�M����c�e�p$�tt�7	\��e,�m�h����\���a��^|�!�ah�et�h|�rT��ȃ�
Ѓ�@�t܃��H�������`�m\��H��d���td2�D������
��a�e�h��iȼk�l�o��r�s�tp�����л�ԉ�e,%i4%l<%mD%nH�oX�r`�t��u��wػ����$i<�r��
$�,(�iČ��0�z����D#.h%b��t��Zax�e�S��Sp�i����h��V܏e����a�[�
�̒���e���sؼt���
d�
����d�(�~l�&����	@�aH�dT�eԽi�o�sp�u|�y��<��r�ar@��al�i|�n��r`�U̟����t�e��s8����e��p(�M��
��!��t���4]����lȥȽax�|�{D������������8�,8�eD�oL�p\�t`Cu�è��8�0�s��,̱r8��
(��T�r��� �h�s���
̶MԾa�b��c0�dH�eX�f|�g��i̿l�m��nl�p��r<�s\�t|�u��w��x��E�"t�ܾl�{�����e�h �k��oH�:ȼ~T���(�e@�s�E��������P�fl�lt�s���
p�����T���m��np��|M4����e������sH�ؿk�n������e���
 �����0��p���aD�cL�iT�o\�s8��h�r(�g4�e��Q����0����'�������:����d�il������	x�a��b��d�e�!k �t4�u�!z�à����,��t$��l��n�s��tȁ�
T���gH�Ed����<�s�������:��Q,�r���L��4�H�k$�E�l�t��P�at�h,����(���GT�1��r8���a�d�
�".�aP�e��f��i��k��l�o�rp�s��t��u@�� ���d�n�r$�t`��A<d���a�{`g\Q�,�u���4�������\2.l�bt�m`+n|�rt�c���{�,D#.��i�al,�,���d��n��sxI�	�
x	����s�	��l@
i��uH
"��u����
��
w�a0�eD�od��T}�h(�aĖ^��<�lT�m�^H5X��\��PQ��o��p��E�����nh$��a����m���Q���a�d�e��l��n��s��t��u��\����sL�
�0�nD�rd�sp�u����(�a�,��<�a$��
� �P�r$ rX�t!h%�P#��x�o&,��i�&�+5�+��ll+���a��e��t�,�
l,���r�.��-��oH/,�/h�/��n �s�0it0��g�0�
�1���c��l��u2��
(�a��eX�i��l,�m@�o\�r��st�t �u�1w�1y��ì2�
�2��x�ht3^�5$�5!��s�6Q�6�����8��`7��c��f�l�n�p$�rL�s8{���8��f$:{�;�
;���t$<�
P<�0�a8<�@>�
$>8�r4>��@�t<?d�n�@!�tl�tB��p�m0B{x�a��e�i�o �s����B�B�����C���i��n��r��������nt��@C����t\C�
D:�C��nTDE{�D$�etE'8�u�F�HL�l�I^�K��K{T�a��ex1i��o���<LHL��x��`LclL��c��i��r$�s�L:DM]��eM����l8MN���b��fN�p��P��h�t��j�Q�`Q�eXX,�n�Y���t�ZD�b�Z��[nT\��L�a��b�c�dx�e��f��gh�k��lp�m��n��o��p��r��s��t �u�_e��a��l��uDo:8`����r�`�d2~��exa����t�24b:�a��o�a{��h�b,(�a<�e\�nl�r�b,4�n�b^�bP�r�i����H�rlc���ccd�a�c��nf��g�f�f��e�jj{��r�k�k,��a��e�l$�n4�r@�tH�u��\l���oHl��n�r�whl{8�J�l�l#+wm>,�uH/��mPo{�nP�rx�u�n,X�a��lpoP\p�LpE��a�qM	D#.��a��b��d��e�f0�i<�nL�zl�:\q��rDyu@r)Pr�r��r����n�r��i�ns�4s
��/�s���s�s$�n��rP�4u,D�u ��|uX�g��l@u��`�a��o��pL�]������l�v:�v���r�v���l�d�\w,�s�w����a(�dT�e|�f��g��h��i��k�o4�s��v��0x�B5@x�����x;`x, �r8�u�xw0y�	8y�@�g�x�H�nd�u�yc�yA��.��s�y�l�o�cHzOz���OXz����blz&��a{�z��t,{���a�o{V{����n {��r���
��m�{Dt�^X| �td|�(�a\�th�z��7	�|fH�l�|P�a�Gz�mx'�p�r�}rx�e�}E ���f|�!�:��e��r���������".��a�c0�eD�pX�tx�u���
,�t�����u<��w��hԅ���7w<��$�r��Ć�<�e\�����P�hh�oP���:��p�m(�{��a��e��h��r�t�u�������b��n��r�����:ԉ{������(�Ћ:t�a��o��i��،�mX���\�ad�dl�e��l��n��o��r��s��t\�ud�w��Șa����\2.��n��s��yܚ��������;(������
��{��tP���a$�e@�oP�r�Ge���t����nP��\����f0�g8�z��r����i���T�aH�i��!����b(�c(6f0�h8�k@�m`�np�ox�p��r��s���l�a��b��dH�e�fp�g��i��k��l��m��n��o �sH�t��u��y�z��è�*	������nd���e�i �t�!U��� �!�! ��l��P�iX�p`����e����6s�6z$��4��l�c��iرU��6i��p��t`����'�Q �������������y�`���s�,��i�{ܶ$��a4�e@�s�����$�t����s�7wط$�r̸E��'.��a��c��h��i��k�8l�m �nP�pX�r��s��t�{u��v�xd�r��1l�������e��n���g����.x���r�Q��e��g$����� �k
x��
`%.�p��K�"������.<�sH�tX�X
d��4�et�h�mX�����d|�f��g��k��l��n��r����a ��L�h�����9��aT��D��T������H�M���Կ�X�0�s�����a`��p�!��,`@.(�e0�l8�m@�t� ���d�_
�g��4;.|�s8"��H�r"��X�e��,d�i�h�l����d��e��n��td����^0�$������e���������a��;�,��lH�������nh�$��a�ed�ix�s��(�.8�bD�i\2������0�e0����8nT�st��H��0�\�n(�E�$p�p����,��a�����a�m0����f��l��n�;p��r��:X�$��
�����i�D{0�1���f|�s����m`�$�u�!��,�kh�n|�r4�$4�e��o��r��s��\�^`�g���x���t�n����
�	;����k��p(
;�����m�=r��s��������{��e��T�:,���a��'�e$�w��!�f1\���a��d�a��d��eP�i��m��o��p�s8�tH�u\��|��x�d��k��n�����!��������f��s��z@�Kp����E��,��a��^��t����n �r,�s<�t���$��h�sl��x�M�p�������w������4�i����H�ml�n��t\wm����d�a|�tL}����\?ip�'��a�?e�������u��:����p���	��l��,��f��lH��e��da��e��{��,�a$�p0�t�"����r��,p��h�k
�@�l��Q����T��l�,
�V.��c��d�f�h�k �l,�mH�nh�r|�s��t��u����h�a��c�d��eP�f��g��i��k��o`�r��sp�t��u�w�y@�z������m ��T������9������ip�,8�e��������@�dX�g�s�LUd�`�e����t�c�i��t`�s�Z�h���e��QD���r��ul������L������QH, �eh�^�����c(�l�,��aD�ed�o|�r��s��u�:���0�s8�s�P�t���X�rp��T�mD>p�o$��,��i��p�)z�;��{ ���m@
�'.�b�c �f4�i@�lT�m\�n��r<�s��������n���e�c���4��f�,��,�n�cL�e0N
x.
��h�t$�	t�r\�"�
���|�.��d��f��g��h��k��l�m�n�r�s$�t,�u�w4�z$������ �!K 	h$�&X	$��o|	����	h
h$
��K0
�H�s�
-��`�ah�u���
3���p�d��g�$x�a��e��i��l��p�es���
j�
���at
��r8h{��o�U�(/
\2.$�c,�d4�eP�mX�n`�oh�sp�tx�v�^����@�r ��\2.dEs�(����P��:H�^|������,����c��lp��b��f�h�Fl�m�n�p �r0�s<�tD�w���@��e�89{9{��a4H�Tc^���(�tD���8
�QX�d(��T�$���h�c��u��p�a��c��e�k�p4�tD�t�F���e���h�����b��l��m��nж{��L\�`Ulr�a�� �o�?L{$�l`(�ah�o\��x��
��H�t� ��P��l S4!$
��a��d��e��f�g�h0�l<�nX�rx�s��w��z��À!Vg��5�"1L"��b��r(��"���b ��
��i��a\��Q�ap�:L#�i����#(�a��ZĖ��$��D�ll$aL�o�
`�$�d�r�$Ml�p�YI$"�����P���e�Lh�H����t�|%��t�&�,'��sx=,\'���t�P��H������'$��Ì'�(��|DfL�gT�k\�t�(���r�',4�e�^sD_p�_]�) *{
��b��f�h��l��n�p(�rX�sl�ux�vl+n0��eL0{6M�:���c.��d��f4Eh��lGr��s�;�`<a�<a�=�8?���;e�f �t�?�@vA`�.@�bH�sP�w`B��E{(���G�\2.�NjN!d�lXO��P��W��a��f��i��l�o�p<�tP�u�Wtg�^(@d�4d���oDb����se�da��e$~
�e��c4h�h,�a(�lXfr�h��kH�l�k,0�al:m\�s�m��mnDK.�d8�fP�gX�il�kt�l��n��p��r��s$�tP�u��w��z��xn��l�a��b��c$�dl�e��f�g��h��ix�k��l�mH�nT�o��p�r,�s��tL�u��w���L�0p,�.(�mL�b�p�p{0�aH�r�+��p!qd�c,q�Xq:�q��l\r]�s��s����d��e��f��h��mtQ��4E~`t��t���f�t��t���i�tQu�i�S�[\u����WElu��h8�i@�m@W-xW��u
�u!H�bh�ep�fx�gv�vEv��_�`��v��h��m�v�����������wQwQ�x!�w,��e��r��sly(h#�xyz��p�y,�h4%bz�l�z�z,�a8�b@�e���L{P�c`�rԜ�����{��X�w�|�V.��b��c��d�Lf�h�i8�lL�mT�n��rH�sd�tx�u��v�}��}���s~����h��k(~�8~�d~�l~��tP~��i ct��|�.$�gMn,�s������|�.̀!D�e�� ��\��p�.x�a��s��t��zL*	���������e�����$��f��n<������e���(����d4�����nd��
h�.�f00h�l �n(�o�!r0�s8�t@�w�����	D��t�Q��9��h�����T�s,�K����\�i��
��!p�lԄ�܄����d�,��i��(�,��e��r���(��S���rx�������|�H���n(���b8�hL�l��$��e`�ol�s�������$��<���,��H�h�{D�s�'fL���X�l���x�tЈ�)��a��È2L�:��b�ZS�����������a��b��c�e�k(�n\�ol�td�̉��t����\���n������0��� �.D�bL�iT�ktNt���W���}�x�<T�{d�a��,D#.��l��r��t��ܡ����� �
��ì�w��>��eL�����n\���b0�$��el�������sĒ��n(�r�$�e����� �s�4�4�s��M<�o�
\2.��a��b��f��h��n��p��r��s��w�)ȕ��p�^��yܗ.�c��bP���O�����d��kЙ!�0�
,��8��������Ԛ,��,�x�8�$$�aT�e\�id�kt�m��n��p�#z@���Н���UL�El�o8�$��(��{4���k�$��a��e�h�i@�s`l(hl��i`�����e��f��sؠ��r��i��	(�$��oH�~,�nt�/p���$�s<� �M8�e���l�fx�m��n��z��Ax��d�sȤ�<������d��g��s��ip*/�����4����b�I:�I����k�H����c�i(�,��eTJBJ!��sĩg��b��c��k��n��s��tT����a��b��c8�e��f��h��i$�k`�l��m��o��p$�r@�s�tp�u��w��y��z����!$�����h���H����z\�^�W.t����o<�Jl���a�e�hįm����pԯU��aX����ص��$�nij,�iL�lt�vж{`�e0�5�X�r�=��,l�a0�J��e��a��u�4:��!��rXX{�,|���d��e��l��n�rt�0����rl�:�y�������t�{��'(��tܿr�a@�eH�lP�uX�y��5�~���U5��Et�o�eT�l�gL��4F������t���r��t�K������������C	��e��i�o�r]���!��k����f�����r ����w�a8�JЭ� �,�u���4�a`�ch�e��o��p��t�4�x�m��r��O�v������e��g�����f��:�c0����a��u��t��Tr�Tu��V����b4�{��b��k<�rP�sX�t����ah�e��i��m�o$�rH�uX��|�lH�bX]��d��}$���`�c��l��n��r��s���D������gd���0h�������s<�����l�p�������e����d��k��o��ph����i��{�r8��p�i<�o������4�p<��H��,���P�����|�s��d�mP��d�����������el�t�����e���r��aT�e�h�i<�ot�r��s0�t��w�z@�È������b�m$�p,�t���|�z	T�$����h ���4�����Y���t�i��n��o��r���<���l�n`�^��d��El���aw���������)��Ø�����h�t��Z��Q�r�!��a��t��4�!�lt����������(�m0�t��!P���D�a��P�bX�fh�n\�!0��=T
8��`�s���a��e�i v*	�u!��m�����u8}���c��st�e�U�g����w��h�{�c�p�u(
�jh�P�bX�u,$ �a`�ex�h��r��s��u\���
Yip�n`���
,�

��o�
5,{�#z�����m�Qw��rP��a��e�jw���C	�����Ts4f�t�����n�s�	 rT�� �a��e��g(�i@�k`�m��n��o�p�r4�sp�t$_x��z����fh�h�i ��t�e0,��r��s�����c4�����c�{|,��a�e�l�s�����t�n��r^t5T,�a(Z�q�t { �l8�n� 0!T��d!:p!��L���$\2.x�b��l��s4���%�0&8
��c��p��t(�M|&T��j�&3�e�g�\i�k\(�i�(��(��)��)'�a4+r�+�-c8,���e$�o�.-1E\/{,�pD�t�1�X�a`�oh�r�1���1�d2'��h��s�3!���4��n�4��h\4{��c6��5,��w@6�=,�e0�o�?��?���aL?��l�n�rp��L@���t�@�(�i4A��C�ET�a|�e��i�sh��TE1lH^t�s�H��\���H�
�H:�".�M�N,��eN����dDM����n��r�s�tpN	xN!�N��O�Q��	�".�a(�e@�i`�lh�pp�t��u��y�Uj�P�t R�
�Q �m8�r@RlRP�nX�sS�S��SE�S]�S$D#.��i��u�:�T��nU,$U�PU��U���n�s0XpY��Z�a�e�i$�wdZ�t��]���n�\����n�a�s�b!�fw�l��
,�bp�cx�g��h��l��o��p��r��s�t�u�v�z�����i"
2���u��
 *�WxnT���T��=�Z���p�c�mTln��r������p�t�H�b��c`�dp�h��i��k��ldn�p�rHs`t|u�v�x�� �adbqcld�e�fg$h,i�j@
kH
l�m�no�
p�q�
rPsht�u�v�w,�y�z��t ��e�s!(�n$#��#��hԋ��$�b8�rH$�eD�i��o�$��$��0�f,%�(lp(L�r�'T�e��r�/M�_&�1��x�bD4��3����e�5{��a�k��l(sDt�5���@:,�l���;���k�:�r�:$�eX�7	�a4m�hP<$c8���<�<$<eTsT=�4Ba	8A��
\b�f�g�kol�p��r�s��w�z�C��D�,D$�r�E�G�0G:�H��I��J{bci$r0t8zTL��K��r�L��MQ�\P's(Q�R�PR`R��@aXh`S��Ujlo�W�
([QZ!tf4�g�s<�5]{�bH_]�_,�_�aP`�ch$r\uPü`����ĉ��
���@a�O,hc{�u�b��r�g��f��mXi�di0r@i8eLi��D��j�lr��
�iL�wȥxe̶�a�cH�d$e<fhh|i�k�l�m�n�r�s$ttu�w�x�zlYԸ,�u��,�t��!hk�!ȼ4��{��Mi4m����l�l��5���Hi��{Pe<���\lx��T���te�n�s��������g�h�i�w�����<�'�����{���a(��tl�,�����ad,ePolsxt��$8r���H�$k@rHsX�������'�5�Xh �$`oH�0��".�m�p��������:����������x�g�o�s�t�ì�{p�����ap�9�����".,aldxe�f�lx�n�p�s�t@�wl��<n\u��{LgTz4t��HiP�1@��
$�,del���r���zЃ� ����,�a��$D#.\����
�a,�$D#.���,�ae4����t��,\��h�i�Wpg��a8eHtT�� ��0�$@at������Te��,\r(���he�r@M����u8�m�8�p�md�,�a�eTf\l�r�s� ��r�u|�ef$i8mLn�+x$z<g|0n��!DiP���M�?H
ta�o�u��l
M�
��
��|���PM����L�X����,��
w�o��Pz�h�t��,�o��r���e�tbs2rT\\2.laxd�e�k	lx	n�	o�	r�	s
t8
z,_j�[dt�b��c��D2n�r����g���f8p�(p���n�n,�i�l�o�rLp�pT�b ��p��q�\q�n0	u�q���a��d<	eH	gP	ir5�q(	f�r|Os��&�s`	nh	p�s5�s$\w��w��p	a�	i�	s�yt�zd|��2p�}��	l�~�!�	a������a�	c
p@ztĄ�<��	o��	hĆ�D�T(
r(���
o0
u��:،|���i�w�".l
a�
eiPo�u�
���{d|
p4�m�l�
c�
d�
n�
u ����
��������8��d�����	�����
b�
ei�pr�����	�����
n4�5l�\2.,m<n���0������4e���0�Hnhpptxw������O���$������c�gP��e5|��r��5��E�a��!|��d��E�i�8
�s *4b@fPldn�p�rx
x�,wl+,l0�h7�6{Hi�:$�:��\axe�u�;cx>{8?�A��Ĕa�g�i�m
n0
sP
t8Ds�D�D�n0E��a0�TE�g� ���.�M�r �
elEM
i��$�E)(
t�F5�F<
i4F$D
eh
h�~rp
uPQ,�G�Oc�PXf�
k�
l�
r�RQ�RQ�Uz�T���
s�WQxnw�
ahe�io@uXào5 hn���
c,d4mH�n<p�op�r�ot0p��r�t�,w�v��Dn�v��L�H��|�'.�c�d��g�i�p�u~�P~mh�rt���k�n�!@�m��\�����c�d�e��gs$�\����mc�~���h�c,n�s8tp�&\a̘������T�$�ache�i�k�op�s�t��u��y\Ø:9����l��l�m��n�r�s�uȫ,����m\����Э�f�s�tD��^�l�e4hXoԯ����X��� n�(aLe��2԰��DfH��H�ij`b�h�m�n�vr�t��9���r���������t|��d�1�������eܿr�i�rl���!�n$s����a4e@hHl�oPr�ZQ<���!,n���8�����hepi�oxu|�j���Q����!�a�eh�ir�è��4�{�s,���x��P��?$����h���c��i�lr�����Fd��n\��8�M0et<�(s,���q
<eH�Dt����DU��$
�ae��hho|r�stu�w�z�è��.�b�f�l�n�r�t���e���������T���c���h i@nTt���<���g0s����	`��8t����RLi���`pd��ato�u���|����0����a�c�et,�m|5��h�W��e	��hlж�
P0aPi`rD�(���
{(n� ��<�X{�.��Xi�:�lm�r�����l�Q>P�a��T�g�h�m�n@rts�t��|����$�&�p�f܅gik8m��tX)��)'��l(o0r�{�
�~8,{Le�-��. �{t1�XaxTpit\/{`st���r\2��e�����d2�@6�b�rL<!�Z,0`ae<u0�|]] d�]�n`]{�e]���t�\��it]bd{\��(��d�Pg�eM�e,HaH��\2.b�cLdlf�g�i�k�l�m�n`r�s�t�u��\abqcLd�e@�f,!g�!h"i�jH#kP#l�%m&nd'o�(p�q�(rl+sH/t�/u�v�w�1z���!ht f,l4r<z4"��"�#��5�'Dl\r�)��+n�*��dll-�,xe�1��3�ma�5��<na����=���o�t8A��	\b�dg��h0k<lPo��w�y\B��,,D$�ae�rXDs`Db���	�E,(u`F�Ha����FQ�K��J��Xb�c�d�m�o�t�u�z�L��L{�Oh$P�\Q{(Q$�i�Q�R�PR{`R���aeio(p��sTtص4Sb�i�S{�S��SrT�8e@r��\��	�Trdn�THale(���T�xl����U,��a@�m�r�W��ZeZ!�ctf�k�s�t0!:]�^�P`����p��rsu�`�����1�8h��j�lr,e8i@yXu
8q��$rlx���!
t��{	xa�e�i�o�r�s�t�u��<��u�c���@��r$�ȥ�nx�|�<D��������8�E`�& ��̶ԾaHb�c�d�e�f�g�i8l�m�n�o�pr$ s� t!u�wx��1t�@ape|r,���q�\m����da���������Q�c�s��<�E��������lT�����m�nrs�����".��stvL}'��������{(e0h0��8�j��{�a�b�d�e$l0oHsht|u�z�è6K`���lzl�tn�>�a��,�r�v��$����s4��r��$�eH��h�rs����sX����s0����s��!��$a���Оrp�!Xe �$<t�)z��Pd�H�$`a��U���tm����������DP�0��i�uH�����a(d<ex�g�tn�s�th�j�mru,��h��4�e��g|���, r����4bTcdixr�P��4��\d������pnp�p�m����a�z(!j���p�$�fp�h�w���(�l�{�r�ul���)l��,b����	�aHe�i�m�n s4�u w��o�$�����4gl�<idn�rȁ�T��\gxs�t�����s��(����f�n�t����n���tN�i<�H�M�o��������x�����o,�$�tTE�
��, a4�r\ ah cp e� i� p� s� t��ЭH f�P u��8�Уl� r����������C	� i��@��� t��� ehh�P� r����� p��� a!r!u$��	t����!�(���!l�wr�$�
aH!eh!l|!n�!r�\!n<{��T!eP#�t!oh%M&��(2,L.�!e�!i�!l�!r��u�1w`7!�!tH>�@^
<?���!e0B,�K8
4^U4]:�!o�[�!lT\���!a8"e�"g�"m�"n#s$#t<#ue��c��0"gT"i\"ll"n�"sHe,`e{�f
f�d"e$i��h��x"t�m{�k,�"u�u�".�s�"t@u���"e`v�w��L�d�"e�"g��s�x�zz$�"aж�	<���"l����#e�(���#a0
u\/MX�E4#s�����	L.�#a8$e�$ih%o�%s�%u�%y$�d������|#b�#c�#d�#g�#n�#u �,���#ep(��
�4
����#zL��X�!�#b�#ft���������#t,$u ���$��|�Դ�����$$f�~
�'.h$a�
bp$cx$g��h�$i�$n�$r�$sd�{���8�m������$n�$t�����$�$r���X��X���{e���l��$a%b%d%e%f$%k,%n4%o<%sP%tX%z���	d�����
j@�'��,��?,��H%cl�&�����'�)�0�`%a�%b�%c�%g�%k�%m�%p�%t����-�	P-{�%h��D�(|�$������`�����%t`��,�{��r��
�".<&ap&e�&g�&i�&o'p's't 'uH'yP'z`&�l���.��cP&t�eD��l���X&�@'�@�&h�&t�&u\�t�c�
�&rxE����&e�&f�&n�&s@$(q��p^�&r'tTDi���4!'|%:4'm�"�H&��,'.,':�'{�''�)0�'l *��X'a@f�'g�'hpIi�'l�'n�'p(r0(sD(t�(y�9� +���'i�1'�2�@66{�'a��<v�'s�:���'i�?�8?���'ed�f`A�A��(a��d((g8D��G�<(tJj�K,ht(t���M��T(g�M\(r�M$h(e,P�Wrxn>�(a`)e4*i�*o+uD)�|o&o��(ihlln���(b�(d�(h)k,)mxp0p,�(lq�Xq�H�Z��d\s)n s')e�r�� )m�v����u�v��8)��*�T+�\2��|X).�)b�)c�)d�)e�)i�)m�)n��r$*t,*v�}�~�T�P~�)e�~�t��0�c�)n�)t�2���	 ���\����)n*z]4���*i�'*e��m�����cP*ep*f��g�*n\��d*s�hQ
����\*e��	Ќ0���x*g<������*c�*h�*n�*s�*u���p��
P�5�*sж�
�I�*lt���*eh�ܙ������*hx�5��+f,+i4+m<+pD+t���Ȥ�إ�X��ܦ���L+cd+n�:T���+a,cl,e-i,�l-o$-px-r�-s�-t@/w��y�h�g�+h�+k�+l�+m�+n�r,u�/^�����+t|��	���$(a���Э,gl�4,a<,eD,h\,od,r���ԯv�LeT,id�5H�vh�&ij��,h�,i�,l�,n�,r�,t�^��+ص���,lж���6.�,i�,nH�U��+������,kܹ���	�,aغ~�!|��,d-ll��������e<-i\-l�o���L-eT-n��1tc,8��
X��d���d-t8��l-a�-��������-���74��-n�-r�-t���-a,.e|.h�.i�.o�.r/u.�(��|��@W;��,�-i��$.m,���.�,��,/�p�B����lL.n`.rl.uX�]��D.t��d��X.r�����\��t.ex�^
���.r��^�.c�.l�.p�.rl���T��h��8�(�.a�.i/o`�M�.tluK���P������/s<�P$/r�,H�8/cd�d�$�,d/el/i �rx/s�/�����(�m�� ����Y�T�/a�jc0d0e40f��hH0ih0mt0n�0r�0sh1tD0 {�/m�/n�/sl���c.�/t�G����,0r0�$0d,0tl�p��c� ��"st {<0nX0s� �%(�$��`0m�&�p�f�0g�0s�0t�($D#.�0e�0l�(��0w�(I)�p*Q�*�8,����l�0t �/Q�0s�/�\/{�0a$1cD1s`1td���/1i�/1h��5h101rt1�81eX1pit0���1$d2�|1a�1e�1h�2��2�^r�3�@6���b�1r�1sL<��<��ZE ^H���1a�2b�2c�2d 3fP3h`3kt3lx4m�4n�4p�4rH5sp5t�5u���1a7bqc47d`7e�>f?g$h<?i�cj�zk0BltEm�Fn�Ho��pd�q�KrPs�RtXXu[v8[w�[y�[z�6�� 1t �2a�2e�2s!!$#H$��o�#���2hp(�ql�'�2e�2i3l$�5,)v�2n�V�Q�2e�+~�+,3l�*��3fD3t(
���03pL,$83s�/�4n�3��X3l�H
�5��l3.�3a�3b4e4l@4pP4sd4tPB~�6���3c�5�3n�3u7c�{p7,�3a�3e�3rP75`7���3i�7
@8%�:{�:$4a44e�:\;�� 4f�:(4r$<��<,P<$H4t=��<$\4rh@9�=��p4t8A���".�4d�4n��r�4t\B'�4sC�|F��G��I��4l�4rXJ��JbKv�J���4aXb5d5m85t�L'�O$`@. 5e�O�.05s:�(Q$h
h�i`R{X5a`5iPR��S��X��U,h5t�b|5i([���5l�5mZ!�5f�5k�5sP6t���!_
0!���5t�\{]{�5a�5c6p$6tt]�0\�]��5a\�gL^6iX^6eP�\�^��06k�^�86r�^,D6ed6olr_m�a}�a��l6fP`��t6f�6s�6u�`���6��>�hK��Z� �t԰���6n��6el��6h8h���6c�k,�k{�6c7p�j���6s�k{�lE(7aDo��ؙa�l��7r��
H7a�
<���@7n��'��d̶��T7a�7b8cD8dd8e�8fD9gX9i$:l�:m;n�;o$<pP<r4>sH>t�>u�>x�>y���7it��7e�7l�7rعb�����
����7e8o 8t�����7h08kH�`���ȼ(8eH���<8gT8iԽ̿���M\8lt8s<��:��|8n���8a�8e�f9i9l 9r49u�8�4�:@����8�|�P���8i�8r�]|�{�8mzs�����8g���9n�����,9i�U��$�����<9ux��T���	P9e�9fg�9m�9n�9o�9s�9t:w��p�{�9p�9u���A�T�C	�����9e�9k��#���L�a�����9m�9tX������{�9s$��X��l�:u����:aH:e�si�:l�:m�:o�:�H��c`:k�np:r����]X���h:st������|:u��$�:a0�5��:i��,�:e����:f��5����؞���{0��:d�i�:m;p�����K�����".@;aP;dd;e�;g�;i�o�;s�;t�;z��h������,H;s�c��\;b�;n�;r�;t�\��|;d��������;g�
��Q��4������;tX��p�$�;rus�;w���@��l��<n<p����`��<hol�0<b�<l�<p�<s����
8<a�<b�<e=f0=gP=i�=k�=l�=m�=o�=t>z�=�t�$���
��^�����,�<sl�l�c�<i�<l�<r$������<g��s8�1@�K����<w�m��,=o$=��������=��Q(����fd=np=s�=t���8=nH�����\=uP���"��x=rtN��=e��lx�,�=eX��T��=u��$�=a��T��=b����������=���Q>h�jp�,hw���>a@>r4���$>t$�����wal>ex>h�>i�>s��
T�d>r����(���>g������>a�����d�
�>e?i���?e����>l��>l|��
��>r,��s�E0?e� ��(?tT\���".�?a�?c�?d@el@fx@k�@l�@nTAotAp�Ar�As Bt �u�[�?c�?n�\$,Dh^:�?g�a{D�e�?h�T�
�S�?rdb�?e�a�?t�b��?e@i�b�Xc��c! @n8@r8g�f�@s<h~H@n�g��,@iP@sHh��hQ�~�a,X@rj{`@f�n,�r\q
�q���@a�@fXs�4sr�@r�w!	�@d�@eAfAh Ai(An0Ao8AsHAt�x�`x,�@u��x��@lAn8y^�y�lz��zd{��{�d|��!$L},@Aa�}'dAnlAr�~�T �3�Ah�Ai�����$���	a�Ae�Ai�An�Ao�As������
�Ae��5,��ArЂ<�z�����aBeBt<�$������(���Bh\?i�{
tBaCbCdCeH	g�CiDDlLDmTDo�Ds8EtHEu�B� �5����lBc�Bn�Bs�Bt�Bu�!�6z����̳}X�!�Bt8�z	����Bd�Bs�Bu ����B��D�hE���l��l�&ܶi��{b0Ce@Cn\Cr�Cs�Cx���"�����8C.x�gTCn<�X���a|Cg�Cn�Cu�Cw�CzL�9D��T����C�H�M�CÈ��L���Cn,��8�KX���Ci���p��l�0|eDfDn Dp(Ds<Dtj$������Dd �:,�:4Dt�����:h�Q��r0�{pDcxDf�Dp�Dr�DsP-^������5�De����HQ����Di��Dc�Ds�Q�8V�������Dn`�$�DaEeEk,Elh�:D��Dr���ܿrEu��U��r$Eo4��������@EfXEk0!�H8l����`Ef��,�EaFexFo�Fp�Fs�Fu�E�@��|��Eb�Eg�En�Er �m������@a,����EcFhFlFu�����E��F���,�c,(�,��\2.4FeDFiPFnlFrx�$������<Fn���\Fsl��,�,���dFa��{�Fn��������
���$@a�Fp��T�{��{
�Fa,Gd4Ge�GiHk(Ho<HsPHt\Hu G�l�{GdGmGn���p����D�:l���G��
@\GitGl|Gn�Gp�Gr�Gx����TGglGn���1������Gf����Gi����Ge�Gl�Gz 	���w��$�Gc,�d�&eHmHnHp(��$�
T:p Hrl;��4Hk�$�4!,HHs|%pHcxHl�Hn&'�!���&��Hf�Hg�(Q
�&
�Hel+P}l *���HbIcdIdlIe�If�Ii�IltJm�Jn�Jo�Jp�Jr�JsKtHKvPKwXKx`Ky�-�P-{Ih(Ikp.�L. IaPIe\It��.{<Il|.�DIr����.�/&xIf��,�/�0���Ia�Ir1��4$|6@6�Iu6{�Ia�Id�IeJgJo(Js0Jy8Jz����6$�Iy�6�Ji�6�P7
P8� Jrt85�8		�8^�8{XJe���]��DJe�8��LJn�9�Jc�Jd9{dJe�9D��:{�Jo4=��>:8?:A��Ĕa�d�Jg8D9pH�\H��Ji�G���Je�JlKpKt�H�IDJ$�K,D#.h4Kl@KsMM,Ki�M"XOm�O(�O,P��P|Kc�Kr�Ks�Q1�Kk��T58V��Kt�V{xn{�Ka�fcXLdlLe|Mf�Mg�MiNoXNr|NsPOt�OuPyPzHLàon���KcLdLi(Ln0pjqt-�s�� Le�w3�v��4Lu�v��<L��O��z�~:�Lh�|`Lc�d�Lf�Li�Ll�Ln�pMr`MshMt,*v�}��~m�"t���L.�Lb�Lc�Lg��M�e�La��	�ì�\�^��d���Mg(MkDMlPMmXMz�'��9��0Mb�98Me��
Ѓ�����!tMa�W(�r��Q��{�Mc�Me�MnNs�^�Mk�bA\�~�Ms`�G�����Ml0���Me�Mh��NT�T(�Z��{�Mtȕ���Nb�h<�l@Nm,�nTPr��uT�~�{8NePNi:~Ԛ&$��x�`Nc�Nn,�u8�$hNa�Nc�NeOiOkOo(Ou0Ow����`��Nl���NhX�
@��Nn�Nr�Ntp�:���������NnНE���D�Of��{�$��4�8OblOn�$@Oahh|Oo�Or�Os`�:�����tOrH�f��a�Oi�Oo�: �M@Xa�Oe<�{��m���Oh�Om��Ȥ:�Os����Ob���\�{p�rT�M	PPapPc�Pe�Pi�Po�Ppt�s`Qt`Ru�4$���<Ph�DPc��n,�u�(al�dPhp��ij|Pc�Pl�Pr�Pxж��`�e��h�����Pl�^̾h�����Pg|��Pn�4�f�!��r����Pa@hQo<Qr�f:��QrL��\���(Q���w�o0Q�4��Ql�Qn�Qu��LQa�Qe|.hRiRo RrTRu�X������Qt(�{��k$�l���Qc�Qi�Ql�Qr��\�1�Qn��q���Ql(�xd���Qb����Rrh��8�s8RaDRi���`���0Rn�����<�LRn��,tRn�(h��lRg�$�Ra�Se�UhViVo�VrXWsXt(Xu�zxSè�,�RkSlSmSn<Ss\St����$�R.�Rs�����RtH5��^St�Xs����,Se4Sk܏�t�� �^LSsTSy@T-tU�T������dSr ���lS�\V��Y������Sc�Sf�Sh�Si<TlPTmtTn�Tr�Us�Uu�Ux������Se��{d�<����SfTlTm TsL��p��Tzp�Q��Q8��
(��(Tt���0Ti@����HTp����$\Tf�Ts`��dTtx�������,�To�TØ����Tf00h�TkUn,Ur@Us�Uz�������T�ܚ������TnP�,�Te�TlD�������Uu��MUe����,$Ue8���$8Uc\UphUt����(TUaX��xUa�Ue�����������Ue�Us�UtН2
��x�^`��Ua��l���,�UeVo��!��i�Uuh��������Vn��4VlHVr�8��{,Vy8h����@Vg�T�<:TVp�����hVk�Vn�Vt�Vu�apVa�Ve�ViWoHWu0W������Vdlu�H%� v��Ve�u!�Vm`���Vf��Wn�d��$�lWsP����@Ws|��$W�PW�Z	0����M�XolWp�Wt(
��Wrj��
wxWi����
{�Wb�
��Wa�Wi�Wu�W�X{�We��\�����Wrx�H��Wr ���W�,iXi�
E�.h���Xs�Xr�l,,4Xa�Xe�Xl�Xr�XuT��<Xb�jc�Xh�XkYl�Ym�Yn�YpZr,ZsdZt|r�,���Xi�Xn�s�PE��d�{�1,��Xa�Xi<?,���0!���Xo�Xt�!�H"��!YaDYe�Yi�Yo0Y�l":x"��(Y�����"<YidYntYr|Yt��s�"\Yg��t�":#10#� #�Yn�#{�$�$���Ya�Ys0&��&��Ya�Yg�Yi�&<@)��($�YsX)!
�+���c.Zs�j�8,:Zg.Q�/\/{$Za�]oHZs �Mt1�@ZaX1p�28Obd2,XZa�Zh�Zi�Zt�Zz�3��{�4z(5�45���Zn�Zr@5$�Zehv@6{�Zb[h�1s�6� 6�Zs�Zud��Tc(�9{�Ze9��[n�=,$[iB
0[l�B��E{\[ap[e�:��FH[lTE!P[l�I�H��h[c�[i�J!�[b�Vm�U���[g�[p�[t�Xm�[e�c:�Xw�[.Z"�Z8`o�[u�e&�d��[gH�".�\a�\b�\c�\f�\g]h]i]j]k4]lX^mh^n�^p�^q�^r�^s,_tt_u�_v���[a�_b�ac�bd�cejf�kg�mhdni�cj�nk�ql@um�wn�}o �p�q�r��s(�tX�ut�v$�wD�x|�z�_� Mt ,�\l4"�#:�*{�\l�+�
�,��/{�1`3,�3{`�.,]t�4]�5:
4;.p]a�]b�]d�]e^i^k^l4^o@^tH^uP^z�]Ä5&$7:07��x]�p7�8@8��i�]n�]r�]tt���8��]t�8��8���]f�]h�]m00�9H9�����9��]a@:r�:]$^a�:�;:�;�,^r�<��={�=E�={`�.�o8A:�^a�^d�^e�^nX�o�^t��z�A:A�^l\B�|Cc|F]�G��I,�^fJ�J,�J��`�.��a`R:_cXh_i_p��s$_t�R��S�T�T��U,\2.L_aT_e\_hd_rl_s�U�VbW	�WHX^Z�_nX\H_'P`:@7m�_r�_t�`������_�d����l���f
`@.�_s�g_
xi��`@.�_a�_spi!�i'�l,
8`aX`bt`e�`i�`l�`oarTasxaua��o!tf�l��,`u|��p
D`ihp$L`l|r�`g�`s8q��d`i�`l�`n�r� s��s�`s��`a�s�`a8t��x�lx�`k�z��z
�`a�`eL{^�|!D:Dp���`����#��>4aea�\��(�,an��b|�!@ae��$Hat�)���`ak��td���han�as����{�ac�ae�ahpbi�bkX'lЈ,��P�ԉ�aabebi bl<%m4boX�rPbsdbt�au��wػ���$i��bn��E8��,boTD�	��\�\h��<beH��Dbp�V
t�\br����xbe��	�ba�beXcilcn�co�cr�cs�t�cy��?<����bb�bm�bn�ut���{�
@��beci$cl8cs`�������c�����c�� ��0coHcp�-��
ȥPco�|�T��dciܩ$�c.x���xcl�U��c$�^8�,�ccL�p��{��
̶���ca$db|dcH�d�de�dfegHei`elfn�go�gr�hs@it�iujx0p:����d�t��7lDdrhdsdô�����<de��$�!PdotdrȺ�Xdt|��
���dh �k��o��Q������dk�dn0+u���da�df9l�dr����
D�:�w�do��2���dlen er<es���(
,ea�(�,z���4ecT�l�'�ss�eu����Pea�ed�ee�el�eo�ss�etX���H��c�er��68��eb��h�eeX����eg��9������ebd�H�$�ea���\2.Xfa�fd�fe�ff�fg�fh�fjgkgmgngo(gp0gr8gs��u�gv�gw�gz�f�h��hfbpfg,�c4��������xf� g������4b�fr����ff�fg��;��AP����fel
H���ff�M�cm����M�]��^L�h�`�����Xgc`gehgipgk�gt�z�����������xg�� ��g�4��grl'��]@��l�rl��p�����ga�ge<hiPhkXhn�hs�htxh�l��hr,hs4hu���hf$hz�iЃiH��x�3���Hhn(��x�EH��`%.hhi �R�������ph�T���h,�$�he�ht��!Qa�he�P����$�he$�X�����hs8��hn4����heis$it0iu���@��ipit����>ap��,��8ia�ie�ih�io�ir�is�it�ið������hih�itT�pir�^����!�io<��d@����i��Ut��ii��{L=e0�9���(����ieܤn�d�{<ja`je�af�jl�jrks8kt�{Pjm �0jr(+t�u�O��1�Xjcdixjn�jr�{�js���9����jg�jh��,H
�ja�j�l
��
��|��j����
�jaked���
M�ju�u�
h<ki�5Pz(ke��ph�{h$0kahke|ko�kr�ksP������Tkk\kr`�htkp�v|a�ki����p�#z�,	�kaHle|li�ll�ln�lomr$ms�mu�i\���kilnlr lsh����kg�9`��lt���n��(lr��0la�<lf\lnhlr���-a���!z�"�"��tlmP#{�l�$$���l�<&z&�la�le�lo�l�X&`&���l��&�	p&�lu�&�d'�(>�bame�)�`)mil+���aXme`mopmp�mtLm�|������Dm�l,�-E��
$-hmr,.��i�-|me��o�mr�m�,�.���m��m��.h��e,/u�/�n2,nane0nm8nn@nrHnsXnu�1wx4��1��nm��r`7Pae(nn;tE��F��K�P��Y�XX��PnmT\
�na�ne�ng�cm�&n�ns�nt�[)�c�k���".(���,�na�oe(piLplxpn�po�pr�psqtDqu�o�t�����nkom$onPordotpou\���p4t�FW����oo4oz���@oe�H�$�{����Hot�����,\ot̗c\��xor$����o��p���5�oi�ol�orpt���(��l������oe�ofphplX���oa@��H��oh�����ܝh�pa|���\2.8pn`�Dpd`x��E\palp�D����ܡ��dp�(���po��أ
���pp�ps���`Q��pl�><Fa�pe�p�t�������p�����rqo�)z|���$qrP�$qe0qr\���TkkT��<qe�V�����`5nTqs(�^���qb�qd8�k�qm�qn�qs�qu���\qa@rbHrcPrd�re4sf�si�slLtm�to�tt�tu�tvuz4r�d�g,��l������6s�����pX�rfrs$rtd�t���rb��^̳��� ���,r������ܶ$��a|re�ro8rx��
����hrtطprn�rr�{T��rrL�1��c��rc�rh�ri�rlsn srl��re��<������rn<�u�t�$ss����stX����8f��g��r��rHslXsrdss �Eh�P�wPsePA�����{lsnl�tse�sg�sn�sp�k��sb���sa��������sd ���6.�ss��h�$D#.tateti$ts��|�^��tr0�����:��,tkdtnpttxtu��,4ta�ti$������\tg��g��1X�:h��tn0���tr�����4�$�th����tm�tn�ts��:L�i���tg��^�&x[������tr��,ua4ue������� uk����(ur����
�".|ua�ue|vi�vm�vo�vp(ws<wtAu������w|�pun�ur�ut�����m��$�ur�us�uu,��
|Xm@�{�uc�5lY�ur�����ugvjd�k vl@vr�>s`vt?w�'d�0���ve0vf���������8vfPvz�������Xvi�����yf��s�lvn��?i�vnp�,�ve�vo`�sH����vt�������vrl�9�����vg�����,�vowswu�j~wek��JV��,4wa��,wt��^p�Hws�������wkl�Pwc�wd�wf�wm�wpxr0xs8xu����\wa`xd�xe�yfzglzh�zi,{kX{md{n�{o'pH|rd|sL}t|}u�}v�y�}z@x��&A��� �{p�X�m 'd�xa xs(xt�P����	h��l���B�<|����(�l|xn�,Pxa�xe�xo�xr�xu�:(�U�xx�D3�i�x��h����x���f ��xs@�ybyh(yi8yn\yr�ys�yu�yx����yeL<\��ye���h��0yg`3p,Dyetyi���Lyb|yh�yl�yt���\0	�8	���y� 	$�y�,��
$�yr|
�0
^�ykx�yn��\�wc���yo\�c.zs�+L��0zf8zg�$zalelLzsl���+0/m�@za�����b|zr�zu�&Xza�ze�I����TEd�ze�zg�zk�zr�zs{t��T��(���zr�{���ze g,��P:�zzP�����{n�{r�, {a$������8{e|�@{l�&L{a�$D#.�{e�{l�{o�{s�{tX	sd���{m@�{rx"�c��{rL�`�{a��{t�!�
4,�{ap��Fd|l$|r,|s4|t�8] {|sT��D��X�dT���^�P|m��X|a�|c�|e�|k�|t}u�Gz���|h���b�|n�|r\�X�]����|tX��`�|nlr�|a�GrLf`�|a}e\�ü��l� (}f0}m40�� v�"�L"8}g4!,@}e`}hh}rL#!l$�t}i��|%�}m�}nH&^P'��&��}zB��0[ll'r�}i�)E�',�}w *,$~c,~d@~eP~f\~hl~ix~k�~l�~m�~n(pTr�s�t�u�v�x�zP-,�.!8~a@���/
�00��H~l�2��4��4{d~d@5
6{\2.9{\2.�~s�:��:
\2.�~a�~d�~s y�ED;��~a�:��~l�~u`;?�;��=�pt���=�i�=$�>:8?,<fDsLt�?M�@��@�A{`�.tc�e�pH�s�txB�\�pC�|n0��4F]�G�tJ�K,\2.�s�M^N�XO,�O!DP,\2.�R��P���k�n�s T�8V��`@.�W���".$faT�e|�f��h��iȀlЀp�r�s�uT\d�nl�r�]�0^�_�^��t�a�`�Db���e��scc����l��nf�4d�d
h,@fl�Ju0i��j'm�n\�dd�kl�txn����a��b�fc��e��g܁h�i�k�l0�m��nЂo��p�r<�sh�t|�ut��0p�Xqlu��v���K�����g��w,LLl�|��e��k�~����ĉ���$�Mĺs���(�{��ԁe����g��'Pgl�����n0�$�i8�,tkH�u�$ �al�e��uX��X�1��5����P�����Ēd�i��:l�x�m��n�p�����t�?ē��r��M��aȂo4�{��n�sp�m�d���a�a0�r�l܁>Ԛ,�h$����w��$�h8�$0�cT�eT�h�gt�j �Mt�$\�s����t�s�h�c��m܃p�r,�uT�����a��b�c<�e8�hL�id�oĆp �s��t��u��Ь'��`�eܮQ|��n�������DU�l��Ra<�h4�r�X���(�r�0�ad�e��i��l$SmĄo؄r��w԰�Dfx�h��i<������$
��gd�����nz�����e�����$�����bH��T��Єe�u�O\[$
���a�i$�o,�upN
,����r�N�Ph�ij�`�ep�h��i��nԅr��s�t��8����h�a��i��h����f��tص����nL}����ąd̅s�'$�����аh�m`u�h@�K�����s�غ�t��m�a�$��!$�s��,,�aؼ!|�D�a\�d���4�f��n��p��th�\t�*|�dT�C	��n,�^��e���K���r�����a�e��iQo�r������b�����c�tHd: �8�c@�u����a\�ch�t��uX��Эi԰��H�e�P�ht�,��a��o��r�Tul�8
��^8�(�:����m���ԇa�e<�iP�o��rh�Ð��4�{̇b�m�Qn�t�5���������l�n$�s,�u����/��<����4�ll�e��{H�c��x�r,���\��P���T�`�M8����a��e<�&�������p����m���e��9t���̈s�Ԉr�>����.���bl�l�m��n��r�t��ủx���
�a(�e��h��i�l0�mD�ot�r�s��t،u,�z��D�,���d�a|�t�Xh���.`%����������e��t�h�ĉc�Ze
��~�����r�s�t ���ԉ����Y���:�����x<��D�gL�ll�n���iT�lh�n��p��x��p�+��`�aT��	`�	t�s�������|�ol�:�p
���r$������d̊i�k�ndn5$o5�Ԋn��܊a��e�o���nd{�:t�
���nD��$�el���<�c@f�^���T�f��n��s��u�a\�a$XeċiЋo�u�����,^�u`��4Lu|������U<=cd��,Xm�n�e
0U�nt�,{$�g�{��a,�e�XoH�ph�t��	&8�r�v\h
c(
�@�e��,�
{T�f�
�\�ax�op$�
��b,$��e��rČs���
a��iXr,{��ph��Ќh�m�n�r$�t��p*;(���s.9����g�&�L�e�Yw��h��L�g���@�rTEd�m�$�@6�=,��a��e�u�3��=����k��nl�hl>����g�?�L?���bԍi�l�n�?�H�9�?��܍tL@�e�tp@Ap�h8,�8E���r�E,8�u`P
P!0�rQ,X�ah�et�i�P, R,�Q`�mlR�Z,��aЎe�i�o<�w0�äI�dZ����p��u�[��]����c�\��Ďi�n��r�]!�e��W�^M�b��a���r�c8c���bd:\��(���fr���D�lH���c��hЏl�n�s�t��X�a$�e<�ih�o�u�v�Lvd1��iL1{��eȏs�/����r|N��5����a|C�8A��܏e�i<E\�T�`R����t�U'���X�n̶��	�a�cd�g��kĐnx*pؐs�t,�w0G�H���P�s��!�5$�l�r��t�e��o��r(���t��@���rP��h�j������a�4���Аt��8ia�h�r�s�t$�u��t����E0��r8���[!T\��4�aP�tX�v(��t���)G *��`�a��b��i��n��rԑs�ul+���r�,)�4��<�:����iA��Ĕȃd�Br�G���~c�NN!�lT! �bD�gX�kt�n��r��s��tP�,�l�KP�d,�n|,8�e(�	0!!@)��($`�s�&�h�g��iX)�.b8,����o\/�
�2&	d2,��eH\2.x�a��b�c�d<�fX�g`�it�k��l\�m��n��o��p��rL�s��t̗u����a|�b��cȘd��eL�fT�g\�h|�i�j�zk�l�|m(�n��o�(pd�q�r��sP�t��u�v�wp�z$�� ��r�v�p�hp$��lt ��b0�eȓhؓl��o�r0ms�t�!PT"�4"�Гa��h"Vp"�����"��"��#��',$�a,�r�'��)^�+
�*��4�lH�rP�tL,��,,�1����n�3V�3��l�a��eh4�D4���n�'d�5��.Дn�5����a�e(�k�l@�oP�t�6:�8U@8�ؔi�n�r�8��6.�sd�k��U�8���i4:f@:, �a8�r����;F=Z�<$H�r�=��$(a��p�����l�f,�t�r�?{��e�?,��f�A{A��l��s�u8A����a�d�^e�g$�kol4�n@�sl�tx�u��w��zBqh�c\B&�a�B�,Dh�E
�Fw|F,�a0G�D#.H~$H^L�nHT�n�G,`�e�H��H,D#.TI�J��I��fK��J����aXb5d�g�i�k�m�p xs$�tD�u`�w�M�M���eN{ps�N'0���f��
(Q4�a<�i4�\Q��QC	`R�d�e`5i��sl�t4S��T����U�t�n�U,|�a��h��r��t�W
�X��,([����o�r�sZ!��f�s��t b� ��[$�p�^Ed6oP`8�hH�mT�n\�rd�s�`������H���b�dm�d��@�i�d�f
8h��`fct�e$h��l
��o��u��y�}�|����n��d�����s��������E�a��C
t���Ԙp<���ܘm���̶���c@�fX�g|�h��i(�lКmܚnԛo�p�r��s�t�uD�x��P�f�(i�E��P�eh�l��{���".<���p�e��r|Nz��o\�{��sO��T�����cЙgؙn��s�t������d�h(��������e0�����l�{L�c�NgT�u�����ap�bx�e��l��sȚtd��(�X���H
����\����
H�$k��n@r��t �:������$��eܿ; �$��kH��0�x�p�����".�a(�gH�l�oT�s��t��h�,�u���x����g��$�a�� ���4��xE<�À��pgk|�t�z�g�h�i4�p�e����np�$��ep�h��rěs̛uuw�"�X��x�� %�l�������ܛl����
�".,�a8�cX�f��g��h̜i�k�lp�n|�o�s��zl��d��'p�� �@�hd�m��,H�a�=������n��l�b�$x�e����a����������)���(���d=n�s�t�����n�|�P�K؜tܚ5�����nx�,�e�o����0�aH�eX��T�(�u���T�e��<�b��Bx\,�\�uH�Md�eT�p�r��e��i(�5�����uP����
4�����.НeL�$8�ȝl�$��ܝa�h��s�����(����p8�s����/�l�/ �h4�{,�c�cd�
�r2&p�otJ��H��h�mT\��
�na�c��d�e�f�k(�lT�m`�n��oԟrܟs�z�b��b��eОr�c�
`e{�o�c��؞l�e�j�j{��l�r�j��n
\qx�q�� �a<�iD�o�s�t�|v�@u��L�i�w���".��g��h��i�n��o��s�ytzlz��z��{�d|:��e�|��},lAr̟s�������
p�t����oP�M|�'�i�x���
L.D�at�e�i��o�u �yܡÐ����V.X�rh�sl�5������`�t�{�V.��a��hȠi�m�r��u�MUh�����id�,��rl���������dؠt��x�`%.X���a(�k�s��l��c8|g4�n<�pD�rT�st�t��z��: ���:l�,���L�cd�t���������l�s��P-�0���c��i��p��s8�T��5��������t�|�s�t ���̡����V���f�tn���6������c,����wL�aX�e��o��u���,�ul�D�b@h�ip�r������x�lp��b��rT�|%,'�l������ *�a �b4�fX�hd�it�l��m��nأp,�r��s��tԤuܤwXKx +{�)E�l�,hl+�jL0�0��,�e�f`�x3{D�u�2��L�l�4r@66{l�a��e��k�6|7s9^��u�:M�:{��eȣsУu�;��=x>8?��D#.�e�f�s$�z�?�_5�?���n�?���e�@�4��A��~aD�cl�dx�ixB����{��L�g�BT�r�B,`�e�D�G{Kp��tJ,�K�K,��a��sȤt�MEh�Xk�M$��aNc�O��e�O{�P�f`lR�xn>,�a��e��h�i`�ot�u���n���dL�t`�ul�z�u�	lu�D�s v��u!X�m`e�v����t��u�v��t�����Dk"�w!��m�|�'.�)cХd��f��g�i`%�	P~ȥ.�)et���Lc�e�h��r�g��m{�����b�c<�eH�pP�s���\�{4�s �����	p�e
���X�nx����l�f���bT�{Ħa��c�eX�hd�i|�o��p��s��tH�uT�z�����m��n�r,�uܮ:|�ܦn������l�hij`b�m(�n4�r,������ �t����<rlH�s�w(�h����,P�a|�\�dt�n����r��f���������a��eQo�u���������a�eRiRo�r4�u4���n�t�*(����z��1���8��(������� ��<��@�m�����,�$��a�ex�h��i�oT�r��s��t��u��z���l������d��kĨl�m�n̨r�u��7������t�����بe����c������s ������Y�����e,�iPTm<�n\�r��x<���`��4�t��&��,H�o����P�f00hp�u��-�,гo����d��m��nةt��8�h�����g��st����5H�
ĩrP���̩e�@f��p�r����;eD>89�a�����g0�i�D�	X(�e�5���<�n��s�aD�a�Vel�o��udMx�sP�0�n�
,Q��n�T���jcتh�l,�mL�n��p��r(�s���r �H"!�!�a�e�i�p�" #�#�%��$��$�l<�s0&��&��&�D�a܅gd�s��tp*Ep�t�*^�Q�+��x�.X-,LLl8,����b��eԫi�o��st:�-���i\���r4.ȫe��M�.�����.$�p�t/,�/,@l�/�h\/{�c�kp�t@�uP2{@6�b��cd�r�<�L<��\�s�ZE��e�]��\��|�lH��\2.d�b �c,�d|�f��g�h�i �k@�ll�m�n$�o4�pl�r�s��tX�uسw�y����a�b��cܶd�e��f��gT�hl�i�j�kh�l��m��n0�o|�p�q��r`�s4�t��u�v�w$�x,�y��z ��� �t 
\�a��b��ef��gȓhĭḽoԭr�s��t�u�whp��s!����n�!�4"�"!�"\�a�i�"�܀M$#��#{�#��#�4$�".�#���e�'�L�iT�l\�md�rl�ut�v,)��IL�"�)N�)QH���*:��a��s��t�*,,,L,-$�,��aЮiܮn�o�}'�.��Ȯo�.�d'
�bl+�1�/����o�1D4�ys�3���eni0�k8�l�4
�5�P�lX�p�:�$<?���n�=��`�i��m��p��tȯu�����T�?,��f�?'h@h��s�����qA���n�AlAԯl�u8A��ܯa\b$�d�f|�g��h��l԰n�pܰs�t�z\B�8�aX�el�r�BE��[���@�wd�z�BH�rp�[�B
�D,,D$t�l��sE��a������4E���e�����`F�̰il�X
|F�0G���Gi$G����XHq�G,��r@Ix�H,�wTI^pJ~�I,�oL�p|J)�J,D�l�K�KX�n�J��`�a��e��gرi�s�tX�u$M��L���i��n��~8M���e�M�XNUdN��ıi�M�̱n�P(Q���i\Q
�R�PR�u`R��	�a@�dd�eH�hP�iX�o`�p|�s��tx��`S;�S�S{T����\Th�r@T�p�e�T,��oUj�U,��a��eвh�r�s�t�VȲl�V5WkX��W�زaHX��X��n�X$�a$�i0�r�X5�v�
�nPY
��M�$8�ehZ'@�sZ!L�bt�f��g��s̳t([��D#.��o��z�{���[
�]�]{��l��rijs^�^��^,d6o�_��aTE>�_�P`��	�c8�d@�fP�hd�n��r��s��t��u�`��ܴ���������ak
�a�\c��b��H�ne��d��\�d8�,�g$p�a�f��x�m8h��`fcxi,�j��j����bĴc̴eԴf�j�k�kf�l,�a4�e`�i��l�r�sT�u|�zXm��l���c�q��q �e8q��(�dL�t�w~�wmD�a�b�lxX�dp�npy����s0{��z��|�t�z
��a��eD`iȵo������|�����L{p|^�'�(�е.�>صe��iT�^܀�t��z$�a,�e4�kh#p<�t���� �Q|��
L�ePdo���d�!`�f���,�
8���h�i�Ep�e��,��hԶl��ԉ��e��i4%l�%r��wȶÈ�������&�d�~��$	�aطe4�iT�ot�r̸sܸt��uȷ���<����bH�c\�dd�g��kl�lt�m��n��r��s��t��uh�^T�k�&~�'c���t����md�����c��ih�m������$�����rD������l��@�ci��l�r(�s�Bx$������e�����.�p������ �� �eȥX�dD�m��|�!x���L�rd�s0�{|����)��a��e��u���H�������nȬ�����5���������8�,ĸa�)t`�&�h��j��� ��m̶\2.d�a��b��cعd��e�f8�gl�h��i �k<�lx�m��n�o�pX�rX�s�t(�u`�vp�x��,��d����{p�s����x�nt���e�7l��:��h,%:���i̹tx�`�Ĺe�m�d�eH��T���z�i���c���a������� �sP�u��(�ah�l�!r`�ä1�:���X��<����r��s��t��\�{��e���t��T���	`%.غbt�c�d�g�nt�s��t���a���eкr�'��K����D#.�d$�eD�kL�t(���u8��T�%8�rLy\\y��0�b���L}���4�T�r ��\�e����h�s��tX��Ԉ,���r����e�_�������$̻a�e�s��{8���$Իc����ܻs���r�E�����a,�r(��tP��l�Q����4�aP�e\�sH�$k �,����d�s0�l�e��o��p��s��r��������
�".�a�d̟e$�fL�i,�k<�nH�od�st�tнz����h�,�d��D���t�,�e�u��wP���'�
�$4�i��$��O��P�m���X�e�zp�$us��w��z�����e���lH��H�������������t��){@�,ȽiX�Tl�{ܽs���a�fD�oP�r��
��$��*��^�i�H� �tDg��,�i�8�s0i.�����".��a��b̾e�fL�g��i��k��lԿr�s�t,�w8�z���l����g\uP�!��&���4gܾml���i�r��0��	����s����,��o�r$���4��������8����0�h\�nd�w�$8�el�i��:8�A��(���p=s���t�nx�r��a�o���I��$��e�����������P�,̿a,�$�>.�k��td�;��!��\KW(K���b�����r��, �ep��D�a����P|m4���L�a��e��h��iXk��t�w�y8���l��rL���������!��e��oh�����Z��`,���t���a�h�o�t��<�f��M0�$�u(�!<�dL�rX�t�$�l���D�op����(����h�e�i��z8l.
d�,��a�e �lP�rd�tp�u���p�� ���h�n��
d���g�P+c�e�i$$��|����s�
��
����H
w4�o��u��P4	�
��<�m�
wD�a|�h$\�r�|�r@����������,��a�d��e�lo<�r��\����t�����a���n��r(�t�j����a�Èw����u������ Xvi`)�(>4�eL�o�*�2r`�i<?���6.T\��a��b�cd�d��e�g@�k��l��m��n��o �p,�s��t|Nu��x��z�[m��c�xk��m��r��s�\�X^�^�^�_
�i�`��a,0�hP�k�:sb�m@�r�a �eH�i�b��bA�a�b�b\�a��e��o��s��$�b|�o�cQ�c$��c�c��c{`%.��b��n��s�v	������s$d��e�fUf���e�xs�h����c�t$i,�k�$�n4�r�lm�m>,�e�n,x2e�r`�s��t����prX�p$q5ql�rq$t�e�q���a\q@u:��a��o|u�vQ\w�l�w����a�d0�e�yfh�gT�hd�it�j��k��o��s�yt��v��z���x�`x,�u4��x�(�fH�hP�pX�sy:��y^Lzzz$`�s�c
 {,,{,|�a�es|$�{���ld|�D#.��a��c��p��tX|�|�{�|J�}��}��}��m�s�~M�\2.$f� ����a����`%.P�al�cx�l��o$zp��s��Բ�<�X�u�`�h���d�N
 �����l(�����a��e��h��s0
ul�:��!�r�#zt��D�,��i��{|�,��a�eĎ�Ў���ix�$�,P�a��e��l��n��o��r�s\�ð���@4p��D�ld�nl�r��^�����6.��ܚ��x�t������n�w��o��u����X�M(�w��e`B�,�����b����r,�M�>��a�o�u`��t�����<�t0�������(����)P���,���H��$�������$��aCb��c��d��eH	g0�iX�k`�lh�mp�n��o�s4�th�u��z��à�hd���b��e�t����	��b�f(�k4�l<�mH�n|
ph�rt�u������+�|�:�f�49 ��� �t@�^l�c$(a�!X�w�6z�H�O9l���`�mX�7g��s���dQ�����m �������������,��hܶ����
��:��h�	��c�e �f0�i<�mD�n|�r�s�x����z�n��p�����(�mx�,�����6.`�ap�dx�gst����,h�uX����6.�a��f��g��n��t�z����,��o\��8���nL�$��e��o`����{H�M��t������r8��X�{p�:��:�=b@�pl� �mH�n�v������sd:s�&h�k�����������e0�x�b��f��p��rԤu��w��E�����Q������f��c�6.��e���0�����f�s�VW8V����e��j`�$�h��k(�pH���r4��L�iX�s��!�D�m���������`�f�=r��s4����{|�t��E��,��a��c�eh�i��p��s��t���@��|���b��r��u�����ct�X�!��sp�,���e0�i<�pD�r4��	�����s����$�n��c���Pvz�������P�d"s�X�n���pl����t�������f���
��!��e��,��t��z�P��p���M�a�d�e�i�\u$��d�`�el���r�r@l����H� *��b��f�g�h8�iD�kX�l|�m��n@�o��p��r�s��t��v��x��l+x�.��e�j��l��rh,:�+!��r�,��,���i�,�L00����e��l��r�0v1
�1��P�u�1,��a3�2���e(�rp4�5�4{0�r@5�P�r�5�6{�'a0Jy4:::h�n9{p�i4=�	�:����o�?�8?����f��t�@a	A��Ĕa��c��d��e��g�o�qH~àB�pC�,an8D{
�E��E��G��`%.4�a<�eL�k`�pt�s��t�G�\H��Hv�H�D�e I?I�X�e�IHI�l�ehJbJ��e��r�J��K{�K,��a��h��i�L���a��i4�	�L��LؕoXO���etO��O�P<b(�d0�fD�gT�h`�lp���Q.
R�<�e��8R,hcmdR��L�r�R��VX
�V��h���W,��a��e��f��h��i��t�Ju�W�]�T\!��n�^!�a��`��o�c	Db����p�k
xnE �a4�eH�uT�Üu~lu��sn���t��|,�sX�M��@�t�v�����T�$��a�cD�e��h��i��l�oH�p��sp�t�uT�z�h�c��d��l��m��n��r,�u@�c�^4������b|F�
���n�LQ����e��i8�ml��h�������$����,��ij0�b|Pc�mh�r��x�<����`�e��gаh<rlH�s�w��{�U��\��,��a��o,�
�v�������p|���m�'T���g��r��o4G�	��{��e�����n��h4�r4F$ �.����(�t��^���@�i(To�rl�u4����b��f��`�a��e��i0ir�u��Ð����5��c��c��i��lвm��r$�������M��e��sd����n�����1����e�t��������m8�n,�s����$
d�a��e��h�i�o��r��s��t<�ud�è�8Ob��g��k��m��n��r��t�u��M����c��e���\�i�����d��gX������b��t���T�,��o������h$�iD�l\�nx�r��s��t�Uu<��� Tsd�����0�m���8�eT�i(�U`�^h�.����������p�a��g�������~�������$�������k�����R��h�,ijeгo��u\���v ������o����m\���b@f`p8�rD�wX�����0�i��
<:D�lt�r|�s��t ���L���Y��T����������l�a��a$Xe��i��o���`�4Lu|����������U��ed��h�c��s��c�Xo�t��
��Wa0�o�.ip{(�c�Ќh�md�r�,5�P�n���X�at�iX{l {|�nT����a��b�Zd��e�fP�g��h��i��m�nh�op�r��s`�t��x
��a��s�l
�Q4�{0,��s���e�f$�tp�X$D,a8�e@�rL,|V	�,|,H�a|�e��l��o��r��s��uHct�bT��
�
�+(���a��pX��/{�b�c.t {��dH%��$����e��f�l�p�Ys�w(���&�0�ap�fL�gX�i��t`�w�&W4e@)�8�c�($@�sX)W,+h4+�8,:��n��t���/I0\/{��e�kp��s4�t��u �t1���a�kc��e�oxTp�t����f�k�sh1��r`�\ܹ\����1,t��(�Èk��H���1$D�aL�r�1$�1(��e�2,d2,X�a��e��o��r���lS:�2��|��3~�2��g��r��\3����g0�3���f��p4:D4��5�@6���b�d�h�7��9,9���l�E�Q{�UT�a`�cp�m|�n��o��u�JhU��L�rDVX�	X��h�p0X���o$XjTX<Z��Z,��a��e�i(�o4�tP�u\�w0���gdZ����c��nX[^D_D�^����k�\����r<b���2d�a���n@c8c�� �fLdE N
�e)<�e�d�D�f�f�p�e�I:Lg��h�cH@�b��c��d��e��f �g��i��k��l��m��n��o��p��r0�s��tX�u��y�����|�a4�bp�c��d��e��f�g�h�i�j�kL�lp�m��n��o��p�q��r��sp�t�u��v(�wX�y4�z���t �\�ed�kl�r0mst�t!:,"~�"��#?&Z�%�|�rH$��t�#����h��i�&{�'�'��a��r��s�)�)�	�)r�*l-
�".<�b�IfD�gd�kL�pT�s`�th�v?w�,��ep�gx�i��n��oH��\�-��>.� �'d.h�.��X�e�.d'Q 3��2:��e�1����s�4h�3����t ��t�5��kДn�t�3u�5����a�]d$�e8�iL�ld�u�4�7�8�@8��r�9,�9�0�e�;�:$D�o\�t4��=p�t`�g�?��=��x�m�A�A��l�u8A��
��a\b,�c��d��e�f$�g0�hol�4n@�s��tp�z\BE�	]�C����s|C���r�t�C�C'�D ,D$�r4E?�w0G�8�ah�c\��G'$G��T��PG��H��I{TI{x�r�J'�I��p�J��Xb��g��o��r�s �u(�z�M�$P/��dDP�\P���P��m�r�P��h�P$�cT�5�Q�R�`R�L�px�s��t�8
TrD�a\�e��;���\Td�n@T�l�e�T��l�T��e��rUB�U�X �����Vc�U���b��n�U,��a��c�e,�r@�s�:|V��V:�V�l �r\�i�V���d�W�H�i�XHX{8�eP�p�X�Z!��f�_nt�s�]�]{l�g�_MpoP`���h��i��n��r�t(�u�`�����ĉ�������\cm�b����n�c�e��d����d,g_
�f����g�i�xi���r�k{�6c�j���s�lrT�e`�lh�r��yrz8q��L�e�z����{|�hԉ��,��a��e��s��u���<�$��D������@�ci8�8�,��e̶(�b0�cD�dx�e��g��h��i0�l��m��n��o�r��s��t��u�wx���t����,Խ�T�e�8�ih�yL�>`�nx��|�HX����Mp�f��n��n�������a<�����T�����fЙg��l��n�s���PxM(�,��a������dd��������o�tX�N@��l��mT�u���� �a\�dd�e��f��l��s��tX�1���H�ؿkx�r��tX�:���`@��,��.��QD�{ �$��eH����et�U|�����n4���rP��0���i�����".�a@�g`�ol�s��th�,hfb0�l0tr�u ������$8�eP�l�� �P��X�r���|�k��o�Qx{����np�$��a��n��w�!\�!����z<�^������il�,��ul�E��eX����������`%.,�aT�e��g��i��o��s��zl��8�p��g��kT��@�sl�H�n�r�d:���d�n���l���$x��(����d�t�����nh�T�{,��اM�����np�,��e4���a8�eD�hT�s��t��u$�����l�:|�5���������8�0�r��4��@��L�a��e��o��p��t��k����p�gh�x�r�����r��,h�����e��op�~�����ax>h(��d�&��i	,��l�Q2&T\
T�al�b�c��d��e�g�k@�l��m��n(�r\�s��t�[(�d�xk�`��_,d�i�b�bx�a�a(H���r�c����dT"i��l�xn��r��t`e��g{��e��ohq�g���r@i$��i�M�k�Po:�n�r�n,�a0�i8�u(p{Dq{�q�X�al�c��d��z\q��fx�eHr�`�h��5̸~Pr$��su@u����p�f�x��s`x,��e�w����d��e�g�	i�o �u�x�An��u�y�z$�"a�sLz��{o|}����`%.�	aD�iL�sT�w��	<��(�{�����2a��c��e4zs��t0�,<�x�ahXn��w���h��
<����<�e(��D�a��h��r��s�u�zt���E��a��u��m�،,,���i$�a@�r���'���,�.�>4�e�Q`�ch�lD�s���h���,	��a��d�e��i��m�p�sD�ut��|��Eb��k��l��n��u������$�9������g��s��zp��X�c��r������i4�lH�nt�r��s��5��� �n0���(�i�$����@�a`�t�����$X�w,�r���l�a��e��5T���c�,�����a����D�bAfAh"s|�t���n��s��\�����c��t��p�D�������i$�p,�t����E��4waL@r�$:�<�mT�n���	�
��l��\�l����h�������E��e��~0
^��i@��s *`�a �b�d,�gP�hX�ih�kt�l��m��n��o��p��r��s��tXKx�(y�.��e$�r@/w�.�s�/c�1,�>.D�a�1:�1��<�l�2��4T�5r@5,`�l6{D#.9^��e�9�:^��e��s�;���r<��=z��u� �>8?���'e��t`A��rA����aD�c �d@�el�gx�k�A��BZ�B,�a0�r8C��C�pC�8�r�~���L�s`DT�n8D$`�eE&�G���t�IMJ��a��e�J�hJ��r�P��c��k`l�Q1�R,�W,��fp�i��o��t�Ju�^{,�eH�ld�o�s�_���g8�p@�r�Dz�_�r����$`�T�iT`h`�p`\�rDbHf��ex�n�kj�k,��axnET�,��a(�c@�e��i��o��p��s��td�u��y�����d���n�p�s,�uP��Ь~\�����Dm�DU�l�8�h\,o�ijT�f\�rh�x�~����@�w��������p�i|�x�n���r�����e��o�r�u���\
��!��d�����t���4�{\�g����a�e�i��o$�r\�uX��l��^8�ZP�e<�������4��x��<�H�n<�8
���m�$
��a�ex�hX�i0�m��s��t�u��z��è�Vb��k8Vm��r���e��U�������t���l�rD�t�Uu���������f�g$�l,�s4�t��\�h�h��u
�2
��R<�a�����P�mt�n��st�K���l�s�<�|�s �������Y��
@XaL=e�k�p(�^�	r��a�
"(
��r���m��r�����tT0�aL�cX�hh�l��n�r$�sp�t ,@�T8�e${@�k�gH"�!`�ax�s$��&���d��f�g��i�k�t�zT'}`',��a�iHs�(��b�($�e�)e�*�P'�,8,����a�ux�b@�f\/MD�aL�iT�o�kp�t@�u�/$�0��0,��2\�ud2,d�a��s\4�
t@6�b�l@�n�tD�5�:��n�:$��ep:����l�=��t���
�rPW$�e�=,�o�C��C^�l�C���l�Er8�aL�eTEw��r�J��H��D�l�Ud�spYH\2.,�b��c��d �f4�gT�ht�i��k��lp�m��nX�pd�r�s�th�u8�v��l�a�bHc�d@e�f�g�hi�cj�kxl�m�npop�qTr�s4!t|%ul'v�'w�'y�'zl��t �h�gp�hhllx�o��r��s��t\��� �� ��T���!�!��":�"
$#s�#��#^��hH$���e�s�t����$��n�%��%&a�a�%��rh(�'�d�e�rp(��)��*,�r�+��,�H�e�l-@�m�/^d�al�n0'�0^�1���e��g82wT2��3��ma��o�4��5��
�".�a�d��e�l(�m4�oH�pT�s@^tH^u`�y�]Ä5r8�@8��n�t�8���tH9!�:$$^a�;,To�;�Ȟp4<m$<,@�hP<$D#.�=��"5�=��h�.��e��n��o��t|>���n��r�>��>�
��~���8A���c.�a�b�d�g��h$�i,�kol4�n<�o�p��r$os�t��wA94B�\B�,D�	<Ef�E|F��Fk{�j,D�i�IL�s�J8C. a�c5d��gi�k��l��m�p��t �u�M��Ox�O$��e�O�(Qs`R��".�c�p�y�
�R�w�R�hT�tU��llU�U�8�a��hКm@�s`�t�U�HX�P�aX�c@XM|XM�X�Z��f4�g��i��l��s([,��f���[?��r�[,�[�<��]{��b�g�o�s��t�w�]�]^�L^�X^�ed���B{4;.|�s�B���rp_5 �eH_,,�iP`��c��e��h��m��n��r��s�u�`��D��ĉ���,'�@a��a��d��dQ�g��f����s8h��c�sl�{�h��j��mDkx�l
e��u��y�r$|r��n8q��i,n4r@s�s�Xu��xe�v���,deph|kl�*���\nԉ<%m�~��,
�a`(ce�i�oDr�st8y(�<���(g��k�n�t��u���ol�n�z|F�����r���L.��,�l@�aciTlln�r�s����Q4n���<�����H�x�$�Ts����`t��bT�xb�����o�s�����e��$�t��{ ���e��ȥ�a`��ex����b$~cpr���P�|�|��D��� �0�����Vt��>8a`epo�u��Ȭ�<��D���hb<�l��s��������������i�n���1���r8�,�o�pṯ��rD��L�{�bu(���a��`�,�h(r��
$�����̶\2.�a�b�cde4f<g\h�i�k�lxm�n�o�p�r0
s�
txu�*w�wx�z�I,��,�p�qs�tԸjt��7l�n�|{
��:�a�k���ȼ�meT����� i���,n�������������D.pn<���Lexm�r��,��\�9T���d�f�g�n��@����,�t��������g�k��l�{(��e�t��J��! a(b0e�si\lhopul�����H�WDkLm�n��2���M��$Ta�����0��".�b�i�p�sP���������L.�abD�cd|e�g�h��i�Ej�k�o�s$tlutv|wh�E������,	b<dDfLgTh\kdplttw�@��H�/���z����P�����\;b�i�n4��\�#�e|*2��$�b�e�s�t��L.�n�
��e9l�������ek������@p�$aLbTl��n\rusdw��z�0��X�G��N���l'D����l�{�bܽs��;���f�o���sDg:�����". apb�e�f�g�h	i�!k 	lX	m|	n��o�	p�	s
t$
u�Qv�z�	�l��@bHlPm�un�p\uoct���r�\2U��!X.�n��,`e�i�x:lx�l���b�������o���
�r �n��]������l�e�$�r��,	��al��������Q�����$	i8	è�t����0	���|����D	��$p	iL	ü�5�h	tH�MD#.�	t����l���	s�����	�����P,�$�	.�	a
c$�������	e��	l���	h��$D#.,�rإ:L��
p4�^L.`
al
c|
k�
o�
p�
s�
t��U�X
n���U$�rt
a�����D|�&@���
i�>a�
i��l���
dkn�wpt u���
a(hPt\ulz�����,�_
�1��4a����M��{<c0�$Ds��{�di(�!�jc�>g�p�r�
�
d�,
�afi<l\ohrps�t�u� ��k�lDi��{�t�������&,0l<	�.	��$eH
Pi<�Hn�+s�
Phh$xo0bs�,�$�ad
dt
eg$h8ihl�m�n�o�rslt�uX
��2s\���b
c
d
k 
m0
n��L������g	���(
d�f4oz�cQ���D
l���L
�L
���l
f�
i�
n�
rz�Q����
n�^��a��
a�
s�r5���
mT�> $�
e�d,�[
�
n\���
���
�,!����!,u�bc"��0d���$��Ds$��L���P#��|i�oX��$�ch%��c�%�
�Q�%E&,�ep&(d'���rlu��(���t�(>�ao�*��*���cl+�4ec,e8pXt�,l,�$h$-�a�.0�.(Di�-Lrx/H/,dsh0:�/xm2&�a�e�u�Ä4��1���n�r�u�4��5�6��6����`7�rP<!XXT\Pa`b�c�d�e@fTg�klm(n�o�p�r�sPt�v�x�[m�`�_,XlPb��als�t�a{th@U��T���s�S�rdb�e�b�x�.�eОr�b{�cM�b�ln r$d�`eaPe�f���eT�<�g��o��`j,sj{4e�k!�k,Late�r�s8�Hlllm),�epm�
$m��p�n��a�e�i�l�r�n�Nl�r�o��o5�r��s8p�g(p���n�q^�vc@u��o p�vK�w�
x�.Xa`dhfpgxh�o�s�t�v\w�`xi�y:z�lzT$|��{��rd|KD#.�e�|X
L}K�}��},��b�k�	lx~$ �{����	a��^c e,p@sHu<���w�h<�'\2.Ćr8i��� ���,(�:litr|s�t0
u��{t���~�c�U��a��$�s,Mt��D���,
�a�e�illn�o�r�s$t|��g���dk l4mDnXut����^,g�9i\�{@�K����<s��̗!Ps8�Q�dh�r$���l���\���c�����c�rH�&X�,�a����f�g�����~`����h�s|����n���t�{�$n,s�
e<iHoPuX�����4e��{�{\n�t�L�M(�wda|eX��4Fh,����t���r�1��f�pl�>�o��Ħ�l��{�a�ezص{��iT���:��k��nP�$aDiXolrة��<t��5�Pp���T�adu�E��&�e����\Fs���n��$	�a@e�f�g�i�os4tHu,���el��bdl n�u�'����a��	����l������(gXt@0ndr�s�x$$ss���|yh$rk�w�z�Ü	��	����TE���,�a��0
8
�e�t8���
$�w$���ip��f�rT���ce$p��(L#,4!,,h&C	|%��@f\nhr�&�p�f�&� *\2.�b$~c�df l4nHpTr�sDtHKv��wx
x�z�,@�a�el+�l�s�z:L{��,��.1�r�/�
0eL0��6U,u6{e(�1�:���".?o8?��@aA���6.|a��c�d�h�m�s�t`A!�d�k�l�A'�AQ�A�8C��B,�r�M�0E��E�4F9�G��x�.�h�pt�HgI�hJxJe r(v�J������m�K0b�K,8a�e�h�i�o�rp���:�/��h�0L�L|i�lLL:�L��a�i4���LvM��L�nDM�,M�ptM�DP��P��(�dflR��R��W
|a4rHs<j�0iw,o�k��j,@yxnE�e�Øw��w!ds�v��lu�v��x�T�����{z�|�sT���a�c�e�f�hilkxo�p@r��s`t� u��y(!zh����dl,nDuPv�:h���l����$g<tĬ�Э�sh��|��n|u����X��k����tsl�@�a�h�t�e�r�w���T���otK�(K���d����r���eH��ij�bdDh\ntp�r�t��x��<�l��$e@�,r��8e$����p����Pslt,�9���`������|f��gаhذk�s�w�z���������(�9���a0����iH�W,����f��,�o|�-l m4n@t�v�����p������,i��^�ePr��
�:(�Xlܿr`a���p4�r���a�e�h�i�or([K�!�tP�X����i��!�r�����^L-e��cx�nL�\�������w,i8o�jj$e���8���-�4�{��k�-r�t��La�eD il o� r�u� y� ô�{�.�e`%��d�^\�1�f���i�m�n r���.
�mX�����t��#(�,�ed�� b, n�����*��M$ e< s��2��{�?;�?��L eT���T f��{` p| rh�50`���� c8��� a� i�n��U� eH�$� b,���� ��A����{��� m� nh�
lRg!r!sGGp*Ld�v��, !i�,�!aL"eL#h|#i�#l$m$ol$r�$s %u�z$"��s��l!s��t!b�!c�!l�!m�!n�!r,�tP��^��������g�!z(�Z	�,�!aX������!b�!k"m�K��
p����"m�^u ���"����Y� �$��8"u��@"a�"b�"e�"g��h�"i�"n�"r#s(#u0#vP�!�"et���$�S���<����"g`�^�"e�"t<��\2�
@���".���"e���#n#s#t�������V����Us��X��H�:��!8#r�@#e�iod#rl#u��\�������t#c�#k�#n�#tLp~���#lAg	����#fT�h�#i�#sT��t��P�m�[��#m��#eD��$�e$o����0$mD$n���{($e�=a8��<$s8��P$c�$i�$p�aX$e�i�$o�$�������$fH��d���$p|��PW��M�$a�$o�$p%t,�$u,�i�	(
�%aL
�
!%r�
ax�o�4%.<%mL%r�����{���D%ad%e5�\%.t%s@5T���c.�%a�%b&c&d&e&f0&g��h8&i@&kH&m�&n�&o�&p�&r�&s�&t'u'x$'z ��%rx8e,�%i$���0���s�
(&e {|�t 0!)�$���".p&b��f�g`0m�rx&s�&z4�i0&��&��&��&a�&gX�i��t�&�(
�&.�&l��)�4+�+:8,:\/8
�&c�ke<'l�/d2�'a��r�2{X5��5��5�@6��`%.H'bP'r\'sd't 64L<���fc�<��=�=rx'eL?��r�E$�U���c.�'h�'mpo�'r�'s�'wW�X��hFuY�pY\W��Z,	(a|(d�(e)iD)od)s�)u�)wP(�[cdZ��(d((g0(n8(u$[�X[��[c�[
��n`(r\��@(�X)�,\��ȥh(st\Ep(i�\�\���(c�(n�(r4^�@^��(e�]���(s�(tL^��w��z@`�L`,�(e�^���(w�k{�k,�(a )s�a��)g0)n$m�lb<<b��()i�c�8c��<)r`:d��P)l$d�hf�Lf��l)k�d�t)r�fc�f
�)a�)u�)�$g�0g���)��)��g�hc *����)oHE
�*b�*c�*d�*gp�h�*i+k +l4+m@+rH+s\+td+u���)al+bP-c�.d�/e0f�1g�2h�4i�j@5k6l9m�:n�>o8?pd�qAr�Gs�KtNuXOv�Ow�Ox,PyDPz�.��!�t �*i�#M�*h�$�H$�*e�*i,%��'$�*ep(�,�1D4v�3���*e+l�4�5v�5��+a?v�=��,+i�J{`R{T+e4So�U$Z��l�+a�+b�+e|,i�,j�,l�,o�,r�,s4-uH-y�+�(n^�l���+l�+n�+r�n�Do�0p�Dp���+�@-�hp8q!�'.,a,i$,nh,r�!w�q�|r��H7n�s�4,dD,sXt
�t�t�<,ev� v�P,nt,sXu��X,i8v�lx��,tHz�,ehzU�z9�z��,i�,o|�p|��|�(���>�,e�,i܀U����$�,h-k-p(-t�#z ��\����$|�! -ad����s�����)
��{t-a�ac�e�-hpbiL.kX'lD�P��-bԉ|-a�-e4%l<%m.o�%r.s$.t8.u��w.�D�E��-b�-c�$i�-rԋ8�=~Č���-k��hKu����.�H��t�)0.e�S��D.f|�E�p.a|.e�.i�.o�.sĐz���h.r���.r|�{d�����̒��)z�`��LK��'��'�d��?�@_���@�aH�d�.e\/i�/o�/r�t�/u���@��.b/i,/n@/sL/x`��$/n������8/e̟ ��Hcp(����ȥT/ap/rx/vܧ�Ԩ��:x����/n�/s0������e ��̶r�/c�/n�/s�/t��{���4����\2.�/s��^ ��<0cD0m�ud���	�/aL0ex0f�+i�0l1o1r<1s�1t��P�=��8i`0nh0r�{���,�,p0a�0i�0l�0r�0s�0ux	�,�0n�0z8
�\��E���{H
�0a�0�l
�
��|�1��5��
I�+a 1�X���+�\����P1mP$01a`1px1u�541�X1ep1rPI��,h�1e�hd,i�,�1a2e<2hX2il2lx2n�2s�\���1b�1d�1l2nL�<n�����1a9����1g���2i02l��'8{(2i�!j�g{8"��D2r"��L2eh%{P#��d2o&l+��2p�2t$-�,.��-�2e�12��
�2a3ed3ix3l4nD4op4r�4s�4t�1u�1w�4y�2À6�6���2�01�`7$3i83nL3r�9QX9��3s�;:;��03s�=u
P<��D3t\3z>�<?tB�3u0B{l3a�3e�o4s`�u�BCp$c�3i�3m�3nP�p�3r��x�m@C�|C�	\C��3g�0h�3w�CKE�D$�3e���F{4g,4i44m<4o�G{��(H,�H\4lh4p�I{�I{T4a�Jc�K{D1a�4i�4o�M5�M{�4nNP$�R�[�T\{�4d�4e�4m�&n5r 5s45t�b�c@u5mD��v,�4u�!��3h����5c�9m��s(���Bh�,�5a�5e�5i�5l��n�5r�5s�5t��{����h5a�5e��p5l� (�������5l���|����5o�ܡ���5��
�5�����r��{�5pP�i�{@6a�6d�6e7fP7gh7i|7k�7lP8o�8s|du�8y�8z�6Ð�X6b8�k`6r|6ud�l�{l6s��t�X�!t6f ����5�ĉ�t�5����6mܶ$�6a�6rt���0Ce�6i�6r7s7x����P9e�6st�X�
X��p���{��,7a,7l47r ��P�w@7aD�:P#{��,H7l`7r<��l�t7k@�
�,�l�7r��)�7e���(�:�����7k�7uh�$�7a�7et����c���7c�7i�7l8n,8r0��<�:���p�,8sD���8d���|���$8kD8w ��,�,<8e0�d8fl8pt8r��{�������t0i�p��8r`�$�8t,�U�8mp�l��,��,�8a�8e�8i�=w\_Q�����8n�����8r�:����8n��{
89a�9e:iX:md:ox:p�:s�:t�:u�:y|��Ebd9nt9r��u��5���P9r����X9eX�w����P�t���>b�9c�9i�9l�9n�9p�9r:s0������9s��0�:�����6.��L�%����9u�9z�����^�������:t�:e4:n�������,:dH:g�yt�9��p�,P:a���vr����,p:f��r��p�$�:u�����:t��:nX��l��hfb<;eD;lL;pT;r`;u�����:a�;b�;c�;d�;e`<fp<g�<i�<k�<l$=n4=o�=s�=tx>u�>v�>y�>z|;�)'���X��d�Xbh��l;s��^D�Ql���t;���
�;eH'���H��;r����;e�;r�,�;e@��;i�;n<rT<t�c�*<s��p����<b��d0<g@<��9�	��	��8<�P��
L<t�Eh�$h<l�<r�<s��vTEd�<k�<m�<n�����<r�p�(��<g�El�xE�<i=oP-�0��<c ^�=n�$=a�Fep�L=d\=kh=rt=s��5�@5,T=eT���;c����h�gp�m��|=a�=e�=i�=l�=p�=t���b�=lж!���E��`�h�!>n4!,�=a8>e`}hd>rp>s�G��!��>t��i�"^$>dL",>nP>r����"�H>w�vl$a\>i�$|%�>k@&�l'E�'�'�0
 *���>f�>kxIp�>r�>s?t�5E@5,�>lA�>fD�HQ�G���>k�K,�r�PC	�Wl?bt?d�?k�?n�?s�W��?a�?e�?f@@hT@i�@l�@o�@p�@r�@s�@tt �HXg�X]|X|?t�X��Z��\T\!�?c�?i�?n�\��]�
�^��@$.�?a�?e@l_���h�_�@rl@e�_���?dd�$`w,@a4@�l
`���j��`�L@ePa�Dbh@a�@e�@n8b�,c�\2.dEsc��p@rtc
�d�`"g�da�@a��e�@iDe�e�h,(�lXfr0iIk��j,�@i�@z�kj�k�xn���".`Aa`BbxBc�BdpCeDf8Dg�Mh�DiEk0EmlEn�Eo�Eq�Er�Es4Ft�Gu�GyB�n	0<b�Ad�Ak�Al�Am�An�Ar�AsBt0p�Xq��q�Am�At�r��r��r��s'�s���Ad��hX��t�Abu�u]lu��At�v�8BnDBt�v��B��E��G�e},w��0Bd|wXx:x��LBr�w,TBapBl�xw�y,�Ba�eD��z,�Bm�Bu�z,	�Ba�BeCi(Cn0Co8Cr@Cs\CuhCw{��1�{�|{��BgL{�BnCu�:ܧ�|Cr Ct@|�T��T|{�|38��LCt(�(��: �TCm�|��|��Ca�Cc�Cf�Ch�Ci�Cr�Ct�qMT},�Cs8~~���Ck�~� !t��$�gMnd��Cf���x�����Ch(�'Dl(Ds�
�ȅwDe@�����$0Da`DelDh|Di�Dl�Mn��J(�XDt�!�!J���tDa�De8"�{�De �~���c�De�Dl�DnEt\�{E.�Dn�Drp������Dt����q,0��T��(Er��,Eahgs����TEa�gep��x���@Es8�HEn,�(�.��M`Ee|Eo4�&	���En�:�p�{�Ea��Qd�Ԛ'�Ea�Eh�܁P8�)�EaFcT�h�#zx��m���Ei��El��Fh4�k\Fn�u�$ Fa�FePQh�FiGo@Gr�GuxF�`����Q��dFr����lF�$G���ؠ�Ff�Fi�Fn�Fr�Fv����<g�sT���Fthl.`����Fe�Ff���X�gH�,�nx0i0��Gf��GftOr�5�uW����,Gu��a4Ga�QexGilGÈw�`��XGu���`G�H��<=c|��m���Gf�Gnx�^����hU:\��Ga�� Hd(Hn0HsT����GaLHc\He�Hh�Hi�Hk�HlIoIpHIsJtKuKy$Kz@�j�M\�԰��8Hel�@HhXoij�`�epHixHnص���!�Htě�,�$�Hs��'lSi�o|��ܿ��Ha�He�Hi�Hl�Ho(������5�\4�T�{��r�Ho����� Ie4Io���,Ic�\���8
 �{hIn���<Ia�Ie�Io�Ip�It|Iø��d�����tI���:4��Ii�In�������Ik�Iz@�i���In,��0�Et��4�{��b(Jm0JnDJrPJsXJu���IahJe�Jh�Ji�Jo�JrKu�v(����Xg\P�|���<Jr�����\�\��`Ji�Jr���d��xJe�Jw ����,�Je\��<�����Jl���Jb�~8��8Ra��e�Jo0U�����8t��-<��Jf�� ����^��,KeT��0Kl`Kn�Kr����8Ke���`���XKkpKz@��
l�It���xKe�Kf���
�,
�KaLe�Lh�LiMl,MotMr�Ms�Mt�/� �^����Ko���Kg�KrLu�������Kk�c4�g���Vb0LiLLlpLm�Lr�Ls<��<Ln��4T�{���DLadLe��f���\Li���
|Lp@�r��������Lw���\�Le�Li�Lo�Lr��z�Ll$:�p���J����Ls�b��c���LmMn�{0���Mi�M�$MlDMpPMrdMs�����f��{\Ma�,����alMa�Me�Mi���UhHn�{@Xa�c�Mp(
T�Ma�Me�r
\h
�P������Mk
�Mr,$�Mex�hNr�

��iT!LNbTNchNftNg�Nh�Nl�Nm�Nn�Nr0OsDOtPOv&${�eb����`Nl|r�Ne�Nl{Tj�j�"�.�!�Ne�$(({`',�Ns�&��Nd�Ng�(L.�($�Ne8,:OiOn4.,��.��MOe�0\/{(Oi5d2,<Ou�5?�=tOe�Oi�Oo�AwL?�lOsB:�OshC%
DC���Oo�C:�E,�Oe�Oi�I��H���Oc�Or(K�DMQPa�_ePi`�lPu$Py�P!�R�
lR��Pd$U{PU�U��po<PspY��Z,�a\Pe�Pi�Po�\!|Pn��ML^�hPa�]��pPt�a�c�8c���Pn�zrxn���Pd���Pr�lHQe\QlpQrxQs���Pb�Qc�QdReRf8RgdRh�Ri�Rk�RlTm Tn�)o�Tp�Tr8Vs�Vt�v\WwdWz�V�`s�l!aTQi8q��8Ql�s��zwhQeL{$�
�����Qh8�Dԉ�QlXar�Qs�QtH���QtTRz`Q�Qut�$���Qi�Qsȥ�(�&8�,�Qt̶d��afbl�a��Rs� Rn���,ReLRlTRrP#��()`72��\Re�Rlhcr�1uC0B{|Re�k,\2.T\���Rg������Re�Ro�Rr���
�)���`%.SaSeLSf`Si�Sk�Sl�Sn�So�Ss�SuTz�����3i(Sm0Snx����M���8Si��,@Se���l�XSmpSn����E�,xSl��h�$�Sa�����Sr��M�Sa0�T`�Q�Sa�)z����,�SnL����Sg\�w��,Tw�����ds��:<TeDTilTnxTs�\u@XTz��|�,PTi@�$dTe��c�2pT\�W���Te�Tf�Ts$`�^���Tl8k�j,�Ttnxn��
�Ta�fcUd,UepUf|Ug�Ui�Uk�Un�go�Us�UtVu�|��z,Ur~��|$UcgiDUlLUr̀cd���Mg�Ml`UzЃ�ȅE(�,hUl��$�Ml�����Um ����,�Ul��
,��Ur��M�Ue@�8�$�Ue�UkН;ؠ�$�UeVr�����$Vn\(A��Ve�{T���0Va�Vc�Ve�Vp�Vs�Vt8iuX���\Va�Ve�Vl�Vm�Vwl�dVh԰����i�����wijpHi�������kcit��,�Te�Th��r���`���V��Wa(We�ih4WsPWt���`���� Wn��@Wc�{�
�,$HWr�E�ZpWe�\�|Ws�`�H!�c.8Xa@XcHXdXXfdXgpXh|Xk�Xl�XnlYp�Yr�Zs([tL[u[����Wa8\b��c@\dT\e�^f�`g�`hDbi�cj�dk�dl�%mxen�eohp0ir�js�ktmu�v�mw�my4�z�[� �#�'��a��r�*��,�r�,�<2h�/���o�38�l�Xt�{�4$�Xo�5��Xt�5���Xa�'i�Xt�X�7:$7�07���X��<�d��A�Xr8A��	�Xa0Ye$�kol��nHYoPYs\YtdYz�CU|C�(Yi@Yux1�Fm0G�<'l�Gr�H&�I�xYiDb
�YeT��Yd�g��Yn,c��Yec���Yr���K��YfZk�J��	�YaZc$ZeHZgtZk�Zo�Zp�Zt�Zz�p��q]Xq��Zt�LM�L�Zg8Zk@Zr0M`MS�M�hZe�h�{TZd(�\Zl�N,�c.�Za�Zl�Zr�N��N�Zm�Zu�N\O�0O�$P-0����(Q$�Zn�Zy`RET;`R���Zp[t�T$�UX ��[��U���c<[eD[r�V��W$Z�\[fl[n([��\UX\qd[iP`�[c�[d�[r�[s�[t�`��x[�ĉ��g��m�@a3�[k�a�a�[e�az	�f�8h��b�i�[h\nxi���[e \h(\o0\s�i�Ft�i��i{�i
�l,��rL\a<��̶!�c.�\a�\c�\d�\e�f�\i]k ]l,$m�]n^p0^r�^s�^t����\rh��f�����\h����{��M�\nT���\c��m������\s(��\t]u\�E���<]a�edD]eh]i|]ll�&H��stT]x�1��	t]k���\]n��i��$a��eh�&�]l�����]a�]d�]e�]n�]o^s8uz �^���,�]a\�f���]n�_
��,Q
����]i^u� �
���l��X^n����$^a`^eh^lx^n�^o�^r�^s�^w��{l����_
ēMH�Mp^aT��dvb�,�,�^a,�)��,�a�4����^a�s�^t�,��md���
L._a�_ef`i$`lp`o|`r�`s�`t� �_bD_dL_fT_i�dkd_np_r|_u`��*:���/d��\_s��`�e|c����_.�_i�_n�_r�_s|:(�m�_n����`%.X+t����6.�_w@�'�^�	�x	���_s,�_n�
��
��`�H
wH`eT`i4�o``u`���
@`i��e��:h`r�
�`a�
P)<'l�#zh��,�`r�(
�(>�`a2L.aa��b@adPaeh�f�ag�ai�ak�al�am�an�ao�ar�1s�at$bu�z,a��1!�6��6��$a��a�0b�47�X9Q`7��Hailan�arP;�;��dad03s,�4P<��|as?G�n{x@,�aa<?���ak�z;0B�tE'�F$�J��H���aphK��K~�R�bh�U@�U,be,Z~XX��bs�Z��[��bsT\��
8ba�bc�bdce4ck<cltcn�co�cp dr4dsHdt|dz�^�\2. bb�a�bl�a{�bh�bQ��G���bn����be��bi8{�be��ble�be�c���bgcl,cr`e{$caPeD�g��n��qLcehcz�r(u54u��Tcru,\ce�w���ye�cg�cs�

t
�cnz$�ce�csLz��|Q
d|��ce�}��ci�cnl~x>U�~
�cu ��deT��dl ]5�,dn���di0�����@dod�~(���#aBhDss`dz,��
<b����hdn|�,pdi�r�du��
�dr����a�".�daeeDeiheopeu�d��(�.�dd�dn,�|�����dg ��������'.$ec,eg4em<en��8��x����Ml�8|gXek`ez@�e��e0�M�����, *�eb$~c�ed�eh�ei�ekflHfn\fo�fp�frDgs�gtxw�gxl+)�.m�2��4��ed�en�4�&�@5�6� fa,fi4fo|61@6fuh7P8 Jr�;��:��@fdl+�>��Tfb�?�?hfk�fr8?��pfa�e�fl�ft�Y��@��@S�A�`A�fl�fuA���fa�fn�fsgt�u�lEr�E,�5�F�fn4F$gi$gr�Qv@Gage4gixGX\H��G��<ge`gt�I�|ggJTga�ge�go�grP���hJ��gi`J��J��J]�ga8R�K,��a�ge�guL��,�O�\Q��P�gl�P���gb��c�W,4haxhe�hf�hh�hl�hris(itt ��W!,hbLhnoa�X��DhlX�5D]Xhr ]�`heT\!lhl�hn�]��^&�`,�ho�a^�d��ha�he�h��d��d��hn��h�d���h�e�Ti�0iw�haiepi�i��j�j,ia�krxnwTia�ieji<jo�i�Xqln��Likdix�v}�v5�id�ig�imDs�v��li��j��vgwnwP�|�'.�ic�id�ie�ii�irjs~�P~k
�~�tl�is���d����\�~��je��g0jn�}�0���(jz��`jb�*chjdpjgxjj�js�jtȕz	d�z	�1{�{t�uP����js�jt���̘{H8U���jf�jn�:�T�,
L.�jake�,hki,kp8kt�ky�kz k��(Hnij��|�kd�����S����(To��,Xkahke�ixkr�u�k�4�^�|��`ka��l8�
H��,����k���~�{��,�ke�,�ka le�/h�li�lo�ir�ls��t�lumy��z�l��kblllt��,d�����lbT�j��<lcDliLll@ntlr��v<���Xle������h�`li����hle�lw�lz��K��K�����ln�0$m�lsD�w�5�E��m�lr�.9����ls�� ����l�`�	T
0�aLNb@mc�Zd��h\mlhmn�&r�ms�mt$$H��$$Hmp�!Pms�&��/d��s��t�1�\/{|mtd2��ms\4�@6��`Ef�ml�mnp:��:x�E,�maTE!��r�U!
�s`tT�ne���mu(�l0,nuH���c.�naob�oc0pd�pe�pf�pgqhqiXqk�ql�rm�sn�tp�truslut�uu�vx\u���na�wb�yc�zd�|e(�f��g�h��i�j��k0�l�m��n�o0�pd�qԚr8�s�t��u��v(�wT�x\�yp�z�vü, {�nbocol��r���5�t �Toa��b`odpoef��g|oi��r0mst�t�ow�oy� :��r!a	t!�!��hor�!��#��#�4$�ot�#���oe�oh(pk����$�bH$�oe�oipt pu��W,%�on&�pì%��orL&7	X&��p��&:�&!�',L.Xpappfxpl�pr�pt(1�'��Ppmhpp4(�)���)��)��)��*{�pa�pe +��*��prt+:8+��pr�,xe�plܮn8$~�.$�petE��/���pmqt�1��1{,qc<qlHqr�a�s��2{4ql��D4��3��Pqetql|qr�qt�qÄ4��4�7e�qo�4� 5w�4$�qih5�p5���q��5{L.ra�]b,rd4re<rgDri^k\rl�rm�rp�rs�rtH^u�ry�5�rbrg$rr6j06�6�8�@8��9��9�Pre�9{H�r�:$$^aprexrg�:(;���	�;,�r.$<,`%.�re<@P<��<�rh����=�0��8>c�rs�=���rasesi smxsp��t�>:|>�sr?-�?'<sa\sehsu��5|?4sn���t���Hss�?Psr�?��d
�?,psl8A���".�sc�sdte�f4tgHtiTtkol`tmhtp��rpts�t�twPB�B,\B,�sa�se�4s�B~�sptr����v�C�|C�ti$tr�C,�DU,D$,ti|E$<E�@te�E,orpF~Ga	0G�D#.�tp�G��ta�Gb�H��I,�tfJ�J�taXb�te f�ti�k�p8zK�$M��L��tidN��M��tn`R{L. ua<ucPupPRSD�R(ul�R0uhT�TrHuhX ��[��v��U�L.�ua�ul�r�us�ut�U�	�HX{t�Xw�X$�uaZ!	�L.�ub�udvevfvg vm`vs�vthZ'�&.�Z�&	�Z'�un([��[�\�8vaPvi ��4\0vgHvn�$5�%{t]x]{Xvcl�gxvp�vs�]�^�_��^,�vs@_-�_,�a�P`��
�vdwfwgwhwm,wnPwr��s|wt�wu�`���v��'�ĉ�����������a,Db��b��d�D#P�d��$w.@wiHws�e��e��fQD#.`wa�f	I
�i{hwexi��pws�j!�ws�wt�k��wc�/��k�we�k�whl��l,	xahxb�xe�xl�`olyrxys�yu@ypl��l���wb(xd4xkDxlXxr� up(��m xe�m:<n�(n�<xa|o�Do��PxeP�thpQ��
�q�pxh8q��xxc�xl�xn4r��������x�`s��x��t9�s���xt�z^�xd ys�z
�xaHye`yi8y�{h��p�yr|��ye���ys0yt��*�|������p�L{{�'.�prH|:|Xyn�>�i��z�ye�yk�yoh#p�yt���yiص5(�U �r�ya<��Ѓ܃���y�|�!|�r�y�d�!��{�ezhpbi�zkX'l���`@.Dznԉze,%i4%l<%m�%rLzsxzt��w��lH�V\zplzth�E R�`Qdzrt��Za�ze��\�T���zw�S�zr����,�zaL{e|iT|o�|r�|t�|wx|�<�����c�zf��k{l{m{n8{p�����t�{����i({t0{z��������~`���".@�@{ip{l|{n�{r@/s�������h{b����{d�{g��E��Mx��{lH��{r�����{e�{i�{n���P��$����{s���{n��*x�M�{t�$|lȥ|a4|n@|t4]!��h$���,|nL�`��ex���H|bh|np|s��0�D��� �����t��>�|a�i`�&�|a�h��P�,�a̶\2.T}a�}b~cP~d�~e�~f�~g hti��k̀l �m\�n,�o@�pd�r�s��t��u�v�w�x�z�}������0}r���8}el}t��,D}lt}m�qs|}t �,��
Ը,\2.�}s�bP�eX����}�̄���'t��}a�}l�}r�}��D����}a���:�����}��!~a(~s�����}h8~kP���(�rH��L�ȼ0~.H~id��`%.�dl~iL��Խd~t̿��Mx~l�~r��P�Q���~e�~f�~l�~o��������m\E.e������~�8{�~���~l��:(��c<���aPe\idllo�	���<t��DnX�,���
@�,T����".�b�e�g�l�m�n��s�����e�lx�����e����w����M�a�i0����p����p��4�e��g@�kH�rp�s|�v��T��,�c��H|<�� �P�e\�X�r4��d�t��~d�(���rX����e������t(�m��el�l�����Āah{b�e�f�o�t�uH��si$k�����H�h��C	0�L.8�iH�pT�uP����.��,@�fH�~����
L.��a��d��eȁg��i�s��t�z��h�'hfb0�l�Q
�,��i��r|�(����n��$�{l؁p����������e0�	p�$�lěsuw�z���@�'(�l�{$�rT\3��8�eT�o\�p��	�����4%.��b��d��f��g�k�l�mD�nt�o��s��tЃz�=������r��;����9Ԃe��5��̂nx����e�iܚ������n|����9$�a8��X�<T��u��D����0��H�zl�s�{t`��D�'T���X����LT��dvb,�$�	.��a��e��p$�ST����Z��9��eȃr�����p���e�y�m�U�4���4%.�a�e$�o,�sP�th�u�{8�����@��8�eh�D�r����@�w�z	>a`�uH�?p��|�$�!p�y��x�h(�!��g��l��nĄr��E�!.
��3d[i�Q��dB�	B��Ԅd�,܄i8����a�i��{���m �i��d�,	��e��iȅl�o(�r@�s��t��ux��4�_
�c��T�t�\�l��u���h��\:�`+n��s�{,0l����c؅nH
w��i��<��
��1���l+�P7,�sP{��g�l �r���
w�ud�Ü{P$8�ap�i��p��t\��\���R��b�h�dP1�|�r��h$0ka��r|���$�a(�e�i�l@�nL�ol�r��s,\���b�d�\h
k�nLc�{H�MԾE�n��aH�bh�l��n��r��tH�7l�v�T�r8{\�e�)�@�,t�w��|�z���-a� �� ��p� ��a�o�"�#�ćl#��̇e"��؇s�#��#���nP#����a �e,�i(�E8$~�u�$8|g�&^&8�od'���b�q��(��X�l�(>`�a��e��i`)��g��s��t��$*��*�4*��nl+��2pЈt�.Z�-Ȉr�u/a2��".�a(�eT�o�1s��u���1:�".�6��$a��a�`7��4%.<�nD�r;�P<�lI?�H��L�eh�ip�l�If�I,Z�XX��x�sT\̉a�b�c$�d\�e�f��g�k�m0�n��r��sT�t��x�[��m��sdtL`�X`$�l�_,�b�a{D�e�h�a��a�a�b�<�aP�o�b��b��4�nxck�c��H�l�c{��f��l��n��r��s�u�d��d|�r`e�f f���e�xs��u��,�g���c.Њe؊s�g��h$`%.���$i�i�h���t�i<j,j{�a<�eT�o\�rd�s��tdc`j4�iL�r�j:,�jEk\k5hkl�r8k$t�e�k�\E.��eȋjЋl؋r<~\l���eHl��n���l,mrLpE��a�n,�l\p54�D@u���bp�s(w���c\w��w��(�ah�d��e��fЌgT�h�i�k��nP�s��t�v`x���e��i�xH�xx�xܨG���z�x���i(yg�yg	��o�y�le�z$Ȍlter{��z�t,{�d{�$�e<�tH�u�x0��m�{�np�r�{��{,4�aHld|K�R.d�ol�pxM��
x�i��L}���e��r8}�@}��gh}�����{�a�c�i�o�p �s(�tĄ,<�؍o��w��h�4
L���kd�ĆQ ��	���H�e��d��4�s�<�r(�{x�a��i��r��t��ul���p�l��t�
�����$��r،�D��	L��,�a �l\�n��rԏsL�t�u�ü�4m�dh$�����X�3D����u�
�a@�iL�oT�uT���8�s��M�{(�h�u�����3t���p�u����x���>��e��i���T}�����a�Uďn0���,��{̏e�hX�p�t��X�,�i���$�a8�e@W��1�i��t$�,��0�c��{P�$D�a|�e̐iؐo�r���<��h�g�p�n��rP�i\�����f��s��w�]��c8����i��:��Đn�rT�
�a��iD��ت�������h�m�n,�{L��(�iX)<�$T�a\�e��i��oԑs��t��Ð��t�ap$c|�i��td����!�ml���s,�!0��ȑs �������V:8V����s`�$,�p�tp�Xe�o���4���$	8�a��dĒe�i$�o<�s`�tl�u��� ��|�0�gX�lx�n��p��{����P�ah�d�]�@������p�s��z����h@J��:�����r���������E��\2.ܒf�o:s�����'�b���e��dH�m�D������i���r4�s��m��,$@aL�t��4wa��p�$X�a���m0&��$��x�s��Mēa�d,�e�f�g �i4�o��s��t��u�yT��l�hfbܓn�r�u���d��i�Kz�D$��d��`�e�i��&$�rD(@`�bl�ft�i��n��r�s�t�x������n�T�e4~�$�Cf��s������e��s|2
�����e�K�����fܔg�h��k��t��	<?,�,�i
�	0
�
�
,�w��Q,�n(�pL=d�p�Fr�d�tl���H���H��V�
X^h��l����$p�Gzt��L"4!,��e|%hr,:�+!��il+��e�j,�o4�s *��ȕb<�cd�d@~ep�f��h��iĖl�mp�n�p��rP�s̘th�u��x����,��,�P-,P�kL�L.H�.�.��.\�e0:x3,�2��x�l��n��r�F4{��ap4�
�4^��r56{��e�l�s,VyH�\�7��ܖn�7$�a�e8��7�n�89{�>.@�aT�e`�mh�t��898�d�9:�9L�rX:�:��:{��e��n��sЗtܗy<K<����b�;���r$=��=�>5�=��n�=,ėa�>2
8?���e�h@@�A�a|~e4�oD�t`A$�l,�t�A�B'�E'PQj4F$<�h�G���hl�it�mt�s��t�H�HI��kc�I^J��a rLK,��u�.�����h�:�Ke�K��g�K,��a��e�h �r,�s@�tL�u0LL�i�L$�L\�o�M�tMa�i�M{�$oh{�M$8�a��*��N�T�tN!\�nx�tDO{\QE�P��l�P����bЙdXfD�gܙh�k�l��m�p�r�s �{�QșudR�R�R�Q
T���i�T,�T8V��D#.(�e�V�Wr	\�ad�e�f��l��o4r��s̚t�Ju�WT\x�r`^�0^��p�e��i(�v�����nDe��da��i�e8k��j,��t�k�xn,�aH�b�fcP�e܁h��iԛn�o�s �t@u8��o,n���b$�r�t{�v����m�v��,��,���wr�||�a��r��s��w8}�D}��h�eT},p�l���d�����sP�{�����t�~�r����kē���M̛a�!�b��mȕg����,�u8�$�t�8
�$hh��^�bT�$x�a��c@�e�h��iНk�=l8�nD�o��p��s��t�w��y��b��d��m��n�r�K�����p|�t�{��gȜpМt���G�Ĭ�԰3�r�؜e�l,Su$�wl��h�=�Č���l����e
�Qص��,�nij4�iX�np�r��t���ądh�e��M����`�e��s�!z�����������(�7غ<��m��a|�(�Xl�nܿr��a�i�l �o�5r(�uX�:�:������e�s���04����5��M��
0�o�X�b`�pp�r`�b�����i����h�d(�t��a��h����G�(�����t4�{��n����a�e�ThH�ix�o��r�u���$�:��ܞc�i$�m0�r�����Q��g\�1�n@���.
�pd����b@�w�����{\�ed�n�p��3T��p�g8�]��^��b��t����,���,��ԟ�8�?��a���̟g`�����n4t�H�����d�,ܟi�$4�aؠe�hH�i��o��r �s`�t|�u��z����
P�mX�s��$�l�m`�n��r��t�;�P<������gx�n�/t��z���������e�X�T�,��t������r ���������Y���Q��Рe�i4�lH�mT�n`�r�sd��<�����f�l$�sp���ra��������,�f ���@�o`��t�s����hle��f�0h��k��lСn�r�s �X
��,��a��o��X
P��T��\�$��aȡe����M�����z�,ܡe�9$�;�����k��i(�o<�u�^�� �l�Y:\���4�m����dd�mp�n��W����\�a����#f���|�p��r����iX{���hVk�a��aТeH�i�o���P$c�i�����sd����s����c|�����M<�e�XoP�p��	4�h
1(
�H�a�r,i/����h�t�p�rṬa�b�ch�dx�f��g��h��iȤm�nإp�r�sX�t��z ,أrx%
,
,�eP���tP��s${�h<�kx�	<Il@$�rT0�eL�u�:����T�a�,\�e�Zr����a��s�{�|e��r� �"st {��nX0s�$:�fPvi�l�s�z�%T�%�0&,D#.�&K�&�0�a<�dp�f��g��i��k��l8m��nȥt`�w`'���aL�e�'X�r�'l�lt�s|�w�������\)��($��lX)���o�*�)'�erx�8*��*i�;�+��Хr8,v��a�e�o�,{�-'�.��0{\/{�i,�sD�tt1�xTp<�tt�$�1,P�a�1^d2�x�e�1h��o��r,35�2p�i�3rD4
�5e��w@6��bܦc�m 6QȦe�s�6
��n�6�6:Ԧhp���:���m�=,�ep@$L@���eL?��n�E,H�u���TP�4�sP!<�nQ$�Uh�cDVw�Z,��a�eP�id�o��t��u�w0��dZ����c��n��rȧsX[{x[:�[�����]��Чe��g�t�\��اn�r��9X^NL^��s�^��8vf0�g8�k@�w�^hD_�L`��a��a��H�d@^8c��\�ft�p�c�dLd,|�e��h��r�d,�dato�e�e,��u�d���g$g�	0g��̨��f
��eԨÌI�Lg���cH\2.�1aĩb$�c@�d\�f��gЪi�k�l��m�nЬp�r\�st�tЭuh�v���a<�bl�cx�dije0�f��g��h|�i�jܿk��lL�m��n�o��p�q8�r��s��t��u\�vd�w��y��z���t �ܩe�l��r�s!{�t�!�4"r��e�"�$#�	L%xH$�o0�t�#���h�%��'Q�'8�a�9md�r�*��*��T�ax�e��f��i,�r8+$�+��+Q-��,��aĪe�n�r�-��-���tl-��n�1^�k�ld2&	�2��3��ma�i�r�t|4Q�4��5��x�.P�e\�ih�op�s|�t��z����8��<�b@8�D�r�9���d�;QP<$��e�<h�=��=���eȫmܫn�p�t�>��>����i|>���r�?�ԫa|?���?��{h@$�o8A�".8�a@�bH�cP�d��g��h��i��l��sĬt�twA�4B�PB?\B'`�.p�rD#,��v�B>h�i�>�,D$|�.4E�<E�`Fa0G
��kxG5XH�G,��r�I��o�rpJ��J��J��. �a(�b0�di�6r8�sH�t�"�KQ�Ki�L��P�\Q(Q$@�iT�`R��T�pl�ytU��U���a��hȚlКm��r��sĭzW!�W�
HX4�
�Y����n�Y$��eZ�6.ĉc��e�f�g �m(�rD�sd'([����b���['�r\��\.4�i4.�<�4]{<�bX�e`�w�]xd�{H_gt�o�C{P`ĮhЮl��mܮn�r�t�u�`��|��ĉ���������\c�	�b����n�c�T�t�d��l�d�gLe;Te$�e�fQxi�<k��j���l0��lCl��(���l�T�a`�e�n�l��L�n8q��4r��
�".��aԯe�h�Ej@�lH�oh�rp�s�'u$��D���m��nįp̯r,��<�H����LKԉܯ.X�a��bİc̰d԰eD�fL�gT�hd�i��k��l�m�n$�o8�p@�qT�r��s��tԲu�&v�w8�z��à�QP���P�kp�l��n��ot3̊X����x�c��g�n`��p�,�������0������h����n����&.��f�i�m �n(�s<�x��P�,\2.;t�:�p��{8��4�s0�|X��h�np�rx��@����\�e|�m��r��s@u,��$����u�A�0��8����e�iH�{X.бi�{�T���ȱn<D�
�Cܱt�Fa�E����h�����p��
P[.XKA�����xhK9h)rd�.
�~X.��H�ep�i��o�MU�M{h�n@N^<���|�m��u���H����e��k�Pt��IJa̲r�R��V
��5�n��pď�����t�Y{̏�p[����t܏��e,�i�M5DM��$�l�d�&���X�p`�r���̔�	(�0l�$�����a,qe��i��r|>�t�����e<�����mȥ|e��̶(�aH�bp�c��e�f��g�hصiȶkжl��m��nh�o��p��q��r��s��t��u�xԸ,\2.��,�t������4�nt�<�e|rH���X�o 8t����`�h��kȼ�����c��iĴr����&��g��n��j��
�����c.ܴ������Դ��:�����l���ah�l�!r<��8�a@�e��i��l��r��s��t̵�(�?��T�i\�ll�rX9����=$��d�kd�1X�x�n��,��\�{��e��Qt��D��8���b�������T���x�.��eЙg�n��s��tx�
��
<�.D�bL�dX�eh�f��g�ht�k|�l��nH�r��s��w�B��(�,�uT��l��|��`�o���x�d{�4��E.��p��t��\���'�������
(�������".��a�e��fH�ih�s|�t\��l�,{d�g�m��H��cLm0�r��sX���(�lh:s��{���@�x�������T�� ��)z��$H�$t�rd�v0���ax�p����x�.Էa�d�g�i�k�o$�s,�tlu��h�,(�g��,�l��$P�l��Q��{��{�b��b��
p�$D#.P�fX�guiěsuw`�z������l�{�rDg�t�s��|�od�m����L.�a�e`�f��g��h	iܹk�n��o(�sX�tp�u��v��l�z	�d��l�t���Ll��f,�iL�n�r$��<�n��� �gܾm$���iT��D�k����,X�o�r��������l����t��p:'���rl�3��,����a0�������ܚ:����ȹnx�,йe���H�M�tT�dvbX8���f������,�$D#.<�aL�e$�A�UT�@D�h��$�>.h�a��L�C	��f�vm��nl���������d��(4���\E.Нe$it0iu��mغa�h$�G�кp�t�N��
(����n���e �p(�t(�{h�sp�d��l�a��l��@�r��$H�e��{T�l �`�lPH
wx�o��2,��a��e�i8nn,�o�ar���1!x�.лkػn�s`3!�4�H5m�6����t��`7<?���6.�d�r�?T�A~I^�H��$�cH�fP�p`�rl�w�I,�J�pC��J��X�ePKZhK^T\ؼax�b��c�d0�e��f��g�kl�l��m��n0�oD�s��tȿvH$!�.�\:��h�[̼c(�d�t@$�,_��a�����b�o�b�e(�o�c:�c�\�gh�nt�r�xsd�0�H�seP�ef���e�g���!rj&�k]��a��n��r̽s�kr�l��u '�m
�m$$m�Ľt����nؽb�n,	�a@�e0�iL�l�rT�s\�t8�u �Ào��o����p`�o��,�l�o54�rLp��pEq��tQ�q��d�o�v�@u��x�m\w�w��
��aL�d�yf̾gxh�i �q��s�yt(�vz.
z$ľa�h�lter�s$jle@zLz���a�z��e�z]���}�}�<�n�~���^�2al�cd�el�ot�p|�s<�'d�,Ćr �:(�^��a��r0
u��c���ut����a\�:��t�,��aH3ex_r�rL.(�a��b��e��i�l��n4�ol�st�t��u`�à�Q
�����a���lH�mX�nd�rl�s��t\�,�������P�z���L���V�.��g�s��,t�e��|����������p|���x�.�f�g�k�r��s�z�$�k'�fԟ�ܟ��� �ah�D����s�����,�mH�rP�w,�,ܤi��$���X�����P���x�|�p������l�r�a�b�e<�iT�o��@�Q������l�n�v��w�,L?�H_,�eس'�� ��������0�r�X���(�sl�L�p�t ��0�\2.p�b|�e����h�e�/���
��a��i��o���l����b,�l ��\2.��s�M��r��e�h]Dqp��t�,l������ *\2. a`�b$~cp�e��f��g��h��l$�m,�n��o��p��rH�s`�ul�v��w��x�zl+c�/z�/rh�t�1�0��|�t�1�3�2����e��n(�r4w4{��g6^��a��e�l�y@6�6��6��i,85�7��r�7$�e�8�9^�:^H�aT�e��s`;�:�@�u�;C	t�nD��,`�e�;*h�dX�h������=���o���x��>�8?����fA��
�6.��a�c��d|~e�g,�i8�o�s@�u`A�fl�B�0D
8D$�a�e`D~�D��D$�e�ED�GM�G�L.X�tJAN!\�nXOmx�etO��O��P��c�f�
k��l��s�R�8V�W��".�a��e��f��h��i8�l��n��o��r��s��t�u0�y���8X��W!	��a,�k<�ll�nx�p��r��u��z���|X��X��X��4�apE���.|EMH�r<E�T�e�X��`�ilYA�Y��Y����a��o��u�Z�Qx
[x[�����L[�`'x[:��h��r�[������b��[�	T\!DK. �l<�nH�pX�r��t��z ]��]�^�(�i�]��0�s^,��-0^��P�it�l|�o��r��sh^��^��^��^��^����^vaE�`��aL@e���$a,,a�����c��rDb����e�k�l �o(�p0�s,c�4c8
<c��cm	�c�4d��dT�ap�ex�i��ud���dQ���d��\��e~DeQpe�xej�e��e��d��g��i��k��l��s��t�1��e?�e�fQDg��M��g,��t0iw�>. �a|�e��o��u\�ào�Ti���c4�n<�x�s�di��i"li5D�ml�s�i��L�����D��i��it�c��d��s�i�j�`j�	<j���b��s�j��j����s����j��j��f�jg�khm�m��n�p �r(�t�+��&��mi�mxn�d�ax�e��Üu�X�clu�L�sn��X�t��^�|p�t�v��T����`7�@7��rP7��eܦ:��k����c��d�71T�� �a�c4�e�=l�o0�pX�qd�st�t��u�ü"ĩ��o�
�bX�cd�fl�gt�j|�l��m��n��s��t�&�$���P�k\����m`3�����a��b�52
p7K�s�����t��s���g��o��s��tTz�F��-Ĭ'\�R�X]t����t|�^������l�e\,op�ij,�c`�gl�h��i��r��s��t�Q��X�a��h�a���f��tص��x�nL}~�u�$����t�z	��t������a��s���L��(�$��eغ��m��a�����f���f�i��p$�r�4�
������i���(To�rn,,0t�m�@�e�,L�u���4����l�a�e�Th��i��u�kà�{�p�:<���r���m���.4�a��b��c��d��eL�fT�g\�h��ivj��k��l��m��n��o0�pp)q8�r,�s4�t<�u��v��w��y��z,�è�{�V.��a��b��c��d��f��g��h��k��l�m(�n|�r��s��t��u�xt�e
�������bP�$l����x������f���<�����|�������e��i�k�m@4p�9�@:��;�����:�i����.L�aT�d\�id�nl�st�wT��\������������4�����L.��i��s����P��S< ����iT�,`%.�!�L.��f��m��r��s��� ���\�8�S�.��������@�gP�ld�nx�t��u �������H��H�\�
d���H�te�����\�d����:p�t�k������s�T����J���.�a$�c0�d8�e@�gT�h\�i��l��m�n�Vpd�r��s��t�u<�vD�x\2�$�!��'��!�n�����h���/��L�r�����<�1p�gx�l��n��4
p����h��5��Q�a���.��l��n��s\E=��v�������P�$���\�D��K��.
�.�a��5d����r`��h�.0�dD�gP�sX�t��*��,(�s��.��<�l��R�$��f���|�.��e��m��n4%W0�^�����sh���r|����d��M��c�����e��s��tН.h���.����e`���l�i�t��0�r��,�e4�f���(�X�jl�,��$�)����i�io��u|�È������t��p�5\�:�.
����a��b��c��e$�f,�g4�k<�lD�mT�np�ox�r��s��t��v����$D�!P[.�g�l�nl�y��|����
����@�:���A����L�fh�n��sd{����������o`��P�����u|���J��D�k����{�'.�b�d �f4�m<�nD�oT�ph�r��s��t��u��w��z���\�m���E.0l,�fx0�v8��h���������L�o�6�����`�.��e��g��i��t$�84Xwt�<�������e��t�$��������>�,��e ����DP�<�c(�td�l�h�����`�a<�e��i��o��u��\����
X�d��g��h��i��k��l��n��s��u��à�	������������s,�D�L����� v��u!��m��`����g(�n|��������� �e`��4�fX�ix�n�����P�bh�f������p�n��U��g��k��s� �� ��Ė{��ld����l��m@�M�{��a0��a�g�k�n$�p(��� P�tm� �J,J�
l�bt�c|�d��e��f��h��m��n�r|zs
� ((�0�h�� ��c�����s��t # (���.��d��i��n�s�t��Py��8*�p** �1 �����e0�l8�n@�t�.]d8 /? �d�cx�r��t���:\�h�&F ���p�.��e��g��sN V ^ 8q
\��P�XW�`���l��T
��a�b8�c40f��hp�i��l��mh�n��p��r��s�vf (�a0�i�l�8�$�L�hDPD�ad�si��\�tt {X0s��t(����c.�!�a��i��t #8$Q%:�$��r��u�$����a��e(�f`0m4�oP�s$%1H%Q�9l�nX%k `%$�s<%{�t���%� �e��T@�r��:$&�0&,H�a`�t��z	�&��.��d��e�HfX�i��t�'r �'����h�'��r`',��e\(M�*K�+^��pHf�8,����a��l��t\/�$Za�h�i�s�0t1:�5M@6H'b4�c<�d@�n�6��7k
P�e�7
�7H�n�=�E,x�a��e��iTE�H��hJ�|MWDM����e��l�M��U��l��nXW�0X��Z,��a�ed�i8`op�u��w���dZv]��\���i(�nL�sT�t(^��]�� �a<�eD�t���L^w�`:a��b:�a��\�s�d�f�\��x���f�Hx ����a�b�c�d�e��f��g��h��i�cj��k��l�m �n(�o<�pD�rL�s`�th�u��v��w��z���lx ����̶<�cD�gL�iT�l`�nt�r����xT������a����x�g��i�o������o��s�t,�$D#.��eT�} d��n��e�H�b2T\��g��n�k,��a )s�w���eX����r����x  *�4�s�G
�Wx xnnT�x X�t����Tt�m�$@6�`��|���=�E�ZH\2.t�a��bP�cl�d��f��g<�ht�i��k�l��m�n`�o|�p��r �sT�t�u��v��x����a�bT�c��d��e��f�g�h��i�j�k�lD�m��n�o�p�q�r�s,t�u\�vPw`y�z �� ���n�� t �>.��a��b��ef��gȓhd�k �l,�n��r�s4�t�w<zD�àn�� :��nhph`!� !����l�n�s��dXt�"� 4"��e�"��#'�#H
� ��<���#�\�u�'��'�'d�a�Aid�r��s�)1�*��`%.��e��f��g��r8+o�+�+� �,���a��e�l �o0�s-�d-�l-��i�n�-� �-��t�.� d'�h/$/�(�t�/
P�l�0h0{H�eh�sE��0$`�k�1{��k��l��n��rd2�2��2��2����s��c.�3�3����a��r��t�z�4�qo�4� �4$��a�b�u5� ��x|5��5��x�.D�ad�b�]d|�e��l��o�Xt�5{T�g\�k06 ��p7,`@.t�k�z�@8���n�8��s��:��������:$��à;Q��p�;��=:��e��i��pp4t|>�sr?{�d��?,��l8A��`%.T�a\b\�d܏ed�fl�g��it�k��l��m��nX�o��s��u��w�zA\B�CK,D��E'��l�E� `F��pF&��e�F>|F��a0G��	.��i��p&�G� �H$TE@�H9��a��lH� �H������H,D#.(�aD�eP�u(l_r �(��0�h�H��8�r�)� �ITI{X�r�I1�[.��s�Il�e�tf�4l�^:�J��Xb��e��i�k�m�tX�u�zLMk8M���s�L���n��rl�.`M����e�M-�N(Q��i�opQ`R�0�a8�pPRT�UW��b��nx�u�U,@�a��e��h�ul��m��o��uTVc�V�V��i��m��r���V�W���e0W� xWk�W$�W��m(�lY��e�m([Q �g�5l(�nZ!��f0�k �m8�s��[� 0!]{�b`�c��e��g��k��l��s��wt]xl�h�]�r|�w0� �]�]��0/�]^� d��H_��_m��iS�	lR����sP`8�b�c@�d��eH�gwhd�lp�m��n��p��r��s��t��u��x�`��ܴ����<����`��a�Dbz	T�y�b� 4���c��\�t�d,|�t�d_
�e� Te$��s�d����g��z�'4
,f�f��D#.��u��{8h!xi:��ttj��j��0��0lQ�l�`�e@�l|.� L�nL.�e�|��kp|{(�c�z
4�oP�!��$t�h��kԶl��rP��ԉl�a��e��i4%l��u��w��!��:�������uD�
 ���n̶\2.$�aP�b��c��e��f�g�h<�i�k�l��m`�n��p��r��s��t�uX�vl�x���8�k@�l�qnķ�����t�H�ad�bt�ehp��!����l�n�!l�a��n������h��kD%!ȼ1	�ae��id�:��M4m��n��r�s���
����@�w���f�(>4�e����r<�: �a0��(�Q������(��T���`%.d�f�rkp�l��n��s��!��{\�r�����h`7:T�r|�e��Q��e��g��l��n��T����ct
Q
�����exE(�ad{������6.��b��!��)(���t����\E.T�a��b��e�f�g(�i@�kP�l\�nx�o��p��s��t���l��h�bp�cx�u�g(�cX�c��&H����b��c��h��id�m��n��rD�u�����e��M��b�����������n ���KX�����d`@�,�.p�{������ �n8�t��:��{���$H�e��!������d�t���l�s����������|�{ �)��kH�$!�ea|nh0�`%.��e��i�m �o@�p��sX�u��d�il�:P���n�?w��'�e��c���r0^�T\!,�r��,4�eP�f���H�$�����.��a��b��d<�e��g��h��i��j��m�tn��s�tt�z��h��hfb��d��g��s��u��\4�*!|�5��b���,�]a�f�i�l$�o,�p4�r@�/!��6!d�7	��� |����\;b\�fd�i|�n�r�;t ��4��4%.\d�/\��t�s����.��l�Z	�#5��{��a�;��{��nX��c��;����m�����e��I��~p�$�a0�b8�d@�e��nP�rdw�z�g��=!�����X�aH�i���0�h4���\�r@�,h�e�}w��^����i����4%.��a(�b@�c\�dh�e��f�gH�iP�k\�l|�m��n��o�r�s��t��u��w��z���l��b�f�um�n8������6.�����, �s8�t��D!��{��@�H�.$�,P�el�@�b��i�<l��n��r��������f���T����d\g����uk��zЃ�K!���l��,��o��r���S!����l��������g0�r�)Y!���il�>$�e@�u+����T�a��m��$8	�(�5�h�r�$p�e��i�Q
��ē��rH�M��a��c��eH�x�,���uT������������P��.�,��e,�$�>.8�cL�eX�tX�B�$�a��,�hp�5T�D�r���l�.t�i|�u��b!H�n�i!��QL�C	��f�H,lH{��h�H�������, �e��à�p�,��a��b<\44���aНeT�p�s`�t��u@�Ô�!��c$�n���@a!|�,�c����4���"��C	L�r�x�a��e(����{p�nh�p!i��r��s�w!�����g06k��}!��R�".�a��h��l�W��t�U������e �i��,��r(�����e(�f0�mܤn8�r�U��`�����$�.H�a�,�B{�,P�i�����d�a��e��i �p��t��z(��@��p��8l
d�i��i	!,��l�Q�e
��r2L.4�a��ep�i0�k��l��m��n��oh)p�r�1s\�uԏv|�y����1!4%.T�g\�id�kt�l��u�,�1�`3:l3�t3��l�.�5!�6��6�������t��`7!�.�a�b�c��id�k�m�n8�oH�rT�sh�u�T7��7.
8:X9��n�9,�:;l(�e0�id;~�;~�;�!��P<��@�r4>:`�e8�Q
�>?<?��k��l��mx@'�@�@u�0B�tE��F�H�c�f�h�l��r�t�uI��I��2��I{�>.�J�K�NwhK��K~@�i�:�M�(�.L�s�M{0�nP��!<X�XX��T�bl�n�Y��Z�[{T\�a�b��c$�dD�e��f�g�k@�l��m�n��o��p�q�r�sP�tp�u|�v�z�[(�d�mX^�_M�a{D�e��
0�d�b�n|�o�b�e����d$�.�c��8�fl�gt�h��l|�n��r��t�ue�<��f��g�	��e�g���c@i�j{`%.��r��t�jv8k��k��e�!Khl���zHl�r�n��a�i4�ro:�n�m�r�p��p�� ���p>(�Àq����al�e��gD�s��t`yu����r:�rd�i|�l�r���{4r���p��ti �1|u��g@u����aL�i�m p��,�v,�a�w��x�.��aD�b �d(�eAf8�gT�it�jh�kt�s�yt �u(�v`xK�x�z�z$0�aȌl�cs{-�zL�tr,{,`�ld|��	.��e�|��}�lAr ����lȀ�n,�>.�m���e�,��u܁�����h����c(�e$zp<�t<�x�a�w���h�����:<�� �iԇ���4�a(�
(���H�e`�u،�d�
X�Eh�mt����a��e�o�r��:������n�����l��r�:�@��Dl�C,�D�|�,��a����e0�i8�u��m$�rx����X����al������tn���`�a��e��o��
|�X�l���
����l�t��t�n��!���s����e����d����e0
{�s@�s�
�! *\2.<�a\�bl�c��d8�i�ek�l�m8�nh�o��p��r�s�t u��w��H+M�)E4�sL�t\+�!�,�	l+T�j,lP-,��h��k$.�!�-|�t`��!P�����t|.���nL.��e�.m�e�u�.��r���!�/6{��a��e�z�6��8�9T(�n9{�e0�g�9:�5�:�H�a\�e�:��~u\:�;�T�h�>��?:?p�k��t8?��x�a��o��t(['�@A���6.�ac��d$e8gXi�stt�u�w�`A��u�u18B,B���nB���l�xBpC�0l̀:8D9Da0D��D�!dt�DLn����EH
4F$`@.�G(�'LH�G���c<�e�>k�p�s�tIIHIkJ��a�ILK,�.����L��K,�e�hrstM��M�N�0ih�nt { �l�P:lcxf�k�l�n�s�tp�ÔQ�!�QdhR��R��R T��V}8V���t�V��W��fxna	�".�a�e��h�ido0u�y|Üc�!n��
�.0c\d�f�g�h�i�k�l�m�n,sLào�HhTt�$m�o@a���0p,�.teh��p(lm�pv�p��r�p{�a�pz	�p�!q���mq�!Xq��q�	@�!�r���s�"�s���.dk4�ns�s�Tt4
pt�!4S�!u{$e<i�S�[�!\u��D�@a��v��Xc�g�n�s���v��`�������w�$e�!,w���e���@iLi�����|�.|�a$b8cPdXe`f�g�i��kl�)mnHp��rts�t�u,*v�z�}�0r�}c~,�LhHk8~AP~�!�~�!�~z	pexo�~��~"�~�t��
�L.�b�c�f�g�h�n�s�t�z�"�n��S�{�m��h�����"|�{̀}L�\���.$d8t��4
0i������8��@�@e\odrT�0i"4%����l.����rt� "��\�h����ĉ��U�c�egnHp\xXTz�$d'"\�{�b�r,!q����gh�0���d0g8i@nЌ����w �5Tt�k�������	`%.�e�i4�k�m�n�o�p�y@~."��e
�>4"�{�.�i�l:{L�;"p��	�>��5�f�?�,P������Dchs tܙ����V�̣{��(aPkXmtn�pl�t0!,Ȥxds��	<����ld�k���إ����e�u���b�c�gȦ�d�ܦ���k�8�\��T�{L.,a�c	e�	i�	k�	l�	o(
p�s�
tu��y��ĩi�
$b\cdd�\hll|m��n�r�s,�u$�^@����B"���tt�v��������S�X���r��a�e�l�rl��h	o��԰3Df�m���M����i`�1H��	rij0�bD	ih	mp	n�vr�	s���ص��<	l\	nL���T	d��������g��th�>@���	e�����	s�c|��	dx�n�	r���(ܿr�	r��Q����	l��r�	a��L�r
u`�<���!
lL
r���
ah
e@h@�i�
o�
r���X
e$ZH"�\��!`
dx
k]z	��c�
n��rHf��iZ	|��
i��w�
e����
a4eXipo�r�u �4�{\�g�
k�
l�����5��Q
x�:i��t,�����V���,pLr��N"d��Do���pht��,��{�.c�rh��X��`����d�n�u8���a�e�io������3���4����U"<��u���������p�2��,$b�\"�$
ha
e�
h�
i�
o�
r,s�t�u����e��	\b��c��d�g��k�l�n�r�s��{<.b"�-��s���s����e�{�G'����t������t �mP��"e���b$
i4
lp
sd�<���
f0s��`�aP
eh
o��~���H
b`
n���x������
a�
��{4�:@����
��6�����
u�����
���io�
��dMsd��a�
o�
u�0��|���p	k
	n�{ eLoXpxtL�1�	�Dr(
The�rh
1X��
�pi,h"��n(�T�abc d(e0fhhpi|k�l�m(n�p�rls�t����m {�lnl'$^L�h�'0��5@e`f PnXr�_�@�p��t {�[s0!:�r�!M�!�a�$��	�".�e�f�gȅk�r�s t�&zH%Q�%��%m"&ah&{0&,�iot��{$����r8���&��.0�aPdXe`fhg�i�t`'�\(����(�xa�s��@)��)�X)�f�o�s"�*��+���c.�p��,�g8,���aceXidn�o�u�(-H
4-����l-,\2��-�.��i8r@sHt�-��:���P.�4.Pn���\/{�a�c�]o�2d2,�a@6:�b�c8�f�m�r8t 6��6�!�6:�h�k�7�P7�s�:�L<���&.�fcegst<Q
�<��<z"`��"��$n�=:,eHz���E�Qai�U�pp�s�Xm|a�X��Z$�a�e�g�iD)o�t�w��dZ�
�g�l�r��u$[�H[x[�[�\����(��\��\2.ci4nhr�s�\c]����e,sD]��]fDeTt���X^		L^�LsL_9�^��`lxs�_)�`8�Pa�l0ar�e<b���2d|�t�a���nLd,��h�i�d:�d�n$g0g�����f
iu���ghH{�b�c�d�lD0mlnxr�s�tH u�y�� ab$c�d0e�f|g�ht i�cj0!k�!l�$m�&n4+o�+pd�q8,r\/sd2tX5u�5v\Ww�5x�5y�5z��t �#�)�'�r�5��
�c.a(b8eDfLoTr\sT�tH^u�5�3up7�H9�	@8�0t�9��;^��aP<90G�8A��ds�J��`�.�a�sLK�K�b�P9`R��0�aW��U,�h�i@W��_P`��@7m\u�`��\2���x5��l,,e8iPl�o�r�s��q18q��$clxDo�y��s�zwhitu�{�|`c�|�xI��|��|p�>�i���u�����T��܀�t�����n��$�a�op<�,�1\���a�����bDp�����{�ac�ePhpbiTkX'lP�`@.ԉDa�e�i4%l<%mD%nX�r�s(t�au��w�À���л��&���c�$i�s8�
8�����l�n�@���P{H���opt<Q	h���r`Q�HV5V�rt�o\brК.
�8mlnxr�@e��i`��"P���dt|��	��,@�a�e�ior8�������s@��n�rH������e�t��iȥ@)e�t��L����ix����/n���>a(u��U̶,
`cld�\etg|l�m�np rpt���� �k�,��,����P*a�e�iH�{�� P�{0��i����e�g��i�oz�Ä�:���r����$�e�)�@�,�u��,l������ahe�g�i�no@�rs(t8u\Ð������T�l���h�i�r�x�o$�����g����g�uk���	��"��(���@n�t����ntN�"H�M�Aes`����tT����,�$c���x��L��0mPnp�����Hf`tȥ�"�{��ha�e��T��k ���h�k�r�s�ud���
�a epfbl�o br�sXtpu�H�8hQ��s������D�P�����8i8m�_n@r�����00h\lhn����$Tex���'�l�r�s\���w�o�Z4�b�r�!�i�<\��Dm�P$io p8t�Ô�5��n�Q�c�o�`�m�!,eP��Dbh$Lehs�E���m�,�adeHg$hTl|!n�o�r(s�à#�"�#�e�t\���b0zf�k�p�s0-u���LE�2ir��8vf�Pl� Q$ r(tH!0s,!$<eP#,ta�o�u�ì#�"�#��ldh%��$�����%�d'��r�P:����������)���s`)�i�(>�e�oÔ*T+\'sD)���l,E@rl+�eHiXp�t�,v-�$-Pa@rlu��.�".��t��m��-�br|Ø1:2�".�e�l m r�1w4>S`7���stB0B{�a�i����C{FtE,�e�K{D1a8 eD iP tX uh �M:lL0 r�M�4nPOi�O{�O5HL��` �T\{� c� e� g� n� s�a~�a{� h�c� m0���kj� eHl��w���c.�����c.!c!i(!t<�3.!s�� h���"��L�!n��2���	\�a�Re�!i�!l��n�!r�!t�!up!èj��\!u$���d!��!�|����!eȠt�{�!i��
P�$D�a�!i�!r������!nT�
���!s(���H"aCb�"d�"e#fH	g #iD#kd#l�#m�#o�#p$s8$tt$vTzx"Ð���6b\"md"sl��!���@7m ���l"�l$�ܶ$8r�"s8�{̸,�"e��8l�"n�"r�"s#t�x����X����"f�jh�CwL�{X����"a�!X9a��
@��l�#k0#n����:sP��,<#aT#n�����!h�$\#a�#e�#s�$��x#s�$��0����#n��#i��,�#e8��0��#i�#r����,|�,�#h��{$m`�$�#a$t($z��5p��ThT��d�4�$0$a|nhX$rd$s�����aP$i��:��:�,x_r���|��$k�$l�$n%r$%u�����$a4%dH%e�%f�%g�%i�%k�%l�%m&p&r0&s8�t�&u�&zL�9�����$l������z(Qh����%tt��X�!%s�t��a��{`%t��<%nl%rx����$X%s����>a�%g�Pl�w��h����I����"s��%n�%r(���"L�p�e�%a��T�MH���%i��{�%l��,�%f��l��at��"��&t��,$&aT&eh&i|&p�&u\�5@�L&r��:��`&m��c���t&ed�{�T�n�&r��4�����L.�&a`'d\(e�(gX)i�)k8*nX*oGrp*s�*t+v,+wP'zl��'.'b$'c,'l4'm<'nD'sL't,��
�����Mp�^��������l�'b�,
T'a�'d�'e�'f�'g(i(Cn(o (r((s��uL(vT(z��~�/c��'i�'r���|Df�'h�����)�'a@��H���b��'d����(rD
�,E.<(pD(t�EPH�/4�}@x(b�(h�(i�(n�(s�!\!���".lGn$z���(t0
�� Lt
�(t�(w�$�(e�(h)l)r@)s8�L$��|�"X���(�h��)���()a0)i�4*�+M�8)a�,�)eL)d�)f�)k�)m�)r�)s�)t�)v�^@$�M{�!���)l���P^��"�'�)a�Ee�)s*t$*u�r��<�D�)t$$*i*rl
�����H*d�$,*a�FegTpP*rh*s�{��D#.e�*i�*k�2p�*t�l�� Z`�*r4!��*a�*e�*rp>s̛u�!H���"��*iL"�*rl$(�C{$+l�C��+ll'r+o�C�
�'9 *{TfbP+fX+rt+s�+t0A��`�.l+cH�sxB��G
�c.L��K,�+e�W��$fa�+e�+fp�iHfp�r�+tT\��+r0^��^��Ja�+e�+i�_c`��k��k,�+a,e(,o0,rtl��0�g l,r�l��i�xn���".�,aX-bl-cx-d�-e�-f.g4.il.k�.l�.o�.p�.s/t@u0/z4-�n�,b�,m�,n�,r-t�Kuo��,a�,eTo�po{�r��s���a��b|Kg0�h�,spt�"�t�-e8M��t�-n�u�lu�-t�vQH-n�v��(-��g� /�,wx�w,P-azf�y{d-h�zr�-i|�|��Lf�-l|n�-p�-r�x̀���-e@�~d����jh@�w(�'�-t�����l�>�-i.o��$�-r$.s�*O�*��.s�������,.cHNmP.n�<0���H.id.stNtP�
��,hgsX��"T�x.u0�$�.a��<�lx�s0�Ex�h�c�.n,�u8�$�.a�.e�.i/t0Ow$Kz��$@��Nr���n�����/r�Q�����{p�,(/aL/iT/o4RwR�P�{d�{T�{�/a�/c0e�0i�0k�0o1pt1s�1tP2u\�\�5��/f�/n��4n��� �{�/t԰���/n��/e0wl��/h(K�����/r���/eij|Pc,0i@0pP0rp0sص�80d�{��m������H0a`0p���	НW����h0et���\2.dEs0��|0r|��0eܿrH�l �o$~j��0c�0h��p�0u���`��Z������0t�!�0r$s(1t����0a01e<1iT1o��u([�����k�����cL1z|d,��cQr��4�`1z���h1e�1o���f������1e4�{�1b�1lu���1a�Th�1i�1r(2u��lT�{���1n8�(2a2i���"`���2s���"��U2s<��82n@2r���"����{��H2r����,�2a�2e�3f�3h�3oD4r\4s�4t5u@5z�2����|�t���2l�2m�2p�2r���|����� ���lS����Y���3g,3i<3n\3r�3s�3t�3v�3x��3e�M`%h<���$3.P9e`��4�t��rh�H3r����P3et3s$��$l3a��^���X�el������i�3i�t.e�i�io��u���3b4c4p 4r(4sh,5�+!�3r\��3el�������f�s��M��|���04a�a84e�,�{T4a�4c�4p|4������t4��(jl�]m�4���4h0��"�����4�(
�H�a
,$�4e�4l�4s�X,������4e5u��4r�m��]4f 5g���(5n�$45e�Yi�Yo�YwTrl5f ����d5e@6��=&�5eL?��".�5r��������5��@��5�Q,�_e�5o�5t�S!�ShU�U���5a�Z,6w6zLg�f
6eh��l�6c�6d�6e�6l�6r�6s�� 6b�6c�7dH8f�8g9h`:kp:l�:m�:n<pL<r�<s�=t�v8lz�V�q�q�	8q
�6c�6r�q�Xu��z�)|����$�6t��:�6hP7k�ԉ�6e4%l7s,7tpP,H��7c�St�$7eP���87n`7r�@7e�7s|�p7ix7s̜U����\����7e̒��7p��:�7a�7e�7o8r8s 8t<8w<�)�����6.�7g�7s@��7nx��8�
x����)8�E8a���`�
h�
t���(8sP�,08ed�����ah8e�af�8ibl�8t��8ix8r�
�8g�h,,h{h$�8o��8e|bl�8n9st�7	����8s��8i8{�8e��8l&,l+�9t�-$2���baH9e�9i�9l�9m�9n�9r8:sL:t�1u�1w`7\9il9n�9rX9��;u
;��d9g�=�P<��x9k\3z<?tB
�9c0B{�9a�9elB�CxFtE,�9o�F{Tce�9s<H�lL:i�K{�9e�4o,:t�L��L��:.@OPO$$:aP$D:p�P��RX:h�U$������h:aHrc�:e�:l|du���{h�$�:a�:e�:o������7i��{��������:t���:n�����:e��:
$;a4;dX;el;f�;g�;n�;s<t�\u<zl�,�{�,,;aerH;s���#@P;n�{�,d;a�;e�;l�;r��8iP�<�;ih0h��$�;l��$�;s��ce�2p�;t� �`�;r4!r({�',<a�W���Te0<i8<pDbh,@flnxn��D<at<e�<f�<g�<o�<r�<s�<t�|giȅ�(�,�<l�rx��
����<g(��<n��$�<e�&	Ԛr8�zcT�e��p�!�$�<hT�����a$=c8=e\=sx=t��Vll�=h��ij0=hL=lT=sж�������kcp=eit4���,�=e<����=e���=n�:�=a�ir�=sPWt���l��H��܇b�c`>ll>nx>r�>s�>t<?u���=a8\bD?dL?e@�f�AgBhBixCk�Cl�|mxen�Co�Dp�Dr�DsEt8Eu�v�w4�z�D�P<z�5��X>s8A��l�g�J��Xb`R�U$�>a�>e�>h�>i$?r,?s4?u�U
�V:�V�>iW���@Wv�>k�>n?o���#�~��>.�~
?s���?n�W�HX
lY,Zc��,̶��?a�?b�?c�?d�?g�?h�?i�?l(@mL@n�@o�@r�As�At�Av�Ax�AÔ�,�qrt����������<��T��?t���
�?s��
l�@u�����?a@e�si�oX�bH�d��0� @a8@uH��h�,0�l����@@ah@cp@eL�i�@�������@n~\��|@dL�������@�l������@aAbAd Ae�
f,Ag4AiHAkXAsdAtxAul���@dAn�<s���������@d��E$�rl��0Mk�i���@An(�5x����j,�$PAt��QpAe��mL��4���Ac�Ae�At���8����
i����Aa�Ae�ArT�!
t��
X���̄�����,2,�[��rHBtT\��
BaPBcdBd�Be�Bg�Bi�BlCm CnDCs,_�a��c,LCt�bXBs(�<���pBa�c��xBh\"l�Br�Bw�g{��e8���krdnx\q,�q���Ba�Be�Bi�r\�r�Bh�s��s�Bn@ucCa|u�\wM�w��Ca4Cs�ytd|�������<Ca`CehCo$zppCu<� d����,�r *���b�Cg�Cl�pDr|Dy�1�1,�Ca��u6^�Cl�7�7$�Ca�CeDi8�Dd�7�Cn8[0�{A�0Da�d8De@DgHDipDm`A�pC#8D�DTDg���xg��g\Dn0EdDe,P��P����c�`���D��W,xn!�Da�De�Don��|�De�~���T�{�DeEt��zij4����DaEe��l.u��p,0,$EtT��,EeHEm�$����fH!8Xa�EbFc\FfpFg�Fh�Fi�FllGn�Gp�GrHs��TEa�HbqcD?d�He�AgMhDMixCk�Nl�|m�Nn�No�Dp�Or�Os�OtPu�w�H�t ��bH$�*e$Fs8Ft�#���EhTR#`QFu�%�Ft&#�%�0Fr��hFl�+,DFe�*��PFf���,��Fe�Fn�lol-m�.��/�Flt��@C���Ft�0�Fn�Frh0{�Fe�i�05�_��1���Fb�5GbGd$Gm4Gtp7��M8$Ga�;��<{�<$,GaHGoXGs�{x=iT=PGtA8A��dGa�Gd�Gg�B#�B���Gs�B�Gl\B,�GeE�,D$�Gs�I!�L#�Gi�J���Ge�GsHt$MQ,�he�P$�Gt Q(Q$He`R0�aLHcd�eXh`HsX�,�R8Ha�R@Hh\T@T�XHeP`{�Hh�Hl�Hn�Hs�`��lH��O��P��bl�clTe��d���Hg8hl�Hcl�@Hh�lr�Hud�
���̶���Ha8Ib�Ic�Ie�If�IgJhJi�Jl�Jn(Kr�Ls�LtMy��$t�0IaXIe�7ldIs�q##����PIbȺ��`%.�In�IsȼlIe�����Ik�"*#P����I.8����Z�Id0�^��b���Il����Ia�IlJrJs��,����(
���<�T�!�b8Je�rkTJs	tx�.
0�� ��@Jp����HJslJt���X��dJr��l�r�xJh �$�Jc�����Js�JtH���Ja�Je�Jrd��h�^̢d4��Jn���h�
�����JaL�iKk�v�>Ki��,Kr����a\KbtKd�Ke�Kf�Kg�KiLkDLsTLtxh���,LLllKu�y�"$�r�Ke�Ku8�1#|{��Ks@��Kn ��
��l��Kr��,�<lL�����Kl�$�Ke\������KoD#x�,L.$La,Le4Ll<Lu�{��U����{,�z��{��$LLa�Le�Lo�Ls|������lLm��tLr��{@�:#8��4����Le�
p�Lt�$�ae�Th�Lo�Lr�Lu�r$��H�A#����sMt���0�$Ms��2�����b$Ma�a{,MkT\��
8MctMd|Me�Mk�Ml�MmNnpNrxNs�Nt�b��c!��l�Mn�Ms�fcf��Me�h���t�nr�q��{�v,�Ma�Mu@u���MmD�Ԝ�x�Mc`x,�Me(Nr�w��Nd�ye0NgdNn�x�z�
F#�	$8Nc�{��@Ns�{LNrd{$XNe��
���� 3e$zp�Ns �M#��j(����Nh�Nz�CS#,��Nl�,��{�Ns�:P-�Oh *���Nc$OhPOl`Or�Ot|-�-OaOe�-!�28Ol�3{x3{0Oe7}dss6{DOf�lA��0DaxOi�Ot�Ou�D�4F$�~r�G�Kr�Q�
�P���Oc�Or�U$�<h�T���Otxn!�Oo�T�,��k�Ot��!�,Pi���T!,Pc4PlTPn`Pr�Ps�Pt$�!�HPsD�$$@Pe�&���s8,���L.|Pf�Ps �{�-'tPa�.z�Pt/L\/��Pt�1�Ted2Z#9l@6���Ph�Pl�Pnp:
�:�H	�c.�Qb�Qd�pelf�Qg�Qm�Qn�Qs���Pa8\b�Qc�Qd�Qẻf�AgBhlRi�cjxSk�Sl�|mxen�So�Spd�q�Sr�Ss�St$UuHUv�wPUy8lzt 1�'��,��=�H'8A���Qz`R��{�e�hX'l��$̶Rgd�kRl Rm,Rn@Rr\RsdRu����!ReH��si0��x�.��{8Rs��������".��eTRs,�Q4��(��T\��	x�b�Rc�Rd�Re�Rg�RlSnSslSt�a,�Rh�a�b��Rm�b�Re�Roh���c�c����l�k��q���a�2o`yu�w��8As���,ScBe4So|�sPSt\Su�d����h���<S����DSô�{،(���dSu�r���Se��� *���Ir�W$�So�F�
�F�Srg$�Se�f���St�e�Srxn,T�E�$	$TalTb�Te�3f�TiUr�lsUuDTè�,0Ts �^��<PTn ���8T����4��
@�
XTo��`Tl�c��xTd�Ti�Tn�Tr�Fv<����`���Tt��������Tf�s�@����Tl���Tlo�����Tb��n�a�Ta��Ur��T0�a8Un@Us�&n\/��={�U{\2.`UspY�H��܇b@Xc�Ul�UmVn��hUa,�bDVctVd|Ve�Vf�VgWh$Wi�cj,WkXWlXm0XnTXo�XpYrpYsZt<Zu�v\WwPZy�z8Vü5���c.�=,Dz8A��VgVk�E�xiP`��$Vt�`��,V����\Vh��:lVsԉPViD%n�A����̶xIf�Vn�Vr�Vs�Vt�������Vn������e4����".�Vp��I��t�hd����ViWl"
We8"�P#!2Wr�K�T\���DWePWsܚ�����<Wn��E�|Wa`�c�We�Wi�Wo�Ws�Wu�����qm�WnH~�,�We����Wt��.l��n������i0��Wn`����,��~|��Wt�����WaXp��
Xip�!
p,DXd����$Xo<t�\u�!0 *��LXfpXm�Xn�Xs�Xu9��<�:��xXi�Xt�=��GN�W�Xa�Xe�Xf<h�XiYoYs�Ww�Xn�XT\w�^{tc�Db���Xn�e��jxn,Ye4YiTYo�<r�|��DYaLYẻ!\�,��{�\YnT���
dYa�Yc�Ye�,h�Yl�Ym�Yp�Yt�Yu��zl�ij�������L�E�Ye���(To�Yr�����)�Yy�����r��Ze(Zo��. Zs����!
8,!HZeT��0Zr�-��Ut �PlH��XZb�c[d�mf$[g0[h8[kH[l�nmX[nx[r�[s�[t�[u��dZa<\b��ct\d�\e@�f0ag\ah�ai�jck�Clcm0cn8codp�q��r$dsLdt�du�v�fwhz\Ð'�*e�.(�,[r�/��3�@8��5��@[el8A@�ap[f��i��l�C��J��Xb�c`�m�[t�z(Q$�i�[r�Q�`R�t�U_#hZ�Z!�[b�[f4�g�[n([�X\d#P`
\c$\h��m,\r�`���[�d��f�@aQ�b��f����g\�m�l�`\� 6�l\e����H\bDp��T\��6���EH7a�\�$��D����\�t�<�e̶���\b�\c]e�f]i�]lКm�]n�^r�`sat�wxH����\o�����\hk��$T����rk$]l<]nD]s`]t��M�ra4]e����Q��)P]tX�)��,��{X]a�]e�]r���h�	t]d��|]n�]r��v���l�r�����]a�]e�]i$l�ss�]t�uH���n@r��� �nH�$�th����\E.(^a�tn�o@^sL^tt^z��h�, ^c��P�m���4^ep��X^sx��54���`^r@�,h^e�����".a�^e�^g_h8_iD_kL_l\_n��o�_q�_s�_tL`wX`zl�@�b�j#����^��$�^e�^���s���^bt3���:_l��)_a���(����t���,_nx�r��$D#.8��H��p_e`��,�h_b�_it�vd�
,�Q��a�_t�r#���_e����_g�_n��$�_a`e`i,`r���`�M�������_e���_r�~�fn�uz#����`u��a `a�S����8`i��,@`ep��d`a��P|�:����l`�4���h c�e�`i�`k�p�`s�`tt`ü��P��$�r�`u����`dh��`n@���`eip��t�[��aa��^���&r$at��S0�$as�rPae���<aa�Dar2,tae�ai;:`7��lan�AT<?���ar�[�!l��rT\��
�aa�ad�ae$bl��m<bn�bo�br�bsct�c��b�ar�g���jhbs�c���arbs�h��h���c.�r�q��be�xc\br�w��0be�yfT�hlbi�bs�v\yM{��zdbtX|{d|�xba�be�bu�|5�|�br(}�#}�bf�}T�bp(j�D�bs����bkԏI������z��(����bh�
��r$ce���e��
 *���b@ftcg�'h�cl�cn@�o�cp�cr�cs�ctl2{�1,lcl6^P>58>�cr�=,�ce�:���ct8?��H�eA��i�cn`EMlEM�ce$K��G���cz�Kg�P��f`ldn TH
�W
T�Q<da�hDdk�#z�ܿ��,pda�de�dh�di�dr�ls�è��u��~�:|de���dhH����!�dr��deгo�����s���dn�P$c�a�deT�8eaLNbHecXedtee�ef�eg��h�ek�emfn8fpLfrxfs�ft�fz �P�${@eh@�k�&dei�|{0,lel�)`�����er|,�ea�ee�Nl�eu�nt0��/�en0!��$��x�.fe��f�g�lx&s<%�H%�en�&�|\e܅g��t�+'�+��$fi�+��,ff�,8,��Dfahfk��lXspftl.�/u
\/
d2��5r@6���bP'r�E
�faLge�gi�gohu0g�FTE!�fc�fh�fngrgs�F��G�lG���fdgg�G��GHQ�HZ	lH{gl�H��$g��g�h��I
�H��Dggpgi�gl�gn�gr�gs�gtJ!�"g�gsTJl�JQ�JQ(KQ�gg�K��LQ�L
pN�DM���gr�gt�N:�N��Ol�O���gr`PP!�gr�PQ�Z$,ha<hiHho0�dZ�b��a��4hs8c1010432230010042404412020410324210540040006302444323226040041231004241312442103101212124300200320302302324343143220321062104341024324100111421210041144000142110234001131221402211101041460300020243054000234613423800241020402148210002020800000003424
80000060004004112540402015244141430104211224202405100611221230065410401025013012325243104403001128420226108420044302223345621012252104300080080020000241034040122301000210132301624221016024260106200013402630212228000344006202424120304325004001132330324063223640134520352680210610224420022345012343110140223000121030340230102321012543010012121252161012141200050101200002020032100014322200344000023100014102023003022060003120100000230112322400010000222322012040100400210401010040321042234223242303000013004400441035432834000	800000000
8000002000602003400403002221044210100412010032043411032034021021042210040011232264252242214221220300031422332121320021202141020324341045004110204104003002021230300100443200041000240042140403300202102103443034304363002003210330436400023201033212140124101221020102124302020432203210200114200154211003140210521610321002110013210400344321032120321103021011202121030041021430321210213122132021212234050100100000050007000021103021210203010001010030425602103021432006004200012002124020144431024142412202100204123222220442022000104340022110430323014310006100034033412101220203002533000102021420000040000043205000034000416102002210100030241000223000430342010322210124104302010102043420002302224322134301342022234303430020024204222043020020412102100010400341032322310442222040020021021042022422023200430303224140002022432011100210121200220240021342303432400400412421124042204234104021252100030232236800022140442201043223234304341021210434300301040046010200040440101012020422243324102002252001003422061030304041021002402244001024520132341021007404204122460410322104022123212303201061242203014210000210100304030430402220043254421043032143400020040600003361021023060006210002544100002123421230302202322103001030040301202000403400403024102102132011132113061020234242240306500010030200230010213600000610010414102300003402044100021211442341444200301323230420010300210402000104300300010030010040010010410342001001030200030210030041240120104010400423043060000010600020140121103003200201230201214163220121210010022044380003031104200201210402001300040121040001004210430603000101034403242004000000201200201100221202011020342010322121030041424320100320122340004300100212000210300082103241024304304043430434322102021203210410210210214200620412402100102120102102324120410410401065020210214200033421043100214404300000034320041000000400002043032106526041402541012201425212142121004300000363321434252040430452202425404060234200232010230020230200401202314340102520040125001034010045010025210210103032021424434400020400610411003220222304212220002140061440222320203404002021522221412214321002062241345243454322004320006050000602241040423243032042120104344021620204422443412031040463352452006012043300204524210334301003210230262103610100001203242001201040001040103060400300321010020040300160032024100033432300000650000032210040043223200040042043010004140010232541030004002310032021060001221203002130322234140412124363224410540124120410410344301004361010011020230300125004010001020020010454300002240010104321212443412140100232142343210214203010320404344001240204000410100430414410454872102340103000432400210003443431203102343430000264033410332201000203200334630000161221040300004060000002434400010221440421210123000443220310024200630003041132462204400011020114504002506521264220344102104324102142200222404034034443400102500104420360002500014210430014340700063414000010041420054210410010412004020403004003244301240020304685210212542132430001010320143004144540320442121024034002104144104304321210406402524310103222103400002100200454450003223223221322000654322304122301202304106500342000434232400340406543443005400004100010264210300602102222520202101016502003202250204023210301234105242304304021210610600304102164030002020301400362040032032052000630100040101300302620204243121012414602410601006504202322221121003002060000104004030023242340412144023034222530023020500410340210100213002344014030400400414430400500000400002630002043032065000203001460002001600120416322302106001004414041321034343410231234011412443011041210234230245080102421014341030443214205003263443412410450304002003300014302122040064002242010014104001021101203320444102104200410424203020102120100036344361334044423040034044030050004102421260010402342326304324101220030234300310002206122130023330022125221200002322422300040610241442303006300043022132103204000325432002144304010200000030320020124210400400040030604323242003040203040102102006420241021042210432200200030220103041223200004302103021441141043030064414122122410021465210200300041010002101242020212223243040432300000020031021021221043421063210021020021021120232164340402424102004002002304003087080020012430302040000122103005400101041043230324108300222004230300034000101201203200006000021020340223002343003000010200104230100630300030124341006524564234242032300211221043400100016600300600610431404140105600032432414124022043010302304243244243102004542100242101000423206022441002021424203033000306306040021045420102634302320431623656200041110314144143241252241241003214041234410142240043440063240012044551202022020042403202000200002430432430230412010323204102124102323230011040100400212400421520320500124430430400300020010003000242321200210120010300080020042228041021432322003241421630430420214202025432014324122024304330410401401324333107021263261021110412100404000430240430120302210600020560030065600610521004101042326146123644125401062134123212343201302200413034325010321040245021024242000526123341421321020034503034102541045240041043002043240650600201220650224202242302000811210460100454100101454450410163020141042142000204280330054133461006242323213201014001020061030222141001230121101023411032214251321423430004321002010420065232030032061042100083000000144420403216604044003202224300434633200010402232	1000871002000643020040300043000202001200430610210120200324522022232022004002465000214410010040320010403625004306111233003310030140304000430260004106050432232020421412541003230103011032124432146101004123223610430124400320430202302123203223043432001002102100640002201004300210400141042232222344041210400161042014022212222146003000040030206440042430420121034020021220011100200234200420400230200232200410400010440000102104123261041041441200060010230400000002021200432100010030043401041220320303222123420322342114452104304105201004120241020450410430004320061041000002000110613030066342214103000223410034221023230020161043430022100030203243001042104105400410300200202224302234404003000212250000252400250410461065400061410320021023012340410200142023434123012020245423030232320434202300040454200430061054201004034000454001043020100450010434421052402002432101402202502002042102540002002202234322306122232102304002034122300000230010023004102300430322365044060034201220003005412032300032020010424340410423001206003204323030243010420002001000140321430232642004306324102001004213050841220030223430032324345240030414424043021441041241043440043040002004004260000000406000400220100120604623043004342204021610403164051030241260141040210034000130004051000632210542002322203006103412042010030201006301054400432320344210600200400221242345000103023444000040041241414421003441004040201322360610441423412212210300430300300040141032321321240343210400500330105300036440014143032101030000322302165010002054020026020020021441200300380000413061204444461212040301050200050000610100400142087010010610100212124123133400121000004300120600204102120PK
!<��#��hyphenation/hyph_de-CH.hyfHyf0tP�����'-4�������$�'–’-111001�c��������.�aXlb�c�d��eT�f�g�1h<[i�jܑkT�l��m,�n�&o�Sp@jq�jr�s��t<u89v�@w�Kx0PyUzD`�8 �xaDlLo����btc�g�i�k�ltm�n�p�r8sLtlu�x���aLbcDd�ef�g$h�i�jkllPmxno�pXr�s�t|u�v8w�xyHz���!`"\ldr�}$~X&|#��lk�-�+�n�r�u.�.2$1���s84@4!�a�2���t07%�6)�r5���be(lHt�7H:-(:n�9!e <2,<��4i�;!<eXs�<7dX;d?)`eL=��hp�t(�>�?!�s�AB�e�rh@���d�g,sltD�F����k�A�n�JB;�iTCNls�RDiDDW$t�,[PF_DiPp�2�<.�F;,�c(G��Xg�F)`a|h|GhglI�stM)HM�eJ���i�k�ms$t�MN)�a��p���c�N!�aPu�O!�cp�p<PyiDP!e�S|Q��0tTU}UDa\hxV�YYdd�f�sDZ��s�N$\��c�t�\��\�h����]��sd]�e_�d��_���mtD`������Xi��h��sܾ��q��b�qJ$r�p��0ehrXl��<e�ouuh,u��`a|e�g�uy�u��b�u!�a�r,�ȅ��.�@.)�e}���g�sE��~�k������r@�u�e\����s\�����h���Ĕ��(b`r�t���0a�ePipo��t��xi�m�M2HM�pn���LC����.�s �)�ehV��u�)�l�����a�ior0s����n���<.��l�����n����eP����(k��Ԥ<nX���De7ut�u\oԧ��dm�p�r�������e��ܨ���fD��L����b������H������r���e�����b@hTi�k	l	m 	n�	p�	rDs�t�u�v�x�8��,iH���4e��l���Letn�s�t��!����la�ed��l��gx���n�����p�t��)0���u�r��`��e��u��)�i�����b��BH�	m��}L	dl	nt	t,�p\	.\�4	r �)@	ed	r<���N��4�}��|	i`�l�%�	tx����	h$��	c@�%�	e,�)�	r����	b
dH
e\
fd
il
s0z|�
n 
r��)�	a0
e�w2t�2�w���(
i@
r��2���T
c(��$�*���N�
t��.��x
n|�5�
i�
r���
e�
r��:ț���
b�A�!�
.e̋u�
t\��
t ��
i|�J,����
.����n��U����n0�)$e����<p`slt�u4�!Xt@�|e�h��Z�_�����s|�}�g�t���D�d���r��et�g����tx����i<r\sT����e�i�s�u��J��J(o���0n�k�Ha�Pt�12`h.�n(��peX���l�s�q2����*u\u�c����s��|���t����a
d0
e�
r�
u�Lp0h
r��
aL
e,�� 
n\
rt
s|
t�
u�<yH��T
al
e�yxu x( ��i{�i�
sli)�
e�)u�
fT)�
f�';�
id0�0u�
s�.���
s�2��2!�
s�2���
t@1��f@lTu�1��a`e�o�;��2��8s�5�5Lt�6xf�r 8��7pe��`;�n|;���a�il<��n�<�T<���np�k$I�t4Iu�e�G���m�Z�<[���a$mPn�r�s`t<g t��aHm��J(�4e�u�lv}hepk�n�u�w�@z*�z�z!xe�|��������e�����a���L����r����o���p��l�����b0i@m`tܑ���a�e�i�ln$o0s@u0�p�l4���8p���VpLod�)Ti�"�H��lm�!tuę���n����n�r�7̚���sĝH����e��H�u�i��e�
�
�e���i̢}����f����p0����� ���8s��Jt�)Lr���TaT���`e�i�o$u�h�����f�v��<.��)�e�!p/���n��)�i����g�v<����r����e4����rH�utL���s�������0dli|l�s����8a�d�e8iDm��������taDT�0�r8����t4��(�������e�ln�������a������l w���h���c����s(t��@��8������0t���L�)T���Lc���Ts���`�,����i�o�ul�
���e�xp���b�c�s�tL}����D)�h���!���l�sd#�|(<(a<e�&��bXhlm�p�rspz�(�lM��0uDs\/��Lr�5�5��da0Z�<xr�;���e�=t=���a�t�BN�sT��D�J�e�t���8��F���g�F���lXF�ae`r4D�t�F�8nTr���De����,d\��G_�F��LePGuM��Lho`VV��|e�t�S�r�S���a�f�h�o$slPW!�h[��\��1�<b)�a�a�ks�c��cr�ctg���@z,sLy��4b�c�i�l�j��@e�i(olèz�z%pa�z��xh`|��|�t�{���nT��eh}���it��]��s̆���ac�R$0a�eX��t@�uh�&\��� a@m��t�u8a�R)h�Lc���Tshs��`���L��8���|b�c�2'�2���r��-�et�1�k �ئ���iĤ���l���	�ac4elh�i�k p,t�u�����h�6���� s����(nPr\tx�;����HuĶ�Է���da�uP���xph����m�t�u,�u�eĝ\����e����i�^@�^�i_u�e_�l _�e��%�gh���e0�i��Lo��FD���8e����@r�K�Xr��`e��)lh(���xc��M�����ex����l�nt�����aeLh\i�o0r<sXu��R��X�l�����kX�u��)o��T���e0f@nD�x��,���8o��xx�����Taxd�m�s�t,���t<.�s����e,>kP����]�����et9a89p�n����i$����n�pw=fd����l8L�H�`us$ed�;$iP,���Di�	��Lr$j�r���de<��pf�l�m�n4r�tH}h2t�i�)�e��md��o����!n�!}�adeg(iT"D#r�#7�$5($ t'}Hipo�s)\n4)v )��Tst�y\)hm�p<��)�,.�-�a�r�/u$2��1���e�1���b�;|�;���e�:�n89���eoD?d�>��r�A��A��l`@hPs�@��(ale�i�o�BlD�<DXs�C��`g�i��J�Du|a|D�t H�G���ePJ�t�I���r4C�C���npJ!�e�L��K���e�PP���l0P��a�LXus�W��$t�W��0iU��<e`i]�\��Xe�n0|J�]�pt`]��xs�u,b@cPg\ndr�s�t�u���a�b|#c'dL)e*f�+g�.h$1i�2j�2k5lL=mh@n�HoIpJqJr|QsUtYu�^v�^w_x\_y�_z����8e� u|#.�+Hrh@MJ�xa�f�txJ)M�DPN�S�|Q���t|W�U)�sYy�_D`���T���'��H��^�Xl8 a� d� e|!f�!g�!h�!i�!k�!lX"n`"o�"r�"s4#t@#u\#wd#yl#z| �l1P id uPm�Xm��H sDZ�o\ fp� u(p��p �T#�0j1�p�|q��p��	� b� e� i� l!r@!sl!tt!w`!��q!�r�Dr��� l(s�� aPsu,u�!k!r$!z������0��w�w!,!e�v�4!sP!tPw������X!��w��w��w��!iX�x�\x�|x��!n�!r�!t�{�Py���!s�yLz�z��z�"a,"e8"i@"oP"u"�{�"g������|��"�H"�\{�h.|x|J��|1�|
}1t".|"f�"n�1�}�}�u�"a�"ø�����"��"�t���|�!�"a�"c�"i�"p#t#z$�"�|h�(��.#a��2�h��2�9 #i��!(#e\�1L#r��=T��t�C��Ḣ����	t#a�#c�#e�#h<&iX&k�&l�&o'u@�XH�������#m<M\��#.$a8$b@$e�$f�$i�$l�$m�$n�$o%r$%s0%t&u,&v4&w%�؊($l0$u�2S5y�YD�T$c\$il$rl7_��e(<k���d$f�$k�$w�$ü<k=r=���$���z����|����������$.�$b�$r����_<�@J�����$�$&�H�|Qh���%u���X%ed%o�%r����S��D%g�R��L%r@Ul�U��Ul%m�U��t%u�U;�%a&i�%��� �%.�%n�j=�%e@��%m��S�%u(V���%�t"� u�%.̋��UJ�%tȏ&f��_(��0��8��X�H&nlv�=p�-P&.x&e�&m�&s�u���p&n����&s�|)�&u�����u�)�&e�&l�&t4��l��ԑ��&a ��`��&nT��`����&n����d'a�'c�'d(e�(f�(h�(i�(j�(o�(p�(q)r()s<)tD)u�'Ì1�Ĕ��	\'.�'b�'g�'i�'m�'n�'p�'r�'v���@�)�����'a�=�|��L��t���^��������'�L�)x����P&.�a,(f@(iL(n\(rt(s�l�$(i�=��8(n��=D�����T(al(iH����(p�(sH�P��� 8�%X�p�(e��Ԥ�(nL��ԧ1�(b��)�*�X|�/(��(c)s �;�(e )uЫk8���%4)z��h���P����6�)b�)d�)i�)k�)l�)o�)p�)r�)t�)w�)x��D�l�`�)t�������)a�)e,����:����>����)o��)`�)���H*kP*nX*r`*th*uT���
�)ap*e�*f�*i�*l+rP+sp+t�+u�*�1x�1�D\!x_���*c�*n�*r�*x��I����*t�O����*l���B�*u�)�*l�2X�l	T�1����*����+a8+e(+�T
`
�� +�@+�p
��y�!H+a`+p��|)P!h+a�+e�+r�+u1����+i�+r@�Yh����+l`�+el�9h1X���+r ���+r�$,ad,d�,e�-g�-i�-l�-n.rH.s�.t�.u|1<,bD,dL,rT,u,�xp��u�u\,ix,r�,u$),�,i�,n�,r-s0!�,a�,e�,t|_��,b�d�,u�!jHu-a���<��,lxq,-a<-eH-iP-pX-s�-tP�u�2�4-l���*��d-e\����w��5l-m��t-ep r4 !�-l�-r� } "!���-tH"�-a�-�t"���@#���-�8%�-e.u�%h.D&��(��';.e,.i4.oT)��)J�*�d.m�*J<.al.c|.e�.p�.th+2�+H,��+lt.tp,u�.o�,��-2�,�.i�.r�-�`.X�.h��)T"��/}�.d�.�.n�1��t".0/aD/ed/i�/l�/nH0o|0r�0s�0t�0u1w1yh0�@1�</t�4!�6�X/r���|;��P/h$>p/n�?�DAAJx/a�/e�/s�/����A�/i�/r���HB�/h�C�A���/��C!4)z�E!�Eu�/a,0e80t0��1���0��F��F��0eF 0r@GltIS�G��@0rX0s�I@J��5��`0��J�Jut0a�0e�0i�0tPKy�0sDL�pL8N�HN!�0a�0r�Np�N;�0i O�PV!�Q!�0s(W(Z)�Z\]��Z1n<[��
1a`1dt1e|1f�1g�1k�1l�1n2s�2vpb-l1t\aT1s����b��h��j�pm)�1.�1e�1upnp p�s\p���1oPw�lv���1d�1e�1g2s�w�,yOh]�{��1p���,2aH2cT2e\2od2s��u���<�42.�<2h��H��Nt2e�2t��h.`����)�1.�2e�2l�2s�[�>�@���2e�����2o���2tH����2kܑ��t".<3a�3b�3c�3d�3e�3i�3l�3o�3r$4s@4t�4u�4z�4�����43d\3ld3ml3rt3t|3z��=4����=d�!�_�p�������*���3f�3n�3u(����ę���3nԜ1H���T�3i�J������4a4o��ܣ��4u��0�X04h��h��=��!84a`4bh4e�4i�4r�4sp����t4i���0������|4kȨ�4i$���B�4t ���4n��
������4�ܪH��l5.|5c�5f�5g�5l�5m�5n6r<6s\6td6uT����4a�6b`7d�7e�8f�8g9i�9k�9l�:m;o�;p�;s�;t�<u=y4=z�6Ü:�#����t5hܬ���5e�,"��Rح�5i�5p̭'��T�"d����5a�5g�5s�5z�IT�,����16.$6e,6m46rt"�2�N�O���L6iT6sİ8�Od�� �p6g\�>���6mز��x6�d;�d�rDr���6n�6s����6i�6r��)�6e7l$7o07rD7s�r���D,u���6h7w��J@�l7iL_�\/1}��7h��<7u8���7��1����L7���!t7r|7uT7�h�����7b�7f�7h�7i�7l�7n8p88r�8s�8t�8u�8x��P0�V�������7n�D����}8d8g��\�)8s��d�_(8o\�bĽg���08fP/hX8l`8mh8tp8zp�O��m��t�z(��x8k���8a�8h�����ȿ��8r����,�)�8r!� �)�8i9l9o�#�H"���8i�k���<9eP9mp9nd��l��(9e,�u09n�<%�1H9b��p4�y\9l@���d9a�9s�9v0��l�� ���9r��)�9a�9o�9s$�2���9h��BH��X����9b:r:u��!�9a(:e�:g�:i�:o�����p�2|� :cH:nh:rt:s8�\���@:d���|�)T:aP���\:f���� ��|:�l*�:����f���:.�:s��u�:r��:e��pg!�:.X��d��:b�����)�:b�� ;b0;gD;pX;rX����u��)(;a<�|���<;e�g����P;c��p;sdR�c���x;.��)�;e�;h��������l�;ld�!�;k�;u��������;m�;n����������;k��!�;a,<el<r�<sX<��������<g���� <i<<r�H<f��J��1����P<�XW�0�;d<e�<i�<ox���<c@�D��1tu��J�<e�<t��4���<�����<�L��<f�<m=r�����1�<b��� ����l[�����=z����=r��)(=eD=wl�l����t".�=a�=eH>i�>l�>md?p|?s�?t4@u�=Ì���=b�=d�=gX�����(��<��\����=�H?���P&.�=b�=n>r,>s8>t@>w�������������=a>i>u4�L�����p��$>hp��`�����1.d>el>rt>s�>tD���������y8��|>a�>ix���%��1��>c�>l��)�>a�>e?i?l?o0?u0��l��x��>i4�1P��>n���<�c��?d����(?s@?t����1H[���uP?a��)X?ft?r\e��X�?c�����?l��?h�S�!�?.�?a�?e$@r,@u�?�0�)T������?�@�d�1p����?l@r������@n�1d�p�u����	\'.�@b�@c�@d�@k�@l Am(An0As,���<@a`Ab|Ac�Ad�BeCfTCgtDh|Di�Dj�Dk�El�Em�En�Eo4FrPFs�Ft�Gu�Gw HzLAà���(d��@i��R�8�����@gAip929�An�=8�=D��T��8As���@A�,F��G����lAu�=��)tAh$�)P&.�Aa�AeBr4Bs�Bu�RT����Ac�Ar��t����At��J8��Ac���As�Ax��1�����B���;B�8�2� BsD)(Ba`Bp�Bux�L�HBstB�XPBa@�"H���lB�t�uP�)�Br����Bc�Be�Bi�Bk�BrCt`��/��t".�Bf4X�:@���BbtA8Ch��(Ci8CtDCuH	��	F�	!0Cs�	y��	!
LC.�Ca�Ce�Cf�ChDiDl Do(DrDDs
S�J�
�Cb�Ci�Cr�
�$�J�
�Cf�Cl�Cw�Cz`�Yd0�J��Qp�L!W|���Ce�Dat"_=$;4Da��jH�<D.XDchDpdh�,^px`Do��
��Dd�De�Dm�Dn�
�cpg!�D.,���Ds�
J�Dr�iho�}�Ds4c��)P&.Ea,Ee8El`EnlEr�EtT�En��1���$EiuLEè������DE�X��p�XEo�;|Ea�E�ܣ�$��4����E�glH��)t*�Eu��Ea�Ee�E��y$,���E�Xy��p�EdFlFr$Fs DT����<HD2T��<F���xFc�Fe�Fh�Fk�Fp�Ft�FzDF�t�Pu��Fn�Fu��1��L��Fr����h��Fa�Fr��h5�Fe�y�)P&.(GaPGe|Gh�Go�Gr�Gw<G�x��� Gr�k2���4G�<2���HGihGn\t�HD`Gn���tGe�Grd��� ��Gn� �� ��Gi�Gox�!�Gn !1 
��!��Gsd#�#,$OHe�G�X$��Ht$W��$)HbLHedHgtHi�Hs�Hw�H��W2@%��DHi\Hs�[�@\��%��%��lHn@_�b2 %���H�T&��Hi8c1�&u�Hi�Hp�Hr�Hs�Ht(1*�;t=4D� H�1.TM�St".8Ia@IehIf�Ih�Il�In�Io�Ip�IrJu\IèS�dX_LIf��L�1�W��TI�[uxIa�IlH[u<\�<]�D]���I��\�It�I�$^��`��a��cX�a�It�d�0d)�Il�Iudi\e�di�@ju�j��t".xJa@Kb�KcLdLeMf(Mg@MhHMiNk�Nl�Nm@OnXOo�Or�OsDPt�Pu�PvQwQy(Qz0K�tj�J.�Jb�Jd�Jl�JnKpKr Kuctk)�Jt���plH�m��Jixn�LpI�o���Jg�Js�Jz�p, Hg�I��p)Kp�p�q�4shs��(K��P�pt�	P&.lKa�Ke�Ki�Kl�Kr�Ks�Kt�Ku�txKud _u1$u���Kc�KnduW|x)�u�v��Ke$��v���d�v�Kn�C�v���1y;�Ko4w)�KrLy�
@LaPLf\LgdLh�Li�Lk�Ln�Lr�Lu�Lw�yX<{${HLfT{k�{1pLrL���|�{��xLnL}k~�Ls�~�~��Le��y8���La�LfP/h�Li�Ll�X��I����)D�XMrP��Ma��0�uԃ! Ml8Mn��)@��̆�dMatMe�Mm�Mn�Mu�)����ulMn�Mr�������Md�Mg�Ds��L� �"(��Mtp����Mi�Mt��I|�)��1`��Ml(Nm4NrDNu8�)�MadNlxNo�Nr�Ns�?Du Nt����Y���<Ne������PNg��XNa�1��pNr0���;�NiL�*�Na04hd����Ȏ�Ns��!�Ne�D����N�x�!Oe Oo�N��������Nk,�Or��D��OrH�%0�,On��J4OaPOe��y\��pObxOd�Op�OrD��ԓ�<��`��$�X�Oa�Oe�Ohpl.`����Od~���y�Oǹ*�OeĀ��)L�!�OaPc$Pe,Ph4Pi�3�Pl�Ph�[$yh�)<�y`�!<PelPhxPi�Po�Pr�Ps��_X�dPe���Pn��О=8�����Pm�;�Pa�Pe�D�9����Ph�PmP�����8�1��X`@��)�Pa���V?P��Q�0�)@QeHQtPQwQì�=D�D��l�4"ئ��XQaĤ`Ql�Qu���lQa�QcXRe�Rh�Ri�Rk�RoSp\Ss�St�Tu�Tw�Ty�Qè��Qs��H�t����Q�P����QaRh���$Rc���Qe,Ri<RlHRmPRul7j��DA��J4RaЭO��i��UlRbtRm|Rx4�k|�y����c�Ri�D���d����Rgh��Rn�Rs�Z�Rt �J���̼�Rp��l�Ro��`SfSlSr��@�D8�0��4Sh<SiDSoLSu@�uh������"��y��!TSa�Se�Si�Sp�St�Sì�����|S���y�Si�ph�y��lL�J�Si�So�Sr�Su������d��i��=��Sa,TeDTr�TuT�������T�8�n���$Tp<Trd�1���dTa�Te�ToxT�X�t����\Tul�z����pT������������Tl��h��Tm(��B�`@�Ts��)�Ta������Tl���Un�����Te�����)
TUa�Uc�UexVh�Vi�Vm�VoWr|Ws�Wt�Xu�Xz�U�0��x���LUb|Uf�Ug�Uk�Ul�Um�Up�Uu��),�L�I��Ut���@��p����Uf���Us��������?�W����T�	LC.Vb$Vi8VlDVnLVp\VrhVspVx�������<g,��,���0Vi,�\����h���TVu��tX�����Va�Ve�Vl,����������pt���Vu������Vs 62�5�Vn�u�Va$����Vm�Vr��WtL�I��d��8WaXWehWoLWÐq�����0Wt������DW�l�D���`Wm�uPutWa�Wc�We�Wi�Wpܭ��Wn0�Whtu�WtĶkul�\Xk Xn,Xu,!�Wa<Xe\Xh|Xr�Xs�Xu��������Xg���1���4Xi�i���HX��PX������hX��;pX�	\	�
��	���XnD�Xk�Xw�
���Xr`
!�Xe�Xi�XoYtYwXZ�`[�)�%u�B�l<t".xYa�Yb�Yc�Yd�YeDZf�Zg�Zh�Zi�Dj[l@[m�[n�[p\r$\s ^tx^u�^w�^x�^z�]�%X�Ylt�\��Yi�Yox|0��Yh�Qh@�Yap�Yt�p)�YrX�)bZnZrx}�1.X�,Zi���ZexL�L��$Zn�u`Zn���8ZapZe�Zf�Zn�Ztx��W$hZ.�Zh�ZrdWH�Nk� �
�\!hZ.�X�ZeH�j(�Zhp��Za�Zu@14u@c[s�)�t[s�[e4[�,@��,[�d2`[al[ot[p|[s�1HX[l��)� B� B�!a�[a�[e�[i�[u�[z�!D#y�X($�[o�!t&cp&?�[t�&2'�[r�&)�[e'T�.�D*\h�\n�\u*u\a�\c�\d�\e�\f�\g]l]o]p$]r,]sd]t�]z�]Ì1��Eyd\.4�l\eT*x\n����*)�\hЭ��*�\m�\o�D��$+�+���\p7w�*�\r4�����4���+u����+�]r��L,ND]aP]e�
8,<]g�,h.�,1-\]a�]e�]i�]r��D-�|]c�]rL�T-���]m$�h-���]e�-R�-;�]id�2h*���]��D�-�����]�X��R��^u�R��^��-)L^eh^op^s^�T���.�8^gl.��@^n\^r�.���/hX/��/��0%�S�H1X�1X�^w�1��1�89)�^a�^i�^r�^s9�^t�9�|=p�?�@��@)�^i�G�_e H�K,_a8_eH_ipL��K$_m�L�M�LM��@_sP�0P��T_a|_e�_s�_t|�(Qt_uh�`�S�_ixTU)�_a�_o�_u�T�T^8`����_a|`b�`cad\aexaf�agDbhci4cj<ckdcldmXdn�eo�ep�eq�er�gs�ht0ju�kv�kx�kz�h�������k��k��_���0��������&�4M�TM��1�Xl�`l�`s�zB|��D��`e\��`e�$i�$l�`r�`s�`u����`hak�7!H�P�����`pȏ�p�-ae�0aa@aiLarTasĔu���X�8aa ��������B�)dalT���laf�al�ar�as�atl	���PN�as����ae�al�an�arbs4by,�,iH"8%�'ba�'J�*#l.c(bt�-'�, br0P,@1�1��<baxbe�bi�bl�bm�bn�br�bs�bt�0u1w�6�$>DAAJ�ba�be�A�bb���{��be\D�Fy�Eu�be�Ju�0i O2�Q� t<[���bmcs���c.H2c,ck����ܑLclTcr�B����T���\ca�cb�cl�cp�cs�cu���)�ce�cl@��X�)��!�ca��:��?l��chd�!�ccL��d�D����cn���ce����di?l0dm<dsLdt�J��)(da���p��!Dde,���	P&.�dd�de�df�dgeiek@enLes��9$�)�de�dr���)�dn$��8�
<�	!�de�dl�dr�ds�u$d�H��de
���)ee(el0erB�XC�!8ee�G\ecleet)0�deh�7�&��xen�S�S���ea�ep�es�d0d)�el�er$e��)g)�ec@jtjB(ff�j���eaLfcTfe�fg�fi�fk�fm�fogs<gt`gz8f��lJ4shs��0f�g��P��v)Lyedfilfn�{�~xfep~8ȅԃ!�frp����Mt̆�fn��B8�)�fl@�M�����ft,��fn�fsx�!�fe�6\�����$gbL�!ge,gi(��h�u<�`�!4gePghXgsX�)D�S0�Dlgw�����W�gg�gr�gt���tgehk,ho4hp<hsxht�Tu��J��gi�gn�����ge�gi0�M�PĶ[��hf̼�gp��lho$hr̢���`��0����!�ecPhepht��ydhrĵJP���\hkL�!���he�hr��5��e���heD`���h����!k���hrx�l����
�haie,ih4ii<ioPirXis�itjujzT�ii$in��)in��j,������$�Hibl�pd�uPutWa�ic�ii�il�Wp�it�i��1���|i�T�il�ir0�ih�u)4�B�*�ie�ii@Ut2�,!�ir�	��`
!jw�)jr<��
$jbljc|jd�je�jf�jg�jl�jm�jn�jrks�kt�k�0)�[p)tjeH)�jl�u�je$�D�jlH�qd=�ja�jsHu� ��!{�[e�[u'=�/*uk.Xkcdkelkgtkk|kn�kp�ksp�x���4k��*�\m<kè*Hkh�*���p+����+�L,!�ec�-�����k��/�-)�kr89k�KU�����kt,����kn�!l�ll�ls���lb�lcmd(mfDmhXmilmk�mldnmtnnor�os�ou�oy��laTpb�pc�pd�pe�wfxg\xh|xi�zj�zk�zl�|m�|n}odp�q�r|�s��t\�uL�vT�wt�y��z(p��"IX%�0%��le�#�lt|#���lhmk�&*X&-ms)�'mr*�LGu�/u0mu�.��8mn2�$1��Pms<3u�2��dma�me�mi�ml�mr̚��3��mr�3X�3T�3��ma415�ma�mlXnt�48�1D����m��9!neDni�mÈ�k(:nb nh0nr��2���h:��(ngx�c�:<ngl�h�;!PnhL=�<@h@��	lna�nd�ne�ng�nk�nl�nsotoz�A��nrB�B�TCOE)�D)�na�nl�nr8E�lE�E�PF,�F H�@K�J��ob<odPoehonxoz�wL)4oe�L1L�Hoi`on�LD@O7PQ�(Q)pow|Q�oa�oc�otlQy�QuDT��S�or�Z�Y�og�osp+$\u�ok�op]u�R\_���oo�`<ph�_��pcDprLpsD`��p�@����`�e1�g1Xl!ppe�pl�pr�`s�pu�p|pp���\{u�pr�z�pe�p��1�|���p�0��8��pc�;�pu\�}�)������1.Pqa|qb�qc�qd�qe rf(rgDri sk(sl�#m�snuo,ur�vs�wt�wu�ww�wx�wzĴ�dqnlqrtqs����������qe�1��qh$��1���qlD��qe�qi��H���J�qh�qrrt!X�����qkrl`�O�����ra�����f���0rrl���	8reprf�rg�rk�rl�rm�rn�rsst��*�<.��)xre��*����ra���t������re��1�rd�rhL�����@����rc�restl��������uss`����Psadse�si�sl�ss�stx�t�Hss��,�\sc|si�sn�stp��������T������!�sa�siL��!4)z(�������?.ta,td@teLtgTti\tnlts�ttuuuzt�t�)trp�R|���tr �) toT�R��8ti���$�y�7��u���dte�tp�tt�tu��D���ta�tr��\���up����!�tb�ti�tr�ts�tw =�������#	<��tn��B��)���Xum`un���	ua�ue�ug�ui$vnDvoxvs�vuXv�o��1��	p���hu.�uw��pui�ur�usX�J�D����uk��
	�	 �u�u.����un��!�ua��	�����u.���unvsh�	�)vs0���Jva4vt �Dl�b����<vp�������Pv�8�	��dva�!lvtx�$	L���vm���va�ve�vl�vpwsPwt�wzP����4��vr|�R��l�vo�����?�vo\�4�!we$wt#z|�)	�w.@�0	�0wn��58wi@�Dwehwo�44���`wlxwrL�8�D�)�we����wa�whT�7	$����wp4����wa\�|����wr`������T�=	xa����xl�l4xaHxe��@xt|��(xsTJ,[Txsx��1�pxuXYl(W��hxt<[
�xa�xb�xkylPyn�yo�yr�ysLzt�zz\�Z�xk_l�xe|_�mpm)�xa�xepn2t".�xs���\pya4yl@yu8p$yu�p�h�A	�r!,yu�s����lv��Hyblyetyf�yg�yt�w��xH	�d,y!|yl`|I�|��yd�ynT}��}u؀N	����yi�����ye�yozpzs(ztH��yl@���T�)�!zc@�_̆ zu@z�T�=`���8z����t".dzalzetzu������u�zm�zs�RHR	��l�!�ze������zo���ztH����zkܑ*T�t".{a\{e|ix|o�|u�|�ȫX	����{b${d4{n@{tT{w���|A�d���,{cd�\L{tȱd����u	�{a�{b�{g�{i�{m�{n�{s�{t�{ut�\	���T���b	�����{d�{n����i	��o	<�u	��{e#z��kȿ���{	��)�{h0|k�|c8|e@|gP|n\|sd|t�L�	,�x����g@���H|g��kD�1p|zP��	�u�|c�|s*�������	L����|n�|t\�\���ز���|���),���|i
��|s��	�&@}s�&���|aH}bp}c|}d�}e�}f�}i�}l�}n�Hp$~r�~s�~t,x(�	<(�	X}l`}rt)l�)8*�*uh}h@+$,6�}il�u�,��}e�,�1�(1u�}sd���2�}nx2u�}a�7���$.�}d~e~s|86~ep8u�80:�	�=t=��~a`~d�~e�~i�~tL~�p>2�>��D~�t?)?)X~ip~r�?�	$@1�?�x~iЈc�@�~g�BO�~r�C�Du4D�~c�~phE��,����~e�H���~n H)�~ehs0IUHJ Jut�LuhL$i@M�TM��8bTf\l`NHO�Slxa�T;�Spg@j)�j;t".�a$�e؀i�o8�u��c2tj���.�d�h�k�spl�@m2�m�	0% qu�t4s��uhs����t��,tLy	\'.P�ax�c��f��g��i��m��p��r�y\���	��X�e�z%`�t�z��l�h${�T{����	�{����f�}i	�82H��	��u��m�r̆Ȁe�g��o�uЈ��|c\�}�h �l(�n�(�R	Ȕ�0���0�cL�sh�tС-X�t-[�0���`�h8��	�!��a$�c��e�i$�l4�oh�p��s��t��u����Ĥ��d؁n�r�t��ħЁdШ����T���zH��rt�����X������98�aT�hx�uP�u<��	���@�n��H�al�ep��	����d�f'�4�����b��l��nЂr�s����6.��e�"���Ȃt�O���f�i�sD�����2�n<�M���,�5h��t4�N�������,�fH�pP�r,�8��1��0�`�lx�u��D��N���a�e$�i0�oT�r����������băc̃k��X�8����t���ԃ�D�����T�d��n�����r���	�0����p��<�d��	��������;L�a��ep��l��	����h���
�	��|�u�����bD��	���	(�����n��!؄a�e�.h�r �s0��x��l�s���DT
��

�r������tT�9d�P�p}����(���1p<�i0uD�h<��P�c��e��f��lȅm�n�r@�s:�����i��l$�,!��a� 
d����k8��"ԅsT")܅e�!}�d�('���g�i,�t)D���)N$�sD*u*u8�a��c��e��i��p�t�u��*d�a�?l�\m��w�*l�h�*
+�*��rd�1\+��n�+lL�
T-��ȆmD-�Іr-܆e��r�-��-)H2��1���c@�g84rD4u �eP4(�l44�e89)�@)h�eE��C��`�l0P#
��p��s0S��S�UB��ećuLX{�W����t�W����i8`}�`�bp�c|�e��g��h��l��n��pĈr�s�t�y��̇a@�c`�d��eT�f\�hX�i4cjp�k �lX�m`�oȓpГqؓr�s(�t��u��y�kz(�����#-|#��h�hL):�+u�.�;75����th@J@I'
I��eJhon܈r�`�OXԈilQ,
|Q���a0txVU)�h\_��_�8�sD`����L���g�uP�eX�h��\����l�oԧ�1��o����
t�c��dĉf̉iԉk܉n��r$�s0�tD�u(�D�Lar��l�`�����g�u��7<����e�o��[�������h���<�a$�=|�0�T��1<.؊a�b�c�dD�e��fȍgЍh�i�Djt�k|�l��m��n�o�(pH�r��s��tȏu0�v8�w@�yH�z��2q��i@1��̊b�k�nT�od�r��s��t�!D�2)`A��3���b0�c�ng@�sL�z|A0
�F�PF�8�t H�4,t"7
P4��\�.x�a��tH4>
��E
�4!��a�4u�oc�V2�4)��o��u�XW�f��e����m�5��ȋr��s�5����ԋ����(��46�kSl6*�pX�6� �M
7$�n$7,�e�6��	8�bp�f��iԌl�n�r��s��t��x�7S
��.��e��st"Y
88� 8���r��`
�8��m�8���g
�9��m�rp9��Če�92�to
;!�s�wX:���t;w
`;�|;���a<�e\�g|�z�;�;4�ih�
t�H�bL<NP�e]����h�e,=)p�i4�O���s�wW8=��a�=�
0�S�=�>*P%h>�
$>��؍a��nh�rDv�?�a$�d4�e@�fH�h�!sP�tX�v�?k�x�
�?�,�s�?�
�?�
@O�|�
x��
d@�`�u�z AJ��e��u�Au��i�����{n0Du\D��E�
�e���
|F��.�F�Ȏi�F��ԎeF�r�Gq�G����b�f,�h<�rHH$�f-�\/�>�tI��4�c�J\�ep�h��oPK)h�sDL�
@��
(��
�L��x�n O���ttP�P����l�P��a�Q�=(W����f�h�n�s�Wq��
�X��f�t�%�
8YJY!�i�Xu�s�Y�ZX(Z�Z�Z��`)<[��P�ch�s��ܑ-��a�e|�i��l��n��o��r�s�t�u̐Ì����n��r����d�"���Đ�X�"d���ؐe����h�i�n$�rh�s��p�Oę���t̚@�aH�eP�hX�n!r��)���x��<����"��`�e���H���t�d��m��n4�@������
��u����;��o8��ȑr0�)ԑt����!�e��[ ����m�p�T��<�eH�oP�u����4�t�`L�����&��c��dȒfԒi�k�l8�m`�nt�p��r��s��t8**u��h@+���i�+-*�,����f(1��t�2u�1)�e03ux2u��e�o5yp���<.D�s@7!�e�5u,�t��;�Ey�9!L�e�7��T�n<��;��l�e�=�t=����a��d��e?�?p4Dx�Hu H)��e�Su@j�j��e�yLy��mp�k���s�}��e���l,gi��!D�eP�ix�u��
T���<�e��\�o�Zh�n�}�
�K�	��p�r<��p��sp&��,�L,!��i*u��s0P����1.|�a��b�c8�f@�gh�h��i��l�m|�nL�pt�r�s �t\�u��Ĕa,�bL�cx�d��e��f��g8�hX�iL�j�zkT�l��m��nԧo�p�q �r��s��tP�up�vx�w��y��z��Ð)����eĕl̕r���p )| ������s1� ����n�!�
�"u�Ke�ü"�
�"��ܕ�|#�t".�h$��#�a�o,�s�$1�O�
$%�$�e*�+�T�i�|��-��L�o�/7�.��`�lx�oH0$1�
��n��s�12��4Ėr5����a̖b�tܖ�6�
�6Bd;R�6��Ԗ��;O�>��>)�a(��L=����m4�o<�pt�t<����� ����[d?Xh�f����H�f,�P�rX?u\�e�?Ih@��
t".��a��c̗dԗg�k�ns �t,�w4�z<@8���<.|A)��e�A�TCO�e�C<8E��a�D)�l�o�nr���)�G��F)�w�GI H)P&.D�itHjI�\�hd�p�I��IxJJ��
l�a��b��l��mԘo46rܘs�t�u�w@K��N
��u�N!��a̘ih�)XO�ODPO�PQX�RU|Q����h�U�4�i<�nU)�eD�lL�m$VDV8��V�Y%YT�ep�fx�sDZ+$\���h��kDb�_����hЙm�n��p�r�uD`�������L��Ld1d��șt�d8Xd��ܙd�g�d1�e`g8�e���z0j$�skXl*@�uP��\���8�c�u\�hl�o\���`�d�r�l��a��h��o���t�����mĔ����r8��ԧ9���a0�bP�c\�el�fx�g��h��i�l��m��n��o�p��r��s<�t\�u��x��)Ĵ)�dtqs(�t�y��uD�e �?���<�n��qh��N��g�d�f�����l�����H�����e��o��r0�L��l�̛cԛm�n���2L������ܛd�sX�F��t���gD�u�����a|�e̜f�l�oX�s��td��\�J �<�g���P�n����X��<�����,�t�c��i��k��m�Jp�=��g��n���	(�P��W�^��)Ĝm���i��!؜e��$�2��1D������X������b,�p4�rD;1��R��1�����D�n�!L�ap�ex�o��pD����u(�l(�7��u|����rH���a��p��g����	t".��e�g �h(�iD�kh�s��t�u�J|���d���n�r��e�����g�%$�u@p0�i��)8�l�m��P�mx�n���X�e��t�a��uP�����u\���a��h��!��h�tw���u��n �{(��̞o4���Ԟs���i���<.D�a\�bp�d��e4�hH�i��k��m��n��o�s�tP�uXv�tk��<�bT�p �X,�)�Kl�����)h�b����|�b��c��dПiܟr@�J(�����kP�R\���i��np���ȟsD���b��f�o�t|�t���������N�^��h�������(��d����@�e`�n������X�f�����l���t����)��l���������u��J��e��̠pl��T���Ԡt�!ܠa��������u��!��a0�eD�r�������d����$�n�J�;<�aL��`�hh�mP�kx��P�)��c��gġm̡n�	p�a��e(�i4�oH�pP�s��t�u���l�P�2t�������ԡn����ܡ�d��4��h�n�t��`Ķ_$�� �n��@�r��1��%4��U�h���X�t ���`�l��m��n@�l�a��eТi�r����F�l����t�t��5��i����Ȣt�q�����ܢtd�;�a�i�o\��X��	��)�����u�t��$�n��0�eL�w�t��|���T�nx�r��sP)O\���p�l|�\��D���s�����i��pl��T�*�l�a�e0�l��Tԣr�ܣt|���s,t".�t�y �a(�e��!H"�1lL�i�@�$>��D�s<[��aȤcԤed�gp�k��l��m��n(�oL�pd�r��sȦtD�v�zz�[!�Z��b��d��m')L]��`DP�e�b�d�n0�s<�t��y`e��,b�d��e�i�s$�Rf��$.��!�g(�c�g�H�hLh�x,kP�s�j)X�epm)�xad�\p��|�s��� t����bDvlv����aԥdtyfܥh�i�n�s�ytPwg�y�z��y�t�z��z��r�z!�e�{,<()�|) �b@�n~��}8�s$��h.x�n���T�e��i��l~1�X؀��s�I��zp��r��t���̆���u�����a��e,�r4�s<�uT��4��g��n�rh����+l�m$�sL�Y��Y@��)�u����)T�Nl�e��i��m��s���r`�X���x�a��f��d�7��B,���|i<(��a�e�l$�r�&����b8�cD�fT�lt�m��n��o��pܨr��sȩtܩu|(1�(t)��)e8*�$i*u,�h�,�<4Nx2uL�l061�5`�r�5uh�at7�7����a��i89@;<�;����eĨf̨p<<4=X�=�t=��Ԩa�c�d�fX�gh�it�p|�t�>�?���)\@X�aH�lP�r<�ô�1Ă��4��p@�P���@IA�@`�e��%�Bd��a�Bu4Dt".��s��t�E7XFN	��a�F20I
 H)��hhK#
�JԩnTM��l�r�sHO�QdRLc�SB�j;L�a(�e�h�i��o8�u��citj��D�.�d|�h��i��m��n��sȪu8mb	@m��t�mXmDoi�o\�o����d0 qu��t�Y�q��c�s 4s��Ԫd�h�t�uhs��ܪ�������s t�	,t'�1Ly �.\�a|�c��g��h��i��m��n��p��rЫs�y)LC.l�stq.�z�	�z��t�k�{i�4�{����c��v���};~8�!	�ȫ.�c��.@�A̆<.(�a0�c8�d@�eT�f\�kd�ll�n��s��t����F@�Ld�R���L�g�cWd��0�c\p2p���LC.,�]�)x�c$�d,������̋M
��u �kD��\�����b�c�d�i��m�t�u�v��Dԓ� ��t��(�����L��(�sh�������0�bT�c\�hx�n��t0M
P���#[D�!d�e��}l�g��ZL�r8���b�)�ah�b��cȮe��h��i�k�l �o\�p��s��tt�u��zX�Ä�.Ĥ�b�m�n8�sD�u��xP��tħ��S~\S!$�i<���,�s���H���ԡnt���L��X����p������p�f��x�e��i��r���hD�������n,��4������b�f�i$�nD�rx�tl�����g�n���x��ܱ�s�I�����g4�t�ID�g����<�f`�hh�k!rp�z���ĵg0�gĶ	��aܶ�Է5��)��a��o,�P��h���m̯nty�d���įf�ػدn��l�a�u�i	(�����l4��,�������p0�r8�<�i��"�|�D�rx�s0�L�aDSo��r��u���8Wd��p������@�������o��u��԰a�e(�iT�o���������̰b4i�
T�1�i�����t������������ap�10��� �l<�pD�s�1��g������L�d������`�s(���h�m��n���\�18�����n�)��e��)Աa�e�h �o(�rH�s��x���̱dD�`T����a��)�e�o������i��$�d�u<�aL�����4�nP�<
��a��bIJfزh�i�mt�nгo�r0�sL�������vu��l��rh\�Yi)��l�вe$8p�@)d����b�d$�e,�f4�g��k<�lD�pL�rT�s`�v�� gT �\ �� �� X� �� �P&.��gT"H	��a�!}h�d��f��k��rȳtH"��g,E�$X��e��l�4F��%g&)<(�'��سc�l�sP)I�)<�)!��a�*�Wn<�r��w�*�h*u$�c,�u�1�1��D�b`�fh�n�3�H6�89l�@)�Pa��eXG�0G��t�C����s0P}��s�S�U)�)l�b��d��g�k�l`�m��n��r��s�tX�u��Ĵa��b�cD�d��e�f��gH�hl�iL�j`�k��lH�m��n��o��pГq�r�s��t|�u�v`�w�x��y�z����yDl\((x�r'��e��l��r���T�N��i)�+;еaصe�l$,��,J�-�@4N�2���t�4u5����a �eL�t�72�7��i8�rt"�88��0�.���;!D�i�=�L=��X�et�o|�t����?7<@uh@����a��o�E�t"J����.ȶa�e46r�vxJup~]�LжeL�ضn�P�|Qu�s\S��U�0�rU)�e8�h@�sH�t\V}xV�|WB�W�DZ�YP�fl�g|�s�Z�d]$\ut�t�_�D`����������������XlԷa�eT�l��oȸr�sԹu���l1�klmD�qq�p���d�i�l �nH�rl!tDr(s��su4�sdtlt�,�e��g,u��@�t�z?l�e��i��o�p�\{u�pr|�u�{�8|�|��ex|}_dR@����s(p������e�e�u��$�ظi8�J|��,�c8�e@�iH�oP�p��t̹ul�	T��e$�9 �h�����4�h��\�a|�y��ԃ�d�t���l������h��i��oĹrx����$�p�J0���tT����\��t�-����t#aP�e$�h�i�k �l(�o<�tԋ�����\�X�e�$i�$l�$m�$n��o��r��tĺuغw��D��\$i|�rp��|�)h�i���p�z���P&.�$bH�����Ya��e0S�R����iȏкh�
8�XX��a�Z$���)�ep�-��s�t�d ��`��4�d��(��	p�ax�d��e�i�oL�s��u��y,��Ĕyx�lLar���a��c��i��n�rP�	��R��y������eлsX�Jh��Ȼe�pD�����t��\�����lX��aԧy�u����$��L�uX���8����)t�e��o��p��t�Bu@�ð��Ȯl�s �)\�l��
�����r0��P���s��

��J�a �b4�c`�dx�e��f��g��i�l�m �n��p��rp�s��t��uпwܿxĴBT�����lP�u���,�eD�h$�P�o��1���D�X�ep�sL�B���������f��l�
���l���mԽn���{JX����e����Ƚs,���k�n�����e(�7	�����H��p��@�ah�cp�ix�o��s\��t�lL�g��c������T����X$������_4������i������
��aܾb�d��e!k�oT�th�u$!z@�ø��,�)Ծt����e��

��y�n$�s,�t|~
��=�g��
��k��"L�s����4��h����N`�r�"
L�����h��kP�B$���t����a��hp����|���B|�_��r`�Ŀa�T�
t". �a��e��f�i�k�l0�o@�r��s�t�ux����8�dH�nT�r\�t\��<@"x���@�a�u\.X�d�u���l�������1.��b��m�*n��r���t��u�)P&.�idalX�X��d�n�s\a�����s�� �Sd	g�ul	�(�u�
����8�a`�et�o����y�p
X�a(�D@��l�l��mt�D�2`
������N��o��p���u��|����nP!�a���m���� �d(�e��l��n��s �t,�u�(
,H�n\�r��s��u0�@�a<)H��T�a4�p��h�o��rxp�td��( t"H"����a��o�$�8%)��i�%T+2�*u��l�*J��a�e�t@,,
�+l��r�-2�,�o`.)�.h�.�@1���c��l��u�1��4�a��e<�it�l�m0�oL�r��s8�u1w1y���21
(2����h�2DT55��s�5�5�����P���6��c��f�l�n �p(�rl7u8�g�7��fp9u;X:���tL;7	|;4�a`;�$>L�nX�s�?P�t�@xحDA��`�mAJh�a��e��i�o�s��ðA�A������A���i��n��r�������np�M,B����tHB6
�B1�B��n8C�Cu�C!�e\DX(�u�E�G<�l|HD�Jl�JuD�a��e�0i��op�� K,K��h��DK�PK|�c��i��rh�s�K1(L��e�K����lL;
�L���b��f�L� M� O#�e04h�i�o�p,�t�O�O�OPu�P�P$�a(WD�n�X�ȳt�Y�\�b�Y�ZT<[��d�a��b�c(�d��e��f��g��k��l��m��n��o��p��r��s��tX�u_c��a��l��uo1@_����r�_��-��eh`����tl. a1�`�o�`u�h\a)@�aT�et�n��r�a)L�n�aD�ah�r��J����`�r4bA
L�`b|�a�b��n��s�d���g�e��eG
��e�g�iL
�hu��r�jy�j)��a�e@�lH�nX�rd�tl�u��R
@k�o,k�n0�r8�wLku`�Z
lk�ka
`*<�k;P�u`.��.)nu�mt�r��upm)|�a��l0n�0o oB��a\pJ	P&.��a��b�d �e8�fT�i`�np�z�18p��r$yuqg
,q7�q��q���n�q�i0�n�q}r0�m
�r��@�s`rH�n��9H�2�s)h�u(��`t|�g��l t����a��o��pH�������l�u1�u���r�u���l�`Dv)(�slv����aL�dx�e��f��g��h��i��k<�oX�s��v8��w�@A1,w��0���wr
Pw)D�r\�u�w<@xxHxd�g�w�l�n��u�x�yx
��.��s�x��oXb
y�
,y����
ly����b�yl��az��y��t@z�a0�o z�
(z���n4z�rT��
�(�m,{%T��
�{D�t�{�L�a��t��zh�	(|2l�l0|t�a�F���
 $���r�|l��e�|B$��fx��1��e��r��y����t".�a@�cl�e��p��t��u��(
D��
����n<�$�iT�w�0�h��
ԃL�u����7w��`�rą���?x�e�ĕ��rt�1������m�u��a�e �h0�r<�tL�u��$������b��n��rd�1x�1���
�������l�)Њ1@��
(�oċg���D�m|�*ܑ
��a��d��e��l��n��o��r��td�ul�w����H����1.��n��s��yę���������������a@�e\�o�Fc��t�����nħ�Ч��$�fL�gT�z����,�r�����JX� ��y��
�b�5f,�h4�k<�mD�nT�o\�pd�rx�sT���t�a��b��d,�e��f@�gT�i��k��l��m��n��o�s(�t��u�y�z����	�����nȫ���e�i$�t�!R4#T����ح�d����5s�5z��R	�����p�iL�R����L6i��p��t԰�����ز�����������yJ���s��)��i�u��!��a�e$�s����!��t�����s7w���r��B�
\'.h�hp�i�7l��m��n�p �r��s��t�{u��v��x�����=��e��n��
�f����.����r���e��g8��
���
��7	�$.��p��t"�
������.��s�tT��
`����ep�I���������dD�fX�g`�kh�l��n��rĽ�P�a���
�IP��p�Ot�a���
���
����|����J���g��
��sd��
���
����a���,�)�?.��e�l�m�td�����
���f���:.L�sL!���r!��(�e �)4�ipg	��l�dt�e��n��t���,�D��@���|�e8�"D�����aL���)��l��s������X�����n��u��!��a�eL�il�s������e|���b$�i<�r�������7n4�s���P���X�D�c\�n0�D�u��!d�p����)x�a�����a��9����f��l��nD;p��r��10�h������i�@u<�_���f�����;md�!��u�������k��!�aL�e��o��r��s�����D�kl�n��r��sT���Dd�g|�t��M�������n8����0���ix�R��N��k(�L����m=r��s���@�H�u��e��8�1��a��X,�e<�w��<b_l��4�a��|�a��d��e\�i��m��o��p �sD�tT�uh�����d��k��n�������������f��s��z4�,p�"P�B4�)��a��D�t����n4�rH�t��7@�!�h �s��)��!�p��g����,�wT�1p�@�i ����T�mx�n��tDv9<���p�a��t`|O8���>i��X��a�>e������p��u\�8�1����p���	��l��)��f�lH�=4a��`�
�e��u��)�a8�p���l�0�r����S
��L�l<�\���`���)
 �.��c�d�f�h�k,�l8�mT�nt�r��s��t��u,���t�a��c �d��ep�f��g$�i��k��o��r��s��t<�ud�wp�y��z����Bd�������4���9����$�i�D�e�H��N8���L�dd�g��YLR�l�ep��D���c��J��t�Y�Y����e�e���r��u������|���G�l����t".�)��e�DT����c@�l$�)�a\�e|�o��r��s��u��1P�T8�H�s��P�s|�Z��h�tl���p�r��`�����m��;��o��D)��i��p4)zX�uP���m�
\'.$�b0�c@�fT�i`�lt�m|�n��r\�s ������nH�e`������8�f8��L�np�l�e�e�q$���t�x��r�kt"�@����.��d��f��g�h�k�l �m,�n4�r<�sD�tL�u,�wT�z��ggXrdy!�I���!�o��
��g8I�I�p���h�sY���a��u 	�	�8
�
����d��g�	!��a��e��i��l�p�s�+Jh�
��a�
��r|y�u�o�R�'�H�XDc�de

�1.T�c\�dd�e��m��n��o��s��t��v�
D�
��
p�r,���1.�Ds�������<����D�������)������lp��b$�f,�hFl4�m@�nH�pP�rX�sl�tt�w�����e��5u�5u,�a4H�T��d�t�D��5���d4�+<����c��u����a��c��e0�kD�p\�t0�l�����et��h�����b�m�n|��P} �g�@RLl(�a�5h�<�o��`�lP�a��e��o��r���T������x�tp�����������th���sW`�\���eD;��i�!
�a(�d8�ep�f��g��h��l��n��r��s�w4�z�����Ug$�_���0�bT�r`�s���L�b����
��*h�a|���l|�ah�1���i���p B��a��(�4 !����l� ;��o���l!���r8!!��ppW����� 
�(�e�G��C�� �t���!H�t�#g�Kj<C��P��,$!X��d$�%���Cf��g��k��t@%��x�r�$)��e�YYXZ�Z�&�&u
��b�f,�h�l�n`�p��r��s��u��v<(N	�,�e�,ux2J�7��c.@�dH�ftDhP�l4FrX�s|8�9H�9H0:��;��<;et�f|�t<<l=�t=��.��b��s��w�>�<B���4D��1.@Kh�J��lL�TM��S�a�f4�iH�l\�op�p��t��u�Sypg[%l`�\`� �ol^��(�s4a�`�
@�eL}��aT�c`d�0d)h�a��l�er�d��g��l�g)��a h1di��s*�tj�J.l�d��f��g��i��k��l��n �p4�rH�sx�t��u��w��zd���j����a,�b\�c��d��e$�f��g��h�i��k`�l��m��n��o��p��r�s��tL�u��w���<ipl�d�.|�m��)�l�lu��a��r+jmXm���clmR	�m1�m���l�n�o�o����d�e�f�h�m,p2���tD�xp
�p�,�f�pR	�p�@�iq2 q�T�i�R�xq��q��\��xV�q�p�h��i��m�V��V�r��q��b��e��f��g0r�<rDr��^��_�4s�h�m�shs������������s�s�s$upt)$�e@�rP�sv%��v�H�p�v)p�h�$��vh�l\w4w)|�a��b��e,���w��c��rP�/���$x����wLy �.�b(�c\�dPLfh�hp�i��l��m��nD�r��s��t��u�v���@z��z�8�s�z���h@�k�z��z��z�zH�t�zP�i�{��{����.��gxLn��s<|g,}��.h}��e�}b�}�~=d�.��a��s��t�z@~��~�~���ep��~!��f�n���~�0�e �h%���d����$�n�!�
8��
<�.t�fP/h|�l��n��o!r��s��t��w���x0�"`�2t�O��I����q��s$�I��)$z�����T�����i�jr����l�/������d�)�i؂D�)�e8�rT��P�����@�rĂ��H���|6t�`�nT�h�b��h��lԃ!t�e��o��>�������H�����Ø��u��s�&G������l@���a��2�`�1��b�YNl�����̆�H�aT�b\�cd�e��k��n��o��t�U�@�t4��@�4��4t�n|�r�[����Ds0���`p�����.��b��i��k�Mt���(�84�g�|��"̋u��a8�)P&.�l4�r@�t�����������$�i�ø�<$�<�;,�e��7��`�nȎH�b��!T�e�g����l�s,�t�n��rx�!��eH�	������s��Ȓ��s��J��o\���c.�a �b(�f0�g8�h@�kH�lP�mX�nl�pt�r|�s��u��w��z�&lD��,�@.��"�1�(��t��ȔDd�y4��<�`�M������8L��L����d��k �4�p��x����������$�)�i������u��r���e@�w��L�!	�aH�e�SiP�k`�ml�nt�p��t#z������R�lX�o�~�%��5L�|�e��������k`�!��a��e�h�i@�s�h��h��i������e��f��s<�����r�JD�xt�X��o��,�n�m
����$�sT�D�J8�e���l�fx�m��n��z@�>(�d�s����g��}��d��s@%�,��L��8���b�������k�C����c��i�)��e�D�|D��s�P�a��b��c4�e��f��h�iP�k��l��m��o��p�r4�s@�t��u��y���Ĥ�bl�dt�n��s��t��ħu��t���<�D�VT����oȪ�H�����ut��������������a�e�h�o�uP�u��pp���������'p(�4� �e��(�bT�gd�hx�i��l��v����D�D\�rܱ����p�n�2���r������e9�)��a4����c��a��uP41Է��r(Wu\�(�D��rh���e�l$�n<��ܺ�d����i4�t�y�,�Xػ<�t��lD�ad�el�yP�p0P2��X��t�g4�l|�o�%�B8�����t������r�u0�?��e��o�r��uY�����kl����r��i����a�������� �u��!(�a\�e��i��o��p�t|����T�m��n��rX�	��p�o����x�s�pP�����e��gx�G
,��h���t��=��f��1|������a��u�����ML���e������b�����b̃kl�n|�r��s��u�	 �a��e��i(�m4�od�rx�u��y������h�8���t�bd����_��5��n��r��s��v���������g���d�����hD�������s���0��p�������e��d����o����L�dL�r8�RD���D�i��Wp�p��;X�o ih�����������u��(�����m��r(���y��y�����e����r������e��$�a��e�hT�ix�o��r�s@�t�w �z���x����1.D�bL�mT�pd�rp�t0�x@����S
`�����\�i��!T����h����x��T���i��n��o��r���������n,�u��d��B���^������������h�����h�t�f|�!�r��4�aH��l�,�_,�l(�R	����@����d�ml�t�������a$����b��fl���,�d�e��a�e��	����m������u��l��c�s�q���w0�hPu��c�p$�ulj�\d�bt�n��u,!,�a��e��h��r�s�u���P�l����l�z���T�1������������4Xi��n,�d�)��o�2	u#zd
�\	���m��r�1p1�P���r 
7�a`
\�ejw<�ø
��
��4��0YdH�t�
��P�nl�s\	l<��t�a�e�g|�i��k��m��nD�oL�p\�r|�s��t�^x�z���L����fX�i����e)�r�s���L��c�)�)�a@�e`�ll�s�M,�t(4�nP�r8Dh�H)X�aN\p�@ut�l��n�����,18�����d�1.�b�l�s���� )� 5�p�tL!�l!�!
�e(�g�[i8�kD#y �ip#��#��$��$X0�a&lp&\(�'��T�el�o\)��+u*ut�p��t-��-X��h�s8/ܭ0��n,0��h�/u��c�1��1)�w�189)�eX�o�^sD;�P;���a�:��l8�nD�r��d�;��0�t<
P�i�<�>�@|�a��e��i�s���`@_ CD��s<C�����tC��C1t".4I	�G����r�s�t<IlI�J��K��	t".�a,�eD�id�ll�pt�t��u��yUh�K�t�L�
�L$�m<�rMLMT�n\�s�M��MD\NB�N�N!P&.��i��u,�1�O����n�O)�O�P��R0P����n��sU�a�e�i �w�T\ttY���n�W���n�\�s^b�Xl��
(�bl�ct�g��h��l��o��p��r��s�t�u�v�z����i!�1T���uL��&�S�j����<�89U���l�c��m�kn��r������p��b�c\�dl�h|�i��k��lx�n�p�rDs\txu�v�x���a`b�pchd�e�f0gPhXiL�jd	kl	l�
m�
no�p�q�r�sPt�uL�v�w��y�z���x��e�f�r�s|!�"�"�|#�h8�;@$�b4�r�#�e@�i�od$l$��,�f�$�\(S(H�r'P�e��r�.J_*$1��t�b�3��2����e5u��a�k��lH�sd�t�4yL���9)��l(�&H:���k(:�n�r�9!�e�iP�,h:���k�:u�	�$�aT�m�,�h�;!<�c8�2�<��;!\�s`A�h@��
p�b��f��g�k�nl�p��r�s,�w�zCg(DTC!��r�Dg�PF_ H�I�Jubc$i,t4z�K8@K�r�K�HM2DP>(Q�lQ|Q��<aTh�R�Uhho�V�DZYpfl�g�s$\��b�D�^,_)_�a�_�ch rXuL�D`����L�������`�N)�bu�uDb��r�f1�e��m�h=�h,r�h4e�h��@�0jXll�|iԤ�X�te���ac��d e8fdhxi�k�ltm�n�r�s,t|u�w�x�z�X��)�uĴ)�t$�%����hk����u��Ji0m�����l��2���Dit�JLeH���Xl��kl���pe�n�s��J�����g�h�i�w��Q��",$1��X�u���a`��tt�)�����ad(eLohs��t��!t7r(�W,� k<rDs ����]��X��2����Th�!\oH�t".�m�p��c�����1�����������g�o�s�t����u������a�t\���O���t".0ahdte�f�l��n�o�p�s�t��w���@n`u��uPgXzLpM HJ��_��)�e����rD�u�zĀ����$�)�a`�!P&.��h���a�!P&.��@��aer��t ���n��5id�e$�Xg�� a@ePt��y,�@�!Ha�����\e�)dr|���pe�rxJ\����u`�����p��T�)�ae�f�l�rs�*��b�ru�bcx�4e<fDiXmtt�*x �8.xPn�tdi\� �t,lz� l	�a�o�u�Ð	J�	z�	������t
J�
�
iT
i`
����@+���o��@�u�!a$h`+p��)��o��Dex�,<s�1l<[�1.�a�e�k(l�n�o�r�s<	t\	z(^h�Z�t$�A�f���f�b���ro��n���npm)�i�l�or olho�
b���o�p�8pnLu\p��a�d`elgti�p2�pDf���qXs �*`r�n�p�r2�rDv�lv���a�i�yt�y�|��l�}��a�y����a	c 	o(	p�����<�	o�	hH���� ��
L	r���0	oT	u��1����ܑgT��t".�	a
e<
it
o�
u�	����${d�	p�����S�	c�	d�	n�	uز���	�4
��
��
���a���\��	�����	b
e(
i�pr����	���� 
n0�2��1.P
m`
n�����@���X
eh��l
n�
p�
tw|�z����L���0�c�
g���
eP�28��
r��2��B�
a�����
d,�Bi�5
s�&Hb(�f\lpn�p�r�xt)�<(@l�3�x2uTit7�7��ha�e�u�8�;u�;��@Yt=���g�i�m$nHsptHA��@�n�A
�a����A�g��,���.�
Jr��e�AJi8�2L�0aTi<B�8t��2 C2C��\i�B!de�h�~r�ulP)�ChL�TMTf�k�l OHO�SN�j��ap
e�
i@oxu`
��k2(
htj���c4
d<
m��nD
p4l�@l�
rl
tpl�o�p��s�4s��L
nhs��T
���Ly\'.�
c�
d��g�
i�
p�
u�z�z���l�{���
k�
n�|�����@�R	̆�
c�
de�gsd�����Dc4t,�e������ r��(e\����cXndsptȔ�ha���~c(����8��!�a$c`e�i�k�o�p`s|t��u��yDÔ�XP��mĤ�m�n�r�su<��ة=���fst�� ^��4ePh���<�j���<n��Dax�e����b�h�m�n�vrx�t��4�D�r|������4�t�h��dĝ\����e��l�i��l��|��ns0��a h(l��o0r8W@����`��jHePi��oXu�)`f�d�����d�=����hs�pa�e�i�r4y�������(���������h����c�m��k���p0����pD�s��e����s��

1e����t��d�1t���<���!	|a�e�hLo`r�sXu|w�zx���t".�b�f�l�n�r�t0���e\�������������h��T����hi$n8t��������gs�����I,���tT�e��0id�$���Dp��d�;Xo�u|�H����t�tP{�a�c�e�Wit�(
�m�20�h@t�et(
�h�l�p��(=4�����0eHi�\@�(r@s��ttud
1�	��Pmhr���p�l�P; 
7ta`
�<�g�h�m�n r\s�t�È�p�d�!}��f�i�kmȳt($��$X��lor�u�t�'u,e\(h.8,uL,!8a|e�o�Sppht*u@s������hk�,pn�,u�)���r�-1�e������-��1D�b�rD7�@)U)�a4e\uP��T�hX�@dtXnLXue�W��t�W��(i`X�_u�V��H�8`�pg�`J�`)ha���
�1.,bdcxd�f�k�l�m�npr�s�t�u��|a8b�pc�d,e��f4 g� h!iL�j@"kH"l0%m8%n�&o�'p�q�'r�*s`.t�.uL�v�w01z �|!��$fDlLrTz�!��"l#��#|#��\hT��'pl�r)8�*�*���l�2dma5���ma���L=���ot�th@��	p�bdg�h@kLl`o,�wp�y�A�CDTC!a,e��r�C��C$b ��	�D)8u�E�Xa����E2@KgJ��hb�c�d�m�o�t�u�z�KgL\�NIXO�xP�DP!�i�P�(Qg|Q�ce p�sTt�Q��pXRUimtRUS�0e8r���������S=@n�SHate�r�uL��,T�llDT�a�edT�T��TTUU)�a��m�rW�YcY�cpf�k�st�1$\� ^g�_����p�r8�s0uD`���1�0jXlB`ate�i�o��oLsl��Tu,u�p��lr|x��u	�a�e�i�orst$u�Ĕ�u\��������r��X��nԧ�"������ ���B��*P�����a�b�c�d�e�fgi�l�mno4pHrxs t( u�wxԷ_��|a�e�r`�JPq��m����aȸ�D�x��N�c�s4�p�l�x��y���ll�����m$nLrTs����t".�s<tDv`|��������)heph�tl��h��2��xa��u�a�b d,exl�o�s�t�u�z��5D����zt��n��;�a��)�r�p������s�r��!e,�@hPrdsh���Y ���Hs�����
\s����!pa���4�rP��!�t4)z�y(�!�a��R����m��������%\�H��iuT���0apd�e�g\tn�s�tt�hDmLr\u8��t��l�c��Tg�� �)hr$����|b�c�i�r0�R��T���d,�!�����n��ġm����a�z�hp����!�f��hw�(����ur,u����u���xb���
<a�e�imn�oPsh�udw0�tk���/p����g���i�n�r|~M��=�g�s�t��7��YD�u����X�f�nt���n<�>�M���g����Jo�������(��2��<o�!Dt`@���)\a��y�pc�e�i�p�s�th������t4��l�vr�y����ih�=�4�!�t@��$�y p���a uT�P�	��|���  l�!�aP ep l� n� r,d n�u\ eH"| o�$J8%�'�1)<.� e� i� l� r��u1w�6� t8=��>>$>��� eA!�J5 ]R\1� o�Z� l<!s<[��!aL!e�!g�!m�!n"s "t4"u�]_�c��b��D!gd!il!l|!nd)(du`e�	�d�t!e�.u�j)�!u�tt".�s�!t t���!eDu�lv��ԥd�!e�!g�s�w�y�,y!�!a���	���!l��"e��y���"a��J|�B,"sܑ�T���	<.t"al#e�#i�$o%s%u(%y@#���"b�"d�"g�"n�"s#uȫp��B�"e()�r��0
d����"z0�p��"i�"u�����"t0���H ��"b#fD���h�
�h��#�����P#t`#u #�ز��,#��|�L�O��V\���X#f�-
\'.�#a�	b\sc�#gh�h�#i�#n�#r�#st�\T���`�����#n�#t0�^̺�#r���b�q�{e\�k��#a0$b8$d@$eH$fP$kX$n`$oh$s|$t�$z���	���,���h���X@�|�'
��qt$c��D���X�&h��$a�$b�$c�$g�$k�$m�$p�$tX��8*o	*u�$h����T�|����d�L��%t|�\�)%e�u��l,�
t".t%a�%e�%g�%i &o,&p4&s<&tD&ul&yt&z�%���h.��c�%t��c�����%�d&���%h�%t�%u���8�%r��	�
�
�%e&f&n&s�h�a�np{l�t����X�!1X&mt"t�"��P&.�#1d$u�$X�&�&l�&��|&a(�f�&g�&h�&i�&l'n('p@'r\'sd't�'y9��'���&i@.X\/�(1l�2x2u�&a�{89p's�7��'i<+�;�� 'eĨf�=�t=��8'a�dT'g�@d4D H)h�'tt�pJt'g|J��|'r�J!�'e�L�Sl�j;�'a�(eT)i�)o`*ud(ük�tk��'illtj���'b(d(h (kL(m0��pl�(l@m��m���K�dxo((n<oX4(eo��@(m4s���uhs��X(�L*��*��1Lyx(.�(b�(c�(d�(e�(i)m)n��rD)tL)v@zk�z����z�(e{��{����c�(n�(t�|���	�}k�~�~��)n8)z�W�����$)i�~X,)e�����̆�
cp)e�)f�g�)n����)s4��H�|)ed��	��p����)g���\����)c�)h�)n�)s<*u0*��=Ȕ(
��k*s����E�)l*rؕ!�)e��(F��*i������(*���,��
���D*h(�2��X*f|*i�*m�*p�*th�R	�����i���t��
8��*c�*nH6O�J�*a�+c�+eP,ih,op,p�,r`s�,tX.u��y�Q�Ĥu,+cl�d4+f<+gD+hL+kT+lh+m�+n�r�+s�+u�$�l��.��Uئ`+t@�xP��'ax+b��Nħ<������+gP����+a4e�+h�+r��x�e�+i�14�C��l,i,l8,n@,rH,t�������,l����6.(,i0,n4�R�����}��Ķ	h�)�d`,l<����0�x��e�,i�,l�,o���h��,e��
l����,r�� ����,t���,a8�������,r-u��,a@-e�-h�-i�-o�-r8.u(-À������� -�D.�L�����8-l�md-nx-r�-s�����\-tL��d���p-m�-r���������-t��A���-e0��-s������-n�-r�-t��D�������
.a.i$.o��J\�J������.s��h�0.n��P.c���(���)x.e�.i�r�.sT�����d�mtJPu�.e<�.a/d(/eH/fزh\/i|/m�/n�/r0s�0t�0��,u�.m�.n/s\��c./t�F����p)/r�8/d@/t8�t�������!s@uP/nl/s�� �d��t/m�!}��f�/g�/s�/t�#!P&.�/e�/l�#��/w�#��#�@%��%�'�/l0tP)xD�7�)N�/sD**u0a@0cd0s�0t��
�*,0i�*40h81�8,L0a|0mL,!T0a�0e�0p�0t@�2�,2�,�0r���L�Jh--�0i�-�����0��-��0a�0e�0h,.l.\^r8/��1��D�b1r$1sD7}�88��1tUB�D���81a2b(2c@2d�2f�2h�2k�2l�3m�3n4o(4pP4r�4s�4t5u�4���@1al6b�pc�6d�6e�=f>gPh$>i4cj�zkAl\Dm�En�Go�pГq�Jr Os�Qt(WuZv(Zw�Zy�Zz�5�8 _��1a2e� �#�o|#��2h(�ql'42e\2ip2l��2�(pT2nl��T�Nh2e�*C�*)|2l*���2f�2t���2pp+!�2sl�.�3��2���2l��5���2.3a<3bl3e|3l�3p�3t|A-�5��3c�43n,3ud6�iju�6)43aX3ed3r�62�6��P3i07�7�9u�9!t3a�3e(: nh�3r\:�h:���3f�;�l<u�;!�3r�?OL=���3th@��t".�3d4n��r4t�AX�E��F��H�Hu4sI�84l@4r�I��I�xJpJ��H4ahbl4dt4m�4tLX�N!�?.�4eOh.�4s�6cDP!�h�i|Qu�4a�4ilQ��R`�T{����4��WNU)�4t<
�a�4iDZ���4l05mY5f@5kT5s�5t�
^�����85t\u$\uL5al5c|5p�5t�\��+�]�t5a�]pd]�5i�5o0Q2 ^)P&.�5o�krh^�	laDxa���5f�_���5f46sX6uD`���5��=�8J�@J��Y�ܬ�����6n��6e�6h�g��(6cXk)ku@6cd6p0j��H6s�k`XlB�6ao��l�al��x6r��6a|�Ĕ���6nĴX�d7t�����6a$7bl7c�7d�7e�7f�8g�8ip9l0:mX:n$;oL;p|;r8=t�=u�=x�=y���87i��7e@7lH7r�MT�ȸ
X��$�P7ex7o�7t���X7h��������D��7g�7i����J�7l�7sp��H�1 ���7n��7a 8e8�fX8id8ll8r�8u8�l�1x���8�x����8i88r����u08m,yY���D8g��L8n�*@��x8i�
R�,�����8u���l���	�8e�8f�g�8m9n9o(9sH9tT9w�����u�8p�8u���4@�x�������8e9k��a
���@���� 9m89t��=����u@9sX�� ��t�\9u����d9a�9e�si�9l�9m:o:�,�t�c�9k�n�9r(���� ����9s��U�����9u��!�9a0�2��9i��)�9e���:f��2����<����\H�(:d�iH:mP:p���������
t".�:a�:e�:g�:i�o�:s;t;z��t��$�����:b�:n�:r�:t��|���:d��������:g8���N$�)\�u����:t��!�ts;w�������4;n<;p�`��\��D;htky��X;b�;l�;p�;s�;u���`;a�;e(<fL<gl<i�<k�<l�<m�<o=t,=z=��� ��
H�D�������c�;i<l<r��p����;gȟs��_��D���<wD�,$�) <o@<�H�T���8<���N����X�f�<n�<s�<t��T<n��<�x<u�� ��Њ���<r�MI�<e�S��)�<e �&��y�<u`�!�<a��d���<b ���������=���N$=h�h0�)lgw���wa\=eh=hp=ix=s����T=r�yT�y�|���=g����=a�����T��=e>i����=e����=l��=l<����=rX�s�B>e,|
t<[��t".h>a�>c�>d�>eH?fT?kh?l�?n$@oD@pd@r�@s�@tX�u�Z�x>c�>n�[TC\]1�>g�`uP�e�>h�S��R���>r0a�>e�`�>t\a-�>e�>i�a� b=�b?n ?rfm
�d�?sg_,?n�f��?i(g2�Cli)4?r�hu<?fpm)r8p\p��`?a�?f8r�rlx?rlv�?d�?e�?f�?h�?i@n@o@t�w�Pw)�?up��w��?l�?nHxD�x��y��yy�z�,{�)`|!@a�|X4@n<@r�}�X~$
T@h\@i������a�@e�@i�@n�@o�@s��7؀y��=�@eT�2���@r��@�����a�@e�@p�������?�@a����>iT�J
DAa�Ab�Ad�Aelg�Bi(Cl0Cm8Co�Cs Dt0Du�AÀ�2����<AcdAnpAs�At�Au�A�d��5z��P�AX���xA�d����D ��Atai	�����Ad�As�Au #�ز���A��C�PD�@�S\�S��*��g��{bBe,BnHBr�Bs�Bx�7t"�����$B.�g@Bn8��x�ahBg�Bn�Bu�Bw�Bz�O��G����pB���JxBÈ��L���Bn������Bi\�O��8|e&f�BnCpCs Ct���@����Bd��1���Ct̆�D�1��N��l�uTCc\CfdCplCr�Cs*D��|���2xCe���E2���Ci���Cc�Cs�MdRq������Cnd�!�Ca�CeDk�vlDtl�1D��Cr(���l�Cu��1P�Do�����L���(Df@Dk��3S����HDf��)�DaEexEo�Ep�Es�Eu�D�X�����Db�Dg�Dn�Dr(�������`<��Dc�Dh�Dl�Du\����D��E�|�dc�����1.Ee,EiPEnlEr(�!��d���$EnDEs��U��<Et��\Es�\��)����dEa��u�En�������l�l��)�Ep��u,�u	�EaFdFe�Fi�FkGo Gs@GtLGu�u�EdFmFnd���8�$��@FiXFl`FnpFp�Fr�Fx$���8FgPFn8p_$afhFfp��|Fi@���Fe�Fl�Fz�p�l�w��

�Fc\�d�%e�Fm�Fn�Fp��`��T1pGrL��Gk0Gt8!N�)8Gs�!`GchGlxGn�"����#�pGf�Gg�#�,#��Ge<(X}l�&���Gb�Gc$Hd,HeHHf`Hi|Hl4ImPIndIolIptIr�Is�ItJv Jw(Jx0Jy*�HhHk8*��7+-Ht@+�$,*8Hf�)�,��,��@HaXHr�-*(1�2�2hHux2upHa�Hd�He�Hg�Ho�Hs�Hy�Hz�r3!�Hy03��HiH3��35.�Hr052H5\5Dx5\Ie��yY��Ie�5��In<6@IcHId�5u$Ied6�����7u\Io�9[@;1�;1t=����a��d�Ig�@O�D��D��Ii4D�Ie�Il�Ip�ItTE�hE%XF��a�IrPGe H)P&.hJlJs�I�I��Ii J�L�8L�hL�L�}TM�TJc\JrdJs�M_Q2dRtJc|Jt�R�Su	�ju�JaLfc<KdPKe`LfhLgpLi�LoHMrlMsHNt�NuOyOz,K��ktj���Jc�Jd�JiKnKtpl�Xm�,p��o���Je�q�,t�4s��Kuhs�� K��N�4w�z1�KhLyDKc�
d�Kf�Ki�Kl�Kn�
p�KrDLsLLtL)v�z�${�t"��{���K.�Kb�Kc�Kg��J(|c�Ka��	<|Jh}~D�8���KgLk(Ll4Lm<Lz�XH��T�Lb�OLeĄtĀ�����XLa$�D�lԃN̆u�Lc�Le�Ln�Ls@�D�LkDa���-�Ls���H��Llp��Le�Lh��ܥN��e�)�LtD��\����Lb Mf�hl�l0Mm(�n�Or�u�,1��et�u(Me@Mi�6e$�*����PMc�Mn �uL�!	XMa�Mc�Me�Mi�Mk�MoNt Nu(Nw����	��Mh8��	��Mn�Mr�MtP�1��kd��h��Mn��BX��,�f���|]cL�Ne(�uT�!������0NbdNn`�!8NaPghtNo�Nr�NsԜ1��О��lNr$�;�Ni�No8�1D�JtWa�Ne�NtT�u@����NeP�����Nh�Nm�����1�Ns8�ObL���u0�l�!
dOaxOc�Oe�Oi�OoPp��s�Pt|Qu��yԥ����POhĤXOc�n �u��Oh��Da\�����Oc��l�Or�Ox`�I�����Ol�D��Id����Ogh��On����,�f|�D�r0��Oa(Pe h<Po\Pr��=�� Prl���4Pr������HP������oPP�U�h���lPt����tPl�Pn|�r�Pu��Pa�Pe�-hQi0Qo<QrhQu�P���u��,�W�����Pl����P�D��tQ�����a8-l$��0���Qe Qn��=D�����(Qr���TQa`Qi(�����LQn\�h�_0.n���(�)�Qn�#����Qg��!�Qa�Re�Th Ui4Un@Uo�UrPVs�Vt�Vu�z�R�x�)Rk Rl4Rm<Rn\Rs|Rt�OL�!�Q.Rs��Qt�4���D,RtUY@�_��LReTRk�������DlRstRy\SY�T���y�=T����Rr�����R�T���
�1.�Rc�Rf�Rh0Si�Sl�Sm�Sn�Sr�Ts�Tt�Tu�Tx��D��h��Sa SeS�4�2������S���ux������(SfPSl\SmdSsp�k��HSz����r<��,��lSt,���tSi�����Sp,��Stp����!�Sf�Ss��������)�SoT�h����SfP/h,TkLTr`Ts�Tz�������T����Tn��) Te<Tl��������)DTe(����!XTc|Tp�Tt|��t�%tTaH���Ta�Te8��|����g�8���Te�TsD�������X����)�TeUo����iUuX�����,������Un
��=	,Ui$�XUllUr\5�uPUy�I����dUg�m����xUk�Un�Ut�Uud�;�Ua�UiVo@Vu(V�h�L����Ud�q� ���Ue���Um$J�Unx��� �lVs���H8Vs���V�HV�h�Pt���PJtVp�Vt`f����`VilhVr��;���Vb���VaHi�Vu�V�`�h��Vr��`�����Vr4���V�,g�Vi���	A�	���VeWr@t���Ws�<�� WadWb�Wh�Wk�Wl�Xm�Xn�Xp�Xr�XsXYtl))\Wa�We�Wl�Wr�Wu�(14���Wi�Wn�s�\B��\�u@1)p�Wa�Wi$>)������Wt(��Wa,XetXi�XoX�41@��X����t$XiLXn\XrdXt��Y�DXg��t�1�_���lXn�uHd���Xa�Xs� ��!��Xa�Xi�!"($#
p&��c.�Xsg�'1�Xg�(ND**u�Xa]oYs8,JL,!Ya8Yi�0p�0t�c�,0Yc0��,.��DYb�-)LYatYh|Yi�Yt�Yz8/y��u`0��0�0���Yn�Yr�0!�YeDp�1u�Yb�Yh|�M�1�Ys�Yu\���b%�bu�Yel4���Yn89)Zi|= Zl>��@uLZaXZe�9d�A8Zl`@@Zl�C���cpZi(|�|DhZb�Q�0P��|Zg�Zp�Zt0S��Zec1hS��Z.xT�U�_o�Zu�`*8`���Zg�t".�[a�[b�[c�[f�[g�[h�[i�[j\k\lL]m\]n�]p�]q�]r�]s(^tp^u�^v���Za_b�`c\ad�be�hf�jg�lh mi4cjpmk\pl tmlvn�|o$p�q�r��s�t|�u��vX�wx�x��z�^ÐJ�)�[l�!�|#1*u�[l�*
�+��.u$1�2)�2u��.\t@451�:.\\ax\b�\d�\e�\i�\k]l ]o,]s4]t<]uD]zl\��4*x61�6��d\��6`77�7��i�\n�\r�\tp�m�7}�\t08z88���\f�\h�\mP/`8&�8�\�F9��\a�9l�9]a�9X;1;�]r�;7�;��<u4=BL=(
��.�oh@1�]a�]d�]e�]n��o�]t�]z�@1<@�]l�A��B��E�F� H�I)�]fhIyJ)J����.ȶa|Q1^cTh^i^p�s ^t�Q��R�S-�S�U)�1.H^aP^eX^h`^rh^sTU�U�xV	W2|WDYy�^n�^s�[$\��^X�_1�6m�^r�^tD`������^�����~�����e���?.�^sg��h���?.�^a�^s�h�XiXl)
@_a`_b|_e�_i�_l�_o`rD`sh`u�_��opfl��4_u|��pL_iTp!T_lDr�_g�_s�p��l_i�_n�r��rq�s�_at�x�|x�_k{�z�_a�_e\{D}@1(p���_�����"��;$`e`�~$�`n����0`e|�!8`t�$��}P`kȳt\���X`n�`s���@�ux`c�u�`c�`h<aiDak�&l@�)؊\��`a�`e�`ial�$m ao��r0at�`uغw��D�\$i�an��|��ao8C�	��U��(arX�p�-aeTas�l�	�aa�ae bi4bnLbo`brpbst�by�� Ĕ���ab�am�an�u�|�u\�8���ae�ai�albs���X��d����a�����a�4�h��bo�(p(��X�bo�|[���,biT�Xb.ԧ��@bl�2 ���D��)hbc��p��{Ĵ�����ba�bbDcc��d\�excf�cgdi(dl�dn�fo�fr�gs�gt�hu�hxp1�����b���@7lcr0cs�b���ȸece��2��co<cr�� ctĹ��Pch$�P�o��� ��\ck�cnh*u�dca�cfd8l�crH����t�1@���co��7���cl�cn�cr�cs���'�ca�'���dcdt�+��Jl�t�XHssLdu����daTdd\de�dl�do�ss�dt �y��7,�t�c�drh�{��ldbX�Itde ����dg��OX�����db�(�!�da����1. eaXed`ee�ef�eg�eh�ej�ek�em�en�eo�ep�erfs�uxfv�fw�fzHe�t��0eb8eg������������@e��e� ����|bper���ef�eg��g
��>p���G
�ee�
E���ef��4c����tJ���D|��	������y���fe fk@ft�z0���,	��W(ffPfp\�0foXfr	��flf�l�O����df�$�d������l���fdT�p����fa�fegi0gk8gnpgs�gtXg�l�X����frgsguD�D�ffgzt�JĀJ��������(gn�������=�$.Hgi��U�������Pg�H�y�gh�!dge�gt�p8����ga����!�ge���4�!�gp�gt��gs�gu�!��$�)���ga0heLhhXho|hr�hs�hthh��������hh<ht�� hr�Y����Dhox������`h�$���ethi�u�<e@�O��|����heT�n�T�u�haielif�il�ir jt����hb�hr`*tu�uim�N�1�ic�i0in@ir�u��O���8igPih���l���Xih�)`ia�iedal���ie��it�isl	�ia�iÐ	=�	�����i��
z��iaje���J�iu�q�p
"ji�
2|uP!jaPjedjoxjr�js���h���<jk���DjrD�L��\jp$�`;pji�{�p#z�)�ja,ke`kilkl�kn�ko�krls$1|���ji�jn�jrksI����jg�Op���jt��T�� ��kr�xka, kf@knLkr�,aH��$!z�!�!��XkmH"u�k�,#@#��xk�t%�8%�ka�ke�ko�kÐ%��%���k��%�	�%�ku &=�&�';ba�ke�(�(�ki�*u��l0lu�*Jla8le@loHlt�+$	�+Nh,�
�,�.i`lo�lrpl��-���=(-��hl���e�-�
|le�1)�la�le�lm�ln�lrmsmu1w�3�@1���lm��r�6�`e�lnX:\D�E�J O��p�X�(W��mm<[DmaLmeTmg�bmH&n\mshmt�Zu�b�j��t".��ܑ)�madnbpne�ni olLonhoo�or�os�ot puLn�D�����mk�mm�mnnr$nt0nu4����3t�E8�����mo�mzX��neLHj��
����nt���d�)nt���<���8nr���@n�|o�\�p�\nu�2�ni�nl�nr�nt�������[̚���ne�nf�nh�nl$���na�a��nhx���i��I���naH����1.on@�odPw��B0oa@o����i����8o���XooX�����`op\��tol��;|Ea�oe�oo�o�$�4����o�X������0�)�oo�oz��y��pr��!�oeprЧ��<jkȨupel�� ����4n0ps��D���pb�pd4�k�pm�pn�ps�puT���8paqb$qc,qd�qerf`ri�rlDsm�so�st�su�sv�szq�ȫ.��bح�d����5s������p ��pf�psqt`�D����pbl�D�����oز��q���cH����!��aXqepqot7r��
����Dqt��Lqnhqr�uH�|qr@�_�t�c�qh�qi�ql�qnrr����qe��"� �����qd�qn�D��t�p�!�qs�����qt���08fX�g��r,�l(rl8rrDrs��Bp
���0re�>l��,�uLrn�Tre�rg�rn�rp�j��rbx��xra,k��g@����rd���6.�rs����!P&.�rasesissX�y�rd�rn��k���P�D|�sr�y��d��1��$sk\snhstpsu��),sa�si �M����Tsg��.��_P�1`�xsn���sr���l�y��!�shL���sm�ss��1H�DL�*8V����sr��)�sate�������tk����tr����t".`ta�te`ui�um�un�uo�upvs$vt4@u����,�w��Ttnttr�tt�����m��!�tr�ts�tuWt�W9��u�tc�2�X���trH�����tg�tjԉkul$ur,>sDut@>wL�X�8�����teufD��$�g����uf4uzd�gT��p�<ui<���tyf�s��Punl�x�lui�un��)tue�uo��Y�����ut�����B,Ui����ur ����Ig��

��)�uo�usvug-�ueDg��I����hr��)vt�0vup��y�vk�8vc�vd�vf�vm�vp�vrwswu,���DvaPwd�we�xf,yg�yh�yi@zk�zm�zn,{o,&p�{r�{s`|t�|u�|vp�y�|z,w�X&�d����u���xX��vaܘswt<�~D�	��$ws<�{���@A�t{�T���@�llwntwu$�)<wa�we�wo�wr�wup�1����8�|wt���ws�wx��2l�����i�wÔ��	���w�0�GP��ws��xb,xh8xiHxnlxr�xs�xu�xx��Hxe�"���$xe��,I$@xg���)Txe�xi@��\xb�xh�xl�xt�d�������x��!�x�`���!�xr�����xk��xn��C�w��yo�	Xb.ys4D�
=@yfHyg�	!ya�dl\ys��+��*ud.mH�PyaXDc�����b�yr�yu�llya�ye�/���.
�Dd�ye�yg�yk�yr�yszt�
@�%t��yr�u��ze�.P�
<1zz@O���� zn(zr�)4zadzs��8��Pze�)Xzt,������pze��xzlt*�za�!P&.�ze�zl�zo{s {t�Y|���zmX�zrl����zr2�za�{t24){ap��EdP{l\{rd{sl{tH5 uH{sT�xD���d<*P�D��{m���{a�{c�{e|k0|t@|u�Fz��t�{h���b�{n�{rPWl�h���{t��@|nLl|a�Fr2(|a��ÌP|fX|mH/��p�!||e�|h�|r,����t|g�� ��!�|m�|n�"Dt&�#��|z|=�� Zl$l�|iT&u�$)�|w�&)D}aL}cT}dh}ex}f�}h�}i�}k�}l�}m�}n,~pX~r�~s�~t�~u�~v�~x�~z�&*)@+`}ap��$,B|-��,��p}l\/H1(1u�}d�1x2u�1.�5u�1.�}s|���7�1.�}a~d~s$~yP�B�7�}at7��}l~u8'
|80:�;1�;)@~fH~sP~t<<JT=�l=�t=u��.x~c�~e�~p��s�~t�>�~�?��~n����B4D��~tXF� H)�1.�~s JD�J�L)hL�L)�1. O�TM���~kns�PdR�?.�S��t".�eaXexf�h�i�l�p�r�s�udXhnpr�Y�0Z[�aH[�\�l^��e@_,_���l�cn�`.0d)�el�Iu\ejgXdi�tjL�dT�k\�t�j���ax�bLfc��e��g̀h؀i�k�l �m��n��o�p�r@�sd�tx�ud��pl��m��q�hs��(K����g�pt)�KlLy��e��k{L}�L�ԃN Ml��s��|�u@�xĀĕ�g8�X�fl@��y�n��!��i��$sk8�ux�!�a\�ep�uH���_�2���@��d��,�T�i�1ؐh�m��nL������tl�'
0���r��J��a��oȒu\�́nȔ,�`���
ԁa��l܁l�{2��y�i$�)��e�h̀;��� �w�(�hL�!4�cP�h$hD�J�t`�!X�sС���p�s�
��a��b�c�e�h(�i@�mH�o��p�s̆t��u��Ĥ��c�m܂p�r �u��XШ�l�e��H����nt������<���Qa<�h�rd����(�r��0�a`�e��lHRm��o��rԃw��p�ft�h|�iH����������et���U�����b ��,���eău�NLZ��̃a�i�o�u4I������r�Ij�Jy4�����<�eL�h|�i��n��rԄs�tl�G
�A�DD�a\�id�D�ܱd�f��t����l�n`|������d��sԳX������`�hĄmHu��I4�I��̄s���ܶ���tĶ��a�4Է�s��)�aĸh� �a8�d���N����,�f��nH�p��t����`�d̿�h�n��Dt�e H�0�?��aąeԅi<Po��r|���r�=���^�h�̅c�t|`1��L0a�c$�u��!�aT�c`�t��u4���������D����,������@�e�H�hL�)x�a��e��o��u<����������������c��mh�1��q������b��m�Pn���a�e�r@�u4y`�Ì���a�n<Tr����;.a�e,�ô�������$��`�=h�8�r46S����L�m���T��� ���l�p(���t�m����e��O�����s����rx�$�bD�lL�md�nx�r�t��u��x����
��a�el�hx�i��l�m �o@�r�sċt�uP�z���P&0��.4�sĥx��)����<�aT�tUI��I����\�g<����p�e��tp�I����c�YM
4��T��Ԉr܈s�t�������8��H���14�<�?T��i �l4�nP�p\�x��1�g��n��,�=,�a�(
,�@�s���\��\�H�oX�1�=��d�r����d��i��kԉn�s�v,�� m�m2 ���n0�)��ảepnU,��n�zP����8�������nt�l�e$�0�c(�f��D�d��
h�aXWeĊiЊo�u���c�	����`�.��f��n��s��u�DL����D���@����S��u������$�<c���`Wm(�ntR�n���u<+gPu�a�e4�pT�tt*$�r�vC�l,�e<���@�f��H�al�e��i��o@�|]c��m��s��n��t1��eQ��������b܋m,!��e�r���ԋp�;thi�	�h�m �n4�r@�t0
�p�
}�s�(O���,�gd*p`
p�eYw<��D����g�
��d�r<B��m��sd}-�*u��t�189)̌a�eL�u�2�9��Čk܌n�9��\�g�:-�:��b�i�l$�n$;�(�OP;���t�;4�e<�t�; ��I'�D@��D�r�@)l�uTK�Jd�r�K)��a��e��i��t�K)�L)�L��mLM�NNU)�a�eH�i\�o|�wp��I��T܍p�u�V��W����c�W���i$�n8�rY0�e��8�YJ^��\��@�rD��T^��T�b_1�V��h��blT�����l�Kp�0y��i|0u��e�s�.����r�����h�l�n4�t��ȎaH�e,�iL�oАuL�vlM�5����a�B�h@���e,�i|DCUXĴ�p�n����<�a|�g��k܏n�)p�tPFF����h�s��,�2@�����r����e��ȍr`���t���\���rȨt�h����ԏa���ga�h�r�t�u����@�7�	l�Z<[��$�a<�v����&F�&��D�ap�b��i��n��r��sĐu<(�|�r�)u(1��7����it=����a��d?l4D�~c@K�J��l<��b�g0�kL�nl�r��t\�)�l&(�d(�n�)�ep+�$/�#!8�s�!}@�g\�i($\)M'��d�ol.,
�-)x�e��1.H�ap�b@c�d�f(�g0�iD�k��l4�m��nd�ot�p��r,�sd�t��u����ap�b��c��d�e�f �g(�hH�i�j�zk�l�|m�n��o�'pГq��r0�s��t �uL�v�wܪz�ÐyT�rdp�p�Tp!\�l�d�b��e��h��l̒oԒr�lsܒt�!3"�
�!���a���"9"�����`"R	�"�4#I')�a��rd'�)A�*�*���l��r �tp+��+)$1����n<3��2��<�a\�e�3��3�T�nl5G�4h�.��n5��p�a��e��k�l�o(�t�51�7R�7���iēn�r�7}6.ԓs`�ND�R88��ܓi�9G�9)�a�r���9O;'l<f�;! �rL=���'ap�p�T���D�f,�L�rX?uX�ed?)d�f�@u<@|�lДsؔuh@����a�d�]e�g��k�nl�n�sD�tP�u,�wX�z0AZ���A*�a�AWTCI�D�E`�E�aPF�P&.`GghGD$�nPG��,�n�F)8�e�G� H)P&.�H�hI�Il�fxJ�J����ahbl4d��gȕi�k�m�pܘs�t$�u�w(MHM�ܕeH�htMuԕsNX��p��f�(
DP7�a�i��=xPu�P�|Q���4i@�pH�sSl\S7��DTU��P�nU)X�a�h|�r��tW|�Wd�)DZ����o��r̖sY��f�sؖt�a���!Ėp ^B�5o�_�h(�m4�n<�rD�sd�tD`����������Dbd,d�� �iXd�e�g���ecT�etg�,iy�h��\�hXl��e��o��y�p}�}}����nt�u��BЗa<�%�����pĔ��ėm������ܗc(�f@�gd�h��i�l��męn��o��p̚r��s��tԜu�x���f$(i(�u��8�eP�l��u��t".H���X�e��rlM���o��tL�ut�s�M�NJ��l�����c��gȘn�s��g��ؘd�hL�A���l�����et�u4�cPNg<�u������aX�b`�e��l��s��tL�À� ��������D����,� kx�n<r��t��1������!��e���!��k(��H���p����t".�a�g0�l�o<�sp�t��yt�)�u���������g��!�a���ز����lB$�è�� fkX�t�z���\�P�e8�=��n��!d�e��h��r��s��u�twH������1�!=��H�����l���t".�c$�fP�gx�h��i̛k�l<�nH�o�sP�z\�Xl�����h0�m$�)�aL=jh�J\�nt�8�b��!D�e`��^����d����l�Ø����<n��st����n0|���,��tę2�����n��)��eܛo��)`���a�e ����y�uH�� �eT��b�������(�u��J0�e��0�lh�et�i|�2����`�u�����|�.��e��14���l$�!����a�hĜs�L��|���̜p�s����*�l�*�h|�u��c��T��l�1*<�o4I�G��4�m<[��
Dma��c��dĝe�f��k�l4�m@�n��o��r��s̞z�`�`u��h�a�\a��e��r`b
(duНo�b����l�d��i��hu؝l�r�i�pm8p_\p���a�i$�o`r�s�`u� t��,�ilv��t".h�gp�hx�i�n��o��s�yt,y�y��y�,{��{_��e�{��|)<@r��s�~���(	p��t��X؞iH��T�<.�aH�e�id�oĠu�y��Ü:b�����.,�r<�s�2�����4�t�u �.��a��h��i��m��r̟uHMR����l�it�)t�r����������d��t̺����$.��x�aȿ���s����؟c@|g�n�p�r(�sH�tT�z@�1���1��)�� �c8�t̆<h��D���@�s��*R	�\�c|�i��p��s�:|�2`��
����t,S����tز�������L�Рf���H2�
����ؠc��,���a$�eX�op�u��à�V��b�4�i<�r��@���D�lpL�bh�rT�!�#����x���&ءa�b�f$�h�&i0�lT�mh�n��p�r<�sP�t��u��w(Jx�'u�&BСll)I<(�j�,��,����e��f���/J�u\/���lx2u�&aD�eL�k03�38�5D`�u`7J�7u|�e��s��u�8�0:;�;��P&.��e̢fآs�z<�[2x<��n<<����eT=����t=��~a�c(�d4�i�>g���$x���gT?�r?)�e�@4D)�Ip`H H)H�ad�sx�t Jl\��Xk�J!l�a�J�8L���eHLuTM��f\l`N��j;ܣaX�e�hȤi�o$�u4��tj���d��t�u�z�q�	�q��sLr�
�q�m�_c4s���tL�uhs��$������j,tD�mLy\'.�(c��d��f��g��i�$�	�zx�.�(e�{���Kc��e��h4|���r�f��l\4��̆��b�
c�e��p�sH�i	��u�s$=�\ȔM
\����n(�����f�)d�a��c��e�i��o�p��s8�t��u��z���Ĥ�m�n �u��1H���x�nt�������h����b�mĥnХr�d������t����rl�s,�w<�Ih�8�d�Mn��l�f��|�0��a0�e<Po��u�����l�a�e��i0Qo��r�Tu$�
����X�z����`�n|�tp�0���D�sl�����������;���(�)��!��a��e�h��iX�oȨr�s�t�u�zt��0�4�sx����b �d0�k<�lL�m�nD�rX�u���L��(�t����P�e<����;4�T���`�s����h��T���<�e��i�Sm��nЧr\�x����d,�����t����)��oh���ħfP/h�u��!��)�o������d�m �nL�t���I,����g0�s��h�2��y8�r����@�e$���(�fl�p��rd�<;e�&�Ox�a������g��iA�	8���eL�2������n��sd�;��aܨo�u�Jt�nPB��t�.,N�	�� �n`
7<��	ljcL�hh�l��m��n�p�r��s���p�X�r��(�`�a��e��i��pty�y��� -d����l��s� ��!��!}��aԩsȳt@%3�tx%[�p&���.(()�Kl'����b,�e8�iD�oT�s�{1\($�i)e\)���)!L�pd�t�)J�*�?l�*l�h*ux�c�kp��u�-u�-�������1D�b�cЪr�7�D7��ȪsUB�e�X��W���l����:.ȫb��c��dܬf�gT�h`�i��k��lحmd�n��o��p�r��sd�t �u��wX������a��bH�c��d�e,�f �g�h�i��j��k��l��m��n�o��p�q�rd�s��tL�uL�v��w�x�y��zز����a�b�e$f$�g��h,�l4�o<�r\�sd�ul�w8 �Tpd�s`� ���n�!��!�`"_�"L�aT�i�"؀J�"d@#\#g�#t".|#��t�e'���i��l��mĬr̬uԬv�(UT�����)7D)2p��*1�a�t�))p+7$,�+��a$�i0�n8�o�|X�-���o�-��&D�b<(�H0)�.��L�o$1t�s̆!2�l�t�3��xs�2����e�mit�k��l�3�5���lĭp�9M�;H>��nL=��̭i�m�p$�t@�u<��>J�>)��a�f��=d?X�?I0�s(����a4@�8�n�@S<@L�lؔuh@��T�ap�b��d��f�g �h8�lL�n�pT�s|�t��z�A���aЮe�r�AB��D,�����wܮz�A��r0�DBD)TC!�l�sDD��J�����tD�0�e����E�D�i��
�E�PFGh��<FRDF��`���GZ�F)t�r�Ha H)��w�HD�IAI��oįp�Ig�I)��l�J�xJЯnJ��دa�e0�gL�iX�t�u�L�L��i$�np~e�L�e(Mm�MR�M��8�iHM�@�nDPtd�ixP��Q�lQl�u|Q��	t�a��d��e��hİḭo԰p�s�tD�sXRy�Rg
�R)�R{S�P�E
�Syܰr\S!�e�S��S��a �o�or0�uD����`��T(�r��=T��<���T����P��U)�a��e��h��r��sȱt�Uy��l8V2xV�8W�W���a|W��W~ܱn�W!��a�i��r X2,�p�����n|X��J�!�e�YX�sY�bD�f\�gl�s��t�uDZ��<�oT�z���Z�]�$\ud�l��r��s$]�,]� ^)�5o�^���a`@;�_���c�f�h�n4�r@�sL�t\�uD`�������������xa=�b�Db����n�d�Xd���d��)�f! �a�e��(�m�g���ec�h$j�0j��T�bt�c|�e��flj��j��jGXl)ija�e�i@�l��r��s�u<�z�ll����c�q��qгe�p��سd��t�w�w��a\a�|x�d �nPy���s@{�{��,�t�z4�ap�eL_ix�od�ð���|��\��\{x|D\'$���.�;��e��i̋D؀��t|��Դaܴe�k�p��t������Nh�u��	���e�o0�2\� �f���������(�i��B0�e�)d�h��l��oD�\�\�e��i�$l%r��sغw����������$&� �C`��!
�a��e$�iH�oh�r��sطt�u�y��Ä��Ĕ���b$�c8�d@�gČkH�lP�md�nl�rx�s��t��u�D0�kX&Z'�@����\�m���|��t��l�i� ��\��������r�������`�����aiضl�r�s�Ax|�)���жe����d�.�p���p������a�e���Ȥ5X��c�d8�m���ܨԧ��@�rX�s��)�� �u��a��e��u��à�=L�����n(��8���2���������)��aзt���
��*�h�h��P��m����s������1.t�a��b��c�d�e0�fT�g��h��i(�kD�l��m��n�o�p�r�s��tȿu�v�xĴ)�dԷ����a��e@7l4�� �u��s�����n�1иh�$1$�ȸi�t�����eD���d�ex����y��� �i�� ���(�a��|��<�sl�u��D�aP�l� r|���_1 ��t��H���r��s��t��yL�u��e����QMl���	�$.�b̛c�d�g�n��s̺t���`��c�r(�X����P&.$�d8�eX�k`�tL��0�u\��x�L�r\x�lx��D�b���`|dP����h�r�!p�e��|�s��t��e������r����e�^�������ܺa�e��u(����!�ch����s���r����a4�r`��tȨ�t�2����<�aX�ed�s,� k�7p����l�sH�t�e��o��p��l������
t".�a�d��e �fp�i(�k8�nD�o`�sp�t̼z���kt�)�d<��\��t �)��e�u��<p����X����!0�i����
��L�m���T�e�z��!�ts��w��z����e��� C�<C����� ��(�����t4���%u��)ļi���
��uؼs����a�f�r�~��\e����t".`�ax�b��eĽf�gD�iP�kp�l�o��r��s��t�w�z�����p�g`u��%,�*p����g��m����i��r�|��xD�����sD��$�)��oؽr��8�H�pT�������E
t��h �n(�w��!��e0�i��`�
�������<s��8�n��l`�aܛo`�)T�`�!h�e�������|��`���)��a�!LC.��kP���7�E �E��ľb����̾r��)ؾe0����a`��P��{m�	�a<�e�h\�i(kd�st�t��w��y4�L�lT�r������`4�7��)@�l�e����7p�$�����t����a��h��o�7x��|�ܿd�r��tpl�.\����o�����,�)����e��i$�z�kqT�)X�ad�e��l�r�t��u��l��P�h��*cx�e��i !��	x����s�	i�	�����l	���o(�u���t
<
	����m���a`P!�r��r {�������)H�a �dh�e�ko�r�s�|��@�t0yT�a,\�n��r��t<hH��|�a���,t�����u0����� <ui�(�';��e�o�)�*��t�,!�1l�i$>��6.<[\�a��b��c��d,�ex�g��k��l�m@�n|�o��p��sD�t�Mu��x��z$���Z�t�c�xk|�m��r�[�L]]�]_��i�_[�`)��h�Lk0:5�`��m��r�`��e��i�E
�`�ay\a��a�e�o$�s���a�oLbpb7�bu�$.X�bl�n0�s�v	���D�s�bL�e`eR�d�d�e�j���n��r��s�k�k��k;��elNpm)�1er��s��t���o)��pp2�o��r�o!��e\p��a8p t1$�a�<b,�o`ty�u2Dvy��llv��4�a��d��etyf��gܥh�i��j�k$�o0�s�ytl�vt�z��w�Pw)��utk�w���d��f��h��p��s��,x1��x�\y,y!��s4cr4z)@z)��a�sdz�P{,{��l�{�P&.L�aT�c\�pd�t�{)�{)h=0|<�|��|��|���m@�n��s�}J�~��1.�e$����a���$.��a��c�l�ozp�s��y���<���u���h4��H���g��)�^����D�1��0�l���8�a�e`�hh�s|�ul��l#z4�1�t�r���x�)��i����)��a��e�������i�7ܑ)�a@�eL�lt�n��o��r��s��À����3p����l�n �r��D����6.p�7ę��,�t���4�n��\�od�ud�Ġ$�J��l�e�>������b����rܣJ��;��a��o��u��$��0����À��������������T�!X�a�Ab\�ch�d|�elg�i@�kH�mP�nd�o��s4�th�u��zD���uȫ�@�e��t����	H�b��f��k��l��m��n�	p��r�u4#d�*�ܬ1��f@4O������t��Dح��'ad���g��w�5z��G�NO�����m ��fp6g(�spZeD����el�R	d����0�mز��8�����d�H�)T�h�������h�
p�c��e��f��g��i�m�nP�r��s��xи1
 �����n0�� ��T���t��u,���������m��)����6.(�a8�d�g�qt�7T���)0�lH�u�D���6.x�a|�f��g��n��t��z��Ľ)t�o �K����n�!��e��o��j �\��J��t����7��r�g�)�1�c;,�u��g���e$�m0�n�u��1�p@����rd�9s��*������X����e�X�b��f��p��r��u|����t�|�����f���6.��e��S<�+���f��s�RdR��e��hd�!��h��k�p(�t(�0�rD������rP��o���L�iX�s������D�m������L���`�f=r��s��YH�u|�t��B��)��a��c�e`�i��p��s��tx��X������b��r��u�����c$�����s �)���e0�i<�pD�rX��	�����sd���$�n�������4uz<����1d�!s��P�n��tol\���l������f��(
�y��)��z�7,�J��a��e��i�[u����l�e���r�
����G��&X�b��f��g��h�i�k0�lT�mh�n��o|�p��r��s��t��v�x����<(P�.|�e�j��l��r8)1�(t�rt)��)e��i�)�,��,����e��l��r|-n�-�p.��l�u@.)��a�/y\/����e�r�0�x1(1u�r�1�(�rT2|x2u�&a�Hy�61�6@�n�5uH�i�96�7��`�o<<=�;��t�f��tl=�t=����a�c��d��e��g��qL~�?��?�`n�@�B�4D�$.�a�e�k,�p@�s`�ttD��D]P�2E��e|E'
hE�$�e�E�E!8�e����F�L�uXFT�et�o|�r4G�PGc��?�G����u +�����`H) H)��a��h��i0I���a��i,��\I��Z�I����oL��e�:�hL�TM8b4�d<�fP�g`�hl�l|��DNq`N�H�e�D�N�b,�N��X�rHO�S�
 S��t���S)��a��e��f��h��i��t�Iu�Sy�Y�dX��n[^��\��o`	l^����p�g�jB,�a8�oL�uX���q-�q��stj�� �t\�����m��J��D�ths������!��a�cD�e��h��i��l��o(�p`sP�t��u��zĤ��c��d��l��m��n��rD�u��ئDP�x+bħ��f��n�EtL2Ш���e����i8�m��h<�hĭ��$��Э�,�ì���b�Oc�ml�r�s��x�"����d�e��grl�s,�wx��
�RԷ���)��a��o,��ugP�����ph���mFu	4�u��e�����n������h�r�B!��.8����th�D0� �iDSo��rx�u��h�bp�f�@�ax�i��r����p<�10���D�s��t��=���(�����m��n��!
��a��el�h��i��o0�r��s�t��u���0��4�sx�����b�g�k$�m@�nX�rp�t|�u,�J�"@��0�id��	�3�����8�d\�ghl���P�b��tX����)h�o���T���	�1.��h��i��l��n�r8�s\�t�Tuh������dSs��s|���m,�����e��i,�R,�D���h�����a�g$����z��e������P����0�kP�td��d��H�r��6.��h��)�e�o��uX��u�����o������ml��$�����b(�fDp��rw8��������i4�1
�����l�s�t�������L�����m2�����kL�ld�; �aXWex�i��o`��m�����u���T��t�`�$p�e�������c��s���PJ��c��p��t0T�l��a���Va��o�4�u��c�	���h�m �r�'2��n����a0�i,u\u8�n<��@�a��b�Yd��e��f�gزh��i��m��n�o�p �rH�s\�t��xH�����a��sl�NL-)��s�вe��f��t��\!h+a��e�rL)`=	�)�a8�e\�ld�ol�r|�s�!u��(0�bP�r���8��H�pH7���Jt�a\ac.@u��d �d����e,�f<�lD�p�Xs��w����!���a��f��iȳt��w�!8($8&I&��&up&���f'10�n8�t��=�)��*)*u@�e�kpd�s��t�uL,!	Ya�ec��e��i��o�Sppht��u��ì���,�������c�,��k��r�,��,����f��kĵ��,J�,)������c-�a,�o4�r ���,���=�����0Q1�-��e�-����@��,.)�-)T�a|�e��o��r�.-l.��t�g��r�.��L�g�,X/����f��p�/1�/H1��1��D�b��d��h�2c5!l4����l�@�Ku0P8�aD�cT�m`�nt�o��s��t��uJP��0�r�P�R��R��L�p�Rl�o�Rh�R�TxT�|�e�TU)��a��e�i �o,�tH�ul�wp�ØU��T��c��nVDXZ%�Y����k�W����r`]���1d�\���n(��T^���fx_B$e�`u4�e8`��<�f`�ga��`)X�sb���e�1�b��x�c�P�aX�b��c��d��f(�g��i��k��l|�m��n��o��p��r8�s��t�u0�yp������a��b �c4�d��e��f��g��h��iL�j��k��l��m��n��o��p�q��r��s�t��u��v��w��y�z\�Ð���t�e|�k��r\�s��t� 1�!��"�%f0%���r�#��t|#����h��i<&ud''��a��r��s)�()�*�,
t".D�bLIfL�gԉkT�p\�sh�tp�v@>w�+��ex�g��i��n��o���4-qLC. ��X�-I�-��(�e�-�&T2�2���e$1����s@4I�2����t���t�4��k��n�t,3u5����a�\d,�e8�iH�l`�u@4M\6�88�7�$�r9�:�9!@�oX�t4���<�l�t\�.�>L=��t�m�@�<@��lؔuh@��
��ap�b��c��d��e�f �g,�h�nl4n4�s �tp�z�AB8�B����s�B���r�tC�CX(D�TC!�rtD%PF�P&.L�ah�c\���<<F�DF��T��xF� H��Hu�Hux�r�IXI��p@K�J����b��g��o��r�s(�u0�z(M�XO���dxO8�O�Э2P��m �rP��h�O!�c,���P�(Q�|QP�p0t|�AS�H�a`�e����T����h��LU�TU��|�b��nU)��a��c��e��s��1�U-8V1�U��l��r�J\V����d�W|Wu��e�p�W�
Yp�f�^n$�s�\J$\u�g\_J�oo�_|�h��i��n��r��t��uD`��<��L�����<���b,Db��t�nc��d�Xd����d�f��e����gPi.�h����rku@6c0j����sXll�e�l�r��y�q��p����e�z����u,�h\��)P�al�e��s�u`��Ĕ�������X�����aiȮ��)x�e����b��c��d(�eH�gT�hd�i��l��m��np�o��rp�sp�t��u�wx��ì�x�)�[�eD���i�yԤ;�n����������J �f8�n �N	|���@�aH����l���\�f��g��l��n��s���<wJL�)��a������dH������o��t��)<��t���m�u������a�d�eD�fX�sd�t �_��(��,��k,�r4�t �1����?���)<�.D�u�!P�e(����e�������p�nL���x�r\��H���i����t".��a��g�o�s@�tt�)0eb��ltr�u,�����!��e��l��P�����r���(�k0�o0�NXu���!8�aT�nd�w�������\�i��)|�u���������`���������$.��a��e�g4�iH�sd�z�����p �.��N��=��s����n�rXd�����n ������!�Ø��@�dt��$�n������J����P�n0�)X�e����a��e��h��sH�tX�u���ئ1P���l��2�������4���r��j���(�4�!��a�e$�i0�o8�p���������g\��rp�c���g�����l��=@�@�er�����$�y��h�ah=h|�}T�*��i(X��l�N�1*<[��a�b��c$�dD�e��g��k��l �m<�n��r��s8�t,���Z��d�xk�_`_)�i�a\a�aLa%��0�r�b��8�dd!i`�lh�r��t(d��fu|�e�f�f�t�r�g)��iT�J�j�n1�m��rpm)��a��i��u�nu pu\p��a�c�z8pyd�d�e$q���h\�2�s t����p�w��?nX�ulv��,�e`�g�ix�o��u�x�,y!�!ap�s\y#,{P�|�����$.�a��i��s��w؀u	@��\��,2a��c�e�s0�<���a�Wn��w���hԃ
��!zc�t`�!��{�^��$������a �hX�r`�s<�u��z@��Bp�ax�u�9�P�	ܑ*��a��r��\'X���.��;��eT�l��c��l��sH���7��)�ad�dx�eP�it�p|�s��u��Ì��Db(�k0�l@�n\�u������ �O����8�g��sT�zp�g��4�ld���s��l�i��l��n��r(�s������t��2T����n������i��!������a��td�(@�!��w��l������a�e(�2���c��p�� �a<�����b�?f�?h�!s��t��4�nh�s����`�c����ld1����m��n��t�?<���l\������,�B��e�e����i���s�&D�a�b<�dh�g��h��i��k��l��m��n��o�p �r��s��t(Jx�'y@+�T�e`�r���p+L�s,@.)P&.��a�.1p.��x�l\/(1:H2l�1)��lx2uP&.�5D��e<6�7D��e��s�8���r�8�0:_@;��;�� 'e��t�=�<�rt=���a�cL�dl�ex�kH>��>�?)D�a\�r�?84@��?�d�rdA*4D���t�F��F���rXF��e��o4G1 H�TMTJc��k\l O�S)��fp�i��o��t�Iu[u,�eH�ld�o��Y�[���g8�p@�r�Cz�[�r�����<\�T�il\�\��\\�rl^�b�ax�n�gh�g)��a�jB�)
��a�c �eT�i`�ol�p`s��t��u��ħ���dĤ��n�+s��H�t�����������h���f8�rD������0�f��wd����ih�H�n����0�r0���e��o��r��u��øXr����d�4Pr��t���H�J������g��l���ah�=(�����md�1��cH2��!
0�ap�e�h��i�m(�s�tp�u�z��x���LUb�k�UmH�r�T�e<�R��O,���\�tT���d�l��r��t�Tu��gh�����f��g��l��s��t���@�I��I|�I������a��������m�n�s�,,�����s����s������PJtWa@�eP�k\�pt)��D(lH�al���ad
"�	��h�m��r���8�t<��a��h��l��nd�r��s�t��)p.(���a��s�d�!��d�f8�g\�iD�kL�tT�zH"DT")�a�J���#$�b�#!,�e�$c�%�t&�d''��\�a|�u(�Mxt�fD*!*u��a��i��o�kp�t��u\+��+)D-5-��e�o�r0Q��-e����-1�e�����,.��|�u�-)�a(�s�/�4�t�(
�1D�bt�lh�n��t�26T�n�5!\�e�51h�l�889)��o$?�?D��l�>����l�@l��a`@���rU)�Zu��1.��b�cd�d��f��g��h�i��k�l�m8�n�p�rD�s��t�u�vp�����a��b�c$�d�e�f�	g�h
i4cj�klltm�npop�q<r�s�t�!u$v,$w�xd$y�$z�����g�hll�r�s��t��p �| �����!N�!�"�"Y4#M|#5�h�#d(�e4�sP�t��@$ �n$%��%��%;<�a0%�D�r�'N'\�dx�e��r(�)u*��r+��+���e���,��m�.D�a�n0/X�/D$1�e�gt1��1��2�dma�o�3S5��
t".P�aX�d`�e��l��m��o��p�s4]t<]u�yl\��4l`7
�7�p�n|�t�7}��t�8%�9y�9!��a�:)�o;�,�p�;,�;)��h�;!P&.= �=��n�rL=���e�n�o,�s$�t�=&>,��C���l��|?X$�ph@��c.��a��b��d��g�h��i��k�nl��n��o�p��r�ns|�t,�w<@3`Ay�A�TCx|D��D�E
�E\gug)��iI�sJ$B.xa�cl4d�g$i�k(�l4�mt�p<�t(�u(MM�N_�N! �e�NdDPY|Qt".p�c|�p��y�
R\�w�Qd�hS�T��l�T�
U���a�hL�m��s�tTU2|W{�a�ctWJ�WJ�WMY�fl�g$�i,�l<�s��tDZ�f�#�Z �r�Z9[��?$\u4�b\�gd�ol�s|�t��w�\�]�,]F�]Md]t�e��kL^ ^)��e�=u�:.L�s�=����r�^p��e�^)��i�_	�b$�c,�e|�h4�m<�nD�pT�sl�uD`����L�����#�|`q�`\a�d+Xd�e=�u�g��L�cd�s<hd0j=x�m�jUXl��a��e�u��yl��P i�rDr����n�p����i�n�r�s�,u��|e@��\����s�)�e�h܉T�����n\��$m�)
T�a�'c��eT�il�o��rDs�t�y���Ĕ���'gČkp�n��t�u|����nl��n��z�E4�g �l���<.�)��l����a�ai�l�n,�r8�sP��X��nd���������è�!�Ss�����t �M���b���� �o��D�e������X�L�a�����eԧ��`�b��p��r�Y��ܨ�����������L����Ut �;��a�e�ou�(����������bl�l��s��������h��8�i(nx��0�_ ���0r��)8oXp|t\�0�r̰_԰��db�u���la�����)�h�r(�L�e������1.$aHb`ctd�e�f�g�h�iXkpl�m$nop@r�s8t�u�)w�wx�zI)Ĵ)p8s@t���h��@7lXn�|��1lat#2D���e�����j�i���nԽ����x�7
���.�nH����e�m�r�o��L�3l���df$g8n��vd�"��)t��|����0gHk�����u`�Peht������a�b�e�si�l�o�ut����,�8�k�m�n(�������J��!�a�m���]H�t".��bips\����� �g��t".paxbh�c�d�e,g �h(�i�Djlk�ots�t�u�v�wt�B���,�� �)	�b�d�f�g�h�k�p�t�wx�Q������8�Q�z�����x������:binT��|��e��8���!$bDe\sdt��<.Tn�
���
�k������e�k��0������!�a�b�l�n�r�ts�w��zp����������<�$%d������u�bؼs�r
��f,o\��8s�c����t".�a�befXgdh�i!k�l�m�n(p8s�t�u�Pvp�z����b�l�m`unT�p`utk���o��1�$��.�n,�)�e�idu�|x����b��$��(oD�D�k4r<��H��T���<�ȅ���!Pr���x�^����p��2�7`�!�i�� ��,��������������!�i��8�ah��t��JP&.t ��h�S
��s����������!0.Latc�t���Te�\l�hh��!P&.`�r��1L���p��<.pc�e�k4�oH�psx�14��i�v��XD�RP�l�a��4�!�i���$���dTk\n�wpdtlu��ath�t�u�z�����p����_���a4��0J�u�c@�!�s�	��u ��i|�ljc�=g�p�rL�r\�T�)	 	a@	fH	il	l�	o�	r�	t�	u4	��	k�����,	��*X`	l`h.(��T	el	��	i`
�<
x	n��LP!�	o�	s�:�	t��)�!
a�
d�
e`gph|i�l�m�no$rHs�t�u�
�d�|��
c8
d@
kH
mX
nx�����H	���P
d�fozdc��l
l ��t
����,�
f�
i�
n�
r sTz$���
nDT�a�
s�
Haso2<��
mH�P!ex�,t�jXd�V4n�V��<��H�4 ���� )hu!���d@��,#���s@#������H"���i�o��#؟c�$��c�$��0%B8%)�e�%y@'�&��r�'��Kt�';a<o�)R	�)��4c�*�dcdepp���+l\hp,x\�a�.`.)|s|/1�.�m�1l�a�e
u���3@1���n�r�uP45��5�5�����6
r|;(W<[d
at
b�
c�
d�
e@gtk�l�m�n�o�p�r�s<t�v�x�Z��_�_)l
l�
uh``T�S���
s�R���
r0a�
e�`�
t�`u�
h\a�P�.�
e��r�au�bJbl n,r�b�(dad��d�d�e�f��H�o�j�j)8a\ehr�,kTl�ku��epm��a�e�i�lr�m�Ml��r�npn2�rh�Yo�g�n���n\pD�u� t���o�p�ulv}
P�.,a4d<fDgLh\ohs�t�vDv�PwJ�x�,y%�yN\{�,{�Tr�{,P&.xe�{�
`|x�|��|) �b�k�l�}~$u����a����c�eps(t4u<���w��h�X��%iԅ��@�_0.n̆u��)�1\hdilrts�tT	ul��x�u@�����c0R��2�����cċ!�e�s�J	u�a��x��ܑ)
a�e�ilpn�o�r�st���.��d(k0lDmTnhuD����D<g�8J4�u�,����Ls����`s��th�r���|���<�j�$�)�a̚���f�g����rP��p��@����hsH����n��+H�u�#n0s�e@iLoTu��,���8ed�uĠu`n����J��ha�e$���BI����t���r��_���ftol��;��od���l0�)�a�e�oz��u���i0�1����k��n��!a8iLo`rL�����0tl�2X���Dp��Ȩ;XuT�B��*�e����\Es���n,�!�a8dXe�f�g��i�os4tHu,à��8e��b�dlnؔud'=d��a��x���l8������$�$�,7$@gpt�Hn|r�s�x�!�qs@���xh�qk�w�z������`@1��)�ap�[�)���e�w��	gp$�frT���e$ph%�)�),h�"��!��@f\nhr#���f\#��&�1.�bL}c�df l4nHpTr�sDtJv�w�x�zt)��a�e<(�l�s{1\{R	�)�@+_�r,
�,e�,R	03R,ux2ueȿ_�7��t".�;P�;��@at=��6.|a�c�d�h�m�s�t�=�d�k�lplX�=>R	�?8?)�r@M>�A
<BD�BO4DP�.�h�pt�D.hE*�F�XFe r(vPG;��J0��`H��0b H)8a|e�h�i�o�rhÔ�������H��H��ti�l�H10IQ�a�i,��\Ip�I��I���n�I��I���p�I8L��L�TM��4�dfl`N+HOV�Sxa0s�g)g)(y�jB�el�<t,tLs4s��Tuhs��`����#zLy|s���(�s���atc�e�f�h�iLkXohp`st�u�zT�Ĥ��dln0u<v�Iئ���lP�dħg(t�����s4�\H����nhut���D�k�ȪS`s�8�a�h��l�e�r�w\�j,��o�Ea�E���dԮ���r��e4�����bd0hPn`phr�t��xD��;pP�e$�r�D$eD���g<p����DsȂtl������0�f��g`�hh�k�s,�wp�z��$��0�����<�OĶ���a4���i$�t,����f��)�o�u�P����ph��mn tܺnd���i,�D�e0rT�̻1ػ8l��l@a�����p�r0���a�e h<Si�o�r\Wg|��t��t�����i���r���nd������i�oPP�Xf{`f�e@��X�����kHt��u�aheoDrXu4ypè?A	p�\@.Xe������`.�i�l�m�n�r�sx����fL���LC.|�������.�t��d�1�.$��<���LC.x<�<<���e0���f����p(rD�1��A	����0c��;8a��1h�Pm��u|b���d�D�r�u(����m�n���	�Qg�r4F*��p�)�i��)a�e�h ip l| m� o� r8!s�!u�z��ĥY0�sx���b8c@lHmdnxrp�t����D@�-���l�)Pa����Xzh����pb�k�mh�g�t��T����m^u������T���
�1.ab$e,g��h<iHn�r�s�Tt�u�vX�yD���u��e���OP���������4g,�DXett���1��`.��1heh��n�s�t��F���|�����Ts��L�@�1����r���eDhod�r�uX� oC0�)�l���� k, nT t�?H	,���$ fܥhD iL s��`�����{FH�\ m8�Bd et�l�e� o��$�� m� n���u� e0:��� sl��c!i!pd�;� e!i !o,!�\4,��� f�i	$�~g���p�p���HV�P!P!ad!ol!p�!t�\!u ��@l��!a�Tx!r��
�!o���i�	���!.�!m�!rd
��u����!a�!e�2��!.�!sk<��c.\"ax"b�"c�"d�"e�"f�"gزh�"i�"k�"m#nL#oT#p\#rd#s�#t�#u�#x�#z�#��h"rhD�)p"i0�p����s��"e$u��@��ud��t".�"b,�f4�gt/mL�r�"s#z���� g�!��!�$#a,#g��iȳt�!)�#�<#.D#l���#�&�p&1'1*5|#cdke�&l�#t�*-!�-�����#��-�T�a�#e��rl.�0�H1��1��1���$.�#b�#r$s$t�1�D7��Lfc8��8�89l $e�:��r�@!X$e�DJ�D8$t|D@$s�C��L$i0P��c.�$h�$m�oo�$r�$s�$w�Q��R���Eu�S��S��S�U)�$a@%e�%i�%o(&uT&w %øU��T�$d�$g%n%u�U�V��V��V<�n0%r�V��%�&�W=�W�W��8%ch%n�%rLY�XY�T%eY��\%sx%tdYw��zT[�`[)�%e�Y���%w�ju����%a�\���%g�%n�?�`]���%h�%i�]"�^�T^���%r\1_��&la��`)&s8`��&g@&r�ag�a��8&k<b�bL&a�&up&Øb��b��h&��&�`c=tc��&}���&o�B
\'bh'c�'d�'gl�h�'i�'k�'l�'m�'r(s (t4(u���&a<(b*c@+d$,e�,f@.g\/h(1iL�j�1kx2l�5m�7n@;o�;pГqt=r4Ds Ht�JuLv8LwhLx�Ly�Lz +à!��T'i|#J|'h@$�#t'e�'i�$'!�'e(�+$17�3p�2���'e�ml�4p5���'aH>pL=���'iJu|Q(e(iXR��RJU!,(e�UyY|Xl|(a�(b�(eL)il)jt)l�)o�)r�)s�)u*y�(�mDl��t(l�(n�(rtn=opR	(p���(��)�Tp�p\'.�(a�(i�(n8)rt!wPq�Dr���6n�s�)d)s,tdtlt�)e�u]�u� )nD)s,u��()ivc|x�X)tLzd)elzR�zO�z��)i�)o|�x|R	}�$���;�)e�)i؀R|�N04h�)k�)t#z�����1���)a\����t�!�u�`cP�e8*h<ai+k�&l��o؊h*b\�
,*ap*e�$l�$m�*o%r�*t�*uغw�*�̊D��*b�*c\$i�*r8�l7)�<-����*k�@J�����*�����*e�Rȏ�*f��$�v����*rp�-�*e+s�)4)zD`���G�'�'�����;��^��p�ax�dp+e�+i�+o,rt,u0����h+b�+c�+i�+n�+xP������+n������+e��������X��+a�+r�+vd�D����1ԧ���+n,s�� �e��eP����)	�)bP,c�)dX,h`,lh,np,sx,t�)x�uH�������!��)�1.�,s�D���,c�,muT���	�,a�,e-f�*i|-l�-o�-r�-s$.t�RL=�8i0in�,r��)�)�,a0-iT-l\-od-rl-st-u��X(-nD-rL-z�_\	���u�BN�ul	�-a�-Ð	�	�����-��
2��;+a�-�`
�� +�������-m�!�-a.p.u�2��_��-e.r0;��)P4.e�h����+i�)p.a�.e�.h/i/l /n0/o</s,�|��h.b�.d�.l�.nx��mb����.aO����.g�,�.i�.lT�X�u�.i� h�fuL!���.r!���.e�$uH"��/o8%�&+�&��(/i�*#l.cL/pp,�
@1�1��T/a�/e�/i�/l�0n�0o�0r1t�0u1w 1y�/��5�5���/�`0��6�/i�/r(9r�8���/s=�|;���/t�/z,=g$>AJ�ba0e�ox0s��u�A\sc40i<0mD0n�pT0r�����,BhBxHBL0g�/hh0w�B�C�C!p0e�	�Eu�0g�0i�0m�0o�Fut�G)�G�0l�0ppHu|Hu�0alI��Jut0a1i1o�L2pLu�0n�L�Q7�Z�<[uH1dP1eX1mH&nx1r�1s\a�b tl1m����u)d1u���<2h���1c�ye 9m�1s�1t�N̆Jܑ)�1a2e(2iH2l��nT2rd2sp2tp�u�����1a2e���1l������2l���H��� 2o������42��<2ø���l0�)\2p��gT�u�2a3d03ex3f�3g�3i�3k<4l5oH5s�cu\5yx5z�2���2b4�k�2r�2uȫ��uD� ��2fز��42�L��P�2���3m��!3a(3rh���BeH3i`3rh3x�����8eX3s�����X�u,�)p3a�3l�3r������3a�1H"u �)�3l�3r���3k�3t��|�D����3u��)��l4r��u4eX�����X���4dP4k`4nl4s:u��! 4a�4e�4è�1T����X4s��1aq8���t4dD���|4�p��|��4c�4i�7l�4n�4r������8�)�4s����4dP��P����4k5wؾc�)�4e� 5f(5p05r��u|�������tP�T5rd�!<5t���Rh5mT�S��l��)p5a�5e�5iD=wpZ�����5n�����5r�1����5n��u
�5a<6e�6i7m7o,7p|�s@7t`7ul7y���Db 6n06r\�u��2���6r����6e��w�����t���=bd6cl6i�6l�6n�6p�6r�6s��d��x6s�����1����6.���L������6u�6zd��p����6D����6t���6e�6n<���ԥd�6g�yt`�O���)7a���ur����)$7fp���!87uL�N���L7t��T7n��`��0eb�7e�7l�7p8r8u,���t7a<8bH8c|8d�8e9f9g89i�9k�9l�9n�9o0:s�:t;u;v;y$;z(8�L)�����hb��8s<�D���� 8������48e�Xܟ�
��P8r,���X8e��d8r$�)p8e���8i�8n�8r�8t��$�@g��@���8b��d�8g�8�XO����8��8�8t�B�y�	!9l(9r09s$H7
p�DdX9kd9mt9nut�P9r��D��}l9g�B��nlB�9i�9o*���9cD��9n�!�9a�Eep`�9d:k:r:s��2=�1)�9eT��P;c������g��$:aT:el:ht:i�il�:p�:t���bd:l���!��dhx�hy�)�:a�:e�|h�:r��JHD�:d����:n�:r�����:w!�� ;�:i�!;k�"�$Bd$�$H2B�1),;l�&��4;k�Hp\;rx;s�;tt=h;f\@�EN4Dp;k H)�rTM��S�;b�;d�;k<n�S���;a<e<<f�<h�<i=l,=o4=pD=rT=sl=t�xlT.�T�T�;t U�dX<c,<i4<n�Xo�Y1
[���#.T<ax<e�<lH[P�hh�<e�[��`<d�[l<r��<\��<a�<Ð	0\���i��\��<eh]l^�<a�<e�<n`^�T_��1.�Ds,_���<r�_�`"g�`�
=a�e$=ipa�a�0d)��l�er\e;\g�g)L=id=z�gh�g��j��t".�=a�>b�>c?d�?e\@f�@g@Mh�@idAk�Am�An�AoBqBr<Bs�Bt�Cu(Dy�>�tjX;b�=k>l >m0>nH>rT>sd>t�m=�m�>m>t�n��n�o�	�oX�o��(>d�hh��p@>b q��q�q�\>t4s�>n�>ths��p>�B�D��dD�s���>d t�t1�t���>rpt)�>a�>l�u��v)�>a,�ė)\w=0?m8?u4w)	�>aT?et?i�?n�?o�?r�?s�?u�?w��\�_x�w@?g�wH?nd?u\�1d���xl?r�?t�x�����xuy�����1P��?m@y�Ly��?a@c@f@h$@i4@rP@ttqJ�y)�?s�z��z��@k${��{�{����gxLn8@@f�g�����H@hD�Xp@l
���h@e�ԃ!|@a�@e�@h�@i�@l8Mn�Z
T��@t� y!Z
����@a�@eL!0�u�@ep�Z̆�
cAe@AlHAnPAt��u<D.,An8Ar�����$At��\pp�
̋�`�tAr8�)XAa|As���L�x��Aa�Ae4�o����As���An,��fn�1����A.��J�Ae�AoȒy\��Bnt7�Ȕu�Aa�Г�$�X,Ba4Bh`�̀�
L��XBa�BcP�h�Bi#z����9��`Bi�hBl�tBhh�����Tk�Bn|�u`�!�BaCelPhhCi�Co�Cr�Cu�B�Ԝ��$����Br0����B��C�D��<����Bf Ci4CnHCr`Cvp���<g��Y���,Ct�h�����@CeXCf��L�.����,�n-J�,��tCfО��|CflNr�1��t����Cu�;�Ca�PeĊi�C�@����S�Cu����C������m��DfDn(�D��8��P1� Da�tDa�Dc�De�DhEiEkTEl`EohEp�EsXFt�Gu�Gy�wzĤ��Dd�Dn�hħJ������De��Dh�Do����<�e�Di�Dn������Dt����!�Ds��X�Ri��oh�����,Ea4Ei<ElDEoػ�\�2��̼���u4�lLEo��10��|Ee�Eo�Er����Ec��5�����u�En��!�Ea�Ee4FoHFpPFt�S�T���d������Eg�EiFn(Fr�1�	����FkFz��J,�P��� Fb��@Fn�����BL�7��Fa�Fe Gh(Gi4GoPGr�Gu��_����|Fb�Fl�Fm�Fn�Fs�Fu`�h����F.�������Xgd����kd����FdGeGw����FrX��ؾ��)Ge�-0��� Qn����HibDGn���.��;TQa�e|GopG�l�����hG�����pt��]h��Gf(�e�����)���Gl�GnHr�����GeHt��#�����Gk�Gz���
��*����GeHf$��
��-��)	`Ha�He0Ih�Ii�Il�Io�Ir Js�Jt|�D,��LHox���THg�Hr�Hu�Hxh�\���xHk���p6g�Hs��14��T���Vb�Hi�HlIm$Ir����Hn����*,����Ha�He��M|��Hi��7Ip�l��gh���Iw���HIe\IilIoxIr��;TIlp9�h���I���udIs���������Im�In,�u8���Iil�@�J$����Il�Ip�Ird���t�f��u�Ia��)��d�;�IaJeJil��$�GnPutWa4JpHJtl�@JaT_��
\Jr��;TJi��hhJk���pJr,!|Je��h�Jr�<�Jb�JcKfKg,Kh@KlLKmhKn�Kr�Ks�Kt�Kv*P(
0u�Je�a�����Jl�lKe$Kl(uHhphth.�4Ked#\T")TKs�!}\KdxKg�#E�Ke�Ks�#<.$W'18�i�Kn��h.��J�Ke\+y*u�Ki�Kt-�0��-)�Ku1�89Li0Lo|=1$Ls�>,
�>Lo�>1�@)HLe`Li�C���EcXLr�E��G�K�La8_e�Lid�l�Lu�Ly�K�M7	LM���Ld�OuP�0P���ooU)�a�LeMiMo�W�Ln�JdY�LaY���Lt�\�^}T^��Mn4wl�j�� Md��(MrXl�Me�Ml�Mr��@Mb�McDNdXNe`Nf�Ng�NhOi OkHOltPm�Pn�&o�PpQrdRs,St�v�Sw�Sz S�(s� a�Mi�p���Ml�s��z��Me\{��1�Mh\�Nl�`r(Ns<Nt|����4Nr���Nt<Q���I�PNiX�[��T�laf�al��pNs,xNn����Ne�Nl�Nr�NsH"��'u�*��6�1���Ne�Nl�br�Ns�0u�AyAJ�Ne O��j)�1.<[��Og�ܑ��Oe8Oo��r@Os��u0��T����$.�Oa�Oe�Of�Oi�Ok�OlPn(Po0PsHPuhPz����40i�Om������d��Oi,�)�Oe����Om�On@�L�B��)�OlX���!�Oa�����Pr��JPa��
d�N@Pa4)z��L�)TPn����Qgl����)`Pw����<ds,�1�Pe�Pi�Pn�[u�
�PzH�}��)�PiX�!�PedX�S���Pe�Pf<\[���Pltj�j��
QaLfcLQd`Qe�Qf�Qg�Qi�Qk�Qn�foRs,RtDRuy4w)DQr�z�LyXQcdfixQl�Qrh}�8���Kg�Ll�QzĀ��BD�)�Qlԃ! MlL��̆�Qm���8�)�QlT����Qr��J�Qe�L�!ReRk��<�`�!$Re<Rr����XRnD# ��}PRe��Ra�Rc�Re�Rp�RsStĤu�=���Ra�Re�Rl�Rm�Rw��Rh����|�i��NЭT�Z���Di0����!�ec�0t�Sa��1��D`��S����HSaXSedShtSs�itx�,��T���PSn��0uPulSc�@U�Se�W��Ss�[;�c.\TadTclTd|Tf�Tg�Th�Tk�Tl Un�UpVr8Ws\WtxWuPW����SaDXb��cPXddXe[f�\g�\hl^i4cj�`k�`l�am�an�ao0dp\ergs�gtdiuL�vjw$jy��z�WÐ�|#'��a��r*����r�+x�.h�.��x�o�2��l�TtX�u@4!�To�4��Tt5���Ta�&iUt�T�\6�x6�6���T��;d��<@Urh@��
UaPUdlUe��k�nl��n�Uo�Us�Ut�Uz�A\Us4B!�BR�B�dUi|Uu�_�E,PF��&l�Fl H*I��Uil^�Ue��`�Ud�f��UnT_��Ue,_���UrX~xJ�4VfDVkJ��	�UaPVc`Ve�Vg�Vk�VoWpWt(Wz�l��m�m��<Vt�Kf\L�L�XVgtVk|Vr�L��LN(Md�Ve l��u�VdT��VlN)Xb.�Va�Vl�Vr(N��M�Vm�VuDN�dN��NXO�����pDP!Wn Wy0
�
(QBSj|Q��0Wp�T����u���DW�U���cpWe�r�U�Y��Wf�WnDZ�[R�[a�Wi�_�Wc�Ad�Wr�WsXtD`���W�L�� d��i��`��e��g�h�Mi�Wh Xn�h���We,Xh4Xo<Xs$i,Ct,i<iuXiXl)��e�l\XaĔ��c.�Xa�Xd�Xe�f�XiYkYl�#m�YnZp0Zr�Zs[tĴ��Xr��D�� �u��J�Xnl�}�Xc��m������Xs`��XtYud���4YaTdd<Ye`Yi|Ylt�*,��stLYx��_��	lYkT��TYn�J���!tYe�sit�*�Yl�����Ya�Yd�Ye�Yn�Yo�Ysuz,�D� �)�Ya|�M���Yn����)��
ZiZu���2������TZn���$Za\ZllZnxZo�ZrH�s�Zw��u`��0�J��JdZa���`�u�Zn��)�Za�Zi�Zào��2�����Z��cw��u�Zg��Ze��)��aP���Za[s4�����T���
<.H[a�[e@	f\i<\l�\o�\r�\s�\t�w�@[bp[dx[f�[i\ck�[n�[r�[u\��*1|��|x����[s�cl�ex����[.�[i�[n�[r\sx1��m�[n�����$.�*t���6.\w��X��X�n�	��	��(\�l	�`\el\i��ox\u0\�(

X\i<
t�e�
�1�\r��\a��N�&l#zP7�)�\r�'�';�\a�1<.4]a�bX]dh]e��f�]g�]i�]k�]l�]m�]n^o^r^s$^tL^uH�zD]�@1�5=�5��<]�^�X^��6|�8�6��`]i�]n�]r �BX:��|]d�]s�:_��|;���]s>��muT?)�]a$>���]k�z�A�\DX�ElI�G���]p@J��J- O�Q�8^h�T��T)0^e�X-(W��D^s�Y��Z��^s<[��
`^a�^c�^d,_e\_kd_l�_n�_o`pH`r\`s|`t�`z�]1�1.a��`�^l�`u�^h\a!������^np�=�^e,�^i�u�^e(�_l�c_e�b�� _g@_lT_r(duL_ad%�f�pm�\pt_e�_z�qt2t��|_r�s)�_elv��lye�_g�_s�
�
�_n,y!�_e�_s\y7�{��{��_e�|�`i`n�};R�}`u$(`eX�4`lY2؀yT`n���<`ip�����l`ot`tH�̆���"ass�`zP�t`]�H����`n��)�`iܑl�`u ��`r�}T��
t".�`a4aepai�ao�au(a��adan����7d���agز��\���\'.PacXag`amhan���T�k��k����@|g�ak�az��c��c�JL����l�aa��`,�)�&bbL}cbdbhbi<bkDbl�bn�bo�bpcr�cs�ctwdx<(u@+�\/�(1�,bd4bnH1yH&��1�x2`balbitbo�2_�2Xbu�3y5�Hr|8��7���bd�bn�9d<(@;���bb�;��;�bk�br�;���ba��e�bl�btV�=l=�>��=�bl(cut=��ca0cnLct�q��A9,�2hC��8cn�B!@cidcr�Pp�C;\cetciĊ��D�4D|ce�ctXF�ca�ce�cr �ÄF1�cgH���F�iTQPG;�ca H)H�a�ce�cu�H��	)hL��M�@MdlTM��dbTJc�S)`da�de�df�dh�dl$erHesTet���SXdbxdn�n- U��pdl �2<Y�drY�dedX�dl�dn�Y�[*�\)�do^D�`�daeee�a��`�dn\��	(a��e�4a��e�\e�ea8eePi�e�8gg)@ea�gl�j��ea�ee`fi�fo�eØmStj��xek�ex�rD4s2�ed�eg�em�eshs���e��f��s��s��s�s�Ly\'.fcfdfe fi4fr<fsLfØz=�zS
{��{S,fs,}J8��F���$z��Df���-̆Xfe�g|fn�|�p���tfz\���fb�)c�fd�fg�fj�fs�ftD�i	ԓi	@.\L�\ؕ����fs(�\�328��ffgnH6W�)
<.8gaDgeLgh\gitgp�gt�gy�gzhg�Ĥ�Dn����y��h�Tgdt���X��0�DSo��he�gr�gu��h�_��58�u�)�ge��)
�ga4he�.h�hi�ho�hp$@ris�tiu\iy�zPi�x�hb hl,ht0�)�������hb��hT�PhcXhi`hl|hn�hr��p���,�lhe|�.��x,���thtL��(��hih����he�hw�hz����,�`�����hn$���� m�hswp�k�a�
X���hoPl�	��h�m(in<ir0�
} ig@�
���4ispe����Hi�0
D<	��a�Jb�ic�Ydزh�il�in\#r�it0(���!�ip��is�!}�.d�isȳt@%��-��is�/��1��HDf�iljn�5�H6�@)ja`@��r0P#
�_t<�Tjehji��0ju|�S)Lju��@u`js���c.Lkatkb�kcpld�le�lfmg@mhXmi�mk�mlom�on�pp�pr qs�qt�qu�rx�q���tjaptb�vc4wdLyeD�fԃg@�h̆iL�j8�k��lx�m��n\�o��pГq$�rL�s`�t��u��v�w�x�y0�zhs�,)�uDkbdkclklT�r@�5���kad�b�kd$f$�g�ki�r\�s��t�kw�ky8 1��r� ��!�\#�d#��#�kt|#���kelhhlk0��@$�b�#�ke(li@lt`lu��t�$ ln�%uTl�0%�4lr�%	�%��Ll�&1X&c'�<.�la�lf0�l�lr�lt�'_d'���lm�lp�'��(�)�<)�L)�*u�la�leX*��)��lr�*1p*��lr�,�+me,ml0�nl#Z�-$me\D��.��8mmPmt�0M$1ulmc|ml�mr�`��r��1��tml���3��2���me�ml�mr�mt�m��3��3�4e�mo4�4W@4!�mi�4
�4���m�5u<.@nax\b`ndhnepngxni�\k�nl�nm�np�ns�nt<]u�ny�4(
PnbXnrȫh6`7>�7��8�9��ne<9u�r�9!]a�ne�ng(:�:��	�:)�n.�;)�$.�ne�;��;��;G
�nhl�=S�=�,orL=��oe4oi<om�op$�t>1H>��>XXoaxoe�ou@�2�>PonH������dos�>lor0?7�`d?)�olh@��t".�oc�od,pe��fLpg`pilpk�nlxpm�pp��r�ps|�t�pw|A�A�A)�oape�Aepppr�k,�p�B�B�$pi<pr�BD2TC!Dpi�D!|D�Xpe�D)�nr�E��PF�P&.�pp�F%�pa�FM�G�I)�pfhI�J�pahbqe�fqi�kt�p4zxJ��L�L��pi�M�HM�qn|Qu<.<qaXqclqp0tlQ<RRDql�QLqh4S4S�dqh�T=���xq��r�U�<.�qa,(e�ql�r�qs�qtTU)�1.8�)|Wu4�t�W`�W!�qaY	�K.rb rd0re<rfDrgLrm�rs�rt�YX<#.�YZ	�YX(rnDZ=�Z�@[idra|ri(��`[\rgtrn��p u�\U$\u�rc�g�rp�rs�rt]�,]+�]2d]�ri�rr�]% ^�rsp^2�^�_)si@_�H_��ss�`8�`ss�`��sh�_��(sc�sd�sf�sg�sh�sm�sn�sr�ss tt,tuD`��4s�'�L��0����8��a�xa�a;Db=d=P&Xd���s.�si�sse=LeD�eP&.�sa�el�g���ecttxh�tKXiute�h��ts0j<tshttk�\tc�*cHkHteXkPth�kkXl)�taub$ue�ul�_ovrvs�vul�l���tb�td�tk�tl�trd u(�m�telm1�m��m�taPo�o���te�tTpN�qS�quh�p��ucPuldun�r���	����<u�(sDuütO�s��\ut{D�ud�us�zpua�uevi�u�${�ܰX�y�ur�!�ue�����us�ut��|��\���p�\{u\'.�prP|1|�un�;T�i|��4vePvkH�o�ppvt��@vi��2ػR��lHvaԃ���\v���Ĺrdv�\��uP�e�vh<ai�&lD����?.�vn\��ve�$i�$l�$m%r�vswtغw�S��a�p�vt<QE���vr���Ya(we����S��ww�R��wr�)\wa�we�xi�xoyr(yt@ywy�Ĕ���c|wfČk�wl�wn�wp8�����|���,�i�wt�wz ��4��L�_���t".���wi�wl�wn$xrtxs��g����wb��|]dxg��J�7xl��8xr����xeTxihxnܟf��j`���@xsH��Hxn �
��J`xt���(p���xlX��xa�xn�xt\%�I�����xnȦ�����eԧ���xb�xn�xs������xt����������L���Kt �;ya�i��*8ya�hԱx�)ta���1.�ya@zb�zc�zd{e${fT{g�{h�{iL}kh}l�}m~nop8r�s��t��u�v�w(�x0�z$z�8�� ���yr����ye�ytĴ)�yl�ymtqsztL�`�u�)�1.zs@�)��p�������z���ԷX��8zaXzl`zr|z�T�%ȸelza��1����tz�$�%�za�zs����zh�zk؊��(�r��v�-hZ.�zi|�WD��$.�d�ziȦW��zt���J�zl{r����2�{e<{fD{lL{o���}0������D.�{e������d{��ul{�(�x{l��14���{cH����{a�{e�{i�{l�{o������{t���{n<�)t��0�)l���t".(|b4|e<|gd|l�|m�|n,}s�����c |l������P|e8���H|w��y��J\|at|iT�y��A����||p���|e��g�|k�|r�|s}v`x���|c����{����D;�|e���|rX���|t���<T���}r��}e@}r��}t�����e8}o`��X}e��vt�����`}a�wb�}e�}f�}o�}t�}u,��}a|si�}kt�*(����g���(�I���H�<.�}i�}p~u\�������)�}fT�e����<.@~aX~dp~e|~g(�i�~n�~s�~t�~z��t�X0eb��lT�� �)P~ih~r��%���n��!xl�~p���cX�~s�!�~e�������~e��^��!�~l��s�tw�~z4�|��X����u�~rdX���e(o0p\�u	p�����!.|b��d�e�f�g�k�lĄm0�n`�ot�s��tĀz=�,�g�r@�r
D�=���r$����O�e���t��n����e�ię�����nH�=`�O�a$�� ����y�u ��,��������X�s`xtL�Ì������D�������l�b ���!0.��a��e��p��H��t����O��e��r��=��0�gԀe܀y��90P=��!.�a�e�o$�sX�t��uP�u̡n4�����4�d4�a@�e(�L0a\�L�r������w@��p�e|�u�����5h�mx�'
������p����y����h|�́g�jlԁn�r�B��
�Wi\���\a�	|=���d�)��i`��Ŀa �i��u����<�i�.T�)
��a؂e�i�lD�oP�r`�s��t̃uĂ������!t�s��u|�l���l���dc����t���lЂu������X1��*n�s�)X`	l���<
��c$�nl	��i,��`
�	���-����<�r���u��È!a��i��p���H��D��x�������d0����rP!jaăr`���!�aT�e�i0�l��n��oȅr�sx�|����d\h@
k �n4�s�uTԣr�(�t��J�B@�n,H�at�b��l��n��r�t�@7lPp,��r�u��eT&���)��w��zH��-a s�ȳp Ԅa��o�!�
"��l"��e!���s�"=t"�nH"��$�ap�e��i����D�s̛L�h�#��X�cl#-d�i��uȿ�#@|g &�8%��o�&��D�b�mR	�'����l�';��a܅e�i�(��g�s��t�qD)��)T)�np,��*J�p4�t�-�,,�r�1xt".`�a|�e��o^sl��@11t".�5��<]�^��6���!.��n��rX:i|;,H �G����e��iĆl`H|H�<[�a4�b@�cd�d��ed�fЈg0�kL�mp�n�r�s̋t(�x,���Z|�m�tT_�`_! �l_)(�b�`uP�eX�h�`��`P�a\a�|�a��o�a�a��t�n@bLb����l�buԇb�f�l�n�rH�sT�u c70c���t�bȇs�c�xc�r(d`e�d���e�u�)�f��c.,�e�f���i@�4�i�g<�t�h�h)�hu\�a��e��o��rĈt��i��i��r@i1)�iBDj2Pj����r j!��e�j��D.�e�j�l�r�e@k�e,k�nL��lk)�k oB<�apm)$�l0o2��% t��D�b�p`�sv�Dv�lv��h�a��d�e�f�gܥh(�i4�k<�n��s��t�vPwg��eԉi�w
�w��x�z�T�̉z�w��i8x.�xH	�oy��d,y!�l�drz7
�y �t@z��zd�e|�t��utHP�m�zX�n��r{R {)t�aHS�{,�Q.0�o��ph(
��i<S`|dЊe܊rt|"||��Ȋg�|^����)�a,�c@�iT�m 	o`�ph�s��t��<��o��w��h��0
(�8�k��u@�NL�u��%�t�a���nT�����<T1��s����r��s̆��e��;ċsD�)�u�a��i�r�t �uD����lx�y@�.�ċ!�r��x�6��ܑ)`�a��lԌn�rL�s��tX�ux�Ì�Dm�th���l�� �������u���a��iČǒu(�q���sd�JĠu���up��L��$����u4�������;$�e0�i����y�X��aȤR<�np���)0�)D�e��p��t�V0|�`�il���h�t8��t�a��e��i��|]c�����u��!��a؍e(�i4�o<�r����d�g����̍n�rħJЧ���f�s�w���c(���J �1���� �nX�lȨH�a��L��	 ���P�hp�mx�n��s��u�����i($"��u�#tT�!��aȎe�i�oL�sp�t$u,���y��a\sc�i��s�tt����t�u��t����y�y��@�sز�� ���R�dR8�sd�!��pd�t���P�\�o��7��!	��a$�d,�eh�i��o��s̐tؐu��(������g̏l�n�p��u����ďa܏d�\t����T�z����h�I)��1<��r\�����4�B���1.D�fL�o�6s��p�X�a)$�T�e��\�dT�m�@� ���x�i����r����)��a��t����a��0��!Đa���m� �d���s,�J0�a��d��e�f��g��iȒo�s �t,�up�y����0ebH�nl�rؔu8�\�dd�i�Jz��%��5��l�e|�i$;D�����p������ܒ��G����$�)��r��b�f�i �nT�r��s��t��x���$�nH�e�_��Bf�s��n$�0�e@�s�A��t�8�e@��L�fp�g��h�k��tXx$>d�x�i�x��8)�w��	N
��n�Gp�9dH�pFr��t,S7	DDT������$p�Fz���y�)�e�!hr�(�Wi<(8�e�j��o��s�&��D�b��cԓd�e�h �i(�lt�mȔn<�p`�r��s(�t��u��Ô)L�)�*u��h��k+-<#.p+�@+̓e$,�/!\/���l�n�r�E�0u�a�0�(1Dx2uD�eP�lPUy`4� 4��<�n<4!D�ah�e�4�4`�n�5uLC.��a��e��m��t����5��d�61<6��r7@7M�7u��e�n�s(�t4�y�8�8���b�8��r�90:
d2�:y�n�:)�a;A�;����eP�fX�h<<��<Rt=x�a�~e��o��t�=���l��t>�d>X�AXlPh�B!��h4D�hЕi`�mؕs��tE��E!�ec�i(��h��eXF�a�Ir�FTHc`H���g H)�aT�eh�h|�rd�s��t��u�H�H��L�ilI0I�`�oJ��I;t�i\u�J!��a�	y�%�hK}��t�J��nȖt�K\�MB@MЖlTM��ؖb �dTfP�g,�h4�k<�lL�mX�p`�rh�sP�uDN�u�N� OHO����tP��D�i�PQdRP&.x�e�R)�Sl��a��e�f�l��o�s�t�Iu�SdXėr��u0Z����eܗi��p��ԗnpa��`�
�i�ays�g�g)�t�g�j)`�a��bLfc��èh�i�n�o4�txu���tk)tj��X�bp�r�pu4s���mhs��x��@��ptlLyyĘaؘr�w�y��y����e�y)��lt��8��Иs�_0�l̆�k0���J�a\�$�b,�mD�.t�B`�!Pgh8�D��b�!��a�c�e$h��k�il�nXo�p`sL�tT�wĤ�b��d��m��n�r��P���p�tħușgЙpP���������r��ؙe�l��h�<W����l�����(�bx�i8�nP�r��t�����dH�e��J����d�e|�s$!zp��$��0���h��<�ܶĶ���aػ8lȚn��l��aؚi�l��oT2r�u�1ĝ1\���Кe�s�����̼�(�2d�J��o0�;(�h@���_����0�dx�n��u�8�a��e�h��i�o �rx�u�Fk����p�t��e@���g���n�����ițr�m��)��td�����b؛w��u�X�������g0����n�p�g�����t��8�a(�D�g����,�nLpW����)L�i��!��a<�eX�h��iОo�rD�st�t��u�z0��x����b��lL�mԜn��r�t�:g������m̜s4������\�g�n/t�z�l����p�e�W���)�t�T����r����$��T����1.`�ep�i��l��n��rL�s��x������h�f��l���ra��,�����f,����.|��h����he�f�/h��k�l$�n8�rD�s���
��)ܝa�o���
��g��1@�!�a�eT�|��J��������)0�e��O���(k����it�o��u��D��ul�l�X1X�����m������d��m��n�8�����a,���$ fd�$���Ȟp�r���i8�u���� (kd�;�a�e8�ol��c,�i|�,��$�s�����sPJT�eh�pt�h��l`�e,g�)����|�t�	����r<ܟa��b�d(�fH�gP�hh�i��m��n��p��rСs��t,�z)�rh,
4)�e����ap)�e�Yr�8�a@�s�u���cpX�r���!s@u\�nx�s��d1��f|ri��l��s��zT �� �� )P&.�!�!}��a�d��fD�gP�id�kp�lmx�n��t��wT"��a�e�")�r�"$�l,�s4�w`��������#W�#!<�l($�\�o�
�$X0erl�%M�%J�p&����r'p��a��e��od'u\()\)�\+u*uȡi�sL,!�Sp�tL�_�-��e�0h$�o��r�.1l.���iX/l�1c�w�1L�bt�c��m�1`�e�s$2X�n`2H21l�h��k�2l�2-��s��k46����m89)Тe�^s�;!�;����e�:�Ģn�s�<��@)�u�i�HK}�s�J��n�K!0P(�c�P�U)`�a��e�i$�oD�t��u��wPØU�TX�cx�n��r��sVu8V1�V����Y����e��g̣t�W����nأr��OpY�dYģs�Y��uf�g��k�w�YIXZ�`[g�\��\���d(�DT^���f4�p�^�_x_)<�eX�h`�r�_)`;Xo a�`)l�u8`��t�g�b�	�b�����b��e�����b����c��1.81a��b�c�d$�fl�g��i��kئlP�mħn��pШr<�sT�t��u4�v��Ĥa�b�cD�d��e4�f��g��hh�i��j��k4�l�m�n��o0�p�q��r��s�t(�uL�v��w��y�zt������e��l�rĥs� u��tl!��!l��e,"R	�"M�$?�#̥o�t|#��ԥh0%�d''��a�mĬr��Hp**���e<�fD�i��r�*��*2�,M�,L�t�,T�n�+`�e�n��r.�$1D��k��l�1	�1�2�dma��iȦrЦt�32�3�@4Y5���$.�e �i�l,�o4�s@�tH�zx��88���b�7��r9�l�d;2�;!P�e�;I4=�L=��e��m��n��p��t>�
>��l�i�=t�r�>�>)��a���d?�$�u�?!��oh@t".�a�b�c�dP�gh�hp�ix�l��p��s��t�pw�]z<@R	`A�|A'
�AX,�.<�rP&�pB;4�iLCOTC!H�.`�sDD�tD�|D��E-�	PF���k�F��G��F)��rI���oȨr�I��I}J���.�a�b�d$i46r�s(�tt"ixJ2@KJL��O
xP�DP! �iS�|Q��4�pL�y�T�U��ax�hD�lL�m�tr��s��zxV|W,
�X����n�X!��eY6.��c��eةf�g��m�r�s`�DZ��Щb��ZX�r@[i)`\T�i��$\u�b,�e�\U�^.@�o�>u�_��	�Wc��h��l0�m��n��r��s��tȪuD`��H��0����d���b�	Db����ndc���tXd��ܙd�g�h0jSܪØk��k��Ԫ�Xl��a�etnl����n�p���r�
t".P�a��e��h�Dj�l�o4�r<�s'uL==̇H�mh�np�px�r������Ĉ���<�\���.�a8$b��c��d��e�$f�g�h�il�k��lЭmܭn�o�p�q,�rt�s��t��u,&v�w�zx�Ð2�؊����f$�k,�l<�n`�oh�u���2�0�����4�cP�gX�z�n�L��T��5ԋ���p�����̮����D���<#.��iЬmܬn�s��x���1.P:0:Ȭp�u��J�s�����ȍYЍ�>����e4�mD�nX�rd�s t,y ��<�g`�(h��P�u�@Jt�0��u�.��i|�Jt�e��i�{6������n C��B��t�EJ�D��������ĭô��
hZ.(Jx
����x@J?�lHO��(YГq\�)�.H� �eH�i\�o�L�pLu@�n0MD����T�ml�u�F��N�)k��T��a��r�Q��Uȏ2��nĮp�Z����t�Xu(�bXZ�� �t8�Ԯe��idH2�G���lH�� �i`��$�p,�rt���o	ؓ�%��l�a��i��r�=���X�eĔ��`�m����x�eԤ��nX���e ����1.�a4�b\�cl�el�f�g�h��i��k��l|�m��nL�ol�px�q��r��sĶt��u�x�)�1.Ĵ)�t �n��� �n��(�e�r���$�D�o�7t���L�hk����c��i��r4���*��g��n��hԽ����c.���4�u@�������1|��̰l��԰aP�l� rH�D�a$�ed�ip�l��r��s1t���4� ��8�i@�lP�r�8����<(�H�kL�_<�\�nt�!��L�ux�e���\��P����b�������l����.Աe��gܱn��s��t��r��
�. �b(�d4�eD�fP�g�hX�k`�lh�np�rx�s��w�{���L�)0�ux����"��<�o������l��z��{�X��<D.��p��th���R
,$���_���`������t".�a�eD�f4�iT�sh�tH��t�)${d��g��m�,�t�c��m�rh�Y ����l�9s��\T��,�x�������@���4)z��~(�!`�r|�pH�t�a��p����P�.��aԳd�g�i�k��o�s�t�u��t�)L�gT�� �)̳l��!��l$����\��u�b��M�����!P&.�f<�g�ti��s�twD�z���4�J��ur�c�\�X�s��`�oГ����t".̴a�eD�fx�g��h�iĵk�n�o<�sl�tx�u��v0��l����Ĵdܴl���PL����f�i0�n�r��z �np����g��mH����J��=(�kD��$�)<�oؽrd��H��p�lT���X���5���7��r���2 �����ȅ�������^���������)�eԵl�������ܵ���J�t�� �(�������tT����f����$���!P&.P�a`�e�x
��H��X�h��!LC.L����f�vm��nl����l��}��d�����D.��e�gu���ܶa�hT��$���Զp��t����	|����n���e$�p,�t,�ul�Yt�7T�t�a��l��r���H�r��!P�e��u\�l�h�lt
�l	���o�����e,Txs�1)Էa��e�i�ln,�o^r@1P�.�k�n�2�3�6$>��6.�d�r�>�d@-HH�G��$�fD�pT�r`�wlI�?FtI��L�e J�<[ĸat�b�c�d(�eh�fp�g��k<�lP�md�n��o�s,�tp�v�#%ܸ.�[1��h�Z��c��d�s�t�#i�]%(^��`���n�a��o\a�e �oLb1�bDP�gl�n\�r��(�<�s�cD�e�f��!r�h*�j���a��n��r�j)�k��uD&�kp���m��bpm)	��a�e��i�lr$�s,�t��u��@n�Ln�����n��n����lpn2�r o��oB�o��s2\p��4�o�ug t��H�mDvlv��
\�aԥdtyf��gܥhܺi�q�s�yt�vyq,y!��a��h��l�drкsph�dPy\y�Ⱥa�y��g���,2a��c�e�o`�p�s �tH�)��̆ zu�DH�aT�rT	u�����@�u@�`�ah�1̌��)h�a�2e�^r�!ܑl<.ػa@�bP�e\�i��l��n̼o�s�t(�u���p��
����Ļa��̻l�m�n�s,�t4�X�k������z,��U�.8�g�sd�)�eP�p�|������H�pH���P�.|�f��g��k��r��z��jX��	��̞�����a<�`�����sT�4��ļm�r�w���J��������0�c�����"h��p ����lT�lX�a��b��e��i��o��2����P�lp�nx�v��wd��^)�e��X���9���p��i��1.̽bؽe|�yX�Ľe$,���l�a��u����,��aH�id�ox����$�b��w,���1.T�s�
J,�r
<�epgDp\�t����p���&���1. aؾb�e��f�g�h@�l��m��n$�o,�p8�r��s�u�v�w��x�z<(�x,h$,)�t$.
�,���t@.��/�\/���e4�n�r�0�0u,�gx2�X�ah�e��l��y�2H3�03`�i�42�4t�r<4!|�e\5R	�5D�7D��a̿e�s8yt7���u�8��n\���)ؿe�8��dDT�����0:��o��X)@;��;��Ĩft=��
6.h�a4�ct�d�~e��g��i��o�s��u�=�bl?g|@�@!|�a��e�@#An�@��e�A%�CJ4D�<.�tXF*�J���nL��e�:0hL��L�TM����f�k �l(�sHO+dR1
�St".|�a��e0�f@�hh�i��l�n�o��r��s��t��u��yp��\T��S	t�a��k��l�n�p�rT�u\�zH�àT�TD�T����a�D���.�DJ�r|D��e U���i��o�U��Ur�UDV���a(�e0�o8�u`V6�V��P=DWPW��@��xW+�_X�W1��r�W��d������W�	dX�J.��l��n��p��r �t(�zY�Z�
�Y
��i�Y����sZD��0Z����i�l�o�r�s\Z1xZ�	�Z�H��[��7	[�4]�\8�a�<e\��<]D]��T��l^��e��k��l��o��p��s,_���rT_I\_5d_�_\	`P\`n�`���a��i�u���`\�`(a�����pa2�a�ahb��a	�dD�gL�iT�k\�ld�nl�rt�s��t@.�b'
<b�Db�bScS�cn�Jd�c)|�t\e�LC.��a�e@�od�u����k=�e����c��n��x�o=�e8�e�e2��m�s�e���������e�f��e�c(�d0�sfP<f/�f�	�f�8�bX�s�fV�fP�s���p�n��^�f��fx�f��ngeg.�g�imdi��n��p��r��tp&=\#�iJ�iS$j�j �a4�e8�oX���q��c�q��stj���t���Ly,�th�tJc���@�shs��L��h��8��cx�d�2�!��a��e��k�il��o��p(�q4�sL�t��u���`"������oĤ	��b��c�g�j�l@�mT�n��s��tX&������kl���2�ئ(�a0�b�4A�6��YP�8�tP�YħL�gp�ox�s��t��z�E)��s��X�]J<��WT����tH�ut������\�����c��g�h�iP�rx�s��t԰���a�DD�aܱd�f(�t�����n`|y�q�x��0�t̴8�t����D�al�sp��`�~<�!d�e���ܶĶ���aĻ�̻����aػ��l��l��a����$�f������f��i�p��r(1(
8���<�i0�DSo��rtxTj)�t0j��e�)�u�����0�dp�r�<�a��e�h��i��o��ux��8�1���T�������LC.8-l��n@s��t��u����;���0���<�p�-s0�1������p`�1h���r(���h�m��|�.��at�b|�c��d��e�f�g�h0�i�tj��k��l��m��n��o��p�(q��rX�s`�th�u��v�w�y �z�Ð�	x���x�a��b�c�d<�fH�gP�hX�kh�l��m��n8�rd�sp�t��u��xL��0���b��h��i��t�z����!���T������$�a,�r���Ĭ��"����4�f,������x��
����`�.��e��i��k��m�3p���9��9��:�@���i��td���3���=������a��d��f�l�n�w$�z�3�����n���H�����l�)�at"����0�.L�sT�t�Odp���R"����\�i��\t"���x�.��f��m��r��sx����
\�����.�E
4�r|`JT�����b�d�g,�l@�m�RrT�th�u�������������t4��P������$�t����=8�tH�&<�1L�tP�VX���`�sp�����$��T����a��c�e�g�iL�l��m��n8�pd�r��s��u��v�xdq�D����n�.��6��n������h��
\�P���r��0�g8�l@�n��<��C��la,�1d�e��l��J|�\�m8�pD���p��L�!x�è�2|����r����a��p��Q�)��f��Y��)��s,�����d��g�s�tl��	T�7��l��ap�f��!�f(�w�nT��\�0�i��vp���D�s(�L�rh���X�e��l��n��r��s@�d��9��|������s��)��a��d������e��t���d����s�1��f(�`L��X�������%����o@�\	\�f����$�eh�fp�m��n��o��r��sd��l�T�.���	�����,���x�b@�f��s���cD�H�DP�)��o0��8��t�������	$�����c�d �m0�pD�r\�sd�u��X�e��������d���(�o���
����<�dT�g��p�i	��,����_l�h���t�c��r��tQ{�WX��������	��f�g�h�i�l(�n4�sX�uD��d�;��a��e\�i��o(�u���(�P|��0���m�x��L��� �s����������<�������P�m�s���Sd�f��g��n���l��������W��e��d�l�
��.��c��d�f�g�i8�nD�pL�tT�v�K�=��h����\,���f(�s|&�~���0�n�P�sL){$hZ.|�a��b��g��k��s��8Јz0��@�R
�)��i�������c��f��k��l�m�s�,�"P�(�u��l��Ju��a��qH=�t �k@�mH�nP�p�.��
�*�
P|,��	��b��c��d��e��f��h��k��m�n`�rL����	3p\�	g
0
�L
=�
4d
��r��s��z�
��
{�
��.$�f,�i4�s<�t`��
 ;PBpIx;TM����D������X�ex�l��n��tP)8P�)W�^�1��h��kp����c��r�fXb������.��e��g��s�m�u�u
� 
}0
\'.�sX
�`
�<��
c.@�ad�b��cH/fزh��i��l�m��n�p(�r<�s)t�a|�il�Dh0���hd�p��a��s$�����t@ux�s��t���c.�`�a��i��t��N�1H��r4�ud���a<�e|�ft/m��o��s�_ 2L�lh�n���  �( !T�s u\�t��T �t�e���
��r �1� �� )��a��tl!��!���d�epGf��i�t�"��"����h�"��rT")��eD#J�%p&D �p�e'����a�l8�t*��Xa04hT�i\�s\+L,7�1���#bx�dh�n�2���e 343��n�@)��e��i�C��htD� Ht�G����e��ldH�0P��l��n�sR��R��S�U)(�a8�e��i�_o��u��w�TR�W�W��0�i\�n��s��t���@Y�Y��T�ap�ex�t���dY��[_\�\�����\�^��\����s8`pbR�T����aP�bX�c`�d��e(�f0�gH�hP�i4cjt�k|�l��m��n��o��p��q��r��s��t��u�v �w(�z<��D`�����H����Xl�����t�iԤ�X�l�e��	��c��g��i��l��n�r�s �t�hx�)��_l�)�����a��e,�u�����g(�i�o�!P&.�e�����s��tH�����T���T<�e,t�b�1�<[`�gl�n�j)��alv)ܑ�T�����,���&��S�@j���uTj)@/t0j���e�jT�T����.h �s<���f�m�n�d)�!}��i89��@�U��P&.@�aL�b\�e$f$�g��h|�k��l��n�rĥs��tl�wTz��ø��0�b��c��d��f,�g��h��i�k��l@�m��n��o��p�r��s��t��u4�x��x�ap�b��c$�dT�e��f��g��h��iL�j0�k8�lt�m�n$�oX�p�qd�rPs,t�	u
v 
w0
y`
z���tn�8 18�nTpI� �� ��T�ll�n�sWx�d,t,"��!���eX"�4#�T#�| �����|#���u']d''��a�@iĬr��s()�p*�*����e�f�g$�r�*�0
+��+�H�aX�e�l|�o��s$,�,��,P�ip�n�,��,h�t�&H.���t�.J�.��l�/�/J��e��sD��/!��k$1u��k��l�n�r�1y�12��1����s�c.�20�a8�rL�tp�z<3D�3��mo84�@4!D�a`�bh�u`4���4���5��	x�.��a��b�\d��e�l4�o4�sUt�4u��g��k�5����6)�?.��k��u�z����7���n�7}ԓs��k(:�g�9!�e��D;�;�,�pL=1X�ed�it�p�3t�=,orH>u�`�d?)l�l<@�h@����ap�b�3d�e��f��g��k�nl��m�n�s8�uH�wl�zCTCd�DX��l8E����E*��e�E�E�aPF�P&.(�i0�p��F��G`@��GO@�a`�� C�<C��X�� H)P&.��a��e��u�$DHRLH����i��r0Z��%����h(&S�H�Hu��r@I_�Z.��sI��e�pf84l�Z�J��hb<�e`�ih�k�mp�t�u�z�LN�L(�sL�0�nT�r���L��L�eHM�N�DPd�i��o�P�lQ|Q����a��c��p��t�Q�S��S

U)	�?.��a�e4�h�qlH�mX�od�sl�uTU��|�b��n�u�U�$V�U�i$�m,�r���\V�xV�@�e�V��V��V�V��P�m|W,�X���mDZ��g�4l��nYx�f\�g��k��m��s0�
�Z��$\,�b��c�e�g�kd�l$�s,�w�\ �r�w�\)��h�* �\)�\|p+�,] ���_�H�i@_H_��@�s�_���ct4d,�e��g�sh��l��m��n�p�r4�s<�tX�uh�xD`�����T����p��aS
��y4b���1dc����td=��tLd��d
 �d!��sXd����g�z�$��e�e��P&.,�u��u�g��h1H�t�i�kG0j��P�sܪ��kXl��e��l�*���n+-��e�|���kx|u��c�z��o� �)��h�k��l�r؊\���a\�e�i�$l�uغw�ȏ1p��ؓC�8�ut�P�0�nĴ���ldqn����D�a��b��c�eD�fP�gh�h��i�k,�l��m,�n\�ph�r��s��t�uL�vX�x�Tp>����b��e � �����n$�6��a�n�����h�k؊�$ �$ ae��=0m0�n8�r�s ��������w�8�f���\�r�';��eH�1��* ��up�rl���x�f�rk��l��n��s������e��g��lx���Bc�
������elB��a��6.��bst��0 ���`��tt��\�bd�cl�u����
�at�b|�e�f,�iL�ll�o��s��t����.��� ����*,�
��b��c��f��g��h��i��m��n�r|�u�����e��J���0�JT�Jh� ���p�=��n��� �����d�?`��)�.���T��$�n<�t��1���!D�e`����X�t���`�s�������x��(�!�daPnh��r�����T�iH���e��i��o�p$�ux�1\���n��)��r ��0ZE
dX��r��)�e�o��6 T�!����|�.x�a��b��d�eT�gx�h��i��j��l��m\tnx�o��s��tD�z��t�0eb��d��g��s��u����= ��2��M��| �)�Ya��f��i̳l��o��p�r��T�y|�	��������:b(�f0�iH�n�r�:t@��T���!.�dtm
|��@�s��7�?.l�lt"���ud�a��$�u��n��4ckl*t�����m�����e�����!��a��b�d�e�r�w$�zp�R(�B 8�!p��;�i4�Rx�����h����,�r��)8�e�|w4�D��T�i���<.��a��b�d(�e��f��g,�i��k@�lL�m��n��o��r��s|�t��u��w��z����<�b�fd��fXum��n�������6.P��
,�)��s�t�����<#.��)�e��|�bL�i<l`�np�r��4p���D�fX~���=X�d�gD����uk��zĀ�\I D���l$�)��o��r���8�Q H�p�lT�����������r�u ���(W ܅��iȅ;��e�u`*�.�1� ������8�aH��`�!�Ð��`�e��2��X�rl�U0�l�r��Jt�a��e������u��2���������� q` `�����s��)��a��e��o��yh.���!LC.(�c<�eH�t����a��hP�2H�4�r���\�.d�il�u|�h ��jx�o ����!t�aL����fPC Cu��h<C�������)ؾe���`�u0�)��a��b$Wv ��a�e �i(�o0�pD�sd�tP�,+c4�T�r�����!<�r��4��X�e�1} \�P�.@����e��s��������t����x����5i��r��s��� ������g��k������X�� ��t".��a��l8���R�����i�)��r|����e(�fT�n8�r��d'B\���0�a|=u�)D�i�x�a��e��i$�p��t��z��,�DD��t��krT�*��i(X��l�l��eH,��r�1<.,�a��c��eh�it�k��l��m��n��o�(p�r^sX�u0�vp�y���@1_�!.L�gT�i\�kl�lx�u�+�$1�21�2t�2��d�.5�5��5�������(���pc�6�t".��a��b��c��iԉk�m�n0�o@�rL�sX�u�6�$7ql71�8��n90:��:eX:���e(�i�:e$;� ���|;��8�r��|)e�='
t"�$>��`�.��k��l��mT?Xh?� t�A�\D��E��Gu��c��f��h��l��r�t�u�GBHH\/�|HuLC.tI�I��J;@JR	�J�<�i���L$�.H�spLu,�n��� dW�(W��P�bh�n�X� �Zu�Z��d��m<[��x�at�b��c,�d@�e��f��g�h0�kp�l�m,�n�o�p0�qD�rP�s��t��u�v$�zL]%�`uP�e�h����`��r��8�d�a�n�o\a�e ��bul�f�lx�n��r��tT�u���t"�xcd�.�d� �fu��e��r�f��c�����W��������g��hu�?.�r�in$!Lk���z,k�r�j)�e$>��l)�i�m1�m�m��rpm) �a�i\�rh�s�o��o��H���o;P���ol\p����a��e��g�l�t@yu��q1�q��i��l�q� �Dsnb�g�r!��eq���p��sg(�_`t�g t���a,�i �m�p�)�u)�alv��t".�a��bl�dt�e�?f��g��i��j��k�s�yt��u�vPw�w�y�,y!|�a�l��s\y�z��y��tl@z)��l�{�P&.�e�{�|N�|�<@r$6.�l�p���\Tj)LC.0j��e�)$�ù����<�h��)t�c��ezp<���a��w�d�hԃ�|�1����i�y�����e��|�B��m܌1̌���n��)�a�e�o�r�[�l�r�1<}�Cl�>)�?��)��aܑT�BH�el�i��{m`�r`�����X�a�� ��l��a��e�o��u������l@�S������t����nL��	T���s<���e���d,�=	�e�J���s(7�&B�sd�t�&���al�b��c��d�i�l�m�nD�od�p��rp�s�t�u (W<(��j@l8*��t*u|�h��k�*� +$ @+��$.��e�up+��r�s��d,x2uX�a<6�
�n�5u�e�g�61����7$�a8�et7�~u�1�8�0�h@;�;1�;L�kx�t�;��T�a��o��t\WX,=� �=��ut=��
��a�c��d��e�g8�iL�tX�u`�w���q_�>p>�n�>����D���1��?��.�lh}��@O�a|@� ���HA
$�t�@,�nB��B��?.�C�X�D4Dh�cp;k��p��s��thE���2�E��u�E!��eXF�
,�2�H����n H)�eh�h�J��iԩn@ut�l�M_,�hTM����cTf�k4�l<�nL�s�t�M� HO�PS� dRD�t�S��f�j;	t".��al�e�h$i�otuHy���kS�htj����c�d�f(�g|�h0�i8�mL�n��s��u���$�l�apl�d�.��e��k(�m�lp�l��r�lu�amS
XmM
oSD�s|?b	�o��x�.h�dp�k��nx�s�o�lp��p�  qu(e��i�R�xq� �q�����Lr�q��m4sS��g�h�n�s@�u,��hs������H����s��s� �d� �s���e�sW�h� �h��$��<t� ,t8�sX�Øk� �k��P���1bLyd�.Ęa�b�c�d��ef��g,i��k�l)m�n�p��r�s�t�
uL)vz@zq�r`z�z=�Kh�k�z��z�{!${S
ef$o{!<{
!L{!�{���K.Tb\fdglhtn|s�z(|!��N<|\�l�|�	,}i	��\h}�~���.�d�tX~��~S
�e�o�r'(\e"!�!���.��(!r��.!0��L�i4�D̆	b`exn�o�p�u�x�Pz��ԇ��.ps��uPb�rȇ4!p�S�d�g�i��l��(��|\$2$��,�����|�(����,\����c�i�kmo p<��6ut�ui@;�<�2,fP�����	����4�����Cc\hdt,��
,S H����lg�k�m�n�*ph�t���=��?�s��6���}�d�kd�<!�-�����L��eu8��b c,g<m`��\�t��
�k4;��C!����4m�`�u�a0ctei(k@olp`s�tu�Ä��Ĥ
�b�cl�d\h�l�m�n�r�+s �u�Dئ��K!P��tШpH��nt�����$�d�j���r��a@eTl�r�hho���p�f��J��JLi,����`r����b�h�i�m�n�vr�s����Q!�����l�n(�kܱ�d|������g4�t\�4�!�e���s��h�dH�n r����l��a8r��%��P�r��S|�Ll�n�r�s0�Ta�e<Si�o�S��e(�W!8WS�X����d�kYS
d����n4Pr��a@eti�o�r4�p������t4i�T�1i����t���(�����aTp\r8�_d���p-mlr��
0���Qe�s��t�����u�c�r��^!D��d!�����d��;�a�i�o�À�������\�8��1p k!�(�bd�r!d���!\a�e�h�r	sL	t\	u0���ex���	Pb�c �d�g�k�l�n�r�s\�uX-x!-q�sX��s,���e��u�FX�����t�����t�����eT����bi,lhr�sx������fs,���,�aHe`o��_|�@bXn���l��h�tg����������|�����X6=����u��������Dho���d�;�o�u��t��������t	nPu	e�Wp<	t@���m(r��,	e�ii,~!�	�	��T	cl	n�
{�����mut	l�	n<���	a�	c�	e
f0
h8
iL
kX
ld
m�
n�p�rHsdt\0�D�h��	rX�����	e�1
e$ 
n(
r�[Hp�@u[s��!���D
r�`�ad
t".�
e�
f�
g��k�
p�
r�
s�
t�
z �T 
\ �� �� )8!u� )�
i�
o`�uD���!��!}	`�.��ad f0gPihnpsxtT"g��
��#!(a@s$�!X$n($Hf`o��!%�@%�!�%�p&��c.m�d'�g'��	�a�c�e,i8nD�o@s�u��'�(����<()�1\(�.$�irst�(������ )})$n���)7*u8�ax�c]o,.�-)\a�1��D�b�cHDf�r
t`2�!H21�h�k�2��2-�sD7��Xb.Lfc�e�g�sh7�
�7�!�7�!,��9�n�81�e
z`
�89�@7�Pa�i0P�1.D
pX
s0S�P
aTS��S�U!�
a�
e�g�i�%o�t�w�
��T(
�
g�
l�
r�u�U��U8V�V��V���
�H��W���1.�
c�
inDr\s�W��W��Աes0X�!Yd e0t���pY�dY(s`ZO�Y��<lTs�Z��[�ht�[���`\pl@\lxe`]����t�\���nx_)X�h�i�_1`���n�b��b����b�i�u��8ctc�u�b�c�d�2h�l�,m\nhr�s�t�u�y��ab0cpde�f�gph@i4cj�k�ldm�!n&op&pГq'r*s�-t�0u1v�SwH1xt1y�1z���y|#)'�r5��
c.ab e,f4o<rDsLt<]u�4,3u�6��8o	�7�t�8�;D�H�;O�;�
PF�h@��TsJ����.�a�s�J�xJ|b�OO|Q����axV�U)�h�i�VUYy�s$\�\_�_���6m4�nXuD`���-���1�Xl)4eDi\l�o�r�s$Ðq1�p��,c�(i|xPo�y���s�z�ti�u|�|lc�|��H�}���p�;�i����!�����̋R	؀�t؁����n|�!�a�op4�)|�_h�a���D�b(p����u�`cPeph<aidk�&l���\s$��؊�?.\�da�e�i�$l�$m�$n��r�s@tPuغw�������$&�D��*c\$i�s��i	��l�nh?���Ou����op$t\P�Pr���lU2@U,r��4o(arȏ\m�X=p�-�*e�)p�a�e�i�o�r�������e�t���r�gX��(e�tx��Ȧ���iԧ���+nL� �;�au8�R��)
8d�Xe@gHllmxn�p�rLsttD�)��)�����)a��e\iT�\�uH�di��}�e�g(�i�o�s�z�ä�1���r�����!�e��u����a(&u��)�u��)������aXe�g�i�n�o@�rstu8��������0���lp���Dg��Lixm�n�r�}0
���pp��m��=�t�xD����g�uk���<�������n�t���n�M�!��J�@e�����!�c���x��!L��m4n��z��},fDt���!�\edp4�9���!$�u��la�e�k���k��hbP�h	k�ruT���
�a$e�f�al�o�ar�s\t|u���g��s������@�L����8i@m�[nHr��t����P/hdlpnT���!\e���������x��X�l�r������oH��b�r��i�"D������!p0t�����o�n��m<r| ed�������DblnP!Lets$�B��m�)
�a d(ephHl� n�o�rs��(#�!4#!�e,�t|���b@yf�kn�psT,u��g���g��B,�.i8rH��uf�OlH")hato�u�Ü"�!t"`d�$�@#��|�%��&�rTM1 ����h�,}��(���s�(�i�';�e�oô)�*$sd(�����*u0lu�*Jal.c8eLi\p�+�Dr@,pP,h|��p,xTa11�1t".�l�m�r4u1wDAAJ�a�i����BuE�\D)�e�Jut0a�e�itu ��K1PK�rpL�0nHNg�Nu�N2,K����XN	(W��,n<[u`che|g�bm�n�s�`��btmH���jh�e,k�lv��c.��c.�c�i�t<�42.�s��ht��!d�(��n̆uܑ��
��aOeHiPlpnxo�r�t�u8�0j��$u���,���H��jde��H�u\i���������.��!��a�i�r �������nȨ ��s���T�(a�AbPdte�flg�ik,l�m�o�p�s�t4vhPz@�ح���� m�����6mز��4�,���!t7rhsȮu��)`e��7l�n�r�s�t��x��Ľ�����fPih�Bw�u��a���8a,�*�����k�n@����9s���)ant��X���!$aHe���|�@nXs��X$�0���`n�hi��)te����i�r����)��)�h��u�md�!�a�t�z��2����=��!�aPnhr$sx��0�;i��7��1L�)�^r�����@k�l�n�r�u����Ha�d eT f\ gp i� k� l� m� p� r� sD�t�!u�!zH�O�����l��u�zp��DPI�����t$����st4�H��u( t�� n4 r���@�!  s�����=aL g�Ol,�w�I������<����!s��d n� r�������H��c� a�T�JH��� i��u� l��)� f�l��HT��!��� t �u��)� a$!e8!iL!pl!tx!u8�1 �!rP�1T�0!m���l�D!e��;���X!m��`!e��u����n�!rd����,�}t".�!aT"dD#e�#g($i�$k%n(%o4Fr@%s�%t�%v&wt&z����\'."b"c "l("m0"n8"s@"t���
�y�J�D8��D�R	��\T�S�"b$�)
H"a�"d�"e�"f�"g�"i�?n#o#r#s�u4#v<#z��_x���a����"i�"r,����Cf�"h���4��"a������\a�T��"d���l���#r��D)<D.,#pXup����D�`#bh#hp#i�#n�#sH���t".PFn��$�#t��! ��
�#t�#w�	!�#e�#l�#r$s`���|�!����#�����#�$$a$iT)H>�
)P$e
$dX$f`$kh$mp$rx$s�$t�$v�
D�h�tJ�u����$l4��<D�%�X�$a,Ee�$s�$t�$ul�l0�8���$t!�$i�$r` ������$d�!%a�EeTp %r8%s�)�
P&.e`%ih%k�1px%t��L�D;p%r���%a�%e�%r�%s��u,�F��%i����%r� %8!7?`�%l�>���%l$l�%o$?7,$O�&u�bb,&f�Hp4&rP&sd&t�,t=��.H&c��s�>4Dc.�H H)\&e�S���ea�&e�&fp�i�ep�r�&tdXj�&r0Z}[��xIa�&e�&i�&l�[\�<\�!�g�g)�&a'e'o�h��L�g4h�&r�h�j��t".d'a((b<(cP(d\(e�(f�(g)iP)l\)oh)pp)r�)s�)txu�)z(�tj�'b�'m�'n�'r�'t Kutk��'a8e�ko��o����a�b�Jg,�h�'s�p�!�p��'e�Liq��'n�q��q��'t4s(nhs���'�g��s�typt) (a�vd�vu4(h�xy4w)H(iLyPLf�(l�~n�(p�(r�}_h}��x(e_8��Pih��wD�X�(t��7��ȅ;�(i�(oԃ!�(r�(s�)�!�)���(s��@��̆�(c�Mm )n(�"p���)i4)s�Mt��� ����y<)u��!D)a\��l�l��B$�X����x)n �uL�!�)a�)e�)t(Nw�gz��Mr ��L��)r`�N`��0�)�)a�)i*oPQwQ��u$�u�uD*a�*c�*e\+ip+k�+o�+pL,s-t�-uh*�$�2Ĥ<*fT*nħ4nH�t���`*��+��Wܬux*t�����*n���*e�*w��*h�E�Ԯ���*r��*e���Oc�*i+p+r4+s����*d(�\l��̴�����+a$+p���	��8��,+e\����1.�Ds(�D@+rh�P+e��C��lh+l��o���+c�+hSl�p�+u*��b�0���+a,e,i$,o��uW����+t|��+rs�t����kh�̅c,z�`���4Pr��1��0,ch,tx,u��!8,a�,e�,i�,o�,à����"��p,eD�)T:aP����,f���,r�,z��h��r������f�,h�1����<���q,-e�����,b4-lt��,aD-e�hh-i0Qo�-r�-u4y\�h����1���<-nT-rd����.��u0���`-nx-s��\��;�-i�-o���̋�\��-t 
"�����-p`�h��-r(��-r(�u��1�-u�����),.al.e,/f8/hX/o�/r�/s`0t�0u�0z�R�x����1.H.lT.m\.pd.r����T�t@�������T���	�1.�.g�.i�.n�.r/s/t/v/xP��
�.e(�J����c.�8e,����.��tp�J(��.rh����.e�.s�|��!�.a��5���L�XX�������*$/i���-e��iDhoP/uX�1$����1.�/b�/c�/m�/p�/r8)2�(x/rl���/e���Dd���t�fآs��JĘ�l��/ad�;�/e�ÌyPu�/a,0cL0p0������0��il�\m@0�00h��"x���80�l�@Ja�,!X0et0l|0s8�C	l�J����0e�0u�	���0r�9��d�0g�
���0n`
!�0e�0i�XoYw������0n<l�1�89x1e�:�t".<1r������(1�<}01��K)8_e\1od1t�N�N7P0P��l1aU)�1w�1z�bb�1e�c�Xl2c2d$2e82l@2r���1bH2c�2d�3f4gl4h�5k�5l46mH6n7pD7r8s�8t�v�kz S��p��p�	�p02r,u��z�u�1`2h�2kD�\�X2e�$l�2s�2txO!���x2c�R���2e$�e�2i����2rp�-�2e�2s��R0����2e�)�2pĔu����2a43eP3ol3rt3s�3t�3w`3ü���6.@3gH3s�� 3n��h��ԧx������X3� �u��B�3a�`����x�)�3e�)T����3a�3elaf�3i�al4t�8i�3r��3g��IX)LuP!�3o�P4e�al\4nd4s��wp�=$4s,,4i�u84e,D4l8%)�*7�1��<ba�4e�4i5l$5m�bn<5rx5s�5t�0u1w�6�4i�4n�4r�8��:�X:���4g�<g|;���4k�/z$><A�DA���4cAJ�4a5e�AxE\D)5oPKyX5i�Ju05e1ol5t�K�K��P5.8NHN!d5a ON�5thQ��P�5u�Q7�5h�T!ܑ�T�1�5a$qc�5e�5l�cu���X�u��!�5a6e6o��|��4id�u@�d����6t��6n����(6e�),���@6ax6d�6e�6f�6g�6t�[u7zT�u$�)p6a�dr$"��6n 	u�)�6a�6e�6l�6r�8i�	�l	��6i�	��	!�6l�l�$��$)7a�S���Pe(7i07pl^0d)�eltj�j��<7ah7e|7f�7g�7o�7s�7tLydfi��D�)t7lP�r�
���7gT��7nԃ!�7e\�N	ț����7rL��7eL�!�7tX�`�!�7hĤ���8a88cL8ed8pt8s�8t�8u���Rl�,8h���D8h\8l���0����J��!l8a�ec�8e�0t���Sa�8e�hr������8e����8n(���1�8a9e4iiPir9s�itjux��lT�P����`�b@c�9l�9n�9r�9s�9tx:u��9a�:b�:d�:e��f\=gd=h|=i�>k�>l�|m�an�>o�?p�?r@s(@tD@uL�v�w��z�:�5��8sh@����gJ��hb|QU!�9a�9e:h:i|�r`:sh:up:zTU$V1�U�9ixV0�l�Vp:k,:nT:o,���"~�4:.�}<:s��H:n|W�X)�X7Y��_}D`���:�Xl)�)����:a�:b;c;d;g;h$;iP;l�;m�;n�;o<r�<s=tL=vT=x@=�Ĵ)lqr�����D�����H��l�0;t���<;s�{t�h;u����D;ap;e�si�}o �M,�|��H�x;a�;uT��t�)��l�����;a�;c�;ep�i�;������;n�-|���;d|�������;����}8<ah<bp<dx<e\
f�<g�<i�<k�<s�<t�<u���L<d\<n�;sl���������T<d,�B��l����Lk��g���<n��2���!�gt��N�<e��9L�8���<c�<e�<h=p=t���4�)������@�$���=a0=e8=r��#
��
���������)�1)�Z��r�=t<[��	l=a`c�=e�=g�=i>lD>m`>n�>s(^74��H����=a�b���=hl!l�=r�=w�fu��e`���jl m?8p)\p��>a$>e8>i�q��q>h�r�`r0>n t�P>a`t�DvJlv��X>at>s�yt�{F����|>a�>e�>ozp�>u�H�y��ܑ)T�l�&��D�b�>g?l�pD?r�?yp.@.)�>a�!ux2D$?l�4�4?d�4?n<4!?e<?i�4D�ut=�h?a��dp?ex?g�?i�?m�?��=��?#"�@7�@�?gЈ��f��A�?n�A�?eB("�>���?��L�S)�j�?a�?e@otj�Ly@e{F\���) @e��z����)t))0@t<��8@eT@md��,�f�\Ta�@bAchAf|Ag�Ah�Ai�AlxBn�Bp�Br�Bs��`@a�Cb�pc�:d�Ce\=g�Gh�Gi�>k�Il�|m�In�Io�?p�Jr�Js�Jt�Ju�w<C��d�b�#t'e0AsDAt|#���@hhQ."��Au$%�$At�%3"0%�<ArjtAl�*)PAe*��\Af���+��Ae�An�ko�,9�-�.�Alp�	,B���At�/�An�Ar�/J�Ae�i�/2_�$1���Ab5Bb$Bd0Bm@Bt�6��J`7!Ba�:��;u�;!8BaTBodBs��u�<��<J\Bt<@h@��pBa�BgTC!`�sIL��BiJ���Be�Bt�L<PDP!�Be|Q��aCcThCs�R�Ba�Q�Bh�S\S!Ce�_uPChXCl�mhCntCsD`�� C��J��K�DbSdcS�d�Xd��`Cg�gS�Cc�Xll\nuĴ�����
�Ca�CbDe0Df<DgtDh|DiEllEn�Er0Gs�Gt�GyԷ���CaDe@7lDs|q7"����Cb�7��f Dd`�����(Dl���TDa\DldDrlDs|)���'��NH�l�(|b�De�rk�Ds�Dt��q����!�Dp���Ds�Dt������Dr0�f��u�Dr4�s��l �r��Dh�!Ec����Es(Et(��<EaPEe\Er��l�D�dL���DEn���t�����dEap�i�EkȤp�;�Ei��)�Er����a�Eb�Ed,Fe8FfXFglFi�Fk�Fs�FtXg�,�)�Kl�Eu�v���lFeFuh�>"�w�Es��FnP��
D���$Fr$�)t7l���t�DFl��!LFe�����dFoP&���)xF.�Fa�Fe�Fl�Fu`�u�2��X�u��0a�!�Ft��u��!�FaGeGoGsL�������Fm�����FrОu@�G"4��LGn�$GeH�pXGt�N"����DGs@��a|Ge�Gi�Go�Gr�Gu�GØ�5iL�u��24������hl�d�x������s�Gt�@�!�Gs����1\a�<[��	�Gd He\HkdHl�Hm�Hn4Ir<IslIt�b`�l<HnPHs`e��d�4He@�u�gHHtpml\p�u�u)lHa�Hu t��tHm��7P�p�w�HcPw)�He�Hrlv���Hdlye�HgIn�w8,ydtT"8!�Hc�z���Hs�z�Hr�z!Ie(Iu�"2H�� Im�1
��T2ezpTIs\It��̆l�h���dIh�Iz�>["P�|IlT�),�u�Is�_*��Ih�&���Ic�Ih JlPJr�Jt,*�8*�Ia�Iep*�\/Jl0u�/JJex3D0Jsx2uJf@Jl�� 4J<4!8Ja|�et=��h?ahJipJt|Ju�@�B!�~r�C7 Hl�M�TM���Jc�Jr,R!�7hQ���Jt�j�Jo\��)��k��)�Vi<Kc<KlHKnTKr�Ks�Kt0�(KhxOJ��KcpKs�N�4Ks�!}�is'���K.pKf�Ks��u�(XhKa�)�
�)!|Kt*(
�-b"l4S�1���Kh�Ks8��	c.XLb`Ld�le�fhLgpLm�Ln�Ls���Ka�:b�Lc�Ld�LeDMf\=gd=hLMi4cjTNk\Nl�|m�an�No�NpГq�Nr�Ns�Nt�Ou�Ov�wPy�kz�_'��+�L= HXh@��xLz|Q��uP�e\�h�&l�!���Lgԉk�Ll�LmMnMr4Ms<Mu�����Le,�|siH��P�.��uMs������t".H�e,Ms�N��|��T�)<[��	t�bxMc�Md�Me�Mg�Ml�Mn�MsHNt�`)�Mh�`��a��Mm\a�Me�Mo���Lb��b��`�l�j\p}�a�1o@yu�{�lv���Ms��)�eNoNs,Nt8NuH�l��`���T�̆ NÀ�u�����@NuܑlT���NetNð�ز��lN���&���Hr�S!�NoHC�C���NrLc!�Nec���Nt�a�Nr�j)�B��!OaPOe,/f�Oi�Oris�Ou4O�x�)�1. Os��DT�@On����(O���=D��T���HOdlOi��n|Or`Cv�����[h���tOf�g"p����Ol�����Oltk�����Ob��nd�;�Oa�	�Or�D<��a�On��s�!N	89u0Pu�1.X
s���`�bdTc�Pl�Pm�Pn��Pa(�b�Pc Qd(Qe�Qf�Qg�Qh�Qi4cj�QkRl�Rm�Rn�Ro0Sp�Sr�SsxTt�Tu�v�Sw�Ty�z�P�5��c.L=TC�h@���Pg�Pk�D��h�_���PtD`���P���Qh�1Qs\��Pi�$n�@i	�x��8HfLQnXQrdQs|Qt������DQn���H�e��t".tQt@�J����hT����Qi�Ql!�QeL!�
H"�1�Qr�J�<[xܑ��Qe�Qsę�����Qn0�BT�$Ra��cTRe�9ilRoxRu�����pmHRnPG|�)4Red���<Rt�h.h�����i�`RnL�)������Rt�����Ra�Rp��B�Rip�#
p)�Rd,����Ro�6t�%�,�&���RfSmSn�Hs(Su�5�89��7��Si St�:��J�STSahSepSfD;h�Si�So�Ss�S�`Sn UdX�[u�_�l^��xSn�a�g7�j�Se�Si�So�SrLyy̆�Sa�Se���)\�$�l�
$Ta�Cc0TeLgh8TlHTm\TphTtpTu��zħuĤTn��`4����[�l@Te|��0�TTa��(�l����Te�ToT���t".�Ts��_$�'�Te<���Tr\([0P�|Ub�Uc�Ud(mf�UgD+h�Uk�UldnmVn8Vr�Vs�Vt�Vu���Ta$Wb��c\Wd�We��f@\gl\h�\iL�j0�k�>l4^mL^nT^o8_p�q��r@_sx_t8`uL�vbw�cz�V��.Dl�#�a�U�|#���Uhԋ�%���U�'��+me�Ur.%�2��7�5���Ue�&i�Ul�9Ih@�e Vf(Vix�l��oC�|D]xJJ��	0Vahb�cdVelVi��mtVo|Vt�zL�HM�XO�DP!�i�Vr�P|Q ^tUm"�Y�Y�Vb�Vfl�g�VnDZ��[r"�_WcWh0�mWrD`���V�_�b��`Db�e����g��mXl�HWì1�TWe���0Wb(p��<W�$2��B�6atWÔ�}����lW���(�e����
�Wb�Wc�Wel�f�Wi�Xl��mYn�Yr�[s\t�wx\È�W$��Wo����Whk��!l����rkXl(Xn0XsLXt��J�ra Xe ������<Xt�����)��uDXatXe�r���4�`Xd�hXn�Xrh�pt�l�����Xa�Xe�Xi�Xl�ssYt�}u,�x�n<rT��$�n��!pa�Xe�Xi� nh\��L��Xn(�!�sh�����D.@Ya\tn�oXYsdYt�Yz��x"t�)8Yc��L�m���LYe��pYs��7x�2����xYr��)�Ye���t".�a�Ye�Yg0ZhLZiXZk`ZlpZn�Zq�Zs�Zt`[wl[z��|�b� ���Y���!Ze�Y�h��t�Zb�2���1Zl��$Za��Ø���t��@Zn��l`�!P&.$������Ze�k��|Zb�Zi�pГr�N��a�Zt|�~"���Ze��x�Zg�Zn��!�Za[e [i@[r,��ԜJ��y����[e����[r�8cn������,[u�;4[a�������L[i��)T[e0�gx[a`��"��[e�[i�[k�[s�[t4����P�l�Cu����[d\��[n�[r4�!�[e�gpԳD��2d��
@��[r��{����\����%r4\t�N@�!,\s�l`\e<H��L\a,T\r�1)�\e�\iX:1�6��|\nd@:$>���\r<[���1.�\a�\d]e��g0]lD]m`]n�]o^r^s(^t�Z� l��r`b�\a�\r�f��Pih�b��]r]s�gc.�q\p��(]e�uX t��<]m��p�w��]rlv��T]etyf�?h�]i�]sl�vlxJz��y�]t�{u�{��]a�]e�]u�{2�{�]rP|�"@|�]f�|:�]p,~h�%^s����]kL�-����zl���� ^h��l@^e���e,��&��D�b(�f�^g�&h�^l�^n��o�^p�^r_s_t/u@.)�^lx2D�:2�:���^r�:)�^e�7���^t�;����et=��i�^n�AJ�AJ�^e�wg4D_z H.TM��f\l0_n�P��S�N\_a04h�)kl_t#zĤ����d_r��)�_a�_e�_h`i`r(`sx���|�u��eh�1�_eT����_h�_s��P�t@�����_r���_e�o,���0�s�����_nl��cd�;`ePB<��0`a�Jb�`c�`d�`e�`f�`g,ah�iDakLam�an�ap�ar�as�at�az0-p*�`i��Hu)�`l�up�����`r�)�`a�`e$Klas au(4�n�,�aeJ�`t@-��/`�.anpX�r<au4��d���.tae,�f4�g<�l�"s � lan�!}�[eȳt�&)�&���aip&���afd'y'���aa�ak�l�as�at8���)I�)�*B�-B�1l�1��D�b�#r�@<ba�be8ciPcotcu�b�A`@4bcXbhhbn�br�bs�A��A�xB��`bdxbg�Bd�B�BXCA	 Cu�bl<C���b�`c��c�<Dr�C���bg�bi�blcncr cs(ct|D4g�bs�DS
ElE�EcgXFd0Gr�Gr4IV�G��0crHctlI1�I��JS�J��XcrTK�Jlcr�KU!�ca�ce�coP��TC�[��W���csT^y10322300100242404412020410324210540040004302444123226040041231004421036304210310212430020032012302322143600203020123220321030601043410243241004211421210041144000440201102340031221022211101041460300022022245400023442380024102040214821000322080021080000000
8000006000400432402020414144010510034148010231420230065410250130111232524310600044022202300140242402482402261082200443022245044045621030225210800241034014010002101323022006040106240200026302122280004400641203043222103044001424122132330236405205266104200223424120141234311014022300012103014030102301023210125430100121212521610121412000501012000020200321000143232000400002310001050010302144100000500200002301123224000100002223220120401004002104010100403210422342432423030000130044105432834000	800000000
8000002000602002030022210422101042033004142340431203411032034021021042210040012231246222142212223220300021122332020320021410141020324341041102041040000020010600101004441000240040406120212420210212344323430432420200321402010364000232051032121241012210223030003204322002103400200144200143000250005421100210521610321002110013210402344321032120321103021011202121030041021430321210213122132021201030021212211023405010010000005000700003021210203010001010030423021120021240443102021002021232242402434002211043036100065000020100340343200341210122023330001020214200000632050000340004161030230300303000302410002230004303420103222100430024130001043020101020434200020204122000221343013420222341034300200242042220020412102100010400341020230032322310424222040020021021024022422043202521010150245020430104002042300022300400200320111002212004210010034102134430211240422044020402125210003023280002214044220104322323410614102121023430030101046033220222442231241020020420225200100342206104041044354030303224202421002111040224201420000343232341020007401224201260410322104022123212302202442203014210000210100122230430402220043210410210430321434000200406000033410304031403060006210001201025441000021230121232001363022023221030010300403012020004034024030241021021321153011330610202344030650000003020023001031000600000610010420400410302300003402042100021212403410120232144220061041030122203233002102020001043003000000342001001030200030041240120104010410423043060000010201401211030032102012302012121001002204438000303131410420020121011214305320122340004300100340221200021030008210324104004043232432123223021020210410210210214200620412402100102120102102324120410410401065020140400210214342104310021440430000003432004100000040000204303210652604140452254104200060021013212100430000036534043025404301406023012023420023201023002040300230200400102314340102520040125001034010020020145010021010414103032021424434410010400020244400610222304212220002140141224210302021212142141210020402000343432200432000605000020110232430320424344020041100242044341104052006012020452343010032400010020001010230242103610203242001201040001040103060400300320040300160002060032024100012465000003221004004322320004001004004004004204301000200124140010232541043430300040023100320210600002212030021303222232203414041212434522441032022140054002412041041234430100100110202303001250040100010652120650200300000102543000022400101012443412140000232121021432040434410124020400041010043021441025487210200420212012340103000432400210001002002002002031023434300002640332201000203200334630000161221040300024060000102434261411113201040001032610221440421210403430403234403432503106003430124200250062204400012520325420344002506221065212642202022434410104342104324102121030220122240403404032022410140003400102500104420360001421043001434070002412634620001420012100412040430010602104685210212542132430001042300230244044134204421210241430403400410430432121040640140031010322210340000210020045445000322322322132200020321065432230412230410650030010082100324003404052000410410432120300214100010261222012222250204023214121234102304304021210101000610600304102164030002020304003203204011020363010004010132010026203041204243121040000360100450422211310300204024030021242340312203002323300401204002144304105000004000024303206500020301046000000603220214322302106001001340004041321234343410211214024124121434103044321415000205002122040064002242010014224332044410232221042004104242030202023210212010003634436434223040034044030022412600104023424004103263043241062101100021022100024302041220206122130030022134512125221200002322422500040610802102414023030063000430221321002042344000325432002122030104304010200000054203032004000400306043232420030402030401021020062402432200230202201030412614232000043021030214411410230243034023232642212241002146521020030004100000230024202023043432430404324520200310210212210234210412100210211202321640404222004002002308708002000243030104000010210300540010104104323032430022200423400010120120320000600002103223234300300001020000423010063030003410012632300234440010001621401032432130101320042431021422012422243102004342100242100000423206022441002041021021032030363060400210254201026343030201320442410201303201412522410020021412000412005421040043440041240302030232022041010200424032000024020043043243023041201032320410214410232323001104010040023252032050002443043040030004000000200100030002423212002101200103000800200612430300804102322003241421630430420202543201432412202230433041040331070212632610211114121004302404140143002040320046000410002366213411220040012034320130230343250103210236105021033414200345030341025410452202424324031200050600213000201220201100102404131420450241524142240224230222002000811210460100254211416302014104232421420102042800300541334233101320101400104211202011401200410622141001230121101023411032214121132142320033430002002144022043210020004200452202504440320300320610300010011408300000044124032166020104404410320222430043423320001030230402232	1000871002006202010430200403000430002020012002101000430610210120200324202014487222320220040024650002144012001040366004324306233003310041100410112300430260004105201060504326400410403240430120421412412144123254100506323230103212443214430024432202302122304342102000640002201004300210400141052002104102002223440412104201402221230004104100100222146003000032120040030206441242430420121052020061422122001110052000020023440023020023220223240000045210043021260010230400020002021200232100040032001003004140103203032221234201114000012322344521063041021034052010041120412021021003021001034430004430405200410300001120000020001436103440130300443222141022125222341022123240100042122430002214100302300401410302210441210421232402410401000232041034200022100050203232001022104102010410320041043022341204052021043040031201040302004030210461040302144430430614106300020040310041042344031041020002012342016102254212320434202100140320200434200410061032200004134000240000234001043020100245000403204042301032402004030010220022032322232102031002106122304002034124100040200240210010021004102430650440602345401002000300123000120200234321212010424340410320432100020101243030223000420002001012413214302326220043041210042084122003022342024030032324341001204240410234400020040042600000003000200406000400220430434300300060046210423434220402161040021234145003024121232030340100140104201006102200003020100200010020002202010200034400232100340210232320144210400221401024234300042014221034002104141430400142100332004312104040001322360450441432444612122101061210004010041021032330003214403214034321025024005003301036441230321034141030000102221650401433210344000421200300380000413021004602044440301050000610100400326438701001002121221214202313340012432043032203200024102120PK
!<{)L�L�hyphenation/hyph_en_US.hyfHyf0tP�����'-4�������$�'–’-111001����������.a�3b�<cXKdPWe4xf��g(�h��ix�jT�kH�l��mp�n�o��p�q�	r0s�.t�=u�JvPwXSxTUyhYz�x����8_a�_cxh`r���c|dbe�fcg�l�m�n(ippr�s$tHudv|w�|����a�bc�d,e�	f<
g$hPih
j��k�
l�m�n�opp
q4rs�thu<v�w�zx�y`|z$��\O�L��Tr 	 aa\eDai�hd�art��be\�t��� ���cg�Sl�t�""�t "���a,dp�� �#�da�cgi�eo,t�$#�%( %��m(es�&	<eLi��,�6 '�fd,ghhgn�hrDs�,0)4jcTkhs�t�8|=�e<���i�y|=A� E�,���c�p�st�my�-H�-�li�Zo$,K�-���r�-���e<0M$0=m.��oh|omoHTp�ot�?H$1��@d�Ts�ptP2R�1��\ilro�RU�2��tn04Rp3���g�n�sr�s�tt�3���ae�vi�zl�{opr�u�#p4�a�rd(sk�!R tl�4���e�lU4aH5��ua|Udxuh�UmrDs�uv�lW\r[<6_<m\thsd\6��To�:j�:��hi\Vo�}uh5A<l(e�i`<���t@;���Vd~f�~s�ttz`~�#A�#p�e "����e�p�<=�cH�l�m<n\p|r�t��u�<���a�e�hi8o�rd�u�Xz�=�c� |'=Hbl>��Pa��>��hl�>4<Wipo�t�>R@�a(�e�bA$@���a�?���l�@l�aT�e�i�l��o|� ȢH�i�t�lB���lxBx�gl�n$tl� TD|r�E�<E=��a0e4�g��k �l��m`�npr��s�tP�u��w�FR�e�n8���r����e���pN=X�c�il�s$NA��i�o�L��([c|�fd�l�mo(rLs���XK��D�a�e�i�o��r�ud�y�iE�l[\O�� a8i�n[��v�O��OjDc�6�I��Xo$Q�`iQ���hlt�P��(�axcX�f\Yj��r�s���T�RS=`�l�t�uVR�U=�Ya�c�m�o�V��V���b4K�X�hxX4tW��r�sPW��a�bX�c�e�g��i�l�n�pL	ql	rx	sԤt�	u��vP�x�	y -E�X��xi�t�Y��lU�]���r�`jغ��b���d�eآi��,�c��$�c�mtg��g=�m�g���a	g 	sh�tt�EZl4i��A��l�(	tl�v�lj0	i�lH@	uAp	X	i�l��`	rLp��u�XZl�wK�w���	eLp�H[t�z���Zbp�r�	s4x��(�a�	eħi��l
o��r]u�����	r,H�	e�il��	mL�s��t�=
rt�xX�l��sD]u����(
a`
e�]h�
i�n�
o��rԇjx
n�
o`i�T��p
tdj����D�d�
g��m(�s|�tЉ������
a�
b�Rx�R؋=�
rt$ ��
i��
dDk����_d��ih�lt`m�
n�rȳt(���aLe�i�ot�u��y\�0��j�`l��m4�p�r�s�tp�xd�������epi������mxo��$���Eab�����b�e\�,x��r�w�����y��e ot���hak��l<�m�n0�rd�t(v(�E��KH�Rp����e��i0l�o����8dx�g�m�n0
rD
s��t����S�\�=xl��E4����m�p������n<�pd�e�i���	�c�d
eP�f
k
s �t�u̾�\�,4����ix����t��L�tp�X�e��A$
r`�R���<
i�o�EE,���T
rx����aa(�e|i\
uKA�����i�
y����
c�
m@�n��r�
tH����
aehi��o8�R��e�.����
r����
e�
h/�T�j�a4g�di@nHp��tPv�` �dn����(e�U��Kh����ap�h������fXg��m�n�oPep�t�(��,�e���n<��4�h�X����ad�=��c�gl0n\r�gs��t�gx�����ape�i0o|u�! ������ao�#(��(alfc��s��u|<��Hi0�4�g�fkPt4�j8�d��g�r�t�mEp����c 3���=�r����T�a�e��h�r8���c��e�ot�����c�l��mh�n�s`�t����i�Ed�y��ae��o�=p�d��lnXr��s�,H�n��Lo�H�b�H��da��=��d��lpt�H��R�����ctjj��tp���\ia��e�i,�o�u��jd���
�[b�d�e4f��lH�phr�s�t�u��v��x������1t�� e����@�f(t$0����Do��=��nLt����Xa�c�d�t
E�
H�E,�h(���t�,A�����l�T����h�H�|�j�t� �\$�l��)�at����d��eX�nTt����
@�aePmf@\h�i��l��o�r8	s�u\DLepi��r�t�p��ht�.����c|e�o�p��r�t�C�2�n0�eE��x�a��l�m��p�\sptTw�j�e�i�\o
RL	��	�bc�d<fm`n�tdv��	��a�e4hHi|o�uqy�&R�
Xt�/W�7la�:tn����o���`ph�i�]j�c�
���a�]cd�eT^fmps�^t�uHwȂ���Tf���t�e���ipj,t�+?�=t� a�o��h����pe<g�nht|�$��`uGR���btq�s�u�w�� ����t� ����d�?RP���dD!EL!=�e� ���a h�iX"r�"y0��Da�cdehi�*k+mt+n�+o�,p�1q<tD8u�;w�y�"K�"��Hf�l���!��	#cPl|#m�n�rp�rD(st(t�(v�"Q�"���������"����#Y#a$e�"eP#�ni�nH�e�#���i'o�'v�$n)at)o�{u�%j�)d0n$�qP&|(gh+Ula`3e�4o<5rd7uT/v0/�Xl�+=`bL2n�2r�2t(.z�;mh<n|.}�^b=f�=l>p�>rDtul=��.���a�e(h8i�orGu�Gy�1U�tc?ll@mn�@s Ax�2��2=n�2��a�tn�@u4jpAa�Aeh5jLl\mtn\6U��h6�BeToȥU�6|lg�k��09�@9�a8=`um�nXBo�pCq�u��R�i�Bo@� �G��9j�s`<�����t4A�u�:��b�Ee:��hCahFei�Fo�C�HtD��4a�c�d�e�Hf�k�o�u�=��@vk@n�p�rs�,�����e\OK��=�r�����e(DTHr|�0D� �D�E�F��E���e�G��G��a��Ah��e�K��dxK�� ndr�J��(Ia,e�IiPKo�Ks�lW�vlL��Xa8we�wtX� �Qjxi�QjLPd�l�Pn�yr�zs�PtP���KaNe�Nh�i�Qo`RrVU0SsTU���e�{i��3���.�a�e�il@oLrtu���b�c�d<e\f�g�h�ip j� k� l "m�#nl'o�'p)q)r�,s.t$1u�1v�2w�2x3yL3z��D4�p3���l�np4�H5j�r�
��l���dx��7���a�tȰ� �=�b(8�a�8��8��t�8na����9 z�9��(iL9��4l�:`oH���:��Xg�;,@;��ll�>��d�o�<=�r�<���a�ehi\r�t�u�*��>��j��i���u�?���o�r�@=$��`Aj�t�@�exB�0e8n@o�B��C|�C���`H��Hb8G��Po6�I��hf��Hpi���6��������,6����86����I,HI���l�m(JAXK d@eLi�l�o�rsu�Q�P��n�L	i��\O��,.�L��4r�P�la|c�e�o�t�u�P��P�Q��ta\�A<Q�r�Q�pR�Բ��RA�R���e�,S=�w�
��T���n�T=�a�-A�U��u�U c(mV��V�lRPiPW��0r�n.�nHHe4x�f�x_�x��h������x��p��|����{��t�=�b�n�����ae(i0l8n@o�u�yP�R$e�4����lԇ���lo u����u���H̊l�H؋�Pg`n�	\�,�nXi�@?|��lrH���te�l�A��A�,(����a�e�l�o��,�t�,��j�a c l$ nd tx�W�������.��`��� y��< iH o��X��4 n4��2��P n@���X ex�H�=ȵ��x nT���� e����� b� d� rH���� a� d!eD!i|!l�!m�!o�!y�!���,�%غ�� iT��!n<!oh����!d0!t��+x�2(!i�i���HT!a`!ex��.x�.h��4��h!v�Hp!e�!iD�8|��!c����?l����!.p����!g��=�.�!s�!t�!zD�?�!t�XE4Y?hYLd�<"bD"gT"rh"s�"t����"a�"e�"iL#o�#p��RX�,h)0�4L"ah��t���`"c��W����t"s����|"i�"o$0^�l�p����"a4����"r��,����"c�"f,#l8#n#������"�#�#�0����"�������������$#y��=D#o4�d��\#nl#r���� ��d#iD�����x#n<�p�#ep��#a@$d�$e�$g %i$&kD&nP&ox&s�&tH'u$��0����#e8�=�#g$l$r4$t�!iH����#y��p$c$$i��u��R\/L$��,$i��`$et$i�$l�$o��z��=X$sHRz���l$s�R=�S=�$w�]������$e�$n�$s�$u����?���$.����$t�u�t��%i%l������$e0�x������%c\%eh%f�%m�%n�%o�%p�%s&t&u�%�p�L`2T%s@����X���p%��%��%�d���x%�L��������=�%e�%i�������=�%e��(��(������%h4�,Բ������&i|�t&l�����0&z���8&iD�U\&t�h&h��������p&a�&c�&n�&p�&t�&u�!�R�&o�'�P���&o*,t�A.��-=�&r�/7�s=�&l��H�&a'i('o0'r8'w��8��'e8�x��L=���[����@'a\'ld'r�A,�E��A���X�=t'r�'t����|'a�'e�'h,(ip(l�(o�(s�(u�������'ot����'r$����'r����'e(i@���8@(rȢH(a���(l�� (lH(nX(t����! ��0��P(ah(uH�X����x(c�(l�(r�(s�������(a��d#i��E���(t,$����(s���(e,,	K�j�(e����(u�	4h)al*b�*c�*d�*e@+f�+i,m,,o\,pd,qt,r�,sh+�2
��T)tL	��
\)c�)d�)l�)m*n*p*t@*uH*vP*w@?��)e�)iHRL��)s�
,���
,�)e�
���)t�
���)e�
�
�)g$E�A(*i���� *o8*vh7�$��2UD4Ut��X*l`H`*aLA%��x*n8
��*a
H�*h�Q�
}�*e�
�*n�
���*i�*r�TA�X�(���*s�
���*a+e+n,+s�]�����+t�+p��$+s�|AH8+it+l��T���P+�|+�h���X+��1_��H�+a�+e�+m�+n,o,z�5��+l�+n@�p�:��+t<A$�@x|�+t����+a�[����\H,i��F���$,dH,nT,o?�@,i,J��E�Up	l,eltDt�,a�,h�40���.�,a-h -iX-kd-lt-o�-p�-s�-t�-u�dj�,b�,n�&� �,t%U�$-ix��!.�%��-a8-b@-cH-t�%��%��&O�&E�&jP-i�&z (�'=l-c�)<)���-h�$U\*��-h�2��+���-nh+���-e�-r$-��E(.�-a�-=�-r|.�D.bP.c`.ll.p�.��
�-a�.e/h\/i$0ol0r�0s�0t�0u1yT/Y0/�<.l�/H0��/X.o�02 �.h�1��t.c�.g�.n�.r�.s/v�@5a�(2U�.o�2���.�l� 3���.a�.n�3 �.a�^0s��3���.tTv4(/e@/oX�T4 /m8/nd�e�4AL/m���h5��T/.�/a�/b�/c�/f�/o0t�/�X5k�5p�5=6u���6���/��/��/�,6���/�86y���������/r�6�/a�6:�/n���\7��0u�88=0g<0m\0pd0s9�P0i,@`���H0z���9�:=�0o�;�`;��x0p�&t�;��0k�|.=�0g<l�0a�0e�0h <4A<<��0a�0e\'l1rt<W�<�E� =��1a|=��>R�=��1bP1gh1lx1nT
r�1s�1tA�A��H1h`1uH�,�Az�� D��p1d�%�H���1b�G��1i�H��H���1n�H���1e�1h4=XJ��1g2n�J���1a2eP2i|2o�J�$K��K��K��2oxK��2n,2rL���.a@2nH2ydo���@MH`2el2gx��r�M,P�O��t2c�2r�O�3L�O���2yP���2a�2i�2l�2s�Q���AXR���2ySU�BTTE�2c3dXS���2ilTA@U���lTU��3a03e83sVU�XU�Y`2ehY��@3id3z�Y�HZ�\3i��	,.4d04gD4lp4n�4r�4s�4t�4z��p3a�4b@5dH5e�6f�6h7ip jx8k�8l9m 9nL9o|:p�:r�:s�:t@;u�<v�<w�<y�6︈��P���3rdP��4e�4gA���(4e� H� ��<4aL����P4g@$X4a�#d4d�4e�4i�$R %E�Kl*H�4i)4�4b�4i�+�+H�4a�*A�-��4i�,���4s.�L3R�35e45iH5�5r�l��7A�7�� 5a7��(5nXKpPW���.�5a�5d�5g�5l6m$6n<6sh6t�6w�6y� UW��|5k�5tY��\��5a�5e�5i�\�],(]��`,�`���5i�5u,a�b��5i6oDd�Le,�e���5�h��6g�g��6i46u�iLp_L6p\6t�r,|s�0s��T6r�s��6i�6r�6w|7�Xt��|6z�tL=�XwE|W��w���6o4x��x��x���6��6��6��x���6��|���(������	7b87d@7eX7f�7l�7n8o(8t|7�p�x��P7n�r���������`7��7��7�0���h7������`���7i���X�H�7z�����=�7r�����7a�7d�7e@��@��x���7t`��P����7r��8g 8u��@�H8iT8rd8up8z��D�=@8ol�,�����=\8a���T�UH�n�8a�8e�8i�8o9u����
h����8tT����.@n�8s�r^�j�8p0�������8sp�A�DRD���8t����9n��pp�49e8� ����,9g���|9i���
@9d�9e�9l�9m�9n�9o�9r0:s@:tp:u��Eh�R�,���9c����9i�A��p�9i�����9bP���9t��9a$�$�J�����!.:a:d :e(:i���
 <�?��?(�H��T���8:ah&hT:op�A������\:d|�jd:n��A$�����:t�	���:i�:o�����:h����:t0�:o)U�'=�:r�.��:l;o;r�78A:,�{|��;r�{p;e�@��(;f�=��	4;f�;g�;l�;m�;n<r(<s`<t�;��{��z��l;r�xt;e�@���;��@���;�t�AA���;a�B!�A���;i�C}lC���;iDR�;t��R�DH�;iF,�E��<ex��H��<e�G�<iD<sP<t�*RPH�<<etH?�H��H��X<a|<i�<oPI�Ht<o|I��JHPTU��,.83s=D=b\=c|=d�=g�=h�=l�=nl>p�>r�>s�>t(?v���<a4?c�?e�@hxBi|Dk�Dl4En<EoGq8Gr�Hs�HtHIuKyKz�7,���0=n���8=iT=l ���xh�N�@��h=n�p=e�_�R��� ���=t� ���=a�=l�=o(�A|!H�=a�=i��$�!�=n�!�@$��#�=d�4e >iD>tP>y%� %��>c4>s<>z�%�,�&H�y��U�'��'��X>r�'��`>e�,,��x>m)4�>o$,�-���>r�-���>e�>i�,���>t�>yH65\,�>g(.)/�.���>h?ih7/\/��?v�J�1�?l�1��?a�<EP?hd?i�?o$AK�@�H?a�B6xB=\?a|�;��pp?aF��x?p<E=�?m�?n�?uF7�HE|�j�?t��PW��
�?.�?d@i$@l<@nt@p�@r�@s�@t�@w�\��T/.@e�N?]��@ndaJ�b���!.4@l$eF�g�T@c\@ed@il@t�gL0hL�h�`iFk��
��lH|@m�l���@a@pRLp���@a�@s�@tsi�@b�r)�@is�0s �sU�@a�s�XwU(���.$Aa`AeBidBolBt�X���AbLAn%^P���4AcXAs�<Ai�%f��j�Aa�Ad�Al�Am�An�Ar�'�x����Ap��H�l����AoTfqX����Ai0h�d��Ae�����.�As�ox �� BeDBi�����AnLBoTBt\Bz�?}x��B.8Bs������0BsX��@�@ДL�t�J�����i��=�Ba�Bc�BeCfXCihCl�Cm�Cn�Co�Cp@DsTDttDzLC�x�2�Bb�Br��)��� \��x��Brl��t����B.��=�Bc����Ci������� C.���(Cc���4C�0���@C�(��H�A`���`CaxCiX�F4������Ca�Ce�Cg�Co���Ct$��|�,x���Cmȥ��!.4����7���Cn`�%(���CeDhDi��,�����EDc�������(DaLDi���0Dt���@�|hDi|7�D�=`Dz��T���Di��EH���DaEeEi,Ey����Da�De����Dr *�(*���Do*A�Dih)���Dt�*���RT����DmD������Ec$Em|�U��}p� �=
�Ea�Ee�Eg�Ei�ElFmFnTFp�Fr�Fs�Fv�Fw�Fz���AxEgh�jH���Er�Ad�U�En4��,����Ec�� ����Ei�Eo��?�Er��,�����Er�����EeP���Fa4Fe<FgDFt����E�� ��,����LFapFi|FlX,@���hFcX�`������Fb�Fo�E����Fn�!R(����Fe4���Fe$�U�O ����Fa�Y�hY���FeGi�YW�H�- ��GtL	��Gs\Gt�	��(Ga�Ge�Gi`Ho�u�����TG.tGi�/���lGcY!(���Gt�
���Ga�Gd�Gt�Gv+�s�����GaPz�j�Gf,Hn4HsDHtH ������G�H�$H�0����G��������p^$��<HiX��|FlpHo���PHpxHsX� ����Fe0l�.�Ha�HeIi4Iu8'w0/|.=�Hb�Hn�$�h0�Hg�Ht�0��1��Hr 3��5��5���Huh5���Hc Im�Eh6�Ii =R<<=,Ir�?K�=��@Id|If�Ii�Il(JmLJn\Jp�Jr�Js�Jt�I��@A�x��@���I��I��I��@���I��|�0At�It|=��A���Iy�Be�A���IiJt��_C=�IsCA�IiJuDC`C�lC�� Ja<JeDJi4���CAD�@E,E��TJilJyTU@	�E=tJb�E��|Ja�Ji�PF���Ja�G:�Js�*�PH��Ji�H��Ji�Jr68�H�Je�Jvh7":�TU=�YhY��Ke�,.�Kb\=c�KfLgLm Ln4LrXLtpLv�Ly�K��� Ka�Lb�Lc�Ld�Le4PfdPg�Ph�Pip j�Rk�RlSmSnSo�Tp�Tr�Us�Ut�Uu�<v�Vw�VyPP��)\�h.p���K��K�L�����K��5�<�� "n�$C�#Lg�*K)4,LdHLkPLy�K��.�?ihLo$0��1|Le2 3��3H�< XK	PW���?.�LaTMb�Mc�Md�Me�MiNl$Nm�Nn OpLOq\Or�Os�OtPv,Py\HW���Lf$M�h_p��M�0M�8M����M�����0ZK8Z��@Mt�YHMilMo�9�L9��dMn<7TZ=xMndZ���Ma�Mi�MohC�Z��Ml\��[=�Mm�\|�]��T/.daj�"f#�d.Dd�MeNq�b���MiNo�PLe�eA�!.DNipNoHfq,.Tf��8NcTNl���L�Ug\Ns�f=dNn�Nr��H�g��Na�No�NtOu����g=�Nri�������Nf�N�`iH�Ni�������N�O�O�����N�$��,���i,kH4Oa<OiDOu�j,U�l��l[�n[�l��TOhtOm�On�Os@o`0&fPo��|Ozdo���Oi�o0Lpj�?.�Oc�Oo�Ot�Ou�p�tr�Ps!0s���Oi�Or|sm�sA�sHPo�t�TvH Piw)�v=Pl�w4xp�x���I��I�\P��x��<P��s�����;a�Pe(i�Py�yԇ��|Pt���(�~����,.�PaQc,Qd<QeHQf�Qg�Ql�Qn�QoRpRrHRspRt|RvlQ�x��Hb�����=�Pm�����PaQe$QtX���p����x�4Qn��=�����PQ�xQ��Q�0���XQ������H�,���Qe���hLoH�=�Qt`����Qa��=�Qa�Qe�Qi����?�x���Q.X�tDz�:RgP� (��|Fl��t,Re<RtH�=|(<��4Ri��XRi`Rt`�?���D�|@���hRiܲ�T���H����Ra�Re�Ro�RuSy�LT����R.�Rd�Rs�\L�q�j�R.�RsT��p������|��Hp���=�.lSdxSe�Sf�Sg�SlTm4TnTTohTptTr|Ts�Tu�Tv�Tx�S�L����dSeh����R�x������S��S��S������S������H�A��A����Sa�Si�So��7�������Sr,`���Tz����Ti$��P�� Tt�(TaDTi��U@�E$�jLTd$����`Tp��A(���H�|�j�Tt4�RXS���H�	=�TaUe<Ui\Uo�Uu�Uy`v@��Tn����ToL	���TgUix
��
U Ua,UnT�(��Ur?4���4UbLUlUXR���TUplUw������tUiE��|UlP���Up�0��Up�Uw�Uy<)R.t(.4��.���Uh�==VaVcpVe|Vg�Vl�Vm�Vn�Vp�=�H?,Va<VePVt@?��@�?��4Vr�8�HHV.`Vs�;��b�@��hVlARtBy�A���VeH5A�Cp�VelC���VbDRE�VeEAPHTU�Vn�VsLW��XU�X���VeWp<) ����Wb�Wc�Wd�WgXlPXmdXnxXr�XsYt�Yv��Wa�YbdZc�\d�]e�^f�`gDahdailbj�bk�bl�em�gn�iokp�lq�lrLps�st�uuTvvXww�wx�wy�^���T)t���Wi��L��WeA�3r����We� RXe0Xo��!�Xr��u�!��(Xu�",�"��<Xr "��DXe@$��#\Xdh)E)4pXa�Xc�Xe�Xi�Xk�Xt�*R,+�*���Xs\A�+H�Xc�XlA� <�Xe`E�-�,���XpYsYt�-��-�.<YePYhdYi�Yu�.�.��4Yn�4E/HYi�/�\/��\Yf�Y�/��/��tY��Y��Y��/��|Y��/�/�0��1�Ye�Yi�Yo2,2���YnP2 |2 �3Ze8ZiDZr�5�T/. ZsH5��Zl(Zn|e$6(8�7��0Zt�:,|=��<=LZd<n�<��	TZa�Zc�Ze�Zi�[l�[oh\r�\t�\u�<;4?E�Za�?=�Zs�*�@)�Za�@���ZsxB��Zb$[f�[m�[tp[��H~��}=�Zt�B���ZaC=[c|[eC��[i�[y\~"~=4[t(C��<[a4C�H[c�[e@C��T[�LC��d[�؟+�1K��C�TD8�[e�8���D���[m�D��[a�[u��=�����[s�E�<E=�[l\m<\nH\r���F��\m(\p��8��p \ep��F4\c�F�X\a`\o����F8G=|\e�e��G��t\m�H��H=�\n�H�\a�\e�HAHI=�\l�I�\a�A� K$XK���\a]d]e(]ih]o�]r�]u�LC\O�L��]r ]s�Oz�PlaH]b|cP]mX]t`]z�,4�,pR=� Sx]l�]n�S4Tn<U�T=�]i�V�]o�U=�]lCHdZPW���]c^d^fT^ld^mt^n�^p�^s�^t�^x0^�(]��\���]i�^�^��^��^�<^�D^��^��^��^�_�DdE�b��L^i�2y�e�gA�g��l^akMLp7�^t0sU�uA�s���^y�w4xH0_e<_fP_i4`o``u�x��x���^��^�_��x���^��|�h_c�_l�_n`t�P�mU�{�_e�z��$_r�{[�}t_i�|=D_c�_l�_n`t�}�|_i�~`�~�U�U�g����_e�_i�q����_e�_i��m����_eX���_t��Ȧ��_et����_t@�L��ql�����`sl�`e�=(`r�tH��@`.<��H`e��,T`s���t�=l`l����t`a�`e�`i�`nao,au<ay��Uԇ���`r������`b�`c�`n�`t܉Ap��T����`g@�{l� �؋=a.as���A=H���$ald'r���(�	Xae��U����Par��j�ac�ad�ag�am�an<brPbt��p�����alĠ����4����abP�u�����afbg(bs�a�Ѝ�X����a�b�b�l����a�x����ȥ�t�?t�� bt(�R��A4bd�C@���Hbedbh�Iy4��x�H�bu ��bi,���xbd�?��R��H�bnT����bi�bl�A(�H�ba�=�. cc0cnPct\cwlcxH���
�ba�cedfDdi$elLeo|es�et�eu(d��D��P�(cd?"?��<cv���Dci�2<S	�2��dcaL��T���xca�cb�cc�cd�cg�cn�cr�csL	?DZ,�ca���crdZ?�\t`������ca�����Pн4�6��6��d�4d�<d��6��d��6��6���	xdb�dc�de�dg�dm�dn�do�dsev��$���pde��D����d.�da���\�Lx��dr���t��db���di|��ȥU�����dg��0��eh(�����(��4eb�Hea<eo�������p���Dec`eghepl�D����.�$4���teh0�A��A�ea�?������ed�egA��d�=�ecfgfn����	�ea fb(feTfi�fn�fo,gpDgudgyX����fa�#���4�=8fl@ft����������da���Hfc`!e�fg�fn�fs�fz�?�=tfal�|fr��x�fe�fi���X�8��eh�fs4��,,����0&z��p�fi��R�=�fggn�h�L���go�gi��,<�p$gi��Tga��=8gl\gn��� ��TU,L#5t���lgo8�=tgm�gnp����ga�gchd0he�hiinio4is`it�iu�iy�iz�&���4�gt�AA0�j�gr���ge�����ghQ����hc����hi�������(ha�$e�Cm\hrphs�ht�hw�o���=Tho�q����hhi�ht���t�@����hr`�y�H������hs�����hc�he�hl�ho�%s&t�u�hz`2i`���6�?���D�ig$is,iv�����H�,����Uw����@ie�s=Hig��HTia�ih��T4pis4��xie����@'a�if�i�� ��,����i��i�#�8����i��|������.���������ifdjgljitjl�jp�jr�js�jt�ju�jwHj�x�����,j�Tj�\j�����4j�����H�d���,��!���|jr�����ja��=�je�jo<�,������jl(�UT�AT:o�H�|�j�jt�X��4kiDkn����	�jaXke�kh�kl�kolrLlsllt�lu4�����P�<kc��t���Pkltkn�kth�FD���lkt�t��t=�ko�t���kip���ktD�kiU����ke�X �ki=��la���kc ld(lh�j�ke0loTZ�Da
�<lb�����Dlh�5���Xlb�`li���la,��xlt�H��H�lu`AE0A��ll�ls�j�li�L	H$mb4mn@mr\mt�	��
�lahmb�mc�me�nh�ni@omdon�oo�os�otpu4pw@	@$t�
,mdP!T/���Hm.���Pmi`tml�8�8
,�me
H|mhH
��?��
��
�m.�ma�mcninl$nm8nnlnq|ns�nt "(���ml�����mo�a,�j�mn���,.�f��e��no�g���0naXncdne+t�?�g��Pne0h8�lR�5p��tns�nt���U�	�H�na�nc�neonoo�:t$ou,ov�(|D-\���nk�L��nnor�A�y���oe��Բp�8oaXJ�\�`_H��Hostot0&z���Poi|ooh4x����ob�oc�or�ou��5hR|�xD��oe�s����ot 3,`���or<���oepl�6w�7=Ppt$R$1��O�� pu�VH(pa;�pg0��
@pa�pc�pedqh�qiHrk\rmtro�rp�rs0st�su�sw�?5���p.�ps����pë8� �pa�pr�pu� |�pn� �8G����!�qcqn8qr\"�qrh\�t#�P#��qc�o��HV.Hqs�#��(qtXqv<�XJ� $��Pqa�$�tqa�qe��d���$��|qn�%=�qc�qd�qg�qmrn rs,ru�%��%)�qe�N��%���qn&? &���qa,&��qn4�RP&F��A����re�&�rt���&?�&H4rn�&j<ri�'AD'TriL(��ru�'=hrl�rn,���(K�ra�(P<)=�re�ri�rr�)Ux)���rr����A�ra�)���rr�j�re\*)si�%��*=sb�+7�+=snh+��$saPsihso|srH6�\,Hsg`smh6��,=tsn-[$-L�so�su`;?H?K�;���sc.K�sr�-=�srpa.z|.=�Hb�.��
�sa teLthXti�tn�to�tr�uu�uy�uz�2f�2��td�1��tn0to3��4��4��8td4��@toh5���/c|td�tn�tr�tt؜?�5�tte�6lD#o����qD�=�to8*v\7���ti`�x09}@9�ta8=�tn�9�:���taueuituo�:�$;��:��uc,ufPu������4u�\u�du�0���<u������H��`;��lug�us�;�t<[<<=�uaW|==�um�=�=A�un�up�ur�usvtDE,G,�E���uo�G�HU�H��ve(vi4vr(I �H vl:8KHXJ=<vppvs�J��Dva�ve�vi8woDwu�,�|vt�-�W�xK���va�vl�vn�vr�K��K���vl�vo�Kt�?�K���vg�vi�hRL=�vbhm�@M=wdwl wn(wv�M,N0Np6P��O��0wc�=�O=pwgP��Lwa�we�wh�wipP|Q�<Q��xwe�Q,�QK�Qj�wl�wn�wt�y�Q���wg(R��T�XS���wp�U�TU���wc�we83sV���!.=	yb<yc�0gXyixyl�ym�yr�ytTzu��xa`zbpzd�ze�{f�|h�|i�l�m�n�o܄p�rd�st�t��uKy�xahzbxzd�ze�|h��m�n$�o�p�rl�s��t��u�|y���x��|���@|��|����x���	yb<ycHygXyixyl�ym�yrztTzu����yl,yrL��A���4ye���$ �jPynp!�|!Hdye� ��lyl"� "���ya�yi"� "���ya�yi8��"���ys)��yt)4�yt4<���yh�-,.���ya,zhhLo.���ya,zh8zo(/�/$ze$0=C�h1z@zt$1��Hzl�3�3 XKAXK��PW��
�.�za{b({c`{dx{l�{m�{n�{r�{v���PW���z.�za{b@{ch{dx{l�{m�{n�{r|s�{v�XUW���zs{tPYY{h�YTZ�dZ�� {aP{tTZ=dZ��8{aX{t�\��\�\��\��Dd!�b��p{i�fA�e���{oh�{e�g���{d��(�l��{r�l��{rp�p	TvU4xp|e$|i�|l�|yLp�z��|sLp��x�8�|=|eT|nl|sPW����8|e`|nt|s����$.p����z.t�R0��̃��z||yTU�����|yK�(�A(�����=T}a�}c4d�g�l��nt�s�\}a�b�}c@d��e��f�g �h0�i8�j@�k�lT�m��n �oD�p�q�r��s��tԲu�v��wijx�|y�zP��x�H�l�mT�n��p��s��t�U����|}.�}ap~e�~i�~su���<���}.~aD�c�~et�h�~ițlܛo��r�~s�tu�=���=�}l0~nH~t�=��<=~l�m8~n��p�r,�s\~t4�y�=[�=�.��>��@~e�.��>��T~e<@yX���h~n�~r<@��?���~nd�o�~r�@�|�8�~a�~exB=�~a��d�~e��n��p�B��B��B��B��H��HH�,HI,<�l�m`�oh�rN�؜�� lp��(eXK����a��dhe�iH�l\�o�*rl�u�L��̜a l ]s\�Kh���|t���h����0�aH�e�h��iĠl̠o�r�u��KT�i\����t`���xiT�lp�y��Hx�a��b��o��s��t��zH���H�a̡e�f�i`�lԢn�o�t�u�vx�y4��|��=nȢHH�i�H��aH�i����������a��d�e(�gP�np���z.�a4�c�d�e0�gt�i�k$�lX�nH�o��s��ţu�y���8�^�u�v@�����l�lx�)����+e��r��sȥ�t�E�a��e�i,�l@�oP�u,������U����`�i���h�t\,�h+����a��e��i��l��r0���z.0�a`�c��e��f(�h`�i��k��l�m�o�pD�s��t�u��H�z��e�id�õyj��a��b��c�d��eܽf$�g �h�i��j��k�l�mh�n��o(�p��r��s�t�u��v�Vwԃy���T�+�jl�sT���t�sLpj8�cL�qԂsPW��L�a��b��f��g��m0�n��pؼr��sh�v��y�r)�z.��U�����n��U�a�e�i��k����x�a$�bx�c��dX�f�gX�kh�l|�m�n�o �q0�sL�t��v��z�������jep���X�r������a�je��i��o����z.��b��c��f��g��m�n,�ol�pp�r��s��t��u�v4����TU���a�b$�m8�nP�s��A����p�Ap����=8�nl�rԄs���H�nl�rԄs��X�dd�t�X�dd�t���e��R����a��e��iȄt����=��t��y3� <�����t��R,(�����a(�K���� �RL	���t�	���a �eD�iX�o(��
���a8�s�p p��0�c�jLUl�K���P�l00E�.�;o1y�.��;o1y�=,pVe܅g�m�n(�r<�sT�t�=��pVe�g�m�n(�r<�sh�tAA���C�C=�nlC���i(DD���ePF,�E�� �iHU�G�4�iL�sPH�H��X<a�H=�H��`�a=	�f��l�mP�n��r��s��t��z$M���t�aćḃdԇe�g\�h��i̊lL�ml�n؋o��p��r�s0�tH�u��w��y\7� ���!.�i�oD!F�!, "�4�elgo����"��,�t %��4>s`�z�#@�it�o��l�aسP&%��)4|�n�-�,����s/.����h?iL3�3EXKUPW��
�?.`{d�e4�lT�n��o��r̈s�t��vhYU�]���z�dADd�nD�sL�z�b�� �ip�y�d�����g�t�a��i��o��y$���g=l�t�hf�h����zi��i��i���m��,�l���Uy�q�Lp��ĈiLtK�s��؈h�o�^y�t�TvR����e4�l@�oԇ�$�r�����K̊j,�u؋} �,����H�n(���P�i|�o��tЖj�jtt���p�u����;o����
,.Љa܉c�e$�l@�mT�n��o��r��s��u��vĊzx��Br��=�i��o|�|�~aܛ�Kx��n�sp���TG.`�U��y����,�n4���4�e�?�����L�.p�g|�s��ȥ��h�et�#�5�����lR��)�����lԲRܲ5��H�j�a�e �i8�o�U��d�sL��i��?T��$�R�����b0�g��p��D�r��E��HX�yTUA8���.p���`�a��e��iċo<1@�����t������t��=��n��o��|�.D�=Ћnd���=
�.�bxSe�g �i,�nd�ox�r��s��v��KH�3d�,d�j�s�n@�aP�d\�iP�8��>���H�o��,$������tDz����l�i��oh�������u(����!.4����E�	=�a��e�:i��o��u K��ԌaL	��܌d�i�n4�pp�yx
B�
n�G<���.H�e\�ih�y$���h�'O�'��@�r8W(�T�c4`3�R�
��x�n��s���)��.p����s�A�@UP����f��x�@��Ѝ�������@��؍��|��J0j�t�+�h+���e4��.��(�h�=Rp�r�=��<�a|�e��iLJn��s��t�>h�>4h�d�@��Ao0A���t�G��H�PETU=��nЎrLWuHXTX��Ȏaz���܎l���b\=c��e��g��lЏm�n��p��r<�sP�u\�z���a�Lb��eL�ft�h��i�k�l0�mP�nt�o܄p�rd�s��t��u�wX�yh���eR<��|�m��t�sR�������u� ,ȏm� ����a8�E "R\�A����؏i�
y�#�c�d�gP�i\�kl�t@$TG.�$4�e@�o������,�rD�(�f %��H�z$&1��A�&Hd�ep(��'��x�l��t��*�h)����n̐s)4��aԐd�l�p(�t�5�*�e�LE$�H�e<����n\,�e`���r<���e�-��,��4�sx1U$1��H�nL3�h�aXY�W�W��p�d��rPW��x�a�c�d�lX�md�n��o��p��r�s$�tD�uxX�<�TZ=ȑn�tdZ��Бa�>?�\A�oh]�Dd��b���i8�lH�o0�|�$�s$eH,�i�2yLe�,g�e��P�p�gp�a�g}�9t�j(�i��|�rkK�l�*p�l����a̒b�e�n��o�ytAhmĒa�m �m��ؒado��o�o��u��Lp=�p�r��sR8�e2d t��0�d�u�4xH�x��Ѝ����_��x��T��(�H@��x�|�n������aГc�g�l�m �n@�oL�pT�r��sДtܔvܛA����ȓoh�K��ܓh`�tp�U������r4����e�������a��R��,�e��4�n(���A��ll�ot�p|�r��,�Rp��",t���l�����e��s4�.��T4��r4����e@���Ĕhܲ4T�AH���a�oP�7�����np��$�r��,�����i��pD�e��U4���<�tp������hz����X�i��s���
d�d��gԕl�m�n,�oX�r��sЖu��v�U�H�R��e��}�����=��r���ȕa�ex�fx�A�����a�e����Fa�y��@�L$�j$�d<�n�7���=D�t����L�ax�i��t��u�5����p�s����Xe��!R��n(�����e��pP#��H�G�ܖe|�jĖsH��K$����l4����e�	��e@�o�]K�
���e��tDz�(�i���4�nX�pX�,X��P�o0��-h�0�|.=p�r�.��x�a��e�y�2=�1����n��s�3�=��|Vg�m�n�s�t��|�tԗeD��ܗk�8ttH��G���t�HRPH4�a�P)�P4 �t�O��(�r�W,�W��@�eh�hTU��H�pp�s�,�X�l�m@�n��p��s��t��x�a�b��cp�dx�e��f�g �h(�i8�j�k`�l4�m��n�o(�p�q��r�s@�tԲuܲv�<wijxX�y�z0�� � "U4�e������� �e�"��(�t�#4h�c�4ix�t�#4p�c�4iXt��������&)�'�'����eh7�\/����v.����iЙrܙu$;��:����cl0=ęi�0�H5U�r�3���e(�iT�lh�op�r��u�l���.a �t�o�7���a0=n<�t(8,.L�e���8 `�i�8,L9�:=|�i�:R�;�@;����n "��<=��m�p�r,�s4�y�<��
��aD�cX�el�h|�ițlܛo�r�tH�u "��l>?l>���>�T/.$�a�>4T/.$�ah)��>K3HIU4?E<�u�i��?��P�o�i���@��@�xB|��d��n��pp�5�C��C���a�C)��aX��,E��D���yP�R<E=ԛc8G�a�Uy8G=�a�Uy(G?(G���H�\e(�u4Ix4�at<��I���\aHI��<�l�m`�oh�r�D�J�XK���a��d؜e�iH�l\�o�*rl�u�� K���i��n L<kc�L�X��L��Ĝl�L��̜a ]s�P��a$�e,�oX]t8�u@���P��n�r)U<Qi�Q� 8uԲW�R��R��@�eTS=T�m�w�U�`�oPWȝdܝg�l�n\�rp�s��tPWUȝd�g�l�n\�rp�s��t]R�\����et`��`��ԝat`�`���aغ��b����d�g��g���a0�e8�nH�t0hRi���[`iH@�i���l��T�.�p�Lp��h�c��t0s��s,4x���.Оe�f��i�l��t4x���z.Оe�t�{�Tho�z��Ğr�{|��ܞn�{p�e\�r�z��ܞn�x�ed�r�x����������x�����x����ȟ����x��<���A���|}��}��l�.�|=t�c؟e�}��}����.�|���c�e�}��}����.�|���c�ex��������Jt��t�����0�aH�eh�ht�iĠl̠o�r�ut�=Hb�����.aԇ��<�rh5E����T�i\���\�t����b��l��n��t������b��l��n��t��$��T�,@�,̊1؋�ܠr�tx�,T������=�e��H����i�r�E=(�E������x�ET����=��b��d��m��rH���H�a̡e�fX�iȢl�tn�o�t�u�v����������e8�R��4L"a���T���ġg�r�v��=h�Uн��6��6�����@��P���6�����6�����H��P���6�� ���6��6��6���Hx�a��b��o��s��t��zx�[$���,��0�����t<��<�=���(���� b�H��a`��G[p���ܢq��A�y�E������r��EX�t�ed�=�g��r����$�a��e�ip�n��o��u����$�ạe�ip�n��o��u��0�4|�y�0�Ti=��r<�F��a�����t4�����nܣt4�����n�t��������H$�d8�lL�nX�t�H$�d8�lL�n`�tL�HT���a̡����0�eX�����D�i���������A��ph�i���=|�n�����l���������ap����?.��a4�c@�dx�eȥgX�i�k�l,�n4�ot�s��ţu�yt��8�=�u�v�������$@U����l�~r����$�e��l�lȥ�����L�g����T�n�R��`�i���+e��r��s�����H��r��=��a�r+�����sh��t�����a�e�i,�l8�oH�uh�=����n�����n�������� ���T�n0�j �iD��D�=d��d�=���,.��a�%o��s��t����,.��a�%o��sȦt��	�=������.4����e�i�y4����e��i�yT�	\�=ܦoT�
	\�=�o|=�|�	|�t��	��p��	D�|\�cd�sl�tD�=\�cd�sl�t�����R������e�-u�����e�-u�,����H��.ħh��H�z.�0h4	��Hاs�G5��������
�.P�gh�lp�m��nȨpܨr�s��t�u���
�z.P�gh�lp�m��nȨpܨr�s��t�uH�����e`�r���=��R$�,P��x�t���a��e��i��=�Uy������r��E��R������h��E����Ԩi(�A�T����h�iT:o���E|�j�r���`�e��h��i��rĩu����`�e��h��i��rĩut�U�L	��h�s��p�a���|�rE��cXA��j��el,,����l	��jЩa �e��i���ةu�jЩa0�e��i�����u�^�	���f\��	���f|���^$	�^��@���������^��H���^��@���������^��h���^��^+	_6	0A�,Qd��t�Au�	A�a(�dH�e�g�i��m��oܬu�	���a(�dd�e�g$�i��m��oܬuL	=$mb�c
�L �
�� �eA)4�e�
��<�d��f�l�s����
��<�d��f�l�sī�H��^=	������Ы���������������ث����������W�D	_N	�_pd j��i�HD�dP�s`�tl�z�HD�dP�s`�tt�z���<�e�A|,$��X�u�U	�5����|�n\H��iH�R�����gĬn�����gЬn�TG.��z.P���l0���?.0�aT�ct�e��f(�h`�i��k��lЮm�o�p4�s��t�u���jxEgD�rL�s)!�,K� l�h� Hl�h0!u�!��r�!����r�#,�$��$�I[	�$����������$������I��$��̭�������$��ԭ��$b	�$i	�$r	�$������n�$�a@�oh%y	@%U8�nP�p���%[�%��X�bx�d��s��t�%�&�\�=8*v�&����i�&�&z'����n�&~��a��~	D'Ȯs���D'ܮs�'��m,�������r�(��e<)H,�iX�y�)�\*�	\�ap�e\*\�ap�e��*xT�l�*U�*��h�n��s�*�	�+=a.h+����a��e��i��lدr�+=�,��2y�
�@-�įl$-R̯a�
��@-��l$-R�a�-��s�G�U|.=�.Ȱb�0g԰m�n�t�.��	 �a�e4�hD�i(�ol�r|�t��u��z��|.=l�.Ȱb�0gܰm�n�t�.��	t�a�e<�hh�i8�ol�r��t��uȲz�A0/���i\0�	\0 h0�1��1��r,�s�1���r,�s 3���.a$�iD3��3A4�4��h5=��a��c̱gܱl�m�o�sh5=��a��c̱gܱl�m��o�sX5�	X5��5�	�da��k�5��daık�5�	�5H6�T��\6��Աlh6��6�	�6:�6��m�6���m7�	9p8= �mH�n8= �mP�n@9%@9�
��9�X�m:��`�a|�y<�	<l�>�t<H��t<<=��a��d\'l�<��=AT/.�=A�z.�=H�J��e`�i��o�J���e`�i��o�K���vlxK���l$�n8�r�K���.�UL��0�.H�s�L�HV.N�,.@M��T�lt�o|�tpN�N=�O�� :e��oO����r��t��O�P �TRXS����o�Y�	XY�гrhY��سa �i<�o�Y�XY���rhY���a �i\�o�Y����	�(�tZ=0�n��H�H�tZ=P�n��c��p��h�a��e�o,�u���tq�'RPW=��r�s��w�l���Os�J�	PsĴe0s��̴i�yLp��شt1�	Xw���R����pdP?�?���g�=�� �d���.��bxEg��i��l��8�a�LbȵeP�f��h��ix8k(�lSmh�n��o̷rطs�t�Vwl���,�U�j��s� U�\�PW����d�e��g�l�n(�r0�s�^t�]��`RDd��b���ihy�g���d�l=LpUD�t�?�	0s��<�.4xE�x��Ѝ�x�����x��X���|�(�U��H,.��c��lԶm�nL�p�s��5Ȣ�`�����l̶o�K4�A�?����ܶ.��d�e�g@�ex��8BsȥR�UehH�H@�eL�y��UT���8�y���������T�sp���\�ex�oD�:�� �����r��s��u�$7(�����h|�u� �����n�	����o0��cd-l�Uy� A�. ����"c�����i�o��
�b�c�d$�g8�mP�n��r��s�t\�v���a|�b��cغdT�eнf�gh�h��i��j��k�l��m`�np�o�p��r��s��t��u��v��w��y�6���
�b�c�d$�g8�mp�n��r��s�th�v@8����i@���e�y�V!8R����nL# "��0�o@$���l�#D�d��el�t@$��l�#d�d��el�t�$A@��$����t z)4��g��i�+E�,���Feh0��-�̹n.��Թa�e?i.��Թa<�eP�ih2�	T2���i�.���lh27T2��(�i�.��0�lh7:\/��H�v�1t�a�1��t�a�1�3��i�3H��i�7U7����n�<�Ⱥeкi�<~Ⱥeкi�?UxB,XK��e,�i@�rXK���e,�i@�r�L��r�m\O���e$�i�n��PU8�sHR�T,�]iWPW��L�a��b��f��g��m�n��p��r�sh�v��y8ZA�Y��it��^H��t�`���!.лg�`���z.ػg��	�����i�e=�t�e���a�/������c�g��T/.T@cX�ex�t�g���z.H�cl�e��t�g���?�0h��P�.�?�0h��d�.`i2`iH�k,k����h��rlt$m�lH��b�l����a�e��g�i�o�l����a�e�g$�i�o�mR �	 j�n�	�oLpj8�cD�qT�s�!��p0�oP*?P*���rgTG.Dv�Tv��`�a��e�v=�.��a��s�v��x�r�l��L��w���e�w��Ƚe�w��w��4x��r4x���r�x��Ѝ�������x��������8�aT�e`�o��	8�aT�e`�ot�D�r���	̈ԇ��L�s؋�(��Ax�p�g�m�r�s�t����x�a$�bD�c�dD�f�gP�k`�l|�m��n�o�q0�s<�t��v��z�����+HTz)4�i��A$0�����o(�����i�C�	|�|0�o����8�i��o�~s��t̿uԿy�C�|�|d�o����l�i��o��s��t̿uԿy�Fܛ=��r�Hl�HV.��z.H�K����	p��ܿa�e �ip��ܿa�e(�i\O؜���r����Ğ
��0�r����8�ed�f��l����8�e�	�����l�����0���t�����l�����0�������1���
�.������e0�=�t���a,�hD�r�.��������e0�=�t���a4�hD�rh�Lh�����=<�a���A`�
`���ptml4���p�bIi��o��p��A<�
�����a��e�i��kx���ax��a��,X�C�������r�	�eP����g���A�R���(�p@�=�?.t�i@�=�?.��i�d
���	\�a��sD�=d�c�d����	��a��sD�=��c�H
8�,�����rܲ���e�=x�Ax���8���l�tT�����a��q.RH�H(�a4�e|�i��o���\cwT��T�a\�cġgd�ll�nt�tL��dZ��b��y�sy����n��#��a�"
p�R��q��u��w0A�j��iGH��u�����j��t������e4�iH�o�����e4�iH�o4���,�t������� �g���(�n��(
�=@�dX�n�7p��p�~����.��b��c��fl�g��m��n,�oD�p��r��s��t��u��v��D4������l������a��AP�����i�������xt�������@��P������������Ѝ�H���6����� ����5����-
܉i����X�cH���`�i��o��u܉=������cH�����i��o��u؋�$�L$���,�,�����r������e��?���g�i�������g�i��R�z��@�K$�j$�d������.����8�e��i��m�����X�.����`�e��i��m@�E9���}��t������a��i��o���hLo�i������e(����!.�eD�o��t(����z.�e��o��t�s�!���t�(4
�(���z�(��i��y�(A(�h��8�p�(>
�(��P�z�(�X�i��y�(Ad�h��t�p�(E
�(���RH�AT�����a� ������d|�j��n��t|�j��n��t�H��H��4�4������D�aX�h��i|Fl��r����D�aX�h��i|Fl��rX�=Hb�����P�ah�i@�ȥ��p�g��x�n��t0����	p�	H0��c��e��i0~�c��e��i�!��%��|e�.A0�aT�e��h��i��r��u�.��0�aT�e��h��i��r��u|.=xEgH�n�$Kh0@�e�1=d�nt�r�2U�l	 3��l�a4��HYi6�sh5����e��s�6U:=<<x��r�EN
 =����a�= �=����aP�bd�c��e��f��i��mT�oh�p��s��t���=����aP�bd�c��e��f��i��mT�op�p��s��t����>��DZr�@H?��\�ht�ih?,t@,�@��|�n�@��Ѝ��������@������R
p�0A���d`CAlC����a��i�nlC����a�i�n�C��C=��p�!.4�i��p�!.H�i��X
����,�a�������@�a�DE`�r�DEEE��PH�G�x�s��thH,tH����e�H��K�xK����n��t�J����eMPTU=��a�b$�m8�nD�s@U@U���L��LH8�W���eD�,LW��0�o�X#\�e�XU\�e�X`
=��b��cX�gp�h��i��l��n�p0�rt�s��t��d�a��b,�c4�e4Pf�h�i�k��l��m��n�o<�p��r��s��t��u0�wPP����������a0�h<�l B��A��eB���n�$�i�DT�(HD�n���L�ih�n8���p��jx�d� =� ����d��i��l�tD!H0�g��n����!U|!H��i��e
 %����a4>s<>z�#��i�'�P�.�+���e�+H�nD�z)4 �iL�lX�v,5H�2yLE!�� ��`�e�,��h�c�Fe��t�-H�.?.����e��h��i�6�\/����s��z�7l
|7���a�3p��a�i�4t
p3����t�7�7����l�n$�v�7���dgܲ�<PW���.h�d��g��l��m��nP�op�r|�s��t��v�\�T/.��i��yly
(]|�a��e<Q��V�
�`���Le����n�b����o��t�e�e��o�f�
�g��ad �e�vi(�s<�t�g��c��0h�4i�4�u�-;`iFd�e���i��H�nl�o�\�a�l��d�sLp���tPsh0s����i�sA��l�s����a��e��h��r�/! t=�4�Lt����i�t��i�yu�
u����c�e��
(������
,.T}aT�dl�g��l��n,�o8�s��t,z���p��L�ad�gdPR�x��
X�Ht�a��e`���|�i��lx��
���������a��d��e��g�t�u@��+�x����e,��
�i�yȥ����l ����^��Ŗ���U��$�t��`�el�lt�t�#���,L�.t�T�r�������`�i��rPL�
دR��y4��	@�����hH�p��H��ad�=��rp�p��a��i��o8����������nD�R�=
L�c��d��g��i��k��l��m�n��r��s�t��u4�v��x�aP���@�r0��
8���X�zD���`�i���l�t���؋AH�����od�j��ed�j��st����0s?��j��tx�����s�����e��,������e������t��e8�gL�i��o�iy������0�e�������D�ah�sĊzЮ�	��=`�mx�t(���U(�����l����a,�
��=��.(�j��e��p�w��!����y�,�@�|�j��f,�s��x��@�����$��du��@�����|��G�4���p|�a��e��h��i(�o��t��y��K��b��4`�a��iX�=l�r������
D�t�����t<�U�����s�����a���a��e�n�tr�sp�����s����,���i���rX�sl�ux�v����L�e����@�t��L�i�G=jd�s4�K:A���rTU��	0��-h��i�%�.A�==��l�m �nh�p(�u����A=��r�A����a�tC�
�iC�lC�Dj,JRPA=��b��c0�g|5kH�lt�m��n��r��s$�tt�u��v��8�a��b��c��d��eP�ft�gx�h��ip j|�k��l��m��nD�oh�pGq��r��s��t��u��v��w��y��zl����
��ut���T/.�a�t�A�k����,.���r���$�eD!pX�a� ��<�i`�tT!k����"���t "��h�i�#4��c��i��k\�}������i&A %����t$&��*E)4��c��e��i��l��m�*�,��,A�c�t� R\,��-���i.�<�ah�o`.��-�4�lH0U	P0��H�z<0�P�i$0=\�m$1���s��tH,�1���e�1L2R�1����e�3��>�
�<=��r�<����a�e�h\�i��o��r��u�@��T/.�?����s$A��@��a0�eD�i��?`Aj(�o��
B��<�lT�s��LxB=8nl�tTDd�E���-a|�jt�r<E=��u8G=HI= K���i��nXK����a��e�il�u��w L��L=��s�O���$.�Oj��t�P���
b�f\�tĊz@��HQ5PQ�
XQ��$��L��T��lQ��,��xQ�
�Q�
pR�V��U=d�c|�r�ER<Q��VH��ePW���?.��a��b�c`{d8�gP�l|�m��n��o��p��q��r��s@�tL�v`�wxXpW����r�Y4�u@;EdZ�k|D��Dcit`= �t�`��,�aH�e�`?�b���ah�i��Dd`�zTf�e��t�i�{o�g� �e�i��kAk����o�l�l=��a��e��i|�r�lH��b��r@m�
�m��n�Lp���.�p�t$�w�r�0s��s�
�/iXt��,�c�s��4�iTvRX�e�v�XwRt�=�b����	h�a��e��h�i0�lD�oX�sd�u�Py4��ԇ����l��n��r0hT����e�m?������e��i�nF�\�����a������b�n �tT�=@���_̊j(�a��U؋=<�v�$��jP�hH�=p�mlC(����a��e�U��b����Ux����n��p����
��a�b �d`2e@�f��g��k��m��n�o�s4�td��@�!��Ap3,����a�lT��p�,�i������c����4�i�����c���L��0���X��H~��}=p�tt���x�a\~�~=��t������a������r��4�=��i,��H��z��=��e�gx��P�.�����$.(�t��A���� �a@��L�h\�ip�oT8r4��
	D�=T�ol9L(�=h�rT�t��e��i��l�o(�=��oȵ����r��t�^��,��H��n(���� ��e4�U<�tp���e�i��U�,����l�����a$�v�8z��e����,�l���8�b��c��d��e��g��i��l@�md�n��p��r��s�t �uH�v\�w�8����
P�����l��h��H���e`� d�����id�j��s�������i�o��������s�����i�����gx�q`���(�cT�z����0�i\�odgy,U	��A�R|�a��i0�AP��t�g�� �hz����o�������iX�����l@	R��=��b��r������aP4PLyL�%(�����c�Fe�tH�T����a|�P,�n��L�K$���4�l4���<�eXR����T�l��	<Oi��r�R��x�c�j��e�	H��uPU0�	��a�c�e �i8�l@�mt-oP�pt�t�Rj��b��td UH ����i�!��s,$�%�%���d0�g,&�&�D'x)A<)��H�e`�i�)��+=`bh+��h�a�.H�sa��e��ix�r��s��u�ol 3����s�1����rh5��b��e��f<�nL�p��5�6�r6n��6�����$��,��,6����86��J�Q�	�6|4�e�dg�����HL�i���T�l`;��`�l:��l�o�;14�,����e<<=��m�=H�=��
��a�d��e�fL�id�m��n��o��t8��t@�@����n|A�{p��e�@���f�z���x�e�@�� ���@��,��lAy0A�D�n\�t�A!lCAt�e|�i4�=�C�Dy�D,�H��;r�J~P	WUTU����m��p�WUhYA��aXY
Al�d��l��r��s��t����a��bP�c��dh�e��fH�gD�hd�i��j��k�l��m�n$�o��pGq��r(�sT�t|�u4�v�wL�y������'!�t�s� ��|�e�*�)4��d�,���Fe��t�- �-����e\/ .����i��p3����b�r�3����a$�e0�iDZrtu�4��5�H5���l7=<�n�7�p�g�?=�<��H�ex�h��i��l��o�r��t��u�@��e$�f`Aj��tC�xB=��f�Ml����@C����������LC�����4C��D��[a��<E=��d
�(G���cD�t8G���aP�e`�i|7�tG��0�z\G��8�i�G�DH��GjX�td9�l9��l�a8=t�r�H��oHI��<�l��rF?�J����e�M��L����d�L	��eXK����d��i�oL�uQ,�P����c�o�Q-S �rtT�HV2PV(�.X�sV0�t�U=@�c`V9�bAPW��`�l��n��r��t��vt���g����g�l,�sA�s����aTv,�|���t4x����i�|��t�x������x�����@���L�e �t��������e(�t|�@��1��pt�=0�rp�t����8�a��e��i��l�n�r$�u0�y����DcihLoԇ=��n��o��r0h?T����e�������������e��s��t���̊t��y��5�q��=��zl�����i�����=�oH����i��E<�n��J(�~X�a�K���P�b��j	��c��d��fl�g�l,�nd�s��t���@,X�����s������e\O�؜����rp����e��������f�_������0�������s�̡���t`����eȥ�����$�gP�t������<�r��HD�eЮk���\�m��o��t�(���x�n��=P n������e������r@�����ex� T����e��i��ȵ����n��H<e�=H�nP�sH���
��a\�dx�e��f��i��l��o�p�t,�u`�vh�y���P��������sغ4h�e���,T���p�r��s�ot8�[�j��cн�8+i�6��6������6��������a�c�d(�fh�lp�n|�o��s��t��vL��x�2X�LD����e��,.D�yl�[	t���0��X��`������8�������P`�W����$�g�50����$.�%h<�=�[e��it�=�to��5|�`!e�H��iĊ�`�����zl�����ip�����g�r��R|Fv���l��UP�,����$�bD�mP�nX�s����<JeD,��5�����p�Rd�=p�h��l��t����x�a��b��e`�i��m��o��p�� ���������z������iH5���p��etml4���n,�r8�t�\����a�o@d���ep��� �s��L�r����D�yT}a���X�ax�c��d��n�����.�daT�5��=D�i������d4�����n��H��e��A������e�=��g��n��<�p$gi�rK�j�op�P�ap�c��d��e��g��i�k(�oL�s��t�u�v8��`�ch�n��A��p��H��ihCq\�=|�l�����oS�����$n��s����htd�At�����u��=������c�ho��s�u�w�������y|�t��e�����iD����d@�mdg@���8�y���h�p��u`�|�rP���\�i�(��At�a�-}����d�n��H��e��i�����f�����H��������������������$��,�Jd�������mXJ;��~�a�j@�dX�k`�pt�r��s���� �eP�i�� ��������i�
�����l�d��K(�����tX��������a��e��h@�iX�o|�r��u��y��ft�����d��r��=��at�k��g�����a(�e�e����n$�
�� �r�$�g��4�n��t����P�np�s�����h�i�x,HTUKL	=,.�g�l$�n�	��
��a<�e��g��i,m�n�o|�p��r��s�t�u�y�����D!H�z�
����i�
�
�
��e�
�g( T�l�
��0�a\�il�s��w r�udqKp��d�h|�t��$.�U`1A j��u�x�����a�c�l�noo�t&uP�\���a��=$���y ����e�����fh�uD��D�����P��X��P��0��8��~A�|�j`�g�Ht�e��p	��h�A��nDt��e��tP#h+U�4�4���i�y<���h�yX����P����m�=�,j�l0���aL�c��id-l�o�p��t� `�et�o��r!A�!L��i�!=h�ppF�8G�x��%����e��t,ru�&����i��o�y(���'�,)d<)���a�o*A�+��th+���a(�i�0��+���i\6�\, �l8�t\7�h0�|.=@�n�.��H�a��e�h�ip�o�cR�c��p�gT2��x�e�1����l��r��s 3���.��s�o��34�e�i�=hhiT4�s�4��5���. �a�eh5���c(�f��sL��6J���6��0��X��`��,6��8��86����9�8=h�s�=j��b�c�e�l��n�v4?��>����lB �@���iH?����h�@���t�AR���4Vr�����eD���c�d������KAxK���n\�r�J��$�e��i��o Ado��H�eL��P�np�sx�t�Ll�L�N�@M����s��t�N��N����i8O�O����l�L����rXK����eP���d��e �i0�n<�o�Q,<Q���l�s�Q��Q���t�Q~�� �R��(�i�Rt@UHTU��D�a=�c�d �g4�i@�lP�n��p��r�t\�vd�y��X�al�ḃdt�e|f�g�hiPkXl9mno$p�r�s�t,u��w�����a4ye��t��U���=�n,�t����a��L�tPyn� A�#��#H�ap�eD>t|�yP�,�$��h�l��,�'H��u�(Av@	���lh)����bHig)4��a�d�e��i�*��*���i�*L�l��+	P�s�.�(�r.���eD�hЙr�.��4��4��0�c/8�iT�yX���1U3��3pPW���.��a�]c��d��e�k�lD�nP�o��rDtWy��rxX4��l�\)�e�i]L(]��a�cl�|A�]A�d^R�bU�b���a$�iDd�Me�g��g=0�n�g��8�aX�ch�t�g�iA`iH`�h�lHT/.��b��g�l��t�a�i��mno(t4u<v$m��l���n��s�����t��|=�l@o��ae�do�oC|,�o�� ipLH�s\epit� t��Tn|7�Xt��hz4xA�x���I��I����x������
��A(���
�.�ae@i�l�o�s�t�u�y�+ ��4�i����rd��No����n$r,s��A�=�.Г=����8c\edn�s�zx��
 ���p�g�������pi����xt�L����Lt����b�n�r��q��e�i���X�Rd����E���X��x�<n����	aXchd�e�g�l�no0t@���B8|�|De����Li�
yp�A|a�e�i���؜,�@dZLx��c�n�,4������p�=�a���r�,`����o��ܶ.do@�_4��2�Cn4��@a@���$hHuH4����T��H� xa�i�o�uP�[���pn�s����Gtx������a�e�g�n\�x��r�=��Rd�Up����i��Rb�����m�Cp�P�R���cTdhe�g�i�l�n�p�r�stu�����!.�eh���`mxt���H��,�j�td�j�nP��	4Y h���t����y��A��i����y�A(���s\*T�A��a��?|�jn��pLaXe�o����48aX�=@rt��tdPkl|n�r��t���D����������[e�����t���i���s�	j�ae�io03�3���eL	���y�Z�����i�mo�
���c<e\fhltr�s�t�vd^!�]��4m4<=HcHHPa����aT��p�p��|e�s�����Tn����eP�������e�n�s�������t��o��<���a���cHf�l�s�tt��$�4t��<i���8�TtD��\�P��h��E�!E����e�H0���e�h�i�$R�%��%���b�.�ae�Uhi4Iu8'w0/|.=�b�1�h6�h5��m�>��=��
$b\e��fllxm�n�rاs�t��@U@Bl�A��dclCRD�E��|�r�H�e;r�t�H?�or2 <���d<l�e�i�6h5���n�=j	a	e���u\���=��	v�@����.0	r8	t�@L�sL���	i��@	b
c@
fx
i�
l�
m�
n$pPr�s�tu$vTzd
���L	a`b
c�
d�
ef g�h�i8�j�kl\m�n�o�pprDs<tPuLv�Vw�yh���,z�
e���
h$
l�D�
$|��,
i\4
fp
t@|��p��P
����X
�t����!A� ���
o��,�"���
t "���
e�
i�"�����$���
o�#�
e�
gio��A�$��
e %P&��',�'��r�'��e<h���'��4y�* )4Hcde�i�*Utf��HW�^����|������������_�8�+H�l�,��/7�/:�n\/���o.���i�1R$1���t���1�i�1��a<e�K,2��4l@3�heL3��Hi�3Hta�i
op3���b�g047j�f�n��X7)`7�h7��������|7�����7��7��7��e�g�7ȥ���!.L9R�<H$
e8
h\
it
u�?�0
n<@��@��aH
e`Aj�gr��xB=T
bl�t(J�HI��l
m�  K��
lXK���
a�
i�P�
a�e�
n�P��Q}$�gPW���?.(a�b�cdHf�g�i�lno,pTrps�t0uPv�w��X�W�� lDnTr`vhwdX!t,axX4Lr�Ye�2A��L	��ptDZ,xa�Y�r����[��l�[=�l�mdZ���o�r \2(\p�e\���p|\h\=�e�\)ei]=(]l$s,tX]<yx=4c�^H<a`exi�
y0_�lr$_��!.P_,�^��^�����^������,�`���s�`���iPbdaj�t�b���5i�u�eTi�`iH�ad�e�g���t�iH����nk�� i@oLu�k=p�s�l=�l}$�iho4u�o#Lp���.�p�s�t�r�r=�is�s=�b�r)�i0s�a�Or�+�$s=�l 3 t���r�s���ei$r|7�Xt��zu��t��i�utDt(v�v��<iTvjpa�e�i�o�JDv=hl�v,�v��|l�r���v=�.�s�t�L�	�L?w��v=�l,��8O���u8w���l�wAXw���h4xHu�
y�����jDedi�l�o`1u��,ԇ��<rTt��܉�����\c|n�s �tT�R�gp�����̊x,�z؋=�n(�U�.�a�������lx�,bp�g�����a4b\c�d�e�gl<m�n�o�p �q�s$t�v�R�A@ap3E,�>��=Hs����PaQeti��o|��dDe��p���e�g�����c�tx��n�r�tH��\�=��P��0�=�n���ait����,X�Hz`���i��$�=(n4���0aTi\olp� �����A<�pde��|TG.����xa�7d�e�gx����?�e(���h�l X��iD������c��ܶ.c�%h(�pT�Ȱu �=b@���aHepi|u2|TG.��<dXr���,.��s��D�=hc����r =�����l�tܲ��e�iM,`�Eȵ����tT����e�l@�A(�H�ei�������nH�H$e8iToT�U`{d������0gHs0�ehp����H|a�e�iX�y�� d�=tc4���n�r���d��p����s���HV.�����g����n�o�t,�����8�=�Nrp����a eHixo�uP������l8r�7t��y���������@c`shtpv��4��ܲ�D�U�u ����,�8������l����b�c�9ef\k�l�m�n,oXphr�s�t�v�xP�P���r�,�z=����e$i�����l�?���0l����8�����D����pe(������hrx���TG.���|e8� �e����t�����e�i�p`�R��H�AP���l��a�eit��R�������s��H��a��$�j$m<tT�?������Dl����LepFi����Ԩixo��)�x)���r����e(����p�s\*����eT����h�yXJA4����a�eXSK��HeTh\iho���t���a<nHr��th��D���4t����,.����4�n,�		�ei o8y�R�
���c�f�o�s��HR���^���|+���������Up���t�����ov��7���n0s�U���83s0tla�c�e�h�iopwx�td H ��xi�!��c�r�s�v\"��rh\A�#��#���.,$�P$%�$��a���%=sb�(��'=�n<)=.��/��xh|.= c�0g�.��,a`e|ir<s�Y2�1��Xbtnto3*h5=�b�d�eHsg�l�s�v�5/�56�BrX�E\6���i�lp�yT�RH7��6���th7��:�:���i(o�;#�;��h`;��p����;�4h�=E�=��Ha�e�g�i�m�ns$t�b��@��xl|�n̊tA���llA�0A��nX��CU�llC���pD�k�t|���DH�y�G��G��c4I�Hn�H��ixKRhltn|r�s��y�J��0e�i�oDdR�K��`i�K�L��,.�L?�LU�t���@M���c(wvO,�URTU���c�n�t����# �eLW���g4YEj
�b�ci�l n8 pH tx u� v� w��a`zb� c�!d�!e�$f�$g�$h�%i�&k�&lD'm�'n�'o<)pP*q��r\*sh+t�-u.v.w(.y�$��|����k�rT)t�Gu\���i� 4� ���r� ���a�m�o t�!.�!����L�# c, d`$A@$$ e�'=�-.��@ ad ip u�/9\/��\ o�0$1U�2�|2��� r�1��� o�2�D>��=� t�<=� n� p� v�<��� a!e0!hL!ix!l�!o��ul>R(?K�?�?��!d !i(!s@�@�@�<!odB��B@xB=D!ed!n@�F�C�\!dEK�D�p!e�!iE��U<E=�!f�!p��u�!�����Ѝ�H���!������!��j��MTF���!yXKPW���.<"a\"ct"d�"g�"i�"l #mP#n�#p�#r,$sD$uP$vx$w�$xWR�zsL"w�2 �[dZ��T"ol"t�\��\��"e�"l]�R��`�"r��N
da��c=�b���"e�"f#v�"�dTdQd���"�#�#�(d���"�4dY<da��?(f��e��#e8#o���f=0#l�g=�9t�g��D#at#c|#d�#e�#g�#i�#t�g�hR`{0h���#dt� ����h���#n@5Z`iH�#d�#l�7�	�jak���#a��l���#.$l$o $v��o�eL��$oLp�d�h�@tlC��uA<$mTv�d$e�v,�v��\$n�wzXw��p$i�w�4x�x���I��$��$��x���$��|U���(��?.�$e%i@%o�%w��=�����$r%vTv? �=����%n(%o0%p8%v@�,L�lܔ�
t�U\%lh%np%r\��ԕ��T%d��X�U|%t��K���H����
�%b�%c�%d,&g@&lP&nl&o�&r�&s�&t�u�&vtDzD�������%c؜���Q.&sp���%e&i ]r�@tDz`�yl���&a�� &n̡R`���8&ep�y��|d&a��e$�g��|�:x&n��e�&a��(��Ut�a��@���\�iܲ:T�j�&e�&iȵ��t������&e�&g��H�&n��H�~'a'e8'i����=tT��4�M<�=$'h����,'t��\'a�'e�'i�'od�,t'l�'n�������l'l�����U4����'l�'n�����������'h����'t\�������'d�=�'lp�	�= (c4(fL(l�(m�(n�(p)r)vH�AP���(et������,(t��=4eb���@(ad(dt(i�(v\������l(c`�e�������(.�(a�(gP����R��A�(h8@���(c�(z����(i�(y�>
��
 ����)c:d4�$)i��X�|\)id)n����,)ax)e�)h�)i�)l*o4��
P�RD���|#dt���l)n�)o�)rP�������)r����)e�)o$��K�U���)lx�n�)o�X L�y�����)n*r*t���|!R� ��$*l	��,*l�j8*a���D*u0�
�*a�*c�*e�*i+l�&nL+pX+t�-u`+wx�*s�,�� ��"��!���*l�*n�*s�t�#oP#���*g,$�T/.�%=�*e+lx�8�rp�^@&��+y�&�$+i8'A|#�l)��,+dx)��4+n<)��@+eh+.��.��
�?.�+a�+eP,h\,i�,l�,o$-r|-w1y|.=Lg�+l�+m�+n�+p�+t�/K�"R\0���+i, �h0�+d�0�1��TG.�1���d$,r4,w�3 �3��,i 3��,n��oXw@,aLw(T4�4��H,eh5�.�/a�,c�Je(�f�,n�,rL���5��,k�5��6|$�g���
�7��7��,ex8���nk8=�,c�,m-n\0p-r�89���,a���@9�,e<�ql9��-e:R@-ap-iPLy�%�9�8-d\-tp�y�0����T-u�=�:��h-dL=C�==�-a�-b�-g�-i�-l�-m�n.r� =�=���-l�>�A�0A��s�-t�A��AAlC�-i�CN
�E�JAPj .o�R�TUR@.cH.lP.np.r�U��V�LW���o��tX��\.nTX��d.i=�.0/b�/c�/d�/f�/i�/l\0mh0n�0p�0r�0s1tP�u\�v(1wD1x�K���|.aP1bX1c@5d�1e�3f4g4hh5i�7l8m`�n8o�9p:r�;s<t<<u.vL=w|=y�=z4���T/l�/o�8��8��@/snH/e �(`/z4��h/i@��t/l�����/i���/o\�� ���/o�j�/l� �/a0e0i 0k40lH0o� !!�0nD!E����!$�s|!H(0i�!��!��@0gL# "��T0o�#, d�0t�&��&Hx0a�'��`>e�0lp(vh)R)4�0a�0c��e�0i�*��+H��z�,���Fe�0y(.�/�\/���0c.���0i1u1�0�1r�2��A�2E01s�2��81i�3�<Ad1h�@Ax1e$�`Ajp1tPW���.�1a2c2d�e(2gT2l�2m�2n3o3p 3r�3s�3t�$u�3x,Py�W�W��1iW���1d�1tY%�Z}dZ���1eP{t�\| 2i(]�`U@2eL2i�`�`��82r�`�b���R.h2ip2sDd7|e��e��2t�e��x2a��,�g��g=�2n�g���2aT@c�2d�2e�2thLph?0h���2s`i2�2aHiTi=�2g�i�kR3eXk�l���c<3dD3i�3n�3v�
��n2X3ed3sl3z�n��s�,���l�ato�Po��x3tdo���3iL Lp��T/.�3s�r�R.T4 Lt���3e�s���3h�w�4x�x���6��6�4��x���3�����p(����?.H4aT4e�4i�4l�4oP5s�7���@4n��h4a�4i��tx��x4s�4t�X�Y��,daj�4s��,. �a�����4c�4l�4n���� ����4k���L�d����4e5it����4d$5oD5r��
X���5c,��
�x���,5ttDzX���45id�	x���5b�t����X5a�5b�5c�5d6e6fH6g\6lh6m�6n�6o�6q�6s\7t��uh7v|7z,6��A�K|D

�����5k��o�5uH��p���i�Lx��5n��j@6y������86���0���6����K�)T6u�?`���T�l4��x6p�6u<�
������6l��|d&a�Qe�6iX��	�:t2c�6n�]�����6e���6e��0�u����6a7e7m 7o(�pH7tt��Ю���W�,�(7a���07c����<7i�7,@���T7lܲ:t7aXJ����7a�7eس
�Yy�Y��7nH���7a�7e�7o���nT����R.�Rd�7s�7t�j�R.�s���!.p���p8e4�U�=
P8bx8c�8d�8f�8g�8i9m@9nl9r�9s�9u�9w�8��ED�?���X8t���`8aP���l8r�
�����8o����x
�����8��8��8������8���#
�*
H���`�r��d�j�8cx�������8a9bdgy��<�	H���9iP��$9l Tt�09aT9o\9y(������������d9a�9i������|9e�9z�(�j�E?|�j�9r�9t�H��Pp�O���9r����9a��pL	� :b0:ch�st:v�	���9a�:e�:i`;o�;u@	C

��(:hD:iP:t8l�t��\e2�<��\:n�:s$��d:e�L1
H �
���:f�:m�:��^5
����:��:��:�����:���_��eR�:iTf �;
����:a$;cD;mv�@�Q;s\��;e8;i�~st�~a<�����L;i���T;m�;n�;p�;s�;v �|;i�;y��8(������;eX���;h�,����;p�E�P���;i�;s0��c��h�Uw�.l <e,<o4<u�1����s8<<}�==t<a�<b�<d�<e�<f�<i�m=n=p =r��s�<��=H�<r�>!?�>���<i�?j�@�@_�@������<��<��@���<��|?�A
0AH
\D<D=�<sD���<iE��|}.FL�E��=e8=iD=o|�yPF?d3sG PU`=ap=i .o�ORU�Qjh=sTU=��a�=l�=p�V��W��W���=e�=h�hYA�=e�Y���bP>cX>n�>r�>t\�v���=a�>bH?c�?d�@e�@fAg0Ai��j�k�AllCmDn�DoEp�Er�Gs�Ht,Ju��v@JxLJz�@�U�#�ah>i %R�&�*p>th)��x>n)4�>a�>d��i�>t�*<E.=H5R�>l�>r�3���>e?i4?l�5�l��>o�o�	7�?nȥ��7��?g���8�� ?.�8n(?e�<,�<��@?ah?i�?l�?r�?u�
yxB=�
bl�tE��D�x?e8G,HI,XK����d�?e�?iD@oX@sd@u\O,�L���?r�?s�?v�O��Oj�?tP	Q��P���?c@e8�s4@tȝ�<Q@d,@sp��pR�4T�S=<@n�%A�U�P@i�UA0h�g��l@e�@s�@tPW��t@n�@r4i�`iHd�e�nH�Xl�l���@ix�4x���@a�l�x�����x���@�d�!�����@n\����@e����Ah$Ai����D�n���`AllAn�Ar�At�Av��X�HLAz`���TAi��Rbg��R��AxAm �}@����Aaܲ��Ae0��8����A.����Ar�=Bb(BtH���
�Aa@BchBdtBe�Bg�Bi�Bl�BmCoCsCtLCuXCv� ??��� Bi�@LBe���4Bh`A~����TBrغ�\BeT�R�Bn����A���Bi���Bap�n�Bsx�k0���eh(���� r�H�Ba�Bi$��|��Bb$�s��M
p����x�(e��=��ACi8CrDCu�9R
��=0Ca��W
��,�l�� d�=<"b����`Ca�Cb�Ci�Co�Cp��p�9i�Cl�8nL�y�=�Cn��U�����Cg� ���Co�=�Cr<�U$�8�=�Ctp����Ca(De<DipDs�Dt�Du�Dy�Dz���4Dr��A��=TDm��n\DshDv��A���ehܲ��]
�Uw�H)�s=|Db��H�Da�De����T/.�����Dr��s��U�� ����	�����Ds����Dr�Ds�ou(�t�=,Er����Ee@Ei�lPEp�Et�����Os��8Ea4�n$pEo�K��\Et���dEr���b�|Ei�Eu4I}L	=T/.�0g�Es�	���Ea�EbFc�dFe4FfPFi�Fl�FnGoGpHGshGt�Gu��`H�Ve
	(���4t�
��Fa�z��;rH(Fe\�r����Fi���DFf�n�Fo�Ft<>z�F������pF��F�b�0���xF�����Fct���=�Fc����2$��Fi�����g8���Fnx�����Fo���0sA�HGe,Gi\A���4GrDt<Ge�3�`��TGs<��\GeP,h|Gi|}�eP,0�	�Ga�GcHeHi4Hl@HpPHstHt�Hu��j�Gd�Gn�Gp �8 A� j�GiL!,<" �!���Gax�k�%��Ha@-c8'���n�&~(Hi<)H+a
\*�HHl$,����e�+��\Hrh+��hHe�-r�-��Hr.7|.=�Hb�t�.���Ha�He�HilIl|Io$Js���1���H.�Hl�HnT2��2��vih5(Il4InPIo\IsdIz��5X�HIz\6��Ii�6|4�e$�g�6�&a�6:DIn�6|7f�7p�8R8=tIf�IgJmJnJu�I�8��8���I��I��I��8���I��8��8��8 �/^�����Ic�����Ii�8��It9���Ia@9��9��;��=Exm$U�XS��8JuhY���=e=
,.�Jb�Jc�Jg�Jl\0m$Kn8KpdKrpKt��XJaxKe@MiOo�Ov�Oy�8hC��Jl����Ji�Ju�E�U(4e`!e
D!H�Je� ���JioKu��H %��tDz�#Ki,(�'��0Kiȝ��+DKd�+HLKe)4XKi.�PW���.�cd�Kg�Kl�KnLr�LsMt�`��b���.�Kl�KoL�y|�!$eH�KiLeA@�,i�Km�g���KoLu��?�i��Le�
�l��LdDLe�Li�Lm�Ls�Lt�mj
�m��<L.\LldLnxLsn�8nLpLcXn�|n��n��nH�Le�R����Ln@o��Li�o��o��Le4,�o���LhLpUT/.�Lt�+A0s���Le tA�or�s��Me�^yD!���Mix�$Ml�Mn����0Ma�Md�Mf�Mg��kNl0NnpNoL�p�Nr�Ns�Ntpv�M�@�T؜��P�.�Md�Mn&sp���Me�i�Mq
�Nx
��,���p%��M��%�0����M���yl����Mn`��Ni��
X�HNt(Nz���
��=HNaPNcXNd`Ng���4��@��ȥ�h�N
��hNl�Nr�Nuܨy�x����A�No��,`����Nt����Ni�No�Nu�,�,D��@����Ni�NrOyl�E|=����,.�i0Ok8OlpOm�Or�Ot�������SaPOeXOt`Ovx����
`�[`� ����hOi��=��b����|Oa�Oi�y��UT�����a�Oe�?�����Oe�J�TUR�
����Ol��
�Ob\PcpPg�Pi�Pl�Pm�Pr�Ps�PtQv���Oa�Lb<Qe�Qh�Qi<RkXRl�Rn�Ro�p�RrSs��t4Sy�����3r���dPe�Po@�d K�j�Pt� ��,. "U<R)4�Pt�-R�,���Pt�.=.���Pe,2�2���Pr�1���Pe�+i�XHQexX4QiW�� Qr{tPW��,QahQd|Qe�Ql(�r�Qs��vS�\��`Qn�^��]��tQt�QvTv $eR�b���Ql0s�Lp���Qt��U(����Qi��j�Ql�QnRrRs(Rt4Rz`�jT�l������d�g������7e4��
@��� Rh�KT�R�tT���DRsH���LRepRi��,����hRnD�Ap���|Ro�x�Rm�Rv�����$����Rn4����ReL	U�	���Ra�Ri�U�Rt}$���Ra�$!0��Shd-l Sp,St<)��H�eh+TUUH�Sc�Sg�Sm�SpL�s��
<Sa�Sc�Se(ThTTi�To�TpUt$Uu0Ux� ����Se@�����So "��'A�<�PWH�Sc��dTr|I��J��So�\=�StdZ���Su�nR�l��Ti To�oR(�H8TiLTu��jDTl����U��E�a�BclTdxTmp��,�i4����%e�Ti�HD�z�E�Tb��A��E�Ta�Te@$�P��TdX�=�Tn8;�\�To�]�Ttt����TcUd���
�.~Uih5,�=HHaXSU���8Uc�Ur�Ut��@Ua�Lb�UcVdVe|Vg�6h�Vi�VlWmLWn|Wo�WpTXr�Xs4Yt�Vw)�
.�<H�Ue�Uh�Uo�?�4Vr�@,�Ue`AzFU<E=�Um�UtT�UXKHPW���6e$VrlVs��t�l=4VfXV��ЍtT���<V�dV���h���DV���?LpU������tVi��H�,H����Va�Vl�Vo�Vu�����l(����Vb�H�Vap�,���9KL9���Vl��p�Vo�����Vb8e Wp|��<�pWa��
��,Wr����4Whp���@Wcp1d�#ghWitWx�����acXS��	�Wd�Wg�Wm�Wn�s�� H���U�����7t��We�WsL��
t���tdXr�����WeXiXo,Xt8Xu��KE,Xc����$Xa,�
 L	��@Xm�	��HXatXi�Xo|�r��
���lXa�,0���c�Xe�Xi�Xo�Xs�XtYu�!!�%���da�%���Xc�Xo�Xsl&��&y�'A\*h+HYa�+,�-=�Hr�4L�4��Yn4��Yi�.��(YhLYi�5,h5��DYc��Yb�Yr��	XYa�Yb�Ye�YiZl�mZo<ZtHZz��)��3APW��Yn�Yp�Yr�Yt�gRkR�l=�Yo�o��sU��Zl01s`�AH��=p�m(Zo�$�j Zl�1U�.��4ZehY�X�y��
�.��TZ.�Zs����\Ze�B2lZt��=xZa (���Zi�'=�ZcT/�
�.���Z.��
�/�Z.�/:�Zn\/���Zo$���Zi��Zt�����ZaE��[n�[��a[i�M��[l��
�=4[.l9��<[y$0=H[r���T[o�=`[t��l[a�8��x[g�8n�[i�����[l��
hF,�[.pF���[cX����[i@����[p���[o4���[rl�H�[hP��[tH�=�md\n�t�t\apno@��$\l����e4\i�o��
�#HT\.�\sq��\\t|�l\n���x\e p��
��1�\.�\T\.]sdZ���\t��=�\c�� �\e��
�c�g�h�\jtm`pds�t�vLw�;��\.��
|==].TD|4i ]y��,]t�<]i��H]c��jT]o�C�`]r�Z�l]p���x]i^o�t�?����].�����]e�#�]cس��]n��q�]a��=�]z����]i�E��]n�=�]g�����Zo����^i|=^t,H$^a(`0^m4`=<^rHHH^oDI�Z.t<`^n|<l^o`<��x^i�����^t4A�^u��^b$���^i o����^r�!H/���^.T/n�^e0/��^l�'dg,�^.$NAd_i_yp=��_m|= _e�=,_d�/p���D_.`!.L_sDN��X_e�9���p_.h7:x_ed ���_vH ���_i�Gj�_t�J:�_aHI���_s4?E�_u�D��U�_.�����_m��_yP���_n\��`ox`y�?L؜�� `.�`sT��(`e�"��8`d8���D`i�V,�`lP`m�V��\`a���l`l�?W ]z�`.�?c�L���`.VH�`eX����`dDa	�`y���`h� ��`e� ���`d�q�8���`.n�`e�K)al K�abT� �`e���,al��8ab�?y���Pa.����Xae<�pdanH���pai�g=|al,U?�aaUU�an�=�ae�?�4yA�a.����ae�d�ac����aa����ap�o��as0R�ao<��br������ b.�bsA��(btA��8bhЖjDbg�4��Pbu4��\bo�o��hbh 3��tbt�1���br���;��b.t���Pa.$cs8���bt0����bs@����bi(����bm��bo��A�bn��=�bo���cr���;�c.����=0c.�=H8cy�=��Dcl��=Pcl ��\cax
�hcc`SsL	��tciDZ,�ca�Y�crT����cb���ce�?�����c.@ds8#=�ce�"���cn\0���ci�s=�cm$�R�ca��dt����de�#p dh������8d.��\�`
Ld.�dd�ds�!?Tde�#ihds$��tdy�#=�dl��t"��d.��,$��d.��hF,�d.`eapF���dc�es�;���di�ey`;���dp:���doT���er��et�%��eo���=4e.�=H<ey�=��Hel��=Tel�Юle.���tem���K�e.�D�!=�e.�� fi�eyx�=�el@����eaP&U�em��p����e.`!.�es��H�ee�*���f.�fsh79fe\/��,fvpK�8fi8o�Dft,oPfa�nH\fv\O��hfi؜��tfr�5��feT/A�LU�f.�Q8=�f.@���fcT��fi|�H�fh���fp��=�fr�����fo�����fmԕ��got���gl �E go�adg,8g.@����gi@gy4�|Lgm<�|\go�gu�jp���xg.x��gs0����ge�uxX4�g.L��grx?��gaho�?��geH?���gļH�gc����g.�i��hn�����h.��4 hy�&(,hrDI8haPI:Dhn�HPho���\hi��hht���thu����hlPj�hoH����hv��A�he��,$��h.�!���hs�jU�heLii�����hs��ho��heAih�(��it�'��io�ip�?��&�8i.����@is��ij��Xi.h��`ixh��lii|�xidX��in$p�ie��dX�i.�L���in]���iah���ie����id����ieB���imtjp�jv�*�xke�ii�*Hjhlt��@�,j.�js$���4jo�ADjg���Pja`�U\jlL�hje�!������j.������j.�jsܔ4�je�jiT/��LU�j.��`N��j.0N=�jg`����jnks�?�����k.$ks�N��kt�;��e.��@�A0k.X�=8kl�=��Dka�ki�u=Pkp$�R`kyH
jlkt���=���k.��=�klX���kaXE�kc��`iH�k.$ls����kt�����kn�H��ke�H�kg�H=�kn�Hla�	��1l.��8��0l.n8le���
Dll&yPlb &��\la�lo,&�hln�*=xlg0ms� �����l.�lsċ=�lr��t�j.�)0%l�l.Dms%���lp��7�li��1�lh�0H�ls�+mt��=mn����ma�&�$mt�7��<m.�F�/=Pm.����Xmc����dmi�8�pmt9��|ma8=�mm�mt���mo Wp�mtW���mp(.R�mmlnn������m.����mcT����mi�S�G�n. �Pns(���nu4�$no@���0nn,W�
<no4W�Hnr@W��TnhP.��`nc�?`�&�xn.�����ns����ni�p�ns�����nop!K�nrx!��neL��nl����nc����ns�����no(/�nr/oe�m{���o.�os�)�$oe�)�4or�)��@oe��,Loh��jXop�=dos8ppoo��|n���o.2|�e.�H���od`<��,pa�oe�����ot4A�ou�:���ob:���oi�0l�or�H/��p.T/npe�HplX<� pb�9�/8p.�/:@pn�I��Lpo(Us�I���paXpi�po�I�hptJ��|pa|I=�pmPqn�qr�1���po��@9�p.$0��pn�a�-��p.���R�p.o���pg�nH�pn�l��qiH5��qr�Cp qe��,qb���8qmJ��qoDqu,��xg.��j`qs\�Alqu@���xqoT9��qm��ȥ���q.�Q=�qg����qn��qi�����qdh�j�qn����qul9���qo�?�d���r.��rs��ri�V$ro�U=0rp(���<ru��AHrd�UTrr|2��`ri,�4��xr.�rs@���rr�W��reL��rd�R���ra�$=�red4�rl���O0�r.���E�r.HscE���rtP��sp̷��su$&tsr��K=4s.X1Apsi<syp���n.�BXssxB=dse��p���|s.`2�ss@,?�seH,�si,,���sn�44�soL�n.<rH�spHrj�si����sk����ss�d���seDdtn�"��ti���,t.��4ty����@tr8ULttW��XteX���dtm�ts/pty�4��th�W�'���t.�'���te� R�th� =�tp�R�tap��tcT%L�_.\%�ts %���tedXui�5��un���$u.�us�Nx,urpN�<uuP2HHuo�1��Tui���`uvDa	lua�{HGt�u.�p����u.x��us�v=�ueTv���ui�T����u.�=��ue1��ul�XH�uyp��uth�`vs����Uivy4��� vh���0vp`��<va�Hvr��Tvg`�,`voT� lvi7xvl7���vb@wdxg`xl�xozt����v.X5��vl���va+��vi8n���vt�m���vn�{��ve���wr�pweHQ=wf��$wf�w�870wi�-��Lw.X5�Twl��`wa+�lwi8n��xwt�m���wn�{��we����wrPQ��weXQ���w�lQ���w��?=0s���w.̈���wt���ws���we��xg���8��x.n$xe� 0xl����<xbȢHHxa�7�Txl�E�Hllx.�/=txs�����xc�����xi�e�xt /�xa(/�xm���xe�����xhx�=�xtp�R�xaTye8��xm�yr�T�=��y.t�yl|�� ya�yi��,ych��<yi���Hyd�?_�C�`y.���hye|�|tyn�k����y.0�p�ys(Y���ym4Y���yhX����yt�U�yyܨ���yh�v��y.,zsd�=�yp8pza(8zm�=�(�$z.�}\O��8z.`$=@zr�zsD��LzeP�\zd�8��hzn�8ntza�zi {o�{u�$������z.X$z�zt��=@zr@��ze�����zd�8���zn�?���j�z.��=�zs����ze��{d�8A{n�����,{.�{s���4{t���D{nljP{ik��\{r����h{p9��t{e����1�{. 3���p.��=�{r8��{e����{t�����{e�����{m�9���{oL9���{lh|o@}rVs�}t����4s.�|s4��|r$eH |e�"��,|l�!��8|lط�D|eX�P|s�9J\|k�|l���o�x|.dX�_.�|sL��|nx����|a����|ex&��r.���=���|.��=�|lX�i�|a����|c����|i���}g����}oxU}l���(}o�9��4}n��ЮL}.�B��T}m�B`}s�A��l}i<<=x}l@:���}u��0	L�}.	���}rD*j�}eP*���}u��}qP���Vn�}s;�y.l~s;���}r(;p~e4;��~f��l;�$~.�~st;��,~r�;<~e�;��H~��;��T~��o�$z.���o�x~.T�\����~.<��~r�~s<���~e(<��~is����z.p����~t��$���~.P&|�~g�*=�~nD<�i2|�u. <��d�
���r.�s����4d�RxDrP��PoHZ�\wLJ��hz�U��e.����.��i�y����n����oTF���h<E=�p\=���o��p����.`2�s���e�������.T�sp!� �r�=H0�e�=��<�l�o��_.�����`�.�
��h�n�
��t�e�"���m�"����a�"����rT^���e.��s�]����l������e�Q��̀eL=U؀h<���w|e��{.��d���.�U�st,	�h�04(�r�-�4�r��s��8=P�.�a@��X�c�;�h�i�;��t�h`;����p�-���o�-����r�0����t����=��.�=Hāy�=��Ёl��܁l���L}.<�s��j�di�i�.���o�.���n��U�4�.�(�=H�.����P�r���\�ed���h�w��1t�oD�����l�B��f��h1z��i$1����l�'(�=��.����Ȃr���Ԃe�����w����ot�������������4�
���.L	��$�lt,	0�at'�<�r|'=H�r����T�a$A��`�p̃r��H��|�.�u��e0t��s�:����u����e(�����r��4�Wl��t�>TU��܃.*E��i�y����p�����a��r��e���$�h�f=0�t�A��<�o`AjH�m�Kp���`�.�h�s,(��t�e�?Z�$����.$�s@47��eH4����n������a���h��Ȅt���Ԅe�����m$����o����r���o��h�$��.�n��l.D320�c$,��<�i�+��H�r0s��T�e��j`�t�fl�sԕ��x�edBJ��l� <����.�s<l��e�����t�*����t��4̅e0�=؅r���aT/w����.������.d�j�l4`=$�i�^H0�o	��<�f�jH�eGHT�u�C�`�q�)���x�.�����e������v������i�B2��t��=��a (����i�'=Ȇc�-�Ԇo�����s�EA�sT/������.H�s`���c����a�E�(�n���Hl@�.�Xi.��s ��T�ldo��d�e(�=p�n����|�r�����e|e�$u.���/��.�/:��ndY����oY̇i�,؇t����a��#�e����n��H�i�E���lX�u���$z.�p0�sD���,Xb<�n,���H�m@$`�.Ȉs*d�d`�Kt�nl�4��a|�=��r��p��a�e��o܉tF����p�U��k.lC���k.ԲHԈmh���uh���iD�����d�����n��7��.h=U$�ep=j0�sL=U<�i`iHH�w�$�T�t����`�n�l�e(���x�n����x|.�s4����r��H��e�����l`;����l��Aĉo��Љr�o�n.���8���.n��e����l|=�b,H �a��il�,�m�=<�rP�EH�oFT�f�g��t�?�t���t�.��s8��|�t,���sЊt���;���.��|==��.��Ċy�����)܊.8�e�����s�����s��=�e<FE�rT/k�*�$�.�*��,�s�H��H��D�.��d��s`<��L�e����`�t4Al�u�:��x�bx�����iDF ��rT/��3����.2|��.�/8p.,�s�/:ȋnDc��؋oPc���i�b=�t�����a��i<����l�o\�sL���g.�?�(���8�.��s��=@�t��EP�s��\�i��5h�n�dt�o�d���i�5���g���;���.�?��&���.�����s��̌i�j��،sH�p���NH��.p�s�N��t��=�n��� �e�)�,�d*��8�n�=D�ol���P�p����1h�.�D>H|�.<��tTZ=��n\"����a�FR��c�F����e`iH$u.���̍t����؍n�$��e@���gH�=��nT����a���_.<G� �sHGt,�e�E��8�s|�jD�r(�=�l.��s���\�r���l�e����x�k<�t��r�F����o�o��j.��!����.�>����e�<=Ȏs�<��Ԏa$&t�c`�s�
�k(G����n8G���a��o��y�Lt��(�.\0�t���<�f�$H�aط�T�h̡��D_.Џs�Q��l�e����|�l����i��=��d����o`H����c4�s��jȏ.�Ld1A܏.T�eX1A�h.���c����t�$�a��h��oxH��$�s�&�A�@�.x1jH�d�\���`�.$���h�r)t�e�'=��v�3�
����.�s�����m`����a�8����r8=Ȑg,XԐo�W���t����p�>����.��	�r.ȑs�U�k���(�n�|z4�i�{p@�l|IAL�fHI��X�f0�n��s����	x~.ܑs����|�k������n�|����i�I������I�����ط��e.�Jط�ԑ.,H$u.l��m�=�r�����odaj�f(D��iLJ�$�e�T�8��<�.nD�e�Y�P�lس�\�b,�h�a��e`���t�z�,����i�,=��mtH����o�J:��t�Y�`�.�dԒsLp���k.�\���k.����`�.����d������n�-U�ud���hx�s\=��(�h��t K�4�c��m�t(}�̃`�.�z�i\�y�"Th�l�"��x�f���!����l�����eLn��s�aԃ����.#a8�i��y�"��̓��"��ܓ�p���l.x��s����e�lp����.x� �s���,�e�y�
��D�.���L�mĔp�=X�al���h�r�V,t�g�=���o|==��l���y�������.4�����h�!��x.$�s�4��Дe����sD.��a�-���b`�pXL��a��e,$��e.�?�D�0�.l�s�'8�h|'=H�tl.T�aP5	�e.�#p�e.ĕs�+��x�p$s=��m�.���a�.����t�.����s���{.��8��Е.nؕe@	��l�D���b�D���a�[���r�j��� �.h7:(�e�=4�v�_�@�i�_�L�t�_�X�iP_=d�n�^Hp�i�������.h7:��e��=��v�_���i�_����t�_���i�^�`}a�}c�}l̖n�^��ؖ��^������H/���.T/n�e�H�l�\=(�b�\4�a�c?@�t�c��L�cN��YaX�e��,2��t�.�s2��|�r	���eЩ���vةj��a�����uTf����q #��ȗi`�ԗm�f��eDN���s�L��f.7�i.�6���m`����sl���(�ix���4�t@��@�aL���L�r����jd�.��� �.̘sh79x�e\/����vpK���i8o���t,o��aT/��LUĘ.�H��`�.\�i4�sI��ؘc�\�idZ����t!��c���e�P��l�Hl�k.@�4��.p�s�B�@�n|�|P�a��x&�h�.�?��$��|�.@47��eH4����n������a���h����t�����e����̙m$���ؙo���r���ol����l�|s.�iȚs
���t���(�c\�A4�a�p@�rHQ=L�f�ԑ.4�iܚs
��d�t���x�cd�����aPQ���rXQ�����lQ��������;���.���;�Ԛ.�6��.H�s�6:�np����o�	�6�.\�s�6:�np��(�o�L��@�.�#L��T�.T}d�.,R=��nh�rRtt�e�r)�r.ph���s0h����s����e����t�n��4��a,)|̛r�H؛aHR��pl�t�1��=�.�7��y�����lH1� �tP1��,�h��8�g̯�D�uدRP�ax�i`R�\�rp-��x�b�?	4yA��.�����e�i,)|��c�8^��a�8j��p(?����s���̜e����؜l�Tj�b�!>�C���.8|�g��n�?D(�(�.0���0�h��<�s��HH�i�S��T�l�?���l�.���t�e|.=��g�����a������t<U����f��v�T=��iT�oHVDH��Н.8���؝s����r���ePL�D_.4L4��i�y�\$�rh��$�a��0�d���<�e\U��H�m�Lp���`�.�+h�s�+Ht�e�?X������.��s������t����s�����i�yX�����l����̞o�D��؞p�b�;��.�m���.��|s.TTE�a�w��$�i'���c0�x�&~<�e�V��L�l�V�YnX�s�H��`�.I��t�c�\��idZ����t�u�O0��.\O����s��=��r�2Lğe�-�Пd�-��ܟnY��e�Hl`�.(�q�s0����c|�s@����i(���(�m<\4�o�[=@�ndZ��L�o�?/t���d�.��s8��l�t�;�ȏ.�-��u.�e������s�D����s�D���a�g����lT��Ƞc\�vH���Ԡn�a��edaj�gT/��*��.�*���s����0k.p�sKH(�e�J��8�uXJ=D�l��~P�a��Lp��h�.���=��|�.��=��l4A^��a<A����cLA��i��o$A����n�@�ġadZ��Сh��ܡcT;���e`;���m:���o�\�r�c?�t���,�0�.\,8�ctH��D�i�G�P�t|�j\�s<E=h�u���t�o��A��cP&U��aH7��(�.�s����tt�=��s<�=��iDd̢t�;��r.���u���.��i�uA��r0h���u���e�k�� �nlj,�e,��8�r�:��D�px���P�e`iH\�r����t�.�|�lPF����a�?�����.�����e�����n����i�k��̣r����أhx���p��e����nk���ix�)x|.�+p��c �t�+,�nXKH<�adK4H�iXJ=T�r�A�`�a�?S���x�.h����e�?v�$����.@47��eH4����nLt����a�y�s��ĤhP��X����.����eT����n�V���eX����l�4�8��$�.T� ,�es�8�l�=D�b�L�P�i�v=\�s��t�v��h�rTv���Zax�e�L��d�.ĥe�i��s<��u.2|8z.�o����dȥ���d.�6|Хg|=ܥn�����.������e`����t����i0A� �s�j,�i���8�u�w��D�q�t�����`�.��4h�y�Q�t�r�
}��a�
��n�
����i������dl'A��r�9���o:��ȦaU~Ԧrȥ���_.��#�g�!��nlyH�ixy���lx=�lL���d.��4�s��@�n���L�o@o�X�i�{�d�m��=�l.T2��|�y�����l�����eX����t�����i�|=��nȦ���et���Чt����dܧi�n�|�4~a�~l�n��s�x���������x��������`�.����0�m��بa<�u���H�l��X�l$���d�e���p�g,�m�z|�a��e�o��u����p�g,�m�j��a��e�o��u�y��e�Ш.�8j��.�8���sn�e�����l��=�b��H�a8��� �mȥ��`�.����8�g ���D�n̊jP�idP��\�l�\��h�g����t�d����t�d(����e.�s��4��t$A����r�@���a�<����h���̩cd����[rةw�����[rةw<��{.���9�.
R�nl*H$�o�>40�b�<=<�r��H�a���T�c`�E`�oT�El�r����x�o���x�oЀ��
�8����.n��e��y�K)��lL��ĪbT��Ъa,�ܪd�
��=�.����.Y���a(Y���i4Y��(�h(.R4�t��tl\c@�y\���(�.h���\�t��h�h���t�g����i�����rȄ����hl���re��A��dLe����aT^��ȫo�]���\bԫl ����e����eh�iT��\����.x��r���� �e�R��,�ih��8�l���D�d�P�nD�j\�e��v|==`�.��=�ix�y����t8O����i�o������l����o�_p���Ȭ.6Ьs��=ܬe�G�D_.��j�s�����u�H��Xi.p���c�$�i���0�t� ��<�cp�x����H�aTU���y.�2����id�y��p�����.x���s�2E��e�( 3����.��=��r8���e���̭t�(�حet-=�m�����o�2�q��.�q=�c�Oj�i�L��(�sh�t����4�e�/=�.Xt��P�c�OH\�i���
`�.��s����t�cL���i8���r�����t��,��e�~�k.�;8=̮.@��Ԯc�;��i�;���h`;����p$-R�o�����r�jU�t������.|=4�l@o�@�a����L�m�X�r�d�e�jAp�hȯrЮ8p.�����mpF����s�;����i`;����p:����o����y.�s\�Aԯn@����oċ=�ml�����oL��$z.E��(�.��s�D� �e����0�l�VR<�cl�=H�n@$T�u�n`�d���l�n\^s��=x�a�^e�i<_o�_u�j�t.�?�����.�s������e$KȰcDv=԰nTv���aD�o��v�:����eT/L�����.�G��d.d�l|�j(�s8w��8�u�G��=P�.4H~X�yT����e.бs�=�p�e�i1���lh+����yDt��t��A��s�j`f��r�j�{.�?�����ܱ.�s0����t�����s�;�n.4yA�a.t�s����e,)|(�c<)��4�ad�@�pн�L�s��w����X�f�@���{.3���_.�O����yP����a����`�.�s����r�����e����g�j̲nl*Hزi��4�bT�l�o��k.�?RlAR�.h�s�l��n�lj$�i�lH0�u��<�q�HH�e�\t��`�.p���(�.�nt�s�nH��e�A����ix1j��rd1A��eX1A��h.����c�g,2��Գ.��s2��ܳr	��eЩ���vةj�a����uTf���q #��(�i`�4�m�f�@�eDN��L�s$NAX�i؜��d�mT��p�eTf��|�dX�����i�o�z�L���.�?�<����.0�=��n����̴i8��شb��t�o�fR�l�f=��gd�p�r��x�[�.x�cX�H$�a�t4�i@��@�l���L�i����X�h�����p�.��s�Hl��.��0�E��.������y������gtj,��o������l����̵o�Uصe���h���/=��.\/���c����i�j��t��K(�a����@�.pR��H�eܶi����T�t����d�i@���p�d��|�o�'����r���h|=��p@o���aai�����	ȶ.hR|жc��Lp���.�9R�s�������.$�=�l�[��a�M�(�m�M��4�i@��@�c�L�e<SHX�d�w��d�a��8�|�.@�����y(�����m����o������nԕ����o8=�.@��̷c`�sT�طi|�H�h���p��=�r�����oP����m��$�o���0�e��o��ЮL�.���T�m��/=l�.Xt��t�c��f��i���t���e�����h��=��t�?(�ȸ.HR�иhL�ܸs��i�lH�d����a��A�r��t�eX���$�s�5��y.p�sH5��<�dP1L�eT���X�b�U�$z.�����|�.|=��l@o���a������m����r���e�����h\U��̹t�T=عoVH�rX����d��p�)�G��.��=�sȏE �u��,,�m����8�aH4��D�l�P�aA\�hX,h�tH���t�o����u.Ĝ���s̜����l؜����a�o���{.�����s4���̺h���غp�=�adj�r�i����g�6(.C�.G���i�y(G��(�s�t��=8�a@W��H�rP.��T�c(.R`�n�Al�y,��x�s����o�Lp�����.x���s -����e�[lGȻ.$�atG��лc\G���i�i��=��.�=H�y�=���l��=�l�����.��s��0�r4��@�e��o��=L�tl���\�i�o��h�n�o�Xi.h�L�.p�=��r�{�@����.4yA��s�����e,)|ȼc�=Լal����p<����s������eċ=�r�?�����.x�s����$�e L4�c�\$@�n��)L�a����X�dT/{����p�.��H/����.T/n��eȰ���l �=��b(8��a?���t�>��Ƚil�=Խb@��u��=0k.T2����y����l�s����eX�� �t���,�i�|=8�nP�ED�i�r����=`�.T2��h�y���t�lT�sȦ���et�����t�����ix����nX������l������������ؾ.$�=�l�qR�a�q=��m,�A�i�������.$�=$�l�qR0�a�q=<�m,�AH�i�?�=L`�.��s,IRh�e4I=x�r�H��uH?����t�;����c�-���u�-����r����t���̿s���ؿa��p���.�������.`�sp!��r40H�e�+K(�lh�=4�lt���@�a���o�X�.�����l�.��4t�y���r�R��a�����nX ��i�C���lL!=��pT��iHR��c�
�s<3��i������d��g�����r��H�e��H��,�.p��4�c�@�i���L�t� ��X�c����d�at�=p�l j|�a̡��@�.(I����e�i�H��l������i̧H��t|==�.����yIH�t��8����.T� �ep�y�
R�lh?=�bV(�i�]=4�c)@�u���L�d��v���=h�.��8��|�.T= ��eD=����l�<=��b0w���a�����cPj��o��K�r.�;��0�i��y`;����p:����oT����r���thF,�.pF��$�c����.�����i<�y��HH�r��=X�a�Q�d�r�6|p�eD�=|�n@�����i�)p�����.�+��s�H��e�����.���sT}��dTf����a$n����i�m���m�����e��=�r�=����4�.��s���<�eD���L�tLW��X�o�w��d�n��sȵ��p�yT����ba��eP�i�co�crT/�����.p����.�s\���e`;����k$-R��o�XH��r83U��t0�U�y.�R�_.��=�g���� �n�tx,�i����8�n��HD�l�?q���\�.p���d�t0�p�s�8|�eH/����.T/n��e�H��lTi=��b<�F��a�����t�"����nX>�4s.�|s`>����r� R��e� =�p� ���a�U��cD��(�sP�ddc4�d�5�i��P�.0
���iX�y$
�d�n�*Ht�e��4��c�?((�����.�%����td@���s����.0�sX���r$p��e������pt�����p�|A��o\�hL��f�=�o�(�.����i,)|<�c=H�a�o�T�p 3��`�s <��l�r<lx�e�s����td)R�r.��s,)|��n�r=��aLp����p�t8�����sD�����ex&��e.�=�p�e1���l0s���y\����t.h����t�aI(�hdaj4�g<Q��@�iL=UL�e\���X�w,�Ld�t��\���|�.�����sP&|��eH����n,����i��j��s��A��u|�����o(�=�{.ȵ����r|D���e�����kp3���c��4�a��� �b��s�!��C�<�.8|D�g�P�n���\�i,)|h�c��t�a�����p�r)�.�����s������s����e�S�A��.H�����d�����e@�s4�����h�����p�=�a��R�r�4���g4��(�o��`�.dg,Xi.9����iL�yT:AX�m@:��h�oL9��t�t������op�����b�g�ej<�n��qfu��v���Y���.,���e`�����z̈��d�.��=��sl�����e0s��(�.̈���t�����s��?$�e��0�g|==�j.TD|H�y�T�tP>U`�i	��l�c�jx�aGH��u|D��{.�sK��k�s����c|sL��u�L����r�LU��t$�����s����e�`�Hl��.(�q�s0����c@����i(���(�m<\4�o�[=@�ndZ��L�o�9RX�cP��d�e\��p�o����fo|�r�oЮ��.�s�����mpF����sX����i����p�j��o�'����r� ����p�{Ȯ~	�.�� 3���.���� �r����,�eH1�8�tP1��D�h$1��P�g'��\�u8��h�ax&�t�l�w���.����t�Gj��p8G����i�Gj��r�G���cH'����sH���Ш.��=��l|R��ad���n j�i��@�4$�.��s�B2,�n|�|<�a�/=H�i����T�c����`�i�el�t /x�a(/��m����e������h��t��x&���.T/����.�0��s�0l��ep����.�j��dt��i|���a���cl�oh��(�iP��0�.���D�e��iP���P�r�Q:`�c��p���x�.6��s<H^��eX����i`�j��t4��0�.l�s<�=��h�F��tl`���it`=��l����a���9,�.h/�cx�s��tt/���i�/��0�l�H<�o��AH�b �lt�pЮxg.��s����h�m��Ȯ~	��.�[ �.��s��=��e,�AĘ.�?6����.,�s�����e<�R��gd�=��a�$���uP���g�/�n��!�ä��Ȼ.���4s.x���8�c�RD�i���P�r�'��\�o�0��h�h�$��(�.@47��eH4����n������a�$������.(�e����sP�t�����iX�����l�;����otu����p����o��,$��.t��s���|.d�s �=4�n��=D�a��x&�\�.`H��d�e��f��ohs,���p�o������r`Aj�u.@�sl����e�}����h$���c����i��`Aj��.T�st����e�}����h8��cD����P�� �����=8�.���=L�.��Ю`�.��s4>h�m@���x�sP���it�=��n��j��at�����g,j��r��Ȯ~	��.��(�.$�����e������g�����a\�i�����l���`�.�� �r���,�eX�H8�t`�
D�i|�P�l�A@�.H���h�d���t�e��s4�����h�����p�=��adj��rP�����g������o4�����e8�i���t.�Rp�����.���s�Gj�e��i���� �r�H,�c�$
��=D�.��yX��L�s����\�i�hn�u�lC��|�.,r���mL�����a��uX����i(�����s Wp��oW����p(.R��mx���. 3����.�H����r<�s�����e���t�?�0s��(�.�3��0�t�!��=H�.4H~P�y�G�\�l|�jh�s8w��t�uTv����ox���vB����el�u��iT���h�o��.T�,��s`���rTU��8g.����y8�=��mH����a���gȥ����.��#(�g|�4�n$eH@�iN��L�l�L��X�l���d�etBRL}.��s�\|�e�\=��ldZ����u������c������e�j4�.d��|s.�*���s��E��h��4��cP����r�\O���.x�s��=�rh��(�e���4�dT���@�n�V��L�e�w��X�l�/�O0p�.��|�.�����e@�����m����ox���r������h������cT�el�i��p�s��t�<�/=��.Xt����cT��iD���t j�e\h=$�g0h��0�r����<�e���H�n����y.��j`�dx����r.��s����x�e��y������l����o��j�e.��Xi.�?������.(�s�R��e�����n�) ��i�����l�����p|�t������ �.|}��}��4�.$�<�c��H�i`;��T�f���$-R`�o� p�r�}J�}����.8���cD�����P�����`2�gs������e@9W��i�op�=��n�����o�G�xg. �P�sT9��uЮXi.�$�m��0�s�<�i(�=Ш.��e��s�A��T�o0A�h�tD*jt�iP*����u��j�hc��q�9j`�.h���Ԓs$$z.�s,j��m\U����o�T=��o�?����r���|s.�^2|�. t���d�@U �e4yA,�t<y��8�ex=D�c6jP�a��\�f��p �u��
h�i����|�t�8���.T= ��eD=����l��=��bD�����a�����cX ��i(����l�#�#����.H����r�G��e��R�s�U���.���,�s���8�dx�D�l���P�e�i\�i���h�f$�n������t�ox�p�it��w�k�U���.�����s�����d��U��lTj��e4j�����Hj�����@3$z.8�sL3����iP���z��a�?�01A0�.S���_.Xw��D�s�kUP�w�k��\�e��r����h�hH�x.$����e��i�����t����ih�e.p=��c�Q���.�Q����t�w����s`�R��eP�s 3����.0�s <���r<l�e�s�� �t'�,�td-z8�eS��D�l�?>�$��\�.@47d�eH4��p�n����|�a���h����tT;����e`;����mT8,��o4����r���"����.t�����e|����m��rd�R��a �e��i��p��s@�uD����je�nxko��r��t�kv�v�/=L�.Xt��T�c<�U`�iD���l�t0�px�e4����m$����h��E��t��4��iK=@�.�g����yT����cD����n j��ep�����g(f=��r|����e�����m��q�tt�u�?����8�.h��@�e�+pL�c�+X�nXKHd�adK4p�iXJ=|�r�A���a�l���v�lj��i����uTG=T�g��.�cP��s�c����s�����eT4��lLt����e@����hdX�.̜�� �n���,�a��8�eE��D�d�?�P�iH?��\�l�uAh�c��8=��.@����cT���i|�H��h����p��=��r����o����m��=��o�� ��s�����.����t|�=�c��p �aF��,�p��=8�mP���D�oD@=P�c�?��\�oD$Ah�d���t�u����eh�	��s�?����.T�����h$�j��t�'=��o@���oL����m,H�k.`�ll���m�=�r@����o<D=�f��y(�i���4�n����=L�.��pT�y�?���l�.���t�e�����n����i�k����r������hx����p���e ����n,����i<�����p������e��ȥ���.�Q=�gt$���n�+�$�i�+0�d�+=<�nh+��H�aP5	T�t R�
`�s(R��l�hp=jx�tL=U��i�����w�?؜����.,�s�5���e�����d�jA��i�i����tx?���o�?���eH?����l�����c��t�?� ]z$�.��(�=8�.��sȵ��@�r|D�P�e
��\�k(G��h�c8G��t�aX1A��r������c���o���.�U�|s.���s�+����dh+����e�o���t��,��sh�����r����w.�U�e����n�|z$�i��o�{p0�l�?�x��L�.����T�e����`�n�|��l�i��o����x���������l��$z.�e��s��A��dd�����al��d�.4�e�s��A��d�����a�|s.��u.�M|��.@���d�M|�~.@��(�d����`y.��s����@�t����eP�s���\�i��yX���l�l����|�o̠���p����o����g�����i��;���.��`�.�p�����.x���s,mt�.T�s4m�d��k�n��= �a�����kn,�r����8�e�U�|�.h0$`�.��s�H=`�n���p�ad�=|�t����u$���gx&�(�.�?�H7����.8�s�6����t�����s��H��i4T��t���n�4���o�4���d��kP�n����o�;�L�.�,0�ED�.����L�y����X�g���d�o$0=p�l\m��|�o�lH��t(�=��a������r�����e�?<l@��.|�,��e������n,�����e�����up�=��l����o`;���tT8, �o4��,�r����8�t�D�iXw���r.��sx�\�w���l�eL��x�i\�����v$�����r4�����eS���e.�NQ����.,���clT���iTTE��dXS����iȥ��$z.�g���n 	�i�U(�dX�=4�d��i��ld�r��t��=4s.+�\�yT�gh�l�jt�sT�����s�	��ePy��l4�t��n <��4�.��s<l��e�s����t!��lr��t@�A�la��e��=L}.4���a��i��o@���l���$�o����0�b4�c��d��f��g��l��m@��4<�a�U�9,p�.(x�c���xg.d�j��d������iL�HL}.��s�Q���m�)���g��m�)��i���4�.�H��H����.H�s�R�e�����t
��u\)��(�hT/��3��@�.�?_0h��T�.�YR\�e�Y�h�n�i�t�e$6����zH5����n|���e�V����bX�����l������yܣ���h������t4�����e�?sl@��.|�,�e�����n,����e���$�up�=0�l���<�o���H�t`�ET�oT�E`�r����l�o�zx�u@
��l�?�l@��.|�,��e������n,�����e�����up�=��l�����o�����t`�E��oT�E�r����o���uX
��(��d
��4����@�OL�.H���T�r���`�e4���l�h���x�p��=��aHi����r�!4l`���.t`=��l������a!���g�),��e4�l����.�d���mDd�sd���ip!��l|!H(�e��7@�.�6��H�m4���T�s����`�i����l�th��x�eD",��n"��g�)����a��e�i4�o|���e.����ch����i�)����dt�t�sx�����.L(����e���l�%���o %�� �sP�,�i�V,8�n�V��D�aX���P�l����\�y�),h�h��r�Y���.�����e������z����i�������.�0���y �=��r�����a|�H��t,#����i�
���l�DH�e.�����t��j�nL#�(�u���h��@�.����H�c���T�i��}`�n��Rl�e@/Ax�gD���o�����h(�(�.�n�N����h�v=��sTv����i��A��v�r)(�.�����s������sP��e`n��.d�s@��nHi��(�oTi=4�gh�H@�aD���L�tL����.��lC��p�.�uAx�m|����u�����etu����lmv�t����od�Ry.i���n����o�����n@�����e�N���m�o��y���nP���%(�.�%=0�e %��<�nP�H�i� ��T�n� ��`�a�V,l�l�V��x�a�i����l�d�@�t��	��s�����i�����l�����e(f=4�.$�s|����e<�i�q�m���e����nl�s�t|��|s.Hf����.t�0�c�)KP�r�)��H�o��HT�h����`�p���x�.�����s4�����h�����p`����a�8����rp�=��g �oT����o�s����.�!���td��e�{p��s�����f$�j�ft����s��0�.�!��8�tl�ED�e�xP�s����\������h���qtTA��.�s�=��r|=��o��=��dX����a��tL}.���0�.$�s�d���eH�i$��n����i`�U�l(���e���,{.�R��.��=0�g�C<�n�<���T�.��U\�sx��h�a�
t�h�=��n��A��a���8����.T= ��eD=����l�=�b����ax���cd�nX �a�e�u���.p�sX��r$p�e����(�p�4��4�p4��@�ol�HL�hp[X�t�o�p�.�����. L|�e�,��c�,j��n�X����aL���sT�����aL��D��.�s$A���nA���i����g|�#�_.������.����tdaj$�sD��0�i j<�et���H�gT�=T�r�U`�e���l�t�y���nix�l4�m��rh�s�ot0h��(�.�g����eV����n8�t����e��gd�p��sP��X����.����eT�����n�V���eX����lLt�� �y��R,�h�?jt���D�.��s8��L�t�"��\�s��h�it�=t�m|V����a�o�;���.��/+��.�/:��n\/����o.���iس��t����a\�,�z,�n��i؋=�n�G��. �P�s(���(�u�4�o���@�n���L�o�W��X�h�?�0h��p�.��x�e�
����nTX����e1���r�XH��yD�#��t���.���.4$��e�#=�t�n�a�����n��=�a���r����g����(�e�,�x|.\,@�c�-��L�i����X�t�b=d�s`�Ap�a�9R|�l�����e��u����o�t�G��y.h�j��s��H/���.T/n�e0/��l,=�b�����a�8��$�.�s�8n�e��p �l�+��,�b��8�m����D�a��h��s8�u��P�t,����.��jt�s�C=��u�����o������mP,����u��{.�s����t�Gj�p8G���i�A�r�;��c�;5�r.�
��Ш.�E=�l =�� �a<<=,�r�8���.��s�8nD�e��pT�lPX��`�b(��l�m�8j��.l���e��A��dLe����ah����o�R`�.�+����g��	�n��4�i�j��r,���a<r�����.Ls���t���$nl�0i�o��1��.)���.�s�'=Xr�@)ho�@��tsH�=�s��e0l��c�?��t�.�Mȥ���.�6|�gh5���n|��	�i<�=�t����t�) i�=l$��$z.xp�RU,p�R��<a���Hrt��y.X�`d$pleT/Z�*��.�*���s�3�e�3���s�+���s����ep����t���s����e�f�����.����r��e�i���t���(n��4i�=��@r|==Lp<lXy���dt�t�dU|.<�|�g���n���
���.�E=�l�E���a�]=�r�?���uH�=�ds��ehu�@)�~.�@��s�?����$.����,e�
8c|J=Dn����Pa����\r�hiL_s����te����i�y��=�nH����e(r�'����.��8���.n�e����l��=�b��H�a�
��m���m�=a`iHD_.���4t���@n��=Le�RXn�s���di�G���.�ox�s�D���u��=�oT��u8���ce�s�=��.)���y�'=�r4��	�o`��e.s,$��r.����`�.�s4��r$eH,e�iD�8lL��HlX��Teȥ���k.��#lg|�xn�_����.lh7:�eH8=�v<��i(����t����i�����bD�~�i��L��.���y,<8�.<lo�H��(tԲH4tL!=@uL�Li���Xc$,���.�s�+��pr0s���e�o���t�����s�H�e`o�o���.-��`�.�shs=�r��t�k.�?����.���e<�Rgd�=a�$�$uH��0g��=<n���Hap�=Tl�t@�A�.X�=pl�=��|a|==�pT����y$���0�.�sPN��ei0N=�c�����n����iT/������.���k.�B2l\�=a�r)�_.���(s����4s���@e����X.X5�`l��la+�xi8n���t�m���n�{��e����r�p�eHQ=�f�����f�	�lS���iD@=�d
f\g�n�w�
�?���oD$A	d��� 	u��,	e|y����H	.X5�P	l��\	a+�h	i8n��t	t�m���	n�{��	e����	rPQ��	eXQ���	�lQ���	��������	.�
l����	eX���	t����	i���
n�SR
io������,
.�
lȦ�4
et���D
t���P
i�S�\
n�S��h
��S��t
�����=�
.T2���
y����=�
.T2���
y���@���
.$
��
s
H�
el��
c�=�
r@�O|�.H���r���e4���(h���4p�=@a�o�SALrE���.�s|�jhp��xu��
.��U�.�s�����m<@��y�����.�
����.s�����d<�t�r����o�U��.�d�d��.�McN��$i]��0l�A�<e�UzHd�U,Te�s@.�`h(.Rpcd���u.�?{����.T@L�e<@��c!���n�p�e<6_�c�>R�s$��e,���b���ȥ���r.
g��
n 	$
i�0
d�
r	��<
d�ips�jL
a�i���`
u�/=�.�
s���|
c����
i�
u����
t�=�
aLi�Hl��.�?3=L�
.1���
e�0��
r�%�`A�
.�`��c����iT���g��(e��4l<U��@p�2����X.����`r�s��Hle$ |t�j�n�?;0s���.�3���t�?E����.�sT@L�e!���c�t!��n�J���eXJ=l�A�a�l�v�l�$i�lH0ux�<q -��Heh`rs�t�,��TiT/V����|.�h0!2�.�x�����.|=�l,H�a�����m(����r��oX,�nH����oX���p �Eyt��|l|��$aL�0c�<it�(Hd�&UTa�e��� ��p.V�xll���a�q)�u�q=�d��iH����s�?��.T����h$�j�t�'=�o�o�&�m�t����� .�/�(y�/�4r�/@a�/:Ln�0��Xo�+��di��=pt����|ap��`�.X����s�����o(�=�pH-O�o@r����4�.�A=�r�A���ad�=�l�$�u@�4g�:;
n�:��$axvl���0i��$M�L.0MTl�E`ali�����.st#��e�*���c<t�*���n�3�e�3���s�����s��H�elAR�t0A��nT/[����.����.X5�l��$a�#�0i|�Ш.T8,Hy(8Tr45��`t�4li@	�xb��h�`�.����y4����h����p`���aR �r���gL��o�ph�i(�(�.�ll|Rh,
s4
�$i@
0f�?��$H.�lt|��PhP
��`sX
��l�d
��x���=�.���y����=�.���y@����y.(�H�e|D��l\=���k����c�-U�a���h�
��s�G���. �P(s2�4u2��@o<��Ln$��Xe��<�Fp.s���xt(f=�n�e���e�
A�m�
��e�
�gL	���nLa�aT4�r(���r����1.��p���.6shD=(e��`�.�s%x@e�H�Pl�u�\�\g�\=ln�\xa�j�k.��4��.�A=�r�A���ad�=�l�\|�.4i����t,R=�cRt�e�r)i�u�6p�.�6:nI��(o���8��@.T� He�
RTlh?=`bVli�]=xcdBJ�.�@��o�]�h���!���.(ds���eL	���s���a�k���r,���h�o�r�,$�.t"�.�68p.,�sT�
	4n��=Do����Pih��\tp��hi@=ts������.�s����t����nlj�i��1�.d9��b-���ahs=�r�����d.�t$��t��itu��f`�����0.�t8�8tD��H�P��T�2|�. <��ld �@xe�2|�. <���d(�1�e�8��T�.n�e�|�l�Gj�b�u�ae0t�s�"H��.�?�H���.R�$e�wj0r���<itr$���.�p�RUXp�R��ha�it�.X��d$p�eH��j.�R���e�RU�t�(����.�o��s�@���oH�=�r\���e4�|c ���o����n�U(i�G�`�.�n�jj@s3�Pu����\o\���heh���tt�c�r)�.����s�����s�'	�e���ra,�j�e�R�l����g�"@:���.Li(sL9���t���o�;��.�����4.Xs���<c�HlXi.H/��܏.�sT/nde0/�tl|.=�b�U���a��t�����d|�j�n@/��t.���(�.Xs�����kp!K�rx!��e8��l�jc�w!�s���$emLtj0l�r,uط��t.��(�.x�=dn�R�pa�e .�|m�sz�o��(�.����ne��.$eH�aP����l����lX�7�eH�=�n�.�o�/��.�/:n�0��o1��(i0�=4t @a�3����X.ȢH`a��ll��	xlt'@�i|'=�r8 =�a�,I�p�,t�a)4�s�R(�.���tL	���u̷���a����r�@��k�@��rx U e�N�=��8.��[@lX�iLa����Xc����di����pg���|o$0=�l�>���o� =�t�g$���.p����g�B�n�]�i�]=�l�A��u`Aj�d0!� e� i� nh!o�qr0rw�r�h��4 .�h��< c��H i���T n��` e���l r����x h<�=� p\B� oB��� z�;�Y=� .�=� rLJ��� et��� z8�=� uP�� a�Zغ�!.�!r�t!dB��!l�@�(!i���4!h���@!c�!t,�jL!l<!�\!o\qt��,U?x!.UU�!n@�,�!e���A���!."sj�!r��!e�W���!h�1���!cT�=�!a�U�!e�Axx!.���"."y$"n$��0"i���<"th��8G��H"uYLХgY��d"n(Y��p"i4Y��|"hK=�"t4�� �r������.#s�+�"t�0H�"a�04�"i�G��"r�G���"a|\���"th\=�"e\"��#r�0��.�?<���(#.�#s�R0#e���@#r�'��L#o�X#h�e=d#p #��p#a�#eP$i�wl����#.$,��Xi.�+���#r�����#e|���#t#��#s����_m�#.�_��#e�_��#t�_�$iP_=$n�^H$i؜��$$f�$�T��0$e�$iTf��@$dl%h�%r�%s����_h$.�_�p$e�_��|$t�_��$i�^��$n�^���$��^���$��\��.����$t,R=�$cRt�$e���$r�/=�
.Xt��%c��f%i�%t�$%e���0%h��=<%t���H%ot���T%m �E`%o�R�r.�%s���x%g�H�%n��A�%iX�j�e.T�����.X �%e<�p�%l4����%p`����%m�f��%i@&k�\���r.4��&d���&e����&l�&H(&l��4&i���=��L&.��=T&lX�i`&a����l&c����x&i�����&gh�=�&o,���&lDN���&o$NA�&i�,�&mhA�&e���&dk���&i�9R�&p$�&e��Ю'.�'sXAf'm<A��,'sLA8'i$A��D'n�@�P'adZ��\'h���h'cpO��t'e$e�'m $���'o��Ȯ~	�'.��@�4�'.T!�'nD!H�'a� ���'i�\$�'l��)�'a`�U�'d(��(e0A�(pD*j (iP*��,(u,$�8(q|}
=��P(.�(s�u=X(p�s��h(u���y.\L�|�yDL���(l�v=�(ed$���(rP$��(e�8����.n�(eW��(l��y�(b�'���(a�����(e���)p�R�{.�)s���)gp-��,)n|sL8)i0s��D)rLp��P)th���\)s@%Uh)eX�j�r.3R�r.�)s�+���)p�O���)e&r�)t*w�%���)s�%��)eLl��e.��`�U�).(���)e�Qj�)p�sz*i,�y.�*s�� *r$��0*e(G��<*p8G��H*a�RT*r�X��`*cTU��l*s�&jx*y��o��*.|D�(�.Xs�/���*k$s=�*cD����*a0�U�*t�����*s����*e�'=�*kD'�*o�dU��.�d��+g�� +n���,+i���8+l���D+e����P+kD���\+r�'	h+o���|�.�+s��j�+di�+i����+ox����+nL(���+e,u�'=�+l�,v��U��+.�H�H���+.,,s����+e,���,tT/��3��$,.l�����.�,s�a�8,nnjH,gDL��T,i\���`,e$���l,r)x,e���`�.�@���y.4yA�,s����,e,)|�,c<)���,a(-ex.h/i�/o�1r�G�|s.�j�,s�C��,u�Z�-o�]-ix)��-ch-l,.n����8z.x-s4��<-r$eHL-e�-i���X-l�-u�o��d.��#Хg|��-n��=�.�����-r|�t�-eD���-k�e���-n����t.�����-t����-f���-i�U��-r�U��.h|#R.tl)�� .d����d.�.ad�j8.d���H.i�)�T.o�)�`.r�)��l.e�.i� ��P�.����.l̈���d.���.sp���.ed���.g@���.n��=�^.hC���.yL|�.lX���.i�)��/cD/n���t�_.���� /s,/rx�8/o��!.x�=P/n�R�\/a�/e .�h/m�szx/o0�U�/w�����/s����/e*���/k80r��!.����/n�?��>���/.X0e�>���/t� =�/s�A0a<�0c�0w����0i0s*�(0t�!�>D0.�>��L0rL�4s.���d0y���p0l��|0e|=�0v Q4�.,Q���0r<Q���0a�Ut�0e1r,{X���0.$1sH��0r�R���0e�RU�0t�R��1i�����1.T�.\���01.x�81r����D1e�7�P1i\���\1lh���h1t��t1h����1g�j�1i(���.8���1h�"���1sPX���1iW���1m	���1aD*j�1eP*���1u���� `.��2e�!��2n� ��2o���(2l�+�42a�+@2dȥ���e.�2l����X2g����h2n�7�t2i<���2l�04�2t��=L�.,�j�2y07�4�s<7��2cH7���2i�6���2t�0���2s�+���2i�:��=3.�4�3yHYE3l����$3i��A03hXR<3t�1��H3l�+��T3a�3e\4r�E�!��t3.<���|3e$A���3s�@��3adZ���3hT����3c�k �3e�^M�3l���3p�RT�W�3.\���3c����3i4���4h���4p�= 4adj,4rH84g�m��D4o$,��P4e�,���.\,h4c�-��t4i<����4t$A���4sx��4a�,���4h�,=�4c@sk�.�r)�4.ph��4s���4s����4e�
A�4n�
�5e�
5g@-�5n�5p�5t$-R(5aT6e�6i7o,��t.4���T5r�`5e�l5g���x5n<���5a$���5h�`�e���5.�5s���5m����5e�-��5g\-���5a�y����5.T�j\���6.x�6rB��6ed1A$6iX1A06h���<6c�:��H6t�!��܏.�X��`6e�1��l6s�x6a��6e���6tp-���6p\�4�.ԕ���6d|����6l�����6o����7e�6h�;�6g`;���6n̈���t����7s\O��Ш.�,$7r�7shA07e@E��@7d=��L7i<<=X7p�st��?��O��x7. ]z�7t�u���7.X5��7l���7a+��7i8n���7t�m���7n�{��7e����7r�p�7eHQ=8f�P��8f�8�@5p8i�-�(8dd9e�-=48b�9m:p;r�ss����`8.X5�h8l��t8a+��8i8n���8t�m���8n�{��8e����8rPQ��8eXQ���8�lQ���8���x&e�8.p9sl&:�8n�=9o��9i���9s��(9s�j49e�w�@9r�w��L9p�>RX9xL��'.�8���.n|9e����9l��=�9b��H�9a�-�9ma�a=�9.$:s����9o�m���9g,E���9eE=�9rE��:e�:r�!a:.0D���c.�:s@D��0:t�@:s�eL:i�e=X:c�e��d:a��p:m�j|:e�;��.�?����:.�����:eP��:c�����:nȢH�:a`����:ldaj�:l0R�:iL���:e.;v�^4s.���
 ;y����,;l����8;g���D;n��HP;i4���\;m�Qjh;m.jt;i�/=�.�����;c�����;i�8��;t9���;a8=�;m���;o Wp�;tW���;p�?�dq��;.|��<h��<s���<e@���$<m�<n�<t,W�
0<o4W�D<r@W��P<hP.��\<c�G���. �Pt<s(����<u4��<o�;��.`;���<n:���<o����<r ���r.x
��<l����<i����<a���<r�/�=f�� ��=.x
�$=l���0=i���<=a�K.H=r�K��T=��K��`=��/���x=.h7:�=e\/���=v�R�=i����=t 0��=a�/�=kPL�Xi.|sL8>i�=y0s���=rLp���=t`>���=s�0��>ep����u.� >sp-��,>ep���x7.�>s�BD>nh1zT>i$1��`>lX�=l>u\,x>a�04�>pt���k.@�O�i.,?sH����>r����>e4����>h����>p��=�>a�c���>r�c���>g�?k�?rT2��?e@l���o�$?.��,�i8?.�?s4���@?c@���P?i��\?t�bRh?e�bHt?n�b���?i���Hl�?.����?s����?c@:���?iL9���?t�o�?o��?b�c�?o$eH�"e��=��.�8@y�H@lP4$@i��=0@r��<@a(���H@r,gpT@o�2��`@pF����.�E��x@e�i���@r�5��_.H5���@dP1�@e0s���@b�3���@t�U��a.�U���@hp���@tp=j�@dL=U�@iU~Aw�3�At���G�,A.��=4AsȏE@Au��,LAm����XAaH4��dAl�,��.\,|Ac�-���Ai�����At�b=�As`�A�Aah����Al�=�Ae@o��Ao�����AmT4�Ar������a|��Bt����Bs@����_.�s��H$Bt����4Bi���@Bk$�jLBl���=��dB.��=lBlT�WxBa\���Bc����Bi4����Bh����Bp�=�Ba���BrX����BgLp���u.	���Bs�j�BeGH�Bu�G���.h�jCs��� Cul9��,Co(�=8Crd ��DCox
�PCt�9�\Ci�Cn`Ev0�W8���|C.�Cs����Crܲ��Ce !�Cv!���Ci�&R�Cex&��Cc<Dg�Dv�
�CsHV&H���C.��L��)D.����Ds����Ds��=$De�$0Dr����.�DslxHDl�L�XDaL��dDsxK��pDr�Ds.A|De���p�.�/���D.�Ds\7���DePs�Dt�L���Di�LU�DtT/�,�A�D.���8��E.nEe�|Ellx EblEl�L�,Ea,2��<Esd:��HErt:��TEe���`�.|Es����k.�?�����E.8#=�Ee�"���En8����Ei�V,�Em�V���EaX����ElLt���Ey��Eh��Etp���!.�nFs�nHFe�A�� Fij,Fr�8Fe�W��DFh(��PFc�:��\Fa�E����.�T*
tFr�=�Fu4�Fop3���Fd�����Fa|�j�Fb`;���Fu�w���y.Gs����Fy����Fe =���FkpGn<<=�Fr83U$z.��{.|Gs����$Gd|�j4Gn,,��@Gu�N�LGo�=XGr�F��dGa�U��r.�
@�A�G.X�=�Gl�=���Ga�uo|==�Gp�A�l�.j�Gd��Ge�/���Gh�0=�Gc�0l�Ga�CHt,�j��y�RHl���$Hg��0Hn|��<Hi4DAHHr�S|`H.�R���HihHyh��tHl����Hd��HnD�j�He����HiP�E�HrT�\����H.x��Hr�����Hel?�u.t��Hr���Ie�JUIuXJ=Ig�Iu4��(�.ȢH8IeP)DIl P=PIl�?	\Ii�?��hIv�?��tIe$1���Id�,t�y.���Is��=�Ir�����Ia@M���Ic(�e�wi<JlpJs�Jv��+��.����Isx���Is$ Je�jJn����JiȢH$JaN�0Jl�-=�y.�Jl�-��HJl�N,XJa�N��dJu��=Ш.|!H|Jy|�j�s,,���Ju��4�JoX�=�JrL��Ja`����Jpp��Ji��L���J.����Jt����JnljKit@�Kr���� Kp����,Ke�U8KcO��DKi�?4yA\K.���dKe,)|pKc<)��|Ka0���Kp�(�d�O���Kd yh(Ll�Lr8Ms�Mv(�=�{.�|s�����Kr����Ked����Kw�z�Koн�Ll|!HLf�L�P��Ll�(�=4L.�Ls����<Lr���LLe����XLw�6�dLo�6��pL��6��|L��)�o��L.����(�.|���Lt���Ls,H�Le�P4�Lm�.��(�.�P=�Lr�P���Le@,(�Lt4,Ma�-��Mw�PR Me�P��,Mt؜���a.�Msp��DMe���TMd,a��`Mi�K�lMu�P��xMg�MlQ���Me ]z��.�s���_.�s�c���Mt�K���Meȵ��$z.P���Me�8���Mk�8n�Mi�Y�Ml<Q��NbpNePyr\����e.|Nsh���$Nt���4Nh����@Ngh���LNi�b��XNn|Q��dNk�;��{.��A(�.�Ns�j�Nr$A���Ni�@��Na����NhT^���Nc�]���Nl�����Ne�Q���NePOiDt�t.�v=`�.�v��OrTv��Oe`Aj Ovl��,OeГ��8Oh�QUDOc�Ot�M|(�.�q��`Od�q)lOe�q=xOd,�A�Oi�Op���OsД���Oe4yA `.�Os����Oe,)|�Oc�r=�Oa�@���t.�W��{.(���Od�rPa�rjPe�r=Pr ]z(Pp؜��4Psp��@Pe<)����a�PrX�jXPp�RhPs�Q��xyctPg��Pa�j�Pep
(�.@
�Pt(G���Pf8G���Pa�<���Prd1A�PcX1A�Ph(R���Pc�!��C�Q.8|Qg�Qn���(Qi,)|4Qc�UR@Qa�U�LQp�
��XQs����dQd�Qk�RxpQr)4LGo8����Qr����Qa�Qh��A�).�Qs��t�QeX����Qst����Qr��U�Qo���{.��{.����Rd|�jRn,,��Rut'�$Ro|'=0Rr$��<Ra�RUHRp�R��TRa�Re�A�X.�Rlx1jpRdd1A�ReX1A�Rh����Rc�
���Rt�6S|�R.�R���RyxX4�k.W���RrV���Ra����Re$,��Sy�+��Sr0s��SelVU$St`Aj��.l�u<SeT�HSh���TSc�A�i��lS.d�tSy�����Sn\����Seġ��Shp!��Sg|!H�Se���,���S.p&��Ssx&��Sa�#�Ss8����Sn���Ta�v�!��T.TTs�,�� TeX�=0Ts�9p<Ta,$�$z.@�4@�.-`Tn -��lTa����xTi� ���Ts�
���Ta@-��Tl�-��TatH���Tr�1��Tt�K�o,�T.�o���Tr�o���Te\����Ttx�Ur`���Ue���Uit���$u.��y4Ug�i��@Un4Q�LUu<QXUn�5�dUe�5�pUiL9��d�. f�Uo6��Ub�[`Aj�U.l�u�UeT��Uh����Uc\���Us-�y.�,=�Un�����Uo0:Vt��!�d.��� Vn(� ,Va0���8Vi���DVn�:��PVw|D�`�.����hVk�QjtVc�Ut�VipD]
�Vw��Vs�?0s���V.Lp���Vt�'���Vs�'���Ve K��Vp�?���VadX��.�5��Wn5�Wa�4We4A$Wb�+H0Wbts[3.hs=HWn0s��TWo�j`Wt��lWs�HxWeXo�l4���W.ȢH�WeN��Wl@M���Wl.A�Wi���Wv <���Ws<l�We�����WtT��Wtx�Xi.�9AXa�Cp Xi�|x�8X.��H@Xa� ��LXiXJ=XXk��dXap���pXvd-z|Xo�����XldBJ�Xs�@��XodZ���XhK�XcK���Xe�?��*���X.�P4�Xe�2���Xr\c�Xa�b=Yw@-��r.$-R Yah+��,Yrط�8YtT���DYs8�EPYk�$��L�.X>hYeV�tYn`Aj8p.l�u�YeT��Yh8���Ych����Yst����Yi8�=�Ym�V��Ya(��w.0����Yh ����Ys0�jZi@�4�d.�n(Zn��	(Za����4ZitBR@Zr�A��LZe-�r.�,=dZn�&ApZox&�|Zt$K�ZsDv=�Zn�����.�>4�ZyHE�ZrP���ZaDZ,�Zu{�Zr����r.�����Zt���[f��[i0!�[r�A$[h�;�0[c0s��<[s,'���T[.��\[a�[i����h[dX���x[ip���x[i�����[.���[n���[a���H���[.��=�[t\7���[u\,�[tt����[it��\tP&|\s�%��\nX�j$\it���0\s���<\g����H\n0!�T\u�R`\h�!�_.�:x\d�Y�\sH����r.�&(�\lx&e�\al&:�\n�%���\o�;��\i� �\s|�t�\t����\k��,�\nx�Zn�*=]aPH� ]i�1�,]s$1��8]s��t.��P]t�Gj\]p8G��h]i�At]r�;��]c�����]s�]v�����]t|����]s\����]oXw��`�.x��]w@M���]e.A�]i���܃.���]n8&��^aD&�^i�'� ^n\',,^nD'8^a�-�D^m��P^s� ��.�P��h^d�O��t^l.j�^ad��^w�����^s�j�^f�����^i�E|D��^.dZ���^k<Q�^ch���^eh���^id�_d��_n�_e���$_h��A0_t���.���H_n��T_eDa	`_r�R��l_h�R��x_e�����_l��_d�����_n�*��|�.T"4�_d"�_rLn�_a K��_m��_ax=�G.�����_a��!�.���`n���� `a@9,`i8=8`n�AD`o,#��P`t�"��\`lЏRh`i,���H�`.�	�`iP&|�`k�����`n|e��`i����`s@�4`�.���	�`nD�=�`a���`i����`t�:d�.�4as��\�=$a.p��,ao��j8ad8���Daix8UPaa���\ak,��&Hta.�&j|aiS���ak����as�����aw|D��ao�����akh��acbn\bp�����.�>4�ay@'[�arH'���aa�#bu�p�T�.�$� bep���,bsP�8be|'=Dbn��RPbaP$���.�!��hbv�;�tbe��A�bsT���bt�=�bm��bo8����bd�bnHcr�,��T.p&��bsx&��ba�#�bs����`�.(���ceP��ch��Hcu���$crH0cs)4<cl�`����.�w��TcgXw��`ce`��lcw���xce�� �ct�����cr8�=�~.didnP��ca�%��cn���ch����cs̷���ciЮ�.����cm�jds��4�d.�>`�.�>��(dr�>��4de�>��@dt��=Lds����Xda�?�UUpd.�T=xdeh���drT���dd$,��� .�+���dr�@ �de�@���dtX����ds�a�dedaj�dcT/���A�d.teiTBLezB��et0!� ei� ��,eh��8ec(��Des@�4�.x�\en�Yheap4�y.p3���en�3���ea�� �eb���#=�e.@�4�eax��en`����ea����ei0A��es��j�ei�F���G.<E=fs$,����.�+��$fr0s��0fe�=<ft0�jHfs��Tfe����`fh�MT`�.0Mxfn�����fa4����fi�����fv����fo$gtH�.tTA�ffS=�fr�
���fo�l���fd$����frP8Ege8=gb� go���;�0g.<l8gs�s��DgtH��Pgt��\ge����hgs�tgu����gh�*x�gc�-��gat����gs�QR$z.�Q���gl<Q���glP���ge�2���gw,(�$u.4(���gt��hfxH��ho�$�����(h.��0hs�(��<hi�(��Hhl�'��Tho����`hp�Ulha�hs,�	xhe�RH�=�h.T����hatr=�ht����ho�F���y.t�=�hwL��ho��=�r.�����hnlB���hel��it\��ih��� ic��,ii�8ir����Dih8�=Pic�is4���e.ȢHlieN�xil@M���il�J���ii-�iv��A�ih�!�y.�:�idP1�is@����ib,js��`>���i.� R�ie� =jp�Aja�;� jcT����.�`��8jn��Dje4���Pjg����\je8�Ehjm@�4��.�n(�jn�nH�ja�����jiT4�jrLt���je�����jh����jt���H���j.Жj�jtt����ju�n	ko(�=khȵ��krT���$ke8�E0kk�Qj<kj�VHHkil��Tkwt���`kd$�jlkr�l��Xi.H5���kr f�ke�e���kb<����kmH����ke����\sD����kb$lop~����.�}���ke$|=�kc�{pli����lfilfll�?��?��4l.�<��<le@|��Hlc����Tl�����`l�4�|�r.�6|xlo?���ln����li� ���lt�=��.@o��lo�����lm,��&H�l.�&j�li0���lk�;E�lsT}Zn$|=ma��mi\(mfx=4mf�m�|A@ma���#\m.��dmn@|��pmap��|m�����m������m.h���maX����mi���mhN���mp����ml���me�`Aj�m.l�uneT�nh���nc���(ns(��4ni8�A@nhD��Lnp����Xno���dns�*��0�.���
|ne��=�nr4����na�j�nc�j�nn�t����n.����ng�����nnl���nu�a�nhdaj�nc��oi̊joe�� ol��,ogX5�8ol��Doa�22Poi�2��\ot����honAtoe�.��4�o.8�=�onP��oa�%��on���oh����os̷���oi� ���or����ok�P~�oa�����.4��pr$eHpe���$pl�&0pl�&j<peP5	Hpk/Tps�����.��lpn8&��xpaD&��pif��pn�e=�pn�e���pa��pm j$z.�l���pgH5���pr�LH�peVH�pb����pd`Aj�t.l�<qeT� qh�6��,qch5��8qs<lDqiT���Pqt�G����hq.��pqrȥ��|qe�Q=�qg�����qn�����qi@����qd���qo�S�A���q.j�qr_�qe����qh����qc�O��ra4���rb�rr�H ra�iغ�<r.�tDrdB��Prl0!w\ri� ��hrh0��trchY���rs(�4�rz�l��`�.H5���rr f�re�2���rb��rmll�re�#���rt`Aj�.0!��re�*�sh�r)sc0�Uss����(ss���4se<��`�.��4Lstt�=Xsr4pdsa<lpsg�H��|st�8�=�s.D&��sa��sn����snDa	�sa	���shD*j�seP*���su�G��sq�n(�`n�nHta�>�ti�>R tr1R,teP���8tb`Aj��.l��PteT�\th���htc����ttsP��ti�@��tn2���th�]��`�.�*���te�r)�te����ts�U�tsi��te,��&H�t.�&juiS��ukXw��us�Y�$uwhY��0uet���<uz�8�Hus9��Tua��� ���lu.ةjtue����uu\���uq����ui4����uh����up�=�ua���urX,�ug��!� .��	�un< ��ua$ vix
�vnL	��vi̷��(va�A4vr���o��Lv.�o��Tve��=`vtx��lvr�a��xvedaj�vn(f=�vi�e���veT����vm���ve|!H�vg�
���vlt���Ȭ.LJ��vg����vn����vuX��wg�m,winj wnDL��,wi�����Dw.����Lwnt���XweD��dwg���pwn`���|wudaj�wl�o���wi�L���we4��\K.@����wh(���wt(���wi�wt4��L�.@����wh���x.����xe|�(xi����$xrH,�0xoP,��<xeh+��Hxh�;�TxtPb��`xsda�lxtȵ��xxi�A�xel���xkD����xh�����xc�	�xi�a���xldaj�xn`Aj�xi0!��xe�R�xhDt�xc��ys���yr8�y. ��,yrdo��8ye(�=Dynlyt4���je�o��`yh4���`fht���{.\�yt$A���yf0!��ya�A�yh<��yc<���ysR��yt��l���y.D����yh�����yc�7�zit��zl\zt$A��(zf0!�4za�R@zh4i�Lzch�UXzsp���dzn4��	pzeR��|zs4���z.@����zh(���zt(���zi�wt@M���zi�J���ziXS���zv0{x(����~.��A�zd(���zr(��{iH{nTTE{i0UU${i@��u.����<{d�����r.�WT{g|W	`{n�P�l{oȥ��x{y�����{g�VH�{n�@���y.8J��{e@J���{u�����{xX>�.�-���{n���{a����{u�����{sD��$z.P�|d� �� |nXR,|aW��8|l�Y�D|ax|ihY��P|ePb��<[sdajl|t|=��|'T��4���|y�����|t`|���|i@|���|np���|�����|�0 ���|s0s	 Lp���|t8|���|s@|��}e�K��}��K��}�� t}'���p���4}eT�4D}c�}t\}HP}n0 ��l}sX" �}l��* �7��}y�;4 TD|�}s|_�}th_��}iȥ9 �����}g�H�}n�_U�}i H~'T~eh~s������}�p���~�T�4~c\}H(~n0B ��@~sPWK `~sLpB 0K \HS �~'e i,s؃�hH��p~rP<?�~e(<��~t�����~s����~u�H�~bhg����~i0\ ���~sg ��?�~d�r o��gD32n�o~ �]� |'�d�i�s����4eD�=Pe��\r0S ��ts^� b� �a���gdaj�n�^� \O� �'�s0����=�r����e0� ���s�O� ��� �U�rX���e<�i�� `2$�rP�s���,�e��� p���H�tl�� �I�\�s�I��d���I��p���5�|��,���u�����c������i������t����}�����Ā�\� $�'\��L�܀fD�vT����a�V����eԃ���l|�s��h~s�L� �Y��0�s�Y8�e�^ �]��P�t�$��X�e�$d�eP�Up�hāpH�w|D� H~'؁e��i�st��]��kx)����c<)����e��� ȵ��Ёd�� ����g�DE�nط�  39 T�s�0�r�0l�e�P��$�t�O��0�t.j<�a�o!�@��}��@��\������h�����}�0������؍��}����������}l�������������<��}��<��Ԃ�����d�|��n�x������x����Xw����x��wt|B @|��4�s�x��<���x��H��t|!@|��`�s�^��h���^��t��t|!@|����s�x������x�����t|(!@|����sT������h���̃�t|\ @|���s^����0^�����t|5!@|���sT�����h���$��t|>!@|��<�s�x��D���x��P��X������l���h��2044000142021043050041005020041503004522204204100400301052005404510310400445200243241040210221003251004002140120530210415214250323250502024035000200341000520125052200010343512012002442222325440052402301552014502050154003222022002443210241200500003423450225502400050220005022002520422300452242104300520445211050205250055100422050002500404200320432005025400051000530023430310030005010212302041410404040040423344033004444402150320400044000201001040000042004550010300002500403000451002400300100322030303001520002000034005043303400033000330025001034030105000004124020401004042245030304004000050022030200222451400540500015000100204050120051200400205220044252003050050200550300255040420302250010050020521025503023100045004050030444001202323204300405025040150051424000210002000223002340002310002330022544040323200300510540041100500425240013012004120122250440040401200324010540100030010200404014240300500410055000550030000034125002000552200502000542300054200000445430020052100400400400100400120130014440220505012004224001043010050024401044000410004300234010225405230451200234040400440200040200220302545204550402324014500345005002304041002304012143050300140001100013002101003020004030121043220025400025200025000250404500040202500000032240041221503025034000503100050330024021250102500000005320453000421404020040345140411002430001410502140322005520004040005005542341030400030300030300222123404022000220023000453000320344542103250250005432441240001004430340255212005010042200301443030404000020103014012345000014032001424501402440202051142300030020501430040210452120042125110504000501000503002520101030444005220405502105001442052000004300004000000400000005032105003210500204500020440432400432402524422055011240542123012424034404331050542025004000503040050000100430100030002050050253030423110021302204505401052122031020041200410443105050015454124230004200000100100034245410025000250022010040420441053212003212004402241220400514000
1000501000501002414000	1004030004010025520034400034300010043000343002302340412421041404005041210420005500012120040240443240222500010400420440020032000400002450412300023401022350020031203012400244405003100550432541004021400002	5005300005300005005100013023002500341053205034104000301104550343055201135003000502043003105520440002540000	500501000405000445050400212022550402524000521000523002403204004251444320500110005005520303434030300550021230450000450412444211002253030400254403140323022503000105014045230500002301000401045003500040343450001204243055001201343012030220050244301003050510022340021004144045405202050500042100505200500020423001000050521050252042245032003420450404040000550220400410233400300302405000424001401354200011430040043210140454000150002004102500551004420040241001035021230022050030540320021002404025044024414500030040001404104020420400020200020000252515000502304030025232423010034010223451252445220005021004310005201003320050420502220032230322434044232530	989888000
989888800089898988000
8889988000
88989888880008888000888880008998889980010000100988989888000980008899800	889988800
98989888008898800
888988880088898888800
88898889888008998800
8889988800	889888800988988888800
988988888880088899888800
9898899889800988899888009888998888009988800998888008889880098898889800988988899889800988898988800
988898880088988889988800200120088988889988880088988898898880098899800
9889988800989898888009898988980098988998988898800
8889998800988988009889898880098898898800988988998800988880098888800	988898800
9889889800
898898880089889888800
988899880098889800
988898898880098889889888800888989888800889888988800898998889898800
2000004000
988988880098898888800	9889988001000100
98988988988008898888988002000002
88988888008898888880088988800
889888980089888800	9889888008888008989898988988880098898898988800988008898988800898880089888998898800
898899880089889988800
898888880089880088898008888800889888988008989889988008998880088888800800880080088800	988899800889800	898898800899888889800899888889988880010000000000100	89988880089898889800898900889800	88989880088898889880088898889988800
888898889880088889888988800
8998888800
8888988800988800898898988800
898988898880089888988800
8988898800
989898889880098989888988800	989898800989888988800
989888988880098988800	888988800
8899888800888988888800
889898880088989888800	800888800898898998800
89889008008988900888008888998880088889988880090089988800
988989880088988898898800888009889988880021000003210
8988989888800988888988898800	898888800
8008888800800888988800898889888800
8008889888800898888889800
898898889880088998888800	989988800
98998888009898800
889889880088888988888998800888889888888988888880098898898880098898898889880099888988800
200000204089898800
988989898880089889800888898898800
8989898800	89888980020400204	8989988009988980099888888800
8889889800	898988800
89898888008898889888988988008898889888988988800
9898898800
9898889800989888988008898898988009889800
8988898988800898889898898008898899880098989800
9889889888800
9898888800888988988009889888988009898889988008898898888004000004088988988988800
8898898888800889889888998898008888988888009898989880090089889800
98899889988009008998899880098898888988800988988889888800	988889800
988889880098898898988988800
9889898898800989889888009889989880088989898800898980088988988800	2000002028899888888008889889888880089988898800899888988800898888898800
889989898880088998989888800889898988800	889889800
898988998880098890088800988900888800
88898888988008889888898880089800
8898898988800
8988898889800888988988800
8898989888800889888800800200020898988988800
8900888800
8989889889800898889888988800
898898898880089888889889888008988988898008989988898880089888988889880098889888800
8988889800898888988002000002089889988880089889898800200002098898899889880088898888988888800989800	8998898008898988888988888800889888889888888008890088889888888008898888980089898898800
889889889880088988998880022000000220
8899889800	98898980040004000
88889889888008989888880089898009880088898998988800	200000020	412000412
898888898880089888898880088889888800
889898889880088989888988800
8989889800
8988998889800
89898898888008898988988989888008898988008898988800
889890098880088989898889800889890098889800889898888800
21200000002128988988980088989888980088889800	888898800898988889988880089898888998888800898988889988800898989898898800
898988889880089898898889880089898898989880089898889880010000010	800889800
988888898880098888889888800
8998889988800	988888800	88890080088890089880098800
898898980089898898980022000224240288998899880010000000100
8889898800
88899888888002000020088889888898800
88889888888002000000020088989800
88988009888008898898989988998800898988898888008989888988888008988989898880089888888800	98988880088889888980088888988800
8888898800888889888800
8888988889800	888998800
88888998008988988989888008988008898988800	8009888009889898888008898988980088989889880088998800
8898888998800
888988988880089888998800
8988889888800
988888980098888898980088900888800889008898800
8888989800	989889800889888988898800898998898898880088988888988880088889889898800	889998800898888889898898800200320	899898800
8899889888800898989988002004200888988888988800
88980088008800880088998898880088998889988800988988898889888800
9889888898800
20200003028889888980088898988800	200000200
8888898888800
202000020220000302
98988888888008988989888988800898898898800455488988888988898888988898800	200000002
200000420089889888889888009100900000900090090000
90090000009009040	90090010084129000900090000000900000099000000
9900000000
990090220099009009040	9900004129900009900000	990009000990002480008000009452810080400809000	80000003090020900042090000190000200	9000090409000412	900090412900900000000
900000000099000000000080000000	900000000PK
!<w+�����hyphenation/hyph_eo.hyfHyf0tP�����'-4�������$�'–’-111001L���������.la�bPc�d�epfHg�hi�j�k,l\m�n\o0p�rDsDt�uLv̟z����Ta�]b ^c�	d\e�f�/gdi�j�k�l 'mnlo2pr(s$/t�u�v@	� 0�Ta�=b`>cP�d\e?gdiP@j�Ak0llCm�Gnxo�Lp�(r�sJt�uTa�Rd\e,Ygdi�]lTnxo�r�fs�qt�u*zT\�Tab\ebfH�gdi�j�wk�wl(,m8nxox�p�rHs��t�u
Ta\ediP@j�gl��m;nxoHs�u�����.�n��
�.����.a,eTi<o� txuD���.8wi����.8�tD���.l�\qr\��>�r�>�eD���p�Qt�
�.���aL�b��c��d@Fe��f��g��h��k\�lȘm�nؘpd�rl�s�tH*u��v�z��@��0
�.���+aL�b��c��d�$e��f��g��h�
i��j��k�lȘmИn pd�r�s�t��v�z����D
�.�
�.��c��v��,aL�b��c��d��f��g��h8iL~j�k\�l��mИn�oؘpd�rs�t��v�z��@��D
�.��cěf��tD�.�Pae :i<cr�u��X a�b�Ic��d�e�f�g��hT.i��jl.ktl�kmȞn�o�>p�rl�s$tDuXcv�z��@�ŬD2a�ji���La�@e�Si�l�DoHrDLu���
a�b,(cld�2e�f�gt)h�ilVjHk�Fl�m @n\o�p�r s�t�:u�v{z�\��K�^a�Si,"o<�u��^i���Ta<2eh"i�l`o`r�2u\�,�?e�Top��(a@>e|Ti�l�Lo�r``uUP1a�Ti��o,�3a�tiH��	h#at3e�#i�ln�`o$rȕu8Uv��l�3a�^i��o8�u�*�4i���	d
a@{elNi<ltn�4o|r�{u�vИ�5�5aTreOi,OoD$�$aDni|�o$ep%a�@e�ni(�oȕu0��	�aP%e$8i�l�o�rst�bu�d8e�8i�Fo�(`ipb�D��
XaX9efXi\k�l�m�n�9o�p�t\zuvl.�9aGepi�l��o�r�v�*��e��`�oD$t�kȞ�>�9aܓe�yi�l�o�r$e�yi��yi$�.a�e�Pir<c�?aLXi8�uXcdmi,���	�ji(lhmn��opp|r�t�v���0������r�����r�c��lti�c�za@Ee4�i�o0�uD��� a GeQix:o�r�u�lL��a�.e�Xi�o�rL;u�"�)	.X	j|	n���	a�	e�	o���,	� ��.���P	.p	n�6�
h	.��P	.�	.tJt�	.X	j�	n8��P	.�?�dE�	.
jLim,
nh
s����	a8
et
ip!jD
o�
u�0v�I���
. 
n�P�

.��
.��	.�	.
j\
n�
s8��
.(��
.�"�	.�/a�
s�"��	.H��
.0=�	.�
s�	X��
d�w���
a(d�
k���tSi�
old0b�Rc�0d1gx1k\l�HmhDn�&p�!r Ks�!t�
vl^z�+�
d���,aqiT�op�^b�^c8l�!n2t�^z�?��,a�ll�w��xo\�k���at�b|�c��d��f��g��hdi�;j��k��l��m��n�oęp̙sԙtܙv�z�� ������<��U��M�X �
d(��a\�^c��gЇk�(rsTV�
d�)\DaltiT��Vc�Ydd�o(Vs8ZtPv�um`n�,X�&�d'X�a'�r '���aLaolNb�l�m�npDr`Ns���aL�b��c��d��f��g��h��j�*lȘmИn�oؘp�5r�s�t4+uD$v�z��@���`�
a i�o8$t�	ԑbNd�Hl$amL
n(�p,4r`4z���
d,�
d8���
a�$�
n\���
o�{a�[gWk�
m|c�
dXc�
al�
xqi�
v�(���Wf�l�
s�lr���aL�b��c��d ePZf��g��h�Wiܜj�k$el��m��nDo�r�spbt��v�z����'�
d(,���aD�Fe�,l�mĂn$�r4�sL�t�G���
a -�n�(���e��-k�bn�p�rԄs��)	.X	j|	n�M��a4e@oHK	.\	.X	j�	n�c����b��i0DpTt�Dg�]��tl�%)|b�r�
�a�e�r,���Jft[i�1o�t�Ju$�m�nr0�rdw��i�c���u,E�	.j,
nh
s,��
. 
n��	.�k��	.
j\
n�
s8�	.�nhs�f��	.�m��	.�
slo}T�X8�n��
�ap����a�g1r�*�n�*
�a�*���p��m����o(1r����&a�&e�g�&i@k�&o�X�,n��
4a,
XLn��Tax�kllKj`nP'r�%tp'z��pa��b��c��d�e��f��g��h�'i��j��k�DlȘmИn�'oؘp�Kr�s�tu��v�z��@�Ř%�nD���a��'ltHYXYn�'r,Y��$a4gTr��L�bT�c��d�e��f��g��h@i��j��k\�lȘmИnؘpd�rl�s�t��v�z��@�ŘX$�n0���a��lL�
�e '���bpl�mLr��nؘ
a/Ln$/��a�+(t\�
4a��@l���4a�;eXkp_n�A��4a��o�psfxk|2l���daL�b��c,�d�e��f��g��h�"i��j��k\�lȘmИnoؘpl�s�t0=u��v�z��@��H����a\gl(l\lm��r�+�n,��$a,#e�Lil�n̟��DalP3k�(nPz��\aL�b��c��d��f��g��hdei��j��kȘmИn�oؘpUrl�s��t��v�z��@�� X$��n8�
�a8���t\�k�n�(r@�n��
a����ia��ivl$l)r��8aL�b��c��d�e��f��g��hH)i��j��k��m��nؘplrl�s�t��v�z��@�Ũ�X8�nȘ
�a�(���m�,Un�r�CXTC�nlC���a�$�m,��e�
l�G��t�$n��t�b|�c��d0e��f��g��hȚj��k��l��m��nęp̙sԙtܙv�z�� �ŜP�n�>�a�f���pna\Ub�etup�s�� ue�i�o�)u��@��`l�n�U���a\Me���l;�XR���M��(X8!t\,n��
8a�ui����idDf,kg�uk�#s�mt�)v�,�n���xa2��al�p��
4aP�u���|*e�i�k�
4a��4d�kD&t�qLnr���a(�u�W�*l�t2���uai�s�d�n��
,a��la8d��n���Ta�&`v���xa`�p�Ns�Nv\�a�Vi�?l�*m$$nxr�Nt��`7d�at8XWdt6g\�k�6l�n�vr\���a07e�ai�+o�7u0��a@p��
i��c�OiȉmlL6g�6j�6l��n$rt�sؒt��8aL�b��c��d�e��f��g��h�7i��j��k\�l(�mИn�$oؘpd�rl�s�tP�u��v�z��@���K�n�K���a�E
�rlC���bX|o��vg�mtln t�}z�ILnJ��a���v,r,�n8��Da\LIf %k�?mPnP�p�wr4bs���d,d�g��7l�nxr ��*��Da��,k�z�
�i����fPg�tl�wl�Om�rdbsW��4a�L�k0��i��l��
 e���,c\@�l�,p8r��s�(X\\nb��da\wi�jpf�xz����,e�,i�o�,�8b,Pk�n����a�8e9i�ZoD�u�����a��m���,aL�b��c��d -e��f��g��hP-i��j��k\�lȘmИn�joؘpl�s�t�u��v�z��@�Ŭ-`X �8b<9l�mPn�yt,
(�a��e��i�o�u�8�nH����a\�f�g��j̝v���xax����r\�.lГm�pp��da0�f cn8lr�/���a�,gU�� ap��,r�D:b8fhll:r�
�a�g��\t'�n '��tal�m���at�b|�c��d܊e��f��g��hQiȚj��k��m��n��p̙s��t��v�z�� ���������x(�nu��a ur���e�$htXl�nr���%aL�b��c��d0e��f��g��h�i��j��kȘmИnؘp�crl�s�t�u��v�z��@��x�n����a�r`Xp�nL�
�a��u����b�<QaX�b�r`�ci@HVln�Xk$/��(il4t��@aL�b��c��d�e��f��g��h�Qi��j��k\�lȘmИn(doؘp�lrl�s�t�u��v�z��@�Ÿn��a$J�rJ���e��t�g���'g@k��l��T n��T n�� i�,( n1��4 a���@ rl��cL f�ll�-m�-n��8Gd i�%�bt n���%)� .� j� n�%r�
� a!eX&i!o�rr������ .� n���
� .��� .0� .p&c\� .� j,!n8��� .� ���8!t�%@!nD��L!aH*X!t��\d!u8@!nH���|!a�&�!g��T^g(Si�!o�+@!n,���!a0Ke$/���Hemi�!l� �`���rd�!tD&�����!t�"n�/t�w��"a�j "k����8!t'8"n '��D"a�DP"m�"\"a�YgEllkn<s,
��8!t�I�"nJ���"a�]�"t�]���"e�Da�"lT(n�2p�"s�."nl.�"a��o�f���"klgt�(��8!t\#np��#a�$E #f��g(i�xi��<#t�iD#n$��P#a8\#l�tm�v(Mz[�HY���#tY�#n,Y���#aH)�#g(qs�P@!n����#a
xqi�#t`hu,@!n8���#a��#n
xqi$o�#tP�u8��8�f(5k$sD&�iiL|$a��ilOo�,@!n���X$a�	��d$r@p$d��rt+t|$6d,vl@!nX�r�G���#a0�$nD���$e����$t����$���$�(,��D"a\�ab��l�$mpWr�
L!a�w��Ѐo%tL�u�d@!nP���4%a��i �>c@%dXIn���!t�+d%n�+E@!n\'r���|%al�%l�jm$�r�.t8!�@!���%t|%�%n�%���%a/�%l$/���%a$��&k��@
&t0��&&l�%�&e�Vi� )(&r� 
8&a,Y���dP&g�pk�hn`>����x&d2���1a�re�>rSs�&u�,E�	.
j,
n -�	.�j�	.
j�/m\
nX?P-�&g'�8\"T\���&�$X8'r\��'a�JuP'Xp0'rL�
8'a��D'bXl\'r̟��d'a*��d'a�Kl�rs|'z�\'r����'a4�k\�'j)X8�'r��
�'a�g���'g�X�hR�'r�R���'a@(dLsr(qsst8_vP��_e(iԉX,�sm<(rT��D(a�+\'r�w��`(a�,\'r���x(a\\'r��
�(a���(fX�liD�X�(aP��(lȘ
�(u����(m������(r��'r�
)a��)g$�X�q()r�q��0)a<)t��Ee�lil��lT)r���d)a�3e4jiЂo�`u;���'g�tUm�)n�%()r�
�)a�G���)t��)n��\TVa�)e��8'a\�)b̟���)o�����*r�g��*a�$*lH��0*u�<*g�G\'r�G
T*a�u�G��`*d�$p*n�X��]�*r�]���*a`�Nn�+�*r,���*a�No�X��*rؘ
�*a�Nlvr(,���*p�g<(r�g��+a�g+n�g��+i�(+l�{n,<(rИ
D+a�n��P+n8/\+r4��h+ehR�*r�R���+a�W�+d2���+il|nb�+p����`(a�$�d�+l�7nPws�OtP'
p�+rL�
�+a����+bl,jԉrb�\��'a<(XD(4,r�/tT(��<,a�"L,n��
X,i(+X*a4+p,l���|,u@���`(ax���0*u�L���*a -Pxc�,p�xstItP-(,m�8o�	\'r�	���,al�,d�Wpؘ
�*alC��-p�|�d��f$yh�Ik-mp�t�Wz0���*aXgD-p�@`-rL��h-a�$t-v '��$�a�-od�uT<)t���-ixys�����-r(+���-a4+�-l.n�A��xPr�-u�d\'r��
�-a�{��.d�g���-a� .l,k
,.uT���9d8.g�ybcmD.n�Gs��.a�Pe��r���`(a��.l�P�^b()r�t�+d�w���.i/()r$/���.a$\'r0���.a�\Qg�.l�InT]rx]sHQt�%E�	.
j,
nD��/a8/eH/o0�	.\/c\�	.
j\
n`>�%Xpd/t�J��l/a�Dx/b$/X�>�/t?���/a`�/gl����a�/eH���/rؒX'�/t(,���/a\�/mX0rD���/o���0t���0��.X�q,0t�q��40aP-@0t���L0i�]��l/a�Rst+X|$t0tD$|0ax���0v8
�0k�	���0e��/t�w���0a(d�0k��
�0o����0a�T�0kU���0i8�/t�/��1a(ri�5TreD1o��l/a�581b�,�/t���P1a4+�m\1r���Srh1u��/tx����1a\�1p�&�/t����1a��1rl�,0tx��1a�����1tD�1s0��Зe�1o�%`�pi�D�/t�=��l/aXLe�_i�(2b�>c�Hfp?lpLmL�pd�s|�vW���0a�Ld2k0��p2i\�/m$e���2o0���2l0=�0k�<mH=o�I,0tJ���2a�He�]i�0Ed�sfpTgt�jAk�Bl$tm�Gn@`p<tr8ms�2txtvXTŘ%,0t�
 3a(+��,3t4+83l���D3uU��P1ap��\3r�h3fLqn$/`�+�3t�/t���3a�ti��u�,�tf�3nȐt�tv�L���1a u�3p@Mr8uzhR�/t�R���3a�"�3d��
4il�/tD�� 4a4�oTC�/tlC��<4a�H4m̟��T4e8�/t�
��l4a�
x4m ln�*���/aMe�4i�4p�@�j�4m`5n0�p��s��t�7@0t�*�/t�*
�4aXai@�o�d�/td,
�4ax��5d�5r�
5o�#,0t�#�45a$
dai@5t$�u$$���NfL5s��t4X�t5t���|5a�,�5vؒm'�5t(,���5a�{o�j�ak�5m���(Ie�Fi�5oX$�/td$���5a Zip$���5r�c�/t<c��6a�f6r�f��(6tH)46s�/��@6i�On,�/t�O\6aL6��h6n\X0r����6o�+�/t,���6a��
�6l���Pva�6g|i�n�@p�6�<|n�6sX �/t�\�6a��
�4a���\6at>7n`>��7e�	4>b$7c�vd��lH|m@In�lrxOt,}z���L0i�\7rT��h7o�|a�vlt7n�|s8��\6a��tD�`�7aP�Uc�7l�7s�at���/t�.�/tl.�7aH���7k�w���6a�W�/t�W��8a�Xi�W�e�Yg�l�jn8r(qs|�v$�/t0��H8ad?e�,T8lDm0]s��t�Wz�,�/m�os�8vxX0r@t5tL���8a�]��l/a��o�G��\6aԃk -$�fX�gTyk<Pm�8n`�s�t�qz�]�/t�]��9aP-�Sd�Zg9l lnXPp�osst�l���6a�b�x9a8Id�H9n��p,�r��sx�X���|ap�op9t�i�0k�i
�9i\�
�6aD���9l�.�9r�.��7d@Xi��9l�9nLv8��\6aHKe�P�/t��l4a,Y��:m�:g$6r�
,:t���8:sLctH���1a�jT:g���`:o\�0k�/mhGt\��l4a��:m�
�:i�g���:t�u��:l�atx�vT.@0tl�
�:i�(���:s��:rL���:e8;v���;i�'�/t�'
(;a���4;g�@;lp��X;f���`;e�;iT�;md|;a�;eD_l�;o�;u '�;�;s�x�;m(,�0\�;l�"�;o\'�"��<a8<e\<i|<o�_t�-��� <tL<v��<.�<n(<r��-03T.T<p@8P��h<i�p<c�3X �<.�<j�<n�>����<.�<n�D�
�<.�-���<.�>�b���<.$=�,k�T���<g�W=nؘ
=i����=p�$=m�o�K�@=n�D	.X	j|	n�=s >ĬD��T=a�=e�=i�=o>u���<�ep=l�$)	.>c\	.X	j�	n�=s�D��P	.�
	.�=s�O�f���=.H��P	.�	.�=s`>W ^@	��>�p=f�=��,>l�4>bl�	.
j,
nP��L>at>eH/o��	.PxX�,�>c����>eH^i`>`�Ri`>`0�Pa�>e�Pi`>X �>cX�g8�	.
j\�l,
nh
sH���>a?e@?i(?oxu��	.\�	.
j\
n�
sH)�	.P?s�f��	.��X,#\?g|2��d?e?X�$|?g�w���?e�vX��?g(,���?e�Ol8/|?g$/���?e�c�?t�|?g��
�?e����?v��b�?rL�t(|v����Za�Oe@~i@o��u��	.
j,
n���<@ah@e|@iH/o@F�	.,Yt@g�@�8\jT\���@�P@`���h�@j�^r<KtP@qDx�@j�Hn|JtDx�@j$e���@e 
�@lpAu@Ac�Ai0Bt�A���Ai|Ep�ArAspEt�EzTAy�stdA4Ai�q~@LAt�Az�ITAi�
dAc�spXEt���pAs�Az*~�R3T.�Ad���Ar�R��F�Ad�5���AilC"��Am�
�Ae4qi8l�DBahBe�BitBo�Bu�BrXE$Be�rx(<B.�<j\Bn�Bs���<.�n<B.�j<B.�<j�Bn�Bs8���<.(���<.P-<B.�Bs�g�f���B.H���<.�<B.�Bs�L�Ar4rt0��4Ca�BiHCutJ��	Ct,	��Ce@	��C�$(C�̟��@Cz8�	.�Cj�Cn�Cs\��TCa�Eb�Ce�Ef�Ci�Co�Cu�6��
. 
n����
.�E�	.\/c�$�	.
j\
n�
st���
.�7�	.�Cs�|��	.P��	.Џl�
s-X�,Dm���De 
$DrlCX8/<Dm$/��DDePDt��\Dah�d8^g�<Dm�|De(���Df�+�Ds,���Da u<Dm����De�'�Dh�KlL�t8
<Dm�	���DeT�Jb�Dd�"<Dm�"��Ee�<DmP���$Ee_i -Dm��@Ee��LEr�c��@Ee�
dErؘ
$Dr�$<Dm�D���EeL�
�El*��|Dede�Ez��
�Ei�A��A�Em�A
�Ee`i(�o�&Dm�l���EeX FlH��Fa�Fs�(��(Fu�4Fr!<Dm� 
LFe��XFtlHVldFn*��pFaP-�FzlC`�$�Fm,���la�Fe�Vi��o4Wu\<Dm|Jt<Dm�~���Fe���Fl��Fb�ZdPf Pl�P<Dm0�FmXInPGo�zr��
$Ee���@EexDGr$J<Dm����\Getp<Dm�ftGe�f���Gt,�8�Gr�G���Gi�Lk�d� .� j� nHs��
�Ga�GeHiDHoXHu����Gd�� .(��� .�"� .,Hs���"��$H.H��� .� .,!n8Hs0=� .8Hs�G�tl��hHd�pHn�pt '��|He�G��hHd8/�Hn��Hnps���He�_i$J�Hn`*�p*���Hd|*�Hn����He���He���I����I� -X�oIs�G�tl��8Idb���He�G��8Id$JE�Hn�xrp���dIe�pHn�*���Ie��Im�A���IoP�Ie���hHd��In�G�hQe�G���Id�%�	.<JaHJi
j,
nXJodJuD���Ia$JeH/o0�	.(��P	.T0Jsd	.�=sl�=s�	.�=sJ�JX�|Jt�]���Je\�Jb��
�Ja Xܔ�Jt�����Je��Jm��|JtD����JeP��Jl�P|Jtl.PSaKe(��KkhSp�$|JtJ`(�u�|Jt8��HKe\TKn���`Ko�]|Jt�]��xdaxKe�w��0Ke�Si,#|Jt0���Ke^�Kl ^���Ke�,�Kc����KaLe\i�X��KtL���Ke -Lv0|Jt<�� Le���,L� �l�m8L�<�|Jt�C�JtlC��dLel�Jb*��|La�
�Lz�
HKer|Jtr���Le�W�Lt0���Li�(��QaRe�QiMmRo�Qu���n�LrL�t��Jt�(
Me̟��HKeu
Meu��Ԑk4Mm<�oJ`�TMt���HKe��hMk�M��tMa��u����at�b|�c��dHKe��f��g��hȚj��k��l��m��nęp̙sԙtܙv�z�� ��T��HKe�'�Mn�]���Mi8
|Jt�	��Ne�~|JtL~
$Ne��0Nj�P<Nn$HNa(��TNtD_l�Mn(qsИ
HKe\(MzDx|Jt$e���Ne(�o8�
HKe�Vi�|Jtl�
�Ne��
�Ke,��0Ke�����Nl���HKe�FOk ln�ps�a��HKe�5 OkL{m����Je�
<Ob\�
HOid��TOl(d`Od `�]i��HKexTKn�&�Oo��`�Oe$J|Jt��b]gtwoTMt\�
0Ke��
�Nl '���Of�|Jt�
�Oep��HKe�w��0Ke���HKe��o-��dLeȃo |JtD-��LPep�X(IdPt�5��lPe��Kl�/���Oe��Pg�W�Mn���HKe�%�Pf(zl�]XxKe�ti :�Pl�m�' Pl����Po�Pbll�<Ob�Yg�Xm�mn���0KeT0Ql�`?|Jt?��PQe�G|JtT��0NjЖatQnh�r<c��ei$�Qr�m�T���QtP-�<.�Qn8Rs�m��<.�Qm�Bs�,�<.�<j\Bn�Bs -�<.0Rn�jE�<.�<j�Bn�Bs�G3�f���<.�f�DRs<���`eLRi�d�	.
j,
n�Xr���hRa�Re�Xi�Ro��	.ؚn�	.
j\
n�RXT.�Rd�mn�
�Ri@�Rd ^���Ri�F�Rd(1q�RiT.�Rd�Si�&�Rd�ps�&�RdD��8Si�.DSr�P�Rd�>\Si�Q�Rd�R`�'�Sd�g�^l(jn(�s�rt�t�Rd\�Rd�Yg�]l4rt�\Ĉ�
�Ri�����Sc4+�Sm��
�Su����Sk���tSi�d�_kTv�
�Rd�*��(Ti���4Tl���@Tk���LT�@?�Rd?��dTide�Sd�YgL`l�mn��sP-�Rd����Ti\�Tr�t�T�Rd=n�st�RXX�TdX���TiP-�Tg�et���|�e�TiP���Ri�Uc�G�� Uo��tSi\�Rd�K��DUi���PUr�C�Rd����hUi�7�Rd '���Ui�+�Um,���Ua��t�b|�c��d��f��g��hȚj��k�Ul��m��nęp̙sԙtܙv�z�� ��@)�Rd4rt��
Vixq�Rd�#
ViZtx�ut[�Rd���<Vi@HVl�Sd���(na`Vi��o�`u�R��&�VdT���Scd�Vnde�Rd4rt�
�{a�Vb�SdHZg(jn(qs8vt$Wv�'�Rd����Vi���RdD$Wi���WvL����etSi�Uc�k4�l�m����Ti�	��LWrL�
�Vi���dWbde�Rd��
|Wix�Um����Wa�Wrrt�W�Td�W���Wi�W�Rd2���Wi�Rd̟���Wi�Y�Rd,Y��XiT.8~a�Sd��m�lrps�+X�.8Xd�l�RdZgL��tSi,#\Xv0��hXe\���Ui�Q��a�c�Sd�cg�kkD_l�jnH�r�qsst0yzT(��"�Xn�X��X�Xn�R���XiP-E�Xd4rt8�	.�[d
jHYnh
s4[tH��YaXYe�YilYoxu��
.[t��	.[b\m\�	.
j�Yn�
s�[t8��
.,[tH)E�	.\l [nP?s�[t�[X,Y`�si,Y`j��jn�i)�Yg�i
�Yi:X :�Yg�#���a�YiXXP-Zg�c�� Zi�m
�ne,Zr�[`p|Ze,YX8\Zg�G��dZi�pZn�"\Zg����ZiX`��� Zi����Zr�j��b�ZdL�t�Q\Zg����Zi,�Zvt��p="�=���Zl� }�m}T��[t8�}$/?��H/<[r�[?D[o�[P[t�[��\[a�
h[g�j�����[o��[r4[?�[uY�[t,Y���[a<)?��?�	��[�Y�[dlinX���[aP-�[glC��]�l	.�]dX	j`\n0Js<��\ap\e�\i�\o>u���8\���P	.�\t0	.�]m\	.X	j�\n�=s8��P	.�\t)	.�\n�=s� ��m�T���\t8��8\�T\���\��
�\�,���\i?��]l�x�\Ġx]i�x��$]t��\��
<]i�(��H]t :�\ĠQ`]i���l]t�	�lC��+�	.
j,
n�Xr,���]a�]eH/o�$�	.�]X��]l���]i�]t���؅r�]uJ���]i�^tP��^eH)�]l��
,^i�u�,�]l�
,^i�]l̟��`^i(�o�KX�'|^l�]���^i@�]l ^���^ip'��`^iP-�]l����^i�K`̟��`^i�K�^z�"X�"�^lP���_i�_d4sn�QE�]l4rtL��(_i�]`d�]l��L_i���X_�T��d_ĤdX�d|_l�d
�_i�d���_t�'|^l4rtXeXds�_llg��_i`g)�]lLmnP���^iPl�_c\l���_i`mL�t4q�]lT.�]l�(`i�L��4`sXe`H��(`i�4�l�mT`s����^i\$�lt`rL�t
�]i$���`t0�`ntU`�`i�)�`m�vXhU�`l����^iH*�`b4�l�O�]l�Xai�ar���aa��l�7�`l�$��4ai�$@am�W�]lxq�]l���_i�pad�w��|ao�v`�7d|a�alX�o�|smtX&�]l� 
�ai���^iD���]i0���]i0�P�o�at 0���a�de�_lp��bi�I�^iH��(bc���]lD$@bil.Lbv(��XbkD�be�d�_k���|ba�j�bd�n���bo0�br0��Xai��kd4�l�m�bp8�s�]q8�bl�G����a��e�bi\��4ai��
�^i�G��cc�z�ab�if�c���li,co�
�]iL|ca��]l���dci@pch���@bi�
�cv���ck`�s�,�cn����ca�zoT�]l\��Y�cl,Y���ci�Q�]lL���ci�dv���de\ddPdl$/���]i�+8dt�w��Dda��o��u����Xddd�]ldrL���X�db�]�dr�]���da��dl�
�di����dtl�dk(�����d��X�dĔ]�dr�]��ea�
el,��Dxeei�&	�X�db4ei�]<er�]��LeaT�kXel�&�Xtei�]|er�]���ea��el�q���ei�,�e.�ej fn=���ea,fe8fo�l�en�",���e.fn�-�
f.���e.��e.��e.Lfj\fn����e.fn8���e.�9,hf.�fj�fnT��pfagego :)�fn$$patpe�fi�poD��lom�ft�B,���f.�fn�L�
�f.���f.�hf.�hf.$gj4gn����f.�fn8���f.!W,k
@go�f��Hgg�fTgn�f`gi�`,xg.�gj�gnT���gaheho�
�gn,����a��e�giH/o�h,���g.�gn�q�
�g.���g.�xg.�xg.$hj4hn����g.�gn8���g.�g��g@hnh��HhiDThl�{,�lh.�hj�hnT��tha�he�ho��,���h.�hn���
�h.���h.�lh.�lh.�hjXimin����h.�hn8���h.�h�X&in� 
ii����(it'4in '��@ia(,�[}iiHY��`it��(it�+xin�(��(it\�inb���ia��
�Ga�ie�iiDHoXHu�� .�ic�")� .jg,Hsj�`>�,Y�8\�keT\��j��i�T�� jd�(jnXqs�i�T��Djdt[Ljn���Xjildjl̟��pja\|jz�
LjnT�� jd�mt�OLjn'X�ji'�jr '���jaQ8~a(jntQ�� jd���$�p\kn8� .� j� n��
kaDkeXki!o�� .,Y�H)Pkg,k�T(��dkgT(���<g�"xkn����ki=n����ki\
<�at�e��i�ko��u,k�T���kg�kn�w���ki�$�kk�
=n�]��liT��dkg\�
li�n��,llt7���<g�7Dln(,��Pli8=n�G��hli 
=i����lp�&=n���li�+�lrP-=n4rt�(���liP-=n����liP- ln����li�=n�q`mi :=n�Q mi���,mtHg�Tg��DmgtQ��dkg�QXmn�%� .� j� n�mr�
pma�me!o�mu0� .�ic�ur$��H���ms�m�D.���mt�m�T���mtT���mt@�mn ^��niTnc�nc����l�o�mt��4nn4rtxtvP-�mn���Xni�'dnr�]��pnie�mn�'�mn�=���ni -�nb�(���ne�u�DBa�ne<oitBo(ou�m�nr�3�nE<B.Lon�nsoz�W3pb�0��ot�<B.op�BsP-�<B.�Bs�G���B.<�� .�oj�on�k
Xoa�oe�oo�6��� .� n����� .t�� .�oc�k� .� j,!n`>�lo��f���omlo��f���om�f���om�pt�os4rt�G���om�I�Pp.Dpjhpn�pr�P���<p.\pn���
Tp.��<p.p.�oc\p.Dpj�pn8��<p.$���f��f���pt��ps ���pi�F�ps�5���pi����prP-�ps����pit[�ps�f��f�� qt�ps8�ps,U��@qi��� qt�G��G��dqtT.lqs�W�ps�W���qi�f���u�f���qt�%�	.
j,
nD���qa�qeH/o0�	.�qc`>�@��q��qi�q�qc�q��re<)XH) rt�qXp�4rt|���<ri(IHrd@4rt ^��`ri�clrc�c��xra�4rtJ���ri�rt�q`��u�"4rt��
�ri��4rtD$�ril.�rv�f���rk�q`�'4rt��si�sb���(so�S
�rvT��@skP��`rideXscp��dsi�[��Y|st�74rt '���si�eX�T�stU���sip���srLA�eXsc$e���si�>�slP-4rt�(��ti�CtrlC��teȘ
�si�(����g0tm��o`tu����ri�Ttd�Q4rtL��ltiL�
si '���tb�
4rtde4rt����tiT4rt���lti�jTtd1���to����tr8�tfȘ
�ta�(��um�ur4rt̟��,ui�F4rt�5��Dui�w��PurD\uk0��T�nhuo\���si84rtT���uide�un���uiH����un��ug�
�uo�n��0tm�lrc(,���si��um���vot[4rt��� vi�q`p�o���,ui�+Dvk�&4rt)��\vi�hvr�
tva$���vg��k�vm4�oȉ
�siP����ri��4rt\�?�vi�>�vl?���va�]���ti�o4r��t�vt�`Xwi4awlc��wi@�pT.(wml.,uiH��Dwkb4rtb��\wixhwfrX�W�wt0���wi@�wp��
�wi����wc��u,���ti�o����tb 
�wi����wp�&8uz����wa�I`ri�r��xc�' xs�=��,xi�$8xb`>���wi�[��Y\xt,Y��dxi�Xpxg�R��|xi :�xd�Q�xi����xt�n��ti�L4rt0���xi��xl|j���xe�K���ti�'�xl�]��yilyb���ya*��,ui��0yzD$<yi�I��Hyv����Duil.`yr
lyktX���xi8/�yl$/���yeP'��\vip�yr����ya�P�yr�wt�W��tie4rt�,4rt���si�/��zm|%Ezg@zk�%��za�
�ri���4ztD:�|zi؍uDLzb0�d�m�p�qm�'tzt�n���a0tm`�c�zi�c���zs�c�znԎmĎ�zt\Ttd,���zoH����zl�j�zg�G���ui��l{n̟��{eȕu��c0��,{k�4{l�5�\{a�{e�5<B.p{j�{n�6���<.�<n�����<.�<B.�$��{�4+�kd�l�m�{n�*>����{l;�{k;���{i�{T�{n|s(P3t[|cL�(|v�
0|o��pW3�CP|r�|�k
(��l|mTt|s�|�0 0���|��|��|a�|i�l3X �|l�]3T.�|l�f���|iT�|E�|a}e}i}o$}u\dx��}�~e~o���{<}c�{D}nt[P}a���\}ilh}l̟��t}a�}e,~u\!�K���}i�E
�}rlC���}b�E�}m�}n�n�0�}r�
�}e�G���}t -)�(���}e�}~r\��3�$~rT88~a�
`~a�~e�~o��B.|~j�~n�/���t~.�~n�6�
�~.��t~.@F�B.\�B.|~j�~n8��t~.�D	.X	j|	n�D���~ae o�����e�~l�$	.>c\)	.X	j�	n�~���4l�<b�G��Ho -Tn���`e�~>��xl��bИ
�o(d�bL�t,�B.�j�~n8���a�e�ox�n�p,��t~.�~n��B.�E�B.|~j�~n8x�nt�pP-��k$�o\
B��D�n����L�o8B��d�nȄd�nԄ��x�o�d�n�����o,�<.�j�n$$����a�e$�o�E��n�p�D,���.�n�F�
��.���.��<.�N�<.8�jH�n����.�n8���.8P�T�n�a�\�ob��h��$$����a�e��o���n\�p�E�<.8�jH�n8����a�e��o\��n\�p�
́o�G��܁t��n\
����a�e��oH/�n\�p�%� .� j� n�
$�aL�eX�o0� .\)� .� j,!n8��$$��l�t�t�n�������t8��l�t�j��n�t8��8����t�Ăn�)��n*��܂oP-�z����i\@�m�r��t�j��nU�� �o��
,�r��n���D�o\P�d$\�o(��h�t���n�w����o\��l0����o�t�n\
��l�t�C��n�
��oD��n�t��.�j<�nx����aH�eT�o����.0�n�T�
(�.���. �.h�E�.�jh�n8���.x�x�B�&|�p����o�|�ptQ����o�Q��n�B���p���Ȅo�,BD�p�L���o��<.8�j�nx����a(�e4�o �<.h�N�<.8�jH�nx�\H�px����a(�ep�oh�E�<.8�jH�nD���qa$Je��o\E�	.
j\
n�NX���t�����o\��k�K��̅i��X�t��
�o�A����d�R�t�R���o\�tpb(�oؘ
4�t��`�1�t�5�t�5��`�o�
l�r�C�tlC����o u��m�����e�$�t '����o8��m��
̆a�(��؆g��r�[�lY��t,Y���o�r��o�r���k��
��oD�t�L��@�o�@�o���X�p(d�tL��p�o\�
(�o�����l�j�t��
(�o�/��(�o�
(�oP3��ćt,c�t��܇o���r�
�t�w���s���p�o�w����g����o�j0�d��t��H�o�
(�o�G��`�t$Jl�n����x�e\L�t�'�t�]����ol��b�N`���T�o	Ȉl,	��Ԉa@	����W����ot[��k<�t�1�t���o�&�t���(�o̟��(�oxL�z(+`p�oH/�tlOD�r�tlO�tW��oW����vt[��k@
��oȘ
��o����k�+��m\�
�a�(����o\��r�]��p�o,��(�o�[���l�t(�g�w��4�i!�t,k
L�oT(��X�g�"d�nP���p�i����o�(����d^��r ^����e�$��r��\(�oL��Ċj�Њv���t����ox(�k�z�t0��(�o8x������,�t�[tl��@�t�H�n�j��T�e�P`�m$l�aċi؋uH��x�t8�\��n��\��o�]����j :��l�gm�Ћl�x�dI�rtI���e�[�
�g,���i�����l��c8��0�dD8�n�D�o��u���P�p�tP�uWj�
t�k�D��|�i�����l���b@s ^����i�,��c�����a\̌r�Q،o�z|�c���op�����r�-8�u'X'�r��md�0�m���,D�j���L�a�JX�rl$/q,x�t�.��.��ll.��a�����k���s08��če̍t�DBa�e�BitBo$�u �r0���e -<B.��<B.�BshH��I��4�d�I<�n���H�e
T�c��eĎi�ppb����x�t���p���p��r�G��o�G����tT.��sԎt�qr�W>�P܎r�>�iD� �a4�e@�o�4�l��t�%<B.�<j\Bn0<B.\<B.�<j�BnP@X0=T�j��
\�u�T�j0��t�u�$��l�����e�+�	.
j,
n��r�$�	.�qc�jD��܏s��P@�D�X��l�����u�gX��l�'`��l�g`�4�l(+X4+H�l���P�u<@\�kP@��h�a��l�f��u�f����tT���ut[��nx�l$/��(�u��
P�u0=�l����u�j�d�(����o\�r�
P�u;���kP�(�n(,��4�u��H�l�w��L�u�X�kThX`hp�l�'X��lL�
��u@!����b�#��n�4�l�]��lN��ȑuXH�lhXD�l��u�/���uT�gP��lx����ul.P�uH��<�k�H�sؘ
T�uؘ
�u�(��l�p�x�r0���uT��p����i���P�uP-��k�c����i$/��̒r�A��P�u�$�k�w���e�=����u,	���u@	����$��uH��,�t���k4�l�m?����u���kJ��d�iP��lc��|�u���p�5����u '����b,��mИ
��a(,��ēn�>�k\��|�u�]��ȑu� 
(�u,
���t�&�n���a�4�l�m���Ԑk�@�r���L�e�X�d�lL��l�uddd��i�&�4+�-l�����u�"��k\��TCaܔe�Ci�Co�u��	.�qcP�E�	.�
s��X��mL�
�u��`$=X0=$�m�R��,�u�W8�dx���D�iXH$�m�G
\�u�G��h�d -t�n�(����e���rp����e���m��
��u��m4+��m���ԕuИ
��u8l���n��r�`�X��m��� �u��m��� �uD��m����P�uDEМr\�s0��h�o���m���m$��uH����tЖ��
����dTĖn�t����k$/���iP��a,�e8�i@�ol<B.�<j\Bn�<B.@E�|a}oh��Hq���lP�s���X�u���G��t�t��|�n�g����e�D"�]����l\��b�,�? ȗr�@ܗrL���aH)�H�����i\�gde�����i�,$�fl��0�aH��<�r�\����
\�l-��d�d -p�mP
�\�
p
H
���\�
\
�
0
D
�kl.�D
L
̟
�������(��0��<��M������8���
P\,
�
D
��P���p�H�����,�\���0�D�D�L�̟������������<��M���������\�G?�4�n�����H�z$?� �,
��d�t�>l�n?��x�ap\D�H\\�L�"�}�G��КtD
��t<c�$�r,\D\�������\\�ut�a\�e|�o�g��0�t�D�l0�<B.l�g?��%\��b�~~����lD$��I�$�L\��
�����d̛f�m �zd,
ԛa<�e��g\�r����d\�r '�̟�������(���0��H�H�s���P�u����h�m�p�r��
|�e�0?4+��p;���n�
��u8��Ĝt��D��������pH|n>�+ �bT�lh�nt�v(,��1@�m���H�o�����`�g�>P�>�$|�d0>�
��p��������d��@��kL����a�e�G��ܝn���p� �����s��kb���o���6��(�t80�l�� ���H�dx(P�l�(��\�a�h�r�]��k�v����a�7��l��r���`�gD>P���t�
�o�u>�؞bp>��f�����d�d̛f�mL�z����ax�e\�r)̟��D�i�/���X�r���`���l��0D��etX�0��l���sl.�D����k��L�b��c��d��f��g��h��j��k�lȘmИnؘpd�rl�s�t��v��@��,
0�o��\(�t120002200041031200001041412000010011202000120220001200010112200012220002304123042030000323030003003112041012120320023013230232301020001022000102220002430430122430120401344010230032041032000010210021122303232023023200121001031120041403014012302013241032003200132000322000201120000012010012010003101210002004213201430001000100011202301001220202301021020000410010200324302322033302323000232300011200410230012012033021023241023241002323011232301	120012000
12001220001200122200012212000	122122000
122122200020320001121200012122000	12122200010212000100001	102122000
1021222000243230432300010230004430320103200000320024301223001222200023301230023400143230340121500205410540132312000410320000132101302000302200030032032220001010122200032004102004102000000020000001200120000030243030010300000032412232001500001200112010400001400000141441204010401440100142012012430450221002140121000210410120112001000123200041021001210000PK
!<�?����hyphenation/hyph_es.hyfHyf0tP�����'-4�������$�'–’-111001P��������8.�axb�c@d�e�fLg�h�i<j�klhm�n|oHp<q�rHs�tfuTv�w	x|	y<z�	��.�b�cdfg h�6i(j0k�l8mDnLp(qp
r`shtxv�w�x�y(z8x��n�t�z@��tL�n�<�h�n�H�n�s�tH��s�zT�	|	�.4a�b�cdP7efg
h�7i(j0k�l8mn�:oLp(q�
r`s tduxv�w�x�y4z�l
.h9e�9i�l
.�s�z<l
.�.�b�cd?efg h(j0k�l8mDn�`oLp(qp
r`shtxv�w�x�y(z�.Lua�bcd f(g h(j0k�l0mDnXfo8p(qp
r`s@txv�w�x�y(z��LhH�l
.�.�fa�bcd�fe f(g h(j0k�l0m�n8p(qp
r`s�txv�w�x�y(z�l
.����.�bcd�fe f(g h�gi(j0k�l0mDn\io8p(q4r`s�txv�w�x�y(z��.�bcd f(g h(j0k�l0mDn8p(q4r`s�txv�w�x�y(z�.�bcd f(g h�qi(j0k�l0mDn8p(qp
r`s�txv�w�x�y(z�.�b�cdfg h�ui(j0kl8mDn�oLp(q4r`shtxv�w�x�y(z�.\5a�b�cd�efg h�ti(j0k�l8m�ntwoLp(q4r`sht,wuxv�w�x�y(z�.�wa�b�cd�wefg h(j0k�l8mDnoLp(q4r`shtxv�w�x�y(z�.�xa�bcd<e f(g h�yi(j0k
l0m�nLzo8p(qPr�s@t�uxv�w�x�y(zHl
.�i�.H�a�b�cd$nefg h(j0k�l8mDn\ioLp(q�r`shtxv�w�x�y(zh��a�n�.�a�b�cd�efg h(j0k�l8mDn�oLp(q4r`s�t�uxv�w�x�y(z��|a�|eP}i�~o�s�z�.l�a�b�cd�efg h6i(j0kl8mDn,�oLp(q rHshtxv�w�x�y4zHl
.�.�bcd f(g h(j0k�l0mDn8p(qp
r`s�txv�w�x�y(z�.��a�bcd f(g h(j0k�l0mDn8p(q4r`s�txv�w�x�y(z�.�b�cd�efg h(j0k�l8mDnLp(q4r`shtxv�w�x�y(z�.�b�cdfg h(j0k�l8mDnLp(q4r`shtxv�w�x�y(z
.����8����	�\ �8�l
.t
b|
c�
d�
f�
g�
h�
j�
k�
l�
m�
n�
p�
q�
r�
s�
t�
v�
w�
xy�
z�� ( (0�!0D84!`�x���%l
.t
blc�
dtf|g�
h�
j�
k�l�m�
n�p�
q�r�
s�t�
v�
w�
xy�
z�)8L4h.l
.bcd$f,g4h<jDkLlTm\ndp<qlrts|t�v�w�x�y<z�1�1111 1(101681D1L141`1h1x1�1�1�1.l
.bcd$f,g4h<jDkLlTm\ndp<qlrts|t�fu�v�w�x�y<z.l
.�abcd$f,g4h<jDkLlTm\ndp<qlrts|tzu�v�w�x�y<z�.l
.bcd$f,g4h<jDk�
l�
m\ndp<q�
rts|t�v�w�x�y<z�181�;�.l
.bcd$f,g4h>i<jDk�
l�
m\n@>odp<q�
rts|t�v�w�x�y<z�.l
.bcd�e$f,g4h<jDk�
l�
m\n0�odp<q�
rts|t�v�w�x�y<z�@l
.bcd$f,g4h<jDk�
l�
m\ndp<qrts|t�v�w�x�y<z�1�.l
.��abcd$f,g4h<jDk�
l�
m\n@�odp<q�
rts|t�v�w�x�y<z@D DD�D�D4DHD�I��t �c����@.�n�@s����n�N|��.�wr!,4S�- a���(cPg 7r|SL`aHo�fStsl
Ytfl.�al����.�s�h�ap>r�����l$5o,qr�t�l.l�Sf�a�of���t,�S����apohef��m����eu��x.a$�b:c@d(be(h�ii�rm�{p�mr�ss�t�L���xeg�e�f���m0�p�m����.���aTtgltl�n�o�s����.s�.�e(!�l�'���bl
|�.�`S�(o���0d�yn�hr�S̏Pof��Xl�����p.<exe��t��|���.Lda�o�����ua�ic�8d,�e�g�\n�xo�qsHl
.���.�'axac�dLexm�n�o\r�s�eu�exH���
.(d�i�o<r0s�@vX.tw��`shlebT�ilo�q�.����s�l
.0ed<et����.Hi(mTn�s��
.�aPdm�s�tl
���.X�`.l��s�o���q4.���<s �.�@��|��`. i�s�h4ahe�4iHH4m|t�@��|�.l
���.����s��e�@v�.tw���sh�o���q.���s
�8��,.Li�s�	����4����l
.8#a,eP�iXl$m�n�o�s�tH��.@'e����.�$b�#iXn�&r�s��0��.��.���Da�s8�X`.l��sxo$n�� m�
�8.���@s(+�.�f���.�m�sl
tfx.��.���Lim�s�	�����ol
 �.d���n��a����g���.(!b�-c�"d�2el.f�i�gl�m n�o� r�s� tp6u�yø���.�i�m�nhs�"�`.+t.��|s(�o46<�.H���sT`.�@����.@l8L�e@v�e l�m�ttw��nXo�sho�R���(.�sltla0eTo���(.�s�``t��d.�sl
n�|.}@v�.l
���.�@��|���.�l��ex���(.�s��a�eTo���d.�s����.�s�4aeDo����.�s����.�s�����T.�s�a\e�o���ll���T.�s���T.�sl
���.�@v�.��.�@v�.l>���s4>�o�$a0eTo���(.4s�|.�x��x@.dlx���Hs��a\e�ol
��x.�x.�@v�.@l
.l�m�n0?r�t����.8�e�����.lsltTa�eDo����l8e@m n�?r�s�t����.�s`t��.�sl
�d.@vx.l
!��.����.h�ex����.ls���a�eDo����lh�e���.�s����.s�aeXo��4.(a�4l���@d����.�s�a�eDo���hl`|e����.�s�d.,@v�.���sD�o�4����.Ts�<a�e,o@v�l��sDo����.hs����.tsl
?�L.K@v`.�L.W@v�.|���s��a�e�o@v�l|���s����.�s����.s]@v�.�.�l.����s8�e8e�0m n�o�s�t�laeXo����.�s����.`|e\i��`s�g����.s��a�e�o@v�l\i���s����. s����.,sl
q�.|@v.�.���`��8.�s@`a@o���8.ps��.���.�wc@8i�clpxm�nx�o@vsHl
.�"e< t�2uH�.�"e��x!bl'd�i(+n�oP"r�sh�uilo�l
.�/a�*d3ft3g�3t�@��|��  .� iP s�( e�@��|H .��
.�dl�nЎs@�t��.���x s�� e�q�.���� s���� .\i��� n`s�l
.,"aX6c"e�ui�(l�'m�)n� o0)sx(t�!����.�!i�!n4!sx!a�x.X�`@!.l��H!s�T!o���`!mxl!a�q�.����!s x.����.�!i�m�nhs����!a�s�	���!�4��!�<�H���!s����.Xn�s$n���.�"i m�"n�"sH����.�!it"n4!sH���`!m�D"a�"e�'m�)nh*ol)s�(td�x.�x.xT!o$n���"mn���!s�x.����.�"i�"n�"s��T!o����"m����!s@l
.�*al�m�n�*o�tl
��#.'e�#tH����$bX'd|#m�#n\$r$#s$tX�`\#.l��d#s�p#o����#.����#sd�#.  �( ���#.0$i�#s< �#eH �P �#.��x �#.� ���#s� $e��� $.� ��$$s� �� <$.� ��D$n� h�&aX&eP$o\%�!��t$.%i%n�$s(!|$a\�lx�4!�$.@!�H!�$.T!���$s`!�$ol!���$mx!�$a��!�$.�!��%s�!�$.��!��(%.�%i�%m�%nx%s�!��0%a&s�!���%��%�L%�`hp%.t|�%.����%s��%o�!+�!���%s�p%.,54���%.�&i&s�(%.�!��$&.�&n�%s���$.��$.�=E,&.��4&s @&o"��t$.'iL&m'n�&s8O@t&.L��|&sX�$.,"��t$.%i�&n�$sD"���$mP"Y�&a�&et"�$.�"�$.�"�$o�"���&m�"��%s�"�$.�"��t$.L'i'n�&s�"�$o�"��4'm�"��%s<�#.�*a�*o@m n�s�t�"4l���,�c�'d�5m�_����'.8�'e�f����'.8(slt (a�'e(o����'l8�'e����'.L(s�p`t��(.X(sl
z�0(.�@vD(.�0(.�����d(.hl(ex���(.8(s���(a�(e�(o����(lh�(e���(.L(s���(.X(s���d(.t*s�)a�(e)o���d(.�*s���d(.�)s���d(.`$)e���(.8(s�(a<)e�(o���L)l``)e�@vx).���)sD�)o������).*s�*a�)e�)o@v�)l���)sD�)o����).0*s����).<*sl
��*.�@v(*.�*.��a�eDo@vH*l\i��\*s��.��.�`���.�,r`s����.4!s�*��t$.4-rd#s�*��t$.�$s�`��*o�c�a�e4+o�`���*l�+mP+np+o�,s0,t�*o�+dtd���.�s���s�D+o�x]�x\+.x���d+s����.�+lpx|+e������+.�+slt�+a�+e�+o`t���+.,s����+.,s�@v�+.l
���+.��+.����.T,lH,m�,n�,e����+.x<,e��h,ax,e�+o����+.,sx����+.,s���+s��,o����.�,l�,m�,n@v�,e��<,eh,a�+e�+o�� .�,a-eH����.(-sl
��-.$n��-s�-.�,�<$.H-al-e�,���$.x-s-�-X-.-��`-s(-X-.�,����-a�-e�-h�-i�-o�-u.� �-o�,4a�i�-t��@8x�f��	���-�.�$.�,.�4.�8�\ \i���-a�-e�-h�-i�-o�-u.�p
.<.o�`.r��.n�/�/e�/l �.a�.e�0h�.i�.o�3t/u0/��w0p 0x��0d1l�0m 1q01sh1v1��1c�1d�1m�1n@2tfh2a�2b�2d�2l��/l�	��$/�L/�T/�\/�t/�8�\ 2m2n8��l/.��	���/��g�/ðw���/l�u"�/i�/�/i�2&�2�/o�2���/rp
,��/r�y���/gH0i�"�e0aP0eh0i@0���	��80�X0��"8�x���`0o�0�\ &�	��x0� 3h7�0a�0�":\5���0dl'?����0d�	���0�� :�8���0r�8�0aE�0l�	�	��1�<7�qI�q(1oH1�\ N�	��@1�:���T1lT\1e�fT
t1e�w|1h�\Y����1n@�1i<r`\5���1lpx�1a�"e�w���1d�h�1a,2ih:����1m�2ih�1ahe���$2ml�j�82aP2��	��80��g7t2m���\2l8n�rx|2l@0ȁ7xv����2b����-a�-e�-h�-i�-o�-u.�\�2o����2r���$3a,3e�-h43i<3o�-u.��2i�u,���x����-a�-e�-h�-i�-o�-u.ä.D3oh3l����3a�3e�-h43i�3o�3u.�h�3e�'L$5�e�y<�3e�3�3j�3���3o�3�3e�5i�3�34e 4t4�� 4ntw��4.\4s884o~@vT4.�|��4.�4sl
��x4.Tv�ti���4.�4s�i�4a�4oP}���4clv�tv�4.�w���4.�4s�w��w�4.�x �4.���5s�5e���5n�3��3��05a�385e D5t���0rc<rlP5n tx�-�<et5o�|5t,����5n�u���5e��5i6��$3a,3e�-h43i�5o�-u.�5fy<�5u�x��6c�5q����5o�:y�w6o�g��$3a,3e�-h43i<3o�-u.�
(6i�L6hf�-of��d6t�x���-a�-e�-h�-i�-o�-u.�u��|6o�.�6ixh�6l����6bDse|6o�6u���	���6���6�f���6n���|6o7i� 7d�gD3oLu��,7l�87a���D7f�7n6��$3a,3e�-h43i�5o�-u�7�<e`7i��7t��7r�	���7�.�$.�,.�4.��'��"���7aP"�7e�.D3o�ih�7l����7cH9t�'��� .X8s(9���8.8a�8s�8|8a8e���P�c08dl
��P8.�8��8d8.�8e�8��l8l�8�9�8.9���8s��l
�(A�8.��g�8.9e����8l@�8a(9e�g��g�8.�g��9s���� .<9s?��9o���49.��-o��f��T9r�w��\9oti���9a�9e�-h�9i�9o�-u�9ÈiYt9o����9c;@<�<�=�:�:���9�.�$.�,.�4.�;�:���9a,:e4:i<:oD:uT:Ø��8fa�9o@<�<�=f�:�:��L:�L/�T/�p:�x:�\ ̏f���:lL�:u����:g<r�	���:�|��;a@<e�<i<=n�=o�:�7���:i�-Y�:c(;t����:cH;d�;g�;l�;p<r6��- ;if�(4;u�"<;jd;up;y�f��\;n�4;u�?�f��|;nL�;e�;ȕf���;l�i�u���;c�g�;i�-'l����;c@�;aH�;tx(0� <t�(5P"<t����;c�(<i���4<f`<r�<t�<x�;\X<cp<z(0�B�	��x<���<��q?����<s�e�<ip
H��<rh�<b@8���<m�<n=t�\�:c,�N��<op
SP=i�=rh=b���$=m�0=i|>t�=u�Y�h<��X=rH`=e�=t�=�x���l=p�=r<a8���=r�	���=�@00��=d�h��=bf���=r@�-oH�=t����=p���-a�-e�-h�-i�-o�-u.��>o\i��4>n�����-a�>e�-h�>i�>o�>u�>� .L>ahp>r�2��p6�y���-�.��>�,.�4.�xyla���-a�>e�-h?i�>o�>u�>�xa�>a�@i����>c�]i(As��@m\i��� .D?sp
. ?om@v<?.Tvuti��P?.�?s�i�?aX?o���h?cp
.x?ilvtv�?.�w��P?.�?s�w��w�?.�A(A�?a@e@i$@o,@u<@�?���?sLfu@���?e�`i,P<Y�pnxA�[��A��4@�X@�`@�h@�p@�`X���A��<e��x@t,����@n�t���@eh�@itw�h�@o�7���@m��l
���@.?���@e��@d\ ��Ar�	��`X�A���Aa,Pe<YixAo�[uA�?��HAe�PAd�^e_g�^i�^t���
�]bD^c\^d�^l@_n\Ar`sT_v`_xt_y< �4!�At!���As(!�Aa@BodBr����Ab�BcEd�EfDFgLGh�Gj�Gl0Im�In�Jp�LrNs�NtXOuxOv�Oy�Oz��c,Bl|��4BlXBt,���PBop
�|Bi�Bo�8����tBd
��w�Bh\i���Bc������BtP7���BiClCr�-�BeDCipCo\Dr�Dt�Du\�����Br�Be�\Ct� �$Ct,���,Cr���8Ce� ����PCrxXCa�:��dCb�Cm�Cn�Cp�CrDs(Dt@tw���Cdpx�Co�CpL�`�<=�Cs
H�Cl�!��Crh)f���Cm�Du@vDt�3<:�u��0Dz�g8DiH���DDl�
.PDa�De�)���lDt�tDi$n���DdT�6���Dv�-�Dix(B� �Dt����Drd���Da<�,���Dz\�De?���Dr0Eu�"�EedEo�Ev@K�e��(Ed� U�,��<Er�,DEa�Em�En�*��PErxa)���pEc8xEeD��B\�Et����Erx�Ee\�Cr����Erl.�Ee�Ei$Fo�i3����EcFlFn3�\3�3Xf��Fr�	��0F�LpFa�Fr�Fu8Fø^� \Fr�f��dFr�d�-|FiH����Fc�Fd�Fvp
.�Fa�Fe�Fu<�m�dT�FiLw$n���FgH�f���Fpf4Gi�*��"Go���Gd�qGa���(Gs�f���Er���@GedGi<3�g��\Gj��HpGtf��xGs<�Gu�f��PCrL�Ga����Gg�g	�Ga�GeHf8Hi�Hm�Ho�Hq�HtIu�g���@npx�Xf���GmHrHo���	��u��$H��u��dHePHnxHv,Hà\:pHe��,���\Hn�w�T:8��Hi�8m�t���Hd���\Gj�����Hlf���Hi(�Hu,q�����Hrh�Hep
���HrhIbf��Im\5��dFr�$Ia\Io�Ip�Iu��xHIll��PIbxIl�Ir��cpIdh���It��x�Il����Ib,w���Ie�x�L�Ia�*���IdJg0JipJu\J���f���Ilt3Je Jrp
��8����(Jd@Jmh3����HJm�	��PJ�@�f��hJd�	��y��|J��x���Jc�JdKr�J�H�Ja8Ke�Kl�KoLrx�����Jb�-�Jip
�"�Jr"�Kc�x�Je$Kixa��i��u��KcL3<��0KglKrxKsx)�7��LKb�TKi�h`Kc�3�i��u���Kc
.�Ki��Klz���Kl@z�KiLz���Kl�Ky|	30e���Kd����KnLtP.�Ke8LiLLoȅ���,���$Lt���,Lex�0���DLbhLp�Lv�ydH`Li
�xatLh���|LcT�Le�c�|���Ll��Lo� �Lb�Le�Lm$Mr�Mt�"3"���Ln�'��Le�'������MgH���Mi�@MaTMelMi�Mo�Mu��L@Ml$n��HMg�Knh����dMm�cxMl\i���Ml�FpL�f���MgX)d���Ml�i�Mu6���Mcx(�Mi�"x@t�"���Mn�@h�Me$Ni0Nn\No�qpGt���NsD�L����8Ng@v
@Ne|Ni���LNsL:,���hNg���pNe����Nc�Nd�Nt� ���Nn� �Ne�NiOo4Or0e�,����Kn6���Ne�Nn<e�\:��En,����NrX�B��Oc����On .(Oa�-��Hrd6@Oop6��LOt�q3���dOsTlOi�:f���On|	�Ou�?\ ���On�	���O�<�Oo�O��:|���On�g�����OlpPrle�Oa�Po�PrdP��Pb�QeRp���Pm�SnXoXq4Xr|Xs����LPl|Pr�	��TP�� ��PgP"�B`B�c�Ps`e���Pl�Pr�Ps�Bt���Pr�B@v�PcL�H����PgDQvp
.�PaxQo�Qu(Q�L)���QgXQv�	��Q��Q��Q��"T<Qe�,TPQe7�cdQl\i��lQlA�d�Ql\ ���Qlf���Dj<)���Qj<�����Qj�	L�y���Q��x��TRc�Rq�RrSt�Q�L�Qa$Se|Sl�SoDR��	U�	��$R����,R��	��8R�S��-��_���\Rtf��dRe�R�<pRu�j8���Rt�	���R�"���Qj�Rn�x�Re�R�<_4���Rj�!���R�<ev�"�Rt� �8�0?�0Sr<��Sd�MgDSo\Sr�����<Sr�h���Qz�hPSelSn�B��
.tSexB@z�SvLz���Sl�St�Sz ���Sr<��w3�w���Sm�
�SaLTcUd Uf�Ug�Vh�Vj�Vl�Vm�Vr�Vs�Vt�Wv(!�4��TbpTd�Sj�Tl�Tm�Tn�Fp�Tr�$Ta�Th�Tl�To�Tr�Tu�T�"m����Dj�Tl|Pr�	��xT���g�TldQl���3� �Tt 7�
����:��
�d�0e��"�Lu��UdUadUilUo�Ur�UuTU�l'����@Ud�	��HU�tU���Xf�\ ���$n��|Unp
.�Ue���Udf���UnHr�	��y���U��f��DVnPVrXVs�U��Ua`VoxVr$V��	��	��V����V��	��V�
��0Vh 8Vc� �tf�|���Gm�SzH��p
.pVax�Hr�f���Vb �Ve(����pTd�Sz��Va�Vo��8�4�`�l��<e�Va,WeLWi�Wo�Wr�Wu0e��Wd��� Wn�Hr,���|Un�Wr6��<WetW���8��\Wn�Wr�	��dW� ��<)�-��e�f��\�����Wn����Wix�WaXoh�����WmxL�Wi�c�Wv|���Wl�<��7� Xi\(XcDXt��<LXt8��TXrL�Ia�Xe�hlXp�Xt�h:�Xa<���Xr�-����Xc�Xn(vԍ�Xz)�6���Xl(!�Xi�|���Xb��XaYiP}��dMm\�(9��Yr�8� YehYi���,Yd8Ce�Yg�YlZm�Znx]s�u���`YaxYo�x���Yn�Yu�g:����Ylf���Ya�&H�Yif���Ys�Yu�\�����YnL�Yi\5���Yg$ZnhZa@ZpP53��Lz��,ZnL4ZodZr�����PZsP.XZe�7B�7pZtP7��xZn��Ze�Zl�Zo$[r�\�ZcD[ed[f\hD\s]tL]v�u���Wn�.�ZiLB��Zp�:���Zr�.H[tf��[s�
.[u�8�0[c�w��8[nxa:���P[c�oX[e�[l\o� @����[nx[r�[sT���[a����_b�[d _eH`n�_s�@ @���[a������[m8B��[mXf���[r�g��DLb \i�aBxa$\t���,\c`L8\eh\t�|��|��T\l�\\a�7"Lmahme\mÈit\i����\c��of�\i�qt�\�7"Lma��\i����\c�	���\�p
GL�\rL����\g]r�q�\e,]o8�P	�,���$]x\����8]rx@]e�:�|��X]n�`]e�ql]tP}&��]i�q�]t����]s�>�?���]c��]e����]d��]e^l$^s���u���]g�.�]i �^r`L^tH�d��0^p��8^u�`��Fr@P^o�-����h^c�^d�cp^a�^l�"Xa$n��\Gj<eB��^t,����^n����^e6��0Djh�^i �Fi�f���^n�^axMl���_lP7�,_e�4_c�TL_i	L_i�|	l_e�_�8��	���_� g�|���_n��_a`L�_tx(`i�_s P�|���_n��_aH�_tLp���`g@v`e�iw�6�� `c���\`e@JmFr�4`i ��,���T`r�7��<3oxah`i,���t`c����`e�-�>���`a�`e�`haiaoau$a��>�`a?���`c@h�`e|���`d�-�>?�>�>�-�>��a�@a�Ha�Pa�Xa�.�>,.4. >o4��`an�laa�ao�at�:���9a�9e�-h�9i�9o�-u�9�@����-a�-e�-h�-i�-o�-u.� .�ao �ar�w\�c��gؕn��p�r��aa4beDbiLboXbulb���bn�w�j�bm��bjf$cm��	��db�L/�T/�p:�x:���q�btT����bsx�bi�b�|��bt����bs�	���b�����0r<�ba8#&\�baDce8cü��crhce�H��0c�,&Tv~ti��Lc.�cs�i�caTco���dccLtci\ ���cg�	���c�Xxitdo�cÀw��Lc.,dm�cslv�tv�c.�w��w�c.p�x���c.��ce�dt���dn� de����&8d.�dsĆ��@daІ��Pd�܆\d����hdg����d.��|���d.�ds܆�da�do����dg�do�f���d.es�@v�d.l
�tf�d.�`���-a�-e�-h�-i�-o�-u.�eoh�-o�er4��wLea|��Tec�`eo$n���3a�3e�-h�ei�>o�3u.� .xeen��aof���ec�er�<.o	�{p|>t q�,q�ei����er��ee��̏lftd6�p6��$ft4��,fuf��e��Dft|��4>nH9t< �ar�hft���tfs����-a�-e�-h�-i�-o�-u.����fof���ec�a�-oxa�ft����fc�flgm�gx�6i� �-o\5��gtxga0gi\goT���$3a,3e�-h43iTgo�-u.�L�l���-a�-e�-h�-i�-o�-u.�.�gel
���g.����gs����-a�>e�-h�>i�>o�>u�>��e�ga�8`.r����gd8hp is�3�hDhaLheThi\hohrdhuth�<��hrH,he���h�P����f���H��lh��h��h��h��h�4���\ ���u�T����ha �hi,���hm\�a�he�p�~���-a�-e�-h�-i�-o�-u.ø�ho�qittw���-a�-e�-h�-i�-o�-u.�px,io|��Pim<=>o�:��Tv.hintvs��watio�w		�jb�jcPkd$kg�km�kn�kp�lr�ls�\�ia�iejijo�ot ju8j�����in�w	�lf(ml@mn�\	�nc	�ncf	Hot�		�kn�\��,j�Tj�`j�hj�pj�8		�ld�\		\ 				ot�|jd|���jr(!�ja�joX6� �jc!���jr�7�7�jtP7���jn�-�je�38 �jt���knf��kaLku@H0kt�*��8kp�"Dka����JbT\ki\g��hkv�wtko�/8Di�/���kl h�ka�ki�i:����kc�kmh?(+2i?<���klH�kellLlr�i��u��lc
.li`8�(ls���`lc0lnP.<le�7&xaXlif��8Ng�@llu� xlr��q�lt����ls��li�:����lt(�li�i?mi����lc��li�7�@mif��mdmu�w��dFr�4ma�u�&�	��Tm�,�nH�m�mn$n��p�a��epmi��o��t��u������me�w"n�maP�cX�g`�sh�tLm	t\"�ma�netnÈ\�mi�\���mc�\�mi`n����nn���niȅtLm	�\"4na�\<ni�\��Hnc�\��Tn�Tm	\m��ln�hm	|d���na�no�\�nu�d���na�nl�no�w�nu�&X�naoe�n���	���n��&6��T1l�oi�u���Qz$oi6��0olf<oiH����-a�>e�-h�>i�>o�>u�>�p
.Toaxor�3Xqo|qu8��oa�oe�oi\ho�ordhu�o�L����or�q�oeL�r8#�x��(ps���,���lh�p��h��h��h� ��`�(	l� pa@peHpoXp��(	�(	�0	�	��Pp�lp�tp�80	\ 0	`�9	l�D	|pa�pe�po�p�x����ps]P�pe�\���pr]�pe�Z�pt�9	�9	�N	�	���p�q�q�8N	\ N	�\Z	DqsP���qn\ qi�m�a	`<quLj	\i��Pqgr	Hdqc�qtf���qmlqp@r	Lr	h�qphL�ep>r����-a�-e�-h�-i�-o�-u.�H�qoD3o����ql@>���-a�-e�-h�-i�-o�-u.��
.ro�-$rr�g�rb�rc�rd�rf�rgtrh�rm�rpsqssstsv {	f��|ru�g���rdܖs<r|�a�re�i\5���rlh���ra�3�33338	L3(3`3h3x3�tsd�0hPsmsqXstsv,���$sn83<e3��	e��`s.0etahso�6	�se�l@�m܉n�r���x��a��e�sićo��rԇu������sb�d��rH����o�su$s�	Ds���snl
�	�t.���ts	�2i�f���-a�>e�-h�>i,7l�>o�>u�>�L,ta���`an`ta�i$rr���xtc�tl(vnhwr�u���ta,3e�-h43i<3o�-u�t��ti�u��r�u�u���t�.�$.�,.�4.�А"��n�ti�u�	ur�u��ua�uui�u��(ul�4ui���@umx�	�"Xu."��`us� lue���|x.xur �s�t�	�t���ua�t�ui�t���ul�x�.@�s����uo����7r�	���u�����uÔ\��4va,3e�-h43i<3o�-u.à\vi�uwtH.�va\vo��	���Tv.tvs�	@vlv.�@�	����v.�vm�vs�@�	�@�v.p�	x���v.��ve��vt����vn��ve��	f���vr� �vu���5ȉwtf�� wl�u���-a�>e�-h�>i�>o�>u�>����8wa�\wi|��4>n4��Tv.�wsl
�	��w.�\go����wmxa$rr����wc�foH9t��3t xa�-o��wtl����-a�>e�-h�>i�>o�>u�>ä���-a�-e�-h�-i�-o�-u.ð(xo�u��Lxg��2ihdxn�|��|x.�xsN@v�x.�g���fo�g�xe����xl�xr,"���-a�>e�-hyi�>oyuy�� �xa�!Hysp6�l�y���-�.�4y�,.�4.�xy�s�m�!@y.��	���Ty.�ys�\yo���lys�	��xy��	@v�y.<exa��yt|���-a�-e�-h�-i�-o�-u.�<�yo,����yz����ye��2if��zrXx��`za,3e�-h43ipzo|zu�zäczi|��p�i@zl�|s�u0{n�zr�x�zmf�zr�u�zr�u���z��z�$.�,.�4.�8{s�{	,��	�t���zepx�zixup<q(5�7�zq�|�	�{e0�{t�*�<r ${dT{t�3�	�3��@{a�3H{ez�	@z`{iLz��h{lLt{o�|	�|�{a�{e�{i|otr|u4|ì|�{tLz���{sH��D�a�{o�r�|	 l�}rP}	~g$~l@mx|n8~z�~	\pf	�~lX~r�		�	��,|�P|�X|�d|�p|�8		�		�|n\ 		�}n		�\:Ld0~e�?�|���@.�@s��|a@v�|t����@.�|s��@.�	`��|.}el����|l�g�	�g�|.�g��}s��	���}.d}s% }a�}o�}�0}l6��D}ll

�\}.�
�p}.\ ��x}n�	���}��
x �}.����}s��}e����}n� 
��}i,q�}g~i q'
�'
�~o�'
D}~a�w'
<~aH~o|'
H�'
�P~a�-
�d~.�~a�~e,���l~r�3
H����~.�~sl
:
��~.��~.$n���~s̏~a�~e�~��B
�	���~���'
8B
xa?x���c��eP�L,rh4pI
< �Te$n��xHv .he�'����a�e�h�i�o�ulb�P.�e�o�nx��e�<?�u��,�g�j�iL���lH�m�<x$n���ex�o�?(v?T���4�nx<�iHN
d��T�p�w\�u����h�c��l�pHW
����p�	������&�c������c��à\?�y��̀nHԀi�0����a�e�h �i(�oD:ulb�@8x�\i��H9t�"7i���<�dd�n��ao X�c�'
x�b�e��f<�g�jX�l��n�p�q�zL�p�bx�c�j��r�e8�b��m�n,�sx�t���b�	��܁�L/�T/�p:�x:�|x-
����.@�s���L�a�o�-�i�xa
�x8�.�u���.d�s�h
 �\�.p
o
(!p�rp
�x!��r8���m�����rl.��i��88�Ȃm���Ђr�	��܂�H�ltf���s<�u(�����s�	����<�na�n�u���km��s4�z�gD�i���<h�a���p�z�	��|��8�q��m�bt����km ��i܃�����ȃm�	��Ѓ�"��P[c�x�e�x���rH�a�w�i�a�6�� �cx,�ih��4&�H�a���P�c�	��\��6&� t�i\5��|�th��aĄ�6���i�����t�	������?���Єr�؄i��t���������r�	����� ?����rH �aP��P"����<�r�	��D��8Di���6��\�lfl�i<t
�����z�	�����P5>o\5����n S�ao���rp
.xeexԅr|���b��cx0gi�����ml�ul
��.�i}
6��,�c�4�i�x�t��L�o@eo�e��`�d|���xs�E�����s�����a�7��|6o�w��i�&�����a�	��Ć�LІ�|��܆g��.�w��x s��el
�
\�.��� �rTv�ti��8�.h�s�it�a@�olv�
tv`�.�w��8�.��s�w��w��.�	t�c��f��l�m̈r�	��n,�s|	t�f<�rf	��r�	��,|�P|����hj�p|��		h�mT�n�
�l�3,��� �n�t��(�eh4�i�L�d�t&`�i|	H���t�yp
.|�a��e�f����e�.��ul.��l��
� Ĉr��
���؈n<e�e��th�P}���m���a�iP�����p�t:���<�m�	��D���imi���\�c�h�i�L�na��i���b,q������rh��e�g��tL`ȉp�\Љs��8���n�	�����:�:�\:����n �i\���,�d�t
���H�n�	��P��d���nü-h�u<&L��e�|&�6n�
n����n$n����i�h�aLhe(�i\hohrdhuD��<��ĊrH�eH�rf��T�d�p��0�bh�c`�rP����o��h�r��vH��4���h��h��h��h�\$�
�&�
��
�p�.��s���x�tT��il
�
H�
��.��
���.\ ����n�	��ȋ��7���oԋ���
x �.�����s��e�x���n�Y�.��ap�e�i،m�x��(�r���D�ol
�
�\�.$n��d�s��
H���|�.��s�\�.p�
x����.���e���t�����n8̌el
�"h�.4�e�8���d�8��a����dl
(A �.?��(�s�"���@�.��mp�s�.H�el
+�h�.p5x��|�.���e���t�����nx��e�3C�3��e�#ȍt8#��ԍnp
J�r��dLz���nLL�oH�R�-a�>e�-h�>i�>o�>u�>�P.$�a8,io�gT�m���`�l����3a�3e�-h43i�>o�3u.�x�e$5���.�s�|����o���eHĎt�V�܎.$,ioLz���-a�-e�-h8�i�-o�-u.�H��o|�� �pp�\i�� �p�8�8��L�.��s08T�e@8��d�d�8��8|�.�����-a�>e�-h�>i�>o�>u؏� .��ah��r��t�y���-�.���,.�4.�xy��s�>o����n��4���sH�� ��,qx�e��i,�è��8�r� `���X�r�`�a,��l�sqg��o q����n��o(�oD�����rxunАi�uB�u��Ȑo\y�lyܐol��	� �a�����t̏�aH��\�.�e�$$�l��uȋ��<�nԋ��D�����P�ü-\�i�5�-t�c���|�cȑr`��a�e4�i��ud���sH�����n� }��h�Бe�ؑt���n�	����������\�����rx�e���(�b��c��d��g��s�t��\ ��T�n�	��\���7��h�Èit�i�`�8��o���f����n���e̒i�����Ērl~��~��ؒr���o�q�t���aH�o(!�l����b�x����(�o�0�i,���<�r�|�	��T�a`L\�txh�s���t�b84�	H4��oly��m E���h�����s�g����̓lf��ԓa �u�u���n8�u�6����a��e��o0�u��x���i�hp6��0�r�*h D�d�w��L�n��p��v$sX�aȔe�iDs��l�n�x�H��a��T��e�h���t�w����s�qh�\Ԕt���ܔn	�|6���x	�\ ���x�	�����6��6��(�n�����<�tP7��D�i�-P�et�i|	Y���l�y
��-��h�����c��zf����aL��u<�
��ĕh ̕c� �=b�tx(h�r �<��2l����e@�aH$�tL>��aoaup>.<�a<=Y�:��X�n��s�-`�o���p�c�eȖn@vY�3� Y��tԖz�����n�*��a ��d(��g��t�u��ܔnP��\�i�����r�w�e�/����f�m  �a|�eԗ�x��0�nH<.��P�p�eX�o�e��d�r�w��p�u�`..��il.��r6@��iH��t\ ����p�	��ȗ��i����c��q<LI
�2���g<rHf���pp
.$�uL0�rhr�I
I
`I
�qI
xr�w��f&��a���ghh�a���`a��.��oȅ��r`.#l.̘r���Ԙf�mT��a(�e�)��e�/(A�t?���s����4�s�`��<�e@H�o�5�`�eH���h�m�t�a��e��o$n����s�h�����s\i����eLhh��p43șmx���ԙi,�o���e����l�����e`�r(hx�j�> �b��8�ap�o����̚n@�s .P�a4�i�����	:�	��|��L/�g��p������l�n�t����a�o(�u����s�*/�ؚd#� �lP7@���e����c�F���� �r�����104002101004000404400221022014024402022202140212012023202450240040004400440040040010104040040000400	400004400300040043400401043240040440040432400400040040434004043440040344004032400	400300400434000434040043004004300004343240043400400434400443440043404400
4340432400	4340004004300300040010210
4300300000400
430030030040043003000004400430030000024004300300300440043003003400430030000400
4300300004400
430030000240043003002400
4300302400430030004400430030002400	400000400
4000004400
4000002400
40030044004002400
4000000400400000044004000000240042400	400002400	403000400
4030004400
4030002400403400430400	4034324004300001054400543240054004005403400540344005404400	54000040054304005434005434400
54034324005430040054340400	54340440054340432400	54300001054340005430000	543432400	54340040010010440400	404000400	404300400
4043004400
40430024004434004432400
40430004004043000440040430002400400004000000040040000000240040000000440043004400100010	543004400121022222022000300030030000320020230303010300103403200320005000105010501250105004103203223004000240040004400	400023400
4000232400
4000234400424300000012000500000430440043204400423400423440042240042204400434240032001034403010100210430101043010104000104210402101042104300040020430010020340000043001012104302400	403023400
4030232400
40302344002002230021013400344002300000100002102300001000210230014023020	1000300102300004023000102300001030210300210
102300001023000202300120	230000010230010300001023000210	102310010230010103012023000000	10230001023100102300030000000230010023210231000102300002030020	10230021010232103000201023001030100300010	230010140
1023000210
2300000040300140101010231021023000120	230000000
2300000000	230000140
230000014023000100	230000100
230000001023000000010
23000002102300014030000210230040230020	103000010
10300000103010001030000100	300000100	230000040
1030000210	301000010
3000000010231034000034210340210	3000000103000003000000	2300002103000210303010340000203400010103000103014034000010340010230410
43000234004300023240043000234400430000003000400	430040400
430040440040300400	403002400	4030044004000000502044002122102343000222220230030003000300030030000300230030023000
3403002300	100000010340300230003030304045400030340103034021043010230400340034012400344006245440023400232400234400
230300040045010540052400450004000004000124324003234400303040030304400	303040400
303040440030030030300304003004003004400303000321010003010	1000200103024003044003020300000102000104440042324002340312031001045001034403404034030400	34030440010000000021043040400	430404400	4300044004300040043000300040043000004400
430000000440043030400	430304400
43030030004005400001000560600	4000424001400101303430140104000010100041010000000210	100040010	1040000101040010
1000400010	1000002101000041040010
1000000210100011010201000040100402102041400004100010410000410000032010212202120021210120022100210001002110PK
!<��Oc��hyphenation/hyph_et.hyfHyf0tP�����'-4�������$�'–’-1110014	��������.�
a(b�,d�4e�GfhIgPOhUilj4nk�{l�m�nd�o̬p��r�s��t@�u�v��z���Ō
�s�
��ha(h<l�n�p�r�u��taetg�h�i8jdk4lmpn�o�pdrs�t�u 	v�	�<D�a����t�>
hP��iPlx��e����4a`gX�L��LsXTa������lkD!ti %�ix���t)��l�rh-�0L %�r����t�%4�%���a�'7|6�a�4��b eHk�64l(7947��,a(<)�;��@lXrL<0��=PN��`ahI��hu�X@�P���ePO���i�XBHX�iU���d�ns�bF�bL�a�b�m�a�i�Q(g�e�f���eolgThl=�k= eDtl��(am=Lo=�m��Le|o4n��Ta�e�i�uXX�p[�p���i�pX�h�_��r�q���p�c��l�x���p�s4�g�y���ae���e�(k ~l�{��p�{��(aPe��=���Heph�m�sL�[X���hi,�o8�|kȑ�i��m|�[d�X�e�r�X�p�����p������P�=a�����a j����a<iPo|
Q�Q(�v�X(ah���0s��=`���He�;7����\k���de�i�o�e=t����p�s�hzp����uD�7P����ad����a�ot�~0����p�����p̬���a e�4�8�Xa���a�@�!,o�4i���@d\���La����Xa�e�ü�=����xa�o�p�sxAX̺)���r(E[����o[�X�i�����h�����t�0���r�����k@p���aTe`k�o�p�tTp)��8r��=X���LaX��ta��@t�la��[`�X�i�����l ��@���xA���o�X�o�t�����ei$o0spu4F��E�r���l�a����n4�X�al��Di��=��<s �@�Pa����Xb����dr�s����a����
��a ����g�u@����l�m�u��=��!�k �����b��d4�����l����	a8	s���	aX	eh������0	kC��D	e$���L	rT��$��d	e�Xl	p���x	p�	�����	��	��	� 
������	�l����	�$�8����	�������	�x����	��P=
s�X
iL��
hL
lX
rh
���%8
el��@
e��T�d��`
�dQ�
��t
l<s�
��|
aDb�d�e�f�g�hiHjdl`
m�
n4phr�s�tu,����
a8b�d@e�f�gxh�i<j�k�l�mxnXo�p�r8!sX$t�%u�&vT'ä'��8�Xi��h��P.�!�h/��d.�ll�xe�iD�� 1���a�!�o@�r�B��
��ex�$I���n��axXa�O�4O!s�0g8s@t�Y=�
i�<X�%�e�r�s���Pa�e�g�i�k
l 
t0
��|�4~=d~�X�� �h�k�l����X��^����e|!�kx������
k�
iD�������(
�@
�x����H
d�n�P
a�
e�
���
rt
e48�(��l���
�x��
a�
dit0����
m�
t��2���
mu������
ud[ 9����k@n�$ep@�8���Hb|f�j�l���Pa�t�ø�<��=8����rL %�e������������X��������$!�bi$r8!���a,e4oDtXux�4������!9#
x��#<a�$Plht����������p�X$��a�e�o��H���$!�a�s�������$�h<�4��m����������������h�%�#�'�i�'�� �(PaXehi�s�'(�)[�*-�*��`exj^�,��,�a�e�i�uÄ-��,�dxA��.�o�r0LT0!��=d3�rs�3!�3��ex���3���|3�5�� i�4��(l�4��4a�d�e�f�g�h�ikltm@n�p�s�t�6X�.�9�6?�s�8D9=�I9�.�a�i�n<�09���j L
<MOp9U$:��;�s\<Zh=?DaPeXohu�=_�=
0iP=8n�=%�>
�f8?%`.�?lD�
XB|i,DX�i�DqtFv�E�t�G�e�;��4���khIei(oDrXuTA|�K���t�J���nL� L��a|M!����0g�M8e��PN!Pd���Pd.PO��li�k�ndp��Q�rhQ
�a���lR�uU��
P.�ahk8l\m|n�p�s0v�T��n�V��S
8Z��u$^�e(l�^��`��_��0mHua
xaP.LaPelp�a9���at.�f�i�sX���b��bL�e�b�m�c��etc��ec�e�r�f%�f���atl��lh!�n8hi r�hTk
�j(alXa�o�u�D�k=P.tl�m ����le�o�u0�L��l�hm(����m���t4n
a8eDi`k�l�r�stuhvo=�n���st�m�as o��p
/�Tq��$.�p,n�q!@r��qLs�r�Tipr�r��s9�r%xs�u�r��a�i�~�4t�t�il�9@w�i$v��i�o�w!x������l�x%�e�?�x.<ml����(p�y�0a|�����HiLz��Pl4z\a�}�}��td�{|l�m�{��
�a eXg|i�l0o<sDtLu�À���}�s�}�a�|'�hT'����ȀԀ��e�l0t��Z<t��T�J����Dt�LapeK�����hd��!�s�f����n�����a�tH�8h�e�&�D�.��e�h���eu�X�����T�!j��?|�������(b����.�t.`dhnP3�����=���ph�t����x���P.�aei o<plØ8���.�a�=P�D�d4�C���n\��t��9h�t`����(l̒%0aXe��.�Lr��J����d����.�adPedi�j�k�n�o�s t��P���������à��l�t<�X�T�m4��oܖ�o�.�,�s���a0eDo�.�oX����<g��a\hp9�t�!xslg�p���poT+f�Dk��%�r�u�x�����h��aP���s��<�oܛ�m��%�at �vp@v��np�%eDi�`}�0��0oȜ!8l<��d���Pd�h�j�l�m�nop<rds�tP
�����e�t���(�=��%8����e�uУ%<�X�a0�qh�
�����u�=t���p�%0r|���e�B��(a�XPa�Hs�������\atu�
���8���|s̬�a�ilo0phr��ĭ?�a�t�v���r�&����a��� OD�!,�q��a���|��4�l��$aHi���D�@nXs��<�!��~`i�o�u��D`�|n����D������\���ks�����a,edhpi�k�m o  sL t` u� v`���k�.�з��e���p@��g�� nDt�%��<u<Q��PPtL0Xi̻a�aLV
��|l�pWc��f�eq����l��������t0��s��a�e��D�.P��e@��ؾ l��d�% t�r�D�, k��!4 k�%@ i|��T�X a������l mX�t a� e� u� ����!� sdEf� u���� t$�!� s�Ih��t���� �D�D� ����� �P����!s����!a��!lx!m�!u���$!a�!e,"f8"i�"j�"k#l#o\#p�#s�#t$u�<�Zp!e�%��?
�!.�?�!aX��!m�!n�!o�!s"t�����B������!s|��\����!e��v���!t"u�%�l�����"���� "���d.d"a�"n�"pDV.LV��P"a��X"lt"n�V!b�,�|"dp��xL�=�"n,��"a�z��"l�z���"�X��"�`���|�`|���"v�{�"a���"a(d���#k4#l@#px�q`�,#e��=��4�H#t �P#ep#lx#o���T�#r�9X�����#n@��#a�#t��0��@��#i�#m�#r\�����#e�����#k���#a���\�$b,$d4$iD$sP$u�����jv����<$s$�8���$a�$e�$i%m0%rp%s�%t�%u��!�$Ř'��'���$��6X\m���$e@��$e�����$j��$l0�=�����$v��!�$i�=��$k��%i����%o@%s|� %oP%u������H%s��\%ol�d%o�%px����T0��%s��(��X�%l@����%a�%b&d &i(&j8&kP&lx&p�&r�&s�&t��4�'��P.|6�%a���&e��P.�!0�l�p�l�0&e��"\&s ���D&u�<�3����d&��l&Ô�������&gt�TP����&o�&u��%�&l��90�&�&o�&s���&o�&r�(��P�T�'a@'e��=$'n0'r��$���'s������$�!8'h����t'�L'�l'�|'�x�l�Lt�
\��'i����'n����'��'����
8X(g`(kh(s�
���'ap(bx(e�(g�(h�(j�(k�(p)r,)sp)t���'a|)b�)d�)e�*f�*h�*iT+j\+kd+l�+m�+n�+o�+p�+r,s ,t<,u�,v�=�=�=8�@��x���e���P.<�(u����(r�(s�(v�,hT��(l�(rh1�������(����)���=$!)s8!�� )aH)k\)pLu
�"@)oP#
\#T)e0%TX$h)r(
�,%�4-�4���)a�)b�)f�)g�)l�)r@*sx*t|649�9��?3�?���)�h=���)üB7�B�)a*k*o,*Ø���CB��3���$*��D
,D��8*iP*tdE�d*i�����!\*s4FT�Ep*r�G
PO
�*a4O;�T
X"l�*vU���*a�*e�*n+p0+sX=�X?�*np@
�D����*��a�*���e�+a�e+l�f�f��(+aD+iL+k`g
X�Tl4n�{t+e�+o��+t��K��?��+n�%�%<������+td����+r̬%���+i,o@�P̻�+g��ؾ�+s�%,o��!��9x�l�(,s@���0,kh,lt,m�,s�������T,� ���\,���Z���P���|,e�,i�,k0�����h��,r���
P-a�-dx(e�-f�-i�-l�-m�-n.o0.r�.s���,a�.b�)d�.e<0fD0gL0hT0iT+j�1k�1l2mL2nT2o�2p�2r<3s\3td3u�3v�3�T4Ō
�`-bp-lDVP9d��h-aT2
�|-o��|[����-n �h����-e�-u���L�-m�-s���[�vx���-n.u���-mX8����.t���$.aP.b`.dl.et.u|.v�vh�
,��X.e,` �� `�.�!f�!�.n8!���.e�.u$�-m(m�4�.a/d/eD/hP/kh/l�/p0r�4��.a/v�4qd6w�6��{�6��/.0/ld7
47��(/e�9�p9��</e�;��@l`/s\<�h=���!.�/a�/e�/uP=�u>��=�/k8��8?%�/v��c�/eXB�/rLE�XE�/i(����/s��/sCB0l$0p�B�/e,0o40uT������C�DB�G%hI0POU
�0a�0d�0g�0i�0l1n 1o@1ph1s�1t�T
�*vHX��Y�[=|Z���0g�0n�0s8\BH\�0a�L
4]���0a�0u�h
�`v�_���0m�b��a1ie%�c��1s0���e�,1o�e41l4�
�fL1ix1r�1s�f��T1a�1p�f������c�g�1rpiTi%�1r4n)�1o|�Lu�1p�{�1e2ulG=��1v,������1t��1s�,2e@2i���\�$2eԐ=h�82k�d�h2hx2i�2s��=f=���p2r��?�2a�2k���L��̬)����2a3e��`��2.l��2a����2m\��2a�2oP��7vĹ��3n��3e0l(3np@?43a�����P3a����H3r���@�|3b�3e�3k�3s|6= �x�l��3sP���3a�3e�3k4�������3eh����?4l���"��3�4�`�=l{(4iL��4l04r@4�<
��T�d��84������d4�L4���x4e�?���p4m�
�<5j<s�
���4aD5bX5eh5ht5iHj|5k�5l�5m�5n.o�5p6r6s\6ud6v���4a|6b�6d�6e9f9gp9h$:i�;j�;kh=l�?mp@nxAoXBp�Br,Ds�EtGulGv�GðG�H8XP5ihD@l%x��`5i���(s�(v�%�.����5a�5uL�%�5t��5e����x���5n�5u��
�%��5a�6e,
8!X,6s<6tP6u�#fH���#46e���$H6t�%!�&Xp6a'=�.(�,�6a�,|��5���6i�4���6l�4���6a�6e7f7i7k47l�7n�7p48r�8s�8t�6���s�4
9�6e$:L<T�;��7rP=%H7rh=��(7ad7ex7u4~v4>�>��P7t�=X7kh��8?%p7u������7i�7l�
mp@���7a�7o�7t��P�
TA9�����7iXB�7a�7i8oi�D��7t����8s�B��B8lD8p�B$8aT8od������CL8sd8t8��,���8e�Dl8n�8s,D��x8i�8kx�db��������8tf&��8r�D�8i����F��8m�F��9��8��E�8�H���GhI09a\9oI�$I��(9aH9s�#TXJ
@9t��|M��T9i4O(PO��h9a�9e�9i�9t�9�P.�P%�9n�9s�9t�aQ�P���9t<Q�9i����R��R
�9a�T�T���9� ��d3:lHX:uU��:dX:e�:g�:h�:l�:m�:n;r4;sx;t�;v�Xl:r$Y[,Yd:i�Y%�.�Yx:a4O
8Z���:a�e�_��0m�:ua%�?�xa�:mLa�:e�dba�:.�a�:e��ؾ;of��;o��f ;.�f��(;aX;i�v����D;t`gL;s���4id;ui%l;a�;�������k��P.�j�;al4�m!�;h4n��	�;a<e<k(<l<<oL<r\<s=t0=�l��o���;i�pDp��m<p�r�<a�r�4<o�t
Lu��v!$v�D<e�ix�t.�<a�<e�<i�<k�<p�<tP$<�Z�<.(x�<m@xp4mLx4Xx�<kt�,px�<a�x)�x�<r�x3�x�<rl�����<l����=��x%=�H{D�z��(=��|:`|��<=v�{D=a�=g�=m�=n�{��	P=a�=e�>i�>k�>o?s?t8?u�?�Dv��=r�}L�=uT��~
�=i���~@�=k4;$:���=s��=i>kH>m�>v�������>s4>t��F��O >o��%(>r\���@>e�V��T>o����\>txG�h>alGt>a��!�>d�>i���]TX����>t�y��x�>sx��>u����>b�>p|�=��T���>t|���%?r,?�������$?��%�.T?b\?id?nl?s|6[l�`��f����.|?s@�v�!.��c����?h�����?���?a�?n@o$@pD@u��.x���?d���?n�?tX$[���X���?e��`�@s�1̒%@r��h����0@sT�8@l��r��P@s���X@u���d@d�@eAk4AsTAtP�o0��@s@����@a���@l�@m�@r�@s�?[p�Cx��@g\D���
�@e�@lEv�rw��%AlAr�DTЛ�ܛ Al��%(Aad���@Asp�%HAolA�\��l���dA�d����a�Aj�Al�Amo�ApBsLBt�k�(��Aa���8����Ae�u0�%<����Aa��{|����Al��%�.Bk$Bl0Bv�����Aa8Bfة����Hi<�������.8���@Bs̬dBl�tBi|Bu��(H�
��
�BaCeDCgXCi�Cn�Co�Cp�Cs�CtDu�D\��Ba�Bk�BlCo��&Lu�Bn��Bo<�����BdP��,����Ci(Ck8����PN!0CtL�8Cu8Z̻PChpCudG
t�8hCu����|Cu���Ca��ؾ�Cr,���CrD��d�%L+k��a�Ck�%�CaDe���l����CsT�`dX���Dn���
 Da\De�Di�DkEl(Eo@EpXEsdEt�EuX�d.pDkxDn0�8��8�_8���Dlp=t���Dj�DsX��Da�De�Dl�Dr�DÄp=����Dl��8$�)$v��z��z���D�`|��{Ea��Ea��4#l8Er��
 �x#o��L;s@�LEi@�fxE��D���pE�\�!�Epl�����Ea�Ee Fi4FrTFstFt�%u�F��p@?Fd��En��P@r�E.X@�Esd@��Eu�=����Fs��!Fi��%|�,Fa�����@Fnl�HFid%ox�D��j0�hFa�Fe�Fr�F�X����|��Fu����9��F����<������Fd�����F��F������Fm��
�����Fe@���Gd0Gj8Gl@GnHGpPGsdGu0� ���w�=P�=\Gkh����!�xGa������t'�L'��G��G�x�;LB�����G��
8Hn�
���GaHd���Ga<0f Hi8Hj@HkHHlL2n�Ho�Hr�Hs�HtIu�
���U,Hi|Z���>tl
4n
�{THo�%tHo�}���`Ha��hHrd��Hl�Hrx��8����Hkؾ
����Ho���He��%�He�8v���Hs�%��IaX$����Ht@�!�
D�Ir�Is�
��Ia�Ib�Ie�(g�IhJi Jk�-m(Jo0Jp@JrXJs�Jt�Ju�Jv��$Ia�Jb�Jd�Je�*fLgL0h LiT+j\+kMl4Mm<Mn|Mo�Mp�Mr4Ns<NtPNu�3vO�h=DO����It8��@���Idx?`5i���0gJs�[�-X4�4L ����8JtPJv� v8!
lJu��$dJk�$3X$xJah)r�%'=P.�Jn�&�Ja$'�(��,9�4�4���JaKdKkTKl�Kn�Ko�Krd3P.�6�Ju�; Kl0Ks(<{�<�\<�(Ke}�P=<KhlKsh=��DKatKe�Ku�K�d~=�=�h8?!4���?���K�������Knp@���Kah�xA���Kk�Kr�!s�
DCv�B�Kg�KuDH%shI
V��T
LjU��	LaTLi`LkpLn�Lo�Lp�Ls�1tMv4]=|Z��LLs$^4X�v�ahLf�Ltp��e=�c���Lr�e�e�Ll�f�n�f���La�Li�Lø_�`g�Ll����h���L�k���j�j�La�{(MÈ���t����M���`Me�����HMalMe��PMt�Ec���d���tMh�Ml�Mm�Mp��
8����Me0�
<����Ma���|����Mo̬����Ma Nu<�?Na\��MnNvH���Na�PT�%,Np������HNr|��@�!xNb�3e�Ni�Nn�No�Np�Ns�Nu|6��Q@�����Nl�����Ne�����������NnP����Ne�Nä�������N�0�=�����Nj�����"�O�(O�l=L�� Ol�
!�Oa�Ol�Or�Osp)t��4OaPe�Ph�PiHQjhQk Rl@RmlRn�Ro�Rp�Rr�Rt�SuTv�T�p�h���Oi�
���Orh(s }��{�Oi��Oa����Ol�8$!B8!���Oa�4(PeHPi\PkhPlx*t�?=�6�� Pm8Ps�8=4;=$:��@Ps4zT�;��TPvh=��/.xPs?�PO��PehP=P�PlU�PaTLi�Dl�Pm�Po�Ps<Qt�T�La[�c��f�nx1r�f���PaQeQt(Q�g=(gQl8h��h��h�� Q��i�i%4Qsl\Qa8l��k=TQa4n
	�QaTi�Ql�Qo�Dr�Qs�Qt�QuR�0p��m�Ql�Qs�p��r)Lu�x��x9h�v�yD�Qk�Qu�x�Qs���z��z��R�R�{��{4Re�=�,Rn��PRo`R�`���������XR���Ri`^���xRat��Rkd��Rm|�<����Ro̬�d�9�����Rs��
�RaHSetSi�So�Sr�Ss�S�%HjSkSl$Sr4Ss��[��Th�[$
����,Su9��@Sg\SndSrp@=8��HX���!lSd4��|��l�w������9��S�@��Sa�3e�Si�Sk�NpTs��4�=����Sd�rl��Sl4��P���Ta�8TadTepTi|To�Tu������0TdPTs������HTth==$�!\Tl�!1n��
�=����Ts������T��T��T��T�x�Dl��LD�

|Ua�UbX5e�Ug�UhViVj,VkLVl�Vm�VnWp<WrlWs�Wt�WuXv���Ta,XbHXd�Xe9f�Yg8Zh|Zi^j$^k�_lLam�an�co�epfr�fsit�iu�jv�k��kŌ
��Ul�Usd=�Ua�}�~���UeP%�UnDO����Uth�8�Ui�Uo�+
�XxXdb�|�Ue���Vn<���$Vr<Vt������DVadVbtVl�Vu���lViL�%�Vt��Vet����V.�%x���Va�Vd�Ve�VnT+���Vj�
mP�8����Ve�~ Wa,Wr���Wrh{,%���4WeTWm\WudWv��` 
� �8!X�Wk�Wt�Wu����"�We�H��W.�#�We$q�$��$�WlX$�Weh)r�%�WlP&=<�'=�Wj Xl�&�Wa�������Xd(8Xa�'��H
d�!u�,pXe�Xi�Xl�.��.�\Xa�.dXa�0T0|Xi2B�1�Xu�4�	�6a�Xe7i�Xk�XlYo,YrDYs�Yt47��6���Xl\<�;���Xsh=��DaYu8?�BDxA��YsXC
�B$Yi<Ys�C9,D���i�o\YsdYtXE�dE��E��ElYn�EtYe�Yu��=�%(�YshI�YaZr�(	$I���Yg�Yr�Yt�Yv@J8$�}p%�Yu�J�Ys�J����MZm�MZa4R
 R$ZePO��,ZlPZtHS��R
HZehZil��tS!`Zn�T�U��tZa�Zd�Ze[g[h,[k�[l\mH\n�p]r4]s�]t�]vpX�oHX�Ze�Ju�X7�ZsDY
I��Y���Za[s�Y�ZaXJ,8Z���:a`^B$^$[aTiP[rd[sp[t�
H_�H[o�xTd_�\[t�_9�_�Ib�[d�e�[s�_��x[a�[e�[��|��#v�_=�[t��h�[l�[sԀ�d�l��@a���[��[�4a����7i�HtLa\a,\e��xa$\t���7i|Cu�a8\at\d�f�\i�\k�\o�\�8b�b�l\e�b!�\m�\tc&�b��\n�bL�\ei9��9P�B�\l�\s8�����������\�<��f�\n ]uf���\a�Rs(]à��������f���LaH]el]i(gQlX]vlG���t.`g`]k4i%i%x]a�]e�]o�]r�]�Ti�h4��]m@As<��8�[pi�]e�������F���lk!�]s^t�j�]e^o�E���%l4n`^a�^e�^l_oH_rd_s�_t�_u$p�0p��L^u�mT^lt^r�^udp��l.e�^k��&=�%���^t8?q���^u�p�^l�^s�q��8*i,s9�r%�^axs�r��^a�^e_u�s�r�t�t_bLu�1p0_s8_vvv��vq$v�@_e�o(x$rx�X_ax_k�_ppxT�x�Mo4F�x%�_r�x!�Sa�{�_s�{��
�_a`i4`kd`l�`m�`o�`p�`tau@a�d~=�_i�~%������_a`s\r�����`i�m�nx�(`aD`ðz��R�d�Q�P`s��X`et`o �BІ�W.��|`e�`o�`u`�d8tԓ�T��`tH����`gt�)P��`l<�
�%�`i��8��`nas���Ta(at�+,� au���ph����4a��xae�ai�ao�ap8?�h=��dau\�lalh�0g�as�h;�X�au8��`��al����ar̒%�ae�bddbe�bi<cn�cs�cv`��,��al���ba8beXbr`/3P/��$bs�.,bkHbr0=3
�2�Pbe��axbe\h�bk���8Ps\<��;���bsH{��z���b����b�t��bk�bmcn�L�ba�becl�����bs0��cn�@Fp@��ceP�hX�1iH�;�(ca�0ca`cid�
p���LciD�Tcs��v�lcn��%tce�cp�ct �� ��$�!n�3�ce|�
<���ced����cddh�j(dk@dl�dm�do�dperes\et��=�tdv��x��!.h���ds��%Tdk8���4de\duxd�Ԣ9У%hdk��������pd�0�%�dn<����dax�`�
<���dat���dd�dnԤ
|����de�di��|�X�de���dr������ek����\a8Bf8ep@ettu��TЪT r���.lei8���Les|eÔ�������te�̬�el�er�es��a|Bu`�
��~�eo���1s���ea�ee�euX�B�ep$�����\��es\�Hbs����faDfdPfndfuh1�T00fs,��8fi���\��?T�\fitfu@��&=t��|fv���fa�fj�fn�fr���	�fa(ge`gilgo�gphs8ht�hu�h�X��d��fa��X����fa����6eghdv�>�l���goX�gl<gmLgt�?TFT��Dgs0�=��Xgg��%�gl�gm�gr�gt �?`��ga�gi����A��A���ga��=�D��F4��gk ��gep#l�go�F���gr��@�hp�������hdx� hn@�,halhi�#m�hr�hs�hu�[�����Xhl��!`hi��
��xhu��
�he���he�p�Ll��hu�!�ht\�!����8m�����h��h� �:����hlirp�=��%4iaTiepir�is�itH����,ia��=l�D@il�Hie���#k�is|�`ia�iu�9X��l��ik�io�
X�T0��ie�%s@���|3bjdjg$jj@jl`jm�jn�jp�js�jtdGu��%�����ia��
t�ji0�X0juH�D�� ���8ja@J���Ljr���Tjatje|jo4��`�%��
�����ji�
��juP����a�js�ju@��!.��%��;���ji�kalke�ki�kü���H
d$kkDklXkndku���kt��L0ks����8ku`��$���Pkt��$�!8'h\Tl�[@|Z��|kl�!�kiL�����k�����k�L'�|'�l�B�����k��
=
8lap(b0Tdhle�(g�li�lk�lm�lo�lp�lsmt@mu���ka\mehmo�mu�mÌ
DPljXll�Irh(sHd=�@��`llxlr�B=��`vX���la��lr����la�le��=X���lh�-8!���Wkmt����#�li�#�liX$h)r,ms$�
p%$mu�%=�%��8mb�Wlh=���Ku�4��Pmld�!|mo��=t��tmk@����3e�mm�ms�������ma������mi�mlP����me��=�8����m��m�l�8�
�na@odLoe�(g�oh�oipj0plDppdpr�psp)t���ma�Jb�Jd�pe�qfLg�qh�qiT+j�rk�rl4MmunLuo�2p$vrxs�xt�xu4zv�zà{Ŭ
��na�ne�
���nd�nj�nl�nros otx!Hwd
Ph���na���8"�ns���oi�
��oa�]�,or�4oe@���Id`ogpol�=h!?houHPQP|oix���oe�d����oa�on�os�ot|j�����ot����i�oei%�os�&�X=�ov<pa�(u�Lps���$puc�<plTprh��v���\pm"��!ppt8!��xpe�pj�"��4	�pe�ph�bkqlHqmTqnlqo�qs�qt47
�pu�6���plx7!�9pp9���pi\}�P=�pj$qkh=��qa4qoh}�����>,qk؎v�?@qbp@��/.dqe�@�xA��D��qa,D��tqk�Dv,F[4F�qa�E�qr�qt�F{tF�qe�G9PO�Urerk rn,ro�p@rs�1tpru�XH_T$^rr�ahLf�c�f�n�f��4ra\ri�t���`gTrkj=�i��hrd�rs�j!�j���ru4n��rr�Qs�ru`vq$v��ra�
�����ro�x�rr�{��ra�seti�to�tu�{%	P.,sa8siHsmTsndsp�ss�st�sv�|v`|��$ss }D؎��}@sb~D0� ~\sp��}�#psid~��xssX$��@'!�sr�se���sa�se�si�sm��$:?�stx;��,X�$tu���sb,tg4tiltkttn<,D��X�?Dtm`ts\�H��8hLte4]��Ttt����tk����%�to�tt<�9���tm���8����ts|6L��tb�tsut����.�tau�4�����D����t�X���,ua@uiH����$ua�e�t�8upd�tuk�ul�uo�urvsvvx�h���lus8�
�ue�����un��
8����uat���us�X����ue����ui�upD��,��ui��%�!.�����uaH�
$�va���`va�ve@wi�wo�wu�wè
��va���Dvn�vv\�Pva�vb�ve�vi�vm�vn�vu�
}���8@FȶD<���vp<����D�0&T0�vi�6�vi���vdwe wg(ws0wt��Ĺ��wm����[��̻8wppws���,hPwl8hXwa ���dwt����wvؾ|wh�wo���������wk�yT��wt�wu�wv@�9�?8��wi��J����w���(xa@xeXxipxk�xp�xt��X�� xei0�X�8xk��X$[a��Lxk�\t$�X�hxl�xÜz8�z���x�0�[���xo ��xl�xr������@��xrxE���%4Fr@�	yi<yjdyk|yl�ym�ys�ytzu(zŠ�=���ys���H���$yjHym0�,yuX���r!�r�Pyal�Xyl(,s@�� ���tyd�ypP�`�h��X�yuP��ys����yi�ym8��ȑ�yiP�D�yi0�0��yv���&r�����jnzs����|,e������� z��Lza�zi$������Dzndzr|����
\zt��+�]%pzu|Z��xzt�!�zi���zd�zi�zp���{��z��z�H{��.P.<��ze��{�zs0���x��{d�=l��,{i8{k@{m��Q`���${s��{P�L�`{ht{l���XX{e��l��l{e�{l����{.�����{��{����
`|a�|d�|e}h }i\}jh}k�}l�}m~n ~p4~rd~s�~uv���{ab<d�e��f�gL0h��i@�jx�k��l��mL2n�oP�p��r��s�t�up�v������
��X|g||j�|r�|s�|vH�hX�|aP��
����|a�&j�|a'�T+v��|j�|u��@��xlr�|vlGTvx���|v�X�X�}k���}e8}g�Y
�Y0}a�l�X=D}p�ov<L}a��(r�(v�P.���x}a�}d�}e�}s�}u<� �[s<L�-s���}a�}p�}s<	(�v��Pa�}sx���}e~id@��(l� ���,~vL~ô�D���D~�$!�1s8!��X~a�~b�~e�~i�~uH��!�mi�~m�.n�!�8"a���$�~t�%D�~d�~n�~s&��=�&[�&���~uL0v�&�~h(a0l�'���!u�+
d+(o�,�ha�i�o�È��-��Ta�,\l8h�h1��ttT0|s���T2�n4��3�����4�a�eX�hx�j��kԀl�m�n$�oX�pd�s��t�44�6D�k,�l@�p(<w7���ld747��$�e��`�78�r�9!h�tp9��L�ip�t�9"�9)�;�<��;����k��s��t\<��V.4FO��i=%��rp���=��kh=��Ȁe>��?�W.�?�a�o���@�rp@��,uaxA�4�hD�l��!���A��<�udBcXBP�l,DX|�e���df\Dt�.��D��������E��t�F/tF��e�G�ԁo�tt���H́o�HThI�a��e��i��o��u�I�$I���e(�i8�lD�nX�sJ��v���0�lx���-n�!�XJ
P�eh�k�"TtKTK��p�e�J��x�lHX� L����d|M%0��PN!��jU
��a�dD�gX�i��kԃl�m�nt�o��p��s�t4���T
X"l�m�*v�V�HX,�a�-��,$�n�Y��P.�Y8�atZ6|Z��P�ap�ex�g��n�Z[�8\
H\��a$^�!.��aȃlP[r�o`^��i�^��^���a�_���0m�ua�a�s�a=�a��sLa�i�ahLf<�iH�sX�td�v�b]�b4�m�c����p�%P�e�cv(d=�c��l�k�e<�e��l�f<Kh�f����a\riĄo؄u��Ìg@lg%��l����h!Єj,��h��l�h�����L�i%�1r�u��[G�k �e�k��(��lT�a8l�k=L�al�od[�l��d�s4n��k��l��r�r�t��r���i��u�t
$v�{�e�i �oD�p<sL�tT�u|�ÄB[��r�1��%��r���t �?��r8�t��v8���0�sP���9�!p��|���\��4��d�è���p�����aІe܆pP�D|fv����a�Ht��u�^�؏���bs\�Ćk̒9d� �d,�fH�g\�ht�i��k��lԇm�o �rD�s�2<���rD����$I��4�lX�<�a������T�al�v�v�8�Th���|�r8���e��i��o��N^���j��S0��n<���ȇaX��t���g�rX�����m����a�������d0�n�v��p�.����8�a̬t�l��uL�F,�`�a��h�a��eܰ
\���!��k����i̻PCh�Ԉk�o��tt�X�̈a�e�����H��@��e��%(�a<�iP�op�s��u��Ø�d;u�0=��!4�l���4�H�k����\�ll�d�e��i�������sl����!��k����8m����܉�����������ȉ�H��ЉÐ���t@�4�aPd�3eH�gX�hl�i��j��k��n܊o��r�sX�th�u��D@�l��t�KhQ����P�kt�=���d�mH�����k0�x�ul�t.��k��Y��?��iX������n̊o�c������4Ԋr���l��u�����aP����3e,�t8�uD�ô�!�li@� �i���-m�����L�����P�a�&r��D���eh=$�!|�l����dЋi4l�p���4����� �����<�=�_����ȋe�k�s��t��0��i��!�X�e�p$��x��,�e��l��D�n|��\�P�g���i��\��\�e���d�n���p��L���h��m�4�
����a�

P�a��b��e��g̍h�j�k,�lX�ol�p@Jr��s�^u����a؎b�Jd\�eX�f`�gL0hh�iT+j��k��lȑmX�n`�o̒p�r(�s@�tT�u�3v��Ô�Ō
Dd�b��l��pDfp�iXDP!d��x�a44h
8��i@4MT���l R�x��čl�|v�kX=܍m<�a�u������.�r��l�(v�Dx}aD�m����<�a��X��P�m�d�l�![8!��x�e��i��t��u̎Ä"[8"��n�#���$��l�����N�(��a�e$�l0�oP�up)��'���t�)v�)���r�Bd+�u�+���j4�y�,��<�a<,��D�s�4��a��eďh؏k�r�s�tP�u�4���n�5
�6D��l8Ps(7[47����a�9!p9����i4zv�;��Џv�B<Ys�t�C9(E~,D����o�E�t.,�l4�nD�s�t��L2`��
TF<�eG`�GhI�U��a��dԐk�n�p�s��t�V=�T
��m��r<W=pX�HX��ep�`^��j$^Ȑa���h�a�ahLf�i�b!�e��fX(�a0�i`�k|�t�f�`g!H�gP�hX�k�LlXg�`�����X�)`h�lh!h�i8hp�i r��s�h��i��i��ei%��t4n)�{��a�e8�iL�o��P�D�t���a�s��=\��r�=ȐBD�lԐ �ah�,�kT^�`��r��d�x�h��k��r��s�����a��
xZh�����s�������g0�n��!������a̬%�a�l�r|�=���l��p����)��-���uT�tfu��o8�p �T���L�sl��@�|�aPd�3e��i��l�Np��sԓt����h�([t�=�����m��v �����sP�8��
P���a��ȓr<�=����d�i�zp���<����(������������gx�D4�k\�=l�DL�kt�r���H�P��T�l\\�e���h�gLD��h����L4��
H�a0Td��e��g��h��i�k�l0�m��n��o ~p��r��sܖt�u����a�b��d��eX�f��gL0ht�iT+j��kؚl4Mm�nP�o�2p��r��sp�t��u�3v��4�Ō

\�hl�l��r�wL
d��d�uP�h��x�a��@����i�
0&ex����kHX=�����dЕs���D+i�w���ܕu��rx���������������r�$�eL�i|Z�X�d@�i�Z���(�`�a %h�sx��t�tX-D�,��t�����ePJv8!
�~bȖs�#�0%��aX$Жr�%��8mb(��,�,�ah�i��o��u���P-��s�, �a�IbL�eT�k`�l�!ux(3�@)o�-=T0�)fx�p@1�T2�Cr0��d3��j��s�����t��3�����4
�.�a�e$�i@�ld�oX�px�r��s��t�4�(7!47���a(/e�pu�6����l�;=$:���vP=<KhP�sh=��0�aX�ed~��=�kxA��PdDC`�Bp�g,D
���E���n�E��a����F���8�hIܘa�l(�o4�r<�uh��$I��(�i8�l�s�uXJ
P�e�J3M�(M����M��|M���Cr�M�PN!PdT�s@�T�N��L�t(O?O��`��U��a�0g��i��k�0l�mX�np�sW�T
��p�*v|Z���0g̙t��
�]%ęuq@�pؙl�r��e$^�k����sLa�a0�e<�iP�l��xa(�t�aH�g0���v�a�Lt`glSd�f��d�i��kP�lX��We4n%��a�Dr��sx��m��n@x
x���eКt�x��{���a8�eD�i�\k��4����e�i$�k��Q�T��r9���0�ft�,rod�(bl�dt�k��o�Kr<��h���(,sh�
|�����at����n���%ܛa�e��k�o �td�uP�v����țp��ЛlX�$v0X��r��!�grH��8�e�:m@��eH�i�xrT�ul������!@�s�
��=\�\�s��%��a��eȜi�o�r(�sT�tl��H����Ir����a��h��l��=��!�0g؜s����po4�8�t|���a�e��!8�!���b8�l�#n$rl��aH�������8����@��0������hx�k����\��4�@���d��iȝrܝs��
������aH�=�����g���������k��P���ԝp���l����T��� ��(��`�=�i|��x��L��<l����{�L4��
���aX5eОk�n�p�u��D�a�b<�d��eD�fX�g��h�i(�jh�k8�l<�m��nt�o|�p�r��s8�t�u$�v��Ì
=X�Ȟu�9�ܞlx���a�X���a((�a4�j,)=�'�� �sT+��,�`�a|�e��u���P-l�l�,T�ap-7D/��.t�hd3��sd4�T4������4`�4����a7f؟l�m�o(�r8�sx*th=X�e�=qh�
�?�i�uD@
xA
x��B�h�B�a)�,D��8*i�GP�r�H0hIl�l�{
Md�a|�i��
����a4O!��kPO����a��t�v�Sw�R
��o�i�i%̠spT!ԠtT�ii%x]aU����t�u��Q�i���al<�u�mX�m��4�s�#��p��H�t�mP�s4n��\�a��k��lܡo�r�r��r���u�r�Pya��i���4t?��nt��i�u�u�ȡsLuСo$v���e�i�u�v
@w
�spw��w
�{!�e�(gx�l��s�{�� �a��d��e�i<�jP�l��m��o��tУu��x}�s�}��l�a�~
d~����u�
<���o�4��n���aԢk�n�v����̢.d@�����d��,tg�i(�l4�sX�D�gx���ԃ�� �u��D@�f�{�e��D�al�ix�à��d�s|���pd����7i����a�(���(���mȣr�%��ah����.�k�u&�1�r���o(,s��������K�������u��$�d���0�ah�b|�o�(�����P�j�n؎X�a���`�t�kH������a�jȤk�����aԤe�g�i<�s\�th�u�@)o��\h�lX��@����e(����\s����ot��1t �u�i����!(�o��%0�o�
��H�ep�%P�r���
ud����a��k�l|�n�p�r8�sh�tD�4\��Qlh�����a|�rХ�{�Љðz��ĥ� �!�u8���ܥa�e�iD�oX�ø��se,t���g0�s8hv4�D(�tH����(<�g�3���P��pd����h��r|Cu����h�a��d�\kĦoЦsئt���/��.��e�����eP�BtMh<�9\�w��3��i|����t8����t����a,�eĹ���$�e������aP�el�����H�l�����8���\�è�!̬��t�a��ļrİ�W.������t,���s����a|Bu̲���~ħa`iȶ�\�ܧi8�lX�md�np�v����
�a��d��g��iȨj��k�n<�t\�u|�vX=���0�g|Z�D�i�L�i<����fa��0�h�x�r,����eL����r��T��̻��a�m9T+��udEfLte�q��Ԩt���s���e�����a�e̾�Nl��.�(�l�%0�aL�r���=T�T�s�����h��X�p����%ةk�u�����a��e�f �iL�kX�l`�o��p��sЪt�u��8���Щe�oLu��%JX���kT��0����s�����.0�n,�=���d�8�sX�@�u��f���#kp�l`�U��
 �x�a@�f��ax�d������j��h�?��sx���a@�Īa�e�o�hrH��:mx�����9i\���t��������4��aX�k����(�ol�r��s��t�u�è���%x�n|�`�a������l���a��À���������D�
0���aīeX��ox�
(��Ыa����ثt��!�s��� Ol�������@���|3b�~n�H�aT�ih�o|�u����Xll����<�a�!,ro8�����`�l������t�n�;���������L��
@�at�e|�l��n��pĭr��s����a|)b�d�e�qf4�g<�hD�iT+j\+k�l4Mm|�n��o�p��r��s�t�u�3v���4��
�,�a�
��4�kT�mh�t`
��Wl��\�e@=����}u�9x����shc���r����s�����aحk�t����L %�a\#�8!���p�,
�48�a�f��k��l̮mܮo�r�t�4XP�jX�s`�uh�vHQ6~\6
d644FO=%p�r�;��x�tP=Bh=����a��eĮt�)À>��=��v?T�?��A
xA��Ԯl,���B�dD���E���p�E�a(�iD�= F! �khIPO%Ul�i��k��n̯p�s�u�]=|Z��d�vH_0$^x�rb��a��d��g�L� L����s����i�e-�eįr,h[8hدa�f���tj=�i����g�{�,�aܰe��i0�oH�up�ø{t.L�a$qkx�m��n��s`|�d�sp�t8"��|��\�i���}�}a�:.~����e���d~����mİtD���#��k�#��i����k�аk�m������!�iP��X����a �gx����9�(�k@�mԇD�!\�u��vh�DT�s������h�����eG�����ud���e��nıo�r����Dt�ܱlܥ
�v���бa=T0�����i�����d̬�4�aT�e�ll�rt�s��u��è�`(kD�s���8�4�L�ad�e�6��)�������!|�d�������������<����~̲a�e<�i`�o��u\�a�a�g��k�u�9����t��8��$0p(�s����� �i,X=̻4�bL�il�{X�t�]�ؾ<g;ox�s��t���8�F���T���m��u@�����a̳o���������ijr�������س�����a���Ck@�!,�a��d4�hH�i\�kt�l��rܝs�������*a��T���@�t���l�T�k��%�`i ���h�t��������t�DȴhԴi�l�����������<���P�������i����s0�=|�B`����ix�8l��(�r4�sPN
\�u����g �LDP�h�!�XH�e�
�aȶi�l�m<�nP�od�p��rзs8�t��u�v��\�a�b,�d��e8�fL�gL0h̻iT+j��k��l�m��nؾo,�pP�rd�s�tT�uX�v��4��P�d���a0�e@�nd�u�
����ll�m��t��v��Ŭ���}L28�e�%
X$L�uP
T�t`
`�a����x�a��t��u�%O�%�&= ,������X7�����eܶnЕs|j�Ue� ;.����a�t�}uD��e0�:m�$�ex��0�d�X��H�m���\�a<plx�t�v���P.b��e��s��v �,��n  �� `!�$!��l8!��ķa�.eoi�t�u�#T�#�r��$�k�lht�����$!$�sX$,�al�i��skQ�$=L�a�$��T�v�$!`�ih����x�np%��e�%v�%����a��dԸkܸm�r�t&l̸o�8&j����&����&�e�3�&�i( �a�'��L�e�,�H�ah�eXbrt�u�ü,L�eX�r0.?�.��.`�ad3��j�4
�.��a�)bĹe�g�ph,�i8�kT�l��m��p�s�t�48�6��0/l�m�r�t�?P.��n�?ܹa�?�48�89[(g
4;���e$:�� �s�;��@l�bsP=�Oih=��H�a|�i��v�>"�>��h�t�>!p�ip��<�DЉ��������?�����XB��p̺r���غe�(,���D�n,D���i�E�E�e �k(�r0�s��/4F�TF0�GD�r�HThId�a��e��i��r$I���ex�h��r�I�@J��6e�J��\hxa
La��e L����m�M��M��aU�a8�d@�gl�i��k�0l��mмo�p �s\�tt�u|�v����T��m,�s�V��W�lWX$�tHX[�YL8�aT�e\�r�J9ZT,Xj|Z��d�b��e��n�Z3���H\��u$^Ti��rH_�Lad9(d����s�c��ļk�Lr�e��l�p��
�e��i�d�f�.<Khx1r�f���a<�eĄo\��(g4�s���4�H�li%P�ol�rpi�i8�j[x���k�����4n��a��e�Qt@o��m��dнi�s�o>�o��Ƚnxp
�p��ܽe�b��p�k�0�aP�ed�i��o܆p��s,�D�}d��$�l�=h=��<�e\�D�l�Y�h�\�g�
����p�a`�x�r��s��=(�����a̾e�\k0T�����d��\hd�	�a�e�k@�l\�m��n��o��s ��D�������h����kD�
P�(�aL�e8���0�l�
h�v<���T�bl�ih�D|��P�t�p����|�o$I
X���at����g���������aпp�t���Mo�$+<��ܿi��?�tĪ�aЪ��a r�������̬D�r�0wt��~8�e��\�i̻!�%��a��l�o��p�tl�=t����l����a��T��J ���ox��Ck@���a��%�a��o�rH�s���h4�@As|����a�i(�op����O�� �o�����4�il�<�a@�|�aPd��j��k��m�s�yt@�u��0����l���r�������a��b�m�p �vȑ`h��P����!.��e�i �u�����t\��0��n������l �,��,�a����4�mP�r������a��i��o��
����l�ad�u����t�l��n��s$����-nh�0������k��Td�����k�!��s����tMh��kh�����lD�nP�r���������X�����`�T(�i|��<�
��0�u���8�gh��x�D4�k����
���h�e��l����p�p�������wl����r��L�x�k�Fm04r�
t�a��b��d��e�g}h4�id�j��k��l<�mX�n��p��r��s$�t<�v����aH�bP�dX�e��f��gL0h��i,�jX�k��l��m��n��o �p��r@�s@�t\�u`�v��Ô�Ō
�P�jl�l��n��s�
�JPS8��aXi��o�+�����a��e������r�@a��lZ$I_��a�e$�u�X
PF���,�.�oaH�n|ex�X=P�n<X�a�(u��H[o�t�r��s��u��jp��.�w���.P�s��t�����a��e�l(�t0�u��vX$=�. X]v�{���a �u~D�L�-m�ZH�p<���x��P�kp�nx�t��u�� .��iD~����-m�Tpr ��pa��s�����i��v� t.��at 
�!B8!����e�t�u�#��li�#��i$�k�-mX$,�l'= Xl�&0�a(��,��4��aKd�e��f��g��h��i0�kl�l��n��o$�pP�r\�s��t��4����d��l��v�5��5����a i���p6=��ld6X��a(7�47����a(/e,�n�6����l<�mP�p�rd�sx�tL2��?
�?4�a�7��7H�ix8
�8��\�i�Ek�8p�a�49��e9[��a(9�09����ap9?��a��o��u���h9S�R�S��9��9�����$:D�d�s:=X;
4;���i�;��;��(�aD�lT�s(<�<�\<�L�pP=�pjh=��`�a��e��i��l��t��u>��=��k�>3T�~����u?	8?p4A�p@����s��[xA����h�k�m�p�rh�=�A[�A=�=XB0�a���Ht���C<�m�BD�e,D��|�et�i��k��t�DP.��n��t,��i��DTdE��E��E��n�E��e�_r��t�G�hI�U�are0�g`�hl�i��k,�nP�op�p��r��s�T� �h�U=<M��Y(�nH�rZ
Z@�a4O! ;.8Z��T�a|Z���0g��s��t4]���0a��eH]
�iO�]%��sęu�q��^��t$^��e��o��sp[t�v_Pd�_Td_���p�z
4z�i�b]�e�b�m�a �iH�s4d
@d��<�e�c��D�l�e�e~\�o�ed�rP�vf��|�r��udf��f�.�f����a��e��i��p��s�t$�u(g!���`g��k�g�x��h��e0���h!�j8h�u�h3lL�a�I~8lD8�r�k=@�a4nt�a��e�i$�ld�u�m���a��nP��n
��a�n����l�x����o�?�.�p����m�p��e��l�nq�q����a�e����=��tTq8�q0g�r�<�aP�e�s��r%4�s�B��sH�rdy��x\�k��l��s�yC|y��x�p�y
�yD��i����a$!
������a����s���eX�=����pd�
0�b<�eL�hX�i(dk`�l�Am��n��o��p��r��s�t�*9�(�i��D�������D�t�D8�x�e��i��o��u��Tdk���X�У��tt���ot���|�����e��ŗc����!.��j��tȨ�<�	��a$�����u8����s̬4�e�[�,�r�x�e��f��i��k��p��t4�u��Tl���d�lX�l�ll�?������������,��hLf����nX���We ����o������r@���a�i�r ��x���t��
��0�j����� �s\�(�s��x�aH�ex�o��r��sT�t�uD��H�?��d���h�a��i��m��n�r �t�� �,�����V=|�����n���`.�0.����d����r����a�����dh���\zt�$,�sX$�i��&P��p@��4�o�<�nd�pp�t0���\�p�E�4�tMht�pd8t$^�L����k8�%��i��k|���e��u8�����x%��u����t���kl��io��X�a��!�s,�u���x������$�dH��T�r����8�����@�
��a��b��d��e��j�k�l<�nl�p��r��s��t$�u���)��'����r|6��a�����r��ud3
 �QH���:l0���ux��!.l���s���a ����g�.����(�e����0�dT�ip����L�s�B|�s�`�e�1r,DQ�v������bP����!.��a��e�js��t��ut��4�7��a��4�s@��i<������n���&r���������t�=�����g@�rT�sh�
����8�a��"����L�a�x�u���L�t���l�s��	��d��h��i,�lL�nT�p\�rl�sx�u����������������,�<���a��e�.
��JHX�������dȋe�g�s$�t��0�i�`�X<�iD�m|�	����9�h������d�a�x��{d��k��n\�X�{l��L�k@{mL�	��a��d��g��h�k �lp�r|�s���@�������T�i4{�sxYl��d�a@�dT�e`�o�[`�8�i��%L�m��\�
���h�a�9T d������
P.H�a\�bp�e|�i��k��l��m�n,�oD�ph�r��s��u��v����a��b��d�eX�f��g��h��iT+j��k��l��mL2n4�o�2p|�rl�s0�t��u�3v�����ń�̢.�
��<�th{8T�i�'@��h�d���Vn��s��Lu���o�%��a��e��l��u\}����j�{�s���aL!�-s��la���ax���d�
X��$�t�+a�8�l��rD�,T�t���\�e\pm|�tL pP���!��r��s��t8!����eoix��!�"��&j�%����t�&��a'=�j(��,7�40�al�e��h��k��l�p8�rl�s�4��4��(�aL�gT�h\�nd�s�=h5=�5!6w�6D��l��tx7[47��|�u�8��9!��sp9����i�9=�;��D�l7rTPv�>0&eh=����k4qo��u�-8?%��.�j�D��vXB�i(�l0�pdB<���B�!.T�a\�d�Kg<Ys�B~,�� D,D��d�a�i��khI�PO���`��T���l�T�����U!	�Pa��e��iD�k�0l`�ml�n��s��t�X�,[X|Z����k�m�s0�v\
\�a4]���0a�]
�](�e_$^<�o��r�v���LaX�a�ahLf��gܘ
��|�a�fi��v�f����a\ri<�=Hi�Ti��ei%��e4n���l��r�rr�{��u��iȐBԐ�ah��k,�s�vd�	h�d|�e��g��k��l�n�oX�rd�s`�<��`�ap@����t�nX�3��x���.��ih�����sLxQXx��kԢ�����k8�����e��uУ`.�-mp����s�����it�T4�n@�pĦ�|���,�o���h�e� ]u���L�a����8ept�tЪ�����a8�ep�i��o�uL��\�H
d��f��h��l��n�t,�u�L��o�H��P�x����h�������l<�?�s���$�8��eܸ6����$�m��%3eL�i`�p,��X�m�:���@�̻h�g��i��k��pd��l�����b��p���X�u���._��o����o��c���l��������sؾ��s�C����mT��m0�p8�u��@�����`�����@��T=d��X�����a��e��iEl�o�p$�u����a��i��j��l4���Js�(�d���u~�����n������a�P���?��hX���h�lT�mh�n`�!l����a4�l��E��,�up@&\�@�n�?H�e�������`�a��sd�}���x�u0������g��i��n��s��M��V��i��%��sl�����t,��tkH�^���e������t�����oijr ��\�D$s��D�aX�e��s��H
dl�f�P�eh�sl��T����p�ml�x�e@�!l�i��r��sl�t��u���l���j������a4��P�����a�e�i�s(�t0�u@��������e0�%@�� �.�k@����[��3����8��P����|��0�X�p��`�o�������x�d��l�� �����e����b��i4l��n�zp��u���H���������������{���������nx��,�e4�k4��\�Q�����k@�p���$���4l��p�h��k@{m�Rk�R
\�a4���d�t�w����|�eL���h��.�X��e��i!�a��e������?��kp4m�;�
�Us�
����a��bX5e�(h��i��j��k��l�m(�nH�p`�r��s��u��v����a|6b��d �e9ft�g��h�i0�jl�k �l��m��n��o�p��rP�s��t��u8�vP�Ì��8X��ih8=�����k��n|XT<��a�<Vt�X�l���Y�t��ex�5u���4�r�<�a,%p�e���T�ex�tĹ8L �8!X��t�#��%��d&��&X0�a�-��-����u�,��l�,����a��e�o�.t�h��oxAT2�r�58�4���l�4���aH�eT�kh�m��oxlr47D�6��@�l�;��s�?%�?`�ahI��i��r����M%��a�M��a��(O�O�����HS�:m��r�R
��e��iPO����tdS�tS!`Zn\*sd3�HX�uU��	�dH�gT�hh�lt�m��n��s��t�v�Y%�Y@�a8Z=`�a4Or�_��0mLa�!.����s�a��a��w�f��D+i��u����h!��t��=Ti��hi%��e�So���Dk����ak����l�j��a$�e|To\T�lk!�ll@�aH�u�k~�m����jX�m�m�d�iP��4n��a��i��k�1o��r�u�v�n���s�m��a|Z���0g�q��i�q��r���iPv9`v��a$v���a��o�wX�yD�ta�x�s�{�{���a@�dX�kx�l��t��u<�|�eP�i�Bx��,r���d�n��l�e��ul��T�!��i���|���a�%��r�!��o܊�����a �b4�e@�fP�ih�p���-f��u��&,�h�+�o؎�r��\�,�tX�����h�H�at�{̒%`�rH����l��t�a�����a��d��il�@T��,���k$�ns�����a��o��
t�P.d�4h2h��r�%�g���̬�l<�p��a,�eܰ!T�B��4�eL�iD�Xs��=���X�l\�`�a��g��j��l��p��u�v����	l�a�b0�dD�e��g��i��k�o(�u���
<��u�������ad��Ը>������k'���a��.h��,��(�e����<�a\�oh�st�txA���!s�����k��tXJ�d�����sL���a��e�����W.��xa��t����e̻��m1n��s ���L+k��|fv����aؾ�r�� ����uT��l��7�.��d��e��l��s���
4�a��e,"f(�g0�ih�kt�o��p��u��������0��������u�46e������t��DX���a��g��l�t �v�����k����kl�����e���!t�u�%SlG[��m����dXggL�h\�n0v`��b9,�T�dX���r��%��h4#l�grL�w �)�go\�4$i��lD$s��������i��?������������?��h������k0�oP�r��sd�tp�u����������ld��4�(�s@�t8��
|�H�u|�T0�\�r��!�Ys��=���|�h���������w������l@���	��a��d��k,�m\�p��r��s��t,�u����Ju���#kl���a�r@w[����i,�����l��� �aD�i�lP�<�s��������P��l��\n�is�u��v����h�a��4����kP�����a��k��s �u��4h��We@��.�����ad*i���s|�������h��������������i���� �r�D�a����dku���d��L'�|'�l��p�r�DL����x�l������,�h����e�
����r\�s�
����al�bx�e�(g��h��i��k��l$�nx�p��r��s��t�u�v����a|)b�d$�e<0f�gL0h�iT+j\+k��l4MmL2n��o��p��r�s<Nt�u�3v��
h�8d�i@��xlrP[x����e����oa��n|���(v�P.�Oi�����a��d�g��<���iL�X��a���@
���ix���a<�eL�k`�tP����%D�u��
 %X�ap�e���(r�
\pm��p|�t��u��v,�T���` ��h� ~� ��e8!��X~ah�k�tX$h)r��u����%(��d�%����d�&��,�4!L�e��h��i��o�r�s�64�6��D�ad�k��lt�s74@E��8��l�p�9�p9����i(;
4;����a$:����sxA��Ԯl�tLB�B�a@ET,D���phI9U!�Pa�d0�pD�rd�sHX�$�edX_pX�a�e��Ll��Tf��<�k����fP�k�f��X�a��k<�,Lut�dX�|�oУ[�����ut����ld�����o̬0�P����ta�u@����s����D�i$�l�8m8�r���D����|��`����i���h���0�kl�Dt��0���P�e��u���X�r���h��T�
d�!��o����o��t����k��s8�(4�e����b<�dD�e��g��h��i^jL�k`�l��m�n�ph�r�s�t�u�v8����l����x��L��)7�,�4���Jap�l��t��u�=P=\�nh=��d�a�Ku�E4GhI�T
T��uPO����vU��tZa�b�e�g�k0�s,X=�XQ�X��k��l�XvPN?�Y�u_
$^�o(gB�f��(�e�yu�r�D`�4n��@�k<��{��X�d|�i��m����t�m��t�.��H
d����a$��؎��l�����b��Dgs���t����e̬Xt�a�i$�p0�r�X�D��e4�%���a����0��8�a<���@�d\�L�n|�s����X�a��d��gз=H�
,����ad����Za��rL���a��o���|M
����a��3��������������������e@�����d���Q����pP�rd�t���$����B0lh���D�e��
���\�a�
���p�a|6b�d�e0�g8�h@�i^j\�k��l��m��o�p�r��s��t�v���,�e�i/��.�e$^=T0�k<�h=���d�4���l(�px*tXB�hI�POU��tZa0gT�s�f=4nx�oTPv��?��oLul�nP���{��i��X��a����s���d���%����u̬���a�l��|Bu��\���a ]u�����a0�d`�ep�i|�o���,����aT�e��vh/��@�lh�H�l���0�̻h�lؾ�����������������<l���,�t4�c������o����!��������\��p��x����a�e �i<�lH�oP�t�-@�Q~��{(�n��0�a�������\�rL�
��d�a��b<�d�e��g4�h`�i�;j��k lPm\n�o�r s|t�u�v��(�u�,@<,���s�t�4���)a�oxAhI�a@J���Oi$I���r�4QP �aPO��(�eh1=�X@�sHXH�iU��T�d��k��l��s�t�^�$^|�e�`
�_����oHu�fX��a�ÐfB�h��h�����4iȣri%�a�p�̢.�r4n���eD`à�X��s�{��i0kx�@)o$��؎<l���Db����atd�e�t���0e������t�cp�%�od����Kk��\��aor����
�a0e\gpi�k�Ho�r�tv)�P�-������|�!sT���i��$l@t��TK=����HlL�Pel�̻hi�k�s��(lg�<��l ����e�t���r���a\��\�!�tP��i�
�%�a��%X�aX�@)o���kDn8�pPs���7i��8a@��#a`o��
p�
|�hi����pr��=@����d��eL�$�!�e r��q�n�����il����k�or�������s���a,i����E.p s�
�
��8a��@a|6b�d��g�h^j4kllm8n\ohp�r�st�vd���-[�e�,�m�,���aPOX�eiHP`P�i�np@���$^�.�P�kt<Q�x�m n4n��(aTk�Ql�m�Qs�r�Ha<��a�{��
`d�eg<i�k�l�m�oD�pL�th��%�k�l�m�s��vԀ8��s��ad���ä������������Yv�a(e�6����� e8Z���4hTidkx�X�_���\oL'��k��p����x��l�{[���aІ�i���e �=��r�����k@J���a\Wu���r����a$iԐ�oh�k��[���0ePip�=t�Hsd���@�t̬���l�p�r�s�_\�B���u����\�d�i���̻�k����o�p�t ��1rx�(�l�s@��ah�=���r����aTie<lHrlG��(v���0e|�kH�uL
xl�p���T��{�l��pa�e�o�
�
h��h�e�
����abe,f4i<jDkLlTm\ndolptt�ä�������(��4�e$r�6��B��G�U�l�4n��{�����d�D̬����x�����|���L��{������
D�k�u���a�e�iDk	ott	u�=�%��4�U�	s�f��L+kd�8@��45052104001001404400102530020023434250303204405104501502010502402050045442450244415423512305244403214224230105231222024310341030304041000401040005050000525200420500010423423005021040404100503010120300500540005000003204002030454300320125030030000200030001000030210212034105203001400040203100402103402005050101200050100500004143210400005210540100402003014050000104010050010012010440103200002145200044105003003404103010040002010240400400120500000042004004500040010320210430003201400500000002100421043030341030000050001003400120012105020034050021030430030020050004101021032004300010	500000020400000404000344300212404101004520010202032440300500220422024430310320444044122023042012401000301432010451050102021041200025220002520200004000103004320050005000300100232254104124004050040022001400504105012050010303005002003500542005105021004521030105004141420041000425050005004000304100500230200402030050000100400210	50000032054403054021054000002545243001042425004101242500	5000000001012021540401040010050004003102304240040230403025400321005400003200054010520002141040442004442104520052010421005000300500044045030030034000503030012151004020104042020010202004134104243402103004105000012030021020410404002440003400104501043010500002004044103202002001004002004000000415050004040344021430200412104023210040041420050400410120404403444020202010001100343003041040000240244120103142352025401121234450450020140035050002105004032244014340440120421230300040320303205100034210304010500000105404405001450403220430550410042401504024010030024250003541025010201005240522020020204050500101054410402000434000400300342003403003054541000450210452000500050050030105430022502402105004100	50000041052041050000300452344000210010250000230100210201442300030040043020130001654041040004005400300510001002400210500012030024500003300405544500450142302021024301004004002454003450200030001003000000040505000200100000300000015210002040102514PK
!<XP�݈�hyphenation/hyph_fi.hyfHyf0tP�����'-4�������$�'–’-111001��������L.�atbc�d�ef@glh�ilj�k�llmn�ohp8q�r�sLt8ulv�y�Ô��la�e�iD
l�op
r�u�y��d
b�8���la�e�i�o�
r�u�y�����������la�e�i�
l�o�
r�u�y��	la�e�i�
l�o�
r�u�y���la�e�i�o�u�y���
la�e�i�
l�o�
r�uv�yÀ	u��	s���a�e�i�o�u�yÔ�i�j�l`n���a8e�iDo�u\yÀ,d��m
px
s<t��l��	la�e�i�
l�or�u�yÀ�
r��la�e�i�o 	t�u�yÔLj8
l�pd	s��
�aLcei(o�p|t�u<yÀs��
d�L	h�	px
s�h�r��la�e�i�o`s�u�yà	L��tr���latodu�y�����������aXetl�odu,�����Ta�i�u�y����la�etodu�y����	Tahei0
l o`s�u0y��8������|�����0aXe�i�llo�	s�u������������e�i�y��������� �������He(iPoXuXl�4���`a�i�u0y��4ahe�u���D����a�e�o��Da�e�i�o�u���T8���ae$ioh 0���,adi���,axu���,ah����a`e�8���a�exu8���a�eoxu8���axu8���eioxu���"�%���������ì���L�������������8���@À���i����di�D����a�e�o�u���Th4����a�o�y�����a�e� ����e����a0u��+���8����s�y@����o���hih-����p��a����i�
k�ol-D���v�
�ih���l���u`1����s����sl,	uL��	a����s�l��8	el��@	j|	t	a�	��peX	i�	4�	a�	o���|	i;D��
k�	n�	tL;�;����	i�	��	sh���	i�B����	k\
t�$
th��
eP
iL4�H
i����e,
o�4�d
s|B���\
t����p
a��
i����
e�1����
s��s����
u��
a����
eB4���
nt����
i�L��y�8s��� u`Jldtl��@a<
o�L��\uQ�Qh��xu�$
t����i��n����i����d1��u����ul����a����r�WL��il��t�L��(oL��Lo0t�Xn^��xn0tDe��pol���a(ole����a����klH
il���ah$
tt���i��n4����el
s���
a�	l��
s���$
o�u���(o�{D
��P
it��X
l����i(o�
u���
al��
a�����a�
e���
o������a8	e�
i�l-�
a��
o��th�
ll�
i��,vt��Dhl���Xh�l��lr1001000102020121032102200301020021202121200012000021200002120202110021210021000021010012000021212003001200PK
!<3�E6�W�Whyphenation/hyph_fr.hyfHyf0tP�����'-4�������$�'–’-111001�S��������'.�a�bPc�d�e4f�g�"hLi�j�+kd%lh	m$4n@ o�
px2q
rXs4t%u0%v"wp(xpSy�Sz ���\a�e�ih"o�Qu8Sy��\a�e�ih"o�Qu8Sy�����������\b�e`g�m�n�p�r�s���a�b�chd�e�4g�i,k�Ol�m|9nh"o� pDr�!s�,t�Qu8Sy���\b�e`g�m�n�p,r�s|1 ���������������� �����������������"
����h49sl3����b	cpd�gH3il�1m$np|r�s�5uL(v���
�	0a�	��8��D����Pr4
���hh�T|e����r|1���d ����LI#x���h����c����sd%(@ ���l40�oP���od%��<ac�4���i��$g0,00l	���Hcp0Pa,��\o�%0hn�1��ti�#����r����t�0�s$���aXt;X���i$#�s�"���� '��`4���h���t�0s$4��e�3i�5tt$G�508ade�sxü5��@i�G�n4	J���p��N$4���e0���n<G�:T����a����t0�s���ol;Y�:���r�:���tD-0�s�
��o�Gpl^x0 s���(i4	��4p���@��0LøXi�$��dh
��pcP&m�Ke����a����t�Li�L�o�K���rX��p1m�t�0��	�a�ei|l�o�ruy�|1 �������������,oH3��<n�0Di$&��Pa<0\m<��heH3��ts����i����a$i�0�.�n�s4	w|����.�,���.0���tL0t$��Hc<tDu��aTohstu4�50�+G�+��La��<��`a%Gd%�e���0�.�n�s������.������.����t@ 0�'u
�e<6u�-0�.�n�s%0pS00��|1��|1�4	�|1��0�ol&p�2r��
<a�c�e�h�i�k$l4oTr`uly��4�%���t@ ���u��a�+e�+k������.����t�0�n	���e����c����eP��	c qP���ex3i�5u�� 	c�dll$&mn�p-q0Hr%0�	n��
�a#e�%i�6n-oxs\	uy/��6���e�0�	c4���	i�Q0�	f�Q���	i4	g8m�sX
t �����	����������
�1a�$c�-eTHh�i$&mpo�.u3y�	�P���e0@
c4��L
i
�ei�8ou�8���
x&a$8eL?h�&i,6l�@nD-od
r�Bs(Ct!uyD<�,:���ep0�
c�Sz����
o4���
d�,0�
t�,���
a�0�
vp0�
c$4��o$4���cDd�(f n�'s�t��	t$a`'b�$l�m,n�+o<s�,v���H���eLIhloX��xc`4t��0nt5r�s�ux������.����t�0�nx���e����c����sL���e�2��ix2�u�H���ehIhX��,c�+t,���e<��Lc�%0\8nXsh	��8adih	��tm�p���
�P���h�iHo�u�"
�.�b�e�
g�i�
lTm\ndplr�s�t�w��#0�.(
n�sL(v�,��
.0��
t���$
4
e�$��<
lL��H
l���T
i�0`
u4��l
e�0x
fT�
e0%���
r�
v�"���
���d%
�
e(o���0�
.�
s������
.	Y�10ct5��aH�p0rX
��	4t�	��<�h	�$4��
�

|e�4o�-0�
.�
sX�4�"�L0�$l�
����p�0�l<���a����s�+	�.�b�ef�
ghdp�s�t4�,�d%�1a�e@ 0pae�g,Pmn�.rx�		��Xc�q���`c�d�p�r�sDu�x �p0|0,:�p0�cX���o�u����s�8�.0�rp(0����40���f(n0x000\s�t4���4m8n(o�9x���dc�0�mlsxe ���r��e4���r���t������0��h	���a�
���b40�r$4��f�Rn tu@ GHl��04ed%��<i
�e%0HHrpS0'rd%`��xl����'�a�d�e4htidlpoTrxsuy��������������0�1m ��������������G@ ��$a30,oT8y	��Dr���Pc���\a�i�-o�y,�4����h4	�c4tp���e<0�d 	���a����e�hr����e�0�dP���i�0�d4��i
Ha�ehit.u���apOc�e 'hi45or�Qt�-uy�N��0�dL03adl�s0�dX���i<��xc�s�'t0���d����n�0�e|���dg�=p!0�d�
��tPo�u�.0�d���$a@iLoTs����+0L��8o@ GX

�acde�ido�ptu�T!	���r�0�c�m�s�t�1'�%+d%���l�0�l4���e�3����r����t�H:�>,g���$"C���$r`'J08b�9��@i���Ls<0Xn�r�x4R���xt%R���u��d�g�ln(r8s�X4!����n�]�$���i�c�0�n,���ev�Rj0%��o
X�+R<��0t�c��D�@:��L�p0Xd�l�p�r�u�q����d%X�$c�&0�l�.���ih5Rt5���m�p�
R�34���r%���f�.:�K:�Qv.0nR�0 l�"��(aXo�{%��Ddp0Lu$I,I0dp,��lo������	���i��3���c�lm$tt���a8eLs���0�d�h$���d�%��1���i�4�450m4��o0���0n@J'���Dh�0�nd%��Xe@ 0XG�S�`a�i�o�u���s�p�.���n�u4	t8m4	�e4���i���0�e4qTi@"��2�� ex2�(u�4��@rP��Ht�0Tcd%��`ex3i�Co��,�0���t#0�n$&���e��m<,������0���a@o��t5���g�0�r$4���e8PiT��,��r0v$4��$i,tp�$I�e,I0Hp,��To<��`c�&0ls�
��xi$8��1�
���a����r4��K0�tR���aR���t%���sH�$c,i�a4��rp(��t	
`3
��
�a�e�i�lpo�rxs�uyd� �������������4	\.cL06ct5rd%x4a�e
�e�'i%0p1m���a�e�h�i|l4npo$"rxs4"uy�����e�0ge4�� rt0g���8i�-o�u�0g�$��Xi�%0g�%��pid%��|l�0�l4���e�3id%��g0�l�08Oc�1m�n$��gL0<3m�n,��g,���eg0n4��i|��g�=p�0�.$&m�n�r�Is��
�a�Hc<e@Jhi$5lpo�.p�Kt.uy��t5��g�=p�0�.D2m�n�rh2st5��g�8��g.0�rX���u���"���aei$o,y#���p��#�$4�e4�0@t�Ha���Tn�:p0ls4to����n�4���i0�t( �i4 ���nLg�(n�����4i������nT�����e�0 dP��,i08c�f�Di�u���Tn���</cdg�/lx0rh	��pa�6i$7o�0�4�<0�t6���a�0�c4���ih	40�m�4-���i-0�n�4�� op0 mt s4 o���( n��:b,:c@:d4 gl:i�4m�.pt5r�:s8<x���:���i '�4��� h�0� t( � a4 ��� n�80� g0Bs|BuB�d

� o�AÀ
��	�<a>et?h�@l<Ao� r�Bs`Cu?�%0\!g�&px's��| !e���(!l�04!bh!c4@!a���P!n	�'���p!n�K0x!g�K���!a�'iX��\5a�Hc�5e8Jh�Ro�!t,Mu�!y4� a����!n$4���!g���!n�0x!g���!a�eipo(,ru
�4a�e%0@"eX(���.(
n\"s����T".@ �"n�;u<v
�
��|".(
���"t@"���"n4"0�"e����"u$4���"g���a#e�ipo�u�#y�"� ����$#���������0�.�s4	P#m4	0���0#�&08#�$&��D#it#o(C�.��`#t-0h#ppS0�#pH8��#a�#e�#i�#o$s$u�#�$80�#r�
���#e$o�1J�-J�	��	���#��JpJX�.J�@GD-0$a<$eD$iLoL$stuxÜGl:G�:G����T$i��\$g���h$l�$rD' '�$r4���$h|���$t�%
d%���$l�$�$���$l�$�$���$l�0�$lP���$i���$cX(e�QnRsL(v�0X%c(n�� %a�3e0(i�RoTruyR�	���$i��
0,a�e�i�%lp1mpo/suy<,�$4
�+���%n�0�%od%���%i 6uL0�%l<3m�Pt�$�%e�$���%l4w�0�%t�%�%��&l�%0&lh	��&i&&��0&l&08&l$&��d1eD&i�&0�$l��`&i�0�<l�&p0,�$
�&a�&e�&i�&o�$���&l�&0�&l���&i��%�4 p0�&m�
��`&iL0'r�0�$l
��'i�"�.�Pe4>lTm\nD'r�s

|e'i�'i���T'r0�$lR��l'i@L0�$l�+���'i�K���'i�9���-o�'t4��l'i�0�'t%��p1m�'t, �%0�'n((s�$
�'i�$���'l�30(l$��(i< L0�3d�$l0(0�$l0%��@(i���L(vL���$l��,Sed(i4%�0�(th5���(at5���(mp0�(r4���(o��)n4)u,���(a@)eX*i�*o+s@+tL+uh)ø30@t$���(i
�4"0)r���)u�5��()g���)f*p$*rL*x�/�)l*n��X)����0t)b4��|)a4���)f�44���)a�$���)t0�)cl���)u�14
���)a|���)r�0�)r���)a(C��
��*tt5e�1=t5��,*a@ ��4*r��@*o��*m�*q�*t�P���i�%0l*t���x*ix2'4'p��*c�*nd%`0�*l,:���*u��$4���*d���K0�*b�'���*a�'���*t�D�#a�#e�#i�#o$s$u�#Ô0+r���4+e4�\+ih+lL���td%'4�	��p+t���x+c@ ���+a�+x,<8<���+y�K���a�+e����.�n�s��	�a�e,hipo(,ruy��"�.,��+��,h
�0x,w ����������������-T(,d,e"��l,r���e����,l4���,a�PhdQr %0�,l0%���,a�,���e�,0�,l�,���,aL���Pp�,v�2���"e�,ix2��,u@ 04-n�7yp0�,v$4��(-o�7t@ 0�@a|-l0$<a<$eAiLoAstu�,v�@�d%��T-y�0�.�-l�n�=p�sd%���ex3id%���ep���-lp0�-l%0p1m�Qn.r0�-l����-u
���-b%0�3bD.cp1m�Mp�8r`0�-l���,.u�$��8.c�50�-l 	��P.u�$��,.u�.0h.c!0�-l�
���.ut5���.p%0�-lt5��.uD-0�.r�
���Ja�JeKh,Ki�.o�-���e�.0�.l�KX���.t �����0���������'	��4/kl/r�H�:��L/cp0T/sT`/o�G�0x/sT�/e�/op���/r<0�/d0i0p0v���/a$0eD0iX0opO�V���/�<���/�H3���/sqL(v�q�00n��,��00t�080n,:qp0P0ch0d@:vp(^|��p0x4	hPc�"qp0�0h����0o�0���0g�0sX1t/���0��e�0nX���0a1e1i 1u$ �Iq<0�0s^Rv.01s�Kb���,1a���41t�0@1s4��L1a#0�nh	��d1e��p1m�1��d1e�0�1m����1a���1g�0�1lh	���1a0,0�1m�0�Cd`3i�1md10�n�1���1e�10�1m�>��2a�=2rD<�� 2��
��,2�h	��82p�K0�1m`4��P2a����.\2t��2u����1m%���2a@"e�p1m�	���2�|���2��p1m�10�2g@
�2aL?�2r���2ht$���2ppS003tT�d1e '3m4��$3h���d1eL��<3m<3���1eH3��T3m���2��0<3mt0<3m����3i|x3i����3lxs0<3m�0�.�3n�s$0<3m0���3i�Pth0T3m�3i4��4r%04t��
�a�eipo�9s4u�9xy��@L0<3m�K��T4i�1��p1m0,0l4m�1��p1m�10�4m�Cnh	��p1m����4m����4eh	���:bp1m�:np1���1e,P���4m�4��d1ep0�4m���4o0,05ltJvd%��5a�Jo@ 0�4m<HrP&���1e|��D5m���P5r$&��d1e
��h5mh5���1et5���5m�0�5r4���5e0�5f4���5i����5m<���5r,Ju%��p1m`0p1m�0�4m��5uP��6h0p1md%�e 6u�.0p1m�10t$��H6m�%0P6a�%\6i�%��h6l�%��t6l-i$&���6o��6m�6s���6��6�$4���6�Xq�6siX���6i(-0$a<$eD$iLoL$stu07�4-���6o-��7n���p�`7��^��@7����H7���T7dx���l7l�	t7a�	���7��7�4���7r���7g���7�0���7���7npS���7e0�n$���7e�08n$&��8a�0�.�nH8r�sh5��8at5��,>h<8m9t,���e&0\8n$&��h8i4	��t8m ���8��	Js�8��	���8�����8�p0\Bs�8�8��h8i�	�8m�	���8�
��4Na�8�0\8n4��9i@L0\8n�K��9iX��Je(9tX�:��D9sp0L9b$4��X9op��d9n$4��p9oR4���9.�9s�0�9tX���9a{X���9.p(���$4���9gp0�9n|�9o����9l4��0:t4��:eP�� :td���8:l$4��L:i�+��T:nL��`:ox����x:s0�:d����:u0�Pp�s�6���:i���K0�:s�t�K���:a4;iH;ol;r ;�X���:tpiL��;oL;r L��;���@L0,;m|L�`L0@;m�CqH0T;d�;t�L\;a�;i�-i4��|;u$4��+���;n0�;o4���;iP���;th0�;c@ ��u����;o%���;a,<���;c0(0<s0%��<i`SGpS��$<ap(��,<y �����=��������@ ��xüh<o<,��t<����<�4v�0�<f�<m=r$���<a=i<=ox&���<n�=r�Ot�0�/���<��1���<��1�|���<a<q�30=sL?��.��$=hH=tp0,=p(C��O�P=e�=�	��X=h�10h=c�=s|��t=a�Ge�=h��"���=��G$#�"���=�TH�=�4	H>n�>r$80�n�
���=e�e����=a���>t$8��>nT>rTH�d%�404>l��<>uH8t>a|>e�>i@o�>u�>��1��-��	/�	���>����.�-��	�>q�	���>�0H���>��:0�+���>s�0�>o ?stu�>���>i�=���>rD<��?�<G0?s@?tX'�T�+��8?a�"�.�?e�?l\n�?o@r�s@tL?��?a�9��t$���?s<0�?n���?a�0�?l#0�.(
n�sd%
�
e450L$s4���?op0�?t

|e4�<@aP@i��<,��@���$@��00@l<�0H@st$��0\@a
��d@i 60p@r,6
|@u$4���@e�����0�@u����@e����@r����@t����@s ��A�p�|1JLG�SG�0�t4��Ae$4��$AtD-��0AnpAs ''�:��LAh�Ai�Ao�Arxs�:��TAt,q4;0|AnH;�l;'�G�Al�Au�8�Aa<$eAiLoBstuBè8���A�<T���Aa�5e�8J�8���A�JG�8���A��H����\B��$Bc��:0<Bt�:��DBa�:��PBt4:���hBh%��pBd��P���Bh0�BcX���By$����Bnp0�Ba�B��Bo�B���Bh�B0�Bc�B���By
qCr�N��C�<C�4��C�0Hq|O4Cr���8:l���HCd!��TCd�2���"ex2�lCup0xCqd���Cot0�Cl����Ci$���Cdt0Top���Ci�G0Dl`Du�	���Ca|De�DiLo�GtxGulD��	���C�
���DeDèT��(DeHDi<�0@Ds@tp(��5��XDx ���D�p��G�Dl$*rd%e
�|1���DrLG�Df�T4���Di�GEc�Ep Fq,Fs8Ft�-���Ds�H�H0�Dpx���Da,EiHEo\Er<���i8Eo�0Esp�%l0@Eu�iTTEi4�P��hEt�J0pEc�.��|Ee�Ei�El�EoPK,K0�Er�x����Ed�0�En,6�Ee�9�$4���Es�.0�En�$�L��Fl�2��Fix2�FuX'Je`4'dFa�Fi�FoGrhGupGy�F����K0\Fb|Fg�Fn�Ft��|K��K���Fd4��L�Fn�Fr L���F�L�<3�T40�Fm�Fp�
�TL�`L0�FcGp�.��L#0GePGi,�L��Gn�0$Gi4�P��<Gth0DGc`Gn���-��L�%�GsR���sp0$a�GoX
���Gr�-0�n|���Gex&0�Gr�
���Ga�.'$Ha�9��QaDh�o�Gp�u$���Gs�10Hn�J0�Gr
���Get5��0Hr
��0Hr�"dHe�Hy#0�.�s�Hu
����xHrT�����Hr�#0�Hd8I��H���HhL?�l&���Hh<0�HpP���Ha8IhIl,Io0H��Hr<,��I�$IÌ.�40$Ip��.�Ie�Is8IY$#
�"��TI�8I�Ii\I�t$��0xIa�Io�+���0�I.�Is�����I.�#L��8a�2���Iix2��Iu����.�Iq<0�n���Je�ed%��Je��� Jl@J��"�.�?eTmhJo�Dr�s�4p0`JmL(�0%�p0|Jvt$0�Ja4���Jix&0�Jt<8qH8���Jm$80�Jr
�Jr�"���J�K�L?�J�$#�Hrd%q���Kl�&0 KePKr�:p0<Ks'��DKo�)|��\Kd�0dKr���pKa���|Kd�0�Kn4���Ka�Ke@Li`Lo�Lr�Ly L�,qL���Kn�0�Ki@ ��L$s�	�Ko�	���K�0H���K�|OLr�N��L�h	q���,Lm04Lg��,:��LLk450TLc|Lm-0<Ks�4��pLoL?3�.���Lhp0�Lp�Lo�Lu�-;4���Luh.���Ltt.0�Lc�Dd%���Le0�Ll��<Ml�3��MahMi�Ml�MuTM�.��MbNr��xt�>`Mr��HM�0HK0|An,x�%0tMn<3��|Mix30�Mm�Mn�3�Mi�Mu,T
q0�MrH8��#o$s$80�Mr�
���Me4N�@Nt�8��NaHNe�Nh�Ni�No�N��"Q�10,Nh4X�-�dNaxNl�Nt�5R���\Nu�%R�-��pNl4Q�8>�8���N�TH'<3v�0�Nm�Nn�(�����Nf00t4qp0�Nf�Nx8<q ������|O���������#0$<a� Oy	��,Oh�V,��DOt�0LOn�XOiP��dOh4	�Ol�<$eAi�Ooxs����O�l���O�:@ ���Ob�Op�.�0�n4���Oe0,���Otd%���Oa�=�OtD<��P��
��P��4�� Pp0�Ot ���Oe��DPt40PPn 	��\PoD-0�Ot�
��tPoh0�Pp �Pi,���.�Pr�Q���Oe4���Pt '�#0�.Qr�s�Hu-0L$sh5���Pot5���Pm�9� Qc4Qt	��xt�9R�9��,Qst$��Hc�Bn<th0@QaxQo
TQi$4�+��pQnd%4���Ql���x:s�Q���Qg%0%�+���Qv0�Qa�Qo$4���Qip(t$���Qx�LY�K���QrX��Rt ����@R��������4	xRl��+��LRi�:��TRk�C0`Rsl��lRo�P���.�3���Rt�30�Rn�,���Re0�Rv$4���RiL(���Re%���Rvp���Ru@ 0 Sl�1���p�0�Rm4��Sad%��St����npS 'Y���@Sh���HSt���h$lTSs��`Sa�Ss�K��H;oX���St���a�eipouy�S� ������S���e22200242030000100012010210034201000000100210100120000001001210220100230023201020002002310020300004000400002000002124400044000022000003032103000004000002420002000000200300001004040004040000230202100021301010100000
1030000100200002300012212002323202303230000320120320210232032032000003200032303230032300032010021002120320000120001200231020000	10200000010200120
10200000001122122034010010123010023000100	230000000230120230010030000004110022101001000203040503003000	10000000011022030010020010043212000021210021010002101012001200100120000230000021020230000
1200000000212000212002121000130120203020100030200010010012012020120340003400004500000450000340022412000001220231000444000444000022000	1200000001200120120001001201003203032003210120PK
!<��]YHqHqhyphenation/hyph_gl.hyfHyf0tP�����'-4�������$�'–’-111001l�������0.�adb$c�def`g�h�i�k l�mnDolp�	q�rHs�t�u(	vP	x�z�ä��la��$Y�8Y�XY�t�������a�Yb(
c8Ud$e�
f�i�l�YmxnlUr�Ysx#tu���_d�]m�_n|_s��
`[a�c�Vd�e(idl�Ym�n�UrDsDux��D��
`[apc�_e�hXi�m<o�#sxu��D�o��\a4e<ito�u��<$Y������j�8Y����]�������]���0����.�a�e�o�"s������.Pt����.�a�e�o������.�c�r����.�a�"d�e�o������.�$0DP��������a�e�o�����i "ldn����.��
�abHc8Ud$e�Wl�Ym�n0olUr�`sxuP����.D���.0$Y��<.���D����\ap
c�e�
f�
g�i�	l�
m�oT
p�	r�
t�u��d<n��b\o�"uD�$Y����������8YXY�cg�r��ap
cpe�
f�
g�	h|i�	l�
mn�oT
p�	r�
t�u8z���f�cPtD&a�(e�*i�n�,u\&���\ap
c�e�
f�
g�i�	l�
m�oT
p�	r�
t�u���cD/s��n��	\a4e<i�	lDo�	rt�u���D�nPt�@s��
Ta4e�	h<i�	ln�o�	r�u����\a�e�i�o�u��lcxl�m�x�dHp�sD�m��	\a4e�	hi�	l�o�	r�u����l��
\ap
c4e�
f�
g\i@
l�
m�oT
p�
t�u���H#b��c�g�>l�x��
hap
c�e�
f�
g�i�
mn�oT
p�
t�u���a$g<l�nto�Hc�l�nrD�n��l��\ap
c@e�
f�
g<i�
mLoT
p�
t�u��0cDpnTr��l�r��\a�e�i�	ln�o
r(st�u���in#r�PeD�l�Tt��d��
�ap
c e�
f�
g<i�
m�oT
p�	r�
t�u���Mi�tdJu��..�
n/s��4ap
c�e�
f�
g<i�
m�oT
p�
t�u���..�/d�m�/n�.sD�b�c��Pb� p��\ap
c	e�
f�
g	i$
l�
m	oT
p,
rDs�
t�u��h!rt!t�T"oD�!p��\a4e<i�	l�o�	r�u����\ap
c�	e�
f�
g<i�
m�oT
p�
t�u��,"nLo����	u�#�.�' #�.�#�.�#�.Xi�o�#�.(a #�.apu�#�.�Ge�o '�#�.�Si"o +�.#l���
n�
sL
t�
#$���
nh
t�
z#(#����
n`���
n��L
t8#D#����
s����.�
sh
/p
���
tx���
c\�
n����.���.H���.�e����.H���.\3���Pa��Xh��<.07	|.����e����t��b����i���h����e ���.\'a�d<*o?(
��a�?`��,aoT?@s�E@��8.\?���Lado	?0MD��l.`[��l.�b0S4�.�s�	#�e,^���l�\D���. Uf8U���o(���d`[m	�a8
e@
iH
oP
u`
��
���o$��pZh
t�_mXm<mxm�]���X
�|
��
��
��
�8YXY���a8
e@
iH
oP
u`
��	#�
o���
r��a8
e@
i�jnH
oP
u`
üU��	�..x\d \e�\lP\m�\n�
o�\t]v���jd�jl�]m�jn�
rks�m<Da�e�i�oP
u�ì
��Li���.`Udlf�g$m�m0mDP����|
��
��
��
��	#�
o�
���l	�adelito|u���
���e�ix��t���nr�� a<bPc@d�it=m nTp(r0 s,!t�m(mDmDm���X
����
��
��
��	Da�e�i�oP
u��T"m�p����ae�i$oP
u4Ø	�i�	���ulU��q�p�p�t���,�|
��
��
��
�����o0���a8
e@
iH
oP
u`
�\\o�	#|i���l�\o(V���ilU���d<*�a8
e@
iH
oP
u`
�����o\�l���aH���l&m�$b�$c%n�$rL%��a\epiH
o�u��$��0o�(m�%n�%r|%t�*m�%n�%r�%t�,m\&���$�|
����
��
�x+�m��a�eitou�,
#�a�
���r���t$m�mm�����8��
��
��
��j���ol��@t<Lp��a8
e@
iH
oP
u`
��do��m�nL�a8
e@
iH
oP
u`
����o��a�eitouè���a$mP.m\�a�eitouÀ��a���p.s$z���0e�Y�a8
e@
iH
oP
u`
��Y��Lo\lm���xax���o�n$���a�o&m��a�e�iH
o�u�Ä(m�*mP&\&����|
����
��
�"�a8
e@
iH
oP
u`
�,
#o�
��4r���@t4Lc ��\'aXe<*o����
o���.�#axd�t�
���o�r �ade�ito�u��,
#�e�MmdJm$���o����
oP	��PEp�tp
���ox��c�nd7��4r�Y��4tD���a8
e@
iH
oP
u`
�
td��|il#�a8
e@
iH
oP
u`
�x#���oh�t�Y���a�i�Da�e�i�oP
u����a�	#�
o8U���r�y#��aTe\ido8"rlu|àrl��<e#ydXy�y�y�W}�W��t����������}X}}d7���o�`���tt���a8
e@
iH
oP
u`
ü�o����edo�U���
o�rd��e����o��#.(n�#s$��t$apZh4o\p�6m�6np7u���\a�d�e�i�ot�u����pn�s@p�7d�7f�7n8p,8rh8s�8x<p�8c�8m�-q�:p�8cP9p�9s�9t�p�9i:lT:nx:s�t87n����8�D�L�X��t�7dtt,9ct�:tTU�a8
e@
iH
oP
u`
�`U��do(�a�eitou��	#�a�
���r��`Ud�f�:o�th!��ae(ido8"rlu8�	�r���e�Uy;sdXy�;n`;o�W��t�T��������}�:s��a8
e@
iH
oP
u`
�`��`o�W���o��a8
e@
iH
oP
u`
��	#�o(
���r@Pt���e`���nP	��LiT�a�ei�ltou�`��a\'�nd��0aH���r�m Dr\Ta�e�i�oP
u�àW��`i�mHDt<�a�e�i�oP
u������i����i "���tTX���a�eitou�dX�alU���i\���n��a<e����r@��Lo�
���e���Ht�
��a�o$��`t��Li���|n4Lo����e��a�eito�u��lU���amhDg����8���
��
�XY|Ds���a���tt�a8
e@
i\oP
u`
À��$o$��Dz�����Li�dr�mEn�Dr\|a�e�i�o�u�� ���i0m�Dmxm�DrD�DrP�������
��
��
�8Y�Ds\y\E.�EidEl/s�
��ade�i�o�u��#��t���<sl��Ho,r	y\E.�Ea�ElFr�.s	y@FgLFl�Ft�Fz	y\E.�Fi�Fs�yGlHGr�}�������������}�Fn}Gn�Gm�Gb`GmlGr�G�ade<iDo�uL�
ehoHm4Hm�G��tG����
��
��
���a�e@
iH
o�u��tHm�Hm�H��X
�|
����
��
��H�tHePt�Hu�H�D��xd��u8U���i`[m�Hl�Gs ���ade4iHoTud����e�MmIn�It�IxDm�IsdJm�Im�In���X
����
��
����Im�In���4r�	#�ed���r�Y���i|/o|\op���i\y�..�edbm�en/s�P���a< e�Oi�OlT o�Ord ul ì���b�gpH��  uPQy�..8bnPPr�.s�Ry�..hPrXRy�P����� �$P������Q}�y4jb#��� aTe� ido8"rlu|à� rl��� e� rdXy`;o
#�aX�adelito|u��d��� e	��!l��� !e�!r�U�a8
e@
iH
oP
u`
�V��<!o�U��\!m����r��a8
e@
iH
oP
u`
�l���!o�Sm�S�!a�!e�i�o�!u�!�,
�!i�Tm�Tm�T�T���!�|
��
��
��
���!p ���t����o�	��	��	��@"u0��l"cH"q��p��d"o����x"�d���"�x���"n�W�8U���"a�"e��\'�"l0��`���".����"a�K���"i�U�"m�U��#eP
��
#u��$#o0#r��<#e�!#u�!��T#o	`#p���l#o�#u,"���#o�	�#nP	���#e\�#xH���.$a�#o0���#.�#s���#���#.�.�4$.h$m$$s�.�/��$.|��0$.���8$e���D$t�P$n�Y��\$e�#.�$s�Y���#.4[3`%tP&�������$�,^���$ìV��U���$vp�$r,_���$e���	#�$o�[���$rl'��%d %oL�lU��'��(%r�'0%aX%o�'��<%�D�\x\h%n0*��p%a|�p
���%i�)���%c�U���%cL+���%c�+�,���%a�%o(,�8,�L,���%a "���%l`���%u`[��&g�&ll'n((t�'� "��0&l`��8&u�]��D&g���P&�x+��[��[l&.�&s8]��t&a�&e,'i�[��[���&.��p�&np
���&eD���&c4�&s0���&.���&nP���&�|P'o'�H��'i\ 'cH���&.��8's0��D'n\�#.�$s\��\'a�'t��T��|'��S�'�,
#�'i�
���'r4W�W���'r�W�'i8U���'e����'d��'a����'�0(.<(s���(�x#��(�0&�Z��4(.$���&n|H(eH��T(i<`(c��l(i�_��x(f�(l�)n�)s0*tL*vD7.�U���(.�V�(s�V���(o�U���(nX�(rd���(e)l�(.4)s@
+)a`)e�(o\(.@)s�8�#��,).�Y��,).lU&(��L)r4T)i@
@ ��l)l�_��t)l�)xl`F�)a����)�P	���)�\4(.�)s����)a<�)t|_���)i�N�Y���).@W��*e�*n���*����$*���#.�#s(	��\'a<*o U_8U��\*o�"d*d�"��p*aX��|*d�*e�*mL+n�+p,rL,t�+�(��*i���	#�*a�*�d���*r�Y���*b�f����*��n��+.`[��+u +a�#(+e���4+r���@+t���X+a���`+�XY��l+��+a�+e����+�(���+i(.�+s����+�l���+�H��4(.��#.�$s,zlU���+a(,o\_���,a�Z�#.�#s\�#.|,d�$s���8,a�,e�,o�,u�,��#x Uh,.8U��p,o	�#.�#sd�	�,lD��4(.�,o����,�H��(.��,s$/x���,c8-l�-q�-r�-sX-t.v(.zd���-b� -m "��,-o�,
#D-y����-a�,oL-r���	l-i�	��t-u �,l����-e4$.$$sH���-a�-e�#o<*_d���-o��-l8U�\�-d<_����-i<.n(	��.i�/����0.t�8.n�]��D.e�/y�4b�4f�4iD/��\.a�.e�.i�.o�.u�.�41y,5m�5n�2yd6d4/yx6rL4y42�����.��.�����$2}03}0���D���..�Y���..,
����/rx��/t��3l(/uD���/a41e�2i4/ox4sL4u42��#��#��h/.�Yp/s�Y��h/.�W�/s�V���/e����..\�����/i��/l�	#�/a(
���/r4�/cD0gp0l�0n�0r1s 1z�0�4��`��0a��� 0s�,0i`��80u<����P0i\X0n���d0i�	����|0r�
���0g������0��`�\�0s@
+�0i ���0l��0l�	#�0olU���0rd7��/r/��1tt����1o���
,1c�1g�1l�1n�2px1q2rh2s�2x�1���	d1e�	��l1u8U�����1d��1i`���1u\'�d���1a�<�1bH���1i�/���1s����0�LV��U���1t�
����2t�2r�2x���$2�03�#��2a�D2rT
��P2e�.��\2p��x��t2z�|2n����2r����2�l���2��P	��U��W�2r8U���2e<�2d3g\3n�3s�`��3n�`��3n$3gp
����<3c@D3n���P3et3g�	��
��l3rX7�d7���3e�3i�`���3t	�(
�\�3c�3d ���3a�3d4i8U���dX�lU���3i\'�3r8U���3a\4d��� 4d���(4u��44t�@4e�
� "��X4f�`4lH��l4u���4r�Y���4o�<�4a�
���4i�4�l`��4a�4o����4�D�lU����4rp��5cd��5oP5ô��5b|5p$F<5c���D5�����\5��d5�T
��p5e�5À��|'���5�����5�(
�\�5c�5z ���5a6��1���5l(6v���<5c6z����5��F ��6l(	��6oL6� F86l���@6���4a�2��X6i�����p6a0����6.<�6b�`���6i��6s�Y���6i�6o(	��Y�6v@�#.x���6e7id7tH�<7c�-d�	�Y��7e< 7m��,7i�D��D7.	L7s�
��X7e3���x7a�V���7i����7tY���7i�Y�\�7b���7a�	lU���7r�#�7r����7a$l��8c8tp
�U��$8cL8mD8tT8vLVV�V<�6vD��\8i�_���t8a�|8r���8o|�H���8i�8u��`���8g,z�Y���8i��p�8n�:���8e9u�:l-i$9l9oD��:�$��8&u����#.�89al��D9i\_�W��\9i<d9l�9t�#��p9i��l&�9p����9�,
#�9o�9ü���9r�!"�9p�����9t�Z_�Y���9e\:s "��:a4:i���,a\(:n\����@:ad��H:d���,a<`:tH��l:i�W�	�:l����:ilU \�:r "���:a��:lp���:uL�:c0�H���:. ;aH;o4�dK��;a@;o0;�4(�����(;�P;����(���0��X;r0��:l;.�;s����;a�;it;o���#���;.\l;.�;s�Y���;.<�3d�#yd<���;a<e<i$<olu,<ä���;nd���;e@�L<yLy�����D<�����}<3g�<m�<n$=t���L<iX/�W��p<��U��x<ü�<r�Y���<eH���<s����<�����<e�<�D�@�<s\�����<aXY���<t���=�	=ü��=o�>y�=b>c`>r�>x�>��0=a�=e�=i�=o�=u�=�h��H=l���h=a�@�|Cd�Cn�Ay(Cyt>g�yx6r�B����D<��=����B}lU�L�=r�Y���=a>�����=r����=����>��>o4>���>��,>�8U�h@>dV��H>a @��T>m�	8`��l>r\�P	���>al`��>a����>�(
��(?i�>�\'�>c`?gt?i�?n@q @rt@t�@u����>a�@e�Ai(Co�B�x=\?nP��?a|?t�����4?���<?��H?e`��T?u�F����?al?o�FpL�?n����?��	#�?i�?�`U���?rx���?d�V<�?n\�����?a����?t�	�?i�	��@ulU���..@@eX@idK��h/.�U4@sTX���;.dXL@al#l;.�;sx#���;a�@id@o=���@nP���@�	�@��@i�	�,^��Ai�@l`[���@b4�@a0Ai\AohAt�Az�W��Al4�H��Ao(��$AclU�\<Ar ��DAaD��PAl����;a�Ae�A�p�|An����A��Z�lU���Ao(���Ar	�Ai\F����Aa����..Bn\�Aa$Bc3gdBk�Bnx�`Cq|�"a8BoH��Bi�#�0��0Bs\����DBaLBt���XBi4����pBe���xBk�_`���Bo\�Bg���Ba�Bf�Bn����B�Hh<�Bc���Bil#�x#���Bo\Ct��CaTCi<*Cn=�4Ce�W��<Cu<HCl�	p$w�hCc�V��pCu�}�Crp
���Ca����Cc�Cs�Ct�}<�CnH���Cix�����Cd	�Cn����Ce0�dX,DaDolU��DiTX����4Drx#��<Du@�TTDs`��\Da���Z��tDo�/������De�	lU���Dq�	����Dq	��
���DeH���Dt�	�`U���Drx���Dd$Et`[�	Ea�
��Ee����0Ei�8ElT
��DEo0�����..�El�"��"��tE.���|Es��@
+�Eo,[�4[���E.`[���Er�#�\'�E.�Esd���Ea�$��h/.0�h!���E.FgFi�
�dX�3d,Fo0���2r��`��8Fo�W���1a|FlV\Fn���dF�@
+pF�<�p���F.	l;.����Fetl;.�;s����;a�Fo�#���..�Z�lU���FoX���Fr<(��Gi\� "��Ga0Ge8Fo4��l;.�;s���8Gah�=r�]��TGa4[3�G��$��$��|G��$���G�,[��]���G.`[���$b�Gd�Gs �GaHi4Ho�Gà[��t&a�]�����G��/�M���Gtl��Hpp��HuTH�D��$Hcl@Hp���HH�\����`Ha�_��hHza�DY���HoXY���Hd����H��
H���Htx���Hs8]3�Hi��\Id�Hz�W�8U���He\'�Hd8U��Ia�M3dIa,Io8O�#.<Is�#�	W�
��DIe�M��LIttMXInh!	pIr���xIeP	��`Ha��#���I.h�����Iah�����IaJ�HJ���Ii���6.xJs���InP��J�<	�KadLcKd�KmLKn�Jr�LspLuJ�d���LapKeJiXKo�J�D��HJn0H��pJ.lU��Lo���
���Jo����Je�Jm�KaLMd�Km,Ln�Jr�Ls���XM��J� �&n Uh,.Ks8U��(Ka<Me�Jo�#��#��K.\'h,.8Ks�$��K.`U���DKdL�#.D���#.@�#.�KndKs�YKs�Y���Ko�W����Ko����#.l`�#��#���K.�Y�Ks�Y���Ko���Ld�Km�Y���K.�WLs8U��Le<�p��$L.,)�XZ��8L.dZ@LspZ#LLeH��XLhx���#.L7���(.X7|Lsd7���Le�`���9e�Lt�
&�Z��4Me�Lt(�|Z���L.�Z�Ln\�#.Md�Km(Mn�$s�Y��K.�WMs8U��Mex���#.�ZW�W_YWDY��DMe�hMd�KmY��DMe\�#.Nb,Oc�Md�Km�Mn�Jr�Ms���tMaOe8Oo�N�(���Mn8U���Me�Jox���#.DKd�Y���#.XOe�Lt�Wh,.Ms�Lh,.�NdHNm8Ks�Y��NaK�K��(N.�K0Ns�K��<No�Y��Y��TN.Z\NsZ��hNoZ�NdtNm(Z���Na��Nb�Os����N�M��(N.M�NsM���Ne4Z��TN.@Z�NsLZ���Ne(���#.@�#.DOdOi�Km�KndKs(
��XLh�:pLu�V��Me$x�ZPO.�Od�Om�On|Os$��Z��tO.|/pJ.����Ooh/��/���O.�/�Os�/���Oe�Z��4Me [��tO.(S/0QeDQi�S/Q�bahcc�adbmtan0ar�cstcu�PDbalfd0cnfs��Q<Pz�Q��DPi�R��@:a�R��\PnH7�tPc���|Pip���Pd�Pn����Q��P�d��PQeQi(Sl�Ro�SrXRu�P�H
��Pc Qz`U���Pi����Pd�Qn Ro�
(	4(QvSS<Qm��El�Qr4_H��`Qo<hQc���tQi��Qr0=\�Q.�Qs����QadX�Qn�U���QiRo��Y���Q.�##�#���Qo��Qs	����Re\��Rt U#8U��,Ro\4Rd "��@Ra�LRl�Rr0�LhR.�Rs��pRo����Rn��Rr�+�#���R.\���Rod���Ri����RblU4\�Rr����Ra��Rn�Y��Si\Sm�	#Si>��4SnP��<S�|HS�$��TSil��`Sc�St lSp�	#|Se|IH���Si	�Sc���SiT�#.Ts`���Sa����SgTlPTn<�Sa�Te�Tu�T�@���#.����I.$Te4dKs "��0Tl���8Tux��DTg�#S4\T.H��dTo<pTc�Z��|TiD���TsP���T���	�Tn�
���TiD���Tt$���Ts�
d���TfUvx���Tn<(%r(	��Ui��#.DWi�Vr�#s���\'a�We Uo0��LU.���TUo����.(Vd�UedXi|VlVm�Vn�ZoLVt�Vv�W� DOd�Wi�Km�KndKs���	(Vd�UedXi|VlVm�VnLVt�Vv�W�#��D7.��Us�
��4Ve�UoD/��D7.�Vs���Ve�LU.	LU.�
��@Ve0F4XV.�Vs@
+`Ve ��pVlL�Us���Vo��Us(	���Vo�\D���V.���(.WaWe�DWi�Vr����We�Vo�4(.D���). Ws�Z�6.XWslU��dWa$WoX��4Wr�e�#��PW.��6.tWs�Y��PW.(��4Wr��#.�Wi�Ys ��\'a<*o�M���#.�(.�Wn<(s����W�X���4(.l`��$L.�Xd�Xm�XnXs�Wa�`o�`��X.�#x�#��(X.�Y0Xs�Y��<Xo����XdHXm<TXa�`��`��pX.�`xXs�`���Xo�Y��(X.�W�Xs8U���Xe�`��pX.�`�Xs�`���Xe�`��X.0�D���X.�Ys�pYa�Xo���Ye��(ZbYd�Zs��Yd���aaYeao��l`aDYdpn�Zs`[���X.�Ys���#���Y.,[��]���Y.H���.�Zed7t��#s����YoD/���#.d���La�#��#���Y.�Y�Ys�Y��Zo\LZdZmd��Za�Y���Y.�W4Zs8U��@ZeD��,).�XZs�	#dZe��(.�|Zn�$. [n�Zs�.��$.h/�p/���Z.|/�Zs����Zo�[d�ZmH���Ze�/���Z.�/�Zs�/��[e�/��$.��lU��	,[.x\d \e�\lP\m�\np_o�\t]v���,^b,_c�[d8]l�]m\n4[r�]s�#� U�[.�[s8U���[aH]e�[o\'�[.�[s�#��#���[.�$���[.LU
TU�[.`U���[ox��,[.�[ddK���[.�U�\n\sD7��U��0\.�U8\sV���\eD\oV��0\.V`\s(V��l\e�K���[.4V�[.@V�[.LV���\eXV7`V�\.]spV+�\e|V���\l�V8\s�V���\o�V8\s�V���\o�V��V��].<*�[.�[s����[a(]o�W�[.^s�X��X��X].�]sY�]a`]oY��p]e$Y���^b�]d0`spY��X].�]s�Y��Y���].�Y��Y���].�Y��,[.�_eP_t�Y�[s�Y���]o�Y���[.�L�[.�^dX^m�^n�[s�Y��^aK�K��8^.�K@^s�K��L^o�Y��Y��d^.Zl^sZ��x^oZ�^d�^m(Z���^aM��8^.M�^sM���^e4Z��d^.@Z�^sLZ���^e(M���[.,)�XZ��_.dZ_spZ#_e(
�� _hL7��0\.X78_sd7��D_e(�|Z��\_.�Zd_nD��,[.���_d�]m�_n|_s�W^s�V���_e���,[.$��Z�_.``n�_s$��Z���_.�Z��Z���_.�Z�_s�Z��`o�ZT`d`m�Z�� `e�Z���_.[<`s[��H`e [���_.���<.�`d�`m�`n�`s���Y���`.�#�#���`.�Y�`s�Y���`o�Y���`.�W�`s8U���`ex���`.H���Zed7t� as\(as�#��Y�lU���..bd�ae�di�am�cod�LU�TUTa.�P��\aoQ��had U�E.p/s8U���Ea(be�ao�U\dd�ci8dmbn4@sD7+�U���a.�U�asV���aoV���a.V�as(V���ae�K��h/.�Y��|/o�W�E.�/sd<���..l`���F.cd�bm$cnhbs�`�`��`b.�#�#��tb.�Y|bs�Y���bo����bd�bm�`#�`���b.�`�bs�`���bo�Y��tb.�W�bs8U���be�`���b.�`cs�`��ce�`��`b.�P���F.,)-XZ��<c.dZDcspZ#PceH��\ch�"���..L7���a.X7�csd7���ce�`���ee�ct(�|Z���c.�Z�cn�W��h/.4(=<(���c.�W�c.hdn�cs�W���c��d�K6K��d.�K ds�K��,doM��d.MDdsDO��Pde�W���c.$L@�W��td.hed emten�dsX|daXIX���d.(XS0X���d.<X�dsHX���doTX��Ded�dmdX�dapX^xX��e.�Xes�X��eo�X���d.�X,es�X��8ee�X��e.�XPes�X��\ee�X���d.8U���/e�fox���..$��Z�e.8fn�es$��Z���e.�Zj�Z���e.�Z�es�Z���eo�Z,fd�em�Z���ee�Z���e.[fs[�� fe [���e.�Xu�X��Df.�fsY�faLfoDY��\fe�Y~�Y��xf.pY��Df.�fs�Y��Y���f.�V���c.�fa�fe U�frW�c.gs�)W���f.W�fs���Y��g.lU���..bd�ge�di�am�codРy�..hbic�gdbm�gngr�gs� ��8ga�heio�h� `gr� ��xge`U��\aox���..�gd U�E.�irp/s����Ea(be�go�U�cibn4@s�Y���..�ee�ct�L�E.�hd8dm�hn�Es4j���ga�Yj�Y��$h.Z,hsZ��8hoZ�hdDhm(Z��Phat}`hb�id�is|��lh�M��Pde4Z��$h.@Z�hsLZ���he(M��h/.�#��W���h.Ty�..�id�hipim�in8irDis(
��\chjix���..dyiu�U�\dd8dm�U��(iedK���h.K�K��Pi.�KXis�"��dioM��Pi.M|isDO���ie�K���h.<(���eeY��\fe�V���c.�ia�feW��c.gs0����i.���inP���i�|j�4��	# je�Y���ji(jl�W�8U��Dje\'Ljd8U��Xja\djd�W��pji�|jl0
�
�j.x���jo����jdd���[a(]o�V��H]e8Y���]d�ks���j.�.���j.�kd\km�kn0ksD��ke�.��.��(k.h/�p/��<k.|/Dks���Pkoh/�p/��hk.|/pks���|ko��kd�kmH���ke�/��<k.�/�ks�/���ke�/��hk.�/�ks�/���ke�/��(k.30301010020200010002000010212022014023020030000004010440020004000040000000	4000020004001041211211022122102221302302230012000230002200032300023232000
2303000000202210002000002001002100201002030003300033200023000000
230000000020000100	2300000002300002300000	23200200023020002014020000002300200023001002301002001000	2000020002323000300032000320120323200032300100323010032312032300
3230000000323032300003200000320000
430000000032030320100	32300010020001002030100200200023012024000003203003230100021203230000032100	32000000032000000320301003203000430000100001000001000000203002012032003220003231002300010023232000	2303320002320000	23230200023230000
232323200023203232000
230323200022222023000004000	323002000320010032002000	340000000
3400000000	3400001002032300020002000	23000200023202000	230232000
232023200020300002030200020303000	2032320003002000300020003030000303020003030300033232000	303232000330200033000020200020232000300320200032332000	32023200032302000	32323200032320000	323202000
323023200032320232000
320323200032030000	320302000	320303000	300002000101001000100	30000000032300000100PK
!<oȚchyphenation/hyph_gu.hyfHyf0tP�����'-4�������$�'–’-111001���������(���4�����
��3l��l�<�<�<�<�<�<�<�<�<�<�<�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�d�l�<�<���D�(���
<�<�<�<�<�<�<�<�<��<�<�<�20021001110002001PK
!<�<�((hyphenation/hyph_hi.hyfHyf0tP�����'-4�������$�'–’-111001���������(���4�������4|�|�|�<�<�<�<�<�<�<�<�<�<�<�<�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�t�|�4�4���D�,���4�4�4�4�4�4�4�4�4���|�|�<�<�4�4�200211001100020002PK
!<�)�|XXhyphenation/hyph_hr.hyfHyf0tP�����'-4�������$�'–’-1110014�������.tahbhc�dtehfhghhti�jhk�l|
m�
nto�p(rd
s4ttu�v�z��@�t��la�b�cTdle�f�g�hli�j�k�lm4nlopp�r�s�tluv(z$���th���a�e�i�o�ut���aeiou���8���T��aeiou����a�e�i�o�u��ttatetitotul��|�����a�e�i�o�uh���a�e�i�o�u����a�e�i�j�o�uT	��aeiou|
���a�e�i�o�u�
���a�e�iTj�o�u0��aeiou����a�e�i�o�u(���a�e�i�o�ud
���a�e�i�o�u���aeiou@����D�4���a�e�i�o�u����a�e�i�o�u����a�e�i�o�u���aeiouh��`b`c�d`f`g`hjkl4m<nPpXrds�t�v�z��tŨ���������T���l�����.h�.,jT	#|
�
Hj0�(.d
�@��l���4�.����`b`c�d`f`g`hjk$l4m<nPp<rds�tHv�z��t���T������.(�.4jT	.(.�.��`b`c�d`f`g`h�jk$l4m<nPp<r�s�tHv�z��t��d
�t��`b`cd`f|g`h�jk�l4m<nPpXr�s�t�v�z��lż��b�cd�f�g�h,j@kHlhmpn�p�r�s�t�v�z�Ĩ�@�����h4�.�a�4,jd
�m\p�t�Tk�.hj�lpr��8�=�������(=d8 �tC�����J8.P�84WX.`j]Dd�8L|j�.�8<WX.�8�=�������\8HWX.8(=��`b`c�d`f`g`h�jk0l4m<nPp<rDs�tTv\z��t��,j(d
l�t���g��`b`c�d`f`g`hjk�l4m<nPpXr�s�tTv�z��t���jT	.d
lp��`b`c�d4	f`g`hT	jk0l@	m<nPp<rds�tTv�z��t�h�t|
ts`���L	bL	c�	dL	fL	gL	h�	k�	l
m
n$
p,
r8
sX
t`
vl
z�	�H
ż����	��	��	����	������	�08.
j,#4<
jHP<8.Dlt��@
�t
��T8.\���`b`c�d`f`g`hjkl4m<n�
pXrds�t�v�z��t���t��`b`c�d`f`g`h0j�kl4m<nPp<r�s�t�v�z��t����.L	bL	c�	dL	fL	gL	h�j�	k�l
m
n$
p,
r8
sX
t`
vl
z�	�H
�h�cd
t4�nDs�8.08.�j,k��`b`c�d`f`g|hjkl4m<nPpXrds�t�v�z��t���`b|c�d`f|g�h�j�k0l4m�n
p<r�s 
t@
vL
z��t�h�n��k�����������n��4bl����h|k�n�t��eh�n�
�cHjLk�t��c4.�c�kdm�nDs��n��nh.��X
b`c�
dX
fX
ghjk0lHmTnxpXr�s�t�v�z�
ĜŨ.����
��
��
�T.�.�q8.l���
�h.�rh.�l�r�<jT	.|
.�
.dj0p.w��nd
.�.@������4.�.�l�.�.h.���b`cdd�f�g�hj�k�l�m�n�p<r�stHvzP������������������.��}�.l��t���8.h.�4j|
.�
�j0.�.d
.�8.@����(�4.<v�.�8.��`b`c�d`f`g`hj�kl4m<nPpXr�s�t�v�z��t�h�l��`b`c�d`f`g`h�jk0l4m<nPp�rds�tTv�z��t�t(��`bX
cLdX
fXgX
hjdk0l4mpn|p�rds�t�v�z�
Ĝż�h�nh.�
dj�.(�j4.�.�lprh.���b�cdd�f�g�h�j�k4l�mLn�p<r�s\tHvz����(�����T8.�.DjT	�
.�j4.���#H<xj��H�X��4p4�40�`4�X#0�<4H#<�j0�X���g���b�i�nz���.�4�4��x�44x4d44��t#���4��t���j�
���ae(�t���zh���e(�t��s�z��l�t��o�412100100010000200101200200021002000020100210101010100201200100102000001100022000020010022100102002200010020020000002110120201020010102021123PK
!<��0�H,H,hyphenation/hyph_hsb.hyfHyf0tP�����'-4�������$�'–’-111001�(��������.�a�bc�d�	e$h�
ik\l�m�n�oHpXr�sdt�u|w�y�z�ü����b 
cl
d8f�
g�
hj$ktl�m�n�pp
rLs�
t�w�z@
�\
���L���h�������������a$e,i<o4u�������	�
���b 
cl
d8f�
g�
hj$ktl�m�n�pp
rLs�t�w�z@
�8���D�	(a0e@i�l�oltHu�ļ�L�����8��������	a`cti|n�os(thu���e���0���Dj4lLo�r<���`d�k�m�n�p������ae0iPj`l8oXr@uHy$�p����b�cxd8fpg�h�j$k�l�mnPp�r�s�t�w0	z,ČŬ	h"H����
����X\�'���h���|a�e�h0i�o�uHy,4<�,D1�a�ei�o�uy�	,�,�,�,�
,L���a�e<i�o�u����D�@,����a�ei�o�ul�h4���d����	�a�e0i(l�o0r@uHy���8�:��a�eiou y��������	:h@������
:�:�:�:\GXG���ae0i`l8o`r@uHyXKXO���a�e�i�l�ohr�u��\O�S�	ShW������S�S�
S$���a�e�i�o�u���a�e�i�o�u��S��a�eHihlXo`rPutShS�SXS\S��a�e�o�uy���p�<�\	�\���ae0i8o@uHy�ļ�������ae0ij8o@u�y��8D\���ae8iHj8o@uHy���8t`H��
�ae0i�j�l�o�r@uHy��L\D`4�X���ae0i�j�o@uHy$�����a�ei8o(p4t@uHy�X(d$��t�	��kH��e �	hape�iXn�o�t�uy��(j0j84���x�@j�jHjPo����pl���ad���ae0i`l8oXr@uHy|��	�ae0i�j	n 	o(	uHy���v@ T!����aT	e0i8o@uHy��!z�a�ei�o�ul����
�ae0iPj`l8oXr@uHyp���|	b
cl
d8f�
g�
hj$ktl�m�n�prLs�t�w�z@
�8���|a�e$
h0i�o�uHyD1�a�ei�o�u����P
�����a�ei�o�u���	�a�e0i(l�o0r@uHy�
���a�eiou y����
����a�e�i�l�ohr�u$���a�i�o�u�a�e�i�o�u�LaTe\oduly����p����~�	~�~�~�~\���a�e�i�o�u����ae0ij8o@u�y���ae8iHj8o@uHyH��	�ae0i�j�l�o�r@uHyX���a@be0i�j�o@uHy����,k��4s����a�ei8o4t@uHy�hape�i�o�t�uy|���ae0i�j 	o(	uHy����aT	e0i8o@uHyz�a�ei�o�u���	�ae0iPj`l8oXr@uHy��|a�e$
h�o�uHy���LaTe\oduly���@
�p���X���ae0i�j�o@uHyd��	�ae0i`ldn8oXr@uHy���b
cl
d8f�
g�
hj$ktlm�n�pp
rLs�t<wdz@
�8����ae0ij8op@u�y|���ae0i�j	n 	o(	uHy����aT	e0i4"n8o@uHy���b 
cl
d8f�
g�
hj�ktl�m�n�pp
rLs�t�w�z@
�8���a�eHihlXo`rPuŜ������LadTe\oduly����p�������a�ei�l8o4t@uHyl
��	��td\��|e���b
cl
d8f�
g�
hj$ktl�m�n�pp
rLs�t<w�z@
�8����b<cl
d8f�
g�
hj$ktl�m�n�pp
rLs�t�w�z@
�8���|a�e�o�uHyp
��n@Xr���du0pt���|i����b��	�a�bhcdnLp%t�w�!z�������b(c`d�h�j�k�m�n�p�s�t�w�zH��,��� e8y��L����@�X�������e�	��hz�1te����j�oT	1$������H���d�|�������������a,eDh4o<u�	�����$1�nt�1D1Tc��\h���@�d����n\�L�d���o����p���b�e�n8o��8�����	����t����j���nL�H��oL�� p���,s`z"�l."��Ha���Tn���a�e��Da�d�itjLst8ul�H��.T���a`���n��zl
���o�	���.�d�	�4���eH��l�S��dPldn�rx����<d\��Da������j�u\y��8��x.��t��ex���d����a���mX���o�`#j|n� wTzH%�t��e���Xa�j���d���a���$m���0o$��<r���Hh���\z�
����y��L���k����s���n�
����g�nH`�u����ju����k�p
���b@r��|td�<u����4.�����H����llP�\�� ��t.�$���a���hxŐ �h8��������Ő o�h��8����8��#z����o�%�h��8��$�L4�H��@o�$����g�n�z���p��+p�nx����p2x��������l�n�r�s8y�8����.X��j�u Ĭ	8D�eh>H���L��ll���,s�xD�eDLdxG�e\dd���pa�L�����������R����������pZp`�v���
���.�h����.d�n�vP��
��$l���,h��\dxml
nhe�q$v���ph���u���������s�wX���a�	�dl
n�aPe�i�o0ru�z�z0z@z4~���tD���s0G$a@�t��<�����
�b�d�fDj�m�no�p�wH�@��ae0i8oHy���b�	���r���c�eXi�k�n tt��Š���.�������$e���zT	\�dD,l���4a�
��@n�
��Lg�����da���lk���xs\���a������a����kp
���s����r����o���!e�m"n�"p�"s������a���kL��k���s\��(u�
��s��@i�	��sD�XeX��dj��Dj�n@ oprT!u��t��.�clm���a�e�i`u��.<j�	���.h jTm���S�.�
���o�S�.4.e�u���4.@�.���Huh��.��4.�a�i����.��j�S�.0�.��$
1�.
���h����.���e�y,��.�����n���td��a0n��|� ��8w���@t�����X.d��`o�
��X.\��xi���	���.�j����a�e�o���������.j���.����j����.8�����.���	�h��
���t����Pj����t��le�	��tlD��e|���"a�j�o$!u@L���t����s4 ���e@ ���b(d<(s� t�
����n��� sP ad e� i� ���� j���4 b!z@
��P ��	��t dX �l
���nh���� ��
��� m$4 ��� e 	� b� t���� o*�&t���� e*���� e"."��!a���!nT!8���h!j,!ut!yL��4!n���H!s�3t�`!e����!w�!����|!a�%e�����!�v����!d�!k������!a"�"���!a����!n�	���!z$�D"k���"a"�."��("a|"j4o\"uP"y����.8���.�h���h".t�p"eHv�v�9��".����"a����"w����"a����"z����"s����"rlCH��".T���"a`��#n�J��� #.���p#.(#m����#m�#tH$u4#y<$��#eD#s�S�[�Sx#.�b�#.D�#j����#����$y�#àm��#.���#j ���#a$e�#.�	���#j8�� #.�w�	��$.@�$$et��0$����p#.���T$.�$rl
��\$e�	��l$d�x$e<���$����	���$.�`�$e���$j�����$.����$c��$l���$ad���(a�$k���%.���%����$%��0%d8��<%��C8��T%.���%j\%u���h%n�!��x%t��p�%.8���%�����%.&m<&t�&Ĥ%�t��%e�����%.D�%j����%����l&y&à��&.�$&j���0&aX&e&.L&j���d&.���,x&.���&e@
���&�8�� #.��'j�&u����&n��p�&.8���&����'.H'mx't�'��&�t��&e�m��'.D$'j���0'�����'y<'�X'.�`'j���l'a�'eX'.�'j�
��'.��,�'.���'e@
���'�������'.t��'o���'jl
��(n����%.t�(e��$(jL��0(n�#
��H(.�	��P(cD�\(e`Sh(j$��t(r����(k8008056037040130200180832038003200302020308003202032080030232212212018003213401001030803020002102011020212010120030001870010870081010010043408051240110112012300040103400010001200021002100012000001220000020010870308700818701020002200040304030340001040300320003280041104142805104280542001104200110000434412805540540320540010044321200002023001003300230012200122000012200012220004010102000102000010200000108010800201402301234100420430010430000438004300010421423100112220	889889800898800888988008889800889800
8889880800	888988800	8889808008088800
8089888800888800	888088000	898888000898880089888980800
8988898800	898889800
898889080088898888000
88898889808008889888988008889888980088898889080088988800PK
!<�S����hyphenation/hyph_hu.hyfHyf0tP�����'-4�������$�'–’-111001����������.̉a��b��cLOd��ewf��gMh��i�Pj@�k�l�m�k	n8'
o�~pT�q��rh�sPf
tu��v�wp�x$�y�Bz��ØP�̉
ldg k�l�m4n�prtt�z��|a�b�c�d`eL+f|.g 2h7i�:j�<k�Dl�Mm<RnxUo�]p�bq�br�isDrtT|u��v��w��xćẏz�� ZŜ\�\��$a\�	,tl���8�����eD�<�Pth���`a�y����k\d���s�z��	�s������e8�$����.4�����@����i��((���s@���o(rTy,�#<��� a�4'����4g8�	<gh�.Ha�e�o���Ť�18�pmH���xa�t�8�r(��������8\�>��C�n�'H�EJt�J������������c<s$�	�cH�������DhLo�t�uü�P��.��TĉXx��T.`	\r���h������otü��
_����r��r�
eP�
������jl��p�ml�g kdp����a�e�i�j oLtlp���a<�P�pe���4��e���H�\#t��\a|ät���t�h�
�g��s�y����n�	�r���������e��T�l��t�lc�a���Hmpm0s��l
�`8kt��@axe�i��M_����dj�lr`�0	�n�rD����|y����n�t�y����n%�#�k$���a�e�����P���d@l�=��o02��(gLt��	(I��DhdiH�	�I\s�[jXZpp�Z��xa���#8�z�����8����p]���y]	�l$]�������dm.�em���yk�n@s`k��a�c`k`�tn.,e n��4z<o�Lo	Lcxo��T�8s��rll�r��ta�c�e�hi<oPs��x��ps�s��s	�l�s����H�t�t����t.o���z���z0t�@tn,op��t��t4l���t P�}��Xh�.t���lh��p�����a�e���Y	��	�m��������@�T$��@�@	c�	g 
lr
s�
t�������$�l9��W��X�\}��~�0����	k���	���.(	�4���4	s��8H���L	n�	t��	T	r����d	��	�@����	yp	��8����5�����	a��	�	l�	s��.�	a
�8������	z�����t8�8�	r
zL����	�����p���
aT
i�
l�
oD
�D����<
��
�pt
g� ��1�`
zt.h
a�"_d���
j�
t��
l$���
a�
i#_$��$�
nD��
k���D8C�
jdkpl�m�p�spC��
�
a�de(iTnho�r�s�t����8��D��D��LðD��Xt E�F��E��xeG�(�P��hG���z���X������H�4H	�c�l�z\H������H	(��xJj@KTm�KlLCLF�N rR� R	4l�Q.<e�Q��Hy(��R`l|m�s�S8T\�����s��	�s�����������ÌVj̣��X���r�p	�p��l$
r�p����Xo.��t_��
z,�8q��
bP
iX
oD
�c1Hq��<
�4q��]��g
��u`
d�
k�
l�
n�
r�
s�
vv��h
ae i0o|u�
ÔŨv��s
	�v���
t�t
�Dw�w��}
�w��w���
��T�t����xp�y�`{pt}�|(lDrLs�}���
�|~Cdhlv(�
	x�
	�~�����@��0������a ePi4o�r�u��D��P�����H������r����aHeXo<�X����b�j�kll�t�uؘ�x�	(l����0�\������Pl�G��WdtP�yla\�	xrl�����0����e��P��D�L�.�t8����z��s����aLuÔ��d�	�l(rt�����4�<�(�X���� o̝C��e,��<�Drh�ОXg����`ex�t���xsĤ.�e���������������l�r�tH�����(�l�������a,{����hĉh���.��t����a@dHiXkxs�t��j��j�����Pr|0
	����dr���lt�z��	�.�k������l�����e�	�t����������r��@�H�d����,5.��$4��s����c4o,���r�@���#����<e����Dklo�tp�#�ds|t��#��T�g�����a�e�ou����e����������l�r�����r<��rX��d�r�e�����`���ü���(gdl�n�r�t�\
#����Pad���Xt�y`
�_
��t�_
.|�x)�8� �vD����a���H��j�n�stx����aHots�t ��������4��@�	c,lT����t���4
_����4v��<d��
�h�
	Tt����\�����h��8��H�����������d�.���i�����a|�$�.�eT����z4����s��<e�i�s�� \t,\���L�0��ô
��$tX	��0nht��	eȨ�Tl ��\e��t,��x�@����e����l,����X"ed"����"����T���n�tP( �'���o�3)a�e�4m(4kHphtȋ,�5	(g�5��0��5��<�t�e7��T�7��\��5,�:	tr�:��|�X:����09�lLO�a�e�iÌ��P���c�s�O���cil�n�u`��Q.�Rn�R��a`e�oHØT�T	4s,T��<�p������T(U_4U	hk�n`U2�e9|V�l,e�EDW���n(_.�Um�t���g@i���nD�#T����aT|���l0rLs|`����4�T�H�T<sԃ��$e���P�>l���Da@�t����XoЋ`f�k�lx���la�c�<�t����a�#L�_\�����`�.��<����s��
B����������h��m��������I��rd���o@���(m4�#D���@a����Hr���b�gl0#s�#tP$vp$x�$zH����g�����e�i�u��0����r���l�����4�p�P������X���,r@y��T#�"��s�$s��� e�>H�.8a�e�i`��J���X������5U��	tr����|�d����ń�	�l�v�\Cd�	�n�J�J�D�8�l����k<nDpLs����a�e0iH lT oD"sL"tT"u�"v��8"��4�.\slz��	DX
8�.dn�o�v�\ �8�H����<�  �� �� �\"�TX�t�cg<hLjXlhm�n�p�rs�tv$[8\�r���e0y�\b����$.$���]	�]��Dt��@ #L ��`a�e��LA�!��|����C�h �g�l� ��e�s�tdh�dU�dU�eU� 	�s�!�"�l"��"P�"��
e`hhipn�s�t�v�zPÀ�#U@#	<sL#��D�����	�#P�#	�#$��x�@$	�npT$���e��,
v�n	�l�n������������9
	l$~�$�ķ�u�t z`%��e(i�u����%.le�%	
0d�g�h�k�l�n�p�rt v`xp0�1�%tk�%��|eP&	�&��&�zl&���eX'�'���e�'.�e�'p��(�ĉ�*���.�)�t�)��e*��*l*
(d`ghhpk�m�n�r�s�t z�*đ������xt�*���i<+x+�+��+��+���o�tĉ�$��.8����a �xv��
	�k��
����d�	,�� z ,���TP.��( e$.0 g8-��< et0Tl k� rL�	�0��d k| o���,1p1��1C� k� l"v��	@p����	� e� h!i!jP!n�!t"v� à!���x��� ��!�\�8 pL����. !e0!Ô���f����(!�@!�4��l�	����H!e�!i|!�8��ę	d!.�!m@���l!��h��@!�L!���!�����.�!e�!n�!�D��D��(����!��!�P�
����v�"	�!n��8��	�YT�1��"��1	 "�82��,"��7j�7j�9��9Cp"lĉ��:��h".�"h�"i�"j�"n�"t�"v��8$sp��8�8�:��:���"e��8X�t<�"l ;���"e#�$<��T.0<	�"g�;��#�\���#rT#t���� #eh#t����@#l����H#ed������`#a�#��t ���x#��#�ĉ��+
�#.�#k�+
���#e�	�#k��
���.����#h$i�j$����#g4�	�#n�
���#����$�̰$lܯ	����$$s �	,$s����8$�����D$��̽\$l$���d$e$��<���|$������$e�$�@����$l��
%d%gh&h|&j�&k@'l�'p4(r�)s�*vP�������$s���$sD����$e��p����%a8%e@&u�%�����0%g`%rl%s�{�|�L%iP���T%e��.x%z��L���h".�%n�%Ø�	�%r�%s�����%�8&���1�����%�]8\�������%s�%zt�P&b&e(&n0&r&ÔC
��S��V�����&�DX
��^
�`��t�p�m	��	H&v����P&�t���\&�@�p\���t&a�&e�&u�&ø�������&�0���&l@����p�����&a'e$'i0'o�&���������&�0�p����&sP�'s���'r�����p����8'a`'cx'i�'vI�D���X'il.�'el'k����e�����'�X���'À*�(�'s�&���'e�'p�#�.���'a�'e(o(ì�t�.�'l����.��(��.#�1�`0 (n�0��((ad(c�(eD)v�(����4P(l$4��X(es�(�\�,�	x(j�(k�4���(�t�9�p7�(l�(s;��<P)Ü<���(z�;	�(s)t�2���(�)��0������(��<��DC$)l��	E� E,)dT)l4E��4)at)�TE��E��T.�E	\)g�E��h)�$R�hQ��)l�Q.�)a�)e@*iL*o�*t�)�\*�(G���)z�R\�R���)�*��*��S��)l�)m*sHT(U�U�V��V�*k��HPS
� *zXS
��(*a0W�4*g�X�0Y�<Y��T*��=b
��h*vb
	p*r�Y��|*��Y���*�8Z��*l e
�,e
���*��d
���*�t��`h	�*gth���*�4+�Dg�� +e�*� ��0���+z�i��+s�h+s�j��j	,+gD+k���w�+at,ed-i�-o�,����.yh+thw��t+i�+l�+n,r,s,,t�{��{�+g�z���+e�}_~	�+v~���+��}���+�(l_�j���+t���+n�~��,oԀ$,t�\�8,rP�������@,e��H,m�,s�,v(���T,e����h,lLn�D����,zl����,s�������,l�����,eP����,k�,mh����,�8.�l������,a-��e���-�`�l���-�4���-��	(-r̚��4-�����@-�`���L-m����X-l�-n��#����t-e$���|-g�
����-���-�l���-z<	�-s�����-������-�L����-g.l<�H����-a\����-t@!�L!��.�����h.�.Ō���(.lĉ�"��D..t.e�"	L.lԨ��\.��"���.a�/e�0i�0o2u/��1�Do�ܾ�.n����.a ����.z��t���.lD����.a/eܿ���.t0/z$����.��/�1�0�#�e����/�����D/sX/z$/À�)P/u��t�ț	.��`/a����h/e@���t/n�/o�/s�b#���/d�/s�e#��th����/�С���/�L�.�/tT����/z��m�����/d0nl0p�>,���0a80n,0Ì�����$0�h�"`)It)@0mx�yH0ed�T0l����`0e�0i�,���|0pd�0s���0a����0g�	H�0rt�0r����0o���0n��	�
#����0e<���0c 1lT#`��1aT1ex1i@1���
����81�t��L1ld1sh�
�Ԑ.�1a�
l1g�1s��4�
��e����1�����1e2s�1�1� ���1zd���1�,p���1n t ���1�( P�*. *��2iM`2a�5e@6i�6o�4�7�8N�M��@2e�2o�2u�2�\M��H2b�2d3i3jd3l 4nH4s�4t�Nt�N�2l�2r�N��t����2�O�`O�$O�2l<O���2a�2e�2z�O�PP(�#0Q�2kP�lQ��3e03o�Q��Q(3m�S�@S	<3c�3l�3r\S��D3�tR���3e�3iX3�H<e�9��x3��S���3��Sy�S���3vT�+g�3l�3p�3t�3vDT��T(�T��T.�U�(U�3s�=��X�3d�X��4a,4elX��4g�XT[(<[44r�Z��<4o`4s��4����X4z\(�[l4g�4t�[��t4a�4e�4o�{
tp\�\��\���4d�\�4l^(^���4a�]���4m(5nT5r�5t�5z�]���4�6��
� =	5n�^.5i�^��5yPi��_��45e�_<5m<_��H5ow_ a��`5j�`h5l�`��t5a�5u�[��a�5s`c�@c�5ldb���5o�5����b���5��e.�c���5i0mt�l�5zxl���5e 6o�i��6t`m�Tq�hq��(6aT6ihn��06t�
�sL6s�1
H�1
	`6s�R��h6�v��t6��u���6d�6l�6n�X
�v���6l�6txx`x���6e�y>y���6eq�����6�$���6�d~���6s�~��7���\7k�7m@8n�8o9p`9z������87ix7�T�@7r���P7e�7o�C�7l����l7��8�����7�p����7�p|
4����7s���7n�		T��7sp����7a�7p�^	P�^	���7a�����7l������8���8�����8t��(8k ���48ah8g�8t�����T8n����\8i��
�����t8ad�|8r8����8eL�t����8a�8e�8n�8�(��8n��t�����8���#�z
�`�#�����8ep��8r����9axi
0i
��9t�B$9r���09����<9�8H9mt��T9o`!�9j�9n:r8:v�:z�&.�9o�%	�9t&���9��%���9��&p�1� 0���9e��_ I	�9g,I���9��8��(:z�9�8	�9s@8��:�|7��:ô8@Cp<C��0:a�:e`:ÌD�,D	L:rLD��T:�8-	TE��l:lEt:l�I��I���:a�:i�MM�:n�P�:a�;et<o�;�tSC;r�R���:��R��;o�:�XQ���:kdS��S��S;v̛C��� ;��h	��(;��\	4;k�\��@;��V��L;��W��X;mHV��d;r�;s|V��p;��<��p_�Z���;��;��Z.�;ìY���;z�Z_�G�^.�;a<è^���;y^���;g<l�Lh_���;�t`��`��<a0<eTa�`a(<g@<v�b�x�t@�H<l*
��P<o8)
��\<b<t��h<b(x��w�<l�w���<a�w���<d�<s�<t�yXy���<e8�
tP�
���<�\����<�@�	4=a�?e�Ai�BlCoDDr`Du�Dv,?ä].��=iD���=e����(=ld=n�=p�=r�>t4�P�P=rh���X=at=e̒�H�H�d��|=c�d�=l�����=o|�������=a��=l�=s,����=a4>c@>e|>i>ð�� r,�	�=s$����=�\>��>��>��>�4uR�t. >eX���(>s��TL>m�w�Ԗ��	T>k�������h>gP�p>nT}�������>a�>�́	�>rLxW�	�>k$����>��t�ę���>h�>z�ء���>d���?a̛��?rD?s���?�L@�XC��C�ԣX?a|`�ģP?ld�_t���d?�@�.l?�P���x?sH����?c�?l@r��e�����?��?�8����?e�?������?g�?v�u``%���?eح���#̵��@a`���@tԾ��	 @t���(@�<���4@À���@@n�@p�@t�Az,_�+	d@s����l@�`���x@�p�T
�@b�@d�@g�@kAlAnApAs$At,Au�����@a\AetAo<A�xZ�[�@[tP��\����@a����[�\����@\�}
��le����4A�d^�<���HAe�PAllAz�
t���T�Al�Am����|Ae�As�Az �t,��x�f�v��T�����A�(B������AgBkPBs�A�dB�h�j@������A�$���B�����BrP�T���	0Bg���8B�|���DB�0Td���\B���#�pBz�xBu8����Ba�Be�B�0�=i�# ����Be4�	�BrH����B�d�
���	�Bc�����B�C�P����B�<���Cr8Cs�
��
���
$Cs8���,Ci������DCal���LCdpCrp������hCe�C�d/,��	�Cs<����C���_��	�Cl�����C�x����C�@����Cb�CdDz �tx���C������C��M�MDsD���Di<Dz0D��t���(D�,'
������LDa�
��TDnĉj��lD.���tDk��Dr����Da��Da`Ge�Ki,Lo�MydFÌM�4���DdElEn���DaHEo4E�\E����Dk�En�EpP(�����D	 Ep���(E�� �l @Er�����TE�\I��(��hEe�E��%��pEt�(��L�D*(�)�El�)���Ea�EeFo�E�\�_�*	�Ec�Er�*���E�(F��*,�*,t�+�ErFs��, ,��Fz,,�X55��0Fe�Fi�Fo�4��8Fb�Fn�Fp(Gz�4��LF��I�8M�0��x5xFl�5��5�Fr?�>���Fs\>���Fc�Fy�^�H@	�Fnl@.�Fa�Fe�@G|A��@���FeGi<�tATGlXF��/�F��8GsG��F��98���@G��X��HGüV��	TGd�Gg�Hi0IkhIm�Ip�Is�It�H�hYm�Ga�Ge�Hg�Hy�HÈnHY���GiTY�Gl4�t�	�Gs(����G�LZ���G�Z�Gl,HspHtx�|dHe��Hg����He8[�� Hlx����8Hg����@He8[��LHlx�XHs��.h���[�xHe�[	�Hn�Y���H��[��\4l�.�]�Hd�;#X���Ho IuI��Hr�W���H��I�d��	Idt��I��=#�_$^��(Il@Ir����DF��HIz�cPIz�c��\Iedkpk��tI��k��|I��i���Ir,n��l���Im�t��o���Ir0�T,x��w���Is`w���IcJg�Jp�Jty y���Ia8Je`Jo|Jy(J���t�y�� J�TJ�XyTDJl�yt�y��y	LJr��#H�L{��hJ��z.pJì����Ja�J��C�Jl����J�(�80����J�����J������Ja4Ke�J�t\e<����J�hK�|K�t���KkP���KeHK�0�$Kl�^�L^��@K���
 ���TKd,�	\Kr܅���CtKrD#�����Ka�Ke�K�|�.�Kt�����Kz�����KsLt��e�����K�xa
��a
�Kr0��Ks����Ko0�����Le@Li����LblLm�Ln@�TLLn(+
����@�TLg�Lll���\La�Lb�Le�L���D�t\����L��L��d
Թ���Le��#�i
,X�	�Lt�t
t�t
.�LiT����Ls����Lc���\8
Md��
yMa��
Mg��� Mo����,Mf�E�`E�DMv�T��LM��T.XM�,T��dMs<���pMc�Mr����|M��k���%s��	�Ms�����M�`����Mà���.�Mo�Na�Oe�Qi�Qo�O�R�(Q.@	���Me@Ng�Ni�Nr@Os`OttOu|	��	(NlXNr	��0Na�Ne�NgtNä	�4��		`Nl	��hN��N�d	|		C�Nr	�����.�	�Nn<o� 		�Nc,	���N�@	��Oi�N�L|_�{���Ng�	�NnСtt�Or����OoTO�4
	.$Ok�		��4Oz��t؛��LO��
	���>h��#�
	lOz|	t\	�Os�	���Oi�	���Or�	���O��P�#	 "	���OePi$PoP�4P�� 	���Og@PihPl�Pn�Pz�#	e�"	���O���#	#PfH$	`$	el$	��,P�(%	.8+	�D+	��HP�X+	��PPì)	��\Pl<
G�.	.tPuT-	��|Py�4	��4	���P�D4	���P�#���Pf���PaD����Pi�6	���PdQnDQs�QzH��:	���P�:	��QoQ�<#=	e =	.$QaXQedQilQo�<	��,QzV��=	�PQt0Wt�Xt�@	�p@	tQs,@	��|QiI	9I	���Q��H	���Q��B	���Qr����^
	�Qn_
.�QaT	���Qy�Q	���Ql<�jph	���Qi(Rk4Rs`\	��R���h	�� Rr(i	�k	�RalSe�To UypT�hn	��n	.XRa�Re�R��m	��`RyTl	��tRg�Rp���n	���R��R�\���n	b0v	(�u	�Rp�Rr�u	���RaSe<SoS�hv	t�v	�|v		�Rl�v	���R�(S�HS��v	�(b�pa	 Ss�w	�tw	4Sr\f���	#̡	��PSa�Se�Sil�	��XSm�So�SsTt$Tu�	��	�Sg�Sl0�	t\I���	�Sn��	#,�	�SlԦ	_�	���S��	.�S�X�	���SzD�ܩ	���Sa@�	��Tt��#8�TmH��D�	��0T��	.8T��	��DTy��	��PTg�Tp�Tv}	��\T���	>ĭ	��|TaTh_ı		�Tlб	���T�L�	���Te�T�8�	��	�Tl�	m0�	���Tr �	(r(
���T�8U��
��0Ue�T�p
��Uk
.UalUo
8�e<R
��Q
@Unx
��HUaP
��TUc�
��`Ul8'
�Ud�UkVl�VrxWtx0
 �0
���Ua�D
��C
�Ud�Ul0D
���Ua�Uk�D
�HI
ThI
���UaHO
���UexN
�Uj@Vs�N
��Va�Vd.��-�� V��-.(V�8P
��4Vz�V,T
	LVt�S
��TV�dS
��`V� S
lVl@S
��xVa��t�
�Vj�Vl�Vn(�
���Va�VeW�\�
���VrWvt_�����Vt�������CWsh�
���V��_,�
�
��WaPWi8W�8�
�@�
��0W�HW��t`�
T\WsT�
���
̮
��dWrh�
��lWt�
�Wl�Wm�Wn�XvX�
d�
���Wa��
�WmX�
���Woh�
�t�
�Wg��
���We��
p(�
���WaXeX�
���
��X�,X���
p��	��
	$Xv�c�(���8Xt4�	@Xr@���LX�8��XXô	dXs���pX�8��|X�\	�XnhYs�Yt�Yv$+j)��	�Xd�Xe�XiYkYnYo4Ys�X�(YŤ+pl-T�-�*���X�<Y�.j�.�T/��/��/�� Y�h0�1�0!
�|@PDYa|YÌ@��LYz�?��\Ys`@\l@��tY�pBT�Yg�Yl�A���Ye�Yt��
8�
Ej�F��F�Yg�Yl8F���Ye�Y�H���H��H���Y�,P8ZaLZipZrX[s�\z�P��Z����Q��P0Zl�^�^DZr��gXZb�Zn�g��`Za�Ze�Zi[o([s4[u�Z�xh�j�|i�Zgk�0k	�Zg�Zs�h���Z�D[��k��G	�l���Zt(l�Zs�m�x�_�V	[g�n��[��n��[�xoph�	�o	<[g4pp\p��P[a�[e\o \p0\t<\uT\z�[�q�q���[��[�\�\�D\��qXq�[g�[l�[r�[s�q��q�r�Tr�dr	�[g�[r$�8<����[ths��s�,t��t�v�Tv��(\r�vpw�4w�@w.L\a�\ep\�?
\x��h\��D
��w�|\g�\l�\m�\nxw�I
$J
�J
���\e�\Èp��p���\��{p�{���\a]e�]s�]uP]�x]�@|T|]g]l$]t�|��|.0]e�|���	,}	8]kd]r|��@]�@}p}��\]e�}��}��p]�~�����~�]a�_e\`i�`l�`o,br$_ø#����]a^i�]����]d ^i<^p�^r�e����]�^�|�(0�^n��e��.���@�(^rX���0^ah^e\^ø�	�RlĄ��P^��_#D�� ���p^r,�x^fH����^a�^k�^tP�T�^r����^e8O� �#0����^a�^e$��$��^l�^rP����\����^s����_cH_nt_r0���_��a��5#D���4_a����<_tВe ���T_�����\_����h_t��4����_yp����_n�����_n�_r4`s�����_a`e`o�_øvt�����_�(`�T_�}���_c���_n���`k���p�	 `l�.�����<`s��D`c����P`at`nȬ ج��l`a�`t��	t�����`������`��=i@����`e`�Tx����`a�`e̶���`t�����`narܷ>��
�`��`d,al�n����aaXaehauDa��	cPal���4a���\�>\�td�
`as���`�tas���|ai�a�Ľ���ak�ar�at��t�����a�X��d�	�asp����a�8����a��
�����aeb�8�tD���b�dj��bkL��� be�boXb�@����%s�	@bs ���Lb�\�
j���dbp����lbo�xbtT����baPee hi�ho$iu\d�4�t���bs����ba ce0cidcocÀc�H����bb�cg�cj\�tl���c�Tc�pc��c���#Е���(ccDclLcz|�t�.��X��ؗ\cr����e���xc���t�(0��clD����cax�e����c������ct�c�D�C�cr8����c������c�d�
_���z���d���.d�x���ds����(dc|dd�df�dg�di�dn�dset(���4d�(f�Th�ti���x���tdr@�����dr�!h����drh�j�+�d����de�����dc�dy(�)���<k���drH����dpet�e`� ek(erl|�`�P���0eed��8erL��Dek�en�ep�etPQ���hea�e�`��ped�e���e�4rP�q���ei�eo ���er@r#�ed��8t�mh%�ek�$���ee`B_-	���ebD,fm�+��feHfu8(��fmXfplfsLgt�gv�gz#\&4�-��PfaT/>d/��dfa�fs�fzDP�,P��frP�f�fe\����fz�/t0.�fage(go�f�8g�H0�T0���f� g�hT�HT���fegl�0��flgr�G
ylU��0_�0t�_$���0g��Zt1��Dgadg�X1ed1��\g�l2ex2��pg��2��xg�`3l3���g�@3���ge�gi�go�Az�gä3��3�gl�gt�3�4��3�gs 4���txJ.�go,hz�I��hs�;��hz� ��K��K��4h��K��<h��J��Hhm$bb`hs�a��hho�a��thka���hc�hmis�iC�ht�h���h�Th���hi�hèi
_�i��i�hn�hsj��o�o���he�n���ht�Ħ��ig����ig<iml������4iaTii@��Lil��#8���`ie���hidh��ia\kc�kelg,li�lkmonpPnt�nz�j�������iaܽ���ih�ijjs8jvd�#�����ieD����it|���jr����jo��(�� jdTjr����(ja�jexj�$��4���Lja�_�	`jl�jr ���hj��j�X��<�y�jil���e��t8����j��j������j������jrks,kv����j��k�pl�n����0���kskz���������$kaDk��������<k�Tk�X�������dkl����lka�ke�ko�k�0���xkb(�e8����k�|��h��kl�ks��������kz���|�#�����ki�����kfp�jL���lrt�#�lm<��� lePl���l�<lk���Dl��4�p���\ln���dln�lp�lr�eH����l�l6���l������la�l�P���	�lt����l�4
hG
�G
	�ll|

���l�

��8miXmo�l�T
��mk|mm�mn�mr��

0mlHms�H
�$e�0
Pml�
t�
dmg�mt�
��lma

��
�

���my�
�h
�mlx
���ma�m�
	Pal�
���m��
��
�mgl
���met
���mr�
Dno�
e�
��n�x
�� nô
��,nth
8nr�%
|na'
��&
��\na�&
��dnt�%
pnr;
)�na�oepqk�qo<rtto�������ne�C�nd�;
���nat;
�nb�nklItLH�nt�<
���na4oe oÀ�_xI	os=
��o�<o�Do��I�$=
�|KeA
�����@
��Lo�?
	Xol�oz0@
��do�q�dq��q��a��X/zD.X�f���o.�f	�orf���o��o��D
���o��C
�og�oipmhpr�ps4��8g.Pmt�I
��p�4p��I
��p��n��T.@pi�I
	$pr�;	X�s_�J
	Hpg�J
��Pp��J
���pv\p�y�yxpd�K
���pa�pe��<y�pv�{_�{���p��p�l{��pe�pôK
���pz�{��{��pg4[
�0�#x���qa�M
	qf0qn<qt0�O
��(qn�Q
mHqepQ
TDV
�,V
��PqnV
	Xqn�V
�[
ttY
��xq�DY
���q�PX
�ql,���
��qz��
	�qsl�
���q�԰���q�4[
	�qkrs`\
�h\
��qkL\
�qa(r�<\
��rz��

4rv�\
��r��]
B�`
Pf
	�ra$vbTve�xo�zr{uH|y�t�Hz��(�j
prl�rr�j
��xra�re�rgsi�r�f
���rg8sntrHttܿ��j
t�j
���r��r�s�k
�H��8k
	�rpLk
<��\k
sn4���2��t
 sg`ss�t
��(sa�sd�se�si�sn�st�su|s�T4t�t
�u
	hslu
��ps��s��s�Pu
jhu
�u
_�se�u
	�svX<#v
��u
�ss�v
4�e�v
`J�J�ss@y
9`y
��t�y
��t���d|
	 tlttrp|
��(t��t��{
���te�to�tu8t�@|
L|
`tiX|
yhta�|
\�t<}
(,}
�trl}
(x}
�tr��
�����
���t�~
���tl,unLup|ur�uvD~
���t��w��x��y�z��{�-t��
)ue0�
��us�
�� uc��
tą
8ul4�
��@uo���WXur(�
��`uo�u�L�
��lus�ut�We�V���u��Y��Y�usX�
���ue�u�h�
t�
���ut��
	�ulH�
���u���
�Ѝ
���uavi`�
Tvr��t��H�
��vch�tܐ
��0vaH�
8vktvr�
��Dva�vj�vr@ws�wt�wz��#`e�
��|v���
���ve�vð�
���
�vg�_T�u���ve��
�vpğ
���va�vv�
�
���vaw��e����v���
�t�
	wk(�
��w�Tw��
��$w�8�
��4wth�
_�CLwl��T����`w�ȵhw�ص��twa��
���wr\�T�
���wa�
�L�
���wk�wnxr� �<�
	�wmL�
.�wex�ȯ
���wy4#�d�
���w�԰
#��
��xaHxehxi4x�@�
�L�
��,x�\�
��
@xt�>_�>��Txt��
\xn�Ne��
��tx��x���
���xs|x���
���xz��
P��
Ct�
���
���xo8�
	�xrL�
���x��
���x���
���xkyl<yphyr��
#��
��ye|�
��yl|�
�P�
(yu\�
��0ya��
,��
	Hyl��
��Py��
��|yo�yz\yø�
<d�R�
.�yeğ
���ys�
��
���ytX�
���ys�yt��
��T.,	�yr8�
���y��
���y�-���
���yad�
��zk��
��zn`�
j�
��(zaXzetzg�zrP�
��0z��{���
j4]�D].`zap
��hzy\��k���zs�zzX�
	�zsh�
���z��
���zôk���.��
�zu�
���za8_jD_���zsP_�zs�-���zuL�
��{n��
��{r4{s<�
��
��,{sD{z��
PX{ô�
��
��P{�0[_<[��d{tl�
l{tx�
��x{a0�
���{r�������{tp�	�{r��
���{�\�
��|s�{�{��
���{z�,Б	�{r����{��
t.|e�O#X��|a8��$|k`��0|��.<|��|g�|n�|tp$���s#	h|s0#��t|��!���|�4���|r�(h(�|n�|o'���|i�	.|{
<���|s�:�|s;���|a(}o}�p�
#h<	}s�<��}�P}��>T��
t�?��0}�t?��8}È?	D}d�C�}j�}r~s$~t�UXQt}s�N��|}a�}e�N�dZp�Z���}a�}e�}�T[�p[���}��[�<\
~pDb��}s�a���}�\a.�}��]���}z��
�bp�b��~aX~eh~oH~�|~�c�$c��@~��~�hcp`e4e`~l�e��e��t~��f��h�~g�~k4s�t�v\�%<m���~.m8�~r�l���~�Tl.�~�,l���~yop o���~ah�
.HŐ{	l�z����z���8z��(t@!8L!��@�,�m0a��Tz�`	\sa��h����tð}	�k�}�����|����0����lD~���eT~�gd~���e��rt�p�����a8�el�i��s��t$����������H�����0�p|��T�	@�s`�t�1pt1��X�e��T��t\H�pH��x�a�~8�~����s��	��sĊ�����a�e�i��o���tP�؀lt����al�o��z8��d����d��gāj�k\�rЂs<��Ĕ	$�rД��,��X��������P��8�	P�t����d�r|����	x�v�t�L��d�.��iX�����yܗ�|�����ed�t�Ёk�lEn�p,���؁a8�e(��P�(��(��_��	�r������@��̘��tD�(��H�k���P�a��e��i����,��	t�sЛ��|��x�����Ngܜ��n�������b��r�t������ap�e��i��o��sЃt�z<���(������m����o��,��	 �g\�rh�s���(��������܃��_-$���T�k��8��l����	x�k������r�
�����ljr���\�����p�ȃr��tؠ��m\����sL����c@�d`�m��r���������8��P�� ���,�����P�e4�����>����X�a ��(���l������t��Ч��d�����a��iȄo�������s��tԨ��l(����s��	Ԅs8���������4�s�È�����r��,��	�gx�����H���H�oP�z(��|���Pt�,��	X�k����`��p�����ol��h���x�gȅn܅r��s�������	��l̾.��ep�����yT�p���ԅa �e@�iL�o|�t��u�ð��������T��X7�ܿ�b0�g�7tH��T�8�kl��|���B_�B	\�k���d������p���>��
t�.��a؆elQoĆ�\�����z0�4<����������T��grX_�X��������n������gpe����dD�m����e���,�z���@�
_��	L�t�q
��T��,���`���l�t���x�a��z�����n�
h�
����e�p�$��B�ad�e��sĈø��\�#�C���e�iHC���b(�i8�jT��D�nGj�GG��0�eh�uT��x�t|G��L������`�t�����t���[��|�ü[��d�[����aԈe �t��àU����r�U�����x��]��Xp^��܈a^	�r�\�����^.0�y�^�n�^���a@�è^,�^t�^��8��X���,<[	P�v�c���oi��m����p�tԻ)�At�������8������������r8�������#��ĉ.D�aH�b��ch�d�e��f@�g��h��i��j�k�l$m02nXRo�Zp`kr�st�t��u��v��w �x��y��zL�ÐY�̉��b��cЊd8�fL�g��j��k�l��m��n�p8�r��s��tȍu�v�zH����e��D������������h��ä�P<�#h���Ȋa(�u�Àk�z\d���s��	�s������ ���k<���,�#@�#����0�o�#@���D�ad�i��yt�Ì�# �����l��h�P�������������������#�����a܋o�uЋ�$��H���ȋ���#l�#���.�a�e`�mt�o��u$��l#�g4�d����4�����	Tl�c����<�a|�H�nX��T�a�l	Dp#0s8!#�##$����a�1#02����aČěiԌo܌s�y�9#<A#�E#lG�,KPXZ#�Z���a �o(�u��]�$]�����d#�i#k#`k��0�at�i|�l��o\��Lo�xo��T��l�� y��z#X���# �P�����zt�#t�����o��y�Pн#����r؍t8�#d�#�����a��o��t��#�����o@���b��c��f̎g@�hT�jh�l��p��r�s`�t�z������P�����4T�X�Ŀ�h���������p������x���P4�����sh���������$�������#@���Ďa�i�o�u$�y�Ð�������������H�#��#��#��P8��D���,�����4�Ô#0���L�uĉF���`�.|�a��o�##|B#�@����o8C#pC����a�e�i��o�uЏ�4H�\H��ȏ�����@KlL��N#�R#�[#�]�8_#t_���aH�o8��$b�Lb��0��P��|h#�i��u#v��X�a��e��iȐoؐu��ôwe�w�����������А������x�y�`{4|��|#�~t�#�����\��|������5����������X�	�b��d��jБkܑl�n�p�r\�u�����a@�b\�e��fؕi,�kt�lؗo�p4�r��sșu�������|������8�����z���l�Pt�U0�����g�L���ȑk���H����a0�I8��l�����rH��0�aP��hn�k(�tX�QLo	<�bxo��D��Ĥ$��y��u�	d�bĒd̒nԒp�r,�zH���p�� ���������0��������rD�y�y��_�D�l�ܒk�����a�o �ôj1�R�n����������yD��L�hl���4�a�����Y��T�a��b��cܓd�f��h�i�j$�l��m��rԔt�v�x��ì�T������������4���������ȓ�����Г����������Y���t�_������aX�el�s��t��vL��ĸи��D����d�mP���7yx�z�7_�7_ ;	D����b`B	��_�����dD����������������e��o��ø���ز�@����$�tt�y,�	�kD�lL�n`�ph�r��t��v��y�yX�edXܸ_��yt�zFt\e�\��|��d�����ü�n������e��h�����lȕr��l����	Еc�d�e(�kT�n��rЖsܖt�z�P�����o��Y��P���� �e8�sL�L�Qu�@�i4���H�a��g|��0}	�|�	h�b����p����_H����k��|��	��i����������������_(���Ȗm����a��`t���_��	��j�r�;8����n������$�r�������8��������h��k��o��r@��@�L�k\���h�a��oė���v�v� �,���s� v���,��������`����Зg��h�k�l8�mX�r��td?
�@����y,��d����t�]
����$��l`
����H�@�kh�sx���H�ax�i�t\��,�p�n����	_��	��b��lȘsИtؘz��.��t�!���8��_��_(I,,�����l��	�r����� ������rd�L �k����(�eH�i��ð��d�kp�nx�tT���?��\�e$��|���B��k4�������t���,�����t,-_�����d4���r(����o.�t�	ԙr�sD�l^t�^�����X��� �z��L������	(�lH�sP�vX�z8z	��	��_�#,���`�a�eؚ��h�l�n �t����|�a��c��ep�f��h�iܞk̟l�o(�p`�sP�td�uܢzD������������������	�t\�����Ĥ���������\_	,�gd�l,��4����������������_t#���l��0t�������i��s����H�������(P��e�z؛�tA����Л����H�H4X		.8�bD�gL�l��m̜nܜt�v�x�	�
��$��
��,��\
t�.p�e��h��l��m��o��v���t�����x��_$.p8-����e0_L�P
_�
����b�
���# ��Ԝa`������4�����,�e���	�l`�pD�r@p�p7X���<�e\�txB�8��T�et\��h�rt_���|�����ȝ�ܝ������.Нo���Tj����j���z���<�P �` ���eT��d<�gX�kd�n��sȫ����$��H��� ��,�Ô��!�!��P�e"��D..x�d��fx"�`�_|'_4'����t�)_�(	��r��v�)	�*��*����o<*ĞrP*��Оa,�ep�i��o�ø��*��*	��l�*����D�������+$�v�+.T�ep+	8�rd�td�n�p����\�e�+0ml��,�,��|�z,��s�,��,	��sx�\-������-#�-��ğu�0t(.؟p�2	L2	�sx3	P3	�r\3�����3�3�� �l8�r�3�4y��j(4�@�lHp��t�3.L�a�ed�i|�o��sġt(�z̠�l���h".�B7����k$8P�����lD7���l�rh7�����L��l�����ء����X8��7���u�9.09��c$�g8�m���:���yX;�,;��0�ll��=�D�b\�k�=B�?�tA�PDy|B�t�n��rp
	xD����m�B`E���cPG�G��G����r6
y�H�Сl�r(6
Bh8
.�H��l�tlIyJ�8J����@���I.8�e��xJ��\
�<KXK��H�r�t�K\�d��l��t L�,L��x�a�Lt�	�L	��j�	�L	��gĢr̢zM	��	,N�N.Ԣa�e�s�u�ÐN�N������N�Ի4$O��P`�P��$�a���O	,�dԣg��k��l<�n\�ph�r�t�zLO��<�at�d��e��fԫg�h�i �jL�kl�oH�p|�r�s,�u@�yl�z�������P�����ĉ�xQ��̣.$�b,�g4�h<�iD�jL�nT�o\�rd�tl�u�ð�������t����U�Q"D�U����PU��U���<�U��U�������8���|���R�����Q����t�S��R��p�R����aܤe�o��U�TԤs4U��n,T������(���,����z|V�sW	LVt8W�DW��4�aL�n�E_�SU�W��T�t�X���a��eȥiܥu���(Y��X��l@Z#Dz_ y	��s�Y������{tLZ��n|Z�dZԥt,�(P[�b,�l4�nt[���a�eT�i��m��oاvl��̧Ű[�<�.x\yT.��aĦb̦rԦu���0\	<�rܦuD\��\�� ��|������D�h\��k��m��t�\�|�
���
�4��������
����
X���`���\�g�l�s�\���t�],�d<��T]	�rȭ�A8�A��4���]d�t�]H�nl�s�]��](,^�^	t�v`�
��_�����d`^��l�,��	��l��t�^��ħ�<�0`��_���o�`_H`	�g<�r|`�����l����������������8bTܒklb��0�aT�ihc�4cL�s�h��h`�l�h��h�e�k.̨e�yܨ�@i��g�i��j�lt�m��n̩p�r�t$�u,�v4�x�k�l��k��Ԩ��l��l.�t�]�n�j|n���e8�h@�mH�nd�rl�tX��p	�q	�q	4r�Hr��P��d�	�r	�s���eb��l��u���Ltt�����xJ_�t��U	�t����g�uP�u��ĩeܩrv	w�Hv���d���x�x�����{#�z���i8��,{�D{t���$~<�tP~��D�a��eȪi�����T|	P�k��l$�m0�p@�rL�s\�tp�v��
�	��s�~�����Ԫ������s��x�����ܪ���pT����eD���t$�n�����o��_D�ԃ��8�zl�.�#̆��T�e��XD���h�e<� ���|�l��oN
_�M
	��t��������������L���k<�̉��̫rЊ�܊����������ЋY8�nx�	�aH�bT�dp�k|�n�o�p�s��vD�.��P4���@�o���8�(D�\�t���d�u4����a��d��gĉ(���.��i�Q�<�_p�_��`������a����Ȭr,�Ԭg��H��H��<�,��	�z��#l����iP�#З��,�aܗ4�n���@�a\�r`�h���d�b��c��k��l�m�p�r��s�,
��I
n$�����e������k���ȭa����������Э�`�����b�iح�0�yj
�$�`�
_d����c8�g@�h\�it�v|�z��
_�:y�<%�;H�n$�P�al�g<�
��	ğ
_,�
,D�
����z�
������u@�	��gخk��m�r8�s|�v��zX�
.0���Юu��
�
����d�����Ȥؤ��������Ä�
6�$�t���,�aP�i\�?d�H�.8H�	\�sp���d��|���p�ÔP,�����,����ì�,��	��kȯlدr�s�,68`���Яd��_�g������a,�e �	�r8�sd����������$�l�������@�lX�r�T�`�`�c��m��zp���h�aȰo��u����������B��m��������������԰g���U�	ܰt$��P��k�������p�tT�P����$�gD�mL�ph�r���*�`�t���T��,���\�ô�_����t�z��	|�s��tܻ.��o$�n��_0�	��g�l�t�z,r1X���̱�d���Ա�(M_н	�_�	��r�zP�	8�����hL�r����$��\���4��D8�C9t��T�b��vL�.\�a��e��s���pU9��������r�������в�ܲ��h9����lP���N�ȲnO���Y�������.����`�bt�c��d��g��h�k8�l�m0�nD�p��r�s`�t��v$�xD�z���l���L������T��T�#P���l�e��s��P���8������������z��� �Pd�#X�����e�yܳ�������Գ�H�P�#�����e#�	���e$�È������#���0�ax�e��oܴu`��д�eH��X�����������t#�j��n�!9�:8(����h�%	��r ,et0#�1e�1�82��ȴ��9#@C#D?���e�i �u�ÈF�lA�����H#�L#�R#tN��(�epl#j��<�ep�ix�od���m��k��\���n#�o#,{��z��d�s����e̵o����Ũ���v�����ص�Ї�ĵs������������##r�����e4�iX�z$��D�Ŭ�������P���#̗�ܗ��<�����ĝP���Ԝa��e��i��è��D��p���|���#̰#�
��������d�#������aܶe��i�o�����# ����������#�#,��d�����̽#$����e���<���0������8������d��g��h�j$�kP�l��nܸp�r0�tD�v�y�������v����rD�����e��#������eԷ��Ř������̷������������#t����e�#\����eP�#�����e<��$������4��\�#����H�e��ip�Ð�������h�����#������������
� �d#�����e-��'��ĸ����&��̸�/�p6m�5�md5����e�0���d �i�=#H]#�Z��(�eg#Dg��<�a\�e|�il���h#�j�th��d������k#�m�hw`�.�j��k��l��m��o��r��sw��
��aкep�f��i��l@�oD�rĽt�u��@y��D..�a4�b<�eD�jL�nT�od�tp�ux�z(��0y�hyxy�� ����y�y`�myTsyl��ĉgz\�.4z�d�yHz��z�$�XRY�~�Ԁ��������m(�����e��ĺl��yP�	ܺk�lD�nL�rT�sh��������p����i<�t0���e���(��P����y(�y�yP��h���\�����d��8���|�p��s����aĻkػt���������zT�#�����e��Ȝ��лa�e�Ƞ#�����e�o8�u ��(�P���4����0��,�@�PL���ch�gp�kx�l��n��r �s(�t������\�������
y�����d��gܼm�rЦy	o�W	��c�l��t�z|����������uмôWo�y	���u�	o�b	���yD�
�D��@��Ĭ0�kЬ��8�a`�ep�i��o���\�`�I���h�z�J� ���|���������n��� ��~
9������������È4����нs��ؽst�_x����l��r ���b��c��gԾk��lx�n��pܿr,�tX�ux�v��z�����a��b��d��e|�f��g��i��k��n��o��p<�r��s��t��uh�y����Ő�_�D�������������\�\h�����a��`����̾a�k�t��_x�_��� �aL�jT�md�o�Vt@�À����k0�pt�������8���_X_T��`�\�mع	4���p�g��o��t��y�EP(I	�������aȿÄ�����������r_����Կc�o�t�������������`�_p��X��m���� �oD�ø�Ƚ��<��н`|�P�rh�t8�	�������p�a����o̿�����n��.�sܿ	��c$�g0�k@�lԒp|�r��s��t$����������� ��4��������ĉ���.��.�i����������axU`��l���8�aT�mp�y��d�	\�n��d�a����D..�a$�b,�c4�eP�f@�hX�i`�jh�kp�mx�n��o��r��s��t��u�����\��d�i��k��s�td��̩�������������<�����,Jy$���L�H�v�M��MyX�MyQy�Vy��y��ðQ��Q����������y�-�y �X����9
8������v����������p��D���	 �e0�f<�hD�kL�m\�oh�sx�v���0���������(�e��l|����$�T�lt������������l@��L���l�����e��ÜQ``%����al%��d�����a��	��j|������@�
.@�cL�dT�g\�lD�mL�nd�pt�r��s��v�x��z��~����8�s(�����H�.��a��b��d��e��f��h��i��m��n��od�r�s$�t4�v<�z����Ť���
	�	H�����������*	��	�����	��	���8�����,����	4�	�:�4�	�	��������g\�yX�,��@�_����l�d��e��k���Pw�T���dL�_����������T�.�����t��,��		��j�k �l<�nH�px�r@�sT�th�vt���T.�n����,@������(��,���0������0il��d�Wp�	X�l<���`������	T.��e��h��i��k��l��m��t����X<����������:H�Xx�������������e�h��,������������� ��8���$��t�_���8�z�n���L�el�n0���`�e��<���t�l��r��X���h".��a��e��i�l�o<�y��������yD���t�t���m4��@���t
8���\��d �k��|��0�	(�r�P0�aX�e`�ih�ox���9p�9��9������p��0���
��a��d��g�k0�l<�nl�o��p��r��s��t��z���p��������e`����a��e�ø�@������ �0���e��_L��(�l�����f��gP�t�_���(��X�i<`�n`#���x��t���t����o_�����t��z(PT�`����a����������m���	X_0	�j,�n4�r<�vD�z�	�_	|_�R�H�L�k���T�ơ	`�r���l��h����l$�rx�� 	.�����e��ot�	0��nPt��b�l@�mP�n��p��r��tD�4����� ����P���t4�y�^
G_
.,�a\������H�a�8nx�o�{
,�{
��d�ztl�sTt���8�g��j��n��r��s��v8�
	�
_(�
_�
_�
_L�
̮
����hh�
����th�Y4	��iD���������<	�r|,	,�l`�n��r��s��t��vؘz.8��X�kp�t<,88��x��D����v���Pp_�_�,_ 	��r��sd��������,� | ����l�rD!�"��"���j"���m�! �mL�v�!��,�a\���"#B0#��T��t��@%U%	l�f�%�%����k��p��r��t��z�&H�$'()��t�(C�))����r�'_(+����d *��n$�rp�t�,y�+�m8�tP+���aT�nH�Ð���,D+��@���-	h<-��\���,��d�À-_X-	|�j��t.�.����a��i��t�.n�/p0,�/	��l��r��s��t�v�z�0_8z_$1_��_н_L1	<1	�r(�z�	�3��3	
0�c��d��g��j��k�lT�md�n��p��s03.8�a\�eL�ix�n��o��u(�����4����84D4�����55.��aP4����y(5�ĉ
<5����.��bd�y�5# �p�5����a(�g0�j@�kL�m�58P	��yHp���8�oX	p�U\6�h6��\�at�y�6��6��6��|�a��j��r���XQpp7����a���\7d7������77�������B8�P�Ô8����z��p������\9y�88�lH�r9������������@�8�9��@�v,��:	T�c��g��l��m��s��v��z�:��;�@<�=	�=����t��z<>���|>�l?��h".��e��k�>8��k��r\?pd�8�?���~.�e �n(�r0�s4@%�@'�@'�A8��HC��8�a0B	@�mp
��
��X��
.`�À#��l�y�E.��o��r�D	��r<�
�\)y�,�pF8��l�G��G8��r�G�����t��\I.��I	��rpI/xI����8�7�I8�.<�aD�nL�r`�sl�tLD?�Xy J��h".9��8��X�zpJyPKT�tR��|�e\M��l��sM����aD�e��i8�o���D�yD�����rH�	��b[������Z������8������D�������>8�k�f�����f. ��f��,�y�c8�l`�r�t�z�g��T�t�����l�e�_x�	��t|j�����Tj������i	��j�]�����t���p_�p����shn��p�t<�
HH�
	��z0s�����<s����hq���k`�
P�
��$�i�u,�v�^�|�	D�r����L��,�����sX��\�	d�s������b��dT�g��i�j(�k�lP�m��n��o�p�r��s�t8�v��z@�ü�#4�����o`�F\�����e�o ������mL�I���������L�,�k����4��<�#d���L�a��e��yt��ȫ����l�����@�#�����P$������a��b��h��j��n��r��t4�#�#�# �#$�#\�#��#������e�TH�bP�dX�l`�rh�s����a��e��i��ox��������t��tPt��e�t���p�����T�>��lx���T��k��s8�����t����l��s8���|�z0��<��������4��(�����b�e �lؽtp��0�e<�o�.x�#T�.p���D�a|�i��pp����I����h�������t��������.��l��s �����a<�dD�e`�fh�gx�i��o��p��s��t��$����aHp	���I��C(�l4�r������T�����n4��� �oT������#���t�	L�ld�������t��p�n������lHG����8������*�t�����b��n��o��r�t(���n�z
��t��	��d�p�#�����a����.D�at�e��i��od��$����<�mT�t��I��I����\�����D�.8(�x�#�����d��8�P�����e��i��k��m��t��z��#������������a�optPX
t�F����a��� �dH�r���(�ad������	P�s���X��t��lI�� ��|��t����o��À�8��m`!��g��j4�r��t��v�z�$��$������$�����l%#�%����a$�u��%�&����X(#\7#|7��,�a�Ho IuT��8�@8��L��d��<��A�`A��l��0A��t��,D�LD��������������<C����e��i������E#�F�PG#�H�hI����d������K#�I���e$��D��4L�\J����4��tP��P��P��<��R�4R��P���Q����uX��XQd�d��g�kL�nx�p�P��t�a��b��dP�eT�ft�g��i�kl�o��p�sH�t��ud�z�����XR�\��dR����a��r<�	@���S�l�R���o,��S��R��$��<������T��T��D�a`�th�y(I_<�,�g	�Z��p�r�VGHV		��a��c��g�nԒp�rl�s��t��z|V�����������4��x�������4�.����V����o�	�hW����d�l�W��Wy4�b,�c<�e̦rL��,JdXM�X80X��D���Y��Y��X���Y����v`��x�([��|�e�\.���\��|������|�\����l��r�\�R��T�m�o�O��l�]����aĉTlV����.(�m0�n8�r@�t|V�m�Q	Q�k	Q�`	Q�n
QP��^
H�c��f��g��ld�mt�n��r��s�x��z��t�^��^������^�����t`��`����a��e��f�l�oL�v,��a�b����e`a��m*_�-��-�����`c�����\xc��c��$���;��;��8�� ;��\�o@�Ø<pXd��d�|d��l�e��zb	8e�tePPe����e�ftpgng����e������f	��g�k�l`�p$�r@�t�g84g�����0h�8h����xh,�h_i.0�e`inP�n�i��8�e�jlj��L�ld�r�jtk�Hk��l�r�k��k��d��j��k��m��n��o��p��z�t8ltLlt�"_ll����t(�$�tH�t�)	m	��r�z�	�o�n���l@��8�b	,�.ho��4��@��*
��L�o8)
��T�b<t`�b��g��l��v�j$�.��a�:
����z�t	��s�t������t�����Pc�|u����a��d@S
	tw�ԣr
	��s
�����4B�����
.�k�T���s�w	(�cd�s�y��y	D�r�y��L��Xy��X��		@z	p�b��k��r��zlz_\@_tY_{	�z	��r��s{�����4��\p�`{L{����l��rp{@�\|����h�k�p�|�|��|	$�g���,��x��`~��d�e��o8���q� �\�g��
,��	p�t@�
>4�����g���r�#t�����i���h@�_$�	��j��s��t؇_������z�����o�r�e�	��g$�lH�s̢z$�	TM	t�	,�z��$���@�a��H�k��t����T�a��e���}
	\���x�l������rԜ�����������4��(������bt�c��dD�gP�l��m��n��o��p(�r��st�t��u�v�z@�����ad�b��c��e��f,�gL�i��k��l�n��o<�p��r��s��tl�uTyH�����t�.`�e����h�sԣy���g��t�����a4�m���}�����a��b��n��o��r��t�QP�
Q��}H�Q�^�$�6,�	��l<�����(��$�6l�	 �mh�_̏�؏��<�aD��l�a��e��k�8�d�g�k��p�,B�������(�����à�����g�nؑ����o8��h������p���h�
p�	��d���������_d����iP�s,����a��c��d��i��rl�ü����.H�z�	<�b|�k$���\�������QX�y��htx�y�U�	��nP��h�y8��������\�����t�z���l�� ��x����j$�t��.��a4�e\�tH�Ø�Q4�2���,�l�_����@���b
�ԝ��T�r����bę��h�a��e��il����l��r��T���T��k��m��n��̯�0�(ĚY��t��<�����������������ĉ�`����.̛	�cl�ft�g��l��r��s8�t���$����t��������������_��_��d���|�a��i��l���s̬�0�������������ء����d�����a4�b�j�k(�oH�tT�uM��y����R���nH��k���HY��4���c<�ä�X�_�ģ\�d��j��l��r��vԣ��d�a��s�z��ø��|`�8a�������������t0�	��sD�������k�B
�0����nD������X�.�u��hut(�n\u�������0�aP�l��
�����H�a\����\�l��
ep�
��p�����x�Ũ���s�������ħ���Ч����iH�
.��c�d �gL�l��m�rh�s��t��v@�PP�����s �������zd�P�����e8������0�� ��8���	D�a��e��h��j��o��r��v�����Ř������x��������m�	8_����	��k�������_ȯ_��_������b�eD@C��l@��`����d,�e@��T��d�L%i������8����,���L����0���`�p|�t��z<�_��8��.��k��m�8(�`������e��i@�P �����z�����e������m<�s̻����eX�ht�rL�À�	��k��m��n��p|�r��s��t��z�1(��4���D�����d��`�����h�ň�y0��<�����y`���\�.��b��e��h�k�l�n0�pT�r\�tl�z���Xl�Ŀ��+e�������d��Ȩ�h-��-��k	���o�.�l�.��$�eH������.��@��/��B���e��$4����
t�c��d��e��m̦rD�tx�v8�z�����,����e�% ���kd���;e������h@�Ae�i�j�A�������`W`B�lB���e��$�kT�l\�t����0�el��x����
� ��,���d��d��|�y��z ��,�.��a�X������e��s��t�e��e��yp�������l��r��AHh�����cx���n�����i@��rH��� �e<�rt���Y��D�a��b��c��d��e��f�g<�hL�jT�lt�m��n��o��r��s0�t8�v@�z������`������i���(��8������L�����,���������D��D��\��h����a4�y(�ô������ �������X�����H��d�ll�y<�_X�,�����i��è����������!�$����d��g��s��_��_(�_���n�����oL�I<���m|���i�k �m(�t�����_�_���x����t�L�m�_�A`A��T�����\���	h�t��zp�,�������d�j�l��r������a@�e�oT�u ��H�ŀ��8�`(����a�kؑ	X�_`����c�����	�l0�p��������ġ,��tD�8�dh�l��m��r��s�t��	����`�l|�m��t0�x�	��L��T������,������0�.��e ��`����������a���l��r(�v4����d��8��.�t��	�l,�s��_��$�	4�r0���<�����.d�m�����l�u��v8���t�a��o���3� ������r���4�	��n�rH������ ���Q���i�����a4��0}	v�|		�b@�hH�iX�kp�s��t��v��z}	�����vh�H�~	��P�ah�o	܂	vĉ0�	��x�.$�v��v�'
Y<���a�b�c��f�k@�lp�m��n��o�p(�r�s`+
	�����j����6
��yL����t�C�T
���s8�	�sD���(�����\�dh�y4��\�nh���T�a_
������y$���
x�c��d��f��g�k�s�t�v�y�zu
y�	yh�yLz
yp|
yp�y��	y�������
Y���e�
`�
4��tP����ap�bx�d��g��i��j��l��m��o��p��r��s�t�u��v���8y@�yЦ_��8�
_�SyГ
y����
y��y�e-��yd�
��������8���������il�	��n �p@�rt�z<�-��<���(��T������0���W�	L�dP,��`��4���h��|@"�@����z������s@�	��s�z�K,��	��lp������D����r�ð)���a����e4���h".8�	�r(�s|��������h".|�$���4�lX�rP�s@�\�Tx���X�i@�	`�d��tL���l����,��t�������e�iH�o|��`��`����a����B�m�tD,.�����e�Z�t1���a��X1d1������$�t��JB8�z�L��a� �@�bH,PT�k\��\�i	h�n������p��z<r6`.��td$���u�����aD�eP�i�o,�r@�u��h���
�d(�
���a����sp	�r��������L��ܛ
	��
��0�b@8�m��.`�m�����d��h�b��e��k��o��s��tD	p�vX#��.8H�PH��
;̆���
_��
	�l��
���������r�s(�
���
���zX�
�\	����4	�� ��|�	8�a0�
	,�.��_���X�v�
`�d��j��n��pp_�����j���
��
����u@L_	��g�j�r_�Z��̦r�_	�gl<tDv<XH��e4n(�,r�`p�� �t�,0,��$La����l`bc�d�gLj�k�lntpXr�s�t�v z���lal
b�
d�
efPg�i�j�k�lXmpo�p�stt8!u�"v�"ydÌ"�����c e(iDolsThT4t\����<rL�/(��P�x����.Xè��T_���g�t����a\�Dl�����d����	`Nl���������eo(y����	�rp��\l8��D. ad��P4d\sp��<a�Ue��4�IddEn�s��la�enHo�t�u��������	�l�p�������\�p�ġ_h���s�����.H ��a4�X�%( 	k@t< ��$���%l �l��Q� 	Td�_� 	hl@�n !|uL!��lt!��\�.�t�D"��(�g�
j�k�lL�m�o�t�"_��	pP#	p'��%���en(y�EM�E��y�(�@aLe�(��(	8lt)�[��)Td�k�El�n�)��\a�Ee�i$oHp��$*��[y�*	�Er�*�����@�T+�d+���l$+�l�n�r�+�`c�<U�c���v�c	t�dt�+l8rFs,�,,�p,�,P�,��Pa�gt�Lo-��l��z	,-�<-���p�zT^
�-.�p�t0..<.�lL.���a���<���yt.�n�.���aPepi�o�t0�h0��$aP/	tl/��$�������1��0Hv��
_�1�\f�1dn$2�2	|vd2t82�l�2C�r�2�(3�43�kD3���a�i�.h3e���3���a����	r�����84�����_�4	
,a�b,d@fHg	m4	r�	s�	t���4��4�����,�|�|!�$"���.HW����	�d�.�a�4�d5���a�r�$���5��5�����5�����U��	�j<5�������T6l8pd6��a�$$�U<7��
T.�a�b�g�h�i�k�n�o�r�t�y��7��71�7��������8��$�88���8�8H8��!��8(�1����8.�a<W.�=.�<��	o A�dH	kHA��$	ap	k\	�xA�4Hh	rDB��P	��H��AmT�	lm��x	e�	�pB���	tDm�@,
��T.P+
	�	rpm���	�@�TD���	d�	e,
f4
i<
lD
mT
r
ØD��D$
p�D��
�L
�\
��zf�z�D#�|�|�~0Ey�E0GG��d
l�
r�
uDG���4��
mV��N���
r�V.ab(chg��h�j�klX
m�
n�
p�
r�
s@t|v�x��DWY�W���W��W���TX�4epX���(���<�LZ��D�ZPlhY��\e�y������z�[	|s�Y������n`]�\.�s��D�'�\����u�W�����I��]�^�$^���a��H`��
ald|e�g�h�m�n 
r(
sD
tP
v`�
�l�	Lg8a��T��_��`ptd�m�`��`�.�b�e�k�m�t���`��.��Q	�1�ha_ta_b	$b_ 4�pb	�k
t�b�����6�d�_�b_�r�(c��0
�c��8
�Dc_�c��ebx
e�
l�
�xc��cp
l\ete���
�Xf_g��
e�g��i��
e\j.�k���d�
e��k�
n�k`l�_�l�ei�8mm��$k<���q�Hp,t�o��4ept�
_p�
��Pt�tXs�t��de�u��o��`v�lv�l��Y,�	�id������v��p�_�wy`w	�cg,k�ln<r�t�v yy�ń|��{l@nHt4|��aXe�o4�t�|���t�|Pmlrts����|��}��}|rh~��D..�e�l����p|�~�n�t0��~8�~�������~1�~����p�8y(e��`��eLkP�d���4d0�s\t�z������TexÌ�$��0���p�P�\�������y�e�l^�0��k�lP��t��n(����i\�v�	�s���������� ����t���� a��	,s����8�4���loxrDÄ�dr(�������c�df g,k<lHm|n�p�rsdt�z��܏�����e�o���B�����t�	�����jL������Ԑ�����L�_���4l4��\�T��h���T�h�y�hs����pa�iD��T��n8�#D����a(��r|�����ĖPؖ���ap��|���������o��\���`����i,m<pPt�1_h
P,�j4o8�pL���He`�����\eȦp@���pa���xm��	x�	�j�vԜ,��_x�����(����È��k@��iPk�sL����a�b�cde f(g0h@iTj\kdllmtn|r�s�t�v�z�ÌR��R��Halb�k�o|�d�vtS��R��t��S��S��U�HV	H�i�r�����L������\��^���a�W���n�o�XX�\8$]8�]8�}���rlj8Hk8ԝ8$��8a����m8�n8�p8�r8Ts8�{8(�8`~8$��4�1��8��8 f�������������L��p\����aeHo�r�u4��T8���eH�l�,v�	 m����(�d?
�<�@hpt�
�P���X���\����m`�`�
v@���,�����e�o��`��.�d�n������t���jk0lPu����a�e�i|����ġ��t(v�_�.@aHkq��"�H��ܤ8���Xll�	`l�z����l�l������(��F_F���s���T��d�g�mrs@vԦ������yX�����b��v�����gm�Ĩ��e$tܨ	,�d���,� }��4�p�1����L�0���T��	`jľt��xd�m�n�p�s����	$����tx����_�����kD��,�p����i̬	�r���L�	�s��ḙ���0�Ю���D�v0�	8dH���@�����L����e��
db�gklHm<nXp�r�sv0�`P�
H=
���s ��s�����o��<e������n�M
���al����t�E_`�.ܷ��d@�Tddlktl|n�p�r�sl���$a�e�ip��4������t�a
tb
�����8��D�	�c�r�t\����� �����c
_$�����r�����nst���Xk
�Ļj�1t0�t<���(����Pd�8n0�x�j��hcpe��	`�`�
P ���xa�c�n�o��r`�
	\�_<�
PP�
�d����s�t�z�'
8l�
�����
����\W
,�
.�l�
�D�
���.ed���s��	 c�g�i�l�n0rPz��d�,Ts��
��\e�
hs���tu��H��
�z��
���a��
�g�k�
# �
���o��
�X�
���o�������n�
T<��������h�	j<k��������$���
�<��.nX���Dahi�����`sLX_,�	tb�l�n��r�sؘz��,��_4�_� .�e|��l�����e�i��0����z�����hk l(m0p8z�� &��<���)Dtp�C�g
��g
��La��Td�l�n����	`a�e�i8 o� r� u!v(!yD�$�0��p<����aw
�w
.�a�t
���y�
	4����vD��lmp vx���0�bL�
��
�(�,4�	(k`n�r����0�| �� �� �� �ȯ
.le��
p^�]��tel���|n̦r�t�ňA��A����L�
�\�
����0�
���ň
����m�n p rX�. a��
�L�
. a@�
�x�
t<�
_|�
��$ d��, lT nh s��� �
��L k(�
,����` z �
��	t c��@�	� nd�
8��� k� n� v����� a��
8H�8���L�� r��	L�t\�
���	� r��p��!�<��!�*��0!pP!s��U����H!sh!t�5	t,`!r�_,�	t!j�!r�!t<���h".�!b��h�!i�!k�!n�!rl�
8�-���8�\8��8l�8x����!�������h"j"n"t�!���
P�
��
��	��gL"lT"nĢr\"sd"tl"v�z��		��	$1	��	��	��	t"r�"z����|"��	,b���"o�����"e�"���������"�P���	�"p#r��.�"a@#ed#i�#u(#�p��d�y��8#l���#�P#��#��#�\�G��yd�8H#v���4�	\#dx#m�#s@����8�����X�	�#g@�L@�	�	���#bH$l@	
�#bP$d�$g�$j%k8%ll%n�%p�%s,&t�&u�&z�%�����#a�'b�(d)e,f8,gX,i�.k�.m$/o�0p�0s1t(1u 'À0�t�		�p$a4�m�$u�$�8	�	h$t,�U�	|$s\	���$��$��	�,�`	��$�D	��		�$t	���$��	��$t�		�	��	�$r�	���$a%t(v�	_@	��	 %kH%p�	��(%aP%el	�
��	��	.X%a	��`%y4TT�	��x%��	��		��%zd������%k4
	.�%a�%e�%�
	2$
	��%g�%mP
	�(�yd
	��%lt
	���%�&��
	_�
	��
	&dD&n�
	��&a�&e�&ox&�\	��	�	��L&��	yT&�p		`&r�	��l&��&�	��	�&l������&dt��&l�&s��,�����&z��e�
	����	���&o,	p	���&m�		'lT'r�'t�	��'�+��.�$0�80��1��1��	�i��k�	��D'al'�	�	��d'�8���h
Bx'.8h
���'��g
���'�	X�'d	���'aD�k�'l�'t�	�	ԛt�	�'g(l(t�	���'al(e�(uT(�	�t�t	�	��(� 	.$(�4	��0(z�		<(s�	��H(��(�H���	d(g�(l�(p�(t�	`	���(e,	���`�����(e�	��T.�		�(r�0���(a�	�(l� 	� 	���(r� 	.<)cH)gd)l*m*n�*r�*s�*v�x�*zT�PP���4)e "	�*	� *	��P)��)	���)e�)i�)j�)k�)n�)o�)s�)t�)vX)��)�p*	<*	�)m�*	�8	+	_�q_r�,	�,	���)�p,	_|,	_�,	_@C`-	��*e.	_T-	��*g4*nD*s|*t�Wy�Z\.	��<*sȈ
X\��P*o \	X*r,\��d*��.	��p*�w_\/	���*d�*��0	�1	���*����X1	���*k�*t�2	y���$6		D4	���*r����7	�*sh7	���*e�6		+h4+kh+l`�p|+r�+t�+vl8	.H+�9	��8	��@+�0�l9	T+t�9	��\+ed5X;	��t+d�+t$��(<	�+k�+l8<	���+e�B%\?	H>	���+k@	8 @	���+��?	���+��
������+�����+ìA	,r�@	��,o(,r�A	 �@B	��0,r�D	p�D	��D,i�B	L,d�,g�,m�,np-r�-s�-tP-�E	.�1a�,�ȫ�<E	���,��,�0E	�4F	B@F	���,�F	���,i�,p�,��!\��	\F	.-ntF	���,a-i<-t�F	�H	�H	-nx�8����(-�8���0-���TD	��H-�I	�I	��\-��H	���-od-�dI	��I	T�-l�-p�I	���-e�-z�I	��I	��J	P�K	.�K	���-oK	���-b.h.m0.rT.st.t.�4�
.�	���-a`�
UL	�DK	��.�0L	.L	��(.oH.�8L	P@L	��@.�L
U��
P��
��\.��.�t
��d.�x�
PHM	�M		�.g�N	�N	���.l$�r�Q	.�.y�Q	�.g�.t�Q	���.e/ô$	,T3	�d3	���.aH>	,�6		�.t�	��/��0
��Q	/dT/k�/l�/p�/r�/s0t0zآ\S	��L/eh/mx/o�_���S	p/z�.�T	���/a�/d�/t@T	_$�84����/�@����/��U	t�e� V	���/s�/t��v�W	��W	��spX	��X	��Z	�LZ	��0i�X		0r�[	��Z		00rP0sX0t`0z�?	�[		(I��\	�8\		h0r�0s`\	��p0��\		�^	��^	���0ad]	���0l�0r�0u�_	��r�|`	�0t(a	�`	���0k�0p��r1tXa	ta	�b	� b	��1r$d	�@1i�b	1n\1rp1s�1th(��d	��d	H1zHd	��P1i�
�d	��h1l�f	�f	|1s0f	���1a�_pg		�1j�f	h		�1g�1l�1t̢z�o�Ph		m	��2a�2uTl	�1d�2g�2h�2i�2k3l`3ml3n�3p4rT4s�4t�4v�4z�k	���1a�5b�5cP8d�9el=f�=g<Ai�Bk�En�EoHGplGs(ItJu,Ky$5��J���m	�2t�m	�\���m	���2a�2y�2��m	��m	���2�hn	�n	.�2a��t�QYp	U$o	���2r�p	�Hp	3ppp	��3aH3kL�m83�q	�q	��03�P3�\s		�s	�$/Pt	��X3o@u	��4�a�3n�3y�3�5�@����3��E	�u	~�u	��3a�3p�3Øu	>�o.|v	��v	���3��3��w	��w	n�w	�3okXx	�3bhx	��4a04c$4�xo��l�0y	����z	.84e�4it4��y	��@4zx�y��`4n\z	��h4��4��4��4��2���4nl����_�z	�({	��z	���4e�4ø���z	���4��|	��4ap|	`�|	t�~	_�|		5g\5l�5p�5s�5t}	��5��;�hB��F�G�tJ��J�L��|��H5i@	��P5mt5o(�l5m�@,�a�Ђ	�5t܂	���5a0�	y�5aD�k�5r �	nЄ	��	8�	���5r|�	�`�	�5u��	���5a46el6h�6i�6s 6Ø�		d�l��	��6�L6��6�p�	T.���T.L�		@6rd��	�X6t��	.`6i|6o$�	t\�	#h�	�6lx�	�6a�1e@��4�	��6j�6rp�	.�6a7e�7i�7o8t8u|7�7�̌	��:	��	���6l��	��6l,7m87n@7pH7t ;	,;��$7b�;��;��<��	e��	��P7���	��X7�Ѝ	�d7r�	��p7��7��7� 8�p�B(����7l�	��7l�7z��	��	���	��7zxD��	��7rL�	_p�	�|�	���7��G��	���	�8t�	��_�	��(8k�	08lt8s�8t�	��<8a�8e<9i�9y�9z9�X9ŨZT^	<�	��|8l�nPl�	���8e�8k�8s�8��	�8l�8r�8s9zTp	4r�Hr���8��r	P�	���d��	t�	���	�$�		9l��	��9�H9�p9��9�$T��	49lx�	t��	e�	��P9��9�|��`�		h9s��	���		|9nК	��	��_T�	���9�<�	.�9��	tl�	�9b:d$:g8:kL:l�:m�:r;u;v ;x(;z�h	�	���9d��	��	��:���	��:��
U�	��0:dl�	#��	��D:a�:el:��:ż�	t̟	��d:��:�0�	��	|:l�:m�_<�	���:b'���		�:l�	�D�	���:�̡	��:u��	��	Pl�	���:e;n;rl�	�	8�Y,{�`�	tl�	.D;.L;eh;tp;z\;�ĉPx�0����	��T;�0�	��	�	���	��x;���	���;��		�;k�;l�;p�;r<t,<v0�	n@�	���;e̮	�<�	���;�ĭ	���;�x�	n�	���;i<v4E�b�	��<k��td�L�	��$<cX<e�<h�<i�<k =z�<�d�	��	P<ml<st<t��	1��	���	���		|<b�<n�<t�<vб	���<�=�p����������������<atk���	�<gH��ll���<e=�Tl�`l��=�P�	8xny��,��	(=v����0=�p���<=�P�	H=l�	��T=��	���=l�=r`=��	x�	�ȵ	.�=a�=o$�	�=d�=l>r$>s8>z\�	���=a�>e0@id@o�@s�@yd>ð@ż�	������	���	���=a��`$�	��>a`�	_D�	��>s|����	��0>o��	,��		D>c�>l�>r�>tȷ	��L>��?�H@��@��@��@���	�	���>l��� �	�>d4�	���>a��p��	���>a"j�>tt���tD�	�>d?g?lt?m�?n�?r�?s��	t��	����f8?h@?jH?lP?nd?tl?vX?Ō�_��_h�_��_8�����4�_4�_���?b,�	������g��	����	���?ex�	p��	���?e�?vP�		�?l�?n�?r @s4�8��X,����?e��	��T.@ň������@�Ȼ	_T�	��	(@m@@zt�	���	�(�	,�	��P@zм	X@s�n4�	��p@i@�		x@r|_L�		�@l��st�	e��	���@��@�(�T�	���@zȾ	��	�|�	CAl�	.�@eAÐ;��	y��	8Ak`�	��A���	��	��(Ae(�		0AdhAg�Ak�An�Ao�Ap�ArBsHBtT�	.xAa�A�D�	���	�x�	���A�$�	`��	���Ao��	����d�Af,�	_��	P�	�An��	��Aap�`hW���	���A���	��Bo�A�'��	�(�	��Bi0Bk8Bm��_�_<�	�\�	��@BaXBtt
	��_�		`Bg�Bj�Bv�Bz��_��	y�	_�	(����BlL�		�Bb�jCkClCn,CrLCtXCu`Cz��	���Ba�Ce�Di�Dl�DolEt|C�`E���	������	��Ce��	�X�	D�	��$Cc<Ci8�	��
_t�	��DClĚ$��d�_��		hCl��	��pC�4D�0E�tE������	�Cg�Ck�Cl�Cm�CrDs��t��	����|�Q�{�Ct4�	�Ci �	���Cedj0�t ��,���De��	��DdHDe\Dt`�		 Dr:�d�@Dm�������TDetD������lD�����	�Dd�Dp������|�	���Duh�_����Dd��	�Dl�Ds4�8��.�Dt��	���Dz�$��;���DbH�	Em����E�`���E�@�		$Er��sDEv�,h�	�t�		LEr��	��TE��j`�	t�	.p�	��|Ee�Eyĉ��	P�E.�Ee�Eo��	9��	9�B
t0�	�Ej�EkFl$Fm0FrTFsdFt�	���t@S
_�	���Ed�b
��b
��F�P�	��F��	�DFoLFr�/s<�
`��	_��	���_8�	��\Ft�hy��	��pFd�Fox�		xFd�Fg�Fn�Fr�
���
j��	n��	���Fi�Fo��	nd�	�t�	���F�0�	���Fi�F��pL�	_t�		�Fl Gn(Gr0Gs8Gt��	_Y_��	_��	,��	��	��@Gl8�r��	���	XGlD�	��`Ge�Gk�Gp�Gs�GtHz�G�
���	���Ga�	��	���G�(�	�|�	���	P�GeX�	���Gz��	���	���Gax�	�,�	��GlH�	.�Ga�He�Hi�HtXH�X�����$H���	��,H���	�8HbpHhxHr��	��DH��H��H��H�����	y�K
�C
��Hs`O
y�M
��Hl�Hn�Hp�O
B�O
�����	��Hl�HnDT
�4[
_�`
j�d
�L�	��Hl̠_X�	��Ilh�	IbHIg��	��Ia\Ie�Ii�Ir�	�(�	��@Ia�	t��	TIgpIn�Is�	���
`��	��xIe�Iz��	�`�	�l�		�Itx�	���I���	���I�	���Il��	�Il�In��	_��	���Ig��
P�	���Ie��	\�d,Jg4Jj<JkDJr`JslJt��t��t��	�4�	��	t�	.LJiX�	��TJz�	�H�		��g�	�`�		�Jg�Jl�Jz��	.�J�tt8�t���J�l�	���	|�		�Jr�z��	���J�P
@
���J�
���J�p
	�Jd�Kg Lk<Ll\Ls�Lt�Lz
.Ka�Md�MeQh$QitQoRsRt0Ru�L�R��
%�o.�Kr�
	`Kr�
��pK��K�L��
���Kb�Kg�Kh�Kk�Km�Kr�Kt|K����vd�P��D�v��v
�<�v8
vX
P����
Ld,Ls�
��La���B�
��4LkLLl�B�
B�
��TLstLz�
'�
.lLt���Lm
���Lo����
6�
���Ltp
8�Ld$MlDMr�MsMà
���L�8O�lQ��Q��Q�<R�DR��
U�
�Lk
��M�X
p
��Mo
$
��0M��
��hMbpMfxMk�Mn�Mo�Mr8M�,�'�M'L
'��'�R%��'�
��
���Mz�	
�	
_T�c�Md�MgNk NltNm|Nn�Nr�	
�P

�Ny�Mü�h���M�@

�t

���

��NaLNedNllNo��0
��8Ne
@Nm\Nv`%8�
Br��
�
�d
��.L%i�NtH
���Ne�N�O�t�U�	�Nn�Nr�Nt�Nv����N��U��U��U�
�
	�Nm Or(Ot�
��O�LYU�xU�B�
80OgdOhpOjxOk�Ol�Or�Pv
�
��\Oe8
��BX8X
���Ovĉ%�
���O.�Ob�Oc�OdPePgPhPi Pk(Pm0PnLPp�MrTPsdPt�Pv@P��3'�
'�
!�='�:' 
%�?'�']',
e@
��8P��'�A'xB)t
��\Pe�PÔP�\
�h
��xP�LC2`C���P��
'L
�
���Pe�PÌ
e�
���P��x_4�	�PnQrXQ���P�lQ���P���Pj���Qa(y,�
_DQgXQndQo�B��z8
��8Qa�
	�
��PQgH<�
�
_�Ql�Qr�
	P
���Qdx
�
� 
���Q�,
���Q��
8�QrL,	\
8�Qk�Qr�Qz�-�x
	�
L�
���Q��
p3 Rr�3�
�<
	(Rr 
\
PRl�
	8'

�Rb�Rc�RdSk@Sl�Sm�Sp�Sr�Ss�SuTv(Tx�R�)
�p(
�Rz�(
���R�4*
#8)
���Re(/
P�,
���Rsx0
#�0
���Ra�RÔ4
��1
���R��C
#0D
��Sa(SÐE
��E
�� S�xN
#dSj�N
��4Sad�.pSaPLSdO
��XSa�Q��l
#l`
��xSo��
���
���S��
���Se�S��
#�
#\�
���Sa�So<�
#��
#D�
���So;#��
�St��#�
��TeTi`�
#��
#��
�� Ti�
�Tb�TcUd8Uf�Uh�Ui�UkDVl�VmWnPWr�Ws�Wt�Wv�Tà�
.�
xTr,�
���T�Ժ
y,�
�Tr\�
���Ta�Ti�To�T��B�
���T��
����`�
���0e(�
�H�
���T�$�
��Ui�T�
�8�B��
�� U�tU���
��PUeXUo(U�hU���
���
��BD���`U�8�B�iB��
��|U��U�(�
���Ui�U�hn�؄BL�
���
�Ul�Us�UvH���
�\�
Bl�
���U��
��Ve(Vr4Vu�Uø�
���
���
��V���
��V�X�
�<�
#X�
��<VadVe�Vi�Vo�VutV�L�
���
B�
��lV��
���
#(�
#<�
y��
�Vg�Vr�Vs��
���Va�Ve�Vi�Vop�
y��
�t�
�X�
���
���
B��
���V�W�(�
���V�,WŬ�
B��
B��
��$W���
���
��8W�`W�h�
��hWi@WÀ�
B$�
#(�
B��
��pW���
��xWÜWŌ�
Bp�
���W���
��
���Wa�We�W��
�,BP�
���W��#8���WoX��������W�\		LXb`Xc�Xd�Xk�Xl�XnYr0YvtYzh�8
��8X��	��@X�P
P���XXs��L��lX���tX�t#���Xo�X������X�@#����Xe�X�.���x���X��)�*���X�)���X�x;�<5��Y�T4��YÜF#8F��(YeDYiTY�DH#�H�xF��LY��N��I��`Y�(I��hY�,P�Yr�Ys�P���Y����|i#�g���Ye�Yi�Y�0k��h���Y��Y��Y�(l#�m��o�Xq#\p���Ye ZzZ�Ht�q��Z�@wP87�L��(Zv�.0Za�Zix��<ZsLZc[d[g,[j@[kL[l�[m�[n�[p\r$\sT\t�\u�\v�\z�~��XZal_bx_d�_e\bf|bg�bi�ck0dltdm�dogp�gr�hs�it�iu�jw$]��f�d�B�`����Za�����2a�������[�̀�� [ô�_���8[c��l[a�[bL�j�Vt4	I�d[g|[k��l
_Ђ(܂�[s����[a8���`�tX���[à]���	�[nĄ���[��qU�	�[n����[�H����[���<���\�l���4\p\�|�_��&dl\lD&nx\sĈ��<\a�\�D�����d\aD�t,�28�	�\lH����\��\���t��h�t܊��\aЊ���P�����\o�0����\������\��	�\jp]l�]n�]r,_sX_td_u0���]�pa��c� f�\f�pj��j����H]p����P]a�]o�]ü���\]ld�\���|]�h��P3�����]n�����]d^i��k^m^s^t���	�]a�^b�^e�^f�^k�^n�^t�^uP^�d�H�E1@���G1��O���� ^�H�	d^hl^nh	rt^v(^�T���4^��^�_������$��L�T,J��|^a��lL��MQ�Q���^a�^oxQ��QY�Q���^y��� ����^������^��n��
C
��_�Th��_�@��� _k���	8_rԓ��@_�����L_Ð�_$�����lT����(r��
.�_d�_g�_l�`m�`naras�*v�x��8����_������_�d���aė���_a��b��f@`gH`hP`jX`k``lh`mp`nx`o�`r�`t�`v4`È`Ŕ%����,`�ha	T_�	�,	��0	��	r��@����`���	�_��_@�.�`b�`e�	���t�p����`e�`i�`yDV�`�~�,����`����a��.,ae<a�Pa�ܞ�D��P���4a�x������Ha�\�Xp���\ael�		dah�aj�al�an�apbr(bs4btHbv��M�����ae\�X(����ae�l�am���X$����ae��8����a�ܤ���a����T.\W
�ĥ.blp���bzl���8�e�?	n����@be4�����TbllbrX��������tbr���d����be�bè��bd�bg�bk,cn`cr�csP�B����b�${`�z����ba��P$����bocrp�	T�Q��	cr ���c�ج����dDcj��s c�<_����Lct���Tca�ck|cð������tc���_,���8BmL�	`Bg�cn�ct�cv�cz|�_��	��	���c�L����c�t�	�_������cldrdv���	ز�$�dz@���$daTd�@x�$�	@dd4���Hd��H��`dg����hde�d�ph	f�dt`\	���d�hi	�Rt���db�dl$epTer�et�evO
xN
�dj����daet|����	�dr�����d�����eÀS_����ec8ee@et�`P��`��`d����Hea�ec�es�et|�
���
��le�(���te���4�
���e��
���e�4�e0����e�غ���e��
_�
	�el�����e�����e��1t\�
P����f����f�Ľ	fk<flPfr��p̾��4fo��p8���Hfi��	��b�fl�fn�fr�fs�fvؘzd�	p�	�=8|����fv�_��	����	�frH����f��j�������f������f���fr(����faXghtgi�gl�gr�gs�gyLg��Q$���,gn��	4gr���@g��,�	8���`ggD�hgnp�������ga���H����gi�g����|����g����h�������gsL����gePhoh����	�gd(hm0hs ���h�Dh�xh��+U@���D..��U��	<hm��`hchhdD���� �v�	phd�hz�k|z���h�@����h�h��L��hn����ho���hk�����hi ioLizi����	�hz�����h�L
	x
��ikT
irXK
	�J
��,it�C
�4ir��.@ie`ifhit8�	�`
.�
.�ia�pid���|ia(�	�ir4����i������ir�i��C�\��pT����i���`�d�ipjs,jt�i���j��� ���js<�,�jsH��� ja`jcTj�=�h<	@jn����Hj�P=��N_��	hjj�i_0�	|jd�g�jl�jn�jt�jvX�z��,_��_<M_HM	�Jr�$����jhH�_\�	�jrl����j��k�����kl�j�H�kb�kdlg0lj�lkmlmn�mpnr nshnt�nuovow0oz����ka�rc�ud�ve�zf�zg�zi�}j�}kXl�m��n��oH�ph�r��s�t`�uH�vxo�(���e�n����ka���la������kalolrt��kt���H�UD��(le������tlk4�u<���<l�H���Dl�T�	Plr`���\l�p���hl�@���lo�lt�l�@�����ll(�
��	�lh�li�ls�lt$����l��Y����
�h���T.\�_�����lkT�m�������ma,mndmy�EUpK|K��4m����<m��	Hmg�ml��.Tma�mb�mh�mk�mn�mr�ms�m�����������2$����m�����������?D.����m.�����max�	8����mc(�.�.4ntHnÐ���nz0�����,ne���X���@n����x�Tnl����\na�ne�ni�nŀ��p��nl��(���nn��e�����n��n���C�nr�����	���nlx�m�����nz<��nsH����na�������on����$oox�yT.�os��		<oc�ogPqlpqm�qn�qr rs`rt�rz(���Lo� y�T}�́���������8�j��.�oi�oo��%P���omh���T.pa�pb�pe�pf�g�ph�pi�pk�pl�po�rqsqt qu@qyxp�T��#.0pi8pk@pnHpt��� �����H��P����	Ppb�ph�pn�pr�pt�pv����Xp��p�8q���0��H������������n���|�$���X��
����pn������(�qk0qn��(��<����8l���Hqc`ql<����]o�]���y|qe��:�(�.�qe�����qy���k0����qa�qk̦r�qurv�q�p�������q��q�Q�X�8_8�qt�.re@roH���rz��<t��,riLt�4rk���P�Lrl`���Traprà���А�����ri�rz�r�(���|rs�y���rl����r����������rd$sg8slDsnXsr`st$����rapseth@ti�to�ts�sôt�T��8���syp��|���0sa��.Psy������������hsg�ss�sv�tH�,�	�sh�sl�snts����s��t�hu����h".�sb�sl�st�
8�8���,������t�,.tè"	��(tgPtt�0tnXtz�#_���CT
��`ts,/	hts8/��tt��.���t�P�tlt�������t�pu�� ���ta���tl�.�ta4ueHui\uou�P�yT�uld��u�X<�$<�� ue��(ur<A��@up|���TurH�h�l��x���xu�����uè�uc�ul����ua(ve�vi�vo�vu�u�DV_4���uj`a8a���ul�	�ul����u�dv��v��v��.8vel�tHv�@vlTvm�n�4r�Hr��@v��s�d	,4		\vl
�
��pva��g�	xvn�
�x
�vr��t��vr��������vzL
�vd0wgHwidwl�wm<xrLxs�xt�xvywD���d	ws,��w����@wy$w�<�(%	.Twl��`��	\we�wh�wm�wnd�r�wt�wv�w��wŤ		$b	t	0�`���w�<�����w�D	�_H���wbxe4xu(x��xl�GU�	xs���x��h"��"P�"��Dxe�xzhx�̗ܗ��`x������txk`#.|xe�x����#���x��x�t����������x��$���x���P�'���xe�x� ������x�����T���y�8(	
Pyg|yk�yl�ym�yn�ypzrDzs�zty�@)��h".`ye�������)hyl*��pya�ye���|*�ykd+��h".�yt
8�+y�,�-�yk�,���ye�-�y��-��-���y��.X�-��zi z�/8/��z�
�0.,zjdztd/��4zz|`
1�Y��Pz�1��Xz�8e�`2��pzat1��xzrD6��4���zrL7�X7���za�6���zh�;`�.�za{d,{gX{m�{n�|o�|p�|s(}z�|��;YH>P>��{e{�B
���b�<�`?��${ad@Qt@	8{d�@��@{��@��h{iL{ð�Pĉ��@p{.�{k�{s�{t�@��x{aL|g`|il|k�|t|��D�o	���{������{�8A��A�0}	Q��	�{b|h |ncr(|t0|vlD���{��|��Q��	Q0�	Q$�Q�B8tB��8|��B��@|����BX|t<CD8�C��t|�tC��||�`D��D��D���|o�D�|n����D�|n`=���|�TE��G_�F���|p}t}z�n�G��}eܚ	PH.}f�I���oL1(L��4}�4L��<}��J	H}t�\_8M	`}zHM��h}�M���}et}�8et`M�}r ��DN���}s�M�}s�M���}a~e�~io,u�}�ġ.lN	�}p~s�N���}�T~�ԣ.��t�N~g@~l�������,~�O��4~�T���O	L~j|~p�~r���O��h~��O��p~�,��O���~dĉ`�P���~.@P�~n�~o�~z�P��P���~t�P�~n$Q�Г
_P����~m�Q�~rv�Q��Q��i��,S$d<sLS��l��TDs�S��Le<	�	��dy|Vlg�t�V��xa�e$�iP�o��	#dW���e�	8�W���l�W	�l�W����\��d��4X.�o�s/	�Z.�G		[���g�Z�n4�sH[tD\,P\��<�z�[D�s�Z	t�\����(�	��l�s�^t�s]����i�cT܀nd����a��e�i��a��k(�lT�mx�p��r��vHd�dde�d�����d��	��d�n S
n�e���a�e���d�c
6�h	4�t�h��<��Th��d�iH���i�hs��_�m��p�t��
	$n����g��o��stn��
	tq�<q����i�v4r	āp��rd�s��v��z��
��v�kw���a$��8xW�w	�j0�m�w����$�
WLB
��
/8�r�
��@����
.L��|y��X�z���	p�rp���x��|������l8@z����d�z,�z	��b܂k�l�n�r�sؘz�{_P|,-8�}���gt~_�~_��		�r8�sd��������	������@�lX�r��\�����`�y0����Ga����t�k��r��t��$������0������8��H�����aЃo؃r�G����H���	`�. �a��e��i�o�u8�vD�y܄�\P	�����l��bP�l|�n��s��ul��x�<�p����D�aDX,u
	\�s\���d��0���p��ԝ8�{
.��t�z
����z���
�́
����a������l��	Ąl$���Є�d��������l��g$�l8�mP�rp�������������	����0�b�
L%iȚ��D�e��Л	\�gx�l��rt�,�A�����p��
p{.X�����a̅ø���n4�	(|t0|vL������ğ
	T���؅z���rP�	P�v�th��tt@���sP �s<��,�iĥpO	_����L�l��T�m|�n��td��0���t�i<�8���sP�����aث_�	��j̆s�t$��0���Ćz��
8H���؆j�r�e8��_�	��t�z����	�zȯ�ԯ��$�aX�,�l`�s|���8�a���\�0���X�s��_����l�y��	t�g��r԰�����������h��i��Xt�yD�����l��rܽ��b\�dh�g��j��k��l\�n��p��r�t��vh���̇aH�b\�dp�eX�fl�g��iЎkX�l��m��o|�p��sВt��u �z<��d��d���gx���P�aH�|�a��g��y<����_ȿ~D��(�����r0�����a����a��f �j8�k@�m����؈b�p�����_hW�|�	�n������l���0�i��@n��_��_4�����H������p�tx�yP��<�_\�������r �	h����a���m8\���n�����������eD���̉������yԉ�4������o��#��,��	�c��g��l��m��r�s �t8�v��������8��������x�����p�_����`�r��h�k���t�i��y\]��\��k�Q����a������nЊo؊r8Xt�u��X����X0���ks�v:
�(:
������9
���à������h0�o�����y0h����@�l��L���T�r���0�
h�d��g��k�l�m�n$�r`�sh�t|�z��	�,�����e��y�����.Ћe��������؋���	�����l��_�����b8��\�������d@��T��|�D���8�����(���L����t��.t�i�����tl�nL�����e��		��ȟk،l�m�p�r �s0�tD�v��n����Če0�,��y �_<�yT.�b�c�d����e��j �|���(�v������<�e�����P�r�\L���d�l|�rp����x�����e��o<���d̍g�n��o�v �z���`���ča��,��4���܍�@���DcjX��T���������������0�o��8P�	�BjX�k`�nh�r��v��z�yp�y�y��t =T��l
��t�a`=�x
,�
8�
������
�����
B
�����4
����l�o<�r�
����� 
�r,
���a(���
�s4�zX�td��� ����\
�����
D�k�
��L�u�
�
	d�r
��l���
��x���	
y�
����rT
��d̏f�kP�mp�n|�r��s��x�6
t�

n�

��ԏe

��܏k�l�o �s0�ttv_H
�0
�s�L
yP
n\
��(�a�

�

<�r�
��D�ow
� 
��\���

��d��x
���d��m��
_p
_�
,�
����z(Tt4�
j4
	��p�r�
1�
��А��
��ؐ�`
,t
	�k�l$�r4�sD�v�
,�
l
���d��v
_P
X<
��<�e�
	�
	P�r�
��X����,
��
��t�l��o��r��u��h
��tl
.��P\ 
�t
����p��z�;
��;
��Б��;
��ؑ�0!
��b!
P�a �i��oX�ø����h$
��n�$
#�.h�k�$
��,�ep�n�$
�<�v�!
��L�����������H!e�,
v�$
�|�c��nԭ�xZ
����y�d
��e
��]
������%
��n�%
��Ēa(�iP�od�r�Ð&
U�&
���d�'
B �d�'
����H�����D�U�,
�8�l@�mļ
�,
�h-
B.
�\�p,.
�|0
�|�a��uD�
8h0
t�g� v@1
��1
B�2
tX2
��dȓgГj�l�r�s8�t�2
�3
t�0��ؓy�3
.��a�n�3
�0�_,4
�84
���z�4
�D4
 �kL�sp4
��(�ah�u\���|�h<��4
��T���4
`�6
,�4
	p�s��t7
,~a�7
_�7
	��dȔlܔs�ztt��t�����h8
�����9
$9
��Ԕtt9
	,�z�;
yt;
��b\�dd�k��l̕s�t�v;
.
��a��eܚf�i��k�o��sԝt��u �v<Dzp��`��<
��<
y|�c4oe��t����<
�KB=
��Do��LBP=
����k���@M�D=
�����`?
Bh?
��ĕpܕsh�	�?
�?
���a�e�h�i\S��y�S�pUy|@
��
�O.��a�b��g��i�k��oȖr�t���?
�$�gؖl�m�rt�t0@
��T����l����������؟� V��V1p@
�������8H���V��!8�X��@
��Жc�m8Y�4A
��C�4B
����a@B
�d0�mLB
���aX�nd�p8\%�`��]��8���]�@�ø]��L�yHV��`�C
��l�a��h��l��o��sXa�dC
'�|%tC
��D
y�C
���d�g$�h,�k4�lh�mԘn�p��r��sЙz�D
y�èf��f����z�f	�sf�����D
�0E
y�E
yL�eX�jTF
�E
D�m\G
	�h��I
��`�c��e��p��u���ȘŬI
��I
e�I
���������I
��o��o��I
e�I
�����J
y�g�UBhJ
�pJ
���i�J
��\�.4�e\�kd�nl�o��r��s��t��v��zP��|���J
�@�jK
@ve�J
��H��<K
�8w�Pw��w�w��t����HK
	XK
��K
�K
�K
.��eșt�K
��L
	�M
y�N
��e�M
�ؙk(�lx�n��r��t�t�N
�l$�1TO
�t`O
���eL��,v�8�s܄��@���	_H	X�gȆ��`���O
��(qnl��PP
%�O
����e���|P
�P
������Q
�PR
�`R
	��rlR
���xR
�������7l�R
��l�n4�rD�s\�vd�zDT
y$�f,�g�T
B�T
BU
y�B\U
��<�mT�tD�C�U
y�U
�V
�|�r��vh�B�V
%pV
����iW
��V
����c�V
��r�V
����a��e؛�,�	�p8���̛����8����l W
�l�s����ByPX
��b8�c@�fP�np�r��s�,
y�X
��z
xZ
��H�n`�oL���B�Z
��h�r��s�Z
Bx�[
����z��4[
���a̜d؜t(�
1H�
�����T�������\
����\
��k�l �n(�r8�sD�tL�v�!8`]
���t��B�]
�8�8D���0�s��y�]
��]
���l�]
��T����bTb��p���]
 x���]
������]
�����|���^
.��u�������b�p�`
��ĝad�e��iОo��v$��L�Ŭd������o$��|`
	�cD�l�`
�������,��`����yp`
��<�l��_�)
��P�b�a
X�m��
�ȯ
��p�e�a
	x�n��sl�_��_������g����n0�_<�����t��Ğl�r�s��
�l����o�r�s��_8�
b�$����z��b
	$�v���,b
	8�s8b
��@��l�_�b
	X�l�H�	l�zp��t��<�����hc
y�b
���r,d
��c
���s̟t|d
'Hd
��ğr�d
��g��z�d
.�g�d
'����e
��e
�f
�tf
���i�g
�̠l�f

,�b�c�d0�gp�j|�kX�l<�n��p�rD�sX�ux�zPf
��8�a�b,�c@�dl�e��f��g�h�i�kt�o�pH�rx�sd�t8�uĤø��h�pt���Ġa��D���ؠ��l	����h
��g
��t�g
���a$�Àh
�8h
�����j
�D�y<l
�l
.<�a����	P�n�l
��X���l
��d���l
���a�e0�tȡ���(�����a����rDm
�m
	��r,m
����������m
��m
ܡl�*�����s������e�m
	�p8n
	DEv\�n�n
��(�u�o
)`o
<�gt�p̢t�o
��D�aܢe�i�l0�mXp
��`�.��b��j��o��p��r���l_��P�$�HY�,�`	������p
�hhq
Ԣg�q
(�q
�k�r
n�r
����e$	�@	�sL����X��$��t
�X�a��yl��D���t
P�l4��u
��d���v
Qw
	x�gw
.��a��àM�$w
8��s0w
�����Xw
��pԣu\���x
̣k�n�x
Iy
��a0���x
��x
��b�nm��yQ y	�n`y
��$���{
T�z
��<�tн.�}
P�rh�t�}
`0V�<V��p�oh~
y�r~
	��b�g�j�lX�mt�n��p��r�s<�t��vD~
��������\�������������4
�
y�o.�a�r�
X<�
l�
y�
yD�lP�m$���
��,�����́
��4��X�
��
ỹ
�؃
`�l�
��h�a��
�ą
��l4�
����oL�
yХaH�it�k��s�t4���
����a�o�
��d�m�z��
�pB	�Ć
���h�k�nxF%�k	���t�M��
	 �t��
��(��\�
�H�
@�s��
0��
	T�r��
��\��l�
jh��x�.����V	��g�V�����(�
�����p�8�������4�
0<�
	ĦsH�
��̦�X�
��ئ�p�
y �z���,��
	�g��
������
P�v��
(�l�
��0�a��b��f��h��i��jħķlԧrܧs�t�u�v���x�
:��r��
��|��@x��x��
��
Č
nЌ
�
�
D�
\�
h�
��
n��
Ѝ
y�
�
���l�r$�
̟`�
��$�l$�܏
��8�r��
�
��L����
��T���
`�g��j�k�l��m�n4�pT�r��sd�tp�zԔ
���
��l��
����e��
.�eĕ
	��rt�
��̨����P�
��بì�
����
�8�
���a`�bh�cp�dx�e��f��h̩j�k�m�n�o 
rL�s\�t��u��v��z4��Ԗ
_�G	D_�
td��g��m��n�8|�
�!B��
_X*_HV�������L������������ة�����,�������
_T0	t0`�
Uܙ
	�nD�t��
��$�����(�
�t�
_�7p��
��T�at��8�К
��l��������Ě
�p9��9��:��
_�_ܛ
_��
����b�e��l���
�
تlH�
�`�
�������
�DP_��
���c$�e �
�@�
�L�
��,�iD�oh�
�̠
�ğ
��L�dt�e����
�
l�cD�
_�
	��kX�
�������
���
�����8�
��xIe�k�t,�zث� �
�|�
���z��
t(�
�����
�����Ÿ�
Ħ
����T�
8,�
.$�mD�Ō������<��x�
���
P�l��
��X�o�
���
y��eL�
	
x�gЬh�k�l\�ml�n��p��rP�s\�t��
��
��t��
n��
��Ȭe\���
ܬl�
���a�e��
>��.�l(�
@�
y0�eL�mT�v@�ü�
X�
���
��8���X��
y��
#ȯ
��d�eX/��
��x�s��
yȭdܭi�l�m�t,�vt�z4x�h�
���
��k��
����e�>���
ԭn(@�
�B�$�
��l0�
���e��
���
�k��
�� �e��
��
.8�abl�
��@�z��
y��v���\��t�
	l�t��
��t��<d��
����j��l��r�
\�
����lȮr�
 ���ЮsL�
��خuT�
`�.0�aD�d\�g��l̯m�n��p��rȰs��t �z�
_��
(�tL�
`l�
��<�eT�o� �̻
.p�e��y���>��
��
��x����,��
���eļ
����l�p�
�d�
�����Խ
��h{i�p���P�H �
����,�
U�ì�_о
��gx�tP���
��T.4�a�pb<�eؿ
n$�
n�
C\�r4�
��D���
_�������d��������ol�è�pL�
���a@�
`x�
���o��ä�
������
��
�ܰk8Bm�zT'_��
P�oPX
Q��
��a�`��
���
������
��8�gH�mT�o��`_�����@�o��
`��
	`Bg��j��p��r��t��v��z�
_l6y��
y�
,�1�
��ḵlԱt4�
����e�Ð$
�xF1<G�X�
	ܱtd�
������
_��
��
���i,�l4�oP�uX�vD���
�<��H�
B��
��<���
��
�C
	�B
��`�t��
h�j��k�lH�mԳp��r��s��
T��l�
����aزo�t�u�D
��������IJs�
̲s�z�
�@�
`HT
�TT
�����(��<�
����|�
���dXU
8P�
t`�
��0��̳��
��t�b��e��j��l��oijs8��
.��
l�e��
���
��r�k
U�l
�m
���
��k��s�����
_ğ�\�
��ec�e�t��
`��
��
�8�d@�gP�vl�z(����
���
	�l��
����H����@�
���
���
_L�8`���X��ğ
��`���
���
��x�s��zxR
�(�
.��f��ld�
(�
jX�
	��p�rD�s��t��
Wt�
	дkl^n��
��ش�d�
���i��l�
�x�
����$�
��X�
/l�
��$���
��,���
��8�tT�z��
���eL�ÈpX�p��d����
�l����
�x�nd�
�
����r�
X��
����o��
	��k�l$�nT�r`�sh�t|�v��zh�
.�e��H�@n�8x������X1��
����d�
��zk�ð;Hp�
	4�mL�
��<���
��H��x�
_��
_��
.��
��p�i�
,��
#�
����a�l�
	��rȶsP�
��������D�
,�
H �
жn,�
��ضe��l�rP�s`�
��
��T����c��
�c`�dh�jp�k��m��p��u��v�
�� �a�i$�o�����yd�
y��Y���x�b��8��`H��
B̷fԷn��sp�
�����8��P��4)U$�
�HT��0�ܷlL�
.�eX�
���z��
l�
�l�c��
�j�U��
	0�gH�n�
�������	X�nD�
��`����
����h��k��l��p��t��v�zl��T�
d�
��
�9
��A
fxA
��ȸ�4A
���
�ܸm$�r@�
�����
.���H@B
�zLB
���a��
��
��0����
8�Ô�
D�d��k��s��
��P�aȹe�i �r��������k�l
����o���<������l�
������
�$�
��gعz�
��
	о
���g��
�n<�
C�r��
����T�
_��
���Ie|�
�X�v��
,�dX�g`�lh�n��r��t��
t��
��
����dx�i�
�L�
���
��
����������
������
�L�
_0�
	��jкl�sl�
y�
���
��غz��
	�g�k,�lD�nL�sX�v`�z oyX�
1�
�����
��<�t ���t8<�
yt�
��Ԕt܂,��
yt�e�~���
l�m���.D�
����i��l�
	��r��z�S8\�
y�b<�d\�g��k��l�m��pнr,�s8�t��z�	t�	�nL����i�o
��	�rl
_x
��(�����L�e0��@i##4��T�ap�hx�oD��L#Ч�����c�.0����l��t������aļi��#(QY�̼e�f�k�m�r�v���ԼaX���	�� �$��!��`y\!		$�bĒdx�h��i��m��n��r��vl��t!��,��H������d��@�yh�'d	y�	y�!y$�y�+�*����h����+��d,��Ľa��i�ô?_�?���r�;�k4.��,����8$�6.�ep/�� �z;��.h�a��iоoh�rp�tx�u����`��:`�d��l��m��s��t�;��;��{
�h<B��l�<�����ܾ��
�>t �
U�>Ⱦn�?_
�b(�h0�i8�m@�nH�rP�sX�t`�v����
v@�
��
����(�
v���
v�
vd�
v�?v�?vx�
v,@��e���
#�C�$C������������B����s����CP��_�Ct�C�j�r<�sP�tm��N��ܿ��N���o��TO#�[�p[�����Z��,�i�� \�\aP�]��4�zd#�b��H�i`�o4e#�h	��d�g�l\�np�r��s��t��vT�zj�(j���������i����Ìj�l#,l����e�yTlP�p#�o���e�i��4��,r�`p����$��@��$s#Lt�tt��t��,��xu�x�	���	��H��Xw��P��y#�x��h�e���xy��|��������ly�z�|P8z����z�|#�|����e�i��h}�t}����T�
#T~#d~���e��$���������~��`�.d�bt�e�h��m�n��r����~�m�1n�~��D�e���m�tyl�n��td3	y B�~������o��`	������������r�z��#������i��Ô���������؏#����e8�i(��P��p������ ��@��А#���Б����H��t��d�\�d��k��n4�r��s��t����d�a��b��eT�i��k��o$�p8�sX�u�����,����tP�n@�j��i8�	_�����h��t�y�z(IUK�x�.�a�
�$�����cH�g���$�a��ed��l�����	P�kЛ��X��̠tؠ.p�e��ô���x�z؟�p������(��8���������������ø���yL�	��d �n,�r��t��z����������l��������n�����a�yD�a`�i@��Ч<�l�;`�.p�i��P�a���ČYP=o`=��x����M̪����a��t�0��@���lT�����a�����l��PH�����e�v����l�s$�t,�zܱ	p���tP�Cķ� ����t|�.4�e(���<�s��H�cl�n��	\���d�gp�<���x�r��_������d��t���l��s��x]
��$����������#_$�	��m��s�?_�	,�	��s�zd�����4]		���8�r\�@���0�pH�th�@�t��P�t<���	d�l��r��z��	|��@��T���g�����a��h��#��s������i�
T��k������y0q����k�H�t����bH�dP�kX�l`�nh�vp����ax�b��e��i\�pp�u$�è����t�t�t4�t\�t�����p�l8����g��lP�d�	`�����fd�g��t����n�s���8�	�.����r�������L�>,�����e�	��	�r������<����������	P0sL�ztY	,�����T�r8~tD�h�t�N	��	|�j�	̢z��	�Jr����������D;.��d�i�k�l�n �s(�t$�����e0�f<�i��m��sX	�	L�	(��		�
	������l(��D;.T�ld�np�t(�0	��\�g|	�"T�"��x�a\2�`1����t�DP8D����h�iHC��c,�iL�lX�p`�s�B��
��a,�d@�e�it�m��oL�rp�s��u��ØD��D�k��#(����oG �kP}�LM��8�a�K��@�tLOj�Qj$Y8<X��h�l��t�U	p�l��n��r��s�U��������$�����,��8x4�[������Z������[�����`��_����e�i��s$`.�o0`�k`��c#c��$�u�e��c8�g`�l��m��x�iP�h��X�ex�jh`m���L�	�k�l�����mt�t��_��	��g��k��rЃ������e��o0��<���l����x�@�����t��_����g���n,�oH�r��`��l���4��@���X�t<�à�_	Ȧ`�gئ��h�a�
tt���b��l��n��p��r�sd��p�����l@�����a��t���Ьt��������<�t��tx�,@����z��0�r�	�t0hȻD���8�����@�������X�����Ի.��i��o��p��z`���x�����e�����k�������r��j4����4`�����t�_��	��j�r@�tL�����(����ø�	��	$�z����8�.��a��b4�cD�d��e$�f@�g�hh�i0�jxk�lmT'n$?o�@ppCrt_svt��u$�v��z���x@���@�����`!��
�\	��C��h�̉��d��j@�kT�ld�r��sp�u|�vh���Ȋa8�o(�u����,�s����� ��\d���sl�#�����al#���L�a��u`k��0�a��؍t�����a@���c��gT�j��m��r�sD�z�������H������?�@�<��H��@���Ďa$�y&#����upC����a�uLb��0��,��P��t_��H�o4�u���g��m#`�#����<�oT�u<�#,��8���\�uX�d�d��k��l �m4�n@�pP�rd�s����p�aT�b��d��e��f��i��k��l(�o�pD�rT�sx�t��u����Š�_�����cx�`������a(8k�(n�'���u$����b���H���,�y�(�	H���H�t��j`���\�e\�,�	p�g��lԒp��r�t0�zH���x�����������������h�8������l��m���8�����������n�u����^�X���"j<
lD
m(�vl���_4��D�8�dt�l��nl���@�a��e�iX�o��Ü��ԭ.��ax��P8_4�����d��_L�	��rl���������t�����X�����n��rl����,�	��l�������d�m$�s4�t��(��������,�a��
P����@�od�s@�H�sP�
	��̱	l�r�_(�	��lL��(������p�������r��t����dd(g��l��m��n��r�������a(�eh�i��n��ĸtи����T���t� �g<�lD�r<�tl"��%���	L�g<+tl*`�nh0D0��t��T0��|��D�����|�P�����o���̿ܿ�������H,�	��c��g�k4�l`�pL�rH�,P��t����e�s����l� �s����(�eD�t������T.d�il�s|�td�X�������t�e�������������h���ȕr�������d��g�k0�l<�m\�n��s��z(�.��a�����d��L������@�_�����r`�p�����a|���$�l�t�Q��D�it�s4���L�a|�d��g��t��y��_8�_(�tt���m��z$!	 �_��	��v��������l$�r� ������r@���k�p\�����aH������ad����d���lD�p\�r��s��vX?tH��j�nx���L�a��i|��@�	,lT���p��t��,���s��,������z�
���	p�b��d��l��n��r��s�fv���	d�	�����	�	��	�r�����h�;$�	$�i4���,������(�e8��,����p��th�zL������p�rtm�4���l������a �����j4���j��s�T�����z(����o.�	��r��	��g(�lH�s�tP�v\�	8�����gh�j|�k��l��n����	�a��c�el�h��ix�o��s��u���H�	t���`�t�������t�l��n�	�`,�����aL�m,K�\�����y,��4��@��d�������������(P��e�o�����s����(�eX	�l0�n8�rtT�
L�	`�pX�rp7n���P�e�R�.d�a��e��o��u��È����������y9����nR�#���sT��a��l�n\�sP�ø!t��	"����c(tg,�t`��(��l�#�� �i&���W�\$8�s���D��4'��(	��v�0t(.p�r��s�1,l1����z(2	�1	��r�2�L2	��l��B`4����l(4���b0�g@�jT�k��l��p��t�3.��a��b��e��iP�o��sl�u��y��z��X��H����BD���8�t���4��L�ad�n�B58�4l�p�4��t�a��j��ol�	�58�5��m�5���p�	7����k��o�������m���D7���c,�l<�rl�th7��������������������P�BN��7��4�gT�k\�n�u�_D8'����8��d�a��H��x�z�c����o�9�09���f��g��l��n��r��t�v:�X:�����;����e��t��y$<��<.�<�@=�=��g0�kT�l\�pl�t�=.D�i��kL�n��p��8�=y�>yH]��?��d�ed@.��a�?�x�g��l��m��n��p�s���!p�����e(�����l���@����i([	�@����t<A.�a����	H����ktA�(�r�A	P
�\
��0�a�B��8�t|B�D�k��l��m��r��s��v�
� Cp�t,C��x�a\l
�0D����k�
	xD����r�D��D����z
�8���D���.��r��t�v�
�`
�|���E�`E��k,�l4�s<�t�
��EB,
y�F�,F�D�sLF��L��<H��G�d�j|�t�H��H��rh8
��H���l��tlI�;
	J
9xJ���n�I.��e�K��t�L	�L	��r�L	�L	��lĢrhP���l�O�b��d��k��l��n�p0�rD�tLO���a��d��e\�i��k�o��p��r��s��t�uL�zh�è��l�`�P����o�Q��8[c�S�R��p�R����a�[b�uj(8khWtW.��a���DW����y�LU�W������W��(X�TX�����Y	�X��(�c�^,t[��<�yH`	�g��l��s��t|`��P��������`��t��$��0��8a_�e�\d����z�|8�g����l i�h����r��z,i4P��@i��c�g��j�lTvmD�p`�rx�t�k�|n� �e�nT�j8�m o��n��0�e�ut�vt�v��L��Hv��T����{P�z��p�i�|��rT|	��d��g��k�n�r0�s���|j��e@}X��������������P~����r�p������e���T�eԃ���t8�z�Pl���(�tx�t`�.<�e<���D�sx�
P�c��d��g��k��l��m��n��p��s4����` �����a$�P�����op�	l�����l�!������i4�����d��t<-_�
y��
��r�����ah��dl�i��kX���������
.d�a8�@�r�$��L��`0����Uv�����x�o��r�����
����a��p�	H�����t�	��	��v,������l\�r��#����a0�e$�����gL�r��s�������p;
#<��H�8�kd���@�a�@�	X�s��.��	l�b�s��v`0z�	��� �	��rd���������X�rP�s������p�����à����t`�������r��	�����v���d��,��	�s0�	��g�l�zt��L�.D�ax�eh�ø������`������R,O��	��c��d��g��l��m��r�s(�t4�vP�����s������zt#�����eд�D?���e�z#�s����e�Ű�#�����e4�i$��D�������e����ܶe������	|�d��g��h��k��l��nܸp��r0�t��#D���t�e�����e����H�ep7#�0����e �i�������;��2���������A��A������D�8���x.��a�x����yhw�gT�lw���a��d��e8�i��o��r�u���P_�z��L�g���8�	`�b��g��kԒp��s��th���h�������Ԃ_xH\�Hp����������r(���h`m����l��m��tD?���d������eP�	�n �#� �dP�m��(�aX�d`�mt�nȌ#X���t����T.$���h�g�8��������
����������dL���r��s�
�\���cЬ����e��� � ������8�	�jn ���b��c��d�j �k��l��m��n�p�r�sH�tT�ud�z�����a��b��d��e|�f��g$�hH�i��k��oT�p��s��t��u��y���<�Ő�.��s�����D������(B�4B����a`�.��k�����d������������t�v�������H�rP�s�����aX�b`�hh�ip�kx�n��r��t(���d�H��HL����Hd�H��Hx����`������aL�j��l��t�_t	�14�.��a��g��k��t��y$��ع_�BH�,��������	(����p,�z��,L��X�4�l����<�o|�$ܾ����\�a|�o̿I��t�n��,ܿ	��g��l�m0�nԒpH�r��s��t$�����������������(��X����l�����d��m��8&������u09���	�l��.�at���$�y\��d����<�a4�b��h��i��o��vt��L������l���;�X���ad
��R����b����k_���D������������s��z�����$�P��D���
��aT�f<�h\�i<
lL�m"nx�o��rh�s�>tx�v8���������L��p��h�8����nhI
�@}��d�k$�l�kh�����l��r���������r���@�
��cL�d��fT�g�lD�m�nd�p4�rp�sx�t��v�x��H������c$�eDP	���l�#����,�ad�eP��������H����tT�\�lT�������,��	��g��h��jX�k �l�?n��p�rt�sT�t|�v��X������e��_���<����������������p�1����T.@�i��m\�tl�v0��P��������(�����H�n�������H��������e��|��_0���`�e���8�������
�X�����l��r��y���P0�aX�e������������ =��^.��i�^����y�]	�n�]����D������#0�0�l��s�8�a��d��f��g0�l��m�n<�rL�sl�tx�z�� ���|�z��.��ep���t.��a������@���n��������\����a��i��`�p{.����a��f��g,�sP�t��_��t��4�i��o�����t\�z(,T``��d�at0	`Bg��n4�r��tD�z�_�����������h����l$�rdvx�ÜR��R������k,�l��p<�rX�s���_����l�`P��$�i�t�t�
nT�
��D�o(��L�k�sl�z�.�l���x�r<	��d��r4�D����������P,	��k,�l�n��r��s��t�vؘz��p�tD.��v��,�8�����_� 	$�rL�sd��,�������| ����l��o�rP�sغ��r8!��l�t(!x�ṛ�%����h��k l��p��t��z() *\�d4Jj�n��r �t(+.�a��d'��+`P+���a���,�D+�����,�X-	|�j<�sD�t._.����t$0	�/	P�dx�g��lT"nl"v�z00�p0�L1�<1	��r(�zP4�3	��g�l�s03.��a��e�g��i<�o��uL���	�5���j��kL�m�	���8���n$�sd
������a\9��880�l��r��s9��8�����������������8�l�m�9��t�a:���s�:G�?y�>8��l��r�?���~.PK��G����03.�� B���ytB�0B	�d�l(�v�7�����d<D���a�#��D	4�bX�sx�t�)�)��P�zL�
8̮
��d�hh�
��l�t�+��E8��s�,�pF8��l��s�-	\I�I	��rMy�I8�hl�t�JL�Q>lQ���k\M�j$�sM���a\�oD���Z��\s0AH�s	0�t�]��8��z����s�uP�r��
��d��g�h�j�m�rH�s\�t|�v��z\����o���L���������d���L�a��y��������|�#�����a���������p��������<�m�����a4�ð������,���PP���@�z�#���T�a�����h�������op��H#t�����`!��g��r�v@8��L��|7�����<C����e�oPH#R�4R�����Q����XQ	��d��k��l��m�n�r�t�x�z�P���a��b��c��eT�f`�i�k(�l��n�o��pds�t�uz0��D� S_�R��|�c(vl`hT����a�Tn�T����u�T����b�T��D�a��th�yDUt�U��DCl$Vt0V�<V����o�V_HV	�gP�lԒpX�sl�t|V����D����������\W_�Y����s[�([��d�a��t��À[��[�����]P$]����h^	H�c�g�l�m�n�s$�t,�v4�x�^��^�����^���Ô`��h`m�n��v,��Xdt|d�Petf��f�$�g,�f	<�g�l`�p$�r@�t�k��dx�l��m��z�ft�%	m	��j��r��v�zTm	��	o����lo��l�n����e�����8�e|������p�ho�����p��p�dD�kP�nX�s`�t�p���ap�i��o !C��<�t�%t<-t,qt8r�Drh�k��pr|�b�s��s	��r�s�������Ts���i���t�t�n0t�8)
t<t�b��pr�Stwt@z	��b$k,l��rlz	xz	�z	��rTs{��4���\p	�|\|��\n�pxt }TL��`~���r�\�d$�	��s�	��g$�lĢr�t�v̢zd��<M	��	t�	�r������bj�����a(s��0�R@�. ap�	(���4r��
<b�d�j�kl4nHr�s�t�u@���Had�b e��f�i�k�l�o�prXs�t�u�v������,�t��\�����a��D������(�����8��pD���aCe$j��	T�	h���,t��,���@atm|zh���$���`��	t�	 �_\����sę��DClĚ�`�,̛	�c�l�t������x�|����0�d�,��p����akl|8���,��H�b�CgHl�m�r�v��P8���@edk�l��m�ŀ,_�.�����le��tn�����e���������	�����b�,�����`�����Թ���������o���`��,��	l<nHr�s�t�v��X<���4e������m`tpv8�z����Tel��d���8|���xs������e<k��th�X(����e��
�Dd�f�glm n@rLsTthvD�th�.�a(��<�	H����l�����	$���g0t��_<������8o|��x�.`a������	�	pr��������l�rh���,���r8����a��_L����s<��kl(p4rHs\t��`����a�Ddi l��`�_����eP�����v���8���@s̮
_\���Tt�p����hil�	pn�.��@�	�l�r��8������`��4��8�	�r|�����x�$���X�r���@�	�kL��������8eLi�
�
��$�`�,���Ds�����p��rptxz�`4	�����r�t�
�j�n�r�tP
�����i�
.�a�
��������h����		�jth. o�n�i		(d�gPlXn`rhtH.Xw	�	��h	X	pr����t����a
D..8	bP	cp	d�	j�	k�	l@
n�
p�
r4s�t�u����a�
bc0dle�f�g�hi�k$l|mo�p�stLu0y�ÌŜ��H$l���������D	�ԣ�\	g���d	a�	������	������	�p��`�t�	�(���	r���	a�	nH ��aD"y
a(�g�kT�m,
�t"84"
k�	��"	
t�"�� 
�p'��%��8
eX
g`
tp
y�'_�(_�(��(.h
a�
��)8�
i�
n�
t�
v)���
�<)��
�L)� ,��+�
s�)���
o�
�,,��*���
��,��Pa,�2���k,�l�-.eT�l�<-�� zؙ�.�@k�-��H�@����`���d�</�t.xt�.���a�ei4lLohtty���0��0�th1yT.41	�rl/����X��1��1���r�1�kn�1�d�t��
���
�� ��
��(�d2���4d82@l�2e�nD3��`i����U	�3|t�55���r�4	�b�g
l0
rt
s�
t�4����D�|���L�����<7��h".�i���7��8q��:89��
lxA� A
kHA��$
aL
fT
k\
nd
o�M8�A8�A�RX�CpB��l
z`�
TD���
c�
kD
m�
v�
�E�E8�D���
�T���\���
��E���
�G��d
l�
r��jlG�
i�G���
adQ�Q	s$Q����N��Po�
r\y$�T�
v�THz\�TX��V	dc�d�f�l�m�r�s,t4z�X�YtH`��X`k�m�od�r��L�pb�b�����f��c���u�k��l��e$z�̗�ܗ���To,�o��v y_`w	<gph�k�l�n�r|vT{X8{��he�{l@n4|��|a�e�è���|�l~�8}�����~�h~���e��LX���ed���T.,b4d4f<g��hDi��m̦rLslt�z�3 =�=X�X�A���0���T�p����Te\�(���T.��Ȇ������@������l�r��4���xrt�lQ���aP��j\����ad�#��s��aDbPdtg<l�m�n�o�prslz����@�o�.�eh������`�Ԑ.�1a�e�����>4����p��_�����d��f�*�����b�t��n�d�ؖ�p��|���������o��������e`il�sPtL���_t�	8k����@����Xs�t̛	x�	tn�r�t�v����	Ԝ�����l��\����l�r��p������������������ø��d@gHjdl�m�p�rt����a�e$iPo��d�v���t"�qPk���Xa'k����p�8����e�o�tx�`��0����tT�
�1���i�*��*�����������,�4�sD����a$e4oTuH�(/��0��,r�2e����@�\3T�,l�	\g�l�r�s�t����d��(��,����̦r���`ts�D(����hD
mT��d�h�j����]t$}�|�����\������	k�t��zD�k\���0�l���8�x�DmP���	\m|�di����pa�o�uS	��x�.���k�m�t�T	vpX	��x�.�v���k�n,���(���b$d	���k����j��	�bDkl(m�n�p�r�s�xT�p{.`il���4a�o�t�u���uth|n�r������������n�t�uȶ����Ԡ����
�L�����k�n�s���ȷ���k����M
	ܷt@�Tddtl|n�r�sl���aXehi|o�sH�\����L� ����Xk
t��`t���`�tt�n
���_����t���hcpe �td�����z�t��j��	�a$r0s<t�ÀTT<�������h�	j���������d���x���LX	,�	Dbhd�l�spz��_(I	`�_<�	xr������|"�`�������l�r�s���������k l�t����8���d k(n�����a�Ie8�����rd�
��
�������0���t��Dghntr|t�z�.�a�������t,�	t!j�r�t<�_��.؆j�k�n�o�r"t�
8P�
84en��	��gT"nĢrd"tl"v�z@��L���a�	d\gdn�p�s��.	a@#e@iTkpo�s�#u�àŌ�j������t|y�� �.taP��(	�����s���#���L������#�4yd�8�j�l�nr v���?%����e�����e�������e�������,e4�	4d$��`�j�4
	���\v��	dd�lh���L8��p��|��������x�L�	���l\r@	�bxc�d�g�j�k�lpr`s�t�u�z�%�����a�b�(d�e�!f8,g�!i�#k�#l�#o�%p�%s1t&u�&v4Ð%Ť	_�	�	��d��	��l�h�		���m|			���g�	_�	���t�	��%t�	H%p�	���a�l�t�	�		g_�	���pt	_@	��cؖBT��$lt
	��,�����4
	.le�o8��		��Lz$
	�,�l�
	�
	�xv�4
B�X
���v���d؟��
	�
	���l�t�yp
	_�
	,<�	�
	�d	���\o	.�			cXf`g�l�n�r0s<txz�	���\!�l#� %�4%��1��&���	d	_�	��	��ha�op	��pl	��	���]n�t�7�	���r�	��k�m�s�	���a4�bi,gn o̦r(u�	��b�	�	���\	X�	X�	X$	����v	����bTe\iD
m�	
	�	n�1|���d��	��l�	�	���lT!	�� 	
�b��j�lh mp r� s!t8!v�x@!z�)	��)	���a�)e0 h��j�)k@ mx`oH sP t` v ��)� *	��P)�( �8 �X ��*	��*	_+		��_p,		|,		�9��,		-	�\/	���*d� e��k� o� �(|�/	� k�0	`���0		� k� t� v1	��� ��x�\���1	PX1	��� e� s!zP�	�2	�d3	.!i,!�3	��������$!���tD4	t�8	pl8	��H!e�6		P!k�!l`�p�!r�+tl9	X�9	��x!e�X;	���!ct+d�!t(<	��l8<	���!e�@	��(,r�C	#4C	�!b"l "m�B	
�!a0"dl"g�"l�"m�"n#p#r(#s\#z�C	#�C	#`D	��D	��("eL"�`"������D"��D	��D	��X"�E	���1a�,��E	_�E	��|"lF	���"m�p�,�8��G	_tF	���"d�"f�"g�"t�"v�G	_�G	_d�n8����"e0-���_@����Aa�H	���-o #td-�_�I	��0Bk8BmP#z�J		�J	��<#��J	.D#�M	�HM		M		d#g�j�#r�#v�#zhM	_tM	,�M		�N	���.l$�rdv�.�\O	�#tpO	���#a�Q		��b$f$kp$l�/p�$r�$s�ev��x��t`S	�tS	��$�S	��4$lh/mD$t$��J
_�S	n�S	<$a�_
��^
	P$s_
.X$aT	��d$y\V	,hV		|$ltV	���$� V	���$i�$o�$�W	��V	�$sxW	<dܠ
��
�$s�W	���$a�s%t%zXnX	���$o4X	,LZ	��X		%rlz��Z		,%k\%ld%nl%rP0sX0tt%v`0zL[		�[		�[		\		8\		h0r�%s�z`\	��|%��&��\	�X�jT]	�%pd]	���%a�%l�0r�%s�^	�p`	��`	���0k&m\n�0p1t&z��a	`�t�b	&d<&gD&jL&n\&rx&t�b	tc	t$d	����d@1iHd	��f	��f	��d&�0f	��l&ì�	h		�&d�1g�1lĢr�1tl"v̢zhi	�ph		�&t�&z�i	��������&u�i	�&s�i	���&a�l	���lTl	�&b�'c�'d�'e�'f(g(j,(kP(lp(n�(o�(p�(r�(s )th)ux)v�k	��'a�*b�*c`-eT/fx/g�0i�2kP3n�3oHGp 5s6t�7u�8y?z�)��4��l	���'h�ä��m	�m	���'a�m	jD�	�m	���'rhn	��n	.�'a�m	���'yo	��`�t�o	��o	��(�$o	��8[c@(r (�p	_Hp	`pp	��H(aH3k��l@���H��@u	���(yd(�tu	��u	.�(a�u	��u	.�(a�(i�u	�(w	�Xx	�hx	���(a�(c0y	_��������(z�y	���(s�{	yT.|{		�(r�z	��)��z	��<)o)�\)Ř���{	4)lL)r�{	(�{	t|	��T)�|	�p|	��|	��p)a��0}	���)r�|		�)b5g�)l�)rT*sh*t�*z}	���)�4.�t2�d4�x4�8�08��	@	���)ll�	��h". *a,*i4*m<*nD*oL*u��1D�	*dh�	��V8�Q8�Rp|�	p܂	����v �	�0�	��`*a�*�\�	tl�	��x*���,ȅ	8�	���*l�5rT�	�`�		�*c+d+j+l +n(+r0+s8+t@+u��	���*a�+e4,hP,i�,sL-z\+à,Ÿ�t�	��	�4�	�d�	�<tp�	�|�	���		d�lx+r�+s��	��H+�,��6��,�D-�̇	��d
o�	�<
tp�	�+d�+g�+l�+r�+s�+t�+z��	���	.�+e�+t��	�$
_�	t�	��	t��d�	pp�	��,eL�		,h(,n@6r����e��	�!_�!��<,lx�	D,ll,mt,p�,r�,s�	���.�,ap���	t����	e�	���t����4�	��,np�	.�,a-e$-p8-z�,�|8��	��,s�	���,��9���	��,b-s�<h
�0G��-o�I��	.0-a\�	���	P�	.�-�l�	T-d�-f�-g�-l�-m�-n.p.s.t.u ;x$.z���8����-�|�	t��	���	���		̡	���-b�-i�-l�-u��	PxJ	��	PȢ	t��	�X�	@�	�8��l�	�	,��			,.d`.gh.k�.l�.n�.p�.r/t$/v�	,��	��T.�n|.s��8@�	,��	X��	���.e<�	���;��.�ĭ	���.��	��	��T.t�c�.d�l�.�4�	T�	8d�	���.�԰	p�	���.e��	�4�	T/dL�	��/a8/eH/��	n��	�б	��@/��	���=r�	�$�	`/b�/r\�	��	h/a0eX0gh0id@o�0u�0y�/Ô0�$�	� �	n4�	���/a��		�/r�/s0tȷ	���/�@0�H@��@����	���/s��	_���D�	0d$0l,0s��	t�	.��	��T.P�		40rP0sȻ	,�	�`t�	`0tx0v�th�	�t�		�0r��	���0��+th�	�0s�	.�.�0aH�	���		�0s�	�(�	�0d1f(1gT1k�1l�1m�1n�1p�1r�1s2t`2z� tD�	`T�	�� 1a@1È�	�x�	��81�����	��L1at1eh1��	��p����	#`�		p�	��|1l��	t��	����d�1n��	��	������	���1r�1t��	(�	���k�1m�1z�	p�	�����	���1�02�\�	��(2e@2o2Ü�	t�	���	82lP2r�t��	���	��X2o��	�		l2g�2v�2z��		�		L�	�j�2r��	���2a�2e�2l,3r��D�	���2oԴ� �	���2n��	�2r$�P|�	���2i3o�Du��P@�-��3���	�� 3���	P�Ee�Eop3ud3�p�	��83y�
�\�	��\3�<
98)
�0�		x3b�3k�3l�3m4n4r84sL4t��x�H
��	�3r�	���3i�t�3u��	`�	����d�3i���P�	9��	�y
�3mt�	��4i�	�0�	�	.$4l��	��,4z��	8�	��D4t0�	���Fix�		X4r��		t�		p4k�Fl�4m Gn(Gr0Gs�4t�4vX�	��	_4�	�@�	���4��	���4ì�	_x�		�4r�4s5z��	���4�d8���	��{,�J���	5sD�	��5el5i�5p�5t�5zT5�<�_�		@5r�	��H5��5���l�	d5s|5z��	�$�P(�	���5e�5i
P��	dr\8
8h8
���5d�7
	�5l\W
	H�	.�5l�	���u�h�	�5c(6n46z��	���5a�6e47i�7rX6�w
~��	�� 6yx���
,p�		<6gt6p|6r��	��D6��6��7��7��7�4�
.��	��Ħb�6iH�
n��	t��	�6d�6k�6lعzĕ
�t�
���6�P�
���6�X*	(�	���6h(�	p8�	���6e��		�6k 7r$�
�<�	��7e�	��7t4�	���	,7dT7ph7st7z �	�,�	��L7a��	H�	��`7t��	t��	_��		|7v<�	�L�		�7k`�s��	 k�7n�	���7a�	8��	,p�		�7l��	\�d,Jg4JjDJrlJt�		H�		�7r8t�	.(8j�o��tP�	`�		�JgL8lT8t\8v�Jz��	��	�܂�|�		�z,
!	4
	p8s@
��x8��J�
���8�p
	�8d�8g9k09l\9n�9p�9r�9s
.	�8a�:e=f =i >o�>t�>u:À>ń�	�
���8g�
B�
��9c(
8�
9p�
��$9a��k�lD��P��D9ap9�
��L9y�L*	p���h9�X
�d
��|9�<
���9����|
���9a�9t�9�4��p
���9��	�
1	�
���9z�
���9stLz
�p
8�9lL:r�:t�z�
��:��;�>�`>��>��>�
�
8:s�
��
@:ahMb�:cpMf�:hxMk�Mn�Mo�Mr�:s�:t�:u�:�
$
���:��:�4
'<
'�'d
'p
%x
9	0�'�
���:l�	
�	
		�:d;gD;lX;m`;nh;p�;s�;t�;vP

�8;yH�	�4	$;z@

.,;e�

P;e
#�
�
 
<\����p;��
��x;�8
P
���
B�
8�;d0OgdOhpOj�;k�;l�;n<r�<s�<t�<v��X
B�
%�
���;e)B	�
��<.�
��<c8<rD<t�<vp<������0<et
��\Pe\<�\
2h
��T<��
e�
��h<�\�h��|<��
���<��B�
��
���<e�<k�<t�
��e'
���Pe���0=�����<��	�<l����<�B��=�H
��
	=dT=gh=k�=r�=s�=t ��@=�8
��H=�l�x��`=ex=r��
B��h�=t�
���=a�=o
�$
y0
���=k�=z0�l�|���=a<1����=�����=Ü
8�=r�l
B
��>l�
	>m@>n�QrT>s��K	�'��8>t�
�
��L>z\
8�Qk�Qrx>s�Qz�-B�
���Q��>�p3�� Rrx4�<
	�>g�>r�>t�
��
�|9��
���>o 
8�>t\
L�
L��\
	�>lx
��?��
��?�8'
�Rc@?k�SmX?pd?s0D
��P?o(S�J
#�
���SeD�
���So�?z��
��
�t?n,�
.|?a�?e�?i�?��
$d�
�P�
��?s�?v�U
��
_��
���?��
�?h@v�
#(�
���?a#8��@a\	(@lH@m\@r����Xeh(�4$��4@��#��<@�(6#T4��T@eY�,P�@s�P��l@���\p�� ZzT��`��@t����@o�@dAtAu�~��
�@ax_d|AeBi|Bo�Br�Bt�Bu0AôB�H�Ĉ���@r���ȋ_��	AgTAlhAt0���A��A�\B�C�C���8����LAlh�
	����`Aad���tAg�Al�Am�An�As�Atė�@��p�.�����(�_l�	�Ag�AtP��l����Ae��#���Ac Bv���Aa(Bm0BnHBt�#��tج��Dcj@Bt��	��.TBa���L�	�cn�X
����hBl��pBl�Br\������Be��	�	�BrH����B��#L����Bo�����ir��<&g�BtH����_��	�Br0�	��g̢z�_���Cl�Cr�CsH�$Cb�CdxDg�Dj�Dk El�Em�FnGp<GrhGs�Gt����8Ca,JbxJd@Ke�MfNg�NiQk�Qn�RoHVp�Vs�Xt�[u_v\H�$V�0�_��_�Pt��Cg�Ct�����Ca������Ca4DbDDnLDoTDr\Dt$D�\�e����D�<D�����R	P�
����H���^�������dD�D���lD�p�_�����Dk���4��Dd�s@����Da8[c�Dn�Dt����a�D���	@t�����D�h���T.Ei8�P�p�� ���E�����DEj�lk�ElT�m��t�EvE�L���T.XEaxE�@���.hEkP��8���pE���	�"_ĉ]	���E.�Ei����EaFePFixFk�Fo�Fs�Fu�E�@�f	t�o	�El�����E�DF��F�h��d)t)FlX<	�h<	��F��+�� F�|+��,Ft��	8Fr��\FnL�t@�x	HO	��dF��.��lF�T/�(X�Fk�Fl�Ft�/�0�	�0H(1��1o	��������Fa�Fg��o`�t��	�������F�G����� Gs�F� f�h	Lo�����(G�8���PGcXGi0G�x�_�zPh[_����`Gp���x�tGjTnl�Gv����|Ga�GiHlHy�G��}
��/2��	�Gl����G�H����\f���Gn��8����G�����G�T�e`�,L�X��� Hr��	(Hb�Hc�Hg�Hl�Hp�Hr8IstIt(���4H�lL��P��T��U��]��^�x�.h�.�HaȖr�HyT�p<�_�l����Ho�,0���h".�qa4�b�^fIi�qkIo̦rIt�qurv�qü�X�(
X��W�	Is��� I�H���\Iz,I�8�_(���HI��.PI�P�X�Id`���hIa�Ib�
c�	d�Ie,
f�IhJi"jD�kD
mJrh�s��t(�v�I�D����8�����I��I�J�J��x8�x
	��8,{�DX�~����<�\���$JlHJoTJrX�vd�@Jc���	��l���\J��J�����JeKr KuhJ��JŌp	����Jl@�Jl�Jmt��s���Je��,4		�Jn��_@	�JrL���J��������K�l��K��$d
#
��,K�LtKd|Kg�Kj�Kl�Km�Kn<xr LsDLvPLx4KÀ������KŰ3DZr�	�Ki����K�4#H���Ka�K� G��	�Kl����K�`.�`eLg4	4#@#��L��"��L�hx� ������0L��'��8Lè't�(8�(��XLr8(	`Lb�Ld�Lg�Lj�LkMl,Mn8MpTMrlMs�Mt�Mv)p)���Le@),�)��h".�Le�Ls�)�(�8|*X*���LeMsMô*�\*��M���d+,-X�,��$Me�-_X.8x.��@M��-��ziHM�\�8d/��dMs|Mz0_�byt1���Mk�Mr��t��v`2�2��T.�Me�Mk�Mr�2��2�Mkll���4��t�l�zr�����/s�6	�Ms�6��N��6��4Nl<NrtNyN�7T8�@���9��DNvl98LNr|9��XN��N��8.�Ni�NodN�(:��9	�Nr,E�4:	�Nl�E�H>`>���NeO�<Oň;�NdHOfTOg�Ol�Om�On,PoTPphPs�Pt�PzP��B
��O��
�	T�UOl���$Ot����0O��>���j?.hOap�epO�<��,l���,��?_�?��|Olt@��@���O��@���Ob��i�Oð		8Ay�@�Os�@���Oa�OcPdPfPgPn$Pt�"v�A_DB_TB_�B_���	tC_�D���}hE��4PoDE<PrTE��HPa�F��F��`Pe�Pk�Pt�Pz�
p�����Po�p�G���PepBPH.�Pa\H`pH���Pa�I��0�o�K_�J	�Pr�PvQzdL	�L,��j�MQk�M��QahQlxQo�Qr�Qv\Q���x���<QolN	DQd�N��PQ�TQ�L�v�QpQk�R�����S���Q���܂	����s�]	�Qs�]���Q�]���Qy�Q�
.Ry\_	�Qnt_.�Qa RehRo�R�P�`.4ReDR�4`	RlPRmXRs
��
�
��<R�h`��`�
��`	`Rk|Rl�Rs�`��
��`�`���R��R�\
�����\va	�Rd�Rk(�l�SmTpTr8Ts�Tt�TvddI�d���R�d����e(Sh4Sm<SoTSr`SspSt�R�\M����� Sa��ePmlp/z`���L
��LSePeH�S	�\ejhSa@hT�Sj�Sk�Sl�SsTh��|Sa�So�S�4h��h��a
t�		ti��h	�Sr�h���S�m
tTj�SkTs�m
t��	�m��Tt$n��n��n$Ts�n��,TapTs�TtdT�,�
�o	PTlo��XT��o�|0
�o��xTr�Typ�H�	q���Th�p���Tt<q���
.�s�Tr�s���T�4r	 Un4Up<Ur�Us�T�`vW�u	�Tt,Uv v��U��u��U�hvW�vjw���apUidU�Lx��w	PUk�w��XU�Lyn
�
��xU�t�
���U�|y���Uk�Up�y��z��z		�Ub�Uc�Uk�l�Un�Ur�sVvؘz�	�{	�}_t~	�~	���	Vr4Vsd��V��^�����.����<Va@�l`Vr�s���P��`���hVat�pVl�Vs����	|VaWeXWi�Wo�Ws�WtXuDXz�V�\�������Vz��_d�	�Vl�����V�DW��W��W�,����VdWl$Wm,Wn4WsЋ���D�tȌ��,،	<Wgd��p�PWp|Wr��B���hW�$���pW�\
	

���Wt���Wk�Wl�
t|���	�Wv܏_��	�Wn<���%
8��WbH����Waԑ�đXtLJ�����X�D��� X�X��,Xkl�.8XahXe$J
x���TXe��\Xnlh
�����tXo�|Xd�Xj�Xl|�n�XrYuH����Xa�Ye�Zi�Zoh[uHY��t�r
�����Xo��������X�����Yi�X��|�y
Ys��́
8����$Yl��	,YlhYr$���8Y�@Z��Z�<[���̗��`YnxYu��Xl��g�Yl8�m�Yr�Ys Zt����6h�Yi�Yl�n�YŬ�
�8�	p�������Y��
�Ț���Yk�.ZeZt��
�L���d�h0�
���
��Zih�pt���,ZeЛ	4ZlXZr����PZdpZtT��`���hZe�����|Zd�Zf�Zn�Zz���H	_X����Zs�Zt��_`	؟���Zg�
��	�ZtԠ���t���Zk[p([t8����
8��
��[h|�
��[tȢ_P�	4[nL[s�_Ф����T[ah�\[r�[s�t��~����|[zĉ�	���[.�[a\b\c\dH\eX\fh\gt\i|\j�\m�\n�\o�\p�\r$]s0]t�]v�]z�[�L�H����i�P\��]�l��	x�1`��,\a<\���l
�x
��4\���H,���1�fĦ��`\y�H����\�HV�|V���\���10����.�\a�\dt�i�\�'��'o	�'���\�P�_�)H���	���]a]�بx�. ��0���]�\�1jsP���.L]a\]ed]rt]u8���l��st=�,@�
���
l]nA�hA1�B��K�	�D���]��	�]k�]r^s^t�]��OH�[�p[���]������]f�]�\8$�_0����]z<�nH���^aP^b��hl^i"jt^l"n|^o�r�^s"t`^�<]x�8����X^���X��8Ȭ�	Ьh���	�^g�^l�^n�^r�^s�^t\8v�z���X�	��	8z���	Ю	��	�^r�z\����	�^c԰��_�|���_�D�.�l�_oܽ
(_b�_d(`jX`k|`l�`m�`nap8ar�as�at�aux�zh���8_a�cbDdc\�dXdeLffl�gdfiThkphn|ho<kp�ksmt�muXozLb�k�<����d��_tx����_a`o�_ø���t��`�ȾPܾ�h��x���`�D���L`t`�D�����8`�����@`�0�����a|�cl`v�_��P����t`a�`g�`i��j�`k�l@�m�`t�	�P��	�_�'_L����`b��.�`ap�t�`yP�����\�,�����a������p(aua�0��\�Ph���0aa|ac�ai�9t�azlaè�U��	Xan����`a�����_�`��_��	�����ap�as��8�o	D����a������al�ao�ay�a�4_���4�,@��������b�D���pbrb���	bb�bg�bl�bp�br@cspct�*z���$b�e��g��i��j�8n��n�D������xbi����ba�pb�bi�pk�n�bì�����bg����8q�P�_H�,��`��bs�����bac�\��d�	ct8���c�$c���C0cn����c�0���8cbXci`ck��v��	(���X����hca�h�ci(8j�ck<
l�cr�cs�ct�cv�c�\�8l����c��c���8��n����,�4�\�����@�ldr���5e�5��d��5��d�(4� dp�3.,da���8ds���0�Pdbh�d�df�dg�dl�dm�n�dp�dr�ds�dtev4�xez�t��~,����dy������$��4����di��L�����dkT�Ř�����d�����,��	eg��hLejTek،lden�pper�esft8fv��_��,��X����\ee<���	T.�d<�ezi�elLs�et�ev�e���������e���$���T�e�e��e�������e�LC�`C���e�8���,H]X|���fe fk(ft�M���X����0fe������DflP�rx��<�\fd1f�fg�fk�fl�fmgnXgptgr�gs�gt�gz�.�fa�fe��D��������fep�_(����fl��������f����gp�f� �_x�_@���gd��f8gg@giHgtPgv��_��([_(�_������`gt���hga�go�gt�g����tc�����_H���0Bk8Bm�gt�gz��C��~������ga��t�	l2g�Bjhn hr��vLhz�p���hs�.4h������,h�Dh����
,4
����l<�rX�v��	��
��hhuT
	��b�hdikLillinxip�ir��s�iv�
T�hl�hn�hs�hu�
���hai�1
tDWt1
ep1
��1
_�1
	�hl$	
���h�

���l$im0�t4S_
8�
��,i��
��4iØ
��@id\ii��D
_�

��ditD
t�
�
���i�x
���ig�ii�ik��r�is��v�i�
_
�L
_
_
�\�
H4
	�ib4jg<jiDjjLjkTjn��p\jr�js�jt�Wv(j�@�
�	���� j��
H��	��
H
H�
H�
��Filj��
��А�|j��
k
��j�
1�
���j�`
Ht
	�k�jl�jn�jr4�s�jv�jz�
��
	l
,<
�x
,�
��
	�jr ks�
��k�o�D
�0�8
(kc�
��0kit�l��rP�slk�,
U<
	Xkr
��`k�( 
�t
��xkk��p�kr�kt�kz�� 
�!
f	�katle�lf�li�lk�lo�ltDlø���;
�0!
��kb�kglk8<
�LH��<
���kal�LJ�=
��l��@
�t!
�(ljdlmllz�!
��0l��l��l��l��l�m�4A
��!
�$"
�$
��#
�|llxR
9h$
��$
��$
�	�,
��$
��lc�lk�ll�lr�X
�DY
��Z
��$
�%
�D%
�	�%
�)
\�gDml�%
��meXmi|mrpm�|�
8�
0mmT)
��8me4-
��,
Pmr4/
	L[s�'
��dm�|0
���Ie�mi�m�1
�1
U 1
	�mf�0
���m�X2
��dГj�mr�ms�mt nz�3
�84
�p4
.�man�D4
�h<��4
��n�n��4
��B�(6
���o.Lnn�4
	(nr\nspnt�6
�6
��6
��Tnzt7
n7
��hni�no�7
n�7
	�7
	�nd�ng�nl�nn�nr�nv�z8
_h8
���5d�ne�8
p�8
_9
_T~�d~���ne�9
	t9
	�nrozTM�8D�t;
�oc\�d�og�oj�ok�olpn8pp@psTpt`pu|pv;
. oa�qePsitkLto(us@uthuu<Dz�p�u��E�8<
���oa�oy�o�0F�`<
���o�l<
��<
��<
��|�c�oo��t@��J�olxL�P=
���oe��k>
. pa(pt0py<N�4>
BH>
�l>
�h?
.Lpz|?
,�?
���e�?
<PU�`UhprpU��ppa|@
����g�py�?
��pg�pl�ppqrTqs`qtlqz0@
���p��r��s��t��t��u��u��@
�	hY��@
���po$B
�hGt@B
qsLB
��qa4qiHq�X?1�B
,qh�]�B
��@q�C
���sC
��l�a�aB<D
��q��C
�tqb�qgrl4rm@rnTrpdrr�rs�rt�rv�rxLD
�	�qdTD
���q���p��U�qe�ff���q��D
��ry�q�f~�G
	�E
��rn$rv�H
	|I
	�I
��,rbJ
���\e��g�\�pJ
�4t�	�J
��\rd|r�@v�J
��tr��K
��L
�pM
��M
tN
B�M
��rj�rk�rl�rnspsr$st<svd
'�N
���rb`O
�|O
%�O
���re�O
B�O
�����pQ
!�Q
��se�<t�Q
!�Q
��4seS
��R
�	Hsd|sf�l�sn�sp�sr�ss�st�szDS
�lT
yDT
���sd,�g��.�sa��<U
�	U
���so��B\U
���sk<�mT�t�U
��`a�U
�h�yV
��sr�V
j(t�h��	,�	ti8���t�,�y�X
��4tl`tmPX
�<tkhtr��s�B�Z
����s��
�<\
��ttp4[
�|ts ]
��\
��tb�tl�tr�ts�tv`]
.�t�]
�D�BtG�xF���t��]
���t�^
	�]
��trus�]
���t� ^
��^
��^
. ua8uu|���`
Lur�b
@1�	����Tui�b
�\un|ur�uthc
��c
�,d
��c
��us�d
��d
��ug�un��z@e
B�g
	�g
���ur�f
�ublvj�vk�vlDwr�ws�wu�zPf
���ua�xb@�d�xe�zf{g,{h`{il|k�|l�|m�|o@phrts�t�ul�v�w�0Ō��l
��Xv��l
���vk`�t`v��	���n
���v��n
���v��l
���vr�vt�n
��T.�vo�n\�	�o
���vhwjT�mwswu0wvHr
nTr
���va�	�s
P����"��w�t
��$w��x
`y
��<wapwc|zdw�@y
�`y
��\w��y
_��<���xw��z
���wÜ}
	�}
�wth
.~
	�wc�wgxlxp@xr�xs�xtD~
���w��y�4|�|~��~�������
���o.�
	4�
.xoą
p�
��
$xd�idxmL�
��,xaĦb`YnP*oxxtԦulx�Ć
���
�����X�
�
�	��
	�xs��
���x�p�
���xz�x�
�
����f�u�xv��
8�
���l�r�xu4��(�
��
�xcyj,yl@ymPynpyr�yx��
�
	8�
��$yl�n�v��
����b�-l��
��Lg<�
�X�
��\y�ğ
���yidyày�|�
T�ykp�
���
��
���y��'��
�L�
	�yd�ye�yg,zk�zl�zp�zr�zt��j��
.�yez��
p�
8�
��z��
�jܬlDzs�
��zaLzetzo�zÀ����
#\zmdzs��|���
lzl�
t��
���z�@�
���
.��
��,b�zi̦rt�z��
X��
���J���
���z���P�	�zsh����z��
����r�z�\�
��Ȯrhn.8{a@{dL�
��{i�n�\ovd�#��
H{g�{lT�

P{a�{d�{g�{l�{n�{p�{r�{s|t |z�
#l�
.̻
.��ð�
	ļ
���{l�
_о
���{g@�
PL�
���{ax�
�����T'	�
���{k|t��
	��
���a��
��L�mT�o����
	l2gH|rP|v��
,4�
�8�v`�
X|l�
��`|e�|l�|r�
��
�hYm�|e��
�|g�
���|eZTD�.hi	���|rph		�|t`\	���|�`�
���|��B
��
�|j@}kt}l�}m�}n�}p�}rh~x K
��.�
$}lP}z�
��0}o`}t�
�p�M
��X}aL��|�
��l}j�}t��
��l
	�
���}l��C �
���}t\�
p�
���
�}n�}s�
���}a~e(~i<~o\~z~�
���
	c�l��
���}��
>��
�`�
 ~s��
t��
4~lL��`���H~�ğ
��P~��
���kX�
	p~i��p�~r$�
pd�
���~i��
	��
	�~b�~l�~m�~ntvzh�
.�~�x��� �4�
8�
���~ld�
	��
	��
,�
��
��
	rP�
��$�@��,�
����l�rP�s��
�l�
Tl�
��\i��
����k��l�m��p��r��t�z��
�e
�	�]
�����
.��Е
P�
���i$�
�k��
���e�r��
��
������
�|�
��X�v��
	�dX�gH�h4Jjd�mp�nx�r��s��t��
t��
���
P�n8�
��X�i�
.L�
��
t��
.��e�
����z��
�L�
	0�
	��jĀr�sx�
��T.�b��h�i��n�o̦rl�
 \p�\�
X��
,��
	�g(�l0�tX�v�
	��
_D�
	�
	8�r��`	L�rp��T��<����e`���v�|�v��gЁn�s�t4��T�ax�o�'��'�������'���o�u���()#x�	��)#�6Pp/����z�:#;���a0��h<��<��(���C<�s�h�gd�l��r��t܂v�o���e��4��xy��|���x��x��t}�������Ԃ��|���i���Ȃň�
��}��}�������
�d~�����t����md��d`�jt�k��l��s��tăuԃz������a��e<�fX�i�o$�p��s̆t�u�Ü���_|���X�k@�>,���l�t������k��t|�	������p��t��zp�Tؠ��j �$���<���̃o�8����sL�	�l0�t����������X��l����4���p̪��(�eP�f�Ih"jD�kX�u�[8�n$�
���`�z��	h�s̭��t��|�����È���g��lH�������а�����(�����Ȅe��rh�	Єd��rT�Xp����i�t��������e(���B���� ��t�d���4�r�����1at����H�g��n��r��sԅz<E	���,�\�����d��f��g��td�_��_L���Bo|c��`������i�k�1m,����m�o���l�	�	�r��b �lD�p,�s�^
_�����v������z�������8������@����	L�r��	$�	d�rP0s��v��	��	,�	��rd������,�@�����k0�pH�t������Ćr4t��؆g�r�t$��0�����zt�	�s�t�.,�i�odn�	�1g��z�C���lԇsHCD�b�dL�j`�kЈl�n �p<�rt�s��tL�u�B��T�a�b�d�e,�fD�gX�i$�k`�o�p<�s0�t<�uЖy�z|��ԑ�(D_8E��D܇lE���a�e$�o8�Ô�̮
�h�
���tl��t���hE��0���G	G��D�t�I_pH��X�c��t|�����I��t��K��T.��a|u��ô�nK8K�����,L�K��p�K��Ĉa�e�kxL`�L_HN.�a�o�y<N��E��N,LO��P�P��(���P��P�cX�m0��Q_4		_�������`�z�Q��h�s��zԝ �.��t�R�,R��l̉nXR����aԉe0�iD�y�èR(\ST�lLSt��yT.�v�S	�r �s�R�����<��,�P���S��S(�k\���T,�T�@V,�U	T�g��lĊrX�sl�t܌z�U��\���������T��x�����<X,�[�i��k�m�s�t�[��
��a@�b,�cH�dX�f`�i��j��k̋o�p��r,�u(��8\�hG�X\�t\	h	r8�t�\����P��tI�,��\c�P�$]X�`08M	h�b��vHM��p��M�����$�0Q�L]j��ahI
W�R����k؋r�]��kTSWHVd���^�����^�������$_���n<�r4_���k_ �nL�s@_�ĉ
$]1D�.�_����s�`X�`��d�a��b��eČȟr�cs���aj a��������Ԍ�0a@a�La8Xa�a�8�a_�b�b���l�c�c����r�e�c	�f@�gH�mP�pX�r`�sh�tp�xx�z�e�m��r@s��y��|��|����	��g��l��p��r�t�v��,�,@���T.�tt�zЍń�8����ȍ��������܍ex��e��X������e��nt����e$�i�n0�����lP�r���T���<�rĒ���
P�d��g��kȎl�m�n@�pL�rd�s܏z����������������4������n �����o�����fl@��d���Ԏ�$�����i�,p܎�ȕ������f�g�j$�tH�_�_���,�r����4�a@����go<�È������\�e��i��sЏz���$������r�eP���������$��h�t�J_���������.ďô�.�m�oȜ_�	��	Ԝ	��j�v���4��0����l4�r�����n���<�ad���D�d��tt�P�l��mАnܐr�s�\
��\
|�ld�����a���t�8T��������_̪����l��_@���Ȑt����	p~i��l�r4�s����o.б1���������,�i�ä�n4���ttpD�z���$�	г		L�b��d��l��m��n��s��t��v��zh�	<����	��	D�	�������	���	��r8���ȑ�@�$����l��r���(/�$���bh�dp�g��j��k��l��mВnܒr��t�uԻ.	�ah�e@�i��o��s��u�z0��|��d����
��B����x�t�4��d�n���������a�`k�l(1�L�����uؼ����Ȓy4������(��������ܽ<$'�����l���l\�r��� ��̓�D��T��l��ȕ�ԕ�ĉ
̾��T�.��t��b��c��f��g��l��m��s�94���,�h��������y��ēj�k��n�r$�t�����p�����ep7!8����e�i�.!`�.0�eX�!��Y���
8�a��d��gԔm�n�p�s0�t<�zx�������p�������D@����e��.��aĔe����� 
�������D��	8�y8���̔m��Bt����g��t�v([B(�2l����������k�������(�a��D��(�
����L�hd�r���_�������t������3
�|����j��tp4
��7
�7
����o����t4�_�vd~��I������������.�o���xZ
9`���n�������(�r��\�dX�g`�jh�rp�t(�tht4��`�t��	��j��r(�_�,��	��g��l��r��z(�.<:	���(����Ȗi���<�Pܖe�����s��ĉ.X�al�b��c��d��eh�f0�g��i��k\�l��o��p��r,�s��t4�uX�v��yH����̉��a�b�c8�dt�e��fԛg�i0�j�k��l$�mH�nСo�pH�r`�st�tĤu�v�y���Њ.D���d\��$�̗t����ԗa�p�d,�j8�kH�r`�sH����aP�i��o̙u��è�_����$�kБj0����@�aX�c(�_`�����zd�ypbrp�	l�b��cИgؘl�r�t����x�������ؙ�4�.Ęs���$�.��i\�_������l�X����ol�y�����vh�yt�c �	�r,�t@�v��.8�e��p���(��ؕH�kl�nH�p{.T���`�aX�����x�a��dؗ��l��	��,X�	��r_0����tș��l0����.��u�����h�s���|����r`�.�oԣ�<��gT�s\�th���$�a��e��o,�u|�èZ������o<�_��	h�r���p����8���������g��s�y�Л�l���gܚmЭDح��Ț����К�$�
���	�d�li�j�k�t��
�خ�\�����,�$�j��	h9s�����zԳ�D�sܳ��P������\���h�g��r��.�.P���rL����������������lp^r�xbi@���ța�n�r��y���<��h�.T.��.���z���t��g����$�a��kl��М�@YX�yH�u��	P�r��t����\��������@\X������o4�@������������4�	Qr��4������Ȝ����������ܜ����p8�s����a��c��e�k�l�o�r �t<�u\�yt����yL�z\�y�.D�t�
��s$�	X�c��lH���d��̝������8������l��� ������g|���T.؝i�	��r�=n�������2i�Du�������v�`�����i��j|u�
.�aP��l�,�r�
��
��H��T#(���d�yll�n���x�aОe��l�t�u���9Q4	��ld�����ܞ��
.�
Ȟs�t���k�n����a\�e��i8�ô��`�l	�lL�sT�z|��$�����ğ��_���Tt�g|�l��m��s���t����,l	��l�����n���	��r0�D��П�t����i؟À�
���z��8!�d�0_$���p3y�10�lh�t02��8�a��c��g�k��y���4��5����s5	p�s$5��|��46�5����e��s�6��@��=����y�B��BȠb�d�l�r�B��Рa �et�i��o��uL��@�	�C�,C��CT,�r�C���	�4D	4�p`�r|C��<��Dy D��X�d����Dl�r�D��D��l��	T��t8�	��Q��L�����,K.���S.XRȡk�l@S.PW.4T�r0d��Z����l�rPh��g���o�r��T.x�n��o�Mr��sh��`k��(�c�g�o(�t�s8�s��`��آ�.�tn�4��t���d��l��n�t.��a̢��t��5�`E_u��Ģ�<8 *n�z���u@h#T����a����md�8܄��������è�����4�g�	<�g<���H�������e��l��ṃn�p�s$�tT�����p���b�
��
�����X�������H
��
��ģih
n|���أe���<

������Ptle�����zĒ`В���a<�������4����
n���H�i����P�tl�\�st���h�e��rX�
����������H�����e��à�
����<�#����dؤlP�r��`���d��t�����a<������i@�	��b4�c\�g��j��k��l�n��p��r�s�t������,�����X�������������p�l�l��s����t�a�e�iĥ��_d�����s���x�	��l������������t��إs�����k�����t���P���r��.�oH��4���$�s��_���@���X@���T�a��yp�ø���������	|�l��r��.��aĦoئô7�l�yx�����t<�	��t���L���Ц�������0����e$�nh�t8�Ø�t�����������Àwe0���0��L��t��e�~�4~T�k���\�a��HH�|�sx����o�	����jاp�����a�e�ih�l��m��y���
tlDC�j������������{_P��d8�nD�oL�s�_���0�g��#D�����T��$��|�o��v\��P�(��|yL����	��dШlبn0.��a��8��d��	#��	�s�0�aT'���i �rX�y(��������	����4�t@���,����4�à8	@�i�8.L�a�C�8Cd�d�i��j��mĩp̩sԩtpC��l�aL�d|�e��i�^n�u�ÌDt�E�G�hG��G(
(HW4H	ܩb �gh	r(�s0�t\H�������������������H�8I_tIW���8�sxJ��@�at�eh��\JthJ��`���J#@K�,M�lL	��n�N��o��p���,P�TP��P�8�0
U	Ī.U��̪� U��ت��T	�n�[��[
��.4�f<�gD�hL�jT�k\�r|�s��t��vX\�h\���|\�$���\�p�ax�9
]h�.$]�0]��]��]��r�]��^�c�c������b�����$b	īrLb��Ы�t_���e�i8�zܫ�Xd#�g�df�s�o� o��kP�nX�sXo.$�are��ix��p�@p��pB�p�`�l��r�p��h��Ĭ�̬�Ԭ��B
%q����u��Ps���l��p�s��s_�t_�u_�v��uܬk�lv���a|�e���vt�w�����$�����~�$�P8���,�a��dX�4�dȭkԭl4�n��p�r�t\�u����D�a��d�e��f��i��k@�oP�ph�s��t��ul�vl��(��t�	8�����s�����a����(8k�s�u�Ev�_���tk`H����at�.����	 �s��.(�a\���4�y�	@�g��lԒp��rȮsЮtH���L���������̱���(��h�������l��m�^8������n��u���,�_H�.��	خg,�k@�lL�mX�nl�r��s̯t�x<�Qd�	�np�������� �� ;_���8�vD����eb������gM	���d�j;n���ܿ�����������8������������������t��_����įnH�_,�	دg��l`�pX�r��v����h".$�e� h,�j4�n�st�OvD��l�p���8 1����<��(@8����P�l��m|�tp�v4E8��n����x�i��P������e����dȰg0�lܰn��t��԰z(�����a�B
4���|�d��t�"v����t�����a�tH���,��	�r��v�_@���,�t��4�kx�lD�p��t�\
����X������`��d���l�t��v,�_̮
	������t��_X�	��b��r���X�,��	ım�rȘs��.��x;�<5������p����i8�z��	�r@�s�����L�� p8��,������H�l�r`�,���`�k�p|�t��T��������r4�&d��g��jIJrزt��� �t�.вa��;��MHdL	�spL����@L�����	�g�1j �sX�,��	�g<�rX�zH�_�����	D�z��L���X��X���`�ðI4�3.x�z������s0<�����H�����L����p`�.��a�P��ijs�Oгc�d�p�s0�tLO��ܳap�rL�üPj�Wj�Zj(�z�Z)t[j��.T|	8�n|`��@���������X�����p�����o`���#������a��b4�c��dԶe�fH�g��i�j�k�lD�m��n<�o��p�r�s��t��u��v �z��Ð�#d�
�c(�d0�j8�k@�lH�nP�rX�sh�t��v�.$�.0�.,�.��.��.H�.8�G
����`�e��i��k��l��t�nn���
���.0�.����j�s�z�����������h��|�����4�.��.d������l�r���<������r��.�aT��P���$�sH�\��@�rl���H��x��������d�t���l�r��tH]%�?����eT����	��n8������������z����.��̶l�s��.��.L��r������l�r�������%s�z�	 �s����0��X���\�rx�y<����d����	d�lH�.l�e��l "8�.����g��sP�.X.��r�_���ķt8̷l���طe�t��p{.8�c�	����e��r��vp��4�t@�.$�aP���,�s�dL
�d��D�����L�ň	X�t��d������|����������i������x���	��lH��ĸ����̹�������eܹo(�pи���\Th����t<�lP�md�pp�t���b	���4�sp!�L ��H�z�k�"��\�r�$|�r�tl&,�%	��k��r(��T.��eĹr,(��'��n�( ,��0�t0Թf����1	�l��V
�1	��a �r82�����5�\7jDy@C0�lD?��8�e��l`�uDM�LX�s�O_�O	l�l�O��t�����tN����n(�y���\T_pT	��kT�	�W����a�eܺ���	��W��Ժ���l�	��W�r�W��W	�gP

��_	�g�^.�e�b.,b4�kP�lX�m\c.�c.Ph.8f`�vDi.�ht�rpl.��dj����e̻lԻr���<
��m��k�����|o��q�<ttps	ܻd@�lP�nd�pl�r|�s��t��v��z�s��
�a��d@�e �i|�o,�s@�tT�u���ܿ��t���ElE�hu.\�y�u��u��u���	�u��t�s`v�H�t�v�,w��v	��gؼl�v��������������`w8tw��мl�lhz�itz���e�ńz8�z�����d��{�����{��h�y$�ìz0�g��k��l4�mX�pd�r��s��t<�|p{.��g(|��p�e���<t�Qt|	��k�|�����}_�|����lԽm��_���ܽ�����ð	�d�n������$��}��}�� ��`}����l(��@!�X~��D��(~��L��~�@�0��l��.t��~����z�%����tt����e����	��gܾk�sd�<���Ծk(���dMs�B�tB�����B����� ����g���n8�sxc_����0�mL�t\�z�G_�BĄ.T�o,�	8���h�d�p�l��r��s���ć�Ї����z4����	��lĿrT�,���d�s���̿�؈�����h".�e�i ��Xq��r��1l�����,���|�n���4�a(�t4�L�bl�nt�r|�t0�t`����t �,�	��g��lp�.��d��t�7ä
�t�������m����
�e8�k\�ld�ml�p��s��t�z���Ԕ���	�l(�s������Q������0�aP���� ���H��X��ܖ��`��t�Pt�a���P���|�zt!
\������d�P������a�e�i�o�Ä��� �����l�x��l؛��L
Q����tĝ.�eT�md�tH�ÀQ
���4�t����<��Р8��С��\�a|��\�th���t�������o.��
	��r$������4���������g�����e,�h@�r���
��`�b�k����l������e��	��tp�����4�
�hnP����$�iȵPص��8�at�o\�������T��l����
Px�.8�|�g��t�.X�.0���t�B������ ���������z������e��o�Ō�����l,��d�����4�_�����r��	l�b��c��dH�gt�k��lܸp��r(�sd�t��v �z��t���d�r�H_h7��x����.��������s0�t���sD�����a�h�j�l �o0�r@�v���`�tp��������(��8������yl�y��y�������yT���y�����ż�	����T�k��\�l����h�a��sx^
������z !�������t����k��n��p�r����
��al�e��i��j��o�t �uL�y(��������0������	�lP�t\�z�����������������,���n����H�oF,��t\�d�d��g��k��r�t�������{,�	��jT���zp�L�y�`�����������
�r@
�
�`
�l�
�H�P	4�c`�n�.<�ep�i�����\��4
	h�nL
�(��|��`0�i��j��l��n��p��r�0��	��aP�dd�ed�i��n��s��t��u��01th1��1��1��1��2�0�b8�h@�v�2����|�������(H���$���5�d5��H�eX7�p7\�b��l$�nD�s�@9��|�e��h��k0�n8�r���(9��m9����e��l��m���D������pB	Q�.Q�_��9|9	��i�9����4_�:.�gt��T#��0�z;��8�sT�z`#,$>t�=\�dt�r�>�8?��^�]����i0A��A<B��e��r���xB�+lC0�B	��sC������ClD��C��t�D�hQ��Q.�aH�o<��(G���z�R4�R��4���X$���ZP�s�Z��X�a��e��h<k��t ^�H]��lL�
�Dg��T.��e��i��n(i��h��k��n��s��t�i��i�ķ��k.�l|v_�u�����u.�ün���s�	����,�st�4�l,���@�e��L�j��lw��X�e��l��r���@������n(�����e��.P�	��nh��������#������a�� ������Ь���e�i�o���\�#��#�#�!#�!���aH�ô��� �r��y#�0#��@���,Chll,��T���F��`��pF8l�k9��x��03.��ô���a@�bl�c��d��e�f(�g��h��k|�l�m4�n�o�pH�r(�s��t��v��� �.$���d�eH{g$�l0�v����#(����a�.(�4���8�rP�y��t�4�.X�z`���`�sH�T��d��l\���x�a��e��żPt�Rt���`���g��l�(L�����������#����d��l8�.�������r���<��zd����aL��,�Qȫ	8�j���@��d������	\�n�[�|�p�t����x�ax��������a���l�����a��e�i�l@�rL�s���,���	hCl��������tT���m����k���l����u0�e<���$�� �Pp���8�o��̿�ؿ��T�a��\�m��n��s(���h�i��l�������t����l�s�$������p�����u�����Ÿ�8̭�������n��P������p���D�a��i�p�&����bT�d\�jd�t ����a��d��k��n�y��Ð�����������.T���4�b��	p�r���|����������	8�������������@�X0�����et�	��t��8�������������ô��
��'
�(�
�ah�bp�cx�d��e��f��g��k��l��m��n��rX��t�t����P�������#�R#��#�4
�X5
t�6
#p;
t������eh�#��#��#t�`T�# �.����r�������������p���r�����a �l(�r@����$����0�m`gt����8�a��k��o��r�t|����Q��	h�n����p��h�f
lN	��i�N�������������D����������������d��_��8p����������������8��#�����iP����hL�i��mh�pl�sx�t\�ô�`�����T�����.
P����p�o@y
�`y
������������r�����a��e�i<�oH�td�u���g��l�rd���6hd�r��t��
	��t8a�
���{g�Zt
�n,�o��
��tP4�nt
j�����P�aTX�rt�t��
t8.�|�s�H�r�����a����Q�	��s������`!	��j�r �vT�z�%����a<���T�<	���@8�����|7����<C����e4�����LD�����tP�\J��@���I��H�����|���`�s��	h�s���t��@�����l��r���$�#8�����i��u��#��#�����a�e�i�o���@��L��������|��$��`�#����# �#�t�U���,�l4�b��i��kH�ph�s���
@�a��b��e@�hd�i��j��n��oL�r��t��u���8��(�.�Q��k���Da�e�i,�o���Ȧ8�����m��t�	��t������8�������ml�x�k@�tl $�l���*��)��@�eX�l�+�d-�<-��`�tpB��`ts�4	t�s�4�������#G����e@l��k����i�V��r��s��t,�v�Àl��t�o��k�n�o����aP�e��i��o��u,��o�D�e�q��$��������������tp�HpH�ld�rt�s(q��q�pq��l�zr��q	��l��r��tTr��TKd�r��<k\�.�ba�s��g(t����xte4�#8u�u	��z�W���I�0�����s�u�s�u�� �e�c#\���8�e�#�L�f�s��T�at�o���f�T��T����a@���nL�����al�	#�����el���k����k��n��ex������L����������8n0���8������������<�	 �z����,��|�#d���D�ed���d��\��T��h���p������x���X�����z4���s����e8'
�a��c�d(�e<�f`�g@�kd�lD�n�oX?px�r��s��t�u4�y��z�tL������'
P�bX�d`�eh�fp�hx�j��m��r��t��v��H�#h�#���'
#��#��#(
#`k#t�#��#�,
t�,
��k�,
����a��e��h�s���8-
#L-
�(-
�����l-
�/
_h/
�����(/
.��ä0
��\y5
��4
 �iL7
�6
��4�l�8
�\8
H�k|�t�8
��P�a��e��o �y��Ä9
���,�9
	��c��s�9
��������:
���/sp;
�8=
,H=
����z=
��s�4����������l����z<	�s?
��>
.�o �	�C
,�p0D
��4�ax�o��s�td��(I
��E
��\����J
p�z��M
���k��l(M
.��a��e$�i���8���L
����zM
��@
B����l��������H����DM
���ll��l`yDT
�XM
��n�]
��]
��0��̣TO
��D�.xN
L�j�N
��X�a��d��i��o��t,�v���<Q
	hts`Q
�����TT
��(��@S
������W
�hW
��m��j,[
��g0]
��\
����e��,^
e]
�������� e�^
��^
�s�^
�� �alo
 x)v�o
��8�a|�c��d��e��o��t���4�	�\s
h�nhs
��p�a��e��h�s
�t
Pu
��u
�w
��T.w
	��r�q
��������	t�z
��r�~
�}
����i��
��8
#��
�g(�k0�m8�t0D
#l`
#h�
#��
��

@�d��k��l��n��r�s�t$�u��v��z\�
��H�a��d��e��f,�i��k��n��o$�r��s�tH�v��zT�����
��
����a��k(v��	P�
�|�
.\�y<G�Ԋ
��
����z4�	�
���r��$P�
�d�
��,�s�
	4�ct�l��z,�
��@������� ����
��
��l�l(�_��
jh�
#��c��l��r��sԍ
�<�
�̎
�؎
�,�
j�iQBp�����X�����\�
��kh�
�����t�
���`�?���oĐ
 �kL�l\�nt�s��t��z�?tؑ
_��
��T�gl�t$�
_t�
��0Bk��s��t��_}_t
_̒
����tؒ
tp�
jt	��\��m�
����au
_ �
����d<�
��n0�
	Ŀr��
�V�ԗ
����(�
��T.<�ð�
8h�
��4����8d��H������P��4���\�rP�
h�p�
��t�e��o��t���
������s����r�W��؃r�
��
	��l��t4�
������
�̣����e��Ț
����r�
�������О
���Hx�	(�z@�
��0���
��<�ü[��kDL��T�aL�	`�r`���l��ğ
����sx���
t�
.��a�ye�
,�
.��tD�
����z��
�<�
��g�l�r �s(�uh�
����ap�e��i��uH���Őo
t�
��
��}
�h
_�
	0�ch�r �
��8�����������������P�
���
ļ
tX�
x�l��n��p��
	��
����f�t��
�H�
���
et�
��
� r$�
C��r4�
�H�
�@��d�
����������
�i$�l,�rԯ
#,#�
��La�ip�ox�uX���	@���P��h��H	$84�
�0�
��rT�
����a��e��i(�o4�s���L�Š�
	�>l��
�������<��,�
@����
��d��p�r@���t�
��_d����d �
�ll�
��
��v���
��D���
	
��a��b��c,�f8�kX�lx�p��r��s��ð�
.`�
��dP�
.�
��gxTr,�
������
��
�����\�
����ì�
�@�
��l`�
����a�h�À�
P��
�ܽ
����p�
��
��$�l�
��(Vr�
.�
D�aX�
��L�ih�m��8H�
�4�
��p�l��r��
���
t(�
��d��n��th�
����a�e4�iT�o|�t�����
tD�
���
�s��
�����`��t�����H�
���v��
���
�g$�l��
���
t$�
,�nD�z�
��et��
L�l�
Cl�sH�
�T�
���
p�
���
��ttp��z��
�\		��c��lX�md�n��r��s��t��v 
P�����hP�x�����d�������	(�bP�c��h��i��k��r��tD�y����y�_,��0���.8����D�sW�	\�r�}e����p��\���x��P � ��g\�y�����������d�y@��0"������!�������T�eh������p"��,el,����L,����8�8 �k#��,���".8���'8�#��P�l)��t�t�Xà04#T4��|�a��e��o(6�;#|@o
�@����z�?����s�C�A����l��s�D8F��(Ye,P	�r��s�P���������g�g��
�ax�e��i,�l4�ox�r��s��uT��X�Ŭhe�h��L�����D��������j�|ip�g��l��m��n<xr\j��jt�jtk,0k	��g��k��spk��k_4��(l��l��n�p�s�z�l��Pg�l��l�m�$�gm_lmy�m0n_�m	<�lln��n��P�����d�n����l�e�njxo��o�h�,�o	��g��l�o,�r�\p����i$�#@�����a�e�Ø~����l �r�4�4�������#��#L����eX�o<���� ���4��P��l������#d�jd���$�e$���t�����@���|��H���k��p��t������a��i��o��u4�Ô����	��s��j`�w
������y(�����gD�s�����al�t`����	�n��s(���$������(�h*_8�	L�tH���T�����`�l(���t���.re���H�����z�=��;��c��d�g$�n|�tL��>#>����a�e���A
�B
�����x>�H>�r?��#�@���o���D0�dX�f`�m`=��8�����8E���	���	h�lpH��p�epBo������n�����a��su�u����a��e4r	��mpu#������m��t\���l�����e��	�g�����h���`�k��l��p��tL�z ��

��L��4
��T�ð
��
��l���
��t��h
#�
����e��i��o
#h
#�%
#�%
����a��i��ü'
��'
�����������,
#h-
��1
��C`4B
���a@B
�dLB
���a�?
�(�r0@
��4��;
.d�fl�k��t@��xR
.�V
�|`
��`
��t���`
��|���[ ��sL�
����oğ
����m�
��rPf
����e�rD\)P\����zX�
�p�
�������,���
���Ie$�it�o����
�l�
P��

l�c`�d��g��i �jd�k��m�rT�s��üP�
��d�h�0.,x�r�������4����i(�#���l���T.4�a��b��c��d��e��f��g��h��i��k��l�m�n�o�p �r(�s0�t8�uH�v���h����j��|,�.X�dd�il�kt�m|�s��t����a�s	���s	�8(��������@����D�L8 (�5
Tn\dlt`nnd|l���n�8�^��P��P�e���X�jp ��p�j���x�a��e��i�o@"��!��l�E	�`#��k�n�s�#��#���aT$�`%�@%�l�,�+��l,���a(�b0�n@��\�	�-�/��,��8���1#p/��L�e|�i��o��p��s�z�2�P2t�n�3��3��ll4�45��4P��e�4����z\Q��6��b�j�l�s�6.�a|�e��i�s0���o��6�(7�t7B47��l\�rT7�� �����������7�7H�k�7��P�al�n�]'�88�t�n`O
B�8���l�Hp09�Hsd��l�m�n�p�sz���$9�L9�����9��j�9	�^
��:��C	�j(�rX�s��t�N���ÔZ��D..<�iD�t \XH�y\al���]��L�z�a_�a��d��d��b��x�i��m`�
8�h		�d�g�k�l(�nH�r\�t��v��z(j������i���èo# o���u�o����d��,,r	�n`p����xw#Xw�� �ezxy��4���x��<�ð|X�|��T�ex�Ì��h}�t}��p���}1�}�����T~Xd~����e�~#�~����e�	�n��r��v��z8�#H����a$�eT�n\�o��u��t��8w�|�����d��������l<�mD�sL�tD������L�X�#t��x�	e����l�������#d8�������i��`�����e������a�o�s����|�t������ �#Бt�����<����	�d�.�al�Ô���$�yL�	4�n����@������L�Ø��X���d���
#p3��x�e$�����t��ĉ.��a�b�c�dX	e\f�g�hTiP*k�-l�-m.n(.o�3p�3sXKt�Ku�MvNz,�\3�̉L�b��c��d�e$�f8�gt�j��k,�l\�n��p�r<s�t�u�v�z��H����l\�r4�����	d�lD���l��������hx��\d���	��s��������h�����ì���#��s��>�����eP��nL����������D����o�����r��_@���0�gT�y8��d�dh�.H�al�u����������X�k���Dd�����a8[c�k�r�����$�	��c0�zH����������l�����o����t ������������il�gX�n`�s���	�ap�b��e�i�o$�p,�s<�y�������
nl
��h�u�nn�n��|�e�
���t�
��s�,�	��l�td�����4����e�pH(��m��p�t������zp�s��$"��"�3��1D�lP=r02��L�ap�i��n<A`|E#�E��x�e f�$]������Z���r�s���xhh������g������h�k�n`k���aԿc(iØq�Lo	nxo����|��z s|�����4ph�sTt��zВdolrP��d�_X��8�tut���|a�eDCl���l��m��P���k�n'.���d��t�����a��`�����o@�	Pb\g�l�npDr�s(t��������(��1�L2��L��L�����pbr@���Ďa$�yx�(������p����|�a�l�md������$����|`-#T'���ei��/�4.	�t�)�����1��0�r�@~
�@��a(o|BX8Cd�d��kqspC��0ahih�k�p�u�;��N`a�n��P=�`=��|��O��[��.D�h|�s@c�$b	�sLb������t_���vz���i��9
G� o��iXo.�aoLtj�u�v�� aPhXiD
m��r��t(�vh�,{8`{����w��`���#\���to����|l�r��#�����at�����jkl,n8r@u�����a��bpe�h0i�k�o(�p(s�zH�tp�	,���b,K~\���$y���$,��4����������t��X	hf�l�m�����m�B��B�r`B���e�
���b�	`�p�r���T.\�t�.��eo��ň��ȝ� ��R�����<�!�T(m\n�p�r�z��"."��Pa(tgT��\$lv���t�p�P�����a'��&���ot��(	��r�+P*���l�r�,��R��R����(.�ev��)	L2	n�sL�z�3P	\a�e�i�o��s uHz��<��5�(4�Tptr|s�t�6��67�P�	D7��l�p�s�th7����l���,�H��|8	�8����l:09��glDm\p��8,�mX:��e�6l(v�:	;p ;��0e,;��8bTlX;B�;��=	=�dl�r�����|�$������>���t`A�?��vdD�|B��p�r�DBxD���k�o|
�`E��jln`
8�E��t�H�G�t�H��I�LF��4��I.0-a��e\�8J����l��J��L	�gĢrN.�e�s��4Ue,T�����R���i��O�lLO���a 	e<	r�U��C�D����D����t�k,	l�s��	e@i	mDt`�#p���4	aL	ì���X�����	a
b(
c<
d\
g|k�l�
m�
n<o`pLr�s t`v�z�	�d�@�l�	t�	u��#����.���	r����	���������	�	dl����	������l
�(�P4
eP���
hL�������z����H
sX���P
a�
i�
o�
ruy�
��	X�k�
s�����
�������%s��T����
a���
n$��t��
r# ����
�����
�d�#��H�.h".4bDePiXm`rhs\:8����	<nd��D8�H8�Hp{.�	��pe�lhh�
��d���
�a��fh8jLo
s$
tP
v��|�lQ	�gH�����(	�%	�rL*nX*��aHV8���$�L���,�D1�t0Ds��_�1	Xa�d�f����82��`��2�
�2���.3	�t�2�����2����83Q���
�1������.������$�80������7����888��
��7��4
o
��p�;8�;��<
� ;��D
äDD�D��\
�D��d
�@Cp
lD?��|
e��l@�	DP���
c�
e�
stN���
c�
gs0t��	tp�	�
g�P�U_��	_�Z	�
g�Z���
�`Z���d�	�,[uP[��$a,bP�lLr�d.plp{.j��Te|iԻr�s�t�n`�����.�a�i��`r���z���Hp�����L�t�r�r���ai,o�����T.��	�r�r�������m|�
t�r$l�{��z8j�s��@e�txÌ�@z���	dz�v��l�������������a�eXolu��8�C�rH�����8�d�t�t��������glm$s�������P�b��_Ȍ��tt���	0lHnĜ_�tx�Pl��th�#$�C�z��
_�����e�t��Ŭ�	(�s�������
�	�j�k ����������r�Ì+
_h0
p|0
���a4���g`s���	e�h�i�n�o�ru��,ż�
�ȥ
��Lzt���Tspz��,��,��	xtp���������_����l̰�l�n�z`��H�����$�yز�nص��8�a��t4�s@�
��
	d8z�����\�
.��� �	@s����H�����T������pr����xe�r��
����e��d4����e��	l�bd(gh�k�lܸp�rps�t�v(����	�lp�����D���ü����� axeLg\i�o�uT���e����L�(���������t��pg�l�r�s�t(��P���e��|����<���������m�����e<�BT�����(������@�BT����t�_��	 k<pDs ����W������Td�g�m�r�z�����.xa��@I(�����H��|����t��T�k�l��t���@��`��t����X�T,l4n<rt����aLelixu��L�������������Dd\g(��
L���dr(�#4������������Ms��#�n�p�r�����a@e4i�t�u�x����90�������	�b�l0r8t�������������h����T�,��,\�.�dxg�i��k�l�m�n�r�s�t�z���]���L��bD_p��t���v�l���	�b�k�npr$sd,�y���k�������e,�x�,mPnXr`s��z�����t�e�e���p���`
T�t�
t �1�`0�g�0���at+di��s8tt�z�Ü<W�;	�s�2�������>�t�=n$s�>W�>�C�� ��<B��,�\�8��
LC	H.`C��P��O�(G��hs�z,���Q.�h�Z��<k��tDg��T.�a�e��i��n$�g�g�d�h�#.�ik��n��sti�(i�ķ���W�j	tth���4��m�����4��<�����D�w��PltrĬ#Ь��la�i�o�!���a�i�����r�$#M.
�a�b�ei�o�psu����\M�djm n4r`tlv<O9lQ�lW��X�lX��gPZ��Y��,cDm�Zy \X,\��L��[��T���]��m�n�z�]��t��������<�D�(^��^�
�^���ydb��c�c���l�c��gvz�d�di��i��i�hn�,bdt<�4����x�o��4�x��q�qLlpthq��Te�r���s��u��d�r�svjz�Hz�Tz���s{��}�d~��~����L�T�D����r@������cpĀ���,r4z�.�B�\��؄�0�����a` d� e� f� g!k�!l�!m"n�#o�&r4's�'t|(u�(v���&���$��a<bhcpd�e�f�gp�h�i�j�k@lP�m�n�o�p�r�s8t�u�v�züŐ�t�����x�����������ІjPaXo`rĆt8�t4����# ���t��a�r�x����g(��d�(��Ԋ�k��t�#(��<��rsH����a ��������\�.�	np����0���_�t(���8ape�o`�4_|���X�x�T�t�_�jt,K�����y�W��tXt�Yt�Y������\���#���Ď.�a(o�����zT��,���� �0� ��\�����x�jPaxo`È�8�t��_����X�p���_t�t��_������؏Ŀt�]t��t�#D�#��ȒT�nԒ���at��l( n< è�����\$��)��)	 r���� �P��� ���$���4 ����`�H gx o\���P e� o� ���ŀu����O����� m�.��� rt�_����� j<�p{.� zd���� a� e!r���@�.�����T�!r���!eD!kl!l�!r�T�jT!n ���4!a\!oP����`�Pl���d!a@�8����x!�p����!� �,0�	�!tP����!�(����!l�!�p�����u����tp����!i�!p��n�����!u$�t��"l ���"ax"d�"e`�f�"g�"i�"k�#t�#u�"vd"�T���8Xt��	L"r���X"�l#�����j�	`���"e���"r��n�����"e�"rT�������"mh��t��"c#l�����"a@#o(#���	��y(���#t4�	#r����#�d#�T��`�8#lX#rp��|���P#s�������	�(�t#r8���|#a�#e@�td��#s��	jX�	���#sT��#s(�x�d��f�#g$i$l8$nD$pL$v��#$r�������$aL����#($o����z
�t���0$n4�#�
.8����T$a�$c�d�$e�$g%kp%l�%m�%n�%o�%p&s�&t�&z�$��%�P��d����$��$��%��%��&��&�H����
�����x�
y��$l$%m����$a@%eH%lX%o4Vu4%è�
yd�Bp���,%����T�
��
y��P%mh%s��y,����
�Vgh���x%a�%����������V������%���������
���
���
���%������
B$����%�,&�����&eL&k`&z�%�X��@�
_�
	$&r

��8&�t�
��@&�h�
�`�X&o8�
B����l&�h���t&���P���	�&n�����&������&���.@��&rd����&�������&t�����&a'o #t|������'d��.P���('iT'k8Bm`'p|'t�'z����D�o��_P&
8��h'd����p'a�$�.�'i�l
t��'j����'aD(iP(o(!y�'È.h".(a(o(�	�'r���'�0(�pp�
����(�Ȉ
p�	���!rL		$(rL
�
<(kP$l�T�(n�(o���\(i�(k l(m�(t��$$D�H@�	�(z�$���(�%���(�`.����(d����(a���`!	)j<)m�)r�)v*z�%����a()�l'�&�� )��,�,��4)a�)e�)o�)ud)��)�-t(-��\)��)��)��)��-��-|)l�9	,8.	�)l$/�D/#x/�������)��/#|7��T��LD��������<C����e�)�OP�I��*s*�\J��4����J����(*a��0*k�*l�*r@���<*a+e��f�+i�+k�+l,o�,r-sp-v�*�\-��_D����*j,����c�*o����*m̛	t�g�*l+r����*�p+��+��,�(-���8d����*l���,gnH��Cg$+p,+r8�`����.@+kH+n���
Դ���
ĿP+l`���X+e��	d+p�+r�;�����+������l�+t�+Ð���tDÈ�lp�_�	�+z�������+l�r8�����u����a<��+l(,pP,r�,s�,u�
P��� ,e��n����4,o\,ul,�P���<,r��
���
h�
��d,���	�,�x,k8����,a�S$x�_@�	�,b�,s�@8�����,s
�
���,�`��,�����,e-i-��P�JL���-������p	�gH-lT-tDvp8H��@-t�_|���x��#���h-a��.��|-s����-i�-u0��H����-a���-br �)	���-o� 	�-l����-e�
.p
	�-k
.�-a.o�k	��.y�
	`Ul8'

d.a�Rbx.e�.k�.l<0m�0o�0p�0rl1s�1u�1v�Rð'
��tH5
��4
p.u E
��C
�.r0D
���.a�.e�.ìE
�� S��.�\��@F
#G
	�ll�.p�G
�O
�xN
�.j�N
���.aD/dl/e�/i�/o0v8/��Q
n�Q
��/o<Q
	 /r`Q
��,/�@S
���a\/�HT
8TT
��T/�(���U
#(�_`��t/rhW
|/b�/m�/n�W
��/a�W
��p{.X
���/a�d0�g�/uX
`X[
�,[
�/r�^
��^
�/d�s�^
���/aPd
TL0l\d
��0a�Le\0ip0o�0�l`
�� 0b�0lDd
��	�	T0zL	� 	h0le
�e
	|0r�d
���0��0�	��l
_�N
#��
�0l�
���e�0f�gy�tD����0������0�\�
���c1i�~m(1o41rD1vĐ
`Th<�
 1m(�
��4,o<���
��
��
L1lT�
��T1oD�
��`1k�1o�s�1z�M
`��
��1u��
�1k�^
4,�
.�1s��t��
$�l�1n�1v'#hA#�
��Ti�
	2n(2r@2vD�
�(�
��2i��
���
��2�h�
��82i2�$�
�8��X�\		�2l�2s�2tD3vtYz@��0"��h2��!��p2����|2t�?����v�2z�@_�
8�A���2b�2e3fd�h3i3k��m�n$3r,3s43t�2�pB�
C18B���2�<3��a8�C���8�
8�D8E8�E18F��h".,P	x3r�P��P3�HM��h���Y��g��l3�@�����a�~���3l�3r@�#L����3a�e�3i�Bo�3� ���4��l����#h�.(4a�cb\�d09eX�fl�g�?i4BkpBl|Bo0GpPGs�Gt�Gu�Izh7�LF�ܽ�`4b�4d�4k�4l�5m�5n�5p�6r�6s7t87v�zD�����l��d�l4tx���t4a�4�������4��4�ܾ SB0����4c������j���4k5p�����4aX5b`5gh5kx5l�5m�5oD5�,����������5���
 ��� 5�<�	P5t(5�X���45���%�[��B��B�p���p5e���Թ������5b���5m�'	L����5b<�y�����5t��	�Rl����5�h6�����6k86o�5�\��
d���6a$6�|�e����6�Te���06rf�
f��D6�����x6rL6���	X6k�6s���
���
���6b����6a��Bh����6c�6g�6k�6z�zy(�y����B�����6s�,�	�6rD����6�����7�$�p4���7a�� 7r����,7a����7b�7f�7g�7r|8s�8t9v���D7�=�tA��D�`E��H��H�`�#D����7e$�y�y�7a�7u �1���7k���T�`��7d8k8m8t�����7aD8nX8u08�����d(�	8�t8���$8��Q�Q��<8y$�1��P8k�X���d8��8�0����8il8���
l��x!�x	�8s�x���8��x���8�\�	�8sl����8������8f9m9r�8�,
�D
����d=
����9.(9o`��
0���9b�9c�9d�9f:gD:kX:l,;m�;n�;p$<r�<s�<t�<v=x��8���t9�����|9�p���4)e�9sP�t\�.�9a�������9������9z�9����t�	_����9f����	�9s,���:e$:ü�Xd���:�ĉ.0:.����8:ed�2����P:f�:l�:t�:v;y�:�����x:������:�@�6H����:� �	�:�4����:�T�!T����:a�:�l�d����:�T�|����:.�<@��G��;r����;e���� ;bL;eX;l`;uD@CD;l�y�L��	��P9�����h;�8���t;d�;i�;y�;z������;s
�\�	�;n�.�;e��Br�����;������;�4����;rؼ�0�	<lD���<�d<�����X<e<�t<����8<d��@<e|�L<p�����(���l<�P���<������<z�<�tz�H�.�<m��ط	�<z�����<������<��P����<�d����<�$�����@=g�=h�=k�=l>mH>n�>p�>r�?s�?t�?v�yd=.l=bt=g|=h�=i�=n�=rĉX���|}_X�����P���!��}l��=sL����=e�=s�}���%�����=a�=sM� �!0����=e�
������=a��f����>bT>et>k|>r�>s�>td>���>k����<>e�p��z����\>��>����f�f���z�������>��>� ����>ð��e)�e���>e<����>d(?vd?z?�8Ny�<���>k��	�>s����?�ĉ?8��� ?.D?bL?hT?r\?v0�����������t��T�t(�.l?a����t?z`��l��?b|����?a�����:.�?e�?n�?r�?v��?k����M�Ԉ�<��D@dd@g�@m�@nAo<Ap`Av$@��T���@���P����0@�x���8@�H�P4���P@����X@��X����p@�����@o�@px@����@g4�� ���@o�0:.�@s@����@a�@d�@pAv�Px��HGB(��d��H���AoX�An|�X����(A����0AÌ�Xx���HA�lA�T���PA��P���Ak�Ar�Av�Az�)�����Ai�8����Ab�Aj�A�M�1����A�x
��
��
���AeBlBnBt���8��ԧ80���
HBz
Bs4
��(Ba��lX�v��)�
��
	PBg�
��XB��
��dB�T
�	�Bb�Bk,Cl0DmPDndDpxDr�Ds�B�p(
�8
���B��B�
���Bjl

t|

���B�

���BkCo�Bð

��9.p/P0
Czx
P�
Cj�
�� CaPCd�Ci�ClDv���
��HCahC�T/��
��`C��C�,��
xC.�Ck�Cm�Cn�Cr�Ct�Cv,
�4
�<
�D
�L
�T
���������C�����C��G ��Cs,���Da�

��

Dr�
��$Do,
D
��<Da�

��DDthBD
��\Dc`�
Bx
��pDc�Di�Dk�Dv
�L
y�
�p�'�
��Dl�Dt�
���DzpY'4
��DdEkTEn�
��
���D��
���D�
���
Es
��Ee4EÀ�
e@
��,E�l
x
��@E��
��HE�t
��EkX�m�En�Er�Es�Ev Fz�2����E�����E�`
���Er�
��Ynl
��9.�Ee�
���������Ez
���Es<
���:.X
f`
	Fnl
��F�x
��FÄ
�\F.dFb�Fr�FsGv�Fô
��,F��I�8���
�DZ.�YlFh�
��tF�8�'�
���Fz@B
%LB
���Fa,
��Fr8
���F��F�
.�F�G�D
���FzL
X
��Fk�]
_dx��G��z��
�h
Gk�
��$Get�l��rP�s��t
��HGl��p�kthGz!
��Ga|K=
��tG��<
��|G�0!
��Gk�%
��%
�Gb�%
���Ga�Go�Gr.
\�p|0
.�Gu@1
�X2
�Hdȓg(Hh<HjPHnpHr�HtX���2
��Hvtx3
�� Ho3
_3
��4Hj�3
��3
��HHa`Hi����3
��3
��hHa�H�4
4
���H��|D4
�Hsp4
���Ha�H�h</�4
���H�n��4
��Hp�Ht�Yj�'7
���Hn�7
�Id8IgXInlItxIv�Iz�7
y�7
��7
��$I�8
��HIy,I�`��<x'�8
��PIn�|�D9
��dIed~�нBt9
�8D�t;
��Ic�IjJkJm;
.
�IahJcxJe�Jf�Ji�Jl�Jo�1shitKu,Kv8J�KŌ<
9=
��X��<
���IèM9�@
��?
�Jldlm�ppXJt`Jz0@
��J��J��J��J�$K����C
��a��C
��J
��C
�pJr�M
�xR
��R
�V
�\W
��B�PX
��Jb�Jk�ll�n�lr����X
���Jn�[
�4[
��Jn�]
��]
���J���� c
9�b
�Kg�c
f
���
#�
��4KadKe|Ki�KolK�Pf
��<Kr��
#p�
�����K�l�
#�
#���KcLk,LlPLnhLr�Lt`T�Kn,���Ki�
���KcT�$0���Ka�Kr����Ko#���LeLt0�� LaDL�\�P��<L�'����d`Lo()`,���a�<��T��;��tL��C	�j�Lr�Lt�Z��h".�!r�b��x�i`�o�h	�g�Ll�LnMr(Mt<Mv��z�o��(�4��<x8Xw���Ln�x��h�eM�xy��|������|����e��Ȃ�d~���e�	TMz���P�� ���`Mm���hMe�M�d�tMn�����Ma�Me4��D�	�MvP����M�̲�ز	�Mv�����M�d����Mn�MÈ��Mn�W��B.,Na�Ne�Ni�No$OuDOy�N�O�HC�@Ni��#G8NbhNh�2kpNnxNr�Nt�Nv4�È�#��#�#�#8�#�U��U���N��N��N��N�O�,O�4O��c��Nn,p�����NhЂ����Ԝ�t����г���8���O�<O���������������	��ĉ.�Oahb�hc�hd@ie �f̉g��hx�il�j�k��lh�m��n�o�pp�r��s��t��u��v<�w\�yL�z|`�d��̉��ahPb�Pc�Pd(QeTQfxQg�Qh�Qi�Qj�Qk�RlWmDWn�Wo�Wp�Xr�Zst[t(_u�_v�_y�_z<P�`�H�(PtL���0P�<Q��W��_�h�nt���TPaH���\Pl�&t�tPr����|Pi�PzܢPԣ�<�p�Pg�Pl�Pth����PaQr�Pð�������Pa���LDo�������������P�|��� QoQ�Ȱ����r��.P�4Qd��r�������LQlp^r\����dQl@���lQe�Qg���M_�����Qht�t����Qe���Qr�Q��	�Qn�����Q������Qt�Q�H�_�����a\RktRl�Rr�Rt0R���.$�	RrH���$R�4��
����<Ra�DRr����PRolRuT�p����|���|R������i�R����|u`P����Ral�RgSj(SkxSl�Sp�Sr�Ss�St����Ra�TexUf�UiDVjPVl|Vo�Vp�Vu,TðV�L��`�t�����jDStTSupSzdSÄ��a��Ð��!B���\S� "������t���Sc�Sk�n��r�Ss�St�S�c��,��i��,D����S�X��,����Sz������StH,4	TgDTl`Tr�Ts�Ttd��T�4U�W�4��9_H	�$	LTk4	��TTa4�b\
n̦r�T�P	8\	��|T��C��	���Tz4
X�	���Ti.�Te�Tl�Tð
�TlUsT�`���T�|\�a	�m�l��U��
�U�X>,�� Ue�	(UkLUl`UnlUr����o.(XyXUe<��T.j����Ud�Uk�Um�Un$Vt0Vz��`,��Uo�Ur�_H.��	(|tؔ���U�|��Vd��f0�g�j�ZtVu�Uà��ԕ`8�_d��Vt�.<Vo8����0�i��\Vox�n��H��dVe�VoplVm�Vr`�T�t`��<�	�Vr�"���V��j�8!�Vd�Vr�Vs|t��tP!��!.�o�r|!	�Vt�h_$��Wc(Wf,_3��10Wl02��8WaXWgtWy�=_�KUK	`Wg,K.hWa�W�QQ�L���W�XRȡk4T�r�Wv�W.XZp{.�WiXk�Z���Wa�Xj��l�p�r�s�StTXè�uD�BP���X�@[��Xô`�]		 XbdXdd^h��i�Xk�Xm�Xtt^vtX�$]��(X��X�D��H������lX�x�`��X_��P_8�( f	�X.k�� cek�XbYd(Yl<YnPYz`k���Xa�Yc�Yd@ZeLZidZu�Y�h�����Y��k�Y�|��m�� Ya(I�m��4Yt$o�0o��HYoTrn`r��\Ya,
fLo	dYtxo��tY��>��r���e�Yh�Ys�Y�t\ut�t.�Yo�s��آ��u��u�Yl�u���YaZeZ��ut�u���Y�0Z�v�(vZl(Zs�t�
edwt�v8Zl�zL�l\���js`�XZs|Zt�������tZa����	�Zl<����Z�����Zl�Zph�s�Zt�Zz�Z�X��h
�|����ZeВ���a �.�5l[t,[�ԝ[���$���[���/p���$[�0�_|��8[tl`v8�@[k�[l�[p�[r�[s\t46zt���P[a�\e�]i8^kT^l`^o�^t�^u�^yD\��^�D�`X����[a�[k|r
_ԣ�����[u0���������[�|{
	D����[s`}
_�{
��\tD��y\l��	$\lx\r�\tĤ��0\�T]�^�l^��^����Х`\d�\m��yh\al������\m��r0�n<����\a�\�|�������\����x��\m����\e(]k$ylD]t]�8]�l��\l�
B��
��]� ]��
��_$��4���0]�\�	x���	L]g�]r�����d]e�]hx�yl]t�vt��D����]d�]k��m�]n�]s^z<�
.�]o��
���\f�]t��px����]eȰ�|t �����\�	^r,^v��p����$^e�j<-���
@^s�
��H^at��&s��	t c�^m�6r�^s��
�D�.�^�
� �
���^�d�j���8��^t@�
���	�^t��	�nr�����^����.�^a_�H�`��_���`�� _kD_pP�rL_s\_t��#,�.h�#8���T_a�_ð��ܾ_l_p����t_���.h��_t�_zT�.�����a�_v��_�������_e�_�@���h�������~.0`o`�p����	�_l����`� `����ܨ���(`k@`n���@�	�`b�`c��f�`g8al�ambn��plbr\ds�gt����H`�T|����@��������0����������`�����pbr�`ð�Rl�.�`o4����`h@���Ďaay�`ø�����p����
��	alPar���a�������Xae`alxay(a�0
�l�$��\��	بn0.laa�a����as����a��8B&T�at���au�a�x&t4�� ��8��8	�adblbn�Lz�8.�aa,b�T'���ay09�\9��
�:��$b�8Cd�d�bg�i�bj�bl��m�bn�bp��r�bs�btpC��	8ba�e4ci�co�s�ct�cu�bøc�xDt�D� E��F�G�hG�t�s�G��bkP�	4H��l\H���b�c�����c�$d����`L�lL	cbĉ�; c.�N(caPcjXcl��phcs����O��P	hP��`ckxcmhG	�R��R�cd�cn�ct�j����d�Tt�U�$V����8d��X��r�[��.�kds�0��T.da��	�cg�����c�$]1d���X�]0d.8��^��_�8_@dd��l��rt_��
Hdaee(ef<eiTeo�et�eu�ez�dÐeŌb,$b	�dg�dr�ds�dtLb���d�le��e��e�T���b�dd�b���da��h�u@cHhc�pc���daXdL�	H=lX���e�Lf��e�f�df4em�sLi�|hLelder�i��ixer\j_�j�k�k���e�m�er|m�m��m�et�ne�o� o��eg fk,fl4fr<fvXo.�ea�fe�fi gkLgs\guTf�<g�LH��o��fa�o�?
�|p��p�`�l|fr�p��Df��f��f�,g�hg�pg�L]�q��tfk�fo�]%�qt�fl�fm�frr4rdr�M
y�r��fbPs���p�fr�fs�s��s��s�tt	gi(t��g�tjgÜt��t_u��4g�xg�(u����hu�Tgs�u��u��e
��uX�glv���ga�	d��e,
f"jD�kD
m��r�gu(�v�g��v�w��w���g��g��g�h��y�4|��������mX�hl����ha0hl\hr\���toHh�,������@h���#����Thoth����4���lh�����̟l�hs(4�Hp�3.�ha|n��8�h@i�hlLO���he�hiio ir,izi�x���d|�.��	�hs|`���h�����idp���`��L�P8is������ia�jb�jd4ke\kf�kg�lh�li$mk|nl�sm�tn�uo�upHvr�ys�zt,{vD{x�{zDk�D��d��ia�c(�d`�ejgp�hjlLjmH�nTjo\jppjs�	t�i�0������i���$�th������jylt,���jaDjo8j��_ ���0j�pt@�#XR�P�#��|jfH���djzܚ����l����j������jà����Jl`��jl�����jekr�z�j�T�Q��	�jn8����j�k�$k�|�,@�	�jv$��0���ka��e��#��,kp�s����������_���Tkf�r���lkl�knX���	tka�kePlg`lillo�ly�lz�kÜl�P��l�_��	�kl�����k�l�xl�D��d��kllnlr|�������kn���	 k4lr@lsd�n����,li���dMs�z0������Xlzt���
r��C�@l�lr��s4��`�Ix����l�����lzH�.�le�Hd�������Qh�lo�8
.\��lg��#���lf��g��	mk`Cz�	��maLme<nl\nohns�m�<�Dmg�ml�mp�r�ms�����hmn<��pme���|ml��8,���mzd
8����mb�me�	�mk�mlnr���m���_HnT���mintpv8�z���TDe0n�|����(n�h���Duh�	���Hnd�PnltnzL)���
�a�nephTpk�pl�qm�qnro�rs�rt�rv�o�Hr���t�nf�ng�nm|ot$�����ny@ �L ���na oePoulozo�`o�!��|�h �g4ok<ol� �� �Hoed"�f#ND�f��Xo�p!Q�o�$��toa�oe`�*h����o��$���oä$�otl&5�%	�ok�ol�osH���o�r�'QT��)���osX*��apo�un�,u�,��p��,�� p�hp��,��,pd�,<pz�,��Hpe�,u�,��`p�`-�-tpl�pn8-��|pa�pelqi�qoLq�qŴ�tP.�$.�pg�plqn,qr|..�pe�pk�`�p.��.=�.���p��.��qe$qt�p��.�
(i_���0�,�	4qj\qk�-��<q��q�\�, /t/dqn|qs��tX/�@/�qsx/��/	�qr�����	�qr�/���q�|�n0���qah08D0���q�T0���q�D1tt0rs�1B�l�Py�� rd�1	(ra\rk|rr82��4r����
 4��Tra6,�5hrj�5��pre��n�7���raĉG�8���r.p8���rt08�rt�7���re�r�l��9	�rr8���r� ;��T.se(soD
�Xs�p��<ss�<n��	0d.dselsktsn|sr�ss�st�sv�<��0s�؂|���H��i	�\��0��`���?�?�sd�sl�szD?���sate`ti�to�tp�tut�(@�8A��A,LA	�sllA���s�Lt��t�DF�@Ctz:�d�(tmlG��0te��m�F	<trDI��HXtmptn\I���g�R	'�J|ti4LDM��L�ts�M.�M	�tlO�<N�tltN���ta�te@ukTuotushuüS��R�tr|�#����ue,�unt��u���	(u�xW��4uiY��XLul�Ye�O��`u�`Z��<*s,b�/d��f�ug�uj�ul�um�ut�b�B
#,[
t\c���uo�c#f#pl.j���uevrvs����qP�q���uevo@r�`r�<t�ps$vd�vk�vl�vn�vr�s��,vawd$we8xi�xn�xo ys,yu�yv�v��xŠD��t��|va�t`�t���vahu��ut,w,�v	�vg�vr�vswz�v���v�$x��x�@y��w,Hx�0�8(����vttz�����
.�zwcPwddwj�wk�wlxrxs`{Q,{��Hwm8}�{��\we�w��{,�{	twn�wr�wt�{��|w�|,@�,��u|�wi(|���we�|P�|���we�wl�wt�w�}	�����w��~��~���d+_��	xl�����0xdL�lXxm`xnhxppxs�� �t�����������	xxk�����x�l����x�ć,Ї���xz��xs��	Ŀr���x.�xsytyv����x�8�1����h".�x�\����,���yp4�T8yt����	��gPylp�����ddye�ytd�p(uv8ulyt�t��tye�ȵ���ys$��ys�����yih�Q�����ya�yg�yizk0ztHzu|zz`z�L���PWp��8 ����y�����z�Ԛ���zs����$ze�o�����@zr$�Clzr���Tz��_Рĝ.tzm�zt�zŰ��С���za8�I����z.�����z� �_��	�zkp����z����{i�z���<�
���za {�̰�zk�B���{������_v���#$���<{a\{i�{o�{�D�Tl{nt{z|��������|���|{��{����Ծ�\���{k�����{a|i|o�r�{�<|�pH����0�	�{r<����{�Ȏ�L��{l|n�������r`�jl�	$|eH|k����,|��\	��t���	l�b�|d�|f�|g�}h~jP~kT�l��m��n��pԃrl�s̆tD�v�����|pD����|a�|u�|�H�tX����|���#��@����|l����	 a@}e|}g�}i�}o�}s�u}��}�����L�P}��}����������8}bpg��	<pd}rp}sL�����k���8�z�����g�}n\��������k�}t������������}�������}st����}e�}s�\���T.~b�\�Bt��	~b|~c�~dhyl�~n�~p�~sXCu`Cz����	$~ae�i`�k��o�r0�u�~���H
�P��4��@�t��t��	t�g�~r�~s�~t�����~���4�����Ѐ� ���8Xtp���������~aD��P�g(llr|s�t�z��.@eHldtX����4�	�����P�h�_��t��,p���tz��������iL���,��	�j�kl�r�t̻,L���T.�n����e������z���s(�v�B�����������`mp��4�s8��<�c��H�ld���T�u\�_���l�d��t�l��p��r��s\��l�t����|�z��C��r����,�	Ȁv�z�����������������	��mL���������Ä�t0�(�tx����<�d��m����	D�a��e�iL�kX�o`�u|�y���������e������h�p��\�xg��l܁s�t��Ё��(��ȁ����t�n	,m�oP+���_��	�n�������������	(�z����4�����@��#`
 o��
	h�k�.���(����lh m����e�TЂc�����a�e8�y����l	�p���؂aH	�rh�����o����ed�k< ��	$�l(.,�eX�id�ot��H
��"	P�dh#��#b\��l��$,��&����i���-��'���������/�`0�g��l�n��r�s�0��	��a8�dH�eĄi�o�t(�yD�z�Ð1t�1��2��2�������|���������d5j�7�p7@�gh�m��s��z�:_:��`�l4#�@#��t��;����t|��L;_�'��<��;	��sHO��=��fԄk �t�@��@܄tAC��r$A�<B���]h,��D,�D	�t|�PF�F��0��F��8���FT��j��l��p��r��z(G��P�a�e`�z��ÌGt�G�@H�XH�<Vt�HCԅr�H�������(��0��@���H_xI� I܅g��l�t�I�����J���i�J_K	�gN�Q�PQ,Q	8�tRhQ�L�k�Q.T�aH�o|�àR
�R��t���������VB�V���k��v�Q
y Z_��t�Z��s�Z����a�e�i�k8�Ũ]�H]�gLbt�a��s`�
��b���e�d��d	$�r�d��,��Dg��T.��b��e��f��h��i�Mk��n�Mr��sĈẗuԈv܈zt���h�h�#.�ik��m��n��stD�z<���i��	 c.�b��e�h�i$�k,�n4�r<�s�iWȶ�Զ�k�n(����WHjoPjW��WXjW0�W ��l�W�j	L�b��i��n��rt��vth��T��4����p�%�j%��Wd����kXm�m�mX��xn��	�wm��.�e�����yP�	��nh�����w��<�l\�r��(�#����4�oD� ������t��Ь���e�i�oL�� ��H��x�	|�r�t��������5������i���ܿ	��z$������������e�l<�rl�y���l�H �#@�.�
#
���e �i0�ä�#��l
��(���!���aL��0#��@��\��%�I�03.d�u(U�tR��x�i\M��l��nM����a�i܊üX�,4elX����gxlm�i	Ȋt�]��Њ�l��hn.$�h,�i4�j<�kD�r�Ôo��o������������������|�e����L��,���T��\�	`�s��Ћa<�c��d�e�f �g��j�kl�l�m4�n,�o�p�rH�s��t�uH�v��z���$��a8�bhch�d�e��f��gp�hČi�j�k�lP�mD�n�o�p��r��sȍt�u�v�zü�І`�i`rT��p�_���L��ؕt �j|�a��i��o<�j��P�t��t�����a���d�����y(��Ԋ��k��y�	Ќpp���،�H����o��@�����l(���8a4�i(��|���X�h�t�1t���<�ap�i`��5_���X��x��<At�F_kt������aĎ.�a��k��������zP��,���0�x�������Ѝe�ot�؍k(�n������h��(���H��lP3�P��� �nP*�`���4�k`�sD����L�j�.T�ax�e$<�|��p�r`�H g��jx o\�����e�i̎����М���QP�	��n������L���؎o(�����u(������l<�.d����ap�e`�ø�L
����4��t���<��ȫ	H�n���T�����@�.��l����Q��	��n��t���������s������e8����	ȏp���Џ�����l$�o@�rD�uP�v܏�l����Q4����h���nTPк�0�a0�8�t������X�n(���`�a��e��i�y���Ԑ��.ؽ��m������g���P���������<�t����̐����p���D�a�!i�p�������������}�����a���k ���
(�a<�d��fp�g��i�j$�p<-t�"v�Ü�T��l����d�a��e���=���8�������������r��	�����cԑn�������̑g0��@�	�d�m�n�r�����d�	_��	,x�,HG_(��a$i��k��lT�m`�vle
t����L�e�
#��T$a�$e�$g��k�m�%o��p�r<�s��u�$��%��$%m�����aH%lВo4Vu4%�L�y��Ȓk��
B����ܒ�h�������Gp�B|����� ���(�i�Ø����>T�k����0�ip�o��p��z�
ĉW@�
y\�.��
d�r��
���
��|�e$�
/��
�����`�����������D�.�b�i4Po��p���r����ԓa�D0�����P�Q��}��`gt�����a'o #t|c����4�jP���<�ad�iT'k8Bmp�t��p{.����}e`���a�|�l��r�����a��eH�t̔ðP���g����Ĕ�xC�n��ؔ�����i�� �m:	_�Lild.� �t� �dT�rd�t���4�a��d�p��\�a�� ��p��t��x��`!	��g�j4�r|�sԖt�v<�z�&n�&����o�%	��t&��ȕ��������%����eԕ�'#�'�(�|(�8�@8����X��|7��D�i$�À:TP�k�:�<��>$�>.`�a��e��Ä>��h�z @�h?���r�@_<?������A��hs�A	��s`A�����0A��Ȗ�<C��T.��e�n��(��EX��yhI�LD�������d��� ��M#�I��4�i*s*�D�Ũ^#��y^T�g�P��`�e��i��o���^.��a�l�k��s�vm<t��n5e@������h���ėì�Зn@���ܗa�i,�l`�rt�v��j���a�e��j�#8���$�a��iD�o��u��#L��������$������i�oL����h-a��i�#pB���sl
z�4	��s�4�����������e���$��T�W��̘��VԘ��j��	�g<t�a����r<�	�k��������_�#		0�r�"	��8�� "	��D��� 	P�g���\�e�v	#�u	��t�eTl	|�p�k	����aЙ�ı	tб	�����L�	������		��v}	��ę����,	�l��	������	����t�		�k8'

h�b��dЛg��k��l`�m�n$�pd�r�u�1v4�y�R�)
�'g��l��n8)
��T�a�e�i$�jD�o����p)
�H�.��y��,��_�)
	��rؚt�)
�������t��|���)
�4*
T�lX*
t��_+
���l�*
��l@+
�T+
�`+
���ep(
��(
��0��t+
`�ll�r8�ì�_�+
��X�d�+
t\,
��,
��,
��,
������4
��1
������0
�����L9
�\8
��r�s�8
����a,�oh�r��\9
_d9
���z$:
��9
	��r��s�9
����t��Pt=
$�l@�rH�s=
�H=
���s>
>
��T���=
��\�Ü>
��D
��C
|�l��s0D
����a�e$�kH�o�4E
��E
��E
	��jhCl�E
��������X��l�����@F
�hG
,G
	�l�.p�J
C�,bPJ
����hI
����<��$��0���4���J
\tp/z\L
�N
.x�o�M
	`�t�p8N
�xN
�.jĝt�N
����a�d@�t,�v�.(\P
��rlP
����a؝e�À0�T�_Q
	�r�P
����@S
���a�o\/�DU
n����\
	�rL�s]
��$���\
��4��HA�,`
xcl`
��T�a0�b��e��l��o��ø��4c
��
4�b��h�j�k�l,gn�o̦r�t�Üb
	��r�b
��������ğ�̟�|c
8\c
��؞��	�S�j��	��n�X��<��Dd
�Pd
�l\d
��$�aP�ep0op�ðd
��d
H�l�d
�e
	\�n�d
��d��$f
�le
|�l�k
B<m
����d�l
��nn
�n
������1B$o
�p
tlo
ԟd�l(�r0�s�o
��ܟa��d��e��o�7s��t�yP�ìp
.��a�q
��q
t8r
_�q
	8�ld�t�q
��@�������s
���5a"jh�s|�v��	u
j�u
���	�{
����sd�z�z
��s0|
�}
̠r�	 �
�؀
	Ԡl�r��
.ܠa�e��|
��
� �
���
�����
���e �l��	�
��4�n�
<�k��l��p��s��t\�
��H�a�d�e$�i��kd�o��r��sԢt�v����P�
e��
��
t�
���
8��
����l��o�
	ġl,�
��ԡ����� ��X��
�rlh�
�
�Đ
�dL�l<�sL�tt�
t��
�̒
��D�a��
��4$l<�
X�k��s�
8�
.t�t��
��|�z(�
��<���
��p��	��������̣�����Ț
��Ȣr��
.��
	�tl�
�t�
�s�
����o4#��
�g�St��
��,�e�
	��a��b��e��f�g�i0�kd�mx�p�r�s\�t|�v���,�
����������T�
\�
����lD�y��
��z��
��$�ḷrL�
�ܿ4�
��ԣ���
����rܣø�
���
.��
�gH�
.�
�vT�
��
��(�l(VrD�v$�
���
���
L�bx�k��
��T�a��o���H�
�p�
y��g��
��r��
���
�����&.�
��rx����
	��d�k��
��Ȥ�h�
���iؤ���
H(�
���
��mH�s��
���aX�ed�i��k��p�r�s�t4�z���x��t�
���
�d�
P�l��
\�.x�p��s��
�h�
�(�
	��r��
�����t�
��T��
���
����e̥i��
�

��ԥ��
��ܥ��
���
�X�
��bl�
�����
��,�r���
���
)D�fL�pxR
	�
B���
��T�r������h��8��p��\		��b̦g��kT�n`�r��sȨt�v8�	����l#@��Ħa�e0�g@�nL�o��u��Ð���������T��|��xt(�l(�v��X���T���8�a�#��@!1L!��\�����d���	p�l td�����$#�#����a�eȧÜ��������� ��D�ܧl�r����	��k�rX��T.4���0�0	(�z1��0���0��<��)��H�tT4��|�a��o|��5�<5��t��������{����sx;	��k�@��?����z�C8�A����l�t�Ey�E��بt�E	�rPE����E�����8F��(YeDYi,P	|�a��b��c�d�e@�f��g�i�k<�nh�p��r��sȮt��ØP�� �����Q.�Pt�nS.�R��g�R��������S�S����l,T��$�lW�W��̩aXU��ԩr�W.dW�l�r�X.dZ.�Y�l,�t [#,[��$�e�[�[��8�lP�rD\��(��(��X���(��`��B	l�k\\��x�el\	��p|\������\����r���],�4�������^��Ȫ�^Ԫr��t@_#L_���aLa�`���l�vp-pc��c.$�a\�ec��,�y$d�0d��H�ed	P�l�f��@�lx�r@g�hj�h	��bثl�h�����H�����`���g���e��h��iЬo<�ux�z���(���2s4
�|i�b�g�l@�mj�j���yPj�\j���e4�ňj��j��,���j�0k	Ī.d�hl�kt�l|�n�)Wpk%Ms�,%\lj(l��f��l��n��p��s4���lj�l��lt�m��mȬk�r�s�mj�����m	�d�z(I_dnjln	�r�n����pojxo4�nP�rX�t�o|�o��o�<[gp�l�o	 p<����evdr	��rq�����\p��	�k�m�n�o4�pd�t��v��z��Øs��s��Э��s��ح��
��s��s����i�
.$�o�s�dx	
��t��t��,�e�5iP���t�u��H��DvPTv��\�a��r��üv��v��x���v��9
@w�
��$Yl~
	��lD~
������x��خr����x�@�����a�e�Ø~���l�r@�s$��4�����L����3a�e�3i�Bo<����P����8�z�h����L�sH�T�pov����`�a�e�oP�u���h���4�g��	��g��s̯t(������԰���P�TLrl`�����a��e�äz_��	�r����������e�`���dL�n`�sĉ�t�P(�.l�bt�e|�h��k��r���T#��0�z�"��T�s�C
6���,�6�$
��^
6�d
�������H04T0�����0.���d/����z8(	Ȱs�b��b���a<�eh�g ��a�gx�n��s��t�b��b����\����c� c4�nT��;
��H�i�;
	P�n4<
��j�j��p�a��ym��l.��i8p)D�f�n����z�p��p����r�rI�rȱj�r��бa4r	ܱb�t��
�y����a4�i��8�
ez����@���y��y,�s ze����H�i`�s\���L�ep�i��#���4�g��	x�g������Բ�h����k(�l��pH�rT�t��z��������e�Ü�	IJg<��0���l8�����
�
����4
����(
#�
�� �i
�
��4���
��<���%
����ax�rh���'
��������|0
.t;
�Jk;
.��a�ed�ll�nt�p|�t��v�ÀX�$X��s�@
����a�?
�ȳl$�r0@
��Գ��C
��rs#�J
����aH�eT�o\�t(�ÄJ
t�J
�� ��D_K
�4�v�J
<�lPw�XK
\W
�DX
	T^
��`
6f
��j
�j
����a�f
��g�l�nPf
����a`�r<���.��ap�ȴd�l<s
��Դa�o
���p��\�tu
��d���t
�����
���nL�
	$�kD~
��0�����p�
�����t������
��$�it�oL���
�sU��
	|�f�
_��
	��l�c0�eP�g��i��j̶l��n,�r��s@�t̷����صm�
���e�h�ü���4���� ����P.(�l���<�l4��D�a��rx��(_�	d�r���l���!_�����t�_h����jj��k��t0����a�e0�i��t��2t������������H	pP����\��d���TD�mL�nT�s4�t�t�������h�t��l��h(`'����i`Lo��tt)_`�
�*��a�d�f��l�p�������
�,�
��ط�$�
���
�X�
�y��+��+�i@�kH�lP�t,���a��ut��H,��,����X���,	X�bd^h��t�,��`��-��.�k0�4/��lظn�r�sp/����a�eL�i��o��z��l��$0�T0�t0t1��0	�l�0�����@���d��1�gP�
���e�(2��(�t�1	4�rP2t�n\�vT�t\��h���d��|��t9
t�9Q�9���d��k0�m�6.��o�1sȨ�
�X
��	��a�b�h�k�n�o �r(�t��d
�G
L
�X
��������X
����Y
�
�L
�@��LZ
Q>�;��8�iX�ox�up�ü>`�?	�X.|�r�<��`��d�
��C	��c��d �jD�r��sܻt�E#�E����e��hкsFPPG��F.Ⱥe�o�zpH��H4hJI���rOe�N����ܿ��N����T��dZ,�c\�l�Z��4�ad�e��o����Z��[\��[	l�np[��t������\��n ]��a��d��̻�\a.����]����zDb_�b�b��Իa"j�l"n$�o�r�Øc�$c�����
8pe�4e�n�h	
t�d��gļhD�kd�lĽn��r(Mt<Mvнz�jb(j��`���i��h��TlP���,l����y<m�� �nm8��r�l������m#�m����a��e�o����m�n����n�0n�l�n#�n��n���������	$�t���,�� o��8��@p�`p��P���o����d��eX�ä���i��p|�p�ttt	��s�t�����0x�<x����eXw����n�~����e����	�p�r$�s8�tP�zX�8�����r������l���\����zD�0���0�r���� �����D��P��0�(��`�j���h�a��eо�d�t�r����	��ad�e<�f �i��o$�p�u�ø��x��́eЛ��Ⱦ�d	��	ܾn�.�al�Ô����yL�	�n@�t�������������������J̪��8�a(�e�����P�e��X�c��lȿn�rH�s��t�����gH�����eX`k��	t������������d�����t����Կl����ܿa(�i4�s�ìw�(�	�r8��������� �pH�������p���X�p<�����D,���`������h��(�t�tķ����e�8$�����mh�	��l��p�r0�_8��p����d�i�t�z����(��@�d�t|�.��a<��(����s���cT�g��t��_����4����`
z����H�a��
HL		`�z���h��l���t��h����r|�.�!�$�	��ld�����������n��t�	�t�f_�	�g�z|�,TM_��	�z�_���iT�$�y���0�a�����H�s$���P�a��j��k@�v���88H��x�b��n��t��v@�������8�d��d�����������d8\ �h �����@ ����� !#� ����e�k�n$�t�!.�!.�.@��=��,���<��4���B.t�a�e,�i�No��s$Ou���`��HC���j��k���P�T$O�����G�pH��r4��U���r��s�U��������N��N�O�,O�4O��_���s��zh`�
�c���� �sd�#|����a���8�l�����(za��b��d��j��n��r8���@��<O���e���p��l���x��XUy`yPce\c�������������y$�\Ի.��a$�eX�i��o��s��u�ø����������P�������������������\4�gD�s,���������<�z����\l�rt�s|�v��y�����D������kH�������������������|�\����j��4���I���ĉ.d�a��bP�c��d��e�fX�g��h��i�j�	k�lD?mtNn,bojp�sr��s�t8�u��v$�x��z�Üi�̉��b�dX�f��g��i$�j0�k,�l@�m��nP�p��rH�s��t��u��v�z0�ô��p�	��l�������H�����o�����ؗt�_������h���Ȋa�r(�u��|��P�4QdL���$��8�_���<������������t�e|�iXoD�Ô��кt��t��_�_D���������8�_\�_@���d�i��� �_�������������d��l�m�p�s��P�������t�#�����a���
.h�a��b��e��h��i��l��o��r��������`�sx�tt��$�_H������،���������������d����t���L�tt�_��t��t�_��_�����_�_\>y4	�nd��������e��u ��$��.h�a��e��i��o��ux������#t'_ '��p��������d)�)��l��r�*�+_�,�X,��n$/#(1t�1_ph	_�0������9t02����e܌s<�y�Ø;_$5����,��G_K�,K.4�a�bt�Z��H�ih�o��rx�ìdt f_$]��p��Ph`�g����o`k��
.��a��b�e�h�j�n(�r0�u���k�n��s(�t n�xo��T�� ��8��\���vt�:��}����́_h��`�t�_��t���@�od�t��zt�È��Вe��_<���l���_d������h�y�����m �.��e�����p������t���Pa0�c8�ex�f��g��h��i��m��n�o8�rh�sp�u��z�À��Ĥ��X�L������x��,��l�tD�g`�t���\�ll�n�2d�Xl���d�e����������]d��n��p��r�	����g��(��(`�
���o�W	��z�Q	��s4X	)P�
�t�t�r����_��_p�� �$�kH���,�a\��H�
8�	H�n����P��x��8�t�(����������.؍t��x$BT�t������i��y$�@���b��c,�g@�h`�k��l��m��n��p�r4�s\�t�������L���8f��h���0��@���Ďa�i�o�u$�yL�ø����� �x��X�e��oT�
_H��p�k�x�s�T��d��k�����a�e�f(�id�o���p	��	t�	al��r��������x�����0
,DC�l��P�T����e�d@�nH�rP�s�t���,m��\�p$_�	p�r�'�'��c��l��tT'����a��e�y���P(� )t�)C rL�t�)�����`-09��8	��lbn0�p8�r@�sH�v�8.��aP�e��i��op�Ð9��9��9�d
��:G�;y�;8X�n��r:��`�����<���O.�
� =	��f��n��s�
B�
����g�=��M
�
����u >	��k`>��C�8C��d �m̩s��zpC����a�u(�ìE�\H����t_���a4�uL�z��XoP�uTv��T�a��eؐut���w����������8����t���X���d�g�j��l(�n@�t\�u�\z������a��e��g��i��kd�l��o��r��sx�tl�à�p������aț�ԛ���a0���$�kH�����t8�y��~t����l\�����g�	L�gԒp��rH���X����������l�p������a�����������h��j��lT�nx�s�x����`������e����tи��������eH�h�������f(�mh �P��� �ef��c4�lX*��<�e�����c�n����`�e������h�t��z�������k�.��e,�	دg��p��rܸ	����4d��tt�z�����0���0,r��t����d�n0�r$�sD�z�`4����a��f��gH�����$!_t��<�z�.�@�P�t\���X�aė��	@���t�t��|�k��l��rX�Pd�����aH�	x�����v��	p�b��s\������s������u,���|�t��	�g�l<�r̢z�.,�������$����h�j��l�n��z����
8�a��b��cT�e(�h��l��s��u��z���,�����k�t\		��gd�l��r,�����D	������a�h�i<�s����_�	��rH�����L���0��	���t���$��(P�o0���X	D�gp�l��n��t�v�����o��v�]�0����o�
����t�� ����b��o��s��t�����$�UX�UطB�<z������H�T�����`�����49���r�.�aL�eX�i��ux��g9��D�n��E.h�nPp�	���p����V#�-����eğu(4���d��l��n�p<�r�3.��a��o��st�zl���4��4����a�5��5	(g �k�5������5����x�������(���6��0���7.��b��o���D7�H�r��th7��\��������<��\��$8�08�����,J8��p�8��=�=���l��r�>���O.�t,C�|B���l�
B`E���l�6
H�6
��s7
�����6
�ü6
��$�z�H�0�s�88
��H�b�H�P�kl�tlI��I.8�e�K\�dN.�e��hPt�O��b��d�n�zLO����a`�eԫg��i��oH�p0�r��v�y �z8����żP�����tW~DW���y�_��`,H`	�c�gԒp|`��$�����X�����l���k���y@iT�g��l��m��r\�sx�t oT�n����e�n��m|n����e�)o�s����l�v�,v��kHv����a�e8�o���tw_�v	��l�v�����,��H���w�$w�l<�_$x	$�k�x�Py,@y	@�l$���y��T�o�z��|������z����eh��Ȩ����l����T.�z	��r~,T|	��j��k8�lT�n0�pl�rx�s��v�~��~�����P~��Ȫi�o��À���������|�.D�o��T���(�y@
�0�����L�nd�y8��ԃ���dl���Da��ohs��z|N#`���WT�	��n��rt������D�����Ô�W<��x���c�d �g��l,�n@�r��������e�`�z ����a4�����t�����8�a�o4�_��	P�rt�v��z����l�n<�_�	������d���l��r��sd���t�vD�
t��	�s�n������s �	��r�sd����������7
t������������`���mp���$�aL�o`����D�g�����X��0�	��g�tP�_�	|�z��������a����C��r��t�������������_@�����h".��	��r��_��	��z\���Ȗi����`��L�.x�v��T��^?@��4�i`�jh�rp�v`���<���������z��8��`�b��c��d��g��h��k��l`�m|�n��p��r��s�t0�vD�zX�����e�y�����D�s��������	���e���.0�a��eL�g��o8��дň%y�%	 �bD�dH��(���%�8*�@C#t�nD?��T�e �u��8E9tN��.��b��j��tyw��y0P��P�P[��^Pj��<�ex�od�ðs����e��o������#�v��ص�����X�z$�������e �h(�o���ز#����ܶe��i�o������
|�d��g��h��j$�k��lD�m��nl�p��r��s0�t��v������e���������̷�������\����e���p�������������H�e��ip������0�k�������������Tz�	�s��������$���#��<�e-���t�'��P�����&��\�ÌO�A��x�s��		��s��	�����<-����è0�� �i�����ŤQP(G����zDg��\�e|�il�ü�������ihw��xw���a`�e��f��l`�o��p��rp���0��p�����8�����H�t��T�k��nDe�R��"��
8x�à
������^.���h�����y$�t,���n�����i��oL����P������a�e(�u��D���k�v���iȠ�x@�#\s	_���0�k��8�l����D�ax�iL�P�n��r��$�p�k��
T��l�����a�����mWt���� �l� ������t��Ь�����p���������x����eH$l��r��� ���b��cMd��g��i�j��k�lH�mP�nx�p��r��s(�tL�u\�v��z�����a��b��dd�e�f0�g��i`�kt�o��p�r,�s��td�u�vH�yd�z���x�Ő�����s��`�Ph����.������<��,�����������c|[kX�n8�p������a@�hL�jT�m�s�Vt@��t\�_��4���p�g�3n��th�y�~��`����p�a��o��r�������������#�\�_�r	������ct�i��o�9t��P��_(�����s�z����.��a�o����0���$[�������k<�rD�yH����|����P����T�a�����h�lt�n���p�oH��P�����a��ܿ	��b(�c4�f��g<�hD�iT�j|�m��nԒp��r��s�t$�vD�z$��������4��l�������H��t��d������������ �s��_�	h��$��,���L�aL�up��@��H���h���.t�������������������4�b��g`Yn��o�	t��u���N���# �#�����#������a`�
D�����.�u����������ax������0�������iT�u8�������j�@�X�b��c��d��g�h0�kD�l��m��n��p�r��s��t�v�x���l��������.��������s|��(�����z��`������ed��t�����e �Q��	�s���������$�ä��H���<�a��e��f<�gD�hX�ip�j@ m��n��oD�t|�v��z(����Ÿ�H���d��g��j��k��m�st���]���������t�U������b��e0�n8�r�th }1Q��L�U�	�g4�r������(U8*�����a�+���P�t���$�����d��Ī�����|�e�1	
���������	��b��d��f�i�m(�n���8��������L�����eX�����b�2t83t�3
��t`5
��.�e�����y����
�h��4���\�e4��tX�T�tp9�����<
�4���p��D��\���l�����e|��������d��yP�$
X�.��e��������e��r�q	���������\���������0�d8�e|�k��l��ð��@��T��.L�dT�kPwy(|��Bp�nh-����h�yL�	�S�ص��������k��tT�����e��z4�������e��L�.��k��Ix���l������a�P�����e��	��gD�mt�n��p��r��s�t�������<�b�o���P�e��k��X�k,���h�e��y�+
< ���	��l�����e<������������s���X/8����
D..�b�e\�gd�ip�kx�n��r��s��t��vP����3y;t��s0�t��z,�(
<�e�;�<���s����D���=yH��.x�y]y4�0
��	��.��������Ay�����h����� �e8������|�yt�bl��s�����zTY��p�����e<���t�l\�@��mX���$�iX�y`��0�	@�lh�n�PH�a���h6�:���8p�s����x�������E.��b��d�e�g,�j4�k0�lD�mT�nt�o��r��s��t��v�zT�_\�����rD�	������d�ep�P��4
.D�.��a�ü�0���ocr\���Ob�"m�����f��gl�o,�s���<9
d�t��|�a��o��`��	�����s��t��z	`��d�a���x$
�������(������a�e������4����Zg�m��z���0	`Bg��nP�rX�vD�z�,_h����l$�rdv��b��cid��i��k��l��m�n��p$�r\�sd�t�,
�dA
���@PP����a��d��v��yD/_0	_
>
\��ij
#(�����t�����gD�o��r��vTh�<�mT�s��
�(�h�
t<	��.��b��d��j��r��v����
#�����o��
�������a���4�D������	<_	��b�k�n4�rP�tX�vؘzP	,�@�gH�m8����D��(��l;D
�;��	�	 	�k��r��s��zd��`�����|i�����e��i���Tp|��������wt@w.��i�����z��| ����l�rP�s���!��t�!���a ��0#��t���%����k lL�m��pT�t��zH&$'��* *\�b��d��m��n��r�s �t�]z��(+��+p{.�i�k�sP�tP+����a�u���+uH,��,��.��+��-	X-	��j �n(�r0�s8�t�-��-	.	.	$0_�/	@�d��g��l��s��t�v�z�l�@0.l�e00��t�y�0Xp0����e��i�0��_<1	��zdoT�����iԨ�s����o 2	�r42�����1�����4��3	
�dl�gt�j��k��l��r�s�t�u�z03.�a��ed�i��o�u����P4�(5��5	<5��|�t�5P�5����a4�/T8������7����Ô8��8�<�<�8\9B�88�l0�rH�t9����4������D��	1�9�����9��@�f$�ì9'$:yt;'�;P�g�;��X�e��fl:lx`o<�z��ŀ:	d�l�n�s�z�3U@	��j��vX�����7Uh
��<���i�l�s��	�`Z��8<>.�t�=����zt>�|>���ml?��|.s�>8(�kP�n��r�?M
�?��H�i�C�0B	\�px�s��z�C�PD�E�D	��k��l��r��s�t,E��E.�)�h�
XF��E8�rpF8��lx>s�z FB�G��G8��r�G����IC,�r4�t\IX6�
��J8<�t�����P�s�K��X�i�O#<O��p�e\Mx�dM����a�e\�ox�s�Üb�Hb��ldb����a�]	��z�]�������0f88f����Df����f����l<�y�c�ld�r�f�pf	(�s�f.0�e�g	<l|�t�g��H���g����e�i�oX���g�hT��l��m��n��s�|t`}th�@h��h��h��n8����lDj�	Tj���e�i	�j$�nX�z�j��j����8���j����4k�@k	0�vxo��o��D��n��h�oL��Ln>������p�z��8�bl�d��e�g�h�l�m\�n(�o�pD�r|�t��v�z�Ø��t��b�h�j�n�r �t(�v������@��0��P��D��ܕ�$���k	�4���0�n���Dk��D��`���ix oL��\���X�e�o��p��ð��l����P�	��n���������������rd����n��yt����p���0�e(����lT�.0�np����a�!i8�k@�sH�z���	��N	��`	��j	���>��d ���P�a��e�i��o�t����Ő���e�������������������g��yt�	��r�t0�y�������a���t�����������	�8����rt�#(� �n��<�m�&t����4�a��i\�op������d��_����h�����T�a��b��j��n��r����
��
��#�����a��o�����h����l�t��mo���P�������i8#`!��gDjXr��tdv|z&����P��%��4�p(�|7��,�at�8��s@8��h�����V
�c���e�8���b�i�o�f���l�
�XZ�c�n
^
li���k<	�X.4aTbddpfxg�lh�i�j��k�mn@rHt�WvD�Y$%u<��<����r���y,<��\n�s����D..�k�n�t �
y��
y��
y`<ux�
���
���a�e�u��8�
H�
������
���
�u��u��D..a8y,��
���
 v��$��
w�x<bLD�����������<C����e��i�oPøI���e*�D��H�XQ	�bdgk0l8n@rHuPz�P���a��bc d8e$ft�g0itk�m�o��p�s�t|�xŰQ�dR��R�(k�S>hT��T�DU�U�<V��`	HV	Xb�g�l�r�t|V��`��d���(�D	�h	��V,\W	4Y��4��Y�����W���t([���Ie�v�\8H]mT]��p`].�a$]��s�]�]��rP�.hs^,c��f�g�k�l�n�s�x09Q��.`e�^��^td�^��|e���^��^�����_��_���c�`��	��a�ce��fTh�o�s�t�u�G_������e`a�d m�Qb��b8e0�nH�a}bD$b��@�X*��a<�e�c��c��d��c.l�c��xz d�(d	�r4d����d����ܴ`|d���
g�zb_t��P����zPe���sz�eP�f	x(j�k�l`�p@�tlj��d�r�k��d��mTn��p��z`l�ll��La��tm	l2g��v�n���l�v�ps�s�k�r���o|u<t�l�p�S�T	�w	�c�nst v�x	�y��y����Xy���\�	8	@z	��b<lXvxz_\���zD.�z��Le@{_�z	dz{��l�\|���pxt ~�4~�d�u`~���ae�i�o	r��p��\�|	�lt�����d�����0	���p���dk �lDsPv\�
n�
��0e8�
��8t́��.x��	Xrx�
�L�
��p�|��(��n�z�������sd�
,D�	�r؃��	�d�l�n�sh�
,�����@��L���	a$	��t���	���	� r@�	$�	<	jX	r`	t�Z	���	�g�	r�	t�jvX�zM_d�,�_(����	l���	bH
cl
d�
g�
jk$lPn�r�sXCu�z@����	ad
b�
c�
dePfdg�i�khl�n�o<�p�rs�tluxv�����4
�����<
�,�<���T
�x
����\
�X���P؏���
yt�P,����
a�
tH�	0d.�
i�
k�
n�
rtT����
�`����
��|��
��
�d�
������H�r����aD����*j8o@tp`�	�_h���Hgdi�y����U����l���.t�X���h".�a�r��,����c�rp��8<��s������x�p\����i����̃ơ	t�gLl|r$
s0
t�������\��������h������8od���@l\m|8���d�i�k�s���da�d�^f��h�k�m,gn�o̦rT�u��v��d��X�1�b1���������
������VH���n�]�P�1D���
�ԣ��
�����aT
d�Ih؆j��m\
o��t@�8�����\�lt
r��v�	|
m,���
����
i$�l�
�����
f�����
�ħ�
����8�r�	�
���
�����
�H�	�
b�d<g�k�l�m�psPv0�_����4gTy����.Le��L
���`�Ժ��h�Ī	tb�sԪ�����������Q ��8���
�a�ehdk<l��m��o��rht\������gm8�g
���eL*p���ah�8t���(�����0����	Hn���P�x���T�a��T�,`�	xrl�����?`�����a�e��D�@C�l�F	xslA����,�P8����i��<����r0����t8z�E
�����e��� l��.,eHo\�t���`i����t�6��hh���p���	
�g�i�kl<pTr�s�t0v|ø�_�����k�����k̻���e�t�t������e4�n$�ؼ������������`���0ø=ԭn����Hi�t�ä�r����hb�	pk���|�������D��8�C�l�����,�.��.�h�n|����zDX
�<����l�����e<kl��t�
h��#.k@t(���eHi�����n������rh�H���\n<�r�����A����g�il�m$n4sPzt�\�`�zh����a�e(�ä�`��jl�������a��
��	�r������L��\����$�����d�|����k �mHp���t��0�o�	��j�r�zL�1����p�����x�p�	H�T�����`����À��r�����a�eo,r8Š�������e��fD��l�������z��sX��h���$i0�����������os`w	DsH���P�8����i\�4��$�xm�9(����������ň������<��b�d�l(p4r�t������4v����al��v̬�,���������,�#@���aT�P���$d`otr��vt�e����L��� 1m�
p����la\���l�t(�
,l�	�h�r��������a���1<�����`�.�@�	�k l��mLnlr8�����d��p��������8ň�1����0�.���Dk\t�0�#`���di|v�����m����a�i��X1�d1����t1���ø�	�tL�����-������s���	m����l&m��pptLzP=
8�8l`.@ahe|i�K
H,�`s����tpd���u�z����a�e$i@o`u���d��X`k@�l�s,�
,4���zx��TKd`	�r�����{��l`�m,�o�
t�8k<�
j
��Ls�	Ts�
\�d�r�t�z�
`�
���a�o����������h������	�j�r�t�Z��T.h.x�ov�f8<xXw��n	n0tXz�.DŰ}��}��<��8L��Pl�4��d����l��P����aH$lTo�b`chdxg�j�k�l�m�n4p@r�s�u�v�z����a�
bDdte*f8*gX*hl*i�,k8-l0mT0nt0o\7p�7s�7t�9u ;v�<yH�82��UX���@tؗHr������	���pg�yD~p�4���d�s���a�c	D"��#��%��%���adei�3n$y\'#('��ip'P�'P�(P�)P�)��,a�,.Pa`��,�Lo�-��X�p�$���-\�-.xa�i�u��<-���z�\.��-������\�3��P�3���a�e��P84��̃o<5��p��5���À4	blg�j�l�m�np r�s�t(z�4����%� ,�p1��1��9��9�<7��h".�a��g�i�n�o7X0���989���d
l�o�;��<.���=1=����\>�e�Ø>�>�����$?#d4��@�HA��
h".��b|c�eL
f��hT
k�n�o�!r�t�ud�4H1DB��\�������$�8�A�lL1�A8�R��U��c�B�8B1pB.HDTTD���a�efk��lmr�Do
�z8E8�|80E8�FpF�� i<o�vt�FJ�N��`�.�
r(��4X��Te�W��\r�Vhb�d�f�g�k�lL m�!n"pl"r�"s�$t`%vt%xhðX��D..�e���`��l��	Y���fhY��\e$y4z 
.(]��e�\	n�\.e��d�	��9,�#<nX��DaPr�W��\��!��I��_�$^��|l�o�r�t8�t�_�s����A�������_v`���r�
_H`���b e md�r4 Ŵ`��b�` mb_(7�pb	  z�b��( �?�sd�c��@ a�bh ep!z!øc�.� i� k� l� m� s(%	?Dv�d�xc��� lE��(�.� m�Q	6e��(�.� n� z��64e��ey�e��� t\e	� l$!rte��� �8!��ey�e��!d o6�M	0!kP!n��	U�M��H!k�k	P�j	��\!�(N��d!È!Ŭk	P�k	���!�|�Ig���!d�!e�!��dI�g�!r0h�h	�!l`g���!��iT�j��i�!l"p�i���!a8"e`"�i�lj�tj�� "eD"l\j("l�l��p,�p	L"rq��T"��k���d�"ed�j�
n�"�Pw�k�"dL%iXl
`l	�"z0l���"��lg
�l���"em�"g�l���"e�#i�#n@$sT$tl$zL#�$�H�y�m��
#bX#ex#h�#i�>k�#n�#r�#s�#th#��m	#s�l��@#����K�H��`#��#���y�L��
y�
y��u
�yQ<���`�.��#k��	���		�#n����#������#�
�̗	�#b$sܗ���#�D
�0���
	 $l�
��($�\n��4$�|n��n��L$a�����kTo.`$e�Q$p	x$t0p���$�<p���$�Hp�$k�$t�o���$e4%t%�0�
��q���$i�n�$Š�Bܩ
���$��q	�zk��l�q���$��
���
	%k���%��t��(%Ä\	|
,�	@%kd���H%��u��T%�D�P�v��l%i�yl����%b`w	�%b�%d�%gP&hl&k'l�'n(r)s�)t*v@x���|u��1Xy�%s&t y���%e&j(&t4&v(�1�8P�
vXz&khz��&e�8�{�T{<&t8{��D&e�{T�&k@n4|��\&a�&e'u�&�p|���t8}���&�'�'���`�����&e�|�&l�&v�&z}�@��L����&e�&i�&tL��0���}��}��}#h~��h".P'dX'e� h�'i�'j4�n�'s�'tT�vx'ä
8�~�dh't01�~�
�~��p'��'��L�8�	8
p�1`���e�'hL�'k���'e��W����'d(tm,(n8(zd����'eh(i��p�(r�(t�zX(Ô��:.$(d ���<��D..@�	@(t����L(��(�X�.�d��t(�����|(�x�%���(l����(e�(hxÌ�t���D����
�����(��L���(��	�(t����(�������o$)z�(�ĉ�
��.).L)el)n�)v�)Ø)�HTo�S�D)l\)m(Uol�	�DX
��d)e�)��	�
T���|)�0Y�
<Y���)�8Z�
�����)�f
%P��0��)l�)s*t����)e<k��t����
������)e(���T.�����l(*r���̊4���0*l�Y�P�D*p\���L*a��#��d*b�*d�*g<l�*m<+nP+o�px+r�+s�+t,v,x,zd+���܏�*g����*e�oh���Ԑ���*a�(�`4����*a +p+�T�Bh���+������n����(+o����0+t�P�H+nd�T����\+�8������p+a�+o\�`����i,m��sPtԙ����+n����+i�+o�+u, ���+lh�T������+�t����+ð����0�ox�	`BgH,r�v81x���4,����<,��t$���T,�,���\,�L�h,r\���t,a�,e�,i�l�r�,vPck�����,�����,��,�L����,dH��,zhcktc���,�L�jl�����t��������-ø�-d`-ll-m�-tPu���-a$.e/i@/o�-ü/Ŵ"	���X-k8����`b|-p�0�D�l�	�b�-l�-t�����-��/��/�ܤ����-l,�
�
�x���-o.ôw	�-s�D���-�(���.m�-��e�
�x��.��|�T��nfP.g|.l�.n8\e�Y��<.�����D.�������\.��,��d.����p.k�.mb�Pg�`g���.�4����.e�.f�.s/z�.��.��S��g�.s`UvT��`����.�l��
bvľ���/d /n$�����f4/g�tH���h".x��rX/s,�
�����P/z������d/����l/�̬	x/rt��L�	�/n�s��_�	�/r̭���/�t�����/h����/���	0l$0t�/�H����/������/�Աv�>	H>	��0eĭ	vę	00p@���80�h0����D0à�	vt�		`0k���b�0f�|j�0k�0l�0m1n�p,1rD1s�x�	�6
���0f���ȶ���0�l����0m�0��	ܷ�4�	l����0l1o`����	���1t<�
� ���$1o<1r(�
	d�P1z,�
���
#��
X1g�1n��	`1i�1r��
#�X����1i,�	Db�1m�n��r�sؘz�����%���1pH2r�����1��5��6�<�	d2b�2d3e83f�3h�3i�3j 4k�4m�4n�5r`6s�6t7v(7z�1Ô����1��:�PS%,�eH���P2�l���x2i�2oX2Ü�����0����2i�����2e�2j�2o�2Ð�e����2�3�)�
t��2.�����2epV���
����2.��	�2t$��r��Ph��� 3�H3�@���(3�X3�8�e\�eD���P3�<CU�s	d3v�]��l3�����x3Ü3ż�P�����3�4����3b�3k�3m�3v�^��^��_�XQ�`���3a8��
�	�3.D4rX4s����3��4����p4l�4t�4u4À4Ő��
����<4t������P4eh4t�P
��8�e|���x4����
�e�Z	e�	���4��b���4��4��be�b���4�Tl	�x����4a�4n 5y5�t�	e\c��5�\����.
5.P5b`5el5ht5i|5k�5n�5r�5t�5��6H
o�	X5r��6�
e��6��6�������5��5���6��6���e�`���h".�5e6i$6l,6o<6�j6H��5g6h6j@�6x�6m���6zlm8�m���8����46���e����H6��6�����x6o�6p�6zP6��s�Hte����w"��)�6e�6i�6ôM
�
����6��6��w"4[
�
��
e\����6�h����6�h����%���6s 7z����7i,�%������.<7e�mT|�P7np+,p��H7e�����l�7r������l7�����t7����k l(m�7zp�6��.�7tt���7j�����7a08e@9r8���.(8a���7d�����7ah�	�7r����8�9�p9����D�D..\8rh8sp8t��u��H8i����P8e8�
y����
\�.�8e�8h�8i�8j�8n�8s�8t9v�8À�
!��e�����8��8����0�
���
����$�����X����8k��
e<�l��4�	9b(9l09tT�������P����89aX9�������P9�0� o���	h9k�t��|9d�9g�9n�9r|t�z����.�9d�_����	,�	�9j0�s�9t����/	��	
�9c$:d,:g�:k�:l�	r�:s��t�v�z��_�_`:y<m���~. �nm88:r�l��H:��PT:�<����	l:n���t:� o���:ì��x��:t`����:e�����:t��_��8�����:t��	�:r�:z�_D��P����:aH;�`�;dT;t����;a<ex<i�<o�;��<Ÿ�Д��@;���t��\;i�;s$���d;a�;Ì�	t;r�;s�;t�����;�0<��<��<��<�L��h	WT�	�;r`����;���_�����;z���d����c���;n��8����<�����<ô�	$<gL<lX<r��p����D<e���T.h<ikn����p<k��	�����<t���<l��e�F��	�<dd��t����<�����G��.�<ah=e$>ih>kt>o�>u=ð>���L���=��=�|>��>��>�ĉ5<���0=.��	8=n���D=�����P=�\�	\=k�=l�=r�=s<��$����.�tP����=z,�18����=�4���=�d�8�=j�=k>l>s�1����=�����=�����y��x��>o4�	>k<>r�������4>aL!�X!	H>jh!��P>�`���\>à����X�y8�8�>m�>n�>s8-y\�y|�����0X�	�>l�9.�>�@�8�>l,rX����>��	t@	�>b�?d�?g�?j�?k(@lp@n�@p�@rAs$At�&u0Av8Az���?a`Bb�(d@CepHf8,g�Hi�.kxJl�Jo4Lp�Ls1t�Lu(NzlA��K�8		�?t	���?a��o�?t�$ô�_�	`	���?i�	���
a�vk�t [��	P�	���?a @u@� �� 	��@��	��	���a��lT@m`@v@�L��@@�X��H@��	_�	�	��h@a�@y�@�(	�0	���@��	��	.�@r�@��	��	���@�		ػU 		�@n,	���@�@	���@Ä����P�@o����Az�		��As�
	��DCl��	���o�`_�		DAb�AlԒp�ArBsBt�	��LA��F�(J�8K�XK��M��M�p	.�Aa�Ad�Ai`	p08p�	.�Aa,gn�Ao�Aurv�	n�	n�	nd	8t	���A�$	���A�	�	��Ba8Be�'lm�'tX�u�	M�vH���@Bg�	HBr�	��TBa�Be�BlCr,{��	xBdX	���Be�Bf�Bs�Bż	�Brl�v̿eܿ���B�,�?g����Bn	���BeC�`w�ė���B��	�d	��Ce(Ci0Co����	���t� 	8Cf�CgDkDlEm8EnXEp�Er�Es4Fu�*vDFzl9#	xCr "	���Ce�Ci�Cy�Cz�#	j`>��$		�Ct�$	.�Ce�C�4�*	�$	���C�d�_0'	Q�&		�Cs�%	���C�`%	��Dì)	��	�aDDbTDehDk�Dl@ m�DoEt�D�4*	_�*	�<*	LDt�,�+	�`De8+	8D+	��tD�X+	��|D�r�d2�,		�Db�De�Dn�Ds�Dv,	���D�3; 5f�4���Dy`6�7��,	�|,	���Dy-	���b�e Eu0-	��-	�!rLEsT-	��(Ee\n�S�DEs,/	�h-G����`Ey80		hEnD0	��tE�\/	���Eè*�L1	#X1	���Ea�Ee Fz�E��1	D�1	���E��1	���EÌ1	�Et02	Q2		�Es2	���E�3	Q3	�Fm�t�2	.Fe��ktzm4	$,4	�D4	��<FapFoxFsdFÀ4	e�4	��\F��5	�d6	7	_�6		
�Fg�Fh�Fj�Fk GlDGn`�plGr�Gs�Gt�7	�#.�Fn�}sh7	���Fe�Fs���8	y8	,l8	.GeGr�8	n�8l9	h't�9	��Ge@:	��eT:	,Gk:	��8Ge(<	�+l8<	��PGe�G�X;	��\Gt��
P�B��xGe�Gn�G�X<		�Gkh<	���G�8�
[H����G���r�=	� =	.�Gl�<	���Gz4>	H>	���Ga,HeHÈ>	��>		Hr�>	��H�^��>	$Hk<Hl�>	�PH.XHh`Hsĉg�>		t�
	\A	�@	��hHl�Ho�Hr�A	.�Ha�A	�Hd�A	���Ha�A	�Hg|A	��A	���B�����H��D	���H�`"��B		�HdIg8IkDIm\In|Ip�Ir�IsJzE	.�1a(Iet��@���E	_�E	��0IsF	.TIa�,�D��tF	���"f�ZspIt8����"e@��d��H	���Ia�-o #t�I�I	BI	���I��I	��0Bk8Bmp�t�Iz�J	.��a�o�IôJ	_�J	���I��L	��L	���I�M	���Zg JoJ�8`M		�.g�j@Jr�#vhM	���8P	��HJm�JvPP		PJk�O	��`J�pO	��lJ�T~�S	.4$l�Jo�Jt�Q	�Jk�Jl�JpKrKt�S	��S	GT	���/a�/d�Jl�JtpT		@�_�U	�p�
_ V	���Jk�/s��vpX	j<Z	#LZ	�� KaLK��X		(Kr`Z	1xZ	��DK��Z		tb�Kk�Kl�Kn�Kr��st%vؘzlz.�L[	.�Kd�~���[	_�[	,�\	��h".�Kk�%n�!rLtL�8\		�Kr`\	���K�N�Pm8�m1�\	���K�do8�]	v�]		Lt�]	��L�d]	��LLc\Lr�%s(L�^	�_	P�_	��TLe�LoxL��_	��_	��pL�l7�,`	��`	���0k&m�0p�Ltta	��b	��.�Ld�Lg�Lj�LkMlMm(Mn4MrDMsdMt`���b	�c	��<c	U,c	��MzPc	<d	�$d	�� MoHd	��e	��d	��<MzPf	f	PMl0f	��XMa�Mc�M��f	��f	��xM��M�g	�Hg	�pg		�1j�Mr�Mt�g		�g	_h		$:d�g�Ml�Mn�	rNt�vX�z�o�����Dh	��nPh	_�i	_ph		Nz�k	�j	�� Nr�l	tTl	4Nb�Nd�Ng�Nj�NkOl$On@OpPOrpOs|Ot�Ou�k	��<Na0PbDPcdQd�Re`Uf�UgDVixWk�Wn�Xo(Zp4Zr`ZsP[tH^u�^ybz�O�Z�l�Pm	���Non	��m	���Nio	�$o	�Hp	Ppp	��OaH3k�lL�m@u	�|v	�v	��,O��u	��Se4O�hx	��`Oc$4�0y		z	��y	��hOz�z	����k|	|t�|		�)b�Og�Ol�OrPt0�z}	���O�pT�dW��Y��Y�T^��~		@	���Ad\mD�	nl�	���Oa<*n�oPu|�	n0�	��km(Pv��	88�	���*l`�	��	��<Pa4,h�Pi�Po�Ps(Qu|P�̇	_��		hPr��	��pP��P��,���tx�	�PpH�	���	t��	�Pl�Psl1t@=�Ѝ	��Pg�	���P�p�	.QoQp Qz�P�xD��	�Qr0G4�	4$�	#�����	��0Q���	��8Q��	DQd08l�Qn�	��PQa$RoxRr�Rz�Q��E��	���Qo��		�ul�Qr�Qt��	���Q��Q�8R�XR��R���	��,gn�g��<
l~��T�$�		�Qj�Qr��	��T.̦rRt8�z����e�$�	t��	Rr2_X�		0RnHRrl�	,��	�x�		PRs������dR�T�	��lR�`�	�0�	t<�	.�Ra��T�	���R��S�l�		Sg(Sl�Sm�Sp�Sr�Ss0Tt ;x�Rè�	���		�Rr��	���R���	��4g�RÄ�	Sk��	��Se̩jX`kPSlh`m`Sn�SvtS��	_
_�	��XSy(n�̟	��lS� ;��@��̡	����l8fT��	j��	��	���S�l�	���Sg�
n�Sv�SÀ�U���X�	�Tz\��h����S�С���S��	.Ttl�	�,�	Tl@�	��$TaHTo�����	@Tsp�	���	��TTa�Te�Ti��		\Tk�.p�Tr�Ts$UtDUvĬ	�Ь	�Tl��	T��s�	��T.�.d�;i�Tt<v�.Ô�	��T�e�	t��	.�TaUeUi��	���TzD�	�gr�W���	�Un�	��<k��	��	0UsL�	��8Ue�<i�	�tUu�	��TUl(�	j4�	lUo$�	T�Un\�	���Ua�Ue VgV��	�T��`����U������U�D�	�Ud�Ur��e�����U���	���U�Ȼ	���%sP�		�Usȷ	��V��	�	���b��	��pVi(V�(�	4Vd|Vg�1l�Vm�Vn�VrWsL�.�hVoT�	�� 1a�Ve�A�� ���	B��	���V���	���V���	���	���Va��d�Af�Vt�"v<-���"e�&���	���Va�A�(�	���k8BmWzd�	tp�	.Wa<WeHWoPWÜJ
���	�4Wr��	t��	���p���	_�		\Wv�P���	��pWj�	��S���We�	�Wm�Wsp�	���We,Xi@Xo�Xy�W�D�	���	p��	���We��		�WgXkXr��	���W�LX�\X���	.XeЬ	n��	��	��	$Xr�	t(�	8Xl��	���	,��		TXl
���		hXd�Xs��	PpXa�Xep3u�Xô
L�	p\�	���X��X�H
���		�Xr�
J0�		�b̏fYiYl Yn,YpLYr`Ys��xH�$�	����d�vy
#t�	��Yi��	��Tt�
eP�	��8Y��	��@Y��	_��	��XYtxYz�
8�	.pYt0�	T�Yl<�	���Ya�Yex�		�Yt$�	��
t�		��b�Fl�Yn0Gs8Gt�Yv��		�	�x�	�D�	���Y���	���Y�x�		Zr��	��Z�l^���	��8�r��	��(�yD�t�	@Zb|Zl�Zn�ZsD�	��HZa�Ze�Zs[z�Z�	���������	t��	�Zl$����e�<����Zt�		�Zr�	���Z�t!
���	���Z���	P�Z�X�	���ZzxZ
�4�	�[nH�	.[o�	�h�	$[ct[d�[g�[k�[l�[n�[s��	��,[a�\ep]i�]o�]r^u,\��	LaD�	�(�	���[y`�	j`o
`��	���[a�[j�[k�[oTr
	�r
PPu
	��	���[d�[yw
�,�	4�	���[s��	��k��t��	��\aL\�p�		\r��	�� \�]��]�(^��	8�	��D\�\\�@�
�Ѐ8܀��d\����l\�(�	��x\m��	�\l�\r�\s�\t8�	��t8a��	��0eH�i�\à�	���\tt�
8(�
���\�ص���
���\r��	,��		�\g]p,]rD]v��	�0�	y�	��$]mh�	�#.x�	��8]e�
B�
��P]�@�	��X]���	d]g,�o�]p�]s,�	.�]a �	���
PH�	���]z�M
_�	���]t��	�]kL�		� n��	�d�]n�	���]a�	��	���	^n^r�	���
,p�		 ^z�	��	��4^a��	<^tH�		�7rd^t�		|�		|�z�
�p
	x^b�^d�^e�^g _j(_l4_nP_r\_sd_tl_u
.
�^at_d�_e�`h�`iaoRsRt�av|`�
���<�
._a_�x
��
��
��_��
��
����k�
���p
��<_�|
��D_ô
�
�D
<�	
j

p

��|_�

.�_��	
���_z�	
	�_d�_l<`rP`v�
y�

���_m`v�_��4��
	�_n�
���_��p�
���_o`�<��`��
�
��(`�H
��0`�D
�P
��H`e��
��\`a�
8d`v�
��p`�>�La�`a��a�hn�.����`i�`�$_d~	�`s�~���`��
	=d�`naraz�
y�
���`gh��
��aa�
��
	`Rk4al<arDasP
�x
��
��
LL,.xa�\
8Tak�at�,l,��pa�.y�
	 
8�at�<T�an 
���aa�aeP=��>�.�al�as�>��?�?���as�
��
�al�
���aa biH#
��"
bm8'

pbb�bd�bg�bk\cl�cm�cn�dp�dr�esft�1v�R�t+
t8)
��hbo2
t�0
��|be�Rè=
g
�=
���bi�=
�bf�=
���ba�8
���br�C
t0D
���ba(cl8coc�x���E
	�bdcr�E
���b� c�X���E
yG
_�J
�xK
��J
0cn�sO
�xN
Hcj�N
��Pca�cyxcü[
_`Q
��pc�_
Pl`
��L�e�ci�c�|h
_�b
���c�j
t�p
tlo
�cldn(dt4du�o
���ca��dtde�df�di�dtLd�q
�dy�u	�H�y�{	dm�q
��do|	$�q
	8�lddr�q
��<d�����d���Xr
��v
��u
ldr�	jz
�y
�dz}
���r�
t؄
t�
���da�Se�dl�dr@����
���
���do�
\�
���da4eeleg�ei��k�ese�,�
��8Y�He���؎
�h�
,esd/_4�
	@es��
.��
Tea�eà�
��\ei��yt�xeb����e��H��H���e�̒
���e�Đ
�et�
jD�
���So�et�ez�
��
�,�
.�ea��fH�
_ �
��f�h�
��(fo0frf�x�
t��
��
��a�fb�Tc�fd�ff�fh�fj�fk@gmpgn�gp�gr�gs�gtPhv��B�
��|f�\�
���f�$�
���T�
���
���fa�c�(�
���fe��
���
���fe��
y��
�fp�
���fa4go g��
���
	gpl�
��g���
y��
,gr��
���ViXg���
B��
��Pg���
���V�(�
���godg�l�
#��
4�
���gr��
��8W�h�
���ge�g�
��
���
���gu�gz��
.X&o�
���Wa�Wehi0hrh�P�
��l&��W�h����B���0����$h�d�<hk8��Dhahhe�Woxhà�h�B���ph��h��W��B\	
LXb�Xd�hk�hlH@m(inDirXisdivtYz���X�����Xe�hi�h�.�x���X�i�i� #!��"��/�*��i�)��i�<5��Y�Pi�T4��4iÌ=��?��`�.8F��(YeDYi�i���xF��xi�,P�ir�@s�P���i�x���g���Ye�i��h���Y��Y�`t
�ibLjd|jg�jk�jlkn,krXkt�\u�\z�~���iaXlbx_dple�nf�niPok|ol�oo$qp�qr`rs�rt�ru�k�q�����Zadj������\j���h�~����tjy؀�����ja�jk�jnh�_�_���jk�jp����jaL�j(8k�jo8�H�p�jm ��8���ky�	n���k�H��� k�8������8k�H���@k�Ĉ��Lkr��	DAb(g�kl�kr�ks�kt0���dk��m�(o��p� s�8s�\����������kü����kl�X����ka@�.�ka8_n����`AalcfD
m8lo@ltPlv(l�`�
8�yjԓ�� l�Hl�x��
��8�1�$�����lhlrt���<
d�ll�lnmr�ms�x��
Q4����lr��lm�ltė���le�ll�)o����-8�-���l�����l�t�p����leD���ml���ma8meXmidmÜ����0mcHmvX�����x�Pmn����(`�x�nП��pmi���xmt�mz �_,����m��.�mm�m��e����m�Р���m�ܡUl�		�md�AgLniXnjhnk|nl�nnbr(n��6�nb4ni<nn���n����6\����Dnt�����8����`nh\�p(���tne�am��$����nk����Tbl� �d����no���nd�nk�nnor�cs oz$����oج����dog��n����na��� #t|cÄ��L�	`Bg@oj�cnHovp�_t�_�����cldr��$�`og�ok�on@���hoa�oÄ�����o�l����o��%\>U�4	�on4����o����dbpcpk pl,pp8pr`psl-
�����oh�	h���pt�����v����8ee������n�es��.�.lpa�pt|p�X���Hpz�
"��
�	����tp����x�.�p�L�
�Լ���p�d�,��	�pl�pn�fs�pvp���p�t��_��n�����pi�	�prH����p�Ls�����qn(���qa|qe�qrPq�@����	<qt���Dq��q��q��`����dqeH`hp�lql�qr�������q������q���C�qr�� �����H����qo�g�������qtL����qe4ri@ro r��U�	rd ���r��@��,rn�d�jPrt��`������Xrktrp|rtL���������rd�'j�����ra�ro�r�4�
,4�	�rv4����r�\�
t��
�rp��<&g�rt,�`H����rasØ������s���	|�j0st��	0�	�g�jtX�zHM	�z����klto�Cr tsH�Xsb<td�tg�tj�tk�tl<umhun�up�ur�us`vt�vu�vz����psa�xbyctzd�zel�f��g��iL�k��ml�n�oԉp�r,�s�t4�u��v\�y�v���X�ؗtr��	��t�(tt����0tahtm�NoptrXt����t��Y�@�_����PD���xte��r��`�����ta�Dk�tt8���8`������t�@����Da8[c�ttP�nh����ti|�P�����taL�guj�lk$uows�Vt,uvL�_��`�"	��L���4ubLup�0	��_����Tuy����\ug�3n�uy����.|uaD.����u.�����ua�up Gs��_8����o����`Gp�uz������ulvv(�.�ua@ve0v��2X��x���vnH��vrX���$v�Lv����,�l����HpP�_����Xvkxvl�vr�vt�_4��H�	��	���vt��`�����vo��	(Hbwf,wgtwl�wp�wrHxs`xt(����v���������t�������������w����w��ph���$we@qy@wü�����\������w��w�<���8o�wvLw�l���`wl�̬�(���n����wo0���T.�wa4�bxeL
f$xkIo̦r,xs�qurvxðD1��wkp��
�����w�x��q���p�X�Q8�V8�����4x�H���Xxz<x���`����Ie�Ih�xi"jD�k�xlD
m"nJr��t(�v�x�D���������x�4\8�P4����xa���xn\����xi|�����xlXsr$���	�xalye�yh�yizkzo zsXzzDy�0�_�	,yrXyt���4y�H�Pz�(_���l:l��`yl�R,.xyo����yd�yn�yp�ys����ya�y�0�H���y�`��p����y�ľ
���yÄ��jPT����d0zn�.zaDz��5�T��ld��8z�<��P��@`zj���hze�z�@�L���z�
T
���z�L,{dh{ep{f�{g�{i�{j(|k�|l`}m�}n~o(~p�~r�~stt$�vPLxL�z�z����{����{Ŵ{t��� {e`{mT{Ø!	�	@{n$��H{�h�U�j|�	���9f�	|{l,���{�����{e�{�X�t�{l�{r��(%	��g�h6�	�{n|r���{�����{�i6t0|l`|sd��|e�|o�|t�|Ì�	����@|�|.H|ň��T|zL~,�	l|j���t|��|��(	�H�|t��],�	�|s��g���|e}l 
r�|Ð��	�|r`���|�4}��.�$.}n���}e,}i/p�1��C��C��<}yD}gt}mH��P}e�}u�}�E���:b�G��G���}�\G���}À���}t�	�}r����}�dMt�}t`���z�b��b���}a�b���}rX�}gD � ~nD~s ��~a�~e�~i�~oX~�X �� Cd~l� ��L~�t _D��P���l~��m��t~�� �~s�!��!�~n�!��!�~sh"��|�k;n�~�l�x���~�����pmi"jxTr�"���~tz��\�.`#.e0Ä#_�#��(�@�t�/���$	Hl�t%��P������$���e`�H%�4�th%�g�r�%��dj �	Ī.,�����<&���� &	�r�vܯ�#.����e'C��U �	�s�������'���ô'��(�.\�e�'0�t�'��@�e�����M
d:	U(��d�h (	l�k,(��x��(�T؀s�(����e8(	
��b�g�j<�k��nȁp�r(�s��t��v��@)��h".`ye���\)�h)�����)��T.~b f$�n,�sTs(��)*��4�ap�oX��P*�\*��P��|���*��*h�l+t�,��e���-��k�,����e@�$-�����\&��-����a�Ä'��-��؁��:�H.�n�-���ezi��X.�x.����d/��dfaP�e\�zDJ_�/��<�v�/D�l0.�fa(gop��T0���������X4�1��1��rt1����e<k�2��
T.܂e4�f<�h��i��n�MrD�tL�v �ô2D.��k�n��st�2��i�p�W�2	�n,�tx2����3�3 3(303�4	H=l�4��T���4��t�l�zr`�È6��4NlH>H g>����eT�o�u{È;��d�g�k�m �n\�o��p��r��s؄t��z�>P?��L�a���,l��$���?����o�@����i�@���o0�ttCH�e̠rP���C@�g�D`�DT�ndshE�DEl�rTE��t�aLF��p+a��ô��������F��0BkxcmĄzPH.�o\H�pH��ЄaJ�J�����I��8�g���J	l2g �j�PrQz�J���o.8��LK8`K��0��S��M��D�r�Qv�X�X��\�aȅeԅo���4Xd�k�s�V��|�e0�i�X�X��������&	��&	����z�X��sY#�(	�8Y	܅l����������2	.���Z���zG	�[���d�Z$�nH�s@[PH[��@�ipp	����a�\T�l]��`�a��n܆y��È�	��L�n�^	��k�]��������p�	��^��^	��d�
��
��Ȇ�t_.І��J
	d���lh/ma�k8�ld�nx�p��rЇs�v S
p�e��$�a�e��,�dH�if`�j��jP�l�j��X�a�U	��m��p�ep�t$n���c��d��m��n��o��s��v��
	Г
	�
	8p.d�f�n��ćz��`<q��܇e��i�r_4r	�c �d4�lT�r�s�����(s�����tp�t��,�o�w1�w��@��w��d�iH��Lyp�,�z	l�c��k�l�r�sЈtؘz�{.��l��oĈ��{8�X��{8�{������A_���pFd	؈d@�l`�m��n��r(��d��������Z.��n���������������8�t�6	P�	��L���b��p�hT��pB	vt�	P\c��x��c����Üe.�n������i����Y1P�������������̉l�s�2���	�v����������`�y���t��d`�ll�nt�t�����a܊e8�ih�k|�o��p��rԋtXu�z���`����`k��t����Xx���|�id�	��g��r������������`�pĊ����a�	���Ȋl�ЊmxB�8����e$���e�����t،	�r ��,�����`��p�0�dL�mT�n��t�����	��H��0���\���
t��t�d�Wlp�_�	��n�
8h
��r�����o��p��.��aH���ȋr��#�����ol�.�t|l
$��iH����a��eD�ix�o܍r�sH���	xpt�r$���8����d�������������7d̗��h�a�8������ �����������kl���lȌsL���؆j�����t�z|�\��.،eȫ
,Л	�j�k8�v$����n@��L�	�n��v\��������,�ø�|ZdT�m ��\�,�	\�zԦ���p�sء��������������	��rP�	L[s��z�
_���ȍṇ��Ѝa��$�
��
	�n������D��	L�t`�Tl��� �i��(�bP�p`�r��s��t��ب`����X�at�u]È�\�~\���|�z����\��P�������	��j��rȎsЎt��_0�,H���؆jt^l�o�rȬ��	$:d �gp�l|�r��s��tX�v�zX�h����g8�y\:�X�.0�bL�nT�rt���H�̭�ԭ\�d����d�e��_�,��	��zȯ�ԯ����a�VtX���l؏n|�����a��e4�o���.�yx����X�����u��	�r4�s@�t԰�������H��T��Щ�����,�a���D�k�P���L��(���T��̲	`�k���l������x��4���lĐr�s@��L�����e����zP�����e`��p�.Аeس��ؐz���	�k�r�������e�����t�z�����,�s���	@�st��|���Laܽ`�.�b��d�i(�l8�nL�pt�r��s��t��uh���h�a�cb@�c`�d��e�i��kX�lܖm��n$�o�p�rP�s��t��uĝz��ܗ�D��x���y,��t`�nl����a�����j@�m��.D�y\����.`��������X����	h���l�c�	������s�Vz�����y@�$X�e�������D��������	��b�g�n�r0�t���̒����������$������,��y������a(�u�����yL�P���8�h ��d���L��L�����rT����L	��p�����x�à������z������e0���d�f�g�l(�nH�p\�r��t ����ؓr�����.�e,����y��_�����m4�8��� �g��n����4�i4���<�r\�_����T�dt�e�}|�l�n0�
P������8�����ä�����t��	�z.�gԔl�p�r�sL��0���̔l�yd��<����c���������s<�\fd<�kP�nd�pp�sH�m�(�t���0�e��@���H�a�P���\�aH���0Bk��
�D�
��|�t�	��s,������\
������	��tؕz�P1�P��ĕ��
��̕��
�
�t4
���a8�i ��
U
	�l
��������
�h
0�cL
��
D�t�
��L�a��e��i��uLZ.Zp�l4
��x�eD
��g��tp
����o���(
��n�
P�	�
Ȗc�
��Жa�|	C}	�����
����

��D..@�aT�b`�kh�rT
�kp�nxipx�s��t�	
�P������d
��H�ð

y�L
y�

��
��D..��a��z�
�h�
����t�
,4
	��ra�� ;��\	����Ä
	��k�z�
��̗��{��
��
�n�
����a$�et�l���\
�h
�c�'
�� 
��0��� 
��8��t
��D�tt�z0!
��kb��g��r!
P`�a��eD�iX�o��8<
���oa?
��I
�J
����c$"
���nܘr�x��w��Ș�,#
���$��TO
�t$
���e(�t�#
���l�!
�����l�P��T
	���0�th$
�8�n�X
�$
�P�k�%
�Wb��f��lșn�p�r��t�z�%
��d�a��ex�i؛o��u ��P��t&
��&
��t
��&
����aؙy�~�Xw
���=o�&
��<wa���x'
4�
,�'
	�pL�rh�t�'
����������������
dxm�(
��@�a`�i`Yn�(
n�(
_�	�T���p�o)
x�k��n��rԚsb��)
����z<*
�T*
����a�
��0eH�i+
��Ět������hP+
	�i�j�m(�n4�rȫ
v��
v��ȯ
�� �k@,
��T.P�i̦r7tX�ð�
n(����(���
_ļ
��d�l�,
l�l��n��p��sțz�,
�-
����a�Zt�{�P-
�@-
����z\-
��-
t.
Лl�p��r�s,.
�.
.��
t�.
,�.
	�r4/
CL[s�/
��T.\�k̦rx�tl��X/
	(�rp/
��D����
�
80
��d��,�
�1
��1
��n2
	� r ^zX2
��d��r�t�3
.��a�4
�4
��Ȝ��4
��n�p4
��ܜ�(6
��
�o.4�bP�hX�i`�kLnnh�pp�r�tD��4
	�rx�t\6
d6
8l6
��<��@�x6
n�6
�6
��7
,�7
	�z8D���t;
�	��c�d�e �j@�k`�lt�n��r`pu;
.��a��eL�gh�i��kРm\�o��sСt<Dz��Ü��`D�lD�����<
����<
��D�tLJt=
��,���<
��P�n4�ìJ	�=
BP=
��X�jH>
�>
��l�ylP�?
����a�B
�LB
����u�?
���rԞsܞt0@
�������t��t��C
BC
.��s�d�	hD
���e�C
�	�cH�g��k��l��mȟnԟr�v�x�f	�f	(�rf��0���D
��X�y<��f��E
!	tE
	`�s�E
��h��0E
��t���h��E
����a8n�I
��l�I
����e�I
�J
����c�J
��\rdpM
��M
���#����o�M
���d,�p@�r܈1T������O
�� ��P
%�O
��8�i�Lj�P����T�a�R
�\�p��s��td�z\U
���kT�t���U
����a8�. W
��l�V
����e���.$X
��Ġe��X���6		�b�i �j(�k0�n8�t@�v�	������;8	�l8	�:	�H>	��?	��Y
BDY
��H�dPX
�P�l`]
.�!��\
�h�l �n(_
�H_
������^
.��e��Ä_
�����b�d�g�j,�lD�p�`
����a��e�i��oP�uh��<��D���j
��j
�����p����ðl
��������$�a<�jTr
_�t�`
��X�f��n|`
	L�r�`
��\���������\��h��,a
�a
 v�a
��T.�a
	��r��sТtl���hs�a��a
��Ȣi ����ܢd<�t���lH�	�rb
C�l��, b
��T.,b
	$�r8b
��0�������H�t��	L�t�b
	�<z�d
��ug��z\���e
���s��t�]
�����0���f
�ubH�cT�d\�gt�j��kԤl\�n��p��rХs�t(�u0�zPf
����a�bԧc@�d�e��f��g��h̰i��k��nزo@pصr$�sX�t4�u,�yp�����l	.����g
��j
��l�y��l
��l
���P�s�l
��|�a��c @u���tm
_��,m
������o
�`o
��g�k�o
��	Ĥa�[b(�d0�f8�g�[j�[k@�o��$p
��"U�p
	�sq
�����
		�q
_�r
��u
tu
��H���t
����yP��v

w
	l�gw
.t�a|x
�Xw
����r�x
Py
����apwc��zdw�t�_H{
	�z
��ȥp�s�z|{
_T�	p������{
.���_�{
���k(�o<�y,}
`<V�h~
���r~
		8�b��c��f��g�h��j�l �r��tD~
��D�����h�����@�����ط����h
����s|
_�
���o.�aЦbئg�n�r�
$�
4�
@�_�
pl�
���u�
��$YlP�m�
$xdL�
���a<�eL�n`�ot���
	�������D�y�j�Ȉ
X�n@�
1��
��l���
X��d�
����a��h��l��sħu�v���
8\�
8��
p�P`�
��̧hH�
j�
�a4�gt�kȨl�m�n$�p,�rt�s��t��z��
t�
����X����
��L�e$��`�
�4�
D�ll����
p{.�wi��kP�
��`�e��Ü��_ĕ
	��k��n�st�
�������<�����k8�
���c�d<�e�h8�i8jD�lp�mЪn�o8�sT�t��v��zȩ��żTpD���o\����$�eT�y�
,�g\�jl�m��s$�<�
�p!�|�
��d�zDbDPb��x��ԗ
�����0�
=<�
�����H�
������
	��rܩs��
�����T�)��ԩsX*��a�e�Ì]8�������f��c�l�*��*��$�a��
,�g�
��\�e�l�0�
��
T�n�n��
��h�e�������r.��e��
	��n��t��
�������$0�,{�l�	��vT0��Īe�y|��
�ę
yЙ
�dܙ
	�a��
����,��8������7. ��t�
��,�zК
��
������
��l�eD��\8���
d�r8?k���x��������Ě
	��rl�
��<
��
��īe(so���D�
D�z(@���
Ыl��
��ثa��b�eܛ
���e�
��
�l��
���zL�
jğ
��L�dd�oX�����
	D�nX�
��L��d�
 ,�
�8�
��l�a��t��z\�
��.�
����e��
���
�����`�
�����,�
.��t�B,���̬�����ԬÀ�
�t��
���eh�o��r,�� �
��T.�
	�rP�sT�
����t���J���
<�n@���D�e`�n�
U��
@Ts@�tȵ�ص��|�a@�
_�
����rL�
	̭g �k��l��r@�s��t�vy��
���o.Цb�g�i�n���
��
����0�
��X\�
�
�
���aX�et�o@�À�
e��
��8��d��(�
���
P�lح
�����
l�s@�
��L�m��
��t�c��i�k�m�tt�z̮�,��x�
	Ī.خn�r�tL�
�����p<W|<W��
W�>���
�eز
��
���e��W�A	�s�A�� ���
>�
��8�at�ohs|�zh��	�g`�
��\��д
#�
.��abl0&r��
��
����a��eЯr\�
��`�.ȯeȵ
��r�_�De��pza �
��n�stĶ
��ܯe<�hD�t<��)<�
���.��W\�
	�hP�id�n��th�
��$����y��H�g��p���\�e�d�d��p������x���
����rȹ
���
��iL�
����el�
.��e�noT�

��d\�g�l �m`�n��p��r�s|tH�zL�
���
�ļ
���lȽ
PԽ
���a��iP�o�pD���
��
��<��P����
 о
��X�a��d\f�Zs��t�"v��zT�
_��n������e��L�
����a�`x�
����a4�i��oܱÄI	���
��Ա��
��8Bm��t�z��
��}ep���
.�a(�oxZ
QPX
� �n��
���
��4����
��8�g`�o<�z<����
���
	`Bg��j��nH|r�rv��zd�
_�
���|l�|rX�v�	�0�	��lP�
����ot�
���
��IJa��
̲d�k�m$�nԳpp�r��s�,u�
��h/m�]t�
���0l�
 �
���ad�iH�nP��d�
�t�
	<�l��
��D���y
t��
\�s�
���d@�g�~m��o��sP�v�CTh8��
��m8�
��
	��
����t̳z(�
.d�f�l�td�
8p�
8 �
,X�
	�c�r8�vX�
�d�
���a,��t�
1��
��$��x�
,��
	��kd�lx�n`�sh�t��v��zh�
.T.�~�H�d�
_�1xF�������
������
	(za̴e�f�r��s��P�
�������
�lt�.\�	شbp�
����h���T=��~������
��T.D�b|ct�i��k�l��n��p̦r��s`��Pi�)HX�
	L�hh�
��T����������
.��
8]$n��
	��k�
�o1|�
�D�
����p��
��p��u�
��ȵa�i��L�
UX�
	�gp�
������4)��
	�f��
����k\n��p��t��
�	��
<�dx�i��n��
��D�a��eĶi��r(�y��|l
jH�
���s�T�
������
y�
��m�
����e$�
��l��
�{n��
���
	жr��
��ض���
�������
���i��l�
�HJ��
����X�
P����
X�gX�nx�r��s��t�3
��
��P�ah�d�'	,�
PL�
��p�a��
P�
����z��
����u���t]\0�
	��jķrL�tx�
��<�i��
_��
	зd�g��l0�tX�v�z�
����d�~1��
�m��
���e���.$�a	l�bx�g��k��mX�nx�r�s�t�z�	#L��d�o4��T�a��������h� ����l�����a̸eܸi@�p��4��!��#�`#Ըm|Ip�s�z�$t�$�t!����� ��H��h���%	�rp0��0��,��8&j�&�'#'��P�ap�i�o���h(#,��.��iȹt����,���z�,�����(�	�;.��g?�`.��9_T7��й��6.ع�p/���z|l
Y�:��i��l0�n8�z;���a�sd��t
�T<��<'h<	@�i��j��k�n��r�<��H�����e��
��t���<��|��H��<����a��kкnܺr���|	�}	���������ĺ���2�=���k<=y�?�����B����s�C�j<�sP�t�h�d�g�lMr��sX�t��v��z�|����el��Ȃ�t}��Ԃ����r�z���D�aL�	��r������� ���������e���t�_����Ļ�p�.̻�p���ػz���sL��`������$���@�e��h�	�lȅnl�r��s�����8�l\:_h���L�bܿT�mp���`�e|��������\�����o��z�.��a���<������l�
yL���t�M������MȼôM��Լ�(�������i�	��z���bP�lX�pp���	�a��b��d̽eD�i��o�p�s|����ZP	h�	`�b��r��t����h�������Ծ����<�������_����p�l��r�8���Ľg�l�rP�����mP
v��t��_�	��k�l�p��_�_�������$������,�Ŕ�8�dp�g|�n��z$��0���\�����d��`�����f��g��tt����z��_��	��j��r�_4��b̾p����	n�rP0sY	8���n������a�L����k0�p,�t`�,l_�	4�g�jvX�z8D.��HC	P�cпdؿfL�j�l�n�r��s�u�B��\�a�b�d�e �f4�gL�il�k��o�p4�r��s0�t`�u<�À��E���t�K�HN��P���az�QtU	�T�t<X��h�l�U	�lh�mt�r��t�U��$��0��`�� ��L��x������Y#�Y��`�a�[��
b��r��Ð^8�]1�\������`_̛�(c	��l4c�����c������e��ry�c�g,�k@�l��m�n�r|�s��t(hQh	�kh�����g�� ���h��h��8�al�e��m����i}j��X�e�i`�m�j��j	x�r<i���������kQ8n��m��lm����eDqq���e,p���ts�@s���a$�eT�o��\���s��s�����t�\t�d<�lD�mL�s�ut�ut�ut�w��w��T��{n{��h�e�y��p�t��zl{�t|�|����a��e �o(�u����|t�|�������0}t<}��g��l��s`}��}��pzh~��T.��4~	�r�v,�@�#��	��gT�jp�k��l��r��s�t�,�������\��������od��<����_�_�����bĉ��m��t@�����e��u�������t�C#|����a�k|���Tz\
	��s
�����8N����0���P�r��T���,�rĒ���o��@�d��g��kȎl��m��n��o��p��r(�sL�t��.��� ����o$����,p8��H�����������d��f��t���`�	�	�.��n�	���@��\�.@�ø�	�gP���������8Bm�Ô�8����8��Л��T�a��,Ԝ	X�v0���4�r��u��vxH�*��c�
����ad���pУt���b��l��n�p��x�	d�����dЬ	ddr������@����8n���<���Tt�,�	�l4�r<�t��,���$��г	D�b��s��vdW�l�	`�e��r��s8���l��(lX�����i��l��s��z���T�t������������
��la t����r�s��rй����o���l����eD�s���dL�lT�md�s����ex�Š�v�H���v�"��\�n���d��p����9$����j��pԻ.��a�i(�l��o0�p@�t��z����9�������t��v����������������l�T�����������
j��4$�4���8�aP�rl��4����X�r��t�	��	p�j��t����_��	��g��s��t��z8zUP:_��,��8�.4�at�b�cD�d��e@�f��gt�h��i\�j��k��lm�n�%o�&p�0r(Gs�ZthfuDgv�nz��8&�̉\�d��g��j@�k��l��n��r��uh���Ȋa8�ox�������p�� ��@���D�ad�i���L�a��e�
�02����a�yxo��l��`k��t�i��o���#����k@���f0�gT�j<�r�������������&�(&��f��f�@���$�ypC����a��o�u(��X��'g��j��k��l��n0Av����	T�a�b(�el�i,�kP�p��r��u���0���������	�	��l��rH������<����������.L�tl������l�������.D������z���st�,,�	4�kP�l`�p��_L�T����X������s��v`��(����k��P������aT�	��	��z�"vL��s������e��t4���g��	��g�������dP�jX�k`�lh�n������a��e��g�hH�ix�o��p��s��u�z�����t����,��\���		p�l��r,��x�����L �t��m�����e��vX	��l��m�
���b��ep
�|
��l������r���.�a8�o(��t��� ��ȝ���` �T@�d��ld�mT���!��\�a�R(.p�b��sl1�3��8�r`49(4���b�3.��a��ox�z,C|B���l�L��K��t�L_�L	��lN.�s�O��b��c��d��g��j��k��l��n��p�r0�sH�tLO���a��d��e��f��i��k��oH�p��r��s��t��ul�zp��X�ňP��P�xQ��Q���	�Q����c��t��u�R	l�P�R����kDW��W��W����a��(XTX�����Y_�X���c�Z	�Z��(�pܕs�]Pt[��@�iX�t�^>H`	�g��p|`��`�������������(��T����	�h����r@i	�g��j��k�ld�mt�nD�p��r��sLmp{.$m����e����x���m	��t�m�������|n���a4�k<�l�)oTp_�p_�B��BD�r`B��L�e�s��X�b��l�t���zw	Hv��	��d��e��g��l��n,�rP�sl�t���{'$w��i��m`}y����S��x�x������x������x��x����d���������� ��D��d��<�� y�4�H���X�����`�Ÿy�y��x�a��e��g�����y����m�yH��o	`z������|_T|	��g��j��k(�l0�p@�rd
P~����b �k�ì��~����`�T�.8�eL�m��nԃ��T.`�iLsh�t<v8�zĄX���T�e�e�\� ���x�rh�Ћ��d�k�l��r��sx�
��a��d��g��k��l4�mD�n��pp�r|�z��#�����z��) �.�a�e(����p�����`��� ����(�P4���<�a��d`�ih�s��t�����	���Bo��� 0_��	��nP�r��v�����l����`}t���k��p��r��s��t$�td�t��@�	��r�	��	��l �n(�r0�t��vpzT�	`�,Ȩ	�����m��n �	8�rd�sd���H����p�p4�`��m��zp���x�a�����U��	��m������������k�p��r�t`��������r��\�d��j�r�t����,����a@�� �_��	 �j<�rD�tD�	ܻ�t�_0�	L�d��g�t�zL�.D�a��e��ø�����������������N4O��`�b��l��m��p��r�s��u��v���0�a��eд�j��p�i�s���Ŝ����e4�i8�z$��D����������ĝ.0�ad�e��i��tT�ü�����x�������l,�y��p�pU
�h����r��s���СLur�#8���f������������g��l�p�r0�t������e�'��ĸ��&�����`0#�0���ay�hw(�id�jw��0�a|�i��k��oz�t�@y��X�t$���d�g��p�n��s��t`�����lL���sx���H$l��r ���bH�dX�k��l��m��n��p�r��u��v��z������a��b��d��e|�f�gX�h��i�j,�kP�n��o��p�s��tt�u�v(�y�����Ŭ���������P�al�kt�n��	d�	�	����|�j�4���h�y���|�h�t����.ܿ	��g�kl��p�r(�t$������������@��`�������������	t�����dD���	�ah�ep�f؆jx�m��s��t��v\����&����T��0�
	��8��8t8t�8��8���������e@�
��c�d��g �j0�k(�l�n�pP�r��s(�tx�v�xD��T�	��n`������(����z���|�,��tH�����a�b|�e��f��h�ip�j �k(�l@ mX�n��p 
r��s��t|�v��z����H���g��k��m��������b��e�n�th g
(��	��r�������h������a��Ì]��������������n�s�+0�_h���@�e�l�P��\�8�n����t������XSyL�������	��a��m��n��t8���p����Q��Q �U\7_�����ra��zV
� �������.���4���T�a�������
������c�e�
g��`�(�iH����	<l���0������������|�e��i<�ô�� {�Pw��h�eT�p�d\�lx�T��m��n��h�����������0�� �	��l��-T�����d��s�z���z8�tL�.��e��k�tС��
H���� �jT���]���	8�h`�nh�t����@��c�p�d������p�a�e��o�xè����	��g �l��n �pL�r��s�v���P�e��k�n�������k,�����eL������5�W��1<�����,��������p�8,���4�tt�z����<�e��h��i��l��m��t��ì���'�4�><�	|�.���������	��.��k���������W����e���x	�����������n�t��ô
��Pygn0����aX�����l0�r8�y����Ph�o�hv�cD�sD���L�ep��؄��]��h�� �#0�|�s�
��a��d��g0�lH�m\�n|�rl�t��v��z�tp���n������e.��a(��T.�p�s�z��dx����� ��t����4��\��<�������T�a��f��g�j,�sP�tt���Ia��o�����a�
�0����n�	��s�������8�g���0	l2g��j,�n��r<�v�zX	�	|	�P���.�j��ah�� �i��l$�rdv�.���D�e��i���	��	d�k��n��t\
��l����	��	���	��b��k��l��p �r(�s��t0�v��x�������i@�P����a�d\ii�l�tD/	�X
	 	��(t��_<	8�cP�r�<			X�b��d,�l��n��r��s��t��vؘz,	D.�Ø� 	,�.��k��r�s�t�vd���������v���i���p�����ipv�zv�%����k��pT�t4�z()�.h����z(���D�ap(�P�r�(��\�� *\�d��n �t(+@L	X-	��g��j(�r0�s��t.��/	@�d��g��r��s��t�v�z00,�2C�2������2����Ü2��z�1���e�3G03. �a��e��i��o�uT���88�l9��H������������$��4����@��x���:����À:	��d��g��l��mH�s`�vp�z�:����;��k��m�;����e���;����e@<.�b�e4<B\<��B��������$��x=0�k�=��<�eX�z<>�����|>��h�el?B�>8|�k��l��n�?��?����e0BC��n��r��s��v\C��C��C�<D�hD��DC��k�s�t�)�ELpF8��lIG�I��J��J8,�lD�r�JytR.��a\ML�l��n��r��u��vM��X�a��e��f�i��k��o��p�s(�u���x��LR��Y�lX����yPZ_�Y����c�9th]$����d��Г�c
��d(�gP�l��n��r��s��t�v�x(�z�dy<���de��4��tPf��H�e��f�6h��l��t��v��ä���e��e��x��f_Df	�182������7	g���z�g�������m�h����eز`i����oHi�Ti����di�������i�� �ep�t��vL��d��0�o	�i��D��x���io	�i��\��0�H��B����	�i	��g��k��l��p��r�]�����������4��@���j���,�k_�38�k����bP\n����l`�thn�c4�f<�nD�pL�rd�tx�z��tPp��pt��.\�a�1t���hq�p�aTqP�st 0	�s	��n�uhu����rvt�u��d��lPw	�v����d�}	p�b��dP0sD��� �l�r�%������������ �kp�ø�����d\�	|�j؄	�gT�r\�t�_�_T�_0�	d�r�~��l������d��g�m�p��r\�t��v\������d���L�a��������o4����#���X����������`!�r(�v4�z|7���HoT��LD�����<C���øI���eXQ��b��d��l��n��r�P��	@�a�e��i��o��p(�s@�u��ä�ŰQ����ahT��T��h�yDU�\W,HV	��lԒp��r|V�����p�������x������W���n`�^��b0�lP�md�s,�vb�`a�m�`��$�e��hh`m8�vHd	Xd��H�b�e,Pe��\�z�f	<�g��j�k�l`�p��t\�.P�X�i����e�k�[.x�l��m��n��p��zll�|u�<t��l(y��w	��r�X	@z	��d�n �r�z	\@	\|���p8�z�}����dT�r\�t����.h�a���Z_$�	p�r$���	��lt�	�z{��������BbH
cP�d��j��k��l(�m4�n@�p\�r��s��t��v�z@�����ad
b�
dP�e`�f|�g��id�k��l��o<�p�r��s�t0�u��������<�t���D�a`��<���T
�p��X�`�	,���x�t|������d������a�k�jn��� ��А�������8���g�jkD�����aL�g�*j��k�l�t�u��_�_8!��	8��� �bh���Hg�����p$���`�p��,���$Cc|zL�����_\���x�p�s��z��.��tH�|���mę����o����e̛	hCl0�p �rp�s��t0�z���������$���������������dL�k���	�a��b��h�k,gnh�o�!rT�uT��X��������l�����H�Xԣ����v
��n�����a�
c�	d��e�f�Ih�iD�k�l��m��rh�s �tX�u(�v��À��0����������x������l������n��Аj�8������0������8��H�D�g��k��l��m��n��p��rp�s��t��v�xĪ	�sԪ������������8����b��e��fdk4�l��m��oX�ph�t��v$����g��m8�}�����e�mH�H�
��U�	�r�������������L�e0Ø����D�n\7	�7nx���`�ax��l���l�����T��`�	��r�����	��l�nȯ����i�����e��	�����g8�j@�_`�����d�eH�g@��T���
�d��c0�d8�i@�z,{y�{uL����_��}p���P�e �X�t0���d�e��z��.��k�5lt��(���t������e��i<�
�@���kȹ�Թ	��s���������o��À�	�g�j8�pL�r��s�t(�vT����Ls�������D��`���(�È�8����
t�c��e��i��k�l��m��p̦r��tpv8�z������d�(tm�n����+�����=X�?�A	Ī.�i��k�A������W����TetD�l��8��|��`�s�z,�.bl����<k(�v(���eHiT�ôj	L�b�nt��v����<��������j��l��rt��H���t�r��
�a�d�g8�kP�l\�m��n��o�Dp�r�s�t�z�.�e��o�������(��8�������h��������h�.�a(�e(����P����0�oH�r$�	H���d�l�����a��ix������p��$�����d��g��h��n0t�"v��_������	��n�����������o��<��|����k �m(�t�z�.��ax����a��	�	�j<�rD�v��z��_8�,��	�l����L�������l�r|�vX�����P8�����u��t<���b�f�k�lP�m\�pl�r��s\t�,u��x�6
0��<�����L������������j����a\�dD�tHAvp�	$�s|���,������8����0l���ec ,eP�����g�~m��r|�z��_P�
n������o8�����t��_l�	��c�l�n�r�jv�zX�
���_4�,x�,@�		�b �l��m0�nP�r,�sd�t��v��z�.�8���(�tH�1����<��`���D������\�l�H1xF��p�����x�ðN1p������D������8�	�r�s|������x���	���8e�i�@���m�����h �kl��p(�rptxzpH���
\�dL�g�jT�n��tHt�����d�id;�Td�c��k��l��sh��l�a��Ð;�H�������	�j�r�t�Z��h".�!nh.��h,�i"j"n o�r"t�v�fy		|jd�g@�lnL�rT�s<tDv\�z<�H��8�e�_�,L_h��h".��b���X	d�r|�z\�8��1����������H$l��r��bd�cx�d��g��j��k�l�m�n�p0�rD�sh�tt�u��v�z�����a�
bD�c�
d\�e�f�gi�k�l�mo4	p\	r�	s
t`
uXv�y�������������X�ÌP���p�a��o����P��������p�P����a�c�n�oH 	l ��h	�#���c�eT$P('	�%���d�)���
�-	�,��(�m,-	<-��<�p\sX�z�-�82P�.��`�o�3��`�3��|�a�585����r�4	��b�g�lL�pT�r��t�4�������p�`����
��
�<7,9.�l �mD�o�:0��H������:��8�o�ø%;0�t�;n�@,HA��h".��bL
f��iT
k\
n�o�!r�urv���DB��\����N�HDo
TD����alc�e,
f�h�xi؆jD
m�or��s�t(�v�ØDp�D8@}����kPE8���D�����E88N4�I.0�z�G��8�s�W.��üV
P�b��d�f�g�k�lLmpn�r�s0t�v�x�W��W������X���z�[D�s�Y�����hY���y��\~^$^���a<e�i�o�u�p^t�^���l��������^��^4lTm\rds�^��^�t$_	�s`_�4_xs�_��_�l�n�t$�t���_��el#(`t�aH`���a�e��f��o(Š`�g3pb	e4j<k
t�b����3� 4Q`B��c��Db`ihp�e`�f�g.�
e�g��j�n�y�zth_�W_�,`l0l�����k�����mQ�m	�s�l�����l��z��t�_������To.�$Ō�_������o��toapedi��o�uT����q��$������������Hp�gH�l�rt�s(q��q	�zk��l�r�s�t$vTr��T.TKd�üŤr�r�����r��r��r���e"n�rs���(s�#.4d8s��eP�s�d�WPs	<nls��D��s��s\k|s�s��s��tzxtC�l�rd�,�
_4�\�
,�
	�z�t�����u���Ővt@x. e`w	
�d(gDhdk�l�p�r,sDtTv`xn y,|{�T{0s8{��8e����|Pk4|��Xet�8}��'�h~,������������d���	T.,b4d�e��mnt�z��:1��m�t`;�@���������(�|����Texà�>����$a�os0�X���<e<k(���T.�e�it�`hj���l�����t���s�tD�z���Ȇ	��n�r�W�X\�8@�������l(*r(��4����r�����d8glk<lxm�nP+o�p�r�s(vTzԐ���1adeT������L���`����Uo4�.�a�i�p(����������d��f0�g�t�v�z��	�	�����o��\������,m�pz,�����|�.��(Pt��� a@�������8�,�	���Lghm Jo@�_x�	�j�r�v�z��	\����l�rX�94�P�u����y ��|��t�����a������	�nH������t��b(kl@rHsl���h/m8s�L
_ ��d����s��zd�_��	Xctn|r����,,�	Db�c�k�l�n �r�sؘzl�f��.�����	4�	$�j<�	�e	r(	s������ ��������	�`���	�4������ 	k�����l�rL�	`�dX���D	�d����	eP	à�����l	r|�t	p���k0p�t�	zp�	!	�]���	n ����	n,���	r8����	���.Dt�	�p��l<s
���	a<����	p��
l����
a@
r����P9�0�X
�����P
i,
���������|9dDg4Jj�
l�9r�
t	����
t�:js�����
a�
��������
�,�	t!j�
r<�	��	�gl�	r�:s��t�v�z�s8����lt`�8��	�"z�k���,� ���4���@z����Le����	db�d�j�l�n�"p�r�s�uvz��.	laPe,
f4
iTk@
o�s(ð>�L�����8�����p�����<��܊���d����8l����
�|>�L
�d
��
�,.\�		Hc|f�g�k�=l�m�n�=r
t���������y%��	�s������������y�����l 
�����e�tP[Bh�8����i4�d�8
jxOk�l>s��j4�	<>r��	�l8�8�>s\
zx
B��:o:l
t :��t
e�9���
t@�8�
l�
tP:y�		�	���
r@	
�
bHdte|i�j�k�l�p�r�s�tu�z����
a�b�(d�e�f8,g�i�.k$o�p�s1tu`w<ü�	jXah�	P�	\	��`�(Qj�	��k�	���$t4		�	���k�	���lL�mg	�	���pt		@	���cX�m�		�p�		�
	r@
	�
		�
	t�		c`lԒp�r�t�	�� �\���x���0�<�p	.pmxo,	8D	p�	��<*n�Au	G	���a�'t	��	���l�rd	�P�.� 		�c�gk(l�r�st�*v�x "	.H&	p{.`%	��eDÜ)	��)	�� aTe0 h`lH s�)vpŸ�<*	LdX+	_,	,	��h��/	P\/	��|e�n�Ŵ0		�0	1	����̒t2	����X1	���ez�è1	��1	����|1	���Ì1	�m�2	,T3	#d3	��a8e(�oD�Pf��3	0l�3	���z�l8	��|.s�6		Pk�)l`�p|r�s�tX;	���!c�d�i��m�td58�;	n8<	�� Fä<	_�>	XH>	���e�@	��hHl(,r4C	"l�B	�a$d,g8Ik@lTnpo|Ip�r�s�t�z�D	�E	.t���E		�E	��8lG		tF	��Ldhf�"v�G		�H	�I	�I	��x��H	���h�-o��$I	_�I	���k�1m�-z�J	PK	���aM	��<VoM		�#r�v�#ztM	��5��T	�c�T	��a<eH�nDt�Q	nLp\shx U	#�U	�U	�X		�W	��Tt(T(Z	��X		pl%r�Z		Db�ld%nP0sX0tt%vL[	�8\		h0r`\	����d]	���%l�0r�`	���0kHGl�0p(�r�Lt&zc	�b	�jn\&r$d	. a'��g	_pg		(rh		�g�	rNt�vX�zPj	\j	��Xh�l		�l	��lb�lTl	tb d\gtk�l�n�p�r�s t4du�k	���a<bXddePf�g�i�km0n<o p@s�t�u(yP$zh�ż�Im	lm	��a�No<�<m	�Hm	��4��m	��m	��H��m	��lrP�Hn		$o	����c�jn�t�		pp	��H3k8W\@u	���a�o��t`u	Nh�x	����x	���üu	���rhx	��`Oc�y	ps|���_`�
	�z	��m<o<�rH���{	4m�|		5g�l�5p�r�s�t}	��H�H�����������@	.�a�c�Ad�)l 	p8��	�D�	�kl�	���a܂	���s��v �	p0�	���a�Ibek�5r �t|�v$è�
	А�l�	���Hl�,�	T8�	��4a�*l�5rT�	��	��Prl�	�-f�g�k�lDmPnlr�s�t,�v ;x؜	~��	���y�	�	���a�d�edflg|i�j�k�l�sL��
Ht�	D..ik m(r0s��f	������	����	,��		8j\�	��@��������PHdHP�؞	tz\o	��H�	�	�	j�a<nH�	tHl�	���	���aghx�jlnX�p4s<t��v(Ű�	>	�		�		�	D�	�� ��7	��		̡	���-lȢ	.\e�R�L�	�l�	��da�e�à�	e��	������(�	�4�	���e�	�mص�	�X�	���z,�	@�	���ae4o��x�	e��	����ԧ	���	l(s$�	�0�	�� t��	#\����b��		<jpk�l�mnr<tHz��	���mb�i�ì��	1��	������	�x�1������@�	.�;e��T�	8d�	�������=a�
x�	���e��	�k��	���e�	��T.t�c�.�X_�԰	(r�	��0ed�	��	���=r��д	C�lܴ	��`��	��l��	��xt�		�s�	����Ĵ	_�	�\�	���r�	����o(�	�dgk�1l$m8n�1pls�t�v�zT�	.�A�$�	���	��o��	���i��	���	��0a��dTf\hdt,�		8�		<-	(�	���1m<�	�\�	��xaT�	�d�	���a��	��		�j��n\Wv|�	���	���l�r-��3�����	���i�ø�L�,�	f<�	�����	�����	P�Eop�	��$y0�	8Xl`mtn��r�s�t��xP�	���}lu
	t�	��ld�	,��	���z8�	��		x�		�c�r0�	,t�		p4k�l�Yn�r�t�v�zL�		��		�	�l�	,x�		�4s��	�����	T��	��e@Gl8�r�	�ZsD�	��4a�GpXthì�	 �	��	��`���	��	��ta$�i�ì�	��|r4�	�����������	lJtH�		d^t�	,`�		�g�lrsX�vz�pn��	���e��_�	_l�	,p
G
.	 a�e�"ih#oRsRt�#u\�R�p
L�
��T��!�4#�La��#��#�$�,.�e�s�	
	
�c�d�f g0 k< l� m� nh;p� r!s�!t�!vT��S .�e

[�	
���z,

�@

�P

�� y�

	�s�

�� �t

��$ �

��NadNllNo� v` �p\
��X ����l ��
��� vt �`���8��
��� a
B
��� c<
yH
��� d� e0`�,{8d
� d���
��� t�
� t�
��!ep!s�!t�!zH!�\!���=	4!s�=��<!�\���T!�t�1	P���h!z4D��|!�
���!�
[��8
���!a�!i�!�,
�|����!�P
��
80OgpOj"k4"l<"r�<s�"td`v�.$"e`nhL�n,"t�p�8X
��
���O.�OdPi0Pn�"t�Pv�"zl"�`e�
��d"��"�xB�t
��|"e\<9	�
'�
%�
���"e�<t�
��
	�"f�"g>k��n#r#s�=taz8
.#� /���"��
��aa�=o0
���=k�.H#Ü
8(#r�����@#�X#�<�
��
	`#k4al�QrDas\
��#n�#rx>s�Qz�
y�-	<
��#r�#s�
�6D
y 
8�#j�attt�t���#��
���#�\
8�#l$s $v($z8zB�:B�
y�
T|$d�$g�$k�$l�$r�$u�
��	0$a�$e%i8%o�%s�%u<Dz�$À%�P
tp
��
��
t
����_\
	�$l�$s�$tx
���$�0%�l%��%��
_�
_�!
� 
�$rH#
t�"
%m %n(%s�#
t�#
�D$
��$
TL%lT%r\%s�$
t %
���P%
�4%
	d%r�t8���x%�x%
t�%
.�%a�%e�%
t�&
�t&
�%rx��8'
�%lX?p�%sxN
#�N
���%a,�
PD�
���%z�
&r(�
#h�
��&ahWi\	,P�P��0&���`�����H&o
P&d�&j�&l�&n'p$'rH'sT't`'ux'v�~��\&aXlbx_d�(e�nf|bg$,ih-k�-l�-o�.p/rX/s�Bt�/u�'�̀������k �P8����&y���X����&o'r�gp�	H���'c<'ü�����4'�l���\sĈ���l��L_sh�tЊP܊��p'a��	(g�'l�'p�'r8(t0����'��+�-�@.�\.��/��/��������'a�]o�����'lؐ	���h".��bL
f(i�o(u,(Ô�p���1T���$(������Ib؆j\(lm��sp(vd(�`�8ԓ��Hl��8�����x(b�(g�(k�(l8*nD*pL*r�*s@+t�+v�xd����(y<��H�~�	�s���(�|����(�ėy8)a�b�ct)e��f�)i�)jdk�)l��m�)o*r$*t0*uH)�*Š��������@)��)�,�Q4���X)b�)e�lr�`)mh�}�%	��r<+�l*�)nИ8ؘ���)�����)�����)e���}n�`�
 4���)e�	�)k@����)���_
���r����*���p�.�le̛��t`*���,���X*�ܞ��m�*r�*t���l*e�*t +z�*��*�\����Q�����*t4�QD�	�*sP����*�x�������*��nП���*epmi"j+��8 ���+��P0+e��kT�Q�������8+al+e,!��}ܠT+e�+��\+t�����x+�����p�al�	�Ag�+k�+l�+n�+r,s4bt��,(�,����e��+k$����+e���T.,iP�n`�#p���,ad����	,d�nkP,m\,n�,p�,r�,s�,t-z�����pج����dt,g��s|,t�	��_��u���,iĮ���,a��`����,a�,o #t|c�'`P�	,����,k8Bm�,tp�_��`�����,a��t|�	L�	-n,-r<-tHov��,��L���4-m�.̛	H-rԱ��P-������cldrdv\-�xtt�q���-�����-���-t@����-e��p�b�-k�-l.n.p.r,.sh�td�	����-d�-v,�	̶��������m��s���X���$.z�T_Ľ	8.c�hT.r8����	�fl|.m�fn�.r�.s`0z�#	|�.�	��tp��.k(����.e�.l�.o�.r�.�P������.����,�t���.dH������.gL���/oL/�|z�� /�@���(/��	4/z ���@/�������h`�kx/ntrp�/t�/z�
������)���d�/g`�j�/n�/r�/s�Bt�b	��.�/o������ �.��	|�j�/s0st<�	0�	|jd�g 0l80r@0s�jvX�z������d00et��	��_��	�	���H0l tsH�P0b1d1g01jP1kh1l<um�1n�1p�1r�1s2tH2uX2vd2xt2z����`0a�3b$4cd5dp7e =f�=g�=i�?j�?k(@lh@m�@o�Ap�As<Bt�Cu4EvFz�2ÔAŌ��D���pg 1ylD�����P����(1a@1t��	4�`@���H1a8[c|�`����`1a(�g�lkL�m,uvE��������1e�1i��o��t�������up�1r�F���	(��8����1a�mc�1Ä���l�h[	�����1pt�s��P����2i�bk82l@2o�vr�vtHy�	(�P���<�PH���P2ax����P����l2o��	(Hb�2g�2l�2p3rH3t��z(����2��;�8?�A�0A��D��D�h���h".�2a�b��g�HyT�Xl�,��p�b�����2�0���$xk03o83p�!r�qu3��(
�HV8P�
	`���@3alc�IefPh؆j�xlm�3r�3v�3Ô�j����|3��3��3��3��3�����~1�8�&��l�8����	�3n����3�\���$JlTJr�3�h����4j�xlX4n`4s$���4a�ch4d�4e�4h�4i�4o,5sXzz�4�5Ŕ���t����B8���p4�<
��x4ü��4d�4m�4s����:bĝ,����4z,Ht
���4l�4mXtztP�����t�5�Pz��t����t����,b�. 5eD5iH���<5sWt�P5m���X5a�5e�6i7rP7z�5�e����5��6�7���q����5�d
���5�h���5b6e$6l,6t<6�P6�@�5kp6m|6r�6sĉ|��5.6i6l���	�m�<n���k���46�Xk|���H6�Ltkt��\6��s��d6�Hv����$zp0z���6e����6t�	��hs4		�6s0
�<
���6�
���6��	�6n�6z��
tX�l���6ad�.07�D7�	7l,r8X���(7���8����<7�4
.�7a�7e�7r���LX7bp{f�7g�8j�8k9l:m�:n<xr;s`;t�;v����
���_�����7a8e\8gl8ix8o�8y�7�t,���7�P8��8�t0d�{l(8m08n88r@8v��t��������_�d	H8l0���d8nt�#��e<��8e��t	�8z�tĉ������8.�	�8n����8�d���8���`���8�T9���(9e`9il9k��od�r�9t�8Ø9Ű�g@9mX����89z�%U�	L9gl*�s�_�V
�	t9a�9i�9l�9m�9n���|9��^y�3�9rl�Q� 	�
�4���9e$Q������9�t���9�4�H���9a\:bp:e�:i�:l�:o�:u4:Ä����,:��:��:���H:r���P:e�Cth:g�:l�:s��P�	��GQ�	�:s�I��:sxJ��J��tF`.�le̗�$;.,;b4;j<;n�jrD;tܗ���:��"��L;t;�8���
�`�c��x�����+�t$��$��X;a�;e|;�$�%��t;��;�h%�g�;lT%t<&��T. &	�;r�v�'���xe�;� �������;�),8(	�;d�Lg <jP<kMlp<n8Mp|<r�<s�<t�<v�)��T.D<à�Wp�	0<t�)��8<�*,�,���e-X<k�,��d<e�-��zi�/t�/�<ld/���<e�<z0.�fa����1��1�<rt1���<e<k�2D.�2���<e��nL�v�T���=�P5=ø4��=it�ll=o�zr���T.<	8=n|=s����D=�����T=��5`=g�=tl_8� D����=o�6��<Nr�<#�;�=l�;�=a>c$>dHOfP>g �k�Olh>m�>n�>o�>p�>r�>s�Pt ?v0?zD>��=t�=.>i�=��>s>��T�o��.L�0>n`=��8>�?.hOap�eT�`�@��`>a��i�Oø@`�@��	|>aPdPfPg�>j�>s�>t�v��zC_HC_tC��D�DE`TE���>a8��LF���>a�F��0BkxcmL�t?zPH�xI��I��?a���I��J	l2gX?j`?n�Prh?vQz�J	�K_dL,�%�Mp?nM��x?a�M��hQlD�r�Qv�?�`r�|r���?�K���?Ø��?p�R���?ehR	�?z�N���?�xt	�l<U���?�LU��@�T@t�S��@e������4@�xZ	 �jx@k<@��W��H@��V���@i\@�lZ��Z�.�e�a�@l�@n�@pTr�@t�@vAx(l	�j���@t�m.p�eq	�p���@ttq�<q���@i�q��t.,�o4r	Al$Arw��d�i�z	��bl�cdAk�lpAm�Un�r�sxAtVvؘz�{����o�|_�A	8�j	�Afd���A��������@�l`Vr��v��Ak�����Ae�Ak�Al�ApBrBtBz0��P���0��H��l�) Bk�V
	Ж��(BtH���0BaxBe�CrC�`C�L����
XBzș��`Bel�lBk�Bl�Bsԙ�����Ba�Bm\��`�
k|�
���B�����B�P����
�Bl$����BiCmЛ	�BkCs$����B�8C��������%s�����$CeP�	,Cv,T�4�	DCclCg|C�H���LC�p
�h��(���tC�̣��Ѝa�Ce�Co�C����T��t�������
��
�Cn�
����Ħ���Ca���Cg4JjDm Dn,DrTDslDt nz�������De0�. a���(2_�1	4Dr����<D�\���|�zHD�8��P���dDa�Do�D�h<������D��>P�	��j�Dr�Ds�Dt���0��H�.�	��gEk|�r��s��tX�v�z��	�X.����D� o���D�t���H;�X�EdTEl`En|��� Ea�Ee�Eu�E�ȯ�ԯ��LEa���������hEy��	pEg԰��|E��E���v�Ee4��El�����Emp����pb�Eh�	�Eg�k�ElܻP�� �#L�`���F�PF�t���FÌ���	�5.`Fb�Fh�Fk�Fn�Fr�FtpFøF�T�	$Fk�Fsd
��q����hF��F����d���������|�qx�q�����F��f���Fl�����FiD���H$lܽ�Fb��dxGg�Gj�Gk�GlHmHn@HpXHr�Hs�auh����FaH�b\�d IeLff�Lg�Li8NkpNl|NodOp�Os�Pt�Pu�Qz�H�ȿ�H���pGy��	D����Gt�P0����Ga�Gl�Gn�Go�Gt 	�	4��\�	����t`a�`g�`k�l�Gt,uv�	L�t�������Ha(Ht0Hy<�	\�~�������8Ha��r\��h���PHal�cX�m�az|Hô�����tH�<_������ap��s��.��	�Hc�bl�Hr�HsIt����H�K�N�O�,O�Q�Q������t0�.��n�����Ha�h��l��.dI�0�
Ib�dfxIg�Ik�IlxJm�Jn�Jp�Jr�Js�Jt�Jv�x���8���\I���	P,���pIe�dyp{.�wi�����Ie�Is�Iv�I��	�s����I�_x_�������	�IaDDbX`k�m Jo(JrDJvPJzJ�ĸ�����J���0�_0<8�;��0J�|���8J�<c	_�����XJr����`Je����lJb�e�Jl�_8�.�Je�g�Jy����,4������T��H�,�����Jz�������Jod�t����b�Kh�Ki�Kn�KvpKÜ�	
�Jg�Kh�KjTek،l�p�KrtLs�Lt�LvX�W<�	LKb�Kh�Kn�Kv�K�8���TK������|K���H��H�WX���XP��l��FnL����Ke��,<���
T.�b�c�d$Le��h�el��np�r�et�ev4L�XL�p7(tm�n�����e��(��A	Ī.��kdLv�A��DL�\�W(�_����lLz|���fe�D.@t�����Le�Lð�	W4�	�Lb�Lt�LvP����L�`�Wh�WL���|�r<�
�c,Md�fg`Mk�fltMm�MnXgp�Mr�Ms�Mt�MvLM�x�t@Me�����8Mt���X���_���XMt�����lMigp@���gd��f8ggHgtPgv������Ma�go�g�H���`����Mal��x����M�T����M��,�	�MrNvx
_8���
Nu
 Nt4
��,Na��l<�rX�v�
U�
	PNg�
��XN��
��dN�T
	��b�Nk�Nl�Nmxip�Nr�Nsd�t�Nv0
�

���No�Wt�
��
tx
y�Ng��n�Nv�
	�
	�
t
��
	4
	Oc�l$On��r�
	t
	�lLOn4�sTOt\Ov�jz�
_,
_<
,�
��t�lxOrP�sl
��Oi�
tt
��xkk��p�kt�Oz!
f�Oa,Pe�lf�lipPo�lt�O�K�0!
��kb�kg�Ok�Op�<
�L!
��!
���Z�dP��l��l��l�m��P�ĉ��"
��P.$"
� PlDPr#
�,#
��<PeTO
V
$
��PPe�#
�XPl�$
��d
��d
�xPl|0
.�Pa�Pu�P��%
���Prh0
��0
��0
���P��P��0
�@1
p�2
X2
�Pd�PrQt�3
�p4
��4
	x�t�7
	�ng0Qn�nrPQt�z�8
��nh}1t}��<Q�D9
��DQ��;
��H$lt;
�
\Qb\�d�Qg�ojRk$RlHRn`RptRr|Rs�Rt`pu�Rz;
.hQa�Se0WidXk�XoLYpTYs�Yt�Yu<Dz�R�<Y�l<
�8<
���QyxIC0�z=
���Q�Do��<
��Rc�Q��<
2P=
���oe8Rg@Rl�=
B�=
B>
.XRtl�y4>
	l>
.lRo�d�?
�h?
�(TP�?
���Ro�?
��?
/�Rb�Rg�Rl�pp(SrxSs�St�Sz0@
���R��V�X��X��X� Z�8Z�\@
y|@
��@
.Sl$Y����8\�@B
SmLB
��SaX�fhSkl�npSuTS�xB
e`Sn�B
��HS���vL]��B
�C
yC
��a��D
0:.�Si�D
���Se�Sy�C
��Sg�SjTkHTl(Um@UnPUplUr�UsVt�Vv�x����f[���	0E
��Ta T���E
��T�h��4T��hC@Tv�h��E
����ahTe�Tk�T���E
`Tg�TmLF
TF
��xTb�Te�Tm�Tn�TÀF
��F
��F
���T��F
�k	U|G
BHH
�H
	�Td�TrUz(H
���T�pH
!	Pl%hI
GpI
Ur|I
��Ue�I
��Ub8Ui8o�J
���gl��pJ
�L��J
XUz�J
��`Ue�Uk�U�|�Š�U@v	�Un�J
���U�hR��N���U�<K
j�U�$L
�L
���Ue�K
�Ut�K
���Ue�Uz���l{.�Utt|T��k�L
��Va4VehVoXV��L
��L
,Vlh~��T.K	@Vr$K��LV�pV�<M
�xt	�rhM
�pM
��|VeN
y�M
��Vg�Vk�rl�Hp�Vr�VsWt�'�N
���Vt�P
������V��O
���V�x�e��|����VzpQ
%�Q
��We Wk�M�S
��R
�(Wd|sfhWk�l�Wm�Wn�Wo�Wp�WrXs��td�z�S
���boxWt�B�S
�8T
�DT
���Wa�Wd$�f,�g�WtlT
Bx�(����W��T
���W��T
<����T
�U
���Wa�soXt��2\U
���V
�Xj�srHXvXV
1dV
��,X�TX�\X�pV
��4XÌF1�H1�V
j@�	�X
��lXtPX
�tXk�Xl�Xphtr�Xs�XtDY
.�Xd�Xt�Y
	�Y
	<��[
� [
��[
�4[
��Xn�\
/Yl �n�tsD�tT]
�`]
��Yd�t�n8^
��Ys�]
�$Yr�]
��0Y�lZ�T^
j�^
.��ulYňI����dY�l���x�s�a
	xYs�`
���Y��`
���Yo�Yr�Y�l����s�Yz���Yrğ
H����b
���Ya�YiX��T�9�b
��YlZt�c
d
���!n�c
�Zr0ZtHd
B�d
��ugXZn�
t��z��
�@e
��PZn��y�e
�dZz�g
��̠l�f
xZb[d@[gX[id[j|[k�[l�[n\p(\r��s@\t�zh\�Pf
���Za<]b@�dH]e�af{g�ai�bk�bm co�dpDer�es�et�eu\fy�\ðd�lh
P�g
��[o0[t$�ô�	Lk
	�j
��8[gP[r�k
|l
��k�l
��X�k�Qttm
	�l
��t[c�[r�[t�n
�n
	�o
��<�j�[kws�[t�s
_�u
��t
���[i�[n�[t�[y�v
_�v
_w
.�a\�$w
*	0w
��\��x
_Xw
��\sy
��pwc8\g�z_�{
T\o }
8,}
L\m�]TTg
��`\�~
	8�b<6g�\l�\r(]tD~
��t\�h`��b��c��c�f�f�́
�
���\l�
1�
�\d�\mL�
���\a]irv]�Ć
1��
1��
��]�\\�H�
XČ
p�
�� ]iԧr�v�
���l�
	�a�]d�]g^k ^l<_mX_r�_s@`vT����	t]n��
��|]�l�
���]ì�
��
���]a�]e�]oT�
�`�
���]e�]v4�
�]l�]r|�_��
�ؓ
#��
p{.�wiP�
���]e�
#8�
��^ad^e�^f_l��oL^�_�|�
t��
��D^��^�\�
��
\^l�^m�^r8!Bȗ
��x^�|�
���^�l"tl&_�
	�^k�^rH�
��T.��e�^�0�
<�
���^�p�
�|�
�^g��
���^o�
��\�eܙ
	�9ltr��
��_��
��
(_l��
��0_e��
Tt_d|_gğ
��H_a�_e�_m�_��
��tt<�
eX�
���_�0���
�_g�_l8�
��[ L�
���_oD�
$k8�
���_i`t`v,`z<�
���	�_n(�
���_��
��`�9
_T�
,�
.$`m��P�
��8`i��
.�o.�ye�n�`tz�L�
	L`g�j�`l�`r8asDat�av�4@�
,$���
�`n��
���`e�ziat$avt�z�`�a�x�
�L�
���`��(��xW�A	�`t�A���`�$�
�0�
��aexÀ�
�
.bl�
��,az��
��P^b"nparda�t�
���
��\a�De<�
� �
xasĶ
���ae�
����l��rl�
�T�

�ad�ag�akbmbn bp4brLbspbv|bz̻
.p�e��À�
`<�
���aoԽ
�о
����d\f�ZtL�
.,ba@�
�x�
��Dbo��ô�
��
��l�s�]zD�
�P�
��\b�,�
��dbÀ�
��<����
	H|r�rv�
���|l�brX�v�bń�
X�x�
���b��#	j��
���bg��
�bg`�
���be8�
L�
���b��
��<ci�0mc���
ckDcl�}m�rpdcr��
P|�
�Tca\cv\�
�,���
����d��g(~i��m�cs8�
C �
_X�
	�cc�ch�cn�cr�
�(�
���ca�
��h".t�
���
���c�d�
���c�
.��
		�ckdl8dmx�nddr`�spdtxdv��zh�
.��8����$d��
��,d��=��=	DdlL�
��Ld��
��Xd�
���
��
��h".�db�di4*m�d��
	�drȶs�dzP�
���d�Hf�Pi8��
p�
�h�
���d��d��o���
,,�
��ضeef��l�rT�
8����
��e���
�� e���
,ef�
��8ea�eole�4)��
	Xefp�
��`e��ie�h��xe�D�
���e��
�em��
����l�m��p��t��
���r|�
����v��
�edX�g4Jj�enx�r��t�
.P�ah�d��
�0�
	fs��
	�g8fr@fs0�tX�v ^z\�
_t�
	�
	8z�R�.Tfe|fn�t�z'��p�i�C�h�fg�fl�fr�ft��z,l���y�o���e�f�4��`p����$��xy������x���fÄ|�����t���.�gr�g�d�
gd�gg�gj�glhm`Enhr$hu4hv@hz����ga�hb�hehkg�killk�ln�lo$�pms�mt�muxnzth��lŸ��Д���g�|�vd��X����gy��������g�|����gÀ�`�����ga(8k�g�4Bd���g�$�t\�	���hc ��d�\����,ha<��|����]o���HhlL�	Thl�ht�hz����`h��j�@l��l��l��m��m�̪����aPhk��rT�.�h�������<����ho����he|�����hgii(ik<il�in�ir�is�jv��H�H��� it@��H���
4ialiftii|il�n�)o�is�it�iv�i������8-_,�BD����i���_��_ܱ_��d����ig�ik�it���H��P�����ii���cHp����ib@jfHjiPjkXjrtjt,jì��8js���� j��U��v���H�H�� ���`j�P�hj��\����jeȺ�p����jeȖr�t�j�h�	�jg�jr,ks��1�����j�p���h".ki�!r�tk�(�1�����j�T�.,N�8N��ka\��� kk@kp\kt�������Hk����PkÌ���0,r��.�*at����tkg�kh�kk�km�knls$lt0lv��1��1@�1\����d�ked�g�kt�kÄ��t�������k���	�����1m$
o	0
��l�l���lŘ1l�_�	8lr���@�	Lls���Tl�<���x�r`l�
�����|lyT���lk�ls�������	�lr���$�	�lrP0sP�tXUH,�	�ldd����l�@�����kH�tmz���@ma��tLH ms�<
��(mat;
�4mk�
��Lmd ���Tma4�	`mr@���lm�����xmàm��
	d!.P�
���m�����mr��	t�	�mj�t�	�g�mkHnlTnt\nzh���D..nb ne(nh0ni8nn@nt�yH����y�����y�y<�����jl�_|�_������dn����ln�E�HC
�ndog(oj8ok�lDonXor`oshotpoz�B���na�b�d�oe �fsg si�tk�to�p�us0�t�vu�vz�o�Tu��Eprl�E��oa oe��#G�4�	pH��0orHN.Poy�N��P��QXR��U�U	T�g��l�os�oz�U��xo��r�`t�u��v��_����v�a	�d.pe�c�oc pd,pg<plXqmdqn�qp�qrrs(rt�rv��x�d����`e��p�pe��p��e��ry�q��h��	8�ap�dhpe��f|ph�pl@qsHqv�pňi`tpn�!�j_�k_d2��k	�pb�pg�pi�pjqk�_n qp(qs0qt8qv�p�l���p��Y��1���p�����3�3��3L
4��q� 4��q�(��0l��6�7�\l_�l_�m_m��Pqb,p��.�qz�qÔOt�p��xq���_�r�@s���a�qi�qo�q��q��st�s���q��vtdv�qn�qp���Pw#�w��T��r��x�l{��y��rzt|#�|�� raHre<r��|�����<}��g|rk�rl�Q��	\rh����dr�t���pr�`}t���,��	�rd��g�rk��l�rr��_@���T.t�c�ri��m��p��tt�zD�XȏC@����r�T���<�r�r�Ē.��o��sd��gȎl��mXsn��o|sp�sr�ss(tt@tvLtz�����dlsg$�t��	�`����tsa�s�(��4����s�@���se�sń����������s�������s�sz�sÔ�tP����s�|�t��.�sat������t��PЛ�� ta�L	��8�����4tô����m�o<�zԜ	Xjxtr�v�zh�	X����Yhԟ���tc��tr0����ta�l4�rL��d����ta��dt��tl�tp��r�ts��x<�x��@����tzг	L�b,uk��n4ur��s<ut��v��4�	��	�	(zadur8���Du��v����h".$����j�urԻ.	pua�ue�uivoTvp��shvu��z�u�4�9�����b�ur�9�9����ukp��D���un����u�$v�Dv�|v�����
9��vd���4vk<vr
��	��������Lvi��9|��`vg(����tvr(����vg�vjh�r��th��	��g�vr��s��t��z<:_8�	��	�vr$��<�P�va�����vs��ĉ.hwal�bx�c��d��e�f��g��i@�j`�k��lL�o��pЬrL�s��t��u��v��yh��D��̉�wbxc$xdTxe�xf�xgyi@yjHzk�zl�}n<~p�~rԀs\�th�u��x(�y�w�L���0P�$�����_���P4����woH����wrt�E.��.�wixo����xhНRh����r��	�lz�P0xe����<xy�Hxg|xl��r�9x�#hxk8���pxe����p^r@����r�xy����xk(����x�y�h�.�xo�x��x���y��	�Nl��������x�L�y�8�xr��.��yd(ys��.t���l��z����0ya�yezt4zuxy�z���_��	`ygP�r����hy�L������^.hs�����yyP��yg�yl�ys��y���ym�����yedk|�m�yn�yvT0_L�_��t�������z�H� zrL���.@za��(zrx�����t�l\zrP�v������t���	����dz�ltzd�zj{kxSl{n({vD{z���
�za�{exUfT|i�|o�Vp<}s\}t|{�}ŸB����z�L���z�\�����z�����z�. {y(,�����0{� ��8{�����P{r4	X{bDTl�{r�{td��d{�H|����|��|�4��4	��d
o�	���h�{v�E�~h���{y�
�{g�{l(|s�|�{m���{e|��
��|���
� |eX�n<��4|i�	<|r��Udh|nt|z|���j�ttp||l�|m�|r�|s4�_H���|l���t�$���|�0���|�,	�|r|C�|k}v�����_�"����p���T.�	}nX���$}����0}��.`H}nt��P}a|}e�}i�}���t}m ���}p� 	�<zD���}�ķ���}t;��}e�9�}v02���}e0~y~��	8,<���}b�r�;	�}v$5��~�K	��.,K.$~a�Z����lL~r�g��k�|��kT~d�~g�~k�~l�~t�~v`k��`~a@eli�k�m��o��u���l��~y�l��l���~��l��~�m�hnto���<oy�~cLo	�~c(l0zxo���Pq_�r,�wt�v8mXp� ` ��Pe{t�zdd�n�z�{�|�����{���Ng��$J_(}��m�o���D�t�}�l�}���a�o�Q��l���	�l��������8�ip�u����$�0�n��	8��	��D���(��L��$d	��X�k�b	d�nhI
����e����|�k����k��s8p,�n����z��t`���tЎ����̀k4p�t<�zĒ�В���a��P+
�����������O.l���vp���$�� �.0����H���H�it���P�r��P�rL_s��t��I����|��8�����Ì�# �����e��i�����T́kԁn�t�����$���܁��������<���������������������La�_e@�	
��b��cԂg�jP�n��p@�r\�sp�tX�z����8��P�����P��������8��D�8������rl�4�����h�����	��p�������@���Ȃ�D����k(����a,�e��0����lXq�hq����4���qx�et2��)��<��T'��p�k��tD�Ø2T��r�2��d�a����2���	���	������	�5��dԃg܃n6����a��e�i �o��(�	�(6�D6eX6����,���6h7�47�sT�	���	�r�7�8C �mpC��4�aP��\H��\�t_����s�uv��h�a��eԄi�m�o��ôwt�w�����������x#�z��TKd�y	��r�{�`{̄s�|f�|�����|����t}t�|�l�e�_@�	�g����$��d���d�z0��X�<�s����L�iЏ����ha����̟l�������p������LO����r���a<
d��e��f�g�i,�j�k(�lh�n�r��s�u��v0�zd��iaP�b�cX�d`�e`�fh�gp�hp�ix�jLjm��n��ptvr��s�	t��v<��0������i�����#�#X�#��#���$�#��#P�H�#��#����d��l��#��������rd��H�.̆i���X���Ԇy���������\����i���n�#���$�at�e��oԇt�uL��|��|v�������^,���`�y8h�g��l��r��s,�v�����m������z���z_(	��s	���̇r��D	���
����b��m�����e�	���t����
 �a��e��i�l�o(�pP�tX�uX��<��H��X��,����������`��TX.tx�c��d�?g�hԈl��m@�nX�rl�s��t��v����z`% ��v���Ȉe� �h �l�n�tL ���e�
l4��8E�He��eQ� 	 �s!��(���!���g�n�"`l"��P�e�l_�"��
d�d��e�i�k�nD�sP�tt�z��$�Ť�y�"��d��g��t�"8@m���*t#�@#	̉sL#��ԉ��#�#�k�m_H
n�#���i�#�$����8��0�0�n@$��8�eT$��|�eh��8�n��`��l$�pq��$|�s��t�$����e��t�$�P�
��t��k4%����e�ä�
�%	Њl(%��؊��uP`%���e(i��4v@v�����%. e�%	 �dP�g��kX�l`�ph�r�t�%,',�,(��
T.��e��h��i��j��knLsȋtЋv�8��'��k��n,(Yh(n�?�(X�*��)؋t�)���el*�bd*8-�t0p1e��_�1	$�gH�r82��,���5_�7�9�9�tN���te|�n��s�W����a��e����W��Ժ��WT��v��	�d�	`Z����c�k	Xs��Člpšb�k�s��،aD�ep�o,���t�`wtw���l�v	�l�vr�v�����{.h�y�z8�g,�_8���P�d|�j��n��t�X�lL�_ [
_�f_����m��r������eč�\�t����������"e���Ѝ�����؍�8��m�����Ŵ���T.0�	�r<�����@������H�s �� ����P��	
��kp�l��m��nܸp(�r�s��t��v��z��T��d��gȎlDzsXCu������a�e4�oX�u؎�P�t؏t��t��e����Ў���L��`��P�g�llr����Hl̻_��	�kl�r����,�l��r0���	D�n0���t������h�aЏe��i�o<�pP�t�����0�,��	��j0r�����������������\��\�xg�k �lP�m��rp�s�t��z�__�����k$a�8a�������4�l��D���a_�(��<��L.�b`�e�c�m����h�e��z,�vt�nT����a�	��v8��h
a��gPnXr`sؐv(�(t�k��sH��t	��	�r�������H|�(�n4	��0�e@
�
��H�r�
e�
��
d�b��d��gđj̑nԑpܑu�z��l�a4�e�iD�o\�u��HG	���	��t���, 	�c$�l<����Ȓ�<��L��T��d��`,���,�cl�d��k��lfm��r��z�!	B�!	��X���!	��`��ĉ�D'	��x�.�C	��tD���������Ä�D4	�P,\	��kؒr|.�Ä;	8�;	����T����n�r$�s,�t4�z�t�t���t��$�x����<Cp�l�o_ ��	x�g��l��r(.��eȓid�oܓø����y� �#��"	��s4#�\��ԓ�l�����#�����������`0@�fH�iP�lX�r�è0���a��cؔe|�� ��d��h1��1�x�%�2	`�d��m��ä2��h��H��HW�������%�4��4��m$4����e8�7��Ĕep7̔g�
_�Ny�r|N�r(G����o(�z$RhQ��l�Q.�ad�eH�àR
T�r�R��<��(S	�S��S�\�gt�lHT��}
��Z|�u�Z����aDg���Mk�uPĕe̕o�n����s�u�v����(���ԕi��ܕlw���e,�iT�l`�sl�y�hcH{g8�kP�mD�t���aH����ax�#ܠT����L�iL�����h��P
��0�ô���t�l���a�cX�d��eؘf�g�k`�l�m$�n��o@�pX�r��sȜt4�u`�x���&�$��a�eL�fp�h$�l�o8�pL�r��s��u��v�Ü����x���������\�D�aXZt����0�ad�È������\��t���_,����Ď.(o|�������z؏���_���������Зe�����tԒ.t�ؗl������ț�L��ܘ`����h8�i��t������h��� ��|�,�t���H�D�n\���L�a��rt��؛t���l������K�������Ì���l��n� r��sИt��.��.Șet���.�����l�r<�� zd����a����l$�rP�v�����A����p���4�i����|���<�c��d��k��l��m(���D�a�eL�i��m��Ì�����|�a���n<�t������0�	DTlؙrP������8��������Լ�c	̽���tؽ�l�n�sgt�l��zTo��8d���$�t4�	,�rľ���D�d`�mh�nؿ��t�����p�d��k��n�Ss��t���x�a�o�u̚� ��<��\��p�e|���Ě�����ؚs@�>p����e���`dg������s ����a<�d\�o��t��u��xp���	���	��H�z��P�s@�t���h�����8�̠rT�l����(�L$v�9�a��e,�o������k��rx�
��؛l�r@�����
���
	�pp�����8�������$�l��I���� �lP�r�������\�oh�s�mx���l�c�p�rP���|�ah�p��t����r0�	����d�l�����a��i��o�u����`����a��jH_@��	�gD�rP�t����d��ܝ�ܒ1p0�k���8�aD.�\a��
_L		\�ht�r�	��TKd
<(k��n��r��z�
t�
���
��jP��pНs$0��ȝz
	� n
_$
	�r0
�����tT�r��(n����i (�m�(tLD��D�a��e��f��gȞlОo�p�s�t$�u����żD����������؞�,��4���J���@L�`N�tR� Y�`Y�tY��Y�����Y�\a�8����zd�d���i�f��f��f���_	<�r(��D��������x�eP��P�tLp�l��`!	��r@8��L���=���|7��؟c�i�t�����.ğa$���̟s�:t�:�n�=0=����r`>�؇������z$�	�s|V��(���P��4��|�m��L�s@���T�i��l��rL���$�����t�����k��n�����aȠeܠi(�o@�u�%��V|�pؖ���Ԡp�st�t������������<���(�\������À��n8�t�����D�k8'
	x�a��gt�h��k\�l��n�rD�t�1v�'
��m�9
	��c̡lԡr�9
���������t���8
����eh�g��o$�u���:
�$:
���qk�o�:
n�<
�=
t=
�r�sH=
t\=
�d>
�t>
�rh`�t`�0�tD@
.8�aT@
��D�z�?
	P�s�?
��\��d?
��h��C
|�l0D
����aآe�i�k�o��ÐE
	�*l̢r�E
���������E
��̦r@F
�H
��gpH
�n(I
�hI
jK
L K
���a�J
�l,�r�K
t�t
.�\
4�n�\
��<�a��ep�ôN
��H�t,^
t]
��h���o
�p
��|�a�olo
��d�k�lL�n`�s4du�o
����a��dԤe�df$�iP�kX�o|�s��tܥy���p��l��|p
(�p	8Hp	��p�p
���a$�e4�j�p
�r	p�p
��,�aD�o,s	pq
.dyz	,�q
��X�zXr
,�q
	l�r��s��t�q
��t����H��������r
���ss
_u
ĤrT�	v
��u
̤g�lldr�s$v
e�v
�dw
�w
	��k$�	P\y
���oy
�k�3m@�n\�s�dzpy
�z
�Lz
�z
��rl�s`|
��h��p|
��t�|
�~
��~
��n}
����i���p
C̥n�}
�����d
�؀
f��
.ԥa��
t�
�dh�k|�l��n��t\�
��
�a��eЦgܦi�m��o��r��s�t��Ô�m��y<�z�	D�s$���P���
��\��|��P�
��t�a|�
�(���
����o�
�h�
��g��lȦr<�
�̎
t��
��N�Đ
L�l�oh�
�L�
��
�l(�mD�rP�sГ
����e��i|��-	td�
Bp�
��0��|�
��8�ň�
��Z�� FÔZ��X�t��
	d�r��
��p���Z��Z��l��n[t<�
4~l,�
���� ����_<�
	ħlL�
��̧���(��Ț
���eا�<�
��
��l�
��PZdܜ
	�rX�
���
x�
0�sh�
��8�o�
	`�r��sh�
���g�
��
.l�a��
��t�z\		��l̩n�s�#���	��a�d �el�i��k��o��t��uԨ�x�����X��x����������|U�PU	�kX����������@�E.�	��,�lH���4�eD��@�b�	L�st�� d�v�+�\�j� #� �!	�/n�!��r<"H"#�.mةe)����n�	T�?��?�����?����,P	|�al�b�d��e@�f��g̪i�k$�nh�pP�r�sT�t��zT�ØP����\���R����������h���S����l|�rTdW�l�r��s`Y.�YlFh,�t�\��d�l\n��rl^.^Īd�g��tL�#�z�^���a0A.�_��t�m	tTl	�g0�pc���a�u	���Xjht�g<�j�g��D�ap�e��i��àj�|ih�m��s�j.pk_0k	��k�h�����ī�(lT��s�o�ثôo	��l������Ы��s\p���l �t0�z��Htq����Dv�Tv���a��r@w)�5lD�pL�tT^
	<rC�x��خrPz.(z`�l\�	T��	��t���}	|��}������{����Ř~���3l�Br��H���s����Ĭa\�e��i�o,�u ���#�����a|qe<����	��m�qnH�s(�������� �������4��H���"���kLP�sL-���P�����,��h��8(	x�n�@-	��n�=ȭh�;��c��gЭpحsܘ>
TE9�Fl(l���a�e�j���ta��n@l���z	�c����s��#����8�oh���@�h`�px�t�
����o�'
������%
��x�rl�ðl
�f
��j��k̮lPf
����a<]b�e4�i���l
���[r�o
���[j��
t�
خf�m��
.��e�
�L�
	�yrD~
����L��`��l��̻
.p�eT�
(�g�{nx�
	��
	D�s@�
	0�
	X�t��
	�g��t ^z��
	��gԯr�s��t 4����ap��������e�+��t,��ȯa4/�sp/���a�e�i0�z�2�P2�s47�T7��������6.H�o ���9��9�@�l�
�Tz��
	T�s��
��`����
��l�È?	x�k��s�<�����;����Ü�
��?����p�C	�j�r,�z�~�4~԰l`~��ܰa�N���t�[�p[�����Z��̦r��thP�g��$�s�h	h�g|�lP�mnȲr��s$�tP�zTl�,l��`�y�o#�o��t�a��e�l�o@�t���,��`p��P�������p.�p��cԱm�p�v�c���
l�i��u�,r	��j�kLr,ps��s�t#tt8�t�t�� ���tb�t�v#v��H�a��e��ot���Tv�`v��l������v��v#w�����w�4�tH�������y������x����t2
C�l�z��Բ��z����8z���tX�
8�
����(2
���Ä|��x��<�Ű}��}��4����~��H�l�	��d��f�p��s�t�z������|�r���h�r\�����rسzЌ܌��l������e�����r��.̳eD��0����r|���������L�����,�i�o����А$�s�!đ��8�t��	@�lH�
CdT��X�����`��Ԩl�s���x�oL�	��r����������������ĉ. �a�b�c�d@�e<�fX�gD�h�ihk
lTm�no| p�!r�%s)t *u�1v03y�Kz$��d�̉x�b��d(QeL�fh�g�j��k��l�m4�n��p��r(�s��t|�u��v�zx�Ì�_H���p�c��r�CsP�y4��<��gȵpеth�����a4�m��o,�u��\�8�����a��b��	,�s����������	p~i �s$�,,��t8����a,�p{.<�k��p�_����D�f��l�.@���\�a0�g��r��yT��\������<������h�.h".l�u����U(����������	ȶn����ж�����X�k�Qtܶ������a8[ctRld�nx�t��v,��$�	��cH��� ��<����H�v(����P�i���X�a@�p����p�u�	��t������������l��g�t���	��a�[b�cH�e(8k`�o��uиv���@Q4	�f,�v ��d�����	�
������E�\h��4�e�
<�gH��dVepT�mt�p|�sX����8!��d��l��s���6�P!����z�"8�"������"��ĸ��$��$��ܸ��$����#�g$����aWc�p�l	��1�b0Wl02��$�al�c��dعg�i�yĹ��6��a��e����5��X�s��	��6�x�l�	B�	���g|7�����7tP8j�;$
$5������=��=��йa��	<A�m�K�K	��g$�t,K.�a4�eT�ÔL� N.�M_,�l�Oy8O8@�r�L��H��h��p���QU�Q�L�����XZ.кcغsĺÜZ����a��l�o�p\�r�����	�����LZ�$\�]	�[n�Xt$]������h���d�k f	�X.0�b8�k@�n�Wv\�
�f� ��hh��H���g���oP����t���l��hn��t��k��t`k����a��k�����Lo	|�kػnxo��������qG�q��лy�y� y	�n�}j��(��������p��g����eT�i��p��s��t��zt��H�t��L�s`��8�	`�n<���h��|����5i�dr��Ptle��������zL��X������В�adolrtF	8<
��Լg���ܼg�n �.�aD�ep�t0��>
�$�yT���gxSsp��� ��������<�s�`
��	P�r$���X��ԝ��d����8�|�d�[r�Gvt�����a��e,�h8�iX�oȽÐ�	 tl�rĤ���������Хؽm��y�al�D�g�p4��P�.��	�shn����$�i���m�	���D�dt�L�lp�mj
�H���h�i���k��l��s��#,�#���d���n̾r�t������aԾu4��X�`��Don�t����ܾa8�d@�eT�g��m��o$��\S#XR���e����`�|��Կ�,��@�nL�r@s�T��TA�`A��\��|���d��Ԝ	p�t\e	jd�����sx���st�����uP$y����d̿n��y,��@�	P�b��c�d��f��g�h,�jl�l�mt�n��p��r��sD�t��v��z����ܿ����0�<��X-��/�p������H�apbrd�Ø����`�t��X�B���4���|�i��s,7P87����a�����v��.��a�e�iH�à�t�������l�T�lD����aD�i��d�Ÿ�th���,��0��p��T�_��	(�lЋ$\�<�aT�s����������\��0�t��#$���x�o@���T.��a�g�i�y�� �����k�t�zH��d��H�X����8q������a��	�	s8��D������������0���$�a�eH���B0���@��t���
�X�p���`�a��c��d��e��i��l��m�yP�0����o�tl��s���s�$����.��u\�Ø����|����o0���`�.0�a|qeD�����(�ll#�4��<��T���&�'T��c��l��p��tT'��\�a��c�d �e<�gP�i��o��s��t��y��Ü(�T*��)	��s�)���������������,.ue��i���*����s�	tL�	��,�����	j`-��g�0.(�.x/��0�y(1��0H�gh�op�sx�z�	$�1�`2��3��3��ld4e�5 5����z6�r8t08��8	bn8�r@�s�8.��aP�e8�iP�k �ô�<����d0�z�;8�r:��������
� =	L�jh!�� ;�
��D��8C
��d�bg��j��m�bn��r̩s��t��v��zpC��
\�a�eX�i��n��o�s�t �u��øcŐG���o�vt���4H	 �g�l�z\H�����L��������c��������H_(�,@K>m0�s8�t L�$�M��X�elL	@�t�N��f��px�s��z�P_hP��p�k��mhG_�Pt�`�R������Q.����Q����y(�t�R��l��p��sT��T	8T����t��z�TC�r<U_�X����r�[��.H�dP�gD�hp�l��r|�s��t\�h\�8�P��X��|��0��`��8��\�L]�0]���a��ut]��]	��r��t^�t_��`�.��aee��i��o��s�et�eu$�zD�����8_@dd��l �t�a��a�����a����$b	�dg`�rp�s��tLb��,��,������e��e��b���da,gn@cW��Xpc��x�o�ftdf��m�s|hder�i���r\j,k���e����l�Dl���������kf��Àk����zo� o�,flX�sL�tX�vXo.�a��e��i��k�ol��}
yTp��D�l|p��p�`�l�p��`��Ĭ� ��8��Ԭ��q�Ps��fs��t�s�t=(t��������tj���,�'|�����z��	��s8��@�����a<t����tLt��kX[
B�t��l�Xn�ty�t�0�vv��`�.��a�b0�e��f��g��h��i��m$�ot�t��v���T���g
��u��d��j��llv��v���a��j`o
Pw	�wC��l�r�t�w�����h���g�0��D�����x_@x_�x�����x���o��
t�x(�gH�lP�mX�n,y�@y�Py��z,�y	`�lx�r�z.T.��e�
n����z����e{4�
�,{����a`{l��s��z�{� |��6	���
������|�����0�
n<�
���at}���d�|�l|~C<�r�~��~j$�0��L���
�P�
��`�����h���/P�l�����i���$�����a���X�1��������T���d�k(�l0�r8�t@�v������a�e �i��s���t��`���P�n �t��	Ј�<�����pUtĊ��
T.��e��h��k��n��o��r��t��u���\�	H�r�s|���x��\��d������8(������]p����]̋X���^,����7XZ��sX�����o���.<�aX��gD�m��pL�sT�z�����d��܏���T��ȑ�ԑ��l��<�.��p��t��u��z���$��4�����a��e)
����`vgȕ_0���������ԕ��4�������������e��l��rL����(���������$��,�	0�ll�rH���<��:�d�X�m����`�e@�#\���x�a��etoHh��#�����a��iTho��u��#��#�5CAg�5������5�����(4���p�3.��a������s�O�uLO���aL�e��i�r��t4�z|��|n���Jl@i@�l\�p�ujP~,T|	d�k��r|`��l��ԃ��T.(�h".4�����a���x���n0}	���	��b��h��r��t�v��������T��0�	�$��p��� �e��olR��#����ܲ�L�.(������a��b��c(�d��f��gt�h��j��kH�l�m��n��o�p��rT�s��th�u��v��y��z��Ð�.d���c��g@�l��.����	������t������e��T���tP�����e�iTn,i��h���z�����d|�z`��L��T���<�nl�y��	D�n8���T��d�G�t �.t�e��i��ø���_������,�t<�_T���������Tkfd��X�����e�o �y���������������t��m���H�.�eL�o<��4�����4��T�����D�Q�h��\�s��t����d�e���i�؄��������_�����tp{.�wi��k�	����e4�l�Is��p�
|������	L~j�k �s��������������8.�Ph��,�iĉB���@�.��a�c�dH�e��h��i��j0�kh�l��m��nLo��s4�t4�v��y���8��hH���d��g��p��sx�4���	��l��rH������������ _�I��G���sD����.,�o<�üTnPU8$Q��4��t�nft�g��k��m��n�s�v�[����l�i��y$�ĉN�����.� �!�����L ����i��k(�mloz����e��.
�!.��d�g�y�!VT$���e�"����t`%��%	L�g4�sH�D
)��
,�bl�e��h��i��k��r��s�t(�z��ØI^�d�k|�n�Jh��r�(	��n��r�(����� ���Kr��D
ĉ}$���.8ND
�d������
�����T�D
X/
�p/
�����P����p��$)�@�.p�vd��P��)}�)��)��H���)��)��\���)^�c4�l��s��tX*��x�e�htitĉ
l*��.��k��n��0+	<+����tL�����.��e�Ô���f���������4��8�������,��$���.=�.��<���.��D��$.P�n8-��\�e�/8�/��t��0��|��T0��Īe��i��Ô�	D
80	��nD0�����h���1�8�V�1	��.X�b��c��e��g��h��jX�k�9l��m��n��o�p0�r��s �t��v��z�����82��������l���D�rd2��L�bx�e��i��o��ì�Ő�	
P2�X2�����x2	
�2	
���������,Tt3
�Y	
�1�������t����t�3t�3	
�3����a,�o��D���f�|V����4��,`	
��t��{��<��ĉ� 4��P�.��a��b��e �h(�i0�k8�l@�nd�rl�tt�u���X�Ŭ�	
�D
8���`��l��r��t��z`�^��^L�^�3��3	��.�n�s4�����H��|��<�rX4r��D
<a	
��D
p4	
��D
@��x4��4��P���aD
�4D
�4	
�4��4��4��P�.��a��e��k��y����4	
c	
Pc�5����������	D
5�t5� 5.��i�d
�5	
��	
������(�t�5).�5��$�eh�o\��x�Ťkr��	H�s<6��P��,6	
ln��n��p��`6��P�.��o��z���H6�P6��������x6	
�6�4w��6)��a��e�i��Ð6��6��6����������6��6��w��6��P�.<��`��~
��6��4��P��l��P�=�
��y��X���6��6	
{�({��|�����7����e��i���D{	
�{��6^7��s��z 7^(7Q$��0�������7���z��Ŭ7.��.,�o ��,��8�����PX
��7����.X�e��o�����D.N08P�.t�k|�m��s��tP�
tx�th8t�8���r.��np8����tP�
09D
9	��b��k��l�rr��t8�����(�D
(9�09D
��n�
8h������:��9	�l,�nt ;��
T.d�bt�e��h��n(so�MrԈv��ð�ż��;�<l�n���0<	��t�;���������8�<��������h=	��g�<.��e��i���$>	��dp@�?��nD?����a,�b\�e��l��o��p���`B���e<�l�BD|�k��lp�Ô��@CD�l�*	e *	��h��hD
�D��De�D������F	�:slA�����TR	��J��c4L_<NT(�d0�r8�s@�utN��
��a|�d��eH�il�k|�n��o�s(�tD�uX�y�z\���NtPO�pOt�O��O	l�ll�rt�t�O��H�� ���O�P�dQ�St�R��g��r��s�v�S��S������S����e��g����	��SQT,�S���z��}t��z,{����e\T.�n�t4��pT	�k�	8��	��,��4VmDV@�d\�nd�s�VtWtxWj,Xp�W��t�i��y���\X��W�����pX9�XP��a,Y��X��p��r��sLYt`Yt�Zy�Z����t�Z	��r�Z�����`Z�� �s��Z�P[4�r�]4�	tH^<�r�__�^.P�el�ht�v�`�a8pb#,b|�b��c��f��k�um��r�ut�,
#�b#Te\e��ale����i�d����g�g.8f��rpl��d �rj����eH�iX�o<��m�p���m	(�s�k��0���n�`p��oP�sXs�psd�bܻd��j��l�vnd�p��s��u�s��l�a@�dT�ex�i��o�u���Řtt�t��ut�v�(H��v	��b�vgؼl�r0�t�v�������ص���� ���w.(�a�wn`x�hzptz��8�e4
��z
L�bPwd��g8j��l��m��n��r��s��v�{���eh�y�{h�	<k�������|�����`}����l�^,�}����y�~t��~����z$������	�j$�k(�lT�p\�td+�8�eH��X+p�1���@��ȁ_������t ���|>aPg��h�nhxp��s��z��#��t�b����g��r��s��t�p����t�	��k�Un���x.�kyv�z��������t�	4�Tt�r�	��g0�lp���P'dH��������@������.�c��d��el�izk��l��n��r��s��t$�vL�z�����������`��������b��i��l�m��n�r�t�����(���D..��d���\��������b4�eT�l\�r�*tH��`����,�t��D����@���
�$�Q<��d�k��H��x�gX�����eH
p������i��P����8���
	��.��g�
������
D
����`�.�epmi"j+�n�:
�(:
�����9
���À�Q���0�k`�sh�tĝ.8�ep�ltzm�K
��L
�\W
8����k��l��n��p��s���x�a��e��r,�t@�uT�ü��Ԥ����\�����y��Хt�
~4�����y���g(�tc,��	�nܩ
���������� �,��	4�kh�l|�r�vp���<�����T����.t�e��
p����T.���̮���(���C��r�_��	�k��r�������,�
8�����t���,��ص������8l������l�������
���b���bX��� �e���4�8�t�,ط	L�zx�#8�`�rx�s�.(������c������i��o������l���$����_ô�#������o�r���l�	(za���������	X�bp�c��d��g��h��jt�k��l��m,�n��p��r�s�t0�v�z��(�D�rt���L�ed�r�����h�c�D���|�a��e�i �oT�u��z���D��`�ep������4��(��\��d��������l��r��s���|z,������z������k$�n,�rD��p�����H�eX���<��|����(�eT�Cp�ld���07���l���#������i���������������#.��k�Fn�}s��tt�����e��h�}s�e����M\���T.D�hL�j,�s8�Àiyi���tp�	 �r����,���k�m���P�T�i��l��s��z����\�eMs��1p�1�&sL�1��	�l��������������eL�m4�vH�y�����\���l��s�t�z����	�n��C������������X��(�vl��.@�ax�eD�o`��(���������P	p�gd
b�
����a��b��eD�o��� t<���������������g��l��r(�|�\	��r�TT�b\�dd�gl�lt�n|�r��t����a��d��eT�ih�n��t��y���t� t\��t�t�t tHC��r��th�����@��`��p��x���������_��X���d��g�k(�l0�r8�t0����s����e �l�W��l���H	`.g8t�L�n��0����t����C(���e��id�o��Ð	x�g�"	P�d��s#�(#4#8��r\�����l�����&��
d=.,�ad�e��i��o�p4�t@�u<����\&�'e�'��4�����������H��p���(��(\�gx�l��s�(t��l�)_�*��*������*����z��� +��n�$,��k-���t<-��-@.�\.���H�������.�Ѝ
.(�	�v4������B��(�Ð/��/�,r`p��P�� 0��X��/	d�l`0T�g��k��l�n��p��r�s�0��|�a�ct+d�eH�ix�k��l��m��o��tl�u|�v��z��ì��P1��2����<�����t��$4����.9tp7�lh�m,�t`;�8�y�;	4�.�=�e\�n$s�>�lNC�?��d���?��l��(@yh@y�@#�A	4�.��k�A������%<B��	�e�h�i�j�l\�sd�v���8���BeC�����t�������
��
�LC	d!.D�eL�kT�v`C�� ����
;���z�D��<��C�Dt4E����.����Ee�E�����F���F�F������F��r(G����a�e,�i8�o\�st�z�� I܅g��l�J,K	�g�H����@��tM��L$�m|N�O��O��OfH�a�O��P�zhQt��v�Q.h�aH�o����R��4������h��p�
�����DL	���XL�����|������X��t�X_ ^�H]�l(r �t�Z���e<k(�opar��t��
� cXDg��
T.��bl�e��f��i��j�Mk��n�Mr��sĈtԈv����h�#.��d�ik��ntL���j	�b�nt��vth��������n�.n�n����a�s��du�Du	�rTu�����u�tz.X�a�z�d�z���a`�ehw �lw��0�a��i��l��r�È��{#L���k���h����t�ä�����a4�o(�uDä"X�"����e�"	��lԨ����������Ì�	�lh�����ĬPЬ���a,�e4�o<�u�� ������|�����\�P�P,�# ��p��r��s����	D�a�e@�i\�o��r�s�y�Ü�Ř�����c(�.��z ��0�����������.��ä���������tH��sH����e@��l����%s��	�s$�����t�����TP`��,�a�4�tx�z����NvP�r�.��i<	h�nD�
n� 	��sd������!�!��f�n�!����a�e�o���$ta#�%����p�/	��g03P0�a�ep�i��o�u�����<5��3	(�k`�ll�p|�r��t6� 6��L���5��T�ü6��7��7��t�a��o8���8��9��88��r�v
z9�����\������������$��@X�����;���ŀ:	�l �n,�p4�r�<��<���g�<��<��D `@��<���?��D���>8P�r�7�0B	h�l,E�D	|�l��m��rpE��E��E�pF��G��G�������I9pJ�I8�t :8�J���t�J8�l�r�J�PK�<O���2e\M �d|�j��m��sM��,�a�i�y��
�k4�	X�iXQ��d��lQ��p��lW��`�.��b��e�h��m�n��r�t�Wb�W��.�hn8{a��	��0�a\�b��d�fgh��j0kLl\m�n<optr�s`t�vz��$��a��bhcpd�e��f�gp�h��i�j�k�lP�m�n�o�p�r �s@�t�u�v�zü�ІjPa`�iXoT����rԊ�mH����a(���8a`����x������y��Ď�a,�e|jf8�i(o�����z����m��x�#L�rď�(��4���T�rT�u`�h�aH g��i��j�o���\���p�e�i�o� ������}Dk������l��uul��x���������`��<���dPrdspuxzd���	a�e�g�i�o�u�y����0��<���Ha��	\���\s�����y�	ȫ	�l�����@�����������@�	p{.�c�g�h�nr(s0t8v���p�tt�������gn|�	Ĭ. eT��ܬ��������`hhlpm�n�r�z��	ȭ��_������xeP��������
	�������e��t4��X��d�t�����������~|�`����al�u_T�!r<s��� e0��ؽ�(���De��lp����a�e�p����	 Xb�d�r�z����t� �Q�	��	Q�.��j���ps ����a,d`�fDkdn$�p��s�t�"vzØ����	�*z����4��_t�������<v��	���	��P�����|i�o�uX��	p��pt�	p(�n8����a�c�"e�h�i؆jsuv0-���`�
��8(�p��	,�.������D�	8�p<8�_��tt��� e��oT�(�(n|tw
8����L���
T$a�$c�$eX�f�$g�kp%l�%p�r�s�tz���`�d����$��&��&�����o4%���P%m �����
������ph����Wat&������Aa s8��4z�t�.,a\oP�L�_X���H���t��<�m`gt����da�i��o #t������tc���x���g��Q��	�n�	P����cL�i0Bk�l8Bm`'pt(z��_4(
��	�l��������}eÀ.<oDu�PX
��b
t�z
��Ls���Ta�i�t��_�c��x���
n�
�t
���e�u(p����(a�o����������HPl�8��	�.(v ����t�� Jo�t��`!	��gXjhm�n�rtv|z�%����a���,��4)a��-�(-��x�$3�D1���� 0���y��6P|7��,�a�e�i�HoT��89#�:#�A�A���z�A	�s`A����0A���<C����e�o�����́	0r$���8�,���Dì�Pr@���\a�i 	l��r�	v���	ŀ�	d+p������	���
���
����(�
����d�	�n�������.�����d���a	st���|���	s8���$�a��iD�o��uD	ä��H���<	�T	�4������	\	n�	rp���d	�D���t	�@�	�	z�L_{C({���	��z���	�8�	�	v|����	����h-a�	ð�4���	��)��X�l�	ph�s8
t���
a�
e�ol
�Ŝ.��Ct�C.@
apB��H
z�4	T
s�4��`
�����TYhY���
a�
e�V�
g�
t�
�LZ.�
�Z�
l��(����
��W��̘��I��o��toaedo��q��|��H�tptHpl �	�`t,���$�Tr��0��q	<rXt�r��"n(t@Ts< ��	pl�.xe���y`w	�n����r�sd���	�s�d�H�Tgl`����e o<�	�r������j�\j��m#"	 "	��(adet�� 	0g�n���De�u#	��#	t�"	��l�\.	��<*s�zT-	���s[P\e	��d	���s�b	�sm	.�'aTl	�d�Nj
l|�p$
r�k	��	�a�5be�i0o(Zp�s�t\
�pp	��0�khx	�0}	U�|		,
bx
h�
i�
n�
r�
s�
t�
v}	��4
����������Uh�_��	��D..�
��	�	���
�D�	�l�	���
aX�	܂	���
z0�	��D..�
�|~l�	���
�$�U�	.l�	i�-l8mXs|t0�	��	$l̡	��,e���DrX�	��Lils�Sz\n���	 @�	��to@�	.�;e��		�l�v4�	pL�	���a���T�	.xAa(�	�g�n�1p�rst<�	_��	���g��	�(�	t<�	p\�	��ap�e�	���]t0�	$kDl`r�	�Pt�\
	�	��	��Xate����4�
�P�	��|�t�		p4k�s��		��	�D�	���k�t��	��	���rh�	�H�		�j�		`�		�gzl�		8'
Pb��dpe�kPl\m�nHoTp�r(s�uv(Tx y�R�8)
���Re`j`+
_<5
#�4
ht�C
��.�Ud�i�l0D
��|a�ilh/mo�t�����D
�
�E
��E
����pH
T�r�H
<�C�J
�����J
����L��J
k0s8tp/z���\�xN
�.jtl�N
��@aD/d�f�i t0v���O
��V
��V
��|a�e�i�V
�W
�W
�n�W
�hW
�m\
�<\
����H\
���ü[
	�r`Q
�����\
�
�\
��o�\
l�\
��a��v4�]
��$��D��]
	0d.,`
�Ssl`
��Patb�e�0l\d
��Le���	���n�t�d
	�r�d
�����Xle
n�{	`�q
���olo
�t�o
��	�a@chd��e�go(t4y(�(r
8r
��l�q
	l�q
�����@�	�hs
��8c��	e�u
��L�����u
���i�oTè�	��T.$�		|r@u
T�k4u
��u
�Xu
���shu
�kRrX�	�$�	�4x
���aÌ>�x
	�l$x
�����z
tl|{
�@
n}
�� o��
.�e�0
#��
@d�
���e�S��
��t�s�z�
ds�t\�
��ta�c�e�i�~moz��Ԋ
)�
j��
����l�
	�l,�
������h�
��l(�
uĐ
�er�<�
zğ
��
#D�
�� e`t�zH��
��
��@�|�P�
��
��XoxTrty��
_ԥ
��
R,�
.�a�è�
���
������̧
_��
���
�a�b�d,�r�StL#�#\���
	�c�
�����
��܇e��
��La0�@���P���
	��a��b�c�d(f<k`l�nx�prls<t�v�z��À�
`�
���h�o�s���.tx��lؾ
���p�
.�abW(�
	�nH�
����$�
��ir��H����
v�����
��u��
��̣rT�
�
��4l(Vr��
�<�
LrX�
��Ta�e��H`.��L�
tlpb��b����8{,��
	�h�
��������L�
�T�
��X(�
���u��
1(�
�dsh�
���aXoD�,�
1H���
�� a��
	(kPr��
��4�0����
���
��j��
��`a��kttp�r�s�t�zD�t��
P�
���a,�r,�ì�
��4A
��
/�m�r�
����hG�@B
�sLB
���a�xB
H�B
��� ��]��
���
(u�
��0aXidr�t�Ps���ti$h���t|l8���a�o�up������d��#�,����,���i�ou���T�	�\			<bPk|l�mnDrps�t�v�	��4ltX��Ho`u$#P�x��h�����b�e�rp��@�d��$��a�e�#���b�l�$�%T�l�r,%�X	t�'�(X)���a4e<t$��)1*���i��+#�0yT4��	|�apbxc�degnPv8�l�
��5��s�t�5.�a�e����8z_Dz�����5��5��l�_������5��5�����6����6�(6X�=��:��{�x;	$k<5��,�H��=1�=y��C�n�?��X��?����s��zd���_�A����l8F��
h".�b�e�h�n�rt� v��ň8�F�tG1xF����p�������8��8��8��1�H���,P	|�a�b�e�f�g�i�knh�pr�spt|u�ztØP�� �<1��R�������h���S��|�r�YشbD\�[���r�\��d�l�� d����^��t�c��c�����c.��c���y�g��
T.�db��hTilj�Kktl�%n̦rYsLt�z��pHy(lLtdzm�Dmlm�m	��k�h��|����o� p���o'�o�����o����p	�sp�����q�Xq�p\p���ei0p<t\����rb
��t��(iTv���ad~,�7
	Hvq��P��x�x��hr�y�(t4|�|	�t|����P ��{���e�o( s�� �@|�T|�g�k�l�|��|��}�й.�}	�s�}�� �4~��l��~.< u �D �|�R������\~�(����X h��` c� l�~��l e� i� l(!oD!r@�s�`ė��� u�#�̪� o����� i��� c��� a@�����a�e!u��h�#$�	#4���!a̶��!g��!n8!r����XaeL����3a�e�3i�Bol!Ì!���� ���d!�4��P��l�����d���!�p�� ����!f�!oH��!f"m�"p�"v�����!a�$e�$id%u0#�@� �#(���!d4"j<"lD"rL"sT"z����!at"e�"i�"m�"ud"��$(T��@	��%�&�t�e����\"�DF�)0���|"k\Fn�"s�-t���l����"j(1T�"t�1������`����"�H����"�8�$��.�"ix����"s��	#cP#f�#l$n@$rp$s(���#�%�%��Tt#n���D#a|#b�#e�#f�#i�#l�#t�}3l�����M
����#e$�<,��#d�����������	�#t�����#�l����e�#�8�H����#�����$d$$i�#��	���p�b����,$�0���P$i4$ü���pH�	R�.\$aH���d$z�.L|$e�n�?��$l�;�$l�$s`���?���$a�Ft�F���$��$��F��l�s�$�HC�$t�G��J	8�z4r	�a@%fāp,%��
T�s��$%��s��s��8%aX%è�
���
��P%���P�p�������%�h���
@�h�%k &lH&m�&n�&o�&pH�r$'t�'u�'v(zp%ì��\��%l����%e��	�%g
#4
���%a&o&r���
#\
.�
#�
��&a �it��
�
��4&��
��\&id&u<&�p
#�b	#�|	�}	��l&��
��t&�0
�

���&oT
�&k�&r
���Wsx
���&s�
#�
���&a��e��i��o��r�&�
�
���&�'�'��
�<
��%
P�%
��'a`'ep'i�'o�'r�'uH'��'
�����h'�x'��'�)
#P+
��,
Ph-
.
T|0
.�a�Pu�1
#�1
�2
C�2
���'��2
���'�X2
�'g�:
�(:
���'��9
���'�=
��X�Do��<
���'�t;
�(k;
.	(a�(c��fl�kd�l�(mt�p�(t�(�p	.�(a$XH(d�@
��T(a�(i�?
�`(l(�r0@
��p(��(�d	��@
#�C
��Q
.�M
��(t$X
.�`
�(Ì`
��Pz�ܿt�j
�(r�j
���(a�rg�f
�(g<)nPf
��)a��e�)i�)rl)�*�u
���s��t
��0)��
pidL�
��H)a~
	T)rD~
��`)��)���
��4x�L�
	|)rԽ
.T�
�)m�
��4KadKe|Ki�Ko�)u�)�p�
��������)��)��K���
�|�
���
#\�
���{��
	�)zP�
��*�`*a�*b�*e�*g�*i��j+l(+nP+r�+s�,tL-yP#|X*tp*y\#�	`L��x*oP#�*l�*r`>P4���*a�*g�*o��r��LP�#��*d��n�*r(#���Le��+t0��+a'����i�u����J$
�,��<+�,��D+�̠	(/��\+l4/d+b�+d�+n�+s�+t�+vp/��	p+a,e,i,,o`,st,t�,z�+�T,Ÿ/�$0�t0��0���t�0�����@��$,�D,�L,�|,��1�f�P2,l�s3t�3��l<,r�3�4e 4�h���d���4�l,z�4f�5X6�47_T7���,��6.�1s�,�d;��:�,c�,k��l��m�,n��s��t8�z;���,a-cD-u-Ð;��t
�P=_�?�8-d�<��-�@�
y�1u�
 -kt?��,-o��
`$���La�C	�-j�-n�-r.s.t�/zXQ#�N��x-a�-u�ìO#�	
��X.�-e�-��X���-y�
L�
���-��Z��T.�-i��n�-o�� \#�\#\a��]��.z�b`�b��.aP^b�
c�.d�.e�.f��g��h�.i"jt/kl|/m"n�/o�/p�r�/s�/t�/v�/z�.�c�$c��|.��.�܏
hc��c��cd�#.�.a�.g�.h/j/k(/nL/rT/s\/td/vl/z�c�hd�\E	��d��dE�d��/o��
o�d�� /a@/äd��d��8/��d��d���
�,�
�e|�
`�
4eX<
f��
y�f�Th#�g���/i�h		�/c$0d00gp0ln�0r$1t��vнz�iP�i���/sT|�(j��0��������i��0�,l����e@0yTl.X0i`0lh0v�~�Tm� "��<��o����d�0e�0i�0�,q��p�0n|�p��$s�0kht�Lt	�0m`p���0�$���x���0c�0e�0i1�yX�;��J�xy��1�1�z��|����e�i���Ȃ��	L1r�z����T.�1b�1e��h��i�j�l�m��n�1t�1u�1�\�0�T��������1��1����#4��d��&s�����1a�2e3i42��3�4���1a��	�1d��.�1at���2yL�	2gT2n`2s���� 2��2�$3��.�a����H2y����s(����%s��	l2s8���x2������2È��2r�2z������2l�����2e0�	�2r<����2� ����2�h�	ȅnP�#\����2a��3n��j��	3s$�.�3a\:bt:d�:eBf Bg0Bi�Dk�Dm�Do Hp�Hr�Hs�HtIu�Kz9ðGŐ�	
�3b�3c4dP4g(5j<5k�5lh6n�6p�7r�8s�8t�8z����\+l��,����3�8����3�ԣ�X��3g4l$4tp����3aD4���������4b\�U�	04t����84����D..�4b�4e�4g�4h�4i�4k�4n�4o�4t5y�4È�yH�#d�	�rp�����4���yD�y������4s��y��y�t���4l��yH���5a5u��p��|��� 5a��B����45cp5n�5t�����	P5k����X5�����d5�h"%d���|5.�5up�!(�����5j�5k�5p�5t�����5a46eH6i 6Ô��<����5t������o��p��8	6���6��	6�4���6���
,6l��(��@6k�l	��T6b���\6a|6e�6yX��D�P�.�6a�6�p������@[����6k�����6ap7j�7p Gs7��U��	�6h7i7k$7m,7n47sP7t����6��7��
N0� ��],_Up��
x���<7�T���D7�e����\7�����d7�d���	|7n�7rPf��B����n�7w�����7a�7d8e$8g08iT8�o��7o�����7m�� ��7l<��6��8��z��8��z��u_h�	88nd8rl8vD���@8�t8�\�,����������|8k�8t����8a�8r���
�����8a�9
[`����8y������8������8��8H9bT9g\9lx9m�9p�9r:s$:t@����8��>�hD��E�pF��I��J���'����@9rd�B����cp9l\mX�8���,�B8�W`����9e�9f�9o�9u�9��M��R�R�9k$]��9s,�9	�����9���_����9���.�9�t���:z��%����:a<:kD:lL:v'0�8�3'��x���T:ldrh$��l:r�	�:d�:gD;k�;l@<m�<n�<p�<r�=s`>t|>z�_��	�:s�����:�X���:z�:� �[�P4���:e$;y;üh��;�4_P.;e��.�0;.���8;eh;��	T;s���\;����;y�t;g�;m(���;e�;n<t<v$g������;�����;����;�����;È��	�;r����;����<����t �`��B�X���,<l$��4<b\<eh<l��T<l$		D�	%�Up<t�U��x<e�	���<g�<y�;z�
[�
��
�<l�
���<a@�B����<d=eD=nL=oh=s8=�\=�,{��<d=j=s�	�~���	$=n���,=��B��
4
��T=��
B0�@p=g�
��x=e�=g�=t<>z�=��=�(!	0	�=l�
���=�L�y\����=���!���=e >i>�0>�,�4	>sD��>�x�!����(>��.���H>ml>t���P>e,ئy���t>m�>r�>s�o����>e��>d�>l4����>eL�!	��H8?b0Og0?hP?jl?k�?l�?m�?n�?p�?rAt$Av���8��?r�?s��$?e@?s,'��%4��H?i��[.x?s���\?ep�1P%0���?e�?t�Ov
'�������?e��?k����?e�?i�ܸBD��\�.�Od4@ep@gPhx@i�@k(Pm�@n�@r�@t�@v�@z`@��,@mD@zP�<�`	L@t���T@��@��=8l%8? �N��?���@e]����(��\Pe�@��@�2���@�LC�`C���@�@'�'8D���@����AkA�X�d��|5.`AalAb|Ae�Ah�Ai�An�Ar�AtBv�Aì�XAd�'�[y�tA.�Ai�Ak�An�As�Ati����'j%h'�',j'x8���A�� '�����lBr`�x��d�lȮr(	
tBd�BgCh(Ck�7lHCm\Cn�Cp�Cr�CsDt<DvPDz|����lBe�BäBż����B�ll���B�������Ba�Be�Bm�B����B��B�@�� ,B<�L��CadlCrx��Ce̊	Ԋ	��4C����<C�DB��TCdpCfxCg��B0B��T���Ca�h�Ctx���Ca�Co�Ct�C��
�
���C����1BB���Ch`ckDmDn�1B�
Bl�|��Da�����(D����0D��ZB���HDg`Do����8�Dg�Dr�Dv�#z��B��, B� ���l�Dr|�v�!��"���s#	�Ds#���D��"���D�$	Ek,ElpEm�Ep�Er�Euh{
D%Es�$��Eo&Bh%��$EdPEv�^
!	�^
<Est&��DEa�&��&\Es�&��dEa�EiP'# ,�)���Ee$)��pDc��m�Es�Ev�e�e���E�t)���E��
B�S��)8FdXFr *f�)	�Er8Fs*���E�*��F�(*�0*$Fk<*��,Fa�*1(+��DF��*��hFiLF�82%,8�Fb�Fd�FkGt�Gv Fz,BL��X����F�d����F�`'L,���Fl�E� .���F. Gd(GeTGntGr8G�4.	�Fd@.���F�.���Gt�GvG�lD�L.�T.�\.��0G�hG�|G�tD�|D��LGe�D��D	`Gt�D��D�43'�z'p.y�.8�Gr�Gs�Gz�.���G�PK��.p�.���Ge�G�0/�</���G�\/��/�����^b��	�Gr0���H��/��<He4�lHHrP�sH��/(�nX08HHDHPHd�~��XHi�z	dHv�0��pH��0��|H�`1��	�ck�Hl&m�Hn�p�Hr��t�Hu�1�12�3
�X2
�Hr�3�p3���Hr84	Id$Ig,Ij<In\Ir��x4��4�h�B�5��4IdLIi�5��5��5��TIa�IuxI��5�5��pI��>
��68�Id�Ii�IjJm JrpJt�I�Iy�K��D���I�|J�dM'���6���I��6��JrJs�I�7'��'XXy�!'H8��JbPJiXJk`JrDJ�p8e�8��<J��8%�!'�!'l9J,9��hJi�f��98
�Jd�Jg�Jl�Jm�Jn�Jr Ks�
t4KvHKz�B�9y�8
V
�9���Jev��L'�L���Jny�<:���JeK�H:,:��K�8zy,Kt�z8�:y@Kex:p�:B�:8dKl�Kr�Kz���r�X;	lK.l;��tK�D;���K�0<�B�� NrS��R	�Kp�R���K�L�XR���e�K�L�HC�Kt�B���Ka�Le`L�M�`T���e����L�p^,^	$Lr�\��,L��[��8LàU	DLrtLs�U��PL�`#�_��lLe��s�|����a�Le�Li �o�L��LŠc�Lt�|e�|���L�<}�rl�s��~�Ln�e����L��	(za8����L���ĉ.\Ma�cb�ce\nfhnihuk�ul�uoD�p��r��s8�t��u��y�]Ô~�̉��a�Mb<Od(Qe��f$Qg0QilQjRktRllWmlXn�Yp�Yr�Zs�[th]u�Y�H���D�.�Ma8NedNi�No�NsOuN�Б���Mk�Ml�Mnܑ.�.p�	Иg$Nr����N�PN�ؙ����4�bܓ�\�0Ndh�yT. �	DNr��ؕ\Nd�Nl<�m�Nn��	|���|NlT���`�a��tؗ��Nl�Nr�Ns�����dH�PX����Na�Nr$�	�
_�����NtL�������Nz��.șOrԣt<�Og`OllOptOsh���$Oa�OePiPPopPu�Pz�Oü�����k\�t�Z.�Oz�Z,��	�c��l�Os����O�P��P�0��\d���s�v�Oz�e.o����g�Ol�Om�tt�����l�	�Ol���Pa4Pn(P����X��|��@Pa�����l�HPr`Ps���h��,�hPrD���T.��h<�i�Pk��n̦r�Pt�PÈ�	|Prh9st�������P���H�\�tl�.�Pa�PÔ�������P�������Qe��Qr@���Qo���lfl�,��	<Qs��t�Qz����DQ��Q���������Qi�Qk�Qo�Qs�QtXQ�zŐ����t���Qn�j�u���}ll��Qmx���H����r��_$�	�QlH����Q����$Rl\zr0RtP�v�QÄ����2i��	0,��8Rhl@Rc�Rg�RjxSl{n�Rr�RsSt���LRaTe(Ui�Uk@Vl�Vm�Vo�VpWu8Wy\S�W���LtX��P�Ra�RÐ���Rz�-Q�-��-���R�S�.�8^����Sk,So8St|2��$Sm�_4��ScTg�Sl�Sr�Std��@S��T��U��V��V�4��6�(6��|Ss$:9���Se4	��TTa4�bd
o̦r�Sv�S�P	�\	���S�4��_���Se�	n�	���Se(�
Tb<Tc�{gDTlhTmpTn�Tp�Ts�Tt�Tv�Tz((.�Te\T��
��TT�X
(�
�
��
�xTe��
��Te�4�@���Te�To(tP��|��To�TŨ����T��v��	��llUrUt�n�yUe<k��t�	�UdTUk�Um\Un�Uo�Up�Us�Uv�Uz,|��0�g�"v�����lUo�t�tUn��.�Uz|��t�t�����i`����Uu��Up����UaVemVe�Ul�L.�.Vt���Vz�(Vs���4Va�VydV�l	�stVt|��TV��V�(�,`�l	|Vj4�4X��Vah	e,	���V�	���V�|��Vzp||l0���1i,	�Vr|t<�e�"���V�(W�8!Dg�Vs|t|"��"R�".0Wa'_'	DWl�Wr '��LW�<X�$���Wb�We�WiXk(Xo4Xu\W�T'��4�b�'d).�We)�Wl�Ws�Wz�)��*��!z�*��-���1mX,�Ws��	��sHO	���W��.��X��/�%z$/Xs(1��1��5���s5	DXs$5��PX��X�02���Xc�Xg\Yt�Yy\X�l6P�5���Xh��	���	���Xz�;	�Xs�=��Xa�XeX0gDYuYà=��l�>T�Xn�Xs�?��?t�?��?	Yld>��Y�,Y�4Y��@e�@���	�h�	<Yt��	��L�(I��PYÈOy8O8hYl�L��pY�,K.|Y�L������]��Z��L~r �e�����Y�l��Y�k�YgZn`k���YaPZcxZe�Zm�Zo(Z��ZŰ��� Vgm��ZgLo	(lxo��Z��Z�8st�r8ZlhZn�r��@Za�c�e�YhDs�dw��vpZl�Zt�x��X	�����Z�����Zü���l��s��_́	�Zr��(����Z�̇�����Za0[i<[oh[p�[z[ð[�<���H���[��[� �t��([z���WlT[r
	|��L[sh
P|���`[ex[o��P���x�	�[t9
���	�[r�ed����[� �.D�p,�8��[b\g\jt����[ap\e�\o,]r8]t,\�D��0��\yp�t���d\rĤ�� \��\��\�]�]�T]���.(8aХL\d��yX\al� vl�_��	|\n�\vĶ
,��,\�	�\v0�
p����\a����\dt��\l�&sش8����\�����\ø�	�\r|�,��	]v�������H��� ]�d�D]r �,���	L]l���`]e�nL_s؍th��_z@�		�]b0�g^j(^m�^n<_rD`s�`tdbz�����]��i��s�{��}�\��؄�(�������]o����	�]s0����]�0���^��TD^l��^a|qed^i�^oT^��tt4��L^��^��!Tp^s(#tp$t�#x^l4%�-$�,.�^e��i�*���^sT'���^c�^y�8	��l�^n�^r�8.�^aP�e_�\9.�9��
���s:8_s:��_����8CTh_dp_l$�upC��	(_ax_i�_k�_m�_o`s0`t(�y�_��Ct Et�NT�_v�It�P�\H���_����Qj���		���_s|V�_s�V���_a|S�S���_a�_o�R�_m�S#pVt|V`l�V��`a(`eW#�X�Lb��,��t_���`z8`�HT(TT`mTp��\`o o�h`tXo.t`a�`e�`i�`k�`ŀq$Ps��?v�V
�r.tj�`au��4g��g
��$���u�`d al,an4arv���`a��eԄi�ao�aubvPa�a��v���[a�t
�Dw�x��w	<ap�w��Da�pa�����a�$���a��yC`�l�ar�z��T.��e��n�|�l�ar�}�|~C�as��
_0��L�������[s�t�t(��al����ae�	�ad`���b�l���bäE�8���(b����0b�T�<bd�bj�bl�bs@�v����Hba�eci@coxcs�cy�b�t��L��Ĉ�Ј���ba��,t����bzp[,\�	�bp�br|����b�,c�d���c�Ċ����n�bu,�Xd���0Bk8Bm$ctX�csD���tܨ��lXt`�4ck`cl\%slctD�	P���Xcd����l�t<�)�ca�ce�ci��p�ct��z�c��Rh�R̓�0����c����@�R4�j��tЖ\�����e�����cl��Ldaxdc�dd�de�df�dg�ei�ekfl�fmgntgo�gr�hsit<iudiv�izd�X�d��vp�4m����\de�dsP���hdc�du<������������P���ds�������di�du���X����daeye��	D�s�����d���H�.eaTeexeo8e��������0e��e��e��:���	LeglesP����des������I���Il����ep���ed�Q�	�ek���e��	���et�eà��%	L�gܩsH���e����ffDfl�fy�e�*�(fr(*P�-�-��0f�8-��\fe8fè.�$.Tfn���h=	hfg�m�fs�<.pfe�fÐ=�=���fz�=��=8�fj=���f�?�D?���fa�fb��l`B��<�l�T��$$spT	�fs�O���f�tN��(ggTgygÌU��0:.P

��0ge�_	<gg�^.Hgedgoa�\c#,blglpsd�b�gd��jl�r��u�s���gahe�hi�hs�hu�güh�<t��v	ؼl�gt�v���g��h�P�o
`x���gahèx���3��}��Lg�zhn0hp@hslhv(~�"��~��8he��H�	Lhz���Th�$���`h�0�C ���xht���hnt��,�j4�8yt�������h�h�������hait�h�������h���	8js؛�����io,�>
���iy����(iz8�0is �B@s����Hi�pi�����Ti���$�t<���xi������ih�&i�in�iz�i��i�,��ԧ�l�B�����i������	 jh,jiTjj�jk�jn�kp�kr(lsxlt�mvnz��b����j�t���jÄ�d=.<jt\���Tdjgljl\���Dje|jè^t0�t������tj���������jn����jl�jn�jr����jakeTkixkn��t�ku�jä���H	��th���j�@k��k��k�dT$kl<�m,kt�t�tL�	��h".H	4kv���Lkdhklpks�1�lt0����#��Ȩ�&���kh�kà'����`0T��r�ks�kt�0���ka�kit�z�1t2��>��=�kn�R��4��Xl��Q.4lel�(G��lz�S�\�gLls�U��U��Dlz�X��ZT�lb�lj�ll�lr�Z��`la�le`mo�mu�l�xZtd[t�[�(\�t\	�l�\���l�Dm�Pm�|m��m��m� ^�H]�llmmms0mz<_��_td��4�
ms@�
��me�
��$mr�a,h`	<mv�b�Dc� cXmltmr�sdc��cC�mr�c_�ct�e#fe`htth���m��m�Dg���me�m��h.�j��j	�mg�mr�j���z�oT$nk�n��ne4niLnoTns�gt��� s,noDnp|s��tT�uw��Pl���na\od�og�olpmPpn�po�pp0qshqt�sz�o�$�`�.�a$obhc �d�eH{gp�h�j0olP�m@onLop�rD�t�nÜ����x�@��l*�ne$���oi��olІ��oe(�#(I����8ot\�#H��\���Toa�or���los����too��yL��ov�����o����.�oe����oy<��ond����oa�oi����(����oap���`�.pe(p�����B���� p���	 |nhps���4p� ���ppg|pk�psDp�p�����h".�����8�����pp(���m�����a�ps8��h".�pe�ph�prqsqz�p�0�p�%������p���8H�8��8|��`s�.qe8��.P���$qeDqnLqo�
�\���#�qk�ql�qn���Tqa�qesi<skHsotsu�q���H`.�������r�Ps�hs�|s��s��
���
���qy��qg�qllrnxrr�rs�rtd�re`rvHr��\^l4rs�D�� r���(rü����@r�D�
��.�
��Tre��
���g� 	���z��
�t	�L		�rl�rr�rt�rv�	��T.�rt$av0�
��ae�	X�	���re �
�#.Ķ
���re�
t
smsn�
���Zt��
	��s��
��$s���0s�P�LC`sn�mr�_
C�rT�����	�slt��o`!	�sd@tgLtjXtm�tr|"#�"���sate0tz�s�d��#�0#���s�t�t� t�(t�|##���@��@$�L$�t$P�$��$��8te�%��()��,��4)a�te�)o�)utt�(-��x��)��-t�-�tlfm�tr�-�\7|7���tauiuo8us@uu�t�,u�8e@8���t�u�u�Hu��:��Pg�:�tn0;��;�<e�<��<��$u��<�=�,>�\����}s��Pus@���\ua�ur|u����P-�����ihY��\e�V�ug�ut����ue�o��e�u�xt��q���u�8'

vd<ve\vi|vk�vl�xmynzrTzs�zt�zu�zw�zz�0
���R�������(v��4
Lvf0v��#���dA
Tvilvt�#�J
�0D
��tvlxN
T�vd�vl�vm�vt�vu�N
���vaPwd�we�wi��k�wm�wnxo`xt�xyw�O
��O
tP
�lP
t$Q
$
_<Q
	wl0wr`Q
��w�4x�@x��x��Q
,HT
�TT
��8w�|w�@S
��hwe@w��T
�xT
`wm,U,XU
	twr�U
�W
�hW
�wg�wn�wp�wvX
t$X
�t�������ws�����we[
�[
�wk [
���wiX[
�,[
xr�s$xtl[
�H\
,�[
	,xrh\
��t
.pxy�\
Hxn�\
��Txaxxew
�0]
T�xl4�t�^
��^
	Шl_
.�xa\d
�l`
���xb�xo�l
��xs�m
,p
�lo
�xg yl(yn0yp4du8yv�o
���xa�ye�yi�yoPy��yŬp
�q
�@q
��|	��q
	8�ltyt�q
��@y�H���y������ �	s
��lya�yo�|X�u
T̤g�yl$v
�\y
ty
�yk�3m�	����jDFoLFr�z
�yr�{
e�`|
���y��
��
���ye\�
��zd ze(zyh�
#|��Ȥ
���
P0zepz�P�
��8zzD�
��HzsP1z�#
��zk��
��dz��z��#
	�d
�L�
h�
���zhp/#��
�zst�
#��
���ze��
�����<��T�
���ze�zi�z�,�
���
T�
	<{a��bp{cH|d�|lD}n|}r�}s�}v�}zT{�$�
.`�
4{k�
��g,�
��H{�@�
��l8+t`�
��`{ah�c�{e�h�{i|s|z�{�,�
��
�{l(,\�
	�{gܽ
���{��{��{���
���
�{mxI�L2t�H_�
���{��
.|�̿
P�
p{.�
�� |ah|��
,|r$�
��<|a�|e�|z||��
��
��`|�(�
eH�
��t|��|��|��|��|��
���
�|mh�
tl�
e��
���
���
L�
�j}p }vX�
���|e<
�\j�|d�i���|e�u�}t�u��}e��
���
,}lX}p(�
��4}a�
��
yP}a��
Pr��
��d}�h�
��p}���
����h�C����}�8���}�����e�}t,���}a\		�}k0~lf����}����}�@���.~aX~eD~���~y���$~g�L���<~��� 	P~l,P	
|�a�b�~e�~f�~g�~kp$spt�~ØP��d~�0��dW�l�~m�r�X.�Y�n�R���~�h���[��8�l�\��d�l��r�`���v�f��x�r4pTPl�Zs�+v\p��	a`e�i�p�s�t�u��0�Š�t�qtXqXltp|s�q�r��r��r�m�s<s��t���5i@u�Dv�WbTv���a(\rw��v�twtq�������8
h8
���t�7
	�lHvt9
C<�r�t��$���9
_�~���3lT�rL����3a�3i�Bo�3�\�j��l�s����t�uh���@�c��kĀpԀs�z�������%�4
���%a�
����i��ot
���kt;
.��a0�l$�������a��d�@
���a�?
��j0@
����\W
.Pf
��,�cT�k`�ox�r��u�
���|l��
$lp�
������
����el��|�	8��
��f��
��tāh�r �s��(����y�,B�,��Ё�,����t؁À.�`.���e�i�.��.�tp/��`�.4�iP�zP2��9H�9�<�n�6.D�o�C	��gȂr,�s�LG�L.p�a��e��i���@L��x�yM�0MXM� M�����dZ�Z����a�e��t��T[tp[�����[#H���r0�t�]�kT�l\�nd�pl�r�]���a��e��iЃo�s\�z�����^�8��$^�0^�`^,l^	t�g�Vl�^��|�����܃�@��_��	��_��m�_��_��_ȃr`�X�������p`D`���kX`P�a(��4`���z�`���Z�8���#
��`��;
�La�H�b\a.P�a��e��o��Ôa���l�a��t�����Ȅ�Є��a
P�
l��,b���l��s�$
�Db�xb�h	�fgn�r�t$�v�x��h�e�fÄ|��Ȃ�$�1������d~�����	T�rT������<�����������iD��D	H��d�r��v@���l��$�����i|��d	�(����g��ĉ.$�a4�b`�c\�d��e��fd�g��h��i��j�k(�lp�m �n(�o��p��rP�s�t u�v�xtz���d��̉��aІb��c �d��e�fd�gԊi�jH�k(�lȌm�n�Wo\�p��r�sx�t؏u�vD�z���`�H��a���tL������������]�X�>������jH���Ća��e8�oh�u��X�Ŭ��\��r�s���_������P��p��x��D��ؗ0�nH�rX����_���������șt�_0�_��_h�t|���l����
��ȧc܇e@�hP�i��ös�Pz�ì�Ō��ܜ���ԇt4�8������������	��l4�rD�����������������D���T.������H�dh�kt�m|�nX����bo�!td���Ptt��_�p�r�1�������t�L��̠�����`�.�p�t�z���0Gjġj(�4��������@��d��������h���8�eP�o�Ø�Ŕ�t�_h�
�l�H�t��Y��	\�e|�s�8���t�t��_�_��������_8�.���l��rȉt`�.���<�������@���������t�e|�i�l(�r8�uЉÔ���tP�_p��D��� �i�o�t��_��_���H��������@�����e��i��u��yP�ì���t��_��t4�_��_���������th�.l�uT����̊g�p�z�������	�n�����������$�e,�o��P�tl�t�����4�d���<�a��e��lԋr�u�vp����H�������������������������y�	��d��r@�y�_��������`����̋e�i��|���|R����,��l�t�t9l�n�t����aT�eh�i�jo��u��y|��h��
L�g����`�b�_d��t�����|_8!�d��sP!��"� '��p�������$���e��u������)t$0_\1t(1�r8:��9�k02���e��4��x�	_�J��,��]	�Xt$]��@������Z��x�e��lh�o�sL�Ä_tpa_Lo	`�dxo������� ��Ѝ�؍�`k���eȍo0�u��ü�t��_��_p�t����e�h$�i@�kL�lT�m��p��tĎzd�È�Ő�t��t���Ď��,��Ў��4��X�������_<���\��|�����l����_|��В�a��idolr<��(��x�_����n �.��a�eP�k\�o,��x|����������Ÿ���r�s����O
y���pp��� ��h��`����<�r����D�e���Xp���l�tt���p�eďr��y��ì��Ĥ��������_��������������H������<����Џd�rL_sн������a��i��o��h�_������$���_��_����,��l������\�e�od�s4��@�tp����_@���c̐g@�h�kԒlP�n��pD�rȔsܕt$�ì���t��L��X�����������@���Ďa�e�i�o$�yL���#HT<�bD�kL�lT�n\�pd�rl�sx���a��e�iH�o��u|�È��<���t4t��tHt�t�e���t����8��p��x��������� ��bđl̑mԑnܑsHt���t0��H��T.�~d	�r�s���T �n(�r0�t �@�T�x�(��@�p\�rd�s4tH��|�z|e�t����������������r��t�te0t�T�n���Ȓa�e(�id�o��@
���a�	al�r������0
_�� �m@�n�(�'8�rp�sT'��@�a��e��i��t̓y����(t�-�`-x�m�0�rp�s�)���������8��8	��d��l�n8�r�8.��aP�e��\9�<��0�z�;8�r:���������
��?�i������8C��dܒk �mpC��0�ah�d��e��i�s��u(��xJ��@�a|�eP7z�J�@K#�O��N��m�[#��n�\98_��l��r��st_��
��aeeL�i��ol�s�et��v��z����Ŝat$b	�dg0�m�sLb����,��P���e��e���_���f��8�rdf@�k�l��kfX�o�k��`�zx�C(:
��x���9
�����Tqy�p���s�p�����Xo.̕eԕs��ÀqR(u4v��X�a�e��i�xH�l`{T�r�{t8�jX��dT�s\�td�ut������aؖe�i|l(�r���`�t�Ĥjh�T����l�����������a�	��lH�����������	��rи����������Ð�̖l�r�s�t ��n����i��.���o������uth�$�j��8�f��g��k��lЗpܗt����@�a��cܘh|�iЙlܙo�s<��8�j����tRl��r�������s,�����o�����ȗr�j.��l �n����a�hP�ip�o��sȘz0��,�4�eH��(��h��x��L��\�0H�n`�r������t.����l(P��e��o�������l,F�<������P���E.�.Иa�e,�i@�oT���e9����k4pDp����Pp����� �n�z9��8�zL����L���'�'��`�a��e���Th�t�'��'	��r�'�����d����l��r�(�-P�-��șu(.�l�4�(4��d0�lHp<�u�3.�a|�e��i��st�z\���4P�4��(�a@�<�7BD7�D�g��lh7��L������� ���9�09�t�b��g��t�v�<.��e���=��gؚr8��?	��.?������>��̚���<A���a�?��p�H	�H��th8
B�H��l��t�L	̢z,��0���O��j��n��uLO��H�a0�bD�d`�e�g�iȞk�o��p��r��s��tԠu�z�Ð��8W�DW����a�y(_$`a88a��ělH`	̛l�r�s|`��؛�P�����@�����cplb���u\d���v\hh��(�rip�h��<�o��z�i��v@i
T�a��gМj؜k�l��m\�p�r4�s@�u�kt�k���������k�����l	H8lĜr4l���o.�$m����v|n�x�j@�mx`ol�t�s���eb$w��dHv���e$�o�x��y%�y��,�a8�j�|,T|	H�g��k��n��r̝s؝z(�p�lP~��x�e8�������y,5$4����sԃ����c`��l���ĝz�nU��#̉���e��#Ћ�sx���a$�lL�o��tx��l��P8t���,�j\�nlUo�t,�4�nds�z
8���h�d�d����l�����������a`%.pSal%��d�����a��	��j���\�r������d��v�Ԟl�mx�T��c|n`�����a0�o �����Э�̳������	��	8�s �	x'.��b��d��hȟiПk؟m�n�p �r(�sT�t��v�z���d���L����U�U�.��������]U�_�U�bUc�<����e�k��Pc\c�������	yh�U��U��U@�oL�p���8�d4��ȮUt�ä����	`�l����h���zU���X�r���	��t�������p���Ġu���P�P@����̠t��	0�	�g�z����`����L�.x�e,�s���N���+��.$�ad�eH���Q���@��x������y$�\\�n��P��p�l��1��ءb��c��d�f �g��h8�lt�m��n��p�r��s�t(�u4�v��#����Сe�����a�di��8��h�������8�eX����yܳ�D����0�d��o`�Ť�Y�1	L�a82��T��?#D?��l�a�	��Z�����`Z����o �s���tN����s��y�	�j��<�eps�gd$�g��j,�k��l4�m<�nD�s�s��
̢ah�d|�e�i�o0�s<�tP�uL���ŀt��t�<u�hut�u��v���_����h�X��`��tzj�{�h�y�zp�g��l��mȣs����r�����e`}����b�~_�~����tL�`���ԣa��ܣg`xn��s����}t��8�t��l��r�),���(�z�H�r܍4���t�	��g����l�l��m��t����t�eؤiX�z̤�D�Ŕ��������t����	��s�������$k|�@����z��	�sp��������� �z�äH8�`�r����ܶe��i������
��d��f��g��h�i�l,�m��nܸpԦr(�s4�t��v���D�����o���@�����eX��t�����ḁeإi���*s��(�����l����H�e��i�������h������������$�eT�oH��\B<��@��$������\�e��h��k���dd�k���|�e�����P�	BL������8�p7��k�n��z�0����e �i������:�A	�X.� k� t�A����0W��Q.�i(G���z�Z��X�aP�ed�� ^�H]H�l�cB�\��\���h.��kDg��p�eԀ��$,thw��sԧtw��	��aܧe0�it�j(�ll�o��r��tP��\�j��.�n�R��tF��
8�à
������^.��h����y��j��(�o��	��	<�rh���D��m�4���\�����@�����ud���p(�	
,�.��bبi�k�n�r�s�t �v̨�LEv�K�D��Ĩ�dMQk�P�����O�����Xv�Zv�v�vgv������a�e4�o(�uL��,���4��D����T����X�oL�`�g|�n����H�n��PЬ����i4�o<�u��Ð��������a�e�iЩ�~
e���ȩ�����8�
���l�{�4���nL��`�������th���$�i �0�g��kȪl�n<�r\�s��u��z����<�a4�b��d@�e�f��gĮk�l�n��o�p�r�s��t4�u��y�Ø�ŀ�P������a����wsتvи	��>4����d�y<L��	��l�.�a�e4��(�s�
�����s����0�aL�zt�UX�(���T�l��zܼ���l�g��l��.t�a��|�P�r���̣.���|��$������ܿ	(�c�l�s(�t$���ȫ�������������X��d��l�����m�����ph �z$��
D���h�e�����r@�
��dp�gx�k��l��nĬrܬs�t��v�x�������l4�H�PH�����e��l��mh�	��	U������l����P�ô�Ÿ�PT���Ԭe�il��D�	HlT�����D������0�eP�o��l�������(�gh��<�	<�lز������	X�r����`���������x�i��U��		��d��gȭl�m�n4�pP�r@�s��Ô�������'i�?v������1������
1���������,���0a$�y,0Ø�8d������,�eD��<���������h".p�j��l�%n��v��Ä?8�D1����x��|�8�f.������X���0�r0	<�v�zh����l$�r��
Ԯc
��ܮa����d�����a �e8�oL���-g0�rl�	.0�s��\
��D��\�����	Pal���d�����p��|�r��s(�@�
T�������<	���	X�bدk�l`0zP�|	| ���r�!��f�!���a�%����k lL�m��p��t�@z�* *,�gH�nP�t(+.�,.X-	0�s�/	�9cP�dx�g��ld"tl"vp0.<1	(�zd������3	`�.Ȱm03.��a��e��p���y�88аk�l9��ذ�@���:	��g(�k�\;	�hh;����D;����l?���Vt�>84�kP�r�?���~.`�t�@'xZ#�Y��h�e\Mp�rM��|�a��бd��g�h�j�m�p$�rH�s\�t8�vH�z`�#\���ȱe�ð��P�������d���L�a��yt��p����i��ð�#����4�a��o4�Ø����o��t��o���`!��g4�r��tt�v4�z<C����ep^^��d��g��s�P����e�^.<è^����yPe�@l��\�Ȳj(���Բe�	l��	�b��j��l4�n��pPr��sسt���@����a�
dT�e��f��i �kl�lطn��oH�pp�r��s�t0�u��<��`C|��d��8�|[kD���x�a̓T������������l��rĔ�\�ȳp�����ę��гr����k �ư	�r(�sH�t��������ĸ�����nԣ���st}��4�l���<�oH�b~gx�l��n��rH�t\�v8����oD�`�����aԴe�i�o�s�u����Š�e�������,{���vzd�ȴd�l�v���$�t���@���s����j4�#������tط	$�l���0������<�������T�o�����h�k��p�k̻��|�e��	��k8�p��r����8�z�t����d8�kصn��o�s$�����dh�s0t��	|����k(�.@�a���lP�n\�r�����a��e�i�o8�u���8�����h���H�y`���p����d�a��	l�r��z����x��̶�����H�� �����D���v����Dd��	��r$����ضn�pX����t����l��t��	,�s(�t0�z���D�,��X���	@�z�.���@r�T�t8���`�a��e��o��u�k�0���rx�����b��mP���	���		��d}	���������̷�,`
T�r�����a<��m4�n b
t��P������������$���(�b��lx�t��zd��H�6P�	P�n����X���|
.p���p�a���\�Pd����������
P�������s<�l�	��pظr�s�������
������.������zp��`��s4����e8�	$�r|���0��$���4�lX�r(%	`�X�i��k���`�e��u���d�L����������U	��mpP���pt�zlT
DT
��ȹd��йn`.ܹi��t<r)
��Ls(�z�	�s����uX
P�

`�.l�b�dt�f|�h��i��j��k��rĺsкtغv���L�4�(��Y4��
���������
�@��vP����ah�hA�0�	�v�����r�b|�c��d��l��m��n�p�r����a4�bH�cؽet�f�g��i�kp�l�mT�o4	p��s��t��u4�vP��P���t�eX�Ø�����4"�D"����a(�d�k�#���5b�%.лaػy�%��(,$+�n�)���i�o�d��+��l��<7���el�g��y|�À4	�gȼnL�pԼr�s �t�4��0��4��p�������������7��t���8��8.��o�<C=�����=�����l@.��f\>����yHA��\
n�o�u��BpDB�����CP�opB���zLt�TD��fmrG��d
llG�G��@�ad�cl�h��s��zH��HPJ��I�t�p�I.|�a��i0�z���L���kxNP�`�`��mH`����e�V̽l�m�x�c���eb�
l�	�v����p8e@������T�e��`w	$�nh�s`���'h�nLD�kdO����`�p�����l�r��_������c��	��cľd��gt�k<lؿm�n�p4�r����e����	оh�j�s�t���ؾ�Ԑ��,�r��,����(�������$�a����
��.��b�h��j��n��o��r��tĿz��ì�8�n���h�o(�QX�ed�������PQ�z
Q��}P�Qx�Q��F(��m.4���̿ah{i�p|������n�t�����ؖ�������|��� ������(�Èe���@��d���H�ø�T�g��t���`�a�eX�i��u$���vD�����b��r��tH�vD3vܨ��|�e��Ĩ����tT��s�n8�n�����k�����h�������	�s8�t������|�������.H�eP�tt�p�e8��xdh�g��t�r�L�t��p���	�	����a|�
��d�g�j �k,�l<�nT�pr\�th�z������a��e��i��o@�u|���)�<	,	����y�	t`��l����a�	��B	����4�kL�y�	��	t ����l	�0�	$�lH���p��������,��8���.�.y������g��l��p��r��s�#	��"	�����0����t\/	�����z��	�Fj�k �l4�r�sl8	_Ա.,�el9	n���T.t+d��tl�àP��;	��L�t�;		T�s�;	��`��(<	����x�e F�@��L���d��k��m��r��s��zF	������t���̲eS	t����k�l�r�s���t4X	��W	���zh�,<�	$�rL����TL�t��t����g�r��|�r��	d�t��,�	Db�s�����h0p��t���D��������e��T��nP�����i����m��s�#H���������s��	�gĢrl"v�z��j`� �k����(�a
	��@�l@	H�g�k��p��r�%����
T�a�(d�e��i8�mP�o��p��r����|��	@	��D..��i�	Lt�		 �d��k�	��������t���������y�	����t "	���bg� 	�g<�lH�md�rp*	8<*	(�m�)	��0�e-	.T�e@C�L/	#\/	��\�a|�it0	�T0	t�nX;	��t+d�6		��rtF	��\h��t�B	��n��tx���������8�������J	K	����a$�i��XK	,4K		��rDK	�����K	��K	�n�b	p�Q	��0�uT	���/d�Q	D�l`�m�T	tLZ	��LK��X		h�r��t�Z	�Z		P0sl���]	�����d]	����u���|`	p�`	X�i�`	����eh		�	rTl	�&b�c��d��j��k$�l8�m`�n��p��r��s��t�k	����a�*b��c�d��ed�f��g��iH�j��k��n��oHGp��s8�tT�u��v�x�z�è��<m	�Hm	��|��m	����e�o���hm	�����Xv�o	��`�t���`o	.o	��r�t$o	����a8[c�k�n�t(vt���(�.�o	����	�pp	��(�d0�k�lt	��̣.T�i0t	����.ht	H�t@u	���a�+��+��l�a�u	��t�lhx	����y	��̣.��i��n�y	d5s�
Up{	�@{	����l({	��l�z	����e�|		5g4�lT�rp�s}	�����t����@�����l����� 	��d@	��(�a��	1D�	@�kl�	��H�a��hh�u|�	�܂	���v`�	�g+l��n��	��|�a��e�h4�i@�oT�s��à,�4�	.$
	��	����tp�	��l�_L�		��l��	��������L������	t��	.�i$�o$�	R��tx�	,�s��	�Ps�	tp�	.�,ap�e��o8-z��Ð�	�p�r���Ѝ	�|�j��r�	�������������	���O.��v(?�xD�D�.�	���rЏ	_���	���tD�	�Ԑ	t�	��b�i8�z�	���aH�e�id�rx�u���p�	t�	��	@�bp�d�g��k��m��t��x��z�jt`�	�(�	��x��8�	�����ԓ	�X�_�z����tD{��	��	_$�		��g��n��s��	�����8��D����_ȕ	���	H�	���k�	�n(�vH��Ԗv��	0�tx�	CT�rPRs`�_H�	�T�	��\�a̶�L�	p�ll�	`�.��d�-f��g��k�-l��m��n�r$�t ;x8���p��	����Ô�	��\8g�y�	���u̡	���:uȢ	���yl�	��ddjtz_,�	�@�	���aH�È�	_d�		4�r��	��<��`���	��		X�ll�y��		l�b��g��k��l�;p��r0�tH�v@�	��	����e���L�����������È�	����r@�	.�;e�'tT�v�	��T.�.d�t�.�xB���	���et\� �	�����	��@�e$��԰	#L�	y��س	P�k�	��X�et�l�	8�	�ص	��|��ȵ	�����$�	��d�=l\�	��	��a��eX0g��i��l�oT�r��u���	8�	����l��		��l8�rp�sȷ	��������H@��@�������`�	�p�	�� ��P��4�	��X�o`�th�u(�Ä�	1�����	8��	p�	����s���D�	|�c?g��i��l��n��r��v��������������Ì�	����j��kH?l�s0�	�����c�xv��	��
��b8�c�gd�hl�it�k|�m��n��p��r��s��tL��yv��\�sԺ	��@�����(�_�:v�	�L�v��vl�vԉv�v,�v�v ��P�		��j k��l�Us��	_�P`�	����a�	��n����	����o�	�м	�r�	��D..`�bh�d\np�ox�t�		�d,�	��<���	��H��hy��y����y4J�h�	��j��t��	���	�p0��<�tȾ		��l0�	Y(�	��e(�gP�kl�l��m��n��o��r��s�t�u��L��h�	����<�yD�	�rT�	���a�Ve��pH�	��8�a\�o��	��@�r ��`�	_p�	��d�lT�p{.��	��x�ah{i��	����d��g��h<�	y�	���	�\�_t�	��gP�	�����(�	��0Bk8Bm���|��<�	��l\�	����aH�	Y�		�Bz�_v�_��(�c^0�k�P��<�e�������T������\��L�	h�c�d��t��	��t�a��eH�i`�o��u���h�	�t�	����o��	��,gn��		��r��	�����4�������,0�����z��	��s������������ì�	���t`�		(�r4�	���	@�o���Hnd��	T�l|�r�ej�e�P���p�s@�		��sL���	��bT�	�.p�	����a��o�	�(�	��r0�	�b��k�l�r��x�	��4$l�	���Ed�t�v�\
_�	����g��	p��	��(�aX��x�		0�dx�r��	1��	��P��d�	�t�	��d��0�	��l��t�		�Fl�Yn�{	x�		��z��	��������D�	���k�m��p��t
�(�	����i��rl
	��	���	����a�o�GP|�	��(�u`�	���th�	�kh�n��	��(�ad�e�h(�i��o�u�����Őv
	��	��`�nd���	t�i��k��t��	��|�a�^f�6i�o̦r �s,�u���p�		��r<�t��	�����x�����D���D�|�
��	��	������j�Ȉ
�nDX�(�
���zn�
�l�	��4�a4�
��	��H�e��r�qy��	P�g��l��n��r@�sl�t�v(�
���		��t��	�����(�	����t���К
�������	������	���n��y/z�
�h�	@�z8�	����e �fT�k`�����l����ü�
�ȥ
��,�z��	��4�s��_��	L�sܩ
��T����
��`���		�j��s��z�
������z�rk�o�������
����k��m�����Ťt�ئ�Duk��cTu�����,T�
<�
��	��s��	���o �u�����	,7dd]g`�l�Inl�p��v��z��
p��	��L�a��	��T�l,�	t\�	�h�	��t��t�	��|��	�<�	���}l��	��m��s�	,�	����zL�		`�s��		�k��r��	������	��h".,�
��	���a��	�r(�s4�t(�	��Ls�t��4�		<�sL�t��	,Jg4Jjh�		H�		d�j��s��t�	��	,`�		�g��l�Ln��r��s��tX�vz��	��<�t�		�	,|�		�Jr$�	�0�	��n��	����e
#
���at&
p�
���u8'
��b�Rc��d��gL�ih�k��l��mt�n4�pT�rp�s��t��x���(
.p(
l�r�(
��t�����8)
����r4
��0
����r�R��9
_�9
������8
����r<�y��ð=
��=
��f�=
����a(��)��=
���.4�f>
	�f>
����>
��>
��#dA
D�k�E
�� S����0D
��P?o|�uX�ÐM
#@S
9�N
����d��i��k��o��vxc�hW
�wvl���d k,[
��k�^
9 b
�,`
��rl`
����a0�i��4c
���t�b
	�r�b
����<���"�j
(�l�m
�p
�lo
D�d�xg�k(yn(�r0�s��t��v�o
��L�a��e��i��k�o|�s�dt��ôq
��|	t�q
	��t�q
���������u
̤g�yl��n�v
���	Hy
��m@�nLz
j�z
��p(�s�{
�{
.�t�{
���z�
���SeD�r��
��
�\�
��L�ad�iĐ
 �kD�
���So��z,�
.��f��
th�
����e|����
����j��t��
��d��
����i����
8�a��bH�c��d��f�g�h��i��j�k,�lh�m��n��o��p �r��sh�t��uT�v��zd��`�
��dH�p��
#�
��gxTr��t,�
��P��8�������й
ܹ
��|�a�
���T�������|f�(��0��\�
����e��l�To�r �u������y �
��s�
B��BT�
���B�����8����
���
��B��B��B�
�`�
��@�ex�i��s�dul��\�
Bܽ
��d����
�4B��
.��k��
�$�
����eUi�r(�z���h�
BH�
���������������� ����
Bl�
B��
B�
/�
�������
����p�
B��
B��
���y@�
0�gX�k`�lh�pp�sx�v��
y��
yܸy(Gy����\�.��
�� U�������tU���
��PUe��i��l��r��u������d�
�p�
�P�B��BD���`U���L�
�t�
�\�B��
����r�
�(�
���a�fe�Ui��uD�Ä�ŀ�
B��
��<��|U�d��l��t���U��sB{B�}Bd~B�~��|��������0�B��
�g��p�Us��z��H����
���fe��o���<t��wBH�
�����T�
���
��d�j@�rX�s�
���a��e��i��k��op������
��$Cc��.��t\���L�z\�
	�ll�
��d������������
gL�
�Dd�B��	�(z�������*�������
��Hnd��
��l��r�����
����t��
B8�B|�������
	@�z<�
�X�
��$�adVe�Vi`�oP����
B�
��H��lV���
���
���Ve��u���Xg��X	B�
���������b	�pg	B��
�(�
����e���x�	B��
�����W���
.��
��k4�
���s�
�(�
��b@�gH�kP�nX�p`�sh�
���a�ge��i��o��u|��D�y��
y��
y�
y,�
�x����
	h�d��
��p��`W�����
'$�
��a��
�T�
B��
���
�j��
����aX�e��i�l�sH�t`�z$��
.��
/�
�������
������
����X��X�
l�
��4���
��,�r<��(�
B��
�
���Wehi��r��u��ô��P�
���W�h�����������B�B�������������0�
B�B�B����g�n�r�tP�`�l����	$�rh�s���,��ph��h�x��8��hhep�i<��8���$�BH�	��r,�����,����e��s���x���\	�hk��lH@m�r4�t��vtYz����Xe�hi��.�x���X�i�T4��T@e4i�\D�8B�� ���A��(��,P|�d��h��k��r��s�t4�v�P��@��0�WXU��t�r�]��]����a�`Ba������`��P�u��Àg��g����a��i(l>�t�\p����p��tTv�px��x���e ��L�
BD~
�����z��z��,�aL��{B({��D���.`�ex��X�sd�c��r@�u�~��
p�a��e��fPok�o��p��r8�s��up��H���̣.��aP�b`�e��h��i��m��n��o��r��s��t8�u0��D.
,���.�l���L���	�l@�nH�t�����x���q6`r6h�U`t@ZX�np�sLxt����:U��І��l��n��s�{����U��U��y���k��n�jy�U,�� ��0�����a(�e��ЄP܄����0��$�.��P`����j�����H�k�]n��	P�n��t0���`��\������m4�t����f��g� l��s8!vd����(yD��P�����������ð���Tbl��u��#��h�����l����k<�r<�
�d��lغ�� �eH�ň���,�t0������d����	T�l�.s����l�d��j��r(���t�atgi���d�����$����	��r�������q�P����f�.gd�j�pPrtL�����o���P=
�����l��.�a|�e��i��oX�����z�@
	���D�l���L�����|I
B�I
��h�b�C
�p�mL���pV
���x����r��<&g��T������oH���l$�m��t������a0�cD�eX�gx�i��k��o��r�s��t��u���������a`�i@��t������8��lJ	�-��L�s�T�sx�Tnl��nx\s�4t����l�a��b��c��d�ne�f�i.m8�o\�pd�r���<����y��,����G���T��l���U,�U@�U�����U���$�l��N�����l�t(�0�lH�z���5.��e�U4�UT������t�k��t4���|��\����x�����i��	��d��f�g��l��n(���������h��������a��tT���lh����a��e4�g��oH����_��	(�lh�r��t��z����0�����t��<���kH���\�ax�o��Xx�n������o������,�t���l��,��	��rP��l�����a�eP3�������n��y(���a �eX���	�l�:,P$���(�h(%	�L<�i|6p�6��P�a�A.�@��d�c�;l�n��z�I��oDNHBz�M��s�M����a��e�N a`�.��b�d,�g@�k��l��m��s�a�x0
��.������a�Ä1
�������pc��b��$�r�c#d��8�a��e\��dd��d��T��t���G
��d_l�r�e��e����a@h�Th����a�n��pTs�t#�t����e4r	��k��nl�
��u����o�p������it�T(�l�����aH�i�Ws\�z8��`�td�e����0��T��p�,l�tl�PЛC��l$���d��������H�����i��op��t�_؟�����zT������rP�e$�	� r8t�.��e\�����z����sD����lܽ
�b��d�g�j�k�l�5m<�nT�px�r��s��u0�zh����a�cb�d8�el�f��h��i��k��l�m\�o��p��s��t\�u(v�z��h��d�Px�����a`o��ø������H��8��D����a�vk���0����e�o(��4��l����t`a��f4�j�`k@�ml�_��.�`a�op�t�`y��.d�o`���������tH�h���l�ü�����sd�_@���tD���pbr��	��b�g��lH�ph�r��t������t���������������"�����y�����g4�yl�B�8�tL�������W(��<��H���@�o���`�T�k����\�a4�b�^f��h�k,gn�o؊r8Xt�u���(��8�������O�\Q������_j��ì�X�n��X�s|�^��������a�ck<
l�cv�L����r�������$�z0�
,�d�9fp�g��k��l�m�n�p�Jr$�s��	�,���h�e���t�QL�	��rd����������ped�_������f�m�o��	�������Jl8����c�JeLg$�`4�����i<�r�����t�����e<�������4�����\�H�l���P�e��i�k���	\�g�l�r�v���<�	��s8�������������d�	��s�����������0����Ov<���T.zi�Zt�����Le<�h@�i,��4�	��n�LtP�����\�fp�8�.����	L�lX���T������P�r`��Pp���|�n��s������i0q�x�����o<�	��d�g�n�p$�rP�s�gt\�z���.�fa@���gd��f��tPgv���r����a������go���0�n��8�eH���D�t����0�o8���
h�u
p�t4
��|�a��l<�rX�v
��
��g�t�
����aL
 
�.��k�r�
���e@�o4��P��`%	��/	�\/	���e�<	�X;	���v�6		�r
��(���Q	�ph	B`\	��H��T
��b�Nlx�r�s�tx
���c��d��o��r�Nv|��������z|
��s�
n�
����a���8�
�����
��L�k��zh�
��Tt4
	�r�
_t
	�bl�c4�l�jn<�rT�s�jv�
	l
.�dL�e�
p
	�
	�jr�
��\���
h
t�k�
��|�e��r��u������rt
����p�t�z| 
�� 
���a8��#
�#
�i,�k4�m<�s,#
���e$"
���r!
P�eL�id��K�`#
x#
8�#
\$
�h$
�D�g\�l����!
���l��l��l�m�X'
��z�%
|�s�%
����a�e�i0�rv�ü{
)4�
v�'
	��p�'
����8�	v�)
���h�o)
�n*
�ļ
�,
�l�'8��
�z|0
��$�e�miT�oH���0
���P��
�X2
��d<Hjp�tp4
.�ma����4
���D�6
_�4
	��j�7
	�ngܔs�t�zpW\9
	��th9
�����D9
���ŀk\d���sĔ	�sД�����t�����1d�9
��aDiP�\�8��<n(:
��0���J��<
��\ot;
�dk�p;
.	pa<eܚf�i�k�sPt�|Ŝ>
n�>
����l>
���e���>
�xB
��B
���� �LB
��hSk(l��?
��r0@
����� �(��]��D
��ry�C
�0g\lhn�s�H
B�E
��TvJ
���\e�K
8�K
tm�K
��|etN
y�M
��jȕ��S
���i�R
��m�n��sDT
��,�g4��V
���là�	DEv8������t`]
	�\
�l��s�������0i��8t�`
��Da�i��<��a
��`
hs�`
��paĦb�t|`
	|r�`
�������h��@��������n�a
	�k��r|�_�����l���ln��
��i����ab
	�l�d
��ug�
t�e
v�e
��8n�e
	@d�e
��L��e
��XØe
�dr�]
��p��l	�����f
�cdgHk`l�n�r�uPf
���a�b@�d�e�af�	g�	h
ikPo<
pL
st
tTuz�0
��g
j$���k
��j
��r4y<l
�l
.,a�m
_�l
��@kXn,n
_�o
��̣.�a�be h4iHjPk`llmtn|o�r�t�u��4o
�`o
�d8B�\	����,q
�����p
	�rq
�������l
�Xq
thq
l�q
C\��$r
.@a�q
(nr
�Tr
�|r
�x�p�r
��XoXU�U�r
�d�U�s
b�s
�4��u
�����t
���t�ìv
	�x
�y
���a�}
$��.�.h
���s~
	�c@gdn�r4sDtD~
����L	��L�
������
���o.Pa�
�؃
T��t�
��Xa�
ܒk�m�s�tL�
��pa�iu��Ć
�p�
1|�
1��
	h	r�t��
����� �؇
�H�
��a�g@�sTO��
Cr�
_��
�k@�
�Th����p�
��(k�xz�
��0�aTf��
8�
�8�
��	\a�ePi̩jpl�m��o�����
dl�mp�r 	s4	z��
��D^� ����
�gl�m�nsxth��!���gDbBPb����ԗ
���t%��
	�:l0rH�
��T.H��И
��<j��
Dn0�
��
\n�
��de�9	�t�
v|y��
	�l��
������
����h�
�ܙ
	�z��
������
��0_e��lğ
��e����
@�z�
}��
��	e��
	t8�
��	e@�
	�
��,	r��
.d	eL�
	@	gt	l�	r�	t�
n��
�@�
��l	e��m\�
��
�	t��
���	et�zȵ
�k��
���	e��t\�
����l�	nȮr��
�z�
�	tL�
���	ol�
tT�
	
d4
gL
k�{l�
m�
n�{p�
r�
s̻
.�ba��À�
P<�
��D
o`
r|
u��
	���h
s0�p
sԽ
���,pо
����d\f��
�����
�x�
����o�
ÄI	��
���{k8Bm�
zp\��
.�
a�
oPX
B��
	`Bg��z�
���|l8o�br��8���$s<�,s0�
.te��
Db�k�n�p�r0s<v4*
���
�j�k�
��|a�o��
� K
t�
�lt�
e��
���� �
����\�
���3l��
t��
�s�
���aið�
����`�
Xcl(�
��f��
��$z��
t`�
jX�
	Dapf�k�n��p�cr�s,�jԠU��
	xn��
������
������
���
�����
���n�t��p�	��	��
�p�yt��
�08d�
���t��
	�n`�s�
���l�
	
rP�
��$
�,�
����l�r��
����p��
���
X
d�
m�
n��
��`
a�
e�
i�
r(uHy,t
�H�
��d���
��s�T�
���
�<�
t$�
�(�g��
`о
���
a�{g��
�
n��
���
���
a���
���
���\��$�
 k8n�
�DLX�
P@i��
X�ghn�r�
����dxi�
�t�
�,�
�lL�
���a�à�
Q��
	�n��
����0�
	��j��
	�g�lL�s0�t�
��P'd���U�������i��P��g�mn$rTs`t�z���
Db�c�d�f�g��lhm�n�r�s�t�v�z���La0e�i�o@�p�4��4 �D t�	�	t� �!�P!t$A��t�!,\!	�lt!����x�������H��4"��!(gPlXn`rhs�Wz@"t�"t�"��"t#��"	pl�r,#����@��`#�d@l�m�n|Ip�s�z�#tH�	�#���jT$t@%�l�r�%t�%e�%�'��P�a�o�+#,��aLu@��,��,��8��.#p/����s;���ax�utÐ<�����C�b cXd�f�hi0jXk�m�nr8sdt�vz�ÌE���T��|f�LE���ei�Tor�Ð��,�B����E�F��E��i8��(B,��0�LIB`I��D�I��pexo�zL�tI�\J��J��Ky�K�l�D���� L�K���r\M�M���a�fe�Ui��u��Ō]B�]����t����~��|��\�B���dMf�N��N��(e@èN��ܿ�����P��O��Pe�l�upÔP���������lQ��QB�QBR�� 	�XX���e��u��P���	�����l�	��X���e���	B}	�����t�	B�[��Z��e�-i�-o(u�]�_��]��0eLoTp�_�$`��b��b��\a�e��cB$c��x���������hc�eBL�BtfB���g���e�i�o��g��ph�������c��g���e�h�/c�g�lMr$1t��v��z�<z����eD��P��ԓ2��Pl�.Xa�e���dsd�tc�d�rpt�����a4eHo�r�ä��$8��Д����t�����d�$���i`�j����ae(i<o\sP�x�#��ܜ n�����4ĺ�Л��H��j�4�n����da�4e�i�o�Ü�(���n��4)l8����&���]t�����.bn$r,s�L�	�s������l��c�eP@����ph��
��k���.h�
8�@tl�
��`��D�	��T���	`k|s����Tt�����,�	��d����������ô���bH�d`�nrsp���	�ax�bLe|f�i�o$p(��D�t�0�_h�	g�����h���4�H��t�Df`g�x8�t�	�p\�h���trp�t���m�n��o�r�s�t`�t�t,�t��.4��b�p��t��	��dP0s�g	��	�rs�������	���� �lT�r��	hjj�f_�	@lpH��P�nHCTk�l�s�B��`a�b`e<f`g�i�m8o�p�s� t� u$!z �Lm�K�g�K���a�k�L	�Q�pR�]Y�]���y�[���n�U	r�U�����|�� �!�Tee`e��<���pe��DàcTd�g�lPmpn�s�v��_��	�k�f��e���yj��i�m�t�h���e�$�tp��j�l�j���e�jQ�j	�r<i����k	��l����m��m0r�m��8em��DbS.p\g,p��de�Mn̒eLz�����y���e����������������Ìy�m�t����	�d��gjkl��p0s�U��U���NP�����|���$�0�����l��� �Hl�tT���Pa|�
�X�lt����to����<�a��È��g�n�r�s�����f��t@����go����0Bk8Bmh�pS	��Q	�kئ���ou,ì�x�k��spg	��	��$�t�4ckll�mps\tȩ�ة��X�d���`ì��̪��
xa�b�f�m�Tn�r�stz�� �
��	�l(�������<��t����U�Q	U�`	U�_�n
U�U<�j@��<z��p����(ax�.0tį�Я��H�����P��
T���ha�	pd�rб������������$����l�r��d��$���di,k8lDr|uԻ.	�a�e i��o��sx t� z��l �,��2k�y�4��$n����LLl4���PHa��
���_PkĽ��X�н�d�ܽpt\�����g�l����� �� ���B4������c�m�v��!����e����b����$�t����l0 mD sX t<�z ��8���( p��K	����< t�������P a�������d �4���P�rd�B4��� l,����.� e�
$���� s��� s����� o���d� r � 4���� a��	��t��	��g��t��z�p����!iT!o@!�Ԝ�����8!����t�L!t��8�.�!a�!bh"c�"d�$e�$g0%i�%j�(k�*l�,m 0n46ol6p|7r�>s0AtCu<Cv�Iz�!�8&�̉@������!��$�H%�<6�(&��f�C�����ha"i8"o(����"sH�h�sx��� "a��,"rT��h
s|(D"sTP"u����\"i�P��Ot"c�"d�"l�"n#v#zLO��|"ah#b|#e�#i$o��p��s $ut$z0#è�żP��R��"a�Rm�R�"gDW��_��_��`	H`	#gP#lX#r`#t|`��#��#����@��@$�L$�8a	lb��g	h��@�l�k�@it#g�#l�#m|n��s�T|	H�g(�l0�p�#rԃ��T.8�z �.�a(��x��#g�#n�#r4�t����t��#l$m��s`�t��\�d0$gP�t �	��	8$j0�	�g`$n�zĽ	�������L�.�$sh$���4������l ���g�����$a�$e%o��t$%y�$���@��$p��s��(��	�$r$����$���p�8�9��%�03.%ô�<%r������o`!4R�����P���Q��P%�XQ`%d�%j�%k��l�%n��r�%u�P��
l%a'e$f|'i�'k�'o��p<(sX(u&���R��|�c<���T���%y�U�4�,HV	�%c�gL&rt&s�&t'z|V���%�l'��'�(�p(�|(�X80X��8&��W��<�ed&tl&u@&�Y@Yn�YH�&z�Ze�Z.�&e�&k�V
�tj�&a[n([���&a�&e�&h�&l�&o��tX�u'v�xX�[4\@\�s�\�\,�^�^'g8'lL'mT'n\'rd's�`�D'ic�Xd�|d8e�Pe��f	��p$�r�k��d��m��n�'r�lt�'�tc�|c���'�m	l2g��ndp�n���'r�0
�<t�'d�'k(l(sd�thut|ut8wt@z	�n,(s4(vL�z�z	�z�\|���k�pxt4�t�P(gh(t��$�	��t�	��g�t���	b�(c�(g�(l)n)r)s$)v@����(a|)ePf�)i*k��l4*oH�pt*s�*uL)ä��؏�D��h�t,�\�.)z�����<�_̛	,)bt�gl)lt)r���4)��)�*�d*��*���d�	�,H�~g�)l�)mDs�)v8�������	�k�)r����T.,b�ň��Dd�)n�)r$�����g���x�	�	�)npr�����r�	��� *l<�(*lH*pP*r�tP�����n��r�/s@�	 �lDEv�����pxz�
L�g�n�*r�*t�
�h�h,	�*tp�*j+k+l���	�*a�
b|+e�+i�+k,s,,tp,u,+��D"��4	�gP+sl+t�4��+��+��+��+�dfTd+kpB��D+i�sl
z�ftTD_hY��Vt+gd����z`w	�+r��a�+m�+r�+s4�������tx�	�r�v�\����+r,�	�s`0z����th�CD,r����,�h,�����`,e,�l�
�����<,kX,oxxtȈ
nD��4�e��tr�,s(/����,b�����,a�,e��s�,z<�#�6	.@	�,n��p����,ap-b�(d�-e�.i0/kD/o�/p�/s�/u(-ü�p	,�		-lX-rd-s�	��-�8.�$/�x/��/�|	�\	D-s�	��L-i$	���s�	���l�r "	�� 	�-g�-l�-r.s�*v *	��8 ��)	��@ m�-�L/	\/	���-a�-e�-è�D0	���_��/	��/	�-g|1	�1	.mX1	��.e�7	��th7	�� .e�6		,.hP.k`�pX.rl8	,X;	��T.̦r�.t|.Ø.ń;	��;	��t.��A	Ī.�i�A���.�(<	�8<	���.e F��B	$d�.g�"l�.n/r/s/vE	.�1at��tF	���"d�"f�H	���Ia�I	t�L	�hM		M		/r�N	��$�rS	��Q	</k`/lh/nLpp/sT	�T	�W	�Z		$k�lP0sX0td]	���0r�`	���0p1tHd	��b	�/r�/t0f	�h		�g�MnNtTl	4Nb�ch0d�0g�Nj�0k�0l�0n�0p�0r�0s1t4du�4z�k	���/a�*bXd�1eT/f�g<4i�4k�4n�4oD5pX5s�t�5u6yD1�m	.�'a|0o<�l���m	�o	�$o	���0a�0k�o	_pp	��Oa�0jH3k�r		@u	���a�3y�u	�xo��(G�hx	���(c�0��y	��\s�z	���l1td�	�|		�)bh1c5g�1l�5p�1t�*z}	�� 1�$3��4�5�5��5��5�L}	,�%�p1j���x1a@	���1h�m �	
	0�	��	�1alcef�1h�xlm �t(Pv��	8l�	�-f�-g2k2l�2mPn3s3vt�	p{.�	��2e��	���c\2e��h|�jdkPSl��md2np2o 
r�2tP
v�2à2��	`�	���y�	`��	̟	��x2�h�Q�		�2hD�	���2��r��
���	����o�2�0�	��	�2l̡	���2e��l�2u��	``��X�	���2d�z,{���;��		��		3gH3l�3p�3r�3s4t04v@�	.�;e�yth3�|3������`3�T�	�d�	��t3�ĭ	,�	��T.�.d�;i�l��m�3����d�	���3���	.�TaUe�3ilQo��	���3z�Wy�W���3t��	��3n԰	n�	��4et�	��	4nL�	��$4e(�	hAg�Akl�ld4n|4o�Ar�4s�z��	����d�Af�g��t�	H(�	��8Bm�		�2z��	���l�4rP�v��	� 
�\�	���4���	P�4�p�	���4y0�	�b8Xl5p��r�s��	tx�		�lt�		�Yn�r�s�t45v<5z�	�l�	���	��@Gl8�rP�sD�	���k|5o�GpXt�5z

m�	t5kH�	����	�5gH�		d�j��	_`�		�5d�g�5l��tX�v��	����t,��	
	�5c6g6l 6r
.�5e(6iP

��

H
��
	XQn8'
�
H6v8���Wo���T6g�6n�~��\6a�6i�6r�6u�6�8��������c��	�6l0����6��6�(�_l�	�6l�6sp����%s��|�z�jL����6o��`�j 7sL���7n ���7ajs47z���h����<7�����D7�H�P7d�7k�7l8pnrH2u����\7a�8b9d89e�zfd:g�:iX;k�;n�;o�<p�<s0=t�=u@8��<�@�����c4�n�7th�	�����ta�7j�lkL�	0d�����7l8r����	(Hbd8g�Hpl8r�8s�8t�8z(���8�:�0;�<��<�,>�`>�h�,0���h".�8aIo�qu���kH����8e(k��s�8z,I�0�#�P��
`����8a�Ie�Ih��s�3v�	p(����8a\���$JlTJrl����9r$��9���� 9�L	,9d|Kg�9k�9l�9m�9n<xr�9s:vQ�	d9s���l9�d��x9���9a��H����b�-l�9u�`.�`e��"�9m�"���9e�9zŰ#`#.�9k�'t8(		�Lg<:jP<kMl8Mp|<r@esL:tX:v�)_�1Xt1��D:e�2��<�h�6��4Nl<Nr?.hOa�;	t:g�:k�Ol�:m�:n�:p�>r�:s0?z����?���:o�@tDB	�@���:dPf�>j�:s$PtHC	TE��F�;z8H�DH��;�PH.;��K	�J	(;nH;r�PvP;z�K	�L��M��hQlD�r|�v�^v�^l;k]��t;o�a�a�;b�;g�;k�;lTr��s�TvAx�b�d��4$lh/m�e.�;a�;d�;v�e��e_0_�s��$%�`<�4r	,<d4Uph<sx<t<�(s���j�FoX<tL<�l�
�s��D<���y�
�|y���Up��z�y�<rz�}	�z	�<n�Ur�sؘz��,	�<r4Vsd���<�������̉l`Vr������h�Ak�ApBrBt=zl�)=t��Ԕt�=g`=lH��� =a�=h�=r�=u�=�l�8x�L=p����T=aȵ
����l=eЛ	t=t$����=�h�Tt����=a�8���=ṇ���=aT�o��h��=r0�.�\d>i���=n,Dr>s$>td��\���,ejsP���	��j��rȎsD>tH���"j"n|^o�r"t�	��g|>l�^r\8v�z��.h���`�p�>t�>z�%
��l�Ì<
t;
��>j?k�l?s$?t,?u;
.
�>ah?eL�g�@idXk�@oLYp�s�@t�@u<Dz<?��@��<
.?o�J�h?
�?
�?
��?
�0@
��4?�,@��@��p��@��@�A�<D
��C
�`?b�?d�?g�?l�?m @r`e��x���D
���?ôD
�TF
�E
�?m�E
���?e�?l�?nTv�?ŜG
B�G
yH
(H
���?��I
��h�b��e@l�:�<K
�	�J
��@k�M
��Vg�rl�Hp\@rt@tdP
!	PP
H@m�O
��P@el@t,Q
'�Q
�� Wk�R
��@d�nS
�V
��sr�@vpV
BPX
��\
�l��n�]
��]
���@��`
��Lur�b
$�c
��d
.AÌd
��@lXZn�
t`p���>��f
|�uPf
��$Aa�Br�Bu`A�p�
����~
	HAsD~
��TA��A�(B��
���aX�eL�
	tAk�Al�As@�
H�
��8�a�Az�A�J��T.�r�A�	�Ag`�
���A�TK�pK���A��
.��alQo BsB� �
40�
��B�l�
�X�
	DaHBf�Bg��p�Bs�Bt,�,,�+	PBs<���XB��
��dB�(�
	pBp4�
��|B���
���B��
���p�Bt��
��
���Ba�
��rp�
�����
���B��
��,{s��
�Bs�h�dt��d�Cd�Cj�Cl�Cn�CrDs����Ca��b�DdEe<�fPGi8HkPHo�Hp�HrIs̆t$IuLDÜ�����|C�|����vk`�t�CÔ����gaL�j8W`����CaHg�Cy�C�x�
�P����C�x�����hcDiܜPؠ.��t����Dz����)rL�	 DblDg`jl�Dr�Ds�Dt����,D��F��H��H�PI�hI�t�,��1ЧtDd�Dm���|Da�qk,gn�Dt�Du`��\���������s�Dz��̪��8�ap�|����Dr�.��
�Dc0Ed�hg��jTEl�Em�En�ErLFsxFtL����z�y��<EkhEmH���DEe�Eg�EŨ�����b�Ee�r�E�h �� �!���E�����U,�	�ElD����E�0����e<����Ebd�.�Ed�EedQ_�����.Fe F�4F�X�����8���F������,F�Զ�mp���@Fe\Fzp�,���(�dFtķ��lFep�,h�	�Fg�Fk�Fl��p�Fr<Gt��,��X$����FeL�m�Ft�F�������F�H�8p���T.�d�i̦r(Gt<v�z�B����G�����G���X����4GeL����HGd�Gg�Gk�Gl�Gm�Gn�Gp�Gr�GsHv(Hz��.�1a��.�Ge���(�_����Gl@�t\�t��L����o��t���GlHs�����GeHs$���������Ha,��P�<���0Hlx�r��t�HHlD�plHr,�s��xh�t�����Up��	tHs$�	tb�Hr��s��v�Hz��,��,��� �l8�r�#����He`���HdL�Hn�����He@�����k0�pH�tIz��) Bk��؆g<Il�rHIt0���
t@��t�	�mj`It�	�	�grTntHv\nz�CHC	�Ib�nd�IgJjJkJlJn$Jr,Ju�B���Ia�b�dKe �f�LgMi@NkXNo�NpOs0�tPu\J��P��E�GpH��K�HN.�P�T�@V��U	4Jg�Jl�Jp�Jr�Js�Jt�U��<J�4L�0N��N��N�dP�tP�<X	p[��[��J�^��\���J��_���s�`��Ja�Je�JÐ`��`�Jda� a���J�@ap�et�c
Kf8�g��j\Kl�Km�Kn�KrLsLt$Lu�rv��x,Lzĸ�<i��DK��K��h��xKeLKÔK��t�ipKl�k��k�l���K�8n�m�Klm���Ke�K�DnBdm���K�,p.�Kep�@s�Lz������y����z�Kä~`�|��Li�$|����	��gXLk��l��pdLr�Ls�Lv������k@���T.,b�d�Le��h�Li��k��m��n̦rl�s�t<vt�z���ĉ�Ll�nD�G|����a�Lz�_t���$�i�T����Ll(�_�����Lc��Mc��gHMkȎlhMm�Mn�Mp�MrNs$Nt@tv�M� ����:o@�Bd���TM�$�����i�p\M�`�_���|Md��f$�tP���L����Mt�M�����M������Mo����tsaTc�@����Ma<���#�����MoNz��.�sa�oď�Л���aԜ	�v�z0����l4�rܨ.t�PNkpNnxNr�Ns@���@��������	�Nrг	
L�b��d�Nk��l��m��n4ur��s��v��z�	�(�n$����Neef�l��r$�e<OlDOsLOuԻ.	�Na�Oe�Oi�Oo��sx t< u��zhOè�|�ܽ����g�Or��v���TO��O��O�l��P�̾.��o�����b�Ol�Osh����.�Oe��Qt������On�Ot���D��t��k��r����_��X�g`�j0PmPPnh�rp�t0�>���(Pe@Pi�����_����HPd(�	��	\Pr��t��	��g�Pl�vr��s��t�v��zD��(����Pe؂Y��	�Pe�Pn�Pr�Pv8����P�H��8�H`����ĉ.XQa�\b$]c�]d^eljfHkg�kh�ki�mj�nk�pl�rmTsn<toL{p�{r\|s`~t�u��z|V�{�̉��c�Qd�xfdRg�RkhTl�Tm�TnDUr�Us�Ut�UuVv$Vx<Vz<��g�Ql�Qth����Qa4�mRoXRu4Rü��\�Bl����Q������o�Q���l��QmRth�
���	�lh@Rs�tx�vHRz���R�8��������,�PRs@���\�a0�gxRi��`��y�R����Rd���
�Ra Sc4Se�Sf�Si�Sk�SoHTrTTt�R�(�B����R�$�	�Ql0�pSrH����R�tS��S��S�(T�����,gn��n����Si��t��,ScLSlTSm�sL�t�����!|���\Se؝i�	dSr���t���~.L��Sm�Sz@���,t�	�Sr��j�SuT�n��.�Si���Sv�������	�Sk4������Ts���Tt��	Tl8Ts��_H�n����@To��T.�ti|u���L�g(8k��lT�ml(#�'���Te�Tu$���Tb�Te�(��*�)�Ts�1D..�TiD�l�Tt02���TaUo$Us<�y�2��4��E`�HtH.Ue8U�lG��Uz�H_XH��0U�`k���aԿcdUo�0�T����\UmC
�T��pUsp���xU� �.�U�����Uzt����k�Ur8�8�����U�H����Uü���r�Utоt8����Uo��yd��Ut����Va��# ���Vi��@`n����0Vo@�	�Va�`b�Vg\WlhWn��p�Wr�Ys([t�\zHWì���HV��f�m��w�@z�$����d�#���Vr@���
T.�Va�pb�pf�g�i�pk�ray(W���#.Wd0pi8pkWsHptWz��|��d��������� W���.��4Wr����<W�$���TWlT'�� �e�Wl�W�h.�4.	|Wk�)���W���	y�Co�C���Wa8C�Wd�i��k��m��r��vpC���WadXe�Xi�Xk�^n�XoYt@Yu|Yv�Yz0X��X��W4H	XnDXs0�t\Xv\H��X��X��X�8I����sTXz\I$�W@KTtXm|Xs�Kt L�TM�lL	�XrTO��N�Xg�XshPt@P�Q���Xi�XoxQ��R��n�ct�T9$V�����X��X�Xl�X��Ya4Y��08Y	 YsHY��(Y��[T�k|�sTYt0]10�0_	\Yl_��dY�_��pY�t��_t8_�Yd��l�Ynt_���Yaee<ZiXZodZt�Zz�Y��`��b_$b	�Yg Zr�sLb���Y�HZ����e��e��b�i,Zk�b��Za����g�df4Zt�gtLit|hPZlm�o� o�lZlXo.tZa�Ze�Zi�o�ZÀq�Ps��fs�L�t��Zi�Zn�Zr�p���Z��[
	�[
��h
Bx'.X[kd[np[t8h
���Z��g
��[��u[dx[lv��
[a�[f�[h\i4\lD
m@\ol\s�\v�[�Ti
����.�i
����.�i
U�v��we�[z�w���[��[�\�D��`���
�Lz,z���[e�y	�[k����z���[e�]��]���[�,{���[�`{XH|s4|	\r��
���
�� \��|��(\�|�s�����L\��.T\�t��`\z��`���x\�l����\i�\�P�<�4�����\s����ha|l�\r]Ŭ�����ithð�e����\�����\��	�\n���]��.8�o����]h`]s�5C�5��4]��5��<]�(4�H]p�3.T]a��s��ܟp]l���x]a��]nLO���]o�]r�]�p����]oĠulR��P�Q	�(z�P���]��O���]Ä�	�]k|`���]���T^a`�bp^d�^e��f�^g�_k�`lXdm|dn,ep8erPesft�fu�fv�fzd�@�lH�n8����j������zd^�|�.���^n��yd��^dX����^e�^y�^�����z�	�^s�����^���	Leg_lH_sH�.�^e�_ixeo�_uh_Ì_�d��$_s,_vx	<	<L
�=��4_����<_�(��4�8T_k���\_�$��\C�d�	x_n������_���������_��K���_�PK8�_r�
��
���_��
���_��	���_c`r`t�����i����`aT`e@`�,p	,`r��4`����L`ld`r�����l`j�`n�`p�`r�`v�`z���t`a`aeci`cl�o(�p�csdtܴuaÐc��t4t@�����C,ab4ag��l��r<atDazH���`��b����lc�`��_l���(�$~���LaytTag�albm0bnD�r\bs�bt�bv`%�
 �av����aedk4�l(
s�a��1�
`b���a�( 	�ab�a�4 ���a�d2Q� h �alL ���ae�
lloz$b� e!��b��!���g#�@#	<bsL#��Db��"���bi�n��t�bzPb�$�Ű#8�#|bkl$,�$�$�bt�$���belv�`%���bo�%	L�g�j�:l�bpcr�_�(��(���be(���bt�*���el*cg0cm@cn�*��`<+��8ca��f0�gXct0+_8-��}e�1	}v�1	L�a�cf�ct�c�82��xc����
83���ce����1���c���
�
�6���cu,��8����c��7.do�cÌ7���czPX
S�7���re4d�l�%9	 dr8��(d�TBn`B��@da�eD?��Hdb�e �u�O��xq�$e�tN���dd�de�di�dold�e�dQj�R2k�dl�dr�ds(St�S��S���zDV\�n�X� �	XZ���deZ	�dresZ��e���	_`�	tj��Իr�s���d��e��ÌŜ����hatee�et�eu�ez�e�D�Ű��m�et������b�*t�_��	�er����e��e����eadrd����#��	Hv�������ecĝ.�ea,t
t��fm���fa\feP�o��r|fu8f�p������z�D�	Ȩ��Hfl�Pfllfm�tX��4�tfn8�tx�.8��fr������l�	H|k�����f������f���	
l�bggThhxhk�hl�hnܸpir�it�iv���� apge|}g�gi(ho�u4g�8h���e\gr����(g��g�������\��>d���Pga�����hgc�gd�gk�gl��m�gr�gs�gt��0��(�tP�����k��t(����	<p�gr�gsL���T.�����Thdhg�m hs��t��.���t����������0h����Fn�}st���Dhe�}s��t��dhs����lho�h�����Ԫ�\�X�����he�'t�� ���hd����ha�heiiQoi�\n_����hsd�hsh��@������hkl�0��T.,bt+d`ie��h��i��k�l��m��n̦r�it<vt�zpi�p7@�g�n�2���+��(�<B��T�e,��e��Z��P�e<k��tDg��T.��b�ie�hji$jk��n�Mr,jt� vj��h.kt�j	�<njr�<tth���i�4��j��k�ll8�m8D�l���4js��<js(���Hje��Tjlw��`je�jl�jr$ku�jä����e4�oD�(�4����js��	�jsԨ���j������jÌ�	�jlh����j�Ь��la�e�i�ok� ���|��t��'��P�a��kn�
P
��0ke`k�8kltkr�l
��Xk�d%#�!��lku<_��|�e�]	�kr�]���k�M���kô��kdlg�j8lk�flLlmlln(�o�p�lr�ls�ltmvH�z�#\����kol�L�����l�d���${a,l����H�����`�rT�#p���Dla�i�����p{. ���`la,d��f��g�j��s�"t�l���	h�b����l������a�lo��r #t|c���`P���0Bk8Bm�lt��_����a���8�������l�`!	��g�)rHmtTmv�z(B	x�k`A��0m�0A��<m�<C����edm�LD��������hT��L�jXQtml0�z�P���mane@ni`nopnp�mìYHV	�ms|V���m�Pn�|n�\b��ni�t�`a�ms�`���me4n�^�ml\'r�b��:_�9	 nla��(n��k��m��zm	��v�z<t(l(sL{����r�	��gĔ�����nr���np�nr�nu@����naoe|oi�ol�om�ooH�pdprtps�pu�pvho�x�	,����ndĚjH��Cgolܑs8������(��(��$o��(��,o�Ŀ8ok`���Doe��	Pop���\o�Hp��p����a��d�et�8���$�a�2i3o�o�<��H����o�� 	T����oe<��Dlpr�
,��	�os�����o�P���pi�o�@�t�pn��<���(p�����0p�l�	<prL�����-����-iTp��ptP��
�ps	��g���h-al�`b�pdql$qr,qt@qz���	�pa4�b�qeDripro�rp�rs�rthq��p�a4"0�pD"��qa�l�,t�.��$e���84��8qopB���s�4	Lqs�qt�4��Xq�(r��r�HD�
TD��|qa�nhY���y�V�qg�ql�qtԘ�H`���Tl�wn���rX�r���qe�q	�qt�q���q��o���qì����rl�w��re�Is`w	rc*v�.Tro��8rk\rn��������d��f�Zt���bl�rn����ri0Ø�#,�	�s�������rr���0p�t��������rr� 	�g����reso`S	�tS	���r�S	��s�Q	skD�ltq	�pp	��,se�si�s�Tl	4sl�k	��Hsati$ts�säq	2�q		lsgq	��ts���q	�ss�	1�	���s�l�	���su�s��|		�sr}	���s��s�0t�|�	�L�	���a��		�sv<�		��	��tg(�	tnD�	���t`�		�Jg8'
�tghuk|ul�um�vn�Spwr8wsHwutwv�v�\8
Md�tk�tl�tn�tz�8
��ptaueh�g uo$�u�t�Pu��8
��8
���ta9
���	t�9
.�9
	��cul�9
���t�u�4u���t��:
,p;
�<
t=
$�l�r��s<C@uv�_�=
��=
��Hu��J
p/z0D
��\uo8s�t�N
��Pca�i,`
T�ug|n��r�Ssl`
���ua�uevfviTvo|vp�u�lv�`
t�b
t�b
���u��u�<��̟�le
|h
	�Ltvv�i
��i
jj
4�j4vk<vsDvv8Itk
t�L	t(m
t�l
Lvldvrhm
tn
������v�@n
jTo
�hs
���
c��h�vi�vs�o
���vc��dt
�4l�t
\�
�Ȅ
�vb�(
���v���
��T�gĐ
�vn\�
���viwr w�(�
��T.,�
�� ���
����oD�
��,wt��z��
�idwk$�l,�r�St�#�
#�
��lwa�
	
<{a�wb�Tc�wd,�f�xk�xl�xnyp(yrXys\�t���\�
����l�wr��
��
���
�wdxk(xl4xn<xt$�
���waPxelxi�xo�|z�x�
�PV�	��
�� xl�
�t[.Hxo`^���
T\xl�
���
���
dxg|xz��
�H�
��
�xll�
C�xrH�
���x��|���
��
��4l�xr�
���
���xoL�
d1sX�
���xe(�
���n4�
����r��
	<k��
��y�h�
��8yiy�$�
X�
���
@ylH�s��
��Hya�ye�yizo�sX�z�y���
���
	|yl�yr��
���y��y�z�0z��_���
���ykd�
�g�ym�
�4�
��
	�yn��
x�p��sx	
`�
���yo��
zd\�
�<�
��5dD�
	$zl�nn\			�Xdlzkxzl�zn\@r�zs�zt�zvtYz���Xo����X�)��t�t�?����vF8�A���zv8F��T.�ze�zÜF\�.�zz �1�GWtG	�znxF���z�,P	{r@{z�P���z�t���g��$6l0{t�i�doT|#�{��8{e�~��`{lp{rH�@�����a�eL����e�3i�Bo�3Ì��H��{d�{f�{i��p�{s�{u�����{a|� �d�j��j��jHT��0��{l0.�{e(|�d/���{z8(	|s(���|�T0�� g�l������4|e�o�|�ܽ<|vh���P|a@�h�|k�|n�|p }t�}z�|À�t ����|��|��x��T.��	�|n�|r��,������4
���|i��h
#H
#�
���|i�
����e��i��o��r}�
��'��%
��'a��ip�oT}r<}��'
��x'��'�h0
�|0
��L}a��u����s�;
	d}s�;
��p}��;
��|}�t;
��}b;
.�}a~k<rt�}�d	`p	���}a$X�}d�@
���}a�?
��}l$�r0@
���}�~�V
�Xqn�V
.�g
`�g
��~a[o$��f
	 ~d�~k�~lnp@rXsdtpuPf
��
4~a �e�af(�iԂk�o<
pL�r��s�et��u��(��,n
	�l
���~nXp
`o
�~p�o
���~a�~k�~l�~v|r
	�r
_t
_w
.��a�t
���~yXw
j äw
��w
���l		�y
,my
��4o��zH{
�z
��Pp�{
���k�}
h�t~
	0�c�g�\l�r�s�tD~
��|�������D����������
p�
���a�
�i��kL�
���a�kl�
j��ap�
���xz�
�
�
���a�
�qgT�kp�l�m0�nX�r��t́v��
p{.P�
��H�e�
�g��m8�
��`�e��f��h̩j��l�m�o�p 
rL�s��v��Ŝ�
}|�
����ed�z�
_t�
���
	Ȁl��
��Ѐ���
��܀�ܙ
���
����(����
����l@�
I �
�r��
��$�e�zؠ
�
@�dXUzğ
��H�e���̴���	h�eܩ
��p������|�ŀ�
��t��
����eȹUԹ	��s�������
���o������
�@�
��0�e��mL�
	�l�r�s��
���l�m�
��hsT�
�adL�m|�n��p r��s��zԽ
.t�i�ph���
B�
��`�����о
����f�{g�t�
.��i(�
���
��8�g��o��
P��
	�rv̂z��
,�
���|l�br|�
���d��
�l4�r�sh~x�`�\�
9�bd�
����8�
�����
��(�j��sX�
	Da��p��s��tx����
���a@�
`�r��
��l����
P��
����o��
����p��t��z�
���Ba,�r��
��
��
��ă�4�
��̃È�
	؃d��n�sd�
���gx�
���,s�
	(zaXze��s4��P�
����p�
��t����
(n�
��@�a|Ki��ot��X�
	`�d�gp�
��d������c�
��j��
	pf��
����p��t��
�dȄn�t�
����d�
�������
��Ԅ�0�
	��j��
	�g�l0�t�
,4�gt�hX�l��r��s�t4��T�aD�oL �L�d��k��l��n��r��t(��T�a�e�i(�o܅�<��R�\�lX�p#|����a�����	ȅl���Ѕ���H��4Tj_�i	��j0qt\�sh�t �r�����4�����0��P�ah�s,��,y�+p�l,��x�aLupU��6���v�6.��aȆi܆�p/����z09��m�9�T7��Ԇ��:�,k;���a���
Qh<	�l�<�����C	�f@�j��p��s�t�N��|�j��H��HW��P���m	X���m��d���m��p��Y�Y����l��r4Z�$`��]����pćt؇z�`��a��̻�\a.�v̇Ôb	�b��P^b$�o�/v�h	�g$�lMrd�t<Mv��z�o���e<�t<��P��`p����tt1�t��H��t|#�|��\�a����	��rTMz�C���lHC	��bпd�j$�k�l<�nH�r��s\�t�B��
��a�e �f0�i$�k��o�p@�s �u���G.D�t��lG�|G�����@HILH�dpH���aX�c�N�HN��4�y�Pt,R̉nXR��P�a<X_�U	h�l��r�U��p�� ������4���[��[��d�[����ả�t\	Xn�\������h��c؉l��m��r�s@s.��@v��s�����yt��	��l��r����gT�l`�nl�rt�sp������L�l���$�t@������0Bk8Bmt���b��k��l�s8�n@�����aܨ����td���D�d؊v���s����̊aT�,�	�d4�r�s4�jD�_г	�s �t��_(/9$��(�bp�lԻ.
0�a��c��e܋i��o��s��u�z���l Ũ�����l���x���u���|�.�al�����s,�������g̋nԋr��������On��p4
�|���t�����.�a� e��ô�p�t����	,�g��ĉ.��a�bЧc�dH�e��fH�g��h��i��k8�l�m��n<�o$�p�r�s�t�
u�v$ytz��|��̉��a(�b��c�d��e�xf؏g�i,�j��kD�l8�mh�n��p,�r\�sętĚu�v��z��L���������̓��_���H��� �a`�bx�h��i�lp�r�CsL��4��L�h@���T�a�]�����M��l��p��|������T������ؕ��nL���PH.�a�b�h �k0�o`�r��h���kt���؍ah�e�D�
8�=@������d���������0��(�kH�nP�sX�t��0���\�0�����P4���Te���D..�	|�lD�������������@�h�s�Pz��è7������g̠��Ď���`�.Ўü6
Bء��s<�ȵph����aL�o��r<��8a���	$�l,�s���,��X��l�Rt��	�li�j|�s��uHRz��
�
8���t�o�Y����������|�������h�g��l��rx���ďn@���̏a0�g�r�yh�.T.l�u������l��	�n����������X�k`�t ��(�
�D�	@�h8���H��H���T������R��������l����p|�dĐs�����a8[ct�l�r��t�vА�X�B�����}sH��� �����	�X.�����i�R����T.�ti�P����a\�t�� �a���l(�p���8�a�[b��e��f��jؑk��l�t����l�v	t�t��������h����y�
��g_���T.0�i�n���Бa�oH��n����e�nt���i�&#,&���e�# �t$��,�a�b�2.�1H�g��l��m��t02��	P�a̒e�g�i �oT�t��y@��h��03�83�����3�����`3��4$:��9ĒgܒlL:t�=���	�<A�m�o�v�A�d�	tF��E�lxF��F	,�d$5��4��\I�(I��L�e�J��J��`��K	��.��g��i,K.t�a��ÐKQ��V
H9y�L8��b�L��������4T	��a�c�$e�g$�h@�kX�p`�z�ÀT��T�����0���&��T���
��U���a�
���
��U��8�oP�r(V�4�
�,�D��]	h�d��t$]��p���Z����pĔr���x��X_����og��gP�g����e�oؔ�h��H����Dh8Ph���f�k.k��dd�k��l��n��r��sĕt�u`k��
�aX�cx�d��e<�fP�i�o,�s<�t$�È���l�\�k�~���|�t�pm��|�a ��m����kn� n.��znghn��\�.DDnؕo(���n�����o.�h4�n<�t<oy�sLo	�c(lػnxo�������
��G��tpsD�l�r��L�e�u��ud�c�u��l�a��z����u��u	��l�u�����P�vpZlĖv�x��yp|y��̖e�o y	Ԗk�l�nl���*��r�y��yn�y���e(�m��(�l�z��0�e{��zH�dl�k��nȗsؗz�?���o�rDB��{��|�d��gL|��T.�b��e��g��r�BpX0�T�8�|��0Bk��m(}��o@h�8.T����a�ü��m$�r<�eH������������n���4�ih�̇H�g���P�a��iؘo��p �sl�t��z����	<�g�Zl<���������[��������dĘpИz�.�a �����Wl�,��	�r|���x[o��Ptle8�iT�o,��`������zX���8�� ���������@�y���H�n�����J�Вlr��� �j��t �.x�a�����T���gp������8��[rt�����a�iP�k|�o��r��y�Ä�	\�lĤ������
�<�
���o��k0�n,�ox�	��(�t̛8D���<�����D��0�
����\�a���d�dt�p�lX�
	`�d�������H�����i����P�._ü���dؤl�nP�rL_s�t8���T_a��o<��о�ܾ_
0d.H�bP�cl�ht�i|�j�
k��m��n��s��t��v`������� �
������X��(��0�|��
�8��@��P��X��`���������n������o@�	<�b`�cx�d��f��g�h�jd�l��mԠnġp�rԣs�t����̛������l��@��������b�����pbr0��|���L�k4���T�a|�e��h��s����#l�@����������X���6������rܜt��.��a�e�i0�o���L�����0�����k�����<���������p �s�����P��(�r�_L�_X���D�������`�b��rD���h�a؝i�z���0���`_P�	��cȝth������t�����������\�Нlt�t0�eL�@���T.D�a�pbܞe�g�ph�i�pk�n�o�rqsqt��uayȞ��#��.��c��i��k��t��z��P����d�a�����.l�k��.|�s
�
������� ��H��d������ W���8q���X��1���X��X�0�����t��0��������	(�g��i��k��n��v��z���0�������������c��d�e��l��yP��x%\>%�E%F%P�0����o��D	��n�P���̟i���ԟt��m�p$���aD�eh�o0�����0d	�s���$��L��T���CX�k�D�P`�md�p	t�d0.|�o4��<������u���&X(t'��g�np�sT'����a��e�i<�y��p(��1t�0��s�)������8��8	�g��lbn0�p8�r�8. �aP�e|�il���:����o:8T�t:��`����� =	��s��vH
�����������pB�����|B��l�@����op�B�C�8C
ءdH�gX�k��l��m�bp��r�bsdt��zpC���a�d�eآi��kH�o��p�s�ct��u����X�xD�8�W�D��P�rh�t�D�x�aLô�p E������o���<�8�H����l4H	��l�z\H�����L���c�����N���PQ\Q����Q��(�i�ø�.,��h������@P���_(���4�d�R<�lX�s8T����zL����c��.d�i��p�zHV��|�sD����[��hL`	(`����t8_��j�st_��ģaeeĤi�k�o �p�et�euX�zD�ÐeŬa_�a���s�X�b���o$b	$�g`�r��sLb��0����P���e��e��b��Za4�bЊo؊r��v�Yz���c������X������@c����t���mo�f�df��k4em�s h��g	ؤr
	H-r
����Th�����|hPZl�t<k\@
��p�(�bp�r|�t�p��0��������Xo.re��i��oD��qyd=.`q��r_Ps��fs�?vLZ
WLt���m�u��u��u��b�d��j�l,an4ar(�uv��
��aX�b��e��f��i��lȦm�o�v0���g
��v���a�w��w�����l�����0��А�������x��P�e�y��y	d�gL���z��x�o`{��p�r��s,�
���
������|�������
��|����e��
��` z�|ԦsT��`�����l��������ha\�l��rP�ð��d�$�l����,�e,�	8�rH���D��\���x�a��eto$��4���p��lh�������iThox�Ü����fa\$��f�s������T��Ø���ħi̟l�hs���&���i��pЋ�lx���aLO���i$�rp���4	a �e��o@�u`��P�#����a�bP�c��d��eĩf��g��k8�l��m�n<o8�p`�r0�s��t��u�vL�zܨ�d�0�j8�k@�lH�nP�rX�s��.����l�	r�t���Ȩ���\�m�����~.�e
Ø�����i �k��������j���(�l��.4�at��P���@�sTE	�D�\�n��rl���d��$�P0�����a������r����<xy����g�^n,kp�s���Tkf�l��r��Q�	ةrD�s������X����y��\:yH�.�b�_ih�lp�n��r��s�_uP��(�y4�8<�k���D��x��$���� "�#y���Hy�Hypp{.�wi��k�	����e�l�r�s�IvԪÈ	�ek�����Ī�h����|�������@tL.�a���z��d�`n��t��v��� �a��e�h �i8j��l��nLo�r�sx�tȯvL�y��z����Ŝ.��l,	��gH�������`��ح����t�f�k�l�m`�p��sȬtجy�
�����c�t�Q��L ����b8�e@�hH�m0�n�rloz$b�h �pB	U�Q	�8"p{.�wi"��P�ex���ke<j��p��@#�L#������"����z���`$Ql$.��e�$l�$����e��o��y�%	��k�ol��r��s)���os�z$)X*��<�epol*(d<�gD�m�oT�s�*#�*�,��+��L�m ,	�-���t�-��h��8-����et��-%�.����t�p�D��$.��n(i�T0��Īe�y��g�1	Эl�1	
��aH�fh�h��k�9l��n̮o�t�z$��82��������1��"��� 3L
(3��4��83��<��l3x3��T���3��\����`��t�z�3	|�p��s4����� 4�����X4� 5�4����y�dV
P��6��Ԯ��6����tܮ�0y�(7UH�pd����a�7���ra0�z�Ŭ7. �ÈA	Ī.�A��<��l���H��9	T�r8��`���7����ol�Ô���
h�������:��9	��l�:��:	��r ;��T.��e�i�k(so$�tD
ð�ń�<�i�kl�n4��x<�8H8����y����,�bh=	4�m�<.@�e��k��Ð�'�=��`�n�=8h�k=��t��\>���(�h>�����`B���e<�l̰rD?����b��lܰuCM�L԰ltN��	�ta�c`�d�te��g��i�doбs�y�PnDP���iPy�@y	(�l��	��0��P�	��<���	H�rdQ��T�e��o���X�	_�Q	t�k�Q��|��$Rn�Un�U����e��	tDV��pȱsW�`Z�� �s�z[)�a�^.�v�f.8f��d�h$�l��r�f.X�
.�n��kj��,�ips��l��pl�r��s��u�s��D�a��c@�dd�e@�i��kԴn�o��s̵t���,�Ťu��v	��lвr�vs�v��������������w_Dz����$�� z.�eز�y���s�:y����l���lH�0�r<yhz�tz��8�e�Ŵ{}�{��P�e�zX�g8j��k��l��m��rгt|���g��s(|����e0��`|����s�|���wl�wvt$#t��ȳa�e��T%���l�e`�������	��gMlT�p,�t��p����$�e��t��8�dp�g�Ol��m|�n��o��s��zL�P���h�a ���Pd��t�"v0��\�������ZtL�j�~*�]����.^��ll���ȴe ����	�dĿr��؈����e�	�d8�eT�k`�o|�s��t�������Y�`a��@�����H�ÄdY��l���h������p��px��x����e,�yp��t(�zԋ�����j�����a�e�r���̶�8�	<ap�sH�����`����������b�g4�s����0eȌ��(�tD�z�,Xyt���L�v�	T�l��v�	t,���p��8���|��Ȣ���	��n��v����_4�	��rضsH������D�
_������܍����Ў���	��t �.�	�g���mp�t����	 �e��izk�p0�s<�t��z���ŀ�B����\�������*td�������ܷ�p�����s|�3������t��	��s�����������ķŴ�	зt���4o�N
?�#
���e����k������l�t�P��P���$�z����	�ea�epmi"jx�k"n��r��th�� ���+��_��-
|0
���a��e��
������jĝ.��a�m��ü���x��0�0�	ĸn���̸�Р��ظ�p�������� �c(�e@�iL�r��t�ä��ԧ�|rkt�t���̰8�dص��8�a��i`�����l��ȥ
p�
��l�s��t�sX�����e�
	d�������8��frx�sܯ	�������s �	ȹs����Թ������i����L�����d�~��	�s����������(���4�t����@�e�rt��l�	(za$|eH|k����`����	Ժb��g�hT�j̻k��l��m<�n`�p��r|�s��t(�v��z��(���rt���Ⱥed�r�f����m�Fn�}s(�zt����e�}sD��L�����(���8�t��p�W�Rt@�@�k\���H�a��e��u|j��b�a��l��0���t�����l\��@���tP��#.�i�k�m�r�s������eD�o4��h�ń���%�����k������p�����s �z����	l:n����(��T���� oW�	L�kx������`��\���l��s��t����t�eL�m�r�y��<p�p��kȼt0����e�q�����	мd��l���ؼ���l�W\	�.@�a4�e@�i`����P��� �zP	(�s4
	��d�
��
8L�l(��T���
T��d��l��n��p��s��l�a�i�o�uȽ��)Ũt	t���e<�����<��T���C	>�ܽs��a�n�sT���$#�Tl�l�jn|�r��t���$�at�d��e�iQo0�y��p�	j�	`�oX��h�i����e�'h��i��k �ld��k(�l��mľr|oDtlt0p<��̾eH	Ծth��������ghkl�n8���g�	x�gH�l(. �eT�À B< ��@�v\�����&��,�aĿe<�iX�oh�sp�u����ń't��r�'�����������`��H������'.�(��(��g�l �r�)�*��ؿ��(y�t�`v���r8*����$*����L*�h�_<���(�l$,0�lP�m|0pP,��-�\.tX/�/# 0��(ü��/	x�l`0T�b��l4�m�n��r�ks�kt�0��	��a,�dd�e��o��tl�ud�vD�z��P0��2e�2�������t��8��6��5���e�5�kd5�� �e<�r7�
�('��D�s`;��L�tp7X�t�B0�B��p�mxBx�l<B����e��,��d
�
�B����bxGe�h�Gn�r�B	��k��sC������r�r��J������^�����^�kLC	�i`C�� ���DCD�l����ث����EP�k4E��X�e�FT��n(G��p�a8�o��s�u,�z���H��He�H�������@�����OfH�a�fe��ÌO����zXP�dP���l�O������P#PQ�Q	�thQe�l�Q. �ax�e��ilQo0�t8�u\��$�ŠR�R��T����������@���S�\�g��l��mLlsHT.�:2(U����l�y�V���d��k��	2}	�������������V����n�W�0W���n�r�W�$Yy0Y��r<Y�����Y�Yt Z�(�8��H�r|[��P�a��n��ÄZ\�k��l��s�Z��p�a�e��i��o�u �v���,n
���,m
�����`o
��[����a����ȥp�\���l����Pm�|m��m��m��]tH]�d4�g<�lmmT�p\�r��s��v�]t ^��`�.L�nT0�L�
tX_(
p�ex�k��Š_��
>���������� �
B|�
������_����t��z���`���\�`�����,`,@`�h`e�a cXml�rdct�e��e�d<yDg��T.�a��bh�e��f��h��i�Mk��n�Mr��sĈtԈv����h�i��m��n��tķ��k�#.�<g��n�k�th��4��n��n����a�eT�ih�ox�s��u���xoe�o�����`��p��,p��o��g �l,�m@�rxl	<p���tXq.8�e�m��q��s��8Bm sH�s`t��t�u��u)�ct��u����u����hvt�v��t����m(�����e����lw����e,�id�jp�l��o�r4����	��r̚�����������`����m�� �lP�sp��|�<�r����D�a<t#@���\�o������a�e��i4�oD�ܠ# �
.��
��k`
��i�_
�����_
.���\�����yL���l��n�����y����������la�e�i�o<�u �� ������|��t��
#
��4�a����<�lh�nt�r\
�������\���!���a�ilku���P#����.#	��f0#��������%� at�`��l�`����a�]	��t�]�����M��$�e8�i|�m��Äf.��kf���y�c�l�n�hn0�al�l`�Ø�.t�L�b�o��T���o�� 	#���t�e����a��b�d��eD�fh�g��kH�l��m$�n�o��p��r|�sx�t��u�v���$���dL�f �g(�j@�k`�l|�n��p��r��s0�v�Ü���x���d�.�.<��	r`�sH���0�at��L�p(���T�ap��|������.\�.����0�aĎP�����zt��z�������,��P�����|�4�����l��r(��`�H g��j���\�����eL�i��r8�Ä��̆QP�	 �tD�z���(��؝Q��X�oL����QL�	`�b��k��m��n��t����h��ПQ؟Q�QT�Q����������������Ì���l��m� r�sИt�wt�.����D..t���t�����e�#��.L�$�d<�k��.����� jT�r���<��.d���\�a��e!r���ȫB��t���������(��@������	��n��z������l��o$�rP�v�z
�4�����n�o����n����������p���-i���np���4�e(���<�lX�y���d�e\��T�p{.��i��kp���l�a�!i�p��Ä	u����	
 Xb�d��i��j��k��m��n�r�tt^v�������H	����d	Q�	��	�	���p{.@�i ����a��c<�d��f��g�j��t\����	�{bcr���L���,tT�.h�a��e��o�����p�s�	_���������	�p����l��tD�	�����s������aP��d���g8�����e��.(��l �m(�n��.t���0$nlUo<�t�d��\�.�9��H�h`�n������.��yp�l�r����t�a��f��lP�r��s��$�P@�����a��e��8�_�����a,�g4�h�ck<�o��r #t�����������n$�y��	��n��������GX���:_��'dL�m��y����T�d��j��k��l��p�ZsP���\�a,�e��i��k�mL�nd�o��p��s��t��T,�D�8������������������t�_�����n��T����	�Zl�����x�����p��8�$�cX�ld�mp�s ��4���D������L������b$�tt�	Tek��.��n��p��r��s�t�������a�`$�����aP���0Bk
�
	��l
��������������@�i,���
�
	�r4��� ��tF	p
8�n�
��l�y

���No\�X�k��r����t�sx���|�o<���	��r����l��o���,
�
h
��r�4������z���a4�e\�o��rh�uH���&
���r�*
�T*
�� �i�(�rh-
����@��,.
8.
T�p�1
��
����p�r��tX�
t�
���rt
����e���x�
t��
�����T. ��s�(t�|�s �t�	l�z��������������`!	H�g��jx�n��r�t8�vp�z�$��$��4���$��X�y<��$%.�.�0��/d�l 0��l�a��e���l�	��4�b 1	��rD1������������1�H3�$3	��l5e@8��L��X��|7����e���89X`�
y0A���m,���
	(zaXzeP�
����<C��T.T�b��ed��(�����F�LD��\���I��$��D�Ŭ��Bb��d��j�k(�l`�r�ul�z@�����a,�bD�ePf��g��i��l��o�ph�r��s�t��u�v���0���`�����a|�����d�����a�kD����*jT�m�g�����>á	<�r$���H��,���T�����8qo��d���x�l̛	��l��r��s�t �z���������������������������L
fT�oT�uԣ.���0�8D����������Ih�l�u(�v�p����l����t
rP�.t�sH�8�c��d��g��l�m,�r`�t��v09\@�.l�e �~������z����8����?g��m8�����e��l�����/�������������1�$�������	���������ܰ������uD�#`���$�aD�dT��T��@�	������L��������iԹ���l�����t�À�	�gl<p��r�����+t8�zH���<�r��	��d�e�g�l$�nX�pl�r��st�����o\�`h�����a(��<��H����l$�����d��g<�k0t��_l��t�D�r����L�a<�`����d�o�����Po|���x�k �m�	�Sr�+z��H���������8�����u���4�	�X.�������j�����al<���l(p4�r��st��������@�����P���(�d��gX�o|�zx�Ø�t��P�s�_�	d�t����l����.�l8�����z<�jl�	��pظr�.���@�	��l��n��t�������������\t�8�����l���4����iP�s@��8�	�r|���$��x������K�	������sx�t���X�i�����L���|�����&m��ppt�
�d��r��t`.	�
����th�����	�j	�g��l<tDvXzH��P�����	Ä�g`�k��p��s�u����a0�e$�i��o��uH��l j��X�ox�r!�� ��p�ih��X_����a�*	��t�*������)����l����+��+����a<-��̀kC_��������������3��z�<T(�l��n\�s�<���a��e��i$�op�À4	 �m�4��<�����4���<��<e��r=��d�����d�|���d�����aT=�-g��h��l��r%	tH=�|=��=��T.��t�=	��r�!�� F�=��k�n�s�=��=�p$��=�l�VL�tԘÀo�n�o��@�a��e�id���q���������_tp��t�vHp|�l�r vTr��T.�q	��r��t��v<]��P�e�r����b���r�s�����(s�#.8s����e�s�LnT�������i���nd�p��sD��(�T|���<��H��4�P�lؖ��X�et�B����p���������x������Ux�	��m�R�Ĵ���������p��t��ð�������f��
�������y,����d@�jH�n��t����aX�il�o��	 �r��
�L�� ���P�s<��H�d�lH���-a��e��i��o�����x�b��s�������������H������r�	��	��l�����,e��s�,z�	@	��b(�i4�s����al�Ä	 �k�				ܾn4	.<�a�	��H�y�		T�n�	��`���m	Tl	x�g��k�k	����a��i(��$o	��rp	��	)(�	��a��k��s�TX�lh�s��	����ap�	(�	����zXT��	���cx�		�c4�r��	������	W8'
��b��c��d�gL�k�l��m$�n�pP�r8�s\�t�Su��v(Tx�z��Ä(
���R����8)
���Re��sp,
_�-
tl-
.��o�,
����h�_�x0
��v�0
����a����9
	��s�9
������8
��$�r�ü=
P�=
���a�E
	�bd�E
��0��\��0D
��t�e��i4$lh/m��o��s�t<��@F
L��r�F
t�H
�pH
��r K
��J
p��l��vp/zDL
�DM
t(M
.��e�L
����z�O
_O
����txN
��j�N
����ah�d��i�l��t,�vD���Q
p�Q
��$�o<Q
	,�r`Q
��8��@x�dS
� S
T�l@S
��\�a\/ØW
�hW
x�d��g��m��n��t�W
��W
������W
�����X
��W
����pX
���/aؘ������a��t�X
��d�X
����aD�e,����PY
	�l\Y
�� ��d���Y
�tY
<�lT�s�Y
���_�Y
	\�t�\
	�r]
��p���\
��|��<e
n\d
����ul`
����b0�i��pl�	br�]	�����@n
����o����thn
��lp
�lo
��dd�j yl(ynl�r0�s4du�o
���a��b��c��eh�g��h��i��o�pP�rp�t��y��z���lp
��q
t�q
	8�l�q
��t��P�������	������*����e����������ths
���
c��h�s����s
C�s
�����pt
_|t
������t
.4�o��ĉ\xD���.@�o�	�$�r|
x|w
�w
	H�n��rh�	�4x
��`�u�	�$�	t�b�x
��|�a�y
�y
��r4{
y�z
��g��l��r��s|{
t��a�	c�	��
��{
����n�r�t�
���	�|�	�D5���a(�r8��t1%8(	0�t(���8����	��D�Ô
�}
��\��}
��d�Ä
�؀
	|�gԠl��
.��a�e��h�
��sx�
(�
�Ȅ
��ne�0�������
�����
	��s��
������
���Sc�dr(�t��������� �i�
@�d��g�Vj��n��s\�
��
4�a��c@�d��e�fЦg�i��o��r�es��t������
��
��t�s��z�Ԋ
.��t,�
���&l�
	��t,�
������������ ����
�`�
����h�s ����lԌ
.�a��
$�d\�gd�s��
��,�a��d��eP7z���xQt�����	l�c���t��������4���
��l��m��s�J�4�
�\
	0�ttԍ
�h�
��c��g��lȦr�s؎
�,�
jĐ
�e4�l@�nd�s��z�?	�?��,�l��
��T�gX�t@�
n$�
��P�et�
���k��8p�mؒ
��x�o�
���
����z<�
��s�Tv$�
G0�
	��i��nH�
�(�
��T.��k��t�!,�Ț
���rĞ
M��
���o��
	�tܠ
t�
$�sD�
��,�a e��s��t��zd���
	PTl�
��X��|�Ȥ
���
Pt�epPo���P�
��|�z��
������
��xTrh�
��
���l,�
.��a��e��i�o4�t���
�4WrP�
���rt�
���
��������
�L�
eX�
�� ���
��(�à�
	x�kp�s �
��@��h�
��P�øBH�
��h�t�zk��
|�aL�
����i�
��r�
����a��iL��������`�
��ÐR���
��l�
����a0�
��tT�
����a@��l�
��`��L��԰����D�
	$�k`���
��0����
	�(z��
�����X���
	��a��b��c��d(f�i��k��n<�p��r��s|�v4�z���`�
��h�c��e�h�
صm��
��bxk�l4xn�z$�
����a�d(�e��i��zd�è�
����h�
���
 �l�|m@�rH�s8�
�T�
tl�_h�
	P�sH�
��X���|��|�,����
x�n��
���9���
.���@�
�Tr,�
��������
��(Vr��v$�
��
	Ī.�t��
�����(�
���i�o����	�D�
�#.l�
X��
��
$�nT�s4�
��,�ad�ex�i���l�t��
���
\�l$�
���
p�r��
C��r��
����������
_��
����(�
��a��c��i�n`�sh�
����ap�e��i��o<��D��
���
���
���
	
X�b��dP�g��iX�j`�ml^nPrh�tt^v��
�����������
��H$�
�\�
���
$�l��s�
.t�
���
	��j��v�Mn��
����eLFt$�
��r��
t8�a��o��
��k�s��
#�op�o����o������t��
%0���
	�d$�
80�
��(��x�
���
��<��(�����P��x���X��
	d�g��
��p�������
���l��o��p��t�z|���
�Ns��
P��
����e̥i4o�
��,�rD�
e@B
�
LB
����a�
/��r�
�������
.�èM� �m,��(�a\		
x�b��d`�k�l�n`�r��s�t�vD�z�	#�	��p�a���PX����l4�n��r��th	��l8
��������8�
#����a�e8�oH�uX�z��<�L�����@��P��|��l(�r0�vHv�S�#$�H#T��P��Ho��r�����p�����x��������l�.��a�e��������s�����c(�e��tp���_,����D�P���l�M��M������@ �l8�s���lsD�t�o�D�zD��L�s�	X�sx��d��x��$�Y@!	��eL!������!T��l�!����a�e"�D� v-)���g�t@�y��	�1�����0��d�v����	���	����H2�\2��(�aT��1.0�v�2��2��L��T4��
|�a�d�e �f0�i��oh�pp�sx�t��u�v���\��<5��t����@��H����������5�����6����x6�(6�d�g�6��7��7	��k�t\8����t�8jT9�9(�k��z`;�x;	�led��T��<j<j4<��r =#h=�hqtԭ��t�=����e���Ō=	��l�=��s�=�����r�$��0d.0������=��	,�.,�b4�h<�n\�rd�t��vL��|��(>��>��>���x>��D��l���>��>��>��>��>��t���>�pA��@.��t�?����z�C��C������C����èA���l�v$��p����F����8F��T. �b��h0�i�MrԈv����DH(�g�H#(I��<�a��e��o�s��up���ŘI��I��h���������������J�J��n�K,`K	��kNtDN#�N��k	j�N	�eO����HO�O#�O�(���(�,��O	�lt������$��,P	|�a��b��c�d��e�f��g�i�kh�p4�r�spt|u�ØP��8��X�xTP,T����h�W.dW��k�l�r�Y�l�n�R������Z#�Z���e�[��P�r�\��\n��rL_.^�t�`���r�g�Zn�g��(�a`�e��i$6l��sx�z����{H|iX�ip�s�j.|�z`#�(l�[.��l�p��v�l��o��o	��l�h������s�\p���k�tZ�Tv��T<�dD�t����aX���	��r0���������~��|�l�r@�s�����GtH�	�lT���L��p��^_�	h�t@�����a�e!u���.Ľ	��t�����iT�����hH���c�j�k �p������a`�e�i �opuL����t$]U�����c@���\�k���.�`eH���(�z��	4�s(���@������L�nt�o��pX�� B� ��|�� �����pB	��+����h�l�r8(	��m�ppO	��`	��-��GU�F���t�;�s�tI�pH���i�bja�g`�k��n��p�s�d���2.�d��<�t�dTH�td��T�ex�oe�ep�d�j��p�a��d��e���4k�@k������k��mj�o��dr�n����t�z8p.��f,u�u�ru���a$e4i@o4r	��m\npu��uI�u,s��
���	6D�
Hk�u��Pi�����hph������ܽ|rh����a�epk�l�m�n�pH�r�t`z è��� Jo0��l��������e\��l����e4iXÜ�	g���������,s<�.d�0��@l8���L��0��(7�4
��&oT���
P�
���a�ut�ä
#�
��\&iH
P�
���i�
���&a��e��i��o��r}��%
��'a��i o(r0uü'
�'
���x'��'�.
#|0
��1
Pt;
�\�d�e�g�j�k�mt�n�r;
.8a,e�i�o<rtPu<Dz��jl<
8<
���y�<
��I#�<
���e�'èM�H?
/?
����?
����|@
B�?
��g$�r0@
����l��s�D�h��D
���Sy�C
� g��lHmTn�v�I
����uJ
���g`O
���e�M
�`l|t�Q
m�R
��@d�l�p�s0�C������������.�a��\U
���kDY
��H�dtPX
��l��st�e�Y
�����Y
���x��H?ll?v,]
V
l4]
��,e�\
�8g�b
�\un�ut�d
B�d
�`g�
t��z�g
P�g
��|a�f
�djl4n@pPrdu�zPf
���a@e��f{g�i�o	p4	r�	s�et�	u����l
�����l
���Ðo
���~a�[b�~lu
��d���s��t
��$�Xw
j�_y
��Hm<�P�}
\d~
	8�b�lr0tD~
��p�`�D�	�|
�d�
I��
�l�
���a�lP�m�
���w�́
�����
.�ia��
�d(j�zL�
���a��
���
�����
��
����f�v�
��gdl�n�r4s8*_8�
��\g��h̩j��lL�s��v��
t_d��lğ
���a�e�io��
�<�
	�r�tX�
������
��
�_g�l8�
�Ȣ
t|�
�nć�Ї��zd�
s��
�9m8�
��(e�~� �
��@�@�
��H�L�
	Tlxr�s�v��
���l�m�rt�@��
���i�O�
���s �
tĶ
���eT�

d�{g�{l�m�
nrs8vԽ
.aȽ
�x�
��
��ܰk8BmD�
	��sP�
�� �,�
��,���
	`BgH|rdv�
̱l4�
��Xe,����a��
��pr��
|g�n�p�rt�
t��
���� �
����\�
���e��
�}s�
���a�e�o�
��
���
�l��
	`�sh�t,�
���r��
(n��u�
��$	a�	i�	o\	��
	�fl	np�
��L	��	�$�
U��
���
��t	�l�
|	�d�
������
�	n�
U��
	�	n��
����p��t�	z�
���
�8�
���	eX�i��
�	m
sd+��

b,
n�
��
aX
z$0t�7y��
�4
r��
��<
�h
���
.��eH
��
_�
.�À�
	p
l0�tX�v�
ce4fHg�jk8l�m�n�
p�
rPsht�z�
��.p�
r����
�,_�
���
c(�lr$s`.�.�����,rL`4��@o��rdy���.\a���T.�a��b��g��h��i��k�n�o�p �r(�s0�t8�u��h��pj|,�.X�dd�il�k��lt�m|�s��t(�����@����e�����D���k$k0��,apc�uJ��I�Ll�.Ta|e��`s�t�������s���sD ���d��l����a��t!��H��`'�'�d
l'���a@
eP
i��nd
o,
�pp	�܂	_�'	
s�'�� 
�p
�x
��'#�(th(H
n<)�()\
rx�	e�)��*���~.�
r�
uL������+��d�i@�kp�l�
m,���
ano4u�
à,���Y�,	�
u�,���
��-_�e��-lD�.�� s�.(c4/��l�+sp/��@a,e�i��s�zpð0�����D,�L,���|,�P2�s�z3�(6t�6��l�r�6.�a�ei�6��8�8��s�8��rl�HptT7�������9y09���l,s\9��;D�;��4��;��<��:Hl�s�z;��Ta-c�o��8�D<�����<����T<�h<	��l�n�<������=Q�>��>�lt?Q�?	�d�p����C	j8pht�N��(s�àO�Y��0a`eT��t0���L��Y��b��Իa�e�f�ou����ce$c�����hc���
C�l�
�����c���ô�
H�e�4e�s�et�e����Df�fe�h	�gHl�r�s�t0vLz�i��p4p�o��<e�optutu��\��t��d�x�y|g�x���e��xy��1�����z8z���t�z�{_�{������|.��4|_�}�|���k\�����sh�	s�����<�d~�� �$���~X�~��De�	hrP�z������h�m�o��0�#��_�A	�s�������d��n�r�����a�i4��\������c�kH�UP
� >	�l�.o����yL�	n�r����$�p��������Hl\���Pe��o��sh�	\s\���|n�tt
Ul����tP����	�c�t��������a��8�
P�
�����T��T.�a,T���s,�	cd����$�Ld�f�i�ks<�T.$��Dn�
%,�	Xn�r�t8���`�zt�@y���t��j����a�%�%�(���s�v��� ���k�n�!����e0�#`1���eð7
e�1����:	�9��(z@���0�,R��lXR��Ha�e��HCTt�B��ha\S�R���&���ĉ.aGb�Gc�Nd�Ve��f4�g\�h��iL�j\�k�l��m�n��o��pd�r�s��t��u��v��y�z�4Ô��̉��a�b�c�d,eHf�g�QipjkD"l�#m�%n�)p�,r<-s�.t�3u�3v�3y84zd�L������<Q����]���p�	|r������H����d��r�Cs����8�t|��gj��l����	�ae0hhi�k�o�s�ì��t�t��صm$t�ܜ��e��.d�aHü�_����@��'��'��Ta�\t<,_P,��tr��|rܞ���o41	�0���r��r�eD�����L�������y@�y�jL���l`�.�a@o�z(è7����g̠���T����|��8r�>
���Ll��t�����a��b�k��n��o��r��t�u<�dth����a�l�ouy��8^��^}��������_Л�l��gܚm�n����	p~i,�<�k@�T��.�$d��l��rp������@f��l����Xc�d�n�s�t�u�v@���`a�ei\o�uDy�$������������x��(���\s,��X��x��$����	�g4l@r������@��������8@���,l|���
�o.�b�eX�fP�h�k�m�n�op�r�s�t�������|���$�4��	<��h�p�x���X��������t����e���r@����	�s�����d0k�m8s�������(_P���Ht��Pnts�,(��lz�� �	�c�r�v������������ôp����������`��4�	�n��\�drt$�tp�t��t���������(���0�h�.8�t��g��l�p�r�s��z����PaHedi�o�s�u��z�x���U��\s�z�U~��	P�l�n�rst���������������yT.��h�nsd&t�Vl�������&h,sl\d_����4tP�<l\n�yst����Ttn|s����lt�����tl��l�mr�z�u���0lT�
y(y_4�	�rx�	��s��8�z��h(t�e��4�dPlpt���
acexi�k lH nl o� r !tL!u "z��P��$jT�m8oh��4�nt���da�_$�	|c�lSr�sH�����D���� �� ��!�������k��l��v��	�s����������������i �t��g,lL��|���T.Xt�	4r0��D���Pe�������doL�lkt�	<�r\�t���s�����a�e�o �8��@��c�lh����l�r������	�l ����������Du���	  bT h\ s4���( ����< �@��p������d b� p� r� s� v����(���� m��|�z��tt���	� z��	8Ts�U�B� m|���� �����!i��u� �TE���!p��T.8!i8�P�0!v���l�D!jt!l8eP��\!��!�8��	�!g�!hl}j�!n�!o�!s�!u�!vd!�4��\����,�8e,�������]���	�!s"t"v�Xh���!og�h�t��"a��lp,"gt"k���
4"a�[b�c��f�"j�"k��l#t�#v�"À��TSu4	�"s ��d���"��	��`ts����n���Бa�"o�"up�H�"t�n���h�	�"rD���"�\#���t��(#e0#iP#ox#v�"��#���.L T ��<#k8 D#n| e!�!��d#�!��l#���n�"���#ip$�P$��#a�#�#d�#k$n$p4$r@$z$���#aT$e$%i`%mt%o��u�$�$�%���#a`%,l%��$y�%. 	�,	�� $�@	��($��&�H)��.y)H$gt$l�$m�$s�)�d)��l$e*����b�*��z4+�+	�$k�$l�$n%r '���$�D%��%��%��%�\+ph+���$eT:	�:	���$e�<	|+���$v����,��%vX,%n4%s�-��8Bm�lt�.e�X	8/��L%��.��T%Ì/t$/l%l�%rXs�/����g0,$0	�%r80��1t�1.�1�%dH�g&j &l4&mP=r02���%a�&c('dp'e�'g�'i4(kX(od(s�(t�(y\&�o	t3�,&a3"`3�\5���)l5	<&lp&rDXs$5��H&��'��(�l�	��T.�&a̦r��v�&�D�	�i�k�	��	���&��6�7��&l�&m�6.�&e�&i'o�5���&s,7��7t,C��7��&l��		��l9��'�P'�P8��D'e\'i'Ø8t�8<'l9	|r<9 oz�:��9h'r�'sX�	��;_�;	�'p$>t�=�'s�=���'a�'eX0g�'i�>�Xn`�	�0@�'np�	�<A�'l(p(s�A(B����4D	(t|C��(��B��D(r((à�	TF��z�EL(slGHI�Il(g(I��t(a�(���	%p�		�(c��	���(��J�K	��g�(l�(u,K.�(at)e�)o)è
�<L���(aD
Y�L8��i4)l<)nD)sL)v�L��)��)�����)�$MB�%�My$�%@NLNT)m N��\)e�M_h)l�)rOO���)��N���)�lQ�Dr�Q�tQ_�)lDR2XZp{.$*kD*l\*tt*u|*z�Z��
�)a�*e$+i�+l�+op,p�,r�,s�*�L,�h�	@[��*k4*t��	l[�L[�<*a��k`�
_T\��T*ml*td�_�\��\�]	 Xb(g�Xk�[n�*r$]���*�,,�HS��,�4^1P^���*��]����h̦r�*��_��e�_�*l+m+n+s�`��`ta��b��b+kd+l(Bm�+n�+p�+s�+t-z`��h���L+ap+e<���T+lX�pȬp{.,c��x+a�+g|,t�_Į��c�����TBa���$d�+t0d���+ah����1u�d�+k,r ,st�>Te���+g��n؅zL�X���,s f	�X.0�b@�n�7r�f��f��D,��,��f��f\,rg��d,aDhh��|,��g���,��hj�X�pj	�,n"v�j�k.�n`k���,a�,c-m�t��z-��r_�cxo��$�������-e8�i�
P|���$-a�5i���,-pd-t�-z|�pd��P-a��X-rt�B�yp-l���x-b�-l�-s�-t �.�-a(.sL.t�-��=
���~.�����-t̕��QT��xSsp����-�.�.�h.�x�B��.n̜y���.d�������<�jĝ0.lX.mԝ��<.a,t
v̟B���`.t8�|�d\j�.l�.r(/s</ttu46zt���t.at0d�0e�1i8^k82oD3t\3u��yl/�3�$p
8D��.kX����.a�[k�[t��/o/t�[��y
`dz
p�y
��/iD�����z`}
	�{
��4/t����	H/j�/l�/r\0sh0tĤ��P/�41�2��2��2�h3�D�8�y�/lХ`\d�i��kؽm��t��y�/a��h`Yn$0o̦rH0t��v�/È�
	ܩb0hXn4����/�0�\\��W(����
�Ȉ
0k̦ئ��00�4�����80� ���yT0z<����\a@����il��g�0l�p�0r�0s�0t1v����\e��lT����Yk��.�0s�0zȥ
_ �
�,�.�0e0�
�d����0i1oX���
������	���1e��kP1�	 1kh1r�1s8�
1��
��H1�4x���`���y�1t�v\1è����d]e�O8P����1s<�
���ao�1r��1k�1n�1s�1z��
��8�a���]t�"v��
_Ȱ��1t �t\�	^r$2v�����2�����2�h��t�02jX2kd2l|2m�&s�2t����h/m����\dt2l��
�H�����l��
��
���2h|�
���2t���p@i�\ø�	�2rp�n|����2i�2ø�	�2v�H�xF���2���_��	�2r����3��3����
���� 3oP�(3k��nd���43a �v���8�T3t�	L]l�3zt�p`�yx3e���3Ŝ�	�3zБ8����3��#���3b��k��s�Ut�����aԾu����4i4r�_��	<����3g�0��D�Ũ����cL4s��4c����,4a\4m��o�Dh� t���T4at4À	�	��l4�@�	5b(6cd6d<7g�8h9l�<m\>n�@pHArpBsTDt�EvFzHWì����4�`w�x�����,��,�����P��p��4r05s�����4aX5ex5i�5o�5r�5u<5ô5��d���(5s����������5������P5lL��\���d5a��l5n�5z���\�����g�Nr(��5r������5�,�e8����5�D����5�������5s�����46t����5�L6���.<6i6�4���6sl�y����l�p���t8s�6zD���T6a�6e�6i�6o7z�6��_t`�����6r�6tx�����\��6mL�t��6r�t`�th����6��6���0�	�6lL�P����7b`7cp7d��g��l|7n�7s�7t@���7a8iH8ot8s�8y�7�d8Ō����P���h7a�����\sH�.DCl�7ol*t<�����,l��	�7l�7mh�r�7t�����7����T8�|8���x�X�����7o���H�8d48g@8m��I���� 8���.(8�������t��e,��<���\8��8���X�����8��L����8���.�8o�8�<�$����8s����8a�8e�c����d<9kH9lP9nd9rl9u���
�8a�9d$:eX:i�:l�:m�;oh<u�9È<�	��	��49a�	�@
.\9yp
,�
t���_��t9g�9l�9s�9t���|9�L:�p:�H<�T<�|<����
,t
��`ts�
��"j0	�9s$���9�0��:o�9�H�P:z��l:l@:s�v�n_���8:tD	�l �mh:st|����x:m�:t$���:a�:kh�o��v�:�����$��X��\0d�:mp�:i|���:a;o<;ux�G����:.�;k$;m,;s���W	����4;k`;n�r���L;b���T;ktr`ul;h�;n�;r4t;iD���;a<o <u�;��;k|r�r�r���;n�;t�;u����;��r���`<n�����<k�E���(<�$��0<��	<<rLC}v||L`<t�,�	t<t���(W�����<d�<j�<s�<u���<a8=bT=e�=i�=o=�>Ŵt`��\s�$�_	�<l0=z4���<��=��=�>��^�,>�T��x,�jX+		���@=l�H=l|=r�=s� �� ��h=�p ��p=�� �P!�\!	�=k�=r�!���$v�"����t�!�=n�=s��	(#���=tl#��$t�#�=r %�|%��%��>�8>��g	��T.�1	 >t�&�'T��g��l�>n8�rp�sT'��@>a�>c$?ed?g�?i�?k@o(@s��tl@y�>�p(t�)C r�)���>�D?�����*��8c�>e?s�>�+��+�>l@�	_,	�>g\+���>��,_�,��?��,.?�`-x�m4?rl�	t�.�4.	<?p��	th/P?tx/��X?a�?e�?��	��/	x?l�/���?�0��0hkl��s�2�Cl�2���?e�?ø�X$�	���?a�		�?r��	���?��?�@�		DEv�39�3@k��l�5 5�� @p8@z�5��
��8	@@j�@l�@m�^n8�r@�sH�v�8.H@a�@e�@�$9�09���@a�
�D;��:	�@l�;8��r:���@�$b�����A��A	�@t0A���@��@��AiAo�@�BTpB�|BAl8C��d�ixAk��l��mdt��v�AzpC�� Aa�Ae�Ak�An�s�ctBuDBàD��D��pAaX�b�Ak�Ar��H8�H��1�K�@K�An|Xs\�vxQ�AtQ���Ao�Q��Q.�Aa�Q���Ay�[��.P�gD�hT�k(Bl|�s��t��v0�LDY�]	0Ba\H��8B����8_@dd�Bj��l�Br46zt_��TBaCeLCo�Cs�eu�Cz�B����(`t8a�$b	�dg�Brp�s��tLb���B���XC��b��Za4�b��h؊r8Xt�B�c��������Xd#(Cg��	�lz�d~Ce�d��Cy̮
�d�hh�
��4Ct|h@Ct�iCĪ.xerDl������kfhCÀk��tCz o�X�s�CvXo.�Ca�Ce�CiDkLgs0D�|p9�D
%�q��Cd�Cg�Cm�Cn�q�4r�@r�Ps���p(t�����tjDÜt�@Dl0�v�p�� D�Ԭ��t�u��jv��
HDa�De�Dh�DiEk�o0ErPEt�Ev�D��
`py���De�x�Dr�
�ȵ
�Dl�z���De�y	�Dt�w���D��g��E���
�,{���De`{,�oH�
e��
��E�l|��E�p�
�����h��@Ei$E�\������HEelE�h��x�
����dE���
��	xEsl�����i�\�X���Ez$����Ei�Eu�E�ԅ�X������E����$��Et����EoT��Ed4Fl����FaxFb�Fe�Fi�Fo�FsXF�ЈtĊ����hlFu\�	<Fr|���LF�L�I,�dFs���ht��FlX�L�s`�T�Fl��_P����Ft������Fl�Fn<�.�Fa��z�F�В�T�_0����F�����s����Gi0GlDGrd�#\���(GitoHhì����adGe��iTho��uth�L#��h�j��l�Gt����lGaHcLHep�f�HhIiHIkЙldIo��p�IsP�tTNuxNz�G�t�`����Go,��4���H�d��xI��I�`N���(P�Gi�o(H����Hs����� H�t`���4Hed�rX	<HlhHm�Hs�v�Hz�
���eb�Hep
8|
xHlĝ�����Hz�.L;e�	`�p�Hv��p����Hi�.�a8�o�Hu�HÈ�� �� �I��DT@�d<It$(�0(	Ir�'��$I��'��0I�P*���rl1���Nt��z(.TIs(2��1	pIr	L2	�Ik�s(4��dJkJl��nJp(Jr@JsHJz�3.�Ia�Je�Li�Mk�Mo��sġtNu8NzdJ��M�\�y�4���It�4��t�a�5����r<è6.PHa8Jo����6���D7�D�g,�l�Jth7��PJ��K�tM��M�$N�����8���Ja�Jh�Jj�Jv�J��8e�8���J����P�	�\���9�09��Jc��gKjKl4KmLKr�Ks��t\��P:�X:��Kf$Kt,Kv�:B�:B,;��$7bTl\�y$<��DKdhKe|K����X<`Kll<Pt<��tK���\�<���Ke=�T�l�KrxLs�Lt�Lv�>f?	�Ks?���K��>���KlLt0Lv�K���������Ke$���|"eL��2���L�\��(?��(LeHL��E2�E��@L��V���kH���TL�t?.`LÀ?��lLz�?����e Wk`��4�	�LtP����L��?���L���h����L�D@���L��?��Ld��mMn(Mp<MrHMs`Mt`���@��Mf�g��t��<A�� Ma�����4MtH����sk<�m�����XMa�A�tA�lMz4Bj4S��B���Mm|B��Mk�MrxD��L[s`E�,�ldr1�
���M��F���M�,F��MsLF���M�ȓ��G��Mg�8
8h8
��Nt�H�Nl�J��I.0NoHN�8J��l��K`�j�L	��lpNt(M	N.�No���N�hP���lOr�O	�NbOcDOdTOitOlPndPp�Pr�PtLO���Na0�bpQe�Sf�SgTi�Tk�ToH�pVr�sVu�Vz$Q��U�4�	�P����LD}�P��$Oo�P,Ot�P��8Oa��øQj�R��R\Og�Oj�R��dOa�Oe�OiPoPt�O�S��TXg�Ol�Os�T�U(LU,4U	�Ol,T���O���0V��U�Oz��|V�OstDW��`�t,PyhW�tW.$PaDPüL��W��<P��X�TX��PP��W��XP�<Y�XpPn�X��xPa�c�Pk�}_�[tP[�Pst[���Pa�Pe�PiQ�\D�g�]�]�Pn�^�l^	�PsD\���P�H`	#gHQlXQrdQs|`��Q�S�pT�<U�PU�@V�dV�8a��ěl\mlb��<*n\d����s@iT�g�Qk�Ql�Rm�Rp|6rSv4�x�mQ�m	�Qk�nX�t�m���Q�$m���Q�|n��
�aRe4�kPRl�)o�Rs�Rt�Rv8R�tR� o}�n���Qe$Ri0�n�nRm�e}�o	��r�o��,R�Lq���l��p��DRÄ3Q4r	\Rh�RvHr��dR� �,D{�Rz7���Re�r_�r���2�D
��0J��r���Røs���b�Re��lt�l�uj�Re�u�,{���x�T|	H�gPSj\Sl0�phSr�Ss�SviWp�	0Sr����8S�~��DS�T�����mԃ��t�c`�i�t8�z�Sð�8����S�`��l����Sz������SsD����Se ���|�lx�r�̉���Sl̫r �����a(��x��Sg$Tl0Tm��n<TpdTsl����fl�����i�.HTaԓ�pQ�.PTaH���XTz��	��nP�r��z���\�r�Tvt��t���TsЛ���Ta��Tg�Tl�TmUr��s��������Ta�Td�Jt�_`�����ld����c��gȤ�ؤ��U����p@i U�@�	,Ur��_��	HUb|Uk�Ur�Uv0��`��hUe����pUl�����	�Uk|����U�`����U��_��p�����Ui �	�Ur�Usd����U��V��8�����Ukp�����ä��d(Vr4Vs�t,�.�a����нs��	 �jTVr\VtD�_ܻ,0�	��g�Vlr�z��nd���|Ve�	��r����������L�.�u�V���DWa�WbTXc�Xd�XeYfhYg�]i�]j$^kH`l�cmgnlio�ip�kr�ls�otLuu�uv�vx��y�vz�Wü������$W�����,W�d�
8Wb(�d��g8�k@�lH�ntWp�Wr�Ws��vP�. y_����|W������Wè�PH����Wz���������i�|����Q��	�Wdl����W�����Xi Xl4XrDXs�W�����XnX��d���Xa�������,Xa(�e��.���P���LXcpXe�Xs���T�hXt�<�09�|Xt��.�Xe�hp�h���Xi�����Xd�Xr`���Xs0����Xe�XoL�����sD�T0YlP����Xa`�Yn���Yo�r8Yt8����_������@Ya�HYl�Yr�YsX���
TYaZe�[g�[i\m$\o|\p�\v�\y�Y��.�����l����Yl��	�Yl�Yt�Yz�����Y��[�8\�X\�$������YoD��X�9d�Zb,ZgLZl[n$[r8[s���������.��4ZmD���@Ze��l�Zv�ZŤ�
��	dZa�Zj�Zk�Zt�Zz����lZ���t��������Z�X����Z� �t<7���Q�Zet���.|����Ze�ZÈ��p����Z��������[eM����[jB����0[.`[e�[n�[r�[t�[z�����X[bt[k|[n�������P�.��������������.�	�[d�[l�[n��.t��0�������[d�[s��t0HD�[gT��\e�t�\n��_l�	0\v-����D\g��	L\n��s` Hl h\c����p\e�����������\�������\�<]�P]��]�H�.	�b�\eH]ih�lX]o`]s�]v�\È]��:���	�\d]l(]nd�U��x<M
�<��]e���]g(��4�84]kd�9hD'��9�Hyt]ü1��1��l]��I�������<`�#���]c��g�]r��sp���,��D����]Ü#����]a^�����]t�t����]�l
��	^d\^p�	��	^a�^e4_i�_k�_l�_o`t�^����ip{.����P^uL_�	h^l�}p�^r��p^�$_��_�(`�<`�|��T.�^a4�bdnp{.��g�^l�^m�^r�	����^s����b�`e`���d�j�Ŕ��l��T��_t�	_r �s���d4�jP_k`_stz��t��C4��X_t��	��s����l_����x_À�h���_i��	����_t��_l�_r4��|�z�,�	�_r�	�4	���_o`à���_r�	e\	��`��,�	 `t�i�	4`d����a�d�`ehagtah�ai8j�albm$bnLo�bsctDcv��z8aÄbń���ttp�`k�`m�`tL ��`a�`ed�z� �h �`i�`k�`s� �� ��$��toaaeao�$�l(t#�%��%	agHarPasH��$a�@b�(�)��ԩs�zn8*��`aoX*��a<�e�ao���upl*0cm�an�az�	<+���ag,t�-���l��a�8-���a�����88	��/	�ajbs�/���a�0��bä<	�T0��Īe�y�q���5�1	8bldZ,�YLbl�1��Tb��c��1	�bf�bs`b�82��pb�\�
83���boXx�dx���b��6)�b�`6���bz��p�7���bo0�z�b�t
�X����b��7��`�a��o(c�Ѝ
v8	cv8��c��r�<c�@�� ;��T.��h�npcs`cØ;��<
����8�D����.�cddeDdgLdh`dihdj�dl�dn�ds�dt�dv,dèd�@CxcleresHetD?���ce�eiXfllfo�fp�fute��f�DQ�vTD�dddkd*	v�*	�<ds *	�� d��d��o�haQ�*	�Xde<���*	}8Q���+	pdk�D��xde�qQ�DBH.�ds�D���d��D0p,	�E��.�d��r��,	���d��d�p9��9D�,	�ees��E��|�k�E���s4ez(3	8 F.,et�3	`d3	��@ei�F,�F	Tek�el�er�eslA��\e��f��f�G� G���ee�etT�v
���!e�5�d5���eelG���ed\���G���es�H�"l�enfs(ft\I���d�"g�I,�I��fz�J	�K	�� fa��tZ��O	j8fe�O	@fgxJ��Lfe�J��Jdfk�fl�fr�fs�J�K��8�g�W	��%z(K,8K	�fr�K�XK	�fl�Ke�K���f�4Lj4M��L�fr�N�<N�fd�tltN��
gaxgc|�d�gethg�hi�hois(it4iu�y`g��hŔO	l�lt�t�O��Pg�h��h�Ti�DP���
c�ge��	�p�	�gl�gm|
��
���geS��R�gg�gl�dr�gt(S�ԧ	���	�gl0T���ge\T.(heԾk�tpT	�gk0hl<hsThv�Tn@�	.h3�T����sLhz�T�DU��T.��	��U`hl�U��hhe4V�DV�hd��p�	��X�hkLul��r@2_�Y	�hv,�	��Y���h�Z���h�Z	�hrZ���h�`Z���Gp�Gt�]��]��ia�CeP[��irH^T<�rDit<^t�	_`�		Ligdit�	_,b�a��c4�k�ilX�m�izhW
t\c���iiT�
#�T.8f�ic̀t�i�ij�ipj���ia\je�ji�jlkoDkp�kr<j�X�.ja@�\���k��ja�k��jldk	$jl�k��0j��j� k��k�d�tplTjgtjl�l��l��ljehn��m	�jk�jl|n���������j�h����j�<����jl�n�jl�jz ot��P|o���joHp,`p���jz�oks$�
,Ľ	kdlq���`v|q,kl$q��8ke�/.@���Pker	Xks r��dk��k��q���eivopk�����Wtt	�kh�t���k������k�8s	�kl�s��wd�ke@li0lÌŬzPwdlj�{B�{��l��{��lè�	$�k,�t�v�� l�`l���L�lPlr���@z���	Xlz(�th�lll����tla�ldme�mknl,nml�p\ns�ntToz�l�̒	�bl����l��m�T|�|`���l�`����lÈ�	�����le���lg4mk�m@mt�	\rh���m�����(mà���	��btme�mh�mi�mk�mn�mo�mrH��`�
���lmi@#l��U�����U��U�$�Ut9
�
���m�����mŬ�	�ms�P�����ma�miz�8��L��X���nani��PЖ�ܖ��$na<nip
P`��t�PDnahn�P���Lnz����Z��d�tnr����|na�neoi"j oo,orP�t�n��
��)
���np���nm�nn���(�l����ne�	�nb ����n�x�\�.�l���؛or|0
y���8���4o�����<o�ĝ.Hoklot��pС��doo�ut��xob�oc�olfm�onpp���	�oaHpe�si(to�tr�ttDuy�qèt�H��ĤPԤ���oa�ol�r
	�t
�\����oa��_����prԺQ��	pb����$p�t���0p��<pktpl�pm�pn(qrpqs�qt�
`Ȩ��lpa`�b�pe��fX`kd�r�p�,�8<��pg��	�paqk����p����pl����pe��l �
�����peqt��
_ؠ
8�
qd,���qe�YkDq�L��X���<q�t�
(�
��Pq�����Xq�t���dqt�qz��.��k�����qh�qj�qvh�ð�Q��
Q<Q��
_��	�qd�jrlHrnTrr�rs�rt8svp����q�xt�u���.re�Ov$rü�
��
���
��r��
���n��
0rkȯ
��<re����,b�`etri̦r�rt�r��XL�
�\�
��|r�����rň�H�D	�rl̮���r�|�P@����rz������rr�����res�8��t�
	�r.��
���r�`���
��seܯsdHss���(sels���$�	L�bxsi�sr�st��v<���Ps�P�|�jW��W̰8�d�sk�l�sm�sn�sstv<�
��`
r ��`����{g��_����st�sz�P�so(�\D�
BP�
��t�,�
��t�̲�ز tdDtkLtn\trhtx��$�t��
�p��Tta�
t��,@�	ptv���̴�td��	�te�tr�����t�t�n����ti��
8ȵ�tfص���ta��
����tgX����te��.��ط	ul8uz�����$u����,u�,��8�|�gduk�fr��t��#���d���lue�u��tun�uz�����uelvo@v�dWe�����u��u�4�e ���̣.�ug�uh�unvtvvv�4�U,�UԧU0�U<���x���U���� vl �	(vl����4v��^
B`Q
��Lv�����Tv��`vlh��|���xv��{�$����ve�vk�v��t̽�vl�vpj�����W�@����v�����vr�����ve�rw�l�	(za(weTws@wÀ���w�`��r\�	�n��	��4w�́�����Lwp��	l�b�wc@xd�xeyf yg8{h�{j4|kh~lmn�pd�r��s�t(�v�x��#����wah�cxhxi$xo,xs�w�x�������w��w��_�	�wk�H�Txmd��x�#��)8xk4BjD�����a`xe�xi�xo�x�xŨ��alxxmX�_d���pxb��	�lP�sp����x�(����Нl��`Ps�}tX���<����.���xr�.���xr����x�Ь�@���yr��<�l����
yaXye�yhziDzrhzt�zy{z�y�8hŠ����Pycxydpg�yl��m�yr��(�	(����yl�ys��	P��L���h".��	�yrDs�����y�z�z����z�ivL��ytX����ye0����zl��`�tP#U#	$zf0#��,z��!��8zì�
v��Pzmxzt����Xze�z��
v@�
��)	�zll)���z����znH�����	�zl�zs(�.�ze�zi�zo{�H��������T����z��K��[��Z��{zX� {st���,{aT{e�{o�{s�{���#.Ddt{m|{s�{t�{z�ft������\��d����{�(����{Ő�t�����{�������{l�y\���T.�{iL�j,�s��X��Ƞb�~d�(g`|jp|k�|l�|p�~s�|tXCu�|v`Cz����	�{a�|e|}i�}k�}o�}uT~v8}��ŀ�t��\����h|a��`����||a@�.��o��.�|o�����tP�g�|p�|s}v���ܷB�����|�p����|z�|À���������}�����}��	�kP}lh}t����$}��}��}�~���.`}eT�vt�n�����e����t}d��$�	�}tX������d����}��e>l����}s���}r��s �,�	�}l�}r�zP��0��*r@�.8~t,~��	~l,r�`p��$~�p|�.��@~g���H~eD�8����`~c�~eL�m�r�~��~�\��#.�~i �s�]����b�n�~r����~�p�����	0�k����~���(���~l���~e�d�g���aLe�kQo�y@�HCh��8�d��g`kpl�����b �l���e��	��x������(.t���&�\&�l�&���a�e���(��(�l,���%s�+	�s�'������������ 0%0��/	�l,r�`p��(��1�`0<�d��l��p�s�kt��v�0��
D�aȀcP�d�eX�i|�n��o�t��uX�v��z���؁�X2��2t�2�����@��|�����$4����.؀b��p7@�g��l�s �z9��:�;����;�����'�� c.0�v�W <,�;	8�j�s ���=P�k$sl�t�P��^�]��t�i���`�	��]������@��s�A		Ī.�d�e�i�j��k��n�`tdLv�A�����������Yc�<B����e<�j0��\��B0�B	�kC��$����
��E��ED�l4E��L�e|�Ô�0�E	h�n�E��p�����F����e8���FT��j�k��l��r��z(G����a�e$�iT�s��z��8�ŠG��H�������@��p��K	�g�stL�`M���Ur�L�k�
t�
��0���OfH�a�fe�O��D�zh8
.,~ðJ�Q	`�l��tPQ_�V	�V���kȃs�R������Q.ԃl�s��àP�
�V����t\W
��_
�TY.܃e@[��Z�g�z�Z����a0�e�it�l��o�r�u<��H]4�gP�l܄n�s��t�v ^��`�.��e��h��m��o��vЄ��y��od^t�k��m��t���^��
�X*�a��
���P_�_��Ȅ���
���pe�_����ð�W��
���h�$i�n��W@`��`��T.h`	 �r\�t�\��,��h��������ȵ
XDa��T�e�b	�Zt�
yc� c|�k�c���h��l��r�c�X�
��c,�cC܅rp�
�Xd��ȅ�dd��Ѕ�L�
�X�
	�gle����De������e��e�nDg���a��bt�e��f��h�i�MkD�lL�m��n�Mr��sĈtԈv܈z��|���h�#.��d�ik��l��m��n��tlij<i����f��itiķ�� c.�j	L�b�h��i��n�rt��vth��Ȇ�T��\���8�j���t�k�#.,�g4�r<�stk�L��l���$��l��ml�kt�n�m���l�� ���{�z����ehw��l,,t�uw����a0�cP�e�i��k@�lp�o��p��rԉs�Ð�	h��tԄp�����i8�	�th�������������x���$�l�������<�e��D�s`�t�.l�ḭ�p���L�m<=�P�	t�l��m�����������������4���k������eP�tt���̈�X���ԈÌ��d�n(�r4�s$�����n�oP��\��sX����o�����k����4�o(�uT���4��<�D�������tL�d�k`�,P�	|�r�B������rЬ���i4�o���d�� ���-�t��`�L���̉p�z;
 �8,�����|�����8�	�l��j ��pl�tx�u���� �a��i̊l(�rd�y��ø�	p~iȽ��T������`��|�j̩t\���s������aܿ	��r$�������������a
��4�a�o0�è#�5t�.�ox��sD����c	�r�!P�!�� �alku4��3	8�d��g��j��l��n��s�u03.@�a�e��i�o(�ű�P4��5���	a�j�6�h6����y�8��88��b�lH�t9������������<���:��:	�g�lT�mt�r��s`�v���X ��;��0�e8�z�À;��BL��4<��@�e@<��H�b,=�8=��`���<��h���'x=��m�=����e�>8��l��r�<s0B	�dԌk\�px�s��
y(C��̌rE��D	�k��l�p��s�E��-�pF8�rx>sX6�I	 �tpJB�I84�t0Qj\MH�iM��P�a���,}t4}l�lD}��t�a{	��n�]��������a��b��c�d@�e��fԐgđh�k�l4�m��n�oؖp��r��s�tt�v��x�z|�È��$��a|�b��cpd�eH{gp�hЎi�j܎k�l�m�oLop4�rd�sȍt�v�z`��(�Ŝ����x�������І��`�iXo��������������i��sȎu���P�ẗ�d�tԊ��dH�����(���8ape��ih���������Ȍ�� �o��$/t�Y����������aD�Ô���t���_d���P�����@�ox�zX��Ď��a|jf8�i���,���� �ؖj4�����e`���`�s��yЏ�0��<���ȏ�`��gL��\���܏e0�i����Ũ�IP�	��n(�t�����̆��`��#��8�cT�d\�n��.���L�$�d<�k0>n��v����d��(��(��th��d���������P���T=�̐������lT�r���D��d���.�a�e��t����]nȫ	�n������L��@�.,�b4�k<�p���x����X����	D�b��h��i��j��k��n��r�s��t��v��z�����Y���t��P��0���[�|���t������al������.�bT�ԑr����e<�k�l��o�r�xQ��_�����l��v���lL�r ���,�el��,������X�d̶	T�r����`����Q4���	x�c��f��gВiܒk�l�m�p��s����n�	Qh�Q8y
G��ȒgLz
Q��Q��	Q�Qp|
Q�������p�����(���L�lT�D..p���(�a|�b��e��ph������	�d��kt^v����T�� 	p�	��t�o�.��r�v�D0	�����d�����ì�jēo�^	�p	�����̓r��ԓk4�l<�n\�rh�s ����a��c��d��e�gT�ih�k|�n��o��tԕuؔ�$��`���dy���H������P�Ø���D�.������T�.t�Ð�����sx�n�����u`�	����.��g��t�	Ĕk���̔���������e�r<��(�dH������4��T������	���� �i8(���B����<�g�j��D�n|�	����`�l��	4����t�y������r8�����e��ô��x�	��z�������X�	�T�̕s�t�	tl��(�p�c�l�n��tt����i���@%e���kD�r�@�j ���8�a�R��R��P��@���d��e��f��g��m��pX��d���d��|��dW��[��\��b��f�p�.�i�ij����Ėa4�e|�r�ÌlŨ���,`�	��r$�tp�����l����������,�gH�l`�s� ���e�`v�,����X�z\���������t�o����8�a4�h�i\�o��r #t|�������l��P����e�i �k�1m,�pL�t|�z���8��Gl�s$�.�������d�sP����k����T1o��j@��
8
��8������8�e\�r0��,��	d�t�v�.�l��t��
,�	��z������P��Ęi����{����r��Иg�l���ؘa`�e��i�o8�th�uL����`��D�.$�a,�k��P_D���\a�	4�t��@�����ܝ����gx�l��m��zd������b4	��	��TKd��tL		��r0�
��7e
lԙn,�o�s�
���t�
t����]tP�k, l$
C�r0
�����l
�`
�kD�sL�tt
��$�al�
�x'
������T�aT\�r����(a��o���@H��tl���������D�.U�	��l��n�s ��Ě�t��,�g@�md�zؚ��QL
$����0���� *p`��$�u�n���8�o8!8@!��L��p��$!��T���8`!	��g��j��m̛n�r��tԜv��z�%����a$�u�, �,����aX5 0��ěsܛy6���e

���c�5	�n98|7���d��h8�j��lH�o��s��ux��M8����;@�d`�gh�kp�m�;1�;�Th�@8��X���<8�=�0A��Ȗô���
	(zaȜpP�
�����x���
����r<C��D..�aC �k,��I��T.�e �i8�lH��̕�KXM�,�sN����s@�XQ��d�P��@�a��eԝh�i�opnp(�s��xc��c��t���`��|��^��l�}r��sPe��desPZH�Y����c\M��r�k��ȝa�k��p�r��z�ltm	��r|V�����4��<tr(s\|���p�	̢z\���T�l��@�s��t@���L�aL�ip�l�r0�s`�ul�v���l���lę����e��i����t�������&����n�y��lĿȞl`���Ԟe��	�p,�s�����������x���l,�.�e|��� �z@�.��8�k��@�at��$�\�n8���d�i��u���H���<	��N��T.�N	��np������D������@�	��zL���3���-�(������,e-i�o̟ð�������b��	�m �p�m�����h	��s���<����H�Ð
T�k���h-a���$�4��|���	�	�j��������������d@�fd�g��jġk�l8�m��n��p�sD�tH�u�����a�
bH�c�
dT�et�f�g��iX�kx�o4	p4�sx�t��u(�v4�y���̭�Hj�t`H�dprl��s���P�a��e�Qgo����L�����nPPp����a4���d����aܡc�t���_� Q� 	�z�����8!n !�iD"��qaT�m�#�!dP�l�#��(�a`�e��o���8%��$�T$X�m�$��$	l�l�$��t���%�t%��r &��%��l�%����aآy�)�t)_Ģr�(.̢e�è�L)�����)���Eaܚ8�-.�f<-���z�..d�at.(�l�n�.��4�aУi�o8�y���,���.��/�P/	l�l��rl/��t��ģ��/yT.��aĦb`\��/��d�1�41	��s�1n��
_d2��ܣt82�l�m�r���|2���o����s�3t3��$����,�U_�3@�t<7���a�pb�g��y�4	T�g�l�m��nL�p��r�s(�t�4��l����D��̬�L�����Ю��8.�.ФÌ8�8��Ȥ��:���]o9��ܤl��m�:���<�gD^l�<���a4�eh�ix�uT��T=�=����t�=	<�r=��H���E	��=`�k�s&�at�?�?����e\>����k ATĥd̥kHA����aܥ���txA�4HeDB��ԥ��f���Urdf�kpB���iH
z�Ðn��B����TD���	d,
fJi�
k�G��@�l�V
��d�f��g�k�lX�m4�n��rĨs�t }v�xԘ�$[�Z��rhY����e��gԦy�[j<]��\������\.ȦÄ^��l�$^���Ü�H`����a �i4�l<�Űa��a�npb	��aH�d�b��,���2Q?�c��P�a��e$�oPou|��LAtte��t�����C��c��g��lԧrxc�̧l��� dD,d������d	e��e_\e	ܧl�r�e��T.�t�G��G����\G����lfg#g��,�a�Mnl�s`���h,�h	L�r�h��T��i@l`xn�k��t�i���`l�0l������xQ̗	��tܗ������l��ܨt���,o�n��Ԩr�o��do`w	P&h0�j\�k�l`�nh�s��t��v�_���k�{��$�e�h_�|.<�s�{D�p4|��P�a��o|����C��r8}��p�������� ���t�}��ll�s����,��	��t0�� c.�b�eD�k0�È~̩th~���e�'tT�v�WpoHr7�	�n<�tT�� ���7��W �	�.L�a��T�yX_�0�l�r���t�e��o���(���T.���Ȇ	��n��������
ľd��g<l�m$�nx�p�r��s�t�z�`Ԑ���a�4����*a +p�h".�����a�dH�g�i�"v��p���@�a`�fh�mp�sԻ	\T�	ؖ.��aĖ���������i��k,mȫtT1p �����o8�nL�����eؘ#T.�l���ԫa�yĚ�ؚ�������� �m Jo��@���pa8�À	�,��0��x�	`Bg�Bj�v\����l�r�,vĴ��������b��f�0l��p��sl���_�6
����f`�P�����ed����s��	�g�r@�v��X�����rh�	<k$�t����������,�i������ð����8�o,�	tbl�lt�n��r��sؘz��_�����t(�84�����s��zl�_`���T.ܭe0{t��<�	��r�s�z���������H�X�i�m������� �X������iT|�#.�����e<�.���(�ik0pP�s8zt
�����0�X
�����P
iX������h�r�	PH�����o����bDg�
l�s|t,�	<�sĮt�����/o��	$:d�g�	r��t�z��	�:z(���l�����e��	�d�����������ì�PX�wL��8�p���D����9�	4�d�	��`�a�r@	l�k��n �t���|�ap-b��c�(d�e0�gL�i�.k��o�pl�s1t��uH�ü�� �� ��ԯ�h	��ܯ�l	_	���tP
	��P9�@
	����
	���r�t�		`l|�nX-r��t�	��0�����̲�<��L���1�ȳ�4	.<�a��Ä	��l�y�>B�>8��t�	�����	��k��t�	8�h��$�l�9H$	�̰n "	��԰o� 	
�g$�k0�lP�m\�n��p��s��t�xt��(	`%	���l�)	��DDbH�i��mp��*	P-	���bT-	���c*g/	T/	��l��,��,/	����iX1	���Ee�sd3	��įn,!Ŕ6		�FgԱl��n`�p�r�v�9	��T�v@:	���eT:	�k:	���e8<	���e�X;	���t�?	���aTB	@B	��(�l0,r�D	��`"��B	@�dp�g|�n|Ip��r��s��zE	.H�atF	���"d�"f�"g�Zs�"t�"v�H	����o��dI	��I	���kM	����zM		d#g/r�#z@T		T	���d��v�Q	�l�Jp�r�ev V	���}s�Y	8�Y	����XY	��$���X		0�dh�r�Z		,%kd%n00rP0sX0t`0z�`	���0kHGl\n�0p1t&z�b	��b	��g\&r��tHg	��f	�����0f	�����h		�g�MnX�z$o	Tl	ܳk�p�k	���ah�i@�üu	��SehP�h�	�sl�	���i�|		(�r}	��4��ę���	!r��	��P�e(�	\�kx�s(�	m8'
�bd�c��gH�il�kܷll�m�n̼o�p �rd�s��t�xĴÄ(
���R�|�����)
�)
شr8)
���a0�e@�iT�uP�y�ô)
B(�c�)
����L��4�,4*
F��t�*
8�v,
e�,
�l-
P�,
��\�h��y��X5
t�c��o��p��z�%��&��n�\8
Md�tl�8
����a�e �o,�r��:
_�9
	صl��s�9
��������8��t���;
�p;
�r=
�r�=
��(�Ð>
thB
#dA
@�r�C
Ƞb��d�j��k0D
��T�aآe�iL�k��oȶÈD
��D
��E
��$�bh�o̦r�u�E
	��r�t�
u�E
���������������E
n4F
��"jDG
�G
	�k`H
�pH
�d$�s,�z�H
I
��I
.@�aHI
4�lhI
��@�al�e��ux�ðI
t�I
d�lPJ
��������J
	��s��t�J
��t�J
�lȷnԷrp/zLz
�xK
����k�K
��N
��Pca�Ed��i�l��thW
`̬8\Y
�����X
�������`
�� ���`
��(��,`
	4�d��g��j��k�l�r�s$�t�zl`
��	@�aԹb��e��i4�l`�oĻp\��lv��`
��`
����ata
���t�	��a
����a�a
�a
̸p�a
��Ըa�o�	� 	�,	����� b
�����		��\slb
���t�b
��b
��0�s�b
	8�c|�g��l��r�b
��D��X�������<��̟��b
�p	_4c
����i��k,gn o�Ôc
p�	8Dd
.�aPd
��l�v\d
��Ĺa	�d	��e
�le
�d�g�n$�rH�s�e
��f
�g
��|�k<��\g
�g
��4���g
��h
�|h
	P�dt�k|�l��n��r�h
�i
�T:	�:	����eli
n0i
����i�i
�j
��dԺkܺm�n��s�z8I�\j
t0H	��j
���k��tk
.�z4k
��k
��k
e����l
�� ���l
��(����S	@�zm
��H�o�l
T�k|�l��r��s��t(m
�hm
��m
,�m
����z�X		pX	����t�X	e�0�@n
����llo

�b��d�xgd�j�k yll�r0�s4du��v�o
��
лaT�cx�d��e��i��k��o��s��t<���q
	�*z�q
��0���y������hs
����h�viX�	e�u
��d��u
��l���u
�yl�y
ty
��r�z
lp|
j}
��̠r��
���
ļi�uj�r��
#��
�Ȅ
�sh�
�؄
��p�
���a`�e|�i��oؽp�tH�Ì�
_��
	4�l��
��<������������
#l�lp�
�d�
��
t�zt�
���
���
��r��s4�
,@�
����zL�
�8�t��
Ľnd�
��̽i��j�i�n�
�H�
�T�
�n��
���i\�
���dP�e8�g�~m\�n��s��v|�z�$.h�
H�t�
�D�
��.��a��b e����
t����
��|�4�
PL�
����ah�
����hȾt�̮
����lh������Ծ���
�� Tiܾ��
	��aL�bd�c��d�f�gd�k��n�p�rd�s��t�vX�z<��,�
���T���\�
����l\�r��
`�
��h�c��e�h��sؾ
�Hp�
.|�a�gW(�
	��tH�
�����$�
��Ŀr�����
���Xo�
��<=���
	пl��
��ܿ���
���i$�l��d�
t��L�
��
���gX�r8��p�
�4�
��0����
���
��D����
��L���
��(�l(Vr��
�jr(�
��t�a�i��n��o��r��tp�	y��	y��	y��
	�Gr��
�����4�
��p�l��r�ð�
�(�
�c,�d@�lL�n`�rh�
���a�e�iH�o��ü�
S��
��$�a��
`��
��8�a��
.X�y����
���
	��i��j��m��r��t��
��h����,������%@�
�$�
����r0�%\�
%��
���
�m�s�
���
_��
	�r��
��Pg$�
��n �s��
	��
���m�
��e	�e��4�d��
<�l��
�j8�n��
��T�a��k��l��p�r�s��t��z��
�t�
����i��
���
����a��
����e4o�
���a,�r������0������eti�o����
����r0�8���o(����1��� ���G4�dx�k��lDon�}t,��<�a��e��i��o����Ō���	�>l,�������x�s�����m��r��s@�t4���k�1m��������vT��l�������$��@	���t\		LXbl�c��d��k��l��m��n��p\@r4�s��t��v�#���d�a��c��h�������L��������������a�Xo��ü�����PU�X����������������d�t�!��!����aX�hd�o(�Ô��h��0"�� ��D��t��|�04�	<�s��
�L�
��P�e��#��
�@�	l�t��z�
�T��h�������#���l���L[	�h(	��l4$������(#)����aT�b4eh�ox�u���*���X�`��p�����t*��*	�t�*�����*��$�ä*	0�k�*��<���*��H���+�T/��/�D1#�1�L3#\3����a��e��À3��3������������3#�3�4	��l0�C��l`p��(7�������ðJ�`@�l@����|@PH�e�Ì@���z�?��(�sl�zX�à@"P��?��P��$At�@.d�e��i0A�<A�|�s�A����l��t��vhE�tE��rE����eF8F��T. �b��e��h��n�MrԈv�ÜF�[.�zz)H�H	�nxF����0��h�H�	(�k,P	��al�b��d$�e@�f��g��h�i�kl�lx�n(�p`�r��sh�t|u��v��z����ŘP��<������P��ct�n�R��������h��4U�DU��dXU����a�e\�i��j��k�z�ìU��U����H������U\�.0�i8�l�l��U�P~_ V	@�kTV�<VT�nl�vH�t`�).l���t�e��Ôio�f	��t|V���������(���������������e��n��r�V	��k�t��o�oĉȨ���.XW.x�edW�l4�s`Y.H���y$���@����T��p\\��T�il\	`�p|\��l���\����yx�Ø,tx]Hh]	��lD].��e0��~������]�����0A��_��t$a�0a����e�`	��sa�����`���l@�o�v��P���x��<�4�rZ�hY��L�e�T�g�a��`�ec����n��y�c.
\�.��a��b�e��h��k��n��r��t��øc�pc	��l�cy�c8��r�c�����D��������\:�d	).�i0�l�	f
x<!�
���i0d��$�v�
odd8<�b`�nh�rp�tx�v�
o�
o�
o
o��
����\
��0�p3�42�Df,Tf	��g`f�����lf�����f����s�e��rf�������%�f	�n�f�����f��@�lx�rP�s���|i@�hx�j��m��t�g��H�e��ix�z������j��$��(%0k	��b�h��������(l��s<q�q	��lq�����P��\p�� �i4�kD�l��p��r�s��t��y��z���x��s��r�n
P�s��,�aح��s��na,tC`�rh�t<t�`
_�t��t��p���t���5i�dru� u�����,u������&
�Dv��rTv����a��r;
@w)D�fL�t��@B
�LB
����a(�r�?
���rx����8���^�|��V
�0�t��
���.L�
	D�tD~
��P���x����o��r\���
��
��|���x������x��l�x����e�������g�z����i��W,}	��t|�����8���{��$�i0�lt>m���l}ot}�����}��@�yp~	L�k4	��D�g��p���L�a(8k���\�l��n�~��
p�ap�c|�e�i`�l��o��p��r�u,��8��h�	
t��vt�������EQ��	 (I����o8�����t�y �,�������l��	�l@�t0��������@������D
mh�sHm�h�P�pȕ.X�a����d�s��Tjg� l��m��n��r@���p�����n��,������������l�	�Ag��l��r(�.��e\�n�.,i��,d$�n0�zȬ`ج���a�����m0�oL�	Hov�44���L��@���T��d�_���l�d��t�l��n̶.����q
��q
�����(����.r�	L�����d��i��o��P�P��U�	��b ������ �.,�z���s�9\��.$�o�������8�e��	@�k(���L������|�eX��(%	jLt�i��p��s��� r���;��q����� ����r`#��"����z
���I�$������m������aܽ��vh���
��a��e@�h��k &l��m��o<�p��r��t��zX��0�����l�g��	@�g���L�����?����h�k����p�i4���|�r0���p4
���%a�|i�ð
#�
����a\&i<�
����sx
����sT
��r�������4
	���
D
���z�
	�s�
��$���
��$-a`[e�5i4ot�rd�ì

��\����l
�
��ԥ�����
��|��,
�%
��'a��ep'ip�o(r0u����'
�����x'��'�`�
.)
��i;
.(ad�e��fd�lt�pp�t��v8�ÄB
����LB
����?
� �r0@
��,����x���D���D
��L���C
�X�g�`
��r|�Àb
8Hl
�Xl
�����l
.��ôj
����y�f
	��gj<�l��m��p��r8�s(�u�zPf
����a�bD�e��f��g��i��k��o<
p��r4�s�etL�u���h��Xp
����b`o
$�p�o
��0�a��e��i��lp�Ü",�p
	X�s\�zq
��`��������|q
(hq
��n��v|�q
e���q
��z�r
|�pt
_,t
����p|x
_Xw
����ry
��pwc�i����|��|��y
��l{
�|{
��$�z�z
��,�s��z�
���o.PaЦbئg�n�r~
	H�g�\l��n��rHAs$�tD~
��h��4�������@��������؃
T��n�
����ap(��
����a�
��dL�
����a�s�u�V��[�(�
������\1��
�r�
��0�aԧr8�
����ld���
4�lx�m��r��t��
���
��\��,�
���
��p�u��
t_dğ
����a��e��u����Q�{��k�
��iȣ
��
��
����a����
��
	��kT�
�������	�}	�����������
���nL�
	(�kT�ll�r|�s��t��v@�
.t�e��m$48��
��d�ct�z�
��8�ahs���	�g`�
�����t�
��
�������
����� �
�sĶ
����e�
����ll�
����eT�
	��d$�gL�kl�l�
mX�n,�op�s|t̻
8���
��
��0��\�
<�
��D�lо
����d�{g(�
��
��h�i�1m�1t��
	��nH|r��t��v̂z�
	4�
,Xe�
���br��
�m��r��sh~x�
��8�d@�g��j�r�cs��
��x�s(�
X�
	�p�s,�t��
����p��z�
h�
.��
	4�l�n�s�
	(za̴e��rP�
��T���
1h�
��t���
��|��,�
��
��s�
����a��i���p�
�����������
	Էn��
�l�
��kTl��
	pf�	n�p�v�,�
����e8�
	�gD�
������
����l��p��t(����
�dp�n��r��s��t�3
��
��h�axiL�
���ax�
���-i0�
	��r��
	�g��lL�sX�v�z�
�� ���aH�b�d4�e��g��i��j��k�lP�m�n��r��s��t�z�
�(.|�g8��8(�dd�ll�nt�sL��0�a��e��i�t���(�|.`�t���	|�r������������������d(g��r��s�.@	�x	�X		��lD
C��r8
�P
���j�
�l������j,�v\y���(�lX�s��_���D�����L�à�(���d�sl�s��t4��x�a��l��o��r��@�L��r(.���r������l��o�s��v�����f���n0���i0�r$��P��\��d��D ��8�d�z���@�a��e��i��p��Ü!�\!	l�lt!��t������W�"����n�!��n`r��s�"�`#��k��p@�t�$B &�,&��l8&����e�'�'��	�d\h��i4�j<�lD�nL�oh�v\���P	��		p�		()Px�	�'��T����		�+@�k�
m8�t,��p�a��u@���.`4/�+d��k��l�+np/����a<�eP�ix�k��st,t��T,��/��/����a��rP���0_�0	�g�l�0����@��`��D,�����1��14�r�2�P2H�m�s3	ؤr03�����@3��l�� 4C��rD4��4P��e��i�4����z@5�=�=����zh<	��n�<�������;��-c��h �r���t?��?	�d@�,@���a�C	X�d��g�j<�r.s��t��z�H�I��P�a��e��zx��LI�`I��p�����tI#�I��I	��t�J4L#@L����a��e0�g��i�o���dLtpL������L#�L#�L��L��l�]�N���d�-u0��m��N��(���Z���-iP�u`�Ä]#�]�p[��X���c	�X.$c��l���b���/vx�ôg�n�g����a�h	
�/c��d�g��lnMr��s$1t��vнz`p��P��p����@���������o��
8�dL�e��h��j�nT�s`�t��v��ü��$Q������
��,���p��pD�l`�mh�p�c��i�,r���r�s�r	i���r.��n��s�c��t\�����e��
P������e$�0�_�����kL�����e�	�l�	��kt����e8�i,�È�	0ę	�k�s�� ��h��Lt���.PH.�t��H�z�t����.x�e�!nP��8u�.��k��m��s��tP�
�x��8�
�(u��
�r.��e��i��j��l��n�s�t(�v�À�
)0�
���
0 u0��0Hu0X�0��
u������<04�e�i`�kh�lp�nx�r��tu��0�����(��T��ȯ
�l���������^
�xub��n���PH.������e�	��r�z��������������1b�i��l��ø�X����e8�i ��P�Ŕ��� ��@��4��ܒ����P�<�tt���D�a4�md�P�d��j��k�l�nH�r@hz����`�a��b��e<�f��i8Hk��o��p8�sX�u���t���p���k|�����a��e�����d,�����a8[c�����gaL�g(8k���`�t�Cy��Q��	(�sЛ��0�����<��(�
B��8T�n����\����.h��t���t�yL�	��g��lԒp$�r��s��t�����������������������:t|�����a��t�����lx�����d���nЧ�i@�kL�s����a�^nl�ox�u`�ðD�� c.l��(�	h	r����T��Ԩ4l��(Bl�����s��z��t��.��i̪��8�aPhk�����r��0Ed�g4�kX�l��r�x�H8��.��s�v|�����y̯�Q�	�b ��������(�è�����e��@�mH���L�edk|il��8���l������t�ä�	Ds����������p������h�	��g��l�n��p�r��sp0���	��l$���L�m�����e����kp����ep���ki<�t��zT�Ô��+l����0�e(��������L���Fy�F��`�iPF	h�sF��t��@�������.��a\�����z<�`L�����e�o��	��d��g�k�m<�nP�p\�rl�s|�z�����1a��������o@����8����������$��\���0�t�`���H�aL���Bo #t����T'k8Bm,���L�m��o���������e�	��v��z��T��s�����eLT�t���k��l�nD�plHr�s������d�v���(t��t$�	�Fd<�lL�n��sT�v�Hz|�.8�t��)_��_���,�	\�r��sd���d�����8�rP�st�	�mj�	�gTnt$�.�a\:bt:d\�e��f Bg4�i`�k��n��o��p�r�sH�tX�u��w�z��|�Ő�	L�d��g��j��k8�l��m��nP�pp�r��s��t��z���X�8�tp���@�a`�mp���B�����h���.|5.��r���|�y`'`�B|�����t��,Ls������a��c��n�t�����������������$��	��s0�����d�y����$�t����,�aX�b4Lk`�mp�o|�tl
BXBH��h�m�	X�4"j��rp�����a��e��Ü�B��������������p�FlX�m+	8Fr��������B�����g�i �y���D�	��gP�.�a�6à]U��	0�n���8������`�rD�ì��h�B����h�c��(��|�l�����a��p�(sh
p�����e�
B`�����l��r�8y�����������o�8L�bT9gd�l�9p��r��s��t@������d��$����8����@������8������@9r@�ì�8���\�dp9l�C8�t�d��kl�m��t`���|�a$xk�Mn�Mr��u�D�G�%t�����s��z��P����:a�f�h0�lD�mL�sT�tL:v'P'@�2H�����<��0��� ���C2'��' �'�	��g��j��k<�l��m�n$�rP�s8�tX�v=x >4����a��y4$;zP.��eP�
�0;.��k�����e���Ժ�	��b(�r�������A�
�A����T���ŠP(��4�e��ft�hl:lh`md�r$_s|�t��v<�z����ń	�	���X ��	�$����a��b\<e��i�lD�!	L���rX�����ept��T��n4		�����$	����à	���g�	����k<��
4
��4��`�B�
��H�d��e�t��z�ü2���l�����t��@��m��t��������e� t���������0	�=l4!s�
�������=e��'�.�k�m ��T�'@|pH|����l>t���,�e,4��D�����L��H8	0Og��h��l�n�?p�r��s��t��v�tA.��n��t����e$0.�e��mPp����?eD��
�O.�c�Od(Pm�Mrd�t�@v�@z$��H�Ũ'`�����t���	lK.T�i\�k���4���3�f(��\Pe�@��D�_���|�z����<td��|5.��e�Ai�ArBv���tA.�Ai�Ak�An�AtXf	�v8���������l(�ä�����t	�l�����(	|�b��d�g,�h�7l@�m\�n��p��r�sDt�v�z��@��H���t�oh�p�����|�g�n��Ô����e�i�o�BŬ������L��h�������.�Ba�Be�����B��"�<�L��$�a�����8�aT�p<C��,B��TCdpCfxCg|�i��t��v��8�B�BT��h��tx����a�i�Co�r�Ct�C���B�PB���kDm�p�tp3
B�=B����a���8�Dg<�n�DrD�v 0B, 	�!��!L�z� ��T�i�l��r$��!��t���!��|�À#��h".$	
�b�d��f�kh�l��m��p��r�s�t�#�<$D$����P$���Ä6
�%��$T�l�$���a0�l8�mH�o\�t�J
B�0Bp�D%@�zX}!`}��T�ah%.��a��d�l��t��vX%�&	<&	t&	�&��dEa)�$)��)��)����z̮
Bh�
���t�)8�d,�s�)�*�����*����
t�+�����+�� ��,8�Fb��l�r\�sh�v��8�-��T�sp.B�.8��r�.��p��x���.��/��4�lHHr�0T�0����i�0	��d�0������0���e�� (1�p`1��h".k�p�t0�z\2.�2�ܸm�2�����2.$��`3�ocp3��<�a�Hr84	Id��g��h,Ij��k�n�r �t�4Bx4����r��
�P5�,5	��s@5������4����È5��LIi�5��68�j��r�t�6��JsH8���O.d�',9���h(�r0�t8�v�'�9''�98�Jd`�g�Jnh�rp�vHKz�9B<:B�:��:8��rD;���O.Ph��l�S'�p�����oxB���Le�B��tB����e�U	�Js�U����P���B�� �e��u��p�Ō|�� ra,�eD�àc�t<}��l4~	@Vr�|��8����	 �s�	(za|�e��s8���\��dWj t���r�s��rй����o���������s�z����s$���ĉ.@	a�	b�hc� 	d� 	e�@	f@B	gpB	h�B	i�M	j�N	kpO	l�Q	m�Q	od]	p�`	r�`	s b	t�b	u�i	v\j	w�j	z�	�`\	�̉��a�	b�	c	d(Qe�	f	gh	h�	i�	j�	k�	l$�m	n�Wo�	p@	r�		s�
	t�
	u�v	z�	�4���,XaH����	r�Cs���D..�	b	t�	�	lD����	������	��
y��<���g0	p8	th���	a�	z\	�\������bL	oT	t����^������x	��	�p�,l�	p	vخ��Tì�	�	kHRzl�=L������8�PD����	a�	i�����	rp���XcH	d|	l�	n�	r�	s�	z@���	�	ad	e|	g�	i�	k�	o(	u<	y	�е����o��0	t����<	at	u\	����t��l	����,�`����	a ��x���	y��,ܿ.�	a���(���d�s�	z�Q��.�	a�������	o��	�g4l,	rD	t����	��	�	�4	������|����np�r�t 	������>aT	o\�n�����\	bdQl������t	r��d0k�t�9����	��.�	���	d��	a�����	i��t���	rts�p����	a �		r@uv$���� 	r��eh�.T.P	bX	u\:���M	����`	h���.�	t��t	l�z���4~nH����	a�	i�����	t(�#\�Bl����	�(����	����	r���	�	a8[c4	k l\	oh	r�	t�	u 	�H��� ��@�����,	eH	r�Suh�y@�����.��P	l�����i|	o� �H������EiP�l��	s|��H���	�0.�	����	h,	sl�	c4	g@	kP	nl	p�	s����	a�[b�c�	d�	gL�j(8k�	od�r�	t�	u�	vl�y��À��TSupSz�D���X	�t��`	�����x	a�.�	t����	z�
_P��	olppT�m0st��T.�	hL�
8!`�"��10Wl�Tt02���	aD	eX	gl	t�	y0	�5B$5��(	�$:t�9<	g0@n�=��P	i��	n(I��d	o|	r�IK	��.��g,K.�	a�	üLQ�L���	��W�XZ.غs�Z���	a��l	r<�s�	�X_Q]	�	t$]���	��g����e��i��oP��Lo	ػnxo�� 	�$���		�`k��	l	bt	c�	i�	k4		mD		o(�y��z,	�\�_�r��T.�	c�	h�	k�	u�Y�x�t�,�����	o(nG`�|���	i�z�	s�}���a�	i 		ä~t�~�	n�Q_�Q			n,		r�}��		�R,����m��<		kl		m�		s<�BH���X		�T���`		Ôo�n��x		s�t�		h�		k�		n�		ŕ	�		k8�
*��(V���@�k�		p�r�		t4
	z|���`[e�dr�a
	elr)
��y�y���
	gP
	m �.$
	e�
	it
	�Ch���H
	e�����\
	t��vp���d
	��
	��
	�D�����
	s��.�
	el���
	vԞp8�	���
	s������
	a8��
	d<	l\	n46zt���	�
	a�	e�	f�	i@
	rp
	t�
	u�
	y�	�t�8D�(	pX���0	a�����.H	a<��P	y�2��	h	g�	r�	zĤ��p	�\	�(
	��D��pAa�	k�	s��y�	a`Yn�	�p�
��HH��
	�	r4����	�\\���
_l��g	lH	mT	s����Yi��lD]t4	�$��4���,	�������@	e��.��	\�l�	r�����l	e��yt	tP�C�	lh����	������	�p�H�	ä�C�����	���]d�	n����d\f�	g
	t�_x�_<�8H���
	�T���
	ø�	
	r �(nH���4
	aP
	ø��������\
	nd���d
	i��t8�|
	t���.�
	a_ü� _k�
	r��s�
	tĽ#н���
	a�
	i��t8���T_a��oL�����
	l�����
	aP	et	s,	À�	�_l<	t����	�h	��`��<
l`��@�H	l԰��T�$�	\	kp�.D �@�	�`b	c��fd	g@�hH	j�	kp	ld	m�	n��p�	r$	s	t�	z�����	��6	�M	��X	��Z	�pg	�h	��6�����	s��.�	a 	e0	iT	�4����	s���������(	nD	r �s����_���L	�@��@���T.T�a�pb�g�	i�pk�	n�	o�r�	u	y�	ø��� W��p��	�8q�H�.�bg������	a����������	k	n����� 	a4.,	a��		d�3����4	n0���<	a�e�	o�	r�	u�	��W��d&t�	h	r0���t	�t���T(l`L�	n�{���	eX�t��	lHTL�l�	rx���	a	e8	i$	�L	�H� H��T.		r���	�D	�����0ml|t�������	�
�X	l�	r���`	a��c�e�	l,	mD	o�	�
��	al����	��	�l8����	tD	�	r�x:m	p	t$���	a	o\���vvDvP	mp�|��$	a��o��<	s`t�P	s��X	a���'Tp�s�vT'��	t	a�	c �e(	ih	s�	t4	y�	�T	ż)�� �D	��	�����*���
c�	e�	i	s�>Ô+#t,�P,�	pЍ	_�,��	��,.	Ø1t�0 	np�s<	t2tx4e�4��4��L	�X�	� 5��`	s�5�c�	g�	l�	s6��t	a�	�(�	���	t4�	tD6CX6���	��	��	��6_�6	�	kx�	_�7	�	l8��8	�adH	g@@jX	k��ld	n0�p8�r�(uH�v�Lz�8.	aP�e�	�8��
�9��P	a\9.p	yL9�<���O.��d0�z�;8x	r:���	��	�La����>L8Cd�dH�g	m�bn�bsdt$�upC���	a4	e\	i�	k�	o�	u	��XŬE�4H	�l\H��	�H	�����c����@K>mlM�lL	@	s�Nt�NT	dt	k��p|	s�?thP���	i`ck�	t�G��G���	��P���	�pQ�xQ�	kQ���	o(���R�	l�[P�g�	h�k�r�18_@dd��lL	p�Br�+v46zt_��	a�	bee��o�et�	zt	Ðe�a��b��,gn���$b	T	r�	sLb��d	������e��e�@c��	n�	r�	ôKX����	�ph0�
0�ml����	j�c���	e o��CvXo.�	a�uX�
kv��	a�
c�	d�	e,
f��g�Ih�	i؆j�	l"n�o�	p�	rh�s�	t(�v`	��w���g��	��g�D��h�`��xTX�n�y�{�`{�	r,�
���
���	��|���	�@h����e���HEeЈ�T��	l�����	a	e(	i\	st���#ܑ.X� 	aL�sD	��#���<	����Fl<�.P	ap	e��zh�$�����c�	k�	r��vX�x	k	l4	n@�pd	v�\z�����	a�	e��g	i	k	l 	oP�pd	r�	sx�t�	u\ 	v�	�8	��G����x�	g����	aL�j(8k8�`H���,	aD	y��.�a4��P	r���X	a���\���p	y�	x	g�	l�	r4	s�	tH����	��	��	�	� 	� 	�������l�R��n�����	o�	r��������	������	�h����vx���	�8�.T	n�	o	���� 	z�|	=T���@	�DX
��H	�Ȩ<t��`	a�	k�	o�	rLt�h	k�X
�Y
�L
��n����	a�Ih���	c�	k	l,	pX	r���4���4)e�j�Ld��g����	e|ild�r	s�7��x�z�������$	e�t��8	gx	l�	n�	s���@	a�	e,	ip	o�	t�	�@��P��|����������	��	�X��@��_g�	s�	v��P�	Ō����	zP$�`$���	�$����	xl	s�,t@B�@��	����	� � 	mH	n\	s�����@	g\��8���T	zp��|�h	l�	s���@�,�	��g�	k`�p�	rt����e�	�$�8�����	�8��D����	sd��	s�����	e��tt�z�������	e��	dH	kl	l�	n�	p�	z��t��4	l����<	a<e`�n����X	a|���`	l�p{.@�i�	m4���x	a�	d��fh�g�v8�y��	������	at��L�m�	o<�z�8�	m��	��v��z������l\����yėü��bL	l�	nD�p�	r�	s����$�ad���@	dp	y����^
	\	k_
.d	a��_D���|	d�W8��
���	��	�Г
���	�x����	m�	opg	8r����	z��.�l�����	z����� e��	�	l�pv��X����	iD	z��		s���,	�@w�$�	`�d4���L	������	e�	oX	Ø'�Lx	vd����	kX�n,����p|�t�Nz|�t�.�	e�	�T����	z4��	sزt��0����	��	��j��	�g��l��s( 	tX�z\�_0�C<���0 	� ���8 	È�D 	zX���P 	ehPj�Oh 	b� 	g�pLO��p 	a� 	i� 	rxQj���peЋ� 	l� 	px�� 	a��p���4	a��o`����@!	aT!	b�!	d�!	e��f "	g%	h(%	i`%	k�)	l-	mT-	n/	o,/	p\/	rX1	sd3	t4	u��vD4	z/	�d�@�lH�n��v����
������`!	e`�h!	r����t!	e�!	r�z�!	�T���l�y��	�!	n8����!	�`���dR�0����!	À��s�!	t�#�������!	a��!	bX"	l|"	n�"	r�"	s�"	vX���
"	a#	e�#	f�#	g�#	iH$	ox$	u�$	y�"	�l$	������P"	al"	ut"	v���и_P�t���������"	z\����t�"	m�"	s�"	t�"	z�����"	��#	�@$	�P$	��8���$	�|�>
��.�.D�.d�pg$#	llr\#	s�#	t�#	zD�t4#	eD#	Ő�#�������<#	����m����P#	et#	zС8��.l#	t��������#	a�#	e��`����	 �l �s�0���r��#�#	g$	n$	r$$	s4$	t������#	��.�#	�d	T���$	n�����	����,$	t4�tt��l�e���`�	X$	rx���`$	�d���,H�	�$	g�$	l��,����	�$	zH�.�$	e�$	hh�lhs�$	��<��D�8�$	t����$	������$�i���$	��%	t����%	e���]rl
t�	4%	d�%	g�%	l�%	m�%	r�%	s�%	t�	��<%	aH&	ed'	i�'	k(	l(	ohns4)	tD)	u�%	�()	ň
t$���k8�t�	����%	c���l*tL,�	�%	l0�p&	r,&	t���%	��&	��'	��(	��(	�h)	�p)	�|��T.�^a0
�T�<��4&	y<&	gp&	l�mp�&	r�&	s�&	v<	���h&	l���`���|&	s8P�&	e�&	m�&	t���&	z,Q�y������&	eP��	��k�&	l'	r0'	sD'	t�T��T.Dd��k'	t���Te0n������('	s�n���<'	e�����P'	o�X'	d4�j�'	m�'	n�Dp�'	s�'	t�����'	�����'	�$����g0t4��(�t��x����'	a��\	�'	r�}t�j��jh���'	u�d b4(	k<(	l\(	n@�ph(	r�s�(	tL�t����DdL(	t��_Lz
_$���T(	k4.|(	a�(	o�(	t4��`���	l�	����(	t�	�jv����p2� ���(	t�	�(	l�(	n)	r)	tL��;vH�	�(	m�����(	�l��)	��_8�t|��� )	��j��l<)	jX)	n`)	r�����e	4`d�g�)	l@�zH����d(ü�Š��d*	k����)	a4*	b<*	e��fhag�*	h�*	i8j+	kX+	l��m�qnro 
rp,	s|,	t�,	v-	y��z *	�,	Ű	4ag��lH��*	��*	�+	��
�t�fd*	kXlp*	m�*	s�*	t�����\*	tL ����eloz�"��$y�*	�$	��r%���*	��%Q�%	�*	g�*	lHar�os'5X*��a<�e�ü+��`�.l*�*	t ,V�,�+	����\.�(+	�ة����-��-	0+	l�-��8+	��+	��+	�8-���+	e�+	i�+	o�+	uD+	��.��.��t+	e$.|+	n,qrd���T.�	�+	r4/� /���+	g/�+	nz@/#�/����2��2���+	e�1	�+	d0,	lD,	tP,	z��82��,	�`�.l���(,	e0y��6��<,	t(7�d,	��}�}��\,	��7��0�z�7��`�a�,	e�,	i�,	y�,	�8��
��r�\8�08�,	r��t��n� ;��T.se��n(soD
��=B�=8�,	k=���,	��<.-	�D?����l0-	uM��L(-	l<N�fd�tl0�r@�utN��	<-	a�-	e.	g.	i\.	s�.	t�.	u�.	y�-	ÔO	t�t�O���-	��-	�,.	�4.	��.	��R�gg�dl�dr�-	t��	�����-	�0T���-	�@���T����spT	�-	s�	��U�-	s�U��.	eDVhkl��pdW��Y���	,�Z	<.	g�Z��D.	�`Z��P.	�p_�\��h.	l�\p.	lP[��|.	e�.	i�]�p]�.	p�.	s�]tH^�T^��^��^.�.	a�.	e#�
���.	a�_	�.	m,b�um8f�$e���/	�Ȩ�j��$/	h,�iX�oԻr�ujpsD/	p|�s�s��
L/	awd�/	eT0	i�0	n�0	o,1	s81	tD0	�1	Ŭz�cPwd�/	g8j�/	l��m�/	t,0	z�{��h�y�|�t��X;a�e�/	����T.�	�/	r`���/	�d��l���0	hx�	0	k����0	�L��� 0	Ũ�	T�p�v��80	�`����L�l��mt0	n�0	pTB	 ���l0	f�0	g�B	��tȆІ���0	�܆.�0	�l����0	y��l��r�0	s�0	vЇt��@���	�0	l 1	s����0	����l���1	�����1	�,���t����r���h�D1	s����L1	a�1	e�p�2	t�2	z2	�������b�1	ð�|1	m�1	s�1	t��,��	�1	n�����1	�����e�����1	������*t�1	��GԔ���1	y��	�1	l02	s���2	�KL
�H��2	����P2	hX2	k`2	rh2	sp2	t$2	��8N%�
%��]�%�(
����x2	b�2	e�2	fxTr�2	Ä���.�2	kx�� ���+��2	��_��a�t������2	nĝ.�2	a3	e(3	t������3	m\�1h���3	�С��3	�t����.43	a\���<3	y��H3	n�3	s���T3	a�3	e�3	i�3	ä��Х�3	z�)4����qy��3	gPflt�t`����g̰�3	n����	�3	rp����3	��3	����x�`8��3	gduk4	s��t���нsP��\�$4	cp4	jx4	k�$u����	,4	a�4	et5	i�5	o$6	rd6	s�6	u�4	��5	�L�tpH.$�	�>l<����4	�05	��5	��5	��%�`e��p4�pe���4	���4	d�4	g�4	l5	r��@���|�j�pl5	��k��k	�4	hl���4	��� 5	e$��p�_0�	(5	k@5	r����T.�d`5	e�ri�����X5	t@�tL�l5	d��m|n,noh��`�	�5	r��t���5	l��rL�tl�	$|e�5	r6	s�����5	������������5	�@u�����5	sT���6	m86	s4���6	e�"�d���06	eL6	rT6	t�v��v��R��.\6	a|6	���_����t6	�l��`�#��	X�b�6	c7	gh7	h8	jl8	k�9	l:	nܸpX;	r�<	sH>	t�?	v,@	z�����.�6	a�6	o����6	s��t�������7	a(7	eD7	iX7	o47	��Ŭ�#0%g����̷�����#P7	s���X�t���`7	a�7	e�7	i�7	o�7	r8	s�7	Ì]t�����7	��7	�(�I���7	g�7	lP���dr�szt���7	r��������	e�������7	����8	�\���T.~b<8	e,�sd���48	s��T��d`|j�8	kȎl@n�8	r`Cz����H8	a�8	e,9	i\9	oX�u�8	��Š�t\������Ў�9	�4��d9	�`��P�g�8	l9	s9	v�z������hHl�8	������8	�d��p���9	e�����	�kl��t}dP9	r��B���<9	����D9	à���t\�.�9	l�9	s�Tz����l9	eL�m�9	y�9	�����W�	�9	n����9	��.@�ax�e�9	�(�����9	�d
L����9	r0:	t���:	aT:	e�:	i��t�:	y�:	� �d
%���8:	bd:	h �ld@:	k$kl��%p,H	l:	k�:	sh��t:	���	_�Lkd�:	npks�:	v8����	x�g��l�:	r(.�:	e,;	i8;	o;	�� �"�!8�:	k\���:	����l����L;	�l
��"	$;	mh#��#.�>�$8@;	l�0��	�cd�e�;	i�;	k<	l8<	t�<	vt�z�;	��;	خn�;	r�;	s�;	v�2���;	�<	�|<	�|<��<���s�<W�>��=�;	e\�n�?������?���;	�H`�@�;	l(@��<	eA	88nxB$�kH<	s<B��(<	eh<	�B��0�B	P<	r��sC��X<	���W�D	t<	l E4E���<	a�F(G���<	a�<	e=	i\�s =	z�<	�H�����=	� I��l�<	r�<	z�Jt����M��L�<	n,O�hQ��l�Q.=	a�=	e�=	l�=	s�=	t\=	è=	�R��R/D=	lt=	r�R��L=	�$>	�S%(S��l=	a�D
��S��=	dt�l\W
�0Y�<Y���=	�TY��Y��Y���=	�>	��Y���=	h�=	i>	j>	l>	v�=	���%�����
%�
%�b
�<%8Z�[t�Z,>	dp>	kx>	n�Z��4>	a�>	e?	j\?	kh?	o�?	u�>	Ä?	�|[t�[�X�
8�\���>	tt\	�>	r�\���>	��>	�p?	�X*���ao ^���>	h�>	tH]�>	l��v��
��T�aL`�h`	�>	g��
���
��?	e0?	�i0�f	?	r@?	v��
�� ?	�H?	��i0��`�
PH.�b��P?	e c��c��dC$�r�d��x?	��?	��e�HftDg��
T.��b�?	e��f��h��i�Mk��n�Mr��sĈtԈv @	��h�#.�ik@	n@	st�i��i��j	L�b�<nth��@	��n����ad@	ep@	ih�oTns�@	uT@	ð@	Őo�����`���o��g s�{l��m�@	n�@	s�@	tXs��s���1m(t��v#Tu��D���z����eA	ohw�@	lA	sw���@	aHA	i\A	l�A	o�A	r0A	ä|��|�@	mԀj�����,aP�	A	mh���$A	�ȜTA	r��<A	t�
������a�o(�uD�е8��tA	t����|A	a\8
�A	d�����A	aL��A	g@�Ĭ�A	k�nЬ���A	a,�e��i4�o�A	� ������-����x�fd�B	l����B	eT�i��	B	p$���$B	�������TB	l �r0B	�
�� �i0��\M��sM��dB	a�B	oHd
	�%
��B	tpz���B	�8zP�B	�Hz���B	zTz���B	s�u�B	s��4C	a0D	bTD	c�D	d�D	eؘfE	g\E	hpE	i�E	k�E	lF	mtF	n�H	o@�p�H	r�I	sK	t|L	u�L	v�L	xM	zD	�$��a�C	bhc�e�C	fH{gp�h�C	l�C	m�o�C	p�C	r�C	s�C	t�v��(��І��Xo�j�r(���peȌ����\������D����C	zĎ.8�iÔ���X�x���D	ixo�C	��t���t�D	z����D	�E	����4�����l��P�p�.<D	a`���HD	s`�H g�D	i��j�D	n�o���\���`D	e�D	i�D	o����D	�lu�ty�D	tP[��X�o��Ԟ���D	lL�B�D	z�����D	��U����l���L�E	fd����aPE	r<E	Ô�	��n���0E	��$����HE	i����lб.��hE	dl����2i�u���|E	l�E	o�E	s4����o�E	t���E	nx��������l(����E	a�E	l��p����E	e��uF	Ì�8$���F	�p���D�a�!i@F	�@����	,F	h����4F	�PF	�t�	�r��\�j�F	m�F	n�s ���\F	a�F	cG	d�G	e�G	f�G	gH	i0H	k�H	s�H	uz�F	�8��`�.dyH�nT����F	a��	�F	r����F	�����������F	eG	h�P����G	aLG	elG	i�G	u4G	��������xG	����H�DG	k\G	rP�	t���dG	n8�8x������g�G	sX�	.t�d����G	l�G	��8�	���G	���t����G	��G	������G	h�G	ô�8D�������j��H	nt�#lDH	nLH	s����H	a\H	epH	���	tP�	t��	���TH	c�	�����hH	�d#��������H	p��	�T��H	kL��t����H	a�8�(��H	n�H	rT�.����da,�g$I	hXI	i�ckdI	o��rI	���
��	I	n����I	��I	��:��;���	,I	l��.4I	e?��@I	yx�LI	g��'dtI	m��������	|I	n���lP����I	a�I	eJ	iDJ	plJ	s�J	z4J	ÜJ	�|�	�����I	v8��I	l�I	ppl�����I	e��	����I	g��J	n J	t�gy�	�Mr���(J	��J	������5i0!
��kb�PPJ	a�J	e����\J	z�����xJ	r��	d�tHvh���|���yd=.��J	r���J	��.�J	f�J	�ܚ�����J	c K	n,K	t����J	a�K	b�K	i�K	lL	rDK	ð.pxy�{
��	�cXK	r��4K	�L	�LL	����h".��h�K	i�K	kL	n�!r�>	t�K	ø�����K	��K	��pl�
� 1�������K	o�
���g
�K	n�K	s�
.�4��
���K	��
���K	�
�
	L	k�
���i0L	o@L	��
��p�
��8L	��	� rxC��XL	�����i`L	� lL	m��s����(a�L	������������L	i�L	��L	�(���������L	��	�X.M	t ���L	�t���L	À��`!	HM	gXj\M	nhM	rtM	v�M	z�$1�$��4M	��$��<M	�X58 0��TM	s|7��$��<C��h".�M	b�h�M	r� v�)���8�H8�I��*�D��i���P��h���M	��f	�M	n|V���M	�\N	��P���M	o�M	�tN	�<t`�b0N	g(l�:
,�t	N	s�t��N	��t��$N	Äy��y��<N	�Xy��DN	Àw	PN	s�z	Ts{��hN	�́C�Zr$����N	�,����N	ì��N	r@����N	a�N	e�N	i�N	l��r�	vHO	�H�$+p���a�et�8���$�a��iD�o��u��������O	t��O	s����$O	ol�	0O	r���<O	�HTO	f�O	ph�s���\O	a�O	eQ	o�O	�HQ	��*.�O	o�*	�O	r�*���O	��)���Ee�i�O	øRn�4�����PP	�hYj�V�O	gP	k$P	pԘà$^���O	nho��j��P	a�i��P	l�{4|��0P	a�P	e��o`P	�`w	8P	k�P	s8}���&��P	�����P	���t�|xP	l�P	s<���T.�|���P	t$}	�r��e��_�����P	���.�P	ü����P	z��	hClȶ���P	�Q	�l����P	À�Q	k���4p����$Q	a\Q	�<�	,Q	s����<Q	�t9
ex���TQ	�	���Ng@�l@	hQ	g�Q	i4�s�Q	t���xQ	a�Q	e�Q	i�	j�
	���k� 	.�Q	g "	��t�P���T=�ؘ���Q	��B	�Q	f8'
PbTR	c��f�R	g�R	h�R	iS	kT	l�T	m�T	n�U	o�U	p V	r�W	spX	t�X	u�1v(Tx�X	z�-
 �,
��LR	i�=
���.4�f�R	k�R	n>
	`R	f>
��xR	��=
���R	Ì8
���R	r�R	y`���k	��>
G�>
.�R	aXA
Td?
���R	u�B
#dA
�R	s�C
Ƞb@S	dHS	kPS	lXS	n�.r0D
���R	aآe�S	i�k�S	o�S	t�S	utS	ÈD
t�D
��D
��D
��E
	�bdhCl�S	s�E
��`S	��S	�X���S	����F
,H�tpH
�S	l�J
�l,�rl�e�M
�S	a�{
���S	t�M
T�S	r�M
��M
CT	sT	t�M
�N
�� o�N
���.a@T	d��ipT	l@�t0v�T	�@S
���aXT	�XU
�TT
��PT	�0Z
T�T	n�X
��dT	i��Z
t�[
	X�i`Q
���T	�l`
���0llo
�5c��d yl�T	n0ypU	r0�s�o
��	�T	a U	e8U	iLU	oXU	s�U	t�U	yU	ØU	�q
t�q
.�q
���������u
̤g0U	r�v
ty
\�s�	#�z
DU	rp|
dU	p�|
(�	�4�	��lU	��	��tU	�}
���U	r`|
��`��|�
��U	z��
���U	���
.�U	�ԁ
y��
�r��_�
���U	c�U	e �lp�t�
�V	r��
��
�d��n\�
��V	a�V	d�V	e�V	iPW	jxW	owr�es�W	ttV	��W	�P�#��
��TV	a�
	\V	l,�
��hV	��W	��� ��\
8����V	���
���V	�h�
��l�V	n�V	r�V	s��
�̎
���_؎
.�V	tĐ
�d�eW	sW	zt�
.0BkW	z��
.�oؒ
���Zg�
|�
(W	apW	tdW	�,�
0W	i8�
��DW	aH�
�P�
��\W	���<�
4~l�W	n�
_ �
���W	tH�
_0�
	�W	nĿr�W	s`�
�Ț
��r�W	�<�
9L�
���W	���
�ԗ
���W	�D�
��X	eX	t4X	zd���
��
��XoxTr,X	y��
�,�
.��aHX	i\X	lP�
��?sX
�\W
��TX	oL�
�h�
��hX	h�X	tL�
����a̮
���X	h�X	o$�
#��
�1n�X	sp/>��l�
.�X	oT�
���X	s�
	��bY	cXY	d,�fX�iZ	k(Z	lypLZ	r�Z	s�Z	tT{�`�
���h<Y	s�5m<]�ؾ
�$Y	p�
.0Y	a��
�lxY	u$�
��HY	a�Y	e�Y	i�Y	o�Y	u�Y	��
�(�
	�Xk�Y	rH�
���Y	��Y	�l�
���
��
���
�Y	pd���
�Y	rl�
C�Y	r��
_,�
T�Y	sX�
H�
��(�lZ	u��
��h
sX�
Z	sX�
��Ta��
�(�
4Z	k`�rh�
��<Z	a�Z	ixZ	�
	P�g��i�Z	j`�m��
��`Z	��Z	�t�����<#�
�Z	s$�
�Z	a�
C88nt�
���
���Z	k��
�L�
���Z	e�
���Z	h\		LXblzkL[	l�[	n�[	r�[	t\	v�$�.[	al[	e�[	i�[	o\[	�[	��� [	s���@[	c�Xe,�����[	�P���l|[	s�K��K���O.�K��[	r(��p��[	l�$�M��M���[	�)��x�uT4����o�[	�td���[	��A����l8F��(Ye\	�,\	�xF��LY�xi��H�����,P	|�a�e�\	k�\	p�\	r�\	s4]	zp\	ØP��8\	�ph	��R�������h���`���l�f��P�s0i,�h	�\	r�h���\	��g����h��i�\	Ür#\p���\	i ]	z�\	ð7
�q���\	�LB
��?
�]	rx��]	�@w.]	À}#�{��,]	i|����@]	rH]	d�]	k�~��T]	a^	c4^	eH^	f`^	i�^	l�^	o�_	rp`	s|`	u�]	����8[c�]	t��������	�l�]	r�]	t0����]	��_	��`	�Q���Xo����]	k����"j^	k���#����^	i�	ė�� ^	t��(^	lX�����@^	rج����d��T^	nx^	rD�#���p^	el��$��^	k@����^	a�^	o�^	u@��Ll�����^	a���^	mh�P̶����d���^	n�^	r�_	s������d_	tȺT(_	lغ��_	aP_	eD_	��
t�,(�	0_	r4���8_	�d�x�0L�
	X_	sԼ��`_	����l_	è�.x_	tX����_	z��	�fs|�	���_	f�_	pL����_	e`	i,`	oT`	u�_	à�@�U�	�_	s ����_	�����=8��`	b$`	c,Po���hhd��f�.gd�jL`	pPrt���t�PL�B��.\`	i����d`	z���rt0�	X�zH� �p�����`	a�`	e�`	�L�n�+m8(	�`	m(����`	������oܽ�`	vh���
�`	a(a	k��l�m�|nLa	oXa	pH�rta	t�a	z4
���%a�|i&o���

��|	dT
@a	n�
���&a��e��i��o}��%
����a`'e��i ox�r���;
.	(a�a	c��f0�l�(mt�p��t��v�a	�?
�$�r0@
���a	�b	�~��C
.d�����a	m����a	e�M
��a	d�f
<)nPf
��b	a�b	r8b	�`b	�D~
��`)�Hb	�L�
	�s�
	�kP�
��Tb	���
.�b	z��
��lb	s��
xb	n�
���b	adKe|Ki�b	���
mp�
�������,���)��K�	`�d�b	gc	j,c	lPc	m$d	nHd	r�d	s0f	t4����r�	h��c	j$y�c	t0�� c	e<c	z����d���Dc	atc	e�c	i�c	kd	u�c	�4��!Pl�"	pl�st!���c	�����#�`#�c	n�c	r�sDvvH$t�$m�$���c	z�$	�c	s�$���c	�%���c	��d	����s�&d	s,('��d	g<d	o�	�()4d	k,��`�.dd	a�d	i�d	u�d	�+H�l�
m8�t�,��,	xd	l�,���d	�$J	�I���d	m�;�d	z�.4/��lظn�r�+s�+tp/��	�d	a4e	eDe	iPe	o\e	s�e	t�e	z e	�T,��0,�0	e	g�l�0��e	�@��D,��1�tg�P2<e	r�3<,r�4��z`5��L&�l5��he	�x5	te	r�5���e	��5���e	e�e	�T*
��dj�5�e	r�6��l��v�6.�e	a�e	�t7y47��e	lf	sT7���e	��7y�j
��:f	gPf	l�f	n�f	s�f	z;��f	ag	c$g	e0g	i�o�f	ð;���axf	vlf	��p
��;��df	��;Q�t
.�f	yw
,8�B<����f	�<���f	�T<�
X<=���f	aĦbԦuh<	�f	rg	s�<���f	�Hg	�`g	�hg	�p�
QH=nP=��g	a8�
tt=g	l>l@g	n0>t�?	�dXg	p�����
tT@��C	�j�g	l�g	r.s�g	tD�V���g	e�g	otR���g	t�V��V�g	l�Z��T.�-i��n�b��P^bd�hh	i"jt/k|/m"n�/o�r"t�/vdX�h	��d�fgDh	nMrPh	t��v��zHw����Xw��8h	Ä|����ehh	z��Ȃ� ~�	
�h	b�h	f�h	g�h	k�h	pi	r(i	shi	t�i	v�i	z�����l�h	r�(�����h	lh�r����0,r�������h	l(�8����h	li	n��r�f�����l0�\��� i	p�zDi	�4
C����<i	��	�k,���Pi	�0���\i	�0��p���ti	s��|i	s`����i	e���8�i ��P�Š�Rtt����i	od��i	d�����i	a<j	e(j	�4.4j	a��	�i	d��.�i	at���j	yL�	j	g����j	��3����2r��#����Hj	i���Pj	hXR���eHChj	t�B��tj	a�j	e�k	r�j	øk	ŠU	X�s�U���j	��k	��k	�@s��T�o�c�j	r�j	t�|����ak	e`k	o�j	��L��|���L�Dk	����0}�<}k	g�rl(k	r8k	s�}�����}��0k	z4~	4�kTk	rh~��TKd,#d��|���hk	a�k	o��	pk	s��#�	\	kdW�r�	�k	e8����k	�Ȼ�D����k	�����k	���ĉ.Tl	a8�	b��	c�	dl�	e�	f\�	g8�	h(�	i��	k��	l��	mp�	n0�	o��	p��	rD�	s��	t��	u��	v
x
y�
z}	ð�	�̉��a�l	b�l	cm	d�m	e�m	f�m	g�Qio	j$o	kpp	lt	m@u	n�u	o�u	phx	r�y	s�z	t|	u�|	v�|	w�|	z�m	ì��l����l	�@����l	�H����l	b�l	r�Cs4�_����$�l<��gh���m	aXm	dhm	e4�m|m	r�?t�m	uHm	�\dQ��	4m	s���<m	���t�_$m���`m	k�P|���tm	e,�.PRs�����m	iL���������]�����D�f@���\�an	iHn	r�n	y�m	�|����	�m	r����m	�(n	������n	t�� �	 n	nT��\���4n	�<���<n	ð4B����Tn	g8�	\n	g�n	l�n	p�n	s�n	vh�.hn	a�n	�����y�����n	j���܊������8�n	k(����n	��n	����n	r�tL�	��#�����n	iX�k���Dd`o	r���o	a�o	k�o	l�o	op	r�	tp	u�o	�́Cl���Lo	�(���To	�������lo	z$�	to	s�o	tH����o	��o	�0p	�8�_�p�����o	o$�������o	i�Du@�.�o	a���o	l����	�.�����R�P�l�p	sh��"n��	$p	t����	�l<p	d�p	g�RjxSl�p	n�p	p�Sr({v���
Hp	atq	e�q	i�r	j\s	kps	o�s	t�s	uq	äs	��)�%����p	a\}t���p	a�p	d��r�Stx_Q4	X{bq	g$q	l�{r@q	td���p	��q	��q	��s	�4��H�9,L
j
��,q	��	��h�s4q	�T�`��Pq	���4�lXq	ð
dq	l�q	nth	�
��q	g�q	t(i	yh".�	�q	g�q	k<|r,_��Ud�q	n�q	o|��0�g���,�	�q	rLr	z�c�
,���	r	bXr	exr	h�r	i�r	k�r	n�r	r�r	thr	� ��r	s����@r	i��y��nP���`r	��r	����
��y���
�
�
�
�
D�zh�n���D..�r	as	i,s	o8s	u@s	z��.�r	c�r	ds	ihEks	ms	t��������T��U�@p�v��$s	n�������Hs	t���Ps	adphs	b||l�s	p�sX(0,,	�s	r�"��̐�t�s	r� L������s	j8!�s	l�s	t��t�*�)�s	r$���s	eht	i�p4u	�-�
�-��t	o�-��$t	btt	e|t	g�t	h�t	k�t	l�t	m�t	p�t	r�t	su	t�t	�X,0t	t�}�	Q�-�
.���t	aQ�K	�.Q.D.���t	�<
Q(.�
0.���t	o�t	�@.L
H.���t	�T.Q\.L
d.��u	�$u	�t.��u	À.L
80� '��,u	�02��8WaXWg��h`u	o�u	y@���E lu	g��	K	��g,K.tu	aXR�lXZD..�u	dv	gv	l0v	phv	rtv	s�Z��	�u	a�v	e(w	i�+ltw	o�w	px	r8x	u�v	�����u	�[���u	�[�l[�L[�v	a(8k@���[�(v	aHv	��[��[��@v	��[��[��Tv	�\��\v	�$\t]	dXd(g�Xk�v	l�Xm�v	r�v	t$]��|v	�Xw	��w	�Dx	�\]8p]���v	l�^�]���v	bX_��D
m�_.w	l�_�v	l+mw	s``_П	a.w	t�b0�l<w	nLw	s,c���+g|,t�c���,k�c��d��d��`w	a�dhw	l�w	r�w	s�w	tTe����mX���,s�w	t�������e��Tt f	�X.�w	rPf,g�w	o�w	r����gDh�h���w	�$x	��g��x	Üh%xh	x	z,j��i0x	tpj	�Brd�ukPx	i�x	n`k��Xx	a0y	c�y	r��z,mQm���x	n�x	yD.�dm.
�x	.�x	a�x	by	hy	ky	ny	r y	s(y	t�x	�ĉ�Tm	�x	.���m���x	��m��m��m��m��m��m����r��T.�y	h��o�	uTy	��8�s��Ly	�آ�$o#�ndy	bp�ly	at.xy	i��������y	�h����y	�D����	�y	v<����y	�����y	i�y	k�rz	tz	z�y	Ü��sЎ�,�aВ�alr<�� �.��axz	k\z	��!	�0z	d���8z	aT��Dz	rp���Pz	��z	�؛���������lz	���؟��z	g8��[b\j�z	l�[rt����z	a({	e�{	i�{	o�^u��y�z	�|	�X����[k�[t��	�/l{	rĤ���z	�|{	�T]�4���0���y`Yn{	�l�D�g@{	lx�j���8{	ep{	l\{	��
���
��T{	��
p�
��h{	e��	\�l�{	rز
��
���{	e��y�{	k�v̯���{	m�{	n���	gt���l�{	r�&s����cs����	�{	z�����{	��
#��|	c��l8|	rL_s�Ut��
	aP|	��,_���H|	���4���\|	td�d|	r�t����p|	a�_v�������|	h�b_�����|	b�o�|	ì�������|	�@�	0}	bL}	cT~	d�~	g�h�~	k@	l��	m��	nl�	r܂	s0�	t�����|	���	��	�x�	�t�	�H�	�`�	�������r�`��4j4���@}	a��h�}	sl}	�d������d}	�������x}	l�}	p�	s��.�}	a�e�}	i�}	o~	z~	ð���9����}	k �sp�7x��}	t�����}	aP���}	l~	r��9���<��@����4�������$~	a�,~	lt~	p|~	r�~	s�~	vD���8~	a�~	d�~	e�~	u�z�~	���0���Z���Oz�_�h���,�����������$d@���Ďa�i�u$�y�`����	�	�x��	o�~	���l0�8	��	bX	ld	np	r�	t�	z��� 	a�	e�	i�	l�	�%�@
��\	a�
��#���x	e84t�
_�	�	t����	��	�p:����lT�	g�	l�	m�	shYt�t����DB�bp@�nH�rL�s$����.0�	ah�oD�	�PH.�p�:t���T��l�	�l�
��L��d���T�	�̬	`�	k4���=�����ux�	��'�'��	dЀ	l8�rT'����	a�	c��e$�	i8�	s�	�P(t�),�)	؀	r�)���	��	�<������*���de4.C�t�1��0�	n5# 5��0�	e8C��d��	k��m�bnĩp�bs�	t��vpC��D�	a�eh�	i�s�ct|�	u�	��XŠDo�D����	aā	b�'h́	kԁ	n܁	oP�r�	td�W��W�DW��o�DW�G�� c.��	tH�_�H,4H	�	c�l\H���	�H�	������Ă	�����M����elL	<�	vČ��;T�	i�N\�	a��p��Ø[H�dP�g��	n��	r|�s��	t��\����	.�\t0]��Dì]	��t8_��st_��Ђ	aee<�	i�etX�	z0�	�c�c����	��b���u�	�$b	�	rp�sLb�� �	�df��k o�P�nX�sXo.H�	areȃ	i�`k��	o��	Èpy�p�x�	g��	l�p����	���	�,g�hg��	��py�ry�r���	nPs���l܃	n�fs�s�$Y
0<t���	sLt��	k�u�`o
��v���	a�u�	l,anv��	 �	a��e��	h��	i4\lЄ	r�	u��	vl�	ôw	��l��	s�w��\�	��g�А�h���p�
8�x����	v{�,{����	i`{,�o��	r�{j��
�h��Ȅ	uX�j�܄	g�	t��|l����\id��t����	����X�	e�	�X��	k����,�	aȅ	l�	o��	rH�	u��	ð�ԺW��	`�	b��	n��	r��	v����h�	�t�����	�,�	��	kH�����	�<�WL�W(�W\���x�atoHh�8�D���܅	a���	n�����a�	iThothð�P�#X7���	i�6��$�	h���0�	g4�<�	r�����u��	T�	c�	j�	k�	l4�	nd�	rp�	t|�	u��	v����`�	a�	b@�	c\�	dp�	e��	f��g��	hx�	ip�	k��	o(�pp�	sP�t$�	uL-y��	z��	��	�t���`�t [Ì����jn�	t��_�P,����	a|�j(8k��t,uvL��\���,�	a��tP�	yK,K.H�	a(����\�	i���1t�.�������	a	,�ġ	r�	s,����	�L�	�H�	��	�D�	�\�	�D����b�k,gn�	t�X88_�����	a�	ô�����	������l��r(P�Gi�oL�	u(H����(�	s �<	���T�	r�	jX	h�	a��	g��	l�	r�	s�	t�x0�	\
����	g���p�d��	eh`m��vtSkЈ	mL @�<dL��؈	e�~�ĝ.�t����	z�_ ���	nL�_(	 �	rT��(�	�(��4�	�	@�	gp�	h`�p|�	vL�*s��d�	e���x�i\����lh�r�.d�a؉	e�	i$�	o��u��	È�������ȝ�8�	�@�	�����`�.�	l�	tf9i9t��<����	���	äv9���	l��nz����h�H�	s@��P�	i�\�	l��	sT	h�	a��	g��	k�	m��	n�	r��s4�	t�z���@�o� ���Ve,l�!���o������̊	��!��Ԋ	�"p{."���	ax�d��f�	gPtt�"_�&���o�ä'`�'��,�	a)	�(	@�	j��r`�	vh�	z�)_*_P*���l��	r�,���e(.�db��	k��	lЋ	r�t�.���t�/`�.����	iD1_�0��ȋ	v|2����.�2��܋	tL2	�	l�	s�	t�2���,s�2.\3����|�	�`4�(4�
,�	b��	c��d��	g@�jJk��	lČ	m��nHp̌	r<�uHJz�3.	4�	a��	e�	i�	o��s��	u�	z�	�|�	�X��H���4��t�a��	jl�B�5��6.PHa�	g8Jo�6B�7yȖrD7��	g,�l,�	rp�	th7���	�Ѝ	���	�L�	�Џ	��	��7��T�.��b\�n`�	r�uT�	�$808��L�	���8����8��h�	a�Jv�J�X:��Kf09���	l�>H?	��	s?����	��	��>��Lt��	�=���	r�	t�DH�?�
�?���	ah�L���	n����	o�?��	k��nD�	oT�	pt�	r��	s`Mt��	zA<��<A��L�	a�����`�	�����gth�	�H���<�m������z�A�
�A����	itA���	k؎	r�	z��A��Ď	��A��̎	�B'�A���	t�B�|B���	b�	k��s�L
B�B���	s8�t�x��,�	��
��4�	�`E�@�	l4�s�z�F���O.��	�,F�`�	rLF��p�	� 
��
����	�pH��G���	r��	t�H.��	a�H��Hy�H�ȏ	t8Iy�H�܏	g�tJ�]	r8J���	�l�@���I.8�e�	ÔK\�d؆g�vj<�	rhLt�L	�1jT�	t�L	�L	�g�t�vX�z��_HM	t�	r�N��N����	�����	����N.�e�sȐ	y��	�ODO	ȖihP���lh�	r�O
Ԑ	b��	d��	jđ	k�	l�	ndPp�	r<�	tp�	zLO���	ah#b��c��d�	e��fԫg�	i��k��	o��pT�	r��	s4�	tL�	u�	y<�	z��	��	�4��P�Pp�	t�P��x�	a��d��	��P������Q��Q����	��Q����	�Q��8[cؑ	k��t\R_�R���uj 
rDW���3ntY	�[n�Y����	��X��$�	r�	�h�	P[�nP�	pt[��,�	a�Pe`�	i�[(�](�]X�	n��r�_���o��	tp;z��	8b�wklb����	aH`	��	r,�s|`����	�$�	����X�	�x�	�`�	���	�l�	�j��ؒ	b@i	�	b8�	kl�	lԓ	m�	n\�pP�	r��	s�	z�m	pbL�	i�m���	�`�	�$m��(�	� ���D�	nD���	X�	z|n����f��	l��	m��	�q��p��	n�p����	e�q_r��o����	��E�t��	r�s��ȓ	e�	u��	àt#�ttt���	��O�hu���	��t��<�	n�	��	��W(�	k�W��0�	e,v�Hv��H�	a�e��	o��	���tw��v	l�	l��	z�v��t�	�w��x#���*t�y����	eؔ	zĔ	��`z������t|z.Д	a�	�t�������	��{��{����	a�}#�|���	oT|	�	gX�	k��	l0�p��	rȕ	s��	tp�lh�	zP~��H�	ex�	Ü1Ѐ��~��p�	�T���8�e��m01�����	jԃ����	a�O��O����	zl�����	sؕ	z`�.�hԃl�	r�^
��X̆���	e��t ����a�ex��	g$TlH�	np�	odTs��	�(�p{.4���<�	a��g��s�P�t���\�	j,�d�	n8�L�$��|�	�h�X�f��	m��	������	�����	k$�	rP�	u�ev���`�,l�	Ԗ	tМ��ܖ	������	�ġ�ԡ	�	l����	�H�	�d���<�	n�	��
n�
��4�	i�t�$@�	2nl�	r������ UÈ�	l�b��	sؘz�����,s����T.�	aD�b,�c�	d,�	e��	f<g��	ilj�	ktl�m��n�	o0�	p̦r��s0{t@�	vx�	�$�	� �	��	rd�����	�К	��g���1�$�	gX�iD�	kL�	l0j1�1l�WH�	T�	k��	n��	r��	t��	v����\�	��	�8�	�|�W|<Ht1H�kH�kdl1����	gИ	sؘ	t���pH1PmȬ�Ь�	k�	s������n�
�	�	s(����	��n�o8|�`���zp���H�	al�	u$�P�d�	k(�����x�	l�p�tԙ	z�A
 �MtxA
����	�4A
����	�Գ���	m�����	���.�	eș	�<��H��	l����	e����	rL�
����s~
	�	r<����	�����(�	�@�.X�	a��@�	t�:���	8$j��	r|�sT[�����t�	�D���|�	�0�	��g��	n�z��8Ľ����	n���T.̦r�1t�	��	r|�zx�.\����	b�	h�	n�	r �	t(�	v�.�#.�0.p3.@�.t����vL�.0�	ax�ed�	uT�	����������$OR��ț	a�	b�	d|�	f��	g؝	h�	i�	k��	l̡	mȢ	n,�	o��	pl�	rX�	s@�	t,{v`�	x��yl�	z�	�d�0�j8�k�	lH�nP�r,���L�aDjo�����lT��0���ka�!	ü����	r@�	vL�	yh�	z��td�,�	l����4�	e������T�	� �.\�	�������t�	fd��{rX�����	e؜	y��	������x	.�	��	r������	�М	���tH�.
h".4b�	eh�	fp�	h��	i��	kXm��	n`rhs��	tX�	�D;���	�	k<n8�	s@�	tH�	z��`>���4�Q���P�	���	�B8�8�B�d�	x�	g�D8�#8�H8�JyD�8��	nX�H��	��	z�����	������Qh̝	Ä���g�������
t�	�	jH�	n�	��
�	at�	e؞	i��j�	k<nl�	oD�	r4)	t\�	�P��	,&	t��P�	���	��	�P�	�Dmg��	r��,�����	�`�����	�|j��L�T�����	È	��	j̞	k��L�n��e�j�	a��	i������4(	kl.�	�	r
�
��$�	�`�,�	��8�	e	�g`�	lH��8�d���gl`j�`n���
l�	a�	e��	f��	gh��	i�	l�	n�	o��	t��z̟	�D�	�	��l�	rH����	���	������(n� .t�nf�	g0�	l<�	mp�	n|�	r���	y$.�. ����(�	eL ��`aX�	ed�	à h P�	l!�����!���q	gl"��%	�bp��C�����	�*����	�8*j�*tl*��	d0cm`�n�/��-��Ԡ	�8-��ܠ	�T0��Īe��tt0�	t�1	
��a`�	dh�	fh�hqkp�	l��	n|rr��	tP�	�82���	��1���a����2�83�l�� 5��4��x�	yP�L
�6����	��6����	��7���r�?�?��	kD?����	a��	b�	e��	i��	s��	ut�	�TB�`B���	a�e�C.<}y@C�	g0�	lP�	sTD�D��(�	e@�	khDQ F,�E��H�	zlG��T.�F	\�	rlA��h�	��Ht�H��	d��	s�I��I����	z�L�My�L��	ltN���
g�n�	s�y�zHZ#`Z���	a�	e �s$�	z�J.�	e�Z�	s���[,b|�b�/d��fT�	g\�	hl�	k��	l��	r�b#d?
#8ct�b��d�	oPct\c��x�	a�ii<�
t�d����	oLj��i��	dj����	a�	e�	i@�	u,�	ôl��|�mplԣ	l��	sxm_�m���	t�n��n�	d8�,Ľ	�	r�k�� �	��rt�r8�	tps�gd,�k�vnl�r��s��z�s��L�	aФ	c�	e`�	i��k��	o�	s��	tP�u��	�ȥ	Ÿv	��b�v����	�H�	�ص���	��	�z>y��Ȥ	o�{��zܤ	g�	j�	k�/	l4�	m��r@�	z�{|p{.(|���	eP}xl`}��(�	eL����	��gxlT�p\�t��8�dܣgL�lXxm��	n��	s ���PfPg��t�����l��r�0	st�	�Un���	k�����	��a��ԥ	����ܥ	�,����H�r �_�	�	g|�8�����	����$�	�(���0�	jh�<�	lD1	s����H�	a��	e\nsĦ	t�	z��	��̒	�bl��	r���|�	����e���������b\�r����	t����pmi+è��D�l�	r����Ԧ	��	�ĝ.�	� �	Ŝ������l��������	���xobl�	l��n���	,�	a��	e�	ix�	k��	oܩ	t�	u��	ð�	�Ԥ���oaD�	Hl��	tp���x�	�d�	��	�����gԧ	l�	m0�	sT�	t|�
�ȩ����	�Ȩ���	mȧ	��	�p�	��	�	a�����	������l��������	et���$�	tpz��@�	t����H�	e�0i��	4�k��	l��	r�	s�v��,����T.TKd̦rܨ	t��	�Ш	Ŵ�8̮����	��(� �	Ī.��k,�����	����d]e�r�|��@����	z̰8�d�ak �	n,�	p��_`����	t���8Z�\���4�	�,�.<�	�|���H�	z��
	T�	s��
��`�	�����l�	�زDtk��	l|�
���	d<�
	��e������	�t�
���
	��	v���ĩ	�X���Щ	�4�tfn��	t��t��.$�	�ط	�	lL�zX�
��
���	������e����0�	t�<�	r���H�	o$���T�	p����xe��	r��	������	d4�����	e��Q��	��	s<�����	���	l�b�	dyf�	g�}h��	k@�	l��	nĭ	p�	r��	s�	tL�	vd�	zD����l����	T.@�	eܫ	j�Kn��	sqt�	y̫	��	�0����8�	kd�	t<��T���P�	�(���X�	Ø�	�8��@�	t�	.��	h��	k��	n��	tT���|�	�8�7�7`�7h�7�������ī	��	����������	������<���	�	r(�.�	eT�	oD�	à�y��80�	nT���8�	�d�	���G�Fy�8\�	k���j��	k�~n�8	r����p�	aЬ	e��	i��	à�������&��	��	�����	���.�	eP�Ĭ	llr���(����	�	v��t}d�s$���C �	l@���1\�(�	s�t����0�	eL�md�	��	�ip�	n���T�	�x�����e���dx�	k�����	e\&T�	d�	k��	l�	n�	p�	s�	u�&����	at�	eĮ	fX�oh�sܮ	u<�	���P&��t�&t�&t'�H't`'��'C\�	ld�	rl�	t0�z�'��$�	�l@�̮	�`��H���	��'_�'_8(��(��g��	l �r��	t��	v�(�
\+�l+��	t@+����	eįn�+��nj-��/��/Ԯ	s 0%�/	�	l`0T��	j�n�0����	a4�	dH�	ex�	i��	tt�zd�	�H��d58@�	e�5�p7(tm�;	�X.��s�2��T�	��������=.�kn��	p�>�<B��,� I��l(G����	eܯ	s��	zį	�H�����Of�fe�O��Я	zhQ�L�k�l�Q.�	aD�	e��	i��	k8�u �	àR��R���	�TL���	�����	�@��Ȱ	��S�\�g�{ld�	mx�	sUB(U��\�	b�U��U��p�	zhW�0W���	kX�8������dXj��	��X�d
y8Z���	lH]4�g�lms�	t�Z��԰	e�	i,�	r �	��
��aT�	r4b��\��p?	�De�gT��	d��	j��	r��	vDg��	4�	a�	b�	e��	i�	n�	o@�	uб	�4�	�P��g�x�	a��	�Ĕ��g����	��g�h�4h�t�_`h	��	gth��ı	���	�$�	�H�	�P�	���H����	��h���	��h	�#.�ik8�	ld�	mt�	n��	r��	s��	t<i.H�	eX�	Ŕ���i��i��P�	�<���i��i��l�	k��	t�iW�i��i���	zp��ķ�l�%�j	��	b̲	lԲ	r$�,�j��h<i�z�	ňA�A���	��k<�s|lY�l���	y�l��l��l��l��,�	��m�m��m	t�n�n#�n��\�	a��	i����t�	i |�	m s��	u(�B4�����	�������	À���	m(�����	e��̳	lw��س	e�	l`�	ox�	r�	ä�����a�o4�	u�)p�)���	e�)�	r@�(�	o<�#H���@�	a\���H�	tL�T�	lĬ�A	kЬ��l�	a��	e��i��	u��	� ������-����d\���	k,�Ph�
���Ð{	Ĵ	l���д	����ܴ	�����	t8�	�	sh����	�x����l ��	bȵ	dh�	k��	l��	n�p$�	rD�	s��	tL�ux�v��	z����$�	a��b��dD�	eԻ	f�	g�	i`�k��	l\mļ	nм	o��	p�	rT�	s��th�	u�	yȷ	Ä�	�е�����	t������	aص	����t���	���
�	. �	a(�	b0�	i8�	j@�	kH�	rP�	sX�	t`�	v8��������p~���
�خ��� ��\��|������t�n|�	r�t��_�������	g|[kL�p������	a�	eL�gL�jж	��	���	��	t���Ķ	�8H�ܶ	l��4����	yػQ��	�	n�����	�����Կc�t��z�	À�_(���<�	p`�	st�	z�������X�	zp���.l�	t�����@r���|�o��.ܿ	��	c��g�	lԒp4�	r�	s��	t�	z$�����	�P�	���	�@�	�L�	���	�Ⱦ	�\������w������	o�	�l�����	lP�\��wkP�	sX�	t���� �	a��	n��	t��	up�	�̩1��|��	8�tt^v����`�	���	����L����.�Aa������	y�X������	a ��k���Gĸ	b�	r�	t��̸	i$�.�	aL�	i��	o4�	������	z����$��`�� �	vl���(�	�D�	��rj��� c.\�	gXS
WG
�X
��d�	����l�	���x�	k��	s��W�������D���	lc �eԹ	iD�k<
lL�mh�sx�v��	��XȰW�	ܹ	j�	s�����	�����8�	t�	�4�W,}
������	o�� �	t0���,�	a@���d��	g��	l��	r�	sD�	t�x���D�s����h�	�����t�	�H�����a��	i�+�����	sl��������	a�	e�	i�	oԺ	����0����������	zT��	s�#x��	m��#T����e��z<�
���za̰,�	k����8�	i��	��g��	l��	p��	rȻ	sT�t�����p�	s����x�	e������à����!c��l��	t���������#�����	a<�����rX���0�r�	y�P`�ih�o�����	d0�	gD�	k0�lT�	m`�	n��rt�	z.��a��e��0���bo`�r\.�i�����f��g,�s��8�g0	l2g��r<�v�zTX8�
��	c
����	e\
��T�������	�̏f��p�	r�	s��tt ����	a��d8�g�Jk`���$o(���	t(�	z�.pYt���i<	4�	r	��b�n��r�vؘz����� 	h�	r��	zd��t�	��	��, 	��	s ����	������	�| ���rP�s"�!н	m�!��ؽ	aH�	u,�	È��\���	hx����	i8�	j#	�	d$zf0#���	���y0��d%@�	n�%����h��k��p *\�d��	g4Jj��n��	r��	t�*t�+PP+����	a�,��,����	aX-	|�jD�t�/	@�d��g�	rd"t�z�0	<1	�r�3	��g<�	lH�	s03.��	a|�	e̿	i�	u`�	���e 6��(�	��5��0�	Ô8��\s�880�l9��T�	���	��	�@<��:	t�	m��	s��	z�=�|>���
��>8��	d��	k��rl?��|.s,"t0B	�dܿ	mHC�X6�I	�	t�J���#ńJ8��	ldN��M���	i\M�	bT�	l��sM��$�	a�	e��	��UjtR��L�	kLM_�b��`�	tHbh�	ldb��t�	a��	i�5o�	s�5Ì]	��	z�]����	��	�@��c��	pxc� ��d���	z�c�	d�5i|��������	�,�����s�	�\�	�	s����	a��	b��	c�	d0�	e� fT�	g\E	h��	kp�	l��	m��	n�	o��	p��	r(�	s\�	tH�	ud�	v|�	x��	zh�	�$��a�C	bhc �d�e�	f�	gp�h�j��	lP�m�o�C	p�r�	s(�	t@�	u�v�z��(�����Xod�j��y(���8a(��Ď.�a|������	zx�jxo8�	rď�؏�X�ܕ��H�	at�P�	t����\�	�@�	���`4���x�	o8�r��X	��	l`�����	e��	hИtܘ.��	a��	e��	o��	��R������@�R`�.H g\�����	e�	����������	��#��(�	tȪ�<�<�	ll�	zd���D�	a� e��	mx�	ä���D�.���$����	���	��j��n��zT�T���b��	l��m�����	e�	k�l$�	oH�	rT�	u��	�x�t�����+t��	��	r�����	� �P��4����	r���	n���PH.�0�	tp���<�	i0�$dp�����e��u(���`�	l��	y�����	�d��	�������	�p����p��	�H�.������	���p{.s �����	a<�d,�	f<�	g��	i��o<-t�	�4����)l��	�	l����	�`�	nd���$�	o�G	ì�����at�	e��	r��	u`�	�������X�	���	���	�����.��	i��	l�����@�	e<�H�����	�T�����	È�# o���	��	k���1pt���	�b �	c(�	h��j0�	k0$nlUo8�	r�t(���	nhs
��x
���G��	���T$ad�d�$ed�	p�t��	vl�	����d����&�,�C<���x�	�T�����	�tg�������	i������	p(�r�����&a�	g4�h�ck'o��	�����hW��	��I	�0md/����	z8(	��	sX�_��.��nP����	ip�	zP�	�d~.��	<�	v���D�	�dp�\�	k�.d�	a��	e��	i��	oDu��	�(�	�0�<���	g��	m��	s�I
����l|������	e���rl����	� �	����p��sPX
�tXk�	l�	nDY
xZ
� _|��0����4�	dx�	g�'j|�l��	s,K	t���<�	a��	e��	i��	o��	�t�z
tp	d����	l���	l��
_L		��	p��	r����	�LL	��	_�
�
��	m��n��rD�P��	b�	lНs|�
����	d �	t��
	$��(�	o���0�	i <�	m��(dT�r���T�	a�L	ü#���t�	a�L	��	\	n ����	�t��8�g��	m��	o�	s��	À	8,����	������	À88��	ml�������	l�.��	o`!	��g��jL�	l`�	r��	t��	v�	z.�+8�	a�*��@�	i\7>|7��X�	a��	ot�	�@8������	�X����	�:e�pt�;��	t,>t�AW�A	��	l`A����	�0A����	���	��
	(zaP�
����	�<C����e�	iPGX�I���eD��|�UX�	 �	s<���(�	����4�	ì�@�	d��	i��j��	k��	l��	n �	pD�	rP�	st�	t�z���@���L�	a,�b��	c�
d��	e|�g��	il�	k|�	l��	oH�p��	r��	s��	u��	À�	����k�����kD����Evh����	yd(ð�������	���.�	Ì�����l�pȗ�P�0�	s,���8�	i\���̀kȳpH��|�`�	mę��h�	oгr̛	t�g��	r(�	sL�	t�����	�`�	��	�@�	�H�	�`�	������a��	o�	u��	ä����	��	l������	�<��H���	l���kģ\�d��l��vԣ���	a��s<�	zX���T���D�	a"j��mh�sX�u����WŨ�h�	s����t�	�ħ��	�Ч����	i09B@�.��	eP�����	sH���	c��d��	l �	rH�	tT�	v ��8�����	a<l�	tl���
�x����	�D��`����	a4�	eT��d�� d@�`����@�	i����À�	l��	p��	r��	s��Ŀx�	l`�����	e��	Ô���l@��������t�c���\�����	�,�.��	�|�����	z���a�g�	m�	n4�	oH�	stÀ�����i$�����d��g0t��z(�P�,�	n��|���@�	i �m��C����X�	������r`�	�8����a��	�4�H�����	�<���b��	k��	s��xt��L�����	e8���@s��z��8������	������	i��	�l�	��	n��p$�	r�n��X�����	i����\�	�@�	0�	l�,s��t�������T�	�4���P�s8�	h�	r|���t�	��������	c�����	aLi��	o-�@h�Th����	a ���	m��	n����H�n�����pptxz�
\�d�	r�ps8�	t�z�
���aH8T$�	lh��,�	a���	�jX�	s�]_	�g�Hl�	c��	p��	t���t�	aP�	e �	��)#�)����	a@�e��	o�+Fs�.�t.��	l4�n�.����	a$e�	�\�yT�e41	��	tl/����	��4	Lqs�4���	���	��Yt�Y��0�	�hY��8�	üVD�	gd�	tԘào����o��	�uC��z�q��t�	�8a�,�	��	s��	v<�����	������	�`w	��	t�a�#.�a����	e "	��(at�� 	��	g�����	e<�	��.���	nt����	�ph		�dt �	�`\	��,�	��Q4��Tl	H�	i��k��	l��	p��	s�k	��	T�	a�	e�	i(�	oh�	st�	u��	y��	�\�	�pp	��,uvx	��H��x	����	üu	����	r�y	j�	8@	����	l�|		��	l}	����	���	���	���	�l�	�Sm0�	pD�	s|�	t��	v��	��	��(�	a��	#X�	��<�	eX�	s`�	z\n	�	�T�	���	h�	t@�	��p�	e��	tܩ	_ �8���	z,{����	ed�	�@�	��	t�	����	e��		��	g��	rx�	p�	����	i�	t(�	��	d�	r��	���1t�	���}s0�	�	r\�	lFh�n��	��4�	�x�		D�	ð�	��P�	�D�	����y��	,JgH�		��s�i,`�		��	c��	l��	.<�t"v��	�tt��t����	�
P��	a��	d��	e��	i��	j��	o$�	s\�	Ü
�p
	��	k(�	l4�	n<�	rh
�t
���	��
���	�
�|
��
.p
8D�	rp�	z�
��L�	�d�	��	���	|	
��	
��x�	r�
	�

����	l��	t��	v��	Ŕ	
	��	l0�	s�
��
����	�0
	������	��
���	e��	� ��|
��	z��U�
���	t�
�	t�
��$�	eP�	��=Ŭ=	8js�=��D�	��	�
8\�	gt�	k�����	i�=���`
�hunl
����	��
	��	g��	�8
.��	� �����	��
��
����	��
����	��
	�Ql�	n�'\
8�Qz
>
�
���	t8'
��	d��	gH�i�	k�	lP�	mt�	n��	o��	p�	r��	s8�	tT�	ul�	v(Tx<4
H4
��t�	�4
��|�	ä0
����	r��Ì8
��`�.��	h��j��	n��	o��	s��	t��	vD���<
�=
�P>
�����1��D
yH�	a�C
��	lP�	rX�	u0D
��	�	a��	i��	k\uo��	r��	s��	ul�	���	�8�� E
�hE
��E
	hCl�E
��`�	����X���	�����H
����gpH
��	n��	sI
C�H
����	thI
j��e�L
������L
��8�o�L
��	z(M
)�M
�S	r�M
��M
	��	rxN
`�N
���	a4�	f��i�lh�y�V
j�	�,`
<�	cl`
��D�	a`�	p@n
lo
T�cl�o
��h�	a��	c��e��k��s��	y��	��y�hs
��8c��h��	s�t
. Qz�q
�������
���
(�k�
���e��	l@�����aP�
t�
��	l<�	t��z\�
���	a�eD�	ih�	m��	r�es��	vP�	��
�Đ
�_v,�
��C� ��P�nГ
��`�	o��8h�
��t�	�(�
��|�	�t�
n�
����	o�
��X��D�
�� e��	s��	t�	z��	è�
ft�e�lf�liP�
����	z�%
�Wb�
����	a�?
.�
��	u,�
.�	a��f0�	l\W
yh�
��0fr��tL�	y��
���
�i`�
p{.�
��`�	i�
	
<{a��	c��	d,�	fD�	kd�	m��	nyp0�	r��	s<�	t@2v�T�`�
���0e�h��
.4xn$�
����	a�	e �	i��	�(�
	�XkH�
����	�T�
���
�	s��
t��
�	s��
��$�l<�	rL�
�
��(�l(�
���
P�	c��
��X�	a��	e��	i��	u��	�t�
(�m��
�X�
��	k0f	��b	��	t��
��
����	���	�D�
��	b(�
����	i��	ot�	�l�
��	n��
1(�
�	c��dD�	f��iL�	lT�	rs\�	th�
���	a�it�	� �1��
��
1D�
1��
	`�d��	s��
��d�	����H�
�|�	0�����	k��
��	k��
����	a��	e��	lttp�r�s��	t��	zd�
�g��
�
���a��
�	e��
��\e�\�\�
��	n��
���
���	a��
$�	l�
��0�	aP�	ih�	r�l��������\�	�\			��	kL�	lX�m��	nYr��	s��	t�	vl�	z����a��	e�Xo`u��	ü������	�<�	�D�	�ܧl��	t��.��	i@��T�,�	��	j�	k �	l(�	r_0,X��T.؝i�+t��T�����eh�	�.��1x��`�	��/�*��t�	�)����	z|�	��
�?����s��	z�@�pB#�A����	e��	i��l�Yt��	�C�8B����	���	��C#�E�8F��T.�b �	e�Mr@�	ÜF�[.)��H	,�	nxF��4�	��NW�N	L�	n�I��T�	�(I��`�	�,P	��	a�b��	c�k	e��	f�\	kh�p��	r��	s,�	t|u��	ØP��x�	�|�	��Q.�P��	lt�n�R�����\�	�h�� T#,T����	a��h,�	s�7yD7��	r�T���	��{��T.�	�P�	�UyU�<�	s U��D�	��Y�l��e����h�	�p���p�	�P�	|�	lh�����	��[����	øg��T.�	aD�b,�c �	e<g��h��i�	ktl��n̦rYs0{t@�	vx�zD�	�xh|�g��	n�	s�h|�{�|i�	i0k	d�hX�	n`�	tp�	v�h��,�	���x�	��,Wt1��D..x���kW�o8��l��	nX�����4p��	l\p����	a��	e�	o�	p�s �	z��	äq�Xq��	l|s|s,hs	��	rq����	�t��s�	k�t��,�e@w.��t�x��خrP�	tly�xy<�	n0y��D�	i�P��\�	l���d�	ap�	d�~��|�	a��	e��	l(�	o�3r@�sl�	u�.��	z����	s�m@�����a�e��	o��	�4���������	���#���`�x����	a̶���	t���	n@�	r`������8�	a,��H���L�	a��	e��	i��	���T�	th<e����x�	�t=x����e�v����	������	����	x������	e|�	����	lL��	f�	n������	ePQ#�����	a`���	dܽ��d��	k��	lx�r��s��u��	v0�zh����	ad�	c�d��	eX�fl�	i��	k�	o(�	p�rX�	s��	t�	uH�	z�	�0���|�c�����`k<�2�	��	r �����	������je��	i��	o��	Ô������	l�������	l��	x�g��l8�	rD�	t����	��	���	�����	� �	�����\�a�����Ha�cvL�Pp�	e��	����T�	h4���E.��	i(�4
`�������	�X�������	������m��	n Jo4s�I	v��	�0���	l��	n�Js��	8����Je����%e8�	Ü�	��	g8k،l�p`�	v0�_<�	$�	j8���,�	�H�	�0��D�k�D.����T�	e<�\fd��	m��	n��	r�s��	z���gp@�����f8gg�������	������	ð�.<Vo�	�Mr��v
����4
����l��	�T
��b�Wlxip��s�tt
	4�s�
��$�et�lD�	o�
�h
<�	r� 
t
��P�	tl�	y��	z� 
,#
�$"
�t�	r!
P|�	e�lipPo��	Ì!
���l��%
����	a��e��	i�Go��	�h
U�'
	��	c�'
����	��,
�,
��	m�3
.��aX2
��	rQt(6
	�4
	�	rt;
�\�dl�	kx�	l��	n��	r;
.,�	a�	i4�	oLYp@�	u<Dz��	��<
�����P=
���k>
�?
�?
����	�?
����	�ĉyp���	.\@
����	a�?
���	b��	r0@
����	�L�	��B
����LB
��pSu��	àU
��R
��	t�sz�Q	BLZ
�� �	mPX
�(�	m�b
��ut�d
���z�g
��̠l��	r�f
X�	b�	c�	d(�	gX[id[j`�	k��	l��	n�	r4�	sD�	td�	ux�zh\�Pf
��h�	a�b@�d��	e�af{g��	h��	iԂk��	o<
p�	rD�	s�et��	u�	y��	ð�	Ŕg
_�l	��x���g
$�àj
��.�j
���	a<�	gD�	yLk
_l
.l�uT�	�Xl
��0��l
����c|�	t o
��|���n
��p�	�@q
��p
	��	tq
����	��o
����	e��f��	�Xq
8hq
��	lI��v
����	a�t
����	t�x
��.�ny
����	apwc�Pk �	�@y
�`y
���	�H{
��z
��,�	p,�s�{
��<�yĽBн��P�	a�}
X�	r~
	8�b��	c��	r<�	sl�	t��	zD~
��p�	���	���	���	�L�	�4�	�p�	���P��	i��	oh
����	s���P���
Lmd�	mL�
����	aL�nu�	�Ć
���
	t^v��
���	�@�
���
��(�	�p�
��`�	v0�	�X�
�d�
��L�	�p�
��T�	��
����i̧lԧrܧs�v@�t��
.��	i��
����	s�
���
����	e�
	��	b��	d�	g(�	l��	m�	n8�	r��	s��	vl�
����	i�]�`�
���
9�
���	���
���	��
�8�
�� �	a`�b��	e��	f8jdkpl�m��n�o 
r��	t��	v��z��	ż�
�ȗ
��l�	�|�
��t�	��
��	m��
	������ܙ
	��	��
����	���
����o�
����ì�
����b0_e��lp�uxR�
����	r��
����	dqt��
�<�
	�	lX�
�� �	�ğ
��h�	e�i��	v��z,�	�|�8�
��T�	e�
\�	l�����t�	l�
��|�	e�
����h8�
����	t��	z,�
.��kd���
����	a��	e��t��	z ���
���o.��L�
		��	g8�	kh�	l��	n��	p�	r�s\�	tx�	v��
�#kL�	s�
��(�	eMs\�	�|�8�
���
��T�	�@�
��0�eL�m�'t��	ì�	�,W�
	��	s��
����	�D�
� �
����	��
o�
����	e��
��	kȯ
����	eȰ
8��
����	���
����	��
��ܭi0�	m<�	tt�zH@���
���	��
��$�	�0�
��x�\�
�ȵ
H�	r��
��P�	e �
��ntĶ
��h�	e<�h��	kD�t��	�\�
	L�b�n�srth�
����	�|�
$�
��
��	nL�
����	o��
|�n,�	tT�

��	a4�	d@�	gT�	k��	l��	m��	n,�	p8�	rH�	st�	v��	z�	��
.l�
���no̻
��p�e\�
�<�
��L�	l(���D..l�	`�	tF	��l�	���
��4�ex�	�ļ
����	lԽ
���p��
��$�	oо
����	f��	g��	k�	t�
��T.4�a��
��D;.��8������	�������	��� l
���	�@�
��rL�
�� �	ax�
����o����
��ܰk8Bm��tD�
	P�sP�
��\�	�,�
��h�	���
���
����	o��
	`Bg��j��	r��	v̂z��
���
����	���
����	�4�
��T�b��
$f�	k$�	l<�	mH�	pT�	r�	s�2t�
����a4$l<�
���a���|�
���	dh�y��
�
��4�	e\�
��ec�
���a��	i��	m�	r��	�
�4�
	p�	k��
��x�	�x�
�`�
��	n�W���
����	�Г
����	�p�
���	o(�
.��l�	t��
���	zp�
X�
	�p$�	r�Btt�
���
���	�d�
��8yi�	�`8��
��4�	ll�	�
	<�	kx�	l�n��	r�
1��
��d�	�h�
��d��6�
����	d�
	(za�	r�	z��	�P�
����	�p�
��4w��
���db�l�%n��
_��
��	z��
���	s��
�	n�
����	a(�y4�	���
mpqk�
	̷fp�
��(�	������
����h��k��p��t`�	z�
)��	v|�	�?
�ܸm@�
��p�	�f
	��
�dX�g4Jj�	m�	n�	r(�	s�t�%����	r8�
����	o�
.h�ah�d�	i�
���
��
���	�L�
����	�\�
t��
.�	a�
���	z0�
	��g��j\�	r�sl�
\�lh�	nx�
��L�	a�Z���
	�g��	l0�tX�v��
���
����	��
��<�t��	����	��	l�	n�.��	a�	e<�	i��	u��	À�	���H8#l`���	�(�	�d�	����	�	l0B$8 �	lx�D	4�	kP�	n\�	v����t��L��.��8l�	r���t�	�,�	��	t	�	c��g��j��	k�	m�	n4�	rX�	s�	t,	�
���	c�P����	a�Du����	l�����'����d��i`Lo��t�+�
m,��(�	a@�4/��l�+s�+vp/��D�	a4e	e��	i��o��	p��	s�	z��	ð0��@���2�P2��	kl4��4o�4��	z�4P��i�6��b�	v�6.�	a�	iH�oع�pU�09���r�:��l��t8�z;���	a@�	e4�	�h<	��l�<��(�	�t=#�C	h�	j��	k��	p�	r�	s�	t�N��d(s��,QmQ�|�	zQ	��	s�P����	��O����	�4Z�Y����	r�Z��T.�b��h��n̦r�Pt�aj�a���	�\a.�	��]����	z<]y�b��	�	bH�	hP�	jt/k"nX�	r�/s�/t�/vL�
y��
y�ey�h	��	b��	d�	f�	g��	l�	p��r�	s�	t܂vl�	z�i����lj�(j����	��i���	v��	Øjl�k���	r,l���g�	r��	yHlTl.��.0�	b8�	c@�	e��	hX0i��	k`0l��	n��	r��	�\:����l	 c.\�	id�	kl�	st�	t�	�D;W�=W`>WmW��	s�l��|�	�LmB���D��m��H��o����d�x�x���	r�zdr8z���	t�|��|�	m�|����	e�i4�	�X�	�T}�h}	 �	st}��(�	�Ԃ��}	�X.� kd�	s�}��D�	�����~��H�l����	P�z��#������	ud���	s������	a0�	e��	i�	o�	s��	Ü�.�i	ah��t����	yL�	�	g��r�	t�����	�T�	�̪_,[#����	ad����	t��$�	nй����<�	at�	�h�	D�	d8�,H�	`�	rX���h�	�\�����d����	n�!_|!	��	tq	����	������	����	l����	a��	n�s@�����kD�����	rp���
a$�.p
a\:bd	
c�	
d�	
eBf Bg�
i�
j
k�
m��n�
o�
p�
s<
u 
v�
z�
à
Ő�	�
b�
c
dd
f�
g�
j�
k�
l�
m�
n
o<
p|
r�
s
tD
ud
v̠B�����
l�
r�B|����	�
l,����
�8����
�ԣ'X�
gp���
a@
�\d%��	,
s����4
�P
��HRzЉ
B����\
fܿ���p
r���
x
a�
c�
e
i
j
m$
o8
tL
u�
��%��	�
h�
r�
v�����
�X
���%��%�yH���n����PyTyt���
s)�����0
a���D
k������	`
n����h
�|����
k��tt
ÀvB����d=.�
a�
c
e<
iH
t(
�l��P���
a���
l�
r��(����
a��yX��h�
lt�,��	
k0���
���(��4
zd���|5.�
b�
i�
j�
o�5ut
�e����l
��2'���؆'��������
g�
k
n(
p�����
aX�b`�m�
tt
�ĉ�<����
.�
j
k
rD�`�� �������
y������bD
h��o��pL
sȨ��,��C��"��T
z�	\
s4���h
��
���l�	�
p�p����
e�
�0�8����
�L��X��
lp����
a�����
l����
a��g
t(IBh�
yXR
t[��(
d����0
a�7pd
�T����	P
t���X
�D����������
m�
zp
�x!����
at�B�����
p�
s�
z�������
zp��8�.�
t������
������
�`���
k0
r�8y���P9����$
ð�<�<
kȊЊP
t܊��X
a�8
H9b�
dT9g
l�
m�
p�
r�
s�
t
�@���p
��
��
��
�\
� 
�\
�����������
�D����
n�
t�����%H��
k�����
�������
dD
lX
o��2����0
�X���8
�(�P
mP!o\!	d
k���l
������
nx
��k	��ĺ�,�y8�t�d�
i��kl�m
s��t`����
a4
c�9e<
hD
jL
kT
pd
tp
uDNv$
�d���%		��bl^n8�tt^v����
�x
�$���:�M'Q����D.	���\
.�g,�������
�t����
z�
��o����
k��.�
a�
�qy`���
r����
�����:a�f�h<:kD�m�HnL	
rL�sT�tL:v$	
�����	
�<	
�D	
�T	
� �X�h�'p����\	
v��.h��p	
e$��|	
r�	�	
d,

fP

gt

k�

l�
m
n 
pH
r�
s8
tP
v=x�
zT�%��	�	
n@���	
�X��

z�	
������

� �.

��B��$

f��'P.8

l\

s4��@

y��'�0;.�

i���d

e�

���6����

��	�

����

��
B(���

b
c
e<
fL
hl
jx
k�
l�
m�;n�
r
s0
t�
v\
��
�h�B��
f0
mp!%���(
z*Bxp���D
a������X
�̩��`
��	�-�-���
�8-���
�|�p0���
a@	�
z�:��
�X���
���6�����
��d�B�������
�x��
�8���
��;����@
o 
�4
!�:!	�9	H
l���P
�t�d��h
z0p
s�
v���|
e�%X�����e,<l�
r$���
b�
e�
i�
lC�����
l��T�
t$	B�	y�	��
c�e�g�
�z�z��(
�@���0
��<
dd
e�
k�
n�
��=j|
k<n�(��t
l�B�$����
y`�%
	�
m4
���
�@��m�
r�
t�
���
e
t
z�=ô�B������l���
j�.�k`h$
k���,
i��h
z���D
e�
ox
�L������x�x��p
���4�y����
r�
�	�
s<����
�H8	�
b
h8
jX
l�
n�?p�
r�
t
v��'8���
r���n?s��
e@?s�_1\$
k4��,
eh'PD
t0��L
eh
m������?e�
n�
�p
k����
e@zL���
��f���).D���
c
e 
it
t�
v�
z�
�@
��,���
.��
m
s4�|l
n�	lK.T�iL
t���,
��f6	T
k��\
�(��h
��DC����
��B	@���
.�2����
�����
� �
l����
e�
k
lP)X���
e�'d��|5.lAbL
e�
f�Ai�
k�
m�An�Ar�
s�AtBv�
��
��tA.�Ai�Akt
m�An�As�At|
z���	!	f	�
b�
r�
t�v8���
��A�T�
fPf`'$j'�u
'p'����
�(	|�bH
d�
f8
gL
h�7ll
m�
n��p�
r0
sH
v�
zl
�|�gX
j���8
e�B�МL�شb����`
���	
xC.�
b�
i
k
n
r 
s(
t0
v�
�d���x
�t����
���
j���ĨĄ���
�بL�V����
�����
��������� ������Ba<�L��D
a\
l\B�����d
a�
bT�p<Cð	B���
s��	�
a�
cTCdpCf�
g�
j�
s��t��v��B0��h".�Bh�Bx���i
o�Ct�C���
d�P!�P��
o��$
kDm�p�t����al
ì�	X
s���`
�(D�ԧB���|
n�
t� B�8�Dg�
j�
l<�n�Dv�
z��B���M	Bl�v\ 	�
bh ���
�@ ���
ü ��D�.`
ax
e�
k�l�
n�Dr�Qv�>X<���(
aH�	0
rT���<
�`���H
è T
r�����. !l
t�!8�!8�"��h".�
e�
td3	���.#�
t b	8$	�b�d��f
kP
l
m��px
r�
s�t�
v�
x�I
!hI
��
e�$��
k0�l,
o\�tD%Es@�zO
X%<
jh%��D
ax
c�
d�
l�
tPEvH�%��p
c S
!&���
a�
��&���
��
�PT	\Y
�����X
���
����h
	�
r
st
���
�<&���
�$���&��d=.@
ah
op
tĉblb
��$
.L
k\
o�&,
tP�^
H�kt�T
md'��n
�$)���c��m�
s�Nvt)	�
!T�
���
o�)���
k�
t�
z�)!�)���
o�).�Dl�Dt@e�(T��)82n,
rP
sx���*	
cl^n(+��
��*��hFi 
�+t�+��<
��+��D
�,8�Fbx
c��l�
nh�v`XB�-88-���
t�.8�
r�.���
��
�/��.���
s�/��4�lHHrP�s`1��D�.�c
e��hk�Hl�p
t���0�
k\2.�.(
a0
�%
�D2��0��84	`
dh
j�
k�
l�
n�
r�
t���4�,5@5��p
��4���
rx
�@�����
�����
�0��5��LIi�
oL���5��5���
��5���
�X6�
a
�<6P(�	p6��
��68D
jp
l�
r�
s�
t'�6��<
dJs`
��6�6��X
��I��7��
ŐU�<8��|
�H8���O.Jb�
hPJiXJk�
n�
o`Jr�8'�8'�\%91	�8���
z$9',9��
�
b�h<
i�
jD
k�lL
m�HnT
p(�r0�t8�v0
�l�x���(
�l9u�'��'�9'�98�Jd�
g�
l�Jnh�r�
t�
z�9��
'�9���
d�neP:B�:%�:���
e�:8�
rH
zD;��
�O.�
bPh
iD
j��l0Pn`Jr,
t
�'X;l;��
�$
���%�;��'p�����4
�0<��<
È
�;	
��.�
b�
e�
h�
k�
n�
r�
t�
v�
�$<��X
��;f<<�D<j�
nP<�X<���
�d<fl<ft<f|<f�<f�<f,@�@	�
k�=���
�L
�T
�t
��<��@
i
�h
�$A��0Bk�@4
s�l��A	@�s�������`
�PI��K#�K��|
e�B	�
khB���
�B���
�8D.4
�HC
�
cP
dp
g�
j�
k�
l�
n
p
r�u�B��
�
a�d 
e4�g�"
i`$
k�$
o�p�%
sd&
tt&
u,'
zx
�`D�lD��,
�hE�� ��`
�E��|0o@
äE��EP�E��h
aԼg�Gn�G���
aG���
tpH��X�c�
k�
o�
t�J>�J�K_�K��(�d�k|N_HN���
g4�y P�LO���
plP��P��
aP�c@
i4
��P��P��,
��z�$W�@V��H
y�U	P
g�l�
r�
s�
t�U��\
��"
�D$
��k	�4%
�x��'
��[����a�n�
u���_X`�_���
e��s�
�@`��_���
��`o
�`���
a�c8�g, 
l�!
m�!
n�!
rT"
sx"
z\i_�h��$ 
b� 
e��f� 
h� 
i� 
k� 
l��m� 
n� 
o@qs<!
tt!
v� !
��ig
j��p 
e�ix 
m�j��a0k�sHk_hk8tk��� 
��k��� 
e� 
Ðk��k� 
n�k���y�k��1���a��k	�9l,!
t4!
z� 
�l��!
��6QPl�xl��`�aT!
�p98hl��L!
�����l`!
r�l��h!
e(so8J�m��Db��e�!
i`�l�!
��nQDn	�!
sdm���!
�8o��Syp�!
r,p���!
e@s��"
t"
v\��|x���a y���paL"
i8"
���.D"
eHy	 "
tTy��,"
���n$�#�y���eul"
z`�l{.d"
k|�t��	��g�"
k��l�"
r�"
tЃ�l�����"
e@���T.�d�"
e��k��m��t<vt�z��ĉ�"
m����(�v��	 	a0#
gȎlH#
m�#
n��p�#
r�#
s�#
t��`����(#
a����$���@#
ap#
id#
�@��d���\#
�ȕ#8�eH���x#
��#
�����#
o�#
��@�e@���<������1m$ct����	�#
r�����#
�0$
���$
e�#
��T$
lX�n�t�
�#.4�
��$
e	$$
v�!_Ԝ	<$
bl2gX$
nL�	0����lp$
r��,�_ܨ��x$
lt��$
k�$
l�$
n %
r�s(��4��$
nd����$
i�$
ü[
�ة���$
���@����$
a%
�0������$
���Ȯ%
n����%
i��_г	,%
nP%
r�sh%
vp%
z4�.\%
��������,�_$����jԻ.x%
a�%
e&
i &
o0�p8&
t��z�%
�̾����%
r�%
t8�v����%
�0&
����,�9����%
g�%
r������uk&
s����
���&
d��r���4�4X���Tma��	@&
r����L&
�����(�rX&
ô�`�j�&
l�&
m�&
r�&
s�&
t�T����&
ap�������&
�����&
� �P4����&
ad�7n�����&
a��s`���_��	'
d��gH�sl"v��z<�4����$'
s��ĉ.�'
a8)
b�,
c�0
d�4
e�6
f�8
gd?
hdA
i�B
j0D
k�N
ll`
m�o
n��
o�
p\�
rD�
sh�
t��
u�
v��
w��
x�
yT�
z�(
�8&�̉�'
c�'
f�'
k(
m<(
nH(
s�������D������'
r�����i(
v��X,t$��(
i�q	�3��(
i�1$(
l02��0(
a���$�id(
zp���0� �.X(
�@��(
g�(
r)
z����p(
�X5
�H%�Ȅ
�(&��f��
�@���Ďa8C�bnpC���(
a�(
i�(
o�(
��NXcl\H������R��l<�P�����(
s�jX�)
b�dp)
l@�p�)
rd�u����)
a*
b4*
e�*
i`+
j	kt+
o�pp,
s�,
u�)
��,
�L�j�)
t`����)
akPH����)
a�����s�	�)
s�)
tH����)
��*
�@+
�,
�\,
��,
��,
����mخ���)
gl����)
e *
�̱�l���*
�H�t��,*
gX*
l��mx*
r�*
z�����P*
e̿�ܿ��d*
����l*
� �t,�	��g�*
r����t�z�#�����*
i�*
��*
k+
l(+
n$�sĸe�����*
����h��*
n+
p|����*
i���Th"."l4���+
a��g �	��	8+
v�_�^L+
k�P��T+
e����l+
a�+
l�+
r�+
t�X
_d����+
lH��x����+
ax�i�+
o��
t���+
s8�eH����+
������+
i�+
�����+
n8���T�X�	,
k(,
nH,
r(�
�����������4,
�����<,
���	��	T,
t��8,���h,
t;t4�|,
t�	 �s��	��g�,
r�tH�	��	�r����,
��������,
������,
���,
k����	�,
a8-
el-
h�-
i$.
k��p(/
s�z(-
�,��4��L-
�X	8�r���	D-
kĉ}�X-
.�.`-
i�-
o|v9��|-
k�-
t�z9��T�-
a�-
e�-
k�-
p.
s�-
è j�!�!���-
r\$T����-
����'4'���-
zĩ+.
fP*��.
e�.
i�.
oT.
�p+	l`.
nt.
r|.
t�*��<.
�<�.l.
e��p�+U��U�+��.�.
a�.
g�.
n�.
t�.
v���h�$�x��(,�,�.
p�.
s�,����n�.
o/
r/
s��
��
U��U�5�(4�/
p�3.	/
a�/
e�/
i0
o��s8t(0
u<0
zh/
�D7�,�l�/
s�zh7��T/
��/
�0
�40
�|8.�/
z���	09��>�=��/
rԦ���aL��/
r$@���/
��?��/
n�/
z�/
��@���g�	�����/
mtA��undD�|B�0
p�G�d�j�H��I.0-a8�eX0
�J8J��P0
������d0
d�O	l0
ah 	b�0
d1
k1
l�p1
sD1
tp1
uLO��
x0
a2
e�2
i��k�3
o��p4
r��st4
u�4
v�4
z�1
� �_�P���0
j�0
rQ�Q��ؑ	k�R���uj�Z(1
s81
zh�_[��Z.01
tt[�@rt_	�_��P1
�\_��X1
�(_d1
t�`H`	|1
b�1
l�1
r�1
s2
z|`���1
�h2
��3
��3
��4
�L$�`a���k�8a���1
l�c�lb���1
u\d��(k��s�z��U�j@i2
d(2
lT2
m`2
v|n.<2
l|p��p��42
a`t��n�s��H2
i,{�T|	H�g�2
n0�pl�r�2
t�2
v�2
z�������2
ĕ���	eD���h�e�2
�T�1t����2
��ny��������2
ex��2
d3
g03
kD3
l,�np�rx3
s �.�a`�n@���3
e���$3
r���l���<3
eT3
i��/dh3
k�+st����	H���p3
p<����	�3
zD��h����3
o��3
b�3
k��p�3
r�����]td���@�a�3
od���s����	�3
l4
r`��`��mp���4
aH4
��.����(4
a��	04
m����<4
�����T4
l̶��\4
a��h4
l�4
r,���X���	�4
n�v�	�4
b����4
������4
�L�.��e�No��5
i5
l5
m0�n<5
tH5
uD�#���4
r�����eD?�� �u��P����(5
e���05
h8�`�r�1v��|5
f�5
k�5
n 6
s46
v0��@���t5
a�5
o�5
�h��� U����P�������5
e�5
i����Tl�lt�n|�r����5
a6
i�5
�HC��rh���5
�l��6
s�Q�(G��6
z�k�Dg��,6
i���D�@6
d�6
k`���H6
a�6
e�6
i7
o�6
�X6
l<7
tw��t6
iL7
l�7
oD8
r8
����t�����6
��6
�����6
l,��8�	�6
rL�T�6
gT�s�����1a7
�ؾ����7
�T��s����(7
lȜ��07
a�e������aD�T�7
l�7
n����\7
a�8n�7
�L�l7
n�7
r�tL��t�t�����7
��7
��y��	��r�We|����7
�����7
�����7
mp��P����7
e,8
��	�7
rD���8
�īC����$8
� ���|����88
�x�����r �
P8
b��g�8
k9
l��	n09
pL9
rd9
s�9
t�9
z����\8
a��bX;
dp;
e|�f4<
g@<
iĮk�<
l�<
n=
o��p�=
rP>
s��tt>
u�>
y�9
Ø=
ŀ��	r�����8
a�8
�,������������aL�j�l��t(9
u@�����tm���89
n����@9
a��c���(���\9
z0�.��p9
g����x9
a�9
e�9
i�9
y���.8��9
n�~��ܿ	��g:
lԒp$:
r�:
s�:
t$����9
��;
��<
�\=
��>
��>
�l���,l��m\���k����:
a@:
eP:
j�:
o��u��	M�M��H:
at:
�([08M	`:
tHM��h:
��R����:
k�:
s�������(k�:
ð��D����:
���nD����:
a;
e"jD�k<
l"n;
o��rh�s�>tx�v;
�0�n$�X����p��`%�l% ;
d���(;
a��	4;
j|���@;
����h;
rL;
��@�	L�d�;
g�l��md�p�;
r�;
s��v��z���t�	�. e;n���T�.�;
e�����	��g �l�;
n<
p<
rt�sh�Y,����;
n��,����T. <
tl�v������e��,��X�����l�	�c�	d��fl<
g0�l|<
n��p�<
r�<
t.��a�������f��g,�sP�tt����oT�`���<
a0	�j,�n��r<�v�����<
c
���<
i���i����<
a<d=
rH=
s0�v�����g<1r0=
z_�.�l��t(��8=
z<�	T=
bt=
l|=
r�=
s|�D�p	 	$�rd���=
�������!���=
i�!�=
f�=
p�!���=
a>
��"uP#��).>
f0>
h8>
j@>
r#	�=
fH>
l0#��>
��#%(>
ahwM
���@��@���#U�%����k��pT�tP+����	a�>
u *d>
r�s �t�.PX-	|�j�/	x�g�>
l�	rd"t�zp0	�3��3	�>
c�>
p03.�>
a?
i?
o(?
ü6��C��D;.0B	�>
s�D	�s�E8���9��?
��#P�llW��4?
a�?
e�?
i\M@?
m�?
sM��T?
a|@
e�@
i�@
n�@
oXA
u�?
äW��W�?
l�?
s�W��W��W�?
s�Z�?
ph[�]	g@
l0@
n�krT@
sl@
z�]���?
��@
�,A
��_�8��^	@
d�Fn�^.@
a�^��$@
y�`t�`.<@
i`@
oD`��D@
zLt��ddb,xd��ct@
cpp_Pp���@
ghn�@
n(�	#�k	���@
iz����r�u�@
rT�
�|.�@
ip{���@
s{	�@
cA
rd}8p}��A
�|}��A
�8F��}	$A
vЁ�؁��8A
����@A
è�LA
r���A
a�A
d8B
lPB
nhB
r�B
s�sz�A
�$��eL�f�A
n�v�#����H�at��A
b�����A
�H� ��dD�n\����A
aB
e�D	oB
zB
�؛	��l����A
�`���oؽ$B
t(���,B
e��T`B
d ���DB
aD�e��t����p^	e��8�tB
lP���|B
e�Wqs�W���B
aHV	�B
r�B
s|V���B
��P��,C
eHC
i�C
t�B
ìY����s�B
z�Z�4`e@`���B
�p��`��8C
oC
��_��C
t^ C
k@ll�k@C
n�n���TC
a|	\C
r���hC
��C
�`~���C
otC
�x��9�C
k����C
�(����C
�4����C
j��C
r��
Tx����C
�D�	�C
ì��BbH
c�D
d�D
k�D
l�D
n �	p E
r4E
sXE
thE
u�E
v@����C
ad
b�
d@F
e��f��gpH
ihI
k�J
l�J
oH�p�L
r�L
s�M
t�M
u�E
ðL
���2t���|D
a�D
�,��<����D
�x
�����r�����D
a�D
v�	D����*j�D
kL�m@tؑ_h���HgE
iE
t��T�_�P,���E
a$Cc8�	i\���x�p�sHE
z��.�f��tę��l*t8�yĚ.tE
t�	�����|E
a̛	t�g�E
j�E
rF
s4F
t����E
�G
�(I
�\L
��M
�8N
��8����E
t����a�E
u��v0����E
lģ��lԣ��F
a��s,F
z
�X�_�����eH�.tF
b|F
c�F
d�Cg�)l�Cm�F
r�F
s$�vG
z��P����.�-��������F
�`����F
k�F
n�F
��Ŭ�>��E�����F
.ȴ�F
lԴ���F
e0���F
À���4��L����		�gDG
khG
l<n�G
p�G
r4H
sHH
t�v���̻��<G
e\G
�(��
4���TG
�����T.�G
e4�n�ytH����t��G
s�G
t��1`�.�G
�������G
���D������T.,bt�c�~d<g��h��i��m̦rH
t8�z������H
�lD�,H
�����TeH
�8C�|�j@H
z,�)����<k��t��`���XH
e�o��`H
d�H
g�H
n�H
r�H
s�tI
zh����a�H
À�������H
� ��$�����d0t������H
�����d�o�H
�|����k �mHpI
t4�p��I
et��L�m�	�jpr@I
v�z8�	��Ƞb��d`|j�I
l�s�I
z����HI
a�I
ehJ
i�J
l�J
o,r�J
uPJ
�(����I
e�kl��������hJ
r�I
�I
�D��I
l J
rDsح������I
��������I
��	��T���J
�,���J
�0��h~�<��,J
�	8J
p����DJ
��J
������`J
d�������tJ
�����|J
�����J
b��	�,s�J
v���Š�$d�J
r��tH������8�����o�J
�<���b K
lxK
n�K
r��sDL
v��T���K
alK
tHK
�,��8�	4K
rD���<K
�p�	L�s|���TK
�����`K
�$���H�n�K
o��	GL{
���K
s���K
k4�#P����K
a�K
iL
mL
o�K
���t�����K
����l�K
sd����
���	�Г
���K
ä�P|����L
a���� L
i��,L
r����8L
aTL
i��`@�		�,b�L
d�L
khl�L
n�L
r,�sDEv�L
z��,`���_`�.D��|�����lN
����Li�L
�L����������L
l����L
e��p(M
z,M	P=
��M
m8�M
l�2	n`.M
aDM
eXM
i�K
�,�<M
s����PM
s�
vp	dM
n���lM
����xM
Ð
\�dL�g�j�M
r�*t�
P�
���M
a�Z��h".�!b��h�!r	�M
r�M
sN
t\a_�]���M
zh��
P^b��hh	i"jt/k"n�r"t0N
u�vX	��g\N
lXn`rht�vdN
zH�L	X	�z
�E.8	bO
d@�fO
j�O
k�O
lP
m$P
n0P
p8P
slP
t$Q
u���xN
aR
c@S
d�U
e�V
fXW
ghW
i�X
l [
n,[
o4	p|\
s�\
tX^
u�^
v_
y`Q
Øjp��̣.TO
b\O
f��gdO
hlO
itO
j|O
k�O
m�O
n�O
p�O
r�O
s�O
t�O
u�O
v�O
ø�UT�U�kUd��mU�U�rUTsU��U�{U��H�����������O
����� ����O
rD"��L�gX-k�k		�#���O
n�(��(.P
a�%��P
y�)j<-���p`�t�.��.��HP
at.PP
l�n�.��\P
api�P
o�P
�P/	�Gl�P
pl/���P
��P
��P
�Q
�Q
���� 1�41	�P
k$2,2	�P
v82�l�P
r��
_����P
n�2	��l�32h3	Q
z�3P�r5���r�4	0Q
b�g�Q
h
l�Q
rT
s�Q
t�4��<Q
��U
��X
��[
�h\
��^
��^
��8#�8���Q
aHA��	4�b��h�Q
ip	k\
n�Q
o̦r8Xt�Q
u�N��a�øRL�kX�nBXTD��Ji�{vlG4j4R
l<R
n�G���Q
aXR
e�R
h�R
i�R
s��z�R
À��\�.HR
y,K�|�LHPR
klR
ltR
s<H��H��H_�H	|R
v�G���R
�d}	��{��R
�S
��H!���:oI�R
k�I��M��M��R
r�I.�R
o0�zS
ňI_�M���R
�`N��N��NS
bdS
lT
n T
p4T
t��u�N�� S
axT
e$U
iDU
olU
uTT
ÔU
�tO���.�S
b�S
e�S
h�S
k�S
l�S
n�S
o�S
r�S
t�S
u�S
�T��O���S
�T
�l
v�O#\�v�vPV��vP�d�vP�
�V�W�,P,P��T
ydP��P�l�P��(T
eHQ�Q	@T
l$Q��HT
��T
�XU
�dU
��U
�T��pQpT
g�T
l�T
m�T
r��s0�v�T
z,R�8R���T
��Q���T
l�T
v�T
�PR_�R_�R���`b|6��{��S�S	�T
sU
v�Sn�S��U
e<T�TU
p4U
sdTtU��T<U
r�v<U	PU
sPUeV$d|U
gP��dVt�V��U���U
��Vt+g�U
lH`�@x,`w	�U
d(g�U
l�U
rh~_d���h". V
d(V
e��hHV
ip�jP�lPV
n0�slV
txV
v�z8V
�`V
�P�8��@������0V
�x��X��|�8���؁��XV
����x�X�8Hz.���V
k�V
u�����V
a�V
eW
i0W
u�V
���@�_�	�V
r����V
�LW
�D��P��V
s�V
t`�����d�g�W
n W
r(W
s(��4����T<W
t��t����	DW
j4���0*l�r��`�b�W
d�W
g<l�W
mX
n$X
pPX
sԠ`����W
u��Ԑ���W
a�W
������	�W
z����W
��(��u.4����W
aX
p\ø��
ԕ#����X
uĖ`ؖ��X
a4X
e4�T@X
t��t �_����HX
k�X
z�@
y�`X
l��hX
�|�.tX
�Ԝ���o.x�	�X
vd����y���X
g�j�X
l Y
n8Y
t���	�X
atY
e0Z
i�Z
o�Z
s�Z
u�y\Y
��Z
��.@a�
j'��d��('��Y
�Y
d0Y
yآ�D���SkHY
tD3	l�	\g����PY
��Y
�|��(�T�dc�Y
g�h�Y
l�Y
m�Y
s��������Y
e�Y
y�Y
ð[��Y���Y
�Ԧ��.�Y
e�`\X����ebĨ��e�	\qkZ
rd���4|i$�����fHZ
g�j��s�Zt��Z
n�Z
s��pH���@Z
e|Z
i,�j�Z
n�Z
rpZ
���hZ
���pļ	8�8��	�����Z
kx�|�b��r�Z
s������z̭���4�������Z
b�l�Z
s��P��\���[
eh�[
k���[
i���0lH[
pX[
rl[
t�x�tP�� ���P[
eȾ	����d[
tL��T���x[
n����[
n�����[
������[
���
�[
d��	�[
e�[
f�[
nH\
rd�t�j�����n�[
o\
r\
t����X������kh�	\
ij<kl^nX\
r$�t����\
����`\
i<\
ø����,�	�l �r�s���k���7j�\
l�\
r�����\
a0]
e�]
i�]
o]
�L^
�Xv<����\
m�\
o�r
��������\
iH�_h�	�\
g$]
t�����\
�p]
��]
�,^
�� �$����\aD�(�gL]
mT]
rh]
s vx�t��.`]
e���8�
.4�	��	p�]
r�]
sl���T.̦r|�_������]
d��	m�]
s�zp����, l�	(�h^
k^
l8�m@�n����
k��
���]
���
���]
À�
X�
��^
o�
.@�	$^
r��_T�	8^
rh���@^
���Dg�Vr�^
t�������l^
�����t^
�,�	0�s��	��gL"ll"v�zP��`��^
d�^
s�����^
a����y�����^
r�	�^
bd@_
gX_
m�_
s��.�^
a@#e`
i�#u�_
������,_
y����4_
g��Td_
s����L_
e���p�88�.l_
t����t_
z���#��_
�`
����#�8�d�8�_
b�_
h�_
i�l�_
n�����?���(�.Ԧ6��_
r����_
�4�	x#m�_
���	���l@	 `
b�`
d�`
f�`
gta
j�a
k�a
l�a
m�a
nb
p b
rlb
t�b
u�z���,`
a\d
b\e
dle
e�i
f0�gj
i�k
j\l
k�l
l�l
o@n
p�n
s�n
t�n
uL-y�b
�n
�	���?a4�m�?t�`
�\	���$��	�	T.a
d(Nl	���`
a4a
e<a
iPa
y(a
�H	t�	e	�� a
�d	#�	THa
n<�t<	.��aha
��*	(���`a
��	��X�k [��	���r%t�	D�g�a
p�	���a
aT�m�t`@vl	����o��r$����`bX			���a
gL�n[8�	�a
d�	���a
a�pb
r	@	���ac8\g�	��	4b
k�	y<b
ap		Hb
r�	��Tb
��
	��P�k�y`b
è
	$�	P	���b
s�		�b
c�b
gc
nԒp4c
r�c
s�c
t�	���b
�|h
��k
��m
��1�$o
�d	.�b
i�b
o�	p�	n4	.<�aP�k�	��c
y�	�i�wk�m�s�	��c
alc
dtc
e�c
g�c
i�m(u\c
�	���|c
�xJ4	�	H		�X. �tN\	`a����t	���c
�$	����s�c
z�c
�	P�c
Èt�p���c
��	�	���c
eD�kD
m0d
o<d
rd
Ì	j`	��d
����(d
l�		��8o�	Dd
l�	��Pd
a�d
e(e
r<e
u�d
�x	��		xd
g�	���d
��d
�e
��	,*
g�d
l	���6h��tdp����d
e�		�d
n�d
rd�n�	���d
i(�
��h".X�	�d
ne
r��. e
a��pd	��0Co ���	4e
j,�j� 	He
o� 	��Pe
i�(r� 	�b�e
c�e
d��f�e
g$f
lh m�f
n�f
og
pg
r�g
s$h
t�*v�x8h
zT�`P����e
e�!	��!	���e
��!	���e
� "	.f
e�.yf
è"	��"	���e
�f
�#	��#	��)	.�aDDbtf
e�)k�f
l@ m�)n�)o�)s�f
t��u�)vdf
ìf
� *	��P)�|f
�<*	��*	�8+	�D+	���f
�X+	���f
�,	�,	���f
��,	p|,	���f
e�-	�T-	���f
e�f
g�f
y��z.		�.	�/	.,/	�\/	����d0g
eHg
nTg
o�g
�Pw��/	(g
d8�i�0	��0	��@g
y�0	P�0	�$;.�g
b�g
h�g
n�g
r�g
sD;t�g
v1	��\g
����l���]�������1	=1	���g
� 1	���g
s�g
�@u8\��X1	.h
e� sh
th
Ì1	�2	�2	��h
��2		d3	��@eiįn,!�D4	.Hh
e\h
r�4	�6	��	d$6	��Ph
eL�nX���hh
e�6		ph
b�h
d�Fg�h
h�h
j�h
ki
l`�p0i
r�i
s�i
t�i
v���D����h
e�7	Xh7	���h
e�Fs<8	p8	���h
e�8	Xl8	���h
e�9	��x!e$i
��Ŵ9	��9	��i
�X;	��T.,b�!cdi
eli
i��m̦rxi
t�$v|.è�d�X�;	.8<	���.e F��eŤ<	,�>	��>	�i
lH>	���i
e��t�?	��@be�i
�@	� @	���i
�\A	��@	���i
l`D	��D	���i
eD,i`"��B	�i
d�.g8Ik�"l\j
m�j
n�j
o|Ip�j
rk
sXk
t�k
z4F	�@F	��Dj
��j
�F	��lj
eLj
��Txj
l<����t\F	��.tF	���j
a�"dhf�"g�v�j
��F		h�bt^v�F	���j
��H	��lUo�H	�j
n�H	��k
i�-o #td-�XI	`�I	��0Bk8Bmp�t4k
z�J	��J	�� k
��J	.(k
�`�J	@k
lpk
nK	��Hk
a�k
e�k
i�k
� K	�4_4K		xk
sDK	���k
����K	��K	�k
sM	��<Vo��zJ�M		��j�#r�v�k
z�M	�\N	C�M	���k
��M	���k
�l
�hN	�tN	��l
��R��`�.H� l
k���,l
ơ	8l
rHO	��Dl
��N	���.l$�rPl
À�_��	pl
t<5��xl
�5���l
�	�l
b�O	���l
�pO	���l
��3
��3
�l
r�0
���l
o�Q	�l
dm
k(m
l<m
nHm
phm
r�m
s��x�R	��sS	��m
a_
T	�� m
y�U	_�T	��4m
t�U	��p�e�
�V	Tm
s V	��\m
a�m
e�m
i�m
j�V	#W	��V	�m
z\�
�d�
���m
�PW	���m
�W	��%t�m
z4X	.d�f��t�Z		$k�ld%n00rP0sn
tt%v`0z�[	�8\		h0r�%s`\	��n
�To
�(^	�4^	,n
ld]	��4n
e�0lhn
o�n
u��^	����s�^	\n
rs��T���r��tn
�|`	�n
t�`	���0kHGl�0p1t�
.�n
s b	���n
e1rȥ
.8�
���n
s�b	&d<&gD&jo
no
r��	t nz$d	.o
o<d	�Hd	th		�&d�1g�1lĢrH�s�1tl"v̢zi		ph		Lo
rdo
z�i		Tl	
�&b�	cp
d�'e,p
glp
j|p
k�p
lq
n@q
p�q
r�q
s�q
t�k	��lo
a�*bhs
cu
d�u
e4x
g�x
hy
iLz
k�z
n�z
oD5pp|
s}
t�
u��
yh�
z�q
�`|
�m	<�tm	���o
ap
r$p
s|m	�	�m	��pg�NiXp
yLp
�(n	��m	��Dp
��n	.p�	hTs	o	��dp
n�Qt$o	���c�0k��n%t�q	(tq	�p
nȞspp	���p
e�p
i�p
jq
o����q	���p
t�q	�p
n�r	���o.�p
u8s	n�ps	�p
k��s<A�@u	��q
i0q
o`�t`u	��w	C�u	��8q
pTq
rL�sx	<o2Lo	\q
c`�dxo��dq
�hx	��tq
��y	��4p�z		�Gl�z	���q
��q
��z	���q
�T]C�q
z`�2�|		�)b r
c5g8r
l�5pXr
r�r
ss
t}	���q
�w
�z
��{
�0|
��
���
�L}	y�	���]o��v@	��(r
lD�	*d�k�r
tl�	��	Dr
a��b�qk,gnd
o8Xt�r
urv�r
��	1|�	XĂ	��	���r
�H�	jX�	.�r
a�r
o܂	���r
z�	���	r��	��r
k��j\�		�r
l0s
sl�	���r
�8s
�0�	���
c��e,
fDs
hD
mPs
r�r
Ð�	��y	�r��	���DeЄ	����e`�	+l��	��\s
a�s
et
ht
i�t
s�s
ì�	��6��s
�\t
����D-�p�	�+g�s
s�s
z�	.�tL�		�wk�s
r���T.�s
��8����s
�؉	t��	.�s
e` �x�	t
d4t
kHt
l�!_��	��,t
r�!	�!��@t
l�	��		Tt
s�	��	�ht
g�	��pt
��t
�p�	.�t
e�t
i|t
�:���	��t
g,7m@=BЍ	��t
g�t
r��	���O. ���	���t
k�	��t
ku
m�@��	.�	��u
e@u
ihu
o�u
�$�����,u
o�	4u
kH�0s�	��Lu
o�u
s��	Xu
k��s�z��� ��|u
��L
���u
��_x�		�u
l��	���u
��	�l�	�u
b�u
dv
gv
h$v
l�v
m�v
n�v
r�v
s,�v ;x�	���	��pIe��	gv
y؜	�؝	���	.
`v
ahv
e��hx�j��m�v
n�v
o 
r4s<t8�vxv
Üv
�l�	��	���	\̟	��pv
��	_�	��	�D�	���v
�̡	�Ȣ	.�v
e�R\l�	��d�j�F
k�v
s�	LX�	.�v
e�v
sw
t��	�\n.Ħ		��		
l�b<w
d`.gPw
hdw
k�.l|w
n�3p�w
r�w
v�	.�qe�}X�}��Hw
eЬ	���	��\w
e|.s��	���	��tw
e(���3���w
e�	��
�w
bt�c�.d�;i�l��m��pLs�Tt<vL�	��T./a�w
b�w
ex
i�Mkx
n�r�	�	X��	��	��		�>l�/sȷ	��x
��@�\�	��Px
epx
i|x
l�x
y$x
�D�	��c0d?g`hlD�	��	hx
k��	��`k�4���		�x
d�	.�x
a�x
ŜG��G���x
��_T�x
m<_���x
o��		�x
r��	���x
�8�	���x
��_H(�	�0d8y
gLy
j\y
kl�lpy
n�1p�y
r�y
s�y
tz
zT�	.xAa�Ve�A�t��	P��	��Ty
e�*_��	��	hy
b��d�Af�g�j��n�y
r��s��t��	_��	��Bo #t�A�(�	��0Bk8Bm�y
t�����Pe<�	T\�	���y
a��	���	���y
���	��X2o<�z�y
��		l2g��n\Wv�2z��		�*ldz
r��	��,z
�xz
���	��<z
��	 l
k��	��Xz
o��m`�		pz
tp�	��|Ee�z
yD�	�
L�	8�z
r\�	���z
��4��z
���	P�Ee�Eo�z
�\
�0�	�b4{
gL{
k|{
l,Yp�{
s�{
v��x�=
���.�R	n>
	{
f>
��{
��=
��{
ä�	��({
r�	T`{
l�	��@{
ap{
o�t��	���\uh{
s�	���Ed�{
l�	�	.$4l�{
t��	���{
z�
����rl�	�0�	��p@ix�		�{
r�{
s��	���	�{
k��	���{
a|
i$|
z��
#�	
�	�|
n��	|
et�		p�b�l�Yn�r�sX|
t45v<5z��	���	����J�D�	��	`�.�|
a�kh1l�m�|
p�kr�|
t�|
z��	����a�	�|
v$��(�	���|
e�|
o��rD�	���	��ЃoH�	)D�f�|
t�`
	h�	�[g�lj<}
lP}
rd}
t46z��	���|
a�}
e�~
i@
o�
y�}
è�	���[k��	��	��H}
aT}
_D�	��\}
rp�		�\l�}
r��	��p}
��~
�(
�p
��
��
���	��rv��	���	�}
b�6d�}
g�}
lP~
m\~
s�	���
����	�(�	��	~
d�6h��	l~
m$~
n@~
tH~
v�}
�4~
�D	�	�����	���	��,~
���		��		��	��0�b��	.h~
e��
�T�	1\�	��p~
�8�	��x~
��		�~
k�~
lh�	���	d]g�~
k�~
m�~
n
st7zT�	���ao`
r��	���	_��	���~
f�~
t�	_��
P
oH�	��
zPX
\��	,��		 
v<�	���0l��	4
mP
rT�	����sD�
p�
��\
i��		d
n�
s�
���
���
tL�		`�s��z��	�p�		�
l� r�z�	��	.�
e��	\�d,Jg4Jj�
nDJrlJt�	.�ah�	_H�		�
j�
s$�
t�	,�	.L�
b�Thh�
i(8jp�
ox�
r��t\�
��	�c�$c��T�
�dp4epX�	�i�`�		��
c�gL8l��
nr��t\8vzXw_�
/�
����
��
����
�p
	̀
c�Jd9k �
lH�
nT�
p
.؀
a�
e��
g��
i�
o4�
u��
�R�
���j4Lk4�
t�
	P�.D9a�
��<�
y<
jh�
��/d
��`�
�
Bp
8t�
l��
rԁ
z�
��|�
�$�
��
� �
�,�
��>��
��xMk�Mn��|�����
�����ȁ
Ô	
	�5c6g�
lX;m�
n�
r�
s�

�
�H
��
��
80OgpOj�;k�;l�;nD�
r�
���O.T�
È
��d"��H�	`�
c���h�
� B��t�
�
	T=g��
kXQn��
t��x����
o|���=a܂
Ø%L	Ȃ
k��Ђ
��
8�Dr�
	�Ql�
m�QrT>s
���}l,
��
8�
r\
�<
	�>g�>r�C��H$l�
D�
b�$g��
j�$r�
��P�
a�
eH�
ix�
sȃ
�H�H����
��
����
Ô
����
tP
�\
	��
g܃
lx
����
�$�
���
��,pet 
�
d�
l�
m�
s, 
��!
�T"
t�"
_�"
	�
k<�
r�"
n�"
��4�
e�"
%mX�
n�#
��lsgh�9�%
�d�
l�%
.l�
e'
�8'
��
iX?p��
r(TxdA
��b��
nPB
#\�
�� ze�
`�Є
b8�
d@�
lT�
nh�
pt�
r|�
u��
z�~��؄
al_b�
e|bg�
iPok��
od�
p��
r��
s��
u��
�����L�
a�P8����y��PX���`�
oH����.��������	��
lЅ
m�
r�
s��
t0�����
�؆
�t�
�L�
�Ԉ
��
�`�_�dt���؅
a@���(k�����Ih�
lD
m`����_d@�
gP�
jp�
l|�
m��
rІ
s�Atd���	���H�
t��@���\�
�ė��d�
�@��u��
z���
s�����
a��d�F
kĆ
��u)��,�����
��l�	�+l�
n$�,���bk(Bm�
n�+p<�
rT�
s�,td�
zج����d�+g$�y��(�
m���0�
a|cô��,���L�
i��t��.<Vo��zL�	�cv���db��
l��
r@�
s�ev��������j��rć
t(�
zȺغ����
aP_	e�
�(�e4���؇
��
��
�����PZd��	�
r�
�@�8`����
�ğ
���
è�.d�fX���4�
z��	�.s���(�(�����
i�gyX�
�,�tD�x�
s��`hc@�dL�����
o����h1ltrp��r�/t��\�d�/r�Bt��	|�j�
s<�_0�	(d��g�
t̢z��	���Cl�CrH��
b��
d؉
f�
g�
kP�
lp�
m|�
n��
p<Gr�
s�
t�vz��
������
a�8b`�
c��
dh�
e,�
f��
gĐ
i8�
jp�
kГ
m�
n<�
o�
p(�
r�
sȚ
td�
u�
vğ
z,�
�ԗ
Ō���0tahtmXt�p�	 ���Љ
fD���pg��
� ������
�4�4�d`�s@����
a8[c4�
l<�
oH�
t�����lh�_����(�guj�lkT�m�Vt�Ev���Lup����ma��
e��
kUo`�t�uy�� �_,�T������
������up��8(�.̊
k����Ԋ
z����Xvkxvl�vtHy��	(Hbd�
cd8g��
l�Hp��
r�
s,�
t(����
�4�
��
�0�
���
���
�О
���P�.p�
a��	ox���P�
s���l#l���x�
e��
l��
�\����w�<�����v��
ø������0���T.�qa4�b�qkIo�p̦rIt�qu��v��
�p�8�����
��q�H����v�
z�.reP�n`���$�
a|�e,
f�Ih"jD�kD
mJrh�s��t(�v$����xa��
hԌ
sXzz��
��	8d���|�
�Pz����|�����,.xyo��
�T�gd����
��
��. 5eČ
������u����
a�
eT�
i@�Jm4�
s@�
x���� �
����(�
��
����g�	H�
n�YL`�
ač
bԍ
c�
d�-f�
g�
k<�
l��
m��
nĎ
p̎
r؎
s$�
v,�
z��
�
�
����
�4
�0��
��̍
s��tP����
e@wy�
�,��4��d� �
e0�
�0������(�
�.t�
a|�
e�wh�)j�wm�wn�od�r��
s�wt�wv��
Ŝ����������
�d	H�`.�`e�g��
y�^� th"���~�".8he�
s�
z|���
�̗�ܗ���
�T#_�Q`#.�
e�'��'8(	�;d�Lgh�
j��
kMl̏
m,Mn8Mpԏ
r�
t�
v�),D*�P*	p�
s\*��x�
�*����
e��
n��
�|*��	�}	����
�������
��+,�-��HM�t1��D:e<k�2��T.�
e��i��n�Mr �
ô2X�2�x2���
��4��t�
i`�üKy�<�
r����D�
�L�P�
����\�
�P5h�
�l7�x7��
i�7��
a�6����
i4Nl�=��=����
o�;
��
b�
d(�
e0�
f<�
g`�
hl�
m��
nh�
o�:pt�
s̒
tؒ
z>���Ne�
iOØ>T �
oL�$�>��>��� j?��${aP�
y��ô��L?�X?��X�
a�@��x�a�Oø@p{.�Os�@��	|�
aPdPfؑ
g�
h�>j�>s$�
t̑
���	 |n(|t�
vlD����
��B��T.��
c�
i�
n�"rp�s@|����nļ	�B	hCtC���
a@�
eT�
Ŭ���C8�
l4DtDD��L�
���#�D`�
g�F���	i��m��
p}t��
z�G	8H�DH����
�PH.��
��H�\H��
npH����
a�I�����J	`Bg�
j�
n�Prh?vQz�J,�K,$����
aP�
�M�
iM��,�
ad�
�4�@���H�
�8M9HM��\�
��M��hQl�Qv��
�K�����
p�R����
ehR	��
z�N����
�|Vp��
t�
z�V����
a�
eP�o��
��
�@
	���IedW���
r�t�W��p�oT!	t4X�
b8�
gL�
l|�
r��
s$X.�.y�*	�XY��D�
i` v�Y��Y	\�
k�Y��d�
��Y��p�
�Z��!z�Z.Ĕ
exZ	��
t�W����
�Ԕ
�\��d���>	n8��X		̔
v�\t�\���
�^.]���
e�
i(�	��Hs�^�
sp(
`�(
��(�
�a	t�
g��
k��
l �
n��
pTr��
sAx0�
�Į���èb��h�
kd����
o��eԷr�e���dH�iЕ
t^
%g	��
l�f����
��f��ĕ
èl		0d.0�
bT�
i|�j`�
nh�
rp�
tx�
v@�
�ll��ܕ
�(l���
ôj���
t��
y��
��l��l��8�
��l��lL�
gd
�$�	��B�x�
��l��l.��
a��
o�
��m��p�eTt�n��L�k̖
t�
z�on�o��Ė
oP�
�8p.ؖ
i�
r�
t�^
��p�lp����
�xp���
�$��
��
a4r	$�
iH�
nT�r`�
s�u��2i��
���j�|y��|�
oT�
�dy��
opyp�
rXy��z		�Ub��
cė
l�<n�Ur̗
sxAtVv`0z��P|��~	d��V���
�������
h@�l�
n`Vr�k	����H0l���
bH�
r�����
a��bx�
i�!k��
uh�
�8��h���\Yad�	P�
t����\�
���
���.��
n��
z4��l��$��8���
k�	,�.��
c�Evt��d(�
j`�l��s����Ș
aP�
e��r�WtH�
z4�
���D�	�
k����
������
�D����
t�����V�h�
�ș
�ܙ
��
���Vd`�
sȌ�،	den��
r��
t8����e���|�
t(]Ht\	��
t�\����
�L�����
��,�	��
r܏���	ԙ
n�5��Nt�	�
l�
t\9
1h9
���
�D9
���
�ؒ���$�
g���,�
���
�l�.d�
i��
t8�
��m�R
�\�
l�[
��[
��p�
��[
��x�
�4[
���
n���Ԕ��<�	g���
g�
l�
n(�
rH�����
a�
e�
i|�
o�
uL�
���T�m0�����a�
�u
	h1c\����
������������
�H����	4�
g��
r$���<�
�ܜ
��Z�ԝ
��
� �
�X�
������p�
d��
m̗��x�
aܛ
b�
n��
uě
��1��H��
	��
n�	r��
����
�\\�(����
��ԛ
e�������1���
kl��g0�
j<�
l8�m��
n��
r��
s Zt��
��H�
t���
�Bal�
ex�
l��
m�yn��
o��
t�v\{	��Y��
��m8�_\�_t0���
_�����pe���Ț����
a����0st���,Ze�yt�Ov�
�Л	Ĝ
l�
r��������
�����m��|Zd0�
k<�
nH�
s\�
t0�P<�
��(�
uX����Zt�����1t���̟��T�
aԠ��h/m��
o�t��h�
k��
l��
r��
t�
��l���$ dT����~m��
_|�
����
t�,��	̝
rP�	4[nL[sP�vФ�����
ah���
rx�
��h".�	�
r�W�	,�
r���4�
���
��@�
�$�	L�
z��&d|�
g,Dr$>tĦ�H���	P^b��hl^i"j"nĞ
o�r�^s"t�	��
tpe�ȬT��
n�		�&d�^g�^l�^n�^r�^s�^t\8v�z��	�^r|���h".,�
a`�
it�
o� v@�
�
�X�`En��B԰��8�
����ȵ��L�
i$�T�
s�����l�
s�������
�PU����.`U��
rpU����
a\���
vt�����
a�
e�
sHIz�d���ܟ
c�
sĹ�t���Fn��.��
aܽ
�b��
d��	k��	l��
n��
pt�rܠ
s��t��uh���
�
a�
e��
g��
iT�
k��
o(�
pP�
s�
t��
u,�
z�
�h��d�`x�����
a��
ø���`���.��
gp�t�`y��_���������Ƞ
z����Р
s��	��b�g,�
l`�
r��
s��
t����
�H�
�@�
���
��
�ĥ
�ԥ
�P����AdTWlH�
o|�1��@�
m`�Tx�
k����T�
a��
i��
o��
u�����TP�k��L�k��n0���ks��G������
a(8j�ck�crС
�l����c����0�
ܡ
d�
g�dl$�
m0�
n8�
r`�s@�
t4�xez,����	�����
b8����t�����	eg،l�p`�
r<����dp�
t$���T�e�e��C$�����
�L�����
��x�����
e<���
d1f�g�
k�
nXgp�
r�
s�	���ܢ
r@�����fHgt����go�gt�g�H���8Bmp3
p,�
t8�
z���Pe����	�Bj�MrLhz4
����lx�
r�
�D
��d�
�\
��l�
�

�T
��
k��
n�
r��s�x
��

����
h��ṇ
oԣ
tܣ
u�
v�z
�D
U�
��
Ux
�4�
4
	�
p`
	t
	�
k�jl�jn�jv�
��t�l<�
rP�sl
�� 
���at
��D�
td�
u��
z� 
Tp�
r� 
t�@
��h�lt!
�x�
l��
r�!
����
�Ԥ
��
�!
PȤ
e��
��
�!
��X
o$"
��Hs�$
���j�$
��]
���J���
���
���
e)
��
t�%
���
e4�
iP�
o|�
u��
yl�
È,
��pPmrțz0�
�.
H�
b`�
l�-
����	d�'
��dm���
��1
�2
e���.��
aX2
��dГj�mrQt(6
���o.�4
	��
rp�s�7
	�ng�nnHv�z�;
��;
���
��;
���
�t;
��
b�ojh�
l��
n��
r��
t;
.�
a�
eP�
i��
k��
o�
t�
u��
�8=
�D=
��T�
�P=
���k|�
t\�
��=
	>
.l�y?
��è?
���e�?
���l0@
����
��
�t�
�̧
�ܧ
��
��
��D
��C
�ئ
d�?g�
l�
n�
p�Hs�E
�J
�pJ
�M
��Vg�j�rl�Hp4�
s|�B@��XS
��<�
e�R
�D�
g�nd�
s\U
���1mT�tV
���
vpV
	4�8�V
����
l���Z
����
o��sħ
zPX
���
rğ
B4[
��Jn�Zr�\
_�
l`]
�����T�m���
l0�
n�p�`
����
at�
e�
oX�
�|�����(�
y�`
��D..��n|`
	<�
r�`
��L�
���
�ب
�l�
��a
l�
d��
l��
m���D�����
l�)
t�a
.Ĩ
äa
	��
rx�
84x����
���_��	Ш
v���s�b
�|urd
	�c
���
r�d
_ �
g(�
l�d
	�d
��g
��H$l�f
	0�
b�d��
gĩ
kԩ
n@p�
r�
s0�zPf
��<�
a<]b��
e��f{gL�
hX�
i`�
kx�
o@p��
r��
s̮
t�
u��
v��
y �
�d�
Ŵj
P[r�l
�t
��t
��̩
a�[t 6y�y
	y
���
c|z�z
���[s~
	8�b<6gP�
rD~
���
��
�H�
���
�t�
�$�
�H�
��
�wkL�
��D�
aĦbL	nh�
à�
�����x�
��
���
��
��
b��g��
l�
m�
v�
�8�
����
aت
eĪ
��
Ŕ�
��D^�\�
��
Ъ
lܙ
���
���
�`�
��t����
���:o��
��
�L�
	�`l��	p0�
r��
��T.<�	tt�z��.L�
��D�
rT�

d|�
m��
n��
p�{r,�
s4�
zԽ
t��
T��.��
rо
����
a��d�
kȫ
�\��H�
�$�
	��
r4�
����
���C����ԫ
���
��ܫ
�@�
�L�
���
a�
i�
n$�
t����k	y��y�
t��
�����
	��
	@�
jX�
nd�
	�
���|l�|r0�
t��
p�
b��
g̬
l�
n0�
r��
s"8�=
��
m,�����
a��
����
r\�
�|�
��Ĭ
a�
T �
��ج
a�
i�8n��py
���~.��
��
n�
s�y
�L�
���
�
k�
��$�
a~ex�
i�o\�
�
	�lh�
r��
��L�
���
�l�
�`�
p�
m��	n(�
.d�f��
����
zX�
	pf��
pH�
s�Bt(�
y̭
a�
i�
o�
$�nܭ
sH'�@�
�L�
�
nX���Fz`�
��
s��
��
���
��
��
��
	$�
t �
��0�
���
���s�Bt`�
z<�
���
�L��h�
���
	l�
lD�sD�
��
��
g�
����
a��
����p��r��t�
j��
��
g��
����
a�l�
�|�
,��
	�
g��
���
���
��r�
s��
~�
���
z0�
	��j4�
rx�
���i�
���
	@�
l��t\�
z��
�P�
��@��,�j,p�
k<��x�
a�f�.��
a��
a��iԯ
l(#|��
g�#0��̯
e�hH�#X����
a,�
id��
gL�
r��
s̰
u�����
a`�
i�
�Ȗsnd�t��8�
n���@�
a��
e��
ix�
�����	d�
t��l�
���
�x�8Zl��	P�k��
sDz�,��ܜ��
s������s �j(��Ģ��԰
o���ܰ
bL�	�
b4�
s�����
�(������4�	�
r@����
����L�
i(�
�X��d�D�
k�j��X�
o5
��l�
l���t�
e0��������
bı
r����
d�
g�
lp�����
i���$�B0���̱
����Ա
�<�UD����
cX	#����
e$����
c�E.��
a��
iHC �
g�l��
m�
n�
r�
t��
u�B��	0�
a,�
e��
i �
oP�
pl�
s�
z��
ü�
��E��F��M��M��
l�M����
a��
eز
iIJ
�M,N�����N�Nв
nHN.�y�P.
a,RxtXR����
a�ep�
i��
o8�
�R2�R	$�
l�R��,�
�L�
�|�
��S	 �s��	t��_����\�
l�Sd�
l\�e�\t(T��
l�T$�U	T�g�
k�l��
r�
s<	t�U����
�T�
��
�D�
���
� #�W��ܳ
e�]X�[���
o�
u_n�_����e��s$�
zh`�c@�gH�m@�
n,p������e��	H�
k��l��p��
r8��|���l�
�@���t�
�@�d@�p�r��
s�
t�tܙ��
j������
aԴ
lܴ
t�
z��D�T������
aL��Ԝ	�
n���0�
st��
r�s�e	��
��g�	8�
ep�
fD�
tP�
f
d�
X�
a��
ü���`�
i���
�|�
b��
hĵ
jص
n�
r��
t$�
v�����
�@��
��0�����
aĉ�P���е
.�
a@��D��
ܕ��е
.�
�|~��w���
����$����
a�
I��0�
n����8�
a$�����r$��p�l��
nԻ.\�
a��s��
�l ��.Ȓy4�_�����
���	��	��
n��z8����v��_j��	ȶ
s����ж
�����ܶ
���8�.`�
a\�
b`�
c$�
d��
e��
f��
g(�
h��
i��
j�
kX�
l��
m(�
n��
o4�
ph�
r��
s�
t�u8v,z,�
���
�̉�'
c��
dķ
gط
j$�
k8�
l��md�
n��
p��
rԸ
t�
u|�v$Vx�z<��Pgh�����
a8�ox��@���D�ad�i��y�����
t����jD�	�
i8����
�H�����
���4�d�	r����
a�uЋì���a��e`�mt�o��uX�
�d����02����a�y��
ÈF�$5��x�
�$]��������Z�� �o(�u��
�`k��0�at�i��o\��t�>�
mt���ȸ
o��yH�H����k��r؍t@�	��b��cP�
g@�h��
l��p��
r��
sܹ
t�����
�@�
��
��
���
�����@���Ďa�i�ot�
yL�������.h�
����|�a��o\H��ȏ���pC���u��
�t_���aL�zĹ
�Lb��P���u#�'dv��й
a��e��iȐoؐu�
ôw��w����
�������А������yX�
$�
b��
j��
k��
l��
n��
pԺ
rT�s�
td�u����,�
a �
e�
iT�
l��
r��
u�
�0�y�y��yP8�H�����
d��
k���jLoexo����
�H����
k��
nȺ
��}��\������
a���t����
r4�y��
�
c\�
fd�
gl�
ht�
j|�
k��
l��
mĻ
o̻
rԻ
tܻ
v�
z�yH�y��y�y�y�������
e��
s��
Ì�eи����
��7�D�y<�'�y��y��y �yH��,�	�
gH����
�l�
�|�y���
l4�
o<�
rD�
tL�
z�'H�y��yty\���h�ed�
o��PX�	8�	j��
l��
��
��|�
�X�����
�4���p����
������wo̼
u��
���	��k��P�Q�2k�Լ
i`����
a4��
d �
sT:�.�
t4�
�T����
z�:�0���,�
���j��
k��
l��
rĽ
sp�	t����@�
a�
e��
h��
i̟lx�o��p�
sP�t��u̿
zܽ
��_������
r,����	a��k�t<��(1
s	d�l,��н
�\�
�xI���
��	��	���
�X	$�
g,�
l��m�v��
�\
��.@�
eh`mP�
�t�`�|��H�
��	`�pl�
t�_��� ���.��e8�ot�
��!.��iT��
m��
nľ
pо
s"��(tg�����Aa4't(4��3.ؾ
aD�
eT�
ip�
o��
px�z�
�
�D7�g8�
rh7���
�L�
�h�
�x�
�X8!�7��0�
u09�=/�?�`�
t��tA/|B�`E/,F/LF����
�0G��
�
�
����
��L	�L	��
n̢zN.Ԣa�e�u�P\�P���
ap�
�O	�
d|�
j��
k��
lP5m�
n�
r�
u�zLO����
a��
b��
e��f|�
g��
i��
k�
o��p��
r�s,�
u��
zH�
���
���P��h�
��Q.��
��Q��Q����
��Q����t�R��\�.��
a(�g�R�DW����a,�o�X���
k�X�
b�X���
a�
�tYe�Y���
�\_	(_�
tH`	l�f�g��lԒpl�
rdQs|`��(�
�h�
���
�l�
���
�p�
���
�lb��,gn|�
u�c�h��@�l(�r�j�@i	��
d�g�
l�
m(�
p8�
rT�
s,�v`�
z�n�n�
m|n��	�
e�
hx�j<�l��	m�)od�r�
vHv�p_�r_�s����b�Je�u,v#Hv��0�
a��`z������y��H�
ð{tT|	H�g0�p�r̉���Slx���d��
g,�n��
pp�r��
s��
z ���(���.��
aԓ�H���@�z����<Vo��	P�r�����l\�r�Tvh�y��
b4�
g@�
kH�
lP�
m��p��sX�
x����a��y��y`�y��
�0���T�@�	`�
k��
rȤ�ؤ��|�
������
È�	ȯl8�s��
t�UvȨ_��	 �	��
rd�����
��`���
mp�����
aL�o��u�
���m�����
���P�y��$�
gD�
rX�
s�t,�.P�
a������нsh�
z����	 �j��
rD���h".0�	�gr�v�z�������L�.x�e��
s��
����.��
o��`�b �
c��d��g�k0�
l��
m0�n��
p�
r�
s,�
t4�vD�z(�PP����
h��s��� �ah�
e��
i��oP�
�д�H��X��t�
������
�t#tpn�%��rl*P�9�ܾ_�		��
k�	����
��B����
äB��
r`B����
eD?����
b�e �u��j��p�ix�od�ðs����e����Ŝ����eX�z$��D�������e��i�����
x�
b��g��
h��
k��
l��
nܸp��
rl�
t��v(�#t���p�
e��gt�����
e��
s�������e��
���������
�4������H�e����dg�����
e�0��\�.�
d$�
e �i@�
td�
z4�
����d5�p7g�;y�2��,�
�<B��|"eX�
��B�C��P�
�F��Z��(�e|�
k�b�hwD..�
a �
b(�
c0�
d8�
e@�
fH�
gP�
hX�
k`�
lh�
op�
px�
r��
s��
t��
v��
z�
�w����
a��
ed�
ip�
l��
oL�
rt�
u��
�D�'0P��w���
���
��wyxy$xyTx'�xy�xy��yHzy�zyXR'<~y�~yԀy$,t\�-�_���y��y����m��n(�����
e����
l8!v���P�	��
k�
l4�
n<�
rh�����
���
�������
�p���,�
t�
�P�%���(���T.І#�H�
b �dH{g��P�
a������a�o(�uT��L�8�c��
g��
k��
l��
r<�
sD�
t�ev��y��y\�y��
������
d��
g�
m4�
r�	�
�W	��
c$�
j,�
l|�����
�����
�H	�
�W�
���D�
yD�y��l�	a��i4�od�
� ���-������\�d��
rԯ.��
aȯ����h".�!n��	��
r�� ���
p������
a��
d�
eL�
gd�
i|�
l��
o��
r�
u(�
y4�
����h;
r��.@��
o�����0i��	�
p$���(�
�p�
��4X���D�
y0�0�l�X�
a0	��r
��4�a�ed�t��
s�����
a��
r�!���a��
�$�#	��
n0#����
�p+
b�+����
a�
z *��
s�,�:	��l��	s03.�
e@�
i��
t��
�0B	D�.P�
n\C����tX��,b�,	\�
rl,��h�
��F��t�
�pF8��
k9����
��
�u
e�3����
��3����
�`3��
n�H����
a��
eH�
.�
��
a�8	�I8��
s\Mx�d<�
lD�
nL�
sT�
tM���
a��
o��
�tRylXy�Zy�[y<b.��
aHb\�
ddb��h�
a�]	t�
z�]����
���
���yy�u��
n��
r��
szyTzy�,�����
s\�	��
s��0�
d��
g �
kL�
ld�
m��
n�p��
rH�s��
t�
vH�z`�\���(�
ed�
it�
oX�
�
�P���n���L�
��#���l�
mL��������
�<�d�����
a��
e��
�@���D�b��i��j��k��n��r��t��v�����
�(��4�����
b,�
j4�
n<�
o�	r�E	t����
n����
o�P��z
����ؽT(���D�
e\�
lp�yp���D�a�i��
p��
��������|�
���yĉf����
.D�i��
ks�
t ���
��
a8�
d@�
fH�
gP�
iX�
j`�
ph�
tp�
v$�
����������
a�����	���	�
n4�r0�
t����
�0�	��yd�y��y��H�yHGy8�y��y�����x�
t������
a��
o��
������
���������
a��
Ø�L	��
k����
�� �d�����
a`!$�
r��tH�
vX�
z|7���i�HoT��LD��������<C��8�
���ŸI���e4�i$��D��dRjXQp�
g��
k��
m��
p�
v�P��x�
a��
e��
uH�
�ĉ�������
.tS	��
n�R����
��R����
r��
�HT.�Ty�ZjVyX�0X���
�d�
��W��T�
mp�
ux�
v�
�HV	 �
r��
t|V��8�
��V%8�	�X9\�
.@Y�|Y%�[��[����
�([����
s��
�l\%^��g�y���
t(�y����
bT�
d��jl�
kx�
l��
m��
p��
r$�
t@�
uL�
vT�
z@�����
a��
e��g8�
hL�
i*kT�
l��
oH�p��
rH�
sX�
u$�
vl�
��P���L�
aА���������`�
�D����*j��k��
�,ed����
��0�8�����
p��
r�`	�XZ�������
a��
c��
s����h�,���\�.�
a�
r�
t��h��<�������
a8�
eгrl��ĚtE
t�y��y̛	��l��
r���\�
���
�H�
���
���
���
������a��
i,gn �uآX����
zH���
dD�g��
m��
r��
s��
t��
v��
z �,��y��b`�y,�
eh�
iP�
�T�ń�p�~���
e�~���
td� �
s<����n�	8�
k����D�
�|�
�p��@�`�
g�,�	t�
g��P��
e0�����
z��\��.��
e(���yL�y��	l��
m�
n�
p��
r�
s�
t���0��<�����
y`���
).<�
bD�
ed�
ll�
n��
p��
r��
t��
zX�
�Xl%Ŀ�P+l�+�����P�
��-%�k	%���
�.t�
l�.��|�
e/%�B%�%����T.��
d��
e��
���
�,�%d���;������
��A��A����
�|��������t(�vT��`����
�8� �
�����,�
i��'��D�
a��
c��
e��
f�g��
jl��
m��
n�
o�
s8�
t@�
v��
�������
���
���
�`�y��-,��D�yX����y��y��i���$�����
c�
e0t�
y���
��'|����k(�t0�
z��x�y�yL�
a�P8����i��
uh�
�H������x�
�4����$.
�������
k��
o<�
��
c��
e��
h��
l�
m<�
n��
o��
p��
r��s(.��4
'd?
y�������
l|h
e�b
����
�����$�
f,�
p�
��i
�������$���4�
c`�
fh�
jp�
sx�
t��
y��
z�	��P�p|
�p�������F��
'�y��e�os������
�P�����
b��
e��g�
i�
l�
m�
n �
o(�
r0�
s8�
u��
��8�������S�Г
��
��������e�d�
����l�	@�
d�l`�
rh�
v���|�_����
�8�@�	p�
l��
r��td�1p�����
��8`�����
dL���3���
�-�<�
�����,e�
i�
o��
��	��
m�
tt1������ ��
n,�
s��\	4�
n��� �kpt�
L�gx�
m��
p��
r��
s��
t���
y�
y47��l����
��.��
e��
�P����
z�thy�Z�	��
r	�g�
l�
sXzH����4���
�����
�p��`�t0�
j��
m��
p��
r�z���<�
aL�
e�
i��
o�
p(�
u��
y�
�$��#��
n�#����
a�)����	a��
o��
�p]Q�*	��
l�*����
��+��lFs�,��tÀ4	�l
b(�
l0�
s@�
t�4����
���
�L�
���
���
�T�
�9HpB���s��TD����l�Vh�
s��
tԘ�mP�l��`�
e`h��
nX�	s�tU��	�o#�o����
a��
eHpT��
v�
t`w	�+r��
t0��)l�����
e��
�,�	��	s<�����
�܎����o��
k,�
m���
a4�
s�#��.@�
z|�.
ox�	tn@�Ttll���X�
aXe��
i��
r��
À�d�
m�s\����L���s�`	H<�������	��
s<t��
è�d�����
l��
p��,�	l�l��r�sؘz�����r���P����
e���
m8�
s������sH�
z�6.�,ì�	d"tl"v��#����d�
a�Kg�	l�
g��.|�
a��
���������
��	��H$l@	��
b(�
c<�
gH�
kT�
l`�
np�
r��
s��
t�b
u��
x�����
aT�
bt�
eX�
i��
o��
p�
ô�
��	��	���
��	���
�	��4�
n�	����n�	���l	yk�@	��h�
a��
c��
e<
h��
kt	��v�	n�	��		�p��
s����	��
	����
e��
ot�P �yd	��		��
g �
l4�
rL�
z�	����
���
���
���
�p	���l�	%�	��,�
kD�
t�X%�	��	���r�$	~ "	��`�
y� 	h�
g��
nT-	��`�.D���6		��
d��
l`�p��
r�9	���
el9	pX;	����
e�
g�
k�
n�
s4�
t��
���
ń;	��;	����
��=%�;	%]%�A%X<	�
h<	�� �
�8<	��(�
�h4C	@�
cܽs�B		H�
a��
b,g��
k��
n��
o|Ip��
s\#z0D	��E	��
o�E	�tF	y�H	��lUo�t�H	��
n�I	y0Bk�lt�-z�0
y�Q	
��
d�
g�
h(�
l@�
np�
rx�
s��
t��x��
z�R	y�R	y�.�T	�� �
a�/du
��T	��8�
dX�
i`�
oh�
t8U	�LU	��U	� V	y�W	y%zpX	y�X	y�Z		X0t�\		8\		��
r`\	����
���
��_	d]	����
rh		�1t̢zph		�dtm	yTl		��
dX�
g��
k��
l�
p�rL�
sh�
tp�
v�k	��
��
a�5b��
eD�
il�
o(Zp�u�
y��
���
��m	yto	��o		`�
s�o	��h�
�$o	����
e��
ot�
���o	��o	��
l�r	���o.��
aD�o�p
u��
�pp	����
jhE��r	��
k�������
��u	y�
at�e,�
Øu	�m.|v	�@�
b�v	�� �
�p��
�`��8�
a�y	��4\p\�
zz	.��t�z	j�|	y@	.�)l �o�|		x�
l��
s}	����
���
���
���
�܂	���s�	.l�	��
d$.z���	�����
���		�
m�;p�
r��vHz��
����	�	���
ad�c�.d8�
�Ax	d�	��0�
�(�	gT�
s(�	���lt��	��\v0�	`�
d��
k��
rhx8���	��
l�	����
u�	yt�		�Yn(Gr�t\�	lFh��	����
�x�		��
ð�	����
�`�		��rp
B
.��
a$�
e4�
i��
o��
u|�
Ô	
B�7��
	,�
l\�
t����D�
�h�
�|��L�
�L	\�
.�
'�
��t�
���
��
B<
B 
'8'
vd��
k��
lX?p�
r�1v(Tx0D
��Sa��
i|�u(S�pH
�N
���%a��
ihW
#\�
���Sa�
i�SoĐ
#�
4�
l&rt�
s��
vX�
��<VaX�
i�VoL�
��
��81��
#��
���
��`�
���
��h�
����
��W�8���Wo��
�\	LXb�Xk��
lYr��vtYz����X�.�,P�Yr�
s�P����
���dr�q����
�Z�\p���
�[dd�
l��
n\r�~��
�
ap�c��
e��
iH�
l`�
o��
r�Bt��
u��
����(8kp�$�p�
l8���x�
a��
o��t�y �`��	TAl0�����
���
�pj���
�����f�_g��
lė��x`od�t����
dT^	n�
o$�
r8���������
�����
�l�$�0�
kX�
s@���8�
a�^	u<-���dbt�
l��
r����	a����Hea,�p8�����
aĽ	��
rL�����i��
o��
��@�d�p����	��
b �����
���js0�	�v �Bl����
�����
�H��
b��
c��
d��
i��
j��
k��
l��
m��
n�
p�
r,�
sD�
tX�
�����(�
al�
b��
e��
f��
h$�
i8�
kd�
m��
o��
p��
t��
u��
�
Ŝ��T�����
et��(tt������
a���d���
b<�h4�
i<�
jD�
k\�
m��n��pd�
sl�
tt�
v �
�4��,�
r@����
�Ԧ���Y��(��T�
o����tP�������8��������|�
e��
o��
z�������@�����
v|��������
a�lk(X������
o��������
i��t������l8����mc�
e�v�h[�����$�
p(�4m����8�
o�_�����P�
����H�d�
nHW��l�
���	��
g��
k�
l$�
m�HpH�
s\�
tx�
�(�����
���
��
��
�T�
�X�
�p�
�h�����g��
y<�����������
h�
i��	l�_\!x	�����
�����8�
o@�
r�
��#�	�`	�H����8eP��`���T�
a�3v\���TJrt����x�
e@wyL��
g��
l��
m<xr�
s@�
th�
z��\we��
l�wm��o�wv��
��	�������
��	H����
b�-l(#��"�
t�"���
e�$�%��(�
�L�
��$��0�
� &���	B��	��T�
��'��\�
ø)���Le8(	t�
jMl��
r��
v�.��-����
i�2���Me�4��t�llc�@c��
t$;����
o8;	��
zD;����
��:����
È<���a�;�
k�=l�;
�
a��
c��
d��
n��
o��
p��
sLt�
zx�
�L��@=��T�
i��
sP=\�
s`=��l�
�l��ܘP�=����
h>t�@��$Pt�D��D����
n�|o�D��
nTEy�>a�F���	i��
m�nhG����
e�I.<Vo�J��J	�
j�PrP;zTQ�M��0�
l�<	.xZ	D�
s�W��L�
��V��X�
�dd�d��p�
���
���
�d����
i��
o��
ux�
�a��
k��sAx�d$
�dPe�k4�z�eP�e�sn(s����
a0�
�4r	��
dp~iāpH�
sXlz0��(�
	�
j�s��$�
���
���^�|y��<�
Ìz	�ll�
m�sЈt�|���l	�rd��x�
�����`Vr����T��̣����
�H�����
r`��l���
g������
a��
e����
m,Dr�
sL�
t��#<�
b7n\����
aL�ejs<�
z0�
���|,��8��P���D�
a�	Ȏsh�
tH�_�	�^n\8v�zt��D�����
lܽ��
b��d�g�
l(�
m<�
pH�
rt�
s��
t��u�`	vh�����
a�cbP�
cd�
eX�f��
it�
k��
l�n��
o��
p�
s�
t�
u��
z��
�p�
Š���4�j�GtL����O
n �_����4�
rh�yh�
ch�
��tT�
t����\�
o����x�p��s�����al��	��b��
fx�g��
l��
r�
t�����
��
�(�
����\�
�(�
�D�
�$�_P���TWl��
o�������Њo�����ci�
o����5��5���
��5�� �
�(4�,�
p�3.8�
a���D�
s��y0�\�
b�dg��
j��
l�
m�
n�
p�Jr`�s�dt|�z\�y�����)j�m4s�I	v�
� �4�����
���.�
b�
i�H�8�t4�.��
o�o���	،l$�
m4�
n�p@�
r\�
t����������,�
e<���T.P�
t$���T�e|��fe fk(ft��
�x�� at\	t�
k�\����
�<�\fd��
g�
k�
nĘp$�r�
s�.�fe�x�����
b��
r����
e��
ll��@���gd��fH���0Bk�gt���	 �
kD�
nh�
r8�	p���<�
h�1���P�
��A����X�
�4
���a��
i��lX�vh
0�c��
s|�.�
��g�
����
a�
ð
�
����
�4E
%�	
�
s

���
a �
k,�
sT
�
k4�
l@�
r�tT�
v�J
��

���
o�L
��
��\iix
y�ig��j��m
tt
	�l4�sTOt�
��X����
�\
h
��
c��
k�
����
e��
i��l��u�
�

��
rt
��xkk��p�
z!
Ptle��	ô%
�%
�
b,�
d4�
fD�
nP�
t�%
����
a�
o�
rl�
�P&
t&
�&
�&
��<�
dx'
�'
��
b �d��
t�'
��X�
���
���
��
�T�y�'
����
b�(
U<+
UP+
	��
gh-
��
�,.
����
o.
�
p|0
�mi�PuH��1
�2
yX2
��
g�mrQt(6
���o.4�
iLnn�4
	�
rx6
Xh8
��7
	<�
l\�
rd�
s�z9
�$9
_�9
!�9
��l�
��9
��t�
�t9
	��
rt;
�ocl�	k��
l;
.��
a\�
e0�
iX�
k0�lh�
o�
p�1s�
tx�
v�
��
�,L�K�
pP=
���
a�?
/H�
g0@
���
��
�P�
���
���
��
��
��V��V4�
l|@
��<�
o�D
��C
�T�
d��
g��
k��
l��
m��
n��
r��
z�D
�L��0E
��E
��I
�J
��J
��).�
e�
k�
v�
z�J
��
p�u��L��<K
5�K
+�K
+�M
��M
/$�
nHeȆ���
��O
���
��R
�D�
m�S
��S
��<�
aV
/�V
y�B�PX
�`�
b��
c��
f��
l��
n�,
��X
4DY
�xZ
�4[
/�\
/�ts�]
/�]
����
�$�
�T^
y�`
y��
r�(�db
�tb
���
��b
���
��c
/�d
/`g�e
+�e
��
kH�
r|��
�e
��4�
��e
��p�
t<�
�d�
ňA�
����\�
���f
.�h
�8h
����
��g
����
�f
��
d�
g��
j��
l�
n�
p��
r�
s�
t�
u��
xPf
����
a�
e��f�i��k�lo�r<s�uP�
ÜŴj
��\�.@�
aL�
bT�
g\�
ih�
jd�
k4�
nl�
ot�
s|�
u�j
�prl���Lk
�\k
����tk
����l
��l
���vkXp
%`o
��
p�o
����
a�~kT�m�
��p
eq
����
��t
y�
t�Ip�v
���
iXw
yy
y�z
�s�{
yнP�}
�
r�
�Paئg~
	(�
gp�
px�
r��
vD~
��8�
�,���l�����4�
�L�
��H)a��
o��
sȈ
�(�
%d�%Ѝ
����
c��
i�
k�
�`�
<vrp�
�
�
���
�<�%�
��g��
l�mr8�
����h���̠
�ğ
��d��
��z�L�
	 gTktt8�
���
��@��
��H��ce��
��`���
��h�T�
�ad�{g�{l|�
m�{n�{p�s�
���]z��
	H|r�rv��z��
#��
���e��
	�k��
�����
�����B
y��
�j$l�}mPnXrdv|�
yDl����X
0d��
��8a �
y�
y��s��
yX�
	�	.|n�
���
	��k�
	XzeP�
��������
��d�f�k�n�
���ai0o����
�d�
���
�p�
�����l�
��kTlü�
���
���D�
��
(m��
����l��p|z�ut�J
Pp�J
��Xe�C
�dr�
.pe��
��s�
��$�	�ŀ�
	�lL�sX�v��
1��
����\�
���s�
	�zl�bgDjPn`r�slt4��T�a�|r����a| d���,ah��8j'��p�i�o,��a;���a�ox�u�Ð<��(�����>#�C�j�r<�sP�t�Z�����h�fg�l\�nMr��s(Mt<Mv��z�o���e<��4���TMzt�yd�dTg\jdk�l�r�s�t����a�e�i�o��X�y|�y,���\�.xs�u���`��������
��y��y���t��L�	�g�k�l�n�r8s�z������t���������D�a,o��.doh���iԨ s���,�aXi`ohrpsxtd��	|h��
��k��eET����������a�i���r��T �p\����n�0
1��d�m�n����yEjl�HC�d�j�k�l�r�s�t�v�B��a�bldxe,�f�hiTo�p�s0�t	u,ô�hE�����|��EGypH��X�c��r�tK	�KP�K���a�P��P�c�QXR����ao���R��,�
�����S	 �s(T�4l�Te�U	T�g�lHr�U�������(	�@	��[���
uXÄ\��
�x��c��di�c8�g�Fl�m�r��x�zm@s��\��|���	��l�t��
j4�	�iXQ����lQ����\M�j,����a����m n��p4s���lsg������,e��i��sLz��t��lln�@���da�i���<�#4�~p�	�s�	�	�r8�����$����r�4j$���kԻ.�a�e��o��z����%
r4�y��	hh�r�&
t�����	 	s8	t��_��	,�gT	l��z(�	��8�.�!a�	b�cd�$e�f@g�i�jk�l�#m)n46o\3pT4r�?s�AtCu8Fv(Iz�	�8&Ŭ����!���H%�Ȅ
�(&��f��
�H��X��	n�����	ah
b�e��f�il0o8
��	��lP
rX
tH���$
���D�h�����	D�l���`
a�
e0iHo�
�L�el����
�$�@�l�*
�t���)
g�
l�
m��r�
sv@�.�
e��L�.�
e8���,�����
z�������e����	l����d��s��@�H<l\sdt��t������(�C��l�t���f,*
g�l��m��n�r���t,�	P�l�r����T.��tt�z����d�n4�����g��td��\����i�,�������x���(r(�
	X�	<n�y����Pl�t��	Xl��R	����t�����|��P�j�����a�c 
h<
iP
s
���\�(P�a�i����s���	`�p
v,�����n���
e�.��e0
��@�	�T�-
�(4$�3.H
a�
ix�z�
��
Ŵ�	=�l
j�
k��th7��t
��
��M��=	�����z�?��
z�D\�9
��I��
rLF���
��R��O�
l�"n,rLO��	�
a|eԫgo4sHu�zL�x��X8a�H`	4llr|`��<�����$�T�l�lb	\kt@itf@�l�mSv�st����D�oT|	�m�s�vl���Dahs��z������sD����e���<�	��	�zD�
�s`�
	@�	c��r�����p�����@s��	8$jds���0�	�jvd���������r������L�.�u���C�����������������hsP�	�sh�����w����x� �b�cH�d�g�k�l�n�p�r�s�t����a��
d(eԻ	fixk�o�prs��t$uxy��dŐ��h�������4����(����P�	ܿ	�bglr t$�������d�����H���	l�����D�	@�T�glkxl��m�r�s�v�V
��	Li���T�����`�H�����b�e��fD�h@ m��o������eH��m��tL�,T����z�����x��	��g�k<
pr�st���,"t����T.Ls��	t��t�d<mDnPs��v\z\������f�����t�0	,�n��r<�vh��$�rP��l�p�r�sT��<	P�r|��!i��	�l�v!�p���� j�$�p�!���e�%����k��p *��d��n8t�,�00_�/	@g��s��t�v�zd������3�03.pa������	�l�����`~���o���P���t��(����bTg\kdllntp)r|s�t�u$)v@����a�
dePf�g�i�+k`lto�p�r�s�t$u�Ä�؏����D��h�����\�ę�Ě���	̛	�gl)l�p�r�tp�	z�������4����T�p�ġ����	���H�
�d�Cg8kDl�Cm�n�rDs�v�z�������0e8�.��	ahe��hh`mp`npvX���ȯ	pT��O��x�����s��б`���D�d�eT����~���zd��s������L�t��	�gk0l<pXrh�s|t�v̻���n|.st��G
s����$e`�_d���lhmt�z����Dep�:t�����������e(���e<�H����l�.�e��o��	�d�g�kn��o�Dpr$s,z�������XMt�`$����a��d��g0t�����o|�ttt�	�j<�r@I
v�z�������L�8���T�L�<�lk�l�n�p�r�v���Hnd�ü[
�D�����$����P���L
o�r�}s��	������l�	�rv|�	`���x�.0�@�	rH������(��C���#<dPS��Da�RPr���\���8�	h�|���x���T�~��z	�sL�������������������d���������p��rz�m���l`.i���
n<rDsLt�
�Ph�	�jX	rhth		�g�l<tDv\�zH.8~t��Ø�	�dg�j+k l(p0r8u@v����a�bc�d@e�f�g i� o�!p�!s�!tH"u�"yx�L!ńD"�)�,��3��3�<7��4	Hg�l�mp�r�s�t�z�4��P����+�� �!�p"��"�9	�<�HA�pB�TD	F�G���
rJ.�I��l�I.�aPepi�o,ÀG���sH��PJ�p<rdJ������7�K���6l�J�Dl`v�<��L��L�hd�z����M��M��k��l�s�D�$N�܏	g�lNy�NS
bdk,l@n T
pHs�N��	�ale�i�or$u0zX���DO��Q�dO`tO��$a�uj(8kP��ZtQe$Q��P�����pQpT
g�p0�v�RG�|_S	�d�l\S�������gT�nU
pU��T�r`Ps<Ue�UCrs�U�����U��U�V�����(V�Vr�V�WN�V8bxg�l�r�s�t�vLZ�ZdlhY��le��gH`�����l�����l�����q��$���o��eao�u��4��`w	�d(g8k\l�m�n`�prDs�t��x�	�x����|��|$s4|��,ePÐ}88}��H�h~��
T.�e�h�i�j�n�s�'tT�v�È~X�~��~����\�XL���	�~n���e`���eL�k���ed���
T.De��hDi��j��k�lnLs�tЋv�zt���'d��k`zԧW ���Xn@�8����l��(����Te��0���p���$�����0��t����e�����r���̋����d�. e��4����yt���	 r���dP gLy
jd lD�ml n�p�+rt s� tlzԐ.�1a��y��t������ؘ`���| aVtd����� c� k� l� r� sl��ܷ �d������	� v�#����� i !l,�	� kl�lt�n��r(!v`��,`���$6ld!�<�	0!r����@!����m�����\!��	�Gr,���p!������r|!����k�!r�t���g
t���!d�!j"l4�n�����!a"i<"r� u0"��0��<����!a�[j���]
d "ll���������("�����T�o���9g��j`"sh"t������,�	�9j0�s��	$:d�g�"l�:s��t�v�zL�#�����"e�"t�"v0��P����"����`����"ì�������"e��.�<a,#ed#f#�������#����\�	$#g<#l<�����	D#l(���L#�����X#�	�@	p#d�#g�#l�#n�#p�#r$u���x#a�$b�%e8,g`'i�'k�'l(o�/p�/s|(t�(u4$ü(�	��		��	�@	�
	��		XbT$g\$hd$l��pl$rt$t|$z�	��$��&��'�L(�h(��(�d		@�	p		�	�	��		���	�$c�$k�$l�$n|*z�	���$a%eT%i|%o�$�x	t	.4	��	t�	���$�@%�l%��%��%��%��	��	%c,%l��mإs�	�	��$%e�	��		8%k	�	L%dd%n�	��	��	t 	t%sX�e 	�H�� 		�%r� 	�b�%gH&lP&m��rt&s "	��(a�%e8&g@&y$&�l�#	�%r&st#	�\#	��&z�#		 k0&p�"	��&�����#	��$	��)	�-	���bh&eD@C`&lX1	.�&e�&t�1	��2	n�2	���&e�6		�Fg�&h�)l'n`�pD'rT'sh7	���Fs8:	W@:	���&b�e'h��k'r���T:	�&k:	���&e8'yd:	WD�	WL;	�;	��$'��:	.,'�X;	��T.�.t�<	���%s�B	,g|'n|Ip�'t�'ztF	t�J	`K	���'a��tM	��L�m JoM		�k
z�N	���.l$�rX��P	���osPP		�'s�O	���'�(�pO	���'Ð��,�	(tS	��Q	(k4(l<(rp/sT	� V	XY	��X		D(d%r�[	��t�t�Z		\(n�b	 b	��t(r�b	��b	�(g�j�/th		�g�Mn�vX�z`\	��N�Tl	T6b`)dh)gp)jx)k�)l�)m�)n�)p�)r�)s�)t�)u�)v�)z�k	���(a�*b�*c$+d�+e�,f-gl-i.k�.m�.nT/oD5ph0s�0tD1u�1y*�/�m	��m	�o	�$o	�pp	�t	@u	��u	�hx	�y	�z	|	��|	��|	0}		�|		�)b�Og$*l,*m4*p<*rD*tp�	z}	���)��+��-��/��/��1��1�@		��		�@�l�	�0�	�T*ah*� �	�'d�y�l�	��`*����D..h�		t*t��	���*���	���*Ø�		�*k��	���*�8�	���*l�*ì�	��6���	���*h�*sL-z�*à,Ř�	Pp�	. Qz�P�Ԑ	��	+b T
p�	��+aL+e$Ro�+z|+Ð+�ԓ	.�	D+m��	��T.��n̦rRt$�		X+r��	��p+���	t�	���+�<�	Pl�	�9b�u
d�-f�-g�-l�v
m�v
n�r�+s`2
v�+xX�	t`�	�	���			�+g,j$,k�.l�3p,,rH,s`,tp,v\�_��	,�	���.d��	iP�l�tp�v��	����sX,z��	_�	��4e��tL�	��T.�,eԈv�,��	0Ust<tԲ	W��		�,rtб	���,���	���	�,m̳	���,eس	�,l�	���,e�=r��	�\�	���,l�r(-yH�	��		-s�	.-a<-ôx
�`�	��A�X-�d-�XF��E8P-rpF�(�	�0d�-f�-g�1l�1m�-n�1p�rs�-t�z� T�	.xAa�-e�A�� ���	t<�	�\�	���-a�		�j�-r�Bz`�	_L�	��g(.n��	���-a�.oX.��	,��	�� .y�	p��	��4.u��		<.rl.t��	��H.��.��.�L�	�����_t��	t.lH�	t`�	C��	���bg��	�.g��	���.e��)T�	�.kp�	���.a/y�.���	t��	���.���		hXd��	P�.a@/e/�\�	���X��4�

~�	
��,/z��		4/d��	�0�	L/dt/k|/p�/r�/shx�	���	�	��	��	�x�		�/c�/r�/v0�	�@2	t�		p�b(Gr�/v�	,��		0ex�		�/r��z��	���/� �	#,��8���0�D���0�H�	� 0k8�	��,0���	��80��		D0g�	��P0�D�	���GpXt�0z\0�H�	)(�	��h.	l��	�0l��	���0e�0g1r1�|���
���0n��
�0n{���0e�@�x�
���0sL�		�0s��z��	���0���	P�	��1a,1�4�	�����<1�(�	��	��d`1nh1rp1sx1t�	4�	�X�	��	�H�		d�j��sd^t��		`�		�1d�gr��t�1zl�	_
. a�Md2e2i,2o\2v�1�p
��
���1�$2�42��

��	
	2l�
	=d5n�
��
�\
8($z|���<@2jx2l�an 
��H2a�2e(3i<3o�2�@=�>,�=	�2r
���2��2�43��>��>�2g�2r�2s�2v8?t�?��\Fz�?�p�_�
	�2g3l3rD@�`@��T.�@_A��3l@
3l<At�A�̀D3jp3l�~��L3a�3e�3�����	��	x3l0����3��3�4����Al�3n�3rt�b|����3�`�.�3�p����3y��ܤ_l�	�3pd���d���	�3lH�XZb�4c�4d�4g�4j�4k�4l�4n�4r�4s�4t�4u5x5z����4al�
b�5c6d(6e�8f�=g9i�9k<:l�:n;o<p<s4<t =u�=v�>z<5�T�����D����@��������8����������x�����X�	��	5b`5ch5gp5lx5r�5s�5t(���5��7��9�`;�x;�h=��=�x��h��l��0��H��`���.�5e�5k�5oDz�$����5s����6l���5l\�����h���5Ì��s\
����5�6����9r�5��
�L 6cx6d�-f�6g�6j7k$7l,7m<7nH7p<xrP7s�7t$�
vPLx�7z�~���p6z�#����6a�6y�6��d	 k,���6�<~`t���6�����6�|�����6z0�6sd���6e7ôC���7��HtT	`��47c ��"��Dxe��Dܩ
��\7��%��d7�h%p7t�$��|7e�'t)X)���7e8(	�7d�Lg�7j�7kMl8n8MpTMr<8s\8tl8v�)��T.*��|.s��
�,���7e-8k�,��8e�/��/(8ld/��08e�1��1H8gt1��P8e��v�2���<e�Mk�4��4��|8��8��4���8i�8o�8�8Ŝ�_�4	�8vP5|Ip�8s�5��5��s6�6	�8r$6���8�>��;9dD9gT9k�Ol�:m`9n�:p�9r�9s�9t�9v�Pz?.hOapOÜ?.�:o�@��PdPg�>j�9k$Pt�"v<C_LF��p+a�F�pH���PaXBt�I�����J	�Prh?vQz�M��hQlD�r:�T��h����9�PR���9�\R���9thR	:l�N��:�LU����
a�T$:t�S��0:eT:i��t��L:n�_�\_	`:l4�	n�Xst_.h:a�:e�:�]��|:y`.�:e4`	�:l�:r
�t`�8
y�`8�:j>s`���:��:�$b����
8�:r�aa;b0;k8;l@;pH;rP;sd��e��m$n��nw�4r	X;r\{��;e�z	l;g�;m�;n�r�;s(4�}x	�|���;��|���;��}��t�t�~PH�e�~���;z�~���;s��z��j���;t�����;e�����Ap��v��.� <rH���(<a=o\<�<�m��	H<v$���P<�|<��<�$���MsЛ	p<k�<l�<r�<st�.�<�<Ō�8�����<��~8 �
���<����|n��P�����<z@���	�<vԠt��=k[p`���=d��j@=nH=rP=sX=t0�����\��P��ث	�	`=j|=r�=t��	H���	$:d��g�=l|�r��s��tX�v�zLr_�	�=k�����=������=�|����.>a(>b8>e�>h�>n�>r�>t�>vx>��>�0�.X�>s���v4�0>kL>mT>t<�vķvl���	\>b�>h�>v԰��d>��>���������������̆�0���������>�������$?e8?i\?ol?u?�t����>sȻ�Ի��?�D��d?�t?����OlD@����0?dL?nT?r��������$��_�t�_H��ܽ|?g�?lh���	�?a�?e@i,@k@@o�@s�@t�@z�?à���	��	�?r����?�P���t0��?l@s|�z�������@s<�XgpLM�4
��<�r�
�T
8@lP@rx
��!
.t!
�X@r�!
��`@��l�!
P�@el@�t
��|@z\"
�$"
��@g�@m�@s�"
��,rb�K
��&
_�&
���@c�%
�@r�%
���@at;
�;
.�@a$Ae<Ai�@opAt8�ŜJ
��C
�Ar\U
.HAz�R
�0AstU
,��_D���PAl�a
XAl�`
��dAe�g
�f
|Ab��j�Ak�Al�AmBsBtPf
���AapBe�af{g�Ci��k�Cl�Co	p�DsEt�EuFv8B��D��l
��o
�,t
�z
�{
�h~
	~
	BbXBgxl`BrhBsD~
��B�C��C�4D�\D��E��
	L�
	p�
��
�6k�Bm�Bp�Bs�Bt�Bu�
v�Bz��
�L�
8�
��
����a�Be��
���
�Bl8�.�
��
C$Ck��
���B�@�
����o�B�L�
	�BlLCr�sxCv��
�$�0���,C�0�
��4C��
��@Ct\CzF8<�
1 �
dCsĶ
��lCex�
�T�
�Cr��
	P|v�
v�C���
�Ct�
���Ce�C�D�th�
���C���
���
���C��
��
�CkDlDr$Ds|�
�
��
d�
�X�
	,Dr4�
��`�blDd|Dn�Dr�D�
	@Dd�Ds�h�l�	Ul�
��tDex�
_��
	�Dt�
���D��D�p����
x	x�
��
	(zaXze4��P�
���D��
��
���Dc��t�l	���
�Dc0Er8Es��
��EatEe�EuPE�`�
�l�
t��
	�el`Er��
��@E��E�L�
.X�
��x�k$�
hEr$�
�0�
���Ee��
���Et��
	�Er$�
t�t��
��j�En�Et�
��
���
	@�
ll�	t��Ez����Ee��En<��Fe��d� Fb\Fk����(Fa�FeDHi��o�HuxF��H�,���L�	dFr����lF�tG�xH��H�L�����z���Fd�Fg�FrGt`�|����Fa�bg�Fo�F��	��	�Fs̭���F�X�#�����ķ��Ga,GeXGoDG���(�$Ggd�	��	l����8G�`G�hG���#��tط	ulh�	�X.�Gg�Gj�Gn�Gsp�	\����	��l�:	r̾.�Ge�G�p����Gy����l���.��a؆elQo�G�\����Gz<���������H������Hl؛�p���$H�L���,H���8Hd$�h\HshHv����l�s��h����	pHd|����t$�	�Hl0k	�Ms�����H������H�,�	�Hrd����H�@����HtHC
�IbhIc�nd(ojJkJlpImxIn�IpXor�It�Iupoz�B���Ha�dJe �fMi0NmDNo�pHOs0�t�Ou,'
z�I�O�8D��M�HN�LO�XR�T��U	4Jg�Jl�Im�Jp�Ir�It�U���I�`K�N��N��N��O��O��Y��[��`�J�La� a���I�xd��cJbHJddJg�Jl�Jm�JnKpKr�s0Kt�rvpe�Lf�4fPJl�e��XJe�Jy�Jøf��Jz�f	xJsf���J��f
�f��h��8�a�Je�i�j���Je�i�Jmm�,p����g�Jtq	�r_�r���Jr@st4~	4�k�|��K��|��$K�LKńCXKr���@K�p_��	��g�Kj�Kk�KlLm0Lp�Lr�Lt�X����Ke��Ѓ�Kl�����Ke�����Kl�Kt�����Ke�am(�tL�)$����K.��܄��L���. Le��n������(LaPLe�LotLÐ��t�HLl`Lm@�t܈��LtT���hL�Ԉ��@���T.,b�d��h�ri�Lt<vt�z���h�Ll�����Lex��e��
�x����Le���� fk��.8Ma\MyLMÈ��LgdMkȎltMm|Mn�Mr�MsNz��� *z�����DM�����, ����oXMt$�������f�g$�p$�t�"v�MÔ��H����M�@����Ma�Mi�go�M�l���4��Ա�x�`�����Pk8Bm`'p�Mz�����tԜ	xtr�z����bg��Ngئ��$Ne�
t�<NbdNklNl�tp�NstNvܨ�d��1(�
��	|Nh�NnP�	<������Ntг	�Nn�Nr�Ns�Nv4�,0�o
8����NzD����Ns��_�	(za�k	eOr8����N����h".,Ok4O�L������K�$��Ի.@Oa�Oe�Ok�Oo0�p��ztO�����lO��O��O������4������vg�On�Or��t���4�(����	�Or�Ot��	��	'
d��g Pn��t�v��z��
����Pn��8�.�Pa�Sb,TcXUddWe�[f�\g�]h^i`j�`k�al�bmcn�do�fp�gr\ps�xt�yu�zv�{z�R�f���,P���̉�Pd8Qg|Qj�Qk�Ql�Qm�QnRp8Rr��s��t\Ru|�v$Vx�zh���Ȋa8�o(�uQ��� Qs���Q� ��\d���s0Qz�eP@���D�ad�i\QyPQ������h�PpQa��H8�	hQg�����a��������a�Q�H���ȋ��Q�������a��e�Qop#$��4��02����aԌoRy��
��L��<P�,K.�Q�XZ#0Rr�Z��Ra �o(�u��
�\�`k��0�at�i��oPR�xo��T������l��rpRt8�#�R�0���kܾ_|Ri�����R�@�
��cSg@�h��
lDSm��pPSrpSs�St�Sv�����R��Y��_�Le��e��y�(z��xbi��k(Ss�t@����Ra�i�o$�y0S�����������p���x�	�pC����a4	e�i��o�u��
�t_���aH�o4�uL�z8��v��X�a��e�S��w����������������Tk�$����S�X�@�p�����Sa�SlTr\�����eto������iTho��uthà���Tp���� TaXTc��gxTh̟l�Ts(P�o���LTs��� ���T�@�	��.dT��4��4���Ta(4��TlHp�3.�Ta��sx�z�T� U�=�|�j�Trh7���T�?��t���>���T� �8�F��Ut,F�UsLF��U��P��P��,Uap�
�O4Ud�Ul��uLO��DUa�Ue<VipVo��pWr��s��tXWz�U��VňS��R�Up�R���UaH`	P#l
z|`���U� V��V�HW��ij@i�Ua�g�Ul|n��~
dx�j�8k�8sV�4rHr��V�T|	0�p�rx�s��tx�4Vd3
gTVndVs4�����ddtH���ܰk���p�Vs�
	D�
���Vt��	ȯl�Uv����h".�%n�!rLt�V� �	�Vr�Vsd����V�H�1�����V�d������Vt`���m0Wpp���Wa�XeL�oĠu8W�T������dR�X��0�	��g�zL�.����t�c��d��g�Wk�Wl�Xm0�n�Xp�Xr`YstYt�Yu�Yv�Yz.�	���We�WÈ	�k���W����0�aXedXi��o�W�xX�H��X�������
���tXk<Xm� B!��$X�x^�L ��\Xg�h�m�n��r,X�8,�l*#�1	`�	d82��lX�D?��l�a�e �u��j��p�ix�o�s����e�X�X��v�����XY�؈��	�XdYh$Yi� k0YmLYrY�����X���(���Y��]��D..`��ln�n��8Y�����@Y�������4�i$��D�����Ԝa��e��iX�#8��Yn������i�o�����#�����Ye��|�dZeZgDZhXZj��
kdZl�Znܸp�Zr,[t8[v�#��Zt������e��iԷ����f���,Zm�*st���4Ze��#\���PZi�����Ð�Ŵ����b�Ze��k�Zr�Z�dtZk����Ze���Zs��0��Zt@BL���Z�D�	�p7X�m[n��z�0���Ze �i[��:��2�����H]�k�Z�� [eDg��<�aP[e|�i�[��h.��k@	s��t�j	�b�<njr�<tth��h[�tz��z�[d�z���[ahw�[lw���[a�[l\oD\r������a�e4�o(�uT�ì�	��l�����[�$\�����8\i�[�L�\kG
	l�r���0\n��la�i4�oL�����B	e�\��	\\p�s$���l\��\������\lh�n]r(]uD]y|\�d�.�\�p�	�\l<����\�P�8X����\�
���e0��	,�l�!���a�iL��8T�*��]a *]b�3	��l\]r03.4]ah]e�]i�]ô7���9t�:	Legx]l�;�,_v<�z��0B	��n�-BpF8�]r9���]�L>C^rY���]��X��]�lX���]g\M�]nM���]a�>n�>��^a��l^d�^g�^k�^m�^n(�o�p�^r_sL_t�_vH�zT��`�L^a��gx oL��\���T^e�ko��ð��d���L�a��e��y���#����^op���Dla�i�^p��ì����%l��s ����^a��#�����^a��i��o_u4���tP�����i@�z`��$�ax_i�_l`_Ô(_l���@_a�B���X_�p_�K	4�`������a��`!��g��j4�r�_v|zLD���������_�<C����e��i�_����h���nb nehI	�_kt�n�P����
e,`o*
��P<oD`�8)
��`b<t `b�`g��t *
��<`���t$�.P`i�:
��X`z�t	d`s�t��p`��t��|`ä�jH��`k��p�`s@����`e<aiLal�arp-va�0��`p�`t�<�`�����	e��	�`p0as����`��a��It Ial|���$ae���et�8���$�ahae��i��uD	�0�#��T����paol�	xar��P����aa�ae-i8�o�a�L���3���-�(��`��ad,À�����e�a�4b��4��P��Z.�Ybr����b�<�	@btbÔ���$b�h�Lbr��,	��		Tbr	��\b��b�	���Nghb�@	xbg����ba�b�	et�.���bh�	nt����b�ph		�dt�b�`\	���b�t�	`�	���bel�	�bk,cm�k	��ce�cy\cì�	_̡	��$cs�	ThcsL�	��8ce��		Dcv}	��Pc���	tp
	�^d�cg�cl
.pcade�c�xdń
.�cax
��
P�
���ca�
p�
���ca�op
8�crdt�
���c�dd�42��
y�	
	�f0dltNm0
�
dm�

��$deHdhdNllNoL
BX'X
��Pdv�
8Xdl�
��
��pd�8'

�Rbvd�dk�dlX?p,er�Ss@ev(Tx�R�0D
����
iP?oX��xN
#ej�N
���da��	�dr�O
���d�O
��ee�d�H#�
�,�
��e�\�
���
i�So e��
��Te�
�Tc�?hhenxer�ev(�
���go�kuh�
��&ahWi�g�8��@aX�\	LXb�Xd�Xk��
l�en\@r�evtYz)����a8F��(YeDYi�e�tG�xF���e�LY�,Pfrxfs�P���e��z��g���Ye�Yilfs4f��h���Y��Y�����pb�Kn،	Dfg�n��Tf��n��`f�\p���Ye�\	i��#<����foL��fj,J���fa����fb��	�fr0����f��~���l�fngo@gr@�s�f��k	����e�H̶��gc��gn4gr`�,al����(gaL����e�3i�Bodg���tgs ���Xg�l��@����sH�P7dhghjXhkdhlxhn�hpH�
r�hs�ht$�u�hz�����gaPib|ie�kf�=g(liDmjPmklml�mo�np�nsdotxou pz�hÀn�D���pglD�p�	����hkLhtX�
_D�	,hl8���4h�����@h�@�����c���phe�
���.X�y�������t���X����	�hbid$ig@�k�2l�Hp0ir(����h�0k�$m��m��m��o��o��t�k4����h�\�i�x���iih����Hy0����qu������<i�\���dillirDi�<������'Ltiah{ejg�{i0jk\jl�jm�jn�jo�jp�js�juPLxkz�i�(�#
���ii
�il
���i��I���t�it����iejyt<.je�
_d��(jc@jh��_���Hjm��Pje�wh��l��
s�wt4�v�jŤ_�	�ja����j�H���wb�-l`���d��zXj@r�.g�q���jo ���jr�"���t'j���'��krĉj@)��k.Xkn`kt8(	kgpkkMl�kp|<r�ks�kv�køKs�4s��s*��hkh��
n|.s�-1�-���k��-���k�d/��dfa�kz0.�fa�f�	����k��2D.�kk�2���ke�21�4��t�l lrl�6�$6��l�D6�;9d\lfdlg`�
h4�ltlm�ln�lp�ls�lvmz�>t?��${a��Ì@��i�@P�@���la�:dPf$PtDEPTE���la�F���$��F���lô������l��I���l�J	x'.J���l��I��mg�d	m0�o�l�`��J	H;r�M�HM��0m�M��8m��M��hQlD�rlT����S��`m�\e�d��xmta�mk�ml�mr��s�e��4�d�mi�mlf	df	$ntw,4r	�mr\{��z	�mg$nk0nl�;m�n�rXnspx	�{��n��{��n�P|��@|�~P8ne�~��@nz�~��Lns���	dnr�ns�nzd��ln���,�nr,u�t�,����@�l`Vr�ns��������.op��r�Wsoz�nÀ����V��n�o���������`[e(il�0oePo�����(on����\
�<om���Do�̣�H���\or0����pon�or�ot��NP����D��	��j|=rh�
t�	$:d�ol��s��t�z����P'd�oeԭ�	Ht�
q���o�й���o�L�	�os\���p�t���p�x�tܽ,pd�g�pj�pn�ppt�r��s�puh���4pa�dXqeLff�rg�ri�sk�sl�sn�so�tp,ur@usTvt�vu@wzqðt�D�	�b�
k����p������p�D����pt��.Ha��.@��D�_��	�pb�g<qlHqr���q�dr�hs�,t�Ht�w�P�.��l������arv0�ܡ
d�qg�ql�dm�qn�qp�qrrs(rt4rv|�z��	`,����qe��y�����m�I	v�
�8�.�qe���4�.�di��
o���|��qj�����qeT����	����rsrzH����	���� rnd�����\�<rl|rr���Dre��Ü�	Trg8k،l�rp��� �	L���\n|�r<�	\fd1f�g�rmsnsp0sr<ssLszT�P����ra�ri�rô������r���@�����f�I	g��t�`���sa�P���(saH����gt�gz����#���Tsa�	\sk|sr�sz����Að
�
�
	�sl
���s�4
����l<�rX�v�s��
��L�a<
�H
�st�
���siT
<Nbtk�Nlxip tr�Ns

.�Nox
���ig4
	Oc<tr�
���Fit
	�lhtnttr�jz�
8�
��`tzl
.�dL�e��v�t�H���
���t��
	P�r ks�
���t�h
�c�tk�
���tet�l��ruÌ
�,
�<
	�tr
���t��
�
	uc
��u��
�� u�( 
t
��8uk��p�ktXuz!
f	�ua�ue�lf�li,vlpPo4vu�u��
�0!
��!
���Z�v��l��l��l����\"
�$"
��ug�uk�ul�um�unt�	rvs�"
��G
��"
���ul�"
�J
��K
��#
��#
�vk$vn�O
�\W
9d%
�P&
��%
<vdpvt�%
��Dva�ve�vi�vr�v�x'
��'
	��
t�'
��xv��v��*
�T*
���vi)
�vr<+
�P+
	�vgļ
��,
�vl|0
.��e�3
X2
�vnwtp4
.�ma�4
	��
rws�6
��<
��4oe���t;
�$wk;
.4wa�we�wixs<rtx�dx�H
(H
��dw��E
��lw��C
�xwl�wr�ws�J
���
k|�ŴK
��șt������wi��l�R
��wl�ws\U
��T�t`]
.�t�~à!��\
��wl0@
���w��^
.܃e,xi��9�_
�$xr��H����8x��e
��@xŘe
�Lxr�]
��Xx��
��r�xsPf
��pxe�xo�xr0yt�yŸ�
eĦ
���x��
���x�8�
���xt|�
��Dl��
�xl��
P�
���xa$�i�Koy�p�
��t��,��ȥ
�p�
��ys$�
ys��
��$yexyi�yo�yu`y�H�
���
	Lys��
��Ty�о
���t��
lyn$�
$�
�
�����
	�yfP�
���y��yg|fn$r`t4��x�o�C�yjzr<�szt�N���o�Z���-i�-o�øb��H�i`�o���h�d�gPzl\zr��slzt��vнz�o���e�x��h�ex�Ä|����e�i�z�Ȃ�t}��������T�rP�zԟC�zl����z�����zô����zfd��zs�����zaD{e({�ȟH@��Ч�zl���{aL�	{r����{��{������a��8{rT{tķy���ܿ\{s�{tp���d{eh�	t{r���C��ԇsHC�{bпd�Ig�{k�lhot��
u�B���{a�bT|e�}i�}k�}oP�
p~s0�t|�}�pHt�U	h�l4|t�U��|�,}��}�\~�p~�@a
	�`��,|e�e��\8gt|iry�c@|g�|k�|l�|m�Kr�|t�f��H�g��||n�k��h���|o�|s�|v\l	�l	m��|b�|e�m	8n�m�|l�|����a�|e �o<}��g�rl(k	r@���T.@}e�ri�l`}t��	}r��ĉ8}m$��0���L}�����T}������l}����}g�}nNzt}Ì���}�������}��t0���4�rt�dNklNl�Nsг	�Ns�	(za�e8����}��~�Ի.��aD~i8&
t��z4~�����t���(~��������ukT~s��9��	�Ot�	��	h~g��s��z�����	�~z��ĉ.a$�b��cT�d��e��f��gȨh��i��k@�l��m��o(�pL�r��s��t��u�v$�wh�y�z0��H��̉`bxc�d(Qe��f��g��ìj�k�l�m8�nX�pH�rl�sĈt��u܊v�|	w�x�	�H����l��صm����le��h$�l�sء_̠����`�.��ԣ<��g�PlHsh����a�e0�i`�ol�u��8a,��	�l����������T�Ol �mt�tT��(�d@�g ����a̮
Uh�
��L�tl�T�t,�.|�t@����	�6l@���\�a�����È���d8������H�����Ð�����t��4�d �r���؀a,�eh�k lp	r��tP��́tl�����(������TSm|���T.�	8�rH���D��t��T������`�u�e����.��l����|�a��e�ti�o�d�D���lȁs v4t����~.�r�Ёr�sl4	g8�p����a��fL�g��l��mĂut��B���$��t��,���E.���FD�dF��P�a4	\�zd��h�������8�������	��l@8L�����X�����8!�Vd�%�\s�#Ђs$��܂a��u(1�r�t�1#�1���a�1��1�dh�gp�l02��
$�a��d��g��i�k�n �o�s �y��ä2t3�|�a3�l�	,5	��rDXs$5������(��=�� Vgp�	t<A��l��	���	��̃o�B��ԃr�E��E���ylG�zH)�t�H>,K.�a0�u0R[tXZp8�d��p��r��s�Z��@�a؄i��o�p�uĄ�@�	�[�x�a\.��a,����.��t$\����z]	�v	l$]�����HS�$���bGl�n�+p,c��x+a�d�+kgj��t�i�r0x	tpj�kD..��j��l��n��r`k��,�ah�bp�c@ZeІi�k�o�r,�s0�t�����#0l���em���tam���x	n��ydm�n�
Xp8xp������o���n$�o,�vȅ�Lo	ԅg4�h<�jL�lX�r`�vxo����������\���pn�1���Q`q8Pq��D�l�q�$��\���r��	T.��a�	c�	h��i��o��t�	u�Y��rn@t�P��y, y	��l,{y�zȆg�l�s�?y|Ol�|���k�}�(l�}����aP�ep�i��o0�ø���}t�}��(��������ć�@~�~H�l�P_�~��\�g�~d�n�|�v�Q���hR	��nDEv�Re�R�����dSt�n��\�.�i�o��̇sdo�po����(������Z�h��8�����m���$�eD�oP���PlH����e��eВ���d�t��z����X�j|�
t���|�l�-s �.��a8��
	d�k�l\	n �rt�����at�e��i�k4�o�
	u��yH���Cȡ����|����X����[k�����a�y�c��	,�lh�rĤ��8�����l��x��t����y`�kl��g��lT	s�����l��	\�l��r��yT.,�d��et	t��]dЉn��r��
	t�
��.���܉a�è20Ԡ����k̛	�nD����������dܣtt�$�l`�m�&s�2tl��
t�L�eH���T�b��	<�s�C��z`���.�
	a���HQ`������
	a����r��d�Ȋt����Њa��` ����i@�	�`b\�cȋg��j��l`�m��nؐp�r@�s��t�������l��L��Ľ�������0����4���T�a��e��h��s���4�����x�������@�C���������<��@����.���@���T.T�a�g�y��.��.D�a0�	b8�	c�e�f�g��	h�i �j��	k`0l(�m<�nP�od�p��	rl�st�t|�uh0v��z��è�	
 c.t�c|�d��i�k<�lD�mL�n\�sh�z�3W4W��y����b��d��h��kȌmЌn،r��y��y(�yP�y����yd��<5���b�i$�o,�r4�t�À�0�����������	T���5y�Wp�Wh6W������T�t�8�H9�8�8p�b��l��nԍt �	vL���x����\�������0����|~e������$:��ȍØ����WB� ��
�����a�o�
���@ ��D�t#��@	��#��0��X�W<�	H�s��W H��H��H�������K��0�����t�T	b�c�	r�����a��dx�
e��l �m8�y,��P	���.�p$���ah�o��v\���%��|���o������0�L�a�	D�s�!���X�i��u���'T��fďm�>n8�rp�sT'��	t�a��e�i�k(�sD�t��y̏�T	��'�t	t�)�� ��W�<������������0hkl�rp�s 3�����,3���è2���r�4t 5��8@z�6TP�kX�l6��4�e�6��6t�8	��d��lbn8�r��t�(u�Lz�8.`�aP�e̐i���
�<����d�;8��r:�������� =	$;	m�@��(oh��������C����8C��d@�spC��	�ap�d�e��i�s��t�uT���X�hG�4H	���\H��H��������xJ��h�a��e�J��J��l�N��p�X=gܑl�n�r�X����a8�e��iĒo�u �ð���X%�~�|����yY��X����i,Y�8Y	�lHY����p��В�t��YTP�l�mX�s`�t�Yt�Y� Z�4Z�@Z	h�l��r��tXZ��T.���T�e�Z��Z��z[�([����t�Z��t<[Cܒk@��h[�5s�[��h�k�t0]���u�X��]	�n$b	p�sLb��$���e�t_��`�eh�i��s��v0�Ðe�Xd�df<e	rT20:
	t�n(:
��|���9
�����v��h�a�e�i�k`�lx�o��r��t̔u�vԓ��w����
�����,y��x�lP�m`{��s�
PH.l|���a@���0̛	(�t��v��
��0��8�
v��
L�g�|��T�a�	��}t�|p�rd�
���
��kh����a��������������\�t�ĔkH�8Ā��ؔt��	�r��0�	��t`�����l��������ha|lt�rh�ð��d�<�l����D�e,�	P�rH���\�������a��iTho����XUo\$��f������T��Ø�����iȕs�3.�ha�ŀ
_�
��ؕr�
�r�F���o,F���sLF�����t�R�dn�R��$�al�e���O4�lLO��H�a� 	rh��Td�g�OlԤs4U	�n,T����������a�c(�e4�fd�g|�kėl@�mp�n��ơp�r�s��t`�u��v �z���d�tWpP�r8� P�����a�c �z�����P����d���Tkf�l��}����D�ed�L�sX���X�e\�r�Cze��	���Ie�r��t4	y�����r	��l�rH�����������ehagTh�j�l��nro��rؙs�t�u��v8�y��z���@�� �td*	k4�m��r��t��L ��,�bh�e��g��h$Ri��k(�m0�n��
rloz���h �`kx�tHey� D!�����8,Q�"��"�����l"����ô$.Șe�$�HV���И�L���ؘ��W�$.�b8-����e�1��1	��a\�ed�g\Rh��j�9l��zL��82�����1���a��c�3V
���HVL
|V��l���3����et�ô�,��
��g`�|���nd�����et
8X���ę��7��̙��7����o
Ä9� ;��T. �e��h0�i(soD
ð��<�"ll�nx<n�<>
D?���b�e��l`�sh�u��ÐL_�L`tN���te��g�n`�y�U��U��d�U����e�XP�E.�b�h�i(�j0�k8�m@�nH�rP�tX�v�èW����y\:9�
	d�	8�r�X������9��		PH.��	9
9�
9��9�09p39 
9�^.�.	a��e|��p
�|`��t���_��_	��l��r<`�,bP�l��p�d.������j���ueԻr����t_�t���tps�kD�lD/	p�s��
�aX�cwd��el�fx�i��kНl�oX�s��z���,���t���Elx�y��P�ct�e��i`ytlyl�l�yt�y��n���
����c�z��cԜg�	j�l8�mX�v<.�8e�{��Ȝy�|��l9k�Ű	�fqk������}y�}���t�}	�r�}�� ��`}��,�������D��$���L��hw�l���d�a��0xdL�l��n ���|>aPdPg������lD������v�����x�h��p���S�\e>���؝t��k�0	sAx�		�Xd8�f�i0Ym@�nH�rP�s� t� v�����8����������Q,�����t(�z����	��g��lp�.<�t���$��0������t��@�����ll�����e��������b��Оb����ܞeX�p��sПt�zP�Ä���8(������f��L �g���(�id�Ŭ�	4�s���D��t9
L
�
��\��^Y̗	p�iܗ��x���Q
0����v������t�P���P�����zd�����ȟa�e��t���r@������kĝ.��aT�eDu,�è����l���� ������G
	����<�lTv���D�ld�m��.p�e����d
�t��x�l����l��n�����a�e,�i��oL�tРä��\��D�Pp���Ƞ�����e��d7��ܠt�!�e,������}<�
���a̰ �k<�m �yĶ�X���D�ix�#8�X�g��	ġcܡd(�gp�h��j��k(�l$�nܤp�rp�sl�t��v�z���Gt��.��u�����sp���d��D���С�t�����b�i�n������������X���� �eD�Ø��P�s����8�������%s���Fn�}s��tt���\�e�}s\���T.��e�{iL�j�Tdjg���~sXCu������aܢe�i̢�����Ў���P�lr��	�kl��t�.�e������n �s������	T.T�b\�e��m�n�'tT�v���ȣ��
\�.�i��k|�l�nT+t�.��e��m�\�X�ib����e���������y�������������أa8�b@�d\�fd�g��it�j��k�n'r|�tH��d�k����e��y��zd
W�WL�����l�����dWlW���W4)	+
P�	�(.,�e0$P$����aȤs��ô$e�$������%P$,|0p�&��Фi�à'���������/�0��t�ct+d��hP�i��m��n̦r�it<vt�z<��H�Ť2���+�P>��=H�g\�n�F��j��l(G��`�a��	e�k��o\�sĥz���K	�s�H�����@��hQt�jL�k��v�Q.��a�eD�iH�o�=	s8�u��X���o��R��T��8��Xl�����S�d�	mH�Ȇ�����O
�� �ØV�,�n0W���nlZ�<Y��P���Z �Z��d�aP�e<k��lpar��v�
y��a��
�Dg��T.��b�?	e��f��h$jk��n�Mr��sĈtԈv8o��nԦk�n��ܦa4�eL�ix�o��s�@	u ���[_xo	�r�o����<p��o,�lD�mXq� sT`�d�@	nh�zs�Lt��t��tp�lput�u.��aP�	`�ph������w����i4�lX�r|�s��Ð�����̧�L�(�kԧ�����������P�Q	�rp����X���ä���4�o(�uD� ������-�Ь��,�e��i4�o<�uH��x�L���t�t�!���alku��ô�����r0#���K�\Mx�d�i�nM����a,�eD�i|�o���0Q��X��X���z�X	�s\X����lX���Ød�c$�d8�l�n�hn<�aX�ch�l`���o��o��`�a\v��ut�i��
.{	��i�]���������a0�cd�d��e�f�g\E	h$�k<�l��mجn8�oĮp�r,�s��t@�v��zث�$��al�b��c �d�eH{gp�h�j��k0olP�m�oLop�C	r�sD�t�v�zP�Ü����x�������І��PaL��܇x�l������e̪i�s������	��l������h��P�Īk�R̈.تep����H�����Ď.�a����z���Зe���`���(�cD�eP�lX	.Й�H�TD�n\���X�a��e� o�W
u���`�L^a��i��nx oL���t� �#����g��r�#���L�īg����̫�D��d���${a�e�o,l�@�`���@�t���������Ø����s(���0�ih�l��	y���P�s|�tp���X�e��u������%�	��s$������p�����i��m��8�_������k�p ���
Ȭa<�d��f�g<j$�p��t�"vz �À�������ad�e��o<��8�,��	(�r���0��x������������P�j��X�l��	���	p�p�	����s@�	8��	���	��c�������8����o�u�����T�	t��حr�����	�r�������T�r��������t���H�n(�,�n��
T$a�$eX�f�$g��kp%l��m�%od�	pt��d����$���������i��
�h�����a�è�'p���iЮr������a����D�.�b�h��n�r�À�Q��Q��Q����4�h'o��j��uP��� �aP�k\�op�th�z�
�����H�o\�xip�.PTa��k���$st�x�s�����o��'jȯlدn�����a�h �o��`��$�a�_l���	�p������,��	�	���Z	e0��� sP�s�e���X�����4��`!	��gp�j|�n��rL�tt�v�z�%���� 0����|7���ta��e�i �o8us4�tİ�@��8��l�r�s@8�����u�X��,����l�	l8��8�89�:�n�s�:��;��;�l�<e0=��r�<���Z�0A���m��	�p���
	(za̴eP�
��`��H���p��s@���|�e�N	i�l��r�	vԱ�0�j��|�����s��	��s���ȱ�8���$�a��i��u�������e�i�oL�Ì������c�cl�kx�p��tزz���$�a�e��i��oh�u4���� ���d�l�)���Ea��	o��Ä*	�v	l�*���������.����fl4Pt4�����\4�����84��̲m�V�tԘào��@�a�eHpH�l�xT�x����`w	X�h���4��$��p�� {�,{D�s8{��L�a��t�|�.d�ü���p�z��|�sl���XeܳoȳÀ���m���X�	��n\������|�t`�Գl4p#�����a0�t\�z<�	�s������xv��v��������$���y��<�r���D����)P�����
mx�r����		d-s�	����������e���� 	�g8'
�Rb�c,�dD�gP�ih�k�l̶n�0o��p��rX�s�t�1v�/
��z(/
.�i�,
���s�/
�x0
.�0
�� �a<�z�4
P�8
����dA
��
npH
T��r0D
��\�i��lh/mеo�t��ìE
�����0�n�J
����e����J
����	�K
> K
����aܵl�J
��l��M
���a���lM
8xM
����`Q
�����p���N
��H�cd�d��t,�v��<R
��Q
4�nR
��<�ad�c\�s�R
@S
���a�[
	��
���%�\
	|�rL�s]
���������\
����i��ä]
n�]
8lo
��o
��	Ķa�c �e4�g@�i�8nL�ox�t��z�	R�t
.��ohs
���s$v
��u
�lPx
�4x
��,�ey
T�dz�z
l�s�	��|
X�d��g��l��n}
��`�aܷed�i��À�ň[�<}
���	��pxyp}
	�lԷr�}
�������p���}
��}
T�l��m�n�s�}
tP~
��	�\~
��~
��~
	�k0�rD�t�	��T.P�	n\�	��<�e
�
��P�z�~
X�s(
���	e��	��x��h�
�؄
�
��	��a�Sc�e��i0�o<�pD�sP�tиÔ�
e��
��ȸ�X������
F�lp�
�
T+k�n�r �s�
t<�
tT�
���
.��
(�ld�
j��
�/z���Ԉ
��
,�c��gĹkйl�m��n�r��t\�
��`�a(�c\�e�ft�g|�i��kغt��	v�����
t�
��\�kP�
�� Ya�Elp�
�<G��
	4�c�l,�
������ ������x�n`�
���a<�hP�s��
Č
����Ԍ
.D��h�
Tl�c,esԍ
���
jĐ
�d�e��hL�l��m��n`�
�l�
���
t�'������b��
��m��uȚ
��Ⱥad�e��i�u4��0���$��
�ě
������
��L�bT�n��<�
	�rL�
��(�����л���ܛ
y�
y0�
��
\�jx�m`�t8�tĜ
�ܜ
	��l��r�
��T.��k�
0�
��
��k��z@�_�Z	Ȼv��
��
ܻr�s�t��,������z���
� �
	�r���H���(����
PpPop��P�
��<�zD�
��L�s��t��z���
���l�`�
�P�
|�l�
����o,X	y�
t,�
.��e�l�t��ô�
�����b
�X�
��̼��
��Լ�0�
�<�
�b0�d8�l@�n(�uh�
���aP�eh�i��o��u����o
�ԩ
���
���
H�l��
��\fX�
\�nx�p��
tH�
e �
�����������̬
�x�
��l�
��
��s�
	
�Tc�f�i�k̾l �nyp8�rĿs�t|�v��z|����
��<�	r��
�s4�u�
���a<�e`�i���@�
���
�)lP�mX�t��
I��
�L�
��dx�m��s��v��
I�
.��z0�
�@�
I��
	,�sl�
����������
t<�
P�nX�
����a��o����
t�
����d�
���
�m��
	Ī.��
����(�
����(�
�dh�
��,�a��e��ip����H�
��P�z��
	X�s��
��d��t����
T�g�ml�
�x�
�����$�
���@�
���
��r��
����o�s��D�
	Hv��
��ؿ���
T�d �l(�n�
���aP�	iD��
t��
��
���
.,	0�rP�
��8��T���C`�l��\���h�n,�
��p��x#,����e��s���������.���\		�kd�lp�n|�r�s�t��v����a�e<�iH�o`u�ü�����D�	�D��l,�r�����4�dt�,#��".P�e���X�y)��<tT4��|�a�d��e��g��o����[	�P7�(6��s�=�<5������@�?���s�A����l�t��vD	�nX�
P��iE���y,P		|�a��bt�d�k	e�\	kh�p��r��sX�ØP����HM��R��������W���XoXU��h�r�YlFh�l�g��	T.��i��j$6l��nYs0{tx�z���(l.Dm8�h�����@w�\p����z�������a��d\�id�j|�k��n��r��s�~���ap�e�hD�i\�k��l��oH�r��s,�v�è�j̀.p�ø��������*k��$���d8�����a��e��k��yL:��PSl�9��l�j ��H���'cl���	TAl$�r@�t0���������P����� ���������j4�o�R�:
k����mT�op(vx�XH��d���\�y��d�g��k��l��r�	\rh�����|������ė���)o*r�������a����x��������l�	�Ag��k����Ms���������Ȩ����d����$�eج��,�g��8�nL�	l2g�����cl<-�$�h�s�+t@���p�a_
~�����y����l��rTh�<�
��m������o��tغ��P_	eD_	ôe�̾,Ľ	��l�rd��p�����8������	�fn�fv`0z���_	f`�mh�zL���4�e��i��o|��t��'�	(hm ���p������@�����n,PoD����chhd��f�.gd�j��sPrt�n����	��b����trp��	��g�r����T.�Cl�z��������� ��x�H�8�x����@�a��e��i�ot�u ����H��l�iLt�m��p��s��t � ����a�C
.t�P��bT#����z�"����s�$����$P%�������.)����i8(	�d8�p@�s(��������������-�d/��dfad�zH0
T0��P��0.X�������p�s�=��x�c�;��c��u��zT�@I��s$J��I����m�K|)l��m�K����e�J	��m�K��aU�a����la	�bD�cP�f��gd�j��m��p��t��v8-
��a��<�ehb�\�e����c�p�e,C
�Tj#Th��x�o؄
��m����a��
��p����e��o��Äp��p������p���<q����o�r�����r�����4r	��b �d@�zLs�(s���e0�z��
P4z.@z��8�a��	�g����L��\���L�eX���d�s�	��sܽ��dh���
��a��i`�k �mL�pH�r��s��t��z�������%���4��@����`������a<���h�
��
���e�	�z�
��<ni�
_4
	,�nt
	�jz�
��`[e��i4od�rl
�!
���l�!
Pl��t
��x�z�%
����i o(r�'u�t;
�l�	kt�n|Rs��t;
.��a8�fL�idXkx�o�s�è?
��x�l�?
��g�pp0@
�����������M
��Vg`R
�lR
��$��xR
��,��U�R
�D�c�l`�s\U
����sDY
���XdPX
�l�l��r�Z
B�Z
����n�\
� �n�ts�g
.��a�f
��d��nPf
����at�e�iT�k\�r��u4���g
��t
���e��hu
#�
(jL�
���a`��~
	�rD~
��(�����4��@���������
Cl�r��
��T���
��
(�gT�k��l��r8�
��L�s���ğ
��,�	À�
���
������
����e���L�
	��k��r(�
t��
��l��
��TKdȽ
��.Խ
����aT�
�m,�n,�orо
���
	H|r��
	h�t��
��
g(n�
��L�al��p�
���������
.��e��i�
��|�z��
��s�
ex�
��̦r0�
	��r��
	0�t�a<�c0�e�b	g\�k��l��n��o��p��r �sH�t�.|�dxD�|B��rD.$�o�
��0�s�B���H�����P��$��h�s��p�a0��|�t'����i��o���()�x�	��'������).�)��rL��*����r�+@�kH�l,����a4/�+d��lL�nX�pp/��	�a,e��i��o��st,t��zp��T,�$0.D�y@0��0	�l��t�0��`��@�����L,�|,�d1��<
lP2��	k�s3��4l,z8<
��6���g�l�6.��a��ä7��T�.47���rT7�������$���O
��8��p:_�:�bd�d�,k4�n�z;��,�ax�i��o��r����`�0>���g>l�n�>��>��l�?	�dXg	p�<�����<@�H@�����,@������C	��r<�s��t\�Z����f�b��P^bh	i"jt/k"n�/o�/p�r"t�/v���h		�gh�k��ln��r��s��t<Mv��z������ o��\�äp���t�o��t�e�qhq����o�x��h�e���xy��1�8z���t�|��T�e�k���t}���������
�����������������#D�s�����i����h�
T����0�y0q��8�kX�#x���P�a���$���
X�b��d��h��j�kD�mX�n|�r��t��vX��H�������#$����o�u#�����o@ ��x-a���HV�h ������ #� ����a,�i4�o ��X!�h!�����!#<�#�"#�"��<�aTl	#�#��P�ah��t#��l&�H�#�0��t�a���4r��0�����p3����h���X�
��3������<#�<����a��è=��=������|����a(�e�Làc��t�B���eH�Ť}�<} �r�	(za|�e|��8���4����ĉ.|�a��e��i��o�u������̉@�	�����������������������������	��`!	8'
�
	\		,P	�P��������$�a<�e��i��ot����|�eX*t`#4�rL�z��#X�iL�#t��,`�b���h�������e��j��l��n��s��#P�#�)@d�C	�h	�	��ĉ.H�a\�b$�c�dLe�4f�6g�:h�;iMj�Mk�Sl�Vm]nao��p��r��sH�t��u|�v|�yt�z(��d�̉��a�bT�c��d(Qe �fD�gd�i��j@�k��l�m��n��p8�r��s��t��uH�vx�x�_y��z����g
.X�#��d`�����a���tL���������,���_�������d4�sH�����a��e��i�l0�r<�ul�� ��`���,�z�����@�a��b�!rp�	H�rЮt����\��X��$���x�e|il\���lؕ\Nd��g<�m��n(�.��a�����@��L������T���`�a�d��t��p��|���������h��t����aH��4���(�iș4e
j|�j����H�a��c��e��h�i,�k\���Pt�a����|�sܜ�����t�t��.��a��i��o�o9���g|v�Н���k��s��t���z������dĞ�О�rܞ�� �aH�i\��x��p�@�t��e���T��l��(-�<��Pg��l��r�th���t�a �e@�m��r��u�����������������Y(����dh����a �el�����������������,��h����.��8�ch"�h��H��P�H�.8���P�i��	\�sx�z��Q|���uQ�,�p{.PRs�����
b��e$I	h��n��r�t����r������eP���d��r������,��l��Ļ��|
u���k�����i��l�p{.d�kt�l@���0�a��d��e��i�y���Ծ�d�����l�o��,��	��c��ntX�������t�Q��#������e��i��T��n���\������l<����a����nh�.T.,�a8�nH�oT�r�8�8�	$�tx��'���	@�n�H��.��\�o��z����t�����������i��n��u|�Ü�ԧy|� �t�4d��u������a�e(�ip�k��o��t��z���U$��	P�r������P��ys��tn�T�
����4�����<��̛	H�r@���T��|�����`��4�8`�l���bp��d���l��nH�����e0�t���T���td�����a��e�s�Ô��P�_�	��n������@��}(��� �a��(�r���4�a��k��l��n8�rh�tp	u$��<���	h�p ���p��������i|�Ä������s�����2i��o�Du��è������������	  b�i4������������H�|t�U�	�zH�������F����0�e�iP�o�R�H�p�m@�Ts����\�u`�lt�b�p	g��k��p���|�a�[bL�j\�k��l��o �Ð}�����ut�� �a��ì,�������LFXF�����,��F�����4	�zd������PH.�4�i���@�a0�i��.����d�k��t���p�a��e������������D�tlC��g�l|�����T��@����vpT�m�#��#��b�#d@�iH�kT�l$n$����a��b�il�m(Xo���4�ń	'%���k8%.`�a(%�'���l'	h�l��r '��t������%��%�<X�d'�l'�����T'��4�btc
e��h,gn̦r����'�*n+����e+	��hL,���i
eX,��d0�k@l<�mL�n|Ip�E	P�E	��(�o�,t�,��,��D�ahf�g�j�	y�.��d�b��e��i��j�
n��r��t�.��.��t�Q	��M	y�`	y b	y�10Wlh�t02����a4�cp�d�e��g�i �kX�s��y �z��$5��PX��6.��a��eH�ih�z���\���5���s�	��7�@�k�7��7��T���	�P8��D..��a�j��z�8�<8��t��u�9P�=�=����a��e��y���L>td>�����,Y��>#��l?��@�@.��e<A�'l�m�B��B���aDH�`X
lXH��,��H.Uex�o8��lG��D�z@Z
>(�	��d�b4�	�l�m,K.
D..��a�b��e��h��i��k��n�)o��r�s�t$��K	��g��l4_n��p��r<L���ca<
�|
�\�6h���t�L8�iL�k4)lp�s�L�������������p����������D�a`�hh�k��6�6�Myx�.�
��M_|�s8O'hYlQy$Q_P�d��n��rXQ��
�lQ�:r
y��y�Q
��r�Q��0yR'R�DR�	�
��
���a4T��a\�fp�l|�mX�p��sP�ÌT�����8U�tV��H��DV��d�ôV���
��W����zXZD..�Z����a��p��r���]	�Xk$]�����g���ihg�tg��n�g����e��i��o��h����m���x	nT�ykp�n(�t`k��(�ax�c�t��z���dm.�x	.�x	a�x	by	ny	r(y	t�x	��r����oxo�������@�k�Zlh[p��s��t(�z$"
���P��e��o������z|�������c�alr�������������x-b�j�-s@�t �.�a��e�tX����T���gx�rp���H������������^'���p�rh���,rb�����m����<t����t�wy�����b��i��n�'�[
y8�y����s|�������yĝ��nԝ���a0�eT��D�td�(�l�a
��T�e��	<�t$���H��h��p��,��`��8�|�d��j��l�[rt���
x�ap�e��iP�k�l(�o4�rH�t`�y��p���`�tD�PX�����a�
tD�Q��	��d�/l4�rP�td�vĤ�����������T��Х`\d�i��k��y �aĦb��v0�p<���H�a`�
#��y\�il��g��l���(]k��lx����e��	��g��r��p��y,�d�{	k�1t�v���n\�	^r��v��n������e��
��
������
����L�
��\�.t��zH���4
	a �d���@�r�	� r���^at�u_���
t<���|�i����d _k��l��m�n��t8_P��������������8���T_a�Uo��è���|�����ܾ������������D����������$��d�0�t����<�a`��t�B����X��$����� ���l�ø�L�j�
n������aP	e��od�_�����t����l��nL��������o@�	X�bx�cx�d�fh�g�j��kl�l��m��n�p0�rH�s`�t(�zHWì������8(��J�4r��z�����D���Te����L�r����d�l4���l�a��e��h��i��s���������n���D�X�fP������������P��X��t��������a�����l��.��a8�i�����g�����,����46t���D	rH�s�������,����X�y�`�nD���l�a��d��j����%P�	��hh������H`ei��������������l�����u\M�M����a�e�i$�����h,�i@�r�c�hn�X��8�$�d�������8�e��t�L�d��mWz@���T�a��i�o(�u<�y�����x�1�������������Ð�	��z����������|8�8��H���azl@8m��sL����l�,X����z���s�+#�� �s����.4�a��u���H9o8�8P�b��h��i��j��k��n��r��s��t��v���L���X��H�y��������oh���o�o�o��o��o$:o$�o����n�����a0�eD��0����kP�ð����(�lHpe����<��0���t�����	\�t���d����x����e��l��o0�rp��,{d���d�����e ��rܑs�������a�����gd�sp_|	��n�r��,��	����D���� ��t1���	<�t���kX	ld	n�	r��t���P�a��c�d�i<�lP�m��y��ü������e�	al��t������p:�����
����l4���G�����������$�����0�����d �m�x:m�p�:t$��(�ah�o\��|��$	a8��	\�lبn��p��s�n	v0.d�a��ÈY���8y�8��s��t�������y4���^������'��l�n(�tT'��
��ad�cp�g��i��k��o�s�t(�yH�� )��)_�)	0�r��s�)��8���W�<���������*���
cx/���?�`2��0|�z�2�2����l�3��l��tL4���	��	��v 5���a`	s`�	t�5�k6���a�8	�ad<�gL�kX�lbnd�p8�r�Lz�8.�ao���8����9��D�o09��4�
t�9�L:���O.:8l�r:��x�������$b���<����m0�z�;8��r�=�>8��r�@�� �l�o��r|B��lpB��B��C�d��8C�d�i��m�sdt��zpC���a��e��h��i�j�l,gn�(
o�t�u���������4H���bd^h�lh	r8�tt^v\H��p���X���@KtXm�;p�i�N��a�n����O�HY���^��X���Ø[��E
l�n�\��]�X.��t�vg�8_@ddt�k��l��r��s��t46zt_��	$�a0�e�iXZo�et�eu�z�Ðe�X`����n8a.���`ala������a����o���a`�b��b����a$b	��g�r�sLb����<���b�dd,Zk�b���a��(�c��������d�Xd(�r�i	
Ī.x�b��g��h��i��j��k��n�r�s��t�v����iW j(j�����4jW(�
W<j�DjWLjWTjW�
�\j��a��|julj�����jW�jW�WWXo.H�	a�`i(�Øp���r�p����8���t��Jn�g
�|a�uD�d��lv��P�a�Di4\l�r�u����v����ax��w	��l�w����������������y	d�g`�l�p�z�h��@EiT�o����Ls��s��C�rĀ�(�j�	�l�����	ap�i��s�c%d���	<�b��h��k��n`2	r��s��t��z���X�D�s�������|����%��%�
%��!D�*ЏP<�.P	a��e�i�� ��0��������F�4��h��̓��n��y8��@���a,�s��ԕ�0����e��oX�<�j��n����L�a(�e<�ld�o��r�u����hu����tl���k|�t�������H�����ð�.�oH8��t`�����.������y�	�tH�������|!���|il���l@�P\���4�ad�
oHh������P�c��X�c�l�	n��P����x�a��iTho��u���$�4�����������t=�;����e4��t�����������������dh�j|�l��n��p�r�s�t������ax�c��ep�f��g,h�i�kЙlPo(�p�sP�t(u�z�ð��|[k,���p�a(8kL��\�����a��yK�,K.��a���������aP��<��ܕs�����
o	,�g(�l0�r,��������t���<�H��,D��h".T�i,gnd
o̦r�t\�uhn�X(P�Gi�o������d�s���Л���� H��D�P
\
����a\8g�yX	��g�l�m<pHv�x�s
z�t��g����eX`k,s ŴU`	d|���
_T�`��4e`���e�H��	Tt����\����h�	tk�l`�p�rs@n����e�iL�m����������4X������T.,b�i̦rX�_p��z�Z���s�. aPepi�o�Hu��T�����	l`ng��o��hg�x���|m�����` .�eT	�d�kHt
lmnpoľ
p�s�zP �!���o,t
r��B�������!����i��"��."��ax�dH�X"	h�bTr(|ttX�d"��0�L"U��B8$��\i�#dn('�4'��|iܰk��tt���m0�o)_�(	�j`�	vp+	x'.hi|.
t�*����P*��i,oDr�v���U��_�+��.$a�.
g���,k-�����,��8�(.؟pdr��s�0�2	�1	lnpIr�/vL2	��bL�n�r�sT4_�2_\3����h�(4���b lHp4r�3.�a�eihk�op��stx�zd����48�4k�4��a\�x�6��,aDl��yH��D7�Lp|rh7��T���H����7��T�.�uX8p09�Kj�l��������e,��mX:���e�r0�����>���b=��r�y����r�?�k<�v�	s�A��$��A��0�tA�<r
�
��T�4B��\�h
xxD��ta@�o|B�|rh�
et�
������
�����
�kTE���a�D��n�F��h".,F��sLF����0Gi
�G��G��o�K\�d<�	r��t�L	�1j�L	��g�	r��s�t�v̢zHM	,�z�N����	�������N.�s�utÈP��x��O	�cd��k4lpn|p�r�s�zLO���ah#b4d@e��f�	i��kx
o��plr�s��t�uz��LżP��x�	a,T�����R��Lk(��p���Da8�ohW�tW.\aDW��dy�W�X���c�	èZ��81
z0`��_���o�`���sH`	�cr,�s|`����4	�\
��
����8b�wktlb���a(o��v�b1�c1�c n�h����z@i�ghk�l�s�x(	zm$m��`a�e�o����Lm�r�m��m	�k�m������Pn�\n�l|n��a̗	� tܗ�����y����D{����ph�3,|		p<|��	��{��	�T|	H�gd	l0�p�	r�	s�	v����@���'�T���x	r�ytT	�\	8ԃ��T.�d`�il���Da�	z`�.l?aT��t����	�D����	�x���d�Sg�	m
nP
o��
zD������	a(�p{.@�i4���
a<
�0�	���8.��	$
t���0
���.,�H
x��	`Bg��z�����t�l
k�
l�
r��sP�	u�����Td��	d����
r@�	�a�
rȤ1ؤ���
�����
�p�_|�	�
l�����
�̦���
�	g�u
l�s��v���r����(o �	4sd���@�`��m0Wp�zp���Xa�o��Ô��԰g�����k�p�t�zJm����k��.�a���d�r`��t�����,�����0�	�g�tt�RL�.a�No��s,�����`�����ܲ�D�,O����a4
b�
c�d�e|�	f�g(%	i�jdklHm`nXo ph"r�"s�$tt'u�'v�'x�'z
�d�(�d0�j�k�lP�rh�#0����a,���L�a��e������,
���������
l���
�l�x�e����
�~.d
b�
e�Qh�
k�
m�
n�
s�
v�W�l�����x��H���p
y��x
g��_�_�k	_��_X�_8�4j4R
lP����
ae�
h0s �z�
�������
���Q�����
sT�t<�9���r��.$aLe`o�<Q09�DtxD9���Xr����ln����ta�eDiXo�z$�r	�����v`��l�n�r�tdQ��t���d���{�x����i��
e�������	�!	n0v8���������e@����<r�����Pr���<��db�j�k�r�t�vT���l� �.��`���\	�h��p�p�����!	t�wx����pH
sX����ate�i<y,�l���,l��	lHr������d���0������PD���Pe�h��l 
r�vd�Xl�n�t�vD�	|�	������g�������(�������t�����e����xÔ��[dnXlzT����jl�	�cv�����.d�H�	$lH�.T.P	bte�$	h�kh�l�nT�rhs�t�vX�	Ä�	`�.�d<n�s@�	t�z�:��Q�Q�D�#�H�<8���$�a�,���������	��|����؂�t�	e8n@rHtx���H�����0���	�%	g�r�s�	��
Pa0ei�'	k<nlHohns�u������$����������,����z�	�%	l0�prs������<�������|��T.$
�	���hDtT�l`r�sh	�������L�`���D�dT�Ÿ�����8.p���|zh,p�h|�����	L~j�k�&	l��n�rs����PH.`nhT��T.�~d�t���Te����8.('	s���d4�j0ml�'	n4p��t\	�'	r�4(	kdlxp�s�(	t����Dd(�	(��pt�	��r���	�k�t�y����l8��|�����l�ps	4`d�g�ltH.��ðJ�0�����a�b�c�d�e��f�h8j�k�lm$bnLodsttDv��z`���H������ �0���8H���x��
���o�ü�nD��,�ot�f�k�m�n������r����tL ��`aetXz����h .@d�g�`i�`kPmtn|r�s�t�z�!	�(��� ��H.delm�6� ,8E�et� ��H.�e�n�E@� ,He�05	edF����DF����$!��T.� 	�r!���� �8�
,�
�l1��e0!,8!	k8nH!�P!��0k\!L
d!��D�p!��L�pŀ!L
�!��h�th��!��|g(��%	�rX*��a<�e�Ø��������\�8��������,����-T�pn8-���a�l����0���qa`���J�����/lh ��/��,��/	ti�l�t<�/��H��/r���r.0�0�$0���e�P����d�1	�a�d$nPz82����<��2��2�i�2���e����3	�k�2����4��4��n<y`5= 5.4e��(7��Hm��n�7��\o0�z�7��`�a�e��7�8	�r�v8�����Ѝ
��8���r.�kp8���t08�t�80x�
uL�
����l�����9	�rv����9B�:��9	(l�:B ;��T.d�b�e��h0�ik��n(so�Mrpcs$�tԈv�ð�Ř;��<
���$�0E�<�d�i�k�ml�nss<��0<	0d.�n�rv�������eX<��f���������8H��8�?�?,dxs�szD?��4a�be�i�fpu���4
	,A��pzLA	�sl�tlA����������B�HB�TB�r`B���ae�l�r�ÌB��PH.$b,e�h�i�k�n�r�s�tpäB�r�x��B�r.HdPiXk`sxB0�{V��0�	0�	=�	��h����:�,	�
L��l�����B`�	���=�B��B���eX���	����CyD�$eDgLkH s8�h�@C�ltnP�	sTD�)m�*	� *	��0�ha�hDU�+	��D	Td�D��\�8E��*glG��T.��m�F	�r�t,HX�G���ePE	�I.�r�H�g(K_8K	�rXK��K,�K	�r�K�����L��Lk(-	l0r8s4MtDM���	��P@kDP��HitN��Tc�d�te4gHs�P�	��	�rdQ���e�o����Q	��g�j�k�Q�������Q�X�	�$R�XRt��,��		�s�	�����O���-	�@��U��U d�U��(edWB`Z�� �s�,b��f���8f
da�c�$e�f�k��l�o�p�s��@�
�,�
�����f��f��f���a��
��g���
���%��g���z���g��i�ijD nX sd u|*zj�� a� e�!i�!oH"p� �$��k��< a���l���P z�\��k8�k��l l� mdk	t l� s� t�k��� �@!�"�`"� �8�k��k�pl�_g� l!n!p !r�l����fdk� l|�m�l_��_�l��!g̛tm����.0!k����mQ�m	8!dX!jh!nt!sXn,�n��n��`!kp�Q�n��n|!d�!l�!nh���p+e<����!l�n��@Btp��o�!k�!sHp.(�.�!t`p���!z�p���:."�H���p��"��pC4"rx;8���� "�|���("À�$q��@"lX"r�q s��s���d�"eH�g�"sxÌ��{��zp�"i�"j�"s@�	z�{��~�,�_����L1	a�"c�"ezkT#s`#z@#�@�����m#p(#t<�	H���#r����#l����#e��	�l���4#� 
pP���L#iĝ.��a�e�#k�#m�#t�#�`$�V
���
v�����#���e8����#������#�Рy��T�#nС���#a8$o$��tp`
�\�	�#l$rh����#�$$�L�_��,��	$vl����0$rH$s$�t���4�.l$e����P$�؂���xob�$k�$l�$n���	t$ah%eL&i�&n�&o('th'u%ä���n
_�����$tԤ��oa�v
�\����$t��y���D�	�$g0%rH%tp����$� &��3	�'�0���n ���(%a@%o`�n����TfȨ��`�b�%l�%m�T%l�%m�%r�%s�%tD�_p������pe,����Yk��.���t����%zĉiP����%.&n&��	�%s,����%������%�\�th���&�`����	4�k4&l<&r��_����,bTKd̰8�dh&kx&l�&n�&s<�
���a {�����l�&t��U`����{g�	t����	�(�	�&k�����&i�tز�&k, l�&s��tp�
8L�
���&��
���&�@�	'rptv��XsX���'e\'iH'�
	x�n���<'��{tĶT'n4���	t8��d�'tt=t����'e������e��$����p�����8n�'���'t�����'e(o�r��	�,(Űe�����'���y��(r�\	��d:	hl�	(k���� (���	�(b)d4)f@)g�)h�)j*kd+l�+m�,n�-p�-rd/st1t�2v@3z��������(��(��(�t����(i�(r�(À��l��(s�(v������������(s�����(e��������)sD���)e8�h��� )�@���()������pb�Kn�4th)��Ř�	��t����\)�x)�@�et���T.\�e�}s�)t8�|d���)n�)t\����)e�)�f�XwW��	�)n�����)���~b|~c�(g�~nXCu�����)a|*e�*i�*o$+u\*���0�1
��0*�p���8*���	D*s����P*��*�+�`����P�t*b�*g�*l|sD�t����	eJ
r�^sX��	�t����k�*n�*t�����������*a+d���*l\�	�	�}rȀv�z0�T(�t0��`�.��b�e�h�n�+r�+t�+�\�0+t����X+e�+fL�m�+y�BT��|+��+��t��t���B���
��d�+l��n,rܑu���+aD,e�,i�,o�,t,è��� 	$�l0,r<��,�|,�L��T����(�T,e�8,l��rTP�;	�;	��\,�|��d,�\	p,r�Lil�,s����lt\�$�,s��
�1���,u�j��,p����,a-eh-yL-ô���'h��i �l$-�d�,kpl0-rL�����l�<,H	8-jh��@-��k��	��l(.\-e��t�(t-d��g�-s�&��|-e�-i�-o�-À*�$,T��p�'���������-��-��-�-l�/��-l 0��0��T.,bt�ct+dH.e4f��h�.i��k�l��m��n̦r8/t<vt�zx.�/�p7�tt�z�;��X.�.b�.n�;	r�.sL@t�2��X.��(��(�p<��<��
D..�.ex#h�#i�>k�#n�#r�.s�t�.Ð<����/���.��.�\�y�/�=�eH�g�A	Ī.� k�A��/�C��T� ��L/�<B��T�e$/��e����FT��l��r(G��T/a�/e\�s0z�/��/ŤH	ԅr�/t�H���/����0���/�I� I܅g�/l�<	r�I���l�
e�
���/�Q	8�tHvhQ��)l80n��v�Q.	�/a�0e�0i�0o�=	s1t8�uT0�$��HR��R��R/@0l�R��H0��0��0�1����01�(U��@l�S�t0mx�	s�V��V��0k�T
��W���0e�W���0t0W��0nHX�X��0vtX��X��0k�0l�X��X_�Zr�Y��Y��1r�d
.D1�8Z�$1l e
,e
��<1���
%t\	P1z�\��X1�2��Z���1e02i<2o`2r�ud1��
,�]���1yH]�1g�1l�1r�1s2t ^��1m��
	X_�,`��_���1z���ܩ
���1���
���1��
,h`	2k|b��a(2z cXml�s`e�	le��L2�De��T2�`hC�2tth��l2��2�H�	�Dg���2e3f 3h(3t03vx2Øh��h\�.�i�2k�� ����2�(i���2ôj	̲	l3r3t��v�j��h<i���D"
ed�y��y�my��y�n�n��83a�3e�3i 4o@4sl3�xoC�r�o��`3�,4�p��,p��o�3g�3l�3r�3s�3t<p.�3Ŕp��p���3��q�r�(r.�3eHr� s`�d�@	n,noDnp4s�s��s��4z�t��t4s�C84r��_�u.��ap4ex4i�4o��u`4��u������4��uR�uR�u_vt�	8�	�4lh����4��4�h6�w��
85fP5i�5k��l�5o��pD6rP6s�4�$6�P�	`�p5s�t(�.�4a$5����5z<��H���5�T����05l�|�n��D5ax5b�5d�i�5k�5n�5s<A	t4�jX���5r$�$�����d�����k�5z�`�����l�5r���M
	�����5tL��5kī������5�P���6��	6rD���6� ������|�����Ь��06�L���̉pt�t�b_��	`6t��j �t6k����
|6a07eX7h�7i�7lļ	nT8r�8y�6�8Ũ��x}	l��n7v��.�6a�����6sܿ	�6c$7z$����6����7������t����7�����7�@��
o@7r������	a,�.D���L7a$����d7a0�l7i�7k0�l�7s�x7a�7l�����o ���@�oL#
��4�a�e�o0��|��7e	�7l�� 	c88zd���7�<���8�t�}	8l��� 8����,8è�	�0#���
��!��d8uH8�d%Pl��4��l8o�3		t8dt�j�8l 9n,9p<9rP9s\9ud9z03.�8a�9e�9i4:o|9�`:��5��5�8k�8p�5���8a9g4Lk�5'8D��9�P��9�h6��t�y�6�h�y�7��49c8���8��H9z<�Y�8��88�l�z9��l9��9�@:��:	��g�9l�9n�9s�;��	B�<���9c�=��>8�<s�9ve�A���9�$A���9�0B	ܿ	m:n\�p(:r\C����t�C��C�� :a�D	�kpF8�z�Gy�G8L:r�G��T:��������4�	l:�XQ��x:�lQ���:�\M�:j;tM���:aP;i|;oD;�pn_8n	�:t����:��y�:� \��:s,\���:��[���:�Hb\�
d��ldb��;a��	i�	s�]	$;z�]��8;�hn�t(v�0v��\;�<vd;��up;e���;a�=b�=c>d�>e�>f?gX?h��j�?k�?l�@m�@n�DoTEpLFr�FspHt@Iu�Iv�Ix�Iz`=�$��aP<bhc�ed<f�gp�hČi�j�<k�<lP�m�<n�o�p�<r�<s=t��u�v�z�(��ІPaXoT���|<a�r�x���t<gH��� �(���8a�<u(�Ô�t���<�a��_�����<�t�������<�Ď.�a8�i(o|������<z����p�x���xo$=r=�ď��e���,=�Ȕ��p=z4=�t�@=s�=z����P=��D����4�6�	x=s|����=������=à����0��4����=ot/r�=�d�����=l`�����	e�=s���T�l�.�=i�R�H�>l\���>a@>dH>e�>i� o�>uB
����D�`�.��i��j��lx ox>r�>s��������p>e4�����z$����>lԠ#����d�>l�>r8�>
�������l�>r������������>�@�p{.d����>e ?i0?l<?r,l���ܮ����(?a�_p��|�D?r����L?alx?�{C�?n�]��l?�D}Hl�����o��	�����?l�?r��p����?i(����E	a�?lP@���hp����?e(@i��u@�<@�8���	�?t$���@�F	�$�tX� @n��ṱ��4@�p�BP���H@��� �U\@a�@���	d@d����t@�p����p�@�P��h����@�������@d@�i\�j Al(An0Ar8As�At ����@a�AcDBd��eTBf�Bg�Bh�BiCj<Ck��nHCstCtlD�$��`���������D�.pAnxAo�As�At�AzdA��D�y	��\A���Q��}���z	�z	����`�.|�������Aa8c�AeBo,Bs�AØ�	������A����
g�Al�Ar����	�@�#��y���Br����B�T�.��i B�H�n���<Be��ud����G	l�����	`Bl���hB��B��B������BetB�������BlT����	�Bt�����	�Bl$�	n8�	���Ba����c�Bl��pl��0��<�CkH���Ce-�������	��$CÈ���0Cr�����H	pXCt���	e(�`Cd8���hCa�Ce�C�DDŐ�.�Ca�Co�Cs��	�Cr�����C�D�|���� �y���d��Cn�Cr �%�����Cf�	.T.,Di D�x�	DrL�
����
�ܭ���	4�.PDk����4D��%�����	XDl���`D�h�(�xDk�Dl��m�Dn�Dx������Dk�����Do�z
yt����Dn�Do�������T$a�$c�d�$e�$g�kp%l8Em�%o�%pE�d����$��&�������ܒ�h���(E�p��WihEr����DEaFl@Fo����D�.�Ea�b�h�Ei��n�r�Es�EtFu�E���0������E�x�D�������El�En������(��
�����EeF�0�L
����E�8�}@��Ⱥ#,��� Fa<���(Ft�4Fr����8�a4�h�Mi�lo��r #t��x�t�pFr�FsP���
xFa�FeGihGm�Go�Gp�Gt�GuPHz�F�t��_��	�Fl�Fp����F�H�H�����8��Fld�mGr�J�����d,�i,Gl4Gs(��P�t�0���<Gv(�	DGr4���PG����\G�x����~.�Gt\�tGr�
_����@��4(
%�	�Gl����G������G�p��\��Gth}�t}���G�����G�$HŠ�	�GtHv��	Ī.���H��
��0Hk��8H��.DHÔ4�	d�Hl�Hn���\Ha��	eIi�H�`�D�.�Hb,�k�Hl�Hn�Ho�Hr�Ht�Q`�tQ|}�Q����� 6y�	�g���H�02�
,�ox	�$n��I����dIh0�	i I� ,Im\opIrhnLIdpB	��XIi�oH��(dH�r���xIa�Io��H�LTp�l����Ie�I����(���I����`�t���Ia$JmxJs�JzJÀ�U�	�It ��J�P�n���Ji8�oDJuXJ��<Jk$8,��PJ�D���dJr�.lJa�Je�����Jb�Jlh��HCn$!���Ja�J�@!��p��`!	��g�Jj�Km�Kn�Kr4LtdLv�Lz�%��D..KaD�hL�j|Kl�Kr�Kv`K�l%xbi4Kk<KmDKs�%��T��U��%lKntKtt^v&��LK�hW��&��p�{���,,�l�,���Ka�Ke�)o�K�-(-���K��)�-	����b�-�Km 0��ěs��|7��,�aL�@8��4,��=��A	��	l��s`A��L�0A��(L�XL��
	(zaXze�kP�
��DL�<C��	T.�LaT�b��e��h��iԈv�L����CTLD��\���L�hI1�I#�I���La�Le�Li�Lo\KtK�LlM,�sXN#R��Q���LoXQMd�%n�P��Ma`MetMi�MuHM�HV	�gԒp|V��8M��M�f^XMt�kt�klMd@z	��s�����Mr(����Mb4�n�npNrDNsسt@����Ma�Ne Ph@Pi4QkTQl�QoSr0�s St,Su�v�N��RŬnD�Ns,���NoDÜ[	Hv����,N�\���8Nü�.��.�N�`���PNs̛	`Nc�Nn�Nr���lN��O��Q�hR�XS�dS���_�����N���#Ԡ���Ne�����
i�NoT�uH�|mH��dOl$+p8Or�Os�x���Om8���Oe0Om�	t0�`�����dlOeT���������LO�,{��TO�d�`Od8�i@�z����Ļ���.�O�0����Oz`���X+e�OÀ�	�Op�Or�Ot,���zs�+	�Os�����O�����T.�+t������t�i����P�4P�����P�D}�{	,Pn��	�a�elPg�Pl�	m�Pn�PoQs$Qzh�.xPa\���zxgHg���Pcؽ�PnH����Pe��p�����Pa$����Pg\��<����P.(����Pt��Pnd��,��Pm|���Qe`�t��Qa<Vo�����+l@�`$�@Qe8���HQi3ohQ�H��������	�����xQj����Qa<��Ql�Qn�Qv�����.�Qa$����Qy��.�Qt�����Qil��������	�l�	�QnRr����Ri�ü����
P|���$Ri4�	,Rs0"��8R�����DR����PRt@�	\Rld�t�Rz�����|R�D����R�4���h".�Kk�%n�!rP�sLt�R�8�	�Rr|����R�0k1�����R��K�L�����-����-iSo�R� � 1m���`u�
�dL�gLSs�.��
eP��@Sz	.s	|jd�g�����tSg�So|Sg�Sph�s(Tt����Sa�Te�glT�(V�\�4l�)��)���Sa@�e��
oTrT�p]��*	�Sl�*���S��,���ot.4�n�.��Ta8T�l/��X�|A#�@��DTe�4	LTp�Ts�Tt�4��XT��U�V�pB���s�T�B���:��DTD���Te�To�T��Dt�D���T��}���TrhY���
a��e��g0Ui8�	üV�TgLUt�
�x���Uz�.Ua�[$Ug�q��$���U��o��\Ue<U�Hp�gtUlD��tp��lUl�UŴp��p���U��q	�zk��l�UrTr��T.$avIH�Ua��e�Ut����U������os�U�`w	�Us�HM��	�g<t�<�	�eHVs����V��������4Va����<Vt���	TVd�	��\Va�r�V�@	hVkWl4WndWt�Wz�%����
|Va��c4Xe�Zi�[j�[k�[o�p�\s|(tL-y�WÔ\Ŕ�.<�	À�y�Vz�	�Vs 	���V�t���	_�	��Wt�	��	. Wa	��(Wy�	yԦup		@Wr�	��LW��
	��|WuXWÀ��
	tWr	��0Vo�	��		�Wr�	���W�p	���WÀ		�WlT�n�Wr�	���W�xZ��[��\��	�m�	���Wa oXu�	�#	TTXr\Xs "	��XedX�� 	$Xg�XkXYl�YrZs0Ztlt\#	t�"	��l�tX��8�Pt<%	|Xn�Xr`%	���Xa�XeYo�Xø%	��%	C�%	���X�Y�$Y�8Y�<&	tH&	�Xg�Xlp&	�'	��X�d�&		�Xrh(	�(	Yr�|t�(	e)	.LY��(		,Yr�(	)	��DY��)	���)k@ mhu�L/	hYn�	s\/	��pYa|�i�Yt�Y��V
�0		�Yi�Yk�Ym1	���Y����D..�Ye8nn�`�`�Q81	�Yr�|1	8�1	�YmX1	��Ze��	4&l�3	��Z�d3	��HZi$Zô3	��3	@Zn�8	��8	TZllrl8	��\Ze�6		lZk�!l`�p�Zr�ZtX;	��T.�Zt8<	���.eH>	���+kpar��t�B	�.g�Zl[n4[rH[sh[t�E	���a�E	���ZlT���F	���ZstF	��[c([td�p8��� [e�H	��d-�J	#�I	��@[iT'k8Bm�Tt[lK	��\[edtHM	_M		|[g�M	��b�M	���[o�N	���.l�T	��T	���[a\eH�n�[�Q	�[nHm
p\rP\s	ddrU	���[� U	� V	���/sPR
H`R
	\rlR
�� \�xR
��,\�4X	.8\f�W	��D\z]	_]	��\\� ]	.d\��\	��p\z8\		|\s`\	���\��\��`	���0k1t&zh		�1g̢zph		4Rs�l	Tl	�\b(]fH]g��i\]k|]p�]sh�
t�k	���\a^e�^i�^oP_ut_y�]�m	j�n	���R��n	.0]��m	��<]yp	�$o	��T]rw	��v	h]s�u	��p]e�]rx	���o�y	p�Az�}	PL}	���]s�|		�]c}	���]��^��^� _���	��	�~.�b$^f��h|�jPSl�v
n 
r,^tl�	�]lH^m\^sh^t��	_��	_��	t��	4^s̡	��<^iĦ	_X�	��T^t@�	���a�^e�^oԧ	e��	|^l��	���	(�t��		�^s(�	�0d�g��	���	�^s�	���^i0�	�^k��	jx�		�^d��	���	_t��	��_et�		_k��	P��e��	,_zX�	��8_s��	D_sp
	�Jd�_k�_l�_r
.\_a4`e�`i�`oRsau`��
Ld�
���_ap���
���_o�
�_k�
���_a4Lk|
��49c
��\mp
8�_l�
���_��`��`��

��dNl�_mL`nlNoT`v�	
	`lh`mt`r�`s�;B�
���_o�
	�
��``bH
��� d�`eL�d
�`z�
���=��
n�
8�`k��s�
	=dazx
HP
���`c�Qd�
	�`l<ar\
8�Qk�at<
	�>r8'
xaa�ab�ac��dhbf�bg�ch�R	i�cjdk�elThm�jn�mo�mp$nr�ns�ptqu<qv�qx�qyrz�'
X�d�an<(
.8)
��`j�al�as\��p,
	.
#�ar$.
���aebo�a�,
���ak,+8`.
�<.
	�anT.
���a��.
kbn$bs$��.
��`�.D���	0br���8b�<7
��Db�t6
Pbt�6
��\bi4�l|brD8
��t\8
�bd�bk�tl�bm��r�8
���ba ce0clDcopcr|cu�cy�b��8
t�t$:
,�9
	�brcz�9
���b���t����_�����cp;
cn�r�<
��`k�P�=
<cl@�r��s>
��f>
��Xc��=
��dc�t>
T�r�>
>
|@
#d?
���ce�c�,A
��?
���c��C
��B
���ct�C

.Ƞb|~c��d,di�j8dk@dlHdnTds0D
��	�ca�de�dieoPes\et�eu�d���	��l�
t�D
��D
t�D
��H�y4E
�<���E
	\db�ddhCl�dn�S	s�E
��dd��d�e��e����x�UԠ�@F
T�ddԑn�dt�F
�����).�����dtG
_�k�l�.ppH
�d��	n(I
t��y�J
ed$el K
��Hnd����	0eg ��8e��L
��DeÄM
j�S	a�e�dM
�lM
	lenxM
��te��M
�ps�M
C�X.T	sxN
�.j�en�N
���ea�edfidfl�ft0vdgy�e�$P
.`Q
������x�@S
��\/ØW
�hW
fd4fg<�@fc�W
�fr�W
��(fa�rD�X
0d|fg�fk�fn�ft�X
��Hfa�fe�fi�äX
�H _ġ���fn Y
�8Y
��Y
�tY
�fg<�l�Z
�0Z
�fs�\
	L�s]
���f�g��\
����v�f��]
	
0d.0�
bl�h�
iDgk��m��nPgtx�
v`���]
u�]
��0g�^
��8g�,���^
	��i_
.Xgahi(hu�g���#8|gh�gn�gr�_
���g��g�������_
��_
8�gb�gh�gi�gn�_
��_
��_
��H.�_
,�_
�gr�_
���g�`
	hü�Q�#	 hkta
���$t,`
4hj�hk�hrl`
��@ha<ibPie�if�ii4jkHjnTjotjp�hø�ńa
���k�h��� 	���h�D			 b
���ho�b
	��l�hmir�b
���h��i� j�hj�<��̟�d	��c
X4c
���hi�k,gnio�	j�d
CHin�d
��$i�\d
��0i��d
�le
�ghilpirH�s$f
�g
��h
�|h
	xij�inT:	X:	���ie�A	��s�i
���io\j
�j
�im�injsjt�z�j
��j
���i��j
���i�k
����tHk
�Xk
��ja�k
�Pl
���(�\l
��(j�(�	n�k	��@ji�l
��rLZ	,�X		`jr@n
����
r�p
	�p
���jj�jt�julo
�jl�jnktku�o
���ja�kc�ke�kg�ki�ko��s(lt�lyDmz@k��yŰs	C�s	�q
��dy�q
j�U#|	kts
���5a`kr�q
	$kt�q
��4k����H���k������Ps
�t
���l�&m�t
.hkehs
��xks�u
��n�ks�v
.`�	z��	�$�	�kl4x
���ka\y
���Aoy
�kk�3m�z
��p��	��{
	�kclr�{
,X�	��|
lb}
��la@le�lillô}
��mPlv��	���	_�~
	Xln�}
��`l�p���l��~
��~
�ln@�
���
���l�p
	@�h�li�ls�l�����lg�
%̀
�؀
	�lcml��
.�lami�� �
���
	P�d$mk��r��
�D�
�P�
,mb�$lh�
��4malme�mi�mo<Dz�m��!
��
dmrX�
�H�
xmn�$
L%l�mr %
t�k	eȃ
���m���
0�m�msD�
#�
���U	c�U	e �l�mons@�
��
�ms��
.�
��
nk��l��t\�
��naTnehnitno�ntDn�,�
���� ��h�
T��g��l�sĐ
��n<�
��sȚ
��rt���
�nr�ns�nt�+vD�
��	�naDoedoipoo�os�ot8pzo�T,�Р
	ܠ
���ns�����al�_�`�
���nk4oo�
	or�
��o�|o�@�p���
�$�
��
<om,�
��
��Pot��
Xos��
�Wl��
e��
Pt�e�o�P�
���oz�#
�vk��
���o���
��
�ot�
���oe�oopyH�
�P�
�ob�ol`�
���
8��
.pa7
_ĥ
	pt�
���lt?n,�
.(pa\X	lxpt\pè�
�xSs��
��Pp���X�
���p��
��lp��
B �
���p�h�
���ph�po�prqt�pè�
PL�
���peج
>�
���pax�
�pnp�
��,����
���p�̮
��H�	h��
�1n�zs��
#L�
��qe�
$qr�
��0qatqihq�4�
���s�
	Pqs�
��\q�`�
p{.�qb�qk�qs4����������
Q��
���qb�qr��
�qd�ql��
���qi��yı
Q�
Q�
���qc�
��LaT�
����a(rs��
�\�
�rnl�
.ra�
	��a�rb�rc(sd�sf�g�i�tk�tlum�un��o�vpwr|ys�yt|�v@zz�sÀ�
y`�t,�
�rj\�
���ra�r��
�����`�
��h�c�h�riss|z�rø�
���
�rnܽ
���{��
��
���r��
.s��
D�i$�
��saLsedsi�s�xs�(�
t��
DspЋ��d��
Xsa��
���
��ps�H�
���|�����PH.�se�s�@�
�sv,�
���s����\��
d��
l����s�H�
���
�sg��
���sate@tiLts\tu����
ta,t�������$t��td�
8tvL�y��
�t�
Ttr�
� �
��htt,�
pts��
y|te�ti��
�tr�
���te(�l(Vr|�th�
�tn��X�
���tm�tod�
1��
�tmT�
���
�tl,ur��
��uapue�ui�ukLu�p�
.8uah�
S��
	�l�
��@u��u��u��u���	��)	.t�
hul��

��
	|ul�-~��
y�uzX�
�usM	��N	y��
�̡	.��
�um(�
���ue
j��n��r�vu v���
	L�bHvhPvnXvr`vthvv<v�
���u��v��v���
��
��4v��}W��	W�
%�	%��%��	�X�	��pvs�xvsH�	��
�4�
��p�l�vr��
���
���vo(�
��awc$wd,we4wf<wgLwjXwm`wnhwppwrxws�wt�wv�w�h�
���vaLyi�wð�
���
�(Q� ��D��|�
o��
��Dwe��
���
��
��
�,�
�D�
�P�
oX�
���w�H��X�W��
	�wb�wd0h(xi8xjLxk�xn yr(ys0yt\Xvxì�
���w�x�Wl�
t�	.xn xvx�
���w�d�
7Dg7h�������0xaH��
��
Dxa|xb�xh�xi�xk�xn�xo�xr�xt�x�d��p����x���
��
�������0����ĉ������x.�xaykyyy���8�H����x����(��0�WH�
W\�
Wl�
ox�
��8y�$�
@y�|
��m@�
yXyo��
dyr��
��pyo�yp��
��̥i�
y�
��	�ybH�	h�yiP�	j�yk�m�ynzrzÀ��p�ys���|t��yP�
y�	vP�
���y� z����ti�	`�l�j,zt,��4zaXzehzi|z�x� t`zn(	�,��tz���\		
�zb\{g�{kP|l�|m�}nh~pt~r�~s�~v�	��4l{�X���x.{b{h{i${l,{n4{rP{th	�zl8
���z�(����0���P������t�|��<{����D{�@��ĦaL�op{����T���	 �	l�{r���|{��{��{����{l�Xo��r�{�X��pv4`weH����{�`���{�W	�{r|s���
�?��|�(|�����|�t
�
���@!	0|sL!��8|����t|yD|�@��#��`|��".h|�x##�#���|a�|eL}mX}o�}u�|ì}�$�4$���|�}�0}�`}�p}��}��%\�.�|d�|g�|i�!	��%�(%	�'%�&	}n }t(}vH>	%�?	%�'�ph	e`\	��8}��Q	��@}�(#L(�%h(	h}d�}k�}tlz%�[	%�(#�(N�(���}�L+�$+���}e)���}d�}k�-.���}a�}e,~o~���	`�		4�pX.��~�L~�X~�t.��.$~l<~s��	�0�	_@�		D~l�.e��\3��`~iT4��T@e�~�x;b<5���~��?#�?���~a�~o�~s��@@#|@P�ua�@���~z8F��T. �be��h��n�MrԈvÜFXxF�����2����,P	|�al�b�d��e8�f|�g�i�k��lh�p��r��s|u\�vt�z�ØP�������SH��a�Rtt�R������h���U����XU���e�o�����U��U�l�V�pV�s�V,�V	�r�V�����YlFh�lbr,�tԀt�[$�s�[��,�a8�l�0��\��l@�\\��L��l\	X�p|\��d���\��p����y�a����t�g��T.�a��e��h�iD�ktl�m��n0�	p̦r��s0{tx�zP��Xh�g�kj|i�gX�i�l(l�ndz�RHhR	$�z�N��,��Pm��8���h���Y���qC��rq��`�������\p���ḱp �t<�zl�ü��Hq�|s�hs	��r�t��p��TQ	��t��,�e�5ih8
.�t�JŰ7
	܁l��tD9
,���xI	�l������$w����4w�$�k@w.0�a�zm�zH�s�z��P�a�}.�{��h�i��ä���}	��n|�������j����i�s�~����e�h(�i��l��rh�ut��D��T���l�.�e����z,�#Ȩ���e�t�� �gT�sh�t��v������@��,���H�Ü������`�a�eX��4���x��@������@�����a�e!u���4������	�*H�(	��z�
��ȃ��
��ԃÀ��cL����e��i��o��� �����,��4������_����<����.D�� ���P�z��\�s0����`	����|���p������aH���l�s0�t������a��e��f�i��k��oH�p �s,�t8�u\�y����t(�.�a�i�����z�t<���yx��n����$�a��eX��4���	D�r���L����	(HbЅd�g�r�sh�t(���d����������D����܅b���k4������\����x���ąi�h����2a@qy0���4�b(`�$��j4�rH����a�8e��s`�zP�À������	<�g���D�����`���Trah�sl*���x�iL��l��m�nH����
b*_8(	��kȆr�s�-.Ԇi�.nxJ��/܆md/���e�4��t�l lr>���Ne�;�d4�n��rL�tl�z�@��D;.�0	g\H#pH��D�aJJ��X���I���Zg�d	m`���M��D�r�m��p�ea��p��rDn��d��$n��؇o��Ð�,����ćztn̇s��
(s���r4r	�dāp�z_�z	�b�s������pH����r��,Dr�	|�r��tط.|���T�nx�����
�ܽh�d�g�i<�k`�l��n̉p�r�t<�v�x0�zh���t�a�cb��c\�d�eX�fl�gp�i0�kP�lt�m�|n��o�p0�r<�sH�tđul�z�����$�G,��a �l�>`�n|���(�u0���0�k8��H�nx�p����P�a��e@�m��o������ԹQ������b����m�����`a0Hy�������������܉o�����`\���.h����a|ac�o�az�����m����1t4�t��(�r����0�a\�iP�� ����j������	��bx�g��lĊr܊t���d��،�������������P����Add��8����������,gn؊r�u�������ciС
�L�f�e��	�����h4�\0�
,�d�df`�gx�i��kЋl�mD�n��rȌs��QL�	@�td���H��,�����yT���#��p�l��������a��e<���g��}������e,���m����ċe��fdk��(rV
 �	�a�9l4������HP�����ih�u��G�.$�aX�e����8���,�yP

�\�	P�gl�lt�rP��t��>�4���|���
��
���������T�d��kT����G��_������s�Jz��	�g��l�p�rD�sL�t0�_<���T.�d�el8�t�0�	�k��� ��$���,�ô�_|���d�e fk(ftx�.��e��o<�
`�d�g��k�fl��m��n��od�p$�rԍs����Ur����"m@���gd8ggHgt��`H���̍i0Bk8Bm�t�������n(�t���	�r�������
4
��H�j��l<�r��	ä
	�
����utF	���kp
\�n�
��h�i�

��|	d��yT
��nxip�r��s؀
CԠl̎n��t�
.��a܎�H�
�$�
���
��Ԏ�x
����d�igl
4�px
.��a�
���s4
	�cP�dp�n��sH]H�
8�
��<���
��D��l
8x
��\���
��d�Ð^LB
��|�r�
/��r�
�����������
����zt
	�b܏n�r4�s�jz�
��tl
����v�
��X���
����l��r�
U
	�c
�����
��$��t
���kt\�z0!
��kg�Ok!
PL�a��e�lit�Ì!
���Z��l��l��l��"
��d=.��e$"
���m��rd3	��I
��t,#
��d=.���8���xBА.�b�e�n �s(�t0�v�w��ؐ��^
؂�H�^
�^
0�^
`�^
�%
�n��t�%
��8�a�Go��rp�ü'
	��	c��
t�'
��`���'�|0
.��a��e�Pu��ð0
�h0
��t�0
���P���� 1
�X2
��	rԑtp4
.�a���D4
�(6
���o.4�
i�4
	�rp�s�t7
���no�7
	��d�5l�z�H.LH0�l�<
��8�a4oe���t;
�D�kĒlВm;
.X�a�e��f0�l�(m`�ol�s��t��v����=
��<o�Do�@MD=
�����P=
����èM�|@
��4�g�?
�ؒg�r0@
����LB
���a(lJ
��l���C
��nX
#\W
��(�oXQd̫��<�aLZ
��H�jPX
�T�m�^
.܃e|�i�_
���#��l�`
����a��
r̓���Xa
�`
����v|`
	��r�`
������e
��T�.�e
�ؓr�]
�����g
��\Pl��	r�f

��b��dԔg�j,�k��l�m0�n��p��rЖt�ux�zPf
���a�bT�c@�dl�e�af{gt�h��ip�k��m��o@p̣rD�s�eth�uĥy�z$��H��h
��g
��t�g
����a$�àj
p{.�j
��Ȕa�i��8\k
�t�l
��[��l
���t���H����tÀ�(�r�l
�� �a��cH�r�n
��n
��@�eX�Àn
��|R��P�Xp
��d�j`o
l�p�o
��	x�a�[b�e8�g<�j�[k�[t�~vȕøU�p
	��bԕrq
�����,q
�Xq
hq
ܕl|+_+	�r�t
�����,t
����v
��u
�m�t
��$�ip�t�~y\���F,4�	H�ru
��P���v
n�v
��h�o��r�v
Xw
���ply
Q@y
	��n`y
�������y
��pwc���^~�{
��Ȗy�}
��}
��ܖo�}
�t~
	8�b��fH�g��l̗r�s�t<�vD~
�����Л������P����$���
���o.PaЦbئgp�j�n�rx�s
��8D�
���
��d��l�
����aX�
�d�
y��a�
��d(j�mL�
����a��iD�sd�t��uĆ
����r�;H�
�a$�n��P=�`=�����O��R
"DX.,�iX��(�
��4�zV
�	���P��X�
����	aB���p��$�����k�n��rĘtx�Ø�
��k��r̘tD���<���
���
	�9g�s��
��Ԙ�p�
���xz���
W�
G�
���a̧lܧs�v<�
t�
��(��Ѝ
��L�i0��`�
T`�
��$�ld�r��
�

�a��g��ișk�l��m��n��pȚr�s��
����yT��$�
�
.��e`�
jP�
���Is�
�d8�
��	ԙa`�b��f̩j �k8�l\�m 
r���`���,�r�,���e��-#�
��0�aP�Ì-e�-��H����
���
aЙ
_ܙ
	h�ap�	l��
��p����
����b��l��
���cqtL�
�
���
��cğ
����a�i��m��o��X�
�� �	���d�
�0	s�x���
T8�
���pL�t��z��
�t�
	(�k(�
��0���_��
��p�n<��s�|�
��\��P�
��d�� �
�`�.��i��k��m��t,�
.|�e��k$`m�	�8gY����

��
,L�
	
țd�g$�kt�lĜn��	p�r��s�t�v�����
���i�
�jܬl�
���at�o`�uH�À�
	�~t��
��<��X��ح
e�
#��
�t@�
��h�e�'iL�m����
	�n��
������
����	e��k'r��
��kȯ
����eܱ
���
Мm��
��؜e��
���d�e4�i`�t$avt�z(��(�
��
�mL�
���`���
ԭnD�t�P�lB�$�
L�k0�
��T�ex�`�
��\������
���k\�s��zp��ܴ
��
	��l0�
��B�����
.lQo�s����u�H_
��Н�l�
.؝Ì�
��<kl��t��v �
�#.tĶ
���e��	k\��Hh��,��p���4��\�
	@�n�srh�
��L��4�
��.L�
��h�a���0�U�]	��g�]�����l�
��<�e�i��T�
��d�f�gl�l �mX�n��p8�	r��s̟t؟z��
���
������
�����j̻
��8��Խ
����i�ph��$�
	�{b|h |n|��4�
��4��о
����d\f�{g��iL��H��
����t��P��`�
��k�
��h�iܰk8Bm��z��
.PTa�@k
l��
����a��
���m<�z����
	`Bg��j��n�r@�v\�z��
#��
���a,����
���
��$���
�4�
��8�eP��������
.h�l0��
���|l�brX�v`�
���d���
$fԠk�l�m��n8�pT�rԦs�D
���
��dPS	l�r�
����a,�
t��`|�
���i �
���a��t�
Cddr��
������
���
��$��\�
���eL�t,�ô�
>�
��@�gp�o�r��sP�v��
��mh	��{��|���{�������#��j�
����e��
��rX�
	��e�p�r �st�
	<k��
��ء�d�
����D�
��T.��	��n �
������
���p4�z����
.L�Ô�
�� !l���
	@�k��lȢn��r�s�v�v�
	p�l��
��x��Xq������e@!	��sL!�����h�
�����d�
��Ynd�
Hp�
	Ԣk�(	mL�
��ܢ��
����x�
����s�F�zz��
���ep�
����
��
	�k��r(��P�
��4�������0hR	X�d�N��`����
��l���
��x�k�l����o%�o	��lh�
�������
��d�f�n�
����a(�i�����
 �U��
���zp�
�����	�P��P=���
����t	�l�
<�k�l��
��
����k��l��p��t`�z�
)��
�d��k��n�r��s�t\�.���H��P�������(y�
��k�
����id�
�,�
Ȥi�lL�
��Фa��	��{
x�
�t��
����aԄ�0�
	��j�s��
	�g<�l0�tX�v�
����dX��l��X�
��
��P����
���
��d��Б1���x��\�
������
	��z0y����gD	��n�.��iإj�s@ ..���i��t��L�al�bx�c`�d��e��fĦg�h��j$�k��m0�n��p��r\�sP�t�#|D�cD��	X�kL��`�i�
���h �z(�lr������r�y��rئt4����a�g��r�����D�
y ���s�	�s������(����������l<�t�	����4�uT��0�
���L��	����r�`�gp�j��l�z���l�a��e�i�oЧ�4�Ű"��*g�!��n`r��s�"	plt!��ħ�����H��`#��k@l|Ip�sDvv�z�$�@%�l �r�%��'_'��(�cP�dd�ix�o�	#�'��H�a\�	�h(\�t�	()p�k����*��d�*����a��e��į�<+#��Gl�+��+��Ĩ��+��d�i�l�s8�t�z,��بal	b��u0�Ä,�,��,	l^n��t�,�� ��4/d+b�+d��lL�n��r�+sp/��<�a��i��o��sܩt�z���̩�T0.8Jo�0�����`��D,�3tP2��z�3t�3��lh���d��TQ	��5�r�5��5���a�8��HpT7������6.$�i��09��pd;�:0�cd�k�t�f	z;��8�a-c��Ð;�?	x'.��b��d��iȪjЪkتn�p�r�s�t��v�<��l����
Ut?U�_��
U��
U�
Ud�
U�?U�?Ux�
U�C	8�d̫gثj��r0�sH�t�z�HT\�d��ld�rI��$�a��e��i��u��y��zt��@I��XtLIt`I��l������I�tI��l4J�J��n��#xJ��J�L@L��īr�N���d�k(s��@O�Z��	T.�b��h<�i�Pk��n�-o̦r�Pt\a.̇��]��$�z�b8�l�b��
<�a�.e��it/k��l|/mȬoЬs�/v��Øc	Ī.��r$c��x��ج��c���
�d��t��
��
����e4eTfy�f�d��hh��dth.�a�e�g���s�h��h		��dh�g|�h��lX�n��r��t܂vнz�l��l	D�lTl.L�et�n,l��X�y�m��m����a�o��t�aԭe�o<�u���0��`p��P��������������L���p��p̭d�sDq��r�,r	�p�r�r�t�LtC�n�ztt	$�g�t��$��du#�u,xu	D�lXw��np��x�	���	��h��xy��|������x��|��zb�|����e�z�Ȃ��	�h	g�h	k�pЮr�s����T.��h�m�1t�������r�s��r\�����o i	p,�t �Ll��H���$�r�������8��X���@��d�L�g��kԯl�r0�sl�t��v����X�a4�e<�f$�i8Hk��o$�p��s̆t �u԰���,���8[c�������p����ȯaL�j(8k��	�	nЛ��������ä����	�b������������D�u`�z �ä�T���p���L��ؠ.T�����<�r��C����x���������h�nt�����a��yL�	��g0�l\�nԒp��r��s�t���������x�������0�������.�i�%��m|����a�]o�wv����l�	�dܾn�.<�ap�Ô���L�y�>�X���h��Ч<�l�Dm���|�a��oԱä�tpo��s�����oԨ��sĩ�����̱�4�	p�s@����������̪����a �o�}���s�|�r�����	,�b0Edt�g��l<�nH�pP�rسs�x�����.`�e|���h�yH���4ia��edk|il�)o�Ŕ�td��mIJs�����b��e�",�	��a(�k4�r  z���D���̲��1.������`�X�	�pd�����p������5�d����igt�t�����a��e(�i�ox�ô��(�e8���p��x2��&Qt����nX���t�	�Yi�������P�eԶ��tp���̳e<�ô�p�����e��kL�n0��h�	�kP�l��n��p��r�v��ܼ��(����1��<�l$���D�eh����`���`������	t�l̾.|�ep�����yh��ܿ��mp�����e��p�t<v��(�	Ī.�s����д��W$�t�����ex�i��L����o��	�dP�g`�kh�n�Gp��rȵs�t �z����H�at�ì�t\�����f��t��p����x�e$�������	��n�������L�����������
e������m�s���Pصe�����z��X����ll����a`,����a\�eh�o@��el���8��p��L���Hqv�P�l���|��	��z��b �lD�plHr��s��v����P/z��t��	�hܶr��8����ȶ�����жÔ?���,s$�	�s,�	��sd�����l�t���bHIt�	�gL�lTntHv�z<�.��t07ðJ��#x���`�e��ì��$���h�b��hطn�r�s�t8�v̷������������H����n@�������#��P�a��e�0��t�a��e(1#�1.`1���np3�����,���
�P�
��$���<����aH�e�>#E��L�oHCP�d��l��s�t�B��	\�a��e��i�oP�
p��sX�u`��\�ŰK`�K����aL�j�k�mM�M	��s M��ȸ�,M��Ը� �P���Q���zp������XR��Ha�e,�o�\�(T$�l$Y<X��8�l�U	@�lDLrx�s�U��L��T��̺�@���_��lLe�s�d`xd����e�c��b��pĹs�t�rj{	�y����t�|�t|йl�|��عa��e �o(��<}��g��l�s�}��ys�	D�k�|�����Tl�lh�t���4�ax�Ì�	D�n��r��s ����Ȇ��p��@�����m|����k	oܑ�!b����a��s������sL�_Ԝ	ĺn��
_�
	غl�����������t���rز/8�r�������.��4���(�z�	4�s�	(za|�e8���L��������a$��h�lВn��pHJzԻ.t�a�e��i��o��s�uԻ�ؼ���������a��\�r���Ȼ�<��̼��� ��T��h��n�����e����b$�g,�r,���������4�gT�k\�rp�v��B8����O.������h�a������a���|�g��n�pX tt���Dcj��t�������rl
���ļr����t��p4
�|���t(�	����r\�'d����t4���l���d�,�k7n����4�a��s�z��D�sp�t��ĉ.ܽa��b�cL�d0�e��fL�g��h<�i
j4
k�
l�
m�
nT
o�
p�
rt
s�%
tX2
u�9
v$�w;
y;
z�ô
�̉D�bX�cx�d(Qe�xfH�g�h,�iD�j0�k��lL�m��n��ph�r��s��t@�u��v$Vx<Vzx��ؗ�H���<�o��r�Cs������<���l0	p��th���d�a4�mȾo��������a��o���l^�l�����������ܾ�l���g`�
���	Ծc�d�lh�li�j$�m,�n4�r��uHRz��@�
����������(�
�����ďn@���<�a��g��nHn	rȿy��ü��@�_��	l�l���t�����������������e ������������h�.h".4b�o8Ô�����D�.�a�o�Y.����r,��8��vT�#��$�gt��X�k����8�a��tx���������	`�gP�n����h��L��4~԰l��n��rH�����a�o���@��. D�x�	��r8���������������r�t��e�����b�DdEn`�s����a Sc|�k l�n4�oP�r\�tp�u�vD������d�r�s����l�a��e��i�o`�u��@�T��g��r��s��t��������l�r������r ��������a���	  b�i �k4�����X��@����o	a��(�l� rH���� �������u����T.�ti|ul�L�gp	s`yl��b�g��k��p�����a�[b��e��f�gl�j��k��m��o�tX��l#�����aTSut���p	a�l�o �r�St,���+�$�z�,U#,��(�a4	0�dl�n�"s��t ��d��<�����8��\>���]n
L

��x���	������
��l����������������	��p��t�Ue���8	��h(�i0�nL�sD�����P����h�|t��r 8�t,��@�a��X�i���`�a��à	8�h��r��t���|����([���r.���Бa�"o��u�pX�H��0�n8�rp��m���`��gt����a�e��h,�i�n|��$�t|!t�#$n$��@�at�e��i��o������<)�)l�c�E	tX,��l�.t '������/�$/��lXsp0��0������1D�l�Tt02����a�d�e��g<�t\�y��P8��9��k$5��4��,��G9\Ip(I��4�eK	`�.`Wg<�	r,K.H�at�üL'�L��l�������[�����XZ��dv	g��t�Z����a<�ed�k��o��p �r0�u��t�#T\����e\���s]	��c�v	l$�t$]�����P��������X_��D
m��o�_t�_4�l��_pa	H�k����c��\�a���<�^
̛	t�b��n��s��t��vԱ��|��Ԡ^
ԣ^
�^
$�^
�d��
s��
�f����r f	��k�s��
6��
��bĿ����ah���w	��g���o���i���B���8�����hn��@��kP�t`k��
\�a��c�i�j(�k��l4		m��o��z��ø�B�q�q����yLo	��nxo�������t.xyo�r����h��i@tnpH��z��t�}��}���e~�}�� �e<�i\�ð~�L�nl�r�~�hRe�}��T��l��XS�V�lT��t��X��|��T�y����mt��p�T��b�����e��m��p�s,�t<�u\�zP����t����P��atle$�o�������zX����Z����В�8����4�tx��<���H�� �.�fl�tԝy�m
2�	t�sȡ��|��|�����8���k��l�n�[pt�����a��i4l��m��o,]r4�yD��X���<�j�[t��Q���������.���<���yХ@�k��y�aX�k��	(�rĤ��8��t����t�j��a��y��k̦r��	d�r<�
����k�{	n�Q	 ��s`�
����o�W	����ze
L
�d
�����t����H�����b��ot���m�r��}����~mH�
,��	�d(�n�
���à�_ü���lX�rd�t�1vн��P|	�8���Uo���ܾ_,�.��i��è���t����Tr������0��k����D�.��al�e��i ����d�d=.��d�lP�`\����a��� ����	�n<�r������������D�p,�y4�aX�i�DtP�`�P�a��t��d�lp�_h�	x�r(�	�����lT���l��nl����������l��@�	D�b��c�g�kP�l��m��nH�p��r0�s��t��v������������4
�t
��4
��7
���tp�0�k����8�a`�e��i��u�����P5lp�r��������x�g��l0�t�������5��������j|,
t�����������.���4�����s�7b8�dX�l|7n`�s�tWz@�����a�e��i�ot8s��y���d8����H�aP�d�����������PH.\s��	(�l��r����p��������T8����|8�H���\�a`Yn��u0���oH���k����t��W��a4���	��dHT(�lT�n�	rx����a	eD�k0������d��x��	�����������ch�i�m��o@�nԹH(��t�b|�m�4����������,t\+������*����s���T'����c�k�t��,.��o,3��2����r6Dh�)�������_X���(�t|B0�s�@��<�o�C����8C	T�d�bg��j��k��l��m�bn�sdtpC��`�a�d�e��i�_k��o��r�s�ct��u8���X���� ��<7��D��D��	 c.pAaā	b�'h́	kԁ	n܁	oP�r�t�D�hG�t�s �z(��4H	 �g�l\H��(��d���������c�������M%lL	\�tt�v�M_�;`�.�N|�a�:k��p��s���hP��RX�s��t�T	�T����t U��\
i�T	��n���������o�[P�gD�h(Bl|�s�]CĪ.8_�Yd��rt_�� �aee�i(k��s��zX�Ðe�Lb����l���e��i�0d.��r�v�k��k����z oRXo.��ao���4B
�q��dq����a�p���r�p������u�H�dP�lv����a��b�
c�	d|�e,
f��h��iP�	j��kD
m��o��r,�s4�tT�u\�vl���g
��v��`5j�w	��l��z�w��\������g����D��`���
�
�y`�l,{y`{��rl|y`}H@}����t�|��k��r�}�|~	x�k��
V
h����a$�e��X�
 p�
������
�ty�yH��x�
����@����l�yp�e��ix���`���!�$���
d=.��a�b �eH�iX�j`�ot�r��v���������d��l��r��.���_�	��l�r������4��l��|���,���������z�	(�r���X�@�s�Ez����lHrl��������������ha��e0hl��r���,5P$4����s������c,�	��rH�����������a��iThoth������r�����aL�h�r���e �e��(�i��kD�n�	t�.4�e��i������.��b��d��r��s��È��`��(��5.t�����eD��p7������e����r������p7�k����e(l���E.��g�l�o��;#�����i�P\�	l�P��$�a�O0�dTOiLO��<�ap�e�i�rDk���R�@i��b��d��k��sd�Øjj�j��j��l�j����e�$m����r�y��p��t��z�0z�z.|z.��tЋ� 	lx��ap���4	a��o@�uL	�����a��bp�c��d�e�f,�g(�h\�j��k��l��m8�n4�p��r��s��td�vt�y��z���d�(�d@�lH�n����l�	r���������������������g�k�l������ah�e��p��s�u<�z8������t�	�����tX�CX�g`�rl���(������%��������L�_�����d(g|�l��s����l:l<�z��_x�����h��z��\��.��e��U��	��d��s(�������e����������4�������	�l����U��(���B��0�������H�p��.P�a|�e���P���\�s09�`v���\�kl�������{y`���z������e�	r��z���8�H��	��l�!	n8������ �.��ø�ŀ�̶l�^n���t�	f �r�����aX�����	e��g��yd�Ä���	D�dt�r|�s��t����L�������������%s��z����0����a��oD�#\�#4����	�d��lH�.��e�$	h���d��4�U��������D�؄C4�v�����������$�_����<�at�e��o������D�t0�����yh�n��rX��X�d	��r��������������.�	��r�	����a8�k<nll�r|�u��	h^l�����������	 �s��t���� ��\�����P�o(���H�s��t�P���d�a��l`)	r���	��r�*	k�����a4*	b �d,�ed�fl�h��i8j��l��m��n�o0�rT�t|�v<c	z���4��H��ĸ�X����PU�$Q����D����t	�f`�g�h��k��m��r��s�t4�v$w
���X�y$_��^��l�����t��h 4okL ����e�k�mloz��Ð"�l"����e@#�L#������"����z���l$P��ü����x��$��toa�e�$H�l4v�@v�� ��`%��(��L��H%�T%��D���%	�os*�X*��a<�e�aol*(d��	s8-�����/	�l�/�����0�����	hl��	�����`0v���h0	��kD0�����T0���y���D1Bt0�s�1	(ra��zH��82�� ����.�1��@���7��T�ad��8��l��t��@�8 ;��T.��eԈv`�<�k�B��.��b��n�B��r`B����ḛrD?����b�l���DG��F	��nlA�����`JlJ����xJ����|O�<N$�ttN��
,�a��d��e4g��i��o��t�y��zh�ôO��t���-	�PQ�dQ��x�a��ÔQt�Q������S�R��rW���=tDV��s�X��r,[pP[����a�^��^	��g,�k�^.��a\�e��h��i��u�v4�Ü
|`��T�������������_����	l�_	P�lt�rd
�<`��l�ep`8�;l��r�
���O.��d��tt
��|"e�`(�.�`����i�`	T=g�-y`a8��s<
��a��
�\
8��l�a(�.�a���e�n�	dH�zj��$�iX�p��r oq#$q��P�a��et��DqePq��l���q�|q��r4r��q����i��o���@rP r��4���,�t����yps��g��l4�m�vnl�r��s��u�s����a\�d|�e��i��k��o�	s<�t|�vD��(�Ÿv	�vgؼl�r�v��0��|�����`��tz��8�e�Ŝ{��\8g��s�zl�g��j��l��p@�sT�t,���{y�����.�|��m�|����e��t4�v��
�t_(~�e��B�������<
����Ō~�d\�T��..$�eL�k�~��0�z�#8t�C
�\�.\�b(���d�z��	p�s��ܣgL�l��n��	s��z �.��al0	f�0	gxht�@����L���l�����z@z��I�x.4�b<�dX�f�k`�mh�n�xsytyvL��������l��؈���(���D��8��`��������x������p�ð�p�m��t������ezkX�p��s�tH�z��Š���H��`��t�P��a�o��P�����z��.����l������X�������e,�ix��ȟ����4�nĝ.<�e`�ktzm$Ÿ�yt�à��8���l��Ĥ`Ԥ����a����l��n�����a��e��i�o$�r8�t���\�����a��y���D�	��tp����������g�l<�nH�s`�tȨ���pedk,�mp�_xW����4�kt���\�.X�e��
�����h��d7� �.��b��ä�	p�k��	l��n��r8�
8@�������ȯ
����k����T.tri��t���x�<�
���za�e {�̰��k�snT��ز��	lȵ� nص���aH�it�oX����yk��Q �	D�s����L������X����$���,�e�i�_�4�	������r��	l�b��f�gL�h��j��k0�l��m��n �p<�r��s|�t��v0��@�����a���8�th����������
 a\�e|}g��i��k�o�}s(�u8��������L�<���}�x)������0����\�.pg��i��k��l��m��n��r��s�t �v��0��|����m(�����e��l���(�_��(�������tP��|�k�,������z(����i��̰`x��������(�e��	0�jX�k<pX�r��sL���T.��n̦r��tl�v��Ä������x���(���,������8�z��.xa��Ð���g��s(����������t,�����`�	��r���r �s���������_�	t�#���<�lp0��<�t���t���T.l�e��h��r�}s�����#.��k�}s��t������������P�����m\�����e��i�Ls���P��yk������e�h�mMs���u�������o�����`��\���lh't���� �eL�lL�md�yT�Ŵy����~�����@�ax�e �<��t����Hfu|��d
H���
��b�e�'h��i�k �l�n�r�t���d��k�����e@o	L�������� D�	H4)	 P�	o	�&�����`0��lx�n�0��
,�a�b��c�ed�.i��l��m��r$�t8�v��yt�z��Ð1�H3��2	��t�2�����������$4y����������;	���H`v@��l(@����eA9��
��B����e�B	�kC����<B����4E��0�b\�e��h��r��v���ET�ip�mx�s<�^
س^
���������|����\�����i(G����d��e��s�t(�z���I����i I��kal�O<�O����z�1
e�'
�����P���äQ.T�a�eT�iH�o�=	sH���R���	�0W��rxZ��\+l�Z`�b��d�Z��l�a�Mkh?	o��r��t �v���[��dC��r�d������d_Dey�eyDg��
T.�a��b�e��f 3hp�i�Mk��n�MrĈtԈvP���h\�.��d�ik��n��t�j	��	b��i��n`�th�vth��4��4���%��%�k\�.���Ď#|�c̎����a0P��g�w�����hw�g���w����a�e��i��l��o�rX����8�	��l�x.��a�x����y����n����� �����������e��i(��P�	8�mh���L����	\rh����d������p��4�|�kȒt�#�T��r��t�������L��������������üK��d�a0�y�����e4�o(�uD�Ь��la�e�i�o ��x� �,�bh�d��
p��	t����4�a�
e�\lh�np�r��j�!�� �a�iL��c8�lM����e��i��o0��hn��.��h��j��k�t������
��u��p�r�w(�
�z����r�z�,�����
sX��\�	�s�]��$������a�bx�d�e� f�g��h�k�m@�nX�o�p�rH�s�tT�v��z��$�hc��d��k��l�n��Ü���x�H�j(�.����yD�����ot��r������l��(����e������4���D�i�Ü�Hh�$�s|���,�i�8�l����s؛	P�s���\�����\�����e�o�>uh�����`�L^a��g��i��nx o��tL���z�P�B�n��U����g��l�n�u��#<�� �zd����aD�ep�n4�ü�������oȫB���,��H��@����.X�k`�sx��ܬ �����h�e���L���p
�|��������a��el�d��c��d��~n�����a�e4�iL�o�r|
u,�~���zԴ�s������eT��r$�s0�.صt��,�nD�r��t���Dlh�n��r��U4���`�hD�jܒk��n��o��r��t��u�z
UP�U�
�P����r��	�d���������p�����e�i�o �p��	u���<�t��lM	��R	�P��g���4�o�^	��^	,�r ����ax�d��e`�f��g�i<j�k��s([t(�vz����ux�p�(����r���������a�e�����������hZ
���D�	����t<v@�		�k��	����c��n�����u���<���	���	��4��t�����nd�o(�H�n�B��	T$a�$c��d�$e�$g�i�%od�	p���d����$��&���
�������a �
�����k����D�.,�a�bH�c�hP�m��n4Po�rp�tFu<��p��r�����a��k��o������e0���4��p����Q�L
���X���E�����`��L��p���|��Po��\�a���̛�Ա����������l��0�m�����a�g4�h��o�t��X�	����d����������0����p���(���T�dP���<�a��il�s��t��z|����t�	h�l���p����������4Gs����ep�H�b�.��a`��i��l����a�e�i@�tL�y ��4#�.
�
t�����
��
,�nt
��4�i�>
���T�	ax�����	d�s���l����������B�X.��b �����t���m������p����o`!		��g��j�kp�n�r
s\
tx
v�
z)��(�s�(���a0�eL��|)T<�r`�t*�L)��D������/T�J	cd�l��n 0��\�a��e�s����0� 1eD1����������2��1��l5�X5y�5tP7t\7�d|7���ad�e��i
t��8	�
l<�sP�t@8�������u�����
��8��(:z�|��8��H�o,9�89\�d��m��n��s��t�9��9��9��$��,_:	��n�:t�r���	<	��apf�l�p�t�4U��<�l� v0=����e`>t�>��̉p(
t�>tA��tzo�A	0
k��	l��s`A��<
�0A��p
rP
�,���B<C��T.T�b�
e�)����E��k�
nԱt�E|�I���La�
e8�l�
r�
�K.�8tP�
l\J���
��P��P����
eXRj��
o�
p�
r�
s�
t�nu@���

a8
eh
i�
j 	l�
o\
r�
v�
y
��m���d
s�dl
c����x
oX{�P��
m,����
iNoD�\����
mę�
r����U0�	�
k0
rP����
�d����
�̛	
l���
�\
�8
�0
�H���pP
s��0���H
p��	�`p���
c�et��U`���|
c@z�|V���
��P���
�8�	htsD����
�����
�<��
l�
r,
s(���
���
iP����
p\�Px	it���
a,�
r8��� 
al�	��pL���3���|���
����p
i�
uD
��$�t\�	|
np#���h-a��i�	�H��d�r<���
�$�
i�
��O
d
g(
sL
t����
aD
e(
i�
u�
Ð
�����
e<-���p�.�t.4
l4�n�.��<
ah
e�
ä0��0`
lx
v1�41	��s�
tl/���
�\�<7��
e�4	�
g 
nLqs�4���
��
��{(d��
i4����
e���
r�@�H@	
ll@.
a\>��
yTYThY��,
a��g�V4
gp
t,�vԘ��o��o\
l�o��d
a�
e�
�Hpl�
m�pt�q	��l�q���
���j`w	�
i�
kDs�|T�&v4|���
e�
o�}��l�����vp�
l����
e����
c��
c@
d\
kp
s�.�����H
����P
ð�U����h
s��<�	|
s�����
�������
s@	�
r�s�%�����
a 
ep
i
�t	@	���
c�	����.
u�		�
r�	��
��	3� 	��	gP
np.	�|.	0
l�.	��8
eT-	��D
t,�U�H	��\
g�B	d
rv	(�u	|
l�u	���
aSe�
o�
sTl	�
p�k	���
a
eH
i��
Ōw	ttw	�
r@i�Li.�
e�h���
z�����il�	
y�
����v�
�
lt
��$
e\�	��0
t(�	<
t8'
�
b�
c�
d

k�
l�
m�

nD
px
r�
s
u
v(Tx8
�8)
���j$.
��,
���
k<Q�n<P���
�x0
� 	g�
ä0
���
a	
eP	
ix	
o�	
r�	
z$	
�d��2
��2
�h2
		
n@	
v�1
��	
�h	
��	
��	
�(t��2
��2
��2
H	
d`	
sx3
��3
��3
��3
p	
b�	
r��s�3
��8�g�3
������s<4
	�	
sH4
���	
�4
���	
Ô4
��	
tܻ	�4
�C
Ƞb<

i�jD

lP

p�.rX

t0D
��	�	
aآe�

i�

ktvl4Sm0
o\
t|

����D
���a �	tXE
.d

o|���E
	hCl�

r�E
��l

�����S	�p
�����E
��,gnpH
lhI
��d=.�

a�

e�

i
�HI
4�l�

p����I
�d�ll��hJ
�

r�J
,�J
	
vPJ
��
�(
���t�J
�k$elH
sp/z��I��h
t�M
��P
a�{
��M
eO
�ee�d�xN
x
j�
t�N
���
a�
d�i�l�
t,�v�
y�
À0`
llP
���
e`Q
�����\
�@S
���a�
�TT
��T/�
�XU
,�.,
k4
m<
nD
rL
tT
v0�vd�v(�
v,Uv\�v|�v�[
	�\
	$�s]
��h
��\
��t
�_
.�Qa�
�`
2�_
���
��`
t,`
�
d�
g�
l

tl`
���
a�ueT

i�

o`�	p(

�`
���Ng�a
�lb
.

ot��|h
	|�lH

t�b
��

� j��

�̟��i
n�i
��@

ej
(�ll

tHk
�Xk
��d

a�l
��l
��x

o�l
�

d�

r��s�W	Chm
���

t�1�hs
����h�o
���

c��d��e
kD
t�
v�
y 
�,z
	��l<z
���

�Lz
���}e�

��{
B�q
��
��|
lb��l`
nh
r}
��,
a�
e�
i�
��
���	�P}
��}
,p}
	p
r�}
��x
��
��
��}
�	. DÐ~
	�
r�~
��~
�
k�
p,�	��
���	���	���
���	���
.�e
i0
���
	
sh�
�Ȅ
 
r�s�(
��(
��
��hc ,e0t���
��T
a�
\
d�
l\�
��
h
a�
e�f�
g
iL
kp
m|
o�
r
s�
t�
è
�P�
�� Ya�
	�l��t,�
���
��
����
� ��h�
;��
p��
���
iĐ
�d�e(
fp�
m�vn0
s0�
�t�
��O���
��8
�p�
��@
��\f�dt�
��X
�Г
��d
�<�
��s�
z��V
r���
a0�
eԗ
�����W	�(�
��T.�
b��n4,o�
�P�
v\�
	�
th�
���
�4�����
��d=.(
aD
eL
ix
z`
�`��Ș
 
l<
r�Zs��P�
p���r�4�
��X
�p
��
�H�
��
�
y��
��
n8�
���
�̣����eȚ
���
r��
.�o��
	�
t�
����oxTrtyD�
���
t�
z,�
�p�lpYt��
$�l,�r�
��Te(
i`�
�qs�
	�
c�
d�
g�i
k`
m�
n�
r
s`
t|�v���ؾ
�H]p�
.l
a`�
��x
s(�
	��i�Xkt^vH�
���
�$�
���
�d�l�k�
���
e(�
	�
p4�
���
���
����r�
�T�
���
�
d�
��
a(
i(Vr@
�L�
0mlx�mH�
�l�
��8
���
f��
��L
���
��T
���
	�X.��
��l
�(�
���
ix
�D�
#��
�(�
�
ih�
���
a�i�
�
	X�b��i�Z	j`�ml^nPrh�t��
���
���
�������
����z�
�<B�;8
r:��
��
.$
��
��0
y8�
	<
nP�
��H
��
��T�r��vT
�\		
�
b�
g`
k�
l�
nl
r
s,
t<
vx
z�������
��	���
�@��Ħa
e0�gL�o�
�T
������
�H
�x�(�
l(8m�_�	
k�����$
����8�t,
��	8
ld���}�����a|
e<�iHo�
�ܧlH	�
r����
�D�	�`�@!	�
j�
zL!���
�����
y�
��H�"_)����a,
d8
o`
t
�T
�*���X�@
����+$+��$
aT/#�/	�Fl�/t�/��L
��0��d�vT4��d=.|�a�
b�
d�
e �f�
i
k��o
s��uPv�
��[	�<5��t��@��H��l�
�6yx6t(6�
d�
g�
n�6�<7�9��z�9�<�?����s��v�2z�A����l��v8F��T.P
e� v�F��NH�N	X
n�I��`
�(I��l
�,P	
�
a�
b,
d��ep
g�\	k|
p�
rD
s�
ØP���
�t9
��P��c��	lt�nS��R�
g�R���
��S����l
o@����
kDU��dL
lXU��
aX
ed
ih�r���ŠU��\�k�U8�l<VT�n�\����r�f��@�li	nx�r�g��D�a�
i
o,
t8�z�
�h�
WP���
t��.�
ox����
s�h	�
c�h���
� 
�(ldz�m��m
l0n,�m	
ldo���i4p�+v\p��8
ah
i�
o�
p �t
z�
Ür�m�
�.�
��t
a�
� t���
r�s�
r�
8�
���
����t��7
C�
l�
tq���
�h8
_D9
_l>
�4w��
p@w.�
a8
�$Y'�@
��
l�?
� 
lx��,
�X
�x
��N
.l
ôM
�L
k�N
�O
��d
�V
������
a�
d�
n�
t�~��	�
ah
e
i,
l�fnh
ol
r@�s
��U8����
n�
	�Ĉ���
u�����	�
j4
nH
r0���
��
�<
�t�����,
aN����@
gT����T
i��\
c�
k�������x
r|����
t�
ul���Ul�	�
k�
r�_�q�����
������
ð�	�
l�����
�����
�
r
s,���l�s�>@���$
e!uH
�$�4���@
�4�U̶��T
g��\
n�
rغ��
_	a�
bP_	e
h
i(
j0
nL
o�
r�
t�
u�
Ø
�x
t4�
A(�	�
g�
r4����
�
��
��
����t��P�
rt��<�
���
n��
�P�
���Kh�
��8
a|�
@
kh
lp
nx
s�
t��
6��6Ԧ6��
D
�
t4��0����
���M
̣���
a�
ô	������
�<�
���
���
��e���
��.X�
eĉU��
��
.d�	
kp���
�8���P
o 
�Ľ	,
r�e���
H
l@�#�
k�
yL���\
a�
e�
i��o�
�@�8�_���P �����4��������
c �H��
f�{u�����
a�`	e
�x�.x����
o��	�
c(���
�,
�8
��s�4r	$
f�	hid���@���D
iD���L
bܽX
b�`	vh���
d
a 
i( 
k��lP 
o\ 
p�rp 
s� 
t� 
u� 
y!
z�
����L���
�\�<rl����
e��i 
�	�
g 
k0�,<�	�
j8����
������t<�Lsz4
��&o<�r�sÌ

��
��< 
oT
D 
m�
��}�!
4t
��h 
z�%
<vd��rpvt�%
��| 
a o(r� 
��'
��x'����3
����aX2
� 
rH��d�r� 
t@���� 
�;
��� 
�	;
P
0!
a$"
eh$
i�$
k�$
oLYp8%
sD%
td%
u�!
�t;
��eL!
pX!
t�>
�l>
��D!
r�?
d!
r�T|@
��?
�l!
g�!
r�!
s�!
z0@
��t!
��#
��$
��$
�%
��%
�8\1@B
�!
mLB
���!
a�!
u�B
P8kh`�C
���!
z�aV
�a���!
a���`e��"
��D
��"
��C
�"
d\"
g�"
k�"
l�"
m�"
p,#
r�#
tf���o�x"
��D
���"
yL"
Ô�.�J�H�	l"
l�f��f.�"
e�h�0E
���"
vH
	Xa(H
���"
��E
���"
ż:��I
���"
l�"
m�o�pJ
j8���{�"
b<#
dD#
n�J
#
i`#
kx#
m�#
s�J
��#
e�#
tl��\��t|B�|��L#
�Xu��p#
tT#
ä|Y�u��`�.�m�"�(K
���#
et�,|K
	�#
r�K
���#
�XK
���#
�L
���\r��V
�N
���#
i�M
��#
k$
l4$
rH$
t8�1D���$
�`O
��$
ŸP
)�P
 $
n�O
��($
i8�V
�Q
��@$
o@��XS
��T$
e�R
�\$
g�n�$
s$�j\U
��|$
sTE�Ԟ�$
lpV
���$
eV
��$
v�V
j��	�PX
��Jb�$
s[
���z0\
���^�<\
���$
�4[
��$
sL���\
�%
c(%
r��s0%
v�]
��]
��^
.|�i�`
P%
r�b
��T�ohc
��b
�\%
r|%
s��j����t%
sd
��h".�c
��%
r|�vؕ�%
l�g
���%
i�f
�%
bP&
dt&
f�&
l�&
n�&
rX'
sx'
t�'
u�'
x�zPf
���%
a�(
b@�d)
e�af{g�,
i�-
k.
o40
p|0
r|1
s�et�1
u�'
�p/
�Hh
?Ph
<&
o�g
��D&
i$��\j
�dj
��`&
�pj
��h&
Ðo
��<�j�[kPu
��&
a�t
���&
d�&
s�~y\��<8��	t�G���&
�lG���&
Ðy
�y
���&
c�&
t`�`�y
���&
.'
a0'
oH'
t����y
'
s�z
6�z
('
s@'
t|�
6���{
�z
��P'
lh'
t�{
T}
��{
��p'
r�}
��'
t�}
. ��h~
��P�.(
j(
k(
n (
o~
	�'
b�wg4(
lX(
n�(
r�(
tD~
���'
�P+
�h-
��.
�4/
��1
�2
�
�
��
�,
6
(
t��
8�
��,(
d��	�Pl0�
��@(
o�
��L(
c|(
s�	eT5��h(
��
��p(
�H�
��L�
���(
i�(
kl�
j��a�(
À�
���
���(
��

	�
���(
a��s�(
u�xv��
.�
���rH�
.@)
k�
�(
aH)
b�]d�qgT�kT)
l�)
m�)
n0*
pT*
r+
s�Buܐ
U��
���
�8�
��
��f��hpl�m�o 
r��
t��vd���Ŭ�
����b0_e��lh�
���
���)
a�)
e�Mn*
o�)
�*
�ԝ
t�
���)
��)
��
� �
�)
g�T_X�
	�)
s`Y���
*
sZeZ��*
�t�
UL�
��(*
p��
t_dt*
j��l|*
vğ
��<*
a�*
e�*
i+
o�*
è�Řt�H��0�
�<�
	�*
c�	lX�
���*
��|`8�
���*
e�
�*
l��
M
��
���*
i|�
�*
l�*
n�*
sȢ
����tԢ
�d�
�,�
P$`m8�
��+
z0���
$+
kt+
t��
��,+
e|+
g�+
rL�
	<+
g�+
k,
l@,
r`,
s|,
t�rv��
60�
�<�
��
���a�+
e,
o`�u�+
À�
��+
t��
���+
�d����H�+
a��M��
��l�+
sܷo	�����+
�|���+
��
�@�
��0�eL�m�'t�Ov4,
��
1��
��,,
���
��t�c�e<�	tt�z(��a��
��hsp,
z�
.ԃl��
��<kT�

�ad�ag�,
m-
n,�o�{p4-
r@-
spbv\-
zȽ
`Խ
���,
a�,
m�p�,
��
��
���,
�\�
U��
.о
���,
a��d-
f�{g�"v��
���
�����
�x�
��$-
��
��l�sP-
z��
PPTa��
����o��
	��j�-
l�-
rP|v�-
z,�
���
���	ü�
�H�#����-
ơ	�-
r��
���-
��
���brX�v�-
���
..
o\�
�-
t|�
���-
a��
�-
l,.
p�.
r�.
x��
�P�
�\�
��$.
aH.
bP.
hX.
r`.
tl_UȨU��
U��
���
	�l�.
s��
��h.
��
��~e(~ix.
��
��
�<�
3X�
���.
a�.
�X�
	�.
l�.
r�.
s��
��
���.
�d�
��,���
U������.
t�
	/
l��
��/
���
��/
�
	(/
kL/
n'r�sd�
��t�t�
	(zaXze�k�/
rP�
��X/
�L2
���
n�
���/
aD�b,�c�/
e��	f<g��h,0
i�l�m��n��p��s@�	v0
���8�
�/
g�/
i�{X�
	0d.0
n0
r$0
vh�
���/
����,�|<��k���
X,�
��ef��l�r��	��
��H0
o��
��P0
d��
\0
n�0
p�0
t�
��h0
a1
i@1
u�0
ä�1�������0
��
�0
�L�
�X�
	�0
gp�
���0
��0
� 1
��(H�
	�0
bl	n��
��|�l�
�0
��s�41
���
	1
f8�k��
��,1
���
\cp1
kL�x	T���P1
�<���X1
�$���d1
t��
���Dc��t�	z��
�d�1
k�1
n�1
rD�\����1
k�
��
���1
it�
,�
�1
lL�
���1
aI�0�
	�1
d�
rX�t��
	�g(2
lL�s0�tX�v�
����	�D�
��T.�i��n�1t�
	42
r
�2
b�2
d�2
g3
h3
jP3
k�3
m�3
n�3
p�3
r84
sp4
t�
�	 L���2
i����mX�v(��$
.�2
r�	�2
r����2
�4����r�2
ô�^
\�(���2
i��,ah��3
j,�8��$3
�D��,3
È83
r���D3
ad3
et3
��#�t���l3
��3
��t�Q	�����3
m'
l'���3
a��d��i�o<+T�*���3
e�+�`�d�i@�kH�l8�t,���3
a0�n��u4
��,	��t�,��4
��9t�6.$4
op/��,4
z�:	d�c�4
d�4
k�4
l�4
m�4
n�|s,K	t�4
z;��D4
a-c�4
u�4
��`��;y�;�;��t
�L<}T<���4
i�4
�$�������4
��?B�d�<���4
���
��C	\5
c�5
d�5
i6
j6
l(6
r�6
s7
t�F�TF�$5
lh5
t�F.,5
ap5
e�5
i�5
k�5
z�5
�E��<5
s7�PG��&m�5
r�G�HH��G��5
p4B��_ÔH��F���5
��5
��H��H�JTI���5
i�M.dM�5
m�k8�N���5
h(s0�ôW�
tR��6
y�Z��D..\6
b@�hx6
i�6
k�6
l�6
n�6
p��rD�tl6
�l�
y�[p[��d6
� \���y�S��\yP]y _	�yn�^���6
��6
��]���6
z�6
�`�\a7
�L\
@<\
���6
zDb��6
s�a���6
��b��
P^bP7
ed�ht7
i"jt/k�n�7
o�r"t�/vd7
��Ō�
hcH7
n�c$c��\7
��7
�d��7
ghd�4e��7
npeh�fC�7
z��
	�h		�7
d8
g88
kh8
l�8
n9
r$9
sD9
tнz�i��i���7
e����o$����7
�,l��$8
g`�y�7
��"X���8
yow o��08
a\��PUe$Q��H8
��
��P8
��o��\8
d�8
e�8
t�8
y�p̭dXu��t���8
r�uC�8
s�u.�8
e�u�Hw�����8
�Xw���8
e�8
�p��xwT�8
v�w��wtxy��|��1��x��9
Üzy8z��9
t49
z|��}y�|��<9
kh9
�l�^
�}	T9
b�}��\9
��	�p�9
rTMzt�������9
a�9
u�9
��������9
��������1aT:
e�:
u(:
�(�
���O.��8�9
n�����9
���.�9
�t����9
yL�	:
gT2n<:
r����:
��:
�x�����D�a�od�H��L:
n�:
z�T���d:
��	l:
��x:
������:
���:
t ����:
eh�	ȅn�:
t������:
s�����:
e�����:
nx������$����:
b��i�B.t;
a�C
b�C
c�d�C
exR
f�Lg�R
i�V
k\W
l$X
mDX
nPX
oT^
p�^
r�^
s�`
t�b
uf
v,'
z0@
��]
�HC��;
a�;
b<
d,<
f8<
g�<
j�<
kP=
l>
nl>
p?
rh?
s�?
t�?
u�?
x�?
z���D�j,�~p�	�;
z�����;
��C���;
d�;
���hE���$�<
�E��<
äEĽ�����$<
t�E��d=.P<
bl<
y`<
�����HF��X<
��F.|5.|<
a�FD�\G���<
.�<
j�<
�lG|G���<
��<
��G�8HX�I��I��<
ipH���<
c�<
m=
���@�,�	�<
r�I��=
�$=
�����	=
z�	%HL	0=
sTL��8=
��K��
X�b�=
f�=
g�=
j�=
l�=
n�=
p�=
s�=
tD=
�B�Ly�yh".�=
a�=
oP���=
k�p�y�y�B�BLM��\�.�=
e>
o>
uhM!8 �� �pN�HN��>
e4>
tH>
y�I8�N��,>
r�N�N.@>
a`>
�h�p���X>
�LO��
D..�>
b�>
e�>
h�>
i
j�
n�>
r�>
t�>
�xO�O���>
��>
�l_y�O��O�>
lpaȨy�b�`Py�iyQ%�P	�>
n�P��?
�H?
��P��8?
b@?
cP?
tX?
v?
�\��Q2xQ���Q�Ry�Q��`?
ph�s|?
z �.��t0	��R���?
a,R�?
lXR���?
a�?
y�T�Uy�?
a�T�?
tU# ���\��U���?
o�U�\@
b|@
g�@
j�
k�@
l4A
mB
n$B
pLB
rC
sC
t�U���?
��M
�V
�4[
��\
��c
��d
�pb�P��T@
rWe�V��h@
�@V���@
yp@
�$W��@
o�@
�8��L����@
�<������W���@
eP��X���@
o<X���@
d�@
iA
�Y��X�@
n� ,���A
��Y��YA
ndA
r�Y��
$A
a�A
c|qe�A
k�A
l�A
m�A
n�A
o�A
txA
�Z�(Z �A
l4Z��lA
��A
��A
��A
����h�XZ �A
rhZ����tl# �#��#��Q	��k	��Z��Z�A
r1��1 �	��Z��B
dB
t6�p[y�[f�[��,B
d�[4B
d�[��@B
aԈe�B
i�B
m�B
t�B
u�B
ä\ot\	pB
s�\��xB
��B
���x�p�����B
�$]�B
��P �V��^_tA.�B
h�9s�	@`	lK.�_���B
��_���B
�T
'�`��C
d\C
idC
l9mtC
s|C
u�C
vHC
�La a��@C
�lC
��C
�(���|��~�,���!�(���b���l�C
r�b�����C
a�	�C
n,���C
��h��$�l�C
àc�<D
bhD
c�D
d�D
g�D
h0E
k�E
l�I
mJ
n`J
opJ
p�J
r�K
s�L
tpM
v�M
x�M
z�~�xd��4D
.TD
ôd�d��LD
�e[�d��`D
h�D
s eS,e.xD
e��pe���D
z��4f�D
r�e���D
e�D
f�D
z\� D����D
�����D
�d�y,g�\F�,F��D
.LF��E
��h.E
ńg��E
s�g��$E
cTE
e�E
r�E
t�E
��g��gLE
gDn�
�g`E
th	hE
i�E
sh��tE
��%D..�,k�	�E
n����E
��h���E
�@V
4)	j�E
o�i\�.DF
fTF
m�F
r�F
t�h���E
e<
fG
g$G
h\G
j|G
k�G
l�G
m�G
n�G
o|H
s�H
t�H
u�H
v`I
z�F
�(H
�i��%j��LF
b�F
e�F
k�F
m�F
r(
z�F
�4o�
�ixF
k��j���F
��F
��.�Q	%�`	8!�����F
e�j��'^
�j	�F
n<i���F
��G
�P
�!8*��G
oL*!�j��G
a<G
�������4G
�HV���HG
�L���PG
��<k��hG
�Hk��pG
�hktk���G
��k���G
ôG
Ÿ� �/���G
�0B��GĪ�G
v�k���G
e�;øk��k�16�1���G
��k	HH
dTH
fpH
rH
�l��H
�3�2��4H
��2��<H
�83��<6��\H
��5��dH
�\l���H
z
�,��8����H
��7.�H
�7!xl���H
a�H
��H
�hl���:�������H
��l��l��|5.,I
eHI
iPI
nXI
tI
Ä;�;��I
��l!	�l$I
k@I
s|
z�l!	x<p�''�lB�m��mhI
r�m��pI
em��|I
b�I
e�I
p�I
��I
�(nt�m�I
k|n^
Dn	�I
ldm���I
�4L�N	А.�o���I
�DPy,p���I
c$J
e8J
nLJ
tXJ
z�S�pJ
r�X��W��0J
y`�
�q��DJ
m���(rj�r��r��hJ
itw,�s	|J
l�s���J
�@s���J
e<K
kHK
sXK
t�K
v�K
z�J
�`{�t���J
m\t�J
d�J
gK
jK
l(K
s�t`�t���J
eu��u�0vv.K
t�u��K
z�NV
��j4K
ex��V
|x��PK
a�K
�Hx�Tx��hK
o`x	pK
llx��|K
� y��K
�Ty��0��t���y��H�d�K
ehL
ktL
p�L
tPL
È<���2�����K
������K
Ìy�K
mL
t�y��4b$L
e,L
i4L
r����y��m��mz@z	<L
sLz��DL
� ���hG
�����\L
�zT{�{��|L
e�L
i�L
j�L
r�L
�+�+���L
�P{�"�`{��}��}���L
�`}���L
�<}�L
l�|���L
e(M
i<M
o$K�\M
ż~��~��M
a�~M
k@�,4M
np��	HM
r���PM
������hM
eL��8�y����M
p���|����M
o�M
r��B����M
bN
gdN
htN
j�N
k`O
l�O
n�O
p�O
r�Q
t�Q
vԀ܀�M
r����M
eN
rd������|5.,N
eHN
r<N
�`y%����4N
�Ȗ'��tA.��n��tЂ��PN
e�=s���H?iЃ$
.�N
d�N
i�N
k�N
m�N
s�N
z�����N
e$O
o<O
sO
Ô�^
����^
��^
p�^
��Ժk�	�N
bO
v�����N
�DO
�(�k0�t<�O
l4O
sT�����|� �^
�LO
k����TO
e���T>e�O
n�lO
k���|O
e||f@/�L/���O
�/���O
�����O
r�O
sh�@����O.�Ob8P
c@P
dPP
e�P
fp@g��h�P
ip�j Pk�P
l�P
m�%nQ
p�MrQ
s,Q
tXQ
u`Q
vhQ
z�P
�P
�$4'8���1ĉHP
ldP
mtP
n�%`�.�m�:�8�e�X.�.n�;	rL@t�P
v|���|P
�t���<� =8�>!	D��P
n(@'h@'Ī���	�P
.T�i\�kL
t�����P
��A8��`0���H
�HQ
�PQ
�����@Q
eQ
ÔP��)$�2���CV
4E'F'�H�l�Q
s����pQ
e��.t����:.�Q
alAb�Q
e�
f4R
i�
k�?n�Ar�
s�At$R
����Q
d�#����Q
.�Ai�?kR
tķHfȍ	R
n�
t�v܍��R
��A���H���<R
i(���DR
a�R
�P�	PR
r���`R
�0�����lP�rlR
�������R
�h�	�R
s�R
�|����R
�XZ�
H����R
o���
��aS
dDS
fXS
g�S
k�S
mDT
n�T
oU
r\U
s�U
t�U
v�U
z���Ē��S
e0S
�8@�/����(S
��B����<S
j�������PS
a�S
ntS
��/���lS
���������S
s ����S
a�S
e�S
��T��S
rh����*
�����S
k$����S
aT
b T
pT
�@�d����S
�x���_	8����T
r0T
u��!��@s���8T
alT
d�T
f�T
g�T
k�T
tH��`���dT
e�T
u�T
�8�e�����T
���!`�	pd����T
o��y`�p�����T
oP�%T��T
g����T
e�YLcTc�T
t@����T
a4U
i<U
o(U
�`��l��� U
�x�����
d��P���HU
�����tU
zPU
�|�����lU
a�U
�_�����U
�T����U
u�����U
d�����U
a�U
��%�	�U
s�L	���U
�lA������ZgV
z$!BԜ��Dg,V
npV
v�V
z�V
L���$V
eDV
n�)���<V
a�D�,D	PV
r���XV
������V
idV
ÔV
�PG��� ����V
�|B�����nr��V
p�V
r0����V
a W
ek���V
bԟ���V
aW
ct_X����V
h��H����W
np�W
n�������,W
hxW
ot�	4W
v����DW
�@����W
eX
oPW
�T����pW
kD.]	 ����W
.�W
n�W
r,����W
g��W
n�W
tļ	��	�������W
lT����W
e��o@��H����W
eT��W
gS	1�Q	X
kئ��X
o�1 �0X
bԧ��8X
ot���Bb�X
d�X
f�X
kDY
lLZ
mxZ
n�Z
r[
s [
t,[
v�
xx0
\�.�X
i�X
kP$���X
a�øQ�1
��6
�`�	�����X
�ܨ���X
kY
o$Y
s�X
Ü>!�>���X
e\uCzt
e ��Y
��L
��Y
�PL�0Y
jd���8Y
a�Y
d�Y
i�Y
l�Y
tZ
vtY
�ȩ	wlة��hY
��������Y
a�Y
������Y
�(���W
�4��Y
m��
���Y
��
���Y
�T����
�d����Y
��/G���Y
d<Es����Z
a$
H��� Z
.T�(Z
r<�j4Z
o̪��@Z
b\Z
s������dZ
s@���lZ
a�Z
d���u
�d�������Z
a�Z
m�Z
n�Z
s�Z
v�ø�	y�
y�ey�
yT�\����Z
�h����
o�Z
�@����Z
tL�
'̮
��[
h����[
t�1���X[
l�[
n�[
r<\
s�\
th�'���P[
mp[
ü�
 �
��h[
�,�4�	|[
d�[
iD����[
�P����[
i�[
o�[
�p�2%�����[
d��fб	�[
n\
t����[
�����\
i�[
�x+f����
�	\
k(���$\
�4���L\
z0\
à�$
.h\
a�\
�.���`\
k���̲��t\
lز/|\
l����\
��\
��\
���
%��n<�eH����\
�T����\
�`�/�\
l����0�rг� ]
b4]
g`]
l�]
r�]
v�]
z�)��]
i$���]
l(V
t���,]
e� �@]
s,���H]
e<���T]
dp]
ed�!(��4���x]
e����|5.�]
e���Ai�B��(za�k	e�]
l^
r ^
s,^
z|��8����]
��e
�$b 4b���]
��a���]
��#���^
aĹpй��^
i�{y8^
eT|pL�10���@^
�$����l|^
oH^
Ì�H̶��h^
zt�p^
n �T,����^
i8�	�^
dD����^
�����^
�$����j��p_
s_
vԻ.�^
a�_
e�_
h�_
i�Ok(�l�_
o0�p8`
r8&
th`
zH_
�|�9��9|���� _
kl_
nt_
r|_
s��t��v���(_
��O��u�`
� `
�T`
�``
����̾�0������%
g�_
m�_
n�_
p�ur����4�9��j����uk�_
nT~s�_
vt���9��vd`
k`
l��rH�9l����l
���`
r0`
vD���
4��Сy@`
l��H`
l4���/H�����l��	p`
l�`
r����|`
��a
�b
��b
������a
e�b
r�b
y�`
�8b
��Lmd�\ma
sa
tX���	�`
aa
eP�f@�ha
j,a
nLa
uXa
v@a
�T��|�
���`M��������$a
y�C���8a
���
�P8k\�y8�
t��`a
l�a
o����ha
e��xa
r��f	��.�ye��	�a
g�a
r�a
t@���7t$avt�8�����a
�x����a
���v��	�a
k�����a
������a
à�	b
rd�����n8�	 b
rT���,b
��������Db
����Lb
��	Xb
d�b
j$���db
�����tb
�,�
44�
���b
k8�	� r��1��.�b
e���
�b
bc
cc
d c
g8c
h@c
j\c
khc
r�c
t4z��.�b
o�	��H����v��x(���c
o0c
r�!B4��h������Hc
����Pc
�4����&
a�c
��,X���xc
�X��`����c
a�c
u�c
��|����c
��6�����c
g�c
jd
r,d
sHd
t�d
z�y�y�\�n(����c
o�Mr d
t$����^�H���d
�y��e����4d
�����	�htd
i�
j�Hn�>o|d
r0�t8�v<d
���!x����y����Jd�d
g�d
k�d
l@e
nde
r$s�e
vX������d
g�� o���d
s8��(����d
de
e,e
��pD��d
dX�yh���e
et�	e
s���� e
�xwV
����8e
e�N>�M��Le
e�e
�<:��Te
k|�G�O	pe
s�N��xe
���B����e
k�e
r������
���e
n��	�e
dT����e
�8����e
�`�������e
.,f
e8f
j@f
nHf
t���e
t���f
i��
���$f
t��H��Ht
v��ĉ.�f
a�
b`�
c܏
d�
e�
f\�
gL�
hT�
i��
j�
k�
l`�
mP�
n��
o,�
p�
r��
s��
t��
u<v�y�zD~
�P�
�̉$g
a�g
b�l	c�g
d8j
epj
f�j
g|l
i�l
j�l
k�o
l,t
m�t
n�u	oXw
py
r�z
s�{
t�}
u�}
v�|	w<VzTg
�D���d4g
l�.`�Hlg
a�<g
tL���Hg
�������]�X�Rؗx�zH���tg
o�g
r�Cs4���,Xa�g
o�g
è�n��������g
�<���l0	pT�sh
th����g
aPh
i\h
jdh
l4�mlh
oj
r$p
s,j
u��v8h
��Z���h
a��b��o0h
u�^��������h
��'�Hh
o �	��	l�@�d�gRt��B�h
a�h
b�d�h
f�h
gi
hi
iȪjTi
k�i
l�i
n�i
p�i
r�i
tj
v�h
�_��������h
�Di
�j
����������(�
U���i
g,i
k4i
n<i
t �
���
���
�����
.خ��Li
a�i
o�i
rti
���
Pl�
��li
���
.��M
(V���i
aX�
���
.(�
���i
a�i
�
P��
���i
�x���UlPP�
���i
�\����i
��|�U|���tm	e QoQ��,�$j
n���lHj
r��.Tj
o̵t��B���\j
�����p^r�j
tdj
Ð��Ľ���j
a�t��j
j�v�j
z@���	�j
ak
eLk
g\k
itk
o�k
rl
ul
y�j
���	o��	�gl�l����j
�8k
��k
�4	�@����k
cdQl0k
rt����	��g������Dk
ot	r��dlk
z�����ts�k
t����\Ft �	�c�k
n�k
r�k
v�Q�,��,���f�k
m<����k
a�k
� �T�B`�d\����k
����sl
tp��h�.T.<l
aP	b8�nT�r�]vXl
�8�	`�.�8��bdl
h(���Hl
��y����Qe�l
�ll
r�zd����I	���������l
��l
������l
u�l
ü�����l
s��	�]s�����atm
c�m
e�m
kn
l,n
n�n
r�n
t�	u�v,m
�$�	�QlDm
rXm
s`m
tH���m
��m
���8n
���.Pm
o(�p���8�����kX	n����lm
eSiL�.�m
e���m
l����	8�r�m
sx����.�m
e��y�m
z����<Ran
d��m
r�����m
o�Su��t�P����n
a�Du��.��� n
a��	�,bln
r8Ts�;�H�	Ln
m����Tn
�`���`n
�l��|���xn
�����n
������n
e@To�n
�,�h�Ut�	�n
n����.�����n
a�n
eo
i"j\�u o
�8��D��n
mP�,�.0!v��8���o
��`���	,o
a�o
h�0
j�o
n�o
o�?t�o
uX�vdz�l
4o
d�o
g$p
kxSll�nXp
p�Rr�p
s�p
t�p
u���`o
ahq
e�q
g�q
iTr
j|r
k�r
l�r
o<s
p�s
t�s
ut
vq
�s
��_��_�``�np
up
�����B���p
�d�Q���p
b�c��j��upSzLp
�D���Dp
�t���p	a�Sktp
l�p
s`	��+ULiG�,j|p
z�tP#����p
e�p
t��
����p
i�3$��
����p
���4	Tg q
l,q
r�"s@q
t�p
�d���p
��q
�<r
��s
�4��9��ܤl4	��TTaHD��	��8q
aPq
rT
���Tl|Ű
Xq
l|q
n�q
s�
��g��
��q
i�	�q
g�q
klUrX,���q
e@�nP���q
e,��Uoܢ
r��q
kr
m$r
n�Up0r
sH��pph".|��r
a��HX
k�	�q	r�|�s���Hr
a0�i@s	zD���hr
l���pr
a�r
o�r
� �(	�r
m4���r
�@�H�r
h�r
tp����r
r����r
e�Vyp||lT�m�s�"����(W����d
o�	s
r,���$s
����\s
ehs
i|s
u0s
ô��Ts
l�Gl���ts
s� ���Iet���s
r8!�d�s
ltr|tL�Q����s
j�!�|!	�s
r�s
t�!��T.�"�"�s
o�"���s
e8%�L�m�#t
lLt
z$��t
a\t
ipt
p�t
u�t
�����&�L,�X,Tt
dT]	p�0��ht
a�t
u�0n\1(1�t
r '��<X��1&jD�l�t
nh�t02���t
aPu
dhu
e�u
iLv
k�v
nX(o�v
tw
yu
�h��l3�\5����	l4u
o5	�t
lDXs$5��u
��u
�4���(�t5p�8��8<u
lP8��Du
e`u
rT�	y�9ܒlh'r�u
s�u
zX�	�(;��;	,j�u
r�u
v�;��T.��k,<���}b�u
i�u
m�u
Ä<1�<���u
��<�$�80A�<A�u
dv
mv
s��	IB���=t D.Xv
e4D	 v
r`v
s|C��,v
�hv
��B��<v
�HD���	��	C|v
r��n$�	��tv
a�E��E���v
y\I.�v
l(I���v
e�v
o�v
r(�	tT�	���s��	�v
r4�	�����I���v
ÐK�K	�v
g,K.w
a0w
ðL%�L8w
d�L��$w
��W�XZp{.�WiXk�w
s�Z��@w
aLx
l��p|x
r�x
s�x
u�w
�\�\��|w
�$\���w
ô`Q]	�w
b��d�w
h�w
i�w
jx
kx
mx
r<x
u0|v|��$]���w
�px
��Qh�V
�\QxQ`�Q�]��]��x
a0x
��TP^��(x
�d_V
$dP0d��Dx
adx
�Hd�Td��\x
� f	x'.�g����oؔ�i� i�x
r�h���x
o�x
s�x
��i�����y�i�x
n���k���x
ek�x
b�i0y
s8y
t`k���x
a�y
b�y
c�y
i�y
o�y
s�y
t`y
� n�hn�Lo	<�b`�d�w
hly
nty
t0|vxo��@y
��q
`rQ��n\���|y
i�r��T.�	h��o�y
u�Y�(p�?��z�y
k��l		m��j�� ��y
b����y
az
edz
i�z
o�z
r��tPz
�$����z
lx�m0z
n��t����T.d�	8z
r܄��Dz
�̟���\z
t<�
����pz
d�xz
l�z
sԦ�̣y������z
c�z
e{
f@�k{
l��m4{
nؘoH{
p`{
r|{
s�{
t�{
v��y�{
z��p��z
mX��L��X���{
u��
��
�� {
��
��({
�
n|���@{
i�dr����
��X{
u����lt�����l{
z�{
a�{
elrĒb)
��9
��� �.�{
k�5lX���<�j |
m8�p�{
l4|
st����{
a�|
e�|
i}
l,}
oT}
r`}
tx}
u�^yp|
�H}
�0�_�{
.��tD���(|
zd�`�.Х@|
i��yL|
a��	X|
rĤ��d|
�`�
ul�||
iT	s��<�
���|
a�|
���|
k�|
n�e����|
�����a�	g�|
�$�
�P����|
�<-���
�|
s�
��}
a���H���}
ot� }
m<}
r�����j����3�H�����id���`i���p�a8�l}
r�� _k�}
th��8����}
a�}
o�����yd��}
n�}
t�����}
a��D�����}
��������}
�@�	h~
bh
c|
f�
g@�hl�
j�
l��
m�
n4�
pL�
rp�
s�
tЍ
v��
z����~
�L�
���
�X�
���
�0�
���
�����	D..�~
e�~
i
j
k�
n
o4
r�~
�x�t�����~
�b����5�<���rlH����~
��.�~
�����~
z���~
s��x�g
n\����y(�
k$
n,
tD�����D�yH
�,��8���@
�|(���T
u4���\
i�#$���t
a���@���D..�
a�
b��
cܞe�pf$�
g�ph�i
j�pk,�
m4�
n�o<�
rqtD�
u,�vay�
��.�
d0pi�z���H�a���� W��p�8q���y��yT�y�!y��qk0qn����T�
l�z0���\�
a��
eĀ
o�
u��
�؀
�������
g��
l�����0�����
�L����'k4�D��Ѐ
�X�1��
l�	bD�
d��jd�
lp�
p�	r���	��
a��
d��
é
lX�
m��
o��
r�
y��
�p	.P�
ad	�
8�p�	yX�
a�
��	wl��
t���x�
�L:��������
���h0����o��lT�	g�	l$����.�
a0�
h8�
j@�
n|�oH�
s��t��u��v�
�x:m�
n�:t������$��T��P�
�\��L����4�����|��$	aH5it�
o���l�
k�`	W(����
r��
t��
m�n
����d�����
u���		��
b�
c��d�
g�lبn��p�
t\9u0.Ȃ
aX�
iD�
�8��\
���0������ �
l#8(�
t�z���4�
�4�@	P�
dl�
r�#s���ltL.t�
e`��|�
z���
s����
a��
o��ux�	��$��#��
sP(��L�m'̃
lT'��
؃
a0�
c�d��e؄
p�
s�
y$�
z�
��
ż)�� �<������������*��8c�de��
k��
sh�
�,�,	L�
ht�
l|�
n\+��T�
���(,�p+	��s�*����
�p�	����
�,)��
o?��	��rĽe0���Ą
�HG��̄
�5� 5���
e�4������8	�l@�s�8.�
a�qe?y�@�@��,�
a��
e��
ią
oh�
�܅
�ACx�
l��
r��
t0A��T�
��
�TA��_hA�|ABGl��
n0BthB_pB����
l|B��
l��
s�B��B��ԅ
�C�������
���
��C����
o��
�8C�
dH�g��
j��lĆ
mT�
nĩpp�
s|�
t$�u��vpC��
�
a��
b�e<�
gH�
il�
k��nȈ
o(�
sX�
t��
u��
øc�h�
l���
th�B�D��@1t�E��E��
.�E����
a�
i�
l�
o<�
tD�
u�
��E��E���
�L�
�PFopO	W�F��F�
k,�
l4�
t�F��Fi1W�Fo�F��F.`�
y��,(�,hG��h�
z�G����	t4H	 �g�l̇
s؇
t\H����
��
�����
��c�@�
����\I�8I��ć
ztI���&l@J�HJ�
c,J���
o�L�lL	�
d@	s@:�dN���
�tN.$�
�N��0�
y�N�y
k��p\�
shP��`ck��mQj��
�DQ0PQ	x�
d��
n��
r\Q����
���P�N����
a�No�R��$im�R��
kX�s�r��T	؈
c�
rXU�dU���
�<U����
�pV���`t|V�
l�V���
a�o8Y	4�
sHY��<�
���
��X����
rH�
���
����ż���h�
t<[	t�
ḷ��T�o�[
��.ԉ
a܉
b�
c<�gD�h�
iL�j��
n|�s�
t��v �
��[�\�\�t\��\o�\���
a0]�(�.��a�]o�[���
��]�]��,�
��]	X�
j`�
kh�
s4�
�ث��]W^�t_��TBaee�i,�
o��s�et�eup�
v��
z��
����$b	��g�
r�
s��tLb����
�@�
��e��e��b�i�b��Ԋ
a��h,gnЊo؊r8Xt�B�@c��
o|h��4
��h���
v|h �
dPZlder�i���k��rT2�:
	P�
n(:
��X�
��9
��d�
� o��k,fl�CvXo.|�
aȋ
e܋
i�
o�
t��
Øp��p����
���
��qtԋ
n@r9PsSLtS�
s����t�@uF�u�dv���
a�
c�	d|�e��
f��g��
hČ
iЌ
j�
k�
lD
m$�
o�	pD�
r\�
sh�
t��
u��
v��
�8�
�xj�w	p�
l�w��x�
� l��g�D��`�L���z����
o,{���De��	i`{�r��
��?	eH�
���
��܌
�l|���
�8�
���
��
g�|���
at}j�|�
l$�0��0�
�h����eȄ	u�Yt��T�
z���HEe|�
�h�������dE���	tl����\i��
o������d�
k��l`En��
s�
z$�����
a �e`�
i`�o�
�h
�t�����ԃ��	��l����
�<�
�p�
��E�l��x�
�Є��	4�
d(�rP�
s\�_���X�X�
n@�s��4��<�.P	a��
p��
�����
s ���rl0�����
��F���\�>����Ď
e����̎
lX�؎
l�����
a��e�
l$�
r\���x�a��e(GitoHh���adGe�	iTho��uth���D�
kx�
sܗt����L�
a̟l��
rď
s<����dGeȋ��5	��
g�5����
��5����
�(4���
p�3.��
a|`�����LO��$�rЏ
���H�
a��
b(�
cl�
d��
f��
g`�
i��
jP�
k8�
l��
m��
n�
oL�
pğ
r8�
s��
t�
v�
w�'x�
zd��iaP�b�c��
d`�eh�gp�hȐ
ix�jܐ
k��
l8�
nTjoD�
ph�
sБ
t��v�
z��
�(��0������i�������ܑ
����jԐ
v8��0����ut�l�
p,����
a0�
i$�
�Fy�	�
z ����
��t����<�aP���D�a\�
�]_x���T�
�H�x�
p��
z|�t�����.��
a��
o��
�������P��\�����
�8�����
�����đ
rh�t�#��D�r�����
e
�p��x����
�(�.�
�P����
hL�
s\�_l���8�
���.@�
����X�
t����`�
i�z��
�X��8�����
����Tkf��rP�����
n�"	r�
zX�����
a4�
ePlg��
iؓ
o�
y�
À�.�
op�����$�
l,�
r������
���
�М	���
�l�	��	d�pg`�
l[n��
r����L�
mD���T�
eH?l 
r8�(�.��
d���t�
eL��	H8l��
pD�s��,���[dȓ
sXlz����tt�Г
nH�	�$	g�
l��.dðJ�H�.T.$�
e�$	hh�lhsP�
�	�s4�
z�UP�v4�8<�
n���D�
��$	�����gx�
n��\���p�
s�������
a��
e�
i�
o<�
u�
�0�
ň.8��
gԔ
l�}r`c_���̔
l8�vT�0�
nd�|����
� �
�������#�tl�x��(�
���	�%	g�	��D�
a��
eЕ
i<nl�	o��
ut�
������ĕ
�ܕ
��
�(`��t��
lX��d���
tT����
e�	��
r��Dp\��	,�s�����dl�
n����
b��gl`j�`nt�
p�`r��s�`v���
�
aԖ
b�
e��
f��
i�
l��
m(�pt�
s��
t�
v��
��
�4�	��g��l�	r��
tH��|�
��
��
�����
�`���
��_��H�����
��
��Ȗ
�h9t���
�P���
�\�
rh���
�t<�
j�k\�
l|�
mԗ
s�
t�
��]yDt�! H�
n���P�
edk4�l����	aL ��p�
b��
eȗ
�`o��
h ��
.�`i�`k��
s� y� 	��n!����
��"���
tPb�$���n�T$���
e�
iop�$���*	Ô%	L9gH�
r\�
t@(vL(	(�
tX(��0�
�(��<�
��)��)��T�
e\8
�����h�
ap�p�
g*��|�
od��h|�����
�l*Ę
mИ
n�
x��
�*��t�i<+��Vd��f0�g,�H,� ,	�
rP.t$.�
g0�
n8-���
e`�
n�.��.��(�
fD�
zD��/�d�	�h�L�
v���T�
il9	�0vl�
e�/	t�
l�/����
�0����
Ü1	l�lQ	Q����
��P����
ä�ę
d�1	Й
a�
n(�
tP�
vh�
z��82��ܙ
��4��n�
y 5G0yy�6�� �
t�cu�{	`�t({��8�
�7��D�
�$��.(7��\�
i�7���ra���
=d�����
�4�����
�08���
l�rt�7����
eК
�9	�r8��Ě
�t:	�X.�:��ܚ
��:���
��9	�
k ;��T.d�bD�
e��h|�
ik��n�Mr$�tԈvl�
ð��<�i�kX�
tķ�0<	v�;��`�
�$�x<��.��
l���@�?��
pԛ
sD?����
aܛ
b�
e��
i�
p,�
u`�
��f�At`B��<�l̰r@C��g�
lTD��gD����
e@�	k8�
v8�,�
ŤD��D��$�
��,	� G_�F	@�
lt�
r�:s��
tlA��H�
��
�T�
�lG��T.�}tH��,H��G����
e�+k�H��H��
d�g@lXtmĜ
n\I���gԜ
tpI_Y	�8K	ܜ
c�frL(L���
�4L�� �
l��
���^	���
i�L(-	l�ts@�
tdM��M.����M	H�
l�N�<N`�
d��
g�tlĝ
r8�s$�t@�utN��
h�
a�
d �
e4g��
i��
o��
t�
u�
y�
��N�PO�P,�O	̝
t�O��ԝ
�X�
�`u�4.	��.	��
����dQ���
hS.8�
y�R�
g�dl@�
r��s؜	��S���g\T���tpT	L�
kh�
vDU��T.x�
i�<nDV�1pYt�X��
l��	�p]��
nP[����
iԞ
rȞ
�,\��(
��]���iH^`�		Lig�
n<xyXw����
n�^.l�ht�v,b��c��f0�
s�e. o��n8�
zj��@�
ih�
ot�
p`p��o`�
s$q���
��,��	��
kPq����
�pšb�
c�
d�vk�vnl�r��u�s����
a��
c̠
d�
e`�
g|�
i�
kL�
md�
o��
s<�tȣ
u�
vX�
à�
�T������0t�<t���
a���x���(�
s�v	0�
cx�
f��
l��
r��
t�v��<�
��
��x�����
��
�w�tw���l��
mP�8�w��IiP�p`x����
a�yPy����
htzj��,{��Hwm�vz�zؠ
d0�g8j �
k8�
lL�
m`�
nl�
o|�
p��rȡ
s��
t(|���	e0�
l<nQ�|��ԽmP}T`}��D�
eP[_�}��X�
t~'~#(~��t�
a��
e��
�� ��~��
l����(�8s	��
lX~����
��~���
tń���~��ء
e+�t$�t���
a�
i�
nL&��&���	��g4�
jD�
kP�
l�,4�
	<���<�
ad+�8�e�Ov��j������h�
o��p�
k��
lȢ
nhxpԢ
s�
z����?����
i��
l�?���a�@# �����
a����0�m�Zt�����d	mL�j�
ü��\R����
chR	�
l�N���
��<	���sxZ	(�
s�W��4�
�����@�
�8���P�d�X�
l��rx�
x�qt��$;.�g
b��
f��
sytyv�����
�8�����,�j(�z4�t�r|�tЎ_��	أ
t�	�	g��
lp�����d��@2j$�
n�����
a؏�h��nr����,�
a��
eD�
izk��n��
o��
pȥ
s�
t,�
z|�
��̒	��	r���p�
� �
�`�
��
��e����l����
l�m��
t������b�
e�qh�
i�
n\�r�*tH�����8.lmi��
n,�t<����}��QԔ�0�
l�1	y��	�
l(�s̔�P���Hgt�8�
n�.p�
n��
ô�	P�
r��
z�;y�8���x�
�ؕ�$��}t���������
nt�P��
eP�����
z�l
�d�ԥ
k����ܥ
a\�
e��
iio�
u(�
�Ħ
�4(
���\l�	�
l8�
r ����
�t�
�L�.D�
a@��T)
��dkh.	l���L�
l�r���	l�
j��
k��
m�+
%�nMs��������mx���
sD�Cئ
rP�����
��/
n(���Ц
e�
��/
8l����
��1
�����
r$�t�L
�����
tĝ. �
eT�
m`�
t�����@�
���H�
���\�a��
ȩ
o��
�xa
.��
e���x�
rha
�ȵ
s����
e��	��
th�����
�l����ħ
r�����ا
k �
l(�
n4�
p����
a��
e0�
il�
k��
o��
uT�
�ܩ
�Ԥ�\�����a���D�	�$gl�
lt�
r��tp���<�
��
��3	���
��_ �.(%a�pg��
lȨ
nԨ
r�
sȨ���%ld�rt�v��
�������
�����pe,�.Dq�t���pzȫ
_��	�
j�
k��	l �
r �����k��n�����
i$av̰8�dH�
k�sm �	n<�
���a`�
��D���X�
�����0s�|�
��ܣtزx�
l��
t|�
tx��4���
r�_ط	��
z@�
Q�
	ĩ
d�z����̩
�������
o�����lT������
a����xe@�
r������,�
d4���4�
e��	l�b��
c��
d��
g��
hȫ
j�
k@�
l��
mȯ
n��
p��
r�
s��
tĶ
v��
z�����ŀ������
sD�����
e����D..�
b�
e0�
g8�
j@�
kP�
l\�
n<�
r�4t�
��	�(������
t��<p�gs�����
�d�
�l�
��y�y,�
 
��H�
aP�y`����Cx�
lp0_t���T.��
e�}s������k�Fn�}s��t|d����
n48	s\�����
e�Kr,�s�
t`~��	Ƞb<�
dX�
k\�ld�
m�~nl�
rt�
t`Cz�����
a��
e��
f��
i�
o�
u��
���P��H�
aD���������P�
a(��\�t��.l*t��	t�g������
�8�
�ح
������
��
�P�|F
cԬ
dg��
k(�
llr|s�t����x4�	\rh�����
������
�$����a��	�
�����
�������r�
��	�klȏp�rT�
s��4��C��
v����\�
�p���h�
�P�	t�
lh�����
�`�����
Ø�_��t}d��kȭ
mP9	rЭ
s\����$�l�����
r��s�e���0��
t@�.�	�
l��T`�
kh�
np�
px�
s����(�
a��
e0�
uL�
y��
� �
����t��D����B�b�l��
r8t�����
��
�������h�T��\��iخ
l��r܁s�Tz����e�Tl�
vЁ�Dc	d���mb�
��	��
kt��������~�D�
�`
T<�
t�
� ��.x�eh�
i�.8Qa4
	\�
g��
t�
p��|�
a��
i��
�\�<����
��Lil�Tt�n�����
a��
eT�iL�
y0�
ô��8�b�
e �l���d�
kD.���
. �
k��eh��(�
��	x�g��l(.<�
e��
id�od�
�\���:	�l���".8Qa�"	t�
g$;	m�'�������Ȱ
��&����
l��
ì-����a 0W�/	��
l`0�b��	j,�
k��n��r�s��v�0��԰
a��
d�
e��
i�
k�
m�
o0�
tl�u��
vL�
�P1���7t3,�2	8�
r�2��@�
�x�
�����5o�5��`�
b��
l��
n��
�б
��5h�
kܱ
md5����
e��
�$60�o46u<6����
�H6uP6��ȱ
�p6���Re�,\
	�
j�5���
�p7@�g��l(�
m$�n4�
s\�
t:�`�l;��H�
sT�z��T#���z('W`;��T�
t�<���s��
z�;	h�
s��
t�<��<p�<����
e$>.Ȳ
e�=��
dXcl��	p$sH>�L�%�Nв
z�?��ز
e�\�\@���
�h@����
ìnt�@�
sxBH<	s<B��$�
ep�
rx�
v0��\�
�LC	�3.h�
k`C��L�
��0�C�<�4E����.��
eԳ
�|�E��
i��
k�%��~������
y�E	��
n�E��ȳ
��FT	4�
b<�
g��j�kD�
l��n��pL�
s��z(G���
a��
e��
iд
o�u�
z`�
��/��F�xG��Gt�Ht�He��
t�H��T�
����@���
��HI��x�
axIt I��
g��lGrL��`M����
o�L��
k�N�|Nȴ
rh8
.����	�Q	ܴ
l��thQ���v�Q.��
aH�
el�
s0�
àR/@0l@�
r�R�� �
�TL�(S��S�\�gd�	m`�
nLls@U���\eTY.|�i�[��Zx�
l�Z����
a��
cȵ
e��
o��
ø�
�ď
.��
a`�
����
sH]�g�
l\�
r��v1U�^���
td^�
m ^���
e4�
kH�
v,�v�	�
s�����
��,��(�
�D�
n�
��@�
e|�
�
X_��T�
il�
s��
�h`	\�t�\��t�
���
�c� c��
kXml�s�cC��
n�mr�c�Dg��T.�a��b$<c �
e��f 3h��i|�
k��n�Mr��s(3tԈvh�
�|��hWL����
b�h�
d�ik��m<�
s�i��).�,k��H�
z�j	P�
sth��\�
�H�	���
�lly�m8��
kt�n@�\�����
��m����
ÀuP�n����
s�zt�zȷ
j�
n�z��з
a`�eA	ohw�
lw���
ad�jp�l�r{����� �
k����(�
a��
�ܿ	4�
z$���D�
�������
e�
l��
n�
rP�
�x�	|�r����x�
���
�������dl������
eܸ
sи
�@���
n ��\���ȸ
��
���o0��#����
e�!���a�ilku �
�0#��@��\���K�\Mx�d��
g��
r��
uM��4�
a��
e�
oH�
s�yQM
Q��h�
eQp�
r$Q��|�
o�Y��`�.h]��c��.ȹ
i�
u�e�Թ
p�e��
d�e7(i�0i��ܹ
z<i�
s�w��v����
i�u�
l$�
n<�
sy��H�n��	yP1w
Tz��4�
z�����c����
a@�
cl�
d��
e�f̻
g\E	h<�
kļ
lԽ
mо
n��
oL�
px�
r�
s��
t�
u,�
v`�
x��
zl
�$��a�eL�fp�h�j�
k�
lP�m�C	p�
t�v�
���x���p���P��H�����
�(���8ax���Pa��l,�tܘ.,�
i`���4�
h`�L^aH g��i��j�o���\���L�
e��
i� o��
����P�	��nD�z�����
��X�o8�����e����
l��nd���${a�>e�
l(�
r�
����$���
�t�Q��	��
k�P����
o�P��� �
a��j���4�
i\�
l��
o��
r���l���T�
e�u4�����
b�o�	r��l�
n��
s��8�����
zp���-i�<�c�
dX�n(�����
a�
eD�
i��
l��	y�
���_0�	�
gP����
���
�hY.,�
yؽ�
g4�
x�\���ؿ���<�
m`�
nl�
pt�
r��
s����ag��4�� �	����|�
k��
������
o`���
tp�����
aF	Ä�eT���kp���Ƚ
aH�
e\�
m|�
r�
���	 Xb,�
d��j�r8�
z�����
�`��h����
� �U �
�	�X1	��@�
s0�n8���T�
uX���h�
i����p�
e���H.����
t������
a�
k�
t����
k(An\�r�
t4du ���
��
aT�
dh�
e��
f�
g`�
i��
k��n�H	s��t�
u4�
��
��Q�_��j��	�	lH�
r���$�
��
�l#�T����t���<Be��u4G	���.��g��
k��
m��	�\�	����
�������
�����~�hw��
rd�����
a�G	lп
rx�	����l��s����ؿ
a$�
e��o�
�8�_��	�
r����
���	�����	l@�
nT�
tH��
����8�
i8�	�D�	��L�
i����cx�
g��
m��
s(�.��
a�����������z�����u<vd�1l�����
�x�����
�@�	�
rT��t��������
�(���l�
nH
xt�����b(�	h�i��j0$nlUo8�	r�t��p�8�
d����@�
a��	l`�
p������ip�
o��#����	�a�	g�i�ck��
o��rh�s #t��
�����p���I	���'d�
l�
m��y�����#��
s�
uP����
a�
e(�
iT'kl�
o��
t��
z���8��Gl��p{.D�
mL�
nT�
r\�
s����$��P��x��\�d�
r����x�
v�����
a���
nP����
i�.��
t���Ii��
r�
��$�it�ox�����
����t�	i��
� ��
m�y��
dT�r����
aP�
Ô�Q�	<�
n���D�
������t�	apm`l�
nt��t�
e��
o��
��	�X.��
j��
k��
rM	t(v ����
�QQ@���.8��
m`!	��g�
j,�
ld�
n��
r�
t4�
v��
z�%����a4��*#�*��$�
aD�
��e,+��<�
�|4�<4P�
o 0��X�
i��
o��
rěs��
y�4X��	6����\7��
n��
r|7����
a��
e��
�,u�n�8	�
l��
s@8����
�X�����8W89�0A��Ȗ�,��D�E�
c�i�$
lP�
r<C���
ex�
i��
sd�
���E��F	��iLD��X�
���
���
�PGT��
k��
r��
v�G��G�H��He�H�I�I��	�La�
e8�
i0�l`�
o��
s��
u��
���
�<J�\J����
�X�
�h�
���
�\K�$�
eK�
l,�
mxK��K��PqbMTH�
kP�
sHM�Nt0N�XN��N�TO_hO��p�
�O.x�
�P#�P���
�tP	��
l,r�`p����
��P��P����
��P����
e��
�\�m�w	��
t|V����
����npPr@����
a`�
e��
i�
l��
r�
v��
�x�
�T�T`���@�
e���H�
dH�T�
n�(	\rh�(��l�
��(��x�
�Ŀ��
k`�����
e��	��
p�����
�H�
�`�.����
l����
a�e��
ø����A���
�,�T8���$�a��i��uD	��K_��	�
lp���$�
�D���0�
�@�	<�
z��lFh���T�
�8�	`�
�|���l�
������a��
e-i8�o�
u��
�L��������|��(�����
��
c
����
�`���
��3��
c8�
gx�
n��
p�����
a��
e��
ø�`�
d�(rP�
s���$�
a�e�QgX�
�������h�
��tp'�%��p�
e��
k��
�pCC((����
�4(����
�\&��x�
��*��*	��
r�*����
��)����
üV��
n�
t�
�g���Mn�o�n0�
s�o���
aP�
edoh�
�|�
���p�tp��8�
et�vHp@�
l�q	��r�q��\�
��t��t��t�
��{l4|����
a��
e`w	��
k��
pDs�
t�4����
�,�
�����|��
m�j�����
e����P9������
�����
rĿ�����
r��	 �
d	��hN�	��8�
�@	D�
gx�
k���P�
a��
e�
u��
��	�=� =	��
r4	.��
i�	����
y�		��
n�	����
� "	���Oe��
it�� 	��
g�9�#	#��
f e	������d	���s$�
z��
��b	�
s�e	P�n	.XRa�m	��,�
yTl	8�
g�k	��D�
a��
e|�
���
�T~	m�|		h�
d}	��p�
��s���
���
�t�	��
l�	����
e��
�l�	��
k�
m�
rT�
s8�y`�	����
dP�		��
l\�	����
��E��	��
r̡	����
el�	��da,�
à�	t��	��$�
���$�	.8�
a`�
ih�
�X�	��@�
zh�t�	���p���
H��
k��	t�
nd�	����
ex�		��
mxW��	�����t�		��
kx�		@wð�	����
�8'
0�
b`�
c��
d��f��
g��
i�
k|�
l�
m �
n\�
p�
r��
s|�
t�Su��
v�
x�
yL�
z8)
��`jD�
l��s\�_�.
�$.
��L�
i�,
��T�
kl0
�x0
l�
a��
s�0
��t�
a4v��
�1
�1
��h	
�����8
��,�r��8B
#dA
��
l�D
��D
����
a�C
��
kXS	nP

p,�
r0D
��	��
a��
e��
i�
o��	r��	s@�
uL�
�(�
� E
��<Ci�E
	hCl��
r��
t�E
��8�
������� �
��	�����R1H�l�
k�E
��t�
o4F
���l��m��rh�s��t(�v�F
�@F
��
rpH
�d��
m��
n4p��
v��t�H
t�t��
�@�����
������
�J
�
z\L
t�L
�����L�
��M
t�M
8�
rlN
�O
�xN
T�
d�.j��
k��
n�r��
t�N
��
\�
a<�
d��i��k��
l�/o��
t��
u,�v�
��O
����t$P
.��
yP
,|28�P
��
mlP
����
o�80Q
����
r<Q
	��
b`Q
���
��S
�
dS
���
oH�
t S
 �
l@S
��0�
a�S
�����4�b��h̦r��
�PY
	P�
r\Y
��h�
���
��X
����
e��
it�
�ܥ���^�tY
�fg0Z
�T	nD�e]
���f��\
����
À^
tX^
��
t,`
�ug8�
k�
l@�
nH�
pl`
��	��
a��
b��
e��
o|vp��
s��
t`�
���
ńa
t�a
�b
t�b
	��l��
r�b
��P�
� j����<��ğ�̟�4c
,\d
��
e�d
�le
hil�n��
rH�sg
��<���l
��s�n
��
8b	����
��n
����
�n
���v��p
����mlo
�
lktT�
u�o
���
a��
e��
g��
i��k��
o��s��t8�
y��
��
�|	j(r
88r
��\�
l\m�q
	d�
l�*z�q
��t�
���
���� �
����u
�yl��
nldr�v
��	4x
����
ry
�Bl�z
l��rl�s�{
	�kc�
r�{
�`|
���y�`��$�
,�
	�
t؀
	Ԡl��
.,�
a�eĈt؄
H�
t�
��P�
a�Sc��
e��
i��
p��
t��
��
F�
+kt�zd�
��D..�w	o��
rH�y��j��
������
@�d��g�VjL�
k��lp�
n��p��
r��
s��t$�u��v\�
����
a�
c�d�
e�f@�
g`�
i��k��
n��
o�
r8�
s�nt��
v(�y��
����
����a`�
nh�
o��_<�
�|�
.|�
y�u,<Gt�
��t�s�
	4�c��
l��
t�z,�
����
���
��� ����
��l�l��
mP������3�,�
���&l��
�|�
8��
����
�`�
���
�h�
Tl�c8�
k��l�V	nȦr�
���
jT�
a4��|6L�
nĐ
�e�Olx�
n��
s��
��l�tt�
��0Bk�^�]����
��
����
à�
�<�
��
p��
s��
v�
�t�t��
����
z<qt0�
C��n��
rT�_(�
��T.0�
i$�
�\�
	@�vh�
���
�x�
n�
T�
c\�
k��px�
tl�
��0��84�
��d�
��W8�
�@�
����
��
����o��
v��
Ô D�
��,�aX	e��
i�
s�
t(�
z����
���
��
k��
n$Cs�
t��
Pt�eP�
����
zP�
��
���
o,X	y,�
.(pa�?eD�
kd�
lp�
t��
p����r.X
L�
p\W
��X�
o�
��Lurh�
��@�.��
t$�
��
��
nL�
����
o̮
����
h�
 �
����
a܇e��
i��
u`�
`��`��
��
��
d�
g��
����
i�
��
��p�o�Rt��
 �
l�
��(�
a0�
4�
tT�
��@�
a�
	��
b �
cH�
d,�f��
g�i��
k�
n��o(�
pd�
r��
s�
tx�
v��
�t�
PP�
����
y�
��
gxTr��
t,�
����
������ܹ
H��
aй
>\�
����l��
r��
��,Xa�
��
���
���
�`�
��$�l��s|�
���
0�
j4xn$�
��8�
a��
eĿr��
���H�
��`�
�T�
��h�
�
t�
s��
�p�
	��
rH�
����
�|�
��
����
l��r��
��T�o\�
	��
rl�
����
��
��(�l��
r��
���
���e�xoV�(�
���vu�
H's4�
���
ad�eL�
i�vrT^	��@Bt��
@�
n(�
`�sh�
��X�
a��
�
	X�bd^h(xiPr(ysh�t��
v��
��t�
�$�H�
�d�
��
p��
����
eH�
k`�
o��
p0�r��
z �
�t�
v��
	��
k��
t��
����
�\�
���
��
	�
t��
���
������
�h��4�
nt�
��<�
a���4�
t��
X�
lL
.x
��l�
o��
t�
��t�
th
��
r��
����
o�
�<�
�D�
	��
l��
�TXe\�
���
n��
.��
e��f��
kX�
B��
���
��
j�
���
aL�
od�
r8�
�(�

8�
	$�
gP�
��,�
��}�D�
m����������Ie�oX�
�8���op��\		��
b4�
d��
kh�
l�
md�
n�
rx�
s��
t��
v�
z��
��
vh
����
e��
o�	����
b4lH���������
��	H�
m�
�L���
�P�
���
���
���l�
n8�o�
Ü���ęeЙ��X�
�����`�
�Ȩ%��	x�
tl����a��
e<�i�
o\�
r`u��
�0�
ż�������
� ���
�����
vX��T.�~d�	��
rt	�{r�
s�����0sxt���(�
��0�	<�
s���D�
����P�
�����b��
c��
g��
tI�����
i�.~a�����
yh�e0"����
���
��!����
��@����
n��
z�
0�$���a�
e$�
o�#����
b4�
l���%T�l|%��O	��'��,�
e\�
iP�
��'��'��H�
����)��	��a�Xd��
e��
k8
o4Ysx�u��
�T
�*���X���
�����+��n�-t<.�H.	��
rX.����
���
�.����
���	m��	����
z`�		��
s�6�(6�
gT4���
e��
tL�
zL�
�<8���%s�7	4�
s<5��@�
�p�
��o�{��\�
sx;	d�
k(<4<��|�
a��
e�
o��
�l��g��
l��
r|�
��
��
m�����
eȚ��<���n|<	��
r�
s\<����
��
��<_=��_P���
l,�
s��t�t�>�4�
al�
�>��<�
sx��̼�X�
l?��`�
��?���v��z�A����l��
t��v@EtPE����
�E����
�8F��	T. �b��h��
i��j�
k��n�MrԈv���DH��
e��g<�(I��`�	���,P	`�
al�b��
e�~fp
g�k��
p�
rD�
spt��
zp�
ØP���
��
��P��c��	l�R�����\�	���
�h���W��W����
��W����
�dW��
k�r�_��t��
v�_.�f��@�lx�rP�sh��g��
g0�
l��	n�g��
��
a8�
e��
i��
j��
k,�l�
p,�
tx�zh�
�dhN|i�gH�
zk�pkW0k	P�
k��
��h��X�
���
��
��k�k��|�
��l�(l��
n��s$m�8M�8m����
���
�Dm����
e��
Ô`�`M��
l0m��M�Pm����
a0n��m	�
l�ny���do��$�
i<�
r\o�\p����	ap�
n|�
p��r��
t��
z��
�
Y�s��h�
y�t��,�e�5i�drTv���a��r��
�xv�v����
�x��t9
�X.�t����
�@w)L�t|C�
r|����
��{����
��[H��p����
n���
n�~�� �
eT�
f`�
l��
r@�s��
u��#����L�
e@�����a!ut�
�4��������L����3a�e�3i�Bo��
u�3�t�#l��x���
nH�����
i����
t������ �����
�H���
fD�
gd�
k��
n,�
s�
t������
a��
el�
i�
o��
up�
��������0�
�D���T�
i8�
������@���\�
l|�
rP��8���t�
o��p�����
a��
�������
d��
s��	�9����
��GX�����
s��
zG<�G���
iD�.��
a�
ol��x���
m��$�
i,�
r���4�4�P<�.4�
a��
�h���<�
y��	L�
g4�
jT�
np�
t(���X�
��
���
�|�
�P�
X�8��
b��
h��
i��
j��
k�
n�
r�
s�
t$�
v��
�����
����������
���
�����
��
��
��
��
��
��
�y���,�
k�5P���@�
z����H�
sd�
y(�.�a`�����$t0.|�
a��
o�
����
sL��
c��
g��
n`tt�������
o�����	a�e�`����
d��
�@�����
�8(	$�
n0�
pX�
sy��.�,���
i�-jH0T0��8�
�0.@�
�d/��L�
z?j�;d�
g�i��
k��
l��
�ĸB�����
��?����
��?��?����
l�D�.��
j��
k`=����
���y�y�f�T	ndf����
i�e����
la�
lD�
md�
n��
sAx�`
�@h,�
d�SjTh��4�
a�jT|�
gP�l��
n�j��P�
a��
e�8n��
t,p
t�j��k��k��
s(l��̠r�n#��
z8p.��f��
t��
�xp����
aLs#(s����
e4r	��
d�
nH�
sh�
zL�
���
�
s�u���
a8�
e(
j@�
o��t�u�l�
.|y����z��t�.T�
i@z��\�
s�|��z	t�
m��
s�~Ul�����
b��
mP�pl�8�d������
ah����`	a�
cT�
kd�
l��
m�|n�pH�r��
t��
u�
zD�
�L�P�
e����
h4��8���,�	���� �
�	,�
g���8�
���
�4
��&o���
��&a|�
e �it��D
#�
��\&i<&�4
	�
i�%
��'a`'e��i ox�r�'u��
��'
���h'�������2
X2
��
bt;
�(k$�
t;
.��
a��fl�kd�lt�p|�t@�
è?
���e4�
��R��L�0@
���(�|�
��`x��P�
e,]
V
X�
l4]
��d�
e�\
�p�
g�g
�
�f
��
d�
gT�
n`�
rl�
sx'
t0�zh\�Pf
����
a$�
e��
i��k$�
o	p��
r�
s$�
uL�
wX�
y��
Àh
B�X.8h
����
��j
���rg(�
o����1utk
�
k�u
_�u
	4�
ru
��<�
��t
��H�
�y
��dwìz
��(|
zx�,�		x�
gT5����
��
����
��
����
s~
	��
n��
vD~
����
���
���
�<�
�x�
�0�
�`�
Ѝ
����
i��
�|�
����
e�
��
m8�
���
eL�
��
�
lX�
rp�
sЙ
V
ܙ
	8�
a��
��@�
�ğ
���i���,�
8�
��h�
a��
���o.L�
	|�
g��
l��
rH�
st�
v@�
��0�e,�
�԰
��
k��
����
a��
e0�
i�
�(�
t�
��
m��
s4�
td<Xp<����
ex�
	�
nL�
���
���
���
(�
d@�
n�>�
���1s�jH\�
	T�
rh�
��\�
�Ķ
��h�
�T�
,�o��
r��
s��
zx�
����o�
��ܰk$!���
����
z��
���
����
a��	���
	��
r�]H�]	��
c��
����
���
����
��
���
n��s��
�
rH�
���
rX�
	0�
d��pT�
r`�
sd�
���	��
��Brh�
.�!ň�
	l�
l��
�f��
g��
n��v�
����
a��
à�
��
s��
���	z��
	|�f��
np�
����
��
�,�
���%e8�
	��
gD�
����
���
���
i�
�<�#��
X�g��
	зd0�tX�v��T���D�
e�Pp�
o��
ìp	h�
r�8��l`��|�
���
�d���
l�9	��
��rH,r	��
sX�����
�8�
bL�
c|�
d��
g��
h��
i��j\�k��
l8�
m�
n�
pL�
r�
s��
t�
u$�
z�
Ì
��h
st
 �
sL��,�
u�
��D�
kL
#���X�
e��
��`�
t���p�
a��
�(
���\���Bx
����
�4��T�ax�o��r�#(����
u���n,T0����
o��
tDL����,	i�
r���Db�dp�j��l\�
nd�
r�z����
a��
e��
i�o@�pl�
�4��� �!�t!��t������H���!(gPl`r��s`#��kԸm��
n�c	r�s�z�#���"f��t'���3
a(�c�
i`Lo��t����(}�(���
�h(\�t�
�L���*���
rT���+$�
cd�
i�kt�
l�
m�s,��,�
a��
g��
u��
��+'����,��l�
o�,	d^h��
nPr�,����
����|6��6����
a�.@�	n4/d+b�rp/���
aL�e�iPe	o<�
s��
z�
�T,��0��0	�
g�0���
�@��$,�D,�4�
�|,� 4t�4H�
z�4P��e��ipPo�6��b\�d��g�l��
p�r��v�6.	\�
a�
e�
idXk4�
oT�
sd�
t��
�H�
��6�47��e	lT7����
��
�й�@�
�$������88��
m�8��rl�Hp09���l�n$�
p��r,s�	t����9��9�,�
l4[
��]
��G��^
. ua܃eT:p�
rx:�:�4
d��k��
l�4
mjs�z;��x�
a-cX�ot]u�
ð;�h<B�
n�<����
��
�=��?	�d�p�
t�?��@#�@��
m�Q�C.�
o�B���
s�C	L�
jl�
lx�
r��
s@�
t�N��x-a�o�-u��TRtR��d�
a��
e�
i�
o8�
s@�
t��
�(�
��Re�R����
��
��
�<�
��
�H�
�P�
�DS�SeL��XT���
t�S�
s�T��T�U��
k�
p���,U��Ut�U�� �
�����UVdW�tW�[�[��X�
zdZ`�
s�Z��l�
ad�e�
o��
�T[	xd	lp[����
���
����X���-_�[	��
r�
s\4�\L�l܄t�a�\a.�
e�
��]���
zPb4�a���
� �
�xb4c�$c��(�
��.��b��P^bx�
e��hh	i"jl"n�/o�r"t�/v0�
�hc�h	
��
d�g�
l�
m<�
n\�
rt�
s��
t܂v��
z(j����	��
��i���	v��
Ìj��o��o���
a(�
el�
ix�
o<�u�
ü�
�`p��P��X�
�����
�����p��p �
d`�m�0n|�pH�
s�vDq��r_,r	P�
rXs�$sd�
rt���� !lLt	��
k�0m�n�z��tt	��
k�Vr�t����
�v��H�a�
��
�P��v	�
k`v���
���
tw����
��w��w�
l�w���
exw$�
tXw��0�
eny|gl�
s�x��L�
e1�<y�8z��9
t�|��T�e�i�k��
�<��t}��p�����,p��~��
n�~����
eH�l�	(�
c@�
d��
e�
f�h	g�
k��
l�
pD�
rP�
s\�
z��
���m$���
p4�.�
aH����
s�O��l����4�
aX�
�x������P�
����	d�
t(���l�
�4���x�
�؂��
k���bht�����
�p����	�P�	��
lh�����
�����
���v�V����
e8����h	l��r�f|����
sT�	 �
s����,�
�����8�
�\��� i	p�����a�
e,�i�os��
ø�.��
a|�	|�
r��
t������
��
��
���[nȏ�p�,p����
e؏�
n����	�s����b<�.�
e̒������e�M�d�nTr����,a�ePip����h�aL�	4�n�z����`�����0�t@��lT����a�i����	p��,�vd����e�Mn�tyÈ��n8r�s��4�Cl������(��T������ jԿl����(ahe�i�o�sX�8���p����X��-g\��(���tz��	|s��bԴ���t���n �pH���tԶjp����eh�	ȅn�rT�\xnp����iL�oH���"� �	�4�����������(l����4ets��@sd����Z��P\ð���hzE������e�	�v$�.
�a�eDidnpo�psu`���	�ikp s,t@u�� �k �����l��j8����z`�8r�<�j�8T9gd�l�r�s@���H�$�L�����8�d����8��n`����a�s�t�����s�	�g�lmr=xP~4���y�(���al:lX�	$��b���<��H88t !���0e(	�8�:rD�v�#�#��\k$	�l�r�%�X%�nh%���a<�
�+
s$)���o�)8,8�.8�s�.����l�\/	�/#�/���eHHrT1#`1��a�p84	$Ig,tX6��
ä68Xk<R�P7��D�07��Là98�:8�U�g��tn�r�c|k�n�r�s�B���esԑ��d�����h����,p��̣.��j@s�yl�jԻ.�c��ĉ.|aLb�
c�de�f4g(h�ihj�k0l�m'n�)o�*p,rp/s;t�@uhAv$Bx�Bz�Ø*�̉�c�d�e(g|Qj4lDn��sPtp�u\y�z������hh���Ȋa�ox�����D..p�b�Z
no�r t�l��r4�
����d��Ԣy@���PQì��L�aX�
�02���yt�����y�����ip�oX��@���g��p�r�st����p�,�4�*�x*��f�A�4Ht\H������pC��|�e�i��L�lL	�j�N�Xst_��L�zv��h�ae�x�x�P���� a��kX�(l|n�r����
8a��d�e�	i,�k�	oP�pd
st
u��H�����
g(�	H����cXGi����h".�o�	�rH�����X	�D
�P
��
��
�����b��d	l@	sĸ�и����0	����$	e��<���	l���P���8	eP	t��	,�	,jx	l�sn`�p�	r��v����$�e����4d�	e��m�	td�X��t�e(�.��a(Ie���	gDk�	nЖs�T.4����	ad���@	d���	l
r��s���x���
k(
vH�_4�p����0
iX�	8
r��	��l��s�fv,���HGl�p4�&d�
s�PT����
z�	�1j�	��	�
d��g(�l̢z��eĤ���
�����
���
t�����
a,c�e�h�ik��pDs�u��z4�p����$e�h`i�sȘzx�P�\��La0Tnpp��H�����L����(P�zt�X	��m��� ������.o��D�.��r�����T�-
p�ô+�.
gP*���io,y,bn8t$#,��������3.,da�e�i��sx�zx�D7�g,�l�rh7��d���09���lD@��?��d�H��K؆g�L	��gP[,�lt[���aL
e(
�O�tLO���a�
i�
o�
rx
�$\20\	
l@
rD\��
�h\��mx\y4
a�\D�g��r\d��(k��sH`	\
s|`��l
��
�,�.x��
odVs�L
�����
r��
kX�#����
e�
o@�	�
s@�
I��
�
rp����]olR���	`�b��d4gPl��m`r�s(�t vX����y���H������D�дŰs��|v�����0������pÜ���.�b�e�h�i�k�n��r�t�uzD���c��������t�����������tȟ����nĝ.e����ܶe��Hgdk��lܸp0�t������eԷ�������
�����XÀ����.pe����xyP�	�nh�����w���l�r�t��ȠP�����eЬ���i<�ul������������à��h���y �g��llp�r�t����a�e�g�i kLo�p�s�u�y���Ŝ���a�������xo�t���l�����e<�o�����	�sȽ��������	�6rܿ	g(rls$�������p�
�h�t��>
���\�Tĥd����aXo��uH��������@�d����:
s���������s|z$��H�@��l�v��������	�l
.X����l��.��e�n��rtv���d�gt�	`.�<
a����l�h����l�e���,����4�@rl����v�-z<	Xs	�=
s�%����k�*Y *�i�t�,t00	�/	�g�zd���	�h6.�3	�n03.�a���O����<O����\Md\l|r�tM��	a4e\ito�p�t�u�Ì��U	tR��Tk nt�Yhs�Y��pa,]��[���r�t8]C0�,�]	�g�l�r s�]�����������l<_��T.4�b�^f��h,gn̦r��vt`��
k�`.aD`��z�f�c,m4�x�	Pp��Dd�@
ghnLnz��؅z�uhrd~	�r�~����x�8����r��.�jh�c	j�~	؄	�z���d�eg�p(r��z\���ȱe���d���L�a n���t����4��`!��jDv<C����e����TtXQTn�P��\a�j|o�m�.�diks	m�ss	t�m��|a�b�cdDe f(g�5
hTi\kdllmtndp|rls�t��u�v(ðQ����R��W��H�u�m	r�m���L�\���t�n
	�f�@npPn�pn8\|8|n�<t��p���Bb�clpDrxsسt@����ad�b�e iDk�lo�p�r��\-��.تe�����s(��8��pD���a�����7lػ��	$n$���,�,����c8à��xSs����T���.`�\���lz̛	�*l�s�t������������ģ��rԣ���a��s��P��D����������mH���lm4�x�����	�g��r���Dp(����t��,l����8a\e�o��D���glm�t�������ta��|l�����	�l������<�	t�sH������8������

�
���������d����z4�	�s<��Dl0r4�THsP���$adi|oX����e����P����ltn@�������l�	�sx�	@�	�b\U	�nL����������	ht���(�����.������scpg�j�l(
s�t���a4�bc�e�i�k�l,o�rp,st�yP�tńjP\sp��xah8�
�gD"���e�
j�..HP
at.�l4�n�.���aei,o��P/	�Gll/�����0�m�1��1kd2�82$l�4	�btk�r�s�4��8�H���8���P�\�x��X�e���������N��aHA���ipB��<�	i(k�s�z�C.@
a�e�CtK��J��l s�I.�e�i�o�u�zH�[	ŀG���s�K�K���O.`v�K�(r|vdJ��8�������0L��L�L��h��L��p���	d@���a�L��g�p(M�tM��M��M��r�M�N�|�t$N_8N��V=itl$t�xԘ�D�#�t��a�o��t4|����
a\e�
olu`w	0k�pxr�|����}dtd���lV
t���
r�����a�i���nx�	�r\����rT��nf�l�m�n����e���X�t4�td�_�	r���������$t��	�[
fxi�k<t�z���
��
Xn �
��`o��
lkd���T�X���Da�s�ü[XH���a��	�r���������	,�	�t���7jr$s�z�����aD��.���8���z�����	0l����8�,�	�
r��	d"t��	t"r����h�4����.�i�	���l�
r@	�b4 cD dp j� k� l� n� o!p!rP!s$At�b
u�zd �����ap-b�(d�!eH#f0�g`#i%k(%n@%o8&pd&s1t�&u�&vt!À0��	���'hl�	���?a4�m�NohÔ���	��\ ��	��p���	`�	��| a� k%t� � 	��P��4	_�	`�	��� a� g�l�t�	_	��h@a� kUo�@y�B_�WY�	� 	�,	��!�@	���c�g8!i@!o!��	`D		P�			�		��H!p�		`g�!l�!r�!t�	��\!��"��$��%��%��&��	p	���!lpm�	��
�a�^f�k,gn o̦r�t(u��v�	���a�c
eD
mX�u�!	�� 	�!d8Cf4"g@"lh m�"n�"r�"s�"t�*v�x�*z "	.@&y�)	�� aDDbx"e�"h�)j��m�)nx`o�)t` v�"�p�<*	` *	��( �8 ��*		�-	t�-	���"�`u�T-	���"�\/	��|e|�k��X1	��� e� s�"z�2	~d3	��6		�Fg�&h#l`�p,#r�tl9	��9	��#e�OvX;	��T.�!c��m�.t�$v�@	��hHl�Hr4C	j�B	
X#a@�d�#g�#m�#n$oH$rT$s�$t�$zE	�#rPE	F	���,p\F	.tF	���#a�"d�"g�"v�*�H	��	�#b($h\�	j0$k0$nlUo8$r@$t$�H	�#nw
	�8��$��x
�����	�d�H	�� #t�I	���kԴ
l�1mp$p�$zDJ	(_�J	��x$��J	.�$�K	�$rL	M	��J�tM		M		�$v�#zx��,�.�$e|����$z��	�$sHO	���$��N	���.l$�rdv�$�Tl	T�k	�� %aS	��x/o�Q	4%k`%l�Jp�%r�%s��xT	���/a�d�Jl|%t��v@�	V	�n�t V	���%a�%i�%�\V	�hV		�%ltV	���%���
��V	�%n�W	��%t%z�X		h�r�Z		�l|.md%n00rP0sX0tt%v`0z(^	.�e4^	 &ld]	��,&eX&i�0rT^	���+g`^	L&n�`	���0k�0p1t|&z�a	��b	&d<&g�&n�&r��	t$d	��@1iHd	.�&add	�h		�1g�1lĢrH�s�1tl"v̢z��.�i	�&s�i	���&aTl	�\b`'dl'kx'r�k	��
'a�'c�'d�'e,(gh(i�(k()o(ZpT)st)t�)u�'Èm	�m	��X'u$o	���thx	���(c�~	,�|		�'g}	���'�(��)��)���	$p�	.�'e�'i��	���'s`A��	��'v�	���+zl�	�l�	�'r(t@�	���		3g$�	��g\�	�� (aD(eX0g\(oD�	?gT(r��	tм	$�l(�	�0d�(f�(k��l�(n�(v�(ü T�	���	���(u�n<�	���(o��	���(g@�	�h�	���(�d�	 �	jL�	�(p)s��	���(a)eP�	��	~g��	�0�	 )d<)rL)t�	��LFr�}s8�	�D�	��XtX[.h�	`)i��	��h)a�	���	�)n�	��(8jH�		�)t`�		L8l8'
��
l�)r(Tx\�
���da�)e�)ih�
T�)t�$tĐ
�o�
 *b,*nypD*r\*sl*v\�
�� �u(�
����e��
��`W�h�
��hWi8*��
���
��T*o��z8��hhe\	�XkYrtYz,P�Yr�P���*���`j�*b��d�*p�*s+t�~���*a+d<+e�+h�+l�+�X���'rl�pĈ��<�rH�TT���+a�����aT+eh+it+s��$+r|+s����L+l��tx�`+nX���ܤjl�	�+p0����+��+�D�#Ȩ���+i��.���+n@����+i0�	̢zP�Ud��+mH��+iH,k�,l�,m�,s����
�+a,-dD-eh-f�-n�-oH.p`.t�.u�,�@���̣.d,hl,kt,n|,r��U��U��U8�U���� h�,klm\�U���̣.�,i��"ṡ������,a$�
pl�����l��	�,l-t(����,�4.�/�P��`���-a���-r��� -a<-e@�L�-f����4��P-�t-��4��X-�h6t�]��]��|-��-�]���-e�-�^#�^P�aja�-b�-k.n.p$.td����a.e�-�ddt�d���-��d>�k#�j��.e�m�pjP|	�z	,.l(�T����@.i�=gH���T.a�.e�.i�.o��
�l�x.t v̟.�.a���.t������Zk��x����.e���.c�.l�.p/s0���j�����
��.�.�\���/z�	�X.D���̠lܽ
(/b�/d�g�/j�/k0l$0n@0pT0rt0s�0t��u0�zh���4/a�cb�d�1eX�f�LgP2i@3k�3ol4p�4s�5t6u�6z�0�h��x�����a�/ø������`�D����	r0����/a|�c��`�����/a�`g��j�`k�`t,uv�����`a80e(Ht������`��\�`h���L0a|ac�az����_����l0s����l*t�y��	��b�0g1l�bp(1rd1t����0��1�3�4� 4�(6�X6�p�������0�����b�g�h�r�0�P����Ad��l`��dd�iT�k����1a4�b��h\1i�nk,gnЊo؊r8Xt�u��È�X�����Ha�h�ci�ck�ct�1u�cvT�np��0�
�1cܡ
d�df�dg�dl�dm�n�1r�ds�1t4rv�xez\�	�����1d���_��	
�1g��hTek،lden�p(2rD�sftD2v<����dP�
t�ev�n����<2e<�\fd�g�2k�fl�2m�2nXgptgr�2s�gt3zL�`����2o���`>a�ri�"mgp�2ô�������2���_@����2cgd��f8gg�2kHgtPgv�_H���0Bk8Bm�������	l2g�Bj hr��v
����d3�4
����l<�rX�v03����k8
	X3i�	
I$	
��p3��
��x3�T

�3d̏fik�3l�3m@a	nxip�3r��s�t�
��
���3a�3d\ii�
_�
���0lx
���ig�ii�r�is4
	Oc4s
�t
	�lLOnD4r4�sTOt�jv�jzl
���dT4ì
��<��h
��
c�
��`4e��li	nd�rP�st
��`�.�4e�4k�4pD�
tl�	y�4z0�.( 
��ح�\ 
���Ze(i0!
��kb�kg�Ok!
P�4a45e@5i5�K�t!
�Jldlm�!
��5�8���l��l��l�L5�$"
��unh$
�D�g�d
�X5l�d
	��
���	��(
��`5ü'
	l5r�'
��x5��5��%
���5e�5i�5r�5�)
\�g�,
x�lh0
�d|0
���5a��eT�o�PuH��(2
_2
	�5lX2
Гj6sQt84
. 6z,4
��4
	��jL6t\7
8d7
��86�7
��@6ð7
	�nd�ngx6l�nn�nr�zh8
.��
�t;
�\Qb�oj�6l��	n�6p�6r(7s`pu;
.	�6a8e09i�9oLYpT:t�:u<DzT7�P=
���a�k,uvl>
���
p?
. 7o7�?
�?
��7������h?
��Lpz�?
��Rgt7l�7p�7r�7s�7t0@
��47��8��9�:��:��:��@
��Sl$B
�4B
@B
�7d�7k�7mLB
���7a�7j��+�D���7o8\`M�M���7eC
����vC
����h8�lC
HC
��8�hD
��C
�8cP8f`8gp8k�
l�8m�8n�8r�8s�8v�x�e��D
P�D
��X8ery0E
.|8eTE
��I
��,rb`�lJ
���\e��g�J
��\rd�8�@v�J
���8��K
.pM
�|O
��O
���8e�M
��8n9tpQ
p�Q
���8e9o8�p T
	�S
��9p�R
�$9mL9n\9slT
	DT
��D9d�j\U
���k�1mp9ztU
�� ;
d���x9aV
��9j|�r�1
P����9��X
���9�PX
��9d�9l�9n�Xp:s8Y
�DY
���9aH�dԭxZ
���9yx��[
��:z�\
��tbl��n�tsp��xa
��4:a�a
<:r�`
��H:ex:r����d:n�b
��l:a����b
��:s�ut�c
�̟t�d
� �
g�:l�:z�d
B��B�f
0�
bd;c�`d�;k�;l�;m�;p�;r<sD<vT<zh\�Pf
���:a<]bP=ct=e��f>iT�k�>o	p,@r��s�etx@y�<�X:���D;l`�.L;e�l	��X;s��%�m
	p;p,m
��x;��l
���;�,�q
���;��o
���;v�;�t
U)T�;l�s	r,t
���;ed)�Xw
y
����äz
U�z
��<c$<e,<f4<l<<v�z
#{
U{
U�{
U�}
���<V��L<ih~
y~
	`<b�<i�<j�<kX�m=n<=rl��D~
��h<��=��?�T@�h��3b�<n ���
�l�
���<ud��x���<b�<t�� e�
���<��
���]n=z�<�$�
��
e��
��$=� �L�
��,=�L�
p`�
��H=ah=��,��`=��
��g�=rğ
��t8a�=e�=i�=udy��
�_g|�
��mȣ
#ȯ
��xknL�
	�=n�,�	�=r����=�<�
����e�=�T�
�=k�{l�)m0>n��p��
sh�
о
��(>el>i�>s\>����t�	H>r4�
��P>��>�`�
T|>g�>kx�
�P�t�
t�H	�I
phI
���>e�
���>k��
�>k�>l,?mD?rP?sX?t|�
��$ d?s�>�U
t�
���>�|\
��
	�lb
��?a��
?t�
�� ?a`�
p�
m�
��8?i��
�|�
���
�8�
`?lH�
��h?a�?e�?�X�
	t?d��p�?s�?t(�
��
���?���
\xl��
����a�s�?t��z�
P�
���?o�
����r@��H�	�?z���@���
�k�n�
��@aH@�X�
	�kp�
��<@���
	��g\�
z j�	d@s�.l@a�@m�t�z��g��l�s����@a�@e�@i@�p�@�t!��t���@��H���!,#,�"	�@r`#Ըm�c	n�h�fg��r(At<Mv��z�|���ø�����4Aa��e(i�A�d�<Ar�As�At����TAa�Ai��
r��eЛ��|A�ؖyT���Alp����A�ؠ.�Aô����Az�T�Ag�����Aa0���j���Ag�������AaDBe8BÔ�Btp���Bi�t����0B�����KT�Bc�Bd�
n�K��LBa�Be�B�HC`BlX�p`�s�B��tBa\Ci�Ck�Cs0�t$CäK���HL	q	g�BtTL���B��B�<L_dL�xL�Bl�	�q	k<X��\�.<CbDClLCr�U	Cl�U��C��C��C��C��
�$Y�d��Ēt��TCd�Cg���.lCe����tCyԜ	xtr�T0����Ci�	�
i$����pԻ.�Ca��z��	,�g��l��8�.LDaLEb�EcId�Je�Kf@LgdMi�Nj�OktRlXXm�Xn Yo�Yp�Zr�]s�bt�fugv�gz�DÀY�̉	xDd�Dg��j�Dk4lDn��s�Du|�vh���Ȋax��@�����y���	r`�s����Da����d@��Dg@�hEl��p$Er�����D��K�`N�<Y�`Y��f��f�@���Ďa�i�o$�yL�������opC����a�i�uX�$�
bdEnpEr����8Ea|l�Er�E�H�����
dH�y$�
�X�	xEdH����E�������iTho��j�El�n�Ev�����Ea�EeFhFi��p�Fs,���� �X	�Et�.��e8�oT@�dHF�4%��g�%��$F�\$0Fk���<F�(4��Fb��d��	gL�jJk�Fl��n dp@Js<�uHJz�3.	TFaPGe�GipHo�Hp��s8t�Hz�F�`4��4��4���FaD7���g,�lGrDGth7���F��G��H��H��H��H�$808��G��7��pMf4Gi<GvGÈ�!_'�8���Jv09�t�b��gxGj��l,7m87n�Gr�Gs\�9$<��|KŔ<��=B=��Gl�Gr�Gv�>���O.�Gd�Gt�>�$���\PeLì?��?�	�d$Hg�[	l0Hm8HnHHpTHr`HshHz ����Hzd@��Ha�@��@��Mf��t<A��\�a���h�	�H�����|B�D�k��s�D/�Hr�
�`E�4�l�Hs�E	0G4`�6
���Hs�H��HslI��H��Ht�I��I.�Ha�O��c@Idk��nLO��	�HatIeJi\Jo��phJr�Jy�Jz`IüP.�
aH`	#gP#lX#r|`��LI��I�xJ�@i�g��j�IlTvm�Ir`2
v|n.8ve8�hHv�Hv��0�
a�Ie��w�$w�IcT|	@�k�Ir�Itԃ��T.t�c̆.Je�px���d Jg4JnPJ� �.(��p�	4���,Jgh��dz����@J����sp�����oL	�0�	�v	�����Jh�Jt�����J�\���Ȗi�Js�J��	`1.L�.h$���	`�b4gKl��m��p,Kr\KshKtxKv���0�a��e��o K�д�H������s����e@K�LK��v�����������h�����X�z���Ԝa��e����ܶe������h�Kl��
n�p�Kr0�t�Kv����H�e�Ũ0��\�.��e �id�
z�Kä2�����Dg��\�ew��Ll Lr��
ä���L�� ������|��Ь���i�oL� �H�d����4La�Le�Li�Lo�Lr�LypL�ܿ	�Ms$���dL��L�@��l����LvP���d�Lll<	�Ls�!��H��P4�3	�Lg03.�LaMe0Mi�u MÀ:	Al�>8��r9��M�XM�0BC@MsHMv�C<DpJ	�I8PMt���Md��g�Mk�Ml�Mm�Mn�pNrNs\�tPNv\���(�
e���T�����Me�Mr`��p����Me(���\�
lp����i��
� ���H�
gh�
t�����&a4��yP���Nk,Nm4Nt�y��y���<Nd���DNa4��`!��rpNv<C����e��i�N�LD�����([����
�HV	�Nt|V���N�O��P���Ne@OkTOolOp�Or�Os�Ou�N�Pe^�Ns�Nz�f�ip�i��Oe,O��f	Ov�i�j��$O�dp��n��8Or�v1<tLOndOrwL{����l�{ �{��xOa����|���O�\|���O����d�OlX������
b�(gPj4PlTPn`PplPrtPt@���
�Oa�Pe@QilQl�Qo�pRr�tRu�P�,��8��jkDPnD���$Pa�D
k%��,h���LPy��y��,�y�8�
eXvk̛	hCl�Pr����P�Q��Q��Q� R�<R��������Pu8�.he��lH��Pl��m$�s��
t��v������
d��
À�	�PrQs|t8Qz|��('	s,Qz,�.�	r������a�Dd\Qn$sdQt$�yx��8����2i��u�yL(	t<�|Ql�Qm�Qn�Qr�Qs��y$���`�
f�Qgh����e�����Q�P��� �
o�Q�8�yl�	�l@�	��l���0ee�
\�d	p�rH.@-t��	�	,RlL�r<t\�z�d��j+l�m�Rn�Rr���TRa�b�
dDSe�f8*g�Si�TkUo�p�UsVtDWu�Wy�RÜU��%.ػy�,����o�4	�gDTlSr$Ss0St�4���R��S��T�,U�dW�tW�HA���n�C�pB��SzTD��(�v�X��V<SdlSl|Sm�Sn�Sr�Ss�v�SxH`.�Y
e�Tl�c���ebg�k���
e�l��v`w	��.�Sd�Sg�Sv@x� y.��(����Sa���d gLy
jd l Tm,Tn�pLTrXTs� t|Tv�Tz4���+�����d��f0�g�j�Zt�"v�����ü���HX
khTz|������l��t����TopTÐ��t�����ToP�Tt,����Ta����Tg�Tzd�_x�	l2g�v\����l�,vl�.| o���Tkl��p�r$Us�xd�t,�	DbPUl�1mXUn �r�s�Uv��	����G��$$stG	`Us���lU�����xU�<�		r�����U� ����k(m0p��t�Uz��)�Utt�|����U�p��Uè���8[g8Vy���Ug�7j@Vl����Va�Vi�Vo8Wr`V� WŜ��<�t����`Ynh�	HVr����TV��V�("�W�T��4�	xVl�Vr�]
s�Vvl�����t��Hss�����Ve��T�]
dsm�Vn�Vp�]
sX�tL�
t|�
����	d���Vl@�	�s��,T�	Wrh���W�����P9�����,W���|9d\Wg�Vr|t���,�	�r0�s��	��gL"lT"nĢrd"tl"v�z�	�d�l�Wn�Wr�u��.�Wa@#e@
o�s�#u�Wð>���p����8#lXt����W� X���0�'����Xld�8�l>s�	y@	0XclXdtXg|Xj�Xr�Xt���8Xa�Xi�Xo	y	y�	y@	y@
	��
	���Xr�B	�a�Q	��
z�l	yTl	�Xc�XpT�
u�k	���Xa�Xy�u	yp
	4_n
.�XaYi�>t�
��
	Yr8'
0Yk@ev0D
��P?o�
PYrt�
sH6vh�
��&a�g�\	�XlYr��v,P�Yr�P��tY����h���Y��g���YÜ�tAg�YlІ
s�~���Ye�Yi�YlZoZp4Zrė.جt���Yn@�����a�È�t��Zr(����.l ���P��L����Bo(Z��H��DZt����LZ�H�
�Zbh�j�Zk�Zl�Zn��p[s<[tH[uXZ�����dZal�
b9d�[e\f \i��k�\n�\oP]p\]s�]up[�D]��y@�����k�����u����`�t�uy(�.D�p����[z���P�[sH���$[a����0[t��P�r��	(Hb�,l�[r�[s�[t(���T[��[��\� ]��]�0���IiH����s`���D
m�
�L�[c�[l,�
z�*���k8(	�[kMl\n\s-��,���[ed/��4��`�È;�=a9d�eHOfH\g�\m�\n�\s?.hOa�\��!�����X\�$�8`\����l\yH��x\n,l���\��@��@��Pf�0	g$Pt�F��Zt��	�x�		�\r��	���\�]���\�a�@t4r	4Up]s�y|y��]p�z	��
cė
l�<nxAt	8�sd��8]�����X�r�����Ak\nBrBtx]zl�.l�n��X=t�	�^l\8vD�����
l��rܽ�]b��d�/j^l8�n$^p0^rh����]a�d_eX�f�_i�_k�_o$`p4`s�`t�`u@av\az�^��Š���t`a������rh�.|acD^j0����,���L^l���T^o��	`^g��l�bp�^r�^t�*z���l^� _��_�`�`��`�`��iT�k�����^a4�b�^eЊo�u��v�^���8����^������l0��1c_lev4�x�����	eg،l4�
n<_rd_t<�����m�Zt�e�t\��\��P_�|���fex_rX_���<��g�_l��	m�_nXgp��rL�s(�t@�t�	�sz4
����lX�v�
��
���_a�3dT
�_l�_r��sx
���Ng�r4
	|Nht
	T�s�
����ed�rt
����pX`z0!
��kgp`kx`v!
PD`atle�li�`��<
mpU��!
���l��l��l�m�����%
pvt�%
���`a�`r|0
�`�2
���`aX2
�`gQt8
	�7
	�`ganat�z�8
	D9
��:
��:
��ae�:
	at(:
��(a��9
��4a�t;
�\�d�ak;
.	Laa�ae bidXk,bo\bpdbs�bv�a��<
��|�c�?
��RgTqs0@
���a��a�P�
�Db�Pb�pb�xb��C
��aj�as�rv��K
��M
/�av�Q
B�tS
��b�XS
��b��R
�bgPX
�l�l�$
s��
'4[
�<be�\
/��sT^
�^
.܃e�c
��d
/�bg�d
���#f
���be�f
`
d�bit�jcvPf
���ba<]bhce�cf{gdi4eo<
p�erfsDfu�fv$c��e�|l
��}
�~
	�elLcrhBsXct��
vD~
��c��c�e�L��tf��f�Ȉ
�L�
��Dco�
���
�
`cf��g|cl8�
��$yl<�z�}
À�
�L�
	�ch�zl�cn��	p�crȯ
���
8��
���cd�zi\Cz�
���
��
��(nh<dn��
�ckT�

�caDddLdehdg�dj�dk�{l�dn�{p�dr�dsez\d��yl�
��
j`
�l
��Td�̻
.|d��
��
��td���1��
	<�
���do$�
o	4�
���d�о
���dd��f�g�t�d�T�
	x�
1�dä�
�����
���1m|t��
N��
	l2g@�
j ev4�
	�
���0m��
(ek`el�}mpen�}p|er�es\�
�|�
��Xea��	d �
m�}t�
y�ea~e���
�}n�s��
����t�z�
.�
	�erP�
���e��
����
a�eil�
���#����ei��	�egD�
���e���
����tf��"tl�
��$f�8�
��,f���
8fmTfrL�
.hfÀ�
���
��`f�0�
	L�t��
	�g��<���fo�fn`r'���o�h�fg�fl�fr�ft��v��z�o��<���x��h�e�|����eL�	�l(gr����g�����g���Lgalgi�go�gu�gü��ЧDgd\gl@�������dgs������xgzԨ�gs8�e�����g���THC�e�gkX�p�rht����B���gaThiP�
pths0�t$h�pH�T�XR���gr�U	h�l<hr�Js�U��h�$��_��[��4huĒ�`ho��Hhd�T$��p�lԻ.hha�he�hi��z�hÐ�y���hl����h������c�����8�.,ia�ib�ic�idLke�kf,lg�mh�ni ok�olvmXwnHxo�xp�xr8zs�|t,~ud~v�~z\i�xx�̉��j<il���L�a@��ig�ir�s����Hi��k�o�`x�(&��f��
�@���Ďa�o�upC����a����|l�Em`E��ikh7���i��3.�iØ����is�y�@i�isLO���ie�jv(j�xj�ԖH��	jt�z|`��j�<j��j���	T�rLjs�����0s�T����Xj� �	`j�d���lj�d�H0�	�jl������a�jekiko<ku�j�0k�d�,�	l�jsH�������	�jl����j�k�k�Dk� ��d�t��#���(kl�����������#����`�bdkgtkl��mX�����eܳ������e�kÔ%eH���k�����k�p��eh����k�����k�w���kflr�k� �������ke��	�klh����k�Ь���o��j@�lk@ll����leHlrTlyH���!���a03. �a�leTmi�mn?
o�läm��8�9��xl�m�tm��:	|f�ll�;����b�m�;�lm�;���le��f�llh`mx`o8-B,��8����l�P?���l��>8�ljmk<mrLmsl?���mbT@�`@��(m��?��(�r0m��y0B	�lhmn(:r\CH��thD�t�	�t#��|m��#���mÜGL�G���m���\M�mf�mtM���ma0ne�ni�non��n��[D`	�]	�ms�]���m�xn�f��4He��fPnl��vdn�cnlTf�\f<nnDf��Dne�1�82��\n� j_�i	pnh�nr�k��T.Ppthn�nn�nthqtz�u�nr$	d~	�ns�~���n����ng�^kd�����`!��r��ln4op@���oaPok�ou��.(����<ol����Doa|oe�oopoÌ������ho�D�T�ol�or��H,����T@�p�
n�	�ob�dg�*j+k�l0r0pt8u����oa�b�
d�pe�f$si�sltnto4	p�ts�ttduu�uy`pÌtŜ.(6��4	8pcHg�lp�r�s�4��@p�,r�ps�� �Lt��^
�xu��XQ�X.�peTX���ps�V
�pc�pd�pg�pk�pl,qn�rDqshqt�x�X����zhY�$^yH`���Tl q�px�
�6��qepb	qt�b��q�g���q	g<qn�W	�l��hxŀoT\
l�qn��s�Gv�o��Pqa�qe�qort�u�q�ot�q��$���q�r�������Hp�gH�l�q	�zk�qr��vTr��T.��n�ü�(tT�qr@Ts\t��e�tjre�t�.$rdl�
�`w	(gLrk�U
l�rp�rr�rs4|��0P	axre�ri�ro'ulr�8}��Ў��|�l�&v��|}�rr�}��1����r�����r�d���T.�rip�jPV
n0�sxV
v�z��X�p����$a��o�ossz����t����.s�dl1g<lD�mLsnXsrdstlz�����Zt�����>a���Vtx�	l2g�r�v������sn����sa�si�+	u�y�s�� @n0������s������s�L�	�sv�����s�@�����
�����sä0
���td4tl<tmDtp� rܷ�l��,�	tb�lhtm��r(!v����,d�<�	�e�ts�tt�tÔ���tt�����\�	�D������tl�6p<Vth���� l0p�t���!d�����ta8ueXuru���.h�	ur����u��
����� ulHusD�(ut v$���������Pua���Vd�9gh"t��	�g�ul�	r��t�v��.�"h�"i�"j�"nt�"v�u�p�1�����u�\�	<#l�us��.�ueP���� s�uz����	�@	vl�#nLvr���
va�ve�f�vi�.k�vo�/p|(t`v�w�@	��		l$r�	��Tv��v��v�w�$w� "	���Ci� 	|vg�6		Pk`�p��rE	.�1a�B	�vg��l�vm|Ip/s�vzF	tM	t�Q	4(l<(rp/s�Z		�s`\	����
�pg		��j�o,h		wl�vX�z�|		$*l<*r}	��8w��w��k	��xwexi<xnHwÀ�	�l�	pwl�wt�wv�
<��wtԧ	���we��	�wl@�	���we�w��	�d�		�ws��	���w�,{��8L��		�3p�	(�	xd�nxr��	�� #t0�	v�	(xpp�	��0xe8'
Txk0D
����
i�
`�r,P�xr�P��lx����g���YeL����3i�Bo�~���xr�	��x�s4		�xs����x�����x������xdye�ytxy��1 ya\8g0y�L�xg<ysXyt���,��(y��"1'�%��Dy��$��LyÈ|��z	dym(���ly�z���H����ya�ye�yi�yo�y��t$����y��y�l�������ys���ykԠH$�Czl<���	�rld
t
��zalzexzo�zsTz�h���zs�zt|zL�t�
��Lz��z�,�t0�dzgP 
p 
�7
C�
l�%
�%
���za{eX{ix{o�{u�zø{ż'
�zg�zl{r�'
���z�{�����{��{����w	4(
	�(
	)
<+
	P+
	{g�k4{l<{r,
�@,
��T.P{e�m�n�,
x�l@�mPmrp{s@-
t.
�.
e'_4/
	�{rv�/
_X/
	�{rضsp/
���{��{��1
L2
C�{z\�
_�?
�D�l0@
���{�4|�T|�h|�;
.|i�{��R
�$|v�U
�pV
�V
�,|v<�����@|t�\
�H|nde
y�d
�`|r�f
|Ab��jPf
��t|a�|e�}k�}t ~zt}ü}��
1�
�|g�|m��
���.�|a�|b�|e<}i}�
ܛ
��
��|l�
tX��H�
	}b(}r`�
��}�t�
���tĜ
���
4}n��
T�}k�
��H}ihs�}zL�
	T}sD~
��h}���
��
H�
���
���}��
���}��
	��sP�
���}���
t��
���}��}���
��~y�}�
e���	�}gX�
P~e��#���~i8~t;���a|����Fa8&g��D~g�~z����T~e��.0�	p~r<���x~� ����~�mm���~ae<iDo�tLu�~�\Šc�~mpt�B���~e�l��dm��p� �n��mg�n���tDn	r0t�n���+k8o��o��oNB�o��T�t|��|��ha �o�#@����a�e��	�r�U�������ĉ�lT���x�.�n��t@����e�����8�.X�a�bH�c��d؂e�f��g(�h<�i��k$�mH�ṅo8�p��r\�s0�t��u`�v�zt�ÀY�̉
��d�Dg@�k��l��n�o�p,�r<�tp�u|�v$Vx�zh���Ȋa������ ������a�Qo02����aЀy,KP��a�,
�$
܀c�K���oK	�g�S#XR�r�Z���a��`k��0�aPR�t�����o@�	��b��g@�h��jEl��p��r؁s�t����H�����p������č���@���Ďa$�yL�è�#0�����epC����a�i��o�ut_��8`��w��������v�������ha|l�rP�ì���th�(4���p|s�3.$�al�i`�Ø���4�s=�\�kh7��T���?�HHpH`	P#l|`��x��LO����e��r̂s���|n�@i��lp�����o8Wà����k��
��c��g4�k@�l��m�Xp��rt�s��t��vd��T���e�	�r�����	��(�����0�a��e`��дŔ%�H��X�������
������e$��D�������e��i����l�n�p0�t����H�e�ô����b�Ze�Zr�Z�d��k���؃e�y@y���ehw��jw���a(�lT�otr����4�o9
�\8
4�l����<�aL�H�gp��$
�|��`�ô��	l�l$���|������ �r���؄�@!	Ī.L!������	 	��s�zd��Ȅ��.DO.4�a S
�dPw����a�v���d�u�lM���o8O���d�d��g�m�p��rH�s\�tH�z\����ko`!��r��tt�v�M	z|7���iT�È��a@�����i��l�rp-v8���$�a��iD�o��Ð��`�ԅp���܅e-i8�o��L�����(�� "	m� 	�g����epp	Tl	0�l�)r�k	��8�a��e�i@�mL�nX�o(Zp��s��u|����}	��8w���t������	P��	����eh؆iX`kd�r<t(�l�	��lDm�s��t��	P��	PX�	���ew
t@�	j�	��T.�Tt��		�r(�	�g�-n�1p�rs��	T��	��8�ep�	��t�y0�	t/kl�l�/s�	x�		�/rD�	���k��t��	��	���	��nx1t`�		s�1z��	��d8�8'
vd@?k�%l�r\�
���So e��
�Tcxer\	�lYr��vtYz���.�@�����aL�Ø~��(�l�fnX�r4����	�L����3i�Bo<�Ì�H�l�d�4lȈm�4nЈu؈v����t�a0�e��
f��i0�o��pĊs��t�u��ä�������H����	5b�l�s��z(�����T����������(��4��H�	���h�yL$�g�[lL�n<xr�yx`.8(	h�
jl�lt�p|�sd+��-	d/���fs��z0.�fa(go���T0����� g�>�;��d؉g�m��n�p�s?.�a<���@�tC	�@���tTE��F���Zt�J	�Pr���a(�dL�lP�pX�rl�sx�t�m�$n�o���za�n��`�t�p�u	4r	��n�z	�l�Urd���A�����@�lX�r�s�����ApBt؊zl�.=t���̣���aH����r����j�nX=t0�H�	�	 �t�	<[g�zD�����tܽD�jh���
P�a��k�o0�pH�rp 
sH�tx�u��z�����	�l���������������	}n�Mr4
��,�aT��\
�h
Ћd�j�ml�nx
��؋a��o �s(�t�m�T
�r|�
�
�
�
����e��i��o��r�%
��'ap�ol�r`���'
��x'�|0
.T�oX2
Гj�7
	��t�J
�� �����J
��܌e����C
���r;
.��e~khitK
��*
e�J
Ќl�p�u.��@!�X~�����p�����t`�	�lD~
��Hb	�Pf
��D�r$��,���
���xa�Ie|Kit�o��ð�P����`��L�
��h��X�
	t�gp�
�����x�g`r�s��t;���ax�u0���C؍r<�sP�t�Z���-i�h�fg�fl��r�t��v��z�|����e�z��
$�.�a��e��ilQot��\����zh�	8�s����D��Ď�����P��0��<���l����������d��T����lgr0W��3n����mЎe�	��z��T�C�HC
؎b�nd�IgP�kX�l�IpXorl�st�t,Ju�B���a؏e �f��gАi`$
k �oP�
p<�s0�t��u,'
z�����pH�K�d�exL��Q�XR��U	4Jgh�l��rȏt�U��|��p����\�����̒�ܒ��[��@�b�j�`����l�u�c�
dKf؉l��m�Kr�s\�t�rv�y.�e4�t<�z(�Ìy�@z�Lz�� ��{_l{,0}�<}D�gh�l�|��L�e`}���	��g��l��r��s@�����m��tt�z��|�����zT����Ll<�r��.8Ma��È���gtMml�r�sNz�����k�1mT�t�z��~Ԝ	X$
nxtr�vt�dNkD�lL�mpNn�tpxNr�Nsd��̪9<�_�	T�c4�r����l�����t��г	��kđlh%
vp%
z�Qt ���l,�����e<�����d�	(za|�e�r8���Б����T.$�e��i,Ok�m��n̦r��s0{t��z0���X�i������Ի.@Oa�e0�p��u��zt��D �����m�%
r��v���`����0&
����``
������x������a|����g��X�g��	\Pr�Ot��	��g�l Pn��t(�_��ĉ.d�a�bd�c|�d��ed�f��g��h��i��j<�k��l$�m��n�o�p@�s��t��u��v�z���d��̉��a��b�ct�dX�g|�j,�k��l$�m�n��p�r��s��t �u<�z���L���������H����l�	r ts@�y�FaL��ԓl0zn�p`�.�aH�z0�Ð����sH���y����l̠��$��@�����(��<��g��j��l0	p�~	s��t�zh���P�a�ed�i��o�zД����Qt����ka��b��klolr8^���	��l���Ĕ�8����������������g�l�n �p(�r0�t�����̩�����l�	�OlP�tT��\���H�eH���\�b(�d|�r��t�t����	P�r��l���l��m�y��	�lh�liȪj4�rԕu�_د_��	ܕr�e���������t��l�.�Pa8�e,�à����9���t(�t�@�s�v@���	H�a��etSgȖiܖl�o<�ud�y��������	��s���������H����<�,��	��n��d�
�
��Ԗe���
k�l�s�������i`	(���tlz��$���(�a��0�r����Q(���P��h�.X��t��r����p�aܗe�o�����,��	��cȗr�������������Q�����y�o���P�ԗgl��ld�,4�	�s��IX�����d�jd�k��p����a̘e�i4�r@�t`�u�������P��������n�������p�����x������<*n$�	��ros�o	tH������������t������g��mD���tD�|���ܘt�	�r��(L��s��,�	�s|����������(���jP�i|uP����yl�X�ph_��	l�tl��ct"k��p�����aܙlt��`�.��ð��$�����l	�s|��ę����Й�02��8Wa�e�gp�n �ox�yP���9Ēg �m�+s�:�,�e�	�,<.`�eh�i�;	4�v$5��D��x�
�X<p�<p�E�,K.�	ÜZ����l��oL~r�UuX���d��sktk��b$�c��d0�jD�kP�ld�nx�p��r��s�~t��v`k����a\�cx�eܜiH�k��o�s�t��uЛ���T�.���0l�@1t�l	�l�<�tmtdm�p�am��X�yTm�m����������z n����sot�o�Lo	��g(lػn��s�t<�zxo���������>�ԝ����\��r.�n r���zTrp`r���a,
fD
m�yo\��|���(���r��0���rTp�gllhZn�r��H�aP�c��h$s��vp�s��tĖv�x�Py� y	��g��k��lȜpМr|y��h".|.s�yYz��HM��zH�dl�k�Ol�m�n$�p,�sX{��{��l0	f�k�"vl|C�|.�|���kxcm�}@n�}��<�ad�bpWj��k0�
l��������l��4Q��t�ä���h/m����k��lĝp$�r�	(�����dx������	̝n���zl�)X����.��
���t���t����e8�iL�od�à����0�n���D�r��Cp�r܄��X���_�_��	x�t������l̇��bH�g�j�r�Vs���
��a8�e�f��i\�sp�t��uؠz��8�Ŕ����l�c�	��b�Fl$�r<���������,����������l���p�0�kT�l�z
m\�rd�t��$��h��̌��T.��k�n,"t��	l�k��r�P�e�Zt�ev��.��	��v�������p������L�	ȟlX���ԟ�X����Ü���d�n�Ms([z�p{.����a8gg��	LOnd����������Ptleh�i`�����H�z ��В��r 1
��0
��|��d�����������s��	��t��4�����Ġl �.̠e�
	i8�|�d\jtut���	�ap\e��i��m��o�t�u8��ܡŐ�	 tlP�rĤ��(��d�����]���yԦu�_��	\�k\�l�	r��	tD�t�|�d��m��n��s����f��gȰ\�	^rt�X2k��l��C�2r����С�d��a��������oP���k8�tWr�� _k�n��hj	t����0�a��o@�	
�b\�c�dt�gtk�l��m��n��p�r�s̪tT�z����L��h�������$��t����0��@���jt�sT�����a��e�i(�oT�u��H�Ř���Ģb��r����t�������xFlx��X� �l8�sH��������@����#4�����h��s������l�p��.t�a�e��ịz���L�������ģ�@������pH�s��_�����l�nD���ԣa�e<�iL�o �������T�is��C4�nh�����X��`����\��k|�r��6rt��0���Wz@���h�a�r��yxè�	|�l��n��.��a��ôx
�h6�L���Ц����jX	lP9nاp�r�s���Ĥa�eH�i|�l0��
� .�4i4���z�	��r�	t��� ��L:��������d`�nH�rP�s�����$��������$��D�eh�oh��L�e���T�d�<j�kD^lP	s��u����a8=b|qe@�i�=o�au��p�Ōt�t��e4����4���=��^�T���!��T.��t\!	$�r�!(�lX�n`�rh�s�"t#t(#��%��>�(�'|�j��nT'����a �eȦih	s�yԦ�p(.��y�(��0p�s�)���������8.�8		�d@@j��ld	nd�p8�r@�s�(u�Lz�8.�aP�e��iX��<���O.��dp�rx�v0�z�;80�r:��L�����La����8<��<� =	��n��s�=�>8��r�C��$Oo�C��t�C����a8�u(��8C��d@�l`�mT�
n��
rl�spC��
Чa��d�e��iԨoD�s\�t��u����X��B��� ��Y���` E.t�aP�e�
�pO	��E��X�lhG����s|�z(��\H��(�����8���c�ĩ����xJhP��`ckxcm̨t�N��s�P	�R��l�m�+n�s�S���0l8pP�t8T����zxp8U�U���� U�� ���T	,�n�V��pT�r0��X���uex��@Z	��rHY��l���[��.��h��r��s��t�\���$]1��z0]t�]Ch�
s8_@dd�m46zt_��Щa`�ed�i(k�et��z@�Ä���`�T���b�d�b���a$b	(�rLb��4����t��|���f.��
odfX�k�s�iC�ntk���� o�P�nX�vXo.��a�`e��iPs�܃	n�uT�lv��
��a�	d�e�[fČ
i$�lD
m�	p(�v��py|�x��r�w���g�D��`��|���C�Ј��L�j`�	tT�0�l��t��v����@�a�e��iԫo�s���t��HT�(Tx�m������opU�d�jX���s��z܏�|���\��d�����`�\%s<�.�ca�ce �u��z�� ��0�������c���l����RtF�n�F��(��,F�4��LF��@���3.L�Ř���X�sp���L	�LO��p�r��̬a�cL�d|�g�kH�l<�md�nt�p��rp�sķt�v �z��d���g@�lH�nP�r����l��������h��T��$�tP����e,�l@�s�����09��t
g��.4�e�����!	�����X�b��c��r��s��zX���`�a�e�#	g,�iX�oh�u��y̭ì�������t��.��e����ĭ�����o�P$	��8��d�pg$�k0�l88r|�s��t�	\rh$�����0�����D���H?l 
rp��������D��X���L�ä�	X�k����d������P#	e��z��,(������t������e�	 kH8lЮp�r�s��_D�8P���خ������t�v�è�����e�������%s$�z������[d@�kH�m4��D�����t�P�l�
rd���;�X�p�md���x�e��	��l��mH�	zH�.��eh�l̯v4<y@<����b�<yt�	ԯk�	��ܯa�Ie<nl ��Ժ�4�ň	�b�ek�s���������,�������@�a��e��f�gTh�i̩j��mro 
r��s��tܱv�y��zа�D��t�f��m��tL ��loz�$���e�%	�*	g�osH�����`��d�_8*���yl*��	s,��4������^���ð3�r�1	 �ip�k��l��82��,��X4�
�3	P�s4��X�� 4��d��T�0`�|�gl�����e�7���ra��z�7.,�o ���7��бe��o�,	�08d�r ;��T.��eD
Ì>�=�����<.�u����>�X�8�	����`B��$��D?��0�b�e��lh�u<N(�d@�utN��
T�a��e�f�g�k�do�tT�y�z��ôO��xq�ز�`u�4.	�4���R�gg�gl��s�<XDU��IJipT	̲v`Uj�UjxW�(^C �l,\�����P[���Ü�	_��	����
�`�		(�l`a�|`��@���^.H��Di��h`�rj��<�eԻrps
d�b,�c�gd8	g��j�l�nl�r�u��z�s����awdX�e`�
g��i�o,�pH�sT�tȣ
u8���B������t�����t.hu.�v$,w_�v	 �g��l�v��(��������h���`���z�/	g�h|�k�/	l��n��r��s(|���	et
l�}���g�~���zŨ�	Ml��ܣg�Ol��mԴn�p��	s ����Oc|�dPfPg��tH�p0��et�������a���$�rT��ԉj`�t�4�l,���<�aԵi�Ws�zx�ü����	d�r����l������������.̵g�Knqt����	��g<�88������|}�8���r�e����	�
t������r������.4�e���9��,�nЍ�܍��@�a���H�rh�\�nD1	s����
`�aԶe�i�k0�sP�tX�up�z��������l����	X�t������������ð�ȶk�t���*t�PWp��sؕ_��	�z����P��t�PDnaD�eP��� �zܘ����<�r���������.��kĝ.`�etzm��Š�������n����������Ԥ.����l0�
s�����a(�et�i��oԸu���ĸ�D�	Hl�rp�����d����� �,�
�4����y��gP�l�	�9l���8��Ȩ��D��̭���	\�g4�k̰8�d �k��m�sn �ز$l@�	�l��v�����	l:
���������4���
r��	t���t�����eol�	(weH|kP�s�����������r���������0�r��8�r����D�o`�s@u��	l�b�dyfp�g��h��k$�lp�n0�pp�r\�s��t��v���0�.����g����������ȹk�l��sD���йa(�e4�oT�z����.`��p�����@��(��L����xxm��`Ps��	�Wv(��l�P��T��b��k<�l����\�aȺe|}gܻh�it�k��o��ì��X������L����(��x)�������pg��l4�m<�nH�pP�rh�s�t|�`(����e\g�h$�i��l,�s�]v��_����_�#�.\�y�$P�t���m|�t����X�e4�}���t�eL���T.��e̦r��v��Ø�	��r<��n����x��h��p���Ȼ�X���лÐ�hd�g�m �z�����z��.�a�����t�HC	0�s8B��8�����D�Ì		P�t���\��,���h�ä��lt���T.��e�}s���#.�Fn�}sP�T�i������e�l�rMsܼ������}������L���������i���P�	h�k����h�a��e�i��oH�tT�u`���Ť�	�b�lx�r���L�������,��T�_\�xg��l��m��rȽsؽv�Tz����Tl4�s�tL���b`�e��X�	s�t���	�kPnXr��s�	�i(�s��������(	�h�	�v����0��
��<��`
���\�p���d�a��e̾y��� �ld��k��m��	_D����b ��	��g�l(.��e��i���< .�:e�s
	\��ԓ������#.(�t�>�$8�l :'�&��D�lL�r��ì-�/�`0��l��n��p��r��z�0��
T�a8�dܿeT�il�o��s��t�u@�z���t2�3_�2	��r��t�2�����(��|��t����p7��lh�m��s�tT#�;���sL&�`;���i�<�8�z�;	�s��
t�<_�?	 ���@�r�=H�kt�rd��@d�k܄tA	�Wv�B��l�sxB��s<B����e���
��T.��	��nC�������,C�8C	�v�CT��g��t�C��D�����E�����D	�k����r.��(�tF��4�e�F�k��l(G��L�a��e�pܯ	s�t�z��� I܅g��l�<	r�I.��e��t,��T�	�H��@�����dO��`[e�'
��'
	�b�'
�����P����Q	Hv�Q.��
aT�e�=	s8�u<���(S��R/(�r�R��0��|���������S�\�gd�lHT��<�l�V��`�n�V�p�k0Y�<Y����� Z���rZ	�d
.��
�8Z���l`	�_����tH]��s�Z����eDg��T.�a��b$�e��i�MrĈtT��gj�h�ik��n����|K��j��sr8��th��D��4�w����et�rЬ��la�e�i�o�����
e �r�X��X���alX����g\M��nM����a��e�iH�c��z��4�a(�cL�d��e��g��k�l@�m\�n�o�pL�r��sl�t,�z��$��aH�
bhcH{g��p��s��à�����X�lĎ.`�a��e������l�zT��$l��r,���������@��X�'�����nh������m ���%l0�TĥdD�����at���r��������������a`����cP�k|�sp+�*��<��P*��D��H����\�g��l0zn�	s�.d�a��e�i4�z���0��L���l\���������$��,��|����lؚ���O.�t�����r��������$��������0Hm�p��`E��_t��`���g�>s\���<�e|�zp�����P�	D�z���d���P���wd����a<E	��T��g�����a��e��oT���	l4������n4|_4�	��kP������(���(�l����8�z`��sp����a��u�)p�����m������
t ���P�a<�d��e��t���������b��x�m���h��4��@�����s��zd���s8�����e��o��ð�	�x���������������s(���np�������a(�s8��������.d�o��0�t����@�a'o|�
�8�\�t|�z5�H���t�.�T��l��uP�����a��e��i4�z���$�,��	��g������@�T���J�8���r��D�
m4Gs�z\���y��r�����.L�o(���X
�PX
�D�k����d��k��l���X�a��e��n���	�m
m�m
y��z�m
	��s,m
�����H�����`y�C������d������lP�
��t������`�gL�lt���e��i��o��s��ul������pl,�	X�l ��`��������|�n4c�8��k��l��m��sl�t�_�������.��z��à 4� $d� �`!	,�d@�gX�nl�r��t��v�z�#T�"��$�i�$#�$��8�e6P��e 0��L�y\7X|7��d�a$��0��е
.��b��e��
|�t�AH��e�A	��l`A�����0A�������p�<C��T�b��ÀH	`kLD������I�I���a��e(�i`�
ot�s��z4��d��\J����
���X�
�tx�h�
�|�����\K�
��e��f��h��l��nX�p�|s�t�|v���KX�l��mxKP�j	�k	�k	�K�K������K.�|b��e��èK��K��K�����XL�4L	��k�s�L��M���gM�n@�s��N��8�k$ctT�zN,�e�P��\����
�O�dP�tPC����G��T���G����øW��t�W����aHV	��r|V������P����e��è^����y^��g�t\f0lf���e$�u|f#����
at�@���,�iP�lp�r8�����i��ud��H���T	�����i���L���|��$���0#�.����e��t�����a��e�V�
�<w	t(w	��n�u	����iTl	��p�k	����a8'

��
iH�jT�k��l��m�nHoX?ph�r��s��u��v(TxHC
#�B
��@�i0D
��h/ml�s�t����L
��P�slP
xN
x�t�N
����a��d��tTT
��T/����@S
�����XU
	0d.�\
T�d�\
����ale
T��cl`
����e�e
D�p
��$�elo
�l,�t�o
���aT�ôq
���4e�r
���s`�z�q
	8�s�q
��H���r
\�
���dahni�~m�
$�s�+vD�
��|�aX	e��s���P�
�ĥ
t�
�����|���
|	c�
��
������
������
	<{aP�ch�d(fD�	k�xnx�p��r��s��t�v(��,�
��������ؾ
� dp�
.8�a`�
��D�s��
��tm	e$�
��\�r��
�(�
t�nh�
��|�a���
	<kl^nh�t��
�����|�_0�����k��
��k�l��
��
��a��	e�i(�o�yp�sh�tt�zD��T���
���
\�v4�
���
 �l8�r@�
y�r��
����z�p�
�����
��
��`�r��
��a��eL��l�	m��
���k��

\�
���n�
-��
��t�
����aT�r��
� ����u,��s�����o�	��r�����8����\		LXbd�dp�k|�l��r�t��v��z�	H�
mL��L����X����Ho�����
g�{Wx;	��k<5�����T4�����8F��(Ye���xF���e��N��N	��n�I�����(I��$�s����Šk	)�N	�eO����HOP,P	|�a�e@�f��i�\	k��oh�p��r�sT�t4]	zt�ØP��,������R��������h��XZ.�Y��j�nt��x�����М���T^��jl^����e^��d�d.�d��l�g��4*m���\p���
p �t�~���3l�3r@�s4
���|i&o��h���,�k��l\�ph�t��z�
����e�%
����a��i ox�r����'
�������T^
.;
.��p��t�`
�u
	\�s�tu
������s��t
�����f
��nPf
����a��e��r@�è5y�yo�
(jT�sL�
���aH�i`��~
	 �rD~
��4����_p�
��L�s��
����
	ܩs��
��l��8�
��x���
��l�
��4Ka|Ki�Ko�B�l�b��m�n�r@�t���l\�
n�����a�@e��i��`#�st!���'����i,��aLu�:��kjs,K	t;��,�ad�cT�Ð<��(��l��P=	�?�C	�f��j��r0�s�t�N��d��s���m1�N������O8��tdZ��d�Z����a�e����[Cl�np[���������b���n�h		�fgh�k<�ln�	p��rT�sl�t|�z�o����dX�Üz8z��L�td�z|�|����e���~������	i	rTMz\�
	��8��b���������.���t�����yL�	��g�����������<j	e��àU	��r�U������B������ĉ.T�a��e��h��i�o$�r<�sH�ud�y������̉x�i��l��r��s�_y�����p�i�
#�����e�v��u����i`k����d��e�v#��������h����b��e��i5
l���������aL�eh�i�ko ��X�el�����X��t��|��������8�e��@�l��t0����`�r�t������d��k��#�#����g��s���h��hq.hn��tM����i����c`�����
h��zNP8'
HopH.�;�t�����i�%
���'oh���0�t�&m$��P�m$���X�o��ĉ.��a��b��d�eh�f��h��i4�o��pL�s��tD�u��ð��̉��d�f�k�l4�nD�r\�v0��<�Ph�����a������p��������l���t�l�	t�����aL�gL�j(8k02����a$y`k��t�i$4�T�#����T�i@�	Pb0�g\Wl��r��t����h������������������8Cܒk��m̩spC����ad
o̦r��u�[Xv��"j��r(�v����ha�l�r\���x�ato��8�gD�kP�l��m��r��s�zX����(y�	��pe�����a��eh�i��ot��tH��l��t�`k��l��m��s��L ���:b8�r�"���bzt0�D?��h&e��l�s���Ŝ���čØ��<���������������	$�h�p0�rP�vt����}s�0��T.�itt�z�h�iDg��D�eЬ���ew��\�rh].\Mt�uM��|�a����a��d��e�gD�l`�n(�o�r,�s��t���$�|�n�v\���>a��eX�gd�i|�j��l@�m�D	o��r��s��u,�v��z(�ä��؛B<�s������b�t���������D�����L���|V��И����l���l���f���L�I��z��������_�����]o��Ü�����������Ԡ���8�#����ld���${a0������;
n��	�n���$��H��(���<�c�fl��T|�n ���T�a,d��e��i���`�t��T��t$�t�����l#����$c�$e�$g�m�%od�	p�r�������d����%�p��|������ �����������lo|c��T�d��lP����aL�e\�kh�pl�sx��8�T�Glp�s����T����t���p�����g��r�����e��i����.L	t�����,��
`!	��j�r �v�%����a��o$�u���'#@8��4,�d��|7����<C����nԈv��8'
L�lp�n��p��s�N
���Ed�Q$lo
X�i�cl�o
��`�a��e�
���Se �l\		�XdYrtYz,P	��s�P��������q����
�\p����Ø~���3l�o,�rغ����
aP_	e������t���r4���8_	��
��
�L����3i��o@�� ���4��h����k\�p`�t�%
����ax�rl�
��
p�dPf
��x�e��i8�ð�
#ļ
����a��e��i�o���T�
��l�
��
������
��
��g`�
�D�
��n�s��
�T����
8�
���t��
	 �rD~
��,��h�s8~t4/�+d��l�+np/��
T�a4e	e��i��oнs��t��u�,z��ÜJ	Ű0��@��D,�������|,�P2T,lH�mt�n4Zt 4e�5��r�56�6��s(6C�tL6_�h	�fl<Mv��z��ĉ.��ax�b�c$d�e�fxg�h(i@ j� k "l�"m�#n$o�/p�0r`1sp3t84u�<vBz@�ü.�̉����a��b8�cp�d��e��f�g|�j��k��lp�m�n��p��r�s`�t<�u܊v��z���H���̠l�o�r tsD�ؗ�n��p4����a(�iD����������,�ä������D�o<�L�k��lȵp��th���X�a��m�������ka��b��ilo0h
uT��Ȫ#P�����il�	��k��s��v��������L�,p���B	ph�����id�
y���n��	�a`�b��iȪj��k��m��n�i
r��sԕu��l�
�
��L������T����
l�
��l��خ��@ntt���U��
��$�(�
�����8�U�Hj
rȉtL���������_�����Љ
fp^r�MdH�g�bmP�nd�sl�t�u�j
z@�����a@�dH�e��g��i��o��s�u�y��ü���tx��\�cl��(��,���DCl|�o ��@����Yl��	��l��r��s �t������d��@����|���
�bP�h��i��j�k�o�r�s�t��X�X`����.��y�a��o
�����a8�f<
l��r0���j��k
cdQl\�s�����	��gt�k����8����|�r��yx������<�P���d�m��o��slk
zl�$��~������z��ts �	�k
r��$�����rth�.h".H�a4bh�	f��	k\�o`r�8U8�	@�t�'U��	T�n�t�h�k����p�a�vk`�t����U��	��n��������������������������d���	��ah�e��i l��n��oT�rd�t0��������l$�	�lH�tH��� �������8��8���D�k��mL���h`m��t��X�l����L�n�	t�k��r@��0�oL���z��� n
a���X�v�	��k4�����������	�@���̣.�al�d����l�����_�	�n$�s��U`���|v��	,�r|���� ���������iD�����h".�2b��i؆j��op�u����	�k������8�P���v|�
����t��� �y���l��g(�j<�kxSl��n��p��t�����a��i��j��o�t4��W�(,L���vkd�����4�bD�j`�kp�o��tTSu��z�U��Hh�k��m��tp�\���b "�(w
����yt�� �a��j�������������|V����P��������p
e�nH���a4	�gDTl�"s`�td����l�����4��
P
��L���	��T���	`Un��x�f��k��n��P,���o|���"v�s	i�����as	ip�|r,	�k�r8$�����0�����`�t���a(�r8�ø �� �D��0���	U8%��D�g�#L�l$n��r��t$��X�ah�bp�e��i(Xo��4�ŰqU 		��r,	�����@	�����<	�&��l,&����aT'��4�b,gn'	��rH�s\�t '������%��%�<X�TqH�p��s�p��$���	.0��$	��<�z�X�'��T�o�'j)�$m��s02	�2		��s2	������*����ÌE	.��oX,��k��npo�"s�E	��,���d3�1��l�t
n0�rD�t02��	��aX�e��g��i�n��oP�y���0��4�<�r�y		�4�1tĉR�9P�.Ēg�kh'rx�t��z@�	t(;�U�;	��m$5������=	<A�u
d��n��s��t��z�AtBtHB���	��	.�a�E��d�nx0
��z
	t�	���nx�		�X.�J��$��\L�K	<�s,K.D�ah�j��up���
8�L��h���
U0R	|�kXZ��.��i��s�Z����a��i��j��l��o�p��r��$\�`U]		��b �d�
i0�k �m0�rT�t�
v��	�$]�������`�U�].�]��(�aH���TPP^��@��X_����.h�ox��x�.|~PL_��p���b��.�vHV	��hH�i|V������P����u����X�����t�d��s��t�e��e������e����� f	
x'.i
h��iȪjX�kd�np�sx�t��v@��@�
.|���8��f��l��f��L�� ���aĿU���D..���lD������|�U�g��g����e��i�o���k�8�uXt�k��d�s`k����ah�c�d �e8�o�y
sD��n� n���zP3y�q���nT�yLo	$�n\�txo��4��h���q�`r��(�v�r��h".�a�b��c�V
h��k�n�o�r�y
u������s�������x�8�8.8�tp�u���.�n��oKr���`���v�m�s��vdw����
l�v�lT�����	a��,�m$�r�1$���H������P��́	\�r��v��zx������|��������ä�� �	�����j̇��l��p�s���	��aT�l�n�p�r��t,�v8�zT�À�(��t
��
���e|����5ix[o�9
��(i �.p�t��	 �dĤ��D�����t�����k�o�r0�t�9
yP��
��<��������	��k�
�.�����a��̛�D������H����h�m}
ot��m�����������H������s�8�	�f �8d���(�r���kd�l��t8	P��P������X�è���|�����8�����op��оBܾ	��i������n������o���U��	�r��������1������������r��@�����bd�g��j�k�l��m�n,�p`�rt�s��t������H����)�,��6��9�@���Ďa�i�o�u$�y0S��T�
l0�����a�@
e���؀
�t0������HTT�nx����aX�e�ôt�����	����4���s���s����a��dXaeX�l0������	�����v�@�m	t$��H�a�	o�wv��è�d	p�s���x������w�D���TD^l��n�pP	s����a�i��t�!TX�n4��T��T'�� �e �n��t�y�ü)���W��������8|B��@��$�o8C��l��m��nĩp��r�s��v�zpC��
8�a,�b�d8�e`�i��n��p�s�t�u@�v����X�pO	H�E����l��o�F��F.X�yhG��L�s�����o�À��������\H��	�������c�,�������v(��v,J�� �e@KD�m�K�P=�`=��L���N��m�XsT���Q��Q.t�a�Q��|�y�TC��r<U����t������HV�����DW��V�����V���èX��D..��r�à�
�8Y	��mHY�����[�	h�r�],�.X��_��8�aX�eP�v4�P�r8_��l��st_��d�a�e _k��z��Üa�$b	�dg��r�dsLb��������e��b��8Xt����i1�;
� o��b�eg,flX�sXo.�a0�el�i���`żp��`��L��������pg��qt�fl�fmԋ
n$sy�r�D�t\�v<sy�S
�Ps�d�m܃	n�fs��tT���t���d�Zr�t��u���lv����a�Ib�IefPh(�ik0�lm�n`�ph�r��s �t�3v���v��w1�w���� ��X��p���y1`{
	�|��T�aH�À4���
��@���C��~�@8h8�1������e��l��r\���x�a��e(Gito���`w��������@h��3.�ha�����svtFT�F����,F���LF�����M����ih����eLO��bHeTnhr�y���2d@i<s�����@
i`�Pp���`a��oĠu�ì���dR����d���v�����ab,cXd�Xef4g�	iP�
j�k(l$m�	n�
p�r�
s�t�v�z������	������l�<�09�t��.eP��� s$�t8���8�����������te�o�uv@�`�pT
g�s\�����z������l@�e����t���������d�����l�	�l�����	�r����������������f�TTl��
n`zX��� a�egiPyh����P"	a��t����ĭ����o��(�D�td��l�rL��8��d����e�	H8l�rs�yԛ
e�����b�r�����n(&n�����z0�����[d��_H�	 g��	�dpnxs@�	tH�	zH�.4ep�	h��l��s�v���U�U��������D�*	��.�i�k�	���e�r�Ä���p.|�����	L~js�����������.e I.���d���ah�c�e�h�i̩j�k�nroxs�t�v��X�lU	hg��lH��p�,���$��t�f�g�h�mr���$�eh 4ok�zL ���e��DF�N��f�����"bl"��e(&y�%��t�%	 gXkdr��s$}��&��D�l&��L�(��T.tR�L*plX*��xa�e��\��������<+Bl*�n�v,� ,�t,p�,���a,{�l�	�vT0���eXSy�h0�D0����1�P���,d�1	4adt�zH��82��@��6�� �
t��p�7��pa���l�v9	�r8����<c��7��4
o��;�;�d ;���a0e�o��������l�;	�l s�;���������;��t<(gHrds�v��� ��0���Pzp���Xstzp�P�e`�\ ���������������0<	�d�g�l�r$<_L<���X<���z��t�<��<�l?��	k�sl��
pPrXtD?��	aX�b�e�i$	lP	o�fpp	ut��@.$At�A�LA	`llA��h���\	��*	� *	����D��Lk��@C�l�r�Et�HTptn�s�t�z�ItK	��a�J	�J�PJv`J		klJ��	�4	�xJ��	�D	�x��<Q	�HQ	��<	��J\sXKtDM.|	z�Ld	s<M,�Oe�O���	�P
�x
�tN��	�	c 
e\
fh
il�k�do�
y��z�	�4�	t�P.�	a�	eDP���	s$<���	��	r̩_(S��
j4
n<
v�R
l��rD
s`S_�S_�S�lspT	�.l`U��`=�DV�hd�1p`�		Lig�^.|��|j��i�
g�
l�
rj���
aDeliX�o�p�r�t,��j��j���
ok� k���
�,k���
ðk�k��ldk	l� t�k���X��l�pl<lhn,�m	Pk�n�ndkH�z�q�|q|r$q���e�q��vo�rj�s��@�d�e�k�n�o�
s��4
Ŝ{���6ae\8g�z�g�	j(k�����e�{tP�(|�� alexoH����|��@���T|�`|��Xz|`s�|#�eh-������y��	�n�v�����O��N�����U�L�����܆_l����y�Pb���r(������	H
i`
nLYrl
s�
v
����
��^y�@
g�����������T
À���|
sp��@uy{({���
�\����
���,����
a�
z�M
�����
��.�
�̒�����
�0�<�����@e�yipt�z�
È�L��d�������`y$ð�0ghj�k�m��r�t��\�t�dP�d��p����x��	�t�������������6��	�n�����������à�����b�mi�mk�mr�ŀ��������
������Ԕ���	(l��	�Mr�
��
��H�̗		�b��h�j�k�n�r�s�vP�ܗ��\�����
���e`���e����D..(nh�r�aycU�
UD
U�{({�����z��������5e����ȟaljDÌ�`,
v�	,sPv ���4��rv����
��X���
��`�(��D�	xrP�������L2
�ĝ.��kT�m�tH|�Т���	�th�����С����Ԥ���l����aehi rnTÈ����g,t�������4t��	<rp���H�<�
̰`k@�
U�
	td����|����t��z�����e�o�x�4Ō���l���rt�����e����	�zk������������t��������(�t����i$�����
8b��
d�gh4j�k0l�nܸpDr�s�tdv����T.<�	�rl������b`ye�i�t���Ř�1��������1���������[.ns$tt����e,s��1��1��1�8\���h".�b\e��iTj�Ls�t8���T�i�)n��Ƞb�dX�
khyl�t����la�e�i����P����1P�xP	llr��	�kl�r��������d9	������k��e����	r��t����l�����Pe�i�l�s ä�\�.�9	sh'tH�	dn|tD�,m���	(�s�����<��0��	���p����eiQo�5
���u.����ed�kplH�	_��r�k`0T��j�k��l�n��r�0��(a�c�d�eli�m��o�p(t4u@v�z����P1t�2����`����4$4���hd5j�e�5v�5�kp7@�g��l�mn4s@tHvPz:��.ep:��:.�_c�:;�� �;��(�`;1�;��'1�<H�;	Xs�=Xcl|n�>1�\�\@����h@����AC�r$A_�A	�r.�i�k�t�A������	�H�xH��A���a�B��B	kC���<B����CT��t4E���.�>vhÔ���E	Tn�E��\��v��ttF��|e�ø�PF�F����L��\������R���	�@���Q.�eH�o��(G���z�Z��Z���a eXk�lD� ^1H]l0rX_1h`	�cn�\��8�`�
��b��PeDg��h".�a�b�e`f�hji$jk�u
mhn�rps,jt� v8À�g1g�d�h8�h�[.�k�m�nst��	z(i1<�1�i1�i1ķ1l�H�j	bHnT�
rPtXvth���x�p�H��H��Hd�8�l8m8�m1�l���Ԁhw�sw���agp�l`r��TŠ��������p�����P�	�lh������t������l����D..��	l�����(����0��	<sD���H�Ь���a�i4�okô����\l�
r��<	�Fg�t$�����<�O����<O����\M�d�p�tM���aeäYj�[���r�]����f��8�v�cl����H�b�d�gLhxk�mnHoTpxrs|t�v�z���Dk��h�`��g��n�op�\���|e�o��lŨ��u�P���������m<�Pd����a,m�ȫ���� ��U��	zT	p�|�4r��t����<a\l�u	��T�dr���le�o��
r�è�����n��	�r�����4����E	t���nT���.p����a�i�,pԊ	Ø����s ���	�a��cDd��f0g�i�jh�s�v����@�at�����o(�<n�����a$���`m�t����ha�i�or�1t�
Ä���̣.�a�n�o�t��l����������P�
U8��H��x�P���d��	��	P���h�Pk�1mp3
p�=t0z�.�PaLe`o\�<�DlxZ
�PX
�Xn�@k
l�t���la���r����a���U�	�s��������� ����t���Zg�m��o��`!����g��j,k�l�r  t, v�M	z�(#�(��$a`epoL�4)�L)��D�D��|�|)#(*�4*hld*t�*��$�
a�e��|+,+��x�����+��7�\7�l|7���a o��,u�8	��b s@8�����8��8�� z�;`�g0A����	�<C����e�������P����
e� oh èi��f	T v|V��\ �@��*
��t o8)
��| b<t� b�j��� iQk`�r@���� a� b !ex!h�!i�!kLal�!n�!r�vh!�P���������� ����m8���!eH�!l�iHp�	,!v|j��4!�T���@!À�	L!j�`p���X!��a�������o���e�!zt�t���m��#�����!a����t&������a-i�o�!�L���3�|��$��`##���!aD"���!t"l4"t���"ad"e�"Ü.��TaP"�41	�
tl/��D"��]j�V\"ix"tԘào��d
a�"��q��|����	<t��
��4���"�	j@	�"d�"t����"a#e0#m#�
	��`�.dfd+k$	���"i�		�"s�	��#�� 	�Q	gxQ	#�Q	��(#aԲ	���		<#rб	��D#�L�	��P#��		\#v}	��h#��k	���#kt#��#�L�	#��	���#a�#r��	.x�		�#i@wð�	���#�L�t�����#�^�#�8)
���Re�j8'
��
�#bP$dh$g�$kh%l�&m�'n)p$)r�)s�Su@ev(Tx�4
�1
��<$��0
��D$�\8
�tl�8
��\$a0�eh�g�$i��o�$��$��9
	̡l�$r�9
���$�����t��$:
_@<
T�$tx0v�<
��=
�����C
T%l0D
���$a0%i�0mD%o`}t%��D
B�E
�E
��%��H
BpH
(%r K
�J
<%lh{
sp�zxN
tl�%n�N
��X%a�%c&d0&i<&tt&v�%�$P
.�%yP
��Q
n�Q
���%i<Q
	�%r`Q
���%�����x�hH�XR
�%mR
���%e�%i�R
T&k�R
�TT
�����PT	�@S
��&��W
hW
(&m�\
��T&et
�T]
�0]
L&r��C����`&��^
��h&�,`
�
l�&n�&s�zl`
���&aH'eP'id'o|vp'ø���a
.�&y�	,�		.�&zt
	�����4
	.�&Üb
	��l 'r4't@'z�b
���&� j�<��4c
���k0d
X�c
��,'o�	,le
�j
4vk(�l<vs�l
Lvllo
��d�'gd�j�'k ylďm(yn�'p(�r(s��t4du�o
��p'a��d��e�df�(i��k�8n�(o��s��t (��(�,p
�o	P|p
��'a@q
��q
��q
	8�l<(t�*z�q
��(�|(�H������(�s
��lya"jk`krh�s|�vh(�8s
8�r
��`(�<w
�w
	t(d�(h��k�(nPw
�|w
�y
�m\�s�(t�dz�y
��XBt|{
t�z
�(l��pl�se`|
���(���
t�
�� ,eTt�
�`d@)k��p\�
��)a��d�
g\)rt)s�
���
�h�
��H)�(�
��P)���l�
��h)o�Ws�e�D�
�� e�s�)t�)zP�
p�
���)o,�
.p�l�1spYt�
��	P�c*dP*f\*g��oyp�*r�+s���(�
	�Xk *r<*sH�
���)�$�
��*�l�
HX`�Hd(*kdQ��0*ap�
���
��H*l��
����r�*y�9y�88l*r��
��t*��*�(�
.�*i�*�@�
��
�(�
�	c�d�*g�
ish�
���*a82i(+�D��X�H��
	�*b4+dP+hX+iX�j�k`+mh+rp+sx+t��
vD+ì�
���*�x�Hl�
�	x�
��<+��Hh��	$�
H0�HH�
H\�
H��
����a�+c�+o�s�+�x��8�
�D�
.�+aP�
���+s��
������+���
X�
l�+r@�
y�rL[s<�
�D�
	�+l�	��@Xì
�\	��,b`Xcd�dL,k�,lX�m8-n�-r�-s.tp.vx
z����a�,e<�i�,o`ul,ü������,� ��<�	��,��l�	�	kt#�,s8��	�,r�,s�����s@!	��
kL!���,���� -t�,�|��4�	-s0"��-��!��-�*���)��l-gX�k�-t`tz,-�-�(-.T-a�-�-��\-yT�	�<-��x-��-��-�X-��,Bd-��-l�0����T4��|�aЯd�-i��o��u�9�9�-t�?���~o��s��v��z�A����lh.p43t�zv@.�@D���E.L.e\.�\D	 .d8B��4.�|�	�
x	�D��T.�	88F��(YeDYi�.�xF���e�p��,P��|�a��b�e�fp�.r\/s�/z�P���.��:��g#�g���.a�.ep@g$6l/p/s8�z</�|iX�i�n8o,�i�n��/z�oH�o	(/l�h��0/��q�XqH/g\p��P/ep/t|/zTv����r@w)�/sL�t�/�LB
��T�.�?
��/rx���/�x4�}	H|k�}���/��{���/Ŝ���n0t�~���/e|�lL0oX0r@�s�#����0e�C���� 0�x���(0�̶��40t��@0nL����3a�e�3i�Bo��
u<�Ä0�_d��|0������0n4����0�\��0�x����0i��	�0d1g(����0�����(1e�0���H��	�0s�����0�h���1ðjL 1a�n�ju����n����<1aܽH1vh���
T1a@�c@�h��k�1l�m�1n�|p2r2s\2t�2z�1��������1�����
e���	�1g�
��&a|�
e�ut�è
#�
���1a�|i�
#�
���1a<��d��d
#t
��2a�e82�L���
��02��'
��x'�t2��'�|2��%
��p�o(r0uD2�.
2
�t;
�l�	kĒl;
.
�2a3e��fl�kd�l43mt�p��t��v�2��@
��@
���2d�?
��2l$�r0@
���2�T3��J
����a(3v�C
��2r��e�K
��3��K
�3�$X
�,]
V
�l4]
��<3e�\
�H3g�f
H�c�3nPf
��`3a�3r�3�u
��H���s��t
���3�L:e��
���3��
��D�l�3�~
	�3lD~
���3���
�f�n4p�
���3a�Ie$�it�o4�p�
�����t��,4��)���
	��
n��	�dx4g��
h�4j�4k�5n�5r6sX6t�t���d4�4���4e�4r�4yl4Ì#�!	�P�	,ah���4j�Ƞb�4r5s����4ap5elEt@5�x5�D�T�`���4�l.5�x��5z`�_�	$5c��lP5s���,5�l3
��.d5�0�����\5���\-�������'��h�d�5iL�oh(P�+��d�i@�kH�l�
m�s8�t,���5aT�n�>
u�5��U�,	�5n�
v�,���5��0�����p/��нs$6z6ì6.46o�,�9B�:�,k��l��m��t8�z;��<6ad�c�6up6Ð<��(�	����6���
P0�
LE����l�6r�C��	�6b̫g�6j07k�7l��pH8r�8s,9t�E�N��d7r��s�6�O1�N���6�����O8�P��P7l@7sX�t��v�O��7eP7�$���Q��P��H7�`7��Q	�l�tTRl7gHj�7rtR��t7a�7e8i08t�7�<8��R�S,�R	�7r�7t�R���7�P�
�0S�DS�	g�7l,tlS�,T��S�7n8sXTt�U�V8gV��$8a�U������Z���!b�8h�8i�!k�8n�!r d
t�8��[	�r.�8l�8vp[��p8�M�
�2H|;��:���8o \��\��\���8��\���8Ŝ`��`���8a�]���8t9z9Ì^��@���`
�\a.9t<]8�b��$9bd�hl9i؆j�k��m�n|9o�9p�r�9s�9tvx��dT.�7
g4e�7
n<
8f8��
8�h��	�d�9gD�k�9l�Ln<:rP:t�:v�:zTl�,l���9y�o���8
e :tX����k�����9�(u���9�8u:t�t��:exy��|��H:��x��,:�z1�|��dIeh:kDQÌ�Ť}8D~1T~p:gd~��x:e�:�|��$�	�:l�����:��~��~���:ePl�:Ø1����:������l����:b,;g�h	k8;pD;r�;s0<z �	�Ȅt؄��;����� ;�8����h	l������b�!rl;�T�	�r.�.b|;n����X;��;��,��}���	�;n��������;�\����;o�zs�;��
��.�
���;a����;r��;rl�HБ		�;bD<ed<hl<kt<n|<r�<t�<vX<�����;����$<�0��|�j<<n\�	�	��	��P<��]H�\	H��H�HpH�zH����\rd��<b0=d�
i��k@=lP=nx=p�=r�&s�=tău�=z�����<a��b�Dd�>e<�fhkg�@i8Hk�Ao$�p�As̆t�Au�=Ü�Ÿ��Д��=�t�����z$=Ô����[b(8k���� k`�td=yx�.H�	a�������p=o�����z�����k<���8qoL�	 Db�=l>r|>t�����=�@�<A��l��A�PI����|���8o�wv����=lЧ^i���	>a�^e�j�lp>o8<r�u��vD>�(�	��
n����8>��.doP>b���X>iԨd>s̪����a��i��t����.�>e��s|����>y���>g4�k�>l�Em$?n0?p8?r�?s�?v�x�2zH���4ia|il�)o?t?��а��?������,	id����Mnt�������ahe(�i�o�?tX?�8���p��|?�\��(���h?z��	p?sT�������?� �P�?�0����?zp����?s�����	a�;��?�,�	x'.d����?�����|uh�	�?d,@kD@l��p`@r�@vܼ��'����� @À�h't$���8@eL�mX@tH�p����d�i�(r�t�@zT��4��@����@e$��#.�����@e�`
z�����@at�����@g�@kAl�GmAn�Gp$As���(���4�e����@l\�����dAt�������8Bm�lt@�_�	4AgPAnlAzX�_@��(�XAs���`Ai�AÄ�4���|A����llHr�s$�	�s�Hz@�����kHGlH�t�Az�����b\�d�rHIt,R��lXR���Aa�eLB�HC�At�B��Ba�Be�k	rhB�MŌ�y,�d�S	4Br�R��@B��U	��rX�s�U��XB��B��|����a �o�BàcxBt�|������B��?�4~	�r�s�Bt�~���e��	�Kk��ĉ.HCa�bb�hccd�ce0�fT�g,�h��i
j0�k@�lئmԧnt�o$�p�rԻs��t��u�v��y��z�U�8��̉�Cb8DcEd��f�EgGiGjpHk�Kl�MmHNn�NoLOp�Pr�QsXRt�TupUv�Uz$O��d=.H����Ca�CbDiDr(Ds�����o���Ck@����Ci�`(����Caؕ�Cg4���Te(�i Do��p�����#����0De�Dh�Di�Dk�DslD�(_�	XDgD���`D��D�Нt��.|Do�!	X����Dr��Dkt�m�Dp�Dv�����a�(t���ܞj`�.�t,��<��Dd��g8ElDEnTEth����Da�Ei��r�Eu�EzhE�DV	����0Ej<�������LEa��olr���������E����|Eat�
������Ee��	�EmHRz@�t,��Etl�.,���Mdd�k�	n$Fr�t�u@����Ea��etFg�Fi��o�Fr�FyHFÌ��ܿ��Fa��	�gl�l\Frcz���0F���|����n�t<������lFy����Fn�t<���$d�n<����Feh�.h".�Fa4b��U8�	�Fk�Fr�7UHV_|V���F�����FÈ��Fj�z����
D�.DGa�Ge�Gf(�i�Gj�Gk�Go�Gt|G�t�d=.4dXGnL������'v��	`Gt�Gv����lG�8H���$�����P��GsT���m�@���l������G����l��Glr4~�H����Ga zrH�D�	4�.(Hk0Ht8���H���
v��%�����y�Ha��@Hd�Hi�Hk�HlInIrlIt���LHa�Ic�IedJi�Jk l�Jn�Jo4�rKtHKu�Kv�I��JŴ�����.���Ht� �k��������HaP��T�k�EvP������HaE
t��0It(���IadIvLI�ĕv r6\�	8Isl���@I�\I�́�H��t���DCl$�	0�p�ItH���xI�LJ���|K�0��8����Ia�IlP�����Ii��������I����I� �����IgJlDJt���L���Je|�m(Jn8J��_�������0J�����	l���0tL�XJn�Dp�Jr������xJo��j�	 �k4����J�����J���.�Dt���Jz���Js��v(���	�Js����J�����K�4K�@K�����P�iK�p�v��	,KvL�l��pXKr�
.�apK��
��
��hK��C��4���K�����K���D	�l�KcLgLk{n,Lp����KaxLe�Lg�Li�Lk,MmLMtTL�������K����tSg�KÀ����jTSuLp
�t��H.
b��j�	��k4	<Ltd��HL�@M�.�Te�Tl\TŰ
dLl�LmX
.8�D���L�P���Le�L�@�p��UdD����Ll�Lt����LaMo���p�H�Lt$	%@	MsL��M�X�� M���,	8Mlt��P}ahMexMi���`Mlt}m�pP$.�#�Md�Mj�Ml$p�MtLt
z$���Ma�b�MeNi(Xo,N��$8%��`�a�����n�,&���M�*�)�Mm�,���gX,Nn�"s�.� '��$N�<X��1��l02��<NapNe|Ng�Nk�Nt�Ny8:�9hNk�=��Ny�@_�B��((ÀI�\I�Ns(I���Ne�Ni�I��I�NlK	`�.`Wg,K.�Nap��XRȡkOs�S.�U.4TOh�WvL���O��_�XZ��.pOdv	g��i�Z��4Oa�Oe�Ol Pp`Pr�,s�O�[�]	Ag0�n�Or�Ot�
v$]��xO�Xw	�P��]n�]���OaX_U�_.�e�_�Ol+m�Osa�Td���o�0d���O� f	x'.Pk@��fUg�@Pa��#����,Pe�f4PnP�9Ph�LPf�g��TPokT~d�Pn�Pp��
r`k��lPaQcHQe4		m�Qv��z�P�m��Pydm.p�a�m��m���PaLo	�
i(lQn��rxo���P�xQ��Q��>��q��T�y�r�h".�a�V
h4Qi��k�o���@tp�x.XQe�v<QtĖvh%��,��y��`Qa�Q� y	hQnHL-���Q��_��	�Qnt����	�Qg�����Q�H����Q�����z
e�QkRlRpЎ��Ro4���P(
PX���Ri|����dr���8�	�[b|�d\j�Rl�Rn�[r</ttu46zt���,Ra\Se�Si�Sk}
l�Sm(To�Tr8]t�Tu�Ty�R�X���0	a�[jX��<���Ra�Ry����y\l�Ro��	�RlSp$SrĤ���R��S�`T��T���
p��.So��pХ`\d��ySaԦu@S�(�84���8S����lSk\{	�l�LSl�Sr�	�������tS�T����Yk|SÄ�	L]g\�l<�
��D
o��Sk��m�Sn�1s^z����d�	g�Ss
	t�H	��j܉a`�
����o̲زTs����Tot�Tk�\lHTm�&s��QH���@Tj��l�����	XTkpTn$�_$��	k�TmH���xToD�
8,�
�����Ta8��Tr�	L]l� r���^a_�t�����Ta��ü��TlX�rUth��,k8���Ua��o0U�ܾ	����(U����d���<U�4���|UbDU�d�PUr�Ut����`Ua\������D;.�Uh�\o,�	@�	PbVd@Vg�Wj�Wk<Xl�Ym�Znp[p�[r�_s�`t�azHWì����U����Ԝ���г���������#D���Ve����Vd\Vk��llVstVt|Vz@��� Va��e�Vo$Wy�V� ���`�.D�o�1H�1d��0>o��	(�l�Vr�����V����W�W�|8�H���\�a�Vu �n�����Va,����Vt���Vl��
�	��	�ViD���T.(�	Wt��.�.HWa0�bhWhpWnT�rxWu��1\Wd�3P4��TWa���#���1xh_D�	�Wk0����W���
��W�t��0����Wo�W��W��(l	��sD��Ѐ
��W���� �nx���WiXÔ	DEv���X��P9ntXpp	r�Xs���
$Xa�Xc�Xd�Xe�Xi$Yl8YmhYo�Yy,�Ð,	�
��lXr4���(s�
����Xa����$���X�0���X�l�pg�Xl�Xm���tD��Xb�dYnH�r����ag�@�m	p$��Yap�|��0Ya`YoTY�0��H���LY���|�b|Yd<	s�0
.�Yax0
�	�l0.�Ya��c�<d�<j�Yl�YnZpZr�z���Ya|Zi�Zox�u4Z�>���	��L�y�.��<���'lT�o	Zt4��(Z�XZ�>��^�T���!�\!	PZlhZr�!���ŌE	t�!tZk�Zp�Zr#�#��$����m�}s�#�Zr'|�jp�s��tT'���Za��e[i �n8[oD[sP[y[Ø)	L�t�)���Z�����0p�s<	t,4,84��$[z�3,[s 5�� @p�8)�@t�@X[d�[g�@��`[a�[o��r��t�B��(�
z|B�[rt�H�C���[dL�o8C�[d��l8\mT�
nH\p��rX\t��vpC���[a,�b�\d]eP�f$]iL]k�]l�]n�]o�^r�^s�^t_u\_v�\��X�xFH�E��0\k��lGt�G��G��P\il\o��	t(�`4H	�l�\s\H��t\�c�^����ĉ	8I���\.�\a�\b�\i�sć
z$��ck�\J hJ���\�xJ��]eP7z�\èJ��m�K@K]m�ND]d�y
k��m�Xs��z���N�Qjl]�xkPQ	X]k\Q��`]�|]�hRe(T^
�S�]t�S���]a _e�Q���]��Q���]y�]��Q��]��_y�_8�]l�R���]��RX�s�T	А. ^a(^b4^dp^rP^Ð���r���o(s^
�s��T<^v�T��D^�XU�dU��\^�<U��d^ô�e����|^������^ÌV\�
k�~.\�|����^y�X�^n�X���^a�ue��
r�^�XZ.@Z	�^rHY���^��[P�g�	h_j4_n|\1�(����n@_r�\��$_k��	o��vX�H_h_��P_a8_@dd�j��l�Br�_t46zt_��h_a`e0`iXZo�euh`z�_��at$b	�dg�_r�dsLb���_�HZ�@`����b���da��h,gn8Xt�B��d�Xd�_ph�8L�`n�f��`odf$`k�s�i	�r.��rT`v�WH o�X�vXo.\`a|`l�`s\W
�(u.��e�g
��`��u�`dv���`a0ab�
cT
d@ae,
fXah(�i"jD
m�ar,�s(�v aÀh
Bx'.ak8h
���`�Ti
U@x.�w	ar�w��a�La��xy@y��x8am�y	`�l,{ylaatae|ai4�
���
�{P��
�h���aaT�oT��bl�at�����aa�eboHbs�að�.�ao(T�����	�av|����a�b�d��P�t`�bl\%s�C(br�������0bj\bs<�.8ba��	opb�|�� ���rl0���db�L�#���|biX��bk�����ba��e�bl�br\�����etoHhì�����i�b�4���lh��b����O�pcsLO���ba�cr�cu4c�tcŨZ(1
sH`	̛l|`��(c�Pc�l���x�sT|	Dcs����L�p �	\csd���hc�p����i,�����cr��daxdb�dcped�Xe�ef�eg,gh8gi�gk�hlmm,pn(ro�rp@sr�ys�|t�u�v�x|�z`d�d�(�d��g8�k@�lX�s�C
%�5. de 5��(dz����4ds��@dn�	r�z���Ld����|����������~.�de�df`	h�dk�
n�dr�dä��.�i �k��	��dl����d�h�	��	��������duT����tP����deeh,es(�~�<U09�et��. ee8a��T.�j�n�et�ev$�	8el8���Te��������ee�ei�z`e����������ee`��er��Нl����f�r�lkl�p��rX����ea4fe�fi�fo�fyfä�	�kl,fr����f��f����_d�pgLflXfp�ftD�����l����t �`fb,���hfe����tft����p�r�	�fr�fs���&b&������fz���[dzlt�P�l��	�dglgnH�	zH�.�fe��l��sd������e���t���� ge����a��g��s`&
���Lgz�
Tgs�
��`g��
lgÜ
��xgi�	��
�gc�ge0hiPhlpho�hr4)	t�hvh���T~<���gy�gg�gl<_����gl�Dnt�	�gi(hk��h��h��h����0ml���<hph��Dhal�_���\ht�dht�Fh�k���|he�	�hv���8eH�����	�hl����hvx���hi���g�`r����ha\ib�ie�jh0kiHkk�kl�kn�ko\lsxlt�lu�lv�lz<i�l�H��ĸ��j��k��l���p�
��Tio���hil���piet	|id�if�ij�ikjmDjpr��s�jth���e�����i�0��]���ii�����th ��
.4okP�	lL ���iej� j�!�����f��T�0j�<j��,j�|j�"��`jeljo4j�("t8"Xjlk#�k,�k	tjl%��|���$���je�jä$d�r�%	�*	g�jrܩsL(=X(���j�(����h�j�X*���je���c4�lkr�g���
y`�kt�+��kel*$kt��������,��<k����-��Tko�-	\kt�-��hk�8-���ketkè.�t+	e$.�knT0��Īe�t0Թf�s��w
�1	�kl�kn�	�]�
�3���ka�1	�kh0lsPlz82���k��p�4plj`6��$la�}P�}��<l�(7��DlŌ7�����8��l���r��7����ohl��ń9P�9� ;��h".�le�nt���4��<�lk�ls�lv��	zp����������l�����l�?���	kD?��
ma�mb�me8oi�k�:l�om�oo��	s�ou�ovdmøo�LA	`g�mr�tlA��Pm�Dn�po��o��o��o��A,�B��.��bd�ht�k��n��r�mt�mäB�mr`B���me�ml�	��	���m��	�
�B8@CD..ng ni(nk8nltn�C�����(%	'Dy�D_D��0nl�F	}blnhTek|nl�nr�ns�nt0ov�F�Gn G��tne�'tT�v�n��Ŵ9	��9	���n��<W�;		�nt�;	���n�lG���n��G�,H#onot�G���ne$oj��
���
� ?	�0?	��o�?	��oä?	��H�gPon`oshoz\I���"gԜ
t�I�Jt(Jt�X	�	��xo��Q	���oàJ�fl�fs8Ke�Kt�K���o�M8�L�ol0r�M��MB�i	��o�(j	�����D���	pr�RpiTpkhpstN��pe�pk�doqt��z�p��	V
�	��LpaT)�S�`pz��>���tp�pT	�pi�pj<?p|pôO���p���\��`�	C�pt��	���p�xW���pÄ�H,[��g ql(qn0qsP[���paDqe�qi�qo�q�rŤ[��[t�[t�	.�1y�\8qgdqlxqrPlvx\��\��\qmh�	��\��pqe]	�j,\���q��q�L��q�|�
_��	���qrp]�qm�.	p�qs�]���	�$�	��]�ql�]tD�
. re��		�qs��	��r�Xqn,b4�kX�mn,n8rb�ri�rn(n��@r��m	�ri�rj�rmt!sTrÄk��`r�j���ri�rl�rr|r�4n�<n,Dn�Ln�rtXnQQ�nH�z|o��P	a��e�q��eis� r���k�ps�gd�sj�sk�sl4�m�vnl�r��s��t�u��z�s��sa(tb4td\te`�
gdvi��k8wnPwo,�pxs|xt�xu yv�s��wŘt���st�øt_8���t���sr�ttT�n,w���sa�v	�sg�vr�vstt�v���s�@v�w��x��w��
�`��`x,�x��xtn�x��titzjHt�4	����@t�4
t�zTtb�tc�td�tgujXuk�ul�um��n�upd�r�us8vu��v`�
���teH{BT{���t�,{���tô{���.�{���te4guy�t��d��{���t�<,8��{��ueH�
t8u�Lu�h	��{��0u��,��Du�(|���	ehul<nU�2%���puj�	xud����u��|���u�`}����b�ui��~�(~���ua4#	(�s@#���u��~��H�
svtvz�u��~���e/�..veL�k0vt�#yt'$��	��g4�
j$�kMlT�p�����	\vdܣg�vk�OlXxm�vn��ows��z�������vo �����a|�dPfPg�>j�9k�vt���H��vl0��ve||�v�4D�DD���v�����0�m �,�	wjT��`�$wll���,wa���xmt�Dwklwl��r�ws�wt8���P�d|wv0��o_Ї���wt�xzX�
��p���wit�	��k�I
�x.X�f�wi�kxlh�nxsytyv�z����w��x��@�����,�(xk0xp8xth�.��.ԋ.��
1��
@xm����Hxo8�	TxlH���`x��x�����xi�xrlx�D�\z
t�����	�xr܍��@�a�xe��
8��
�xn4�l�nt�r�xs�xt��������Bt���$=ð�yd$�
n����ya<yeTyÄ����4yl��	�El���Hy���,��	`yk����hy�����tyð��yd4mk�yt�����ye�zi�zm�zp{tl{zLz�Š�����b�ye�mi�mr����.�ysztH�v`������8.\zbdzdlzeP2	h�zi�zk��n�zr�zs�zt$2	ì�	zs���@z��e�H��������x	.�zi�zk�zm�zn�zs�����,xJ,�J8�J,�L�
8N��
���B���t��ztܖ��<ni��5e��r���@{kH{m����{eP{i"j�o`{rP�t+�x���)
�x��.�l|0
��$�eĝ.�ea�{eH|i`�kh|o��t�{�|@
y����{g�����{�$|��f�D
�{tH����{e����{g�{l|n�k��G
���{e�����{l�Uyȟ��|gdN
y��|h<|lTO
%`O
��4|eh���WmX|p\��P��\��`|l��xob�oc�|l��n���	t|a<}e�~ik,o@�u�y�|Ð�Ĥ�Ԥ���|a���D�	�|j}l�p}tp����|�4~��~�\�xt����,��_��
�$���}�4���$}��0}g`}l�pmȨ
n�}r�}s ~tȨ���%l�%m 
rȧ	Ø}�����a��	H�d|}�����}�,���x�k�}��xX����}��_ �
	�}g|�
���}�t����}t�}�~r|0
��,���~������0i�n~ä�	�qd�j��	lh~r�~s�~t���d]exüŌ���T~t���|�P�~e@���x~zH�
K�����rḛ8�d�~k�l�sn��<�
���~a�aoh�	�Ztx�U,�.�~e|����~z��
	�~s��
����������ز$m@nLp$����}tԳ��,��	Trt�p���hi��t�à�	pr���������`�����ط	� rL�z,�P8�|�g�rx�P����4�8�tD��d �m(�p$����i8�pX�td��p�������p���0�e,�t8���D������L��|�������r����p�e��r��	�b�dx�f��gЂh�i�j��k��l��m�n�p@�r|�s��tt�v�1(�Ԁrt���܀ed�r�ȹk<�lD�r��sL�tD�����a��e,�iL�rX�u��zT���|������H��p������ ��8��L�������	l�t���t��������è���kȁl�r��s�t�n��n��m�����e؁m�q���m�����m�����e��z{��z���i��	P�s���k����d�n����@�e��Td�r��h�����@���l������	h".�b`ye�i��nȖr�tԷ���P�8���[.n�}s$tt�����e�}s��y���d\���h".�e��i@�j�Lsd����)n�m�m�� �en(�l�m��4�e��W��	L�l�)n����T�����~dhyl�~n�~p�~s����p�aЃe<�o�����	�~s�����������`���|��P��*g�k�llr��z��	x$t������������è�����r��	�klT�
s���+d��0�lT�s�������L�z�	�}r@�.��ðJ��	l�l��TԄb<�d`�
kh�
nx�
s����
��a�e�i��m��oP�t`�uX�y܄�D�Ť�������������h�,���(
��e�Tl�tЁ�\���l��r$�t0��
).�b`�e��f�h��i�n��p��r��s̅t�����p�x.p�n�p���T��x��؅����do@W�tW$�W�
P�
������t�����������������kd��X�h,m$�sL�_����t(	%�	0�s���8��
y�.@�a��eD�op��(������BP���|�hP	��s������e���,gnH	��rh�����`���k�������t�d�eP�i��o��tȆÐ��d��g�k(�l0�r���8�b�e��k �l||n�Zr@��L���������Tl�khklt�mh�opks�$�`�<|�m���y��������	��l\&	T6g�l�n�p$�rܭ
s,�t�	u4�v�&����at�eĮ	f��i�oh�s�uT���&��&���y'�$'�T'�x'��',�'	<�rl�	t�'��D��܈�������H���	��(��g��l��z�)	�(y��l ��\,�$,��n|0pĈr̈s�,��,�<-%-	Ԉt�-T�-l��s,.��/$dԮ	s`0
�b�gl�lt�m��n��p��r�ks�kt��v�0��	�a8�dĉeD�i`�o��s��t|�à��h1�<u1�2����8��x�������@9����k0�n���(9��m9����ep7��l�m��s�tHv\:�:���b`�l;��T�z('%`;���t�<��D..#b�.e�#i�>k�#n�;	�s�=\�dX�m$sh>��@d�k�
s|y^
A	p�s�A	Ī.�i�k��s�t�A��������0B��j<B����a��eP�h\�o0��XBr`B܊zlB���exB�kx�l�t�
0��
���l0�Š�uܩ
��(��i0��
<�tt���D�e����F4�
b��n��r��z(G��d�a��e�i��ohs�z���H�����Ћ�@��@�� I܅g��lGr�tK	�g�s`M��L�k�N�|N�khQR�Q.�a&bblD�s4�ØV`�R��,����TY4xZ��Z	L�b,>	d�g�lj��kx�
l��p��s�u�Z��	T�a�e��i�Mk8�o�u �v�h��|[�\t����\���J� ��`���m���
.H]܌n��s��
#�_���e���e�|�
����h`	�>	g0�nȯ
, cXmlP�mtmrX�s�}���
�f��d���?	�Dg��
T.�a�b��e`f�i$jk��n�rps,jtԈv܍��h�#.k��n��s�j	HnPtXvth��ȍ�x��k��[.tz��,o
a�z��dȷ
j�z���aX�ehw�lw��$�ap�ep�l��o�r��{��{P�lh�s(|(����
l��r8��D���g�����e�TȎt�����a؎iL���r���ؒ
�ܦЎz�(P�����i�����d��	��lh�����������sl�zܿ	 �s��z$���0��ȏ� �������l��r@��h��$������5�����t���(	x$t�(������(�����d���k������e��	��p؏s�����	a�e�%s��äH�������܆m
��4�a0��<	�Fg�b���,���.4����@�y 	L�n��zd��X������1�������t��1�}�,}	��r�!�� �ad8u��P#����.��f0>
h8>
j�R	k�R	n@>
r#	��f0#����\���#��^�]	�nD�t|�z�]����M�� ���`d�d�`��8�a��H��	P�z8���X��$���d��db��p�k��ܑa��b��cĒdؒe��f��g �k��l$�m�n�o��p@�r��sЛt��v��z��$��a�C	bhc �d�eH{gp�h�j`�k0olP�m�o�p�r��sD�t�u�v0�Ü����x���������p���،�0�H���P��Ď.�a(o������l�z4���t/r`���(�c��hܘ�`��g�op�\�����e�����Ō���l��m4��L��a8�kD�pT�t(�è���������������� ��x�������5
eܸ�X��4���L�al��t\Bd���d���f������jd���.��a!r�y��<�.��zT�Q������gГo��}�Uȫ	ؓs����������	��j��n��B��.�e�����at�i�l��uh��p��|�<�k����D�e��	P�k���\������k��m��n���ص����g��.0���rp
sؽ��r(�����e�i��4�	��j��kP���Ԕ������0�g���n�tT���.��kp����ax�b��eȕid����	 Xb��j��k �
l�r�t8�
z����@�����ܕ��	�����g��l<�������t|.Ô�	��r����Ld����n��pxt������@d Al(An0Ar�s4du ����a`�dx�e��g��i��n��o̖s�tԗuH����	��i�	l���8�����Ė���������u�����-b��l�m���t�	��g������a������hB�����
mp�n����j(�X�d�l�s8���ԖaT�e��o(�è�	�4�	�<����	�t8�z������ȗ���	,��.d�e��tt��d�@�l��r��	���B����l�����Mv������j��p��v��	v�������m�
_��		��nT�lJt(���l�n|tt���lUo�s|����T$a�$c�$e�$gx�kp%l8Em��n�%o��p�r��s�tP����d����$��$��&��&��$%m�fp���h�a@%eH%l4%���y�%��
�$����%������L&k�p��z����
B����
��ؘ�p��i�r�����a �lP�r4�Ð�������i`�	�
jp���(������Tca��e��o��tl�Ô�Ű�B|�n����`���I	���UD�R�d����������aԙoę�����p������(����#�T�d��l(�m0�r8�sP���
ܙa��e��i�o$�sD�t�Gu��zP��T,Ŵ5�x�������	�Zlt�r���@�������T��h���nh���l�op��8���g�Fl�<	r��s$�.��e�Jz��t�	8kؚr�v�Z���e���̚t@�p����i���_lL�
n4Gs4Zt\��iv��e��0�z�P�uapPo\�P�r0����Pa`�e$����	Hv�<
�p�t�j��v�.|�a������J��	�\l�r�����$��\��p�������e0�iP�o���p����u���g�lPynd�� 
r�	�L		�t
lH�n��p��r�
�PP2rLCh�r�c��	L�t.�|�r����d�����a�L	�t��Ȝm�	ox����o`!	��g�jL�nh�r|�t��v|zl%p?n�%����a��e��&�������/�J	cp�d��kd�l��n��s��t 0��(�a�e�n$�oP�uܛy���h0�p	��0��x�u�0�1��1���)l 1	��lܝr�tD1�����������������X��l�	_�1��1�r�4�T�	��4���a�y�4P�Ee�4T8�d@�kH�s��	t�	t���5#�5�\7�|7��`�a o$��0A��XLŐC�C��jt�kĞl̞r<C����aԞe��u���ŤC��C�EP�
r,�td�����<I�$I�lLD��t
����	b��l��pԟr�s@����ad�b�
dp�e�h�i4�lt�o�p��rd�uУv�y8��x�
ŀ8�t�kD���|�a�*j�t������lx�Q�	��d<�j$����������,�������Q�	�ńC�r���\���̀k������vd����l̛	 �l���,�����$��������.P�s����X�yH�d�g��n��s��t�W������n��.��k0�����z����,!ŀ�	�Or؄����̠�����ԠÈ��a�e�g(�st�h�.(��x�������|�����8���$�a��e��uT��<�H���L�����T	����\�d<�h�l��nСr�s�}
_p}
	��rd������ȡ�p������$�����tp
8P���X�o��.�l��t8���ܡz4����
�8�t��
�r�����ol�	�kT�p`�z��v��
�����@��<���H��4����s�J��J��l�e��l��t�lD�����e��@�	��z�����k�����a8e8�oآ�L���������|������U��	�m�t�Z.t1���a(��X1Pd1�� ����ix�rD��4����@��8��L�t�
X�l��r��t����Q�
	��n��t�
������
�����-���8.�����l���ģa�	�$�	b�	hإj�k�	n�	r �	t�v�.�<.t.��	l�n�.���a(�t���4�a�e,�iT�o���T=��<��\�e��i�4	d�mLqs��v�4��t��������=�s�E��D..̤e�k�n�o03vܤØ����E��Ԥ�<�y��hY����g�V��g\"i,�nT�tԘ�th��D..g�� �g����o8�k\
l�n�o��@�a��e��i�up���q����������Hp�g��lt�stp���%l�q	��l��r�sإt��vTr��T.Хv��
8�r��������(��������o�os��`w	�s�T8�n�����a�i���n<��������@�e��H�gp�p�T�s���d�a���Ľ�H��������	��s��
8�rd�����o�	��b
r@	��p�s���Ȧa��eP�ix�u/	���R�� 	�g��#	# "	���e,�o���H$	�$�m\F	tF	��8�a\�e�B	@�n�G	��d	��x�k��z����b	d�s��t e	��L,��e	�>����k0f	����o�u	��p]eTl	��p�k	��ȧa�i �oT��0�	j(�	�e��@�	#h�	����8)
�0�	�b��	��,�r��	��,�tx�		8�s`�t}	��D��<�	l�rh�	8'
�
bP$d��eܨkd�l̪m@�n�o<�p��r@�s��t�1v(Tx��#�4
��s�C
��.�d�i0D
��Ȩa�i�>k,�l\uo@�t�ÈD
H�E
��`�	�pH
T4p(%r0�p�J
��$�e���M
��8�axN
	b��jtl��s�N
��	L�a�d �e4�	f4�i�
ld�t��vة�O
�ee��øC��r�O
�������8P
t<Q
	��
b /r`Q
��ȩ�L��@x��x�dS
� S
�l@S
����a�h����U
�X
����fhW
(�nD�sPX
t�X
t]
��h
�t���\
����rT���]
	,�.�����Ie�^
�/d�^
����a�`
�,`
��g�k�
l@�
n�p�sl`
��
��a<�b`�e��f��g��i̫j��l�o��p�s�z(�Äa
��		��b
	 �
l��
r�b
����t����\d
jT�o�	��$
. 	H�rle
��ghil�n|h
B|�l��t�i
.@

e�i
�0��j
T��n�z�j
��k
�k
������k
�����hm
���/s�l
ثrTs�m
I@n
��n
Y�j	�lp
tlo
�jh�kp�l��n(�r��s4du�o
���a�e<�iL�o��s��tԭy��|p
�p
��H(a0�k����p	�q	�����q
.��y�u	,�q
��hOz0y8r
����dl�q
	��l��t�q
���0���y������s
���&l�u
T̤g�l0U	r �s�v
��w
�w
	(�vy
@�n��o�z
��rp�sH�
���	��\���{
��d��\~
��}
|�s}
����e�li��r�	����e�J�؀
	��d�l�n�rH�v��
.��a�e��i�� �
�H�
���
	P�dL�j��
�0l0�m��
U��
	�l��
��$���
�� ,eX�h0��|��Ȩ��P�o�
	@�d��f��g�Vj��l�n��s�t$�u\�
��
d�a�e�fȮi��	m�o�r�es4�t��؉
�Đ
�dL�l�nؑ
	��
��ܮg<�
4~l��
p��s,�
���
��� ��(�
��h".P)�̣���IeȚ
��(�rD�
��L�kh�tx�z�'
�l�
��T���
���)o\��,�
.��fp�l��t�����
��t�
����a��
�h�
����eЯà�
B�p �
��į�̭
#��
yܯa�
	��a�wb<�cT�d��f`�g�iȰj԰k�lP�nx�p��r4�s��tijv���`�
��$�l��
 �l$�
��H�e��
����
l��y\�
_h�
	p�rt�
��x����
�����
8��k��
�����(�
.���x�
 ��
����a�
��(�l(Vr�u��
y��aX�
�pX�
��D..h�m �od�
���5b��
�mT~	^
��
	,�dp�i��
��4��(�
��2ix�j�n��o�tD��h���P`�
1l�
��d(�
�dT�	rısh�
����a��i��(��,�
����z��
	��iX�j�kh+r�sx+t��
v��
��б�H�
���s �
�h".(�
	�k��n��
������
����k��	lP�t��z(���
���ah�è�
�l�
��`��@HLHt�dl�	��|�a��
���k��
.��a�e�À�����$Y������@
����l�
/̲l�
��ز���0��`��$J
���
���e\�
��n�
/<|l�BP�
/(�j e
	�r.,e
��<���d
��H���
/T�l@�
W�
��l�a��
t�g�
����a0hr��� .�ye,	��gP�
�����8��(��\		
$�bL�ch�dt�g�k<�l��m��n4�rD�s��t��v�z$
�8
����<���	��4�o��0#D��8���D�cL��lX�P����X��@��Ħa0�g��iL�o��uܴy�����������ȴ�L���ld��CԴl�_xP�� !lHo�r�������������y^
l�s��� �eT�s���,�dd�e`�
n��y��8|@\�bt�s���<��".|�v�#���l�(�)��	��a�b�e��i�Wl�n��r<tе�*��������*y�+��+l-��	��.���e�/�6�(6 �jT4��(�e\�f��n�ph*v�è���8��T�el�o�8�h:�|:.t�a��e��Ð:��|�y�_��:�����������:��:	��lضs
�`��жz�`y�:8�k�:�<5��,�<��0a$����t�����|@�@��0�z�?��8�s��vx�zb
��`
��X��pA��`��@.l�t�A����	e��l��v8F��h".��e�Fii��kԷt�zzP��,G��lG��ȷe�Nb�I����(I����,P	`�
al�b@�fH�g�\	kl�m��nh�p�rйspt��	ØP��������\����rx���bf�b��X���b��`���c��������c.��ex��c����yH
�d	��rdd8�;kиrt
��
��ȸtxh��gܸn�g���a�eL�kT�lp�m
o��z���|i 6c8�glD�r�i�j��0�eh"tPmylmyh��V�(V��`���o��g�h��t�� py��ø��T��`������pp������r�s\p��Ĺi�ḱp�t�zTv���a�i��r�v�@w)D�fL�t����n�~���eT�
f@�lt�o��r@�����a��	o��غ��P_	e��ôeň���T�t��h�r�t4������@�PL�����a��i��oT`	ux�j8�����e(Q��lH�Ⱥe����Ժa��eD����
.l��t���i4�����\���x��� �i��	,�d(���8��Ȼ�$�e$��T����������e��z\��Ll�d�n�����l��,	��k�8�n�J	��.h�.$�a�cbl�c��d��eLff�Lg��i�k�o��p��s4�t|�u�jw��z�Ô��ܽ�(/bd�d��g��h��j�4k��l�n��p4�r|�s��tܽu�zx���t4at�ø����4�H�����<
.��yD�����t������	j�5m��t�Bl�
t���ļ�\�.̼�����ؼy��.�����a�kd�������	�n������h���P�jX�k`�l�6z(����(�����l��\�.h�t����p�z4	������l��r��y,]4��t�_xC.�n�������d��Ľ�@�нt@�����	H�bT�f\�g|�k��l��p̾r��tX�z�������D���������4��D�'D���@�r$������g|�0���h�����p�ä!P�����a��c��m�'��H��`�8k8t������a�i�u���
���|�������e�k0�o0�s$�ô��D�ul�����,��4�f�	8�s|���@������L��L�~���d�h|�s�3.T]a��jp���sL�����e�r0���b4�cT�dxGjh�l�m�n�r��s�v=x�z��#�����a�i$�o,�z���0����o���r��<�yp���4)e������@������H�Ð�y����`�l�m��ì���P�������83U �	��ftr4�������������r������e�����bL;e8����c�qe��t*
j������a@�ex�i��o(��0�eD��� ��d�����|�8�lP�s@�.\�z0�,|�	$�k�����p�n�����n������e��zH�.�L��X������d�����Ð�������g�h�l(�p8�r`�t��v��P�eL����=s0�y �e �p �y�e'<���0�d�GtP�yd?z���H]�|���X�e Wkx�r��t������\�����.������e<��D@d��g�k�7l8�mt�n��p��r��s��t�v�z,��4������P@������è��L��r����o������$�����T�i`�u,�à���L�n��	���@s@���h�a���\�a��k��������o���t�����a��o4Mt8�����H���<�m��t������t�����ax���lA�T����ð���4�m<�o��z���B8���h�b�jp�kx�p��r�Av�z�!���l6�����la��b��o��r��t�������������X�
�������8����è� ��^
�;��s���
��
�4
����l<�rT
�@�fH�kl�l|�m��p��r��s�6
�

���Mm`�tP
�\
��X�a�
�� Ca\ii�
�D
�x
��ot�	�
����y�
����t��z�
4
���n�rl
�x
������
������
��
������
�����t
�L�kx�l��sD�v�
f�
	,�r�
��4��`
��@���
f�
	X�z�
��`���
��l��
B�
��
������I��
�
��r�
����it�l��rt
��xkk��pP�	t��z!
4�eK
�#
��l,#
����e$"
��rP&
��%
�dL�t�%
��$�a\�ol�rp��x'
�,.
�.
T�p�1
��'
��h��X2
���d��g�
l��m��s�2
��2
�� Z
.��r�2
	��r�2
������2
����r����2
����3
�84
��z,4
)�4
��jСl(�r6
BdZ�(6
�� �a�7
�T�gd�lx�n��s��t�Iz8
y�8
�h8
��\�t<x��8
��p�n$9
	D9
y8D9t;
���c�kg(kx`v;
.��a,�ed�fL�iX�k0�l`�o��pp�sx�t��ø��?
��pp�s0@
������J��J�h��@�����C
��I
��C
�$�m@�npJrJ
���\��R
�йn�V
�PX
�4[
��^
/�`
)�g
��̠l �o�f
��b�cD�dp�g��k��l�n�pD�rl�s��tpuPf
����a<]b@�d��e�af{g��h��ip�k��o@p��rt�s�et��u��y���T��X��tg
�r��I�g
,�lT�t�g
��4�a$��h
�$n�k
��\�e�j
��d�rm
�,m
��|������l
����c@k�~nl`v����H8n
	��z`	�����Xp
�����`o
��p�o
����a�eXq
.�ehq
��l|��t
��X�a4�e\�k|�y0)�hu
�@�s�u
�,v
�<v
��H��Lv
��P��w
	x�g��k��sw
.h�a��u��� L�\L�w
�$w
8��d��m��u0w
������
v��0R��w
	0�n47s�Ot�w
�������Xw
���p\s,�u���px
�\��x
$�k�x
p{.y
��8�apwcX��`y
�����ؘT�z
��d�oPp,�s��zԝy��a�{
.��tĝh\��|
��g�{
����i�k<�y~
	8�b$�c0�gH�lX�rx�s��t��vD~
�����������H��������8����P��	oh
���s�
���o.�a��
��@�iP�mL�
��	�a��eH�i��n��o0�s@�t\�v���D�m _��Q�����������y��ì�.t�a�a�Ȉ
��c��k��lhI
%��
����k(�����t�n�
����a�
	�r��
����t�(�
��(�m�ApX�
x��_��H��_��P��
P��o��tp�
��h�z�
��
���
����a�
����hܧs�vЍ
��vi��
��

��bخf�g�kD�l�)
m\�n��rx�s��z��
��
�������
���1y��P�
��H�e8��ĕ
	�st�
��,��8�
��
 �	a`�b��e��f8j��l�m��n�o 
r@�t��� ��|�
���
������
�ng��m|�
����e(�md�z�-��-	��t�-������
����ð�
���
�����ę
�����Й
��dܙ
	�a,�c��
����,TQК
��
���
����o4�ô�
�h�e��
��P�t�\���
t*
j��kğ
��p�a��d��e�i4�o���<�
	�	l�rX�
�������X��̠
j8�el�
'�ut�
��o��p�~�|�
����o��
���
���l|�
�l�*
s,�z�
�d�
sD�t�p���
��H���
	L�l,�
P��e��k���8�
��d�z �
Q�����x���
���o.Цb�r��L�
		��g�j��k��l��	p@�rl�sx�t��v�
��X�e�n@�
��0�eL�m�'t��0���
 �s��
����,H�~� �
��(����	���
��TKd�l<�	tt�z4x��
P�h�n�
��\�z��
��<k���<�
���K. �
��sĶ
����eD�tg��
��nL�
����e��
 �gT�
��a �dP�f�gp�k��l��m��n bp�{rP�s��tL�
��nl�
���eH�i<��
���
��4����
����� jl�
���
\�n<�
��d�o��
��4�eļ
��|�lԽ
���ph��о
����d��f��g �tD�vX����
����e$�	o�
��T.�pb<�e�g�u���
8�
������G	�X0����������e��	��	8��	��0������8���
��ܰk8Bmx�t�8���d����
��}el���l,K	t��
����a��
	��jH|r��v�I������e�
��k4�
����e��
���
��i�k<�l�pl�r$�s�
�z�
���o��
�������
��$��|�
��0�t`�y@_
��^
	L�g_
.T�a�
���a�e��g��k��n��r�v��
n@�
j��i�O	��s��
�������j���t	��\��m��
����a$�
��d,��
���������
���o(�
.��l0�t��
���zp�
��lp���
���C
�X�
	t�i��n��p��s��t<���.�a�h�gD�
X�
����i��
���p��
	��k��lȢn��r�s�v��zh�
.�e\�
�d�
����sp�
	��kL�
������
�����x�
���s��
���e,�i�����
$�g�
	(za̴ed�r��s��P�
��8������
����h�l��sXqiiD�
��x�e��ô��Ht�q�������
8��
����� ����
����z��
��n�
����aX�i$���������x������X�
	�d�gp�
����L	�P����
���
��8����
��@��l�
L�kl�l|	ð�
8��
����l\n��p��t�	z��
�dX�g��r�Bs��t,�
�L�
����a��u��
���
����a��ut]�0�
	��j��rx�
��
T.�b��h<�i�Pk��n�o̦r�Pt,�ü�
���
��$����
	�gl�l0�tX�v�zX�
��s�
��T���
��|�l`�ØsD�
��T.�i�
	��r���.��e
��b�c(�g4�hT�l�m��n4�r��s`�t8�L����a�b�i�ul���	�t
#�
�� s4��D�.X�a��b��e��i��o��� ���P�gh�pl���(��p�u�	x�r��s���������0������l_����������n��t��v��L��r@tt�������@��(��,�a�TL1lt�n��s0��@�e��i��y���xg_g��l�c�lt���ؔ����������à��nP�P������C��g�.��a����Db�dp�j��ld�
r�s�����a0�e��i�c	k�o��u���4��!Td�g��lXn�$	��$		D�z@&�L�e4".X�y�"	pl��nt!��p�����H��:	,@�t`#��d��n|Ip�#t�&��&��r�'���o'����d@
e@1i�ø�	p(t�'������t�	e�+H�l�
mH�t,�� �anX��/��,��P��4/�+d��k��lL�n�+sp/��
d�a,e�ix�k��o��s��t$�z���T,��/��/����a�0	�g�0�����@��$,�D,����4��z�4f��e��iX6e�S
�09��k�6.�i@�oL��9��9�8�nT7��@�
��:`;��X�a-c��o�6u|�Ð<��\������?	�X.��b�li�j��n��
��
��C	��d��f�g�j(�r��s��t��zL�K����ldL�pL�����@L����øN��d��s�6ÔZ��d�eL��t1.�[	8�tp[��@��Tq��a�X�s�a��`��\a.l���]��x�z�c	�X.��r$c�����\������b��	d�h��i؆j�n|9ox�r�9tv����cHdp. �a<�b�7
gD�hL�nT�v�c��D..,�a4�r�c�k<��ԋy4��\E	��d�,�
�e1��
�p�
��d���e��l�Àf�h�$h���������g����d���c�$���h	�d�g(�l��n<:rP:t��v��zm8��	s�l�����Tl. �k���,l���y�D��o��8�dD�e����i1�p<�p���XqP�b��t�t��X�ett	h�s�t��t��(r�x#Xw����i��
n�~�1n�~����ePl�	��c8�r��z$�t4�.��a�i(�o��H�����sD7_`�����l���5
p|B$t�#����0�a��i��l�1uT��������|������f�|���h�sT�	p�s��0?z�S��S����a��������a�e�o�s��À�Ŕ�����������.��ep�	��rĉn<�P������	a��e��iX�����h�o`��Ԩ$�s���4�oL�	@�r����L���2�po�Nm��.��a|���t�y����gl���`�.��j�������t���	��c��������
���������k �r$�����a(�b<�jH�nl�r|�s��t��v��	x���`�e��
#@ ��4�e�#����e`��h#�t#��X���0����ed��`1��a��oT
#p3��,���<��H�e�U	@�lDLr��t�U������B���i,�l<�s��è`��r�a��eH����������È��n�#@���$�e�yԻPx�i�X	o`���� _
kp�m���P�����������D��2210112100010021210230120002002000102010200010000101210021101022201022102002100021021221112210020000011022120200202020201020220121020200030200002300011002010110002120012110012010100021011100010200110212000210200021000022002210022010210121102221020220000300020220100020200012001200001110020002110010010100100000012201000001020200100200100100100100010002000100000002102200120101011001011000201200111011102110021022020100210220200100020230001222002002002200302200022120320210100020023020122010223002102000021200222300321003200000320002132000320002140010030000322322202022100021102210002111230100301000200012021002200100100212	20000002120000002011002000202011002210001013000420102002000122021000200022232020010102120000200101002020001000210100212010210000101000100010020000120100210020220020310031000211000210010021012200010023000012000003020130012020012010001200001012200002020000	2000000021220020000000120010012000000230002001020000120010001310002002122000200	200000000
2000000100210020021011003001003200202102010210202020020002002022101002210212210200221001000210000021220212100212000002012020120001200200201010002011100020100002011000120200012000102101021010221202002121210020002102100
210000000021201002120012212002020000221210001000200	20020000010200000210000002000020	20000100021020010	200000100210210220012210021000101000010100001001000020010022013	212000000102210000010212010001200220	120000000120010100002020002120010210202020000100	10000000010001001000012102100202300
30000021202002000021210220120122000210110001200022112221102	210200000320020102000201200002010012201010020100100	2010010002011022110102010	200200100210002012101001	100001000
2100001000202200	210200102401400010002211100021220011200300100010000020100100002200000221000020200221230001220201000200001222020002300010230000022102030001000212021	21000000010010021210023001000211100230100010010300021002102010023010021001012130000022102002211002200100	10200000032000	10000010010201002122	100010000210110212000102201000	100020000	1000200101021200102122121021210002010000011000020102202010200220100200201002
1020001000102002001020001010201000	102001000102200003230000010301022001020121020010021020210200103012300220100202001002201012220100	100000010
1021020000100110002002213020020122230020022110002002020210220001001220020012210	212001000212001021001222000001021002201200203232121202120000020021200100102102021000100202120	21000200030002010001210210	2212001002300100
2012001000	201200000
20120000002210200020100020211010002010210200002012200	201200100230020221000002212000300200300030	201020000	221020100
2010201000	201020012230000002012100320000221010022101000	201002000	20100000020100220302000322221200230030002210220430003200122102020021002000
21000000101001100
21212010002121020	210001000	21000020020212300020021221212121021000210210202102120120002001200110012000012	1200110001120300000020210020212100122001202001020120002032300032020320100320100032010320200320001003200010	30000100032100021020012301032103002032000000120001003000100300000002020100101122000301212000011020002000000100220000200
2000001000	200011000100000001001000000021021000002	120000200
1200000200	200002000
2000000000
1200000100
1200000000	12001000020001100	230000100	201200200100003221200020
210000010021000001000210002002012100020002000212020	120001000	210201000210200202022021022101000010202
1000000000120022020211000202100120	210000100
120000002010200220020200	2102001001020212200012010100210200022000001220022000210210110120011002012201101000102102
1000001000	1000002002120001212002001001200400200100122012010	20120100020120100220002010210001021100102003232300202002002000010312201102210200000003002100	21001000020000212002002
2102000000322001212032002003202000	320000000	320000020
3200000000	320000200
3200001000	32000100021000020
320001100032000200
3200002000	32000110020011000320000203200100032001032002000
300000000010040000021220001020002010021023200000100012002100120021202102120020120010200021210120002020020	201000100201022002010201020102000000
2010200200
2010200000
20102001002010200100020102000100201020000000	2010220002000002020211002200220212000300212021122021102302010002031221110211102122002020021200000223020000410220210200120022002002102
2300000000100002120012241000212002100120	2100110002100123020	100212000	212000010103	210020000
212000000010030	2100000201003000100011001000120003
2102001000212000000002002120021000000000
10030001001000000210002030230200202120020020010
210200022221000012100012001001010020010000
2100020010
21000200002210212	22102000030200100102101001022000
1002101000
10020010003120210211002220020000121010000	200000200	210010100	201020010323202001200
210100100020210220000030320212020000001000
2000000002	212000200	2120100002120000221002223002120	323001000110100
2102002000221220210200322020202012020022120020	20100002020002203020020020002002201021002210020	201000200
2010000000
2010000100	2010000102010000100030000200	3000000002010102232002122001012010210000301300000210300021201210000021201100210202120000000100200000002002000000010002310220020100
2000000200
200000200020110200102	2030010002030000020300002000010210200002102120	102000100
1020000000102102001021000021001100	210201020210201221020102210032002102021221020001000	1200000201201220022010010200	32300020012000102100001022002120	200000010	100011000
1002102000200000322000000223	212010100	21200010021000010100	21000001021000032
2120000200212000010001030012021001012002120002	10021010020202003202	200000020	210200200	21020002030002000300000102020101210200010021000
10000001001200021211000030000101020012
1000000010320010030212	120020000	120000100
10000002001220200102021300300030021030200000201021220002100
300030200020121200	20101100022000000300012300022030001020102020	22102200022102200	10200001010200000100	230000200	230000000	230000010	1120110002320	10020000030000100
300000000010002021120100121200	210220000
1000210200
21000000202211002110020210202002100012000000100
12000000103203003001000021021
2120002000210030210001123
203000000012102100230	300000200	3000001002002100	10000210010001100	21200002021200030	100101100
10000011101102100010033220002210210032203400100300000221000022000001	320000100PK
!<���-�$�$hyphenation/hyph_ia.hyfHyf0tP�����'-4�������$�'–’-111001�"��������.a�b�cXde@
f�
g�h(iLk�lm�n�o@p�q�r�s�t4utv�w�x ypzl���ph���axc�d�i�!m�!q�s�����sX���e
(���n�T���i����e���adb�c�de,gXhhi�j�k�l�m�n o(p0q@r|s�t�v�w�z�pl|
|o������a�e�h�o�r�uXl�rd��X�m�v� � l r��	%�	��o�
DePg<)�
<d� �D.(��`c|s�v�
�.L�����a�e�o�u$�i<2���6��m����is<�p�@�����8a\ido�uly(��X���th�tH@��h��yDF,�rt�p���a0bDcLdTeXh\i�j|l�m�ndo(p�r�s�t�u�v�
<odI�X
(ps�
M�
��ha����u�y�Q���U�4�e�i�h���i���a4c<dXelh\i�k�l�m�o0q�r�s�tu,y�z�
X�YL	��Dn��Ln�_���dr�s�t�ud�d4iL
�
(n�l����c�p(r�u�x�
r�{�u�o 4��a�e�i DnPr}��<eDx��
�aLd�e�g�hi�j�m�o�ru�v�y�r�s�	}�	���u�	��	���e�o�utM
}�

t����e|�s(��a0pDs0�((t�
}�
��<a`ehipoxu�
M�
}t}TM
��l�s�M��a������m�u�o������st
du ���shu���apbxc<d�e�f�gXh�i�j�k�l$	mL	np	o0q�	r�	s�t
u�v�w4Q�����a�e�h�o�r�ut�l���e�i(�@
�fl
 �
(����a�e	o�u$�	d	m��H�8	a,)�0	g�d���D	ld	o(��\	p��	g�p�r���8a\i�	o�	uly��	g\	p���	r8I$	<T�	m����	e�	t
u�
�2T
e4,
c8
n�}���$
e���	�al
ft
h\i�ldo�r�t|
u@

�
4���aLd�
e�g�
il�mndo�r�su(ylz�
v����
i(�
m����
a�u�
4�<n ��mLn`r���}��Da�MDXap
u�c�u��te�l�m�n�o�rs�tx�t��
�	r�����c����o����	g�prlI���h��l��a�bDc�d�e�f�gXh�i�k�l�m
n8
o(p0q\
r�
s�t$u�v�z�M����a�xX�o�%�p(F@
��a�b����dd �����
f 
r(
s0
v���x�����	gL
sT
x��u���8a\it
o�
u�\	p���	r|I��
c�
s�
u����
a�
e�
ip|I4I02T�
q�
s�	I�2�
ln
I�h4�}���,e��4l��@a`epr����ha�����|g���lr���apb4cLd$el
f�gt
h\i�kHlhm�n�o�p0q�s�t�u�v�yD�@���c�Ix��h��c<i�<�4d�
\u�����Tr
to<d<d�o����d�p��\�(�i@
�
�u������n4���e�i�r8� c��t��h���adbDcle�f�i�j�l�m�no|p�s|
u�v�y�
c�s�	M�	��|e(�p�s�
��
���i
�eL	�l�n�
�a�o�2��m�
s��bp�2(2�<n�pPrds|�\��4y�2���Hr����\ptt��@
�s2 �r�<D�r,e<lPn\p|
u���apbDcLdde�f�gXh�i�j�l�m�n�o0q�r�s<thu�v|y�z������4g�����Hi(tq|x0.�(tq�s�	t2�
���p�x��b�s|x��l������p��i,l4t�)e$r@�T
�u�PaX)���Hh`p()4a�e�i ���a�bc<d�e�f�gXh�i�j�lHm\n o(p0q�r�s�tu�v,y�z�ls�I|���o��(l$I� e�}�
4a��<n����Tapo(
s��|s����8a\ido�r�ul}��hT����l�p8	�o��ao������c�����s4$g�
 �n��
4aTe�h\i�l�ndo�p�rs0t8uhy���|s���a�o������p��a�e�
dQ@
�i)\�a�
o(y�)td|�
4LbTe�i�x�d�to ��\l�}4�a��|uP ���n���lqr���apbDcLd$e�f�glh�j�l�m�n(p0q�r�s0tu�v�w�y�z0dcDgLnXu�
%�<i�2
Pt(i���di|
u�y�� ���d��a�����q\<d���e ���shb|c�f�g�n�p�q�r���adb�c<dTel
f�gXhi�jLkTl�m�ntop0q�r@s�tTu�v|y��d`u�����tt@
 �%,�r�*����n(/0/�4@���g�m�t�404�l$�e(,g@r�
��$et
�\
��8oL�ha�����`v��b�c�d�r�sx�x�e�lT��44�c<�oL4����d�g@9����s� @a0l8o4 (ih�>d �ULa�I4axb�e�i�n�}���pa�r�n� ���lmr���aLd4el
f�gHhti�l�m�o�p�r�s0t�u,ylzd���b0�@��t��x�� o��(c����@lXm�C��
��`p(��hs
�o�������p����s�xH����y�u�o�N����r�
4��a�e�is�Mh��a��Pnps�v��
 a�b�c<d�eil0o8rhs�t$u�v|�I�xl����a�l�r|�I��o����a�e�h�o�r�uX�m$	2l��l�T(��b`c����a�e�o�����8a\g\ido�udX�
To����	t���a�i�n�r�
I\
���u(���r�Q�^���n���aDc�eXh�i o(p0q�s�tuy4a �l�a���apbtc�d�e�gXh�i�ln o0pDrds�t$u�z����la�e�h�o�tl�M�
�a�^X���r�Q�a�e�o������c�mPI��n����ae�2dgd�<s@��$o��Po�<sT.���\e��`e\iulz�f����. i` m���n4
�u����c� i���l�ml�.��e�m@U�i��� s� s�z4��( .<0 eL	��< tlH n�T e�����l .� i4!m��t nD� u\� c��l� .�� e� m@U� i���� s�� s��4��� .<!eL	��!tl!n�(!e��$	@!.$��H!m�T!ed��`!l��l!s��x!o������!.8"i|"m���!n4
�!u����!c����!l|�!a����!u��l�!.��!e"m@U"i��� "s�,"s��4��D".<L"eL	��X"tld"n�p"e24011021034001004020120121010343101103103001041030000143004302002124301210202010100042032201234001303434043210302030002310430040010430004310010121330104010101011430102300310040100200030114043334040001212424003040310320020121003120301221103210321221202210430114030011230100211200889800889988989800889898898009889800
988998898980098898988980088988008888980088889988989800
8888989889800PK
!<3X���.�.hyphenation/hyph_is.hyfHyf0tP�����'-4�������$�'–’-111001�"��������.0
a�/b,5c|5d�De0Pf�ZgTghTliH�j4�k�lآm �n��o\�p��q��rD�s,�t�u,v�wx�yXz�(�t$a,k<l�
��x�0
��	Pf�g�l�mn$rds�u�����a�bd�edf�g�h�i4jdkl�m<n�o\	p�	r
s�
t�u0v�y�ô@@ԑ��4a,V
���Hnlr�s,�lde��dW���|���\b��������� ���n 8���e�l��"����s�&����knh�)�-e�<aPt�
0�44bL�8� ��Hh�";�!\pX?�&paT&Dxm�+G((���m�n�(�������������<,,-�a�/I�/���aP5G m,v|5���a4r<sDu4L6Oa�6SP:Gp;G�<Xs�<W=��PtEZE_dk�D��li�D��xd�f�i(k<rPy0�b��_�r�E���itE-�t�rL�_d�gT__�ntni�i`F�gnxGGdGmk��G�G tL�J4m�TN��HfPG0P��\a�i�j�l�r�SG������rԂ����U����|UG�alUG�VG��x�qpW����pZsr,s�Z���a8dHe\jhlprxu�[vd"?�\$lx]G�IW�]��@m\`zt`��Taa<cG@eDi}Li���k(i�s�hG�rTg���e�oGTl���l�n�s�rb�r���j�q-�g,wDPu-�m�sh��ltz��s �z@rH���(a�v��G�l�m�p�r�s4���Ha�n�ru ����|e���$�4�e��x������4��������t$����t�G��GL�G�_�rԑG<g���ate�u�� ���44e������Hv4��Ps�\k����hi<������bh�������G��z�j�kآ���a�i�ju(y���L����r�G �Gܬ�I���t,st4��n�s ���4a`��{L����Lfh���T�x��L,���pf������.����uؾ����H���������l� ����a 	f(	g0	k@	mL	s	� >x=_�l�����a���	�L�G��z��G�����8	i��@�\���T	al	l��G���	m����t	a�	����G�����	��	��	���G����	�4����	Ü�������	m$�4-�	n���	a����	mD���
a8
kd
ll
m�
n�
p�
t\�IL
iT
j\
rl����Gt�G���
�ܥ�0��x
l@����
���G�
a�������
a�_�
r��G�
rD�Gh�I�
a�����G,����
ai@rHudv4ô�s l������Lf����(��G�s�qH��P�H�_X���(���pl��xg�n����n�����-�a�4�t�_�s�-�u�q�	���r�t�
q�st�� _��E��s���gT��g,��$eTi�w�x-@tt-HsHT�_`r���hi���tfx�-�s����i����v�����l4���f�g
r
tTb�r|��!���sX"�D"��
u0
��
b�
d�� 
a�
b@cHd`e�f g�h�i�j�k8l�m�n�o�p�r�!s�$tT&u�'v�'y�'z�
��
�H�((��(���
�(���t���(��'��'��/��
ayÐ/�2��2���
s1��4��
�t4�D�4�� l�4��(k,5�|5Olu`�|908��X��<��=�	�a�fgil(m8sLuXvt	�
����x=_�l�r�� >�>P(?�a��PZ	�Q����0?��?��@ i�@�A!�'B��0h�.TC_D.D5�D�pixs`F;�Li0P4
�afgj4l`olr�s�u��ܕ?�P4�dP���l�r�D�P4�ntQD�Q����4S�S���r|S&U��I�U�� �|U��Du(�VO�VT�V��Lk|V�Tr�V�u����YpW����|��W^�Wa4�g�W���d�k�sHXmem�BG�oHY-�l�nr�O�Y��Yt�Y����Y|èZ4	Ta�i�j�n�o�r sXut�T&�pZ_LuL]]`l]��h�x��_�T__�st`���Hb��ab_�r$b���a�e�4t�;�b���t<c��a(c�\��p����0�_�td�i4pDt������h�-<j�e�@e�PlgIg��d��f�l�Tg�Tl��n��PmD�l�����qi*tz���hH�4�à�DԂ����4�4
<ade�i�j�r�suv yP�0�T��sH��$p����0ud�t���H�0�(�����q�ܴ�lns-ti��m�nL����n�sh��0�-�u�m�t������,����i�����e4�����ls����� �����D�4�a e8f@gThdi�k�lmLo�s�u�v�y���Oԑ��|g�n�s����ip���dPm��l����P�_�n��\s��7m�uh��H�Gh�������x��������I����m0rt���_���Le8��*�l������\sD~����pn��x�4�j�s�u�����������������4�s�����t�I̡�8�	X�����X�-0n��(�4a<i�����(n���D��GDg`khpԞ�������p����xÔ�0���t��������4��8���4�p�	L������|��آ44a�d�k�m�s�t��D��gHn��-$nXrps���D�mPihsT�p�P:����xr�D��������,����i�i��i����s|��ȫ����e�i0�-t�_ ��<.La�deg�i�k�m�n<oPsxt�u���D�/�_D.pd|g�l�u��?��Oha 0�#4�)P�/h�������������������.�e�r���_85p���������Gp=<�Cpi(�H,a4d<jXsl���x]_t`�$�M4�Di���Lp0gR<���d��lWܴ�xb���]�����D~d�����n\�i�l�v��n ��bq�L,�-�dfhk l(s��v*|��s�v���4k��I,���I\i���Dt���|�D����d����l�ܺ���,��\�������� �|\�4�aei o0rDtXuxü�@�_�l������rP�?$�_l�,�����(ep�`���<e�l�_Pn!a4��dr����l����<.�a�d�efhg�i$j,kpm�n�o�p�r4 s� t� u<!vL!y����t�4�u��4�fk$nDrPspu��4r���g4i��������<p0��dt̑��"�\.X�x���k�r�s�t�Ô���	x�`!�������!�(!�X!�d��l���������� ��h.�8���D��L�����]X��.Xadils|u|������8����@Ü�_Lr����]��TC_t.,�H�f�i�k�l�n�rt���.�/5������g�ndG��GH�����J;|MD����H(eLsXu8�\�S�8���0�HX�DS��Dk��P�\���`j�r�s\d��c��|������td�l�d������������f�nst���m8�!�d�r��G-\.��-�t��&���|�����.LiXj`r�iL���Ds����������hs�Ä�D������X�_�.d����a�]0�������G�l�t��H��t�,4���H�����a i u��-`-�������  �|���T e\ kl px t� u( ����m$�2��d ih��� ���7����� �����?��_� l����� i� r��� a�D���� ���4� ���!g !m!��������!�<�H����K4!s������4���D!fH��h��!vx!��.���p!�����D��!a"e("iT"j\"kd"l�"m�"n�"o�"p�"s�"t�$u�$v�$y�!��C�!nT�|��Q�����!��$� "��"��$��$�<�CS�0�WH"n�\��-4"a��m<"n��c\�h�Hx"a�m��p"g�s���L�C��x����"��I����"t(����"�����"v��~h��
(#aX#e�#i�#j�#l�#o$$rd$u�$yh#�������#����D#nP#u#ü���<#dT&���`��h����$�`#��#��#��$�|$������_�#g�#n�#r ���#g�r�t�H����#�0����#������xL��X�xl����8$����$�L$�D$�T$���-$�d��l���0$�4��\��T��L��h��\$lt$nt�������������������
(�0����/,�4%a`%et%h�%i�%j�%k�%n�%r&s&u4&v�%���D����%�����4%k<%lL%nX%r%���?�2��D%v(�
p��l%y@�|����O��_|%l� ���������%s,�������%�L&�$&�������%a�%e�%ix�������t���%p��?�_&n��t��H�_,&eD&it�����&g�&m 'n4'pX'sx't�&����.�&l�&nh���x&��@P��&iX��\ p�_�&s<�4�&a�&b'u�I����&a��2�&n��pH'r��'d��L4,'eH'lP'p��& %�l'a�*�Hd'mL	�	c,��'a�'i<����'.z�'rLDt-�'r�4�'f�X�tc�'�\/l���'��|450
_�(k�(l��(a�(b�(dL)eT)f�)g*h *i8*k�*l�+m,n|,o�,p�,r�-sh.t/u8/v�(���
((���hN�tz��=� ��4��	���t���L�84�(o�/_�(u�3�|5�=�)s8)u�(���/��(�h/�B-)n,)t����Y�B:$)aTC-D)s��D_0P�)l�)up)�tQ�Q��h)��)��TT|U�)��G����)�����)��U���)��YHY_�)nT_-�Z���)i�)l*r*s*vaG<ctd��f�Tg_Tl_��-H*mP*p4���(*a`*�$�����8�Gt���X*�\��ԑ��l*m�*r�*s�*u���t*a�*e�*f+mD+pT+s�+u�+�D�c�*fh�>�C��D����*tH�����*d+sHX�����+k(�0+a<+u�X�-(+nܬL\�<����4L+el+v��Ht�Gd+a���4x+g�	h����+��+��I����+�����+�آH,s�+�t*Lܥ�+a����+l�����+��G���,l �-<,aT,od,�����(,o�_0,rL,s�!�,�,�Dh���t,�\,�\�D���\�� ���4�,g�,n�,r�����,a-f-g-k-m0-nL-sX-t`-uh-v�,ü���M�������p-��,���\�L�R��_��G<-ld���$-s�h��|���D-t��L��_���h����<��x-nD����-e�-k�-l�-m�-t@.uP.v\�m�-a�-e��H0���U�zh�z�-i.r8.v��L���8$�����-�,.���-.��a4�.$.k��Y��^��d��GH.a����%�,���\.a�.e�.r�.t/�p��0����4�.s����.a0�h����.r����.a�.i�.u�� �h�..��_�.n���_�.n�������/��_(/kt�T,��0/eP/��DH��`/�H/���t��a���p/����x/�0
-�/.0h,0k80n�0r0����/a(1b�1e�1i�2j�2l,3n43o\3r�3ut4y1���@@Gt��/k�
���/��"�;��0l�4 0s�mT0a�0k�0s�0td0�L|�]���\0���m����p0n�ix0aP�x,�I���0b�0n�0Ô!�X_�0s��0a����0��-�-((���0s�(���0��4��2�<3�5�D4�|4��/P1a�1i�1u�/D0��<1��/-d1nt1rD1�80��0d�0�l1n�1&�Dh����1��3���1ÀD��1sMI�L-�1sTlz82fH2lh2n|2s�2t2À?LPm���1i$2l02r�l���1��@t�@��2s�@��2i�my�m�b�o4@2jX2l�p�s��qm`2nTv�Pu-t2k�x��2a�x-Xz|tz���2a�2s�2uh�d��H��2a� �-�2r�D 3Ì�Tf�2n����2n���3uh���3� ���� ��P3k��
����H3s��D�3a�3o�3u�3�T�`���t3���4�3s|3�0�D�������I�����3������3m�3r<4sL04s�3�8�����3������4����4è�m4a��$4t��	�T4fh4s�
Gh�-$)a
H\4t����L�-����4i����4k�4n�4r�4t`l���4a��,w�h�-�4m��_�4s����4i����4t4�5kL�]t��5i0
��$5a@5iH5kTlG4�G0
��	�/.�5f�5k�5l6m6nX6r�6s�6v��P5a�6b�6d�6eX7f`7g*ht7i�8j�8k�8l@9m\9o:qP:rp;s�<t�<u8=v@=wt4y08À����I84�5.6s���O��6g46ì����,6�H��4@6dx6Ä�H6a�*f�6p�6�T�5`���p6���x��6r����6��\"��!�6k�6ld"|�'��/_|54tD��D���6a7f7g(7i47nP7pD7�tE�FD,G�`F 7l$Jm �@E��<7��J0P�Zsl7iT_�Tl_�7f�7l�7m�7n8rHY��m��7u�p?�o4�7l@q�7a���Tr�r_�7v�qm�7a�7e�c�t��s���7sx{�tz��8dp8n|8s�8v\8À(��P=�8�|9�p=�=�|4�H=��{a{��T8��~�D~-h8uhI�H��8a��LH�8a���8f �-�8r4�s�s�8a,9i89uԑ49g9u�8à�������8��?��Pm��l��9����� 9ð��آ_�H���H9f����P9l��a���h9� ����9f�9l�9m�9n�9r�9sp9�L�D�h����9������9Ä���9at�-(+n�$��9e��I��-:e��������T��:fp:s`��� :���4�:f�:r0:�����<:a�:e;iH;oT;u$;�H��|:t�B�,Vt�4�:n���	�:a|�I,����:g�:i�:yps����:p��x=\�����:a����:������:�,�I����;k����d;�;��a0���4;���G<;��G���h���\;tD�4	�;a�;j�;k�;l<n$<s,<tT<ut<��?����;l�������;�����;�\�z�;o�;u��������;n�_��a���<�����<���h��8<i��2��h���@<���_H<�����`<f����h<�,�s�_�<f(/k�<m�<r=s��<t�Xa�����<b�����<�L���<È�-�<a����<t4(=a�	��=n0=t��
G,��t��`=fh=r��4D0
_	�5f>g�(k >lH>mP>n�>r�>t�>u��x=a�6b ?dL)e(?f0?g*h�?i@j@@k�@lAmAn|,o0AplArBsLCtTCuDv\Dy?� �43ԕ�>o84>b�d<>i\�d��4>s���Up>m�G���\>g�qd>e����|>a�>f�>l�>t�>� ��&�>iP�h(���>s� (!����>�%�$4�>aT&((%�(��?�(A�dD�h/�|5s0P�Z_L?aT?i\?ld?nl?upZ�T_�a$b�@eOx?l�eYTl_�?g�?k�?n�?stn
$o�q�wLPu-�?l�?t�?v�wI�?a�w$��Tx�?ep� �-�?sH����?a0@u$@Ì�DԂ��@�����4���8@eX@rl@u�@v�����4x@n��`@n� ��H��h����@����@�����@i�@u�@��������@n�@s���l�m�@t�DD����4�@rآs,�Gܺ_Am ���Au �\�sh.x���8At����@A��A��A�;��A��A������Ae�Ai`-uLA�,����)$oY�����Ak4���Dh���Aø?W�?_�As����Ai����A�4�4�Al����AfD���BadBe�Bi�Bk�Bl�Bo�Bs�Bt(Cu8CvLBì������DB�DC�F�<��\Bg�����mpBn0�_xBn\�m\
r�Bv�Bì�.����B��B���4����������BuL�4�Brp�|��Gh�:C���/h���C� C����������G0Ci0�H,��_pCg�Cl�Cn�Cr�1�<��?�4xCl�Cs�?�d���Cn�Cu��L�Cf�Cg�Cs�C��9����C�l��R������Ct,_(Dadp��D�zDDnPDrD����)<Dn���n�_��$�0
��lDt��tDa�Db�DdtEfFg`Fi�GkHl�Im$Jn�Jp�Jr�Ls|Mt(NxTNy@E�,�L\3D�De�/���Dr�6|5���DeEi$o)t7_EkEt�xx=&LEl�=��$Ea\Ei�(��0E� >?�o�?_TEllEs�?i0P-�Ea�Ei�Ej�En�Et�P?P���El�S�DU�T�EnU���Ea,V"�X���.�Es YC�]��^���Ee�Z���EgFi(Fr<FuT_�(dL<c�� Fu�eU@e�4FlTFrdf��LFsTl�Ff�Fk,GlPGmdGn�Gs�FÄ>x=_�FrPm���Fa�Fs�l���F��BI�m��Ft`Q�$n���Fs�m��Fa���$o��FaGlu����Gi�o��o��Gg�o4 GaDG�4q�o��<G�@qi<+u�rG�qm\GgxGk�Go\�Ms|�w�4ôw-�GaPu-�Gt4��Gi�Gj�GnuH���L����Gs��L�����Ga���St����G��-lHd|He�Hf�Hg�Hi�Hk�Hl,IrHIshIt|IuXHÌ-H���DHsh���LH��H�$I�ܕ����tHgh��\�-�Hn����Hi�������Hr��_���HnD~�d�x�4�He��IaIep�ԑ���Hn�Ig�G�h����W����4I���4XIk<I�؟�p�����`Ie�L��4tIsآ-�Ib�Ie�Ii�Ij�IlJp<+u8�i���Ia�Ie�1\�@�-�Is����In� �\�Ji,O���b,���Jl �-@JcHJg\JihJn|Jt,5�(�TJa��ܴ��?k,�btJt��\����h�Ji�Jl�Jr�Jt$����L���Ji���`���KaLKetKf�Kg�Ki�Kk�KlLmHLs\LuhLv,KÀ���4KlKuX�gx�����$K�DK�(L�pL�X�,�XKt|M0�����`Kr����hKa�Kr�Ku������x�R\����Kl�Kr�������-����s�Ka�Ki�K�(����h����K���iLn����Lit����4��� Ll���-4Lj|���<Lk�����TLn��-��|Lf��GD�-	�La�Lb�Lj�Lk�"oMpMs,MtXM���80h�/-�Ln�6_�La�(u��C0�\�-�Le@�����La��-���%����M�h�- MaDMì�kh���<M�0�D����PM����_dMk�Ml,���lMi�Mn�Mo�Mr�Mu�p?�4�Ms�����Ma�RH���4�Mn����Ma�Mu��L���_�MlNs�4�x��Nu��Nt��q��44Nr���<Na���HNr�/���
`Nb�Nd�Nf�Ng�Nk�Nl<Or�Os�Ot�N�|5_B-�=���Ns�(���N�tR0P���Ne�Z4��� y�_Oa,Oi4Oj,Ir�ԑ��Og Or���cOa��4����qlOrtOs�Ouh-v`O�x������XO��|�G�����|On�xD����Om����_�Og����Ou,����Ot�Ou����_�On�Or��GtD�
���O�0
���Pg�Pk�Pl�Pn�Pp�Pr`Qs�O���PaRb$RdtReSf|Sg*h�SiUj�8k|Ul@9m,Vn|Vo�Vr�Ws�XtHYu8=v(Zy�Q� �����84�Pl�Pu��|����P����Pü��4QeQg8QkDQlQ�����Q���h��������$Ql,��,Qa���X#��"�LQe�!TQtT)&((��lQf�Qn�Qr�QtRv�(��tQ�HZ�S��T�hR��V�PZ�h/��a,-�Qsx�D�,���Q��,���Q�\.h.���Qa8/��/64P5��Rn|5��Ra8RrHR�P:�=08��@R� >x=_TRl�=��\Ra�D��Rf�Ri�Rl�Rn�Rr�RytE�dG�`F�RnH��D$J-�Rd�K��Ks�Ri�J�Rl�Rt���TN�hN�P�0P��Sa,So4SrDSspS�|V4�V�Xi�W��<St|���PSaHZ�XSr�Q��dS��ZsL?aT?i�Sn$bTl���?k�Sl�SmTnHTr�Ts����o4�S.�Si�7l�Ms�Sv@pL�����@q��Sa�C�qmTeTu\s�t�t�$Ti`Tu�s��,T��s��hTgpTnxTs<T�TC�\��tD�t�Tv�Pu-�Tk�To�Tt�TvL�̑��w-�T.�Tuh��TxRtzG�Tk�Ts �a�|���TvhD �DUn0U�H����TaPUeL�d�DUup���$U��d�<Un4���Dp��XUdԑ��`Un�Ut���lUa�Ue�Ug�UoVuVv�U����Ua%.���H����Uth����U�$V�V�`������Un�����WL	���4VtL�O̡�|�� ��DVg`VkpVoTV�(�_���h���LV�\�&0�,���hVk����Vp�Vr����G�����Vf�Vk�G �����VaDWi�WupW��/��4�V.Wm(Wn4WrW�T�D`����V��Wl\������ Wd����/|����<W.TWn8���}�����\Wm�����W�dW��W�L�I4����Wf��G�W���D�����W�h���Wk�Wn\������������W��X�D���
(XeHXkTXlpXn|Xo�Xr�Bs�Xt�Xv�W�|��<�� Xl������4Xf\�-<Xa�G`Xi|�������hXaL�4�Xrp�4��Gh�-8<i�XØ��h����X���0������Ys%�,����Xa<e Ys4YuY�\"����Xk�������Y�t�i,Yl�L��@YlL���_�/.pYg�Yn�YrZs�1ô�I<�4hYl�6�P5��|YspH�Ya���YdL|�Ye�Ys�Yt�YÀC�TC_�Yl8��Yu����Y�D?G`���Zt�-\. Zu	���<Zr��D���4Zt��4�dZr\!D!��\Zl0
_�/.0[f<[g�(kh[l�[m�[n�[p�[r�\s�\t[���pZa�6bx]d�]e�^f�^g*hT_it`j�`kalbm$bn�bocp<crtds$et@eu�fv�fy]�t��
��[������[r�4$[a 4�)lL[n�I|����T[g84\[a<>i6s�[t�����4-�[l�4�[a�[i��� Wd�[g���4�[a���.���[d�>fp\k|\l�\t(\��4\.<\aD\iL\s\\u���\��Xdl�C|_T\r���,��h\l��&� ��!'�/.�\t�\�4���!���\��"��T.�\a#,#���\�(#��\�%��$4�\a�\r�%((<]fL]l�(��]��f� `��]��b�0g�|f�h/�T)4�*�*��D]eP�6�X]sP5��`]n|5��l]a�]elu�]v�68=^�>�(,ox=_�]r�=���]a�D��]i�]l�]n4^rt^t`FH:$JI^d^g^t�HJ?|JDKE,K��H^�^��JP^m`^v$^���LG��nhL-X^a�M�|M��l^r0Ps�_WT__�^s�Z���^i�^j_r _s,_u8_vH_�L`�\`-�^f�^rt`���^a��c�c���^�_�<c���^�\dD�X^td�_v@e�pCg�f-0g�]��@_�Tl_�/.t_k�_l�_m�_n�_s$o��_v�oܕm�o4�_d�_l�[t�p?��@q��_a�qd�uLPu-�_a�_p`tdwQ�$V�w���_��w-�_�~���`atz��`r0`shG����8`r��4@`a�`o �-L`f�`k�`l�`sH���\`a�`�`�������(#����`ap�G�`t���Ԃ���`�4��`a��������`lh����@��a�\a��a��a����Daedaftai�as�au�ay�`�0E�@E��0a����Tag8a��DhN�&�?����latD~�����an��D����at؟I��4�ak�^.�4�ag��4�ag�as����^�I����arآ���_bpHbr ���bapbg�bs�bu\bô G���@btP��h���Tb�xb��b�(��X�D0������be�ܺ_�bg,���.H�D�����bl�br����.����b� ����brcu�bÈ�bL���_cr\�_�]��4 ckXcn`cs����(ca�ce�ci(du�cü��0�ztcs�"b�"�lcvx�H�����c�\d�;�d���f�cs,����ci�cy�G�N��N���c������c��-<W.�c���G����c�4�Ipl��dd4dn����dn�s�?���<di���Dd�h���ldnPdà�D���de�dk�dl�dmeoes�Xv�dì�v�����d�`F�<���di\�I����Ue�du�d����|��\����d��&L���Xr��ei0��,���\.a<e8est�"���ef�elHfmTfndfr=s�eÀ?|���dei�el�es�euh���le�P����z�eÜ�������e����0�����eu�| ��ei(���eg�4�eafixCl fsP�\��fn�?�a����(f�(�0f���<filGL��\fftfg���	�fs���h�-�fa
H�ft,_�fa��fl�?�Na�N���f�����f� gl(grg�x=_�0s����fa���g��I�4W0
I�/.�gf�gn�gt�g���8ga�he�ig�ii jj(jlLjn�jo�jrksku�kv yh�tW�
���g����4p�4�gs�R����gr���gd�%��$4�gr�)�((���gg<hlDhrdhs�ht�hv�(���g��k�j��j�<l�hk��*G�,��del���-mPhi�-��Xhk�ht�hu����-zxha@.�h.G�hi�hr����.G8/_�DG�hiilin(ir�iy`FD���d����hs�H��hiH-�hl��-$J-ir�KI�J ik<ilLis�K<LIHL��Dikdil0��<���\iaH� -pitTN��xis�Z�Tl�ig�im�in�ittn�`q�@q��ib�q�����x���is�x4�ia�inhytzijajbXzM,{LH�G�z�/.<jsDju������ �D`jj �IP���Xja�j�0�8���pjlD���xj��+���4�juH����jl�����jl�jrt�b�����jm ��jm�jt�����e����GD���G$kgDkl\kn_<�4kr4ku@e�\L�4<kipH�r��Pkd�	|km�kr�ks���\�L
H�kk,G�ka�ki�vz�kr$o�t-�kkL��ka����kflt���_�ks���lu���lth��"(lt4��0ls0
4�ls��Hla�lb@c$md�me�mftng�nh�nioj$ok�ol@qm�qn�opsp�srPus�xt�yuzv4zx�lÔ!S((5�(���l�Hz�Pm�hs�Pz��y�@z��/��lbml(1�3 3��m��2Dm�|5�8ma@mexrP5�6����=��Hm.tmf|mg�mj�mr�ms�mu(?g0?��d����m����m�?-�mr@���malA�B��maB���;lTC5�D��Rf0P�$na<neHngXnidntlny��P4nnP��nrlDt�RbtR�4nn|SD�T��S��Pns�X(Z��Zi�na�ne�nr�nÜ[�pZ_�nmTCL�]���nu]���n��]��]��nn;�c���n�<c���n�Tg�Tl4o�Pm�l��o�H��4��Poidot�ovDo�d�t���<o�L��Ds�R����\oop��<���po� ���xo��4
�oa�oepfpg@pi�pk�pl�po�ps�o���ԑ���og Orh����@� q�lp�4q���C�����oapi\�m����m�Pm�� pu�l��(p�����Tpn\pp4p���psh���dps�pu������|pm�@�x�4�pvԑ��9g�Hn�4�pa�p�H�D�����p���v����4�paqkqnqt؟m�H�I4�G���qkt a|���,qnآ�`qbtqi�ql�qm�qs�q��"lqud�����q��������q�t��\�_�qa��"|������q�T�����qv �mra�rd�rgsnso4ss\su|r�خ��_rf(rn0rr@rtLruTrvt�����%LlD�8rr4���'�hr��H��`r�P��h���tr�$s�������.(���,a�rj�rr�rvD��P����ri���rn\`-�rrt`���raT���f��,�-�rssu�5,�_X��x�)���,sk�%pHst�-$)a$��ܺ_Tsl ��\�4�siP�?$�_|sl����
<.�sa te8tfXtiptk|\l|tm�tn�tsut<uu�sô��x�/�����s�(t��t��t��!�Hu���'X��titnA],����p�-����0tiHtsDS�������Ptghtt����Hu����d����td�tiܴ�.�tn�ts�q�x��4���������t�|����tt�t�h������<%l�����taui ul0uô�LT��������(u�����pCg���D�-�u.�ua�ud�ue�uhvi4vjTvkwl,wmLwndwp|ws�wtTxv$v����H�ug�um�us����uaT�����>4��<�c�k�_�uv����m�us0�_vn4�����v��$���CHv�8�I���@v�\�bxva�ve�vi�vo�vu�vv�vØ��H�0����vl��
l�i�vm�vn�as-�v.��m�vn������W�������v�T�\���w��v����w��D ��s$wa<we\�5D�W����Dwi@�_�O��Xwatwut�������i�wÀ�T�����w�������wlh�-�wa�we�wi�#o8xy�w��/h����$�`#�$x�0x�@x���_�#gxkxs����$�*���xnl������0Lxk��a��dxi�xÔ���xn|x�|�����tx���7��������x�,�4
�xa�xeyh$yiHykhyn�yr�ys�yu�yv�����xn%���<Un��>p����xi��2ya8g��4�_l��_yl4ysh�-�m̙ �_@ya�����Tys���\ye�D����ty����|yì�h����y����y�t���yt�_�.n�yr�������ynH�_,&e���	�,� z����zsH��z�D����,zst|��4�0
_�zk�zlL,s�zt�zu��Xza,{bx{d�{e�{fP|g*h *i�|k0}l�}mD~n�,prhs�td�u�v{��8��zd�ziܕ?dD�$T&g((�(��{��{��~����/@{r�/�� {aH{eX{è0C�1�D4D1��P{� ?P5��d{g|5��l{aB��{k�=���{s�B��D���l�{n�{t$J��M�|M���{n�{t�0P-|e|i4|r@|s0ED@E���{�tR�|ØS
dWpW�� |��V(|��W��Dk4p�Z-`|ep|i�]�_?T__h|l���|n4���||a�|i�|k�|m}n}s$}u��H<���|rH���|a�|i�Q��L����|s����4iЋY�-}e<����"d���}s�HT}a�}f�}m�}sx}�(�_ԑ��L}k�*s��dH���d}rh���l}��@��R��7t����}�(�4�}�؟���4�}kآ��}a~g ~o(~t8~u8�DX����}���-~l~n�}ü����D@�Lt�L$�ܬ_0~n �-x~e�~g�~t�~uh~�P�h���`~��~���l�(����~e�L��ܺ_�~g�d�D�~� ����~pd������~u�����~����4�~k�3s�����~a$i4s�RtH�����|���,vh������@�X���<�4D���`e�j�l�-m�t�����e�i`�j����n|�Lh���e�J����r����4������l��,����a �i(�j8�r�?��4�s����bx����0�eP�������H����r���
\�b��l��mTfnĀp̀r�s�t��v����h�������X:����a�L؀a��������aL	��	xTG,���ot0
-��f��k��l܁m�n�rp�sp���� �a4�e *i�uԂ�tD|�i�
��d��~,V��4��n��S�4��l8���iЁ�d��oÄ���ȁ������4�m���aPi$�kP�nd��L,���i�����0��ܴ�8�à��D�i`!����\���!G��k��t��\"h��u�"��*�t*����s�*����a�f((����l�r�v�(������������*�a��0-.�,����n u8/(�a��z �r�D_`�nl�pt�r|�tP5���L�a$J-T�d�J��J"|M���L �����b��f��s̃u؃v��L�����r��-<���_ăg$���0/e�_�f�g@�lP�rt�s�<�� �a4�Ì\�p�_�s����,����DLL��H�e`�t`�l�iP����t�-�T.�������n4����f��kĄltI�����kԄm(��8t�܄s�
����0
����fągЅj�k��l$�mH�n��p��r�sH�u������a�6b�(d��e�f(�g*hL�i��j�k��lЋm�n��o��r��s��t�u �vt���P������n�4��a��rl�T_�&s 4��a�܅a �L��D�4�l8��_d�f�l8|�����4�b4�s��L�rf��<�a`�nx�tl���-�5.���$s�x���a���|�4��a,G����k̆lԆm�n܆r�t�v���p��-� �<!^�!�6k�s$�t�"���(#��l�"��a<�u�/�d$�4�.T&�T�p4'�8*b((��\�k��m�(��d��X��8�����������+��/$J-��dćn�D����nԇrhJ�tKI�J̇f�l4m�KY0P�a���P���s�P4�lP���l�Z��)lPmDx�l�l��4��Tl��	�ig��l��m��n��pȈr�s<�t@�ä@L�o4�_l��s0���p4��i@q��r�qm��gps��s��k��n�t��t��s����pt��tu�Pum �e(�k0�t�uCTv��w-�T.�x�H�e�x0}�tz��P�ll�s|�th�����t�u�� �-��l��rH�����a܉u�������ảf?���_ԉg����rԂ����!.����$Ql4����a<�eX�jd�l��r��sĊu�v��L������D������L������i|���W@���t�������D����������������L+e|������l����Њ�t-؊� ����i��a\�e��i��j��u@��ԑ��4�n�8�ԓ�p��,�nh�����ȋ����Ж����T�f��im�L��p�a�x�f��������n�D��m��4��k�	I��آs��iHm����܋.����������Ü��_�r ����a<�eh�i|���H��4�ydx*TxH�ix�-P�vܴ�\�s�Ih���t�������b��f̌l��n�s$�t��H��bl�-��ft��H���Čd�l�s��t����4�v@�D�s4��$������ist�"�a��� ���0�dP�m`�rt�t��|������X�a������l�t�����4��l��s������aȍi���0�������c��,�����-܍f$n��m�ԍa����f��.D��4�lT�m`�nh�px�t��y���\������d����$��0��@���@���sH�Ø���v��h�-p�e��r���l��h����������G�����f,���\.a؎e�o y��p����r�����������������a�G�r��	�f(/k|�l��m��n,�pL�rd�sP���Ddeih���D��(FG���\�r�oCd�g�4p�eG��b�����S.��a؏i�n�Cu�/�_��.Џr��ܴ��.��.���ah.���tx�t������-��0�D4$�s8����8��L��\�k@���L�4t�l|�tT5�-�T.��u��yX�	���m8x��	̐lؐn�r�t�����Đu4�����e�_�ks�
���u$JT���n,���eP�u<��8.H��h��4��p����\�nTL��/.�4I|�r!]4����k��l��nđsܕ���d� #t ����n"��H0
��̑.x�c��d̒f�g(�kH�l\�mp�n��p�rh�s�t�u�����ԑaԕbܕd��e�f��g*h��i�jx�k�l(�m��n��o\�ph�r��s��t��uL�v��yh��@�6CP5����nHO��at/��a�
��������_��l���a�r�s����Fsl*�D�].t����� 4 �e���]��.<��8�P��4��83�d�"�4T�bL5��h�a��d�[gȓkԓn�tl�üH��.��s�7б�S������s�i��a���a�_4�.x���4�l�c�aQe0�g@�i�n@bt`�yT��hG����8�s�����L��L!��!i|�i��lĔt(">��d"H��e��i|�#C#�����(#�����"���a�����Дn�%��ؔ��$4�u��&T&��m4�t�&Kx	Sh	�� �sx'(�a�)-((��@�g��m��n��r��tRv�(��H��������|��̡�����L�+H��a,-x~e�,�h.ȕuX�Q�_��m�/�|54�a�d�e4�iP�rX6P5����r�6��II�6���m,�yTNt7_�ig<:4�.sh�uP:��@�ap�ix�o���X�C;�H;�4�$;������D�
�/.Жf�g�iH�kP�l`�nl�pt�r��s��t��x��ytEbܖn�EY$b�F���n�FS�F����s�F��a`F�f4�k@�n�o��F�,�vdG^�G�H�hJd$J-X�n�J��JL�����,K�����<���L-��e|MH(N�TN����.ԗjܗmL��P���Fs0P��	�aH�dT�f\�it�j|�l��s��t0���QtQ���t�Q��$�����8R�$R��@�rS��S-h�nTm�vnU|U-�a�V��W��4p�X��@r��u4Y��On�Z�ܘa8�eL�i�)l��n��r��pZ-�r�s4 �[��s�\d�\'�t]j]����p������L��]�0�s�_�T__D�ld�sv2�_-\�i `x�Lܴ�x�s$b����i<c�|f�Tl���/.ԙk�l�nd�rl�s�t��$o�̙a�_v�o��a��i�o@p�rH�qm�dD�gP�n\�u�do�d��$�����,�Ür��8�ss��v.\sx�s	Pum��.��i(�k��r��t��Øu��$v�����v����w|�w-��iКuh��4�.(���x��ܚr�x4�a�lT�IXz_�5.tz���a<�bD�f`�k��m��p��t��u,{�{�P�aPL�|��|��X�k�8~_l�g�}�t�u�,��d����f��hțt��W�]����r��� �-ԛrH����aT�u0���d�������~<�kD�l�����sԂ��$���L@���%�_L�gd�rP�Gp�ut24�4��ade��r�@v؜y̜����P*p��s�����I����rt��������4�e4�f<�gd�i��kĝm�s�t�+u �v��è�|����t,�y��������x=�Pm��D�a�l��L�������?g��s��tX��|wGl�m|�s�O��������th������ԝ�x��(��������̝t���h�����H�-�È�4�tp�����eL��آ4X�ad�bl�et����m@�ihs��-H�r�����������v�_��r �����aD�i��o,���GԞk�n�r�s<;�L�a0���̞i@����C$��lA�������r����� ���4�gH�m��<c��-,�rt�i����@�a`�L\���T�t���t�i��4��t���D�4��j؟k�nH�tt�v���4�G�������������l�iğn\�-̟i�o,����n0�|�����i��4�s�����e��l���(�����0��h�-<�a`�i ����_X�n$����Gl�e,��\.a� i8es���������/�����4	�/.�g��k�l�m�n8�pD�r�1ô�G<�4ܠl�t4�i�����a�(�d�Cup���40�eL�,�\�a�����flt��dDp��l��d�������uH������G��f��nġr�D,�����ܡf�t���,����a��i���tcl�����g4�kh�t��L�a���(�jX�k�����-D�r�������`�if4��t�g��l��t��D"�0
-̑.�
b�
d�5f��g��k��l6m�n�rp�sX������a�b��d��e��f�g*h�i �j,�k\�l��m,�n@�o,�pL�r��st�tܬu8=vt4y���t��5.d�al�it�r|�s��u�
��8�����lAI8aL� ���n�?���������s84��a�d�f��l�p�tP�ܕ4ܣr8��?\����"<����.T�at�b|�d��k��l��nФs�t�uh��D�L_L�.�����`��ذ��������_�0s�-��a��i��sȤtܴ�(7�LP�ܤaظxD���m�a �b8�k�nL�oT�s(���?,�q���Q�h��,D�s����4 `�m\�n(!��!�6k��p��t�"��"��T.((��.ܥl�m�p��r�(�����HZ���|��ح����ĭ�h/��*�f�*�+���uܬ��,Y�/�8�aP�id�u�/�|�i0�� ���/-H�n,��80��1&\�r�s��3��Tfn�6���tP5��p�s|5��|�a�]v(#��"���a�D�	�/.7f�g�i,�l@�nX�r��s��0E@E��ئ��_�F��nF���i�G�`F�shI�@rH- �t��$J-8�dćn����JP�hp�����,K��h�� M�,M-|�a�L-��t0P����T��Q�����ԧ��!dZ����uPZ�ȧr�Zs�)lPm��i,�r�l����Tl��<�g�AkP�lt�n��r��s��üm�ntni4�e��o4H�j`�l�pH�j�rD�qml�g��j��n��sTuP�as-�5.4s�sPumȨkبl�mTv>��w��Шu,wtzH@����-�l������sԂ����H�����4�_@�a�4i�l��4P�r��s���Qg�_t�a,Oi��u���ԑ49g9uH�H��rh�����������?��4��kآ�ԩaܩb�qs��u�à����|��������ܬ�����L-�u��-�k���s ��� �e�H����8�gX�k�blh�n0��(�b@���`�g�� ���t�d��gܪn��r�s�t���B������s��������R4��<���Ȫ�$�Ъ�X��|�4�u�����a����Ip�������\���0�e�Ji �ô�4�u����@�adeh��x�����`��@�|��H�DD��ȫe�k�l �nH�sT�v������G��l�������<�4piH������ԫu\�-ܫa��j��H�\���������Ø�G�MGD(�r$��0�t����<�e��vh��������`��,���\.a��i��uȬvЬô����sh���?�_��g��n�ks��H�-����L&��_�/.�g�l�m$�nD�rd�s@e<�4�u���44�d<�gp�(�DL�p�u���-P�a���X�t�
I�
��p���	����g��n��sx��<���u4��p"
H��a�DЭmhL4��r!G0
_�/.��dخf�k0�l\�mt�n��rlDt4�u̮����aذb�d�e��f(�g*hܴiP�j\�k�lbm,�n,�o��p��r�s�tܺu�v�yh��P54HO��a���8�`��������_��rt���a�
��������l�n4G��a`UlU���n,V�<�4�a(�r��8�D�a�dL�i�d4m�4T�aL_rf��h�a6g<Un�4��k����aįf0�g@�i�m�r�u �� ԯi��p��PZ�8��ܯ�pL8�t -��n����i� ����h���&?T&,�gH�kxmt?((���g��k��m̰n�(��	P��\�������X�������,��h/��)_8*_�+	�"dL,���t<,_��s,-��a�/_���xG�4���j1�����JU�6���r|5���eP�i\�j��rбsܱup��Tx	Pu-<�vt7_D�s�8|9���at08��d�����:���kP:����e��u���T;d;�$;��������<����np;4ıe�<_pCg�l�r��L�<����g=�D�T�fh�mt�n|�r��s��t��yH��0E���Ns@E��<��tE�`�l|U��Ii��a$J:�J	��I,M-��r�L-��tT�G|M����l *MTN����iвjLD\���O��زkhN���s0Ps�r�VG�[�pZ_�mX�rd�s�Z���al�e��i�lT�r��s̴u<�È�[�P�e�\	�]4��k��r��t�Ry�G;4^t^Dt_	T__��k��l@pԳÐ_4��i(pa4p��̳��?ԑ���ga���a�ita-oÐ������s�����f�b��$�l]��0��(c�h�n<c��H�a��ux��Xc;I�c��_�p��(d����td���j4p��th�-p�edf����s@e���r�tTl� �f4�gH�lT�md�rx�s��Pm��dei�l�����VS�m��r�ntni,�r���o4@�u@q�<u��s��\�u�uDPu-p�a��k��m��sеt���Tv����D�I�v�����,wz|wL�w��w-ȵe�Ì$	�w����Xz�tz���a�s$�u\�h���kd��|pm��G����0�lԂ��8��H���D��4���ade̶i�u y����D���|��������n���H�� Wd��sPDt���H�4����L������x@n��Զn���s�i��G �s̑���-�.l�m�t �-h�ep�g��k��s�t�uX��P�!	h���P�����(�&̶\�x�i�����j��k��tx�-�LeX���-��o̷ut�ah��ķn,�����ط�������ܺ_��g�s��4���x�ô������$�r<�tt�Y�a����D�f ���L�l��t �&	��_h�n���p�i����|�t\���_��aĸi�?��4��mh�u����t�?���иkD���ظa0�eP�ix�k̹nعr�t ��h.a��G�t������\��<��\Bg@�s��/	���0�_H�s4����ml�id�l\�-l�i��j�����4	������r������Ĺ���d������a��a����(=a��L��h�-�e����0�k<%l%�,����a�.e\�gp�i��s��u̺v��ä�4�v.@�i�a�]W�]�H�n��_P�e�?��_h�l��H����|��L&�Ժ�t�i��k��m��}�;	��_��gH�����_	�f�g�k$�l,�mX�nl�r��sԻt�<�4�rt�������a<�d����(���D�s��L�glLL��d�f|�l��D�-�T.��a�����tĻv��?	�����������<	F	h	L	4̻aK	�	���m��s
L,_����'f �rD����k��@�gH�lP�sD�����زk��p�l��-h�a�I4��|�f��l��n��_dt D��/����s����bl�f�g0�kH�l�m@�n��p��r$�st�t�u��v�x0�� >�x=_�l�=���a<�rL�s`�u�(����lAG�BLB��D�lpC�TC_X�g0P-��a��f��o��rĽs�P�gP����nHYS����u|V�Vn�W����jԽu���h[�pZ_ܽl�s�Z���a�s�\Q	�\'�ttd�(Cu������s4���$�aԑ��L}k���<�at�d��g��lĝm��tؾÈ<�ܕ4l�u��i�����g�4��e��i̾s��?d�����t�i�4ľth���$I����\.���l����aآ-�Sa�m �u��V	ܬ-0~n�����,�v ���4�sT�th�u��Ô��ܺ_`�s��@�_t�p\���|�a��h��u*�l�R���������a��d�e�f,�g`�lt�m��n��s�t�u�v�y@��t7�8�_�i,�_\�4�����e��4\���$�r��I����8�������L�g��sT�ax������l�a��i��id����e�����h�m��a|�����t��G������r�u��_��g��_pCg���i������������4��D�D�k�dmMsP�tl�
\�-<�ih�-�-ih�ut�h��`�n,�"��a��r�s�u������l��m%������Z	�������o��������t�����_��n�,��0/eG0
_��f��n��r���a��b��d,�eL�f��g*h *i��k��l��m$�nd�p��r��s��t��u$�v8�y�àUlU��x�t4����a�4��l����s,0�/-��k�/����a|5��L�a�?�=����i�l�m$�v�(��������@GAD�DL�PP��4�r�Fs0P��<�ax�l�r��u��y���V4tIs|U��l�uZ5HY_��s(Z4PZ��Q������Z-�������o4�����l��r��u��ԑ��l*m�*s�����a �i�pk@�oT�sԙ�����k0�sl�i����G8�m؟s��4L�k8��X���`����-��rh��آ��	t�a��b��g�i��l��p�s��u�����4�1�����������z\�,��h�_	h�-��u�����tحH������ �P�k<���h���4�� �\�H�v��\���\�et�h*W��4�.s����|�a��d�e�f �g(�iP�j�VkX�m`�n��r��s`-u���x�G�������D��t��8�-���p=�L�������L�,����s��G\�G���4�s��i,�G����<�k|�G��Gd���T,o$�4���l�n�Gp�)|�����a�������f�(kD�����a��e�k@�lh�p|�sh�Ð��<����m��n��-�Rd���������f$�s\�-�a4�i��d	���l�i,�n�-T�a�'S��L�v�������`��$������t�e���bp%�,�����a��e��iȬv���p���7f�4�t��_��l������������,���_�m�����bD0�t,���a,j	�G0
_��g��l��m��n��r��@�a�6b��d��e�^f�g*h$�i��lbm�o,�p��r0�s`�tl�u�v��� � �84��e���)��g0�Dl�����H��Äm �a4�iT�t�uH��D��4�t�x���,�fo	���@��� H`�e4�8*H((��h�k��l��m��r��s��t�(��p����� ������*��+?�,��--h.�|5LHm�D����l��n�r\Ju	$J-��iHLI�J�s�ZsTl_P�lh�n��s��t O4�o��<�r�o4D�a�[t\�'�qm`�k�wL�w-t�iPu-|�t�x��,tz����p��t�-���a���ԑ����s�8�h�{	��h���������|�I0�������k��I ����s\�-d�a,'eH5k��l��r��sx����4P�s@�_X�k������p�������s ���_0����dk�����sa��i��j����-��s��������	����|�D4���f �m�F�	<����sL����aL�L��D���(�a�BsD�th�m�4����L��,���T���_��k$�l��r��	L����a�	DT4f�G����u0
4	�/.t�f��l��n��r0�sD�tX�u`������a,�b8�d,�e��f\�g��h��i|�j�k��l��md�n��o��p�r|�s��t��u��v4�y���0�	8��@�ht�H�s�
��T���4l�f84�d��i�l��sd�?��	L_��l����a��i��nl�À��.�-��s�	�a`�y ��T��`������4�Ô��\��h���!|�i&�	�$4<�u�&�T&P�m�(�	�(��d��((����f��k�r �sl�À(��
x��h��������X��4��������H��h/��ST)��ih��<������ ������8*����vL-G�,���s���-���a�/�a|5_L��8H08��D���=���a��i��l��m��r�s$�vx�	�>��|���>����x=_��r�?��?_��s�@sܬ�As��u@ALA�����lA�����(C;B����u�v8CtE-�/.L�sh�t�D���f|�g��i��n��u��y�W����	�ECT�k�E��\�sa�F��t�ltn�	`F��g��k�F�ܺM$J-��u�,�TN����nhN��P�	(�iP����l0�r0P��	��a\�ep�i|�j��l��rDSs��u8��d�P��Q�������0Ea@E��H��tR�P�ÈTm�S��h�sU�����G������rԂ�����$V�U�����|U����ô�W�V���g�V�u�Y��Y����dHY_��n �r�Y�Y�����Y|��d��p���,��\`-p�r4��t`��@�a�Z��P�jx�l��r��aI��y�a><c���a��e(c.�c��c����yTgLTl��	�/.�g,�l8�nx�pd�r��s��t��Hm�	Pm����.�l�����tnI �a�\�	�n_�s�o4�d�qhx~el�gTu�r�r-L�r�r��T�a�r��`�jps4sTv�	Pu-��k��l��n�Gt��uwnLwI4����_��n�x�	��l���T�����itz���.�f,�k8�lH�s��u\���{��|�	�|���s�|�� �i0}�	��	h��@�t��	{��T��� �-h�rH���p�a��ð�������lԂ������������-��a��k������s�L�-��a$��������m0u4�����a<�e��j��s��v(��d�Dt��� ��0�������L�f\�ltEm�H�	H-T�lt�s��]HI4l�st��܉_��s������u���L+e��m��pT��	h�����j	�����i �����e�s�a(�iĝmd�oԑ���n�8�ԓ*p���n���P�slatD��Pm��l��<��l�m��sl�I��G\�f����-p�k��nآ��x�ad�b��i�j�k��lD�r����!	T+�ܥ��s�����l�������8���-�l�����������P�� �,���.����$��|���,��L�G����l�_L�r ���X�aDVg��s��u�����n��X�����rh������\�C �����������m��o���L��ܺ���m(�,�����i��G �g,�n$�r� �_�s����a@�G ���4�gL�m\�s��t���������T�at��`�Rh���l����\������i��?@�_��l\�����a��r��s y��� �D������������i���0�-��k\����5��4�r�����a�Ae�Kk`-u8��x������0��@��A�X��`��h�����4�D������fD���
p�a��e�Bi�k<�l\�mh�t��u��v��À�$����G��r����������F���)l<����g��r��0�D\�-��e�i���l�i�lH�C��(�n���0�a0�H@���H���sP��h�m|�i��v��à�W���h��� C���-��np�4�����d�����v�����,g4%kYs%�,���	��a4�eL�h��r��s��t��u��v�����	������/�`��l��������p����rl%yk���_D�u�|����X�k,�Dh�I����t�����|��t��L+e<-l�G����p�u�_��rH��,&e��������f��(�uh�����������k<�lH�mD�rX�sh�t��àC]��_ �n\�44�i-������P�s��tL	4̺v�
��
��t���	����m��s|���h�W
H��t,_��a0/e��i�u ��zt��t��?g��n$D0m��gTL��4�n�DH��������,�rh����@�m���	���T�t���\�k��m��n��t|�L������a����dl��������ipY����g�����u4����f��k�l�_t����r�^0
��̑.��b��g�l�mT�nt�r��s��t��u�6v������a�6b4�d<�e��f��g�h0�i��j\�k�l�m��nL�o��p��r��sh�t��u��v��y����
�t���f�
������g O��r��t��$e�������t84��a�dܕ�	�
ܩb4�e<�mD�sL�y��
�
��t4��ch�d��`�s�|Qe0�g(,o<p�!��$.�>aT&
H�k((G��b��f�l$�r,�s�(��	���0��S�4�����������(��h/��(�T)
�*
t*���r�*���a�,��-D|5�D�	h�f|�l��m��n��r��st��x��ytE:lH.H-p�d�I
�I-��i$J-�Qs�JL�Lb(NTNc��èN��N�����0PR��l�r|UG�ZhlTg_$��dh�g���sh����Tl_��f��g��n��r��s�tp��HmIPm��T�.|�adei�l��\��x=#
�m���iXntn*
X6�P5����r|Ys�r����a�qm��d��n��ss�s-��u4s�w�Pu-��t<�v�x�x4�a\3,{�� �rtz��
(�b|�g��k��m��n��r��s��u��vp�Ð{�	�/k{��d��P|-�)l�|D�}���L\���aD~-��kDh�d����v.����eT�H���aT�u�Ì�? �-��f@r���Ԃ����8�������L����0�lP�mL�s����24�-
̑.��a0�el�i��j��n�ot�r��s��u��v��y������.��f��l��n��p��s���������������4��a��/
H�K��5
�.d��t�����������D�����`Ff����(�iP�m\�nd�y����I-H�m���TNIL�i��.��n��p��r��s���4�<@��������mTu���ȈK�^.X�����r�������������������.0��������,�n4�s<�t�����;
$�A
8��P�g��#�cG
����X������������`��4�]��.����DL�����i����r��s|��d�4��t �H���I��ԑ$�f,�mH�n�����a|�i��o��u��y\��̒N
\����S
��H4�sp��<�dhNh������T��������l������t�s����Ð{]{�����l����G��f��kԞD����4�as ���G��s4�I�����kآs �a\�eh�i�j@�à���-_���(�sT�v����0��p��x��8/L��CH�k�4��ح��4��i��r��s��y �����eD�i��o�ØF��F�����`F���|�Z
���|5.������d,�-���mh���@�����$���`
,���b���,��\��4����`�dh�np�r|5�@�D��H��ð�\X��|�i@������ �W��l��m��s�����e��s��T������a���t�-��g���\� jj4�lD�rt�u(�ô�������t��������'�sg
����<�a\���������T������dl�_h�n��4��4��nh�u������a�Ae��o�����0��p-��A�X����������D��m
�������0��D���$�e8�kd
l@�meoH�t��v���<���r4�\�~�h�m\�i0����_T�r,�-
̑.��a�e��h��i��jL�lX�o��r`�th�u��vh������.��l��p��r�����p�r
H��i��s(�C��fLr
(z
�������p��(�f8�iL�kX�ttE���
��^0�n������D�k4��P���������`�����l���������������_��e��_�.��g��stn�
w�h�-��lP|s�����g��m��u�d�]�W ��$��
�����n0�r�����@����r
!r
��8�rT����e�Wd�fl�,��|�n��r`��
l��
�-��a,�eD�y��<���4��l��n��uP5������aH�4��d��g�
P��
d���m�r
x����.������4��T��<��x���.@��H�Dt r
h���L�n�L��t�n����dd��H�2��I�����k�������l��n����
(�������
� r
t ����g�_�f �m4�n@�pT�rt�s��
X�
���a(�H��,�g4P'p�?L��L�a�K�-`�i���h�t�	G��l��n��r��t��G�����n4��a�����s�
b,G��a$�e��i��o���z|g�l�t�,�<aH����T��@�fH�i`�ph�x��x���	��kX�n��
��(N��?��..��p�i���|��t-��k��n���$o�u0tT��H���H�������
���������f�n �r�x����i,�	�Y��`�ft�j|�l��sT���]���L���l�a�x�
�p�4���g��l��m��r?��u`�h���sܕ�
���dP I!�tH�
�����0
��
��f��k��l��m�n(�r��slDt��v�������a�6b�Ndp�e\�f��g��h��i�j �kT�l��m�n�o��p�rt�s�t�uH�v���l��4��r<��4��a��e���
��-��dd���n84�/.��a\������m��T�a�t�M�� �g�P���CH�a\�i��kh�p��s����4x6��
���T�s��  T( ��p��4 ��x�Ä!T`!��v�������!�/.��ld"4��H������'����((,�k@�n�(��	������P�����,����������h/���8*��$�n�],-8�d `]]��L��F����uT�ÀD��`�g��i��k��n �p4�t@�y<F�
`F^��n��s\GRdGm��g�G��G���j��k�GIX�b�����j$J-8�d��X�h������J.�J���l�D|M��,�tTNH�NhN��H�l0P&t�iHT����r�S��h�r�Z_Tg_��eLi(i��s�hG��rTl_��a�k�l �n0�rh�s��t��Hl4��nD�
Po���s$o���iܕ��o4�d�qhpt��s��(�kH��h���s��@���wI\.x�a��iPu-T�t��v�wY���w_��lTx�
�xOtz��.�2a�l�m�n��t�8v�Ð{.{����0}L�}D~������Ԃ����H�����4�_̙a0�iL�i̒Z	ԑ��8�fl�gx�n���@�a��i��u����?�.p�4�nh����@�����-D�v��4��rآ��-�k �����sx�I\
r��G�g��n�r��@�,�I�����g�u�����D�a ��� �m`�nl�r̝tt��P�sp�S��
$�X�l��q|�a��i|�4Ku4�
(����s\���
���4��b<�lH�n�rd�u��������ax�e��i��j�uH�y������i�_��rT���a`�����xI���(����40�ü�4T�n���X'X�\�s���,���p�y������\���������h��@����i�i������sv�||�,����a�����kL�a4����f�t�I�����t����pCg0�n���(�a؏i��4���fà�Ih���T�n�������D��	(�a�Bi��j��k�s�t(Cu�y���4����������v\����_h����4����$�k0�r%�,���	�a`�et�j|�l��o��r�u�v<�Ø��4�i(�C̉f����Y�/����Jl�n���p���P�rd��LT�_���G��r\�D���������������l�_��gܠ�H��_�/.�g(/kL�lX�m��n��p��r�s�t �����deih�����Pm�l��,��\��4���4@�il�it�t(�d�n(�H��l��x�g�7�D L����e��k�s��������a�u0	2L	4@r|5�	����d �l(�n4�r<�sx�ð4f(=a��
H��k,_t�e�^r
�E��T�j��\�gT��h�g��r��a�����s�I�����kt�4����k�l�f�s���0
�T�f�zu���a��b@c�d�e�f<�g�hXiljtk�lmnopLr�sL	t�	u�	v�	yh�À((��À(��	\��Hz���d���hs��	��	����G�����l�/����t�(������/�|5�a�]exr��P5�((08������|9Dx=5<�r�=���aH�kP�l�mX�n`�r��s��u�>�Qe@@�@AKlAt�i��d�A��l�sdB�B����e�v���DCLB������C�TC_��l�D�hN�0P��d�it�j4Sr0�u���tQ�Q�����+�$RG�x?�S���t�?HY_(�l�Z4	p�a��e��i��j��l�SnrDt��h[pZ_h�l��t�\�]C�_T__��lt`Cԑ�a����a�j�u���a�b�]����P�Xc�(c4�n<c����ae$j4ìc�|��d�	�c��_�,�$e���Qa0gxTl�o�tz�H�44�4�ade�i�kv���������k��sL����nd��Ċ��s����ut���4���4
<.(a@b�oe\i�k�l�o�s�t�u�vt���ԑ�� g8r�|ԕ!���mHd����Pn0�s��h�����l����x��ԑ��4�a�n�t������M�D� ����i��4D)sL���D|��آ��<.Xa�bde(i�j8kDlTm`oppxr�s�t�u�y�à�bp�k��n�r$��?ln�mta��/�l��������h��������*&t*���rܥ�a���Ia�Ø41�����5���H��ci��Rn���4i,�_0i\�2ԩ���La@�5|�,��L�H���l�t�̑lh�-�.t�ܬ2�n$�4�Cn���t4cĭ�HZ/ح/��+�
a�_�rL,sHtPu ���
apdTe`�k�n�o�s�t�uX�lD�4�/h�������$s����HP�X���x�,�-�s�u�Ì���5�g��,��T�&ظ���n����a��ܺ5�rl�����lH�G\�4 p,�:0aDid�L<l���$�����<.�aDelf�g�i$j�k-m�n�os`ttu�v�y�ô�.�f�m�nt��;��4�n����-�ax������������8��t��!�X!�|�=��������Ü�_ rX��,a,�HTk\y�G���������da|l���])\����e`j����TWn�t��C���;k������a��D������d���������|����k$n��tDv������a4�����0���v8ô�_� llr����Pi0����_�n�s�����nX�����ix�����sp�4����kh��H(lD����a�ueT"j\"kTl\mdn�"olp�t0	u<	vD	y8��?���	����0��\��"��$��$����I��H��Q��?	��Ctf���|rh�-�aX#e�i�#o	r	u8xy����Nh������$�����0x��$� 	�(	�`����5��U����/�h��	.��\���	��_��n��M��C,�4h	a<e� i�	s�	����x	s%ð������/�l��L�������t���	t���	5,
�4D!f440
_4
l<
r���	aT
b�
e�
f<gli|k�lm4ntp�r
s�
t�
u�
v�
�8����*f�/-d1n�/��H
al
l���2Dd
ix=_�r�=��x
a�
g�(���
���0?L�D��PP���
r0P���
a�
ls|U�
i�
y������W�W���
��W���\[ah[4apZ_$l�Z��0aT��b]��d�L��f�Tl2��s4���tl�r}s����`-ut�c����r����e�fo�����a�P����l��ph�������h��آ� r(sL����ei �Pdhgܱ_X�g���Du<���@_�(���\�\����Jl��,����f�����e�s�t�u
v���G�����t����
���<�|����l��_����Tfn��Lh�DD�H�pa4
eh
s�XvH
�`FU<��,
i4�����@
�\�2H�mT
i����\
t,vp��,���x
e�
h�
i�
j�
l�
r�����_�
e�m���_�
f��s�GT�s�_�2,_�
aI0
z	�f�h�l�n�r s,t4xp���
aTelhtiDnToxrks�uH�lAbt�\r�
��d�`�4|o���nԑ���g�4�a84�l�)x�ip���a���mp�v�����H�s(!9�����!��t�$D0E��x�l@E��<��D��xf�g�i�l�p�rH�tE-�NeF�`F�n�s�ØF.�F����r_�0sdGm�a�G��HH-�g�JH�J"g(k0s�DKv,K����K��`j�KHL-hN\sdt�(����8����O�O}Tg�Tl-	̑.�l0n<pLrxs�t�v��Pm��a�l�l�����>�x=_�r�@�@pÐo4�ino(pI4p������I�p�r�r��j�qm$gps�pt��s��Dk���v�XnTvb`uPu-lk�s�v|wITx^�xa�x4�a�k�nHy8hy-z�,{�tz���bks<v�Ð{{�����|a0��h��i0l��&�(a�_ �40������Lklhr�����VklOr���4TLn����h�����la����g0
G���a�i4s^�qm�sTl���n�0
���rL,s���a�
e,f4ixt�uX�0P�TlL@sPuItzdk�~u�(��H��|G������,���la��/��/���a���b�f *iLjpk�l�m,n�p�r s\t�v�N�0P��P�a�i j@t�S_$U0U����T�U��ah�i��_,s�X��4iH���p�adÌ�Ԃ��\�4���<�e̶i�su�v���	����u\�L����i����f�g�l�v��G����nԑL�4�aL�آ���Sa p�� ����,���� �Pjtk|Jt|u �-�^rP���DahÌ�D���`�\�DܺL�4l�_�s\����u�����De�i�jkls`-u��_�l�r,�d��|�&�����a����v��z|�-D�-4jMsHt��;h����gh�-<u��,���Thpi|k��_��l �,�0
L�f��
�a�fg *i\jhm�p�r�s4u�v<y�À��(��{��N���|UGu0P���lV?<c�Z��r<ule��e��(�@e�0Ì�� �-HfH���Paآ���_axk,�����T;u\����r�s0�-�����f�i|\l@q
�����m�px��0���gD����ilm(t�����D���h����h�-��L�L �G�(��D���L�0
-�	m�r��	da�b�ij o�ruLvlÄ��8f�mp�/�Tl-�s�w��w-�iPu-�t��GԂ����H�����<�LH���a����l8r�����0fLgTm,��t�G ��|l�r�(��\�@������.�i(����z��a�iT;u�� �Gx����s����������I�����s8�������lt����pa(b���m0r��L
��	��8s,-xe<L�0-Xk�"`sT��lr���6bflg *ixj�k�lmln�rps�t4u�v��Ab�i�=���n�Ns`�u�(����x�Wܴ��s0P�a$i@uP_�S�Z�Z�,tHY_4s`d�_-LtT__Ts�Z��`iH��a��� �-�l�r$�G���k��_4����a�lu������i�-�a��e�up�ԑ���n���$lآ��aLu���ܱ�4��,u$�44dܬ_@n���_Xk ���`atk�������	|a�e�g�i|\l(ns8uP�,��� bt ���g0gW�n]����\������m�����fs�G���-t vTx�d�����0nHsX������h����i0�_\sD���dih�- �e��_|s,����i�l��LT����iz�,g�l�r,���a0/ei�?��a��0�t-�n�=hlpm�u�v�(������fgtk�lP mt n!r"sD"t4u(ä@A8�CTC_xnDD;(Dz�n�rD_�aPDP�0P���a�l�nru�U��$I�|U����,V��W�V�uHY��Z4gTrhu�^�^��,jDv8_��c�<c��Le=4@e�`s4���<�e�r�s�u�v�����^�������x������t|����l ����a��f�k u0 v�x��(5�� i��4 m( n�L��D a�;\��< nآ��T�b��u�8D���` l ���h d� g� k� n� u�X����� v(���� s� u� v=�̴�� s�f� a�f�\��D,�-� lܺ_��g!nX������e<!g\!l|!m�!u�!v"y�!�\����Ee�C���H!n��sP!a$�ܬ_h!n����p!u����"��������pCg�!n�!s��!dpDX�G��-�!a�!i����!l< n�!r����4�_h�D�0"k<"u����l\�-$"u���,���8esX"ud"v�_��gH�-�a3142404303001025001302404403214020345200151032050424245021421325150010543404201201121042252415020240051001023504400452054000010002044023105220424300302504022033050400450232344104142102210323000500040010002000003420000400222000103000002001054032003000300001200500434004450000400344001003000400230032425204200400040045020012425120120502410504023040200050000000500000102004402023405010030101243205002050000030430020304034234000402002020240242210004040004030004100200050102304404104000005020200252301440012400104040410005400302040041014300520001020200044140000503000303000301000301240504400010300000030120503420500120414003010011040440500040400432045240404404010500100052005010002010023004201050001021004120302002112410041444002000400242410000410420404120400004004110004030210000400000054000043000004002002410430400410200430004140002110210441402340024252004044025021125020600450012222025242502243045434040504001452241031005005120400544014002020232300040002030043004004400100005000001240214304000412442101020051023220500101002050003440005500012224200430025000440004050030303021400400022500205002012042542210002502100410100202054214500504300312240002543040025241030312052103200010300010340203200032410500404430045204054230000540004420500002001200020400340000030000103400010340004050000300400245020003000221203004054040020404102510000320454410040302000300020520000300104120050120222400441254000002403243200305004005200104020440100232422022060410404222124040012032203034400043250004322340005402040300204204502021400050512050445100400401004004410500122204450003000020404144043050030304402502450241230040523242512022504020050400020033202022520244302052020112030204405303015410
50000000002011005000023002001400050020050002014222002123400002030400500020040500000214404012042020100240000104230101000201000415000324010401000102404434000230023002300410430102402220004343505030300400210500300512423034010003204040144440420244504500001100500041202520300004512502000440434023102444342043410404025240415001041020425402224052000103202023101400004040450001243420120243000040522320124052342000224400500402201125404100000432001220401102242500221055003454001014204020210240430401444010200042303002055444043402344200340450000004023003001201042040000404000403000015002000500404030000024100404345203010000003000323020050210005255021054202014500012	5000000003000401040054403210240030121242101021454044124023424004042010002102002540440045000200140400300550001004410430314243040460042004314514400022PK
!<j��7xxhyphenation/hyph_it.hyfHyf0tP�����'-4�������$�'–’-111001��������d'�.�a8bc<de�f�g8	h�	j�	k�	l�
mn,o�p0qHr�sHt�vwHx�y�z���tn����n|p���a(b4c�d@e|f�l�noxpr@s�t4w�zm�	�i�t�
�
n,��o8��i\a�i�o�Hs���Pp�
��hm�	puH|c���r�o�	�r�t,���n<i �e���n�i�'�c��s*�� uH(e��4xX/Lk���Tn�`a�e���lr3���e�5:�a��s���p�	���i�	oH�c����r���a�	o�t,��pLr�>,��,p�	4oH@t�HXa���`r���la�o�r�s��,p�	�i,���l��eA�D�r�'�c���s���iH���eJHe���r�(a�4huNd:Pc�d�l�n�p�r�tXs���|n��aH���r<N�	NRP
NHW\
N^��u8�lr���b�N(	N�n4��g��(aXe�c�	Dt��LlAg��l���t��l���a�e�o�u���i�t�u�yl,ll���o����a�pH�.���u��i$w���i�'�.|b�c�d�f�l�m�n�p�r�s�t�v�8		<	�	�
		�	�:H	�	�	H�udyl}t����������'�.|b�c�d�fphXk�l�m�n`q�r�s�thz�		0	�	8	�'�.$b�h4n,r��p	����y�'�.���}�.������������dA�.��lg�.t�������|��	����'�.|b�d�g�l�m�n�p�r�s�t�v�w��	�			�'�.|b�f�g�l�n�r�s�t��'�.|b�d�f�g	h�l�m(	n�p�r�s�t�v�whz�8	0	tH��'�.|b�dp	hx	i�l�m�n�	r�v�8		��|pH	�'�.��'�.�f�g�	hXk�l�m�r�s�t�8	X
'�.|b�c�d@
f�g�	hH
jXk�l�m�n�p`q�	r�s�t�v�whz�
���	��dA�
'P
.lg�.�
�t��h
����x
�d�l�t���
�����
��'�.|b�c�f�l�m�n�p`q�	r�s�t�v�w��'�.|b�c�d�fXg�hXk�l�m�n�p`q�	rds�t�vhz��	�n�:�fH���pr�xe(	RH����t���i8	�e�'�.�d�	h�l�n�p�rs�thz�	e5�:$i���c�'�.`q��'�.|b�c�d�f�g�	hXk�l�m�n�p`q�	r�s�t�v�w�xhz�H	�s�/H	'�
'�
.�hP
p4
s\
thz��8	
'�.�m(
��������
���
���@
m�
����H
nH$b�
c�
d�
f�
g�m4n�
p�
s�
t�
v��������������������dA '�
.��lg�
.<�t���
�����d�lt��(����0�X
'�.|b�c�d�f�g�	h�l�m�n�p�r�s�t�v�w�z�
��A�cp	�hH	�s����k�s�	�yX
'�.�c�l�r�v�
��'�.4a�	h@y����r�	�'�.|b�c�fp	h�m�p�t�w�,���u���i|oX
'�.|b�d�l�n�p�s�t�vhz�
�32010121010032024322120230000122100223332002123222120202032301230100230230022120021002200200222220022210200002000000200000
200000000022020202322000222222002002230102000103201022200243023204004000400000422242002002320202010PK
!<:����hyphenation/hyph_kmr.hyfHyf0tP�����'-4�������$�'–’-111001h
��������.�a�b�cde0f�g�hi(jtk�l�mDn�o	p 	q0	r�	s0t�u\v�w�x�y
zè
�HT|a��rt���a���k�lm nTr`v�yx	��a�
�.$�t0������D����(� 0Ô		<n0	Ha\ta�#�ly�)�a�b�l�r�s�x�-�)�)0	)�	)�)�b�k�lt04k��������t8xa�b�d�g�r�t�y=(@i�Hj���T�``ôlv�)�èD�a���n����)�axD�)0)�F��o�J����	8aHdTexf�l�r�t�zdôP�@yR�P��\�t0prx-��a�	U�tX�s)�e0	�dH
=0
)�a�0�r
)a$l�]``xv�aPltr�s�t�x���ddÌg���\���0	�ol
l�
�����b�h�k�l�n�r�s�t�)t)�qD)�bd�km�n�r�s�t)�)0i�lXnxr�sR�t�8r���@�DL��zdh0	le�	qa�d�e�j�l�ps8�z�t(q�}�	�l���e�y0
)i�	tha�h�km�n�t��tH.`Pa�\v
�b�k�lm�n�r�s�t�v�w�x�y���l����\)�)xa�b�cd�e�f�g�h�k�lm�n�p�q�s�t�v�w�x�y�z�����4��<�Ht��T�`ôlm�)-0)	) 	)�a�bd�fm<p�r�s�w�y�z0Ì������������0Ìt��$�	��a�b�cd�f�g�h�k�n�p�s�t�v�x�y�z�È��������0	�e��rt)�iD
0q�f	h�s	w0R�q�q�s�t�p���	a�b�cd�	f�g�h�	j�k�lm�n�p�q�r�s�t�v�w�x�y�z�ô-�	ÌD���	�0)�	i�()
a�b
e�g�k�p�r�s0
t\
u�y 
ô������
�d
�0)H
aT
rh�p@
v�z���4
�
a�
b�d�
g�
hkmnpt v(x�
���l
�`���
v�8$z��
l���
��8�8�8D8	80�\8�8pa|g�k�lm�n�p�r�s�t�x�y`��hv�)�eDD)����	��	�0	�a���������	��eik<rP�P]tq�)i d�	-$n0	0al
��
��H��bd�g�h�k�n�r�s�y��
�cd�h�k�l�n�r�s�t���a�l�n�t	w��0)�r	�b�d�l�n�r�s�t�v�w	�bdL
e�km�r�t�y�z��D
z6411011416000000410560013104021021041220100201060000144204000021216100121340144001210020400104013601606000	60400000140140000012124043013001434030004034020124310441400023262001340PK
!<6�u�,,hyphenation/hyph_kn.hyfHyf0tP�����'-4�������$�'–’-111001��������$�(���4�����
��6����<�<�<�<�<�<�<�<�<�<�<�<�<�<�������������������������������������������������������������������������<�<���D�4���<�<�<�<�<�<�<�<�<�<�<��������<�<�20021001110002001PK
!<lMkE�
�
hyphenation/hyph_la.hyfHyf0tP�����'-4�������$�'–’-111001�	��������.�a�bclde�f�gh0ij$k<l�m�nPo�plqxrts�t�u�v�x�z`�l���tl���|b�n���a,c�d�e�op`s0�m	�i�	�t�
|	�n|	����mL	�u�	c0��r�� iho�		��<n0��Du�	PiP��\n�	te0��|n�it$�c0���sl���i����x�'P���b�����i(u�	�a���r���a	�*�/0u�8llr���Dbt2Tu�$.�d�p�q�s�t����.d4�el���u8|	�.���ml�e	.Dh�l8np�rstdzt?,iC0��$c�	Le	I��Du��X���X��O0��xa�e�o�u���e�iP	l|	m�	n�	r	uO$O	O����a�o(uP0���u���iP	l|	m�	n�	r	uP���i��P	l|	m�	n$o�	r	u���iP	l|	m�	n�	r	u	SP��pu��P	l|	m�	nxo�	r	u	.�b�d�l�m�n�rst�	l	</�	�	x/�	t?�V.LcTh�l�m�n\q�rstdz	ZlV�	.�d�g�m�rs�v�	�	.�f�l�n�rt�	
.�d�f�g�l�m�n�rs�v�/.pt�	Th4k$	.�bLc�d�f�g4k�l�m�np\q�rst�v�	�t]<	x		.�b�l�m�np\q�r�v.�bLc�d�f�g�l�m�np\q�rst�v<xt?4f,m���	/Tp\taa�4��du.�bLc�d�f�g�h�l�m�np\q�rst�vdz/Df��htk�a��.�b0c8d@fHgPlXm`nTphqps\txvLa�a�a�a�o�a�a\tty�at.�bLc�d�f�g�h�l�m�np\q�rt�v�l�r�v.t<x.���xa�e	i$	o�u0OxO�~���,	a\	ed	il	ot	u<4	u~0~x~	~�4	u�4	ux4	u23010121010032021210102300001223230020210223020021232010320001003020022320221023202322030220022122100PK
!<��?��H�Hhyphenation/hyph_lt.hyfHyf0tP�����'-4�������$�'–’-111001�D��������.�a�	btc,d\e�f@g�hti�j�k(l� m$"n%o�&p�*r�/s�4t8u�:v<<w�<y<<z�=�DA�$�i�r���tp�r(s4th����a�d�e�iknhp�stu`Ę�t�t
,�i��bt�.�.i�2� ptDaLr�H		 Tv	��\�"
�ta���|n,���i�r\���k$�i0����t���ŠA(Pt�*�r����i�$
0s$"��u1
�$-(i�0���<�l(4D�L(4Pe�&��\iD0�/��tm�/��|a�i�k�t�u 
�,7�a1���dl1:3=@84�r�3
�k\D�5���s�4���e�8G8��k4�X:$l:��,�D��:J�@=�?��Ls�=��T�<NX;4ln�Bte�@���vDA�����R���ab$c8dXelftg�h�ijkhl�m�no$p�r�sttXu�v�w�y�z��	ň	t0h�0,Lr�*UX
Da\dl4�@�rZ�a�i�o(^`bT^�oph�p
m��ft���n�st�rt�k�6�v�r��,l<v\��z�;0�4i�?~@?��Ht���P�(�i�o�y�ĸ�0��� �T � �����B�� ����� $"�k�st�#^�#�l$$�$�kx$D$r%4�&La`eti�l�r�s��&�Dkx��'4Xi�^L(4lm�()^�)��*�e�g�ix,�h,���t@,���ix
D-��,4�s�/ik0l@mXnht1
s�.�l1�(l�1�42^�!0|28iPu"0�2� 7�3`u�4�a�e�i�o�pr �LŤ8�l5*�g�44�u����tx���s�5���i�54lm6h6��.U�6u�7��7����0P=��,��B��4��7��@�8*tk�s�t9�8lll1��9�k�l�9Z,:�:D�r�:<<�<h= �=����������	�>�@?��? �@ 4	n<	tH	vDA��	�P	���X	��A�hB��r�B��B��C d	lTD�$7���l	c
l
s��t	a 
b(
c0
d@
ep
fx
g�
hj�
k�
l�
m�
n�
p�
rstu$v,w,z4�HŰ4���	ah�	t0Z�
l�	t,���\��8
pT
sh
t\D`
ip
�D�@��(�
a�
i�
o�
u0��0���
z���0� $"�&�*z�
i�,^�/�4�8*8��k�:<<�=�����@�DA��@�X��C��=���`r��ha 
b(
c�dp
fx
g�h�
k�l�
m�
n�
p�rs�tu$v,w,z4�H�,,�����i(�*�48�k�8�����b�� a 
b(
c�d�ep
fx
g�
h�i 
j(
k0
l�
m�
nL
o�
pX
rs�t�
u�
v,wz4��*\���k�m�p�s0�a���7�r�2Z\�p4*t���p
st�
k����(�$&^&8
i%��@
r�*l
o�
Ĝ
��-x
b4@?P/���
��x/���
��78���
a�
k�
s�8*�
a`�9�l�:�
ai�:�T7�;���
a<<*DA��@�(��C<iDD4D��4o�R�l�p�t��Ha�b$cdPelfXg�hxijklm<n�o�pr\s�t�u�v�w�y�z��d�h$�it�	R�elr@

�
��
,D rX
�
i4oD�l
�
��
��<�\@dr^�a�t��pe�k�n�s����x�l�"t�k�l�t�Z�'�r�3z�3�a-0�����r�^(� 0a4!1� 4(s$"dkxo�s�t�u�#^pa�#Xl�#6�#�$;P?D$�a�$
%4�r��&��&��&�����&7�l�r�(�(^�i)EaiD)�)I�*4e�i@oP�H�@,��,a�-@?(P/��H��/pi|k�v1��sl1M�a�o�1
�Qd4^�48=�Ō:Vl:�����:�i�;��=����������> @? �?Z p0sDt�@^l1a�@(k�6�@<r�@ 4	nH	v��DA��P��������B��B��|��B �C �� 
b(
c�dp
fx
g�
hi�
k�l�
m�
n�
prs�t$v,w,z4�H�tet���s�*i�,	V	�� ����(���4a 
b(
c�d�ep
fx
g�
h�
k�l�
m�n�prs�t�u$v,w�y,z4�HŤi\���o(�e�o�0�0$"��h=lH�����&r�*^(a@eTipo�Ĝ
�p+(�*4 i8n��@,QLih,��,^`o,-v���-4h.D/�P/��|����?|�48��:
��a�<���v�� 
b(
c�dp
fx
g�
h,i�
k8l@m�
n�
p�rs�t$v,w,z4�H�\�t��$b(z� LeT!0�4�gik8nLpTr��Ta\bdcxd�elfDg�
hpijxk�l�m�n�o4pPrts�t�u�v�w�y�z��0�t��0���������7 ��7��D����,t�7�$=���	Dtpe\
,�r��X
zDĸ?�@?���m4����\4�d�gXi�kn$p8s �D�rX7���l��<t���7r|r\0k@Tldr�^�.^^\utR��n�r�
^�(�o�
� �d��$"�k�v�#�#�l$m%4gk(p�r��%rR�(Z& l�& 
jDr)z\u�*ha�
i�+D�*4`s�/�a�i�k�lXn�t�/1l1M42^�i�X3�3�e�ox3�4v84h=0�=����������	��@ 4	nHtPv`�t�DA���������hB��B��B(�B��X� ?�(?��l��B0�C����h.u���a 
b(
c�dp
fx
g�
h 
j�
k�l�
m�
n(o�
p�rs�t<u$v,w,z4�D�49taX*n�&�4v%��t878 DA��@�X�X��B����	h.�dkp(r0s@tLvlz��`a 
b(
c�dtep
fx
g�
h�i�
k�l�
m�
n�o�
p�r sDtXu�v,w�y,z����8�Gl,Z�Z$r������t8i��`a��:��Xr�D\��b�r�^4��ln�et*�.�a�b�l�t��Th\�����(a(e@oLuXyh�t��n��h���� .8l���� .��� . 0 � ��`�� ��`�%��*z�a�i�o�uy�+��*4�uD-��t�,4�sT-��-^�v���.��n/�<D/�P/����/�k4l<p42�2��4Pa�4^8�lbk|p�8��9Z`97tr�:�a�o���:0(<�<�������<���th=��=�������@�l��DA����X��A�A�y���h.�p��a�b(
c�d�ep
fg�
h0i 
jlk�l�
m�
n�o�p�r�s�t�u�v,w y,z Ĕ �x3�*�o���t$�s�	r\��h.l	c�g�dZX�r�
�����@$s3^tt��h.HaPo`�T*��0��X��xo�%��h.�g�p�%=&��&�n�(ta�/�k<p�4�8��h.�p`9�:���&�����<����h=��=�� ���T �@ �� �>!h=��8 s�?!@?��L mh sx ��?'�-�&��p ��?4�@9DA��� � �X��4(!i4!s��� a�b(
c�dT!ep
fx
g�
h�!i�
k�l�
m�
n�!o�!p�r�!s�!t"u$v,w,z4�H�x>��� !d��H!k�1D�@!i\��l	cl!i�Ix��d!sTZDx!l�!rt���!g�!ntdZ$���!s%4�!d�%��& l�r�/�4Pa<9�8���!o�D���"s�"t��"a 
b�"c�"d�"ep
f�#g�
h�#i 
j�#k�l�
m�
n�#o�
p�r$sD$t�$u$v,w,z�$�H�tDt�"e\��ln,�"r�
l�
���"�X
�"�\
#g#i#oD#rP#sh
t�#�X=�4x��#m�^(#r�MH�P��0#�8#�\`
i0lt�@v0d#tZl#s���x#�@�#l�#r�Qt�t���#s��#l�#rU��#a��#y6%�/$$k4l<p8$t�1l1Z$u�330$r�4Pa`$px$r�6Zh6X$l�.��$o�6l$u/��7=8���$a�$b�$g�$i�$k�$o�$s�8D�87�8�8R<9^�$sT9R�9->9�=���$����%ab|%c�%d�%elf�%g�h�%ijk�%l�%m�%n�%o&p&r\&s�&t�v�w�y�z�&�&�t,�%rX
�\�%t�@t(�%e���ln� �%p�!r$"xo%R�&�*$&i<&tP&Ĵ��,4&e�s�.Z�.4&rh=P/��H&��/l&l�v42^|&e�&oP2UX2�4D�o�=���������	��@ H	vDA���&���������h'dx'i8nLp�'r�'s�'t���&a 
b�'c�'d�'ep
fx
g�
hL(ix(k�(l�
m�(n�(o�
p)r�)s *t(*u$v,w�*y,z�*Ĩ*�8R�4���p'l#m�'rP4��'s�'�`.(i@?^P/���'��R�'r3�trt,�\4�'r(e$(i0(s@(�40-c�4(m`.�HhP��8(�t4l(eP�d��X(��4`(Ōv(^�(e�(i�(o�(u�(y��0��(o�(uPm�U���(j�� $"%�)g)l�%*0
�%)i�*zD)a�)i�)o�)y�)��)�p+��*4<)il)u�)�10�X)i�+*`)s	�	��x)��,^�)sD-��-4�.�)m�%��<�D/�P/���)��)�@?�x/�����/(i*k*t�vl1sx7w3*y�4�8QX*st*t�0i�048*u1��@*a�9L*i�k�l,:|:Dl*r�<4�=�����*�@?0�@�DA���*��*��Cw�4
h.X+b`+cp+i�+k�+l�+m�+s�+u,v���*a,b(
c0,d@,ep
fx
g�
h�,i�-k�-l�
m�(n�-oL.p�r`.s�.t�.u�v,w,zP/�x/�7$������h+m�+t�?��7���+�v�+�rh���0Q��+l�+m@�h�`���+.t�+aX*�+k���	 ,o(,r%
�
Q,��\��8,bh,i�,l�,p�,s�t��x��`,nx,t�����\�4lt4h.�,d�,j�,k�,l-m,-o4-pD-sp-v|-Ĉ-�x7��x�,r���� 4-.�-a$-t�!���47��t<-iT-t���I�;��\-n�d-i���X�0���-�X�����-l�-rX���-y�Q(%4�-d.g�,j.k .n(.p8.rD.s�%�Z�%.r��%�&�x
�&0.g\&��&X.r)Q�/�.k�.p����p..l1Zx.o�2Q�4�.i�.r�.v�-�54�.k�.st��6^87^84/k�,l/n/o�p$/s8/t�8�49�T9!<94/s�9��k��:D0/oh=�`/s�=��D/��$�>��@9�/l�/mDA��h/���X��A��>�
h�/a����/lD0mX0nx0r���/a�0b(
c�0d�0ep
fx
g�
h1il1k42l|2m�2n�2o�2p3rs3t�3ud4v,w,z�4�H�X�H��00��80���P0t`
�-d0a�l0k�	,\4�0k�0nZ�0r�"�"���0t<�0at�X*�0kT4�0u 1v01�t���0a�,d<1kD1p�.s`1Ŵ^	�	��(1�x�4�Ph L1v0��T1��Z
p..�1a�1e�1i�1l�1r�1u2v2y2�h�`���1.tw�1n<��(���1e��lX��1b����1a�i������
�,2�h=|(zP2e�iX2o`2up2������w�B�� ��h2�� �2e�2Ġ�T!���2.h=�4���2�$"��%�%�2r%���2d�&^�2e�2n3u�'^�(�(*��*�
i�4D3aX3el3ix3o�3r�3v�3��3�h��44<3l<��5��P3n�5��54d3n64�!d�3j�3v46��'p+�*4�3i�6�3a87z�7���3�`��7��7���3��2��B8
4b 4d(4g84kD4sX4ŀ8l�8D�8D9^�804l�9D(i�:Zl:��P4��:t4e|4yX;z<�h=��=���4��4��4�>w@?��4@5b�+kH5sP5tl5u�5����4a 
b(
c�d�5ep
f�5g�
h�5i�5j�5k0
l6m�
n6oh6p�6r�6s�t 7u87v,wx7y,z�7��7����t��*�9X5aX*`5r�����x5�\��8,b�5m�5oh
t��*@�t4�5g�k�5n�5pD�!r��4�����6l�� �%446jT6l`6s�
w\�,6e� � ��@6��%H6�\&D�&|6j�6l�6r��(^��)E�*�6i�6o�6Ĝ
�,-�,4�6o�6�
�-���6��-UP/���)��/(i7k��1�7il1Z7r8���$a07r�9��:H7od7�%0(<�P7j<��X7��8��<��p7d�=�����7� ��?��7u�7�@?���7m"�h=�4���7��7�@?��@DA���7��7��C �Rx8i���7a�8b|%c�8d�8elf�8g�h�8ij�8kl�m49n<9o`9p�9r�9s:t4:u�v�w�y�z<:�l:����	�8j,\R@�8rQ�8i`%T^�8o�t���8m�8n�'r���9lr 9v(9���9e(0�������$"%4�rT9sl&Z\&L9l�&7p9l�9r�(Q�)^)zx9o�*�9k�9o�9s�-��9l�-��-+`.��/�9a�9l:v�//�/���9l42^�9a|&e�d4�a:et4^�4D�o,:r�6�8�=����������	��@ �/l�:nH	vDA��X:�P	����:��A��C �:i�:l�:v�:�(D44D���:mTD��D�D�������h.LpP;t���:a 
b(
c�dX;ep
fx
g�
h�;i 
j�
k�l�
m�
n�
p�r�;s�t$v,w<y,z<�H�t7\4h;pp;s�\��0p��x;a8�;i�4�;st���;e�;s�;t��t�;k�6��v�;r�/EX4 ���;u�8�;a�<���;d�=�� ���(<�@?�4<p&��� 
b(
c�dp
fx
g�
h�
k�l�
m�
n�
p�rs�t$v,w,z4�H���%ab|%c�8dPelf=g�h�%ij =kl�m49n,=o&p4=r<=s�t4:u�v�w�y�z�&�P=�@=iTt��=a��l%�*7�/�k�@ DA��H=���������%ab|%c�8dPelf�%g�h�%ij�=kl�m49n,=o&p�=r>s�t4:u�v�w�y�z�&�P=���h=�>�@?�h=��?���*�/�	���>bp>cx>d�>e�>f�>g�>h�>i�>k�>l�>m�>n�>p�>r�>s�>t�>v?w?z?�(?�t�,�xo\���>k��@���t��\-n��(�� �$"��&��*��/��4��:�<<�>
�=��?��@
DA�� ?�8?��C
��%ab|%c�8dPelf�%g�h�%ij =k�?l�?m49n,=o&p�=r�?s�?t4:u�v�w�y�z�&�P=�(� �?eT!��/�4l�4�r��%ab|%cT@d\@elfd@g�h�%ijl@kt@l|@m49n,=o�@p�@r�@s�@t4:u�@v�w�y�z<:�P=�,R\4@*�:(4� 4�&*�*4�/(i4l�@m�@r�@t|2z3Z3r�4�:4�	����@bp>c\AdlAe�>f�Ag�>h�Ai�Ak�Al�>m�AnBo(Bp�>r0BshBt�Bu�Bv?w?z�B�(?����@��B�h=��C�,;x \��dAi|Av�4@@�Et�Att���As�Au�8�4�Ak��(��Ai�
u�Ay�0���Aj �$"�Bexo�"-&4%��Br�&@�/L<Bi1
\Bk`��HBp<1�PBa�4�X:l:��pB�8��xB��:�4i�By�;�<���Bd>Q�=���B��B�4@?���Bj��%ab|%c�8dPelf�%g�h�%ij =kl�m49n,=o&p�=rPCs�t4:u�v�w�y�z�&�P=��/dCk4llCtl1�3;Ds�tCt����Cn���CaDbp>c\Ad�"eDfDg�>h4DijLDkTDl`Dm�>nhDp�>rpDshBt�Du�Dv?w?z�D�(?��6W�	;�L@"�� Dut��(DmDDo��;(��
u� ��&�/�(i�k�DllCt42�84�Ds�9D�:��Di�o�;�=��?��D��@G�?���Ds1121330121010410230303010020324054200203024145203213101211124020420320540205020521044223222022030300501300042214034324123244045231054040003010252302341022430210040323020202043034201032004153522000450002250040023402112014102023020130543400414403202403213425300103104433324454132000212440205000042254301003030003000002001034104501210405240101201003310005030300205010500030325232430004040412510432054010302004010050403012030300103201020010030044100230105340411312340330032241022310200245040542430024104302110201241410402324523210202003012400214001021210054210540005240400004410434022104341050221003000020300443020201004344000102302230045020521211020004020322014205210203440300435PK
!<�@��hyphenation/hyph_ml.hyfHyf0tP�����'-4�������$�'–’-111001��������(���4�������6����4�4�4�4�4�4�4�4�4�4�4�4�4�4�������������������������������������������������������������������������������<�,��������������������������4�4�������������
����,������������(����#200210011100020012
20000000002000PK
!<
V�r 9 9hyphenation/hyph_mn.hyfHyf0tP�����'-4�������$�'–’-111001�7�������t.TЄќ�,�0T�����������$Р������0�<�<�<�<�|�<���������<���D���������$�,�������,����`������\��������\h�|���$�T���ӄ��1������$�,�������$Д���ӄ����$�,�����T���42�����l2���������҄���2���$������\��$�$���ӄ������$�,�������$�\���ӄ��3������$�,����������<�`�(�x��|���������\�T�������h3���x4��������ӄ��	�4����3��3���$�,�������$�8ф��<5�����$������T�������l5�����\А���ӄ���5�����$�,�������$�����ӄ���5���$�,�����T�����������ф����������H�$�0T��@�D0��0��0�`���`�����t�T���x1��1�����T��������ф������\T�����2�����ф���������@����,��8����t�l����L�\"`����|���������"T���������ф��l����������ф������T���� 6�|6��6����0����l7�(�0"T��@�h�p���HМ"�"<(H��x���������0���Ф����	��������<�l�����0�� 	� 0����,0����80����D0���H����
�0��д�T��
(�0���\���L�L���l�(�T#��$��%���8Д)�<(�0((�($����$����<�����Ѡ����$������<����1(T����<����<����x1(��� �H�P����(��1(�(l2(���X����`Єь2(���|���������������(�2(����3(�2�����2����h3(���������@	������P	���	��`	��	��	��	�x4(�4(���H	���(����x	�<5(8��p	��5(����	���x���	�$�������	� 6(���	��	����	����	�|6(D(D0(H���	��������
�0��
Р
ш�� 
��
��
���
��8�T��0
�$��X
�<��h
��t
�<���
��0($���������
�`(<����<����ш������ �H�����
�42(����X�����������������$�$������D��D�d�\����������������t�P	ф�������`	��� �T����`������L������l5(\����x����ѐ���	�����0����8��6(0��������
��
������3.D�
ф4��\��4��lЬ4��x��4�������������$�����
����H���������������@	��������
�0
��5(���
����$
������
��
��<
�
�$��P
���`
�0��l
���x
����`��2������H	�����
����
��
�(����H������0���
�Hш���
�`���������
���$���<��$Ш�(($��@��������TЄ�����<�d��������p�\����D�������	����Є����H�������
�0����������
����l�����2�����������@	����,�L���H	�\��3(x������	��	�0���p�0����8������8�������<����H���������
�0������������$���<���H�� ���,�$��8�����(�<��D��H����0��l����x��
������������T��������Ј����H���������������������L�������
�d�8����	��	���(�H�����	�x���������0��D������h�� �l�8�D��x�\�$�������Р����<���ш����P������$�����(�����H�P����������,��2��D����L���P��������T������`��xИ�L����x������p�<H������\�d�d���l�$��	����
�����`�8��D�����dф����,�$��8�D��DД0$��d�d���0T����d���аф��d����������������x1�1��������� ������$������,�D��DИ�����Є�$��T�����������������H	����(��x	����	��	�0�P�$����0��������
����L�$�����������������������4���@�H������0��X���h��
��
��x��$��������$������
�����8����(������0��	�����T����<:$���<�� �H��,��l��0��8�0ш��P������`���$��p�<���Ш��4B�4�������4���Ь4�����4������������ф�����3B�0��`�ш0��|����,Ш
�$�� ����������D�$��T�<��`�0��������x�l7($�����$�����1����H����0���д������l�������Р�$�����Р���<��(�T��4���@�(���X����`������x�����Ш
�����H���	�x�������������0������������
��d�|�l���8����Ь�$���<��$Ј��0�x����<Ј��P����XШ��P����p�$��x�������|(���
���d����$����<�����Ѵ������������	�0����д(|0L�0�� ��0��(�H��4��
�@Ј�$��P���l��1��`���$���
��1L�1�����1���Ѽ1�����1�����1����@�H��\���$�����`����1T��L�1���2�2��(�2��4�$���
�H����0��X����d���p��$�������Ј����X�����а����������T����	���������0����H������
��p�l�����Ф�$��<��1��LЄ�������d������|������������`	�d����(�����H��x�����0�������������
������@�|���0������(���4��8���L�$��L���`�H����0��xФ��������8�H�l�h����Јр2�����2���Ј0�����0����H��������$���\�8 �"����`�<��D�ќ����d��������X������,Р���x���������H	����3((�����������	��	�0�������0���������������H���h�Ѐ�$��(�<��8�T��D����P��0�������,�$��t���H���	�x�������0����D ����	�����\ �h �|�� �� ������ р2�����2������� ���� ш�� �x!����( �$�������
��<���	М����dѰ��X������x Є��������� и ����������,�� ���� ���
�d� !����H	�\��������	���0���!�8ш��d����0!���$��D!���T!�$��`!���l!�H���	���������0���!�"�����!����
�@"�l�\"�h"���!Ј"�$���!�<���!�����!�����!�#�$������������� �H����,"��������L"��2����x"��2����������������"�����0��	����"�x����������"�#�$���"�<���"����"���
���`\$�� #����#�(#�$��4#�$�<��D#�H����0��d#����p#����|#�$���#�<���#�H���#�H������0���#�$$�����#��
�8�@"�l������#�0$�T���#����$�$������d�T$�����0��	���@$���H��������0��d$�$$����x$��%���h"���$�$%�$���$�<���$�$���$����$����$�����$ш��������$����tШ
���%������H�����������
�@<%��%�H��X%��
��%��&� &�h%�L&�$���%��&����%�$���%����%�$���%�('�D���%��'�$����<��,�����,�0�����0��&�0&�\���������d&�$��<&����t&����\������Д��������&�H���	�������������0���&�4'ш���&���
�T'�l�`'�D���&�l'�$���&���'����'����'�$����������� ����D'����L"Є�����'����'�`���������	��	�0����'�xф����(���'��2���'�\(�3���'�\���'�����H����\��(�H��x�������0��(Ј�,3��4(���(��
��2`D(А(ш��x�����H����l(���
��(�)�d��(����	���0����(�������)���(�$���(�<���(���8�$���(��)�H����x�����0��()д)�T��@)���)��������P)�0*�$��l)���|)Є���)��*��,�-��-�X.�$����@����� �H�����)��)ф��@�L$���)�`*����)Р��*�<��*ф��$*�0��lЈ����<*��p*��L*�|*������
�������*�$���*�<���*Р���*�<���*�H���*����*�����*�����*Ш+�H������0��+����+��
��%���� +�+�T��8+��H+�$��T+���`+Є��l+��x+�$���+��4���+����+��,���L���t���+����	�0����+�(�H��x��������+���$��,���,��,��
�����,��,ф��<,��L,�$��X,�D��d,�T��p,����|,Иdш������ �H���,�$�� �d��,������	�$�,�(�<����8��L�(��-ш�������-����(-����4-и-�$��D-�<��T-Ј��`-��l-А5��x-��5���-А���-�L�x���-����
��f�.а/�$���-����-Р���-�46n�-�<6���-�H6��.�T6��.�d6.�p6��(.�|6��4.���@.���L.�D0xH��d.��.��.��.�0��l.��.�����.��.�/�H/�<x�0x<xx$���.�x$���.�<���.Ј���.�����.�/��x���/�����.��.��.��.�X/�`/�(/�h/��x�2x��/��.��/��/�(x|x�4x����/�����/����/��/��/�����.�<���/� 6x���/�0����/�0��6x0���.���$��0�<�� 0Р��,0�<��80�<����P0��X0�$��d0�<��p0����|0�����0��$���0�D���0�$���0�D���0�<�H���0�1��0�(1�$���0���1��(�$�� 1��T��41��<1�\��H1�D��T1�$��`1���l1��4��4���1��4���1Ѭ4���1��4���1����1�����1Ѡ���1�2�<���1������1���1�$��2�<��2�$���)�<��(2���$��@2�H2�$��T2���`2��L$��x2����2�������2���2�$���2�<���2�\���2�D���2��L,3�$���2����2���T��3�3���� 3��2����83��@3а���3�L3����X3�(4јL$��x3�(���3�\��x3�x���3�T2.`2���3��3��3�l2���3��3ј.�.$���3�.�2���3��3�44��3��2��4�D4����4�x2��.$��<4��3�$���3���T4�$��`4�D��l4�$���3��3��4��3����4��4Ѡ���4�<���4��.��T���4���4�$���4��$���4�<��5�H��5�5�$��$5���05���1��H5�$��T5���`5����1��x5�$���5����5�<�H���5��5��5�$���5�<���5�$���5��(2���5�42���5а��6�,6����6��L<���46����<6�T��H6��6��T6�$��d6���p6И$���6����6�<�$���6����6Є���6���6�$���6�<���6�47��$���6���7Р��7�<��7Ѡ��(7���$��@7�(��H7�8��T7�(��`7�100001000200201002000020304002030030400	30004000041030400	40005000030400003000400300400003010000	40000040030000030400	30001000030000000000	30400000030004000000
3000300040000300030004003000300000030300000000	30304000030400000000	302000000	3040500003040000040030400010000PK
!<�P{�T�Thyphenation/hyph_nb.hyfHyf0tP�����'-4�������$�'–’-111001���������. �a�b 4ch>d�e$f@0g�sh��i��j k�]l��mLn�eo̵p��q8�r�Bs<�t�?u@�v �w8�x@�y��z`� �4aTb�c�d8ftg�i�k�l�m@nxp�r�
sdt u�v��xa`
b�c<d�e�$f$)g�-h2ih9jH;k�Al�Jm�QnLVo\p�_r�iswt�up�vP�w|�x��y��z����t
�<ade����Dbts�

�*�lt|��n�<�i|����tO#Ț(�s|��l����e�lr$s��-Te2�.��6�ae��e.x�:�=l�APu����,fHtD�EğIW.��MXa�eС��dn�rtWP.�T0�X�Z�kn(rDspt�v��p��.|�]�a�elr�'���.�'a0�e��i.��me8�qh�-��0.����8a\e�(�T.�t�ha�e�r��T.�r�Mx�e�{$������X�a0�Z
b(f@gdk�lmLn`s�t�v�+������n����e|�mu��(� a\�T.<�]4ePiP�iP�2�.��Xa�e�oh�(��Px.̐(�����m��(����.���a�e�o�sL�T.�g|��@�i`���'�\��.��a$e8i�-�.���d�0n���Da(��غ�Xkpo����xT.�rĻxa�e�o�����T.l��<����.���eT�A@����b�em$s4t��t(�.4���aeT�T.����t��,a8�Z
xa�d�eg<jTk|l�n�s�t���pb��T.x�t�a�e�r����x.H�\�aD�������2���r�s$��<&T.�t�ae���T.(s����3��T.�30a`��.l�]Hade�4T.P����.\�pa����a�e��T.�l�:��PT.���a�e�����.|�T.����ae(i����T.�T.m p ��LX�h�T.@nHsPvDZlZ(�(h�-P��Xn\��`ax���ll�p�t��<�����H���i�����b�d	e$	g@	i|	k�	l�	m�	n
r0
td
v8�.H���e<����	o�T.0	s`�	e ���Z��8	aP	e4�d	n�i�P\	e��i�.��p	aa�����	n��	ax�T.�	a$��	e��8�2T.��	a�	e|�2x.�	sD1����.��
a
ulA�����.��$
aD
eP
i��2T.L�Xx.h�X
ax
e�
iX����.�
d�
r@X(Y(���8�Z�
kl(p4sPt�������
a�
e��"T.�
s�'؏-�
e4�2��
.��8ed�.p�Xa��;�i��<u����Dr$�?����\h�k�l�n�o�s�ty��8C�i�T.�G�e�2T.����a��:����K�nx�t�e�rx�Ol�i���]e@���dDeTgxk�l�r�s �S�������Lade���L��.D���la�P.�����eL�x��W.�����a�e�k�p�i".�\�l_��X
d$
nX����i<
lX
s�c��hБt
e��
d|����.l�0
aL
e��T. n��	�
a�eDi�ldodr�uy@�r���
y\x�
b�
l�
n(rPt�vP2�
.\���
a�
e�"T.�T.����
ek<6|]lL2�.X��e8i��T.(�.�	��De\x.�n�t(��\e�ix
��pd�e�kl(nPr�s�t�x�e�Q����p.�����dHi.P���e�"�.����a$����r���a8e�2T.�".p�Dade�i��.xs�t�(���x�t�.p������a�_�k�tt���n����e�8���.���ae�rv��.\��4��$.�M,e\x8dde�l�tl�tH]\n(T.�t���pe���|���r`�a�e@i������d���e�kt�k�I���q��fs�*��q�t$v�(��r���,gp4n j�L"La�ed!�Td�g�k�l�nrD"�.#��"]�s #��e��w��.`#�n8{�$��a�$��a�$T.�%�e$i,rx%���t�%�(&����XCnx�0C4s�S]@d�(�Ldd'�Xu�|�|��p�h�`+2�.h+]�a�r+��d�e�s�D�F��+���t��t�-�y0-���sH/.T/���e�.��d,tt0.�0�� a�0X ��8�d����1��Pad1�Xk�n<-2tn�r�1�|e�- 4�ae(o����T.|4�n�3x�e�r�s0��4���sP5t05���e�5�5��b<4t�:��s(<�h>�	�a0e�i�orsu�yü?T.�?��he�s>��tg�l�m�n�tu�@���
e���t�#�eTA�$B'T.�A���exD,�D���a��� E���s�\�KWr�K��a`o�H�� k|l�m�n�rLs|u�v�LA�L�Xd�MT.�iPM��le�sN�O1xPT.(P���i�o��6�P�e��HR;�lDQ���s�T�.t�S]�ei8oTU@hU�T.(v��E����UJ0m��O�VMDm`opt�WS X(�WXho�Yx��Z���s�YM�iD\��[���a�efnPs]�]]�l�r�t@]t��T.�]\]]�t�_WT.�_��e<g�*a4`d(a�_t0s�ahTa��Hk`t(bm�lqPihn�r�t\h��pe�m�r`m��m��ju�j���elh(l���a�e<'�.pox|o���mn���u�m�atl?xt~��aHehf�g�k�s�~�T.�~��<nXr�E��Gz��`tXHT. ��te�M~T���np�"T.4�M�e�t���Ԃ���.�����e�����r���.�����aȃ���l$rPv�������p��4a<e���X��̄���.����De�����\n�t����de<���tr�
����b�cdLf�g�i|k�l�m n�!p�!r�"s�#t�$v�$xt�<a ���b=�P����u�F�\�ǎ��d8lp�T.|�6,e����MDoht��xs��\a�e8)�0� ��rP�.`���eԖ���d�e�g,lTnxo�s����lԗ��G.P��aeD�T.l�G(T���.��� a<e��T.x�.��MHade��PT.�\�̙pt<���e�i�c.,ei�X�dg4kXl`m�nrPt���l����a�e�����n��2T.��]�a eԠ����r�{��],aLe�(�D.���0��xa�e��-H�Wp.t�.�
̟��s����a�i�o�s����C�Р"�g���D����a�o���t<��H���r��]a4e@u���,sD���i$���He`r|��8�x��]hod���pk�l�n�r�s@t��T.`�K�e�i0.2 �.�m�e����0.Ĩ���aep$t�2.@��i�G�\��0aT�����8aTe`o �T.|�
hp.4�tha�eȫ��td�g�i�k�l�s@tpv�hT.́�د]�r��������e��n8�����<��a k,t�Z�������l<�Z����.��4aPe̵�.܂8���\g��de��~����|b�f�m�n�p�����a\�T.����a�e�'-t��.�.���a4�@�{d��� a@ dx g� k� o� s(!tl!��T.�t4 aT e` r��x.t����T.� v��th a� e� r� s�&��2T.0�	@�
�4t$�]� a� e�T.����� g��t��t� a!e!o !p��T.��i,Q��O8!eD!r���.t�T!a\!e�����������d!��"�Mx!t4�&x����!a�!e�!g�!k�!l "mT"nl"t��P�����!r�����!e�����.���!a"e��T.`+.�"a0"e��2T.@"k��(|�2T.h�]H"a����.��`"a|"e��T.�j��j��"t4��"s�����"a�"k�"l,#pD#rX#s�#t|�M�"a�"ep�*�".�o/��"T.,�#a #e$���.4�/P�#.�58#a8�.��9���.��L#al#ex#o��T.�����i�����#a�#e�#i�#r�#u��2�#.�#t��=�C��i���T�I����.�G�#a$e�����#l$$n@$s�$t�.��$��$a4$e<�T.`�NT$a`$el$i�����.�#��.d%P��K�$r0�tt$e�$i�T�tdc��]�$a�$e��M�$j�$n���T.�T.0��$e8�$
,%a�%e�&i8'jd'l�'oD(r�(u�(y)��R���$%eH%g`%nLV���@%s�T.���T%ex%tl�Lp%oT
Z�	���%e�%i�%l&m\&r|&sX8;�
(�%i�
���%l���%a�%l�%t�],�d�p�%a�����&a$&e4&o@&t�b�g
�,&g0
�A��kH&o�
��P&rl&s�p,����t&l�&tDt�&e��xT|����&a�&b�&l'r'sZ�T.����&e�g��&g��&o��&eh"��'k ���$'l���,'e	;�d��D'o���L't��X'a�h�T.\��p'i��|'b�'l�'r�X���'d�'e(m$(s�j�D�'a�'iT��'r,�������'.t-�'n�-O� (s���� �(o�$�X$��0(e#X8(et(i����%T(e�%]\(rp%��h(e8,X�(e,���(g�(l�(sXH.t,X�-".�-���(e8��h.��(e�-���(s �-H/�(l /-�(e�.��)r���)�@0�
\)a@*e�*h+iX+l�+n$,ot,r-up-�X1��/��T)f�)l�)m�)n�)s*t$*v�1"T.�1���)e�)i��.\2�42���)e�2T.�2���)e�)g ���2t�)s5*t��i�5���)a6�6"�.0*e�6��*e���\9�8��8*ad*lx*n�*o�*s�<Ph<��\*e ?2�>]p*e�*i�*s�?r@O�@�hD��*atCt�*t�*v`���D���*pDE��s�LJM�*a\�;���*s�K]�*t�J��+f(+n0+sL+v�Lx�Mx<+phNa`O�lO��D+a`Q�t+o�+y�+�|�ZhT��l+i�+v U��U��U���+s(��U���+e�U��+s�U���+�e
e��+a�YJ�+l�Y���+��VM�+�p8��eP,s8Z,e�Y��,d4,s�\�����<,d��"D,n�^JP,e@]��\,v]h,a�,e�,ul_�x_���,e�^���,i$]O�\��,sLa���,n$a���,n�,sHV;ha��,s�CT.�n]�,e -v�n���,d<-lH-s\-t�DE�d(�P(-t`o��0-a@p��vT.Lp��P-u8rXG���-�h-�4��Ds��-e�r���-s�-vpsx�s�-a�.e�/i�/ox0u�0v�0y41ìt��t��-s�s��-i.k0.m`.n�.v����ti..�t��.a�ui.�u��$.ap��l.a�v�<.l�vtH.s`v��T.dT��z�xPt.k�x��|.a�.i<yt���T.����.r�y���.e�.i�.n/rX/sd/t�zA�z��.e�|��.i��5�}L}��/i/o��0/e8/i@/lH/sX��|6����4�����~��P/sp|�/et/s��/e�#��ܰ?T����/lD����/l�/n�/rX�x���.�]�/eԃd�x�/d�/e0r@0tX0vplЅ�.$0n,0t����0e��Ę���.p�]40a����.,�ML0a$D��d0i���l0d�0s�o�o���0lho��0a�����0vh��0eD����0t̉�0i���Ļ.�����0a1e��Z�0r�����.�|T���1iL1r`1v�1y����1�1�$�����D1e�������X1ap1e���.�:����|1����1����1t�����1s���.��%�1n�1r0�t�1e2n���������1l��t�1sl����1d��X\2al2b�2d�2f�2g3h3k|3l�4m5nD6ox6r�6s�8t�8v\9�T��t����d2l���Е��x2aH/i /-�2ed�*�2rl����2�,����2�8Q�.ءM�2a�2ex����2l,R�T.L}(<����2r ��3e�X03kD3lL3td3v��"<3e0�<�d�OX3e|�.ث/(�2�.�3nT���	l3a�3b�3d4e84j`4kx4l�4s�4tta3�:�3eج���3dL"La�g��3d�fm�3o��L�t4e�3r|�.T�*�4g(4s��?��(�]04aH4e@��.p�D�.��T4e<�T.ܰl4e��t����4kH�T.t��4el�t\����4e�4�����4.�H�4eȸN�4t�����4�4i$�5dи��5a@5dX5g�5k�5n6s 6t��i��t85e��.��tL5al5ex5r��T.��a�5i��0�S�5a�4�.��W�5e�5i�IT.�5k�5tX[L<J�5s#a$����5s�5t8�g��|��O�m��6s��6eL��\�],6aX6o<���46nDD(�P6m��(��d6r����l6e�6k�6r���?��.���6a�6e��x�.��t�����6a�6c7e7k87lt7m�7n�7p�7s8tt8u�8���2��tl�r���@���7a����7gH7m��8(7aP7e�`,��
.`7t��(`�T.@�Mh7e|�uġ�7.��M�7a�7e�8{��i�7.X�i8���7a�7eH����|�7a�7e8k���.��T.��i���,8eP8iX8j`8r0�/4�"$8.@8dH8m���|����
|��D��$��.����h8t8�L����8�$�������8i,�2�8n�8rT����8a�8e89r��(�������8.�2�8l9n9r�((��b{t���9.$9e�������.���,9aH9e���.��2����T9����9a:e�:i�:o�:u<;�|�T.@����9e�����9d�9e�9n�9s:vL���������9e�-�>
�9.�����9e4�T.@����9a��R\��:a4:l<:mL:rx:tH�����@�I����D:ed:n0�	��\:s\��p���p:a��l�.pl�:n���:e�:sh�2T.X�]�:e�Z����:a�:l,�t���:a;e;iX��d�W�S�X�;p���;e��]$;d���0;� �
�;a=e0=i�=jH>l�>n(?o\@rh@s�@u(Av�Ay�A�X���	�;f�;l<m <nD<p`<r�<t�<u�<vT���
.h���;a�;i�;v�.�������;l���;e�i���<e02<��<a0<e�T.��T��<<r`T.t<v��P<a|<e�<i����.<���"�<n$
T.�	���<e�<h$�ID�\�
���<k4�.�
���<a�<e"�.
�����<e=l`P8�=r���$=eT=nx=r�=s�=v�h���L=ah=ip=o�
����2T. ���=e,�W.� t�=a�=e� �.��������=e�!��=d�=k !X�=e(>�|�x>l��������>n$#]>n#��>��-x�,4>s ,<>et>i�>o�>yt��x.��`>e0.h>n,��.��>a�.���>v��
p/]�>a/���>s�
 ���>e�0�>s�0��>a?e@�����>r�0�>p�2i.�2?aX2��?dX?gl?l�?p�?r�?s�?t$@v4��3]P?r8{S@5��d?a�?e�?jd5�.��72.�7���?e@Cԝ�?d<9��?e08���?sH:T.:]�?e��.0;]�?a@e$;T.<���.�;��@ed�Dt0@sP?��8@g�>��D@nT<�P@i4B��S�|S��p@l�R��x@f�@l�@r�@tT��S���@eDU
.LU���@a�@epU�.T�.�V���@a�@e�V-�V�@.�}|W��AaHW��ArWXAaPAe������8Aa\Ae�W��@Ak@�i ��. Z��dAa�Ae�Y��pAl�AtZT.p[.L[���Aeh\��[���As����A��A���t8]���Aa�\���Ap�]�8BaCe`EiGoHu�Hy�I�Й�T.�^��BaTBe�]��(BdhBg�Bm�Bt�Bv�^�T.��S,_��`BaxBe��T.�Br���`T.�`���Ba�d�.�d���Ba�Be�Br4eT.�ea�f�\f���Ba�Be�f��.,��@l]Cr�k��Ca`Cd�Ce�Cf�Cg DiPDkpDm�Dn�Do�Ds�DtEu0EvmT.m��TCapCelmx.��n�|Cr�Ct��S(n��Cl|n���6]�Ca�n"�Ct�n���Ca�Ce�CioT.�Clԗ(�o20.H����'.�pADrhp��De8Dv���ԣ��0De�q�T.�q��DDe`Drr`t�ds��hDu0uT.�t��|De(v	y��x���Da�De�DpXy"T.�Dk�Ds�y(��pz�{��DaEt|
����Dn(}tEat}�}2T.�}M$Ea@Ee~���.PEs��S��<���XEa�Ed�Ee�Eg�EkFm(Fn<Fr`Fs�Ft�Fvl��
.P����Ee���En������Ee��Ea$����Ek�EoFs��ST�̆�H�S\���Fa�#��� Fkp�t|�]4FaLFe���.�������XFapFe�T.p�T.���|Fa�Fe�Fi؋T.��4��.�Fn����Fe�Fs(����nT�(�Fu�Fv����,L���	�Fe@GghGixGn�Go�Gr�Gs�Gt�Gv��i,G.ԟ]4GeTGi`Gr�0.@�	|�xԡt�]pGad���t�����Gi�
. �]�Ga�Ge@�T.��t��]�Ge,������Ga�Gep�2�.�B?����Hd,He<HnPHpxHr�Hs�Htd��Z�ķ��4HaD_t\���HHa`He�_T.8a.����lHa�Het���.�".�����Het�x.�Hs�����Ha0q0��T.T����HeIs����HdIe$Ig<Is�It�Iv��m`�5t������Ie$�IT.п]0IeTIk\IslIt���D�94�=@��dIr��T.`���xIe�2�.�M�Ie�-��I.����Ie�����IdJeJn4JpXJv�Jy�~���J��I�����.�B�In��.����Je��.����(JaDJe��T.L������PJiܭ���xdJe�lJn����xJe���4��Ja�����Jm�Jr�Jt�i�J.�����JeP$L�D.`����Je���4Ka�LelNiOn0Oo�Pu�Py4Qð�T.����Ke\Kr����$KglKk�Kl�Kn,LrlLs�Lt�Lv��G`�2(���dKa�Ke�Ktx�"T.dL�����Kt��Ka�Ks(�;p�"T.<����Ke�Ki��T.@�Kh��K.(����KaLeLi��2T.�T.��T.���� LaHLeTLi 4(��@L.�QT.��WT.<���`La�Li�Lt����t��VX�t�La�Lh�Lt$�[X�_��t�Lo��x@�cMe Mi4Mr�����LdLMihMk�Ml�Mn�Mr�MsNt����Mi��,,Mn�_��	��ADrXMt���<Me��i��E����`Ml �"�.��]tMa�Ml�M���x���Mo��fl����M��T.�����Me��������Mi���@����Mk8NtP�l$Nld���NaLNr��p`�*Nlt�i����0Ni0�(��wDNi�|��]XNjh���`Nk�Nl�Nm�Nn�Nst�T.L����Nel�T.�����Ne,�T.�Nk�����Ni�Nk�.�0���NeL����������Nt(���MOe|����Oe���$Od\OetOn�OrPs$Pt`Pv`mt��TOr �T.��]hOa�Oe�OsT-�i�O.4���Or ��Ot4�	��I����Oe`����Od�Oe�Ot����.���l���Oa��T.��]Pe�����]Pa@PeXPrT�(�8PkPPr`�(|������G����hPa�Pel���pPg�Pl�PrXH.<�.�Ps ����PeL� ���Pe-_H��4�t�Pa�Pe����Pk,�2T.X��.DQsd��Qe<���QrLQt����pQ�$Q�h��X��.�TQa|Qo,��`Ql��L��Qa�Re4SiLToUuUy�U������Qo����Qb�Qg�QkRn$Rr8RsDRt�2.����Qe<XRe��".8	O���Rs�	xWT. ��,Ra�XTRrdRtax��8t\Rr x.�Rn���pRe�Ri�Rr�Rs\���Rd�Ri�RpSsSt�(D;�|�V�A����Re4�.��RaSe�i.��d"xp��D-�� Sr�,��	(SahSe|Sg�Sn�So�Sp�SsTt(Tvt.tL.]`Sr�.x�.]tSl�/�T.�/���Sa��gd0��Sg��T.�0���Sa�Se�0.���,1���Sa�Se1.p��D2�TaTo�2�H3SP3�� Ta�jP.HC4Te<B��@TdpTe�Tk�Tm�Tr�Ts�TvTCS v��C��xTal�"T.DD���Te�Ti0D.�DxtF��F]�Te�Ti��.����T.0GM�Ta4Ex$]���Tet`�$`��Ua4Us\Ut�.ha(UaDUe|a�.�a2T.�a��PUeHb�Pb��hU.\b��pUtb��|Us(b���Ua�Ueb��UrD$��$V��U��U����.�b,�b���Udhb���UdVr�c�.�c���Ue�dT.�d?Ve�d��Vd@Vl e.e��4Ve�e�
�Vb�VdWfPWghWk�Wl Xm�XnTYpZr�Zs@[t�[v�"OiP�Ve�g���Vo�FT.dj�Va�Ve�iM�Vd�VlPjT.4e6�a�Ve�e.�'�`�Vi<o]WsPn��Wf<Wt<pT.HWb,p],We|��XpLwxWa�We<v��XWk�Ws@w�.hw�`yPT.Dy���We�|m$|t�Ws�z���Wd�Wg�Wi�WlXm(�T.�~]�Wa�~x���WaXe�p.Ѐ"��LXadXbxXe�Xg�Xm�XsЃ��T.X�@XrX��.��XXo��pl�pXn���8.4����X�P�S�X�X��(���Xe���x.�Xsԉt�XeYsĈ���XdYk$Yn��4�m���d�Yl�t��Ya4Ye�;.�i��@Yr����HYatYp�Yr�Yt���]lYa�Ye�Yi�Yl�Ys<���YlL�
L��p��Ya4e.��J�Ye ����Ytl�S��L��$��0���Ya8ZePZrXZs`����YdhZg�Zk�Zm�Zr�Zt�Zv\�T.HZlp�(�� ��x����`Za0�i�.8�tZa�ZeX�x.�.��Ze0?Z�.l��Zel��`�,l��Za��8����Za[k[s,[tL�x�<��D�[.��� [e��p���8[r\[t���x.��tP[ap[e�[o�x.�[s�S<�2�.@���[r�����[e\r\u���[e�[i�[kT�Sd����[d���[nЈ�0�����t���[e�{̵�`\a�\e�]h�]i ^l�^o�_s�_��u�<\.���D\at\e����P\l�\n�\r,��T.�I�\u�����\e�\iX#��I4��.����\e�2�����\o�����\d]k0]lH]nT]pt]s�T.����\e]t(�O�����]jD�t$]sԿT.p���<]e$�`]r@�4�.��Mh]a�]e�]t$������]t������.�����]e�����]l�]p�]r�]sP�Z����]�]a���LuX���^g�^n��^eL^u�^�H�X^e����4^s���@^sP��(�
����`^e���h^s����t^��m�`��^t�����^e�^l_p4_rd_s|_t����.�����^a�^e�^o���T.ԟ��"�^g���_e_p$_u������������,_aD_eD���.T_s��B�t�]\_t|�.\�]p_a�_e����2�_k�X���_s�����_nt����_��_������������_d8��(`a�ae�di�eo8guhyxh�,��.4���`a����`dh`e�`g�`j�`k�`m�`n$appar�as�at�av��t��m``t��WT.����t`a�`e��T.����T.��M�`a��$����`a�`s���h����������`a�`eash�WT.���t�taat�.����aa8aeDas��.���Tae\ao(��h�����.����dae��W.����|aa8������aiX����<a�ae��"�.��(�]�al����
�aa bdDbedbg�bi�bkLcldcm|cn�cp�cs@dt\dvx�.����ba0be�x.��Z���<bl��h2Pbl4��Xbetbi�x.�X�bd�be�bn��
����be$ADr��t���bs�x�W�b.����ba�becl8cn�T.�b��cr$Kca(ce0ci@'TW�
�".���@ce�T.���Xca��T.���pca�ce�cs�ct8T.�Mt(t�ckT,�����cs�|
���cs�ctt�����cn����ca0��&�di����dr�tdk|t(ds�
��4dt����.lds`MLdetdn�0������|ddP	e�dk�dm�dpeshet�ev���.�]�da�"����da�deT.h���Sa�deeoxT.�;�T.���ea4eeHek\e��T.d�iX ��@eo!�!��Te�(!��|Faxee!2."W."���ea�ee,"�.�fp 4]�en04��	�ea�edfe$fk<fm`fp�fs�ft�fv�jPT.T5�eefik��5,�6.�6��fe 8"T.88��0faLfil���9;pfr�9��Tfaxfe��$:2T.�:��fr�:]�fa�fe�i��:T.�fd`o(��T.�;]�fa�fe�;T.�fk�;'�<��<���fage�<2�.�S.�S��gate\glS��$ggpgk�gn�gs�gvI]�H�Tge�L,T��hgaZT.�T��|ga�ge�gg[T.�gsX[0(���i"T.�U��ge�gkdk�pWi.xWM�ga�ge�W���.�q,hnZ��heDhkܭ5���$he�]�.dZt8he�\X�hs<\��Phd�hl�hr�hv�hy@=��i�\h��\:,�.`]���hep^�. ^���he�^M�ha�he��.$_���.�_>\_���he��.�`?�ha8ie`��idLiehig|ik�in�is�`T.�`q�`%Din\ir��2.�`B��.�`��pia�ie�ilx�T.$�Gp�.ha���ia(�.�a��ia�B�@ja$kc8ke|lh�li�mjnk�ol�pmqnpqorp�rt�tu�tvxuyvz v�cT.�c��$je\jn4b��0jgxjl�jm�jnku dZLf�$fdj.�e��lja�jedf�T.lgT.�jr g]�je@�(,hK�je�ji�g���jd�js����\�OPi��jui�jklT�k��ke�B��B��kh�Rtn��0kb`kehkixkk�kl�knlrDlt�o�qxXr�r��pkl�kspGY<G�kg�r���kou^�kis]�kvLu9Pv".v���ka�ke�ks�v"T.wbTx�T.x���ka,lv�y��y��ll�y leL|"T.�{��8leT��t"Pl.���Xle���dlk��Mpla������	�ld�le�lgmk$ml@mnpmr�ms�mvT�t8�]�lr��.ă]�la�le�T.��IT�]�le �]mk(��.�]ma�&e؅WT.����4meXmn�g ��Pms�T.��]dmaD�k̇��|mr,�]�mt��S�����ma�meԈ�.X�xԉ��mi����me�muXpg�^���mg�����moT�Dnaxne�ni�no orPou�oy�oô
���,naTne����4nk\nt�p��u�dndЎlniАZ�nn@����nn��z���nsT�3P�n.���ned����nd�ng(4;����nu�!��!t�ns|�J�nt0���ot���oiXC�x�0C,os ��8od(���Dod`om�����Y�hoa�Y%pol���|ofT]i�����oa�����orP����o�\��x��om���oipo0puhp�P�r8���osH�]�ot(���ptl�̜��pa<peh��� pk��4.(�i���Hpe���Pps����\p�<�x�pk̞tps��M�pa�po�p�������pgp���pk�pl�ptD����p��r�����pt�����8qe<qi\q����2�2tqsh��$qt�0qtTc�(��Hqk���Pq��������hqa�qd�qm�qn�qprrrt�j���qe��K\���qaܦ���qmhO�@�]�qa@��$�]�qaب��qpP��l����qt��]�fa@��Hra\re|ri�rrԸi���4ra��<rr8��H�Tre��ĭ��hra����prlT��$����rsx���ri���ra0se�si�so tr4tuxt�dT����ra�����rk�rlst���x����ri������ss�����JssH�$sdHsi|sm�sv\��\sn̟�����Tsalss���H��0�Jtsm$�L��sa$�-�sn��
�����se���smpi����se�����sn�srxt�����sa������ts����tk��teP��X���,taLtv7(7MDta��Sp>��Xtl`���`tl����lt����h�t�ts�����td�����tn�tr�ttl��(����ti�qi$���tet��ua`ue�i�����ta�����tl(urT��}u.4���ua<ueDusT�i�~�@�����Lun����Tur����ue����lud�un�ut�uv��I\�.h�]�ua�ueķT.���P��ur4���ue�uid�2���vo���H����ug�����X4vrl~���v�v�lv����Hve����[@vg��	����Tva�ve4���\vk�vl�vr�vy��T.��D�(�vv�������va0��vn�����ve��,�������vn����ve`����vpwrL�2��we<��pwa�xelyh|yi�zop|r}s8}u�}v8~y�~�|�".4���Pwe�wr�ws����\wk�wlxm8xnLxpdxr�xshX� �����wk�wp�G$��p����w.8����wa�wexl��"T.�ws��(l��$��wi4�E����xm��i��t$xe<���,xd��9����Dxr��T.����Xxatxep��.`����\����xe�xi�xk�xmynyo0ysH�Z�xeH�D-�xr0�.@����xe��T.x����xa��q@�.����ye@�"T.��$yeDyiTyt\�
X�((���Lyadyr"'��@���
tya�yd�ye�ygzkzl4zmLznpzp|zst����ye�yr�ys�2(�
.���|�]�yr�i0]�yizi���(� zd���(zeh�T.|��@za\ze�WT.	9���hzlp	xDg6���zm�]�zr���
�za�ze{k,{lD{nP{oh{r�{s|tT|v�l�.��zn�zr{t`m��T.�x.���t{k�".h�� {ehi.@]8{a���mgxT.���\{a�{e�{o�{s�{y'�.�{bЗ;���\�@<����{e���{k�{vD��E��(�{eܧ'����{r8�{t��4|a@|e�JȨ;|.Ш��|a�(|l�i.�;t���L|ad|e����.���|a�|e�P����|c�T$���|b�&o�|p�|sXAD!YL_�|a�d�|j�|o����|k�|p}t�j�o�I#X}j�%��E�T.4�� }nd3��,}eX}n�}r�}vZt�4��P}ah}e�4T.�5.L5]t}a�}e�5�.Xx���.(7M�}e(�s�7���}n�}r47M�}e~Ð�|���}a�7��}r0�x�����}g$���~�,�.�8��~a�He�8��(~dX~ep~r�~tt�,�P~t:���.�9��d~e�~sľ}�:�ur�:��~e�~id�i;�.0;���~e�:���~r����L��~��~�p=�`=���~det;���~v�=���.r$t��@�����H>,r(>�4e>��@kpl�p�r�>T.p>��de���>��|n�>��\n�t�>���e?�l?��?���a�b0�dD�e|�f��g�i�k@�l��m��n��o��pЃr�s؅t�@�$�e�p��g�A�r�B�<�e�C
4EP�r�ET.d�al�eF�FWG�F��t�f��s4b��'��a�G�H����a��i܀l�n�HiT=�H�Ȁ.�H�Ѐa<I�JM��m|u�4L�la�e �n�Lt�M��P2T.t�k��n��tPP��(�e��ík�l�mD�nP�td�ut�v��y��À���t,��ePQ��|�d��eu�0u��{pR( R���n�Rt�Rāa܁e��Px.�R���.�R�a�eS.\��. T�a,�eԛT.�2T.�8�e�T�\�e�Tt UT�;�U�l�i�U�V*�Q������Vi�V����a��uXY�Y��d�eH�gT�nh�o|�s��t��uXt�Z]�ixZ�rTZt��e�r\[�,�k4�t��d"��[T.�[t<�e�\q`�s$]�P]2|JT.t]tp�e,^,�^��^MP_M��i��l�_�`�8a]�f�nla����a$�d4�e\�il�k��n��o��s��t�u��y�����at�eP��b��cx�b,�nD�td���8.T�sL�Pd�d�ed�eP/t�e]x�a��e�ex.�e�f�K��g��aĄe�gT.؄m�tMBY|V�h���t�h2`��h����i�@�aX�eh�kt�n|�p��s��t̅y�h��T.P�s�j(�iiLk*dk��`�a`l �l�P�2m��e�m���e��� n"��k��l��������ąr<p�
�aH�ex�i��k��nІo��rD�s`�th�u�p�$�ep���g,�k���4�*ؾ�`r]4�.�q<�nd�s���p�o�r�X�k�[s2T.�<����tX����n8C��a�T.Xt���eh�pt��Ȇl�v�t��u2�r�t8�e�o,��8 ?Pu���v|u��u��$���u�.�u68�eT�id%T.4v��v2@����a��e��i|�o��y��pz��Ba�9ez����d��iԇk�l$�nh�r�z{"T.�z��ȇe���{"�sx{���e�g���{]�s�|WT.�|���e4�n$}@�sl}	�����H�aH~P�t~��\�e��i��s4?��"|�e�~i�~���k��x.�����n����	��e�g0�i`�l�n$�r��s��tЊvЂ"T.܂���a�e�i �s��i.���<�V��ă��(�gD�sT�t�p�����L�a�c��a��d��e�l��oԉy�à����t(�"��r��X�O���x����"��s̉v����S�=���܉�`�T.�t(����et����"T.<�s\����aL�e`�mx�s��(�D�.0��$�SX�oD�P��e���l�t0���������l��t���u.�����e�����.̌MĊe��"T.���܊a$�e8�Ì����dD�eX�k��l��m؋n��s<�t$�$.ԙ ����0��H�����.��]L�ap�����d�l��t�l�ax���|�l�d���W.������a��el�.̾%,�:ċs���̋g�nl�;��T.�����a�e�T.$�r�x)����il�T.x���0�aP�e\�r��T.Ԕ����eЙ��d�sH���p�k��r��t`yI��Z,�]�?a\�Če|��dJe����n�������Ќa�e����،r$�������Ģ�.��/�����d(�r��c4�s�4��x��<�r ���D�ah�i�"��Z`�e�X8���t�e@����eЍnd�p��r�sT�t|�v�8�.��t��a�eh�����d�g�k@�s��x.�.$�t�e�4�.H�]�a,�e8�v�4.X����||��.��]H�ap�eȺ��T�p��2T.,#��.�"M|�a��e�����j��k�tL#�.�#�@���aЎe0�x.�sP�|��S��(�t�������ex�8�iX��r���e�����4�n��<�e0���H�rd�tL�Op�el�P�.X������j���<���r�t`��<�������H�>T�����a�t��ďs����ԏe��P,x.��t�e�����t(��	\�gh�i��k̐l�m4�r�s�v��y8Q,d���D�a�2e���L�l�|t�e�"��P���|�o��tTKt K��a��e�LT.�e2,���d������e�s�vx�D��D��T.����eh�����m<Hh���tX��s����(�e\�jh�k|�n��sܑt@�qL�MT�e|��P/2.�]p�a��e�Px.��|�����i����k��mȑtGN���<���nH�Бa�eL`�.,�X�tD�x�,t����d<�e\�r�|H�s����0�r��p���.���P�eP�.`���h�e����t�d��eܒn��r0�s��qВllJn�R.�����e��W��g����Ēed�x�a\�t��t�����a�e����r��-��X���r��$�tt�Xt�e|�f��g��k��lēm�n�p4�rH�s��tؔv��Z�Z0�Z ������t��u��"����.��M��t���t��a�e��s����̓d�x.���t���a�����n$���q �k����(�s4�t�X@�eh�kp�lx�m��t��v��8�]�����a����e�f,x��t�x.̔o���e��g`�iĔg�Z� ����d��f��g��kЕlܕm�n �p4�rl�s��t��v���a��b|�c�d�e��fСg��h0�i��j�k0�l@�m8�n��ox�p��r8�s��t@�uX�vlw�yzD��(��tD�nСx��������s��v��s�20���ȕa@��8��.�s�t��x�t��t��|���rx����X�e����(�id�n��PD�e4]L�n��8��x�e�R�����<hX�\P��b�v�����a�b8�e��h��i��lP�oȘr�s��uL�y4
�x$D
�l�u�(���t(,�i����eP�tx
(�ll�n��s��vE����X�.���`�ap�����x�a�_��k��HiH�ȗr\P��eؗl�sh����Зa,�����t`� �a4�o@�uP�<��a����r$ � ��,�k@ ��!�d!�H�al�bt�e��k��r�!��"���r�"��w� #����l0�x%����d��t�%��%���ed'�Ԙe�'��*�$*ܘl�+��l+��e�f,�s@�tlE��F(x-Y0-��$�kp.�@.��8�e�.T.d�bl�kt�r`�%�"0� 4���a��h��kșo�3�.�y�8����a�7����eh9l�:>�0�l@�nT�rh>��Йah�d|�e��f��g؛i�j��l�m�n�ox�r�s��u��v�y�ð@��B��A��8�t�C�C��L�t�F|F`�aPI��H
t�b��i��kȚl�n��o�p�r8�sl�t�K(KJ��s�KJPM(Ԛv�O��Q�DQ��ܚd�e�QR<Si�S����S]�.,�l����$�e�V-P�e\�lW��V�H�r�W����X��d�.|�s`���
	�	��rPZ��e�Z	��e�Z��W(]��e]]��n�[̛elcM�u\d�4e
	pg	,h�hA\h�a0�b@�pL�r�h	���kJ8�p(l�X�al	��n��`�f��n�mh�a��eМiܜo�ôn��oJ��l��p��t�pC��q*�*|q��Ȝd�r�s��s������"	0u���tPt���mt��a0�i`�l��twoR.����8�e�xD�gl�t�x8P�e��(	�zi��k0zt�e��r���p{.	�it~��n�	؝a�eH~i~��ĝe���̝rL�d��T.�3	ȃ���y�������m<�aD�dl�kx�l��n��pܞs܅�̌�T�e\�r��2�]�Od���d�tȫ��T.��a��i��o��p������Od�����t�9	��".|���Ğe����Оk�t��x��$��
�a8�c@�el�f̟iH�jl�l��o��r �sD�tx�y��� 4�	x.P�i�
�@(�X�n�]`�a��e��iğu�
(���i��s��P��.��d�|��P�a��b�n �r4�t��n$�T=	$B	����n�
��eXH	d��,�i�N	�@�o������T�.��r��\�a��e��i�b�$2�ip%���k#X�a��i�S	�Ԡr�M��.���Ƞa�etY	��n<�i���e�#�,#��s(��'�l�)pH),�iX�lt(]4�e`�oh�s��@|*��*6.^	�-p�r /b	��i�.���r������$�i�/W�a�e$�nP�rd�s|�v@0��
��a��d��e��g�i�l��m��n��o�rt�s��u����J81���g	�2���f<�g �m	�2t4�s�4��3��H�t�5��\�.�6"�.�6��p�e�7i`7���e=q	�<��nh<����e�8��lܢm�n�r`�sp�t>J@@x	�>]�t0A	�@]��a$�eD�iP�sX�u�A�	xA�kX �(B(0�k�A8�sC�	 C�tC��E��E��h�aG(G����|���3��G��rģsHG��ạeԣsܣu�G(�Gi�I=4J�	�tLp�Jx.�d�sTKp�MZ8Q�`Q���a8�ed���R(,R�0�mP�sydS��H�a����U��\���g�	�Vp�e Vx�o�Xp�X��m�VM��o�YW̤dԤrܤs�t�v8Z�\�\��\n�\�^�@]���s]K��a$�e,�i4�uD���^P`$a8<b��a��<��Xc�	Hc�	P�b�bXX�m��ndb#	d�a��e�i�j�k@�lt�m��t��y�h"pc����g�c�	�c��iХlإm0d�t�*���	�����g,e]�n���e�u�e(�e���a4�u��(�f��,�lg��t�H�ap��P�lD���\���gMh��ka�h����r��v���	�l]�n.��a�2l?����y��psM̦a�e�r�Ԧv`s���.�sM�i�n �vD�i,��	̉T�8����	(�a\�e��i��kȧn�r�s|�v���ܙ~p�r�*T���h�.Ĥ���M|�s��4.�]��e��o��vT�iث�	���и����e�g�n�o������	`��	t���]��o��P�����e4�kH�mX�od�sl�vl��@�h8��	@����t����P�m��;����iT���t�e��i���������M��ąe��i�l��@�\������d\��	.ܨs���(��������� ��.X�a�d��e�h�iT�j|�kX�l��nȬo�p8�r��s�t��u��v̰��	��.��d��f��k��n��o��rܩt�u���	�	�Y0*�����s<Y��|�̩b��	\
�	�	��ԩr�
�x��" �c(�h0�i8�kL�l��rĪs�t0�	�
�	�
�8�&p�i`��@�e|�i��	�A\�r���d�e���A�	���k�����e����	������� H����mԪp�


���ܪe�|Z`���n ��e�W(�d0�k@�nL�sx
Tg���8�d � !
8$: $��\�d�#d�nl#]p�a��e�i�j�o0�r<�uP�v�$�'.��nثr�s�%*D�
���īt�%��̫sX&('K��m�s�� �d'|�( �l�(2�)�)�(�il+H�l�S(�+ , 
t�e��i��u�}%
�,l�v�
0.��k���.����s�0;��e��o�0i�1�X2"�m�n�s��v�5��4.�6�:��;���*
L<�e��.
(=���a�<�� �sT<�,�a\�e|�o��u�=3	l�ix=��P�gt�s�=4
`>*�?]h�.��e�5:
�@,h?
�B����dB��n4B��
��a�e8�iL�jX�kl�lt�o|�r��tԮu\C(C�k�l(�sPtE
pC]�o �vu��C(����D0�dԉK
�D�elEd�u�ER
 FW
<G�D#[
`Ii�H����i��o��rd��������lxI�Ȯe�Ia
�I����nJ���(TK��	ܮb8�i@�kT�ll�m|�n��p��s��v K�a��eدh�i�l$�rd�sx�u �(�K(��h
.�K��H�ed�l$������n
�K��t�sL(DL(pL(M(�L��g��k̯m(M(��t
xMz
įe�
��
xN�g��t�N��n��8�e��ghG�e@P�4�eD��XP��"*�!��<�����@Q8P�a�P�X�l�Q�
�Q��p�e�VX�R����tW�
��ahW*HW����l\��[����l���ܰ�����\����
�]���.��d��gԱhܱi�l�m�n��o��p��r0�s<�tP�ud�v�]���a|�b�d�e(�f<�g��i�j��k��l�m�n�oĺpغsĻt��u�v��y�Ì�
�^����r,_x��a��mȱr���
���
���|_���a�_��_����
�`J�l�`�	�`����eaaa���a4�dT�ed�gp�lx�v`a�
H�eh��
ta@�.�a�
���at\�rb�
Db�
������lb����r�b�
�b��
��.Բa�e�k��l�m�r�s�t �vcRXc�
 cܲn�c�
��
�c�
�c�
0��
�c�
�c�
Dd�	d��(�i�ddH�r�e�f�\�kD��\f"�Ba$gPT.�fmp�a��o��u�"?�g���g�+�n.�+����r�g���e̳m�V�
�h2�r�gtԳe��o�r�h��i�Pj2j�e�k"
T�f\�g��i��kĴlдm�n(�pX�r��sԵt�u�v(n-�nJh�.��
�p��pAp�nhp��x�e��k���
�q�
�q����e��s$r��r�r����idsJܴu`t�t��.��h�t���u�u�r���v��s<v��o8�t��
��l�eDw'@�d�v��L�ex�o��u��.x%|x�x���e��k��lĵuXy+z���r��0dz5�{|��{��̵a�eX|;t}-�}��a�i�}�d~��	,�b�a�d�]4�.\�eh�u����9A��T�bx�]Ԃ����p�r����x��4��
�����l<���l��mȶn\�J�F�����.�a�d��s�tЇ�u.D�����
܉�
����X�.\�]�a0�e`�ot�u��ø��h�.L�nX�r0���c�D�d�*���l�rĎ(���	��vp���|��(�L�R�����a��P��r���e̷s@�r,_��`Ba�e���Էg(�p8�r���aL�eظi@�o`�s��u��ü�.��(�cX�b��0�t�6�D�a|�g��k��n��r��sȸt��R.����p�e�(d����.Px�īt�����s����tH��T����^0�иa�d��e�n,�sP�($�et��
h����el8����.���� �t��s ���8�m��(��L�n8��T�ex�tT�W<���p�iH�������l��x������.йeعt�����s��������(�D�����ԛ�k�t$��eL�(��L���	�'.H�bP�iX�m`�np�p��r��v��w��Q|��`���p�aP�Jh�eĢ�����|�d��i���t�=p�*����e8��ؤmкs�2����e(�kH�lh�nx�p��t��uH������d���
���p<�r�����a����4�e@��\�Ü������T�����h�Xp�u<����r�����e����T�t�����k�lȯ��a��el�o��h
.`�*ػe<�*H��d�m8�rD�t�zh�mJ�a|�����$�a����,�n�xT�a\�eX��l����ܲ��d�pض@����x�k��l��nؼr�R��R��aH�����l���ķ����dt���.����̼e��u.�k����a<�e��i��o��s4{�(�eܺZ�s�.\�h���4�dP�rd�s̻�\�m$����;p�.d�F������x�eT���s�|������t��`�����e����Hpe���Ƚs�v�~�����Խ���*����t`����%���h��i��k��lоn�r�s,�t�����aT�b��e��h��i�l4�mH�nl�o�p��r��s��t��u��y���i�����s(�J��i��s����*<�JȾd��(�*������ؾ.�a����<�����.�o8����������X�t ��d�(L�P8�rd�sl�t,�@�at�e		�	(x�i�J��e��s��|�i��k�n@�r|�u��-d����JĿs�8��0.�t̿e����ؿd��s �t(��LV�
4V���v�����rd��ap�(��",�m����4�a\�eh�i���$�T�t���t�n�����\��@���uh�"��n�s���������a��e��\�z�u�����k���|Sp����e�t$��s����e�zY��(\� �d4��(�a�Y�@�.`�e�((�X�d�]��e��p��r��v���H�"`�@�t������e��(T���p�r����a�ed�ip�l��r��u���4������e��il��i4�rH�s���T�R�a@�e����$�iH�`�0��m����P�n����X�n����e�{(	�|�t��S�����e4�(@���n��J��������������u���<��$������H� �k,�l4�tP�u\�vx��0�����uL�,��@�r��X�@���H�t|��h�e<|���J����p����2��d���i��m��v����*��a��
4�# �tl���l��i���r��<����y��������@�.��k��l��n�r,�t8�uL�vL���al�bx�d��e��f�g�il�k\�l��n(�o��r�s��t0�up�v��y��è�.�'��e<J��t�2�����a��f��y(� -���e��g��t40���:�	��b�tHC>�
D�2���$�odJD�s|����X�.�P�
X�f,
_`�e�t�8.��a��e�o�sTAW���m��t�\�����ad"���k��n��r�t��x$�e ]��l�X���sm��8�ep�l��tH�T0�lP�npa|��H�d���\�t�8d�e8�D|�e��r�J\	��d��g��l��m�n�r$�st�t��vP�EJ��g��l����v�J<i�d�ut�������.8�t@�v�!�"o u0]H�a��e��k�";P�k��sd"��d�it|��p	(	�#-�$��$��l�$��a��à�H/��. /-��e�%���r�%�����\'P<�dL�nl�r��s�%t��e��i��o��r �s��t��u���P''����'nD�h���tBX�e�'n`�m�D3hD�x�e�';��t<(x.��vlO�
tZp�(��f��sT)�h)�
|)K��a��e�y�)����.�s�t*Y*8�a�`+�*#�mP�t��v��äk�+8�oh�Ì+��@�rt�y�k��k��`���+�hl�tlX|�a��Y�lJ��y�l������m��m��e0m��r��vdn�pn��e,(,��n�,���X�D-��,�a(�b8�sD�v�-D��,1��0�jP3�0�<��L�a�4T�nD4]`�a��i��j��o��r�u(�v8��5��d��2	�62��b��p��s�7(:�`7��a�e@������.�<����at7����nx=28��7�t8���(T8��H��0����0�8dt�nd8P�a��Ð8��8��l�d(JP����ah9���pt9�����	?
����s���nx���a�e0�i\�o��s�y���������e�;��m�n �rX<��<�X=�d=(�dH�n0��=��@�n0>>T�mp�nx�v�D�|>%<?(?��i�>t��e��i��k��l��t�u�?(�?��d��[P�������?�����dO�4@e��a\@��@��A�BJB����<Bh�.L�al�dx�g��r��t��v�B]h�.t���jPX�bHC`�e�C��E��D����m�F
0G���a��e��iDG�<
�]��e\H����k�Gm��iD���c����tI����fX�kd�nl�t�Ht
��a|�e��i(�k��l��n��o��p��t4�u`�vt�y����d
LI��P�a�I<J�<K(|Jt�k��n��r��sdw�TK����tę��xP��eH����nP�"��e�K����i�K( ���L��k�n�L��L���d�s��
(N(�M�� �iD�jL�l\�up��LNN��

���N��T�nl��P���h����
�O��|��dO���lPQi�P����r���,QX��i��o �
(����s�����t�����r�Q��a$�e,�i\R
�Q���r�R��R$
�S�L�nh�+
X�a�S��@�dpZ1
�S:
l�eT@
�T U(�T��|���U��U����a�U����l��n<U��a�eh�i��o��r��s�uV�	V����d��s��G
��*�V��d �m4�nH�s�W��WJ�i��M
X��,�n,�5�X�@�l�YU
�Y]T�l\Y\�kx�m(Z(���Z����i��r[
�Z����d��$[����b0[��a��o��y\[�[����ml!a
T!����gX\9�\g
�\���l$�s�6x6���t$]"H�sT�tx^l
�^��@�a�^p(���_��\�nT_d�e��i�x�_i��n$`���at`(b
��rD$�����������(b�hbq
��kTcd�d
�eM�a�g �i(�kH�nT�p\�r�eXpw
�t]<vq�0.ԉt0�eĈ]<�d���`�xX����d�a̵��l�e��iH�j\�l��o�p��r��sd�ux�y���h��t����������������a��d��k�n�o �r<�s��Jl�"$�
�{
l����sD�����
,�e�����
����4�e��M8��t�s��P�a|�i��o��y�����2��T�]�������l��n��o��p��s��td��d�����|���]��i�]��t\�����
0�.8�eL���r�]�aT�et�l��rh��
4��
����-@�k�H�s�� ���`�s���h�a��e��i4�W���������il�2@����a��e��il��������n��t<`��*������n��
�a(�e@�lH�yX��p���n�� �l8�szx���
��a�������P���'p�tp�H���
��lx��
������s�����.��
<�bP�kp�l��m��n��r��s��t�u<�v8�����aH�bH�d��eP�f`�g��i��k�l$�m�n��op�p��r0�s��t��uh�v��y8�ø��
����4�i���$���H�s`�t��OĻ�
,���h�t�h���|�i��d��o��s��v��(���rt�G
p����d���
������s0��u.�����e,��
�����g�k<��
(�ep�4� �nD��
X�J4�hh��
x�a��e��i��j��l��o��r�u�y���
��n�.�����e��
����h��n��t���
���
��`�
T��
��dH�La���
��
�.��i�
���(����3�
PIT�0�bh�l��8�e��f��r��s\N)���`�i�Z-�2e�Z�t�r�Z���������2H���e�����a����t���a0�bP�dX�f`�it�k��l��m��n<�oD�p`�r��s��t�v(�
(�rP��D�aXxX�P<�r��J��-�Jl�nt2�J��t�,�����a��e��i�����k�"`�
�J��n��o��
���.�(�����a�g�i0�t�/5�X����sT$�e�i����xx	L�l	�T�e��f��k��o���	;�	|�a��B
HX
Nd
�����t
�|
����.��kp�d��a�
���a�8q	p
��.`%�a�nL�0��
$�e�.�
@=��������0�����hULJ��X�h��i��j��l��o��sPI�O�x(
|��H����j�#��t�"��a�b �d,�m<�nx�s��v�
���e�����n�Z�ap	_��c����r�(�i���4�nP�sd�t�o\�k�u�o�2��a���l�e��t�n{� �	� ����o"J�
(�#����u�#	��a��e�h�i<�lt�o��s��v���4$2��s%�l%N	x%�a� 	�%2�t,�v�%��%W&4�e`�����JL�f`&��T���2U�&��l�dh(�0(��e��r�'���txI�܂��(����g�(X��eX)88)����yH)�����,_��`Ba�)����g�)��a|�(`+�p+�aX�bx�e��i��l��3� ��D���+�L����@���d�i,l�d��t�,���r���T�g	�,����f�,��n0u.�t����e$���np-���e�.�H�a�.���l�.�����/k8�a|�e��i��o��u���4(P/20�dH�l�/(�
���P�ax#��X�l�1-d�a@0p�v�1222�3a4*�1�������04��	��.��d�k�n�p$�r,�s@�tP�v�jP.T5��e�6� 9��9�a�9;t:��:�8�t`;��;��<*�<��H�e���=\�dX=d�e��o��r��������tL>���e?��>��a��e��i$�À��0?Z��e��s@� @����k4��@2��e��t�A?�a�A���d�A�����A�l�a��e��i��k4�pH�th�v|��@Bt0B*X�aB`�k��ni��g��|�s�CnLC��l��r����C����s DWD��n�D��D���m��r�D����a�e �r�D�؏�Ў"�sԔ��E��eTH��G�,�r�I�|H�@�r��(�JT�i�J�\�e��I\K��t��p��8�����aH�e�K����l|K
��a��e$�hL�id�nl�o��r��s��u��yXL2��l�t��h�L����e���Y���s�T.0�s�M�ax( H	�N]8�a$N@�k\�m����`O�x�p�O(�O���a��iP�YhP����n�Q	�P���t���D*�X*�����RYXR��vr%XTS����l�n �r(�s8�t�T�UI�U�	�V�|V��0�bH�nXt`�	PX��P�s��t�WX�e��i�)���x�e���������a��eXY����s�.Z�[$_���.�^����e<\����v��y\_�`��d(�p0�t�`.�`?�e|a
b�
�B��.��a��b��c�e��h�id�j��k��l8�op�p��s��t�uD�vX�y���4bW��b��k��l��n��u�bxd�f5�e����o�gtlC�k����gx
��l�
��e�B�Jtn�.@�aH�dP�gX�k`�lt�m��n��o��s��t��u�n`oJlpJrJs�l�e�situJd"x�v"|�tv����e��kwS	���wi��lz�	��t|{L|�{����e0}�����u��p��B��b$�p,�r<�sT�v̆J�����,�]4�.L�ṫ���J`���\�a��e��o��$
����x�tԉ���tD������r(������rT���	��a��e�i �j(�op�r��u��v����t�iЎ"��tTY'@�!�s`����&d����'.D�eT�p`�rh�tP3+�70�7��L�i$�*h�n���|�a|�7��*��a��e(�����l��t��<ȖC�VJp�N�\�l�����yP��������8�a�e,�i@�(	7a������g\��,��v�(x�$�t��T.L�mT�vܦpܪp�(��\�n@�Xd�a��e��i��lH�2$�������s8�S���X�.��b�l�m����a$�e��i��j��k�n�o4�p<�tt�v��y����(x�(Ⱦd�t�f����P��'.�fd@�k\�rt�sr(y��x"H�eP���P�il�v�y
��(��X�]|�r����f��n��s��t���T�����g��^��ض]���uȖc.������e(�����lȷ�����'. �a(�s��X��4�.���ظ�T�a`�r������L�nx�jl�u���m�����|�s��������k��������������b�n$�t�����a8�ex�j��o��r��u��ܺ(��
�o��e����d�v���u@��H�0�.T�d\�gd�kl�n��*ܽ(��(x���D�h|��,�F������f��k��vD��t�p��������n����e��o��(P��X�����a�}v4�}�����v��������� �n0�s������
���(�a��t�X<�aT�h��P�mp�n�Z�h�]h�d0������|�e4����yl~��������`�i<���T.�a�c(�e�f$�h8�i��j�l`�m��n��o,�r��sx�t�u4�vd�wl�y������
P�e\�fx�k��l��n��r��s��t��u�v�����<�eh�=D�n��t�{d�d�.4���l�ap���<\.8�����a��G
<�����g$������r������a��vh��
`�\�.����JD���J��h8���]�h���\� �ah�cx�d��e��i��k�l$�m0�n`�oh�r��s��t��u��������Jp�d���H�(��d��g��nt�}��n��2L��@�����a��e��t0�_��tT��\�����a �;T���e��
�����e���
x�J�b���D�eP�iX�u@���.���T@��i���.��a��e��i�x�"��t������kH�����l���������t���|�(������������$���r���e�T.0�s��ax-�Wh�k|�l��n��r��s��t�]T�a0]\�k��t�i̻��W��a|����e���H	��p 	���e�
-�
����ap	����t�
�	�����o8b�a����t����nhG�a@��x�,�y���4�����L�s�T�o(�l�l��t�e�������a��g��i��m��p�r�s�vx]��r��J���]��r�����p��d�8�e�%
���$�aT�e��i��o��u���
$��L�gh�np�r*�*`S	L��x�f��n�*�;�P����t@ ��^��!����s��y�!�����P"�#��e�j4�kl�è$*�#��r�%��%�e��� &� �t0&��(�aH�jX�u|&���(���P�lD*�X*��d���*t
�8.��a�e�f(�iX�ox�r��s�y����*��th��P,��.�n�-�x-*�sH/
 /-�e�.��r��������Ü.(`. �d<�sP�t/xH�e�	�</*�/Mh�pp�v0����0K��a��e��i��0����f1�L����n�1xh1t��k�l�p�v�1��'�)�d:��2���s34.����d3$�e,�t4�6J47�X�iT����'.H�]@�r8��L�e�8�8��r��v0�0.@�|�e�9����kr�t;��r�s�y�<�@<J��st�$=�e�=3	>��ؾ.�a�e�r,�s8�t\��=�>�4�,?�$�e4?�?��T.��a��b�c�d �e��g�iD�k��l�m,�nL�r��s�t,�xl?b��rl@��T�.�@��a�.8A����e�@P��n�B�h��C�.�B]�a�e�i�C�	�C�t$DT.4E"8�n@�r\�sp�t�E�	�E
L�eFi��*`F��T�t���Fh�a��e����"��.�1(�G��l�sH����a�e�n�u5(h<(��eXH�l�".����e�<�g<I*@JR�J��'�L �i�L(�l\�t4L��4�el�lx�s��v��H,i�Md�aN0�W�PX��ed�P��s(-tPP����a��l�u�R���a UipW�`W"�n�V���e��o�spX"YI�X6�k�t(Y�TZ��Y��$�d���8a8�ela��@�ap�e��i��s��u�c��bh�s��td�Pdi�f9h��ehx�hWi����a�k��o�s�t��u��à��dk���j�r�k:plm;ȼ��m���s�m���aL�ed�i��o��r@n?Ln�8�t n"@�t8�Ep�eXniX�s�	K�nQtn��x�n�nk��i�IKPo��nDo(pV�o�����pZh�.�r<p���a�te��o�r$�y q�pt�	�v�t�	u�	�t8�e4w�Hy[z��b��e��g��l�n�r,�s@���4�a<�bH�dh�ex�f��g��ill�n�o�r s�tuv<�ؐ���С�x{Y�vH|`�|��|���d�g�l��s}f\��
�}l~��h�.�t:
 �a��.T�\s��m4�i�y\�ePM	�T�l��"��d��i�k�l�n�r`�sh�vH����eh�~����.�H���l�n�2�J(�����a�e�����g��
��(���s\�����.8�aD�dT�e�����"0�s�;P�x�L�i��̌-`��<�p�a���p�������r������a�g�k�n�rsPtt������e����t�Xh������'.������a e<iHt�C������(g��0n`��x����\ ���XtX��`u�Ü��Ƚs�~��|���U�a�e��
t�%��sH��k�r�vЙ(���D�xt��ei|
(���s<�ix���	aTe`i|k�n�p�t�u�v�����Lg�����d���hg$�po�c����i@���el��8�]�g����e��i0��$�	al�x����aD���k�" ����i�����(l$���L�0����	Xl|�����t ���`a�e�i����^	�nd���D�e������n�r@����a�stTH�v�.��e,�i�J��]�t0����i����u0zl�����(o(�`��T�<�t� �Pab4c@d�f�k\l�nXr	s�	t(
u4
v<
z��\aD
bH
cP
dx
e8f@gHh\i�jLk`l!m!nd!oP'pd'r$*s�*t+u�.v�.w�.y ���L�$s����y0�H:�|��,l��\atd|e�i�sЙ�T.ln@��h��|�.�b�nt������.�sHR�؛2����(���a(e�i�k�rs@tHv���X��nr��8s��p*@d��"nXr`sht�?
Le�i�.��*Ī����h�.xtL���.̫�ث���s���r|�]�e�o�r�t0��8���e�i�o\���>�|�2�F9	�����mp(t�G^	������� i8r�������=	�2�k0���Pa�e�i�j�lhoptxu�_
��B�"�s��T.��Է(����g�n���a�e4iHo�s\�d�(|�(L��dgi$v|�(�(Е%`��	ظ,g�@���@v������T��Ļ����P�K.�n�r8����a�d�ek@t����3	��x�t�a��(���l�s$�(T`��pl�]�a$o0u08(��2r������8u��2�t����Le�i�k�s�t�u|O���xt����e��4]�n�����s ������r0���k�����r��i�����n@	rL	t8���	eT	i\	k|	s�	t�	u�yx��8	u��(�2��l�����d	k�	n��l	a�	i�	o�	t��(��K�	s����<���	r`���]���������	a�	r
t,�(�tx�t
i������
a@���
lX���
t
a�
eDhLixl�o�r�u
y0
�\��
k�
r�
s�H�!X���
b		���
ex
h�.bdf$g,h4iTkdl�m�n�o�r�s�t<v�
�(��a�'$,�?@�DeLg�-���PJ�����\ottl�e(K��(�1�2�.����e�ip�	�fp�"	�8�kp�=����a���	�ae$i,o4r��x��n�� )@��-H�\�\elnHD��[
���dn`m�e�t�t�nd%Hd!��p���'���kd'��elH�,���a+��r�t@.���'.
ep.��.L
e 
kt/��d1S ��@
�(
�3h 4]h>\
e�H��kp
n;pDQ���
a�
b(d�e�f$g�h@iPk�l�nhppr�s�t�v�|�]܅]�
up��T.ek �m�
o��]�'.���
rċ\��T.Lř��a\epi�r�s�y����D�n��Tr̛D�he�(�d�K|e�o�l�d��X����a��r<��̎��������d�fБi����a�i8�*e�WrԖ�DaPi\ndo�r�ü�]T.��x.��̙���3	�^��l.�n�Pte��8�.�_���e���	�8.�������s�.�gܚM�aeu4�ht�L�T$����s����r���xțt r����(��Z�id���Heli�k�l�s���Хܥtl�r�s���|e�%�`�OĨ�X^X��"�bgȫ��
�a,ehi�l�o�stHuPvx�`B�
(����a�p]0���e<gDn��iLk�p�8q�H�(Їb����Ta�"\nxv$�\yi�g�a̱�s�t���e������l����������<���p�t<���(�����g��a(e<��̵ sp������4�D����i ���Xe�q`r������l��
L���e��q
�n@���td����a�egi$s\tp���2�d�s�t���8�t��i��tu��iH����ti@kLpLN&�M��8j,Q^	��i��Te��x���a�d�eg�i�k�n�or0tHu|���4�"�i0�s �;�����p�st�w�����t�
p��x�e����aTedjllto���0?t4e�]<d��Hn0	s�O����������|������b�k�s<i8�]�e���������l|�ih���a�e��i��"T.a0���e|��i��i��(e@i�i��"\s�h�
(��	Ta�^�l�q
hv8���p���_�.�e�i�j�kno,p4s�t���"T.����k ��\���p|����e�n|��$�9	eL�
���$vܪ������HaXedi����h��Pl��
��d�xls��tn�s�����e����.��`����������	�.�a�e o@rpuxy��ܮb���is�p�m@��set�"n�����,r�w4a\ehi��
�T.4��,���"T.t���ĝe����r��M�a�e�i�o�r�����.�u.����e�����sd�x�D��n,������`��3��0��d1�$�@0t�s]Tu����P�a�b�c�dHe|f�g�k�l�m�no4pHr�s|tt�iT����kh�t�X�a����l|�iH����eЕMe,r4u ØJ�X�e-�����������ĚiXeܙ]<lhr��0C6T���`v��,�]tax��$���]�a�k�l�r���i't<�� ��(��.suT����a(eDiPjdl�m�o�(f(�*4r�tԮ�į(l�<n(����itgܰXa�e,_��7a���<��g`�S��M\��8	G
��]�s$���n��a�got|��l��t�o�Z(`�+��X<��(m��4��� s��((��\s��]<i|k��o���4�.�$(��hn��pe�������
�a�e�h�k�l�np s,thv��2p���i��t��Ol����e��8P�a���8�_ix����i��e��@aPi\r����S��HlD��l�u���p�.����pa�e�i�rD�T.�s��E$��.��(|�K�e�i��������r�����a e@�|����o|���t\��k�Z��],r���4� �Xh  �]X�atepi�j o@ u� y� Ì^�	�a�]��	�d�ftg�k�mn<r\tduЙ�_�`S�_���r�`i�`���eTa`at�aa��d k4tl�S	,e�4i8b��b�Pa���cHr�d*fx(n%�klf�i�k�m�rsTt�p��rhp���eH�i�q(�a�e|qL���q��sp>�ds���o�v*a�v�x��H�a@sLt�z�z�� e�z(n�z�4e�z��{d\��]\e<�dk�m�n�sH��\����a�e��,e"�d�i��t�s����g�������s\�i�a��H��$��rL��� d$ k4 m������`���, m��tX el s`Fd���P s�����d a�������x l���� g@���.���� e�� r�~��� �� �L������� n� r� s����
��X� a!e4�u.��Lt,!e\!i\h�.@!rP!s�*�"���H!.�,
�e��!a�!bL"d�"e�"f�"g#h#j #k$l�$m�$nP%od%px%r8&s�&t 'u,'v<'wH'y8��e]�!s�����et
��!n�g�!a"y�g���!b("l<"s
L�'. "e
(�hR4"a�h
XiW
�j��iD"el"ix"r�"ì_kd"n�k-4lJHl���"��l1pl�"f�"n�"r�"t�l7`m]�m��T.Pnih<(�p�"lXp]�"e#n#sxr=hs@�tD�u'<v��
ؾ.P#a`#ex#i�#k�#o�#r�#s�#u�#v v]�v�vX#lp#t���MLw�xdx2�#p�>�x��#iDyI�#i�#p�?O�y���#a�y��#d�GZPz]�W2�zX�#e�z�($e4$iH$ot$sX}��e�|"$i�~+@$n��"�n.\$id$rX�U��@h��܁�l$p�$t���Z�$e����$b�$m�$o�$s��((��$l(����;�W
l�� ��$p%tĈ]�$a %n4%sD%u������%o��,%e�;iL�� ����<%sd��\%m����8ܔT.`���l%a�%e�%i�{o�%t\�-�.�%l�%n�%t��`�*Ę*�%e�
<�.�����%f&i&s &tl���%e(&i0&s��MSY�<���P98�l&e�&k�&l�&s�&tL|".�{��T&e�`&t���L���x&a�e�i��&e��%����&op���&a�&e'o'r'v��T.�&k�&l4�(Ш(P�.X�i�o47u����c��%�Z8��4'e��Z̵�\'l�8���'a�'e�(i�(o�(u�)y�)����	�'k�'s�i$����'a�'s��{4�i�����'e�y/(���'p�����'s�����'d(i8(tD(v$����(e(sl
|t$(e�
��,(t`?h(i���X�.��?P(r���\(ex(n(������(a�(oS04]�(a�(s�f 4]�(r�:�S�	�(k)n@)p`)rt)s�LiT(�(e)s0T�Z���.�T��)a,)eX[[$)s`�P_��8)l�c��UL)n�U�T)e�h��U�l)a�)l VZ��)n����Z���)s<\(�)d@=���)��)��\i�\���)e�`�`���)e*n*t�ha���)e��4.b��*e�B	P*e|*i�*k�*l�*m�*o�*t�*v�*�tnPh*at*lx���n�`*ms��IT�x�W���*i��8�"�����*f���*fh��t��`��l~���*�<�O�*s#��?�L+aX+bh+d�+e�+i,k ,ll,n�,o�,p�,r0-s@.t�.v�.�l@�l?��D+r�@(C2�B]`+a�+d�+e<[I0C|+h�C2�E�4E���+m�+n�+r�+s�E�T.�+eT3��ET.`F��JM�+s�Kt(MJ4L���+i,l�M��PiPP��,a4,eH,l�PT.S2�R@,exZ
TZtT,e�,s�Y��\,d�,g�,k�,n�Z��[.�[t�,e�\�d\��,r�8��tg�=�,a�\��,l`�s�^P_Mla��lHa-e-o-u�b�.�e�h2�i�T.i��$-eX-hx-k�-s.t,.y���jP-el�
P���d-�dk��l-�rP��-k�-l�-n�-tm�-e�-j�-n.p.t.v��S�����t���-eL|".Dmtȷ����mq�m~�m$.r�n����p`.l<p��4.ap.e|.g�.s�.u�.øp(<r��qh.m�r�v��u��.t�v]Tw�lw���.�$x%�y��y���.�@� �;@�/aT/dt/e�/g�/i�/l�/n0r0s�0t�0v�X$/l4/rԩ����,���,/e��
��@/p<���H/e���T.dJe�/s�`/n�/r��,�]�/sP��	��T.����/e�/l����%
L�,����/o�/��0����/�h�����.���0e���.80e@0iH0pP0td0v8��$����
�:\0eX�W������$�l0r0���t0a�0e�0rx�T.��KX�-0���0.�����0r�����0a�0eH1i\1u<����0r��K���.1n1r01s<1t��4���.(1ex	�T�����.�����.�2T1sL��8�9	(��		�1e�1f�1k�1l�1nD2r�2t�2v�2y����1n��������1s��$�%���1sP����1e�����a�1e,�.���2e$2n02o���<�;2s���2e���	X��.����82e\2s|�;��T2kt2p|2t�2v(g�����2a��/�����2. �~(�i��(�2at�M,�
�����2r�����2a�2e��lJn�2s3th��t�qt��	43dH3ep3l�3r�3s�3t���@3s��?(3ep8����\3n�`���T3e�������h3a��3g�p����|3e4�������3e�3i�X�3t����O,���3j�3o�3rHtl �Pt4b|4e�4l�4m�4p�4r05s�5t���3a�5b�5c�5d�5e�7h�8ih9kH:l�:m�:oX<pd<r�<s�<t=u�=y�=z>��p0����4e��i@����4e�4ol�il�T.x����4e�4r����4iP�Y�����4s��2�����4ad�n5o5t��i��D
e��8���(5aP5e\5hx5t���H5t�������d5l����l5i�5o��i��M�5h�5o$�
����T.�t 4��5i�8h>��5e�H�p� �m�5o����5b 6d(6i06ll6n7o7rt7š��Mȫ�@6e`6l��i�̱H6s$v��P6ed���ؾ.�6e�6s�6t�X�d�6e�6m�6s�6t�6v�<��t��.d"(��#���0�)��2�6m���6i�6o`�t��x��47b@7eH7oP7tX7u��P�!n��.(7a��2��u�����3\������`7o����h7t�s��
�.�7a8b8ex8hl8i�8m�8n�8o�8p�8s�8t�8vPupu���7e�s��7l�7m�7n8t�u��v
`v���7edx�d	��z9�y��8i88lL8t�{?�{��08s�+pXD8el��T���X8iD���`8l�C��,���8eP�i0�d�x�8m�8s�S(-��H̆h̉8H�M�����8c9d9e49lH9n\9tЕ*ܙ9n$9sl�&<�Mܰ�T���,9l���и��@9ep�i����T9a "�9a�9ex#i:k:o:r :s(:u��Q���9.�9t����9r�Wp��9n�9r�9t:y���h�.�9e�W�]�'.�al#X2�T<r4Bf�R�8:p@:t U��V�]�l:ax:y��	�_��X:r�]��`:i���M�:i,�K�����:ih��:n�e��:t�e���:a;b ;c(;d0;f8;gX;l|;m�;n�;o�;p�;r<sD<v`g��?�h�;d�g��;i�id�i�PnXp
8{W�.d;bl;kt;r�z��@;aX^%�_(�{����;a�;tX�pԇi�F��;sĈ]�;o�;s�;tL�O���d���+�����;a`�J<a<eܔx\�'l��8�]<o(<t����X@���0<r����8<e@��̵��P<r8���<a�<e�<o�<uD�x����|<h�����<c�<s��m���<k����<n�����<e��q�:�04���<sS��B<�=a=y��t�8��?T.4=lX=p�=r�=tPP��,aH=e�u�Pi`�P_��P=lt=o|=r�=s�=�8`t�`��`ta(a���=�laJ�=e�c�b�=s<pZ��@����=a�=p�=sȺ	��z��Z<��`���=� ��>a�>b,?dp?e|?f�?g@h4@iD@k�@lTAm�An�Bo�BpCr�Cs�Dt EuXEv�E���>a�EbtFc|Fd�HePZf�Zg<[h�[ilcj�dk4elpgm,hn\ho�mp�mq�mrts�|tt~u�v��w��y��z����(py8��>r�����>e?o?sP����Ț�|�?l��� ?e@?gT?ä����`?r���H?�p������mh?f��J47�
С���?b�?e�?j�?l�?o�?r�?u@�`F����?v�O�����W.�6�o���?n������@��y���M@e,@l��0�MX�����<@ad@ol@rt@s�@t�@vȬ8������\e����@g��@i�����0����@aAb$Ad�e�@j4Asxu�<\.�@s�@u0�(P����������@r���Ae|�mAu�?
�tغ�,Ao�T.lAntAs@���@Aa|Ae�Ao�Apо(�*��T.�An�Ar�As�At�*h��@����Ai@��d��l�2�
���Ae8���
�.Ba$Be,Bi4Bl<BnhBopBs�Bt�By��x�Bl��'��\����TBe`Bo<��LBb\�(�h�t�8.�Ba�Be�Bk�Bv��2HK�|��BlDf��M��Bn(����Ba`�����.�Bih�������x�JCeCpl�2�n����
ؾ.�aHCbXCdlCe�Cn�Co�Cs�Ct�CvH����VH��PCs����dCa|Cs��(����]�Cs,���'.0�����T.���Cah�X
a�CeX����.q��Ci8����CeDh Di<DjHDkdDtpDu���
������(Dfd�0De���TDo(���������\Dr����Dl����xDa�De�Do�DrEs���	H�e�Di��ȶ���Da���Dn��((��Dk����Da��,�	�����Ej�
4Es@���EdDEgPEt�Dm	XI����<Eo�dX�JlEbtEl|Es<��l <�D���E��m�EaFe0FlTFohFy�".\���Ee\P�El�En����
e��7.��	�Eaex
�Et����FgpFn`X$Fi�"7.�"<Fnd!�HFe��?�.`Ff 4h>�Fa�Fe�Gi�Go�Gr�Gs�Hy�H�,?J>�Fd�Fg�Fk�Fr�?D@($�"	C���FmdK-(KJ�Fe�H�Fi��k0GlhGm|Gr�Gslb(�M"GpPM��$GaDGoTGs���/O!LGvxP�(P��`GiV;�S]tGstX��VM�Gu]��[�Ge�j�\h�Gm�oW�m�Ge�Gu�T�sJ�GnPtHlt��Ga8HkXHtu��t�Ha����w�$Hpx��,Haܺ�dz��DHb0zLHalHetHr�z�	p{܁-��|Hbȃ_�Hr��y�����H��H�p�&���<����Hr�0IaPIbJcJd�Je�Jf�Jg�Jh(Ki�Kj�KkPMl(PmDQn<So�Sp�Sr�Vs�Xt�Yu�Yv(Zy8Z�܅�@IkHIl؆+�] �/�Ia�Ie�Il�Io�Ir�Iu�IyJ�XxX�PxIr�Is�It	�		���D��p��
�In�3�$]�I.d'�
@.	���ItD��|u�
�u���I�P�̌���.8Ja@JdPJe|Ji�Jr�Jy���\�;h�A���HJ.dJklJl؍��H�tJa�O������.�Jr�
��-�Jf�Jt|���jԖJ�Je�Jg�Jl��UP�[�2ܚ-KaTya�x��Ks�s�Kv�JPKddKexKg�Kk�Kn�Kr�Ks�Ktl�x4����e�AXKn|�i��]pK.�Ke�Kr���.�Kn��pН�����"��x�J�'.�Kt��*$�~�-L��d����Ka4Le<LiDLjLLk�Ll�Ln�Lo�Lr�LsMt Mu(Mv8M������������`La|Le�Lv�#lLn $(Х(ܥtLl�+�`�P�Ly/x@����Li|���Ld�Ln�Ls�2��6�:���\�tMiMrĨ���Lt���xI������Ȫ����t���HM�0M����ȫ��T.�Ma�Me\Ni�NlOsdOt�Ou�Ov�Oy�OÌ^x��"�Md�Mg�Mn�Mu���
(����Me,bn
�����Msf������MdlfNiNk,Nl8Nm@NsLNv0�Nde\p�H��	�r�`fJ$NeT��T���Lt���P��"TNd|Nn�Ns�Nv�������tNj�Nt܉X��$�����(�p���Na�Ne�Ni�̱�Ni��k�Nrp�B���Nf0��	<��Ng<�!(Oa4OeHOiXOp������ Ot(�2@Osx���������XPOo����Oa�Oe�Or�wY�*xOa�����Ol̵2 �����D����Ok8�i���Oe���������P��O��O�@��PpPy����(Ja`�
�������� Pa`PbhPexPi�Pj�Pn�PoQpQrQsQu,Q�
������pPe<���.���Pa�T.�Pg�Pk�Pl�Pn�Ps��H��
d��i�]�Po���$�i4���Pe��н����ܾ̾��<Q�$Q��d����.xQa�Qd�Qe Rg,Ri4RoHRs�RtSuS�@��Qk��`� ����Q.���Qn�t�Qep���Qb�d�Ql�Qm�Qt�Qvlx<��d"��)ؾ0�'nR.��Rn��tRe�����@Rm�(��;`RilRt�L(H�2XRn��94�=V��tRt����|Rn���Ra�Ri�Ro�Rr�Rs�RuS�HD��2�Rf�Rt<L`��t��X\T��]]���R�(���X����S�,S���d�]�4Sb\SgdSltSp�Sr�Sv��_���h�c,��lSp���@�(�����Se��4��Sk�Sp��Sa�Se�Si�Sl�Su��T������j�x�]8Ta�d�TehUi�Uk�Un�UoVsP7t4VuXVvtV�p��4�"0Tm`TntTr�Ts�Tt�Tv��{
����XTt���X���lTe�Tk��
��p�����Tt������Ted��(�c���Ta�Td�TfUgUpUsTUt���Td\�X$�%P����
����Ui,Ut��<U.DUex�������xLUt`���`Uf|Ul�UnH��x��
�U�b����U����h���e@����J�Ub�Ug�Us�UtVvt��4�c�Ue�:�.`��l�P�I���Vn$Vp��<�i��J,VnHVpPVsT��(��������l�q
`Vr8����V�hV�,����M�V.�Va�Vc�Ve$WhLWiXWj`Wk�Wl�Wo�Wp�WttXu�Xv�Xy�X���4���B����VgWrD���������Ws���0WpX�MWo��J4�;<�]8Wt��@Ws\��|�dpW.xWe����P�y,��We������Wl�Wr���x����X	�W.�WeXiXj Xo<XrHXuPXy`X��������W.Xm4����x��\�L�����(X���0X�T���t�	����XX�����lXp���H�`�������X�����
�.�Xa�XeTYi\YjdYolYrtYu|Yy�Yä���XkL�*l�a���YaYg(Yk4Yl<YnDYpLYs��cD�x��$��� Yk����$�%p��������t��,,������������Y��Y����-�Yr�Ys���@����-�YaZiZn Zu�|W.<����Ye����Yn����\(eZk��20�9��iL�>HC,��HZ�0Z��I$�Z���O��\Zr@/�dZa4/�pZr /-|Za�.��Zr����Z�@0��Ze�Zi([j4[o�8�"l�Ji�Zn�LZ��V�c���ZhX��Zn����ZeQ][rQ��[��O[ìY2�sh[a�[e�[u�[�Ty��x��T[s�s�\[vT�\�z�t[s�z�|[m�y���[i�������[a����[g�xT����[r����[���D\a�\b�\d]e\]f�]gh^hp^ix^j�^k�^l,_m�_n�`oap4arTasxbt�bu�bvXc�t�tT���<\kl\l�\r�\s�\t�\v �`����d\g|\i0�ip��\e����M�\p�\t<�eX�s��id�M�\s�����=	����\iЕ���\r���
Ě���\lܙ]]l(]n@]rH]sl��'.8]d��HT�<��t��p]e,�]P]f�]u�]Øt,	,x]ld�nl����]��2u����]d���]n�]tx�]�]a�]e^g^j^lH^r`^u ���{P��]s(�>̡�ءM�2a$^e,R�.�_���]0^n��t��K8^eX^u$a�] �,��%H�-$���.�]�^a�^e�^j�^n�^o�^r�^v<���^mp���0�T�� ��ثal��T����^i_l��"<�_sܰ_e���\���$_aP_e�_o�_p�_�@�xl�H_dh_lp_sx_t���@�ȶ�t�����,������_�$�и��	�_a�_d�_e�_g\`ih`np`ox`v�`à��,����W�_nQ���t�_k`r4`sP`Ô�axd��*`n@`r̾d$`e�d�����H`�,�KT.���`�������8����`����<���`g�`l�`n�`r�`t��|L�2\�]�`a�`o��(�a�����`i����]�`r��Jal�������adDag��]$aeLas4��[
����	�.�ac�ae�ai�akd�s(btHbv\by��tH����ad��P�ae�_��P�ae��]�anl��aa�ajblbo buD��X���am��4��d���'.beP3���
��M<bi�����4bk��/Tbi�������(p�dbl����lba�be�btD�
�bkp�t�((��bt��t�be�br��| ���X�.<�M�bs,�WT.clcnT����ba(ce4ci<cu��������� ck����������Dck����Lc�������dca�ce di4do\du�d�\����.�cn�cr�cs�cv�����i���ce�����cv����H!.���t�M�ce�&
���cn,dr��t�caX���dg���dn�&
��$�-,�<d.��Dda���Pdlldp��
�da�
D_xdr�����]�d.����d� i�da�di�djeo(eu`�.���da����dr�� !^	08�X2���drTT.�S��ee�R��el�]6Tea�ee�fi�fogy@gÈ]2��g�en�et�evLa�Tales`atteaa���ed4e.�d���ee\f���m���ei�k
�edT�ffg@fiTfk`fl|fm�fn�fr�fv�nfr�n���ea fe�3�oH�����.�pA(frhp��4fe|q��q��Lfa�rJ8�	(tthftds��pfs0u�Qt�t���fe�v����u�}���Yn�}M�fa<�i�ff�fk؄
\�i�]�feL�i$�P.п]�fe���gs��.����ga���(gp�~��Pg�4g����dgr���d\ge���ge�goh� ��.��]�ga���gl��g��ge�gr�gs0��`����gs���X��.d���ge<���gr���� h��g��TQa,�hlL�<haPho��HC��e<BDhd�e�ha�hb idPie\if�ig�ii�ik0jl�jm�kn�kp(lrms�mt�mv�m�tg�
�e]�hv�g�ha�he�hli�|gh��h���hn�h��ha�i �i��i� j��iia<i�4l�`?rHl��0i���plHiuPnJliftir<on�o&@�TV|id�is V�ieXp]�im�ir@���r�t��2�in<v���iijkjn\'����ie4w@w�inLw]ja8x/�|pHjd�z��$jelji�jj�jlP}�`>��O.��Pje|jj�~"\jn�js��5��x��E܄l�"�js����je�ji�jl�jo�jpkrks�ku��l��js��=�����C؆G0���tka4ke<kiDkkLklxko<����Oh��L��lka��"@���Xka@���`kg���$�Ĉ]hOa�kg�kk�ks�kt�O��d��ke�kl��M�iL�t�kt��	��J�kaleli��"T.@�T.0�Rܔ�`ln`���lahld<e�lg�li�lm�{o�lp�lr�ls�lu�ly|��0�T�Ya<��,tlt��2|ls���le���<��le��`��
l�Zt���lk\�+<����le�`�d��(�le8�]�.,meDmkLmlTmo\mpdmt�T.<mt�{(L��il�o����dpmrܧ.	�Cp�]xmr��J��a�me�mn|�3	@����mn0<r��Դ���E�̵���i8�na�oe|qi�rosu\sy�s�������.8na@ndXng�nk�nm�nn�np�nrHos\ot|ou���4���h�.Pne|����ulne����dnnĪ`�"xns$����ne�nt��{h����������ng�	�����n.�na�ne�npt����c`�����dZaob$oe0om8or���0�h��ot������
�������@oa������Tot�V+���hoa���pom��q
����
�o.�oa�od(pghpi�pj�pk�pl�pm�pn�prqt\qvĈ������os(�]�os��dpepr ps���.prD����(��4���.DpePpj��h2<p.�OR$�tpn|pr���Xpe4��H����'.�pnh���K�������pa�pk�pt��{���pix���"�pg�H��	�	�
��ؾ.qn$qr,qs4qt$��D�L�|�@qs�r��M
���Hqt`MPqelqn0�������tqa�qf�qg�qkrlrn0rtprv`xT���ؾ.�qa�qe�qk�qs�qt���.<Rd2�r��(	�q
rgD�����!trr(!��$rt@ru�!{drn�4.5tLre�!Xrg"�	�9� 9]xrn04���rn�rp�rr�rs�rt�rv�9".�9���ra�rs,��t:q
�:��;]40a�<q
�rase�<i�<2SJ(sk0sl8sm@spHstT�XT��T�	P_*|V�
dZ�lskZ��Psktssh��p[�h]1<\��|sm�sr�st�sv@=���s��s�p^�. ^��se�^H$_2�^���se�`8ie`���sd�sp|a�B�Pta�ub�uewi�wjxk�xl`ym�yn�yo�yp$zs0zt�{u|vt|y�|�4b���'.�tb�td�tg�tk�tl0um<unPurXushut�b�b���tlc�teȚ�@c�tl�c��ta�c�.le���texd���ts�e�uauf ulLf$fu.(��
��Blg� g�(ue�g�Hue|h%�i��jJ��k�`uf�1Zd1�	tun ��|u��l�
�u�tn
�'.�ud�uf�ui�ukvl4vm`vn�vp�vr�vs�vtwv`oxHp+q"�r�r���usTs0s]va(vs�;pt6 vatu�@vi�ua\v<dvtHvatvev��Pvd�vg�vsxv0.�v�wC�w�	x��H!.�vf�vk�xI y��z[
z���vk�vt|{�|O�{���va8le�vj�|'�}+�(wdlwl|wn�wr�ws�wv��n<waXwedwr���(�U��\Dwt܂Lwn�c���������twd��n,��Ԉ������we`�8ԉ��wn��we�wo�wu�i�wrD�
��t�3	����wb,xn��r4xtT����wa<xj�xo�xr�xv`�8�����_Txetx�P"����Lxn`#nL�]`xt4���hx�d��xth����n����xop�
��8�xa�xeyiyoLyu��������x.�xn����,��xm��"x�yk(���.8yt�v8��$y.H�],yt̜(h��Dyk̞2��MXyatyoh�Ä���2��M|ye�yox�i����ym�ys�yt�q	���ytܦ���ysX�����@���yezizy��H��ykzl��~���0������zt�	dza�ze{iL{j\{op{r�{u�{v�{yT�*����\zk�zl�zr�zt�zux�8����(	��ظ@�JH��z.�zi�zm{o���\���zk��.0�J�za�zpt�����|����{g${k@{l�����,{i���4{l|��������T{rD����h{iZ�����|{a�{dX����{n�4�����5���{e�{v�������{r�{sD��(����{e���|kdk[t��(|a<|e`|i�|���Ye����|n�������4|t����.��]H|a����T|k����l|k�|n�|th�n�|a\�t4��4�"�|rl~���|��|���Y`��<�	�|aH}e�}i�}j�}o�}r~s$~v@~����}l4}sp����
.8���}a(}e��h
T.`�xx�*�xa\�<}ml}n�}r�}s�}t|������d}gl�P�x}e����}l��$ye���2�Z�
��(	����}g��}a�}o~�85�P���}c�!(	�!��~�#�P7
47~at;�P~k`~r����l~�0~��;��<�@<JX~s>�?�~b�~c�~ef g<iTkhl�m�n�o�p�r4�s��tȀvXA��@���~b�~lBf�BM�E��+es4E���~n�+s�E�hG��F��oH*,u@J��K��J��4t�L��.4L��Ha�R�PP��`lxy�U�	�X|�V���s�Y��|ga�d�i�sTZtT,e�[�t]��t�Q��]���a�^����P_���kla*lHa�e(�t�b�. �ixc�g��aiM	`�ap�e|�i��k��l��p��s��t��v�h@t���i"h�m�j�dk]l��lmm6�mhom<p"z��.$xM��a؀eXx���.@��aL�e��ol{���
.x{����a,�ez���l8�n�{�	�{"$�l�|���e�p����D�sd�v���̌M\�at�e�����.H�
��r�� �@�
́a܁b�f��k,�l@�n|�p��r4�s��t�\p	�
`�Mԁa��-t�]�'.�t�e�l �� �5�[�����,�J\�T.h���4�a�ueT�kH�S	`�s�7���%
��&h�sȺ��p�e��rP<ĻT.�����aԂe�l�s�u(��X�s����a����ȂboR.�n���e�)�g�)��eľ�Ŀ��i,��� ����]4�.P�aX�ed�st�t��8��.D�>�����l�r0�����.<����r`��<�����ȃ����������s(���8.�d�g$�k@�m\�pp�r��s��t��v�y��d�����s�������rP�d0�n�����h���8�a��T.h�n����L�e��(��J|�i$�,�d@�.��tD�z���d�t�����āe؄i�l�*����ĄlL�d���e��.��������.�a �g(�s���������\�st���0�dp�n|�p��r��sЅt8�p�.����d�a���t���.������a��iX�i�Xؾ.�iaąe��4���m,��4�. �]��b��d��f̆g؆k�lx�m��n�p,�r��sԈt|�u��v��܅a �bP�čd��e��fԖgܚh�i�jd�kȫl��md�n�o�px�r��s��t�u��v@�wL�yz,ì�����i��l؛�f���
	��i����]С��Ću���r��s�t8���{
�����te0�����.0�lD�oX�tl��L��.��$�e��(���<�v��RĻP�eԽJ���d��@���a��nо	���nH��8��.će܇i�n�o�s��T.ԇtt��
�W��>��((��r�t@�.�l��ax�$�el����H�eh�i|�k��m�Ct��]X�r`�t`�������������.��p�a�.$���a�8�����i��pĈsp�N�������̈a�e<�hL�lX�oh�rt�t�(	(���l(�r4�td����nh���e���$���t�GD�e��-���,�`�ix��@���g��r؉s��t������a��e�.@�.L�����aЉep�t����a�e����i".X��l|��l��a�e�m
X�at�b��e4�hD�lp�o�s�uD�y�u�@�\PP�d�En�
2x.D
h�e\�(����ex
��d��i�n�r�s(�t�A(fr@Z��e@O$t̊k���Ԋs��*
p��s������u�_�k`���	 �sH� �`X<�o\�ð � ��T���!d!�h�b��d��eċk؋lL"ia�m�\�.�"]��s�"��r�#� #����r8{"$�Ћa$*OF��+�e �n�+���r(�t+��eh���Ftt0��0��0�a�.8�t 4]l�at�c|�e��h��o�3�5�5�7N	�7����a|;"�:����m,?J�'.>��d�g�rh>��
��a\�d��e�i|�l��o�rl�s��tؐu�v�y̎Ì?��st�^CJ,�k4�v���
�C�
$G'0G��<�a�FD�l|FP�et�y��(�Hl�r�H
h�.��g�Fi؍k�l�n0�pD�rp�s��t�J-�JJ��eȍn����L��K��Ѝs�M.PM���e�$g	DQ����f�g �t R3�RO@�9�S(�r8T��S]<�a\��hV�	tV��T��`W��VMh�k��l��m��n��p�W���$���W?�X��X����aĎt0�/����d���t��D\��n�[�a̛e$�g0�sP�v������e^��]]�jTa�@�iH�k�a��a��b� f.f��X�e�ed�g4e6p�e�h(\h��bďkЏl؏p�v`yP.Dy����e�i����s0j(�k"�m"�mK�a�e4�oH�yn](p*�o���g �v\qM��.88*�r��,�m��
\s��@�g@��x��T�i��rt�\�k��l��m�xE�x�	�x8��a`y�|��|��a��eȐrH}2�}�l?t~Аa�
��a�i��Z������s������k4�nX�rT��@���,�k0��.@�@�e����L�k���p�Jȃ�l�r����aȑbБd��g(�iT�k��l��m��n�p�rh�s��t��v܅� �~̌]ܑi�2�G�P��aԖ���g�n�Ä�Z��3	���������PKd@�n������8�i�`d���L�el�t ���d�e���ȫ��x�a��i�����a������nd�xȒbؒi�k��t��P�/2��Вn$�"�U��l4�Zx����a,�e8�i@�lT�m`�2��$�n��2��C��2T.�H�eX�_����`�hx�k|�����a��e��".��x��M�$n$M�aX�e|�f��i(�j0�ll�n��o��r��s�tP�ul�y����W�l�n8�r\d����s|�����a(�ki��
�*0�kH�s0o�	t�	P�ah�f`
y�2��k�]p�e��oP	$2�W��d��gДl�r�s����,~���Ȕm���ܔn��e���'��nh]�e���k����L�e`��d8�e$@�g�����X����h�.\��t�i��b��n�n�`o#��']�'��jЕkؕp�tT�]@�����@(��rt(��a �eD�lh�s�(]�r,)�H),�i0�r�)���gl*8�e,�ľ�.XX�s�-`�r�.����x������.�	��l��y/i/����e���/"�f�l(�m<�v@0����a`�d��eP�g��i�lh�m��n̙o�r<�s���X1*�1�1���a42��6"�.�6��0�e8�7H�ll�s`7�P�ep8��:�0:t�o�8|�d��iԗl�m�n�o(�s�;��;J��e$=��<��th<��ȗe>J�e��@O�k�>]�s�M��@��@i�mtC�4�pDD�H�	�G<�nHGD�ed�lt�u8I�Lp�4Jl�t��2K����n�J��a��e��kИshKmT��L]��ohN��M��Șp�Q�8Q�ܘd`Q���a�e@�iL�y\�ÀR�,R��d,�syhdS��$�a8T(	�S8�t�U
�U��U��T�� VU��Wp�g�VMx�a��e��o��Ì(tWP��m�Xt�Y"�Y������YWT.ܙi�Z�]P�a��t�r�]���s@]����n�a�a����Hc��bX(�mdb#0�aX�kl�t�f�	�e��P�i�h��h��d�a8r���yG�����x���rlJntr����e�r�	��v�psM��aКe`s���.�sM��e\�o��y����ye�y���a�i$�r�z�z��eL}���'.<�eD�nL�s�}��}�t~`�Ld�xT�gl�v,�������.����t�e��Z��r���������aT�����v���ț����0�t@�.T�Z<�kH�nX�r����ԛad�cl�d�ep�f��gp^i��k��l0�m�n��o��p��r�s$�tԣv��t���4�t���
���p���P�.H�xЕ��.��a��e�f�g�o��r�uC���"��r��xX���aȜoМr؜tD��d��	�X�PZ�
����
������ܙA$�d,�g4�nH�r`�s$��H��l��X�.H��T���@�aX�t ��<����,�]h�l�x�]|�.��a��e��iНrܝu��2P����v`FUp�i��
��Kȝa�t��$���a�]�a�el�r|�s�*��.8�e@�hH�lX�rd�t
��
�P�(̦�����P�i�( ���������t�a��e��o��t��T�(��e�l��o(�sm����d؞g�oET���Оg���ܰ�u����h�.�a�p �s8��	�p�����\�(H�at�e��o��p��W��.\�gd�l����@�(l�l�d��i��n�is��t����ȶ���t0��t�t������$��ğl�n<�rи��
̟aH�dd�e|�f��g��i��kh`nРo��sP�ud�vt�ô�]�s,�t���
h���4�e��T�up��ػ���W\�dt�l��T������t��a`r,�W��t���0�Ġa�
�4��r`�"P6m�v0G%|L\(�2�d���i�l �o0�pD�t0�|��Z�����
(�i4�����<�o��\�m0^*���"8���l����i<����r�����a��e��s��@�2������a�e�i�s$��ءk�����n(�2��;��J8�aD�e��h��i��k�p�i���0�r��Wh�.\�id�n��rd��	��*yD�x"l�e���t�i`�(p�M��a������l�l�����.Ԣeܢj�o����-d�2H��	8���e�ix�(p�(D��kL�r�����ed�ot�t��u��v��������D�a\�e����]��
��tl�a�6����t�7*47��e�����������(,�W��k�rT���ģa��e�i�s��\(����n�2�����M(�o@�u���:e���:a���4�l�W��lؤm�r�v ��L�a�e��h��i��j��k`�l�n|�o�rĨs�t��uȪvL�wT�yt��Ȥeh����i�����g��Фs`�.���a�2�
J��r�
JDe��i@�rL�s\�th�y�H���8�oH(����T�a��,�8d�xp�v x�o�T. !>l#�ܥe��i�j(�l8�oP�uX�y\%
<%��ȥe�$Хlx � ���t'�sd'���$#S	#�����'��4��(��0�kH�vp)�l+t�+*
 ,K��a��eĦuԦÈaCa��x�eH,����n��,��g��n��t�-YT��.*|/J�/��̦����/J�0�a �e@�iL�o`�up�ð0X�.���0�k0�s(1�@1PL18�p�12X�k�1��1n2�2��h��X2�T.��l��rاt�vd5V@5����e�8�p8���s08����d$;.0;]̧e�;(T<\�a�ep�ix�o��y��À<nh=�x=���d8�f@�nP�th�v��>����>��H�.`�s�>a`h�>n�?n$A��A���d�A������Anxd�	B��k�l�m4B����a�e �i,�k@�p\�tĩØB(�BupCnC��l�mtu	ă��D�glEM8�a\E(�G
L�e�GihH(�H��T�a��e��i��o�����Ix�l`Ia��lXI|����IoJ������J(�J�����$��
L��ЩmTK��ةr�v K�a �eX�i|�o��rpL��L%
�L�f4�p<�s$���M�H�t4NY�(xNP�dl�mt�v��O�4O���rl��P��a�O����i4P�@P��a�RelW��a�e�ihW(HW��ܪl�r|W��@�.�WY�d�W�����X���k4�n<�sD�v�X(YY�Y�Yi`�tL[J�Ae�[*������l������[��\��Ap�]"	(�g@�kp�l��m��n��rȬs�t�v�]����a�3b �c4�d��e��fدg�i0�j8�k��l̲m�n�o�p�r<�s��tD�u�v��y���,_��`Ba8�g$_�
�_"T�e\�sh�t`',`��(�eD`��`"�`��`��x�ea��p�l`���b����g�r��tL�h�c���id��\�.جe4d��d���Be�o	�l�\fJ��l�s�f��7��g�h�ii�gt,�o\�r|�s|j�Pj��H�rjP�ePk�8k8h�e�j�p�lm��eiԭr�k
��dܭe�g0�iH�k`flT�m��n�p�rT�sh�t��v�mCn�`��n���d�e�nXo�oR�rx����M�aTq�hp��(�r@�shq"�q����edsJp�.��a��e��m��pT(	T��\s�x�t8�3�s���n��t�s�
���t6�t�ԮaD�hܮs�t�t��u����u�uXv�<v���a�vi �a4�e@�g�v".�w�Dw',�t���
z2`�u�x��H�kPz�{��̵ax�eX|4���}M��.��a��n�(�}����kp~�li��i(�X�.�]̯a�ePi�sH��G���e����l��<�"<�eh�fx�g��nȰo�t$�v���	\�r�����H�r����P�e؄�����np�r����d��e��s��it�W��s�������p0�S����sp�.���԰a��e�t��(؋�tČ�(��4��n����e\���T�ah�e��n`��P�2L�l`����P`�s��t;���x�e�������x.ır����a̱e<�i`�s��u����b���d�g�i�r�s�t��(���e���gX|)T����e$��n.`��	$�a0�0�gL�s����0�j(�
8��X�a|�k��l��p��v����h�^	��/L��������r����������$�IJoܲu4�i��L�"�k(�mT�p\�rx�s��t��v v�.�����a`�J<�mD�nL�s�����
���P�"��"h�tl����i �]p�t��������.�����sp�����r����eȳn��"	��(<�гtؤسe�hȥ�X�?(�����s$�E�o��(h�e���ip�nx�s���	(�e��i��j��k�l��n�p<�tl��pq]<�*T�
ܩ2t�X\���e0������l̴n�r������a����3	Ĵd����شe@��x���M�o��Wh�X�e �r���x�(<�(�n���0�eP�iX�j��W|�(��J|�k�������`�������u�����e��"��p����lȯ��a̵el�o �r4�uH��'.�g�r�tD��H�����o�?
����tس(,�p�:�o�����]��*����
<�at�e��h��k��m��nĶpܶr�s�td�!�J�xJM|�eض*T�m����eķ����g��.\��жe�_.�������������r�2���n����a8�eh�rp�s���\�sh���0�dL�i\�r��(�̻��T�ed��|�=��.Ģ�.����|�e�����r@�������Wȷeܷnt��.`�;��t���<���Էd�q �r8�t�~�������@��h�U@��s����e0�v����
��q
P�r`�y��!�'����X�.x�a��d��n����.d�'��q
��r��t��q
`������T.�a��b�d�e��i<�j`�l��m��n�o4�pнst�t��u��yܾ���J��
��dH�fT�gl�ht�k��lԹm�n�r(�sT�th�u|�v��5L�-@�o����Ked�r��:��-(����Ke��o��t��u���0���i��p�".<�����e̹j�@�(h��(���ܹaLi��u0�)��������e�m��9���<��� �i@�k��x����8�u��GX�tL�a�����J`�k��p��Jt�e�M\����lL�P��l,���a��e��	x���dԺrܺt�4�Ul�;��'�i$�l8�n\�t|�v������e�����]�i�G
����0�d0��p�s��wD�id���P�rX x,�8h�k�����Bh���b��e̻gԻn�r(�s ��p�����t��]��r�������i�o,�i��s�1t�����t�D`�"�r��
������� �a��\����4�eL�uX��$�il�s���T�ep�ZT��'.��n4��t�e��i���`�.�?���dļeؼi(�T.X�d��m��D��]�l��]�o��n��p�v�����(l��dP�r��$�e\�l��oT�����H�i����a��i��a��il�.��t�e������n(�
D����l�s��(8������s�tH�t��e�j�l�n,�o8�tL�uH���T&e�����uL�6���(�$�r,��D�eP��@��p��8���T�a�2\�l�h�a��iS����l�*l���g��k�i�iľk4�,<�
�r�y������̾�d����(�n���a0�s,��l8�ra�,��pn�"��kȿl�n$�rP�sp�t|�vL��@�a��b�d�e��g��h��i$�kD�n��o8�px�r��s��t(�uH�vP�wX�y���<"�������dܿi�l�i��
x������d�e�g�tt0g���
�	��p�.8�e@�r
'��
��z ��H�s�� �\�p���d�s����i<ya,
��e���
����a�
��nx���A����d��n�t�a��el�lt�r��s������k�lD�mP�r`�s|�0���O�h^s�O��$��xP��J<�i n�W5�MX�l��\���o��a���tD���r������p����eX�{P�E�aJ�g\�g�i$�k<�mD�n`�rt�s��v�G��JDeX��J�s4�td����<iP�g��h����X�g� ����l�k�#���e�#��#����n<&���r�%t��a��e�l0�r@�s��u��ô&�>(\'��m��r�A��'n��i�u�'��(�(�st(M�edS(|)T�*��*#8�eX�o`�pp�t�gi,h^	lj��+��h�iLp(,|�t���,�����h���,_��j�,T.��e��f��r���t.����kL.]��r�.��0�xt5���a0�i�4�rD4]�e����d�lx�8�al�e��i��o��s��Ð:��;���m��s�<��<��|�t��v"?d=�>��?��>t��j��t4@���aBIB�����<B"�g�k�m�p,�r�C��C2DD���.�D"�DB	�D��$�d\G�X��(���G��D�s�G��L���(H2d�m�Gml�e�b�I����b��f��k�Ht	��a��eH�i��l��o��t@�uT�vx�Ðci���e���rLI����e|Jt�k�l,�m8�s@�u�sHK]�i�u�t�J$�b�K'<��L2p�d��e��mhL�pL\�.|L��d�e�L����t�L]|�rd�(D���O��g��vdO��e\�?L��ب���t�P����p�Q�a�e$�ix�[�Q����l4�(�R�r�R�h���S��,�d�S4�nT��SXL�ed�iHT��TJ��k�T�����l����* U
�U���'.��p<U��a��e��i`�ot�r��u�V�'.��c��g��l���W(�W(\Y2�e�g �k0�m�Y�<���Y���(Z��(�aH�e���Z@�n�Zg
l�i�Z��T�r��0[��a��e��o���X[(��n��[3	�[����l��p8: ����i�"88\������\/�\����l�n �s�4.5t��e�\��g6	$]"8�l@�r^+l^�T_�`,$`T.t�b|�r��s��t`�-4aha�$���a����ab���mD$������������Tcxhb���k��v��y di`��<d���d�d��s|e�@�.�e
T�ad�b|�d��g��i��k��l��m��n,�p��r��s��u�e�h��g��\�et�l�hR�i���dPj.dj��eXp��r�r��t<v]��sDy*�z]��o������$sĈ]<�d�e�k$�t@�id��
�l�����:
���H�aP�lX�oh�pt�r���x�Sؒ����]`�r��0���Ya`���|�d��e��i��o\��<�"��d��e��k��s�(��S����K��W8���l���̵4�aT�e��i�lp�o��p��s�t�����,�t��)p���@�o��H�nt�p��r��s��t��u$���������|�r��%
�]t0��L�����a,�l��������d��k�s$���.$�]��a��r ���������o��$�eX�<�e��nD�rL�sT�t0u�x�����{	�mt`�\�t��d�e��k���������el��]��stu����m��s�]��e��l��t�u�yz'h�����u���8�]��n��i�n<��i,�r����"��b��d�e�f,�g4�k\�lp�m��n�pX�r��s�t@�ud�v8���4�a��b �d��ep�f��gD�h��i��j��k��l�mh�n��ot�p|�r��s��t��u��v�y8����	������b,��.4�����a��5��m�l�J$�tx���g
$�"D�eL�l`�h
X����,���T�uX��h���h�a@�=��|�.������a��d��e��l��o �(���.8�t��eh���h����i(�����.�r������e(�i4�oD�p����%� �nD�,T��`�]<�e��(	����P�.h�g<�2|h8��tp�e���x�n��r������a��j��k��o��s��t�i�
��@d���el��8����
����0����k�����e$�o0�r8�t��	�rt�G����JP�k\�s<�M(�eL�X�Jh�.��h��l��n��r��sD��
��S�Y��_ �h�.��i��o�y��|����i��P��tH�La�eT����d�/-�.��lT��<�dL�n���e`�il�r��s��vH�(,�d���D�s(�(4�X�sH�K|�a��e,�����<x������j����k��t����o,�(��g�8�i��a����$�fP�gd�i��k��l�m`�n|�o��r��s��t�v�i��M�f4�t`�oh2<�n4��D�e�i���\�dt�e$v�"���|�a��e��s��t�.�8�;���e�i�����e��i�l�s���"��t<������iP|T��J�.0�m8�nT�`���
@�n tH�e���T�dt�gD�h����	�.��f��i�	�	�	���|
����s�
x�.��a��h��o��s��tp
,���8pL�|$
@qsLi`M�a$�e,�n�[
0��	$�e@=��,��L���l����*P�i�X�rhd�a��i��r$#�����e��i��l��o�r4�s�=jh<����v���l�K�P��f�S������i(T.x����tXK�e���� �o�#(�k`�p��t��8i�.@��P�eL��\�rX��h�����a��b�c�d8�kH�ld�mx�n��o��s��t��v �������d��kt������=	��b��l��|�������e�J�.�n����	(��$�t�],�a�^n�����P�.���X�a����p�d��g4�n��sd�tD�
��a��s�&�����r,etdd��i�o(��Dd��i���r����.�a �e(�o0�p8�t��ä��2���8��� ���W.T�a\�el�ix�o� �� �2�2d�l� H!�!�����!�(!����e��o�!��"�"����o`#JH!.L#���s�"M��e�#��#��a�e��l��n��o��r4$�<�rP�sh�t4"�$�(�e�$��0�i��%��H�a`�e%�X%��x�ex�tL/T&�`&�����&��è&��&��'��)���en�)��a��e�)�g��s<*�(*����t,2+��e0�o<�s\��|�s.���e|-$�d�-�.�TQa�.�D�l�.��P��/�|�a��e��oP/20�d�(��e@0��d��e��r��s ���<1;1����oD1�8�t2]��b<2u04J0�a@�bX�eh�ft�g��k��l��m��n��p�r4�s`�tl�v 4n�h��4��8�lP�ri��5y�5n�5��`�f6�47��6��|�sh7���y8088���k��o�8�8p 9���d��oԉ��9�9�.��a�e�9���
$:�nt:�p�.$�e�:�:�	�:],�aH�jP�l�c�:����;]X�a�<X=��>���a��e��i?2��e���?(0?Z��l��4]��n�@2��eB(�k4�l�A���aD�eP�jX�k��l��m�n�pD�t`�uh�v���@B�0B* �aPBt�w�
LC<�pHD�Dh�a��o�D*x�.��r�o2�T����n��r�D���a��%�E3	�E����r�FK��a��e���p��F��d$��GM��i@G�LGM�e�G-(I3I���s�H$�nP�rX�v|H�0�e0I�HI9xJ�J?x�a��e�J(�J�`����r\K������E�Kd��r|K��a��e�iT�ot�r��s��y���p��4L����e�MKXL��sN�$N�a(�b0�e8�lH�m�\ZlN�N� P���@�o`O��x�p���P��`�l�O:h�o�P���a�P2�pXR����e�>���n�>����e�R����r�R�����SJ�a�d�i$�k,�m<�nT�p`�r(�st�t��v�R��SVT�T[�T�	(���T��4�gL�iDU`P_��ؾ.�U&l�e�U�|V���'.��a��eH�n��o��slV(	�Vf(Wl�uqxW�����X����ePX����l�W��e��i��s��XYY�Y(�Y�����ZK(�a0�e<�rD�s�Yw�,hn["p[z�[�`�r�[|�[��X�e<\q
��k��l��n��r��y@]�]����n��o��`]��]3	��n�]���� ^���a��s�^�|_�\_����a�e�n�s�_�d�x�_�.$�k`�`q
\�.l�d|�e��k��l��n��p��s��t��v4��
����`?d�a�e�`��`x��ex�� a!haq
|a��a��a���ebZ��a�a����B���.4�a�b�eX�h��i\�j|�k,�l��m$�n��o�p��s��t��u��vH�y���4bX�a`�dh�gp�l��m��n��r(bpcJ�c��e���aLf�$f|�.�`�lg��t g���ehi�
�g����tj��i���b�i����a��v�j"	�l�lP��e�l�
��itn"	D�gX�i��k��l��n��r��s$�tD�u�p�lp��<�eq�q��P�dl�ex�gpqA�.�q��e�
I.r*��u���pt�s]��svnx���.��e��i��kxx��x' y�	zf�t�v|{Rx����|��s�{���a8le�vj<�s�|�0}%��Xp�r��ML�a��e��i��opwX�.���|�r �(l���p`�p�tl�����r���a��e��l �n<�s,�vT�����t8�]��r���i�ll��_�
���
�����s��Ż2,�]4�tD�i��H�r�P�o�����h�bT���p�a��e��i �j|�n��o��r��u��v��y����Ў"��d��e��r
ď(���@����l��npА����g�n��
���s��_D�eX������0�.���8�r(��4���P��p��D�
L�]h�nP�d����.��a��t\�"h�x���(����e�iP����t̗*p��
��i����r�
l��P������������r��������$�aP�e,�i��o����(,�H�k`�t��(p�.x�sؾ��|���3	(�����k,�������k��v���������������e̞��k��M��a�e0�(d�����tP���t|�Jġ�.��M�aL�eX�o`�uh�y��ð���D�rx�]����]�Ui���p�e��qx�r��������W��b��d�*f��k��m��r��Q����8�����sܦJ��m\�/�il�����i@��8�aX�e��l��o��uԸ���$�a��,�rH�s(���83	H�P�.l�d|�r������
0���t�m8����ex�5̮i�����l��r����*���nP��fd(�e0�g8�iT�nt�sl�t��v��
��e��j��k��l��o�p,�t��ut�v���D���(q���(�t@�i���H�dd�k�d�������e�}��}-x�a̶�ض��a��eԉX��������r,�`7t�����eL�������m��sX����W��X��a`�;T���t`��lظ� �iH�o`�ud������@�lX�r���������ap�e���|�n�E�.������������A����
��d�e�k(�l<�n`�pp�r|�t��u��v�����a��e��ix�j\�o��rT�u���h�G�LT����t��Rx��� �a4�Y���4�eP�sX�t���`��|�i��`����h�t����o�����8���J��.��s j@��
H�	��.\�g��id�k�l4�md�n��r��sp�]\����e�g�����(�l$�s��g����0�J,�aL�eT�o\�p��n\�t��x�xt�g|�k�u�|�4�����.��k��n����������e����t(��dN5lNm��n����e�f�g�k4�m@�sH�vT�x|�����$�k�S	������,�a8�2�������e����x�g��k��lP�n��r4�iD��
d��p���������dl�i,�������k����a��e,�i@�y��8������e������n�r���h���L����D�D���$�bT��4�d8�k��	X��d�dl�s���6�`����r��s����t���>��\n�>����e,?���e4�h����t$��4���<ue������rt�X��a�e�i��(	������k��������g4�k@�s�`��],�t��i���`�nl�t\��h�]X�a4��L����dt�i4��|�v��yl~�������
�8.0�e8�fL�k`�l��m��n�pP�r��s��t��u��x<�����a��c��e��h��i��j�l�m$�nt�o�r`�s0�t,�u\�v��y��z���h����JD�o���4�*X�oȬ)8�*|�aȾd��e��l��sp�2�w.���$����i��s �Y,�����tl���s���غ����������b��n
<�����d�e�2(����n���$�a,�e<�p�Z����T����]4�e�������H�.d�el�tp�'��X������t�sd�|�e`�����j��	��i��o��td��t�����
��3	�����nD�-�\�"�i$�k0�mP�rp�s��tDH����e@�����ax�J�xa���
�"<�n���D�a`�u<�p\�{��h�i@�l����.��sؾ���	|���������������������"��t<*�"��e��0
�����r(����h����<�e`�i�#?�4�v�/�l�s�/��H�g�iT�n�/�"��e��p��r�t�y�r]��d�����pi�����d���w�a�e4�iH�u4�(	����d�k,�$�e$�f���I8L��,�s�@ ��@�.X�m�T�#_��a��k�l��n�ôiF�i��|�.\#����r(�(��l0&����uȖc.������e<'��a����7g��%�������M��D*JX*�����*�&k`�l��n��p�*t
�a��eT�ix�j��o��r��s�u�v$�yh+(p�e|�t�+h
.�����S	�+����k�+-P,�.��i�l�n�rH�s�,<�B-���t$�S	x-*�k�n����w �t�-����a(�f0�k8�r�ip�+�M<�1.��-�@�k`.�d�a��lX.�p�n����]�/����a�0���e12\&(�1����ih1t��k�t�1W2K�i`2*l2���g�rL5��i�8����e8���s�2�i�2]d3"<�iD�n4��4��P}aT�i(5947�	h�aP7?|�n�7�l7��t�g�8"�:��:�	��r��20;����at;�>��r�>i�>���e�?M
a�f8�gD�hL�k|�l��m��n��p��r@�s\�t��z�F��H,�H�$�aH��,�lxJx4L��la`�et�r�L]l�m�L3	�MPP��VX�Y�H�g��uD_DP_����a��k��Ila]�a�e��i�n$�o8�t��8a�t�bKPd��en�a�eP/��e�.�e�0�p@fN�g:
i]L�i�j�p<p��T�ap�sx�t�uU4v����@�M�a��e��i�j0�nd�ox�r��s�t��u�y,�Àz�pz����rz���d�k�l<�nt�r��t��P{Z�i�z����tl{�x{���al},$}`(�s�|��0�nT�t����}L�a��f�}`�r~��h�a��b��kH�mx~"	�{
(�����fH�s�a�e������d�g�i0�nt�r��s��t��v��I�����l܂Y�g�[�yă���s��~(��(�nH�sX�td�y�O�i��P�y8���b�\���l�.d������.̋������ǎM�.H�?�r������e�n�s���T����e��t������s�T.�����eHt��]��]@�k��$�aH�uؗ�$]
��4�JP�sH���X�m��Wt��p�e��i���<�����.`��P����������v0��p���T�aD����l$��a�i���t����n@��
����k\��r�J$���$���� ���8�a@����b��d��k��n��r�t\p	Pl�l`�Mt�a<���O$��h�����g��k�#t@���a�e0�x.0���i��rd�`��a�2����iu��<��@r`���� �H���(�Kpr|yP/2.�]Xa����dn���a�e0�������n������nt���k�n�t �����t�a�����d�[,���t �M��a�b�d�e�f�g�h�i�k�l�mn o(p0rDsXt`v����	t��<����������H����(���Mh>M��p��ȃ��r��������$M@0M�sM��M M�]M��M\;tL��ed"��e�̵M8�M<j�"�BMPetn�<�M@�M�a�ol{�x{��paz��xlȚ'�.�����eH����rt�i`����8�M�e���r`v����P�dX�fk$nD�p0sDv��`�8r���e|
�<t��`-�h����Ls@���Tea-�]��ln�]��ta���l�r��t8����a�e�o�s��04��F��A��l ��b�c�d�f�g`ipk�lpm�n�p�rts�t@	v���ad	b|	d�	e�f�g�h�i�j�k�l!mno�"p#r�'st(t,u�-v�-y��P�9�����o|���5i|�2.����e�m�I��x���С���a�e i(o0rLsXu���Brtp�xiTY��]�����<o�`��2t�#Dk��0��8�����hr�s�t�u�D\��t�����e���a��0���ak8lDmLo\s�`���P�s���ell�|�������sL� r��,e����(��غ�Tkl�5@���ho��K.8���
|a�d�e�f�gkl$sLt��ux�����������a ��t�t�sXj��+��vP����el�S	\�����hH��,k�4s��@edilo|rh�i��2��(��tyx�J��*
�.�a�e�gid�n0s<tXvdy����2T.���.|Cs��"ls`��e "(!(��"t(E�����j0��$k����X(X���Dlh�Le������8���la�e�l�t`�S��l@	r�����������r��*���l�����a�e	o	r(�T.	rD��h���s��,�:$	eT���i�U,	eX���4	nX	�0�<��P	��p	a\P�Enh>�h�.�	a�	c
dT
e`
fh
gx
h�
i@jPk�l�mP
n�o�
p�
rs�t0u8vd�܅�.�	l�IP�����̌��
a 
e(
r��R��8
a@
i�n|q�	��xT3e���H
n��-Ԗh�dܚMp
j�AtpnDr����
e�
g�
ln4t�7�@�m�
e��]�
d�
e��I�� ���
s��(�
eX8i�����.ed��L��� r$���(e��L��d���Hallto|r�t`�C|��
��<�h ��s�v��elNy(����"�gȫ���a�e�i,lPopt��3	���n�pek<����{̱s�� eDð������<���\k�i����X�.��da�e�i�r$�(̵�k��(�( �:�at�i��k�����a�b
n
o
s0
tD
è�ļ���e��нO��(��
dt�$
i̾)ܾ��<
�d����.p
a�
d,Ri�6s�
t@�t���tx
r�n���
gl*�
e���
l�Tx���	�.�
a8dPe�i�n�r�su\�4�"�
lmstp�����t��(<�$m ��,aHe�����TaUglktn�r��(`�t���|.�s$
�4"�n���e���P�eh��|�k�� h�*�rX��ae����kЎ+�������d�.4i<kDt�y���|����]\idrtu����]��V�� T��lm��
 ������H�;�8����.�p�����a�e�irt(y�y��"T.Ya��p�ly� x��0�tj s�������-��-Le0�(����DnHty,��HZ�X�|@�.0 ���r`���/��.����"��.�$]�a�ePi�j�l$o@r`slt|u�����.b g(k0l@nLrpsxt�u�v�(��	p���������8n�*��b`dheH�p�
t��@��@	� ���		�b�d�e�h�m�rs,tDv
T
�x
?�J`��8��i�
���de�(PrJ�%���$idYoL�8-<e���b|c�d�e�n�p�sh�t��te�Jl�����n$�����n�t����J�]�2�g���aei���7a�$g�2]0l��X$�#X8ePot&(�'Xlt(E@-Z,tr�t�-i���x�����.(@0~�s��aTbtc�d�e�f�gh^h$k�l�n�o�rsdt�v���T���l$n,p4s��x`���_H�]\nT�P<e���Hip��|���`nH���he�i�5@�
Е���iܙ��rT��e���,�?�i�l�o�� �i��% x�thr�J`��t��) $����l�]aLeXk��oxs�ul���Dn��]T�ahnL(���
����pa���(��.T����a�i�l,m8o\t��l�2Ĵ<��l�r_stܰ�eԔ����i�|- T���iX�O\�t`� a����ths��:@epit�Lr��3 ij(�����x�����t$���rи��
�a�d�e�gj$nHo\s�t�u��O��WT.�rp������t�r$�9 <<p�i���e<s�? �W
4a`�	Tr�D���G |l�M $�hg0�pe�����s���e��
<�S ���a�e�i�2.8�����e���n�s�����.�������a0c8e\ihk t�����.Pr�X ���Hs��.l�]�a�e�louX��d�l�n�r�������`����d��^ ���t�/|����d��'�n4�

ȓe d����l�����i����i0u��xT��i8.����@e$��Lm����Xi|r�t�y|�C��O��t�s�8
��.T����e���ehu�����\���g l,n@rTs`tL�n
$���d,��n��eH�Jl������e����4n�������Lkp��id��s8���pe���|r�����X�m  S�]��a$e�i�oXu�y��,_���.�g�]���gks$_9 `+�_��ed(�nd�kgHiPkXlhs|tl�vhpZ�q(�rdz��x��`l(}G
�{��ttt������e<��n�s�t8�������tČ�����tL���gkm,rLs�pԟ��a�r ����s`��������$od�x p���8l �]@k��tte�s�t`F�d���ls�����i�*�sп�������n�s�~�������������s����X�a�e4�
L��e�.Ha\bhepf�i�k�l�n�p�r "sh"t�"v�e]�(rXi{
�g��Tspl�Pn(hu
pu��xa�t���l�v.<v���e8{W�.�z���a�d�ek8lxu�|�$|;�r�|"T.dP}�p�4dev��� rPs�(epiXy'�"He�(P\s�i���Ĉn�a�d�e�i�s ��ԉ|�(���d�n �(L����ܔM`����aTdte�g�h�i k$ l� m� n� o� p� sL!t�!u�!v�!� �} ���0v�8edo0��Drlu����I\�-�.�i�n�s�t����tT������sĘ��e�
"�.�n���� ���
<��n8����� �*Z v��U oX �g(g��4 a�*�< pd s�*��H ������+e`�\|-p r�x oT�����`��ԝ2t��� e� i!m!t !v��� ����� a� e �2� dԂ� ܂� .����&!e��D����h
.����(!e����4!ll��@!ad!e�!i�!v�Ix!i�� ���p!e<���!dp�*�!a47�XT��M�!ll���!aH~T����!e`�x�!r��ȡ��"��!�"�ܡ� ,��8�]"eT"f`"jt�p��4"e\� <"r��H"o��p�]X�a�&e�"o�"s�"tX��"k�"m�"p�"r�"�(���(����"v�)������W�*̵~�"o`�S���"e8�X,#aX$ep%it&o�&u$'yH'����L#l\#m�#n�#r�#s<$v,����;h���T#ep#i�#m�;��2.����x#e(��#n4���#a�����]�#o�����#k�#s|�
t�t�#e��� ��M$e $i,$k4$p4��4.$p�w�
8���$el�
p��X�xH$r��� ��J����P$.�$d�$e�$g�$k�$m%n %sv� �u�$l(���$e�$t�����$s0z���4�(���$m�$s\��#nT��$atW
8
���%e�8%o|
��%kH%v�� h��0%.D�eP
� ���PP%e���X%r���d%a�%d�%e�%n�%s@&t� ����%ip���%f4]�%r��P � X ���%a����%k�%p&t8��2� 2�%l� ��&i��2�.����&e!2(&r(!��4&eh&i��i�O.����P&e\!�	\&m04���}c�&f�&n�&s�5�H�� �9���&e�9Z�&l�9�&a 9]�&t`;��:]�&t�U_S���&s't�V*H�elV(�&l|V��'a�[�Z��'t�\� <\��0'b`'k@=��t'�8'��� ]��X'o�a� `��l'r�'v��M�'e�B	�'h�'i�'j(l(m (n4(o@(tl(v����'ol������� �����'k��'u���(e,�D�g����,(e�� ����T�v��T(aܺi����L(bd(n��
t�� <�]
�(aH)eT*il*lt*n|*o�*r�*s�+t,u��	h�.�(f�(k�(l�(m)n,)r8)s@)v���4�8�����a�(g��� ��"x�S	<���)a)e$)l(�h ������P�.`����\� �a��d�)e�)g�)i�)k�)m�)n`�o�)r*s0*t��XD�JH�J@�Jx���������)a�)d��h�)s0��|�� @�t�)v��	����)a*i*sx��*nD�;����tX�����(*a�t�f|��@*d�H*nd*sp	�h�P�m�*g�*n�@!���e���t\#���*l#��*a�*e+i$+kT+lx+t�+u�+�($2�#�*i+r�$��$���*s+v�y�d%0&M8+a�d &�0+f����`kgd+n<'8D+a(e��(	 )*�+p@(Kl+o�+r�+����
D��T)�+i��������+��)MD*wX*���+��*	�+ad��4����+a�*�+kd3M�?8,gt,l-n@-r�-s�-t�G.H��,,aX,lI�Pl.d,g�H�H,e�R��PtPP��l,e�,l�R!�,a�,e�,f�R��R���,rS�X�.�,n�,r�,s,S(�,d��0.L�t�,e@S(LS!�ä\�-s�Y��-n$]�4-lPO��7gdO(-ala��lHaX-ep-ox-u�b�.h-td��e
h�Pj��i"�-li���-e�-k�-o�-tdktpl	�m;�?<p���-h�-u�v!@��a@�.l.rh.s �i,���.a�X0.aH.eT.k`.tĻ.@.b��@���.@�A|�e��O���t.a�.<�>(���.d�.f/k/l /r�T.����.a�.e�K(X��.k�.n�.sDQ��V%�..�V���MP�d,�T.����/e��-4/aH/e�/t���@/r���X��.\/sx/th�Bh/v��!�
!��tp/e�Nt��/nH��/it�(�/r�/v����n�t�����/e��i�/u������M�/a ��0b�0d81eX1fh1hp1k�1l42m�2nX3p�3r5s6t�6u�6v,7w�E����/a47bX7c`7d�8e(GfHGgLJh�Ji�OjQk`Ql Vm�Vn�Yo]p]rdbs0mt�nu�pvhqwpqyGÜ�������0a�0o�0�P��d1� ���0�Йi����0a1e1g 1m(1rJ(|�1d��!!�'!x�-!l�;�m01k�l��D1o����L1l��t��?���s�1t�1v����.��1a�1e�������w.0����1a�1e2l 2o����"�1i�1n����3!L��1s���1e2iظ�8!�T.@���(2aL2b\2e|2mT�
@����T2r���
T�h2l4��p2e�8�x�t�2.�2e8����2d�2e�2g3k4Bl3n(3sL3t��>!���2n���L����P�2n�t�2e3jh(;l�F!������t 3a�BvX[���83e��@3rx���(Ja�4et3l|3s�3u\�9��~d�L!����h�.�3a�3b4d(4e`4ip4k�4n�4t�4u�4y����3f�3kP�H���3r�3y��Q!�X!�(8�4kH��4e��=�� 4.dCa<4s��iT4k�p.��dH4e����v��h4i|�2�k|4e��$
a�4e�4i�4s���.L�R���<'8�4a����4l�������4n�Z(���4l���8���5c,5i@5kh5p�5s�5t������".����45aT5e`5i��".�p��$�Pl�5n�5r�5t��p5e�5l��\��}������5t������5e�5o�5r8�i���������5a6iD����]$6eT6i	o\6rx6t(�T.@6nL6s@��0��86s���8��,�a�+��d6px�tl6a����.�����6a@����6lX���h�.�6d�6e7g7k7s7t$7vH�����h�"�6i�6rЈ�����6i��_!(�e! j!�'!
lJ�p	aL7y�:�.D7k 4h>��7a�7e�8o�8y�8�Ļ��@��|7t>�7l�7u E0Ip!�H�7a�7b8d8l 8m08n<8oL8rp8sx8t�8vPI��7a�Iu!|J�J���7i�pPM��8o(PJ�QG
DQ��(8d<S{!8T�S]D8a`8eh8o�T5�U3�V-�XJd�.�����aZ���8s�Y-�8i�kp\h�8p�8r(l<�x���8d�
ȃ��8y�����8��\9a�9b0:d;e,;fP;gh;h�;i�Kj0<kh<l>m�>n�@ohp�@rtCs�Et0u`Fv(Zy�F�܅��9k�9r���{
l9a�9e؆�t9t��.���H�]�9n,��9e�9k|�� ���9a�9e�9r$:yX��!��5�(	d'��9i:��)��)��:�0	D�:ř\:ap:e�:i�:o�:r�Jy;��A��T:n�x��h:l��!�|:a��!�j���:.�:a�:e�:m�:s���:m�:sX��!�j�!X��!(��:ek�!mc�t�̎��;���� ;n��xؿd��-�JtD;�x��!����<;�Ԗ=	��g`;n���
ܚ-�;ð��.����t;e�����;v�����;��J�;e�;n�;r�;s<t��!�W����;e��C�J�;p�;t��r$��<a <o(<rp�d��|�|d��
H<lP<nX<s`<t`����!Ĩ��{
ȫ���.�<a�<e8=it=o�=s�=t�=u�=v�Oy�=à�(��"�<r�Mu��.�<a�Md�<i�<k=m=n$=t@l�0��<e�pA�rH�T��!����.=sܮ�!h��
0=t(}��h
TNdX=gd=tl=v(��x�nP=g�$�x���=v���<��!�=e�=i�=k(�2��i���!�����=l��ED���=n���8�
���=e��!����P��=�>�@������p�.H>aT>i\>ld>np>ox>r�>s�>u�>y,Q����.���`��!��"	�a�����!н�
�>t8��!��2��d�]ؾ.�>a?d ?e�?g�?i�?k�?l@n@s@@tT@u`@yp@�@���>mT�������>t�t?s�n�?a�QmH?rT?s\?tp?v\�`���@?ft��d"xh?i�"���)|?e������.���.��t�?a�?r0���/����?n�?vP3x$��o�!.�n���?e�8i�?gd8�?eD������t@l,@u4�@�$@nt���8@r0^(�"L@mX����"����h@��@�����i�@g�@l�@m�@n�@p��"���l�u�@t�����@e�k��i,�
"�@e@��x�]ؾ.0AalAdxAe�AiTBltBm�Bn�Bo�BpCs Cu0CvTC�4�"�'.�
lPAn\As��X����HAt���`�i ��dAi��ad�AfUg�Ak�AnUp�AsTUt$�"��8`����Ae�
"�����Ae�����AdBk Bm(Bs0Bt8Bv8�cBaBe,�,<�
d�(��(��(��(�\��@Bg��HBe�""`+`B.�hBah�p!�Be���0����J�Ba�Bm�Bp�L����B.�Bm(�)"�1"���B.�Be�9"t���c����Bf���Ca�A"��JCdHVp�������sel�q
8CrdCv8���lC�DC��^!,��	��t�.�Va�Ce�Ci�Cj<kDl4DoDDphDt$EuDEv�XyXE����Cn�H"��n�C.�CswP"���`��\��CaDeԉ@,�KDu(D�h�X"��(���� D����X���<DeTDrx��`Dah������D.�Da�De�Di�DrE�]"���ܺ���Dl�����Db���W.�Dk���d�(�Dk��.h�d"Ee,����Dd���Di#.t�k"����E����4�]�.���,Ek��X8Ei������tE�PE�lE���`��ErL�i���Ee�����.�EaFeTYi\Yj(Fo0Fr8FstYu@FyTF�L�����Ek�ElFt���.`�*�EeTs"���	�E.Fe@��
��;��.t���{"`��"����{e������LF����Fa�Fe�Fi�Fn�Fo�F���"��dh�������Fr�Fs��t����Fa0�"	$�ed�5���
,����F� ?,��HZ��F�G�H�`���r�$r�G�8r�$<Ge���	4Gl@0
�Ga�Ge�Hi$Ij8IlLInTIo�Ir�Is4Ju�1�/xGl�Gs�Gv@5�5���Gk�6"�:�0:�Gi�8�Gd�Gi�GlܢmHn4HrxHs�Hv�;"�<h<���GaHe�<��>]X�ud�xxA Hi�@](HePHidH�x�(�AHHnDC�	TC��\H���
tCtpHb�Hl�Hm�Ho�Hp�Ht�HvD����4D�DD���"�D�HohD��HiDE�"`F�hK��J�HeIfIm�K�xL@�Oq0Is�OIe�P�`Q]DIu8U��V�Ymh�.ܤs�]*�Ia@]��dId�It]KpIa�Ie,��"H^�"�^���.�Id�Ik_*�_C�(	 h�Ir,hX�Iadb#�IpJtJy Jäj*�h���Io�l��l*�l��J��n7�n,JdDJr�o@�shJe�Jt�J�|�y��`Jn@������tJe�|Jn̆��Je�Jo��������J���h�.Ka�\bLKcTKdhKe�Kf�Kg�Kh�KiLkHLlxLm�LnXMo|Mp�Mr�MsOtXOulOvT�����k0KrDKvp�<KbH��"��H�ЕJ`KeX�2ܙ|Kk�Kn�Kr���l�R�Kd���T��,�]ta�Ko�Kr�Ku�����,�x�]�la�Kr�� ���-$�
�]�Ka Le(Li0Lk8Lr�u�R,����� ��"�iT���@Le���"@���TLsl�\Ls�Lt\���hLe�Li�L����"ȶ��Lr ��ȸ�"�����L�и���_dMgMi$Mn8Mo@MsHMtx`v��" �n�Li���Ln��t�Le��(,�Mn��T0Mu@��"`����"���"4�5<��PMmlMptMr�����x��J�Mst�i�]�Mi���]�Ma�Me�Mo��2�.t��Mb�4�����.NaNe(NkDNl(�ohNp�Nt�����2Nl Nn��c��l��4NaX������8<Nu\N��( ���TN�8��xNa�NrX��	���"�;�����Nu4�"�Nr�����Nei�NrOuPXy��iD���Na�Ne�N��
h����N����"\����Nr������T9a(Oe<OrLOtD�54Os���|�G(�i��tDOe<�-,��clT���`Oa�Oo�Oup��������Oa�Oe�Po�PuQ��q
�����Ok�Or�OzX��"����OnL�q
8��	\���OfPg Pl,Pn|Pr�Ps�Pv��8<��"H�JPs���`PolPstPtl��"���@Ps�"HPp��TPp������"�����
�����Pk�Pp���"��d�1�Pkt�M�Pa�q
���Pb�"�Pt`��X�����Pe��]�Pd���Q� ~,Ql/ ,$Qy�]�T.�Qd�Qg�Qn�Qr�QsRt$Ru�]��
8Qa,Re�SiLTj`TlhTo(Us8Uu�Uy�Uð^i�^���Qe,_�`Ba�Qe��.`a�
a���Qd�b�0�t�Qv�cAd�	ReRsRt4d#�d9 �dl�W.�d�f��k�
�RdT�f�Rg�Ri�Rk`fl�Rm�RnSp SrdSs�St�Sv�m�h8olmhRrm��tRe�n�� fe�Rg�o�ahpY4fe�qJ4�.��eLt#ds���Rt�t�h�.�ReSs0u�.�Rst�1�u#�v#<v�Sl�v�0SaPSe�v��0.����8SeDw'DSd�x#�x��\S.|Se�Sk�SmXy".z5��#�{��}-P��	�Se<��Sd�SeTgTm(To8TtDTvЖ(l��Sl�����]�SnTr����%#\�� Te��b��Č����0Tt�[\��XTa��
���L���T.�Ta�Tb�Tm�TrUs Uv8�����l�*#`����Te����Til�1#�Tn��Ta�Te�tX4�|�P�Te���Tn ��UtD����dUe�
���"�������0UgPUkXUn�tض�ķYdUg��pUe��.T�����|Ud�Uf�Us��6#$�PT.п]�Ue��<#����Ug�Up�UsVy�~���U���������Ua��������V.VmD����TVe�Vi�Vo�VuhVä�;d���<Ve�Lr��DVt��������V�`V�,������xVih��Vn��gel�2�Vl<�. ����Ve,�P�lLMWaPWdtWeXi�Xk�Xo�Xr�XsLYthYu�Y���k0WlHWvд�,"Wm���$We@Wo���J�hW�D�`?r���\W�\P�We�Wl�Wr�Ws�Wt��v���l���Wo�3����Wo�W�hVNtV���W��8�Wel�k8�t�.d"�Xr�"B#�-B�,Xb4Xd<XkPXndXs�-(�.
�/�\Xs�/��DXg�/,1xtXk|Xtp1��1t�Xr2H#D4EDDJ<B�Xm�Xp�Xv�D0G��Gi�H�XkYmYo$Yp8YtDYv�x��M���XrPܦ(�P��YmpQ*
,QXYr�RW�Q��0Yi�Sm<U	d^il^��TYe$]\Yr��t(b��tYeb|YrD$���Y��Y��dJ�e�YaZb8ZdtZf|Zg�Zi�Zk�Zl,[m�[n�[p\r�\s�\t�\v�fM#�e]�YnZr�f��.�gJ Zeh� j�0�lPZr�i(ZaXZd�eehZsCidjG �k��k�`ZtPnJXp]�a�Ze�Zr�p�4.�r��t<vJ�Ws�|��z���Ze�Zf�Zo[v [�,tt~�Za�p��i[r��Za0���(X~��[��J@[at[e�[mX��,�S#0���H[s��wP[i��\[rl�"h[t(�]#x�?
`����[d ��[nĈ]�[a�[e�[i�[o�[s@�Pl.��
�C���[kL����"ܔc#�'.4\e<\s`����[aD\dX\el\gt\i�\v�\���i#���0��P\a$�t\�'d\a�����n#<�W��el���\a`��ܡ�ȡ���\����8�]�\p(<tX�p�]�\o�\r�O��(�\e�\i@����.��".̵8�K.@]a�^eP`i�`o$au�ay�a��������
8]bp]c�]d�]f�]m�]n^sH^tx^u�^v�	|]e��i��t#4����]s������]f�]tx�{#h�|\��#8�t�]s�����]d�]t��i���]o�]r����4�^.0^t����^e8^k@^t���l�t�����X^el^i0���O.��x8�Wd^s�x�z�4��^kX�J�^a�^i�^t���#���^s�$
2�^a(�������^a_d,_e8_fT_gx_i�_k�_l�_n�_p�_s<`t(��#����_s�����$_n���#D_eX�h$4��L_a4�{�_e$�`_n���l_e�_s���#����T�����_t��#��_i�_s���
- |
���_e`s,`t��d	k��#�_a `eT�����t`n��|��
��4`t�3	���H`.t`b|`dx�f�`g�`s�`t��#��T�#��',����`e��`t����`e(!�04���(a�`i�`m�`nasav|6l
88���$m 9�L�i�:]�`k�<��seT3	S��aiDalLanXaphas�atXT8�T���ggP_(	�h��U�`aaxat`V�|Vq
8ZdZ���ad�ag�at�3	�[�#�[���am�\�4.�\���ae<\���adbrbvby@=��<b��a� ^��^�be$_i\_(b.Td�#�`�#�a`��0bdTbk\bn*t�`ha��B#�ba�ub�cc�ce ef,ei�ej�ekgl�gm�gn�go,hp�hs�ht@lutlv�ly�l�4bX�bkclHcmpcn�cr�cs���#�����bse��brxd���be��H�e��c. ce0cgdf- 4�
xf�#(c.@ce�f� g�	Xcb`cnT��
H��
�h�#�g��hckj�i��|cd�jJ�ct�j�0m��B���ceHpytn�cfP�g�ci�ck0dlt�mxdn�dr�ds�dtevqJrJdrdvhr�#Ȫ�#��Ts�d.s]$da@dspt6 vaPdt<��h��xvXd.dvt`dev��ldd�dk�vs��t�.w]�dexJ�.�di�xh
|{�z���dt�{x�de�di<�s�dtL|h
�|�#}"�}i�_���e��~e��]Ted`eshev�@܂@er����Hee,�i�����(`���per�xea�ee�ei�eo�e���#ԉ��efԊ��i@������e����x�. fd(fl<fmDfnTfpxfvT����ea�fi�fj�fo�fr�fugv��0�(4fl$gD�
`��ԍ����Lfahfepfi��#��
���$�����ft@���fl��$�f�(�$4����f�d�����t>�
Ԕ���fn����fe�S�(����fgp������`kg��ga,geXgu�gy,�D�gDgr�w8�d<gi̜�h��Pgklgntgt�(4�(X�.T�(	<��gn8��P��gd�gr��M�ge�$�����gt��H��gl��M�ge��������gghp��ب�he����hl@�X haHheThi�ho�huH�zl���dhklhl$��ĭ�xhe����$����hs����hr�����hr��|�heD�ZP��heT�������hk$inDisPit����ha�ielji�jokr�kuly(l��!$���id4igD��ؼ($ȼ�<ij�H|isx��L���\i���9diÀ�/$pirH��0�.�ie�il�im�irXjv��4$��
�����il���.0�J�ia�itt�:$h��4����iejfjk jn(js0jv��A$�$��H$D�O$T�V$<A�8jnL�g@je$�-Ljn�(	��dj.�je�f{g�j�lN]$`��h����j������.�jld�(�j.�ja�je�jlc$X������
X�������jp ku���ja0ke�ki�ko�k��
��j$����(kaTkd`kipkk|kt�����Lke��3	�p$����hke�
h�x$EeD����kd�$�����kf|�h����k��k����$���X����ke�kt��f@���at���kk���kr�H���lr����8l�l�`�(	��XLlg<�6�|��|��Tle����\lnt�Xhla�li�l���������lk���l����l�������ld�lm�ln�ltT�mh�]�|a4��4�Jmk mtl~��(m��l��4t����mip��`�<�	\ma|me�mi�mr0ns<ntPnupnv|ny��2lmk}l4���l�aPwe\�]�mk�mm0�.@����mex�J�za�2�md�mf(#.�*�meH�$���.��men�8���"?�ms�"��nd�!��n��)�#�(nv�*O4��|�nd3��Dne���7��\nk47dne�8���{e�nt�:�$�?�nb�ndoe(ogHoi`ol�om�on�or@psLpt\B�$dB���n��@���n�$D]�B]�nios0z��D�otlE�4E��olH4ol�H�$�JxToa�J��<od��"PZrPP��4,e|ok�ol�oo�oÔRX�oe��P0.�R��R�oa,T{`VI�Q���o��V��[,�Y���ogDdla���ogpipn puPd��e]�a�e�R�h��pa0peh*�m�i��8pt<p��	T�axpb�pd�pe�pg�pl�pn�ps�pt\q�$�q�$�q�.�r�$�s�$Xt��u�$v((v�pk4vt�pa�pe�pr�-Spv�ps�v�$@�qa<qe\qiz��ԇkxl�f�|�6qiH��$ql����0qdl��x���Hqa����Pqt �@��qa�qd�qm�qn�qr�qs�qtrv�J,�T.<����qaL��ķ2h����qe����](Ua�qe8�P.0����0e�����.X�Mre���<���rtP�3	(���0rkXrrdrvtry������Prst�i��\����lra�re�rn��Z���rrd��t��	�ra�resksmsn(sp8srDsspsv8�t\�M�rs���.��%�rnsr��2T. �M����8�d�����$����0se��Pst��� ����Xsn|st�M`se��S ��$ta4tb@te\tfhtg�ti�tkul�um`vnHwppwrxsdxt�xu�xv���sad	b�yc�ye�f�hD�ih�j�l�m,�nd�o(pp�r��s̆t�ủv��y���x��_����,ta�Ltlx�]̟�����TtiС�xta�te���T.�n���tn0���tsH��$����tmX�i����ta�te��"T.�tn�trusp�x'�����taĪ���
.0���uaDudPue|ul�us�uu�uv�j�$�t<us�"T.`uv���L�hui��pue�uô������u�(��غ��uk�uo�ut�������i�^�i@����uave,vn�4oDvs��.��Y����vt`�vsH� ve4�*
Pvr��8vt@��$�i8���Xva�vd�ve�vi�vn0ws\	x�t�vr�vs�vÜx�$����vr���vkD�����v���T.�!�vs8����$we wo��wlwn�W��\��$|�2�t(we@wk(��x���$�e���.�wi�wl�wm|Cs����Twe�wk�wo�ws�wt`����(��*���P�������wv��
0���weP�����we�wi���wr��(d��$xe8���xj,xl<xpHxv���������$p�X4xlD�f��*��Pxr����XxaxxeL�l(�T.@���xk�xs�xt4��4.D����xe�t�����xt������xr4�PT.ynX����xaye<yiDyoTys`yupy���$h�"�.,yg4yiP�s܂(��(����T�� �Lyl�$0<���y�hy�L�$ 4�����.�yazb0zdtzf�zg�zi0{k�{l�|m�|nL}r�~spt�v܅�ynzt�����ysh�Ԉ���yr!O �mzn��x.Hzř�� zeTzrVD�]@zs�G���\za�ze��Mdzt*m ��zs��2T.Ԗ���ze�zr����za�ze�zm�zn{s${tԛ�$�Atpn0����:
����zs����J{k�$���{sd��L{s��kl{rĨ��<{ex�x�x"X{n�C��`{i��+�.ȫ��x{a�{e�{l�{o�{sh|t||v��T.�{i0��$�gĵu̱�{s���{e��<�%|a|e(|k0|p@|tL|v�2(�� |sx�%��2�^	0��<���8|e���S̵T|s��\|e8���t|et�(��|k�����|a�|n�|sļi���|e�
%нt�|k�%��2r�%���|��$�|�d����|f}o}s��%���$}e��i��%����,}a4�"4}dx���@}a�}e�}i�}j�}l�}n�o~p(~rt~s�~u�~ü�����}t��T.���8�%����}e���}g���}e�}i$��n�}aL*��}gh�!%t�'%�?��Z~i<~sH~t|��~e @��4~i�@x�Kh�*P~.�~rX�X~a���h~k�~v�,%����~eh�3%<���J�~nl�8����~������e�~pDt�R�~a�~e8�
���X��~lnr��|����i���� i��(rTs����4edü�9%��Y����\���X�e�i�s�t��"T.����|`�_�a�p�'0�����.�r��M�aĊe�n�~�
t����m$�]X�.0��a$]�s](�u�i�� T���0�o����
8�a��b��e��f�g�k8�mX�n��p�ŕs�t8�v�Xp	P��k�����al�Pܙ]��n��t��],��Ѐf؀rt����?%P�.x�]�e��	�]��e�����]�oT��ld�E �e\���,�m���$��D�nи��L�a��n��s�(�l�s���t�e��W
��k���\����e��*
������lāp�������e�i��k��X�.d�n���l����	�����o�r,�t|����i��t$�iT����=a�eL�s��������T�r����\�a��e��o؂u\�:��m�B%��J��eD�,����e`�����t����r�	�e���̂lX���]]�aa��]����n�� �a(����nL]@�aP�e\�s�]<�\H�n�H�ex��aăbԃd$�f`�g��i��l0�ml�nx�o��p��r�s$�u,�vh�w�f���'.�e]��r�g� jt�ĩa�e�y�jPT.�.n��r�j��.li<o0�a8�eL�iPn���f0o|do|D�r���o�hs
x�tXp]T�stG%��a�s��l�rtL%�t�8{�ȄgЄn�z����a؄e�i��l�o�s�ut�|{��|"T.(�~"�n��`�(�"�m܁�"���(�t��
����Te@�o��"T.T�d\�s�������IĈ]d�od�?��".������ale��i0�.ܔi.`�����aЅe��o؅t\�l�R%8�?�a�e�i��{4�����n�rx�����X��MP�e\�������<�e@���D�r$�����8�>8�]��a�e��i��������e�����n��s��BĆetn�<���e�r<nt����e\��r�K�K�BL�aT�s�?���d��e��gćjЇk��lD�mp�n��r��s��vC��D����L���`�e�|�h�r�|��t��4E��Xr��t�F.H����ateL��.�KM��a4L��Ha�r�MZ�M��a�P�PP���a�e�PT.$�g,�s4�v�n(�Q��}%�Vp�V��<�a�?xZP�sTZtX�e�Y��d�d��n�\�$hKla����u�h%.܈ri��
��a��e�i�j$�k,�m4�s<�t��u���xiX%�e�r�j�
�i��i"T.�d<j��j,k�dkTl�m��mML�rx�y�n]%d�ap�e�nJ�n��\�n�nK�PopJ�o�����z�$xM��a��eXx���.ĉs�xb%@���a�eD�it�o�{px{���ez���l�sT�\��(�a�����r���ĐIx���0�i����8�lX�rh�th�S��x���`�sH�X��r�����i<�g%@�Z��åb؊d�e0�gH�l��r��s�`�`�MĊl<��q��nܭ��'.�s����et���G�X��l��e���$�g ��.,���<�ap�d|�e��q��\�sx�td�eشT.��%T���sl����e�����d��zP�a̋e؋j�t8�PT.8������<����g4�r`��0����T���k%�������
������(�fD�s������(���
L�e��f��gČǩl�n�r\�s��v��y��������rP`2��e��i����a��a�����P�������1ex������،a�e��T.��@/r������a$�eP�s�c�X��n8�r@�sP��h�B(g|��H�p,��p�t8��D���h�a�����p%�������d�t�����a��e؄i�����������a܍k�n�r�s�Od��H�����r�����a���$�e����-e8�P�tt�tP�a`�ll�n��p�r$�v\��������X�e�����ia��d��e�x�����r�����k��t��s �����l����(JaԎe�lt��T.��9������e�i�s�/uX����x.�M�a4�e����.Е���@�l ���H�a�b �dD�g`�it�k��l��n`�pp�r�sd�t��u��v��T�a��bH�cЕdܙe,�fx�g �h��iH�j�kT�l\�mиn<�o��p��r��s��t<�uT�v<�w`�y|�z����v%�����l�0o|������e4�j<�r�{%x��С�P�e��]ȧ0���X�n8�����l�r��s��u���(�e��i0�����.Ԑa�e�f �g0�i<�kL�od�s|�t��ul���M����Dn�"ܐi��tԵx�i�t�|�%(}?
(�'<��ȶ(��(�n���p�����D�p\�v��غ��.t�t��xĻؾ.��M8���
��.��a�d�e�k�m$�o,�s<�tL���"Бl�r��(Ⱦd\|x�tܑr��dj.�s�t$�-t��l���9�%(����Ba�l��y��
����D����]x���X�aCp������.��d��eؒn�s���H����o��s��u�������"ȒaВl�����%�k�.�s�2#��=�����a0����k��8����i4�m<�pD�sX�t��{%p�����P�i��R�����.��M|�e��rt�t��y(�i,���9t������al���rD��@�����k��
p	a��b(�eT�it�l��o��r̔sؔt�y��D
�o�u���Rp��e4�ox
�r��h���'.H]<�r\PH�e<����`�r`Xh�a��et�$�g��s�d!���e��n�",�$Ad'�%�*�%$*Ĕt�*[/���r�.�a4/��X�.���%�	�r���a��r8���$�a�3P0�n 4��<�a|�e�5i��kșo��y�6il6��h�e�5��p�nh9�%��e�%�9]��e�9��t�=i>"0�l�s�vh>����a<�dX�e�g@�iX�j��n��o��r�st�t��u��y����C*XE"0G*H�u�F �lP�p|F,�e�O��SU�H4�.��at�b��f��gĖkЖl�nD�od�r�sHI20I���l�J?�JU�JJ��e<Lh�K����iPM���.�v�O�DQdܚd�s(�tT�^	HR;��v�R�%�R2�f4�t�R�i�R�%�S�<S<�v�x8T"P�p�S]X�a��e��lܗn�s���%�T��.��n��s`��U1����tg����aЗe����ȗg�U�V����VM�m>�Z�m$�s�Z��etC"(]�]],�n�[4�eP�o�`�%lc�l�e�c��c�d�r<h],h�x�a0j�\h��l�n�n����n�m��aИe�i(p��o��Șg�v\q�%|qdtd�a �e<�iH�l`�t0u�Pt���m�uK,�lv
d��w4�m�xT�a�x��`kg0z�l�u�{��|�%�"t~|�n��]��l��r,����*ȃ*̙y����ԙ�����[<�
�]�a$�d4�fH�g`�i��kĚlH�ml�nT�r<�s��t�u �v܅m̌]|����M,�f���Ԗ��@�eX�o̙��Mx�d��e��g��nl�Z��%��n��������i`��%d�����lX<s��ȫ����a�e�i�s �t4�Ø�"�m��vT����%\���"�m<��%���%@�q
�Up����(��������@�a\�bd�e�����d�
��a��d�i�n�o��s$�t0�u8�y؛�@�+�;ěe�M)����a����l$-����Л���pD�>��p����k�v�M�T�t�|���r(�pX����(4�"@�nx���H�a��d��e̜i�l�o��r�s �t0�u0Cv �S���.��g��nĜtP��t�?
`�����g���%��"|Ul(Bs8Bv�������.���|���a���$Vp�vh��%��O<�*��J(�n��,G.\�cd�el�s|�t��v�Bx����;�������t�i���%����aԝe��r�ä�"��b̝t���%������l��������3	����ܝ��&�w�e����&�����d��M�e$]	`�et�f��i��j��l�o��r�tl���	X�s�]�]l�a����e�������������$2̞s����eh(� ����ԞmܞrX$#X�et(�$�aD�e\�s�((�(�l���)��0�eH)8�rT�s*;�*q�.*���d��@0]
��a@�dP�e(�g`�hp�i̡jءl@�nd�o��r�s�u81��/��e�n�r �t8�v3i�2��؟np4�
�3���k�t�u�4��42$6
6]�e0�tx6��6J`7m�9;�8H�b|�dܢm��nԠr�s��t�:0:t�i��o�:&�:����m��s�:��m�:&�>nĠo��;C�@]̠sdH�tCt�Hp�E�	�E���a�t0���G'�G�d<�sHG�eL�rX�sxH�pI(�IKD�a�I� LJK"�Jh�a��b��m��o�\xL(\��XM���n�P&�PM��a�O���v�O��e`QM�d�i �o�gS�.�����e�S�s�T�hT���aX#�tWP,�u�VM4�eP�o�XitZ"&x�r�YX�f��m��n�o^	,["�[��[]��o@]]]K��a��eȢ��^]�aq
�a�������0b'&<b��آd�t*�c��bX��ldb#�a<�eH�jX�kx�m��p��t�v0d��c4�l�e��e��e��P�ah�r�f.&����gMp�u h�	,hX��a�hd�a��e�r�i���kȣlУm����i��i��j(kأahl�tlX�a�o�n�n�tLp�s�<�e`�ot�u�|�y��4�n؄"�n.����H�ed�xT�lЇ(���l�k��l�������a��e�u.��M��nĤsؤtиi������.p������Фa ]$�a�e,�ip�j��k<�lT�o �r��sd�t��uثv�y����L�bl�ft�k��n��o��p��r�s�J`�e(��
�X�l�J�Jx�<��|�d��-���T����o�������.ȥaХe`��iܥi`��������a��p�	3&��.,�d4�iP�ll�n��rܦs�t8��
��1�<�g`��D�e �id�u�ip���.��a��e��sT��9&���ؾ.�a��e̦iԦo��?&���.����H=	�i��k�o�u�2 Y���li����.$�s��F&�D�nd�s\�
���<�eT�n�x i ��\�t !��#(�#x�fاn�p�r�s�tl#]
��a0�e��j��lĨo�r�s�u,�v4�y0 $��ЧaT(h��|$���v�$(�$(�t�$/�;�%���t�$$�nL�r`�s@&H�%��D�u X&��X�kp�t�&�$#���p#��x��d'X���<#�'���a�'��'����g�(]بrp8�08��Шd�)*
�e�)2�(�d(���vl+L& �n�T��+��d�+m�+� ,8H�È/��̦�X2��a��b��d��f��gȩhЩk�n�o�r�v@2���r�2R&�2X&�2���e3�Pn�3��t]&�4JhOu.�6tةat7�p8b&08����d�e�8h&�;JT<�@�aT�et�o��y�<(�<��8�k��x=��L�m�4s�?`�e�?]h�b��s�@n$A]B]4B����a��e�m�o(�p4�tC�k���Ȫa�F�ЪlG��ܪ��FM�ÔG�<G�t��C�G�l�GX�e�H����iH�oP�u��i�I-TK����v KX�a|�e��r��s�L��g@P�@Q8�a�P���l�RīlЫtT.�S����e�Vn&W��i�X��4�n�Y�[*���������\*�p8]���Ua�]	��d��f��kȬlجn��r�s(�t0�v�]��(�aL�d�e@�fT�gl�i(�j��kܰl`�m��o`�r��st�tȴu�v@�y�Ìc�^����r_+�_��`�a�a��Ьa�d`a:�c
�b���k�t�dEd���s �t�d��d�\fp4h(h8�k�gt@�a|�e��j�rԭs�Ls&�K��h�o�hp�k��t�X;�X����elc
��*��e��r�~��a�j�ȭf�t�kG,��8�e@l��r�k��a@�eT�fT�gl�i��k`fl��n��r��s�tH�� ny&oRT.�n��H�ed�n���hp"x�g�p��qJ���t����oxH�v����o�xMĮe�SkLtXy�|h�|~&̮i�{��Ԯr��t�u}i(}t�etY�&�~��~��H�����.��2r���(��4�Ì�i�]L�ed�ld�<��d��e�}g��k��mįn�r�sĄ�������s��]��r��\�"����dدe�st��&���|�?���&|�i�k8�����i�����t����h�.\�]�a@�e`�oh�u���T�s�' �%L�e������D��r��s�p�e��o��u��`�(��&������sd�i���d;b��m�n$�p,�u����a<�e��i̱oرs��X�Ta�`at�ad����d��ؒ�ܭ�4�e`�i��n��s$v�p���X�eP��&��l�rL�tt�ed�����dzY�t��k`��	�}a0���g���& ���ım8����kؘ�&�����������0�r�������@��.���$�e,�3	8���<�tԛD�s|�v$�P�et�i|�sd���O��yL���
��eIJlвm�n�p�r�s$�t8�vD�wD��&(�����o`�(ܲvh������nP�J�p���l�� �]�o����]�up����0�e8��&�
����L�t$�ET�e�|�&�|"l�eDl��t�j����t�����e��l�m�p�t�u$�v@�a̳a�el��8��&D���Գe(ܳg��p�uh�����rЮk�
�oH�a�}4���0�a<ue����8�r�w`�*T�a���\�lȯh�a��r��������t8�����t�:��e>������������D�hPUk�l�n��r�s�tH�� ķ2�����������(�e\��&h��� �d8�r̻x����T�r�������.��a\�bl�e �i0�jL�ld�mt�o��p@�rT�s��t��u��Ü�W��dܵe�g��k�l�m$�n,�p4�r@�sP�t@���mԵn��J���&(����k��o��&<����n�J(��|�J�����.<��(�CX�tH�r,�=x��&��d�e��g��k��l��nȶt�y��-��JP���]��u��P������hd��h�.ܶa�iP����L����-������ȸ��L����ah��l�Vn��
��%$�8�v���@�e\�u��94�E�
�l�g��l��r��vd�\�����a`�����d$������=�e��l�o�s�uT��&����طil��r���D�����e��@����%����$�f�$k�s���,�e��H�tL�et�k��m��t���	���l�l��r��eP���*��e,�P��ih�
�h�al�W<�NL���.$�a��c��d��eT�f��g,�i$�j0�k��l��n`�o��s��t��u��v��y8���X�gt�k��l��m��nԹp�r�s8�tD�u`�v�(d�g��E\�&<Jl�kt(,"��t�����eTX��e�a�]Ĺe̹n�&d��	J�p���	��p�.��s�
�&d� ���kh5p�s���
x���	�$�k���,�adP�k\
�S���X�lp�r
!XX,Mx�e ����r�t��e̺i�o�r,�sp�u|���]�ĺg�i��غgH\�a�u��s*�si�s�����i�$�k@�pH�t���n�&��P�r�MX�t�d�s���ԙ�\W
̻aػd��g�i�l,�n@�pp�r��s��vT� <��e����r�J�y�����J�dDe�l���e�i���<*$�g4����8�aP�e�i.� 4.��n��s���\�e��i�n��t,�
8�%��&���k�������.мk��s�t$�È ��a�ex � Q.�!q|!�t����!���iL"�8"����$�
D$��0�����	��k�$H�et�o��u��y�%�&$%l�s��th"�,��-��1��$Ad<&��l�v�%t��a��eh�j|�o��r̾s�u���&(���9��j\'�b�m �n4�rL�v�?��'n�i�A��'n,�iD�u�'H(-P(N	\(�T�nh(\�e�Y��(t�a��sT)�|)���e��i�_��)����p�`�,*����s�*d�o�t��vܦi�g���m�+�tlf,x,p�,����D-�`�r�, �al�bt�e|�f��g��l��m��n��p��s��tp���T�.�-=	L.��.��.�(/(X/�0��/����n�0J���,1����aпe12ܿs��@3$���aD2��i�o�2�|��
�3��k�3�eD4�L�ep�l��o<5��4D�l\�rt5��8�o06�<6h�a7(�62|�kd8;�,ax�4�.�a�eX�fd�g��h��i��o�s8�t@�uH�Ð:(��l�;��m�r�s$�t�<��\��L ��<���i|�t�<8�t4#'#t0�et\�$D�s�$L�a='u3�,�l�lL=_t�a��od�
'd=T.0>'>��m�v���|>M��a�>W
�.�a��e�t,�v�x�>	?i�nX?��@�4@��eT�%AX$�ePA'�A�B���U�X��B�<B"t�a��d��k�B���r�B�HC��C"&��s�C��H�8.��a�e(�i<�j\�k0�lp�m��o��p��t��u��v��yIT�.��l�r�I�J�|J�'.t�k �n��rTK*�L24�s,MLPML�aT�e@M(lM"�M���a��e��i��j��o��v�Ü���M���s��t���Nx�M"��t(N�	LNN���`#�L�]��t�N�����ī�N����r�x'l�P�����D��D�a�O�gdO$�eL�o\�Ð�!(�*����O��T��$P�PMh�aب$'�P��|�p,Q
��a��l��o QW8��"��px�3�Q����l�Q����a��e �i4�oD�r|��@���R��.�n�R����.�s�)'�R1',�nS�S8'���0S<�a`�el�u�����X�l(S?'`�Y����t������S��l�S��TG'<U|�.��a��e0�iP�r�Uo��l��V��.��g�r�s(�tWM'�eWT'XX� �Z'�X��sY`'\Y5D�mZi(Z��<�e0[:h�a|�e[�$[��`�nl[(	X[��t�r��s|[�$]���i��l��t�]Z^e'�^J��.��e_�T_y��ě�
�_����v�x$`��i�t�axhb
$�k,�t4�vTc��cH d*�e�|�a��b��d��f��g��i��k��l4�m\�n��p��rl�s��t�e��g]�T��j]��e�jP��r�i��e<o�Pn����fxr Xp]��n�tMDy�<v����s�z�Ze �j�c�����n��e$�9	���,�u`����s �@�nx�rĈ]L�a��e��g��i��ux���@�r`j' ���t��*��s@���r�����n���x�\������l��p��ܔ���'.`�����a�d<e(�iL�l0����a���<� �e8�nК�����tg��U@�a�q'L���X�v8�]`�k|�t��h
.Ш����e����lp�]��a��e��rP���(�K��a�������p,�t̵��
��a@�ep�i��l��p��r�s��t��y����WT.��M �iL�v'��8�e\�lh�px�{'D�tT�a$��'����e��n�����
l�����e��g���'���
��s����e�"ԾL�t��i�s���l�]��e$�ll�r|�s��uD�nh� ����n����a`�à-h�i4�.p���<�e|�H�r����T�������Cl�t�t��y���\��h�]��a����n��W������������]��n@����a��ul���tT�_�9.D�����ep�M�]�aH�eh�ft�i|�k��l��t��{��@�l����T���~\���]p���"D�g����e��h�����r<���aH��'��*t������8�]
�a��d��e �g(�i��k,�mt�o��r��s�tD�u�����4�nH�r\�sp�t8�+
����,�d�������@�k���
����T�tt�����h�rT�i��|�e��s�����i��k��p��r�s�t���t�e�tp�R���o	*��aH	��
|
����t�
*�o8!�A�@�aH�kT�n|�t�x�]�qa���p�d4�np�s����G h�t(!\�#���x.��t�#��a��e��l��v��($(�$���.4$��r��s%��t��&4�e�(^	8)iH)�����T+(`+�g<�lL�s+�a`�è+(��<���D�t����.��X�� 4�04��l�aX�e��i��l��p��s|6(h7�\:�'�9����p�:0?x�>��e�E��D����o�A���k��p�t�G�|H�|K��a8�i�K2Н	xN]$�r$N,�gSX�k`�mt�sT@�T5�h(��.�U�h�a<\�@=��������`���l a�'4b$�k8�nt�s��u�B����a��b��c��ep�h��iD�jl�k��l@�m��n��o8�p��s��t��u��v�w�yL��le�xd���sl��'�g��0�bT�g\�nd�sl�t�h��h�	i�
hi:
�j��k�'�k��|�d�)�d'���y�l�
��rTm0m���n�B����e��h��o��r�B��m2d<�'tnH�d4�e\�gd�i��k��lt�m��n�r�s,�t<�u(p�'�o�,�lH�nP�r0p�8p� �nlpqJx�e��g��spq-�q�'�q�rJ��t�r�'��e�r�s;s]��d��i�s��vpv����e��o��sH�ew;��ix���.�s��zJ$�sp{E�{����et�j0}-4~�
l~��X��D��8������7n��v��M`�a��i����x	X�� �����n������e�����vā�������P�_e8�]��n���e�l�n$�s��'�]��d��������d,�]L�tԋC����0�n�8�u�o����P�.��l��rT���X�a��e�j4�ld�o��r��u��v0�*(���t���'h�.��e���Ў'�'.��e��l��s
�'H�(��s$�'؏��"��X��e�o��'p,��7a��Y�g���'(�aH�eP�oВ�.2���'x�rd���X�g��ph�t4�'�7*�����iP?�
0�����n(���l��(��a���p�
��i̗(��8��u ��h���t4����'8����.����y��������̞,�k��M4�a`�el�o��u8�(P�X�d����eT����x�s�i��M��e��u��i����d������l��m�p(�t0�vL��'���ܦ����o��r���'���ب���a�e �r�it��'���ܪ"@��X�ax�i��r�(d�e��L�n�i��.$����p�sİ(x����uP�t�s����e��i��k4�oH�t\�v��ü�i��s��(��(�����j�o�r�y ��4�(���$P��h��x�����0����,�k���ظ�@�o���XT�el�o���������t�r���|�����ܺ�������b��l�n�p�r�v�����a4�e��i,�oD�r��u��ì�(x�����e4�t
�����e|����3	�JlEb,�i��H�"0�.T�kp�l|�m��n��r��(`�t�X�������h�l0�J�zax�t��.��e����
4�����.��n��ô����N�������|���������������k�l�s�f�����b8��D������$�k<�ld�*���\�e��y��(����T�.x�n��r��s��t���(��.�%(�f�4�i6*X�����s����f��l��n4����i��t���a��(������k ������nąr(�t4�-(��q
4��0�kH�y������`���P�kh�r�"����l�n4�u<�v<���p�aD�e$�i��j��m�n�o|�r�s��t�u��p��8�����a��e@Wo�yPxB	������s��h
��r��h��n
<����g(�s�B2(��t �v����(\�	��c�)gp�k��l��n`�o��r��s��t@����xe��ys�د9(������g����P�i�2�����e�o��s��uD�<�%�'��e@�".��?(�����s|������4��(���������
`�ex�f��k��m��o��p��r��sP�uX�v���.�mT�rp�sE(H��� ���m0]��a��vثK(�i�����e���J 	p!\	�p	����a�e�i�j(�k4�p<�t�	2 Nn
�(
��
L
�� �o�
K(�
�H�rk�-�d�s t��Q(�l�n�O��t�a�����r�����a��e��i0

����r@
+<�����e���l���a|!`����s���s���e�4�a�"m<�rH�st�v������d8?h�tЧ�ܧT�a���\�r���K��a��e��i��o����8.��e���*$����lh�n����aL����m��n��s�.
� �'P����s\##��a<�e`�kl�lx�n��t��u($��#4�i��rP�s�$h(��0&��X�u<'8�a��`�(�@(K��i��*�)����n�*�g��k��l�nd6p�t�*t��a(�e��i��j��o��r��s��y4�h+(T�a����+���n���,�P, �dL�lX�nd�st�t�rX(-��D�ox-*�k�-��^( .��l�t`. �d��l���/M��f��n�/�/��0��&�1����rh1t��k���D*(X*������2��sd:e(d3�e4�0;��:� �r>j(�?Md�al�bt�l|�m�n�p�r �sl?Z�@�PPI�V���'.��f��i��m��o��s�����r�W��a�W�,Xo(pXSY�X6��e��t��u(Y�@���Y�P_la]�ipi���ezW��d��j��k��l��n��r��s�t@���,�a�e�i\�lh�np�o��r��s��u��y���pz(��?4{8�z����sx{���a�|��Ьa��kl��~����b��s�~��
T����sx��(�����t��4�gT�i�kt�r��s��th�v܂JD�eL�n��tX�t(ă(`�g������s\���h�aD�d��e��y(���t�������k��t��v�������iD��
���������������$���(����G��� �n,�s��(����s�����'.<�b���(���8�e��D�gX��P�e���H�|�m4�+t����(x�����l����a��i��k��l��n��o��v�]$�]T�������0�f\��(�r�����k�����(�����n�����p�� ���4�aL�i��|X�e��(@���rt����'.��s����l�a��e��o8��`��l�(���k��r�s�y`�� �����P���������.��i��s$�{�;,�(�et�2���t�KD�rh�t<���q0�l����8�s�x.�P�e,��\�t�����t�k0�r ���|�a@�dL�e`�g��h��j��k`�l|�m��n�r��s�t,�u@�vL�z����a�bT�d\�e�f��g�h�i��k��l��m�n�o(p��r��sXt�u��v��y��4�Йh���8�a��X�n��
���.x�a��e��r��WT.��rP����T.�O�sq��M��a��v ����x<%(�e����l|�]��e�o �s�����k(�r<�tP�u\%h��d(�8��(4�e\�-�H�e����
�20���X�oxuT�A��i@���p�b��h��m��sH��(����t;eT�����v�����������4���������t��r8�����g�l\�E��.4�p�����aD�eX�nl�t�����t����<�k�2��kP�s��i��d�a��e��i��2�'.��t�����.L�
��x8�����������8����>e��k��t��(������i���������i�����CaL��@�
�e �"�r@��� �e��X���8�i�h>]����a��b��d �e8�f��gP�hX�il�j|�kH�l��m��n��ohp��r��sp�t\�ut�v���܅���k؆� ����bt�PI�����b�ǩ����e�r؍��C��(����p0�v�����-`�el�j��l��r��s��t��X�X�t(�������t�g0��|�aX$������e4b�����a��i�'��*G����r��iԖ����e�g�l8�o@�rH�yD�iP��e�Y,�e,���$�s̙���(pq�ܚ-�Md�s��(�%L��d���t�a��k��lto��t<��@&3�%����uܥ��r�����e`�C��i0.W���n���a�e�i �r0�s�K(�N�X��n��|�QT�P�(�tt���0M�ȫJ|�e��g��l@�t\�ut�v�o�(���h�r��p�g��i��l��sH�
�pA��r0�����e�rh`fJ��eT�(�
د]��e,_������g�n����a,�e8�o�a�d����gd��̱$�n ����!L�r ��(�D���T�t��n.��h�a��J��i��p��s��%����a��t�(4��(��l\��(��iнt��e��th��8����i@�Vd�����a0�dD�et�g��i��k��l��oSu��y����<�rt��(�h�.X�s�Qtt�R
P'k��`�d��th�e��s@�����$��d8g	X������h@��{!�*4�"��fPAnx���
��a(�d@�e|�i��k,�l��m�nD�oP�sd�tp�u���t�����p ���e8�s�����xadX�g Hih�nP�x�(`���`�n8����t�k��m��nd�b�(�����x������P�(����s����e��l��v��	�(�L.t���u |�2�sh���a0�s�2�2t(�l��
��J<�n���$Vp�����\�s��,�N8���x���~�
������f��h��k��p��t4�uH�vX�I���(�����l|�����i��(���(����a������e�i$�r4�Y���m��.	���(��,�t����X@�e����T�g��k��r����\�a��r��s�tH�vP�yd�tL�*��aH��P�����.��a��e�	d��(H���w��u`��������n0�t��a,�e8�o@�s��B����m�� �r�����\�	��"�{e�0�1��r��s��Md�a��e��i��n��)t�����kT����x������s$�]��.0���a��e�
H),�����@0�����T.4�b@�eH�kX�nl�r��s��������,�bܙ��
T���P�ft�
��]d�oD�t����x�j���������� ��a����p�]��L.�i�,i�e��b �c8�dl�f��k��l�m�nP�p`�rX�s��u�g��i� jT.H�t�i(�aX�iDjTakP�s�o)Pn��d�l�w�hw�x�rLw]��e��o<v����k�w)4�S�z����t؆)����rĈ��e�n�s�>L�6�a�e �p(�tH�uI�|JK,Q���8�i@�r���4�'T�K��(ܔi`���X�a��d<e�i,�tP�u�\���k��r0����e�i�o�r��s�uXV)��]��v�[��x�.���s����� ��l�x�t<� �e��Zl��$�aD�eY��<�t��8��h�e��t�2t�sP�(\��
D�|�i�����e����r<�|8��B~��'l@����el?���r�?���a �b@�dx�e��g��k�l��n�p�rL�s`�t��v����@�0�i8�o�ADd!��B]�+eP�o�D]\�e�D���
�E�d�i4E��l�n��r��s�E�e`F\H��\gl��oXI)4LX�a�e�s�L��L�.�np*�B���]�rNJ�aPP�,�aX�ed�i��l�(�P$�.@�rH�s8��d�hp��PP�i R�T.|�d��k��nP�LR�L��pR����n�R��[$)�e�Y����g�i�[
�[*)P_-�=s��a0a����8a� �sla����a0�i<�o-uD�yTb��d	Pd(�s�e�h�i�X�opl�<p��T�ap�opt�	z��.��k$xMx�a�z�y�y�����@��@���d�p��.<����eȺH�u.0����e<����l8�r|�t`��@�����������h�.H�n����(�ad�e��t
FT���P�.��X�s����n.����p�e(�]��.�a�b�e�.f�gd�kp�l��m�n�p�r��s�t(�v�/)��.����6)�f�g�i �k0�nP�t��<)Ԗ�xd���C)����(�d@�g��C)��9"����H�aT�K)P���\�j�����e��v�x(,���s(�������r�����eX�ih�����e���,%e�����n��i�����a�$(l��l|���e�����k0�s\�th�u�����t2p@�v �i�L(`�H�lH�P�e��2t�t����������|��z(t���s,�����e��tD�X(�i�v��(��a�e�r�t��X��.��t�x�a(*� �Kl�K��t�et��4�e�����8lt�J`�ax�g��l�]�'.\�MT�rp�Pl.0�Jl�e���.������a��e�T.��s��t��Q)��� �����.�a�bd�e�fDgdh�i�j�khl�m<nTpr�s�	t�
u�
vHy���aPbXcxd�e�f�g h�i !jl#k ,l00m�0nX2oL<pT<r4Bs Kt�RuWv�Yw�Yy���J�.�n�s�t�V)l�"��2��\)8��s�����e�o�r�yP��	Șc)L��Йi4l����aLe1g�i�r�u�y�i)0��,eȚ�\.|�@llrTKhU��]di̛A؛xex�q)���������m�n@�2�����efort8������e���rl�]�e��w)���D��(a�(i������0�СJTi\r���P��-4�})���lk�m0���tsH��
"��M�o���X��.����a�ei0s<tDu��"T.�dls8'L�(Ī(�WT. sL��L�;����(j����2�*���Lm�n�s0���
Ta�d�e�ik$lho`pps�v�*�t8b{
0�*�j�t�s�t�k�)���n�v�	��"���
���s���)y�Y�
�
��aLeXs��3����8oL�@r`��ĺ~��غ�hvd�<���|s���e@���a�e�mp(u�.��.�rh��)@����i��DT��l4���ed����i r��*
��h����.8���
0axd�e�i4Bl�m�n�o�st��.x�tla�eh��)���.����r���P�n�/��9�)\�R����o(��x.�mDD-�t 3ail������)��Z@eLr0�g4���,d�4n���)x���4�.�a�e�i�p�rsP��
��xd�k�s��((��l�i�l�n�MD�t�lp�`�����t��#��2����e��C���
t������.`a�b�e�f�g�ik$m,n<ohp�r�s�t�u��xb�m�n�v<������)<�*H��)�e@�)���i��dCa�k�lt����P��)`��
��"T.�d ���)��it�(�)$��)�p!h7*����4lLm882�=�d�Tsp�\exs�����)���iHD�0���j�tH�P��Z�iL�i������8�����8����.	e<	j`	k�	p�	s�	tq��	i$	k,	vX�*�}y��]d�4	uT	���)���L	����p	ax	o��*(��p��
�	i��(���)�	a�	i�	oc�����	d�	kl�(�������	o�	r���)����)�����	a$
eH
o\
rd
st
t�
yx�*(�
d8
g@
tD�	��*��T
p���,�G���)��ix�tl
a�
e�
i�
r-(��
l�
r�
s�-���m�-;(���l��(x�K�
il�;L�*@����
rX�Je(i<l`�mh�"s�
���� s`9l�4u���W
 4]de�5ipnl6h>�v�I��ab0c8d
ed
f|
g�
h�
i�Kj8k`l<mpn o4p�rHs�t�u�v�y��܅*�l� ��r$ud'*,��kP��̌`a�e�i�r�s
u
y;ø�itg|l�n�*�@�*�A���.�t�B!*��i�i�m�s�F)*(Pp�"�.�V0*���� \��l���kؐ�����d���
g8
k@
lH
pP
r\
vT�d��d�����pn���#��%t
f�Jt|�nԖ-�
g�
lP�z�a<[8*ܘ��
h���
d����
aܚ��
e��jpA*�����
t�Jdgl m(n0sl�"������0�W����
d�JPlP<nX<sXt`�P�H*ȫ��.�a�e�i�l�o$s�t�uPv�y�Ü���k�l�Mn�t@��	p���H���pA�r0����e<gk4s���i<l8NmLnXp`shtpv���(e��I.@�M*`f�LuC)���Dg�U*T�h�$��"<�\*�n�"xe�g�n�s�t�Nv��x���n��a*����|jj�Nt���e����
���"�fv�
���.e���<��"Tk`l0|phtpv�i*���@r����Ha�H<�i��|a�����e̵<��D����a�e�k�r�vt�o*lv*��*�a�e�{*ܶ�T��*���������=��@��(gp���*0r�
��q
(e��JHi����t@�"��.�m�p�s�td���
Ta�dehf|hpi��k�n�o�s�ty�Tx�	tP�(���*8t�sp����t��inr�t�er��*P��*t��*�2<bDkLl�Qm\t�Qvp�*$��l#�!d"��Tt�$�*����v���*P3�D�>�e�Wcl���l��"@Rm����e�t��0�����
���e�r�ut�����X�G'��������i,n��2��Dihl������.Xa`e��1���
��*\��4�"plm�n�ux���xa�e�i|m�o�t�u�(�t���@�)*��
adUgi�Ak0m8nUplrxs�t\��d���d r(s�*�*��`�ZLeTg\n8�
t�/�*��X���dt��*�s�t�u���*��(
�*���*�.�a�s���*��+��R`Uf�k BmnDs8�].�et<9"�	+�x���e,g�U�<(����
$i<jh����(Xt�W+8���P.lateT�+\�#+�,+�����J�p�r�s�t�2+4���e�:.`�n����o�!vT�i<�2��J�n�s(��
�el�q
8Cr0y8����V����9+����(a@e�B+�����.�a�d�e�iXWj kxl�p�tluxv�Xy�ä���.4��m�K+�S+�@���e�m�l�m\+�a�"��.�l���������lt���o`+p��.|���a<eHiPj`ø�g+.��� �n+�u+���X�P�D�g,�lel�3	X��d���e�o�uD�}+����t���+�����W.�a�e(o8rHXuXì�n���W.diXm n���+���+d��+\�*,����0iHu��+�������P������dn���`������������k�l�r�����aehTYi\Yj(Fo r��sLttYu`yt�`�*�Ee�s���+P�Y�����.k$�����~&���+�*(h��0nXs0�t<eH��+�����e���$������l������
�=(�s����y���=	�r��i��-�ae,i8j�nPrx����Yn�sT�
�	����l��.���a���� s��Ha�$edctx��+�������Xa���`r,���l��L���t -,��HZ���G���t`���\��[����[�X�+$�i�i@0ehu�82LJ �nK�s@a`e�j�m�ti�s�8kPrpw(���y��Xo\T�+̂	ls؂��tlh����u�"p��T����r�����ad8ehfpgxk�l�m�n�o�p�r s� t� u� vX�ȜoЕ���e$o0r���+��l��aܙ�`lPr��iT���h�.HeĚt,��x�E�]��a�k�l�r�s��+��]�s<��% ��%��xT���	ma�d�&eij<lPoxt��|�cL�t�el�i(�S	a�H����tt<�$sܰ0e8������Ha�^ghspv�n8�(t�n#H��+�������W.\����a�Ne�o�pt��+�l��)���+x����]�d$���n<rHsи���aTd\ehfpg�i�nosHt�ux`v�

���4k���\�.��n
���.T��#��$)�a�e�&��&���d���n��2�/������g,��n����s�?���#ad�e�?�d�W
�ip,Q�`����G <ihL�+pL.|L��$e(�20d��G tiZ""<�iT.D���\e0�5hm��:<���l�s��5l���J�e�l�p�s@�i��9��i��]�e��+��� e i s��i(�i��������e< kP m\ px td��l���4 o��a@�MH u8��������d v����l a|mr��D�� n����� e� i� o$��	��� n@�<�%T�t� e!i!s�(�� k!n(��D�h�.��m��XD!a�!e�"o�"u#��������<!nl!p�!rT�i�]X!ex���`!pX��+���x!nl��8�-�!j�!r\���!f�!g�!l0"mP"nh"sx"t���+��+�����!g4��H�J�!d"l��,��!g��"e(�3�"s��J$"a@"p��,��
,����H"k`"y��,��M��,p���p"r�"t�O`�Y���"rX��'.�"n���"e����"l�"rPQ��\l�,8����"l����"r����"�$#��[����#i��]#d<#pL#r`#t�/)$�����D#i���(X#r ]�#a�$e�&g'id'j�'k�'lL(n�(o�)rd(s\+tl+u�+v�+y�+���#b�#f$k$l$m $nl$o|$r�$s�$t�$u�$v�J����#t�"h��J<��0�b8$d\$gd$nx�H$.P$e�8",��2n�?
����%�m��t$b�$g�$t�B��$
a�	���$tpDut
n
�	���$t�
"�
"��8�$o$%u�	�$d,%e(�h4%i<%l�%n�%rX&s�&t
),
��
"`��\�.\%et%t|%u��T%il%vp/,���%k�%n�(��i�T"�%np���%a����.�%e&i@&u��%i�%l&n��T6,8Z�%tTc�R&g$&t���T�	�2,&d�J4&n��x&uH��L&a�&e�&i�&l�&p�&t�k�
���xG����>,�n�&d����&a;�&t����&e�&À�F,��t���&��� �'nT'rTM,���'d4'f<'nD'sL'th��S,oHo��D!� !X\'a�'e�'u|���!�x'k�'n�'r�'t���P"���'t�Y,�����'nx"��"il#�p,xH,���'g(n(r (u ,�'a((o0(y@(�a*�b-f��./|/��/��8(��0_��e\(y�1`,4B�	\*a|*e�*i�*k�*l�*p�*t�(vP+��H�@+a�2	X2��	�(d�(e�(f�(l)m,)o4)p<)sp)vP3e,PnW�
�~"�(g@5���(i��})�5�)l)s$)tt6��6j,t7U�7:�!P)iX)k`)tx:��:��:�<*�;��h)e�<(�)m�)pT<�|)a�)e�)i*o *u<*�h��=8(�nx=���)a�)i�)nt�s�=3	>*?p,�>���)k�?2*e*s�5[�@?�@2,*s�@S�A��A��L*�4*��A��BtBT*ll*r�B(H��Ct*d�*i�u�	E(�D�*nlE�*j�E� F�*a�*e�*iPF*,�`x���Ga�GX�*e�H�+a,+e8+r�H3	hH��+k�v,I$+pxIT�HO�J|,�J��H+� K�4Li�R��d+k�+n�+t�[�,�T��+i�V�W��+o$YJ�Y��+l�+r�+t Z�Z�L[+�[",l,r,y���,��+�\\\Z�\��\��].H,a�,e0.i�.o�.u/y�/È]����.p,g|,k�,m�,s�,t0m�,_��h,t�_d�,e`"Pl.����`�,l�`���,ed�pe�,�d���,i�,r�e�8��D��,t�l��,y�k	�,b0-dg`-i�-m�-n�-r�-sTtXmx	m��(-dH-edm�,lm@-bX-s�m%hp��X�et-k|-v��ԣ�dsJ�-.T�,���,�t���-o8��w�-k�v���-i�y�	�x���-j�-n.p .s.tX��$�M�-opz�,0{��z��.i�z�,���<�(.aT.e`.gh.mx.n�.p�.t��]��.�i\��,,��,���p.i�.j��[8��	��̡�,ԡ�.l�]�.aL����.n�.s�.v ��#�q
@��,ض���.t�����.k/nķ�/tD��,���|Ud4/fD/np/s���,$�o<���</g�-+���P/e$�PX/tп]d/e�>�/r�~��0�|/��/�����ex�
�����/a����/n�/r�/s�/v��Y��x�������/a�/e��iL��,����0n$0r�i��d0e���T0at0�<��,(���@0n��H0n��,�`0l����h0�Lm�0a�0e81fL1i�1o�1u�1y2Ä	���0p�0tT���0e����0r�Cp��0l\�0b1d 1e(1sD����1i�����We8�t�$;�0�h1p�,@1pt1t|1v�����]`1eD2�$P3��CA<B�1g�1k�1p�1t�w�,�CJ�1l�D��f�F]�1a�1e�Fi$]n�1m0^8$`n2sha?hbJ,2l82yD$��2�//��$2e<d��e]h�.�2k�2n�2r�e��@2a�2b�2dP3e�3gD4i�4j�4k@5l�5m�6nt7o�7p08r:s0;t�;u�;v4<�Lf�����fx�2e�f
�g�2o�2ri�i�, ji�i�2a3d3eD3�dj��jPT.,3k43n�j	DQ�
4lHl��<3�pl�l3ft3k|3l�3s�3v�l�,�l��l9�mS	(n��??
q]�3g�p�3nXp]�3e�3g�3l4r4s(4u84� q�	,q�3r8q�3ePr��r(�rK4e�s�hs#4ttt�Ttdt��04��tJl4ex4k�4n����PX4eLu]`4n`u��u�u'�4a����. v�.�4b<v���4a�4e�4i�4k5l5o$5u�(�vT.X#l��wZLw]�4o5u�w��w�,dx�Pz�$g��{m,5a�z��45bd5e�5l�5o�5p�5y�5��|�$�g�5i�5l�5t�5uX}(�}L~(t}%�c��5e�"u.ȁ�Ԃ���X~���5���	6a�Te(6g06ikrt6s�6t�6u�6�X�"T. 6fL��,P�{#l�T.D6lT6sL����,���L6bd6e���<��tl6e�6l�6o�6tL�a��i,��ԇ-$�
��������6�ĈthOa�6d 7e47i@7sh7t��uԉ��6r��x	-���7�|���7�@�7lP3��,7vL��L7v`�-@-Ԏ��T7n��\7od�	�7m����p�����7a�7e�7i�7l8o8p(8r�ؑJ�7s@��7r�7t�f0�T.�7pP�Xx�-ؒ;���<��8r�]8e�-`���l%ap8d�8e�8i�8m�8o9p 9r<9s�9t�9u$�.0��d8a�o�8s��u�|��|���8� ���8�\�%-�8f�8iL�m,0t���
�*-<�2�8k��
�
h7���(�8l|=1-`�9a9eT��l�8-��(��(9lt��09al9ix9l�9t�9v �B	 DX9n �2`9nx���I>-���9rD���M���9sl���9e�9o�9r�9s�Oh����9p����P�� :m`W)X���9e8��
<:aH:ex:i�:k�:l�:mTmo�:p�:t;À��.��fd@�kl:l��nt�s��(�]d:e���:d��\L�]�P��@�M�:u������:s����:a�:e;jȼ� D�B	4����:sD��:r|��@��L���;� ��P�;sp�]$;eP;i\;rp;s�;t�;y����l�Ch;e8���q�;tT)T�K|;r(����t�;i�;sP�,�;t���ȯ����;tp��;ah���������;a<e$<r,<s�����<e@���<rt�C-$�I-Դ��D<��E���̵8���<ax=e�>i�?o�@u$Ay�A�4�*����
x<d�<f�<g�<k�<n=p(=sT=tx^u`=v��������$����<kl.O-L.]�<n���<e�����<i=k=s��t�����4����� =e<=jD=sd�U-��;	����L=oX��	������.�=i����
h=d�=g�=i�=k�=l>n0>o@>p`>s�>t��)4�	�=i�\-���=e�=s$b-�	�Y�=l$K0ci�d>a,h-���>n(>tTo-�v-@�|-�8>rP>s���XWX!|
��X>jp>s��-p
��
��x>a�>n�>s�>t$��-L�-|�
��-���
�>bx�f�>g?k0?mP?nd?pl?s�?t�?vTA?a�>s�H�S	�e?j$?kp��d]T�a��<?e���-\?t���D?s���hi��	�?e���-����x?e��?n(!J�?e!�)"Y04]	�?b�?d@f$@k8@n`@p�@s�@t�@v�4�j�T5�?e�oh
@r�5���?o��6A�6��@kH9 9]0@iL@lT@oԌ[�9P6m�9(p@nx@p��G\:�-`;��:]�@t�;J�@n�;]�@el��<�T�S���@i�@m�@sAt�T��U�l)a��{dWt�@i|V��Ar�@t0W�-��Z��Ag@ApdAs�Z
�Z��8AaTAe\Ai��_0��p[��\i]��lAe<\��tAkbr�Av@=���A��A��^��`�-�Aa`���Ad�)e�Ak�Al�AmBn����`�
8ai a��Ae��ha���e4bh�.�Bd�Bl�Bm�Bn�Brt�s�B��Ba��cCe�Df�DilEk Fl�FmGn<Go�GpD#rLHs�HtJu�HvlJwtJy�J�c��f�
�e���Btlgi g��Be|h��g���Be�Bs��ti��i��i���B.�BaCk�ih\j�tn
H�d8CeHCg�ui\CkpCl�Cn�Cr�Cs�Dt�o��ptlp��@Ce�r�r��TCtTs's]hCa�Ce�s�-�s�Cv`�idw��Cov���Ct�x2x���Cf�Ck�Co y�
��?&Xy��Clpz�z���CaDkDm$Do,Dp4Ds�z1���-��P{�p{[HDexDp�Dt���-PllDry�-�x"XDeP���`Di��,�q�{���-e������D��~�D�l����Da�Df�DkEn8Eo<�sHEt������- �n�Do��������Dd Eg(Ei0En�s���
�
 ��-��m$�H	�n@Ei��-���TEb�ElT���\Ea�Ei�Ej�El�Eo�Er�EuFv0��- ��-@���Eo������-hO
��-�Ea�Eed����En 7i���(�X�.FaFt�R��V-p�.��8PFa\FodFu�Fð�W.@���8Fa����DFg(�ih��pFg��.�.���xFs�Fv�����F�������Fa�Fe�/i4�.�(	̞�Fk��M�Fa�FeG�ȟ�P��Flp��D����F�x����MGo0G�(����(G���`GahGdpGg�Gm�Gr�Gt0�v����U����.ܦ��xGbl�"��������Gl@�X�Ga�Ge�Gi(Ho<HrDHy�*H��Gk������Go�Gr�n��	(��Hr���4HsHt���Hr�.x��0�*���)XHm�*9	��&����`Hd�Hk�Hl�Hn�Hv���hHaIe`IiL{jxIr�Iu�IyJ�T��x�*���X�tt���Ha4Je`Ji��$Je�����Hk|n��L���JIaH�Id\�g0Ii8Ik@Iv\�.���$��LInL�g$�e��S��XIlpIn�����K�Ia�Ie�Ii��o(������In��3	�����Ik�In�Ir�x�)*D�(X���lm�Is6����{v`�d�����I���Jn���� .x��
����,JgLJr̉'.���DJt@�(	����XJn ������n�Js��P���
�Jm�������Jrl~���J��J��J���CT.�����Je4���Jk�JlD�K`�ؾ.KpKrKt��9�:,<�TKa�LexNiOj4Oo@Pr�Ps�Qt�QuHRytR������'.�Kf�g�Kk�Kl�KnLpLrDLsdLtpLv��r4�����a�Ks�Kt��8�..8���}ax�i�K.<����Ka$)l�Kn�Kv@�5.��il����n�����Kp�������L.,Lr4Lt���	���
��;.`���<LsTLt����A.���\Ll�J��h7k�Ll(�G.��*�La\�
�Ld�Le�LfMgMi(Mk<MlxMm�Mn�Mr�MsHNtlNv�����M.$��$,����L���-�L��D���MeH��	L��@��� Ma��i����4MelMiXMs ����PMkd=%��"dMtx�z
�Mn0��
������h���h�.�Mi�Ms�{�Ms4]�Mn��Me��R.D�9�"���M.NaNe Nk4Nt,�@�)��'.���Ne���(���,NiX�[
���@NaXNel�;0��$�e �-`Nn���b�Nl�Nn�Nr�Ns�NtOv�	X�
|���Ns 	��
�p	���Nt0��<J�Ne�g(���Nd��Nn����Ne�J���Oa,Ou�

���4�adOf�Og�Ol�Om�Op�Or$Pv���J\Of�]W.ltpOmxKxOa]�Or�h���Oi�J�Of�_.�"����.�Oa�OdPePiPsxh��.t
��D�Pe��)�",�*���,Pl�4PaXPe`PotPu$"P]lPs �@ "T�"	 g�|Pb\#���Pm�Pr#��Pa�PeQi$Qk@QlTQo`Qp�Qt�iir��#�Pk�Pm�Pr@$��$�x%d%�Pn|�j�&�Qa0&��QrX�u8Qv'e.<'8(e�'�'MLQk�'X��axQr���x��pQe|((@(K�Qa�Qo�Q�)
 )���Qn�Qp���`�������Q��*j.�+Pl.4���Qrd3���QeRkRn(Rt(4X�4n.�4��Rd�6"@��
|�e�9��0Rk�8��<Rr@<t.�Ret;��TRr�Rs�Rv�����R�`R�X�&$=y.`=q
�>i>���Rrl?�Sn�?���RaSb4SddSe|Sf�Sg�Si�SllTm�TnUo UpLUrVs�Vt�VuW�P@�AT.�@��Se,Sj�A9	�B�HSa�@(C@Sl�ET.tSa4E��TSr�tF��F�Sl`GCH�SrpI��K
�J���Ss4t�i��Pt�Si�SrPP���SdTe<TiHTlPTtj�m(�P�Sd$�g$Ti,Tm�"n4Tt4�vhpDQ(�{( R�.�R,+�T.(�(�V"XTn�V��`Ta�Te�Ti�Ts�Tu�U�����Tl�W�TapW���Tt`W"�Tn�W;�X�.XYR�Y��TdUgUn�Z��1lTZt�Ts�[�<�e�\h�^xP_�`He8Upp`�)D`m0Ul8a
la��DUapUe|Uo�Us�Ut�UuVv�b�$k�eP�Up@f��Ua�9Z�f�.�Ukt2p�Ut�Uvgt<g�dg~�g�h��UkTh� �n�UrDh��Ue�L��h�)ViXY�i��.8Va\VexVk�Vp�Vt�h(��.LVkTVrxdIxii�i'hVlPj�Lk(dk��pVaH���l��Ve�m��
<p���V.T�a�Ve�Vo�Vr�ps���q�V.ptp�t-�w�.����y��Vy�y���V�@�XHWa�We�Xi$YoPYu`Y�{Z�z��4Wez��<WkhWl|Wr�Ws�{�x{��`WiH��~��tWd�Wi�Wt�O4]�Wn��"�We�.tT���WeH�������Wd�WeXi(XlDXrhXs���.�K��WlXsD��
�JXe��4Xa(�p�h-\���<XdTXn@��.`XsP��.��tXt���������|Xa�Xd�Xe�Xl�XnYsYv��	H���Xn�Xr`���eT�il�#x����Xal��.����Xn�i�����XeYt`��p�dH��8Yg@YkHYt���Й
,���]��8$���pY�XY�����e ���xYe�Yu�i��@���
�Ya�Ye�YfZg Zl8ZnpZp�Zr�ZsL[tl�t��Yl��%�Yl�����Ye�J�(شZg,���Ze���.dZkh���,Zs8�x`��DZr@�&LZe�]XZjȺ�|ZrP<9���Zi��k��i�P�Ze�]�ZnĽ�Ze��]P�a�Ze[k[s [t8��.�Zt���T&e��E`����Zr��tD�[k��.0[a@[e$�P�(X�8[l0�J\[ap[e$�.8�'x�h[s���<���|[t(���	��.�[a�[e�[k\l\n\\rh\s�\y��]��[r��6)�[n�[r����T.��]P�
,�������[e���H\s$�x0��\r�N$\e���0\j��<\k��,+����T\r,�t\et�i,�����.����|\r�����\a�\d�\e�\r`����lJn���.x� ����\et����\k]l]n8]p`]rt]t�2�����\e�����d ]e�it��T.D]n����(]e������t���L]s����T]a�e�i,��]t�3 �����.�aX^b�^d�^e_f,_g�_h�_i�_k�`l�`man��olbp�brds�dtfu\fv�fw�fy���]a�fb�gc�gd�kef�g�h<�i\�j�k��l$�m�nL�oؤp�q$�r��sȯt��u��vlJw�y�z�~ì�M�0ap^lx^o�y���P�7�^n�$�.����8.�^e1g�^i 1m�r�^s|���^m(P(؛he�V���^i�^n_p�d���.�����^t���-��J_ll�o��g	С��$_gL_i\_nh_o|_r�_s�KtW�(��MT_e���.t_m,[p�����d�X�_nt�#�_a�_j���s�
��M�_aȧ�0����_n����ta`e`j`k`r,`sD`t�`��"T.T��.|��8��L�h����$`j<`ll��.��p`e�M��MP`e�M��X`i��d`r���̰��|`�0�J�`k��y�����`b@����`aL2b�`e�Ao�`u@�(���`s�`td�(��b�����`l8���aaDac`ad�ae�agblbn�o,bs8btDbv,����Lasx�tTaatae�ar��X�b\���i�n.�att��'��P�al��m�ar�t�ae��r�asb�`�Bl�n�am �V�ae�*;x,0�����a�\�g	����o��;�t$bi��dip��Pbi������Xblx���`ba�bi�bl�bs��
\�
����
�ba�be�bi���(��bn���������b.caHCb ce�cf�ci�ck�cm,n�cr�ct�cv<���cb���.@caHcdPcmXcnpct��.P����������.hca���.��p|ca���P�+����cr��"�ca�eP��.���ci$�����
���$
a��mX����cth��ce��t8���	dc4deDdiPdkddmxdp�ds�dt�dv�i8��<de��]\dj ��.���pdi$��p�/����d	k�dm�dt���da�de�
k$�Pl���dr���D����en$eu�����da4eepei|em	o�er�et��?
����eg��)*D�((�,egLek\enhes��t��0��Teo���	8��.`�
$����.�en�er,��eaX��/<�@(��esx�t�ei�er�eu��(x�K�eal2Z��/@����ea$fd,fe4fn<frLfsPEt�� �/,��L���i+����Dfe�xtX�J�fa�fe��h�fi�fs�fø�/4�|fl�(h�"�fll�����fn�����fn 	L/<���f�l����m$gaPge�go�gu�gy�g�|2@�ge\Pgd8gk�En�xDge("�'.x
i`gnxgr�����a��.p�lge�!�d!��gaxJ?+��ght/�.�ge3� ���g� 4�ge�5��T.h>tha�he�ii�iojr�js�kthi�>��.4hkThnhhtt@�D@��,hsDht�@�x�#�A��Lhd�D#/�D��`hoxhyl�O�Ht�b�.n<8o�p�hrDis�Sn4�.�ha�he�hiikTBl(is8T)/�T�AkhU�-k��./�����h����häU��hl0��D��ie4iiV�itlI$W��VM<ih\it<X��WXTir�������k��a7/Ta��xik�[�is�h\h�ia�ik�il�ir�ivDy��i���is0jJ<</(l���ie�m��mJ�in�n�n��jk8jn@js�mjaPje�ji�jy�jôn*Ho��o
�o��Hj.pjg|jr�js��(p��hjn�p�	�.t
B/|
���j.|q\s��s��s���j��u��jk�jlkst��jekk8kl`kmpko�kt�u(v��v(xMka$kj�w(<xI/�x��7g�x8,kaPke���xHkg`7t`y�y��y�hkmp{a0z|kr6d3���ks�|�kuȃ_�kd�kl�ky��������@la�lb�lcmdne(nf�ng�ohhpi�Kj�qk�rldsm�tn(vo<vp�vr�xs�{tt}u�}v�~x8Z�܅�\lgdlklll�ls�lt̆
؆��xla�i���Ԉi ���le�lo�lÔ��m��x�&���la8&�ltp���ls��N/�0���lk�u���l�P�����<mř��	maXmdlme�mo�mr�ms�mu
y�m��(�H��H��Dm�\�Lm�PIV/��dmb�^m�mp�mr�ms0�]/D��p�%
����mp�mv؏d/��4�(�K�mo�myH��l�=ؐ̎��n�;�<�n���ni(�Y��`nn��M	nalnftni|nl�no�nr�nt�ny�n��.���Tne|��#���0�k/��@r�nth"c��q/�l����$�����n�X3x��"�nposԖ���naoe�og�oi�?j�ol�oo�or �s�ou�o�5�
��R8od@onXorp�t�?v|�'��	0Ahonpos�@]HoaxooPA1\Aw/�BH�H�P��oi��2�of�K��
̙W�l�^�P�oe�n���~/�����o�ܚ-paux�s��ol$pm0pn8puHpv���/�u��pr`vx�x~yi)�x��@pe�
l���Tpo���\pd�pe�pg8qnTqrhqstqt�A@�.�pn�ptě���;�pe4���pdX4e��	@����]�pd�peqh qnԠ�p�.����prqt��(`��/Wt@�Mqa0qe4���dDqt��:
D�����Lqu����J`qt$�L���.�qnd���|qa�qe�qiDLj�qk�qlrntorr$rs\rt Mu�rv8M�<x����2d(�����qs`��/��/ra���-<G�Ĩ��ro4rp@���O[
����<rd|��Drr�Protrs�Q��P�lrtȪ���ȫ���ra�rd�re�rg�ri�rosssu,sv�y4s�4�u����rmT��د�x��/�"�rg��<�6sk���D�$sk��n�j�����O�Ds�@��Psp�����Aa������\sa�se�snp>otpQr(tsLtt`tuhtyxt���.�st\��srh��/p�8�skD���ssP�w�si���/�sa����$��sdts4���se`����нt ti@tì������8t�t�n#Xti��i������̾�ܾ��<Q�pt�@�"T.�tl�tvd����ta�td0ueLugpui�us�utv�ȿ(|�@�&�tn�t�taueur���
���tn�xt��ue(ui�j9"��d�Qm�Qv'�/��tDud`urhuu0�������i,7v���uk��t|ua�ue�ut���,�L���um�us8�k�(���ua��(�����um���ua�urt��va������� v�,S����i4vl�����Xvahve�vj�vl�vo4�iD�"T�`vl�vnȿ��2xvn��t�veH����vd���8����.���va�ve$��/p�����/�����va4�"�vb0wmx����va�dDwe�wi�wnxoPxspxt|xu�xy�x�p�<wb���/��'	T�.pwa�AfUgxwi�Ak�ws�wt�wv(�d��/����wo�u��/���*�"���.�we�wn8Bv4�
�wi$��	x����wab�/�1���w�h���w���J�'.$xm4xp@xsHxt�����
��,xe4�n`�������.`xp��/��
��hxe��"�xs(��
�e��xr<��l��8����V��x��������x.yaHycXye�yh�yi�yjzkdzlpzp�zs�zt�{u�{v�{y�{Ðb(4�yb0ym8yr@yu�������k�Bd`o��"Pydxyg�yk�yn�yr�ytD�(��n�����*$����X�M�yo��.�yg�yntă���� ����yg�yn ���\��yu���p��zp|���za4zj<zoPzu ��/�����l�V�/���Hzt$��,�\za���za��*8�|zl����ze4�'.T����ze���zn���z.�zl�����za{e0{i(oP{rd{yp{Ä��/(�0��@8d{g {l\�(	�(	4����({m|�0���<{���D{�t�0��\{r�����{�P�t�0<�!0���{g����{i��H�%����{y�����{���3����
ؾ.|aX|e�|i\Yj(Fo�|r�|s(}ttYuL}v@FyT}ä�ܮb,|kD|lP|sL���+a��(0.`�*8|e��(	��"��.�|a�|gk�|n�|p�|r�|s��D�����|k$�00$�80P����.p�y���|k0��~&�|e�|i�.4��`��}j}k�%t�� �
��}a0�t}e<}rD}s������\��	����d}��Y���?0|�9�Ml}l�}n�Yr�}t��F0\�d�}e�qW��M	4�.�}a~ed~ip~nx~o�~r�~s�~������}d�}l4|����}u��L0����~d(~g0~i8~kH~rT~v�R0��@�Y0t���@~n��a0�s����\~d0��d�dx��������,����~�8���h0���~e�r�~t`��������~��~��~����XM��.����$,a\edfli�j�n�o�u�T.HnTrL����@t���	i�����g|n����i��a�e��^�-$]�l��u�(�m�-(,�t@0](�a��e4�gP�id�l|�n��ór�sx�u���X1�/ �fP�lp�n|�t<�v�1".�1��D�e`�tĻ"	�,�2��h�a6�0:��8��d�Glܢm��n؀r�s�>��>]��aPA�0A"��n�p�@]Ȁa��i�u�y�(�Ai CJ,Vn�CDCtCt�l,�pDDn0HG;�KfhK<�n�JD�ehT`Q��\�ot�y�Ui�V�Z��Y��b��d��k��m�v8Z�Z"8_t0�^����f]K��e��bz0�a��܁y�a�����c�db#��e�kP�ld�t�e]0�r�f(	�f�(�eD�\,g<�ggD�ek��h��\�r`ox�np�l��n��r�o2�o2$r�G�����������8ri�r�@�.ԂrԦv8s���.�s��a4�j��{�ti�.�t����a�s��k(�vTy�x�� �sh�i����a�bP�d��e؄f�gh^h�k�l\�m�n�o8�p|�r��s�t�u�vT�M��l��mȃnԃsԐ%������a@�],��������s��p	����܃a�e�i8�y4�p����o(��rT��(�eH��t/;�0�e��"�FkЕ��D�al�ex�o��r��tX�1d��T.��%t��0l���eܙ]��n��rЄsT���h�.Ąs��\�.<��,�n�a��f���tt��x�n$�ax�e��n��r��s`^u��T.<�nD�r`�s���(P�t��v5��X�.��nD�hP�l�nqt@�I�����K��e<�2�#��eȅiЅl؅p�t,e�ga���lj����i��r�k���$���nD�rL�s�]�a\�e��i��k��l��n̆s؆u�����
��T�ep�n��sl�*�����x��ܦ=	���,�Ĩ���]��o<�P�0�04�x����Ćt��i(�T����a�d�e4�lL�3�2�l��l��e<��bȸtܰ$�e4�(��W@�r|�s��t\���H�a��e��h��p��rȇu@����G'P�t��el��T.@�
����=��r@�����0$��T. �lи��Їa(�cD�dt�e��f��g��j�kL�n��o��s܉tx`v�Ô����Z4�hp���0��t<�rd�sX�up�l�s,�Vd��0��WT.��a��m̻�0�T��h����t��j��l�s�vt(�xd��H!.�*Ȉn̾dԈeX,g��$��u�5�0L��s0���e8�lD�v�,�p�0�e8���Tl�ex�i��s��t$���d�t���0��00[�08�'��r�D(`�"��r������ẻkԉt\��0��������08�����\�]�`o�s<����n0�sL�tLp(�v`�/l�I��JP�ed�p�� 
@�H�s|���]\�s�r��]p�a��e��"����k$�����k������a�e�k�o�p�s8�t,����؊t4��0l����l��d8����|���p������$�n����,�ap�e|�i��o��r��u��y�B����\�k4�"d�r��i��l��0,��D��0���|��0h��������|��̋rt����kD��)e�k����؋e�ih�jp�nx�o��rČt�y@,$���aP�d<�g��lD�mT�s`�v����(<�5����L�tX�'�����0�i|����e��������r-L�����e(���lԌr��t��e�s�-��\�k�m���8i<��,�W.$�kT�����a4�eH�iT�s���4�p�,�gH���@�e��(��]��a��e��i��o�up���������|�n��r��s����b�Cs����(\��
��d�e�i��k�l�cn�r �s8�t`�v ��X�-|�JH��vt������ؾ.��%�.0�t��xp����.L�eX�r����.���0t�-����h�����8��0����d��e��mĎrЎspl[�(��`�����iX�x��i���؎n�r��t��`�
��z�s������. P�a��b��el�l��n��o�r@�sd�u��v����2`�l��nht(t�i��D�l�e<�P	�P	h�.��d�e@�h�l�n�r`�s�t8�ԏa�e|�
`ȉl��(�
��i���g`����e���4�.�a0�i0�s8�u��[
�;���0�n@�d���H�aH��T�t ,:|�i��u0.��.��/�
L1��n�0m��iX2��.̐m�s�t6��5�Đa<:�0:�ؐa0;�(=��<���sT<���a �i?1�>���k�C(C,�r4B��4�eT�h\�t��
1�HP�R��elt�t�V��e�V�܂Y�W����gWX��e�\��������]�a�d�e�g0�i0�jȖkЖm �o��p�r8�s��t��ud�v��y���t(�]��
�a@�fH�kp�lX�md�n��p��sؒu�v_��_J���`��P�ia��T�ep�l|�o��s��,b���k(��`b`lb����a��s�b1d�Ēpxd1<f3	Љef��̒rl1\fJ�l��r�1�g�@l�h�k�k�a|�dܭeT�f��gȓh�i�kĴl8�md�n��p�r�sT�t�uЕv�ydl#1m�.m��p�a��r�m|�?�oR��v�n����e��r�o)1p*�o-��aܓo��\�-1ț��������p�hp����g�q��q���i,�s\��$r��$�tdsJ�t&.�tt@�a��e��r��t�t��L�d�k��s�tSu�t31u��nu�(�;1�uB1�vH1�v���e<v���l8���w̔k�v��Ԕi��o@�xJ�b�xt�o(�pH�t��Sx��pz� �rP�+p{��4���z��<���{����.̵ap�s��t����|O1-U1���x�e}��l(}t��e��^1T}�����0�e1~����n�v�}M��e��T~k1�~���F�L�r1�~��4�.�~�����������/��X�n<�$�e`�g��mh�n��p��r��s��v�����	����d�s|�t܉�
��.|�u18�*|�������n������e�(�-$���uT3|1�E�ܖe�s����n�r4��e�E���1L����'.T�b\�dl�f��i��k��m��pȗrؗs�t���$��<oi����d�fl�i|���x�d��1������s`����s��P����p�����"l�� �]Зo����]�eؤ����X����n$�E�axd(��� �kp�m���	(�a��e��k�l�m(�n<�t��vt��lg.4��	d�e(�(��|�m��ndv�<�����d��ĘaИv����1����r����(���ؘ�\��l����g@���a�a���yx���M �o<�����4�eT�i|�
��L�g������`�k����h��ȯ��o��1ܲ����e��-������aܙe�f�g�k �m(�n@�pL�rT�s\�td�g
p�(��
ضx�a�e�sl�1�,N3T��ķW4�g�t��e\�����i��"��*��"���"x�a���p�n����Us,����s�����kPpĚvКyP����������e����V.x�a�e��n��-�r��td��1��n�����r�s���1��\�a��bԛed�i�ld�m�o�s�t4�up�y��Ü��'.��n�p��r��v4��1(���x�mx�v\���i������k���,�A�|��]��s�u��ěl�p�r8�sL�tP��p��h���o�����f�i���t�m����$�i@���,�t���1d���D�rL��x�eh�X�l��n؜st�.���������d��gxVi��nȜsМt����
@����ed��1��������;���pH��j�p��@����<�T. ����el�(�lD�t�����0.��
L�e����X�k�d�r<���8.��s��y�������|����
��1��a,�hlНr؝sp}��1L�a�e�i(�y�J���b\23x�,�u$`
�f�e]0�l��n��r�e��8�a��b$�d��e��fԟgȩh|�i��j��k(�l`�m�nP�p��r �s��t�u�v����fx��e�fP��.�gJ�bt�l
L�'.��a�e�gОy��/X�n�r4/���.
}) j��i�aD"eH�r\�s��u|q��k@�i`y��k�T�mx�t��v�z`�k�1p�e|�lOm$
�l���gpl��n�Pn����j̟t,p�#Xp��e�i�o@�r`�s�pi��v`F�1�M�pq�r�r�$�p�[a�_-�r��,�s�rK4�eP�o�`\s�hs#X�a0u���'.��e�t��l�d�4nX�a����u��a<v���4iРk�l��o�s�v�w�Lw]Ƞo�u�S�w���l�w�1dx2�LnDy�1��e�z��|"�g�z���e<�jD�of�]�m�1l�mL�r���T�d��i��m��s̅�l�x�n���X���l(���e��,�����r�t��t�( �̡lĈ]ԡa�d�e�g$�iD�sԉn
\�|�t�rD2S���tLN���80�jL�t8�k��JH�ap�e��p��t8��@�h�a�5�]|�aL�~ܔ`�����aĢd<eТg�i��o��0��d8a��2ܢl�;���<��e�\�ܡd��dȡ������
8�]�a@�ep�k�:l|�p��t��-kt�sT�v�}���W���'\�eL���d�l��e�d ��1P���sp�]��e��iģo�t��.X�2 .��̣t��tԣe0�����n���,�ap�e��o̤s��x{���d���� �l@�n`�r�*P�dX�e�|#�|�&�*8��1@���h�d��r�b�1�����.<�e��n��s��t@��.X�̉�1ԲH�2$��Ĥa̵��a<�eȥhХi�l��o�u��
�r �s4�i����e(�r������(�o��0�d8�e`�ih�l��s��t��u��JD�;x�e��i����gԾh
��n����6#��-��m��;L�����i,�i�����n���1u.l���ܥe����d�ep�(��t���8�EX�a��e�i(�o���,��14���@�ap�i����H�d��n�bxl�h�uh�W.����|�e��������e������d��iЦk�v�x�����Ȧa�e�.L�`M�a�����a����m�v"���ee04����d@�pX�s|�t�9���ra�:.�:]L�ah�e�:T.��;�;]t�s<\���srhv@=��������`��id�B��a��c��eĩf̩hܩi\�j��k@�l��m�n�oh�p�s�t�u�v���4b��`�dh�g(�l4�mD�n��r��t�e��Ⱦd g�	�g2�g��<�ah�kt�od�s�h.�h]\�e�h�\j��i��|�kT�k���.�mW�B����utnh�.H�d8Ce��i�l(�m<�nH�rT�sDlt<�u��vqiq���d��g�s,s]�e �vu`tuJ4�b�u2v����ox��H!.z�x�k��l��p��s��t����z��p�u({�8�iP{���ap{;��2|{��r�}?�~��ă��ԩg��k�l�m0�n ��_��]�ld�( �ux�2 �i����(�n�������<�k`���D�k�P�at�e�2ԉ�l�f��g��n8�2`�*T���X�.�a �e0�i��j��l��oܫr�s��u�v(�ô�s��Ъi���تb��n�r`�3	�'(���i�t��"2Ў��)2@��(�fL�ld�n�/2����D�d�62�sА��X�n������p�d��Xx�e��o�"��<2@(�\�id�����aīr̫v$���;*|�����ԫa�A2�F2(����n���p��
�a��i���4�pP�����8]��Aa���l�a(e\Fo��u���@���`Bax�n����\�g\_L2̜(	h����k�������s���������Ԭ�T�
0����rD���\��Ȭ���"��M�u�������k,�n�GrL�sX�t0�vp��8�e@�n �d��.|��X��D�e�����"@�X`�a��e��i��rl�FH���g��i��l�����S2���x��̭eحi���
����ĭd$�*��PT�8�����k�zl(�n����a<�e��i��oЮr��Y2��� �k@��-H�4�.\�dd�gl�it�l��r��(ܽ�\�`2��(��f24���|�r��.��elN��
������n��������n��Įe�iD��
��]��b�k��n��N	��"t�m$�el�i$�i#�����a<�eH�kX�r�����n���
8�
���P�e��S	����d�k��n4�n
@���|�d���4����r��yl~����������q
`�"<��a,�bH�e�i��j��l��nܲo�r8�s��t��u$�vL�wT�y�����%����	�aD�fT�k`�l��n��r��sܰt�v��(�:
4���L�t8�*�wat�b��e|�m2����h
|�v<���$)l��o(�t����4Lt���`�����t4��+@�Ȱs���аe�o��rt��,��(�s2�J�k �Ô�?&<����P�x2�R��J4�e\�<�d$�k�mmt�n��p��r�sؾ�����l�.D�h��*$����i���"��s�����aısD�|2D����t��.���бe,��ܱm���a��2�*�s��d@�ex�fL�gT�kd�n��v��m8�t��
0�h"|��\�a@*d|�n��s4�2X:
������i�"�����ahG8�e�9�]Ȳr���вa�g��sn8?�t�*�:(�a8�eijiسo ���*��-$��0�g`�ip�k|�l��m��n��s��v�8��2���h�o�
�[�c�����s�i��t��00I(3	L����t�*P��гl�m��p�s�* ���Ua �"��!���d�!��0�����"�#_h�at�i|�t��vlg. g�P�e\#��\�md%�@() �)~��i*��*�2�4��4����dd3����nдr,�t�5�L5nȴa�e�s�è5g%��n�r�c�	��59`�6����47�'.8�aD�sP7xxl���8;�82`�d�8�2�;Ct;��h�l��v����p��`=il@l?����r�?����a��bH�cd�ep�f��g��iжjضkH�lT�mķnL�o\�p��r��s��tT�v�@J(�b<�r�n
����dt
��nXA�a4B(B�24�i�B�F`�EP�e4E��X�r�F�����Gx�rH����a��oXI�2�J�ĶdKt�J��r�J���e�K{%4L����.la�e,�k8�r@�t��L��t&�%���ipM�r0M� �e�Me�N�,PP��xy�V�t�e��o��s�����
`W"l�lpX�Yt�X6��k�Y(�Y�����\�TZt��r�Y����d�e�g�i$�n8�sD�t[T.�[(�[�l�[t��e�[�'�\i�\��e�]ct]"0�j,^��^Z�`�2P_��T�sl�ua�2�b���i�nla��t�e��i��n(�txcx�e�&�a��e�e0.i����.�a�e�h$�k,�oH�s`�t�h��kxd�Pj
�i"��l�7/�j�edk�pl|��(��4�dm<�iX�t�m��mMl�r�nph�.<p��t�a��e̹fԹgܹh��l�m�o�r�ps8�tL�v�r��q��t�r�r�2��e0�2��sG�aDt"pt"�t�h+((v�lD�p�t4vt$�a�+(wI$x{%��ad�eXx���.�nx�r�x�@����ah�eT�i�j�l��n�oH�pd�r|�s��t@��zT.ܺk�l�m�n0�rD�s�zZ�t�P{Z�il{tx{����a�|g
�|��Ьa(�d�|�2~��4�.H�2T��<�a��s���H��T�a����\�d��i��l��n̻r��s �t�ă����s����a��e���(��	Ļe`�\���l�.�a�uć�2��"�u؉����k�l4�t��� �������.4��2���,�r$���н���Ľ�����e��k��mļnؼr�'`�p�eH�]x�n��rT���]H|a���
��,�:��r�����gx�8h��мo��MX��gy�����"H����f �p(�r@�v���2�����e8�s��D��l�	��R
����P�rt��X�at�i<��=��e��k��l��i$��T�OD��2$���a���2�����e���g�p�r0������Z�M@�����at�bT�d`�e��g��i��k�l�m<�np�o��p��rпs`�t�v0��2<���L�r�;l�sh����"l���t�e��l��I���M��s����t��jܾk�u�v�Ø��rh�]оe��i�e�[x������,��<�iL����e,�pܶ�2\��h���4�a�ue\�ih�s(/��,T�l����J|�k<vi��
%Ⱥ����e����at�e��kĻ.@��
�T.�k��]��a$�e��k�l$�p@�tL�y,�@`��j�l�����2����������,�a��4�r��$�.0���T�a��e��i�0r��x�x�k��r��s��������i8��2|(	d���n��2X�M�e����bD�	<��d�r��t��Y����.,�r�����a@�e|�s0��8�m���2���.X�i`�rh�s���24�*T�,��.�J|���t�v��t������a(���d�e,�k\�ll�m��n��p��r��sD�t��v��yX��.�l��m����e��(�^������B�rd'^	����jP��� �kD�nL�oT�r�3�
3$�9��t(X�h���d�ex�]����x�a��sH������t8��
������sL�O$������d��k��n��r|�S�����,�����.�a(�e4�k<�s�]�n�g����t� �r��������2T�a`�e(���X�.X��.p�g��(~\d���x�rt�����a��d��e�3�����.��r��s�*$�E����	�.��a�d�e(�f0�gD�mT�st�t��`�����+�d �l�����i��<)�x<�e��{L��31��L�.���x�`�r����h�ed�T���l0�J��a��r��s��t���
��g�i�k4�mL�nl�p��r��s`�t��v�^��T��e����(���������# �8 �ex�.0��,�eD�o\�����\�dd�s����H�.��dx�n����d��a��i��ut���.��r��s������*X�����n��(�.�X��a��k�l(�tD�u��o,�8����W4�n<�r����ex��4�����.,egt�k,��L�e|�i��t�m(4i.�o-�M4�e �h�a��b��c��d�eL�f��g��h�i�j(�k<�l�m(�n��o|�p��r<�sX�t��u��v�z����a,�bX�cl�d��e��f�g@�hh�i��j��k��l4�m�n�o�p��q��rH�s�tl�uD�v�w�y����Jؾ.x�r4�2��J��rȘ3��*������e|����h|AiTA����eЙ���m�����a��e�r�sȚ(|���lh�*x��a�3�mh?f0�k@�nl��������8�e��-d�ip�r�!3̟P\�a��(3���t'
H'��|����8Fa��e��n��r0:'����d��l��s��(`�.3��6#���yd��M��e��0�����s�����M�a����
 �.`�ax�e��k��l��o��r�t�u4�v��X�X�bp�d��33��"s��
|�]��eX�:3��et�t0;�Ȭ"��t8����e��o\�*8@�|�]��nT�(�����l���a�i �o(�s��4O�d��(�t��@30���	�ap�e��i��lD�o��s��tT�u��Ðli#�"h�b��n��tLun
�����gԵ(��J��]��n����e���غF3$������kĻ��e��*������@����r@����e�Ao8���T.h�a��c��d��e��f��g�i �l4�m<�n��o��*�`�k��l�m��v���L�X�,M��e(���m��nx�t��o�k���2��K3<&��t��a��e�r���!���$����a\��#,�e�8��9=��T�eh�s��(�L�m��	��t`�l���x���t�a��r���������b��d��e�i\�k��m��n��o��s�t�y��H�9�� 4.��g��i�l�n�s4x`�*�����P3�����W3@�eL�n���P,�e4]4�n<�*�����T�ax�i��v�]3�2p�t���$����oP��Pl.`�����a|-��r�'%����p�����0����h��l�F���i����e��T.8������`�y�$�lh�t8���	,�ep�h��i��j��kh5p��s�tP�y�����l�(�x�a��k��s ��<����xd���u�e3������i��o(�$�(�e��t����e�Ü���k%������������.4�e@�oH�r0��8�,�m�����2X����t��a��e��f�i	o(�rl�s��t����d���(�2T.��d��l��n��r��tx��(0�q	�j3h����.��a��i)�����'���e|�y �.8�W�l�0,�K<�aP�e`�o$��H�s�3	T���[����X�m��'x�l<'�1x�tl
a��rx�r3@�J��k��l��n��r��sD�x���,�L����
������uX�J��e4�h7kh�"�.�n������e$�ol�2�L�ax�eH�i`�lx�o�u\Pl$od�rH��X��\�d�
�x
p�ab��dfTk��l��m��n�o�r4�t(����a���t��������h�����i��J�������a�v'�Ugp��e,�o��x3�����r\P<�aX�b�=	`��"���n��rd!�h�e��i��n��p��r��t�"�"7��d�"]�'.��eTm�0ui�t����d�$�d%}3�%�x%����e�&�D+�P�.L+���r+��a0�eP�s�+xD�rF
�+<�e0-� 4Md�o�:th>m|�e��o�H2�j
\h��m���a�b�c@�dx�e��f��g��h�i�Kj��k��l��m��n��ohp��r@�sd�t��u��v���܅�3�l�m�ix��3P��$�kh9m�D��,�ť��
4�a|�b��e��f��i��m��o�r(�s0�uP�v�Jyl�ØE�3����.1d��i��n��t�Fp�����PZ
�2��k��r�^c4acpgn#ď����k�m�j��eqY����tl��"ؐ�@�nH�s�4�E��\�eL��<�̎��d����a��f��k@
l��s��v���#T�Yh�������-t
f���3��lԖ����a��r�ou����/ܚ%�A �n�����eg0�lD�nd�s4�{L����((�dH�:
���<�dT�i�������J\�kt�o�����d���	|�i��j��k��lP<n��o��r�s��t���3���3`��|�H	��3�O��e ��ȫ] �ad�e��i��l��o�sD�tP�uX�v�yl�Ü�<�kP�n\�r\�(	@�"4�s�a�3����H�e���
��2T.�Md��i�<k��m��n0�T��3Lu?
�����g�c�"��k�Nsd=t�Nv̱i����e��o���3 �����m��"��k�v���<��HOi,�j4�k<�t�����<�����OrD��d�iT����������=����@��	���3��J��o�.���3��t��ad�����g�?k��l��n�o(�sd�t��y�8i�gd8��eD�h�y��"@Rm�r,�(H����t �i<�mL�pP9	8��3,QXD�l��ix�k��X�e��o8@r\W�Z(`�����m��(X���t�i��s��x����.��alAd$�e��i��l��n��o��s��u�y����4�"��b�d�lPAn��\����adUgL�i�Ak�%lh�nt�s��td����(tT�e`���\�s��*�s��Z��W��b�k��n���3x��
��Зeh��34�i��J��s����kX�i<�*�d��J��n�s�T�(��
�8���,���~�,��8�k���3����
�.|�a��hXWj��k��l��o��p��s��t4�u�Xy<��4�X��|���e,���eP�t�������[������.��a��et�i��o�rHXu�����W.\�a
����o$��|��3���(X����������lE�����H�b��p��r����
P�a��e��i\Yj��o��rtYuX�v`�yh����3P�(����|gik4YlDYp��"�l@�t�"��n���w��a�e0�i<�uL������n�r �s������4���(�kH�(�!�3�!��D��\��3�������x��LF����� 
��s@��3��-��a<������n,��HZ�G�J`��,������<��$�aL�ep�i��r��u��*�a0�t��r<�v���<�(�a@	�PJ�	D�kd�s��4��\�s�T.��b��t��	4T���rdJ�&4�&����s#X��u-4-���s-����n,��n��s�-".�-����e@0S�a(�i4���/T.�Jx.G�����sP�e\�u�y���a���T.����a��c<�d��e��f��g�Kh�Ki��j��kL�l��m��n��o�p��s�tl�ux�vT�M��l��n��r�\v��������e��p��� 4.H�Z�h88Z8���l�7���e4����(��Е��X�i`�jh�sp�t0��@��X��Vt�N�#4Ě��x�eܙ]��lp�r��s<�D,���]3��]��f��K��ax�]��rH�% p�]��h�j�^o$�r8�sD�up�?t�]�'.��n ���o��i����0�a��T����at�e��i��s���Z��i�h�t4�v�|*4l�
��nį$�������v\�J�a��o��pt����04$��T.�rи����a�e@�nd�s��u��v��y����WT.,�r [
p���$�s��54���8�hT�s\�t�<48�C4��t�p|�t��J4��x��i�����;��
<����v�����n������a��eH�hT�i\�kx�l��m��s��t\by��8�(��2�kNl���D�P4L��������tht�(�e`��0�gp�M<�a��W4l�]h�od�����	��8p�a��e,�@���;����k��p��]4T�\������k������a��e��rOu4�a4��(D����aX�p������a0�e8�j@�oH�rX�tD�2�����|�h4�����tP�s�i<�Md�rT�J���(�������������.��n��r������a��e��i<�oX�up��������.��nX�n4\�i��.�(�n�&w���n4�r��t�aX����g�&w��(�
��XD�e���L�k���#��s���d����f����|�t ~��o��r��u���X2��T.�?LT<���o�R��īl�\���p�������]��a$�e��i��j��y �È]
�g,_��Xka�k	T�fP�i��k`fl8�md�rp�s��t��vhp��De�v�v��\�a�x��|Se��k��vz��{t4�{��̵a~���.�}M��e<����i\�]��a$�P.п]��e�����s��.������e����p,�y�~���������\�aT�e`�i��n��u@�����T�i��k��lԹm��n��r0�t��z4(�����l<����ep�".���4��l��t��o(�����d0j�����i��oL���W3��n���	T�
\�]�ah�]�k�W�kX�t$�il������@���<��
H�d��g��i��k��l��n��r��s��t0�u��J�J�����]�u��������iSu���� �t��u��(��J��n@�-��t���d����.ܶa�i�r���4��w�a(�e�3�����*������8�������%��nh�T�a�\b��d��e��k��s���4<�(l����]��n���\�i������k��o��t��i��2��l�2<�_��.���a,���lL(�e��i�s\�WeT�i`�lp�r��s��t��v���JL�dl������h�a�'���|�el�k��t�!�!����a��i���4����eؾ�
d"����.��e�";�0�,��p�M
�Ht��k�e\�ap�b��d��e��f�gȩhH�kd�l��m��nH�p`�r��s��t��u��v��w�enh�r�f]�g���ha�js��m�i|�e��i��y(P*ki.l�pl:
��n�l�T.Pn����f��o�h�.\����i�o"��bpqtXp]�i$�l,�o4�sPr��rihs�4 v��.<v��<�a\�e�4i�v��z���a|�e��t��y�|�T.��s ~�4�
Ԃ2����4.��fkr��s0�
���ȇ������t���Ĉ]�e�i�o �s@�tP�����a���s�]L�4�t�RW���,�e����Jܔ�T.��r`���	P�a��d��e��i�kL�l�o0�st�ä�(c.\�I0����e��l��r4e�4�	\����s��=<�"T.��d��e����4&�8���l��(�k �m88( ��t��(�iL�kd�p(E�<���D�j\�ytF����ܡq
ȡ��l��4��	���n8�]��e��k��o��tL�Al��ܺ
������b�����a������pp�]��a�et�o|�r��s��tP�T.8�gD�iT�k`�rl�s���4D���0�e|�"���4����L�l�����. �SX�i�C��e8�
��_��a��v���4|#���d\#����g����)���a��������r��������a8��3̵�T�al�e��i��l4�nD�o��p��r��s@�up�y��ä������<�a`�t��D�np�L�a��8�e��l��n��p��r`�s��t��u��y�"��tD�t��o`�)p�����a$�U��4�O.����e\�"��t������a�e4�fT�i��$�s��s���4d�0p�,�aX��d�]@�n8�RH�e��=	t�t��t���l�e��i$�W��L�����t,��	l��������d��n�@$���el�����j8����.�n����a�e(�ih��4����.��
��] �n��	�g��<�b`�e��g��k��p`�W
t�t�m�m��l�a���4����J��p�n��d�8l�����d@����a��e��o��*�Ua�����p��e�p�t��$	k��Z��� �[.�[t�e����(�g�4�nT�r\�t���p����'.��H���h�s���t������|������h�.���8����o��u$�ô:]L�a�e04����s���V&��.|V����eS����t�\�\���e<\���d@=��<�����a�4`��4�r�Bt��a��c��e��i�j�kL�l�*m��n�o�s,�t@�u|�v��y���4b���'.��k��nxdhi��g����t�ts]��utn��i��l�m�n �p,�r8�sH�tq�tu(v���vg��4�w��lx��l�vz��|�4�{��|�j@�nD�'�O��X�e���`�r��s�|"l�a��J8�"���e��n��"������g0En`�����a����wb��kT����a�i$�o0�u��4А�g@���np�4d���`�r(���*����8�n��@�ax�e��u��y��)����d�a,�l�g���4h����a��e���4<�������e�yo������4���������m�r0�v�5ܦ���s�nl����i��C�����zl��� �aP�eh�i��o�r@�.H�H�.`�a��i*��]�lx�m��*�����p��r��3	��p���������d@�5(�(��d�e������n���a��e�i$�uH�5��3	�����d$�f��n��D����k�(�\�	����,�n����4�nX�tp$��P�ap�e`r��qh�nt��H�a<|e�Z�h�]��d����n4���Jk�r�yl~���������3	���<��a�b$�e��i�o$�r4�v`����2P�5�e���\���e�)gH�k`�l|�s��@���@�sX�vx�5��(T�(���h�u��p�t@\���a��d�e�fT�k�n��%5�*��a�i�n��.H
|���i�+5�r`���������i��7t47,�eL�i|8(8��D�n>�����X���?d�a��b��e�g�h�k �l��m��n �rT�s�t�@�4E���l�r�slE2�E�	`F�HJ�l�H;�J�xJM�e4L��a�e�L15�L75PP��,a<�eT�l|�txy�P$�gL�s�Q��Rm`�s�S���Th�v�T�p�i�V���i�Ts�W��Y��Dac�e�g�i�k�n�t[�gs�[G
�eP3��[��vd\C$]�\���sH^|,^�rla��lHa-e<�nD�sL�t�e��f��g{
i]l�e��k�t��øi"T.|�s�j'4~(�o�����dk���e�=5H�����a�k"��l�m�a���m���n<pJ��a$�s8�tpR.$f��e���a\#���l�u��a�v) 4vt0�r@�l�a��e��iP{C5�z��X�tz��`�k�l��r��\~����i<��܂����s������gH��������e�s�.�����e �]@��Ya �g4�k��r�s�t����a����gt�2�t,�e`�kl�lt�r��(��L�sh�]T�e ���
Ļ]��n��r���|�aH.e��k�u���H��@�
��e0��Ŀ;��z�a̋e�o�t���0����0e�t��gL�"�s<��0�l(��.0���$�a(��
t�b��d��e�k��nDodr�s�t�y��~��Ml�l�V�X���s�����e��I5��n���������s�#������aP����k�nL�o�N5x�t�����ae8s��T.s��\(t�!�31��0k|��l��|�Lexr����Tk�s�ô'9G ���m��2������,���t\e��H�a�e�tD�T5(����s����0�eX��r�����da s,�����r����.t��4�.`a|e�g�k�l@nTppr�s�t\����Y5���hh��%pn0��. �M�a�e��.x�T.�s������a�e������r�T.dimL�����p����4�WLe�(s����4e�R����gaԎehl����d�a�e�o��u�Zt����r���8t�k,���a�f�r��(�"l�*����� ��a�b4d�f�gh1h0i<k�lTm�np	o�	p�	r s�tdu�v���a,
b,c�d\e�$f�%g�,h�,i�3jD4kd8l�9mxn<Bo\Gp��q�Gr�Hs<Ut$]uT_v`w$`ybzD$��:���s����nP��
rt�����o�y��a5����r����t�&
|�1dPm���$eXihots|v(P-؛K@���`p�d��2��-�f�o�r�tl��Ԡ�����r��D�AС��8Fa�e�sü�2�n�ct�V�(m��l���8r�������S	0���(r�J\ktll�r|s�t�v|�
la�op�
X�j5����a(�e����'�axIi����.��p5��T5����gr0����a,e�g�i�l�o�stu y0������ev �u5\���"$gPiXkhp�stt`uv�����(��(��`oԵ���t<���e\�.��(���lԷ����g���a��P�����p���������D�pغ}5Ļ�ؼ*����r��w)Խp���@�(����Lr���5@����.�e�o�s��\�h@���pe��xr�s@��l��5����������8���h�.�aef0g<kHldnpo8	sh	y����l������5������������t(rl��,�e\��
Te�8� �����\e(�;L��aP;bt;d�;e=gL=hd=i�=k�=l>o�>r�>sPAt�Au�Av�AyB���:aĹe�:i�:k�:l	s�:t�L:d`:ft:i�:k�:l�:m�n�:r;s ;t(;v8	?
�(��$	n�t,	iT	k`	tD��(���L	j��9��"���l�i�	sx���x	e�	s���������T.�	aHCb�	d
e<
i�
k�
m�
s�
t�
v�
���(���	p��H���	r��dCa
i 
k`�+t���;4](
n��"0
e`
k��5���L
k�]T
s,��5�2l
v��t
i���5����
�$���a�
e�
�x�;0���
t�H'H���
e��G $
a�ih�A��i8����
��i���WrP�s8���a@hPidkpt�u��� ��5�Hk�X����\i��E|a���������	a�e�l�or s8tTy��(��k�m$����5��������mr���e���i$�,�a����p����kx�t,aHe�}al�D�d@���\k|s�l�����tpX���
�<a�d�f�g�h<yi�k�l��n
o
r
s$
tH��"x�B���5D��(��5l�5
eЖ(���s���5 �2�_L
a�
e�
o�
u�
y �\�P�dp
s@	�	\
r	��d
e$,���|
sx
�
n�"i�"�
td!��
e�F+��
f�
t�t�5@.���
r0��.�
r�1�1���
ed1�	ltun ��� 4MHaXeph�o�y�3�06�5��Pl8*|i�7��de8�5p�
�:���t�=h>t�a�d�e�iTjlk�l�o\rs�t�uvy���>(4�.>
�a(b4d<fDg`kll�r�s�t���!�>�� i,?(|?�	�?�5Pst�tt@�D@��Xs�@*�f�l�s(�g	��"	4A�5C��0�k�t�C�5HD&�C���k�D�xD��l�D���a|F	�Hh�.,at�b4dDfPidk|l�m�n<8o�p r�s0I�JJ����J-<i(K"\n�KW�Kttlh�o�L�(PM��e�i�s�MT.\N"��k���O!�kPdt(PJp�.�o�PiDQ���.�es`�x�Q�rHRC��8T"b<s�S]aDeXidkxl�T�TL�id�hUPm�U�5�����g��laLW��VM�i�t�Gu�W�5����G�D��[4�e�n�st���
�_���t�a:Ta���k��e<����i�rxb��o4t��;�bt,a�c*�c�@rlc��Hedu\d�di|f(�etm�s�v4e6|e�x��ĮeLt�f%\h�ia�b�kmo؏pr<t�h�5j�&�i���kk�5�j���sd��(lJ$g,m�l�
�l1P��m]4en��8�etm8jn�mHa�e�ji�o�n8�p�o��|k�r�s�tH	��p�	�a���/�
��n|
���e4q6q���t�r��t(Pt���g,k4mt��aTe�j�k�lpt�t20u�|Pbujv]@v�uHl|n�rtv�Pvthe`v��pd�v(�wX�w�ex����j�u���(����l�x8T�a�e(}n
�����t�x�t�`�y��u�z�	(g0zeDrܽ��������0kp{8eTy4����d�\.4���da�|�pk�||at~�f�i�n�p�s�K2<���s�(�g�[��e�J|�4�M�ik��A�A��Zg(k0s��4��<p�^	ȃ��klTy�q
��apb�c�d�efg�h�i�Kj�kll�m<n`o�p�r�sd"tX#u�#v�#y�#�܅��e�g�l m<r`shv��̆x������epsĺ�غx�6��	6��(n,�0aTe���H�]Ln����J ���b�e�lt�,+���tWD�X�eh9�
P�]�k����ľ��
�ad e<gDi\o�r�s�mu�ð@	\��i�^6�Gk��dmb4glJl����Z��T.Ts0����|v0<6�m��hr�"pe�6�e�l���t0za̎����������<����r�������g�i8
k�r(���!6��-,�f�JtԖJ<aPl�rT6'66](i��"0t�"���Hatepz/6,���`p��hs��%�u$axܚ?�J�dLg�n�rl�J�a�A-��"�n�"�i��56���d�JalP<n$rXsd�tL�W�.`��(�;68iP?�p�n0n�C��DrĨ��Le��ȫ��	da�d�eiHlTs\u�Oy�Àh24�t�e���Md�g�<i=m�o��j����g$I�!�~�"����x��=���d��"TNd(g�Nsd=t�Nv$�9"x�n a�( ���4v��<o<�PD��@���Up��@6��q
ps�������a�b�e�ijknp>o`tu�>y,Q�	$�c�'�l�r�s��@�x���e���<�Y�����
$eļ�-u.4l`�H6d�*pa�d�e�gh,Ri(sHtSu`@y���@�|tp���u.��n�t�e�rt�M6�4
�d�Qm�s�t�Qvt�*�l� T6d"�a�"����T.��t�a�,� t��_e�,\6��t$}e,Wp��4i��<eXrt�d6�i�.tr|s������
���eT�i��x���
�.�a�b�e\fhg�i�l�o stu�ytV�4�"�dP�p��k6��
�Ta@�d�AfUg�Ak,nUp8s�wt�wvTq6`���$t����s�uX�y6d�Hrp�Pa���6xa�e2��2���6���b�k$&t�v8�c��)���
��a@����J�b�c�fk$xmtv85�h�����`��l��	=���.<eDi`xp`tD�
D�$�-0�LnD��TeXTn��Jll,Vn��r<�d/4��T.�r�����a�Vc�eL ih j� k� l(!m4!nT!o��p|!s�!t$Eu"v("y8"����
� k r, t@ v�����t<��6$���$ s�}��}-8 a��2\ nt ���twd\�D�e1p��p m� r|���x a� e� iPj� o� r� u(��6��Q���� n�6����� s������6��,�q)!i!�,�a���6����!�$�t��M !i$��H!�(��6����@!����`!t��2��
���h!m��p!a�!t`�(,���!u�����W.�!a�!e�!o"rHXuX�ȼ��!.�����!s#��5.���!.\�"r������6��� "a���H�"�������X"�0"�L"�����{y`���Er����
ؾ.�"a�"e�"i\Yj�"o�"r#s#ttYuL}v|YyL#ä���"k�El���L�*�"s�����.��;�"d��t��6�"m�x��6`��#p�'���0�t#a4#e���,#l���@k����@#��-h#rp#s��@�i���.��Mx#a�#e�#i�#n�~r�����.�#nX��60���#t��������#k�8s0�,+�;L���#t,��$�G��),�`$bh$dp$fx$h�$l�$m�$o�$p�$r�$v�$�`���d�b�$�TG�hb�������#����#�� � (�#0I`���6����$�$�$a$%o�%�8�%s���$l��$l%n`��6LO���%tH
%aD%bT%l�mh%r�%s\(����L%e�����`%a�%e��X�.t-x%n "��.��.�%s�.���%e�.��%d�%y����%����d�����%e@0t<&a'd\'e((f4(g<(ih(jt(l�(n�(o|)r�*s,uX,vl,y�,��0b�/4&dd&fl&gt&k�&n�&r�&t�&vX1(Сp1��2��2t|&.�&a�2���&d�&k���63�6�3���Fm�&t�&u�4��.�&a$
�
�4�6��o�6��0�e'i���`7�"$'e@'r8(�7'd8'l;p8(�ma\:�0:H'a�8P'd�'l�'n�'r�'sp�t(vh<���Ga�>n�'a�'e�'g@�oX�u�>�6 ?h�?�6�@n�'e�'uTU�*xA�'t<�� CJ�'ntC;(lDa`F'%�.����(�(G(�HG�J�dt��6,P�H(g�O�P(n�O\(e`QM�(e�(u�R�,R��(m8U��V�8Z?�(d�Y	�(d�(e�(k�(m)n )p,)rT)s`)tXZDpl��Z�	,[Y	)et[��[]��d@���[")e\J<)dD)kD\8��
�\��\]L)p�\�@]��h�.�)d�)n]Kh)a�)e,*i<*ox*Ì]����.�]��]���)d�^i*�^���)a�)g�)n�pr*s*t*vT_*�_*�)d �,`��_��*t<`*`%
|`8P`��$*d��n�`����.T*a\*ed*s�(�	�5�a�6<b��آd�a��l*��bX.db#�*a�*e�*j�*k+l`+mt+n�+t0d]@v�c�*l�*m�*st�(�d(�e]�e]�*j�f$D��,g+gg+e<+�8��
���(+y����0+�p��hlD���H+��gMT+��g2�gMl+e�i�$+p�h���+e�+r�+y�+Äkd"�k���+dk�+i�k|�e�k��+kl��+rlFl��+r(l���+�TZ��o��,d�n,n8,r p���'.�o��,,u��x\q��D,s�pL,i� pqd,e8r��,yG����x,�trq
�s_�,a�,edx��s��,t�NpX�,s�y���,t��D-a�-b�-c�-dL.e�.f�.g�Kh�.j�.k(/lX/m�/nd0o�0p�0r,1sD2t3uP3v�3�t�T���<-kT-n���t-d����`-p�th-et������-l�-y�-��� ��(��d<*H����-r��'Е���-a�-e .o(.r0.s.�Ж(.eX��-l�Mh�����.���t����7<.t`�7H��ܙ]D.gd.ll.nt.rĚl�
7T���h�.�.i�.s��t̜p�m��7,�]�.r8QN	ءM�.a�.ox�]�.l ��H���2�]�.e�^j/o/r/vT�5 �7ث�l��T��� /i8/uȴ�4���W@/rl/s\���H/at/o|/p@�7t����#7���$���/lи��	�/a�_d��f�/g0j0nT0s\0tx`v|���t�/o�/r�/s��̾�/e0t�*K�+I��0e$�����	40b<0hD0sL0tP;)7��)7�078�77������<���.�0n�0p�s�0v\���0sL�������5L�(h�.�0s@��0t�����0e�0l�Ms�>7���|�i���1k�$����0l���0et����1m؊t����1e\1ip1k�1o�1s�1t42u<2yd�i��T1m��"l���h1j�1od����D7�����1l�1n|�J7@�n�1e��6ȼ������1s�1t�����1a2i2r 2y(2��e ������1k2l��*D�.	������P���=����l2ex2j�2o�2t�2u�2y�2z3�D���d��Y���2g�2sH�P7�����t�2r�2s����2k��t���82�:�;� ���2e(���2r����2�l��
<�M3b43m@3t���|���,3f<pd,��T���H3ah3e|3o�3s��t3iT�U7p����� �)�����3����3a�3e��?�����3h�3v@���J�3e\���3d4e4i4s 4t����. �X�?��-d�.p�Jؾ.44r<4t��Z7�� ]�4a�4e�5i�dj(6k<6l�6n�6o`7r�7s�7t�7u8vL8yT8���4k�4l�4u�J��s$�h���4l�
J���8�4o�	�4d5e5h(5i<5l\5nt5r�5s�5tP
�
�5r�
��]�
J 5e$�`��45sL5t��+|�*p��T5hl5o�!���4�f�5sD�q����5tH}3�t���5�����5ô�5d�5e�5n(8_7�5rP*T/����5d6f6n 6thd7��-Hj7l#�H,�d6n ,06ax6i�6o�6u�6y�6üa�a��\6g��H0.p6s��*�.���6k�.�/2|/p7�/���6��6�0��0m`�u�6y�1nX22�6f7k,7lP7tX7vPn(7i�ov7Dy��4��7sd5�6@5��$7eD7lx��5<7a0;
�;T<�t7a`=��<��l7v4B�TK��@�k�7l K�7a�7i�7s�K�N(xN�7n$Q2�P��7k�Rel�7r8tPd�8eLU���7i�d��V���eW��#e88iD8ox���X��08t$Y�Y����������]�8a�8e9i9j(9uP9yt9�Ta�`at�8a�8sa���8d�]���8n8�t���8e�ki@fi�8m\s�.ds���8a��T.��]�8n<�9e\�2d�x���� 9e��.T���49e`9i���@9d�Us�t���Pp�~���9�h9��������9a�9i�9o4:u@:�<����Ke���9l��zh��9k�9l�VnL���.��]�9e��9sT�A �n:k�j:kT�]:il�(:s���� h�4J�'.�{7�-X:t(�0��l:r<�������:a�:g�6��T���:e����<�7H�7�����:a�	���.;i<
P ��;i�k�J�J4;l��7�
��
�<;e,
_D;oh;u�+\�
�`;e���;�D��Hr����;����.�;p\�;a<b1d�We4<f<<iL<k�WlX<n�<r�<s�<t��v=y��X�
X�P�;rp��;a <e,<u�5��<r�
?�"�R�JD<e<�	p<a|<o|ph<t��	������<.�<e�<o��%l�H���H!.�<j�<l�<t$��h �$��7� q)�<a�!�ؾ�d"���<.��e�#>�%�$��.���=eT���(=r���4=��,_@=��-(�=e�,X=d�=e�=m�=n�=s�-aL.�X/J���7�/���=f,1��=j�=tD���1�D4/d8��e�=�P����eh9��=pt9���=�<BT.(>f0>m�p`>r|>v�CpDD�<>s�D�7T>y��]��a��H>n�DQl>d�DmDGi0GMt>e�G�7�dLI���>aI���>k�Pm�>n?r�Ht�>a?e�?i�?j�?k@o4@t�@uAv Ay,A�,h��I���>dJ2K�|J?g<?iH?mX?nd?sx?t,K�7K��4?gt��w�&TK��P?k�K���.L��l?e���7|L���?a�L�?d�?n�?s�L�,M�8M�
@M���?kPM�?aDf3	�M��?n�M���?a@l�����'@iب-(�P��@p,@rQ2�Q�\@a�@e�@i�@o�@r�{vܺ�
�Q��T@bl@lx��<��
t�(t@t�R|@l�@n�@p�R(�R�S��R�@nS���Qp���
0S�@e�@o�������S�@l�S2�SXAaAo���T��n�T��DA�l����5 U��<Ar<U-xAe�AoY".�X�`Ae�VlAs]�Aa�Z���Ag�]M$]�Ai�An�AtH�:<^"�Ag�^"�_iD,sT_�Aia"$`�An�thb�$�k,BvD$��4B�B� d��d�e�Ba�BbCcHCdTCe�Cf�Cg�Ci�CkDlDDm�Dno�Dp�Dr�Fs�Ft(Gu0Gv�e]��g�Bk�Bn�BrLfP����fx�Be�f����.�gJ�Be�BlCr�xhP�Bl�h)Ce�hii�7�:i�i�Co�j;k](Cnk0Ce�i<Cipl:
�"flCit3ktCn�l��l��^tPnJ��f�Ct,p�7�rn
�rK�CaXp]�Cr�t����d�Cn�u2<vJ�4i�Cr�Cs�x�(`yP.Dy���CeDi�y�7�|��z��De<�j<�l�(Dd`DkhDl���0DipDjxDm�Ds�Dy��
L�( ��(��7�-t�tL�W
Ĉ]�Ds�Dt��O��J��pܔP`ln@/r��s`���	�Da�DdpEe�Eg�Ei�Ek�Em,Fs`F�0���Ya0Ee<EnDErLEs\EìQ/���Ed\�$EnĖ�7�� �V��(���TE����\�-hEe�Ek�%n,0t�*����Ee���)�Es���/���<��Ee�Et�$8�
�ElH,&�Ea�Ee�%��� FlX-�h^sd-��F�p-�F�t��	LFk�o�0��8F.<���@Fa��*ȡ��XF�(���lFlt�s`&t8�]tFe\mp�Ft�Fy|�*����Fj�FrЧܧ�Fa���p�]�fa�Fe Gs�;yP�T.GiGkGll�s|�O���
�(����(��MDGeLGiȳn@�2��%H��̵�pGe�Go�G��i|��7\�]xGa���Gt��������Gkt����G�8�m�GaHe\HitHo�Hy�H����Gn�Gs��������Ga�����a��2 Hb(HiHHnTHo����(e<��84Hm���<He��7���.T54Te04��hHd�Ht�;"Z��`?�Aa`���Hd@=���H��BtIaPJc|Je0Lf<Lh�LiPMj�MkdOlPmlPn�Po,Qp�Qs�Qt�Su�Sv�Ty�T�4b����.4Id<IgLIk�Il�InJr<JtcP�c�4e�xd��DIkdIrlIs�Iv\e�7le*�te�;��&$fR�e���Ia��d�Ipĺ�7|h�7�g���Ie�f�In�Is��t�hb&Di�i�"�Ie�Ii��������I.j�In�i��Je$Jm,Jtlj�7�jT6�k�4Ji�B��B��HJh`Jo�m*�J�n�hJltnpJa�Jd�Je�JfKgKi<KkHKlt�mTKn�Kr�KsLt<�u`oJ�Jd�Je�o��o��o��Ji�Jr p�8p6Hp?lpJ<�eqEq��Kdl�e,Kg�q �r�r��4Kss] �vv���.lKe�Ki�Ks�v"��.r�Ks�Qt�v1�v�w�7x��d�.�KaP�iTx;z���Ca�Ki�Kk�Kl�KpLtLuLv�z��z�({aP{�7|{��{x8�{����e<�s�^	�~(Lr��PLaXLe`Li����� �iԂ8܂hL.����pLe�|Ld�Le�LnMr,Ms$����P�Lt8�]�Ln�LrT����.��O�����Ld�Lg0EnMt��8��\0��Le����]Ma��>,�]$Mk��*`���8MkdMv�@MalMe�Mo���ԉ�l�f��8D�dxMd���Mr���Dfn�MuT���	�Ma�Me(NiLNj�No�xr�Nu�xvOyD���
���Mk<�hH����MeЎ"�MlNt�;���Ne��
@�� Nm<NrDNs4��'`����X�|atNe�N�|��
�!J`Ne���hNlLxn(�	4����N�4�2��x�Nsd����Ngīr�P���Nt(����Ne�Ng�NlOm�S�ؖ8�����Ni������. Oe0Os�!80�'8 �S	(Os�a�����<Og����DOn�Or�Os��POa�Oe�Oi�gy�O���.8���Or�����Oaԙ�,�D�g�OkDgr\rx	����Ot���x��OdX�������O�$�2��M$Pa�Oi0Po@PuT+�$��̞Pl�������8PrPPsT�S	�	(	ġXPr��M`Pa�Pe���
�|Ps�68�����Pd�Pf�Pk�Pl�PnQrQv8��n��p�G
8�e@�n�Pd�;8�]�Psl����Piܪ"Qn��,+��,�r@�X QaXQepQr�Qu����;DQsH�LQlhQt���x���Qo��B8\�I8|Qs��"��Qr��
�he�QðO8����Q���2�����Q.$RgRk4RmDRn\RrtRu����Qa�Re�RiL{jSo0SrhSuT��,Re@��|�'л���T8���<Rk��XhRa����PRt����.��(���H�|Rd�Rit�l|�m�Rn�Rp\��x���|�k��������Rk�RlSnSs�[8����Rd�RfSs|b8P����(8�h8�����.����(SuTS���n8|�v8@Syh���HS����X���`Se�Sk�Sr�St(4
"���&��}8���Sk�Sn���H�G
�����Sg}��|���Sg�����SnTrt�X�SaTeHTi�Tu�T�4���0�a��� Tg0Trx�5.����(Td@Tn@����dTlxTn�Ts�$��\Tl4�G
@���pTd���,�W���l����T���x���Td�|t���4���Tn�Tv�Tyl~�� U��T����	�������Ta��2+D]n���Ue`���Up0Us���4�.<��Ua�VeHYg\Yil*l�Zn�Zo0[rX\st\t�\u�\v�\y]�����	|Ub�Ug�UlVnLp4VrXVsxVt�Vv�C����Ur��(p����Un8����Ua�Ul$��@��
x�]�U.<����UaVd$)l$Vv��?
l����L����,VtLVv���h�DVi`�tlVi��a
��dVa���*�J�Vi��r�Vs 7����Vt\��Va��eWfWg,Wi\Wk�Wl�WmXn$XoLXpXXr�XsYt,Yu<YvT����%���D���We$Wihjn���H�J<WnTWs������iLWs�����@�����apWn�Ws���8<�����xWe��<�g�����We�Wi�Wl�Ws�Wt��"�rg���
��<��8x�J�xa�b�Wi(�(��Ws������.XaD�e��h86s���8��i8Xr������0Xi���$��DXp���.xXe�Xi�Xs����pXk�Xs��
�����XtX Y�����Xk��Xs�X��"���.�"���Xa�"�Xr�"���X�D����M.Ye��c@�"Yl���(*aXNe��	8�h �-4YaD��7@M�Yt�PYaP�d�Ye<�g�Yk�Yl(ZmDZn��o��plZsd�����m�Ym�� ��Yp0]�Ya�Yi�Yl��<��8�Yi0.�`���YeZo��2T�8�Z.4Zs���Ze@����|��<Zd`Ze�id"�WXZtp	����a�Zt�
�8�
��|Zi�������Za�Zb�Zm�Op�Zr[u$PvDJ����Of�Zn�Zs��,�
���p�.[e[g�8�B�JX���[n�$[aX[e�[i�[o\u8\�X
$��P[pl[r|[s���;�dt[i�[kD���!���[eD!���[rL_�[a�d�[j�8L���[n�7t�7�[i�J�[lP���[l\m�Y� Y@ ��\lP"�!��$\y�!��,\�|&&0&��D\j#�L\k�4ll\t@(P�*��3�d3��|\b�\e�\l�\n�\r(Rt4\\4x�4��5�8L5n�\o478�a�8����e�\rľ��9���\s�>��4�.>��]r����]��?T.t]a�]f�]g�]h�]i�]k^l0^m<^nH^oP^pl^r�^s�^tH_uP@2l?��l]n�]v�@��G��]s�F���]t�G�HJ�]l�H�H��]axJg
�K&�K���]e�J���]t4L��T�8PP���]t^u ^y U��U
`W��V��(^e�Y"H�g�^XP_\^l`e�b�la��d^e�h(�'.i��x^a�^e�^k�^l��s�^tpj��i"�^n�^r�j��k�	dk���^ll��mt�^a�m2<pJ_a_e�plp
�q�. _r�rE,_i��x�x��4_e�wM<_m@�p_a�_e�_i`u`�z���l�_n�|r�|���_d������_e\����_l�����_r�_s�_��i�_e�_k���H�]�_t��]L�a`e�i�2$���l� ��@�t`a�`d�`e�`g�`h�`kalan ao(ap4arhas�at�av�a�,����'.���h`r�`s���D��`t<�J�He�h?f�(�`e��i��̰M�`e�t,�e�`laoP��	 ��`e|��,����/oh�J��d�(Ⱥ(�Ua����0aHae`au�����.Xav`�Ŀ���|ae�ak�at8��`�]�:�auX�x�20����ae��rX�-�aa�aex���.�akp�������.\�5H����a���M<�(br����.<bn\bs����ba8�����4bd�8����Hb.����Pbt(��
�bdce cfHcgTck/ltcm�cp�cr�cs�ct dv<dy�]����ba�bd�bscu@��8�beL� �2����bk�bt\{�0z�bo���t�]cn���8���0cl8crD��#�������@crP�����odcsP��h���lco�cuh� �����
�����ca�ce��y��X��cd,����e�ck�cl����X�
��(�cedrdt ����n
dr��t����d0de������r���Td.ddapdhxds�
30�9����\dn̰����n.t����ra�dd�de�dj�dkel,emPen\epter|es���de1����dd��,��%�dn``tH� ��8���ea ee��]�S��ev��Ja��.��t8ee����Ded��hen���sa��s���ea�ee(�i4�K �]Dfc��fLfk�fl�fm�fn�fp�fr`gtlgutgv���ea�gb�ic�idplePnfXpg�th�ti�uj<vk�zl�mĈnd�o��pX�q`�r8�sp�t�u��v8�wp�x��y��zԴ�|��xpfkd���Чap�Xfn|�]dfa�fu<����	0����flh�t@��8�x�dx��8��H���fe�fi�����fdge<giDgm0gì_�4��fn4@��gggn����D�h0�v8���(g����$���������Lg�����Tg�@�X�P\��gk���|ga�gbhe�hi�hj�hlioirXispiuxiy�i�d(�]�gs���gkxD
�glhohrhu�q�T��x
P,haHhr�hs�
���4htp�<heXhs��lht�HiD��dha|he0�i��8\��heHS�{%`��ha�he�hi�ho����x.th�.�hn�t*pp�hg�� �d!Pia�!d'z
(ia4ie<ii�'�	@/r�'�(x	P*PDir$*Liehit�*�8+��."�il�/�d1� ���i��i�3�	 4��ik��y�9��Ws�9�irh9"�ieh> jadjd�jeki0kj@kk`ko�kr�kslulyHl��>>jb4jfDjt|?�x���D��<jt�F��itjl|js|FPje�jo0G(�GM�t�G�HPt�b�ji�jk�p�jrp8s�jt(K�K��h�o�ju M��S],�l�X���.�js`�O1(]P]]�jn�[ke$kg^��]]kllc�e��di8ko�j�1�j��Lkl\hTkmpkv�m��op�mxke�kÄs8�s���k�x2�kat��kk�kp�kt�w�	���8�y��ko0z�1�kod�(	\{���kl4��t~ls��T.$lb܁%$��ȃ�,lkXls`ly����hl�4l������<�\�
�lf�li�lk�ll�lm�ln`mr�ms�mt(nv��Z�(����lnĨZd����lsȫ���������lud��maT3emgHms��t@��'���tmd0mr8ms@mu0��8@��8�����4�.���	tmrx���Tme�mm�ms��
`+��|maX�R����mk��]�mi�mk̆���mp�mt�
|�����aĞe������mn�����manhni���8��p0�$�e��Mnn�"�.�ng�nr�ns�nv$��4na�ne<of�oi�ol�oo�orps,ptPpyo�������na���*�na��t���ne@	J�	�b��i�nr�ns�
�
���naD����nt�����o���8@��od�$on�]0oadoe�oi�or�ou�oyP(�\oktos�P|oa�od�p@||]�-t�"4p���oe$i",#�@�.ps#X�oa�epi�#�p%�
�'��a petn.t(
<peHpsH)`�l�*�$�-"@0]
�pa�pe8qgpqi<rjPrlxrn�ro�rrhss@ttttudt��/�.�pn�pr�pv3��2���pk(4��3���pe�6+�8�.��d�Giqnqt�>���.�>]�pa�E*0A4H]qa�G qrHG,qePqjXqs`qu$I;�IV4J�K��Jhqa�qe�qf�qg��k�ql�qm�qn�qors rvhKm�qv ���K��K
HLJxLJ�LJ�qgM�
XM'��x�N���qarr�M���qt�N�8lOJ|Pa
�O�(rr�O0re8Q�	`Q��Hra`ry�U�tWPX�dwl�VMhre�ri�roX�8�F�&�X�rs�Y�.@]t]K�ra�re si0syDs��^���.�rg�risrstT_8x_8	(4`9<`��stP`��ai�a��(sd�a3	�a��Ts�<s�<b��bZ�sndb#\sa��e�sk�sl�sp�st$typct�e�*jg�se,g�,h��sa hi�i9�h���se�si�sj�sotrlja|���j�k�tate�j�0k��li0tv��
9�m�0m8tr,9�r��tsLttG��Tt�����n�nDs9�s��te�tm�to�tÈppX�te�y���tt ����tad�xT.T�����t�T�����	�ta0udLue`ukpul|um�un�ur�us@��Е��(ui��rDutt�ܙ]��n�Xr`�s��(��T���hua\�и���.�ug�uo�us���`�������up��"���.�ua�ue�ui�����"��W��vevo\��.��T.��vf�vk�vn�vr�vs ��
 va�vep�jLwk�wl8xndxo�xrDysztPzu�zv�z�i*���xvt��9<���vi]�vk��*d
 9��vf�vlwnws$wv$%9`���vswvP*9p� �H��wk�?8$?
 $��,wd�#4wnl#]@wahwe�wj�wo�wu�$��wr�wsx)�%��xwaX&��'"d'X�we�(��h�.�wl�(��+(l+���wn ,'�waxexu,x�H,���.�wm�,��,.�.�xt���0��/��$x��0�Xxa� ��Dxa�0LxsX22.�xl�xm�xp�xr�xsX7v@5�t6��5��xs�xt�6/9�7i�7���xe�xp8��8�	08���xm:68�xeH:T<�ye(yu0>59x=��yo yt�>Y�@��B�B0yl4B��8ya`ye�yi�yt�yvCP�yk�m�ynl{r(�s�yt\C(�yrhr;9�C��-��D���ye�D��yd�����?a�HX�yiMr`It`J�H��yiTKizn K�ya zi(zoHzr�K	xN24Oi<zrP;�O��4zi@P��Rdzkxzl�zt4L�T.�S��lze�VZ�ze�V`HW�WX�za�zi�X*�[����y����z��z��\*�]��.8{a�{b$|d�|et~f�~g�~ij4k�lЀm��n�oȁp܁s4�t��uĂvԂyX~È]Wh{dtg|{n�{r(�tJ�^�T{d�^��\{eb�
a��t{l�{t8b:
�b��l�.�{b���HC��{exg�Pgi�{r�fm�{e�{o|u�"��g��{e|g|l�"��.�"]�{e$S�g@9�g;H|aT|e�|o�|r�|sTh�h@|n�h�'.�^mt|s؜t\i�5DiMl|t�i2�|v�mD9�iJ�|e\qIPj���|vj�|e�|i�ja�x.&kM�|r�j��|k�k"
<}aH}bP}dT�fX}ip}k�}l�}m�}n~r ~sL~t�Sv���
dl�(}s@l�0}k�l;mJhp��<gh}n8q�qJ|}i�qH9�rJ�}e�R.����}e�r��}gdsJp�.�}s(t�0u��.�Rs�Qt�t���}e�}i��opu5��xDw'�}d�v��~e��u�xJ4~t�z��z��,~aD~rP{k�{����.�~���������l;l~i�~j������]�~e�~ld�P<�"�~b�~en܃N9���~a��]��]�~n�~r �T9�����~t���`>e\��`���&ewkPt��Ps�(eXl`ohupvT��l���d�t��^	����x.�k�m��xa�e�i�od�st�u���H��X�(���.�d�l�n�sĴ(d���"��9 h����j0��n ����^g,�m4�s<�v��ؗ��(P�e��*p���H�r<�x8��\�t��v��]����p��s��y������|����YКY�J8���tԛ��s$�Āe��,����(�������L�"
T.<�aH�eP�fX�i��k`�ml�p|�s��v��Y98�]4�n��:
��(|��`���L�sP���[ �]t�.��e��f@�g���p�i����e0�(<���dؤ��e�
���ԁo�t�v�����m��a�id��l����kH��dD�pȯ$�eL�r����e\�ad�e(��8����x.��rx�t������l��n��rH����pUeķ����g�����i�����a��o��dT���������r��������.X�a�bl�e0�fP�g\�hl�i �j(�m��o��p؆r�sԇt$�uh�vt�y��Ü�"x�l��m��nЃr�s��v<�����d�i���(�����g��i��n��t�|�f<�_9��O���	����ȃs����� �����<����",��(�e<�iH�lX�od�u4�_7x� �t�(H�P4�s`��L"gx��P�d�e9��"��k��nȄr܄s�t�u��"��aL��t�����s������e��X ������s�)@���Ԅed����aL�(P��k�r�����k9�	��n��$�a�^	]K<�e�SD�r@�p9��-h�
d�a�\b��e��g��k̅n�o��r�s�u��|Kk�������]��k��&����ąe�n�s@�md����S�������0�j�t���l�-��M4�D�aX�e���0�:\�<�tH�(T�P�dp�t��"����h�r��u9@���|���"��f��p��v��������o��M�=̆aT�,�t��|9�e�o��u��d��d��dH�t@�.0�a<�eh�k��o��uȇ��e2����(�l��@�kL�tH�	��$���T�d���\�o��u������x�a0���l��(���m4�2@�����n���	��������m�a�e�i�v8�	�2�l|�S$��s��24�9	l�0�sT�nĢ�.����8�e���D�r$���P��D�\����d�r�d<����k��r��y����������dx��1,�L] �a��bԉd@�e؊f�g��i\�jd�kԌl�n�oL�s��t��u8����4����o��bH�cP�k`�nl�px�r��t��v|��<0����X�g�	(Cp�	���b��g��v`��	�
�����r�Q,
�(���r�tȉa�e�r4�sD� ]�e��r�2\�e$�i�j�w?�,�i\T.h�i|�l��m��r��s̊t��v�J 5e��l��t�e��lH�
�*��utHL e�����il�kĊt�!�9d"����e�$><&��.�%t
�a�d$�eH�iP�o\�r��s��u��y���'V\'�'.8�d@�sP'��'�9<(i�(�'.|)Ph�o<*-t�sd*�9�*?�*#|�j,]l,�x,��,������,	X�.�d�e�l�m �n8�o@�pP�s�-�	L.(/J��dL��X/J�i �t���
�/���f0�n0�d0'�0��1Y,1��H�o�3�D4��a��e��i��l��u�4I<5	�4��l�5I�,W<6��e�7i�nZ�8i��gd8ȌeL:���dx��a�i�yd=2�Ai<BT.,�r8�v�D��p�.0Gg
��e<L�HtD�ht�i��k��l�tT�u`�v|�ÈL`��s��v,M���x�M8��e�M+dO8��aPO��`kgF��+��a�e��ȍr�؍eT�J�u�R�t�Q����i4�rF(��(S� �m0S(�uh���S��@�d�SH�n�S�l�eT� U�T��t���UZ<U��a��e̎iԎo�r�V2��dĎn��(X�\Yi�Z���"k�r�Z�0[:^� $]�l�r �sl^i�^��^���khb�$P�yD$��\��,��Td�9<d�H�.�d�e]	��d��f��k؏l�m��pL�rd�sx�v�k��i��s<v���aďe v_wi�v��s8{�9�z��Џal�;����i�$o����a$�eD�i��v7�n�@��4�n<�t���0��`�PX�d0��P\a8��p�p��|��P̵���.��a@�e(�h0�ix�l��nؒo�p�r,�sL�t��"ؐb�k�n�r$�s0�u��(��J�������d4���n�����eH��d��(�t�jD�(X�n��8�a`�i��l��nؑr�t��M������h���p��D�t|�lp�d��a��dđeБt`���#�nԿ��t�L��J�.��a�o�t�'\�d/�rX���"4��L�d��a����D�ed�np�r��]|Kk���9���
P�sl���X�g����"&��a|�i��u8����n���9h�����d��T.�����aВe�������]
4�.�a<�eD�gL�ip�l��o��rȓuܓ�L��(�r4�s�X���� �t��"�����9l�T.`�ah�s����(���|�i��yT�c`��)h���e�����oX��0����p����$�d��]��n8��9 ���ԓ�@�\��e�u���t����C������p����.�] �a<�i�b��<�D�r��x8���T.ܔaܕb�c0�d\�e�f��g�h<�i8�k��l�mT�n��o`�pl�rt�sl�t�ul�v��yȡ�h�;4���Ĕd�r����
̔d�g�k$�ll�m|�n��p��t��uԕv���9��J$�,�d<�dD�gT�s`�t�����j$���غ�L�tĻ@�.h��	�������t�a��e��g��sh�56��G
��t��������t�(̕k<��'X�Qh�.�i�y|���P�t�.
�%xD ����a<��t��$�a\�e��iĖn�r �sP�y0InT�T�ap�l��p�x�S�9t�x�l��4���k��s��t(�x��e�a�4��l��9��0.(p��̖i����ؖgH��e(u�.0u���ePt���m���a4�e<�t����aP����a�D�r��-��aЗb�d�i��l�nl�o��r��sĘtDvD�&P���a��e(�]��r`���'�7d'���a��ėr��9����ܗi����a�d4�n
�.����a<�eT�k\�s0�(84�dL�t|���(���d�g�	B	�x�f��o|
���c��t�
�9�t�����ep
��
����aؘn�v$��9\��9h��a<�il�ài��.�*�e��r�����(�e$0�nH/�.�-H�e��T�r���`���9��.�x�a��e�i��s�'��2��r��s� ,�|u!ęe�̙nPؙe��#�kht\��g0�l��aPu�u��(�e�
l�at�b��e��g��kȚmКn��r�s�t�*��PX4e��t4]|�n��t��94hT���	�]��a����p�d�k4�n��s�t�����o�Y�a�P(!Y$�i\!�9Lm�#i�#0�aX�ed�o��s%	4$P�s�&i|�s|'.�'�p�e�'C�)U��a��e�B
�)����e�)����t�)i�+����g`+țn��p+ԛa�e �u4��|�<,�,�l���L.�n�j(�.��,���/(P/2@�r/]H�ax�e��o��t����r@0l�d��t�W
�1�D(2��l�3�04(0�a��eh�f�g�i�n��p�r(�s��t4�v6�|6��9t:W�dd8�:��a�:��:] �a�<��H�eD�n����=��=L�sX=T�e�>��A���aԝe �i<�kx�l��m��n��o��p�t`�uD�vP�y`��PB���tB��lv�LC̝n�r�t y
�C���k|�9�C����a�e8l"�n.D2�D�'�D�(�r�D��0�a\�eh�lp�u��9Ў"T�iTE�9F�	�FT�.�Ȫa(G���l4G�����GM���LG`XG�Оn�v�G�G:Ȟa�e�G:0�:H��G��aI(�H�n|H��eT��J��$�e�J��,�r�J�8�aK2LK�\K��X��|K���a�e<�ih�o��r��K��K����k��l��nğpПvL��Vd��: L����eLL���I�L��؟gXL�i�k�m�r�L(M*p��`M��f,�m��"	�K�$N4�iP�lX�s�Nm�N�h�`O��`�d��k��n��r(Gu�O:p@���e��xl�iP�� �s�O:��eܠØ"8�P��Ƞd�P��Р��R�r�R�����>8SM �dD�hX�m4�t�S�,�e�C�|V|D�aL�flV'�r�~��0�aXxT�r�W`�a��e��s�X�PX����g�Y�8Z(��a��n�Y:�Z�[��`�r@=��,�����ܡ�<\d�k�r�v��y]:�tP][ ^Q�^��d�[�a`��$�s�B]��aТb�e��f��h��iL�k�l@�md�nl�o��p�s�t0�vL��4b��b�	d��f��k��u�b��cJ�e�xd����u�k�
�l!:�l�
Ȣi��%:Hp-ܢitn�fP�g(�lt�m4�nP�sD�u���
�s�is]�ev]@�aPv)({z��H�l��ml�s���p{d�k��p�Dt�|�*:�����a��t�~��a �]̣s��M��iD�/:����Pԣe8�]ܣn�r��e(�nl��T����e��t������� �g�il�eЎ"4�iT���@�e|�i��l��oԤr�v(�A���@��t�l��Y��r���'��a��e�-�В��nd��Ĥp�7�0�����̤ip�q'��:�e�o�u,��,�2(���d$��
h���������$��̞��M8�aT�e\�oP�i��2����2��v���ܪJx�e��.@�X��a��eĥiإoH��.ĭ(�����l�(���ХrP�@�kإm$�n0�r<�v���ed�ix�k��l��pĦtܦv����vsP���P�i�}�	ć�̇��D�e��4:L�t��X�s@����p�i�j��o4����G�����X��a���x����oظ���rԦy���9:x�k��#�r�����d(�l�����aD�e|�i��oܧr�+�x��4�e��=:@�D:H�<�.`�gd�kt�l{oܽ(l�g���
��t���8.��a��epIn�s��D:lNJ:��M��n�n
�����s(������ȧn��Чa��e�o�u$�à�2���� �����p���h�����t�����4��8�yl~��d��@����`���\�r<�]
��aP�el�f��iȫj�nX�o�r��s��t��uȯy������dШl�n�r@�sp�;8���ȨaȾd�e��o�P:��h
�v���<�v(��<����e����L.0�g8�m`��
$��/`����x\�	H�d|�i��k�ll�n`�o��r �sH�tH�t��n��������t��{@�����i��l����g����aЩe��W0����ةm�v�����e�ll�T:d������n���a<�eH�iX�����4�k�����-����P��������d�a��d�00.0�t|�ex��"��m�����aȪe�o*s�u����'tتv���
<��(��	��������|���r(����e4�����t|��X��,��X�{���@�a\�od�s(F[:��a:�(�X�T9���x�t�m��r���e��k0��a��o +�g:�ثa�e�0
����r<���Wo��r���e8�oL���F�n.�F] �et,�t�d���D����.��a�Zb��e��fĬgԬiܬln��r�t[u�C�.�y�zn���f�n]��a�Or�(hJ�2x�l����a�d���3�K8�eT�id�ox��l�$��0�d��rL�v0�8L���
P��\�m�!�
�!��p��si�#��l#���e��h�k �o4�pT�tt��D%�u���5l���ȭ�ho�Эì���ܭv��J�s\&�0&���iبm:,�p�'M�p$���'X �rp(�|(��@�b@(KH�ad�e�(`�*tX*��l������+����k�*��nȮs�*t��a�e(�i<�oP�s��u��y,,���I�)JЮnP,خg�n�s$��x-*��k�M'�-��e�.�`. �n���/��4�a�1�h1tH�kl�p��t,(I�'Xd�o�1�2Kx�i`2�l2����g�9(�2����pd3��t�6�8�{eدt�:����������>
�@��?����b$�l0�n<�r\�sp�t��vPP��`l�Y��(�glaxL�iT�oPd
�eii��Tap<p��h�aXx$xM|�ez����eذk�n�r@���
��a@�e��h��i��j��nԲo$�s�t,�v�z��|��f�o��vx}�p�:
~��4�. �b�!e(�t0�vH�>�h��
H�����8�d`�ih�l|�n��rx�t��t�a(��(��	��d��y�G
��e���L��	8���a\��	�a��d�e�f �g0�k<�rX�sp�v�����.\Asl�r:���r���s|��Ĉy:���:�'F,�(�s|��: ��:DD�k���L�ih�p���/�����	D�U��"��s$��������o��J������a�e̲i�,�H�(�'.�m��r�t�v4����,��D�����x����l���aL�el�k��l��p��t��uгy��@�k\�n�g
8��$�d�ep�WT�Kx�e@��пi�����e��i��W���������n�O��ȳk\�h
.p���ܳeD���l$��a���	̡���iء���g�� �e ��H�ah�h��:X�s`�t�@���_�s�8����r@���x�a��s��t����]��l0��t�������a̴y@�2`����<�t�8����a�e$�i���r,�t��]�i�i�<�x4b�B��4�a��<�s\�td�v<��@���8���l�e��t�r ��>a0�cP�d��j��k�l��m��n��p�r(�s��tD�uP�v����at�b��c��d��e��f��g�h��i��k�l��m��n��o�p@�r�s<�t�u��vH�y��zt��|�ZD�el6-�5��<�n����'.p�e@?g|�st��:|�h�b����-���.��a��eĶk�tX���p��s|�]��a�e�g
�l��Զs�&�:��i�R0����a,�e<�iT�jp�l|�m����������4�mL�nȶi��������\����d����
Խ�������@�J�4e���l�p8���
��a�d�e�g$�k<�nP�sp�t��v|�y�	(��x�t�e��i ���t�s`�2l�]�a�<p.���0�e|�i�tH�e`�l�����1��h�r��h���.x�����aCe��o��p��r���)�]��r���̸i��2��T.$�d����ԸaHCb4�e`�gh�i��k��m��o��rĹs�t�u�v��4��
t����,�kH�nT�s����hca�����s`�����T.x�sx�*��h4i��l��v<�-$������
��a���.0��:�L�̹d�g��n��ԹixN��N(���h�A�
i8��������8�tH�aP�eh5pd�s��t��v��%:�\�rx����*x�a�������p�b�����a��i��(����r��iD�?%��Mغe�i�r �t,�u4�v(�2��������t8�W�sT�,��e�����kx�t�a��4��:D��@���<�kX�J`�el�ih�"�.��R��p	a��oL"̃ad!���d�5� 4����eh>�̻e��PM���e�H��lȃ_�d����ػ��\�X�a|�b��c��dL�e`�fl�gP�h��i�Kj�kD�lD�mp�n�o$�p��r��sL�t,�u4�vP��܅�@Ikp�rH��,�h�e �P�z���ȼš��
��aмdT�e�fpi�o�r$�u,�y4���C�
\���Zk%�Z��ؼ�PZ�ðh
����a�n�k
�C�moؐ��),̎��D��;�<����X�r�2��-�JtԖJ|�gX�oP�E��eD�.��S��e�����lȽnؽs؞(����g��@�?�Jнmd�J��a�l(�t<�uL�W�.`�T`P�4�s���o��rlP�:���ȫtx�a��d��eԾi�j�l�o�s�=u�y,�Ü�<�k�l��r���4�����Mdlf��i��pľr̾v�U��:��?�"l=v0�B��8�o�"�v(�I<���e�i$�t���<�
����<��>���N��JP�b��	�(@�"X�n��rd���
`�a��dԿe�g �i8�lT�s�t�y��$����2ȿn�t��e��:�����.�r�td"*���:�e�l�'(����l�#�/x���n,7vP9d80�y���t�k��l��tD�a��k��l��m��o��t����I�:�I��|�a�Mi���Px��I��P��a�e���`�U������g���a8@rX���:��������i�s�����@�aL�p����'.4�4�r��A���4�"T�a��b�d�i�l�p�tx���\�a�e8�i�Uk��l��m��n��o��r�s4�th�v�y|�����	��(\�(����e���4��adL�i�Ak�s�wt0�v��:�����p�t�u�%��Rd�ep�kx�s$&t��j�PP�s4]X�n8�!0���������s����eL.i���uh����H��s4�n�e4����j��Z��g~i|����e�oA�
��Z����mh�(t�:�oH�ô�$�r�P3	�P��@����"��YT�k��\�il�N8������t��,��:��-d�.��e��i��k��l�n��s�t��.��
p���".|�����a��e��h
T.,��:$�x�����D.��e$�i������l��H�b��k|�l��s��u����
0�a��e��i\Yj(Fo��r�s�ttYu��`�n����������.��d��i��k4Yl��m����x$�x0�������d�l��m�n�(��6�uH��`��)0�G
����x���Y�����-�YaH�i`Nn��i,��$�0Z�06#��`�r`��������h�����$��e��o��r4��D��:P
����n�	��n2#^	��ip%@0m�j|P��O���r�O��e�s0�a`�e��i�xX<�aD�s�s� �v�x�
Tyt��pXL�e�y��T�tx�v�
�Mp�a��eĊiT���iD�����l��:������eT�����n0�r8�sL�v������a��b`�cl�d��e��f��g�Kh�i$�k��lX�ml�nD�oP�p��r��s`�tXOu��v���p����D�e�����<�P.H���T�aЕJx�s��l����dܙ]��n��r���:T���h�.��s��1,����r����i(���e��ux�]��g�j4J�̡���?�5h@�lP�r�]�ep�k��l��ud��P�Ԧ����H�o�'���\����;|�oĨ2<��8��(�i�.T�����a��e��l8o<v�
���p,_(������gܰ��a��b�e$g��fm��a|�<��d(�gD�rP�s��(4�a�n\�v;���<�a���\�J$��T.и��`�a��e��g��n�s�È�WT.��d��l��vػ��(������
��o�r��s|�t�*i̾d��k�����s0������l�38�����@�L� �n\�](�a�;s<��4�n��"`�ip�lp��8����h�aH�(�|�r��]��a��e��u�(����s|V(D���t������e������i�k�o�s4�t@�(l�����i�����l.p,�tH�q��M@�rD��	T�ah������L�.�����a��i8�j|r��t���p���l�.�����.������a��������$��
��OT��	 ���a�
�	��a�����v4���]�.8�a�e��i��o�uT�y��Ì^d\�e�]��,�dl;kh�n��p��r��s��t�^�.a���8.��d��e��g��sta
`at��e�n]3�ai��.�aO,bOlb�b�	dJ��t�dh�dJ�ih5������ape���n�k4�.4�dD�i��kX�nx�r��s��vmdhq�hp��<�s(�;�t��h�aP�u�t�|x��v��p�uXy".�x����e�}�<�]�.t��v�*��a���<r�ԟ���jL�����g��i��k�v|������3	���� 9e �k(�nض(ķH�d�j����4�i��t<�r�`�dT��@�i���h�e�p�r�~�����|��������Psp���L���e��\��r��s�.�����e�e�a<�d`�el�f��gȩh��i��k��l8�md�n��p�r�s\�t(Gu��v��w�i�L�a�ee j.�ltplX�mPnJx�r�o-Xp��v�<v����e|{8{W��n�z����a��e��j��l�o$�s0�u�|���kp}(M,�pi`��"��m�h�����a܁��k��]�JP�p\�s@����=H�u�OĈ]�8.|�d��g��sԉO\��(�t��r��s��
L�G ��a��vIi`�_���������a��e��n��s@�.��;,�T2`����.��a<�dD�eh�gp�ix�k��s��t��vT��0�\��$�f�3gȡ������l����'!<��8�n#D�-t����v��g
h�����rl����o��r�P���:��o���Р�ܠ�����l�n#����T.t�s8�]��e`"j�tT�v�],�aL�e��(<�sD�tȼQ��D�20��p�]�.|�a��e��h��ixmr��h��k4�P�2T.��k��;������l�(���a��R�����e@����r@�;�����n8�̵]L�a�el�iH�j��k��lX�nh�o��p��rl�s��t��u ��P�J��D�dt�k��l��n��p��r��s�t��J��t�:
���;������e��g��k��l��";�t��r$��\��
��J���h�.��r��"	��(;(�t��t��.;��	8�e<�iL�lX�n��p��r�s@�t��u����D�tD�sp�����a��d��e���P�3;��2p�r��tx�e<�rԿ�s$�;;����@�\�p������a��e��k� 4.����U���l��A�����n��-��e�n(�t�T5$����� �i0��T�sL���4�a\�ed�t��%��;�?
����d��l��m��n��r��s��"X�pT�zl�����f��n��[
��
��r��x�.����e4��������t����� �a4�e��b&��J�t8����s4�i�,�dH�iP�vD����@;��El�"��`�f��l��m��p��r��v�����d$|�8�p��"x�������k��Q��@����e�i0�o,��������i��������n�s(�vP�� �s��b8�������.X�pd�t����F;D�i����L�r�;����e��k��p��t��'.������e����t��*��kp�����a�����e��lH����K;���@�o<�/�]�k�th��p�i��a0�st���8����D����8��T.l�a��e��i��o�u���4������d�d��k��l��n��t��y��$�����s��
,�����i��d��st��T������.��e0�P;u.����I������f,�iP�l\�ml�nx�s<`t$�(fr��� �e<�ip^a0���m���D�e��	(,���d�s|
���.��s��tt
W;��#�_aȼe �����s�����a`n�����f��k�l�nP�sp�t��v�]�eX�����eD�����g(�o0�s���^;@�eH�i�e;(�e;��`�kh�tX O� �(!d,""��x�e04�|�.��c��f��g��i��p��s85��5�6]��.��e��l46Pr|68�9���.�:��e�:�S0�nD�t�
(�t�e�T��$�glV�|V��<�aT�e�V_ ^m;��s<\��\�r��s��v��y@=�����h������^���t�^�^�
��e̅t;�����s$_����l\_�`��Hd��k���`����i�k�oX����S�B]p�a��c��e̩h�iP�jp�k��l8�m\�nh�o��p�s��t8�uP�vlJw��y���4b����.��n�gItnh�.��iX�k��l��nH�r��t�
��q���eq����gs���y�v��d�.�vsL|'�{����e�s�|z;����b4�n@�s�������,�s,���(�H�o��8@��\�lT���d�i��j��oP"������n��X��ed�"��.��d��e����,+P3�;,�"����e��o��(����.����Fa$�e�����v����0�����/i��.X����P��p����lD���D���� �o��Mt�d��
���prl@�X|�i��l��r��ux��8�\��e���x����e��o\�I8�i�X��a�e�{v$��x��������l0�3	H��m`��������(�x����0�rH�s���	t��h�ap�e��i��è�J��Jp��;@���x�g������n���	l������,�2�t��e��o����k��s@�����	4�"�k�yl~�����������Je���0�eܭ���x�e0��;$�n<�h�a��e��i��o��u8�����a(}e����X�l��r���
����x�k\�2��k0�m��r@�Z�
�����e�iA�����g(Gu�6�6����td3����s�t�6(�?
@�bL�c\�dh�kx�l��m��n��r0�sp�t(B�;�@��8�r�B�EM,�B]T�y4L��P
PP��p�a�e��l�V���a�V"T.�7�/��ed\���t�Y����k��n�LI�\�laJ��e�i�k �u(�v�b�.�ac� PdRe,+h��h�i��`aaD�hT�l�j?�l
l8L�upT.��s<p��`�a��eԹg�ps��t��v���0q�0�r��q��r��v�r��v�4vt��r��ul2�w'!Tw�lw�����@��e(�i\�������r�����e�����s0��<���4�r@���<�d`�op�r��s�ĻR���h�a��e��o��h���
��a̋e��t�.�Z��;<����r�����UeT�?P�����j(�����k,�rD�sX�tp�v�@(��Z�t���e���� �r�.,���8�a(���(P�ah�eX�t��;P��;��yt���	x�b��k��l��n��p �r(�sT�tt�v�.T. �������a��e���.�T.���t��������e�l�n�����;��M�
@�kL�m,�����8�o�� ,�h��>�;���`���  ����|�a��u�?X��a��el?����r4E��rܕ9�����m��r �����a��b�c4�d��e�f��g��h��i$�k,�lh�m��n��o��p��r��s�t�uX�v,�w<�zT������ah�b�c�d��ehf�g�h�i�"j�#k�)l+m/n04oX=p�>r�As|KtSu�Wv�Yw�~xZy�[z@=�4�����P��s������i��o��r��yP�PȘ�;L����*�5����t|����e�h$�i��2�8�Й����,�a\�fl�i|�j 1m��r��s��v��
�`�;؛d�o��r��x���e����(����	��iT��D����e�m��d��l��rx�x�����k�(����rl�]��e�����f8�iD�jL�od�rx�t��u��y̟P� uH�]��w)�4.\�s "����;p�eX$�D��,�x�t\9�
����a��eС����e��l;��d��0Е�0�����d��n�sȧ��Мt4��;�����k���;X��dP�r���	�a`�et�h��k��r��s��t��u�v���-L����"X�l@�(�l�a|����e<%(����l8�:��o|����d��a��e��t��t��.�����i��r$�������;,b�����s����n0��� �ax�b��d��e��g��iT�j�l�m �o0�y8��̳31���d�m|�ml�u�jF,�t��sm�"��d��o(v�;<����e��o\�0��0��I��]��n����e(�nL�i����e�AP�t(����iD�p�����H��������;��(�P�g��s@���
X�a��b��e�i�o8�p\�s��t��u��y�(T����i��uH��;�+�s0�x��n����e��H_d��r��t@��	d���.��"D6ll�$��;4����i� �r��,�eH�u������t��P�e8�����aH�e�2h�l��x�a�����8���T.��a�c8�dh�ex�f��g��i��k��l��m�nt�s��t��u����(���a�l�r��'�<�
,M�sh�z0�� �.L�lx�t(�e�ar\�s|�;�I��T�a��W����p�a����t��e��i��s���P�i �#��t��a��n�/(��;l�]��v\���i9�9�;��S,�e@�iL�s#/�<���t� �tH��0�8�n���(�i*�M�T�r(���\�a�th�k�Bv\��h���k����i0�p��tT�;���	�������x���	t�a��e�i<�lD�o`�p��r��s��ul�`vl����n$�r,�s4�t �<<�(`�\�����P�s�����]X�at�l|�ot�<h�c��I��
<d�</��������.��b��e<�gt�i��k��m��n��r��s��t���H����e��<����h��dCaDag�l �p0�t��ID�<��`���(�a`�	H�ut��"<4�iP�a��e<���X�n��"h�n�������.����a$�2��a�+<��
��e��x0����1<$
a��*8���������8���	��a$�c4�eD�hl�k��l��s��t��v���@�7<�,�a$�l���T�<<��L�a���T�r����`�a��e\i��o���(����W
���l��t<�B<��d5l������iXj��rPXy��i����eD�a��(�k������a0�eh�f	ot�r��t��u��yx�(��T.D�gT�lD��"��F<���L�i���`�e,�T��o��������y����������$-����l��sx�t��e�-�3���b��t,�pl��$�s@����d,�g<�kL�s�Dm��Y��a��eD��3�p����D�sX�J	��d��e7g��i��l��r$
t�y ��H�`��	h�"��s����s�l�5��u���XN<`����t|��Up��s������Ƚ�S<\�`��0���r<����l(0���4�z<�`�yD��H��������a��e��iT�o��r�u��\PP�d(���e��rx
��d\K��n��t�i��i��K��aH�ȗr\P��e�g�k(�s����,�4�r����t\Vl".L"<�id!�H�dp�n��r����$]h�i��s4%-�'��'�	��sd'���a��e(��'����i�+���n+���e ���i� 4Mh>�<�a8�dT�e�f4�iL�jd�ml�n��oH�r�s|�t�u�v�y���>(>
4�bl�gx�h��k��l��m�n�r �t0�u�?��s@�D@��@��a�
e��s�@�4AW<�A,|A��sTA����ehB\<�A���o�t�B��a���$��	C����m�t�C�
�e!�D���l|em E�|F�|JJ��@�i�HH�d��e��f��g��i�k�l�m�nd�ot�p��r��s�t�J�$_n��56�J-��i(K"�K��K����atlЍs�)PM���oxP(P���ixQ'DQ���a$�g(h,�sX�t RgHRb<8�e��K�U��(!e�R��@�l�RL�a<Sv'p��Sl�ohU�k�S]��i��sV���t���VM��m�X�-�X����a�t0�g�������d��������	�sPZ��e��2l�at�sD\���n|�u�[$�a��d��e��f��g�k�l�m�o(�s4�t<�u��g<,�� ��Y�\(]~\]��_Y8^]��sH^K��e�]]��r�s�V�^s&�^]�a��l�u�^J �,_����i�`��v��2p��Ta�� �hxbJ�b-�c�lc��D�e\�u\d�pgA,hm���b�D$��x���hn<\h��b��dЏl�n؏p�r��s�v i@�.���a�k]�n(ldmim]�t�mQ�e0<s<�m���r@n�
n��$�djk`�np�r|�s�m,�a��e�ji��o�n�dZh�n�h�aHo*hp��o����i��t �v�rn�r����t�ua�u��it���e�k�tp�vЎ'x���e��v�xN��dz���n0z�a,�oH�rT�u\{*8�g4�i���p{@�e�{
4�\(|��\�r|�d�a�|;��a��r���|��+k�}0~x<@~������������dt~��nL�i��e�i����.��]�a������k�(���g<�pP�r\�tp�_|���4�eԂ2����H�e��}<��p�r���|�e���<���<ȃ���sPv��*<�����r��s���<�����.(�a�b��d��e��f4gP�h�i�Kj�k�l�m�n�o�p	r|
s�
t0u`vTw\ä��܅] �nP�rh�v���<��<�n,�D�a`�eH�����D@	��p�k��m̌��
x�a�e�i��l�o�r(�sl�ux�y;�TAx�J%
����f�n�ms���2�g$�2|�������i|q�\���X�ul���k8�l��6H�aX�e��`,p���xP�dT	ؐd�k������lX�r����T.����M��a�f�i�j�o��rt�|�S	���(�}8 "���s���<����~/������3��"r\tԖ��$ahe�g�i�l��r�u�o�6]̈a��2�b�l�n�r�9�*ԗ��c�@cP�g	����a�l��(HL(�M�n�n�b�d�ncl�d���	�d$e,gTk\ltnrstv�����<aHe��2.���.��S	��)̟��'.�s���da�d�e�s�v�����H��ed�W�'.�s������\���j���kp_�d��a�����a8�i�Ja0i@kXldm��2X������8a,���d��8Le@��ģ�ԣ��la�e��id���@�.�a�e��jk$l`o�r�s�t��uL�W�x.�v��p���n $t�#�n����aP�v�,3����m`�Ka@eTi0-(��8d|��&0.Lr|��	xb�m�Ln�s�2�<�5�:�<���\�a�u�@	��RĨ���a�p@��<�M( ��r��eȫ��
.,a�e�g�i�j�l4mPs`uhvpyxÜ��<gtn(��<���D.�e��Lk`at\s����hdЎ�<�����g�i�k�n0�3	H�x��C�e0u�<د����"�t0��-����a$e������gn`an
d���d��̱d$v̲~x��<(�<s<��De�kD������f�������=�>����<�r���<��J�aP�bi4jTm`nlpts�tQu�y�����@0n��nl��_e��]�n���el$sL�Z(����<���,iH��t�@d���He��B�sa4��н���<��*|a���dt��i���<��ܾ����@�d����a d8e�gi�k�ln�o(sTtSuxz������l�te<�r���0d\gdiDkll|s|�t����l��!�t���tt'�<��t�d�l�n�s���(�<p�i@�#�t$���l<6�d8z�<�<a�����ll���sD��e�<=��i��t a8s�Q=<=��2@t��Hihrput�=��xbI��!=�������@�����g�p�r�u,��
������
H��T��n��e�i	u��/��	s0�xx��	H	ax	e�	f�	i�	n
o$
sD
ud
��x4�"@	dd	np	t��)'����\	g����
�x.ad�AfUgL�ilk,nUp�	s�wt�
(=�����	e�ud��p��	a��&g�kb0=�1���	�h���	����J
l$xm��st��`xp��8=t���0
e��J8
t��[l�q
P
y8����V�X
���	����t
.�Va�
c�
eiXWjk|l�m�o�p�s�t(
uP
v�Xy<�ðB���Yl�m�ytl�`�����
e��]�
r���
en �|�d�eLjXlhoty@�D��8. �_@e��x ���
����`d��,�)�uh�(�� �o���ܪ*��W�vX�����e(�i�rx�@=���#	ae(i4j<kXoxDp�Dtdyx�(���l��@�k����t�����tP�G=h���Dt����Lr���4�M=<���le����tn�u�����a�e�i
r
v 
y��T=T�\=�����v���rTs|�*�����{�����#4XIl��.	
e�����b=�����8
k@
n��i=���*��XH
i��/L�*\
i��d
k|�l�
p����p
a�
e i(j8oDrLs|t�u`�y���S��"Ya�
klmn�w$����
.�
en0��
��p=��x0��"�����%���ct�"0a�x=`�_l$i\t@(��,���dd�r0�tle�o�s����%�#�t��t�e,�%���y����(����~=�=��.�=(�ae`��=t��s�=t���=�-<r��"����Dk�n�r��MLa�e�i o,yH�<��H~��.t����e�������g0~i�l�n�r�s�0��(ht����;t�������gnl�[
���nX��d���m��=��3	����4p,���<�@�*,��HZ�$�a�e$i0l\txu�y�Ð*e��r�t��H
x.T
��n�r�t�	�eiPm����xT.X8i�
(�i�
��l$��k��`��<
/D
��<����D�t(��-Z�-��de,ls.�-�r�.�=���������Hr@/��a4/��r /-�a�.��r@0
a�ePi�ln(oXr�stu���/�'.8lXn�r<�v�1��D�eHi����2��2��Pdlntv3�	p�X`4�	�3��|i�t�4:
0:t�i�8�d�fܢm�n�or s,;%@@W
�>]�t�@�=xAL�i�'t�@]�eu C;�C�tCtj�l<t�DhD�4aKM�J	Ha�e�k�ql�qm�o�r�s�ÄKu!hK|nL�XM��M��i(��	DN�=�M���l���������,R"�'.mnr`Q���e�R��R8 S8�V|Z��Y g<m`)t,[*8]3	@]��Db]KLaxe�i�u�^�^��pa�l�p�_��_�=�_�s��P`���n$a*�cidb#�ckl nHt��y$��f���r�e���o,g�ge�g`�����(llj0l�h��<i\odu�j��k��nA�nld�tLpt�r�rM�s�r��a�vG�����,psM�a�e`s���.�sa�j$o0uX��xXs�s��vTy�=ԃ�d�xd�
$�i���8eT���@r�����L����=��thsl���pd�e0�t|n����,f4lDsT����nPr|�uhv�����a�b�c�d4e`fTg�Kh�Ki�j�kl�m�nohp�r�s(!t�!u"v�"ô��+\��+`��=,��<vp�<Kb`r������xa�o4�W��=��=	�lt�~<�P.H����a�oșT.��p�lЕ���aei��r��t��=0���ed��X�r@�i���=`�MeXgܙ]$ihktl�nr4tLv���X<�����`sĚ�d�e�i�l4�$
��+�"�m��=�"�m������al�P�d�h�t�����)7$�o-r��T�a�.iH�p̝�=��" t��(a�����n �M@e��,�]	Xa�e�fijl�Ko,u<ÜD��e�
���i`��rP�t�]�i���2��.�aP���b8�r���e���s�����=$a�i,�d��6l���4���T.x�]Ha|g�i�j�r�sL��(�trp�2̡���c��K�e�d�t��-H�-�.��h���i$���l�n�]
�a,d<eHidk|l�o�r�s�t�u�v�x�
ܦZ�4s,�
$��0�Pn��]Xetj��f<��8�aH,�T�i�n�$
 ��=lE������k�uJ;d���s���lTZ���mث��n�����(��T���aDdXe�l�o�s��|�;L�t<emx�Pdhil��H�Y��tpt<�xsܰ�e�������n����������br\���	�ae0iHlPmXolptu�y���4��l��s(tȶj ��T.@s���L�>d��=t��dr��(���=����t�������$���l�m�sи���a�cde��fDg�j�k�n�o�s�tx`và��(����8��G
up�R��W�6e(s��(�(Y	|�0m��t8o�rds�*�tk̾dXa�p�bi h',hX|a$��0��3����e����s��G �k�m\�1p���G �j�r�;P��(�=8�������<��f<mlMpDrLs\t4�G'��dl�E�����]Tr��Jxe�o@������s��������u����
�a�e �h0 i< jX k� t� v!y!�H�����d\�i k l��)*��c i��5��> �n o��$ kD��H u8�X��l���P a��iup y���
>����x d����� a� e2i� o� u� �4�"d,�(� f,�Q�������� n��e ����t��� ������8��L���!�D�2T!o����	!e\!i8�j�!o�!r�!s�!t�!u�!y`�>$��	t!d�l��*l!a�I|���)2:��t�!t��t�!s�4��!n�8���
<�M�!n�!r@3t�?��,�W�!rT���"a,"et"i�"oH�>�$"dL"gX"ih"rD�4�JD"eT���>t���`"k���"s,�g
�"e��p�������"k�"r�"y�����"��"���������"a��w���"a �C��M,#aL#e@�u��&>p�A#i|���#b���� #m@������8#e\��@#r`#sh#t��Jp���$i|#rpu���" �#a4$ex%h�%i�%j�%k&l�&n�&o�'r�'s�(t�(u�(vH)�<�
����#t����#k$nl$o$p($t��9 <���#f��T��$r\
->�	�� $r�	h�.(�hl$i8�k�$l�$n�$r%sX%t���x$a�
J`$k�,�`���$o�$u��p��ؾ.�$a�$e�$i�$sT"�tvp��3>��%s����$i8�o�s8�u���D�	��H��%eL%l��`^e���(%s����4%�x@%è��h�.`v(@�d%n l%a�2�%d�%e�%v�8C!�� t�%i�!�x'k !X�%e�%o�"�
�"��%rl#m0-�
�,�%d ,�%e &i0&u`&�0.]/3	�.��(&n@&t����/��Xe|/>H&r�/���&�T&�����$0dt&a0��|&r�0�k�&p�&s�0m�&e�&o�9>(1��1a�2X&'rX2���&b'f#h$'lD'pX'r�'s�2?>�o)Pn��'o8{�@5��'a4'i�~�7��7��<'el%�08��P'ah'u�9E>l:c�'eH:p'l:�|'e`"jd:K>(=[�<���'sT<��'a�'ex=���)i�'m��4B��'a(e(i$(k0(t�yvBl*rHC=C(g�DlE��8�a�HT(ah(et(i|(j�(o�H�-hH��L(lx��I`(n`I�L{W��(xNi�(s K�(i�(o�Nx4Oi�R��elЫt�WS>�W���(d�(i)l)r)sWX�(e$)iX�(X(	DX�hX�-����X��)k\3	�[��0)lX)y���x)�8)��\id)s���8�\x�)e�\��l)k�Ap�)s�\0�Y>�]
�)a�)eL*i9j�*o�*s(9u�*y�*z�*��d���Be�]���)t@l{�k�)aT�f��p*r(*s�5u�w[
�v��*idz^>�x�� *l<*tD*u�z��{�<��dkt*n�*t�V��t`*s���h*gp������*a�FeL�Z�*g��vԟd>�������*k�2�*s���п]�*k�O���(gp+y�~���*���i>��`+a�+b,e�,i@-jp-l|-o�-p�-s .tL.u�.ð�.����H+e��T+g�+k�+l�+n�+r�+v��o>(����+l��0���+e<����+t(���$)l�+s�+v�"p�{
�����e@�r��p,����x���+g��k<,lT,nt,r�,s�,t�,ylb �(,p��]0,a�i�Qt����H,e��
$�`,v����h,e�@����,e���P��,rd����,aL��X��<����,eh��,d�,e�,n0-v��]�n����xVi-s���\��-id�-k(-t|�u>x���������8-e����Ua��L-p ���X-����d-���ge�-f��p�-s��J��
��]�-t�P$������-oH�t�-k�-l�-n�-t.uL�K���,�.i��h�].l@��h�a0.e$�2P@
d�Z8.nl�@.a`.e�.l��:t.nT3|>�E�l.e,i4�. ����.a�.e<�.�d�.<���.y�����.��.�,�\�.�.l�.p�.r/t��T�
p�
�'���/aL]P/a0d@0e�1g�1i�Xk�1n2o�2s�3t�3u�1�2p/k�/l�/p�/r�/t0v<W�����x/d���>��/r�]�/a�	���/pHCm�	���/b�/t�
�
�����/r�/v4�F!�><y�/s���0i4;l��40�D�`?r���(0�\h�.t0a�0b�0c�0f�0l�0n1rD1s�1t�1v��>��Up��|0ep��0o��%lJ�0e�0l�0s����}e��0gH,+T�>��<*�0g1h�'�L�i���1e01i<1o��x�(1s�����.��ed1il1kt1m|1oL �9� �(!�T!}8��(	�"��1bd"���1a��e�#-�1r�~3D�#$��1sD$��4��1��3��%�,��nx�<B�'.42a<2b��dD2iL2kT2ml2p|2r�2s�2v�B��B�	�C(�CDDJ�Znd2s�D�>�DW�E�	�D��t2k�F�0G�|J
�Ht�2e�2i�2k�2l3m83oX3pp3t�L�?n�M��M���2e��rPO�dO�2a3e�Oܳg�O�PM3iQ�Q"$3n�P��,3vd�����D3n,QXL3oh3rpQ.&�Q0Yi�V2<U|3e̎i�3r�3v0[��3eX[�\?$]K�3g�3t�]p�^ihb�,�t�3v d3	 e.e��3e�d���3l4s|e�1�e]�Bk�4s�e�� 4a�4b85cT5d�5e�5f6g|6i�6kh7l88m 9n�9pt:r�:s�;th<u�<v8�x�4i���1|g��4n�4r�g���4a�4e,5�X1h�>�4d�4f�4n�4s((�K3��
�h�O�>��]5j�1��5g�i�5l�i�� 5��i�	�G�:dj@5s�iH5dd5o`kh���l�l5d�5splt5nHm�>4n+Pn���5a�5e�5f�(�n�5l�5m�(p%i�o|�5i<o]�5r�p��.(6tXp]�5a46eT6rl6st6u�*:6] 6a�p��.s��r��@6t�rKH6ed6y0shs�"tt]�t��v(�v�6l<v���6e�6i�6k$7l,7n47s@7v�i�$@w�6tLw]�6a�6e7l7o�w���ihw��6r�')(�w��7m�w�>8x�>Dy���e�zf$�
�nH7a�~"P7g�z��\7i�jj�7l8o8yx��d;b(�p��7a�7e�7i�7y��(�7e��7g�7s��R.�"��e��(��7s��t����"�7vԂ�(�X�"8kd8ll8n|8t���	 8a�8b�8e�8k�8n�8okr�8s9tx�(���>���X�tt8e��>l�;�8d@�����>��	`�(��"�8r@��L��8a�t�8l�8y9à�h���ȇ���8�ԇ~@�u.Ĉ]9eH9i�9n�9o�Ds�9t����@9m`9s@��>P���X9mx9t�1��1��p9a�9e�9i4��>2����9a���F���9s�U��H�e��Z�9l���9aP�(��"�9d�����9a$:e8:iP:l\:pd:y�
@�:t��(0�0:a����.x�"&D:a��H�ܔ�`���l:a�:d�:e�lu0��d8a\��k(���:t8�]�:a�:e�:l;m ;o,;p<;s`;t(����:l�:n4�n��>;a��2@���l�2;v���>Ħq�4;tdyȼ(����L;s���T;at;i|;r|�aܧ.	�;�$������ ��>T��;e�����;tP��;k�;sp�]�;e <i@<oL<uT<y ��<t�����;r����;e���>��<a��<k��>����,<dX�4<r��iȯ0�Xx<d���\<n�<tTZ�p�i�����'.�<r�<s�����<a�<e�<s=v�!�>����<eT���@����<k�<nD�r|�C�� $���<m ��,�=e\�.<���=e����(=t`��`��[�4=�<\�̵|=a�=e�=j>lL>rX>u�>Ì�i�=r����b���`�-�=o���=f�=n�=s�=tp�����o�=����������-�=kL����Z��>a,�i8���>d��i���
 >eh���(>v����4>�@��@>��2p>n�>t�[�����h>g��
p���|>.���
�����>yt����>��>����>�����>k8�
?a(?d0?e�@i�@mAo\AslAuxAv�A���-�����>a�g?r��� ?o�������Zd?bX�f�?i�?k�?l�?o�?p@r @s�@t�@u��?a�?�p?.X��x?tX�P�?r���t���?t����?n$C����?l��
,��?t����?a�v'��SlD
;	�@u|
��@k@@mH@oX@pd@sl@t�����?��P@e��*���@a�@i�@r�i�?
kL�
���@s0�	�2�dk�@p�@vh"�+�@��.���l�.���@��5S04���@eAm,AsPAt88��:��:]$Aa<Ae�:.����;]HAa�A�T�S��dAn�W��`i�`?�Ae`���Ad@=���A��B�.Ba��cLCeDiHDj�Dk�FlGmLGnXGo�GptHs|HtxJu�JvKy0Kz\K�4b Bb(Bd0BkPBllBm|Br�b2c+xd*@BaHBt�di�e�e����adBd�l�u g��j2�i��tBr�Bv�j�	�?pm.�7��hm.�Bl 4��(ma0me�Bhxml�mo�mu��4ba�lb�Bc�mdtne�~f�g��h�i�jT�k��l��m��n��o@�p��r��s�t��ut�v��y��zl~���`o��8Citn@CdpCgxCk�Cl�Cr�Cs�Ctlp	r����uTs�s]�Ca�Cd�i �v�s��o�x"�Ckx���Ciz*�KiLu�{��8le�Ct}:�od��$Dp��Cm Dn$�s|/"?��0Dg8Ds��(?���`���@Da\DhlDo�%D�o��dDr�����xDd�Df�Dm�Dp�Dr�DsT���
�DaEi(EjTEl�Eo�ErFu`FvtFy�F�[D�2������(���D.���	@�(ElEn���А���n��_HEe���P"��4Es���<En��<2pEa�Ee�E�p,��YhEg�Er�b�В�|/.?�/���E�d����.�Ee�Ep�Er��tP34?�72$������Ea�EiFo|�`���$0����Ev��i(���.$Fe@FnP�(	0Ft�
<���q
8Fd|W�����LFrp��
TFa ����lFsl�iP����F�,�lf�FiH�k�Fs���Fe�Fi�Fo�FuКY@����x��Ft(����6k��h���Fn��M��aGo4GÄ�T.p���.lD���(G��4�v��M@Ge�� �a|Gm�Gn�Gr0�v�j:?ܦ��tGl@�:�Ga�Ge�Gi�Gs��hO�Gl|�@?��I���l�"�Gd�Gi��[
�F?�]�Gs@��Ha0He��iإoTHrhHÈ�\�n(Ht��?H��lDHrLHs0��t�9x��̭el��L���`H������	�Ha�Hb�HelIi�Ik�Ir Ju8JyLJ���ܺ���Hb�����Hb�Hn`�p�������Hk(��7H�\�dImIn0IrHIv0�(0��x���Id(Is�S4�����.L�g�sa$�-<In���If�IhSs�Iv��TIl�Is�It|p�M?�T?8��T�H��[?���Ie�Ii�IoJô��4�����I.�Ie��`?��nD����In��3	|�
h���J���f?X���JtLtv89���0Jg���
����\J�DJ�`���Mk���dJl����lJk�Jr�Js(�\�������Ja�Ji��t���Ja�JeKi��-�����Jk�Jr4���0�a�Js�~l?��Kk�������Pt��`�n(Kt4���P�����8Ks4��@KkpKyl~��LK�0�t?����hKe<��KaXLe�Mh$Ni��m`Oo�Or�Pst\tRuXRy�R��������Kd�Kf�KkLn Lp4LrLLv�����d��K.4����Ka<���)a$)l�Z����La��
����,LrDLt��{?�J��s\���c�Ld��e�Lg�Li�Lk�LlMm0Mn`�o`Mr�MsYt�Mv��J�La4�eD���8�o@�rH����e�Ln���@�����a�me�Li�Lu��� M�����lMix�J�zaMpt�6\�;@�tMk����$MsTMt8��D�@Me��HMl��|Md�Me�Mi�Mk����?���
��Ms����Ms��
@����Me�Mk�Mt�����(����Mi �%��[�����Mdt�s@��NnXNr�NalNexNg�Nk�Nl�Nn��o��p�NsLOtXOvp���.�i�mdNn�]Нr0]��vH���Na�Ni�No�����h�?|���Na\	
p	���NaOkOo Op(Os8Ot@
�L
���NaOit
�(��?�
�����2�
��0Or�R<JDOu�J����'.�Of�Ok�Om�Op��r�Os�Ov�J��f�Oo�oR�th�.�8�?����Oki�O.����Op4�Y8n&�Q�:PaPehPi�Po�Pu�P���$���I.<Pd0�g`�iLPkX�lXPpl��?���DPoX�D�?L��`Pb,�s �P��xPs!i@ ���Pt a�$�"���Pl�!���P��i2\#���Pr#�	�PaQe$Qi,Qj@QkXQnlQp�Qt�Qu�#xygQn�v�H$��Qgd%]�%��aЎ�0&��8QePQj|&N��MGo�'�	�'XdQe�(W@(KxQi�Qr�Q���$D)���QkT)�QeX��?����Qy�����Q����h�t�Qe�����Qd�)���Qn�E�.4��Rnd3��Re4Rg��n(Rt�}v8��,�.�8�<Ra�e�8��HRdtRr�Rtľ;�9����alRs�:-(��0;���Ra�:��Rr�����R��R��R��;3	t;���Rl,?�?>���Rsx��P@���Rdl?���RnLSv�?���RahSb�Sd�Se�SgTiTkXTl�Tm�Tn�Ur�Us|VtxWv�@XSh�@�)B9:�@��`SlxSod!��B]�i�Sr�Ss�D�D��l��WlE�Se4E���Sl�Sr�Ss�EP`F��1�G�Sl|�tH���Sa�J�J��Td4L(0TsDTt�N�NJ(Tu�O��N<Ts�PPP��PTaxTi�Tk�Tl�Tv`Uy R�'�R;�oe�R��U7�V��Ta�Tp<�2�V"�Tl�X�?�X��TlxZX!UlUrTZt�Te�ar<Us�Y���Td(�gDUiTUk|UØZ��Z�?,U.4Us��?�ZM
�Z�#�[p,�\�d\�LUr�U��^�?�^*hUy�^��pU�la��Ua�Ue(�t8ai�Un�Ur�a-0b-	��b�Uri��UhViVj Vl,VoHVs`Vt�j�+ ��?�j�Uk,k{��l8VuplP���P�4Vlm<Ve.t�nK�m��XVrp(�Vl�Vn<p��
lVa�Vb�pd�VeWg Wk(Wo0WrdWt��v�p*��a�p����.�q�\q�Ve�q&�Vi`�lWnWrr,@��.`r]�Ve�r��r�?8�?pt��t�5@WePW�u�|uK�u��HW��v�4vt\Wozi$xMpWa�We�WiLxXx���Wd,yg�Wi�8l�Wrĉsxx(���x���We�x�?�Ws���@�	XaPXeXYixYj�Yo�Yr�Ys�Yu�Y�zx(Xk�l�z
�z�� Xa��2�RnH��4Xe|Xr����	@Xd�Xg�Xi�Xk�XlYn(Yr`�sh�v�	Ђ܂���Xa�Xi�Xr���t��(0��ă���Xm�XsD��?�J�Xe\�� ����Xl����aYḻ��Ye��?
(��Yg\�����.8Ye�i�lYex���@Yl����LYl�)�dYd������H����Ys�YvD�%t��+�dh�	-t����Yg��Yn��������Yr$����Y� ��8@���
�Ya8ZdLZfdZk�Zl�Zn�Zp[rp[s�[t0��<���0Zr�	
��MDZe\Zl���t�etZl �ش
,���|Ze�Zll��?x��Zs\��Zi��?��t�Zlh����Zd�Ze���.ķ�Zs��T.Ⱥ���Za�J [e([k4[oD[ä��@�	�eh���,���<[�x�4���P[lD�X[a��]d[s$�i0���|[a�[e�[m�[r��(x��[k���)��.	��xH�
0����[e<����[l�[r4\v���?\n�����[e \i,\t��?@�i�\n|K[��(���4�.�\a�\b�\d�\e�\f]k`]lh]m�]n^p ^r�^s�^t�^v\_y�����\r����\e��T.X��.k����\e�\s�����@��B�\pD�f�����\l��T.,]rP����\e8]l@]nH]sP]tX]v����8�o����@P� K������dh�Zt]a��`�<��;|]s����]e�]s�����]n�]s�]t���t�]a�]kl�i�N�����]o���]kHY@T�@�]g����DJe^l`���<^dp^e�^l�^o�^sL�9h�@D^. tL^e�c��X^dX�d^n�^t�����gh����t2p,���|�.�e�^l�^v����8�^at�f(���^s��(�^aD��
t�����a$_e؄iP_À��H��_e����_d<_r��)@���4_s�������H_�����Td.|_a�_e�_k�_r�_s��r��t���_dlJn�_s3th�^	����	�_e��i�v��v"�_sv���_e8�P�_n���_e`k`t`�/@�qt���4�.\`ap`b�`d�`e�`f�`g�`k alhan|ap�ar�asbt\��h`gСP��8�X7@��x`t��?�`e�`y�����a���`r������`e��%�`n��#0�?@p�E@ ����`jarauav����e�������8aeTau��L@�0asHat��(��.�� ���taa����\ad�t��d��q
�as�au$���q�ak��i���ab�ae�ai�aj�at�lQ@4�X@��i�X@����bk,���aa�r but��l�aD �J�. ���(ba�bb�bccdlce�cf�cgTdixdk�el gm�gn�ip�ir�jskt�kuTlv��PT.�����ba�be�bl�bo�by8����_@���P���bk�bn�$�L�i�52|���be�?(��bg,cm���ca@ceLcmTco\cvTA�]��e|�4cr�2����'!D�;�mdcd�n�crx��@�������ce���T.�cs�ct���ca�cedgdi dn,do<drDdsd�(6��d@�]�ct���cn��l@�/W���Mda��W. g�r@t�V��20���Ldkhdrpds�i����\�.�da�db�ddeeefeh$ei,ej4ektlLemd@oTep\erles�et�eu�e�X�]�d.p�d�di��x@��P@��0�����
��@ee�����@T�N|�]�rDey�+),00�9��'8��@��0����de.�ei�ej�ep�er�eu8��L���G�|��@Ԯ����a�eo4O�0��(���=̰���e�0���
��.$fa\fbdfexfg�fi�fm�fo�fs�ftxugv y�Lf.@frTft��@����8fm��@<��@|�J:��@pftԵ�<��#�fe��o�fs\������8�(��fs��fe�iغ�@�fa�x8�����frĻ�fe��:ge4��@<���gd@���.@galge�gl�gm�gy�]�'.Tgn\gsо�@��|�p��dgi�,l�`t��@�ge�8�v4���ge���T��gn�����#�K.hrht8����ga,hd|he�hg�hk4Bl�hm�hn�hoishit�iv�C,�*��.Hhlx�tha�e�rdhs��l7y�p�8Pho��Xhltht�����@�hk��@�t��.�he�hr���@���@\5��4�hnl�]�he�9})�i����he�ho\��(�]iris����Fi�de. 3aDiePikXil`iv���|�<in(�����@`��@��|�.�ie�ir�iy�����\p��"x�"�io��]��	����
�i.�iaHCbjdjePji\jkljm�jr�jt�ju�jv�jy���jb<�xH�S��P4��j.<jaDagHjv���
4jl����h
T.��p�a�ci$�'!xjex�i.��	����Ca���h�
��W8���jj�jp�js�jtkuky���d��jup��@���@�� 2y��X�����	�	aDkeT6iPkllkm	oxkr�ks�kt0�3	(�<kn�AL�	AT�Xks`�`ko,�A4������kk�8�0x�t�k.�ka�ke�����>��kn��s@���p�.�kdlelg4lk<lmDlsLlt�?li�u. �(?�����a(le�R.D�q
�������X�"pla`�e�li�ln�lu4�"�A����xln��5h
��
p	a�li�lo�lumyH"\P�led!���d�+��(�t+��le�lgmlH ,t0��0��ma�.mt�3��5�DmlTmn`mr06q
�6�l6��Lme7*�3��H:}<�:���8.�mc�ms ;A<S	i(=�msh>\+�mane@nuhnÌ?���s>�mg�mm�A9TA���mp�M.PM��ne�Hnl�~�.�~��$nnLnrt~0ne�E���ȃ�Tnv����\n���na�b0c`od�oeHpflpgP�hqi�qjrksltumvn�wo�wpxrzs�{t0}u�}v(~y���܅�ؾ.�g�nn(orHoux�����ndos����toaH�,� oe�$A؉�4ok|�<os��tǧ��	Toa�od�oe�ol�4o�:r�ou�ov;�\�O��2��.|�,Aؐ��82A\��oe8����ol�
�on��oa���
�.pd��f
g pi(pl0pn8pr@ps\
vБ�(����������h����-,�fXpt�<A��)�plԖ��`pa�pe�pg�pl�pm�pr�Z��]�ps�pv(�BA`F�
�G.P��pa����.�pe�SGA���pth�Y�OAl�d�aDqe���	qdpqe�qg�qk�ql�qm�qn�qr�qs����<qnt�J�Pqs4��Xqe�Adqn�qt������qa�
e��2.�����UA0�K�d<�d�i�qk�zsDqt��[A������-�.L�fd���
�qaD<e|�iDLj@rkXrl`rntohrr�rs�rtsysä��Lreܥ�s`�O�`A�D�a|ri�rop��x����,Ĩ���rj4rp�rt\���K�����rn��ra�re�ro �.�rr���M���ra|�fAT�5t���HM�ȫ]
Tsa�sd�se�sg�siDtlPtopts�tt�tuuv�yXu���.tskP�n|sr�ss�st@�d/���
�st��XȬ��4���T.�sd�sg��i=mLn`s�sv��mA�ni)����sa��tAد�&�"��ktl�Nstt��{APY���$tp����,t���8t���`tlhtm(��(�*<�6�ti�tk�tlXOp0�(��2�tn ��A�����te\��l����tg���ta��SD�d�trܶn&ܺ���tkun���ta ueLui��8���.4ui<urL�'\�(ļ-T�Dun����<���=���o����	lua�ub�ud�ue�ui�unp>oQp�uè�,+��A����T.�un��Ի��ui��'!�ua�ud����!$ܾ��<Q�d����.Pvadvd�ve�vg�vh�viwkwnwsdwtT@u`@y�w�dx@�"Hvu�J�t\vaxve<�r����l�tn�"�d�vg�Qm�vs�*��At����va�ve��A.���os��t�va���A����n�d�A$�]�vjD��@��;0weTwj\wo,��A���-(wmHwndv/TK��@wdPM���2���twe�wr���rP[�A��(�wpt��we����S���i�wd�wr|��A��2���wextT�2xrXG���wo�����ws�,+x����.Txa�dxxe�xf�xi yk8ynXyo�yt�yu�yv�yz�4��m<�n�ppxs�u���
���
��.�Taad�AfUgL�i�Ak�%l�xnUp�xs�wt�wv`���\n$t�����wo�s�t�up�	��"ye�k�Pęe4]�xn�%�A�%Xye��yj0yr���Ah��Lye�1���Dyv���lyptyr|ys��W�[4�����A�ya�yr�K(��d�yk��tt��(��"�yl�ynXT<�������
d�yl���ye��(l��A8����V��y������.pza�Vc�ze�zi�zj�zk({l<{n�oP{pp{s|{t�{uxv�Xy�{�p��Ⱦd|zs�t4�\zl�f�A���zn�v��n�ze �i���zn\�2De�zu���A�����zk0��p���zl{r|����za{v(��'���$��,�{t,�{i�a$�M4{ax�e��H{rT����\{n��d{i���W.��e Xo��i���{n��������{��{�`�2�������{.|aL|e�|i�|j�|l�|n(For�|s}ttYu }v`�y��ä��(|.0|e8|kD|r�8�A0�GL�*�+aP����"�|a�|gikp|l�|r�|s���D�P���x|a�|o����.�|k�|s0�p	���@�|.��"�|e���A��A$�B`�
BD�M
����|s���|r0�t}e\�B��(3�M(}mH}np}r|}t���58�c��P}e�KX}n��]d}e\����-�}a�d�}i�}nPr~��|W.<����}e����}n�}rt���������}a�����}s0�	�$e0�B����~g,���~���dL�� ~n��~a�~b�~e�~f�~j�~k�$l�~o�~p�~r�~s�~v`��`�����4~�4���#���B������� #B(�0} D�`�#$a(ePi0l\o�rl�y���T.���e�n�	�De�s��x.T
�8t��sipr�����ha�lH k%X ����$ U��#X�at(i�.p7��������c�H/�n8�r /-�e��i�.��r@0��aL�e|�ü1x�/�l(�v�6����a0�e@��>]8�s�8@�nd�shD�tCt\�t�r���vG��p���sM
T.��a�e �iP�jl�o��s��u��yā�t��s���kԀt܀udx��x�	�y�a��i�r�z)B`.B�}�fL}���iD���T.4�pD�s����3	́�<�kh�(�6B����X�pd�x`�p��r��w؅=B������th�p7���)�J���T��ԁl�v��������̌��t����܁a�e���.0����l�a��b��d8�e��făgh^h �k�ld�m��n��ŏp��r,�s�t��u��v������X�vT���`�l��m��n,pL�v@�#B,��������s����-l��xЕ����a܂e�o�r0�y4�DBX�Ԃ.��n�o�r��t�\D�v'd�S	�.�����KB(�a�����S	ܙ]D.gd.l��nT�rx�sT����'.l�e��Y��d�n<�?��t|�*`�,�]��e��l�Ko��r,u�]��o��Cx�n�e��i�l�r̠QBԠ]܃sP��rp�� ��ءM�o���n�a��e@�hT�k��o؄t WB�����r��]H�a��e��o����2h�t�$�p�e���|�t<%�0���lĨ�T��Ȅm��p��s��t�5(��Wd�ЄsT�]�.�d<�jD�k_l��L��
�ePM(|��l,�r4�s�h�Di%(������
l�L�lx_t\���T�e|/px�u�������t��eT�uи����d؅e��f��g�i�j �n��o��s\0t��v��*��WЅk�*�̾d�a��t�s�u�[B,��$�g	���D�.L�aT�el�ht�k|�lL0t4��A�bB��.d�t$����iB�=oB�=�*�B�`�"��b���\����e����k���"<���0v�����Sa܆e@�.���.�n��]�e�i �k(�T.������l��]X�e��i��j��k��ṅt!y�����P�ll�rx�v����Cf�}������n8�xD���ul����e�����4������će�i�o��W,�2��nT9a�j(�rL�st�t��Z�����i���|�K �a<�eD�i�����(�|X�v�)�|a(��
r��s��td�ed�uB<�M43m��t<p�,��ĈnT�����aԈe�i�ø����̈k�.��
(����t���������.`�aĉb̉dԉe��fĊgԊi�k�l�m��n�o�8pT�r\�sd�t��u�Ì����.��g��m��n��r��v��z`�zB|����(��l��B�����n@�oL�[�-T�
\��	dj.�f8�gk@�l`�n��r��s��t��b%8�-�i$�l0�t|�(����a�����H��L�e|���v�����X�.t�e|�tD�X!���B����de.��p���de.��tT���t��i�-���\����̊m��������������Pb �k(�m0�nD�rL�u�������B<�s�`�d��q
��'��TX�x�s�%�B#�p�j�M�����a��b��gȋlԋn܋r�s �
��[,������a�����L�
����k�p8�rD�sL�t�����d��M|������$�����,�ä����B ��.��a��bȎdЎe0�f8�h@�i��j��k��lH�mP�nd�o��r�s�t(�up�v�yP�����o.�b�d�e�g�k0�lD�m`�n��p(�r��s��t��y����e��J��BD��dT�B<�rh��$�a����q
P�l�=00<��X�a��d��e��o��t�9"xt|�e��B�0�B4��$T����.ԍa�e�i �p��B�̍.�s��5��B��.�i�n��B��t�T8�T�ah�bt�d|�k��s��t��vx�`�L�b�h��)`�eH��#���B��'h��B�q
$
�	����eHJP�BxU�"�d(�h�iH�l|�n��pďr؏s�t$wv(�y`58�a�$o�
(�e�ADrp�B�4�v`��<�e`�il�tt�u�"k����p��ؾ.��a��o�$sT��v|�i=��g4���t����.8�o�sH-�i�m�n��x$� ������t����e �j\Y�B�>�� '��
��.|�a��d��e��f��k��lАn �o,�p4�r`�s��v���8F&h�x�<B�����l��tx$\�XZt���Đe�i�n�o�(��n�
����s#��0���l����H�eP�kX�s 5���# [
 ��@�.t�s|�t���Bx �� C�!�@�.��m�r !X��e�o4��$"�̑s0"J��a"�B��C�e����ԑm��s��CP�C�"C�"��r�"$C�"���l�"���r#��(��L���3$#]D�n`�pt�r<#-CX�DQsL#��h�el# ,�'��aВe�i(�u<�yH,Y��k��nȒs|,�a�
�,��,g�i�k`-Y�q*���x.���g0.��n�t�.3	p�3	�.�� �f|U�/��4�d00p!�0�`�u@2S	X2��\�a��d��f��g��iȓl�m�n�o$�r`�sh�t35C�2��dPn��3xD4i@5�ܓa�e�l8{<Cd5CC�5JC�5�	�a�e6)�Tt�6�-t7208��ШdD�pP�sX�t��x9<�i<9QC�9WC:�0;�t�t�;��<���.��lĔtT<�|�aԔe0�i��oĕu�y����-,��0T=���
x=��̔.�f��g�k�t�v���%�=^C�=��>�0�M`M�n�>eC�>��(�bT�f\�gd�kl�p|�t��vx�lC�>sC?lCd?zC�?�C�?Jt�e�?C6@�?]��g��n��p8@�`@{A�S
�@����dԕs�@�d[�dA�ܕs$A���s�Av8�A�����4B=	 K�C4S��R�� �dP�e��f��l�m�nH�pX�rdS�h�nt�r�E"�E�`�dTS�L�e|Sua3�P��n��r�S����aȖeؖi�l�p�t8�3Tc��31<T�ЖsHTJCؤ
PT�C�T�ClT����s�Tq
<�d�Z�C�Z]�vxZ$�r�Tt0�e U�	�U�CLU��P�sh�vV�CW�
��a��e̗iؗuHW���'.��d��spz	�W*(�
�W����nėrDX*�X��PtPY��Y���a�e�r �s@�t�Y��Y�C�Z�	�e��2�ZS	8�k0�s['8[�$L[��[Y������H��l����m;�[��d�.��a��e�[�C�[�C�\���r��t�i`]����et]�C�]��a,�ex�i(�oh�u<�y���Й��^���a,�e�]���d@�gT�kx�l��n��p��rԙs�t�u�v�^4��i.,_��4�e`p�_��L�eh�sp�t,`�D`x	�`�`a�Ca����dlb��`!p��
c��n�b����aęe c��d�Cd��̙t�d��Bef�	��sLf�\f-�e �i�fd/�f(�f���n�k
4�.p�d��gКi�k`fl�m �n8�r@�s��t\�vh�ym�m��h�a(4�!�3��|�e�n"��r��s�n����a��iȚnh�ro�C�o����hpYDe|-v�q�C�q���e��k�q��s�Cds���e0u�C�r�t���e0�t�u�C�vd�x�T�j4Do�t�y�}��aL��?���<�	p�a��d9e��g��k̛n�p�t�vP�d��]�feěk��S	�dܛj�t���C܉D8�����de.�e؋�,�3	����J�bL����bH�t���.\�e��]8�ṭ�?ԣT�t���
��a��e��f��g̜k�m�n�p(�s4�t��Dd��
��s`Fip�
���Niض��Ĝs�X
D�eT��؜pl�I���
ķ����d�s8�(?\��D���� �a��(�ȷeT�n\�sd�t<��п�`�t�),��l�i���t�r�����a���r�~��X����������������n�p�/r�s�v8�y��D�Za�����a�i��D��i�����a�/e(�iL�i�&D����0�dH�e�.D�5D����P�.p�ex�n��t��#L�7��e�����r`�/)��M̞aP�e$�i��o��u�yD����Z��Ğd�k$�l0�n ��+(����.�ep�x�"��n���p�"�t<����e(��@����8.x�e��i��k��8�d��g��i��kȟl�n��r�s�����<D�d-��������8������l��t���,4�CD��]��dܟl�t��JDD�$�����x	�����t@�Y�i����?h��aD�e̻gP�l�Vnt�t�����nL���@�.d�al�e�
t��J\�-�|�a$�d��k��tH�J�����̠gl���gԠl�r�HQD �X�a�d��e,��PG
<�L��. ����t4�XD��k��x<��(�nT�rh�t����p��0��dv8`�s���^D,�(c.��r��tp{�eD�����tLMġa�e�ix�o��u�y��|�.�b�k�s��t�v�(<" 8��-��\�8.D�bL�d��eT�g\�i��k��l��m��r��sآtp/��(��h�e�A�r8�$;6t�i�J|�r��td��l(�d���YȢaТv��0"lDd"��.
�,	�f�g(�kH�lP�nX�p`�rh�tp�v�.�.]�l�.�<�kĨi��]4�o(/��/�-�0��0�&D2�
P3i<B.��e��r��tTC�)�D�D����d�F9(�B�&$]��d\�mԣrܣtl^(�^"�`�$`�hbq�rD$�����(��(b���Ue��$hb� �.P�d\�ed�rl�st�v����b��H�rcrD�cv8�cM d��(�i�f2�e]��l�r�v�e����a��b�d �e8�f��g̥hԥi�k�lܦm@�nبpl�rX�s��t(Guܪv�f�tg��g"t�l�i	�a jT.pl�4nyDP�gX�sPn��(�a`�fp�r|�t�n(�n(<o�&�;�oXh�e,p~D�p]Xp]��a��i��l��npq�Pr
hrP��mxrM��e�t�D�t���d8x�D<v���n��sDy��We�z��|.<�aL�dT�e��f��i<�j��o��s��t��uЦ�8{W�.0�v$|�D�|"T.h�i|�nX}���<e�}��}��t�et~9�~�D���O.܁�4�ET�S����m�(X~��Ȧ����d�.$�e8�fD�gL�hT�k�jl\�m��r�s(�t�n
�����dl�"�n$��0�0�aP��D\��D���?(��D��a��e��{���l�aD�t�rX�C��r$�������e؆Għe���0�d��nԧr	�D<�? $���ܧ����l�t �yL�,���ih����ԇ�D��.8�a��DĈnhOap�d|�e��g��o��sĨųyȉ
ԉth�a@�	��a�� ��V�t��s����L�t��k�kt��]$`T.�����a�e$�pt�r����"�kpfr�
@��t4��]�.@�aH�e�YiX�l��<��,4��Dp��P�eT�`���d�.��a��b��dȩe�g�i4�k@�nH�sP�tܔ���n|�*ܕ�D0���Ya\��D�$k���Dx��9ԩ.��ܩa��l��<�]�e�s$�u���D�$�!�X�8�,�eT��t��Dl�G=8��t�c|�e��f`"j��l�B3	�.��B��(P�i.p�]��e��r��s̪t����i��tĪe�;i��J�a��e8�j��2@����.�mn�r(�s0�t�g%����e�|x��7���̵XT.��aH�e��i8�l��ox�r�s�u0�yL��LJIС��t�h��|�g��k�l�n�r(�s�D������.ȫaЫeثk��[B���DĶ�-�J�l��tp�,+Ļg<�������nh�CC����i(�G
<�e\��DP�4�r���8.|�a��d8�e��i��l�n�p0�rt�s��tX�{��E������a����4���������	D�;̬e�(��Ĭg��s�th�3D��p����n�t��$����?
�����d\�"�n����$�aL�iT�l\�rd�s8�E������6���E��-l�i��t��L���h�.���ĭlحn�o�p�r$�s0�z8;������o���Cl���ЭgP��4������s��]�e�id�o(�
�E�����s|�i�\`�ax�e��i8bEh���L�t8���T�np�r���-������]��n��t�.�A�����e̮l�r<�sD�tX�v`�G \�t��dܮe�i��$E�~+E�������a�e�s(�t4�vD�2E��8E�P��� �s��#��'\�S	P�e�����	��1l���`�g��n@��h�a��e$�i\�oİṵy�ô�
4'������gЯiدk�m�n��s�t,��
���e�i\��l��0x��<`�0�?E���H�s�t�����n8�oT�sH�FE0�^;@�iP�OE��I8x�b��d��f��g��s�4�T5,+��������]��.��e��p$<;VE�����s�uZ(��^Eh���԰y�������ܰ����0��@���b �lx�d<��H���(�d���\�ld�yt���l��<����'��������.|�d��fE8�9��e��iܱoL�ü���Цk4mE�����eбk�m�v�,�]ȱa04ife�p�s�t�9���a�:t�:]�a�e�;(�e0�o�;@<{�^M<\��8�v@=��x��@����T.�`?\�a��e`��h�d,em���s�`��rV��B�aP�e̩h��iضj�k��l�*mȷn�o��p̸rظt��u�v��y��(���b��l�b����eH�l4b��
�bP�eX�jl�kx�l��m��nȳr�s0�u�bqElc;��%e�.xd��`�e�e����a��l���lgT. g���e��p��g���Ie�i�i����.��i�mtBr�v�94]�nPjh
�elj��j
�Ce�j�Dl��a�e�k�� �s��wE�o�<�ttn
D�e��g��lt�m�nP�r��s��t<�u��ylpJ��e��g�pi�pEs���i��sĴv�sppt}Eu�h�.ܴ�н�����Դ�v���.�a(�d4�e�Ki�dkX�uD��Pv".xv��ndvt�e�v'���w��<��x���.h�a��xTx�`�b��t�z
Hiz��x�kLt�{"�{����.<�s(~�l�x���a�e�f,�l@�mT�n��s��v���l��rT����e8�]��rt�S	��]�f��Y�]$�oT��Ed���8�e��M,����L�dx�i��j0En�s����p�t��
,�4:��m@��Ԉ�Nn������e`��������g`�����r�̶a�5�����e����p �rT�����a4�oP�yx��(�L�a��8d���,�fH�g�Er�����.`�e��C�����l��pP���h��]�8]������o��y(�<�i�	ġ��r��M��aX�o��(�(��d�k���ܷ�P��Tc�8�J�����f0�k@�lL�mh�r��v����p���8�oܦJX�aX�W��H9l���`�a��gܩ�E�x�a��*ܪJ��eĭ�����l@�X��iإo����9ĸi����a�e`�ix�r��u��v����`HdH�\�d�k8�lD�p��*��t��$�i����,�l�_.ؤ�
���L�p��T�lp�p�"���Įe������h�����������J������l��r(�*�}
4���Ĺa����̹rt�Xعa�e�����lnąrl~�������<���.��a(�b0�c8�dH�eh�fp�g��h��i|�j��k��l��m��n��o��p��r��s �tX�u��v�y����q
������.ܺb�d�f@�gL�hT�kx�lлm�nt�o|�p��rȼs�t�v���E����Ժs���#��a�o��2���t�����a(�e0�l��t���E���E��� ���8�e��E4���dah�kp�l������E8���d�.��aȾd��e��i��tp�~��k�_����ET.��<��g�56����Ȼe�m4��E<���	�Ua�d4�e<�fD�g$)lP�o`�tl�v��o(�e��E�� �.(��D`�g	��G
�s(�9L��E4�X�rl�:
x��E��d��a��p����Zh��S	������������h
������i`��ؼj�td�($�������o�ut��pl���2h�d/�J�eP���AD�p!�i\�@�.��a��b��d��e̽fܽgP�h\�i��k��l0�mx�n��o�p4�r�s�t$�v\�����|��/��J�n.�LaĽt���)���E�CD���Խ.�aWe�h�n@�r(�s8���pl��#���E�@64�l��#�p���E$�(����<�r��%D�eH��p�e��k��nD-�r���E��]|�n���E������s��A@�����.Ծaܾe�Li�n��o�t�vL��E0��t\�(���EtF�B�x�a��e�i$�kT�l��m��n��o@�pd�rx�s��t0�vȟ���za��b�d��e<�f��gD�h��i��j(�kX�l,�m��nH�ol�pt�r�s$�t�u��v\�y$����пe��iD�
F�����t������sH�Ŀk|�mT��x�F��F�����a�e�Wi��F���.��g�v�&Fx�J(�dH�mP�n`��$�-F0��pt4F����X�������r����l�a��e�gP�i�k�o�s$�(D�<F@���n��r�sh�t�DF|�LF$���g<p�@�t�l��SF4vl$�YF(�p������r��]�e���.\�ah�e��f��i��m��r�u�_F��m��Xsx�v��"p�������d��s��v���8BfF��JC<�mF<�J�'n���Me�lP�`,��e��(*a�r�ì�tF�������� �-�e<�iD�lL�n��X�zF0�gHZ�F����T����D�T��a����|�v��Ԧv����������e0i@i���a�b,�c4�dlNeT�f|�g��j��k��l��m��n�p0�r8�sT�th���\�F�e$�l(��t��F����
H�eЖx(@�lH�`�t��F����h�n�]p�e��i��j��np�[B��F��Fx^� j'0]��a��e��lt�F�	Խ.�a�d �e@�kH�n�NoP�s`�tH��g�(T07`�4�gH�%T���,�e�{
<]/P���\X�.t�a|�eh�31hp$���r�����e��u������FT�����<�F�M,|����d�i��n��s�t4�Fԉ'X��tHM��	 �p(�s���D	B8 	�p	dD�kL
��C<JL�t��F`��`���(���t�a��e��u
�F����l��n��r
���t���)0
�
�8'h�F���}5���
�.,�f4�gD�kd�l��m��n��p��r@�sH�td�ut�v��	0�t<�sT�t<�)�d/h��\�ix�j��l<��F�
����m�Zs��t$�Fԇ�F�@���i@�a�����e��p<�F]��l������d�e(�m0�o8�shg
'\�.�t�B��,��F8���T�r�����F�i�;��Jl�a4�u�	��.��a��e4�hD�i��o�u4�yh�����
h�.��d��f�i,Pl(�nX�p`�rh�s��u4����-����f�iGT�
�����lX(��a@�dH�eP�s8�	GPGhG���!G�����.x�t������.�������m(G$����a��d��i��k�n�rl�T.G����k��s��6G�����.�l��H>G���i�J�.,�s$
FG�B�1L��<�ah�dp�e��g��n��p��s�x$��P���]x�ehNGUG�\GP����e��g��kгl��m��n��p�cG�$�iG�n ��H!.��a��e �� [@ ���g �k(�m� pG�T��.T!dD�gT�kl!�# KwG�!tL�t�!�@�!�����`��|���!v8��b��m��r��s��y�\~G0"�G8"�G�^���e��t��GD�3P"��"�G�"����d#�a�y g�d�e\#����m$*��n�*�G<�aH�rh+��p�e�*0�l�0GT3(d3��P�a��b��d��e��m��n��r��t�3�	�3�4J��l��r�3�+�L�e�4�5�G�4����g��n��t<5�G,^�GL5�6247��e�7�G�7���d�8�	D�aL�dT�k\�ld�nl�pt�r��s��v�d�8q
h98,�Z�9��9
�9���sľ�t:'d:��kr���erit;���k��l��m�p�r�t4�vX�y����`������;��;�0<�G <�0�<�G@<J�k0=$d�����.H�r`=d$�aP�e(�i~��=2�=->�p�ex�k�=�G(>��?��T.��a��b(�e4�f<�gd�hl�i��k��l8�m��nUo��p(�r��s$�t`�vl?��l@��@!��l�o�s��Bad!�@B�G\B�dB����4E�Lnr�F�H�P�ate\�g�G.�H�xJ��JE|�s��t�K�p�{�K����a4L��e��k��r��s0M�G�MN�GPP��4,e��f��i�t�u�y,��Q�G R���kU��T��r U��Uq
@V�Q��$���VnT�i��o`�s�W��W"L�d�X�TZt(c.��e��i��r��s�Y��h�dH�g��i�Z�GxZ��r��t�Z��Z�\-�Z�G�[HP_n��e�i�r�_T.��r�_H�_H�`H�e��-�b�la�� �dD�e\�fl�i��s�b�.T�ixc{AdH�d�Pdd�e|�u�!%g��f���kt2p��tXgW<g���ii���a��e�i�p�s�y`i*�h(��n�i"T.��s�j�j���k�l�m���]<p�<�ru��.�t80�aT�e��u2L�g$x�l�i�x3@�X	.��a��e��i��o�Yr,�u@�yl��z�z����a��i��k��l,�m4�rp�s�z��z���W.��e��s{d/4{xx{���a�e$�i�{"�s�tԵ
�{<�|�~���8.T�e`�mh�t��yH~L�t�~,+x	T
܂
��i����x�g��i��k��n�r��v�����H���e��s�!H��������a��s�����(����s������s\����d8�eL�gT�j\�kp�mx�v��D�s|�-���
��(H��
h�i�%�$�/H��5Ȟ<HH����l������e��g��k�l@�n��r��s��vd.�t�S	��4.��]��e��g��s�t��BH(�����.D�x	�iP�IHx��|QH�� �lБt(�e���4�d\�ep�g�( �WT�s̾�,�:h�s��"����|�tl�����n�m��ih��#��d�a��e����n��r����p�-��e����	H�����l�r����tl�b`��8�m� �l T�\�aX�e|��$he��L�n��	$���������d��p������s��
@�
��c��d��e�kT�mh�n��r��s4�t��v��x��T.��n<�����e����E�r,�]�t�Pa,�e@�oȱ(8�et�] �rر�|��L�g�3
L��`�e<�
h�]�Zd��k��s��tH���t��6��kĹ2����eĻ���r�����a��e��kH�����.@�'!��a�e�#����
�e�l �t8������,�r��|0�iP�e\�hd�t���x�H�nP�IL�YH�J ���l��X�Mt��x��c��t�=x�M��z��@�.<����l��r�t0�������[e��i�l�s�`��l���e��T��_H�����e,�t���(���8.x�d��f��g��j��kD�lx�m��n��p��rX�sp�t��v��y����eX�������H��@�fHP�����.��e��j��k�nL�o�s8�t��CT�mH��tH��jd'e��{H�m��aP��� �e�k(�ml�G�F�HTK� K0�a���T�a`�e���.,�T.p�td��h�����|�����������������n��s���H��($�(����s������a��e<�lPrsL��X��.�n(�t ?
�e�c���d�.���24�t|���:$��i����D��,����.h�a����H��a��e���D�x��t(�����s���X���l��s��Et�d��e��n���p���.������.�a�d0�e8�l@�nH�rP�s���`��$�eP�.���;@�d�u>������H0��t���X�g��k��l��n��p�r��s��vx�� �����e��(	��e��
��d����t��a8ee�����a��e��n�u���t���=t��H����� �aL�e`�st��t�t8�s�����!.����,�tP$(���D�.$�2�qX�k��I���l������a��b��e��k��t(�h�l�H4�������H�} ��a�q�M �����a��b��c��dh�e��f�g�h �i4�k8�l��m<�nx�o��p��r`�s�t��u�vD�x����aP�b�cD�d\�e�fD�g�h�i�j8khl�m�n�o4p�r#s�*td3u47v�8w�8y�:z��ì����o��r��s��u���Ș�:����T.d1�
 �����|���(�a$�eP�s`�uЙ
���|��i4�tl��H����y�<�a��D�p��v��t�=��i0�k��l��n��s�Yx�������d�h��ܞt@�������e��f��i��ll���
̟P��sl��H���С����e�?j����
0����su���
,�.d�a|�e��k��lhr��s�t�u0�vX��.t�u�y��"�n��r���<%�����l|�]��e��l�'�X� 
�a���l������a�te��i�ej��t8�����
���a�@i �o$��`R�
tR�������0���
��.p�a��e��g��j$�l�o��t��u��������h�m��n��s�*��gd��0���h
��iXk��r��s��(��ex�]X����.��r<��#�e��e ��H0����sL��dgD�rP�s���el�i��s��y���H����<�g���H���`��	X�nظ`�g(��`��x�a���8�
��n����rĻ��e,��H�����������T�n#�a�i@�����b�e,�i4�o@�P�!nH�u!��W$�td���Ql��H8���
.x�a��d(�e`�f��g��k �l��m��n��s4�tl�v�]�`l��r��s��t�8 �,��`�
����kx�t��a��e�r�����.��lT�H|���.��e��D��r�����p�J��e����l<�mL�t���#C)t���D�t��R��X�ax���������p��((]/�t��f��r���Hl��#�=P�����l��o��s\�����(�t��a�t��k�l�t�Bv�M����H0S�H����r�Y�Hh� �l��(�iL�r��(��D�a\�eX[�Hd��p�d�e���x����.�Za��e��i��l��pl��T.��R\���]X�a��il������	��.�a0�bp�e��k��r��s��tT���k�m$�rP������d�eH��@�oP����I�
I���H����I����\�a��d�n���|�.��a��e�ci��I�������a��0����i��p��v4�eh�
����e �i8�r�M��M���t��2�s@��L��k0�tLO���!I�`'I���@�k8���H��8�����a��e��f��i��p��s��t�u�2�~-Il�����a��e��f��l��s8�������<�o(p�Z�����a
������o�r���5�������	a@�ed�it�o��s��t��mP�e(�4�s\�v@�". ��8����2����l�p��/$�2x�t��e��rx�3I@���.��e��g��k��n��sPEt �9I��3	D�T,���e��������kX�J�i(�l������nl?I4�i����8���<�y��l�a��e��o��u��y\PP�d|�n��x
.��t��	e��r@���a4�`+�h+]��a+���d���.��k��s�t0?�0
�0����e 4]$�e8�hCo�5pn8��7��0�eh>�P�y��<�p���a|�b��c��d��e��fD�g��hH�i�Kj@�k��lx�m��n��o$�p�r�s�t��u �vl�y��Ð��܅]��d�g�k�l�m �n(�p0�rD�sT�tl�ut�v̆�؆�
�EIx�r1���
��,��<�bH�1��xh��)Ԉ��L�rd�tt�� |����� ���b��ut������d06�|���lP�]��e��o��̌J��d�e<�iP�o��s�mu��y��äG;\���ih��
���.dmb4�fdJklJl�m�n�J���H�l�^�
���`�e��rPi�zn�rt�t�m��.(l(\��l����k��l��6����rL�X�����k̎����������-��a�i|nl��o�t,�Ì���r� �KI���e��x@����$�����|�tԖ��8�a��d��e��g��h��i��j��l��s�ou�v4��6�`�|����a��e;�P�g	�paLJn#����m�O��dHa��i@��<�#pm.�p�v8�RI,hX��ltlZI\q��p�i��~/@�s����(��DsaI���	�.t�dDe��g��k��l m��n�sl�iI��.��]|�a��e��r���P�.Нr:�0oI��]��n��i��(��e|�vI�����f��n�t��h`���e��}I���e��(�o��x��n��rd���0�e��iDLj��k��l��nto��r�sT�t Mux�vsy���@�������l��9>`���a��e|�i���H-Xd.0-����e����d`�����u�-�a�u���@Ĩ���.4�a<�e4rpD�t����R\�����
�L�rh�sp�y�PHRtȪ~��t���HM��������k��r��vȫ��
��a��e��i��l�ro�s<�tH�u�yd��@�;���I�N��(��	��.�Md��i�<k0�mD�nP�rX�sl�v���IT�J(�e0u����<�e��IT�(d�kH����a0x�a��Y�"TNd��ktl��n��r�Nsd=t�NvT��̱��f��i$v����e��i8�o�(0��	X�n<���g(�2�s<����e �khvx��I����3̵(�t��0�eD��T�t����I����P�\�����J
��a��b��e�i$�m0�n\�ot�p��s����oI���e��n�x��s�G
��s����a���"8�����n�'��n������k\�����a��B<�eļu.���`���H�d�P�r$�i��l4��h�e��o���I���Iн�ܾ��pt�d���ؾ.��a(�c0�d@�e|�g��i��n��o@�s��tT@u`@y$����@�"��b�l�mX�n�tȿ�T�p�(u.,�,�t�e<�r��d\g4Hmh�rt�s�QtX��I`���`�gt��
�����o)�I�(��n�.�����g��k��s,7v�.�
,1�
���D����o��"@Rm��r�t�v,���m�E�I�Fc0G?TGa
����h@��������2��t8�a\�kp�l\wo|�v�M���]oh���h�uT�fDZ���2��n����iD�l����kdc���x��s��d���m��n��p�r�v���,�9ܔ������a�d|�y��d��D�al�e�l��p�r���4�2�SkX�rd�s���$�a(��T�5x�eL��Ih�1t��������������e���r��]��e$�t����e@����4�"�cd�kx�m��p��r��sx����a47b�d�e�g�h�i�}j�kP�l��m��n�o<�rD�s �t<�uT�v�y��À��4�"\�k4��Gp���p�mt��2�����a��pD�DX���lTe���I������a�e�t4�9"���I ����ad�AfUg$�i0�l@�md�nUp��r��sTUt��vLd����s��(��I�J8�.P�m0��It�?
t�a`���X�g`�n��S��
���|�k��I�����i������t 
�M��o���'D��0T����g�k��s8Bv0 9"�����i����.(�e0�h8�r@�sH�t��Ix%�+���I�'J�(o-��lal�e���Գe��`�g��v~���.�}M|�e`+.���a��2h����o�s�2O��J�'.�e��f�l�m,�sHxt4�vX�
Jh�*�7>����l���$�rkJ4��l��|�����	�W.�h��j��k��l�m�n�v��|�J��p�o���x��P����TE�X���l��o�����T��x������M�eh����C.@K���eLK���k��������$J,�oT�XT��J4�lL�m,�*J��At�i�t����`�e��Yh�s��Ql�q
��r��t��v8���������^1J�^8l�9J,�q
��d�s��AJ����.,�a@�eL�h\�i|�j��k�m�n�o�p�r �s(�t$Eu�v�Xy��4���r����"8�nX�GJ����T�d�kt�nt ��\����e�MJԉ���f|����a�i�o�oUJp����.��������
��|\Jp���eD�����$�GJ��}8�eJ��kJ���*�����W.X�a|�e��i�o"r 2yX�������P�f<�sl�t|�qJ��yJ���-t�.iH������d��k��l��v4�������J\�n��?��-l��������J��������`���r��`�e����
ؾ.X�al�e��i\Yj(Fo��r��s�t�u }v`�y�ä���k�El�
p���@��.�xe�|gik��m�����k��s02p		��J`��JT�0�t��i�s���,����.����������-�k�Yr�sL�@����-8�aX�l`�oPr�L�l4|����D�uX�?Id�����rL�"&���t�a��bp$f�h�i�l�m�s`��>��:�|��t;���#��#�k�M�e��#PE@DM�e�t�$$�e0l<�oT
��	�e4�l�x
@0~�ah�e��i�r��\9{�8`�ax�i�;��Jx.��r����M��s�M]��ol_b-x_����e�^����i]K�e�r��8.G�����sa0eho�r�u�yHw(�s�p�y�J�y��(aHi�z�J�z�@m�+����Tid�x\l�rЅ�J����xep�n�i�����e����l�s��Z�����Z�r��@a�\b�c�d�eHf�g�Kh�Kix^j0kl�m|n�o�p 	rp	s<t�u�v8y`� �dT���	8dlgtk�l�m�p�s�u�\vD��t����<����|l�sd�@��
`��<�x3����p������JH����kЕ*
ad(e`ilotr|u�v�yHü��T:n<���J�JX� e8m(P��J����@���Ta@�Xs�����5������i�����nܙm�f�i�m�n�rst4��#`��#H�dl��hT���af��kX�tp��<�!k|�*��2,a4t��g<0����|n,�]<a�e�l�Ko�r�t,u�]��.���pe`�����H���J��t'�H'�������eD�iP�X�.x�]�e�j�nu@��#�oP��J���n��*$��.hr�]
 ate�i�j�k�o�r�u�y�È�J����`r�i�t�,�5p�-�����]�jT�o��2�l8���P�rī���a�tPT�J���n�����T��HaTd`e|f�g�i�k�l m<nPs\tpu��(��gL�W
�e��lil�a\�J@�teT����l��dp��J���e�n��oI��<��.ܰ�e�hi���Jh�����n��X"����`�
b�JD$��(��0��K
���Htt�heH�ȴd�����Wxb�t\���	�a�el o�p4s<uDyT�P��l�T.�dr̛�J��2�e@����i�������ai���JL�Kt�
K,r��KT�������ȸK����L��$��`b�r�zи��ha�d�e�f�gij k4nHoXsHMtlv�y���u��W��mT��@�'H*���s��t�e��K,�t$�l@0��3,s�7%K���@e�;`�p\�,K��Pk��pԉt���xap_�����2K<���l�m�n�0p�r�v4�JL��\�]�s������2�����a	l	r	s8�����a��),�����<	aH	e��op�x�4	t��$�f$�x��T	k�	m�	n����\	a�	e
i(
jL
k�
l�
n(�o�
p�
t\by g�8����2�	i�kNl1m�	p�	rd��F�w%Tx�.����	a
s���
g 
o��yD�De8
u8�X��	d
nl���@
at
i�
ou`�����@��l
ld���0�k�������8K����
�����
�8���
r�������
t�����
a�
ir 2y(����-T.Hl\��D��e���4y�����X�AK��J`a0�ehjplxr�s�tp�
��hHK|�T�����
l�9<�M�b@3t�|?
�����d,�W�n�rT����aio s�����!e�tX,�t��sp����i���i0k��i`�������.te|t<���@r������T�����h|K�.��� �`��-�����g�Ok�Or�����a�e@
i�
o�
u�\����.�g
l
n(
p0
r8
v��q
H�NK���
s��t��t
th���dt���"p
n�&TK��L
n|
r��tT
aX���d
g�&TK�i�
r��`����
d �q
����
b�
e�
n�
r�
vx�x��d�Y��]KX�(d��
s8����
e����
r���0��(����@�H CXa,Ql��u���D���Pg�dr�]G�a�eiTu�È]����g�t�d���Yem�.m���aԭr�k�d�fP�i��s�t(n?�|�6�{���i`�� t�n�h<�g<nHt�J���t�W(s���0e����Fe���� 9edt��������Za���lp�y�~����x�����V.�2L���e�����n�r��cK���a@exi�o��������dg,k�m��.����e�(���$u�����8g\i��kD�2���Tn����xVi�sh�hnd������s��.��a,��l������L��a�e�ito�s��2\��el<r`s(gl��i(����k���0eT�hVftV��L�����Wel�k8�t<B.�Ht��kD$���Y��e]�Ynr t(u�hv�e���aDb`chd�e�fg#h�i�j�khl�m@n�o�p�r8s�t�v,w�m��f����.`gZlgHhphP0r�g��8eXlTs�hiK�iS�i�2a|e�o�jPX�.�r�j]D8a`kRply�k�l�t�ld�l
"�mxPnJ�f�r<o]�.�R�oX�eip�
6��ptXp]aHeTg\lxr�st6uq��p@n8qg	Prt�^�*�rtdv�rKla4e�s2hs#�tpu�t���l�n�u��uK3<vt�e�i�^j�k��l0s<t$5u�vnK�2Lw�e���%��dhw�n�ytKDy��(iz��c"	�{��Dm8{WLr�t�z��	Xa�e�i<�j�klLo\tp�(�{K�}��e�|"�l�r�}�K�v�~���a��Pje�~"�n4�K�i��x��d;b��g�a(e��� g@t��n
T���8t�h
L�)14�Tr���X~��h�X�"X�.���|a�b�e�f�g�i�8kp-l$mkr,s��K����.�����el�"�n0�n#P�/Hl�2s������et��(��K��8.@��$Ĉ�hape�g�i�o�s�ųy �i@�X�d�l|�(����t�s�-_@���b��I2L�t�a�lLp������d�p!x�������lppr���K�]d,g<l`ohtD��K4��Kp��4eT�|��K����L������V/��ܔ�k�l�n�s`���xade�g�i�m�n�s�tv ��W`��$�d�t��?
|����gHAt���0��a$�.\�'��g8kLmTn`rht�(Ds��������%t��?&Ęxte�
�x��E��|a�D���t<��n�
��e(sH�iT�]�a ��t���il��K�X������il�eܡ2ȡ��0��,�8�\adelhtj�k�p�Fy��������@�pL���|e�u(�-���H���p�]�a�e��i�oT<yP�iX��4.�"r��J�;a�e@�����gr�����de$s�X��#8�l̵�Lahi�o�GÌ�
���l���Te����\n�p`�
P�"xi���^e8�.�a$eLiPo@ uT!y�!��	�K��P�t�����a����	�bf,kXn�p�r�s�tv8��@���i��.$��� aDt4O
��<oh������Pehst�LF|k�M`h���te����e�o��2D�5��4���4.�s�����e�ü��"4~�D�������J�e0����.X������.(�]l����aJcld�f�g�i�klnXp�r�s�t0v(��K����dsP�4��Mxu4
�i�����nt��Мt@������.�e�l�o�$�$��]3`�	�r,�K����a��K���a8e@gHi>n�cs8i�n
�K�h�Pe|rx��K����hs@��pe	J��o@u|
d�i�k�l�n�u�h�d�o|->$�^(
��
���a�ot8)d�Kld(l|te�TK`�<aL����
Db�c�d�e�g�k�nos(t|�u4vHx��	��$#4]�iT]�.dS	�]�k������a�e�g�n5D�9���K� ����t�!I(!�� o"J@it"i8��04��
�.�a�e�f�g�i�k�l�m�n p s8 v 4��(r�5�'�5(6�|6��Cn�6(h7J��!88���. 9]�d�g��ؑ�$:�r�9�� e�:�( k0 lL��$�:�K�<MS��.x a� e� g� i� k� l� n� p!s!t8!v�R-��.̌�
�S��� d� t�F��S�TM�M�KT(� n�T�0XT��� l�TL� d�T�
�_P_��� e�U�x^a!kdk$|Vd��a��e0!is�7xW�0Za8Z��@!rZ��H!dl!g�!k�!p���!gX�i�x!eh�] �.�!edZt�!k�����&��.�Z���!e�[@=���"��!��!��\
L<\���!a"d("k0"m8"r@"tP"y�\����. "td��)]h]� ^
3�^�|_�\_��H"al"e�"r�"s�"t�_Lx"t3��_]3�_�J��3�`?�ha�"e�"r`���"d�"k�"sx`L�`�"t ��$���`���"l�a L�a��"t�B�.\#a�#c�#e%fD%hd%i�%j0&k<'l�'m�'o�'p8(s@(t�)u�)v*w$*yX*�4b���'.��b|#g�tk�#n�#u�c��g��gL�Z�k���#r�B�#h�7
"�B���#a�#k � tn�'.$d$eD�g($i@$mH$n�$r�$s%t%v`oZ8p_�o�$rqiIq�� $d8$g�qtu]v��d�.l$d�$k�vs�$txv�dvtd$e|$i���4�w]�$a�dedw��ux���.�di��k�$o�$sXy'L-L����$.z��$k�$t�z�|{��{���va8le�}Q�~�,%ð4L���$%�l�x��p��M8%o_�
�]P%l�X%lx%n�%r����0En�s���8�Uԉ��%g�%t��%e�%o&u�%:L��BL�%j�����%s����r�%v��C�����%.��JL����&k&r܋���� fdDfnT��� &a\&i|&j�&o�&r'v$'y`�@��T&s������h&d��Xp&e�&�L��4����&�ȓ��&ld����&l�&t�PLh��>�Ԕ���&n����&e�&u'�ĕi�A�����&�p�.'i̗���.4'n8Z[��8P'oh'u,��(�����.`'tH��h��t��$�p't��Mx'iT+Ü�M�'d�'k�'m�@����0���'tܦ���'s��H��'kzl@�X�'e(i,(o���(k�WL$�](e�����$(r����K|(a�(e�(i)j )oT)r�)u�{v�^Lܺ��h(b����p(b`Hd\zk�zl�(n�(rD�t�zu������(d4ig������H��(dd�g8Ik��L�g��k)lpIn�It@�^ ����(k|�"�������)n8)r8�eL����0)s�����el)n��D)e�Io��y�)��� a�����t)lh���|)�X����g
�����)a�)g�)l�)r�)st
<���)e��x�)i��lL(������Jit��*i�������*k �M��l|k(�t����Tvap*n4��4*k|*v�*yl~���*�D*�L1��mh*i������0��;�*r�����*e`��*t,�<�t�*aP,eL.h`.il/l�/od0p�0rh1sl2u�2v�2w�2y4.���
h�.(+bL+dX+fh+l�+n�+p�+r,,s<,v��J8+aD+o��P�!n�qL��(�r��Jr��t8����wa�+d�+e�Ul�M,Ĵ���h
�+lx�i<����+a�+e�+g��s�Kv(�t�����]<�e�����+p�������+.,e,Lr,tp����wL,a��Y���`���$,e�j�(�Vi7k��r\�h�. �a�,d��e�,f�)g�,i�,k-lD-mx-n`�o�-r�-s .t��*�La�,e�,r�~��,.�}L��%t
fH�J�,d��nt��L@�J�,vx��Ll��L���,v����-e8-i��.d= -e��",-tx�(�xaT-n0���sal-eu<�d-.��*��.d�aD�eP�i�-s@�O�.�e����.�-e�-u�-�$���-i<�J,Vn��f�������-����Me.i.k@�l\�������e�����.(*aXNe����D3�3���3����e@��X.a�.d�.ex�f�.g�.n/s</tЖ(�.l�*�.e�~���.j��|��@*d�.e/g/i�Ns�.���0��@8����.����p	���j(/t�
��
�� /i0�H9<J4/elm�.���H/e�T/dhG`/e�/i<�|/n�����.�/f�/m�/n0p0rH0u��L�J�/r����4.�/rk�L@��/n��Yan����/px����0a��d00e80i@0m�����
���L
P0r4�X0a�0�(��
�G��t0s�G��|0� �,���0a�0e����0k�0m�0p�0t��0a1e41oL1y\1�`�'h�*��L��X�	$��1p1s$1v�i0���P��,1mD1p �T!��!q
�!��T1�#t�1a�1k�1l�1o2t�c�\#���1f�Pr &��?n0&���1a�1j|&^	<'8ga�'��'M�1k�g)��1l�(�1l@(K�1i2o,2rL2� )���Qp���T)$2aX��
���82y����@2�,��	8���X2ld3��`2g�2m�2n�2t�4��4�g�6�2e�q'478�a�2e�2u�7i�8�8�2n�8i�8���'.�{e�2t�:��:��;!t;��3l(3r��v83y@<�	�=�L�=(03r>K@�l?��L3l�3v�?��T3a�3b�3d4e8�g4i(4k\4l�4m�4nL5r6s�6t(7v�@��@D�@���3aSe�3�\BhdB���3�(P�C�3m�B]�3elE�4E���3l�+rHK�J��4n4L�<4aD4eL4t�L�L�L��NL�PRPP��T4ap4e�4l�P�LS�LSx4s�R�4e�V����m�4s�X��Y��d�.�4d�4e5g(5i<5nD5sTZG
[RDE�L�';�4v�[�4s�[t5d�4e 5sx[�L�[�9�[945oD\S�\�Lt]YHlan�5a�5e�5i�5o�5p�5s�5u6�$b��a��t5t8a|5n�5sTb(xc�	�b�5i�$kPdK�5n�d(�eM�f��f�Dh�Lh���5k�5s�U��h��h��6�i��
��.��aH6bP6f`6il6mt6o�6s�6t�6u�l�L�~,+k(�jX6nTlpl��m�m|6t�n��m���6rPo�<p��Wg�6j�6k�6r�6t�s�8�L�t�Lpv�n4vt�6e7o�v��v���6r7v�pz��.$xM7a@�P7a�7e8i�8o�8uzxd7kl7n�7r�z�|���}e�7g�l}�L~JH���e�����7d�7i�7r�7t�Că���7t\�C�7t̉�L����7.�7s���Lp��L����a88l�����7kD8l|8n�8s�,�LH,��$8s<�8,8ax���de.\8ad8el8il� ���Đ�,�-���t8g�����a�eH��h��Lt����8g��8n��L ����8a<��@����8d89gL9hh9k�9m�9n�9o�9p�9rd:s�:trv�G�X�9lD9s�9e���,9g<��̰?��rh�]T9s�t\9k<�l�dL���t9eh���ue�Ⱥ��9e���L�9r�9t������9e$�(,�(Ļ�9k:n����9a:e@:i�M����:n������g�p4:sT�iD���,:tĽT.T:e\:s�$����t:k|:t`����0�i�:a�:e$�����x��:lH�n��]�:a�:e�:lt�i���`��:ld�M<���:l0;r0�������;e@;h��;nH;rP;t����;eX;s���'4��	������D������`;l(���h;f�;k�;l<m<n <p@<r$=s0=t`=v�=yP�q
�;e�;tH�����;tTK� K�;a����;ed�M,��;th�M����]e����<n���4<s��i8��%,<e��Jh<a|<i�<k�<n�<s=t$����`<s�2$�t<n|�� �.�<a�<e�#�l�����]�<e��
�����<.���<a�<e=i���<t0I����<r����=nH��,�h�a���D=aP=eX=t(���'.X�2���t�dp=d�=e�S��h���x=e�����=l��(�=a�=e�=r�=t�����M���=m���x�i�����=e���T.��%�=nt����=e(>kp>l�>n�>p�>r,?s4?t ��H>eT>i\>r����.x�<>t��i��q���T�.����d>a�>e�>s�$�g�>l�r�,����t�a8ee�����>d�e����>e�nt��.����J.�p?t�����>e?i?n$?s�
	X����%M�,M��,�L?a`?et	D?k����X?n ���	��g@k@lP@nl@r�@s�@t�@u�@v��l?a�@b�Bc�Bd4Ee�FfHgxJh�Ji�Kj4LkPPl�Vm�Yn�^oP_plaris<pt�wu$xv*wHyxdyy�y��@s���0�����.4@a��l<@oT�u�
�����.8���D@e�ld@s��������.�@d�@r�@sH�;���0��\�.8���p�h���@��?X��@hD��)\PT.$Al8AnLAr����@aXAb�Ae�Ah�Ai�AjBl(Br@BsPBudB�p�:\��At@����0At��X��DAkD
holArtAu���q����|Asx
�AlH��Z�At\P�As,�	�Mh�3Mta�A.t�Ae���Ad����An`X�AaBe�tBsd'�24Bi�(���n$*��++�HBe3S ��\B��|i8��pBn�Br�7��xBe 4���Bh�5i�Bk�BuL}ih9�%=h>]
Ca0Cd�Ce$DiTDjxDl�Do�Dr�Ds�Dt�DvEy E�>h�.Cs�C����.�F'�FCg|F$CaHCrXCslC��G��u;�G�PCe�HJ�H��dC�0I;M�Cr�H
xCat�b�Ci��k�Cl�p�Crp8s�CtDv,�8�edK��r(KJ�Ce�O2PM���Cu�S]h8o�XnĎtd���Y-DoD\X��n�[Da4�e<DoDDr�`�4a
 d
lc��LDif�X�e�e`Dg4e6lDe�h@M\h�Db�De�Dt�DvPi�)�m�0<EM�m���Dr�mJ�De�m8�uKt��De�|������	�`r��Y><���Es����E�܅����
,EadEilEl�Em�En�Eo�Er`Fs�Ft�Fu�iȫ���sa����tEnd���EdT3e�Est�JM�t�ErJ�����Er��t�Ea�rx�FaFeHFi,�lPFm��-L�����Et4�"Fs���.0Fn8Fr@Fs`����(��(��i��������XFexFi�Fk�Fn��] ��|����Fj$�Z���Fr�PM|��:�M�Fl�	x.Gb�e$���FeGf<Gi`GlhGo8cr�Gt�G� �;��(Ge�"	���Gm�Gr�\�4GlTGn�����LGe��%a�Gr� 	���xGs�(�Gnt(]�Ga�Gs)(D+�T+8�Ga�*��Gl�p�.�����G�С��/�Gg��r<Hv@0���GaLHbXHe�Hg�Hi�Hl<InXIopIr�Is@JulJè6L747DHy�8ܢmxHn�Hr�Hs�Ht�Hv�>��@ndH�tC,��
�E���H.�Ha�E�HGXqs�M��J�Hs`Q��HaIet�y8Q��HdIr�Q(	�Q�l�.,R�T. Ir,Is S�h�.dS�W
�VM4IaLIetWP��.�Y!T.�^��Ii]KdIe�I�l_��xrx_���Ie�a��a���I�,e�db#�Ii�Ik�Io�Ip�Iv�ex�g
x�.&,hX�Irhl|,tlX�Ia���Js��Jn���Jal?��(Jy�n4Ja�rUMtr��LJn8r��TJyG��`J��sM�Je�{��y���JlT�����	�Ja�Jc�Jd4Ke<Kl|umHKn�Ks�KtH�pЕ��Je(KiЖiX��JlKnKr�d�4.Ks��d�i�_i@� KnܙT��,9lи��@9e�ug�Kn�O�+�KgPM\Ki�W
hKm���tKs̻[M@�{%�����Km�Kt��������Ka��2D��Kn�Kr�����Ke�Kt��i�����MLave(L���(����Ls@����� L� ��T.�La�LeMh(Mip�j0Mk�Ml�Mn�Mo�MrNs�Nt�OuPv$Py8P����Lr�Lt��Le�Lt���`M�	���.�d�Ll�Lm�Lr`<H��x"�Ln����La��eMi�fMd�q Mo�
l#���apMe|Ml�Mn�Ms�&n�zl�&��LMaX&��XMt�$dMs�'�
L(]4d(r�-��,�Mm ,�Me�Mu�.�(�t�1��0m�MoX2�Mg�3��<JT<��MaNu�@�	�@��Ns4BJ��a@NehNipNk�No�Nt�Nu�N�C�kTNl\NtpC��D��P/e�D]lE|Ni�E�<G]�H�JiJ�Nn�JJ�J���N� N[
�M��Nk(Ol�L�Ns K�Ne<OiHOjdOopOr�Os�Ou���+e���Os����O�,�O���xN4OdOtP��O��POi4O��XOr@P�8@Q��P�xOl�Op�Ot�Ov`Q�)lM�QKxQiF��Q�OaL�e�Q���Or�Q���Oe�R�OePldSZTi�S���OeWX$)i�Z��Y��Ps�[a���HP�0P��\�]��.�Pa�Pd�Pe�Qf Ri0�j�Rk�Rl Tm,To\Ts�Tt Uu�Uv�Uy�QÈ]�.�Pd�Pf�Pk8�r�^�_�,`*�_���Ps�X(�h�Pt�gt�Pe�klf��k$QlDQmPQntQr�Qs�u�rJ0Qe�r���g\s5ds��<Qa�t��ؾ.dQilQspu��u�
�v���.�Qe�Qi�Ql��oT�P3Dw'�Q.�w2����xJ�Qt�z2�W.�Qe{�W.�~���V�@V���`V���QlRoRr��#�<��4ReLRkpRn����n\��4.�]@Re\Rh PЇ�Pl.���dRa`>e�Rj��b8P�i��Ra�Rv��C�����g�m�Rr���RaSeTSi�So�Ss�Su�7yT��c�
�b���Rm��( Sg��Rg,Sn@SrLSs�oE�ad�����gDw�(���8Se�3!0�.,g�Sn�Sv�%��tlSsh���tSg��@ؗ� ����Ss�St�Sv�� �pM(����Se8���Sl�Sm\�t�Sv�x��������J���T�4�P$�TuL�l+i@TpPTvP�p,�
��HTa����*k�TnxTp�Ts�Tth�X��o���m	�]�TrЮuMȯ��Te�Ti�ToUrH�,eg�kT|s���l��mܲ��Tr��8���:�TeUiij2�������Ua@UeHUkPUpXUs`Utd�*ض�\���������X�.tUa|Uet�zM������n.�Un�Ur����Ua�Ue�Ut�Uu��0�(h����.,ygL�i�Us��S�������Vk���о�Usܾ]Ve��tVk�t���(Vah�e�0Vr(�2����LVe���TVsxVy��M����pVe��t(�����Vl��"gWr Wu��v�����VaDWbPWcXWd`We�Wf�Wh�WiXjXk$Xl,Xm\XnpXo�Xp�Xr�Xs0YtXYulYv�Y�������We��p8Wk4��n��x,We,��t�eX�%l���"pWn�Wr����0�d��h�Wt��;d��Wi��p�����Wa����Wo@��h�"�Wd��e�Wn<�{
<����Wd��b�����Wa܉t������Xa��T��b%4��<XeDXiT�2`�hDlTXs��(��hXa���W�Xg�Xk�"H�i���Xa�Xl�XuD��T��Xn�W����Xe\��@��Xt��]&H�6YeYk�n,�o(Yt����.0����l���Yu,���tDYa8��2<Yl�pl�PYt���D�dYed��`�s<��xYr�Yy�����Y��Y��
,X"L���.ZaTZd[e�[g�[id\k�\nP]ot]s,^t�^u�^y�^�Й:4���Ya��Yd(Zl4Zp<ZrDZsLZt����:g�	��	� ��*�t4�.pZaxZe�Zi�Zs��Xd.��k�Zl�Zn�Zr�Zt|��xX��MV��Zk ]�Zs�ZvXV�M�X,_���Zm��Ziw�\��d�6e([i4[lH[nX[sd[v�Jh�.lJ�0e�?
<*@[g��o�=	d�.�#-p[n�#�M'�M�%tx[d�[e�[j�[l�[o�[r�[s�'����e\'�[l��mh(t(;�(�|)��[u$a��*#ȅi�,�T.\d$\f,\m4\nD\oX\s�-J�.
X/(�/*�0�Md0�<\n�1�,1��P\tD4�x\l�\o�\r<6��6
`7*
�:`��\kx��\a�\e�\i]o$]s<]tH]y�<��@?f$�s�\v�;�\r|]s��"	P3(d=�\v>]�@�4@�]e�>t]t�U����lPA-0]a�A�<BT.d]sl]t�F��FA�H"�]j�]k�]p�]t^vԊ�PM�]i�M����r,Q�����R�]k�]l�]n�Q���]e^ot��|��M�R���]kS��S��U(�U��^l�um@^s<U^aH^r�^uXV"0[�`^el^iP["	X[��X^p�[(�\�Xrg�\��t^n$]�T�t$`2hb*�^yD$���^��^�`��1<d��^d�^g���d*�e���a_b_m�p _r�v�g�_s��`����d<e4_o���P����<_v̵��
D_a�_e�_f�_i`l`n8`oD`p�`r�`s�`taua�X��M���_a�_r��t����_a�_e\��`���_n��U��2.�_n�:
l����_s��|�i��4�v���`e������$`e��,`p�m\`ep`l�`r�`s��d �(���h`a�����|`el��@���`al�*�
�`e�`k�`p�����`np���`eЎ������X�`r<�U�����,avt���a����t� $ai���ad�f�ak�al�am�an0brTbs��tlbu�bv8���8aa�bb�bc�bd�bedfDdgPdieklel�em�en�eo�fp��r�fs�gthu�hv�hy�h�4�J$�"0�:,����ayh�(����Pebs$btt��
X[��be��br����P�.DblLbv��
h��
�����!.dbpp�&�
xbk<��3(�eX�"H��T���bdh���bo�x���bi�bo4�2��;��
ca,ce@cgxci�cl�cn�co�csdtdv��(�]cl,��X�2ce��� cr��
4��8cgPci��Mdcs�N���\ctt	���pcn�x�ce�ci�cl�s�ct�0���M���M�?
����cg�����cm�s(
�|
���cu�
����a�%e`;h�$de�|�b4dd
�2�<de�T.�da�de�dk�dm�dn�ds���tde���|dn�PX�.X4e4]�dn�drX�.�n�*�����e4�n��sd�t8������dp4$��s�#�de$ej@elPenXes�%�0eaD!�H,-&8ea4�e�&��'�)��tg�)`ea|ee�)�g�es(*����eT+�`+�eg+�eat0�M@0�ea/]�ee�es�eà2��1���Y�04��T.fc$fe4fm@fp`frhfsPAt85h�5��8O88��,fs�9JPfiXfp8:
\:t:�:|ftT;(`;��tfa�=`vlX=�fe�fu�>bX>2�ftB(�k�fl�A��fage��igk(gp<gtdgv�g�PB(�C�LC�fl�E��D��gr0HW�G� ge�H�|H�4geXgiX�jTI�lIPgl�J�pga�J2LK"\K��xg�`��=XL�go|K�ge�gi�go�gr�gs$Ni`O�hP�O:�gi�QT�P��gt�SP<�e�S���grS��he0hgDhk\hllhm�hn�hv�S�M<hl�HmT�The �n�L�XTtpX�M�T�dho(�,�T��xhg�hkTU�MxW�PX��Dl�W�he��sp[
Z���hs<\X�hd��y@=���h��\�4b(`inxir�iu�B���ha�ic�ie�jg�jh�ji,kjdkkllTlm`lnplo�lpms�mtPouhov�o�|h�g��Xiepig�h��i��|�.�ie�ik���j�in\j�
�k
�BMtn"�ia<jd�uiX�kPjlt�mpjn�jr�js�jt�jv���.�n��il�o�M�oje�o��jl�o
jn�o$ja`o��0jv�s�s]Hje�;dvt\jiv��djd y�
x��|jkGm�zz���ji�{��t�j�}*�}-�ja��M���ja�jo���l�������jdkn�����Dd�yn���M`���kg� ka<kiԊ(�����Dkf,xn�kr�ktT���Lka�ke�ki�kl�ko�krluly(��L�a���|��Ў"�kn`��
@���ks���MH�e�ko�.�d�����kuĕ��V�(����kt��	��8(e4li�ư�x�,ln0��D���@l���MHlà������hlk�ll�lr�lv���ldL��Ml��li����M���le�����lrܪJ�le@���liح�	����lnȳ�����lr�dt��maDmjXmk�ml�mm�mt�mv���0�ض8mà�����Pmjlmo�mu4�����rX��(���xmr���$�`�*�miHl�`��ظ��mi�mrx�j�������nntnv����ma neXniL{jtno�nr0ou�Q����*H�"T�d|�i4�nLnt���M�t8ne��@nt��idng|��&,��@����lnf�nnX�r�nv��N�nn�mt�h�D:�����n.�nn�nr���na�ne�Ii�noo�(��`�N��Y��n��*"�|�v8odh���(o�o����"X���JtH������<og����Don`or(�Jt���oa�oi������xoe�ot�����ol�onĻN�|��]�a�����ok��
�oll~���o�p�H�����oex�N4���om�yh�N����pe�pg�pl�pn qr0qsTqu<���pa\qb�c�qd�qe�rf�rgsi�sj�slDtmXtnpto�tp�tr�us4vt�vuwv4wyLwzlw��J�pg��b8���
8����p.�pbȾd�pl|�A$�,+�����t�pe<����pdqoqv(�
l��)$��
����qm4Lt`�8\�.DqaLqe��/��/�� 
P�tqa�qe�qi�qyl�PT.(���qd\���.D�!N�qa�D�qa>�qtxD���.\�
�. �a�)erirk<rm`rn`�o�rr�rs�rt�ru�rvH�(@�J(rs�*4rtD�&Nx�J�zaPrmXrt$��t��8��]tra|rkX�u��)$���d��������(*a�rt��
���8�-N �-�ra�y�ra4��t���rsD�2Ns��r7N���re���s��i�mfHsk`sl�sv�?N�]4su0]<skXsu�H��Ni(��lsn���tse�M�se�su
����sn�
tP�	��sihG�se�s�x�Ƚs���t��s�p�
���sa���tn�i�te�� tl���,t��8t�����Ptihty$`��J�tm�ts�tv,�����ts8���@�EN4��tr�tàG(�G���t��8uaue<uiPuohuu�u�l��4����ti����td$2(un@��� ug4�L��4uv��P��Hul`us x@ �tul� ��!(�ud�!��|u�"�
#��u.�ue�ujvkvt�($�#�ui�ut���|"�ua%���uj�%�0&2@(";,�KN4���v.Tve�*vk�*t(vapve��i�vo�vr�vs|���
�-��\vkP,dvr�vv8� �-�va���00��va0���ve�/���vr�0K�va�vy�0(L1�h1�d3�e�vnwt�4Xrg�6p47	$wiD8�8��wl�s�8�.Dwd�88�:� t;|wr�ws�wv�wy�����w�Tw�@<�$=SN�wtD��`=XN�=UM�=(�wa(>�>���wk�wl�wn�wrp>��>��>��?Mxmxt�V`�V���waxe`W�<p
@�MXxe�xi�xrysyu<y��iH��Dxi����Lxdxxi�xr�xs�xt�xvă���n��Ј�xs\����xi���x.d�"��������x�����x�̌?��iT�k��it���xax���ya�iT�_N`���ye���$yr$���0y�8�_Tya,��Z@���\yatynh�
<��yr`���y�|y��y�`��}gl��ye�����yl(���yvt��t��
�����ya�yet����yr��ܕ����zm`zn ���zaؐbpzd�zf�zi�zkx{l�|m�|n�}p~rTs(�t��u��v��z�,�����hzj�zrx�8���zr�����0����zr�zs��X�..�zn���
�za{e{h{id@o${r4{sP{t�u0�v��[��"��r�9 �W.8�8�[B����,{i�ejH{t��I�Z\{r$�E�*���d{n0���
l{a�{e�{g�{i|l |m,|o4|uH|vd|��"�Sd�{k�{l���-�����{sĴ́|<�]�{r��l�e�2���{a8��|s�|e���@|n��(�����Խ�P|y���t|�X|���I�����||u@����|p8���
aa�|d�|e}g}i$}nx}o�}s�}t|�y�fNx�t�|s��W�|s�|t$��t�(����r}s ��$�.��`@}aL}il}s��S��8}n0��\v�@���eX}i��t`}t(�P6m�"�}e�}k<�mt��|��}m(��
�}i ��h�2���}i�}r���x�J�}o��i��T.0~k8~l@~m�	p�����}aH~ex~k�~m�~stP�p�(��(���.`~gh~m|Cs4(�������p~a�i$��~a�~ex�d�el��0���~a�~k�~t�~v���lI�H���~ih���������~a�����~l��a(e<iM��2 m�N@L�4s���T.8���Hate�i�k�s�t�0^t��x��d�e��f��k��l�s8�
<�
��]����u���a�e��0�($��-l����������������a��&h�.�����aT�ex�l��n	o��r��s�lN(�L�ll�rp�n#h��d�f�G�e���,����a$�I��'@�(X�"x�mp	a�e��o�p�Ԁgx
܀rh>�eH�i\�ot�r��u����Y��H�v4`��_t(�s�_��0�g�[<�n�iqN\hT�il�n�k��m��~�.�~��|�n��rt~��e�Etȃ�������������.$�a�b�	cH�d��e`
f܂gP�hăi�Kj�k�l�m(�n�ohp\�r�s��t��ǔv���܅�4�g<�r̆t,�.̌�h�o��s��t��È�vN��`�bx�m�j�l�9:��k\����}Et��̎��������<�����|Cr<���"ȂvԖ��
Ђa��e�g��j �lX�n`�ot�r��u���P��*�s�I���0�eL���{N��$T��8�d\���@�����̙��^x��t�Ph�eX^u4`�9<`����t�n|�t��5���������N؃n�����e�l�t4������(�l$�YL��8�.d���
��a@�e<Li��j\�ltorr��s��t��T.�dT�e
�N`�p�ax�e��i������Ĩ���t\����rxI��Z��aȄoЄr�K������n|�5�����t���؄�ȫ���.(�aX�d��e��i��o̅s�uPv�y��Ȭ���" �s@�tT�N���8�.P�p4�N4�t�.l�ex�s�h�l|��6���Md��i��t0�xh���"�Nsd=tl=v��ąc�i�#<�=��e�m0|pht��9	��D����g�%n�����=�d���J �a�id��	T�a`�e��i��nȆo�s�t$�u8�y@�".��Qmt�t�Qvd"���e�""�/(����n��OD����i��l���'�=��e���$�h��Іi�r��t؆t0S���Z����a�r�s�iyX\(��0�s�^(X�u.L�aT�mt`L�(x����b.��a�d�eĈfЈi�k$�m@�nX�o��s̉t؉u�y��4�p4�"��k��lću\�2@��N��?��̇s ��ԇe�i�v`�d"n�r4a
���N��.adUgP�i�Ak0�ld�nUpt�r|�s��t,�Nd���H�gt�g`���\�g\n���������.��xLUt��u��N�-�2e����r������p���À�X�.�a�e�k�l�����l���4�H�����N���n�S�/|�2,�lh��4�aP�s�2g��JX�.h�m��*x��h�*p�.X�x�a��i�����k��pE�إ�N�-��o��dh�l����a��"�m,��	�I.�)l��<�s8���lC�����^.	���d�.`�at�e��h�Ci��j��k��l��m��o��p�tD�vp�y|��4��
l�n����"�ytX��\��|���Hi,�q)��i,��������N��Ԋe|��-X�̊r��@8d�n�����e(�o4�rHXuX�0�/d�x�dt��\��� �v��������X<�a�N�ZtP�s`�]X�dH��d�n�������lE�`���Er�����.̋a�e�i(j�o0Frp�s��t�v��y����kd��DL�*؋a��"�|gk�s�vp��N ��N��Vt��T.4�eD�kh�r���N��N�t<�e\�s��N0��T�.��(`��N��i��tx�k0�t��s���{e�-��m��9:�d�k�n�r��M��a�i�s�t����d�8s�������m�p@����`������������$`�a���8���L�l�T�l��H/l�. /-t�e�.���r������@0mča�e(�i0�j<�o���/T.؍l��v�1���)e�i��t�6"�.�6���e�8P��N|P��s�O��r�O$�e�YK�st��$��.���P�eT���\�r���h��T��Ўn������a�b�c�dH�et�g��kx�l��m�nH�ph�r��sx�tp�v��t�|���؎lt7�|����sH����eЕ�$�e8�o@�rh�sX�i0�oD�q������ܙ]d.l`�n���l�X�d V?x�]l�m��u�����8r�G�����$��$�t�]
��a�b�e��g�k�n �o(�sD�t`��P�.,�k��d��N0�i��]�e�0iT������n.0�a<�l F�d��P�ixN����$���X��(��x.T���
l�a��eĐiАj�l8�m�oL�s`�t��ð�}3���sį�l���n(��#������ؐnܰ�a�e0�u|�(H/e<��d �i(�p`�(��O���`����P�i���@�tܲ�t�X�o��r��
����p�t��:x�epi��J������\�(��p��u��C���9��4��tȑl�uи��Бd �e,�gl�nؒo�s�tp���i�����t��W�s��:�nP�o�r\�u�(Y	�.|�D�m�
O�2���d�a��e��h��l��sL0t1(���d��\)�=p�[8��ODG2��r0GM��e`�"̒v��m:��jL��<��a�S���s���e0�r|��P�:(�e��.����<�a\�e��s@�.���x�o��ut��D�h�� Bbēk̓n������a�e�k��m$�o8�pT�s`�t$�28����>d4���ؓe�n��*��g�vn
l��rԔt����e��0�m��28�_D�aX���Z��L�p����ip��&k����	l�a��eĔj̔oԔr�s �t8�uX����'��'��eD���s�����|���a�e��
��i`�����r���kd�O(��s��t�e�4��g�,�n�=-!���D�y���L��,�WT.T���d�a��e��i��h���s������dca��e��i�o�u\��Еt\�;p���ȕa�e�r��t�u�����	��,����`������t GD�l(eu�,�P�r ,8�e�-(�]�|�a��e�i�s �uX^?�]��t�b�g�k��d��g��i��lЖs�n�hp��X�e�rtXy"T.�x��Ėe�n�t��v$�I�z��D~r�{���<��v��O����l s��L�aX�o���<�����e��@�l�$�dp�r���`���h�d,�Ъl����|��L��a�d�eИo�sLYt��4*���dؗk�l<Zr<JRe�ii�t�yp�\�b,�d@�l��md�rt�s��v�J8�e 
l��t�eT�i�Wo�I���\�e�<o�M��t�!����a��e�!��D�@F���sLF����a,F�	��k�D����s<BĘr Q�,QXܘa�Ht�p�t�QPb�rD$����,���c�hb�$�r�e]0�lh�r�e��8�a��b��f��gșiЙk�l4�mL�n��p��r��s,�tD�v�gPnJ��f$o-0o��n<o]��aXp](4u�t�<v�5o�sDy��klE��z��i(�t�~ O�t����2u$�T|s4��e�J ��`�lh�rĈ]<�ax�d��s��x���iԉtp�eL����hD� ���0��a`�����dȚeh�gܚo�s\�'Ԛn�����<��t���k8��`"j�s����eP�,egGlp�]�e<�r�)��MT�e`�i@����.��".̵�8����a��e<�i�o4�u<�yP��$��������k��r�����̛g��n0�t4���=i���
�t؛e�i�j�t����g<(9"h(�0m]/�,|t�s�
��$�t���.`�dh�e��g��m��n�v��45xHp�sT]x�a��.�����a��e.D�
�����gМs�%O,"�."��؜e04�b�f�k�p(�t�4(�9(�;.�;]�eSZ
<\���sv@=��`��D��`�l�d�`?�ha4b��.��l�f	�e����itn.��g̝l�n�rlp(ĝg�pzs�	؝i�s[
vx�.�g��s�v?
w(?xJ�.�n��T�8�eH�u�xvЎZ��
(���@�m��K@�ap�e��i��,�h�t���x�|�kp��ЪlD��������M��à��̞a|yeԞyܞ�ġ��2�������(�8�+O�����d�m(�n0�rܦ� �s��@��l�H�]@�X8�e����l�e����L�d��9X�eظ���p�t������������	����l�����e4����k؟l�yl~�������D�0O����e���0��;�r`���|�.�rL�
���e<�D�a��e��iȠrРvؠy���T.x�kp�l��s��h
d|�n`uv8���\�e��`�Z\�LXp����nh�|����a��e�W.��47��8���{e�?�dX�k`�lt�n��o��tL���B�
�d0�e<�s0CODQ�� �t�C$�n�D��y��D��4LPPJ�[�<�e�Y��h�g�^6O@��aءe �i�4Mt��.�a�L����g����n܂����i����̡g�n��'�������i(����n��]`e0�l�����kL�s<�-̓���8�n����@�a�e@�l�a��e���>���t�s�|�n<����rܢt��������aĢe�������t��i.����Тa��(����d�lH�m`�r��s��v��y��x �i<�
�����(�t���0�eh���<�mX�c�.����T�e|�k��nl�c|�t�e�]p�at��.,�����et�����	��e��pܣnܭ�4�.�s��x̣et��
\� t����a �e8�k|�l��n��r��s�t��,�n���(�d �(H�aT�e��.x�T.p�n��.��*d�e������e��� ]e��u$]�*�����n������e�X�ia����̤s,��Ԥe�f�à���� ��\�l�p��r�@s��tȥz��
�aХb�e��it�k�l�m��n��o��s�u�ybz0�Zp�dx�k��l����,��o����?���8���rH����e������d�����M�oH'�d!�ܥy�X$�a0�b8�gH�rT�s��t�Ԉ���e܅]�t �/Ԗ���ix���@�e��Xd�s��t��������n��l�r����x�e��;O�����t0����ZԦa�c�d�e,�f<�nP�r\9tT���`�r�7H����h��z�=�,Е�l��ܙ]�n �rT���X�.,��,�2и��4�i�i��]H�a`�e����nT< ��l�r�HOL����s�g��e����b��u����tp���B�̧kT�Z�f*
la��ԧs�?��ܧr@�X�o��r����k8�!8�����t ��� �n��,�ax�b��f��g��h��iШk�lبp�r�s\�t�u��y��$��/�@0����a�s�H�������c��dȨeЕ�ܙE �̵�8���B��?Z@�X��� ����b��c��f��h��kԩlܩm�n,�rD�sX�t��v���a`�b��c<�d�e��f�g̰h�iH�j�k,�lL�mh�n�oȺp�r��s0�t �uX�v*wH��|������x�ȩt��9e0��@�
8�.�a�ve�k�s�"l�A����t�l��	����$�k<�r���
8�P�i�i����Mp	a��b��d��oتu�y(�
��dD
��e\
�P
��ed!��+t<�e�pn�+����r+�̪e�/��.�g 4M�e$�o��5��.>����:z>jbl�vh>��
,�at�d��e�i��j�o0�r��s��uԬ�XEp|F���e$G)0G����a�F��l;pDQ��(h�H��nd�o�pЫr�UH�S]ȫoTa"�[ܫs�clc���e�i d\hK �p(�v�k��mp�mKL�aX�el�o|o5n��D�u�od�m�p3	�r(��k47@O�6��x�sw9	t���i��v|Zt~]��n�ȃ��m`?r�s�������@����q
�	$�d@�kl�l��m��n,�rh�st�t��v|�GǑ���l4�r�K�ed�]L�sĨ������T�n|�tȫ��\�e��sh���;<����j�k��m��t���<�O��]d��ܭe�hi��k�l�s���r`���KO$��d8'��e����e������0�lx��� �e@�nP�sh�nX�����H�k`�tD�H	��X��m���ԝe��y��p��$M��aܮj�l����n� J��l��Įl�Юe$W�'.���e�/.��v@0���aL�d��e�g��i��l��mİr�7(�78�l`7�@�al�e��o��r��y��ð7�.k8'l;p�8]�m��8�8J�8������;�	�8��iЯn�r��s�?v�>]�*sTUy(xAܯt�@]�etC;42�G�mL�rHG�aX�ed�jl�lt�r|�s��u��Ð4��3��D�t�G<�s$Ii8I��I��I�4Jt�rG������J�hT�`Q����o VA]O�sM�e�~��y��ذs��M��n�sи��^.����`qt tH�at�e�4i��jh�k �l|�o�r��s��u�v�X�ph�rT`��`�a�]��l�nȱr�t`����.��e��i��sL�0���n�h
$�!�*رe8�o��%l����.�s��PO !��aD�e�!�$�eD!���rD��d�1�Ynt�M,�a�!�8�v $��L�a�#P�nl#]\�a��eԲj�n��s�vX�y�%��T5h�$��n��sXW�X&����jȲt�&VO�&����ad'�L1iL(_ܲi�*;d(��k�X]O�+��iH, ,�aP�e\�ol�uH��.Y�-��<�t�,D�s�.�����.��d�bT�sX2��a��l��mгsܳt@2��~)@5����i���5���o<:�:�ȳa0;��iP;8T<�4BM|W*HW���rWX�a�]tg�]�� �ax�dشe�i�k\�l��sĵt�u�v@�y0��Th(hd�n�gtl�a��e��r̴siF!�hn��k�h��r��sDixj8kI�j�Ĵk�k�g�s4�v�x�`�bO�n�h<���g�� �e���,�s`�cD�l$��7gx8�a�$v��P�ex�i�����'.0�l�s��D��tĮ
Ю��e�����r��H���tȯ��e�!����еe����صs��fOh����n�����e@�
����e��r�~��$������it�t����<�e��f��j̶oܶp�s �t<��d���<Ve
WL���d����e��������a������r������a�T.��e��H���a�e�l�����.���L���2.��a0�e$�.�����V�x�mO���H�d�P�nL��	\�a��dķe$�gH�kh�n��o��s��t�����s؜t�t��elJܷd\��l�r��s4�v��hsO����g��iL �+�'�\'�l��m�%t�e8�r���|)��6yOD4]@�oX�v8��ix�`�a,%e��s��ü>�B�	B�����<B2T.�H6�a�e�i�kp�tx�u��vJ�J��̸eI��Ըr|J�*m��tL��L]�M],�e8�i@�jN��h�.�M" �t(NILN&`�e`N}OhNJL�etN�T�l8�r�Q��S]0T3	T���r�SX��e�U����p<U��aĹeW-�V��g4iܹk�t\W(XN"Y���e�p�ctXp]��a4�i�e���g@�mH�nx�p��r��upq�.�Ĉ�d�np�u�;�n��X�e��t���`��X�d��e\�����tp����&�.8�e�n�r$�t̵��	��e0�il�lt�o��pP<r��s��t��up�����ađe�0�l�����e0�L����a��T.H�n���l���@�gX�n���d�a����@��iL���]|�a��e�2�������"k<���o�2��,�k8�lH�rT�u\�v8���Ļal�d��e��gĽi@�k`�mh�o��rľs��tĿu�v��y,����$���$�a,�����O����@�b��X�p<�
��d�a�������|����(�]��l����
��aԼi�k�l�m �n�?o,�rD�s|�t���l�n��s��k�OP6�����st��J�s��P}e	�T�e��o�
�O|
��<�cT�t����W.d�e���W.t�nd�l�
���a�%e��o���8�=������
���a��e`UfH�k�n$�sD����g4�n�@�.�P��e4]�n8������p4$��eP�s�#0�eX�j%x�%^	+�04��\*e��m��p��s(��88����mL���:]��k�>��g(B��n�A���a�e��k�p0�tx�v�C�LC�t�Dij�r�E:�G��H��H���nD�r|H� �ad�ep�i�����hI��L�e�s�HT�nlIa�J�XL2��k|K��e��r��y�L�O��aP�XR�S�l�t�R���.�T�ԿaXT���l0W�O|V����r�W�]*<\���k<�y@=��D����\_``�d��m��s��t��v�`�Aa��e��i�A��s�`t�r���C���a�O�a���tb����B]�a8�e$�i8�j`�k��m��n��o�pD�s�t��u��v��4b,�k���O�����se��rxd�� �etnP
p�b�fd|�eP�g��kt�m��r�s�tD�u�q��h�u�o�JirJ��t�rh�r��ex���Ka��i���x"��dH�e�z�z���j�$k�l��tLu({A|{��e����{F�{���.��$e���0�k�����D�g|�r��uT���L�a��l���T�(��t�a��e�h�
��h\kl�����sP��������M��i(�(��������M�����rl��@�X8�e �i,�o$������s���$���d	kh!m��
4�a|�e��i��j��k��o��p��t��u��vq�P�t�i��kr�,������l(�(�����u�]�����eH�i��(DHbظ���a�mi����i��$�aX�e��i��o��r��y��2<�rH�t���O����4�.����*H�P�lp�nx�r��sx�*4�8��i��	4]��n����e�|����l�����2������p�����y4�	-����t�X�e�N	`����rl~������l�n<���
$�ax�eP�hd�ip�j��m��o��r0�sL�t��u��y��<����+a\�
��eWf��g��i��k��n`�o��r8�s@�tD�JMeH�����n@�����a�Li����d�aTeo��'t�����e�o�s,���;D�*
�k��8Qe��	-����$���N�JD�n�H�iB	�\�l�t��a��e�
Q����n��r0
�\(@��i�`s���e�2�Pa��e��i��$���.��rL�k���!��0��H$��#�n#�$�evkD�v�)/�*"d�al�e��i��s�*IP,P��r��sT�ud���|�d�-��n�-����e�-*
��t�pm.�.~��n`.��e�1����mh1t��ad3��8�sd:q>x<�������?M8�a@�eH�gP�ll?t4E�HPPx@�Mx�a��e��i ���z(z��p�kxlH��O������d��r��s�xv�,Ј��a(Bs\�����it�����e��yp�"������a�e������s��������r4�t$�����Ԥi���,�e<�i`��d��@��\��(�it����� ���l�b��e�g�k(�n<�v��t�aD�b�cX�d`�e�f�g��h�i�kd�l�ml�o(p��r��s��t��u��v�z�A��sܞ�С|�I����h�K4�a8����i��X��"�8P�u+Ph>M�x�a��b��l��n܅��O �m��u�uȫ����i�����ed�����s��t������
�sN	�s����a��i(�uD�2ܙ�������e �f,�n<�s���,�]�iи����gМt���P�t\�v\by����PXy���O�]�e���'. �e��f��n��r��sPn�Ĉ]�`o`�����e8�
8���BM<�4E��?����e��n�Y@�Mt�����.������a�e���`��l�.i���$�yt����
,�b��d�f��g��k0�l��rp�s��t��vd1� ��l���Hih>����e��i�[
@0� ���e�6i��j�k �l�2 !9:��a��eD!�!���.��r����h�.�$�ll#]�e<%� ,��]�]��(�aH�e|�j��s�kT.`�dp�n4�vm�t�t��h�a\��(�@����e�����l$�W������k�n0�r\�t8�����ax�b��d��e��f��g��h�iX�kl�lx�m��n��o��r��s8�u�vX�����e��s��m@�e����$�bP�t��������1<H�e�W�����d�eh��l�u�6��
��a��dX�f��g��i��k��l�nD�p4�rT�s��tDv(����(4J�O(e�J4�g�����d8�'.$�s����e|��	�	�,�n��68�V�@�m|
��H�at�k��l��p��:dl�r|�-�)�
����a��e��s�
�
"��kL�Oh
�L���n���eL�X����������,�e8�l@�nL�s�Pԣe4] �n������s�Z�'.�#J:�)�g�)`�e+
�@�/���e���@04(�1�����04���m88���s�8��>��C
LC��l�A���e�i�k,�n�p��D�l�Dx��LGM$�oSD�t|V�<\w�hy@=��h��L��`a�B�� pe��j��k�]T���i<�����a��e$�r��t\��T.�*��*t��azi��r@�����a�e�i~��������s��i ���r��u���a��b�d��e��f��g�h�iH�jP�k��lh�m��n|�o��p��r,�s��tP�ut�v��yD����
������e@��M��e��l��r,̾v�����e��ix
��lh�`���u@ ��?2>��g�Fkh>��	�a@�dX�e��j��r��sd�tt�u���|F�L�e�FX�.�H�.��d��f��l�^md�o��rPJ�J��|�e�MPM����a�S]diȫolci�m8��e�o2�o����d��k�p�p(�B]t���c�e �kD�m�u�u�kx��e4�o<�u�x�(�:`yMp�u���}
P�m�|X�oȐrt~]��n��r��ȃ��������B��d��n��p��ř���d�����e� ���x�����e�	�$����e$�f0�jD�lt�n��t�2h�l�]�e������.��8�aT�e$.��O���`�a�H)T�st(]|�e@0�
��a�e�g<�jd�l��n��r��s��t��u�/���k��l��r��sp1�1i�3�5��@n�8��r8IiHG�l0�är-G��(���O�p�� U
D�ehT��L�v`Q��X�o$Y-�Xp�p�VMx�s]e��e�^���x.��s�_����.db9:��t�i��h����e��rk'\m�0m��a�n���x�e(�n<�sH�tܙ?���и�� �gl������4�k��� ��@�.��a��e�4iT�j��k��l�n�o$�rP�s��u��v��y���v�
���e"�.�@�h��l��r$�sH�t`���$o���O������ax"��d�����a�e�u�L�l�%HM8�e@�l��t�)x�O���x�e !�p�a��e�!��$�eD!��d�r��s����!!l#���e��l��r<%��$��l��t�&��'��)�p,*H,����g(n ,��a�0X@�0m��aX2��p�7���xpT<�8�a���<��0�rB0.4B��D�al�e|�i�No��tC.(�s�D��H���iLUx�'.��e�R����rpU��W
WX��e�Y
�]�hH7m�]��
��a$�d,�e��g0�j��k�l(�oP�rx�s��u��v@�y�_-�g��k�edP�l��rd�t�Svs�r��H�u|�{��\�at�eX|%Ȁ؀]|�a����r��s�]��eDE� �t��v��?��s���e`���tĮe���s�t �v����e�|�OT����iЕ?L�4�pP�+������<��$�ED���
��\�l��r��s���d�e��t��vxx�OH�����eT�,��������PUk(�t����Ur�����a��e<�iH�oP�r���h�����i�k�l(�r���-��
̻�� �kļ�T�4�n��d����T.��t����	X�e��f��i��m�o$�p8�sh�u���d��p��-����ih�i�VnT�P�d��r��t4����e��H������o���h�rH�����p��"l��l���e@��H�t0�pL�t\�v,����|��T�il�4]�p�d��l��sL��x�a��eL=h��n��o��sT�tt�u�\��m��s���d�.Lex��a��i�s��d=K�tD2��Ii�>���l�>t�a<�eL�il�k��l��t?��?@�?D�dtNN	LNXX�e�?��`�j��r��uԔ��x���e�NidO�4@DD<B��m�H��a��e�kH�tI��T�.��klBmLI�|J�'.�r�K*LNN0�e�M���j����
.tN�$�r0S�,�a�Q��<�r<U@d�al�o�U]�Z]$]�O�eM���O������a����d��i��n��rH�s�t̵����e�i`l$�pP<r8�s���p���Su���������o�u��HL���\�e��i����0�l�����%�aP�l|�tX�����ah�e��-��"`�g����Xt�e���.��f�ak�m�n$�s<�u8�����aL�dX�e��f�g$�iL�j|�k��l�m�nh�o��r�sH�t��uxAv����"h��������s�t���
�����a4�i�t��T�P��D�e����a��dX�f��g��i��k �l�cn4�oD�pP�rh�s��t��uDv\��������d��ul�tY�����n�J��l��s��t$O�*��XyZT�����e���s,�t����eh�1�{!�wy(x	<�t	�D�e`�uD
H|
���.��e4~i��k��o<t��v�
;t"�O��C��i0�����v�����id��r��P
-�
t��a0+�ih��e O��s��e�0�m�h#(L#�8�t�"M@�e�$5�$��X�i4$`�r��s�#l�e��lPen��v���%(p,JH,����g&��a�((�(X��ex)(H)������)`eaL.�t+��u@0P8�rD�s/]�eL�u\��1�1��0�eD1}3�3|�3��1��T��04M|�k��s��v�6��:���t`;8�<x�?(0?Z��k��p�>��e��i���?��@a��s���A����t�A�����0B( �aB�kT�m�A��a5ch�et�i|�k��l��o(gp��t�u �v(��lB�d�e�CLC`�sD]�D����igr�F��v�F��e|G(XG��m|H��a��e�i�o����H����r\���H��i$�n|��lI��g��*xJ\�J�\K����4L��K��4�r|K<�a`�et�i��oXL�kN�$Nl�a��d��e�f(��*��edNipm.lNm��n`O2�T�S����m��t|V�^!<\����s8�v@=�������|aq
`���p4b��mh�r�B��
�a��ct�e��j��k��l�m$�pD�t���i�Hr�i��\�atn�.t�m��n��r���v����o�x O��ex����iy������o���T�rT�����a��8�a�u@�(������gP�]��M�e@��ȼ�`�t����,�s���8�ap�e��o��r�����OܽH�h�g��r��tD��D����e4�����s������t��Xlnf��v��8��e����������4�
l~�����<�((�aX�e �r,�s��tH�u�y4�2���� �k<�rD�s���`�xP�a���\��. �a��f��g�)i��k��l��n��r��s�t��?��D�����e��@�����s��J��a����.��*P�i���ؾ.��u<���'�n��O���(*aL
��i#��a@�pT���'��*�h�rX*��H���-���oP,��k`�r��s��t�*t��al�e�r�s4�u@�y(�Y�-���t<,��*��v���,J��s .���a��eXN��0i�0��a�ih1�(�ä*�rX*����l2���r�2�d3�?�8�gziz��\�a��n@���	d�a��eL�id�lp�n��o��r��s��Ä}��|����s�iă����n������i��k��n�r$�s@�t�(Ȇp(����od����n\����e�0�t�x�����8�e����)k ���dtX��X�u����e���,�������H�)t����e������b��ya���$������ԩ������l0�n@���
��a`�d��e�g�k@�ld�nt�o|�p��r��s��t�v��@�.H�s�I�P���.kp�r<���P�e|�iЫ]��e�& ��K���b��d��e��l��n��p��r��m$�����e��~\�Wl�����e��x�,����%f�������lt�]h�.�t�e0�s8�t��8 KO,�dL�eش�SdZg\�ih���\�a�i��ȺY��e��&�.`vl�����a��e��i���,���.������r"ZĽ��v�*,���������a$�i,�l4�mD�pd�u�i���r$�I��:�� �(�X<�ih��*����P�d��X�n��(x�p�i`�l��r��s0���x�e��i�0r��y������������e8�+�*��ad���d��t;�������ă(�����iX�M�e0���3 ���(��(��`��T��<��t�� �M��k��n�rD�u��\�aP�b��d��e�f0�g�h�iH�j �k��l�m��nl�o��p��r�s,t�u�vl��ha�����t8�|�k`��.l�]��a,�e��]$�a������np��L���0�e@���8�r�8d�ax�e��o\P��np�x
p�r�&�&���ad!���th>?��a��e��i �r8�s�A�>��n��rC��H1d�.k�^md�o;p�rp8s�S]��edih8on��m�a0�e�o�t#T�k\�l��n��p��tx]�x8��ap�o���y�X������x���y��y�	�y���e�u0z��e�z��%��l�Em��n��r��"ȫ����ed���1sx�2�e������.$M(�e4�Ä	�@0JT�ap�e��n��r��s��u����/T.d�l�1���d�8T.��i��s�;+W�VM��a��etW�]T�i�eZdb#��k��l��p��tg,he�hx�np�lG��������\�nd�rp�v ��
�ax�e��ip�j��k$�lX�o��r�s�t�u�vl��<**t$b�
+�5h��n��r��s��vp*��aT5h�������eH���lx5,��-��i�(�dl#�a�l�r�#i�'��)H,�� (u ,�aL�i@(�8������8�t0.@�sX2p�p|�s8n�7��h�p:68�<tT<���a���tA��A����k�A���������A*4B��l F� K��R��l�Sjl{��<\.hW����aHW���lWX�a���H���(�.��0�r�\�.<�a�\��H�r�[��T�y���|��`���\��]�m�r��s�u�v�]��
��a$�d�e�g�i|�j��l�o,�s��t��u@�y����4�`���p�b���bd�f�?\f��k	T�fL�i��k8�mX�nt�r��s��t�Sv8qd��ihp��@�n�t��D�h�}i�w�xs�v��h�i�x=	��uh�+
������d�{��n�{��x�e�����������]����h<��o����g���a��2L����rD�(����n��� �aL�ed�il�pt�t��y��@�k\�rH�(ܩ�	h�����P�i����n�i��dȯ��i�u��M��l��q
�e�����y�~�������
P�P�����k�tL��I.`����e��0�eH�l\�o�Ø�.���T.���<�i���T�m�	*�h�r��sL��p�a��d�eT�i\�od�y ��t�ar��s�����a�e�oT��y_\T.�d�i �l4�s@�t��s�P�{!lJ,�y�OP����.d"*L�r�"%P�,2<B{$`�e������n�r̵��
t�e�i�=j��l�n �p(�r<�sL�t`��`�	p�����a�i�s �;T������o��38�����a��T.����e�A@��4�al����
h�t<�D�a��(t���X��$�2����l�k��n�r8���t�a��d�eD�fX�id�kx�l��n��o��p�s��u�y�ì�h�����aH�k��r���t���s����P$. �i(�l0�s���|
�<t$Kh<�i"����P�v�#~�)T.�)l�e/��s 9204����n��p�92���X=��o�Aq	�a�e$�k<�lP�od�t��u��v��yB��f�C2LC��l�mtu*�E2�D���o4�uF-�F�a�*XGH�l�H2|H�\�a��e��i@�o��u�H`lI2 J�xJM�J�>KiS��X�k��n�T2Z@=�������`j�BX(�a4�e��h��i�k8�l@�nX�o`�p|�s��t�u�v���4b�iutnT.\�el�i@�k�mx�r��s��t�oq�q��d�dx��P�iz��Hm$Do|+P�{����a����.�o������.T����a�e�i�j,�o��rX�u1P|����eЎ"��n@���_�7�d���$�p��K��M��eP�i�2��i@��l�l8��'�i��t�a��e��i��kP�i��2������e\�QCH���i���n�t��2$�7Pt�X�l�l~���@��`���T.tk�l��n�r<���a�e(f4i@nHo\plr�s�tDu|v�y�����4���ls8���a�ep���<P�'.�n����
�����k�Fm\��. �a�)e�i�mk�or��sH�"��v'��8BP�
��d$�r�e�i��d�KP�Tv�+4QP$�de�i�u��.hJ�a�eL���px�.@ 0&i#��k�*�eior(s8Ô,(P,�d`.��/�L(�0ih1�304.��0�d3MPn�4��}i�7JXa�eP7x`r47pa�2eH~
t;���Rv�������?��n�Y���,g@�M�a�e,s4yT�zxH�WP�����d,ygl nt�rh�v�����(��i�n\��2�����e���<r$���H�(��`�y`��`�h�\P�x.0�t�e�*�dTeP\�.@w�e44402107006566106000100410484040008000300414248122702545601014014426020030430650021266600401505047520120103500085656004006708105030800214644540045005340264043070006586125210100062162801452182047972022205012900402118002100521002010240001445024043263610416466041009041217607010087660586000434230065100540329000240060660066000044000254026000300563420404043220034670984208184800202266516080894810736460650105242244526220180181004405485636702600454074502000650440040209607282164008304202105203232404206374012106854102680002788042150250007200610043005054087001901941464021600344546322508606424109850546548063024465001304260180130028000132890660004700017004412228344221098800565222046080536045362004222340360076007608402309032465600090225483010901000050102074360020270156460005644080026569610980044546525606208021243361500401030005128001006600601014374000300225644367000674016760074007688000480005100520006700670421414000040100238421058436565062220330540174404042210704365085154244900520041012032450124566740524522380244130220682364090144804568034401229464000214048016805804324500263450410210460242002243830036526106048960005212652176616840001547801342582086710213208140696605663202415444700504046108000046124907800044502904600400482020167700874470205834466787004660456584526706655004250030508061620400698659201003609080065022544040086458640663005660500244124406178667840244120040804085614124502280656530400608006010086541596510982407632708635250621221655410890008702232600001002200005800322106020704896901029430700062210167122080600100801001086305062664100444604016470000500001510045100241630665236034000601244452342030656102800147048716210060404264300020450836400809103010040341743100522036320020521967000424062250190040010206901001400052903020043045000100182045610406102272458005060050400022100300043200030401300000121424141210402376100452105400030044500031020126540040120502040200878050280404041087874000034061024304060007040017036804321012033400566002320245912140024542420720410001200016218404549200205050900404000421000162823006200210250120201430104064480010104361045600223403000207080614408906070003210060600581002008402107002005000002002810002700449005021032103060050030005630043410436105800070210342210672105240090000982005016042100254004341245050440000042500270065064000442300321292000450609210045610300010500250606000403160000040010080000027400281070010020200602005000030045300892868327060541003000050100760003401000643400060344100450000020546002002367680294192500710816103261003610061861218256106070030041040564505608060023000205025601205000500560200305000454125030020100589000105040502436080202606681004414005002006272742103300301000766108900276303200605610243461000476600000060060096600500601001010240230040600500040040714454003500040404070210002096061026530300040900006000100221894380810940454106500047040721012182014060001060340060002200202000820120205400401400727212200421242900670000461003223006560040500650100602104200062100062103012149250044340023610400902240405030060045000041045001400403006210460044140503270500287465400502001006400680304216040004081060024150056125810706270002214702002320010205002563045010078005601003601009802402250660432523430010040643406004003002504087041040015001002900076102034441002570044000026505000610302200650616702070024050005065053002060584312622023023307001014010504007028103010050020120766016006806002006067300620825042401438002360034465050140161050300010100121245200212005006003227012220943040670601004142427272600650072409960060400400065404676600185000500660524523546642010460204521824104030900000441670100029851420512444743105270690036405001210018000000329040320026524430020905227060026434552000600546010640716221546050407383440580010145034240450613082000027524430276052900490510012003072000943067440060100201781122165001032800503010216710022420700401644024020050204364140165665466707629100408432160645040000452324501041414110056044010004014060160400024560404290104060420354400215294640403240544401040904218620004002085242308610225150001561054385144025226447627040040080020654000421420054700200250276250640000654410021410980004101029001008610021218412200522040020106000044005680032504050450116123850607481020031232725000000676107030004056002014466500504105000800380000500320032200707008410024102522006612400210400600401200225663700506104012245200063401060004103067416072460300042100122100020500281003021626100020100065062000440010650300004386656104000200400200045824700140140020040026507600080006000600001004600600401001607200604026014004010000360004010409060005021002410020122702306605606506000506500040300000601000843000603200223006052210402210421643002007280200140104201066200006076262552307022404403000896003645400000020102602122011665620040080045812216004502328500414002161046100030106400226747630402000201040409007000003030040832404234080980002034004004500422124000410404308722121667060421400342101107410607642030016000300610560600540100312185121094800900600500141024764508009000000320270722007032003281062524300210906163404003420020600160103200003801254900788000155002110231003081067098052600612102326050061032300043004202065040009600000326003800090200650810450365406002310430103440021202460061400403451604656345422127432450212400360061220000049021041330001410470110054513020721006660085600600062045230063601054123026280065102867432002465271425105421020221000380023001701012903810034000002602400421403472509061038003003650214213046506032116818124301006064002234100024076006214616106230034206302502185601041028063005200134070227002106540401540104560103012004440050321030604504247610652105050063800303230214290060370004058000065025083840544410002650600656032214326107060006500400306370000600902304304063000025000061054020454020044101500021030340227204900000630021080561003410441264560054501000430650342220403602632676000343005030210328000762109410026304700110226140230323062226071632010063200251454090005470025410454000431004121050125004000204014445040456123621032682683232504542002327043810940006321030472341042607040150021003325081060000000600124050400405606710030500424003002001405403122502478507045232520000383004302480200059620000601025066072010401024502044344040752034120432603018090904141423149404004502124100002104056040065046043000045047067004302304023040067010960010786000650000308545254040012202609440110404106005080615890000025424720074060032254445005023476200581270610690400818100540610500210608100760600650200878000560210204345654100685400560125420081000025414520481261073250045021028472321030001001800056002054454101432707042166368674502145203221004562105002250651604340415064543020003414106101440042000041218600212065600400540000082204590425100200067065010010584127106002200146740090200070410080102414042106927670011009435400292003900060900221641001010500722764723007300060410630000020610600707405504604606106010000400820094262490010296107032900327000432902050410050078768820057000892650500401600405640060050107002109054020708492132005623120021376341017067282100412142121045004463450700600500040250867052000852010041212563621800223030140205660140560590020007002100900210070000100600060030300112704312432000506037081461742210013043090216300458100301004470015020160020400010600230090800040022146052000400010050520350020005002600100730600098500068100006004000221045045120780600640060502662050504012185464050410421044030026500010055441610540660500052220814102346006102043270050032021090600926960006071032015621003007300102000162144309470360	6000430007221436045721220210900823602564083437000450800200106320004656002041020134120065021027000060601050040010200362005221670122233003201050050054021074027600009606106524009806003801002421520447002105762105422005404005402703421250800045230540010098400054400056000029204544005460216124123061232763621414400104230000781084744747006423545267650052606441000280345048223064380215669203090042250227087010040602244349281706404756002261056608004443015610091050410121010253001430568048326	800020000261730054000200540020009050001240043001042121270184181092106004040132806254001280400670216002214600060540960200663601034066030290612501000504024020290700202342165640048160676040520307222124224007410056200146067032004051550009210911400820307008165200428040725030100961005245050000004404200420467010010632300040452110674243231206090600303400403600503000060300507000871003541050458908650120283240364210972604508215072520047060404125240500840340306103560041061080325814300202124322507010469000061260025216270617000520567024329008810005020006050000252041076267200304654021340312720000224506056007220500942545060600040562058942717021452061866001408020211401242580601244248165364100046000400601001804000203222420050254198100752106010442298321667230501011060066601000001401220001040006004090270125042460041240118600210020004261005430500410661043441005603000414303000046942000511218400006016210630300650610102103003006052200416800500147612470704022184000840540050500009000610602000703600050000210300002564060720004052109288405001027200401020912010051006010106254005000060065060006504200410054402212304105641260026400220060020004000400402341040101004000405043100707040160006168050600080700000605021003021050012080480000604604709050000700021008969280004521004010026070000504020050410040520004052400465400040500005002847674128400308158804140008971085010521812326000223672220876240041030081660023091426543000210840000401064540106000610607620050080063600294004522003500163049252402602721054074016074500404272564000742100204224781200234010047805661040650670106100205440646044230830060440444020104000894402522560067220620024962210681604071096005001423060444020810843706707804001010320290430400400610600365000643041067281040401220123606107000250020204603144060104350104031010010080020050323400490006300060404252461600216287000402166652400324009821007606106406106900060454676366361260345700600004525210246263847600200765004142106211040010185606102072502200668300400236011042016000023020040065042084210250614472200432230043030070500240402505488704606645007410007665021706300200160704810081200014025000020400272600210302100420222800290900024100060002166452012424212503200
8000600000892104081410410040001402323040980040041004501001601424302609809021000700000040120000403400048100004052001032500600020060721080100020320640214600006002200404010500586072223010409000431902124190540004500010326050143201046006146500004540000665062424400640068041270006081062564322105076200209004526043202314540276125218503205245004065000006004105860015410050401100601008410005100040701061980000790097025074262001640871205006030224343025443620250064501484658024144200745000494003076618000650061041306820712003521085045430052010303041561052205230600204066124281221500700010926006102003244065225043212363669245012086149400600720678108014870162406230412323056008600471008090004050042960901540124742083229342452442108730410212210000670680518689030000130002465241200042014221022814441050900032140430000014207050000807843402401422025120212622004410222200225865006300656000634254054501605106405012229040040601200004094004006000423207423063410400485045650068589000040310027300781243041243061461067410302981000407004725027002016472220605824251460105404300700209212580140041443420060660207046000081040061006023410420691000542656810200901560089070230202300164160080006005054100642607329220040108891204110022650106504062302129040600041270700024520142702607021028523005050910087000040043006006502000803200406362121245636406124021860014403207004281363610625044020602346435060491989006500200540271145044550043005012234100212640000027090610004402000460000026305043421840006104003000422204030125094401405020050020610401006710004030042250022144608061040050081580080043250422106436006000650562058245347032043402200164000502081240041240061441067490416190007650042404508216444022741027610320012108927045250534104361250140047410667229229280327002348363003054022944108307080060365042665212525002621067260040142403423250003460065040050540564106604107020000124076500063020087200600541020045061470410243404700000120000243007421290210427004001490400040510058500450104298806000400630240121024341252634602748314220796506070045450047060051605056435450007022411058155850432526503625161277077030040145765050360544041012021148210301002121640380260459205260000417089145060010212610223104476261083080300502309880623201034054006104072020960650468943265769802190109007014908014741054901702152048024325008300040212750405010221503043627600262060010436021030221050221050003200283272734424948260725042300065052000506083080024000040001224270006000436040070310060260080200625405214201542260905005010470430471089032002258490106106310650610061200032666530607606100005081254401546304660080041044810285001410214600000740018300403200023430652000420025036405800690140301085054508030221430426545001640221760001040321080000100622202165032141080073087621008990268281570890264340210090234025700867207605602100670627670068002400023601200610002
9000006000
9000002100700010050634276002107092900805006008144000708080450190030476670200043050464700140023004301001403018040000678610898100504300065270702210600020400421222524	500000000
7000000401407000102019001303671450243803869601010230610040021004212706076000403200010022201264025441812500010360362006020366021400021067014060144506125221042658405105060100450625042607041202500094254465004075006503000696000225230870367210140	500000100760447643058002027640232600001080002100400241121501007234545090900200127000000040910040710056260208175460043001506304543076580767000450810450856645272032247004002445890410643610564504670046900432324507207601245800065033496630020074000000400016762002123214325084176458045041483040083040004005104002125600292611	60000300090400600043030000004205420010220784008942001008012634061065410212043410003061410410141001265408904542210781000525000123005282350213407504301250043600030014043010445008461010221402414102163643022004056456560061440504030050414	60000050041800660270400004278434800623444312340500445000434010434000850650076212802010060421002141230502450200087050210702504000020540006966102256005052823400402034000050210007663652810440122320300200040058400505025706420067020032261032400041004504404277601007401003041250272445485090405410430610902021636004038802125400804101006106109800000500000006301000602400380210461485401032430700410650000005600243300005407000400360000450002106100000740610744100322100430210724004026106410003220123402004762003020204300300263441421063165000230850400810210641065601241401260002543060045600056000008501604830040070060663830410406658007028454705850000340010052000004004179400006503008506003402106504101264047400080061020192524012210162016216522121260012700412565821012254500100012212600300800010054218181007405000430200324507660005421005600065800760630100300700560410890061056230067210048140542400620610830607608300005600410320000050601028978012603603085089100410340623320500405224801040201443210030021465603000650004420342401045001056002106430021266654441004542124201204540800805324223008050005062123402144461000542300045050003606004003622210852100604060602250287646306126201064201290002340800498003200104505006008000364100461400420104522000263642360098214650212500164024005601000630014050006004054100636060040005440230764020076600006360500603040034600050003063061054250645000964000700610656001046006042100005003006006010467663060065445038061034021654005600021454401052610342501205034410850020094080067260500330940461221050270342000540010402566702107401067045245001006005450946007605049600005400025202003600100560001010620270210610214408944015063404002245108700221073450022002034070040006301045050445000048000400670000032002052054004922300450300604346508069210301047460030612209801301006002106600002500002220040300212600490105010050450040083006230003065621001687300103634222070002006004210700036604410070060008094000605021445310048010074000660080416605021216	5000000103063080340040040004006010430122011060020109260007000401060122508904030000506700040544002030002812430421422334050832120261632032470000205623005006000210601421040014340081070010015000020050041002043040107400680203002604400700012400059000460540000808000306362200056000000380102007032221030050054050005020107106302021682144002146102126500100450010012658690000043080040290036270036050050100005007000764004003450021221009608250004154300000034014022130566000340011005600400040274020630210230100230210452400334049020456508010427040732474010500027121653000700605120563461081454450430630894543210143206006153100007104018032000602256180500440066507040005400590020038020021042170030002209702340271005421845005042091065430150962103083647000340046050000100500023030250108143002027602003204101067821032526008410774200	6700005004696870902144126507661901001208480054760210320601812123054080060091232800401040262300067016000408412004030090040850678070509823316010270400032500400050006328020003705021200404250056021271606430050000800546760201016709010300900000001040146770452291060890656020646781921072065015103400560602207445070484241290402406500850661930140098600422303036700100712187227053065054640140670400814000202112940542904012870600632080004923004254017840760	70002010045900020710900860040044050645080544106421442056090300274132103500004002081060632207189889800898800PK
!<��M���hyphenation/hyph_nl.hyfHyf0tP�����'-4�������$�'–’-111001��������.�aQb�cc�pdX�eX�f��g�
hi,Ej�Xk�vl�m�n��o�
p�&q,'r�\s��t4�u��v��w��x��yP�z�P��(alb�c�d�fPl�nPr|s�t�v��xa�bpc�dpe	f@	ghi,
jL
k�l@m�n,o`p�r�sxt�ulv@wPx\ydz	��"�d@ nPr\t�$�$<s�#��Dt%h[t%dl|�H�e�xd8'#�nD''�e�&���tP&���h�r�',�'0�e�(4�'���i�*8ls0t4+:�+=X�A$�Ee�#r�+$u�H�2#<e(2��Depfxk�m�o�s�3L4�o܍,�vl�O�4,�4T�aP�X�4#�m�5\�4�t��`X8#�sp8���ade(g<t�8c�9g�9#n�:H<:T l�=,�=T4h�AlA��HdhtFpE`r�Gt�F��th�l�t�u�Gw�Hz�r0�~�I��a؋�JE�I�a�Ot�OE�ndO��eQ$e0�ido�u�UX�U��a�TE8�hlDr�sW,W��<aTi�W�<_��\E\s�c�o�u|l��k���o�r�l��l���ohp�o��s�p�aLe�i@odu�qpq�rq���ag(k0n�,(r��i r�r=�r��s��TH�8e(w��@blk�n�o�r�s�x�|l�s�x��l��=�z��rz���k���4{E�d�|X�{���o$~~~��a�}��t�E����ae$o&���p����8���,tX���4e<�nTvd�`�E����\wX�
�d�eTi�l�n�p�rLs�t�u|���l��e�w�x������a`��ȜE��E�nr0sDt��T���ed�t���s,��<ox��$t��D��ԥL���Llps�tH=,�hc�p@��\��d������s�l����s�t�A|�T�h��4����a��t\�#�s����ef,i8t�7�l<8���XD�X$n<��Ds�����8`cpp�t(����,l�#he���T��|r��l�X�n�����e�h�s��������.����e0��tT�8�sX�����P��	�X�4	el����� 	sl���(	l��l	a�	e�
i�
o,����X	tX���`	a�	n�:�<:T|	sp����	g̕�8���	v�����	a�	l
nH
r�
s�
t������	a�	e�	oH��$�:\���	v0��(
z����
a0
e8
o@��H�,|�,X�Xp���@
a`
eh
o�
s��8���St(��p
a|�x
t���
k���,�����
a�
j�
o����H�����
ddE�����
mo���
t�
�da�e�i�o�yt�t���(s89X4l�@e���Ld���Xn��,��pnX����exi<E�r\�h�tL�s����t������o���lu��l�
�s�
���d@d`j�k�n 
s4{t� #,op ��4e�-lltp|t�,�Ls�Tw�-L�-L�-84�d�g�k�s
tp4=l5 �r�5$@6(�r�w|6$x�,$7 �l�tl7,�7��7,�7T
aH=��<��
c,E@
o�R@R��8
r�Xl
a�
e@o�u�YLY��d
d�
g�
tTZt�\0�\���
a�\�
aP`5\`���
l�
r�
u`���
ks�]���
r$s8�t�`=�`�Є9$a?�
na�e�`t`aE|k�`k,tPi��4o\ptv�k,�k��Tell�kDl���tHt��|n�v�a�e�i�ouLM��wL�e�w�t�w�s,w�t@w���a��nĀ�X����b(g`itn�r�s�t ���� o8r�P4",(�T@tD�XHsP��Tdx�]���lsăwL��������hD��P����dԈ���n�������f�o�sD���a�t��ĜL�����e,i8s��8�jМ� tl�E�`a�edi�o�tx���XdxØ�$����p�,��̣��d��E�l�s����e�l�nrs8t̤,��T�a��,(����i��=�����l$����kD�\t��\�l�X$e����,elQ�\-XDiP��LnԨ��Xj��n|���att����|o�r�s8�?�
n���e���tЬ���s��aDe�i�o�q���tT����ad n(sȴLX�L����d�0s,���8e`pts�t�u��la�����EH�,T���|e�j�c��������j����t��������j���t$����ed��8�a�t�����os$vlh�Tt��:��	`e�l�m�n�o�p8rDsTv8��<�EXrts�v�����4��|e��L�����f<��m�g|7k�e��$����a�deg$i,k4oHt���e��o��Xr��t<�l�����p�@v�x,�}deP�h|s����X\etrT�'��=�A��@�j0��t��a�i�t	�	���s���e|�E�j�������e����eil r(sX����:p���������0gP`4�Le�
�a�ei�o�u���D��|j,��t����e�r������i�s����?�.,��e�t�B�̲���j��tl��eLt4:��B .�B�(e�4t�@sLc�d�XjT��`talr(��xo�s�T�tX���j$&��t�%��i�t��`&�,'0a�e�iHo�p, ��e� �lx Xs�'�d�'��$aLn\*�Xa4*��@d�8���Xt�#`nP3��hex/��D�ctm�n�o5E�v�3���o<5��5t\<�;�t�:���e�jn$=�,=T�p�<��s�5=>'s�=��g�G�G��(j(GE0t�F��<edlts�t�v\HE�K�\KTlt0���K���hPL:�\	�a�ce�i�k�n�oHpXt|_�T]���p�vdO:����`���h�id�Xc��eDkXrdtlv,e��d�$eeT,te��8s g��f��Pe�gdh:� gp ��tej��|d�nP�n��s,n��i�v�0v#�u�u��e�o�v�`!}Tw���knl�u�x��x���gLc�d�jT��t~�$rp}E0o�z���i<o���le���T�#dm���ae�iolrxs�u�y�E��$�jЎ���t��r$����a�n�8�<��a����d���\����a(f0k@l`n�r�sԕ����H�X�8e���#Lc��Tato�tl�X���șT|j\�X�T�a�p�s�L�X��a�����E����a�e����n����4��p(���rT��p����e<lHpPv�j0�T4o��Eԫ:�Ph���XsH�`iL��a��8\b����j����t����i@��и�r4��i k4r\���s�t��X��jH���(e�]����e�������,a\KD%@j@�HtL���Ta���`a�ei4o|�T|j����t�E�n�r�s�t��D�e�h8�i��k,n�r�sl������p��������j|�E��r���e������ s$���(l���a�e<i��5?T.�5
\e�4ht����ts(����l�t$�p���o��E�k�t�����e�l�nsD�0����T�tx���s�����r���t4����(sl���0nx�����He���P��e�i�o8����Exs(������t�8�a�ix����o����.@ at%bP&c�'d�)e�*f,gP.h�.i0jX0k(2l`6mp8n<>o�>pAqAr�Fs�It�MudOv<PwXPxlPy�Pzd*��x d� f� gL!i`!k�!l�"m�"n�#p�#r�$s%t�'X� a� o� r� s(t0)td)|_��� p�)�� a�*#�*��� a,X� a!e!o!r!sH,t�,�L-�p-(�-�,!a4!o<!pt,X0�-4D/L�.��D!lD08X0��X!a|!e�!o�!r�!s�0�\1t�1(�1X�1��!p2�(2��	�!a�!e�!f"i"k"m"o,"st"u�2t�3��!l�3���!oT��3t4t�4l�48H5<�4$"lD"oT"ph"tt5,H��#L"e�5��5
`"e06t�6t`6��|"a�"o�"s�7���X08E�"a�"e�7T�"t$8�X8tp8���"a #d8#e@#gH#iP#jX#k`#nh#o|#s�#t�#v�w@89X#e�8#e0#rt9H�98<:l;���L|;=��R<�$=X�#ox<'p#p����=X�#ax=\�c�>t�>���#a�#i�#o�#rD?i�?`D@nPA�A��	�#a,$d8$e@$iH$nP$ox$s�$t�$uus�AT$s�Ak $a$Bx�Bt0E|`C8|9�R�X$l�D`$e�C\l$p�W��E��$nE�$o�$s0F=8F�$l\F4(G��F���$e�$i�$t�G��H��$r�I�JL�I�$a$%e,%h4%i<%oD%r�J�0KLPK4�KLwlR�LR�L%sxQXT%k�%l�%tQ`%a�%e�%i�%o4&r�RE|L��S�%s�X~�X���%n�T��%u�-��Y��%z(Y��%jP^��\E�%n&o(&t�^��^E&j�^a&t�_��&jD`�@&u\aX���c��
H&.�&a�&e�&hp'k�'l�'o�'r�'t�'u�d��c#�&l�&r8e�(f��&s�g�Dg��&tH&�h��
�&.�&a'e'i'l'o 'r('sD'tP'uPh��h�@i�tz�|i���\�(#0'c�i'8'e�d'u����\'t�jX|'l�j�Xk��k��m��'e�6~�m���'s�n�tnk�'a�o��p��H&.(ad(dl(e�(h�(i)k )l0)oH)pd)r�)s�)u�)w�)y?q#(.<(cD(dL(lT(r\(s�qXr�sE�t�uv	(w#(.�(i�(r�(s�(tdx|A�{���(e�}l~�(a�(e�~l���#�(a)oLX���(l�8���\�(c)d�8Ѓ(؃-?2X�#().@)o��8��=2B��P)iԇGX)ex)i�)o��\H�8����)lD���)e�)o�)t���K؎H�)a��~�����#�)it�z�OX�*g$*k,*p4*r<*sP*u`�G��*e8�w4���L���H*tT��T�X��Xx*r�P��X*��/�<O���X����*a�*c�*s�*tX����*a�*d+e,+i4+l<+o�+r�+s�+t�+u,y�HP���������H��*i+r+w �TX��l��$+e�����h�`����.X+eh+nt+p�+r��Xp��@���`+d��]b�|+g�����P�+c�+e�+l�+p ��8����g$�m(����+a�+r(b$@���#�+i��s����H&.H,a�,d�,e�,i<-lL-op-r�-s.t .u��
X�#@,d`,fh,mp,r��XX����X��X��x,i��#(.�,e�,n�,r�,x�t��x�,tH&}�����,.|��p����,sl��0X�#�,l-n0-op4���-d -s$7�8:,���(-t��@���#D-f`-rh-v����X�-a�-u��-p+~XT��-e�-u �-l�-p�-t���G�L�-e�-i���
.a.r�B(	��	��t@
#.a8.i@.rH.u@����$���
T	|.a�.e�.i'l�.o�.r�.t�.u�.y��<��.r��X���.i�#Xl�DX�Xl:���.a/d$/e,/j4/kD/l\/nd/ox/s�/t�/ut!�p ��/r�"��,
�-�2z0��</mT/o<2T4T,8�p/v�:��<Phc�/l�/n�/o�/t�>=?p\?#�?z�@�j�/rPB�CPdD�/s�C#�/n0s�D�<n��D���/o,Ek80ax$�E�0s�E 0r�E��,0aY��0f�0g�0r�X��D0a�0e�0i 1k,1l41n\1o�1r�1s�1t�1u�1wZ�TZX\��]��]#�0d�0m�0t�?_���0i�aL�b�b#�0d1n1s�d��c���0k�d,Pi�Te=1o�e�xh D1eL1i�h,�h���Pi#T1fp1nx1p(kX�k�HmX�1u�n�<o��1o�1p�1t�p�4q~q���1e|q�ls,�r�1o�1w���t Pu2iTvX@w#t2c�2d�2g�2l�2p�2r�v��2a�2d�2e�3f�3g�3i4kl4l�4m�4o�4p�4s�5t06ul3�(x�x�l2h�x��x���2r0y�y��{��{���2rE|���2t�}�X�#�.3f3g3l$3rD3sX3tȁ�Ё���2f��Ԃ���ă��3o����L03eL��83t�����P3j0���P��d3�Ԇ��#x3n�����3e�3l�3o�3u�`@�~0��3n�G��X�k�3l��Ԉ#�3e�3n�Z�P����3t8� �X4i����4e<4lH4sX4uЍ~��T44e��zTt�#P4i��,$�kd4e��Xx�#x4cL�T�4a�4e�,��#�4l�4n�4u�4vD������Xĕ��0=|����4g$5n4��4a45c@5jH5l\5md5nt5o�5t��bؗ#,5r��:И$T5i ��T�t��(yԙ��l5o����?�5m�
n��
�5e�5o��~���5uЎl���5rt����5aL���5a�5h6r$6s��?�����5a@����6op�\P�6t��#D6i@.rX6sМ,P6t8�1l�7���
H&.�6a�6b 7et7i|7m�7o�7p�7sD8u��x�#�6c�6d�6f�6g�6rd):����6rT�Et����0����6tb?d���6t|Z�ĥT7j(��7t�#7nL7r`7tl7ut�X�#87n$���@7a������X7jT��Ԩ��\E�J����7i��#�7l�7vp{P0�U����7���k�7l�7�L�\�7e��X(�\d�T�7m8o8p08tT�Px~`l��8r��e�.t�E$8e��@�#<8iȴ�T�#P8d�8l�8n�8r���X8a�8c�8d�9e<:g;i|;k<ox<s�=t�=u0>z�3j�����8gX���,�����8c̸p�o9d$9n@��8a89eP9j`9ot9r�9s�9u�E�suX���9k̺���X09dH9x�~X �z`�X,�#X9m��ԼGl9i�9o�������9o�r, ��9o@�T�9l0����#�9l�X,�#�9m�9n:r:s(:u�B�9i���8���B���:t��X��� :s����T4:.d:a�:e�:l�:r�:s8�X�#\:nt:pD��p�A���|:t��#�:s��,���:e���<����:a4�\�:n�:t �����:e��8���:t$�#�:e(;j8;mL;o`;sl;tt;v��83�\���0;a������D;n��� �X;l���TC�@��4:.�;a�;j�;l�;o�;rp�0��#�;a�;n�;sd[E���D�z��X`�T�;u�X��#�;f�� ��#,<c@<oT<r`<sh<tp<u$v������$<h���8�a8<rx�$���L<kh����������'�<a�<c�<e�<i=o$=p8=t(������<n�bPX�#�<r�����X�<g�<r@���f���<v�X��X�<d��X=r4�,��0=r\���=i�;~���P=ed=r��L�?H=.���@��\=uH�X��Xp=c��Tx=a�=e�=h�=i�=s�=w�����X�=nx�X����,���=l�������=f>i>r>u$�~,���]���8�d��E>sh�k$>e��k\>gd>ll>mt>p�>r�>s|�E���<���p�����|>tPX�>pt�L�
��H&.�>a?eD?i|?j�?l�?o @pD@r�@s�@t�@ud#�>k�>s�X��?.?s��H��#8?t�9�X$?nP��,?el�d?j���-��P?j�X?tt?zx�����?e�?i�?o�?u�XLXlX���(���?n�?o�?p@s@v�xL,z�?a��?tp{�����@a8@e���,0@nL `\@ad@e�@i�@uh X!�p@m(!�?��!��x@sT#X�#Th#T�@a�@i�@l�@n�@t�# $ L$<�$?4:.�$��@e\%�@jds��%�&�,'��H&.PAa�Ad$Be�Bg�BiCkHCm`Co�Cp�CsEt\FupFw�Fy�'#dAaxAp�As�'�Ht�[+��pAp�+H-T�Ac�Ak-k�Aa�Ao�ArBuBw<-X�rX<.��Am�Ap�Avx���.��.�.BaBe�.X�.���`/zx/#DBaLBgTBm\BnxBs�Bv�/�1�P3��3hBo5�6 �6��pBp�7$�� �8�Bh�Bl�9X�:#�Be�Bj�Bn���\<���Bj�;�Bt,=z�<��Bs�>��=���Bs�?� Ca,Cl���l?#Cc�@H�X�C#4Cp�C<CaXCuE��F#�Cb�Cg�Ck�Co�Cp�Ct���H?H���Ce,H��Co�� J)Ja�CgtJ�K�m�L�Ci�N\
 Dc8DeLDiTDlhDmxDn�Do�Dp�Dt�Dű/xO#Dh0Dr�O4�O�DDed��PX�Px`DaQX�Qt�v?8RTpDi\R�R�DoHSo�SX�Dl�S��Da�De�Dr<��0�9T?�Dk�T?$U�U�xU#�Da8Ek@EnTEp\ErDU�DadEexEh�Ei�Ej�EoFr8Fsh��XLEkP�ud�X�U�VopEiDV�VT�VDWX�EjPWI�EeJO8W�EvDW���Es\W��Ef�Eg�EoFv|W������Eel�o��a�Egԫ�W  Fo(Fu`XXlX��XP�X0FlPFtDY�PY�HFe�YXhFi0Z�d\U�Fe��Zx\��|Fs�\#T]X�Fd�FgGkGp�\���FaGc(Ge�Gh�Gi�Gj�Gk�GlHmHn8HodHpxHq�Hs�Ht^XL^�^�|_
�`T Ge$a�Xc�DGcLGg\GrhGt|Gv�cX�d]g��f��TGa�gE|jXhdh�tGe,i,i�Ga�lbj#�Gs4m��Ga�Gi`m,�LX,n��Ga�GiXn,�n�xo�Gu<s�sLHiu,�u(He0Hi0v�vTwXLHbTHl\Hr�i48xL�yE�zpHl}P��0�,�P�Ha�Hm�Hu�sfT�l��L�HaIelIixIo�Ir�Iu4I�8��4:.�Hg�Hs�Ht�~��r�UT�XIkIm$In0�x��~Є؝�ȝ��,I���<�\@Ip$�THIs���TIg�X`InX�#�Ib�IpP��������Ie�IoP3~�����Im�9����I.����Ja�Je Kg0KhPKi\Kj�Km�Kn�KoLr|Ls�Mt�Mu�Mw$����a8JcHJd\JfpJn|Js(�O\�@�X@Je��1����TJ.P�����hJk�����\�#�Jk�Jl�Jn�JrKu$����Jl��\to�Jt�=ș��Jr���\����Jd�T�JaKs��,��KlX��������(Kud>,����<Ki�XDKn��hKeH�tKshI��Kb�Kh�Km�Kp�`�����I��I��-L�-����Kg�����Ko�Ksl��l\���Kf�Kt��4���d���LcH�La8LeLLi`LolLu�Bx���0LiX��h���DLb���@���XLe�,��cL�tLa�Le�Li�Ll�Lm�LnMpMt�c���X�Lc8�G�Ldp ��� �Lo�X�t<��Led$�8�������Lr`�L4MaLMedMixMo�Mr���H��,MaDMk�9Ĵ?4:.�
nT��@��\Mj$�����pMl�MpH&���X�M.h2�$����Mkܵ��Me�P$���Mu������Mi�����4����Ma0NcPNdhNgxNn�Np�Nr�Ns�Nt(Ow4Ozp�XX�(Nh@No�kX0�,�HNe`NjȃD����X����pNa�Nt �z������NeX�A�Nu<7L|�X�No�Np�Nt��0�����`���No4�4:.OoOs��Op(�Ll����L���� OaP�]����$����DOtL���LOs���XOa�OePiPo4Py$�L�E�Oc�Oe�On�Or`��������Oi�Os��AH�T�Op�L����Oa�Oe�Oup�Xx����$�TPn$Po,Pr,��@����~�c���k�������DPs����LPi�������dPaL�2���xPfP��Pi�
L���Ph�PmQr���P��p�0�����t������C��C�����
��������\k�e����Pm,'L�.xQa�SbLTcTTd�TeYfYg Yh(YiX[j`[kh[l�\m�\n�\o<`pD`rhasbt8buxcv�cw�cy�cz�X	�QaRd4RgLRk�Rl<SmPSn�Ss�St�D��D��Qex$\�Qt�#���Qs�Qt@ �Qr�E��$�Qj(Rr�'���QaRr$RsT(�d)P�)=���,��,RhDRl<-�X0�\RolRs\1P�1�1�dRp$z2#xRn�Rr(2���Ra�Rd�Re�RiSkSo Ss�2X@� �2��Rw�2G�Rv �,�*�����R��3#�R�<4(4��Sl�4X�4�,Sf�h  7`6��4SeX8Ap8��HSahSk�;�|;�`Sa�Sl�So�Sr�Sw�;(�;��;Px���F��F���Sa�St�H\LX�So�I�Sr`L�Q�Te0y��U#�Sg�U���Sa,Te�T�Tl8TnЭ�VX$Te�V�DTaȳ��c��p�dTetTi(w,�t�#lTaX���Ta�TcUd0UeDUgTUi�Uk�Ul�Vm�Vn�VoWr�WspXt�XuГ��Ts�Tu\�X��T\�X$����Th���`�\�Te Ui(Uo�Uw�������<UtD���4�����LUehUspUt��\��jt�,8���|Ui�Ul�Uw��H��#ܬ�̫#�Ur�Us���	�Ua�UdVe(Vf0ViPVkXVotVs�Vt���$p��Ur��XVc Vx���ĮX��t0��DVn�Z����<Vt�L�#dVv$�ؗXd�lVc�Vp�X���������Vst�#�Vn����Ve̶B�#�Vp����Ve�VoP���,̻L�Vt�#���Wa4WeTWg�Wi�Wo�Wu�Wy��w\�#,Ws���(9#@Wf8�HWatWe�Wl�Wr���D9#lWt0�(,:�D������Wv����X�\X����Wa�WhXjXoXp Xs8Xt�]�h����Wc�����,���We�Xl�,$�=��#�.PXnT��(XedXiP�D�HX.��9��X\Xe����Xh�Xo�Xr�Xw����~(�#�Xn�3~�����Xn��G�Xe4���XiD�� �L����XdT����XnYr �������XsX����k�
��
`Yd�Ye�YjZk ZlTZn�Zo�Zr�Zs([t�!p ��XYspYu�"T0���$��xYi�"�Yl�Yn�Ytd%��c�(���Yj-��,��Yd�Yf�Yg�Yk�YpZs�JLdL4M(S�-4.��-Za0~0Zo8Zs<2X�2cԼ�p4@Zr4��HZdpZs�Zt�7�$7ThZt��c�7T|Zj�8!,8��Zb�Zk�Zm�Zs�Zw��X<�,�_'�9�Zo
,�;8�?�[r�<�ZtH�,�@��[o|@2[oPBG�@ [r@[s��|B8[eP[p�B�,E��X��v��[a�[e\iX\o�\uX�A�(#�[j�x���[i@w���[d�[p�{7�bX����[d�[s�[tL���[p��~���[e��d�~Ԉ#\d(\jH\k��=<\t\��\s�U�ĊC4\e؊\0������P\it\k�\oL�(�Tl\l��D�E�\j��a�\tl�������\s�k����E]dD]e�]f�]k^l0^mP^n�^o�^p _r<_s�_t`u0`v�w�X�#]c���]e(]y�g8�����0]a<�E8]gd]k|]s�]t�(@���\]nt]o�����]t<����A���]t\����]s�������]l��L�]a�]j^s4�X��#�]n8ee�]e����T^t������^k$��<���(^a@^od�I�,$���H^al^d�^e�^o�^t���x^sl�N��Rp�X,�T|Zj�^o��=�c|�E�^j�^s��a�^tP
? .d
��^e��^t�`�SX�S��^a,_o4_r�_t����_s�TX�T]P\_al_cx_i�_o�_p�_t�L`b��d_o��,�A�t�,z�_o����^j�_o�_r�_s�_u,	�	b<
�	�_p�_td
\|
T�,����_a�
��`t$`w�l���`s4`Le�
�,'��`a�`eai@ao\au� gx X``s�'�h`d�'��t`a�`n�`s\*�a4*���`d�+l�+L�`r�+���`t�=2��`sx/���`i\<E aj�;�`t�:���`e,alH����I.���ae�=E�H�Lao�F��4an�IrtZ��Y��Tal�\��aa�ac�ai�aj�al�am�asbtT]l�`,jX�adp L�m,4m��ae�o�xo��aa�sl�=��C�X�aj����ai��� ba(br0bs$�XH�GL�4��pbi�bl�bn�br�bsctlcu���X�\bj\��dbt@�E0���|bk�buPT�t\�#�bt�����be�,w�����bb�bi0�,���|����bc�bo�`��4����bacj$co,cs����Tl��@ctLM���L8cePW��Lcj<���Tct$�`cr��(��z��8P�����.�ca�eb�ecfd(fe�gghh�iitjj�jkXkl�kn�ko�mp�mrDnstnt�outpw|py�g��#?.@dbPdcddd�de�dg�dl�dm�dn�doep8erXes�et�eu�evt%L�&#P&��Hdh(�'��\daxdo�dr0)Xd)��)�,`l4](2���dlSoL7t 7#�dr`6���deX8Xp8���da�dt�=X�T<>k�dfD@} ea(ei�>��er\@��@~PAXA��0eaHeu\F�(G��F��PeeleptetdH��Hz�$rJX�I�ea�ee�eh�J��J#�en0K��M8�es�N\�NX�etdO`Qk�c��eafe�cgDg7(f#fs�pE foX�,X�#PfdXfelfl�fn�fogrDgs�gt����dflԛm���fd�fe�fi�fk�fop��|����#�fs0���t��ȳ�����fa�fe�fo�ft��P�X|��|Zj̻�X���ga gn(go8gt��L��,|�P<�0gr���`gahgcpgh�gt�guh��(�,���8��T��xga�gi��l�t`�X�gr|�X�����ga�j�go(�G�gg�go�X�������P���g���L�
��H&.Pha�hc�he@iiLinTip|ir�it�iw�iy,i��/�d*��<h����hhophsxhuDh�<>��Fx�M��hf���c����h.�hs<E�hf�hm�hq�hrisiu����������hi��X���.iiu<XD�X�X���is����P��$i�8����8ir�U�H��H��\iol��dimTpio�'�]��X�ia�ie jj(jlHjnTjo0=r`jt��l�ib�imhX�#z�"�ikjrju�'�8'��js�)
�,0~8je</m�0�p4�4��@jd,8��Zm�@clja�@,,ELYz�X��	|ja�je�ji�jlknkokr,ksPky�]#�0d�jf�jr�jtd^�D`,`���je�a�b#�0d�e ke�fxh Pi HmP�o�<oT$ke@koHkt�p�|q��c��v�tka�ke�ku$z\@w��lkn��LX����ku��~�k��#�ka�kdlilk$ll0lm@ln|lo�lp�lr msLmtlmu�mvL�X��kd�L0��lnp��� ��X����la<�~��9$���8ld\lgdlollstlt<�kp�����,��������la�li��X�o��<k�lr�����ld�le�li�lomrmsmu3X�#�ll,<��M���( �PTmc4me<mp�,�,�X���Dmh\mr�	�����
��dmn|mt��4:�
,'0�ma�me�mino0nu�+x�'���msh0tx/���md�:��:���mp�ms?~�Gt�F���mfnk no�v,HlJD[N�Y��(nsTwX�\��<noXnphnt�zXT�?���`ne��k�na�nc�ne�nhojooXorxos�'�(���nt$����nc�nd@��ܓ�ĕt�a�nt\�#�ne�nn����k��������of,ol4on<or0���O\�d��d���DopH�Loahoex����<L�pol�op��X4�
�od�oe�oi pk<plPprhps�o��w��XH����on�\������os����o��\���osptX�\peA�� h�t�#(pp0���0pa��X����Hpa`ps0��|����bo��L��##�pa<>����po���.qa�ub�ucvd(wefg�h�iȃjЃk؃l�\m,�nX�o��pԇrD�sP�t��u��vt�w�yP�zl3��#?.pqa�qb�qcrdre rf(rg�ri�rksldsm�sn|tp�trusHut�uu�r�� �@ hqg�ql�qp�qr�qt�!X�#�8$��#���qe%��%�t%�qo�e�P&���qc�qe�&�z~l(#�qn�'���qerrd)�)�*O,�HraPrexro�rr�rs�ruH, �,X`rdhrt��
�����L-#prnp-P�-��-�rt .T�.��/Xd*���r��0X�rrX0���re�rk�rr`X 1�1�2o,sr(2���ra4sc<seDsfLsiTsm\su�2x�}j�2X�3��3X�406z`6xsa�se�sp�6��sc�st�6��� 7!�ssD���7H�7k�slX8�ssp8���sa�skto,tsptt����;x|;��sld�&@<att<#to�<x<'$tiDtmLtpTtt,��$=�8=�`ta���=,�=Thtw�>O�te�tu?��@3PAzA���ta�tb�te�ti�tmuous�,�$BR�BR�D�tnHC�toL�,`CR�C�ut�D��F�,ua<ut�F9�I�HL4uu�I��ea@jdullumtur̤?�K?L H���M��|ue�ut�uw�NE(OxQk�ue�uoTU7�T��ui(�$`���ui`���uw�\E�uu�cl�pP4vahve�viwlwo�r�@vp(r�vsq#(vg�-�hyK@y#Hvv�x��Pve(w#\vl|vn�vrz��vaHz��{�vl�{���va�ve�vo8�,|T�ve�vp���||��|L�L�#�vaL������vs؃��veX�, wpP�PX�#?.�wa�wc�wd�we4xf@xgdxi�xk�xl�ymzn4{o�{r�}sl~t�~u�v�~xȔTГ|wl\�#$����whܗ9|�#�wn��we�wiL�U0�#�wr�wt���ܚ���E�wgxlxn$xrԛExiL��Ȝ\��E0�b����,xf��H=.Txg\xy����
h��,�xe�xg�xl�xs�xw4��|��ԥ����CX8�~�xe�xl�xw�XHhX��T�xu��LH��̫#�xa����xayd@yexyiPVk�ys�},p�ya�UrP�Э,yk��#4yeXyk`ymhyv��n4�u �|`��0�Opytd�\�ye�yi�yoX��\�4ԙ�.n�O�yi����ya�yh�yizǫz�@�L����yd��#�yn�����	z.Hzatze�zg�zk�zo�zs{u({vX��ȳ#@zc`zt����Xze8��#lzi�zp�zv̶]4�,�	�H�T�zt����#�zf0���zoD�XP����zc�zrи�i �T�zh{t@���E{rp�X����� {l̻EP{f\{lp{v���,���H{o��,h{id�4`t%����x{b�{d�{g�{k�{m�{n�{p|s����{a|e�|i�|k�|o�|sH}tl}u�����)�L��`������+�H=.|s�H�\�
D|dX|gd|il|mt|n||p�|t�0���<|e\��D��P|d|�����o��X�X��D�X�|j�|m�=�d���X�����|n�|r�|s�]\K�O����|a}o}t�x�\R#�|m(���e$}o4}r�T~�T��T2,}ad�A<�@}h\}sd}w��������zx}p������}a�}m�}n�}p�}s~tdO$h����}v��L��xl���}e�}l����}l��������$�P�}mT��$~a<~eD~i`~o0��8���~k4~l|��������P~c�����~����X~p���~a�~wH�n|�#|~p4����P�����~oT����~r�~s�~tX���?.��������~j\���~a�~p������X�k��z$aDe�l��XX�#f�X���0a��#8lTt���do�r��Htn|v�,ԫ�����
k�e�i<��e\��E�r������.#�a��c��d��e�gX�j��k��l��m��n��o�r �s��t��vl�8�e@�kP�n\�sl�t�,�#�XH�o�(` d�r���x�hh!p ����o\#��"��f̀r�s��tju�'�8'��Āo��(��؀r�t�(��(��r�)xp+��a8�eX+(�a�+��+��+X0�s�N 4MTD�r�,�L�kl�ot�zd-��-��. �-|�o0~�3�3����p4A4����a�d�f�g(�j8�kD�rL�sT�tt�v|�w��zp41L5���l5T��.�e�����#�l���h6�@6�0�o7�$7��7�h�e���7,`�l�<P�h��,8\��fĂlԂn�s@�������n�8L��o�8(9�9X̂e�i����9��c�9
D<��;���o�r�M�<H�aT�e\�il�kx�p��t0=A=#@�g�=,�>,o��>(d�r�?t�?z��r|@xAX�@��h��j��r�AcPBTC,Ek�X��v��e�i �oX����.P�EԈ#��n�	�X����s�����t��<�eL�i,�, �$$�#D�s��#?.��a��b��e �i0�lx�m�nP�p؆r,�sP�t\�ud�v�������z��j��sha)<��I.�d�f�k�n�t�v��t4����eT�@��d��\��4�/0�4������(�eH�id�kp�sd�T�n�����l����\�s��5<����.��i��o��r��s̅uԅv܅zL�;d�A��j4�(��T��n��t�F��K$���	�I.�a�e �i(�n0�o8�u@�yH�z������,T�Qp�W��XP�,h����	|�a��b��d��e��j��l��o��sȆz����]��b�g������e��l��r�R���&x}����Іg�iL<k�s��<����#���$t��m�XPT$�pD�t,z<�a����^j�
~4���e��l��o����x�s`E��r�����
zȇrH!!����nL ��e,'G�a�e��i8�o��u�'��I.,�aD�cl�d��f��g��m��nȈpЈs؈z�"x�'�$�m<�p�#x4(XT�e\�hH(��&�H&�`(��d�.|�aX(��(�()9�)o�)����a��e*R\*�4*����d+��+��,�x/��c�d �e<�nP�s\�td�v00��o��h0��0X,�k�0�T4��3��4�d$���6��H�s�6��7�p�e���<;�:��x�c��e��f̉g�j�n�ms�t(�;��st<�<��<ĉa-��<�؉d��k�m�s4M�<Px,=��=9,?2���(GE �ih�r�F��
(�ep�gx�k��m��n��oȊpЊu؊v�z�G�H�,H��H��H����aLI~�H����d J�Ja��g��s`��tJ�8L�PL��
���Z��s�Y���g�i4�k<�sH��0Z��m,�t$Z~<Z�D[	�\T��a؋c�e��h��i�j �l|�m��n�o8�p�as؎t�uH�yT]��a��b��l��t�]	�]	�^�l`�T`���eXa	�i�`#̋hb�Xc�	 �c,�d4�e<�fD�i`�lx�n��s��t�?�c���o��d�xdX�dP�s�d1��8eTX�fp�i�e�Lf�g��g�ik��o�iXj��dЌg܌m�n��sp �X+�<k�Ȍa�kTl	�k���g�l�4mc�o�m�nn�xo0�aL�i�o�<�s�p9�q��q#D�cd�el�mt�n�q�Prlrx�sk��a��i��o,tX�s#��kDu	u#��j\u,�uЍe�u0v܍u�v8w�Tw�LHb�m�n�o �p�xX�xXy4xy��z�T�nd�t�z��(�ax�e��i��l��o8{9PK��{\�i�{��{��p�c��l��t|�P��|�}�}!	p}E��eȎkЎr �~������ad�e��h��iȏo�r�y8��(�a4�b<�kD�lL�nT�t\�v��'	 � �t��f�X<��t�X��.	T�?H&.��e��k��r��vԃ,0����'��rT�4	d�;	������t��X���)�0���Џm��؏a�eTeA	h2���k������k��H	؋X4�r@�uX�M	����,�e�H@����Tp�a|�h��j��o(br��u$�#�a�#����#�#4�#	Аcܐe��i��n��o��rȑsБt�w�c�X�ȐaH�~�on�t��X���\���d�f$�k,�l4�tx�vD�9�����X�R	P�.X�d`�eh�gp�v�X	 �_	A���e	��l	��r	��E��ix��x����lp���������f��vL�x	|�b4���cj��4�#ܑn�����e���a0�eh�i@�\tL����a`��E(�eH�n��,���@�a�����T�s�X\�e��T��a��e�i��o�rP&�(�����c��s��t|�������#Ԓiܒk�t�z�~	������4��l�#����r�����X��#(�o8�s������X�80�pH�t��P�k���.Гa�b$�c�d��e��f�gp�h��i�j8�k�l�m�n̻o4�p�q�r��s��tT�u4�v��w\�x�y8�z`��� �a(�b<�c\�d��f��g��i��kȔl��m�n,�p@�qH�r\�s��t��u̕v@ Ht%t4�o�%�P&XL�aT�t�&�'l�'#t�a��exdo��s(X�y�l(#|�m�)�	��h����*#,#�.XX0��\Ro(2#ܔa�i�s2X�3?�4�t7�`6���ip8XH&.�g$�s<:�	x<�	�>,8�rD@�	A�A#T�c�,��F#p�cx�e��oG�(G�	��thGt8H��ItH&.��s|L��N��NX��t�M����sdO8�\��tQԕo�r�_\�^e�_�t�_����sD`$f��c���dL�e\�h��k̖l�o�t�z(fX�h	h��T�et�i��t��u@i�\�(#|�c�i'��e��s8����o��jX�k,Xk�Ėe�l��k#ؖrtnE��a�nX�a�0P���pD�a|�e0�it�o�r�sT�u`�w(rq#<�g\�md�nl�sds�su�	�w�(w#t�a��i��l��mܗn,o�rdx,?.@y,�x����e�y@zHz#ȗcz��Зa�{�n�o<F�	��k��sT��|���g���(l�im�# �aD�eL�r���������T�j��\�tX�#h�e��o��p̘rؘv���	�&FP����vT��S���e���t؆����sd��	ԇ<�oG�8����d�)lD�k�)e �l(�o0�p8�u ����8�,��4�1���@�t��#H�it�\t�a�������l�nX�EЙaؙb�c�d��fܚg�i��kԛl��mȜn$�o8�p��rx�sD�t8�v@�zГ��t�c,$����a�hT�o\�����eD�z<�cD�s��aL�eT�od�rx�s�w�ql�s|�,t�,��X�<\�u��,�kp�i�����a��i��l��r|�X��cP�X��X����x��X8�#Țp��Кa�l�� ��L8��$�a\�ep�ix�l��n��rțs�xw�X8�a@�kP�l��	X0Xl4�	����H�l�th�tL��t�X��<��o���x�mX��G��e��i��o n�ȪX���T��t����a,�d8�eL�i`�kp�o��u̫��d�sl���������.���p�$�u��XD�eЭ�	0��X�j\��w���	��h�b��gВ���e�����	��r��u��X$�H�X��a��	���a��e�g�i�kȳ��rشE��H�l��X�e���0��̻:�TP�n4���,�aX�l`�op�rTX��\��G!�h�Gh�e�@u����aԝe�i�k�od�s���aĝd̝l�{m�{nh��	�X8�E\�X�e�i���	|�D���c<;d�tT�������g4�o�Ct��~��a,�m����T�@�n(��H�op�r��T�t�T������c��eОj؞l�n�p,�tXa/(�#��hf�@����mȞt�g�,��p�<�9o��<l����l�o���	@~~p}E�tD���#�nT�� �e<�uL�����t�a��e��h��j��nğo�r�s���|�#l�a��l��nؐ�0��l�,�����p�GL����a(�X؟g�go�r�X��e��E�����o��X�k,H���X0��p,�t������$�e4�:8�t ��	��XH�nl�r�P��P����~t����#t�d��zX���|�aРd�e�iX�l��o��r�s�PE�xt��#��lH�Ġe�o�X���l�#�i�r�����(�eD�nP�s�(E4�j\��t���	$7�����<�s��	h��	l�i|�o��u<�X��8x���t�o���4�X��#��m��ot+p��x��G̡i�o�<B\���ġj�H�����ءn�� ����f���8�a`�e`�i��l��o�r �sL�u�?.D�kX�#(�s�G������L�b��#T�b��c��i��l�n�r�O�P��X����g����.H����i�����e̢o$��	\��	Ģv�XH��آt�����e�o���|�#��fX�p����a@�eL�oH�����,�g�#4�n8�\X�n��,��|�j��p��s���,t�z�@@G�?z��e���t�X?���#��.�r�s��u���أgD�	l�x���d����f�G�a P8�l4!o��T0�e��]@
#D�r`�th�u�
$���
���a��e��i�,��E��s<E��iL������t��a�d4�el�f|�g��j��kԥld�n�o�s\�tԧv�zltp X�o(�sh!X؋�!T �c�"�D�n`�rd%�P�s�%l�',8'��X�e�*P��p+��t�l��n,|�,��.H�-��l��nĥr̥s�.H,/$P/X0��4:.�a�d(�e<�mD�o�[��0�t�/#��n�0�	 �r�0p�0
4�v@1|2l<2zT��4#L�b��c4��T�a��d��gĦk̦oԦs4,���op4��o��r�~ԼPl5T�r@6��6T$7T�l,8�=T�<�a�h(�l@�pP�t,i~l>��a�>�> �a8�o�r��?E@�?zH�a�@t�o��s�A��A#l�o|B���c��n��t�`�<�w�B .�
n�B���eħrܵ�DCHTC�̧e�C],E��e�HXlY�|Y��n��r��tY#�a��f��g��l��mШn�X���a�et�i��l�n$�o��r�s0�tH�u��w�Y��Y�l�j�Y��t�t(�Z�?.TZ��Z��?.��t�Zb�Z~�[�d[��Ȩg^��]#ܨe�i�m�r,�sL�tx^
_�`���I.$�s�`9`a�8�txaED�r�a��a�`�e<���aXX�r��b#l�c��l��s�cX�d�yEȩa�e����m�eT��aةe�i�o�6� �$�f��Щvlg9(g#�m0�

�ga��s�g���oxhTL1iPiXD�bL�f\�lp�p|�r��s�i
�~d�?�j��T�il
�k��h�zl~�l
�.��s�l��m�dm���nHmG��aȪo�
�xn����z�o���r�t<oTԪe�t�</�<���v 
\r?�a|qE�rs��r(�e@�olsXtG\�ih�rp�uTt�P6t�t�$�H�\$�e8v�x�t@v����s�u����tPu����e��i�v%
Tv����n@w#H�a\�cl�d��f��l��nĬpܬr�s4�uH�z�v��
̫ap�d��e��f$�g0�i�k�o@�pd�s��t��uخ�w%
T�d,w<�n�wxO�x��x��d�e��j��m�2r��v�x��xK�x+
y��yO$z��aPz�{��M.Ԭp @1
|���tb�8c�m�2t|��24|����.�i$�p,�sT|�dH��H7
�|O@�t�N=
(}�T�i4}C
H~XT~#\�c�}d�e��r��u �0�X�#?.Эe�f��i,�l4�mH�nP�r|�s��tĮx��%
�ȭuȁI
Ё��ܭfD�9P���d�e��g$�sh�O
p���r��ԂX��@�p��j�ă�d�al�st�v��V
(��@�$L�B��t��E|�\
�����a��r��uL�Px�b
\��d�����c����P���p E��#�d�����i�l�s��	��P@
X�k�uԈOP�eX�gh�k|�m��n̯x��4���؊`�w0�����t�pD�2P�����d��g��kįzl5��r�����t̋h
���^� �Xԯe����ܯeH4s�u�w�t��#�r����\�et�k|�l��m��n��oаp�r�t�u$�v0�z���4�ET�pl�s�����D����P�����s���������d��t�n
��8Ȱd��s��t
P�����s�t�v��z
���
���g�}X���&j��~�
ĕ`�eؕ�
����8�oP�sh#�X��m4�X�ex�k��m��ch�lp�#��dT���e��~�����eL����w���бi�kМ�P6t��
 �Tܱsx�O,�n@�sT�t����at�e�i��o<�s��ud�o@���$�aGX����8�c8�,��L�o��
��E`�s�#h�e��n��r�ss(���E��e��T��t(����s��$�����s�(���Bj,�̲tԨ#زe����#�k�7l�p�r,�v���Ь��أg�sp{���Xd�T4�a\�cl�l�7mx�t��u��A���Td�it��؋X���P6t@�#��i��tH��
�%
����pH�rP�sT�#
��aX�cl�ft�k|�l��m��nȴpشr�s�t0�v8�z���ȳaH�c|�d�e8�fH�g��i0�kP�o��q�s|�tp�u��vĻy��x@��l�Od�c�e�
P����O���?.��s�4P�b����b��e4��X����g<:���D����дi�sX��
���	��p���ȷ��e(�t�J��J#�l�M4��
x��i�̸k@�i`�y��
|p#X�.p�X��a��m@�l�aеeܵo�r���	xs�
ds��al��
H}��i4�����t��Xĵr,��Լ���e��9�O�k̾,�#�cH�dT�et�g��l��m��n̶p�r�t0�u|�G�@�e�xd�dl�t@����tܿc�.��g���
���������aX�f�?����a��
����t�,ضo��?l��
8����e��g�<v���
|�?T����a�eH�(�n����B�I�,��T@�a`�lt�r��s��Hl�e�����H��i��4�(��e��mL�$  ��$�#��eԷj�m��n�v-9��̷d\��T5�
��T�a�����g�k@6bTC$ �o$�2���@��(�a������<�h����
D�c��f��l��n��o��pиr`<s�u�v0��<�����f���������i��O8�a8<rtt|��~$���ȸ.�d����X���
�&�$a�X�#�e��T	�cL�e��f��i��lȹoԹp�tt�u��`�n��X@�ih�kp�m�d~e���
�h�hkx�e�X��X��n�y�h�����kP�T��a����#��n\�
�o��9����tt�����n��E��a(�eH�ih�u��9L�? �n���
���4�v���
<�jX�n��(����`�r��X��Tx=a��e�h�o�rD�sh�w������X��e��iкr���
�,T�TȺe�<v,I�
4I����x�
��~��X��m<�H �e0�oX��ԯ~����(�l�(��<�lX�m`�p�L0�������#��i��n>r>u,��t�,��
��~��o$�E��l���eP�G��L
��d�e,�fD�kL�nX�o`�p��rؼs�t�L�oX�g<���s��80�u���$�f<�r��K��P$�, �i�����|�a|�e��l��r��s��X�����ap�<��<l������d|+gȼiмo�<XP �t����e�h,� 
d#d�ax�c��f��k��l��p��rȽs�
��
�aԽe�i|?j��l��oh�r��s`�t��uPxp�k`!��XLX����e�������p @�dH����p�#�.�e��rpC
,��n�~�s~�tl#,�d<�j`�n��t���-��4�st?z ?L�r���T�gp�sT�����t�x�s�B��B���e��	��e̾o�u���Pfd��l��ľdܾe4�l������s(#�g(�oL�sX�t`�u������ealr@�t��t8�j���.@��L G|�a��i��oh �!�X�x"��e("����d��t #h#LԿe�l�n��p�t�#�4�e�r�#X $xL$w�$��$X(�a�Dl�$��a0�o8�r ��	%,0%�D�o$%~h�X�%��L�k\%�T�at�j|�rds��%���a��od�X@�~X�$&���t�%
��i�f'��a�&�u�'��?.h�a��c�d�f�g(�i8�lL�m`�n��p��r��t��z,'���a��d\�e8�gD�id�k��m��n��oX�p��r��s<�t��u@�vT�w8�Ü'#��d��k��n��p�qtx 9`!��P�.(%
��p���H&��#����.4(��M.T�e�o�'(`(��M.�o0)?�(X�f4Z.�,X)#�n()���ed)�l4K�)��0�l�)?�)��D�a,*\
4*��X�a|�d��e�84\*t�a�9�?;+����l@+���8c��e��i`+�p+R,A��r4,2�,B�,��iH-�-k��a�i �o(�rH�uT�w.�<.��.<4�R	HX.���0�t��X<�i`/�x/#��a��c��d��e8�fD�g|�i��k��l��m�n��o��p��q��r��s�t(�x(0F�/��t00��0L�0��i�0#��th0��i�0T�0E��n�p$�r0�t1�L181��sD�Bt1ܭf�1� .\�dd�gl�t�12�1�2�2b2�t�e�xg��l��n02�82@2����d���h2����l�fX3����k�\KP3����m��p4_�T4�$�e�3����d0�e\�md�t�Z`4X�l�4@�gP�nt����b�4�H�.��ih5�x�h��o��w�;`5ox5v�5L��g|�6?�5����i6D�86����i�6��ȸ.��k��s��tX�F$�|�6��6���j�n �s7��2���9��80�l�:Xt�a��f��g��j��k��n(�o0�s�:��:l�bt<�<��a�<��<X�<���l��s��v��z,=�P�.�c��-�h=@j��=����d�i�k�Bs �td>\
�>x>��t�>��>u?D^�8^E8�s^E@�r�?#L�e�?��X�et�nPA<|D��C|�i��o�D,�F,0Ek��u���F����.�b�c�eL�fT�g\�kd�l��m��n��o��p�r�v$�x��z���	G���(GE�d4�f<�p0tD�v ��G��G��GXH�,H�\H��M.x�i��l�HJ���HX�H#��.��e��vz�|I��I+
Ja�Cg��r��s0J%
`�

tJ���a�edJ��~�J#��nLK�PL�|L��LE�i�
��P��0���X@M�D�u�LL�lxNE|j\Nd�tlN��p�e�Ml|�ixO#0Dr�N��c��j��l��p(�t�P�p9Q����g�PT��a�RET�n�R#��a�o`qXHSE�t��9T?�m�S��eV,DU4�ed�hl�it�o|�r��s��u��w�V�W�\W��W��X,�X��e��l�X(\Y�|Y��Y#��b��g��i�n�r�s$�u�Y�Z�0Z,��t$Zj�Z��s���[�D[��[����
�[��,�r�[�4�a�\�d\L�t(^�T]��`�f��g��m��n��p�r�s�\��h�a(�c@�e��f��h��i,�jX�kp�l��m��n�ol�p$�sT�t`�ut�yL^_�@_~|_��Mx�_����r,`��b�`bO�p�`#�o8�r�b4Xc�\�cl�ex�l��n��x�c~HdC
d�d�n8e��f�Lf��e��g�f�b��hT�hk��eli�i��e��i�iXp �j#��d��e�n�r�j�k#�lXx���m#�.<�s4m��eD�o�m��m�nXn�,nP�ah�ro$xo<��a��e�o��.��g��k��t���y��p��p��g qz
�s���u�u��l�u#��i���u����.�uT�e0vTw#(�l0�oP�p`�r8x�y�	D�r@yHy��<�.xy��?�y��X�e�z#��a��e��i��l�rx{��zE��s|��{����l�B�B���e���t���s�|����t}�~X	�m�~����ex~��i,%��s��P�m��8���0�k|�l��p��r��t���8�a��e��i��o��r8�sL�uX�y<������X����X��e���iT�#��.��a��e�i�k0�mD�n\�r|�s��t��uH��ԛCԃa��l���.$��(�m0����a������<�o���ЄP�.T�b������p�e��t�#h�oh��@�XX�xT���X��c��e|����xX�����t(�0�����.��c�k�l�p����a$�e���(���)�d�~��������iԊ���0�c(�X��D�r���؋Xl�r��E@��$�#?.��a��c�nd�f�g�k0�nH�pT�t\�ud�v����
|�al�e��h��i�j(�o��r0�s��u4�w������n(���K�����z,��h���I.$�����(�a@�d<�9d��ԬpT�b��X���\�X��a��d��k��l��m�n<�r��s���?����i��~���I.��f��sؗ� �?h�I
P���b��m�xh
��5��T��u���s(�t���ș� �j\���T4�aP�ol�sܛX��,��TX�a��`�l|�m�Q���~�#��a��o��u4��T�X�����r������X��d��e��n�q�s���K������c�&Ux�,�����a��#H�cP�e,old�p��r��s,�~T�\�v����Et�e|�l������\�����k4\����fH�G��e��o�u��%
x�����c��s8�9��@�����e�n�o�s��x�,��	����(X�ad�uL� �ll�nt�o��p��t��uxp�����P�p�<�h���l5oܳ�������e�Lr��u��Ĵ���k�
n`����e��i�~@�����r��u�@��#�i$�r\b��R	�j����t(�E��X���,�eD�i��T�A4���L�a��c��d��e��g�l,�mL�n\�ol�p��rX�s��t��u��wX��l��|�#��o���i��r�0H�L��Pt���rD�����d��r�<ăX��#�r0����e$�i$��#?.<�d@���T����D�a�Ntx��X�T��d�o$�X��#x�d������a��e��i��k��o �s��XX�
��c��n��s�����XX�0�#��j|����#�v��X0�a����a8�eD�r0��t�Du�S�	T?�.�T"����h|���L�cl�p��t0�x�a�z�`�\��r������4�����j(br�����a��i��s�����~��#��n�?t����r�T��t$��Qj4�����t@��rL����aL�l`�r��� �a��e������D�s�B�����X�i����p��~l�o�Ex�l��n��rа,��'����a�����a��C��a� �����a��e8�h@�iH�o(�,��,�ns�,�tx���s(�?�.�E �e�,�l�,����p{v�X����T�.��a��c��e��i��o��p@ ,������a��f����ko@���o�2x�E��g��m�x��X��n4X@�,,�����pP����������o�sX�(�t�����,P�0�aX�e��o��z�����EP�el�n|�r���$����t�sx�X,� �L��	��a��d��g�Phtjj�n �pQr(�t�p��e��i�w(w#��e�g���e��8��#�e�L�
L��H�cP�lX�s`�t�cX�v��\���tl�a$���p��t�
���e�������r��tXT�.��b��c��d�etjj�l �n �p��r��s��t0�vL�zQ��cL�pLX���n�8�v�~@�cL�eT�sh�t��̸k8�e,�����`�cX�,���x�h��wx�,����'#,'����a��e��i��ox/#�:#�F#��g��sH�\Kz�\���i��tjX��L���sL���tĴ`���e��<�n���$�e����P�D�u���.��a�b@�cH�dl�e0�f��g��h��i4�jT�kh�l��m��n��ot�p��r��s�t�u��v��w��y��z���#�a4�bP�ct�d rf|�g��l��m��n��p��r��s�t�ev��=�"���b@ �n,�t%Ut%�@�o�%�e�P&��H�cd�el�h�&}�&��'�,L�4�(2����s`6L<:�p8����g��t�=�|Zj��s�=��>HCUA����m(GC�F����e�KT�I�oQ�4�eUAU �w�T�(�d�c�pl�a��e��i�o �r8�sX�wqX��g��n��r(r��s~�t#�xo��l(w#��k��s�xx�}��e��l��m��t@�p�5�}�~����#��nX�#�;f̘r�ԇG�a0�u��D�T�)ep�i�)oP�p8�Xt�\d�o���X�#
��a��c��d��e�lx�n��r��s��t�xГ\Xo��E��r$�����t|�g��l���e��XȜB��E��n��sx�I�t,��r����Ud8�eD�iT�ol�sЭ���#0�e0����X��L�md�pа�d��Vp����fo���a��e��o���x{b�{n��\�#��t��������m��n��?��������j��u$�t��#��r\���P��d3��g�(��0��t��X��H�eP�iX�sx�ul�,��,��(h�hp�il� x�X�X��`��e��g��r��tp�w��t�
�#��a@�bP�c\�ed�gl�j��k��l��n�o�r�s�,l��c�l�im �p8�sL����a0�o�?��E\��#�H�h�"p+~�,�4�s|�z�-O�/�P/T��t�-��s0���m0Zox��2L��a��o42�tn4��@jd��i��j�k�r�5R���@6U7[�;E�<�T�e,�o\?~,EH�ehI��H��@�s�X�`�e�]X�v���a��e<�ix�o��u,wa@w����a��f��my��yXP�BX�����i��n�s�t�v$�x,�z�� �ex�T��t�����sL����j�� ��d�X8����R	Ԉ#4�eX�j`�kh�pp�t\�g؊��X`������t�k��r�����������t�����#
�b��c��e�f$�k4�m@�n��o��p�rP�t`�ul�vL�~<�E�d�tP�,\����a�������<���,�s$���H^ap�d��e��o��t�,��X\�n��d�e��s|�wl�T��l��,�g��#��np�#,�m��e��,����i��m��n0�����9�X�s��=��$�e�li,�m<�tH�u�l��PWcp4�j(t�L�
��
��X�d4s�
=��e��i����s�����rl��Lt,'��a �e\�i��o��u�'���k��m�'����a��c�d�s�u�"4(�`(��+�d,�00�x/���c4�k<�qh2�6l(E�;D�sl�t�:��L�ex�k\<E4�jh=�GX�F����d��l��m\H���o�H�D[]�Y����s�\���a �c8�e��fl�hx�i��l��m�o$�pD�tt�yT]X�Fd�n�r@_X�_X�`X��h0�r�b�Xc��Lc4�eP�i`�t�d��\
�gEX�hik��ejT��e��m�n�kX�ox�o����a��c��g��pxo���ap9xp~�s���a��i��o�u�sXu\uX�uXTwLHb�m�oy�	�z�8�r�~Xx~0�e��El�ax�e��i��o��r��u���8���d�sT�?��c��l��m��r��9���\���l�����T��r���f��9X����c��n���`���2����(�a��e��h��o(br��s�u$�#8JcH�kT�pd�r<Ah���@�ld�E�����\�t<P���p�h�Tx�s\�#��r�L��#��n��9�����d��t,�m�� L���l��ml�n��o�p��h���L�#$�r4�#@�iX�ml�n|�r��s��uX�R	\��8�t�d�c���L�a�X����������t�o|��$����(��aL����l��8������P�k��el���E��t���.X�a��b��c��d��e��f��g��h�iX[j`[k�l�m�n��o�p�r s�	t@
u�
v�
w�
yP�z����#?.��a��c��d��f�h�lX�mp�n��p��r�s��t��u��v�#���Qt@ ��r,�tP&O�'����m�*��H&.��d��r��s�w�*�+K�+7
��)P.](2��4:.(�aD�e��s2X4�p�2�$3~�2#<�r�6U`6��P�bh�it7�p8���d��e��s�8�8=�x<'��t�?�	�>����lA��d�.��b��e��m��s��t�,�$BXHC��C�E1�Qj�FE �c�$i4�lX�o`�px�tGl�o����.�G(�aH�iP�o�q?�r~8H�dHL�I��a��o�HLh�r�I��(�l�I�@j��s�M��@�t�O�dO���e��oP�Ql�ck�p�aD�e��i��r��s��u�q~q#��c�g$�r0�t(r~�t���2tHu�<�e�J�(wX\�lh�r����x��T�f�{���I.t�v����#x�d��m��n��X���ԇ���u��~��,D�T��a0�p��XX�#?.8�a��b�Oc��d��e�f0�gX�i��k�l��m��n��oP�pp�r��s��tX�ud�vl�xt�zГ�T�dh�nt�q��r��u\�#�drX8,�X`�a@�4�B�H�#|�i��,���.��b�S����i��r��w �L0�#��a�wt�,`�A��x��s�tx���tt��D���a��(�f0���=D�e��x`�#<�s|�����P�gp�k��l��t��1��ԥ��x�a\����j��8�����a�xe|Ui��l��r��s��u��w���������T��tH�H���4�?̫#��u����a<�dH�e(Vf�fk\�ol�s�}Pp�4�a���T�m4�n��	\�,d�d�i��l�Vp��tИ<����
��e�������a��b��f��o��p��s��ud�U\�K��,���<��	������	H&.0�aH�e�zgt�i�k|�o��s�wt�?ȳ#(�k@�z8����X�dh�nH��H�����`�d��RP�#��f��g���P�Xx���k��e�T��f�t���@�n�E��o�uh�~�r`��<P�̻L�Zm�p<�rH�s�Vt|�,`����e��x��(�e����0�kؼp4�w�#��a��l�p�s���X�a�e�i8�o|�s��t��u�!~h�#��l��p��}������.8����E�e?��+���.D�u\�#�g�m�n��������.D��,�n�����$�i��
H�fP�lL�d��p9����X�n��T`�a��i��l�l��p<Q���j$r��<�\}s�������a��c�e�i�k�l�nXoD�pp�t��wh�X(�X@����XX��p�,��(|� �d����(�nT�rl�#4�e\�oH|�p}o8����nT��d�a(Xe��r��2,��|�X�a�m����	��a�h�i�j��o�r4�s@�uH�w�����~��T�����(������a�o$�ud�~��~��`��0�,�j��,4��\��T���P�i4��\�X8�u�����d�P��d3�|����G����iX���iR�R���e�QT��t\-X�sl���n��#�j��P�eh�hp�i��l��,8l<�r\�s���8�
(�np���0�o��,p��H�i���P�t����,��n�5h��|�s�����g�$�
k��e�t�uh�<E��t�X�e(,�����m#H�dl�e��f��g�j�m�n��o�p0=r�s\"�4"�(�e�!T0�tp ��<�s',8'��T�a|�s�"\�rju�'��*\��r�*wX+�p+����a�e�s��p�����e�+X��r �p,��e8,��t�,,4�s�z�-f3�4X4���a@�bH�fh�gp�hx�o�r��KL5b
X�e`�rl����l5����6�,8��(c��d��n��r�8T��(9#��t9X��e|9T�;�x;�t�:���s�<�v�a\�e�i`�o�yxb@w���b0�i8�sL�tT�z�.x4|�D�eL|��|b(}.X���ȸ.��e��n��p��r��s�t�v������r��tD�t����7ă9L�B��j ���X��e�hԈ#�c,�f<�jH�mP�o ��L���*\�����(�����\��4�s��܋.��=����	X�b��f��g��m��n��o��p�s0�z��xВ�
P�x�������a��nP����؝�����a�@jȞ�tx�#�a�k �a(�ep�i|�uT�X,��<�gD�mL�tܿ��XT����j�����Bj��X�t$�#d�e��X��#?.�a�b�c��d(e@f��gTh\kdm�n�o�prDsLtxu�v�L���h����������as��]t�w4��<�E f8r8�U��Lr��<�E��L<����I.|a�l܅z$�'���	x���#�z$����a�g�o�t<��p�,,�����r�t��%
|��O|�arp��\������a(e4i<s��(.
�\P	
���\mr�w�#Xe�
`e�
��ld4�	�
l�eD�����s,'G�a,e�i�ou�'��I.dAa�k�msv�)~�)��&.ap�)�$*

�+�,00%
x/��
$c\d�e�i�k�m�n�s�u�Bvh0�litu�0��0�0�0E|kdfl�p1C�~2��sh2�P3%
T5�3���s�6E<7�:��:���a e,jXnx@sltxv\<���j�;t�<�̷d@kHm4M�<P�>��=��Pgh�,?dsTC
�GD�G���j(GE�t�F���e�f\�k�oЊu�v�G��Ja�k�m�t��xXJE�\jZT�Y���gix}p0Z��m�\ta�c�eh$i4jHkTl m,nXo�phqxs�t�	u�	v�	w�	yT]��g�lL^E�2��^��r�^T�a�t�5��`TXc� �c,�dl�e�i�n�rtv�d�Lf��e�f��t�v�g�dh,i��ej��e��s4m��n#�.,n<exopa�e�i�o�Gu�o�a�m<�s� 9�o�g0p~�p#�p�uXq�tq
�q��q#�e�nlrX�gl5
�rgkn�r&
����~�s��sa�u<aLi�u��v	�v#DjTw�plxm�n �p8x��xT��.y��x���s�zL�a�eiHo`s�898{X�d�zE�n�{x�{���c�l�t|�P��|e l(n0=r�|��x�|���h.8n�6,
�}9p}E@lXn�}����t<�,�Ppc8���a�cdnpt����a e��h�i�o(	r��u�y�q9 ��l�n��3
�����ǵhԁ9t�9��
��T�eH.Pd��eXi`khlpnxr����l�m0��\�9Є~����h.�r�sT�����T���X�c�g�j�����;
����g�B
X����f�p�r	v��9Ȉ���0��@	d�kH	mP	pX	t��	ah	e|	o�	u\�H
�)~d��,O
8�~����`	s�V
���t	e�n����	cذ�؋��E�	r���	e,��@�����	a
e��o8
r��u$�#
pd��\�X
r$
s�TP�o��]
0
t��H��4�#`
.h
e@�i�
n�
s�
t�~H��t
uȼx�����|
i|����\�
el��
t4����
sLM? .��(�
o$�,�����a��#�
p�U���.�a,b4d<e�f�g�Ph�itjjtl�m�nopr�s�tu�w�y�"��"EXa�"T`t�"��ls@ xm�r(t���
�a4fXgtl�m�nd
p�
r�
tvTDpx$\�l�pt�#���s�Qtl$,�D��D��eML�
e|Lt%s�*����dLt���+Du,Lde�,,�3(2��lf�s�z�49�t�5X�5
�o��b
�7z`6���pp8��4:.�d
g4
st9��8�r�s�9��9T�ld:X<:T
a$
l|	s�:wP�Px<',
lH
oP
t=�8=��@Lp
e�>��X
s�#��DEx
a�
e�
j�
o�
r�
sA���
tpV�dEo�
l�Eh
�E�X~F �
e0Fp8F�
l�I@jtur��sdO` e?n
�OE.Q��pTX�E�a�c�e�f�ik\l�m�no pXrDsht�uГ��rH�T\�x$����h�o�X��E4:.�g�kl$p\r|s�tܚP��E�a�l$�4x�5p��ԛ�o����c8���s�~�S��0kT��8ad�Dt���Ps��~,��hox��pthIu
H����s����eD���j�����E�d�spUt8�t(�T�p�X�s���z8����al,s�xw��$��8e�T t�qe�.Hn�q9��X0�e���Pe|i�m�o�p�s0��L�T��a�}��@��a�� d��m�����o�,����e�k�o���0���rP��̻�<��#j4���i4l<r���h�X�#�a�{d�u���Da�i�m�o�pst<uh�{
d,D��|��(���gD#�n���e\K������s�Wv�L��LX�aX��a(��
���t<��0sDY�PY�e��$t��T�����eXp`tl�TT�l�����s�,���teT���|lX�z���p t���d�e�j�k�l�mn,p8r@sLt\#�"�f,l8rlt$��
0�#s<��e%g�$�� s8'Pe\u�~�'#Hn�'<)=�(��do�r�s�)����T(�-T�l�,��sP/N�-�s�w�/ 0��</mL�H�3k�lr3���p��(p4�4��d<�s�Zt8;D�:��$l�;�
�<x�p�@�/rhs�B\��e|B\t�vT�a�e�o@wX�g0yX���X����pL�������c�L�a�ex�X�X�r$�X�aoT�T��8����z.\a�b�c�ef g0kDmhn�o�pTr�s�tp{v�w��hnl���^�apt �Exo��z�o��XL����hl�Xx�#�r@����e<�E�k�s�t��E�l �5\�L��deH��l��|�`��T��T(ad��<���<oD����Pr�s$���Xd�g�i��ol�<�z��G8���E�e��a�l�m�r�t���L��	�r�����t$|��
���|�aeo r4s��#�z0%?�� r��(tt,<k@e����Hdtlмo�s<�tmu�B�
�S��e�|tT?�.�
n�XPT�a����^j�r�s�	�hoed
\�^e�	�t
�
�oT
4�
kil,'T8aDeXilot%E�'��0bx/Pa�/�:,HL|o�F��`k�o���XJJa�t�\�aXnp�tT]X�����T
�a(eh(i4j<ohr�s�u�Mw$�(��clnؐ�a��?x<����s\�#?.\cpexf�i�m�n�r�s�vx̔�ho��
�Pԕ��P�,���tș�
�wx5b
\�L�T�a�oܛ�X�+
����kd��
�e�o��o$������l�7�����t�Xn����Te`p4�~T��Lf��,H��|ox��@���to�������pL��a�e�i�lm8o@pxt����r�X8�T��(�ao��~s�����t,$��e,iDuB
u#$jh��������epHlTr8�]
���lH��\aDMk`��ha�e�i�o�r�9Ĵ?�k@�����ĵ���e����r$�����kܵ��e����L�a4����b,iXllrxt�u�C2\��$z��X��#8rx�@e0���Ld������dt4���cj�s��L�
el��t���<����s$��r���P�\����a�����l�p���o �8���.la\b�cp d�"e�*fp+g�,h�,i�,j�-k0l3m4n,8o�:p�;r�<s�@tCuTCv�Cw�Cz|*�� �a�b�c�d�e�f�g��ikLlhm�n�o�p�st<vt%��i�%�
P&�)?�'���y�)�.�* ,�rp-�X0���.(e<lDr�0:4m�0X,1
�1�(2��?.`a�i2�`6Tx.�iH&t7�
p8X�o<�<>z�>�a�i�>,D?��F��8�cPee��o�p�tp}�dH�o�H��HL�a�I h(r0K�LL�L i�OdO�4e�[~h[�HeQPlxo�^apt�\Elo�c�e�h k  o@ t(fX�p�44����aPh�h���a�et�i'o 'r�h	�e�E�E�t�j(�j�� l mX�k# s���o��, ptnk4 oX s�`�xoP cq#\�m�p��
d a� d� e(!iT!mh!ot!r�!s�"u�)w�v=�v��� ahv#� rvP� e(w#t�a� e!r!s�wE4:.�|��p!t�{��� s}��}��L�ib�#!a8!o�����Xx�#@!k�\kH!a���X�#`!kԇX�!a\�u������!nD�T�!a�!e�!i�!j�!l�!m "o4"t��c�fX���!r��D�� �w|��"a"e��,�tt
tt#"e�P���
L"k؎�("a\"el"it"r<�~��9d�?T"k�5m����������#|"r��u�"w�LX��"a#d@#e\#f�#g�#k�$l,%md%n�fo&p8'r(s�(t�)u8*v@*zP#�Г(�q�D�#�"c�#a$#e4#r�w|�X�X�<,#o��HP��`���H#�������it#l�#o�#rX��
�#e���@����#�#n���(�08�#�#s���#a�#e�#i`�X$7����#s`���#n8�T $e,$lL$nh$o|$s�$u�$wlb��#$v��(<$iD$u�XHh~�$��(k��T$d$�X\$n�p=�Tt$n�$pqH�X(u������$a��̫#�$n�$p����$a�$d%e�fk%sĬ~�~,p��$o%r��������id�lVc�����$%aD%o,���#<%v��|�P%r���X%d�%i�%n�%sԷ���#|%j����%e?�
��A�%.,���T�%m�%p�%tԹA��X�E�%a�%o�%r&u���@�?`��
h��&r4�D&ed&i�&o�&r�&s�&t'u�~Խ#<&n̲�P&t�#X&e(o(�ap&g��#x&o�&u`�3~!���&lh�G�&e�&o#�
�����&s�� �&t��|�$`���&r����
�&i����'d\'l�{m�{pd'sl't���'a�'e�'i�'k�'o�'s�'u8���+X��������.�'s\�#t'l�nd�����PgD�X�'nd�����'fL����(�'u���'l�Q~��X�'i����� ����(f4(i<(l�}m\(nd(ot(p�(t��Tp�(L(eT(i���qX�� �X��l�#l(lT��xga�(e�(o��#�.hl�(rp�\���(e�����0|�#�(a)l)n����	�(a0)e��j<)o�)r�s�)u�)w�)yؐ��=�0���)t�l�X()n(��X)g`)oh)pp)r�)v�����d����X|)e��,ԫ�������)e��G�)o$����#�)r�)ut�HD��4���)iи�
T��*k*r*s0*z�z���*e���X��(*pl��P��4�`8�L`*e̫�x���L*aX�ET*l�����os�*t�P���*�l*��*�(��0��
��X��X�*g�*vL��0���*aX��*a4+l�*r+t�+u��#�*at�d��+a��(�#�a0+r�+a@+rL+ud�Xx�X(bG8+e�#��rX���+a�+c�+l�+v����X+a�+e�+i,n ,o8,s�,u��H����~��K��X�+s���
�+k��
��#�+j�+n�+o���@jd�����,�k,od��#,m0,p�� �P,kX,l`,pp,tH,T��4����h,o�
X@
#|,n�
L�,e�,n�,s�",4��<,,E�H&.�,a-d-eT-i\-nd-o�-s�-t�-u�-v�-z�Et-d�'E�FX�HX4-f�i<-lD-nL-r��XI�I�
DI#�LL�PX@Rp-u�R U�
(TTx-o�-p�-tXU��UCW,�V���-e�-rPW��W�$��X`�-ox���X�-o�X	4.aL.e�.i�.l�.n�.o,/rP/s�/w�Y�|Y .rY#(.aD.m�ZX�]X�jfh.rt.t�_`��`.a�a�	tc,�b#|.j�e<�.a�.o�.u�eX�g�Hh�xh��.a�h�LjAPi#�.g�.m�.o/p /r�j�`k��k�/e�k?l�l��/dHm���e@/iH/o nXxnX<oT$kep/ix/l�/n�/p�/t,p,lpH�p�/o�pqX�/a�p�|q��/a�/e�q~�qe�.Pu�/a�u�
(u���/r@w#?.P0ad0cx0d�0f��k�0l�0n�v��
�/a�0d�0el1ft1i�1k�1l2m<2o�2s�2t�2uخ�,w�\0nw7x�p0t�'E�xXy��y�dz9$z�0d�}T�}�0a�0r�0s P�XX���0e1l1r$1t�ku@1vd1zl��
��0nԂL@��ă��1v�E01rL�(� ��81eX1iD�P�XP1n8���tԈX�1e�1n������1g�1t��t���P����1k��H�����1l�1s���4|L@�,�1s$�k�1a�1u���L�L2e(2i42o��ĩX\PX 2n4���#X2gl2nx2o�2r�2vВ��e��2����d2d��8��n�2r�����O�2eܔЕ�
ĕ`�2eP��4��2h@5j�2t�����
�2i��TL���2h��#���H&.83ad3e�3f�3o�3p�3s�3ut��H3ox�#,3gP3u����M,����n�#X3e|3m�3n�3x��H�j(���3cd��\�E��#�f�p�3rЬ��أg��,��k�3aT�l5od�T�3ox�t@�Xl�OT�#4cT4u���
4ap4d�4eL5fl5g�5i@6k�6n�6o7r$7s�7t8u�����p�#\4a@�d4a�4e�4s�4utzo����4e��X�4n4�X@�T�4c�4td�����4e���|���4e��i,�#�4d5e 5k(5o85tD5u�d�d����`���T���05s�����X�5a�5g�5l�5n��TT5a�5l�5o�5r�5s��H,X�X8��
��,���5o�T�����\4�\�5t$�#4:.��e6o6s��,����5n��p �6l,6t@���z$6aD�C@��86jX6nh6o|6r��$�j�
��#`6m�X��Gt6ip�x��L�6o��#�6c�6d�6g�6n�6p�6s7v������P�\��#,|����6e�h���6t������L�z7i��T	4:.X7cd7el7l|7m�7o�7p�7t�7wx�/X�#P7h��,P�<��?,�Lt7i��X\�#�7o���
��E(�el�~,���7id����X�7p��T�7a�7e�=h8r��,�7s��<���# 8u����08t���x8a�8b�8c�8d�8e�8f g�8l9nX�oP9p|9r�9s8:t�:v�:x�:z���8s�F��lL� X�?����8e<�E�s�L��X�.�8e�8u��4�#�8nl�����8s$�X(9e49g<9sP���9l��#9e<����]H9c���X|�ad9rl9tp�K$��\�X����t9a�9imu?P?.�9c�9e�9f�9h�9i�_o<mp:t:u:y�
��0���,�9.����#�	(���E:k�# :e���,:hT:i�^j�:o\mr���-Ȫ��\:s��#d:n�	p:e,	#|:r4�d
��
kd#�:c�:f�:p�
���:a;e0;i|?j8;lL;o`;rx;s�OLX���#;r$;tT�f���;wP���jl#��	D;u�X(#X;gEL Gp;i�@u�!�h#�;e�@i�;l�;t�#�	�p� $T�;e�$?�.�;n�$��;e�$9�'X<k,'��	�;a<e��i8<kD<o\<s�<t�<uخÄ)Lx/
<s�6(<t�64B�?��0<s�F#T<p�vtJL�N\p<cx<p�<txO�RXT��S��<e�WxDU�<r�Y#�\=aH=c�=eX>fl>h�>i�>j�>k�>l?m?n\?o�?phq�?s�?t�@u�	yT]# =a(=d0=g��p8=r@=s�]6^�L^��_O,`��``=h�=oxaXa	X=ap=r|i��b~`bOx=l�=o�=p�b;�b��b��=e��XcX�=d�=i�=l>n>r >s4>t�d$�j8eT�=l�f�Lf�=g�f��1vT���g>t����gE,>j����h#@>ed>r�hkH>e���i�x>o�i,p �j#�>d�>j�>mXk,�k�`mX4m��>a,n(�>a�>eXnX�nXxo�>a�ep��o���>g<�s�s��uP,?e@?iL?o(vA0v#$?d�vG�v#8?j�v,8x�Tw��T?lt?o�?s�?ty��?rHyXDz Pz��?t$�$�zL��a�?i�?l�|T}M�P�Ha�?oTw,��z@a@@eT@h\@o|@rX�y8�#0�k@p @s�Ht�����9�����T(@dL@eT�#0@r����X�#p@nX�`���h@g�2�@i�@o؉~����h.��؋#�@m���@aAexAh�Ai�Aj�Ao0BpPBr|Bs�Bt�Bu�Mw$�#��c�@d@����R�aAn\�#Ae4An<Ar$
s\At����PAe��#HAi@�E�D���dAe���lAi�Ao�AuX�#�O���Ae�(AL���At���As����#Te�Ao�Au�Av��a�Kg���ԫ�,�~Bt���Bs<���Br$�P$Be���x���<BdH�DBe`Bu�#|�?`��hBeL�pBj�Bl�Bo�Bp�Bt�?��T�Bi(�?h����Bp��#Ĵ4:.`���Beض��l�o$���Bo�X4�cCm<Cr�E$Ca,Ce4Co�xL�X�����L��hCntCr���DCeH�l���`Cs����Oe��t�Cil���Ce�Ct\��t��P���Ce����Ctl�L8�Ct��#D.Da@�c$Dd8DedDn�Do�Dr�Ds(�t$Ez���t�p�PDeX���,Dn�����.�#DDnT�z���\Da�Ds�Du,�t��TxDm�Dp\�X��L�Dr����L�:X,'���Di�Do�FX�\��4:.�Da�DcEl<no��tT]L�`Eh�aXa	EexoP�t���.�Ea�Fb@�c�Fd�He�JfdLg�Ph�Litjj4Mk�Ol<Pm�Pn@RoSp�Sr(Ts�Vt�WuXvxXw�Xz�#��Qt@ �Er����Ea@db�Ef�Eg Fk4FmPFn\FptFr�Fs�Fw�Fz�*�,�Fe�,��,���Es�,#Fr41�X0��Fnt7�`6��,Fix<',
lP
tp8��@Fs�>����llFo�?#A��Fe$BG�F]�FpdHt<Pj�P�QL�Fa�FexQX�%l0V-�U���Fi�T��Flq�$Ga,Gn4Gr�p���FaDGe�vi�Go�Gr,Hs\HudHwpq��sX�t��w](w#<GedGn�Gr�Gstz�z��\GetGs�z�GpԹ��{��1v�}��Gp�Gt�}4~AX�X�Gm�Gn�Gpx���,P��ԇ��GeHiHoHu�����8�����D���HiD�T HeHHpTHt���8���@Ho؎T��zt�zxHa�������pHnX���Йa�Hb�Hc�HeIlIn0IpDIrhIs0JtxJu�Jv�Jw�JxxQ���Ha$����Th�Ht���E�Ha����Ia̫X�#(Ia�oȳ�4�~d&i��X���<IoTIs���`Ip��X���Ia��c�Ik��l�Im�Io�Ip�Ir�tJvJwh�x�Il�^
xhbX��In��t�����l�#�Ia�Ir���i$�����Io(��
,��Ja(Jil��
���LJe`JolJs<�Xl�XDJrԫa(�#XJv0��]tT�~��o4���Jr��\�X��kX����Ja�Je�Ji KlLKoTKrtKsDLt��l�#�Ji�Jnx�ul���#�JjKn�b����Kkh�`8KaDKe�|����0Kt��,�� ��dKalKo������\	�Ka�Kc�Ke�Kf�Kl�Km�Kn�Kp�Kt��  ��f�8���Kr��l����t�u$�L�Ka�z�D��La(Le4Li<Lol�X0k�n���x�? Ll��������TLi\Lw�,�����xLe�Ll�Ls��X�Lrl�,|��Llp����Ls� �= �Le�Ln�Lt,��L�Le �*����LfMgMn(Mtp+Xl5z4��Mg�A��@ Mj�XT	`Ma�.i�MllNnxNo�NrOstOu�OwYXxMa�Mr�Mu�Mv|Y0�Mr,$��Y���Md\X�tbL]X`]|�e��MaNe(Ni8NoXNu,wz�e���Ma�Mk�MpNs�y~0f�8f~�fXNd Ns|fl��(g,��x�g��0NnHNph~@h�Hh��PNc�hOxhTdNaPi#�f�Nl�Nn�Np�j�(k��kX�Nb/e�Nl�Ns�k��kbl~HmX�Na��eH/oOudmX�Na��n�m��n�lp�8�o<oTOl(OpHOtq��pHl�qX\Oa0k�n|qE4Oa���qTOk�lTtt#lOiPuL�Oa|��(u���Os@w#�Oa�On�v���Oa�Oel1f�OiPoPu,wx$z��aX�#�Of3lЁ�Ԉ#�1n��#Pe4�X��XT��x�#$PfTPr���,Pa\PidPopPs0�ԨX��#�pd�zT��Pa�Pc�Pg�Pk�Pm�Pn���	xPaQdQeXQglQi�Qk�Qo�QsRt��0l��p����X���Pe4�,X�Լ,@�Qrп�,�#Qf0Qn@Qr�Ed��8���8Qk1v��H��TPQl\��$�#dQm|Qn�����k@�P������T�Qi�Ql�Qo�QpRtP� �Qa�Qup~h����Qc<s���#=r\�TpHl���ReL�?4:.��X$Rr<��\�8<�E,Rt����	4RelRi|Rl�Rm�Rn�Rp�Rr�Ru�Ry0��4������tRe�Rid�#X�j<�X$���Re����X|�e����t9amu�
�RrD��Re8At
8dX0Sc@Sr�
���RaLSehSi|?j�Sl�So�Sr�Ss��x���8Sm�#XSmp��l��`Sj|Sn�So��p�����Sa�?i(#�Sr��!XL �Se�Si�So�!X("��\�kh#X,'LTa� �x X�Ss�'�Td�'��Ta�\T\TadTe�Ti�TlUm�Kn UoXUp�Ut�Vut�yT]XcX<�f�Tl��m�Trxe\
8eT|Ta�Ti�e��f�jk�Tn�Tr�k,�l�p�Texo��Te�Ti�px�qX�ettUlUt�s�Te�t���~Tw�0Ue8Ul�wo8xX���zE@UcpUr�z��HUaxUepHl�UoT{��{�p}��UoЎr~8lr8���Ur����Ua�Ue��hVi<VotVr���T���Ue�Uk�UlVnԃ�0��\��H��ЄVg���XVj���`���(VdX���0VnPVoXVul�����P�~0���`Vn��hVa�Vo,H
����Vk؋T�q���Vl�Vr$����Va8JcWgWkWn�����VaWe$Wh4WoPWr�Ws��uЎ�,��h����\�#?.�T�����,WeDWp����l9tH����apWe�Wi�Wo���x���hWd�We0Li�Mk�0h�X,H�@����Wk�Wt���L��4���Wd�Wm�Ws�Wv�L�X|�8�������We��`XelXi���EXn<XrPXtD��HXp���0Xs8����L��d��XXs�X`Xe��<I�����Xo��E�XrP��Xe���.Yal]bt]c�]d�]ef�bg�bh�biejTek�ellhmxhnPiompHmr<os�rttu�tvPuw�cyP�z�t��#|Ya�Yb��c�YdZfTZgxZh�Zi�Zl�Zmd[n�[o�[p\r@\s�\tL]u`]v�"���b�Yl@ lYn�Yr(t����$��Yj�#���Yt�E��E���Ys�EI�Ye�%�t%�Ye4�o�(8l(#�Yt�'���Ye��mxdoZv��F�*���d4Zf<ZlDZt0�]4+��+�H,,��LZalZe�,��,#dZnP.X�.LH}�(2���Zb<se�Zf�Zi�Zk�Zt�Zv�3��Zs����3�4��Za����5U��`6�P�b[e<[p7]
 7#[n([r��L7�� [e���7k4[a�slr���	X8#P[ap8��X[a��d�[g�[s�[t<:mP�(x<'�[lP
t�=T|Zj<>��>���M.�[e�[llFo�[p8�r�[s?X�?�@� @��[a�@zA���8c\o \t`C�E�0\i8\r�E�F��FE �cX\ih\t�\u�G\
xIx�HL`\ox\r�I��\a�\o0�,�I����؋#�\r�q~��\lJ���\a]l]n�I�\a]i@j(]l0]o<]r��s0��ؐT�\epJXPK� ]o`��̤$�KX)gL$�N��M��D]tPdO�X]oQz�ck�]a�cX�pl�viX�#�]b�]c�]d^ed^fx^i�^l_mT_n�_p�_q`r`as�atLbulbv�bx�bz��$��0���]iȜT��E^n,^p8^rX��8���$^l��ED^sd��X^tH��T��P^o���p^f0�U���H&.�^e�^l�^s�^t4�]ԥ��P\��ĬX̫#�^p����^ayd�^e(Vf_iT�ol�s,�~��#�^l`ym0�ODVn�
���_a,_m4_p�\z��z���ȳ#<_np_u���D_a�_e�k�_o�_s�X8��H&.�#x_i�_n��P�k��Թ
�_o�T�_p̲t��_t�#�_e4����_i������{m���	�_aD`e\`k�`n�`o�`s��t@avTaw�4z
���0`k\�#8`n�@<x`ed���P`l�`n�`r�`u�`w$AXt�(�AP�B�BTE���k�`a�`o�E���s�Q����`m�`pat�R~��#�`ear�S`(���^a$ae0ai����?a.�T�4��@��8aaL��T�Lat���papxatl��T��xga �e�(o�arP������aa����aa��T�ae�aj�ao�ar�s0bt<buDbw���l�X�al��(�X�� <��$�Tbs����bg�Xbn$��$bi���4��T����P&t�XTbe4��`bi|bl��\��8���z�
�boXu#
�bd�betcj�cl�cn�o�dp�dsetTc�p ,#f�"�bdcs̲t@czt((��cp$ct�(�0ce�(�`*@*L8ce�*�|*��Lc��M(4MT`cl�,�hck4�s�cv�cz�-�-�<2X�cv0���co�2:4���a�cbddXdeddgp�h�di�dk�dr�ds�dt�dv��|���4X�clp4de<dsHdu�4�x.�4�$de�4T0dt�4
���4#Pdnl5�xdsh�w�5\pdl��2�5#�deh6R@6��do7$7��7��+
8;��:���dl�<T�e�dp�?�|B���t�@�ds,E(ea8eeDeoLeu�E��J��H��0ew@RX�W]�X=�eex_�_#`eiT_��hee�]#ten�er�es�`��et`���es$a?$Ina��eexa�`a��et�vT�ea�fe(gi�goHhux�@w���ec$fd0fp8fs\fttfv�(�x��fi�{4|X�h.LfsTft�H%d|,�|ȸ.lft�M2}8��fiX���|fd�fe�fg�fm�fn�fr��sgu0���O
��fd�fr����h.�z
���9ă��ȸ.gags���(�~��t
X�Ԉ# gcHgdPggXgjlgm�gnd��4�\�Tdgs��>�Xxged3P����E�����gb�gd�gk�gohphs,ht8hz�����h.�ga�ge�@�R��J��a�gd�gfX�PRP���he���	�^tX��� hsؕWX������@hc`hi�,]М�Xhh�k�a�T�ha�he�hiioDiu��T�#�hm�hp�hr��X��̾�,�#�hc�hm�~$���heipiv���h.�TC/��x����il0it����(ieT�%
����<im��#�ib�ic�ieLjgdjk�jl�jm(kn`ko�kplr�ls�ltl�v�lx����z�ilL��it��d4��<�E�if�ikjr�s(jt@���ie���	x�#�it�����jp8�Xjs���\��� jj8js���t��G|���Dje\jr�j��pjo���4��	�jn����xje�jm�jo�jy���L���؝$�I<����ja�jg�jp�js܅z���
��l��p��T�jlkt��E��rp�G<�Tke$���kg�iDkt��2,�T<kb|ZjXkr<�P��tkg�i|ktl��|�8�j���
|�a�kb�kd�ke�kg�ki��j�kllslz��v��|��#�knȸ9����k.�|X�l�����R	�&�
<z<lo����ldDlgLlk`lstltmu�,Ox%
�S�8�e�Tltdplla�lo�lr�P�P
��.�lj�lo�lsT��le\��`��lel#�lr�<������sd
��
zmi<mlȇrx�l��msx���(mm��0ma,'Gdma�me nixno�nu�'��ma0b�md�mnx 9�Ss�'�|md`(9\*94*���md�/�x/���ma$c�md�menfngnl�0�h0�me�00�pDtt1�13%
<;��:��nc4njdnt�<���kHnpPnt=x�-E\nj��,?�pnm����F���e�nl�nn�no�np�ntЊu�nv\H��HX�nt�I�(J�Ja�nntJ����aL�K���noPL�Yoboi(ol�Y���0Z�ostZE4oadZ��\T
�oa�oe pg,pi4jlpl�pm�pn�poqp�as|qtH�y�^��omT]��xol�n��p�or�os�^��_#,`XXc\\�c�od�oi�op�<rt�ov� �d��.4�Xdh`p�t���pt��#ps�h�pejDpl�nLpsTptpk~�l��l�o��pb�pnxo�\pa�peT(ip�`p��pX\u��s�po�uX�u��pa�po�pu�vX8wXTw�m�pn�x��x���pg�z]�n,qr�z���pa4qe<qiLqopqr��uT{��{��|�~8p}EDqoЎr`qt@~�T#�x~hqu��E�qa�qe��hriro\rr|ru8���qa��� ��qn �tT�e��c�qd�qn�qr���Є~�qtȄ���ra��r�t�X��0rn8ro@rpHrt`�~l����
��������Mk��Prelri؉�t����tre�riX��H���rt���rase$Wh4sidsjlso�sr�Bu$�#�ra��cd�r���n�rr�rtЎx�\�Xsc$sx̔Bso�\
��Xܡ�X,sjLsmTsn\st��X��Xأ����#�smHp�sr�sv��X\��sd�sg�si�	Ԫ}��ԫ�H�G�se�so8��x����ss��ԯ���sl@����slts��I4�#@tbTtidtkttm�tn�tr�ts�tt$���L8teD�\\��Ltf8�t�lL�����lte���ti�tsx�������Hpa�te�tsX��0��|�B4�8����P���t����te��un|�]���ut�!U�ul(���uahucpudxug�ul�um�ur�ut����(ua�ueTvi�voP&%
�����������9$���ue$B��f��m��E�ur�����ue�ug�ui�ulvnvr vs@vt�����0���T��tx��vs������,vt���|r������8vs\��l���Lvexvj�vk�vl�vn�-���\pvz���T�%
���vd�~������.@waH}b�}c�}dX�e��f�g̈hԈi��j��k$�lL�m��n��o�p$�r4�sL�t��u��vxXw؝y��z�"��wd�wh@ wn�wr�wt���,waxbxc�xd�xeyf0yg�yk�yl�ym$zn�{p�{q|r4|s�|t�|u}v }w(}z #X	�28$p�#���we�Qt4M~ML�wa|L�wt%�ws�%��%E�wnt%�woP&�L�a(xhpxi�&��&�� x.@xaHxeXxt�&X'�8',D''Pxehxs���i�(l�'��	xxa�xe�xj�xm�xo�xr�xs�xu�xv�(nl(#�xtȃz�\|0)zd)��)g�)T����)��*�ya<Zl yo��s�*'<+�H,z,��(ya`yelyotyr|ys|�m�,��Lyt�,#TynL-Tp- �-��yatD0�X0���ya�yiDr�0(2��yd�yo�2��4����7k�yjzlzo`6���ypzs�7�Ԯ�8��7Tzpp8	Pzadzd�ze{g<{kX{nh{ot{s�[tX8m\zcl���8~|za�zo�zr�zs�8��za����~�`9#�zot9Pd�?x.���ze�9T�zt̾��9#�zc�zl��?��:#�z.<:T�ze$
l{s�:\@Ip0{t���:�({a|;�`SaP{l�Sw�;P���T<X<#`{rx<��{lP
tP�x�>#x�c�>���{a�{i�[l�{o�{r�{uD?��?d@~D@`�{e�@=AL�AX�Ak�{aA���{d|m |sHC�P�.�C\ut�Fn�F��,|aL|eT|id|t(G��GTxIX�HL\|ot|t��JX�I||e�|h@j�|r�|s���0K���|e`L�L�|o|L�	�|l�L��M��|f�es�|w��(O�dO`}a}oXO8P<P�	�P�4}i�PC@}jt�Qp}exo<��|}jW��X}t�T�d}rPW��ck�}elf8(f#�}l�p�}aT~e�vi�~o r�s0�u@�w�q	pq�}tq#�}a~c~k~l$~r@~u�q��rDs�s��~f�t��8c8~i�2t�t�u��w���o(w#H~cp~k|~z���x~h~s8���9���~fX�#�~e�~l�~m�~n�~o�~prvH��0����~ix������a�KgP���~iX�?<j؆��dd�ԇ�Pa�e�i�o�u�q�l���<e��Ddhmxn�t���h*�����pg,��O�e �!	�B�����j(��8����e�)l�m����B�����iD�T��a�l�m0�p$�t �H��?|�k�a؎��4e��XH�i��ut�kГ\��n��tX���H�aĀb��c�eЁf�gP�i��kԂl�n��o��părL�s�t��u �vd�x�9��X��e�� ��FaxA��Ѐt��'؀s\����t$����h��0�gP�k\�ld�ml�nt�p��s��uܚ*e@�i`��b���H�hԛt���Ȝm8����o`�x��x����e��l��p؞5������oT��0������ȁf(�w8�#܁s���a�e�l �o4�s���`�#�c��,�.�� �=(!��X<�i���D�dp�eP�g|�t`��4��h�r\��j��s��%$��8�����o��s�m��t���E��rp�9���̂d�e�i��p��T0��@�ȴ�ȳ#�d<�k����aD�eX�kd�ox�s��tt�]�RP�m���0���wP�Tp�p���T�f��m,�4�)|�T��h̻t4���4:.�R�k�����a�e�g�o(�s��t�)�\�#�i��v�78�w����n�����(�� �l@�p�o��#8�o���	x�a��c��e��h�}m��p��s��t�uh�@��Ȟt���l���e���0�0$�P��aT�LЄa�e��r8�(�a\����#܄l`�����$�a������h,�i@�oL�rl�sx�u�����8�n��~(�#h)p�� X�e����\�e0�`�t��z��4�T��oT�����k��m̅r؅s��z��,�#��a�������ąoX��t�����\�eP�b
H������s��E�n4���eP�i86�@6�0�j���8�k�XD�n��f\���\�c�~pP����#t�c��gĆs̆uX���|�a�e�l0�o��r��s�t�u|�$��L�M�x�E�d��el�#Ԇn�z|�2�?8�,h��l�i �o(�ux���X��L�eT�l\�pl�r|�u��o������<�	�d�d|+g`�~�������a��u���P�8����i�����eЇl܇m�o��(�pe��z������a\Lw(�,����#�u��kH�al�e��l��o��s�����4�rX�#<�aȚp0������X�a��#`�n���x�a(��#��e��g��o���4 PĈe���
�#$�aX�cd�d��e(�f4�g\�j؊k�mP�n܋o�p4�s`�t��x���l�g�im�Ѐt�i'4�s���@�t�L�hp ��I.x�m��sT!��! �#��#���a��r�"��g̉k؉s��t�u���X�#Tĉa(��cp�t�(�<)��(���o�)��*7|*����+~�* �tp+@�o ,X`M44MTH�a�,�P�k��m��n��p��sЊt��z,P�<P����a��vt�=\-9S��-,Ċt�UD�-C��a�-��-ĥr�s�w�/,P/T�p�/��iTv�83�3���a\Pi0�p�3z�yjd4p4<�a4��D�d��f��h��i��k��n�rL�s�Ztċv̋zL5��5,���@6���a�6R��aT�R�Fh�|�8,8�ԋb�Zm�s��t�9I8:X�:T�:���a �i�dl0;X�=XDDe�<(�eH�oP�w\?�,�uxA
�@X�h��r|�s��|Bt�a��l\t�B(���,E(Y#�0f�n�r�X��
��a �ep�i��l܍o8�r��s�u�wPky|;bd[���kHC�\���m�t \2�]X8�i@�mP�nh�px^_��_�T_��H�e`�s�_�_��bX��nddM�c��|�g�e���Oa��g��s�eT��aЍe�i0y�8f9 ���f��ȍvPiX��e�n�o$�p|�r\�u�ioDk�(k���t`k�i�k���Nbdm~HmG0�aP�ex�i��o�m�`�ph�s�5��6�4nB n��p�jxn�<oL��e��o܎t�oH�p���n��o�p�yS�q�	�q�Ȏa|qEЎa�e�r�q?��l\�\r�t#h�uPuX2i�vk@�a��e�i �o@�s@w,T�d\�gl�t�xL0y�|$�|d�rP����gX���x�i��k��l��o؏r�s�t�v��LԂ��w����ďnă��̏o�}�L���m��P�X��e ���iԈ���X0�fn��tP� 4�8�h�Tx�a�e\Pi4�ol�rx�sȞx��tx�#l�a��c��fĐk̐rܐs�ux���&�����aT�X���0����8c�6t��`�MX�X�d�p �s(�tP�U�ih�?��D�\������e��XH�g�7lP�r��XЬX�
E�F��X�z\�k`�od�T\�c�zh��u�z��i �$�#��s������a��b4�e��fВg�kD�lP�m��n��oP�p��r�sX�t��uĕvؕz��z��j�o�� �E�o������aT�r<�E$�d\�gt�n��s��u��v��P��U��r��T��td���h�s���T��4��0��#��a�����a��dȒsH�����|��l�o��r�s|�w��X�]�����#�0r��T�a,�o4�r<�u�����P`�X��,la<����ll�p��tԅv܅z��k�yj|�lL�cܰ�$���ȸ.��d��g�o�t���<��̓aؓo�r�0,�#ēa��|�(p�L,�|Zj��a�k�p0�sD�tX�,D�c�i0f`�(�h��4|�E<�e���|�a�kb��de��h��l��n��r��t��v��w���m��a�s$�y�&+
<PK����.ܔe�g�i�o��smu�X�X<��������ePT�p,�tD�u �_o<�r"��Ah'x�l���L�a��e��h�^j��o�_rؐ���X��t�zX	,	#��fd��
����s4`Еe`�
�
X�
�aL�el�i��j��l��o�rd# �a4�g<�m��rD�sPx,�nH�X�@����#\�nd�x��\��lX��n����x�gp�s�L��	��a(mm(#Жfؖm�n�p��sX�t�X���X�%
x���e�L G�a� �h T�m,'�Ta0nu�\|�aؗc�e<�gP�h\�i��j��kИlT�m��nԙo�p��t,�ut�yT]����a��d0=g��m�n��p�os��t^
_�|_BT`��bX`bOėr�`#̗o�uc�XcX �c�d<�fx�n�p�4�p�\����t��#$�s�h�0�e�i�iH�ijXl�mt�n�k��kX��j��k��t`l��l�4ma�Gaxn��n#��l,n��eȘi�n�xoT�a�e �i8�o�o��p��d�e��g�nPfb�p�	8q~�q�,�nlrX`!��s4�k�s#<�a�sH�ap�e��i�tUtt#h�d��e�t�u~u#��d�u��pa��e��o0v?�v�ęrw��w�Tw��̙c�f(�l �p��r�v�wX�y��z�z#,�aH�e<qiX�lp�o��r�zX@Uc@�npUr8{��{@}:}�P�i~���tp}Ed�o��rt�~xx~���ah~���
Ța�e��i�o �r��u�y8��ؚf�k���
�I.T�?��k��v�X��0����aa�kX	t���a؋T<�rD�s���̌����t�a��h��o�rP�s�Bu��$���l�a8Jc��m��n��X��eؑ���X4�O��n�����aԛiܛo�uD��X����0��i�����l�v��JԫPH��0�aD�u�+Ld���(�s�\���<�gL�pol�opp�tĴ?�.`��d�e����edl4���|�bĜeМi �k4�nl�s��t��w$���#H�����n\��ȸ.�a�d�e8�t�������s̾]���h.��L,�t�T�s�E����pNaH�cP�i��,x��P�`��X�a|���`�t�X4���x�acj�
s��X������i�EhCn̝r�����e�����ĝa��#�i�s�tX�8(�tP���o���.x�ad�b��c��d�e\�f��g̨hԨiȃjx�k��l�\m��n��o��p\�rd�sܰt@�ut�v<Pw��yȲzH���#Ȟa�c�d@�eT�ft�g��k�l@�n�p�{q0�r��s��t�ev����$8@ ��s�tMA|L؞t%�s�'jP&����t���'���v���H*� �o<*�(�t�)4�s4+�*��L�l��rl�s�w�+�,DRl��o��rL-,p-��/Gd*�����D0�X0����a̟eԟrܟw�0,�1��1 2��c(2���a �e4�st2~������t�2#�n�5A�4,�tp8��H.d�a��d��n��o��s��tX8#\zct�l�8x�8��8|�a��o��s`9,�9��Q<,x<�Рe�o�pTtt�<ܠe���=�$=L�=�=T�h�r<�H�>���a8�r�B�P�eA��$�i\�kl�s��t�B�BH�tC�<P �C\d�h|�t�D��E,E��i8H��F����o��p̡sܡtp}dH��o�H�I��HLԡe�r�I�J,�I�a$�e@j8�oD�rX�s�J
�J#�r��X�K��0�mL�M\�
e|LL�tQ�x�l��o��rh[��\E&o��s�_�<_��tD`H�pkȢaТi�o�r��wq,�#�va�s ��	X�X wpԇ�t�XX�#H�cP�d��eT�g��i��l�m(�n�p��q$�rD�s��t4�vd�x$��L�eh�i ��0�#`�ax�uC�ܚ���E��g��ḳl�r�s��H�����r�~�����sԛ���aܣop��ԝ,����e��i��x�I$�t|��8����l,���a@�oL�r�������8�v������.p�a|�d��g��t8��܁s���	��E��npUtd���T̤a�Ud�k��o�s���.̫#��s܍���ؤo�r8���
���d��m�����i��	H&.T�a|�g�k��o��q��sĥtܥuȳX`�s�����t�Hh�aH�Tp�rи~P�����r�����t�X��u�T��u|�T|Zjԥwh��p�,|?�4����j��#L�a�kX�mt�n��p��u��v����a��e��kЦo�s<�u��9h�#D�kL���=��*T`�e`���h�t��Xd,��,||��\�#��id����l�`n�`w�@(�,����Ȧc�n�o�|s�Ct<����#�g��8�P���i �lhDm0�t�S,(��(�a��X����hd�l�}mXo|�p��tp��t�iP�o�q,l���a��e��o�����~@~�p}E��t��#hlT����eԧo<�u���v���	0��
|�#�n�����a�Xh�i �r�s�����$��@�n4��(�e����P��d3��(��X�kx�a��l��o�L��#p�th�a��i<�X��e4����,��l��ep���#��r�
k#	�d,�eP�j`�kt�l��mĩn��sd�tp �4:.�d� �,$,�#T�l�" �k<�s(���t�-��,�H�s��z�-��nt1�0��l�i0Zo,��Ԩ#��e3����i��p�3j�~4#��r4����a�fh�g�hD�rL�s�u��w�1
8,�<�f�i$�l0�tX>l�>z�> �a�?�D�r0��|@2<�aT�o�@~�@��@\�a [rhs�X���a|Y��rY#��a�vz�k��aT�,��#�.��a�d0�e��g�k�l$�mL�n��o��pЬr�s8�td�up{v�:x������a��e����s<�E$�dL�fT�ip�r|�s4����������\�t8�Xd�s�����p��tl����<����e|����eثl��̫n������īs|�K��'4������e�i�m�td���e���L�U<��0�vt�+
D� ��8�r$���@�d�od�s��t���p�o�X�,,�Tx�a0�~��a��i��|�a��e��pȬs������������t9a�eмo��s��D��p�t�S�8�eP�mc(�lD�t�=�����0�h�^jX�o�sl	X,	#P�l�
��X�dt�w����i(�
k��a�e@�iL�lԮo��r4�s�%
�����hd����cحf�n�rL�T~��8Sm�t8F9���s������c<&n(�r���1v���p�sl��4�n�\l�a��e��iD�ux���d�m|�n�9�������g��iЩvBL#�3e��9d�#��eD���i(��Ȯl�n�p�~�g<�L ��e00�!���c$�d,�sh0�T!�h#��acL�e�zhT�u�#؋,,'k�\T
��a��cܯj�l(�m4�nT�ol�pt�tt�yT]����m��n��p4_�@_����a�`
̯oԯu`b�c4m��e�m?xoT�e�i�p��n8q��q1�q#�e�s���a�uTH�eTv�0v#@�eTw(�ld�r�yX�z���E��a��e��i��o԰s8��T�e��chl�
n��
X��İc̰n��7`�9��]��T��a�e|�h��o8
r��u$�#�TȺe\�#�r �s��]
4�t����,�a4�#|�d��e��i̱kܱl�m��n$�sH�td�ul�z���t�aH�?�T~#��.�0��e�����d\����l8�t�����0���Աp����������o�tL�� �T�jd�X|����a<�tP�,`��4�a4�~cjX�sl��@ct$�xP�u����a��eL���`�r�(�e��rp�������e��#��e��LP�kԲe����t���.T�a��b̸c@�d,�e�f��g�h$�i��j@�k��l��m��n��o��pL�r��s��t��u�v<Pw0�xP�yh�zخ��#?.��aT�bl�cȴd0�eP�fp�gx�h�ri��k��l�mX�n��p�r��sȷt�u4�vx�z@�ð!x@ ��l�m�n�p��r@�s�qt�"9�"��#��#���&.�d�s�Qt,$�x$�,�t�D"�D�$�r�$��$��8�tTTt%L�dd�e�%WP&���a��e��i��o��t�&TXf��t�&���e�i��'�'K(��'����a�efi��m�r(�vl(���.�n�s(z�(�~d)G�a�� �)X*d*��8������*�d�l��4+`\�u, P.k�/R�0OX0����e��lDr,1�H}](2����bеe�f�m�y�2&ܵn���3��4�؝��s�,`6�P�b4�e@�iH�p��AD� �t 7#(�st7#�7jX83p8��P�al�kt�t|;��=��h.��e|Zj�=���n��9�=���n�>��{a��lȶrжs�?AD@��@zܶt�@��,OA���b�e(�gD�iP�muoX�s��txB�$B#�s�B��Bt�B0�t�B#8�eHCO�CGd�t�D�p�e�D?�.�
nEM�F���a��l��p��t�F��GHdHt�HL�a4uu�IH&.�i@j�l��o�sPK�̤�KX|L��pMX���M���.,�s�N9dO�L�eX�i�O��OED�nP�xPS�P�`�f@}j�Ph�iQk��e��o8XA�W���t�T���s��tpX:�\E�t�ck�e�h8�o(fX�s�gEDg��t�h�h���e�t0�u�iZ(�rh0L�k,�p
p�a��e��i �j,�oԼr@�s��u@�w$�yq#
(.��aԹb�c�d,�g8�lX�nd�pl�rx�s��v��z�"�pq��nĹs�$~�qR�q̹o�q����t�x��q#�lr���e�rr_�-t�r�p(r� �ss��~fH�mTs��s��s��P�a|t��t��2tu�?s��t<u�dO|�P�(wX̺dغi�l�m�n4�r��t�w\
�wĺidx��x'�x#�a�x���a�y����mzz���.�{L�a�vlX�n�{�� �ah�e��i��oh�#��l�{���H
|
`�c�����.�|Xt�nT�A�|����gl�X(�nl~��e�#�va�e��j�n$o����Bj��Իt�-m�I.X���s8�b�����k�L�ȃk�iX�#	(.��c�zf�~l`�ml�n�~o��pؘv��jx���X�d�X��a��dlls��t�
��e,�����P����e��p�$Ȉ9����p�tԇGļa�e�o �u�O��k�sh2XP��8����Ct�B�����gc؋#,�uD�T4�cp�e��i��j��m��p�t8�u �X��h�c��t��o�����c��o|��8�X��o̽r���h~mx~Ľa(��l���ؽa؎��a��#H�i�rh�u��]�eX��XX�#?.��a��b̾c�d�eпfܿg8�i��k��l�m�n��o�p8�r��sT�t��ud�x<�,Г��c��m��X�W�T���s���e���$���ľkܾl̖0�#�wt��i�o�u��wt�XT�?��@�dH�kP�lh�mp�nx�r��s��t�f��tԛ��d`�op�i���Ȝl�����i���x����eD���a��o��r��sğ����0�r����ȿf�c�e��X`�#�l�n,�rh�A�x�n����e@����$�e���T�el�g��tD�U4��L�nd�r`��|��x�d���\����P8�����r̫T�����a��e�f��#`ym������b�e�i��m�pd�bt�
p+n�#��g��K��	�.T�bH�dt�e��i��k��o��p��t��|�@�.X�oܵ�������`�k�#h�n����g��~0�=P��x=�|�T��aL�,̻L��c�k�Zm�pH�s�VtD�p`�tX�
�#�g4�����a�e �iԽX�#ms�#d�d���	,�al�e|�f��g��i��o��s��u�Wy��\�C8��7��t�e8��h;�D�X��d�����s�� �l��p��t(����e����������s�t0�t$�P��a�t��tT��xga �e��X0�i8�k��9<�'l�X@�r����H�ep�o��s�X(�#h�n8�0�|�iT����m��s,�o��s���X����t��T�a��eL�i��l�o��r4�s��u,�U@j����tX�#��a(�d`,f8�nD�pL�r\�sh�v��X8?p��0�a������4�p�ET�l��$T�8��p�d��#x�a��e��l��m��n�r��xxn����.��
��el���t�����s��b������b��~��X�#��p �sp����a�X����X(�r����0�e`�s�#<�g�+jh�n��	���@jd|�k -s@6����a��e��i��o�x~�����d��m��n�y~$z\����[d��u��~�T��nP�~��~`�����p(���#�eD-fL�n\�oh�px�r��t@�ô������8���O�7�T�r��(2��p�eأgL�����a��e��i�o(�u4(�����c�d��i��sd)��̡s\�,����d���e�j ,B���ءnB�� �i \L�eh�l��n��t��P�~�T�pT\�ox�u�G,0v�,��e�X�I.��e����k����a��r�0h	���Mk(	���e��i؉�@��P6t@
#��i�
��e<E�k#\�d��e��g�j0�kT�l\�m��n��o�p �s��tp \ة~,$(d�e�#Tl�l�"x�k��r��s��t��u�'8'����it(�(����p�(���r�)m����+X��ep+����e�r��G�a�,��Yf(�kt�z4Mz�-ĥr@�sP/L�p�/40�3��d�.p�f��p�3��14��x�.��b��d�f��gp�h��j�r�ds��tt�v��jp4��5Pl5T��r�7�,8���n�r�s9XH&.̂e|9,8;��:���l�<P�ad�e��f�i��l��n��o��t�`X=#H�u�='�=X\�lt�vdh�H>?X>k|�e�>P?=�?\?����t@@#hl�?z��e��o\@,xA��@��h [r��s|B�,E��e�HX�s8�thI(�p0�t�I,�A0JL�X�
��a��e�jiD�j`�l��n��o��r(�sx�w|Y��n��rY#p�a�0f��m��r��s�Y���Mdt�t�Z\����b��s�C�@\E$�pd^���f�]#��f�m�n8�rp^b_����pT_��e�_��`,\`��$�u`��,�ke�$f��e��L�d�eTT�a|�o��u�g��g��t�d@h2Hh����c�s�h�xhT��a��e�h?Lj���ePi#��g4oDj:dmHmG��a�i�o�=� n���m�n%
xn���l<oT$kep/iP�lX�m`�nh�op�p�1tlp<�p��p<�p=q#Pu��a��e2iux(u����a�u���z� �L��e8�ip�o,�A��e��n�p �t�l�tt������e��G�l����oD�T���|e0�j�0$��X�n��=��TD�r����L�g8�att����d�o$v����?.��c��d��e<�fP�g|�k��l��m��n8�o|�p$�rh�s��t��u��v��x��z@��L����<�EL�f�n�s�t��ld����s���\����sP��0��(�i���0�f���|���H�ed�il�rp����L�H��Tt�l��wp��d�T������i��o��<����.��a�l��sԅv|�w��z$����������$�����.�b �c(�d0�i��t����8

��������a�gdT�f\�g�id�tX�9l��|��8�j���s���p�a��be��g��l��ols�z�������<�����i���d�����i��`��e��#��o�rX�X�&<z�����dܔeDlgH�mP�rX�s`�z���M�
�~D2P���f|�n$�p�H����a��i�^j�_rh&��p����T���
����.4`��a��eX`�d
��
k��e�i8�oD�sx�l���sT`ta �r(��,�oh#X,'z��a��e��i� ��!lx X`�s�'�l�d�'��x�ax/,�:,�\T��aX�c��e��iD�jP�l,�m��n��o\�phq�as��t��ut�y^�T]����d�g�l��m(�n<�pL^��^� �p�4@_X4�c`_�|_��d�aOD�l�`#L�ax�h��i��o b�Xa	p�o���`b�Tdtd���tXcX��e<�f��g��i��m�d���e�#��d�P�s���	f��pjX�d�n(!?p ���i�k�4�g��j��k��tlM�m�4m�<�oxoTh�a��e��i �o�o����.��a��g��p�o��gxpx�M.��p @��pX��p��t��uXq9hqXtq9\��q#��b��e�m�p�q1��p&�Pr�h�9sx�I.�r���t�sLP�a\�e��il�ox�u�~�s#H�ctt�x\u#d�l�u��uT��a��e��o�uO0v��v�v����d��o��tw����Tw#��b�f(�l�n(�p4�r<�u�v�i��w��x��g �z�x�h�jxyO��p�y`z��za�zED�ax�d�z��L�a��e��i��o��r{%
�{���eX$l��r��t�{�H|�P��|p}���l��t�}�@~�x~���E�aL�e��h��i��o@�r�y8��(�a<�gD�l�����g4�s ��n�����<��T�?��cl�i|�k��n��r|�s���0���t�o��s̖�Є�h.�qt��T�h.�����X��e��
��g��j��x�-�����vX���e��n�r������fȈx�q\�  �e0���(�d��4�a\�el�o�0�����T�e��k���t	e��g��o���d�'H�a��sX�,,�l؋#��i��T��a��ex�h��i��j��o<�r��sd�u��w4I�$�X�a8�bH�cX�dh�g|�r��s��t��l(�r,$/Ў�� �d�Qt�b�&�(��@�hp0t@�� �?,���`�a�2����t�t�T�u\�X��c��i��l��m��nT�r̔~������oP���D�a4�e�9�O��h�#��c(�6�>�nP�E�e,��(�t��~\���@�d�TH�al�o��ܛ��d�f�����a��oX�X��l�����~�X��g��nX�K������w�������o��X��g�l�Xn�o �p�0��i������Eg��X|�l4�ml9t��H�GX�e��i��o��u��yx�����c0Lip�lx�s(��8�E�Sb
X���bh�����b��jܮF��Lx�a��s@�����o��uİ�����s�\~��LL���a�l�n0�pP�t��Td�u<�T(�oP����#<�r8�`H�e�~~`�
\�eĴ��X|�i��n��2���t�t�E��U��\��f������j��s�����i�<���4���
��.�c�e$�f,�iH�lT�mt�n��r��s��t��u�wX�H���on�t�X\�,8�l��\�T0���@�o��d�al�m�z�\����H�c��ix�?��X��i��o0����,|����o�c4���8
r��s��\l���t$�0��t��9��t������a�i��z��$�e�EH�n��8@�eH�ox��,���#`�i(�o��P�k��e0��l���t�s��E|�tX���.�a��bL�c�d<�e�f|�g�h0�iȃj��k��l<�m$�n��o�p��rPs�t�
u4v
wd
xt
y�
z����
 �aL�dT�f��gxZh��i\�ld�ml�n��t�u��v��x�'T�*X(2#`6�p8
����t�vPKX|�e�I��idO,XPXQzH&.��a��j�l �o8�r�R��txQX��l�5X[�\Sh[���i��~�^a�r�\E�o0�r _�D`�	�c�l�a�c��@�a��e��h��k��o��t@ \tГ(f#x�ah���&aT�e��i��o 'r@i���jG�j����e�k���a��s�k� m tn �q,q#��c,�g�p����aX�e�vit�r��s�)w(r�0���yO4�r�y��<�al�o(w#H�m�(r!szԇG��u؋,D�T��c He��i��l��m "o�p�t ����a��i0���m��n`p9L�?��9��#��k|�k��a8���@Ho8r؎�)a �ed��x.4�k�
n���X�E��a��d�e4�f��g��i@�k��l8�md�n��p8�r��s\�t�,Г|�n�TsD�z��d���a��e��o��r�u�wrEܗE|�#��nt���(��e�i�o����X�XT�z��E$�r��A|������,�aT�e\�id�l��o��r���'X��
x�aDKe��o��X|�X��=�� ��L`�#��l����e��i��l��o|�Y`����j����X��#��u�,�����i�n�`s�td�L\��j�/r�0�#,�a8���4�ax�e��l��o��r�$u��w\`~���d�k�#l�r��tL���� p��$�X��p��̫#�^p��r�����a�Ud��e(Vf�ol�sܬX����#��i�m4�~�}�e$�p\��аt�O�6c���,�aH�o��x|��ȳ#P�l���X�a��e��g��o��s��t��H�T��rP�L��(�T��n4����i��l�r�s0�u��`�����d�#��n��H�o̾~h�P�a|���\(�eԿ
��z�XP�al�e��o��s�#`�a��lh�^��l\��x�i|���.��et�z
�����e�g��m�R��,�� �l��p��t�SX0k(����a�e����4:.�f�i �l,�o<�t�����k�e�q�p�<�i�P8�T��4�aT@h�(o|�#�(a����P�a��h�i��j��o��r��sDbw��z��(�P��eP�cd����G��a���0���p����P������������X��#��r0�t8�vX�����aH�d��l��o��r��sP�t�+u� �eil��H�@�ad�ep�ox�r��w��#�(i�z � X��h�`��i��o<��x�X�����m��ot+p��r�:x������a�������+e��l�pD�t��(�a�pe��~$�LxUepHl(�o0�rp}�8����2�aD�E8�r��\�u��)r�)uX�#�+c��l����l�a��d��e`�hp�i|�l��n��o��s�u�y�E��.4:2D�,����ex,i����#��d�l8�m@�rT�sH��	�#�a����a,�e��AH��$�i���p��L�op�����L�t��g����#h�g��(���k��e����#��p `,p��t��.a��e��o��rXn e��i����n(	?�o|	~@
#�+i�
X�
k(�m��T�d$/e`�j4/kp�n|�o��sp ���o�,Ll5�4��h�g,8P�<Phc��p��t�?L�?���j8�K�CX�XT
��a@�eL�lkn��o��r��s@�t`�up�wY#?.�a$�b�0g,�l4�n��z|Yx�s�$��Y �Z�d[��l�k�]#�jf�e�h�et�i|�u�~�f��`�t(g�Hh,�j�Pi#��l��p��s��u�k��?.��e�k�l�	�
�Hm ��a�o#<oT��ax/l`�n�p�t�~�pq��iq���r|qH8e,�r\r"s,T�r�r4�e����TL�stGh�r��uPu|�iTvT@w#��b��c�2l��p��r�s�v����a�d4�e��f�gd�i��k�l�o��p��s�ux�x��{�|���mH�4|���m�}�$�o,�w�~�@��X�#	?.`�gh�ix�k��m��n��r��u��x��P�
�������p�s�\j�����mă~�����m��X���d�����a|�T������a�3l��r��s���Ї,�����l�k�3l�o0�rT�u��k���-p�G$�aD�eL�i,X��@
z$��Ԉ#\�a��c\d��k��n��tX�؊P��h�g�3t`��0��#��f������a��e��r��s��v �,8�X���t�
�������e$�k�i��X��c<�kD�mL�pd�s�2v�zP�P�X?.��p,�H�T\�t�'�Gp�a�x�r4��	��e��hd�i��j��l��n��o�Vp�t�,P����0ИH��HԙX����?��r��
��e���бi�r��Ex��l�at�c|�f��n���
$�a��eL�i|7md�o��p��r��s��u��zȞ��#T�#@������f��l��n�r8�sD�t���̤?��T��a����t(����s(�?�I.�E��e�~��#��n$����e$�k��,����l|�,D�0�p��tԨ
X�nĩ��t���t�l|�s�t�E������i��k��e�F�\�k��o�<d�T��l��t��e4:.t�E��e���@�#��iȲjT�#4c\�gd�ml�nt�p��t����a�8c��d��e<�g��i�kT�np�o��s,�t��up�P��X����0KXȷ|�h��p�#��c��s@���a��e�i8�oD�rl�s��ux�t4�~��X��r��t�������e��#x�d�j�����s��
`���$�.,�#,�mԼXT�e\�o���X���@�Td�j|�l �P�o�����r��,�#(.��b��clzi��r��s�t�v���̾|��8�����ft�v�������tT���P�.4���"~���nX�p�#�a`�p��T,�ap�e�:l|�r��s�#~D������#h�n��<4�(��e`,p��t���\�A$�#��d��g�[���#��p@����a�e�i(�l4�n�w�^~�^#�p�^���a��#�l�j�|��`�T �o��<��]
`�t��A<�s��LH�e�A<������h�f��l�6n�v��LX�X��T��c��e�f�%m�n�o�p�t����i���hL�� (����#�p��r4�\�
�e��l������(�e{r��T4:.X�ad�ht�i��sp�v��w�0��XP�ax����n��Xl�d��y��p0�,��,�������i>r��a
H&.�c��dX�fl�g�k�l��m�nD�p��r`�s|�tL�L���a�e(�o0�r@�s��tX�(.X�tt���H��T8�lP�p�X�Xd�i��X|�E��a��e��i��r��sl�t���p�,������hX,l �����T��a�el\l�s�w@�X�� ��t��p����E0�a8�eD�fL�gT�il�kt�o|�u���4���od��t�ld�#X�gd�j\����L�K��$��<�����a��e��i��o�s���L��d����e .��E��e��T�t$�X�a�d(�i4�k<�o��t��������u�����# �n�=p��c`�a�eh�o rx�s��
�x���p�p\������
��a�d�e��g�i�k�m�o,�sL�t$�,#��a<k�a�o�.~��p��xt�t<$��$�m<�t�S��<e$}op�X�h�VP\_a4met�n�w�E��a��e��h��o�_r�sh��X��s��,	�
(�	�l�
#d�0ScH�dحfP�k<�mX�nl�t�
���a��b��d��eP�fX�i��j��l�n�op�r��s$�tD�u X��T�d�d�8?.|�t�M$�`�
D`���e|��rԇ�����r���?.�e�i��l�n�r<�tH�v,��tD��������ȸ.�i�������M.,�a�o�P��4�e4��El#l�cx�d�_e��j��n��s��t?z���?.��a�x�U�`�o��l����s��(#�e�f�g�i$�l4�nX�r@v��#~0�D~����,�dH�iP�t��������dh�e�L ���i��u�!2�!����cT#Xh#
�c�e�i�l�m�@n�o�p�t��u�`A�#��#, $�4$E��a�s?x$X�$X�$��$��a\%4:.4�r�%$$&��%
<�iT�nP&g�'#�c�d�f�g��lnt,'��\�a<d�egixk�mn<o�p�spt(u�WyDz4(�@�h��t`(����m�(#()E�3K�)���g4*��X�a�J,epq0H-#$aXs-k,ate�o�rBwu�({��-��`v�-#hn<.�n�r�vt.��T$d؆X�.P�.��al���.��dx/#�a��c�en0,�/�d�0�3��hBo�8�Bl�:#4aHg`n<Ü:b|*�����<�I.�ex>b�=��Xk�Bsl?
�?��pa�e�l�n�w�?��@<PA,�BX�C#�6c�s�C�a�e��X���D#�il��TEX�c0Ek�ai(o�EXh���E�� s(G��F��4e�)ldnxo�p�r�s�v�H#pt�I�J0tJ
LKXPL��LU�a�s�LX0Sc�MTP c0=gO���g�N�a�cel$m0oDpxO,�O,<Q?�PTiR?�QEi\RX<ntRX�RXTa\u�R��S�xU#8EkDUda�e�i�ors����a�nV#�e�O9�V\�lWX�j\WX�f�o|W��<XfX���d�WG�e0Y,�XpPFt�Y�\�\E0t�\�8e�\T�a�c�ef0h��iTjxk�l�m�n�o�p�st��u�	y�]�T]���c�s�u,`~�`��`�a�i�lԯua�	���Xb�XcX�[dlr8e�f~�hzx�e,iXi(aDiLo�i��iT4m�le�m��I.�m#`r,n(�oo,xo�i�q$�s��uL?oTw��z��z���a�e�i8r�{����r�|#�r�;X�P?m��z,a�eT@h�or`uX�y8�Pahc<�gpnxr�s�t ��l`r��́�t�~��~��~��T�#�.�m�r���$H��G����(������T�oX��l�5u	v0��$�'�i0�����.,a��a4e<iDoTuP����X؉X�xD����xLm�e$��	�a8Jc�f�g�k�pd�rsT�t����
hae�h�j 	l,	o�	r�	s|
u�
w��Vr��X,���1h����t @$d����p�]
\�X	<dDe\gdi�l�n�r�s�t����a�I.Tsx�I�u�����#ln�ta�����s��,���p�\����t@�E�a�g�#�o(KuX�O��	ehI(�pH���	s��̤�	i��#X	fl	l��mx	n�	p�	r�	s0�����P	f��$0�Td	v�����t����4�m�	o�	r �,p��\�\��H���	o�	u@���X8��L��	i
l(
m4
n<
pd
t��$
a
i����X0�?� 
o<�<��XH
a���Ĵ?4:.��k�
n`��P
et
u�X�#�
i�
l��������4���
T�..a�
c�
de pkDrds�t�wX���(��
aeoL�0�#�
s��TH� t��������.TnX�
(n����8e\o5Q���|�X�a�bcxt`���T4����a�h�j�o�r�s��L���zH����,l��p�t��L�e���� Oad(i��,��ko�������s��#n���Xa`e�i�l�o�r�uL���E(.xe�r�s`��8������g�s8�,D��p�t\���e������sH�,0��o���$������8��(
a0
eD
h�iL
nT
o\
r(�#��#<
nx��,L��L��X��L��EX�����l
sP���e�vL���
l�
p�
r�
s�
�
e�,'E�\�tT�u���.da|b��c�d�ef�ghli�j�k�l�m�n(o�pL rh#s\%t�%u�&v<Pw�&y�&z���"�@ Hn�r�s���Pax{b�c d<eLfXg�k�l@mTn�pr�stPuhvtzB,,$k�u�#���d�Qt�$�P&��H�c�hpxiu�&�t��D''s�'��'��d�.4a�xv(�)��*2�*��Dd,�paxe�Bh�iH,
�,#�n�,�X0��ya�e�yi�l�0�
,1](2���Zb�epf�il0m�2R�3X�n�5�l5T�s�3���gd4$l4kex4��4#c�4T$a42o`6EX8Xpdp8��Ha�e�k�s�tP8����9#xe�l���|;U$=,x<'�p<���=T�r�>��e�i�[lo8�r8?�?#�tD?
�?XA���&.<a�tbDdPkplxm�oP�r�t�uPA��A�{aC�`ahl C,C=�BRHCf`C,�D,E�a�i�
j�n�E\L�\F,�r[t�F�a �cPee�o�t�F�8H��HLT@ho�$rxI,�J,�Ie8h(]l@rs0K�L�N�M��Ht`z4O�
dO�|bl�PbQ�p�ȔГ�lX����a�cd,exg�i�k�lpm�n\p�rDsPt�u�i�$����iot����
�Eu��$#e�o���ȸ.dlpnTsP�P�\`���H�L��ԛ�\iȜC��ԥ��a����l�s����ܨ��#�e8����e��l�n�s� �a�Ud e8iHoTsH�g̫#ak�y���X0�e0t���0�]���@rd�d�i��o�Vp�\����hmȳX�k�pp_u�z���|a�c�dg�kn s@tt��ȴ�8�`�RH�k�y��|��r���H�T�e���
��X�Ta8l��m�%p��,|�XLo�,�J4���Tipoxs�����l���H&.�a�c�e�i�os<t�#�As�t���,�D��\�#�g�q���
D���s0�����ns\K���~�`m,t�T�(��$i�Dr<������a����ȸ.ta�e�i��r�s�u|�,<�l�X|r�������e0��t�����T����k�t����P���g�(����k�e��%��#�s�
 a0eDiPo����<E(c@����<sXl�im�n�t��Xa�d�egj�k�l�npoxs�t�,�p ���,,$(�a�#T�l�"�k�p̀r��s̲t&9X+lp+���at�l�,�d�.4kPl\ndpxz4Mz@e�],�O�O��Hi\-�S�pa�R��-��.P�-�lX2w<2#�g0���o�~4#�m4���a�dg(�j4kLrTsdtp4R	�ad4�l5�h.a rT5��5���i|6x@6�,rDs(�l7�$7�7�7T\a,8��<��n�t@,�?z�a�@��h��j��r�s�B|B�p�EO,E���aH�e�Gi�o@R#�Xk��a�v�a�eLilo�u@w��	?.Pa\dxm�n�p�s�t�y�w@j,wDt�x%
hi�(��y��y��pp{�$z�g�t�[m�{�4|��|�r�|PlP9��XX����c�ein�p8u$�x��tD��P��d��ą�̅��$o����,r4�1Ԉ#Dg\j\�CP�X����dm�n�o�����^e��aȰd�i��n�2М��e�����i�k�aTt�����t@����sx�#�n�k �ae������m,�#u����?.|d�e�fg kDl�m�noxp�rs@t�up{v�������ta��m<�E�i�s�t�z����t<�t\���8js8������������O����ap�R|����ir�$�b���Ti8o����#0l���Tohs��`pL������t��?�.�
te<���l���l�*���s$����d�s��t�y�%���T�m�t�� �eP���� e��a�d(g4lTrtt�0l�EP�.�He��~8��@n����P�.dtL��Lcj�-���pa�d�e�h��1���(������gLlkмo�tmup��e�orԕ��#�f��W�G�aP� a��f�#�0h�(aXs���0a`e�j�_r�	,ts����lt�(0�x�
���l�
��a�e( iP�sd,�,�l�n rH���o����i,��e~�t����s�l� kl�� e@ jd��8 p,'h a!e�!i("oT#u�'T
ȸ.� a� d� k� m�mn� o� p� t� x�'���dHt`(~�1�
�)��� t�)���+�,UXPXH�7�0E� u�0E� kx/��!e(!mH!nT!sh!tP3�4!m�\=h5*|j�3��<!t�6x`!o�R�6x�jx!r��x<;��:���!c�!e�!j�!k�!n"o"u"v�;��;�!k�!t\<��j�<�h=~̸?�=���!cg"s�>��>.CFTC��L�F�� "bp"cx"d�"e�"fp�g�"j�"l�"m�"o�"p�q#s #t8#v@#yG�G���(GE�"d�"f�t�"v R�GY�G9ȃ`|H4\H���"a�HxJa�gd��idJ�tJ���"a\K�mc#t�K�h��K��#a0#oL�PLf�L�p[`#o�Y��H#t�x�\T
�#a�#e�#i $l4$mL$nx$o�$p�$t<%y�]�T]���#b0=g�#k�r�#s�^�,`��?sXc��#r�#td���f���#k�g�jX$d$nLpsp �kXxoT�pettT�s,$eHi�u��u�D$ad$e�vB0v#\$u�wXTw��p$f(�l�$p\HrxyOl9t�z#�$l}l�$e4}�����$a�$e%h%o0%r��u�y8����tT�?�$n|�sЄ�qt���Ȉ�X���%r����n��$%o@��L%cT%sX�lt�����%a�%e�nhTLidsj�%o�%r$����nd�%l ��ؐT�%b\�X����FvH���%i��h����%c4�
�%b&c��e$&iD&lP&n`&t��X�(Nh,�����&o\��&l��\��0&t0���8&s���4:.�t4���ȸ.�&e�j�o�&r�
s��,��X�&n��T�&i��#�&rH����&aD��L����&n`�r���
P�l�&e'uX�,4�'e$'o��EH���'sx�~���.�'a�,b�,c-dx/e�7f�8g�Ph�:i`?j�?k�Bl�Cm0En�Fo�Lp�Mr�NsDUt�Yu�[vd\w�\x�\y�\z@ �(n(r����'a4(c`(d�(f()gd)i�)k�)l�)m4*n�o+p�{q@+r�+s,td,u�,v�,z�"��wd�#x$(.�d,(s�QtH&�x$�P&�L�aH(eP(l�&��'q(��'��X(a�(b�qe�(i�xm�(r�(s�xv�u=�(��(a�(o�(�)wd)_�)~�*R	�*���(d4Zf�(o)r`+�h+���(d<+��(n����+�)at���,#)z,��)eD)iL)n��oT)s�,,���-�x/|�.��\)s�0}�0#p)tX0��x)e��l�!r2�(2���)a�)e�f�)i�t���2#�)e���3#�)m@�X�6#�)n`6���)a*e,Fi$*pl7� 7#*u�7X8op8��,*a\*dh*g�*i�*j�*k�*s�*t�8�r<:Tؓo|*r�*s�:�H&��:��*.�:\�*t8;X;#�*m���|;��Sl�Sw�<x<'�*aP
t�=T|Zj�*w�=2�>���&.o�[p,+r�"o("��+eD@` +o�,�A��8+b`+ep+iP�m|+r�+t$B�,?X	�B#h+t�M
E��d(G��+i�F���+e�+k�+l��o�+p�+t�Gc�GPdHk�+op}�؉,�I��+i�HL�+rPK��I,i@j8�o4,rL,s\,uLXL,,aD,iLL~|L�pL�t�M��M�t,s|,t�N��N����dO��,r�P�@}j�P�,i��pX��,i�T��,tQ�,e�c�,e-h@No(fX�,sDg�h����i�pk	H-a�-e.i4.l<.o�.r/s��u`/w�q����tq#<-cl-lx-m�-n�-r4�s��d-kds\��a�s��t�(w#�(i�-l�-n�-o�-r.s�H
�x#�-s�x���-a�$|��-rz���-d4{w�{��@av~��}�.t�#�va,.o���؃�X�X��c\.lt.n�.o�.p�.v0��h.iH���e�����t`����a�.s��XP���.ed��Ј����.sԇG�.a�.e/o/uP�����.s�����a�.s8����.o��TD�T��a8/c�)o@/pH/t8�u؋�8�#؎�)a�4e�Tt�TX/ip/o���X���?.�/a00ch0d�0et1f�1g2ih2k3lP3m�3n�5o�5p6q86r�6s�6t<7u�7v�x<��Г�/c0d0k0m(0t���\�#�/e����6�����0b���$����oD0rT0t�m���,�EL0aD�R�`0a�0d�0e�0i�0u��wv|�}|�#�0s�A����0i0�#�0tT�R��E��E�0k�0n1p81r`1sȜ�1e1h�����8��1s���
ĝ��$1d���,1aL1sX1wd�X	tT��x�l1h������1bȁf�1lx�u�UX����4:.�1d�1e�1g�1i�1l2t����l����1e`�#�1n<�s���`������	�4�U���2e02l@2npUtL2zԥ���X	d���82d��
�[X�#T2p8���	\2a�2e��l�2n�o�2r�2s�2u�Uw���T_���2i�#�2n�(�m���G�2eqX�T�2pH���vp��2i 3o(3r����2d03e�fkD3u�~���P��#��i������<3r���ȸ.t3a�3i�3o4_p�.X�Ol3i�B�̲���3jز�3t�#�3e,����#�3v����.4aT4d�4e�4f�4i�4k5n5oD5p��qT5sh5t�5vȳT4a84dD4l<_nL4r��0(4rH����ȴ��04e|��ش�|��`4eеX#ex4r���ĵ��p4t�#(.�4l�4n�zp�4r��X�����.�4k�������I.�4f�v|�~8�L���`��0���4l���P����zc 5g05p<5vP���i��(5l�������(�TL5l��|�T`5ox5wh�2���`�E�5r��E�5e���5e,�̻L�5f�5p`���,4����5a�5e6iP�
Խ#�5t���#�5e�U$6u����6a�7����06f\6gd6ol6st6t|6v�6w8�O������<�1@�/T�������a�e�6l�}m�6p�6tp�(�6e��,l�kT�L���l�X�6m�����6e�Xh7i7n,7o�s���7k8�nl��L����(�#$7g�oT��`7rx7s�����
L7s����T7e����eX���l7t0��
�X�7s4���7i�7o$�x����#�7a�7c8g8l8sX����7a8e<8l�8o�8r�8s�8uP�����t|�]��X���l�X08nH��x���(8gh��	\8ap8e|8i�8u0y�����T8g������h8v<�������s�����#�8p��G�8e ���8a�/9��H�8m�8p���$��|���#�8r�u��(9aD9e�9i�9l:o,:rL:st%�X�# 9b89mX�����b��#��e`9i�9l�9n�9rX��l9s��<��Ur���t9d�����M.X��9pp����9a�o��� -s�#�9n8�����9s��9a�9o:u`�������#h-v,����:j<:t�G:iD:ol� �`:m4!op:p kx~"�Lh:r�U��%�|:l���:el�:b;g;k�n;s;v���:a(;b<;ch;d�;et<f�<g�<jh=k�=l�=m�=n�>o?s,?t�cT�<(P�\ ;l���4;eL;o  X� G� PT;ep ��\;d|;i�;o(!X0�Xh!#�;l�;o���4#,#�;r�"�;d�;k<lH<r\<tju�[��#�;p�#T�;a�l<w�$X�$$<a�$��<a4<eH����%�,<i�'\8'��@<o�(�(��T<al<o<)��*<Zl�<r�*�p+�<at�l�s�+�X+��<a�OX�<.�<d�<t�,��<l�<o=p,=s�cv��}9L�b
d-w�S\S��=l=r�S��-��-T$=pH=t�U���r�-C<=eX=rtV?�.=�-`=l|=n�=o�.(�.�0��<�m83X3���=a�=p�3kr4��@�b@jd�=e>f>g��hd>ix>k�l�>n�dr�>s�>t�>v�4,L5�l5'(>aH>eT>l�5T5X >nt�
��~4>i��#<>n�5w����5#\>t`�P@6�p>l�do�>t�rX	T���6L�>a$7m�>t�7�7m�9,,8��>n�>s�9_X=#�>m�<�>a?c��o ?tH=�?z��o�@��jL?ohsX?t�A�A#D?v�B2,Ew80aY#�.��a�?d�0f�?hШn�?r�X��l?a�?e�@i�@j�@lPAnlAo�ArBsTBt�Bu�Bw�Y���6rxZ� \�\���?tx^�]#�?i@n(@r@@sT_E`�s�` �l`��@s8@v@a�`aEh@t$���T�et@r�a�L@exa�\@r86�&�b|@p�b#�@e�@j�@ntc���v�cK�c���@b�@k�db8e��@�se�@e�e����gAt�eT�@a$Ae0AoDAu\f�P���d�f��Ai�g�<Ao�g8Hh���sxh<\Ae�h��i=Pi#dAb�Al�Am4o�Ap /r��s�j��j���Am�j
�j���Ag�kK�k���AgHmT�Aa�AiBo�m9dm��An nBs?9�n9xn��Bn<ok0Bfp/i8Bp�h�qEd��sX@Bv�rHBepBhxBi�Bo�Br$WL4sls��st#\�i(u���a�BtPu���Ba�Be�Bi�u}�u��u���BeTv���n�vLCaCeLCi�Co�Cu@w���a��8rX���Cg(�`Ct���(CsP���4CkԈ#@Cn�q��1�XCeȒ�����lCs����tCf8�М��Ct�����Ci��CaDe|Di�DoEsEu��HT����Crx�#�Cf�Cr��20����Ct��#Db(Dg8Dr�ssDDtT��P$���0Da�����j\Ds�A�TDt�ĩ��hDhԨ#pDn�Ds��\��X�De�Dp�Dr�Ds0�o��X�Dm�j8�����De���DtЬ���Ds���Kf���d�TEax�t@�#��i(En���kTEa�Ee�Ei�Eo<FsdFt�FuT�XhEmpEpxEr��������s,�#��e�En�Es�tt�\
���Ee���\�Y$�#�Ed�En���������n�EoFp Ft0Fv8�a�2rFtd�u
l1|���Fs��~����`(Fe��X��m�DpPFt���\Fa����T�=h �jxFo��,��X�Fr��"���Fc�����FaGcGd(Ge�GfHg,Hk\Hl�Hm�HnJotJpLKr\Ks�Kt8LuPLv�Lx�Ly�G�P&,L�Gh��#X�����Go<�E	ȸ. fTGg`GmtGn�Gp�Gr�Gt�Gv��U8r8�x��X�Gmd���hGs,����c����Glx�l���Gi8�X�Ge��\����Gj4��������$H����0�f,+i�Gl���T��
��#Hs|���He�����Tt�l@HmPHslh=�X��THHp����H&.|Ha�He�Hg�Hi�Ho��z���L���Ht4�#�Hs��d���2��X�Hv$��<����Ha�Ho܅zd��$���z.,IaLId|Ie�Ii�Ik�In�Io�Is�It8�u�Ivȴ��#$Id<Il��U����DIadIolIrtIu8��D�P�����O��O��Ia��T�Pp�O����It(���.���Ie,���Ij�Ir���<�x��X����a�If Jg(Jn0JrXJtl��
�
��'P
�d
�8Je�@Jt|�ELJs���Ja�Jn���dJa�kb�Je�Ji�Jl�Jn�JoKr$Ks8KtDKuP0X�X��#�Je�'��XX�#�Jn��`�Ja�-��� ~h TKkp��Ka���zh�%�$�0KeD�\������dмoPT�a4me��f�Kh�Ki$�p�Ks�Kt0 �������P�Kt�#hlz�Ke�Kr~��Ka�����e0�h�^jLo\mr$Lsx	~,	#�KnP
� .d
�Le�	Lt�
��
��0LdHLt�34`\Le`�pLr�����hLi���d
E|Lit
��
�LaMemi,Mj@MlTMo�Mr�MsdX�La�Ld�LnP,�n ����v�bT���Lk,'���Me Mi�2��Ms�zlLMd��4Mo�~(��xodMvp{00�!��lMcL tMe��i�Mo("�#�h#T�Ma�@i�Ml�Mp�Mt $$H�i�{~�$#�Me%��$��Mo,'lNelNi�NoD3�3��Nux/��Nl<Nn�oHNuT5(�3��4Ns<7�H<���.�;PNrxNt�:��\Ne\<E�I��H���Nk�F���Nn�Nt�K��&j�\OaxOc�OePf<Ph�Pi�Pj�Pk�Pl�Qm8Rn\Ro�Rp�St$Uut�y^�T]��	�Nd0=gxol,Om8On��p�rDOslOt_��0b@_���a,`XXOsT���HPPOel`~T`�dOe�`#̋h�Or�bl�d2�d��OsXcX�Oi�Ok�Ot�OvPz,e��d��OeeT�Ote���Os�g�Xhdh��Oeph��h#d>r�hkPeLiX,i��(Pli0PaH�iXPo���i��PPchPt�i�Xl��k��pPi��tj#xPn4m��Pa�Po�PtP&�`m���Pc�R9�m���Pun�
�\Xn#�Pt,n�PaxoTQa(Qe<QilQo�Qu�o���pn����p�� Qc��p��u�q#��bPQeXQn�q9lr���g�r?�r��dQb�Qe�Qo�rJT�p�rD�s�TsJ�Qs<s���Qi�sE�QaReRi0Ru`!��s�Qk�Ql�s#�Qa�Qk�!�,t~tt?u#��j Rs(Rt��d�9�u,�uTLRad$eTRo�u��v�Tw#pltRn�Rr�v�x���g�y��Rk@@����Rsz9�Re�z#�Ra�Re<SiHSo�Sr�Su�zz�zE�Rax�d�RrT{��Re$Bo�{�p�c��eSk(Sn��t|9�o(|� Se�9�|��4Stp}E��edSglSntSo`qt�}��}�~;� h~T|Sax~�Sa��	�Sl�StD&�`&�����SaTe�Ti�To�TrX�y�O
 ��Sk�n8����SaTst��7T�?��.(TrXTstTv��T�h.@TkHTmPTs����P��Wh�_dTt�e���d�(lTa���tX���Tm��n�Tr8�k@����Ta��rȈ���Ta�2�Ta�TeUo0���P	p��y�����Td�iUn�3������gUzx�y؋X8Usd��̌E0Ua��xUa�ncVe�VhWiPWj\Wo�Wr�Xs\Yu|Yw$�#
�.�Ua�UcX�d�Uf�Umn�Ur\�ud�v����Ud�Uv��Un��9�#�(������TJ.��f��X���Vt�n
�̔�Vo\�#VcDVipVl|Vm�Vn
r �s�Vv����gTVl(����H�#\Vi�dVeP���b���I.�atod��4������Va�Ve�Vi�Vo(Ku�.y����Vr��D�XX���Vl���ܡ\Wk�X�Vj Wn�z�����i�@k��hI0WcH���8Ws��DWe��X|Wf�Wk�Wm�Wn, p�Wr���P	f�Wo��
��������.���d2d�
\����WiH�G�WaXe�Wi`XolXud���Wp�Wsd�X�+�x�����c<XdHXkPXsz�ܗ��X.�#$Xn��0Xeh2�8��ԯ�@���XXl��|Xk�Xs<��D[���c���Xo��X�Xc�XiL��Xe�Xl�LmYn$Yo0YpPYtر�
�� �Xe�XiYu��,��P�,<��Yoy�h���Yo��#<Yr8��	Ĵ?$In`��DYe�#pYi���
���hYt���D�iD`��L�Yr4����Yb�YdZe$�fZg0Zi<ZktZl�Zm�Zn[rD[sp[t�[u�[w�{L0�#�Yr��YeH�D��Zr�����X�Zr\��$Zt�LLZeTZi�]��b�,wH�#\Za�Zp0���dZa�Zi�Zsh��$�T�Zj�Zn\�������T�ZiԨT����ȸ.�Zd�Ze[i ��T�\�#�Ztx�[vp����E$[a,[e��,X�
(.<[s��~|�T[ed[t��4���`��\[r4�Xcj�
s$�0�� �����[aL����a�[l����[a\eX\l0�\�[e��[t�����[s$�?�.\���eD��[t����[s�E\r$\s���0\pl�XD����a8\t���@\o��XL\o��x\e�\h�\t����vn�,������T�\j��L �X�����\uP���\el���lJs��E�\t���.T]a�`b�`ccdXce�hf�hgihji4mj,nkxol�sm�unTwo�zp�q�r�s��t؋u�v,�w@�y|�z�h�����.�]a�]b�]c^d(^fL^g|^h�ri�^j�^k�^l_m@_n|_p�_r,`sT`t�`u�`x@ H�]g�]i�]l�]t� �L!��]sx/��!�%t%�4�oP&O��a^r�'�����'��^v�*O<^eD^o��r+&<+&,����gd^il^nt^o�,��=L-XP.�0jX0��^s�^t�1��1�(2T�I.�^a�yd�^h�^l�^m�^n_o2�̈b
l4j�4m�a���	�4� 7�`6��_e$_m,_p|7��7lX8mX_tp8��4_a`_ch_ep_tȷ��8E�9��='|Zj�>�d�.�_a�_e�_r�>X?JD@?�_o("~A��0ea�_b�_d�_e��m�_o`s`t�,��A=$BT`CG`l\HX�C�EM `s8F��e�F��4:.D`a8�cL`t�F��H��I�l`e\�it`l|`t�J�̤��M�PN��M���`d�`rt,s|,t�`z�N�4O.XP�Q��`a�`e�`oxQ,�T��\��c#
aa$aeXah��iXbl`bo�brcucyLa�cO(f�8an�f��f��0aa0���g��Da�h	�&.xaa�aebi boLbtPhE�ak�ap�auX0�d
�xh��hE�h.�ac�af�an�aq�ar�av�aw���h����hP�h�4�����@i�br8i������bl4bo<bt������i'DbaXk#�kO�bl�bo�bp�br�bul�$l��|ba|l����
�l��be�bi�l4�l��l���be|m�lm���bt�m��bi�m��m���bs�o|pO�p�dTe0ch8ciHcoPcr��w���#lTa�sX�Xԇ$X�X�.�ca�]b�ccdexdf�dg�diek8elfmLfn�fr�gs�gtDhudhv�bxphzГ\�ck�cl�cr�cu��XȔXH�G��X\��$����chdrdtdz�m��������8dd@diHdnTdthdv�}��
Ȝl1hD�E`ds�,8�0������pdf���#e�do�dr��n
�T�����.�dg�dn�dspUtL2z|��d�X�h.�de�4��R	�dtP���,ee�T�dt8���es�Uw�q�
�qe$en�TH.xea�ee�f�ei�fk�em�o�epfsftful�X̫#ped�eg�ek�es0y��yX�X��XXyk�elT�m,����������eg0�O�en�et`��L��@�9d���Vp��b
��I�,�a0fm@fo�\�	�����#8fo�	�I.�fa�fe�fg1h�k�fo�fq�ftp��ȳ#xfg�fn��~�#آtt�H�T�frP����}h��|�T�fw���
H.ga ge8ggdgipgnxgo�gs�gt�Wu��gud,�\�W0ge��n���8�ULglTgo\gr0�:,:D���|j��K��X�gp�����\�gp�gt���(�����<��gw��hgc(f�gk�gsX�!$����Etahehi��j hrlJs4huDbwl����,��x,had���,�XT���<hm��X��EPhn4��Xhe8�U|heX��P��d3��X�k�he,+i�hr�hu��fl�#�hd�hi����"���hmX�t����huh
]
@
#�he�
,ialie�ii'l�im�ioju���H&.DigLil`imXEt���I.�'���Xip<��ii�`�is�Exid��
�����ie�ir8��������ie�io�ip�itjw�8����(ie�.X~��jl#�.djaxjc�je4kf<kgXkjpkl�km�kn�lr�ls�lt$mu,mz(k�lu��c�im�n�@�iL;o�"ȸ.�jf �k�jp�jskt�j�H#4P#���j��#$\#���jr&�(�jl�jo�t<(,d(��(,�(��kako<)$�*;|*�� k��*�p+�Hko ,?�-�,�Pkv�cz�0�0��hke(�����|kr�3k�ke3���kp�ku�3�44���ka��blcld�flgPlhXli`lkplmxlnLr�ds�lt�lv�lz̸Kp4Ml5X0l.8laH>e@llHlr��
T5C�5H�5I�$�5m@6�0�o�r��O�6��7��
h�K�;��lo��rD<�<��le�li��t�=��led��>U�@�lomrmu�A,mv�A[PB�mu`B��B,C`�C�,E�	H&.`maxmd�me�mk�monsnt$nu�E���.pmb@d�
�F��H#x�.�mb�me�mi�mr�ms�mw�mz�Hb�He��~	DI��miD�hI��Jk8�4Mq��@R���mfnn�R2(T��V�W��X	Xna�ne�niojooor\oshoupowY#��ahnd�Y~�^o�^��pne�]#xnl�nn�nsT_~`a~	�b���.�ne�ns�n��b��b�nd�nscLLcvTc���n��dhcezPiX�m~HmGoe<ouH�9o�(om�n0oi�q?|qEHoe<oTPott�Pu��v��oa�pe�qi�ro<su�sy@w���.�oapbpcpdpg0pm`pnxpp�ps�pt�pv�pwT�zwU,w�on�op�or�#��wO
xfxX�x�0y�(pm���y��4Se@pp�yk�h.Ppe��dz$zXpdppg{*�{x�pe�pl?��[}4|��|�}� }h
X���Pfd�pe qg(qi8qnXqp��r`qshqttqu�qv���.qpqrqtt�U���D�C|j�bP�FH�����0qgt�i��sPqt��*��XL�����j����qg�qs�qt���؅���� ��Ԉ#?.�qcHgd�qerfX�g$rjHrkPrmlrnh�p�rtX�2��R	�qdrfrr#�\#x8'R(��J0\��rf8rp@rs��U���؊���\ra�����P���dri|rn���`�2��N�����rb�rc�re��f�rg�rl�rossst$su,szL�%
4�JВ�D�B��a�gd��n��sstD����X�xsr�_	���ؕ�X������4scTsi�sn�ssМJps.xsd�ss�st�szȸ�O
����.�st<���se@@�8���C�4��l�7؝���satteui\uo�uu�PȞ�stx�#�sa$td,tk8tl@tnHtpXtrdtt�x����0l.�x@�7�~���0���Ptt��}H�}�#ltc�td�te�ti�tl�tn�tsܗZL�,�tnP��te�f��E�td�ttD�������k��T�to(�9D��4:���u.Ԩ#ud0ueDujLun,��<us<�P�ĩ~����#TuklunL��z���#tun��|ue�����ud�ue�ut����ul@�#�ui���2����ua0ve�vi�vo8wuT�#�mvpvrvu vv��9�X�x4�8�,�#(vdTve`vghvlpvsxvt�vu�zl�tܿ���9��7T�9���vt����vs��z�A$�#��e�vj�vm�vpiv�J\�~����������vdwewgwowr$v��JP��8��$���4:.(wmH��$������0wfLwi<im,�����?.�ib�wc�wd�we�wfxg�i`!k8xl�xm�xnyoxyp�yrDzsPzt`zu�zvؕz@��L��we��?�$<�	L�f�wp�s���0������wfxtP��|�, xa(xeثl0xyl����e����T�I.laXxehxitxo�xs4�X4�d�#`xs���cv���<�T�.$_m�xsԅv��z���$���H.�xa��b�xd�xg�xnyoysyt�dv�Fw�R��r���<�T�Rp����',����80yg8ykHyrl����������@y.`ydhylpyt�=�B�L���O�.�ye�yh�yo�yr��9D��yi��p�K�ya�yeh �!�,������ybdzgzkzo0zs<ztx9<E zr��S��(ztp�PX��f��
��
��Xzdtzl|zt0���L4��ze`	�
��
H&.�za�{e�|i}lp}ox~r�s�t�udE	�za{d${k8{nL{pT{rx{s�{t�{uP�Qk��l{n{tHU%O
 ��%
0{e�TXD{n���9�� x.h{ip{r�BoP��!�M'�{tP�������{a�{c�{e|i|k|l(|n@|pH|rx|s,�z.E�{u����{t,6Dt�\�X$���|l���I.8|a|\~���&.`|ih|mp|r����f��D���l���|d�|e�|j�|n�|o�|s�<�3t\n��|n�|pdO
���x�gpBx�|t\@��z�|o��$}a4}e@}i`}oh}u9�����,}tL�	P}nX}tP��`��
l���(E�}e�}g�}l�}m�}n~o~p~r0~s@~tL~u�}ÐX�}s�H�N����}��D��%
���4:.�}nT���x��4���~e(~io��	�@��8~s��� U� ��T~th T\~k�~yL h~a�~e�~i(olP�h0�!���~d�~i�~k�~m�~s�~u2�h2�(!�T!0<7��!]�!�~t�!���~ejk�!��!� "e("�� bPcXd`fhgpnxo�sp"lx"s�"H
p�s�HU�"�#�h#�\%]�%�b�i�n�r�w�%�$&B
P&���X����&E,'���a�o�F���v�\P0�a<�cT�ex�i��l��pT�u�	y,`�T]��(�s�`#��oL�ucXc,l�i�fo�d��d�d�sj,xo,�z���a��s�z����a�zx���H&.8�a0�b8�c@�dT�ex�f��g��h�i8�j@�kH�l�Km�KnX�o�p�r��s�t�u��v��w��yЋz� ���f��n�r��t$��� �a��b́cԁd�f�g�k<�lH�mt�n��p��r��s�t��u�v �x(�z������.��g��w@#z����f@j�%����o(��@���sx�������.�o���<+�,�Xh�X$�i,�k�t�0����T��ؐT4�i����$(.\�md�p|7����<�����l�d$�sd�X��o|�O
�,�������c��i����%
̂i�j��G#Ăep��x�X؂oT��i����t@����|�o�����9����-ܓ� ����2��k\�#
H�a��c��dԃe�i0�k\�l��mЄn��rh�sp�xd1z���̔���h��o���4��ău��h���ãk�n�r�nt�����h������4�P���n\Z�#�r����$�a@�k�����,�H�h�P�dx�e��l��0H�#p�e��m4��$�?P��h.��d��m��o����x�x��ș���Ȅt�u��Ԛ~\����n�T�a�m(�oT�r`�uě��'ܛ�� �g8�s��<N�D���@�nT�lH�e����R��
Y-��X�Va��e�i�o��u�y��EPfd̅e؅k�m�Vr\��Eąr���D�#X������\���X�aD�bT�c`�d|�e��jԆl�m�n�t$�vx����	��L�u��
�����h�e��p�f��v��r	̷H
ܡ���d��g��lĆsdL��OU4�����|�̆s��X��p����<Ki�s�t -�7�У�أ�e��0�o���
���̤� �[����P�b��c��e�f�g�k$�l@�m`�nl�o��pȈr�s��v,�%
4��T����fȇl�n�r�s��z���
��.؇e���O
4�nT�%��P��	������e��0�'0�i��,�2����8�aP�z��9T�����X�g��@|�g��tl�F|�E�\j��X��e��t����%�$���o��M܈t\�����a�d�iU9��\�Tԫi$�	H��0�a��e؉i�o��uȸ�d���(�.P�a\�dd�fp�gx�i���Ht`( �Z��.���n��ax�����c��f��g��i��lȉp��9�h��(�U�5�
t<h���Љf�j�s=�ܮg�p�9���0�p@����e8�mH�op�ux�z�Go��X�vx�a@�k\�md�s��Z��~�h.İO
�
]�x��i�hW����.��k���L��Ԋc܊e,Sf�kpol�o�p��t�`X��,,n,h�X��,`�Y$�����b0�c8�dH�ip�kx�n��sh���l��O
���@�nX�t��R	�jh�k���ķ��r��ix�?��e���@�������-и���lȋp\���b�4�#`
.�a$�b@�cH�dP�gX�i��k��l��m��n��p��řs�u�b��8�a��ex�l��X��bD��\�,$�kh�tX�M�h.|�l��s������.��0���~�����L�.	������v|�ET[e<�$�،r�����a�eL����a`�r��E$�i�E�r��,���8�o��,��E��.X�c`�nt�s��U���l�e���X��P�����.$�a��bܓc �d\�eYf�g�h�i�j��k̤l��mL�n��o$�pH�rL�s$�t�u��v��wиy�zȝ�`!��0l.@ �k�ql��n�qpЎr�s�t����a�b(�c@�d��f,�gh�kؐl��m�n�od�p�{q��r�sT�t��u��v��x��z`9� #|�o�"����d��k��s��wX#�|#'P
t<P=x$\�p�#��ĎsM\�a|L܎t%�s4M��%�t%�e �lh[�P&���a8�i�i��'X\�ep�o�6rx�s��vl(h�r�(x0)�)�0�����e�R	�*��	��.ȏdЏep^f�h�r�s�w�z�*1+��x���؏a�+���o����+������|��0H,#�a,�� �aL�eX�ltyr�,��,#D�e<-�D0�X0��`�a��g��k<l��n��pȐrАs�b�teZ�e#��n 1=��e41�m��1��1�(2T��a �b0�eT�i|�l��m�Zt2#�Oa�c�'�t2��tH}v���2#(�.H�n��v���@�t�3X`�g4��$�O
��h�al4kp�i�4��4T��a�6#@!k`6����a��bؑe��p�6U�Ha7��t 7#̑n7�7T�j�7|�u�%�p8���I.$�a<�dP�kX8�4�gX_tp���8�zr�;=|;�H�r?��>��\�e�i��l|�o�?�A��0ea��b��i��m��o̒s�t�w�BHC�`CX�D,�C\Ēp�D'�D#ؒaE�a��i�E�pF�F�,ua,�j4�olep<�tt�y�G�8H9�HL�a�rJ��IL�ax�i@j�l��r`�PKXp�oL�M�@�tP�dO���oXP��P�Q�Гa�R�xQXȓl�c��hh�a�e�i�uPh~�ho@i~~�pcL�o�S��,�t؆��4�sX�#@�r�.vX�#��a̔c��d�eԕf�h�i��k�lP�m��n�r��s@�tX�ud�v��xГ��kĔm�����t��$���h�o��\���ܔt�o��i�u��aT�d`�gh�k|�m��n��rĕt̕ud�x.؎�0�ex�k<�t�H�sܚ���Ct�lx�P��Ȝ���h������e��m��i��~����nD�ET����$�f�1lp��`�04���r����eP�g(�l4�nH�tT�wD��ԥ�� �od�X@�d��9\���j�C#�_�T_��\�e��s�#d�n��r8���t�e��k��o̖sԖw�_v��Te)$��Ėm�j�����X�i�����a,�dH�eؗf�fk�o �sD�u���t̫#�n�^pp�ya���p�ox�t��#8�c��e��f��g��i�^lȗvЗx����l�/Э��n��������.��e��t�u|�/ ��Į���L\�����e�g�od�p�rВ���8�Eg�]d��4�e��o<�uX�M,�,���h�u�h�b�xmx�o��pd���X��#p�r<%v(�Z�����r��k��e��oԮ��	�a,�e�wh��kl�o�fq��sșt�u�&XX�O�hȳ#�c�g(�k �rp_up�H�ش���e��@�dH�lP�tH� ��X�Eܔ�и��X�eP���`�r�b5�#x�r�T��c��n��p��u��<Թ�t����u��|���a��eLox5wx=��:��C�.��J�np��T
\�a�eP�f��i��j��kěmܛoT�r��s��9�Ssh�#@�dD�k���
L�a��b��c��d��fĚg̚lԚnܚpd'st%�L�,�����a�X��e�q���X8��`�X��X��U\�#�c�i�l$�m,�n|��k����X��X�����.0`k@�v�5�
�Q�7��H�.h�dp�rx�tH�X	�8��Q����H=.D�X��n`?3d���P�.��t��wTBX	�B�����h.ԛi|�X����Ȧc�d�f�g�n0�p�|r�sL�T�B��� �s�I��,���(�e2�N��<�id�u��lD�et�oHN_LKZ�N��l�r��#ar����p��t�ga.(����eH�o���؜a�}mXo�t��h���Мp8�XT���a�e �o,�r8�u��ohl�n��������p����+iL�������ga �r���T���P�v4�(��a��e��l�JrD�B
 ���|�n�����E��r���\����.��e�~p��R�P��؝��g�0�8��c�a��eX�#`	a��,8l��r�
��H&.4�a��eD�i�imX�oܟr�u�OX�ad�dl�kt�m��n��r��v�xHn�'XX0��~4
����|�s�
��<E
��a؞b�e�i�l��n�o�r,�s4�t���Eu.�R\���vX�a$�eD�\��DxhxX���<�n�	��P�ex�g��k��o��uԟv �0��|��f��,H���iX�X��d�
�ğd̟s�
�dPp{�����l�m�rX���lXX�.\�ax�b��c��d��e��f��gܡjl�k|�l��m��n`�ox�sأt��vlb�ib8�ep�p�K\b�c���a�e��i��u�i��o;p �"
�I.�d�f�k(�n�pL�s`�tju��v#w\#���hsL$<�#T��n�o\$Xh$X�nd%���3c@�s�%��%T8�t(�X�l<(��(��kakot�w�)���Z��E|�n8*`��el�X�*��e�<rp+���aȡmСu�U�,���r�,�̷d�e�k��n,�p4�sL�t�cz-``M,4MT�a$�l�M�St�-Tȸ.D�t�-�-`\�r\Lw�-$�.X�-d�oĥr0��s�2�3��l�m��o�3ȴE4#��d�ss4����a�cld��f$�gPlhd>i(�j�dkH�l��qLr -sP�u�dvX�w�o�̸k�uL��l5T�i<�st(�5\4�a��b
8X<P|,8��Zmp�pP9��<��a��j��l��o�dpģt=,�>��>�\?X�n@?�?z��aA�@Уe��h�rPBT��TC��i�o$�,E�,�aH�e��o��u�EO8�a�E�	�H�
�H��@�ed�kl�nt�t|�u8�~I?0JxJ9@R���u�R��W��Xc��a�C�\����sY#��r�v�ܤe�iX����.�b`qsĀ���,�e@�o����e��T�t(���s�# �n����#8�s��p�a��e�7���\�oT�#d�m8�E,�#|�r��s��]
�����.��a �b,�c@�dT�e��f�g �i�k0�l��m�n��o��p\�r\�s��t��uԫv�w���c�rP&XAX����z�lL��8�h�����L�a��X<����d��j��k��l�m�n(�p4�rT�sh�t��v��z��w��@�t��X������a��eЦi��,X��0�OȦcȞ��t,�Oܦa8����ad���dе�|��e������ �l8��D�iL�nD��������`�t<�A\��� jjx�s���
��.��e�t���4��8����ħadЧr�t��,��#��r�����4P��اh��=|����n��r4�X��#�n��T�a(�s��z��TX�al�e��f��id�k�jo��p��y��Xd�a,w�4�,x�t�����d����c�e���
x�x����r؝?<���4:.�a�l�Ho�p�sl���t$��ܨa��k�yj��
$����I.��d0�eT�g`�i(�n�^oh�s���xeD�nL�r����~<��h.��&����k��E��e��at�m��n��
���	H&.ةa��e�i�m �o��p4�uP�v���ĩt��̩r�s���������n�t<��X�X�z�,�s�D�\@�s|�~�&x�&H�a�����I.��ad��eԪg�i�k�m�omrH�sT�u\�X��g����9�#��cȪnT5v����s��a(9���a���4�xL����a<4�l@�m�H�)�,�i�H��~Tlt(�P��at�l|�p��t�x�
�,z��eh
�����a\mr�
��X�d��rDx̫e8�4`�e���@�a�s`E�n�rH�m�����g
P�
P<�emiP���4�t,'d�ax�eh�i@�o�u�'��ȸ.��a��c�f�g�i(�kP�nd�p�u؈zx n�'���d�g��g�����4(������(�̬.�(��Ԭo()x�e)�)#��zd)��n\/
D0��)�� �a<�e� tx)R�*�\�a4*��D�s�*�+�� x.?�x/��p�.��a��c��d��f�g�i(�l8�sP�t�u�/�00�h0�h.ԭa�e�u`0��0��0#ܭs�0�t1~�1z
��2��s �zL2�3��V
�6��0�eH�u`���6�(;ȸ.��u�:��
X�b��cȮeԮgܮj�l�n��o�s�t8bXL;�<;��oT<,\<����a�;��t�<��<g�=9�=��<Ki�>��>R?�i,?ȸ.$�i�A���G��,�y�F��4�d��e��f��g̯jԯl��m�nx�o��p��s��tİu�v���(GE�"d��s��x���G����yHx$H��G�����ȃ�\H����.�a�o|H�H��H��\�mz�H���.4�a<�dD�eL�nT�o\�sl�t,I�LI9|I�I�I$�I+�I��I�d�jJa�gd�nn��s`�2�J,tJ����i\K'|
��K����u8LxаtHL8X��h.�Y��ذc�f�g�i<�kD�m$�9ZC0Z��h.,�e�m4�t�'$Z=<Z��Z'�\��a��e8�i`�j��l�m<�nh�o��phq�as`�t�uH�yT]���Nd�g��m��p�os_#�EXcX��dl�eرi��n�r0�v�d�n�dX@�d�fLf�e�g�f�8g?(�e�f���gD9dhEj#�>jP�n`lb�k��H�k��t4m��>a|�e��iIR�m#t�n�LxxoT��a�e�i�o�u�o0�o����a̲cԲg�mp�p�$(.0p��p��q#��b�rt
�r����e<s�s,$e0�o0��\u#(�e�u�d$eP�o�pu�v`�r Ftw~Tw��LHb��l�m��n(�p��r�v8xL��ihx?�x#�y��zEx�d@�n�z����aܳe�i�o8�r�{��p�c��t�|���l���}Jp}E�e �o(�r`qt~�~9(�x~0�o��u8����a��n�Ur��s���H�aĴe��h@�ix�j��oܵr�y�� ���kP�Pt�����k��t�=����T�?Pd��e�k�l�m�r,�s8�u���0����o\���l�s �W��T�h.��r�sh�R@�.X����T�jh�lp�n��m`�g��'	Ԇ9�~8��X����e��nĵr(�������pX��`�����g���Ȉ����e0���@	d��еa$�e�i��o؉x���t	e؋��rD�s@�u��X���D�aL�e̶lضo�r�u$��\�,x�l��n��r�o��`�e��p�h�od���\��tș�|j��,�T��i��ܤ����t̤���e�����Ao�p���H���������m�)u��4����a�%bh�ct�e��iķk�l��m�n�o(�r@�sБtt�u�[w�o�p�xX�`�hH����uȼl��\����g��n��p��tp����X�R	\bj�'зi�b���,��طi0����p�\������enx��,����� �b8�i0�X|����,p�$H�a��P�l<���\�s$�h�r��(�a��e�������i�JR��\��fl�����jȸn�����
�pl
s��P����a�i�w��X��������c��n��p��rعs�t���a�bX�c�dH�e�fD�g�,h\�i�j�k0�l�m��nx�o��p��r|�s4�t$�u��v��w0�xD�yP�z��p8X��e�9,�>LA��x.йtE��F���Sa�I�QL�a$�e<�iD�oxQX�c0V��U���i�T��l4�m�V,(YA�\�P�r _,�cp�h��k��lкth��T�e��t�,���l�i'��s�j�j����i�k8Xk���e�n�tnkȺa�i��X�X�n�p�a0�e|�i��o�r��s@�wq�(.(�mds,(w#�(iL�sX�z�}��}�D�s8��d�eX�������l�Rm�#l�o���В��e�X��gĂ��o���	X�#��eܻn�oؘv�����Իd���ԇ�D�T�9l�m �t|�"aRet7i؎T0�al"i��XL"k$�Xx�oX���
<�c@#e4-f�i��l��n��r��sȼuܼz�T�#�������e�#��i�=XD�X��l����>tT�#Լl�~8� 0���P���������#��nX����a$�l,�r4�sh��	��H��\X�,����<�at�dĽeнi|�lܽo�r�s����l�a��e��o��rh�XD�X��rX�T���\�5�����o��#��l�#�+j��T�rX��T�e ��c�e(�p<�t��h�c�E4�a����P�r	9(	�H�a�
��a��d�eD�fl�g��k�lH�mp�n�p�sX�t��vl��cd zp ����aľo̾s�uh!k�!�ܾp�t8�X�r�~�x~�e\"?�M.4"���e�"��"�0�n|�d%��(�t�*T�*<�a\�l��rd�u4+`�+Tp+X��a|�l��o��r��u�+�X+���a ,z�P�,�-�̿a�lԿoܿu4.z�.TtT�/T�a0���a�e<�m,�oP0����0��mX2�	<2#$�g@�o�Hvx2�3XT�a83z`�g,34�4��h�a��g��o�s�tl5l�6T��g��r�6A$�X ��l7<��o$7T��l�7TxFo�:���dl�o��rL;P`; �<� �a
c(�lx�p<�t=z�>P4�u<s~�?��@P��a��l��r�@	D�a��g��j��l��o��r�s�w�z��ؐ,��~�^�A(̤2�AkPBd�B<|B�l4
n�p4t�B,�M2�2TC`,E��XT<�at�k��lkn��okr��s�w�Z�Y#4�lL�s@\��e��e��T�s�e#\�rTe=h�e�e ��a8f��e����sPiz��c��p�i�k���l�k�p�<oT��m��p�t�pjq����a��lr�|qE�iPu@w#��c`�mh�p�v��
�ax�d��e��f$�i@�k\�o��p��sPu�y�{E�}��}p�a��e��o��u�|o�{����kT~#��r�~o�~#��p0��X�#��s��,L����p�\������l�o��r0�,P���?.h�g0�nԈ#�n����������8�a��eT�l�� ��#l�pssP���Tx�c�t�a��i��l��o��rl������T�P|�X4���a��o��p��t�z�?�.��l��
��ex�#��f0�r����a@�dL�e��o��sD8u0�XȢl��k8�a�O\�ed�s��D��p�t���$���#x�m�pT��d�T��o��tt��T�#\zc���
��a��c �d\�eh�gx�i��k��o��s �t̸L�h|iz�����rļXԼG�a@��r0�s@�D�t����<�a�4eT�E,�#T�t��zTCt$�#p�v��P@����r��E��g��pP�L|�A����E��aRe��o��r�u��T��t���@�]H�A����ih�X��X�g��T�a@�eL�j�^o\�sh�u��X�e�������T�md����X����p�l��n�o��r��s��t$�X��#muPXhz�����a�
��a��e(�h0�i8�lX�o��r��td#�#�k�r�L������oBsKlX��	L�e\���D�i(#l�lt�m|�pD���
xXL G�%�\%���r�'#�a�c$�d4�r<�sD�z,'����aL�dX�e�f$�g0�i|�k��o�p0�s�t\FuL�vT�y(%
�'��n4(OX(�
`(���a��v@+]�+8�,-k �ox/
��c��e��l��m��n��s��t\��00��|�h�0E��n��s`1dD3���r3����u<3�P3X��bd���3E�6���s(<t$���6���a�7X�a�7T�9X�8�i�:#L�gX�jl�nt���<D�l�<��%z@j2�=��d�d �t�?X��e��i,Cl��s`�l@E��s�?#��n�@��@#��e|q�Bk��t�F#��p�r�s�ktJ����b\Kl�M$�L�r�OX�<rT�v�N �ed�fp�l��m��n��p��t�O�PPk\�e�P$��a��i�o�	Q����a<Q,�Q�8R$�R��a`$e��o�R�HSEЎr�X��e�S����k�S���a%h�i�T��kl�xUXDU�a8�r`XXD�e�WG,�o���[|�\��]�T]��\�a�Fd��mGp�\��d�a��c��e��i��j��l�m��n�o0�p`�t_��`��r�b�XcXhGt�jj#��e4m�xo=�u(Tw�(�s��8��@�����Dz��zz@�iH�u�|,��8��Hg��l���P�a��e��i��o��r�u<�XT����i���l��X�����o0�����.����a��e��o��up��������.�~������r�)u(�X$�#d�a8Jc�fWnt�s����
�a�nc��e��h��j�ol�s�t��u�
w���n�5r�x���a|�s\�#��e��k��m��rx��X��s̖�P���mԚ�\�����n�T��a����oX�X��#�f�Ao(�pP�s��t��X���� �e@�l�	oH�r��ap�\���]�����X�aL�`�a��c��e��m��n��p��t�u�`������g��X��n��<�T��X��a��o�����`�L�aLMe�rH��0kܵ2�$$�P4�<�r��t��x����4�a\�ed�il�kt�o��sX�x0��|����8����<e��i0�|�t��4�X��a�&r��d����~��s�E��lH�n�����e(��g �r������a4�e��i��o�r �u��$�,�td���#T�c\�dd�ep�i��n��s<�D�X��E��n��4���x�nx����e���l�#��j��n�����z�-b�������d��g��s �z,�4�����e�g��|�����e����`��8����(�a�� ����<�aP��h�e|�il���E`�t�����t�eL�P������s����t#��b��n��r��sQE,'���Di�\��El�
�.L�a��b�c�e�it�j��l$�o��rh�t��u�cy�T�$x
a��r�#��$�t@ 4�r���@�a��d��g��k��lD�n��p��r��s��t�zF(l(X�'����e,�0TX0����i��l��o,1A\1T2z(2����a"m��o�s<�u�4X�e4���4d�i�p0�t���5?T"k�5
$�e06�p8�\�ah�dx�g��oX8#\zc�8�d:�<:Tp�a|*r<X��c,<X�>A����e�F��Pee��o��t��u8H��I��HL��r�I||e��h@j0Kz�PL�c�X�E
?.L�b`�e��g��i��k��l��m�n��r��s��t��z�bԛEt�e��ET�l|�p��s8�8�x����,Rh����^spUt8���~��a��d��ePVk̫#�egp� 3o��X�~��a��ȳ �r�����a(�d�k0�o8�rH�s|�9P�zL�[L���T@�ed�l��p��t��u��(|�a��o`p����t�n �,Թ\(�TOk�����a��k�E��a��e|�~(�?��kt�P���a\�dp�e��i�k�o0�pD�sl�tx�u�Wy�,�aĝd8�gH�nh�#Ĺs�E|��`���@�d ����kT�o���\�#h�c��d��g��i��n��t�� D��|�B��s������.��d�����-~�����d�l�sD�X��j�'n��~���d�w��L��m(�v��X�t��#�`e��8�p\�t�S~(��T�a$}o<�d}w��t�����tT��H�i������j��o��r��s(�X�gg�go��r����� ��o��J������e0�,�t8��
X
P�ch�d��e��j��k��l�nTjo0�sh�t�����0�a�X8�p�D�e�!T@tp ��\�s'�8'��t�a�"|�r��s̲t�u(��n\((�J�,���fP/�-��s�c�2���j0����tl5X�s4����g�5�@_�=#�n�<$�aH�ox�p\�t\?#T�t�?��?���r�@ [r,EL��e�H��t0J�vX��a��e��i�o|�@w����rP���^sX�����i؉�����sԈ#��e�	�X�����s�����t�vĕ������.X�e��g �i��l,�n@�o��r��s��t��u<��l�tP��\���d�a��r��s��P�������p|��Dje4�T������e��g��i�pH���k��a��r$�X0�G��ad�Td�j�����n��X�t���e8�u$����d����8T�dd�i|�r����@�0�\�t��a����p�n�,����t9a��m��s�����a�S��e���tP�	���^j�
���sd�,'��e(�i27x/���i�;D�s�:���e8�j�<��YkH�s,=X\�t<=�H=CT�e��L��H���p�s4���x�e��l�u0����p������.(�a�eb@�c��d��ef@�g�,hl�i��j��k��l��m��n��o��p��r�s��t��u��vxXwx �@ �d`�r����ap�b��d��g��l��m��n�p$�r|�s��t��y�#��qe�Qst%��ax�l`%X�'T�dr,2TxRn(2����a`6Xt9=�8��rp8����d��g�o�s<:T|*r�sh�p�:\��l<kx<��>�A<�cD�dX�sd�t�,b�A��D��e�C\L�tE�
jt�o�E��F��T|i��l��p��t�G=dH��HL�$r�J��I��e@jD�rlP��pk��o�����mX�#��oX�����a<�cD�d��e��g�i��k0�lX�mx�n��r��s��td�x4�z$�,��@�eX�i`�r0�z�Pp����h�i��r��Ep�k��l��s��Pԛ�ܣo,����ex����t ������a��e�l�o�r8������<v`�#��r��z�=���hUspUt���`ym���$�e|iH�oP�s�'d����h�ap�e�Xt�X����a��e��i��k��o��tȳG��dȴX����m�4n��X��X(��0����a|�TLo�7������f�g �kx�p��s8�fl?d����aD�l�`nL�oT�r`�uh�w�@=lA��AP�1u�BP�B��L�X�p�a��l��rL�c�M������t(���e�����p��tl�#(�o���T����o����ȸ.�h�aj�sx�u��\0��t�Nl���sX�E�n8�(�e���$�$��H�a�"P�l��t��\�e��j��kT�l�nt�t�(E�,\؉d��k��s`M4MT��a�-��l��p��t�T�-��U��-C��a�-�d4�p4��a��r4���d,�g�r4�sl5'$7LT�t(����@�a�7EH�ad�r@�xA��@l�h��j��o��r�A ,E�Xk�vk�T��a��e��i��o,�X$�X��X��4������b�e,�lx�o��r<��s��8 �t4��<���a��E<�aL�f����������D�s�A��TX�f�X`�s��al�n��r�=�k��r������dD9���e������g�
T5T��t�3����sx/����n,'����e�\T �aԊc(�l<�oH�pt�tT]`xoT�e�x�Tw��4�m�zXX�l`�r}lx~`l�ah~�����ai��T��e��j��o(br���\�#��s��	e4��L�������a�*�����f|�gX�md�n����a@�cx�e��f�,h��i�
l�m,�oP�pQr|�s��ttpw�cy7l7�8�t 7#@�n`6��L�ep88p�t�=lX�E��n��r|�������d���<IoX��"��t����e��gpo��s�tp+L�?z��a��o�<��t\@?�A��@�i�$����^o���� �n@�pH�s�P �
h�r!�t�sL \�eT!��\��<noXnp����a��ih��$�����k����X��e�4�s@�t����a\�b��c��d��e��f��g�,h�iD�k\�l��m��n��o�p@�rX�s��t �u<�vH�w��ðF��p�I �R�xQXH�lQP�a��e<��W��l�t�T�t�r�c��h@No8h����o�p��vi��rԇ$��o8�8X���s��8�P�����o����u@
��n�,s4pG p�$�e<oT,�g�X��8�s@wX�0l�v��P�ap�e��oX�X|�t����X��lD�P��D\�k��l�����f�3p��� �a��e��o��t8�X,�#��r��k����n��sP�t�
�h �o4�sH(8,�t@h#z�i,'P�e��ix/��\8
��a��c��e0Bf��h��i��o��p��tt�yT]��`�Xc���t�gti�j�Tw��z��?l8������a��r����0Kh�o��8�f�p��,��4���.aP�i4�r��������a����.��a,b��c��d��e��f��g��h��iL�kT�l\�mx�o��p��r��s��t��u��v��w$�y,�z�$��#����t@ ��r�����a��f�g �kL�n��p��s�*`�-��-�t,���sX0���ya0�r�1 �8\\�ad�i�rp8��8�dt�g�8���X�:�<:Tl�s�>��o�F�c�pX�E
��e4�gd�ip�kx�l��n�r8�sl�t��v��L��E��k��l�r$�sԛ��d���Wv����o�sd��x�`�,��,�eL�s8�P �PD�l��p���X�s8�t����Ud��e��f��s��#�m��d��yol��|���a�����d��i�k��o��X��#��nP� 5g�n��?������a<Io$�s��\0�e�O�����c��e��lX�t���T��P�e|�z����d�a��h�,i�aj�ar��a��n4����e��8X����
z�	�eL�fT�gt�j��k��n�o�p(�t,$P�#T��l�o$�w�"��k4�l@�sh$��$L%��$��,�s(���jl�*p+���a�M�4MT`�l�,�h�k��p��s�SXS����o�-(�/��-��w4P4����a��g��k��sl5T�s@6�p>l$7��t�7E`ta{r,8�0;g�:���i�@�<�e��jD�uA��B��X��v���v����d�i����l�d��e��f��i$ll��m�n8�oD�ph�r|�tp{v\����j��s<�E��t��,�t�`0����e$/���}<�����e�T$����a,�s��t����T$�f��a�i�(9�L�at�l�Wr����T�g�B�����h\mr�
�,'��\T���(!�������i/r\����d4�����i�Ws����E�e�\�e���t�����s�����t��,P�zH�i\�oh�w��X���@�nh�Xx���T�r������e��p�. �s�e#x�r�A	��e������k�����k����ex���r���tD''��s�&����t�����h�8o��c #�a���e��.��ĵ��,�.\�s��tеX4�r��|T�.�����h�.��s���
p�g����iH -��.p,���e8,���t�;T_����.�0#��n0~��e8����kD���aP��t\��s�E�r��E �ep��,�e�I����D�.�e��#L�l��\�e��h�g�#t�n���e�a���m�aE��rXa	��e(�#��h�W���cp�t�XH��.H���n�,ITy����.`y#�np��eH�,�gP�$$�a\�0�l��th���<�st�L�rL��X�u8X�d�uT_��D�.��|�n��X��e������k|�i��a�����k����e�q�r�9T�tl���st����d�U��n�-C�aZ�t�Y��s(Y�$�j?y��A<�.�s��9D�e���T�n���`�nt��l�e���x�t�#��n4A��aA#��ndb��epb���t8bE��i������.����~�.L�#�l ��a��~�a��X�r��$�e���0�d���qXH�. �P�lȚ�\�a��
h�aH��P����.0���n�����e\�#��n��d�����.t�#��n`��e e�k�5\�eb��tbT��sb���g$bX�n0b� �i�aT,�t���9D�.ؽL�l��X�a�z�d�a�zTp�tdz|�s$z��d������.�����l$����a����t�������.�s�1k�e����l\����lT����i�#�u�� �e�,�f��8�e~��D�t�EP�r��#\�o��h�p0@Tt�s��#��r,6z��e6��t�5#��sĩ����i����.H&���~�.�#�n�T�e�����g��,�n���i��X �d��|,�f��X8�od�D�o�(d���\�.t�#d�n`�p�e e|�k�5\��e����t�T��s�����g�X��n����id���l�6�9�.\�lh���ax��a���t��' �s\���,�t00��8�h�B@�vP�.H���X�t0�#d�n<�Tp�e�S��|�l}���a�|��t�{����sT~#��r�0��e����db��lXa��i�SDW���.��sPW���e<��j�����t��$�r��#0�eH���<�i��EH�n���T�e`z��`�v�a8Wx�.�p�����.�����i������e�#X��l�#���e�|��g�|���e�{����.��
�e����t�����s��#�lH�T �e����,�gp�#8�nx��z?P�.�z�X�e�zTd�tdz~p�s$z|�d@�,��n�k��a������l����l�PL��ox��z?�.�z��e�zT�tdz~�s$z��d@w���n��	�a�'�� �l��X,�nx�$d�D�.0d�L�e<dTX�tdd�s�@��p�d�@#|�n�w��ix��z?��.�z���e�zT��tdz~��s$z�dC���n�BL�a��l|�#��r�e�E#d��k e���?8.x�@e��Lt��'Xs\���dt00��phN��|c��l�ex�0��.<�q�eH�k�tT��sԃa�d�?�e\���eD��t��*�.�s��mr�� u4>
,e<>~8iH>#DnlXPeP���\g�LGhn�ti����s�S�t�S���a����aK�t�JT�s��#�r���e�� ��.4544405400153501054003400100420050014214535245452040344304152200300420324323320250534505423050001404130125402414532020215441053044043010215051001402334045244513240440315044100401545015243402450345125424324242524012345044125244044354504003100252422220434221400024342531032105040110215222340425025043402544105142244430545400404002200244401503152405003034431500242052042404522500200040123244508889800120145411450202124150254312514330112324500004014430014300302043003003021241304034450313440210300045004030023304022041504224552003010442230444004440021040410001000320053004005334214023002142400004120300524342320303450100221041404232243043400304222303020104314210012420402340501030024300202301304445440043004320040142432540424002344454041433230410000431034101010042343500430004320454004234102041030014040452040204320434234203300314222442420202340025310450404421023100234230000132240041100321040100541020210504240503014140030010045434400121230103444405000054402030043442024043343423010202234212001050040000054400241021204343043322145240031030122304054023242450004404542040402504524010334410441400103050024000300003040030032323041404303020000502103020050430500105005030403204400445002030220403140004003405023234314303004544214402452003052450400231105503520500423404250003022440020423024530100305044542034225045404213415010410430254430203402010050200500000100205004005004105001005200250043005105223025420123200050250213210423340004004040104433420454202003401025043004543043052120540004543041034451220305400004302043410251004340004320004012434400430400500300100510014104302004142350005420041010532344003450232102123034040045000004301443010025230232002321245302325034403504501234304341445012544505012044343212432105403050340321432323444540104103232504501034030034300543003014043010503001010430040404400404343201250004043002052000345003200404002402305220010542404324041441040340242043402052040540010540124002504041250045425013120204004004003043241043230412500323401232020223220342404304254044034502043442232250445210404443230210413444404035425003352344512245325434022014040444300502404330432503443534023000432124043043434400344502025410450000305404310043230452102520054040454104521223440505022550025500454410434202132300000514050000540543452500520010400204300400405032130053203030035004100451001043342005004012000124452300344105401005232210144551045403202302050000005544003023454030030510304342343043032430121020324003210032203240254002501050004002504540010050500045010045040450020441434014420045030500123032430303004053505344450454442034430410304530210325005002321010404102023043044200204020041225004433000233005002104330020443013042040305440224204005054540312502254125000100500110050004100402334502203220021200541002013221023012440005002004500450003040502503500105402045400050001045004030045041040432403400500431444300140301520022004050030244044100402504412314022250340040540302403101101302443312543404143540033020204403030001014454003530003530554005500354055000200214452042350101502025424304021301003300124304503034221053004450214321432443254220552100300425402005404004404230230232141001302132032410500430300434044510210344452534005002444003241010041230512034140500434302203043034032430220523034040504000250504000102523450504303244021443232214004023054400052205044004431045020054042330305202330010544400521040402044021234034102454250034004003042205024525001533100212241340021445343052043000030101400212105055450204334250025010441300010230322121042410412313403043201032042450043200423434503445422054002504345000205004325403224223304450003400430210500420540220341432300305400545000305020212123410040000313200500003032342404204140034340042024310440540041020414101030300544524545440043240105421043121030010305050010104342104104030043434312105030010010010340444004254444100420054200403204503004000324050004040004050240450210400540010033002502054024014100252042444544040324052054033041410430411041544114301043401003014443031234432205240241305001030050244323445220250403400105023541205000443000230043030304200341414543050520030543002004023240001043002103443254230340000340012102103005222425214030042032230305410500440040014503424502404051030142404032021020301223034050525014054542110054012431325000020204304523045030005030100412122310251020502100041210300542440032454304214020450010450040053040205204541004053004050105021225225000240050304500541301232142121244203422021430100045203405000141000041002050001245200250004234050032000040052500344500024500104252123202103244030030250150530204305044323410130405234323214032410425432030023323405225012450220430030430045400434505052040054002045403	5000000005043005000410543241030200422141030543212433402002103054410030003054025021045500430430000034444144005245030023432300430040054000004542304324004520004000250403021024100502243400454504210340210540041043034421200403100340020340040400043054045450002105402104300105000000044324501204432002001250005004500142050040410040321034042403040400300452043000404230004454105020005000200404024024525043243043241224015241100530454224341434234145030445454450102502305023452200505400503000301205040240041030004434200100031004001230504045435020514250000400405420100300440400500340500002210031001001005405010424301203455205300125001450003005050201343305010434001050010005005400430541050010230420050004345441054414540320500301500230502400302100502030502100502200402404500544401043223204045041050040410432100543340503410012410345214401445242333003434243023332003144120152454010013032300054015443588988888988898800889888889888988800
888989880088898988800
88898988988008889898898898800
1000000001100
898888989880089888889898800898888898989880020001402000024889889898898004000544010500300889889898898800
88988988008898889888008899880088988898898800
8888988800888898008988988898898988889800
100000010089889888988989888898800
8988898898800888889888008888988898988800
889898888880088989888888800
888988980088989888980088988988889800
8898988889800889888898008898988988889800889888889800898898888898989898800	1000400108988988888989898988800PK
!<.���T�Thyphenation/hyph_nn.hyfHyf0tP�����'-4�������$�'–’-111001���������. �a�b 4ch>d�e$f@0g�sh��i��j k�]l��mLn�eo̵p��q8�r�Bs<�t�?u@�v �w8�x@�y��z`� �4aTb�c�d8ftg�i�k�l�m@nxp�r�
sdt u�v��xa`
b�c<d�e�$f$)g�-h2ih9jH;k�Al�Jm�QnLVo\p�_r�iswt�up�vP�w|�x��y��z����t
�<ade����Dbts�

�*�lt|��n�<�i|����tO#Ț(�s|��l����e�lr$s��-Te2�.��6�ae��e.x�:�=l�APu����,fHtD�EğIW.��MXa�eС��dn�rtWP.�T0�X�Z�kn(rDspt�v��p��.|�]�a�elr�'���.�'a0�e��i.��me8�qh�-��0.����8a\e�(�T.�t�ha�e�r��T.�r�Mx�e�{$������X�a0�Z
b(f@gdk�lmLn`s�t�v�+������n����e|�mu��(� a\�T.<�]4ePiP�iP�2�.��Xa�e�oh�(��Px.̐(�����m��(����.���a�e�o�sL�T.�g|��@�i`���'�\��.��a$e8i�-�.���d�0n���Da(��غ�Xkpo����xT.�rĻxa�e�o�����T.l��<����.���eT�A@����b�em$s4t��t(�.4���aeT�T.����t��,a8�Z
xa�d�eg<jTk|l�n�s�t���pb��T.x�t�a�e�r����x.H�\�aD�������2���r�s$��<&T.�t�ae���T.(s����3��T.�30a`��.l�]Hade�4T.P����.\�pa����a�e��T.�l�:��PT.���a�e�����.|�T.����ae(i����T.�T.m p ��LX�h�T.@nHsPvDZlZ(�(h�-P��Xn\��`ax���ll�p�t��<�����H���i�����b�d	e$	g@	i|	k�	l�	m�	n
r0
td
v8�.H���e<����	o�T.0	s`�	e ���Z��8	aP	e4�d	n�i�P\	e��i�.��p	aa�����	n��	ax�T.�	a$��	e��8�2T.��	a�	e|�2x.�	sD1����.��
a
ulA�����.��$
aD
eP
i��2T.L�Xx.h�X
ax
e�
iX����.�
d�
r@X(Y(���8�Z�
kl(p4sPt�������
a�
e��"T.�
s�'؏-�
e4�2��
.��8ed�.p�Xa��;�i��<u����Dr$�?����\h�k�l�n�o�s�ty��8C�i�T.�G�e�2T.����a��:����K�nx�t�e�rx�Ol�i���]e@���dDeTgxk�l�r�s �S�������Lade���L��.D���la�P.�����eL�x��W.�����a�e�k�p�i".�\�l_��X
d$
nX����i<
lX
s�c��hБt
e��
d|����.l�0
aL
e��T. n��	�
a�eDi�ldodr�uy@�r���
y\x�
b�
l�
n(rPt�vP2�
.\���
a�
e�"T.�T.����
ek<6|]lL2�.X��e8i��T.(�.�	��De\x.�n�t(��\e�ix
��pd�e�kl(nPr�s�t�x�e�Q����p.�����dHi.P���e�"�.����a$����r���a8e�2T.�".p�Dade�i��.xs�t�(���x�t�.p������a�_�k�tt���n����e�8���.���ae�rv��.\��4��$.�M,e\x8dde�l�tl�tH]\n(T.�t���pe���|���r`�a�e@i������d���e�kt�k�I���q��fs�*��q�t$v�(��r���,gp4n j�L"La�ed!�Td�g�k�l�nrD"�.#��"]�s #��e��w��.`#�n8{�$��a�$��a�$T.�%�e$i,rx%���t�%�(&����XCnx�0C4s�S]@d�(�Ldd'�Xu�|�|��p�h�`+2�.h+]�a�r+��d�e�s�D�F��+���t��t�-�y0-���sH/.T/���e�.��d,tt0.�0�� a�0X ��8�d����1��Pad1�Xk�n<-2tn�r�1�|e�- 4�ae(o����T.|4�n�3x�e�r�s0��4���sP5t05���e�5�5��b<4t�:��s(<�h>�	�a0e�i�orsu�yü?T.�?��he�s>��tg�l�m�n�tu�@���
e���t�#�eTA�$B'T.�A���exD,�D���a��� E���s�\�KWr�K��a`o�H�� k|l�m�n�rLs|u�v�LA�L�Xd�MT.�iPM��le�sN�O1xPT.(P���i�o��6�P�e��HR;�lDQ���s�T�.t�S]�ei8oTU@hU�T.(v��E����UJ0m��O�VMDm`opt�WS X(�WXho�Yx��Z���s�YM�iD\��[���a�efnPs]�]]�l�r�t@]t��T.�]\]]�t�_WT.�_��e<g�*a4`d(a�_t0s�ahTa��Hk`t(bm�lqPihn�r�t\h��pe�m�r`m��m��ju�j���elh(l���a�e<'�.pox|o���mn���u�m�atl?xt~��aHehf�g�k�s�~�T.�~��<nXr�E��Gz��`tXHT. ��te�M~T���np�"T.4�M�e�t���Ԃ���.�����e�����r���.�����aȃ���l$rPv�������p��4a<e���X��̄���.����De�����\n�t����de<���tr�
����b�cdLf�g�i|k�l�m n�!p�!r�"s�#t�$v�$xt�<a ���b=�P����u�F�\�ǎ��d8lp�T.|�6,e����MDoht��xs��\a�e8)�0� ��rP�.`���eԖ���d�e�g,lTnxo�s����lԗ��G.P��aeD�T.l�G(T���.��� a<e��T.x�.��MHade��PT.�\�̙pt<���e�i�c.,ei�X�dg4kXl`m�nrPt���l����a�e�����n��2T.��]�a eԠ����r�{��],aLe�(�D.���0��xa�e��-H�Wp.t�.�
̟��s����a�i�o�s����C�Р"�g���D����a�o���t<��H���r��]a4e@u���,sD���i$���He`r|��8�x��]hod���pk�l�n�r�s@t��T.`�K�e�i0.2 �.�m�e����0.Ĩ���aep$t�2.@��i�G�\��0aT�����8aTe`o �T.|�
hp.4�tha�eȫ��td�g�i�k�l�s@tpv�hT.́�د]�r��������e��n8�����<��a k,t�Z�������l<�Z����.��4aPe̵�.܂8���\g��de��~����|b�f�m�n�p�����a\�T.����a�e�'-t��.�.���a4�@�{d��� a@ dx g� k� o� s(!tl!��T.�t4 aT e` r��x.t����T.� v��th a� e� r� s�&��2T.0�	@�
�4t$�]� a� e�T.����� g��t��t� a!e!o !p��T.��i,Q��O8!eD!r���.t�T!a\!e�����������d!��"�Mx!t4�&x����!a�!e�!g�!k�!l "mT"nl"t��P�����!r�����!e�����.���!a"e��T.`+.�"a0"e��2T.@"k��(|�2T.h�]H"a����.��`"a|"e��T.�j��j��"t4��"s�����"a�"k�"l,#pD#rX#s�#t|�M�"a�"ep�*�".�o/��"T.,�#a #e$���.4�/P�#.�58#a8�.��9���.��L#al#ex#o��T.�����i�����#a�#e�#i�#r�#u��2�#.�#t��=�C��i���T�I����.�G�#a$e�����#l$$n@$s�$t�.��$��$a4$e<�T.`�NT$a`$el$i�����.�#��.d%P��K�$r0�tt$e�$i�T�tdc��]�$a�$e��M�$j�$n���T.�T.0��$e8�$
,%a�%e�&i8'jd'l�'oD(r�(u�(y)��R���$%eH%g`%nLV���@%s�T.���T%ex%tl�Lp%oT
Z�	���%e�%i�%l&m\&r|&sX8;�
(�%i�
���%l���%a�%l�%t�],�d�p�%a�����&a$&e4&o@&t�b�g
�,&g0
�A��kH&o�
��P&rl&s�p,����t&l�&tDt�&e��xT|����&a�&b�&l'r'sZ�T.����&e�g��&g��&o��&eh"��'k ���$'l���,'e	;�d��D'o���L't��X'a�h�T.\��p'i��|'b�'l�'r�X���'d�'e(m$(s�j�D�'a�'iT��'r,�������'.t-�'n�-O� (s���� �(o�$�X$��0(e#X8(et(i����%T(e�%]\(rp%��h(e8,X�(e,���(g�(l�(sXH.t,X�-".�-���(e8��h.��(e�-���(s �-H/�(l /-�(e�.��)r���)�@0�
\)a@*e�*h+iX+l�+n$,ot,r-up-�X1��/��T)f�)l�)m�)n�)s*t$*v�1"T.�1���)e�)i��.\2�42���)e�2T.�2���)e�)g ���2t�)s5*t��i�5���)a6�6"�.0*e�6��*e���\9�8��8*ad*lx*n�*o�*s�<Ph<��\*e ?2�>]p*e�*i�*s�?r@O�@�hD��*atCt�*t�*v`���D���*pDE��s�LJM�*a\�;���*s�K]�*t�J��+f(+n0+sL+v�Lx�Mx<+phNa`O�lO��D+a`Q�t+o�+y�+�|�ZhT��l+i�+v U��U��U���+s(��U���+e�U��+s�U���+�e
e��+a�YJ�+l�Y���+��VM�+�p8��eP,s8Z,e�Y��,d4,s�\�����<,d��"D,n�^JP,e@]��\,v]h,a�,e�,ul_�x_���,e�^���,i$]O�\��,sLa���,n$a���,n�,sHV;ha��,s�CT.�n]�,e -v�n���,d<-lH-s\-t�DE�d(�P(-t`o��0-a@p��vT.Lp��P-u8rXG���-�h-�4��Ds��-e�r���-s�-vpsx�s�-a�.e�/i�/ox0u�0v�0y41ìt��t��-s�s��-i.k0.m`.n�.v����ti..�t��.a�ui.�u��$.ap��l.a�v�<.l�vtH.s`v��T.dT��z�xPt.k�x��|.a�.i<yt���T.����.r�y���.e�.i�.n/rX/sd/t�zA�z��.e�|��.i��5�}L}��/i/o��0/e8/i@/lH/sX��|6����4�����~��P/sp|�/et/s��/e�#��ܰ?T����/lD����/l�/n�/rX�x���.�]�/eԃd�x�/d�/e0r@0tX0vplЅ�.$0n,0t����0e��Ę���.p�]40a����.,�ML0a$D��d0i���l0d�0s�o�o���0lho��0a�����0vh��0eD����0t̉�0i���Ļ.�����0a1e��Z�0r�����.�|T���1iL1r`1v�1y����1�1�$�����D1e�������X1ap1e���.�:����|1����1����1t�����1s���.��%�1n�1r0�t�1e2n���������1l��t�1sl����1d��X\2al2b�2d�2f�2g3h3k|3l�4m5nD6ox6r�6s�8t�8v\9�T��t����d2l���Е��x2aH/i /-�2ed�*�2rl����2�,����2�8Q�.ءM�2a�2ex����2l,R�T.L}(<����2r ��3e�X03kD3lL3td3v��"<3e0�<�d�OX3e|�.ث/(�2�.�3nT���	l3a�3b�3d4e84j`4kx4l�4s�4tta3�:�3eج���3dL"La�g��3d�fm�3o��L�t4e�3r|�.T�*�4g(4s��?��(�]04aH4e@��.p�D�.��T4e<�T.ܰl4e��t����4kH�T.t��4el�t\����4e�4�����4.�H�4eȸN�4t�����4�4i$�5dи��5a@5dX5g�5k�5n6s 6t��i��t85e��.��tL5al5ex5r��T.��a�5i��0�S�5a�4�.��W�5e�5i�IT.�5k�5tX[L<J�5s#a$����5s�5t8�g��|��O�m��6s��6eL��\�],6aX6o<���46nDD(�P6m��(��d6r����l6e�6k�6r���?��.���6a�6e��x�.��t�����6a�6c7e7k87lt7m�7n�7p�7s8tt8u�8���2��tl�r���@���7a����7gH7m��8(7aP7e�`,��
.`7t��(`�T.@�Mh7e|�uġ�7.��M�7a�7e�8{��i�7.X�i8���7a�7eH����|�7a�7e8k���.��T.��i���,8eP8iX8j`8r0�/4�"$8.@8dH8m���|����
|��D��$��.����h8t8�L����8�$�������8i,�2�8n�8rT����8a�8e89r��(�������8.�2�8l9n9r�((��b{t���9.$9e�������.���,9aH9e���.��2����T9����9a:e�:i�:o�:u<;�|�T.@����9e�����9d�9e�9n�9s:vL���������9e�-�>
�9.�����9e4�T.@����9a��R\��:a4:l<:mL:rx:tH�����@�I����D:ed:n0�	��\:s\��p���p:a��l�.pl�:n���:e�:sh�2T.X�]�:e�Z����:a�:l,�t���:a;e;iX��d�W�S�X�;p���;e��]$;d���0;� �
�;a=e0=i�=jH>l�>n(?o\@rh@s�@u(Av�Ay�A�X���	�;f�;l<m <nD<p`<r�<t�<u�<vT���
.h���;a�;i�;v�.�������;l���;e�i���<e02<��<a0<e�T.��T��<<r`T.t<v��P<a|<e�<i����.<���"�<n$
T.�	���<e�<h$�ID�\�
���<k4�.�
���<a�<e"�.
�����<e=l`P8�=r���$=eT=nx=r�=s�=v�h���L=ah=ip=o�
����2T. ���=e,�W.� t�=a�=e� �.��������=e�!��=d�=k !X�=e(>�|�x>l��������>n$#]>n#��>��-x�,4>s ,<>et>i�>o�>yt��x.��`>e0.h>n,��.��>a�.���>v��
p/]�>a/���>s�
 ���>e�0�>s�0��>a?e@�����>r�0�>p�2i.�2?aX2��?dX?gl?l�?p�?r�?s�?t$@v4��3]P?r8{S@5��d?a�?e�?jd5�.��72.�7���?e@Cԝ�?d<9��?e08���?sH:T.:]�?e��.0;]�?a@e$;T.<���.�;��@ed�Dt0@sP?��8@g�>��D@nT<�P@i4B��S�|S��p@l�R��x@f�@l�@r�@tT��S���@eDU
.LU���@a�@epU�.T�.�V���@a�@e�V-�V�@.�}|W��AaHW��ArWXAaPAe������8Aa\Ae�W��@Ak@�i ��. Z��dAa�Ae�Y��pAl�AtZT.p[.L[���Aeh\��[���As����A��A���t8]���Aa�\���Ap�]�8BaCe`EiGoHu�Hy�I�Й�T.�^��BaTBe�]��(BdhBg�Bm�Bt�Bv�^�T.��S,_��`BaxBe��T.�Br���`T.�`���Ba�d�.�d���Ba�Be�Br4eT.�ea�f�\f���Ba�Be�f��.,��@l]Cr�k��Ca`Cd�Ce�Cf�Cg DiPDkpDm�Dn�Do�Ds�DtEu0EvmT.m��TCapCelmx.��n�|Cr�Ct��S(n��Cl|n���6]�Ca�n"�Ct�n���Ca�Ce�CioT.�Clԗ(�o20.H����'.�pADrhp��De8Dv���ԣ��0De�q�T.�q��DDe`Drr`t�ds��hDu0uT.�t��|De(v	y��x���Da�De�DpXy"T.�Dk�Ds�y(��pz�{��DaEt|
����Dn(}tEat}�}2T.�}M$Ea@Ee~���.PEs��S��<���XEa�Ed�Ee�Eg�EkFm(Fn<Fr`Fs�Ft�Fvl��
.P����Ee���En������Ee��Ea$����Ek�EoFs��ST�̆�H�S\���Fa�#��� Fkp�t|�]4FaLFe���.�������XFapFe�T.p�T.���|Fa�Fe�Fi؋T.��4��.�Fn����Fe�Fs(����nT�(�Fu�Fv����,L���	�Fe@GghGixGn�Go�Gr�Gs�Gt�Gv��i,G.ԟ]4GeTGi`Gr�0.@�	|�xԡt�]pGad���t�����Gi�
. �]�Ga�Ge@�T.��t��]�Ge,������Ga�Gep�2�.�B?����Hd,He<HnPHpxHr�Hs�Htd��Z�ķ��4HaD_t\���HHa`He�_T.8a.����lHa�Het���.�".�����Het�x.�Hs�����Ha0q0��T.T����HeIs����HdIe$Ig<Is�It�Iv��m`�5t������Ie$�IT.п]0IeTIk\IslIt���D�94�=@��dIr��T.`���xIe�2�.�M�Ie�-��I.����Ie�����IdJeJn4JpXJv�Jy�~���J��I�����.�B�In��.����Je��.����(JaDJe��T.L������PJiܭ���xdJe�lJn����xJe���4��Ja�����Jm�Jr�Jt�i�J.�����JeP$L�D.`����Je���4Ka�LelNiOn0Oo�Pu�Py4Qð�T.����Ke\Kr����$KglKk�Kl�Kn,LrlLs�Lt�Lv��G`�2(���dKa�Ke�Ktx�"T.dL�����Kt��Ka�Ks(�;p�"T.<����Ke�Ki��T.@�Kh��K.(����KaLeLi��2T.�T.��T.���� LaHLeTLi 4(��@L.�QT.��WT.<���`La�Li�Lt����t��VX�t�La�Lh�Lt$�[X�_��t�Lo��x@�cMe Mi4Mr�����LdLMihMk�Ml�Mn�Mr�MsNt����Mi��,,Mn�_��	��ADrXMt���<Me��i��E����`Ml �"�.��]tMa�Ml�M���x���Mo��fl����M��T.�����Me��������Mi���@����Mk8NtP�l$Nld���NaLNr��p`�*Nlt�i����0Ni0�(��wDNi�|��]XNjh���`Nk�Nl�Nm�Nn�Nst�T.L����Nel�T.�����Ne,�T.�Nk�����Ni�Nk�.�0���NeL����������Nt(���MOe|����Oe���$Od\OetOn�OrPs$Pt`Pv`mt��TOr �T.��]hOa�Oe�OsT-�i�O.4���Or ��Ot4�	��I����Oe`����Od�Oe�Ot����.���l���Oa��T.��]Pe�����]Pa@PeXPrT�(�8PkPPr`�(|������G����hPa�Pel���pPg�Pl�PrXH.<�.�Ps ����PeL� ���Pe-_H��4�t�Pa�Pe����Pk,�2T.X��.DQsd��Qe<���QrLQt����pQ�$Q�h��X��.�TQa|Qo,��`Ql��L��Qa�Re4SiLToUuUy�U������Qo����Qb�Qg�QkRn$Rr8RsDRt�2.����Qe<XRe��".8	O���Rs�	xWT. ��,Ra�XTRrdRtax��8t\Rr x.�Rn���pRe�Ri�Rr�Rs\���Rd�Ri�RpSsSt�(D;�|�V�A����Re4�.��RaSe�i.��d"xp��D-�� Sr�,��	(SahSe|Sg�Sn�So�Sp�SsTt(Tvt.tL.]`Sr�.x�.]tSl�/�T.�/���Sa��gd0��Sg��T.�0���Sa�Se�0.���,1���Sa�Se1.p��D2�TaTo�2�H3SP3�� Ta�jP.HC4Te<B��@TdpTe�Tk�Tm�Tr�Ts�TvTCS v��C��xTal�"T.DD���Te�Ti0D.�DxtF��F]�Te�Ti��.����T.0GM�Ta4Ex$]���Tet`�$`��Ua4Us\Ut�.ha(UaDUe|a�.�a2T.�a��PUeHb�Pb��hU.\b��pUtb��|Us(b���Ua�Ueb��UrD$��$V��U��U����.�b,�b���Udhb���UdVr�c�.�c���Ue�dT.�d?Ve�d��Vd@Vl e.e��4Ve�e�
�Vb�VdWfPWghWk�Wl Xm�XnTYpZr�Zs@[t�[v�"OiP�Ve�g���Vo�FT.dj�Va�Ve�iM�Vd�VlPjT.4e6�a�Ve�e.�'�`�Vi<o]WsPn��Wf<Wt<pT.HWb,p],We|��XpLwxWa�We<v��XWk�Ws@w�.hw�`yPT.Dy���We�|m$|t�Ws�z���Wd�Wg�Wi�WlXm(�T.�~]�Wa�~x���WaXe�p.Ѐ"��LXadXbxXe�Xg�Xm�XsЃ��T.X�@XrX��.��XXo��pl�pXn���8.4����X�P�S�X�X��(���Xe���x.�Xsԉt�XeYsĈ���XdYk$Yn��4�m���d�Yl�t��Ya4Ye�;.�i��@Yr����HYatYp�Yr�Yt���]lYa�Ye�Yi�Yl�Ys<���YlL�
L��p��Ya4e.��J�Ye ����Ytl�S��L��$��0���Ya8ZePZrXZs`����YdhZg�Zk�Zm�Zr�Zt�Zv\�T.HZlp�(�� ��x����`Za0�i�.8�tZa�ZeX�x.�.��Ze0?Z�.l��Zel��`�,l��Za��8����Za[k[s,[tL�x�<��D�[.��� [e��p���8[r\[t���x.��tP[ap[e�[o�x.�[s�S<�2�.@���[r�����[e\r\u���[e�[i�[kT�Sd����[d���[nЈ�0�����t���[e�{̵�`\a�\e�]h�]i ^l�^o�_s�_��u�<\.���D\at\e����P\l�\n�\r,��T.�I�\u�����\e�\iX#��I4��.����\e�2�����\o�����\d]k0]lH]nT]pt]s�T.����\e]t(�O�����]jD�t$]sԿT.p���<]e$�`]r@�4�.��Mh]a�]e�]t$������]t������.�����]e�����]l�]p�]r�]sP�Z����]�]a���LuX���^g�^n��^eL^u�^�H�X^e����4^s���@^sP��(�
����`^e���h^s����t^��m�`��^t�����^e�^l_p4_rd_s|_t����.�����^a�^e�^o���T.ԟ��"�^g���_e_p$_u������������,_aD_eD���.T_s��B�t�]\_t|�.\�]p_a�_e����2�_k�X���_s�����_nt����_��_������������_d8��(`a�ae�di�eo8guhyxh�,��.4���`a����`dh`e�`g�`j�`k�`m�`n$appar�as�at�av��t��m``t��WT.����t`a�`e��T.����T.��M�`a��$����`a�`s���h����������`a�`eash�WT.���t�taat�.����aa8aeDas��.���Tae\ao(��h�����.����dae��W.����|aa8������aiX����<a�ae��"�.��(�]�al����
�aa bdDbedbg�bi�bkLcldcm|cn�cp�cs@dt\dvx�.����ba0be�x.��Z���<bl��h2Pbl4��Xbetbi�x.�X�bd�be�bn��
����be$ADr��t���bs�x�W�b.����ba�becl8cn�T.�b��cr$Kca(ce0ci@'TW�
�".���@ce�T.���Xca��T.���pca�ce�cs�ct8T.�Mt(t�ckT,�����cs�|
���cs�ctt�����cn����ca0��&�di����dr�tdk|t(ds�
��4dt����.lds`MLdetdn�0������|ddP	e�dk�dm�dpeshet�ev���.�]�da�"����da�deT.h���Sa�deeoxT.�;�T.���ea4eeHek\e��T.d�iX ��@eo!�!��Te�(!��|Faxee!2."W."���ea�ee,"�.�fp 4]�en04��	�ea�edfe$fk<fm`fp�fs�ft�fv�jPT.T5�eefik��5,�6.�6��fe 8"T.88��0faLfil���9;pfr�9��Tfaxfe��$:2T.�:��fr�:]�fa�fe�i��:T.�fd`o(��T.�;]�fa�fe�;T.�fk�;'�<��<���fage�<2�.�S.�S��gate\glS��$ggpgk�gn�gs�gvI]�H�Tge�L,T��hgaZT.�T��|ga�ge�gg[T.�gsX[0(���i"T.�U��ge�gkdk�pWi.xWM�ga�ge�W���.�q,hnZ��heDhkܭ5���$he�]�.dZt8he�\X�hs<\��Phd�hl�hr�hv�hy@=��i�\h��\:,�.`]���hep^�. ^���he�^M�ha�he��.$_���.�_>\_���he��.�`?�ha8ie`��idLiehig|ik�in�is�`T.�`q�`%Din\ir��2.�`B��.�`��pia�ie�ilx�T.$�Gp�.ha���ia(�.�a��ia�B�@ja$kc8ke|lh�li�mjnk�ol�pmqnpqorp�rt�tu�tvxuyvz v�cT.�c��$je\jn4b��0jgxjl�jm�jnku dZLf�$fdj.�e��lja�jedf�T.lgT.�jr g]�je@�(,hK�je�ji�g���jd�js����\�OPi��jui�jklT�k��ke�B��B��kh�Rtn��0kb`kehkixkk�kl�knlrDlt�o�qxXr�r��pkl�kspGY<G�kg�r���kou^�kis]�kvLu9Pv".v���ka�ke�ks�v"T.wbTx�T.x���ka,lv�y��y��ll�y leL|"T.�{��8leT��t"Pl.���Xle���dlk��Mpla������	�ld�le�lgmk$ml@mnpmr�ms�mvT�t8�]�lr��.ă]�la�le�T.��IT�]�le �]mk(��.�]ma�&e؅WT.����4meXmn�g ��Pms�T.��]dmaD�k̇��|mr,�]�mt��S�����ma�meԈ�.X�xԉ��mi����me�muXpg�^���mg�����moT�Dnaxne�ni�no orPou�oy�oô
���,naTne����4nk\nt�p��u�dndЎlniАZ�nn@����nn��z���nsT�3P�n.���ned����nd�ng(4;����nu�!��!t�ns|�J�nt0���ot���oiXC�x�0C,os ��8od(���Dod`om�����Y�hoa�Y%pol���|ofT]i�����oa�����orP����o�\��x��om���oipo0puhp�P�r8���osH�]�ot(���ptl�̜��pa<peh��� pk��4.(�i���Hpe���Pps����\p�<�x�pk̞tps��M�pa�po�p�������pgp���pk�pl�ptD����p��r�����pt�����8qe<qi\q����2�2tqsh��$qt�0qtTc�(��Hqk���Pq��������hqa�qd�qm�qn�qprrrt�j���qe��K\���qaܦ���qmhO�@�]�qa@��$�]�qaب��qpP��l����qt��]�fa@��Hra\re|ri�rrԸi���4ra��<rr8��H�Tre��ĭ��hra����prlT��$����rsx���ri���ra0se�si�so tr4tuxt�dT����ra�����rk�rlst���x����ri������ss�����JssH�$sdHsi|sm�sv\��\sn̟�����Tsalss���H��0�Jtsm$�L��sa$�-�sn��
�����se���smpi����se�����sn�srxt�����sa������ts����tk��teP��X���,taLtv7(7MDta��Sp>��Xtl`���`tl����lt����h�t�ts�����td�����tn�tr�ttl��(����ti�qi$���tet��ua`ue�i�����ta�����tl(urT��}u.4���ua<ueDusT�i�~�@�����Lun����Tur����ue����lud�un�ut�uv��I\�.h�]�ua�ueķT.���P��ur4���ue�uid�2���vo���H����ug�����X4vrl~���v�v�lv����Hve����[@vg��	����Tva�ve4���\vk�vl�vr�vy��T.��D�(�vv�������va0��vn�����ve��,�������vn����ve`����vpwrL�2��we<��pwa�xelyh|yi�zop|r}s8}u�}v8~y�~�|�".4���Pwe�wr�ws����\wk�wlxm8xnLxpdxr�xshX� �����wk�wp�G$��p����w.8����wa�wexl��"T.�ws��(l��$��wi4�E����xm��i��t$xe<���,xd��9����Dxr��T.����Xxatxep��.`����\����xe�xi�xk�xmynyo0ysH�Z�xeH�D-�xr0�.@����xe��T.x����xa��q@�.����ye@�"T.��$yeDyiTyt\�
X�((���Lyadyr"'��@���
tya�yd�ye�ygzkzl4zmLznpzp|zst����ye�yr�ys�2(�
.���|�]�yr�i0]�yizi���(� zd���(zeh�T.|��@za\ze�WT.	9���hzlp	xDg6���zm�]�zr���
�za�ze{k,{lD{nP{oh{r�{s|tT|v�l�.��zn�zr{t`m��T.�x.���t{k�".h�� {ehi.@]8{a���mgxT.���\{a�{e�{o�{s�{y'�.�{bЗ;���\�@<����{e���{k�{vD��E��(�{eܧ'����{r8�{t��4|a@|e�JȨ;|.Ш��|a�(|l�i.�;t���L|ad|e����.���|a�|e�P����|c�T$���|b�&o�|p�|sXAD!YL_�|a�d�|j�|o����|k�|p}t�j�o�I#X}j�%��E�T.4�� }nd3��,}eX}n�}r�}vZt�4��P}ah}e�4T.�5.L5]t}a�}e�5�.Xx���.(7M�}e(�s�7���}n�}r47M�}e~Ð�|���}a�7��}r0�x�����}g$���~�,�.�8��~a�He�8��(~dX~ep~r�~tt�,�P~t:���.�9��d~e�~sľ}�:�ur�:��~e�~id�i;�.0;���~e�:���~r����L��~��~�p=�`=���~det;���~v�=���.r$t��@�����H>,r(>�4e>��@kpl�p�r�>T.p>��de���>��|n�>��\n�t�>���e?�l?��?���a�b0�dD�e|�f��g�i�k@�l��m��n��o��pЃr�s؅t�@�$�e�p��g�A�r�B�<�e�C
4EP�r�ET.d�al�eF�FWG�F��t�f��s4b��'��a�G�H����a��i܀l�n�HiT=�H�Ȁ.�H�Ѐa<I�JM��m|u�4L�la�e �n�Lt�M��P2T.t�k��n��tPP��(�e��ík�l�mD�nP�td�ut�v��y��À���t,��ePQ��|�d��eu�0u��{pR( R���n�Rt�Rāa܁e��Px.�R���.�R�a�eS.\��. T�a,�eԛT.�2T.�8�e�T�\�e�Tt UT�;�U�l�i�U�V*�Q������Vi�V����a��uXY�Y��d�eH�gT�nh�o|�s��t��uXt�Z]�ixZ�rTZt��e�r\[�,�k4�t��d"��[T.�[t<�e�\q`�s$]�P]2|JT.t]tp�e,^,�^��^MP_M��i��l�_�`�8a]�f�nla����a$�d4�e\�il�k��n��o��s��t�u��y�����at�eP��b��cx�b,�nD�td���8.T�sL�Pd�d�ed�eP/t�e]x�a��e�ex.�e�f�K��g��aĄe�gT.؄m�tMBY|V�h���t�h2`��h����i�@�aX�eh�kt�n|�p��s��t̅y�h��T.P�s�j(�iiLk*dk��`�a`l �l�P�2m��e�m���e��� n"��k��l��������ąr<p�
�aH�ex�i��k��nІo��rD�s`�th�u�p�$�ep���g,�k���4�*ؾ�`r]4�.�q<�nd�s���p�o�r�X�k�[s2T.�<����tX����n8C��a�T.Xt���eh�pt��Ȇl�v�t��u2�r�t8�e�o,��8 ?Pu���v|u��u��$���u�.�u68�eT�id%T.4v��v2@����a��e��i|�o��y��pz��Ba�9ez����d��iԇk�l$�nh�r�z{"T.�z��ȇe���{"�sx{���e�g���{]�s�|WT.�|���e4�n$}@�sl}	�����H�aH~P�t~��\�e��i��s4?��"|�e�~i�~���k��x.�����n����	��e�g0�i`�l�n$�r��s��tЊvЂ"T.܂���a�e�i �s��i.���<�V��ă��(�gD�sT�t�p�����L�a�c��a��d��e�l��oԉy�à����t(�"��r��X�O���x����"��s̉v����S�=���܉�`�T.�t(����et����"T.<�s\����aL�e`�mx�s��(�D�.0��$�SX�oD�P��e���l�t0���������l��t���u.�����e�����.̌MĊe��"T.���܊a$�e8�Ì����dD�eX�k��l��m؋n��s<�t$�$.ԙ ����0��H�����.��]L�ap�����d�l��t�l�ax���|�l�d���W.������a��el�.̾%,�:ċs���̋g�nl�;��T.�����a�e�T.$�r�x)����il�T.x���0�aP�e\�r��T.Ԕ����eЙ��d�sH���p�k��r��t`yI��Z,�]�?a\�Če|��dJe����n�������Ќa�e����،r$�������Ģ�.��/�����d(�r��c4�s�4��x��<�r ���D�ah�i�"��Z`�e�X8���t�e@����eЍnd�p��r�sT�t|�v�8�.��t��a�eh�����d�g�k@�s��x.�.$�t�e�4�.H�]�a,�e8�v�4.X����||��.��]H�ap�eȺ��T�p��2T.,#��.�"M|�a��e�����j��k�tL#�.�#�@���aЎe0�x.�sP�|��S��(�t�������ex�8�iX��r���e�����4�n��<�e0���H�rd�tL�Op�el�P�.X������j���<���r�t`��<�������H�>T�����a�t��ďs����ԏe��P,x.��t�e�����t(��	\�gh�i��k̐l�m4�r�s�v��y8Q,d���D�a�2e���L�l�|t�e�"��P���|�o��tTKt K��a��e�LT.�e2,���d������e�s�vx�D��D��T.����eh�����m<Hh���tX��s����(�e\�jh�k|�n��sܑt@�qL�MT�e|��P/2.�]p�a��e�Px.��|�����i����k��mȑtGN���<���nH�Бa�eL`�.,�X�tD�x�,t����d<�e\�r�|H�s����0�r��p���.���P�eP�.`���h�e����t�d��eܒn��r0�s��qВllJn�R.�����e��W��g����Ēed�x�a\�t��t�����a�e����r��-��X���r��$�tt�Xt�e|�f��g��k��lēm�n�p4�rH�s��tؔv��Z�Z0�Z ������t��u��"����.��M��t���t��a�e��s����̓d�x.���t���a�����n$���q �k����(�s4�t�X@�eh�kp�lx�m��t��v��8�]�����a����e�f,x��t�x.̔o���e��g`�iĔg�Z� ����d��f��g��kЕlܕm�n �p4�rl�s��t��v���a��b|�c�d�e��fСg��h0�i��j�k0�l@�m8�n��ox�p��r8�s��t@�uX�vlw�yzD��(��tD�nСx��������s��v��s�20���ȕa@��8��.�s�t��x�t��t��|���rx����X�e����(�id�n��PD�e4]L�n��8��x�e�R�����<hX�\P��b�v�����a�b8�e��h��i��lP�oȘr�s��uL�y4
�x$D
�l�u�(���t(,�i����eP�tx
(�ll�n��s��vE����X�.���`�ap�����x�a�_��k��HiH�ȗr\P��eؗl�sh����Зa,�����t`� �a4�o@�uP�<��a����r$ � ��,�k@ ��!�d!�H�al�bt�e��k��r�!��"���r�"��w� #����l0�x%����d��t�%��%���ed'�Ԙe�'��*�$*ܘl�+��l+��e�f,�s@�tlE��F(x-Y0-��$�kp.�@.��8�e�.T.d�bl�kt�r`�%�"0� 4���a��h��kșo�3�.�y�8����a�7����eh9l�:>�0�l@�nT�rh>��Йah�d|�e��f��g؛i�j��l�m�n�ox�r�s��u��v�y�ð@��B��A��8�t�C�C��L�t�F|F`�aPI��H
t�b��i��kȚl�n��o�p�r8�sl�t�K(KJ��s�KJPM(Ԛv�O��Q�DQ��ܚd�e�QR<Si�S����S]�.,�l����$�e�V-P�e\�lW��V�H�r�W����X��d�.|�s`���
	�	��rPZ��e�Z	��e�Z��W(]��e]]��n�[̛elcM�u\d�4e
	pg	,h�hA\h�a0�b@�pL�r�h	���kJ8�p(l�X�al	��n��`�f��n�mh�a��eМiܜo�ôn��oJ��l��p��t�pC��q*�*|q��Ȝd�r�s��s������"	0u���tPt���mt��a0�i`�l��twoR.����8�e�xD�gl�t�x8P�e��(	�zi��k0zt�e��r���p{.	�it~��n�	؝a�eH~i~��ĝe���̝rL�d��T.�3	ȃ���y�������m<�aD�dl�kx�l��n��pܞs܅�̌�T�e\�r��2�]�Od���d�tȫ��T.��a��i��o��p������Od�����t�9	��".|���Ğe����Оk�t��x��$��
�a8�c@�el�f̟iH�jl�l��o��r �sD�tx�y��� 4�	x.P�i�
�@(�X�n�]`�a��e��iğu�
(���i��s��P��.��d�|��P�a��b�n �r4�t��n$�T=	$B	����n�
��eXH	d��,�i�N	�@�o������T�.��r��\�a��e��i�b�$2�ip%���k#X�a��i�S	�Ԡr�M��.���Ƞa�etY	��n<�i���e�#�,#��s(��'�l�)pH),�iX�lt(]4�e`�oh�s��@|*��*6.^	�-p�r /b	��i�.���r������$�i�/W�a�e$�nP�rd�s|�v@0��
��a��d��e��g�i�l��m��n��o�rt�s��u����J81���g	�2���f<�g �m	�2t4�s�4��3��H�t�5��\�.�6"�.�6��p�e�7i`7���e=q	�<��nh<����e�8��lܢm�n�r`�sp�t>J@@x	�>]�t0A	�@]��a$�eD�iP�sX�u�A�	xA�kX �(B(0�k�A8�sC�	 C�tC��E��E��h�aG(G����|���3��G��rģsHG��ạeԣsܣu�G(�Gi�I=4J�	�tLp�Jx.�d�sTKp�MZ8Q�`Q���a8�ed���R(,R�0�mP�sydS��H�a����U��\���g�	�Vp�e Vx�o�Xp�X��m�VM��o�YW̤dԤrܤs�t�v8Z�\�\��\n�\�^�@]���s]K��a$�e,�i4�uD���^P`$a8<b��a��<��Xc�	Hc�	P�b�bXX�m��ndb#	d�a��e�i�j�k@�lt�m��t��y�h"pc����g�c�	�c��iХlإm0d�t�*���	�����g,e]�n���e�u�e(�e���a4�u��(�f��,�lg��t�H�ap��P�lD���\���gMh��ka�h����r��v���	�l]�n.��a�2l?����y��psM̦a�e�r�Ԧv`s���.�sM�i�n �vD�i,��	̉T�8����	(�a\�e��i��kȧn�r�s|�v���ܙ~p�r�*T���h�.Ĥ���M|�s��4.�]��e��o��vT�iث�	���и����e�g�n�o������	`��	t���]��o��P�����e4�kH�mX�od�sl�vl��@�h8��	@����t����P�m��;����iT���t�e��i���������M��ąe��i�l��@�\������d\��	.ܨs���(��������� ��.X�a�d��e�h�iT�j|�kX�l��nȬo�p8�r��s�t��u��v̰��	��.��d��f��k��n��o��rܩt�u���	�	�Y0*�����s<Y��|�̩b��	\
�	�	��ԩr�
�x��" �c(�h0�i8�kL�l��rĪs�t0�	�
�	�
�8�&p�i`��@�e|�i��	�A\�r���d�e���A�	���k�����e����	������� H����mԪp�


���ܪe�|Z`���n ��e�W(�d0�k@�nL�sx
Tg���8�d � !
8$: $��\�d�#d�nl#]p�a��e�i�j�o0�r<�uP�v�$�'.��nثr�s�%*D�
���īt�%��̫sX&('K��m�s�� �d'|�( �l�(2�)�)�(�il+H�l�S(�+ , 
t�e��i��u�}%
�,l�v�
0.��k���.����s�0;��e��o�0i�1�X2"�m�n�s��v�5��4.�6�:��;���*
L<�e��.
(=���a�<�� �sT<�,�a\�e|�o��u�=3	l�ix=��P�gt�s�=4
`>*�?]h�.��e�5:
�@,h?
�B����dB��n4B��
��a�e8�iL�jX�kl�lt�o|�r��tԮu\C(C�k�l(�sPtE
pC]�o �vu��C(����D0�dԉK
�D�elEd�u�ER
 FW
<G�D#[
`Ii�H����i��o��rd��������lxI�Ȯe�Ia
�I����nJ���(TK��	ܮb8�i@�kT�ll�m|�n��p��s��v K�a��eدh�i�l$�rd�sx�u �(�K(��h
.�K��H�ed�l$������n
�K��t�sL(DL(pL(M(�L��g��k̯m(M(��t
xMz
įe�
��
xN�g��t�N��n��8�e��ghG�e@P�4�eD��XP��"*�!��<�����@Q8P�a�P�X�l�Q�
�Q��p�e�VX�R����tW�
��ahW*HW����l\��[����l���ܰ�����\����
�]���.��d��gԱhܱi�l�m�n��o��p��r0�s<�tP�ud�v�]���a|�b�d�e(�f<�g��i�j��k��l�m�n�oĺpغsĻt��u�v��y�Ì�
�^����r,_x��a��mȱr���
���
���|_���a�_��_����
�`J�l�`�	�`����eaaa���a4�dT�ed�gp�lx�v`a�
H�eh��
ta@�.�a�
���at\�rb�
Db�
������lb����r�b�
�b��
��.Բa�e�k��l�m�r�s�t �vcRXc�
 cܲn�c�
��
�c�
�c�
0��
�c�
�c�
Dd�	d��(�i�ddH�r�e�f�\�kD��\f"�Ba$gPT.�fmp�a��o��u�"?�g���g�+�n.�+����r�g���e̳m�V�
�h2�r�gtԳe��o�r�h��i�Pj2j�e�k"
T�f\�g��i��kĴlдm�n(�pX�r��sԵt�u�v(n-�nJh�.��
�p��pAp�nhp��x�e��k���
�q�
�q����e��s$r��r�r����idsJܴu`t�t��.��h�t���u�u�r���v��s<v��o8�t��
��l�eDw'@�d�v��L�ex�o��u��.x%|x�x���e��k��lĵuXy+z���r��0dz5�{|��{��̵a�eX|;t}-�}��a�i�}�d~��	,�b�a�d�]4�.\�eh�u����9A��T�bx�]Ԃ����p�r����x��4��
�����l<���l��mȶn\�J�F�����.�a�d��s�tЇ�u.D�����
܉�
����X�.\�]�a0�e`�ot�u��ø��h�.L�nX�r0���c�D�d�*���l�rĎ(���	��vp���|��(�L�R�����a��P��r���e̷s@�r,_��`Ba�e���Էg(�p8�r���aL�eظi@�o`�s��u��ü�.��(�cX�b��0�t�6�D�a|�g��k��n��r��sȸt��R.����p�e�(d����.Px�īt�����s����tH��T����^0�иa�d��e�n,�sP�($�et��
h����el8����.���� �t��s ���8�m��(��L�n8��T�ex�tT�W<���p�iH�������l��x������.йeعt�����s��������(�D�����ԛ�k�t$��eL�(��L���	�'.H�bP�iX�m`�np�p��r��v��w��Q|��`���p�aP�Jh�eĢ�����|�d��i���t�=p�*����e8��ؤmкs�2����e(�kH�lh�nx�p��t��uH������d���
���p<�r�����a����4�e@��\�Ü������T�����h�Xp�u<����r�����e����T�t�����k�lȯ��a��el�o��h
.`�*ػe<�*H��d�m8�rD�t�zh�mJ�a|�����$�a����,�n�xT�a\�eX��l����ܲ��d�pض@����x�k��l��nؼr�R��R��aH�����l���ķ����dt���.����̼e��u.�k����a<�e��i��o��s4{�(�eܺZ�s�.\�h���4�dP�rd�s̻�\�m$����;p�.d�F������x�eT���s�|������t��`�����e����Hpe���Ƚs�v�~�����Խ���*����t`����%���h��i��k��lоn�r�s,�t�����aT�b��e��h��i�l4�mH�nl�o�p��r��s��t��u��y���i�����s(�J��i��s����*<�JȾd��(�*������ؾ.�a����<�����.�o8����������X�t ��d�(L�P8�rd�sl�t,�@�at�e		�	(x�i�J��e��s��|�i��k�n@�r|�u��-d����JĿs�8��0.�t̿e����ؿd��s �t(��LV�
4V���v�����rd��ap�(��",�m����4�a\�eh�i���$�T�t���t�n�����\��@���uh�"��n�s���������a��e��\�z�u�����k���|Sp����e�t$��s����e�zY��(\� �d4��(�a�Y�@�.`�e�((�X�d�]��e��p��r��v���H�"`�@�t������e��(T���p�r����a�ed�ip�l��r��u���4������e��il��i4�rH�s���T�R�a@�e����$�iH�`�0��m����P�n����X�n����e�{(	�|�t��S�����e4�(@���n��J��������������u���<��$������H� �k,�l4�tP�u\�vx��0�����uL�,��@�r��X�@���H�t|��h�e<|���J����p����2��d���i��m��v����*��a��
4�# �tl���l��i���r��<����y��������@�.��k��l��n�r,�t8�uL�vL���al�bx�d��e��f�g�il�k\�l��n(�o��r�s��t0�up�v��y��è�.�'��e<J��t�2�����a��f��y(� -���e��g��t40���:�	��b�tHC>�
D�2���$�odJD�s|����X�.�P�
X�f,
_`�e�t�8.��a��e�o�sTAW���m��t�\�����ad"���k��n��r�t��x$�e ]��l�X���sm��8�ep�l��tH�T0�lP�npa|��H�d���\�t�8d�e8�D|�e��r�J\	��d��g��l��m�n�r$�st�t��vP�EJ��g��l����v�J<i�d�ut�������.8�t@�v�!�"o u0]H�a��e��k�";P�k��sd"��d�it|��p	(	�#-�$��$��l�$��a��à�H/��. /-��e�%���r�%�����\'P<�dL�nl�r��s�%t��e��i��o��r �s��t��u���P''����'nD�h���tBX�e�'n`�m�D3hD�x�e�';��t<(x.��vlO�
tZp�(��f��sT)�h)�
|)K��a��e�y�)����.�s�t*Y*8�a�`+�*#�mP�t��v��äk�+8�oh�Ì+��@�rt�y�k��k��`���+�hl�tlX|�a��Y�lJ��y�l������m��m��e0m��r��vdn�pn��e,(,��n�,���X�D-��,�a(�b8�sD�v�-D��,1��0�jP3�0�<��L�a�4T�nD4]`�a��i��j��o��r�u(�v8��5��d��2	�62��b��p��s�7(:�`7��a�e@������.�<����at7����nx=28��7�t8���(T8��H��0����0�8dt�nd8P�a��Ð8��8��l�d(JP����ah9���pt9�����	?
����s���nx���a�e0�i\�o��s�y���������e�;��m�n �rX<��<�X=�d=(�dH�n0��=��@�n0>>T�mp�nx�v�D�|>%<?(?��i�>t��e��i��k��l��t�u�?(�?��d��[P�������?�����dO�4@e��a\@��@��A�BJB����<Bh�.L�al�dx�g��r��t��v�B]h�.t���jPX�bHC`�e�C��E��D����m�F
0G���a��e��iDG�<
�]��e\H����k�Gm��iD���c����tI����fX�kd�nl�t�Ht
��a|�e��i(�k��l��n��o��p��t4�u`�vt�y����d
LI��P�a�I<J�<K(|Jt�k��n��r��sdw�TK����tę��xP��eH����nP�"��e�K����i�K( ���L��k�n�L��L���d�s��
(N(�M�� �iD�jL�l\�up��LNN��

���N��T�nl��P���h����
�O��|��dO���lPQi�P����r���,QX��i��o �
(����s�����t�����r�Q��a$�e,�i\R
�Q���r�R��R$
�S�L�nh�+
X�a�S��@�dpZ1
�S:
l�eT@
�T U(�T��|���U��U����a�U����l��n<U��a�eh�i��o��r��s�uV�	V����d��s��G
��*�V��d �m4�nH�s�W��WJ�i��M
X��,�n,�5�X�@�l�YU
�Y]T�l\Y\�kx�m(Z(���Z����i��r[
�Z����d��$[����b0[��a��o��y\[�[����ml!a
T!����gX\9�\g
�\���l$�s�6x6���t$]"H�sT�tx^l
�^��@�a�^p(���_��\�nT_d�e��i�x�_i��n$`���at`(b
��rD$�����������(b�hbq
��kTcd�d
�eM�a�g �i(�kH�nT�p\�r�eXpw
�t]<vq�0.ԉt0�eĈ]<�d���`�xX����d�a̵��l�e��iH�j\�l��o�p��r��sd�ux�y���h��t����������������a��d��k�n�o �r<�s��Jl�"$�
�{
l����sD�����
,�e�����
����4�e��M8��t�s��P�a|�i��o��y�����2��T�]�������l��n��o��p��s��td��d�����|���]��i�]��t\�����
0�.8�eL���r�]�aT�et�l��rh��
4��
����-@�k�H�s�� ���`�s���h�a��e��i4�W���������il�2@����a��e��il��������n��t<`��*������n��
�a(�e@�lH�yX��p���n�� �l8�szx���
��a�������P���'p�tp�H���
��lx��
������s�����.��
<�bP�kp�l��m��n��r��s��t�u<�v8�����aH�bH�d��eP�f`�g��i��k�l$�m�n��op�p��r0�s��t��uh�v��y8�ø��
����4�i���$���H�s`�t��OĻ�
,���h�t�h���|�i��d��o��s��v��(���rt�G
p����d���
������s0��u.�����e,��
�����g�k<��
(�ep�4� �nD��
X�J4�hh��
x�a��e��i��j��l��o��r�u�y���
��n�.�����e��
����h��n��t���
���
��`�
T��
��dH�La���
��
�.��i�
���(����3�
PIT�0�bh�l��8�e��f��r��s\N)���`�i�Z-�2e�Z�t�r�Z���������2H���e�����a����t���a0�bP�dX�f`�it�k��l��m��n<�oD�p`�r��s��t�v(�
(�rP��D�aXxX�P<�r��J��-�Jl�nt2�J��t�,�����a��e��i�����k�"`�
�J��n��o��
���.�(�����a�g�i0�t�/5�X����sT$�e�i����xx	L�l	�T�e��f��k��o���	;�	|�a��B
HX
Nd
�����t
�|
����.��kp�d��a�
���a�8q	p
��.`%�a�nL�0��
$�e�.�
@=��������0�����hULJ��X�h��i��j��l��o��sPI�O�x(
|��H����j�#��t�"��a�b �d,�m<�nx�s��v�
���e�����n�Z�ap	_��c����r�(�i���4�nP�sd�t�o\�k�u�o�2��a���l�e��t�n{� �	� ����o"J�
(�#����u�#	��a��e�h�i<�lt�o��s��v���4$2��s%�l%N	x%�a� 	�%2�t,�v�%��%W&4�e`�����JL�f`&��T���2U�&��l�dh(�0(��e��r�'���txI�܂��(����g�(X��eX)88)����yH)�����,_��`Ba�)����g�)��a|�(`+�p+�aX�bx�e��i��l��3� ��D���+�L����@���d�i,l�d��t�,���r���T�g	�,����f�,��n0u.�t����e$���np-���e�.�H�a�.���l�.�����/k8�a|�e��i��o��u���4(P/20�dH�l�/(�
���P�ax#��X�l�1-d�a@0p�v�1222�3a4*�1�������04��	��.��d�k�n�p$�r,�s@�tP�v�jP.T5��e�6� 9��9�a�9;t:��:�8�t`;��;��<*�<��H�e���=\�dX=d�e��o��r��������tL>���e?��>��a��e��i$�À��0?Z��e��s@� @����k4��@2��e��t�A?�a�A���d�A�����A�l�a��e��i��k4�pH�th�v|��@Bt0B*X�aB`�k��ni��g��|�s�CnLC��l��r����C����s DWD��n�D��D���m��r�D����a�e �r�D�؏�Ў"�sԔ��E��eTH��G�,�r�I�|H�@�r��(�JT�i�J�\�e��I\K��t��p��8�����aH�e�K����l|K
��a��e$�hL�id�nl�o��r��s��u��yXL2��l�t��h�L����e���Y���s�T.0�s�M�ax( H	�N]8�a$N@�k\�m����`O�x�p�O(�O���a��iP�YhP����n�Q	�P���t���D*�X*�����RYXR��vr%XTS����l�n �r(�s8�t�T�UI�U�	�V�|V��0�bH�nXt`�	PX��P�s��t�WX�e��i�)���x�e���������a��eXY����s�.Z�[$_���.�^����e<\����v��y\_�`��d(�p0�t�`.�`?�e|a
b�
�B��.��a��b��c�e��h�id�j��k��l8�op�p��s��t�uD�vX�y���4bW��b��k��l��n��u�bxd�f5�e����o�gtlC�k����gx
��l�
��e�B�Jtn�.@�aH�dP�gX�k`�lt�m��n��o��s��t��u�n`oJlpJrJs�l�e�situJd"x�v"|�tv����e��kwS	���wi��lz�	��t|{L|�{����e0}�����u��p��B��b$�p,�r<�sT�v̆J�����,�]4�.L�ṫ���J`���\�a��e��o��$
����x�tԉ���tD������r(������rT���	��a��e�i �j(�op�r��u��v����t�iЎ"��tTY'@�!�s`����&d����'.D�eT�p`�rh�tP3+�70�7��L�i$�*h�n���|�a|�7��*��a��e(�����l��t��<ȖC�VJp�N�\�l�����yP��������8�a�e,�i@�(	7a������g\��,��v�(x�$�t��T.L�mT�vܦpܪp�(��\�n@�Xd�a��e��i��lH�2$�������s8�S���X�.��b�l�m����a$�e��i��j��k�n�o4�p<�tt�v��y����(x�(Ⱦd�t�f����P��'.�fd@�k\�rt�sr(y��x"H�eP���P�il�v�y
��(��X�]|�r����f��n��s��t���T�����g��^��ض]���uȖc.������e(�����lȷ�����'. �a(�s��X��4�.���ظ�T�a`�r������L�nx�jl�u���m�����|�s��������k��������������b�n$�t�����a8�ex�j��o��r��u��ܺ(��
�o��e����d�v���u@��H�0�.T�d\�gd�kl�n��*ܽ(��(x���D�h|��,�F������f��k��vD��t�p��������n����e��o��(P��X�����a�}v4�}�����v��������� �n0�s������
���(�a��t�X<�aT�h��P�mp�n�Z�h�]h�d0������|�e4����yl~��������`�i<���T.�a�c(�e�f$�h8�i��j�l`�m��n��o,�r��sx�t�u4�vd�wl�y������
P�e\�fx�k��l��n��r��s��t��u�v�����<�eh�=D�n��t�{d�d�.4���l�ap���<\.8�����a��G
<�����g$������r������a��vh��
`�\�.����JD���J��h8���]�h���\� �ah�cx�d��e��i��k�l$�m0�n`�oh�r��s��t��u��������Jp�d���H�(��d��g��nt�}��n��2L��@�����a��e��t0�_��tT��\�����a �;T���e��
�����e���
x�J�b���D�eP�iX�u@���.���T@��i���.��a��e��i�x�"��t������kH�����l���������t���|�(������������$���r���e�T.0�s��ax-�Wh�k|�l��n��r��s��t�]T�a0]\�k��t�i̻��W��a|����e���H	��p 	���e�
-�
����ap	����t�
�	�����o8b�a����t����nhG�a@��x�,�y���4�����L�s�T�o(�l�l��t�e�������a��g��i��m��p�r�s�vx]��r��J���]��r�����p��d�8�e�%
���$�aT�e��i��o��u���
$��L�gh�np�r*�*`S	L��x�f��n�*�;�P����t@ ��^��!����s��y�!�����P"�#��e�j4�kl�è$*�#��r�%��%�e��� &� �t0&��(�aH�jX�u|&���(���P�lD*�X*��d���*t
�8.��a�e�f(�iX�ox�r��s�y����*��th��P,��.�n�-�x-*�sH/
 /-�e�.��r��������Ü.(`. �d<�sP�t/xH�e�	�</*�/Mh�pp�v0����0K��a��e��i��0����f1�L����n�1xh1t��k�l�p�v�1��'�)�d:��2���s34.����d3$�e,�t4�6J47�X�iT����'.H�]@�r8��L�e�8�8��r��v0�0.@�|�e�9����kr�t;��r�s�y�<�@<J��st�$=�e�=3	>��ؾ.�a�e�r,�s8�t\��=�>�4�,?�$�e4?�?��T.��a��b�c�d �e��g�iD�k��l�m,�nL�r��s�t,�xl?b��rl@��T�.�@��a�.8A����e�@P��n�B�h��C�.�B]�a�e�i�C�	�C�t$DT.4E"8�n@�r\�sp�t�E�	�E
L�eFi��*`F��T�t���Fh�a��e����"��.�1(�G��l�sH����a�e�n�u5(h<(��eXH�l�".����e�<�g<I*@JR�J��'�L �i�L(�l\�t4L��4�el�lx�s��v��H,i�Md�aN0�W�PX��ed�P��s(-tPP����a��l�u�R���a UipW�`W"�n�V���e��o�spX"YI�X6�k�t(Y�TZ��Y��$�d���8a8�ela��@�ap�e��i��s��u�c��bh�s��td�Pdi�f9h��ehx�hWi����a�k��o�s�t��u��à��dk���j�r�k:plm;ȼ��m���s�m���aL�ed�i��o��r@n?Ln�8�t n"@�t8�Ep�eXniX�s�	K�nQtn��x�n�nk��i�IKPo��nDo(pV�o�����pZh�.�r<p���a�te��o�r$�y q�pt�	�v�t�	u�	�t8�e4w�Hy[z��b��e��g��l�n�r,�s@���4�a<�bH�dh�ex�f��g��ill�n�o�r s�tuv<�ؐ���С�x{Y�vH|`�|��|���d�g�l��s}f\��
�}l~��h�.�t:
 �a��.T�\s��m4�i�y\�ePM	�T�l��"��d��i�k�l�n�r`�sh�vH����eh�~����.�H���l�n�2�J(�����a�e�����g��
��(���s\�����.8�aD�dT�e�����"0�s�;P�x�L�i��̌-`��<�p�a���p�������r������a�g�k�n�rsPtt������e����t�Xh������'.������a e<iHt�C������(g��0n`��x����\ ���XtX��`u�Ü��Ƚs�~��|���U�a�e��
t�%��sH��k�r�vЙ(���D�xt��ei|
(���s<�ix���	aTe`i|k�n�p�t�u�v�����Lg�����d���hg$�po�c����i@���el��8�]�g����e��i0��$�	al�x����aD���k�" ����i�����(l$���L�0����	Xl|�����t ���`a�e�i����^	�nd���D�e������n�r@����a�stTH�v�.��e,�i�J��]�t0����i����u0zl�����(o(�`��T�<�t� �Pab4c@d�f�k\l�nXr	s�	t(
u4
v<
z��\aD
bH
cP
dx
e8f@gHh\i�jLk`l!m!nd!oP'pd'r$*s�*t+u�.v�.w�.y ���L�$s����y0�H:�|��,l��\atd|e�i�sЙ�T.ln@��h��|�.�b�nt������.�sHR�؛2����(���a(e�i�k�rs@tHv���X��nr��8s��p*@d��"nXr`sht�?
Le�i�.��*Ī����h�.xtL���.̫�ث���s���r|�]�e�o�r�t0��8���e�i�o\���>�|�2�F9	�����mp(t�G^	������� i8r�������=	�2�k0���Pa�e�i�j�lhoptxu�_
��B�"�s��T.��Է(����g�n���a�e4iHo�s\�d�(|�(L��dgi$v|�(�(Е%`��	ظ,g�@���@v������T��Ļ����P�K.�n�r8����a�d�ek@t����3	��x�t�a��(���l�s$�(T`��pl�]�a$o0u08(��2r������8u��2�t����Le�i�k�s�t�u|O���xt����e��4]�n�����s ������r0���k�����r��i�����n@	rL	t8���	eT	i\	k|	s�	t�	u�yx��8	u��(�2��l�����d	k�	n��l	a�	i�	o�	t��(��K�	s����<���	r`���]���������	a�	r
t,�(�tx�t
i������
a@���
lX���
t
a�
eDhLixl�o�r�u
y0
�\��
k�
r�
s�H�!X���
b		���
ex
h�.bdf$g,h4iTkdl�m�n�o�r�s�t<v�
�(��a�'$,�?@�DeLg�-���PJ�����\ottl�e(K��(�1�2�.����e�ip�	�fp�"	�8�kp�=����a���	�ae$i,o4r��x��n�� )@��-H�\�\elnHD��[
���dn`m�e�t�t�nd%Hd!��p���'���kd'��elH�,���a+��r�t@.���'.
ep.��.L
e 
kt/��d1S ��@
�(
�3h 4]h>\
e�H��kp
n;pDQ���
a�
b(d�e�f$g�h@iPk�l�nhppr�s�t�v�|�]܅]�
up��T.ek �m�
o��]�'.���
rċ\��T.Lř��a\epi�r�s�y����D�n��Tr̛D�he�(�d�K|e�o�l�d��X����a��r<��̎��������d�fБi����a�i8�*e�WrԖ�DaPi\ndo�r�ü�]T.��x.��̙���3	�^��l.�n�Pte��8�.�_���e���	�8.�������s�.�gܚM�aeu4�ht�L�T$����s����r���xțt r����(��Z�id���Heli�k�l�s���Хܥtl�r�s���|e�%�`�OĨ�X^X��"�bgȫ��
�a,ehi�l�o�stHuPvx�`B�
(����a�p]0���e<gDn��iLk�p�8q�H�(Їb����Ta�"\nxv$�\yi�g�a̱�s�t���e������l����������<���p�t<���(�����g��a(e<��̵ sp������4�D����i ���Xe�q`r������l��
L���e��q
�n@���td����a�egi$s\tp���2�d�s�t���8�t��i��tu��iH����ti@kLpLN&�M��8j,Q^	��i��Te��x���a�d�eg�i�k�n�or0tHu|���4�"�i0�s �;�����p�st�w�����t�
p��x�e����aTedjllto���0?t4e�]<d��Hn0	s�O����������|������b�k�s<i8�]�e���������l|�ih���a�e��i��"T.a0���e|��i��i��(e@i�i��"\s�h�
(��	Ta�^�l�q
hv8���p���_�.�e�i�j�kno,p4s�t���"T.����k ��\���p|����e�n|��$�9	eL�
���$vܪ������HaXedi����h��Pl��
��d�xls��tn�s�����e����.��`����������	�.�a�e o@rpuxy��ܮb���is�p�m@��set�"n�����,r�w4a\ehi��
�T.4��,���"T.t���ĝe����r��M�a�e�i�o�r�����.�u.����e�����sd�x�D��n,������`��3��0��d1�$�@0t�s]Tu����P�a�b�c�dHe|f�g�k�l�m�no4pHr�s|tt�iT����kh�t�X�a����l|�iH����eЕMe,r4u ØJ�X�e-�����������ĚiXeܙ]<lhr��0C6T���`v��,�]tax��$���]�a�k�l�r���i't<�� ��(��.suT����a(eDiPjdl�m�o�(f(�*4r�tԮ�į(l�<n(����itgܰXa�e,_��7a���<��g`�S��M\��8	G
��]�s$���n��a�got|��l��t�o�Z(`�+��X<��(m��4��� s��((��\s��]<i|k��o���4�.�$(��hn��pe�������
�a�e�h�k�l�np s,thv��2p���i��t��Ol����e��8P�a���8�_ix����i��e��@aPi\r����S��HlD��l�u���p�.����pa�e�i�rD�T.�s��E$��.��(|�K�e�i��������r�����a e@�|����o|���t\��k�Z��],r���4� �Xh  �]X�atepi�j o@ u� y� Ì^�	�a�]��	�d�ftg�k�mn<r\tduЙ�_�`S�_���r�`i�`���eTa`at�aa��d k4tl�S	,e�4i8b��b�Pa���cHr�d*fx(n%�klf�i�k�m�rsTt�p��rhp���eH�i�q(�a�e|qL���q��sp>�ds���o�v*a�v�x��H�a@sLt�z�z�� e�z(n�z�4e�z��{d\��]\e<�dk�m�n�sH��\����a�e��,e"�d�i��t�s����g�������s\�i�a��H��$��rL��� d$ k4 m������`���, m��tX el s`Fd���P s�����d a�������x l���� g@���.���� e�� r�~��� �� �L������� n� r� s����
��X� a!e4�u.��Lt,!e\!i\h�.@!rP!s�*�"���H!.�,
�e��!a�!bL"d�"e�"f�"g#h#j #k$l�$m�$nP%od%px%r8&s�&t 'u,'v<'wH'y8��e]�!s�����et
��!n�g�!a"y�g���!b("l<"s
L�'. "e
(�hR4"a�h
XiW
�j��iD"el"ix"r�"ì_kd"n�k-4lJHl���"��l1pl�"f�"n�"r�"t�l7`m]�m��T.Pnih<(�p�"lXp]�"e#n#sxr=hs@�tD�u'<v��
ؾ.P#a`#ex#i�#k�#o�#r�#s�#u�#v v]�v�vX#lp#t���MLw�xdx2�#p�>�x��#iDyI�#i�#p�?O�y���#a�y��#d�GZPz]�W2�zX�#e�z�($e4$iH$ot$sX}��e�|"$i�~+@$n��"�n.\$id$rX�U��@h��܁�l$p�$t���Z�$e����$b�$m�$o�$s��((��$l(����;�W
l�� ��$p%tĈ]�$a %n4%sD%u������%o��,%e�;iL�� ����<%sd��\%m����8ܔT.`���l%a�%e�%i�{o�%t\�-�.�%l�%n�%t��`�*Ę*�%e�
<�.�����%f&i&s &tl���%e(&i0&s��MSY�<���P98�l&e�&k�&l�&s�&tL|".�{��T&e�`&t���L���x&a�e�i��&e��%����&op���&a�&e'o'r'v��T.�&k�&l4�(Ш(P�.X�i�o47u����c��%�Z8��4'e��Z̵�\'l�8���'a�'e�(i�(o�(u�)y�)����	�'k�'s�i$����'a�'s��{4�i�����'e�y/(���'p�����'s�����'d(i8(tD(v$����(e(sl
|t$(e�
��,(t`?h(i���X�.��?P(r���\(ex(n(������(a�(oS04]�(a�(s�f 4]�(r�:�S�	�(k)n@)p`)rt)s�LiT(�(e)s0T�Z���.�T��)a,)eX[[$)s`�P_��8)l�c��UL)n�U�T)e�h��U�l)a�)l VZ��)n����Z���)s<\(�)d@=���)��)��\i�\���)e�`�`���)e*n*t�ha���)e��4.b��*e�B	P*e|*i�*k�*l�*m�*o�*t�*v�*�tnPh*at*lx���n�`*ms��IT�x�W���*i��8�"�����*f���*fh��t��`��l~���*�<�O�*s#��?�L+aX+bh+d�+e�+i,k ,ll,n�,o�,p�,r0-s@.t�.v�.�l@�l?��D+r�@(C2�B]`+a�+d�+e<[I0C|+h�C2�E�4E���+m�+n�+r�+s�E�T.�+eT3��ET.`F��JM�+s�Kt(MJ4L���+i,l�M��PiPP��,a4,eH,l�PT.S2�R@,exZ
TZtT,e�,s�Y��\,d�,g�,k�,n�Z��[.�[t�,e�\�d\��,r�8��tg�=�,a�\��,l`�s�^P_Mla��lHa-e-o-u�b�.�e�h2�i�T.i��$-eX-hx-k�-s.t,.y���jP-el�
P���d-�dk��l-�rP��-k�-l�-n�-tm�-e�-j�-n.p.t.v��S�����t���-eL|".Dmtȷ����mq�m~�m$.r�n����p`.l<p��4.ap.e|.g�.s�.u�.øp(<r��qh.m�r�v��u��.t�v]Tw�lw���.�$x%�y��y���.�@� �;@�/aT/dt/e�/g�/i�/l�/n0r0s�0t�0v�X$/l4/rԩ����,���,/e��
��@/p<���H/e���T.dJe�/s�`/n�/r��,�]�/sP��	��T.����/e�/l����%
L�,����/o�/��0����/�h�����.���0e���.80e@0iH0pP0td0v8��$����
�:\0eX�W������$�l0r0���t0a�0e�0rx�T.��KX�-0���0.�����0r�����0a�0eH1i\1u<����0r��K���.1n1r01s<1t��4���.(1ex	�T�����.�����.�2T1sL��8�9	(��		�1e�1f�1k�1l�1nD2r�2t�2v�2y����1n��������1s��$�%���1sP����1e�����a�1e,�.���2e$2n02o���<�;2s���2e���	X��.����82e\2s|�;��T2kt2p|2t�2v(g�����2a��/�����2. �~(�i��(�2at�M,�
�����2r�����2a�2e��lJn�2s3th��t�qt��	43dH3ep3l�3r�3s�3t���@3s��?(3ep8����\3n�`���T3e�������h3a��3g�p����|3e4�������3e�3i�X�3t����O,���3j�3o�3rHtl �Pt4b|4e�4l�4m�4p�4r05s�5t���3a�5b�5c�5d�5e�7h�8ih9kH:l�:m�:oX<pd<r�<s�<t=u�=y�=z>��p0����4e��i@����4e�4ol�il�T.x����4e�4r����4iP�Y�����4s��2�����4ad�n5o5t��i��D
e��8���(5aP5e\5hx5t���H5t�������d5l����l5i�5o��i��M�5h�5o$�
����T.�t 4��5i�8h>��5e�H�p� �m�5o����5b 6d(6i06ll6n7o7rt7š��Mȫ�@6e`6l��i�̱H6s$v��P6ed���ؾ.�6e�6s�6t�X�d�6e�6m�6s�6t�6v�<��t��.d"(��#���0�)��2�6m���6i�6o`�t��x��47b@7eH7oP7tX7u��P�!n��.(7a��2��u�����3\������`7o����h7t�s��
�.�7a8b8ex8hl8i�8m�8n�8o�8p�8s�8t�8vPupu���7e�s��7l�7m�7n8t�u��v
`v���7edx�d	��z9�y��8i88lL8t�{?�{��08s�+pXD8el��T���X8iD���`8l�C��,���8eP�i0�d�x�8m�8s�S(-��H̆h̉8H�M�����8c9d9e49lH9n\9tЕ*ܙ9n$9sl�&<�Mܰ�T���,9l���и��@9ep�i����T9a "�9a�9ex#i:k:o:r :s(:u��Q���9.�9t����9r�Wp��9n�9r�9t:y���h�.�9e�W�]�'.�al#X2�T<r4Bf�R�8:p@:t U��V�]�l:ax:y��	�_��X:r�]��`:i���M�:i,�K�����:ih��:n�e��:t�e���:a;b ;c(;d0;f8;gX;l|;m�;n�;o�;p�;r<sD<v`g��?�h�;d�g��;i�id�i�PnXp
8{W�.d;bl;kt;r�z��@;aX^%�_(�{����;a�;tX�pԇi�F��;sĈ]�;o�;s�;tL�O���d���+�����;a`�J<a<eܔx\�'l��8�]<o(<t����X@���0<r����8<e@��̵��P<r8���<a�<e�<o�<uD�x����|<h�����<c�<s��m���<k����<n�����<e��q�:�04���<sS��B<�=a=y��t�8��?T.4=lX=p�=r�=tPP��,aH=e�u�Pi`�P_��P=lt=o|=r�=s�=�8`t�`��`ta(a���=�laJ�=e�c�b�=s<pZ��@����=a�=p�=sȺ	��z��Z<��`���=� ��>a�>b,?dp?e|?f�?g@h4@iD@k�@lTAm�An�Bo�BpCr�Cs�Dt EuXEv�E���>a�EbtFc|Fd�HePZf�Zg<[h�[ilcj�dk4elpgm,hn\ho�mp�mq�mrts�|tt~u�v��w��y��z����(py8��>r�����>e?o?sP����Ț�|�?l��� ?e@?gT?ä����`?r���H?�p������mh?f��J47�
С���?b�?e�?j�?l�?o�?r�?u@�`F����?v�O�����W.�6�o���?n������@��y���M@e,@l��0�MX�����<@ad@ol@rt@s�@t�@vȬ8������\e����@g��@i�����0����@aAb$Ad�e�@j4Asxu�<\.�@s�@u0�(P����������@r���Ae|�mAu�?
�tغ�,Ao�T.lAntAs@���@Aa|Ae�Ao�Apо(�*��T.�An�Ar�As�At�*h��@����Ai@��d��l�2�
���Ae8���
�.Ba$Be,Bi4Bl<BnhBopBs�Bt�By��x�Bl��'��\����TBe`Bo<��LBb\�(�h�t�8.�Ba�Be�Bk�Bv��2HK�|��BlDf��M��Bn(����Ba`�����.�Bih�������x�JCeCpl�2�n����
ؾ.�aHCbXCdlCe�Cn�Co�Cs�Ct�CvH����VH��PCs����dCa|Cs��(����]�Cs,���'.0�����T.���Cah�X
a�CeX����.q��Ci8����CeDh Di<DjHDkdDtpDu���
������(Dfd�0De���TDo(���������\Dr����Dl����xDa�De�Do�DrEs���	H�e�Di��ȶ���Da���Dn��((��Dk����Da��,�	�����Ej�
4Es@���EdDEgPEt�Dm	XI����<Eo�dX�JlEbtEl|Es<��l <�D���E��m�EaFe0FlTFohFy�".\���Ee\P�El�En����
e��7.��	�Eaex
�Et����FgpFn`X$Fi�"7.�"<Fnd!�HFe��?�.`Ff 4h>�Fa�Fe�Gi�Go�Gr�Gs�Hy�H�,?J>�Fd�Fg�Fk�Fr�?D@($�"	C���FmdK-(KJ�Fe�H�Fi��k0GlhGm|Gr�Gslb(�M"GpPM��$GaDGoTGs���/O!LGvxP�(P��`GiV;�S]tGstX��VM�Gu]��[�Ge�j�\h�Gm�oW�m�Ge�Gu�T�sJ�GnPtHlt��Ga8HkXHtu��t�Ha����w�$Hpx��,Haܺ�dz��DHb0zLHalHetHr�z�	p{܁-��|Hbȃ_�Hr��y�����H��H�p�&���<����Hr�0IaPIbJcJd�Je�Jf�Jg�Jh(Ki�Kj�KkPMl(PmDQn<So�Sp�Sr�Vs�Xt�Yu�Yv(Zy8Z�܅�@IkHIl؆+�] �/�Ia�Ie�Il�Io�Ir�Iu�IyJ�XxX�PxIr�Is�It	�		���D��p��
�In�3�$]�I.d'�
@.	���ItD��|u�
�u���I�P�̌���.8Ja@JdPJe|Ji�Jr�Jy���\�;h�A���HJ.dJklJl؍��H�tJa�O������.�Jr�
��-�Jf�Jt|���jԖJ�Je�Jg�Jl��UP�[�2ܚ-KaTya�x��Ks�s�Kv�JPKddKexKg�Kk�Kn�Kr�Ks�Ktl�x4����e�AXKn|�i��]pK.�Ke�Kr���.�Kn��pН�����"��x�J�'.�Kt��*$�~�-L��d����Ka4Le<LiDLjLLk�Ll�Ln�Lo�Lr�LsMt Mu(Mv8M������������`La|Le�Lv�#lLn $(Х(ܥtLl�+�`�P�Ly/x@����Li|���Ld�Ln�Ls�2��6�:���\�tMiMrĨ���Lt���xI������Ȫ����t���HM�0M����ȫ��T.�Ma�Me\Ni�NlOsdOt�Ou�Ov�Oy�OÌ^x��"�Md�Mg�Mn�Mu���
(����Me,bn
�����Msf������MdlfNiNk,Nl8Nm@NsLNv0�Nde\p�H��	�r�`fJ$NeT��T���Lt���P��"TNd|Nn�Ns�Nv�������tNj�Nt܉X��$�����(�p���Na�Ne�Ni�̱�Ni��k�Nrp�B���Nf0��	<��Ng<�!(Oa4OeHOiXOp������ Ot(�2@Osx���������XPOo����Oa�Oe�Or�wY�*xOa�����Ol̵2 �����D����Ok8�i���Oe���������P��O��O�@��PpPy����(Ja`�
�������� Pa`PbhPexPi�Pj�Pn�PoQpQrQsQu,Q�
������pPe<���.���Pa�T.�Pg�Pk�Pl�Pn�Ps��H��
d��i�]�Po���$�i4���Pe��н����ܾ̾��<Q�$Q��d����.xQa�Qd�Qe Rg,Ri4RoHRs�RtSuS�@��Qk��`� ����Q.���Qn�t�Qep���Qb�d�Ql�Qm�Qt�Qvlx<��d"��)ؾ0�'nR.��Rn��tRe�����@Rm�(��;`RilRt�L(H�2XRn��94�=V��tRt����|Rn���Ra�Ri�Ro�Rr�Rs�RuS�HD��2�Rf�Rt<L`��t��X\T��]]���R�(���X����S�,S���d�]�4Sb\SgdSltSp�Sr�Sv��_���h�c,��lSp���@�(�����Se��4��Sk�Sp��Sa�Se�Si�Sl�Su��T������j�x�]8Ta�d�TehUi�Uk�Un�UoVsP7t4VuXVvtV�p��4�"0Tm`TntTr�Ts�Tt�Tv��{
����XTt���X���lTe�Tk��
��p�����Tt������Ted��(�c���Ta�Td�TfUgUpUsTUt���Td\�X$�%P����
����Ui,Ut��<U.DUex�������xLUt`���`Uf|Ul�UnH��x��
�U�b����U����h���e@����J�Ub�Ug�Us�UtVvt��4�c�Ue�:�.`��l�P�I���Vn$Vp��<�i��J,VnHVpPVsT��(��������l�q
`Vr8����V�hV�,����M�V.�Va�Vc�Ve$WhLWiXWj`Wk�Wl�Wo�Wp�WttXu�Xv�Xy�X���4���B����VgWrD���������Ws���0WpX�MWo��J4�;<�]8Wt��@Ws\��|�dpW.xWe����P�y,��We������Wl�Wr���x����X	�W.�WeXiXj Xo<XrHXuPXy`X��������W.Xm4����x��\�L�����(X���0X�T���t�	����XX�����lXp���H�`�������X�����
�.�Xa�XeTYi\YjdYolYrtYu|Yy�Yä���XkL�*l�a���YaYg(Yk4Yl<YnDYpLYs��cD�x��$��� Yk����$�%p��������t��,,������������Y��Y����-�Yr�Ys���@����-�YaZiZn Zu�|W.<����Ye����Yn����\(eZk��20�9��iL�>HC,��HZ�0Z��I$�Z���O��\Zr@/�dZa4/�pZr /-|Za�.��Zr����Z�@0��Ze�Zi([j4[o�8�"l�Ji�Zn�LZ��V�c���ZhX��Zn����ZeQ][rQ��[��O[ìY2�sh[a�[e�[u�[�Ty��x��T[s�s�\[vT�\�z�t[s�z�|[m�y���[i�������[a����[g�xT����[r����[���D\a�\b�\d]e\]f�]gh^hp^ix^j�^k�^l,_m�_n�`oap4arTasxbt�bu�bvXc�t�tT���<\kl\l�\r�\s�\t�\v �`����d\g|\i0�ip��\e����M�\p�\t<�eX�s��id�M�\s�����=	����\iЕ���\r���
Ě���\lܙ]]l(]n@]rH]sl��'.8]d��HT�<��t��p]e,�]P]f�]u�]Øt,	,x]ld�nl����]��2u����]d���]n�]tx�]�]a�]e^g^j^lH^r`^u ���{P��]s(�>̡�ءM�2a$^e,R�.�_���]0^n��t��K8^eX^u$a�] �,��%H�-$���.�]�^a�^e�^j�^n�^o�^r�^v<���^mp���0�T�� ��ثal��T����^i_l��"<�_sܰ_e���\���$_aP_e�_o�_p�_�@�xl�H_dh_lp_sx_t���@�ȶ�t�����,������_�$�и��	�_a�_d�_e�_g\`ih`np`ox`v�`à��,����W�_nQ���t�_k`r4`sP`Ô�axd��*`n@`r̾d$`e�d�����H`�,�KT.���`�������8����`����<���`g�`l�`n�`r�`t��|L�2\�]�`a�`o��(�a�����`i����]�`r��Jal�������adDag��]$aeLas4��[
����	�.�ac�ae�ai�akd�s(btHbv\by��tH����ad��P�ae�_��P�ae��]�anl��aa�ajblbo buD��X���am��4��d���'.beP3���
��M<bi�����4bk��/Tbi�������(p�dbl����lba�be�btD�
�bkp�t�((��bt��t�be�br��| ���X�.<�M�bs,�WT.clcnT����ba(ce4ci<cu��������� ck����������Dck����Lc�������dca�ce di4do\du�d�\����.�cn�cr�cs�cv�����i���ce�����cv����H!.���t�M�ce�&
���cn,dr��t�caX���dg���dn�&
��$�-,�<d.��Dda���Pdlldp��
�da�
D_xdr�����]�d.����d� i�da�di�djeo(eu`�.���da����dr�� !^	08�X2���drTT.�S��ee�R��el�]6Tea�ee�fi�fogy@gÈ]2��g�en�et�evLa�Tales`atteaa���ed4e.�d���ee\f���m���ei�k
�edT�ffg@fiTfk`fl|fm�fn�fr�fv�nfr�n���ea fe�3�oH�����.�pA(frhp��4fe|q��q��Lfa�rJ8�	(tthftds��pfs0u�Qt�t���fe�v����u�}���Yn�}M�fa<�i�ff�fk؄
\�i�]�feL�i$�P.п]�fe���gs��.����ga���(gp�~��Pg�4g����dgr���d\ge���ge�goh� ��.��]�ga���gl��g��ge�gr�gs0��`����gs���X��.d���ge<���gr���� h��g��TQa,�hlL�<haPho��HC��e<BDhd�e�ha�hb idPie\if�ig�ii�ik0jl�jm�kn�kp(lrms�mt�mv�m�tg�
�e]�hv�g�ha�he�hli�|gh��h���hn�h��ha�i �i��i� j��iia<i�4l�`?rHl��0i���plHiuPnJliftir<on�o&@�TV|id�is V�ieXp]�im�ir@���r�t��2�in<v���iijkjn\'����ie4w@w�inLw]ja8x/�|pHjd�z��$jelji�jj�jlP}�`>��O.��Pje|jj�~"\jn�js��5��x��E܄l�"�js����je�ji�jl�jo�jpkrks�ku��l��js��=�����C؆G0���tka4ke<kiDkkLklxko<����Oh��L��lka��"@���Xka@���`kg���$�Ĉ]hOa�kg�kk�ks�kt�O��d��ke�kl��M�iL�t�kt��	��J�kaleli��"T.@�T.0�Rܔ�`ln`���lahld<e�lg�li�lm�{o�lp�lr�ls�lu�ly|��0�T�Ya<��,tlt��2|ls���le���<��le��`��
l�Zt���lk\�+<����le�`�d��(�le8�]�.,meDmkLmlTmo\mpdmt�T.<mt�{(L��il�o����dpmrܧ.	�Cp�]xmr��J��a�me�mn|�3	@����mn0<r��Դ���E�̵���i8�na�oe|qi�rosu\sy�s�������.8na@ndXng�nk�nm�nn�np�nrHos\ot|ou���4���h�.Pne|����ulne����dnnĪ`�"xns$����ne�nt��{h����������ng�	�����n.�na�ne�npt����c`�����dZaob$oe0om8or���0�h��ot������
�������@oa������Tot�V+���hoa���pom��q
����
�o.�oa�od(pghpi�pj�pk�pl�pm�pn�prqt\qvĈ������os(�]�os��dpepr ps���.prD����(��4���.DpePpj��h2<p.�OR$�tpn|pr���Xpe4��H����'.�pnh���K�������pa�pk�pt��{���pix���"�pg�H��	�	�
��ؾ.qn$qr,qs4qt$��D�L�|�@qs�r��M
���Hqt`MPqelqn0�������tqa�qf�qg�qkrlrn0rtprv`xT���ؾ.�qa�qe�qk�qs�qt���.<Rd2�r��(	�q
rgD�����!trr(!��$rt@ru�!{drn�4.5tLre�!Xrg"�	�9� 9]xrn04���rn�rp�rr�rs�rt�rv�9".�9���ra�rs,��t:q
�:��;]40a�<q
�rase�<i�<2SJ(sk0sl8sm@spHstT�XT��T�	P_*|V�
dZ�lskZ��Psktssh��p[�h]1<\��|sm�sr�st�sv@=���s��s�p^�. ^��se�^H$_2�^���se�`8ie`���sd�sp|a�B�Pta�ub�uewi�wjxk�xl`ym�yn�yo�yp$zs0zt�{u|vt|y�|�4b���'.�tb�td�tg�tk�tl0um<unPurXushut�b�b���tlc�teȚ�@c�tl�c��ta�c�.le���texd���ts�e�uauf ulLf$fu.(��
��Blg� g�(ue�g�Hue|h%�i��jJ��k�`uf�1Zd1�	tun ��|u��l�
�u�tn
�'.�ud�uf�ui�ukvl4vm`vn�vp�vr�vs�vtwv`oxHp+q"�r�r���usTs0s]va(vs�;pt6 vatu�@vi�ua\v<dvtHvatvev��Pvd�vg�vsxv0.�v�wC�w�	x��H!.�vf�vk�xI y��z[
z���vk�vt|{�|O�{���va8le�vj�|'�}+�(wdlwl|wn�wr�ws�wv��n<waXwedwr���(�U��\Dwt܂Lwn�c���������twd��n,��Ԉ������we`�8ԉ��wn��we�wo�wu�i�wrD�
��t�3	����wb,xn��r4xtT����wa<xj�xo�xr�xv`�8�����_Txetx�P"����Lxn`#nL�]`xt4���hx�d��xth����n����xop�
��8�xa�xeyiyoLyu��������x.�xn����,��xm��"x�yk(���.8yt�v8��$y.H�],yt̜(h��Dyk̞2��MXyatyoh�Ä���2��M|ye�yox�i����ym�ys�yt�q	���ytܦ���ysX�����@���yezizy��H��ykzl��~���0������zt�	dza�ze{iL{j\{op{r�{u�{v�{yT�*����\zk�zl�zr�zt�zux�8����(	��ظ@�JH��z.�zi�zm{o���\���zk��.0�J�za�zpt�����|����{g${k@{l�����,{i���4{l|��������T{rD����h{iZ�����|{a�{dX����{n�4�����5���{e�{v�������{r�{sD��(����{e���|kdk[t��(|a<|e`|i�|���Ye����|n�������4|t����.��]H|a����T|k����l|k�|n�|th�n�|a\�t4��4�"�|rl~���|��|���Y`��<�	�|aH}e�}i�}j�}o�}r~s$~v@~����}l4}sp����
.8���}a(}e��h
T.`�xx�*�xa\�<}ml}n�}r�}s�}t|������d}gl�P�x}e����}l��$ye���2�Z�
��(	����}g��}a�}o~�85�P���}c�!(	�!��~�#�P7
47~at;�P~k`~r����l~�0~��;��<�@<JX~s>�?�~b�~c�~ef g<iTkhl�m�n�o�p�r4�s��tȀvXA��@���~b�~lBf�BM�E��+es4E���~n�+s�E�hG��F��oH*,u@J��K��J��4t�L��.4L��Ha�R�PP��`lxy�U�	�X|�V���s�Y��|ga�d�i�sTZtT,e�[�t]��t�Q��]���a�^����P_���kla*lHa�e(�t�b�. �ixc�g��aiM	`�ap�e|�i��k��l��p��s��t��v�h@t���i"h�m�j�dk]l��lmm6�mhom<p"z��.$xM��a؀eXx���.@��aL�e��ol{���
.x{����a,�ez���l8�n�{�	�{"$�l�|���e�p����D�sd�v���̌M\�at�e�����.H�
��r�� �@�
́a܁b�f��k,�l@�n|�p��r4�s��t�\p	�
`�Mԁa��-t�]�'.�t�e�l �� �5�[�����,�J\�T.h���4�a�ueT�kH�S	`�s�7���%
��&h�sȺ��p�e��rP<ĻT.�����aԂe�l�s�u(��X�s����a����ȂboR.�n���e�)�g�)��eľ�Ŀ��i,��� ����]4�.P�aX�ed�st�t��8��.D�>�����l�r0�����.<����r`��<�����ȃ����������s(���8.�d�g$�k@�m\�pp�r��s��t��v�y��d�����s�������rP�d0�n�����h���8�a��T.h�n����L�e��(��J|�i$�,�d@�.��tD�z���d�t�����āe؄i�l�*����ĄlL�d���e��.��������.�a �g(�s���������\�st���0�dp�n|�p��r��sЅt8�p�.����d�a���t���.������a��iX�i�Xؾ.�iaąe��4���m,��4�. �]��b��d��f̆g؆k�lx�m��n�p,�r��sԈt|�u��v��܅a �bP�čd��e��fԖgܚh�i�jd�kȫl��md�n�o�px�r��s��t�u��v@�wL�yz,ì�����i��l؛�f���
	��i����]С��Ću���r��s�t8���{
�����te0�����.0�lD�oX�tl��L��.��$�e��(���<�v��RĻP�eԽJ���d��@���a��nо	���nH��8��.će܇i�n�o�s��T.ԇtt��
�W��>��((��r�t@�.�l��ax�$�el����H�eh�i|�k��m�Ct��]X�r`�t`�������������.��p�a�.$���a�8�����i��pĈsp�N�������̈a�e<�hL�lX�oh�rt�t�(	(���l(�r4�td����nh���e���$���t�GD�e��-���,�`�ix��@���g��r؉s��t������a��e�.@�.L�����aЉep�t����a�e����i".X��l|��l��a�e�m
X�at�b��e4�hD�lp�o�s�uD�y�u�@�\PP�d�En�
2x.D
h�e\�(����ex
��d��i�n�r�s(�t�A(fr@Z��e@O$t̊k���Ԋs��*
p��s������u�_�k`���	 �sH� �`X<�o\�ð � ��T���!d!�h�b��d��eċk؋lL"ia�m�\�.�"]��s�"��r�#� #����r8{"$�Ћa$*OF��+�e �n�+���r(�t+��eh���Ftt0��0��0�a�.8�t 4]l�at�c|�e��h��o�3�5�5�7N	�7����a|;"�:����m,?J�'.>��d�g�rh>��
��a\�d��e�i|�l��o�rl�s��tؐu�v�y̎Ì?��st�^CJ,�k4�v���
�C�
$G'0G��<�a�FD�l|FP�et�y��(�Hl�r�H
h�.��g�Fi؍k�l�n0�pD�rp�s��t�J-�JJ��eȍn����L��K��Ѝs�M.PM���e�$g	DQ����f�g �t R3�RO@�9�S(�r8T��S]<�a\��hV�	tV��T��`W��VMh�k��l��m��n��p�W���$���W?�X��X����aĎt0�/����d���t��D\��n�[�a̛e$�g0�sP�v������e^��]]�jTa�@�iH�k�a��a��b� f.f��X�e�ed�g4e6p�e�h(\h��bďkЏl؏p�v`yP.Dy����e�i����s0j(�k"�m"�mK�a�e4�oH�yn](p*�o���g �v\qM��.88*�r��,�m��
\s��@�g@��x��T�i��rt�\�k��l��m�xE�x�	�x8��a`y�|��|��a��eȐrH}2�}�l?t~Аa�
��a�i��Z������s������k4�nX�rT��@���,�k0��.@�@�e����L�k���p�Jȃ�l�r����aȑbБd��g(�iT�k��l��m��n�p�rh�s��t��v܅� �~̌]ܑi�2�G�P��aԖ���g�n�Ä�Z��3	���������PKd@�n������8�i�`d���L�el�t ���d�e���ȫ��x�a��i�����a������nd�xȒbؒi�k��t��P�/2��Вn$�"�U��l4�Zx����a,�e8�i@�lT�m`�2��$�n��2��C��2T.�H�eX�_����`�hx�k|�����a��e��".��x��M�$n$M�aX�e|�f��i(�j0�ll�n��o��r��s�tP�ul�y����W�l�n8�r\d����s|�����a(�ki��
�*0�kH�s0o�	t�	P�ah�f`
y�2��k�]p�e��oP	$2�W��d��gДl�r�s����,~���Ȕm���ܔn��e���'��nh]�e���k����L�e`��d8�e$@�g�����X����h�.\��t�i��b��n�n�`o#��']�'��jЕkؕp�tT�]@�����@(��rt(��a �eD�lh�s�(]�r,)�H),�i0�r�)���gl*8�e,�ľ�.XX�s�-`�r�.����x������.�	��l��y/i/����e���/"�f�l(�m<�v@0����a`�d��eP�g��i�lh�m��n̙o�r<�s���X1*�1�1���a42��6"�.�6��0�e8�7H�ll�s`7�P�ep8��:�0:t�o�8|�d��iԗl�m�n�o(�s�;��;J��e$=��<��th<��ȗe>J�e��@O�k�>]�s�M��@��@i�mtC�4�pDD�H�	�G<�nHGD�ed�lt�u8I�Lp�4Jl�t��2K����n�J��a��e��kИshKmT��L]��ohN��M��Șp�Q�8Q�ܘd`Q���a�e@�iL�y\�ÀR�,R��d,�syhdS��$�a8T(	�S8�t�U
�U��U��T�� VU��Wp�g�VMx�a��e��o��Ì(tWP��m�Xt�Y"�Y������YWT.ܙi�Z�]P�a��t�r�]���s@]����n�a�a����Hc��bX(�mdb#0�aX�kl�t�f�	�e��P�i�h��h��d�a8r���yG�����x���rlJntr����e�r�	��v�psM��aКe`s���.�sM��e\�o��y����ye�y���a�i$�r�z�z��eL}���'.<�eD�nL�s�}��}�t~`�Ld�xT�gl�v,�������.����t�e��Z��r���������aT�����v���ț����0�t@�.T�Z<�kH�nX�r����ԛad�cl�d�ep�f��gp^i��k��l0�m�n��o��p��r�s$�tԣv��t���4�t���
���p���P�.H�xЕ��.��a��e�f�g�o��r�uC���"��r��xX���aȜoМr؜tD��d��	�X�PZ�
����
������ܙA$�d,�g4�nH�r`�s$��H��l��X�.H��T���@�aX�t ��<����,�]h�l�x�]|�.��a��e��iНrܝu��2P����v`FUp�i��
��Kȝa�t��$���a�]�a�el�r|�s�*��.8�e@�hH�lX�rd�t
��
�P�(̦�����P�i�( ���������t�a��e��o��t��T�(��e�l��o(�sm����d؞g�oET���Оg���ܰ�u����h�.�a�p �s8��	�p�����\�(H�at�e��o��p��W��.\�gd�l����@�(l�l�d��i��n�is��t����ȶ���t0��t�t������$��ğl�n<�rи��
̟aH�dd�e|�f��g��i��kh`nРo��sP�ud�vt�ô�]�s,�t���
h���4�e��T�up��ػ���W\�dt�l��T������t��a`r,�W��t���0�Ġa�
�4��r`�"P6m�v0G%|L\(�2�d���i�l �o0�pD�t0�|��Z�����
(�i4�����<�o��\�m0^*���"8���l����i<����r�����a��e��s��@�2������a�e�i�s$��ءk�����n(�2��;��J8�aD�e��h��i��k�p�i���0�r��Wh�.\�id�n��rd��	��*yD�x"l�e���t�i`�(p�M��a������l�l�����.Ԣeܢj�o����-d�2H��	8���e�ix�(p�(D��kL�r�����ed�ot�t��u��v��������D�a\�e����]��
��tl�a�6����t�7*47��e�����������(,�W��k�rT���ģa��e�i�s��\(����n�2�����M(�o@�u���:e���:a���4�l�W��lؤm�r�v ��L�a�e��h��i��j��k`�l�n|�o�rĨs�t��uȪvL�wT�yt��Ȥeh����i�����g��Фs`�.���a�2�
J��r�
JDe��i@�rL�s\�th�y�H���8�oH(����T�a��,�8d�xp�v x�o�T. !>l#�ܥe��i�j(�l8�oP�uX�y\%
<%��ȥe�$Хlx � ���t'�sd'���$#S	#�����'��4��(��0�kH�vp)�l+t�+*
 ,K��a��eĦuԦÈaCa��x�eH,����n��,��g��n��t�-YT��.*|/J�/��̦����/J�0�a �e@�iL�o`�up�ð0X�.���0�k0�s(1�@1PL18�p�12X�k�1��1n2�2��h��X2�T.��l��rاt�vd5V@5����e�8�p8���s08����d$;.0;]̧e�;(T<\�a�ep�ix�o��y��À<nh=�x=���d8�f@�nP�th�v��>����>��H�.`�s�>a`h�>n�?n$A��A���d�A������Anxd�	B��k�l�m4B����a�e �i,�k@�p\�tĩØB(�BupCnC��l�mtu	ă��D�glEM8�a\E(�G
L�e�GihH(�H��T�a��e��i��o�����Ix�l`Ia��lXI|����IoJ������J(�J�����$��
L��ЩmTK��ةr�v K�a �eX�i|�o��rpL��L%
�L�f4�p<�s$���M�H�t4NY�(xNP�dl�mt�v��O�4O���rl��P��a�O����i4P�@P��a�RelW��a�e�ihW(HW��ܪl�r|W��@�.�WY�d�W�����X���k4�n<�sD�v�X(YY�Y�Yi`�tL[J�Ae�[*������l������[��\��Ap�]"	(�g@�kp�l��m��n��rȬs�t�v�]����a�3b �c4�d��e��fدg�i0�j8�k��l̲m�n�o�p�r<�s��tD�u�v��y���,_��`Ba8�g$_�
�_"T�e\�sh�t`',`��(�eD`��`"�`��`��x�ea��p�l`���b����g�r��tL�h�c���id��\�.جe4d��d���Be�o	�l�\fJ��l�s�f��7��g�h�ii�gt,�o\�r|�s|j�Pj��H�rjP�ePk�8k8h�e�j�p�lm��eiԭr�k
��dܭe�g0�iH�k`flT�m��n�p�rT�sh�t��v�mCn�`��n���d�e�nXo�oR�rx����M�aTq�hp��(�r@�shq"�q����edsJp�.��a��e��m��pT(	T��\s�x�t8�3�s���n��t�s�
���t6�t�ԮaD�hܮs�t�t��u����u�uXv�<v���a�vi �a4�e@�g�v".�w�Dw',�t���
z2`�u�x��H�kPz�{��̵ax�eX|4���}M��.��a��n�(�}����kp~�li��i(�X�.�]̯a�ePi�sH��G���e����l��<�"<�eh�fx�g��nȰo�t$�v���	\�r�����H�r����P�e؄�����np�r����d��e��s��it�W��s�������p0�S����sp�.���԰a��e�t��(؋�tČ�(��4��n����e\���T�ah�e��n`��P�2L�l`����P`�s��t;���x�e�������x.ır����a̱e<�i`�s��u����b���d�g�i�r�s�t��(���e���gX|)T����e$��n.`��	$�a0�0�gL�s����0�j(�
8��X�a|�k��l��p��v����h�^	��/L��������r����������$�IJoܲu4�i��L�"�k(�mT�p\�rx�s��t��v v�.�����a`�J<�mD�nL�s�����
���P�"��"h�tl����i �]p�t��������.�����sp�����r����eȳn��"	��(<�гtؤسe�hȥ�X�?(�����s$�E�o��(h�e���ip�nx�s���	(�e��i��j��k�l��n�p<�tl��pq]<�*T�
ܩ2t�X\���e0������l̴n�r������a����3	Ĵd����شe@��x���M�o��Wh�X�e �r���x�(<�(�n���0�eP�iX�j��W|�(��J|�k�������`�������u�����e��"��p����lȯ��a̵el�o �r4�uH��'.�g�r�tD��H�����o�?
����tس(,�p�:�o�����]��*����
<�at�e��h��k��m��nĶpܶr�s�td�!�J�xJM|�eض*T�m����eķ����g��.\��жe�_.�������������r�2���n����a8�eh�rp�s���\�sh���0�dL�i\�r��(�̻��T�ed��|�=��.Ģ�.����|�e�����r@�������Wȷeܷnt��.`�;��t���<���Էd�q �r8�t�~�������@��h�U@��s����e0�v����
��q
P�r`�y��!�'����X�.x�a��d��n����.d�'��q
��r��t��q
`������T.�a��b�d�e��i<�j`�l��m��n�o4�pнst�t��u��yܾ���J��
��dH�fT�gl�ht�k��lԹm�n�r(�sT�th�u|�v��5L�-@�o����Ked�r��:��-(����Ke��o��t��u���0���i��p�".<�����e̹j�@�(h��(���ܹaLi��u0�)��������e�m��9���<��� �i@�k��x����8�u��GX�tL�a�����J`�k��p��Jt�e�M\����lL�P��l,���a��e��	x���dԺrܺt�4�Ul�;��'�i$�l8�n\�t|�v������e�����]�i�G
����0�d0��p�s��wD�id���P�rX x,�8h�k�����Bh���b��e̻gԻn�r(�s ��p�����t��]��r�������i�o,�i��s�1t�����t�D`�"�r��
������� �a��\����4�eL�uX��$�il�s���T�ep�ZT��'.��n4��t�e��i���`�.�?���dļeؼi(�T.X�d��m��D��]�l��]�o��n��p�v�����(l��dP�r��$�e\�l��oT�����H�i����a��i��a��il�.��t�e������n(�
D����l�s��(8������s�tH�t��e�j�l�n,�o8�tL�uH���T&e�����uL�6���(�$�r,��D�eP��@��p��8���T�a�2\�l�h�a��iS����l�*l���g��k�i�iľk4�,<�
�r�y������̾�d����(�n���a0�s,��l8�ra�,��pn�"��kȿl�n$�rP�sp�t|�vL��@�a��b�d�e��g��h��i$�kD�n��o8�px�r��s��t(�uH�vP�wX�y���<"�������dܿi�l�i��
x������d�e�g�tt0g���
�	��p�.8�e@�r
'��
��z ��H�s�� �\�p���d�s����i<ya,
��e���
����a�
��nx���A����d��n�t�a��el�lt�r��s������k�lD�mP�r`�s|�0���O�h^s�O��$��xP��J<�i n�W5�MX�l��\���o��a���tD���r������p����eX�{P�E�aJ�g\�g�i$�k<�mD�n`�rt�s��v�G��JDeX��J�s4�td����<iP�g��h����X�g� ����l�k�#���e�#��#����n<&���r�%t��a��e�l0�r@�s��u��ô&�>(\'��m��r�A��'n��i�u�'��(�(�st(M�edS(|)T�*��*#8�eX�o`�pp�t�gi,h^	lj��+��h�iLp(,|�t���,�����h���,_��j�,T.��e��f��r���t.����kL.]��r�.��0�xt5���a0�i�4�rD4]�e����d�lx�8�al�e��i��o��s��Ð:��;���m��s�<��<��|�t��v"?d=�>��?��>t��j��t4@���aBIB�����<B"�g�k�m�p,�r�C��C2DD���.�D"�DB	�D��$�d\G�X��(���G��D�s�G��L���(H2d�m�Gml�e�b�I����b��f��k�Ht	��a��eH�i��l��o��t@�uT�vx�Ðci���e���rLI����e|Jt�k�l,�m8�s@�u�sHK]�i�u�t�J$�b�K'<��L2p�d��e��mhL�pL\�.|L��d�e�L����t�L]|�rd�(D���O��g��vdO��e\�?L��ب���t�P����p�Q�a�e$�ix�[�Q����l4�(�R�r�R�h���S��,�d�S4�nT��SXL�ed�iHT��TJ��k�T�����l����* U
�U���'.��p<U��a��e��i`�ot�r��u�V�'.��c��g��l���W(�W(\Y2�e�g �k0�m�Y�<���Y���(Z��(�aH�e���Z@�n�Zg
l�i�Z��T�r��0[��a��e��o���X[(��n��[3	�[����l��p8: ����i�"88\������\/�\����l�n �s�4.5t��e�\��g6	$]"8�l@�r^+l^�T_�`,$`T.t�b|�r��s��t`�-4aha�$���a����ab���mD$������������Tcxhb���k��v��y di`��<d���d�d��s|e�@�.�e
T�ad�b|�d��g��i��k��l��m��n,�p��r��s��u�e�h��g��\�et�l�hR�i���dPj.dj��eXp��r�r��t<v]��sDy*�z]��o������$sĈ]<�d�e�k$�t@�id��
�l�����:
���H�aP�lX�oh�pt�r���x�Sؒ����]`�r��0���Ya`���|�d��e��i��o\��<�"��d��e��k��s�(��S����K��W8���l���̵4�aT�e��i�lp�o��p��s�t�����,�t��)p���@�o��H�nt�p��r��s��t��u$���������|�r��%
�]t0��L�����a,�l��������d��k�s$���.$�]��a��r ���������o��$�eX�<�e��nD�rL�sT�t0u�x�����{	�mt`�\�t��d�e��k���������el��]��stu����m��s�]��e��l��t�u�yz'h�����u���8�]��n��i�n<��i,�r����"��b��d�e�f,�g4�k\�lp�m��n�pX�r��s�t@�ud�v8���4�a��b �d��ep�f��gD�h��i��j��k��l�mh�n��ot�p|�r��s��t��u��v�y8����	������b,��.4�����a��5��m�l�J$�tx���g
$�"D�eL�l`�h
X����,���T�uX��h���h�a@�=��|�.������a��d��e��l��o �(���.8�t��eh���h����i(�����.�r������e(�i4�oD�p����%� �nD�,T��`�]<�e��(	����P�.h�g<�2|h8��tp�e���x�n��r������a��j��k��o��s��t�i�
��@d���el��8����
����0����k�����e$�o0�r8�t��	�rt�G����JP�k\�s<�M(�eL�X�Jh�.��h��l��n��r��sD��
��S�Y��_ �h�.��i��o�y��|����i��P��tH�La�eT����d�/-�.��lT��<�dL�n���e`�il�r��s��vH�(,�d���D�s(�(4�X�sH�K|�a��e,�����<x������j����k��t����o,�(��g�8�i��a����$�fP�gd�i��k��l�m`�n|�o��r��s��t�v�i��M�f4�t`�oh2<�n4��D�e�i���\�dt�e$v�"���|�a��e��s��t�.�8�;���e�i�����e��i�l�s���"��t<������iP|T��J�.0�m8�nT�`���
@�n tH�e���T�dt�gD�h����	�.��f��i�	�	�	���|
����s�
x�.��a��h��o��s��tp
,���8pL�|$
@qsLi`M�a$�e,�n�[
0��	$�e@=��,��L���l����*P�i�X�rhd�a��i��r$#�����e��i��l��o�r4�s�=jh<����v���l�K�P��f�S������i(T.x����tXK�e���� �o�#(�k`�p��t��8i�.@��P�eL��\�rX��h�����a��b�c�d8�kH�ld�mx�n��o��s��t��v �������d��kt������=	��b��l��|�������e�J�.�n����	(��$�t�],�a�^n�����P�.���X�a����p�d��g4�n��sd�tD�
��a��s�&�����r,etdd��i�o(��Dd��i���r����.�a �e(�o0�p8�t��ä��2���8��� ���W.T�a\�el�ix�o� �� �2�2d�l� H!�!�����!�(!����e��o�!��"�"����o`#JH!.L#���s�"M��e�#��#��a�e��l��n��o��r4$�<�rP�sh�t4"�$�(�e�$��0�i��%��H�a`�e%�X%��x�ex�tL/T&�`&�����&��è&��&��'��)���en�)��a��e�)�g��s<*�(*����t,2+��e0�o<�s\��|�s.���e|-$�d�-�.�TQa�.�D�l�.��P��/�|�a��e��oP/20�d�(��e@0��d��e��r��s ���<1;1����oD1�8�t2]��b<2u04J0�a@�bX�eh�ft�g��k��l��m��n��p�r4�s`�tl�v 4n�h��4��8�lP�ri��5y�5n�5��`�f6�47��6��|�sh7���y8088���k��o�8�8p 9���d��oԉ��9�9�.��a�e�9���
$:�nt:�p�.$�e�:�:�	�:],�aH�jP�l�c�:����;]X�a�<X=��>���a��e��i?2��e���?(0?Z��l��4]��n�@2��eB(�k4�l�A���aD�eP�jX�k��l��m�n�pD�t`�uh�v���@B�0B* �aPBt�w�
LC<�pHD�Dh�a��o�D*x�.��r�o2�T����n��r�D���a��%�E3	�E����r�FK��a��e���p��F��d$��GM��i@G�LGM�e�G-(I3I���s�H$�nP�rX�v|H�0�e0I�HI9xJ�J?x�a��e�J(�J�`����r\K������E�Kd��r|K��a��e�iT�ot�r��s��y���p��4L����e�MKXL��sN�$N�a(�b0�e8�lH�m�\ZlN�N� P���@�o`O��x�p���P��`�l�O:h�o�P���a�P2�pXR����e�>���n�>����e�R����r�R�����SJ�a�d�i$�k,�m<�nT�p`�r(�st�t��v�R��SVT�T[�T�	(���T��4�gL�iDU`P_��ؾ.�U&l�e�U�|V���'.��a��eH�n��o��slV(	�Vf(Wl�uqxW�����X����ePX����l�W��e��i��s��XYY�Y(�Y�����ZK(�a0�e<�rD�s�Yw�,hn["p[z�[�`�r�[|�[��X�e<\q
��k��l��n��r��y@]�]����n��o��`]��]3	��n�]���� ^���a��s�^�|_�\_����a�e�n�s�_�d�x�_�.$�k`�`q
\�.l�d|�e��k��l��n��p��s��t��v4��
����`?d�a�e�`��`x��ex�� a!haq
|a��a��a���ebZ��a�a����B���.4�a�b�eX�h��i\�j|�k,�l��m$�n��o�p��s��t��u��vH�y���4bX�a`�dh�gp�l��m��n��r(bpcJ�c��e���aLf�$f|�.�`�lg��t g���ehi�
�g����tj��i���b�i����a��v�j"	�l�lP��e�l�
��itn"	D�gX�i��k��l��n��r��s$�tD�u�p�lp��<�eq�q��P�dl�ex�gpqA�.�q��e�
I.r*��u���pt�s]��svnx���.��e��i��kxx��x' y�	zf�t�v|{Rx����|��s�{���a8le�vj<�s�|�0}%��Xp�r��ML�a��e��i��opwX�.���|�r �(l���p`�p�tl�����r���a��e��l �n<�s,�vT�����t8�]��r���i�ll��_�
���
�����s��Ż2,�]4�tD�i��H�r�P�o�����h�bT���p�a��e��i �j|�n��o��r��u��v��y����Ў"��d��e��r
ď(���@����l��npА����g�n��
���s��_D�eX������0�.���8�r(��4���P��p��D�
L�]h�nP�d����.��a��t\�"h�x���(����e�iP����t̗*p��
��i����r�
l��P������������r��������$�aP�e,�i��o����(,�H�k`�t��(p�.x�sؾ��|���3	(�����k,�������k��v���������������e̞��k��M��a�e0�(d�����tP���t|�Jġ�.��M�aL�eX�o`�uh�y��ð���D�rx�]����]�Ui���p�e��qx�r��������W��b��d�*f��k��m��r��Q����8�����sܦJ��m\�/�il�����i@��8�aX�e��l��o��uԸ���$�a��,�rH�s(���83	H�P�.l�d|�r������
0���t�m8����ex�5̮i�����l��r����*���nP��fd(�e0�g8�iT�nt�sl�t��v��
��e��j��k��l��o�p,�t��ut�v���D���(q���(�t@�i���H�dd�k�d�������e�}��}-x�a̶�ض��a��eԉX��������r,�`7t�����eL�������m��sX����W��X��a`�;T���t`��lظ� �iH�o`�ud������@�lX�r���������ap�e���|�n�E�.������������A����
��d�e�k(�l<�n`�pp�r|�t��u��v�����a��e��ix�j\�o��rT�u���h�G�LT����t��Rx��� �a4�Y���4�eP�sX�t���`��|�i��`����h�t����o�����8���J��.��s j@��
H�	��.\�g��id�k�l4�md�n��r��sp�]\����e�g�����(�l$�s��g����0�J,�aL�eT�o\�p��n\�t��x�xt�g|�k�u�|�4�����.��k��n����������e����t(��dN5lNm��n����e�f�g�k4�m@�sH�vT�x|�����$�k�S	������,�a8�2�������e����x�g��k��lP�n��r4�iD��
d��p���������dl�i,�������k����a��e,�i@�y��8������e������n�r���h���L����D�D���$�bT��4�d8�k��	X��d�dl�s���6�`����r��s����t���>��\n�>����e,?���e4�h����t$��4���<ue������rt�X��a�e�i��(	������k��������g4�k@�s�`��],�t��i���`�nl�t\��h�]X�a4��L����dt�i4��|�v��yl~�������
�8.0�e8�fL�k`�l��m��n�pP�r��s��t��u��x<�����a��c��e��h��i��j�l�m$�nt�o�r`�s0�t,�u\�v��y��z���h����JD�o���4�*X�oȬ)8�*|�aȾd��e��l��sp�2�w.���$����i��s �Y,�����tl���s���غ����������b��n
<�����d�e�2(����n���$�a,�e<�p�Z����T����]4�e�������H�.d�el�tp�'��X������t�sd�|�e`�����j��	��i��o��td��t�����
��3	�����nD�-�\�"�i$�k0�mP�rp�s��tDH����e@�����ax�J�xa���
�"<�n���D�a`�u<�p\�{��h�i@�l����.��sؾ���	|���������������������"��t<*�"��e��0
�����r(����h����<�e`�i�#?�4�v�/�l�s�/��H�g�iT�n�/�"��e��p��r�t�y�r]��d�����pi�����d���w�a�e4�iH�u4�(	����d�k,�$�e$�f���I8L��,�s�@ ��@�.X�m�T�#_��a��k�l��n�ôiF�i��|�.\#����r(�(��l0&����uȖc.������e<'��a����7g��%�������M��D*JX*�����*�&k`�l��n��p�*t
�a��eT�ix�j��o��r��s�u�v$�yh+(p�e|�t�+h
.�����S	�+����k�+-P,�.��i�l�n�rH�s�,<�B-���t$�S	x-*�k�n����w �t�-����a(�f0�k8�r�ip�+�M<�1.��-�@�k`.�d�a��lX.�p�n����]�/����a�0���e12\&(�1����ih1t��k�t�1W2K�i`2*l2���g�rL5��i�8����e8���s�2�i�2]d3"<�iD�n4��4��P}aT�i(5947�	h�aP7?|�n�7�l7��t�g�8"�:��:�	��r��20;����at;�>��r�>i�>���e�?M
a�f8�gD�hL�k|�l��m��n��p��r@�s\�t��z�F��H,�H�$�aH��,�lxJx4L��la`�et�r�L]l�m�L3	�MPP��VX�Y�H�g��uD_DP_����a��k��Ila]�a�e��i�n$�o8�t��8a�t�bKPd��en�a�eP/��e�.�e�0�p@fN�g:
i]L�i�j�p<p��T�ap�sx�t�uU4v����@�M�a��e��i�j0�nd�ox�r��s�t��u�y,�Àz�pz����rz���d�k�l<�nt�r��t��P{Z�i�z����tl{�x{���al},$}`(�s�|��0�nT�t����}L�a��f�}`�r~��h�a��b��kH�mx~"	�{
(�����fH�s�a�e������d�g�i0�nt�r��s��t��v��I�����l܂Y�g�[�yă���s��~(��(�nH�sX�td�y�O�i��P�y8���b�\���l�.d������.̋������ǎM�.H�?�r������e�n�s���T����e��t������s�T.�����eHt��]��]@�k��$�aH�uؗ�$]
��4�JP�sH���X�m��Wt��p�e��i���<�����.`��P����������v0��p���T�aD����l$��a�i���t����n@��
����k\��r�J$���$���� ���8�a@����b��d��k��n��r�t\p	Pl�l`�Mt�a<���O$��h�����g��k�#t@���a�e0�x.0���i��rd�`��a�2����iu��<��@r`���� �H���(�Kpr|yP/2.�]Xa����dn���a�e0�������n������nt���k�n�t �����t�a�����d�[,���t �M��a�b�d�e�f�g�h�i�k�l�mn o(p0rDsXt`v����	t��<����������H����(���Mh>M��p��ȃ��r��������$M@0M�sM��M M�]M��M\;tL��ed"��e�̵M8�M<j�"�BMPetn�<�M@�M�a�ol{�x{��paz��xlȚ'�.�����eH����rt�i`����8�M�e���r`v����P�dX�fk$nD�p0sDv��`�8r���e|
�<t��`-�h����Ls@���Tea-�]��ln�]��ta���l�r��t8����a�e�o�s��04��F��A��l ��b�c�d�f�g`ipk�lpm�n�p�rts�t@	v���ad	b|	d�	e�f�g�h�i�j�k�l!mno�"p#r�'st(t,u�-v�-y��P�9�����o|���5i|�2.����e�m�I��x���С���a�e i(o0rLsXu���Brtp�xiTY��]�����<o�`��2t�#Dk��0��8�����hr�s�t�u�D\��t�����e���a��0���ak8lDmLo\s�`���P�s���ell�|�������sL� r��,e����(��غ�Tkl�5@���ho��K.8���
|a�d�e�f�gkl$sLt��ux�����������a ��t�t�sXj��+��vP����el�S	\�����hH��,k�4s��@edilo|rh�i��2��(��tyx�J��*
�.�a�e�gid�n0s<tXvdy����2T.���.|Cs��"ls`��e "(!(��"t(E�����j0��$k����X(X���Dlh�Le������8���la�e�l�t`�S��l@	r�����������r��*���l�����a�e	o	r(�T.	rD��h���s��,�:$	eT���i�U,	eX���4	nX	�0�<��P	��p	a\P�Enh>�h�.�	a�	c
dT
e`
fh
gx
h�
i@jPk�l�mP
n�o�
p�
rs�t0u8vd�܅�.�	l�IP�����̌��
a 
e(
r��R��8
a@
i�n|q�	��xT3e���H
n��-Ԗh�dܚMp
j�AtpnDr����
e�
g�
ln4t�7�@�m�
e��]�
d�
e��I�� ���
s��(�
eX8i�����.ed��L��� r$���(e��L��d���Hallto|r�t`�C|��
��<�h ��s�v��elNy(����"�gȫ���a�e�i,lPopt��3	���n�pek<����{̱s�� eDð������<���\k�i����X�.��da�e�i�r$�(̵�k��(�( �:�at�i��k�����a�b
n
o
s0
tD
è�ļ���e��нO��(��
dt�$
i̾)ܾ��<
�d����.p
a�
d,Ri�6s�
t@�t���tx
r�n���
gl*�
e���
l�Tx���	�.�
a8dPe�i�n�r�su\�4�"�
lmstp�����t��(<�$m ��,aHe�����TaUglktn�r��(`�t���|.�s$
�4"�n���e���P�eh��|�k�� h�*�rX��ae����kЎ+�������d�.4i<kDt�y���|����]\idrtu����]��V�� T��lm��
 ������H�;�8����.�p�����a�e�irt(y�y��"T.Ya��p�ly� x��0�tj s�������-��-Le0�(����DnHty,��HZ�X�|@�.0 ���r`���/��.����"��.�$]�a�ePi�j�l$o@r`slt|u�����.b g(k0l@nLrpsxt�u�v�(��	p���������8n�*��b`dheH�p�
t��@��@	� ���		�b�d�e�h�m�rs,tDv
T
�x
?�J`��8��i�
���de�(PrJ�%���$idYoL�8-<e���b|c�d�e�n�p�sh�t��te�Jl�����n$�����n�t����J�]�2�g���aei���7a�$g�2]0l��X$�#X8ePot&(�'Xlt(E@-Z,tr�t�-i���x�����.(@0~�s��aTbtc�d�e�f�gh^h$k�l�n�o�rsdt�v���T���l$n,p4s��x`���_H�]\nT�P<e���Hip��|���`nH���he�i�5@�
Е���iܙ��rT��e���,�?�i�l�o�� �i��% x�thr�J`��t��) $����l�]aLeXk��oxs�ul���Dn��]T�ahnL(���
����pa���(��.T����a�i�l,m8o\t��l�2Ĵ<��l�r_stܰ�eԔ����i�|- T���iX�O\�t`� a����ths��:@epit�Lr��3 ij(�����x�����t$���rи��
�a�d�e�gj$nHo\s�t�u��O��WT.�rp������t�r$�9 <<p�i���e<s�? �W
4a`�	Tr�D���G |l�M $�hg0�pe�����s���e��
<�S ���a�e�i�2.8�����e���n�s�����.�������a0c8e\ihk t�����.Pr�X ���Hs��.l�]�a�e�louX��d�l�n�r�������`����d��^ ���t�/|����d��'�n4�

ȓe d����l�����i����i0u��xT��i8.����@e$��Lm����Xi|r�t�y|�C��O��t�s�8
��.T����e���ehu�����\���g l,n@rTs`tL�n
$���d,��n��eH�Jl������e����4n�������Lkp��id��s8���pe���|r�����X�m  S�]��a$e�i�oXu�y��,_���.�g�]���gks$_9 `+�_��ed(�nd�kgHiPkXlhs|tl�vhpZ�q(�rdz��x��`l(}G
�{��ttt������e<��n�s�t8�������tČ�����tL���gkm,rLs�pԟ��a�r ����s`��������$od�x p���8l �]@k��tte�s�t`F�d���ls�����i�*�sп�������n�s�~�������������s����X�a�e4�
L��e�.Ha\bhepf�i�k�l�n�p�r "sh"t�"v�e]�(rXi{
�g��Tspl�Pn(hu
pu��xa�t���l�v.<v���e8{W�.�z���a�d�ek8lxu�|�$|;�r�|"T.dP}�p�4dev��� rPs�(epiXy'�"He�(P\s�i���Ĉn�a�d�e�i�s ��ԉ|�(���d�n �(L����ܔM`����aTdte�g�h�i k$ l� m� n� o� p� sL!t�!u�!v�!� �} ���0v�8edo0��Drlu����I\�-�.�i�n�s�t����tT������sĘ��e�
"�.�n���� ���
<��n8����� �*Z v��U oX �g(g��4 a�*�< pd s�*��H ������+e`�\|-p r�x oT�����`��ԝ2t��� e� i!m!t !v��� ����� a� e �2� dԂ� ܂� .����&!e��D����h
.����(!e����4!ll��@!ad!e�!i�!v�Ix!i�� ���p!e<���!dp�*�!a47�XT��M�!ll���!aH~T����!e`�x�!r��ȡ��"��!�"�ܡ� ,��8�]"eT"f`"jt�p��4"e\� <"r��H"o��p�]X�a�&e�"o�"s�"tX��"k�"m�"p�"r�"�(���(����"v�)������W�*̵~�"o`�S���"e8�X,#aX$ep%it&o�&u$'yH'����L#l\#m�#n�#r�#s<$v,����;h���T#ep#i�#m�;��2.����x#e(��#n4���#a�����]�#o�����#k�#s|�
t�t�#e��� ��M$e $i,$k4$p4��4.$p�w�
8���$el�
p��X�xH$r��� ��J����P$.�$d�$e�$g�$k�$m%n %sv� �u�$l(���$e�$t�����$s0z���4�(���$m�$s\��#nT��$atW
8
���%e�8%o|
��%kH%v�� h��0%.D�eP
� ���PP%e���X%r���d%a�%d�%e�%n�%s@&t� ����%ip���%f4]�%r��P � X ���%a����%k�%p&t8��2� 2�%l� ��&i��2�.����&e!2(&r(!��4&eh&i��i�O.����P&e\!�	\&m04���}c�&f�&n�&s�5�H�� �9���&e�9Z�&l�9�&a 9]�&t`;��:]�&t�U_S���&s't�V*H�elV(�&l|V��'a�[�Z��'t�\� <\��0'b`'k@=��t'�8'��� ]��X'o�a� `��l'r�'v��M�'e�B	�'h�'i�'j(l(m (n4(o@(tl(v����'ol������� �����'k��'u���(e,�D�g����,(e�� ����T�v��T(aܺi����L(bd(n��
t�� <�]
�(aH)eT*il*lt*n|*o�*r�*s�+t,u��	h�.�(f�(k�(l�(m)n,)r8)s@)v���4�8�����a�(g��� ��"x�S	<���)a)e$)l(�h ������P�.`����\� �a��d�)e�)g�)i�)k�)m�)n`�o�)r*s0*t��XD�JH�J@�Jx���������)a�)d��h�)s0��|�� @�t�)v��	����)a*i*sx��*nD�;����tX�����(*a�t�f|��@*d�H*nd*sp	�h�P�m�*g�*n�@!���e���t\#���*l#��*a�*e+i$+kT+lx+t�+u�+�($2�#�*i+r�$��$���*s+v�y�d%0&M8+a�d &�0+f����`kgd+n<'8D+a(e��(	 )*�+p@(Kl+o�+r�+����
D��T)�+i��������+��)MD*wX*���+��*	�+ad��4����+a�*�+kd3M�?8,gt,l-n@-r�-s�-t�G.H��,,aX,lI�Pl.d,g�H�H,e�R��PtPP��l,e�,l�R!�,a�,e�,f�R��R���,rS�X�.�,n�,r�,s,S(�,d��0.L�t�,e@S(LS!�ä\�-s�Y��-n$]�4-lPO��7gdO(-ala��lHaX-ep-ox-u�b�.h-td��e
h�Pj��i"�-li���-e�-k�-o�-tdktpl	�m;�?<p���-h�-u�v!@��a@�.l.rh.s �i,���.a�X0.aH.eT.k`.tĻ.@.b��@���.@�A|�e��O���t.a�.<�>(���.d�.f/k/l /r�T.����.a�.e�K(X��.k�.n�.sDQ��V%�..�V���MP�d,�T.����/e��-4/aH/e�/t���@/r���X��.\/sx/th�Bh/v��!�
!��tp/e�Nt��/nH��/it�(�/r�/v����n�t�����/e��i�/u������M�/a ��0b�0d81eX1fh1hp1k�1l42m�2nX3p�3r5s6t�6u�6v,7w�E����/a47bX7c`7d�8e(GfHGgLJh�Ji�OjQk`Ql Vm�Vn�Yo]p]rdbs0mt�nu�pvhqwpqyGÜ�������0a�0o�0�P��d1� ���0�Йi����0a1e1g 1m(1rJ(|�1d��!!�'!x�-!l�;�m01k�l��D1o����L1l��t��?���s�1t�1v����.��1a�1e�������w.0����1a�1e2l 2o����"�1i�1n����3!L��1s���1e2iظ�8!�T.@���(2aL2b\2e|2mT�
@����T2r���
T�h2l4��p2e�8�x�t�2.�2e8����2d�2e�2g3k4Bl3n(3sL3t��>!���2n���L����P�2n�t�2e3jh(;l�F!������t 3a�BvX[���83e��@3rx���(Ja�4et3l|3s�3u\�9��~d�L!����h�.�3a�3b4d(4e`4ip4k�4n�4t�4u�4y����3f�3kP�H���3r�3y��Q!�X!�(8�4kH��4e��=�� 4.dCa<4s��iT4k�p.��dH4e����v��h4i|�2�k|4e��$
a�4e�4i�4s���.L�R���<'8�4a����4l�������4n�Z(���4l���8���5c,5i@5kh5p�5s�5t������".����45aT5e`5i��".�p��$�Pl�5n�5r�5t��p5e�5l��\��}������5t������5e�5o�5r8�i���������5a6iD����]$6eT6i	o\6rx6t(�T.@6nL6s@��0��86s���8��,�a�+��d6px�tl6a����.�����6a@����6lX���h�.�6d�6e7g7k7s7t$7vH�����h�"�6i�6rЈ�����6i��_!(�e! j!�'!
lJ�p	aL7y�:�.D7k 4h>��7a�7e�8o�8y�8�Ļ��@��|7t>�7l�7u E0Ip!�H�7a�7b8d8l 8m08n<8oL8rp8sx8t�8vPI��7a�Iu!|J�J���7i�pPM��8o(PJ�QG
DQ��(8d<S{!8T�S]D8a`8eh8o�T5�U3�V-�XJd�.�����aZ���8s�Y-�8i�kp\h�8p�8r(l<�x���8d�
ȃ��8y�����8��\9a�9b0:d;e,;fP;gh;h�;i�Kj0<kh<l>m�>n�@ohp�@rtCs�Et0u`Fv(Zy�F�܅��9k�9r���{
l9a�9e؆�t9t��.���H�]�9n,��9e�9k|�� ���9a�9e�9r$:yX��!��5�(	d'��9i:��)��)��:�0	D�:ř\:ap:e�:i�:o�:r�Jy;��A��T:n�x��h:l��!�|:a��!�j���:.�:a�:e�:m�:s���:m�:sX��!�j�!X��!(��:ek�!mc�t�̎��;���� ;n��xؿd��-�JtD;�x��!����<;�Ԗ=	��g`;n���
ܚ-�;ð��.����t;e�����;v�����;��J�;e�;n�;r�;s<t��!�W����;e��C�J�;p�;t��r$��<a <o(<rp�d��|�|d��
H<lP<nX<s`<t`����!Ĩ��{
ȫ���.�<a�<e8=it=o�=s�=t�=u�=v�Oy�=à�(��"�<r�Mu��.�<a�Md�<i�<k=m=n$=t@l�0��<e�pA�rH�T��!����.=sܮ�!h��
0=t(}��h
TNdX=gd=tl=v(��x�nP=g�$�x���=v���<��!�=e�=i�=k(�2��i���!�����=l��ED���=n���8�
���=e��!����P��=�>�@������p�.H>aT>i\>ld>np>ox>r�>s�>u�>y,Q����.���`��!��"	�a�����!н�
�>t8��!��2��d�]ؾ.�>a?d ?e�?g�?i�?k�?l@n@s@@tT@u`@yp@�@���>mT�������>t�t?s�n�?a�QmH?rT?s\?tp?v\�`���@?ft��d"xh?i�"���)|?e������.���.��t�?a�?r0���/����?n�?vP3x$��o�!.�n���?e�8i�?gd8�?eD������t@l,@u4�@�$@nt���8@r0^(�"L@mX����"����h@��@�����i�@g�@l�@m�@n�@p��"���l�u�@t�����@e�k��i,�
"�@e@��x�]ؾ.0AalAdxAe�AiTBltBm�Bn�Bo�BpCs Cu0CvTC�4�"�'.�
lPAn\As��X����HAt���`�i ��dAi��ad�AfUg�Ak�AnUp�AsTUt$�"��8`����Ae�
"�����Ae�����AdBk Bm(Bs0Bt8Bv8�cBaBe,�,<�
d�(��(��(��(�\��@Bg��HBe�""`+`B.�hBah�p!�Be���0����J�Ba�Bm�Bp�L����B.�Bm(�)"�1"���B.�Be�9"t���c����Bf���Ca�A"��JCdHVp�������sel�q
8CrdCv8���lC�DC��^!,��	��t�.�Va�Ce�Ci�Cj<kDl4DoDDphDt$EuDEv�XyXE����Cn�H"��n�C.�CswP"���`��\��CaDeԉ@,�KDu(D�h�X"��(���� D����X���<DeTDrx��`Dah������D.�Da�De�Di�DrE�]"���ܺ���Dl�����Db���W.�Dk���d�(�Dk��.h�d"Ee,����Dd���Di#.t�k"����E����4�]�.���,Ek��X8Ei������tE�PE�lE���`��ErL�i���Ee�����.�EaFeTYi\Yj(Fo0Fr8FstYu@FyTF�L�����Ek�ElFt���.`�*�EeTs"���	�E.Fe@��
��;��.t���{"`��"����{e������LF����Fa�Fe�Fi�Fn�Fo�F���"��dh�������Fr�Fs��t����Fa0�"	$�ed�5���
,����F� ?,��HZ��F�G�H�`���r�$r�G�8r�$<Ge���	4Gl@0
�Ga�Ge�Hi$Ij8IlLInTIo�Ir�Is4Ju�1�/xGl�Gs�Gv@5�5���Gk�6"�:�0:�Gi�8�Gd�Gi�GlܢmHn4HrxHs�Hv�;"�<h<���GaHe�<��>]X�ud�xxA Hi�@](HePHidH�x�(�AHHnDC�	TC��\H���
tCtpHb�Hl�Hm�Ho�Hp�Ht�HvD����4D�DD���"�D�HohD��HiDE�"`F�hK��J�HeIfIm�K�xL@�Oq0Is�OIe�P�`Q]DIu8U��V�Ymh�.ܤs�]*�Ia@]��dId�It]KpIa�Ie,��"H^�"�^���.�Id�Ik_*�_C�(	 h�Ir,hX�Iadb#�IpJtJy Jäj*�h���Io�l��l*�l��J��n7�n,JdDJr�o@�shJe�Jt�J�|�y��`Jn@������tJe�|Jn̆��Je�Jo��������J���h�.Ka�\bLKcTKdhKe�Kf�Kg�Kh�KiLkHLlxLm�LnXMo|Mp�Mr�MsOtXOulOvT�����k0KrDKvp�<KbH��"��H�ЕJ`KeX�2ܙ|Kk�Kn�Kr���l�R�Kd���T��,�]ta�Ko�Kr�Ku�����,�x�]�la�Kr�� ���-$�
�]�Ka Le(Li0Lk8Lr�u�R,����� ��"�iT���@Le���"@���TLsl�\Ls�Lt\���hLe�Li�L����"ȶ��Lr ��ȸ�"�����L�и���_dMgMi$Mn8Mo@MsHMtx`v��" �n�Li���Ln��t�Le��(,�Mn��T0Mu@��"`����"���"4�5<��PMmlMptMr�����x��J�Mst�i�]�Mi���]�Ma�Me�Mo��2�.t��Mb�4�����.NaNe(NkDNl(�ohNp�Nt�����2Nl Nn��c��l��4NaX������8<Nu\N��( ���TN�8��xNa�NrX��	���"�;�����Nu4�"�Nr�����Nei�NrOuPXy��iD���Na�Ne�N��
h����N����"\����Nr������T9a(Oe<OrLOtD�54Os���|�G(�i��tDOe<�-,��clT���`Oa�Oo�Oup��������Oa�Oe�Po�PuQ��q
�����Ok�Or�OzX��"����OnL�q
8��	\���OfPg Pl,Pn|Pr�Ps�Pv��8<��"H�JPs���`PolPstPtl��"���@Ps�"HPp��TPp������"�����
�����Pk�Pp���"��d�1�Pkt�M�Pa�q
���Pb�"�Pt`��X�����Pe��]�Pd���Q� ~,Ql/ ,$Qy�]�T.�Qd�Qg�Qn�Qr�QsRt$Ru�]��
8Qa,Re�SiLTj`TlhTo(Us8Uu�Uy�Uð^i�^���Qe,_�`Ba�Qe��.`a�
a���Qd�b�0�t�Qv�cAd�	ReRsRt4d#�d9 �dl�W.�d�f��k�
�RdT�f�Rg�Ri�Rk`fl�Rm�RnSp SrdSs�St�Sv�m�h8olmhRrm��tRe�n�� fe�Rg�o�ahpY4fe�qJ4�.��eLt#ds���Rt�t�h�.�ReSs0u�.�Rst�1�u#�v#<v�Sl�v�0SaPSe�v��0.����8SeDw'DSd�x#�x��\S.|Se�Sk�SmXy".z5��#�{��}-P��	�Se<��Sd�SeTgTm(To8TtDTvЖ(l��Sl�����]�SnTr����%#\�� Te��b��Č����0Tt�[\��XTa��
���L���T.�Ta�Tb�Tm�TrUs Uv8�����l�*#`����Te����Til�1#�Tn��Ta�Te�tX4�|�P�Te���Tn ��UtD����dUe�
���"�������0UgPUkXUn�tض�ķYdUg��pUe��.T�����|Ud�Uf�Us��6#$�PT.п]�Ue��<#����Ug�Up�UsVy�~���U���������Ua��������V.VmD����TVe�Vi�Vo�VuhVä�;d���<Ve�Lr��DVt��������V�`V�,������xVih��Vn��gel�2�Vl<�. ����Ve,�P�lLMWaPWdtWeXi�Xk�Xo�Xr�XsLYthYu�Y���k0WlHWvд�,"Wm���$We@Wo���J�hW�D�`?r���\W�\P�We�Wl�Wr�Ws�Wt��v���l���Wo�3����Wo�W�hVNtV���W��8�Wel�k8�t�.d"�Xr�"B#�-B�,Xb4Xd<XkPXndXs�-(�.
�/�\Xs�/��DXg�/,1xtXk|Xtp1��1t�Xr2H#D4EDDJ<B�Xm�Xp�Xv�D0G��Gi�H�XkYmYo$Yp8YtDYv�x��M���XrPܦ(�P��YmpQ*
,QXYr�RW�Q��0Yi�Sm<U	d^il^��TYe$]\Yr��t(b��tYeb|YrD$���Y��Y��dJ�e�YaZb8ZdtZf|Zg�Zi�Zk�Zl,[m�[n�[p\r�\s�\t�\v�fM#�e]�YnZr�f��.�gJ Zeh� j�0�lPZr�i(ZaXZd�eehZsCidjG �k��k�`ZtPnJXp]�a�Ze�Zr�p�4.�r��t<vJ�Ws�|��z���Ze�Zf�Zo[v [�,tt~�Za�p��i[r��Za0���(X~��[��J@[at[e�[mX��,�S#0���H[s��wP[i��\[rl�"h[t(�]#x�?
`����[d ��[nĈ]�[a�[e�[i�[o�[s@�Pl.��
�C���[kL����"ܔc#�'.4\e<\s`����[aD\dX\el\gt\i�\v�\���i#���0��P\a$�t\�'d\a�����n#<�W��el���\a`��ܡ�ȡ���\����8�]�\p(<tX�p�]�\o�\r�O��(�\e�\i@����.��".̵8�K.@]a�^eP`i�`o$au�ay�a��������
8]bp]c�]d�]f�]m�]n^sH^tx^u�^v�	|]e��i��t#4����]s������]f�]tx�{#h�|\��#8�t�]s�����]d�]t��i���]o�]r����4�^.0^t����^e8^k@^t���l�t�����X^el^i0���O.��x8�Wd^s�x�z�4��^kX�J�^a�^i�^t���#���^s�$
2�^a(�������^a_d,_e8_fT_gx_i�_k�_l�_n�_p�_s<`t(��#����_s�����$_n���#D_eX�h$4��L_a4�{�_e$�`_n���l_e�_s���#����T�����_t��#��_i�_s���
- |
���_e`s,`t��d	k��#�_a `eT�����t`n��|��
��4`t�3	���H`.t`b|`dx�f�`g�`s�`t��#��T�#��',����`e��`t����`e(!�04���(a�`i�`m�`nasav|6l
88���$m 9�L�i�:]�`k�<��seT3	S��aiDalLanXaphas�atXT8�T���ggP_(	�h��U�`aaxat`V�|Vq
8ZdZ���ad�ag�at�3	�[�#�[���am�\�4.�\���ae<\���adbrbvby@=��<b��a� ^��^�be$_i\_(b.Td�#�`�#�a`��0bdTbk\bn*t�`ha��B#�ba�ub�cc�ce ef,ei�ej�ekgl�gm�gn�go,hp�hs�ht@lutlv�ly�l�4bX�bkclHcmpcn�cr�cs���#�����bse��brxd���be��H�e��c. ce0cgdf- 4�
xf�#(c.@ce�f� g�	Xcb`cnT��
H��
�h�#�g��hckj�i��|cd�jJ�ct�j�0m��B���ceHpytn�cfP�g�ci�ck0dlt�mxdn�dr�ds�dtevqJrJdrdvhr�#Ȫ�#��Ts�d.s]$da@dspt6 vaPdt<��h��xvXd.dvt`dev��ldd�dk�vs��t�.w]�dexJ�.�di�xh
|{�z���dt�{x�de�di<�s�dtL|h
�|�#}"�}i�_���e��~e��]Ted`eshev�@܂@er����Hee,�i�����(`���per�xea�ee�ei�eo�e���#ԉ��efԊ��i@������e����x�. fd(fl<fmDfnTfpxfvT����ea�fi�fj�fo�fr�fugv��0�(4fl$gD�
`��ԍ����Lfahfepfi��#��
���$�����ft@���fl��$�f�(�$4����f�d�����t>�
Ԕ���fn����fe�S�(����fgp������`kg��ga,geXgu�gy,�D�gDgr�w8�d<gi̜�h��Pgklgntgt�(4�(X�.T�(	<��gn8��P��gd�gr��M�ge�$�����gt��H��gl��M�ge��������gghp��ب�he����hl@�X haHheThi�ho�huH�zl���dhklhl$��ĭ�xhe����$����hs����hr�����hr��|�heD�ZP��heT�������hk$inDisPit����ha�ielji�jokr�kuly(l��!$���id4igD��ؼ($ȼ�<ij�H|isx��L���\i���9diÀ�/$pirH��0�.�ie�il�im�irXjv��4$��
�����il���.0�J�ia�itt�:$h��4����iejfjk jn(js0jv��A$�$��H$D�O$T�V$<A�8jnL�g@je$�-Ljn�(	��dj.�je�f{g�j�lN]$`��h����j������.�jld�(�j.�ja�je�jlc$X������
X�������jp ku���ja0ke�ki�ko�k��
��j$����(kaTkd`kipkk|kt�����Lke��3	�p$����hke�
h�x$EeD����kd�$�����kf|�h����k��k����$���X����ke�kt��f@���at���kk���kr�H���lr����8l�l�`�(	��XLlg<�6�|��|��Tle����\lnt�Xhla�li�l���������lk���l����l�������ld�lm�ln�ltT�mh�]�|a4��4�Jmk mtl~��(m��l��4t����mip��`�<�	\ma|me�mi�mr0ns<ntPnupnv|ny��2lmk}l4���l�aPwe\�]�mk�mm0�.@����mex�J�za�2�md�mf(#.�*�meH�$���.��men�8���"?�ms�"��nd�!��n��)�#�(nv�*O4��|�nd3��Dne���7��\nk47dne�8���{e�nt�:�$�?�nb�ndoe(ogHoi`ol�om�on�or@psLpt\B�$dB���n��@���n�$D]�B]�nios0z��D�otlE�4E��olH4ol�H�$�JxToa�J��<od��"PZrPP��4,e|ok�ol�oo�oÔRX�oe��P0.�R��R�oa,T{`VI�Q���o��V��[,�Y���ogDdla���ogpipn puPd��e]�a�e�R�h��pa0peh*�m�i��8pt<p��	T�axpb�pd�pe�pg�pl�pn�ps�pt\q�$�q�$�q�.�r�$�s�$Xt��u�$v((v�pk4vt�pa�pe�pr�-Spv�ps�v�$@�qa<qe\qiz��ԇkxl�f�|�6qiH��$ql����0qdl��x���Hqa����Pqt �@��qa�qd�qm�qn�qr�qs�qtrv�J,�T.<����qaL��ķ2h����qe����](Ua�qe8�P.0����0e�����.X�Mre���<���rtP�3	(���0rkXrrdrvtry������Prst�i��\����lra�re�rn��Z���rrd��t��	�ra�resksmsn(sp8srDsspsv8�t\�M�rs���.��%�rnsr��2T. �M����8�d�����$����0se��Pst��� ����Xsn|st�M`se��S ��$ta4tb@te\tfhtg�ti�tkul�um`vnHwppwrxsdxt�xu�xv���sad	b�yc�ye�f�hD�ih�j�l�m,�nd�o(pp�r��s̆t�ủv��y���x��_����,ta�Ltlx�]̟�����TtiС�xta�te���T.�n���tn0���tsH��$����tmX�i����ta�te��"T.�tn�trusp�x'�����taĪ���
.0���uaDudPue|ul�us�uu�uv�j�$�t<us�"T.`uv���L�hui��pue�uô������u�(��غ��uk�uo�ut�������i�^�i@����uave,vn�4oDvs��.��Y����vt`�vsH� ve4�*
Pvr��8vt@��$�i8���Xva�vd�ve�vi�vn0ws\	x�t�vr�vs�vÜx�$����vr���vkD�����v���T.�!�vs8����$we wo��wlwn�W��\��$|�2�t(we@wk(��x���$�e���.�wi�wl�wm|Cs����Twe�wk�wo�ws�wt`����(��*���P�������wv��
0���weP�����we�wi���wr��(d��$xe8���xj,xl<xpHxv���������$p�X4xlD�f��*��Pxr����XxaxxeL�l(�T.@���xk�xs�xt4��4.D����xe�t�����xt������xr4�PT.ynX����xaye<yiDyoTys`yupy���$h�"�.,yg4yiP�s܂(��(����T�� �Lyl�$0<���y�hy�L�$ 4�����.�yazb0zdtzf�zg�zi0{k�{l�|m�|nL}r�~spt�v܅�ynzt�����ysh�Ԉ���yr!O �mzn��x.Hzř�� zeTzrVD�]@zs�G���\za�ze��Mdzt*m ��zs��2T.Ԗ���ze�zr����za�ze�zm�zn{s${tԛ�$�Atpn0����:
����zs����J{k�$���{sd��L{s��kl{rĨ��<{ex�x�x"X{n�C��`{i��+�.ȫ��x{a�{e�{l�{o�{sh|t||v��T.�{i0��$�gĵu̱�{s���{e��<�%|a|e(|k0|p@|tL|v�2(�� |sx�%��2�^	0��<���8|e���S̵T|s��\|e8���t|et�(��|k�����|a�|n�|sļi���|e�
%нt�|k�%��2r�%���|��$�|�d����|f}o}s��%���$}e��i��%����,}a4�"4}dx���@}a�}e�}i�}j�}l�}n�o~p(~rt~s�~u�~ü�����}t��T.���8�%����}e���}g���}e�}i$��n�}aL*��}gh�!%t�'%�?��Z~i<~sH~t|��~e @��4~i�@x�Kh�*P~.�~rX�X~a���h~k�~v�,%����~eh�3%<���J�~nl�8����~������e�~pDt�R�~a�~e8�
���X��~lnr��|����i���� i��(rTs����4edü�9%��Y����\���X�e�i�s�t��"T.����|`�_�a�p�'0�����.�r��M�aĊe�n�~�
t����m$�]X�.0��a$]�s](�u�i�� T���0�o����
8�a��b��e��f�g�k8�mX�n��p�ŕs�t8�v�Xp	P��k�����al�Pܙ]��n��t��],��Ѐf؀rt����?%P�.x�]�e��	�]��e�����]�oT��ld�E �e\���,�m���$��D�nи��L�a��n��s�(�l�s���t�e��W
��k���\����e��*
������lāp�������e�i��k��X�.d�n���l����	�����o�r,�t|����i��t$�iT����=a�eL�s��������T�r����\�a��e��o؂u\�:��m�B%��J��eD�,����e`�����t����r�	�e���̂lX���]]�aa��]����n�� �a(����nL]@�aP�e\�s�]<�\H�n�H�ex��aăbԃd$�f`�g��i��l0�ml�nx�o��p��r�s$�u,�vh�w�f���'.�e]��r�g� jt�ĩa�e�y�jPT.�.n��r�j��.li<o0�a8�eL�iPn���f0o|do|D�r���o�hs
x�tXp]T�stG%��a�s��l�rtL%�t�8{�ȄgЄn�z����a؄e�i��l�o�s�ut�|{��|"T.(�~"�n��`�(�"�m܁�"���(�t��
����Te@�o��"T.T�d\�s�������IĈ]d�od�?��".������ale��i0�.ܔi.`�����aЅe��o؅t\�l�R%8�?�a�e�i��{4�����n�rx�����X��MP�e\�������<�e@���D�r$�����8�>8�]��a�e��i��������e�����n��s��BĆetn�<���e�r<nt����e\��r�K�K�BL�aT�s�?���d��e��gćjЇk��lD�mp�n��r��s��vC��D����L���`�e�|�h�r�|��t��4E��Xr��t�F.H����ateL��.�KM��a4L��Ha�r�MZ�M��a�P�PP���a�e�PT.$�g,�s4�v�n(�Q��}%�Vp�V��<�a�?xZP�sTZtX�e�Y��d�d��n�\�$hKla����u�h%.܈ri��
��a��e�i�j$�k,�m4�s<�t��u���xiX%�e�r�j�
�i��i"T.�d<j��j,k�dkTl�m��mML�rx�y�n]%d�ap�e�nJ�n��\�n�nK�PopJ�o�����z�$xM��a��eXx���.ĉs�xb%@���a�eD�it�o�{px{���ez���l�sT�\��(�a�����r���ĐIx���0�i����8�lX�rh�th�S��x���`�sH�X��r�����i<�g%@�Z��åb؊d�e0�gH�l��r��s�`�`�MĊl<��q��nܭ��'.�s����et���G�X��l��e���$�g ��.,���<�ap�d|�e��q��\�sx�td�eشT.��%T���sl����e�����d��zP�a̋e؋j�t8�PT.8������<����g4�r`��0����T���k%�������
������(�fD�s������(���
L�e��f��gČǩl�n�r\�s��v��y��������rP`2��e��i����a��a�����P�������1ex������،a�e��T.��@/r������a$�eP�s�c�X��n8�r@�sP��h�B(g|��H�p,��p�t8��D���h�a�����p%�������d�t�����a��e؄i�����������a܍k�n�r�s�Od��H�����r�����a���$�e����-e8�P�tt�tP�a`�ll�n��p�r$�v\��������X�e�����ia��d��e�x�����r�����k��t��s �����l����(JaԎe�lt��T.��9������e�i�s�/uX����x.�M�a4�e����.Е���@�l ���H�a�b �dD�g`�it�k��l��n`�pp�r�sd�t��u��v��T�a��bH�cЕdܙe,�fx�g �h��iH�j�kT�l\�mиn<�o��p��r��s��t<�uT�v<�w`�y|�z����v%�����l�0o|������e4�j<�r�{%x��С�P�e��]ȧ0���X�n8�����l�r��s��u���(�e��i0�����.Ԑa�e�f �g0�i<�kL�od�s|�t��ul���M����Dn�"ܐi��tԵx�i�t�|�%(}?
(�'<��ȶ(��(�n���p�����D�p\�v��غ��.t�t��xĻؾ.��M8���
��.��a�d�e�k�m$�o,�s<�tL���"Бl�r��(Ⱦd\|x�tܑr��dj.�s�t$�-t��l���9�%(����Ba�l��y��
����D����]x���X�aCp������.��d��eؒn�s���H����o��s��u�������"ȒaВl�����%�k�.�s�2#��=�����a0����k��8����i4�m<�pD�sX�t��{%p�����P�i��R�����.��M|�e��rt�t��y(�i,���9t������al���rD��@�����k��
p	a��b(�eT�it�l��o��r̔sؔt�y��D
�o�u���Rp��e4�ox
�r��h���'.H]<�r\PH�e<����`�r`Xh�a��et�$�g��s�d!���e��n�",�$Ad'�%�*�%$*Ĕt�*[/���r�.�a4/��X�.���%�	�r���a��r8���$�a�3P0�n 4��<�a|�e�5i��kșo��y�6il6��h�e�5��p�nh9�%��e�%�9]��e�9��t�=i>"0�l�s�vh>����a<�dX�e�g@�iX�j��n��o��r�st�t��u��y����C*XE"0G*H�u�F �lP�p|F,�e�O��SU�H4�.��at�b��f��gĖkЖl�nD�od�r�sHI20I���l�J?�JU�JJ��e<Lh�K����iPM���.�v�O�DQdܚd�s(�tT�^	HR;��v�R�%�R2�f4�t�R�i�R�%�S�<S<�v�x8T"P�p�S]X�a��e��lܗn�s���%�T��.��n��s`��U1����tg����aЗe����ȗg�U�V����VM�m>�Z�m$�s�Z��etC"(]�]],�n�[4�eP�o�`�%lc�l�e�c��c�d�r<h],h�x�a0j�\h��l�n�n����n�m��aИe�i(p��o��Șg�v\q�%|qdtd�a �e<�iH�l`�t0u�Pt���m�uK,�lv
d��w4�m�xT�a�x��`kg0z�l�u�{��|�%�"t~|�n��]��l��r,����*ȃ*̙y����ԙ�����[<�
�]�a$�d4�fH�g`�i��kĚlH�ml�nT�r<�s��t�u �v܅m̌]|����M,�f���Ԗ��@�eX�o̙��Mx�d��e��g��nl�Z��%��n��������i`��%d�����lX<s��ȫ����a�e�i�s �t4�Ø�"�m��vT����%\���"�m<��%���%@�q
�Up����(��������@�a\�bd�e�����d�
��a��d�i�n�o��s$�t0�u8�y؛�@�+�;ěe�M)����a����l$-����Л���pD�>��p����k�v�M�T�t�|���r(�pX����(4�"@�nx���H�a��d��e̜i�l�o��r�s �t0�u0Cv �S���.��g��nĜtP��t�?
`�����g���%��"|Ul(Bs8Bv�������.���|���a���$Vp�vh��%��O<�*��J(�n��,G.\�cd�el�s|�t��v�Bx����;�������t�i���%����aԝe��r�ä�"��b̝t���%������l��������3	����ܝ��&�w�e����&�����d��M�e$]	`�et�f��i��j��l�o��r�tl���	X�s�]�]l�a����e�������������$2̞s����eh(� ����ԞmܞrX$#X�et(�$�aD�e\�s�((�(�l���)��0�eH)8�rT�s*;�*q�.*���d��@0]
��a@�dP�e(�g`�hp�i̡jءl@�nd�o��r�s�u81��/��e�n�r �t8�v3i�2��؟np4�
�3���k�t�u�4��42$6
6]�e0�tx6��6J`7m�9;�8H�b|�dܢm��nԠr�s��t�:0:t�i��o�:&�:����m��s�:��m�:&�>nĠo��;C�@]̠sdH�tCt�Hp�E�	�E���a�t0���G'�G�d<�sHG�eL�rX�sxH�pI(�IKD�a�I� LJK"�Jh�a��b��m��o�\xL(\��XM���n�P&�PM��a�O���v�O��e`QM�d�i �o�gS�.�����e�S�s�T�hT���aX#�tWP,�u�VM4�eP�o�XitZ"&x�r�YX�f��m��n�o^	,["�[��[]��o@]]]K��a��eȢ��^]�aq
�a�������0b'&<b��آd�t*�c��bX��ldb#�a<�eH�jX�kx�m��p��t�v0d��c4�l�e��e��e��P�ah�r�f.&����gMp�u h�	,hX��a�hd�a��e�r�i���kȣlУm����i��i��j(kأahl�tlX�a�o�n�n�tLp�s�<�e`�ot�u�|�y��4�n؄"�n.����H�ed�xT�lЇ(���l�k��l�������a��e�u.��M��nĤsؤtиi������.p������Фa ]$�a�e,�ip�j��k<�lT�o �r��sd�t��uثv�y����L�bl�ft�k��n��o��p��r�s�J`�e(��
�X�l�J�Jx�<��|�d��-���T����o�������.ȥaХe`��iܥi`��������a��p�	3&��.,�d4�iP�ll�n��rܦs�t8��
��1�<�g`��D�e �id�u�ip���.��a��e��sT��9&���ؾ.�a��e̦iԦo��?&���.����H=	�i��k�o�u�2 Y���li����.$�s��F&�D�nd�s\�
���<�eT�n�x i ��\�t !��#(�#x�fاn�p�r�s�tl#]
��a0�e��j��lĨo�r�s�u,�v4�y0 $��ЧaT(h��|$���v�$(�$(�t�$/�;�%���t�$$�nL�r`�s@&H�%��D�u X&��X�kp�t�&�$#���p#��x��d'X���<#�'���a�'��'����g�(]بrp8�08��Шd�)*
�e�)2�(�d(���vl+L& �n�T��+��d�+m�+� ,8H�È/��̦�X2��a��b��d��f��gȩhЩk�n�o�r�v@2���r�2R&�2X&�2���e3�Pn�3��t]&�4JhOu.�6tةat7�p8b&08����d�e�8h&�;JT<�@�aT�et�o��y�<(�<��8�k��x=��L�m�4s�?`�e�?]h�b��s�@n$A]B]4B����a��e�m�o(�p4�tC�k���Ȫa�F�ЪlG��ܪ��FM�ÔG�<G�t��C�G�l�GX�e�H����iH�oP�u��i�I-TK����v KX�a|�e��r��s�L��g@P�@Q8�a�P���l�RīlЫtT.�S����e�Vn&W��i�X��4�n�Y�[*���������\*�p8]���Ua�]	��d��f��kȬlجn��r�s(�t0�v�]��(�aL�d�e@�fT�gl�i(�j��kܰl`�m��o`�r��st�tȴu�v@�y�Ìc�^����r_+�_��`�a�a��Ьa�d`a:�c
�b���k�t�dEd���s �t�d��d�\fp4h(h8�k�gt@�a|�e��j�rԭs�Ls&�K��h�o�hp�k��t�X;�X����elc
��*��e��r�~��a�j�ȭf�t�kG,��8�e@l��r�k��a@�eT�fT�gl�i��k`fl��n��r��s�tH�� ny&oRT.�n��H�ed�n���hp"x�g�p��qJ���t����oxH�v����o�xMĮe�SkLtXy�|h�|~&̮i�{��Ԯr��t�u}i(}t�etY�&�~��~��H�����.��2r���(��4�Ì�i�]L�ed�ld�<��d��e�}g��k��mįn�r�sĄ�������s��]��r��\�"����dدe�st��&���|�?���&|�i�k8�����i�����t����h�.\�]�a@�e`�oh�u���T�s�' �%L�e������D��r��s�p�e��o��u��`�(��&������sd�i���d;b��m�n$�p,�u����a<�e��i̱oرs��X�Ta�`at�ad����d��ؒ�ܭ�4�e`�i��n��s$v�p���X�eP��&��l�rL�tt�ed�����dzY�t��k`��	�}a0���g���& ���ım8����kؘ�&�����������0�r�������@��.���$�e,�3	8���<�tԛD�s|�v$�P�et�i|�sd���O��yL���
��eIJlвm�n�p�r�s$�t8�vD�wD��&(�����o`�(ܲvh������nP�J�p���l�� �]�o����]�up����0�e8��&�
����L�t$�ET�e�|�&�|"l�eDl��t�j����t�����e��l�m�p�t�u$�v@�a̳a�el��8��&D���Գe(ܳg��p�uh�����rЮk�
�oH�a�}4���0�a<ue����8�r�w`�*T�a���\�lȯh�a��r��������t8�����t�:��e>������������D�hPUk�l�n��r�s�tH�� ķ2�����������(�e\��&h��� �d8�r̻x����T�r�������.��a\�bl�e �i0�jL�ld�mt�o��p@�rT�s��t��u��Ü�W��dܵe�g��k�l�m$�n,�p4�r@�sP�t@���mԵn��J���&(����k��o��&<����n�J(��|�J�����.<��(�CX�tH�r,�=x��&��d�e��g��k��l��nȶt�y��-��JP���]��u��P������hd��h�.ܶa�iP����L����-������ȸ��L����ah��l�Vn��
��%$�8�v���@�e\�u��94�E�
�l�g��l��r��vd�\�����a`�����d$������=�e��l�o�s�uT��&����طil��r���D�����e��@����%����$�f�$k�s���,�e��H�tL�et�k��m��t���	���l�l��r��eP���*��e,�P��ih�
�h�al�W<�NL���.$�a��c��d��eT�f��g,�i$�j0�k��l��n`�o��s��t��u��v��y8���X�gt�k��l��m��nԹp�r�s8�tD�u`�v�(d�g��E\�&<Jl�kt(,"��t�����eTX��e�a�]Ĺe̹n�&d��	J�p���	��p�.��s�
�&d� ���kh5p�s���
x���	�$�k���,�adP�k\
�S���X�lp�r
!XX,Mx�e ����r�t��e̺i�o�r,�sp�u|���]�ĺg�i��غgH\�a�u��s*�si�s�����i�$�k@�pH�t���n�&��P�r�MX�t�d�s���ԙ�\W
̻aػd��g�i�l,�n@�pp�r��s��vT� <��e����r�J�y�����J�dDe�l���e�i���<*$�g4����8�aP�e�i.� 4.��n��s���\�e��i�n��t,�
8�%��&���k�������.мk��s�t$�È ��a�ex � Q.�!q|!�t����!���iL"�8"����$�
D$��0�����	��k�$H�et�o��u��y�%�&$%l�s��th"�,��-��1��$Ad<&��l�v�%t��a��eh�j|�o��r̾s�u���&(���9��j\'�b�m �n4�rL�v�?��'n�i�A��'n,�iD�u�'H(-P(N	\(�T�nh(\�e�Y��(t�a��sT)�|)���e��i�_��)����p�`�,*����s�*d�o�t��vܦi�g���m�+�tlf,x,p�,����D-�`�r�, �al�bt�e|�f��g��l��m��n��p��s��tp���T�.�-=	L.��.��.�(/(X/�0��/����n�0J���,1����aпe12ܿs��@3$���aD2��i�o�2�|��
�3��k�3�eD4�L�ep�l��o<5��4D�l\�rt5��8�o06�<6h�a7(�62|�kd8;�,ax�4�.�a�eX�fd�g��h��i��o�s8�t@�uH�Ð:(��l�;��m�r�s$�t�<��\��L ��<���i|�t�<8�t4#'#t0�et\�$D�s�$L�a='u3�,�l�lL=_t�a��od�
'd=T.0>'>��m�v���|>M��a�>W
�.�a��e�t,�v�x�>	?i�nX?��@�4@��eT�%AX$�ePA'�A�B���U�X��B�<B"t�a��d��k�B���r�B�HC��C"&��s�C��H�8.��a�e(�i<�j\�k0�lp�m��o��p��t��u��v��yIT�.��l�r�I�J�|J�'.t�k �n��rTK*�L24�s,MLPML�aT�e@M(lM"�M���a��e��i��j��o��v�Ü���M���s��t���Nx�M"��t(N�	LNN���`#�L�]��t�N�����ī�N����r�x'l�P�����D��D�a�O�gdO$�eL�o\�Ð�!(�*����O��T��$P�PMh�aب$'�P��|�p,Q
��a��l��o QW8��"��px�3�Q����l�Q����a��e �i4�oD�r|��@���R��.�n�R����.�s�)'�R1',�nS�S8'���0S<�a`�el�u�����X�l(S?'`�Y����t������S��l�S��TG'<U|�.��a��e0�iP�r�Uo��l��V��.��g�r�s(�tWM'�eWT'XX� �Z'�X��sY`'\Y5D�mZi(Z��<�e0[:h�a|�e[�$[��`�nl[(	X[��t�r��s|[�$]���i��l��t�]Z^e'�^J��.��e_�T_y��ě�
�_����v�x$`��i�t�axhb
$�k,�t4�vTc��cH d*�e�|�a��b��d��f��g��i��k��l4�m\�n��p��rl�s��t�e��g]�T��j]��e�jP��r�i��e<o�Pn����fxr Xp]��n�tMDy�<v����s�z�Ze �j�c�����n��e$�9	���,�u`����s �@�nx�rĈ]L�a��e��g��i��ux���@�r`j' ���t��*��s@���r�����n���x�\������l��p��ܔ���'.`�����a�d<e(�iL�l0����a���<� �e8�nК�����tg��U@�a�q'L���X�v8�]`�k|�t��h
.Ш����e����lp�]��a��e��rP���(�K��a�������p,�t̵��
��a@�ep�i��l��p��r�s��t��y����WT.��M �iL�v'��8�e\�lh�px�{'D�tT�a$��'����e��n�����
l�����e��g���'���
��s����e�"ԾL�t��i�s���l�]��e$�ll�r|�s��uD�nh� ����n����a`�à-h�i4�.p���<�e|�H�r����T�������Cl�t�t��y���\��h�]��a����n��W������������]��n@����a��ul���tT�_�9.D�����ep�M�]�aH�eh�ft�i|�k��l��t��{��@�l����T���~\���]p���"D�g����e��h�����r<���aH��'��*t������8�]
�a��d��e �g(�i��k,�mt�o��r��s�tD�u�����4�nH�r\�sp�t8�+
����,�d�������@�k���
����T�tt�����h�rT�i��|�e��s�����i��k��p��r�s�t���t�e�tp�R���o	*��aH	��
|
����t�
*�o8!�A�@�aH�kT�n|�t�x�]�qa���p�d4�np�s����G h�t(!\�#���x.��t�#��a��e��l��v��($(�$���.4$��r��s%��t��&4�e�(^	8)iH)�����T+(`+�g<�lL�s+�a`�è+(��<���D�t����.��X�� 4�04��l�aX�e��i��l��p��s|6(h7�\:�'�9����p�:0?x�>��e�E��D����o�A���k��p�t�G�|H�|K��a8�i�K2Н	xN]$�r$N,�gSX�k`�mt�sT@�T5�h(��.�U�h�a<\�@=��������`���l a�'4b$�k8�nt�s��u�B����a��b��c��ep�h��iD�jl�k��l@�m��n��o8�p��s��t��u��v�w�yL��le�xd���sl��'�g��0�bT�g\�nd�sl�t�h��h�	i�
hi:
�j��k�'�k��|�d�)�d'���y�l�
��rTm0m���n�B����e��h��o��r�B��m2d<�'tnH�d4�e\�gd�i��k��lt�m��n�r�s,�t<�u(p�'�o�,�lH�nP�r0p�8p� �nlpqJx�e��g��spq-�q�'�q�rJ��t�r�'��e�r�s;s]��d��i�s��vpv����e��o��sH�ew;��ix���.�s��zJ$�sp{E�{����et�j0}-4~�
l~��X��D��8������7n��v��M`�a��i����x	X�� �����n������e�����vā�������P�_e8�]��n���e�l�n$�s��'�]��d��������d,�]L�tԋC����0�n�8�u�o����P�.��l��rT���X�a��e�j4�ld�o��r��u��v0�*(���t���'h�.��e���Ў'�'.��e��l��s
�'H�(��s$�'؏��"��X��e�o��'p,��7a��Y�g���'(�aH�eP�oВ�.2���'x�rd���X�g��ph�t4�'�7*�����iP?�
0�����n(���l��(��a���p�
��i̗(��8��u ��h���t4����'8����.����y��������̞,�k��M4�a`�el�o��u8�(P�X�d����eT����x�s�i��M��e��u��i����d������l��m�p(�t0�vL��'���ܦ����o��r���'���ب���a�e �r�it��'���ܪ"@��X�ax�i��r�(d�e��L�n�i��.$����p�sİ(x����uP�t�s����e��i��k4�oH�t\�v��ü�i��s��(��(�����j�o�r�y ��4�(���$P��h��x�����0����,�k���ظ�@�o���XT�el�o���������t�r���|�����ܺ�������b��l�n�p�r�v�����a4�e��i,�oD�r��u��ì�(x�����e4�t
�����e|����3	�JlEb,�i��H�"0�.T�kp�l|�m��n��r��(`�t�X�������h�l0�J�zax�t��.��e����
4�����.��n��ô����N�������|���������������k�l�s�f�����b8��D������$�k<�ld�*���\�e��y��(����T�.x�n��r��s��t���(��.�%(�f�4�i6*X�����s����f��l��n4����i��t���a��(������k ������nąr(�t4�-(��q
4��0�kH�y������`���P�kh�r�"����l�n4�u<�v<���p�aD�e$�i��j��m�n�o|�r�s��t�u��p��8�����a��e@Wo�yPxB	������s��h
��r��h��n
<����g(�s�B2(��t �v����(\�	��c�)gp�k��l��n`�o��r��s��t@����xe��ys�د9(������g����P�i�2�����e�o��s��uD�<�%�'��e@�".��?(�����s|������4��(���������
`�ex�f��k��m��o��p��r��sP�uX�v���.�mT�rp�sE(H��� ���m0]��a��vثK(�i�����e���J 	p!\	�p	����a�e�i�j(�k4�p<�t�	2 Nn
�(
��
L
�� �o�
K(�
�H�rk�-�d�s t��Q(�l�n�O��t�a�����r�����a��e��i0

����r@
+<�����e���l���a|!`����s���s���e�4�a�"m<�rH�st�v������d8?h�tЧ�ܧT�a���\�r���K��a��e��i��o����8.��e���*$����lh�n����aL����m��n��s�.
� �'P����s\##��a<�e`�kl�lx�n��t��u($��#4�i��rP�s�$h(��0&��X�u<'8�a��`�(�@(K��i��*�)����n�*�g��k��l�nd6p�t�*t��a(�e��i��j��o��r��s��y4�h+(T�a����+���n���,�P, �dL�lX�nd�st�t�rX(-��D�ox-*�k�-��^( .��l�t`. �d��l���/M��f��n�/�/��0��&�1����rh1t��k���D*(X*������2��sd:e(d3�e4�0;��:� �r>j(�?Md�al�bt�l|�m�n�p�r �sl?Z�@�PPI�V���'.��f��i��m��o��s�����r�W��a�W�,Xo(pXSY�X6��e��t��u(Y�@���Y�P_la]�ipi���ezW��d��j��k��l��n��r��s�t@���,�a�e�i\�lh�np�o��r��s��u��y���pz(��?4{8�z����sx{���a�|��Ьa��kl��~����b��s�~��
T����sx��(�����t��4�gT�i�kt�r��s��th�v܂JD�eL�n��tX�t(ă(`�g������s\���h�aD�d��e��y(���t�������k��t��v�������iD��
���������������$���(����G��� �n,�s��(����s�����'.<�b���(���8�e��D�gX��P�e���H�|�m4�+t����(x�����l����a��i��k��l��n��o��v�]$�]T�������0�f\��(�r�����k�����(�����n�����p�� ���4�aL�i��|X�e��(@���rt����'.��s����l�a��e��o8��`��l�(���k��r�s�y`�� �����P���������.��i��s$�{�;,�(�et�2���t�KD�rh�t<���q0�l����8�s�x.�P�e,��\�t�����t�k0�r ���|�a@�dL�e`�g��h��j��k`�l|�m��n�r��s�t,�u@�vL�z����a�bT�d\�e�f��g�h�i��k��l��m�n�o(p��r��sXt�u��v��y��4�Йh���8�a��X�n��
���.x�a��e��r��WT.��rP����T.�O�sq��M��a��v ����x<%(�e����l|�]��e�o �s�����k(�r<�tP�u\%h��d(�8��(4�e\�-�H�e����
�20���X�oxuT�A��i@���p�b��h��m��sH��(����t;eT�����v�����������4���������t��r8�����g�l\�E��.4�p�����aD�eX�nl�t�����t����<�k�2��kP�s��i��d�a��e��i��2�'.��t�����.L�
��x8�����������8����>e��k��t��(������i���������i�����CaL��@�
�e �"�r@��� �e��X���8�i�h>]����a��b��d �e8�f��gP�hX�il�j|�kH�l��m��n��ohp��r��sp�t\�ut�v���܅���k؆� ����bt�PI�����b�ǩ����e�r؍��C��(����p0�v�����-`�el�j��l��r��s��t��X�X�t(�������t�g0��|�aX$������e4b�����a��i�'��*G����r��iԖ����e�g�l8�o@�rH�yD�iP��e�Y,�e,���$�s̙���(pq�ܚ-�Md�s��(�%L��d���t�a��k��lto��t<��@&3�%����uܥ��r�����e`�C��i0.W���n���a�e�i �r0�s�K(�N�X��n��|�QT�P�(�tt���0M�ȫJ|�e��g��l@�t\�ut�v�o�(���h�r��p�g��i��l��sH�
�pA��r0�����e�rh`fJ��eT�(�
د]��e,_������g�n����a,�e8�o�a�d����gd��̱$�n ����!L�r ��(�D���T�t��n.��h�a��J��i��p��s��%����a��t�(4��(��l\��(��iнt��e��th��8����i@�Vd�����a0�dD�et�g��i��k��l��oSu��y����<�rt��(�h�.X�s�Qtt�R
P'k��`�d��th�e��s@�����$��d8g	X������h@��{!�*4�"��fPAnx���
��a(�d@�e|�i��k,�l��m�nD�oP�sd�tp�u���t�����p ���e8�s�����xadX�g Hih�nP�x�(`���`�n8����t�k��m��nd�b�(�����x������P�(����s����e��l��v��	�(�L.t���u |�2�sh���a0�s�2�2t(�l��
��J<�n���$Vp�����\�s��,�N8���x���~�
������f��h��k��p��t4�uH�vX�I���(�����l|�����i��(���(����a������e�i$�r4�Y���m��.	���(��,�t����X@�e����T�g��k��r����\�a��r��s�tH�vP�yd�tL�*��aH��P�����.��a��e�	d��(H���w��u`��������n0�t��a,�e8�o@�s��B����m�� �r�����\�	��"�{e�0�1��r��s��Md�a��e��i��n��)t�����kT����x������s$�]��.0���a��e�
H),�����@0�����T.4�b@�eH�kX�nl�r��s��������,�bܙ��
T���P�ft�
��]d�oD�t����x�j���������� ��a����p�]��L.�i�,i�e��b �c8�dl�f��k��l�m�nP�p`�rX�s��u�g��i� jT.H�t�i(�aX�iDjTakP�s�o)Pn��d�l�w�hw�x�rLw]��e��o<v����k�w)4�S�z����t؆)����rĈ��e�n�s�>L�6�a�e �p(�tH�uI�|JK,Q���8�i@�r���4�'T�K��(ܔi`���X�a��d<e�i,�tP�u�\���k��r0����e�i�o�r��s�uXV)��]��v�[��x�.���s����� ��l�x�t<� �e��Zl��$�aD�eY��<�t��8��h�e��t�2t�sP�(\��
D�|�i�����e����r<�|8��B~��'l@����el?���r�?���a �b@�dx�e��g��k�l��n�p�rL�s`�t��v����@�0�i8�o�ADd!��B]�+eP�o�D]\�e�D���
�E�d�i4E��l�n��r��s�E�e`F\H��\gl��oXI)4LX�a�e�s�L��L�.�np*�B���]�rNJ�aPP�,�aX�ed�i��l�(�P$�.@�rH�s8��d�hp��PP�i R�T.|�d��k��nP�LR�L��pR����n�R��[$)�e�Y����g�i�[
�[*)P_-�=s��a0a����8a� �sla����a0�i<�o-uD�yTb��d	Pd(�s�e�h�i�X�opl�<p��T�ap�opt�	z��.��k$xMx�a�z�y�y�����@��@���d�p��.<����eȺH�u.0����e<����l8�r|�t`��@�����������h�.H�n����(�ad�e��t
FT���P�.��X�s����n.����p�e(�]��.�a�b�e�.f�gd�kp�l��m�n�p�r��s�t(�v�/)��.����6)�f�g�i �k0�nP�t��<)Ԗ�xd���C)����(�d@�g��C)��9"����H�aT�K)P���\�j�����e��v�x(,���s(�������r�����eX�ih�����e���,%e�����n��i�����a�$(l��l|���e�����k0�s\�th�u�����t2p@�v �i�L(`�H�lH�P�e��2t�t����������|��z(t���s,�����e��tD�X(�i�v��(��a�e�r�t��X��.��t�x�a(*� �Kl�K��t�et��4�e�����8lt�J`�ax�g��l�]�'.\�MT�rp�Pl.0�Jl�e���.������a��e�T.��s��t��Q)��� �����.�a�bd�e�fDgdh�i�j�khl�m<nTpr�s�	t�
u�
vHy���aPbXcxd�e�f�g h�i !jl#k ,l00m�0nX2oL<pT<r4Bs Kt�RuWv�Yw�Yy���J�.�n�s�t�V)l�"��2��\)8��s�����e�o�r�yP��	Șc)L��Йi4l����aLe1g�i�r�u�y�i)0��,eȚ�\.|�@llrTKhU��]di̛A؛xex�q)���������m�n@�2�����efort8������e���rl�]�e��w)���D��(a�(i������0�СJTi\r���P��-4�})���lk�m0���tsH��
"��M�o���X��.����a�ei0s<tDu��"T.�dls8'L�(Ī(�WT. sL��L�;����(j����2�*���Lm�n�s0���
Ta�d�e�ik$lho`pps�v�*�t8b{
0�*�j�t�s�t�k�)���n�v�	��"���
���s���)y�Y�
�
��aLeXs��3����8oL�@r`��ĺ~��غ�hvd�<���|s���e@���a�e�mp(u�.��.�rh��)@����i��DT��l4���ed����i r��*
��h����.8���
0axd�e�i4Bl�m�n�o�st��.x�tla�eh��)���.����r���P�n�/��9�)\�R����o(��x.�mDD-�t 3ail������)��Z@eLr0�g4���,d�4n���)x���4�.�a�e�i�p�rsP��
��xd�k�s��((��l�i�l�n�MD�t�lp�`�����t��#��2����e��C���
t������.`a�b�e�f�g�ik$m,n<ohp�r�s�t�u��xb�m�n�v<������)<�*H��)�e@�)���i��dCa�k�lt����P��)`��
��"T.�d ���)��it�(�)$��)�p!h7*����4lLm882�=�d�Tsp�\exs�����)���iHD�0���j�tH�P��Z�iL�i������8�����8����.	e<	j`	k�	p�	s�	tq��	i$	k,	vX�*�}y��]d�4	uT	���)���L	����p	ax	o��*(��p��
�	i��(���)�	a�	i�	oc�����	d�	kl�(�������	o�	r���)����)�����	a$
eH
o\
rd
st
t�
yx�*(�
d8
g@
tD�	��*��T
p���,�G���)��ix�tl
a�
e�
i�
r-(��
l�
r�
s�-���m�-;(���l��(x�K�
il�;L�*@����
rX�Je(i<l`�mh�"s�
���� s`9l�4u���W
 4]de�5ipnl6h>�v�I��ab0c8d
ed
f|
g�
h�
i�Kj8k`l<mpn o4p�rHs�t�u�v�y��܅*�l� ��r$ud'*,��kP��̌`a�e�i�r�s
u
y;ø�itg|l�n�*�@�*�A���.�t�B!*��i�i�m�s�F)*(Pp�"�.�V0*���� \��l���kؐ�����d���
g8
k@
lH
pP
r\
vT�d��d�����pn���#��%t
f�Jt|�nԖ-�
g�
lP�z�a<[8*ܘ��
h���
d����
aܚ��
e��jpA*�����
t�Jdgl m(n0sl�"������0�W����
d�JPlP<nX<sXt`�P�H*ȫ��.�a�e�i�l�o$s�t�uPv�y�Ü���k�l�Mn�t@��	p���H���pA�r0����e<gk4s���i<l8NmLnXp`shtpv���(e��I.@�M*`f�LuC)���Dg�U*T�h�$��"<�\*�n�"xe�g�n�s�t�Nv��x���n��a*����|jj�Nt���e����
���"�fv�
���.e���<��"Tk`l0|phtpv�i*���@r����Ha�H<�i��|a�����e̵<��D����a�e�k�r�vt�o*lv*��*�a�e�{*ܶ�T��*���������=��@��(gp���*0r�
��q
(e��JHi����t@�"��.�m�p�s�td���
Ta�dehf|hpi��k�n�o�s�ty�Tx�	tP�(���*8t�sp����t��inr�t�er��*P��*t��*�2<bDkLl�Qm\t�Qvp�*$��l#�!d"��Tt�$�*����v���*P3�D�>�e�Wcl���l��"@Rm����e�t��0�����
���e�r�ut�����X�G'��������i,n��2��Dihl������.Xa`e��1���
��*\��4�"plm�n�ux���xa�e�i|m�o�t�u�(�t���@�)*��
adUgi�Ak0m8nUplrxs�t\��d���d r(s�*�*��`�ZLeTg\n8�
t�/�*��X���dt��*�s�t�u���*��(
�*���*�.�a�s���*��+��R`Uf�k BmnDs8�].�et<9"�	+�x���e,g�U�<(����
$i<jh����(Xt�W+8���P.lateT�+\�#+�,+�����J�p�r�s�t�2+4���e�:.`�n����o�!vT�i<�2��J�n�s(��
�el�q
8Cr0y8����V����9+����(a@e�B+�����.�a�d�e�iXWj kxl�p�tluxv�Xy�ä���.4��m�K+�S+�@���e�m�l�m\+�a�"��.�l���������lt���o`+p��.|���a<eHiPj`ø�g+.��� �n+�u+���X�P�D�g,�lel�3	X��d���e�o�uD�}+����t���+�����W.�a�e(o8rHXuXì�n���W.diXm n���+���+d��+\�*,����0iHu��+�������P������dn���`������������k�l�r�����aehTYi\Yj(Fo r��sLttYu`yt�`�*�Ee�s���+P�Y�����.k$�����~&���+�*(h��0nXs0�t<eH��+�����e���$������l������
�=(�s����y���=	�r��i��-�ae,i8j�nPrx����Yn�sT�
�	����l��.���a���� s��Ha�$edctx��+�������Xa���`r,���l��L���t -,��HZ���G���t`���\��[����[�X�+$�i�i@0ehu�82LJ �nK�s@a`e�j�m�ti�s�8kPrpw(���y��Xo\T�+̂	ls؂��tlh����u�"p��T����r�����ad8ehfpgxk�l�m�n�o�p�r s� t� u� vX�ȜoЕ���e$o0r���+��l��aܙ�`lPr��iT���h�.HeĚt,��x�E�]��a�k�l�r�s��+��]�s<��% ��%��xT���	ma�d�&eij<lPoxt��|�cL�t�el�i(�S	a�H����tt<�$sܰ0e8������Ha�^ghspv�n8�(t�n#H��+�������W.\����a�Ne�o�pt��+�l��)���+x����]�d$���n<rHsи���aTd\ehfpg�i�nosHt�ux`v�

���4k���\�.��n
���.T��#��$)�a�e�&��&���d���n��2�/������g,��n����s�?���#ad�e�?�d�W
�ip,Q�`����G <ihL�+pL.|L��$e(�20d��G tiZ""<�iT.D���\e0�5hm��:<���l�s��5l���J�e�l�p�s@�i��9��i��]�e��+��� e i s��i(�i��������e< kP m\ px td��l���4 o��a@�MH u8��������d v����l a|mr��D�� n����� e� i� o$��	��� n@�<�%T�t� e!i!s�(�� k!n(��D�h�.��m��XD!a�!e�"o�"u#��������<!nl!p�!rT�i�]X!ex���`!pX��+���x!nl��8�-�!j�!r\���!f�!g�!l0"mP"nh"sx"t���+��+�����!g4��H�J�!d"l��,��!g��"e(�3�"s��J$"a@"p��,��
,����H"k`"y��,��M��,p���p"r�"t�O`�Y���"rX��'.�"n���"e����"l�"rPQ��\l�,8����"l����"r����"�$#��[����#i��]#d<#pL#r`#t�/)$�����D#i���(X#r ]�#a�$e�&g'id'j�'k�'lL(n�(o�)rd(s\+tl+u�+v�+y�+���#b�#f$k$l$m $nl$o|$r�$s�$t�$u�$v�J����#t�"h��J<��0�b8$d\$gd$nx�H$.P$e�8",��2n�?
����%�m��t$b�$g�$t�B��$
a�	���$tpDut
n
�	���$t�
"�
"��8�$o$%u�	�$d,%e(�h4%i<%l�%n�%rX&s�&t
),
��
"`��\�.\%et%t|%u��T%il%vp/,���%k�%n�(��i�T"�%np���%a����.�%e&i@&u��%i�%l&n��T6,8Z�%tTc�R&g$&t���T�	�2,&d�J4&n��x&uH��L&a�&e�&i�&l�&p�&t�k�
���xG����>,�n�&d����&a;�&t����&e�&À�F,��t���&��� �'nT'rTM,���'d4'f<'nD'sL'th��S,oHo��D!� !X\'a�'e�'u|���!�x'k�'n�'r�'t���P"���'t�Y,�����'nx"��"il#�p,xH,���'g(n(r (u ,�'a((o0(y@(�a*�b-f��./|/��/��8(��0_��e\(y�1`,4B�	\*a|*e�*i�*k�*l�*p�*t�(vP+��H�@+a�2	X2��	�(d�(e�(f�(l)m,)o4)p<)sp)vP3e,PnW�
�~"�(g@5���(i��})�5�)l)s$)tt6��6j,t7U�7:�!P)iX)k`)tx:��:��:�<*�;��h)e�<(�)m�)pT<�|)a�)e�)i*o *u<*�h��=8(�nx=���)a�)i�)nt�s�=3	>*?p,�>���)k�?2*e*s�5[�@?�@2,*s�@S�A��A��L*�4*��A��BtBT*ll*r�B(H��Ct*d�*i�u�	E(�D�*nlE�*j�E� F�*a�*e�*iPF*,�`x���Ga�GX�*e�H�+a,+e8+r�H3	hH��+k�v,I$+pxIT�HO�J|,�J��H+� K�4Li�R��d+k�+n�+t�[�,�T��+i�V�W��+o$YJ�Y��+l�+r�+t Z�Z�L[+�[",l,r,y���,��+�\\\Z�\��\��].H,a�,e0.i�.o�.u/y�/È]����.p,g|,k�,m�,s�,t0m�,_��h,t�_d�,e`"Pl.����`�,l�`���,ed�pe�,�d���,i�,r�e�8��D��,t�l��,y�k	�,b0-dg`-i�-m�-n�-r�-sTtXmx	m��(-dH-edm�,lm@-bX-s�m%hp��X�et-k|-v��ԣ�dsJ�-.T�,���,�t���-o8��w�-k�v���-i�y�	�x���-j�-n.p .s.tX��$�M�-opz�,0{��z��.i�z�,���<�(.aT.e`.gh.mx.n�.p�.t��]��.�i\��,,��,���p.i�.j��[8��	��̡�,ԡ�.l�]�.aL����.n�.s�.v ��#�q
@��,ض���.t�����.k/nķ�/tD��,���|Ud4/fD/np/s���,$�o<���</g�-+���P/e$�PX/tп]d/e�>�/r�~��0�|/��/�����ex�
�����/a����/n�/r�/s�/v��Y��x�������/a�/e��iL��,����0n$0r�i��d0e���T0at0�<��,(���@0n��H0n��,�`0l����h0�Lm�0a�0e81fL1i�1o�1u�1y2Ä	���0p�0tT���0e����0r�Cp��0l\�0b1d 1e(1sD����1i�����We8�t�$;�0�h1p�,@1pt1t|1v�����]`1eD2�$P3��CA<B�1g�1k�1p�1t�w�,�CJ�1l�D��f�F]�1a�1e�Fi$]n�1m0^8$`n2sha?hbJ,2l82yD$��2�//��$2e<d��e]h�.�2k�2n�2r�e��@2a�2b�2dP3e�3gD4i�4j�4k@5l�5m�6nt7o�7p08r:s0;t�;u�;v4<�Lf�����fx�2e�f
�g�2o�2ri�i�, ji�i�2a3d3eD3�dj��jPT.,3k43n�j	DQ�
4lHl��<3�pl�l3ft3k|3l�3s�3v�l�,�l��l9�mS	(n��??
q]�3g�p�3nXp]�3e�3g�3l4r4s(4u84� q�	,q�3r8q�3ePr��r(�rK4e�s�hs#4ttt�Ttdt��04��tJl4ex4k�4n����PX4eLu]`4n`u��u�u'�4a����. v�.�4b<v���4a�4e�4i�4k5l5o$5u�(�vT.X#l��wZLw]�4o5u�w��w�,dx�Pz�$g��{m,5a�z��45bd5e�5l�5o�5p�5y�5��|�$�g�5i�5l�5t�5uX}(�}L~(t}%�c��5e�"u.ȁ�Ԃ���X~���5���	6a�Te(6g06ikrt6s�6t�6u�6�X�"T. 6fL��,P�{#l�T.D6lT6sL����,���L6bd6e���<��tl6e�6l�6o�6tL�a��i,��ԇ-$�
��������6�ĈthOa�6d 7e47i@7sh7t��uԉ��6r��x	-���7�|���7�@�7lP3��,7vL��L7v`�-@-Ԏ��T7n��\7od�	�7m����p�����7a�7e�7i�7l8o8p(8r�ؑJ�7s@��7r�7t�f0�T.�7pP�Xx�-ؒ;���<��8r�]8e�-`���l%ap8d�8e�8i�8m�8o9p 9r<9s�9t�9u$�.0��d8a�o�8s��u�|��|���8� ���8�\�%-�8f�8iL�m,0t���
�*-<�2�8k��
�
h7���(�8l|=1-`�9a9eT��l�8-��(��(9lt��09al9ix9l�9t�9v �B	 DX9n �2`9nx���I>-���9rD���M���9sl���9e�9o�9r�9s�Oh����9p����P�� :m`W)X���9e8��
<:aH:ex:i�:k�:l�:mTmo�:p�:t;À��.��fd@�kl:l��nt�s��(�]d:e���:d��\L�]�P��@�M�:u������:s����:a�:e;jȼ� D�B	4����:sD��:r|��@��L���;� ��P�;sp�]$;eP;i\;rp;s�;t�;y����l�Ch;e8���q�;tT)T�K|;r(����t�;i�;sP�,�;t���ȯ����;tp��;ah���������;a<e$<r,<s�����<e@���<rt�C-$�I-Դ��D<��E���̵8���<ax=e�>i�?o�@u$Ay�A�4�*����
x<d�<f�<g�<k�<n=p(=sT=tx^u`=v��������$����<kl.O-L.]�<n���<e�����<i=k=s��t�����4����� =e<=jD=sd�U-��;	����L=oX��	������.�=i����
h=d�=g�=i�=k�=l>n0>o@>p`>s�>t��)4�	�=i�\-���=e�=s$b-�	�Y�=l$K0ci�d>a,h-���>n(>tTo-�v-@�|-�8>rP>s���XWX!|
��X>jp>s��-p
��
��x>a�>n�>s�>t$��-L�-|�
��-���
�>bx�f�>g?k0?mP?nd?pl?s�?t�?vTA?a�>s�H�S	�e?j$?kp��d]T�a��<?e���-\?t���D?s���hi��	�?e���-����x?e��?n(!J�?e!�)"Y04]	�?b�?d@f$@k8@n`@p�@s�@t�@v�4�j�T5�?e�oh
@r�5���?o��6A�6��@kH9 9]0@iL@lT@oԌ[�9P6m�9(p@nx@p��G\:�-`;��:]�@t�;J�@n�;]�@el��<�T�S���@i�@m�@sAt�T��U�l)a��{dWt�@i|V��Ar�@t0W�-��Z��Ag@ApdAs�Z
�Z��8AaTAe\Ai��_0��p[��\i]��lAe<\��tAkbr�Av@=���A��A��^��`�-�Aa`���Ad�)e�Ak�Al�AmBn����`�
8ai a��Ae��ha���e4bh�.�Bd�Bl�Bm�Bn�Brt�s�B��Ba��cCe�Df�DilEk Fl�FmGn<Go�GpD#rLHs�HtJu�HvlJwtJy�J�c��f�
�e���Btlgi g��Be|h��g���Be�Bs��ti��i��i���B.�BaCk�ih\j�tn
H�d8CeHCg�ui\CkpCl�Cn�Cr�Cs�Dt�o��ptlp��@Ce�r�r��TCtTs's]hCa�Ce�s�-�s�Cv`�idw��Cov���Ct�x2x���Cf�Ck�Co y�
��?&Xy��Clpz�z���CaDkDm$Do,Dp4Ds�z1���-��P{�p{[HDexDp�Dt���-PllDry�-�x"XDeP���`Di��,�q�{���-e������D��~�D�l����Da�Df�DkEn8Eo<�sHEt������- �n�Do��������Dd Eg(Ei0En�s���
�
 ��-��m$�H	�n@Ei��-���TEb�ElT���\Ea�Ei�Ej�El�Eo�Er�EuFv0��- ��-@���Eo������-hO
��-�Ea�Eed����En 7i���(�X�.FaFt�R��V-p�.��8PFa\FodFu�Fð�W.@���8Fa����DFg(�ih��pFg��.�.���xFs�Fv�����F�������Fa�Fe�/i4�.�(	̞�Fk��M�Fa�FeG�ȟ�P��Flp��D����F�x����MGo0G�(����(G���`GahGdpGg�Gm�Gr�Gt0�v����U����.ܦ��xGbl�"��������Gl@�X�Ga�Ge�Gi(Ho<HrDHy�*H��Gk������Go�Gr�n��	(��Hr���4HsHt���Hr�.x��0�*���)XHm�*9	��&����`Hd�Hk�Hl�Hn�Hv���hHaIe`IiL{jxIr�Iu�IyJ�T��x�*���X�tt���Ha4Je`Ji��$Je�����Hk|n��L���JIaH�Id\�g0Ii8Ik@Iv\�.���$��LInL�g$�e��S��XIlpIn�����K�Ia�Ie�Ii��o(������In��3	�����Ik�In�Ir�x�)*D�(X���lm�Is6����{v`�d�����I���Jn���� .x��
����,JgLJr̉'.���DJt@�(	����XJn ������n�Js��P���
�Jm�������Jrl~���J��J��J���CT.�����Je4���Jk�JlD�K`�ؾ.KpKrKt��9�:,<�TKa�LexNiOj4Oo@Pr�Ps�Qt�QuHRytR������'.�Kf�g�Kk�Kl�KnLpLrDLsdLtpLv��r4�����a�Ks�Kt��8�..8���}ax�i�K.<����Ka$)l�Kn�Kv@�5.��il����n�����Kp�������L.,Lr4Lt���	���
��;.`���<LsTLt����A.���\Ll�J��h7k�Ll(�G.��*�La\�
�Ld�Le�LfMgMi(Mk<MlxMm�Mn�Mr�MsHNtlNv�����M.$��$,����L���-�L��D���MeH��	L��@��� Ma��i����4MelMiXMs ����PMkd=%��"dMtx�z
�Mn0��
������h���h�.�Mi�Ms�{�Ms4]�Mn��Me��R.D�9�"���M.NaNe Nk4Nt,�@�)��'.���Ne���(���,NiX�[
���@NaXNel�;0��$�e �-`Nn���b�Nl�Nn�Nr�Ns�NtOv�	X�
|���Ns 	��
�p	���Nt0��<J�Ne�g(���Nd��Nn����Ne�J���Oa,Ou�

���4�adOf�Og�Ol�Om�Op�Or$Pv���J\Of�]W.ltpOmxKxOa]�Or�h���Oi�J�Of�_.�"����.�Oa�OdPePiPsxh��.t
��D�Pe��)�",�*���,Pl�4PaXPe`PotPu$"P]lPs �@ "T�"	 g�|Pb\#���Pm�Pr#��Pa�PeQi$Qk@QlTQo`Qp�Qt�iir��#�Pk�Pm�Pr@$��$�x%d%�Pn|�j�&�Qa0&��QrX�u8Qv'e.<'8(e�'�'MLQk�'X��axQr���x��pQe|((@(K�Qa�Qo�Q�)
 )���Qn�Qp���`�������Q��*j.�+Pl.4���Qrd3���QeRkRn(Rt(4X�4n.�4��Rd�6"@��
|�e�9��0Rk�8��<Rr@<t.�Ret;��TRr�Rs�Rv�����R�`R�X�&$=y.`=q
�>i>���Rrl?�Sn�?���RaSb4SddSe|Sf�Sg�Si�SllTm�TnUo UpLUrVs�Vt�VuW�P@�AT.�@��Se,Sj�A9	�B�HSa�@(C@Sl�ET.tSa4E��TSr�tF��F�Sl`GCH�SrpI��K
�J���Ss4t�i��Pt�Si�SrPP���SdTe<TiHTlPTtj�m(�P�Sd$�g$Ti,Tm�"n4Tt4�vhpDQ(�{( R�.�R,+�T.(�(�V"XTn�V��`Ta�Te�Ti�Ts�Tu�U�����Tl�W�TapW���Tt`W"�Tn�W;�X�.XYR�Y��TdUgUn�Z��1lTZt�Ts�[�<�e�\h�^xP_�`He8Upp`�)D`m0Ul8a
la��DUapUe|Uo�Us�Ut�UuVv�b�$k�eP�Up@f��Ua�9Z�f�.�Ukt2p�Ut�Uvgt<g�dg~�g�h��UkTh� �n�UrDh��Ue�L��h�)ViXY�i��.8Va\VexVk�Vp�Vt�h(��.LVkTVrxdIxii�i'hVlPj�Lk(dk��pVaH���l��Ve�m��
<p���V.T�a�Ve�Vo�Vr�ps���q�V.ptp�t-�w�.����y��Vy�y���V�@�XHWa�We�Xi$YoPYu`Y�{Z�z��4Wez��<WkhWl|Wr�Ws�{�x{��`WiH��~��tWd�Wi�Wt�O4]�Wn��"�We�.tT���WeH�������Wd�WeXi(XlDXrhXs���.�K��WlXsD��
�JXe��4Xa(�p�h-\���<XdTXn@��.`XsP��.��tXt���������|Xa�Xd�Xe�Xl�XnYsYv��	H���Xn�Xr`���eT�il�#x����Xal��.����Xn�i�����XeYt`��p�dH��8Yg@YkHYt���Й
,���]��8$���pY�XY�����e ���xYe�Yu�i��@���
�Ya�Ye�YfZg Zl8ZnpZp�Zr�ZsL[tl�t��Yl��%�Yl�����Ye�J�(شZg,���Ze���.dZkh���,Zs8�x`��DZr@�&LZe�]XZjȺ�|ZrP<9���Zi��k��i�P�Ze�]�ZnĽ�Ze��]P�a�Ze[k[s [t8��.�Zt���T&e��E`����Zr��tD�[k��.0[a@[e$�P�(X�8[l0�J\[ap[e$�.8�'x�h[s���<���|[t(���	��.�[a�[e�[k\l\n\\rh\s�\y��]��[r��6)�[n�[r����T.��]P�
,�������[e���H\s$�x0��\r�N$\e���0\j��<\k��,+����T\r,�t\et�i,�����.����|\r�����\a�\d�\e�\r`����lJn���.x� ����\et����\k]l]n8]p`]rt]t�2�����\e�����d ]e�it��T.D]n����(]e������t���L]s����T]a�e�i,��]t�3 �����.�aX^b�^d�^e_f,_g�_h�_i�_k�`l�`man��olbp�brds�dtfu\fv�fw�fy���]a�fb�gc�gd�kef�g�h<�i\�j�k��l$�m�nL�oؤp�q$�r��sȯt��u��vlJw�y�z�~ì�M�0ap^lx^o�y���P�7�^n�$�.����8.�^e1g�^i 1m�r�^s|���^m(P(؛he�V���^i�^n_p�d���.�����^t���-��J_ll�o��g	С��$_gL_i\_nh_o|_r�_s�KtW�(��MT_e���.t_m,[p�����d�X�_nt�#�_a�_j���s�
��M�_aȧ�0����_n����ta`e`j`k`r,`sD`t�`��"T.T��.|��8��L�h����$`j<`ll��.��p`e�M��MP`e�M��X`i��d`r���̰��|`�0�J�`k��y�����`b@����`aL2b�`e�Ao�`u@�(���`s�`td�(��b�����`l8���aaDac`ad�ae�agblbn�o,bs8btDbv,����Lasx�tTaatae�ar��X�b\���i�n.�att��'��P�al��m�ar�t�ae��r�asb�`�Bl�n�am �V�ae�*;x,0�����a�\�g	����o��;�t$bi��dip��Pbi������Xblx���`ba�bi�bl�bs��
\�
����
�ba�be�bi���(��bn���������b.caHCb ce�cf�ci�ck�cm,n�cr�ct�cv<���cb���.@caHcdPcmXcnpct��.P����������.hca���.��p|ca���P�+����cr��"�ca�eP��.���ci$�����
���$
a��mX����cth��ce��t8���	dc4deDdiPdkddmxdp�ds�dt�dv�i8��<de��]\dj ��.���pdi$��p�/����d	k�dm�dt���da�de�
k$�Pl���dr���D����en$eu�����da4eepei|em	o�er�et��?
����eg��)*D�((�,egLek\enhes��t��0��Teo���	8��.`�
$����.�en�er,��eaX��/<�@(��esx�t�ei�er�eu��(x�K�eal2Z��/@����ea$fd,fe4fn<frLfsPEt�� �/,��L���i+����Dfe�xtX�J�fa�fe��h�fi�fs�fø�/4�|fl�(h�"�fll�����fn�����fn 	L/<���f�l����m$gaPge�go�gu�gy�g�|2@�ge\Pgd8gk�En�xDge("�'.x
i`gnxgr�����a��.p�lge�!�d!��gaxJ?+��ght/�.�ge3� ���g� 4�ge�5��T.h>tha�he�ii�iojr�js�kthi�>��.4hkThnhhtt@�D@��,hsDht�@�x�#�A��Lhd�D#/�D��`hoxhyl�O�Ht�b�.n<8o�p�hrDis�Sn4�.�ha�he�hiikTBl(is8T)/�T�AkhU�-k��./�����h����häU��hl0��D��ie4iiV�itlI$W��VM<ih\it<X��WXTir�������k��a7/Ta��xik�[�is�h\h�ia�ik�il�ir�ivDy��i���is0jJ<</(l���ie�m��mJ�in�n�n��jk8jn@js�mjaPje�ji�jy�jôn*Ho��o
�o��Hj.pjg|jr�js��(p��hjn�p�	�.t
B/|
���j.|q\s��s��s���j��u��jk�jlkst��jekk8kl`kmpko�kt�u(v��v(xMka$kj�w(<xI/�x��7g�x8,kaPke���xHkg`7t`y�y��y�hkmp{a0z|kr6d3���ks�|�kuȃ_�kd�kl�ky��������@la�lb�lcmdne(nf�ng�ohhpi�Kj�qk�rldsm�tn(vo<vp�vr�xs�{tt}u�}v�~x8Z�܅�\lgdlklll�ls�lt̆
؆��xla�i���Ԉi ���le�lo�lÔ��m��x�&���la8&�ltp���ls��N/�0���lk�u���l�P�����<mř��	maXmdlme�mo�mr�ms�mu
y�m��(�H��H��Dm�\�Lm�PIV/��dmb�^m�mp�mr�ms0�]/D��p�%
����mp�mv؏d/��4�(�K�mo�myH��l�=ؐ̎��n�;�<�n���ni(�Y��`nn��M	nalnftni|nl�no�nr�nt�ny�n��.���Tne|��#���0�k/��@r�nth"c��q/�l����$�����n�X3x��"�nposԖ���naoe�og�oi�?j�ol�oo�or �s�ou�o�5�
��R8od@onXorp�t�?v|�'��	0Ahonpos�@]HoaxooPA1\Aw/�BH�H�P��oi��2�of�K��
̙W�l�^�P�oe�n���~/�����o�ܚ-paux�s��ol$pm0pn8puHpv���/�u��pr`vx�x~yi)�x��@pe�
l���Tpo���\pd�pe�pg8qnTqrhqstqt�A@�.�pn�ptě���;�pe4���pdX4e��	@����]�pd�peqh qnԠ�p�.����prqt��(`��/Wt@�Mqa0qe4���dDqt��:
D�����Lqu����J`qt$�L���.�qnd���|qa�qe�qiDLj�qk�qlrntorr$rs\rt Mu�rv8M�<x����2d(�����qs`��/��/ra���-<G�Ĩ��ro4rp@���O[
����<rd|��Drr�Protrs�Q��P�lrtȪ���ȫ���ra�rd�re�rg�ri�rosssu,sv�y4s�4�u����rmT��د�x��/�"�rg��<�6sk���D�$sk��n�j�����O�Ds�@��Psp�����Aa������\sa�se�snp>otpQr(tsLtt`tuhtyxt���.�st\��srh��/p�8�skD���ssP�w�si���/�sa����$��sdts4���se`����нt ti@tì������8t�t�n#Xti��i������̾�ܾ��<Q�pt�@�"T.�tl�tvd����ta�td0ueLugpui�us�utv�ȿ(|�@�&�tn�t�taueur���
���tn�xt��ue(ui�j9"��d�Qm�Qv'�/��tDud`urhuu0�������i,7v���uk��t|ua�ue�ut���,�L���um�us8�k�(���ua��(�����um���ua�urt��va������� v�,S����i4vl�����Xvahve�vj�vl�vo4�iD�"T�`vl�vnȿ��2xvn��t�veH����vd���8����.���va�ve$��/p�����/�����va4�"�vb0wmx����va�dDwe�wi�wnxoPxspxt|xu�xy�x�p�<wb���/��'	T�.pwa�AfUgxwi�Ak�ws�wt�wv(�d��/����wo�u��/���*�"���.�we�wn8Bv4�
�wi$��	x����wab�/�1���w�h���w���J�'.$xm4xp@xsHxt�����
��,xe4�n`�������.`xp��/��
��hxe��"�xs(��
�e��xr<��l��8����V��x��������x.yaHycXye�yh�yi�yjzkdzlpzp�zs�zt�{u�{v�{y�{Ðb(4�yb0ym8yr@yu�������k�Bd`o��"Pydxyg�yk�yn�yr�ytD�(��n�����*$����X�M�yo��.�yg�yntă���� ����yg�yn ���\��yu���p��zp|���za4zj<zoPzu ��/�����l�V�/���Hzt$��,�\za���za��*8�|zl����ze4�'.T����ze���zn���z.�zl�����za{e0{i(oP{rd{yp{Ä��/(�0��@8d{g {l\�(	�(	4����({m|�0���<{���D{�t�0��\{r�����{�P�t�0<�!0���{g����{i��H�%����{y�����{���3����
ؾ.|aX|e�|i\Yj(Fo�|r�|s(}ttYuL}v@FyT}ä�ܮb,|kD|lP|sL���+a��(0.`�*8|e��(	��"��.�|a�|gk�|n�|p�|r�|s��D�����|k$�00$�80P����.p�y���|k0��~&�|e�|i�.4��`��}j}k�%t�� �
��}a0�t}e<}rD}s������\��	����d}��Y���?0|�9�Ml}l�}n�Yr�}t��F0\�d�}e�qW��M	4�.�}a~ed~ip~nx~o�~r�~s�~������}d�}l4|����}u��L0����~d(~g0~i8~kH~rT~v�R0��@�Y0t���@~n��a0�s����\~d0��d�dx��������,����~�8���h0���~e�r�~t`��������~��~��~����XM��.����$,a\edfli�j�n�o�u�T.HnTrL����@t���	i�����g|n����i��a�e��^�-$]�l��u�(�m�-(,�t@0](�a��e4�gP�id�l|�n��ór�sx�u���X1�/ �fP�lp�n|�t<�v�1".�1��D�e`�tĻ"	�,�2��h�a6�0:��8��d�Glܢm��n؀r�s�>��>]��aPA�0A"��n�p�@]Ȁa��i�u�y�(�Ai CJ,Vn�CDCtCt�l,�pDDn0HG;�KfhK<�n�JD�ehT`Q��\�ot�y�Ui�V�Z��Y��b��d��k��m�v8Z�Z"8_t0�^����f]K��e��bz0�a��܁y�a�����c�db#��e�kP�ld�t�e]0�r�f(	�f�(�eD�\,g<�ggD�ek��h��\�r`ox�np�l��n��r�o2�o2$r�G�����������8ri�r�@�.ԂrԦv8s���.�s��a4�j��{�ti�.�t����a�s��k(�vTy�x�� �sh�i����a�bP�d��e؄f�gh^h�k�l\�m�n�o8�p|�r��s�t�u�vT�M��l��mȃnԃsԐ%������a@�],��������s��p	����܃a�e�i8�y4�p����o(��rT��(�eH��t/;�0�e��"�FkЕ��D�al�ex�o��r��tX�1d��T.��%t��0l���eܙ]��n��rЄsT���h�.Ąs��\�.<��,�n�a��f���tt��x�n$�ax�e��n��r��s`^u��T.<�nD�r`�s���(P�t��v5��X�.��nD�hP�l�nqt@�I�����K��e<�2�#��eȅiЅl؅p�t,e�ga���lj����i��r�k���$���nD�rL�s�]�a\�e��i��k��l��n̆s؆u�����
��T�ep�n��sl�*�����x��ܦ=	���,�Ĩ���]��o<�P�0�04�x����Ćt��i(�T����a�d�e4�lL�3�2�l��l��e<��bȸtܰ$�e4�(��W@�r|�s��t\���H�a��e��h��p��rȇu@����G'P�t��el��T.@�
����=��r@�����0$��T. �lи��Їa(�cD�dt�e��f��g��j�kL�n��o��s܉tx`v�Ô����Z4�hp���0��t<�rd�sX�up�l�s,�Vd��0��WT.��a��m̻�0�T��h����t��j��l�s�vt(�xd��H!.�*Ȉn̾dԈeX,g��$��u�5�0L��s0���e8�lD�v�,�p�0�e8���Tl�ex�i��s��t$���d�t���0��00[�08�'��r�D(`�"��r������ẻkԉt\��0��������08�����\�]�`o�s<����n0�sL�tLp(�v`�/l�I��JP�ed�p�� 
@�H�s|���]\�s�r��]p�a��e��"����k$�����k������a�e�k�o�p�s8�t,����؊t4��0l����l��d8����|���p������$�n����,�ap�e|�i��o��r��u��y�B����\�k4�"d�r��i��l��0,��D��0���|��0h��������|��̋rt����kD��)e�k����؋e�ih�jp�nx�o��rČt�y@,$���aP�d<�g��lD�mT�s`�v����(<�5����L�tX�'�����0�i|����e��������r-L�����e(���lԌr��t��e�s�-��\�k�m���8i<��,�W.$�kT�����a4�eH�iT�s���4�p�,�gH���@�e��(��]��a��e��i��o�up���������|�n��r��s����b�Cs����(\��
��d�e�i��k�l�cn�r �s8�t`�v ��X�-|�JH��vt������ؾ.��%�.0�t��xp����.L�eX�r����.���0t�-����h�����8��0����d��e��mĎrЎspl[�(��`�����iX�x��i���؎n�r��t��`�
��z�s������. P�a��b��el�l��n��o�r@�sd�u��v����2`�l��nht(t�i��D�l�e<�P	�P	h�.��d�e@�h�l�n�r`�s�t8�ԏa�e|�
`ȉl��(�
��i���g`����e���4�.�a0�i0�s8�u��[
�;���0�n@�d���H�aH��T�t ,:|�i��u0.��.��/�
L1��n�0m��iX2��.̐m�s�t6��5�Đa<:�0:�ؐa0;�(=��<���sT<���a �i?1�>���k�C(C,�r4B��4�eT�h\�t��
1�HP�R��elt�t�V��e�V�܂Y�W����gWX��e�\��������]�a�d�e�g0�i0�jȖkЖm �o��p�r8�s��t��ud�v��y���t(�]��
�a@�fH�kp�lX�md�n��p��sؒu�v_��_J���`��P�ia��T�ep�l|�o��s��,b���k(��`b`lb����a��s�b1d�Ēpxd1<f3	Љef��̒rl1\fJ�l��r�1�g�@l�h�k�k�a|�dܭeT�f��gȓh�i�kĴl8�md�n��p�r�sT�t�uЕv�ydl#1m�.m��p�a��r�m|�?�oR��v�n����e��r�o)1p*�o-��aܓo��\�-1ț��������p�hp����g�q��q���i,�s\��$r��$�tdsJ�t&.�tt@�a��e��r��t�t��L�d�k��s�tSu�t31u��nu�(�;1�uB1�vH1�v���e<v���l8���w̔k�v��Ԕi��o@�xJ�b�xt�o(�pH�t��Sx��pz� �rP�+p{��4���z��<���{����.̵ap�s��t����|O1-U1���x�e}��l(}t��e��^1T}�����0�e1~����n�v�}M��e��T~k1�~���F�L�r1�~��4�.�~�����������/��X�n<�$�e`�g��mh�n��p��r��s��v�����	����d�s|�t܉�
��.|�u18�*|�������n������e�(�-$���uT3|1�E�ܖe�s����n�r4��e�E���1L����'.T�b\�dl�f��i��k��m��pȗrؗs�t���$��<oi����d�fl�i|���x�d��1������s`����s��P����p�����"l�� �]Зo����]�eؤ����X����n$�E�axd(��� �kp�m���	(�a��e��k�l�m(�n<�t��vt��lg.4��	d�e(�(��|�m��ndv�<�����d��ĘaИv����1����r����(���ؘ�\��l����g@���a�a���yx���M �o<�����4�eT�i|�
��L�g������`�k����h��ȯ��o��1ܲ����e��-������aܙe�f�g�k �m(�n@�pL�rT�s\�td�g
p�(��
ضx�a�e�sl�1�,N3T��ķW4�g�t��e\�����i��"��*��"���"x�a���p�n����Us,����s�����kPpĚvКyP����������e����V.x�a�e��n��-�r��td��1��n�����r�s���1��\�a��bԛed�i�ld�m�o�s�t4�up�y��Ü��'.��n�p��r��v4��1(���x�mx�v\���i������k���,�A�|��]��s�u��ěl�p�r8�sL�tP��p��h���o�����f�i���t�m����$�i@���,�t���1d���D�rL��x�eh�X�l��n؜st�.���������d��gxVi��nȜsМt����
@����ed��1��������;���pH��j�p��@����<�T. ����el�(�lD�t�����0.��
L�e����X�k�d�r<���8.��s��y�������|����
��1��a,�hlНr؝sp}��1L�a�e�i(�y�J���b\23x�,�u$`
�f�e]0�l��n��r�e��8�a��b$�d��e��fԟgȩh|�i��j��k(�l`�m�nP�p��r �s��t�u�v����fx��e�fP��.�gJ�bt�l
L�'.��a�e�gОy��/X�n�r4/���.
}) j��i�aD"eH�r\�s��u|q��k@�i`y��k�T�mx�t��v�z`�k�1p�e|�lOm$
�l���gpl��n�Pn����j̟t,p�#Xp��e�i�o@�r`�s�pi��v`F�1�M�pq�r�r�$�p�[a�_-�r��,�s�rK4�eP�o�`\s�hs#X�a0u���'.��e�t��l�d�4nX�a����u��a<v���4iРk�l��o�s�v�w�Lw]Ƞo�u�S�w���l�w�1dx2�LnDy�1��e�z��|"�g�z���e<�jD�of�]�m�1l�mL�r���T�d��i��m��s̅�l�x�n���X���l(���e��,�����r�t��t�( �̡lĈ]ԡa�d�e�g$�iD�sԉn
\�|�t�rD2S���tLN���80�jL�t8�k��JH�ap�e��p��t8��@�h�a�5�]|�aL�~ܔ`�����aĢd<eТg�i��o��0��d8a��2ܢl�;���<��e�\�ܡd��dȡ������
8�]�a@�ep�k�:l|�p��t��-kt�sT�v�}���W���'\�eL���d�l��e�d ��1P���sp�]��e��iģo�t��.X�2 .��̣t��tԣe0�����n���,�ap�e��o̤s��x{���d���� �l@�n`�r�*P�dX�e�|#�|�&�*8��1@���h�d��r�b�1�����.<�e��n��s��t@��.X�̉�1ԲH�2$��Ĥa̵��a<�eȥhХi�l��o�u��
�r �s4�i����e(�r������(�o��0�d8�e`�ih�l��s��t��u��JD�;x�e��i����gԾh
��n����6#��-��m��;L�����i,�i�����n���1u.l���ܥe����d�ep�(��t���8�EX�a��e�i(�o���,��14���@�ap�i����H�d��n�bxl�h�uh�W.����|�e��������e������d��iЦk�v�x�����Ȧa�e�.L�`M�a�����a����m�v"���ee04����d@�pX�s|�t�9���ra�:.�:]L�ah�e�:T.��;�;]t�s<\���srhv@=��������`��id�B��a��c��eĩf̩hܩi\�j��k@�l��m�n�oh�p�s�t�u�v���4b��`�dh�g(�l4�mD�n��r��t�e��Ⱦd g�	�g2�g��<�ah�kt�od�s�h.�h]\�e�h�\j��i��|�kT�k���.�mW�B����utnh�.H�d8Ce��i�l(�m<�nH�rT�sDlt<�u��vqiq���d��g�s,s]�e �vu`tuJ4�b�u2v����ox��H!.z�x�k��l��p��s��t����z��p�u({�8�iP{���ap{;��2|{��r�}?�~��ă��ԩg��k�l�m0�n ��_��]�ld�( �ux�2 �i����(�n�������<�k`���D�k�P�at�e�2ԉ�l�f��g��n8�2`�*T���X�.�a �e0�i��j��l��oܫr�s��u�v(�ô�s��Ъi���تb��n�r`�3	�'(���i�t��"2Ў��)2@��(�fL�ld�n�/2����D�d�62�sА��X�n������p�d��Xx�e��o�"��<2@(�\�id�����aīr̫v$���;*|�����ԫa�A2�F2(����n���p��
�a��i���4�pP�����8]��Aa���l�a(e\Fo��u���@���`Bax�n����\�g\_L2̜(	h����k�������s���������Ԭ�T�
0����rD���\��Ȭ���"��M�u�������k,�n�GrL�sX�t0�vp��8�e@�n �d��.|��X��D�e�����"@�X`�a��e��i��rl�FH���g��i��l�����S2���x��̭eحi���
����ĭd$�*��PT�8�����k�zl(�n����a<�e��i��oЮr��Y2��� �k@��-H�4�.\�dd�gl�it�l��r��(ܽ�\�`2��(��f24���|�r��.��elN��
������n��������n��Įe�iD��
��]��b�k��n��N	��"t�m$�el�i$�i#�����a<�eH�kX�r�����n���
8�
���P�e��S	����d�k��n4�n
@���|�d���4����r��yl~����������q
`�"<��a,�bH�e�i��j��l��nܲo�r8�s��t��u$�vL�wT�y�����%����	�aD�fT�k`�l��n��r��sܰt�v��(�:
4���L�t8�*�wat�b��e|�m2����h
|�v<���$)l��o(�t����4Lt���`�����t4��+@�Ȱs���аe�o��rt��,��(�s2�J�k �Ô�?&<����P�x2�R��J4�e\�<�d$�k�mmt�n��p��r�sؾ�����l�.D�h��*$����i���"��s�����aısD�|2D����t��.���бe,��ܱm���a��2�*�s��d@�ex�fL�gT�kd�n��v��m8�t��
0�h"|��\�a@*d|�n��s4�2X:
������i�"�����ahG8�e�9�]Ȳr���вa�g��sn8?�t�*�:(�a8�eijiسo ���*��-$��0�g`�ip�k|�l��m��n��s��v�8��2���h�o�
�[�c�����s�i��t��00I(3	L����t�*P��гl�m��p�s�* ���Ua �"��!���d�!��0�����"�#_h�at�i|�t��vlg. g�P�e\#��\�md%�@() �)~��i*��*�2�4��4����dd3����nдr,�t�5�L5nȴa�e�s�è5g%��n�r�c�	��59`�6����47�'.8�aD�sP7xxl���8;�82`�d�8�2�;Ct;��h�l��v����p��`=il@l?����r�?����a��bH�cd�ep�f��g��iжjضkH�lT�mķnL�o\�p��r��s��tT�v�@J(�b<�r�n
����dt
��nXA�a4B(B�24�i�B�F`�EP�e4E��X�r�F�����Gx�rH����a��oXI�2�J�ĶdKt�J��r�J���e�K{%4L����.la�e,�k8�r@�t��L��t&�%���ipM�r0M� �e�Me�N�,PP��xy�V�t�e��o��s�����
`W"l�lpX�Yt�X6��k�Y(�Y�����\�TZt��r�Y����d�e�g�i$�n8�sD�t[T.�[(�[�l�[t��e�[�'�\i�\��e�]ct]"0�j,^��^Z�`�2P_��T�sl�ua�2�b���i�nla��t�e��i��n(�txcx�e�&�a��e�e0.i����.�a�e�h$�k,�oH�s`�t�h��kxd�Pj
�i"��l�7/�j�edk�pl|��(��4�dm<�iX�t�m��mMl�r�nph�.<p��t�a��e̹fԹgܹh��l�m�o�r�ps8�tL�v�r��q��t�r�r�2��e0�2��sG�aDt"pt"�t�h+((v�lD�p�t4vt$�a�+(wI$x{%��ad�eXx���.�nx�r�x�@����ah�eT�i�j�l��n�oH�pd�r|�s��t@��zT.ܺk�l�m�n0�rD�s�zZ�t�P{Z�il{tx{����a�|g
�|��Ьa(�d�|�2~��4�.H�2T��<�a��s���H��T�a����\�d��i��l��n̻r��s �t�ă����s����a��e���(��	Ļe`�\���l�.�a�uć�2��"�u؉����k�l4�t��� �������.4��2���,�r$���н���Ľ�����e��k��mļnؼr�'`�p�eH�]x�n��rT���]H|a���
��,�:��r�����gx�8h��мo��MX��gy�����"H����f �p(�r@�v���2�����e8�s��D��l�	��R
����P�rt��X�at�i<��=��e��k��l��i$��T�OD��2$���a���2�����e���g�p�r0������Z�M@�����at�bT�d`�e��g��i��k�l�m<�np�o��p��rпs`�t�v0��2<���L�r�;l�sh����"l���t�e��l��I���M��s����t��jܾk�u�v�Ø��rh�]оe��i�e�[x������,��<�iL����e,�pܶ�2\��h���4�a�ue\�ih�s(/��,T�l����J|�k<vi��
%Ⱥ����e����at�e��kĻ.@��
�T.�k��]��a$�e��k�l$�p@�tL�y,�@`��j�l�����2����������,�a��4�r��$�.0���T�a��e��i�0r��x�x�k��r��s��������i8��2|(	d���n��2X�M�e����bD�	<��d�r��t��Y����.,�r�����a@�e|�s0��8�m���2���.X�i`�rh�s���24�*T�,��.�J|���t�v��t������a(���d�e,�k\�ll�m��n��p��r��sD�t��v��yX��.�l��m����e��(�^������B�rd'^	����jP��� �kD�nL�oT�r�3�
3$�9��t(X�h���d�ex�]����x�a��sH������t8��
������sL�O$������d��k��n��r|�S�����,�����.�a(�e4�k<�s�]�n�g����t� �r��������2T�a`�e(���X�.X��.p�g��(~\d���x�rt�����a��d��e�3�����.��r��s�*$�E����	�.��a�d�e(�f0�gD�mT�st�t��`�����+�d �l�����i��<)�x<�e��{L��31��L�.���x�`�r����h�ed�T���l0�J��a��r��s��t���
��g�i�k4�mL�nl�p��r��s`�t��v�^��T��e����(���������# �8 �ex�.0��,�eD�o\�����\�dd�s����H�.��dx�n����d��a��i��ut���.��r��s������*X�����n��(�.�X��a��k�l(�tD�u��o,�8����W4�n<�r����ex��4�����.,egt�k,��L�e|�i��t�m(4i.�o-�M4�e �h�a��b��c��d�eL�f��g��h�i�j(�k<�l�m(�n��o|�p��r<�sX�t��u��v�z����a,�bX�cl�d��e��f�g@�hh�i��j��k��l4�m�n�o�p��q��rH�s�tl�uD�v�w�y����Jؾ.x�r4�2��J��rȘ3��*������e|����h|AiTA����eЙ���m�����a��e�r�sȚ(|���lh�*x��a�3�mh?f0�k@�nl��������8�e��-d�ip�r�!3̟P\�a��(3���t'
H'��|����8Fa��e��n��r0:'����d��l��s��(`�.3��6#���yd��M��e��0�����s�����M�a����
 �.`�ax�e��k��l��o��r�t�u4�v��X�X�bp�d��33��"s��
|�]��eX�:3��et�t0;�Ȭ"��t8����e��o\�*8@�|�]��nT�(�����l���a�i �o(�s��4O�d��(�t��@30���	�ap�e��i��lD�o��s��tT�u��Ðli#�"h�b��n��tLun
�����gԵ(��J��]��n����e���غF3$������kĻ��e��*������@����r@����e�Ao8���T.h�a��c��d��e��f��g�i �l4�m<�n��o��*�`�k��l�m��v���L�X�,M��e(���m��nx�t��o�k���2��K3<&��t��a��e�r���!���$����a\��#,�e�8��9=��T�eh�s��(�L�m��	��t`�l���x���t�a��r���������b��d��e�i\�k��m��n��o��s�t�y��H�9�� 4.��g��i�l�n�s4x`�*�����P3�����W3@�eL�n���P,�e4]4�n<�*�����T�ax�i��v�]3�2p�t���$����oP��Pl.`�����a|-��r�'%����p�����0����h��l�F���i����e��T.8������`�y�$�lh�t8���	,�ep�h��i��j��kh5p��s�tP�y�����l�(�x�a��k��s ��<����xd���u�e3������i��o(�$�(�e��t����e�Ü���k%������������.4�e@�oH�r0��8�,�m�����2X����t��a��e��f�i	o(�rl�s��t����d���(�2T.��d��l��n��r��tx��(0�q	�j3h����.��a��i)�����'���e|�y �.8�W�l�0,�K<�aP�e`�o$��H�s�3	T���[����X�m��'x�l<'�1x�tl
a��rx�r3@�J��k��l��n��r��sD�x���,�L����
������uX�J��e4�h7kh�"�.�n������e$�ol�2�L�ax�eH�i`�lx�o�u\Pl$od�rH��X��\�d�
�x
p�ab��dfTk��l��m��n�o�r4�t(����a���t��������h�����i��J�������a�v'�Ugp��e,�o��x3�����r\P<�aX�b�=	`��"���n��rd!�h�e��i��n��p��r��t�"�"7��d�"]�'.��eTm�0ui�t����d�$�d%}3�%�x%����e�&�D+�P�.L+���r+��a0�eP�s�+xD�rF
�+<�e0-� 4Md�o�:th>m|�e��o�H2�j
\h��m���a�b�c@�dx�e��f��g��h�i�Kj��k��l��m��n��ohp��r@�sd�t��u��v���܅�3�l�m�ix��3P��$�kh9m�D��,�ť��
4�a|�b��e��f��i��m��o�r(�s0�uP�v�Jyl�ØE�3����.1d��i��n��t�Fp�����PZ
�2��k��r�^c4acpgn#ď����k�m�j��eqY����tl��"ؐ�@�nH�s�4�E��\�eL��<�̎��d����a��f��k@
l��s��v���#T�Yh�������-t
f���3��lԖ����a��r�ou����/ܚ%�A �n�����eg0�lD�nd�s4�{L����((�dH�:
���<�dT�i�������J\�kt�o�����d���	|�i��j��k��lP<n��o��r�s��t���3���3`��|�H	��3�O��e ��ȫ] �ad�e��i��l��o�sD�tP�uX�v�yl�Ü�<�kP�n\�r\�(	@�"4�s�a�3����H�e���
��2T.�Md��i�<k��m��n0�T��3Lu?
�����g�c�"��k�Nsd=t�Nv̱i����e��o���3 �����m��"��k�v���<��HOi,�j4�k<�t�����<�����OrD��d�iT����������=����@��	���3��J��o�.���3��t��ad�����g�?k��l��n�o(�sd�t��y�8i�gd8��eD�h�y��"@Rm�r,�(H����t �i<�mL�pP9	8��3,QXD�l��ix�k��X�e��o8@r\W�Z(`�����m��(X���t�i��s��x����.��alAd$�e��i��l��n��o��s��u�y����4�"��b�d�lPAn��\����adUgL�i�Ak�%lh�nt�s��td����(tT�e`���\�s��*�s��Z��W��b�k��n���3x��
��Зeh��34�i��J��s����kX�i<�*�d��J��n�s�T�(��
�8���,���~�,��8�k���3����
�.|�a��hXWj��k��l��o��p��s��t4�u�Xy<��4�X��|���e,���eP�t�������[������.��a��et�i��o�rHXu�����W.\�a
����o$��|��3���(X����������lE�����H�b��p��r����
P�a��e��i\Yj��o��rtYuX�v`�yh����3P�(����|gik4YlDYp��"�l@�t�"��n���w��a�e0�i<�uL������n�r �s������4���(�kH�(�!�3�!��D��\��3�������x��LF����� 
��s@��3��-��a<������n,��HZ�G�J`��,������<��$�aL�ep�i��r��u��*�a0�t��r<�v���<�(�a@	�PJ�	D�kd�s��4��\�s�T.��b��t��	4T���rdJ�&4�&����s#X��u-4-���s-����n,��n��s�-".�-����e@0S�a(�i4���/T.�Jx.G�����sP�e\�u�y���a���T.����a��c<�d��e��f��g�Kh�Ki��j��kL�l��m��n��o�p��s�tl�ux�vT�M��l��n��r�\v��������e��p��� 4.H�Z�h88Z8���l�7���e4����(��Е��X�i`�jh�sp�t0��@��X��Vt�N�#4Ě��x�eܙ]��lp�r��s<�D,���]3��]��f��K��ax�]��rH�% p�]��h�j�^o$�r8�sD�up�?t�]�'.��n ���o��i����0�a��T����at�e��i��s���Z��i�h�t4�v�|*4l�
��nį$�������v\�J�a��o��pt����04$��T.�rи����a�e@�nd�s��u��v��y����WT.,�r [
p���$�s��54���8�hT�s\�t�<48�C4��t�p|�t��J4��x��i�����;��
<����v�����n������a��eH�hT�i\�kx�l��m��s��t\by��8�(��2�kNl���D�P4L��������tht�(�e`��0�gp�M<�a��W4l�]h�od�����	��8p�a��e,�@���;����k��p��]4T�\������k������a��e��rOu4�a4��(D����aX�p������a0�e8�j@�oH�rX�tD�2�����|�h4�����tP�s�i<�Md�rT�J���(�������������.��n��r������a��e��i<�oX�up��������.��nX�n4\�i��.�(�n�&w���n4�r��t�aX����g�&w��(�
��XD�e���L�k���#��s���d����f����|�t ~��o��r��u���X2��T.�?LT<���o�R��īl�\���p�������]��a$�e��i��j��y �È]
�g,_��Xka�k	T�fP�i��k`fl8�md�rp�s��t��vhp��De�v�v��\�a�x��|Se��k��vz��{t4�{��̵a~���.�}M��e<����i\�]��a$�P.п]��e�����s��.������e����p,�y�~���������\�aT�e`�i��n��u@�����T�i��k��lԹm��n��r0�t��z4(�����l<����ep�".���4��l��t��o(�����d0j�����i��oL���W3��n���	T�
\�]�ah�]�k�W�kX�t$�il������@���<��
H�d��g��i��k��l��n��r��s��t0�u��J�J�����]�u��������iSu���� �t��u��(��J��n@�-��t���d����.ܶa�i�r���4��w�a(�e�3�����*������8�������%��nh�T�a�\b��d��e��k��s���4<�(l����]��n���\�i������k��o��t��i��2��l�2<�_��.���a,���lL(�e��i�s\�WeT�i`�lp�r��s��t��v���JL�dl������h�a�'���|�el�k��t�!�!����a��i���4����eؾ�
d"����.��e�";�0�,��p�M
�Ht��k�e\�ap�b��d��e��f�gȩhH�kd�l��m��nH�p`�r��s��t��u��v��w�enh�r�f]�g���ha�js��m�i|�e��i��y(P*ki.l�pl:
��n�l�T.Pn����f��o�h�.\����i�o"��bpqtXp]�i$�l,�o4�sPr��rihs�4 v��.<v��<�a\�e�4i�v��z���a|�e��t��y�|�T.��s ~�4�
Ԃ2����4.��fkr��s0�
���ȇ������t���Ĉ]�e�i�o �s@�tP�����a���s�]L�4�t�RW���,�e����Jܔ�T.��r`���	P�a��d��e��i�kL�l�o0�st�ä�(c.\�I0����e��l��r4e�4�	\����s��=<�"T.��d��e����4&�8���l��(�k �m88( ��t��(�iL�kd�p(E�<���D�j\�ytF����ܡq
ȡ��l��4��	���n8�]��e��k��o��tL�Al��ܺ
������b�����a������pp�]��a�et�o|�r��s��tP�T.8�gD�iT�k`�rl�s���4D���0�e|�"���4����L�l�����. �SX�i�C��e8�
��_��a��v���4|#���d\#����g����)���a��������r��������a8��3̵�T�al�e��i��l4�nD�o��p��r��s@�up�y��ä������<�a`�t��D�np�L�a��8�e��l��n��p��r`�s��t��u��y�"��tD�t��o`�)p�����a$�U��4�O.����e\�"��t������a�e4�fT�i��$�s��s���4d�0p�,�aX��d�]@�n8�RH�e��=	t�t��t���l�e��i$�W��L�����t,��	l��������d��n�@$���el�����j8����.�n����a�e(�ih��4����.��
��] �n��	�g��<�b`�e��g��k��p`�W
t�t�m�m��l�a���4����J��p�n��d�8l�����d@����a��e��o��*�Ua�����p��e�p�t��$	k��Z��� �[.�[t�e����(�g�4�nT�r\�t���p����'.��H���h�s���t������|������h�.���8����o��u$�ô:]L�a�e04����s���V&��.|V����eS����t�\�\���e<\���d@=��<�����a�4`��4�r�Bt��a��c��e��i�j�kL�l�*m��n�o�s,�t@�u|�v��y���4b���'.��k��nxdhi��g����t�ts]��utn��i��l�m�n �p,�r8�sH�tq�tu(v���vg��4�w��lx��l�vz��|�4�{��|�j@�nD�'�O��X�e���`�r��s�|"l�a��J8�"���e��n��"������g0En`�����a����wb��kT����a�i$�o0�u��4А�g@���np�4d���`�r(���*����8�n��@�ax�e��u��y��)����d�a,�l�g���4h����a��e���4<�������e�yo������4���������m�r0�v�5ܦ���s�nl����i��C�����zl��� �aP�eh�i��o�r@�.H�H�.`�a��i*��]�lx�m��*�����p��r��3	��p���������d@�5(�(��d�e������n���a��e�i$�uH�5��3	�����d$�f��n��D����k�(�\�	����,�n����4�nX�tp$��P�ap�e`r��qh�nt��H�a<|e�Z�h�]��d����n4���Jk�r�yl~���������3	���<��a�b$�e��i�o$�r4�v`����2P�5�e���\���e�)gH�k`�l|�s��@���@�sX�vx�5��(T�(���h�u��p�t@\���a��d�e�fT�k�n��%5�*��a�i�n��.H
|���i�+5�r`���������i��7t47,�eL�i|8(8��D�n>�����X���?d�a��b��e�g�h�k �l��m��n �rT�s�t�@�4E���l�r�slE2�E�	`F�HJ�l�H;�J�xJM�e4L��a�e�L15�L75PP��,a<�eT�l|�txy�P$�gL�s�Q��Rm`�s�S���Th�v�T�p�i�V���i�Ts�W��Y��Dac�e�g�i�k�n�t[�gs�[G
�eP3��[��vd\C$]�\���sH^|,^�rla��lHa-e<�nD�sL�t�e��f��g{
i]l�e��k�t��øi"T.|�s�j'4~(�o�����dk���e�=5H�����a�k"��l�m�a���m���n<pJ��a$�s8�tpR.$f��e���a\#���l�u��a�v) 4vt0�r@�l�a��e��iP{C5�z��X�tz��`�k�l��r��\~����i<��܂����s������gH��������e�s�.�����e �]@��Ya �g4�k��r�s�t����a����gt�2�t,�e`�kl�lt�r��(��L�sh�]T�e ���
Ļ]��n��r���|�aH.e��k�u���H��@�
��e0��Ŀ;��z�a̋e�o�t���0����0e�t��gL�"�s<��0�l(��.0���$�a(��
t�b��d��e�k��nDodr�s�t�y��~��Ml�l�V�X���s�����e��I5��n���������s�#������aP����k�nL�o�N5x�t�����ae8s��T.s��\(t�!�31��0k|��l��|�Lexr����Tk�s�ô'9G ���m��2������,���t\e��H�a�e�tD�T5(����s����0�eX��r�����da s,�����r����.t��4�.`a|e�g�k�l@nTppr�s�t\����Y5���hh��%pn0��. �M�a�e��.x�T.�s������a�e������r�T.dimL�����p����4�WLe�(s����4e�R����gaԎehl����d�a�e�o��u�Zt����r���8t�k,���a�f�r��(�"l�*����� ��a�b4d�f�gh1h0i<k�lTm�np	o�	p�	r s�tdu�v���a,
b,c�d\e�$f�%g�,h�,i�3jD4kd8l�9mxn<Bo\Gp��q�Gr�Hs<Ut$]uT_v`w$`ybzD$��:���s����nP��
rt�����o�y��a5����r����t�&
|�1dPm���$eXihots|v(P-؛K@���`p�d��2��-�f�o�r�tl��Ԡ�����r��D�AС��8Fa�e�sü�2�n�ct�V�(m��l���8r�������S	0���(r�J\ktll�r|s�t�v|�
la�op�
X�j5����a(�e����'�axIi����.��p5��T5����gr0����a,e�g�i�l�o�stu y0������ev �u5\���"$gPiXkhp�stt`uv�����(��(��`oԵ���t<���e\�.��(���lԷ����g���a��P�����p���������D�pغ}5Ļ�ؼ*����r��w)Խp���@�(����Lr���5@����.�e�o�s��\�h@���pe��xr�s@��l��5����������8���h�.�aef0g<kHldnpo8	sh	y����l������5������������t(rl��,�e\��
Te�8� �����\e(�;L��aP;bt;d�;e=gL=hd=i�=k�=l>o�>r�>sPAt�Au�Av�AyB���:aĹe�:i�:k�:l	s�:t�L:d`:ft:i�:k�:l�:m�n�:r;s ;t(;v8	?
�(��$	n�t,	iT	k`	tD��(���L	j��9��"���l�i�	sx���x	e�	s���������T.�	aHCb�	d
e<
i�
k�
m�
s�
t�
v�
���(���	p��H���	r��dCa
i 
k`�+t���;4](
n��"0
e`
k��5���L
k�]T
s,��5�2l
v��t
i���5����
�$���a�
e�
�x�;0���
t�H'H���
e��G $
a�ih�A��i8����
��i���WrP�s8���a@hPidkpt�u��� ��5�Hk�X����\i��E|a���������	a�e�l�or s8tTy��(��k�m$����5��������mr���e���i$�,�a����p����kx�t,aHe�}al�D�d@���\k|s�l�����tpX���
�<a�d�f�g�h<yi�k�l��n
o
r
s$
tH��"x�B���5D��(��5l�5
eЖ(���s���5 �2�_L
a�
e�
o�
u�
y �\�P�dp
s@	�	\
r	��d
e$,���|
sx
�
n�"i�"�
td!��
e�F+��
f�
t�t�5@.���
r0��.�
r�1�1���
ed1�	ltun ��� 4MHaXeph�o�y�3�06�5��Pl8*|i�7��de8�5p�
�:���t�=h>t�a�d�e�iTjlk�l�o\rs�t�uvy���>(4�.>
�a(b4d<fDg`kll�r�s�t���!�>�� i,?(|?�	�?�5Pst�tt@�D@��Xs�@*�f�l�s(�g	��"	4A�5C��0�k�t�C�5HD&�C���k�D�xD��l�D���a|F	�Hh�.,at�b4dDfPidk|l�m�n<8o�p r�s0I�JJ����J-<i(K"\n�KW�Kttlh�o�L�(PM��e�i�s�MT.\N"��k���O!�kPdt(PJp�.�o�PiDQ���.�es`�x�Q�rHRC��8T"b<s�S]aDeXidkxl�T�TL�id�hUPm�U�5�����g��laLW��VM�i�t�Gu�W�5����G�D��[4�e�n�st���
�_���t�a:Ta���k��e<����i�rxb��o4t��;�bt,a�c*�c�@rlc��Hedu\d�di|f(�etm�s�v4e6|e�x��ĮeLt�f%\h�ia�b�kmo؏pr<t�h�5j�&�i���kk�5�j���sd��(lJ$g,m�l�
�l1P��m]4en��8�etm8jn�mHa�e�ji�o�n8�p�o��|k�r�s�tH	��p�	�a���/�
��n|
���e4q6q���t�r��t(Pt���g,k4mt��aTe�j�k�lpt�t20u�|Pbujv]@v�uHl|n�rtv�Pvthe`v��pd�v(�wX�w�ex����j�u���(����l�x8T�a�e(}n
�����t�x�t�`�y��u�z�	(g0zeDrܽ��������0kp{8eTy4����d�\.4���da�|�pk�||at~�f�i�n�p�s�K2<���s�(�g�[��e�J|�4�M�ik��A�A��Zg(k0s��4��<p�^	ȃ��klTy�q
��apb�c�d�efg�h�i�Kj�kll�m<n`o�p�r�sd"tX#u�#v�#y�#�܅��e�g�l m<r`shv��̆x������epsĺ�غx�6��	6��(n,�0aTe���H�]Ln����J ���b�e�lt�,+���tWD�X�eh9�
P�]�k����ľ��
�ad e<gDi\o�r�s�mu�ð@	\��i�^6�Gk��dmb4glJl����Z��T.Ts0����|v0<6�m��hr�"pe�6�e�l���t0za̎����������<����r�������g�i8
k�r(���!6��-,�f�JtԖJ<aPl�rT6'66](i��"0t�"���Hatepz/6,���`p��hs��%�u$axܚ?�J�dLg�n�rl�J�a�A-��"�n�"�i��56���d�JalP<n$rXsd�tL�W�.`��(�;68iP?�p�n0n�C��DrĨ��Le��ȫ��	da�d�eiHlTs\u�Oy�Àh24�t�e���Md�g�<i=m�o��j����g$I�!�~�"����x��=���d��"TNd(g�Nsd=t�Nv$�9"x�n a�( ���4v��<o<�PD��@���Up��@6��q
ps�������a�b�e�ijknp>o`tu�>y,Q�	$�c�'�l�r�s��@�x���e���<�Y�����
$eļ�-u.4l`�H6d�*pa�d�e�gh,Ri(sHtSu`@y���@�|tp���u.��n�t�e�rt�M6�4
�d�Qm�s�t�Qvt�*�l� T6d"�a�"����T.��t�a�,� t��_e�,\6��t$}e,Wp��4i��<eXrt�d6�i�.tr|s������
���eT�i��x���
�.�a�b�e\fhg�i�l�o stu�ytV�4�"�dP�p��k6��
�Ta@�d�AfUg�Ak,nUp8s�wt�wvTq6`���$t����s�uX�y6d�Hrp�Pa���6xa�e2��2���6���b�k$&t�v8�c��)���
��a@����J�b�c�fk$xmtv85�h�����`��l��	=���.<eDi`xp`tD�
D�$�-0�LnD��TeXTn��Jll,Vn��r<�d/4��T.�r�����a�Vc�eL ih j� k� l(!m4!nT!o��p|!s�!t$Eu"v("y8"����
� k r, t@ v�����t<��6$���$ s�}��}-8 a��2\ nt ���twd\�D�e1p��p m� r|���x a� e� iPj� o� r� u(��6��Q���� n�6����� s������6��,�q)!i!�,�a���6����!�$�t��M !i$��H!�(��6����@!����`!t��2��
���h!m��p!a�!t`�(,���!u�����W.�!a�!e�!o"rHXuX�ȼ��!.�����!s#��5.���!.\�"r������6��� "a���H�"�������X"�0"�L"�����{y`���Er����
ؾ.�"a�"e�"i\Yj�"o�"r#s#ttYuL}v|YyL#ä���"k�El���L�*�"s�����.��;�"d��t��6�"m�x��6`��#p�'���0�t#a4#e���,#l���@k����@#��-h#rp#s��@�i���.��Mx#a�#e�#i�#n�~r�����.�#nX��60���#t��������#k�8s0�,+�;L���#t,��$�G��),�`$bh$dp$fx$h�$l�$m�$o�$p�$r�$v�$�`���d�b�$�TG�hb�������#����#�� � (�#0I`���6����$�$�$a$%o�%�8�%s���$l��$l%n`��6LO���%tH
%aD%bT%l�mh%r�%s\(����L%e�����`%a�%e��X�.t-x%n "��.��.�%s�.���%e�.��%d�%y����%����d�����%e@0t<&a'd\'e((f4(g<(ih(jt(l�(n�(o|)r�*s,uX,vl,y�,��0b�/4&dd&fl&gt&k�&n�&r�&t�&vX1(Сp1��2��2t|&.�&a�2���&d�&k���63�6�3���Fm�&t�&u�4��.�&a$
�
�4�6��o�6��0�e'i���`7�"$'e@'r8(�7'd8'l;p8(�ma\:�0:H'a�8P'd�'l�'n�'r�'sp�t(vh<���Ga�>n�'a�'e�'g@�oX�u�>�6 ?h�?�6�@n�'e�'uTU�*xA�'t<�� CJ�'ntC;(lDa`F'%�.����(�(G(�HG�J�dt��6,P�H(g�O�P(n�O\(e`QM�(e�(u�R�,R��(m8U��V�8Z?�(d�Y	�(d�(e�(k�(m)n )p,)rT)s`)tXZDpl��Z�	,[Y	)et[��[]��d@���[")e\J<)dD)kD\8��
�\��\]L)p�\�@]��h�.�)d�)n]Kh)a�)e,*i<*ox*Ì]����.�]��]���)d�^i*�^���)a�)g�)n�pr*s*t*vT_*�_*�)d �,`��_��*t<`*`%
|`8P`��$*d��n�`����.T*a\*ed*s�(�	�5�a�6<b��آd�a��l*��bX.db#�*a�*e�*j�*k+l`+mt+n�+t0d]@v�c�*l�*m�*st�(�d(�e]�e]�*j�f$D��,g+gg+e<+�8��
���(+y����0+�p��hlD���H+��gMT+��g2�gMl+e�i�$+p�h���+e�+r�+y�+Äkd"�k���+dk�+i�k|�e�k��+kl��+rlFl��+r(l���+�TZ��o��,d�n,n8,r p���'.�o��,,u��x\q��D,s�pL,i� pqd,e8r��,yG����x,�trq
�s_�,a�,edx��s��,t�NpX�,s�y���,t��D-a�-b�-c�-dL.e�.f�.g�Kh�.j�.k(/lX/m�/nd0o�0p�0r,1sD2t3uP3v�3�t�T���<-kT-n���t-d����`-p�th-et������-l�-y�-��� ��(��d<*H����-r��'Е���-a�-e .o(.r0.s.�Ж(.eX��-l�Mh�����.���t����7<.t`�7H��ܙ]D.gd.ll.nt.rĚl�
7T���h�.�.i�.s��t̜p�m��7,�]�.r8QN	ءM�.a�.ox�]�.l ��H���2�]�.e�^j/o/r/vT�5 �7ث�l��T��� /i8/uȴ�4���W@/rl/s\���H/at/o|/p@�7t����#7���$���/lи��	�/a�_d��f�/g0j0nT0s\0tx`v|���t�/o�/r�/s��̾�/e0t�*K�+I��0e$�����	40b<0hD0sL0tP;)7��)7�078�77������<���.�0n�0p�s�0v\���0sL�������5L�(h�.�0s@��0t�����0e�0l�Ms�>7���|�i���1k�$����0l���0et����1m؊t����1e\1ip1k�1o�1s�1t42u<2yd�i��T1m��"l���h1j�1od����D7�����1l�1n|�J7@�n�1e��6ȼ������1s�1t�����1a2i2r 2y(2��e ������1k2l��*D�.	������P���=����l2ex2j�2o�2t�2u�2y�2z3�D���d��Y���2g�2sH�P7�����t�2r�2s����2k��t���82�:�;� ���2e(���2r����2�l��
<�M3b43m@3t���|���,3f<pd,��T���H3ah3e|3o�3s��t3iT�U7p����� �)�����3����3a�3e��?�����3h�3v@���J�3e\���3d4e4i4s 4t����. �X�?��-d�.p�Jؾ.44r<4t��Z7�� ]�4a�4e�5i�dj(6k<6l�6n�6o`7r�7s�7t�7u8vL8yT8���4k�4l�4u�J��s$�h���4l�
J���8�4o�	�4d5e5h(5i<5l\5nt5r�5s�5tP
�
�5r�
��]�
J 5e$�`��45sL5t��+|�*p��T5hl5o�!���4�f�5sD�q����5tH}3�t���5�����5ô�5d�5e�5n(8_7�5rP*T/����5d6f6n 6thd7��-Hj7l#�H,�d6n ,06ax6i�6o�6u�6y�6üa�a��\6g��H0.p6s��*�.���6k�.�/2|/p7�/���6��6�0��0m`�u�6y�1nX22�6f7k,7lP7tX7vPn(7i�ov7Dy��4��7sd5�6@5��$7eD7lx��5<7a0;
�;T<�t7a`=��<��l7v4B�TK��@�k�7l K�7a�7i�7s�K�N(xN�7n$Q2�P��7k�Rel�7r8tPd�8eLU���7i�d��V���eW��#e88iD8ox���X��08t$Y�Y����������]�8a�8e9i9j(9uP9yt9�Ta�`at�8a�8sa���8d�]���8n8�t���8e�ki@fi�8m\s�.ds���8a��T.��]�8n<�9e\�2d�x���� 9e��.T���49e`9i���@9d�Us�t���Pp�~���9�h9��������9a�9i�9o4:u@:�<����Ke���9l��zh��9k�9l�VnL���.��]�9e��9sT�A �n:k�j:kT�]:il�(:s���� h�4J�'.�{7�-X:t(�0��l:r<�������:a�:g�6��T���:e����<�7H�7�����:a�	���.;i<
P ��;i�k�J�J4;l��7�
��
�<;e,
_D;oh;u�+\�
�`;e���;�D��Hr����;����.�;p\�;a<b1d�We4<f<<iL<k�WlX<n�<r�<s�<t��v=y��X�
X�P�;rp��;a <e,<u�5��<r�
?�"�R�JD<e<�	p<a|<o|ph<t��	������<.�<e�<o��%l�H���H!.�<j�<l�<t$��h �$��7� q)�<a�!�ؾ�d"���<.��e�#>�%�$��.���=eT���(=r���4=��,_@=��-(�=e�,X=d�=e�=m�=n�=s�-aL.�X/J���7�/���=f,1��=j�=tD���1�D4/d8��e�=�P����eh9��=pt9���=�<BT.(>f0>m�p`>r|>v�CpDD�<>s�D�7T>y��]��a��H>n�DQl>d�DmDGi0GMt>e�G�7�dLI���>aI���>k�Pm�>n?r�Ht�>a?e�?i�?j�?k@o4@t�@uAv Ay,A�,h��I���>dJ2K�|J?g<?iH?mX?nd?sx?t,K�7K��4?gt��w�&TK��P?k�K���.L��l?e���7|L���?a�L�?d�?n�?s�L�,M�8M�
@M���?kPM�?aDf3	�M��?n�M���?a@l�����'@iب-(�P��@p,@rQ2�Q�\@a�@e�@i�@o�@r�{vܺ�
�Q��T@bl@lx��<��
t�(t@t�R|@l�@n�@p�R(�R�S��R�@nS���Qp���
0S�@e�@o�������S�@l�S2�SXAaAo���T��n�T��DA�l����5 U��<Ar<U-xAe�AoY".�X�`Ae�VlAs]�Aa�Z���Ag�]M$]�Ai�An�AtH�:<^"�Ag�^"�_iD,sT_�Aia"$`�An�thb�$�k,BvD$��4B�B� d��d�e�Ba�BbCcHCdTCe�Cf�Cg�Ci�CkDlDDm�Dno�Dp�Dr�Fs�Ft(Gu0Gv�e]��g�Bk�Bn�BrLfP����fx�Be�f����.�gJ�Be�BlCr�xhP�Bl�h)Ce�hii�7�:i�i�Co�j;k](Cnk0Ce�i<Cipl:
�"flCit3ktCn�l��l��^tPnJ��f�Ct,p�7�rn
�rK�CaXp]�Cr�t����d�Cn�u2<vJ�4i�Cr�Cs�x�(`yP.Dy���CeDi�y�7�|��z��De<�j<�l�(Dd`DkhDl���0DipDjxDm�Ds�Dy��
L�( ��(��7�-t�tL�W
Ĉ]�Ds�Dt��O��J��pܔP`ln@/r��s`���	�Da�DdpEe�Eg�Ei�Ek�Em,Fs`F�0���Ya0Ee<EnDErLEs\EìQ/���Ed\�$EnĖ�7�� �V��(���TE����\�-hEe�Ek�%n,0t�*����Ee���)�Es���/���<��Ee�Et�$8�
�ElH,&�Ea�Ee�%��� FlX-�h^sd-��F�p-�F�t��	LFk�o�0��8F.<���@Fa��*ȡ��XF�(���lFlt�s`&t8�]tFe\mp�Ft�Fy|�*����Fj�FrЧܧ�Fa���p�]�fa�Fe Gs�;yP�T.GiGkGll�s|�O���
�(����(��MDGeLGiȳn@�2��%H��̵�pGe�Go�G��i|��7\�]xGa���Gt��������Gkt����G�8�m�GaHe\HitHo�Hy�H����Gn�Gs��������Ga�����a��2 Hb(HiHHnTHo����(e<��84Hm���<He��7���.T54Te04��hHd�Ht�;"Z��`?�Aa`���Hd@=���H��BtIaPJc|Je0Lf<Lh�LiPMj�MkdOlPmlPn�Po,Qp�Qs�Qt�Su�Sv�Ty�T�4b����.4Id<IgLIk�Il�InJr<JtcP�c�4e�xd��DIkdIrlIs�Iv\e�7le*�te�;��&$fR�e���Ia��d�Ipĺ�7|h�7�g���Ie�f�In�Is��t�hb&Di�i�"�Ie�Ii��������I.j�In�i��Je$Jm,Jtlj�7�jT6�k�4Ji�B��B��HJh`Jo�m*�J�n�hJltnpJa�Jd�Je�JfKgKi<KkHKlt�mTKn�Kr�KsLt<�u`oJ�Jd�Je�o��o��o��Ji�Jr p�8p6Hp?lpJ<�eqEq��Kdl�e,Kg�q �r�r��4Kss] �vv���.lKe�Ki�Ks�v"��.r�Ks�Qt�v1�v�w�7x��d�.�KaP�iTx;z���Ca�Ki�Kk�Kl�KpLtLuLv�z��z�({aP{�7|{��{x8�{����e<�s�^	�~(Lr��PLaXLe`Li����� �iԂ8܂hL.����pLe�|Ld�Le�LnMr,Ms$����P�Lt8�]�Ln�LrT����.��O�����Ld�Lg0EnMt��8��\0��Le����]Ma��>,�]$Mk��*`���8MkdMv�@MalMe�Mo���ԉ�l�f��8D�dxMd���Mr���Dfn�MuT���	�Ma�Me(NiLNj�No�xr�Nu�xvOyD���
���Mk<�hH����MeЎ"�MlNt�;���Ne��
@�� Nm<NrDNs4��'`����X�|atNe�N�|��
�!J`Ne���hNlLxn(�	4����N�4�2��x�Nsd����Ngīr�P���Nt(����Ne�Ng�NlOm�S�ؖ8�����Ni������. Oe0Os�!80�'8 �S	(Os�a�����<Og����DOn�Or�Os��POa�Oe�Oi�gy�O���.8���Or�����Oaԙ�,�D�g�OkDgr\rx	����Ot���x��OdX�������O�$�2��M$Pa�Oi0Po@PuT+�$��̞Pl�������8PrPPsT�S	�	(	ġXPr��M`Pa�Pe���
�|Ps�68�����Pd�Pf�Pk�Pl�PnQrQv8��n��p�G
8�e@�n�Pd�;8�]�Psl����Piܪ"Qn��,+��,�r@�X QaXQepQr�Qu����;DQsH�LQlhQt���x���Qo��B8\�I8|Qs��"��Qr��
�he�QðO8����Q���2�����Q.$RgRk4RmDRn\RrtRu����Qa�Re�RiL{jSo0SrhSuT��,Re@��|�'л���T8���<Rk��XhRa����PRt����.��(���H�|Rd�Rit�l|�m�Rn�Rp\��x���|�k��������Rk�RlSnSs�[8����Rd�RfSs|b8P����(8�h8�����.����(SuTS���n8|�v8@Syh���HS����X���`Se�Sk�Sr�St(4
"���&��}8���Sk�Sn���H�G
�����Sg}��|���Sg�����SnTrt�X�SaTeHTi�Tu�T�4���0�a��� Tg0Trx�5.����(Td@Tn@����dTlxTn�Ts�$��\Tl4�G
@���pTd���,�W���l����T���x���Td�|t���4���Tn�Tv�Tyl~�� U��T����	�������Ta��2+D]n���Ue`���Up0Us���4�.<��Ua�VeHYg\Yil*l�Zn�Zo0[rX\st\t�\u�\v�\y]�����	|Ub�Ug�UlVnLp4VrXVsxVt�Vv�C����Ur��(p����Un8����Ua�Ul$��@��
x�]�U.<����UaVd$)l$Vv��?
l����L����,VtLVv���h�DVi`�tlVi��a
��dVa���*�J�Vi��r�Vs 7����Vt\��Va��eWfWg,Wi\Wk�Wl�WmXn$XoLXpXXr�XsYt,Yu<YvT����%���D���We$Wihjn���H�J<WnTWs������iLWs�����@�����apWn�Ws���8<�����xWe��<�g�����We�Wi�Wl�Ws�Wt��"�rg���
��<��8x�J�xa�b�Wi(�(��Ws������.XaD�e��h86s���8��i8Xr������0Xi���$��DXp���.xXe�Xi�Xs����pXk�Xs��
�����XtX Y�����Xk��Xs�X��"���.�"���Xa�"�Xr�"���X�D����M.Ye��c@�"Yl���(*aXNe��	8�h �-4YaD��7@M�Yt�PYaP�d�Ye<�g�Yk�Yl(ZmDZn��o��plZsd�����m�Ym�� ��Yp0]�Ya�Yi�Yl��<��8�Yi0.�`���YeZo��2T�8�Z.4Zs���Ze@����|��<Zd`Ze�id"�WXZtp	����a�Zt�
�8�
��|Zi�������Za�Zb�Zm�Op�Zr[u$PvDJ����Of�Zn�Zs��,�
���p�.[e[g�8�B�JX���[n�$[aX[e�[i�[o\u8\�X
$��P[pl[r|[s���;�dt[i�[kD���!���[eD!���[rL_�[a�d�[j�8L���[n�7t�7�[i�J�[lP���[l\m�Y� Y@ ��\lP"�!��$\y�!��,\�|&&0&��D\j#�L\k�4ll\t@(P�*��3�d3��|\b�\e�\l�\n�\r(Rt4\\4x�4��5�8L5n�\o478�a�8����e�\rľ��9���\s�>��4�.>��]r����]��?T.t]a�]f�]g�]h�]i�]k^l0^m<^nH^oP^pl^r�^s�^tH_uP@2l?��l]n�]v�@��G��]s�F���]t�G�HJ�]l�H�H��]axJg
�K&�K���]e�J���]t4L��T�8PP���]t^u ^y U��U
`W��V��(^e�Y"H�g�^XP_\^l`e�b�la��d^e�h(�'.i��x^a�^e�^k�^l��s�^tpj��i"�^n�^r�j��k�	dk���^ll��mt�^a�m2<pJ_a_e�plp
�q�. _r�rE,_i��x�x��4_e�wM<_m@�p_a�_e�_i`u`�z���l�_n�|r�|���_d������_e\����_l�����_r�_s�_��i�_e�_k���H�]�_t��]L�a`e�i�2$���l� ��@�t`a�`d�`e�`g�`h�`kalan ao(ap4arhas�at�av�a�,����'.���h`r�`s���D��`t<�J�He�h?f�(�`e��i��̰M�`e�t,�e�`laoP��	 ��`e|��,����/oh�J��d�(Ⱥ(�Ua����0aHae`au�����.Xav`�Ŀ���|ae�ak�at8��`�]�:�auX�x�20����ae��rX�-�aa�aex���.�akp�������.\�5H����a���M<�(br����.<bn\bs����ba8�����4bd�8����Hb.����Pbt(��
�bdce cfHcgTck/ltcm�cp�cr�cs�ct dv<dy�]����ba�bd�bscu@��8�beL� �2����bk�bt\{�0z�bo���t�]cn���8���0cl8crD��#�������@crP�����odcsP��h���lco�cuh� �����
�����ca�ce��y��X��cd,����e�ck�cl����X�
��(�cedrdt ����n
dr��t����d0de������r���Td.ddapdhxds�
30�9����\dn̰����n.t����ra�dd�de�dj�dkel,emPen\epter|es���de1����dd��,��%�dn``tH� ��8���ea ee��]�S��ev��Ja��.��t8ee����Ded��hen���sa��s���ea�ee(�i4�K �]Dfc��fLfk�fl�fm�fn�fp�fr`gtlgutgv���ea�gb�ic�idplePnfXpg�th�ti�uj<vk�zl�mĈnd�o��pX�q`�r8�sp�t�u��v8�wp�x��y��zԴ�|��xpfkd���Чap�Xfn|�]dfa�fu<����	0����flh�t@��8�x�dx��8��H���fe�fi�����fdge<giDgm0gì_�4��fn4@��gggn����D�h0�v8���(g����$���������Lg�����Tg�@�X�P\��gk���|ga�gbhe�hi�hj�hlioirXispiuxiy�i�d(�]�gs���gkxD
�glhohrhu�q�T��x
P,haHhr�hs�
���4htp�<heXhs��lht�HiD��dha|he0�i��8\��heHS�{%`��ha�he�hi�ho����x.th�.�hn�t*pp�hg�� �d!Pia�!d'z
(ia4ie<ii�'�	@/r�'�(x	P*PDir$*Liehit�*�8+��."�il�/�d1� ���i��i�3�	 4��ik��y�9��Ws�9�irh9"�ieh> jadjd�jeki0kj@kk`ko�kr�kslulyHl��>>jb4jfDjt|?�x���D��<jt�F��itjl|js|FPje�jo0G(�GM�t�G�HPt�b�ji�jk�p�jrp8s�jt(K�K��h�o�ju M��S],�l�X���.�js`�O1(]P]]�jn�[ke$kg^��]]kllc�e��di8ko�j�1�j��Lkl\hTkmpkv�m��op�mxke�kÄs8�s���k�x2�kat��kk�kp�kt�w�	���8�y��ko0z�1�kod�(	\{���kl4��t~ls��T.$lb܁%$��ȃ�,lkXls`ly����hl�4l������<�\�
�lf�li�lk�ll�lm�ln`mr�ms�mt(nv��Z�(����lnĨZd����lsȫ���������lud��maT3emgHms��t@��'���tmd0mr8ms@mu0��8@��8�����4�.���	tmrx���Tme�mm�ms��
`+��|maX�R����mk��]�mi�mk̆���mp�mt�
|�����aĞe������mn�����manhni���8��p0�$�e��Mnn�"�.�ng�nr�ns�nv$��4na�ne<of�oi�ol�oo�orps,ptPpyo�������na���*�na��t���ne@	J�	�b��i�nr�ns�
�
���naD����nt�����o���8@��od�$on�]0oadoe�oi�or�ou�oyP(�\oktos�P|oa�od�p@||]�-t�"4p���oe$i",#�@�.ps#X�oa�epi�#�p%�
�'��a petn.t(
<peHpsH)`�l�*�$�-"@0]
�pa�pe8qgpqi<rjPrlxrn�ro�rrhss@ttttudt��/�.�pn�pr�pv3��2���pk(4��3���pe�6+�8�.��d�Giqnqt�>���.�>]�pa�E*0A4H]qa�G qrHG,qePqjXqs`qu$I;�IV4J�K��Jhqa�qe�qf�qg��k�ql�qm�qn�qors rvhKm�qv ���K��K
HLJxLJ�LJ�qgM�
XM'��x�N���qarr�M���qt�N�8lOJ|Pa
�O�(rr�O0re8Q�	`Q��Hra`ry�U�tWPX�dwl�VMhre�ri�roX�8�F�&�X�rs�Y�.@]t]K�ra�re si0syDs��^���.�rg�risrstT_8x_8	(4`9<`��stP`��ai�a��(sd�a3	�a��Ts�<s�<b��bZ�sndb#\sa��e�sk�sl�sp�st$typct�e�*jg�se,g�,h��sa hi�i9�h���se�si�sj�sotrlja|���j�k�tate�j�0k��li0tv��
9�m�0m8tr,9�r��tsLttG��Tt�����n�nDs9�s��te�tm�to�tÈppX�te�y���tt ����tad�xT.T�����t�T�����	�ta0udLue`ukpul|um�un�ur�us@��Е��(ui��rDutt�ܙ]��n�Xr`�s��(��T���hua\�и���.�ug�uo�us���`�������up��"���.�ua�ue�ui�����"��W��vevo\��.��T.��vf�vk�vn�vr�vs ��
 va�vep�jLwk�wl8xndxo�xrDysztPzu�zv�z�i*���xvt��9<���vi]�vk��*d
 9��vf�vlwnws$wv$%9`���vswvP*9p� �H��wk�?8$?
 $��,wd�#4wnl#]@wahwe�wj�wo�wu�$��wr�wsx)�%��xwaX&��'"d'X�we�(��h�.�wl�(��+(l+���wn ,'�waxexu,x�H,���.�wm�,��,.�.�xt���0��/��$x��0�Xxa� ��Dxa�0LxsX22.�xl�xm�xp�xr�xsX7v@5�t6��5��xs�xt�6/9�7i�7���xe�xp8��8�	08���xm:68�xeH:T<�ye(yu0>59x=��yo yt�>Y�@��B�B0yl4B��8ya`ye�yi�yt�yvCP�yk�m�ynl{r(�s�yt\C(�yrhr;9�C��-��D���ye�D��yd�����?a�HX�yiMr`It`J�H��yiTKizn K�ya zi(zoHzr�K	xN24Oi<zrP;�O��4zi@P��Rdzkxzl�zt4L�T.�S��lze�VZ�ze�V`HW�WX�za�zi�X*�[����y����z��z��\*�]��.8{a�{b$|d�|et~f�~g�~ij4k�lЀm��n�oȁp܁s4�t��uĂvԂyX~È]Wh{dtg|{n�{r(�tJ�^�T{d�^��\{eb�
a��t{l�{t8b:
�b��l�.�{b���HC��{exg�Pgi�{r�fm�{e�{o|u�"��g��{e|g|l�"��.�"]�{e$S�g@9�g;H|aT|e�|o�|r�|sTh�h@|n�h�'.�^mt|s؜t\i�5DiMl|t�i2�|v�mD9�iJ�|e\qIPj���|vj�|e�|i�ja�x.&kM�|r�j��|k�k"
<}aH}bP}dT�fX}ip}k�}l�}m�}n~r ~sL~t�Sv���
dl�(}s@l�0}k�l;mJhp��<gh}n8q�qJ|}i�qH9�rJ�}e�R.����}e�r��}gdsJp�.�}s(t�0u��.�Rs�Qt�t���}e�}i��opu5��xDw'�}d�v��~e��u�xJ4~t�z��z��,~aD~rP{k�{����.�~���������l;l~i�~j������]�~e�~ld�P<�"�~b�~en܃N9���~a��]��]�~n�~r �T9�����~t���`>e\��`���&ewkPt��Ps�(eXl`ohupvT��l���d�t��^	����x.�k�m��xa�e�i�od�st�u���H��X�(���.�d�l�n�sĴ(d���"��9 h����j0��n ����^g,�m4�s<�v��ؗ��(P�e��*p���H�r<�x8��\�t��v��]����p��s��y������|����YКY�J8���tԛ��s$�Āe��,����(�������L�"
T.<�aH�eP�fX�i��k`�ml�p|�s��v��Y98�]4�n��:
��(|��`���L�sP���[ �]t�.��e��f@�g���p�i����e0�(<���dؤ��e�
���ԁo�t�v�����m��a�id��l����kH��dD�pȯ$�eL�r����e\�ad�e(��8����x.��rx�t������l��n��rH����pUeķ����g�����i�����a��o��dT���������r��������.X�a�bl�e0�fP�g\�hl�i �j(�m��o��p؆r�sԇt$�uh�vt�y��Ü�"x�l��m��nЃr�s��v<�����d�i���(�����g��i��n��t�|�f<�_9��O���	����ȃs����� �����<����",��(�e<�iH�lX�od�u4�_7x� �t�(H�P4�s`��L"gx��P�d�e9��"��k��nȄr܄s�t�u��"��aL��t�����s������e��X ������s�)@���Ԅed����aL�(P��k�r�����k9�	��n��$�a�^	]K<�e�SD�r@�p9��-h�
d�a�\b��e��g��k̅n�o��r�s�u��|Kk�������]��k��&����ąe�n�s@�md����S�������0�j�t���l�-��M4�D�aX�e���0�:\�<�tH�(T�P�dp�t��"����h�r��u9@���|���"��f��p��v��������o��M�=̆aT�,�t��|9�e�o��u��d��d��dH�t@�.0�a<�eh�k��o��uȇ��e2����(�l��@�kL�tH�	��$���T�d���\�o��u������x�a0���l��(���m4�2@�����n���	��������m�a�e�i�v8�	�2�l|�S$��s��24�9	l�0�sT�nĢ�.����8�e���D�r$���P��D�\����d�r�d<����k��r��y����������dx��1,�L] �a��bԉd@�e؊f�g��i\�jd�kԌl�n�oL�s��t��u8����4����o��bH�cP�k`�nl�px�r��t��v|��<0����X�g�	(Cp�	���b��g��v`��	�
�����r�Q,
�(���r�tȉa�e�r4�sD� ]�e��r�2\�e$�i�j�w?�,�i\T.h�i|�l��m��r��s̊t��v�J 5e��l��t�e��lH�
�*��utHL e�����il�kĊt�!�9d"����e�$><&��.�%t
�a�d$�eH�iP�o\�r��s��u��y���'V\'�'.8�d@�sP'��'�9<(i�(�'.|)Ph�o<*-t�sd*�9�*?�*#|�j,]l,�x,��,������,	X�.�d�e�l�m �n8�o@�pP�s�-�	L.(/J��dL��X/J�i �t���
�/���f0�n0�d0'�0��1Y,1��H�o�3�D4��a��e��i��l��u�4I<5	�4��l�5I�,W<6��e�7i�nZ�8i��gd8ȌeL:���dx��a�i�yd=2�Ai<BT.,�r8�v�D��p�.0Gg
��e<L�HtD�ht�i��k��l�tT�u`�v|�ÈL`��s��v,M���x�M8��e�M+dO8��aPO��`kgF��+��a�e��ȍr�؍eT�J�u�R�t�Q����i4�rF(��(S� �m0S(�uh���S��@�d�SH�n�S�l�eT� U�T��t���UZ<U��a��e̎iԎo�r�V2��dĎn��(X�\Yi�Z���"k�r�Z�0[:^� $]�l�r �sl^i�^��^���khb�$P�yD$��\��,��Td�9<d�H�.�d�e]	��d��f��k؏l�m��pL�rd�sx�v�k��i��s<v���aďe v_wi�v��s8{�9�z��Џal�;����i�$o����a$�eD�i��v7�n�@��4�n<�t���0��`�PX�d0��P\a8��p�p��|��P̵���.��a@�e(�h0�ix�l��nؒo�p�r,�sL�t��"ؐb�k�n�r$�s0�u��(��J�������d4���n�����eH��d��(�t�jD�(X�n��8�a`�i��l��nؑr�t��M������h���p��D�t|�lp�d��a��dđeБt`���#�nԿ��t�L��J�.��a�o�t�'\�d/�rX���"4��L�d��a����D�ed�np�r��]|Kk���9���
P�sl���X�g����"&��a|�i��u8����n���9h�����d��T.�����aВe�������]
4�.�a<�eD�gL�ip�l��o��rȓuܓ�L��(�r4�s�X���� �t��"�����9l�T.`�ah�s����(���|�i��yT�c`��)h���e�����oX��0����p����$�d��]��n8��9 ���ԓ�@�\��e�u���t����C������p����.�] �a<�i�b��<�D�r��x8���T.ܔaܕb�c0�d\�e�f��g�h<�i8�k��l�mT�n��o`�pl�rt�sl�t�ul�v��yȡ�h�;4���Ĕd�r����
̔d�g�k$�ll�m|�n��p��t��uԕv���9��J$�,�d<�dD�gT�s`�t�����j$���غ�L�tĻ@�.h��	�������t�a��e��g��sh�56��G
��t��������t�(̕k<��'X�Qh�.�i�y|���P�t�.
�%xD ����a<��t��$�a\�e��iĖn�r �sP�y0InT�T�ap�l��p�x�S�9t�x�l��4���k��s��t(�x��e�a�4��l��9��0.(p��̖i����ؖgH��e(u�.0u���ePt���m���a4�e<�t����aP����a�D�r��-��aЗb�d�i��l�nl�o��r��sĘtDvD�&P���a��e(�]��r`���'�7d'���a��ėr��9����ܗi����a�d4�n
�.����a<�eT�k\�s0�(84�dL�t|���(���d�g�	B	�x�f��o|
���c��t�
�9�t�����ep
��
����aؘn�v$��9\��9h��a<�il�ài��.�*�e��r�����(�e$0�nH/�.�-H�e��T�r���`���9��.�x�a��e�i��s�'��2��r��s� ,�|u!ęe�̙nPؙe��#�kht\��g0�l��aPu�u��(�e�
l�at�b��e��g��kȚmКn��r�s�t�*��PX4e��t4]|�n��t��94hT���	�]��a����p�d�k4�n��s�t�����o�Y�a�P(!Y$�i\!�9Lm�#i�#0�aX�ed�o��s%	4$P�s�&i|�s|'.�'�p�e�'C�)U��a��e�B
�)����e�)����t�)i�+����g`+țn��p+ԛa�e �u4��|�<,�,�l���L.�n�j(�.��,���/(P/2@�r/]H�ax�e��o��t����r@0l�d��t�W
�1�D(2��l�3�04(0�a��eh�f�g�i�n��p�r(�s��t4�v6�|6��9t:W�dd8�:��a�:��:] �a�<��H�eD�n����=��=L�sX=T�e�>��A���aԝe �i<�kx�l��m��n��o��p�t`�uD�vP�y`��PB���tB��lv�LC̝n�r�t y
�C���k|�9�C����a�e8l"�n.D2�D�'�D�(�r�D��0�a\�eh�lp�u��9Ў"T�iTE�9F�	�FT�.�Ȫa(G���l4G�����GM���LG`XG�Оn�v�G�G:Ȟa�e�G:0�:H��G��aI(�H�n|H��eT��J��$�e�J��,�r�J�8�aK2LK�\K��X��|K���a�e<�ih�o��r��K��K����k��l��nğpПvL��Vd��: L����eLL���I�L��؟gXL�i�k�m�r�L(M*p��`M��f,�m��"	�K�$N4�iP�lX�s�Nm�N�h�`O��`�d��k��n��r(Gu�O:p@���e��xl�iP�� �s�O:��eܠØ"8�P��Ƞd�P��Р��R�r�R�����>8SM �dD�hX�m4�t�S�,�e�C�|V|D�aL�flV'�r�~��0�aXxT�r�W`�a��e��s�X�PX����g�Y�8Z(��a��n�Y:�Z�[��`�r@=��,�����ܡ�<\d�k�r�v��y]:�tP][ ^Q�^��d�[�a`��$�s�B]��aТb�e��f��h��iL�k�l@�md�nl�o��p�s�t0�vL��4b��b�	d��f��k��u�b��cJ�e�xd����u�k�
�l!:�l�
Ȣi��%:Hp-ܢitn�fP�g(�lt�m4�nP�sD�u���
�s�is]�ev]@�aPv)({z��H�l��ml�s���p{d�k��p�Dt�|�*:�����a��t�~��a �]̣s��M��iD�/:����Pԣe8�]ܣn�r��e(�nl��T����e��t������� �g�il�eЎ"4�iT���@�e|�i��l��oԤr�v(�A���@��t�l��Y��r���'��a��e�-�В��nd��Ĥp�7�0�����̤ip�q'��:�e�o�u,��,�2(���d$��
h���������$��̞��M8�aT�e\�oP�i��2����2��v���ܪJx�e��.@�X��a��eĥiإoH��.ĭ(�����l�(���ХrP�@�kإm$�n0�r<�v���ed�ix�k��l��pĦtܦv����vsP���P�i�}�	ć�̇��D�e��4:L�t��X�s@����p�i�j��o4����G�����X��a���x����oظ���rԦy���9:x�k��#�r�����d(�l�����aD�e|�i��oܧr�+�x��4�e��=:@�D:H�<�.`�gd�kt�l{oܽ(l�g���
��t���8.��a��epIn�s��D:lNJ:��M��n�n
�����s(������ȧn��Чa��e�o�u$�à�2���� �����p���h�����t�����4��8�yl~��d��@����`���\�r<�]
��aP�el�f��iȫj�nX�o�r��s��t��uȯy������dШl�n�r@�sp�;8���ȨaȾd�e��o�P:��h
�v���<�v(��<����e����L.0�g8�m`��
$��/`����x\�	H�d|�i��k�ll�n`�o��r �sH�tH�t��n��������t��{@�����i��l����g����aЩe��W0����ةm�v�����e�ll�T:d������n���a<�eH�iX�����4�k�����-����P��������d�a��d�00.0�t|�ex��"��m�����aȪe�o*s�u����'tتv���
<��(��	��������|���r(����e4�����t|��X��,��X�{���@�a\�od�s(F[:��a:�(�X�T9���x�t�m��r���e��k0��a��o +�g:�ثa�e�0
����r<���Wo��r���e8�oL���F�n.�F] �et,�t�d���D����.��a�Zb��e��fĬgԬiܬln��r�t[u�C�.�y�zn���f�n]��a�Or�(hJ�2x�l����a�d���3�K8�eT�id�ox��l�$��0�d��rL�v0�8L���
P��\�m�!�
�!��p��si�#��l#���e��h�k �o4�pT�tt��D%�u���5l���ȭ�ho�Эì���ܭv��J�s\&�0&���iبm:,�p�'M�p$���'X �rp(�|(��@�b@(KH�ad�e�(`�*tX*��l������+����k�*��nȮs�*t��a�e(�i<�oP�s��u��y,,���I�)JЮnP,خg�n�s$��x-*��k�M'�-��e�.�`. �n���/��4�a�1�h1tH�kl�p��t,(I�'Xd�o�1�2Kx�i`2�l2����g�9(�2����pd3��t�6�8�{eدt�:����������>
�@��?����b$�l0�n<�r\�sp�t��vPP��`l�Y��(�glaxL�iT�oPd
�eii��Tap<p��h�aXx$xM|�ez����eذk�n�r@���
��a@�e��h��i��j��nԲo$�s�t,�v�z��|��f�o��vx}�p�:
~��4�. �b�!e(�t0�vH�>�h��
H�����8�d`�ih�l|�n��rx�t��t�a(��(��	��d��y�G
��e���L��	8���a\��	�a��d�e�f �g0�k<�rX�sp�v�����.\Asl�r:���r���s|��Ĉy:���:�'F,�(�s|��: ��:DD�k���L�ih�p���/�����	D�U��"��s$��������o��J������a�e̲i�,�H�(�'.�m��r�t�v4����,��D�����x����l���aL�el�k��l��p��t��uгy��@�k\�n�g
8��$�d�ep�WT�Kx�e@��пi�����e��i��W���������n�O��ȳk\�h
.p���ܳeD���l$��a���	̡���iء���g�� �e ��H�ah�h��:X�s`�t�@���_�s�8����r@���x�a��s��t����]��l0��t�������a̴y@�2`����<�t�8����a�e$�i���r,�t��]�i�i�<�x4b�B��4�a��<�s\�td�v<��@���8���l�e��t�r ��>a0�cP�d��j��k�l��m��n��p�r(�s��tD�uP�v����at�b��c��d��e��f��g�h��i��k�l��m��n��o�p@�r�s<�t�u��vH�y��zt��|�ZD�el6-�5��<�n����'.p�e@?g|�st��:|�h�b����-���.��a��eĶk�tX���p��s|�]��a�e�g
�l��Զs�&�:��i�R0����a,�e<�iT�jp�l|�m����������4�mL�nȶi��������\����d����
Խ�������@�J�4e���l�p8���
��a�d�e�g$�k<�nP�sp�t��v|�y�	(��x�t�e��i ���t�s`�2l�]�a�<p.���0�e|�i�tH�e`�l�����1��h�r��h���.x�����aCe��o��p��r���)�]��r���̸i��2��T.$�d����ԸaHCb4�e`�gh�i��k��m��o��rĹs�t�u�v��4��
t����,�kH�nT�s����hca�����s`�����T.x�sx�*��h4i��l��v<�-$������
��a���.0��:�L�̹d�g��n��ԹixN��N(���h�A�
i8��������8�tH�aP�eh5pd�s��t��v��%:�\�rx����*x�a�������p�b�����a��i��(����r��iD�?%��Mغe�i�r �t,�u4�v(�2��������t8�W�sT�,��e�����kx�t�a��4��:D��@���<�kX�J`�el�ih�"�.��R��p	a��oL"̃ad!���d�5� 4����eh>�̻e��PM���e�H��lȃ_�d����ػ��\�X�a|�b��c��dL�e`�fl�gP�h��i�Kj�kD�lD�mp�n�o$�p��r��sL�t,�u4�vP��܅�@Ikp�rH��,�h�e �P�z���ȼš��
��aмdT�e�fpi�o�r$�u,�y4���C�
\���Zk%�Z��ؼ�PZ�ðh
����a�n�k
�C�moؐ��),̎��D��;�<����X�r�2��-�JtԖJ|�gX�oP�E��eD�.��S��e�����lȽnؽs؞(����g��@�?�Jнmd�J��a�l(�t<�uL�W�.`�T`P�4�s���o��rlP�:���ȫtx�a��d��eԾi�j�l�o�s�=u�y,�Ü�<�k�l��r���4�����Mdlf��i��pľr̾v�U��:��?�"l=v0�B��8�o�"�v(�I<���e�i$�t���<�
����<��>���N��JP�b��	�(@�"X�n��rd���
`�a��dԿe�g �i8�lT�s�t�y��$����2ȿn�t��e��:�����.�r�td"*���:�e�l�'(����l�#�/x���n,7vP9d80�y���t�k��l��tD�a��k��l��m��o��t����I�:�I��|�a�Mi���Px��I��P��a�e���`�U������g���a8@rX���:��������i�s�����@�aL�p����'.4�4�r��A���4�"T�a��b�d�i�l�p�tx���\�a�e8�i�Uk��l��m��n��o��r�s4�th�v�y|�����	��(\�(����e���4��adL�i�Ak�s�wt0�v��:�����p�t�u�%��Rd�ep�kx�s$&t��j�PP�s4]X�n8�!0���������s����eL.i���uh����H��s4�n�e4����j��Z��g~i|����e�oA�
��Z����mh�(t�:�oH�ô�$�r�P3	�P��@����"��YT�k��\�il�N8������t��,��:��-d�.��e��i��k��l�n��s�t��.��
p���".|�����a��e��h
T.,��:$�x�����D.��e$�i������l��H�b��k|�l��s��u����
0�a��e��i\Yj(Fo��r�s�ttYu��`�n����������.��d��i��k4Yl��m����x$�x0�������d�l��m�n�(��6�uH��`��)0�G
����x���Y�����-�YaH�i`Nn��i,��$�0Z�06#��`�r`��������h�����$��e��o��r4��D��:P
����n�	��n2#^	��ip%@0m�j|P��O���r�O��e�s0�a`�e��i�xX<�aD�s�s� �v�x�
Tyt��pXL�e�y��T�tx�v�
�Mp�a��eĊiT���iD�����l��:������eT�����n0�r8�sL�v������a��b`�cl�d��e��f��g�Kh�i$�k��lX�ml�nD�oP�p��r��s`�tXOu��v���p����D�e�����<�P.H���T�aЕJx�s��l����dܙ]��n��r���:T���h�.��s��1,����r����i(���e��ux�]��g�j4J�̡���?�5h@�lP�r�]�ep�k��l��ud��P�Ԧ����H�o�'���\����;|�oĨ2<��8��(�i�.T�����a��e��l8o<v�
���p,_(������gܰ��a��b�e$g��fm��a|�<��d(�gD�rP�s��(4�a�n\�v;���<�a���\�J$��T.и��`�a��e��g��n�s�È�WT.��d��l��vػ��(������
��o�r��s|�t�*i̾d��k�����s0������l�38�����@�L� �n\�](�a�;s<��4�n��"`�ip�lp��8����h�aH�(�|�r��]��a��e��u�(����s|V(D���t������e������i�k�o�s4�t@�(l�����i�����l.p,�tH�q��M@�rD��	T�ah������L�.�����a��i8�j|r��t���p���l�.�����.������a��������$��
��OT��	 ���a�
�	��a�����v4���]�.8�a�e��i��o�uT�y��Ì^d\�e�]��,�dl;kh�n��p��r��s��t�^�.a���8.��d��e��g��sta
`at��e�n]3�ai��.�aO,bOlb�b�	dJ��t�dh�dJ�ih5������ape���n�k4�.4�dD�i��kX�nx�r��s��vmdhq�hp��<�s(�;�t��h�aP�u�t�|x��v��p�uXy".�x����e�}�<�]�.t��v�*��a���<r�ԟ���jL�����g��i��k�v|������3	���� 9e �k(�nض(ķH�d�j����4�i��t<�r�`�dT��@�i���h�e�p�r�~�����|��������Psp���L���e��\��r��s�.�����e�e�a<�d`�el�f��gȩh��i��k��l8�md�n��p�r�s\�t(Gu��v��w�i�L�a�ee j.�ltplX�mPnJx�r�o-Xp��v�<v����e|{8{W��n�z����a��e��j��l�o$�s0�u�|���kp}(M,�pi`��"��m�h�����a܁��k��]�JP�p\�s@����=H�u�OĈ]�8.|�d��g��sԉO\��(�t��r��s��
L�G ��a��vIi`�_���������a��e��n��s@�.��;,�T2`����.��a<�dD�eh�gp�ix�k��s��t��vT��0�\��$�f�3gȡ������l����'!<��8�n#D�-t����v��g
h�����rl����o��r�P���:��o���Р�ܠ�����l�n#����T.t�s8�]��e`"j�tT�v�],�aL�e��(<�sD�tȼQ��D�20��p�]�.|�a��e��h��ixmr��h��k4�P�2T.��k��;������l�(���a��R�����e@����r@�;�����n8�̵]L�a�el�iH�j��k��lX�nh�o��p��rl�s��t��u ��P�J��D�dt�k��l��n��p��r��s�t��J��t�:
���;������e��g��k��l��";�t��r$��\��
��J���h�.��r��"	��(;(�t��t��.;��	8�e<�iL�lX�n��p��r�s@�t��u����D�tD�sp�����a��d��e���P�3;��2p�r��tx�e<�rԿ�s$�;;����@�\�p������a��e��k� 4.����U���l��A�����n��-��e�n(�t�T5$����� �i0��T�sL���4�a\�ed�t��%��;�?
����d��l��m��n��r��s��"X�pT�zl�����f��n��[
��
��r��x�.����e4��������t����� �a4�e��b&��J�t8����s4�i�,�dH�iP�vD����@;��El�"��`�f��l��m��p��r��v�����d$|�8�p��"x�������k��Q��@����e�i0�o,��������i��������n�s(�vP�� �s��b8�������.X�pd�t����F;D�i����L�r�;����e��k��p��t��'.������e����t��*��kp�����a�����e��lH����K;���@�o<�/�]�k�th��p�i��a0�st���8����D����8��T.l�a��e��i��o�u���4������d�d��k��l��n��t��y��$�����s��
,�����i��d��st��T������.��e0�P;u.����I������f,�iP�l\�ml�nx�s<`t$�(fr��� �e<�ip^a0���m���D�e��	(,���d�s|
���.��s��tt
W;��#�_aȼe �����s�����a`n�����f��k�l�nP�sp�t��v�]�eX�����eD�����g(�o0�s���^;@�eH�i�e;(�e;��`�kh�tX O� �(!d,""��x�e04�|�.��c��f��g��i��p��s85��5�6]��.��e��l46Pr|68�9���.�:��e�:�S0�nD�t�
(�t�e�T��$�glV�|V��<�aT�e�V_ ^m;��s<\��\�r��s��v��y@=�����h������^���t�^�^�
��e̅t;�����s$_����l\_�`��Hd��k���`����i�k�oX����S�B]p�a��c��e̩h�iP�jp�k��l8�m\�nh�o��p�s��t8�uP�vlJw��y���4b����.��n�gItnh�.��iX�k��l��nH�r��t�
��q���eq����gs���y�v��d�.�vsL|'�{����e�s�|z;����b4�n@�s�������,�s,���(�H�o��8@��\�lT���d�i��j��oP"������n��X��ed�"��.��d��e����,+P3�;,�"����e��o��(����.����Fa$�e�����v����0�����/i��.X����P��p����lD���D���� �o��Mt�d��
���prl@�X|�i��l��r��ux��8�\��e���x����e��o\�I8�i�X��a�e�{v$��x��������l0�3	H��m`��������(�x����0�rH�s���	t��h�ap�e��i��è�J��Jp��;@���x�g������n���	l������,�2�t��e��o����k��s@�����	4�"�k�yl~�����������Je���0�eܭ���x�e0��;$�n<�h�a��e��i��o��u8�����a(}e����X�l��r���
����x�k\�2��k0�m��r@�Z�
�����e�iA�����g(Gu�6�6����td3����s�t�6(�?
@�bL�c\�dh�kx�l��m��n��r0�sp�t(B�;�@��8�r�B�EM,�B]T�y4L��P
PP��p�a�e��l�V���a�V"T.�7�/��ed\���t�Y����k��n�LI�\�laJ��e�i�k �u(�v�b�.�ac� PdRe,+h��h�i��`aaD�hT�l�j?�l
l8L�upT.��s<p��`�a��eԹg�ps��t��v���0q�0�r��q��r��v�r��v�4vt��r��ul2�w'!Tw�lw�����@��e(�i\�������r�����e�����s0��<���4�r@���<�d`�op�r��s�ĻR���h�a��e��o��h���
��a̋e��t�.�Z��;<����r�����UeT�?P�����j(�����k,�rD�sX�tp�v�@(��Z�t���e���� �r�.,���8�a(���(P�ah�eX�t��;P��;��yt���	x�b��k��l��n��p �r(�sT�tt�v�.T. �������a��e���.�T.���t��������e�l�n�����;��M�
@�kL�m,�����8�o�� ,�h��>�;���`���  ����|�a��u�?X��a��el?����r4E��rܕ9�����m��r �����a��b�c4�d��e�f��g��h��i$�k,�lh�m��n��o��p��r��s�t�uX�v,�w<�zT������ah�b�c�d��ehf�g�h�i�"j�#k�)l+m/n04oX=p�>r�As|KtSu�Wv�Yw�~xZy�[z@=�4�����P��s������i��o��r��yP�PȘ�;L����*�5����t|����e�h$�i��2�8�Й����,�a\�fl�i|�j 1m��r��s��v��
�`�;؛d�o��r��x���e����(����	��iT��D����e�m��d��l��rx�x�����k�(����rl�]��e�����f8�iD�jL�od�rx�t��u��y̟P� uH�]��w)�4.\�s "����;p�eX$�D��,�x�t\9�
����a��eС����e��l;��d��0Е�0�����d��n�sȧ��Мt4��;�����k���;X��dP�r���	�a`�et�h��k��r��s��t��u�v���-L����"X�l@�(�l�a|����e<%(����l8�:��o|����d��a��e��t��t��.�����i��r$�������;,b�����s����n0��� �ax�b��d��e��g��iT�j�l�m �o0�y8��̳31���d�m|�ml�u�jF,�t��sm�"��d��o(v�;<����e��o\�0��0��I��]��n����e(�nL�i����e�AP�t(����iD�p�����H��������;��(�P�g��s@���
X�a��b��e�i�o8�p\�s��t��u��y�(T����i��uH��;�+�s0�x��n����e��H_d��r��t@��	d���.��"D6ll�$��;4����i� �r��,�eH�u������t��P�e8�����aH�e�2h�l��x�a�����8���T.��a�c8�dh�ex�f��g��i��k��l��m�nt�s��t��u����(���a�l�r��'�<�
,M�sh�z0�� �.L�lx�t(�e�ar\�s|�;�I��T�a��W����p�a����t��e��i��s���P�i �#��t��a��n�/(��;l�]��v\���i9�9�;��S,�e@�iL�s#/�<���t� �tH��0�8�n���(�i*�M�T�r(���\�a�th�k�Bv\��h���k����i0�p��tT�;���	�������x���	t�a��e�i<�lD�o`�p��r��s��ul�`vl����n$�r,�s4�t �<<�(`�\�����P�s�����]X�at�l|�ot�<h�c��I��
<d�</��������.��b��e<�gt�i��k��m��n��r��s��t���H����e��<����h��dCaDag�l �p0�t��ID�<��`���(�a`�	H�ut��"<4�iP�a��e<���X�n��"h�n�������.����a$�2��a�+<��
��e��x0����1<$
a��*8���������8���	��a$�c4�eD�hl�k��l��s��t��v���@�7<�,�a$�l���T�<<��L�a���T�r����`�a��e\i��o���(����W
���l��t<�B<��d5l������iXj��rPXy��i����eD�a��(�k������a0�eh�f	ot�r��t��u��yx�(��T.D�gT�lD��"��F<���L�i���`�e,�T��o��������y����������$-����l��sx�t��e�-�3���b��t,�pl��$�s@����d,�g<�kL�s�Dm��Y��a��eD��3�p����D�sX�J	��d��e7g��i��l��r$
t�y ��H�`��	h�"��s����s�l�5��u���XN<`����t|��Up��s������Ƚ�S<\�`��0���r<����l(0���4�z<�`�yD��H��������a��e��iT�o��r�u��\PP�d(���e��rx
��d\K��n��t�i��i��K��aH�ȗr\P��e�g�k(�s����,�4�r����t\Vl".L"<�id!�H�dp�n��r����$]h�i��s4%-�'��'�	��sd'���a��e(��'����i�+���n+���e ���i� 4Mh>�<�a8�dT�e�f4�iL�jd�ml�n��oH�r�s|�t�u�v�y���>(>
4�bl�gx�h��k��l��m�n�r �t0�u�?��s@�D@��@��a�
e��s�@�4AW<�A,|A��sTA����ehB\<�A���o�t�B��a���$��	C����m�t�C�
�e!�D���l|em E�|F�|JJ��@�i�HH�d��e��f��g��i�k�l�m�nd�ot�p��r��s�t�J�$_n��56�J-��i(K"�K��K����atlЍs�)PM���oxP(P���ixQ'DQ���a$�g(h,�sX�t RgHRb<8�e��K�U��(!e�R��@�l�RL�a<Sv'p��Sl�ohU�k�S]��i��sV���t���VM��m�X�-�X����a�t0�g�������d��������	�sPZ��e��2l�at�sD\���n|�u�[$�a��d��e��f��g�k�l�m�o(�s4�t<�u��g<,�� ��Y�\(]~\]��_Y8^]��sH^K��e�]]��r�s�V�^s&�^]�a��l�u�^J �,_����i�`��v��2p��Ta�� �hxbJ�b-�c�lc��D�e\�u\d�pgA,hm���b�D$��x���hn<\h��b��dЏl�n؏p�r��s�v i@�.���a�k]�n(ldmim]�t�mQ�e0<s<�m���r@n�
n��$�djk`�np�r|�s�m,�a��e�ji��o�n�dZh�n�h�aHo*hp��o����i��t �v�rn�r����t�ua�u��it���e�k�tp�vЎ'x���e��v�xN��dz���n0z�a,�oH�rT�u\{*8�g4�i���p{@�e�{
4�\(|��\�r|�d�a�|;��a��r���|��+k�}0~x<@~������������dt~��nL�i��e�i����.��]�a������k�(���g<�pP�r\�tp�_|���4�eԂ2����H�e��}<��p�r���|�e���<���<ȃ���sPv��*<�����r��s���<�����.(�a�b��d��e��f4gP�h�i�Kj�k�l�m�n�o�p	r|
s�
t0u`vTw\ä��܅] �nP�rh�v���<��<�n,�D�a`�eH�����D@	��p�k��m̌��
x�a�e�i��l�o�r(�sl�ux�y;�TAx�J%
����f�n�ms���2�g$�2|�������i|q�\���X�ul���k8�l��6H�aX�e��`,p���xP�dT	ؐd�k������lX�r����T.����M��a�f�i�j�o��rt�|�S	���(�}8 "���s���<����~/������3��"r\tԖ��$ahe�g�i�l��r�u�o�6]̈a��2�b�l�n�r�9�*ԗ��c�@cP�g	����a�l��(HL(�M�n�n�b�d�ncl�d���	�d$e,gTk\ltnrstv�����<aHe��2.���.��S	��)̟��'.�s���da�d�e�s�v�����H��ed�W�'.�s������\���j���kp_�d��a�����a8�i�Ja0i@kXldm��2X������8a,���d��8Le@��ģ�ԣ��la�e��id���@�.�a�e��jk$l`o�r�s�t��uL�W�x.�v��p���n $t�#�n����aP�v�,3����m`�Ka@eTi0-(��8d|��&0.Lr|��	xb�m�Ln�s�2�<�5�:�<���\�a�u�@	��RĨ���a�p@��<�M( ��r��eȫ��
.,a�e�g�i�j�l4mPs`uhvpyxÜ��<gtn(��<���D.�e��Lk`at\s����hdЎ�<�����g�i�k�n0�3	H�x��C�e0u�<د����"�t0��-����a$e������gn`an
d���d��̱d$v̲~x��<(�<s<��De�kD������f�������=�>����<�r���<��J�aP�bi4jTm`nlpts�tQu�y�����@0n��nl��_e��]�n���el$sL�Z(����<���,iH��t�@d���He��B�sa4��н���<��*|a���dt��i���<��ܾ����@�d����a d8e�gi�k�ln�o(sTtSuxz������l�te<�r���0d\gdiDkll|s|�t����l��!�t���tt'�<��t�d�l�n�s���(�<p�i@�#�t$���l<6�d8z�<�<a�����ll���sD��e�<=��i��t a8s�Q=<=��2@t��Hihrput�=��xbI��!=�������@�����g�p�r�u,��
������
H��T��n��e�i	u��/��	s0�xx��	H	ax	e�	f�	i�	n
o$
sD
ud
��x4�"@	dd	np	t��)'����\	g����
�x.ad�AfUgL�ilk,nUp�	s�wt�
(=�����	e�ud��p��	a��&g�kb0=�1���	�h���	����J
l$xm��st��`xp��8=t���0
e��J8
t��[l�q
P
y8����V�X
���	����t
.�Va�
c�
eiXWjk|l�m�o�p�s�t(
uP
v�Xy<�ðB���Yl�m�ytl�`�����
e��]�
r���
en �|�d�eLjXlhoty@�D��8. �_@e��x ���
����`d��,�)�uh�(�� �o���ܪ*��W�vX�����e(�i�rx�@=���#	ae(i4j<kXoxDp�Dtdyx�(���l��@�k����t�����tP�G=h���Dt����Lr���4�M=<���le����tn�u�����a�e�i
r
v 
y��T=T�\=�����v���rTs|�*�����{�����#4XIl��.	
e�����b=�����8
k@
n��i=���*��XH
i��/L�*\
i��d
k|�l�
p����p
a�
e i(j8oDrLs|t�u`�y���S��"Ya�
klmn�w$����
.�
en0��
��p=��x0��"�����%���ct�"0a�x=`�_l$i\t@(��,���dd�r0�tle�o�s����%�#�t��t�e,�%���y����(����~=�=��.�=(�ae`��=t��s�=t���=�-<r��"����Dk�n�r��MLa�e�i o,yH�<��H~��.t����e�������g0~i�l�n�r�s�0��(ht����;t�������gnl�[
���nX��d���m��=��3	����4p,���<�@�*,��HZ�$�a�e$i0l\txu�y�Ð*e��r�t��H
x.T
��n�r�t�	�eiPm����xT.X8i�
(�i�
��l$��k��`��<
/D
��<����D�t(��-Z�-��de,ls.�-�r�.�=���������Hr@/��a4/��r /-�a�.��r@0
a�ePi�ln(oXr�stu���/�'.8lXn�r<�v�1��D�eHi����2��2��Pdlntv3�	p�X`4�	�3��|i�t�4:
0:t�i�8�d�fܢm�n�or s,;%@@W
�>]�t�@�=xAL�i�'t�@]�eu C;�C�tCtj�l<t�DhD�4aKM�J	Ha�e�k�ql�qm�o�r�s�ÄKu!hK|nL�XM��M��i(��	DN�=�M���l���������,R"�'.mnr`Q���e�R��R8 S8�V|Z��Y g<m`)t,[*8]3	@]��Db]KLaxe�i�u�^�^��pa�l�p�_��_�=�_�s��P`���n$a*�cidb#�ckl nHt��y$��f���r�e���o,g�ge�g`�����(llj0l�h��<i\odu�j��k��nA�nld�tLpt�r�rM�s�r��a�vG�����,psM�a�e`s���.�sa�j$o0uX��xXs�s��vTy�=ԃ�d�xd�
$�i���8eT���@r�����L����=��thsl���pd�e0�t|n����,f4lDsT����nPr|�uhv�����a�b�c�d4e`fTg�Kh�Ki�j�kl�m�nohp�r�s(!t�!u"v�"ô��+\��+`��=,��<vp�<Kb`r������xa�o4�W��=��=	�lt�~<�P.H����a�oșT.��p�lЕ���aei��r��t��=0���ed��X�r@�i���=`�MeXgܙ]$ihktl�nr4tLv���X<�����`sĚ�d�e�i�l4�$
��+�"�m��=�"�m������al�P�d�h�t�����)7$�o-r��T�a�.iH�p̝�=��" t��(a�����n �M@e��,�]	Xa�e�fijl�Ko,u<ÜD��e�
���i`��rP�t�]�i���2��.�aP���b8�r���e���s�����=$a�i,�d��6l���4���T.x�]Ha|g�i�j�r�sL��(�trp�2̡���c��K�e�d�t��-H�-�.��h���i$���l�n�]
�a,d<eHidk|l�o�r�s�t�u�v�x�
ܦZ�4s,�
$��0�Pn��]Xetj��f<��8�aH,�T�i�n�$
 ��=lE������k�uJ;d���s���lTZ���mث��n�����(��T���aDdXe�l�o�s��|�;L�t<emx�Pdhil��H�Y��tpt<�xsܰ�e�������n����������br\���	�ae0iHlPmXolptu�y���4��l��s(tȶj ��T.@s���L�>d��=t��dr��(���=����t�������$���l�m�sи���a�cde��fDg�j�k�n�o�s�tx`và��(����8��G
up�R��W�6e(s��(�(Y	|�0m��t8o�rds�*�tk̾dXa�p�bi h',hX|a$��0��3����e����s��G �k�m\�1p���G �j�r�;P��(�=8�������<��f<mlMpDrLs\t4�G'��dl�E�����]Tr��Jxe�o@������s��������u����
�a�e �h0 i< jX k� t� v!y!�H�����d\�i k l��)*��c i��5��> �n o��$ kD��H u8�X��l���P a��iup y���
>����x d����� a� e2i� o� u� �4�"d,�(� f,�Q�������� n��e ����t��� ������8��L���!�D�2T!o����	!e\!i8�j�!o�!r�!s�!t�!u�!y`�>$��	t!d�l��*l!a�I|���)2:��t�!t��t�!s�4��!n�8���
<�M�!n�!r@3t�?��,�W�!rT���"a,"et"i�"oH�>�$"dL"gX"ih"rD�4�JD"eT���>t���`"k���"s,�g
�"e��p�������"k�"r�"y�����"��"���������"a��w���"a �C��M,#aL#e@�u��&>p�A#i|���#b���� #m@������8#e\��@#r`#sh#t��Jp���$i|#rpu���" �#a4$ex%h�%i�%j�%k&l�&n�&o�'r�'s�(t�(u�(vH)�<�
����#t����#k$nl$o$p($t��9 <���#f��T��$r\
->�	�� $r�	h�.(�hl$i8�k�$l�$n�$r%sX%t���x$a�
J`$k�,�`���$o�$u��p��ؾ.�$a�$e�$i�$sT"�tvp��3>��%s����$i8�o�s8�u���D�	��H��%eL%l��`^e���(%s����4%�x@%è��h�.`v(@�d%n l%a�2�%d�%e�%v�8C!�� t�%i�!�x'k !X�%e�%o�"�
�"��%rl#m0-�
�,�%d ,�%e &i0&u`&�0.]/3	�.��(&n@&t����/��Xe|/>H&r�/���&�T&�����$0dt&a0��|&r�0�k�&p�&s�0m�&e�&o�9>(1��1a�2X&'rX2���&b'f#h$'lD'pX'r�'s�2?>�o)Pn��'o8{�@5��'a4'i�~�7��7��<'el%�08��P'ah'u�9E>l:c�'eH:p'l:�|'e`"jd:K>(=[�<���'sT<��'a�'ex=���)i�'m��4B��'a(e(i$(k0(t�yvBl*rHC=C(g�DlE��8�a�HT(ah(et(i|(j�(o�H�-hH��L(lx��I`(n`I�L{W��(xNi�(s K�(i�(o�Nx4Oi�R��elЫt�WS>�W���(d�(i)l)r)sWX�(e$)iX�(X(	DX�hX�-����X��)k\3	�[��0)lX)y���x)�8)��\id)s���8�\x�)e�\��l)k�Ap�)s�\0�Y>�]
�)a�)eL*i9j�*o�*s(9u�*y�*z�*��d���Be�]���)t@l{�k�)aT�f��p*r(*s�5u�w[
�v��*idz^>�x�� *l<*tD*u�z��{�<��dkt*n�*t�V��t`*s���h*gp������*a�FeL�Z�*g��vԟd>�������*k�2�*s���п]�*k�O���(gp+y�~���*���i>��`+a�+b,e�,i@-jp-l|-o�-p�-s .tL.u�.ð�.����H+e��T+g�+k�+l�+n�+r�+v��o>(����+l��0���+e<����+t(���$)l�+s�+v�"p�{
�����e@�r��p,����x���+g��k<,lT,nt,r�,s�,t�,ylb �(,p��]0,a�i�Qt����H,e��
$�`,v����h,e�@����,e���P��,rd����,aL��X��<����,eh��,d�,e�,n0-v��]�n����xVi-s���\��-id�-k(-t|�u>x���������8-e����Ua��L-p ���X-����d-���ge�-f��p�-s��J��
��]�-t�P$������-oH�t�-k�-l�-n�-t.uL�K���,�.i��h�].l@��h�a0.e$�2P@
d�Z8.nl�@.a`.e�.l��:t.nT3|>�E�l.e,i4�. ����.a�.e<�.�d�.<���.y�����.��.�,�\�.�.l�.p�.r/t��T�
p�
�'���/aL]P/a0d@0e�1g�1i�Xk�1n2o�2s�3t�3u�1�2p/k�/l�/p�/r�/t0v<W�����x/d���>��/r�]�/a�	���/pHCm�	���/b�/t�
�
�����/r�/v4�F!�><y�/s���0i4;l��40�D�`?r���(0�\h�.t0a�0b�0c�0f�0l�0n1rD1s�1t�1v��>��Up��|0ep��0o��%lJ�0e�0l�0s����}e��0gH,+T�>��<*�0g1h�'�L�i���1e01i<1o��x�(1s�����.��ed1il1kt1m|1oL �9� �(!�T!}8��(	�"��1bd"���1a��e�#-�1r�~3D�#$��1sD$��4��1��3��%�,��nx�<B�'.42a<2b��dD2iL2kT2ml2p|2r�2s�2v�B��B�	�C(�CDDJ�Znd2s�D�>�DW�E�	�D��t2k�F�0G�|J
�Ht�2e�2i�2k�2l3m83oX3pp3t�L�?n�M��M���2e��rPO�dO�2a3e�Oܳg�O�PM3iQ�Q"$3n�P��,3vd�����D3n,QXL3oh3rpQ.&�Q0Yi�V2<U|3e̎i�3r�3v0[��3eX[�\?$]K�3g�3t�]p�^ihb�,�t�3v d3	 e.e��3e�d���3l4s|e�1�e]�Bk�4s�e�� 4a�4b85cT5d�5e�5f6g|6i�6kh7l88m 9n�9pt:r�:s�;th<u�<v8�x�4i���1|g��4n�4r�g���4a�4e,5�X1h�>�4d�4f�4n�4s((�K3��
�h�O�>��]5j�1��5g�i�5l�i�� 5��i�	�G�:dj@5s�iH5dd5o`kh���l�l5d�5splt5nHm�>4n+Pn���5a�5e�5f�(�n�5l�5m�(p%i�o|�5i<o]�5r�p��.(6tXp]�5a46eT6rl6st6u�*:6] 6a�p��.s��r��@6t�rKH6ed6y0shs�"tt]�t��v(�v�6l<v���6e�6i�6k$7l,7n47s@7v�i�$@w�6tLw]�6a�6e7l7o�w���ihw��6r�')(�w��7m�w�>8x�>Dy���e�zf$�
�nH7a�~"P7g�z��\7i�jj�7l8o8yx��d;b(�p��7a�7e�7i�7y��(�7e��7g�7s��R.�"��e��(��7s��t����"�7vԂ�(�X�"8kd8ll8n|8t���	 8a�8b�8e�8k�8n�8okr�8s9tx�(���>���X�tt8e��>l�;�8d@�����>��	`�(��"�8r@��L��8a�t�8l�8y9à�h���ȇ���8�ԇ~@�u.Ĉ]9eH9i�9n�9o�Ds�9t����@9m`9s@��>P���X9mx9t�1��1��p9a�9e�9i4��>2����9a���F���9s�U��H�e��Z�9l���9aP�(��"�9d�����9a$:e8:iP:l\:pd:y�
@�:t��(0�0:a����.x�"&D:a��H�ܔ�`���l:a�:d�:e�lu0��d8a\��k(���:t8�]�:a�:e�:l;m ;o,;p<;s`;t(����:l�:n4�n��>;a��2@���l�2;v���>Ħq�4;tdyȼ(����L;s���T;at;i|;r|�aܧ.	�;�$������ ��>T��;e�����;tP��;k�;sp�]�;e <i@<oL<uT<y ��<t�����;r����;e���>��<a��<k��>����,<dX�4<r��iȯ0�Xx<d���\<n�<tTZ�p�i�����'.�<r�<s�����<a�<e�<s=v�!�>����<eT���@����<k�<nD�r|�C�� $���<m ��,�=e\�.<���=e����(=t`��`��[�4=�<\�̵|=a�=e�=j>lL>rX>u�>Ì�i�=r����b���`�-�=o���=f�=n�=s�=tp�����o�=����������-�=kL����Z��>a,�i8���>d��i���
 >eh���(>v����4>�@��@>��2p>n�>t�[�����h>g��
p���|>.���
�����>yt����>��>����>�����>k8�
?a(?d0?e�@i�@mAo\AslAuxAv�A���-�����>a�g?r��� ?o�������Zd?bX�f�?i�?k�?l�?o�?p@r @s�@t�@u��?a�?�p?.X��x?tX�P�?r���t���?t����?n$C����?l��
,��?t����?a�v'��SlD
;	�@u|
��@k@@mH@oX@pd@sl@t�����?��P@e��*���@a�@i�@r�i�?
kL�
���@s0�	�2�dk�@p�@vh"�+�@��.���l�.���@��5S04���@eAm,AsPAt88��:��:]$Aa<Ae�:.����;]HAa�A�T�S��dAn�W��`i�`?�Ae`���Ad@=���A��B�.Ba��cLCeDiHDj�Dk�FlGmLGnXGo�GptHs|HtxJu�JvKy0Kz\K�4b Bb(Bd0BkPBllBm|Br�b2c+xd*@BaHBt�di�e�e����adBd�l�u g��j2�i��tBr�Bv�j�	�?pm.�7��hm.�Bl 4��(ma0me�Bhxml�mo�mu��4ba�lb�Bc�mdtne�~f�g��h�i�jT�k��l��m��n��o@�p��r��s�t��ut�v��y��zl~���`o��8Citn@CdpCgxCk�Cl�Cr�Cs�Ctlp	r����uTs�s]�Ca�Cd�i �v�s��o�x"�Ckx���Ciz*�KiLu�{��8le�Ct}:�od��$Dp��Cm Dn$�s|/"?��0Dg8Ds��(?���`���@Da\DhlDo�%D�o��dDr�����xDd�Df�Dm�Dp�Dr�DsT���
�DaEi(EjTEl�Eo�ErFu`FvtFy�F�[D�2������(���D.���	@�(ElEn���А���n��_HEe���P"��4Es���<En��<2pEa�Ee�E�p,��YhEg�Er�b�В�|/.?�/���E�d����.�Ee�Ep�Er��tP34?�72$������Ea�EiFo|�`���$0����Ev��i(���.$Fe@FnP�(	0Ft�
<���q
8Fd|W�����LFrp��
TFa ����lFsl�iP����F�,�lf�FiH�k�Fs���Fe�Fi�Fo�FuКY@����x��Ft(����6k��h���Fn��M��aGo4GÄ�T.p���.lD���(G��4�v��M@Ge�� �a|Gm�Gn�Gr0�v�j:?ܦ��tGl@�:�Ga�Ge�Gi�Gs��hO�Gl|�@?��I���l�"�Gd�Gi��[
�F?�]�Gs@��Ha0He��iإoTHrhHÈ�\�n(Ht��?H��lDHrLHs0��t�9x��̭el��L���`H������	�Ha�Hb�HelIi�Ik�Ir Ju8JyLJ���ܺ���Hb�����Hb�Hn`�p�������Hk(��7H�\�dImIn0IrHIv0�(0��x���Id(Is�S4�����.L�g�sa$�-<In���If�IhSs�Iv��TIl�Is�It|p�M?�T?8��T�H��[?���Ie�Ii�IoJô��4�����I.�Ie��`?��nD����In��3	|�
h���J���f?X���JtLtv89���0Jg���
����\J�DJ�`���Mk���dJl����lJk�Jr�Js(�\�������Ja�Ji��t���Ja�JeKi��-�����Jk�Jr4���0�a�Js�~l?��Kk�������Pt��`�n(Kt4���P�����8Ks4��@KkpKyl~��LK�0�t?����hKe<��KaXLe�Mh$Ni��m`Oo�Or�Pst\tRuXRy�R��������Kd�Kf�KkLn Lp4LrLLv�����d��K.4����Ka<���)a$)l�Z����La��
����,LrDLt��{?�J��s\���c�Ld��e�Lg�Li�Lk�LlMm0Mn`�o`Mr�MsYt�Mv��J�La4�eD���8�o@�rH����e�Ln���@�����a�me�Li�Lu��� M�����lMix�J�zaMpt�6\�;@�tMk����$MsTMt8��D�@Me��HMl��|Md�Me�Mi�Mk����?���
��Ms����Ms��
@����Me�Mk�Mt�����(����Mi �%��[�����Mdt�s@��NnXNr�NalNexNg�Nk�Nl�Nn��o��p�NsLOtXOvp���.�i�mdNn�]Нr0]��vH���Na�Ni�No�����h�?|���Na\	
p	���NaOkOo Op(Os8Ot@
�L
���NaOit
�(��?�
�����2�
��0Or�R<JDOu�J����'.�Of�Ok�Om�Op��r�Os�Ov�J��f�Oo�oR�th�.�8�?����Oki�O.����Op4�Y8n&�Q�:PaPehPi�Po�Pu�P���$���I.<Pd0�g`�iLPkX�lXPpl��?���DPoX�D�?L��`Pb,�s �P��xPs!i@ ���Pt a�$�"���Pl�!���P��i2\#���Pr#�	�PaQe$Qi,Qj@QkXQnlQp�Qt�Qu�#xygQn�v�H$��Qgd%]�%��aЎ�0&��8QePQj|&N��MGo�'�	�'XdQe�(W@(KxQi�Qr�Q���$D)���QkT)�QeX��?����Qy�����Q����h�t�Qe�����Qd�)���Qn�E�.4��Rnd3��Re4Rg��n(Rt�}v8��,�.�8�<Ra�e�8��HRdtRr�Rtľ;�9����alRs�:-(��0;���Ra�:��Rr�����R��R��R��;3	t;���Rl,?�?>���Rsx��P@���Rdl?���RnLSv�?���RahSb�Sd�Se�SgTiTkXTl�Tm�Tn�Ur�Us|VtxWv�@XSh�@�)B9:�@��`SlxSod!��B]�i�Sr�Ss�D�D��l��WlE�Se4E���Sl�Sr�Ss�EP`F��1�G�Sl|�tH���Sa�J�J��Td4L(0TsDTt�N�NJ(Tu�O��N<Ts�PPP��PTaxTi�Tk�Tl�Tv`Uy R�'�R;�oe�R��U7�V��Ta�Tp<�2�V"�Tl�X�?�X��TlxZX!UlUrTZt�Te�ar<Us�Y���Td(�gDUiTUk|UØZ��Z�?,U.4Us��?�ZM
�Z�#�[p,�\�d\�LUr�U��^�?�^*hUy�^��pU�la��Ua�Ue(�t8ai�Un�Ur�a-0b-	��b�Uri��UhViVj Vl,VoHVs`Vt�j�+ ��?�j�Uk,k{��l8VuplP���P�4Vlm<Ve.t�nK�m��XVrp(�Vl�Vn<p��
lVa�Vb�pd�VeWg Wk(Wo0WrdWt��v�p*��a�p����.�q�\q�Ve�q&�Vi`�lWnWrr,@��.`r]�Ve�r��r�?8�?pt��t�5@WePW�u�|uK�u��HW��v�4vt\Wozi$xMpWa�We�WiLxXx���Wd,yg�Wi�8l�Wrĉsxx(���x���We�x�?�Ws���@�	XaPXeXYixYj�Yo�Yr�Ys�Yu�Y�zx(Xk�l�z
�z�� Xa��2�RnH��4Xe|Xr����	@Xd�Xg�Xi�Xk�XlYn(Yr`�sh�v�	Ђ܂���Xa�Xi�Xr���t��(0��ă���Xm�XsD��?�J�Xe\�� ����Xl����aYḻ��Ye��?
(��Yg\�����.8Ye�i�lYex���@Yl����LYl�)�dYd������H����Ys�YvD�%t��+�dh�	-t����Yg��Yn��������Yr$����Y� ��8@���
�Ya8ZdLZfdZk�Zl�Zn�Zp[rp[s�[t0��<���0Zr�	
��MDZe\Zl���t�etZl �ش
,���|Ze�Zll��?x��Zs\��Zi��?��t�Zlh����Zd�Ze���.ķ�Zs��T.Ⱥ���Za�J [e([k4[oD[ä��@�	�eh���,���<[�x�4���P[lD�X[a��]d[s$�i0���|[a�[e�[m�[r��(x��[k���)��.	��xH�
0����[e<����[l�[r4\v���?\n�����[e \i,\t��?@�i�\n|K[��(���4�.�\a�\b�\d�\e�\f]k`]lh]m�]n^p ^r�^s�^t�^v\_y�����\r����\e��T.X��.k����\e�\s�����@��B�\pD�f�����\l��T.,]rP����\e8]l@]nH]sP]tX]v����8�o����@P� K������dh�Zt]a��`�<��;|]s����]e�]s�����]n�]s�]t���t�]a�]kl�i�N�����]o���]kHY@T�@�]g����DJe^l`���<^dp^e�^l�^o�^sL�9h�@D^. tL^e�c��X^dX�d^n�^t�����gh����t2p,���|�.�e�^l�^v����8�^at�f(���^s��(�^aD��
t�����a$_e؄iP_À��H��_e����_d<_r��)@���4_s�������H_�����Td.|_a�_e�_k�_r�_s��r��t���_dlJn�_s3th�^	����	�_e��i�v��v"�_sv���_e8�P�_n���_e`k`t`�/@�qt���4�.\`ap`b�`d�`e�`f�`g�`k alhan|ap�ar�asbt\��h`gСP��8�X7@��x`t��?�`e�`y�����a���`r������`e��%�`n��#0�?@p�E@ ����`jarauav����e�������8aeTau��L@�0asHat��(��.�� ���taa����\ad�t��d��q
�as�au$���q�ak��i���ab�ae�ai�aj�at�lQ@4�X@��i�X@����bk,���aa�r but��l�aD �J�. ���(ba�bb�bccdlce�cf�cgTdixdk�el gm�gn�ip�ir�jskt�kuTlv��PT.�����ba�be�bl�bo�by8����_@���P���bk�bn�$�L�i�52|���be�?(��bg,cm���ca@ceLcmTco\cvTA�]��e|�4cr�2����'!D�;�mdcd�n�crx��@�������ce���T.�cs�ct���ca�cedgdi dn,do<drDdsd�(6��d@�]�ct���cn��l@�/W���Mda��W. g�r@t�V��20���Ldkhdrpds�i����\�.�da�db�ddeeefeh$ei,ej4ektlLemd@oTep\erles�et�eu�e�X�]�d.p�d�di��x@��P@��0�����
��@ee�����@T�N|�]�rDey�+),00�9��'8��@��0����de.�ei�ej�ep�er�eu8��L���G�|��@Ԯ����a�eo4O�0��(���=̰���e�0���
��.$fa\fbdfexfg�fi�fm�fo�fs�ftxugv y�Lf.@frTft��@����8fm��@<��@|�J:��@pftԵ�<��#�fe��o�fs\������8�(��fs��fe�iغ�@�fa�x8�����frĻ�fe��:ge4��@<���gd@���.@galge�gl�gm�gy�]�'.Tgn\gsо�@��|�p��dgi�,l�`t��@�ge�8�v4���ge���T��gn�����#�K.hrht8����ga,hd|he�hg�hk4Bl�hm�hn�hoishit�iv�C,�*��.Hhlx�tha�e�rdhs��l7y�p�8Pho��Xhltht�����@�hk��@�t��.�he�hr���@���@\5��4�hnl�]�he�9})�i����he�ho\��(�]iris����Fi�de. 3aDiePikXil`iv���|�<in(�����@`��@��|�.�ie�ir�iy�����\p��"x�"�io��]��	����
�i.�iaHCbjdjePji\jkljm�jr�jt�ju�jv�jy���jb<�xH�S��P4��j.<jaDagHjv���
4jl����h
T.��p�a�ci$�'!xjex�i.��	����Ca���h�
��W8���jj�jp�js�jtkuky���d��jup��@���@�� 2y��X�����	�	aDkeT6iPkllkm	oxkr�ks�kt0�3	(�<kn�AL�	AT�Xks`�`ko,�A4������kk�8�0x�t�k.�ka�ke�����>��kn��s@���p�.�kdlelg4lk<lmDlsLlt�?li�u. �(?�����a(le�R.D�q
�������X�"pla`�e�li�ln�lu4�"�A����xln��5h
��
p	a�li�lo�lumyH"\P�led!���d�+��(�t+��le�lgmlH ,t0��0��ma�.mt�3��5�DmlTmn`mr06q
�6�l6��Lme7*�3��H:}<�:���8.�mc�ms ;A<S	i(=�msh>\+�mane@nuhnÌ?���s>�mg�mm�A9TA���mp�M.PM��ne�Hnl�~�.�~��$nnLnrt~0ne�E���ȃ�Tnv����\n���na�b0c`od�oeHpflpgP�hqi�qjrksltumvn�wo�wpxrzs�{t0}u�}v(~y���܅�ؾ.�g�nn(orHoux�����ndos����toaH�,� oe�$A؉�4ok|�<os��tǧ��	Toa�od�oe�ol�4o�:r�ou�ov;�\�O��2��.|�,Aؐ��82A\��oe8����ol�
�on��oa���
�.pd��f
g pi(pl0pn8pr@ps\
vБ�(����������h����-,�fXpt�<A��)�plԖ��`pa�pe�pg�pl�pm�pr�Z��]�ps�pv(�BA`F�
�G.P��pa����.�pe�SGA���pth�Y�OAl�d�aDqe���	qdpqe�qg�qk�ql�qm�qn�qr�qs����<qnt�J�Pqs4��Xqe�Adqn�qt������qa�
e��2.�����UA0�K�d<�d�i�qk�zsDqt��[A������-�.L�fd���
�qaD<e|�iDLj@rkXrl`rntohrr�rs�rtsysä��Lreܥ�s`�O�`A�D�a|ri�rop��x����,Ĩ���rj4rp�rt\���K�����rn��ra�re�ro �.�rr���M���ra|�fAT�5t���HM�ȫ]
Tsa�sd�se�sg�siDtlPtopts�tt�tuuv�yXu���.tskP�n|sr�ss�st@�d/���
�st��XȬ��4���T.�sd�sg��i=mLn`s�sv��mA�ni)����sa��tAد�&�"��ktl�Nstt��{APY���$tp����,t���8t���`tlhtm(��(�*<�6�ti�tk�tlXOp0�(��2�tn ��A�����te\��l����tg���ta��SD�d�trܶn&ܺ���tkun���ta ueLui��8���.4ui<urL�'\�(ļ-T�Dun����<���=���o����	lua�ub�ud�ue�ui�unp>oQp�uè�,+��A����T.�un��Ի��ui��'!�ua�ud����!$ܾ��<Q�d����.Pvadvd�ve�vg�vh�viwkwnwsdwtT@u`@y�w�dx@�"Hvu�J�t\vaxve<�r����l�tn�"�d�vg�Qm�vs�*��At����va�ve��A.���os��t�va���A����n�d�A$�]�vjD��@��;0weTwj\wo,��A���-(wmHwndv/TK��@wdPM���2���twe�wr���rP[�A��(�wpt��we����S���i�wd�wr|��A��2���wextT�2xrXG���wo�����ws�,+x����.Txa�dxxe�xf�xi yk8ynXyo�yt�yu�yv�yz�4��m<�n�ppxs�u���
���
��.�Taad�AfUgL�i�Ak�%l�xnUp�xs�wt�wv`���\n$t�����wo�s�t�up�	��"ye�k�Pęe4]�xn�%�A�%Xye��yj0yr���Ah��Lye�1���Dyv���lyptyr|ys��W�[4�����A�ya�yr�K(��d�yk��tt��(��"�yl�ynXT<�������
d�yl���ye��(l��A8����V��y������.pza�Vc�ze�zi�zj�zk({l<{n�oP{pp{s|{t�{uxv�Xy�{�p��Ⱦd|zs�t4�\zl�f�A���zn�v��n�ze �i���zn\�2De�zu���A�����zk0��p���zl{r|����za{v(��'���$��,�{t,�{i�a$�M4{ax�e��H{rT����\{n��d{i���W.��e Xo��i���{n��������{��{�`�2�������{.|aL|e�|i�|j�|l�|n(For�|s}ttYu }v`�y��ä��(|.0|e8|kD|r�8�A0�GL�*�+aP����"�|a�|gikp|l�|r�|s���D�P���x|a�|o����.�|k�|s0�p	���@�|.��"�|e���A��A$�B`�
BD�M
����|s���|r0�t}e\�B��(3�M(}mH}np}r|}t���58�c��P}e�KX}n��]d}e\����-�}a�d�}i�}nPr~��|W.<����}e����}n�}rt���������}a�����}s0�	�$e0�B����~g,���~���dL�� ~n��~a�~b�~e�~f�~j�~k�$l�~o�~p�~r�~s�~v`��`�����4~�4���#���B������� #B(�0} D�`�#$a(ePi0l\o�rl�y���T.���e�n�	�De�s��x.T
�8t��sipr�����ha�lH k%X ����$ U��#X�at(i�.p7��������c�H/�n8�r /-�e��i�.��r@0��aL�e|�ü1x�/�l(�v�6����a0�e@��>]8�s�8@�nd�shD�tCt\�t�r���vG��p���sM
T.��a�e �iP�jl�o��s��u��yā�t��s���kԀt܀udx��x�	�y�a��i�r�z)B`.B�}�fL}���iD���T.4�pD�s����3	́�<�kh�(�6B����X�pd�x`�p��r��w؅=B������th�p7���)�J���T��ԁl�v��������̌��t����܁a�e���.0����l�a��b��d8�e��făgh^h �k�ld�m��n��ŏp��r,�s�t��u��v������X�vT���`�l��m��n,pL�v@�#B,��������s����-l��xЕ����a܂e�o�r0�y4�DBX�Ԃ.��n�o�r��t�\D�v'd�S	�.�����KB(�a�����S	ܙ]D.gd.l��nT�rx�sT����'.l�e��Y��d�n<�?��t|�*`�,�]��e��l�Ko��r,u�]��o��Cx�n�e��i�l�r̠QBԠ]܃sP��rp�� ��ءM�o���n�a��e@�hT�k��o؄t WB�����r��]H�a��e��o����2h�t�$�p�e���|�t<%�0���lĨ�T��Ȅm��p��s��t�5(��Wd�ЄsT�]�.�d<�jD�k_l��L��
�ePM(|��l,�r4�s�h�Di%(������
l�L�lx_t\���T�e|/px�u�������t��eT�uи����d؅e��f��g�i�j �n��o��s\0t��v��*��WЅk�*�̾d�a��t�s�u�[B,��$�g	���D�.L�aT�el�ht�k|�lL0t4��A�bB��.d�t$����iB�=oB�=�*�B�`�"��b���\����e����k���"<���0v�����Sa܆e@�.���.�n��]�e�i �k(�T.������l��]X�e��i��j��k��ṅt!y�����P�ll�rx�v����Cf�}������n8�xD���ul����e�����4������će�i�o��W,�2��nT9a�j(�rL�st�t��Z�����i���|�K �a<�eD�i�����(�|X�v�)�|a(��
r��s��td�ed�uB<�M43m��t<p�,��ĈnT�����aԈe�i�ø����̈k�.��
(����t���������.`�aĉb̉dԉe��fĊgԊi�k�l�m��n�o�8pT�r\�sd�t��u�Ì����.��g��m��n��r��v��z`�zB|����(��l��B�����n@�oL�[�-T�
\��	dj.�f8�gk@�l`�n��r��s��t��b%8�-�i$�l0�t|�(����a�����H��L�e|���v�����X�.t�e|�tD�X!���B����de.��p���de.��tT���t��i�-���\����̊m��������������Pb �k(�m0�nD�rL�u�������B<�s�`�d��q
��'��TX�x�s�%�B#�p�j�M�����a��b��gȋlԋn܋r�s �
��[,������a�����L�
����k�p8�rD�sL�t�����d��M|������$�����,�ä����B ��.��a��bȎdЎe0�f8�h@�i��j��k��lH�mP�nd�o��r�s�t(�up�v�yP�����o.�b�d�e�g�k0�lD�m`�n��p(�r��s��t��y����e��J��BD��dT�B<�rh��$�a����q
P�l�=00<��X�a��d��e��o��t�9"xt|�e��B�0�B4��$T����.ԍa�e�i �p��B�̍.�s��5��B��.�i�n��B��t�T8�T�ah�bt�d|�k��s��t��vx�`�L�b�h��)`�eH��#���B��'h��B�q
$
�	����eHJP�BxU�"�d(�h�iH�l|�n��pďr؏s�t$wv(�y`58�a�$o�
(�e�ADrp�B�4�v`��<�e`�il�tt�u�"k����p��ؾ.��a��o�$sT��v|�i=��g4���t����.8�o�sH-�i�m�n��x$� ������t����e �j\Y�B�>�� '��
��.|�a��d��e��f��k��lАn �o,�p4�r`�s��v���8F&h�x�<B�����l��tx$\�XZt���Đe�i�n�o�(��n�
����s#��0���l����H�eP�kX�s 5���# [
 ��@�.t�s|�t���Bx �� C�!�@�.��m�r !X��e�o4��$"�̑s0"J��a"�B��C�e����ԑm��s��CP�C�"C�"��r�"$C�"���l�"���r#��(��L���3$#]D�n`�pt�r<#-CX�DQsL#��h�el# ,�'��aВe�i(�u<�yH,Y��k��nȒs|,�a�
�,��,g�i�k`-Y�q*���x.���g0.��n�t�.3	p�3	�.�� �f|U�/��4�d00p!�0�`�u@2S	X2��\�a��d��f��g��iȓl�m�n�o$�r`�sh�t35C�2��dPn��3xD4i@5�ܓa�e�l8{<Cd5CC�5JC�5�	�a�e6)�Tt�6�-t7208��ШdD�pP�sX�t��x9<�i<9QC�9WC:�0;�t�t�;��<���.��lĔtT<�|�aԔe0�i��oĕu�y����-,��0T=���
x=��̔.�f��g�k�t�v���%�=^C�=��>�0�M`M�n�>eC�>��(�bT�f\�gd�kl�p|�t��vx�lC�>sC?lCd?zC�?�C�?Jt�e�?C6@�?]��g��n��p8@�`@{A�S
�@����dԕs�@�d[�dA�ܕs$A���s�Av8�A�����4B=	 K�C4S��R�� �dP�e��f��l�m�nH�pX�rdS�h�nt�r�E"�E�`�dTS�L�e|Sua3�P��n��r�S����aȖeؖi�l�p�t8�3Tc��31<T�ЖsHTJCؤ
PT�C�T�ClT����s�Tq
<�d�Z�C�Z]�vxZ$�r�Tt0�e U�	�U�CLU��P�sh�vV�CW�
��a��e̗iؗuHW���'.��d��spz	�W*(�
�W����nėrDX*�X��PtPY��Y���a�e�r �s@�t�Y��Y�C�Z�	�e��2�ZS	8�k0�s['8[�$L[��[Y������H��l����m;�[��d�.��a��e�[�C�[�C�\���r��t�i`]����et]�C�]��a,�ex�i(�oh�u<�y���Й��^���a,�e�]���d@�gT�kx�l��n��p��rԙs�t�u�v�^4��i.,_��4�e`p�_��L�eh�sp�t,`�D`x	�`�`a�Ca����dlb��`!p��
c��n�b����aęe c��d�Cd��̙t�d��Bef�	��sLf�\f-�e �i�fd/�f(�f���n�k
4�.p�d��gКi�k`fl�m �n8�r@�s��t\�vh�ym�m��h�a(4�!�3��|�e�n"��r��s�n����a��iȚnh�ro�C�o����hpYDe|-v�q�C�q���e��k�q��s�Cds���e0u�C�r�t���e0�t�u�C�vd�x�T�j4Do�t�y�}��aL��?���<�	p�a��d9e��g��k̛n�p�t�vP�d��]�feěk��S	�dܛj�t���C܉D8�����de.�e؋�,�3	����J�bL����bH�t���.\�e��]8�ṭ�?ԣT�t���
��a��e��f��g̜k�m�n�p(�s4�t��Dd��
��s`Fip�
���Niض��Ĝs�X
D�eT��؜pl�I���
ķ����d�s8�(?\��D���� �a��(�ȷeT�n\�sd�t<��п�`�t�),��l�i���t�r�����a���r�~��X����������������n�p�/r�s�v8�y��D�Za�����a�i��D��i�����a�/e(�iL�i�&D����0�dH�e�.D�5D����P�.p�ex�n��t��#L�7��e�����r`�/)��M̞aP�e$�i��o��u�yD����Z��Ğd�k$�l0�n ��+(����.�ep�x�"��n���p�"�t<����e(��@����8.x�e��i��k��8�d��g��i��kȟl�n��r�s�����<D�d-��������8������l��t���,4�CD��]��dܟl�t��JDD�$�����x	�����t@�Y�i����?h��aD�e̻gP�l�Vnt�t�����nL���@�.d�al�e�
t��J\�-�|�a$�d��k��tH�J�����̠gl���gԠl�r�HQD �X�a�d��e,��PG
<�L��. ����t4�XD��k��x<��(�nT�rh�t����p��0��dv8`�s���^D,�(c.��r��tp{�eD�����tLMġa�e�ix�o��u�y��|�.�b�k�s��t�v�(<" 8��-��\�8.D�bL�d��eT�g\�i��k��l��m��r��sآtp/��(��h�e�A�r8�$;6t�i�J|�r��td��l(�d���YȢaТv��0"lDd"��.
�,	�f�g(�kH�lP�nX�p`�rh�tp�v�.�.]�l�.�<�kĨi��]4�o(/��/�-�0��0�&D2�
P3i<B.��e��r��tTC�)�D�D����d�F9(�B�&$]��d\�mԣrܣtl^(�^"�`�$`�hbq�rD$�����(��(b���Ue��$hb� �.P�d\�ed�rl�st�v����b��H�rcrD�cv8�cM d��(�i�f2�e]��l�r�v�e����a��b�d �e8�f��g̥hԥi�k�lܦm@�nبpl�rX�s��t(Guܪv�f�tg��g"t�l�i	�a jT.pl�4nyDP�gX�sPn��(�a`�fp�r|�t�n(�n(<o�&�;�oXh�e,p~D�p]Xp]��a��i��l��npq�Pr
hrP��mxrM��e�t�D�t���d8x�D<v���n��sDy��We�z��|.<�aL�dT�e��f��i<�j��o��s��t��uЦ�8{W�.0�v$|�D�|"T.h�i|�nX}���<e�}��}��t�et~9�~�D���O.܁�4�ET�S����m�(X~��Ȧ����d�.$�e8�fD�gL�hT�k�jl\�m��r�s(�t�n
�����dl�"�n$��0�0�aP��D\��D���?(��D��a��e��{���l�aD�t�rX�C��r$�������e؆Għe���0�d��nԧr	�D<�? $���ܧ����l�t �yL�,���ih����ԇ�D��.8�a��DĈnhOap�d|�e��g��o��sĨųyȉ
ԉth�a@�	��a�� ��V�t��s����L�t��k�kt��]$`T.�����a�e$�pt�r����"�kpfr�
@��t4��]�.@�aH�e�YiX�l��<��,4��Dp��P�eT�`���d�.��a��b��dȩe�g�i4�k@�nH�sP�tܔ���n|�*ܕ�D0���Ya\��D�$k���Dx��9ԩ.��ܩa��l��<�]�e�s$�u���D�$�!�X�8�,�eT��t��Dl�G=8��t�c|�e��f`"j��l�B3	�.��B��(P�i.p�]��e��r��s̪t����i��tĪe�;i��J�a��e8�j��2@����.�mn�r(�s0�t�g%����e�|x��7���̵XT.��aH�e��i8�l��ox�r�s�u0�yL��LJIС��t�h��|�g��k�l�n�r(�s�D������.ȫaЫeثk��[B���DĶ�-�J�l��tp�,+Ļg<�������nh�CC����i(�G
<�e\��DP�4�r���8.|�a��d8�e��i��l�n�p0�rt�s��tX�{��E������a����4���������	D�;̬e�(��Ĭg��s�th�3D��p����n�t��$����?
�����d\�"�n����$�aL�iT�l\�rd�s8�E������6���E��-l�i��t��L���h�.���ĭlحn�o�p�r$�s0�z8;������o���Cl���ЭgP��4������s��]�e�id�o(�
�E�����s|�i�\`�ax�e��i8bEh���L�t8���T�np�r���-������]��n��t�.�A�����e̮l�r<�sD�tX�v`�G \�t��dܮe�i��$E�~+E�������a�e�s(�t4�vD�2E��8E�P��� �s��#��'\�S	P�e�����	��1l���`�g��n@��h�a��e$�i\�oİṵy�ô�
4'������gЯiدk�m�n��s�t,��
���e�i\��l��0x��<`�0�?E���H�s�t�����n8�oT�sH�FE0�^;@�iP�OE��I8x�b��d��f��g��s�4�T5,+��������]��.��e��p$<;VE�����s�uZ(��^Eh���԰y�������ܰ����0��@���b �lx�d<��H���(�d���\�ld�yt���l��<����'��������.|�d��fE8�9��e��iܱoL�ü���Цk4mE�����eбk�m�v�,�]ȱa04ife�p�s�t�9���a�:t�:]�a�e�;(�e0�o�;@<{�^M<\��8�v@=��x��@����T.�`?\�a��e`��h�d,em���s�`��rV��B�aP�e̩h��iضj�k��l�*mȷn�o��p̸rظt��u�v��y��(���b��l�b����eH�l4b��
�bP�eX�jl�kx�l��m��nȳr�s0�u�bqElc;��%e�.xd��`�e�e����a��l���lgT. g���e��p��g���Ie�i�i����.��i�mtBr�v�94]�nPjh
�elj��j
�Ce�j�Dl��a�e�k�� �s��wE�o�<�ttn
D�e��g��lt�m�nP�r��s��t<�u��ylpJ��e��g�pi�pEs���i��sĴv�sppt}Eu�h�.ܴ�н�����Դ�v���.�a(�d4�e�Ki�dkX�uD��Pv".xv��ndvt�e�v'���w��<��x���.h�a��xTx�`�b��t�z
Hiz��x�kLt�{"�{����.<�s(~�l�x���a�e�f,�l@�mT�n��s��v���l��rT����e8�]��rt�S	��]�f��Y�]$�oT��Ed���8�e��M,����L�dx�i��j0En�s����p�t��
,�4:��m@��Ԉ�Nn������e`��������g`�����r�̶a�5�����e����p �rT�����a4�oP�yx��(�L�a��8d���,�fH�g�Er�����.`�e��C�����l��pP���h��]�8]������o��y(�<�i�	ġ��r��M��aX�o��(�(��d�k���ܷ�P��Tc�8�J�����f0�k@�lL�mh�r��v����p���8�oܦJX�aX�W��H9l���`�a��gܩ�E�x�a��*ܪJ��eĭ�����l@�X��iإo����9ĸi����a�e`�ix�r��u��v����`HdH�\�d�k8�lD�p��*��t��$�i����,�l�_.ؤ�
���L�p��T�lp�p�"���Įe������h�����������J������l��r(�*�}
4���Ĺa����̹rt�Xعa�e�����lnąrl~�������<���.��a(�b0�c8�dH�eh�fp�g��h��i|�j��k��l��m��n��o��p��r��s �tX�u��v�y����q
������.ܺb�d�f@�gL�hT�kx�lлm�nt�o|�p��rȼs�t�v���E����Ժs���#��a�o��2���t�����a(�e0�l��t���E���E��� ���8�e��E4���dah�kp�l������E8���d�.��aȾd��e��i��tp�~��k�_����ET.��<��g�56����Ȼe�m4��E<���	�Ua�d4�e<�fD�g$)lP�o`�tl�v��o(�e��E�� �.(��D`�g	��G
�s(�9L��E4�X�rl�:
x��E��d��a��p����Zh��S	������������h
������i`��ؼj�td�($�������o�ut��pl���2h�d/�J�eP���AD�p!�i\�@�.��a��b��d��e̽fܽgP�h\�i��k��l0�mx�n��o�p4�r�s�t$�v\�����|��/��J�n.�LaĽt���)���E�CD���Խ.�aWe�h�n@�r(�s8���pl��#���E�@64�l��#�p���E$�(����<�r��%D�eH��p�e��k��nD-�r���E��]|�n���E������s��A@�����.Ծaܾe�Li�n��o�t�vL��E0��t\�(���EtF�B�x�a��e�i$�kT�l��m��n��o@�pd�rx�s��t0�vȟ���za��b�d��e<�f��gD�h��i��j(�kX�l,�m��nH�ol�pt�r�s$�t�u��v\�y$����пe��iD�
F�����t������sH�Ŀk|�mT��x�F��F�����a�e�Wi��F���.��g�v�&Fx�J(�dH�mP�n`��$�-F0��pt4F����X�������r����l�a��e�gP�i�k�o�s$�(D�<F@���n��r�sh�t�DF|�LF$���g<p�@�t�l��SF4vl$�YF(�p������r��]�e���.\�ah�e��f��i��m��r�u�_F��m��Xsx�v��"p�������d��s��v���8BfF��JC<�mF<�J�'n���Me�lP�`,��e��(*a�r�ì�tF�������� �-�e<�iD�lL�n��X�zF0�gHZ�F����T����D�T��a����|�v��Ԧv����������e0i@i���a�b,�c4�dlNeT�f|�g��j��k��l��m��n�p0�r8�sT�th���\�F�e$�l(��t��F����
H�eЖx(@�lH�`�t��F����h�n�]p�e��i��j��np�[B��F��Fx^� j'0]��a��e��lt�F�	Խ.�a�d �e@�kH�n�NoP�s`�tH��g�(T07`�4�gH�%T���,�e�{
<]/P���\X�.t�a|�eh�31hp$���r�����e��u������FT�����<�F�M,|����d�i��n��s�t4�Fԉ'X��tHM��	 �p(�s���D	B8 	�p	dD�kL
��C<JL�t��F`��`���(���t�a��e��u
�F����l��n��r
���t���)0
�
�8'h�F���}5���
�.,�f4�gD�kd�l��m��n��p��r@�sH�td�ut�v��	0�t<�sT�t<�)�d/h��\�ix�j��l<��F�
����m�Zs��t$�Fԇ�F�@���i@�a�����e��p<�F]��l������d�e(�m0�o8�shg
'\�.�t�B��,��F8���T�r�����F�i�;��Jl�a4�u�	��.��a��e4�hD�i��o�u4�yh�����
h�.��d��f�i,Pl(�nX�p`�rh�s��u4����-����f�iGT�
�����lX(��a@�dH�eP�s8�	GPGhG���!G�����.x�t������.�������m(G$����a��d��i��k�n�rl�T.G����k��s��6G�����.�l��H>G���i�J�.,�s$
FG�B�1L��<�ah�dp�e��g��n��p��s�x$��P���]x�ehNGUG�\GP����e��g��kгl��m��n��p�cG�$�iG�n ��H!.��a��e �� [@ ���g �k(�m� pG�T��.T!dD�gT�kl!�# KwG�!tL�t�!�@�!�����`��|���!v8��b��m��r��s��y�\~G0"�G8"�G�^���e��t��GD�3P"��"�G�"����d#�a�y g�d�e\#����m$*��n�*�G<�aH�rh+��p�e�*0�l�0GT3(d3��P�a��b��d��e��m��n��r��t�3�	�3�4J��l��r�3�+�L�e�4�5�G�4����g��n��t<5�G,^�GL5�6247��e�7�G�7���d�8�	D�aL�dT�k\�ld�nl�pt�r��s��v�d�8q
h98,�Z�9��9
�9���sľ�t:'d:��kr���erit;���k��l��m�p�r�t4�vX�y����`������;��;�0<�G <�0�<�G@<J�k0=$d�����.H�r`=d$�aP�e(�i~��=2�=->�p�ex�k�=�G(>��?��T.��a��b(�e4�f<�gd�hl�i��k��l8�m��nUo��p(�r��s$�t`�vl?��l@��@!��l�o�s��Bad!�@B�G\B�dB����4E�Lnr�F�H�P�ate\�g�G.�H�xJ��JE|�s��t�K�p�{�K����a4L��e��k��r��s0M�G�MN�GPP��4,e��f��i�t�u�y,��Q�G R���kU��T��r U��Uq
@V�Q��$���VnT�i��o`�s�W��W"L�d�X�TZt(c.��e��i��r��s�Y��h�dH�g��i�Z�GxZ��r��t�Z��Z�\-�Z�G�[HP_n��e�i�r�_T.��r�_H�_H�`H�e��-�b�la�� �dD�e\�fl�i��s�b�.T�ixc{AdH�d�Pdd�e|�u�!%g��f���kt2p��tXgW<g���ii���a��e�i�p�s�y`i*�h(��n�i"T.��s�j�j���k�l�m���]<p�<�ru��.�t80�aT�e��u2L�g$x�l�i�x3@�X	.��a��e��i��o�Yr,�u@�yl��z�z����a��i��k��l,�m4�rp�s�z��z���W.��e��s{d/4{xx{���a�e$�i�{"�s�tԵ
�{<�|�~���8.T�e`�mh�t��yH~L�t�~,+x	T
܂
��i����x�g��i��k��n�r��v�����H���e��s�!H��������a��s�����(����s������s\����d8�eL�gT�j\�kp�mx�v��D�s|�-���
��(H��
h�i�%�$�/H��5Ȟ<HH����l������e��g��k�l@�n��r��s��vd.�t�S	��4.��]��e��g��s�t��BH(�����.D�x	�iP�IHx��|QH�� �lБt(�e���4�d\�ep�g�( �WT�s̾�,�:h�s��"����|�tl�����n�m��ih��#��d�a��e����n��r����p�-��e����	H�����l�r����tl�b`��8�m� �l T�\�aX�e|��$he��L�n��	$���������d��p������s��
@�
��c��d��e�kT�mh�n��r��s4�t��v��x��T.��n<�����e����E�r,�]�t�Pa,�e@�oȱ(8�et�] �rر�|��L�g�3
L��`�e<�
h�]�Zd��k��s��tH���t��6��kĹ2����eĻ���r�����a��e��kH�����.@�'!��a�e�#����
�e�l �t8������,�r��|0�iP�e\�hd�t���x�H�nP�IL�YH�J ���l��X�Mt��x��c��t�=x�M��z��@�.<����l��r�t0�������[e��i�l�s�`��l���e��T��_H�����e,�t���(���8.x�d��f��g��j��kD�lx�m��n��p��rX�sp�t��v��y����eX�������H��@�fHP�����.��e��j��k�nL�o�s8�t��CT�mH��tH��jd'e��{H�m��aP��� �e�k(�ml�G�F�HTK� K0�a���T�a`�e���.,�T.p�td��h�����|�����������������n��s���H��($�(����s������a��e<�lPrsL��X��.�n(�t ?
�e�c���d�.���24�t|���:$��i����D��,����.h�a����H��a��e���D�x��t(�����s���X���l��s��Et�d��e��n���p���.������.�a�d0�e8�l@�nH�rP�s���`��$�eP�.���;@�d�u>������H0��t���X�g��k��l��n��p�r��s��vx�� �����e��(	��e��
��d����t��a8ee�����a��e��n�u���t���=t��H����� �aL�e`�st��t�t8�s�����!.����,�tP$(���D�.$�2�qX�k��I���l������a��b��e��k��t(�h�l�H4�������H�} ��a�q�M �����a��b��c��dh�e��f�g�h �i4�k8�l��m<�nx�o��p��r`�s�t��u�vD�x����aP�b�cD�d\�e�fD�g�h�i�j8khl�m�n�o4p�r#s�*td3u47v�8w�8y�:z��ì����o��r��s��u���Ș�:����T.d1�
 �����|���(�a$�eP�s`�uЙ
���|��i4�tl��H����y�<�a��D�p��v��t�=��i0�k��l��n��s�Yx�������d�h��ܞt@�������e��f��i��ll���
̟P��sl��H���С����e�?j����
0����su���
,�.d�a|�e��k��lhr��s�t�u0�vX��.t�u�y��"�n��r���<%�����l|�]��e��l�'�X� 
�a���l������a�te��i�ej��t8�����
���a�@i �o$��`R�
tR�������0���
��.p�a��e��g��j$�l�o��t��u��������h�m��n��s�*��gd��0���h
��iXk��r��s��(��ex�]X����.��r<��#�e��e ��H0����sL��dgD�rP�s���el�i��s��y���H����<�g���H���`��	X�nظ`�g(��`��x�a���8�
��n����rĻ��e,��H�����������T�n#�a�i@�����b�e,�i4�o@�P�!nH�u!��W$�td���Ql��H8���
.x�a��d(�e`�f��g��k �l��m��n��s4�tl�v�]�`l��r��s��t�8 �,��`�
����kx�t��a��e�r�����.��lT�H|���.��e��D��r�����p�J��e����l<�mL�t���#C)t���D�t��R��X�ax���������p��((]/�t��f��r���Hl��#�=P�����l��o��s\�����(�t��a�t��k�l�t�Bv�M����H0S�H����r�Y�Hh� �l��(�iL�r��(��D�a\�eX[�Hd��p�d�e���x����.�Za��e��i��l��pl��T.��R\���]X�a��il������	��.�a0�bp�e��k��r��s��tT���k�m$�rP������d�eH��@�oP����I�
I���H����I����\�a��d�n���|�.��a��e�ci��I�������a��0����i��p��v4�eh�
����e �i8�r�M��M���t��2�s@��L��k0�tLO���!I�`'I���@�k8���H��8�����a��e��f��i��p��s��t�u�2�~-Il�����a��e��f��l��s8�������<�o(p�Z�����a
������o�r���5�������	a@�ed�it�o��s��t��mP�e(�4�s\�v@�". ��8����2����l�p��/$�2x�t��e��rx�3I@���.��e��g��k��n��sPEt �9I��3	D�T,���e��������kX�J�i(�l������nl?I4�i����8���<�y��l�a��e��o��u��y\PP�d|�n��x
.��t��	e��r@���a4�`+�h+]��a+���d���.��k��s�t0?�0
�0����e 4]$�e8�hCo�5pn8��7��0�eh>�P�y��<�p���a|�b��c��d��e��fD�g��hH�i�Kj@�k��lx�m��n��o$�p�r�s�t��u �vl�y��Ð��܅]��d�g�k�l�m �n(�p0�rD�sT�tl�ut�v̆�؆�
�EIx�r1���
��,��<�bH�1��xh��)Ԉ��L�rd�tt�� |����� ���b��ut������d06�|���lP�]��e��o��̌J��d�e<�iP�o��s�mu��y��äG;\���ih��
���.dmb4�fdJklJl�m�n�J���H�l�^�
���`�e��rPi�zn�rt�t�m��.(l(\��l����k��l��6����rL�X�����k̎����������-��a�i|nl��o�t,�Ì���r� �KI���e��x@����$�����|�tԖ��8�a��d��e��g��h��i��j��l��s�ou�v4��6�`�|����a��e;�P�g	�paLJn#����m�O��dHa��i@��<�#pm.�p�v8�RI,hX��ltlZI\q��p�i��~/@�s����(��DsaI���	�.t�dDe��g��k��l m��n�sl�iI��.��]|�a��e��r���P�.Нr:�0oI��]��n��i��(��e|�vI�����f��n�t��h`���e��}I���e��(�o��x��n��rd���0�e��iDLj��k��l��nto��r�sT�t Mux�vsy���@�������l��9>`���a��e|�i���H-Xd.0-����e����d`�����u�-�a�u���@Ĩ���.4�a<�e4rpD�t����R\�����
�L�rh�sp�y�PHRtȪ~��t���HM��������k��r��vȫ��
��a��e��i��l�ro�s<�tH�u�yd��@�;���I�N��(��	��.�Md��i�<k0�mD�nP�rX�sl�v���IT�J(�e0u����<�e��IT�(d�kH����a0x�a��Y�"TNd��ktl��n��r�Nsd=t�NvT��̱��f��i$v����e��i8�o�(0��	X�n<���g(�2�s<����e �khvx��I����3̵(�t��0�eD��T�t����I����P�\�����J
��a��b��e�i$�m0�n\�ot�p��s����oI���e��n�x��s�G
��s����a���"8�����n�'��n������k\�����a��B<�eļu.���`���H�d�P�r$�i��l4��h�e��o���I���Iн�ܾ��pt�d���ؾ.��a(�c0�d@�e|�g��i��n��o@�s��tT@u`@y$����@�"��b�l�mX�n�tȿ�T�p�(u.,�,�t�e<�r��d\g4Hmh�rt�s�QtX��I`���`�gt��
�����o)�I�(��n�.�����g��k��s,7v�.�
,1�
���D����o��"@Rm��r�t�v,���m�E�I�Fc0G?TGa
����h@��������2��t8�a\�kp�l\wo|�v�M���]oh���h�uT�fDZ���2��n����iD�l����kdc���x��s��d���m��n��p�r�v���,�9ܔ������a�d|�y��d��D�al�e�l��p�r���4�2�SkX�rd�s���$�a(��T�5x�eL��Ih�1t��������������e���r��]��e$�t����e@����4�"�cd�kx�m��p��r��sx����a47b�d�e�g�h�i�}j�kP�l��m��n�o<�rD�s �t<�uT�v�y��À��4�"\�k4��Gp���p�mt��2�����a��pD�DX���lTe���I������a�e�t4�9"���I ����ad�AfUg$�i0�l@�md�nUp��r��sTUt��vLd����s��(��I�J8�.P�m0��It�?
t�a`���X�g`�n��S��
���|�k��I�����i������t 
�M��o���'D��0T����g�k��s8Bv0 9"�����i����.(�e0�h8�r@�sH�t��Ix%�+���I�'J�(o-��lal�e���Գe��`�g��v~���.�}M|�e`+.���a��2h����o�s�2O��J�'.�e��f�l�m,�sHxt4�vX�
Jh�*�7>����l���$�rkJ4��l��|�����	�W.�h��j��k��l�m�n�v��|�J��p�o���x��P����TE�X���l��o�����T��x������M�eh����C.@K���eLK���k��������$J,�oT�XT��J4�lL�m,�*J��At�i�t����`�e��Yh�s��Ql�q
��r��t��v8���������^1J�^8l�9J,�q
��d�s��AJ����.,�a@�eL�h\�i|�j��k�m�n�o�p�r �s(�t$Eu�v�Xy��4���r����"8�nX�GJ����T�d�kt�nt ��\����e�MJԉ���f|����a�i�o�oUJp����.��������
��|\Jp���eD�����$�GJ��}8�eJ��kJ���*�����W.X�a|�e��i�o"r 2yX�������P�f<�sl�t|�qJ��yJ���-t�.iH������d��k��l��v4�������J\�n��?��-l��������J��������`���r��`�e����
ؾ.X�al�e��i\Yj(Fo��r��s�t�u }v`�y�ä���k�El�
p���@��.�xe�|gik��m�����k��s02p		��J`��JT�0�t��i�s���,����.����������-�k�Yr�sL�@����-8�aX�l`�oPr�L�l4|����D�uX�?Id�����rL�"&���t�a��bp$f�h�i�l�m�s`��>��:�|��t;���#��#�k�M�e��#PE@DM�e�t�$$�e0l<�oT
��	�e4�l�x
@0~�ah�e��i�r��\9{�8`�ax�i�;��Jx.��r����M��s�M]��ol_b-x_����e�^����i]K�e�r��8.G�����sa0eho�r�u�yHw(�s�p�y�J�y��(aHi�z�J�z�@m�+����Tid�x\l�rЅ�J����xep�n�i�����e����l�s��Z�����Z�r��@a�\b�c�d�eHf�g�Kh�Kix^j0kl�m|n�o�p 	rp	s<t�u�v8y`� �dT���	8dlgtk�l�m�p�s�u�\vD��t����<����|l�sd�@��
`��<�x3����p������JH����kЕ*
ad(e`ilotr|u�v�yHü��T:n<���J�JX� e8m(P��J����@���Ta@�Xs�����5������i�����nܙm�f�i�m�n�rst4��#`��#H�dl��hT���af��kX�tp��<�!k|�*��2,a4t��g<0����|n,�]<a�e�l�Ko�r�t,u�]��.���pe`�����H���J��t'�H'�������eD�iP�X�.x�]�e�j�nu@��#�oP��J���n��*$��.hr�]
 ate�i�j�k�o�r�u�y�È�J����`r�i�t�,�5p�-�����]�jT�o��2�l8���P�rī���a�tPT�J���n�����T��HaTd`e|f�g�i�k�l m<nPs\tpu��(��gL�W
�e��lil�a\�J@�teT����l��dp��J���e�n��oI��<��.ܰ�e�hi���Jh�����n��X"����`�
b�JD$��(��0��K
���Htt�heH�ȴd�����Wxb�t\���	�a�el o�p4s<uDyT�P��l�T.�dr̛�J��2�e@����i�������ai���JL�Kt�
K,r��KT�������ȸK����L��$��`b�r�zи��ha�d�e�f�gij k4nHoXsHMtlv�y���u��W��mT��@�'H*���s��t�e��K,�t$�l@0��3,s�7%K���@e�;`�p\�,K��Pk��pԉt���xap_�����2K<���l�m�n�0p�r�v4�JL��\�]�s������2�����a	l	r	s8�����a��),�����<	aH	e��op�x�4	t��$�f$�x��T	k�	m�	n����\	a�	e
i(
jL
k�
l�
n(�o�
p�
t\by g�8����2�	i�kNl1m�	p�	rd��F�w%Tx�.����	a
s���
g 
o��yD�De8
u8�X��	d
nl���@
at
i�
ou`�����@��l
ld���0�k�������8K����
�����
�8���
r�������
t�����
a�
ir 2y(����-T.Hl\��D��e���4y�����X�AK��J`a0�ehjplxr�s�tp�
��hHK|�T�����
l�9<�M�b@3t�|?
�����d,�W�n�rT����aio s�����!e�tX,�t��sp����i���i0k��i`�������.te|t<���@r������T�����h|K�.��� �`��-�����g�Ok�Or�����a�e@
i�
o�
u�\����.�g
l
n(
p0
r8
v��q
H�NK���
s��t��t
th���dt���"p
n�&TK��L
n|
r��tT
aX���d
g�&TK�i�
r��`����
d �q
����
b�
e�
n�
r�
vx�x��d�Y��]KX�(d��
s8����
e����
r���0��(����@�H CXa,Ql��u���D���Pg�dr�]G�a�eiTu�È]����g�t�d���Yem�.m���aԭr�k�d�fP�i��s�t(n?�|�6�{���i`�� t�n�h<�g<nHt�J���t�W(s���0e����Fe���� 9edt��������Za���lp�y�~����x�����V.�2L���e�����n�r��cK���a@exi�o��������dg,k�m��.����e�(���$u�����8g\i��kD�2���Tn����xVi�sh�hnd������s��.��a,��l������L��a�e�ito�s��2\��el<r`s(gl��i(����k���0eT�hVftV��L�����Wel�k8�t<B.�Ht��kD$���Y��e]�Ynr t(u�hv�e���aDb`chd�e�fg#h�i�j�khl�m@n�o�p�r8s�t�v,w�m��f����.`gZlgHhphP0r�g��8eXlTs�hiK�iS�i�2a|e�o�jPX�.�r�j]D8a`kRply�k�l�t�ld�l
"�mxPnJ�f�r<o]�.�R�oX�eip�
6��ptXp]aHeTg\lxr�st6uq��p@n8qg	Prt�^�*�rtdv�rKla4e�s2hs#�tpu�t���l�n�u��uK3<vt�e�i�^j�k��l0s<t$5u�vnK�2Lw�e���%��dhw�n�ytKDy��(iz��c"	�{��Dm8{WLr�t�z��	Xa�e�i<�j�klLo\tp�(�{K�}��e�|"�l�r�}�K�v�~���a��Pje�~"�n4�K�i��x��d;b��g�a(e��� g@t��n
T���8t�h
L�)14�Tr���X~��h�X�"X�.���|a�b�e�f�g�i�8kp-l$mkr,s��K����.�����el�"�n0�n#P�/Hl�2s������et��(��K��8.@��$Ĉ�hape�g�i�o�s�ųy �i@�X�d�l|�(����t�s�-_@���b��I2L�t�a�lLp������d�p!x�������lppr���K�]d,g<l`ohtD��K4��Kp��4eT�|��K����L������V/��ܔ�k�l�n�s`���xade�g�i�m�n�s�tv ��W`��$�d�t��?
|����gHAt���0��a$�.\�'��g8kLmTn`rht�(Ds��������%t��?&Ęxte�
�x��E��|a�D���t<��n�
��e(sH�iT�]�a ��t���il��K�X������il�eܡ2ȡ��0��,�8�\adelhtj�k�p�Fy��������@�pL���|e�u(�-���H���p�]�a�e��i�oT<yP�iX��4.�"r��J�;a�e@�����gr�����de$s�X��#8�l̵�Lahi�o�GÌ�
���l���Te����\n�p`�
P�"xi���^e8�.�a$eLiPo@ uT!y�!��	�K��P�t�����a����	�bf,kXn�p�r�s�tv8��@���i��.$��� aDt4O
��<oh������Pehst�LF|k�M`h���te����e�o��2D�5��4���4.�s�����e�ü��"4~�D�������J�e0����.X������.(�]l����aJcld�f�g�i�klnXp�r�s�t0v(��K����dsP�4��Mxu4
�i�����nt��Мt@������.�e�l�o�$�$��]3`�	�r,�K����a��K���a8e@gHi>n�cs8i�n
�K�h�Pe|rx��K����hs@��pe	J��o@u|
d�i�k�l�n�u�h�d�o|->$�^(
��
���a�ot8)d�Kld(l|te�TK`�<aL����
Db�c�d�e�g�k�nos(t|�u4vHx��	��$#4]�iT]�.dS	�]�k������a�e�g�n5D�9���K� ����t�!I(!�� o"J@it"i8��04��
�.�a�e�f�g�i�k�l�m�n p s8 v 4��(r�5�'�5(6�|6��Cn�6(h7J��!88���. 9]�d�g��ؑ�$:�r�9�� e�:�( k0 lL��$�:�K�<MS��.x a� e� g� i� k� l� n� p!s!t8!v�R-��.̌�
�S��� d� t�F��S�TM�M�KT(� n�T�0XT��� l�TL� d�T�
�_P_��� e�U�x^a!kdk$|Vd��a��e0!is�7xW�0Za8Z��@!rZ��H!dl!g�!k�!p���!gX�i�x!eh�] �.�!edZt�!k�����&��.�Z���!e�[@=���"��!��!��\
L<\���!a"d("k0"m8"r@"tP"y�\����. "td��)]h]� ^
3�^�|_�\_��H"al"e�"r�"s�"t�_Lx"t3��_]3�_�J��3�`?�ha�"e�"r`���"d�"k�"sx`L�`�"t ��$���`���"l�a L�a��"t�B�.\#a�#c�#e%fD%hd%i�%j0&k<'l�'m�'o�'p8(s@(t�)u�)v*w$*yX*�4b���'.��b|#g�tk�#n�#u�c��g��gL�Z�k���#r�B�#h�7
"�B���#a�#k � tn�'.$d$eD�g($i@$mH$n�$r�$s%t%v`oZ8p_�o�$rqiIq�� $d8$g�qtu]v��d�.l$d�$k�vs�$txv�dvtd$e|$i���4�w]�$a�dedw��ux���.�di��k�$o�$sXy'L-L����$.z��$k�$t�z�|{��{���va8le�}Q�~�,%ð4L���$%�l�x��p��M8%o_�
�]P%l�X%lx%n�%r����0En�s���8�Uԉ��%g�%t��%e�%o&u�%:L��BL�%j�����%s����r�%v��C�����%.��JL����&k&r܋���� fdDfnT��� &a\&i|&j�&o�&r'v$'y`�@��T&s������h&d��Xp&e�&�L��4����&�ȓ��&ld����&l�&t�PLh��>�Ԕ���&n����&e�&u'�ĕi�A�����&�p�.'i̗���.4'n8Z[��8P'oh'u,��(�����.`'tH��h��t��$�p't��Mx'iT+Ü�M�'d�'k�'m�@����0���'tܦ���'s��H��'kzl@�X�'e(i,(o���(k�WL$�](e�����$(r����K|(a�(e�(i)j )oT)r�)u�{v�^Lܺ��h(b����p(b`Hd\zk�zl�(n�(rD�t�zu������(d4ig������H��(dd�g8Ik��L�g��k)lpIn�It@�^ ����(k|�"�������)n8)r8�eL����0)s�����el)n��D)e�Io��y�)��� a�����t)lh���|)�X����g
�����)a�)g�)l�)r�)st
<���)e��x�)i��lL(������Jit��*i�������*k �M��l|k(�t����Tvap*n4��4*k|*v�*yl~���*�D*�L1��mh*i������0��;�*r�����*e`��*t,�<�t�*aP,eL.h`.il/l�/od0p�0rh1sl2u�2v�2w�2y4.���
h�.(+bL+dX+fh+l�+n�+p�+r,,s<,v��J8+aD+o��P�!n�qL��(�r��Jr��t8����wa�+d�+e�Ul�M,Ĵ���h
�+lx�i<����+a�+e�+g��s�Kv(�t�����]<�e�����+p�������+.,e,Lr,tp����wL,a��Y���`���$,e�j�(�Vi7k��r\�h�. �a�,d��e�,f�)g�,i�,k-lD-mx-n`�o�-r�-s .t��*�La�,e�,r�~��,.�}L��%t
fH�J�,d��nt��L@�J�,vx��Ll��L���,v����-e8-i��.d= -e��",-tx�(�xaT-n0���sal-eu<�d-.��*��.d�aD�eP�i�-s@�O�.�e����.�-e�-u�-�$���-i<�J,Vn��f�������-����Me.i.k@�l\�������e�����.(*aXNe����D3�3���3����e@��X.a�.d�.ex�f�.g�.n/s</tЖ(�.l�*�.e�~���.j��|��@*d�.e/g/i�Ns�.���0��@8����.����p	���j(/t�
��
�� /i0�H9<J4/elm�.���H/e�T/dhG`/e�/i<�|/n�����.�/f�/m�/n0p0rH0u��L�J�/r����4.�/rk�L@��/n��Yan����/px����0a��d00e80i@0m�����
���L
P0r4�X0a�0�(��
�G��t0s�G��|0� �,���0a�0e����0k�0m�0p�0t��0a1e41oL1y\1�`�'h�*��L��X�	$��1p1s$1v�i0���P��,1mD1p �T!��!q
�!��T1�#t�1a�1k�1l�1o2t�c�\#���1f�Pr &��?n0&���1a�1j|&^	<'8ga�'��'M�1k�g)��1l�(�1l@(K�1i2o,2rL2� )���Qp���T)$2aX��
���82y����@2�,��	8���X2ld3��`2g�2m�2n�2t�4��4�g�6�2e�q'478�a�2e�2u�7i�8�8�2n�8i�8���'.�{e�2t�:��:��;!t;��3l(3r��v83y@<�	�=�L�=(03r>K@�l?��L3l�3v�?��T3a�3b�3d4e8�g4i(4k\4l�4m�4nL5r6s�6t(7v�@��@D�@���3aSe�3�\BhdB���3�(P�C�3m�B]�3elE�4E���3l�+rHK�J��4n4L�<4aD4eL4t�L�L�L��NL�PRPP��T4ap4e�4l�P�LS�LSx4s�R�4e�V����m�4s�X��Y��d�.�4d�4e5g(5i<5nD5sTZG
[RDE�L�';�4v�[�4s�[t5d�4e 5sx[�L�[�9�[945oD\S�\�Lt]YHlan�5a�5e�5i�5o�5p�5s�5u6�$b��a��t5t8a|5n�5sTb(xc�	�b�5i�$kPdK�5n�d(�eM�f��f�Dh�Lh���5k�5s�U��h��h��6�i��
��.��aH6bP6f`6il6mt6o�6s�6t�6u�l�L�~,+k(�jX6nTlpl��m�m|6t�n��m���6rPo�<p��Wg�6j�6k�6r�6t�s�8�L�t�Lpv�n4vt�6e7o�v��v���6r7v�pz��.$xM7a@�P7a�7e8i�8o�8uzxd7kl7n�7r�z�|���}e�7g�l}�L~JH���e�����7d�7i�7r�7t�Că���7t\�C�7t̉�L����7.�7s���Lp��L����a88l�����7kD8l|8n�8s�,�LH,��$8s<�8,8ax���de.\8ad8el8il� ���Đ�,�-���t8g�����a�eH��h��Lt����8g��8n��L ����8a<��@����8d89gL9hh9k�9m�9n�9o�9p�9rd:s�:trv�G�X�9lD9s�9e���,9g<��̰?��rh�]T9s�t\9k<�l�dL���t9eh���ue�Ⱥ��9e���L�9r�9t������9e$�(,�(Ļ�9k:n����9a:e@:i�M����:n������g�p4:sT�iD���,:tĽT.T:e\:s�$����t:k|:t`����0�i�:a�:e$�����x��:lH�n��]�:a�:e�:lt�i���`��:ld�M<���:l0;r0�������;e@;h��;nH;rP;t����;eX;s���'4��	������D������`;l(���h;f�;k�;l<m<n <p@<r$=s0=t`=v�=yP�q
�;e�;tH�����;tTK� K�;a����;ed�M,��;th�M����]e����<n���4<s��i8��%,<e��Jh<a|<i�<k�<n�<s=t$����`<s�2$�t<n|�� �.�<a�<e�#�l�����]�<e��
�����<.���<a�<e=i���<t0I����<r����=nH��,�h�a���D=aP=eX=t(���'.X�2���t�dp=d�=e�S��h���x=e�����=l��(�=a�=e�=r�=t�����M���=m���x�i�����=e���T.��%�=nt����=e(>kp>l�>n�>p�>r,?s4?t ��H>eT>i\>r����.x�<>t��i��q���T�.����d>a�>e�>s�$�g�>l�r�,����t�a8ee�����>d�e����>e�nt��.����J.�p?t�����>e?i?n$?s�
	X����%M�,M��,�L?a`?et	D?k����X?n ���	��g@k@lP@nl@r�@s�@t�@u�@v��l?a�@b�Bc�Bd4Ee�FfHgxJh�Ji�Kj4LkPPl�Vm�Yn�^oP_plaris<pt�wu$xv*wHyxdyy�y��@s���0�����.4@a��l<@oT�u�
�����.8���D@e�ld@s��������.�@d�@r�@sH�;���0��\�.8���p�h���@��?X��@hD��)\PT.$Al8AnLAr����@aXAb�Ae�Ah�Ai�AjBl(Br@BsPBudB�p�:\��At@����0At��X��DAkD
holArtAu���q����|Asx
�AlH��Z�At\P�As,�	�Mh�3Mta�A.t�Ae���Ad����An`X�AaBe�tBsd'�24Bi�(���n$*��++�HBe3S ��\B��|i8��pBn�Br�7��xBe 4���Bh�5i�Bk�BuL}ih9�%=h>]
Ca0Cd�Ce$DiTDjxDl�Do�Dr�Ds�Dt�DvEy E�>h�.Cs�C����.�F'�FCg|F$CaHCrXCslC��G��u;�G�PCe�HJ�H��dC�0I;M�Cr�H
xCat�b�Ci��k�Cl�p�Crp8s�CtDv,�8�edK��r(KJ�Ce�O2PM���Cu�S]h8o�XnĎtd���Y-DoD\X��n�[Da4�e<DoDDr�`�4a
 d
lc��LDif�X�e�e`Dg4e6lDe�h@M\h�Db�De�Dt�DvPi�)�m�0<EM�m���Dr�mJ�De�m8�uKt��De�|������	�`r��Y><���Es����E�܅����
,EadEilEl�Em�En�Eo�Er`Fs�Ft�Fu�iȫ���sa����tEnd���EdT3e�Est�JM�t�ErJ�����Er��t�Ea�rx�FaFeHFi,�lPFm��-L�����Et4�"Fs���.0Fn8Fr@Fs`����(��(��i��������XFexFi�Fk�Fn��] ��|����Fj$�Z���Fr�PM|��:�M�Fl�	x.Gb�e$���FeGf<Gi`GlhGo8cr�Gt�G� �;��(Ge�"	���Gm�Gr�\�4GlTGn�����LGe��%a�Gr� 	���xGs�(�Gnt(]�Ga�Gs)(D+�T+8�Ga�*��Gl�p�.�����G�С��/�Gg��r<Hv@0���GaLHbXHe�Hg�Hi�Hl<InXIopIr�Is@JulJè6L747DHy�8ܢmxHn�Hr�Hs�Ht�Hv�>��@ndH�tC,��
�E���H.�Ha�E�HGXqs�M��J�Hs`Q��HaIet�y8Q��HdIr�Q(	�Q�l�.,R�T. Ir,Is S�h�.dS�W
�VM4IaLIetWP��.�Y!T.�^��Ii]KdIe�I�l_��xrx_���Ie�a��a���I�,e�db#�Ii�Ik�Io�Ip�Iv�ex�g
x�.&,hX�Irhl|,tlX�Ia���Js��Jn���Jal?��(Jy�n4Ja�rUMtr��LJn8r��TJyG��`J��sM�Je�{��y���JlT�����	�Ja�Jc�Jd4Ke<Kl|umHKn�Ks�KtH�pЕ��Je(KiЖiX��JlKnKr�d�4.Ks��d�i�_i@� KnܙT��,9lи��@9e�ug�Kn�O�+�KgPM\Ki�W
hKm���tKs̻[M@�{%�����Km�Kt��������Ka��2D��Kn�Kr�����Ke�Kt��i�����MLave(L���(����Ls@����� L� ��T.�La�LeMh(Mip�j0Mk�Ml�Mn�Mo�MrNs�Nt�OuPv$Py8P����Lr�Lt��Le�Lt���`M�	���.�d�Ll�Lm�Lr`<H��x"�Ln����La��eMi�fMd�q Mo�
l#���apMe|Ml�Mn�Ms�&n�zl�&��LMaX&��XMt�$dMs�'�
L(]4d(r�-��,�Mm ,�Me�Mu�.�(�t�1��0m�MoX2�Mg�3��<JT<��MaNu�@�	�@��Ns4BJ��a@NehNipNk�No�Nt�Nu�N�C�kTNl\NtpC��D��P/e�D]lE|Ni�E�<G]�H�JiJ�Nn�JJ�J���N� N[
�M��Nk(Ol�L�Ns K�Ne<OiHOjdOopOr�Os�Ou���+e���Os����O�,�O���xN4OdOtP��O��POi4O��XOr@P�8@Q��P�xOl�Op�Ot�Ov`Q�)lM�QKxQiF��Q�OaL�e�Q���Or�Q���Oe�R�OePldSZTi�S���OeWX$)i�Z��Y��Ps�[a���HP�0P��\�]��.�Pa�Pd�Pe�Qf Ri0�j�Rk�Rl Tm,To\Ts�Tt Uu�Uv�Uy�QÈ]�.�Pd�Pf�Pk8�r�^�_�,`*�_���Ps�X(�h�Pt�gt�Pe�klf��k$QlDQmPQntQr�Qs�u�rJ0Qe�r���g\s5ds��<Qa�t��ؾ.dQilQspu��u�
�v���.�Qe�Qi�Ql��oT�P3Dw'�Q.�w2����xJ�Qt�z2�W.�Qe{�W.�~���V�@V���`V���QlRoRr��#�<��4ReLRkpRn����n\��4.�]@Re\Rh PЇ�Pl.���dRa`>e�Rj��b8P�i��Ra�Rv��C�����g�m�Rr���RaSeTSi�So�Ss�Su�7yT��c�
�b���Rm��( Sg��Rg,Sn@SrLSs�oE�ad�����gDw�(���8Se�3!0�.,g�Sn�Sv�%��tlSsh���tSg��@ؗ� ����Ss�St�Sv�� �pM(����Se8���Sl�Sm\�t�Sv�x��������J���T�4�P$�TuL�l+i@TpPTvP�p,�
��HTa����*k�TnxTp�Ts�Tth�X��o���m	�]�TrЮuMȯ��Te�Ti�ToUrH�,eg�kT|s���l��mܲ��Tr��8���:�TeUiij2�������Ua@UeHUkPUpXUs`Utd�*ض�\���������X�.tUa|Uet�zM������n.�Un�Ur����Ua�Ue�Ut�Uu��0�(h����.,ygL�i�Us��S�������Vk���о�Usܾ]Ve��tVk�t���(Vah�e�0Vr(�2����LVe���TVsxVy��M����pVe��t(�����Vl��"gWr Wu��v�����VaDWbPWcXWd`We�Wf�Wh�WiXjXk$Xl,Xm\XnpXo�Xp�Xr�Xs0YtXYulYv�Y�������We��p8Wk4��n��x,We,��t�eX�%l���"pWn�Wr����0�d��h�Wt��;d��Wi��p�����Wa����Wo@��h�"�Wd��e�Wn<�{
<����Wd��b�����Wa܉t������Xa��T��b%4��<XeDXiT�2`�hDlTXs��(��hXa���W�Xg�Xk�"H�i���Xa�Xl�XuD��T��Xn�W����Xe\��@��Xt��]&H�6YeYk�n,�o(Yt����.0����l���Yu,���tDYa8��2<Yl�pl�PYt���D�dYed��`�s<��xYr�Yy�����Y��Y��
,X"L���.ZaTZd[e�[g�[id\k�\nP]ot]s,^t�^u�^y�^�Й:4���Ya��Yd(Zl4Zp<ZrDZsLZt����:g�	��	� ��*�t4�.pZaxZe�Zi�Zs��Xd.��k�Zl�Zn�Zr�Zt|��xX��MV��Zk ]�Zs�ZvXV�M�X,_���Zm��Ziw�\��d�6e([i4[lH[nX[sd[v�Jh�.lJ�0e�?
<*@[g��o�=	d�.�#-p[n�#�M'�M�%tx[d�[e�[j�[l�[o�[r�[s�'����e\'�[l��mh(t(;�(�|)��[u$a��*#ȅi�,�T.\d$\f,\m4\nD\oX\s�-J�.
X/(�/*�0�Md0�<\n�1�,1��P\tD4�x\l�\o�\r<6��6
`7*
�:`��\kx��\a�\e�\i]o$]s<]tH]y�<��@?f$�s�\v�;�\r|]s��"	P3(d=�\v>]�@�4@�]e�>t]t�U����lPA-0]a�A�<BT.d]sl]t�F��FA�H"�]j�]k�]p�]t^vԊ�PM�]i�M����r,Q�����R�]k�]l�]n�Q���]e^ot��|��M�R���]kS��S��U(�U��^l�um@^s<U^aH^r�^uXV"0[�`^el^iP["	X[��X^p�[(�\�Xrg�\��t^n$]�T�t$`2hb*�^yD$���^��^�`��1<d��^d�^g���d*�e���a_b_m�p _r�v�g�_s��`����d<e4_o���P����<_v̵��
D_a�_e�_f�_i`l`n8`oD`p�`r�`s�`taua�X��M���_a�_r��t����_a�_e\��`���_n��U��2.�_n�:
l����_s��|�i��4�v���`e������$`e��,`p�m\`ep`l�`r�`s��d �(���h`a�����|`el��@���`al�*�
�`e�`k�`p�����`np���`eЎ������X�`r<�U�����,avt���a����t� $ai���ad�f�ak�al�am�an0brTbs��tlbu�bv8���8aa�bb�bc�bd�bedfDdgPdieklel�em�en�eo�fp��r�fs�gthu�hv�hy�h�4�J$�"0�:,����ayh�(����Pebs$btt��
X[��be��br����P�.DblLbv��
h��
�����!.dbpp�&�
xbk<��3(�eX�"H��T���bdh���bo�x���bi�bo4�2��;��
ca,ce@cgxci�cl�cn�co�csdtdv��(�]cl,��X�2ce��� cr��
4��8cgPci��Mdcs�N���\ctt	���pcn�x�ce�ci�cl�s�ct�0���M���M�?
����cg�����cm�s(
�|
���cu�
����a�%e`;h�$de�|�b4dd
�2�<de�T.�da�de�dk�dm�dn�ds���tde���|dn�PX�.X4e4]�dn�drX�.�n�*�����e4�n��sd�t8������dp4$��s�#�de$ej@elPenXes�%�0eaD!�H,-&8ea4�e�&��'�)��tg�)`ea|ee�)�g�es(*����eT+�`+�eg+�eat0�M@0�ea/]�ee�es�eà2��1���Y�04��T.fc$fe4fm@fp`frhfsPAt85h�5��8O88��,fs�9JPfiXfp8:
\:t:�:|ftT;(`;��tfa�=`vlX=�fe�fu�>bX>2�ftB(�k�fl�A��fage��igk(gp<gtdgv�g�PB(�C�LC�fl�E��D��gr0HW�G� ge�H�|H�4geXgiX�jTI�lIPgl�J�pga�J2LK"\K��xg�`��=XL�go|K�ge�gi�go�gr�gs$Ni`O�hP�O:�gi�QT�P��gt�SP<�e�S���grS��he0hgDhk\hllhm�hn�hv�S�M<hl�HmT�The �n�L�XTtpX�M�T�dho(�,�T��xhg�hkTU�MxW�PX��Dl�W�he��sp[
Z���hs<\X�hd��y@=���h��\�4b(`inxir�iu�B���ha�ic�ie�jg�jh�ji,kjdkkllTlm`lnplo�lpms�mtPouhov�o�|h�g��Xiepig�h��i��|�.�ie�ik���j�in\j�
�k
�BMtn"�ia<jd�uiX�kPjlt�mpjn�jr�js�jt�jv���.�n��il�o�M�oje�o��jl�o
jn�o$ja`o��0jv�s�s]Hje�;dvt\jiv��djd y�
x��|jkGm�zz���ji�{��t�j�}*�}-�ja��M���ja�jo���l�������jdkn�����Dd�yn���M`���kg� ka<kiԊ(�����Dkf,xn�kr�ktT���Lka�ke�ki�kl�ko�krluly(��L�a���|��Ў"�kn`��
@���ks���MH�e�ko�.�d�����kuĕ��V�(����kt��	��8(e4li�ư�x�,ln0��D���@l���MHlà������hlk�ll�lr�lv���ldL��Ml��li����M���le�����lrܪJ�le@���liح�	����lnȳ�����lr�dt��maDmjXmk�ml�mm�mt�mv���0�ض8mà�����Pmjlmo�mu4�����rX��(���xmr���$�`�*�miHl�`��ظ��mi�mrx�j�������nntnv����ma neXniL{jtno�nr0ou�Q����*H�"T�d|�i4�nLnt���M�t8ne��@nt��idng|��&,��@����lnf�nnX�r�nv��N�nn�mt�h�D:�����n.�nn�nr���na�ne�Ii�noo�(��`�N��Y��n��*"�|�v8odh���(o�o����"X���JtH������<og����Don`or(�Jt���oa�oi������xoe�ot�����ol�onĻN�|��]�a�����ok��
�oll~���o�p�H�����oex�N4���om�yh�N����pe�pg�pl�pn qr0qsTqu<���pa\qb�c�qd�qe�rf�rgsi�sj�slDtmXtnpto�tp�tr�us4vt�vuwv4wyLwzlw��J�pg��b8���
8����p.�pbȾd�pl|�A$�,+�����t�pe<����pdqoqv(�
l��)$��
����qm4Lt`�8\�.DqaLqe��/��/�� 
P�tqa�qe�qi�qyl�PT.(���qd\���.D�!N�qa�D�qa>�qtxD���.\�
�. �a�)erirk<rm`rn`�o�rr�rs�rt�ru�rvH�(@�J(rs�*4rtD�&Nx�J�zaPrmXrt$��t��8��]tra|rkX�u��)$���d��������(*a�rt��
���8�-N �-�ra�y�ra4��t���rsD�2Ns��r7N���re���s��i�mfHsk`sl�sv�?N�]4su0]<skXsu�H��Ni(��lsn���tse�M�se�su
����sn�
tP�	��sihG�se�s�x�Ƚs���t��s�p�
���sa���tn�i�te�� tl���,t��8t�����Ptihty$`��J�tm�ts�tv,�����ts8���@�EN4��tr�tàG(�G���t��8uaue<uiPuohuu�u�l��4����ti����td$2(un@��� ug4�L��4uv��P��Hul`us x@ �tul� ��!(�ud�!��|u�"�
#��u.�ue�ujvkvt�($�#�ui�ut���|"�ua%���uj�%�0&2@(";,�KN4���v.Tve�*vk�*t(vapve��i�vo�vr�vs|���
�-��\vkP,dvr�vv8� �-�va���00��va0���ve�/���vr�0K�va�vy�0(L1�h1�d3�e�vnwt�4Xrg�6p47	$wiD8�8��wl�s�8�.Dwd�88�:� t;|wr�ws�wv�wy�����w�Tw�@<�$=SN�wtD��`=XN�=UM�=(�wa(>�>���wk�wl�wn�wrp>��>��>��?Mxmxt�V`�V���waxe`W�<p
@�MXxe�xi�xrysyu<y��iH��Dxi����Lxdxxi�xr�xs�xt�xvă���n��Ј�xs\����xi���x.d�"��������x�����x�̌?��iT�k��it���xax���ya�iT�_N`���ye���$yr$���0y�8�_Tya,��Z@���\yatynh�
<��yr`���y�|y��y�`��}gl��ye�����yl(���yvt��t��
�����ya�yet����yr��ܕ����zm`zn ���zaؐbpzd�zf�zi�zkx{l�|m�|n�}p~rTs(�t��u��v��z�,�����hzj�zrx�8���zr�����0����zr�zs��X�..�zn���
�za{e{h{id@o${r4{sP{t�u0�v��[��"��r�9 �W.8�8�[B����,{i�ejH{t��I�Z\{r$�E�*���d{n0���
l{a�{e�{g�{i|l |m,|o4|uH|vd|��"�Sd�{k�{l���-�����{sĴ́|<�]�{r��l�e�2���{a8��|s�|e���@|n��(�����Խ�P|y���t|�X|���I�����||u@����|p8���
aa�|d�|e}g}i$}nx}o�}s�}t|�y�fNx�t�|s��W�|s�|t$��t�(����r}s ��$�.��`@}aL}il}s��S��8}n0��\v�@���eX}i��t`}t(�P6m�"�}e�}k<�mt��|��}m(��
�}i ��h�2���}i�}r���x�J�}o��i��T.0~k8~l@~m�	p�����}aH~ex~k�~m�~stP�p�(��(���.`~gh~m|Cs4(�������p~a�i$��~a�~ex�d�el��0���~a�~k�~t�~v���lI�H���~ih���������~a�����~l��a(e<iM��2 m�N@L�4s���T.8���Hate�i�k�s�t�0^t��x��d�e��f��k��l�s8�
<�
��]����u���a�e��0�($��-l����������������a��&h�.�����aT�ex�l��n	o��r��s�lN(�L�ll�rp�n#h��d�f�G�e���,����a$�I��'@�(X�"x�mp	a�e��o�p�Ԁgx
܀rh>�eH�i\�ot�r��u����Y��H�v4`��_t(�s�_��0�g�[<�n�iqN\hT�il�n�k��m��~�.�~��|�n��rt~��e�Etȃ�������������.$�a�b�	cH�d��e`
f܂gP�hăi�Kj�k�l�m(�n�ohp\�r�s��t��ǔv���܅�4�g<�r̆t,�.̌�h�o��s��t��È�vN��`�bx�m�j�l�9:��k\����}Et��̎��������<�����|Cr<���"ȂvԖ��
Ђa��e�g��j �lX�n`�ot�r��u���P��*�s�I���0�eL���{N��$T��8�d\���@�����̙��^x��t�Ph�eX^u4`�9<`����t�n|�t��5���������N؃n�����e�l�t4������(�l$�YL��8�.d���
��a@�e<Li��j\�ltorr��s��t��T.�dT�e
�N`�p�ax�e��i������Ĩ���t\����rxI��Z��aȄoЄr�K������n|�5�����t���؄�ȫ���.(�aX�d��e��i��o̅s�uPv�y��Ȭ���" �s@�tT�N���8�.P�p4�N4�t�.l�ex�s�h�l|��6���Md��i��t0�xh���"�Nsd=tl=v��ąc�i�#<�=��e�m0|pht��9	��D����g�%n�����=�d���J �a�id��	T�a`�e��i��nȆo�s�t$�u8�y@�".��Qmt�t�Qvd"���e�""�/(����n��OD����i��l���'�=��e���$�h��Іi�r��t؆t0S���Z����a�r�s�iyX\(��0�s�^(X�u.L�aT�mt`L�(x����b.��a�d�eĈfЈi�k$�m@�nX�o��s̉t؉u�y��4�p4�"��k��lću\�2@��N��?��̇s ��ԇe�i�v`�d"n�r4a
���N��.adUgP�i�Ak0�ld�nUpt�r|�s��t,�Nd���H�gt�g`���\�g\n���������.��xLUt��u��N�-�2e����r������p���À�X�.�a�e�k�l�����l���4�H�����N���n�S�/|�2,�lh��4�aP�s�2g��JX�.h�m��*x��h�*p�.X�x�a��i�����k��pE�إ�N�-��o��dh�l����a��"�m,��	�I.�)l��<�s8���lC�����^.	���d�.`�at�e��h�Ci��j��k��l��m��o��p�tD�vp�y|��4��
l�n����"�ytX��\��|���Hi,�q)��i,��������N��Ԋe|��-X�̊r��@8d�n�����e(�o4�rHXuX�0�/d�x�dt��\��� �v��������X<�a�N�ZtP�s`�]X�dH��d�n�������lE�`���Er�����.̋a�e�i(j�o0Frp�s��t�v��y����kd��DL�*؋a��"�|gk�s�vp��N ��N��Vt��T.4�eD�kh�r���N��N�t<�e\�s��N0��T�.��(`��N��i��tx�k0�t��s���{e�-��m��9:�d�k�n�r��M��a�i�s�t����d�8s�������m�p@����`������������$`�a���8���L�l�T�l��H/l�. /-t�e�.���r������@0mča�e(�i0�j<�o���/T.؍l��v�1���)e�i��t�6"�.�6���e�8P��N|P��s�O��r�O$�e�YK�st��$��.���P�eT���\�r���h��T��Ўn������a�b�c�dH�et�g��kx�l��m�nH�ph�r��sx�tp�v��t�|���؎lt7�|����sH����eЕ�$�e8�o@�rh�sX�i0�oD�q������ܙ]d.l`�n���l�X�d V?x�]l�m��u�����8r�G�����$��$�t�]
��a�b�e��g�k�n �o(�sD�t`��P�.,�k��d��N0�i��]�e�0iT������n.0�a<�l F�d��P�ixN����$���X��(��x.T���
l�a��eĐiАj�l8�m�oL�s`�t��ð�}3���sį�l���n(��#������ؐnܰ�a�e0�u|�(H/e<��d �i(�p`�(��O���`����P�i���@�tܲ�t�X�o��r��
����p�t��:x�epi��J������\�(��p��u��C���9��4��tȑl�uи��Бd �e,�gl�nؒo�s�tp���i�����t��W�s��:�nP�o�r\�u�(Y	�.|�D�m�
O�2���d�a��e��h��l��sL0t1(���d��\)�=p�[8��ODG2��r0GM��e`�"̒v��m:��jL��<��a�S���s���e0�r|��P�:(�e��.����<�a\�e��s@�.���x�o��ut��D�h�� Bbēk̓n������a�e�k��m$�o8�pT�s`�t$�28����>d4���ؓe�n��*��g�vn
l��rԔt����e��0�m��28�_D�aX���Z��L�p����ip��&k����	l�a��eĔj̔oԔr�s �t8�uX����'��'��eD���s�����|���a�e��
��i`�����r���kd�O(��s��t�e�4��g�,�n�=-!���D�y���L��,�WT.T���d�a��e��i��h���s������dca��e��i�o�u\��Еt\�;p���ȕa�e�r��t�u�����	��,����`������t GD�l(eu�,�P�r ,8�e�-(�]�|�a��e�i�s �uX^?�]��t�b�g�k��d��g��i��lЖs�n�hp��X�e�rtXy"T.�x��Ėe�n�t��v$�I�z��D~r�{���<��v��O����l s��L�aX�o���<�����e��@�l�$�dp�r���`���h�d,�Ъl����|��L��a�d�eИo�sLYt��4*���dؗk�l<Zr<JRe�ii�t�yp�\�b,�d@�l��md�rt�s��v�J8�e 
l��t�eT�i�Wo�I���\�e�<o�M��t�!����a��e�!��D�@F���sLF����a,F�	��k�D����s<BĘr Q�,QXܘa�Ht�p�t�QPb�rD$����,���c�hb�$�r�e]0�lh�r�e��8�a��b��f��gșiЙk�l4�mL�n��p��r��s,�tD�v�gPnJ��f$o-0o��n<o]��aXp](4u�t�<v�5o�sDy��klE��z��i(�t�~ O�t����2u$�T|s4��e�J ��`�lh�rĈ]<�ax�d��s��x���iԉtp�eL����hD� ���0��a`�����dȚeh�gܚo�s\�'Ԛn�����<��t���k8��`"j�s����eP�,egGlp�]�e<�r�)��MT�e`�i@����.��".̵�8����a��e<�i�o4�u<�yP��$��������k��r�����̛g��n0�t4���=i���
�t؛e�i�j�t����g<(9"h(�0m]/�,|t�s�
��$�t���.`�dh�e��g��m��n�v��45xHp�sT]x�a��.�����a��e.D�
�����gМs�%O,"�."��؜e04�b�f�k�p(�t�4(�9(�;.�;]�eSZ
<\���sv@=��`��D��`�l�d�`?�ha4b��.��l�f	�e����itn.��g̝l�n�rlp(ĝg�pzs�	؝i�s[
vx�.�g��s�v?
w(?xJ�.�n��T�8�eH�u�xvЎZ��
(���@�m��K@�ap�e��i��,�h�t���x�|�kp��ЪlD��������M��à��̞a|yeԞyܞ�ġ��2�������(�8�+O�����d�m(�n0�rܦ� �s��@��l�H�]@�X8�e����l�e����L�d��9X�eظ���p�t������������	����l�����e4����k؟l�yl~�������D�0O����e���0��;�r`���|�.�rL�
���e<�D�a��e��iȠrРvؠy���T.x�kp�l��s��h
d|�n`uv8���\�e��`�Z\�LXp����nh�|����a��e�W.��47��8���{e�?�dX�k`�lt�n��o��tL���B�
�d0�e<�s0CODQ�� �t�C$�n�D��y��D��4LPPJ�[�<�e�Y��h�g�^6O@��aءe �i�4Mt��.�a�L����g����n܂����i����̡g�n��'�������i(����n��]`e0�l�����kL�s<�-̓���8�n����@�a�e@�l�a��e���>���t�s�|�n<����rܢt��������aĢe�������t��i.����Тa��(����d�lH�m`�r��s��v��y��x �i<�
�����(�t���0�eh���<�mX�c�.����T�e|�k��nl�c|�t�e�]p�at��.,�����et�����	��e��pܣnܭ�4�.�s��x̣et��
\� t����a �e8�k|�l��n��r��s�t��,�n���(�d �(H�aT�e��.x�T.p�n��.��*d�e������e��� ]e��u$]�*�����n������e�X�ia����̤s,��Ԥe�f�à���� ��\�l�p��r�@s��tȥz��
�aХb�e��it�k�l�m��n��o��s�u�ybz0�Zp�dx�k��l����,��o����?���8���rH����e������d�����M�oH'�d!�ܥy�X$�a0�b8�gH�rT�s��t�Ԉ���e܅]�t �/Ԗ���ix���@�e��Xd�s��t��������n��l�r����x�e��;O�����t0����ZԦa�c�d�e,�f<�nP�r\9tT���`�r�7H����h��z�=�,Е�l��ܙ]�n �rT���X�.,��,�2и��4�i�i��]H�a`�e����nT< ��l�r�HOL����s�g��e����b��u����tp���B�̧kT�Z�f*
la��ԧs�?��ܧr@�X�o��r����k8�!8�����t ��� �n��,�ax�b��f��g��h��iШk�lبp�r�s\�t�u��y��$��/�@0����a�s�H�������c��dȨeЕ�ܙE �̵�8���B��?Z@�X��� ����b��c��f��h��kԩlܩm�n,�rD�sX�t��v���a`�b��c<�d�e��f�g̰h�iH�j�k,�lL�mh�n�oȺp�r��s0�t �uX�v*wH��|������x�ȩt��9e0��@�
8�.�a�ve�k�s�"l�A����t�l��	����$�k<�r���
8�P�i�i����Mp	a��b��d��oتu�y(�
��dD
��e\
�P
��ed!��+t<�e�pn�+����r+�̪e�/��.�g 4M�e$�o��5��.>����:z>jbl�vh>��
,�at�d��e�i��j�o0�r��s��uԬ�XEp|F���e$G)0G����a�F��l;pDQ��(h�H��nd�o�pЫr�UH�S]ȫoTa"�[ܫs�clc���e�i d\hK �p(�v�k��mp�mKL�aX�el�o|o5n��D�u�od�m�p3	�r(��k47@O�6��x�sw9	t���i��v|Zt~]��n�ȃ��m`?r�s�������@����q
�	$�d@�kl�l��m��n,�rh�st�t��v|�GǑ���l4�r�K�ed�]L�sĨ������T�n|�tȫ��\�e��sh���;<����j�k��m��t���<�O��]d��ܭe�hi��k�l�s���r`���KO$��d8'��e����e������0�lx��� �e@�nP�sh�nX�����H�k`�tD�H	��X��m���ԝe��y��p��$M��aܮj�l����n� J��l��Įl�Юe$W�'.���e�/.��v@0���aL�d��e�g��i��l��mİr�7(�78�l`7�@�al�e��o��r��y��ð7�.k8'l;p�8]�m��8�8J�8������;�	�8��iЯn�r��s�?v�>]�*sTUy(xAܯt�@]�etC;42�G�mL�rHG�aX�ed�jl�lt�r|�s��u��Ð4��3��D�t�G<�s$Ii8I��I��I�4Jt�rG������J�hT�`Q����o VA]O�sM�e�~��y��ذs��M��n�sи��^.����`qt tH�at�e�4i��jh�k �l|�o�r��s��u�v�X�ph�rT`��`�a�]��l�nȱr�t`����.��e��i��sL�0���n�h
$�!�*رe8�o��%l����.�s��PO !��aD�e�!�$�eD!���rD��d�1�Ynt�M,�a�!�8�v $��L�a�#P�nl#]\�a��eԲj�n��s�vX�y�%��T5h�$��n��sXW�X&����jȲt�&VO�&����ad'�L1iL(_ܲi�*;d(��k�X]O�+��iH, ,�aP�e\�ol�uH��.Y�-��<�t�,D�s�.�����.��d�bT�sX2��a��l��mгsܳt@2��~)@5����i���5���o<:�:�ȳa0;��iP;8T<�4BM|W*HW���rWX�a�]tg�]�� �ax�dشe�i�k\�l��sĵt�u�v@�y0��Th(hd�n�gtl�a��e��r̴siF!�hn��k�h��r��sDixj8kI�j�Ĵk�k�g�s4�v�x�`�bO�n�h<���g�� �e���,�s`�cD�l$��7gx8�a�$v��P�ex�i�����'.0�l�s��D��tĮ
Ю��e�����r��H���tȯ��e�!����еe����صs��fOh����n�����e@�
����e��r�~��$������it�t����<�e��f��j̶oܶp�s �t<��d���<Ve
WL���d����e��������a������r������a�T.��e��H���a�e�l�����.���L���2.��a0�e$�.�����V�x�mO���H�d�P�nL��	\�a��dķe$�gH�kh�n��o��s��t�����s؜t�t��elJܷd\��l�r��s4�v��hsO����g��iL �+�'�\'�l��m�%t�e8�r���|)��6yOD4]@�oX�v8��ix�`�a,%e��s��ü>�B�	B�����<B2T.�H6�a�e�i�kp�tx�u��vJ�J��̸eI��Ըr|J�*m��tL��L]�M],�e8�i@�jN��h�.�M" �t(NILN&`�e`N}OhNJL�etN�T�l8�r�Q��S]0T3	T���r�SX��e�U����p<U��aĹeW-�V��g4iܹk�t\W(XN"Y���e�p�ctXp]��a4�i�e���g@�mH�nx�p��r��upq�.�Ĉ�d�np�u�;�n��X�e��t���`��X�d��e\�����tp����&�.8�e�n�r$�t̵��	��e0�il�lt�o��pP<r��s��t��up�����ađe�0�l�����e0�L����a��T.H�n���l���@�gX�n���d�a����@��iL���]|�a��e�2�������"k<���o�2��,�k8�lH�rT�u\�v8���Ļal�d��e��gĽi@�k`�mh�o��rľs��tĿu�v��y,����$���$�a,�����O����@�b��X�p<�
��d�a�������|����(�]��l����
��aԼi�k�l�m �n�?o,�rD�s|�t���l�n��s��k�OP6�����st��J�s��P}e	�T�e��o�
�O|
��<�cT�t����W.d�e���W.t�nd�l�
���a�%e��o���8�=������
���a��e`UfH�k�n$�sD����g4�n�@�.�P��e4]�n8������p4$��eP�s�#0�eX�j%x�%^	+�04��\*e��m��p��s(��88����mL���:]��k�>��g(B��n�A���a�e��k�p0�tx�v�C�LC�t�Dij�r�E:�G��H��H���nD�r|H� �ad�ep�i�����hI��L�e�s�HT�nlIa�J�XL2��k|K��e��r��y�L�O��aP�XR�S�l�t�R���.�T�ԿaXT���l0W�O|V����r�W�]*<\���k<�y@=��D����\_``�d��m��s��t��v�`�Aa��e��i�A��s�`t�r���C���a�O�a���tb����B]�a8�e$�i8�j`�k��m��n��o�pD�s�t��u��v��4b,�k���O�����se��rxd�� �etnP
p�b�fd|�eP�g��kt�m��r�s�tD�u�q��h�u�o�JirJ��t�rh�r��ex���Ka��i���x"��dH�e�z�z���j�$k�l��tLu({A|{��e����{F�{���.��$e���0�k�����D�g|�r��uT���L�a��l���T�(��t�a��e�h�
��h\kl�����sP��������M��i(�(��������M�����rl��@�X8�e �i,�o$������s���$���d	kh!m��
4�a|�e��i��j��k��o��p��t��u��vq�P�t�i��kr�,������l(�(�����u�]�����eH�i��(DHbظ���a�mi����i��$�aX�e��i��o��r��y��2<�rH�t���O����4�.����*H�P�lp�nx�r��sx�*4�8��i��	4]��n����e�|����l�����2������p�����y4�	-����t�X�e�N	`����rl~������l�n<���
$�ax�eP�hd�ip�j��m��o��r0�sL�t��u��y��<����+a\�
��eWf��g��i��k��n`�o��r8�s@�tD�JMeH�����n@�����a�Li����d�aTeo��'t�����e�o�s,���;D�*
�k��8Qe��	-����$���N�JD�n�H�iB	�\�l�t��a��e�
Q����n��r0
�\(@��i�`s���e�2�Pa��e��i��$���.��rL�k���!��0��H$��#�n#�$�evkD�v�)/�*"d�al�e��i��s�*IP,P��r��sT�ud���|�d�-��n�-����e�-*
��t�pm.�.~��n`.��e�1����mh1t��ad3��8�sd:q>x<�������?M8�a@�eH�gP�ll?t4E�HPPx@�Mx�a��e��i ���z(z��p�kxlH��O������d��r��s�xv�,Ј��a(Bs\�����it�����e��yp�"������a�e������s��������r4�t$�����Ԥi���,�e<�i`��d��@��\��(�it����� ���l�b��e�g�k(�n<�v��t�aD�b�cX�d`�e�f�g��h�i�kd�l�ml�o(p��r��s��t��u��v�z�A��sܞ�С|�I����h�K4�a8����i��X��"�8P�u+Ph>M�x�a��b��l��n܅��O �m��u�uȫ����i�����ed�����s��t������
�sN	�s����a��i(�uD�2ܙ�������e �f,�n<�s���,�]�iи����gМt���P�t\�v\by����PXy���O�]�e���'. �e��f��n��r��sPn�Ĉ]�`o`�����e8�
8���BM<�4E��?����e��n�Y@�Mt�����.������a�e���`��l�.i���$�yt����
,�b��d�f��g��k0�l��rp�s��t��vd1� ��l���Hih>����e��i�[
@0� ���e�6i��j�k �l�2 !9:��a��eD!�!���.��r����h�.�$�ll#]�e<%� ,��]�]��(�aH�e|�j��s�kT.`�dp�n4�vm�t�t��h�a\��(�@����e�����l$�W������k�n0�r\�t8�����ax�b��d��e��f��g��h�iX�kl�lx�m��n��o��r��s8�u�vX�����e��s��m@�e����$�bP�t��������1<H�e�W�����d�eh��l�u�6��
��a��dX�f��g��i��k��l�nD�p4�rT�s��tDv(����(4J�O(e�J4�g�����d8�'.$�s����e|��	�	�,�n��68�V�@�m|
��H�at�k��l��p��:dl�r|�-�)�
����a��e��s�
�
"��kL�Oh
�L���n���eL�X����������,�e8�l@�nL�s�Pԣe4] �n������s�Z�'.�#J:�)�g�)`�e+
�@�/���e���@04(�1�����04���m88���s�8��>��C
LC��l�A���e�i�k,�n�p��D�l�Dx��LGM$�oSD�t|V�<\w�hy@=��h��L��`a�B�� pe��j��k�]T���i<�����a��e$�r��t\��T.�*��*t��azi��r@�����a�e�i~��������s��i ���r��u���a��b�d��e��f��g�h�iH�jP�k��lh�m��n|�o��p��r,�s��tP�ut�v��yD����
������e@��M��e��l��r,̾v�����e��ix
��lh�`���u@ ��?2>��g�Fkh>��	�a@�dX�e��j��r��sd�tt�u���|F�L�e�FX�.�H�.��d��f��l�^md�o��rPJ�J��|�e�MPM����a�S]diȫolci�m8��e�o2�o����d��k�p�p(�B]t���c�e �kD�m�u�u�kx��e4�o<�u�x�(�:`yMp�u���}
P�m�|X�oȐrt~]��n��r��ȃ��������B��d��n��p��ř���d�����e� ���x�����e�	�$����e$�f0�jD�lt�n��t�2h�l�]�e������.��8�aT�e$.��O���`�a�H)T�st(]|�e@0�
��a�e�g<�jd�l��n��r��s��t��u�/���k��l��r��sp1�1i�3�5��@n�8��r8IiHG�l0�är-G��(���O�p�� U
D�ehT��L�v`Q��X�o$Y-�Xp�p�VMx�s]e��e�^���x.��s�_����.db9:��t�i��h����e��rk'\m�0m��a�n���x�e(�n<�sH�tܙ?���и�� �gl������4�k��� ��@�.��a��e�4iT�j��k��l�n�o$�rP�s��u��v��y���v�
���e"�.�@�h��l��r$�sH�t`���$o���O������ax"��d�����a�e�u�L�l�%HM8�e@�l��t�)x�O���x�e !�p�a��e�!��$�eD!��d�r��s����!!l#���e��l��r<%��$��l��t�&��'��)�p,*H,����g(n ,��a�0X@�0m��aX2��p�7���xpT<�8�a���<��0�rB0.4B��D�al�e|�i�No��tC.(�s�D��H���iLUx�'.��e�R����rpU��W
WX��e�Y
�]�hH7m�]��
��a$�d,�e��g0�j��k�l(�oP�rx�s��u��v@�y�_-�g��k�edP�l��rd�t�Svs�r��H�u|�{��\�at�eX|%Ȁ؀]|�a����r��s�]��eDE� �t��v��?��s���e`���tĮe���s�t �v����e�|�OT����iЕ?L�4�pP�+������<��$�ED���
��\�l��r��s���d�e��t��vxx�OH�����eT�,��������PUk(�t����Ur�����a��e<�iH�oP�r���h�����i�k�l(�r���-��
̻�� �kļ�T�4�n��d����T.��t����	X�e��f��i��m�o$�p8�sh�u���d��p��-����ih�i�VnT�P�d��r��t4����e��H������o���h�rH�����p��"l��l���e@��H�t0�pL�t\�v,����|��T�il�4]�p�d��l��sL��x�a��eL=h��n��o��sT�tt�u�\��m��s���d�.Lex��a��i�s��d=K�tD2��Ii�>���l�>t�a<�eL�il�k��l��t?��?@�?D�dtNN	LNXX�e�?��`�j��r��uԔ��x���e�NidO�4@DD<B��m�H��a��e�kH�tI��T�.��klBmLI�|J�'.�r�K*LNN0�e�M���j����
.tN�$�r0S�,�a�Q��<�r<U@d�al�o�U]�Z]$]�O�eM���O������a����d��i��n��rH�s�t̵����e�i`l$�pP<r8�s���p���Su���������o�u��HL���\�e��i����0�l�����%�aP�l|�tX�����ah�e��-��"`�g����Xt�e���.��f�ak�m�n$�s<�u8�����aL�dX�e��f�g$�iL�j|�k��l�m�nh�o��r�sH�t��uxAv����"h��������s�t���
�����a4�i�t��T�P��D�e����a��dX�f��g��i��k �l�cn4�oD�pP�rh�s��t��uDv\��������d��ul�tY�����n�J��l��s��t$O�*��XyZT�����e���s,�t����eh�1�{!�wy(x	<�t	�D�e`�uD
H|
���.��e4~i��k��o<t��v�
;t"�O��C��i0�����v�����id��r��P
-�
t��a0+�ih��e O��s��e�0�m�h#(L#�8�t�"M@�e�$5�$��X�i4$`�r��s�#l�e��lPen��v���%(p,JH,����g&��a�((�(X��ex)(H)������)`eaL.�t+��u@0P8�rD�s/]�eL�u\��1�1��0�eD1}3�3|�3��1��T��04M|�k��s��v�6��:���t`;8�<x�?(0?Z��k��p�>��e��i���?��@a��s���A����t�A�����0B( �aB�kT�m�A��a5ch�et�i|�k��l��o(gp��t�u �v(��lB�d�e�CLC`�sD]�D����igr�F��v�F��e|G(XG��m|H��a��e�i�o����H����r\���H��i$�n|��lI��g��*xJ\�J�\K����4L��K��4�r|K<�a`�et�i��oXL�kN�$Nl�a��d��e�f(��*��edNipm.lNm��n`O2�T�S����m��t|V�^!<\����s8�v@=�������|aq
`���p4b��mh�r�B��
�a��ct�e��j��k��l�m$�pD�t���i�Hr�i��\�atn�.t�m��n��r���v����o�x O��ex����iy������o���T�rT�����a��8�a�u@�(������gP�]��M�e@��ȼ�`�t����,�s���8�ap�e��o��r�����OܽH�h�g��r��tD��D����e4�����s������t��Xlnf��v��8��e����������4�
l~�����<�((�aX�e �r,�s��tH�u�y4�2���� �k<�rD�s���`�xP�a���\��. �a��f��g�)i��k��l��n��r��s�t��?��D�����e��@�����s��J��a����.��*P�i���ؾ.��u<���'�n��O���(*aL
��i#��a@�pT���'��*�h�rX*��H���-���oP,��k`�r��s��t�*t��al�e�r�s4�u@�y(�Y�-���t<,��*��v���,J��s .���a��eXN��0i�0��a�ih1�(�ä*�rX*����l2���r�2�d3�?�8�gziz��\�a��n@���	d�a��eL�id�lp�n��o��r��s��Ä}��|����s�iă����n������i��k��n�r$�s@�t�(Ȇp(����od����n\����e�0�t�x�����8�e����)k ���dtX��X�u����e���,�������H�)t����e������b��ya���$������ԩ������l0�n@���
��a`�d��e�g�k@�ld�nt�o|�p��r��s��t�v��@�.H�s�I�P���.kp�r<���P�e|�iЫ]��e�& ��K���b��d��e��l��n��p��r��m$�����e��~\�Wl�����e��x�,����%f�������lt�]h�.�t�e0�s8�t��8 KO,�dL�eش�SdZg\�ih���\�a�i��ȺY��e��&�.`vl�����a��e��i���,���.������r"ZĽ��v�*,���������a$�i,�l4�mD�pd�u�i���r$�I��:�� �(�X<�ih��*����P�d��X�n��(x�p�i`�l��r��s0���x�e��i�0r��y������������e8�+�*��ad���d��t;�������ă(�����iX�M�e0���3 ���(��(��`��T��<��t�� �M��k��n�rD�u��\�aP�b��d��e�f0�g�h�iH�j �k��l�m��nl�o��p��r�s,t�u�vl��ha�����t8�|�k`��.l�]��a,�e��]$�a������np��L���0�e@���8�r�8d�ax�e��o\P��np�x
p�r�&�&���ad!���th>?��a��e��i �r8�s�A�>��n��rC��H1d�.k�^md�o;p�rp8s�S]��edih8on��m�a0�e�o�t#T�k\�l��n��p��tx]�x8��ap�o���y�X������x���y��y�	�y���e�u0z��e�z��%��l�Em��n��r��"ȫ����ed���1sx�2�e������.$M(�e4�Ä	�@0JT�ap�e��n��r��s��u����/T.d�l�1���d�8T.��i��s�;+W�VM��a��etW�]T�i�eZdb#��k��l��p��tg,he�hx�np�lG��������\�nd�rp�v ��
�ax�e��ip�j��k$�lX�o��r�s�t�u�vl��<**t$b�
+�5h��n��r��s��vp*��aT5h�������eH���lx5,��-��i�(�dl#�a�l�r�#i�'��)H,�� (u ,�aL�i@(�8������8�t0.@�sX2p�p|�s8n�7��h�p:68�<tT<���a���tA��A����k�A���������A*4B��l F� K��R��l�Sjl{��<\.hW����aHW���lWX�a���H���(�.��0�r�\�.<�a�\��H�r�[��T�y���|��`���\��]�m�r��s�u�v�]��
��a$�d�e�g�i|�j��l�o,�s��t��u@�y����4�`���p�b���bd�f�?\f��k	T�fL�i��k8�mX�nt�r��s��t�Sv8qd��ihp��@�n�t��D�h�}i�w�xs�v��h�i�x=	��uh�+
������d�{��n�{��x�e�����������]����h<��o����g���a��2L����rD�(����n��� �aL�ed�il�pt�t��y��@�k\�rH�(ܩ�	h�����P�i����n�i��dȯ��i�u��M��l��q
�e�����y�~�������
P�P�����k�tL��I.`����e��0�eH�l\�o�Ø�.���T.���<�i���T�m�	*�h�r��sL��p�a��d�eT�i\�od�y ��t�ar��s�����a�e�oT��y_\T.�d�i �l4�s@�t��s�P�{!lJ,�y�OP����.d"*L�r�"%P�,2<B{$`�e������n�r̵��
t�e�i�=j��l�n �p(�r<�sL�t`��`�	p�����a�i�s �;T������o��38�����a��T.����e�A@��4�al����
h�t<�D�a��(t���X��$�2����l�k��n�r8���t�a��d�eD�fX�id�kx�l��n��o��p�s��u�y�ì�h�����aH�k��r���t���s����P$. �i(�l0�s���|
�<t$Kh<�i"����P�v�#~�)T.�)l�e/��s 9204����n��p�92���X=��o�Aq	�a�e$�k<�lP�od�t��u��v��yB��f�C2LC��l�mtu*�E2�D���o4�uF-�F�a�*XGH�l�H2|H�\�a��e��i@�o��u�H`lI2 J�xJM�J�>KiS��X�k��n�T2Z@=�������`j�BX(�a4�e��h��i�k8�l@�nX�o`�p|�s��t�u�v���4b�iutnT.\�el�i@�k�mx�r��s��t�oq�q��d�dx��P�iz��Hm$Do|+P�{����a����.�o������.T����a�e�i�j,�o��rX�u1P|����eЎ"��n@���_�7�d���$�p��K��M��eP�i�2��i@��l�l8��'�i��t�a��e��i��kP�i��2������e\�QCH���i���n�t��2$�7Pt�X�l�l~���@��`���T.tk�l��n�r<���a�e(f4i@nHo\plr�s�tDu|v�y�����4���ls8���a�ep���<P�'.�n����
�����k�Fm\��. �a�)e�i�mk�or��sH�"��v'��8BP�
��d$�r�e�i��d�KP�Tv�+4QP$�de�i�u��.hJ�a�eL���px�.@ 0&i#��k�*�eior(s8Ô,(P,�d`.��/�L(�0ih1�304.��0�d3MPn�4��}i�7JXa�eP7x`r47pa�2eH~
t;���Rv�������?��n�Y���,g@�M�a�e,s4yT�zxH�WP�����d,ygl nt�rh�v�����(��i�n\��2�����e���<r$���H�(��`�y`��`�h�\P�x.0�t�e�*�dTeP\�.@w�e44402107006566106000100410484040008000300414248122702545601014014426020030430650021266600401505047520120103500085656004006708105030800214644540045005340264043070006586125210100062162801452182047972022205012900402118002100521002010240001445024043263610416466041009041217607010087660586000434230065100540329000240060660066000044000254026000300563420404043220034670984208184800202266516080894810736460650105242244526220180181004405485636702600454074502000650440040209607282164008304202105203232404206374012106854102680002788042150250007200610043005054087001901941464021600344546322508606424109850546548063024465001304260180130028000132890660004700017004412228344221098800565222046080536045362004222340360076007608402309032465600090225483010901000050102074360020270156460005644080026569610980044546525606208021243361500401030005128001006600601014374000300225644367000674016760074007688000480005100520006700670421414000040100238421058436565062220330540174404042210704365085154244900520041012032450124566740524522380244130220682364090144804568034401229464000214048016805804324500263450410210460242002243830036526106048960005212652176616840001547801342582086710213208140696605663202415444700504046108000046124907800044502904600400482020167700874470205834466787004660456584526706655004250030508061620400698659201003609080065022544040086458640663005660500244124406178667840244120040804085614124502280656530400608006010086541596510982407632708635250621221655410890008702232600001002200005800322106020704896901029430700062210167122080600100801001086305062664100444604016470000500001510045100241630665236034000601244452342030656102800147048716210060404264300020450836400809103010040341743100522036320020521967000424062250190040010206901001400052903020043045000100182045610406102272458005060050400022100300043200030401300000121424141210402376100452105400030044500031020126540040120502040200878050280404041087874000034061024304060007040017036804321012033400566002320245912140024542420720410001200016218404549200205050900404000421000162823006200210250120201430104064480010104361045600223403000207080614408906070003210060600581002008402107002005000002002810002700449005021032103060050030005630043410436105800070210342210672105240090000982005016042100254004341245050440000042500270065064000442300321292000450609210045610300010500250606000403160000040010080000027400281070010020200602005000030045300892868327060541003000050100760003401000643400060344100450000020546002002367680294192500710816103261003610061861218256106070030041040564505608060023000205025601205000500560200305000454125030020100589000105040502436080202606681004414005002006272742103300301000766108900276303200605610243461000476600000060060096600500601001010240230040600500040040714454003500040404070210002096061026530300040900006000100221894380810940454106500047040721012182014060001060340060002200202000820120205400401400727212200421242900670000461003223006560040500650100602104200062100062103012149250044340023610400902240405030060045000041045001400403006210460044140503270500287465400502001006400680304216040004081060024150056125810706270002214702002320010205002563045010078005601003601009802402250660432523430010040643406004003002504087041040015001002900076102034441002570044000026505000610302200650616702070024050005065053002060584312622023023307001014010504007028103010050020120766016006806002006067300620825042401438002360034465050140161050300010100121245200212005006003227012220943040670601004142427272600650072409960060400400065404676600185000500660524523546642010460204521824104030900000441670100029851420512444743105270690036405001210018000000329040320026524430020905227060026434552000600546010640716221546050407383440580010145034240450613082000027524430276052900490510012003072000943067440060100201781122165001032800503010216710022420700401644024020050204364140165665466707629100408432160645040000452324501041414110056044010004014060160400024560404290104060420354400215294640403240544401040904218620004002085242308610225150001561054385144025226447627040040080020654000421420054700200250276250640000654410021410980004101029001008610021218412200522040020106000044005680032504050450116123850607481020031232725000000676107030004056002014466500504105000800380000500320032200707008410024102522006612400210400600401200225663700506104012245200063401060004103067416072460300042100122100020500281003021626100020100065062000440010650300004386656104000200400200045824700140140020040026507600080006000600001004600600401001607200604026014004010000360004010409060005021002410020122702306605606506000506500040300000601000843000603200223006052210402210421643002007280200140104201066200006076262552307022404403000896003645400000020102602122011665620040080045812216004502328500414002161046100030106400226747630402000201040409007000003030040832404234080980002034004004500422124000410404308722121667060421400342101107410607642030016000300610560600540100312185121094800900600500141024764508009000000320270722007032003281062524300210906163404003420020600160103200003801254900788000155002110231003081067098052600612102326050061032300043004202065040009600000326003800090200650810450365406002310430103440021202460061400403451604656345422127432450212400360061220000049021041330001410470110054513020721006660085600600062045230063601054123026280065102867432002465271425105421020221000380023001701012903810034000002602400421403472509061038003003650214213046506032116818124301006064002234100024076006214616106230034206302502185601041028063005200134070227002106540401540104560103012004440050321030604504247610652105050063800303230214290060370004058000065025083840544410002650600656032214326107060006500400306370000600902304304063000025000061054020454020044101500021030340227204900000630021080561003410441264560054501000430650342220403602632676000343005030210328000762109410026304700110226140230323062226071632010063200251454090005470025410454000431004121050125004000204014445040456123621032682683232504542002327043810940006321030472341042607040150021003325081060000000600124050400405606710030500424003002001405403122502478507045232520000383004302480200059620000601025066072010401024502044344040752034120432603018090904141423149404004502124100002104056040065046043000045047067004302304023040067010960010786000650000308545254040012202609440110404106005080615890000025424720074060032254445005023476200581270610690400818100540610500210608100760600650200878000560210204345654100685400560125420081000025414520481261073250045021028472321030001001800056002054454101432707042166368674502145203221004562105002250651604340415064543020003414106101440042000041218600212065600400540000082204590425100200067065010010584127106002200146740090200070410080102414042106927670011009435400292003900060900221641001010500722764723007300060410630000020610600707405504604606106010000400820094262490010296107032900327000432902050410050078768820057000892650500401600405640060050107002109054020708492132005623120021376341017067282100412142121045004463450700600500040250867052000852010041212563621800223030140205660140560590020007002100900210070000100600060030300112704312432000506037081461742210013043090216300458100301004470015020160020400010600230090800040022146052000400010050520350020005002600100730600098500068100006004000221045045120780600640060502662050504012185464050410421044030026500010055441610540660500052220814102346006102043270050032021090600926960006071032015621003007300102000162144309470360	6000430007221436045721220210900823602564083437000450800200106320004656002041020134120065021027000060601050040010200362005221670122233003201050050054021074027600009606106524009806003801002421520447002105762105422005404005402703421250800045230540010098400054400056000029204544005460216124123061232763621414400104230000781084744747006423545267650052606441000280345048223064380215669203090042250227087010040602244349281706404756002261056608004443015610091050410121010253001430568048326	800020000261730054000200540020009050001240043001042121270184181092106004040132806254001280400670216002214600060540960200663601034066030290612501000504024020290700202342165640048160676040520307222124224007410056200146067032004051550009210911400820307008165200428040725030100961005245050000004404200420467010010632300040452110674243231206090600303400403600503000060300507000871003541050458908650120283240364210972604508215072520047060404125240500840340306103560041061080325814300202124322507010469000061260025216270617000520567024329008810005020006050000252041076267200304654021340312720000224506056007220500942545060600040562058942717021452061866001408020211401242580601244248165364100046000400601001804000203222420050254198100752106010442298321667230501011060066601000001401220001040006004090270125042460041240118600210020004261005430500410661043441005603000414303000046942000511218400006016210630300650610102103003006052200416800500147612470704022184000840540050500009000610602000703600050000210300002564060720004052109288405001027200401020912010051006010106254005000060065060006504200410054402212304105641260026400220060020004000400402341040101004000405043100707040160006168050600080700000605021003021050012080480000604604709050000700021008969280004521004010026070000504020050410040520004052400465400040500005002847674128400308158804140008971085010521812326000223672220876240041030081660023091426543000210840000401064540106000610607620050080063600294004522003500163049252402602721054074016074500404272564000742100204224781200234010047805661040650670106100205440646044230830060440444020104000894402522560067220620024962210681604071096005001423060444020810843706707804001010320290430400400610600365000643041067281040401220123606107000250020204603144060104350104031010010080020050323400490006300060404252461600216287000402166652400324009821007606106406106900060454676366361260345700600004525210246263847600200765004142106211040010185606102072502200668300400236011042016000023020040065042084210250614472200432230043030070500240402505488704606645007410007665021706300200160704810081200014025000020400272600210302100420222800290900024100060002166452012424212503200
8000600000892104081410410040001402323040980040041004501001601424302609809021000700000040120000403400048100004052001032500600020060721080100020320640214600006002200404010500586072223010409000431902124190540004500010326050143201046006146500004540000665062424400640068041270006081062564322105076200209004526043202314540276125218503205245004065000006004105860015410050401100601008410005100040701061980000790097025074262001640871205006030224343025443620250064501484658024144200745000494003076618000650061041306820712003521085045430052010303041561052205230600204066124281221500700010926006102003244065225043212363669245012086149400600720678108014870162406230412323056008600471008090004050042960901540124742083229342452442108730410212210000670680518689030000130002465241200042014221022814441050900032140430000014207050000807843402401422025120212622004410222200225865006300656000634254054501605106405012229040040601200004094004006000423207423063410400485045650068589000040310027300781243041243061461067410302981000407004725027002016472220605824251460105404300700209212580140041443420060660207046000081040061006023410420691000542656810200901560089070230202300164160080006005054100642607329220040108891204110022650106504062302129040600041270700024520142702607021028523005050910087000040043006006502000803200406362121245636406124021860014403207004281363610625044020602346435060491989006500200540271145044550043005012234100212640000027090610004402000460000026305043421840006104003000422204030125094401405020050020610401006710004030042250022144608061040050081580080043250422106436006000650562058245347032043402200164000502081240041240061441067490416190007650042404508216444022741027610320012108927045250534104361250140047410667229229280327002348363003054022944108307080060365042665212525002621067260040142403423250003460065040050540564106604107020000124076500063020087200600541020045061470410243404700000120000243007421290210427004001490400040510058500450104298806000400630240121024341252634602748314220796506070045450047060051605056435450007022411058155850432526503625161277077030040145765050360544041012021148210301002121640380260459205260000417089145060010212610223104476261083080300502309880623201034054006104072020960650468943265769802190109007014908014741054901702152048024325008300040212750405010221503043627600262060010436021030221050221050003200283272734424948260725042300065052000506083080024000040001224270006000436040070310060260080200625405214201542260905005010470430471089032002258490106106310650610061200032666530607606100005081254401546304660080041044810285001410214600000740018300403200023430652000420025036405800690140301085054508030221430426545001640221760001040321080000100622202165032141080073087621008990268281570890264340210090234025700867207605602100670627670068002400023601200610002
9000006000
9000002100700010050634276002107092900805006008144000708080450190030476670200043050464700140023004301001403018040000678610898100504300065270702210600020400421222524	500000000
7000000401407000102019001303671450243803869601010230610040021004212706076000403200010022201264025441812500010360362006020366021400021067014060144506125221042658405105060100450625042607041202500094254465004075006503000696000225230870367210140	500000100760447643058002027640232600001080002100400241121501007234545090900200127000000040910040710056260208175460043001506304543076580767000450810450856645272032247004002445890410643610564504670046900432324507207601245800065033496630020074000000400016762002123214325084176458045041483040083040004005104002125600292611	60000300090400600043030000004205420010220784008942001008012634061065410212043410003061410410141001265408904542210781000525000123005282350213407504301250043600030014043010445008461010221402414102163643022004056456560061440504030050414	60000050041800660270400004278434800623444312340500445000434010434000850650076212802010060421002141230502450200087050210702504000020540006966102256005052823400402034000050210007663652810440122320300200040058400505025706420067020032261032400041004504404277601007401003041250272445485090405410430610902021636004038802125400804101006106109800000500000006301000602400380210461485401032430700410650000005600243300005407000400360000450002106100000740610744100322100430210724004026106410003220123402004762003020204300300263441421063165000230850400810210641065601241401260002543060045600056000008501604830040070060663830410406658007028454705850000340010052000004004179400006503008506003402106504101264047400080061020192524012210162016216522121260012700412565821012254500100012212600300800010054218181007405000430200324507660005421005600065800760630100300700560410890061056230067210048140542400620610830607608300005600410320000050601028978012603603085089100410340623320500405224801040201443210030021465603000650004420342401045001056002106430021266654441004542124201204540800805324223008050005062123402144461000542300045050003606004003622210852100604060602250287646306126201064201290002340800498003200104505006008000364100461400420104522000263642360098214650212500164024005601000630014050006004054100636060040005440230764020076600006360500603040034600050003063061054250645000964000700610656001046006042100005003006006010467663060065445038061034021654005600021454401052610342501205034410850020094080067260500330940461221050270342000540010402566702107401067045245001006005450946007605049600005400025202003600100560001010620270210610214408944015063404002245108700221073450022002034070040006301045050445000048000400670000032002052054004922300450300604346508069210301047460030612209801301006002106600002500002220040300212600490105010050450040083006230003065621001687300103634222070002006004210700036604410070060008094000605021445310048010074000660080416605021216	5000000103063080340040040004006010430122011060020109260007000401060122508904030000506700040544002030002812430421422334050832120261632032470000205623005006000210601421040014340081070010015000020050041002043040107400680203002604400700012400059000460540000808000306362200056000000380102007032221030050054050005020107106302021682144002146102126500100450010012658690000043080040290036270036050050100005007000764004003450021221009608250004154300000034014022130566000340011005600400040274020630210230100230210452400334049020456508010427040732474010500027121653000700605120563461081454450430630894543210143206006153100007104018032000602256180500440066507040005400590020038020021042170030002209702340271005421845005042091065430150962103083647000340046050000100500023030250108143002027602003204101067821032526008410774200	6700005004696870902144126507661901001208480054760210320601812123054080060091232800401040262300067016000408412004030090040850678070509823316010270400032500400050006328020003705021200404250056021271606430050000800546760201016709010300900000001040146770452291060890656020646781921072065015103400560602207445070484241290402406500850661930140098600422303036700100712187227053065054640140670400814000202112940542904012870600632080004923004254017840760	70002010045900020710900860040044050645080544106421442056090300274132103500004002081060632207188989800889800PK
!<E�ƅhyphenation/hyph_or.hyfHyf0tP�����'-4�������$�'–’-111001���������(���4�����
��3h�h�h�<�<�<�<�<�<�<�<�<�<�<�<�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�<�<���D�(���<�<�<�<�<�<�<�<��h�<�<�20021001110002001PK
!<	�$���hyphenation/hyph_pa.hyfHyf0tP�����'-4�������$�'–’-111001l��������(���4�����
��0T�T�T�<�<�<�<�<�<�<�<�<�<�L�L�L�L�L�L�L�L�L�L�L�L�L�L�L�L�L�L�L�L�L�L�L�L�L�L�L�L�L�L�L�L�L�<�<���D����
<�<�<�<�<�<�<����20021001110002001PK
!<{�ӧ4�4�hyphenation/hyph_pl.hyfHyf0tP�����'-4�������$�'–’-111001���������.��a�kbXmc�od��eDrf�rg@shЉi�sjxtkulpumvnT�o�vp(wrTxs�yt̖u��v8zw�x��y�zz�f��fČg�\h�b�c�d�f�g�h�j�k�l�m�nprst$v,wpxxz\�p��f��|���`a�b�
c�
d�efghi�jhk�ltmln(4o,<p�Or�Rs�Vt�Xu[v�[w�`x(az����L|X|��T�X}@h��h���������4h�h�h�h�h
�h
�h
i
i
l}x}i
�}(i
4i
@i
�}Li
Xi
�}�}dihl
tl��8����@�0�LrЉXe�}di
�i�b�c�d�f�g0h8j�k@l�m�npHrst$vPwpxxz�Ōg����X��� ���pi|i����d|�i�����������j
j
j
(j
4j
@j�b�c�d�f�g0h8j�k@l�m�npHrst$vPwpxxzļŠj�j������������,g�blc�d�f�g�h�j�ktl|m�np�rst$v�wpxxz<�H��j�j��4��j��h�d��������}g
k�}�}�}���o�r�f�d�f�����q�}i8zT��w\~���o�m�����y����(��tЉa�g�b�c�d�f�g�h�j�k@l�m�nprst$vPwpxxz�|�,k��h�d�������p|�g
��h h����Hk�b�c�d�f�g�h8j�k@l�m�npHrst$vPwpxxz�Ũk����������8�||�x$m)@t�k��Hs��Tb�d4e�n�r0u�o-�a�e�i�o�u�y��0�pX�4Љ4�uT�4�pr��4��4m7�v���t��<�v@(w@<�D@aHePiXo`u���ó(r�H0�N`R�S���@a�ePi�o`uxv)hyv���t́`�iV�g������	@ab�ePik m�o`u�Űl)�y(w���c�kz(mZxtpu�r��(gkDt�ydo��^̏��Ph(�@aXc�ePiXo�t�umbv���s���nz���a�y���r�N�k�	b�	c�	d�	ed
fl
gt
h|
j�
k�
l�
m�
n�
p�
r�
s�
t�
v�
w�
xh�y�
z4	�H	�hltl��,	��k�k��@	�d	�l	�t	�|	��l�l�l�l�|f�l
�l
�z-�	a
c�	e
i(
m0
oP
wX
z��	zvi��ԗc�m�	nXmi���	cxtiЉ
k�mn�l)
h�T�D
b~)�k��<
jD~<}qP
w�l
�l
�}~�l
~�l
�l
m
<m�
z,~um
m
8~D~P~(m
Xm�a�b�c�	dd
fl
g�h$i|
j�
ktl�
m�
n�
p|r�
s�
t�
v�
w�
x�z4	�\�\~�m��T�d	�l	�t	�|	�,gV�g��x�T�k�ňi���o�g����0����m
�|f�mu�r�~u|~x�z`��w�l)�ov���k0�nhpЉe�~Vxt��0rT�8k\~��Do�m��P��v��\��m
�~�nu�a�k
t|
wT�k�l)�o(w���n���r�n|�m�4
sXm���h����c����e@
o���r�o)
eԓ-� 
t�n�(
e<���(w-�
a�
e�
o�
��fL
r�f��d
�D~��p
��f�tl���
��
�������<���o�a�b�	c�d�ed
fl
gt
h|
j�
khl�
mpn�o�
p�r�
s�
t�
vw�
xhy�z4	�0Ÿo|u�o��$�d	�l	�����zV�y��Lr�Tt�w�r��`o̖pgdp�Dq�xt���o���ku���e���l�|f�z-�a@e`oXyX��z��b,wu���l�k��i̖�8z��$uui���8lPr�i���T��~�q
\hV�f��x�T�bc(d8f@gLhTk`llmxp�r�s�t�w�z���ňiV�k�g����x�����gVHkV�kȑ$h,z�oTz<���rdr@sxt�tu�npu�k�v
cpr(w�s\z��Tx�c�m�z�y�k�r8zc�zi�mq�lq�uqn�wq�zq�{�<mz,~n��n�nndp��o��4�L�Dq��qn�wn���\z���z�l��s��|��n����h�yn�l�z�q�o�z��w�r���o<��g�~uD~0��s-<a�
eDo�
��fj�f��$� ��T��Tx-�e�o�t�y�z��Lsxz�z-�e�u��\������x���a���y����̖�qu<i@���Љ�oXm���i����c�����Љ�Tx��i0� s�wЉ,e\h��s�f��H�����cT����`�Љ�p�8z��|iLi�(
e�tb�yxt���s���g�klmDs�y�D��ty��eTx���z���spu��el�����aTx�� m�,sTx��8e`hlt����}��Xa�x)��ta�ePiXo`u�r��|óN��4<�taePiXo`uz���om)�rxt���t����ku���e0��Dr�b�	c�d�f�gt
h|
j�
khl�
m�
n�p|r�s�t�
v�
w�
x�zxĄ��~�~��p��~��T���������(4@�|fLXdp|�r�b�	c�d�ed
f�gt
h|
j�
khl�
m�n�o�
p�r�
s�
t�
v�
w�
x�
z4	�lŬr�r��d�d	�l	�t	�|	��l
�z`-���o�|f��-��u��uzԃ�w�k���o̖�b�wu@s�b�	c�	dd
fl
g�h�i�j�
ktl�
m�
n�
p�r�
s�
t�
v�w�
x�
z4	�pŨs�s��h�d	�l	�t	�|	��|f�-�a�e�
o���r�v���ep��p������s
�s
�s
��-8�a�f8iDoTuЉ�n,s�z�~�Tx��$lЉ�	cT��\k�w̖�xt�v��dn�~��la�l)xlT�ta�e�iXo`u�z���o`RN�sDa�b�	c�	dd
fl
gPhXj�
ktl�
m�
n�
p�r�
s`t�
v�w�
x�
z4	�pŸo���o�o�� ��o��,� �8dXt
�|fdt
xt�b�	c�	dd
fl
gt
hi|
j(khl�
m�
nHo�
pxr�s�
t�
v�
w�
x�
z4	�l�ԓ- o��tTx���e̖�s�l)�uu���k�l(��|f�i���o�g��0�`��n<�zb�a�e�o�u�yxv)XrT���tt��0d�gDkXllr�st��<�H�l�wu8z��Љ�r���i�Tx���ey�����y��a�l�o��(m�lxt��<cmu��Pt�l(w��dg�~�oz��`�Tt�w�l)�o�y���k�f�t�f����\h��f����<����t
u�b�	c�	dd
fl
gPh�j�
kTl�
m�
n�
p�r�
s�
thu�
v�w�
x�
z4	�p��|f�o���o܌\dpu�b�	c�	dd
fl
gPhi�jktl$m,nXo�
p�rds�
t�
v�w�
x�
z4	�p�m pu���p(��mЉ�au���i̍l�u
�|f�u
4i��oHk��4n�g��@�H�L�v
v@a�b�	c�	d�ed
fl
gPh +i�j�
ktl�
m4n�
p�r�
s 4t�
v�w�
x�
z4	�p��o%� a�$b�$c�$d� e�$f�$g�$h�i�$j�$k�$l�m%n!o%p�r%s %t,%ut w!y$!z8�T�0�b c�dL!f@gLh�j�!k�!l m�!p�!r�!s"t"u�w$"z����s(p!al&b�$cx&d�!e�$f�$g�&h�&i�&k�&l�&m�&o%p`(r�(s�(t�(u�(w�(z8� &��V$�����x�����k $rXm
h,z�f+tl��0��$�0�dp0�o��l�,$�L�4%��oV�u�k5̖xbЉ�r(wi�l<�u�m@Xm���h��c�q8 eT u w` z ��nGXm���z�f�ctl�����sO�l)�z�v�� cD t��( pmTxt[̖L k�~ad#ep#y@#�D~� o�lT(w��� nT�� r�sf���� jL�lX�D�%b c(d�%f(gLhk�!l m�%p�%r�%s�$t�w�z�İ%�T�DH"b�"d#k,#l��D�qn@!id%mp%o�%wD%�Љ+�#eDr�#c�#t\{k���\!d ��X&kd&ud!z��D	�#f�#g�#k�#m�#n�#r�#s$u�#wxtL)ru�v�)oprH��#c$d$k�*o$r�t\z��Tx�m�z�y
c�k���z�*mP
w�kq�k��4"��k��|"o�"r<"��nx�l)\"z�s��d"cԃ�p"j�"wЉ�8z���"i�y���"t<m���"o�~��q���"z�o���"rv�0��"n��"ext���"i,~�<m��#z�m)#ru�� #b�f�Tn��8#�8#��n�Xm��P#z��X#c���pu�0�x#m�lqpqDr��r��t�xt���#o�#s`��v���Tx�8z��l�pu���#f���lq�lq}q<m��z�l�|$rpu�0�8$mЉ@$e�q�L$i�o��X$z<�d$d�}��p$ohl��m<�l<
h,z�|��%Šy�$a�y����$t�l<�l<�}~�l<�~�q<m<m<�zm<�r��Dq��f��f��<%�Tx���P%s�o�X%yT���nXm��x%z���%cD~���%y�o��4��g����x�����kDr�v�#c&n�t�&z��Tx�z�vq�n�s��s��&�8&�@&�H&��l��l��l�m xt��P&t���m<�(e�l<�(oTz<�Xt<Љ�l<L)r�m<�l<T�P'b�'c�'d�'f(g(h(k(l (m((p0(r<(sH(tP(wX(z'� '�\h�f��'��i�g��'�8'�@'�H'�,g�gHk�k�$c�)d�$f�$g�$h�$j�$k�)l�&m�)n%p $r%s�)t�)wT)�h)�̏$h,z�o�$c�)d�$f�$g�$h�$j�$k�$l�&m%n%p%s�)t�)wTzT)İ)���r@sxtupu�v(w\zTx�z�y8z�z�s<|(o\z�zTu<�p(zm<�m�zdt<�k�)r̖�)c�s<(m<P
w�z-P
w��(zT��%bc(d8f(gLhk�!l m,)p4)r�%s@)t�w�z�İ%ňv(w\z�y�k�~tl���$��kV�k��`)�8&�@&�H&��l<Tz<�~�l<m<D~�o��8&�4�4%��|�Tz<���Xm��,z@�%�*d<�ܐ�%bc�)d8f(gLhkd*ll*m,)p|*r�%s�*t�*w�z��L*�ĐVА��D*�x����8�t��*k�*pܑ\z�y��mq�|q�u��z-<��*z�0��%b0+cH+d�%f(gLhk�!l\+m`,ol+p|+r�+s�t�+u�+w�+z�İ%�Љ�*e|�gXm�+c�+h$k,z�o�+oTz�+�pu�#c�+k�v�.o�0r�T3o&zTx�3u�z��)c,.w8z�3y�zP
w�|qdp�o���+�L��uq�mn�bԙ�T�bc(d8f(gLh�!k�!lH,mT,p4)r�%s@)t�w�z�İ%�pu�*k�v
c`�,bH-cX-d�-f�-g�-h�-k�-l�-m.p.r.s$.t,.w4.z�,��,�\h�f���,��i�g���,��,��,��,�,g�gHk�k
�$c�)d�$f�$g�$h�$j�$k�&m%p%s�)wT)�4-��k��8&�@&�H&�̏$h,z�o�$c�)d�$f�$g�$h�$j�$k�$l%n%p%s�)t�-wTzT)Ġ-��o���-�8&�4�4%��oVD~�-r�s��r@sxtupu�v(w\zTx�z�y8z�z@�%�$b�$c0d�$f�$g�$h�$j�$k�$l�m0n�/o%p/r�(s�)t�/u\/w�/zT)��.�ܐ�%bc<.d8f(gLhkd*l�/m,)p|*r�!s�*t�*w�z��L*��o���.�8&�L� 0��oV/u̖"�q8/aP/e �Hk5�g��$/���H/p,/ňv[��(( pD~t/a�/o�s[��l/jT�l/jT�D�/c0s��D�q^t�̏�T��/c����/ȍ���/h<�/$����/z�x���/rTx���/t�|�<�4�<Dq@�iV�0k�g��(0�x������71b c�0d,2e�%f82gLhD2k�!l mT2n\2ph2r�s�t�w�z��40�,~�L0e����0zi=�o%�$c�)d�$f�$g�$h�2i�$j�$k�$l�m%n%p�1r�2s�)t�1u�)w!y�1zT)�<1�k01r,~B<m��(1z�o��T1�8&�L��2��oVx1uHkI�2y�g��`1�̖l1��q�1aT u�1y�1z�yIl��1tpu���1a���1mu[���1l�~Q���1mpui�q^�2a�2g�2i�2j�2l�2w�tXxt��2s,�] 2k�r3rxt$3lL)rv��
c�,3z��Xmd��x2cDqn��s|oxpu}Љ�2m~��o��s�D~���2o�2r�s�T��2jЉ�m<3zy^�w����3z�~���z-�3b`3e(
m�3tTut3�<�43z����3r�l�@{��l3�4�� $r�m)�r�k-�3i̖�3b0�����3e��b c(d�%f(gLhk�!l m,)p4)r�%s�t�w�z�İ%�}fxv
T�x9a�4b8:c5d�-f;g�-h\;k�8l�-m�;p�;r�;s�5t <w4.z�,Ą4�,g�4m�g��x4��,��,�@����̍�4o�}���4i�k%�$c�)d�6e�$f�$g�$hH7i�$j�$k6l�&m�)n�9o%p<6r%s�)t�9u�)wT)��5��o%h:at:b�$c�)dx8e�$f�$g�$hT7i�$j�:k�$l�&m%n�:o%pl7r%s�)t�:u�)wD8zT)İ)Šy�5ch~�hn���5��m��5Űl)�5h�kV6o�5��k���5�8&�@&�H&��f�|i���5��5�̏5P#zT�7�5c~06uL5�z��6g܌$6z<m�h6a�6o�6y�6z\h5�f��T6����6s\6�\��Tx��x6o@j5�g���6����6n�6�v[�g5�g���6����6z�6��z[,~� 9e49uT9y�8��D�%b�9c(d�9f(gLh�9k�!l m,)p�9r:s@)t�w:z�İ%Ō�D�%bЉd7u�:z����q�7a�7o�7u8w8z�7Ġy5�f�7ttl���7���H/p��I���7n�k���7i<��7b�y[̖�7tЉ"�s)�7i�~n08e<8w\hI�f��8���$8�d���q�\8i(��`9rЉP8al8e0��h9mX�D�%b c(d�%f(gLhk�!l�:mT,p�%r�%s"t�w�z�İ%��}��l��8n�s���8�u���;e�8�Tn��8#�9��o��f9d�z���9zXm�̖,9cD9t�y�v���L9n(w�pu��z��p9z<��$����9z�x���9rȃ���9tԃ�9sT� (mXm
h�n,z�T��0:m:t&z��mTx�c�z�z(:wD~q�l�̏H:h,z��n\:r�~n|~xT:z��d&u�m<�:eT��z���:o�l<�:rTx
T��:s�nXm���:z���:c (m��*zpu�:k�uZ�~��\z�rP;n,;�u���n�f;l�f�� ;�@�TtЉ8;o���D;ixtl;aL)r�� m`��x;o�v
c�l!�s���;�(w���;t\z�;��f&tl���;�m)�;�Tx<i�zpu-<s0��;mЉ�;ev)(
e8z,�s�v�b>c�	d@>ed
fl
gt
hX?i|
j�
k�?l�
m�?n�?oLKpTKr�Os�
t�
v�
w�
x�
z0=�4	Ĥ<�\~�<a�m���<�d	�l	�t	�|	�0��w����<oTx���<k���<s�i�X�a�=e<=kH=mP=o�=p�y�=Čg���<��f=��f��$=�i=L)r(i=T�h=b|=m<m��k��`=r�lxpu��t=d�u
pu���=k���=m,~��=y����=z@i��=r�f+|i���=��=��n1Xm���=z���=c��k�l
>h�mu4i�Do�i��>n�g��(>����>c�>e�>p`>rL?z4>��-�
e�>i�
oTuXy�l
Xm��|>kЉ��>n���u8��>lTx���>e,�S�>s�(,�=�>r?s����>e�v���>eTx(�B,�I?r��?em)?e�y��(?p��4?t�z��@?eЉ�?e�?op��s)�o���l?w0�x?rv���o@��?nw�v
ĐV�0kА���?�DD�x����ܐb�@c4@d�Ce8f@gLhD2k�Cl�CmPJn�Cp�CrDs�t�*w(Dz���?�@�%�Ba�$b�$c0d�Be�$f�$g�$h$Ci�$j�$k�$l�m�@n4Co%p�@r�(s�)tdCuDBw!y�Cz�D�8Ĵ@�̏H:hPDz�o���.�XI�L�4%�4�<�@a8z[0��@w�q	8/a8 e�AoBu�Aw$By8Bz�A�@A��nGGnXm��Az�f$AcTAttl��0A�D~I�y��LAwHkI�g��`A��fhA��f��tA�ЉQ�s)�Ai<���AbBc�*z,/��fW�f���A��k���Aa�Ao�Ay�AÔ��ԃ_����n@̏���Az̖$6z�r[��Bg��"�~�0BeD~t/a�Bo|B�lf�s��XBn�f`Bj�Br�f��lB�(w5`RnT���Bil/j�Br�w@(w���Bz��D�IlX�D�%b c(d�%f(gLh� j�Dk�!l mT,p�%r:s�It�w�z�İ%Ŵ�tЉ�InCuT�D
�Db�/c0Ed@Ef�#gHEk,#l�Ep�Er0Fs��DdFcxFd�Fp�Fr�Fs�q^GaLGbXGe�GiLHẃ 2k8�XHst��*k`Hp�v
c�Joprܑ�#clHf�n,KotHt\zDŤly�s���C�Tx�c�m�Ht�z�z@Ka8KmP
w�h�@j��<Dc�nn`DdhDtto~�o~gȚc�fpDw�f��|D�xt�Ds�t��z��f�Dz�f���D��k���DiEoErHFs�D��o�(��Dd���Daԃp"j���Dz<m��Ea�l��o��TFd(Em��xt���"i\En�Er�l��r��fdEgtl��lE��~���EexE�Tx����EsXm��\"z0��EcЉ�Ee�v���Ei8z����Ew(w���Eyxt�(��EkЉFa����FiЉFnTx��$Fi�/ty�m)@Fz�|��n�Xm��\Fz�q��o��pFzP���v���Fau����Fl(w���Fa����x���Fa�����Ft�FzT�/8Iy\y��Fz����Fc�o��~�xt��Gr��Gk�2m8Gs�x�Tx��0Gt���lo�DGi���������`G��G�`G�Љ(HcHe Hl(Hm0Ho8HshG�G�v��|Gi����Gnu���Ge0��Glx#mЉ�Ge@Ho\h��i��g���G�Xm�v�0��Hnu�pu�@��Tx�@�x#mD~���2rmqmq�Ik�lq̑qKfKmKwLIļl~�x���Hd�Hf�Hg�Hh�Hi�Hk�Hl�HmIpIr(Is�l~�l~�}�Љ�n~w��l~m~pu<�Im$���Io�x~v��0In�ftl��DI��l��Ir�o�fdId�f��lI��}��xI�u���p��
�y�Ik�lZ�l$�o%�Jc�Jd�$f�$g�$h�$j�$k�$l�Jo%p�Js�)tTzT)�xJ�0�b`Jc�Id�Jf(gLhk�!l m,)p�Jr�!s�*t�w$"z���v��Ja�JiXm
hpJz�n^�o��8&�L�T�(Dr�#tH�\z�l<�JhpJz�m^�o��L��|q�J�m<0�k�wЉ�Jeܐ�*wD~$�l$�l$�z-Tu<�� Kz�n��"u}f�pKaLe$LzTx���KphKs�Kw̖.�l)�Ku8z���Kn�Kz(m
8z���Kn���Kw����Ka�v���Krܐ3X���KoTx���Kp���Ks�o��Ly�Ld,~u4Le\Oy���LbMc�Ld�Me�%f82gLhD2k�!lH,mT2n\2ph2r�s�Mt�Mu�w�z��40��o%hNa�$b�$c�)d�Ne�$f�Ng�$h�&i�$j�$k�$l�m%nMo%p�1r�Ns�)t�1u�)wlMyxMz8�<1�k�MrXm�Mh Ni,zT�4MbLMs`Mz<m8�k��,Mr�x8�MaTx��@MtT���z��XMo��DDOs�q^�Ma�2g�2i�2j�2l�2w,�S 2k�y�kPOr��y>���Mt���<m�Mz,~��mnNr�~F|~x�Mz8z-�a4Ni@NwЉNw0�NЉ,Ne0}�v��HNn�r��PNo��\Ng�Nk�Il�l xt��|Nc(m �r���NzX���Ng�Nm�����Nrpu���Ne�l<$Orm<Oi3z8zU����Nw����N�ЉOĜwF���Oz̖3���0OuTx��8Okz�a���Ob c(d�%f82gLhk�!lH,m0�n�OoT,p�Or:s@)t�w�z�İ%�k�Or(w�Oz��<m`4.z�w�w
(w�b�	c�	dTPed
fl
gPh�j�
ktl�
m�
n�Po�
p�Rr�Rs�
t�
v�w�
xz4	�pż��t�z-�Pa�3bXRd�Pe`Ri(
mhRopRp|RtTuP
wt3�<�`Pz���Rg(Rn���TQb\QclQd|Qf�Qg�Qh�Qj�Qk�Ql�QmLRn�Qp�Qr�Qs�Qt�Qw�QzQ�$Q�\h\�f��Q��i\�g��Q�<Q�DQ�LQ�,g\�g\Hk\�kbXmb
h,z�obTz<�Drb�rb<Rz@sbxtbubpub4Ro��b
c�b&z��Txb�c�z�yb8zb�zb�siЉg�r��Ri0�mЉRev��RiH�g(mtxvtv��DRt\{�ЉT��m)�Jom��r}f�w
TxHSa�b`Sc�	dd
fl
gt
h�Si|
j�Skhl$Tm,Tn4To@Tp|r�
s�Tt\Uu�
v�
w�
x`Vzx�S�8x��d�����t	�|	�H�Xck,)pTSr�wpu�� So��<Sm(w���*o�lSh�uX���;m�o��tSe�Sm0��SdЉ�Se̍�o�l)�Si�hlTrT�܅zDq��Se�o���S��o��t�i�S��f�Sdtl��T����x\��bX�TopTÈi��g��PT��fXT��f��dT�@�%�*d�/z<�ܐ�%bc|Td8f(gLhkd*l�/m,)p�Tr�%s�*t�*w�z��L*�ܑ�n�Tt\z̑q�x8Ue`-taDUePiXo`u���Uo��� Ue��,Uŕ��k-tUi�Uo̖LUb�Up0����lUe�l��y���Unԃ�Ut�-�Ua�e�UiTVoVz���Ur�v���Ue�yiVr���Ut@��Љ�Uoz�lo���Vb�y�̖Vt�~�$Vu�q��0Vz�o��<Vr<�HVdyu�VeT���s\���lV�h����VcxV�t����V�D��Vs�V�,h��og��ViTx-�y<Wa�b>c�	d�ed
fl
gt
h|
j�
k�?l�
m�
n�Wo�
p�Wr�
spXt�
v�
w�
x�Xy�
z4	�\Ŕ��olWrv���LWn�w�TWa(w��`Wz��(�xWazXaXXz�W��s-��a�We�
��f�Wj�f���W��n�Xm���Wz���Wc؛kmbHXa(Xe4XiPXo�y�zv���Ws��Xn��@XuЉlUe�s��N\�(zudXe���c$}fXm-�Xa�
e�Xz���xXc����X�Љ�X�Tx���Xi���Xs0���n��X�\~}Do���X�̖XYb chYd|Yf(gLh�Yk�Yl�Ym�Yn�Yp�Yr�Ys�t�wZz�,�@YŌg�����,�����k�(e�Or�o$kTz<�Dr�nxt,ZlL)ruXHs�tpu@Zb�*kv�#c$d�v
c�ZoTZp(w�*o�s\z��Tx�chZt�z�z�Zb[e�s��Zj�~�� Ze<m��m)8Zrw�}qLZs4m~�x��|Zc�Zk`Zn�l��n���4���Ze@�%[a�*d<�ܐ�%bc�Zd8f(gLhkd*l�/m,)p|*r�%s�*t�*w�z��L*Ŕ�D���w���bp[c�dx[f�gt
h|
j�[khl�[m�n�p|r�s�t�
v�
w�
x�zxĄ�����8z�b \c�	d(\ed
fl
gt
h^i�j�
ktl�
md^n�^o�
p�r�^s�
t�
vP_w�
xX_y�
z4	�\Ŝz�z���[�d	�l	�t	�|	��z
X��%b c(d�%f(gLhTk�\lH,m�\nT,p�\r�\s@)t�\w�\z�Ą\ňiV]n�g��x\�x��u]wv$d]t�]b$d�n�s�t&zx�8]pX]t�z8z�]n�zd]b�]g4i��sqxvq�mq,~����$]zX��,]r�m��l)D]hh���L]c4��8Zr�r�r��p]�L��x]�(z�z���]z�y���]r�f�]ttl���]��l)�]�T�^dk��u���]o(^u0��]lЉ�]eX^l�oTx��(
e܌^sT� m�n�4^o�l)@^zu��L^c�l
|^i0��bЉp^eT�\dm
�^p<_z�i�`Ri_o�u_w�g���^��f�^��f���^�X���^�D~��k���^wT��^b4j=�m�`oH_wXm��_hD�$_cy�0_e�~�0}f��b�_c�_d�`e�%f@gLh�`i�_k�!lH,m`oxp(`r�s8`t�w�z�İ_Ōg����x����_�HkVT`sXm
h``z�o�rTz<�xt$3l�`r�q��o��`r``d(w\z�Šy
c�k�ry�Li�L`z�nnt`h����}��l`a�tbxt���`ś�`kT���z���`o`R�`z�~\z��bp[c�dx[f�gt
h|
j�[khl�[m�n�p|r�s�t�
v�
w�
x�zxĄ��z�aa�b�	cpcdDded
f�gt
htei|
j�
khl�em�en�fo�
p�fr�
s�
t�
v�
w�
x�fz4	Ġa�\~�o@{���a���l	�������b cbd�%f@gLh(ci4bkHbllmdcntbo,)p�br�s�t�bu�w�z�İ%��oco�rLyTz<�xt$3l�`r�tu�bg$k�t<m��k��\br�bs`dbbm�(w�bc\z�Ŵ��bt�lq�m��l)�bh(���y���bo�j�cu�j���b�,g���bČg���b�T��b�̖��-@ci`Rcn�`zXm�Љ8cc0�^dЉLcev��Xci\{
�ce�cy�z-�ca@eXyX��cz8z��/u���cwxvT����ct0��cn����codr8dwTx���ck���cs�o
��dd���deu
��$dlD~��,da���%b c(d�%f(gLh�dk�!l m\2p�dr�ds�dt�w�z�İ%�xt$3l�$k&z��Tx�cTem�z�y�deHeh�IkTx���dspu���de,ep����dm esu�elTx��eem��v��4ep�}��<ea��u)Dopu��\enЉhem��ea�m�H_wXm���eh���ecD~���eym)�ew`����etl��er�<fi0��%b clfd�%f(gLhk�!l|fmT2n,)p�fr�%s�*t�w�z��Hf�Љ�ee�g����df�x����@jV�oTz�+�pu�#c�&z�-T��fo�~�fo<� Kz<}f���f�\h��f�gw���f�@|��z)�fz8z��gcXm�d}.,hi��H}.�hbgc�hdifigl}hx}jikkl�}m�}n@ip�}rLisXit�}v�}w�}xdiz�j��j����i�@j�,g��g�Hk��o�d}. h���H}.�hb�hc�gdifigl}hx}jikjl(im4in@ip�}rLisXit�}v4jw�}xdiz|i�,k�dp�o��h�Љ�@j P}.�g��X}�4h��h��h��h���H}.�hb�hc�hdifigl}hx}jik�}l(im4in@ip�}rLisXit�}v�}w�}xdizX|�@h�,g P}.�g P}.Hk P}.�k�d}.Xm�d}.�o�d}.Dr�d}.�r�d}.xt�d}.pu�d}.v�d}.�v�d}.Tx�d}.�y�d}.�z�d}.\h P}.�f��pi���H}.�hb�hc�hdifigjhjjikjl(im4in@ip(jrLisXit�}v4jw�}xdiz|i��iŌg��d|�4h��h��h��h�@s�d}.�s�d}.u�d}.(w�d}.8z�d}.��H}.�hb�hc�hdifigjhjjikjl(im4in@ip(jrLisXit�}v4jw�}xdiz|iĬjňi P}.�g���j�4h��h��h��h��h&\h��P}.�jc�f���j��g��X}��}��h��h��h��l-u��d}.km$kn�l-�g��X}��}��h�p|��h���H}.�hb�hc�hdifigl}hjjikjl(im4in@ip(jrLisXit�}v4jw�}xdiz|iĨkŌg���j�4h��h��h�||�i3�i���}.�kk�g���k��l��l��l��l����}.�|b�lc�ld�e�lf�lg�}h��i~j�lk~l�lm�lnԃomp<mrmsmtT�u8~vD~wP~x(mztl��k�\h��}.�f��hl�@j��}.,g��}.�g��}.Hk��}.Xm)�}.�o)�}.Dr)�}.�r)�}.xt)�}.pu)�}.v)�}.�v)�}.Tx)�}.�y)�}.�z)�}.�l�(w���}.t=d4mn�o,~z���}.0�a�mb�|c�ld�lf�lg�mh~j�lk�ml�lm�lnmp�~rmsmt��u8~vD~wP~x�nztl�mŌg��\~��l��l��l��l��k)�}.u)�}.@s��}.�nb�nc�nd�nf�ng<�j�nkD�l�nm4mn�np|~r�ns�nt`�u�~w�nzTn�hn�hl9tl��Ln��l9�s��h~�`n��n��n��n��l9�l9�l9�m��l��l��l��l��l��l�m�m�m�(m��z��}.lob�nctod�nf|og�nk�ol�om�on�np�ns�ot�ozTn�Do� @{��<o��n�\o�do� ( 4�\{�L��~�����m�$~.<}?�h3�i���}.�obXps�g���o��l��l�dp�Dq����}.�mb�lc�|dX�e�lf�lg�}h~j�lk�~l�lm�qnmp�qrmsmt��u8~vD~wP~x�qztl��o�yDLi�Ppz�g��}.�ob�pc�pdqfqg�kkqmqn$qp,qs4qt<qz�p��p�piK|i���p��}R,k���p��p��p��p��hKp|Y�hK�h3�g3i3i3(i34i3@i3Li3Xi3di3Hk��}.�ob�pc�qd܅eqfqg��j�kk��lqmqn$qp��r,qs4qt��w<qz�pĤq�4hK�k������q��p��q��q��hK||Y�h3v)�}.�{i(w���}.4mn�~z�z��}.lob�nctod�nf|og�nk�ol�om�on�np�ns�nt�ozTn�Do����}.4b�lc@d�|fLg�}hp�i~j�lk�~l�lm�lnȇoXp�~rdspt8~vD~wP~x|z�~��~ňi���}.�ob�g���r��l��l��l��l����}.��a�mb�lc�ld�lf�|g�}h~j�lk�~l�lm�n�omp�rmsmt8~vD~wP~x(mztlļr����}.�mb�lc�ld�lf�lg�|hp�i�sj�lk�ml�lm�lnmp�srmsmt��u8~v�swP~x(mztlĴsňi��}.�g���s��l��l��l��l��s)�}.(w)�}.8z)�}.���}. �a�mb�lc�ld�lf�lgXth�|j�lk�ml�lm�lnmp�srmsdtt8~v�swP~x(mztlĴs�@s)�}.�y)�}.�r�����}.��a�mb�lc�ld�lf�lg�}h�i~j�|k�~l�lm�ln`�omp�~r�tsmt8~vD~wP~x(mztlļr�Tx)�}.�t�tzy`�{t���}.�mb�lc�ld��e�lf�lgXth�sj�lk�|l�lm�lnmp�srmsmt܌u8~v�swP~x,�y(mztlĴs����}.l�a�mb�lc�ld�lf�lgXth̍i�sj�uk�ml�|m�unH�omp�srvsmt�u8~v�swP~x(mztlĴs�xt)�}.�{nv)�}.�{eTx)�}.�{k�t���}.0�a�mb�lc�ld�lf�lgXth�sj�lk�ml�lm}nmp�srmsxvt8~v�swP~x(mztlĴsŠy)�}.4mn���}.P�a�mb�lc�ld�lf�lg�}h~j�lkwl�lm�vnܐo}p�rwsmt8~vD~wP~x(mztl�m�v)�}.|eTx)�}.wzy`u���}.4mn���}.�mb�lc�ld�lf�lgXth�sj�lk�ml�lm�ln<�omp}r�wsmt8~v�swP~x�wztlĴs�Tx)�}.�z�z��}.lob�nctod�nf|ogxh xj�nk�ol�om�on�np(xr�ns�nt0xwTn�w�\~ t~.@{���w�<o��n�\o�do��}�~��~�D~��g���r����l��l����}.�mb�c�ld�e�lf�lg�}h~j�k�~l�m�xn\�oXp�~rms�xt8~vD~wP~x��yyz�~�8x�v���}.�nk�y���}.�nk4mn$�r�xsH�wyem)�xz�z��}.�a\ycD�e�nfhyktyl�om�yn�np�ns�yt0xw��y�ozTn�Py�@{���n��l�T�z�l�$~.�~�$kn��$~.m�$~.��ox�r���}.�mb�lc�ld�lf�lg�}h~j�lkwl�lm�ln(�ompzrms$}t��u8~vD~wP~x��y(mztl�m�(w���}.�nk(zz�o3�w�$~. zn���}.�mb�zc�ldX�e�lf�lg�}h�sj�lk�ml�lm�lnmp�srmsmt8~v0}wP~x(mztlİzňi��}.|a(|o�g���z��l��l��l��l�Xm)�}.@|z���}.4b�lc\{d��e�lfLg�}h~j�lk�~l�m�nmp�~rmsmt8~vD~wP~x<}ztl�@{Ōg��\~���l��(��o)�}.�nkt{nx�r�q�8zk@�|{wЉ�{o�yr�lx�xkTx���{t���{s�{z~k�z���{j�lr����{n�}���{u�ok��|dTxkT� |s�� |s�n�4|a\h�P}.�f��L|��i�P}.�g�P}.Hk�P}.�kq�}.Xmq�}.�oq�}.Drq�}.�rq�}.@sq�}.�sq�}.xtq�}.uq�}.puq�}.vq�}.�vq�}.(wq�}.�yq�}.8zq�}.�zq�}.��H}��i��P}.�}�@s��d}.�s��d}.u��d}.(w��d}.����d}.8z��d}.���d}.@j��P}.pu��d}.v��d}.��H}��}�@s���}.�s���}.u���}.��o�}��w�$~.�����}.8z���}.����}.�i���}.�s�t~.�}��sx�~z�}��w��~.�sx$~.(w���}.u���}.�w�$~.d�w\h���}.�f���~��g��\~����(�@j���}.,g���}.�g���}.Hk���}.�k���}.�o���}.�r���}.�v���}.Tx���}.�y���}.�z���}.v���}.(w���}.$�aD�o�x��$~.(w���}.��c�e,~zy�$~.Xm���}.�h�m�$~.xt���}.0�o�r�~���~zpu���}.<�rz��$~.<�z$~�(z�4�.D~��$~.�n��~.l�b�}�lo�d�.z���~.���}.4b�c@d�fLg�}h~j�k�~l�m�n�oXp�~rdspt8~vD~wP~x|z�~��~�Xm���}.Dr���}.xt���}.pu���}.���}.4b�c@d�fLg�}h~j�k�~l�m�nXp�~rdspt8~vD~wP~x|z�~��~��q��$~.��z�~�4�.āáe`Ri`o��u��y̖����S���}��y��ԁh,�S܁t���e�fT�ky�d���zDr���s�l�v��(�d��0�n�}��<�ext��H�h̖�xt��`�uT�h�k����t�oЉ��nd�rЂs�z�v�`���phy���oy���kTx��ĂzTx��m��܂s����e�z���n��pu����m�}���a�l)(�h����4�g̍@�n�l)L�i(w��X�m�z���p�z��x�u�}����axt����hT���kl�3�����aTx����mT�ȃs�����yD~���a�o���w��d<��a��
�o�$�ey�,�mTx��؄i8�z̖��kD�sD��P�d�n�n�l�a�t`x�p�t)��zxt����smx�mTx����s����s������eЉ̄n��|u���lX��lD~���e�l)�wu���d��$�l�sx�mx�x�Tx��L�t��T�sT�LWn�w�l�o(w��x�z̖��r�jj8j8(j84j84q�z��ąb�m��̅z�q�l"v���d����nD�x�a���l����hm)(�c�y��4�s�@�t��L�u0x8�x+����l�tP�t�s�n���ay���pTx����z̖��s�a,�e`Ri`o�r�u��yXm3���c�w��a(w���z̖ �syTx���z��S�l(w��4�m��<�r�}��H�aTx��T�h��zЉ`�s�����|�n4����iy���b�$�t)��txt����sT���k�l�rP�s(w�T��r�~���o�l)�lu���k�y9T��t�z�� �o��,�z�l)8�aTx��D�f���$t�l)\�e�o��h�g��o��t�d��@m)��a�v����tT���pd��z�l)��su��ȈfT�ԈlT�Gu���oЉ�l�l)�iv���d���nzq�y��0�r<�8�t�n$y�P�pTx��X�zЉd�s�n�0xwXm��|�z̖��cĉx��M����y�~����e�����l	(�a0�e8�i��n@�o��u��y�����f��f���������f��f��������ЉT��lSXm��H�k̖P�c<m��\�u�m)h�rm)t�b}q��sv����nԊsXm\̖��c<m����u�m)��rm)Ȋb\~��m����z)4c<}q��b,�m�z���z���z�cv�<�4�n���<�o���H�rm)T�k��r�m)`�s(w��p�l��|�r��h@s����e̖��h�~����u��o�����fT�ȋf�|qԋo�m��h�l)�h(w����cЉ�r��Tx���m��$�s���0�e�l)<�rv��H�gT�T�nD~��o��l�w��t�d��w�|q��eDr����f����fD~����ap����wDr��Čt̖Ќf�k�����r�l)�e�t)�fxt���s`%��$�o�w��w��8���w�x�l��n@��(w��L�z��`�r�o��o�y�Tx����zl���s�o���ay���mTx����zЉ��e��s���i@�����܍z0��rЉ��$dl���az���exv)$�rv��0�tT�<�nx�zxt�`RX�k��`�i�z��l�aD���m���lXm����hЉ��cTx����i����s�w���a(w��Ȏz̖Ԏrm�8z���s`��wi��o�i���k�g������H�r$��8~q(w��@�v�ȁće�f`Ri�o�u��y<��$���|�z�x����r������t̍��s�n���i�m���mXm����hԓ��؏td���e�|q�sDr����fT�S�����rpu�� �e0�,�mv��8�a��D�n��s$��0�`�l���h�a��hTx��t�c���{.�q)��i�o����n̖��d�i����u�g��Đ�T�@�d8�lt�mܑr��wА�,~������z�v���r0��p�{�e�q)(�i�o��4�n�f�tl��L��L���u)��iT��pu��d�nЉ.�}��y����h���tH���u�����om)��mm)�l��s(w��̑t�l�v���d���nw����a�y
0��tЉ�eX�uu��(�i��.(w��D�e��L�rD���dy�d�em)p�z8z��|�su�����l�m���a�l)��hP�
�n���ay�ȒpTx��Ԓz���sv�����n�l)�exv)�gv���t́$�nT�0�e|�k��s�n�̏��P�z<�X�c�~��d�oxt��p�r�l�̏����k(���c�x����oTx����t��3w����e�y��ȓl��ԓt�����rv���eT��n�w��o(w���z`�$�r�sxЉ�z8z��D�iT�P�wm�v��h�tT�p�pv��|�o����n��s���m�x����eTx����t��	\~��ȔyPy��Д�y�ܔ�Tx���z���s�l�v���gT��n�~�� �o�z��,�l��8�zt���j�j��T��,g��\�Čg��h���l�u����mT���l�}����oxt����h(���kTx���ĕsD~��̕a�l)ؕwv���k���n�y�`��t���oT��y�y
̖4�t�~�<�u�q��H�z�o��T�r�S`�dԃl�o�m)x�o(w����b̖��r���d�r����o����g�áe`Ri`o�u��y̖S�t�l)�su����kT��l�lv�� �d��(�nxt��4�e,�S@�k��L�ex�s�x���Hf�HmTx��h�t�áe`Ri`o�u}q�v����p����p�}"�m���.Xm��ȗh�"̍�.pu���i�}*�s���.0��jЉ�e�u) �i�l),�n�s��8�m0�D�jv��P�a��\�n�60�Et�.Љ|�e�q���i�l)��z�r����d0�O��gЉ��ev��Ęi0�ИnЉܘe�q��i�}T�s���.���j�n��eXm�� �z0�,�c�}]u��D�.�]��X�.�l)`�y��z�r��l�d�d0���.Љ��e�q���i�m����.�n9��ehn��ș��w���.�n��y�}"pu���.D��my��em) �z�}��m�8�.Xm��@�h��L�c|�m��̍h�.pu��p�i�}�xt����.�}� |k��.4|��s�f���ag)��z�}��s��Ԛ.0�ܚjЉ�e�u)�i�l)�n�s���m0��jv��$�a�}��o��<�.�fD�dtl��P������\��Љh�n�}��m���.Xm����h ���cěm��̍��.pu����i�}�xt��Л.830810818220321125232240822302102314141521052150030110002100212302312342223383400424304822021824310122103401341408343000343014030542040221022123402012031001201023250204021210121011010000210223045204300030413043040520140521032102220300134023400002323400403400040342034021034000344030003021043220043200040432000143200004320404300040450001450004045001450000432043220432014321043212302500004323200050250015200050150004032132023223023440331034001100001432004010001201240304234000004034423223100012	450000000450245214512450004500451045220100000324000424004210341210342110002123220034000130344234213402052360212100450000047000032004033013201320324010021102322022001304013040402020101221002010100123420134000013401013400210340023021310042304342343200000432000	43200000043004043430432404320014500201045014500000020145000000000
4500000000101021
45000000214032403000	540000000540013000025410103013201230134300454000040000421034213000323430234324520145202145430250014001010234210452002520210000402031020110270003340404300421043014304043042204322232304034040301434040012000342302102300004321202400201002020102021022010221002430200040220100200100240300214020040320000322203220320214030080008080008080080088000880088800220088800080880088880080088002000234221021023201203000230002302302100022302120301203021043020200400302102030000250002120001032012100123021400002104000004430210001200043020300001201000251002510241004012501	2700002304030210300004023200023403400004032001	40230200020000230200023004023032025000002321020100140000222322121089888008988988880088889889888800	10010002110218898880089880089888800	88889080088889800	8090888008090880088908988800
888988988880088089880800	88098880088098800PK
!<N�à�hyphenation/hyph_pt.hyfHyf0tP�����'-4�������$�'–’-111001�������L-�.�a|bc|d�e|f�g�i�j|kl�mn�o|pTq`r�s|t,u|v�w�x�zX����a�e�itl�o�r�u��`��,X��	��������������������
�a�c�e�h�itl�o�r�u����0a0edi0olu|���8��,
X��	t�t�t�t�t�t�t�t�t����a�e�itl�o�r�u��,�a�e�i�o�����a�e�i�o�u�����a�e�h�i�o�u��,���a�e�i�o��<u���a�e�i�or�u�����a�e�i�o$s�u����tl�r����a�e�o���a�e�i�o��X�����`����a�e�oDu,�"�T.`��\e���hr���ta|���w`���d����r���a���h�s|���t����f����o12010010001000010403030010	888988800PK
!<�{i��@�@hyphenation/hyph_ru.hyfHyf0tP�����'-4�������$�'–’-111001\1�������,�-�.@�x�dp���� ����,�����8�p�0��H�-8д�@��X�\���<���p�\����P�h��t��L�\�`�H
����l�
єP�O����O������������x
������Р����$��,�$'���D�Lќ��X� ��d�l"x"��|��"$�Д"����� ����� �������h$�#�����#����ќ#����#��X���ѸD�$���<'�-*d-��X��$�H-*,��)�����<����,*��H�-H����?.�?��p�h?x�t?���������������?����h���7����������t��8��H�-��$�Ѵ�4`@����l@���*DN���TM��l���4G������G��H�-,м�ѬU:���T��P��T��\�HR��`��h��������R��H�-t�<����U�����$XW����lW��|W������<W�����W\<��РZ�����P����LZ���4р]<�\��,�x@�w��@��wH�f�����T�Hf��H�-`��*�����\������Ѡ��������,�����@���������H�-��Ĉ4Ԉ������Д���<4�D�������\���$����������������H�-0�ј�<���d��������X���l�МB��������,���l�:��|YFT�����(���@�����x�@�� �������Ld����t�0���Р�$���(�`��l���H�-8дѤ.�x��`��wh��$���������������$�:��������������������������������*d������m������,����H���$и����4�0���h�������H�-@���R���p����x�l����������и�X��������������~\������Hh����������������������И�h������4���t	����� �|�����<����	����H�-L��	�H�B����\
�t��
�����<p�b��4	���d�����������А�������	���\�h�������������|j�����	�����	�P	ь���$	�\	����Hr���H	�P�Bp�|N����h	�����p	�l���|	�T����	���`���	�a���	�h]���	�8����	�(����	���<�����	���	И
�0���
�����	�L���
��Ѱ���
�p
��
�p��
�����H�-,
��
Ѡ?����\
���d
�LB����|
����
�T�x�z�����
�����
Ј����
����
������
А����
�,�<���P>�0����������l����`�x���<����DЀ����P�����.����p��[R��������|
<��8
��	��0����	�4�(�������h
����hѼ�����h�����X��H�-�ЈѠr\�� �t��(��):���@����p��H�@�����\����hм��|�|����.���������Ѱ�������м� �����
��
�$�h��H�-����x������h������@������������Ļ�л�ܻ�������P��DgzTg��P
��fzX
ь�� ��d
��
�T��p
Єn.� а���
�����
�$x#���
�`#�
�4#���
��
�|!���
�|�*d'.D'���
�T'����%����%���X�*.�)��4�@���)��<Є)��L�h��**�x��x��p��wx�d5��p"���85*��P2��`!�"������"�����2��H�-����7:�@:x&�?���%�����`*,G���G����N��M��4(�(��M*0�)ѠK���&��'�@����K��H�-PЌ)���<P��|�����hP���д�*�l�*0^:h]�������8�H�h�x����]��H�-�Ш��_:<`Ԏz������а`�� ��`��,�\*�a:L�$��+�P��a�*�X��a�lb�*�b�����b�����b���������+������Xd:�+�df*pf�����e������.\������1���q����p��Ѡm��(��m��H�-4�|$�x��P��x��X��x���-�d�tx��p��v����<w��H�-��T��x��/����������H�-��0���-�Ф�@������������\�<��L���`���� �<������������ ���0�,*�8�G��R�Hf�L������������l����������X��*������ *� ������Խ*,*��-�p��,d�,�����,�����,�����+���)����,��.��.���l. �DѬ/Ll/��<��4x3��P��2��X��2��d���064X9�7�����������8����-��T��9�;�x;����d;���<<��<* <�����?�A�@���,��A�LB`B��4��A��<�A��H��������������C��C��x��C��@D�D�Eztx��v�����6$�� G��4G���� �T�|���G��-���ќG�G���0HzhP�����(���0ЌI��<��H*H�tJj�I��`������I��hДJz���HK�������J�Ѥ��TM��Q�4O�����R��-����dS� ���HR����@�x������jxS���p�zW��,��V4�Xz�W��L��W��T�|W��`�<Wl�(Y$�Z��Z�����Z���РZ����LZ��Ta_�����^����ќ^����(�4�P��aj�a���(dB�c����b�td*���e��<��d�D�Hf��-���TMz�i��p��ix�f����������<Wzlj�����j����r*�w$�yz�x����y����$y������{*��@����,���@���$�`�������������-0Д�@�:��P�Ѱ������p���x� ��������RH��^�^����؋���Ѩ�Ѵ�����(�H������t�D��P����|F`������$�����4���<�T�����T�����\Р���h���̑��Ԓ��������-���Ѵ�X��\�����Й(������X�z ��������-���������(�0�����0��*\�L�8X���<�<���D����P�������h���\�*pи�l�Bt������������B�������������������М���������$�<�$�����H�h���l�����-М��N����4�0�\�H��d���T���������p�P�x�D�������������\��Hh*��x������~���������H��������$��р�r�������P�l�����������-� ќ�V̞�����X�H���`Ѽ�\Ġ��x�$�����l�����������P��0����������������$�м�����H����������<x�z��������4L���0��8�h���D�����P������`���������-p�\�lf|���������l�����������������������������(�$��h�pt����T��H�DxL��(����0�l���<��(���T�������X�����t�|�$|ФѨ�R��|���������������`jh]����8����������$��-�,Ѩ�*�������*|�*����$�<���*PѴ������H�H�X��\���(��dм��t���X��-��P ���@����0��h
���  ь
��
����|
��8
�����4��� ���� �`�� �z@��, �p ����4 м��D �d��\ �0��d �0!� ��| �h!��!��!��!�<"�H"�P"�h����-� Д"�x��� �#�`%�<'�((�P(�Խ��(��(������ ��4��0)��)��)�$���$���!����!Ѱ��$!���|0��<!�8D!����P!��$\!� �L��t!�\|!�l���!�����!��Q�����!��$�!�T�!�`j
0���!�<���!Ќ���!�"�,u��"�z"�� z� ��("�� 0"�|!�%-�(-��X"��,��`"є,��l"�D,$x"�"ф+���"��"��"��"��"��"�Ȧ�T-$`-���"�.�/$�0��1r27�P2���"�8#�X#��2����-�"М#ф��(;��$#��:*,#�<(�;��D#�h;L#д���6��d#� @�l#��?��x#��?$�#�?���#��#��$�,%��Mj�A���#�A���#��@���#�$�0$�h$��@���#Д$ѨNN�A���#��AL$МB�HB��$�0B�$$�H$�Ъ��B��@$����C��T$��C��\$�lg�8F��t$�HF��|$��D���$��$���h���\���$��F��$Јj�|���$�|���$�%ѬI���$��I�$�0|�8|��%�l���e��%��J %ЌK|%РK��8%��%��%��%��%��K����-D%Р&�hL�hK��t%��L(�L���%��%��L���%и%�8Mj�M���#�����M���%��Mr�%�hP|S&�T&�,S�8S���%�HS��&�XS��&�@&�t�\�S��,&�T4&�P*lT��L&�l����`&�|U��h&шU��t&��&����U���&��U���&��&��&��&��&�,'�4'�@XX[�6Lh\�h���\���&��\\�&�'�`���\��'�$'�������\r$]�]��-X'��'�0^zh]��P'�l'��'��_*�'�LZ��_��x'�h�jPa���'�`a���'�dfpf���'��e���'��b���'��'��'�(�lg*�g*(� h�h���'��\��j$�h��(��h��-(Ќmr�m��8(�x(��m����-@(Ќ(ф�:���d(�ppl(��r�q���(�<w��-�(��(��vj�v���(��(�xyz`z�y���(��|z{���(��z��-�(��z���)����)� ���)����-$)�L)Ѵ�zȌ��D)��Kȑ��X)�ؑ��`)����l)�����-x)����-�)Ќ�*�����)��)��)�p�*�*0��-\*�(+�@���)�l+�x+��+��+��+�0-�<-�H-�`��`.�l.�d��$0�00�������)��2�x+$��<*��D*����P*��*��*��*��*��*�O$���|*���*��z����*���*�L�*�������*�����*� ���*��L8#B�"��+�#��+�@+�� ��+�`+��#�#��8+��&�&��L+�`%��T+�,*$��-8��-�+��E
�A���+�G��-�R$��-Hf��-�+��,��g�f���+�,�,,�8,�@,��iz�i���+�,��i�+�$jz�k$�k��,��j�� ,�xlNHn�X,М,�n$n��P,�t,�|,��,��,��n�n���Bo�o�o���,����{��P��,��{���,��,�$y���,� -�(-�`}�}$-��}���,��A4~��-�-�0B ԁN���L�$��-����-��r��-d-�H.�H�N\���\-��-��-��-��-�X���-�̘L$��,�N<.<P����-���-�d����-�,��-�l���-�.�4�z@����-�.�.���z(�z��z����.��z����(.���0.� ���<.�X.�X��.��-l���-�.�l/��������.��.��.�����.�$����.��.�8�(�.М�2,����.��f�xl<d����.�/�0����.�0/�t���\���/����/����$/�@/�0�<���|���H/��/�D���P/�����`/��/��/��/�0�H���`<��F$�N$uV��*�/.�/�0�p�������/��/��/����\����_�����/������/�����0������j�.��-����-|0��1�d��t���D0�D�L0�P���X0�L���d0Ѱ���p0��0��0�1�1�01��1��1��Ed`����0����0����� ����0�l��x����0������0М����0������0�T�<�r���1�$���$1�`1Ѥ������@1�$�H1Ѽ���T1���4����l1��t1И1��L����1��1��k8�������1�T����1������1�|����1ѐ����1� 2�|2��2�|�z����2�02�����2�p2��D2Ѐ������<2�0B����P2���X2А���d2���N�2Ш2Ѹ�q����2��*����2�@*L���2��2Ѵwh����-�2.x3Ј5�x���2��5�$6�06��6��6��6��6��6��6����6�<7��7��7��7����N���@3��3��3��3��3��3���H3��3� ��h3�D4��4�5�d��4���3�@���3��(~D�������3�4�`&�4�4��&������3�H���3ј(<�8<(���$4�T4�\4�d4�T��,4�t4�$�D���������l4�"�H!���4�|!���4��4�|S������4����4��4�4#���4�h\t����4�H���&���4��&���4��%���4�,5�<5��%��5�l5�<(���|(45.�6��)��H5�x5��)��P5Є)��`5��)$�+��+���5��5��5��5��"����.�5��.��.���5��.���5�@.���5��1F�1��/.�2��-6�7�P2��6�6�89��K��-�]r��-d6�|6ш_$�_��D6�8_L6�h]��X6��	�Xd�b��t6��h$��-�m$��-�s$��-<w$��-�z$��-�$��-T����-�6�7�ԂXt����6���*7�07�����6���l���7��@���(7������-�2.X7ь������P7�h7������Ȍ��p7����-x7Ѱ�4��-x)����-0��-<8�8�@���7�l+�X9��9��9��9�<��d;�<<�`��P=��=��>��>��?��A����7�A�dzp��8��$8����08�\8�|8��8��8��8��N�$���d8��8��l8фe:|�4�����8�<��8�L�\���8�����8��# 9ќ#���8�#���8�� ���8�`+�,9�49�<9��D��$��9�P(ԽN�)N<<�7��D9�8����-L9�t9��A�A��l9�G��-�R��-�9�^��\���9�LZ�9�HR���9��g�f���9�:�(:�<:�D:��:��:�Hf����-�9�:јiN�k�k��:�4:��j��:��k�xlHn�\:�|:Ѱn�$n��T:�l:����or�o��t:�ا�`�X45.�wX�\�x���:�y���:�$y���:��:�@;�L;�T;�\;�Ч��{��:� ;Ѹ{��P�A�~��;��};��}��;� B,��,;��~��4;�ԁ �0������-x;�;�@������;��;�؋:�������;�����;Ј�:�;�Ԓ���;���B�d���;��d���;��d���;�h�z�����;���H�<�X<�\���<��<��<��<��<�������- <�,=Ѱ z���P<�p<�x<��<������d�Gz�����<��<����<�Й�X����м��Ġ���<��<�$����<є��l�<xL#���<�X#�<�L���=�x���=� ��� =�@=�H=�����*�$��-l=И=��*����d=�X�����x=�l�*�=ќ����=�l���-�=�`>��z�=�$����=�>�<>���8�����=�T��d����=�>�0����=�$>�H��h������>�4>����jH>�P���@$����|>�����T>�0�p��������t>���$��-�>Д��H������>��$��-�>�$?�������>��>�?���������>�p��>�T���8�?.@?�T?�(���?�`?����a�h]��8?��g�H���L?����L��?а���	h?��?�@�L@�l@��@��@��@��@�������-t?��@ќ.(�����?����?�0����?����?�P=d�����?����@�,@���
����@�������$@�yzL���8@���@@�0�$����X@���`@А�X����x@�X�B�@���<�����1��@��@�|����@ѐ����@�A��A��A��A�\��1������@�,A�4A�PA�����A�|A�|�r�R�=�0���<A�<�DA�������\A���dAѐ���pA����2����A�A�����A��b4L���A��X��-h����-�2.`B�tC�x���A��C�@D��D�`E��E��6��6��E��6��E��E�|F��F�G��7���$u$��DB.xBАB� ��LB��B��B�LC�����3��3�������B��B��B�l-Խ7T��DB.�B��-����B�"�H!���B�C�C�C�$C�|!���B�4C�l"(���",�`#�4#��,C�DC����%j`C�\*<�)��XC��0A�+��lC��C��"�Ԩ��1I�2��-�C�0D�85QP2���C��C� D��)��7���C�47���C�D7���C�D�7���C�D��7Y�8c�8��D��=m�Ju?��(D����K��-tD�HSXS��PD�|S��XDЈDѠK��dD�PBlT���D�T]h]���D��D��D��D��D��D��]����-�D�E�8_B�`���a�lbBXd�b���D�,E�TE�Hg$Xg��E�8E�lg��EДg.xy�g��@E��g��HEЄh��-�p��m��lE��m����-tE��z$��-�E�,��D|���E�T|���E�{���E��:��-�.T����-�E�@F�Ԃ<t�������E�$F�0F���dt���F�����Fм��<���8F�TF�pF���:L�4T���\F�`�dFЈ���-�2.�F��F�$�I�����F��F�$��\����F�l�X�FР���P7�h7��F��F����I��I���-��r��-4G�4O�0*��-|GШG�@�� G��G��G��G�0H�HH��H��H��H�`���I��I��K�L�TM�O�����8��#
�#���G�#���G�� ���G��G�<'*,*.��-�G��)
�)���G�8����-L9�G$��-���R��H�4RH�HR��H�@H��R����- H�<W(Hf����-?.dH��f}f��\H��H��H�\s��?�ls��xH��sL�s���H��H��sz�H��HѤtzHvTv���H�L�.��-��$��-��*��-$IЌI�l.$�)��I�̘$I�<I�\���I�\I�ܘd���4I�4�zD���HI���PI�l�L���hI�x���pIРI� ���|I�<�
(����I��$��-���DB.J�\J�$����I�tJ��J��J�l�����-�I�tKѬ������I�J�0J�`��|������J�d�$J�,%�����<J�Ƚ��DJ�Խ��PJ�lJ�t�~0��J����>�����J.�J��J�$������J���P����J�P��J.K�HK�d��l����J�x����JЈ����J� K�(K�0K�8K���7<��|�7����\d���@K�b~x���TK�D���\K�����hK��K��K�����A<������K��K����K�$�7��.��-���h����K�L����K������K�,L��L��L������-�K�Mф���p�$L.HL�tL��������@L�\L�dL�ls����������lL���4����L��L���L��L����L����L�L@�t����L��L�T��L�L�X�����l����L�(��l��(���M���L�����$M�|M��M��M��M��M��M�N�������-,M�DN���$����hM���pM����������M�T��M��$0����M��M���*l��x����M�X�B�M����$�z����d(���M�|�$����N�`N�|N�4A��N�����NА���8N��N��N��N�O��4q�7I����hN����pN�$��0����N�<��N������N����NРBx���N�8�N��r����N�����N��X$��-h����-�2.�O�Q�x��O��Q��Q��Q��6����Խ��6�R������ ��4��(R�����d��\����O�J����O��O� ���O��O�@P��P�� �����O�������O�\4�T���O�Oь��L;��!��!��P��!��P�H!��P�C�$C�PP�|!��(P�XP��"x4#��,C�hP�|�|M�%��pP��P��P��P��P��%��xP��P�T'��'�(�T)\*�)���P��P��*lb*@.���P�.���PЄ+���P�0Q�<Q�\Q����1��Q��0$Q��1�������DQ�lQ�2��LQ�|Q�H���02��tQ��2��-�Q���<2FP2���Q��Q��3��K.��-_h]���Q��]����-�Q�Q�XdR��b���Q��d$4d��R��z.��-�.��-0��-�R�4S�@��4R�l+�dS��9��T��T��V�0W�<W�`���I�(Y�\Y�hY�LZ�O���HRМ^ѼX����R��R��8��*��H�dH���R�HH���R�����R���R�S�Oz����R��#��ќ#��S�#��S�� ��(S�XS��&�&��DS�`%��LS�8:��-xSЀT��7�����S��?�T�4T�<<��S�SѠ�- <���S��S��S��<-�<�<���S��S��<���S��< =-,=���S�T���~�=R@-t?��T�DT��?��$T�lT�T���@
�@��LT��@��TT��@��`T��7j�A��xT����R��-Hf��-�T�TV�l.rg���T��fz�T�U�f���T�$U�0U��U��U��U��U�V�DgrTg���T��h�h��U��g��U�Hn�HU�lU�n$n��@U�\U��,��nz�oz�o��dU���xp��xU�\p�U��q���U� q���U�H��r���U��r�U�s���H��U��sz�U��u<x��w���U��wVР:���V��x$V��x��0V�y��<V�$y��HV��V��V��4.�{��hV��V��{��pVРV�|r�>z�}���V��l����V������V�L���-�2.W�$W�<������V������VФ����V�W���.��#����W�����-����-|W��X�(�$����PW��W��W�H�XW��W�\���lW��W�XX�h�*h(���W��W� ���WЄj���P<��W��z@����-�X�l����W�X�ȡ�С���W���XА���.�0X�LX�آX ���8X�(�@XМ�pXЄXф
z����hX��d���|X��Dj|����X�<��X�(����X�x���,�аX� ����X�@=��X�X.�������X�Y���X�m��l��Y��l$Y�l���-PY�d����=�0���8Y�$���DY�����-�$��-�Y�Z�������|Y��Y��Y��e�����Y��Y�p��Y���+�����Y������Y�T�7�Y�ZѰ�<t����Y���:l���Z���7(���Z�@Z��(����,Z�l�4Z����-�Z�\�,,?�+��`Z��+hZ�0���tZ��Z�L����Z�[Ѱ���
�Z�4[�H[��M�h[��[��[��[�\�\���$�ZМ.K�����Z�d�xt����Z�D��Z�P���[���*����[�@[���$[Ј>4��\[�hYd����T[�T�t[�0����M��*�����[�X�B�[���������[���[ф�W�����[����[�����[�$����[��$����[��\��8\�|\�,��L\�p�����(\��c���D\�0�����X\����`\Є�����l\�4.�����\�����\�����\�|����\ѐ����\��\��]�^�0^�x^���]�ls(d5���\�85r�\Ј���]�8]�X]�h]�$�(�6��$]��6r,]�<o���D]��L]�@��<������N��]���p]м]�TR\�&z����]�����]��]�����]��]��]�XZK����]�����������]�����]�0.<��^�<^�X^�P^�t|{�A�}��D^�x��L^���z����d^���l^�h����-�2._��a�x���^��b��b��b��6�Td�td��6��d��6��d��d��e����G��7�0������^�D_�h_��_����^М_� ��_��_��`�Ta��E�T��0_�d��8_�`�4��P_�t_�@��X_���p�8��|_�D���_а���O��_�`&��_��_����(��T�_М`��f�����_�`�`�`� `�p`��8��r���8`�d`�������0`�L`�T`����|.Ps�$s��\`����z���x`�����`а`ь���`� \ ���`�|!�`�a���H!���`��`��`��"���#$�4#���`�L.8&��a�D&a��%�� a�da�la�ta��a��a�,5��P��%��,aИaј&�'��<T��T'�����)��)���a��P��A�4.�@.���a��a��a�b�.���a�bф+���a�$b�Pb����0��.���Иa�/��b��0�8bЈ��1��0b�Hb�Ԃ<2hbЈbь������`b�xb���<X��02���b��2��-�b�7:�b�P2���b�8�D7���b��K$��-�]��-cМc�|^B�^���b�0^���b�h]��c��D�,c�Lc�hc��_j@c��z�_��8c��a*Tb$`b��Tc�lb\cЄd4d��tc��c�Xd��|c��c��b���c�d�d�(d�Hd��dI`e�c�,e���c��@�Te���c�df7pf���c��e���c�lg,yz�g��d��g��d��Ȍ��4d��\$<d��m��-�t(�s��`d��s��-hdАdьv(@v���d��d��v��z��-�\��-�.�d����r*����d�(����d�<����d�T����-e�he�Ԃ�,e�t���e�8e�Te�4O��|*���H�����@e��aHeЌ�����`e�|e�(-�������-�2.�eдeѨ����F��e��������h7��F��e�����I�e�������e�0��-df��f�@���e��f��g��i��j�xl�pm�Hn�\p�`�� q�r��r�ls��s��w���f�$y��*�f����Xf��f��f�x����xf��f���L*�$����f��$�f�p���f�����f�� ��XS�,*z��-g�Tg�+*(+���f��)g��)��g�h3�x3��$g�tg�P��g��2��,gИg��2��Dg���,4��`g�D4��hg�5��g�5��pP��5���"��g�<���5��9�g��7���g�D9�h�Ph��h�8����-�g��hѴ:��:���g��>��>��h��>$h�(�����$h��,h��>��8h��>$DhЀh������`h�l�hh�$?��th��?�h�$��t?���h��B�B���h�LB���h�`B���h�i�Di��A���h�p��A���h�����Li�li�H���B-(i�8i�x�B�� i�T4����\;��B�E`i�lExtE��Xi�|F� G*�4G��ti��i��i�$j�G��-�i�@j�0H*�f�J���i��I���i��I���i�`��i�j��I���iЄ�єJj�Jj�Kz�K��j�0j�L��j��LzO�4O��8j�4TxS��Lj�dS:Tj�HR��`j��j��j�k�Pk��R����-lj�k��V*�jМ��W���j����$]���j�PY���j�(Y�j�8�Z���j��Y7�jфY��k�hY$k�l\�|\��$k�\,kѠZ��8k�LZDkЀkѸ�p]��`k��]hk�\��tk��9��k�x^��a�DB.�a���k��^���kќ^���k��k�P�c�l�c���k�,l�8l��b�k�Ll�X^� ^��l��^��^��l�_�� l��D��c�dlќc��@l�pl��c��c��\l�Hh8Hf��-�l�mѠ�� <���l�D9�l�g���l��g���l��l�f���l�m��h$�h���l��HL�i���l��i�l��{4m�$y��m�Lm��}z@m��}��(m�4~�����*0�����Tm��m��m�L���-\m��m�$U����m���mд����m��m�|�$�m�4�������$�����m������z����m�����mѠ����m�,��n�dn�@���n��n��n��n��n��n�o�������-$nЌo��@���\n��x ���pn�@���xnЄ^�^���n�؋���n�(����Ԓ���n���<�nр�NT����n����nѨ������n�(o�0o�8o����o�Ho�<����L�R��r����@o�\o�do��Rp��zē��lo�����to�Ԓ���o��o�Tp�����_rD����o��o��o���o��o��D#̔�Xdp� p�����o�,p�Hp�`<z4d��p��I\,e��p�lgrX��d���4p�p���<pФ7�����-xp��pѴ�X\���pp��p��p�����*(�*l�L����p�x����p��p� ����p��D|����p�<��p�(����p���<qШ����p�Hq�Pq�Xq������-q�lq�T�*d���4q��HLD�\�#��j�qМ���`q��q��q��z����q�P�ܸ���q���z�q��q�-+�����q������qм����q�l�r�$����q�r�l�����-�q�pr�0�Hr������$r����,r�\r����8r��HR�}��Tr�D�����hr�����r���������<l����r��r������r�����r��r�����-�r�$s���B,������r�����rѠ`7h]��s�����s�����s��Ps����(�30���<s��Ds�����|Y��s������-\sШs���$�����s�(�$�s��j(����s��s��s��s�|���<�r��z��-�s�Tv�L�rHtѰ���
�s�ht��t��t��t�u�,u��u��u��u�5zt���(t�D�0t�P���<t��(����Tt���\tИtєP��O��xt�P����t�\����t����t�|�������t��t��t���z�t�0U�T���t���z��rX�.L��4���u��uд=$���$u.Hu�tu�� ���@u�\u�du�������D����lu��u�$�L�$T���$u.�u��u�|Y(8����u��u��u�H�j����s0�����u��v�v�lL����u��� ���z���v�,v�F��(\���4v�|���<vА���Hv�pv�w�Xw�`w���:�v��v�<<(�4���v��4r�vЈ����v��v��v��v��|�I�������v����vѐ����v�w�$�TTR���w���w�Lw��z���,w����4w����@w��r��r:�w�d��hw����twѼ���w��w�x�x��x��x�X��-�w��x������w�lF��*L����w����w����w��
���w�(z$x�xx�X��\�Lx�lx��(���8x����@x�H��@��Xx�0`x�|��hr��x��x�l\T�����x��*�x�
dh
�j����x��x�����d�y� ���x��y��y�tz��z�h����-�x�p{�x��y��{��~�`������ԁ����(������ ��4���� ��0��$�������py��xy�����y��z�yи���l���y�T�y.z�dz�$u �0���y�<���y� zь���y�,z�Hz�Tz��k�k��z�D|4���4z��<z�xB��p���X��\z�|!�z��"t�"���z��"���z�H!���z��%�z�D{��%*�z��%���z�{�({�4{��%�%���z�<<cpM���z�|M�z�'K(��{��'{�<(��*<�)��<{�L�4�+��P{��+dX{Є+��d{��{��"��"��"�Ī�.�{�lb#@.���{�<24P2���{�|�|�|��|�P}�X}�`}��2����-�{��}��)z(4���{��3
�{��47D|Ш|Ѵ$47��,|.`|�D7��4|�l|��|����$7��X|���jд~(8��t|�88*||дz�8*�|.�8���|�89��|�8}�Ƚ��9���|�9���|� 9���|��|�}��9�J���:��}�(}�:}�8:�d:�p:��0}�H}��:L�:�`<��=��}��}�8	��=��p}��=x}��=���}��}��>��>���}��>���}���>���}��@~�4~�?���}�l~�tL�hK���}��@���}��@��~�$~�,C#HF��D��,~��~�@I��@~�LIH~�$I��T~�I:`~��ѠK��X)��~��~��%���K����-|~�,�M�L���~��%��~��L���~�,��TM���LO���~��Mr�~�|S��TlT����UT��U�� ��&��&��&�,'�4'�XZz�Y��L��]��-|�0^��b�h]��p������`:̲z�����������`����a����̘j�����i���h�������h��-��<���j*ll�k�����k��$��l��0��`%*���dn��H���mT�Ѡm��`��؀���4���m����-l�Є���%�D%����� �<8������7��Ќn�����pnr̀�T�N,p����p�Иo������o����L��p����T���p$��q��p��@���pH��Pr�q��`���q��h�Д���q��t������r���"��gr(s������r�����Dt��s������s��-ȁ��v��@E�<w����-�����-��*̎��������-�����-@�М����)���а��,$��\���d��$8����p���8�0����-�����@�������$��0��|�����0��0W�p��`���I�����������h���%D%����`%����� ����,*��-8$��-G��-L��p�� G�4G��D���l�`���I��Qz4O��h���R$��-���\�����R�����4R���؃�HR�������4��D��L���(�4S��Ѓ�r��T���� ��,���T�иU��U�����U��V�<Wzl�Ѐ��(Y�LZ��b��^��T��p��x���e��7�f���+����Є�܄���Hf����-����шj<�rq������U� q������s�w<�{��$y����|��{�������������D��L�R��- ���ќ�zX���+�����P��\����<�������-d�Ф�������������� ������l�$��-��*��-ԅ�������̅����(�$��-�������?�L�j������������-��8�Ѥ������0��H��8�\��lx��T��X��-���h
���x��.z�+�����h����-���x��������@������6�����6��6�������� �����������Я��2$��-�\��-�.����-�2.X7�0���-���@��@��,�����@��H��؋�(�����������`��������������������@��Ԓ��j����������D��`������8���������������������Bj<����H����0��T�����<�������.�tC��(�����p�����L���T�Ѽ*���l��\t�А����Lm�L<�xy����������$�������Ĉ�<ԈЄ���8�`�p�������!*� ����l��t��� �� ��� ��	4��|���������܉��������4<"*#:����#:�#�����`%:����QkD%�����P(:���(*�(��ȉ��(Љ��(<�),*��-�V�7�.T�И���7�������D9�Ċ�����8����- ����<8���I�|���������h�� ��p���8�<98�8������9���D:��9�����P=4�=x�=��̊��=ԊР?���t?���h���T�(�A��l9�(��li� ���D�<���~E��4��G$��-`�� G44G��X���������I���i��I��t���K��j�L����а��8�jM�����dS�HR�����������R����-ċ��(�TV�����T�єM��Z����LZ��Hf��-8��f��\H�`��x���lz�l��L��xlT��s���H��szl��L���-��<��-����-?.Ȍ�T�Ѵ��X��.\������������Ġ��܌�$�����ܡz@������8��D��l�����H�jT���$��(�,��t�Rx�� ���L������������4���l����t�јaB������������|�N�$��-Ѝ�\�X�Ш���č���<����܍�l���-�А��Խ��(-������$�����D��x������,��T��P�4��d��0��T��d���\��T������p����*x�������������-�����d���������-��t����J.��<������Ԏ�\��d��l��h����K���$��,��L�\�`��h��H������4��T���������)�p�j�(T�j(���������@������+j0��������L�����а��������<��H��T��1�\��d��p��������ܐ����L��������-��Р��t�L��������@[��z����(����0�Ш���,@Ѥ�X����}45.�45.��Ѩ�x�������$����\�R���������<�R�<45.А�����,&�T���Ѩ������������(��8�.�����P�z`��� ����z���4��\���<��l��,�������d���\���x��đ�|������P�ѐ������̑���h��p������A�����:ؑЈ����v���<�����2��� ��(������X��(#�0|S�<z���0���]����8��l�����H��\����P���T|z<��|���(��<�� �����`��������h����-���ē�x�������������������6�|���6���������� �����G��7�0������� ���*���4��Ѓ������L��T��T��x�������p��$�4#�����|!����ф%����)��)������+���"�ؓ����1L2���Kz��-��РK���%��]��-?.D����^f ^�� ��0^��(��t��h]��4���������������D����̔�|^.�^��l��_ l�8_�<`N�`�����U�Ĕ��a������xlb��Ѩbz�b��ؔ���b�����b�����b����8��p��x���e��e��$���e��,��x�x��D���vL���g��X���g��d��<w��-���-�.�d���T����-�����Ԃ�t������Е�ؕ���*�aHe�����$����������`e�(-������F�%�����-(�.��8�Ѡ����F�$'�0��-�����@��H��̘�,��������4��$��X�������,��,��(��$��l�������\�� �Ѽ$���	�������(��h��������З�ؗ��z���O��������Tj�����4�����xj(����<�����D��0��P��L��\����z ��t��<|��`����p�����ȗ�������P��t!����$�"#�"����#����D��� �����������������@��#�� ��P���#��(�М#��8��h$���&��X���&��`�Р&��l�����`%��x���&�(($P($�tz�s�����Խ���,*$��-���2$�2��ܘ��87<8�����7����7����@��H��D9�P��8����-��`���9jd;�?j�Ar�A��X�����������������C{�E��`i��EA4G���i�ș�Й���G��-���D��HH��I�ܙ��I��`��i�Lz�ИL��K������O��`g��O���ШO������O��$��X��4O��4���PQ��P��p��x�����0Q�\QjdS
HR��������̚�����R����-������T��$���T����Vr�W��p��W��Ԛ���|W����<W��LXRX�����^���n�,���e�Hf��-x���Ѡh��T��hH��g��T���g��`��f��l��������ě���܄����i*�j*��МT�lj�����Hn��lU���\s��Л�ls��؛�x�w�����w���y*$y����L���-�8�@�@���4��������М�ܜ�������-<�Д�,Lj����l������t��`������H�$���؋���t!�Ԏ�����������Ĝ����������������-�:������X��-�����-L���є�4l�Ѩ���@��������ĝ���ml���d����z���x�����������D9j��j@�������������\�4����̝�����ԝМ�������$����z�q��Ѭ�$������l�4l���-d��ܞ�Խ#Խ��@�����h��H��$���T������������@R����|���������d������P����O`�#|������D���Ğ�����О���$�\��h7������������l��$#����ğ�����-��؟�T��\s��<��ls��D�Ѐ��,���P��H���`��8���s��x�������������������ȫ4Ы�������$��м�$����П���x�z���\�zh�����(�������8��p�������-��Ġ�<�L������@���H��t���T�����T�`�Ш��,�4���������М�l������|�����(������$��,��X�����ؠ�0B
�@����p�����|���������d�0�LL���а���	4�����������ܡ������(��t��������-@�А���z0������T�z`����������Ѽ���С���z����ȡ�T��А��0���������h�������$�����T�T��|M�����4����<��8���H��<����`���h��|�����1ѐ������آ�(��h��������P��\����������2����������Ģ��7������������<���K������8�� ������H�Ѹ�����@��8_L��T��8\��8z���t�����|��PRX$��-��м���w�أ������z|��ģ�(��̣�
.�<8
����h
����h��-�����x����x����������X��Խ�|��H������� ��4��������Ъ�$�����$!������l�� ��|��������̤���<'��z� $4#���
�ؤ�|!����Ѽ#T'�%�����%����x+�+�����}p��������д2��(��<24��P2��	@�����`N����l�����X#������2����-L��(���3q7���4��47��?.إ���D7������������78$7��Х��7��7��������7�дN(8����88*��8*?.�8��(��p9~T9��@��9��H�� 9��T��89��`�Є�Ѭ:�p:��|��;�(;������:*��м��P;�\;������vz8=��Ȧ�H=��Ц�=��ܦ�`<�Ѥ(�>�����=��?d?�� ��<��,%��@L��|���@��;�$~����F��\���F��d���D��p���K��-�����$L*hK������K����РK�����������L���%����%��L��Чи%�Mj�R�U$D���U����������|W&�W��$���V��,�шU��8��h��|(N�X��T��Y��\���gj�[��t��X[��|���\��'�$'��\\��Ѱ]��-���h]���D��j��h��̨��h��-ب��р�L��������kzl����nB�m�� ���m(��t�Ѡm��4���������m����-D����#jdn��l��pq��јo������o�����8p0@p������p4̩иp:�p��ĩ��q��`�����q��ةШ���q������hr�r�|z�|�����|�� ��L^�{��,���z��-<��d���~z�~��\��\��̎��p���������-x�а�ј�*ؑ*Ī��������K�ȑ��������-��Г*������X���ѐR:��-d��@�����t�������������<�����\��`�������d��p�����ܫ�dSHR��\�������-��а�$����������������-����z�����������(*���ȫ�X��-Ы�P2�����|�����Ȭ��2����-��ج�x��@������������� ��Խ����������� ��4�������H��d��47�D7��d�����7��l�И�z�F(;������:*��м��P;(\;�����h;$XH.?��Ь����I�K������K����-��pp$�m�����m����-��<���r��q��4�����-�d�� �����
T��,��4���O����J����Ԯ�ܮ����0����-d��l��@�����l+����G�t�����<���H�D��`�����������̳�\��O�����М��PjX����d����p�������$d��p�ѰY��Y��D��hY$L����X�����d8�0���,��3�L��|�� ��45.���Ȯф�������B������<��t$��������(��p �4 ����D ����P �����"d�"��4���"$<�є"��H��� ��T��� ��`�����������د���Ч�#�@+�`%���є&r�&�����P(��~$�~��į��(̯ф)<8������7��8���7�����D9�\��8����-��h���8��8��0����T��t?��H���?��P��A�����R$��-���4R
HR�����Hf��-������e�f�����԰����i���+��iȰ�k��:��j�����{���:���$y������}\�}����,��\���(��X�����������-0�И��l�l��آ����d��أz����x����$��� ��� =���������-ȱј�.�����������/.0��$���Ա�<��t��̲�l�����-��@��,%_������Ƚ����Խ��$��0����/.X��h�7��.d���L��/�l����x��.��д��@������������J����D��P������IJ�ЪNP��.���ќ������������N���d���������B��|��� ��D���(������4��`��h��p�����������$�F��I�/.?V��|�.��д��L�8���������x��������$��-�$��-��P��$����������������$����t�����T���<������0��l�8��(���D����*��-��м�є4xt���p��D�x��P������L�����Ѱ������д���DT�(��`�����������а72����ܴ����,@Ѥ���������$��Ѽ�����$�����$�cl���4������<������H���T��sL����l��T���t���*����������|�����1ѐ������ܵ���8��p��|������Ј����v������|�BS���������Иz���$���,��$V,��D��8L�Ѽ��X�����d��Ph����-�2.��h��x�����������l���6����6��6��6������ �����������0��r������$��T����L�� ���������$�����,�����4�Ќ��@��\��� ��!BH!��d�����������|!��l�д��"�l"��"����4#�����ķ�$���0�%��̷������P����%��Է� ���%�'�)�t)��)�����a��P�@���������*�//��H��.��P�ф+��\�����Г�����x��2����И2z��-ܸм��p��ȗ������д2�����<2ĸ�P2��и�hV�����(��P��l��X}��4<�4̘#�6�����6r���7�D7��4��\��d��7��<���7r88I�:B<�hK��t��̹��@��|���@�����-�Թ��@�����ܹ�?�������tL`�C��D���p���HF�G��K$��-0��`��hL*hK�����K���РK��$��H���j|S��@�.ѼU*�U��X���]*��-������^�^�����_�����h]��������J�Ժ�������ta�����`a��ȺјaNlb\c��Ѩb$�b������b�@���b����X��|�����Ի�x���c��b��8��4d��tc�Xd��L��p��`e,e��h���e���єf�pf������g�g������g�����ؑ����������Ļ�T���m��-�����-��*��-0����-$u.���Խ�@����|�������$��0��<�������`�������������P��O���$��������|��\������,����	����� ���O��8�8��D��d���f��s���м��ؼ�S��R��R����4R����������0���3�L��,���$X���*���P��<45.��д}���t�.��|���#�� �������P���#������ќ#�����#������ ��Ƚ�4��@��`��l��t���#$��D��$����$��,��Ȱ��$��F�`%�����<'�T��Xdz�'��L��((��P(�),*z��-����2�2������7����D9�Ⱦ�8����-���h�Ѡ? (|G��о� G*ؾ�4G��������G��-���IzTM�R��-Hf����-$u.d����g��T��g��H�Ь��f��T��̿�����H��h��t������h�h������h������h������i$�i������i��Мjrlj��ؿ�����j����Pk�o�$n����Hn����0�рo�o��(��q���	� q��<����\�r��T���r\��ls���u��|���u�����s������sz�����pv�Tv�����|q�{������,��H���{����$y����h�����|z ��4|�D|�����|#�}L�}��4��`}�<���~|~��T���~��\�Ѐ�� r,��x��������t���������@������������-��Ф���-��P�ш�*������H���\������@��x���������� �����P�����,��,���4��X�є��`���P��Й�����d����l�Ш���j$������4������D�������B������4���ѐ�zL������,����ğ\������(���А���0X�l����Ѥ�BL���,��h��x���4�� ���D��<.���������pn.D���p�����X���x�Ш�ј����������$��-����*������������$l�$��-���$�\�����������X�$��-D��H�*���$���,������8������-���d�Ѩ�$0���d��L���l�и�Ѱ���x�����<��H��|��������0��D�rP������ЪW,=�����<<������������hM��������@�t?�����?����0H.����$��\����,��8NDN��H��TM��P��ls�����h����p�Є�0������T������<'#l������(������4�������������L�����Ա�������T�������������������$���L\���<�����|���D���1ѐ���T���������������������@���������Ф��NШ2�xz<�����P��И2��-8��x��@�����$6�D���������6�|������������$��������������3�P2��0���]����-$u.|��x��|^R�^��\��0^��d��h]��
p���Q��D��������������(��`��`�$`������_�����<`��R�`������`����Є�Da�����a����Ѩa��a����a��0�X��4��Tbz<��`b��H��lbT��Xd؝��b��l�����b�Ч��e����фh��-�m��-��Рm��p��T���-$u.�����H�t���������J�b�$�����45.�����������-�.D�д��������<��h��p���e�x��������$��l��Ї���$��L�3|��������������Ѡ������h7� ��(��H��x��$'����������������������������$�����Խ����t�`|���0��X����8��h�Ѡ�`d^`����`���������*��-$u.������̎��8���J��������x������������l)����-?.��x��T�`������(��p������8�ќ�����D��L��Ⱦ2�E�����0��Г���NTؔ��T�����\�Ј�����h�����T����"�X��������������������������L��|��8���������f�0����-������@���������������+�H��0-�0W����`��x�����������������N`��Lz���X������*�����l�фe\�$������������������ �����89B�"�����#�������� �����P���G�t��|���#��#����&�&��$���%,��D%��8��`%��D��h�є&$�&��`��((�(N����($�(�����,*d��-���H-4�)��������l.*���0�l/�����8��-G��-��4G��`���f:df�����e��f�� ��,��t��������Hf����-,�����m$�l��\�����xld��rz�j�������������\m�����pm���o$n�����Hn������{���:���$y�����̞�(��4~�����}���}��������(���$���4��4���<��D���H����T��\���`����������������-l��l��4�<������������X�����Ф�~МR�z���������,���ф�ԝ�����@������<��l�����.ф
����(��t�0��l�rL���H��x���P���� ���\�����-���̳��������l�$��-���xl�d������`�0������$�������$��-�����������>���\���N������p���0�Ѱ������(��L@t���<��l��t��|��T�D�Ќ�������(�\��l�����������'�H������8����(������Z�б�����
$M�0��<��l@�H�����������������������-��������d��������$�Ш��,@�T������`������X��x�R����l������t�ќ������X�B�����.4�����������T���N�����1�,��T��x���������������Ј�����8��4A���������|�)X����@����H��l���$���d���NX$��-Ы�����^������ �������$��h����-���,��x�����8���b����(���6��6�x������������<��h�������d��|!��%j�+���"��2$��-T�а��7*P2��L��h�����h;*=j=��p��`<x��0B��@������@�����?������dr4d�������Xd�����p���b��������]����-����d�pf���'�����e���фh$��-@���i��h��8��hw���I�t!��vL���v��\�����<w����-h�Ј��Ly��������,y���H���z��-�$��-��������������a3��t������T���-���0�Ѽ�z�$������*���(������-�2.P�Ѡ���P7�`���X�$��-x7�����x����������-��ь�r�������x������0��-h�����@�������$�����(��p��<�������`�������d����T����������(���-Nd-��,��H-r4����@���L�М�����	X�������������(��h��������d*p�����HrT�����������L*����������|�d�������<����L,�$�����8�����H��$�zx����@��(;��T���\���4��t��<|��t*���������Ј!��є!�����$���!����Ј ������ ������ ��
���,��H��l��x��Ѓ����(-����<9�x#�%D%��4��`%��<�а���kxl��X��((`��P(�(4�(������(���D)$L)�����0)���G$��-������G$4G���������\��$I�����H*����I�O*4O�����R$��-H�����4R���HR��<��h��p����������dS�T�tЈ�рV*TV������V*0W{��^�������_�����_�������$���^�����8�ќ^�����\��h���_z@!��`�����`��Pbj�a��0�����=�b��H���bP��td$Hf��-�.��Ќ���ff������������������������Є��� �����9r�g���������g�����Ph\��th��h������i�j�,��h���jrlj��$��<��Pkr_�_��D��x���k��L��k��\��4:�Ta�xlB��Јj*�l������m4\m�����pm��Єn$n��������������Hn�����0���r\p��4�r�����r���smP��8�Ѱt(�t��0���t8��s��D��`��,uz� r�x��h�����y��p��$y��	������P��h�����������������\*�D{������z��Ѹ{���V�������{�������h;4`}p�}
�}����(��0��hH�l~��~r|~��8��`���~��@�Ѐ��TR4`E0�z<���p���x�ф������t|k�(������(���0�����-���̑�������������@����������-��4��\�����H��T��p���������zx���4��4�<������zL���\��,�d��T�:���d���|��,���� jh���������\��ؠM�������������Ġ�����$�������K������L���p��hI�P��x�����x�� ���$��@=�X.�49�����<�D*|���X��<�`��(���l�������N�$��-����Ѩ������d=������:�������܍�\�*����j������������М��������l���-�$��-H���<t���(��T�0������<������-t��l��L�:��Ѱ���h��|M����L@��������(��D�RP����������@�,@�0������������T������`��,�z��4̳d4����������8�����������@������8��$��\���<��|���P���1ѐ���\������N�����������|N�����������������9j�������r���d���������<�X<��-Tj �����t��P"�h����-��x����|�����8��d��p���6��������6���������<��H��l����|!j�2$��-���|��P2��hV������������Ȭ�8��X���4D7��������7�����8-(8�����88*���89z��9� 9�����:.���<��$��`<,��=Y�=��D���=L���D��X���@��d��?��p�����Ь�����b*H�����G��ѸIa�K$��-���\���L$�K������������r@������l������<P����hP����|Uzp�А�шU��$������������U��4�����U��L��`UglU��h����� Vq� �����������:�ѐVzPX-X�����@X������'7�X�����Y�����Zz�мY�����(��0��Zj�Y�����"\XZj�]��-H���b���'�d�\���N�h��-�m��-��Фp��m�����<w$��-����v���(��z$��-����~*�~�����T����-?.����-�2.����Ѩ����F�%��e���$�I����P7�h7�(���F��F����*��-��r��-`��̐����X�����-���`������p���|�М������0��-��@�����L�����������������T��X�������$����T����������А�Ѭ������ �����)j�)��(��t����������,*����-0��P���-�d-��`��H-rh���.���=�����.����Ш.�����l.������h���/Il/�����d�\�r$1�����01�����|0�������00���,�р1< 2*�1��$��<��|28�2t���2��D��,�����h3��DB.���x3��d���3(H3���������3(<7A8��-���`�Ѹ=��=���������=����7�������P��?��>������>$��<��Hh�T?����?$��$?��0��`B�����A��H��A��T�����������C��x�����Cx��88��C������C�����@D�G��-���\��HH(4G�������DBV�I����.�I�����$��0���I����ЌJV�J�.�J�.\Q�Q��<��O��D��4O��P�����R��h��4Rp��HR��|��������H���������R����-��М��L@$T�����4T�����xS�����dS:��МT?.$��H��@�����0���TV����lW{|W��0��T��<W8��(�8<.�P���\���d��PY��p��(Y|��l�8\�����\�����ѠZ��8�����LZ�����l\�|\�������� ��8]g]������\��\����<���6��]��(���]0�р��^��H�������������_��P��_��l��������^��x��L�ќ^�����$��d�����h_�����_��_������_����%�,a�����pP�Ta�����(d#�c�����b�����He��0��Te8��x��e��D��<��d�T������$��p���e���И���e�����Hf��-���L���f$f�����������,��8���=��g������g�����Є��������m���\m����,�pm���s$�{h��$y��@���������}z�}��`��j|~��t���~��|��ԁ*4������X�����|�$�����Ѥ������L���-���T�Y\������x+.��������Р�����h��,����@���$��������,��d��������������-0������ ���pn�������@���p�����ls�������������D9z�7x������t���j��������`������H�$��д\��\�������ċ����؋�� ��D�ф^��^��<��x�$8���P��(�X��l�(���p����x��������Ѩ�j�������������ܐ-L��P����1�������Ѡ��������#D����������Ԓ����4��ؕ����� �����(��L��`e����D����B��-x�М�Ѵ�X���\���l��������������L�������4��X�*(�*��Ф���������}��-����:�����������-4�д��ܮ�d����������Ш���(�����L��������`��p��@���X��������l��������\�*t��(������И�z�qМ�����������������0���(��8�������l�*�����$�����\��h��������l�����-�м��ؾ��L��*8�����D���P��0��������p���x������������P��D���������������r������<|���������̅�,��8��t������-�������L��������d ��H�������@���H�Ќ������T�������$d�и��P��0������l��D������\�����j������������������������ �����x�*���|z{��������<�\,��P���$'�����|Y���|��H����������-8�а�ф�<����h��p�p��h�t������T�����(�������s�����������L���t����\�������$�����-��`�Ѱ����M�8��k���$���,�ш����������D��t�ѐ���P�������������v�X(������������$�N�������������Ѽ�����,�����X��-���Ѱ �d�������DB.D��X����P��\��(����t��`�����<��jd��0z�*��.���|��d��������j�����������8
����4�h
�������.����$��`�����\@���������м����<���Mu���$��H���$,��|Sz�� ��P��������T�����h����-\����x��|��������8�����p���P������ ��4�������X����������.������$��ЄrT0��H���8���(������`Ќ��<��|!��ш#�x#��`��`#h��4#��t���%���*��)������)����Є)�����T-z`-����D,$�ф+������\���"��"��"�Ȧ�@.�����(��.����P���.�<���.f�.��4��/�/��H��l0#�2N�2��d����<2l��P2��|��������|�������<���2����-��А��<�<�3����)((4����47��,|.0��D7����P��l|�\�����7����м��`���7��(��\�����<������D���7�J.(8��t|��a�������88*h�Р��`�p8<�8���A��8*,|.�ќ8�������Իȶ����89r��`��J��9�����9����� 9����4��<�E:�� ��:(�������@���:H��p:��T��(;��$#�����:*l��,���������������(�<�;�����<��Р;�����h;����H��������l���P<�����T�d�����=�����=��$���=0��T��x�>��L��d���z@L�?��l�����?$t��?����������$��X��d������@�����@z�@����EE�����D�����D�����g�H����G��hH��H��H��,���H��4�ДH��@���HL��I�,�(D|��l��T|��t�ЬI������I����K��-�����L:��,�ѠK�������(�X�������L���%��~��%�T��G�+8N��M����Mr�@�<O�LO��8�P�0P�hP��И��Q��d�R��l�R��x��Q��lU���^���|U����шU����<��&�x��U�������U�������0�������@����4���hV��$�\4��V��,�LьW��\��\�X�X��da����P�Y��dИ�`��Y�`Y��������0�Y����Z���мY����<�����Z�����Z���T]*D[���X[���$��[������6�]��-L�x�h]�����d*4d��X�Xd��`��b��l���X)��e�������e���� f�(B8Li�����h����h������<��h��-�ФѨi��j8�j����i������j��(��j��0�l�T��D���L�k��T� k��`�|�|k8�lj�k�����k����l����`���nj�m�������8��m����-��L�Lp����0��o����H�z����pp��1�q��$��p,��q�q��D���vz�s��\��s��-d�xx�����v���v������<w����-���xy*�Јyz`y�������y�`z�y����|SjLz����7��{����{*$�{��0�x����z��-<�����z�|��d��|��l������������м�����~���0�����������Ȍ�������-�Ѱ�$��-���-�Lќ����)����)�H"zؔ��,����4����@��м������h�����p��������*�4�8��0����-��d�@����t��`�����+�l�<������
�`���(�d��
�h
�h��=B|F\T���lsz��� �\(�89�"��@�#��Hа�� ��T��(�4�H�T��@�#����������#�����ќ#�����$�$������lFA���&�����&���Р&����`%���ѨazX'���<'�((B�(����P(��<��(d��8��-Hf��-�и	�`jqlj�������j����f������8	�P	�p	�$�j|W����<W��Hn*��n�$n����	�o�Աj���	�����	�q�� 	� q��,	�pr�����r��D	�xF�w��\	��wd	Дz�z��|	�tz�	��x��$���	�y���	�$y���	��	�(
�T��{��P�Ȭ��{���	��	��}����
�
��H$l~q $,��
��~��
�8�D���4
����<
����H
�H�T
�\���`
��
��
��
�������-l
�ј8��W�$��
��$����
�,��
ѐ������
�$����
�K2(����
�x����
� ��������-l�����-�2.X�|�$uz���@.x�$���H����0�ȽԽ��p�h�45.��d���������0�������8��$s����t�-�А�\\s�������������T;�@������(������P��J.@Ј��� K�(K�J�����P�������XЬ�����l�������b~������������*DB.��l)�������x��x������������$��-
�,
�����|Y�$
�T��(���`?�����
$M�|
��
� �t����M����8�������-8
�`Ѩ�$�Д
ш�{`����
��������
���:�
Ј����
��
������
Ј�Ѵ[#����D���
�����
�\<������������0�L���Lm����p���<�����D�0���P��������T���\��� �����,����Є�R��*������X�B�Ѵ�������$����Ь��d�����t�$��,��������и������$���\���x����|���@��1ѐ���P�������$�\�8��� *��p�и�����������d������Ȣ������������Ш2��p}jl����x����0�<���PИ}��D^�@��}j�B ���H��P�X��-���
8
��x�h
���м����h����-�2.@��x��������d�����6����6��6��� ����������h�������T�\�d�����T��Д� ��0���p��$�rht��l�zt�h������|!�� Ѱ!BH!��������������!"<�"B,�B��qܞ������B$�4#������%R�%��0��%��8��%��D����	��������%��P��Ѩ&r�&�����&����T'�А�D'�����'�T)�<:�*�����*�Є)�����+���5����X���2��-@�@3��2�� �<2(�P2��4�h�X}�$���6��T��6r\Ѐ�X.��6��x��K$��-�����L.�K�������Q��Q����hP�����X[��U����0^�h]����0��]����-��x�L_.$_���8_$��dr�d��<��d��D�4d��P�Xd��\�p���b��h�����m:��-<w���-����-�2.�Ѡ���h7�������������Ȍ�������-�Ѱ�X��-0����-$u.�а�@�����������T�� �!�|!�`��T$��$��$��$��%�,+��� Є+���h��2��K��]��h��m��s�<w��z�����T����������������������d������@������D�x���\������,�00d$p��$�l��$?��8��>$@ѐ��L����X�����<����p�|z@*����������8Ѡ���������b��b���������Ѱ�����<��Ѽ��Ȍ������0��(�L��4�p�4��D���P��XД��d�*��|��������X��0�����D�����0.������ ����x�B�����<����<�H��� �p��,����8�lѤ��
��$T�\��`�����"�"��������#������� ����H�l�������89��=<�#М#�����@��#�����,C4t%<|%���8% �D%��,���`%��8а���n�@(��X�P(��`Ш�����x����Д)���������,*$��-�М.z�.������l.����)����8��.�P�zl/��<���� 2B�1��$�00,Ѭ(<8��D�����7LИ��7��\����<�8����-l�t�8������8%2D%����`+����pmN�9�������9������s�@;��:����<<$�0��<� <���,=������?h�,�r0���H�T���P�t?��\��A��li�Ĵ�G$��-����4G���<���X���I$�R$��-��H�(S4S����4R��HR����h��������<�lW*|W���<W��\�\��(�LZ0ќ^��h��Hf����-$u.�Ќ��h�h��l��g��t�f������<�x�����$�D������x��Kz�������i���рi������i��$j*8kp0��Dk���Pk�P�lj�� ��j��0��\��k��H��l*�l��\������xldдm4D��\m����pm�д��$n�����������Hn�����Ԏ����������o�o��(��t:�������`�X��rq��0� q��8�\Ѹ6�lq��T�\������h���$pиr��|����r��r�м�����������\s����ls����������s�����u�s����$��sz�4��uHvrTv��,�L�T�\��r�pr���x��d��wl�y<$y������h��� �� �� ��7D|����|�и{���������{���� Ѵ|j�?�����?$���}�� �< �P �~�~��$ �H ��}, АC�l~
`zBh��X ��g��` ��b��l �`x �`z�y��� ����� ��(��(��� ��(� А���?.!Ф���� ��8�L���-� �<N����!�H���z��-0!�@�r@���(!����H�B\���@!��!��!��!�"�H"�l"��"��"��"��"�������-H!�4#�̘<IѰ>4����!�,����!��H3�����!��!��!����!�ܙ��j�Й��!��4̚g����"�����"�0"ѐnr���("�x�����4�<"м��4��<���X"�X���`"��*���x"�,��"������(��"м"��4؟���"�$�z�"Ѵ�BĠ���"�ܡr@����"�l����"�#�آ*����#�$#�(�R�< ���	,#�`#�����#��#�$�$�$�H$�x�x#а#ь��L���p#��#���;NL����#�<��#�(����#����̨$ب���#���#��#�����X�D������X����#�ԽH�0�:���$����$$�Ȍ��0$���<$��$��-p$Ѐ$��z����h$����������l���-�$�����������r���$��-�$�������$��-�$�,�p�����$����$������$�(%����x%� ������%�p�%�@%�������8%���4����L%�����T%�l���`%�T�l%������-$u.�%Є)��������%�����Ф%�0����%�L����%и�Ѱ���
�%�|M�D&��&�'�T'��'��'�<(�|(��(�)�T)��M�,M��&�TM�� &�����,&���8&�t&�P�D���T&�P���\&�\���h&�����@�����&��&������&��&ш�R�&���DYz|����&�<������&����&ќ���$@��&�����������d���'�0�4��@���$'�$��,'�0���8'�d'�T���D'�l'ф�r���X���'�D�L���|'�4���'ѐ�������'�X�B�'р��t����'���*�'�4����'���'�(ф�������'�D��P���(�H(���(����$(�$���0(�Խ��1�����P(����X(�����d(��p(���h����(��(�|Y��(�8����(��(�T����(Ш�����l����(����(��,����(�8��(����)��)�d��t���()�,���0)����<)��H)�,��\������h��|���d)��1ѐ���	t)��)�\*��*��*��*��*� +�(����)�*ш����)�*�,A��������)� *�\�j�����)���������)��)XH*����*�4*�<*����J)������D*�l*���L*Ѐ*ѐB����]����t*�8�\�����h���*�����*�����*���*�,������A�x*<���*�+�P�*�8~d�)��+�+�X$��-X+�X��\��(��<+�l+Ѽ��H+���|��d+�h$��-x��x+��+�D,�.��/��/�l0��0��0��6����0��1��1��1�2�T���2d��-�+�$,�P2��L��Ȭ��+��=�A:8,��@��,��@��,�?��,�`A�LA��0,��K$��-�,�`-�(\L��X,�hL`,д,�hK��l,��K��|,РK���,��,�����,��������,��L$�,��L���%��,��=$HSjXS���,�|S���,�(-�DT#LT��-�\T��-�lT��-�|Ux-ЈU��4-��-��&��U��@-��U��T-��-��-��-�lU���3��V�[�[���-��-�X[���-�,yf�g���-��g���-��\��'��\\�-�@]#H]���-�$]���-Ѱ]����-$u.@.�/��) ^�� .�0^��(.�h]��4.�0��.��.��.��.�� ����`.��h.Ѽ_��t.��_���.�`�-�����.������.Д`���.��`�.И��Da���.�a���.�`a�4d��tc�0/�Xd���.�T/��b��/�����/��/��dBTe�� ��`e��8/�`/�,e��D/�pe���HgXg��l/��/�lg��t/Фp�g�����g���/Єh$��-�/�h�����mz��-�/�`0�pnL�m���/�0�0�L0� o�����n0Ьo,0�px�o��$0��p9�p��80��p@0��q�q��X0��s$��-�0�Dtz�s��|0�<w$��-�0��v���(��0��y$�z$��-�0����~4{���0�T���-$u.1Є1�d��t����0�01�<1�X1�b�dSq���1����$1Ќ���L܃��D1����L1д�$l���d1�����l1����x1��1���X����-�2.�1��FѨ����F�%��e�� ����1����-�1а�*��-$u.2�x�����2������-���02ќ����(2�0��-�2Ќ3�@��<2��3��4��4��4�85��6��6��6�`��7�89��:�h;�`<��=���P2�?Ѽd������2��2�3�@3�`3���������2�$���2�3���2�0L<�AD���3�3М��(3� ��43�h0���L3��T3�T$�� ��l3�� ��t3�� ���3��3�(��3�x��TR�D%���3�`%���3�T+��k$l���3�((�3�,*
��-(4�d4�@,M<4��+��4��+4�P4��)��4��,�X,��44��,<�,��H4�06��2����\4�|4��4��6\�7�8r��-�4�ls��9���4��9���4��7���4�G��-�4�4G���l�X��p��R
��-5�<��4RzHR��5� 5�(5�05��Tz<W�LZzHfr��-d5�6рi����iL5�f��X5�|5��5��5�Hnq�����	� q���5Ь5�`q#lq���5��5��qW�r�r���5��r�5��fLz���5��y�5��x���5�y��6�$y��6�@6�|z�{��,6��{��46�`6��}zl6Є6��}��P6�~��$ �|6��Au4~���H��L�r��-����-��r��-�6�6�X��\����6��6����@���p�D��l����6� ���X.������-�.D7М8�Ԯ~d���7�x7�����$7Ќ7Ѩ���47�����7��7�8�88��\p��d7�ܮ�l7�((<l����7��7���-p�]����7�$����7�0����7�D����7��*�7ф�j8�@����7�������8�̳#���P8Ш���8�p8�\�*(8Є8�|Yi8���H8�u����\8���d8�|�~����|8���*�8М����8����8������8��8�d�l����8������8Д�8�����8�l�*�8�ȶ�����/.T9Ѐ9�$���9��9��9�:�l�����- 9�p:Ѭ�����L9�h9�p9� ��d�t�8Խ��x9�T��d����9�8,�J��9�0����9��9Ѩ�qh�������9��9� �N��.�9�P����B�P��.:�D:ш���l��8:������$:��,:�d�����b8x���P:�D���X:�����d:��:�h���:��:�����������:��:��:����:�J�b8��������:���*�:���(��*��-(;�\;��������;�4;�H���;����;�0���b�����<;�����D;�����P;����-�;�P<�X�<h���|;����;�;������;��;�<�<�,�������;������<�����;�p��;�p�8����;���;�T�j$<���t���<�\�8H���0<�8�8<�(���D<��������-�<�=�L�����Ѱ���t<��<��<�1��<��@���$�<�T�*`����<���������$����<�H"�H!���<�T��<�\����<�|���=��1ѐ���=�H=�p=��N��=������v�������8=�`=ьJ\����@��X=�����Ш2�<���*�P�=�X��-�=��>р�����=�����=м���=��=�P>��>��>�������>����l�=�>�P	��	��l4�,>�4>�<>�ԁ<T� ��l
��$h��
��D>Ѐ>��
��
��`>���h>���t>�
dt8
���>��>�h
���>��>�����`���>��F����>�h��-�2.p?�x���>��?��@�G�XH�hH��H�I��I�����I��I��J��J��J�K�P������L?���T?Ѐ?� ��`?��?����4���?�H����\H��?�T���?�<���yИ2$��-�?И@�P2��	и����@� @�(@�0@�8@��@��+��?��4��@��4r@Ь6�7489*h;$L@��;��;��D@��<�=��X@�=��`@�=��l@�`<x@Ѱ@:?���@��@�I��K����-�2.�@��DьK��45.�K���@�A�A��A�0B�C�,C��C��C��C��D����2��L��A.LAАAѴ:�,M��,A�M��4A��L��@A�`A�|A��=7$��@M��hA�TM��pA�xT��M���A��ML�A.�A�AѴ�`���M���A��A��A��N�ls��t�.�~����.B�LO���A� B�(B��~�|~��B�0PB�(NhP�HB��B�4��<P��@B�`B�|B��B��P� Q�(Q��hB�4QpB�lQ�HQ���B�\Q���BдB�|Q	�Q���B��B�,�
	|���Q���B��B�C�t|u<����B�H��BЈ���Q��R��d�<C�DC�TR$C�pC�0����\C�d��,�����TC�����R��hC�p���R�S#|S��45.�C�PD�HS�XS��	�C��C��C��C�D�D�D�D� D�|M����C�����a��S����X�	�S�T�8T�DTLT��(D�lD�\T��0D�|D�lT��@D��D��D��D��D��TA�T#	�T��tD��T��T�p���U�(�/	�U���D.E�F��U��
�D�(F�HF�\F�dF����lF��F��F������V�0E�|U���DшU��E�PE�xE��E��8	(WD	hV��8E�T4��V��@E�PXD	X��\E��E��E�@X��dEРE�l"�$CP	�LP	�X���E��X��̷��E�da�Y���E��E�|M\	`Y��@���Y(�Y���E�Z���E�FѼY���E�Hh4Z��F��Zf	�e��[��0F�TF�X[��8F����6#\\�h\L�FЈ���6��xF��\��F.�F��n	����\���F�����F��F�`�w	�����]���F�$]���F�G�X��H]���F��]��-,G�H�0^*\G�h]�� G��Q��G��G��G����G��G��) ^��TG�lG�X^R��$_��tG�8_|G��gzP`���G�<`�G�`*������G��a�G�l:`b���G�lb�G�4d��0/�4H�Xd���G�p���b��H��'�<H�d��+<g��ix�h��DH��h��-LH��m��-�H��nx�m��xH��sF�s���H��H��H��H��s��-�H�|Y�	\s���H�ls���H�pt���H��t���H�0u�v�<w:��-$IМ���v�L���v��I�LI�pI�@E��I��x��d�tx@I�,��@y��XI��I�,y`IЈIј-8Ly������y(�z��-�I�{��+��
��-�.�I�����;��I�(����I�<����I��BT����-J�TJ�t����6�Е���<J�($����(J���0JЌ�*�J����HJ�47����`J�����hJ�l���tJ�����-�2.�eФJѠ�����h7��F��F���Ȍ��p7��J����-�J�|�\��r��-K�x�����J����-(K�LK������� K� ����]����4K����@K��d�М�����XK�$L�0L���hL�tL��L�0����-hKШL�@���K����L��G��+��M�0-��H�hP�`���Q�TR��R�S�|S�U����K��U�B�<���K�H��L�T��L����L�`��P�\��<L�0*DL���PL��\LЄztz���|L���L�#�@+�� ���L�`+��L���8����-�2.�LРMѰ7��7���L�M�8M�TM��:��9���L����9��M�,M�@;��:��$M�<<�t?�����dM�lM��?��@M�|M�T����	A��@��tM��M�����A��Aj�A���M��M����xT�����D�Hfr��-�M�LOрi��,��i�M�f���M�N����8N��N��N��N��N��j�p2q�� N��	� q��(N�PNѸ6�lq��HN���$d���\N�rdN��q��pN�r��|NМ��	�r���N��r�NФt.�s���N��sz�N��N�Tv��T�xd�w���N��w�NЄy��y��O��xO��x��O�tO��O��O�y��$OИO�$y��<O��O�0P�Lz��`O��yhO�!�	T$.�"2p{���O�4|�D|���O��O��O�|�O�PѸ{���O��{���O�P��7j�|#�|��|���O��}����4�zl���P�����$P�\���	���|P��r��P��P��P�Q�4Q�\Q�������-<PРQ�̘$Iи��Ĝ���P�М�P�<����P�X����P���P�x� ����P�,�\�*h����P�T����P�d����P�,�Q�ğ��� Q�(�(Q�$�����@Q�lQ�tQ�$���HQЄQ��08�~$�Ġ��|Q�x���P�а#� ����Q��Q��Q��4����Q���Q�X���������-R���,��d����Q�,R������QШ���R�HR����ܮ��~����4R���<R�l�$��-�RДR�x�*����hR�P�pR�$���|R������������-�R�S�\�*�R�l����R������R�����R�<�������R���d�����R��$��-�,��,��S��+ S�0���,S��S�L���8SРSѰ���HS�|M��S�1��S�T�8T�������-XS�lTѨ�r,��	P����S����Sь�4�����S���*�����S���S��������S���S�T�8�\���T���\8���$T�T���,TЄ�\���DT��T�|���LTДTѐ���\T��T��T��L�� ��+�	����T��1��T��0��r�����T��������T��T�X�r�����T�����N�U����TАkX��-@U�d�� U����(UѼ��4U���	p��LU��TU����`U� V���lU� ��|U�(V�0V�8V�@V��V�@X��X��X�Y��Y�h����-(�.�UмY�x���U��Z�[�X[��6��6�\\��6�����6���h\��\��\��\�$]����~�#���#�#TV�<
���LV��e���`V��V��V� W�(W� i�HW�pW�T��hVЌW�00�g���V��fz�V��V�06�	Tg���V��V�<7��g�g���V�����V�WѐE�t��W����<�n����0W�TW����8W���x$�w��\W�xdW���	�W��Wь��|W��W��W�L;�X�T;��R����W�<  ���W��&�����W�h����Wф�&`��~���W�� X�H!��d��PX�lX�$C�xX��X��X�|!��XШXѰ!��!#�!��XX��!`XФ"�"�	�"���X��"z�X�"4#��DC��X��L��#B�$#�$#��8&���X�D&�X��%���X�`��4Y�8K�TY��%���X�`YѠ��x��� Y��'B(Y�<)�H)��@Y�T)HYЄ)�����Y��Y��Y�@K����)��|Y��)���Y�\*Bp�	,+#x+#�+���Y�Z�@Z�HZ�PZ��"�XZ�4��4.�@.���Y�Z�Z�$Z�.���Y�4Zь.��.��a����/��,Z��/#�6#�N2��$u.tZФZш������lZ��Z�DQ�8��J��Z�Г-$�N02��tQ��L��Z����2���-�Z�P2�������[���[��wd5���Z�85r�ZИ=�K$��-�Ј_z�_��$[�8_,[�h]��8[��[��[��]����-D[��[�d����J�p���l[Д`��x[��`�[Шbf�b���[�lb�[��dr4d���[�Xd���[�p���b���[�\�0\��L�g$�f���[�g\��z�0h��\�@\�Hh \�P\ѐ��`�	����H\��sd��-T���-�6�|\����x1���\��-�.�\��\Ѩ���h��p���\�Ї*��������\�`������$��-��*��-�\�ؑ����\�������]�8��8]������-]�H]ш�������@]�0��-�]��]�@��T]�0^�_�8_��_�<`��`��`����`��a�`a��a��a��a�lb���h]��b�Hj���]� ���]�����]�#z� ���]�r��+��^��+^��)��^�X^�,*����- ^Ќ^�1|0��D^�00L^�p^� 2:�1��h^��2�^и^��2��|^��^�h3�x3���^��5+�5���^��(7���^��6��^��9��$u.�7���^�D9�_�Ⱦ�8����-�^�h�Ѥ=*4G���<���L_�G��-$_И_�Lz\_Ѐ���K��j�l_�,Lz�O�O��t_�O��|_Ь_�4O���_�\QQ���_�Ȧ�HR�����_��R����-�_�$`�dS��^���_�_���_�_���_����^��`М^��`�4`��b�Hf��-P`�l`�f��������{|`�$y��``�Lm��{��P��`��|�����X@�L���-�`�@���(!��`�������-�`���`�L�B�����`��������`��Pq�$a������-�`�Da�\�r0aШ���l��l�����<a�$����q�h��l�����-Pa�ta�����hr��a��������ќ���-�$������-\s��a����(����a�����-�a�8b�L�*�����a�b�$b��0b���*$�A0���b�T���b��*����<�Lb����X(z�bѼ��Tb�X��-`bдbѸr|���b�p�@���b�����bм���b� ����<c�H"��c��c�h����-�b��c�x���b�Xd��e��f�g�lg�Խ��g�Hh������ ��4���\���0��$��Thc�x�.����Hc�h���Pcь��\c����p$��tc�T$$|cЄ%�cИ&��c��%���c��&$�&���c�T-*`-���c�D,$�cф+���c�d��"��"��"�Ȧ�.��2��2��d�<2d�P2��(d�|��d��d�e�Ȭ��+��2����-4d�,e�D7��`J����d��d�7��ldмd��7B(8�����88*�dА8I�8���d��d��8*$u.89*eФ8�T9���d�9���d� 9���d�e��9��:r�?.?��$e�`e��e��e��e��e��A��@��Le��@��Te�pe��D��,�����XHR�H�Iq�J\�e��F��J���e��K��X)��e��%�f� f��K����-�e�pf�LO��8��Mr�e���z�R��f�TR$f�|SLf�{���,f��4f�XS��@f��U���&��U��Xf��U��df��&��&��f��f�4'�����\\�&Фf��\��'����\I�]��-�f��f�h]��T��Xd*�b���f��i�	�h���f�g��h��-�f����j(g�����f�nr�m��4g��m<gЈgѠm��Hg��g��g��m����-Xg�`0�`%*dn���g�pnL�gР?�	�n���g��o�g�Lp���r��x���_�tx�g��v���g�<w����-�g�h�@���y��h� h�����z�|�{��(h�\h�dh�|h��z��-0h���T|��|zph��|���q��~�h�l�����R�i�8��,i�0����-�h�Li�@���h�l+�$���G��+��i�<������i�`���I��j����S��j�����`��L@i�p��i���� i�����8i�� ��`+�di�(B�li�P(*0)��h�h��ti��g��|i�f���i����i�Hf����-�i��i��s
�{���:�$y���i������-jаj��X�����i�H��i�\���j�<j�Hj�dj��j��<������<���0j�$�*ܜz<���Pj�X���Xj�������pj��xj�����j�$����jЈ�N ����j�$���d�)��l�����-�j�L����SѰ����j����������-�j� k�|���D��Tkѐ���k�pk�|k������4k��$<k����Hk��1������@�����dkФ�N�k��BXS���k�|S���k�����k�����^�Ll����k�\l� ���k�ll��l��l�h����-�k��l�x���k��l��l�0m��6����Խ��6��������� ��|m��������d��@����Tl��?�T��T�Єl��*���|l�|!�l�H!���-�����%�lм(��%���l��+���"��2.��-�K$��-�lРK�����m�\T�lT��m�|S��m�h]���{��]����-$m�dm�fd�e��Dm��e��Lm��b��Xm�tm�g�����-�2.0��-�m�dn�@���m�l+�pn��G��n��n�<��<-��n�`��po��o�pp��p��p��q����m��q�����2�n��*�0n�<n��*�Lp��n����$n��
�0�"��Dn�� ��Ln�� ��Xn�8r��-�nЬn��9(�7���n��n�<<(�E��A���n�̹��R<��-Hf��-�n��saf���n�����- o�doш����o�H�o�\���o�4o�Po�4���
���<o�X���Do�x� ���\o����-����,�����|o�o�$����o��o�p�l�����-�o�Lp�Խ���|��o�$�4���f`d����o�0����o�p�������p��9�P�?.,p�@pш���T��T�(d���8p�����hr�����x����\p�����-dp��$��-�p���Y(����p�����-�p�q��#�����p��p���p�l\����p�q�P�����p������pѐ����1�@q�Tq�pq�|q������v�Lq�����0q��~�����\q����dq�X��-����(r����q�8r� ���q�Pr�hr��r�h����-�q�r�x���q��r��b��r����hs�Խ�ts��6������ ���s��������d��D������0r����T4�T��Dr�H!�����|!��\rЀr�`#�4#��xr��%��pP��r��%���r�|(��r�����1��+���"��"��2��-�]����-$u.�r�(s�8_rh]���r�s�$����<`��brLs��b��s�Xs�F�`s�t�����b��Ds�Xd����m��-<w��-����`������-�s��s�@v�0��-?.t�$t�@���s�l+�Dt��G��+��t�<���H�0u�`���u�������S�v��A����!��L�P(�� ��t��9z�7��0t�8����-8t�Xt�A�������Hnf��ht��t��t��t�Hf����-pt��t� qwRTv���t��sz�tѬwdWи{���`��{���t�$y���t��t��~$u�`+c�L��u�X)u�|~��u�����-Puиu�,�Xlu�\���Du��u��u���BL���du��z@���xu�l����uМ�L����
�x����u� ����u�@=��u��$����u���u����-L�r�����u�$v�������-v�4vѨ�#�������,v�x��x+�����b��v��������6�����d�������v�4������v���d���]���-T����-�vм�*�v�t����v�(B�����v������-0��-hw�x�@���v�l+�hx�����+�tx�y����,y�`���I�xy��y�S��y�������v�y�����*�0��Lw����\w��w��hj�g��xw��g���wиwѐ���w�\�w��w��h������z����w��#��������#���w� xќ#���w�#���w�� ��x�Dx��$��$��,���%2D%��0x�`%��8x�\x��&��&��Tx�8��-Hf��-�x��x��g��H��f���x�԰�8��d{
�x�p{���x�y���x�$y���x��x�,$,���x��{�����{���x�L�*��- y��:����y�����-@y�Ly�\����-� ���49��x$���Xy��y��y��y�l�����-`y�0�z�y�̿Nd����y����j�2.P�x��R��-�y�����8���y��z�����-x��@�����`z�|z�����z�Խ��6�������� ���z�����������Kj�K��Dz�tz��C��K����-Lz�p���ML�]��-�z�a�h]���z��m*��-����-�2.���F�{Ш~�����2�H{��*��{�0����-�zа{�@���z�l+��{�������|�<�����T|�`��t|��|�~�8~�����~��`�Є*���z����X{���$`{�p��l{����x{Є"�"���{�� ���{�� ���{��{��#��@+�8*��-�{��9��\L��9���{��7���{�f��԰��t�܄�V�Hf����-|�8|��{$y��0|�\���|P����������-D|�h|� ����u����-�|�(�������|�\�*�|Ш����|�d���<���|�0����|�$����|��|�p}�l�����-�|И}�t�-��2.}�4}�����$}��J�,�7D��P���,}�\}�h}��Mg0.��H}�<.P}���P��2.�}Ј��� K��}�<������������}������7����}��K��}����}��}ш��x������}�~������7��$��-$~��Y0~.����~��
�$��-T~�x~��&
����L~�H�����8�`~�(���l~�X���-�~Ѥ�����~�x��x+�����~� ��6�Td��6��6�������� ��`��������d���K����-�����U��(F�h]���G��]����-�T�`e,e��4�Xd��<��b��H���\��-�.xЈѨ���h��p������x��$'�����и��@�����t�������������<��������`��0�������S��������fz�T�f����԰���,��Hf����-��T��\p@�Ѵp�p��8���{$y��L��h��p�����(���أ$����|����$���\������������-���x��x+�D��@�����������Խ����������� ��|m��������d��<����f����������Hf����-�Ф��@�����t�����������(��<�����\��`��0����d�����������j*���lj�����yL$y������́��nzl������������а���-x��@��D��@�����������Խ����������� ��4�����؁�0������:��-H�и�B����@��t����,*����-I�@�����d�����Ԃ���H��$��������`����D���a����������4G��X������X��G��-����H4�I*HR�����R����-��4��_����@���^����8�ќ^��$��TajHf:��-�z ���T��@���\��@���h������M����������-t��؋*��$Ф�:��-ȃд��\�������$����ԃ���8�������-܃Ѐ$��*��̲(�����DT����$��\�*,��l�����-�R��t����T������\��l���h��T�t��������������-���h
�������X��-������� ��̄���X��h����-؄И��x���������@��������Խ����`������� ��4�������̥�d�����Lm�T��L��<2�P2��d��������Ȭ�X}��+��2����-l����854D7����7�����̅�8r�8��ą�hH?��؅��@������L���%��L����РK�����K����-��0���U��X������]:��-�|${��L���z��-T��|��`��~��t����Р��,*:��-���@���������������$��<��\��l��`�������Ї���$��<���7z�A����8����-��ѐR����-H�Hf*��-8���i�f������t���$@���H��������-P�Ф�X��-��z����x�������-��Р�Bl�������r��������������������-���� �.
������p���������������-���*��-���(J�X��-0��p��|S����P���$X�м��d��P2��L��Ȭ��2����-|����x��@��������������P��Խ����������� ��4������������0B��@�����@����$��?�����F��D���������0��<8��hK��D���K��P�РK��\��������ȉ��K����-h����4QB<P�����hP�����T�lT�����|S�����7lU��ԉ�|U��܉ЈU��������U����(���U����Zz�Y�� ��(���p
h�Рm��8���m����-D��T��p��`����z���t��������-|�Ш���z����d^����*��-Њ�p�����Ȋ����Q�4O�������� ��Ȍ�8��-p��@�����t�����G�|�����<�����\��`�����������T��d���A�A��h���R����-�nѨ����	������-��и�ј�\̋ќ��������z����ċ�0���dN�$���؋�l�����-�М�*��-�����̅��*��-4�����(���,��T�*����@��������-H��X*��-����
Bl
��t���
��|����� ��T����`��h����-���l��x�����|�������h��t��������������� ��4����������d��|!@��l�
t��� ����(��4#��4���)�)��L���%��T�ф+���"��"��2��-�����P2����l��p��@��-��@�����ԍ�?������D$�D��̍��K��tz�0���K����-��\��PQ����Q��<P����hP��$��|U�U��<���U��D���U��P���h*��-�m:��-��Фp��m������s*��-�:��-̎���0��-@�������hx����p�����<�����؏�`��,��\���$����������,*��-(��X���)��<*�I�D4jx3��8���2��@���2��L���^��,���R����-d��Hf��-H�L@������̏����������-���,�*Ȍ���������Ф���- �� ��R����4R�Ј������������\��������-<�Ш���ԃ�$����q�h��)�l�����-H��x�р�6
����p����\(�����������-�������- ����,��L��P"�h����-���l��x��̐�x��ؑ���������Խ�|����T���� ��4�������d��$��T@�И����8��|!X��4#���
�7$P2��d���2����-l�а���@�� ��Le�Թ��@����м��?������D��,���K���%����K����-ȑ���|S�U���&��&��m��-4��#dn�����m�Ѡm��(��|m\����@�����-H�����-�2z������2��p��,*����-���@��������p�����Г����<�������`��$��H���������������������h3x3������P�D4��5���^����>
���(��,�0�� <��<��<<��H���7��T��Ⱦ�8����-`�Є��A��������Er4G����X��G��-���dSHR������R����-ēИnј�z`�����H�$��@���������������-�����-8�Ѐ$Ѩ���ԃ����l���-�R���.����X����d`�����l������-x�Р��<�\�����������|Y������-���̔�(����s� ����H"�P"�h����-ؔ�T��x����x����8�����X���������(������ ��4�����������d���1��+��L��7P2��`��@��2����-h�М�Ѱ@*�����?�����AB�@�����Թ��D����K��X)���������K����-̕� ���LBhP��дP<P�����U���&��&����4'��]��-�o�m��D���m����-L��l���q�����t�s��x���s��-��а���-�2.�$��-�0��0������0�����0��̖�1��ؖ�|0����00����0D��p���C�����A�������,4�����`�D4��4��P��Z��D��p��D��[T��\��[��h���Z��x����0�������I���������������x�,4��ȗ���D4��З�$��t����
���H��D���P�����T4�t4��<>��!��4��0��TC��4��8��`�Ѹ4��4��X��$�j4���l���t��p������������А���������������$�,��Ș��$��И�����ܘ������f*df�����e��P>(0���,����4��H����L��,���T�����`���l�Ѐ<��T#��#�x��`<�����$����������)z�Y������Y��ę�`Y��Й�Y��ܙ��or�o�����o���|Nz���������Ш���(��p�4�����@��$���L�Ѐ�z����d����$l��<���x��D4zx3������2������2�����d�����ѸD��D��Ț��@��К�?��ܚ���<:����:���`�����l�����L��� ����z���8��0�.����L���.J
�.��`���.��h�и.��t������<��<�����%X
D%�����4������x�����H��ě����Л�,���ܛМ��(����4����Ш����������Є@����,�� $4�� ��@�����L��,��	|W��d��|��	�X��x��,�*������������@���$��Ь?�m��Ĝ�8(r̜�@(��؜�\�*̎�����)���02��`h��$������f
�?���0��|���8�Ј���D��d�P�Ф���\����<����t��P�|��$��������$����\������������-���Ȭ�	4d��Н���uĠ����$�����|W�����<W��lj����d���j����|�m
]��8���\@�Ѐk��L��PkX���P{
�P��p���P��x��Q�����Q���<P������%���|~������~�����8@��?��؞� �d��(�������������(<v����Hv�� ��Tv��,��(�(���D���*L�и���X����d���2�,%|�.��
������|�����Ј������d���Ф��������ȟ����ԟ�$����А#�
��������?��������ЀZ�� ���Z��,��$UY���D���L�иD�
|���d��<�l��(���x��x������ ������д�(8�����88*���l������|���Ƞ����Ԡ�tx�.h��������������|�:�Ј�������$�����0���<��H�����T��$�z\��4���h�����������t����������@<��Ѐ<�����`<���hYd����̡��r<ԡ�<P����h;r������X�����dz�����y�����\[��0��H[8�Дcf�b��P���b��X�����d��$�p�ќ���|��,�*�B�HB�����0B���������������h�p��Ԣ�|$ܢЈ��������v��LI�|��М��(\F8\��(���mF�m��<�����
����P��|�:X�Ј���d����p�����|������0/��<>�������ќ�������5��5��ģ��5̣Ј5��أ��2����DCP	�E�����xE�����������������(����4��d�l��L��x��T�Є��`���l�����x����
tx��.�v������6$��м\�
l����������Ĥ����Ф��aܤ�K��.������������O��~�����~r ��|���,������8��l��0��D��'T�Ѵ�
0�l�.�1��x���1��Є+�����Ժ�������p�����ќ�����������-���`+:(+��ܥ��)���)������j�T��������-,��4���0�� �8�����D���p��(��а��h��(�����|��d������(��ı�����б����������T�D�N���Ц��LئЬ������-(��<�$H�����X�����H�����4��а��4�����<��x��<�\H���X��T���`��`���l��$�����<���������T������ħѰ��x���������$����<�H�����X�����H�����$��������-��D�Ѭ������:����,�����8��T�����(��а��\�����d�Ѐ��`������d���������������������Ѽ�-<������T���Ȩ�P�Q�I������������ �����T��X������-������<������D�Ј5��P���ct���h��X�����|��T����8��<���������ĩ�����8���ԩ��Ѥ���0`���4��������D�*H������������������������(������0���\��8��D���T��Ԩ-tC��h���jtK��|��Я�0���h�FĴt�����T���HR��x'�X���Ъ�<���ܪ����������������ќ��������M��$��,*z��-���@�����8�������X�����������\��`������D��T����������H����x3��$g��2�����ȫ��2������5���"��7����Ⱦ�8����-ԫ��ѴB-`B������A����A�������a��a��(���^��0�ќ^��<��t���R����-H�ќc��pl��bh�Ѥ����m�L���-���@����n�̬�������-��д���(o�0o������Ш����`�Pq�Xq������-ج��ќ���`q��q�l�����-Pa�$������hr�������s������-4�������-\s�̔�:d��h�����p�Ѽ��|���w�����x��x�X��-�����(zȭ�ԭ�X��\�|��hr��x�����x� ����h��H"�P"�h����-��t��x�������(��������x��Խ����������� ��4���������d��T�y.�+��d{��"��"�P2���{������X}��2����-�����7ЮШ|�47��,|.D7��Į�l|�89��@��4~�?�����@��~��K��X)��~��%���K����-��D�ѼU�U��<���&��&�,'�4'��m��`��؀�������m����-`�а��p��T���p����q������q�������H������������-į��Ѡ������Яz�����������������4��$�����,���jȦ����H���#<�z���d��$�Nd���j<���(��T������r��Ȧ����<��(����H���ܪ���(��4��������-а�`��̬��������������ج���	������T����zx�Ј���<������H�����T�����Į�Ю��p��x��T���������X�������`���������ı�d������ܱ���z$�B����-��8�H���������������,����4���a��@������-��Ѐ���,���h��H���t��d��D��������б���̲��������(��ı��IJ�س��<�����ز�0�j��̷�������r��$��d��p�����<������4���0��T��@�$���L��d������-���������x������������������<�$D������(������4�-��а��̳����س���<�$H�����T������`�����b�t�*����$��X�,��(�����D��T���L�Ѵ�$����d������l��<���x������y�����H��(��D�����������Ѭ�z����д�8�zش�<�����T����Є+��� ������������h��������x��H���,�����������������-<����̬�rԒ��p�����T��������������D����T��������$ �H ��ȵ����ص�������������z�������������X���0�z�^��4���u��r��-��д�ј�W���d�����l��H���x���z@m��������������������x�8������(��`���Զ����ܶѠ�L��8����̷������T���t�.(�����.T��p���(��`��h���~����L���B�N�������x��������X���r��-̷�p��4G��X�������H�����������L��̬�������������`2ج�����	������,�����HN���d����8����@������O����X�����d�������Į�Ю������O�ȸ������Pш�������������88#��z`���и�x���ظ����IJ�0��ќ������H�x�&������@��T���$���XѼ��p���L����rT��,��`���zM��x��$�����r$��H���������Ш~��`���(8�`а��Թ����ܹ���T��`�������z����-<��t����x����$�����0��������y��D.��.�H����L�U��h��4�-а��|���������L������������-���x�*�������T���Ⱥ�P�4��*��-���H������d�z��������-�.�
@�-h��`��,��
,��
����H�-L���H�-�H�-���H�-|�мфh��H�-�,Мs��H�-h-��z��H�-�/М��H�-�H�-T�H�-��H�-���H�-���H�-�0�@1���
�����������ȼ�м�ؼ�4��H��P��X��l��t��|�����������0������@��X�����d����t�����d��������\��������4�����������,��
��
��
��
��
\�
$��
� �����������������Ľ�̽�����������,��@��H��P����<��
L���-L�
��
`��
�X��-��
 �
<�
��
��
��
� �
#�
`%�
<'�
((�
P(�
Խ�
�s��-�(�
�(�
���
���-��
���- ��
T���-4��
����-0)�
�)�
�)�
�)�
�)��X��������Ⱦ�о�ؾ�4��<��D��X��L��T��\��p��x�����,*��`����l+�
x+�
�+�
�+�
�+�
���
�2��������������������Ŀ�̿�Կ�ܿ����������������0-�
<-�
H-�
`.�
l.�
d��
����-$0�
00�
���
X��-�2�
�5�
$6�
06�
�6�
�6�
�6�
�6�
�6�
�6�
��
�6�
<7�
�7�
�7�
�7�
�7�
�7�������t��|��������4������X������������� ��8�����X9�
�9�
�9�
�9�
��
�A��(��0��8��@��H��P��Ŀ�̿�X��ܿ�`��h��p��x����������d;�
<<�
P=�
�=�
�>�
�>�
�?�
�A�
�A�
�C�
@D�
�D�
`E�
�E�
�E�
�E�
�E�
|F�
�F�
G�
 G�
4G����������������p��x�����X��������������������G�����G�
�G�
�G�
0H�
HH�
d��
4O��������������������̽�̿����������,������������������-�H�
�H�
�H�
�I�
�I�
�K�
L�
TM�
O�
O�
�Q�
�Q�
�Q�
���
�m����-R�
(R�
��
����-��
�����-4R�
HR��$��������|����������������X����������� ������R��,���dS�
�T�
�T�
0��
�^��(��0��8��@�����H��P��̿�X��ܿ�`��h��p��x�����������V�
0W�
<W�
(Y�
\Y�
hY�
LZ�
�^�
�b�
�b�
�b�
Td�
td�
�d�
�d�
�d�
�e�
���
���-�e�
f������������������`��h��p��X��x�����������������Hf��������f�
�g�
�i�
�j�
xl�
$y����������������������������������,�������������pm�
Hn�
\p�
 q�
r�
�r�
ls�
�s�
�w�
y�
�{�
�~�
`�
��
���
ԁ�
��
��
 ��
0��
���
H��������������������D��L��T��X��h��p��x�������������������x�����P�����8�����������������������x�������������P��0����-8��
��
���
G��-X��
���
Hf����-���������������������̽������������,��x������������
���
\��
������-��
��
D��
T��
���
������-���
��
���
(��
���
�]����-���
�h��-x��
���
<w����-��
�z��-���
������t��|��������������������X������������� ��L������
$��
0��
|��
���
Я�
����(��0��8�����������Ŀ�̿�����L����T��x����������0��
p��
���
���
��
��
h��
���
���
@��
�K����-��
��
,��
@���\�����������������0��8��@��X��H��P��X��`��h���������d�������
@��
H��
؋�
(��
Ԓ��p��0��x�����������Ŀ����Կ������������x�������������
���
���
���
��
���
���
��
���
��
��
|��
���
���
 ��
H��
\������������ ��(��|������������������������������������0��̘�
,��
���
���
4��
 ���������������������̽�������������,��x��������$��
X��
���
��
,��
,��
(��
$��
l��
���
��
x��
���
���
��
X��
H��
���
Ъ�
�����l��������������4�����T���������\�� ��(��0���X���t��
,*����-���
8����-��
���8��L��T��������\��̽������������,��x�����d�������
������-`��
���
��
l�����-p��
���
ܫ�
@��
h����-��
���
 ��
H��
���
����l�����������������4��x��0��X��8��@��H��P��X��������t�������
t��
���
����`��h��p��x��������Ŀ�̿�Կ����������x����������D��
���
���
���
̳�
\��
���
���
���
l��
��
��
��
��
$�����������������4��t��|��X��������H�����������l������(��|��
���
��
$��
0��
��
����8�����������������Ŀ����������������x�������� �����
��
���
���
��
P��
���
D��
���
���
���
$��
���
���
���
������D��L��T��о�\��4��������X��������H����������������d�ќ��
���
���
H��
����������8�����������Ŀ����������� ��(�����0�������
x��
���
���
��
���
���
8��
���
(��
x��
���
���
��
<��
h��
���
���
����8��t��|�����������4������X������\��$��,��4�����@����
(��
p��
��
(���<��D��L��T��\��d��Ŀ�l��t��ܿ���|��������������������
��
���
��
��
T��
���
��
|��
���
8��
d��
p��
���
���
���
���
<��
H��
l��
���
���������������$���������������������������������������4��L��
���
���
���
���
X��
����������������������������������,���� ��(��,�����
T��
X��
���
��
$��
��
T��
��
��
|��
���
���
8�
��
��
p�
��
P�
��
��
�
��
���0��l�����������4��������X�������\��������X8���`�
l�
h��
���$��,��4��<��\��D��Ŀ�L��Կ�ܿ�����T��\��d��������
�
�
(�

�
h
�
h�
��
�
��
�
��
��
��
��
��
�
 ��l�����������������H��P��X��X��`��h��p��x��������h��t���Ѹ�
��
��
��
T�
T��
�+�����������������������������ܿ�L��������������������� �
!�
|!�
T$�
�$�
�$�
�$�
�%�
,+�
x+�
�+�
D,�
.�
�/�
�/�
l0�
�0�
�0�
�0�
�1�
�1�
�1�
2�
<2�
P2����`��h��p��x��������������X�����������������2���А���3�
�4�
�4�
�4�
85�
P��
?��$��,��4��<��D��L��T��\��d����l��t��|���������������6�
�6�
�6�
7�
89�
�:�
h;�
`<�
�=�
�>�
�?�
�@�
G�
XH�
hH�
�H�
I�
�I�
�I�
�I�
�J�
�J�
�J�
K�
�K�
�K�����t����������4��x��`��X��h��p��x������������K��������L�
�M�
���
�U�����������������������̿���ܿ�L�������������������hP�
�Q�
TR�
�R�
S�
|S�
U�
�U�
�Z�
[�
X[�
\\�
h\�
�\�
�\�
�\�
$]�
T]�
h]�����H��P��X��`��h�����������X���������������������]�����p��0^�
_�
8_�
�_�
<`�
�b�������������$��̽�,��4��������,��������������`�
�`�
a�
`a�
�a�
�a�
�a�
lb�
�b�
Xd�
�e�
�f�
g�
lg�
�g�
Hh�
�h�
�h��<�����|�����о����4��������X��������H�����������hD�Ф�Ѩi�
l�������� ��������̽�̿���������(��x��d�������i�
�j�
�j�
�k�
�l�
�l�
0m�
|m�
�m�
�m��0�����������������4��<�����X�������������$���m��8���pn�
�n�
�n�
�q��,��4��8��<�����D��̽�L��Կ�������T��x����������n�
po�
�o�
pp�
�p�
�p�
�q�
�q�
�r�
�r�
hs�
ts�
�s�
�s�
�s��\��������������4��x����X�� ����H�����(�� ���sd�����Dt�
�t�
@v�����0��8��0��������Ŀ����X������8��,��x��@������0u�
�u�
v�
�v�
�v�
�v�
�v�
�v��H���������������������X�������$�����,�����<w��P����hx�
tx�
��
�y��8��0��4��<�����D��̽�̿���������L��x����������y�
,y�
xy�
�y�
�y�
`z�
|z�
�z�
�z�
�z�
{��T�����������������4�����$��X��,��4��<��D�����L���z\������{�
���
�R����-|�
�~�����0��T��\�����H��Ŀ�̿�d��������l��x��d������T|�
t|�
�|�
~�
8~�
�~�
�~�
 �
��
`�
�����l��L�����������4����� ��X��(����H������������t�������
�������<��8�����������̽������P������(��x������������
0��
�����-D��
�2����-���
<�����l��������������4�����T��X��(����\�����������X����(��
���
���8��<��8�����������̽������������,��x�� ��(�������
�����-؁�
0��
t��������������������|��������X����������������T�0���d��
Ԃ�
��
H��
���$��,��4��<��������̽����D��������,��x�����L�������
���
��
D��
���
���
��
���
��
@��
`��
̥�
���������������������4�� ��(��X��0����8��@��H��P����T����ј��
��
��
$��
��
����8��X��`��������h��̽������p������,��x�����x�����\��
l��
���
Ї�
��
$��
<��
���
���
P��
���
���
 �����l��������������4�����T��X��4��<��D��L��T��\�����������
|��
Ȍ��d��l��t�����|�������������������,����������������
��
��
��
T��
d��
���
|��
��
h��
t��
���
���
���
���
̎�����������������4��h��p��X��x�����p��������������������
p��
���
���������������������̽�������������,��x�������������
؏�
,��
\��
���
���
̐�
x��
ؑ�
��
T��
��
d��
������(��0��8��@����4��������X��������������������������H�ь��
p��
���
Г�
�����������������������������������,��x������������
$��
H��
���
���
��
x��
��
8��
X��
���
���
���
�2�,$��.�,����H4<��P4����4(��(4��4���3
@��"��L���
hY$d�.ċ��l��t,x��X,�����@,*����+������+����дls����.,������;�����(;�����P}����������������Ќ�����\�j��Ĝ��(��М4��<���@��X���L�И���.<���d��P���p��ܸ��|���$���������h����h�����������������p������������`�X���d6�����06*���2����?+��$�.<���,��X���8�����D���P�Ш.��\��l.h���)��t��l+��д8K��.������������Ѥf������������������������������������� ����d���<�����$����0���*��<��\*��H���)T���)��`����l��x;��x��P��d;�����Q`\��.�~�����(�����0����������p�����T`.���8`����� `�����_�����_��_�� ���^��,�М^��8���;:D�Ѵf�@<\�.t?��d��,��p��4���|��@�������������9����������r������������������������@�����@���д{���.D7�� ��7��,��P2��8����$D�а���P��<�\�����h��$���t��45����.D������������������,���������������@����
$���.t������$u+��.�����\A� ��dA��,��pA8��|A��D��A��P���@��\��������&\x�.�������������l������x����ф�����������l���������A��д�r��.�������HR��H��`\x�.�~��$���(0��<R��<����l�*T�.������\��|����.���x�����K����.L*�����\*��и�����������\�������1�����,����������l������x����Ј�������������(�����4�м��8
��@��h
��P�м��\���Ah��Hg�.Xg�����lg�����x����������Ѽ������������.D7�����7�����P2�������$��а��������ш��������$�����0���h�<�Рh��H���hT��g��`���g��l��d5��x���C���аC�����d�.�>������=���P2�������$���@v������6��Ѵ�TM����.����������D�����h�(��p��4��|$@�Ј��L���X��!��d���!p�Ј ��|��� ����ШG����� G*���ԁ\�.�������|_��ф_������_�����P������l������x�����$@��,@����H���$�ш���0�����<�����H���MzT��,M��`����|�x�.Ȍ������1���Q�����O�����4O������� -N��.�,����� �f���(������4������D��������Ԓ�����D$�ќD��0���D��<�ЀT��H��dS:T���2
���l�.�x��t���<��ѠY������Y��ЄY�����hY��д�r��. ��d�����x���@��K��.�������8�$��$s�����~����.h����m��,������~rP�.���|~��X���2,�Jt�.P��|���O������~�����?������Z����,������A`\�.,�Ѩ~�����(�ѴRK�.����������ш�� ���g �8�.���t4����@���2t�J`�.����h��x���t�����������ؑ����.����������t0B���., �����< �� ��������������.�������7���&�����4�� ��5��,��5��8�Ј���2��JT�.�� *��\����`5��l��l5��|�������Mr��.h������h7���ФJ�����@K����.LK����K�Ѵ� ���.��������*�Ѹ�d�.���� ��t�$,������8��\D������\\�.D	��d��P	��p�Ѥ��|��l��И�����h���8\������+\px�.dp����x(��`����`&��М_�����_����_��������^�� �М^��0��$u��*H�.����P���j��\��h��h����t���������$X����_������_��д��Mr��.h����������Р�����|m��l������d���.��������$������(����4��`���@��p�L�����X��$���d��|W��p��<W|��lj������j����д�|(��.�%����������LT����\T��������H�����0��������$�Ь5�����5�� ��d5��,��x�8�Ѐ��D���P��$u�.��h�.,���p������|�� �����,%T�.(������D������P�������yj��`y�����(z�д(����܉��@�����$u�>���.�=�� ���=,��g��8�����D���_��P���_\��_��h���^��t�М^����������$u������.l������H������X�������Ѡ�����ܜ��|S����.�������L�ЈL�����L$���K��0��L��<������H����T�����`����*l�Ф���x����$���������$�������2��8*��.4����������L��������t�.|����|�����(���������$��$�0��|���<���������H��d���X������d�Ш���p�������l�.t�������,B��Ѵ�����.��������Ј���������������������������М������I$$��HR��0��<��<��D���H���T�����`��(���l�Ѩ���x��\�*���DѴ
���.������
����р�������А��������м������<���$����$$��� �����,м���8���{8�.H���P��s$\�\s��h�ls��t�d5����85r��ܸ���������М�����45�0o�.o�����,��X,����@,����+����+��)���l+$$�����0���<����H����Tд�����l.����t�Ї�Мe�����e�Ф*�����*���z�����z���м���������d�����P����Ѵ����.�����ЇМe��(�,��4�d���@�p���Lј���X��(
�@<p.�<��x�pz�А�����|���Ȍ�������Ѵ5
$���.,�����8������������є���8�������������(�45�p�#@.��x���H�����XА���d���pШ���|����������̞����������t�B
D����.P�����\�����|�$�Ѥ�����<���|G��� G*����� ���,�D���8�\�D�����P���\��Mr��.h���t������Р�����|m�Ѹ������6$���1�����1�����������2S
���.����������������Јq��$�`qj0�lq��<� q��H��+��T��`�L���l�X�x��2f
�z�.�y����|���4�����d�(�������������������А����l�*��.������I$ш���(�����4��7��@�DYL��C��X��C��dЀ��p��|�Ѱ�����|z��.� ����� ���ьW����PE����E�����D�����D�����@����0D���|��x�.�������(МFw
�F�@.pe��H�`e��T�,e��`�Xd��l����x���є����X������������(���dѴ�
��.���������������ܵ���ф8����*Ѹ���(���4�L���@�@�L�H���X���B��.����p�p�|�D�����P�����`�����l����������(����2�
�J�.������\*��.��������	�\���	��1�� 	�,���,	��8	�0���D	�L���P	���\	��$h	��	�HW���.,���	�<���	����	�����	�����	����	�,���	�Kl.����	������	Ѵ���	��
Ѵb��
��G 
�,G��,
�G8
М#��D
�,�P
����.|G��h
� G*t
�$_���
�@�8_�
�X'���
�<'�
д�
p��
.����
���
�����
�����
�D����
�$�м�����Mz�,M��(�TM��4�$u��L.���T��v`��v��l��vx��v����pv:��8����� ����2�,%�.�������|����|�����|�����|����8�(�����D�����9�� ��|,Ѵ�
|S��D.���L�[$XЀ���d�4`�T�.$`��|��_����|����h���K��.����������Ѵe����,���������������h���
�t�r
��2�
�J$
.����,
�����8
�d���D
�p}P
�l��\
�x��h
�<��t
�P�
�,����
�0$��.���
���
�����
��3��
��3���
�d����
�d��.\����
���d���������h���(�|Y�4ЄY��@�hY$LЈ���X�����d��H�. ��|�\�$�Є���������T9����9����������r�д�
����.G�����F�����D�������Ѱ��� �T\,�T��8���TD�XS��T�|S��`��Ѵ�����|.�������$�������������������T��lT����$u��2g�e\�.T/����/���4Z�����
hP��$.����,���8�6��D��6rP�4���\�D���h�P���t���� ����,��������Иf�.Ѡ�������P����`��ԁ�.`���x���\�.�6�� �(�r,�ܸ��8��$D����P�<\� ��h�,t�8����H����0!����| �ш ����� ����d����$u�.���.p�����l��t�������z�����$�$�xќ	��4��	��Dи	��P�l\���
�&\t.0Ѡ&��|���j�ѐ����������Ѩ���������,�����H�����<z����Hz�дK.���������'��$�����.�|��<�� �Hи ��T�� `����l��)|.L�����I���$I����I:��
�������������
���Ѵls���.�9�����g��Y����Y��РY��(��Y4�k��@�k$L�lj��X��j��d�d5��p�x�|�x������
{�Д
��������.�������Ј��������O�����O���ШO���P����\������$�����.�]��<��]��H�0���T�<�`����l��
x�
�����
�����B
p��.�B�����B����HB����0B���L�����x��Є���������Ѱ����B�455��4.��<��H����T���`����l�P�x�LT����\T����lT���� f�Ѹe����������x�����d����Ѽ������������Ѵ�
�FI.�F���|F(ф���4�p���@�@���L�0�X�|��l.{��p��d|������ ��Ѥ������������������$���d�.(��������|�����.x����$����4����T��Mr��.�Ѽ���8���H��2L�J`.P��h��O��t�LO�����Q���.\��������Ѡ������������|��������_,&���.8&����D&��%������LT��$�\T��0ЈD��<�dD��H�tD��T�@D`�h���l�����x�������t�h���.(����((��l����������Ѥ|����t|��{��������2��/_.�/���0��$�0��0���I<�����H���Tє��`����lЬ��x�����������h�����|Y���8�����T����Ѐ�����Р�����|m��l����((������� ���,��2*8Ш2��D��P�`��\��m�.D���t��4�А�����������0����hP���.�K�����b$��0"�����<�Ѹ������`������. ��� �\
�,���,�<���<�$H� ��T�t*���р*��l�\*x�?��s�.hd����4*������� ���|o�����o���Иo�����A�ЬA�����*���ܴ2�.����д(�(8��4�88*@�D7��L�7��X�@��d�p�txD.�v����|��ДR����D�����t����� ��д�K�.������8�$��$s������0����đ� ��\��,�h��<�t��H������T�4��d�@��p�  ����|��2��J�.P�����O�����~�����~r��\�����h����� �H�����|�?�����Z�р������K,.���4�����@ш��L�`\XѨ~��d�� p�$u{����.l�����H�����X�����������.������Ĕx�Ф���������X����d �p�� �����.����, �����8 �����D �T>�P �`>��\ ��h �xn��t ��>l�.?��� ��l.� �l��� �XH� �$,��� ��+� Ѵ�r��� .8���� �(�� а`��!��j!����!��$!�\��0!����<!Ѵ2��H!�<2��T!�$u���*l!.����t!��j���!�h���!����!����!�$X�!��_���!��_�!�_���!��^���!�<����!��4:�!�@Z�.Z��"�hY$"ш���("�����4"�,M��@"�TM��L"�L5��X"�X5d"Ј�j|.����|"������"Ѩ����"����"�,����"�;���"�(;���"��:*�"Ќ�����������"��/���"�0��#�0��#���I#�����$#���0#�0���<#�t<��H#����<������`#Ѥ���l#�<�x#��<���#��<���#�xX�.dE���#�����#�X$�����#�(����#�p$�4����#�@��#�`=���#�H=��$�=��$��$��f
`\,$.d���4$�$@$����L$��1*�.F��d$��$�tQ��.�Z���$�XZ���$�`������l����$���x��$�$ф����$�p=�$�l�*d�.�������$�,%�.����%�Ƚ��%р9��%�9��(%����4%��%�*)@%�)��P%��)��\%Є)��h%��%��t%�p?���%��>���%�(���%�D����%��9���%��9�%Ѵ��I��%.J���%��I���%��I���%�`��&����&���&Ј��$&��0&�@E��<&�PE��H&�E��T&��D��`&��D��l&�����D.�����&�<��&�\L���&�hL�&�hK���&��K���&�TM��$.0����&�(o��&�o���&���&а��'����'�z�� '��y,'Ш:��8'��:��D'�,M��P'�M��\'��L��h'��L��t'�h��\�.D����'�$��'�pr���'��N���'��%���'��%r�'�D%���'�XS���'��f���'��e�'�dN��(�pN(�|N��(��N��((Р@<��.t?��@(�,��L(�4���X(�@���d(����p(���j|(д����(�L%4�(�T%���(�`%���(�l%���(�x%�(�$���(��$$�(�$O���(�<O���(�LO��)���+()�).0)�� )�@Y�,)�HY��8)�TYD)��X��P)�Y��\)ЈU��h)��U��t)��U���)��)�0m��x�.l���)����)��\���)��\�)��$�.�.���)��.���)�@.���)��"�)��l��*��z*���� *�g,*����8*��D*�Ԓ��P*��d�.���h*��t*��*������*���$�*�*�����*�P���d�.�*�0����*��~r8�.@����*���x�.�����*�.���.,���+�����+ш����t��(+�|���4+�`�@+����L+��:X+�p:��d+��d*p+�4d��|+��q�.ph���+�dhz�+�0h���+�Hh�+��\�.t����+�T��+�����+��-�+�D���,����,����,�,���$,����0,��kB<,Аk��H,��k��T,Шk��`,�|kl,� k��x,��j���,�h���,�H��������,�\����,�`T���,�lT���,�Lj�,�Tj���,��!
�,��!���,��!��-�!��-��! -Ј ��,-�� ��8-�$t��D-��sP-��s��\-��b����.�b��t-�|z�-�����-�P 
�-�L����-�X����-�dz���-��y�-��x���-��(��x���-��jd�.����-����.Ќ��.�T�� .� ��,.�@���8.МF�
�F�P..����X.���d.ѐ���p.��\|.�\���.�L���.Ѵ����.�|�$�.Д`���.��`�.Дz���.�|z���.д�@K���..LK���.�K/ѠV��/��V�� /�TV��,/��T8/�HR��D/����P/Р\��\/��F�h/�4~��t/��}�/��}���/�0|�/�8|���/�|���/�{���/��@<x�.�<���/�`<�/�l����/�� �f0.(���0�4���0����$0��00����<0�h[H0ѠZ��T0�LZ`0�ē��l0�Г��x0М����0��TM���0.�����0����0�D����0�h��0�p���0�|$�0Ј���0���0Ќ��1�T��1�ؔ��1����(1����41�100100001000210230003001201022010020020300302002302330040000202001010020120001830100201000010200420000202300004010220203103403003000000102022000100302020330200002000200	20000000020000002000002	201020100100014032010100	20000000110001001000002	10001000020300001020102	20300010020300020102300010040104	200020100	2010200002010202102000040202	10000000010000000100405020010000001000102	1030002004000120001021000201	100000100101000030002003000201300012010120302102011010250420003021030000	102000000	300010000	1020001002000001202000120300000203000000013010220002200000	201000100	2000200011020001000010000000102000102002040100302000200001020200400	300030000	3000001002020000	100020100	3020000004030000300000	202020100102010020100000	201000000	200000100201000110002003010203	403000000	40300020030201023020200202012000000000120000000	302000100	300020100	200000102	20002020180000030003022000300	10000020230202013020100	101000100400022010002010010200000001	300000000202010030200020100403000120102000000201000000002000000010020000000000201020100003020210000020100200000000021020000010010002031000000000010000000001	2030000002000201	200020000102020120301002030001100032030200	200000002	400000000100030220102001030200	20000020010000010100011000001203010230000014010040302104103004010000010400403000000004020100400000120102013010000	300000001300000001000300	200010000	102030000203201000310002022020300	1000001021000100000010401021000103	302000200	1020000012010300	100020102	300020102	200000203	10002000030200000	202000000
1000000000101102034000000	302010000	302010001	201000102300010000002020200300000000001030240000010002000000	100000001	30001000120202	400000100	400020201	400020100	1000102001000101	401020102300010220100000102302000120003	200010200	201010100	200000300	20102000110200020100	201000201	10002030030300	300010200	300000300	1000003001000001000030002302030210000020000	10001000130003000201	100000200	201020102	10102010010000000201201010220100000100	203000302200043000001000020000030000	20002010220100020102	203000002	203000201400010020300010000200010420201024020000010400000	100000201	30201020020001020100200010201022020102010220001000001	3020003002010004010020600000400010001002000100000020001000100	401000100204010220400000	300000201	200030000	20102020140201	300020000	201000001	100040100	10200020010000010200	30000020040100020102	403000100	10002000130400304000020103	201010001402004030000040100003020202101020030000020000
2010000000201
2030000010000600000
10002010202001000000020102004010200000020000020100	1020100003020300
2000000000	20400000060000000878888840808808087808208081080830808809080808000
808080800080809080809080800080808090800080808090809080808090800080808080809080808000809080908080908080008080809080800080809080800080908080808000
8090808000808080908080908090800080809080908080908080008090808090809080008080800080809080908080008080808090800080808080909080008090808090908080800080808080908080008090808090808090800080808080800080908080908080008090808080809080008090808090800080809080808000808080808090800080809080809080908080008090808080908000809080908080008080808090808090808000808080908090808000809090808000809080908000809080908090800080908090808080800080808090808080008090808080808000809080908090808000809080908080800080908080808090808000
8080908000808090808080800080809080908090800080908090809080808000809080908090808080908000809080809080809080800080908090809080908080008090808090808080008080908080908090800080809080809080008090809080809080808000808090808080908000808090808090808080008080908080808090800080808080908080800080808080908090808000808090809080008080809080908000PK
!<��S4747hyphenation/hyph_sh.hyfHyf0tP�����'-4�������$�'–’-111001D*�������t.xab�cd�e�fxg�h�i�j�kHl\m�no`p8r<s�tLu�v�z���
�\}д|��P.���b�c�d�fghtjk�l@m$n0p�r8s@t�vdz�İ�HP.�j�X.8P.�P.�j�l���b�c�d�fghtjk�lm$n0p�r8s@tdvdz��tŴP.�l�r���b�c�d�fghtjk�l�m�n0p�r8s@t�vdz�İŴP.�j�l�r���b�c�d�fghtjk�lm$n0p�r8st8vdz�İŴP.dblctd�f�g�h�j�k�l�m�n�p�r�s�tz�T����b�c�d�fghtjk�lm$n0p�r8s@t�vdz�İ��XŴx��\�Hj`<���t��P���
�
��l������������<�
��l����"e,#jX�\8$i�t$a�$e�$i�jX$o�T%a<%e�%i�%u�$"eh"j��%e4j��%i\�&a�&utt ���b�c�d�fghtjk�lm$n0p�r�s@t�vdz�İ����b�c�d�fghtjk�lm$n0p�r8s@t�vdz�İ����b�c�d�fghtjk�lm$n0p�r8s@t�vdz�İ����b�c�d�fghtjk�lm�n0p�r8s@t�vdz�İ��#e $jXŜ &aHj&o<4&iD&oP&u�P.dblctd�f�g�h�k�l|m�n�p�r�s�tz�T����b�c�d$fghtj�k�l�m,n<p�r8s�t�vdz�İŌP.dblctd�f�g�h�j�k�l�m�n�p�s�tz�T�\P.dblctd�f�g�h�k�mpn�p�s�t�vz�TŜP.�j`P.dblctd�f�g�h�k�m�n�p�s�t�vz�TŀP.dblctd�f�g�h�k�l�m�n�p�s�tz�TŴP.dblctd�f�g�h�k�l�m�n�p�s�t�vz�TŴl&eP.dblcXd�f�g�h�k�l�m�n�p�s�t�vz�T���0b�c�d�f$	ghtjk�lt	m,n0p�r8s@t�	vdz�İ�P.dblctd�f�g�h�k�l�m�n�p�s�tvz�
�xP.dblc$d�f�g�h�j�k�l0m8n�p�s�tz�T�\P.dblctd�f�g�h�k�l�mpn�p�r�s�t�vz�TŴP.dblctd�f�g�h�k�l�m�n�p�s�t�vz�T�t���P��X.bcdf$g,h�j4k�l�m�nPp�rXs`t�v�z�Ĕ���0
b(
c8
dD
fL
gT
h�
j�
k,l<m�n�p�
r\
stPvx
z�� 
����
���X.bcdf$g,h4k<mDnPpXs`t�z�Ĕ�HX.�j\X.bcdf$g,h�j4k�l<mdnPpXs`tv�z�ĔŜX.�j`X.bcdf$g,h�j4k�l<m�nPp�s`tv�z�Ĕ��
.�X.bcdf$g,h�j4k�l�m�nPpXs`t�z�ĔŴX.bcdf$g,h4k�l<m�nPpXs`tv�z�Ĕ�&�
.��������
�<&�
.�b�c�d�f�g�h�j�k�l�mnpr$s,t�vHzp�X�*�
���
�
�00l
Ŵ0x0�0<0�5t��d
��0�
**�080t<X.bcLdf$g,h�j4k�l<m�nPp�rXs`tv�z�Ĕ����
blcTdD
f�gT
h�
jtk,lm�n|p�
r\
s�tPvx
zX� 
�X.bcdf$g,h�j4k�l<m�nPpXs`tv�z�Č�xX.bc�df$g,h�j4k�lmnPp�rXs`tv�z�Ĕ�\X.bcdf$g,h�j4k�l<mdnPp�rXs`tv�z�ĔŐ���
������0�0`0�0t����d
�<*���b�c�d�fghtjklm$n0p�r8s@t�vdz�İŴP.�lHHj\�&a�&i�&o�X.�j�l��0
blc8
dD
fL
gT
h�
jtk�l�m�n|p�
r\
s�t(vx
z�� 
����<��H0�&a�j\0�0�j�C����
����
����b�c�d�fgh8jklm$n0p@r8s@t�vdz�İ��8���b�c�d�fgh�jklm$n0p@r8s@t�vdz�İ����b�c�d�#e�fgh�#i�jklm$n0p@r8s@t�vdz�İ��dblctd�f�g�h�j�k�l�m�n�p�r�s�t�vz�TŨI����L��O�O�O��OOO8OOO�jO$O�j0OHT@O8O@OPZX����dOlI�I����0���I���b�c�d�fgh�jklm$n0p@r8s@t�vdz�İ���&e\&a���������&X.(b0c8dDfLgTh\jdkllxm�n�p�r�s�t�v�z���
a 
������
hlnTh��D
n�hT
n�
ntn,h�jh�h�j|n�t�
n\
n�nPhd
{�����x
n�
a��X��������
a�a��0
blc8
dD
fL
gT
h�
jtk`l�m�n|p�
r\
s�tD
vx
z�� 
�H0�j����8a�b�chd�f�glh(i�j�k�lmn�op4r@sPt�!u\v�z�Ġ�<������su��H�,j\���4a,jPsu`�oP r��8� !a<�uu@yv��0vr��L,a��mi�Ÿ�t��x����a�
��
����������������<���\eti����x��e�$a,e4i<oDux������L��$a,e4ihopu�L���a,e4ihoDux��$a�e4ihoDu���$a,e�ihoDu��$O�j4T@O������x������$�hj0�8�@�4�P�X��p�d�l�����������������������@�$��jH��O����x�����j���O���O�ODj�T��xŰO�����j�O�jO����j��h0
�l�8
�<�D
�L
�T
��
�t���j����j|����
�\
���d
�l
��4�x
��
� 
��P����
����h���h���
����j�����j��t���j��t�������(�d���d��8��o,� rx�db0fd,e�fgLgnh��To4*s 'z,��To��To�x���nx��g�tx�a����kx��-b�g.k$.nLt���
�a�,btdXg�,i�,j�,ll'm�,n-o�rL-u�,v��xgpon�s�z8>����Dk$	ln�0o�0r8���-a�n�0r�O�a�o�uLx��6a�k�p<a�<ik�<o`=r�=u<��,*cT7k�9l�:mt7pH<r�t�=u��Ljx��nx�|d(g�Ajt���#�Tv$\e�i�wo��	`?i�?j�>lhnP@o�@r�Au�>vxw�<$������#��c�Ĥ�x+�w.�hm$s�wtx��a����k\�1<��m4t�1x��d�g�Jr��<a�g<KiXJjdJl�m4xn�KoxJrTMu�Jvw� DbPd�t$���n�n��nx� x.�hm$sTxt�x��a����k�Ld�g�m�yn�r@ts�t�#��w.Tv$,e�wo��	�Ma@OidOj�Ol<n�Po�PrRu�Mv��j\��xnH8�8���v�Ę=�����x��w.�hm tTv�a�xe�xi�xu����k�xn���Ri0Rj m$So�SrTu<Rv@wŜ� d� t�@ e� ox��hm t` a���t k��g� t�B� a� i���8Ui� kxBxG�g$.n|!t���� a�UbH!d�Ue4!g�UiEj�Ul�UmVn8Xo$Vr�Xu4Vvx�Ys� zxU�$	\WalnhWr�\!n�#B�OT!ixLh!a���p!k8$B��!ix���!m�nL�xd�!g|_s�!zdd�x��t���
�!a|\b�\d�'g�\iEj�\ll'm�\np^o�\rp_u�\v�T���"i��t"l�"n"tTv�[���<"i���"l�"nD"t�"vtP"eH�1���|"jH$�$�c����"jtk���".��"dx���"o���"gP#ktt���".��"dx��#o��#g|#kt#e�~x�\#d8#k���@#a~�kx��#dd#k���l#akt�"��#.��#e�#jx���#d���#g���#gt$#��#.,#�#e���"n t�$���"n$tt$eH|"j��,$lH$z�<�l$jP$sH$z�x�l$j�1����$d�#�Tv�$z%�������$d���$z0%�����$e�#��$j��X���$����$��
���$���X��%���%��
��$%���`%s$t�"vp%�x��"v<$<~���h%�H$�"j��|%l�%o$th�8$L��%r�#�H$z�%�$�
���%�~�
���%���&g�%�x$�&g�%�xx��%Ō��,&k���TvL�l$jx�8��d&r<��4tx�x&sL�,$l�&od&rh���H$zx�,&k���,&k���&jx��&n���B'.'m\�t�tt)a���
((a�'b�'d�'g�'i'j\'ll'mt'n<o�'r�'u�'v��'j�)ot	,�)a�'j���*a�	$)u0�(e�(j�(o�$	��T)sL�$*b*pH���'l����'o��'h(oH��(kx�(ld(t��88(k\��@(s�L(m���X(o$����(ap(n�+x(d�(lx�$����(a�(n���(dt�(ex���H���(iH��(l�	<���(a)n���)�L�)Ĝx�0)n�8)a`)e<��D)t��0)n�x�l)k�)�<����)����(ap(n��)b� x��)j<���)a����)�x��)�<��*a)n����)�x��)�x)`11�<��,*cL*kX*pd*t��*r<�*o�4+i�+r�+s +vH L�|*l`���*uL��*p��*u$6����*a�*nG�*j����*o��*k�*rx?8���(ap(n8x�+r�+a��,+dH+j\+l����(aL�H��l+jT+u�HxO���t+ax�|+n�+s��+a<8W���+rH���+e���+l�O�+e�_����+�,��
���+��
���+��_�e���,r�#�$,v���0,e�,ux�<,n�kx�X,v�`,a<��l,tL�x,s08.ax>ep-i�.l/otD1e��'jX1o,�1i�'j�1o�	0/a�-e�/i,0j\0o���0d1m�0��2a42b3dH6fx2gd6hH3k�3l4mt4n�4p5r85s�5tL�,7nH7pxs���\-a�z x.�ycd-j�-v�.Ĵ��\-a�s���-b�-e�-i�-n���-d$�����x��-jx����-a����-aH�x�.l���.a�x� x.0.vx����H.ax�P.vt0\.a<��h.k���t.���x��.n�
���.a�
���.�D��.Ŝ��.u��.e�x��.k����.a/k��.j�H��@/aH/e�(iX/jT+ux�/l�>nx������P/iT���d/d�
��l/��z�/i�/j�/n�/r0tx/�����/r<���/k���/s����(a`)e�l+j8`��/p����x.�/o��0dt 0e�x�80c���@0al0c�L0j�/r�����t0t\��|0e��0m� 0e�0n�0t$�@��x��0v��0a����0�1��\�� x.1i��,1j�� 1l�
��,1���81ŀ��1g�1pP1zx1��
��p1�x ����1�x��1�`���1a\�#��1m���1j\��#��1m����1e2o$��1n��1m��x�$zr2t��$2aX2r���D2n�L2o\�x�d2mx��l2a�2e�2l�2o�2rh����2o<���2s��2o����2n��x��2f��2a��d2m�2n���2i$3o���3z��D2n�03i���<3lx3o�W�X3nH��`3o�l3l�x��3c�3tH���3a�3e�3i�3o�3u���+r��0v8����3s���3kL��3k8���4r44t\��4eT4o�	���,4r�8��@4f�H4r���#�`4f���h4e�4o�d2mx�2t`���4a�4e�4l�4o�4s��4r���4eH���4l���2f��4e��x�5h8��5a4��$5i<��,5ed5i�5k�5t@���P5t��X5n|5s�	<��t5tH����5l��5e�5o`���5p��x��5z��5a�4ex�5h�4l����5a�4e6o,6rG�2n6p`��$6oG�2n2t���86ox��4l3z���T6a�6e�6i�6o,6r���4l�����6jd2m�6p2t�`���6s�4rH,��6lx��6e7i 7��H���6j4#�6l<��P/i���7��1�)���47�`��<7Đ�7a08i|8o�8r�9u�7v<�:a;i8;lp;o�;r,<ux�l)k�7n�7r�7t����l+j� h��7t���7o8���7ix��+s��7a8r�0���8��8Ĝ�z(8n@8t����(a��8L8k����x.�ya�ziT8stzu�`zk`8n�)�\x��x.�8m�3t�zv��8a 9e�8i89oL9s�zu���x.�3c�3t9��8ŀ
�
���8�<���9���6j���x.9n��09j,H8�(aD9n��*a�\9j`��h9lL�t9px��9b�1m��9a�9e:iP:j\:o�:u����9���,+d�9���1���$:d�9�����9e�z:jl)k(8n���,:���0d4:��@:e�0.v�
�
��h:�p1�L�p:Ŝ8��:e�:i�:j���H/e���:j�=t�:ex�;l0.v� ���:v��:iH���:j8���(a��;r���3tD;��(;i���,:���P;j���X;o��zdd;k�;l(8n�;r��0)nH���;i�x��;v8���;a���t+aP/i�;k<l�(nx��;v��;a����<j���8�`��<�L� <p�/r��,+d�<<eX<kC����(i�<lx��<k�1m�/r�<s`<v�<��H��<j$�����<n<����9��z�<c�<f(8n�/r����8)a�l)k=r4=�8���(i�	@=j<���(a=n���$=��Px��x.0d|=n�0v�H=a�ze�=i�zo�=u����x.�3cD;Ĥ=��
���=�xL��x.�=g`L��=p�1L��=k�=s>t>�<1�1�
�
��>�X���+��� >��
���+�,>��Y��H>r�P>b���\>o�>u�� x.h>z\aL��>m���H>r�#��>v����>e�,@a8@e�'jd�>a?e ?i8?lD?ox��(l<���(i?t���>s�H���1j�7txh��0?a��:j�/rx��,r��T?g�?n�?s�o���t?�����?a|?�xv�e<���?k��x+�?h�?�t�?a�?e @u<�����?������?k@z�?Ā���L�@nx�(8n��p:�8���?a��@fD@rp@s�@��<���@e�@jh@o�}�st�@e�}����@i<�����@�x+@n�@s�?�(A���@a@AeXAu��<���@t�����A��
��A��
��A�$�.l��4AaPAp`�L�lAg�?k�?�x�8�x�tAr��|AaL��Ad�Am�@�\�x��z.�BdPCh�Bk�Bl�BmCr8CtDCuXBv�Bz�B�����AaBe(Bi4Bo@Bu���z.�CcXCd�Cs�Ct�Cz�C���z.��z.L��z.�Cr�=����Ba�:ePBixBl���pBjx����Ba�:ePBi���(i�
��>��B����PBi=����Ba�BoH���Bo\���w.PBiClCn����8��0CcPBi(Cm������PBiD�l)k�1��0?aPBi�0npCr�����Ce�(iT+u�
��xC���,�����Cd�Cn����z.�(i<���Ct�����Ck�18��DePBi��|=ntFe���De�EiDjTDl�GotDr�Iu���Ea�Gu�4Fa�FeGiDDj�Go�Gu�
Ha�HeIiX<k0Il<InxIoLIsTIt�Iu�Hv�H�dI�0����Db�'d�'gEjEll'mEn Eo(Er0Eu<Ev��dEs�Dz,�t��'j,TEa��L�$*b�	HEi���1jx��/n�)�<��,*c|Ek�Ep�Et�<�,@aHEi�Erx�|=n��Ea��T?g�Es�k�Ej�+�En��Ei<���Et����l)k�/rFs�7t(F�<���(i�
��h:�x��-jhFkLFm|Fs\�l����TF����\FŐ�<��tFk��TGdlAgPAp�Ft�F��Fŀ������F���
���F�`k��8Gg�FpGt,G�����Fe���Fj�@s`GvxGz�k��
��$G�xk�k��@Gn��HGe��x�GtX,v���hGa�Gi�����\-a��GkL��?k�?�L��3t�Gz�L}8���Gu��Gr�����Ge�
���H��G�x�pHm�/n�7t4Hz�<�Hŀ���x.@/a`Hd�(iD9nXHoT+u��H��\��hHb���
��|Hn�H������H��C�����pI��H����<c,+d�1m�<s�Ht�H�x1���)��9�����x.�(ip(n���<c�1j�<s�7t0.v�)�CDIj$CH�8C@Cl����\I���zl)k�/n�Iv�I���9��)�����(al0cL�l)k�/n�<s�)�(F�k�
���I���I����IoL��Ib0Jd8JmJz$Jŀ���
��J��\���@e�@iPJj,rtet�Ke��Ka�'j�Ku�lLa�LeMi0Il<In(Mo�LpTIt4Mu�LvM�d�Ja�Je(Ki�1o�8���Jgx��1jx1�<���(aD9n?t���Jsx�@x.�;v���Ka�<n��Kk�<s�<��T?gpKsXKz�	����hKdPKv��<���?k�Kt����Ki���(l(8n0.v�<�x��1g�KkP1zx1Ō��@/aL�l)k�)��e����KdLg�Lk$Ln�Kz$	e������<LdLlh@o�Gu���#�����x.@/a�LeDLiXHoT+ux�LLn�LpTxtP1z�L�x1��#�����9�7�`��@/a�(i�<l��9n9��Le��,+d�1m(8n�7t�9�0C������L����9b�<c�/n�)�(F�z�/nL��1gx1Ŵ���@e�@iL�Lk�Ml�AmDMvtMz�@Ā���Kd�@e�@i�Ml�eH�����Mmx��Md�k���Mj8���Mix��Mrd�MaNe NitNl�No�Nr4Ou��@z�?��F���?k8NlPNn@NrH�8������HNj<�x�\Ns�NŨ�dNa�
k�
���N�+�Nd@z�N��FŐ���F����x��NtO����Na�Jg Oi,OsO�|?�k����N��o���O���\Ns8�L��F��T?gXOz�	e���POvtpOe��(8n�)�x�Gt����Oa�Otx��Ok��Oa�Oe�OilPj<PoLPu@��lAgPAp�Ft�F��F��Pj�@s@z��8Gg�FpGt0P�,G����Pe����(P�+�?kLFmL�PAp�?��F�L��Nt�`Pu$����xPnG�Pf�Pk�@ĬPŀ
��
���P���$Qj����Pnx��NdLFmHQnTQs�Pv$Qz��Pa\QepQi�Qo�QuH�����Qd8Qr@Qu��Ls��HNj<���PApTQs@z���?k�Ft+@n`Gv�F�L��Qb�?k�Q���<���Qa�Qi�Qn����Q��x�\-a`���QlL��QpRs�e<��Rm(Rn,et�RedPRalRe�Rox��1j�/r<��?t��`Rs���\y.�(a�Re�(i�xRj�/r�<s���(8n��0JdT?g�Rs�Rz0e����Rb<�Ki<���RpSts�So8Qr��PJj�SbPSdhSk|Ss�#�<Se��DSrL�\Ns���\Su�e<���Se�SjtSv�vt<Sex��Nd�Ft�?���Sa�SuL��?���N����S����SeLTipTjL��Sb�Ad�Tg�Tp�Ts�Tv���� T���(T����4Te��@Tj���(P���XT�tdTe��tAr���|Te�Tj8k�+�TrGtt�Te����Tvx���To`�� Oi�So<���Tl�����Ucx�Uk���UaPUo(ErhUu�� Uv���,r��PfDUt<�L�0Jd`Us�
�� \�,>�0PVa�Vo��tWv���0d|Wj�Wl,7n�Wr>t�Wz�W���Wa�Wi�'j�Wot	�za�We�xi�woXr�xu,Xa�ze�zi�'j�wo�xu�<<e�Xo�	�>a�Ve�ViWo4Wrx+,+d(8n�/rlVu�V�D38 L�tVr�
��|Vu�
���V���1j�/r�����Ve���Vd`Rsx��XHo���Vg�Vj�7t�<����WaH/eT+ux��1j(8nx��3tD;��$Wa�0tPWĄ)���HW�x��9���0t�1�1H181�1����0��0�x+�1gP1z��l)k�)���1gP1zx1Ŝ��w.�7t�<ĐH8�Wk���Wsx+�w.�7t���?a�Ml,r�$Xb0Jd`Xr|Xtx��Nt�Tv8��PXatXt�Gu@����l)k�)Ĵ���@eL��Xd�Xl�Xm�Xv�Xz���?a�Xb�@i������Kd�@e�@iH��|Aa\��,r<��,*c Yk<Yp[r`Yt��Ya�Yi�YlZo0Zr<<ZadZe�Zi�Zl�Zn�Zo�Zr�8[aT[eh[i�[o�[r�[ux+l)k�Yn������Yd��(8n`x�0d�Yn�Yp��Ya�Yo����6j�|=n�Yp`��{.ZaP/i<lx9�(F���Yp�$Zox+�1j0.v���Wa�Ve�(iXHo��{lLZth{Č��T+u��xZk�7n��9n��Zi�C �
���Z���Z�H���Zo��Zl(8n�/r��0a[e���8m����H��0[d�:e[��Cx+�w.�1j(8n�7t0.v���w.�7n�{r���w.�(l�7n�/r�<s�7t��1j�{m���6l�3t��[e89o�0tL��w.�=p�[r�<�8�[ĠB����[��_�{aL\e�{i�{o�{u����[�,\��
��\�_p\i�J���8\j���{.@\nS���\\���d\�0]o�T]i��T?gl]n�]s�]a�]i�'j\:o�]u,�]a�]e^i�'j0^o��^e_i_nH_oX_u�	]a,]i�1o��/rx�(8n�7t��<]jx1�����(aH/eXHoT+u���<cx��Nt���`]a�8Qr<��x]k���<c�1m�7t�<���]b���]oL��]dx� x.l)k�)Ĵ���Ve^j�#��]v�)t^e�#� x.�<c�{k�)�� x.�1j�\<��@^t��H^s��T^e�^i�`^b�^h�^r�d<���^t���^s����^e���^jx��Nt�Tv8���^a���h@o���7t0.v��t0t�;v����^e��_j$CDIj�����x.�(a(_oT+u�0_k�)�L��1jx��|AaL�d_g<��,*c�_k�_phbrct��_a�{e�{i�_l`o�`u<�`aae�ai\aj�ao\brx� x.l)kx�0d4:���_a8
 x.``kt`l4`m�`p�`r�`s�`t<`vL`�\n�n�
t�
��D`�{���X`oL�H���`el`u��x�`���`a8���`a��<���w.�`i������`rLz x.�=px��`l0.vH��XHo���ah�(l�1mpan�{o�7t0.v(F����4ah�6l�8m�3t�0v�8�t<ae@����hat����(aH/e�au��|aj�ak�{o�
 �
���a�L��aŌ���au�(8n�ar4bs8���x.�(abe�(i�bj���an(b��< ��� b�<���]o\���(aP/i��@bm�Pbe��ba0[d�:e�biX<k�bl<In�LpTIt[�M�DIjx8��$:d����be���bj<kx��bsH���bax��bl,cr��bapce�co�cr du\cvx�8��$ca�����8c��hcd@c�Lcr���lAg@Nr�F��`Gv��x��cj.l��ca�ce�Jg�ck�cndpd�`����cb�cp\Ns��$�0����O�d��oLzLFm@Nr�FĠ_|a(|e|u���4d�,��
��Hd��
��Xd����cjH��pdix�|dl�dn���daeeDeiXejlel�eo�erfu��x��dc����da��x��dk����da��er�dv8���da ei��tAr���Nt8��,ei��8erL�tPeux��dea�ee�eoPeu��lAgPAp��?kx����eg8���ei��er$#�dk���ea��ee�eo�lAg���\SuL�fz8���da��fr��$feEj|fl�frdfv8���-b��LfrdXfex��Ft�pfa��@n��fe�eoP$X$��fs�fz��fox���fnH$`<x��fppgthgz���fa�gi�gox��fbhddhe kf�gg�kh�hk�hl`im�in�ipjr@js�jt���ga�ke�li�vj8mo�<�<h���xgoH<��gl�<���gnx���ge�gn�go�grX$���go}x���ga\<���gm��he8hiDho��`��$hl��,hp�hgz�8��Pho,�Xhrx��gl�gmpgt���pha�he�hl�ho�kr�hu�<���hf��ga�hi���<��hj�gl��L��hzH��<Ldie�kf�Jg$ii���fp��8ispgthgz<<@�8��@it��Hir\��Tiexin�#��pie�8Qr����id�ietXt�#�xgo��ga�ie`���il�in�irjt��$pie�io���io@�hiSo8��$jh`t�8<x�,jr<��4ja`je�jp�jt4���Xji��x�ljz<tja��ga�ji�jox����jg\���jm���gm����ji�jokr��jc�gm�<��ie�hi�iox�hgz���ka8ki\ko�kr���cjPklx�H��Hka��gn,jrk�lkd�tko����kn��ko�kuL�x���ka�,jr����ko�ir�#��|Te�#��kglkLlldln�loplp�lr�ls����Ml4lu\�L�,lm���dkH��@le�#�tAr���Xle`���@i8 h�|lr8���Jg,ei����le<���lt��,r�#��ld mr�lz��eg,lm����lomuL�tAr���md8��mi���|Aa�pmfTmk�Tp`mr,mv���,Os8���Jg$jh���tXt�tAr���|mox��mk<���ma�mjnktnl�no�npot���ms����mkL��mnt�mu�.a�,noHnr`nu�mv����Ot� nn<nr8��hcd��@eXni�vL�@Nr��lno8��pdi��nr��Gt8���nix��nr<�na�no�nr�@n@z��cp��no�@n��noor��daL��Fp� ou��,orx�	8ob�odXpf�ok�om�on�oppr<pu���Doappe�vj�po�qtlqu��@le����ocmu��@e\��|mo�#��dc����oi�k���ok��oi`���olps��@t8��tXtL��Trx��$puD�0pgLpr8���ka���@le���mi�#�0JdXpf�pg�pk�pl�ppdpv�#��e���,Os�pvH���ei`��Uc���Ml��pbHqf�pgqkqpx���ge����koquL��cp`��|Te|mo0qs��@e���dc���<qi�8Qr\��TqbL�`qm�qn�qt�\-a����qd�ck��.l����qi���rj8���are�qi�rj�qopu���qr@�qe��rg�� rg4rs�r���x��rn��<��4|.`rahre,ri�Cn�BoXruL=x=��d
����pr�Txr����rd�
���r��h����re�&���rex��3tx���ra<��|Ek�Ep���rs8���riL�sr��8Nl0selsi���(sb�sl�sr�suL��ss8szH��HNj��`slL��cb�xsu��sj����se�sj�&��seL�$J��TQs<�so�sr<���sp��god�x��tz��tg�telti�tl�to<��(ttL��Ltu���Ttd��`tn\��xtm���ti�����tk+�tn�tp8���tr`���tex�>����ta4ui�mjul@uo\urL��tb�up������ uv��(un�Tmklur��PAp�Pue�eo8��<Ld���uo8���a�uexui<o�uu���ur`���ueL��h��/rx<|.8�ua,*c<veHvkulTvm\vnhvo�Eptvt|vu�vv����usx�vn�$va��*p��vr�,�'j�L|m�L�<|.�0d�vm��PBi\1��no���?k��ve�vuL�@Nr�@sH� wjx��vl�&�va�@e�@i(wvt���v��\�e�&�Se�@it��0w�8h8Qr�&�waLwd�@e�wi�wnqut��Xw��!�h�wj��� wjx��wn��tAr�Tvt(��w.t/���w.����wit8���w.�
���we�
��x�tB� x.$(xotH��@x.���HxitP��`x.�
��hxe�
��tx����w.L��w.���w.��4xn�"� x.��xe�xjx���xdtY#��x.,#�xet`�"�y.�yepyjx��ydL�(yg�Pye4yu��(ygth#�\y.,#dye�#q�#�|y.�#�ye�yj�#���yd�#��yg����ye�#z�#��y.�#�ye����yazeHxix�@x.��@x.t���z.8��HzaTzezi<zuL�z.x�z.��z.����x.�yatzuL�@x.�#�@x.L��x.t�����z.���x.�ya��x.���x.t�P�x��w.�#��w.�#��w.t���w.H��<{a{o0{uL��w.x��w.L��w.��t{a�weH{u���T{�x��w.t�\���{.8���{.t����{.��{.L��{.x��{.����x.�� x.�� x.h��x.t�L�|.x�|.��|.tqt�t�\��D|.�&�
.�|��,}�����������������X|�X|����������|�d|���t��0���|�������~������,��������������\}����8}�����������X|��������t�&�
.�&�
.��М�����}�d|���؉�(����(~�L}���$��8��8��X���l��d��8��Ԁ�\}�����}�����������X|��������&�
.��Ь�Ѵ|��,}�����������������X|�X|�L��������8~��}�\}����l~���$��������X|�4��@�����&�
.���đѴ|��,}���\��������������X|�X|����������~Ѐ~�\}���������������X|��������&�
.L�м�Ѵ|��,}�����������������X|�X|�x��������h��\}����8}�����������X|���l����$�*(�*�*L}*�*(~*�~*X*l�*d�*Ԁ*0�*�|*�*8�*,�*��*L}*�Ј��l�*\��d�*����*�Ѐ�Ѥ|*���`��d�*����*آ�l�*��,�Ѵ|��,}�́���������������X|�X|����������Д��\}����������������X|�����������d|���8��d|�\}�����������������X|���������|��d|�\}����������������X|��������L}*��0��d�*��0�*T�Р���&�
.؏��Ѵ|��,}����������܁�������X|�X|�X|�������T����X&�
.p�Ф��\}����Ђ���������@��X|����������l�&�
.p�А��d�&�
.Ԁ&�
.D��t�Ѥ|&�
.`�А���&�
.D���*�(�&�
.�а��\}����ă�\��H��������X|�������������p��L}&�
.X��(~&�
. ��L�Ѵ|��,}�����������������X|�X|�X|�����l�&�
.4����&�
.���&�
.T�Ј�Ѵ|��Ԅ����������؃����������X|�X|�X|�����P����X&�
.p��ԋ�\}����ă���������<��X|����������l�&�
.4����Ԁ&�
.���Ѥ|&�
.`���ш�&�
.Ȑ���t�*(�&�
.��м��\}��܄�ă�H��4��������X|�\�����������p��L}&�
.X����(~&�
.�����l�&�
.4�м�Ѵ|��Ԅ���������������X|���X|�X|������|��,}�����������������X|������������а���&�
.��А��\}������������������H������l�*`��\}����8}�����������؆���������T�И�Ѵ|��Ԅ����������������������������*̤�\}���������������������������И��\}��
���������������p��������������(�И�����Є�Ѵ|��Ԅ���������������Ї�ć����������*���*���~��|��������Ȉ�`��$��܇�������|��(��|��|��|��|�����X�\}��L����Ԉ�,���������ؓ�4��|��������p�������l��d�����x�Ԁ�\����t��h��0��P&�x!Ѥ|�t#���ĭ�8����,�����$��(�������L}�����(~�����P�Ј��؉�\}��H��h��p��x����8��8������|���������̉�\}��H��h��p�����8������|��ĉ���Ј��؉�\}����h��p�������Ј��\}��H��0��p��������H�Ј��\}��H��h��`�����8����h��	h������X��@��������������
 ��(����������P��H��8��|��|��0��8��8��������������l��������x�������������|��
h������X��@����|��ċ�̋���d|�� ��(����������P��H��8��8��8��8��������� ��(����������P��H��8��0��8��8��d|��
 ��(����������P��H��8��|��|��8��8��8�����
h��������X��@������̋����������(����������P��H��8��8��8��8��́�d|�� ��(����������P��H��8��|��8��8��8�����h��������X��@����|����̋�������
���(����������P��H��8��|��|��8��8��8���}��h��������X��@����|����̋���8~��h��������X��@����|���������}����� ��(����������P��H��8��|��|����8��8��$��4��@��L��8~��	h������X��@�����������}��
 ��(����������P��H��8��|��|����8��8��8���h���������X��@����|����̋���d|����� ��(����������P��H��8��|��|��8��8��8�����8���
h���������X��@������̋���|���h��������X��@����|����h�������|���
h��������X��@������h����d|����� ��(����������P��H��8��8��8��8�����
h������X��@����8��D��̋�������
L�� ��(����������P��H��8��8��8��8��8��H��,}����h��ؑ�����X��@����8��D��̋�������L�� ��(����������P��H��8��|��8��8��8��8��T���h��������X��@����<����̋�������D�� ��(����������P��H��8��8��8��8��8��8��؆�Ԅ����
h������X��@����|����̋���X|����|��d|��|��d|��|��|��d|�����|��,}�d|�����|��|����8}��}�l~�8��������t����Ј�����h�������������$��T	�؉0�м�����D��X����`��4��8���p�����H���X��8��������X�������8~�����@��8~��̒�(~��Ԓ������؉���L��������؉�h�����*��|��(���|��4�Ј���@������������0��؉�X�������
p��D��t����<�����\�����h�����8�����Ь��H����%�L�������8���������|�����|���Ј������\�_,��0���� ���$����H�_t�А��8�(������L��̋�T�Ќ��X������d�����������؉,}_Д�p���������P������������ܾ�Ȕ���_4��X��������������x��0���p���ш�����D���`�����l��,�����@�8~��L��(~��T�Ф�����`����؉�p�Ь��(������L��}������|������������(���ĕ�4��<$��*ЕР}��T��\��0�������L}����<�ш�����@����p��$����~������h$�H���$�؉&4$.Ė�P��h��h���*|��|������|����Ј������ ��l���������0T���Ж�0���ؖ��Ѽ�0�������������؉����Ѡ}��������������������$�4��L}��(�И��P������P��8�`��(�����}�����L}������*Ԓ���|�������*��Ј������$�H��%�؉��$.Ė�̗�h�����*���|�����|���Ј�����P��������Ę�$'�8��4�������L�����4$.���(���h��<$��*x�Р}����|����������4��L}�����L��l������ܘ���d|��Ԙ��������t�����(�ш������  ����Ї����� ���������H��؉�4$.���4��h��D��8%�%��*X��,%�|��l���|��|������������|�*���ܘѠ}�����������d��L}����Р�����ԙ�����h����������,}&�Д�����؉�Ė�4��h��(���*8��|��D���|��P�Ј���\��P�����8��t��К�8�<h��ܚ�����*���|���������|����Ј���Ě�؉<�����0��؉D���������T�����t�����`��l�����������\��8�����|�����L��8��؉�H��������X��\�_��Ш�� ������$��H�_���p�<(������̋����X������؉Lh��̛��*ԛ�|�����|���Ј������L�<\�����4�*��8~��$��̒�(~��0�Ф���@��P%������L�� ��؉�L�����
p�����,��ܥ��������������8���|����8�Z�|������|��ĜЈ���М�l����t��ܜ�|����8�f�|�����|���Ј�������������(�����8��X|&H��d|��T���������l���t����������tt�L}����.P������8�����8~��ĝ�(~��Н����ܝ�Ğ����Р}�����t�L}���.P�����8��$��8~��0��(~��<�����H������T�����d��X|&p��d|��|��X����О����؉����h�����X�����L}�X������؞�؉���h����X�����L}�t�����.���$��$�*0����8~��<��(~��L��H���X��8��d�����X����|��t�d����.p������|�&��Ј�������������l����̟Ѡ}��ܟ��|t�������������������X|&��d|��$��H���t��l��8��<��h��|���L������l��8�0�����|��l��8����Є��(���Ԡ���������P���t������l��؉�Ƞ�H�0�����8�����|����������������H����� ��8���(��������4����@�����P�����\�Ј���h��p��t��X|0p������$������X|�p������$��������������̡��������ء�8��������؉���Ј���|�������H���l��H��8��(��P�Ѽ���������l��t�̉��`�����h�ј���t��|������t��������(������$�H��������8��������̢�(~P��������8����(���$�����������؉���XH���0��8��8��d��T���D��|��������P������8��p��̉��l�����������������ܚ�����`����������ģ�0�����ш���ܣ�؉���|���������l��H�������8��d|����̉��`��l��H���t��8��H��|������T��������0��؉�t��d��P���0��8�����d�������؉����ؤ�����������l���<�.�������l�������t�X|_��p����������������8���`��(��X|_<�����
���ĥ����ܥ�ԥ���T��������x����_��_���X|_,}_ة�ă_@���_L�и��H�_\�_8����є������P������8�����~�����~���Ј���(��8��4��h��@���*L�����X�����d�����p��؉�|����@�T�������*���d|�����l������P���Ħ�8��Ц�|��ܦ��|���Ј������!�}��\����L}�������$�����&0�����@����؉-�9�}��ħ�d��L}��l�����|���G���������X|&��а������؉S8�-���̧����ԧ��aP�����8����T���\����������̉�������(�Ѱ���4��d�k���L��؉�T��`���`�������&l�����|��0�����ш���������L�������Xa���Ĩ�؉�̨������ب���a������8���\����(�����P�����8��$�����0���k����H��؉�P��T���\������h�Ј���t��؉����(������T�������������Ј������؉�������̩�؉yԀ���������(��܁_���l����0���P���ш���$��D��@�_��T���D��`����_`�м�_ԫШ��������|�������Д������Ԁ����Ѥ�����������Є������,}&̪Ѥ���ت������l���������Ј�����8�D��h�� ��X��,��P���8��8��D�А��D���P��؉����\����t���t�Ј������t���������؉���ф������8}&���`���ȫ����L}aH�����H��8����$�����\������Ј���������-����4�����<��؉�(���T��d���\�����h��؉�t����������,}&��А������(��0���������t������̬���Ԭ���������������������T����� �������P�����,���4���_,}�d|��X�����`��|���l�����x��(������d������8��������؉���������������Э�؉�ح�`�������&��������0�����̉������� �ј���,���_���X|_$��X|_\�М�_ȵ�ă_|��H��8����Ф��8��D��Ļє��0��؉������������Ј���|'����Į�8�	�$.,��Ю����P�������<����������������H��� ��8���}��������8�����L}��@�����X����d�����ر�p��0���������)�������؉����؉38���̯�(���ԯ�h��̯�X�����=�����؉���(�����d���$���a���<��؉��$.D��؉I���`�����h�����t��؉����h������*���T�������������d�[���Ȱ�؉�а�P���ܰ�,�����̉��������ш������������$��X|&0��Xk���H��؉�P�����\��������h�Ј���x��8������!�������̧�������<��������$��؉�ȱ����-������4�m������$�����H��������IJ�8�	�Ь��,}y����@��@�&H��T���T��0���`�Ј���l��`��x�����\����������Ј���̲��������d�at�aԀkP���Բ�8��ܲ�|�����|���%.��L}�������������$��X|&0�а���<���k����T��؉�\�����h�����t�д�ш���̲����8����Ѭ!��������|k���������ȳ�|���Գ�l�����P�����8��������$��<��,}&��L��L��� ����������D�������X��؉�`�����l��,}&x�а���������������L}�H������ܴ�8��|���ȴ�l����$.д�X|mp�����$�������������������8�aP���p��h��0�����8��8�����P��$�a(~a����H��؉�x��������Ԁ�����l�ad������p�����(��������������8��ص�l��|�����������(�����|��d�������� ��H��,��P���	8�����D��<�����@���������P�����8��p�Ф|������'����؉����8������(��������d��P���ȶ�8��ж����ܶ�,}&��d|����l�������؉���8~�� ��h��������(~��,���Ѽ�����T����\��0������t��8��|��������X|&���d��P������8�����������̷�؉�Է������,}&���}�����H��������8���Р}�� ��\��L}��,��8��P���H��8��P��H���ȶ�8��h�����t��X|&���h��������X�����d��P������8��������ȸ����Ը�P�����8���Ф|�������P��؉�����������X��p�����(��̹���8�����P���X��8��d������|��X���������������Ф�����������И������t������ع�,������|�����d��l������,}�d|�����|�� �Ѽ�����8��t���@�ш���L��8��X��������p�����x��(���������d������P�����8����Ј������؉�ĺ����к���0��P��Ԁ��ܺЀ�ш���ع������������X|&$����P���<��8��D����Է�T���\��́*h�Д���t���~��������؉����������t�����Ј��������T��ܽ���p�����������T�����P�����0�����$�������$��d���,��H���8��8��D�Ѐ�Ѽ�����`��0���h�ш���t���������������p������ܼ�@�&���Ԁ�P���ȼ�8��м�8�������؉���`����������&�������؉�d�Д��|��0��������|��@�д�����<��P���������8�Dp��Ԁ�8��������,}&���d|�����8�D���ĺ����������н����H��<��؉����~�����8��T������~���Р�����<����,�Ј������<��8��d��D��H��������́�����t��Ԁ��|��8���р~�������������h��������������؉�̾��ш������p�		��T���̧������Ј�����d������(��T��������@��Ԁ��H��@�_���x��T���`������������_|��H��؉�̨�ȿ�p������D��������|a���� ��������|�����ܿ������H�����8����������t�����؉���ф���,��8}&8��t���!����T��,}&\�ќ���h��H���IJ�8�	��д��|��\���|����Ј������@�	T�������*�И�����(�d����%.@(���P����'���8������l�����d(�$��؉��%.,�������<���������,}&P��<�Ѥ���h�����������P��������8���%.���,�����������d����������%.���ш�������������8���ќ�mT���\�����* �Є���0��P(����ħ�X|&L�Д���X��Ԁ��d�Ѥ���p�����|�����������؉�������������H�����X|&������(�a�����������������������H����`������������$�Ј���0��8�	���<��L}k���X��������X|&l��H���Ĩ�IJ�P���<��8�����,�ả����������д�ј����������h���!	p������4����&���`�����������Ј�����8��(���)	���@��X|&H�А���T��؉�D�����D���l�����$��l���kH������8����������������Ј������t����Ј������8����ш��������8����������X|&��8��P��h��0��X��<��P���p(�H�����IJ�8��T�����H���L��8��|��������������������؉����������t�����Ј������8�S���T�����X��d��������h�������؉������$��,}&0��t���<������9X|	d|��`������T��Ԁ��t�Ѥ�����������Ш��̉��̲���������������,}&����3	���̧�������$��������D�����؉���P��d|����l�!h��0��X��8�Ј���̲�d����0�a8�	���|�ш���̲��������a���`��������P���Ĩ�8��������t���ԧЈ������������������!����������X������؉��%.����� ���(�x���(�,}&0����ѐ���H���������P�����8���%.���h��$��H������(~������������%.��Є������Ԁa�������������X����������������0��̉����0��8���|�,��p������$���@��,};	d|��X��(���`��P���l��8��x��������8������������������$.���l�I	�������������p���������`��|�����������(�����d�����X|_���d|���������������}_x�������؉�`�Є���l����������(��T���̧�0��������ш����������Ѽ�m���������������8�����؉U	������|���Ј���̲���8�����}��X��(~��4��H���@�����8��L�и�ќa	(������l��d���t��؉k	@��T������0�����Ј�������~)���������؉&���������(��X|&���l�ш�)X)���8��������@��8�)������d�)����L�����T�И���`�����IJ�؉�x����������������������Р�����h��������t�����Ј��������t�����8�����8�3T���,����0�����T���w	�����4��X|&<�����H��8�w	���`�����h�Ј����L��؉&������������4��,}&���h�ш�������������	������0������X|�	������,�����H��	�������@����$��Ԁ)������������H��t�ф���X��̉����(~)t�=�������؉���Ѡ}�����L}����Ф������������������l�̉�����؉�	�(.��м��������0��D��P�������l�����(.P����8���(.8���(.����(.l�ј���\��8�)	������@��x����������������������(��L��d�����X|�	d|�����؉�	�}�����@��x��L}��������x��$��������x��8�����8�)	h�����4��X��<�����4�����X��|���x��������l���4$.p�������x�����t��������ш����������$��8��������������|��x���|����Ф���Ĩ������~��}�����x��<��L}��,��l�����@��������,}�	d|��d��������x��$���x�Р�є�)	p�����������	���������8������H��	`���̧���&�(.���������0�����ш�����<��H�����|��(���|��0�������x��x��t���P��̉��`������X|_@��d|�����0��������(���4�А��X|_������,}_l��X���_���������ܥ�ԥ�������d��p��8������L�������������8���$�������� ��p���x�������������_���8��,}_������ă_���8��ص�������؉�x�����(�����������������������8����0��� ����ш������D��@�_T�����0����_��_H�Ј��`��������������؉�X�����d��,}&p�А���|��H���@��8�������d��H������8�&�����`��������&���������0�����ш�����������������Ĩ���$��L�����0������̲�d��������0�����Ј��������؉����p��������(��������X|&��Ш��l�)���������������X�����@��	T������0��������������P��@������@�Ѥ|)����8�����$�)Ԁ����������X����`��������t�������Ј���������8���М�Ѥ|��������$��(~�����������Р}�����L}������)H�����H��؉�ح�������(��T��8���8��8�h�����X��\��P���h��8��t�Ф�����������t�ј������������̉������������ј����������0����w	�������t�����ш�����8���������$���,�����8��������IJ�؉�D�������\��H���������������X����������̧������8����%.������8�-p���4��H�m�!|������l�����Ј������$������P�����,�����<���m���4���3	Ї3	����$��,�����<��4��P���������h�������|�Р�ш���d�������������|��̧����|���%.��Ј���d������������8�������H���<���3	�3	�3	�3	�3	�3	P������Ĩ�IJ�8�	D��h�ш������������\�����|�И��d|���������Ĩ�IJ����������̉��d���������$�=P������8�����8������(�����Ф�����L��D��<��d�������8�$�L}|���,��`��l���T��|��X|�d|��X��t��X|_t��d|��������������X|_������,}_�������}_���a	������t�����Ј������؉�0��صф�����X����������h��0��� ����ш���<����L��؉��$.���h��d��0��X��t��H������8����Ь�ш���d����H���@�����8�������ă�	��������8������H��	0������D�ш�����8��	`���$����&,�����8�����<����IJ���P�Ѐ�����d�����������p��h��0�����؉����������h����X����Д������ј������̉����H�����@�����8�����P�����H��l��8��	 ��\��X�a	(������P����d���X��|�ј�������a	p�-(�������������d����%.���<�����h��0�����D��؉���� �����	���|��������������4�������-�����$����������̧�Ԁ��4��T�є��������Ј�����`���*p�Ј�����������������IJ�����м�ш���������3	����,��`�����3	H������IJ�8������ш�������������P���IJ�8�	(��D�ш���������p��h�����P�����,��`�����l�Ф���|�����H�����������������������,��`��t��8��������a	�}�����L}�����������؉��Ј�����8�������$��t���0�Ј���<��؉�H�ф���T����������<���}&`�Ь�����P��8�������@��H��������8��������t�)����������0�=������� ��؉����������|����,�=P���X��P��8��8�&(��L�ш������L})�|=����`����؉�h�����x�����l����������м�Ѭ����������������P���������8������a	̉�����������H���@��4��8����ă���� ��8���(��X|_���d|�����L��@��������IJ���d�������p��؉����h�����X�������������؉������������8�����X|&�����������������P��@������@��H���8��8��,��p����`�Ѐ�����H�����X�Ј������d�����������P��������8�&��Ф���P��@��������t�ј������̉��8�������ј������X|&���l�a	h����X����P�����8�D(��L��,�����`�����D������������h��������t��X�������L��؉����8��������X��������,}&�������d|�����H��	������8��������,}�	p�����(�����0�)����0��������8��@����H��h�ш���0��H�����8��t�А�ш���8��P�����P��L��8�&��Ф���������������ф������(�)T������8������������̉����X|&��������Ԁ��(�Ф���4�����@�Ѐ�ш��T���\�����0���d��̉��t�����X|_l��d|��\���������}_��Ј���̲���؉���ф��������L��0�����ш��������������\��X��̧�����&.�Ј���̲�d��(��8��8����	��d�����`��H���@��L�����8��x�������������8������D���$����&���T������0������ �ш������8��`��������&��,�������������d|��t��(���8��P���D��������8��P������	���t��,}&|��d|�����L}�����̉����������������X�����Ђ�T��������0������$�ш�������k	X|&|�����������8����؉�l��0�����@��,}&P�Є�����X�����t�ф���x�������������8������8��(������t�Ѥ��������������4��������p����	���������������������Ј��� ��8��,�ш�������D�����P��X|&\��d|��h����������������������t�������������
���������X|&���d|������=P������8�����8~����(~����������Ԁ��(��������X|&@��T���L��0���X��̉��d���a	��|��X�����������؉��������������������H������8����Ф|��`�ш������`��8�G���d|��x��8��0�̉��$�����H��,�Ѥ���L���_�������8��������p������4�����������X|_(�а�_�����ќ�_���(�,}_���ă_p��l�������IJ�؉&�����������d������̲�$��X����
t�k̉��,�����4�����@��,���L��8��������l��������4���y�}�����L}������������������8~�����(~�����H������8����������$��<�ш�����������؉�	�������8��(��ص�؉G������D��,}&P��x�Ѱ���\������D�������؉����,}&L��L���������������������0�����������H�����������(��t��8�����p��0��؉&�������H��l��H���Ĩ�8��<����P���p��h��0��8��X�Ј����������4$.x��4����(����%�<$�@�mT�������*��Є�����������Ѡ������ %��������؉&4$.���(������(��(�<$�8������t��(���,��`��P���<��L��8��L�Ф�ш���`��؉G���l�����x��t��������ш����������������a	�����������|���������8����������,������Ф�����T��|������������Р}�����`��`��L}��@�Фa	������,��`��8���h��������������l���`�����������0�����Ь�ш������d��@�_<��T��T����������_p���Ѽ�_0�������Ĩ�h��؉& ��p���0��t��������!(���T��d���\��8�����Ԁ���������؉G��������������X|&���P��������8�������������Ԁ���(.����є���`��؉#
8�����P������8��$�����0��,}&<�Ф���H��؉&D��ص�D���`��������0��<������|�������̧�����|����Ј������H)���)д����4��X�����H������8�����D��H�����8�����������X|&$��̋3	$�kP���D��8��L�����X�����d��P���p��IJ�8��|�М�ш���̲����l����,}&���t���������$�����Є���P��,}&���Ѵ3	��������؉&4$.H�����`�����h�����������<��IJ������)�����4$.X��H�����8��4$.x�И�ш���̲�d�������8��|)�ص����������������,}&��L�ѐ����������4$.��� ��t�a8��̉�������3
����0��X|_����(����p��D��,���T���_�����4$.D������(�|��%�<$�8�����Ȑ������_D��H�_��8����H��p����������������X|_T���ќ�_��,}_��\��ă_`��8��������8��؉�x�������P�������H���h��8��t�д�����\�������������<�ш�������������8�����X�����H���@����؉�l��(�����d����Ф�����@�&��T���$��0���0�Ј���<�����P��t�����H������8��h�Є�ш�����������(�kP������8����Р}�����L}����Ф���������И�����؉��$.̨���(�����H����������������� ��t��|���,�����<���?
���T��X|&\��d|��h�����������p���$.�)Ѐ��8���$.صѼ�M
������0�����ш���������8��������(�����P������8���д�Ѽ�[
���$��0���,�ш���8����D�����P�����\�Ј���h��8��t��؉����l��������t�����Ј���������~�����~���������������D����������<����P��ȳ��������� �Ј���,��8��8�����������������8�yh��\��l��X���%.t�����P������8�������̉����������8~�����(~���Ф�������������������0���@����̉����l�@�_p����T���0��P����_8����؉��$.̨�p���`���)��)�������������؉���Ќ��������X|&��������8�!	�$.����l�m
P�����@��`��������m
,�m
�������������8�w
h��,��X��4���
����L�����x��T����
���p��؉�
������Ԁ�����������t������8��
T������0���4$.��,}�
d|�����|���є�	�$.��Ф��������<��\��؉���D���,�����d������������P��~a�������h����������p�����<�������,��)��~���������������и�������X|&��t����������X�����$���!�����d��� ����\�������8Дш���H�8����X�,�k̉��t����|ј���������X����H������)�8��(������\���̧�t����%.�Ј�����@������ш���,�H����kT������0���4�|���ħ����l���L����\���h����t�,}&������������������������,}&���ф���,��P����؉!	��`�����������Ј����8���0������(�؉�0����<����H����T�؉�`���`���l��0�X���&|а�؉�����t����Ј�����Ї������,}&��ќ�����8}&�Ѵ����������������@� �������P�����8��LЈ�����؉���d����p�����������,}&��ѐ�����,�Ԁ=��������������(�=�a	�a	�a	�����P���a	���������	 �<�̉��������X|_�)��)����P��L�,���\�8��d����x������������؉�	��8�����`���0���(�������=������؉���(�����d����X=����؉�$����0����<����H���TЌ��������t���pЈ�����8�����8��l�������t����Ј�����8���є��
������X|&��d|����@���؉�
����P�h�X|&���������@����@�P�����8��\�(~=H���t�8��|������t����Ј�����8����H�		$����������������,}&��P������8���p������8���Ѥ���(����4������t���LЈ���X���dѠ}����p��L}��|���������t����Ј��������ф������}&��d|��D����؉���������X|&����L��������(��,}&4�|�����P������XѤ���d�@�*t�8~����(~����t�������Ԁ�
���`	���؉�
��p	�8������	��	�(��������	���	��
��T����
�d
�؉�		��(���,	�������d���<	�8��
�|�
����h	����
H���|	�8���	���
P����	�8���	�d��
����	����	�8~���	�
� 
�(~���	�H
є��
����
�@�*
�8�w	؉�
���(
�,}&0
��}��<
�l��
���T
���\
Р}��h
��
��
�L}��t
�X|�
����
�Ԁ���
�H����
�8���
�P���`	�8���
�8��
����
�t����
Ј����
�h�������	�T
�؉�
�p	�h��,�x�����X��<�����
����d���l����(
���X|&��8��
�����	�8���	�8�=��������������������������(�@���������������H���`	�8��4�X�0��
����P�h	���
����h�t���pш���|�����|�������l����Ј��
�������*��d|�����*��(����� 
�d�����,
�|���|	����
���������(
�t
�X|&8
����H
��
�Ԁ��T
и
���
(������
��*|
�8��
����
�,}&�
����
��
��|��������*�
������
�$�t����
ш����
�`�\���4��a	t��
����,�؉�4�T���@�����0���L���p��
���p���x�8������؉���D�������&��`���(
��(���&�������(~H�����8����l�P����8���H���T
�8��4�|��@�x��|��Lм�P���T
�8��lА���
���������t
����
�,}&��d|�������`	�؉���������L�����Д�8��4�d�؉��������H���(�P����	�8��@�4�L}�P���\�8��d����p�,}&|�d|����d�P�����8���������,}&���є��
������؉�	������*�И����8��4��~����~��(�Dр~���
�L������X���P�|���\����(�T�4����h�|�h��t��X������l�=��������������������������������Ј�����(����d����Ԁ��h��t�+����@����H���������t���`Ј���p�����	`�������&�������0�����L}��`��d������p���Є�P���t���8��������8����@�̉��������(�p���4�L}=H���L�8��T����`�t���lЈ���x������������P�������4��8����������X���ф���$�t�������ш������d|���������8�����h��(�X��4����@�؉�L�T���X�<����t�0���d�ш�����8���Ѡ}�����(�����d����Ф���������И�����X|&��������������8}&�p��� �p�@�&,дј������d���L�P���X�8��dЌ�t���Ј��������,����,}&�Ф�������8�k	̉����������8��
�����X|&��t����Ј����8���8��������(�t���4Ј���@�؉�L�D���X�����&d���P���8��L��8����P�����8���������,}&��t�����P���L��8����`�������&��(����0�,}&А��������X�����4Є���@�,}&L�d|��X�(���d����p�����(�L���؉�|���(�������X�d�����xѠ}����L}������4��X�������4���*�|���(�l����p����(���4�d���@�X�H���X�8��`����l�X|&x������Ԁ�����Ѡ������́*�є������������t����ш�����8�x�̉�����������}���(~��Ѥ��� ����,�`������t���H�̉��T���������l����`������|�������L�������������X�8}�h����X����������Д���|��Ԁ��ш���x�8���t��(���(�P���4�t�����8��@�(�8~���	�(~��h�h���X���дѤ��������������������(�Ԁ�����T���4��́*�Д�����8�����������Ј�����*��|���4�l���@Ф���L������X��ј���P�h��}������*��(�������d�����H�����8����|�����|����̉����8�5P������x���\�t����hш������,�|��8��*D�8���Є���ĉ�p���	����@��8~����(~���������$�8�AT����������4��0���*.��ш�����������؉)	��	p������$�����U	���0����8Ј���D��Q���\�ć*d�؉����8~��|�(~����P�����0��� �Ј�����8���������t�����̉��������ј���������������������&$����4�8���@��Ѥ���L����\Јј���h��������8���Ѥ����������И�����X|&��p�����<�����
�����,}&��$�������ć*Є��������D�����0�8��h��D���H���&TЬ�T���`�0���p�̉��|���� 
�,}&��t������[8~����(~�������������`�����p �� �� ���&����� �0��� є�gd|��, ��*4 �(���@ �d���L �H���X �8��d �l�uH���| �8��� ����� �|��� ��[(���� �d���� �P���� �!�8�&� �t������� ���� ����!�Ԁ��!�؉��!�8���(!��!�����!�(���4!��!Ѥ���L!��"����\!����l!�����8���a	(����!�d����!�H����!�8���!�d|��"���P�����8���!�@"����@�����!����"��,}&"�������t���("Ј���4"�8���"�������"�L"�x��t���X"и"ш���p"����"�����"�Ԁ���"Д�������"���	������H����"�؉�*.T���	�"��#��#��#�����#��#��#�0���*�"Ѐ#ј���#�d���,#����8#�؉�D#����P#�,}&\#�d|��h#�����#��#���������#�@�_$ш�_��_8��(*м�_���*.$$�Ђ_��������x��,}&�#Ф���$�l���������$�t�8��4$.t�8��H$.�|��P$��|��\$���H$.P���t$�,����$�t�8���$.(����$��*�$�t�8���$.�|���$��|���$����$.P����$�,����$�8��4$.���4$.�� %���4$.�}���$�L}��D%����$.���\%�$�*h%��%�8~��t%�(~���%�t�d���%.p����%�|�&�%Ј����%�t�����%.����%�$�*�%Ь&�8~���%�(~��&Ф���&����&а���(&�Ђ&t&�4&�T���@&����&���\&�D���h&�t�d���&.p����&�|�&�&Ј����&���$���&.0����&�<�*�&�\'�L����&�X����&�|����&����'�(���'�d���'Д����0'.����8'���&D'����P'�����'��$��$����h'�؉��$.t8���'.����'��'��'�t����'��'є���'.�����'�؉��'.���'.X���%.((�(є���$.��(�h���'�p���$.(����'�4(�����%.t+���\(.L}���%.�(Р}���'�8���%.���%.t8�
A؉�4$.p��4$.���� %����4$.tN8��H$.���<)��(�����(�0)����)����H$.����$)�؉�H$.���X)�0)����<)�t$�tAl���h).P���p)�t���h).X���%.d����)�8���$.���$.����%.����$.�����)�؉��$.����)��)�t�t]tjl���*.P���*�2022021021202120021002000200002002010020102012002000002012022100221022120221200220100200202201020022022012002002002222022131231202102021202021200202100202020201020201202020120020201002032020322023202322020302042211413202322123022122203200032000021021320020322032003221212121232213200213200021320002032000000	32000000032000003200002032000200
32000002003200313202132031302000302002043000	2300000002300002034021034002134002102100213400021340003402010340000034000034000000342102134003400021034000021340020340212140000002030023020230000023002110212302102321212300023121230000
3400002121
3400000000342121340002034000020102313421034121340002204000004000041031100214002104000204002114003141021100313421003402100342134021341034002203402012030230203023002340212321002230212302123002023000200210031230022313402202341213402034102123021002300000034000200341003412023210232302310211212302120230230230002123020102340021223002121000213403123400021450210030400020304000002300020	230000020450004500004521214500214500202321121212000312000212210212000020234221230021020000002210021100231100340213420120023321203210203002120300020300002102312123123022089880089889800	8988908009880098898009889080098880088988008898880089898800	898988800889898009888800898008988800898888008908080090808008889800888988002013012030120002002030200402020140130200	20302020120201202002020200	302000000	2010002013020000020030202012010201
201020302020130200000201
302000000020130200000000302000000000003020000000000000
302000000000030200000000000030200000
3020000000302000000000200	302000301	40300000040300000203000000000000002030000000002003040002010030400000201
304000002010020100000201
3040000000201	304000000
304000000000030402010201304000030400000000000030400000002010030400000000020130400000000
3040002010201
4000000000000	203000200
20300000000002030000020110002012030002010020302010201	203000000	20301020120300000000304000000020102013040000000000000030400000200304000000000200	100020301	304020100	30401020140000000000	400000000	401000301	1000002014000002010040000000200	40000020110400000301	401000201	100000301
30400000002003040201	3040002013040100
30002030002003000203000020304000201	20302010020203000201	20300020120300000200201000003012020301	30400020020304010201	203020300203010002011020120300020300
2030400000201
2020300000201203000000000000
2010000000201	30400030120304000000020140500020100
3040201000201
203000000020020300000000000200	405000000405020102014050000020140500000200203020120000000000	102010201200000003012000000020120201000201
200000000020020304020201
2030000020100	200020100
2000000000000
2020100000201	20000000010000020301100000304000201	20303020120003020100
200030000020120003000000
200030000000020100020301	201020301
80908080008090808090800090808000908080908000
90808080008080908080008080908080800080908090808000809080908080800080809080908000908080808000809080008090808080008090808080800080808090800080808090808000PK
!<Ni�I�Ihyphenation/hyph_sl.hyfHyf0tP�����'-4�������$�'–’-111001(E��������.�a�	b<
c�d�e4f<g4h�i� j�"k�$l'm�(n0,o�0p43q|3rp7s�:t�<u,>v�@w�@x�@y$Az�L:�|3�|r����v���a�d e<ixo�p,s�v�z�D�D�(9D�o�p����s����i�#�s,v���k�$@���4sLz �,�,Td�t�,\i0,��lb�d�d-|2T2�n42�d�v�0���or�/�/�sL5"5�n�2�d�2���e�7�7s�7��kp7�� eHiTt4&(8@s49)�?�?\l�n�?��dz,>��to�0+C/�B4�z�B���i$A���l����a4bxc�dDeLfxg�h�i�j�klm$n�o�p�rHs�t�u�v4yHz�Ĉ�	Lblr�	8�	��Da�6<lXd���`o<
�c�84
A�i�
������l�or8ulE-H��l�E�b�N�!S�.a,���l$o�$N�6<�6b�8���0r�4dgpt�8<��\aS<4�m'W�i�o�'8(8��n�[� �e�f$hHido�s�u��T!^`!���k4Wio�8�8(8l!Aa4el8�!To�!��<m(N�!c�!��\s�h49��pb8"Sxt�md"�.�g��|"r�"����xx�(m�D���nh�����"�s�#t $|�$'z$AW�(Ldpm�z\�x*S8ad*�@gdh���*S\i'W�i,�,S|i0,�b�,[�0�h�1W`3|3���a�deiou$x�
��@���a\
S�w�4�86��6�7�@��\a8o@u0,8�<8p7`�4:�t�L:��T�:�(:��l�4:L:����l	��:�f�i���;�<�f�k�l<=A�,>���m<@���iL@������D�a�m<<8�:�� o�@��(t�$A��@elf�i�l�o4	r`	v4��ohB�TBxg�B��B���a�e�iP%��B��B4�l�t�vC�C�|C�`C�b	r	��������,/��	a$	o�`3��C�,	aL	e�C|�CD	d�?@D�X	p�D�	m�D��D��x	iD�L���	a4�	b����	b,
n8
u���	a@
c\
dd
ex
h�
i�
j�
k�
lHm\ndo�r�s�t�u
v$
zP
Ĵ�@*�X*��
�$ 
ĸ&<
S�
���H
��S�p
v�4�� �
.� ���
a�
e���`!���
l�
m�
t�����"Sh�P%�
pt�$���
e$i�:��a(��%[l8t@v����'Ti�'��(S0,�c�r�vp
N`
��xm4-�hl/�,/���d�/X	p4�`3���b�s��|3���aelo��H���������5�,g4i<r�4zX�<W���,
Ded��L��6��6db\��xad7�d�6����p7S4:�L:����,
��:S�
<=���i�u
v�<���k
r�$8�$8D78�=��
y,>S$A�D<
A��4
c`
h|
k\n�t�4��X
.p
m'��\a�"A�
a�
o�
s�
w�L#��.�
v�#�#�8�@���
e�	��
E.�
b g�
i(j0k8llmxn�o�p�s�u���
�<� #�"�$#Le�(P%Dt��l'��Xs'��`e�(//0,���p�0p7`=/�<���p�	S���b@
c dLe�g�h�iPj�
kll�m�n�op,r�stt�u�v�zP
�h�S@v�<�?��,j�S4o�Etl�m�z�%5P%`mhe�'|�'���n��i�n�'W<<��i<S4S|
8H�k����cns�@��do�F@0kDp�8����(r�2N�8<r� S�KP%Xt�$��`e�i�o�%Q8t@v�&�'UxY�(�c�p�rs�Ĩ(���ae iXjxo)_()�����f�l�)rdf�*b���@v0z f�x���8�� ��@�+��Lal	����d�`+��l�0,E�b�f�r�vH
�P
�����,�d��\
���l/,/���d|>m�>�����/z��0��0SP4m`3��z|3�� aHeh�Xn�4<p`v���5|L:�����:S�e(;�4;�s@;���k�=W�o�<���r�u�6��<���m,>S�iL?�$A����adb�c|de(fxgx
h`i�j�kl�mnohp�r�s�t�u�vdw�y�z��d�	tj�l�
&<$[|z�
���i<
�
�dlo$tPu\v�������e�i�oL��8��L�8#�e�����p�:�8i o@r�;8`<8�����Hp,>pr�h|3��hs��f�i�l�o�vz4W�����g��e�r�s�E�b	�d
 -N���S���A��b� ph�48fTt�WDed�:8ALa��p�z3� 3��pi$xz E�e�� �"�m�t<����l�!S�a�4d$��eX$S�r'�(0,(bHd\z�,�, j8r -lWd-@l�040Tn�0�n����tk�(��|i`3�z|3���ae�fio�rud4�P4���l�r�v4	�`	��6�p7dt���\a�:m49��a4i���9��,.HhPlx
K(K4:tpL:��X�,
��0��o42�:�h�i4��������t;�Ő;���n�<,>eh,pPs(?�?�l�>���t4��a�2�2��e�?S rX9N49��8e\v@��@t,>m�@A�i�l�t���xd����nP%�$���e�:W oA��@���w$A
dg<i�l�m�o�rtu(�B�A��jr,�BE(l�$8��ToTB0jdmps�z�!N�'ml\npB�|t49N �B�o<C<(C�m$<����n�!S�a�o(W`C�b�r,/F�C��C��e�/���Dm,
���� ���
�
ade�f�i\n�or�st4u��l�h&��ptxj4��p
mCN4 �j �l����z��=���i�/�r0,���u�&�4�u|3���e�:A(v�>8,>�� e�<��\
d|e�io�t4u|N�\e`dg���pi�l�nH
�P
������H
�P
����+�����j������r��p����t0,v0��/z���8
u��
(ale�
kxl\n�r�t�uP
�h�`���di�$���
.�o�&���4�n|3���e�<�f���xm��f���a@bHc�dDe�f�gx
h�i�jk(llm�no$p,r@s�t�u�v x z�Ĵ�	<
Tsp7W�#�
��\a�e�i�r�u�v��d��#�
`38|3���a�#|38,>���r��o��E�r4<�h�"��
�a����$\edoP�0H
�<kP
��D�P%�&'xh4W�i�(�d�e�o�p�st��d*�*x+"`+���s(�7-�+S�e�3k�&�����"9`<-�+Sr0,�0|38e�4p7\axe�i�k�s��6h���dt�7��lr4@(8�s�$�\8���v�EL:����,
��:�p�28�0���r�<,> j�?F�?S�o�@$A4 lh m� o� p� r� u�BD aT i�BWCJ�B4L zl'8�!S` e�oLP�CUx e`C� dS�o�CSD�� r��=��� eX$��� t���� k��� a�b@
c\
d`!e�gl!h�!i�
k�!l�!m\n�!op"r8"s�td"u
v�"zP
�h��#Z�L!s���T!k4Ax!i�l'l�!e����!m�!n���!t�$S'S�,|0,���!b�!d�!s"�d-U�!llx/l	^���"�|3S$"a`30"zP4)p7SX"i49d4D"t(8L"s�<�"d�"s�"Ĵ"Řg���|"� =m=�"m�=,
q�=���"�$AS�"v�<TD���"s@D��"e��@
c\
d8#e�
i�!mD#nL#o�#r�#sX$t�$u�$v�8�$#i���,#t�(0,`#k�#v�#z4.ul#d\
0@H�/��t#e0��|#��/�#ŀ0x�0���#o40�#l�4|3���#ep7��X
.�#a$c$p $t������#t�7��8�$o(9�494$.D$aP$eX
|�:��:��<$zX9��:Sd$r`<�p$a`3�=���o�<��x$r$�L?���$p,>���$i,���$r����$iD%v���$a�b@
c\
dP%e�%f�%g�h�%i&j�
k�!l�!m\n�&op�s�t�&u
v�&zP
��&�t@��.�8%z�d%e�%i�%mxN�l%z`t%p�` e4S<S�%��
��a���%��[�%d&zE��D���%.h���%���%� �&d�A�� ��X
.<&cX&k`&nh&s�&uL&�x&�@
�H
�P
��D&��
E\E8"E��h��p&�P��"�&jd"�&d0,�&�����&�L:�����&�<=���i�u�<���&k$AS�D����b@
c\
dl'e�%f�'i�
k�!m�'n(op�s�td(u
v|(yP
��&��|P'n�'o�'r���X'dx/<�Ex's,W��d�'p������'n�'t��4���.��'h���@v�(���'i�H�:���'.��'t���(�0,��D(v(��/���%.0��,(��/8(�\��=��P(n�<��X(r4���i�@��p(h�H)d�)j�)s*v *z()����(a�b@
cd*d�*e�%f�*g�h+j�
k�!l\n`+op�+s�+t4u
v,y,zX*��&�����)l�)e���)�XF���4)j�<)n\)r�)ul)at)e NH0�8��|)r���)a�)e�)o�)u� <� ���)k^����)nd�)�����)���H�t@�*e�*z��H#4*o	��,*r�
\a�e�i�u��@*���x*g�*h�*m�S\a�S�i�<��*d�*h�*z|�x
��x��*m�*v@D�<S�*h�*v4Wa,>W+iL?8� ��X
.0+cL+eX&kh&sX+vx&�@
EP�8+s`!��@+v
�0,���rx+s49�x/p+tp7S�+i4u(8�+s�:S�+e�+g�*v(;)4;�+s@;���+k<W\a�+e|8,3�43���+u�@���+q$AS,iTB���,s��$,a�,b4-cd-d�-e�f.gx
h .i�j4.kd.l�mn�.o/p,/rx/s�/t�/u�/v,0y40z�Ĉ�H��	�,g�,i�,j-l -r,-z<��(lL���,e�
�,d�!8�
���,o�$��
��-al����-o$
�<
L-k\-r8#8|
AD-e�i|3W��-d�-n�-r�-z�-�@ S�-v��
l����-a<�H�-p,���-e�A����-dh���&��.lP%)�-e<.l�$��,.z ��"D.b\.t�	WDaT.ed
X$��$�.a�.g�.r`@�D%x.t�$���.v��%���.a�%���.��%S�.�|3W�.e�40,�.d�.l/md-�!ld.S���0/m$/y'W` e�@�|3���al/deiou�^���L/�LET/�\
S`/ep7�/edK�7���/b�/m8��:�<�&k,>�/s0z0�8��7���/m@���/eX@�<@���/kL@���/�t@�0a$0d�@�A	�@$A\0bl0dx0g�0l�0n�0o�0r�0v�A
B�A�d0jB(C�0��B���0oDCLC���0�D#�0iH�����0c�0ŀ ����0�`C�C@D&��
@
c\1e�1h2i�
k(2l42o�2r�s�t 3zD1�h��
)P1k��81�0\a��1k�1t�1v|1Ĥ����t1�����1t�;�1e��1l�;�8+s�1t`@.p7��1o4���1s�88� 8 �1o����1z40/�&2z�$��2o0,T2d�2l�2s4���ad-H2fl2l|2n�2ol<�-�-��t2a�2����2��E�2Ĥ,<�&�2bd.�2o\
h49���2dx/�2t�5u�4�2z|3���2ehB��.TB3g$A��3i�<���,3u�8�@3u���H3�����3e4j(4vP4zT3���`3a�b@
c\
d�4e�f�g�h86iPj�
k�!l�!m\n�6op�6r�s�6t7u7vD7yP7zP
��6�D?������3n����3��4�t@D44a�4z�@G�^�<4dH��D4id4lt4o�-	F�l4r�
�4a�4c5dl5ix5j�5k�5s�5v�5z�4�L��4l`
��pt��4h\���4v����4�H
RP
���4�|,5iL5n5ĤY���$5gd"^X��85u���@5j�	N@X5a``5b��,o���5m��8a��5d@�P���5k��5s)d()���5��(�5�D#�5a��5n6u,6v�=<6sTDN@D�$6e�H6n\6z��T6o�� x0gp6lx6n4 D#�0,�6b�6d�6z�,k�,�6d�,�6i�-d-�6i�0�40�6o|3L:����d7��:S�*h<=��(l�<��7k,>S$7j�?o�o$���07n�@��87a$AS\7l�BE�Dd���b�7c�7e�%f(8iPj\8k�8l�8o�8p�s49t�9u:v<
�
.�7i�A�#F�#���7a8i��7s����7k8m8v(8�\a,�8p�<8dP8g4s���(���.�D8n�"��X
.p8n�8rD#A�#s�#��x8eD%|�$���8v�$���8a��8i0,���8n�y�8�d)����8��09l(9od-/2�8d(2���8ol2{T29l429d�:��X
.X9el9f�9iX&k�9m�9r@;�d9n|�|d���t9��;���9m�9r|9�lK,|�!E`<#�9a�`3���9.�<�9bd��<�9o?�>���9t,>���9e�
A\:.d:kl:n��:���E.|:e�:j(:���4:��D�E0�x��E���t:s� �@���i����:w�:z���:a�b;c\
d@;e�f�;i�
k�;l�!m<n<<o`<r�s�t4ut<zH)<
S;h0,`
��;o $k�#�� ;t�(;s���4;k\;mh;x���T;a�@��\a��d�;o�'p�;s�;t���t;n������;s���S�;e�`+�<���;o��;nP%�;t�$���;e�)r�(<j�(��<a0���%��/$<�0,��0<v4�l<r�6SH<u|3��T<t�=N$A���
.�S���<a�<b�c=d,=e�fxgx
h4=i�j<=kl�mn`=p�=r�=s�=t>v >x$
z���=�	tj�<p�0W�o� =m�'���.�U=i���"���
aP=eX=o8#L#��0�=�P1xa81)l=kD1��x=�|3���ae�=gi��p7�=p�8�4:�=eL:���=�,
�t:|:���=s�:>h4W;o,>����m�@��>e���b@
c\
d�>e�fxgL?i�?j�
k�!m\n�?o�?p@s`@tt@z�>�L@��
\a�>e�i��|>��#�>r����>l�>m����>����di?t�>�l��|����>n��>i(?l�;��;?t�;��?e�`?a�4?v���@?dh?v�?�����<���'.,
p?g���|?�� S�?o�!�0,���r�?z�0���e40�?l�0S�?a�?i���|2���?j@l(|p8�\8��@np7��@k�K|:��(@k4:��0@eX@kL:��<@��"�:Sl@k�

$A��@a�@g�@k�@o�@p�@uA�B��Bc`C�xDA���
a�@o0,����f��@e�%f�jlAw�@��An�Au�Av�Az��Aa�Ab�c�AdBg�hTBiPj�Bk�Bl�!mD#n`Cop�Cr�s�/tDu@Dv�zP
ĴŘ*�'.$�Ae�8��Ak�?x��ApH��-d�	S�Ai,L�
�Ar��BjBvd"PSBu�x<4BnHBo���(��,Bi�/�@Bt�hBgpBs�B��@U:�(:��xB���BĴ���B��"S�Bu�$��$���s�$���Ba�BiCj(CoXCu�%4�BlCtCvCz(L�K�K&�&#.L�& Cg<CmLC��l	����DC��&0,|Cb�Cd�Cg�.l/m�,�d-U.�|3��CeDiDu�4�5���C�5�CĀ4�Cd�Cz�C�X�d���C��C��5/,
�867��<(Dj0Dp8Dz��`=U$
U,>�TDe`DnpDo�>��d9n\x���?��hDj�Dk4.|��E.�
b�Dc�:j0k�Dm�D�<
�
g���D��''���Di�
����D�4:�L:���D�E��D���50106001100113040151045067041415010161215006010200206000060002104002145102310621050002310031040106500232540610010140005020041041103003013401054010431005100510111012405002304210050103010500005210301004205000006050050120001320101000403010400106500104304123245005001022123043010450100450104300041010450010043010040210045210232054260064300212520405104021030003065041210065062323400511040145454100610125046210051210014000050404001123220013431012103000012100400000101422101104560202110404401006020100530201146014200060043500268000101045212421022100560101004121050000106010043143232425021405020500016301065210650004545000522031224025036010012050100222302254451003000002011526220300105021006510600016001006103204100154402080002020104343104300004321060000005111001006032005200220321045002001015300100450210040010088000PK
!<���X�Xhyphenation/hyph_sv.hyfHyf0tP�����'-4�������$�'–’-111001�J��������.�aH)b�0c4dAe�Rf�Zg�fh�li�{j�k̑lȣm�nt�o��pH�q��r��s�
t�u0,v4wd4x(5yP9z<:�|,i<oHr���
xbPcXd�g�k�lm`n�p�s8tdu�v���a�b`c�d�e8fgph�itk`	l�	m�n,od
ptr<s�tu�v�x�y��T,�d4l��Lhrps�a<$xa�e����rXa'4�,����lĔ0���e�p�u �0����r�.4@�8�r\���b p,u��<�@l<�`;DP�I4rd�M<at@Hr���Td�e�g�s�Q�Ixs<�4����rV�e�o��Z�^� @����p�$b�e0$���kt@Pe��k�t�%e(i�o�%un�%y$'�&~0aHt (<L)u$(��Pt�(��Xs�,D�(Ipl�(��xa�s|1�H)��a�ei(oh��)���s�)���k`I^�+���s�*���n��q��s�p���dT,��l�-b�-�� k@r8.� .��8t\w�2��Ls�0�Ti�y0�o�6��ppp6�xo86���l4b�k4��è@��@���rh@�����E��D���vA���klPn|r�st$v0x�E��E��a0i$�Q�FI(n�IZ`I4<tH��Dsh�xJD�J��`�L<�K��ti�Mb�k�t���Nk�d�l�phNb�aT��`��XO�r ����d�O��a�P�@P���r$-�dR��n<R~a�R�R�`e�r�àA<�A�Lr�S��TbPV�\VIlmhV��ta�e�V��VI�s�C��:I�lx:���epY���g|Y�������Y@�Y���ae�Y���r�Y��YIn�Z�Du�eD�e��$��e��,�Xeb8lTs�ehGb�h��\m�f��de�u(�jb�a�j���sPm��l���b�k�m�n\sto�l�<Lr���p�r�ad$g0k8s@t�r4(s<<�Dxs��r\sVet��t��u<��Z�w�Ha\w��Pklo����a�o 	rT	���4�k����s|b�nq�����i@�b�l�r��؈@�sx����t��,	s����g̉��	n�$	i��M<��X���4	s ���<	pȐ��H	�̑��	a�	e�	u�����t	r����|	k��|���	r0����	tĔ���	kD��,����	t�����	sȣ�$
aX
e�
i�
j�
o����8
r����
eܤ��
k����
sL�����0
i��4P���D
e����L
rT���sud
s���l
i����x
n0���E@�
s�E���
kP���
l�~���
��{���
�8������
sĨ���
n$<�����
sD����
n4s`���l�����=���~,tp��8�b@o��Hk���Ts����`n�����xr�����kD����r���e�o������r��H����n�D8Db�r����dؼ����T�4�4uH��kt���	 c`k�m�n�o�r�sP
t\
u|���~Xtpv,	�������xa�o�s�u���D�
<�0������k�=P���s�����d������c
k
l4
t@
v4�@�!��� 
a��b(
r<���`���H
r|�~����
a�
i$rhu� �� �|
s�����
p�����
p�
s�%'����
e�
i�����
t�%'�y'��b�
ih����
t<����
sX��P���s\�Is �,eho/p-��0a� I8k� ��DiL ,Pl4���\b����e�uy0��C�X�~�r�����g�s�O5l�b�e<����t&������s@����n`��7���d�b�mJ:H�~e����vT���$����la�e�i�j�k$l0t�uH�bT���dk�>��bxr�����g��A@����ox�h�������b�À���o �<����sL����t��b�th�D����sT��t@��u��$Lale�r|b�IDm����Xsu`n�J���xg�P�n$�i�&<���pt���p�
��a8eXi�o�r<U|,�l�
���bk�l4�YLOTbaD�� tb,s�8��~Dr���Lgtl�oP$�ll����4	s ��p����p�B�$�b�s�I�d�s�$�e��<�`\���k����t"���kDldnpp�r�t�#a$�0r@#��8tP%b`%VPe�$��XgP&�d'4t'��xa�s�'<�)��a�r�s�)4�*e�*I�r�*>�e+�0,�aTexø-kl-���t�+���r4t��lun�-�e�-~(t�B^Bb@s.��Hd3b�s3��`g�2��l�?<d4��b(5���a�e����<���g�9���n�rs0t<:��P���p�=o|<���s�=�P=��t��=r�=��$edBhA��<o�=��Dr�H4G��\o�C��drH),be<ldo�r�s�u��|b�cLd eDf�gdhpi�j�k0l\m�n�o�pH!r0$s�&t�(u�(v<)�/4P*0�u�*(l4s(Gk@+�� s�+b�,~Ha�,8.s .��Pt�-Xr�mx/��pc�o�.��xi��}�/0p/�c�k��4�/����2u�sT24�eis,u�0��k\wD�2�s���83�k���L3��$p�4��48g4~@ate�j�o�rs�6�x6ulk�l�p�6�87�9<�:$:�pL;o;I�n�:M�a�e�iu�;y�s�;��p��;���l@�=�BZA��dPT4TM,o`øR�4ftr�thT�pT��X��V�hV��li<DHX�r�W��o`ZI�r�Z���a�egl(o<rPu8!�h[���a�[I�.�P�`�^��r�_@�`��`��dt`kr�a�`$4o�eDXekHr�i4�f~\j�l���b�v�z�{~|u�r����a�e�i�n�orhs�t�u�v�$�������t��Z�Ĭ<@�k\�@�Im�$a4iHo�Ả��,s��<���@b\��8�Ta0��\t���ČItg|��|i�r<�����i�I�-�P����i4����r���a�e��|<o�9���rtȐ������=� ����Ipd̑��$a�i�jk\l�m�o�s�tu4v@yX��D�~hmtm�Pm�|rd�I�b�n�s$�P�.�e�s�o���t��o�yI�kȗb�i�����t$�4���|kk��@a,v<�����DȐ��4�ܝu��@Ht���Pst�̡�����l�����4�k̚��s �I�r���ܛ���t���M�ah�@�p(������r�u�x��D������I$s,�A��<p��,sH�k0����IHfȣ��Paxi�p�s���l�@�rH���=�D����t`�������D��d4|,�o��I�b,l@r���
�aLctd�eg4iHk\o�stt�u�y�ø��Į��$fT�8���8k��Xi�A:���`k�@ha�e�u���6��6���s(�Z�l�$D4?�nh@��@���n�����$C�I�f�]4����s��V�ei$rP�u����zp�k,ud�>��@r��bH�ITipkxp��0�D1��,�eз4�c�i�j�kl p,t`v�� ����t��k�s���x�8�b�u�����i4���Ir�,a0�k<oDyxD��$�,Lr�,Ta(�$�@lj�r,��غ$�e@�I��Z��ؼ����t�~8�I�. n����
�e i( lT o� p� r� s!u!y,!�1<�I�D���� n��b a��4L�b4 r4��< t��IH s8!9����` a��Ih rx��t a� s��<��� k�mA����� c� f� s �,� i��A�o<��� e� l!p�����F��A4�I!s(5Z������$!�\���!n�!r����8!a�!b�!d,"eL"gX"h`"i�"k�"l�"n#o#p(#s�#t�#u$v$y$��������!n$����!v�5I�!i�!o�!rT,N�-T�.�\����!r"s�>Z=4"t������"d��I "n�b8��8"i�@@"sD�Z��I�"g�\_4o��l"n�~t"e��u����"a�"l�"r�������"n���"a����f����@�".�"a��k��I�"l�I#b�p��fT����� #aP#kh#vT�u��k<#l���D#a`#l��{�M�D��$p#oT�@x#r�#s�#u|>Y���#p$D����#r@�D�#r�#st'D(��3�3���#g�2���#���@�#��u���T���$�T������($ax$b�$c�$h�$i�$k�$l%p4%s�%t�&u�&v�&y�&��*	���p$e��������A�����$t@�I�$sX��0��$s�����$i�$o�$r�$u���p��x�@���,%a %i<�}����%g��l��,%kX%od%pl%t|%u��Q�IP%p�M���Q��It%p��%r��b�%a�%e�%i&o&rt&��!�����%d�u�%r����k�%r����%e�I�%k&o�Ax�4&aT&o����,&kD&lL�l�����L&l�ol�`&r<��h&�t�&l�&n�&t@#D���P�&d$�<F�kp	Q�	���&��&�$
P�
~$'a0'eL'iljX'r�'s (tx(u8!Q���'a�
I'rZL_T_��8'��I@'���x'a�'e�'i�'oLe�p'l�'r�.�����}0P�'a�'c�'e�'k�'sh��s0�o�e���".t�Hb�'a���'kL�TI(c8(k�(a@(jP(o\(s�P���D�PIH(s0<�����d(g���l(n���(b�(tL Y�)��)���(a�(o�s��*I�(m�+��(r0,���(a)g)i)r()ø-�l-���(tt/�|/�<1<3��2�� )��9<:��4)���)aP*b�*d�*eD,fL,hT,i�,j�,k�,l�-m�-n�-o�.p�.rp/s�/t�/uD0vL0yp0à�)d�)k*n<*s084L~�)i�)o�)s�4
���l�)o*u���4H�l���*k�%4�%b$*aH*u0$��,*t��H)0t*a�*b�*l�*r�*s�)D�)l*k�*t (��&~�*tP*��,�*e<-D-I�*r�.�/4p/�*o4A
+d(+e0+k@+l�+n�+r�+s,t4,u<,x�B�Bb+r�BA�D~DF��E��8+dX+u`+yh+�<GH�u\G��l�x+� �����H���+g�+�̼��J���+���,�M���+��K���+��M~�+l�+s�+t,u�NO^LO�XOb�+a,e�O�$PZ@P���r,,v�Q�Q�R��R�f��lh,dx,n�,sn>�e��r��p,vxu�w��,o�,v\w���,k��L|�{���,e���������,r�,t̑���,a-eX-i��G4h�~-oĔI-m,-n<-rH-s|��xK�����4-aЕend�IP-dp-k�oܖ��h-r�-s�ȣ�<t��-g�-k�-m�-n .rh.s|.yL����~�-r����~�-l<�I�����-u�����Z��b.e���.t����.s8.t��4��@0.iL.rT.s�����`.l��~t.c��0T���\�"\�I�.f�.r�.t�����.a/iH/uT/yd/�����(��~�.i��0����..0a/e/s(/Ð����.k��/(�6�p=�p�� /�L)�(���4/t@���</s�D��eT���\/����/c�/l�/o�/p�/t��,�e�/h��U���`����/n@���/a<���I�/f�0��$�/j,��
� 0n80s����$@0s�$��0dl��l%t$(��,0s0,(5uX0r�7A�9��n�0t<:���0�`0��0��=��=���0s�=��C��0r�H�4G���0s��0a1c1et1h2iT2k�3l�3o�3r4s4y������0p�0FA41n`1u�I`I4 1tH��(1sL1tJIJ@D1rR�Q��X1s�D�f~l1.�1a�1e�1o�1t�1Ä(P,g���1u�hV�h���1fH�\j���1c�jb�fg�k���1shk���1�2�Ll�l��1r�lI,2l82mH2pr4�p��$2uLr��v��v��@2p�4�2a�2e�2i�2j�2l�n�2o3r83sL3u`3v�3�|u��u��^4���2i$����I�2s@�u�2rH�lP���2nx�,�2d3g3t��s؈��>3e��4����$3a0��,3l�A���D3s4���X3a�e|3�$�����t3���Ȑ���3��3� �AbĔI�3ȃ���3eh�bt����3a�3m����4�3ol������3s��4(5k~�4a�5b�5c�5dx6e�7f8gL,h08i�9j:k:l:m:n$:o�:p�:r=s,?t4?u�?v�?w@yD@zh@à�4.�4d�4g�4k5l5mD5nP5p`5r�5t�5x���yL~�4r,b����4s�P�4o���0���4i\�$5Ü����5��_t�05.���85d���!�H!��X5bp5t�#�X'��&~x5r�5t (�d4�H)8�0��5e411�5n4@�5d�5i6j6o(6rd6s�5�,908�5s�9�:D$:6s;�:M 6a@6eH6iX6�;�;�<e�<��P6�=�p6v?FAu�6k�6l7n87pD7r�7s�7t<,x�D��6oE��E��8+d�6s�6u(G@ #a�6t�ܝ��6iX��<G���6tH47t��I@'�J@7i(7o���$K�J�07r�K�\7kd7o�7s�7u�L@�L�p7ll����'XMbx7iM��7t�M4�Mb�7e�7p�7t8N�O�OXOb�7i@P4�r�R88�Y�Y���7a�Y��7r|Y��8��Z@(8l�_�l`8at8e�8g�8k�8n9o,9s�9t(m�xl~X8s�E�n@l8l�`�o~�8rhouto���8a�8udph���r���8f�8g�8r�8s9t���\sV�8oLt��t�9pu��u�v~9d\���\w��$9c�jH9k`9pp9t�w��,oX9rp��xx�yybh9o�9ry�y$�9a��dz$�9ez~�9r�{��9o�9uX}��|b�9r�&�P&���9p�}��9p�9r�#��̑^ȣ@�@t�H:ld:m�-n�:p�:r�:s�:y��T:o�(������\:r|:s<��D�4t:k@�D��I�:k0����:aP�������:d�:i�:t�������������:tT�F��@��M;a�;e�;iH<o�<� ��\�I;d<;gL;n`;r�;s�;t���������D;dX5���X;bxK�����l;a�It;r4%��;e��4�;sx(o��~�;u|De��I�;j�;n�;s�;t���<�ox���m������;c<f <g(<k0<n8<v����}0��l��H��H�x���@<ch<pt<r|<t������`<p ������<t �����<s���D�<n�<t�����<k�<n�<tT����<��<������`C�������<d��4T=a`=e�=f�=i�=j�=k>l(>m`>nl>o|>p�>s�>t?u?v ?ü��T�IL=n���t=n\��T���l=h���@���=n��@����=s�������=e�=i�=l�=n(�V��k�=f�=n�D0�����#@�8>oH�D8���>tĨ48�� >o@>���(���8>�p��|���L>����T>������,t>ll�
L��I�>l�>t��b�>a�>e�>i?r�(��>aH.���I�>g�>n���$�>ot�<,�	���&��
<�p?b|?p�?s�?t�?vx �� P?e� 0X?lL ,d?bP&D$(~�?c�?t�?�|(M�L)b�?e�&��)���?��)DH24�+@�?u0,@�?r<1u4<(5u$@k8@r�D|k@n86��@aP�4�7��0@oP94x:o�9��L@g�@s�@t<:���@�T@�A��@�P=4�=��>��=���@g�@rhAb�C��@d�@g�@p�@s�D:8D>�@s�D��F��I�I���@tlJ��~�Ak�Am��Aa�Ab�AcBd�Be$CfPCg$Di|Dj�Dk�ElhGmHn�Jo�Jp�Kr�Ms@Pt�Qu<Rv�Rx�R��D���xAe\4H)��Ar�.��Ai/���l�0��Ah�AiBot1U�AiTI�n��Af<ib�Af2ILs�34bDBaXBdlBi�j�Br�Bs�B�D5��0Bs�48Bn(6M�5@PBr�z�08dBu��o;IxBl�:M�Ba�=Q�$o=4�Bk�Bl�Bo>l>�T@�h@���B�A~�Bd�BiCkCnB�$D4�DH��l1.CexH��R�4CsDCt\WkXU�W�<Ce�Z~pCaxCe�Cl�Cr�4sD�`Z�[�ClT\b�_@�Ce��P�Ca0
iĔI�CrxK[Da�aI�Cn�Ct�`$�Ca�Ci�Cu��|a��0<n�a�0fb<f��D�o�<Dn�l��DgDDkLDn\DsTogto�rI�$Z\w��TDhlDk�wk�|k�{��tDo�Du�D��}u�~��~���D��D�P����
�2a�i�Dl�nEolEr�u�Ev�Ey�E�4�QEaEeЅp\����Dt��@�k4Er=^P�� Esx�,(EdDEr��<F�����LE.��ITEt�$`Ee|Eo<���k���Ee�EiH0�ԏ�Es\�k��,Ȑ���E���IFk0Fȓ���EaDFdPFe�Ff�Fi�Fk�FlGoGp(Gs<Gu\G��v��PFt`[{����(Fb�eT��<F.ĔITb�Fk<����`Fi|��hFr0���tFt\V�hV���Fa^8�Frܖ���8ud�I�Fk���d�����Fi�Fs�Fà�<��������F� �IGb���@��h�@ Gi�!����4GgLGv�+�h�����TG�ȣ~�Ga�Gi�Go�Gp�&��I�Gtd�x��I�Gs8������GsĨI�Gn�Gr�����t8�����Gel�@�Gl\��8����Gt��IHrTHv���HadHcxHe�Hj�Hk0InLIo`IsJtdJupJy�J��(P1���8\HeT���IpHd�Hk�Hr��D����M���H������Hà�Z`��4���Ho���Hl�Hn�HrI�4�ud���������H���<IsԴ�Ig �kIn�@$Ii�M��bH�IDIm��uз4XIi�Ik�Im�Ip�It�Iv�I���Z8�b�Ieȸ��Md��0�b�Ie<De��In�,�Iip	D�	���I�,g4�@�Ia$�@�IhJrغ�8JaLJiTJu�~LE.DJi���(Jt���D��h$(�@���\Js�����ؼ��xJ�t�~�Jg�Ji�Jr��@�������	�Je i�JlKp$Kr,Ks<KtPKudK�8�I�����b�Ja�Ge<�Ktx��Ks��� ��<�<(���@4KrL F4�IHKb�������\K����\�IpKk�Ks����xKa�Kd�KgLi�Lk�Ll�Ln�Lo�Lp�LrMs�Mu$y�MÄ�����_k�@�Kl�KuL�Xe�P=�0fk�Ks<f��L���I0Lb8LnhLs���l����8fPLg�s��VHLs�b�i���\Lt$�D��ItLn��|Li����Llh������L���@�Lð���@�La�I@b�Lm��P���������La����c0Mk<MlXMt|Mv��k�=n���$Me��HMe���p��|�bPMalMetMr������@�I�Mm���T:�8����MdT����M��M���,�PNlT�I�Ml,Nr����
�Ma�$c8NeHNh\NihNk�Nl�NmOpOsXOt$Pu�&y\�L��,���$Nm��� ����@@Ni,�@�ITNu��b�Na�Ne�Nu����k|Nn�Nr�������k�Nn�Nr���l��x����Nl@���Nat�o`����Ng���8���Ni��t>ll��,%k8Oll%t8��@���$O��8,O�Le�IDOl��bLOa�Oe�Oi�Oo�OrP����.uxOn���~�Oe�I�Og�>n����.x���Or��Oe�OuP�hI�Ore�D|���O�,e<��P�tk|F�
I,Pb�
��4PatPi�Po�Pr�PsLQtXQu�Qv�Qy�Qz�Q���P�L_T_���P��I0P�.�Ps�$�Pi�P���o���P���QaQk Ql(Qm0Qp<Qv��@�I�Pdp��HbQr�88�<|>,�a<0�>�DQr�IlQr$w4$��dQix4`�xQa�QuH2Tk��,�Qc����Q��t"�����Qk�Qm�Qp�#rRs0RtP$���4.P&��(l1.$(��Re�*�*>Ro�)��$Rr0,~dRapRj)r��l-��PRm�+��XRr�04���d4xRp�Rt��5���Rr�=D<:���R��Sa�Sb�Sd�SeTf�Tg�Th�TixUk�Ul�Um�Uo�.phVr\Ws�Wt�Xu(Yv0Yy|Yà0ScPSn\SrpSs�St�Sv��DSk�Z�4<Se���0BsH!��X5b�$u0$��hSh�St�%�lD����S��&~�S��(DH)�4A�Sf�SlTs<,x$C��EbLOXOb�SaTo�M���St�O��RM8Tf@TlHTnPToXTr`TspT�T��U���UhV\W��Y�|Y��hT��T��Y�Y���Te�Y��Tr�ZF�fA�l�TbUl UnPUslUttm��TiPm��Tr�hm���Tgq�p���Ti�t��t4Um�r��UsT���k,Ul�w�4Ua\Ur\w��@Ukp�>��z~dUi�<̑I�Uo�44p�b�Ua ����Udȣ�Ua���t�I�Uf�Ul�UmVn$VrDVs<�����Ukl����Q�������Va�@ >o����Vm��
��b0Vr���8Vt\b�Vp\�IPVm����\Va�Ve�ViWo0WuPWÀ�$�D0
i��I�Vr�Vs<�����l1.�Vs�Vtxx�����Vp(�����b�Val����Vt����Vs�y�L)bWr(���Wt@���$Ws|��8���<WnT���DW���<�Wk�Wl�Wm�Wn�/p�Wt<Qv��b@��8�4�������
��WaXe4XiHXoPXr`Xs�Xt�Xv�
�Wf�Wk�WpXrp��D�D
����Xm�uXr,Xx���@Xn�D��
����XXa�Xe�Xi�j�Xt�Xv��D$�Xt@��T��t���X�<,�X��`@��XrYt�'b.e�'��Xtt'���XsYu@���)��Yu0,4(5ZDYm��47��<Yal����PY��=��XY��9��dYt<:���Y�pY��Y��=,�YsLCb�6iCD�Yt�C��Yr$G�4G���Ya�YeZiZkZs0Zt@ZìGP�GI�YnH�tH<��H�Zm(ZutI�hIIPI��8Z��|,LZr���TZb�Zg[k[lT[nh[r�[s�[t�[u�[v�5x��`Za�5b�[d�[e^f^g�^h�^il_j:k�_l:mH`nt`o�:p�`r,bsDetXeu�ev�ewfy<fì������[t0�,[l@[�P<\��$[sh�����8[�\4���L[o�!�H!��`[bPRm�[op5t#�0$���St�[u�&4L'��&~�[i$(��(��[s�(4�[s�1|1��[n4@�[r�:�A���3a0\b8\f@\jT\l�\m�\nx]o�]r�]s�]t�A
$C|D�F�l\s�E��H\i|\s�\y�\ð��ܝZ(G@t\tH�ATG�\n\G���\����hG��DHI�\pH���\a]g]i]j]m(]o<]sT]tp,v\]yl]ø�p�#�H):�DILII ]mT�4`I44]aL]e���J/pJDxJ�J��d]��JbxK5�]r�K���]a�]i�]sX5;���]bL!$�aM��]n8N��M���]e�P4@P���]o^s^t�P�LQC�R8�Z�<^aH^eP^gl`^o�^s�Ku`ZI�\p�[I^I�`Dt`kX^s`�4<c8l^a,b�t^l�^m�^t�^vDckd��d��f@�^t��j��^e�lk�^o�^sD_vT_�v~pxN\w���^n_thy�yb_e _r�y�,_a�D&l<_tHR�z�`{�h{��L_��|�|_r�{��`_o�_�X}e�~���D�̑��_a`i`j4`�4���0Bs�����_n�_r�_s��,��P�_e�_k�_Ð��ēY�&_�&���_�n�d�I`d$�D ����@g@`m���$`�@�g�@``al`ot�D��IX`gH��t�k�`.�`l�`n�`r�`s����8�
�����`s�`t���P�n�����`d�`m�:t�����
l��$aaXae|ai�ao�au�a�\��\�IafxBl4amDan�Ks\��t����<as�{��IPacpatLQ�x���hat���� <g(<k�ap�'s��o������ai�av��b���@����am�asL)A(����at8�Db.T���b��a����$bs����bn������ba�be�=f�bi�bj�bk<clDcmXcn�co�cp�csdt�du�dv$ey8eü�IT�Ixbn�bt (�t�~�bt<����I�bll�@�I�bd(����k�bu4�\��k�bf�����bacicn(co4cy0�����L����b ct���@�88�@Pce������hcixc� ����D|���pc���I�I�cr�,t>l�co�cr�cu���cr(�D�����,�ce4�l����I�ck�>l0dr��b�caDdeXdi|dj�do�dr�dè����(dk�Du<dn���IPdfpdlPe��hdl,5�ex���dl$�9a�deh4���`��dm<���d��DtI�dg<,�deeie����d�t��e�\	�bet�	��	��0e��
@Per���k|eg�el�em|?p�er�es�et�!Dh#�Ua@#���ed�eÄ��$���e�P$��xat'��Yu�(k$(���ek�)P�eb�)�0,@44(5u$ft�LQ�fj�8��ft�9kdfr<:���f�0f��f��<�h<�Pfs|<��Xfdtfi$w(H?D�=��|fl�fr�fshAPC��Ck�@g�fr�H4G���fo�ftI�~,ga�hc�hd�he4ig<ii�ij�ik�il�im�injo�jp�jr�js�jt�ju�jyhkà���b\ggpgl�gmhn@hrThs�hv����Tge�40��hgi�gs�gv�4�@�go�2����g�4��g�ت����gt\���gs�����gs�Z�glt@�ge hs����gd4hg>M��4hl�ZV,haH!��'a�$00$��Lhchhp|ht%0thl�\�%�(��hs|1��0Y4��&�A~�htA���ha�hf�hlithV4$C��hrTG�\G���h��E���h��P�$it,iv@P��isT�<Q��Z�lbPirdispit�vD�x�,%k\w��Xisz<�~��xis�~���il�~���i��{���iü~̑��ieĔ4ȣ��4Va�iih��p�k�it@������iut���jnDjpdjs�jw������$jo���,jl0���8jpTjsT�<��F���\jp|jt����btji@�D������Yu��~�
�$(b�jt����js�y�L)b�jr�`��5���jr(5b�jgks�8 ka0ket8��kt�8eu(kr�<
h<�<ks|<��Dkd�9��Pkr<:���k�\k�l����ksH?��|kl�ks�=���kl�kr�fs�kv������?@�ko�A4hA���kil-D�zI�kr�C���ka�C�lgLlrXls�Db,le�jr�\�EI$ln��^�H@8ls4G��@ln�Illt��Ibdlr�~�lfmgml(ms<mt��xlaPmb�mcnd�ne�nfog`oitok�plLrm�rnvo�vp�vr\wszt�zu�zvL{xh{�tD��lr�40��mu�$40$�� mkX'��&~4mr�,<H)�Hmltmr�po/��`ml�.��hmi�0�0��ma�me�mh�mk�mo1l1.�1�t1~�ma�2��2,�md�2u�mrT24�mo�muL3>�34b<nePngdnopnrd6s�u�n�`I74(nsx6u0nn�[48@Hned:D$:\nm�:M|noH<h@���B��n��@DA@T��S�ns�R��ne�nn�nr�hVI�n�<W�DW���nnPW���n��Z~ oa4oelTonT[�`Z��on0\��[��,obDoj@\�l`H`@Loo�l|k�r���hoa�i�ol�n�o�or,psdpu�pv�py�p�`��4���oo�oì�o�����on�����o����$�oepo��<����obpfps<�xl����Z0��$phPpk(���b<pr��$Dpo(�����\plxpm�ptl���)���\�u�9�Ȑ���p��p� ����Ȋ���paqdqi<qjHqkdql�qm�qo�qtru@[�D5D�4�pnT���pa$�Pd�Iqnx�,T���(q�$���0qÄ������pIPqk���Xqa�qi�qs�qu�q�ܖDd�I�qk����hAo�����qr�����q��P��I�qn̚��qi �I(�����qj��$rf0rp`X��!�rs�!�rt�D�.<@�88rrȣ��@rblrs�-u�r�ȪZH�4drm�goD��`����r�|r��r����D���(b��I�ru���
�ra(sd\sg�si�sjtk8tnLtr�ts�ut�uu�uy�u��o����r����sø�4sk@st�@ssLsu�>'�>b8si4?��4��VTsaxse�si�sl�ss���l1.�ss�]��l1.h��P���ss�_u�_���ssx���saDdZ$�b�se���stp�u�sf�n������b��te��tv�f��$th�@,te��@�@Dte`tiptÐ�����T���ht�L���I|tmз4�te�tk�tl�tm�tnupPut<��H����t�p�$�t�8�b�tr��ȸk���t�ul�,�te(ur����un��!�&��0ukp�I8un0�bDuilur�u�/��$duu,�<��xu�x�Z$�@�ue�ur�usغ7�ua�����<@�I�n7��b�um�,ؼ���u�H�,t���
�uc0vg8vkLvl`vmhvn�vp�vr�vs�vt��=��UDvr���IXvid������xvo�vsH�y8�A0�I��I�I`�I�����Je i�vo�vp4�E��I�vs��x���vi\������vawe$wi4wk@woTwu<�D��Iws��I�Y���,wl�ILwp��@�I����
�c�we�$h�wk@xl(Qmpxnxxp�xsytzvzy�I�<����I�wl����we�wnxoxu$xy�b��k�wp���J�wa���0����wa��b�wpx�xn��D��O\o`���,xm@��4xadx��e���Pxn����Xx������xr��D��,�xi��l���xa�xe�xn�xt�`�aT��b�xr�x�`Y<���x�L��I�xl8ynLyt��b�xahye�yi�yo�yr�yÔ_���0ys�
f��Dyt����.xytuXyn�ys�lDD�}�I�yg�yn\ss���yg����.�yex���yrt��$�yo,y<���y�<Y�u�
~0zc@zeljdzr�zt�z�
�u8zi,Xx���Pzn�$Xza|zi�o0Ptzn�'s Q�PI�zp��zo�zr�zu�
D4�Q����z����+I{k0,���za{i)r${s4{ü,P|/{n�/D|1��2,�2��,{�5bX{ud4@{t��CI<:��`{��I�{k\Sr�{s��t{a�5b|dL|e^f�|g�|h�|i�|k�|l�im:n�|ot}p�}r�}s�}t�}u�~È��..����{t%Y0$���{p4@|r8|sD|u�:��>b$|y=4,|t4?4AI�3a`|f||s$C�LO'XObh|a�M��p|t�Z�f��lI�|n�rD�4�|l�|o�|v4��@�u�>̑@t�b�|b}k}lX}rl}u����~Dvrd�����}j(}�t���x+��8D084}nP��<}id}s����H}d=
|����4�}l��Z��<��@�}a�k�}m�}tT���48���}e���b�}e�
��}s����~k<~l�#r\~s�~t�"k�"k~st"��~o,~s�"<��<@#��4~k�(��(@H~r$(��P~kp~sx~tl�L)�~a@)��Tk�~a+��~t�)���~s �H?���~ops�=���~l8r<:���~�t�P� ��x?���~r�?@4]a���0B4kB@ shA��,nHs�B��C���0rhs�I�I��`tlJ��k�`. �dH�fh�ix�j��k��l�m�n(�p��r<�s`�t�ru��|a�5b|�c��d��e��f�g�h�i�jxUk4�l:m̆n@�o�:p�r0�s|�t�u�v\�yȐ�L~<F.4�e@�rtu�..�0D�\�ÐY�|Y��T��p�}Z�~p�u�P��t|�����i^�0����f��gЀl$��l�4\��Ȁo$[s��\���p��r@�����<F.�d �ot@05.\�PD�iT�lh�pt�s��t � �( bL�a��0� �`�r� �Kt\�u��n�@��eH!��	<F.́aԁb�i��k�n�p$�t�!v8!��!��e,��*�t`"�"��"[�"@�a#�4	s�#@05.4�e�0$� mkT�t�%�%bL�i�&~<F.p�s�'��..�0�4���od:�$:��mAk
�`.܂d�e(�f4�l`�n��p��r��s<,x�4BbԂ.�a��r�sDBT�B�B��K4�B~�r�XT$C� �u�E��8+dD�s(G����`I4L�eH��T�sx��̼��J��p���J��L��K����n�Mb��sO�R8̃ôYA�r|Y�����Z�Y��؃i�Za�f�d6nb��s�l��	�d�sf<�gL�k`�l��m��n�t�vo�,pkto��D�s�q�p��X�t|r��r��l��Lr��t�ð�>\sV��r�r����gĄnЄs؄t�u,t�8t@��e�t��u��u4dz�z~�r${<�z����s�{4(�o�_�}o�|b �l̑�\�a�e��i`�o��u���t������T�g��m��nąsЅt؅vP�\��|�a��ix���V��.4�����g4%�������s��8�uĔ��j|DP��pd�I�g�m�p$�s�P����P8�t��ȗb0�rP�4b.p�a\��D�k ���T�cx�s�2�eL ������b��k���`>h�����dĆg�����������a�i$�u,�yĮD��I�l��p���ep�k�p�v���@�����8�k86�t�b	l�c|�d��g��l��m4�nx�r�s�uH����Z\�bt�e��l��0�<��~��n�-r��sd�$�������aqi}j�t�v(��D��܇rp��b.��,�m�p$�r,�sا(�.(��D����,L�f\�sh�tp�uh�4��:8�4T�t���@���,��g��i��r؈t�������A��eH�G8�I��s���l;a0'Z��r��@̈e4������k
l�t��O�r���|����$@�a��ẻi<�o|�u��y���\�Iaf\�gp�n�U���T�e\����h�g$���4-a��I|�r��t\_��u��nx�����ed6�nb��s������d�g�n�v�c�4s��b�s��k��V��sl����gH����H�s����$�p���0�p�I �@)IP�dL)bX�a(���d�t@���p�s�5��b��g<;��:����s8�����k؊pT����������4D<~Њa��x>@�.`>b�d������d�k(�l�<t��DlH?_���	p�a��c��e��k�ll>o|>p8�t?uhiH���\�sT�Id�k��sh�Q��,�/h��D��I��r��$�=lȋrԋv��`�'p�$��a��p�e���܋��� �et���b.`�����g@���a(�i0�y��H����\�ap�i${�
PH�s�IP�v�u�Ih�d�
���eČi��o�r��s��t��u܍�u,Xx��~��e�I��g܌n8t����Ԍn�s�t{������d�mP��,�e<�iP�o������0P4�n\����H�dd�gL&l�`Cu��l�t��t��T�����ta�D�����g��t0PH?���ȍl���Ѝ��!D�n����g(�ll�m��n��p̎r�s�!��p@#�� �a<�dL��h#�($�$��D����Zl�@X�lP$��`�p�%I`%Vx�s�$����g��n�.��DD&I��nP&����e�'4t'��Ďo܎s�'P$(���t@)�L)b�a0,�4�a��eԏi��o�Ð[�,�� �i�+��(�lP�r��t�-l-��H�eh�np�sx�t�"��-<�-����-~��o.���n�.��.@��e�.����t8tl�/����n|/ȏn�rw��v���e14�2��$��,{�X3�D3�l3���l4�r�3�d6��5@<�s�5bD�d(5bP�d|�l��r�FtLn�6��p�i�7P�7����k̚�H?����m�=����l�m�n�r<:����� ��@�@��ؐp�@�h@���n�s�@����hA���l�C���@gL�kX�pl�r8���E�8�t�E��@�s�F��b.��k4G��d�l��s�H��H�|�l���
�`.D�d`�ft�g��k�l4�nt�p��r��s�t0�u8�v����aD�bL�cT�dĔe^f$�g\�hd�i$�j��k�l̚m�n �o�p`�rh�s��t��up�vH�y����tu0�.L~8�eX�m�4r:�D��[@�..���h�d��i��m��r�^k�|n:.<>�PВaܒi��t<F������.�uĒn���|I�s���i�0���o�Z��.t@�e@�i���$�dD�4` �h ��H�at IP�r� �\�a���h�pH!��`[b��n��sp5t�"�(#��$<0$����hēk̓t�$��%��e��%uؓ.���&~�.�r$�tX'��D�I�s (��i�(��(P�QuH)��0�4���e6j��r��s�7kD7�l�sx6ut�r�:M 6a��y��L=�T=I��n=4��aAI�`.�g�i0�kh�m|�n��rЕs�t�u�v�xxCl1.PC~��eD�$D���g�D��D��(�l@�v�E�$
D ���H��H�4P��hG~\�s�|�H��t�l�K��<F.��k��n��s�L�lM�XMb��eM���t�N�M��ȕl���Q��ܕ�@P�����Q�Q����m<R~�?u�R��Z@8�j@�lH�sl_��_u,b�T�tdZ�f@�lI
��c��f��gܖk�m$�n�^o��p��s��v�mg�m���h�nAo}��oȖst`�,b<�o,�ito��Жl�s���,p<P����I��rLr���e(s��r���dT�g��i��j؄t��Ts4@�.\sVH�al�ot�r��s�������sA�s�|�t�s��s��vP�F\w����cȗt�x�yb��a�r��|��ؗ��y$���z����o�s${��{Pt{I�k�{���a<�oD�uT���|��}�9��~��h��L��x���~,�0eP��p�r�@�2e��j��l�rXt�}�4��u4��̘a��T��\���Ęg��o����ؘ�����a@��̑��
�pa<�dX�e4~kd�ll�oGp��s�t�uT�v\�y���T��H�r���A�ĔIP�b�~ �I��k,a��~x�v��DT�I��nh�@��a��e̙kl>o�v�����Ԝ�ęadDl�Mؙa��I�g����i�r��I �nH�s�Q��y$(�aD�b0�r,���<�tp��H�Z�^I�>�d�s����l�gh���x�g���������8D �����d��rd��ĚtI.ȣ��o��s�DĨIܚlتuH�4�t�~D�h�~�ft����a\�cp�d��g��l̛mܛr�vT�44�4H�uH��P�k��\�bh�l��o��s$:4�uؾ4��t�[���.��~��ed�������j��PP������ԛd�g��s�����k������o�����:a�Je i0�lT�s�����b(�a@�����x+����<��L�t����@��c��jԜkl�lx�m��n��pԝsܝtX�ul�v�Ið�M�|���k��oȜ�\��h����������e�=i$�n4�o�tr4cyL���IT�4��v����s��k�n��L�o��b,�t�e\�p���܋�@���T��@��d�u8�4 >o�8���b��t�����y�M��et>lȝot������I��ll�^�����a�e�o �y4���y�k����%rx�D,�gp�<��H��P�P��`elot�d�rt'�<Mx�i���d���
�ĞaОe�i��o(�rl�sx�ux(�h
���u�
I��tu�f���Iܞf�h�f���H(s���~�i��t�$�aD�eX��$��I<�t����P��HZ��d�k��"A�����i��k؟l�n�p,�sX�t��4#���ot"����t�#��05.@#��̟l�$P�g`%��&�P&����p�uP'4�l���p$(�� �sD�t�#L)b<�.�)*�)��P�bh�s+�0,���a��e��iРo�r��s��+IXRrM��.����s.I��r|/41k1IȠs��<1�ܠi�|1��p3,�v�2�����C�83�8�tT24$�s�0�0�k(5b<�cp�g��s�[�5I\�t�5��d�a��r��s�`>$60�8b��e��nġrt8����t�7�8��9��Ԃ.�g�n8�s<:��h��̡���� ���:<x:����s��>(�s<���g�<D=4P=��0�kP�tZ�=H�e�u�@>E�=��`�c��g��n�s�t�>��>����r�@KT@@��sh@����dܢgD7Sx6u��r�[@Ģe�@VТdCD��a��p$C0�C�\(s`CP�t�E��C���l@�mH�nd�rp�s��v,F�PFe$���GIP�r4G��X�e�I�3D�2��x��J~���lJ��I0�fD�gx�jd�k5lx�nȤrܤs����a@�bX�c`�d��eĦf�g\�h��iȧkЧlاm�nĨol�p@�rH�s,�t<�u�v�y`��tD�(�rH`����<�nT�s,b�>���\�rt����p�d��g��i�bV��a��o���4Z��k��~`"]H!����i�$I0$��Ԥh��kT�t8�v�$5�$���o�r,����$��o �o���$���&H)8L�sp/�k�0�4@��a�)i��o�[r�5�4x�tAI	��dإk�l�nP�r\�s��t��u�x�B{Bb��sD|u�D��D��Хl�oE��E��8+d�oX+uG:�H�H���k(�u\]ydJh��0M��0�oM�8�k�K��D�s�M���gol�tXOx�i�Oy4P�@P����a��r�PPtzn�P$��i��o��Q��R8ܦe�nTD�SԦs�4�Z��lI�d,2l�nd�s��t�r���d8�gD�kp,vX�Ä�k\sV0�ot�P�rd���u����\w�x�f��p��s���xx��x�x���t@zul1.z~��e�r��t�z�\(s�@̑<ȣ8�e�s�u��A��I�dH�
t�D<����l�@D�at�d��g��s��t����P��I<�m`�s�Svl�x���X�td��@l�r���0�Zз4��t�$�@��u̼�ؼ�����t�I�d�Ji�m�n�s0�t\��(�c�����r�������a4�����kT��`���(�aP�i\�ud�v��i��IH�n(�4P�<��@��a��l�vo�p�s4��@�P��I��k��r����p5t���ȩa�iD�}������d�n�t�o���ܩe�}$��X�I��nx�a<��� k$�p���D����,����@��4p�a��k��lȪmتt ���DT�Ih�l�������|�et�e`�����g@����a������������8����bЪe�o�rx,v�i�P4�n
{�	,�p�	�����
@� ����4�dt�l��m��n��r��s��t�#��#��`�s@#��h�l8tP$�`%��$����g��k�%��)ot'�@��$(����iثt@)yL)bЫa$+�+��l�)���s0,<T7,(5b�n0�s�8b�}et8��$�tx:��9��<�gt�l��n��r��s<:�����D��D��H;��0�.��e�������.ĔI��t<��b.|<�P=~�=���@g�k�n�r�s(�t,?�h@��s�@��A�hA����kC��tLCb ka`C��,4H)�0�l�C��8�b|�g��j��r��s��v�\���".EId�n�D��p�etEe�H0tH���l4G����k�IeJ��I
�`.X�d`�ft�g��kĮl��m�n8�rx�s����aD�b��c�d�eh�f��g\�hp�i��j�k�|l:m�nH�o�:p@�rзs$�t@�u�ev��yļzؼ�L}d�s�<����l�r�P��o��r��t�/���oH��v^�ܮl0����f��g�s�u�U��<�@��\���b�n�.����dp,v��D8!�$�pH!��,�a`[bT�kPRm\�t�"��#�&
�%bd�r0$��l�t�08��h�Aiدyt1@��a��e��i�1��1�<iDp6F86��įl4k̯k4@�a �d(�eD�id�r��s���4D�4�k�Sv�5�x6Z4�s�7�pD08<�l;I,&kx�s��t�:MP�a��i��o�;�;}�;`mlh<eH<����pps�=�D#a=4��kаn�o�v`>���l>�ذr?,�XÀ@{T@����sh@����AI�`.T�d��g��kılرn��r�sH�t\�u�xlB4BbL�ip�lx�r��s���:��B��B�B���n��C���ePC~��rXaI�;s�D���".�E��\�y�|�H��бl�M��m�K���sO
�M���s�tXOb�+a,�i�O�y�P$4�e@P��<�r0RI�Q��T�t�R���a��iP�\��x�s0����lS��lPU�T��s�ZV�a�eP�ix�l��o��r�sd��h[D`Z���r�\���"."d �s0�t�[���n<�rH�t<]����T]/(�i�]��PRm�]D�^�`�gh�soU�^H���_�p�yt`�`vm��r�` �}aI��d�`$��aȳÔ���a������bI��r,b�Գe��k�p$�tD�v�b~�c,%a��XdI�mdb�i�,�d��0�l�d,8�a8D��fkP�d<f��X���lk
`d��e��kȴlԴn�^o�pT�sh�t��v�A,�n@��c�4to����i�p���i�r��g�s���k�v\sV�s��D�Y�vD�p�v�`�r,���$�ihyu,�ryb8�e0�r\w��D�tdz�z~`�r�1Z${�t�k��t�z��|�s�1Z�{@��aеu�_�\SDt{I��r�$��}�ȵn�s\~��	hoa�i4�n�od�r��sԶt�u���I �s\w���".,��̆�,�y��rsa�@�t@�IL�f�$X�a|�i��o��ủ<�,p��|�����s�,0����l��p|>܌DČI��n|��ȶi �,Ȑ�����@�d�e �i,�k<�s�<�p�kd
s�<0��з44�tt�It�d��k��l��m��r��sķt���\�bl�l����~��l�����r�s�����`d�g����}a�c`���\�u��4	��i8�k��lȸmܸo�p�>s0�t�v@�k�n �s���@����t�����".0�e�w���bT�a`�ip�o����kL�n0��(����bh�r,bIt���|�s`�����g��s@����a������I��t8����i���IԸc��,�,�it>l�o��������s�I�nT�p��b �ad�ep�i��r��uԹv�y�e�Du\�l�IPdf�yg�$��a��i���P��k�����p�/���l�bȹi�d,<,�aei���e����rt�����
@X�ax�e��i��oغr��s�u�v�����k�
IL�r4����d�sul�r����8f�I��n�������b�����m���d�h��k�l,xm�$��a,�eD�ix�oX��d���loL���a�i���J��I$�p<�re0P�'a4�n�Ps�r�IX�n(��`�i���l�p��r ����F����c��e̻k�t$4(���b��rHb��o��Tkػr0D����t�e�����`���`X��!� �s�!�(�t���4�f�g`�ih�m|�p��t"�P$�t�sh$�P&P��p�&��)	�)����b�`��5����r(5b��gP9A�9����r<:����̼����=���".`�c�m�<t@e�D	�C���g0�jD�k�lP�stEe<�at{��E���"r�I�\�k�����Ie�~�r��h�a�bH�c\�d�e<�f��g��i�j��k�l��m��n�o0�p��q��r�s`�t|�u��v@�wL�xT�yd���!	H!���dH),�j�l$�y�,X-��,���iL0�3
T24,�rsT�u�0�4�kL34b��e��i��l��rؾs�à6�x6u|�k`808��ad�	:^��i�:M�BaȾoH<��==4оk�t�>���iH*u�>4�@�h@����A~�W^`T��tTM$�s�R�0�fT�l\�r��ÀU4hVD��`���d���@l���Y��x�m�Y���r|Y������Z~ؿa�g��j�l0�nL�rd�s����[�`Z��пv�`�^��rl_4 ���_��o0�y���<�tl`��sH`@$�o��Z|aD�`$D�iȳìb,b�\�et�td�fD<f��|���l~��l�p4�~�l�n�s,v���4����i�0���l�u?4��IPqk<�ȗ��
�aX�fd�jl�k��l�m��n�o$�sD�tP�u@yt��0�Q�YE|Y��D��^8L��$�,��@�2a��r��u<����4@�4��@��i�l�����s�F��o�������l������̚��sз�~�s �I�k,���~�vh�@4]a�D���0�����8�È�I|eg�#r|<�̡��`�r������h��h�,���d/������.�����@�8��rȣ����b��e��o�p(�rD�s��t����I��n����ԛdĨI��r��l�@�a �l��u@��4�a\����IH�4<�k`�t���Ъ	تbX�e
	�,l�t ���t����,�@��r��v`�����	��c��g�i(�j0�k8�s��t��y���P�Z��V��i$r�s<cM����lԴ�Єsp�k�n��<��з4��cX�id�m��t ���kP�sȸ<\�ed�ul�l��r0�bt�eD�&	�����sغ$��a�e$�@��r��s��k��k̼ؼ���u������\�t�����d�m(�rD�^�����s�������Je it�l��p��rT�sdK���$���T�eX�I\�n��bh�i����ex����l`�r��u8��4��� �,��iH�A\��$�k4�n@�p������aP�dt�e��g��i��k�m,�nP�o`�p��s��tTwu$y��À�������,�g��P:���H�nh���,	t���`����I�h`Z���n�@��a��r���41	T[����i�`�0fb<f�������I��eJ9	H����t�n@��n��bD�D`������@��@�4��@$�uD���Dؼ��<���I�����@X�lp�r ���H��x�a�����p��t���I��g|�b��iT�@0'e��r���,�e��oT����M��+������������8��Ô����f4�k`�o��p��s��t��A	����,�lL�o0����bD�pH���IX�m<�����l�g�,t�i��l����k��t����I�yg�%k��l��b��i��r(�uP�����l��o��������n��|����@#���� �l4�P$I4�n���<�a�
IH�l�
��
T�a��i�Po��r��s��t(�uP�v�Qy\���I�".��e��n�����$�>o��u���h����n��@
v����i�r �s������$��0��I4�n�����<�n`�D�i���l��ܕ���$����t�n��p��r��s��tP&�t'D���T����H�$(D+F	�)����t�+I��n0,����a��i)r�s,��$-|1���i
l�m�1��}e3,�2��$�� 44��8�ed4�(5,�9<:�����\�����h@��=��x�n�C�I
$�f,�g@�kT�lp�n��p��r��s��t��u����aD�bX�c�d8�e^f�|g\�h<�i��j��k��l:m��n��ox�p �r<�s�t4�u�ev���D�����Ls���4�i$0��L�ah�s@[�������d��t��t@��r��H!��`[bPRm��s(#<�$4��i0$����k��t�$K	�%�X'P	�&~��r�(<4�<�����i\�o|���r�D�� �tA��,�kd�l��m��n��r�t<,xP��E��8+d|�s G�(G@t�ihG��sH����g��n��t0I�JaJ@��r�580�l�K����b��i��s��V	LI��oMP�l<M��P$�>o@P���r�E4�n@(�l�l��0�e<�g�^oX�ph�s�v��\w��`�o��t�x'ybx�a��o�y��{b���C]	�~����s�~�������̑b��a8�eX�i|�j��o��������c�n$�r4���b.�e����������n�}ĔI0�vH�x�d	���d�IP�cl�kt�tܖ�z�$�̛� �����m��p0������ip�ut�I��a��c��fTi�l(�r4�sX�vh�H��<��zV	d�I��t����i}j �y@j	�����g��L�tu��r��b@�e��Dl��$�D,���d�������a��e��i��j�l��o��p��r��t�u!y�Ô�I��r��s�5x��{������k8�<�I��n�r��Єs����b�a$�i<�u`+y\����X�I��n4�s������e̡��D�np�t���x��L������=eh�� ����n	��I��c��f��s	x�x	 �,��a��iu$y���<������������(���@��r4������������,<�a\�e��i��o��\��H�xd4}	<��h�t��IP�sl�x�a��oLOy�O���� <g��k`ml��n��o0�����	l�����c��g���Lv��~��l����gt<rL�8��T��������0��(��	�����sH�V	����(�v���p�a��h��k��l��n��o��t ?��P|�mT�Id�l��jb��@��o����$Me@��(�i��y�����a����d(�D����a���I�kT_`���v<������
@(�r0��$ �i�I|egl�l��n��s�Ql�IL�v�#��T�o@#��`�l��t�#�$�|�r`%s�$����g��k�%PL)$(����t�:T:~��r�9����d<:����������0��H?e�=����l�rdBhA���o4G��C��(�r��T�e��<�uT!}�I�`.�c �d4�e\�ft�i��k��l��n��p�r��s��tTHv��y��\�a�5b�c�d��e��f�gD�h��iX�j��k��l�m��n�o��p��r��sT�t@�u��v��w�yT��2Ipit���iL~4�e�4rD|u X�	`��<��4MD��D�P�f�lr\wp��l�s�P��0����o��	��.t@��e��r��s�����d��sd��	��`��4��v( u�����lH!�
l1.X5b4�e<�gD�kPRmL�nT�s\�tx��,"�L"��"b�"f(#b�#�h�a|�����$��p��0$4��h��t�%���e�	�%u��.��L'I��v�&~��i��r�'�X'���i�7(5b��o�084�(�a6j6o\�rh�ut�Ä4$�f8�g�4��l�rL;�;ID�n��t�:ML�a4?�&lh@�����B�����@��@���sAI�`.��a�b�c$�dD�fX�g|�k��l��m��n�o$�r<�sx�t��v<,x$(_�(���sA~��u�A��A�	�oB]Bb+r4�u4?DC�$C�<�t^�PC~P�gh�nH`@�..lE��D��t�r��t���i|����r�E��8+d��u<G�G�hG~��p��sH�&	H��l1.��i�k�l��p��	�H��|��J�����J�K��<F.��n���M��4�oX�sl�t,%�O�P�k�O�XObd�i@P��<F.��e��r��y�Ð_u��t�P$��o�Q�o�	|/��g<R~��i�R8��ôY��r|Y������Z@�g8�s^ b�d���pdb �o,b�,�t�f@p�a h��g@P�sh��X�d,g��d�nmexl~|�l�l����a��b��f�g0�kT�ml�n��o��p�s(�tH�vPm�T�	�n���i��r�u�n����n��n������X�	De�o~�t,p�to��(�sH�t��	|��@�nLr�� >o(s���s�r��`�d��g��r�s؄ts�"�	�����.xs����n\sV��e��rLt�v~��t�v�8�'��b��e�v����l`4�y�����yb��\w���tz~�uH0D{4�s�z��<�i��s�{4p�i|�op�u��Ø|I�s�|u�~���D���	��a��e�i�l@�nT�op�t|�u��Ø��	��P��t|k��k�DD��k��k��s���ąe\�����s4���a$�e4���� ������,��̆<L�e�4@�k`�d|����|��h�r�I��p��DȐ���E����l�o �����ȓ@��a��d�sВ�	��P��a������k��v8��T�M�s��A��h�@�pȣ@@�aT�ih�s|�tȤ��X5b��I4�r,2D��IL�l���H�4`�j��,�@t�r�@��a��g��k��o��s��tĮ���I��l�@��PH�I��m8�з4��k��t0�$�<�Ft���
�bL�g`�kl�l��m��n��p �rl�s��t�aDL�$8�o��~@�r����~X�r�����j��ld�4����|�i����n������ax���(���I��u������a��d��v�@b.�ekt�u0�����l�p<������s��������a�`d�gD�iP�n���	��I<�e,��@�D@�IX�n���`�i�Wn�&v`���0'e��@��l������,����b���0�D������������a�d8�eP�i\�kl�o��s��u��\�I��n���^�	��}�i�FI�g���� �i��I,�lH�s<������0Lb��a��	���d�b|�m��D��@��k�b@����#r�����e�i�k��l�m$�nH�p�>s|�t�v�&y<���8+d��I��l��x���@�P@�I��n0��8�d�����iH�nh�o��u��������J@�a��o��T�l��b\�l|�s��t�DL���<tx�P������d,b���vt�����s`�����g@����a��e��o����8�D,���������8�� >o��I�Gt����a<�à��|���4����X�l`�o��������Ih�k��bp�a��e��i��o�r�u���k��n\o�.�����d��ip��	Po����l�I��lx,�n�����ð�<�4�aH��TDd��,�g�4t��@���
@|�a��i��o��r�t�up��
It�f��k��n��	����i�����tP4���Єs�I��nD�����m0P�Vt�$��i��e����<� A� 0�lL ,$�bt�r���0�b��d��k��l��m��n�p(�s<�t|�v��è.!�pn� b|�a�"��&vt"����s����l;aX�I��r�#����e@#����lP$��<F.�8�s�$����n<���&�P&���p���4�a$(���h���)PT�ih�r��*IL�g��*>`�a|/��+@t�i��]�+�����0,@��s|1��j��k�{�������1k���4d6^�5@��s�5b��d(5b��d�o�7A�CA�:I�lx:�� �e�9��,�gh�l|�n��rs<:�����8�����H;���V(�s<��p�g\�|<����a83�T>4��s@>���k�=����c��k��m�n(�s`�v|��	,?D��t@��@V(�sh@����g�sA^�@4�tCi��a<�sL�t<C�'LCbD�i|1��C��X�s�6�Dul�l8Dbt�e�r��s�C��	��d��g��i��k�n�@p�r,�sH�v�D�D406}�E<�E����s�FxF4��tPF����s4Ge�s�Hk�I�8�r�I�� �t>HJ
J~@�r�I�`.��d`�fh�iH�k�l`�m��n�p,�rh�st�t��u��v��T�a��b��c�*d��e��f��g��h@�i��j��k@�l8�m��n�o�pd�rl�s��ttu<v�y�	�L~<F.�e�j$�r,�stU�D�
^4$4�i���<�rX�t�
\�t�a��m��oP�x���I|�nا8��a��PĨI��rt@��s�����d��g��l�s��|����h��������������a�4�l�P��H!��$�.`[bL�mX�n\�t�!v���.�"�4%�0$��`�s�&~�.��ux(4�(���kt"��(�H)��0,��h��il1�t1~
��.��a�b�e@�iH�kP�mX�p`�yp�Ü1��u�1
H)
H��1���n0�r8�t�K�i�<i>�i��i��j%
�jO2��1��h��H22I|�pAI	��a��g �k<�lL�mT�n��r��t��x�&~l1.A~��tPCb��g��l�r^�C��C�C$��aEQ�DQ�e�D���l4�r�	tlE>�E��<F.\�yhG�H��<F.�	��	��`��M�h�ÈK��t�s@P��<F.xR+
�R��p�R8���hA��05.�Y,��r|Y���������YA�Z��f@�a �i4��4u,g���wdi~<ib�slbhk��,���lI	l�dx�e��g��k��l@�n��o��s,unb��s�n���r��s��tL��K����ihN��M����kLQ�@P����tosto���t���|����sdq��b.�p����l �v����I�rp���e0�r�<(sc�r��8�d`�f|�n�8r��s��th�	��,th�s8t@p�e��i �/�tA��tPu>�u@b.��r�u1
v�y\w����t�{k�a�n �t(�uh���{4�tt{I��k�{8
:f�}b�}�8�k@�r~o�#i��~��H�s�~��P�l�~��\��x��Pe���l1.��a��e(�h0�i��j��l��n��op�rT�s\�tx�u��v��y�Ð`+
|k
��.�d4�fL�gT�l`�p��r��s��t�Sv@>
 �~�a�o�THTE
4M �nH��(�fD�t�M
�D��P�..(��b.��e��i��s���Ix�tD�T
t�������a��v́[
�!a
<��`���`���k��.��d�n�pԂh
܂b��.xH�`����eK�����p�o
�H�fP�nX�sd�v�st
��o\w��05.�oX}�(�bl�r�4t�o��u�}z
4����at��\�����p��s̆J@�b$�.��g�l(�rL�t���
��s���
��$��g����a�o���
��`��x�, �p@�s��k���8�t`��p���b.@�IT�n��t�$`�a��e��i�o�uH����V	��D��I��t��v��V	̉���f��g��n��p� s��v���
�����������o<����n|�D$�b,�v0��
|�����
���4�c����<��0�<|��
��(���d�p���l�l��p�������e��b\���n�rT7D��
������k�9�h�l�p�rȐ������D<~�.|<_��
����k�����n4�r��9	���,�v̑�`�a�e�i8�oT�u��Ð�����.��d��n��r�s�tD�~�.$��4�����d��g��t�
t�����<F.��������..ĔI�v���d�I�c�n �s(�t�tA$����s���z�ܛ� ���0�rH�t`����<t���l�ct�f|�s��t� A�!A,����tD�OX�e��o�*4̡����.�r��������,��|<g,?ih����k��m�n�p�r�t@o�����n0A�hA�
��
tEh
 ���$�jȣ�l�a��e��i�j�u�y(��d�P�..|�t��I\�k��l�8
5P����Ԃ.��I��t����I��d��t����tT�
����a�{�!o<����g�5o�b��g<��D����g<�kD�s`���T��������:��`>:����L�dh�lp�sH?���
���D���x�r����a�e �id�u|��|�
��I��b�c�m�r��s�t
���,"8����el�Ox����t����I�rPmVp�k�b8�l@�tT�vȴeh��L�t�zM
�����e@���\�t̼D��rؼ�����p�������g`��
�����c��rhA�����..�g�r�s��
4G�P�>t�I	�c(�d�ai0�lH�m\�n��p��r�tH�A �k4��
\�������j@�s$���PT�mا�
���p�s���8�4h�t<�'��,|�a0�����r��u4�4����<F.��d�gP�nP�n�.`���t�����,�at�e��i�l��r�s$���@�.p����n��I�n4�rL�sh�t������kD�v$����X�s4%8
���
��~`�r8�,��d��k��l�n�r�tB�,��d����sl�|�@��l��4��4�P�����sdq��p���l<����l�r�ve���(�aT�i|����,8�nH�t�e�'��~@�sX��d�nl�t$��t�h�o���t��\����s �,��e��i�oT/y��P�e��
������s�t(�V	��+
�����������������n<����8�n@�r����P����\��<�|<�h@����H�n0�e������	��a�e�i�k�l`�nl�s��u��v��T�I��d�FItLn<�����i��I��l���@�I�l��b�l�u�����x�����n@�8,�a@��t���Ԃ.`��� �g�P����8��P��,�P�����X�al��s�Pt�ntI|�n<Mei�
b�.�ace�i,jhmxor�s�t�vy<Ä
I
��.c d�WfLl|m�n�r�tu8XD~i�^��^k,s$�@4i���@gdl�p�
��\ati&8��b.�mا.tlP���t�"��.����k�tD8
I�d
��a0'5h
��e�l�(<�
�{u	.HaPg\k�l�m�nrlt`�|b.����".pnxt�B|�I�F����l8~�..�e��[H�HI�r\���a�e�sxH�
���
|4�e�m�IPxKX����a,iLnPL_8L��gI n<u�z�	��
(@De�h�Xs���`i����I	xa�b�c�d�k�lns$t4p�m�D��kPv`�_��k�n����e$��..t�����k�|��czc(���uT�d�l��<l`r���D�8�:ZH����pc�l�m�r���kl����Q4�@�u�I�e�s<���i�s��@�<P��0��f��fHt�$ahe�iuLy|ô����I4v�~<iXo������I`c�j�n�r|D�������	0P�a�d�e�f�kst�e<n���@�l���H��0����t�w����kh�(k8m���ا�\��0m���bDc\k86������dk�l����l���h��������c�d�n�v<����l��g`��,F�����m�x	`biH0e�sT�$p�+
��PgXl���`�,�l���
H;��h�ll��I�b�g�i�m�np\Js$tL �!�"P$��$P�.�d�e�g�$��$Z�r�$@�e�`%�P&Pau��P'��)���o4r�*
0,,da�eitÜ,��b.�n�+��Tg�k�m�p�r�s�t�,��,�-o�0��-l-���sx�t�-��-}t�.I�c�n�prs�.e�.��.�/�/�|/l<nHsXt���/��4eH0��z��0~Pt�2���Md�r�2����d��2�2���i�@	d3���g3���n�r�s�t�@��.�3���mC��386D(5b�k	l	n8	rD	s\	t�6�T7�(	kh7<�7��7��0	kt8�P	t�8�%e�8�h	h�g�9,L@g�	k�	l�n<:���	�p	�$
��:�t;��;��	eH;���	d�=,�	d�	g|fl
p
s
t`>��>�>���	g0A4Ce`C
a�
��C,P�dD
g`
k�l�@pl
r�D4P
a�D@���E��X
o4G���0s|
tI�I�`.8cDdpf�g�k�l8mPn�p�rD
sh
t�
v�
x���
aD�b�
c�de�f�g�h�i(j:kЧl:m�n�o�:p�r�st�u`vTy�z��t1=��0hL~4�eXi`s08N4%D�hf�����|a����.�r�t��'$�et+|I�g���i��o0����fl(v�w������ed�I�s\��i sP�4����\��0eHix3���<F.ta�d�fбl���Ilbt@�e��9h���a( ����l�ph �t I�r� ��aH!��<F.X5b
mX�n
s
t�!vT�4�@�i(#��#�8
a�
@�
P$
l|�I,
v0$��ēkT
t�%O`
r&I�&��
a�
s�
tPD$'I|
n�'� (��(P�
e�
l${s.��`h�����
.�
n�|@�
a�
o4�� ��d4N�0�ht1~��a��eAu`albtc|g�i�j�kl8m\n�rDs�t�x0$4A~Xs�A
�ASPC�r�C��eXay$D�n�ufLDI�t|DD�s�}��D��Хl�n����k�ks�<�iT���E��8+d$� ��\G����GhG~0aLoTu�G�<�4H����g|s�t��XIX`I4tiJ�J@�rxJ��J������4Go����r�K���.ikm(n4s<t�7u`{_h{����LI�ÔL��.�Lg�L@ aM�T�<�M���.TtXOhepiH*u�On�OI�k��tP|k@P���i�s�P�4��R�a��p�R��Z��f@�eHD�h���n�lI��a4bDcPd�e�f�g�k$l�m�n�o�s�vT_�Pm�2�m�<inDdelu|�<n�	�4�nu�n��t�B��n@�d�W�rs�n��tT\P4o���lo~�e�h��o�8r�t�^��oFto���lsu,pP\p=dp��l�p�4jPl<qoܝ|�q<tdq��Dsdvt�T���q��q��l���Lr���e�r���d��r؄tv���d�n�shv��v�\w��ck���z�n�z���ai$-D@z�0~e{t�{b<eLo��L|��|��|bDb\ul}+
�~_�~��dl�m�n�~��l�@_h@��a�e���I�r\�bh�lt����d�k�lm p<r�s�v��}����".}j�����rsD��0��@�#���(p����0a`d�i�mP�n@�s�tP���.LQ�@P��lt��@tt��I�e���sh���@05.4�����k�t�u��b�.��4��$a�e0i�ohu�y�\��0fLl�n�.r�s�t\��<i�T�������D.late|o�s�t$�Ĕ�
�����������a�����sT����4<F.��~<F.��oX����I�g�nr$t���<F�
$����.n����P��..x���s��PTbhctd�e�n�/���Lu�mt�m�`knb<�s�Eo�n@�l����..l����gH���k����c�g�n(p<sHt4�&	L�����k�k�����ioH���� I.����i��Il���4s������nl������Tp@���\m|p�s�D�p��L)b�~a(����t�0��k�b�c�gT24�.�^�6��h�5���g�:_8����kT��������������b.<dPfXg`kln`>:Hs�>��R�
�>���u�<n��xip�1	t{_tE���a�����j�t�I�lJo����a$eHk�l n4o|>pp~sTt�uy��������dT�I�nrt,����Gtt��	��8g@n��x���T�4��bta`�i�=l�oЀ�T�P`l��khl�t (�����t ������sL���t��b�tH�o8����t@���o���e����k���������|����,����ø�e��Hr�������@d��k�e�i|dj�o�r�u�y^.P��xf���l�I�lx���dl�n�$�a�yL�$�����r�7o��rt�d� D�Z�
�Tale�i�j�Po�r0sDuPvpyx�8A�
ILc�ruB�udd�g�m�n�s,Xx|�8\bD��I��n�p�vDt{(b�a���dnt�$�a,�e i(y������d�}0�������e Gi�~t���g`@d��D���\�Tu���l�����el���r�!��!���n����g�np$r0t�?v����$��$���d�g��n��`%V�as�%<P&����p�'t'��e�)����bDvT�X+s�+e�+��L�0,�xa�e�i��+I.�o}|/�g�n�t�v�/��/���g�0}�0ex:e�2���g�n�2������2�d3e3���n r4t�B<�3��s�C��3�
,t�5�5b@a(5bHdpg�n�p�s�5���.�[�H7V|dT7���g�7��i<�t8�P9��9���g�s�@t<:�����t�l�P=��2�T>4�o@>��k�=��c0g��m@rTv�>���hA��8m�|��C��Ll8D.�e�C��`d�@g�p�r�v�DZ�F��i�44G��X�e�s|��H��t�-D�+���sJ~�a�~���aL b� c� dT!e�!f�!g"iX"jt"k@#lP$m�$n�oP&pt'r$(s�)t�+u�+v�+�H),l a� b� l� o0�)d l�*4�*x eP*0� l��AX-I� c�,��� i�-4t1~��a�0�� h� l�34b!a!e!r@!sD|uL!y�4�kx6Z�:,!e�;D�;I$!tT==48!a@�A~`!s�M�hTDpT��h!�TMp!øR�|!f�!t�W|
n�W��!a�!oHX4`Z��Z���!aH^e�!g�!n"s�^<^��!sH`�"sз�,b��ng�l��"eLDn0"sL"t\w�e@zu8"tz~@"e�{�A
��k`"b���	h"e�"i�"l�n�"o�"s#t8#u�|v�4���"a\�@�k�"s��0���"k��Z��$�"o<�P�'s���"i|���"r#s,#Ô��ЍD܍��$#��̑���pah#d�#l�qo�#s$t$u$�T���)i�#sD|u�#�(>���4�#mh@���n���ܝ��#e��@�#t����#s�#ì�������#n�����#�h�@�6t(������#r��������($� �,�YD|Y��0$�Ħ88$�ȣ��D$fh$s�-u�$�H�4�gox$tت��eD����$r`����$��L�D7��$l%s$%v(�Z�$r�@�$edno,%r<%�����$d`%g�%i�%k&s&t�iu$
	
�	��%��7�%�
d��D���4%�8\����H%f�%r��VP%e�%i��r�%s0M��]�x%k<����%szgP���%t����%p�n�p�k�%f4�F���%l�%nP�r4�<8�kз4�%kغ�8&a$�@&r����~$&i���,&t8�It&r����D&e i�&l�&pP'u�L�����l&n����&e�&i�e8�I�&tl�YX�I�&kS�^8�&ax���&f�&g�&i'l4'r<'s�|
��D��I�&n�!
�b'a('�x�h\��� '����<�<$�p�)4�IH't��Q\�I\'k�'r����d'a�'e$wi�'o�'s(y$����'v���Id�b|�m���I�'n|�b�'i�'��'t`�<���'��ZH�PT�I(k\(n����
(a|(c�(e�(k)lpxn)pL)t�&v�)��������T(l��e��~h(a��,p(h���(nT���@�(a�(l�(r�(y�(���u��D����(ap����������(��������(�@��)��M,)e�+
t�,$)n���I8)t��b@)ad)i�yr�)ÀI�ygx)k�'n�e<���'�xu��	���)��I��&��	D�
I�Wk�
��
�)a�)b�)e*i8*j�*l�*o�*r+sD+tX+vx+y�+�D�(
u�f*sD���I*k0*l�P�$b(X*Ì�l��D*m���L*���A$�@d*s4���l*d����x*nЧ<�*a�I�*p�*s ��A�>�*e�*o�*uD�I�*r�4�*p(�h�l�-
@�I+d��+i$+l�8+������0+��P+j�`@l+et4
��d+cTZ�}l���+v����+��A0,@�+apRj���+��{k<:��4)����H<��M�+oL~�+r����+d�,g�,k�,l-m$-n�0pl-r�-s�-t�ru.v���+aD�b�c�d.e^ft/g\�h|/i�0j:k�|l�0m�0n1o�:p<1r|1s,�tH2u�ev|2y�2�H`�����,n|;
���,i����,t<0���,k�,l�,s-v\��|�iܝ��@�,t4�P\��-a����d<-pL-s`-t�:�,��4D-t��t@X-rH!��`[b�-e�-i��k�-s�-t�-u,"Iws`"Il1.(#�x#��#@�-r�#��%�%b�-i0$���-t (���n�&~�-t�(D.i)B
AItc@.kL.l�.n�.p�.r/s,/t�Db�2e�E��8+d����X.��Ib`.�`I4l.kH��x.s�.t�.y�.�l���l;ax�u�.rJ@�.epJ5���J���.��Jb�K��K���.g�.k�LP�M�/p/tOXOk4P�@P��$/aD/eT/sh/tUQ�P�L/a0�LQ�`/s�Z@�l�/c�d�/l�/nH0s�0t�0v�m�p���d�/t�q�(s@�..�r���/d�/g0kl`o,0s@0y�s�\sV�/s4��
t�0l�_Pub0.80e�t40td��uZ\w��<F.�}ad0ht0k�0t�$�xZ�w�l0oy��0a�xdz�z~�0r�z���ka�{����oȣ@�0Ø�������0kD����0r`����0���t�I1k̛m$1r41s��~�".����ԛd�g�����X1ap1eu��u\�IP1k$����Ca��Id1r���t.c�1k�1m�1nl>o�1t42v ?À�k�1r��4p�$�1i��48���1i �����1i��k�}e2r(2ylo|��2�$2�0� 2c<,e�� H
���@2dh2nt2p�?t�$��$��`2dP&,	(5u�;�H;���2d�9���2l�2n�2r<:��3��2��3����V�2s<���2gh<6|<���2d�2iu$w��>e83g�=��3gD3ld3n�3r�3t�3x�^��>�03sH?X3l��$���P3sh@��Llx6K
��p3ehA��x3d�3i�3j�3k�3l�AI<�lX�S
�A������@�3d`C�
d4Z
�C���@g�@p�3r4Ge�I���3a 4e(4hD4iX4nA��fb44i<i,�n��l��<4e��4���P4a�4a�4e�4i�4k�4l5s5tta����4d����4nȩ��G��4a�G@�4lhG~�4pA���4m�l��Ls��d��̑���4i����Q�
��5i8��b�a�5b�5d�e�5g06i86k�6l7mT7n�7o�7p�7rt8s�8t9vH9w<)���/��x5s�.���5iH)��5r�5sp/�4b�5a��o|r�5sD|u�#Ä44�5nD5�=�`ZI�Z���5aH^e6g$6s^���r��Øc,b�6p�l~����ip6l�n�o8�`
$�PP6t��IX6s4��d6i�6o`����-n̑���pa�6g�FiHqk�6l�qo�6t8�4$�@�6j��������6�����6�(������6r�U�Ħ8�6l(7oȣ��7f<7�$VD�UI 7rD��`���47���V��r���H7gh7kt7s��lз44]a��tt���(�r�7u|������Je�7o�7r�� �,��i��I�Vs�����7e$wi�7kP�o8s48tH8�|�4����7u8v������8k,8t|�ZT�@��u8�g
T���@8��1o��~T8o��,\8h����h8c,0s�8t���b�8a�8i�8r��8k����".$(�a�
I8(k�
���8a�8e�PoLQt9vul1.9a``��+0,��9a,9i<9�|/�3��2��49�4A���3ax9e�9i�9o�9p�9s�9u�9zAI�9r�L4�K���9o�lIt�I�9n��I�l�����9a��4��A��9I�|nP9���9i���aT:d�ep:fx:g`oi�:kH;l�;m<n<<oD<p|<rP=s�=t�=v���=��9�lJ��C�4~6jd:s=4hl�R��Z���5a�:e�:l�:s�:�[I�Cl�_<�b�,b��:k�:tdu0fl
<f���:�����i;l<;sT�q
\����:g4���:a�_|��;�$;�8�$;r0��0;t̑���pa�;dqi4~k�;l�;s�;�t�y
��ul;rT��t;e��0��@�;p����;s���h�@�;eHt������ȣ~�;a��x����;s��I�;nd�M�@�;rD|u���<d$<sз4��t4<v�t���~T<l������(�a64��`<o�<r�<s����h<d�<e$wi�<o=s0=�L�4\�M�<a=��<t�>��D��I�<l ������<r���
T�I�<v����<a=lh#v�b���=i��T���(=��������<=e����D=kpxnh=s�=tl�t=k�b�$r���
���=e�=r�=s�=t�$ i�=�e���=����=j�`/s0,~���a8>b@>c`>d�>e�>g`oi ?j,?kH?l@mh@n0AphArCs`Ct�CvH),�0�T>k`3�T24L>v4bx>d�>e|r�>s�5@��sD|u�7x6u�>s`==4�>e�>t�>uA�Z���5aH^e�>gl�>r?s`^�^��>o�`>?eXa4,b�\�e�|Z�{��?o�D�i�n@?r�>̑���pax?p�?s�?t@v��D�:Id?p���l?a��I8gh�@�?e�?k�?l���
4�b�?gԜ��?od�bl���?u(������?r�?ux����{np���?il�@lȣ��@p�^�
��}(@hd�I0@g:^<@i�@H@l,%r�@s���T@d�@g�@n�@o�@s$At�>k��4�@t�����V�@rd
> �k�@s�@�@i�@s<�H�4��8з4�@lAtAv0��Aed�Z�0غ�$�@Ar������ePAp�rT�s�x��HAl\�I$�p����	\Aa�Ab�Ag�Ai�AkBndBo�Bs�Bt����.���Ae�58�Ar�_��@�Al��r��I�Ab���
�ED�����A�����A�T�Dp�kBs��@Bi0BsDB������4(Bt̼Pؼ��<B�TB��D�����\Bbp7l�Bp�<rps���������Bi�Bkl>o�Bph#vh������BoH�0����4�Bd|�I�BnT�@�Ba�BrCs����<��D$Cp<CsLCt�I��,%a4Ci��l����k�/p��b�8a8�r�'��
PHXo�Cr�Cs�Ct�'�$xCeH���CklZ��Ce�CoDQr�zu�Cv b�PI�CpP�0,���za,9i�Qu���a8Dd�e�Dg06itEj�Ek�El,FmPFn�Fp4Gr�Is�ItJv4b\Da�De�Di�Dr�DsD|u�#Ä4�kpDlxDr5D`5���!v�7�x6u�Ds,9D08�Ds�:M 6a=��De`=`ZIEkEr�Z���DaEdEe0Egl8EnLEo\Es[h[��[��[I(Er�]�^�H`�DEe��t`�
�b,b�TEilEk�b�{��|�o�Es�}@<Qv����PRm|k�Er����Ea�i�n�Es0���l̑���pa�EkFp@[�����@�EvF� �DȐ���E��bx����IFnȣ�� FaDFk4��ȧ@<Fl����9a��oxFs��u8�bdFeз4lFk�Fo�Ftܸ4��>0�b�Fr8��Fl�����Fe�i�Fl�Fp�rd�DX���b�Fi$jo���
x���Fn���
��@Ge����Gd\�IGn|Gs����$Ga�Gb�Gd�GeHgHitHk�Hn�Ho�Hp�HsIt,Iu4Iv@IyPIÄ��ēk�5�\�����Gr���
�����Gi��I�Gn�Gs�;tHv<���Gt�O�
l�b�Gr���
���Gg��~�Gi�<��I�Tg8HnPHrXHv���
l���0HgHHt؄��vH���D��k`Hl���hHa�Hl���Hi��'��@Bil�����Hl�Hr �I��Q��b�Ha��@�Hl�k����Hk�Hl��I������I�T�@0'e�s@�~��@�?r���IT���HI�hI�������`IdT�I(k����tIapxn�Is�Itl���j,%k�/p��b�8a�Iv��
���8a�Is�ZHb�Io���Ik�ItT�0,~Je8JjHJr`Js.�$Jr�.0Je��_�0�X1<1�@Jaܠi�1�|1�XJk���>e423505003003034032243054002451005024224002122302104000100222250020152045020300001205000032031001023442502202112231434050010300015000500010210042432454124521202432200203545244105001004204405020404125230443424452214030232100332022301202320105345040230102042000310524050254540002402453210500230104020500050141500003030001030024001242522040210303241001000102244000025210123010034002300204521040022321223200232401241440105140442010000001000433424100210030023212124030020300221040000042104004203252000405212220204024500220500000100002200443000430030002412010300240201300000121021404300040200442434240220432040015004501050020300024300230252500225010030012030022050300030203002243020030012520024200404341021033210402222235012010210412124022214401204021440400200022104203400041210452204010013010130210512125000010045000300200340041032000150020234103040430245205001240040200004211002025202252421023032030010052005101200230101410242124022210002051505012214440024203044232104441050002004504444212222413054010442025325024320040410212125210050224454201220403210002513212032210122101210040412125220022505345040104005050020005400002022050040054402500200500040050200342014000322045100202003400001100401000321442540402502102400022120101033000005245425405003251000501001010005000001050223030020030100001002411040330044220132043241300010040025200100210502011000440303022410004522002501000402123021050000004421040032040010010105050004003002021020100300000030041020010500003450003201002210002023000054502433021250210302143000202041030000043002050030040020110010200205250425020204005214301000300210200032042350021030040010000050300003003000210021005025000512200110510020000004000242032050250122052203120404504000022423050200041214300320500254100201420021000400	50000500050300212104400054010020242500021340234500000004003120004001030024202125212504502154002034420450104043004002000005025425212300104000000405212144000422201010043223032000304004500500012030002104042323050403022430001030400032001123250223022234500013002123440002303000405204004323025000440220120200010303003002545004000034105032020021012540325001202001004004101022020322214102302004001240030230002100243041032030030000301002004301004230054021032000042143400230220140104000432340100300002500022500200224105000200320201430000220003000100414000300225420024005450400502123052440002025400104502441450002340300200010050021210052010200200230500410500022010010010000201500300025012230203400223400000400210300022240300000304000030440220500001000410200021254000003002002430002030000201102052014540254200340010030002012502002302104042520222210001030140230300500002101424300154025003205000100500500320420404000300022030025040022430225000020030030043021010201200023402001000250002105421020000225232202210040544521430020130002002412	100001000100021010430100300121420002030540221010052140413000320400400300015000201003120450100001005000500210015010	4000002104014101201004103405202112040042425001030201PK
!<ps�hyphenation/hyph_ta.hyfHyf0tP�����'-4�������$�'–’-111001����������(���4�������&h�h�4�4�4�4�4�4�4�4�4�4�4�4�(�(�(�4�(�(�(�(�(�\�(�(�(�(�(�(�(�(�(�(�(�(�������<���
��������������������h�h�P�h���<����D���P�200210011100020000012001PK
!<����((hyphenation/hyph_te.hyfHyf0tP�����'-4�������$�'–’-111001��������$�(���4�����
��6������<�<�<�<�<�<�<�<�<�<�<�<�<�<�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�<�<���D�4���<�<�<�<�<�<�<�<�<�<�<������<�<�20021001110002001PK
!<U�B1��hyphenation/hyph_tr.hyfHyf0tP�����'-4�������$�'–’-1110010�������pa0b0c0d�e0f0g0h4i0j0k0l�m0n|o0p0r0sPt�u0v0y0z��$�D�pa�e�i�o�u�ì��a�e�i�o�uì�������X�������a�ce�i�o�u�ì��a�e�i�o�u��,�������a�e8i�o�u�ì��a�e�i�o�u@ì��a�e�ido�u�ì��a�e�i�o�ulì��a�e�i�o�u�ì��a�e�i�o�u�ì��a�e�i�o�u�ì��b�c�d�f�g�h�j�k�lm�n�p�r�st�v�y�z����
 b c d f g h j k lHm n p r s\t v y z0�<�P��b�c�d�f�g�h�j�k�lm�n�p�r�sdt�v�y�z���������b�c�d�f�g�h�j�k�lm�n�p�r�st�u�v�y�z����0�.��k0�e0������$�����D����P0�#���(�$��(��D��(�PPrp)�-���x����������1-$����41X-|1�-�1�--p1�4��������������)4$��$�4)���x�\�������X4|)���x����������4�)���x����������4���x���������4�:0�k��r0>pk�a21201111012200002112101201120101232302322030244114011PK
!< d�xYxYhyphenation/hyph_uk.hyfHyf0tP�����'-4�������$�'–’-111001\N�������''�-�'.@аш$�T'�d#'��Ѡ!Ґ#���$��	��,
����������@�h���X�������$`!-l�@��������z��z��{���"�8|�0"�&��|�0~��~��H����
`!-�є
`!-��Ѱ��@��؁�|��\��"�Ԃ��"��"��"�#�#�p�|��������
`!-�������
`!-�����
`!-�
`!-\
`!-�
`!-d#'��8Ѡ!Ґ#�$
`!-��@�����'��� $����"�H(�0"� &�<����Є�x����l*��`!-��є
`!-P�Ѱ���,���X�����"��(��"��"��"�#�#�p�|���(����
`!-X��l���
`!-��(��d#'��0Ѡ!Ґ#�$
`!-t�@�����'��)���4$��"�H(�0"�,&�*�0*��$�D*�x�l*��
`!-���Ѱ���,��*��*����"��(��"��"��"�#�#�p�|��������
`!-����
`!-d#'�|Ѡ!Ґ#�����`�����L����x���p%��&�����\�����$
`!-l�@�����'��)� $�4$�P�"���`�@&�*�L������R�l�l*��
`!-T�Ѩ
`!-�
`!- �а���,����*����"��(��"��"��"�#�#�p�|�������d#'��dѠ!Ґ#�$
`!-�д��@�����I�@� $��I�<�"����0"� &�؈�0*��$�����(���
`!-���є
`!-����Ѱ��������*����"�����"����"�#�#�p�|���P����`!-$I�0���
`!-���`��d#'��`Ѡ!Ґ#�$
`!-@��������P�t��4$�D�"�Ȍ�0"� &� ��H��Q�D*���4]��
`!-�
`!-N���l�������"��(��"��"��"�#�#�p�����P����
`!-�`!-�
`!-@��d#'��@Ѡ!Ґ#�@�����'��)� $�4$�D�"�H(�0"� &�*�0*��$�D*���l*��
`!-����,��*��*����"��(��"��"��"�#�#���|���4����`!-d#'��Ѡ!Ґ#�@�����'��)� $������"�Ȓ�0"� &���0*��$�D*���l*��
`!-�а��@������*����"��(��"��"��"�#�#�p�P��������`!-d#'P&�xѠ!Ґ#���t���*��*����"��(��"��"��"�#�#�p��%���*���\`!-d#'��0	Ѡ!Ґ#�@���'��'��)� $�4$�D�,��P��0"� &�*�0*��$�D*���l*�����,�t���*�,%��"�����"��"��"�#�#�p��%��$�*��$�h`!-@��ܛ���0� $�4$��?�"��0�0"��0�x	��0��$�1�����	���ȝ'�	�
Ѡ!���`!-``!-�0.D1Ѱ��1��	�P
�,%�\
�h
�t
��
��
��
�#�p%��%��$�����$��`!-L`!-�`!-`!-�1�x`!-�`!-p%`!-�	`!-�0.�U�@������
�X$� �,���D�X��=���l��������������'�
��Ѡ!���,
`!-`!-�0.`��1��`!-�0.�`Р`!-�0.2�h`!-�0.�U��`!-�0.�`�0�X�P'`!-�0.�P��`!-�0.�`!-�0.VЀV��`!-�0.�Vа����4�H�<��\
�\�t
�l�|���#�p%��%��$��>��$�``!-�0. 2��`!-�0.DWѰ`!-�0.x`!-�0.�`!-�0.p%`!-�0.@`!-�0.'\#-��0'p#-8��H'����T'����@���'��'��0��\2����"�H(�0"�d(�x	�l2��2��2�d���	���d#'��\
Ѡ!Ґ#�``!-�0.�2Ѱ���2�H
��
�,%�\
��(��
��
��
�#�#�p%��%��$��f��$��`!-�0.`!-@���f�@g��g� $��$�����%��%����d(�x	�3��2�3��J��	���d#'�
�4Ѡ!Ґ#�``!-<3Ѱ��$3�$��
�0��\
�h
��
��
��
��
�D�p%��%��$�|@��$�h`!-���@���'�l3�|3� $��3��)�T�H(�0"�d(�|�0*��i�D*�P��	���d#'��Ѡ!Ґ#�``!-�3Ѱ���i���P
�,%�\
�h
��
��
��
�#�#�p%��%��$�*��$�h`!-�0.@���@��W��3��3��3����"���0"�|m�H�3�L4�h4�����	����'XмѠ!�l�����4���P
��S�\
�h
��
���
��
���p%��%��$�DM��$�x`!-do��	`!-@������85� ����)�"���t��d(�H�L5��$�D*��S�l*���d#' �Ѡ!Ґ#�`!-�`!-�	`!-�0.@���'�����D��)�T���0"�d(�H��$�`���X*�t���d#'�А�8Ґ#�`!-,
`!-�0.l$`!-�$��,�`!-�0.�`!-X`!-�0.�5р`!-�0.�`!-��������
�,%�\
����
��
��"�#��p%��%��$�*��$�``!-�0.�`!-�5��	`!-�0.�6��6�@��x�������L��t���0"�d(�����\���T��t���P'��Ѡ!�|��`!-�0.h`!-�0.@X��`!-�0.�0��`!-�0.�p��`!-�0.�pѰ������
�,%�0�\��
��
�|�@�#�p%��%��$�����$�``!-�0.�6�L`!-�0.p%`!-(q�'\#-���0'p#-��H'��`�T'��p�@��TJ�������dA�t�,�0"�d(�<�P�\��$�<Q��	���'��|Ѡ!�,�`!-�0.,
`!-�0.�6�7�`!-�0.<7�h7Ѡ`!-�0.h`!-�0.�7��`!-X`!-�0.�`!-�0.���l�����,%�0�\���l�|��
�X�p%��%��$�����$�``!-�0.�7д7��`!-�0.8�(8�`!-�0.�1�'\#-0'p#-H'���T'�� �@������)� $�D�|K�"���0"�d(�*�3��$�D*��J��	���d#'8иѠ!Ґ#�@�����������t�0�0"��<�H�\�p�hH�t���'�И�8�,�`!-�0.�q��q�`!-�0.�8А3Ѡ`!-�0.�8�2��`!-�0.�8�X`!-�0.����`!-�0.09�``!-�0.d9Ѱ��l�����H��0�\���l�|�����p%��%��$�*��$��`!-�0.�9Ф9�p%`!-�0.�9�@`!-@��L������ �|�l��"�H(�0"�d(� :�L5��2�1�$��L ���d#'�Ѡ!Ґ#�`!-�9�:�@���'���:� ����B�T���0"�d(��\�L5��:�D*�H��l*���d#'�АѠ!Ґ#�`!-�:�@���'���)� �4$��)�"���0"�d(�*�0*��2�D*�@�l*���d#'М"Ѡ!Ґ#�@���'��'��;� ����)�"�H(�0"�d(�*�0*��2�1�,��l*���d#'h�#Ѡ!Ґ#�@��x���)� ���4��"���0"�d(�*�0*��$�D*�X*�l*���d#'�Ѐ#Ѡ!Ґ#�@���'����)� $����)�T�H(�0"�d(�*�0*��2�1�h��l*���d#'0��$Ѡ!Ґ#�@������0� �4$��)�"�H(�0"�d(�y�<��2� <�X*�l*���d#'���#Ѡ!Ґ#�@���'������D���T�,�0"��\���p����&�t���d#'�МѠ!Ґ#�h`!-�0.�4�X`!-�0.xo��4��`!-�0.5Ѱ��l�������0�\��l���
�#�p%��%��$�*��$�``!-�0.5��`!-�0.,5�`!-�0.�`!-�0.X�* �&��0��&(`!-�0.8�hш* `3ќD��\�|����* �" * 4.�&���4����&(`!-�0.���ш* �D����|��' 4.�&����0�<�H�X�`�,���l�x����&(`!-�0.�Иф) 4. $ 4.4$ 4.�5�" H( 4.�$ 4.D* 4.l* 4.�, �D��	�������������� ��* 4.06ќ* 4.|6Ѭ" 4.�( 4.�" 4.�" 4.�" 4.# 4.d( �&����<�p�,���x����&(`!-�0.4Ј�H( 0* �$ �D��\�|����������( �" �" �&(`!-�0.8��ќD�����&����X��!��:��&(`!-�0.�а���,�� �!��Q�x�Xx��:��"��!�#���p%��%��$�*��$�4$ �' �&��`��\��&(`!-�0.h�P;Ѱ�� ;�"��$����"��(��8�4;�x"�#�x�p%��%��$�*��$�L ,����`��`!-�0.��XѰ��`5���t5�,%��"��5��"��"��5�#�#�p%��%��$�*��$�!,�0.��L�|��!4���h��(�0.p����X�Dq���`��`!-�0.�� Ѱ���8���t5�,%��8��(��8��"��"�#�#�p%��%��$��K��$�!,h1��� �dq�|�*,8���r�hB�Hs�  ��Y��s�d ����`!-�0.( �t ��	>�,����Y�l �� �� �� �xB�t5,�(,�",``!-�0.� �(p���� ��\,���	�t�8:�Hu��u�H:�� �T:�tv�$!����`!-�0.� �8!�l*,",l!ѐ��	`:�,!�x!��w��!��!��8��w��w�X"F0"��d!��$>�",�8,�#,�#���!����`!-�!�HP ��t���!�*��4.�!��!��Z���!��!�t5Z :,��"���`��`!-�0."�0"��� �X"��!4�1.���@"��(L"�y,���d"����`!-l"�`��`!-���"Ѱ��\;��"�l;�,%�0��(��"��"��!�#��;�p%��%��$�����$��� �� �!,���"�� �`��`!-�"Ѱ��\;�#��;�,%��"�h
��"��"��5�#�#�p%��%��$����$����"�`��`!-d#Ѱ���,�p#��;�d���"��(��
��"�x"�#��;�p%��%��$�*��$����ty��9�� ����`!-�0.�#�H$Ѱ���,�L<��#�,%��"��(��"��"��
�#�#�p%��%��$�*��$�"��l!ѐ��<$��y�`!-�0.�_���d#'4)��y�8Ґ#���l$�`!-�%��`!-�0.�%��`!-�0.�%��`!-�0.�%Ѱ���,��*�<�,%��"��(��"��
��"�#�#�p%��%��$�*��$�����,�L<��*�,%��"��(��"��"��"��
�#�p%��%��$�*��$���d#'4)�(%Ѡ!Ґ#�%d|���%�����%��%�p%d����0��%��%��%��$d4���%��l`!-�0.ȭРl`!-�0.ܨ�2�@p`!-8��@p`!-@p`!-�H��@p`!-ܻ�@���'��'�����ؓ��)�"�H(�0"� &�*�0*��$�D*�X*�<���t`!-D��(d#'�&МDѠ!Ґ#��z`!-@���'��'��)� $�4$��)�"�H(�0"�d(�*�0*��$�D*��&�l*�~N'�G�p���H���G�̘�I�@N���0'���H'�ȝ��	��`''�-0*�L*�'�@����h'�(���T(� � ��(�( ��-��-�4.��(� )�0�X)���| '�'А)�Ҡ �������'�����'�����('�-h*��+�@(�����,(����4(�d#���L('�-4,�d,ф(�x#��#��p(��#��x(�������('�-x,�-�x-��(�P��`����(�l����(�P�X���('�-p.�)�`�p���(�|��)�����)'�-�.�L)�� ��8)�,��@)����L('�-<Cа.ф(����)'�-�G��.�L)���t)�/��)�x��)�0�,0�H0�*�L �X �d �p � �4 �@ ����L('�-�/��/ф(�L��L('�-�/Є(����L('�-d0Ј0ф(�4$��	��(*��<���1�
��D*��
��*'�*�
��X*��*��*�+�+�+�X�4+�<+�D+�|+�ȝ�������*�����*���X$��*'�*������*�����*� �,�D�X�(+��3�X�� +�l�����T+'p+�P��P��P��\+��P��d+����+'�+Ѭ+�d#�x#��#���+��#���+��(��V���+�4��+�����+�,� ,�>�2� 2���+�H�y����,�|�,��%��
��,,�L,�T,�0���%�3�$�4��\,��W�X����p,��,��,��,�T,��,�(N��3���,'�,�LN�XN���,�dN���,��3��3��,�%�4���,�L4��T+'p+���<-�P-Ѽ��-�\-�d-��� :���4-�!��3��H-���
�,Д!��!��p-������-�@���-�-�L5� ���-��-�h���-�-��-�l*������-��-�.��5��5�����.�H�.Ѭ��.�����-(.�P.������H.�`.������h.��.�h?�0�����lG��.��8��и���.��.��.��F����(�������.� :�(/����.��-�4/�`���-�.Ѐ/ѼX��X�� /�L ��+'T/�h/Ѭ+�Y�( ��L/�� �t ��`/�G�!���x/��/�Xx��/�0*�Hx���/��\�����/� ;�����/��.��/�x�0*����/�h���/�����-�/��;��#��0����- 0��$���.�x���-I�<0�y����\0�x0��-�<��#��#���0��'���`!-�0.���`!-�0.@��`!-�0.h�����`!-�0.h]�0є0�(�0.����0����`!-�0.���`!-�0.L^�d_�!���0.h1Ѐ1���01��1��0�8:��T1.� ��\1�`:��T1.8!��t1�4��!���1.����1��(�1��;(�0.�#���1�$3���0.4���1��1�D(�0.�(�0.����1�!��h1�@2���2�L2��b��8(T1.8!��42��(�0.�1���`!-�0.���`!-�0.cЄ2ѐ��xc��0��c�X��`!-�0.���`!-�0.�c�ld����`!-�0.�d�f�!��h1����2��2��(�0.���`!-�0.���`!-�0.������`!-�0.lhиh����2��1��0��(L3.��T3��	��`!-�0.��`!-�0.Hi�4���1���`!-�3����2���(N'`!-�0.�j��k�dN�,
��`!-�0.0l���`!-�0.4�<4��'��g��4.�
��Hl�4�04��l�3���0.4���1�m�X���P'`!-�0.Lm��P����`!-�0.�mЌ4�X(�0.|���4��n����`!-�0.�n�o��'��4.L5���0. ���4��0.����4�Xp��p���0.|���4���01�L2�"���0.��� 5���`!-�0.�����`!-�0.�0����`!-�0.0
����`!-�0.�5� ;���0.����5����`!-�0.���`!-�0.����4��;���0.h���5��'�$�5.4���5��0�8:��6.� ��6�!��6���$6�@6���L3.d6�4��!��P6.���X6� ;��L3.���p6��0(T1.�0���6��0���6ф	���6�1���0.
���6����2�L2�l2���0.�6�����6��2���6��2���0.\
��7�$7�H
�0.�%l�0.�
��07�P7�\7��%l�0.�	4.4���1�����0.��t7��7�t5���0. :���0.���7���01��7��!���1.8�����7��(�7��'�d(���7.�!���7��\��4.�����8�����5�t8��8�T1�\1��<8.h1��D8� ,P8�0"��\8�"��h8�x(�0.���`!-�0.�A�lF�L��`!-�0.��`!-�0.�
��4�07�h4���0.X���8�(�0.9А���8�` �1.���9���0.L9�|�� 9��4�01��T1.�7��@9��,��4.��X9�x9�L2�!���0.h1�@2Ѱ:���0.����9�����5��8��2(T1.�3���9�L<���9�(%���9��
��\7�tL3.lh���9�$3���9�4��:�h��`!-�0.�X�\YѰ:���0.�Z�d(��4.�:���0. ;���0.�ZЀ:�(L3.����[�t:�\���`!-�1��$��4.��`!-�0.�:�H�0.����:�P
4.����:�X��`!-�0.;��0.���;����`!-�0.���x��`!-�0.�*��`3ќD��D;����`!-�0.���`!-�0.�;ѐ���8��&(`!-�0.��`!-�0.������`!-�0.�x����`!-�9Ш&(`!-�0.�;�*��4.�&���;����`!-�0.���`!-�0����`!-�0.@<�P4.���4<�`��`!-�3�?��<'�<м<Ѩ<��?��\<��?t<���P�`����<�l����<��LL���=��<��I�����<����<�<=Ѭ?��t=�؜��<�Ԋ܊��=��=����=����$=�d��0=�t?�|?��H=��?P=Д?��\=��?��h=Ѽn�n���=��n�=��n���=��4���=Д%0��p��x���=��`!-0��=��>�,?'<>А>�H?��>���=��>>��) ���d>�>�0� >ШN���>�?�0>�����P>����X>�D*)���p>��>x>�>���>� ?��"l$����>�0��>�4���>�4$���N�>����>��`!-�>�f1|�)����?��?�|@7��d#?x#B�#��4?��#��<?��������T?���\?��>��>��t?�l|?��k���?��3���?�X�����?���H(���?�LL�����?�@��x���`!-�?��>|?�����?���`!-x�@��>G�>�� @��>(@Ь���4@�����@@�P&��L@��
X@�|��d@��`!-���p@����4@�X$�@Ѥ����@��z���L'�@�M����@�0�P�LQ�`�|J����$`!-�@�O��>�xA'�A��>��A��>A�������A����(A�������8A���X������O��`!-DA�@U�d#�x#�#���A��#���A�hHQ���Bќ��8F�DF��A��>Y�>���A��>�A�����A��)���A����A��"���AИ��d�����B� ���=�����A����(��H���"��0B�\��pK��(s�HB�|Ks�XB��KQ�B�Dt�X$@Ѹ����B������B��K���B��;���A�h���B��(���B�8���B��`!-���B�HKc�C�XK���B�dK��C�pK��C�|K�� C�8��,C��E��"khg��LC�$gTC�4g��`C�@g��lC�����xC��C�D�D�D�LD��D�����xA'�CмDєA�$K���C���'k�g���C��g�CРg���C��g���C� $u�$u�2u`kl�� D����(Dѐ��4D���@D�'{���XD�3��`D�8��lD��D��	xD�؈k�J���D��J�D�$3u,����D��D�<E��E��E��������D� ��D���������D���E�����E����E����$E�@�0E�lE��{���LE����TE�$���`E�\;��"��xE�\
�Eф
uJ�$F�F�,J���E�$��E�����E�����E��J������E��J�Eи�Ѽ�1�S��0F�F��{1���F��%1Q��0"QdP�TF�0��`T��=�*Q���dF��K�,?'�F��F�H?��K���F�xF��K�FФK1�K���F��F��F�|Q7�Q���F��F���1��1��1����B��B��G�xB�G��
��
�� G��
(G�
��4G�l���@G�,J�����LG�TJXG�(��DH��H'�G�GќH�\H��|G�hH�GМ���G�D�1$����G���1 H��xH��G�(�|hXm���G�dm��G�pm���G�|m��H�t������8��T��H����������������' H�$��T�����DH��`!-\H��1���<�H����H�T����H�H(��<'�<�����H���HМ���H��,���H�8���H��)�H�0���H��Il<I'lIМI�XI����I�p��D��L���I?�IB�I��DI��I��LI�Q�4P��dI�hH�L^��T�xI�1���IРM���I�H��ȝ?����	���I'`!-4PРM��I��B������I�����I���,?'`!-$��0��H?�{��xA'���SєA���`��DO�J�����T�LQ��T�l��$`!-,JЀ�ь������hJ�H
pJ�0���hJ���J�����L�����@��J��`!-�JЄ���������M����l��0T�8��Q��J��`!-�J�<T�l���hJ��
K������p��������\Q�K�����$Kм�ќ��HK��,��XK�8��dK��`!-$��pK��������K��J��ИK�
���N��K�4$���KЈQ�������K�h��`!-�K� &����K���L�
��L�4$��L�4M��$L���DM0Lм����@L��I���� R�\L��dL�@��tL'�L�J�8���(R��L���L�����L�����L���� R�pN�\L���L�8���(R�xN��L���L����M����M��)���L'M���$M��P��`!-4M����\L��TMф)��`M'�M�|���lM�̇������|M���
�����M�8����L���M�����M�����M�@���L'���M�)��tL'�L����M����W����N��N�,���W�8���4N��@N����LN����XN���,�����pN���NѰg���N'�N�
���N�����0��8���xN���N�����N�����N��"�@���N�p
�N������O�(S������� R�pN��(Oьz��8O'��рO�8���(R�xN��XO����hO����tO�pN��L���O��L�O��M���O'P��J���O��J�O�8���O�l*���O�xN��L���O�M�O�M��P�M��P��0���N'�N�	��$P�T����� ��Q�����)���N'�N����TP� ��Q�D��U����N'`!-D���N�������P�P�P�������P�`�P�p���P�|���P������P'�P�X���P'`!-x�М���P��$���P'�P�������Q��`!-,QР~���P'�P�Є���P'�P⠳��H��P�|@�lQ�4�����|Q��P��P���Q��P�Q�Q���Q'R����Q�`��`!-�QиP��P���Q��P�Q��P���Q��P��R��M��M���� R��0R�<R'�R���HR��,��XR��B��dR��B��pR�8��|R���R�|���R�@��������`!-�R�8���(R���R�����R�����R�X*Q����R�0*��Sа��4��S�|
S������4S���<S����HS����TS�`���`S��
lS�d��@��xS��`!-��ЄS�`��`S��`!-0�ШS�$���`S�0��S�4���S��������`S���
�S�O���S��T�t���T�������� T�d��t�����xS�(��x
�L������
�SѠ����lT��tT����T�����T�����T���T�8������T�h����&p��мTь�l�����T�H
�T�d���T�x
U� �l���U��
$Uа��8��0U���������zP]��XU�\]`U�h]��lU��0��xUЄ	���_��U��{����U��'�UЬ���U�L5���U� ���U��)����U�0"�UЬ���U�3��V�8���a�V��a������0V��z��8Vи���DV���PVаA��\V��8��hVи��tV��)����V�0"�VЬ���V�3���V�$����V��I���V�\H���V��b�hH�VМ��`b��V��`��W�TW�H�Wј�� W� ;��,Wѐ��8W��b��" &/\H��\W�hHdW��W�L^��pW�1���W�
���W�|j��	��`!-jИW�p;d���W��W��;�G����W��R ����W�<_���X�pa�dAg���X�D*��$X�TX� ��0X�,%g|��LX�dX���g@�q��lX�rtX��X���X� :���XЬ���U��X��X�Y�L5���X� ���q��X�r��)g�*0���X�0"�X�X*gt5q���Y�l*��Y���� Y�0",Y�0
��8Y�`5��DY���PY��r��z���lY��'tYаYѬ���Y�3���Y�|����Y�TW�����lY����YаA���Y�(Z��Z��8���Y�D*q����Y��*��Z�TF��Z�HZ�DFQZќ*q��4Z�|
<Z�T�<��D���TZ�P���`ZР���lZ��AQxZЌ�Ԥ���Z���ZѼ
���Z��3q����Z�"���Z�x����Z����Z�[ѐ����Z�8[�`[�Ԃ��[��'��� [�D[�0"([�"\H��\W�p[�x[�hHL[Ј[�؈�$�"d���[��[��W��[��W��[��|;�;����Y��[�0����[�<����[�H��[��"q@&/���[�$\�*\�,\�L�|��TW�x�DA��8\�dA@\Ј��L\�D*��X\� ���x�d\�h��`!-p\�@����\�x�\����\��$���\�]����|���\�D*���\Ѱ���\��%�\�����\��A���Y��8��]и��]����`!-���(]�"����H]��'P]Ь��\]��]��]����H]��]�0"|]��]�H(� :����]��*���]�0���]�؈�P�����]�X*�]�^�*��#���]��"��^�d��^���и���$^���0^Ѐ^ќ��<^��^��^�0"�p���`^�|���h^���t^��������^�����^�8���^��^�H�X���^�H(���^�����^�0"�^�8_�����_��'_А��_��*�� _�0��,_�4$�<���D_�H�L_И��X_�����)����t_�0"|_�x�����_����_Ф���_�����_Ќ���_���_м
���_�0~��f���_��f�_�
��`�P`�X�x�� `�p`��L��(`РJ��8`��JD`�x��|���\`��d`Є������|`����`м
���`��)���`�����`�0"�`Ќ���`��{�����`��'�`�,aѬ���`�pa�1�ȁ��a�؁��a��� a�Pa��#��#��<a��"��Da�@����\a�X*daа�x��|a�����a�$����a�|K�aЌ������a��%�a�h����a�t����a�<T���a��J�aф)���b�0"bЬ��b�3��$b�,|��0b�8|��<bи���Hb���Tb�&���lb��'tbЌ���b�"���b�4$�����b�p%�b�L�����b�*�bд{�����b��'�bЬ���b�0c�Xc��'����c�<c�0" c�D*��I����Dc�X*Lc�Ȍ����dc�,%lcАc��"�`���c��������c�<c��%�c��,J���c�TJ�cЈ���c�d�(d�0*�����c�0"�cЄ��,Q��d�<Qd�*�0��4d��"��<d�`��Hd�,%Td�|��`d��z�����xd��d��d��d��c����d��dќ���d�(e�le�����%�~���t^��d��c�e�e��"���p/�/��8��e�He�e��;9�#��4e��"��<e�\H��Dc�H]�|e��e�hHTe���DH��{�l���e�x�e����e��$���eЄ��(����e�8��e�D����e��P���e�<����e�8f�Df�H��eИ��f�`f���|��$f�D*��,f�4]��)���Lf�*Tf�xfќ*�|��pf��c��hE���lh���f�$3���f�4���f�4$���f����f���`!-�f�Q����f�$`!-�fЌz�ț��g�Pg�Xg�ܛg�hgф	��$g��	��`!-4gМ���|��\���`g�xg��Y��0����g��=�gм
���g��g���`!-�g�h�Ԥ��T��e��g���g���/@&%�>���g��>�g�$h����h��,�|��h�Hh�*c�$��4h��"��<hѸ���H]���ThМ��`h��h��$�\H��|h�hH�h�<���H]��h�H��hИ���h��h�i�0��l*�����h��%�h���c�$i�*�h�x�����i�����iА���xd���0iм
��<i�x9���Ti��*��\i�|��hi���ti�����i�X��`!-�i�*tiј���i����`!-�i�'o���i����i�ț���i�\j�ܛ�iЄ	��j��{|��j��� jј��,j��'8j�d��Dj�����PjЀP����hj�,%pjДj���`���j�����H]��j����jм
���j��j�Lk��k����������j�k���j�kр�����^��9��k��*�� k�=��,k�Xk�`k��=8k��"�#��"�`��hk�H�pkј��|k������k�Ԥ���k���k� ��0����k�<��k��k�����k�l�`��Xk�|��pf��>�k�I�P���l�d�l����$l���`g��f<l�D�x��Tl��L��\lРJ��hl��Jtl��l�@������l�̵�l�ص���l�����lЄ����l�1����l���lЄ����l���p<��lѠ���m�|@mЌ������(m���0mЌm����<m����|h��&tXm���dm�@��`!-pm�|���c�DO�,J���m��m��m��m��c�TJ�m�nш���m�dn��T%@�Ш�ј|�0~�|�������m�Ln�Xk�e�e��I����� n�x�(n�Ă��4n�Ԃ��@n����H]�0"Xnм����pn���xn�X��`����n���n�$����n�DH���n�\H���n�hH�nМ���n����Lf�p%�nИ���n�(o���Lf�4o�<o�*o�L���R�l*����Do��)Lo�0��Xo�xQ��po��o��o�Dp��$�h����o�x����o�����o�L��o�#�0���o�0"�o�p{����o��,���o�|���o��?p�@��p�@�� p�D���,p�T�8p��'�,%Pp.�p�x�<���hp����pp�`��|p����hp��*���p�|���p����p�"�,Q���p�<Q�pЈ���p�*�pј���p�x�����q��)q�4)��q����\a�Pq�$�4q����Q��hj��QXqА���g���pqиqѼ
��|q�0"Ep����q�|����q����q��>�p�����q�����b�t��q�؈������q�r��S�q�0r�(��@��`rЄS��$r�lr������@r�(�Hr�4���Tr����@���tr�L���|r����r��r�*�r�3�8���r�l*���r�����`���r�t5�����r�<����r�$����r�\�XK��s�dK��s�D*����4s�0"<s�t?�|?��Ts��?\s�����hs��M��ts��J���s��s��J�s�4tф)�����s�0"�s�`����s�l����sЄu�u���s�Hu�s������s�����tД���t�����t�<T�����(t�|���p�Tt��"�L��|���\t���dt�
��pt�����|tј���t�g��`��t��t��_�Xg��'�t�t�8|�������t�&p�t�@��u����t�(u�4u�xg���o0���u�|������"����Do��B<uДu�"����Xu�`uМ��lu��,��xu�u��B���u� $����u�*�uИ���u�������u��p�0"�u�v�L5����u��*���u�0��v�8v��E���$v��*��,vѨ
�x��Dv�@��Lv����Xv��v�H�dv��v� &���\H���v�hH�v�$����v�0����v�w�<����v� w�Dw�`w��R����v�*�v���v��o���w����w�8w��QED���0w�pYD*����Lw�|
Tw�4]����lw��tw� $�����w��%�w��'����w��w��r�*�w��w�@&��,�|���w�8���w��)�w���x�L��`!-x�0*����(x�0"0x�h��<x�xx����`!-HxМxє���\a�,�lx�����b���x�#���x�T������x����x�H�Q����x�����l�(x��S�x� ���x�h��`!-�x�Py�
���r�4$��y���$y��b�*0y��w���@y�������\y����'dy����4u���y�<���8f�H��yИ���y����`!-�yѰ���y��*��*�,%��"��(��"��"��"�#�#�p%��%��$�*��$��*0��z�0"z�$���(z�0���4z�<���@z�H�Lzѐ��Xz�Hdz����pz�h����`!-���|z��� ����z�����z�ԧ���z����z�����z�,
��`!-�z�0{�e�e���z�(e{Ѭd��{��2��{�\
��${�|�н��<{�ܽD{����P{�85��\{�0���h{���t{����{����{�
���{��{���`!-�{А���s��*���{Ѐ����{����{є�������{�@����{�O��|��@|�X�� |����`!-,|Є)����H|�0"P|А��\|�t5��h|��|���t|�4}��}�h��`!-�|�0~�����|����|����|���|Д5���|�|�����|�`b�|����}����}�`��}�T}�,%$}������@}�l���H}�t5����`}�l*��h}�(���t}�4��}�����}��}��"���}�H(�����}�0"�}��f�\
���}��}ќ���}��,���}�8��~��)~Ѭ��~����`!-$~�`��0��<���D~�H�L~�p���X~�|���d~���p~�x|~����~�X��`!-�~������~�*�~�`����~�0~���~�,J����~�TJ��~�\ш���~����`!-��$����$�H�,�p���8�|���DЀ���P����|�,��h�8��t�D�������8������x�����`!-�м���"������%�ѐ���������$�����|K��0*dv����tv$������0������<�Д���H������T��<T��`���Jl���"��B������B��ѐ�����t5����и�����x����Ȁ�L�Ѐ�x���܀�Q��������,%��\
�����ќ��$��������`!-0�Ф��x�|���T���$��`��\H��l��hHx��T�E`������l������������$���������,��`��`!-ȁ��rq�r�����r����s�����hB��( ����L �� ��X��8��8���)@�Ѭ��L��L5��X�А��d�����`!-p��*E`������0~����ј������'���h�����x�����`!-Ă�����u�X*�Ь����0*����������|���S�� ��$��h��`!-0����Q�,Q��P����X�����d�����p���{|���������ј������'��������� $��������̃�@������)�؃Ь�������`!-��Ј5��5����t5�������(��<���4�� ������x���L�����X��L�d��Ą��p��Є��|�Ѐ#������"�����d�����T���������X��`!-Ą���2�\
���� $���ј�������������d�����М��,���,��8��@U��D��dA�P�ш���\�����`!-h�� �Ѵ{����������� ������|����Дt������tą�� ��Ѕ� ��!��܅�����`��`!-��єu��8��Hu�Ѹ����,���'4�А��@�����`!-L������ȓ��l��ؓ��t��P&�����������м���������ȓ�����ؓ�����P&��Ȇ�0��؝Ԇ������t5������*����t5	����*��$���,��xu�8��<���?H����E���`���,��h��0��t��0"�������v�������T����������а��4$�����"�|��؇�T��`�����0~�����f�����f��
����4���w�pWE�W��<���W��D�ИW��P���W��\��X����h�����`!-t��Є����������0
�����`5����������h��`!-̈�XK��h�dK����pK����|K��8����h�����`!-�Ь��|����<���'D�Ѭ��P��3��\���$����t��0"|�А�����t5����и�����Љ�`��T}�����,%��Ѭ)E���܉��"����4)��܉�#������"������TJ�ш��(��D*��4�����@����L���8��X��d�М��p��Ȋ����`!-|���h������x��������������иI����Ԋ�$�܊�����`��`!-��D�ь��Lw�"���а�� ���%,����8��X*o���P��0*��X�Д���d��,��p����h��|�����`!-���P���v�\�����h�����lr���a��*��Ћ�0��܋��o�0"��0�����x��`!-���z����� ���p���(�м
��8�����g��'P�����\��,
��`!-h��ll�����$l���0l������3�����X��������`!-��д{����،��'�Ь����L5����� ����x��h��`!-�Д��3�8��4��t<����H��8���T�����`���Sl��`5���؍������D*��������4]�����<������H���И��̍�����)Ѐ�h]�����0���Є	�����'���������p%$�А��0�����`!-<�ќ*�0��X��0"`����l�������b���������������$K�����HK���XK�����dK��Ȏ�pK��Ԏ�|K��8�������`b�X��������`!-�����ț��,��ܛ4�Є	��@���L��!��d��܋��l�����x�������������������d�����hH��ь���а��̏��%؏И�����L��J������J�Ѐ�����TtDt��(��xBQ0��t ��<��L ��H����T��`��`!-`����@,Q�����<Q��������Xg���Дt������t���� ��Đ�(��!��А���������"�|������K�Ѹ����$!,��0"p���4��|���<����H��8�T��D���`���P��l���Q��x���Q��Ќ��Ԥ��������Ѽ
������:����А��ȑ����`!-ԑ�<��4P���l��I����\H����hH�А����� ;��$�А��0��p�p���H��|���P�Ј���\����h��
��t����`!-���*�������0"���X��������`!-�����|��ؒ�*������h��`!-���l��`����H��ј��(�����`!-4�ьz�
��P��$
X��DY��d��PY��p��\Y��|�� :����������P��`��`!-���3.�
���������`!-ȓЌ�<s� :��������*����0����0"��8��$�����`!-0��0������v�P�М��\�����`!-h��(��������H���А������*�����d����������X��Ȕ����`!-Ԕ�`:�8!����!����������*����@����p%(�ѐ��4�����`!-���@���,�|��`���?h��@��t��@�����D������T���������X��`!-����6 ���̕�x���ԕ������\68��|������(����4����@����(��04����<{��
L����X��*d�Ѐ#��p���"��|�������������рP�������,%���|�����1��̖���ؖ�`��`!-���%D�
�����3���Ќ�������`!- ���3QX��<�����`!-D��!Q��`��`��`!-h��'P��<l�h��������`!-���*l���������
�����O��ė��@��ԗ��"lи�����>�4$lT�����p�
�Ѐ������)l���4��������$��,����G-<��<�Ѱ���K�L�<L�dL��L��L��L�M�,M�TM�|M��M��M��I��J�\����G-<��<�Ѱ���K� L�HL�pL��L��L��L�M�8M�`M��M��M��M�J��J����P�l,�l0*l�$lt�l0	��4��L����l�N�>��T���>\��������
t��Ԥ��������Ѽ
�����0*�������0"���L���ę�X���Й�8��ܙ���X ������t����|���������
��$��� 0�����<��(H�А��T��h�����,�l���^�������|@���4�����4$���Ф����<����b���К��,��ؚ�8�����?�ф	������������ �М��,��l^��D��*L�И��X���,��d���B��p���B|��h|�����t|����Є|������|��<Ь�ј���������$`!-ț�\��D�nP�����\����h�����x����������l�(��ȁ��Ĝ�4��؁��@����P����z����h���5��p��(8��|������|�����0������<������L�����'��М.0*���������Є	�������������М��(�� &p4��|�����L��$�T��0���`��<���l��x��������*�����x������I���p������'~\#-���J���̘��؝�0'�p#-��H'����T'���� 9�09����p ��|���,������8��D���D���&tP����\��l�����t���`!-|���f��f������f���
�����\2��������Ȟ� $��Ԟ�������`!-��T���|�����X��h���$��t���,��<T��8���JD��8��P���	\��ȭ��h���%lt��
�����4$����������*���x�������L�ğа���П��P��ܟ�0*kP�����d�����l����������P&��$�����0������
0��d����H���*kT���d����k�@��x���@���X�������H(�����`g�<l�����Hl���4��Ƞ��3��Ԡ�tb����8�����������S�� ����*��$�Є�n����<������D��<T��P���J\��0���h��<���t��ȡ�l*ndP�����DFQ��аA������8�����0������L ���ԡ��*��ܡ�0����0"��|�����������P��"���(����0�ѐ���<���5��H��(8��T���`��|��l����x�ј��,����$`!-���1uh�����\��И��Ģ��,��Т�8��ܢ��`!-����D�����T�,&p�Д����� ������������Tp@�Ш��,J��L��TJ\�Ј��h��D*��t��<��H���������T������m��m�����h4�����ܨ��ȣ��%lԣ�x�)���������Ѐ��$)�J��������J ��"�0^��<�����<^D��L^��T��1��`�РM��l���I��x��8|�r�X����� :������������������d��Ĥ�����`!-|��Ԥ�؈dv�����tv��� ����!����D���$�����0�ф���<���J�ܥ�H��$���T���I��d�����p��x���,|����������\������h���������������ĥРJ��Х� ;n�����4�����@������L��������:�� �А����,��� 8�И��H��(T�ѐ��`��0*��l��dKpK�����|K���8�����t��Д~������~����И��Ȧ��'Ԧ�Ȓ#������M��p�����Ȓ,������M ��̘��,��`7l��D���\L�а\��X���\��d��h��p��x(|��������X������8������`!-ԧЬ�р^��������̧�DHCxZ�����ZO���Y������Y����t ������L �� �����0�����<��d��H�����`!-P��T��4�[@���x��L���������|h���Єh�����Єi���������Ĩ�X��Ш��$������H���p������|����������'̩� �ь��,����X��������0�����t<���ќ��`���,��p��8��|�����`!-Ј�ј2s�{������{����И������%}�
��ة�4$���������p%���
����l3������4$�0���,���=4��Hi��@��|3��L��Q����d��0�l�А3��x���3�����@�D������T���Д~�����LQ������@��Ȫ��@Ԫ�X����H(����d��p�����|��Ј�����|$��l;��"��<���"��D�р���P��TJ\�ш��h��D*��t�Ј*�x������
���8������l�Ь�ќ������i��ȫ���ԫ�����?k�?�����?��Є	�����������,��$��8��0����<��X��H��H(��T�����`��`:�8!��x��!����Ѭ3�������И��"�������,%���0��Ȭ�L5��Ԭ������$�������H���p�����|�������$���'0�ь���<������H���0��0��`��L5��h��8��t������ ������x�������������)��Ќ������|��ԭ���ܭј�����'��d����������ț����ܛ$�Є	��0��<����H����4�����$���*��T�М��0��p���`!-���\b0������@������������'������Ȯ� Ԯ�,n������ܽ�������85�����X����t�� :��$����0b,���D��8���L��q��X�����rd��оbܾ����������Ѱn�����������DY�����$��PY��į�\Y��ԯ�`��X�n8�������"n�$n,Y����8Y��*����0��l���8��`��D�����,%P����<���l�����t������D�X*��Ь�����L5����М*0�����0"İ�,��а�8��ܰ�D�����r��( ����d�������L ����8�фb���4��(s��<��XB��H�����hBT��4;n���p��X���x�Ѭ){��АY������Y����� &�������0"� T��ȱ�0T��бЌs��ܱ��s�Р�nxZ�����ZQ���Y�����Y�� ��t ��,��\���k`��H��,%P���u�����uh��� ��t��!������������p�� [�([�����8[���Z�������`:��̲�8!��ܲ��[��[����`[����"�7����<�ш��(��D*��4����@��,%L�М"��X��x��d��л�ܻ��|��@&p��Є���������������������8���ij�l*��̳���س�����|����`���4��@��t�
�Є������J|�$��0*lT�����L���������,���
`�И��d����X��t��0	�����L��`�Qh������t������x��|���ȴ��kдмj��ܴ��3����4��t������������>���b�l���k��(��lt��@���H�Ќ��T���`��x��l��@��x��P������d���������� $�����\H�����hH���|���̵�����ص�`:�8!���������������l�����`�����>���4������<�����H�����T��d��`����4����8���u��?���j�����h������t�������������P������d�̶����ض� :�����$��������4�������>L>X��,��D���:�����H�L��p���X��|���d����p��`b|��V�����������������`!-���r��q��ȷ�rз��X��ܷ� :��������������|�����K�Ѹ��$��!n��<��؁��D����P���'\��0��h���"��t�м���������������������$��,���
���и�0	��L��0"3���ܸ��*����0����0"�������P����X��� ��d�,��p���8��|���D�����P��$�\��0���h��<���t���������`!-���P�E��������������������M@��ȹ�p���Թ����'~\#-��P�P�����������������M@��$��̘��0�����0'�p#-@��H'��P��T'��`����� ��x��ĺ�̺�؈�����ܺ������� ���`!-��Ь)�t��,%�̈��Ժ���*�T������d��������,
��`!-��0"]���(���0�М��<��8��ܙ�0*����`��0"h��L���t��X������8������)��Ѭ�����0"��������ĻМ��л��zn�z�����z���z������z����l����x �����,��d��`��Xg��t��P���tX��� ��d���",p��"��|��#�����#�����Ԃ�����|����� |���,|��ļ�8|��мИ��ܼ�T��	�$`!-�м
��ܽ����d�����hH��|���(������4��|�n��L���'T�Ѭ��`��3��l��8��x��������p��� 	��������Ф������@��������Ľ���н�D�gP�����\���h������x����Ќ����� �м
��,���)��8�����D���
P��0��\���`!-h��s������l��И�����X���5��@�Ф��(8������ľ�|��о��	����ܾ����8����$�}0�����@�����������(�ѐ���4��x ����L��8���X�����d���Sp�� ��|��,��8������D������P������\����h������x���̿�DA��ؿ�dA�Ј����D*���������,%����� ���$��,��$��0���D��<���L��DA��X����dAd�Ј��t���R�������������D���`!-���D��P������\���h�����x�����,&������0"��д�����������̈�� ��؈��,�����8��8��`!-D�а5�����d�����l�Ѱ��x���M�����p�������5�������������Ѱ������M����̘�����*#�����*,�����]�|]�����]$��h]��0���0��<��t���H������T�Ќ�����l���%t�И���������|z���������'�0�����q�l���x�Ѹ3�,|�����8|����и3�ܨ������%l�Ф{���
������4����0������)��8�����H������$��,���
T��<����������p��|��'��������������$����X�<��T��H������T����4P������I���������������0���&t��t��Xu�`u��4��lu<��xu��H��4$����`��0"h�����t����� $�������0*�P������d���Ќ���������%���f������2�����\
����� �������Љ�Ь�����(���$��&
���<���'D�Ќ���P������\��D���x�h��po����xo�����p���L5nȁ�����؁����������(Z�Y������Y�����t �����L ����� �����,���ؒ����$���,�����8�����D�Ѱ��P���%\������$���p��$�������H���А�����H���Xg�g�����$g���4g�����@g����Ф�������������Xk�8k����Lk�Ѽj��x��$���3��0��X��@��H(��L��H]��j��d���jl���� ������,����8������L������X���������H���������
�����t�%��������������0	������
�ф)���$��0",�Ь��8��3��D�Ф��P��������\��
�|����|������ѐ������ ;����А������*�����|��������`!-���<Q�ш��$��������`!-����������� �����,������(���8�����������4�����L��Th��Xg�`ht��lh�����$3�����4����������T��@&p��Є��@D������T����x������Q���������,%��TJ(��d��`��4��0��؈l|�Ј�l�ll��`��T�������h���$����|��H����p������|�����������`�%p������|�����Ј�����������
�����D���h]��������0���Ќ�1����,����4�����@���2��L�Ѥ{��X���{��d��P]��p��\]|�����������$���
���<��8��0�����8��P��l*����� &p��� ��������� &p��������������@&?��,����4��ld��@���2��L�����X�� $��d��̇|M��|���M��РM������I�����|�����4����АѤ���
����������,�������������ј2G����$��4�� ��@�����0���D*���L����T��$���`���I��l�РJ��x��Ĥl���Ԥ�������М�Ѽ
��4�4������)�������`:O8!����������������Y �����x����ь�����(�дSE���@���]�H��|]��T���]`�����l�� ���x��4P��l������I�����0��<������H���ѐ�����H���@�����l��dA����������1�����4������������|��Ժ����4��|
@����0����q�l\��x�фh��Ԋ��h|��lh��������$3�����0�������I���а��Th���t�`h����g��g������g�����$������xc����������������$����0��|��<��D*��H�ь��T��"��`�Ќz�i��x��j���j�����h��������%<T��������������
���<�QD������P������D*o������%������������� ���Z��0���Z<��Z��H��`:��T��8!��`��0������!�����l��D������������8�k������������S����p\��d������ ,���� ���
�h�����h�������� �,���������$����,����8����D��
��P���\��4��@���t������|��ľ�����о���ܾ�����������Ȍ��������\
���D���������������(������,Q����<Q��h��� ��x���,�����8��L�D����P��T:��\��|����t����|�Ѽ
�����TP�����dP�����DFQ��аA���������8����и�����$!,��ј��l�kD�������$����"����,!,$��T�`���<��	k�x��P���xX��|�k����p��TJx�ш�����D*�����B�����A�����n�����*����Ѩ����������М"������!,��Ѭ�����������М��$���,��0��v��<���uH��� ��T��!��`��D���l�����x�ш"u�"�����|��������$����0������<�������������B��А������*�����|����H
�*8���ѐ����0*��,��D*u�9��D���L�и�ј��X������h��X�k8�������ѐ�������5����Ф9���������k�������)���������"�����l*������0"�������� ��|��,��D*��8��D*����P��t5��X�����d������p�Ѹ���|��<
���\H���������P�����Є��������)��������� ������4P������I�����&�l����x���� ��\,����8���$��D��<��D���\��P�d��\���p��h���|��������(�����д{����ј�������$`!-���<QQ���������м
�������������&p���g�g��(��4��0��4��<���3��H����,&���d��0"l�Ј��x���$��Ѐ������(m����0m�����<m���Lm�����L4����Ѭ)ğ�0������"����ШS������S`���Ѽ�������D��4M����DM8��l�!x���P���j�X�Ѡj��d���jp�мj��|��\������3����Ѐ��0������Lk�����ќ*�8k�����L��m�����m���<4��������T�����$l��0l�� ���3��,�Ш��ܢ�k��H���jP��Do�Lo��h��Xop��do��|�����`�+h������t�����Ѐ��������������T������~���`�6h������t�����Ѐ���������@������ ��P��,��`���<��l���H����Cȓ��`��ؓ��h��P&��t���I���J��� :��u�����v�����v������u���� �����!�����D�����������фS����x�������� ���$����0���`!-<��Q)0���T�X���enTe��p��lex�Ьd������2�����\
������)k������0"��Ь�����3�����T���������7K�7����<��������$�И��l��0��$`!-<��8���������`!-\��8�����4�|�����������И������,������k`�����,%��������*�����8kD������T��������$���Ќ8����4��l*��<��p#u�#��T���,k�=��h���=p�Ѽ
��|���9�����L��������H�����X�������h������t������0~k�@������@���X��������8�k�������S ��p\�����,���\��8�м�n���T��,%\��0��h��L5��t���:��,��L�Q������2�����"�����"����и������D*[�9��������И������������d����l���`!-��"D���4��t��0���H��<�P�����\��85��h��]�(]�����4]�������������А�����������8FQ��аA������8����и������	����������|���������$��d��0���`!-<���a|��X����`�Ѹ���l����x�М�����x�k������L����������$����� �����<���������������`������`!-��hHg���� ����(�Д���4�� ���@����L���`!-X��pzm|z��t���z��|��
��������$
���0
�������${Q0{������z������Q������<��������������<������ ��ț��,��ܛ8�Є	��D��P�Ј��\���R��h����t��*���T����������ф	������	��`!-����^��t_��^���L^�����1����РM������I�����,Q����<Q�Ј�� ��p,��|���8������D��d��P���`!-\��,�u8���x��D������P������\����h������x��������������А�������5�����(8��������� �����x�����������`!-(��������$�D���"�`��\���
`�d����\��\
���`���\��X�
���(���\��$���\��0����0"Q0������"�����$������0����4���������������
���
���d��<��T�H�є~��T���~��`��|
@��|
@��p��|���������м
������������P&��������
����"��������TJ��ш�����D*����؁��� ���'(��0��4���"��@��x��L��L����
��X��Ԥ��h���x�Ѽ
�������`!-��Ј*�0�����0"���4������@�����ЄS������S��� �����*�����������4$��� ��*(�И��4��t���@��H(����X��0"`�Ьd��l���2��x��\
�������������������
���Ԥ��������Ѽ
��������X��������l^��А��X���[,��������lN����0����0��*H�Ѐ#��T���"��`��4M��D��DMx�м�����������������`!-���D�^�������������������������������`!-��ЀN�����L� ����,���$��8�����D�� $����\��X*d��$~��p��0~��|��l������
��Ь�ԧ���������������� $����� �������l������|
��Р�g,Q����<Q�Ј��$��D*��0�����<����H��$�����T���I��`��*��K��|���K��Ь������(����М�����������']�����������������d��������'����������������Ь��~��~��(���~0����<����H�Ф������Ȕ`��Ԕ��l�����x��������I�������$����`������l������`�����,%��������"�� 
���Ѽ�^������B��L�����X���(��8��4���`!-@��0����0"\�ф	��h���	��`!-t��1[�������������������1Q���������������������P�����|
�����Ѱ�l�������|
��Ѱ����������,��ȹ��<��8��ԹD�����������`��$���<��l��0�x�������.�����|������p�������(���������8��d������������'��~��������Ԃ�����0"�h�.hH�H�����0��X��P��`���%�p%�*��'�Ȋ�����"�8��|��p�Ql�������xS
���l������x����h��t������������������
��иR�����*�Ѐ#�����"��(�Ѡ�l����@������
H����`��d���l�ѠM��l�x���I������"gd�����X*��Ѭ�����0*�����P������d���������� $����Ќ��d��������t���������(����^DA��@��dAH�Ј��T��x `��H���l��T�(x��`������0~��dА�ќ*�|�����*��д�ѐ�����0*��H�������������T�(���`�����0~����l����x$����0���$��<��؁g��T���'\�Ѭ��h��L5��t��|������������������������ ����,������8�������������S��А
�h7��������������И��$���'0�Ь��<��p���,�8��X���?`�ф	��l���'��x��������"�(������$���������������д���ќ"������"�����l������D*l�����p
��؈�\H��$��hH,�М��8��@���D�����\��\
d����p����|�� �����؈��������|�����������|�����������̧�~��$~����0~���И�������$`!-��D��;��#��0���"��8���B��D���BP�ѐ��\���*��h��d��t��<Q���������������'	�$����.X�������
��$������������*�������l�����`�����`!-$��lu	xu��@��<���H��H���T��T�`�ќ*�����x��|����$�]0������<���������������{�������'��4)����#�����o����0���������������$��@�l�*�|��D��*L�ѐ��X��0*��d�ѠJ��p���J|��$�������I�����X���������
���(�����������p��0*���Ь"����������T�����`�����l����� $�|M��4���M<��D*�����T����\��`��l���t��x�|�������� ��������<�$�����,������������x����@����@��������f��
����4$������$���`!-0��<���$��H�L��h���v�t�d�� ��p��؈��|��������L5	������*�����h������t���� ����؈����������&z��4�����X(��|�����`!-���hѸv��v��@��tvH��� ��T��!��`����l�����x��|������v��@�����tv����� �����!��������������|�����&	`!-��є`!-���tv���� ����!�� ����,�����8��|��D���v��v��\�����tvd��� ��t��!����������������p@�����|@���*P���>������l���p@�����|@���w������w���"n|�� ����(�ќ"��4���"��@����L���'X�ј9��d���������0��H����p��4ј�����p�|�������k��мj�����3����X����X�м
�����9������|����0��� ��<���,��L�8����D�����P�Ј���\����h��
��t�����Ь)�������"�����������x���p\�����\����,�b8�����&p��l�����x����������:�� ���Ѡ�,	����@��|@H��4��T�����`�����l�����L�x���btќ����� &p��ь�k������������;����t11�1����01����D1������	ѠM����I������(�����H�4�D*����T���\�$���h��I��t�P��\�����h�����t������������Kk�K�����K�и����l*����D*k�����0"А�����5��Ф9��(�����4�����>	����P�ĐX�А��d����p����|�l�������������t|n�|�����|���ј�����'�������"������k�������#����(��(�������@��H����T����`�(���l�4�x�������8�����n�����$���0�����<�����������B��ԑ���������`���!����܋��$�t|Q�|��<��|��D�t|[�|��\��|����dј��h�`�p����|��dј��h�`�������P�����XK����L�dK����pK����|K��8����l*��И������������(�8|�0^��@�<^H�L^��T�1��`РM��l��I��x�8|�0^����<^��L^����1���РM�����I���������@������\H����hH�Ȏ���p��Ԏ������0��<����H�4]��T�015��l��tќ���� &p�������L��������\�М�����,���Ь������z���z�����z��������:���L���(�X���4�~��@�~L�$~��X��2���<7��p�01L	5������ќ���� &p�������L��������2����
����H ��H�����T�(�`����0~�� ��:���А��8�� DИ��P�(\ѐ��h����t��������p��"�������p������d����8��@���0
����`5���������<���������$��� ��(�؈��4�01^	5��L��Tќ��`� &pl����x�L�������l �������(�������������������Ф���������0����0"	�01n	5��,	��4	ќ��@	� &pL	����X	�L�d	���p	��:��|	И9���	���	И���	��,���	�8���	�4��	�����	���	�\����	���
��
���
��$
����(��"��<
��#���	��#��T
��#��`
��"��l
�01~	5���
���
ќ���
� &p�
�����
�L��
����
�T:���
�h1���
�x9���
�d9���
��ј��������d��(��`!-4�|���������P�d��\����(��*��t�|�����K�Ѽ���������ј����$`!-��`�$~����0~���и��������,
��`!-�*s����$�@���,���8���DѤ���P��z��\И��h��@��P��@�����@�������@��*�	������@������������Ѥ������z���И����`��'
Ќ��
��������@���,
���8
���D
Ѥ���P
��z��\
��f��h
��ft
�g�\
Дt���
��t�
�(����4�
���
������
��K���
��K�
���
�����h
������8���$�lh��0�$3��<�4��H��{��Tј��`���8��x���lh����$3����4�����{����t5������<�����������)�������"���8���������r��)(���4��"��@����L�����L���d�*�	��|������|������������Є�������������������*�	���������|���������\Q��(�$K��4�HK@������X���`а���l�����x�����<�����������а����������������`!-�Є%�5�������������T��&p д������p<�p����T�@&p\�poQxo��t�p|�p�	����� &p�� &p\������l2[h�����t�����0�����,��S�����T��0"�����4]�� �8��h��l*��8� ��D�H�	T��\�`��d�l��p�x|������,�|��"������|����*��0�����0�����n�����0"��0
����`5������*�� ќ*n0��8�0"@����L�(���X����d�X*p��b$�����0�����<�����H��ј9�����;����р#����t���k�������ѐ����*10���Hs �( ��,�L ��8����D����P�|��\�*h�~�	���������а~��~�����~���~�����~�����~����(����0�����<���H����T����������$� ��0��~����H���TИ��`�$`!-l���`���h�����x�����'l�l��`��
���`��
��������`!-��I�P����d��h���$�t���0�h���$l�t���H�0���T�<�`�I�P���x�d���h�����t���������,%�Ј*�0����0"��������(�	@�����L�������:��И9�� ������,�ј��@���	���\��d����p�(���|������H��РA�	�A�����8���и����l*���Ѭ��	x��������А������5��Ф9���X�t
���,��R��4���@�*LЀ#��$��"��d���p���|� ����`�
p�����|����Ј���������0��X���0���ф	�����'������������DO���l����(��)��0�0���<����=H�@��(��L��dѠJ��p�|z��(��@���Ј��@������l���������M����TP��0�dP�����DF��аA����,J���TJ�$����� ����,����8��`!-D�0���<��=`м
��l�����`!-xмJ|�
������`!-�д@�����@�����@��'Q�p��p������������$��������
�-$����w��p@�p��L��X�����p��$��xѼ���xѠ������xѨ@����LQ��x��P��x�4M����DM��d������\Q����Q��x�����p�����ќ��������4Јќ���D�Q��T���`��
�-l�p������H�
��$ 
�%�����$������Xg��Дt�����t��� ����!����D�����$6���Xg�$Дt��0��t<�� ��H�!��T�D���`�`���l����lф�����8������� ���܅�������������������8�����`!-��@U����dA���0��� �@���,���8�����P��'XА��d�t5��p�̈��|�؈���рP������,%���$�����"����D�����P���\�����h�����P�.
\����h��������(���(Ќ�]�
��@�4$��H���T�ܼ����l���t��U����l��8
�F'�-!�h-�()�xG�;
�?
$
�-,
���-l$���-�$�����
�-\
�-����-�
�-�
�-�
�-p%���-�&���-�
�-�
�-'���-0'���-H'��� �T'��� ��B
���� �h!�p!�x!��!��!�"� "�("�L"�T"�\"�d"�l"�t"�|"�$4#'� ��!ш!�P#�@��� ��#�&��'��*�$,�x.�t/��0��3��4��5��6��7�09�\:��H
��O
�zO
�zO
�!O
�!���!�l$��`!-�$���!��{O
�O
�O
���"��"��"��"��"��"��"��"��"��"�#�$#�,#��!�<"�D"�"O
���`!-8|O
0"O
�`!-�O
�O
&O
�|O
0~O
�~O
O
HO
�O
@�O
؁O
|�O
\O
�"O
L��`!-ԂO
�"O
��`!-�"O
x��`!-�"O
���`!-#O
p%��`!-#O
�&(`!-pO
|O
d#?
x#S
�#��<#��#��D#��X
'~\#-�^
0'�p#-H'��x#�T'���#�ܛO
�	���#�$�$�$�,$�@$�"��$�("��$��$��$��$��$��$�%��	���%'�#�P$ш!Ҭ%�B
�0O
 $O
,
��`!-4$O
��`!-�?O
�$O

��%�%�%�$%�8%�@%�H%�P%�X%�`%�#�h%�|%�H$��$��$�\`!-�0O
��O
�$O
�`!-�0O
x	B
�0O
�$O
X��`!-1O
��O
�	B
1O
�	B
P
B
,%O
�`!-\
B
h
B
t
B
�
B
�
B
�
B
p%O
�`!-�%O
�`!-ȝ?
�S
����%�����%���O
�
���%�$&�,&�4&�<&�D&��&��&��&��&��&��&��&��&��&��&���,''�%�L&ш!�H'�
B
X$B
 B
,B
�O
����&��&��&�'�8%�'�H%�'�'�$'�#�h%�|%�H$��&��$�DB
XB
�=O
�>O
�B
lB
�B
�B
�B
�O
�B
�B
4B
HB
<�O
\B
lB
|B
�B
�f
�k
���4'����<'��'O
���T'��'�$��'��'��'�"�@(�("�\(��$�p(�x(��(��(�%�,
��4#'\'�'ш!�P#�$`!-�'O
�	��`!-B
\2O
��O
\
���(��(��(�$%�8%��(��(�P%�X%��"�#�h%�|%�H$�T(��$�H(O
���`!-�fO
d(O
@��`!-l2O
�2O
�2O
d�O
�2O
H
B
�
B
�(O
���`!-�
B
4)��T'��'�|)�$�,$��)�"�@(�("�\(�*�(*��$�<*�P*�d*�l$��4#'�(и)ј)�P#�$��)�@���'��'��)� $�4$��)�"�H(�0"�d(�*�0*��$�D*�X*�l*��)O
��`!-,B
8���)��)O
�`!-�y��x*��*��*�$%��"��(��"��"��"��"�#�h%�|%�H$�*��$�*O
�`!-*O
h��`!-0*O
���`!-D*O
���`!-X*O
�`!-l*O
���`!-�yO
�*O
`��`!-�*O
���`!-�fO
�
���*�+�+�$�$+�,+�|+��+��+�\(��$��+�x(��+��+�%���4#'�*�4+ш!�P#�@gO
�gO
�$B
��O
4���+��+��(��+�8%�@%��(�P%�X%�`%��+�h%�|%�H$��+��$��%O
�%O
��O
|@O
3O
3O
�JO
$3O
$B
0�O
DO
�O
����+��'�@,�$�H,�P,�"�@(�("��,��,��,��,��,��,�d*��4#'�+�X,ш!�P#�O
�O
B
8���,��,��,��,��"��(��"��"��"��"�#�$#�,#��!��,�D"�(O
 &O
<�O
�O
ЄO
x�O
�O
�,O
���`!-�O
X�O
�O
P&��T'��'��-��-��-��)�"�@(�("��,�*�(*��$�<*�P*�.�\4#'-��-ш!�P#���H;�d<��=��>��?��@�hA�<B�C��C�8D�<E��E�L-��1��2���O
O
ؓO
�B
x�� .��*��*�(.��"��(��"��"��"��"�#�$#�|%��-�*�D"�<�O
t�O
�O
���T'��.��.�$��.��)��.�@(�("�\(��.�(*�/�<*�/�%����4#'0.Ь.ш!�P#�l3O
|3O
�3O
��/�/�%�$%�8%�@%��(�P%�X%��"�#�h%�|%�H$�*��$�TB
|B
�iO
PO
�iO
�B
�@O
X��$/��/��/��/��/��/�"�0�("�0�0��+� 0�(0�00�%����X0',/и/ш!�t0�WO
�3O
�3O
�3O
��O
���80�/�%�@0�8%�@%��(�H0�X%�`%�P0�h%�|%�H$�0��$��B
DMO
|mO
HB
L4O
h4O
��O
�4O
�SO
B
�O
�?
P�S
`���`0�l���h0��O
����0��'�|)��0�,$��0�"�@(�D1�L1�*�(*��$�<*�T1�d*��4#'�0�0ш!�P#��O
O
0���,��*��*�(.��"��(��"��"��"��"�#�$#�,#��!�<"�D"�0"B
,&O
xO
�O
��\1��'�|)�$�,$��1�"�2� 2�02�*�82�@2�H2�P2�d*��4#'d1��1ш!�P#�PO
|���,�X2��*�(.��"��(��"��"��"��"�#�$#�,#��!�(2�D"��O
`O
�B
@&O
L�O
��O
�RO
lO
�O
���T'��'�|)�$�,$��2�3�3�("��,�*�(*��$�<*�,3�d*��4#'`2��2ш!�P#�DO
0	���,�43��*�$%��"�<3��"��"��"��"�#�$#�|%�H$�*�$3�,�O
P�O
�$B
�O
t�O
��O
���T'��3��3��'��3��3��.�4�("�4� 4�(4�04�84�@4�H4�@��4#'D3��3ш!�P#�B
�B
DB
�O
���P4�X4�`4�h4�p4�'�x4�'��4�`%�#�h%�|%�H$�*��$�,B
B
\B
�B
pB
�B
�&r
tB
lB
�B
�B
�O
0B
B
B
��O
 ���4�$��4�4&��4��)�"�0�L5�\(�0�T5��$�<*�\5�d*�h��4#'�4�5ш!�P#�85O
�B
��d5�l5�t5�$%��"�|5��"��"��5��"�#�h%�|%�H$�*��$�t�O
L5O
�SO
`5O
�O
t5O
�5O
�5O
���T'��3��5��'��3��)��.�0�("�\(�0�@6�H6��&�P*�H4����4#'�5�5ј)�P#�B
���P6�X6��(�$%�8%�`6��(�P%��"��"�h6�h%�|%�H$�*��$��$B
`B
�B
�B
�B
O
xO
��p6��6��5��'��4��6�47�0�("�\(�D7�(4�L7�T7�\7�H4�X��|7'x6��6ш!Ҙ7�B
L�O
���d7�l7��(�$%�p4�'��(�P%�'�t7�#�h%�|%�H$�<7��$�tB
��O
�B
\B
�B
T�O
�B
B
@B
Pf
`k
p���7�|���7�TJO
����7�$��3�8�8� 8�47�4�("�\(�x8��8�L7��8��8�%�����8'�7�(8ш!��8�B
B
dAO
|��P4��8��8�$%�p4�'��8�'�'�`%��8�h%�|%�H$�p8��$���O
<B
PB
�$B
<QO
�B
�B
�B
XO
f
k
 ���8�,���8��O
����8�L9�T9�$�\9�d9�"��9�("��,��9�(*��$��9��9��9��4#'�8�l9ш!�P#�IO
@O
�IO
<O
d���9��9��*��9��"��9��"�:��"��"�#�$#�,#��!��9�D"���O
PO
؈O
�O
�B
(�O
��O
�O
�O
��O
�O
�O
8��:�$�|)�$��3�x:�"�0�("�\(�*��+��$�<*��:�%����4#':Ѐ:ш!�P#�|KO
����:��:�t5�$%��:��(��:��"��"��"�#�h%�|%�H$��:��$��KO
�JO
�8O
�O
�8O
�8O
��O
����:��3�d;��'�l;�t;�47��;�("�4�x8��;��;��;��;�H4�����8';�|;ј)��8�B
B
O
���P4��;��;��;�p4�'��8�'�'�<�<�h%�|%�H$�*��$�0B
HB
\B
pB
hHO
�B
�B
H�O
�B
�O
L�O
��<�$��<�4&��<��<�"�@(�("�\(��<�T5�x(��$��<��<�`��4#'<И<ш!�P#��O
|B
l�O
���,��<�=�=�=�=� =��"�(=��"�0=�h%�|%�H$�*��$� :O
$�O
L O
� B
!O
�QO
xO
XxO
�:O
�!O
�O
���T'�$��=�4&��4��=��.��=�("�\(��=�T5�>�<*�>�d*����4#'8=Ь=ш!�P#�:O
�BO
���>�>�$>�,>��"��(��:�4>�<>��"�D>�h%�|%�H$�*��$��B
�\O
�:O
H�O
 ;O
"O
�$B
�O
4;O
x"O
xO
�O
���L>��>��>��>�,$��2�"�?�("��,�(?�0?�8?�<*�,3�@?��4#'T>��>ш!�P#��O
�PO
t�O
`��H?�P?�X?�`?��"��(��"��"��"��"�#�$#�h?��!� ?�D"�ȌO
PO
 �O
H�O
QO
4]O
�O
l�O
�O
�B
�O
��T'�$�|)�4&�,$��)�"�0�("�\(�*�(*�x(�<*�$@�d*�L��4#'p?��?ш!�P#�"��,@�4@�<@�$%�p4��(��"��"�(=��"�D@�h%�|%�H$�@��$���O
@O
\;O
�"O
l;O
�;O
h��T'��'��@�4&��4��)�"�@(�("�\(�*�(*�x(��$�A�d*����4#'L@и@ш!�P#�;O
#��,@�A�A�$%��"�@%��"��"��5��"�#�h%�|%�H$�A��$��O
,�O
#O
�;O
���p6�$�|)�4&��4��A�"�0�("�\(�*�(*��$�<*�P*�d*���4#' AЌAш!�P#�4�O
�#���,��A��A��A��"��(��(��"�<>��"��A�h%�|%�H$�*��$�p#O
�;O
d�O
�;O
0��T'��3�|)�$��4��)��.�@(�("�\(�*�(*�x(��$��B�d*�x��4#'�A�XBш!�P#��$���,��*��B�$%��"��(��"�P%��"��"�#�h%�|%�H$�*��$�h�O
<O
�O
����B�$�$�4&�,$��)�"�@(�("�\(�dC�lC�x(�tC�P*�d*����4#'�B�Cш!�P#��#���,�|C��C�$%��"��(��"��"�X%��"�#�h%�|%�H$�*��$�yO
<O
 <O
L<O
�#O
p%��4#'�(ШCш!�P#�(%���,�|C��*�$%��"��(��"��"��"�`%�#�h%�|%�H$�*��$��&��T'��'�|)�$�,$��)�"�@(�("�\(�*�(*��$�<*��D�d*��&(4#'�C�TDш!�P#�D���,��*��*�$%��"��(��"��"��"��"��D�h%�|%�H$�*��$�����,��*��*�,%��"��(��"��"��"�#�#�p%��%��$�*��$��&O
#x
���L>��'�|)�$�,$��2�"�@(�("��,�*�(*��$�<*�,3�d*��4#'�D�XEш!�P#�@���,��*��*�(.��"��(��"��"��"��"�#��E�,#��!��E�D"�4O
�B
����0��'�|)�$�F�F�"�lF�("��,�|F�(*��$�<*�,3�d*��4#'�E�$Fш!�P#⌒O
�O
���F��F��*�(.��"��(��"��"��"��"�#�$#��F��!�tF�D"�ȒO
�O
�O
@�O
��O
PB
�GO
�G���F�<H�dH��H�I�4I�J�<J�dJ��J��J�K�<K�dK��K��K�'~�M'�F�dIѼH��GO
�G��G�DH�lH��H�I�<I�J�DJ�lJ��J��J�K�DK�lK��K��K�0'�GдI��H�N�H'��TG�T'��lG��~
$�G-@���G�LH�tH��H�I�DI�$J�LJ�tJ��J��J�$K�LK�tK��K��K���
$�G-@���G�XH��H��H�(I�PI�0J�XJ��J��J�K�0K�XK��K��K��K�LHO
XHO
�	���G-�	���G-tHO
�HO
���G-���G-�HO
�HO
,
���G-,
���G-�HO
�H���H��HO
I���H�l$���G-�$���H�l$���G-�$���H�IO
(IO
���G-���G-DIO
PIO
��G-��G-�IO
p����K�L�,L�TL�|L��L��L��L�M�DM�lM��M��M�\I��J��J�JO
̘���K�L�4L�\L��L��L��L��L�$M�LM�tM��M��M��I��J��J�\�G-\�G-$JO
0JO
����G-����G-LJO
XJO
����G-����G-tJO
�JO
��G-��G-�JO
�JO
��G-��G-\�O
��O
�JO
�JO
@���G-@���G-�JO
KO
h���G-h���G-$KO
0KO
����G-����G-LKO
XKO
X���G-X���G-tKO
�KO
����G-����G-�KO
�KO
��G-��G-�KO
�KO
����G-����G-�KO
�KO
����G-����G-LO
 LO
`���G-`���G-<LO
HLO
����G-����G-dLO
pLO
��G-��G-�LO
�LO
L���G-L���G-�LO
�LO
����G-����G-�LO
�LO
���G-���G-MO
MO
x���G-x���G-,MO
8MO
����G-����G-TMO
`MO
p%���G-p%���G-|MO
�MO
�&(�G-�&(�G-�MO
�MO
��G-��G-�MO
�MO
��G-��G-N�
(N�
4N���M�@N���M���
'~N-��
0'� N-H'��(N�T'��4N�2012030130120100206320600320001006002010000	2010000002020100	201000600	200020100	200010000300020140020030601601606000686008610666600620661002060066064061060660000060600000
6060600000606060600000600060060606000000040340634060032045040300	50400000030004033045040000504012050401636003	4000004033040201	4030004032010203	40300000040300	40300020140300004050400000040201	20103060120102013002020103020150400605040040504000200300401300004013040040000004030400000030404000000
30404000201003040400000000003040400020030404000301
30404400003040400400003040400	304040201
3040400000000
3040402010201
3040400000201	300000000300000000003000000020130000020100
3000000000000	304000000
30400000000003040002010030400000000304000000000000
3040000000200	300000200	30000030130000000600304000002013040000	3040003013000201020130402010201304000000020100
30000050400003040000030400000600
3040000000403	30400020020100000301
30400000002013040030403003040000304030030400000000030400000000000404003040000000002013040300
3040000020100	304000400	201000401500004014030002010040300000000204030100000201500000	4000000005000000400060040100	40304000020400
4000300020100	201000201405004030000000000007000201504040150400000000	204050201
403000000000030040000
3000040000200030000004040500000020103040201
4050005000000	407000000	40504000040500000400	405000000
40500000000004050000000020100000201403000000020100403000002014030201020140300000200
204030000020140404000000	400030400506004000201201020100000000020140000000002010201
3004000201300004000201	20104020140000000000	40000020140003020100
4030400020100	405040201	405000200
3004000000300004000000	203040201407040020403014030140704000000403040000000000
4070400000000407040002004030304020140000020400
403040000000040002004050000	504040000	50400060050400000600404050000000000
4040500000201
40403000000004040300000000020140403000000000000000040400000000000000
4040300000201
2010003040201	400000300	40300020020405040201405000000000200	500020100
4005040300400005040300404030040403	4040302014000020403302013040100404030400020100
400000000000020601
504000000050405000000	50400020120504010201
6050400000
4000002000100
400000002010020504000201
403040000060040000040502010201704000000002020120104030000000301
40304040000004000403000000000040003000000000040403000000000040404030000000000201030400000201	40201020120100000304020102014030400	403010201204020140403000000
4040304000000
204040304020140403040100	40002010011040201
4000000000201	3040002018788888208088080878088008808878080087208820800830808608088808780080876886008008888780080087PK
!<V��$��%chrome/remote/content/cdp/CDP.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  JSONHandler: "chrome://remote/content/cdp/JSONHandler.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  RecommendedPreferences:
    "chrome://remote/content/shared/RecommendedPreferences.sys.mjs",
  TargetList: "chrome://remote/content/cdp/targets/TargetList.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.CDP)
);
ChromeUtils.defineLazyGetter(lazy, "textEncoder", () => new TextEncoder());

// Map of CDP-specific preferences that should be set via
// RecommendedPreferences.
const RECOMMENDED_PREFS = new Map([
  // Prevent various error message on the console
  // jest-puppeteer asserts that no error message is emitted by the console
  [
    "browser.contentblocking.features.standard",
    "-tp,tpPrivate,cookieBehavior0,-cm,-fp",
  ],
]);

/**
 * Entry class for the Chrome DevTools Protocol support.
 *
 * It holds the list of available targets (tabs, main browser), and also
 * sets up the necessary handlers for the HTTP server.
 *
 * @see https://chromedevtools.github.io/devtools-protocol
 */
export class CDP {
  /**
   * Creates a new instance of the CDP class.
   *
   * @param {RemoteAgent} agent
   *     Reference to the Remote Agent instance.
   */
  constructor(agent) {
    this.agent = agent;
    this.targetList = null;

    this._running = false;
    this._activePortPath;
  }

  get address() {
    const mainTarget = this.targetList.getMainProcessTarget();
    return mainTarget.wsDebuggerURL;
  }

  get mainTargetPath() {
    const mainTarget = this.targetList.getMainProcessTarget();
    return mainTarget.path;
  }

  /**
   * Starts the CDP support.
   */
  async start() {
    if (this._running) {
      return;
    }

    // Note: Ideally this would only be set at the end of the method. However
    // since start() is async, we prefer to set the flag early in order to
    // avoid potential race conditions.
    this._running = true;

    lazy.RecommendedPreferences.applyPreferences(RECOMMENDED_PREFS);

    // Starting CDP too early can cause issues with clients in not being able
    // to find any available target. Also when closing the application while
    // it's still starting up can cause shutdown hangs. As such CDP will be
    // started when the initial application window has finished initializing.
    lazy.logger.debug(`Waiting for initial application window`);
    await this.agent.browserStartupFinished;

    this.agent.server.registerPrefixHandler("/", new lazy.JSONHandler(this));

    this.targetList = new lazy.TargetList();
    this.targetList.on("target-created", (eventName, target) => {
      this.agent.server.registerPathHandler(target.path, target);
    });
    this.targetList.on("target-destroyed", (eventName, target) => {
      this.agent.server.registerPathHandler(target.path, null);
    });

    await this.targetList.watchForTargets();

    Cu.printStderr(`DevTools listening on ${this.address}\n`);

    try {
      // Write connection details to DevToolsActivePort file within the profile.
      this._activePortPath = PathUtils.join(
        PathUtils.profileDir,
        "DevToolsActivePort"
      );

      const data = `${this.agent.port}\n${this.mainTargetPath}`;

      await IOUtils.write(this._activePortPath, lazy.textEncoder.encode(data));
    } catch (e) {
      lazy.logger.warn(
        `Failed to create ${this._activePortPath} (${e.message})`
      );
    }
  }

  /**
   * Stops the CDP support.
   */
  async stop() {
    if (!this._running) {
      return;
    }

    try {
      await IOUtils.remove(this._activePortPath);
    } catch (e) {
      lazy.logger.warn(
        `Failed to remove ${this._activePortPath} (${e.message})`
      );
    }

    try {
      this.targetList?.destructor();
      this.targetList = null;

      lazy.RecommendedPreferences.restorePreferences(RECOMMENDED_PREFS);
    } catch (e) {
      lazy.logger.error("Failed to stop protocol", e);
    } finally {
      this._running = false;
    }
  }
}
PK
!<z�y&&/chrome/remote/content/cdp/CDPConnection.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { WebSocketConnection } from "chrome://remote/content/shared/WebSocketConnection.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  UnknownMethodError: "chrome://remote/content/cdp/Error.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.CDP)
);

export class CDPConnection extends WebSocketConnection {
  /**
   * @param {WebSocket} webSocket
   *     The WebSocket server connection to wrap.
   * @param {Connection} httpdConnection
   *     Reference to the httpd.js's connection needed for clean-up.
   */
  constructor(webSocket, httpdConnection) {
    super(webSocket, httpdConnection);

    this.sessions = new Map();
    this.defaultSession = null;
  }

  /**
   * Register a new Session to forward the messages to.
   *
   * A session without any `id` attribute will be considered to be the
   * default one, to which messages without `sessionId` attribute are
   * forwarded to. Only one such session can be registered.
   *
   * @param {Session} session
   *     The session to register.
   */
  registerSession(session) {
    lazy.logger.warn(
      `Support for the Chrome DevTools Protocol (CDP) in Firefox will be deprecated after Firefox 128 (ESR) ` +
        `and will be removed in a later release. CDP users should consider migrating ` +
        `to WebDriver BiDi. See https://bugzilla.mozilla.org/show_bug.cgi?id=1872254`
    );

    // CDP is not compatible with Fission by default, check the appropriate
    // preferences are set to ensure compatibility.
    if (
      Services.prefs.getIntPref("fission.webContentIsolationStrategy") !== 0 ||
      Services.prefs.getBoolPref("fission.bfcacheInParent")
    ) {
      lazy.logger.warn(
        `Invalid browser preferences for CDP. Set "fission.webContentIsolationStrategy"` +
          `to 0 and "fission.bfcacheInParent" to false before Firefox starts.`
      );
    }

    if (!session.id) {
      if (this.defaultSession) {
        throw new Error(
          "Default session is already set on Connection, " +
            "can't register another one."
        );
      }
      this.defaultSession = session;
    }

    this.sessions.set(session.id, session);
  }

  /**
   * Send an error back to the CDP client.
   *
   * @param {number} id
   *     Id of the packet which lead to an error.
   * @param {Error} err
   *     Error object with `message` and `stack` attributes.
   * @param {string=} sessionId
   *     Id of the session used to send this packet. Falls back to the
   *     default session if not specified.
   */
  sendError(id, err, sessionId) {
    const error = {
      message: err.message,
      data: err.stack,
    };

    this.send({ id, error, sessionId });
  }

  /**
   * Send an event coming from a Domain to the CDP client.
   *
   * @param {string} method
   *     The event name. This is composed by a domain name, a dot character
   *     followed by the event name, e.g. `Target.targetCreated`.
   * @param {object} params
   *     A JSON-serializable object, which is the payload of this event.
   * @param {string=} sessionId
   *     The sessionId from which this packet is emitted. Falls back to the
   *     default session if not specified.
   */
  sendEvent(method, params, sessionId) {
    this.send({ method, params, sessionId });

    if (Services.profiler?.IsActive()) {
      ChromeUtils.addProfilerMarker(
        "CDP: Event",
        { category: "Remote-Protocol" },
        method
      );
    }

    // When a client attaches to a secondary target via
    // `Target.attachToTarget`, we should emit an event back with the
    // result including the `sessionId` attribute of this secondary target's
    // session. `Target.attachToTarget` creates the secondary session and
    // returns the session ID.
    if (sessionId) {
      // receivedMessageFromTarget is expected to send a raw CDP packet
      // in the `message` property and it to be already serialized to a
      // string
      this.send({
        method: "Target.receivedMessageFromTarget",
        params: { sessionId, message: JSON.stringify({ method, params }) },
      });
    }
  }

  /**
   * Interpret a given CDP packet for a given Session.
   *
   * @param {string} sessionId
   *     ID of the session for which we should execute a command.
   * @param {string} message
   *     The stringified JSON payload of the CDP packet, which is about
   *     executing a Domain's function.
   */
  sendMessageToTarget(sessionId, message) {
    const session = this.sessions.get(sessionId);
    if (!session) {
      throw new Error(`Session '${sessionId}' doesn't exist.`);
    }
    // `message` is received from `Target.sendMessageToTarget` where the
    // message attribute is a stringified JSON payload which represent a CDP
    // packet.
    const packet = JSON.parse(message);

    // The CDP packet sent by the client shouldn't have a sessionId attribute
    // as it is passed as another argument of `Target.sendMessageToTarget`.
    // Set it here in order to reuse the codepath of flatten session, where
    // the client sends CDP packets with a `sessionId` attribute instead
    // of going through the old and probably deprecated
    // `Target.sendMessageToTarget` API.
    packet.sessionId = sessionId;
    this.onPacket(packet);
  }

  /**
   * Send the result of a call to a Domain's function back to the CDP client.
   *
   * @param {number} id
   *     The request id being sent by the client to call the domain's method.
   * @param {object} result
   *     A JSON-serializable object, which is the actual result.
   * @param {string=} sessionId
   *     The sessionId from which this packet is emitted. Falls back to the
   *     default session if not specified.
   */
  sendResult(id, result, sessionId) {
    result = typeof result != "undefined" ? result : {};
    this.send({ id, result, sessionId });

    // When a client attaches to a secondary target via
    // `Target.attachToTarget`, and it executes a command via
    // `Target.sendMessageToTarget`, we should emit an event back with the
    // result including the `sessionId` attribute of this secondary target's
    // session. `Target.attachToTarget` creates the secondary session and
    // returns the session ID.
    if (sessionId) {
      // receivedMessageFromTarget is expected to send a raw CDP packet
      // in the `message` property and it to be already serialized to a
      // string
      this.send({
        method: "Target.receivedMessageFromTarget",
        params: { sessionId, message: JSON.stringify({ id, result }) },
      });
    }
  }

  // Transport hooks

  /**
   * Called by the `transport` when the connection is closed.
   */
  onConnectionClose() {
    // Cleanup all the registered sessions.
    for (const session of this.sessions.values()) {
      session.destructor();
    }
    this.sessions.clear();

    super.onConnectionClose();
  }

  /**
   * Receive a packet from the WebSocket layer.
   *
   * This packet is sent by a CDP client and is meant to execute
   * a particular function on a given Domain.
   *
   * @param {object} packet
   *        JSON-serializable object sent by the client.
   */
  async onPacket(packet) {
    super.onPacket(packet);

    const { id, method, params, sessionId } = packet;
    const startTime = Cu.now();

    try {
      // First check for mandatory field in the packets
      if (typeof id == "undefined") {
        throw new TypeError("Message missing 'id' field");
      }
      if (typeof method == "undefined") {
        throw new TypeError("Message missing 'method' field");
      }

      // Extract the domain name and the method name out of `method` attribute
      const { domain, command } = splitMethod(method);

      // If a `sessionId` field is passed, retrieve the session to which we
      // should forward this packet. Otherwise send it to the default session.
      let session;
      if (!sessionId) {
        if (!this.defaultSession) {
          throw new Error("Connection is missing a default Session.");
        }
        session = this.defaultSession;
      } else {
        session = this.sessions.get(sessionId);
        if (!session) {
          throw new Error(`Session '${sessionId}' doesn't exists.`);
        }
      }

      // Bug 1600317 - Workaround to deny internal methods to be called
      if (command.startsWith("_")) {
        throw new lazy.UnknownMethodError(command);
      }

      // Finally, instruct the targeted session to execute the command
      const result = await session.execute(id, domain, command, params);
      this.sendResult(id, result, sessionId);
    } catch (e) {
      this.sendError(id, e, packet.sessionId);
    }

    if (Services.profiler?.IsActive()) {
      ChromeUtils.addProfilerMarker(
        "CDP: Command",
        { startTime, category: "Remote-Protocol" },
        `${method} (${id})`
      );
    }
  }
}

/**
 * Splits a CDP method into domain and command components.
 *
 * @param {string} method
 *     Name of the method to split, e.g. "Browser.getVersion".
 *
 * @returns {Record<string, string>}
 *     Object with the domain ("Browser") and command ("getVersion")
 *     as properties.
 */
export function splitMethod(method) {
  const parts = method.split(".");

  if (parts.length != 2 || !parts[0].length || !parts[1].length) {
    throw new TypeError(`Invalid method format: '${method}'`);
  }

  return {
    domain: parts[0],
    command: parts[1],
  };
}
PK
!<��P
P
'chrome/remote/content/cdp/Error.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "chrome://remote/content/shared/Log.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.CDP)
);

export class RemoteAgentError extends Error {
  constructor(message = "", cause = undefined) {
    cause = cause || message;
    super(cause);

    this.name = this.constructor.name;
    this.message = message;
    this.cause = cause;

    this.notify();
  }

  notify() {
    console.error(this);
    lazy.logger.error(this.toString({ stack: true }));
  }

  toString({ stack = false } = {}) {
    return RemoteAgentError.format(this, { stack });
  }

  static format(e, { stack = false } = {}) {
    return formatError(e, { stack });
  }

  /**
   * Takes a serialised CDP error and reconstructs it
   * as a RemoteAgentError.
   *
   * The error must be of this form:
   *
   *     {"message": "TypeError: foo is not a function\n
   *     execute@chrome://remote/content/cdp/sessions/Session.sys.mjs:73:39\n
   *     onMessage@chrome://remote/content/cdp/sessions/TabSession.sys.mjs:65:20"}
   *
   * This approach has the notable deficiency that it cannot deal
   * with causes to errors because of the unstructured nature of CDP
   * errors.  A possible future improvement would be to extend the
   * error serialisation to include discrete fields for each data
   * property.
   *
   * @param {object} json
   *     CDP error encoded as a JSON object, which must have a
   *     "message" field, where the first line will make out the error
   *     message and the subsequent lines the stacktrace.
   *
   * @returns {RemoteAgentError}
   */
  static fromJSON(json) {
    const [message, ...stack] = json.message.split("\n");
    const err = new RemoteAgentError();
    err.message = message.slice(0, -1);
    err.stack = stack.map(s => s.trim()).join("\n");
    err.cause = null;
    return err;
  }
}

/**
 * A fatal error that it is not possible to recover from
 * or send back to the client.
 *
 * Constructing this error will force the application to quit.
 */
export class FatalError extends RemoteAgentError {
  constructor(...args) {
    super(...args);
    this.quit();
  }

  notify() {
    lazy.logger.fatal(this.toString({ stack: true }));
  }

  quit(mode = Ci.nsIAppStartup.eForceQuit) {
    Services.startup.quit(mode);
  }
}

/** When an operation is not yet implemented. */
export class UnsupportedError extends RemoteAgentError {}

/** The requested remote method does not exist. */
export class UnknownMethodError extends RemoteAgentError {
  constructor(domain, command = null) {
    if (command) {
      super(`${domain}.${command}`);
    } else {
      super(domain);
    }
  }
}

function formatError(error, { stack = false } = {}) {
  const els = [];

  els.push(error.name);
  if (error.message) {
    els.push(": ");
    els.push(error.message);
  }

  if (stack && error.stack) {
    els.push(":\n");

    const stack = error.stack.trim().split("\n");
    els.push(stack.map(line => `\t${line}`).join("\n"));

    if (error.cause) {
      els.push("\n");
      els.push("caused by: " + formatError(error.cause, { stack }));
    }
  }

  return els.join("");
}
PK
!<���

-chrome/remote/content/cdp/JSONHandler.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  HTTP_404: "chrome://remote/content/server/httpd.sys.mjs",
  HTTP_405: "chrome://remote/content/server/httpd.sys.mjs",
  HTTP_500: "chrome://remote/content/server/httpd.sys.mjs",
  Protocol: "chrome://remote/content/cdp/Protocol.sys.mjs",
  RemoteAgentError: "chrome://remote/content/cdp/Error.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
});

export class JSONHandler {
  constructor(cdp) {
    this.cdp = cdp;
    this.routes = {
      "/json/version": {
        handler: this.getVersion.bind(this),
      },

      "/json/protocol": {
        handler: this.getProtocol.bind(this),
      },

      "/json/list": {
        handler: this.getTargetList.bind(this),
      },

      "/json": {
        handler: this.getTargetList.bind(this),
      },

      // PUT only - /json/new?{url}
      "/json/new": {
        handler: this.newTarget.bind(this),
        method: "PUT",
      },

      // /json/activate/{targetId}
      "/json/activate": {
        handler: this.activateTarget.bind(this),
        parameter: true,
      },

      // /json/close/{targetId}
      "/json/close": {
        handler: this.closeTarget.bind(this),
        parameter: true,
      },
    };
  }

  getVersion() {
    const mainProcessTarget = this.cdp.targetList.getMainProcessTarget();

    const { userAgent } = Cc[
      "@mozilla.org/network/protocol;1?name=http"
    ].getService(Ci.nsIHttpProtocolHandler);

    return {
      body: {
        Browser: `${Services.appinfo.name}/${Services.appinfo.version}`,
        "Protocol-Version": "1.3",
        "User-Agent": userAgent,
        "V8-Version": "1.0",
        "WebKit-Version": "1.0",
        webSocketDebuggerUrl: mainProcessTarget.toJSON().webSocketDebuggerUrl,
      },
    };
  }

  getProtocol() {
    return { body: lazy.Protocol.Description };
  }

  getTargetList() {
    return { body: [...this.cdp.targetList].filter(x => x.type !== "browser") };
  }

  /** HTTP copy of Target.createTarget() */
  async newTarget(url) {
    const onTarget = this.cdp.targetList.once("target-created");

    // Open new tab
    const tab = await lazy.TabManager.addTab({
      focus: true,
    });

    // Get the newly created target
    const target = await onTarget;
    if (tab.linkedBrowser != target.browser) {
      throw new Error(
        "Unexpected tab opened: " + tab.linkedBrowser.currentURI.spec
      );
    }

    const returnJson = target.toJSON();

    // Load URL if given, otherwise stay on about:blank
    if (url) {
      let validURL;
      try {
        validURL = Services.io.newURI(url);
      } catch {
        // If we failed to parse given URL, return now since we already loaded about:blank
        return { body: returnJson };
      }

      target.browsingContext.loadURI(validURL, {
        triggeringPrincipal:
          Services.scriptSecurityManager.getSystemPrincipal(),
      });

      // Force the URL in the returned target JSON to match given
      // even if loading/will fail (matches Chromium behavior)
      returnJson.url = url;
    }

    return { body: returnJson };
  }

  /** HTTP copy of Target.activateTarget() */
  async activateTarget(targetId) {
    // Try to get the target from given id
    const target = this.cdp.targetList.getById(targetId);

    if (!target) {
      return {
        status: lazy.HTTP_404,
        body: `No such target id: ${targetId}`,
        json: false,
      };
    }

    // Select the tab (this endpoint does not show the window)
    await lazy.TabManager.selectTab(target.tab);

    return { body: "Target activated", json: false };
  }

  /** HTTP copy of Target.closeTarget() */
  async closeTarget(targetId) {
    // Try to get the target from given id
    const target = this.cdp.targetList.getById(targetId);

    if (!target) {
      return {
        status: lazy.HTTP_404,
        body: `No such target id: ${targetId}`,
        json: false,
      };
    }

    // Remove the tab
    await lazy.TabManager.removeTab(target.tab);

    return { body: "Target is closing", json: false };
  }

  // nsIHttpRequestHandler

  async handle(request, response) {
    // Mark request as async so we can execute async routes and return values
    response.processAsync();

    // Run a provided route (function) with an argument
    const runRoute = async (route, data) => {
      try {
        // Run the route to get data to return
        const {
          status = { code: 200, description: "OK" },
          json = true,
          body,
        } = await route(data);

        // Stringify into returnable JSON if wanted
        const payload = json
          ? JSON.stringify(body, null, lazy.Log.verbose ? "\t" : null)
          : body;

        // Handle HTTP response
        response.setStatusLine(
          request.httpVersion,
          status.code,
          status.description
        );
        response.setHeader("Content-Type", "application/json");
        response.setHeader("Content-Security-Policy", "frame-ancestors 'none'");
        response.write(payload);
      } catch (e) {
        new lazy.RemoteAgentError(e).notify();

        // Mark as 500 as an error has occured internally
        response.setStatusLine(
          request.httpVersion,
          lazy.HTTP_500.code,
          lazy.HTTP_500.description
        );
      }
    };

    // Trim trailing slashes to conform with expected routes
    const path = request.path.replace(/\/+$/, "");

    let route;
    for (const _route in this.routes) {
      // Prefixed/parameter route (/path/{parameter})
      if (path.startsWith(_route + "/") && this.routes[_route].parameter) {
        route = _route;
        break;
      }

      // Regular route (/path/example)
      if (path === _route) {
        route = _route;
        break;
      }
    }

    if (!route) {
      // Route does not exist
      response.setStatusLine(
        request.httpVersion,
        lazy.HTTP_404.code,
        lazy.HTTP_404.description
      );
      response.write("Unknown command: " + path.replace("/json/", ""));

      return response.finish();
    }

    const { handler, method, parameter } = this.routes[route];

    // If only one valid method for route, check method matches
    if (method && request.method !== method) {
      response.setStatusLine(
        request.httpVersion,
        lazy.HTTP_405.code,
        lazy.HTTP_405.description
      );
      response.write(
        `Using unsafe HTTP verb ${request.method} to invoke ${route}. This action supports only PUT verb.`
      );
      return response.finish();
    }

    if (parameter) {
      await runRoute(handler, path.split("/").pop());
    } else {
      await runRoute(handler, request.queryString);
    }

    // Send response
    return response.finish();
  }

  // XPCOM

  get QueryInterface() {
    return ChromeUtils.generateQI(["nsIHttpRequestHandler"]);
  }
}
PK
!<�������*chrome/remote/content/cdp/Protocol.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// The `Description` below is imported from Chromium Code.

// TODO(ato): We send back a description of the protocol
// when the user makes the initial HTTP request,
// but the following is pure fiction.
const Description = {
    "domains": [
        {
            "domain": "Accessibility",
            "experimental": true,
            "dependencies": [
                "DOM"
            ],
            "types": [
                {
                    "id": "AXNodeId",
                    "description": "Unique accessibility node identifier.",
                    "type": "string"
                },
                {
                    "id": "AXValueType",
                    "description": "Enum of possible property types.",
                    "type": "string",
                    "enum": [
                        "boolean",
                        "tristate",
                        "booleanOrUndefined",
                        "idref",
                        "idrefList",
                        "integer",
                        "node",
                        "nodeList",
                        "number",
                        "string",
                        "computedString",
                        "token",
                        "tokenList",
                        "domRelation",
                        "role",
                        "internalRole",
                        "valueUndefined"
                    ]
                },
                {
                    "id": "AXValueSourceType",
                    "description": "Enum of possible property sources.",
                    "type": "string",
                    "enum": [
                        "attribute",
                        "implicit",
                        "style",
                        "contents",
                        "placeholder",
                        "relatedElement"
                    ]
                },
                {
                    "id": "AXValueNativeSourceType",
                    "description": "Enum of possible native property sources (as a subtype of a particular AXValueSourceType).",
                    "type": "string",
                    "enum": [
                        "figcaption",
                        "label",
                        "labelfor",
                        "labelwrapped",
                        "legend",
                        "tablecaption",
                        "title",
                        "other"
                    ]
                },
                {
                    "id": "AXValueSource",
                    "description": "A single source for a computed AX property.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "type",
                            "description": "What type of source this is.",
                            "$ref": "AXValueSourceType"
                        },
                        {
                            "name": "value",
                            "description": "The value of this property source.",
                            "optional": true,
                            "$ref": "AXValue"
                        },
                        {
                            "name": "attribute",
                            "description": "The name of the relevant attribute, if any.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "attributeValue",
                            "description": "The value of the relevant attribute, if any.",
                            "optional": true,
                            "$ref": "AXValue"
                        },
                        {
                            "name": "superseded",
                            "description": "Whether this source is superseded by a higher priority source.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "nativeSource",
                            "description": "The native markup source for this value, e.g. a <label> element.",
                            "optional": true,
                            "$ref": "AXValueNativeSourceType"
                        },
                        {
                            "name": "nativeSourceValue",
                            "description": "The value, such as a node or node list, of the native source.",
                            "optional": true,
                            "$ref": "AXValue"
                        },
                        {
                            "name": "invalid",
                            "description": "Whether the value for this property is invalid.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "invalidReason",
                            "description": "Reason for the value being invalid, if it is.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "AXRelatedNode",
                    "type": "object",
                    "properties": [
                        {
                            "name": "backendDOMNodeId",
                            "description": "The BackendNodeId of the related DOM node.",
                            "$ref": "DOM.BackendNodeId"
                        },
                        {
                            "name": "idref",
                            "description": "The IDRef value provided, if any.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "text",
                            "description": "The text alternative of this node in the current context.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "AXProperty",
                    "type": "object",
                    "properties": [
                        {
                            "name": "name",
                            "description": "The name of this property.",
                            "$ref": "AXPropertyName"
                        },
                        {
                            "name": "value",
                            "description": "The value of this property.",
                            "$ref": "AXValue"
                        }
                    ]
                },
                {
                    "id": "AXValue",
                    "description": "A single computed AX property.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "type",
                            "description": "The type of this value.",
                            "$ref": "AXValueType"
                        },
                        {
                            "name": "value",
                            "description": "The computed value of this property.",
                            "optional": true,
                            "type": "any"
                        },
                        {
                            "name": "relatedNodes",
                            "description": "One or more related nodes, if applicable.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "AXRelatedNode"
                            }
                        },
                        {
                            "name": "sources",
                            "description": "The sources which contributed to the computation of this property.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "AXValueSource"
                            }
                        }
                    ]
                },
                {
                    "id": "AXPropertyName",
                    "description": "Values of AXProperty name: from 'busy' to 'roledescription' - states which apply to every AX\nnode, from 'live' to 'root' - attributes which apply to nodes in live regions, from\n'autocomplete' to 'valuetext' - attributes which apply to widgets, from 'checked' to 'selected'\n- states which apply to widgets, from 'activedescendant' to 'owns' - relationships between\nelements other than parent/child/sibling.",
                    "type": "string",
                    "enum": [
                        "busy",
                        "disabled",
                        "editable",
                        "focusable",
                        "focused",
                        "hidden",
                        "hiddenRoot",
                        "invalid",
                        "keyshortcuts",
                        "settable",
                        "roledescription",
                        "live",
                        "atomic",
                        "relevant",
                        "root",
                        "autocomplete",
                        "hasPopup",
                        "level",
                        "multiselectable",
                        "orientation",
                        "multiline",
                        "readonly",
                        "required",
                        "valuemin",
                        "valuemax",
                        "valuetext",
                        "checked",
                        "expanded",
                        "modal",
                        "pressed",
                        "selected",
                        "activedescendant",
                        "controls",
                        "describedby",
                        "details",
                        "errormessage",
                        "flowto",
                        "labelledby",
                        "owns"
                    ]
                },
                {
                    "id": "AXNode",
                    "description": "A node in the accessibility tree.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "nodeId",
                            "description": "Unique identifier for this node.",
                            "$ref": "AXNodeId"
                        },
                        {
                            "name": "ignored",
                            "description": "Whether this node is ignored for accessibility",
                            "type": "boolean"
                        },
                        {
                            "name": "ignoredReasons",
                            "description": "Collection of reasons why this node is hidden.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "AXProperty"
                            }
                        },
                        {
                            "name": "role",
                            "description": "This `Node`'s role, whether explicit or implicit.",
                            "optional": true,
                            "$ref": "AXValue"
                        },
                        {
                            "name": "name",
                            "description": "The accessible name for this `Node`.",
                            "optional": true,
                            "$ref": "AXValue"
                        },
                        {
                            "name": "description",
                            "description": "The accessible description for this `Node`.",
                            "optional": true,
                            "$ref": "AXValue"
                        },
                        {
                            "name": "value",
                            "description": "The value for this `Node`.",
                            "optional": true,
                            "$ref": "AXValue"
                        },
                        {
                            "name": "properties",
                            "description": "All other properties",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "AXProperty"
                            }
                        },
                        {
                            "name": "childIds",
                            "description": "IDs for each of this node's child nodes.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "AXNodeId"
                            }
                        },
                        {
                            "name": "backendDOMNodeId",
                            "description": "The backend ID for the associated DOM node, if any.",
                            "optional": true,
                            "$ref": "DOM.BackendNodeId"
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "disable",
                    "description": "Disables the accessibility domain."
                },
                {
                    "name": "enable",
                    "description": "Enables the accessibility domain which causes `AXNodeId`s to remain consistent between method calls.\nThis turns on accessibility for the page, which can impact performance until accessibility is disabled."
                },
                {
                    "name": "getPartialAXTree",
                    "description": "Fetches the accessibility node and partial accessibility tree for this DOM node, if it exists.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Identifier of the node to get the partial accessibility tree for.",
                            "optional": true,
                            "$ref": "DOM.NodeId"
                        },
                        {
                            "name": "backendNodeId",
                            "description": "Identifier of the backend node to get the partial accessibility tree for.",
                            "optional": true,
                            "$ref": "DOM.BackendNodeId"
                        },
                        {
                            "name": "objectId",
                            "description": "JavaScript object id of the node wrapper to get the partial accessibility tree for.",
                            "optional": true,
                            "$ref": "Runtime.RemoteObjectId"
                        },
                        {
                            "name": "fetchRelatives",
                            "description": "Whether to fetch this nodes ancestors, siblings and children. Defaults to true.",
                            "optional": true,
                            "type": "boolean"
                        }
                    ],
                    "returns": [
                        {
                            "name": "nodes",
                            "description": "The `Accessibility.AXNode` for this DOM node, if it exists, plus its ancestors, siblings and\nchildren, if requested.",
                            "type": "array",
                            "items": {
                                "$ref": "AXNode"
                            }
                        }
                    ]
                },
                {
                    "name": "getFullAXTree",
                    "description": "Fetches the entire accessibility tree",
                    "experimental": true,
                    "returns": [
                        {
                            "name": "nodes",
                            "type": "array",
                            "items": {
                                "$ref": "AXNode"
                            }
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Animation",
            "experimental": true,
            "dependencies": [
                "Runtime",
                "DOM"
            ],
            "types": [
                {
                    "id": "Animation",
                    "description": "Animation instance.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "id",
                            "description": "`Animation`'s id.",
                            "type": "string"
                        },
                        {
                            "name": "name",
                            "description": "`Animation`'s name.",
                            "type": "string"
                        },
                        {
                            "name": "pausedState",
                            "description": "`Animation`'s internal paused state.",
                            "type": "boolean"
                        },
                        {
                            "name": "playState",
                            "description": "`Animation`'s play state.",
                            "type": "string"
                        },
                        {
                            "name": "playbackRate",
                            "description": "`Animation`'s playback rate.",
                            "type": "number"
                        },
                        {
                            "name": "startTime",
                            "description": "`Animation`'s start time.",
                            "type": "number"
                        },
                        {
                            "name": "currentTime",
                            "description": "`Animation`'s current time.",
                            "type": "number"
                        },
                        {
                            "name": "type",
                            "description": "Animation type of `Animation`.",
                            "type": "string",
                            "enum": [
                                "CSSTransition",
                                "CSSAnimation",
                                "WebAnimation"
                            ]
                        },
                        {
                            "name": "source",
                            "description": "`Animation`'s source animation node.",
                            "optional": true,
                            "$ref": "AnimationEffect"
                        },
                        {
                            "name": "cssId",
                            "description": "A unique ID for `Animation` representing the sources that triggered this CSS\nanimation/transition.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "AnimationEffect",
                    "description": "AnimationEffect instance",
                    "type": "object",
                    "properties": [
                        {
                            "name": "delay",
                            "description": "`AnimationEffect`'s delay.",
                            "type": "number"
                        },
                        {
                            "name": "endDelay",
                            "description": "`AnimationEffect`'s end delay.",
                            "type": "number"
                        },
                        {
                            "name": "iterationStart",
                            "description": "`AnimationEffect`'s iteration start.",
                            "type": "number"
                        },
                        {
                            "name": "iterations",
                            "description": "`AnimationEffect`'s iterations.",
                            "type": "number"
                        },
                        {
                            "name": "duration",
                            "description": "`AnimationEffect`'s iteration duration.",
                            "type": "number"
                        },
                        {
                            "name": "direction",
                            "description": "`AnimationEffect`'s playback direction.",
                            "type": "string"
                        },
                        {
                            "name": "fill",
                            "description": "`AnimationEffect`'s fill mode.",
                            "type": "string"
                        },
                        {
                            "name": "backendNodeId",
                            "description": "`AnimationEffect`'s target node.",
                            "optional": true,
                            "$ref": "DOM.BackendNodeId"
                        },
                        {
                            "name": "keyframesRule",
                            "description": "`AnimationEffect`'s keyframes.",
                            "optional": true,
                            "$ref": "KeyframesRule"
                        },
                        {
                            "name": "easing",
                            "description": "`AnimationEffect`'s timing function.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "KeyframesRule",
                    "description": "Keyframes Rule",
                    "type": "object",
                    "properties": [
                        {
                            "name": "name",
                            "description": "CSS keyframed animation's name.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "keyframes",
                            "description": "List of animation keyframes.",
                            "type": "array",
                            "items": {
                                "$ref": "KeyframeStyle"
                            }
                        }
                    ]
                },
                {
                    "id": "KeyframeStyle",
                    "description": "Keyframe Style",
                    "type": "object",
                    "properties": [
                        {
                            "name": "offset",
                            "description": "Keyframe's time offset.",
                            "type": "string"
                        },
                        {
                            "name": "easing",
                            "description": "`AnimationEffect`'s timing function.",
                            "type": "string"
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "disable",
                    "description": "Disables animation domain notifications."
                },
                {
                    "name": "enable",
                    "description": "Enables animation domain notifications."
                },
                {
                    "name": "getCurrentTime",
                    "description": "Returns the current time of the an animation.",
                    "parameters": [
                        {
                            "name": "id",
                            "description": "Id of animation.",
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "currentTime",
                            "description": "Current time of the page.",
                            "type": "number"
                        }
                    ]
                },
                {
                    "name": "getPlaybackRate",
                    "description": "Gets the playback rate of the document timeline.",
                    "returns": [
                        {
                            "name": "playbackRate",
                            "description": "Playback rate for animations on page.",
                            "type": "number"
                        }
                    ]
                },
                {
                    "name": "releaseAnimations",
                    "description": "Releases a set of animations to no longer be manipulated.",
                    "parameters": [
                        {
                            "name": "animations",
                            "description": "List of animation ids to seek.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ]
                },
                {
                    "name": "resolveAnimation",
                    "description": "Gets the remote object of the Animation.",
                    "parameters": [
                        {
                            "name": "animationId",
                            "description": "Animation id.",
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "remoteObject",
                            "description": "Corresponding remote object.",
                            "$ref": "Runtime.RemoteObject"
                        }
                    ]
                },
                {
                    "name": "seekAnimations",
                    "description": "Seek a set of animations to a particular time within each animation.",
                    "parameters": [
                        {
                            "name": "animations",
                            "description": "List of animation ids to seek.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        },
                        {
                            "name": "currentTime",
                            "description": "Set the current time of each animation.",
                            "type": "number"
                        }
                    ]
                },
                {
                    "name": "setPaused",
                    "description": "Sets the paused state of a set of animations.",
                    "parameters": [
                        {
                            "name": "animations",
                            "description": "Animations to set the pause state of.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        },
                        {
                            "name": "paused",
                            "description": "Paused state to set to.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "setPlaybackRate",
                    "description": "Sets the playback rate of the document timeline.",
                    "parameters": [
                        {
                            "name": "playbackRate",
                            "description": "Playback rate for animations on page",
                            "type": "number"
                        }
                    ]
                },
                {
                    "name": "setTiming",
                    "description": "Sets the timing of an animation node.",
                    "parameters": [
                        {
                            "name": "animationId",
                            "description": "Animation id.",
                            "type": "string"
                        },
                        {
                            "name": "duration",
                            "description": "Duration of the animation.",
                            "type": "number"
                        },
                        {
                            "name": "delay",
                            "description": "Delay of the animation.",
                            "type": "number"
                        }
                    ]
                }
            ],
            "events": [
                {
                    "name": "animationCanceled",
                    "description": "Event for when an animation has been cancelled.",
                    "parameters": [
                        {
                            "name": "id",
                            "description": "Id of the animation that was cancelled.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "animationCreated",
                    "description": "Event for each animation that has been created.",
                    "parameters": [
                        {
                            "name": "id",
                            "description": "Id of the animation that was created.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "animationStarted",
                    "description": "Event for animation that has been started.",
                    "parameters": [
                        {
                            "name": "animation",
                            "description": "Animation that was started.",
                            "$ref": "Animation"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "ApplicationCache",
            "experimental": true,
            "types": [
                {
                    "id": "ApplicationCacheResource",
                    "description": "Detailed application cache resource information.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "url",
                            "description": "Resource url.",
                            "type": "string"
                        },
                        {
                            "name": "size",
                            "description": "Resource size.",
                            "type": "integer"
                        },
                        {
                            "name": "type",
                            "description": "Resource type.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "ApplicationCache",
                    "description": "Detailed application cache information.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "manifestURL",
                            "description": "Manifest URL.",
                            "type": "string"
                        },
                        {
                            "name": "size",
                            "description": "Application cache size.",
                            "type": "number"
                        },
                        {
                            "name": "creationTime",
                            "description": "Application cache creation time.",
                            "type": "number"
                        },
                        {
                            "name": "updateTime",
                            "description": "Application cache update time.",
                            "type": "number"
                        },
                        {
                            "name": "resources",
                            "description": "Application cache resources.",
                            "type": "array",
                            "items": {
                                "$ref": "ApplicationCacheResource"
                            }
                        }
                    ]
                },
                {
                    "id": "FrameWithManifest",
                    "description": "Frame identifier - manifest URL pair.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "frameId",
                            "description": "Frame identifier.",
                            "$ref": "Page.FrameId"
                        },
                        {
                            "name": "manifestURL",
                            "description": "Manifest URL.",
                            "type": "string"
                        },
                        {
                            "name": "status",
                            "description": "Application cache status.",
                            "type": "integer"
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "enable",
                    "description": "Enables application cache domain notifications."
                },
                {
                    "name": "getApplicationCacheForFrame",
                    "description": "Returns relevant application cache data for the document in given frame.",
                    "parameters": [
                        {
                            "name": "frameId",
                            "description": "Identifier of the frame containing document whose application cache is retrieved.",
                            "$ref": "Page.FrameId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "applicationCache",
                            "description": "Relevant application cache data for the document in given frame.",
                            "$ref": "ApplicationCache"
                        }
                    ]
                },
                {
                    "name": "getFramesWithManifests",
                    "description": "Returns array of frame identifiers with manifest urls for each frame containing a document\nassociated with some application cache.",
                    "returns": [
                        {
                            "name": "frameIds",
                            "description": "Array of frame identifiers with manifest urls for each frame containing a document\nassociated with some application cache.",
                            "type": "array",
                            "items": {
                                "$ref": "FrameWithManifest"
                            }
                        }
                    ]
                },
                {
                    "name": "getManifestForFrame",
                    "description": "Returns manifest URL for document in the given frame.",
                    "parameters": [
                        {
                            "name": "frameId",
                            "description": "Identifier of the frame containing document whose manifest is retrieved.",
                            "$ref": "Page.FrameId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "manifestURL",
                            "description": "Manifest URL for document in the given frame.",
                            "type": "string"
                        }
                    ]
                }
            ],
            "events": [
                {
                    "name": "applicationCacheStatusUpdated",
                    "parameters": [
                        {
                            "name": "frameId",
                            "description": "Identifier of the frame containing document whose application cache updated status.",
                            "$ref": "Page.FrameId"
                        },
                        {
                            "name": "manifestURL",
                            "description": "Manifest URL.",
                            "type": "string"
                        },
                        {
                            "name": "status",
                            "description": "Updated application cache status.",
                            "type": "integer"
                        }
                    ]
                },
                {
                    "name": "networkStateUpdated",
                    "parameters": [
                        {
                            "name": "isNowOnline",
                            "type": "boolean"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Audits",
            "description": "Audits domain allows investigation of page violations and possible improvements.",
            "experimental": true,
            "dependencies": [
                "Network"
            ],
            "commands": [
                {
                    "name": "getEncodedResponse",
                    "description": "Returns the response body and size if it were re-encoded with the specified settings. Only\napplies to images.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Identifier of the network request to get content for.",
                            "$ref": "Network.RequestId"
                        },
                        {
                            "name": "encoding",
                            "description": "The encoding to use.",
                            "type": "string",
                            "enum": [
                                "webp",
                                "jpeg",
                                "png"
                            ]
                        },
                        {
                            "name": "quality",
                            "description": "The quality of the encoding (0-1). (defaults to 1)",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "sizeOnly",
                            "description": "Whether to only return the size information (defaults to false).",
                            "optional": true,
                            "type": "boolean"
                        }
                    ],
                    "returns": [
                        {
                            "name": "body",
                            "description": "The encoded body as a base64 string. Omitted if sizeOnly is true.",
                            "optional": true,
                            "type": "binary"
                        },
                        {
                            "name": "originalSize",
                            "description": "Size before re-encoding.",
                            "type": "integer"
                        },
                        {
                            "name": "encodedSize",
                            "description": "Size after re-encoding.",
                            "type": "integer"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Browser",
            "description": "The Browser domain defines methods and events for browser managing.",
            "types": [
                {
                    "id": "WindowID",
                    "experimental": true,
                    "type": "integer"
                },
                {
                    "id": "WindowState",
                    "description": "The state of the browser window.",
                    "experimental": true,
                    "type": "string",
                    "enum": [
                        "normal",
                        "minimized",
                        "maximized",
                        "fullscreen"
                    ]
                },
                {
                    "id": "Bounds",
                    "description": "Browser window bounds information",
                    "experimental": true,
                    "type": "object",
                    "properties": [
                        {
                            "name": "left",
                            "description": "The offset from the left edge of the screen to the window in pixels.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "top",
                            "description": "The offset from the top edge of the screen to the window in pixels.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "width",
                            "description": "The window width in pixels.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "height",
                            "description": "The window height in pixels.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "windowState",
                            "description": "The window state. Default to normal.",
                            "optional": true,
                            "$ref": "WindowState"
                        }
                    ]
                },
                {
                    "id": "PermissionType",
                    "experimental": true,
                    "type": "string",
                    "enum": [
                        "accessibilityEvents",
                        "audioCapture",
                        "backgroundSync",
                        "backgroundFetch",
                        "clipboardRead",
                        "clipboardWrite",
                        "durableStorage",
                        "flash",
                        "geolocation",
                        "midi",
                        "midiSysex",
                        "notifications",
                        "paymentHandler",
                        "protectedMediaIdentifier",
                        "sensors",
                        "videoCapture"
                    ]
                },
                {
                    "id": "Bucket",
                    "description": "Chrome histogram bucket.",
                    "experimental": true,
                    "type": "object",
                    "properties": [
                        {
                            "name": "low",
                            "description": "Minimum value (inclusive).",
                            "type": "integer"
                        },
                        {
                            "name": "high",
                            "description": "Maximum value (exclusive).",
                            "type": "integer"
                        },
                        {
                            "name": "count",
                            "description": "Number of samples.",
                            "type": "integer"
                        }
                    ]
                },
                {
                    "id": "Histogram",
                    "description": "Chrome histogram.",
                    "experimental": true,
                    "type": "object",
                    "properties": [
                        {
                            "name": "name",
                            "description": "Name.",
                            "type": "string"
                        },
                        {
                            "name": "sum",
                            "description": "Sum of sample values.",
                            "type": "integer"
                        },
                        {
                            "name": "count",
                            "description": "Total number of samples.",
                            "type": "integer"
                        },
                        {
                            "name": "buckets",
                            "description": "Buckets.",
                            "type": "array",
                            "items": {
                                "$ref": "Bucket"
                            }
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "grantPermissions",
                    "description": "Grant specific permissions to the given origin and reject all others.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "origin",
                            "type": "string"
                        },
                        {
                            "name": "permissions",
                            "type": "array",
                            "items": {
                                "$ref": "PermissionType"
                            }
                        },
                        {
                            "name": "browserContextId",
                            "description": "BrowserContext to override permissions. When omitted, default browser context is used.",
                            "optional": true,
                            "$ref": "Target.BrowserContextID"
                        }
                    ]
                },
                {
                    "name": "resetPermissions",
                    "description": "Reset all permission management for all origins.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "browserContextId",
                            "description": "BrowserContext to reset permissions. When omitted, default browser context is used.",
                            "optional": true,
                            "$ref": "Target.BrowserContextID"
                        }
                    ]
                },
                {
                    "name": "close",
                    "description": "Close browser gracefully."
                },
                {
                    "name": "crash",
                    "description": "Crashes browser on the main thread.",
                    "experimental": true
                },
                {
                    "name": "getVersion",
                    "description": "Returns version information.",
                    "returns": [
                        {
                            "name": "protocolVersion",
                            "description": "Protocol version.",
                            "type": "string"
                        },
                        {
                            "name": "product",
                            "description": "Product name.",
                            "type": "string"
                        },
                        {
                            "name": "revision",
                            "description": "Product revision.",
                            "type": "string"
                        },
                        {
                            "name": "userAgent",
                            "description": "User-Agent.",
                            "type": "string"
                        },
                        {
                            "name": "jsVersion",
                            "description": "V8 version.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "getBrowserCommandLine",
                    "description": "Returns the command line switches for the browser process if, and only if\n--enable-automation is on the commandline.",
                    "experimental": true,
                    "returns": [
                        {
                            "name": "arguments",
                            "description": "Commandline parameters",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ]
                },
                {
                    "name": "getHistograms",
                    "description": "Get Chrome histograms.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "query",
                            "description": "Requested substring in name. Only histograms which have query as a\nsubstring in their name are extracted. An empty or absent query returns\nall histograms.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "delta",
                            "description": "If true, retrieve delta since last call.",
                            "optional": true,
                            "type": "boolean"
                        }
                    ],
                    "returns": [
                        {
                            "name": "histograms",
                            "description": "Histograms.",
                            "type": "array",
                            "items": {
                                "$ref": "Histogram"
                            }
                        }
                    ]
                },
                {
                    "name": "getHistogram",
                    "description": "Get a Chrome histogram by name.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "name",
                            "description": "Requested histogram name.",
                            "type": "string"
                        },
                        {
                            "name": "delta",
                            "description": "If true, retrieve delta since last call.",
                            "optional": true,
                            "type": "boolean"
                        }
                    ],
                    "returns": [
                        {
                            "name": "histogram",
                            "description": "Histogram.",
                            "$ref": "Histogram"
                        }
                    ]
                },
                {
                    "name": "getWindowBounds",
                    "description": "Get position and size of the browser window.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "windowId",
                            "description": "Browser window id.",
                            "$ref": "WindowID"
                        }
                    ],
                    "returns": [
                        {
                            "name": "bounds",
                            "description": "Bounds information of the window. When window state is 'minimized', the restored window\nposition and size are returned.",
                            "$ref": "Bounds"
                        }
                    ]
                },
                {
                    "name": "getWindowForTarget",
                    "description": "Get the browser window that contains the devtools target.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "targetId",
                            "description": "Devtools agent host id. If called as a part of the session, associated targetId is used.",
                            "optional": true,
                            "$ref": "Target.TargetID"
                        }
                    ],
                    "returns": [
                        {
                            "name": "windowId",
                            "description": "Browser window id.",
                            "$ref": "WindowID"
                        },
                        {
                            "name": "bounds",
                            "description": "Bounds information of the window. When window state is 'minimized', the restored window\nposition and size are returned.",
                            "$ref": "Bounds"
                        }
                    ]
                },
                {
                    "name": "setWindowBounds",
                    "description": "Set position and/or size of the browser window.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "windowId",
                            "description": "Browser window id.",
                            "$ref": "WindowID"
                        },
                        {
                            "name": "bounds",
                            "description": "New window bounds. The 'minimized', 'maximized' and 'fullscreen' states cannot be combined\nwith 'left', 'top', 'width' or 'height'. Leaves unspecified fields unchanged.",
                            "$ref": "Bounds"
                        }
                    ]
                },
                {
                    "name": "setDockTile",
                    "description": "Set dock tile details, platform-specific.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "badgeLabel",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "image",
                            "description": "Png encoded image.",
                            "optional": true,
                            "type": "binary"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "CSS",
            "description": "This domain exposes CSS read/write operations. All CSS objects (stylesheets, rules, and styles)\nhave an associated `id` used in subsequent operations on the related object. Each object type has\na specific `id` structure, and those are not interchangeable between objects of different kinds.\nCSS objects can be loaded using the `get*ForNode()` calls (which accept a DOM node id). A client\ncan also keep track of stylesheets via the `styleSheetAdded`/`styleSheetRemoved` events and\nsubsequently load the required stylesheet contents using the `getStyleSheet[Text]()` methods.",
            "experimental": true,
            "dependencies": [
                "DOM"
            ],
            "types": [
                {
                    "id": "StyleSheetId",
                    "type": "string"
                },
                {
                    "id": "StyleSheetOrigin",
                    "description": "Stylesheet type: \"injected\" for stylesheets injected via extension, \"user-agent\" for user-agent\nstylesheets, \"inspector\" for stylesheets created by the inspector (i.e. those holding the \"via\ninspector\" rules), \"regular\" for regular stylesheets.",
                    "type": "string",
                    "enum": [
                        "injected",
                        "user-agent",
                        "inspector",
                        "regular"
                    ]
                },
                {
                    "id": "PseudoElementMatches",
                    "description": "CSS rule collection for a single pseudo style.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "pseudoType",
                            "description": "Pseudo element type.",
                            "$ref": "DOM.PseudoType"
                        },
                        {
                            "name": "matches",
                            "description": "Matches of CSS rules applicable to the pseudo style.",
                            "type": "array",
                            "items": {
                                "$ref": "RuleMatch"
                            }
                        }
                    ]
                },
                {
                    "id": "InheritedStyleEntry",
                    "description": "Inherited CSS rule collection from ancestor node.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "inlineStyle",
                            "description": "The ancestor node's inline style, if any, in the style inheritance chain.",
                            "optional": true,
                            "$ref": "CSSStyle"
                        },
                        {
                            "name": "matchedCSSRules",
                            "description": "Matches of CSS rules matching the ancestor node in the style inheritance chain.",
                            "type": "array",
                            "items": {
                                "$ref": "RuleMatch"
                            }
                        }
                    ]
                },
                {
                    "id": "RuleMatch",
                    "description": "Match data for a CSS rule.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "rule",
                            "description": "CSS rule in the match.",
                            "$ref": "CSSRule"
                        },
                        {
                            "name": "matchingSelectors",
                            "description": "Matching selector indices in the rule's selectorList selectors (0-based).",
                            "type": "array",
                            "items": {
                                "type": "integer"
                            }
                        }
                    ]
                },
                {
                    "id": "Value",
                    "description": "Data for a simple selector (these are delimited by commas in a selector list).",
                    "type": "object",
                    "properties": [
                        {
                            "name": "text",
                            "description": "Value text.",
                            "type": "string"
                        },
                        {
                            "name": "range",
                            "description": "Value range in the underlying resource (if available).",
                            "optional": true,
                            "$ref": "SourceRange"
                        }
                    ]
                },
                {
                    "id": "SelectorList",
                    "description": "Selector list data.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "selectors",
                            "description": "Selectors in the list.",
                            "type": "array",
                            "items": {
                                "$ref": "Value"
                            }
                        },
                        {
                            "name": "text",
                            "description": "Rule selector text.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "CSSStyleSheetHeader",
                    "description": "CSS stylesheet metainformation.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "styleSheetId",
                            "description": "The stylesheet identifier.",
                            "$ref": "StyleSheetId"
                        },
                        {
                            "name": "frameId",
                            "description": "Owner frame identifier.",
                            "$ref": "Page.FrameId"
                        },
                        {
                            "name": "sourceURL",
                            "description": "Stylesheet resource URL.",
                            "type": "string"
                        },
                        {
                            "name": "sourceMapURL",
                            "description": "URL of source map associated with the stylesheet (if any).",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "origin",
                            "description": "Stylesheet origin.",
                            "$ref": "StyleSheetOrigin"
                        },
                        {
                            "name": "title",
                            "description": "Stylesheet title.",
                            "type": "string"
                        },
                        {
                            "name": "ownerNode",
                            "description": "The backend id for the owner node of the stylesheet.",
                            "optional": true,
                            "$ref": "DOM.BackendNodeId"
                        },
                        {
                            "name": "disabled",
                            "description": "Denotes whether the stylesheet is disabled.",
                            "type": "boolean"
                        },
                        {
                            "name": "hasSourceURL",
                            "description": "Whether the sourceURL field value comes from the sourceURL comment.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "isInline",
                            "description": "Whether this stylesheet is created for STYLE tag by parser. This flag is not set for\ndocument.written STYLE tags.",
                            "type": "boolean"
                        },
                        {
                            "name": "startLine",
                            "description": "Line offset of the stylesheet within the resource (zero based).",
                            "type": "number"
                        },
                        {
                            "name": "startColumn",
                            "description": "Column offset of the stylesheet within the resource (zero based).",
                            "type": "number"
                        },
                        {
                            "name": "length",
                            "description": "Size of the content (in characters).",
                            "type": "number"
                        }
                    ]
                },
                {
                    "id": "CSSRule",
                    "description": "CSS rule representation.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "styleSheetId",
                            "description": "The css style sheet identifier (absent for user agent stylesheet and user-specified\nstylesheet rules) this rule came from.",
                            "optional": true,
                            "$ref": "StyleSheetId"
                        },
                        {
                            "name": "selectorList",
                            "description": "Rule selector data.",
                            "$ref": "SelectorList"
                        },
                        {
                            "name": "origin",
                            "description": "Parent stylesheet's origin.",
                            "$ref": "StyleSheetOrigin"
                        },
                        {
                            "name": "style",
                            "description": "Associated style declaration.",
                            "$ref": "CSSStyle"
                        },
                        {
                            "name": "media",
                            "description": "Media list array (for rules involving media queries). The array enumerates media queries\nstarting with the innermost one, going outwards.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "CSSMedia"
                            }
                        }
                    ]
                },
                {
                    "id": "RuleUsage",
                    "description": "CSS coverage information.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "styleSheetId",
                            "description": "The css style sheet identifier (absent for user agent stylesheet and user-specified\nstylesheet rules) this rule came from.",
                            "$ref": "StyleSheetId"
                        },
                        {
                            "name": "startOffset",
                            "description": "Offset of the start of the rule (including selector) from the beginning of the stylesheet.",
                            "type": "number"
                        },
                        {
                            "name": "endOffset",
                            "description": "Offset of the end of the rule body from the beginning of the stylesheet.",
                            "type": "number"
                        },
                        {
                            "name": "used",
                            "description": "Indicates whether the rule was actually used by some element in the page.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "id": "SourceRange",
                    "description": "Text range within a resource. All numbers are zero-based.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "startLine",
                            "description": "Start line of range.",
                            "type": "integer"
                        },
                        {
                            "name": "startColumn",
                            "description": "Start column of range (inclusive).",
                            "type": "integer"
                        },
                        {
                            "name": "endLine",
                            "description": "End line of range",
                            "type": "integer"
                        },
                        {
                            "name": "endColumn",
                            "description": "End column of range (exclusive).",
                            "type": "integer"
                        }
                    ]
                },
                {
                    "id": "ShorthandEntry",
                    "type": "object",
                    "properties": [
                        {
                            "name": "name",
                            "description": "Shorthand name.",
                            "type": "string"
                        },
                        {
                            "name": "value",
                            "description": "Shorthand value.",
                            "type": "string"
                        },
                        {
                            "name": "important",
                            "description": "Whether the property has \"!important\" annotation (implies `false` if absent).",
                            "optional": true,
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "id": "CSSComputedStyleProperty",
                    "type": "object",
                    "properties": [
                        {
                            "name": "name",
                            "description": "Computed style property name.",
                            "type": "string"
                        },
                        {
                            "name": "value",
                            "description": "Computed style property value.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "CSSStyle",
                    "description": "CSS style representation.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "styleSheetId",
                            "description": "The css style sheet identifier (absent for user agent stylesheet and user-specified\nstylesheet rules) this rule came from.",
                            "optional": true,
                            "$ref": "StyleSheetId"
                        },
                        {
                            "name": "cssProperties",
                            "description": "CSS properties in the style.",
                            "type": "array",
                            "items": {
                                "$ref": "CSSProperty"
                            }
                        },
                        {
                            "name": "shorthandEntries",
                            "description": "Computed values for all shorthands found in the style.",
                            "type": "array",
                            "items": {
                                "$ref": "ShorthandEntry"
                            }
                        },
                        {
                            "name": "cssText",
                            "description": "Style declaration text (if available).",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "range",
                            "description": "Style declaration range in the enclosing stylesheet (if available).",
                            "optional": true,
                            "$ref": "SourceRange"
                        }
                    ]
                },
                {
                    "id": "CSSProperty",
                    "description": "CSS property declaration data.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "name",
                            "description": "The property name.",
                            "type": "string"
                        },
                        {
                            "name": "value",
                            "description": "The property value.",
                            "type": "string"
                        },
                        {
                            "name": "important",
                            "description": "Whether the property has \"!important\" annotation (implies `false` if absent).",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "implicit",
                            "description": "Whether the property is implicit (implies `false` if absent).",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "text",
                            "description": "The full property text as specified in the style.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "parsedOk",
                            "description": "Whether the property is understood by the browser (implies `true` if absent).",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "disabled",
                            "description": "Whether the property is disabled by the user (present for source-based properties only).",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "range",
                            "description": "The entire property range in the enclosing style declaration (if available).",
                            "optional": true,
                            "$ref": "SourceRange"
                        }
                    ]
                },
                {
                    "id": "CSSMedia",
                    "description": "CSS media rule descriptor.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "text",
                            "description": "Media query text.",
                            "type": "string"
                        },
                        {
                            "name": "source",
                            "description": "Source of the media query: \"mediaRule\" if specified by a @media rule, \"importRule\" if\nspecified by an @import rule, \"linkedSheet\" if specified by a \"media\" attribute in a linked\nstylesheet's LINK tag, \"inlineSheet\" if specified by a \"media\" attribute in an inline\nstylesheet's STYLE tag.",
                            "type": "string",
                            "enum": [
                                "mediaRule",
                                "importRule",
                                "linkedSheet",
                                "inlineSheet"
                            ]
                        },
                        {
                            "name": "sourceURL",
                            "description": "URL of the document containing the media query description.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "range",
                            "description": "The associated rule (@media or @import) header range in the enclosing stylesheet (if\navailable).",
                            "optional": true,
                            "$ref": "SourceRange"
                        },
                        {
                            "name": "styleSheetId",
                            "description": "Identifier of the stylesheet containing this object (if exists).",
                            "optional": true,
                            "$ref": "StyleSheetId"
                        },
                        {
                            "name": "mediaList",
                            "description": "Array of media queries.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "MediaQuery"
                            }
                        }
                    ]
                },
                {
                    "id": "MediaQuery",
                    "description": "Media query descriptor.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "expressions",
                            "description": "Array of media query expressions.",
                            "type": "array",
                            "items": {
                                "$ref": "MediaQueryExpression"
                            }
                        },
                        {
                            "name": "active",
                            "description": "Whether the media query condition is satisfied.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "id": "MediaQueryExpression",
                    "description": "Media query expression descriptor.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "value",
                            "description": "Media query expression value.",
                            "type": "number"
                        },
                        {
                            "name": "unit",
                            "description": "Media query expression units.",
                            "type": "string"
                        },
                        {
                            "name": "feature",
                            "description": "Media query expression feature.",
                            "type": "string"
                        },
                        {
                            "name": "valueRange",
                            "description": "The associated range of the value text in the enclosing stylesheet (if available).",
                            "optional": true,
                            "$ref": "SourceRange"
                        },
                        {
                            "name": "computedLength",
                            "description": "Computed length of media query expression (if applicable).",
                            "optional": true,
                            "type": "number"
                        }
                    ]
                },
                {
                    "id": "PlatformFontUsage",
                    "description": "Information about amount of glyphs that were rendered with given font.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "familyName",
                            "description": "Font's family name reported by platform.",
                            "type": "string"
                        },
                        {
                            "name": "isCustomFont",
                            "description": "Indicates if the font was downloaded or resolved locally.",
                            "type": "boolean"
                        },
                        {
                            "name": "glyphCount",
                            "description": "Amount of glyphs that were rendered with this font.",
                            "type": "number"
                        }
                    ]
                },
                {
                    "id": "FontFace",
                    "description": "Properties of a web font: https://www.w3.org/TR/2008/REC-CSS2-20080411/fonts.html#font-descriptions",
                    "type": "object",
                    "properties": [
                        {
                            "name": "fontFamily",
                            "description": "The font-family.",
                            "type": "string"
                        },
                        {
                            "name": "fontStyle",
                            "description": "The font-style.",
                            "type": "string"
                        },
                        {
                            "name": "fontVariant",
                            "description": "The font-variant.",
                            "type": "string"
                        },
                        {
                            "name": "fontWeight",
                            "description": "The font-weight.",
                            "type": "string"
                        },
                        {
                            "name": "fontStretch",
                            "description": "The font-stretch.",
                            "type": "string"
                        },
                        {
                            "name": "unicodeRange",
                            "description": "The unicode-range.",
                            "type": "string"
                        },
                        {
                            "name": "src",
                            "description": "The src.",
                            "type": "string"
                        },
                        {
                            "name": "platformFontFamily",
                            "description": "The resolved platform font family",
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "CSSKeyframesRule",
                    "description": "CSS keyframes rule representation.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "animationName",
                            "description": "Animation name.",
                            "$ref": "Value"
                        },
                        {
                            "name": "keyframes",
                            "description": "List of keyframes.",
                            "type": "array",
                            "items": {
                                "$ref": "CSSKeyframeRule"
                            }
                        }
                    ]
                },
                {
                    "id": "CSSKeyframeRule",
                    "description": "CSS keyframe rule representation.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "styleSheetId",
                            "description": "The css style sheet identifier (absent for user agent stylesheet and user-specified\nstylesheet rules) this rule came from.",
                            "optional": true,
                            "$ref": "StyleSheetId"
                        },
                        {
                            "name": "origin",
                            "description": "Parent stylesheet's origin.",
                            "$ref": "StyleSheetOrigin"
                        },
                        {
                            "name": "keyText",
                            "description": "Associated key text.",
                            "$ref": "Value"
                        },
                        {
                            "name": "style",
                            "description": "Associated style declaration.",
                            "$ref": "CSSStyle"
                        }
                    ]
                },
                {
                    "id": "StyleDeclarationEdit",
                    "description": "A descriptor of operation to mutate style declaration text.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "styleSheetId",
                            "description": "The css style sheet identifier.",
                            "$ref": "StyleSheetId"
                        },
                        {
                            "name": "range",
                            "description": "The range of the style text in the enclosing stylesheet.",
                            "$ref": "SourceRange"
                        },
                        {
                            "name": "text",
                            "description": "New style text.",
                            "type": "string"
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "addRule",
                    "description": "Inserts a new rule with the given `ruleText` in a stylesheet with given `styleSheetId`, at the\nposition specified by `location`.",
                    "parameters": [
                        {
                            "name": "styleSheetId",
                            "description": "The css style sheet identifier where a new rule should be inserted.",
                            "$ref": "StyleSheetId"
                        },
                        {
                            "name": "ruleText",
                            "description": "The text of a new rule.",
                            "type": "string"
                        },
                        {
                            "name": "location",
                            "description": "Text position of a new rule in the target style sheet.",
                            "$ref": "SourceRange"
                        }
                    ],
                    "returns": [
                        {
                            "name": "rule",
                            "description": "The newly created rule.",
                            "$ref": "CSSRule"
                        }
                    ]
                },
                {
                    "name": "collectClassNames",
                    "description": "Returns all class names from specified stylesheet.",
                    "parameters": [
                        {
                            "name": "styleSheetId",
                            "$ref": "StyleSheetId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "classNames",
                            "description": "Class name list.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ]
                },
                {
                    "name": "createStyleSheet",
                    "description": "Creates a new special \"via-inspector\" stylesheet in the frame with given `frameId`.",
                    "parameters": [
                        {
                            "name": "frameId",
                            "description": "Identifier of the frame where \"via-inspector\" stylesheet should be created.",
                            "$ref": "Page.FrameId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "styleSheetId",
                            "description": "Identifier of the created \"via-inspector\" stylesheet.",
                            "$ref": "StyleSheetId"
                        }
                    ]
                },
                {
                    "name": "disable",
                    "description": "Disables the CSS agent for the given page."
                },
                {
                    "name": "enable",
                    "description": "Enables the CSS agent for the given page. Clients should not assume that the CSS agent has been\nenabled until the result of this command is received."
                },
                {
                    "name": "forcePseudoState",
                    "description": "Ensures that the given node will have specified pseudo-classes whenever its style is computed by\nthe browser.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "The element id for which to force the pseudo state.",
                            "$ref": "DOM.NodeId"
                        },
                        {
                            "name": "forcedPseudoClasses",
                            "description": "Element pseudo classes to force when computing the element's style.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ]
                },
                {
                    "name": "getBackgroundColors",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node to get background colors for.",
                            "$ref": "DOM.NodeId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "backgroundColors",
                            "description": "The range of background colors behind this element, if it contains any visible text. If no\nvisible text is present, this will be undefined. In the case of a flat background color,\nthis will consist of simply that color. In the case of a gradient, this will consist of each\nof the color stops. For anything more complicated, this will be an empty array. Images will\nbe ignored (as if the image had failed to load).",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        },
                        {
                            "name": "computedFontSize",
                            "description": "The computed font size for this node, as a CSS computed value string (e.g. '12px').",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "computedFontWeight",
                            "description": "The computed font weight for this node, as a CSS computed value string (e.g. 'normal' or\n'100').",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "computedBodyFontSize",
                            "description": "The computed font size for the document body, as a computed CSS value string (e.g. '16px').",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "getComputedStyleForNode",
                    "description": "Returns the computed style for a DOM node identified by `nodeId`.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "$ref": "DOM.NodeId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "computedStyle",
                            "description": "Computed style for the specified DOM node.",
                            "type": "array",
                            "items": {
                                "$ref": "CSSComputedStyleProperty"
                            }
                        }
                    ]
                },
                {
                    "name": "getInlineStylesForNode",
                    "description": "Returns the styles defined inline (explicitly in the \"style\" attribute and implicitly, using DOM\nattributes) for a DOM node identified by `nodeId`.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "$ref": "DOM.NodeId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "inlineStyle",
                            "description": "Inline style for the specified DOM node.",
                            "optional": true,
                            "$ref": "CSSStyle"
                        },
                        {
                            "name": "attributesStyle",
                            "description": "Attribute-defined element style (e.g. resulting from \"width=20 height=100%\").",
                            "optional": true,
                            "$ref": "CSSStyle"
                        }
                    ]
                },
                {
                    "name": "getMatchedStylesForNode",
                    "description": "Returns requested styles for a DOM node identified by `nodeId`.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "$ref": "DOM.NodeId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "inlineStyle",
                            "description": "Inline style for the specified DOM node.",
                            "optional": true,
                            "$ref": "CSSStyle"
                        },
                        {
                            "name": "attributesStyle",
                            "description": "Attribute-defined element style (e.g. resulting from \"width=20 height=100%\").",
                            "optional": true,
                            "$ref": "CSSStyle"
                        },
                        {
                            "name": "matchedCSSRules",
                            "description": "CSS rules matching this node, from all applicable stylesheets.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "RuleMatch"
                            }
                        },
                        {
                            "name": "pseudoElements",
                            "description": "Pseudo style matches for this node.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "PseudoElementMatches"
                            }
                        },
                        {
                            "name": "inherited",
                            "description": "A chain of inherited styles (from the immediate node parent up to the DOM tree root).",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "InheritedStyleEntry"
                            }
                        },
                        {
                            "name": "cssKeyframesRules",
                            "description": "A list of CSS keyframed animations matching this node.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "CSSKeyframesRule"
                            }
                        }
                    ]
                },
                {
                    "name": "getMediaQueries",
                    "description": "Returns all media queries parsed by the rendering engine.",
                    "returns": [
                        {
                            "name": "medias",
                            "type": "array",
                            "items": {
                                "$ref": "CSSMedia"
                            }
                        }
                    ]
                },
                {
                    "name": "getPlatformFontsForNode",
                    "description": "Requests information about platform fonts which we used to render child TextNodes in the given\nnode.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "$ref": "DOM.NodeId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "fonts",
                            "description": "Usage statistics for every employed platform font.",
                            "type": "array",
                            "items": {
                                "$ref": "PlatformFontUsage"
                            }
                        }
                    ]
                },
                {
                    "name": "getStyleSheetText",
                    "description": "Returns the current textual content for a stylesheet.",
                    "parameters": [
                        {
                            "name": "styleSheetId",
                            "$ref": "StyleSheetId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "text",
                            "description": "The stylesheet text.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "setEffectivePropertyValueForNode",
                    "description": "Find a rule with the given active property for the given node and set the new value for this\nproperty",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "The element id for which to set property.",
                            "$ref": "DOM.NodeId"
                        },
                        {
                            "name": "propertyName",
                            "type": "string"
                        },
                        {
                            "name": "value",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "setKeyframeKey",
                    "description": "Modifies the keyframe rule key text.",
                    "parameters": [
                        {
                            "name": "styleSheetId",
                            "$ref": "StyleSheetId"
                        },
                        {
                            "name": "range",
                            "$ref": "SourceRange"
                        },
                        {
                            "name": "keyText",
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "keyText",
                            "description": "The resulting key text after modification.",
                            "$ref": "Value"
                        }
                    ]
                },
                {
                    "name": "setMediaText",
                    "description": "Modifies the rule selector.",
                    "parameters": [
                        {
                            "name": "styleSheetId",
                            "$ref": "StyleSheetId"
                        },
                        {
                            "name": "range",
                            "$ref": "SourceRange"
                        },
                        {
                            "name": "text",
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "media",
                            "description": "The resulting CSS media rule after modification.",
                            "$ref": "CSSMedia"
                        }
                    ]
                },
                {
                    "name": "setRuleSelector",
                    "description": "Modifies the rule selector.",
                    "parameters": [
                        {
                            "name": "styleSheetId",
                            "$ref": "StyleSheetId"
                        },
                        {
                            "name": "range",
                            "$ref": "SourceRange"
                        },
                        {
                            "name": "selector",
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "selectorList",
                            "description": "The resulting selector list after modification.",
                            "$ref": "SelectorList"
                        }
                    ]
                },
                {
                    "name": "setStyleSheetText",
                    "description": "Sets the new stylesheet text.",
                    "parameters": [
                        {
                            "name": "styleSheetId",
                            "$ref": "StyleSheetId"
                        },
                        {
                            "name": "text",
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "sourceMapURL",
                            "description": "URL of source map associated with script (if any).",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "setStyleTexts",
                    "description": "Applies specified style edits one after another in the given order.",
                    "parameters": [
                        {
                            "name": "edits",
                            "type": "array",
                            "items": {
                                "$ref": "StyleDeclarationEdit"
                            }
                        }
                    ],
                    "returns": [
                        {
                            "name": "styles",
                            "description": "The resulting styles after modification.",
                            "type": "array",
                            "items": {
                                "$ref": "CSSStyle"
                            }
                        }
                    ]
                },
                {
                    "name": "startRuleUsageTracking",
                    "description": "Enables the selector recording."
                },
                {
                    "name": "stopRuleUsageTracking",
                    "description": "Stop tracking rule usage and return the list of rules that were used since last call to\n`takeCoverageDelta` (or since start of coverage instrumentation)",
                    "returns": [
                        {
                            "name": "ruleUsage",
                            "type": "array",
                            "items": {
                                "$ref": "RuleUsage"
                            }
                        }
                    ]
                },
                {
                    "name": "takeCoverageDelta",
                    "description": "Obtain list of rules that became used since last call to this method (or since start of coverage\ninstrumentation)",
                    "returns": [
                        {
                            "name": "coverage",
                            "type": "array",
                            "items": {
                                "$ref": "RuleUsage"
                            }
                        }
                    ]
                }
            ],
            "events": [
                {
                    "name": "fontsUpdated",
                    "description": "Fires whenever a web font is updated.  A non-empty font parameter indicates a successfully loaded\nweb font",
                    "parameters": [
                        {
                            "name": "font",
                            "description": "The web font that has loaded.",
                            "optional": true,
                            "$ref": "FontFace"
                        }
                    ]
                },
                {
                    "name": "mediaQueryResultChanged",
                    "description": "Fires whenever a MediaQuery result changes (for example, after a browser window has been\nresized.) The current implementation considers only viewport-dependent media features."
                },
                {
                    "name": "styleSheetAdded",
                    "description": "Fired whenever an active document stylesheet is added.",
                    "parameters": [
                        {
                            "name": "header",
                            "description": "Added stylesheet metainfo.",
                            "$ref": "CSSStyleSheetHeader"
                        }
                    ]
                },
                {
                    "name": "styleSheetChanged",
                    "description": "Fired whenever a stylesheet is changed as a result of the client operation.",
                    "parameters": [
                        {
                            "name": "styleSheetId",
                            "$ref": "StyleSheetId"
                        }
                    ]
                },
                {
                    "name": "styleSheetRemoved",
                    "description": "Fired whenever an active document stylesheet is removed.",
                    "parameters": [
                        {
                            "name": "styleSheetId",
                            "description": "Identifier of the removed stylesheet.",
                            "$ref": "StyleSheetId"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "CacheStorage",
            "experimental": true,
            "types": [
                {
                    "id": "CacheId",
                    "description": "Unique identifier of the Cache object.",
                    "type": "string"
                },
                {
                    "id": "CachedResponseType",
                    "description": "type of HTTP response cached",
                    "type": "string",
                    "enum": [
                        "basic",
                        "cors",
                        "default",
                        "error",
                        "opaqueResponse",
                        "opaqueRedirect"
                    ]
                },
                {
                    "id": "DataEntry",
                    "description": "Data entry.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "requestURL",
                            "description": "Request URL.",
                            "type": "string"
                        },
                        {
                            "name": "requestMethod",
                            "description": "Request method.",
                            "type": "string"
                        },
                        {
                            "name": "requestHeaders",
                            "description": "Request headers",
                            "type": "array",
                            "items": {
                                "$ref": "Header"
                            }
                        },
                        {
                            "name": "responseTime",
                            "description": "Number of seconds since epoch.",
                            "type": "number"
                        },
                        {
                            "name": "responseStatus",
                            "description": "HTTP response status code.",
                            "type": "integer"
                        },
                        {
                            "name": "responseStatusText",
                            "description": "HTTP response status text.",
                            "type": "string"
                        },
                        {
                            "name": "responseType",
                            "description": "HTTP response type",
                            "$ref": "CachedResponseType"
                        },
                        {
                            "name": "responseHeaders",
                            "description": "Response headers",
                            "type": "array",
                            "items": {
                                "$ref": "Header"
                            }
                        }
                    ]
                },
                {
                    "id": "Cache",
                    "description": "Cache identifier.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "cacheId",
                            "description": "An opaque unique id of the cache.",
                            "$ref": "CacheId"
                        },
                        {
                            "name": "securityOrigin",
                            "description": "Security origin of the cache.",
                            "type": "string"
                        },
                        {
                            "name": "cacheName",
                            "description": "The name of the cache.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "Header",
                    "type": "object",
                    "properties": [
                        {
                            "name": "name",
                            "type": "string"
                        },
                        {
                            "name": "value",
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "CachedResponse",
                    "description": "Cached response",
                    "type": "object",
                    "properties": [
                        {
                            "name": "body",
                            "description": "Entry content, base64-encoded.",
                            "type": "binary"
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "deleteCache",
                    "description": "Deletes a cache.",
                    "parameters": [
                        {
                            "name": "cacheId",
                            "description": "Id of cache for deletion.",
                            "$ref": "CacheId"
                        }
                    ]
                },
                {
                    "name": "deleteEntry",
                    "description": "Deletes a cache entry.",
                    "parameters": [
                        {
                            "name": "cacheId",
                            "description": "Id of cache where the entry will be deleted.",
                            "$ref": "CacheId"
                        },
                        {
                            "name": "request",
                            "description": "URL spec of the request.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "requestCacheNames",
                    "description": "Requests cache names.",
                    "parameters": [
                        {
                            "name": "securityOrigin",
                            "description": "Security origin.",
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "caches",
                            "description": "Caches for the security origin.",
                            "type": "array",
                            "items": {
                                "$ref": "Cache"
                            }
                        }
                    ]
                },
                {
                    "name": "requestCachedResponse",
                    "description": "Fetches cache entry.",
                    "parameters": [
                        {
                            "name": "cacheId",
                            "description": "Id of cache that contains the enty.",
                            "$ref": "CacheId"
                        },
                        {
                            "name": "requestURL",
                            "description": "URL spec of the request.",
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "response",
                            "description": "Response read from the cache.",
                            "$ref": "CachedResponse"
                        }
                    ]
                },
                {
                    "name": "requestEntries",
                    "description": "Requests data from cache.",
                    "parameters": [
                        {
                            "name": "cacheId",
                            "description": "ID of cache to get entries from.",
                            "$ref": "CacheId"
                        },
                        {
                            "name": "skipCount",
                            "description": "Number of records to skip.",
                            "type": "integer"
                        },
                        {
                            "name": "pageSize",
                            "description": "Number of records to fetch.",
                            "type": "integer"
                        }
                    ],
                    "returns": [
                        {
                            "name": "cacheDataEntries",
                            "description": "Array of object store data entries.",
                            "type": "array",
                            "items": {
                                "$ref": "DataEntry"
                            }
                        },
                        {
                            "name": "hasMore",
                            "description": "If true, there are more entries to fetch in the given range.",
                            "type": "boolean"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "DOM",
            "description": "This domain exposes DOM read/write operations. Each DOM Node is represented with its mirror object\nthat has an `id`. This `id` can be used to get additional information on the Node, resolve it into\nthe JavaScript object wrapper, etc. It is important that client receives DOM events only for the\nnodes that are known to the client. Backend keeps track of the nodes that were sent to the client\nand never sends the same node twice. It is client's responsibility to collect information about\nthe nodes that were sent to the client.<p>Note that `iframe` owner elements will return\ncorresponding document elements as their child nodes.</p>",
            "dependencies": [
                "Runtime"
            ],
            "types": [
                {
                    "id": "NodeId",
                    "description": "Unique DOM node identifier.",
                    "type": "integer"
                },
                {
                    "id": "BackendNodeId",
                    "description": "Unique DOM node identifier used to reference a node that may not have been pushed to the\nfront-end.",
                    "type": "integer"
                },
                {
                    "id": "BackendNode",
                    "description": "Backend node with a friendly name.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "nodeType",
                            "description": "`Node`'s nodeType.",
                            "type": "integer"
                        },
                        {
                            "name": "nodeName",
                            "description": "`Node`'s nodeName.",
                            "type": "string"
                        },
                        {
                            "name": "backendNodeId",
                            "$ref": "BackendNodeId"
                        }
                    ]
                },
                {
                    "id": "PseudoType",
                    "description": "Pseudo element type.",
                    "type": "string",
                    "enum": [
                        "first-line",
                        "first-letter",
                        "before",
                        "after",
                        "backdrop",
                        "selection",
                        "first-line-inherited",
                        "scrollbar",
                        "scrollbar-thumb",
                        "scrollbar-button",
                        "scrollbar-track",
                        "scrollbar-track-piece",
                        "scrollbar-corner",
                        "resizer",
                        "input-list-button"
                    ]
                },
                {
                    "id": "ShadowRootType",
                    "description": "Shadow root type.",
                    "type": "string",
                    "enum": [
                        "user-agent",
                        "open",
                        "closed"
                    ]
                },
                {
                    "id": "Node",
                    "description": "DOM interaction is implemented in terms of mirror objects that represent the actual DOM nodes.\nDOMNode is a base node mirror type.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "nodeId",
                            "description": "Node identifier that is passed into the rest of the DOM messages as the `nodeId`. Backend\nwill only push node with given `id` once. It is aware of all requested nodes and will only\nfire DOM events for nodes known to the client.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "parentId",
                            "description": "The id of the parent node if any.",
                            "optional": true,
                            "$ref": "NodeId"
                        },
                        {
                            "name": "backendNodeId",
                            "description": "The BackendNodeId for this node.",
                            "$ref": "BackendNodeId"
                        },
                        {
                            "name": "nodeType",
                            "description": "`Node`'s nodeType.",
                            "type": "integer"
                        },
                        {
                            "name": "nodeName",
                            "description": "`Node`'s nodeName.",
                            "type": "string"
                        },
                        {
                            "name": "localName",
                            "description": "`Node`'s localName.",
                            "type": "string"
                        },
                        {
                            "name": "nodeValue",
                            "description": "`Node`'s nodeValue.",
                            "type": "string"
                        },
                        {
                            "name": "childNodeCount",
                            "description": "Child count for `Container` nodes.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "children",
                            "description": "Child nodes of this node when requested with children.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "Node"
                            }
                        },
                        {
                            "name": "attributes",
                            "description": "Attributes of the `Element` node in the form of flat array `[name1, value1, name2, value2]`.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        },
                        {
                            "name": "documentURL",
                            "description": "Document URL that `Document` or `FrameOwner` node points to.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "baseURL",
                            "description": "Base URL that `Document` or `FrameOwner` node uses for URL completion.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "publicId",
                            "description": "`DocumentType`'s publicId.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "systemId",
                            "description": "`DocumentType`'s systemId.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "internalSubset",
                            "description": "`DocumentType`'s internalSubset.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "xmlVersion",
                            "description": "`Document`'s XML version in case of XML documents.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "name",
                            "description": "`Attr`'s name.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "value",
                            "description": "`Attr`'s value.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "pseudoType",
                            "description": "Pseudo element type for this node.",
                            "optional": true,
                            "$ref": "PseudoType"
                        },
                        {
                            "name": "shadowRootType",
                            "description": "Shadow root type.",
                            "optional": true,
                            "$ref": "ShadowRootType"
                        },
                        {
                            "name": "frameId",
                            "description": "Frame ID for frame owner elements.",
                            "optional": true,
                            "$ref": "Page.FrameId"
                        },
                        {
                            "name": "contentDocument",
                            "description": "Content document for frame owner elements.",
                            "optional": true,
                            "$ref": "Node"
                        },
                        {
                            "name": "shadowRoots",
                            "description": "Shadow root list for given element host.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "Node"
                            }
                        },
                        {
                            "name": "templateContent",
                            "description": "Content document fragment for template elements.",
                            "optional": true,
                            "$ref": "Node"
                        },
                        {
                            "name": "pseudoElements",
                            "description": "Pseudo elements associated with this node.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "Node"
                            }
                        },
                        {
                            "name": "importedDocument",
                            "description": "Import document for the HTMLImport links.",
                            "optional": true,
                            "$ref": "Node"
                        },
                        {
                            "name": "distributedNodes",
                            "description": "Distributed nodes for given insertion point.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "BackendNode"
                            }
                        },
                        {
                            "name": "isSVG",
                            "description": "Whether the node is SVG.",
                            "optional": true,
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "id": "RGBA",
                    "description": "A structure holding an RGBA color.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "r",
                            "description": "The red component, in the [0-255] range.",
                            "type": "integer"
                        },
                        {
                            "name": "g",
                            "description": "The green component, in the [0-255] range.",
                            "type": "integer"
                        },
                        {
                            "name": "b",
                            "description": "The blue component, in the [0-255] range.",
                            "type": "integer"
                        },
                        {
                            "name": "a",
                            "description": "The alpha component, in the [0-1] range (default: 1).",
                            "optional": true,
                            "type": "number"
                        }
                    ]
                },
                {
                    "id": "Quad",
                    "description": "An array of quad vertices, x immediately followed by y for each point, points clock-wise.",
                    "type": "array",
                    "items": {
                        "type": "number"
                    }
                },
                {
                    "id": "BoxModel",
                    "description": "Box model.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "content",
                            "description": "Content box",
                            "$ref": "Quad"
                        },
                        {
                            "name": "padding",
                            "description": "Padding box",
                            "$ref": "Quad"
                        },
                        {
                            "name": "border",
                            "description": "Border box",
                            "$ref": "Quad"
                        },
                        {
                            "name": "margin",
                            "description": "Margin box",
                            "$ref": "Quad"
                        },
                        {
                            "name": "width",
                            "description": "Node width",
                            "type": "integer"
                        },
                        {
                            "name": "height",
                            "description": "Node height",
                            "type": "integer"
                        },
                        {
                            "name": "shapeOutside",
                            "description": "Shape outside coordinates",
                            "optional": true,
                            "$ref": "ShapeOutsideInfo"
                        }
                    ]
                },
                {
                    "id": "ShapeOutsideInfo",
                    "description": "CSS Shape Outside details.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "bounds",
                            "description": "Shape bounds",
                            "$ref": "Quad"
                        },
                        {
                            "name": "shape",
                            "description": "Shape coordinate details",
                            "type": "array",
                            "items": {
                                "type": "any"
                            }
                        },
                        {
                            "name": "marginShape",
                            "description": "Margin shape bounds",
                            "type": "array",
                            "items": {
                                "type": "any"
                            }
                        }
                    ]
                },
                {
                    "id": "Rect",
                    "description": "Rectangle.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "x",
                            "description": "X coordinate",
                            "type": "number"
                        },
                        {
                            "name": "y",
                            "description": "Y coordinate",
                            "type": "number"
                        },
                        {
                            "name": "width",
                            "description": "Rectangle width",
                            "type": "number"
                        },
                        {
                            "name": "height",
                            "description": "Rectangle height",
                            "type": "number"
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "collectClassNamesFromSubtree",
                    "description": "Collects class names for the node with given id and all of it's child nodes.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node to collect class names.",
                            "$ref": "NodeId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "classNames",
                            "description": "Class name list.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ]
                },
                {
                    "name": "copyTo",
                    "description": "Creates a deep copy of the specified node and places it into the target container before the\ngiven anchor.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node to copy.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "targetNodeId",
                            "description": "Id of the element to drop the copy into.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "insertBeforeNodeId",
                            "description": "Drop the copy before this node (if absent, the copy becomes the last child of\n`targetNodeId`).",
                            "optional": true,
                            "$ref": "NodeId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node clone.",
                            "$ref": "NodeId"
                        }
                    ]
                },
                {
                    "name": "describeNode",
                    "description": "Describes node given its id, does not require domain to be enabled. Does not start tracking any\nobjects, can be used for automation.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Identifier of the node.",
                            "optional": true,
                            "$ref": "NodeId"
                        },
                        {
                            "name": "backendNodeId",
                            "description": "Identifier of the backend node.",
                            "optional": true,
                            "$ref": "BackendNodeId"
                        },
                        {
                            "name": "objectId",
                            "description": "JavaScript object id of the node wrapper.",
                            "optional": true,
                            "$ref": "Runtime.RemoteObjectId"
                        },
                        {
                            "name": "depth",
                            "description": "The maximum depth at which children should be retrieved, defaults to 1. Use -1 for the\nentire subtree or provide an integer larger than 0.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "pierce",
                            "description": "Whether or not iframes and shadow roots should be traversed when returning the subtree\n(default is false).",
                            "optional": true,
                            "type": "boolean"
                        }
                    ],
                    "returns": [
                        {
                            "name": "node",
                            "description": "Node description.",
                            "$ref": "Node"
                        }
                    ]
                },
                {
                    "name": "disable",
                    "description": "Disables DOM agent for the given page."
                },
                {
                    "name": "discardSearchResults",
                    "description": "Discards search results from the session with the given id. `getSearchResults` should no longer\nbe called for that search.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "searchId",
                            "description": "Unique search session identifier.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "enable",
                    "description": "Enables DOM agent for the given page."
                },
                {
                    "name": "focus",
                    "description": "Focuses the given element.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Identifier of the node.",
                            "optional": true,
                            "$ref": "NodeId"
                        },
                        {
                            "name": "backendNodeId",
                            "description": "Identifier of the backend node.",
                            "optional": true,
                            "$ref": "BackendNodeId"
                        },
                        {
                            "name": "objectId",
                            "description": "JavaScript object id of the node wrapper.",
                            "optional": true,
                            "$ref": "Runtime.RemoteObjectId"
                        }
                    ]
                },
                {
                    "name": "getAttributes",
                    "description": "Returns attributes for the specified node.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node to retrieve attibutes for.",
                            "$ref": "NodeId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "attributes",
                            "description": "An interleaved array of node attribute names and values.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ]
                },
                {
                    "name": "getBoxModel",
                    "description": "Returns boxes for the given node.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Identifier of the node.",
                            "optional": true,
                            "$ref": "NodeId"
                        },
                        {
                            "name": "backendNodeId",
                            "description": "Identifier of the backend node.",
                            "optional": true,
                            "$ref": "BackendNodeId"
                        },
                        {
                            "name": "objectId",
                            "description": "JavaScript object id of the node wrapper.",
                            "optional": true,
                            "$ref": "Runtime.RemoteObjectId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "model",
                            "description": "Box model for the node.",
                            "$ref": "BoxModel"
                        }
                    ]
                },
                {
                    "name": "getContentQuads",
                    "description": "Returns quads that describe node position on the page. This method\nmight return multiple quads for inline nodes.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Identifier of the node.",
                            "optional": true,
                            "$ref": "NodeId"
                        },
                        {
                            "name": "backendNodeId",
                            "description": "Identifier of the backend node.",
                            "optional": true,
                            "$ref": "BackendNodeId"
                        },
                        {
                            "name": "objectId",
                            "description": "JavaScript object id of the node wrapper.",
                            "optional": true,
                            "$ref": "Runtime.RemoteObjectId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "quads",
                            "description": "Quads that describe node layout relative to viewport.",
                            "type": "array",
                            "items": {
                                "$ref": "Quad"
                            }
                        }
                    ]
                },
                {
                    "name": "getDocument",
                    "description": "Returns the root DOM node (and optionally the subtree) to the caller.",
                    "parameters": [
                        {
                            "name": "depth",
                            "description": "The maximum depth at which children should be retrieved, defaults to 1. Use -1 for the\nentire subtree or provide an integer larger than 0.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "pierce",
                            "description": "Whether or not iframes and shadow roots should be traversed when returning the subtree\n(default is false).",
                            "optional": true,
                            "type": "boolean"
                        }
                    ],
                    "returns": [
                        {
                            "name": "root",
                            "description": "Resulting node.",
                            "$ref": "Node"
                        }
                    ]
                },
                {
                    "name": "getFlattenedDocument",
                    "description": "Returns the root DOM node (and optionally the subtree) to the caller.",
                    "parameters": [
                        {
                            "name": "depth",
                            "description": "The maximum depth at which children should be retrieved, defaults to 1. Use -1 for the\nentire subtree or provide an integer larger than 0.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "pierce",
                            "description": "Whether or not iframes and shadow roots should be traversed when returning the subtree\n(default is false).",
                            "optional": true,
                            "type": "boolean"
                        }
                    ],
                    "returns": [
                        {
                            "name": "nodes",
                            "description": "Resulting node.",
                            "type": "array",
                            "items": {
                                "$ref": "Node"
                            }
                        }
                    ]
                },
                {
                    "name": "getNodeForLocation",
                    "description": "Returns node id at given location. Depending on whether DOM domain is enabled, nodeId is\neither returned or not.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "x",
                            "description": "X coordinate.",
                            "type": "integer"
                        },
                        {
                            "name": "y",
                            "description": "Y coordinate.",
                            "type": "integer"
                        },
                        {
                            "name": "includeUserAgentShadowDOM",
                            "description": "False to skip to the nearest non-UA shadow root ancestor (default: false).",
                            "optional": true,
                            "type": "boolean"
                        }
                    ],
                    "returns": [
                        {
                            "name": "backendNodeId",
                            "description": "Resulting node.",
                            "$ref": "BackendNodeId"
                        },
                        {
                            "name": "nodeId",
                            "description": "Id of the node at given coordinates, only when enabled.",
                            "optional": true,
                            "$ref": "NodeId"
                        }
                    ]
                },
                {
                    "name": "getOuterHTML",
                    "description": "Returns node's HTML markup.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Identifier of the node.",
                            "optional": true,
                            "$ref": "NodeId"
                        },
                        {
                            "name": "backendNodeId",
                            "description": "Identifier of the backend node.",
                            "optional": true,
                            "$ref": "BackendNodeId"
                        },
                        {
                            "name": "objectId",
                            "description": "JavaScript object id of the node wrapper.",
                            "optional": true,
                            "$ref": "Runtime.RemoteObjectId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "outerHTML",
                            "description": "Outer HTML markup.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "getRelayoutBoundary",
                    "description": "Returns the id of the nearest ancestor that is a relayout boundary.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node.",
                            "$ref": "NodeId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "nodeId",
                            "description": "Relayout boundary node id for the given node.",
                            "$ref": "NodeId"
                        }
                    ]
                },
                {
                    "name": "getSearchResults",
                    "description": "Returns search results from given `fromIndex` to given `toIndex` from the search with the given\nidentifier.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "searchId",
                            "description": "Unique search session identifier.",
                            "type": "string"
                        },
                        {
                            "name": "fromIndex",
                            "description": "Start index of the search result to be returned.",
                            "type": "integer"
                        },
                        {
                            "name": "toIndex",
                            "description": "End index of the search result to be returned.",
                            "type": "integer"
                        }
                    ],
                    "returns": [
                        {
                            "name": "nodeIds",
                            "description": "Ids of the search result nodes.",
                            "type": "array",
                            "items": {
                                "$ref": "NodeId"
                            }
                        }
                    ]
                },
                {
                    "name": "hideHighlight",
                    "description": "Hides any highlight.",
                    "redirect": "Overlay"
                },
                {
                    "name": "highlightNode",
                    "description": "Highlights DOM node.",
                    "redirect": "Overlay"
                },
                {
                    "name": "highlightRect",
                    "description": "Highlights given rectangle.",
                    "redirect": "Overlay"
                },
                {
                    "name": "markUndoableState",
                    "description": "Marks last undoable state.",
                    "experimental": true
                },
                {
                    "name": "moveTo",
                    "description": "Moves node into the new container, places it before the given anchor.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node to move.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "targetNodeId",
                            "description": "Id of the element to drop the moved node into.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "insertBeforeNodeId",
                            "description": "Drop node before this one (if absent, the moved node becomes the last child of\n`targetNodeId`).",
                            "optional": true,
                            "$ref": "NodeId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "nodeId",
                            "description": "New id of the moved node.",
                            "$ref": "NodeId"
                        }
                    ]
                },
                {
                    "name": "performSearch",
                    "description": "Searches for a given string in the DOM tree. Use `getSearchResults` to access search results or\n`cancelSearch` to end this search session.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "query",
                            "description": "Plain text or query selector or XPath search query.",
                            "type": "string"
                        },
                        {
                            "name": "includeUserAgentShadowDOM",
                            "description": "True to search in user agent shadow DOM.",
                            "optional": true,
                            "type": "boolean"
                        }
                    ],
                    "returns": [
                        {
                            "name": "searchId",
                            "description": "Unique search session identifier.",
                            "type": "string"
                        },
                        {
                            "name": "resultCount",
                            "description": "Number of search results.",
                            "type": "integer"
                        }
                    ]
                },
                {
                    "name": "pushNodeByPathToFrontend",
                    "description": "Requests that the node is sent to the caller given its path. // FIXME, use XPath",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "path",
                            "description": "Path to node in the proprietary format.",
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node for given path.",
                            "$ref": "NodeId"
                        }
                    ]
                },
                {
                    "name": "pushNodesByBackendIdsToFrontend",
                    "description": "Requests that a batch of nodes is sent to the caller given their backend node ids.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "backendNodeIds",
                            "description": "The array of backend node ids.",
                            "type": "array",
                            "items": {
                                "$ref": "BackendNodeId"
                            }
                        }
                    ],
                    "returns": [
                        {
                            "name": "nodeIds",
                            "description": "The array of ids of pushed nodes that correspond to the backend ids specified in\nbackendNodeIds.",
                            "type": "array",
                            "items": {
                                "$ref": "NodeId"
                            }
                        }
                    ]
                },
                {
                    "name": "querySelector",
                    "description": "Executes `querySelector` on a given node.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node to query upon.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "selector",
                            "description": "Selector string.",
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "nodeId",
                            "description": "Query selector result.",
                            "$ref": "NodeId"
                        }
                    ]
                },
                {
                    "name": "querySelectorAll",
                    "description": "Executes `querySelectorAll` on a given node.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node to query upon.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "selector",
                            "description": "Selector string.",
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "nodeIds",
                            "description": "Query selector result.",
                            "type": "array",
                            "items": {
                                "$ref": "NodeId"
                            }
                        }
                    ]
                },
                {
                    "name": "redo",
                    "description": "Re-does the last undone action.",
                    "experimental": true
                },
                {
                    "name": "removeAttribute",
                    "description": "Removes attribute with given name from an element with given id.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the element to remove attribute from.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "name",
                            "description": "Name of the attribute to remove.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "removeNode",
                    "description": "Removes node with given id.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node to remove.",
                            "$ref": "NodeId"
                        }
                    ]
                },
                {
                    "name": "requestChildNodes",
                    "description": "Requests that children of the node with given id are returned to the caller in form of\n`setChildNodes` events where not only immediate children are retrieved, but all children down to\nthe specified depth.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node to get children for.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "depth",
                            "description": "The maximum depth at which children should be retrieved, defaults to 1. Use -1 for the\nentire subtree or provide an integer larger than 0.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "pierce",
                            "description": "Whether or not iframes and shadow roots should be traversed when returning the sub-tree\n(default is false).",
                            "optional": true,
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "requestNode",
                    "description": "Requests that the node is sent to the caller given the JavaScript node object reference. All\nnodes that form the path from the node to the root are also sent to the client as a series of\n`setChildNodes` notifications.",
                    "parameters": [
                        {
                            "name": "objectId",
                            "description": "JavaScript object id to convert into node.",
                            "$ref": "Runtime.RemoteObjectId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "nodeId",
                            "description": "Node id for given object.",
                            "$ref": "NodeId"
                        }
                    ]
                },
                {
                    "name": "resolveNode",
                    "description": "Resolves the JavaScript node object for a given NodeId or BackendNodeId.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node to resolve.",
                            "optional": true,
                            "$ref": "NodeId"
                        },
                        {
                            "name": "backendNodeId",
                            "description": "Backend identifier of the node to resolve.",
                            "optional": true,
                            "$ref": "DOM.BackendNodeId"
                        },
                        {
                            "name": "objectGroup",
                            "description": "Symbolic group name that can be used to release multiple objects.",
                            "optional": true,
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "object",
                            "description": "JavaScript object wrapper for given node.",
                            "$ref": "Runtime.RemoteObject"
                        }
                    ]
                },
                {
                    "name": "setAttributeValue",
                    "description": "Sets attribute for an element with given id.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the element to set attribute for.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "name",
                            "description": "Attribute name.",
                            "type": "string"
                        },
                        {
                            "name": "value",
                            "description": "Attribute value.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "setAttributesAsText",
                    "description": "Sets attributes on element with given id. This method is useful when user edits some existing\nattribute value and types in several attribute name/value pairs.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the element to set attributes for.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "text",
                            "description": "Text with a number of attributes. Will parse this text using HTML parser.",
                            "type": "string"
                        },
                        {
                            "name": "name",
                            "description": "Attribute name to replace with new attributes derived from text in case text parsed\nsuccessfully.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "setFileInputFiles",
                    "description": "Sets files for the given file input element.",
                    "parameters": [
                        {
                            "name": "files",
                            "description": "Array of file paths to set.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        },
                        {
                            "name": "nodeId",
                            "description": "Identifier of the node.",
                            "optional": true,
                            "$ref": "NodeId"
                        },
                        {
                            "name": "backendNodeId",
                            "description": "Identifier of the backend node.",
                            "optional": true,
                            "$ref": "BackendNodeId"
                        },
                        {
                            "name": "objectId",
                            "description": "JavaScript object id of the node wrapper.",
                            "optional": true,
                            "$ref": "Runtime.RemoteObjectId"
                        }
                    ]
                },
                {
                    "name": "setInspectedNode",
                    "description": "Enables console to refer to the node with given id via $x (see Command Line API for more details\n$x functions).",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "DOM node id to be accessible by means of $x command line API.",
                            "$ref": "NodeId"
                        }
                    ]
                },
                {
                    "name": "setNodeName",
                    "description": "Sets node name for a node with given id.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node to set name for.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "name",
                            "description": "New node's name.",
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "nodeId",
                            "description": "New node's id.",
                            "$ref": "NodeId"
                        }
                    ]
                },
                {
                    "name": "setNodeValue",
                    "description": "Sets node value for a node with given id.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node to set value for.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "value",
                            "description": "New node's value.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "setOuterHTML",
                    "description": "Sets node HTML markup, returns new node id.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node to set markup for.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "outerHTML",
                            "description": "Outer HTML markup to set.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "undo",
                    "description": "Undoes the last performed action.",
                    "experimental": true
                },
                {
                    "name": "getFrameOwner",
                    "description": "Returns iframe node that owns iframe with the given domain.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "frameId",
                            "$ref": "Page.FrameId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "backendNodeId",
                            "description": "Resulting node.",
                            "$ref": "BackendNodeId"
                        },
                        {
                            "name": "nodeId",
                            "description": "Id of the node at given coordinates, only when enabled.",
                            "optional": true,
                            "$ref": "NodeId"
                        }
                    ]
                }
            ],
            "events": [
                {
                    "name": "attributeModified",
                    "description": "Fired when `Element`'s attribute is modified.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node that has changed.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "name",
                            "description": "Attribute name.",
                            "type": "string"
                        },
                        {
                            "name": "value",
                            "description": "Attribute value.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "attributeRemoved",
                    "description": "Fired when `Element`'s attribute is removed.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node that has changed.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "name",
                            "description": "A ttribute name.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "characterDataModified",
                    "description": "Mirrors `DOMCharacterDataModified` event.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node that has changed.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "characterData",
                            "description": "New text value.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "childNodeCountUpdated",
                    "description": "Fired when `Container`'s child node count has changed.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node that has changed.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "childNodeCount",
                            "description": "New node count.",
                            "type": "integer"
                        }
                    ]
                },
                {
                    "name": "childNodeInserted",
                    "description": "Mirrors `DOMNodeInserted` event.",
                    "parameters": [
                        {
                            "name": "parentNodeId",
                            "description": "Id of the node that has changed.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "previousNodeId",
                            "description": "If of the previous siblint.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "node",
                            "description": "Inserted node data.",
                            "$ref": "Node"
                        }
                    ]
                },
                {
                    "name": "childNodeRemoved",
                    "description": "Mirrors `DOMNodeRemoved` event.",
                    "parameters": [
                        {
                            "name": "parentNodeId",
                            "description": "Parent id.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "nodeId",
                            "description": "Id of the node that has been removed.",
                            "$ref": "NodeId"
                        }
                    ]
                },
                {
                    "name": "distributedNodesUpdated",
                    "description": "Called when distrubution is changed.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "insertionPointId",
                            "description": "Insertion point where distrubuted nodes were updated.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "distributedNodes",
                            "description": "Distributed nodes for given insertion point.",
                            "type": "array",
                            "items": {
                                "$ref": "BackendNode"
                            }
                        }
                    ]
                },
                {
                    "name": "documentUpdated",
                    "description": "Fired when `Document` has been totally updated. Node ids are no longer valid."
                },
                {
                    "name": "inlineStyleInvalidated",
                    "description": "Fired when `Element`'s inline style is modified via a CSS property modification.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "nodeIds",
                            "description": "Ids of the nodes for which the inline styles have been invalidated.",
                            "type": "array",
                            "items": {
                                "$ref": "NodeId"
                            }
                        }
                    ]
                },
                {
                    "name": "pseudoElementAdded",
                    "description": "Called when a pseudo element is added to an element.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "parentId",
                            "description": "Pseudo element's parent element id.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "pseudoElement",
                            "description": "The added pseudo element.",
                            "$ref": "Node"
                        }
                    ]
                },
                {
                    "name": "pseudoElementRemoved",
                    "description": "Called when a pseudo element is removed from an element.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "parentId",
                            "description": "Pseudo element's parent element id.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "pseudoElementId",
                            "description": "The removed pseudo element id.",
                            "$ref": "NodeId"
                        }
                    ]
                },
                {
                    "name": "setChildNodes",
                    "description": "Fired when backend wants to provide client with the missing DOM structure. This happens upon\nmost of the calls requesting node ids.",
                    "parameters": [
                        {
                            "name": "parentId",
                            "description": "Parent node id to populate with children.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "nodes",
                            "description": "Child nodes array.",
                            "type": "array",
                            "items": {
                                "$ref": "Node"
                            }
                        }
                    ]
                },
                {
                    "name": "shadowRootPopped",
                    "description": "Called when shadow root is popped from the element.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "hostId",
                            "description": "Host element id.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "rootId",
                            "description": "Shadow root id.",
                            "$ref": "NodeId"
                        }
                    ]
                },
                {
                    "name": "shadowRootPushed",
                    "description": "Called when shadow root is pushed into the element.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "hostId",
                            "description": "Host element id.",
                            "$ref": "NodeId"
                        },
                        {
                            "name": "root",
                            "description": "Shadow root.",
                            "$ref": "Node"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "DOMDebugger",
            "description": "DOM debugging allows setting breakpoints on particular DOM operations and events. JavaScript\nexecution will stop on these operations as if there was a regular breakpoint set.",
            "dependencies": [
                "DOM",
                "Debugger",
                "Runtime"
            ],
            "types": [
                {
                    "id": "DOMBreakpointType",
                    "description": "DOM breakpoint type.",
                    "type": "string",
                    "enum": [
                        "subtree-modified",
                        "attribute-modified",
                        "node-removed"
                    ]
                },
                {
                    "id": "EventListener",
                    "description": "Object event listener.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "type",
                            "description": "`EventListener`'s type.",
                            "type": "string"
                        },
                        {
                            "name": "useCapture",
                            "description": "`EventListener`'s useCapture.",
                            "type": "boolean"
                        },
                        {
                            "name": "passive",
                            "description": "`EventListener`'s passive flag.",
                            "type": "boolean"
                        },
                        {
                            "name": "once",
                            "description": "`EventListener`'s once flag.",
                            "type": "boolean"
                        },
                        {
                            "name": "scriptId",
                            "description": "Script id of the handler code.",
                            "$ref": "Runtime.ScriptId"
                        },
                        {
                            "name": "lineNumber",
                            "description": "Line number in the script (0-based).",
                            "type": "integer"
                        },
                        {
                            "name": "columnNumber",
                            "description": "Column number in the script (0-based).",
                            "type": "integer"
                        },
                        {
                            "name": "handler",
                            "description": "Event handler function value.",
                            "optional": true,
                            "$ref": "Runtime.RemoteObject"
                        },
                        {
                            "name": "originalHandler",
                            "description": "Event original handler function value.",
                            "optional": true,
                            "$ref": "Runtime.RemoteObject"
                        },
                        {
                            "name": "backendNodeId",
                            "description": "Node the listener is added to (if any).",
                            "optional": true,
                            "$ref": "DOM.BackendNodeId"
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "getEventListeners",
                    "description": "Returns event listeners of the given object.",
                    "parameters": [
                        {
                            "name": "objectId",
                            "description": "Identifier of the object to return listeners for.",
                            "$ref": "Runtime.RemoteObjectId"
                        },
                        {
                            "name": "depth",
                            "description": "The maximum depth at which Node children should be retrieved, defaults to 1. Use -1 for the\nentire subtree or provide an integer larger than 0.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "pierce",
                            "description": "Whether or not iframes and shadow roots should be traversed when returning the subtree\n(default is false). Reports listeners for all contexts if pierce is enabled.",
                            "optional": true,
                            "type": "boolean"
                        }
                    ],
                    "returns": [
                        {
                            "name": "listeners",
                            "description": "Array of relevant listeners.",
                            "type": "array",
                            "items": {
                                "$ref": "EventListener"
                            }
                        }
                    ]
                },
                {
                    "name": "removeDOMBreakpoint",
                    "description": "Removes DOM breakpoint that was set using `setDOMBreakpoint`.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Identifier of the node to remove breakpoint from.",
                            "$ref": "DOM.NodeId"
                        },
                        {
                            "name": "type",
                            "description": "Type of the breakpoint to remove.",
                            "$ref": "DOMBreakpointType"
                        }
                    ]
                },
                {
                    "name": "removeEventListenerBreakpoint",
                    "description": "Removes breakpoint on particular DOM event.",
                    "parameters": [
                        {
                            "name": "eventName",
                            "description": "Event name.",
                            "type": "string"
                        },
                        {
                            "name": "targetName",
                            "description": "EventTarget interface name.",
                            "experimental": true,
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "removeInstrumentationBreakpoint",
                    "description": "Removes breakpoint on particular native event.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "eventName",
                            "description": "Instrumentation name to stop on.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "removeXHRBreakpoint",
                    "description": "Removes breakpoint from XMLHttpRequest.",
                    "parameters": [
                        {
                            "name": "url",
                            "description": "Resource URL substring.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "setDOMBreakpoint",
                    "description": "Sets breakpoint on particular operation with DOM.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Identifier of the node to set breakpoint on.",
                            "$ref": "DOM.NodeId"
                        },
                        {
                            "name": "type",
                            "description": "Type of the operation to stop upon.",
                            "$ref": "DOMBreakpointType"
                        }
                    ]
                },
                {
                    "name": "setEventListenerBreakpoint",
                    "description": "Sets breakpoint on particular DOM event.",
                    "parameters": [
                        {
                            "name": "eventName",
                            "description": "DOM Event name to stop on (any DOM event will do).",
                            "type": "string"
                        },
                        {
                            "name": "targetName",
                            "description": "EventTarget interface name to stop on. If equal to `\"*\"` or not provided, will stop on any\nEventTarget.",
                            "experimental": true,
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "setInstrumentationBreakpoint",
                    "description": "Sets breakpoint on particular native event.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "eventName",
                            "description": "Instrumentation name to stop on.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "setXHRBreakpoint",
                    "description": "Sets breakpoint on XMLHttpRequest.",
                    "parameters": [
                        {
                            "name": "url",
                            "description": "Resource URL substring. All XHRs having this substring in the URL will get stopped upon.",
                            "type": "string"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "DOMSnapshot",
            "description": "This domain facilitates obtaining document snapshots with DOM, layout, and style information.",
            "experimental": true,
            "dependencies": [
                "CSS",
                "DOM",
                "DOMDebugger",
                "Page"
            ],
            "types": [
                {
                    "id": "DOMNode",
                    "description": "A Node in the DOM tree.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "nodeType",
                            "description": "`Node`'s nodeType.",
                            "type": "integer"
                        },
                        {
                            "name": "nodeName",
                            "description": "`Node`'s nodeName.",
                            "type": "string"
                        },
                        {
                            "name": "nodeValue",
                            "description": "`Node`'s nodeValue.",
                            "type": "string"
                        },
                        {
                            "name": "textValue",
                            "description": "Only set for textarea elements, contains the text value.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "inputValue",
                            "description": "Only set for input elements, contains the input's associated text value.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "inputChecked",
                            "description": "Only set for radio and checkbox input elements, indicates if the element has been checked",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "optionSelected",
                            "description": "Only set for option elements, indicates if the element has been selected",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "backendNodeId",
                            "description": "`Node`'s id, corresponds to DOM.Node.backendNodeId.",
                            "$ref": "DOM.BackendNodeId"
                        },
                        {
                            "name": "childNodeIndexes",
                            "description": "The indexes of the node's child nodes in the `domNodes` array returned by `getSnapshot`, if\nany.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "type": "integer"
                            }
                        },
                        {
                            "name": "attributes",
                            "description": "Attributes of an `Element` node.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "NameValue"
                            }
                        },
                        {
                            "name": "pseudoElementIndexes",
                            "description": "Indexes of pseudo elements associated with this node in the `domNodes` array returned by\n`getSnapshot`, if any.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "type": "integer"
                            }
                        },
                        {
                            "name": "layoutNodeIndex",
                            "description": "The index of the node's related layout tree node in the `layoutTreeNodes` array returned by\n`getSnapshot`, if any.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "documentURL",
                            "description": "Document URL that `Document` or `FrameOwner` node points to.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "baseURL",
                            "description": "Base URL that `Document` or `FrameOwner` node uses for URL completion.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "contentLanguage",
                            "description": "Only set for documents, contains the document's content language.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "documentEncoding",
                            "description": "Only set for documents, contains the document's character set encoding.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "publicId",
                            "description": "`DocumentType` node's publicId.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "systemId",
                            "description": "`DocumentType` node's systemId.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "frameId",
                            "description": "Frame ID for frame owner elements and also for the document node.",
                            "optional": true,
                            "$ref": "Page.FrameId"
                        },
                        {
                            "name": "contentDocumentIndex",
                            "description": "The index of a frame owner element's content document in the `domNodes` array returned by\n`getSnapshot`, if any.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "pseudoType",
                            "description": "Type of a pseudo element node.",
                            "optional": true,
                            "$ref": "DOM.PseudoType"
                        },
                        {
                            "name": "shadowRootType",
                            "description": "Shadow root type.",
                            "optional": true,
                            "$ref": "DOM.ShadowRootType"
                        },
                        {
                            "name": "isClickable",
                            "description": "Whether this DOM node responds to mouse clicks. This includes nodes that have had click\nevent listeners attached via JavaScript as well as anchor tags that naturally navigate when\nclicked.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "eventListeners",
                            "description": "Details of the node's event listeners, if any.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "DOMDebugger.EventListener"
                            }
                        },
                        {
                            "name": "currentSourceURL",
                            "description": "The selected url for nodes with a srcset attribute.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "originURL",
                            "description": "The url of the script (if any) that generates this node.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "scrollOffsetX",
                            "description": "Scroll offsets, set when this node is a Document.",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "scrollOffsetY",
                            "optional": true,
                            "type": "number"
                        }
                    ]
                },
                {
                    "id": "InlineTextBox",
                    "description": "Details of post layout rendered text positions. The exact layout should not be regarded as\nstable and may change between versions.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "boundingBox",
                            "description": "The bounding box in document coordinates. Note that scroll offset of the document is ignored.",
                            "$ref": "DOM.Rect"
                        },
                        {
                            "name": "startCharacterIndex",
                            "description": "The starting index in characters, for this post layout textbox substring. Characters that\nwould be represented as a surrogate pair in UTF-16 have length 2.",
                            "type": "integer"
                        },
                        {
                            "name": "numCharacters",
                            "description": "The number of characters in this post layout textbox substring. Characters that would be\nrepresented as a surrogate pair in UTF-16 have length 2.",
                            "type": "integer"
                        }
                    ]
                },
                {
                    "id": "LayoutTreeNode",
                    "description": "Details of an element in the DOM tree with a LayoutObject.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "domNodeIndex",
                            "description": "The index of the related DOM node in the `domNodes` array returned by `getSnapshot`.",
                            "type": "integer"
                        },
                        {
                            "name": "boundingBox",
                            "description": "The bounding box in document coordinates. Note that scroll offset of the document is ignored.",
                            "$ref": "DOM.Rect"
                        },
                        {
                            "name": "layoutText",
                            "description": "Contents of the LayoutText, if any.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "inlineTextNodes",
                            "description": "The post-layout inline text nodes, if any.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "InlineTextBox"
                            }
                        },
                        {
                            "name": "styleIndex",
                            "description": "Index into the `computedStyles` array returned by `getSnapshot`.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "paintOrder",
                            "description": "Global paint order index, which is determined by the stacking order of the nodes. Nodes\nthat are painted together will have the same index. Only provided if includePaintOrder in\ngetSnapshot was true.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "isStackingContext",
                            "description": "Set to true to indicate the element begins a new stacking context.",
                            "optional": true,
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "id": "ComputedStyle",
                    "description": "A subset of the full ComputedStyle as defined by the request whitelist.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "properties",
                            "description": "Name/value pairs of computed style properties.",
                            "type": "array",
                            "items": {
                                "$ref": "NameValue"
                            }
                        }
                    ]
                },
                {
                    "id": "NameValue",
                    "description": "A name/value pair.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "name",
                            "description": "Attribute/property name.",
                            "type": "string"
                        },
                        {
                            "name": "value",
                            "description": "Attribute/property value.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "StringIndex",
                    "description": "Index of the string in the strings table.",
                    "type": "integer"
                },
                {
                    "id": "ArrayOfStrings",
                    "description": "Index of the string in the strings table.",
                    "type": "array",
                    "items": {
                        "$ref": "StringIndex"
                    }
                },
                {
                    "id": "RareStringData",
                    "description": "Data that is only present on rare nodes.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "index",
                            "type": "array",
                            "items": {
                                "type": "integer"
                            }
                        },
                        {
                            "name": "value",
                            "type": "array",
                            "items": {
                                "$ref": "StringIndex"
                            }
                        }
                    ]
                },
                {
                    "id": "RareBooleanData",
                    "type": "object",
                    "properties": [
                        {
                            "name": "index",
                            "type": "array",
                            "items": {
                                "type": "integer"
                            }
                        }
                    ]
                },
                {
                    "id": "RareIntegerData",
                    "type": "object",
                    "properties": [
                        {
                            "name": "index",
                            "type": "array",
                            "items": {
                                "type": "integer"
                            }
                        },
                        {
                            "name": "value",
                            "type": "array",
                            "items": {
                                "type": "integer"
                            }
                        }
                    ]
                },
                {
                    "id": "Rectangle",
                    "type": "array",
                    "items": {
                        "type": "number"
                    }
                },
                {
                    "id": "DocumentSnapshot",
                    "description": "Document snapshot.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "documentURL",
                            "description": "Document URL that `Document` or `FrameOwner` node points to.",
                            "$ref": "StringIndex"
                        },
                        {
                            "name": "baseURL",
                            "description": "Base URL that `Document` or `FrameOwner` node uses for URL completion.",
                            "$ref": "StringIndex"
                        },
                        {
                            "name": "contentLanguage",
                            "description": "Contains the document's content language.",
                            "$ref": "StringIndex"
                        },
                        {
                            "name": "encodingName",
                            "description": "Contains the document's character set encoding.",
                            "$ref": "StringIndex"
                        },
                        {
                            "name": "publicId",
                            "description": "`DocumentType` node's publicId.",
                            "$ref": "StringIndex"
                        },
                        {
                            "name": "systemId",
                            "description": "`DocumentType` node's systemId.",
                            "$ref": "StringIndex"
                        },
                        {
                            "name": "frameId",
                            "description": "Frame ID for frame owner elements and also for the document node.",
                            "$ref": "StringIndex"
                        },
                        {
                            "name": "nodes",
                            "description": "A table with dom nodes.",
                            "$ref": "NodeTreeSnapshot"
                        },
                        {
                            "name": "layout",
                            "description": "The nodes in the layout tree.",
                            "$ref": "LayoutTreeSnapshot"
                        },
                        {
                            "name": "textBoxes",
                            "description": "The post-layout inline text nodes.",
                            "$ref": "TextBoxSnapshot"
                        },
                        {
                            "name": "scrollOffsetX",
                            "description": "Scroll offsets.",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "scrollOffsetY",
                            "optional": true,
                            "type": "number"
                        }
                    ]
                },
                {
                    "id": "NodeTreeSnapshot",
                    "description": "Table containing nodes.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "parentIndex",
                            "description": "Parent node index.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "type": "integer"
                            }
                        },
                        {
                            "name": "nodeType",
                            "description": "`Node`'s nodeType.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "type": "integer"
                            }
                        },
                        {
                            "name": "nodeName",
                            "description": "`Node`'s nodeName.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "StringIndex"
                            }
                        },
                        {
                            "name": "nodeValue",
                            "description": "`Node`'s nodeValue.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "StringIndex"
                            }
                        },
                        {
                            "name": "backendNodeId",
                            "description": "`Node`'s id, corresponds to DOM.Node.backendNodeId.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "DOM.BackendNodeId"
                            }
                        },
                        {
                            "name": "attributes",
                            "description": "Attributes of an `Element` node. Flatten name, value pairs.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "ArrayOfStrings"
                            }
                        },
                        {
                            "name": "textValue",
                            "description": "Only set for textarea elements, contains the text value.",
                            "optional": true,
                            "$ref": "RareStringData"
                        },
                        {
                            "name": "inputValue",
                            "description": "Only set for input elements, contains the input's associated text value.",
                            "optional": true,
                            "$ref": "RareStringData"
                        },
                        {
                            "name": "inputChecked",
                            "description": "Only set for radio and checkbox input elements, indicates if the element has been checked",
                            "optional": true,
                            "$ref": "RareBooleanData"
                        },
                        {
                            "name": "optionSelected",
                            "description": "Only set for option elements, indicates if the element has been selected",
                            "optional": true,
                            "$ref": "RareBooleanData"
                        },
                        {
                            "name": "contentDocumentIndex",
                            "description": "The index of the document in the list of the snapshot documents.",
                            "optional": true,
                            "$ref": "RareIntegerData"
                        },
                        {
                            "name": "pseudoType",
                            "description": "Type of a pseudo element node.",
                            "optional": true,
                            "$ref": "RareStringData"
                        },
                        {
                            "name": "isClickable",
                            "description": "Whether this DOM node responds to mouse clicks. This includes nodes that have had click\nevent listeners attached via JavaScript as well as anchor tags that naturally navigate when\nclicked.",
                            "optional": true,
                            "$ref": "RareBooleanData"
                        },
                        {
                            "name": "currentSourceURL",
                            "description": "The selected url for nodes with a srcset attribute.",
                            "optional": true,
                            "$ref": "RareStringData"
                        },
                        {
                            "name": "originURL",
                            "description": "The url of the script (if any) that generates this node.",
                            "optional": true,
                            "$ref": "RareStringData"
                        }
                    ]
                },
                {
                    "id": "LayoutTreeSnapshot",
                    "description": "Details of an element in the DOM tree with a LayoutObject.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "nodeIndex",
                            "description": "The index of the related DOM node in the `domNodes` array returned by `getSnapshot`.",
                            "type": "array",
                            "items": {
                                "type": "integer"
                            }
                        },
                        {
                            "name": "styles",
                            "description": "Index into the `computedStyles` array returned by `captureSnapshot`.",
                            "type": "array",
                            "items": {
                                "$ref": "ArrayOfStrings"
                            }
                        },
                        {
                            "name": "bounds",
                            "description": "The absolute position bounding box.",
                            "type": "array",
                            "items": {
                                "$ref": "Rectangle"
                            }
                        },
                        {
                            "name": "text",
                            "description": "Contents of the LayoutText, if any.",
                            "type": "array",
                            "items": {
                                "$ref": "StringIndex"
                            }
                        },
                        {
                            "name": "stackingContexts",
                            "description": "Stacking context information.",
                            "$ref": "RareBooleanData"
                        }
                    ]
                },
                {
                    "id": "TextBoxSnapshot",
                    "description": "Details of post layout rendered text positions. The exact layout should not be regarded as\nstable and may change between versions.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "layoutIndex",
                            "description": "Intex of th elayout tree node that owns this box collection.",
                            "type": "array",
                            "items": {
                                "type": "integer"
                            }
                        },
                        {
                            "name": "bounds",
                            "description": "The absolute position bounding box.",
                            "type": "array",
                            "items": {
                                "$ref": "Rectangle"
                            }
                        },
                        {
                            "name": "start",
                            "description": "The starting index in characters, for this post layout textbox substring. Characters that\nwould be represented as a surrogate pair in UTF-16 have length 2.",
                            "type": "array",
                            "items": {
                                "type": "integer"
                            }
                        },
                        {
                            "name": "length",
                            "description": "The number of characters in this post layout textbox substring. Characters that would be\nrepresented as a surrogate pair in UTF-16 have length 2.",
                            "type": "array",
                            "items": {
                                "type": "integer"
                            }
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "disable",
                    "description": "Disables DOM snapshot agent for the given page."
                },
                {
                    "name": "enable",
                    "description": "Enables DOM snapshot agent for the given page."
                },
                {
                    "name": "getSnapshot",
                    "description": "Returns a document snapshot, including the full DOM tree of the root node (including iframes,\ntemplate contents, and imported documents) in a flattened array, as well as layout and\nwhite-listed computed style information for the nodes. Shadow DOM in the returned DOM tree is\nflattened.",
                    "deprecated": true,
                    "parameters": [
                        {
                            "name": "computedStyleWhitelist",
                            "description": "Whitelist of computed styles to return.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        },
                        {
                            "name": "includeEventListeners",
                            "description": "Whether or not to retrieve details of DOM listeners (default false).",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "includePaintOrder",
                            "description": "Whether to determine and include the paint order index of LayoutTreeNodes (default false).",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "includeUserAgentShadowTree",
                            "description": "Whether to include UA shadow tree in the snapshot (default false).",
                            "optional": true,
                            "type": "boolean"
                        }
                    ],
                    "returns": [
                        {
                            "name": "domNodes",
                            "description": "The nodes in the DOM tree. The DOMNode at index 0 corresponds to the root document.",
                            "type": "array",
                            "items": {
                                "$ref": "DOMNode"
                            }
                        },
                        {
                            "name": "layoutTreeNodes",
                            "description": "The nodes in the layout tree.",
                            "type": "array",
                            "items": {
                                "$ref": "LayoutTreeNode"
                            }
                        },
                        {
                            "name": "computedStyles",
                            "description": "Whitelisted ComputedStyle properties for each node in the layout tree.",
                            "type": "array",
                            "items": {
                                "$ref": "ComputedStyle"
                            }
                        }
                    ]
                },
                {
                    "name": "captureSnapshot",
                    "description": "Returns a document snapshot, including the full DOM tree of the root node (including iframes,\ntemplate contents, and imported documents) in a flattened array, as well as layout and\nwhite-listed computed style information for the nodes. Shadow DOM in the returned DOM tree is\nflattened.",
                    "parameters": [
                        {
                            "name": "computedStyles",
                            "description": "Whitelist of computed styles to return.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ],
                    "returns": [
                        {
                            "name": "documents",
                            "description": "The nodes in the DOM tree. The DOMNode at index 0 corresponds to the root document.",
                            "type": "array",
                            "items": {
                                "$ref": "DocumentSnapshot"
                            }
                        },
                        {
                            "name": "strings",
                            "description": "Shared string table that all string properties refer to with indexes.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ]
                }
            ]
        },
        {
            "domain": "DOMStorage",
            "description": "Query and modify DOM storage.",
            "experimental": true,
            "types": [
                {
                    "id": "StorageId",
                    "description": "DOM Storage identifier.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "securityOrigin",
                            "description": "Security origin for the storage.",
                            "type": "string"
                        },
                        {
                            "name": "isLocalStorage",
                            "description": "Whether the storage is local storage (not session storage).",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "id": "Item",
                    "description": "DOM Storage item.",
                    "type": "array",
                    "items": {
                        "type": "string"
                    }
                }
            ],
            "commands": [
                {
                    "name": "clear",
                    "parameters": [
                        {
                            "name": "storageId",
                            "$ref": "StorageId"
                        }
                    ]
                },
                {
                    "name": "disable",
                    "description": "Disables storage tracking, prevents storage events from being sent to the client."
                },
                {
                    "name": "enable",
                    "description": "Enables storage tracking, storage events will now be delivered to the client."
                },
                {
                    "name": "getDOMStorageItems",
                    "parameters": [
                        {
                            "name": "storageId",
                            "$ref": "StorageId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "entries",
                            "type": "array",
                            "items": {
                                "$ref": "Item"
                            }
                        }
                    ]
                },
                {
                    "name": "removeDOMStorageItem",
                    "parameters": [
                        {
                            "name": "storageId",
                            "$ref": "StorageId"
                        },
                        {
                            "name": "key",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "setDOMStorageItem",
                    "parameters": [
                        {
                            "name": "storageId",
                            "$ref": "StorageId"
                        },
                        {
                            "name": "key",
                            "type": "string"
                        },
                        {
                            "name": "value",
                            "type": "string"
                        }
                    ]
                }
            ],
            "events": [
                {
                    "name": "domStorageItemAdded",
                    "parameters": [
                        {
                            "name": "storageId",
                            "$ref": "StorageId"
                        },
                        {
                            "name": "key",
                            "type": "string"
                        },
                        {
                            "name": "newValue",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "domStorageItemRemoved",
                    "parameters": [
                        {
                            "name": "storageId",
                            "$ref": "StorageId"
                        },
                        {
                            "name": "key",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "domStorageItemUpdated",
                    "parameters": [
                        {
                            "name": "storageId",
                            "$ref": "StorageId"
                        },
                        {
                            "name": "key",
                            "type": "string"
                        },
                        {
                            "name": "oldValue",
                            "type": "string"
                        },
                        {
                            "name": "newValue",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "domStorageItemsCleared",
                    "parameters": [
                        {
                            "name": "storageId",
                            "$ref": "StorageId"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Database",
            "experimental": true,
            "types": [
                {
                    "id": "DatabaseId",
                    "description": "Unique identifier of Database object.",
                    "type": "string"
                },
                {
                    "id": "Database",
                    "description": "Database object.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "id",
                            "description": "Database ID.",
                            "$ref": "DatabaseId"
                        },
                        {
                            "name": "domain",
                            "description": "Database domain.",
                            "type": "string"
                        },
                        {
                            "name": "name",
                            "description": "Database name.",
                            "type": "string"
                        },
                        {
                            "name": "version",
                            "description": "Database version.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "Error",
                    "description": "Database error.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "message",
                            "description": "Error message.",
                            "type": "string"
                        },
                        {
                            "name": "code",
                            "description": "Error code.",
                            "type": "integer"
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "disable",
                    "description": "Disables database tracking, prevents database events from being sent to the client."
                },
                {
                    "name": "enable",
                    "description": "Enables database tracking, database events will now be delivered to the client."
                },
                {
                    "name": "executeSQL",
                    "parameters": [
                        {
                            "name": "databaseId",
                            "$ref": "DatabaseId"
                        },
                        {
                            "name": "query",
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "columnNames",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        },
                        {
                            "name": "values",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "type": "any"
                            }
                        },
                        {
                            "name": "sqlError",
                            "optional": true,
                            "$ref": "Error"
                        }
                    ]
                },
                {
                    "name": "getDatabaseTableNames",
                    "parameters": [
                        {
                            "name": "databaseId",
                            "$ref": "DatabaseId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "tableNames",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ]
                }
            ],
            "events": [
                {
                    "name": "addDatabase",
                    "parameters": [
                        {
                            "name": "database",
                            "$ref": "Database"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "DeviceOrientation",
            "experimental": true,
            "commands": [
                {
                    "name": "clearDeviceOrientationOverride",
                    "description": "Clears the overridden Device Orientation."
                },
                {
                    "name": "setDeviceOrientationOverride",
                    "description": "Overrides the Device Orientation.",
                    "parameters": [
                        {
                            "name": "alpha",
                            "description": "Mock alpha",
                            "type": "number"
                        },
                        {
                            "name": "beta",
                            "description": "Mock beta",
                            "type": "number"
                        },
                        {
                            "name": "gamma",
                            "description": "Mock gamma",
                            "type": "number"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Emulation",
            "description": "This domain emulates different environments for the page.",
            "dependencies": [
                "DOM",
                "Page",
                "Runtime"
            ],
            "types": [
                {
                    "id": "ScreenOrientation",
                    "description": "Screen orientation.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "type",
                            "description": "Orientation type.",
                            "type": "string",
                            "enum": [
                                "portraitPrimary",
                                "portraitSecondary",
                                "landscapePrimary",
                                "landscapeSecondary"
                            ]
                        },
                        {
                            "name": "angle",
                            "description": "Orientation angle.",
                            "type": "integer"
                        }
                    ]
                },
                {
                    "id": "VirtualTimePolicy",
                    "description": "advance: If the scheduler runs out of immediate work, the virtual time base may fast forward to\nallow the next delayed task (if any) to run; pause: The virtual time base may not advance;\npauseIfNetworkFetchesPending: The virtual time base may not advance if there are any pending\nresource fetches.",
                    "experimental": true,
                    "type": "string",
                    "enum": [
                        "advance",
                        "pause",
                        "pauseIfNetworkFetchesPending"
                    ]
                }
            ],
            "commands": [
                {
                    "name": "canEmulate",
                    "description": "Tells whether emulation is supported.",
                    "returns": [
                        {
                            "name": "result",
                            "description": "True if emulation is supported.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "clearDeviceMetricsOverride",
                    "description": "Clears the overriden device metrics."
                },
                {
                    "name": "clearGeolocationOverride",
                    "description": "Clears the overriden Geolocation Position and Error."
                },
                {
                    "name": "resetPageScaleFactor",
                    "description": "Requests that page scale factor is reset to initial values.",
                    "experimental": true
                },
                {
                    "name": "setFocusEmulationEnabled",
                    "description": "Enables or disables simulating a focused and active page.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "enabled",
                            "description": "Whether to enable to disable focus emulation.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "setCPUThrottlingRate",
                    "description": "Enables CPU throttling to emulate slow CPUs.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "rate",
                            "description": "Throttling rate as a slowdown factor (1 is no throttle, 2 is 2x slowdown, etc).",
                            "type": "number"
                        }
                    ]
                },
                {
                    "name": "setDefaultBackgroundColorOverride",
                    "description": "Sets or clears an override of the default background color of the frame. This override is used\nif the content does not specify one.",
                    "parameters": [
                        {
                            "name": "color",
                            "description": "RGBA of the default background color. If not specified, any existing override will be\ncleared.",
                            "optional": true,
                            "$ref": "DOM.RGBA"
                        }
                    ]
                },
                {
                    "name": "setDeviceMetricsOverride",
                    "description": "Overrides the values of device screen dimensions (window.screen.width, window.screen.height,\nwindow.innerWidth, window.innerHeight, and \"device-width\"/\"device-height\"-related CSS media\nquery results).",
                    "parameters": [
                        {
                            "name": "width",
                            "description": "Overriding width value in pixels (minimum 0, maximum 10000000). 0 disables the override.",
                            "type": "integer"
                        },
                        {
                            "name": "height",
                            "description": "Overriding height value in pixels (minimum 0, maximum 10000000). 0 disables the override.",
                            "type": "integer"
                        },
                        {
                            "name": "deviceScaleFactor",
                            "description": "Overriding device scale factor value. 0 disables the override.",
                            "type": "number"
                        },
                        {
                            "name": "mobile",
                            "description": "Whether to emulate mobile device. This includes viewport meta tag, overlay scrollbars, text\nautosizing and more.",
                            "type": "boolean"
                        },
                        {
                            "name": "scale",
                            "description": "Scale to apply to resulting view image.",
                            "experimental": true,
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "screenWidth",
                            "description": "Overriding screen width value in pixels (minimum 0, maximum 10000000).",
                            "experimental": true,
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "screenHeight",
                            "description": "Overriding screen height value in pixels (minimum 0, maximum 10000000).",
                            "experimental": true,
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "positionX",
                            "description": "Overriding view X position on screen in pixels (minimum 0, maximum 10000000).",
                            "experimental": true,
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "positionY",
                            "description": "Overriding view Y position on screen in pixels (minimum 0, maximum 10000000).",
                            "experimental": true,
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "dontSetVisibleSize",
                            "description": "Do not set visible view size, rely upon explicit setVisibleSize call.",
                            "experimental": true,
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "screenOrientation",
                            "description": "Screen orientation override.",
                            "optional": true,
                            "$ref": "ScreenOrientation"
                        },
                        {
                            "name": "viewport",
                            "description": "If set, the visible area of the page will be overridden to this viewport. This viewport\nchange is not observed by the page, e.g. viewport-relative elements do not change positions.",
                            "experimental": true,
                            "optional": true,
                            "$ref": "Page.Viewport"
                        }
                    ]
                },
                {
                    "name": "setScrollbarsHidden",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "hidden",
                            "description": "Whether scrollbars should be always hidden.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "setDocumentCookieDisabled",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "disabled",
                            "description": "Whether document.coookie API should be disabled.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "setEmitTouchEventsForMouse",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "enabled",
                            "description": "Whether touch emulation based on mouse input should be enabled.",
                            "type": "boolean"
                        },
                        {
                            "name": "configuration",
                            "description": "Touch/gesture events configuration. Default: current platform.",
                            "optional": true,
                            "type": "string",
                            "enum": [
                                "mobile",
                                "desktop"
                            ]
                        }
                    ]
                },
                {
                    "name": "setEmulatedMedia",
                    "description": "Emulates the given media for CSS media queries.",
                    "parameters": [
                        {
                            "name": "media",
                            "description": "Media type to emulate. Empty string disables the override.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "setGeolocationOverride",
                    "description": "Overrides the Geolocation Position or Error. Omitting any of the parameters emulates position\nunavailable.",
                    "parameters": [
                        {
                            "name": "latitude",
                            "description": "Mock latitude",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "longitude",
                            "description": "Mock longitude",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "accuracy",
                            "description": "Mock accuracy",
                            "optional": true,
                            "type": "number"
                        }
                    ]
                },
                {
                    "name": "setNavigatorOverrides",
                    "description": "Overrides value returned by the javascript navigator object.",
                    "experimental": true,
                    "deprecated": true,
                    "parameters": [
                        {
                            "name": "platform",
                            "description": "The platform navigator.platform should return.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "setPageScaleFactor",
                    "description": "Sets a specified page scale factor.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "pageScaleFactor",
                            "description": "Page scale factor.",
                            "type": "number"
                        }
                    ]
                },
                {
                    "name": "setScriptExecutionDisabled",
                    "description": "Switches script execution in the page.",
                    "parameters": [
                        {
                            "name": "value",
                            "description": "Whether script execution should be disabled in the page.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "setTouchEmulationEnabled",
                    "description": "Enables touch on platforms which do not support them.",
                    "parameters": [
                        {
                            "name": "enabled",
                            "description": "Whether the touch event emulation should be enabled.",
                            "type": "boolean"
                        },
                        {
                            "name": "maxTouchPoints",
                            "description": "Maximum touch points supported. Defaults to one.",
                            "optional": true,
                            "type": "integer"
                        }
                    ]
                },
                {
                    "name": "setVirtualTimePolicy",
                    "description": "Turns on virtual time for all frames (replacing real-time with a synthetic time source) and sets\nthe current virtual time policy.  Note this supersedes any previous time budget.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "policy",
                            "$ref": "VirtualTimePolicy"
                        },
                        {
                            "name": "budget",
                            "description": "If set, after this many virtual milliseconds have elapsed virtual time will be paused and a\nvirtualTimeBudgetExpired event is sent.",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "maxVirtualTimeTaskStarvationCount",
                            "description": "If set this specifies the maximum number of tasks that can be run before virtual is forced\nforwards to prevent deadlock.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "waitForNavigation",
                            "description": "If set the virtual time policy change should be deferred until any frame starts navigating.\nNote any previous deferred policy change is superseded.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "initialVirtualTime",
                            "description": "If set, base::Time::Now will be overriden to initially return this value.",
                            "optional": true,
                            "$ref": "Network.TimeSinceEpoch"
                        }
                    ],
                    "returns": [
                        {
                            "name": "virtualTimeTicksBase",
                            "description": "Absolute timestamp at which virtual time was first enabled (up time in milliseconds).",
                            "type": "number"
                        }
                    ]
                },
                {
                    "name": "setVisibleSize",
                    "description": "Resizes the frame/viewport of the page. Note that this does not affect the frame's container\n(e.g. browser window). Can be used to produce screenshots of the specified size. Not supported\non Android.",
                    "experimental": true,
                    "deprecated": true,
                    "parameters": [
                        {
                            "name": "width",
                            "description": "Frame width (DIP).",
                            "type": "integer"
                        },
                        {
                            "name": "height",
                            "description": "Frame height (DIP).",
                            "type": "integer"
                        }
                    ]
                },
                {
                    "name": "setUserAgentOverride",
                    "description": "Allows overriding user agent with the given string.",
                    "parameters": [
                        {
                            "name": "userAgent",
                            "description": "User agent to use.",
                            "type": "string"
                        },
                        {
                            "name": "acceptLanguage",
                            "description": "Browser langugage to emulate.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "platform",
                            "description": "The platform navigator.platform should return.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                }
            ],
            "events": [
                {
                    "name": "virtualTimeAdvanced",
                    "description": "Notification sent after the virtual time has advanced.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "virtualTimeElapsed",
                            "description": "The amount of virtual time that has elapsed in milliseconds since virtual time was first\nenabled.",
                            "type": "number"
                        }
                    ]
                },
                {
                    "name": "virtualTimeBudgetExpired",
                    "description": "Notification sent after the virtual time budget for the current VirtualTimePolicy has run out.",
                    "experimental": true
                },
                {
                    "name": "virtualTimePaused",
                    "description": "Notification sent after the virtual time has paused.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "virtualTimeElapsed",
                            "description": "The amount of virtual time that has elapsed in milliseconds since virtual time was first\nenabled.",
                            "type": "number"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "HeadlessExperimental",
            "description": "This domain provides experimental commands only supported in headless mode.",
            "experimental": true,
            "dependencies": [
                "Page",
                "Runtime"
            ],
            "types": [
                {
                    "id": "ScreenshotParams",
                    "description": "Encoding options for a screenshot.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "format",
                            "description": "Image compression format (defaults to png).",
                            "optional": true,
                            "type": "string",
                            "enum": [
                                "jpeg",
                                "png"
                            ]
                        },
                        {
                            "name": "quality",
                            "description": "Compression quality from range [0..100] (jpeg only).",
                            "optional": true,
                            "type": "integer"
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "beginFrame",
                    "description": "Sends a BeginFrame to the target and returns when the frame was completed. Optionally captures a\nscreenshot from the resulting frame. Requires that the target was created with enabled\nBeginFrameControl. Designed for use with --run-all-compositor-stages-before-draw, see also\nhttps://goo.gl/3zHXhB for more background.",
                    "parameters": [
                        {
                            "name": "frameTimeTicks",
                            "description": "Timestamp of this BeginFrame in Renderer TimeTicks (milliseconds of uptime). If not set,\nthe current time will be used.",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "interval",
                            "description": "The interval between BeginFrames that is reported to the compositor, in milliseconds.\nDefaults to a 60 frames/second interval, i.e. about 16.666 milliseconds.",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "noDisplayUpdates",
                            "description": "Whether updates should not be committed and drawn onto the display. False by default. If\ntrue, only side effects of the BeginFrame will be run, such as layout and animations, but\nany visual updates may not be visible on the display or in screenshots.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "screenshot",
                            "description": "If set, a screenshot of the frame will be captured and returned in the response. Otherwise,\nno screenshot will be captured. Note that capturing a screenshot can fail, for example,\nduring renderer initialization. In such a case, no screenshot data will be returned.",
                            "optional": true,
                            "$ref": "ScreenshotParams"
                        }
                    ],
                    "returns": [
                        {
                            "name": "hasDamage",
                            "description": "Whether the BeginFrame resulted in damage and, thus, a new frame was committed to the\ndisplay. Reported for diagnostic uses, may be removed in the future.",
                            "type": "boolean"
                        },
                        {
                            "name": "screenshotData",
                            "description": "Base64-encoded image data of the screenshot, if one was requested and successfully taken.",
                            "optional": true,
                            "type": "binary"
                        }
                    ]
                },
                {
                    "name": "disable",
                    "description": "Disables headless events for the target."
                },
                {
                    "name": "enable",
                    "description": "Enables headless events for the target."
                }
            ],
            "events": [
                {
                    "name": "needsBeginFramesChanged",
                    "description": "Issued when the target starts or stops needing BeginFrames.",
                    "parameters": [
                        {
                            "name": "needsBeginFrames",
                            "description": "True if BeginFrames are needed, false otherwise.",
                            "type": "boolean"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "IO",
            "description": "Input/Output operations for streams produced by DevTools.",
            "types": [
                {
                    "id": "StreamHandle",
                    "description": "This is either obtained from another method or specifed as `blob:&lt;uuid&gt;` where\n`&lt;uuid&gt` is an UUID of a Blob.",
                    "type": "string"
                }
            ],
            "commands": [
                {
                    "name": "close",
                    "description": "Close the stream, discard any temporary backing storage.",
                    "parameters": [
                        {
                            "name": "handle",
                            "description": "Handle of the stream to close.",
                            "$ref": "StreamHandle"
                        }
                    ]
                },
                {
                    "name": "read",
                    "description": "Read a chunk of the stream",
                    "parameters": [
                        {
                            "name": "handle",
                            "description": "Handle of the stream to read.",
                            "$ref": "StreamHandle"
                        },
                        {
                            "name": "offset",
                            "description": "Seek to the specified offset before reading (if not specificed, proceed with offset\nfollowing the last read). Some types of streams may only support sequential reads.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "size",
                            "description": "Maximum number of bytes to read (left upon the agent discretion if not specified).",
                            "optional": true,
                            "type": "integer"
                        }
                    ],
                    "returns": [
                        {
                            "name": "base64Encoded",
                            "description": "Set if the data is base64-encoded",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "data",
                            "description": "Data that were read.",
                            "type": "string"
                        },
                        {
                            "name": "eof",
                            "description": "Set if the end-of-file condition occured while reading.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "resolveBlob",
                    "description": "Return UUID of Blob object specified by a remote object id.",
                    "parameters": [
                        {
                            "name": "objectId",
                            "description": "Object id of a Blob object wrapper.",
                            "$ref": "Runtime.RemoteObjectId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "uuid",
                            "description": "UUID of the specified Blob.",
                            "type": "string"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "IndexedDB",
            "experimental": true,
            "dependencies": [
                "Runtime"
            ],
            "types": [
                {
                    "id": "DatabaseWithObjectStores",
                    "description": "Database with an array of object stores.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "name",
                            "description": "Database name.",
                            "type": "string"
                        },
                        {
                            "name": "version",
                            "description": "Database version.",
                            "type": "integer"
                        },
                        {
                            "name": "objectStores",
                            "description": "Object stores in this database.",
                            "type": "array",
                            "items": {
                                "$ref": "ObjectStore"
                            }
                        }
                    ]
                },
                {
                    "id": "ObjectStore",
                    "description": "Object store.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "name",
                            "description": "Object store name.",
                            "type": "string"
                        },
                        {
                            "name": "keyPath",
                            "description": "Object store key path.",
                            "$ref": "KeyPath"
                        },
                        {
                            "name": "autoIncrement",
                            "description": "If true, object store has auto increment flag set.",
                            "type": "boolean"
                        },
                        {
                            "name": "indexes",
                            "description": "Indexes in this object store.",
                            "type": "array",
                            "items": {
                                "$ref": "ObjectStoreIndex"
                            }
                        }
                    ]
                },
                {
                    "id": "ObjectStoreIndex",
                    "description": "Object store index.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "name",
                            "description": "Index name.",
                            "type": "string"
                        },
                        {
                            "name": "keyPath",
                            "description": "Index key path.",
                            "$ref": "KeyPath"
                        },
                        {
                            "name": "unique",
                            "description": "If true, index is unique.",
                            "type": "boolean"
                        },
                        {
                            "name": "multiEntry",
                            "description": "If true, index allows multiple entries for a key.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "id": "Key",
                    "description": "Key.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "type",
                            "description": "Key type.",
                            "type": "string",
                            "enum": [
                                "number",
                                "string",
                                "date",
                                "array"
                            ]
                        },
                        {
                            "name": "number",
                            "description": "Number value.",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "string",
                            "description": "String value.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "date",
                            "description": "Date value.",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "array",
                            "description": "Array value.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "Key"
                            }
                        }
                    ]
                },
                {
                    "id": "KeyRange",
                    "description": "Key range.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "lower",
                            "description": "Lower bound.",
                            "optional": true,
                            "$ref": "Key"
                        },
                        {
                            "name": "upper",
                            "description": "Upper bound.",
                            "optional": true,
                            "$ref": "Key"
                        },
                        {
                            "name": "lowerOpen",
                            "description": "If true lower bound is open.",
                            "type": "boolean"
                        },
                        {
                            "name": "upperOpen",
                            "description": "If true upper bound is open.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "id": "DataEntry",
                    "description": "Data entry.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "key",
                            "description": "Key object.",
                            "$ref": "Runtime.RemoteObject"
                        },
                        {
                            "name": "primaryKey",
                            "description": "Primary key object.",
                            "$ref": "Runtime.RemoteObject"
                        },
                        {
                            "name": "value",
                            "description": "Value object.",
                            "$ref": "Runtime.RemoteObject"
                        }
                    ]
                },
                {
                    "id": "KeyPath",
                    "description": "Key path.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "type",
                            "description": "Key path type.",
                            "type": "string",
                            "enum": [
                                "null",
                                "string",
                                "array"
                            ]
                        },
                        {
                            "name": "string",
                            "description": "String value.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "array",
                            "description": "Array value.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "clearObjectStore",
                    "description": "Clears all entries from an object store.",
                    "parameters": [
                        {
                            "name": "securityOrigin",
                            "description": "Security origin.",
                            "type": "string"
                        },
                        {
                            "name": "databaseName",
                            "description": "Database name.",
                            "type": "string"
                        },
                        {
                            "name": "objectStoreName",
                            "description": "Object store name.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "deleteDatabase",
                    "description": "Deletes a database.",
                    "parameters": [
                        {
                            "name": "securityOrigin",
                            "description": "Security origin.",
                            "type": "string"
                        },
                        {
                            "name": "databaseName",
                            "description": "Database name.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "deleteObjectStoreEntries",
                    "description": "Delete a range of entries from an object store",
                    "parameters": [
                        {
                            "name": "securityOrigin",
                            "type": "string"
                        },
                        {
                            "name": "databaseName",
                            "type": "string"
                        },
                        {
                            "name": "objectStoreName",
                            "type": "string"
                        },
                        {
                            "name": "keyRange",
                            "description": "Range of entry keys to delete",
                            "$ref": "KeyRange"
                        }
                    ]
                },
                {
                    "name": "disable",
                    "description": "Disables events from backend."
                },
                {
                    "name": "enable",
                    "description": "Enables events from backend."
                },
                {
                    "name": "requestData",
                    "description": "Requests data from object store or index.",
                    "parameters": [
                        {
                            "name": "securityOrigin",
                            "description": "Security origin.",
                            "type": "string"
                        },
                        {
                            "name": "databaseName",
                            "description": "Database name.",
                            "type": "string"
                        },
                        {
                            "name": "objectStoreName",
                            "description": "Object store name.",
                            "type": "string"
                        },
                        {
                            "name": "indexName",
                            "description": "Index name, empty string for object store data requests.",
                            "type": "string"
                        },
                        {
                            "name": "skipCount",
                            "description": "Number of records to skip.",
                            "type": "integer"
                        },
                        {
                            "name": "pageSize",
                            "description": "Number of records to fetch.",
                            "type": "integer"
                        },
                        {
                            "name": "keyRange",
                            "description": "Key range.",
                            "optional": true,
                            "$ref": "KeyRange"
                        }
                    ],
                    "returns": [
                        {
                            "name": "objectStoreDataEntries",
                            "description": "Array of object store data entries.",
                            "type": "array",
                            "items": {
                                "$ref": "DataEntry"
                            }
                        },
                        {
                            "name": "hasMore",
                            "description": "If true, there are more entries to fetch in the given range.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "requestDatabase",
                    "description": "Requests database with given name in given frame.",
                    "parameters": [
                        {
                            "name": "securityOrigin",
                            "description": "Security origin.",
                            "type": "string"
                        },
                        {
                            "name": "databaseName",
                            "description": "Database name.",
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "databaseWithObjectStores",
                            "description": "Database with an array of object stores.",
                            "$ref": "DatabaseWithObjectStores"
                        }
                    ]
                },
                {
                    "name": "requestDatabaseNames",
                    "description": "Requests database names for given security origin.",
                    "parameters": [
                        {
                            "name": "securityOrigin",
                            "description": "Security origin.",
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "databaseNames",
                            "description": "Database names for origin.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Input",
            "types": [
                {
                    "id": "TouchPoint",
                    "type": "object",
                    "properties": [
                        {
                            "name": "x",
                            "description": "X coordinate of the event relative to the main frame's viewport in CSS pixels.",
                            "type": "number"
                        },
                        {
                            "name": "y",
                            "description": "Y coordinate of the event relative to the main frame's viewport in CSS pixels. 0 refers to\nthe top of the viewport and Y increases as it proceeds towards the bottom of the viewport.",
                            "type": "number"
                        },
                        {
                            "name": "radiusX",
                            "description": "X radius of the touch area (default: 1.0).",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "radiusY",
                            "description": "Y radius of the touch area (default: 1.0).",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "rotationAngle",
                            "description": "Rotation angle (default: 0.0).",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "force",
                            "description": "Force (default: 1.0).",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "id",
                            "description": "Identifier used to track touch sources between events, must be unique within an event.",
                            "optional": true,
                            "type": "number"
                        }
                    ]
                },
                {
                    "id": "GestureSourceType",
                    "experimental": true,
                    "type": "string",
                    "enum": [
                        "default",
                        "touch",
                        "mouse"
                    ]
                },
                {
                    "id": "TimeSinceEpoch",
                    "description": "UTC time in seconds, counted from January 1, 1970.",
                    "type": "number"
                }
            ],
            "commands": [
                {
                    "name": "dispatchKeyEvent",
                    "description": "Dispatches a key event to the page.",
                    "parameters": [
                        {
                            "name": "type",
                            "description": "Type of the key event.",
                            "type": "string",
                            "enum": [
                                "keyDown",
                                "keyUp",
                                "rawKeyDown",
                                "char"
                            ]
                        },
                        {
                            "name": "modifiers",
                            "description": "Bit field representing pressed modifier keys. Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "timestamp",
                            "description": "Time at which the event occurred.",
                            "optional": true,
                            "$ref": "TimeSinceEpoch"
                        },
                        {
                            "name": "text",
                            "description": "Text as generated by processing a virtual key code with a keyboard layout. Not needed for\nfor `keyUp` and `rawKeyDown` events (default: \"\")",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "unmodifiedText",
                            "description": "Text that would have been generated by the keyboard if no modifiers were pressed (except for\nshift). Useful for shortcut (accelerator) key handling (default: \"\").",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "keyIdentifier",
                            "description": "Unique key identifier (e.g., 'U+0041') (default: \"\").",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "code",
                            "description": "Unique DOM defined string value for each physical key (e.g., 'KeyA') (default: \"\").",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "key",
                            "description": "Unique DOM defined string value describing the meaning of the key in the context of active\nmodifiers, keyboard layout, etc (e.g., 'AltGr') (default: \"\").",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "windowsVirtualKeyCode",
                            "description": "Windows virtual key code (default: 0).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "nativeVirtualKeyCode",
                            "description": "Native virtual key code (default: 0).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "autoRepeat",
                            "description": "Whether the event was generated from auto repeat (default: false).",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "isKeypad",
                            "description": "Whether the event was generated from the keypad (default: false).",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "isSystemKey",
                            "description": "Whether the event was a system key event (default: false).",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "location",
                            "description": "Whether the event was from the left or right side of the keyboard. 1=Left, 2=Right (default:\n0).",
                            "optional": true,
                            "type": "integer"
                        }
                    ]
                },
                {
                    "name": "insertText",
                    "description": "This method emulates inserting text that doesn't come from a key press,\nfor example an emoji keyboard or an IME.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "text",
                            "description": "The text to insert.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "dispatchMouseEvent",
                    "description": "Dispatches a mouse event to the page.",
                    "parameters": [
                        {
                            "name": "type",
                            "description": "Type of the mouse event.",
                            "type": "string",
                            "enum": [
                                "mousePressed",
                                "mouseReleased",
                                "mouseMoved",
                                "mouseWheel"
                            ]
                        },
                        {
                            "name": "x",
                            "description": "X coordinate of the event relative to the main frame's viewport in CSS pixels.",
                            "type": "number"
                        },
                        {
                            "name": "y",
                            "description": "Y coordinate of the event relative to the main frame's viewport in CSS pixels. 0 refers to\nthe top of the viewport and Y increases as it proceeds towards the bottom of the viewport.",
                            "type": "number"
                        },
                        {
                            "name": "modifiers",
                            "description": "Bit field representing pressed modifier keys. Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "timestamp",
                            "description": "Time at which the event occurred.",
                            "optional": true,
                            "$ref": "TimeSinceEpoch"
                        },
                        {
                            "name": "button",
                            "description": "Mouse button (default: \"none\").",
                            "optional": true,
                            "type": "string",
                            "enum": [
                                "none",
                                "left",
                                "middle",
                                "right"
                            ]
                        },
                        {
                            "name": "clickCount",
                            "description": "Number of times the mouse button was clicked (default: 0).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "deltaX",
                            "description": "X delta in CSS pixels for mouse wheel event (default: 0).",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "deltaY",
                            "description": "Y delta in CSS pixels for mouse wheel event (default: 0).",
                            "optional": true,
                            "type": "number"
                        }
                    ]
                },
                {
                    "name": "dispatchTouchEvent",
                    "description": "Dispatches a touch event to the page.",
                    "parameters": [
                        {
                            "name": "type",
                            "description": "Type of the touch event. TouchEnd and TouchCancel must not contain any touch points, while\nTouchStart and TouchMove must contains at least one.",
                            "type": "string",
                            "enum": [
                                "touchStart",
                                "touchEnd",
                                "touchMove",
                                "touchCancel"
                            ]
                        },
                        {
                            "name": "touchPoints",
                            "description": "Active touch points on the touch device. One event per any changed point (compared to\nprevious touch event in a sequence) is generated, emulating pressing/moving/releasing points\none by one.",
                            "type": "array",
                            "items": {
                                "$ref": "TouchPoint"
                            }
                        },
                        {
                            "name": "modifiers",
                            "description": "Bit field representing pressed modifier keys. Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "timestamp",
                            "description": "Time at which the event occurred.",
                            "optional": true,
                            "$ref": "TimeSinceEpoch"
                        }
                    ]
                },
                {
                    "name": "emulateTouchFromMouseEvent",
                    "description": "Emulates touch event from the mouse event parameters.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "type",
                            "description": "Type of the mouse event.",
                            "type": "string",
                            "enum": [
                                "mousePressed",
                                "mouseReleased",
                                "mouseMoved",
                                "mouseWheel"
                            ]
                        },
                        {
                            "name": "x",
                            "description": "X coordinate of the mouse pointer in DIP.",
                            "type": "integer"
                        },
                        {
                            "name": "y",
                            "description": "Y coordinate of the mouse pointer in DIP.",
                            "type": "integer"
                        },
                        {
                            "name": "button",
                            "description": "Mouse button.",
                            "type": "string",
                            "enum": [
                                "none",
                                "left",
                                "middle",
                                "right"
                            ]
                        },
                        {
                            "name": "timestamp",
                            "description": "Time at which the event occurred (default: current time).",
                            "optional": true,
                            "$ref": "TimeSinceEpoch"
                        },
                        {
                            "name": "deltaX",
                            "description": "X delta in DIP for mouse wheel event (default: 0).",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "deltaY",
                            "description": "Y delta in DIP for mouse wheel event (default: 0).",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "modifiers",
                            "description": "Bit field representing pressed modifier keys. Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "clickCount",
                            "description": "Number of times the mouse button was clicked (default: 0).",
                            "optional": true,
                            "type": "integer"
                        }
                    ]
                },
                {
                    "name": "setIgnoreInputEvents",
                    "description": "Ignores input events (useful while auditing page).",
                    "parameters": [
                        {
                            "name": "ignore",
                            "description": "Ignores input events processing when set to true.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "synthesizePinchGesture",
                    "description": "Synthesizes a pinch gesture over a time period by issuing appropriate touch events.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "x",
                            "description": "X coordinate of the start of the gesture in CSS pixels.",
                            "type": "number"
                        },
                        {
                            "name": "y",
                            "description": "Y coordinate of the start of the gesture in CSS pixels.",
                            "type": "number"
                        },
                        {
                            "name": "scaleFactor",
                            "description": "Relative scale factor after zooming (>1.0 zooms in, <1.0 zooms out).",
                            "type": "number"
                        },
                        {
                            "name": "relativeSpeed",
                            "description": "Relative pointer speed in pixels per second (default: 800).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "gestureSourceType",
                            "description": "Which type of input events to be generated (default: 'default', which queries the platform\nfor the preferred input type).",
                            "optional": true,
                            "$ref": "GestureSourceType"
                        }
                    ]
                },
                {
                    "name": "synthesizeScrollGesture",
                    "description": "Synthesizes a scroll gesture over a time period by issuing appropriate touch events.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "x",
                            "description": "X coordinate of the start of the gesture in CSS pixels.",
                            "type": "number"
                        },
                        {
                            "name": "y",
                            "description": "Y coordinate of the start of the gesture in CSS pixels.",
                            "type": "number"
                        },
                        {
                            "name": "xDistance",
                            "description": "The distance to scroll along the X axis (positive to scroll left).",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "yDistance",
                            "description": "The distance to scroll along the Y axis (positive to scroll up).",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "xOverscroll",
                            "description": "The number of additional pixels to scroll back along the X axis, in addition to the given\ndistance.",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "yOverscroll",
                            "description": "The number of additional pixels to scroll back along the Y axis, in addition to the given\ndistance.",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "preventFling",
                            "description": "Prevent fling (default: true).",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "speed",
                            "description": "Swipe speed in pixels per second (default: 800).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "gestureSourceType",
                            "description": "Which type of input events to be generated (default: 'default', which queries the platform\nfor the preferred input type).",
                            "optional": true,
                            "$ref": "GestureSourceType"
                        },
                        {
                            "name": "repeatCount",
                            "description": "The number of times to repeat the gesture (default: 0).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "repeatDelayMs",
                            "description": "The number of milliseconds delay between each repeat. (default: 250).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "interactionMarkerName",
                            "description": "The name of the interaction markers to generate, if not empty (default: \"\").",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "synthesizeTapGesture",
                    "description": "Synthesizes a tap gesture over a time period by issuing appropriate touch events.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "x",
                            "description": "X coordinate of the start of the gesture in CSS pixels.",
                            "type": "number"
                        },
                        {
                            "name": "y",
                            "description": "Y coordinate of the start of the gesture in CSS pixels.",
                            "type": "number"
                        },
                        {
                            "name": "duration",
                            "description": "Duration between touchdown and touchup events in ms (default: 50).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "tapCount",
                            "description": "Number of times to perform the tap (e.g. 2 for double tap, default: 1).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "gestureSourceType",
                            "description": "Which type of input events to be generated (default: 'default', which queries the platform\nfor the preferred input type).",
                            "optional": true,
                            "$ref": "GestureSourceType"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Inspector",
            "experimental": true,
            "commands": [
                {
                    "name": "disable",
                    "description": "Disables inspector domain notifications."
                },
                {
                    "name": "enable",
                    "description": "Enables inspector domain notifications."
                }
            ],
            "events": [
                {
                    "name": "detached",
                    "description": "Fired when remote debugging connection is about to be terminated. Contains detach reason.",
                    "parameters": [
                        {
                            "name": "reason",
                            "description": "The reason why connection has been terminated.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "targetCrashed",
                    "description": "Fired when debugging target has crashed"
                },
                {
                    "name": "targetReloadedAfterCrash",
                    "description": "Fired when debugging target has reloaded after crash"
                }
            ]
        },
        {
            "domain": "LayerTree",
            "experimental": true,
            "dependencies": [
                "DOM"
            ],
            "types": [
                {
                    "id": "LayerId",
                    "description": "Unique Layer identifier.",
                    "type": "string"
                },
                {
                    "id": "SnapshotId",
                    "description": "Unique snapshot identifier.",
                    "type": "string"
                },
                {
                    "id": "ScrollRect",
                    "description": "Rectangle where scrolling happens on the main thread.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "rect",
                            "description": "Rectangle itself.",
                            "$ref": "DOM.Rect"
                        },
                        {
                            "name": "type",
                            "description": "Reason for rectangle to force scrolling on the main thread",
                            "type": "string",
                            "enum": [
                                "RepaintsOnScroll",
                                "TouchEventHandler",
                                "WheelEventHandler"
                            ]
                        }
                    ]
                },
                {
                    "id": "StickyPositionConstraint",
                    "description": "Sticky position constraints.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "stickyBoxRect",
                            "description": "Layout rectangle of the sticky element before being shifted",
                            "$ref": "DOM.Rect"
                        },
                        {
                            "name": "containingBlockRect",
                            "description": "Layout rectangle of the containing block of the sticky element",
                            "$ref": "DOM.Rect"
                        },
                        {
                            "name": "nearestLayerShiftingStickyBox",
                            "description": "The nearest sticky layer that shifts the sticky box",
                            "optional": true,
                            "$ref": "LayerId"
                        },
                        {
                            "name": "nearestLayerShiftingContainingBlock",
                            "description": "The nearest sticky layer that shifts the containing block",
                            "optional": true,
                            "$ref": "LayerId"
                        }
                    ]
                },
                {
                    "id": "PictureTile",
                    "description": "Serialized fragment of layer picture along with its offset within the layer.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "x",
                            "description": "Offset from owning layer left boundary",
                            "type": "number"
                        },
                        {
                            "name": "y",
                            "description": "Offset from owning layer top boundary",
                            "type": "number"
                        },
                        {
                            "name": "picture",
                            "description": "Base64-encoded snapshot data.",
                            "type": "binary"
                        }
                    ]
                },
                {
                    "id": "Layer",
                    "description": "Information about a compositing layer.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "layerId",
                            "description": "The unique id for this layer.",
                            "$ref": "LayerId"
                        },
                        {
                            "name": "parentLayerId",
                            "description": "The id of parent (not present for root).",
                            "optional": true,
                            "$ref": "LayerId"
                        },
                        {
                            "name": "backendNodeId",
                            "description": "The backend id for the node associated with this layer.",
                            "optional": true,
                            "$ref": "DOM.BackendNodeId"
                        },
                        {
                            "name": "offsetX",
                            "description": "Offset from parent layer, X coordinate.",
                            "type": "number"
                        },
                        {
                            "name": "offsetY",
                            "description": "Offset from parent layer, Y coordinate.",
                            "type": "number"
                        },
                        {
                            "name": "width",
                            "description": "Layer width.",
                            "type": "number"
                        },
                        {
                            "name": "height",
                            "description": "Layer height.",
                            "type": "number"
                        },
                        {
                            "name": "transform",
                            "description": "Transformation matrix for layer, default is identity matrix",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "type": "number"
                            }
                        },
                        {
                            "name": "anchorX",
                            "description": "Transform anchor point X, absent if no transform specified",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "anchorY",
                            "description": "Transform anchor point Y, absent if no transform specified",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "anchorZ",
                            "description": "Transform anchor point Z, absent if no transform specified",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "paintCount",
                            "description": "Indicates how many time this layer has painted.",
                            "type": "integer"
                        },
                        {
                            "name": "drawsContent",
                            "description": "Indicates whether this layer hosts any content, rather than being used for\ntransform/scrolling purposes only.",
                            "type": "boolean"
                        },
                        {
                            "name": "invisible",
                            "description": "Set if layer is not visible.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "scrollRects",
                            "description": "Rectangles scrolling on main thread only.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "ScrollRect"
                            }
                        },
                        {
                            "name": "stickyPositionConstraint",
                            "description": "Sticky position constraint information",
                            "optional": true,
                            "$ref": "StickyPositionConstraint"
                        }
                    ]
                },
                {
                    "id": "PaintProfile",
                    "description": "Array of timings, one per paint step.",
                    "type": "array",
                    "items": {
                        "type": "number"
                    }
                }
            ],
            "commands": [
                {
                    "name": "compositingReasons",
                    "description": "Provides the reasons why the given layer was composited.",
                    "parameters": [
                        {
                            "name": "layerId",
                            "description": "The id of the layer for which we want to get the reasons it was composited.",
                            "$ref": "LayerId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "compositingReasons",
                            "description": "A list of strings specifying reasons for the given layer to become composited.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ]
                },
                {
                    "name": "disable",
                    "description": "Disables compositing tree inspection."
                },
                {
                    "name": "enable",
                    "description": "Enables compositing tree inspection."
                },
                {
                    "name": "loadSnapshot",
                    "description": "Returns the snapshot identifier.",
                    "parameters": [
                        {
                            "name": "tiles",
                            "description": "An array of tiles composing the snapshot.",
                            "type": "array",
                            "items": {
                                "$ref": "PictureTile"
                            }
                        }
                    ],
                    "returns": [
                        {
                            "name": "snapshotId",
                            "description": "The id of the snapshot.",
                            "$ref": "SnapshotId"
                        }
                    ]
                },
                {
                    "name": "makeSnapshot",
                    "description": "Returns the layer snapshot identifier.",
                    "parameters": [
                        {
                            "name": "layerId",
                            "description": "The id of the layer.",
                            "$ref": "LayerId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "snapshotId",
                            "description": "The id of the layer snapshot.",
                            "$ref": "SnapshotId"
                        }
                    ]
                },
                {
                    "name": "profileSnapshot",
                    "parameters": [
                        {
                            "name": "snapshotId",
                            "description": "The id of the layer snapshot.",
                            "$ref": "SnapshotId"
                        },
                        {
                            "name": "minRepeatCount",
                            "description": "The maximum number of times to replay the snapshot (1, if not specified).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "minDuration",
                            "description": "The minimum duration (in seconds) to replay the snapshot.",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "clipRect",
                            "description": "The clip rectangle to apply when replaying the snapshot.",
                            "optional": true,
                            "$ref": "DOM.Rect"
                        }
                    ],
                    "returns": [
                        {
                            "name": "timings",
                            "description": "The array of paint profiles, one per run.",
                            "type": "array",
                            "items": {
                                "$ref": "PaintProfile"
                            }
                        }
                    ]
                },
                {
                    "name": "releaseSnapshot",
                    "description": "Releases layer snapshot captured by the back-end.",
                    "parameters": [
                        {
                            "name": "snapshotId",
                            "description": "The id of the layer snapshot.",
                            "$ref": "SnapshotId"
                        }
                    ]
                },
                {
                    "name": "replaySnapshot",
                    "description": "Replays the layer snapshot and returns the resulting bitmap.",
                    "parameters": [
                        {
                            "name": "snapshotId",
                            "description": "The id of the layer snapshot.",
                            "$ref": "SnapshotId"
                        },
                        {
                            "name": "fromStep",
                            "description": "The first step to replay from (replay from the very start if not specified).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "toStep",
                            "description": "The last step to replay to (replay till the end if not specified).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "scale",
                            "description": "The scale to apply while replaying (defaults to 1).",
                            "optional": true,
                            "type": "number"
                        }
                    ],
                    "returns": [
                        {
                            "name": "dataURL",
                            "description": "A data: URL for resulting image.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "snapshotCommandLog",
                    "description": "Replays the layer snapshot and returns canvas log.",
                    "parameters": [
                        {
                            "name": "snapshotId",
                            "description": "The id of the layer snapshot.",
                            "$ref": "SnapshotId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "commandLog",
                            "description": "The array of canvas function calls.",
                            "type": "array",
                            "items": {
                                "type": "object"
                            }
                        }
                    ]
                }
            ],
            "events": [
                {
                    "name": "layerPainted",
                    "parameters": [
                        {
                            "name": "layerId",
                            "description": "The id of the painted layer.",
                            "$ref": "LayerId"
                        },
                        {
                            "name": "clip",
                            "description": "Clip rectangle.",
                            "$ref": "DOM.Rect"
                        }
                    ]
                },
                {
                    "name": "layerTreeDidChange",
                    "parameters": [
                        {
                            "name": "layers",
                            "description": "Layer tree, absent if not in the comspositing mode.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "Layer"
                            }
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Log",
            "description": "Provides access to log entries.",
            "dependencies": [
                "Runtime",
                "Network"
            ],
            "types": [
                {
                    "id": "LogEntry",
                    "description": "Log entry.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "source",
                            "description": "Log entry source.",
                            "type": "string",
                            "enum": [
                                "xml",
                                "javascript",
                                "network",
                                "storage",
                                "appcache",
                                "rendering",
                                "security",
                                "deprecation",
                                "worker",
                                "violation",
                                "intervention",
                                "recommendation",
                                "other"
                            ]
                        },
                        {
                            "name": "level",
                            "description": "Log entry severity.",
                            "type": "string",
                            "enum": [
                                "verbose",
                                "info",
                                "warning",
                                "error"
                            ]
                        },
                        {
                            "name": "text",
                            "description": "Logged text.",
                            "type": "string"
                        },
                        {
                            "name": "timestamp",
                            "description": "Timestamp when this entry was added.",
                            "$ref": "Runtime.Timestamp"
                        },
                        {
                            "name": "url",
                            "description": "URL of the resource if known.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "lineNumber",
                            "description": "Line number in the resource.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "stackTrace",
                            "description": "JavaScript stack trace.",
                            "optional": true,
                            "$ref": "Runtime.StackTrace"
                        },
                        {
                            "name": "networkRequestId",
                            "description": "Identifier of the network request associated with this entry.",
                            "optional": true,
                            "$ref": "Network.RequestId"
                        },
                        {
                            "name": "workerId",
                            "description": "Identifier of the worker associated with this entry.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "args",
                            "description": "Call arguments.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "Runtime.RemoteObject"
                            }
                        }
                    ]
                },
                {
                    "id": "ViolationSetting",
                    "description": "Violation configuration setting.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "name",
                            "description": "Violation type.",
                            "type": "string",
                            "enum": [
                                "longTask",
                                "longLayout",
                                "blockedEvent",
                                "blockedParser",
                                "discouragedAPIUse",
                                "handler",
                                "recurringHandler"
                            ]
                        },
                        {
                            "name": "threshold",
                            "description": "Time threshold to trigger upon.",
                            "type": "number"
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "clear",
                    "description": "Clears the log."
                },
                {
                    "name": "disable",
                    "description": "Disables log domain, prevents further log entries from being reported to the client."
                },
                {
                    "name": "enable",
                    "description": "Enables log domain, sends the entries collected so far to the client by means of the\n`entryAdded` notification."
                },
                {
                    "name": "startViolationsReport",
                    "description": "start violation reporting.",
                    "parameters": [
                        {
                            "name": "config",
                            "description": "Configuration for violations.",
                            "type": "array",
                            "items": {
                                "$ref": "ViolationSetting"
                            }
                        }
                    ]
                },
                {
                    "name": "stopViolationsReport",
                    "description": "Stop violation reporting."
                }
            ],
            "events": [
                {
                    "name": "entryAdded",
                    "description": "Issued when new message was logged.",
                    "parameters": [
                        {
                            "name": "entry",
                            "description": "The entry.",
                            "$ref": "LogEntry"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Memory",
            "experimental": true,
            "types": [
                {
                    "id": "PressureLevel",
                    "description": "Memory pressure level.",
                    "type": "string",
                    "enum": [
                        "moderate",
                        "critical"
                    ]
                },
                {
                    "id": "SamplingProfileNode",
                    "description": "Heap profile sample.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "size",
                            "description": "Size of the sampled allocation.",
                            "type": "number"
                        },
                        {
                            "name": "total",
                            "description": "Total bytes attributed to this sample.",
                            "type": "number"
                        },
                        {
                            "name": "stack",
                            "description": "Execution stack at the point of allocation.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ]
                },
                {
                    "id": "SamplingProfile",
                    "description": "Array of heap profile samples.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "samples",
                            "type": "array",
                            "items": {
                                "$ref": "SamplingProfileNode"
                            }
                        },
                        {
                            "name": "modules",
                            "type": "array",
                            "items": {
                                "$ref": "Module"
                            }
                        }
                    ]
                },
                {
                    "id": "Module",
                    "description": "Executable module information",
                    "type": "object",
                    "properties": [
                        {
                            "name": "name",
                            "description": "Name of the module.",
                            "type": "string"
                        },
                        {
                            "name": "uuid",
                            "description": "UUID of the module.",
                            "type": "string"
                        },
                        {
                            "name": "baseAddress",
                            "description": "Base address where the module is loaded into memory. Encoded as a decimal\nor hexadecimal (0x prefixed) string.",
                            "type": "string"
                        },
                        {
                            "name": "size",
                            "description": "Size of the module in bytes.",
                            "type": "number"
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "getDOMCounters",
                    "returns": [
                        {
                            "name": "documents",
                            "type": "integer"
                        },
                        {
                            "name": "nodes",
                            "type": "integer"
                        },
                        {
                            "name": "jsEventListeners",
                            "type": "integer"
                        }
                    ]
                },
                {
                    "name": "prepareForLeakDetection"
                },
                {
                    "name": "setPressureNotificationsSuppressed",
                    "description": "Enable/disable suppressing memory pressure notifications in all processes.",
                    "parameters": [
                        {
                            "name": "suppressed",
                            "description": "If true, memory pressure notifications will be suppressed.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "simulatePressureNotification",
                    "description": "Simulate a memory pressure notification in all processes.",
                    "parameters": [
                        {
                            "name": "level",
                            "description": "Memory pressure level of the notification.",
                            "$ref": "PressureLevel"
                        }
                    ]
                },
                {
                    "name": "startSampling",
                    "description": "Start collecting native memory profile.",
                    "parameters": [
                        {
                            "name": "samplingInterval",
                            "description": "Average number of bytes between samples.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "suppressRandomness",
                            "description": "Do not randomize intervals between samples.",
                            "optional": true,
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "stopSampling",
                    "description": "Stop collecting native memory profile."
                },
                {
                    "name": "getAllTimeSamplingProfile",
                    "description": "Retrieve native memory allocations profile\ncollected since renderer process startup.",
                    "returns": [
                        {
                            "name": "profile",
                            "$ref": "SamplingProfile"
                        }
                    ]
                },
                {
                    "name": "getBrowserSamplingProfile",
                    "description": "Retrieve native memory allocations profile\ncollected since browser process startup.",
                    "returns": [
                        {
                            "name": "profile",
                            "$ref": "SamplingProfile"
                        }
                    ]
                },
                {
                    "name": "getSamplingProfile",
                    "description": "Retrieve native memory allocations profile collected since last\n`startSampling` call.",
                    "returns": [
                        {
                            "name": "profile",
                            "$ref": "SamplingProfile"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Network",
            "description": "Network domain allows tracking network activities of the page. It exposes information about http,\nfile, data and other requests and responses, their headers, bodies, timing, etc.",
            "dependencies": [
                "Debugger",
                "Runtime",
                "Security"
            ],
            "types": [
                {
                    "id": "ResourceType",
                    "description": "Resource type as it was perceived by the rendering engine.",
                    "type": "string",
                    "enum": [
                        "Document",
                        "Stylesheet",
                        "Image",
                        "Media",
                        "Font",
                        "Script",
                        "TextTrack",
                        "XHR",
                        "Fetch",
                        "EventSource",
                        "WebSocket",
                        "Manifest",
                        "SignedExchange",
                        "Ping",
                        "CSPViolationReport",
                        "Other"
                    ]
                },
                {
                    "id": "LoaderId",
                    "description": "Unique loader identifier.",
                    "type": "string"
                },
                {
                    "id": "RequestId",
                    "description": "Unique request identifier.",
                    "type": "string"
                },
                {
                    "id": "InterceptionId",
                    "description": "Unique intercepted request identifier.",
                    "type": "string"
                },
                {
                    "id": "ErrorReason",
                    "description": "Network level fetch failure reason.",
                    "type": "string",
                    "enum": [
                        "Failed",
                        "Aborted",
                        "TimedOut",
                        "AccessDenied",
                        "ConnectionClosed",
                        "ConnectionReset",
                        "ConnectionRefused",
                        "ConnectionAborted",
                        "ConnectionFailed",
                        "NameNotResolved",
                        "InternetDisconnected",
                        "AddressUnreachable",
                        "BlockedByClient",
                        "BlockedByResponse"
                    ]
                },
                {
                    "id": "TimeSinceEpoch",
                    "description": "UTC time in seconds, counted from January 1, 1970.",
                    "type": "number"
                },
                {
                    "id": "MonotonicTime",
                    "description": "Monotonically increasing time in seconds since an arbitrary point in the past.",
                    "type": "number"
                },
                {
                    "id": "Headers",
                    "description": "Request / response headers as keys / values of JSON object.",
                    "type": "object"
                },
                {
                    "id": "ConnectionType",
                    "description": "The underlying connection technology that the browser is supposedly using.",
                    "type": "string",
                    "enum": [
                        "none",
                        "cellular2g",
                        "cellular3g",
                        "cellular4g",
                        "bluetooth",
                        "ethernet",
                        "wifi",
                        "wimax",
                        "other"
                    ]
                },
                {
                    "id": "CookieSameSite",
                    "description": "Represents the cookie's 'SameSite' status:\nhttps://tools.ietf.org/html/draft-west-first-party-cookies",
                    "type": "string",
                    "enum": [
                        "Strict",
                        "Lax"
                    ]
                },
                {
                    "id": "ResourceTiming",
                    "description": "Timing information for the request.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "requestTime",
                            "description": "Timing's requestTime is a baseline in seconds, while the other numbers are ticks in\nmilliseconds relatively to this requestTime.",
                            "type": "number"
                        },
                        {
                            "name": "proxyStart",
                            "description": "Started resolving proxy.",
                            "type": "number"
                        },
                        {
                            "name": "proxyEnd",
                            "description": "Finished resolving proxy.",
                            "type": "number"
                        },
                        {
                            "name": "dnsStart",
                            "description": "Started DNS address resolve.",
                            "type": "number"
                        },
                        {
                            "name": "dnsEnd",
                            "description": "Finished DNS address resolve.",
                            "type": "number"
                        },
                        {
                            "name": "connectStart",
                            "description": "Started connecting to the remote host.",
                            "type": "number"
                        },
                        {
                            "name": "connectEnd",
                            "description": "Connected to the remote host.",
                            "type": "number"
                        },
                        {
                            "name": "sslStart",
                            "description": "Started SSL handshake.",
                            "type": "number"
                        },
                        {
                            "name": "sslEnd",
                            "description": "Finished SSL handshake.",
                            "type": "number"
                        },
                        {
                            "name": "workerStart",
                            "description": "Started running ServiceWorker.",
                            "experimental": true,
                            "type": "number"
                        },
                        {
                            "name": "workerReady",
                            "description": "Finished Starting ServiceWorker.",
                            "experimental": true,
                            "type": "number"
                        },
                        {
                            "name": "sendStart",
                            "description": "Started sending request.",
                            "type": "number"
                        },
                        {
                            "name": "sendEnd",
                            "description": "Finished sending request.",
                            "type": "number"
                        },
                        {
                            "name": "pushStart",
                            "description": "Time the server started pushing request.",
                            "experimental": true,
                            "type": "number"
                        },
                        {
                            "name": "pushEnd",
                            "description": "Time the server finished pushing request.",
                            "experimental": true,
                            "type": "number"
                        },
                        {
                            "name": "receiveHeadersEnd",
                            "description": "Finished receiving response headers.",
                            "type": "number"
                        }
                    ]
                },
                {
                    "id": "ResourcePriority",
                    "description": "Loading priority of a resource request.",
                    "type": "string",
                    "enum": [
                        "VeryLow",
                        "Low",
                        "Medium",
                        "High",
                        "VeryHigh"
                    ]
                },
                {
                    "id": "Request",
                    "description": "HTTP request data.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "url",
                            "description": "Request URL (without fragment).",
                            "type": "string"
                        },
                        {
                            "name": "urlFragment",
                            "description": "Fragment of the requested URL starting with hash, if present.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "method",
                            "description": "HTTP request method.",
                            "type": "string"
                        },
                        {
                            "name": "headers",
                            "description": "HTTP request headers.",
                            "$ref": "Headers"
                        },
                        {
                            "name": "postData",
                            "description": "HTTP POST request data.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "hasPostData",
                            "description": "True when the request has POST data. Note that postData might still be omitted when this flag is true when the data is too long.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "mixedContentType",
                            "description": "The mixed content type of the request.",
                            "optional": true,
                            "$ref": "Security.MixedContentType"
                        },
                        {
                            "name": "initialPriority",
                            "description": "Priority of the resource request at the time request is sent.",
                            "$ref": "ResourcePriority"
                        },
                        {
                            "name": "referrerPolicy",
                            "description": "The referrer policy of the request, as defined in https://www.w3.org/TR/referrer-policy/",
                            "type": "string",
                            "enum": [
                                "unsafe-url",
                                "no-referrer-when-downgrade",
                                "no-referrer",
                                "origin",
                                "origin-when-cross-origin",
                                "same-origin",
                                "strict-origin",
                                "strict-origin-when-cross-origin"
                            ]
                        },
                        {
                            "name": "isLinkPreload",
                            "description": "Whether is loaded via link preload.",
                            "optional": true,
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "id": "SignedCertificateTimestamp",
                    "description": "Details of a signed certificate timestamp (SCT).",
                    "type": "object",
                    "properties": [
                        {
                            "name": "status",
                            "description": "Validation status.",
                            "type": "string"
                        },
                        {
                            "name": "origin",
                            "description": "Origin.",
                            "type": "string"
                        },
                        {
                            "name": "logDescription",
                            "description": "Log name / description.",
                            "type": "string"
                        },
                        {
                            "name": "logId",
                            "description": "Log ID.",
                            "type": "string"
                        },
                        {
                            "name": "timestamp",
                            "description": "Issuance date.",
                            "$ref": "TimeSinceEpoch"
                        },
                        {
                            "name": "hashAlgorithm",
                            "description": "Hash algorithm.",
                            "type": "string"
                        },
                        {
                            "name": "signatureAlgorithm",
                            "description": "Signature algorithm.",
                            "type": "string"
                        },
                        {
                            "name": "signatureData",
                            "description": "Signature data.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "SecurityDetails",
                    "description": "Security details about a request.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "protocol",
                            "description": "Protocol name (e.g. \"TLS 1.2\" or \"QUIC\").",
                            "type": "string"
                        },
                        {
                            "name": "keyExchange",
                            "description": "Key Exchange used by the connection, or the empty string if not applicable.",
                            "type": "string"
                        },
                        {
                            "name": "keyExchangeGroup",
                            "description": "(EC)DH group used by the connection, if applicable.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "cipher",
                            "description": "Cipher name.",
                            "type": "string"
                        },
                        {
                            "name": "mac",
                            "description": "TLS MAC. Note that AEAD ciphers do not have separate MACs.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "certificateId",
                            "description": "Certificate ID value.",
                            "$ref": "Security.CertificateId"
                        },
                        {
                            "name": "subjectName",
                            "description": "Certificate subject name.",
                            "type": "string"
                        },
                        {
                            "name": "sanList",
                            "description": "Subject Alternative Name (SAN) DNS names and IP addresses.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        },
                        {
                            "name": "issuer",
                            "description": "Name of the issuing CA.",
                            "type": "string"
                        },
                        {
                            "name": "validFrom",
                            "description": "Certificate valid from date.",
                            "$ref": "TimeSinceEpoch"
                        },
                        {
                            "name": "validTo",
                            "description": "Certificate valid to (expiration) date",
                            "$ref": "TimeSinceEpoch"
                        },
                        {
                            "name": "signedCertificateTimestampList",
                            "description": "List of signed certificate timestamps (SCTs).",
                            "type": "array",
                            "items": {
                                "$ref": "SignedCertificateTimestamp"
                            }
                        },
                        {
                            "name": "certificateTransparencyCompliance",
                            "description": "Whether the request complied with Certificate Transparency policy",
                            "$ref": "CertificateTransparencyCompliance"
                        }
                    ]
                },
                {
                    "id": "CertificateTransparencyCompliance",
                    "description": "Whether the request complied with Certificate Transparency policy.",
                    "type": "string",
                    "enum": [
                        "unknown",
                        "not-compliant",
                        "compliant"
                    ]
                },
                {
                    "id": "BlockedReason",
                    "description": "The reason why request was blocked.",
                    "type": "string",
                    "enum": [
                        "other",
                        "csp",
                        "mixed-content",
                        "origin",
                        "inspector",
                        "subresource-filter",
                        "content-type",
                        "collapsed-by-client"
                    ]
                },
                {
                    "id": "Response",
                    "description": "HTTP response data.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "url",
                            "description": "Response URL. This URL can be different from CachedResource.url in case of redirect.",
                            "type": "string"
                        },
                        {
                            "name": "status",
                            "description": "HTTP response status code.",
                            "type": "integer"
                        },
                        {
                            "name": "statusText",
                            "description": "HTTP response status text.",
                            "type": "string"
                        },
                        {
                            "name": "headers",
                            "description": "HTTP response headers.",
                            "$ref": "Headers"
                        },
                        {
                            "name": "headersText",
                            "description": "HTTP response headers text.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "mimeType",
                            "description": "Resource mimeType as determined by the browser.",
                            "type": "string"
                        },
                        {
                            "name": "requestHeaders",
                            "description": "Refined HTTP request headers that were actually transmitted over the network.",
                            "optional": true,
                            "$ref": "Headers"
                        },
                        {
                            "name": "requestHeadersText",
                            "description": "HTTP request headers text.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "connectionReused",
                            "description": "Specifies whether physical connection was actually reused for this request.",
                            "type": "boolean"
                        },
                        {
                            "name": "connectionId",
                            "description": "Physical connection id that was actually used for this request.",
                            "type": "number"
                        },
                        {
                            "name": "remoteIPAddress",
                            "description": "Remote IP address.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "remotePort",
                            "description": "Remote port.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "fromDiskCache",
                            "description": "Specifies that the request was served from the disk cache.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "fromServiceWorker",
                            "description": "Specifies that the request was served from the ServiceWorker.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "encodedDataLength",
                            "description": "Total number of bytes received for this request so far.",
                            "type": "number"
                        },
                        {
                            "name": "timing",
                            "description": "Timing information for the given request.",
                            "optional": true,
                            "$ref": "ResourceTiming"
                        },
                        {
                            "name": "protocol",
                            "description": "Protocol used to fetch this request.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "securityState",
                            "description": "Security state of the request resource.",
                            "$ref": "Security.SecurityState"
                        },
                        {
                            "name": "securityDetails",
                            "description": "Security details for the request.",
                            "optional": true,
                            "$ref": "SecurityDetails"
                        }
                    ]
                },
                {
                    "id": "WebSocketRequest",
                    "description": "WebSocket request data.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "headers",
                            "description": "HTTP request headers.",
                            "$ref": "Headers"
                        }
                    ]
                },
                {
                    "id": "WebSocketResponse",
                    "description": "WebSocket response data.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "status",
                            "description": "HTTP response status code.",
                            "type": "integer"
                        },
                        {
                            "name": "statusText",
                            "description": "HTTP response status text.",
                            "type": "string"
                        },
                        {
                            "name": "headers",
                            "description": "HTTP response headers.",
                            "$ref": "Headers"
                        },
                        {
                            "name": "headersText",
                            "description": "HTTP response headers text.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "requestHeaders",
                            "description": "HTTP request headers.",
                            "optional": true,
                            "$ref": "Headers"
                        },
                        {
                            "name": "requestHeadersText",
                            "description": "HTTP request headers text.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "WebSocketFrame",
                    "description": "WebSocket frame data.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "opcode",
                            "description": "WebSocket frame opcode.",
                            "type": "number"
                        },
                        {
                            "name": "mask",
                            "description": "WebSocke frame mask.",
                            "type": "boolean"
                        },
                        {
                            "name": "payloadData",
                            "description": "WebSocke frame payload data.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "CachedResource",
                    "description": "Information about the cached resource.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "url",
                            "description": "Resource URL. This is the url of the original network request.",
                            "type": "string"
                        },
                        {
                            "name": "type",
                            "description": "Type of this resource.",
                            "$ref": "ResourceType"
                        },
                        {
                            "name": "response",
                            "description": "Cached response data.",
                            "optional": true,
                            "$ref": "Response"
                        },
                        {
                            "name": "bodySize",
                            "description": "Cached response body size.",
                            "type": "number"
                        }
                    ]
                },
                {
                    "id": "Initiator",
                    "description": "Information about the request initiator.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "type",
                            "description": "Type of this initiator.",
                            "type": "string",
                            "enum": [
                                "parser",
                                "script",
                                "preload",
                                "SignedExchange",
                                "other"
                            ]
                        },
                        {
                            "name": "stack",
                            "description": "Initiator JavaScript stack trace, set for Script only.",
                            "optional": true,
                            "$ref": "Runtime.StackTrace"
                        },
                        {
                            "name": "url",
                            "description": "Initiator URL, set for Parser type or for Script type (when script is importing module) or for SignedExchange type.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "lineNumber",
                            "description": "Initiator line number, set for Parser type or for Script type (when script is importing\nmodule) (0-based).",
                            "optional": true,
                            "type": "number"
                        }
                    ]
                },
                {
                    "id": "Cookie",
                    "description": "Cookie object",
                    "type": "object",
                    "properties": [
                        {
                            "name": "name",
                            "description": "Cookie name.",
                            "type": "string"
                        },
                        {
                            "name": "value",
                            "description": "Cookie value.",
                            "type": "string"
                        },
                        {
                            "name": "domain",
                            "description": "Cookie domain.",
                            "type": "string"
                        },
                        {
                            "name": "path",
                            "description": "Cookie path.",
                            "type": "string"
                        },
                        {
                            "name": "expires",
                            "description": "Cookie expiration date as the number of seconds since the UNIX epoch.",
                            "type": "number"
                        },
                        {
                            "name": "size",
                            "description": "Cookie size.",
                            "type": "integer"
                        },
                        {
                            "name": "httpOnly",
                            "description": "True if cookie is http-only.",
                            "type": "boolean"
                        },
                        {
                            "name": "secure",
                            "description": "True if cookie is secure.",
                            "type": "boolean"
                        },
                        {
                            "name": "session",
                            "description": "True in case of session cookie.",
                            "type": "boolean"
                        },
                        {
                            "name": "sameSite",
                            "description": "Cookie SameSite type.",
                            "optional": true,
                            "$ref": "CookieSameSite"
                        }
                    ]
                },
                {
                    "id": "CookieParam",
                    "description": "Cookie parameter object",
                    "type": "object",
                    "properties": [
                        {
                            "name": "name",
                            "description": "Cookie name.",
                            "type": "string"
                        },
                        {
                            "name": "value",
                            "description": "Cookie value.",
                            "type": "string"
                        },
                        {
                            "name": "url",
                            "description": "The request-URI to associate with the setting of the cookie. This value can affect the\ndefault domain and path values of the created cookie.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "domain",
                            "description": "Cookie domain.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "path",
                            "description": "Cookie path.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "secure",
                            "description": "True if cookie is secure.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "httpOnly",
                            "description": "True if cookie is http-only.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "sameSite",
                            "description": "Cookie SameSite type.",
                            "optional": true,
                            "$ref": "CookieSameSite"
                        },
                        {
                            "name": "expires",
                            "description": "Cookie expiration date, session cookie if not set",
                            "optional": true,
                            "$ref": "TimeSinceEpoch"
                        }
                    ]
                },
                {
                    "id": "AuthChallenge",
                    "description": "Authorization challenge for HTTP status code 401 or 407.",
                    "experimental": true,
                    "type": "object",
                    "properties": [
                        {
                            "name": "source",
                            "description": "Source of the authentication challenge.",
                            "optional": true,
                            "type": "string",
                            "enum": [
                                "Server",
                                "Proxy"
                            ]
                        },
                        {
                            "name": "origin",
                            "description": "Origin of the challenger.",
                            "type": "string"
                        },
                        {
                            "name": "scheme",
                            "description": "The authentication scheme used, such as basic or digest",
                            "type": "string"
                        },
                        {
                            "name": "realm",
                            "description": "The realm of the challenge. May be empty.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "AuthChallengeResponse",
                    "description": "Response to an AuthChallenge.",
                    "experimental": true,
                    "type": "object",
                    "properties": [
                        {
                            "name": "response",
                            "description": "The decision on what to do in response to the authorization challenge.  Default means\ndeferring to the default behavior of the net stack, which will likely either the Cancel\nauthentication or display a popup dialog box.",
                            "type": "string",
                            "enum": [
                                "Default",
                                "CancelAuth",
                                "ProvideCredentials"
                            ]
                        },
                        {
                            "name": "username",
                            "description": "The username to provide, possibly empty. Should only be set if response is\nProvideCredentials.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "password",
                            "description": "The password to provide, possibly empty. Should only be set if response is\nProvideCredentials.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "InterceptionStage",
                    "description": "Stages of the interception to begin intercepting. Request will intercept before the request is\nsent. Response will intercept after the response is received.",
                    "experimental": true,
                    "type": "string",
                    "enum": [
                        "Request",
                        "HeadersReceived"
                    ]
                },
                {
                    "id": "RequestPattern",
                    "description": "Request pattern for interception.",
                    "experimental": true,
                    "type": "object",
                    "properties": [
                        {
                            "name": "urlPattern",
                            "description": "Wildcards ('*' -> zero or more, '?' -> exactly one) are allowed. Escape character is\nbackslash. Omitting is equivalent to \"*\".",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "resourceType",
                            "description": "If set, only requests for matching resource types will be intercepted.",
                            "optional": true,
                            "$ref": "ResourceType"
                        },
                        {
                            "name": "interceptionStage",
                            "description": "Stage at wich to begin intercepting requests. Default is Request.",
                            "optional": true,
                            "$ref": "InterceptionStage"
                        }
                    ]
                },
                {
                    "id": "SignedExchangeSignature",
                    "description": "Information about a signed exchange signature.\nhttps://wicg.github.io/webpackage/draft-yasskin-httpbis-origin-signed-exchanges-impl.html#rfc.section.3.1",
                    "experimental": true,
                    "type": "object",
                    "properties": [
                        {
                            "name": "label",
                            "description": "Signed exchange signature label.",
                            "type": "string"
                        },
                        {
                            "name": "signature",
                            "description": "The hex string of signed exchange signature.",
                            "type": "string"
                        },
                        {
                            "name": "integrity",
                            "description": "Signed exchange signature integrity.",
                            "type": "string"
                        },
                        {
                            "name": "certUrl",
                            "description": "Signed exchange signature cert Url.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "certSha256",
                            "description": "The hex string of signed exchange signature cert sha256.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "validityUrl",
                            "description": "Signed exchange signature validity Url.",
                            "type": "string"
                        },
                        {
                            "name": "date",
                            "description": "Signed exchange signature date.",
                            "type": "integer"
                        },
                        {
                            "name": "expires",
                            "description": "Signed exchange signature expires.",
                            "type": "integer"
                        },
                        {
                            "name": "certificates",
                            "description": "The encoded certificates.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ]
                },
                {
                    "id": "SignedExchangeHeader",
                    "description": "Information about a signed exchange header.\nhttps://wicg.github.io/webpackage/draft-yasskin-httpbis-origin-signed-exchanges-impl.html#cbor-representation",
                    "experimental": true,
                    "type": "object",
                    "properties": [
                        {
                            "name": "requestUrl",
                            "description": "Signed exchange request URL.",
                            "type": "string"
                        },
                        {
                            "name": "requestMethod",
                            "description": "Signed exchange request method.",
                            "type": "string"
                        },
                        {
                            "name": "responseCode",
                            "description": "Signed exchange response code.",
                            "type": "integer"
                        },
                        {
                            "name": "responseHeaders",
                            "description": "Signed exchange response headers.",
                            "$ref": "Headers"
                        },
                        {
                            "name": "signatures",
                            "description": "Signed exchange response signature.",
                            "type": "array",
                            "items": {
                                "$ref": "SignedExchangeSignature"
                            }
                        }
                    ]
                },
                {
                    "id": "SignedExchangeErrorField",
                    "description": "Field type for a signed exchange related error.",
                    "experimental": true,
                    "type": "string",
                    "enum": [
                        "signatureSig",
                        "signatureIntegrity",
                        "signatureCertUrl",
                        "signatureCertSha256",
                        "signatureValidityUrl",
                        "signatureTimestamps"
                    ]
                },
                {
                    "id": "SignedExchangeError",
                    "description": "Information about a signed exchange response.",
                    "experimental": true,
                    "type": "object",
                    "properties": [
                        {
                            "name": "message",
                            "description": "Error message.",
                            "type": "string"
                        },
                        {
                            "name": "signatureIndex",
                            "description": "The index of the signature which caused the error.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "errorField",
                            "description": "The field which caused the error.",
                            "optional": true,
                            "$ref": "SignedExchangeErrorField"
                        }
                    ]
                },
                {
                    "id": "SignedExchangeInfo",
                    "description": "Information about a signed exchange response.",
                    "experimental": true,
                    "type": "object",
                    "properties": [
                        {
                            "name": "outerResponse",
                            "description": "The outer response of signed HTTP exchange which was received from network.",
                            "$ref": "Response"
                        },
                        {
                            "name": "header",
                            "description": "Information about the signed exchange header.",
                            "optional": true,
                            "$ref": "SignedExchangeHeader"
                        },
                        {
                            "name": "securityDetails",
                            "description": "Security details for the signed exchange header.",
                            "optional": true,
                            "$ref": "SecurityDetails"
                        },
                        {
                            "name": "errors",
                            "description": "Errors occurred while handling the signed exchagne.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "SignedExchangeError"
                            }
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "canClearBrowserCache",
                    "description": "Tells whether clearing browser cache is supported.",
                    "deprecated": true,
                    "returns": [
                        {
                            "name": "result",
                            "description": "True if browser cache can be cleared.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "canClearBrowserCookies",
                    "description": "Tells whether clearing browser cookies is supported.",
                    "deprecated": true,
                    "returns": [
                        {
                            "name": "result",
                            "description": "True if browser cookies can be cleared.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "canEmulateNetworkConditions",
                    "description": "Tells whether emulation of network conditions is supported.",
                    "deprecated": true,
                    "returns": [
                        {
                            "name": "result",
                            "description": "True if emulation of network conditions is supported.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "clearBrowserCache",
                    "description": "Clears browser cache."
                },
                {
                    "name": "clearBrowserCookies",
                    "description": "Clears browser cookies."
                },
                {
                    "name": "continueInterceptedRequest",
                    "description": "Response to Network.requestIntercepted which either modifies the request to continue with any\nmodifications, or blocks it, or completes it with the provided response bytes. If a network\nfetch occurs as a result which encounters a redirect an additional Network.requestIntercepted\nevent will be sent with the same InterceptionId.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "interceptionId",
                            "$ref": "InterceptionId"
                        },
                        {
                            "name": "errorReason",
                            "description": "If set this causes the request to fail with the given reason. Passing `Aborted` for requests\nmarked with `isNavigationRequest` also cancels the navigation. Must not be set in response\nto an authChallenge.",
                            "optional": true,
                            "$ref": "ErrorReason"
                        },
                        {
                            "name": "rawResponse",
                            "description": "If set the requests completes using with the provided base64 encoded raw response, including\nHTTP status line and headers etc... Must not be set in response to an authChallenge.",
                            "optional": true,
                            "type": "binary"
                        },
                        {
                            "name": "url",
                            "description": "If set the request url will be modified in a way that's not observable by page. Must not be\nset in response to an authChallenge.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "method",
                            "description": "If set this allows the request method to be overridden. Must not be set in response to an\nauthChallenge.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "postData",
                            "description": "If set this allows postData to be set. Must not be set in response to an authChallenge.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "headers",
                            "description": "If set this allows the request headers to be changed. Must not be set in response to an\nauthChallenge.",
                            "optional": true,
                            "$ref": "Headers"
                        },
                        {
                            "name": "authChallengeResponse",
                            "description": "Response to a requestIntercepted with an authChallenge. Must not be set otherwise.",
                            "optional": true,
                            "$ref": "AuthChallengeResponse"
                        }
                    ]
                },
                {
                    "name": "deleteCookies",
                    "description": "Deletes browser cookies with matching name and url or domain/path pair.",
                    "parameters": [
                        {
                            "name": "name",
                            "description": "Name of the cookies to remove.",
                            "type": "string"
                        },
                        {
                            "name": "url",
                            "description": "If specified, deletes all the cookies with the given name where domain and path match\nprovided URL.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "domain",
                            "description": "If specified, deletes only cookies with the exact domain.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "path",
                            "description": "If specified, deletes only cookies with the exact path.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "disable",
                    "description": "Disables network tracking, prevents network events from being sent to the client."
                },
                {
                    "name": "emulateNetworkConditions",
                    "description": "Activates emulation of network conditions.",
                    "parameters": [
                        {
                            "name": "offline",
                            "description": "True to emulate internet disconnection.",
                            "type": "boolean"
                        },
                        {
                            "name": "latency",
                            "description": "Minimum latency from request sent to response headers received (ms).",
                            "type": "number"
                        },
                        {
                            "name": "downloadThroughput",
                            "description": "Maximal aggregated download throughput (bytes/sec). -1 disables download throttling.",
                            "type": "number"
                        },
                        {
                            "name": "uploadThroughput",
                            "description": "Maximal aggregated upload throughput (bytes/sec).  -1 disables upload throttling.",
                            "type": "number"
                        },
                        {
                            "name": "connectionType",
                            "description": "Connection type if known.",
                            "optional": true,
                            "$ref": "ConnectionType"
                        }
                    ]
                },
                {
                    "name": "enable",
                    "description": "Enables network tracking, network events will now be delivered to the client.",
                    "parameters": [
                        {
                            "name": "maxTotalBufferSize",
                            "description": "Buffer size in bytes to use when preserving network payloads (XHRs, etc).",
                            "experimental": true,
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "maxResourceBufferSize",
                            "description": "Per-resource buffer size in bytes to use when preserving network payloads (XHRs, etc).",
                            "experimental": true,
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "maxPostDataSize",
                            "description": "Longest post body size (in bytes) that would be included in requestWillBeSent notification",
                            "optional": true,
                            "type": "integer"
                        }
                    ]
                },
                {
                    "name": "getAllCookies",
                    "description": "Returns all browser cookies. Depending on the backend support, will return detailed cookie\ninformation in the `cookies` field.",
                    "returns": [
                        {
                            "name": "cookies",
                            "description": "Array of cookie objects.",
                            "type": "array",
                            "items": {
                                "$ref": "Cookie"
                            }
                        }
                    ]
                },
                {
                    "name": "getCertificate",
                    "description": "Returns the DER-encoded certificate.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "origin",
                            "description": "Origin to get certificate for.",
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "tableNames",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ]
                },
                {
                    "name": "getCookies",
                    "description": "Returns all browser cookies for the current URL. Depending on the backend support, will return\ndetailed cookie information in the `cookies` field.",
                    "parameters": [
                        {
                            "name": "urls",
                            "description": "The list of URLs for which applicable cookies will be fetched",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ],
                    "returns": [
                        {
                            "name": "cookies",
                            "description": "Array of cookie objects.",
                            "type": "array",
                            "items": {
                                "$ref": "Cookie"
                            }
                        }
                    ]
                },
                {
                    "name": "getResponseBody",
                    "description": "Returns content served for the given request.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Identifier of the network request to get content for.",
                            "$ref": "RequestId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "body",
                            "description": "Response body.",
                            "type": "string"
                        },
                        {
                            "name": "base64Encoded",
                            "description": "True, if content was sent as base64.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "getRequestPostData",
                    "description": "Returns post data sent with the request. Returns an error when no data was sent with the request.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Identifier of the network request to get content for.",
                            "$ref": "RequestId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "postData",
                            "description": "Base64-encoded request body.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "getResponseBodyForInterception",
                    "description": "Returns content served for the given currently intercepted request.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "interceptionId",
                            "description": "Identifier for the intercepted request to get body for.",
                            "$ref": "InterceptionId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "body",
                            "description": "Response body.",
                            "type": "string"
                        },
                        {
                            "name": "base64Encoded",
                            "description": "True, if content was sent as base64.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "takeResponseBodyForInterceptionAsStream",
                    "description": "Returns a handle to the stream representing the response body. Note that after this command,\nthe intercepted request can't be continued as is -- you either need to cancel it or to provide\nthe response body. The stream only supports sequential read, IO.read will fail if the position\nis specified.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "interceptionId",
                            "$ref": "InterceptionId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "stream",
                            "$ref": "IO.StreamHandle"
                        }
                    ]
                },
                {
                    "name": "replayXHR",
                    "description": "This method sends a new XMLHttpRequest which is identical to the original one. The following\nparameters should be identical: method, url, async, request body, extra headers, withCredentials\nattribute, user, password.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Identifier of XHR to replay.",
                            "$ref": "RequestId"
                        }
                    ]
                },
                {
                    "name": "searchInResponseBody",
                    "description": "Searches for given string in response content.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Identifier of the network response to search.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "query",
                            "description": "String to search for.",
                            "type": "string"
                        },
                        {
                            "name": "caseSensitive",
                            "description": "If true, search is case sensitive.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "isRegex",
                            "description": "If true, treats string parameter as regex.",
                            "optional": true,
                            "type": "boolean"
                        }
                    ],
                    "returns": [
                        {
                            "name": "result",
                            "description": "List of search matches.",
                            "type": "array",
                            "items": {
                                "$ref": "Debugger.SearchMatch"
                            }
                        }
                    ]
                },
                {
                    "name": "setBlockedURLs",
                    "description": "Blocks URLs from loading.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "urls",
                            "description": "URL patterns to block. Wildcards ('*') are allowed.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ]
                },
                {
                    "name": "setBypassServiceWorker",
                    "description": "Toggles ignoring of service worker for each request.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "bypass",
                            "description": "Bypass service worker and load from network.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "setCacheDisabled",
                    "description": "Toggles ignoring cache for each request. If `true`, cache will not be used.",
                    "parameters": [
                        {
                            "name": "cacheDisabled",
                            "description": "Cache disabled state.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "setCookie",
                    "description": "Sets a cookie with the given cookie data; may overwrite equivalent cookies if they exist.",
                    "parameters": [
                        {
                            "name": "name",
                            "description": "Cookie name.",
                            "type": "string"
                        },
                        {
                            "name": "value",
                            "description": "Cookie value.",
                            "type": "string"
                        },
                        {
                            "name": "url",
                            "description": "The request-URI to associate with the setting of the cookie. This value can affect the\ndefault domain and path values of the created cookie.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "domain",
                            "description": "Cookie domain.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "path",
                            "description": "Cookie path.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "secure",
                            "description": "True if cookie is secure.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "httpOnly",
                            "description": "True if cookie is http-only.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "sameSite",
                            "description": "Cookie SameSite type.",
                            "optional": true,
                            "$ref": "CookieSameSite"
                        },
                        {
                            "name": "expires",
                            "description": "Cookie expiration date, session cookie if not set",
                            "optional": true,
                            "$ref": "TimeSinceEpoch"
                        }
                    ],
                    "returns": [
                        {
                            "name": "success",
                            "description": "True if successfully set cookie.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "setCookies",
                    "description": "Sets given cookies.",
                    "parameters": [
                        {
                            "name": "cookies",
                            "description": "Cookies to be set.",
                            "type": "array",
                            "items": {
                                "$ref": "CookieParam"
                            }
                        }
                    ]
                },
                {
                    "name": "setDataSizeLimitsForTest",
                    "description": "For testing.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "maxTotalSize",
                            "description": "Maximum total buffer size.",
                            "type": "integer"
                        },
                        {
                            "name": "maxResourceSize",
                            "description": "Maximum per-resource size.",
                            "type": "integer"
                        }
                    ]
                },
                {
                    "name": "setExtraHTTPHeaders",
                    "description": "Specifies whether to always send extra HTTP headers with the requests from this page.",
                    "parameters": [
                        {
                            "name": "headers",
                            "description": "Map with extra HTTP headers.",
                            "$ref": "Headers"
                        }
                    ]
                },
                {
                    "name": "setRequestInterception",
                    "description": "Sets the requests to intercept that match a the provided patterns and optionally resource types.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "patterns",
                            "description": "Requests matching any of these patterns will be forwarded and wait for the corresponding\ncontinueInterceptedRequest call.",
                            "type": "array",
                            "items": {
                                "$ref": "RequestPattern"
                            }
                        }
                    ]
                },
                {
                    "name": "setUserAgentOverride",
                    "description": "Allows overriding user agent with the given string.",
                    "redirect": "Emulation",
                    "parameters": [
                        {
                            "name": "userAgent",
                            "description": "User agent to use.",
                            "type": "string"
                        },
                        {
                            "name": "acceptLanguage",
                            "description": "Browser langugage to emulate.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "platform",
                            "description": "The platform navigator.platform should return.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                }
            ],
            "events": [
                {
                    "name": "dataReceived",
                    "description": "Fired when data chunk was received over the network.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Request identifier.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "timestamp",
                            "description": "Timestamp.",
                            "$ref": "MonotonicTime"
                        },
                        {
                            "name": "dataLength",
                            "description": "Data chunk length.",
                            "type": "integer"
                        },
                        {
                            "name": "encodedDataLength",
                            "description": "Actual bytes received (might be less than dataLength for compressed encodings).",
                            "type": "integer"
                        }
                    ]
                },
                {
                    "name": "eventSourceMessageReceived",
                    "description": "Fired when EventSource message is received.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Request identifier.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "timestamp",
                            "description": "Timestamp.",
                            "$ref": "MonotonicTime"
                        },
                        {
                            "name": "eventName",
                            "description": "Message type.",
                            "type": "string"
                        },
                        {
                            "name": "eventId",
                            "description": "Message identifier.",
                            "type": "string"
                        },
                        {
                            "name": "data",
                            "description": "Message content.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "loadingFailed",
                    "description": "Fired when HTTP request has failed to load.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Request identifier.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "timestamp",
                            "description": "Timestamp.",
                            "$ref": "MonotonicTime"
                        },
                        {
                            "name": "type",
                            "description": "Resource type.",
                            "$ref": "ResourceType"
                        },
                        {
                            "name": "errorText",
                            "description": "User friendly error message.",
                            "type": "string"
                        },
                        {
                            "name": "canceled",
                            "description": "True if loading was canceled.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "blockedReason",
                            "description": "The reason why loading was blocked, if any.",
                            "optional": true,
                            "$ref": "BlockedReason"
                        }
                    ]
                },
                {
                    "name": "loadingFinished",
                    "description": "Fired when HTTP request has finished loading.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Request identifier.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "timestamp",
                            "description": "Timestamp.",
                            "$ref": "MonotonicTime"
                        },
                        {
                            "name": "encodedDataLength",
                            "description": "Total number of bytes received for this request.",
                            "type": "number"
                        },
                        {
                            "name": "shouldReportCorbBlocking",
                            "description": "Set when 1) response was blocked by Cross-Origin Read Blocking and also\n2) this needs to be reported to the DevTools console.",
                            "optional": true,
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "requestIntercepted",
                    "description": "Details of an intercepted HTTP request, which must be either allowed, blocked, modified or\nmocked.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "interceptionId",
                            "description": "Each request the page makes will have a unique id, however if any redirects are encountered\nwhile processing that fetch, they will be reported with the same id as the original fetch.\nLikewise if HTTP authentication is needed then the same fetch id will be used.",
                            "$ref": "InterceptionId"
                        },
                        {
                            "name": "request",
                            "$ref": "Request"
                        },
                        {
                            "name": "frameId",
                            "description": "The id of the frame that initiated the request.",
                            "$ref": "Page.FrameId"
                        },
                        {
                            "name": "resourceType",
                            "description": "How the requested resource will be used.",
                            "$ref": "ResourceType"
                        },
                        {
                            "name": "isNavigationRequest",
                            "description": "Whether this is a navigation request, which can abort the navigation completely.",
                            "type": "boolean"
                        },
                        {
                            "name": "isDownload",
                            "description": "Set if the request is a navigation that will result in a download.\nOnly present after response is received from the server (i.e. HeadersReceived stage).",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "redirectUrl",
                            "description": "Redirect location, only sent if a redirect was intercepted.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "authChallenge",
                            "description": "Details of the Authorization Challenge encountered. If this is set then\ncontinueInterceptedRequest must contain an authChallengeResponse.",
                            "optional": true,
                            "$ref": "AuthChallenge"
                        },
                        {
                            "name": "responseErrorReason",
                            "description": "Response error if intercepted at response stage or if redirect occurred while intercepting\nrequest.",
                            "optional": true,
                            "$ref": "ErrorReason"
                        },
                        {
                            "name": "responseStatusCode",
                            "description": "Response code if intercepted at response stage or if redirect occurred while intercepting\nrequest or auth retry occurred.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "responseHeaders",
                            "description": "Response headers if intercepted at the response stage or if redirect occurred while\nintercepting request or auth retry occurred.",
                            "optional": true,
                            "$ref": "Headers"
                        }
                    ]
                },
                {
                    "name": "requestServedFromCache",
                    "description": "Fired if request ended up loading from cache.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Request identifier.",
                            "$ref": "RequestId"
                        }
                    ]
                },
                {
                    "name": "requestWillBeSent",
                    "description": "Fired when page is about to send HTTP request.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Request identifier.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "loaderId",
                            "description": "Loader identifier. Empty string if the request is fetched from worker.",
                            "$ref": "LoaderId"
                        },
                        {
                            "name": "documentURL",
                            "description": "URL of the document this request is loaded for.",
                            "type": "string"
                        },
                        {
                            "name": "request",
                            "description": "Request data.",
                            "$ref": "Request"
                        },
                        {
                            "name": "timestamp",
                            "description": "Timestamp.",
                            "$ref": "MonotonicTime"
                        },
                        {
                            "name": "wallTime",
                            "description": "Timestamp.",
                            "$ref": "TimeSinceEpoch"
                        },
                        {
                            "name": "initiator",
                            "description": "Request initiator.",
                            "$ref": "Initiator"
                        },
                        {
                            "name": "redirectResponse",
                            "description": "Redirect response data.",
                            "optional": true,
                            "$ref": "Response"
                        },
                        {
                            "name": "type",
                            "description": "Type of this resource.",
                            "optional": true,
                            "$ref": "ResourceType"
                        },
                        {
                            "name": "frameId",
                            "description": "Frame identifier.",
                            "optional": true,
                            "$ref": "Page.FrameId"
                        },
                        {
                            "name": "hasUserGesture",
                            "description": "Whether the request is initiated by a user gesture. Defaults to false.",
                            "optional": true,
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "resourceChangedPriority",
                    "description": "Fired when resource loading priority is changed",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Request identifier.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "newPriority",
                            "description": "New priority",
                            "$ref": "ResourcePriority"
                        },
                        {
                            "name": "timestamp",
                            "description": "Timestamp.",
                            "$ref": "MonotonicTime"
                        }
                    ]
                },
                {
                    "name": "signedExchangeReceived",
                    "description": "Fired when a signed exchange was received over the network",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Request identifier.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "info",
                            "description": "Information about the signed exchange response.",
                            "$ref": "SignedExchangeInfo"
                        }
                    ]
                },
                {
                    "name": "responseReceived",
                    "description": "Fired when HTTP response is available.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Request identifier.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "loaderId",
                            "description": "Loader identifier. Empty string if the request is fetched from worker.",
                            "$ref": "LoaderId"
                        },
                        {
                            "name": "timestamp",
                            "description": "Timestamp.",
                            "$ref": "MonotonicTime"
                        },
                        {
                            "name": "type",
                            "description": "Resource type.",
                            "$ref": "ResourceType"
                        },
                        {
                            "name": "response",
                            "description": "Response data.",
                            "$ref": "Response"
                        },
                        {
                            "name": "frameId",
                            "description": "Frame identifier.",
                            "optional": true,
                            "$ref": "Page.FrameId"
                        }
                    ]
                },
                {
                    "name": "webSocketClosed",
                    "description": "Fired when WebSocket is closed.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Request identifier.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "timestamp",
                            "description": "Timestamp.",
                            "$ref": "MonotonicTime"
                        }
                    ]
                },
                {
                    "name": "webSocketCreated",
                    "description": "Fired upon WebSocket creation.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Request identifier.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "url",
                            "description": "WebSocket request URL.",
                            "type": "string"
                        },
                        {
                            "name": "initiator",
                            "description": "Request initiator.",
                            "optional": true,
                            "$ref": "Initiator"
                        }
                    ]
                },
                {
                    "name": "webSocketFrameError",
                    "description": "Fired when WebSocket frame error occurs.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Request identifier.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "timestamp",
                            "description": "Timestamp.",
                            "$ref": "MonotonicTime"
                        },
                        {
                            "name": "errorMessage",
                            "description": "WebSocket frame error message.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "webSocketFrameReceived",
                    "description": "Fired when WebSocket frame is received.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Request identifier.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "timestamp",
                            "description": "Timestamp.",
                            "$ref": "MonotonicTime"
                        },
                        {
                            "name": "response",
                            "description": "WebSocket response data.",
                            "$ref": "WebSocketFrame"
                        }
                    ]
                },
                {
                    "name": "webSocketFrameSent",
                    "description": "Fired when WebSocket frame is sent.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Request identifier.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "timestamp",
                            "description": "Timestamp.",
                            "$ref": "MonotonicTime"
                        },
                        {
                            "name": "response",
                            "description": "WebSocket response data.",
                            "$ref": "WebSocketFrame"
                        }
                    ]
                },
                {
                    "name": "webSocketHandshakeResponseReceived",
                    "description": "Fired when WebSocket handshake response becomes available.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Request identifier.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "timestamp",
                            "description": "Timestamp.",
                            "$ref": "MonotonicTime"
                        },
                        {
                            "name": "response",
                            "description": "WebSocket response data.",
                            "$ref": "WebSocketResponse"
                        }
                    ]
                },
                {
                    "name": "webSocketWillSendHandshakeRequest",
                    "description": "Fired when WebSocket is about to initiate handshake.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Request identifier.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "timestamp",
                            "description": "Timestamp.",
                            "$ref": "MonotonicTime"
                        },
                        {
                            "name": "wallTime",
                            "description": "UTC Timestamp.",
                            "$ref": "TimeSinceEpoch"
                        },
                        {
                            "name": "request",
                            "description": "WebSocket request data.",
                            "$ref": "WebSocketRequest"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Overlay",
            "description": "This domain provides various functionality related to drawing atop the inspected page.",
            "experimental": true,
            "dependencies": [
                "DOM",
                "Page",
                "Runtime"
            ],
            "types": [
                {
                    "id": "HighlightConfig",
                    "description": "Configuration data for the highlighting of page elements.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "showInfo",
                            "description": "Whether the node info tooltip should be shown (default: false).",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "showRulers",
                            "description": "Whether the rulers should be shown (default: false).",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "showExtensionLines",
                            "description": "Whether the extension lines from node to the rulers should be shown (default: false).",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "displayAsMaterial",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "contentColor",
                            "description": "The content box highlight fill color (default: transparent).",
                            "optional": true,
                            "$ref": "DOM.RGBA"
                        },
                        {
                            "name": "paddingColor",
                            "description": "The padding highlight fill color (default: transparent).",
                            "optional": true,
                            "$ref": "DOM.RGBA"
                        },
                        {
                            "name": "borderColor",
                            "description": "The border highlight fill color (default: transparent).",
                            "optional": true,
                            "$ref": "DOM.RGBA"
                        },
                        {
                            "name": "marginColor",
                            "description": "The margin highlight fill color (default: transparent).",
                            "optional": true,
                            "$ref": "DOM.RGBA"
                        },
                        {
                            "name": "eventTargetColor",
                            "description": "The event target element highlight fill color (default: transparent).",
                            "optional": true,
                            "$ref": "DOM.RGBA"
                        },
                        {
                            "name": "shapeColor",
                            "description": "The shape outside fill color (default: transparent).",
                            "optional": true,
                            "$ref": "DOM.RGBA"
                        },
                        {
                            "name": "shapeMarginColor",
                            "description": "The shape margin fill color (default: transparent).",
                            "optional": true,
                            "$ref": "DOM.RGBA"
                        },
                        {
                            "name": "selectorList",
                            "description": "Selectors to highlight relevant nodes.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "cssGridColor",
                            "description": "The grid layout color (default: transparent).",
                            "optional": true,
                            "$ref": "DOM.RGBA"
                        }
                    ]
                },
                {
                    "id": "InspectMode",
                    "type": "string",
                    "enum": [
                        "searchForNode",
                        "searchForUAShadowDOM",
                        "none"
                    ]
                }
            ],
            "commands": [
                {
                    "name": "disable",
                    "description": "Disables domain notifications."
                },
                {
                    "name": "enable",
                    "description": "Enables domain notifications."
                },
                {
                    "name": "getHighlightObjectForTest",
                    "description": "For testing.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "description": "Id of the node to get highlight object for.",
                            "$ref": "DOM.NodeId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "highlight",
                            "description": "Highlight data for the node.",
                            "type": "object"
                        }
                    ]
                },
                {
                    "name": "hideHighlight",
                    "description": "Hides any highlight."
                },
                {
                    "name": "highlightFrame",
                    "description": "Highlights owner element of the frame with given id.",
                    "parameters": [
                        {
                            "name": "frameId",
                            "description": "Identifier of the frame to highlight.",
                            "$ref": "Page.FrameId"
                        },
                        {
                            "name": "contentColor",
                            "description": "The content box highlight fill color (default: transparent).",
                            "optional": true,
                            "$ref": "DOM.RGBA"
                        },
                        {
                            "name": "contentOutlineColor",
                            "description": "The content box highlight outline color (default: transparent).",
                            "optional": true,
                            "$ref": "DOM.RGBA"
                        }
                    ]
                },
                {
                    "name": "highlightNode",
                    "description": "Highlights DOM node with given id or with the given JavaScript object wrapper. Either nodeId or\nobjectId must be specified.",
                    "parameters": [
                        {
                            "name": "highlightConfig",
                            "description": "A descriptor for the highlight appearance.",
                            "$ref": "HighlightConfig"
                        },
                        {
                            "name": "nodeId",
                            "description": "Identifier of the node to highlight.",
                            "optional": true,
                            "$ref": "DOM.NodeId"
                        },
                        {
                            "name": "backendNodeId",
                            "description": "Identifier of the backend node to highlight.",
                            "optional": true,
                            "$ref": "DOM.BackendNodeId"
                        },
                        {
                            "name": "objectId",
                            "description": "JavaScript object id of the node to be highlighted.",
                            "optional": true,
                            "$ref": "Runtime.RemoteObjectId"
                        }
                    ]
                },
                {
                    "name": "highlightQuad",
                    "description": "Highlights given quad. Coordinates are absolute with respect to the main frame viewport.",
                    "parameters": [
                        {
                            "name": "quad",
                            "description": "Quad to highlight",
                            "$ref": "DOM.Quad"
                        },
                        {
                            "name": "color",
                            "description": "The highlight fill color (default: transparent).",
                            "optional": true,
                            "$ref": "DOM.RGBA"
                        },
                        {
                            "name": "outlineColor",
                            "description": "The highlight outline color (default: transparent).",
                            "optional": true,
                            "$ref": "DOM.RGBA"
                        }
                    ]
                },
                {
                    "name": "highlightRect",
                    "description": "Highlights given rectangle. Coordinates are absolute with respect to the main frame viewport.",
                    "parameters": [
                        {
                            "name": "x",
                            "description": "X coordinate",
                            "type": "integer"
                        },
                        {
                            "name": "y",
                            "description": "Y coordinate",
                            "type": "integer"
                        },
                        {
                            "name": "width",
                            "description": "Rectangle width",
                            "type": "integer"
                        },
                        {
                            "name": "height",
                            "description": "Rectangle height",
                            "type": "integer"
                        },
                        {
                            "name": "color",
                            "description": "The highlight fill color (default: transparent).",
                            "optional": true,
                            "$ref": "DOM.RGBA"
                        },
                        {
                            "name": "outlineColor",
                            "description": "The highlight outline color (default: transparent).",
                            "optional": true,
                            "$ref": "DOM.RGBA"
                        }
                    ]
                },
                {
                    "name": "setInspectMode",
                    "description": "Enters the 'inspect' mode. In this mode, elements that user is hovering over are highlighted.\nBackend then generates 'inspectNodeRequested' event upon element selection.",
                    "parameters": [
                        {
                            "name": "mode",
                            "description": "Set an inspection mode.",
                            "$ref": "InspectMode"
                        },
                        {
                            "name": "highlightConfig",
                            "description": "A descriptor for the highlight appearance of hovered-over nodes. May be omitted if `enabled\n== false`.",
                            "optional": true,
                            "$ref": "HighlightConfig"
                        }
                    ]
                },
                {
                    "name": "setPausedInDebuggerMessage",
                    "parameters": [
                        {
                            "name": "message",
                            "description": "The message to display, also triggers resume and step over controls.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "setShowDebugBorders",
                    "description": "Requests that backend shows debug borders on layers",
                    "parameters": [
                        {
                            "name": "show",
                            "description": "True for showing debug borders",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "setShowFPSCounter",
                    "description": "Requests that backend shows the FPS counter",
                    "parameters": [
                        {
                            "name": "show",
                            "description": "True for showing the FPS counter",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "setShowPaintRects",
                    "description": "Requests that backend shows paint rectangles",
                    "parameters": [
                        {
                            "name": "result",
                            "description": "True for showing paint rectangles",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "setShowScrollBottleneckRects",
                    "description": "Requests that backend shows scroll bottleneck rects",
                    "parameters": [
                        {
                            "name": "show",
                            "description": "True for showing scroll bottleneck rects",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "setShowHitTestBorders",
                    "description": "Requests that backend shows hit-test borders on layers",
                    "parameters": [
                        {
                            "name": "show",
                            "description": "True for showing hit-test borders",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "setShowViewportSizeOnResize",
                    "description": "Paints viewport size upon main frame resize.",
                    "parameters": [
                        {
                            "name": "show",
                            "description": "Whether to paint size or not.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "setSuspended",
                    "parameters": [
                        {
                            "name": "suspended",
                            "description": "Whether overlay should be suspended and not consume any resources until resumed.",
                            "type": "boolean"
                        }
                    ]
                }
            ],
            "events": [
                {
                    "name": "inspectNodeRequested",
                    "description": "Fired when the node should be inspected. This happens after call to `setInspectMode` or when\nuser manually inspects an element.",
                    "parameters": [
                        {
                            "name": "backendNodeId",
                            "description": "Id of the node to inspect.",
                            "$ref": "DOM.BackendNodeId"
                        }
                    ]
                },
                {
                    "name": "nodeHighlightRequested",
                    "description": "Fired when the node should be highlighted. This happens after call to `setInspectMode`.",
                    "parameters": [
                        {
                            "name": "nodeId",
                            "$ref": "DOM.NodeId"
                        }
                    ]
                },
                {
                    "name": "screenshotRequested",
                    "description": "Fired when user asks to capture screenshot of some area on the page.",
                    "parameters": [
                        {
                            "name": "viewport",
                            "description": "Viewport to capture, in CSS.",
                            "$ref": "Page.Viewport"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Page",
            "description": "Actions and events related to the inspected page belong to the page domain.",
            "dependencies": [
                "Debugger",
                "DOM",
                "Network",
                "Runtime"
            ],
            "types": [
                {
                    "id": "FrameId",
                    "description": "Unique frame identifier.",
                    "type": "string"
                },
                {
                    "id": "Frame",
                    "description": "Information about the Frame on the page.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "id",
                            "description": "Frame unique identifier.",
                            "type": "string"
                        },
                        {
                            "name": "parentId",
                            "description": "Parent frame identifier.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "loaderId",
                            "description": "Identifier of the loader associated with this frame.",
                            "$ref": "Network.LoaderId"
                        },
                        {
                            "name": "name",
                            "description": "Frame's name as specified in the tag.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "url",
                            "description": "Frame document's URL.",
                            "type": "string"
                        },
                        {
                            "name": "securityOrigin",
                            "description": "Frame document's security origin.",
                            "type": "string"
                        },
                        {
                            "name": "mimeType",
                            "description": "Frame document's mimeType as determined by the browser.",
                            "type": "string"
                        },
                        {
                            "name": "unreachableUrl",
                            "description": "If the frame failed to load, this contains the URL that could not be loaded.",
                            "experimental": true,
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "FrameResource",
                    "description": "Information about the Resource on the page.",
                    "experimental": true,
                    "type": "object",
                    "properties": [
                        {
                            "name": "url",
                            "description": "Resource URL.",
                            "type": "string"
                        },
                        {
                            "name": "type",
                            "description": "Type of this resource.",
                            "$ref": "Network.ResourceType"
                        },
                        {
                            "name": "mimeType",
                            "description": "Resource mimeType as determined by the browser.",
                            "type": "string"
                        },
                        {
                            "name": "lastModified",
                            "description": "last-modified timestamp as reported by server.",
                            "optional": true,
                            "$ref": "Network.TimeSinceEpoch"
                        },
                        {
                            "name": "contentSize",
                            "description": "Resource content size.",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "failed",
                            "description": "True if the resource failed to load.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "canceled",
                            "description": "True if the resource was canceled during loading.",
                            "optional": true,
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "id": "FrameResourceTree",
                    "description": "Information about the Frame hierarchy along with their cached resources.",
                    "experimental": true,
                    "type": "object",
                    "properties": [
                        {
                            "name": "frame",
                            "description": "Frame information for this tree item.",
                            "$ref": "Frame"
                        },
                        {
                            "name": "childFrames",
                            "description": "Child frames.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "FrameResourceTree"
                            }
                        },
                        {
                            "name": "resources",
                            "description": "Information about frame resources.",
                            "type": "array",
                            "items": {
                                "$ref": "FrameResource"
                            }
                        }
                    ]
                },
                {
                    "id": "FrameTree",
                    "description": "Information about the Frame hierarchy.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "frame",
                            "description": "Frame information for this tree item.",
                            "$ref": "Frame"
                        },
                        {
                            "name": "childFrames",
                            "description": "Child frames.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "FrameTree"
                            }
                        }
                    ]
                },
                {
                    "id": "ScriptIdentifier",
                    "description": "Unique script identifier.",
                    "type": "string"
                },
                {
                    "id": "TransitionType",
                    "description": "Transition type.",
                    "type": "string",
                    "enum": [
                        "link",
                        "typed",
                        "address_bar",
                        "auto_bookmark",
                        "auto_subframe",
                        "manual_subframe",
                        "generated",
                        "auto_toplevel",
                        "form_submit",
                        "reload",
                        "keyword",
                        "keyword_generated",
                        "other"
                    ]
                },
                {
                    "id": "NavigationEntry",
                    "description": "Navigation history entry.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "id",
                            "description": "Unique id of the navigation history entry.",
                            "type": "integer"
                        },
                        {
                            "name": "url",
                            "description": "URL of the navigation history entry.",
                            "type": "string"
                        },
                        {
                            "name": "userTypedURL",
                            "description": "URL that the user typed in the url bar.",
                            "type": "string"
                        },
                        {
                            "name": "title",
                            "description": "Title of the navigation history entry.",
                            "type": "string"
                        },
                        {
                            "name": "transitionType",
                            "description": "Transition type.",
                            "$ref": "TransitionType"
                        }
                    ]
                },
                {
                    "id": "ScreencastFrameMetadata",
                    "description": "Screencast frame metadata.",
                    "experimental": true,
                    "type": "object",
                    "properties": [
                        {
                            "name": "offsetTop",
                            "description": "Top offset in DIP.",
                            "type": "number"
                        },
                        {
                            "name": "pageScaleFactor",
                            "description": "Page scale factor.",
                            "type": "number"
                        },
                        {
                            "name": "deviceWidth",
                            "description": "Device screen width in DIP.",
                            "type": "number"
                        },
                        {
                            "name": "deviceHeight",
                            "description": "Device screen height in DIP.",
                            "type": "number"
                        },
                        {
                            "name": "scrollOffsetX",
                            "description": "Position of horizontal scroll in CSS pixels.",
                            "type": "number"
                        },
                        {
                            "name": "scrollOffsetY",
                            "description": "Position of vertical scroll in CSS pixels.",
                            "type": "number"
                        },
                        {
                            "name": "timestamp",
                            "description": "Frame swap timestamp.",
                            "optional": true,
                            "$ref": "Network.TimeSinceEpoch"
                        }
                    ]
                },
                {
                    "id": "DialogType",
                    "description": "Javascript dialog type.",
                    "type": "string",
                    "enum": [
                        "alert",
                        "confirm",
                        "prompt",
                        "beforeunload"
                    ]
                },
                {
                    "id": "AppManifestError",
                    "description": "Error while paring app manifest.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "message",
                            "description": "Error message.",
                            "type": "string"
                        },
                        {
                            "name": "critical",
                            "description": "If criticial, this is a non-recoverable parse error.",
                            "type": "integer"
                        },
                        {
                            "name": "line",
                            "description": "Error line.",
                            "type": "integer"
                        },
                        {
                            "name": "column",
                            "description": "Error column.",
                            "type": "integer"
                        }
                    ]
                },
                {
                    "id": "LayoutViewport",
                    "description": "Layout viewport position and dimensions.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "pageX",
                            "description": "Horizontal offset relative to the document (CSS pixels).",
                            "type": "integer"
                        },
                        {
                            "name": "pageY",
                            "description": "Vertical offset relative to the document (CSS pixels).",
                            "type": "integer"
                        },
                        {
                            "name": "clientWidth",
                            "description": "Width (CSS pixels), excludes scrollbar if present.",
                            "type": "integer"
                        },
                        {
                            "name": "clientHeight",
                            "description": "Height (CSS pixels), excludes scrollbar if present.",
                            "type": "integer"
                        }
                    ]
                },
                {
                    "id": "VisualViewport",
                    "description": "Visual viewport position, dimensions, and scale.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "offsetX",
                            "description": "Horizontal offset relative to the layout viewport (CSS pixels).",
                            "type": "number"
                        },
                        {
                            "name": "offsetY",
                            "description": "Vertical offset relative to the layout viewport (CSS pixels).",
                            "type": "number"
                        },
                        {
                            "name": "pageX",
                            "description": "Horizontal offset relative to the document (CSS pixels).",
                            "type": "number"
                        },
                        {
                            "name": "pageY",
                            "description": "Vertical offset relative to the document (CSS pixels).",
                            "type": "number"
                        },
                        {
                            "name": "clientWidth",
                            "description": "Width (CSS pixels), excludes scrollbar if present.",
                            "type": "number"
                        },
                        {
                            "name": "clientHeight",
                            "description": "Height (CSS pixels), excludes scrollbar if present.",
                            "type": "number"
                        },
                        {
                            "name": "scale",
                            "description": "Scale relative to the ideal viewport (size at width=device-width).",
                            "type": "number"
                        }
                    ]
                },
                {
                    "id": "Viewport",
                    "description": "Viewport for capturing screenshot.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "x",
                            "description": "X offset in CSS pixels.",
                            "type": "number"
                        },
                        {
                            "name": "y",
                            "description": "Y offset in CSS pixels",
                            "type": "number"
                        },
                        {
                            "name": "width",
                            "description": "Rectangle width in CSS pixels",
                            "type": "number"
                        },
                        {
                            "name": "height",
                            "description": "Rectangle height in CSS pixels",
                            "type": "number"
                        },
                        {
                            "name": "scale",
                            "description": "Page scale factor.",
                            "type": "number"
                        }
                    ]
                },
                {
                    "id": "FontFamilies",
                    "description": "Generic font families collection.",
                    "experimental": true,
                    "type": "object",
                    "properties": [
                        {
                            "name": "standard",
                            "description": "The standard font-family.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "fixed",
                            "description": "The fixed font-family.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "serif",
                            "description": "The serif font-family.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "sansSerif",
                            "description": "The sansSerif font-family.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "cursive",
                            "description": "The cursive font-family.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "fantasy",
                            "description": "The fantasy font-family.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "pictograph",
                            "description": "The pictograph font-family.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "FontSizes",
                    "description": "Default font sizes.",
                    "experimental": true,
                    "type": "object",
                    "properties": [
                        {
                            "name": "standard",
                            "description": "Default standard font size.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "fixed",
                            "description": "Default fixed font size.",
                            "optional": true,
                            "type": "integer"
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "addScriptToEvaluateOnLoad",
                    "description": "Deprecated, please use addScriptToEvaluateOnNewDocument instead.",
                    "experimental": true,
                    "deprecated": true,
                    "parameters": [
                        {
                            "name": "scriptSource",
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "identifier",
                            "description": "Identifier of the added script.",
                            "$ref": "ScriptIdentifier"
                        }
                    ]
                },
                {
                    "name": "addScriptToEvaluateOnNewDocument",
                    "description": "Evaluates given script in every frame upon creation (before loading frame's scripts).",
                    "parameters": [
                        {
                            "name": "source",
                            "type": "string"
                        },
                        {
                            "name": "worldName",
                            "description": "If specified, creates an isolated world with the given name and evaluates given script in it.\nThis world name will be used as the ExecutionContextDescription::name when the corresponding\nevent is emitted.",
                            "experimental": true,
                            "optional": true,
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "identifier",
                            "description": "Identifier of the added script.",
                            "$ref": "ScriptIdentifier"
                        }
                    ]
                },
                {
                    "name": "bringToFront",
                    "description": "Brings page to front (activates tab)."
                },
                {
                    "name": "captureScreenshot",
                    "description": "Capture page screenshot.",
                    "parameters": [
                        {
                            "name": "format",
                            "description": "Image compression format (defaults to png).",
                            "optional": true,
                            "type": "string",
                            "enum": [
                                "jpeg",
                                "png"
                            ]
                        },
                        {
                            "name": "quality",
                            "description": "Compression quality from range [0..100] (jpeg only).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "clip",
                            "description": "Capture the screenshot of a given region only.",
                            "optional": true,
                            "$ref": "Viewport"
                        },
                        {
                            "name": "fromSurface",
                            "description": "Capture the screenshot from the surface, rather than the view. Defaults to true.",
                            "experimental": true,
                            "optional": true,
                            "type": "boolean"
                        }
                    ],
                    "returns": [
                        {
                            "name": "data",
                            "description": "Base64-encoded image data.",
                            "type": "binary"
                        }
                    ]
                },
                {
                    "name": "captureSnapshot",
                    "description": "Returns a snapshot of the page as a string. For MHTML format, the serialization includes\niframes, shadow DOM, external resources, and element-inline styles.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "format",
                            "description": "Format (defaults to mhtml).",
                            "optional": true,
                            "type": "string",
                            "enum": [
                                "mhtml"
                            ]
                        }
                    ],
                    "returns": [
                        {
                            "name": "data",
                            "description": "Serialized page data.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "clearDeviceMetricsOverride",
                    "description": "Clears the overriden device metrics.",
                    "experimental": true,
                    "deprecated": true,
                    "redirect": "Emulation"
                },
                {
                    "name": "clearDeviceOrientationOverride",
                    "description": "Clears the overridden Device Orientation.",
                    "experimental": true,
                    "deprecated": true,
                    "redirect": "DeviceOrientation"
                },
                {
                    "name": "clearGeolocationOverride",
                    "description": "Clears the overriden Geolocation Position and Error.",
                    "deprecated": true,
                    "redirect": "Emulation"
                },
                {
                    "name": "createIsolatedWorld",
                    "description": "Creates an isolated world for the given frame.",
                    "parameters": [
                        {
                            "name": "frameId",
                            "description": "Id of the frame in which the isolated world should be created.",
                            "$ref": "FrameId"
                        },
                        {
                            "name": "worldName",
                            "description": "An optional name which is reported in the Execution Context.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "grantUniveralAccess",
                            "description": "Whether or not universal access should be granted to the isolated world. This is a powerful\noption, use with caution.",
                            "optional": true,
                            "type": "boolean"
                        }
                    ],
                    "returns": [
                        {
                            "name": "executionContextId",
                            "description": "Execution context of the isolated world.",
                            "$ref": "Runtime.ExecutionContextId"
                        }
                    ]
                },
                {
                    "name": "deleteCookie",
                    "description": "Deletes browser cookie with given name, domain and path.",
                    "experimental": true,
                    "deprecated": true,
                    "redirect": "Network",
                    "parameters": [
                        {
                            "name": "cookieName",
                            "description": "Name of the cookie to remove.",
                            "type": "string"
                        },
                        {
                            "name": "url",
                            "description": "URL to match cooke domain and path.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "disable",
                    "description": "Disables page domain notifications."
                },
                {
                    "name": "enable",
                    "description": "Enables page domain notifications."
                },
                {
                    "name": "getAppManifest",
                    "returns": [
                        {
                            "name": "url",
                            "description": "Manifest location.",
                            "type": "string"
                        },
                        {
                            "name": "errors",
                            "type": "array",
                            "items": {
                                "$ref": "AppManifestError"
                            }
                        },
                        {
                            "name": "data",
                            "description": "Manifest content.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "getCookies",
                    "description": "Returns all browser cookies. Depending on the backend support, will return detailed cookie\ninformation in the `cookies` field.",
                    "experimental": true,
                    "deprecated": true,
                    "redirect": "Network",
                    "returns": [
                        {
                            "name": "cookies",
                            "description": "Array of cookie objects.",
                            "type": "array",
                            "items": {
                                "$ref": "Network.Cookie"
                            }
                        }
                    ]
                },
                {
                    "name": "getFrameTree",
                    "description": "Returns present frame tree structure.",
                    "returns": [
                        {
                            "name": "frameTree",
                            "description": "Present frame tree structure.",
                            "$ref": "FrameTree"
                        }
                    ]
                },
                {
                    "name": "getLayoutMetrics",
                    "description": "Returns metrics relating to the layouting of the page, such as viewport bounds/scale.",
                    "returns": [
                        {
                            "name": "layoutViewport",
                            "description": "Metrics relating to the layout viewport.",
                            "$ref": "LayoutViewport"
                        },
                        {
                            "name": "visualViewport",
                            "description": "Metrics relating to the visual viewport.",
                            "$ref": "VisualViewport"
                        },
                        {
                            "name": "contentSize",
                            "description": "Size of scrollable area.",
                            "$ref": "DOM.Rect"
                        }
                    ]
                },
                {
                    "name": "getNavigationHistory",
                    "description": "Returns navigation history for the current page.",
                    "returns": [
                        {
                            "name": "currentIndex",
                            "description": "Index of the current navigation history entry.",
                            "type": "integer"
                        },
                        {
                            "name": "entries",
                            "description": "Array of navigation history entries.",
                            "type": "array",
                            "items": {
                                "$ref": "NavigationEntry"
                            }
                        }
                    ]
                },
                {
                    "name": "getResourceContent",
                    "description": "Returns content of the given resource.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "frameId",
                            "description": "Frame id to get resource for.",
                            "$ref": "FrameId"
                        },
                        {
                            "name": "url",
                            "description": "URL of the resource to get content for.",
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "content",
                            "description": "Resource content.",
                            "type": "string"
                        },
                        {
                            "name": "base64Encoded",
                            "description": "True, if content was served as base64.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "getResourceTree",
                    "description": "Returns present frame / resource tree structure.",
                    "experimental": true,
                    "returns": [
                        {
                            "name": "frameTree",
                            "description": "Present frame / resource tree structure.",
                            "$ref": "FrameResourceTree"
                        }
                    ]
                },
                {
                    "name": "handleJavaScriptDialog",
                    "description": "Accepts or dismisses a JavaScript initiated dialog (alert, confirm, prompt, or onbeforeunload).",
                    "parameters": [
                        {
                            "name": "accept",
                            "description": "Whether to accept or dismiss the dialog.",
                            "type": "boolean"
                        },
                        {
                            "name": "promptText",
                            "description": "The text to enter into the dialog prompt before accepting. Used only if this is a prompt\ndialog.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "navigate",
                    "description": "Navigates current page to the given URL.",
                    "parameters": [
                        {
                            "name": "url",
                            "description": "URL to navigate the page to.",
                            "type": "string"
                        },
                        {
                            "name": "referrer",
                            "description": "Referrer URL.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "transitionType",
                            "description": "Intended transition type.",
                            "optional": true,
                            "$ref": "TransitionType"
                        },
                        {
                            "name": "frameId",
                            "description": "Frame id to navigate, if not specified navigates the top frame.",
                            "optional": true,
                            "$ref": "FrameId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "frameId",
                            "description": "Frame id that has navigated (or failed to navigate)",
                            "$ref": "FrameId"
                        },
                        {
                            "name": "loaderId",
                            "description": "Loader identifier.",
                            "optional": true,
                            "$ref": "Network.LoaderId"
                        },
                        {
                            "name": "errorText",
                            "description": "User friendly error message, present if and only if navigation has failed.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "navigateToHistoryEntry",
                    "description": "Navigates current page to the given history entry.",
                    "parameters": [
                        {
                            "name": "entryId",
                            "description": "Unique id of the entry to navigate to.",
                            "type": "integer"
                        }
                    ]
                },
                {
                    "name": "printToPDF",
                    "description": "Print page as PDF.",
                    "parameters": [
                        {
                            "name": "landscape",
                            "description": "Paper orientation. Defaults to false.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "displayHeaderFooter",
                            "description": "Display header and footer. Defaults to false.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "printBackground",
                            "description": "Print background graphics. Defaults to false.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "scale",
                            "description": "Scale of the webpage rendering. Defaults to 1.",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "paperWidth",
                            "description": "Paper width in inches. Defaults to 8.5 inches.",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "paperHeight",
                            "description": "Paper height in inches. Defaults to 11 inches.",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "marginTop",
                            "description": "Top margin in inches. Defaults to 1cm (~0.4 inches).",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "marginBottom",
                            "description": "Bottom margin in inches. Defaults to 1cm (~0.4 inches).",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "marginLeft",
                            "description": "Left margin in inches. Defaults to 1cm (~0.4 inches).",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "marginRight",
                            "description": "Right margin in inches. Defaults to 1cm (~0.4 inches).",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "pageRanges",
                            "description": "Paper ranges to print, e.g., '1-5, 8, 11-13'. Defaults to the empty string, which means\nprint all pages.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "ignoreInvalidPageRanges",
                            "description": "Whether to silently ignore invalid but successfully parsed page ranges, such as '3-2'.\nDefaults to false.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "headerTemplate",
                            "description": "HTML template for the print header. Should be valid HTML markup with following\nclasses used to inject printing values into them:\n- `date`: formatted print date\n- `title`: document title\n- `url`: document location\n- `pageNumber`: current page number\n- `totalPages`: total pages in the document\n\nFor example, `<span class=title></span>` would generate span containing the title.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "footerTemplate",
                            "description": "HTML template for the print footer. Should use the same format as the `headerTemplate`.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "preferCSSPageSize",
                            "description": "Whether or not to prefer page size as defined by css. Defaults to false,\nin which case the content will be scaled to fit the paper size.",
                            "optional": true,
                            "type": "boolean"
                        }
                    ],
                    "returns": [
                        {
                            "name": "data",
                            "description": "Base64-encoded pdf data.",
                            "type": "binary"
                        }
                    ]
                },
                {
                    "name": "reload",
                    "description": "Reloads given page optionally ignoring the cache.",
                    "parameters": [
                        {
                            "name": "ignoreCache",
                            "description": "If true, browser cache is ignored (as if the user pressed Shift+refresh).",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "scriptToEvaluateOnLoad",
                            "description": "If set, the script will be injected into all frames of the inspected page after reload.\nArgument will be ignored if reloading dataURL origin.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "removeScriptToEvaluateOnLoad",
                    "description": "Deprecated, please use removeScriptToEvaluateOnNewDocument instead.",
                    "experimental": true,
                    "deprecated": true,
                    "parameters": [
                        {
                            "name": "identifier",
                            "$ref": "ScriptIdentifier"
                        }
                    ]
                },
                {
                    "name": "removeScriptToEvaluateOnNewDocument",
                    "description": "Removes given script from the list.",
                    "parameters": [
                        {
                            "name": "identifier",
                            "$ref": "ScriptIdentifier"
                        }
                    ]
                },
                {
                    "name": "requestAppBanner",
                    "experimental": true
                },
                {
                    "name": "screencastFrameAck",
                    "description": "Acknowledges that a screencast frame has been received by the frontend.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "sessionId",
                            "description": "Frame number.",
                            "type": "integer"
                        }
                    ]
                },
                {
                    "name": "searchInResource",
                    "description": "Searches for given string in resource content.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "frameId",
                            "description": "Frame id for resource to search in.",
                            "$ref": "FrameId"
                        },
                        {
                            "name": "url",
                            "description": "URL of the resource to search in.",
                            "type": "string"
                        },
                        {
                            "name": "query",
                            "description": "String to search for.",
                            "type": "string"
                        },
                        {
                            "name": "caseSensitive",
                            "description": "If true, search is case sensitive.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "isRegex",
                            "description": "If true, treats string parameter as regex.",
                            "optional": true,
                            "type": "boolean"
                        }
                    ],
                    "returns": [
                        {
                            "name": "result",
                            "description": "List of search matches.",
                            "type": "array",
                            "items": {
                                "$ref": "Debugger.SearchMatch"
                            }
                        }
                    ]
                },
                {
                    "name": "setAdBlockingEnabled",
                    "description": "Enable Chrome's experimental ad filter on all sites.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "enabled",
                            "description": "Whether to block ads.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "setBypassCSP",
                    "description": "Enable page Content Security Policy by-passing.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "enabled",
                            "description": "Whether to bypass page CSP.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "setDeviceMetricsOverride",
                    "description": "Overrides the values of device screen dimensions (window.screen.width, window.screen.height,\nwindow.innerWidth, window.innerHeight, and \"device-width\"/\"device-height\"-related CSS media\nquery results).",
                    "experimental": true,
                    "deprecated": true,
                    "redirect": "Emulation",
                    "parameters": [
                        {
                            "name": "width",
                            "description": "Overriding width value in pixels (minimum 0, maximum 10000000). 0 disables the override.",
                            "type": "integer"
                        },
                        {
                            "name": "height",
                            "description": "Overriding height value in pixels (minimum 0, maximum 10000000). 0 disables the override.",
                            "type": "integer"
                        },
                        {
                            "name": "deviceScaleFactor",
                            "description": "Overriding device scale factor value. 0 disables the override.",
                            "type": "number"
                        },
                        {
                            "name": "mobile",
                            "description": "Whether to emulate mobile device. This includes viewport meta tag, overlay scrollbars, text\nautosizing and more.",
                            "type": "boolean"
                        },
                        {
                            "name": "scale",
                            "description": "Scale to apply to resulting view image.",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "screenWidth",
                            "description": "Overriding screen width value in pixels (minimum 0, maximum 10000000).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "screenHeight",
                            "description": "Overriding screen height value in pixels (minimum 0, maximum 10000000).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "positionX",
                            "description": "Overriding view X position on screen in pixels (minimum 0, maximum 10000000).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "positionY",
                            "description": "Overriding view Y position on screen in pixels (minimum 0, maximum 10000000).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "dontSetVisibleSize",
                            "description": "Do not set visible view size, rely upon explicit setVisibleSize call.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "screenOrientation",
                            "description": "Screen orientation override.",
                            "optional": true,
                            "$ref": "Emulation.ScreenOrientation"
                        },
                        {
                            "name": "viewport",
                            "description": "The viewport dimensions and scale. If not set, the override is cleared.",
                            "optional": true,
                            "$ref": "Viewport"
                        }
                    ]
                },
                {
                    "name": "setDeviceOrientationOverride",
                    "description": "Overrides the Device Orientation.",
                    "experimental": true,
                    "deprecated": true,
                    "redirect": "DeviceOrientation",
                    "parameters": [
                        {
                            "name": "alpha",
                            "description": "Mock alpha",
                            "type": "number"
                        },
                        {
                            "name": "beta",
                            "description": "Mock beta",
                            "type": "number"
                        },
                        {
                            "name": "gamma",
                            "description": "Mock gamma",
                            "type": "number"
                        }
                    ]
                },
                {
                    "name": "setFontFamilies",
                    "description": "Set generic font families.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "fontFamilies",
                            "description": "Specifies font families to set. If a font family is not specified, it won't be changed.",
                            "$ref": "FontFamilies"
                        }
                    ]
                },
                {
                    "name": "setFontSizes",
                    "description": "Set default font sizes.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "fontSizes",
                            "description": "Specifies font sizes to set. If a font size is not specified, it won't be changed.",
                            "$ref": "FontSizes"
                        }
                    ]
                },
                {
                    "name": "setDocumentContent",
                    "description": "Sets given markup as the document's HTML.",
                    "parameters": [
                        {
                            "name": "frameId",
                            "description": "Frame id to set HTML for.",
                            "$ref": "FrameId"
                        },
                        {
                            "name": "html",
                            "description": "HTML content to set.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "setDownloadBehavior",
                    "description": "Set the behavior when downloading a file.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "behavior",
                            "description": "Whether to allow all or deny all download requests, or use default Chrome behavior if\navailable (otherwise deny).",
                            "type": "string",
                            "enum": [
                                "deny",
                                "allow",
                                "default"
                            ]
                        },
                        {
                            "name": "downloadPath",
                            "description": "The default path to save downloaded files to. This is requred if behavior is set to 'allow'",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "setGeolocationOverride",
                    "description": "Overrides the Geolocation Position or Error. Omitting any of the parameters emulates position\nunavailable.",
                    "deprecated": true,
                    "redirect": "Emulation",
                    "parameters": [
                        {
                            "name": "latitude",
                            "description": "Mock latitude",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "longitude",
                            "description": "Mock longitude",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "accuracy",
                            "description": "Mock accuracy",
                            "optional": true,
                            "type": "number"
                        }
                    ]
                },
                {
                    "name": "setLifecycleEventsEnabled",
                    "description": "Controls whether page will emit lifecycle events.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "enabled",
                            "description": "If true, starts emitting lifecycle events.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "setTouchEmulationEnabled",
                    "description": "Toggles mouse event-based touch event emulation.",
                    "experimental": true,
                    "deprecated": true,
                    "redirect": "Emulation",
                    "parameters": [
                        {
                            "name": "enabled",
                            "description": "Whether the touch event emulation should be enabled.",
                            "type": "boolean"
                        },
                        {
                            "name": "configuration",
                            "description": "Touch/gesture events configuration. Default: current platform.",
                            "optional": true,
                            "type": "string",
                            "enum": [
                                "mobile",
                                "desktop"
                            ]
                        }
                    ]
                },
                {
                    "name": "startScreencast",
                    "description": "Starts sending each frame using the `screencastFrame` event.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "format",
                            "description": "Image compression format.",
                            "optional": true,
                            "type": "string",
                            "enum": [
                                "jpeg",
                                "png"
                            ]
                        },
                        {
                            "name": "quality",
                            "description": "Compression quality from range [0..100].",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "maxWidth",
                            "description": "Maximum screenshot width.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "maxHeight",
                            "description": "Maximum screenshot height.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "everyNthFrame",
                            "description": "Send every n-th frame.",
                            "optional": true,
                            "type": "integer"
                        }
                    ]
                },
                {
                    "name": "stopLoading",
                    "description": "Force the page stop all navigations and pending resource fetches."
                },
                {
                    "name": "crash",
                    "description": "Crashes renderer on the IO thread, generates minidumps.",
                    "experimental": true
                },
                {
                    "name": "close",
                    "description": "Tries to close page, running its beforeunload hooks, if any.",
                    "experimental": true
                },
                {
                    "name": "setWebLifecycleState",
                    "description": "Tries to update the web lifecycle state of the page.\nIt will transition the page to the given state according to:\nhttps://github.com/WICG/web-lifecycle/",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "state",
                            "description": "Target lifecycle state",
                            "type": "string",
                            "enum": [
                                "frozen",
                                "active"
                            ]
                        }
                    ]
                },
                {
                    "name": "stopScreencast",
                    "description": "Stops sending each frame in the `screencastFrame`.",
                    "experimental": true
                },
                {
                    "name": "setProduceCompilationCache",
                    "description": "Forces compilation cache to be generated for every subresource script.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "enabled",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "addCompilationCache",
                    "description": "Seeds compilation cache for given url. Compilation cache does not survive\ncross-process navigation.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "url",
                            "type": "string"
                        },
                        {
                            "name": "data",
                            "description": "Base64-encoded data",
                            "type": "binary"
                        }
                    ]
                },
                {
                    "name": "clearCompilationCache",
                    "description": "Clears seeded compilation cache.",
                    "experimental": true
                },
                {
                    "name": "generateTestReport",
                    "description": "Generates a report for testing.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "message",
                            "description": "Message to be displayed in the report.",
                            "type": "string"
                        },
                        {
                            "name": "group",
                            "description": "Specifies the endpoint group to deliver the report to.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                }
            ],
            "events": [
                {
                    "name": "domContentEventFired",
                    "parameters": [
                        {
                            "name": "timestamp",
                            "$ref": "Network.MonotonicTime"
                        }
                    ]
                },
                {
                    "name": "frameAttached",
                    "description": "Fired when frame has been attached to its parent.",
                    "parameters": [
                        {
                            "name": "frameId",
                            "description": "Id of the frame that has been attached.",
                            "$ref": "FrameId"
                        },
                        {
                            "name": "parentFrameId",
                            "description": "Parent frame identifier.",
                            "$ref": "FrameId"
                        },
                        {
                            "name": "stack",
                            "description": "JavaScript stack trace of when frame was attached, only set if frame initiated from script.",
                            "optional": true,
                            "$ref": "Runtime.StackTrace"
                        }
                    ]
                },
                {
                    "name": "frameClearedScheduledNavigation",
                    "description": "Fired when frame no longer has a scheduled navigation.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "frameId",
                            "description": "Id of the frame that has cleared its scheduled navigation.",
                            "$ref": "FrameId"
                        }
                    ]
                },
                {
                    "name": "frameDetached",
                    "description": "Fired when frame has been detached from its parent.",
                    "parameters": [
                        {
                            "name": "frameId",
                            "description": "Id of the frame that has been detached.",
                            "$ref": "FrameId"
                        }
                    ]
                },
                {
                    "name": "frameNavigated",
                    "description": "Fired once navigation of the frame has completed. Frame is now associated with the new loader.",
                    "parameters": [
                        {
                            "name": "frame",
                            "description": "Frame object.",
                            "$ref": "Frame"
                        }
                    ]
                },
                {
                    "name": "frameResized",
                    "experimental": true
                },
                {
                    "name": "frameScheduledNavigation",
                    "description": "Fired when frame schedules a potential navigation.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "frameId",
                            "description": "Id of the frame that has scheduled a navigation.",
                            "$ref": "FrameId"
                        },
                        {
                            "name": "delay",
                            "description": "Delay (in seconds) until the navigation is scheduled to begin. The navigation is not\nguaranteed to start.",
                            "type": "number"
                        },
                        {
                            "name": "reason",
                            "description": "The reason for the navigation.",
                            "type": "string",
                            "enum": [
                                "formSubmissionGet",
                                "formSubmissionPost",
                                "httpHeaderRefresh",
                                "scriptInitiated",
                                "metaTagRefresh",
                                "pageBlockInterstitial",
                                "reload"
                            ]
                        },
                        {
                            "name": "url",
                            "description": "The destination URL for the scheduled navigation.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "frameStartedLoading",
                    "description": "Fired when frame has started loading.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "frameId",
                            "description": "Id of the frame that has started loading.",
                            "$ref": "FrameId"
                        }
                    ]
                },
                {
                    "name": "frameStoppedLoading",
                    "description": "Fired when frame has stopped loading.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "frameId",
                            "description": "Id of the frame that has stopped loading.",
                            "$ref": "FrameId"
                        }
                    ]
                },
                {
                    "name": "interstitialHidden",
                    "description": "Fired when interstitial page was hidden"
                },
                {
                    "name": "interstitialShown",
                    "description": "Fired when interstitial page was shown"
                },
                {
                    "name": "javascriptDialogClosed",
                    "description": "Fired when a JavaScript initiated dialog (alert, confirm, prompt, or onbeforeunload) has been\nclosed.",
                    "parameters": [
                        {
                            "name": "result",
                            "description": "Whether dialog was confirmed.",
                            "type": "boolean"
                        },
                        {
                            "name": "userInput",
                            "description": "User input in case of prompt.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "javascriptDialogOpening",
                    "description": "Fired when a JavaScript initiated dialog (alert, confirm, prompt, or onbeforeunload) is about to\nopen.",
                    "parameters": [
                        {
                            "name": "url",
                            "description": "Frame url.",
                            "type": "string"
                        },
                        {
                            "name": "message",
                            "description": "Message that will be displayed by the dialog.",
                            "type": "string"
                        },
                        {
                            "name": "type",
                            "description": "Dialog type.",
                            "$ref": "DialogType"
                        },
                        {
                            "name": "hasBrowserHandler",
                            "description": "True iff browser is capable showing or acting on the given dialog. When browser has no\ndialog handler for given target, calling alert while Page domain is engaged will stall\nthe page execution. Execution can be resumed via calling Page.handleJavaScriptDialog.",
                            "type": "boolean"
                        },
                        {
                            "name": "defaultPrompt",
                            "description": "Default dialog prompt.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "lifecycleEvent",
                    "description": "Fired for top level page lifecycle events such as navigation, load, paint, etc.",
                    "parameters": [
                        {
                            "name": "frameId",
                            "description": "Id of the frame.",
                            "$ref": "FrameId"
                        },
                        {
                            "name": "loaderId",
                            "description": "Loader identifier. Empty string if the request is fetched from worker.",
                            "$ref": "Network.LoaderId"
                        },
                        {
                            "name": "name",
                            "type": "string"
                        },
                        {
                            "name": "timestamp",
                            "$ref": "Network.MonotonicTime"
                        }
                    ]
                },
                {
                    "name": "loadEventFired",
                    "parameters": [
                        {
                            "name": "timestamp",
                            "$ref": "Network.MonotonicTime"
                        }
                    ]
                },
                {
                    "name": "navigatedWithinDocument",
                    "description": "Fired when same-document navigation happens, e.g. due to history API usage or anchor navigation.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "frameId",
                            "description": "Id of the frame.",
                            "$ref": "FrameId"
                        },
                        {
                            "name": "url",
                            "description": "Frame's new url.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "screencastFrame",
                    "description": "Compressed image data requested by the `startScreencast`.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "data",
                            "description": "Base64-encoded compressed image.",
                            "type": "binary"
                        },
                        {
                            "name": "metadata",
                            "description": "Screencast frame metadata.",
                            "$ref": "ScreencastFrameMetadata"
                        },
                        {
                            "name": "sessionId",
                            "description": "Frame number.",
                            "type": "integer"
                        }
                    ]
                },
                {
                    "name": "screencastVisibilityChanged",
                    "description": "Fired when the page with currently enabled screencast was shown or hidden `.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "visible",
                            "description": "True if the page is visible.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "windowOpen",
                    "description": "Fired when a new window is going to be opened, via window.open(), link click, form submission,\netc.",
                    "parameters": [
                        {
                            "name": "url",
                            "description": "The URL for the new window.",
                            "type": "string"
                        },
                        {
                            "name": "windowName",
                            "description": "Window name.",
                            "type": "string"
                        },
                        {
                            "name": "windowFeatures",
                            "description": "An array of enabled window features.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        },
                        {
                            "name": "userGesture",
                            "description": "Whether or not it was triggered by user gesture.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "compilationCacheProduced",
                    "description": "Issued for every compilation cache generated. Is only available\nif Page.setGenerateCompilationCache is enabled.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "url",
                            "type": "string"
                        },
                        {
                            "name": "data",
                            "description": "Base64-encoded data",
                            "type": "binary"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Performance",
            "types": [
                {
                    "id": "Metric",
                    "description": "Run-time execution metric.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "name",
                            "description": "Metric name.",
                            "type": "string"
                        },
                        {
                            "name": "value",
                            "description": "Metric value.",
                            "type": "number"
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "disable",
                    "description": "Disable collecting and reporting metrics."
                },
                {
                    "name": "enable",
                    "description": "Enable collecting and reporting metrics."
                },
                {
                    "name": "setTimeDomain",
                    "description": "Sets time domain to use for collecting and reporting duration metrics.\nNote that this must be called before enabling metrics collection. Calling\nthis method while metrics collection is enabled returns an error.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "timeDomain",
                            "description": "Time domain",
                            "type": "string",
                            "enum": [
                                "timeTicks",
                                "threadTicks"
                            ]
                        }
                    ]
                },
                {
                    "name": "getMetrics",
                    "description": "Retrieve current values of run-time metrics.",
                    "returns": [
                        {
                            "name": "metrics",
                            "description": "Current values for run-time metrics.",
                            "type": "array",
                            "items": {
                                "$ref": "Metric"
                            }
                        }
                    ]
                }
            ],
            "events": [
                {
                    "name": "metrics",
                    "description": "Current values of the metrics.",
                    "parameters": [
                        {
                            "name": "metrics",
                            "description": "Current values of the metrics.",
                            "type": "array",
                            "items": {
                                "$ref": "Metric"
                            }
                        },
                        {
                            "name": "title",
                            "description": "Timestamp title.",
                            "type": "string"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Security",
            "description": "Security",
            "types": [
                {
                    "id": "CertificateId",
                    "description": "An internal certificate ID value.",
                    "type": "integer"
                },
                {
                    "id": "MixedContentType",
                    "description": "A description of mixed content (HTTP resources on HTTPS pages), as defined by\nhttps://www.w3.org/TR/mixed-content/#categories",
                    "type": "string",
                    "enum": [
                        "blockable",
                        "optionally-blockable",
                        "none"
                    ]
                },
                {
                    "id": "SecurityState",
                    "description": "The security level of a page or resource.",
                    "type": "string",
                    "enum": [
                        "unknown",
                        "neutral",
                        "insecure",
                        "secure",
                        "info"
                    ]
                },
                {
                    "id": "SecurityStateExplanation",
                    "description": "An explanation of an factor contributing to the security state.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "securityState",
                            "description": "Security state representing the severity of the factor being explained.",
                            "$ref": "SecurityState"
                        },
                        {
                            "name": "title",
                            "description": "Title describing the type of factor.",
                            "type": "string"
                        },
                        {
                            "name": "summary",
                            "description": "Short phrase describing the type of factor.",
                            "type": "string"
                        },
                        {
                            "name": "description",
                            "description": "Full text explanation of the factor.",
                            "type": "string"
                        },
                        {
                            "name": "mixedContentType",
                            "description": "The type of mixed content described by the explanation.",
                            "$ref": "MixedContentType"
                        },
                        {
                            "name": "certificate",
                            "description": "Page certificate.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        },
                        {
                            "name": "recommendations",
                            "description": "Recommendations to fix any issues.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ]
                },
                {
                    "id": "InsecureContentStatus",
                    "description": "Information about insecure content on the page.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "ranMixedContent",
                            "description": "True if the page was loaded over HTTPS and ran mixed (HTTP) content such as scripts.",
                            "type": "boolean"
                        },
                        {
                            "name": "displayedMixedContent",
                            "description": "True if the page was loaded over HTTPS and displayed mixed (HTTP) content such as images.",
                            "type": "boolean"
                        },
                        {
                            "name": "containedMixedForm",
                            "description": "True if the page was loaded over HTTPS and contained a form targeting an insecure url.",
                            "type": "boolean"
                        },
                        {
                            "name": "ranContentWithCertErrors",
                            "description": "True if the page was loaded over HTTPS without certificate errors, and ran content such as\nscripts that were loaded with certificate errors.",
                            "type": "boolean"
                        },
                        {
                            "name": "displayedContentWithCertErrors",
                            "description": "True if the page was loaded over HTTPS without certificate errors, and displayed content\nsuch as images that were loaded with certificate errors.",
                            "type": "boolean"
                        },
                        {
                            "name": "ranInsecureContentStyle",
                            "description": "Security state representing a page that ran insecure content.",
                            "$ref": "SecurityState"
                        },
                        {
                            "name": "displayedInsecureContentStyle",
                            "description": "Security state representing a page that displayed insecure content.",
                            "$ref": "SecurityState"
                        }
                    ]
                },
                {
                    "id": "CertificateErrorAction",
                    "description": "The action to take when a certificate error occurs. continue will continue processing the\nrequest and cancel will cancel the request.",
                    "type": "string",
                    "enum": [
                        "continue",
                        "cancel"
                    ]
                }
            ],
            "commands": [
                {
                    "name": "disable",
                    "description": "Disables tracking security state changes."
                },
                {
                    "name": "enable",
                    "description": "Enables tracking security state changes."
                },
                {
                    "name": "setIgnoreCertificateErrors",
                    "description": "Enable/disable whether all certificate errors should be ignored.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "ignore",
                            "description": "If true, all certificate errors will be ignored.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "handleCertificateError",
                    "description": "Handles a certificate error that fired a certificateError event.",
                    "deprecated": true,
                    "parameters": [
                        {
                            "name": "eventId",
                            "description": "The ID of the event.",
                            "type": "integer"
                        },
                        {
                            "name": "action",
                            "description": "The action to take on the certificate error.",
                            "$ref": "CertificateErrorAction"
                        }
                    ]
                },
                {
                    "name": "setOverrideCertificateErrors",
                    "description": "Enable/disable overriding certificate errors. If enabled, all certificate error events need to\nbe handled by the DevTools client and should be answered with `handleCertificateError` commands.",
                    "deprecated": true,
                    "parameters": [
                        {
                            "name": "override",
                            "description": "If true, certificate errors will be overridden.",
                            "type": "boolean"
                        }
                    ]
                }
            ],
            "events": [
                {
                    "name": "certificateError",
                    "description": "There is a certificate error. If overriding certificate errors is enabled, then it should be\nhandled with the `handleCertificateError` command. Note: this event does not fire if the\ncertificate error has been allowed internally. Only one client per target should override\ncertificate errors at the same time.",
                    "deprecated": true,
                    "parameters": [
                        {
                            "name": "eventId",
                            "description": "The ID of the event.",
                            "type": "integer"
                        },
                        {
                            "name": "errorType",
                            "description": "The type of the error.",
                            "type": "string"
                        },
                        {
                            "name": "requestURL",
                            "description": "The url that was requested.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "securityStateChanged",
                    "description": "The security state of the page changed.",
                    "parameters": [
                        {
                            "name": "securityState",
                            "description": "Security state.",
                            "$ref": "SecurityState"
                        },
                        {
                            "name": "schemeIsCryptographic",
                            "description": "True if the page was loaded over cryptographic transport such as HTTPS.",
                            "type": "boolean"
                        },
                        {
                            "name": "explanations",
                            "description": "List of explanations for the security state. If the overall security state is `insecure` or\n`warning`, at least one corresponding explanation should be included.",
                            "type": "array",
                            "items": {
                                "$ref": "SecurityStateExplanation"
                            }
                        },
                        {
                            "name": "insecureContentStatus",
                            "description": "Information about insecure content on the page.",
                            "$ref": "InsecureContentStatus"
                        },
                        {
                            "name": "summary",
                            "description": "Overrides user-visible description of the state.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "ServiceWorker",
            "experimental": true,
            "types": [
                {
                    "id": "ServiceWorkerRegistration",
                    "description": "ServiceWorker registration.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "registrationId",
                            "type": "string"
                        },
                        {
                            "name": "scopeURL",
                            "type": "string"
                        },
                        {
                            "name": "isDeleted",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "id": "ServiceWorkerVersionRunningStatus",
                    "type": "string",
                    "enum": [
                        "stopped",
                        "starting",
                        "running",
                        "stopping"
                    ]
                },
                {
                    "id": "ServiceWorkerVersionStatus",
                    "type": "string",
                    "enum": [
                        "new",
                        "installing",
                        "installed",
                        "activating",
                        "activated",
                        "redundant"
                    ]
                },
                {
                    "id": "ServiceWorkerVersion",
                    "description": "ServiceWorker version.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "versionId",
                            "type": "string"
                        },
                        {
                            "name": "registrationId",
                            "type": "string"
                        },
                        {
                            "name": "scriptURL",
                            "type": "string"
                        },
                        {
                            "name": "runningStatus",
                            "$ref": "ServiceWorkerVersionRunningStatus"
                        },
                        {
                            "name": "status",
                            "$ref": "ServiceWorkerVersionStatus"
                        },
                        {
                            "name": "scriptLastModified",
                            "description": "The Last-Modified header value of the main script.",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "scriptResponseTime",
                            "description": "The time at which the response headers of the main script were received from the server.\nFor cached script it is the last time the cache entry was validated.",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "controlledClients",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "Target.TargetID"
                            }
                        },
                        {
                            "name": "targetId",
                            "optional": true,
                            "$ref": "Target.TargetID"
                        }
                    ]
                },
                {
                    "id": "ServiceWorkerErrorMessage",
                    "description": "ServiceWorker error message.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "errorMessage",
                            "type": "string"
                        },
                        {
                            "name": "registrationId",
                            "type": "string"
                        },
                        {
                            "name": "versionId",
                            "type": "string"
                        },
                        {
                            "name": "sourceURL",
                            "type": "string"
                        },
                        {
                            "name": "lineNumber",
                            "type": "integer"
                        },
                        {
                            "name": "columnNumber",
                            "type": "integer"
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "deliverPushMessage",
                    "parameters": [
                        {
                            "name": "origin",
                            "type": "string"
                        },
                        {
                            "name": "registrationId",
                            "type": "string"
                        },
                        {
                            "name": "data",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "disable"
                },
                {
                    "name": "dispatchSyncEvent",
                    "parameters": [
                        {
                            "name": "origin",
                            "type": "string"
                        },
                        {
                            "name": "registrationId",
                            "type": "string"
                        },
                        {
                            "name": "tag",
                            "type": "string"
                        },
                        {
                            "name": "lastChance",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "enable"
                },
                {
                    "name": "inspectWorker",
                    "parameters": [
                        {
                            "name": "versionId",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "setForceUpdateOnPageLoad",
                    "parameters": [
                        {
                            "name": "forceUpdateOnPageLoad",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "skipWaiting",
                    "parameters": [
                        {
                            "name": "scopeURL",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "startWorker",
                    "parameters": [
                        {
                            "name": "scopeURL",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "stopAllWorkers"
                },
                {
                    "name": "stopWorker",
                    "parameters": [
                        {
                            "name": "versionId",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "unregister",
                    "parameters": [
                        {
                            "name": "scopeURL",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "updateRegistration",
                    "parameters": [
                        {
                            "name": "scopeURL",
                            "type": "string"
                        }
                    ]
                }
            ],
            "events": [
                {
                    "name": "workerErrorReported",
                    "parameters": [
                        {
                            "name": "errorMessage",
                            "$ref": "ServiceWorkerErrorMessage"
                        }
                    ]
                },
                {
                    "name": "workerRegistrationUpdated",
                    "parameters": [
                        {
                            "name": "registrations",
                            "type": "array",
                            "items": {
                                "$ref": "ServiceWorkerRegistration"
                            }
                        }
                    ]
                },
                {
                    "name": "workerVersionUpdated",
                    "parameters": [
                        {
                            "name": "versions",
                            "type": "array",
                            "items": {
                                "$ref": "ServiceWorkerVersion"
                            }
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Storage",
            "experimental": true,
            "types": [
                {
                    "id": "StorageType",
                    "description": "Enum of possible storage types.",
                    "type": "string",
                    "enum": [
                        "appcache",
                        "cookies",
                        "file_systems",
                        "indexeddb",
                        "local_storage",
                        "shader_cache",
                        "websql",
                        "service_workers",
                        "cache_storage",
                        "all",
                        "other"
                    ]
                },
                {
                    "id": "UsageForType",
                    "description": "Usage for a storage type.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "storageType",
                            "description": "Name of storage type.",
                            "$ref": "StorageType"
                        },
                        {
                            "name": "usage",
                            "description": "Storage usage (bytes).",
                            "type": "number"
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "clearDataForOrigin",
                    "description": "Clears storage for origin.",
                    "parameters": [
                        {
                            "name": "origin",
                            "description": "Security origin.",
                            "type": "string"
                        },
                        {
                            "name": "storageTypes",
                            "description": "Comma separated origin names.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "getUsageAndQuota",
                    "description": "Returns usage and quota in bytes.",
                    "parameters": [
                        {
                            "name": "origin",
                            "description": "Security origin.",
                            "type": "string"
                        }
                    ],
                    "returns": [
                        {
                            "name": "usage",
                            "description": "Storage usage (bytes).",
                            "type": "number"
                        },
                        {
                            "name": "quota",
                            "description": "Storage quota (bytes).",
                            "type": "number"
                        },
                        {
                            "name": "usageBreakdown",
                            "description": "Storage usage per type (bytes).",
                            "type": "array",
                            "items": {
                                "$ref": "UsageForType"
                            }
                        }
                    ]
                },
                {
                    "name": "trackCacheStorageForOrigin",
                    "description": "Registers origin to be notified when an update occurs to its cache storage list.",
                    "parameters": [
                        {
                            "name": "origin",
                            "description": "Security origin.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "trackIndexedDBForOrigin",
                    "description": "Registers origin to be notified when an update occurs to its IndexedDB.",
                    "parameters": [
                        {
                            "name": "origin",
                            "description": "Security origin.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "untrackCacheStorageForOrigin",
                    "description": "Unregisters origin from receiving notifications for cache storage.",
                    "parameters": [
                        {
                            "name": "origin",
                            "description": "Security origin.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "untrackIndexedDBForOrigin",
                    "description": "Unregisters origin from receiving notifications for IndexedDB.",
                    "parameters": [
                        {
                            "name": "origin",
                            "description": "Security origin.",
                            "type": "string"
                        }
                    ]
                }
            ],
            "events": [
                {
                    "name": "cacheStorageContentUpdated",
                    "description": "A cache's contents have been modified.",
                    "parameters": [
                        {
                            "name": "origin",
                            "description": "Origin to update.",
                            "type": "string"
                        },
                        {
                            "name": "cacheName",
                            "description": "Name of cache in origin.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "cacheStorageListUpdated",
                    "description": "A cache has been added/deleted.",
                    "parameters": [
                        {
                            "name": "origin",
                            "description": "Origin to update.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "indexedDBContentUpdated",
                    "description": "The origin's IndexedDB object store has been modified.",
                    "parameters": [
                        {
                            "name": "origin",
                            "description": "Origin to update.",
                            "type": "string"
                        },
                        {
                            "name": "databaseName",
                            "description": "Database to update.",
                            "type": "string"
                        },
                        {
                            "name": "objectStoreName",
                            "description": "ObjectStore to update.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "indexedDBListUpdated",
                    "description": "The origin's IndexedDB database list has been modified.",
                    "parameters": [
                        {
                            "name": "origin",
                            "description": "Origin to update.",
                            "type": "string"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "SystemInfo",
            "description": "The SystemInfo domain defines methods and events for querying low-level system information.",
            "experimental": true,
            "types": [
                {
                    "id": "GPUDevice",
                    "description": "Describes a single graphics processor (GPU).",
                    "type": "object",
                    "properties": [
                        {
                            "name": "vendorId",
                            "description": "PCI ID of the GPU vendor, if available; 0 otherwise.",
                            "type": "number"
                        },
                        {
                            "name": "deviceId",
                            "description": "PCI ID of the GPU device, if available; 0 otherwise.",
                            "type": "number"
                        },
                        {
                            "name": "vendorString",
                            "description": "String description of the GPU vendor, if the PCI ID is not available.",
                            "type": "string"
                        },
                        {
                            "name": "deviceString",
                            "description": "String description of the GPU device, if the PCI ID is not available.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "GPUInfo",
                    "description": "Provides information about the GPU(s) on the system.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "devices",
                            "description": "The graphics devices on the system. Element 0 is the primary GPU.",
                            "type": "array",
                            "items": {
                                "$ref": "GPUDevice"
                            }
                        },
                        {
                            "name": "auxAttributes",
                            "description": "An optional dictionary of additional GPU related attributes.",
                            "optional": true,
                            "type": "object"
                        },
                        {
                            "name": "featureStatus",
                            "description": "An optional dictionary of graphics features and their status.",
                            "optional": true,
                            "type": "object"
                        },
                        {
                            "name": "driverBugWorkarounds",
                            "description": "An optional array of GPU driver bug workarounds.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ]
                },
                {
                    "id": "ProcessInfo",
                    "description": "Represents process info.",
                    "type": "object",
                    "properties": [
                        {
                            "name": "type",
                            "description": "Specifies process type.",
                            "type": "string"
                        },
                        {
                            "name": "id",
                            "description": "Specifies process id.",
                            "type": "integer"
                        },
                        {
                            "name": "cpuTime",
                            "description": "Specifies cumulative CPU usage in seconds across all threads of the\nprocess since the process start.",
                            "type": "number"
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "getInfo",
                    "description": "Returns information about the system.",
                    "returns": [
                        {
                            "name": "gpu",
                            "description": "Information about the GPUs on the system.",
                            "$ref": "GPUInfo"
                        },
                        {
                            "name": "modelName",
                            "description": "A platform-dependent description of the model of the machine. On Mac OS, this is, for\nexample, 'MacBookPro'. Will be the empty string if not supported.",
                            "type": "string"
                        },
                        {
                            "name": "modelVersion",
                            "description": "A platform-dependent description of the version of the machine. On Mac OS, this is, for\nexample, '10.1'. Will be the empty string if not supported.",
                            "type": "string"
                        },
                        {
                            "name": "commandLine",
                            "description": "The command line string used to launch the browser. Will be the empty string if not\nsupported.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "getProcessInfo",
                    "description": "Returns information about all running processes.",
                    "returns": [
                        {
                            "name": "processInfo",
                            "description": "An array of process info blocks.",
                            "type": "array",
                            "items": {
                                "$ref": "ProcessInfo"
                            }
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Target",
            "description": "Supports additional targets discovery and allows to attach to them.",
            "types": [
                {
                    "id": "TargetID",
                    "type": "string"
                },
                {
                    "id": "SessionID",
                    "description": "Unique identifier of attached debugging session.",
                    "type": "string"
                },
                {
                    "id": "BrowserContextID",
                    "experimental": true,
                    "type": "string"
                },
                {
                    "id": "TargetInfo",
                    "type": "object",
                    "properties": [
                        {
                            "name": "targetId",
                            "$ref": "TargetID"
                        },
                        {
                            "name": "type",
                            "type": "string"
                        },
                        {
                            "name": "title",
                            "type": "string"
                        },
                        {
                            "name": "url",
                            "type": "string"
                        },
                        {
                            "name": "attached",
                            "description": "Whether the target has an attached client.",
                            "type": "boolean"
                        },
                        {
                            "name": "openerId",
                            "description": "Opener target Id",
                            "optional": true,
                            "$ref": "TargetID"
                        },
                        {
                            "name": "browserContextId",
                            "experimental": true,
                            "optional": true,
                            "$ref": "BrowserContextID"
                        }
                    ]
                },
                {
                    "id": "RemoteLocation",
                    "experimental": true,
                    "type": "object",
                    "properties": [
                        {
                            "name": "host",
                            "type": "string"
                        },
                        {
                            "name": "port",
                            "type": "integer"
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "activateTarget",
                    "description": "Activates (focuses) the target.",
                    "parameters": [
                        {
                            "name": "targetId",
                            "$ref": "TargetID"
                        }
                    ]
                },
                {
                    "name": "attachToTarget",
                    "description": "Attaches to the target with given id.",
                    "parameters": [
                        {
                            "name": "targetId",
                            "$ref": "TargetID"
                        },
                        {
                            "name": "flatten",
                            "description": "Enables \"flat\" access to the session via specifying sessionId attribute in the commands.",
                            "experimental": true,
                            "optional": true,
                            "type": "boolean"
                        }
                    ],
                    "returns": [
                        {
                            "name": "sessionId",
                            "description": "Id assigned to the session.",
                            "$ref": "SessionID"
                        }
                    ]
                },
                {
                    "name": "attachToBrowserTarget",
                    "description": "Attaches to the browser target, only uses flat sessionId mode.",
                    "experimental": true,
                    "returns": [
                        {
                            "name": "sessionId",
                            "description": "Id assigned to the session.",
                            "$ref": "SessionID"
                        }
                    ]
                },
                {
                    "name": "closeTarget",
                    "description": "Closes the target. If the target is a page that gets closed too.",
                    "parameters": [
                        {
                            "name": "targetId",
                            "$ref": "TargetID"
                        }
                    ],
                    "returns": [
                        {
                            "name": "success",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "exposeDevToolsProtocol",
                    "description": "Inject object to the target's main frame that provides a communication\nchannel with browser target.\n\nInjected object will be available as `window[bindingName]`.\n\nThe object has the follwing API:\n- `binding.send(json)` - a method to send messages over the remote debugging protocol\n- `binding.onmessage = json => handleMessage(json)` - a callback that will be called for the protocol notifications and command responses.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "targetId",
                            "$ref": "TargetID"
                        },
                        {
                            "name": "bindingName",
                            "description": "Binding name, 'cdp' if not specified.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "createBrowserContext",
                    "description": "Creates a new empty BrowserContext. Similar to an incognito profile but you can have more than\none.",
                    "experimental": true,
                    "returns": [
                        {
                            "name": "browserContextId",
                            "description": "The id of the context created.",
                            "$ref": "BrowserContextID"
                        }
                    ]
                },
                {
                    "name": "getBrowserContexts",
                    "description": "Returns all browser contexts created with `Target.createBrowserContext` method.",
                    "experimental": true,
                    "returns": [
                        {
                            "name": "browserContextIds",
                            "description": "An array of browser context ids.",
                            "type": "array",
                            "items": {
                                "$ref": "BrowserContextID"
                            }
                        }
                    ]
                },
                {
                    "name": "createTarget",
                    "description": "Creates a new page.",
                    "parameters": [
                        {
                            "name": "url",
                            "description": "The initial URL the page will be navigated to.",
                            "type": "string"
                        },
                        {
                            "name": "width",
                            "description": "Frame width in DIP (headless chrome only).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "height",
                            "description": "Frame height in DIP (headless chrome only).",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "browserContextId",
                            "description": "The browser context to create the page in.",
                            "optional": true,
                            "$ref": "BrowserContextID"
                        },
                        {
                            "name": "enableBeginFrameControl",
                            "description": "Whether BeginFrames for this target will be controlled via DevTools (headless chrome only,\nnot supported on MacOS yet, false by default).",
                            "experimental": true,
                            "optional": true,
                            "type": "boolean"
                        }
                    ],
                    "returns": [
                        {
                            "name": "targetId",
                            "description": "The id of the page opened.",
                            "$ref": "TargetID"
                        }
                    ]
                },
                {
                    "name": "detachFromTarget",
                    "description": "Detaches session with given id.",
                    "parameters": [
                        {
                            "name": "sessionId",
                            "description": "Session to detach.",
                            "optional": true,
                            "$ref": "SessionID"
                        },
                        {
                            "name": "targetId",
                            "description": "Deprecated.",
                            "deprecated": true,
                            "optional": true,
                            "$ref": "TargetID"
                        }
                    ]
                },
                {
                    "name": "disposeBrowserContext",
                    "description": "Deletes a BrowserContext. All the belonging pages will be closed without calling their\nbeforeunload hooks.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "browserContextId",
                            "$ref": "BrowserContextID"
                        }
                    ]
                },
                {
                    "name": "getTargetInfo",
                    "description": "Returns information about a target.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "targetId",
                            "optional": true,
                            "$ref": "TargetID"
                        }
                    ],
                    "returns": [
                        {
                            "name": "targetInfo",
                            "$ref": "TargetInfo"
                        }
                    ]
                },
                {
                    "name": "getTargets",
                    "description": "Retrieves a list of available targets.",
                    "returns": [
                        {
                            "name": "targetInfos",
                            "description": "The list of targets.",
                            "type": "array",
                            "items": {
                                "$ref": "TargetInfo"
                            }
                        }
                    ]
                },
                {
                    "name": "sendMessageToTarget",
                    "description": "Sends protocol message over session with given id.",
                    "parameters": [
                        {
                            "name": "message",
                            "type": "string"
                        },
                        {
                            "name": "sessionId",
                            "description": "Identifier of the session.",
                            "optional": true,
                            "$ref": "SessionID"
                        },
                        {
                            "name": "targetId",
                            "description": "Deprecated.",
                            "deprecated": true,
                            "optional": true,
                            "$ref": "TargetID"
                        }
                    ]
                },
                {
                    "name": "setAutoAttach",
                    "description": "Controls whether to automatically attach to new targets which are considered to be related to\nthis one. When turned on, attaches to all existing related targets as well. When turned off,\nautomatically detaches from all currently attached targets.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "autoAttach",
                            "description": "Whether to auto-attach to related targets.",
                            "type": "boolean"
                        },
                        {
                            "name": "waitForDebuggerOnStart",
                            "description": "Whether to pause new targets when attaching to them. Use `Runtime.runIfWaitingForDebugger`\nto run paused targets.",
                            "type": "boolean"
                        },
                        {
                            "name": "flatten",
                            "description": "Enables \"flat\" access to the session via specifying sessionId attribute in the commands.",
                            "experimental": true,
                            "optional": true,
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "setDiscoverTargets",
                    "description": "Controls whether to discover available targets and notify via\n`targetCreated/targetInfoChanged/targetDestroyed` events.",
                    "parameters": [
                        {
                            "name": "discover",
                            "description": "Whether to discover available targets.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "setRemoteLocations",
                    "description": "Enables target discovery for the specified locations, when `setDiscoverTargets` was set to\n`true`.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "locations",
                            "description": "List of remote locations.",
                            "type": "array",
                            "items": {
                                "$ref": "RemoteLocation"
                            }
                        }
                    ]
                }
            ],
            "events": [
                {
                    "name": "attachedToTarget",
                    "description": "Issued when attached to target because of auto-attach or `attachToTarget` command.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "sessionId",
                            "description": "Identifier assigned to the session used to send/receive messages.",
                            "$ref": "SessionID"
                        },
                        {
                            "name": "targetInfo",
                            "$ref": "TargetInfo"
                        },
                        {
                            "name": "waitingForDebugger",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "detachedFromTarget",
                    "description": "Issued when detached from target for any reason (including `detachFromTarget` command). Can be\nissued multiple times per target if multiple sessions have been attached to it.",
                    "experimental": true,
                    "parameters": [
                        {
                            "name": "sessionId",
                            "description": "Detached session identifier.",
                            "$ref": "SessionID"
                        },
                        {
                            "name": "targetId",
                            "description": "Deprecated.",
                            "deprecated": true,
                            "optional": true,
                            "$ref": "TargetID"
                        }
                    ]
                },
                {
                    "name": "receivedMessageFromTarget",
                    "description": "Notifies about a new protocol message received from the session (as reported in\n`attachedToTarget` event).",
                    "parameters": [
                        {
                            "name": "sessionId",
                            "description": "Identifier of a session which sends a message.",
                            "$ref": "SessionID"
                        },
                        {
                            "name": "message",
                            "type": "string"
                        },
                        {
                            "name": "targetId",
                            "description": "Deprecated.",
                            "deprecated": true,
                            "optional": true,
                            "$ref": "TargetID"
                        }
                    ]
                },
                {
                    "name": "targetCreated",
                    "description": "Issued when a possible inspection target is created.",
                    "parameters": [
                        {
                            "name": "targetInfo",
                            "$ref": "TargetInfo"
                        }
                    ]
                },
                {
                    "name": "targetDestroyed",
                    "description": "Issued when a target is destroyed.",
                    "parameters": [
                        {
                            "name": "targetId",
                            "$ref": "TargetID"
                        }
                    ]
                },
                {
                    "name": "targetCrashed",
                    "description": "Issued when a target has crashed.",
                    "parameters": [
                        {
                            "name": "targetId",
                            "$ref": "TargetID"
                        },
                        {
                            "name": "status",
                            "description": "Termination status type.",
                            "type": "string"
                        },
                        {
                            "name": "errorCode",
                            "description": "Termination error code.",
                            "type": "integer"
                        }
                    ]
                },
                {
                    "name": "targetInfoChanged",
                    "description": "Issued when some information about a target has changed. This only happens between\n`targetCreated` and `targetDestroyed`.",
                    "parameters": [
                        {
                            "name": "targetInfo",
                            "$ref": "TargetInfo"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Tethering",
            "description": "The Tethering domain defines methods and events for browser port binding.",
            "experimental": true,
            "commands": [
                {
                    "name": "bind",
                    "description": "Request browser port binding.",
                    "parameters": [
                        {
                            "name": "port",
                            "description": "Port number to bind.",
                            "type": "integer"
                        }
                    ]
                },
                {
                    "name": "unbind",
                    "description": "Request browser port unbinding.",
                    "parameters": [
                        {
                            "name": "port",
                            "description": "Port number to unbind.",
                            "type": "integer"
                        }
                    ]
                }
            ],
            "events": [
                {
                    "name": "accepted",
                    "description": "Informs that port was successfully bound and got a specified connection id.",
                    "parameters": [
                        {
                            "name": "port",
                            "description": "Port number that was successfully bound.",
                            "type": "integer"
                        },
                        {
                            "name": "connectionId",
                            "description": "Connection id to be used.",
                            "type": "string"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Tracing",
            "experimental": true,
            "dependencies": [
                "IO"
            ],
            "types": [
                {
                    "id": "MemoryDumpConfig",
                    "description": "Configuration for memory dump. Used only when \"memory-infra\" category is enabled.",
                    "type": "object"
                },
                {
                    "id": "TraceConfig",
                    "type": "object",
                    "properties": [
                        {
                            "name": "recordMode",
                            "description": "Controls how the trace buffer stores data.",
                            "optional": true,
                            "type": "string",
                            "enum": [
                                "recordUntilFull",
                                "recordContinuously",
                                "recordAsMuchAsPossible",
                                "echoToConsole"
                            ]
                        },
                        {
                            "name": "enableSampling",
                            "description": "Turns on JavaScript stack sampling.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "enableSystrace",
                            "description": "Turns on system tracing.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "enableArgumentFilter",
                            "description": "Turns on argument filter.",
                            "optional": true,
                            "type": "boolean"
                        },
                        {
                            "name": "includedCategories",
                            "description": "Included category filters.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        },
                        {
                            "name": "excludedCategories",
                            "description": "Excluded category filters.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        },
                        {
                            "name": "syntheticDelays",
                            "description": "Configuration to synthesize the delays in tracing.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        },
                        {
                            "name": "memoryDumpConfig",
                            "description": "Configuration for memory dump triggers. Used only when \"memory-infra\" category is enabled.",
                            "optional": true,
                            "$ref": "MemoryDumpConfig"
                        }
                    ]
                },
                {
                    "id": "StreamCompression",
                    "description": "Compression type to use for traces returned via streams.",
                    "type": "string",
                    "enum": [
                        "none",
                        "gzip"
                    ]
                }
            ],
            "commands": [
                {
                    "name": "end",
                    "description": "Stop trace events collection."
                },
                {
                    "name": "getCategories",
                    "description": "Gets supported tracing categories.",
                    "returns": [
                        {
                            "name": "categories",
                            "description": "A list of supported tracing categories.",
                            "type": "array",
                            "items": {
                                "type": "string"
                            }
                        }
                    ]
                },
                {
                    "name": "recordClockSyncMarker",
                    "description": "Record a clock sync marker in the trace.",
                    "parameters": [
                        {
                            "name": "syncId",
                            "description": "The ID of this clock sync marker",
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "requestMemoryDump",
                    "description": "Request a global memory dump.",
                    "returns": [
                        {
                            "name": "dumpGuid",
                            "description": "GUID of the resulting global memory dump.",
                            "type": "string"
                        },
                        {
                            "name": "success",
                            "description": "True iff the global memory dump succeeded.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "start",
                    "description": "Start trace events collection.",
                    "parameters": [
                        {
                            "name": "categories",
                            "description": "Category/tag filter",
                            "deprecated": true,
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "options",
                            "description": "Tracing options",
                            "deprecated": true,
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "bufferUsageReportingInterval",
                            "description": "If set, the agent will issue bufferUsage events at this interval, specified in milliseconds",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "transferMode",
                            "description": "Whether to report trace events as series of dataCollected events or to save trace to a\nstream (defaults to `ReportEvents`).",
                            "optional": true,
                            "type": "string",
                            "enum": [
                                "ReportEvents",
                                "ReturnAsStream"
                            ]
                        },
                        {
                            "name": "streamCompression",
                            "description": "Compression format to use. This only applies when using `ReturnAsStream`\ntransfer mode (defaults to `none`)",
                            "optional": true,
                            "$ref": "StreamCompression"
                        },
                        {
                            "name": "traceConfig",
                            "optional": true,
                            "$ref": "TraceConfig"
                        }
                    ]
                }
            ],
            "events": [
                {
                    "name": "bufferUsage",
                    "parameters": [
                        {
                            "name": "percentFull",
                            "description": "A number in range [0..1] that indicates the used size of event buffer as a fraction of its\ntotal size.",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "eventCount",
                            "description": "An approximate number of events in the trace log.",
                            "optional": true,
                            "type": "number"
                        },
                        {
                            "name": "value",
                            "description": "A number in range [0..1] that indicates the used size of event buffer as a fraction of its\ntotal size.",
                            "optional": true,
                            "type": "number"
                        }
                    ]
                },
                {
                    "name": "dataCollected",
                    "description": "Contains an bucket of collected trace events. When tracing is stopped collected events will be\nsend as a sequence of dataCollected events followed by tracingComplete event.",
                    "parameters": [
                        {
                            "name": "value",
                            "type": "array",
                            "items": {
                                "type": "object"
                            }
                        }
                    ]
                },
                {
                    "name": "tracingComplete",
                    "description": "Signals that tracing is stopped and there is no trace buffers pending flush, all data were\ndelivered via dataCollected events.",
                    "parameters": [
                        {
                            "name": "stream",
                            "description": "A handle of the stream that holds resulting trace data.",
                            "optional": true,
                            "$ref": "IO.StreamHandle"
                        },
                        {
                            "name": "streamCompression",
                            "description": "Compression format of returned stream.",
                            "optional": true,
                            "$ref": "StreamCompression"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Testing",
            "description": "Testing domain is a dumping ground for the capabilities requires for browser or app testing that do not fit other\ndomains.",
            "experimental": true,
            "dependencies": [
                "Page"
            ],
            "commands": [
                {
                    "name": "generateTestReport",
                    "description": "Generates a report for testing.",
                    "parameters": [
                        {
                            "name": "message",
                            "description": "Message to be displayed in the report.",
                            "type": "string"
                        },
                        {
                            "name": "group",
                            "description": "Specifies the endpoint group to deliver the report to.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                }
            ]
        },
        {
            "domain": "Fetch",
            "description": "A domain for letting clients substitute browser's network layer with client code.",
            "experimental": true,
            "dependencies": [
                "Network",
                "IO",
                "Page"
            ],
            "types": [
                {
                    "id": "RequestId",
                    "description": "Unique request identifier.",
                    "type": "string"
                },
                {
                    "id": "RequestStage",
                    "description": "Stages of the request to handle. Request will intercept before the request is\nsent. Response will intercept after the response is received (but before response\nbody is received.",
                    "experimental": true,
                    "type": "string",
                    "enum": [
                        "Request",
                        "Response"
                    ]
                },
                {
                    "id": "RequestPattern",
                    "experimental": true,
                    "type": "object",
                    "properties": [
                        {
                            "name": "urlPattern",
                            "description": "Wildcards ('*' -> zero or more, '?' -> exactly one) are allowed. Escape character is\nbackslash. Omitting is equivalent to \"*\".",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "resourceType",
                            "description": "If set, only requests for matching resource types will be intercepted.",
                            "optional": true,
                            "$ref": "Network.ResourceType"
                        },
                        {
                            "name": "requestStage",
                            "description": "Stage at wich to begin intercepting requests. Default is Request.",
                            "optional": true,
                            "$ref": "RequestStage"
                        }
                    ]
                },
                {
                    "id": "HeaderEntry",
                    "description": "Response HTTP header entry",
                    "type": "object",
                    "properties": [
                        {
                            "name": "name",
                            "type": "string"
                        },
                        {
                            "name": "value",
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "AuthChallenge",
                    "description": "Authorization challenge for HTTP status code 401 or 407.",
                    "experimental": true,
                    "type": "object",
                    "properties": [
                        {
                            "name": "source",
                            "description": "Source of the authentication challenge.",
                            "optional": true,
                            "type": "string",
                            "enum": [
                                "Server",
                                "Proxy"
                            ]
                        },
                        {
                            "name": "origin",
                            "description": "Origin of the challenger.",
                            "type": "string"
                        },
                        {
                            "name": "scheme",
                            "description": "The authentication scheme used, such as basic or digest",
                            "type": "string"
                        },
                        {
                            "name": "realm",
                            "description": "The realm of the challenge. May be empty.",
                            "type": "string"
                        }
                    ]
                },
                {
                    "id": "AuthChallengeResponse",
                    "description": "Response to an AuthChallenge.",
                    "experimental": true,
                    "type": "object",
                    "properties": [
                        {
                            "name": "response",
                            "description": "The decision on what to do in response to the authorization challenge.  Default means\ndeferring to the default behavior of the net stack, which will likely either the Cancel\nauthentication or display a popup dialog box.",
                            "type": "string",
                            "enum": [
                                "Default",
                                "CancelAuth",
                                "ProvideCredentials"
                            ]
                        },
                        {
                            "name": "username",
                            "description": "The username to provide, possibly empty. Should only be set if response is\nProvideCredentials.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "password",
                            "description": "The password to provide, possibly empty. Should only be set if response is\nProvideCredentials.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                }
            ],
            "commands": [
                {
                    "name": "disable",
                    "description": "Disables the fetch domain."
                },
                {
                    "name": "enable",
                    "description": "Enables issuing of requestPaused events. A request will be paused until client\ncalls one of failRequest, fulfillRequest or continueRequest/continueWithAuth.",
                    "parameters": [
                        {
                            "name": "patterns",
                            "description": "If specified, only requests matching any of these patterns will produce\nfetchRequested event and will be paused until clients response. If not set,\nall requests will be affected.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "RequestPattern"
                            }
                        },
                        {
                            "name": "handleAuthRequests",
                            "description": "If true, authRequired events will be issued and requests will be paused\nexpecting a call to continueWithAuth.",
                            "optional": true,
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "failRequest",
                    "description": "Causes the request to fail with specified reason.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "An id the client received in requestPaused event.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "errorReason",
                            "description": "Causes the request to fail with the given reason.",
                            "$ref": "Network.ErrorReason"
                        }
                    ]
                },
                {
                    "name": "fulfillRequest",
                    "description": "Provides response to the request.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "An id the client received in requestPaused event.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "responseCode",
                            "description": "An HTTP response code.",
                            "type": "integer"
                        },
                        {
                            "name": "responseHeaders",
                            "description": "Response headers.",
                            "type": "array",
                            "items": {
                                "$ref": "HeaderEntry"
                            }
                        },
                        {
                            "name": "body",
                            "description": "A response body.",
                            "optional": true,
                            "type": "binary"
                        },
                        {
                            "name": "responsePhrase",
                            "description": "A textual representation of responseCode.\nIf absent, a standard phrase mathcing responseCode is used.",
                            "optional": true,
                            "type": "string"
                        }
                    ]
                },
                {
                    "name": "continueRequest",
                    "description": "Continues the request, optionally modifying some of its parameters.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "An id the client received in requestPaused event.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "url",
                            "description": "If set, the request url will be modified in a way that's not observable by page.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "method",
                            "description": "If set, the request method is overridden.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "postData",
                            "description": "If set, overrides the post data in the request.",
                            "optional": true,
                            "type": "string"
                        },
                        {
                            "name": "headers",
                            "description": "If set, overrides the request headrts.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "HeaderEntry"
                            }
                        }
                    ]
                },
                {
                    "name": "continueWithAuth",
                    "description": "Continues a request supplying authChallengeResponse following authRequired event.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "An id the client received in authRequired event.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "authChallengeResponse",
                            "description": "Response to  with an authChallenge.",
                            "$ref": "AuthChallengeResponse"
                        }
                    ]
                },
                {
                    "name": "getResponseBody",
                    "description": "Causes the body of the response to be received from the server and\nreturned as a single string. May only be issued for a request that\nis paused in the Response stage and is mutually exclusive with\ntakeResponseBodyForInterceptionAsStream. Calling other methods that\naffect the request or disabling fetch domain before body is received\nresults in an undefined behavior.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Identifier for the intercepted request to get body for.",
                            "$ref": "RequestId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "body",
                            "description": "Response body.",
                            "type": "string"
                        },
                        {
                            "name": "base64Encoded",
                            "description": "True, if content was sent as base64.",
                            "type": "boolean"
                        }
                    ]
                },
                {
                    "name": "takeResponseBodyAsStream",
                    "description": "Returns a handle to the stream representing the response body.\nThe request must be paused in the HeadersReceived stage.\nNote that after this command the request can't be continued\nas is -- client either needs to cancel it or to provide the\nresponse body.\nThe stream only supports sequential read, IO.read will fail if the position\nis specified.\nThis method is mutually exclusive with getResponseBody.\nCalling other methods that affect the request or disabling fetch\ndomain before body is received results in an undefined behavior.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "$ref": "RequestId"
                        }
                    ],
                    "returns": [
                        {
                            "name": "stream",
                            "$ref": "IO.StreamHandle"
                        }
                    ]
                }
            ],
            "events": [
                {
                    "name": "requestPaused",
                    "description": "Issued when the domain is enabled and the request URL matches the\nspecified filter. The request is paused until the client responds\nwith one of continueRequest, failRequest or fulfillRequest.\nThe stage of the request can be determined by presence of responseErrorReason\nand responseStatusCode -- the request is at the response stage if either\nof these fields is present and in the request stage otherwise.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Each request the page makes will have a unique id.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "request",
                            "description": "The details of the request.",
                            "$ref": "Network.Request"
                        },
                        {
                            "name": "frameId",
                            "description": "The id of the frame that initiated the request.",
                            "$ref": "Page.FrameId"
                        },
                        {
                            "name": "resourceType",
                            "description": "How the requested resource will be used.",
                            "$ref": "Network.ResourceType"
                        },
                        {
                            "name": "responseErrorReason",
                            "description": "Response error if intercepted at response stage.",
                            "optional": true,
                            "$ref": "Network.ErrorReason"
                        },
                        {
                            "name": "responseStatusCode",
                            "description": "Response code if intercepted at response stage.",
                            "optional": true,
                            "type": "integer"
                        },
                        {
                            "name": "responseHeaders",
                            "description": "Response headers if intercepted at the response stage.",
                            "optional": true,
                            "type": "array",
                            "items": {
                                "$ref": "HeaderEntry"
                            }
                        }
                    ]
                },
                {
                    "name": "authRequired",
                    "description": "Issued when the domain is enabled with handleAuthRequests set to true.\nThe request is paused until client responds with continueWithAuth.",
                    "parameters": [
                        {
                            "name": "requestId",
                            "description": "Each request the page makes will have a unique id.",
                            "$ref": "RequestId"
                        },
                        {
                            "name": "request",
                            "description": "The details of the request.",
                            "$ref": "Network.Request"
                        },
                        {
                            "name": "frameId",
                            "description": "The id of the frame that initiated the request.",
                            "$ref": "Page.FrameId"
                        },
                        {
                            "name": "resourceType",
                            "description": "How the requested resource will be used.",
                            "$ref": "Network.ResourceType"
                        },
                        {
                            "name": "authChallenge",
                            "description": "Details of the Authorization Challenge encountered.\nIf this is set, client should respond with continueRequest that\ncontains AuthChallengeResponse.",
                            "$ref": "AuthChallenge"
                        }
                    ]
                }
            ]
        },
        {
            "commands": [
                {
                    "name": "clearMessages",
                    "description": "Does nothing."
                },
                {
                    "name": "disable",
                    "description": "Disables console domain, prevents further console messages from being reported to the client."
                },
                {
                    "name": "enable",
                    "description": "Enables console domain, sends the messages collected so far to the client by means of the\n`messageAdded` notification."
                }
            ],
            "description": "This domain is deprecated - use Runtime or Log instead.",
            "deprecated": true,
            "domain": "Console",
            "dependencies": [
                "Runtime"
            ],
            "events": [
                {
                    "name": "messageAdded",
                    "parameters": [
                        {
                            "$ref": "ConsoleMessage",
                            "name": "message",
                            "description": "Console message that has been added."
                        }
                    ],
                    "description": "Issued when new console message is added."
                }
            ],
            "types": [
                {
                    "properties": [
                        {
                            "enum": [
                                "xml",
                                "javascript",
                                "network",
                                "console-api",
                                "storage",
                                "appcache",
                                "rendering",
                                "security",
                                "other",
                                "deprecation",
                                "worker"
                            ],
                            "type": "string",
                            "name": "source",
                            "description": "Message source."
                        },
                        {
                            "enum": [
                                "log",
                                "warning",
                                "error",
                                "debug",
                                "info"
                            ],
                            "type": "string",
                            "name": "level",
                            "description": "Message severity."
                        },
                        {
                            "type": "string",
                            "name": "text",
                            "description": "Message text."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "url",
                            "description": "URL of the message origin."
                        },
                        {
                            "type": "integer",
                            "optional": true,
                            "name": "line",
                            "description": "Line number in the resource that generated this message (1-based)."
                        },
                        {
                            "type": "integer",
                            "optional": true,
                            "name": "column",
                            "description": "Column number in the resource that generated this message (1-based)."
                        }
                    ],
                    "type": "object",
                    "id": "ConsoleMessage",
                    "description": "Console message."
                }
            ]
        },
        {
            "commands": [
                {
                    "name": "continueToLocation",
                    "parameters": [
                        {
                            "$ref": "Location",
                            "name": "location",
                            "description": "Location to continue to."
                        },
                        {
                            "type": "string",
                            "enum": [
                                "any",
                                "current"
                            ],
                            "optional": true,
                            "name": "targetCallFrames"
                        }
                    ],
                    "description": "Continues execution until specific location is reached."
                },
                {
                    "name": "disable",
                    "description": "Disables debugger for given page."
                },
                {
                    "returns": [
                        {
                            "$ref": "Runtime.UniqueDebuggerId",
                            "name": "debuggerId",
                            "experimental": true,
                            "description": "Unique identifier of the debugger."
                        }
                    ],
                    "name": "enable",
                    "description": "Enables debugger for the given page. Clients should not assume that the debugging has been\nenabled until the result for this command is received."
                },
                {
                    "returns": [
                        {
                            "$ref": "Runtime.RemoteObject",
                            "name": "result",
                            "description": "Object wrapper for the evaluation result."
                        },
                        {
                            "$ref": "Runtime.ExceptionDetails",
                            "optional": true,
                            "name": "exceptionDetails",
                            "description": "Exception details."
                        }
                    ],
                    "name": "evaluateOnCallFrame",
                    "parameters": [
                        {
                            "$ref": "CallFrameId",
                            "name": "callFrameId",
                            "description": "Call frame identifier to evaluate on."
                        },
                        {
                            "type": "string",
                            "name": "expression",
                            "description": "Expression to evaluate."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "objectGroup",
                            "description": "String object group name to put result into (allows rapid releasing resulting object handles\nusing `releaseObjectGroup`)."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "includeCommandLineAPI",
                            "description": "Specifies whether command line API should be available to the evaluated expression, defaults\nto false."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "silent",
                            "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "returnByValue",
                            "description": "Whether the result is expected to be a JSON object that should be sent by value."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "generatePreview",
                            "experimental": true,
                            "description": "Whether preview should be generated for the result."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "throwOnSideEffect",
                            "description": "Whether to throw an exception if side effect cannot be ruled out during evaluation."
                        },
                        {
                            "$ref": "Runtime.TimeDelta",
                            "optional": true,
                            "name": "timeout",
                            "experimental": true,
                            "description": "Terminate execution after timing out (number of milliseconds)."
                        }
                    ],
                    "description": "Evaluates expression on a given call frame."
                },
                {
                    "returns": [
                        {
                            "items": {
                                "$ref": "BreakLocation"
                            },
                            "type": "array",
                            "name": "locations",
                            "description": "List of the possible breakpoint locations."
                        }
                    ],
                    "name": "getPossibleBreakpoints",
                    "parameters": [
                        {
                            "$ref": "Location",
                            "name": "start",
                            "description": "Start of range to search possible breakpoint locations in."
                        },
                        {
                            "$ref": "Location",
                            "optional": true,
                            "name": "end",
                            "description": "End of range to search possible breakpoint locations in (excluding). When not specified, end\nof scripts is used as end of range."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "restrictToFunction",
                            "description": "Only consider locations which are in the same (non-nested) function as start."
                        }
                    ],
                    "description": "Returns possible locations for breakpoint. scriptId in start and end range locations should be\nthe same."
                },
                {
                    "returns": [
                        {
                            "type": "string",
                            "name": "scriptSource",
                            "description": "Script source."
                        }
                    ],
                    "name": "getScriptSource",
                    "parameters": [
                        {
                            "$ref": "Runtime.ScriptId",
                            "name": "scriptId",
                            "description": "Id of the script to get source for."
                        }
                    ],
                    "description": "Returns source for the script with given id."
                },
                {
                    "returns": [
                        {
                            "name": "stackTrace",
                            "$ref": "Runtime.StackTrace"
                        }
                    ],
                    "parameters": [
                        {
                            "name": "stackTraceId",
                            "$ref": "Runtime.StackTraceId"
                        }
                    ],
                    "name": "getStackTrace",
                    "experimental": true,
                    "description": "Returns stack trace with given `stackTraceId`."
                },
                {
                    "name": "pause",
                    "description": "Stops on the next JavaScript statement."
                },
                {
                    "parameters": [
                        {
                            "$ref": "Runtime.StackTraceId",
                            "name": "parentStackTraceId",
                            "description": "Debugger will pause when async call with given stack trace is started."
                        }
                    ],
                    "name": "pauseOnAsyncCall",
                    "experimental": true
                },
                {
                    "name": "removeBreakpoint",
                    "parameters": [
                        {
                            "name": "breakpointId",
                            "$ref": "BreakpointId"
                        }
                    ],
                    "description": "Removes JavaScript breakpoint."
                },
                {
                    "returns": [
                        {
                            "items": {
                                "$ref": "CallFrame"
                            },
                            "type": "array",
                            "name": "callFrames",
                            "description": "New stack trace."
                        },
                        {
                            "$ref": "Runtime.StackTrace",
                            "optional": true,
                            "name": "asyncStackTrace",
                            "description": "Async stack trace, if any."
                        },
                        {
                            "$ref": "Runtime.StackTraceId",
                            "optional": true,
                            "name": "asyncStackTraceId",
                            "experimental": true,
                            "description": "Async stack trace, if any."
                        }
                    ],
                    "name": "restartFrame",
                    "parameters": [
                        {
                            "$ref": "CallFrameId",
                            "name": "callFrameId",
                            "description": "Call frame identifier to evaluate on."
                        }
                    ],
                    "description": "Restarts particular call frame from the beginning."
                },
                {
                    "name": "resume",
                    "description": "Resumes JavaScript execution."
                },
                {
                    "name": "scheduleStepIntoAsync",
                    "experimental": true,
                    "description": "This method is deprecated - use Debugger.stepInto with breakOnAsyncCall and\nDebugger.pauseOnAsyncTask instead. Steps into next scheduled async task if any is scheduled\nbefore next pause. Returns success when async task is actually scheduled, returns error if no\ntask were scheduled or another scheduleStepIntoAsync was called."
                },
                {
                    "returns": [
                        {
                            "items": {
                                "$ref": "SearchMatch"
                            },
                            "type": "array",
                            "name": "result",
                            "description": "List of search matches."
                        }
                    ],
                    "name": "searchInContent",
                    "parameters": [
                        {
                            "$ref": "Runtime.ScriptId",
                            "name": "scriptId",
                            "description": "Id of the script to search in."
                        },
                        {
                            "type": "string",
                            "name": "query",
                            "description": "String to search for."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "caseSensitive",
                            "description": "If true, search is case sensitive."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "isRegex",
                            "description": "If true, treats string parameter as regex."
                        }
                    ],
                    "description": "Searches for given string in script content."
                },
                {
                    "name": "setAsyncCallStackDepth",
                    "parameters": [
                        {
                            "type": "integer",
                            "name": "maxDepth",
                            "description": "Maximum depth of async call stacks. Setting to `0` will effectively disable collecting async\ncall stacks (default)."
                        }
                    ],
                    "description": "Enables or disables async call stacks tracking."
                },
                {
                    "parameters": [
                        {
                            "items": {
                                "type": "string"
                            },
                            "type": "array",
                            "name": "patterns",
                            "description": "Array of regexps that will be used to check script url for blackbox state."
                        }
                    ],
                    "name": "setBlackboxPatterns",
                    "experimental": true,
                    "description": "Replace previous blackbox patterns with passed ones. Forces backend to skip stepping/pausing in\nscripts with url matching one of the patterns. VM will try to leave blackboxed script by\nperforming 'step in' several times, finally resorting to 'step out' if unsuccessful."
                },
                {
                    "parameters": [
                        {
                            "$ref": "Runtime.ScriptId",
                            "name": "scriptId",
                            "description": "Id of the script."
                        },
                        {
                            "items": {
                                "$ref": "ScriptPosition"
                            },
                            "type": "array",
                            "name": "positions"
                        }
                    ],
                    "name": "setBlackboxedRanges",
                    "experimental": true,
                    "description": "Makes backend skip steps in the script in blackboxed ranges. VM will try leave blacklisted\nscripts by performing 'step in' several times, finally resorting to 'step out' if unsuccessful.\nPositions array contains positions where blackbox state is changed. First interval isn't\nblackboxed. Array should be sorted."
                },
                {
                    "returns": [
                        {
                            "$ref": "BreakpointId",
                            "name": "breakpointId",
                            "description": "Id of the created breakpoint for further reference."
                        },
                        {
                            "$ref": "Location",
                            "name": "actualLocation",
                            "description": "Location this breakpoint resolved into."
                        }
                    ],
                    "name": "setBreakpoint",
                    "parameters": [
                        {
                            "$ref": "Location",
                            "name": "location",
                            "description": "Location to set breakpoint in."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "condition",
                            "description": "Expression to use as a breakpoint condition. When specified, debugger will only stop on the\nbreakpoint if this expression evaluates to true."
                        }
                    ],
                    "description": "Sets JavaScript breakpoint at a given location."
                },
                {
                    "returns": [
                        {
                            "$ref": "BreakpointId",
                            "name": "breakpointId",
                            "description": "Id of the created breakpoint for further reference."
                        },
                        {
                            "items": {
                                "$ref": "Location"
                            },
                            "type": "array",
                            "name": "locations",
                            "description": "List of the locations this breakpoint resolved into upon addition."
                        }
                    ],
                    "name": "setBreakpointByUrl",
                    "parameters": [
                        {
                            "type": "integer",
                            "name": "lineNumber",
                            "description": "Line number to set breakpoint at."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "url",
                            "description": "URL of the resources to set breakpoint on."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "urlRegex",
                            "description": "Regex pattern for the URLs of the resources to set breakpoints on. Either `url` or\n`urlRegex` must be specified."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "scriptHash",
                            "description": "Script hash of the resources to set breakpoint on."
                        },
                        {
                            "type": "integer",
                            "optional": true,
                            "name": "columnNumber",
                            "description": "Offset in the line to set breakpoint at."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "condition",
                            "description": "Expression to use as a breakpoint condition. When specified, debugger will only stop on the\nbreakpoint if this expression evaluates to true."
                        }
                    ],
                    "description": "Sets JavaScript breakpoint at given location specified either by URL or URL regex. Once this\ncommand is issued, all existing parsed scripts will have breakpoints resolved and returned in\n`locations` property. Further matching script parsing will result in subsequent\n`breakpointResolved` events issued. This logical breakpoint will survive page reloads."
                },
                {
                    "returns": [
                        {
                            "$ref": "BreakpointId",
                            "name": "breakpointId",
                            "description": "Id of the created breakpoint for further reference."
                        }
                    ],
                    "parameters": [
                        {
                            "$ref": "Runtime.RemoteObjectId",
                            "name": "objectId",
                            "description": "Function object id."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "condition",
                            "description": "Expression to use as a breakpoint condition. When specified, debugger will\nstop on the breakpoint if this expression evaluates to true."
                        }
                    ],
                    "name": "setBreakpointOnFunctionCall",
                    "experimental": true,
                    "description": "Sets JavaScript breakpoint before each call to the given function.\nIf another function was created from the same source as a given one,\ncalling it will also trigger the breakpoint."
                },
                {
                    "name": "setBreakpointsActive",
                    "parameters": [
                        {
                            "type": "boolean",
                            "name": "active",
                            "description": "New value for breakpoints active state."
                        }
                    ],
                    "description": "Activates / deactivates all breakpoints on the page."
                },
                {
                    "name": "setPauseOnExceptions",
                    "parameters": [
                        {
                            "enum": [
                                "none",
                                "uncaught",
                                "all"
                            ],
                            "type": "string",
                            "name": "state",
                            "description": "Pause on exceptions mode."
                        }
                    ],
                    "description": "Defines pause on exceptions state. Can be set to stop on all exceptions, uncaught exceptions or\nno exceptions. Initial pause on exceptions state is `none`."
                },
                {
                    "parameters": [
                        {
                            "$ref": "Runtime.CallArgument",
                            "name": "newValue",
                            "description": "New return value."
                        }
                    ],
                    "name": "setReturnValue",
                    "experimental": true,
                    "description": "Changes return value in top frame. Available only at return break position."
                },
                {
                    "returns": [
                        {
                            "type": "array",
                            "items": {
                                "$ref": "CallFrame"
                            },
                            "optional": true,
                            "name": "callFrames",
                            "description": "New stack trace in case editing has happened while VM was stopped."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "stackChanged",
                            "description": "Whether current call stack  was modified after applying the changes."
                        },
                        {
                            "$ref": "Runtime.StackTrace",
                            "optional": true,
                            "name": "asyncStackTrace",
                            "description": "Async stack trace, if any."
                        },
                        {
                            "$ref": "Runtime.StackTraceId",
                            "optional": true,
                            "name": "asyncStackTraceId",
                            "experimental": true,
                            "description": "Async stack trace, if any."
                        },
                        {
                            "$ref": "Runtime.ExceptionDetails",
                            "optional": true,
                            "name": "exceptionDetails",
                            "description": "Exception details if any."
                        }
                    ],
                    "name": "setScriptSource",
                    "parameters": [
                        {
                            "$ref": "Runtime.ScriptId",
                            "name": "scriptId",
                            "description": "Id of the script to edit."
                        },
                        {
                            "type": "string",
                            "name": "scriptSource",
                            "description": "New content of the script."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "dryRun",
                            "description": "If true the change will not actually be applied. Dry run may be used to get result\ndescription without actually modifying the code."
                        }
                    ],
                    "description": "Edits JavaScript source live."
                },
                {
                    "name": "setSkipAllPauses",
                    "parameters": [
                        {
                            "type": "boolean",
                            "name": "skip",
                            "description": "New value for skip pauses state."
                        }
                    ],
                    "description": "Makes page not interrupt on any pauses (breakpoint, exception, dom exception etc)."
                },
                {
                    "name": "setVariableValue",
                    "parameters": [
                        {
                            "type": "integer",
                            "name": "scopeNumber",
                            "description": "0-based number of scope as was listed in scope chain. Only 'local', 'closure' and 'catch'\nscope types are allowed. Other scopes could be manipulated manually."
                        },
                        {
                            "type": "string",
                            "name": "variableName",
                            "description": "Variable name."
                        },
                        {
                            "$ref": "Runtime.CallArgument",
                            "name": "newValue",
                            "description": "New variable value."
                        },
                        {
                            "$ref": "CallFrameId",
                            "name": "callFrameId",
                            "description": "Id of callframe that holds variable."
                        }
                    ],
                    "description": "Changes value of variable in a callframe. Object-based scopes are not supported and must be\nmutated manually."
                },
                {
                    "name": "stepInto",
                    "parameters": [
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "breakOnAsyncCall",
                            "experimental": true,
                            "description": "Debugger will issue additional Debugger.paused notification if any async task is scheduled\nbefore next pause."
                        }
                    ],
                    "description": "Steps into the function call."
                },
                {
                    "name": "stepOut",
                    "description": "Steps out of the function call."
                },
                {
                    "name": "stepOver",
                    "description": "Steps over the statement."
                }
            ],
            "description": "Debugger domain exposes JavaScript debugging capabilities. It allows setting and removing\nbreakpoints, stepping through execution, exploring stack traces, etc.",
            "domain": "Debugger",
            "dependencies": [
                "Runtime"
            ],
            "events": [
                {
                    "name": "breakpointResolved",
                    "parameters": [
                        {
                            "$ref": "BreakpointId",
                            "name": "breakpointId",
                            "description": "Breakpoint unique identifier."
                        },
                        {
                            "$ref": "Location",
                            "name": "location",
                            "description": "Actual breakpoint location."
                        }
                    ],
                    "description": "Fired when breakpoint is resolved to an actual script and location."
                },
                {
                    "name": "paused",
                    "parameters": [
                        {
                            "items": {
                                "$ref": "CallFrame"
                            },
                            "type": "array",
                            "name": "callFrames",
                            "description": "Call stack the virtual machine stopped on."
                        },
                        {
                            "enum": [
                                "XHR",
                                "DOM",
                                "EventListener",
                                "exception",
                                "assert",
                                "debugCommand",
                                "promiseRejection",
                                "OOM",
                                "other",
                                "ambiguous"
                            ],
                            "type": "string",
                            "name": "reason",
                            "description": "Pause reason."
                        },
                        {
                            "type": "object",
                            "optional": true,
                            "name": "data",
                            "description": "Object containing break-specific auxiliary properties."
                        },
                        {
                            "type": "array",
                            "items": {
                                "type": "string"
                            },
                            "optional": true,
                            "name": "hitBreakpoints",
                            "description": "Hit breakpoints IDs"
                        },
                        {
                            "$ref": "Runtime.StackTrace",
                            "optional": true,
                            "name": "asyncStackTrace",
                            "description": "Async stack trace, if any."
                        },
                        {
                            "$ref": "Runtime.StackTraceId",
                            "optional": true,
                            "name": "asyncStackTraceId",
                            "experimental": true,
                            "description": "Async stack trace, if any."
                        },
                        {
                            "$ref": "Runtime.StackTraceId",
                            "optional": true,
                            "name": "asyncCallStackTraceId",
                            "experimental": true,
                            "description": "Just scheduled async call will have this stack trace as parent stack during async execution.\nThis field is available only after `Debugger.stepInto` call with `breakOnAsynCall` flag."
                        }
                    ],
                    "description": "Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria."
                },
                {
                    "name": "resumed",
                    "description": "Fired when the virtual machine resumed execution."
                },
                {
                    "name": "scriptFailedToParse",
                    "parameters": [
                        {
                            "$ref": "Runtime.ScriptId",
                            "name": "scriptId",
                            "description": "Identifier of the script parsed."
                        },
                        {
                            "type": "string",
                            "name": "url",
                            "description": "URL or name of the script parsed (if any)."
                        },
                        {
                            "type": "integer",
                            "name": "startLine",
                            "description": "Line offset of the script within the resource with given URL (for script tags)."
                        },
                        {
                            "type": "integer",
                            "name": "startColumn",
                            "description": "Column offset of the script within the resource with given URL."
                        },
                        {
                            "type": "integer",
                            "name": "endLine",
                            "description": "Last line of the script."
                        },
                        {
                            "type": "integer",
                            "name": "endColumn",
                            "description": "Length of the last line of the script."
                        },
                        {
                            "$ref": "Runtime.ExecutionContextId",
                            "name": "executionContextId",
                            "description": "Specifies script creation context."
                        },
                        {
                            "type": "string",
                            "name": "hash",
                            "description": "Content hash of the script."
                        },
                        {
                            "type": "object",
                            "optional": true,
                            "name": "executionContextAuxData",
                            "description": "Embedder-specific auxiliary data."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "sourceMapURL",
                            "description": "URL of source map associated with script (if any)."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "hasSourceURL",
                            "description": "True, if this script has sourceURL."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "isModule",
                            "description": "True, if this script is ES6 module."
                        },
                        {
                            "type": "integer",
                            "optional": true,
                            "name": "length",
                            "description": "This script length."
                        },
                        {
                            "$ref": "Runtime.StackTrace",
                            "optional": true,
                            "name": "stackTrace",
                            "experimental": true,
                            "description": "JavaScript top stack frame of where the script parsed event was triggered if available."
                        }
                    ],
                    "description": "Fired when virtual machine fails to parse the script."
                },
                {
                    "name": "scriptParsed",
                    "parameters": [
                        {
                            "$ref": "Runtime.ScriptId",
                            "name": "scriptId",
                            "description": "Identifier of the script parsed."
                        },
                        {
                            "type": "string",
                            "name": "url",
                            "description": "URL or name of the script parsed (if any)."
                        },
                        {
                            "type": "integer",
                            "name": "startLine",
                            "description": "Line offset of the script within the resource with given URL (for script tags)."
                        },
                        {
                            "type": "integer",
                            "name": "startColumn",
                            "description": "Column offset of the script within the resource with given URL."
                        },
                        {
                            "type": "integer",
                            "name": "endLine",
                            "description": "Last line of the script."
                        },
                        {
                            "type": "integer",
                            "name": "endColumn",
                            "description": "Length of the last line of the script."
                        },
                        {
                            "$ref": "Runtime.ExecutionContextId",
                            "name": "executionContextId",
                            "description": "Specifies script creation context."
                        },
                        {
                            "type": "string",
                            "name": "hash",
                            "description": "Content hash of the script."
                        },
                        {
                            "type": "object",
                            "optional": true,
                            "name": "executionContextAuxData",
                            "description": "Embedder-specific auxiliary data."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "isLiveEdit",
                            "experimental": true,
                            "description": "True, if this script is generated as a result of the live edit operation."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "sourceMapURL",
                            "description": "URL of source map associated with script (if any)."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "hasSourceURL",
                            "description": "True, if this script has sourceURL."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "isModule",
                            "description": "True, if this script is ES6 module."
                        },
                        {
                            "type": "integer",
                            "optional": true,
                            "name": "length",
                            "description": "This script length."
                        },
                        {
                            "$ref": "Runtime.StackTrace",
                            "optional": true,
                            "name": "stackTrace",
                            "experimental": true,
                            "description": "JavaScript top stack frame of where the script parsed event was triggered if available."
                        }
                    ],
                    "description": "Fired when virtual machine parses script. This event is also fired for all known and uncollected\nscripts upon enabling debugger."
                }
            ],
            "types": [
                {
                    "type": "string",
                    "id": "BreakpointId",
                    "description": "Breakpoint identifier."
                },
                {
                    "type": "string",
                    "id": "CallFrameId",
                    "description": "Call frame identifier."
                },
                {
                    "properties": [
                        {
                            "$ref": "Runtime.ScriptId",
                            "name": "scriptId",
                            "description": "Script identifier as reported in the `Debugger.scriptParsed`."
                        },
                        {
                            "type": "integer",
                            "name": "lineNumber",
                            "description": "Line number in the script (0-based)."
                        },
                        {
                            "type": "integer",
                            "optional": true,
                            "name": "columnNumber",
                            "description": "Column number in the script (0-based)."
                        }
                    ],
                    "type": "object",
                    "id": "Location",
                    "description": "Location in the source code."
                },
                {
                    "properties": [
                        {
                            "type": "integer",
                            "name": "lineNumber"
                        },
                        {
                            "type": "integer",
                            "name": "columnNumber"
                        }
                    ],
                    "type": "object",
                    "id": "ScriptPosition",
                    "experimental": true,
                    "description": "Location in the source code."
                },
                {
                    "properties": [
                        {
                            "$ref": "CallFrameId",
                            "name": "callFrameId",
                            "description": "Call frame identifier. This identifier is only valid while the virtual machine is paused."
                        },
                        {
                            "type": "string",
                            "name": "functionName",
                            "description": "Name of the JavaScript function called on this call frame."
                        },
                        {
                            "$ref": "Location",
                            "optional": true,
                            "name": "functionLocation",
                            "description": "Location in the source code."
                        },
                        {
                            "$ref": "Location",
                            "name": "location",
                            "description": "Location in the source code."
                        },
                        {
                            "type": "string",
                            "name": "url",
                            "description": "JavaScript script name or url."
                        },
                        {
                            "items": {
                                "$ref": "Scope"
                            },
                            "type": "array",
                            "name": "scopeChain",
                            "description": "Scope chain for this call frame."
                        },
                        {
                            "$ref": "Runtime.RemoteObject",
                            "name": "this",
                            "description": "`this` object for this call frame."
                        },
                        {
                            "$ref": "Runtime.RemoteObject",
                            "optional": true,
                            "name": "returnValue",
                            "description": "The value being returned, if the function is at return point."
                        }
                    ],
                    "type": "object",
                    "id": "CallFrame",
                    "description": "JavaScript call frame. Array of call frames form the call stack."
                },
                {
                    "properties": [
                        {
                            "enum": [
                                "global",
                                "local",
                                "with",
                                "closure",
                                "catch",
                                "block",
                                "script",
                                "eval",
                                "module"
                            ],
                            "type": "string",
                            "name": "type",
                            "description": "Scope type."
                        },
                        {
                            "$ref": "Runtime.RemoteObject",
                            "name": "object",
                            "description": "Object representing the scope. For `global` and `with` scopes it represents the actual\nobject; for the rest of the scopes, it is artificial transient object enumerating scope\nvariables as its properties."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "name"
                        },
                        {
                            "$ref": "Location",
                            "optional": true,
                            "name": "startLocation",
                            "description": "Location in the source code where scope starts"
                        },
                        {
                            "$ref": "Location",
                            "optional": true,
                            "name": "endLocation",
                            "description": "Location in the source code where scope ends"
                        }
                    ],
                    "type": "object",
                    "id": "Scope",
                    "description": "Scope description."
                },
                {
                    "properties": [
                        {
                            "type": "number",
                            "name": "lineNumber",
                            "description": "Line number in resource content."
                        },
                        {
                            "type": "string",
                            "name": "lineContent",
                            "description": "Line with match content."
                        }
                    ],
                    "type": "object",
                    "id": "SearchMatch",
                    "description": "Search match for resource."
                },
                {
                    "type": "object",
                    "id": "BreakLocation",
                    "properties": [
                        {
                            "$ref": "Runtime.ScriptId",
                            "name": "scriptId",
                            "description": "Script identifier as reported in the `Debugger.scriptParsed`."
                        },
                        {
                            "type": "integer",
                            "name": "lineNumber",
                            "description": "Line number in the script (0-based)."
                        },
                        {
                            "type": "integer",
                            "optional": true,
                            "name": "columnNumber",
                            "description": "Column number in the script (0-based)."
                        },
                        {
                            "type": "string",
                            "enum": [
                                "debuggerStatement",
                                "call",
                                "return"
                            ],
                            "optional": true,
                            "name": "type"
                        }
                    ]
                }
            ]
        },
        {
            "commands": [
                {
                    "name": "addInspectedHeapObject",
                    "parameters": [
                        {
                            "$ref": "HeapSnapshotObjectId",
                            "name": "heapObjectId",
                            "description": "Heap snapshot object id to be accessible by means of $x command line API."
                        }
                    ],
                    "description": "Enables console to refer to the node with given id via $x (see Command Line API for more details\n$x functions)."
                },
                {
                    "name": "collectGarbage"
                },
                {
                    "name": "disable"
                },
                {
                    "name": "enable"
                },
                {
                    "returns": [
                        {
                            "$ref": "HeapSnapshotObjectId",
                            "name": "heapSnapshotObjectId",
                            "description": "Id of the heap snapshot object corresponding to the passed remote object id."
                        }
                    ],
                    "name": "getHeapObjectId",
                    "parameters": [
                        {
                            "$ref": "Runtime.RemoteObjectId",
                            "name": "objectId",
                            "description": "Identifier of the object to get heap object id for."
                        }
                    ]
                },
                {
                    "returns": [
                        {
                            "$ref": "Runtime.RemoteObject",
                            "name": "result",
                            "description": "Evaluation result."
                        }
                    ],
                    "name": "getObjectByHeapObjectId",
                    "parameters": [
                        {
                            "name": "objectId",
                            "$ref": "HeapSnapshotObjectId"
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "objectGroup",
                            "description": "Symbolic group name that can be used to release multiple objects."
                        }
                    ]
                },
                {
                    "returns": [
                        {
                            "$ref": "SamplingHeapProfile",
                            "name": "profile",
                            "description": "Return the sampling profile being collected."
                        }
                    ],
                    "name": "getSamplingProfile"
                },
                {
                    "name": "startSampling",
                    "parameters": [
                        {
                            "type": "number",
                            "optional": true,
                            "name": "samplingInterval",
                            "description": "Average sample interval in bytes. Poisson distribution is used for the intervals. The\ndefault value is 32768 bytes."
                        }
                    ]
                },
                {
                    "name": "startTrackingHeapObjects",
                    "parameters": [
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "trackAllocations"
                        }
                    ]
                },
                {
                    "returns": [
                        {
                            "$ref": "SamplingHeapProfile",
                            "name": "profile",
                            "description": "Recorded sampling heap profile."
                        }
                    ],
                    "name": "stopSampling"
                },
                {
                    "name": "stopTrackingHeapObjects",
                    "parameters": [
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "reportProgress",
                            "description": "If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken\nwhen the tracking is stopped."
                        }
                    ]
                },
                {
                    "name": "takeHeapSnapshot",
                    "parameters": [
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "reportProgress",
                            "description": "If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken."
                        }
                    ]
                }
            ],
            "domain": "HeapProfiler",
            "dependencies": [
                "Runtime"
            ],
            "experimental": true,
            "events": [
                {
                    "name": "addHeapSnapshotChunk",
                    "parameters": [
                        {
                            "type": "string",
                            "name": "chunk"
                        }
                    ]
                },
                {
                    "name": "heapStatsUpdate",
                    "parameters": [
                        {
                            "items": {
                                "type": "integer"
                            },
                            "type": "array",
                            "name": "statsUpdate",
                            "description": "An array of triplets. Each triplet describes a fragment. The first integer is the fragment\nindex, the second integer is a total count of objects for the fragment, the third integer is\na total size of the objects for the fragment."
                        }
                    ],
                    "description": "If heap objects tracking has been started then backend may send update for one or more fragments"
                },
                {
                    "name": "lastSeenObjectId",
                    "parameters": [
                        {
                            "type": "integer",
                            "name": "lastSeenObjectId"
                        },
                        {
                            "type": "number",
                            "name": "timestamp"
                        }
                    ],
                    "description": "If heap objects tracking has been started then backend regularly sends a current value for last\nseen object id and corresponding timestamp. If the were changes in the heap since last event\nthen one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event."
                },
                {
                    "name": "reportHeapSnapshotProgress",
                    "parameters": [
                        {
                            "type": "integer",
                            "name": "done"
                        },
                        {
                            "type": "integer",
                            "name": "total"
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "finished"
                        }
                    ]
                },
                {
                    "name": "resetProfiles"
                }
            ],
            "types": [
                {
                    "type": "string",
                    "id": "HeapSnapshotObjectId",
                    "description": "Heap snapshot object id."
                },
                {
                    "properties": [
                        {
                            "$ref": "Runtime.CallFrame",
                            "name": "callFrame",
                            "description": "Function location."
                        },
                        {
                            "type": "number",
                            "name": "selfSize",
                            "description": "Allocations size in bytes for the node excluding children."
                        },
                        {
                            "type": "integer",
                            "name": "id",
                            "description": "Node id. Ids are unique across all profiles collected between startSampling and stopSampling."
                        },
                        {
                            "items": {
                                "$ref": "SamplingHeapProfileNode"
                            },
                            "type": "array",
                            "name": "children",
                            "description": "Child nodes."
                        }
                    ],
                    "type": "object",
                    "id": "SamplingHeapProfileNode",
                    "description": "Sampling Heap Profile node. Holds callsite information, allocation statistics and child nodes."
                },
                {
                    "properties": [
                        {
                            "type": "number",
                            "name": "size",
                            "description": "Allocation size in bytes attributed to the sample."
                        },
                        {
                            "type": "integer",
                            "name": "nodeId",
                            "description": "Id of the corresponding profile tree node."
                        },
                        {
                            "type": "number",
                            "name": "ordinal",
                            "description": "Time-ordered sample ordinal number. It is unique across all profiles retrieved\nbetween startSampling and stopSampling."
                        }
                    ],
                    "type": "object",
                    "id": "SamplingHeapProfileSample",
                    "description": "A single sample from a sampling profile."
                },
                {
                    "properties": [
                        {
                            "name": "head",
                            "$ref": "SamplingHeapProfileNode"
                        },
                        {
                            "items": {
                                "$ref": "SamplingHeapProfileSample"
                            },
                            "type": "array",
                            "name": "samples"
                        }
                    ],
                    "type": "object",
                    "id": "SamplingHeapProfile",
                    "description": "Sampling profile."
                }
            ]
        },
        {
            "domain": "Profiler",
            "dependencies": [
                "Runtime",
                "Debugger"
            ],
            "commands": [
                {
                    "name": "disable"
                },
                {
                    "name": "enable"
                },
                {
                    "returns": [
                        {
                            "items": {
                                "$ref": "ScriptCoverage"
                            },
                            "type": "array",
                            "name": "result",
                            "description": "Coverage data for the current isolate."
                        }
                    ],
                    "name": "getBestEffortCoverage",
                    "description": "Collect coverage data for the current isolate. The coverage data may be incomplete due to\ngarbage collection."
                },
                {
                    "name": "setSamplingInterval",
                    "parameters": [
                        {
                            "type": "integer",
                            "name": "interval",
                            "description": "New sampling interval in microseconds."
                        }
                    ],
                    "description": "Changes CPU profiler sampling interval. Must be called before CPU profiles recording started."
                },
                {
                    "name": "start"
                },
                {
                    "name": "startPreciseCoverage",
                    "parameters": [
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "callCount",
                            "description": "Collect accurate call counts beyond simple 'covered' or 'not covered'."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "detailed",
                            "description": "Collect block-based coverage."
                        }
                    ],
                    "description": "Enable precise code coverage. Coverage data for JavaScript executed before enabling precise code\ncoverage may be incomplete. Enabling prevents running optimized code and resets execution\ncounters."
                },
                {
                    "name": "startTypeProfile",
                    "experimental": true,
                    "description": "Enable type profile."
                },
                {
                    "returns": [
                        {
                            "$ref": "Profile",
                            "name": "profile",
                            "description": "Recorded profile."
                        }
                    ],
                    "name": "stop"
                },
                {
                    "name": "stopPreciseCoverage",
                    "description": "Disable precise code coverage. Disabling releases unnecessary execution count records and allows\nexecuting optimized code."
                },
                {
                    "name": "stopTypeProfile",
                    "experimental": true,
                    "description": "Disable type profile. Disabling releases type profile data collected so far."
                },
                {
                    "returns": [
                        {
                            "items": {
                                "$ref": "ScriptCoverage"
                            },
                            "type": "array",
                            "name": "result",
                            "description": "Coverage data for the current isolate."
                        }
                    ],
                    "name": "takePreciseCoverage",
                    "description": "Collect coverage data for the current isolate, and resets execution counters. Precise code\ncoverage needs to have started."
                },
                {
                    "returns": [
                        {
                            "items": {
                                "$ref": "ScriptTypeProfile"
                            },
                            "type": "array",
                            "name": "result",
                            "description": "Type profile for all scripts since startTypeProfile() was turned on."
                        }
                    ],
                    "name": "takeTypeProfile",
                    "experimental": true,
                    "description": "Collect type profile."
                }
            ],
            "types": [
                {
                    "properties": [
                        {
                            "type": "integer",
                            "name": "id",
                            "description": "Unique id of the node."
                        },
                        {
                            "$ref": "Runtime.CallFrame",
                            "name": "callFrame",
                            "description": "Function location."
                        },
                        {
                            "type": "integer",
                            "optional": true,
                            "name": "hitCount",
                            "description": "Number of samples where this node was on top of the call stack."
                        },
                        {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "optional": true,
                            "name": "children",
                            "description": "Child node ids."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "deoptReason",
                            "description": "The reason of being not optimized. The function may be deoptimized or marked as don't\noptimize."
                        },
                        {
                            "type": "array",
                            "items": {
                                "$ref": "PositionTickInfo"
                            },
                            "optional": true,
                            "name": "positionTicks",
                            "description": "An array of source position ticks."
                        }
                    ],
                    "type": "object",
                    "id": "ProfileNode",
                    "description": "Profile node. Holds callsite information, execution statistics and child nodes."
                },
                {
                    "properties": [
                        {
                            "items": {
                                "$ref": "ProfileNode"
                            },
                            "type": "array",
                            "name": "nodes",
                            "description": "The list of profile nodes. First item is the root node."
                        },
                        {
                            "type": "number",
                            "name": "startTime",
                            "description": "Profiling start timestamp in microseconds."
                        },
                        {
                            "type": "number",
                            "name": "endTime",
                            "description": "Profiling end timestamp in microseconds."
                        },
                        {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "optional": true,
                            "name": "samples",
                            "description": "Ids of samples top nodes."
                        },
                        {
                            "type": "array",
                            "items": {
                                "type": "integer"
                            },
                            "optional": true,
                            "name": "timeDeltas",
                            "description": "Time intervals between adjacent samples in microseconds. The first delta is relative to the\nprofile startTime."
                        }
                    ],
                    "type": "object",
                    "id": "Profile",
                    "description": "Profile."
                },
                {
                    "properties": [
                        {
                            "type": "integer",
                            "name": "line",
                            "description": "Source line number (1-based)."
                        },
                        {
                            "type": "integer",
                            "name": "ticks",
                            "description": "Number of samples attributed to the source line."
                        }
                    ],
                    "type": "object",
                    "id": "PositionTickInfo",
                    "description": "Specifies a number of samples attributed to a certain source position."
                },
                {
                    "properties": [
                        {
                            "type": "integer",
                            "name": "startOffset",
                            "description": "JavaScript script source offset for the range start."
                        },
                        {
                            "type": "integer",
                            "name": "endOffset",
                            "description": "JavaScript script source offset for the range end."
                        },
                        {
                            "type": "integer",
                            "name": "count",
                            "description": "Collected execution count of the source range."
                        }
                    ],
                    "type": "object",
                    "id": "CoverageRange",
                    "description": "Coverage data for a source range."
                },
                {
                    "properties": [
                        {
                            "type": "string",
                            "name": "functionName",
                            "description": "JavaScript function name."
                        },
                        {
                            "items": {
                                "$ref": "CoverageRange"
                            },
                            "type": "array",
                            "name": "ranges",
                            "description": "Source ranges inside the function with coverage data."
                        },
                        {
                            "type": "boolean",
                            "name": "isBlockCoverage",
                            "description": "Whether coverage data for this function has block granularity."
                        }
                    ],
                    "type": "object",
                    "id": "FunctionCoverage",
                    "description": "Coverage data for a JavaScript function."
                },
                {
                    "properties": [
                        {
                            "$ref": "Runtime.ScriptId",
                            "name": "scriptId",
                            "description": "JavaScript script id."
                        },
                        {
                            "type": "string",
                            "name": "url",
                            "description": "JavaScript script name or url."
                        },
                        {
                            "items": {
                                "$ref": "FunctionCoverage"
                            },
                            "type": "array",
                            "name": "functions",
                            "description": "Functions contained in the script that has coverage data."
                        }
                    ],
                    "type": "object",
                    "id": "ScriptCoverage",
                    "description": "Coverage data for a JavaScript script."
                },
                {
                    "properties": [
                        {
                            "type": "string",
                            "name": "name",
                            "description": "Name of a type collected with type profiling."
                        }
                    ],
                    "type": "object",
                    "id": "TypeObject",
                    "experimental": true,
                    "description": "Describes a type collected during runtime."
                },
                {
                    "properties": [
                        {
                            "type": "integer",
                            "name": "offset",
                            "description": "Source offset of the parameter or end of function for return values."
                        },
                        {
                            "items": {
                                "$ref": "TypeObject"
                            },
                            "type": "array",
                            "name": "types",
                            "description": "The types for this parameter or return value."
                        }
                    ],
                    "type": "object",
                    "id": "TypeProfileEntry",
                    "experimental": true,
                    "description": "Source offset and types for a parameter or return value."
                },
                {
                    "properties": [
                        {
                            "$ref": "Runtime.ScriptId",
                            "name": "scriptId",
                            "description": "JavaScript script id."
                        },
                        {
                            "type": "string",
                            "name": "url",
                            "description": "JavaScript script name or url."
                        },
                        {
                            "items": {
                                "$ref": "TypeProfileEntry"
                            },
                            "type": "array",
                            "name": "entries",
                            "description": "Type profile entries for parameters and return values of the functions in the script."
                        }
                    ],
                    "type": "object",
                    "id": "ScriptTypeProfile",
                    "experimental": true,
                    "description": "Type profile data collected during runtime for a JavaScript script."
                }
            ],
            "events": [
                {
                    "name": "consoleProfileFinished",
                    "parameters": [
                        {
                            "type": "string",
                            "name": "id"
                        },
                        {
                            "$ref": "Debugger.Location",
                            "name": "location",
                            "description": "Location of console.profileEnd()."
                        },
                        {
                            "name": "profile",
                            "$ref": "Profile"
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "title",
                            "description": "Profile title passed as an argument to console.profile()."
                        }
                    ]
                },
                {
                    "name": "consoleProfileStarted",
                    "parameters": [
                        {
                            "type": "string",
                            "name": "id"
                        },
                        {
                            "$ref": "Debugger.Location",
                            "name": "location",
                            "description": "Location of console.profile()."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "title",
                            "description": "Profile title passed as an argument to console.profile()."
                        }
                    ],
                    "description": "Sent when new profile recording is started using console.profile() call."
                }
            ]
        },
        {
            "commands": [
                {
                    "returns": [
                        {
                            "$ref": "RemoteObject",
                            "name": "result",
                            "description": "Promise result. Will contain rejected value if promise was rejected."
                        },
                        {
                            "$ref": "ExceptionDetails",
                            "optional": true,
                            "name": "exceptionDetails",
                            "description": "Exception details if stack strace is available."
                        }
                    ],
                    "name": "awaitPromise",
                    "parameters": [
                        {
                            "$ref": "RemoteObjectId",
                            "name": "promiseObjectId",
                            "description": "Identifier of the promise."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "returnByValue",
                            "description": "Whether the result is expected to be a JSON object that should be sent by value."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "generatePreview",
                            "description": "Whether preview should be generated for the result."
                        }
                    ],
                    "description": "Add handler to promise with given promise object id."
                },
                {
                    "returns": [
                        {
                            "$ref": "RemoteObject",
                            "name": "result",
                            "description": "Call result."
                        },
                        {
                            "$ref": "ExceptionDetails",
                            "optional": true,
                            "name": "exceptionDetails",
                            "description": "Exception details."
                        }
                    ],
                    "name": "callFunctionOn",
                    "parameters": [
                        {
                            "type": "string",
                            "name": "functionDeclaration",
                            "description": "Declaration of the function to call."
                        },
                        {
                            "$ref": "RemoteObjectId",
                            "optional": true,
                            "name": "objectId",
                            "description": "Identifier of the object to call function on. Either objectId or executionContextId should\nbe specified."
                        },
                        {
                            "type": "array",
                            "items": {
                                "$ref": "CallArgument"
                            },
                            "optional": true,
                            "name": "arguments",
                            "description": "Call arguments. All call arguments must belong to the same JavaScript world as the target\nobject."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "silent",
                            "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "returnByValue",
                            "description": "Whether the result is expected to be a JSON object which should be sent by value."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "generatePreview",
                            "experimental": true,
                            "description": "Whether preview should be generated for the result."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "userGesture",
                            "description": "Whether execution should be treated as initiated by user in the UI."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "awaitPromise",
                            "description": "Whether execution should `await` for resulting value and return once awaited promise is\nresolved."
                        },
                        {
                            "$ref": "ExecutionContextId",
                            "optional": true,
                            "name": "executionContextId",
                            "description": "Specifies execution context which global object will be used to call function on. Either\nexecutionContextId or objectId should be specified."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "objectGroup",
                            "description": "Symbolic group name that can be used to release multiple objects. If objectGroup is not\nspecified and objectId is, objectGroup will be inherited from object."
                        }
                    ],
                    "description": "Calls function with given declaration on the given object. Object group of the result is\ninherited from the target object."
                },
                {
                    "returns": [
                        {
                            "$ref": "ScriptId",
                            "optional": true,
                            "name": "scriptId",
                            "description": "Id of the script."
                        },
                        {
                            "$ref": "ExceptionDetails",
                            "optional": true,
                            "name": "exceptionDetails",
                            "description": "Exception details."
                        }
                    ],
                    "name": "compileScript",
                    "parameters": [
                        {
                            "type": "string",
                            "name": "expression",
                            "description": "Expression to compile."
                        },
                        {
                            "type": "string",
                            "name": "sourceURL",
                            "description": "Source url to be set for the script."
                        },
                        {
                            "type": "boolean",
                            "name": "persistScript",
                            "description": "Specifies whether the compiled script should be persisted."
                        },
                        {
                            "$ref": "ExecutionContextId",
                            "optional": true,
                            "name": "executionContextId",
                            "description": "Specifies in which execution context to perform script run. If the parameter is omitted the\nevaluation will be performed in the context of the inspected page."
                        }
                    ],
                    "description": "Compiles expression."
                },
                {
                    "name": "disable",
                    "description": "Disables reporting of execution contexts creation."
                },
                {
                    "name": "discardConsoleEntries",
                    "description": "Discards collected exceptions and console API calls."
                },
                {
                    "name": "enable",
                    "description": "Enables reporting of execution contexts creation by means of `executionContextCreated` event.\nWhen the reporting gets enabled the event will be sent immediately for each existing execution\ncontext."
                },
                {
                    "returns": [
                        {
                            "$ref": "RemoteObject",
                            "name": "result",
                            "description": "Evaluation result."
                        },
                        {
                            "$ref": "ExceptionDetails",
                            "optional": true,
                            "name": "exceptionDetails",
                            "description": "Exception details."
                        }
                    ],
                    "name": "evaluate",
                    "parameters": [
                        {
                            "type": "string",
                            "name": "expression",
                            "description": "Expression to evaluate."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "objectGroup",
                            "description": "Symbolic group name that can be used to release multiple objects."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "includeCommandLineAPI",
                            "description": "Determines whether Command Line API should be available during the evaluation."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "silent",
                            "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state."
                        },
                        {
                            "$ref": "ExecutionContextId",
                            "optional": true,
                            "name": "contextId",
                            "description": "Specifies in which execution context to perform evaluation. If the parameter is omitted the\nevaluation will be performed in the context of the inspected page."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "returnByValue",
                            "description": "Whether the result is expected to be a JSON object that should be sent by value."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "generatePreview",
                            "experimental": true,
                            "description": "Whether preview should be generated for the result."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "userGesture",
                            "description": "Whether execution should be treated as initiated by user in the UI."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "awaitPromise",
                            "description": "Whether execution should `await` for resulting value and return once awaited promise is\nresolved."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "throwOnSideEffect",
                            "experimental": true,
                            "description": "Whether to throw an exception if side effect cannot be ruled out during evaluation."
                        },
                        {
                            "$ref": "TimeDelta",
                            "optional": true,
                            "name": "timeout",
                            "experimental": true,
                            "description": "Terminate execution after timing out (number of milliseconds)."
                        }
                    ],
                    "description": "Evaluates expression on global object."
                },
                {
                    "returns": [
                        {
                            "type": "string",
                            "name": "id",
                            "description": "The isolate id."
                        }
                    ],
                    "name": "getIsolateId",
                    "experimental": true,
                    "description": "Returns the isolate id."
                },
                {
                    "returns": [
                        {
                            "type": "number",
                            "name": "usedSize",
                            "description": "Used heap size in bytes."
                        },
                        {
                            "type": "number",
                            "name": "totalSize",
                            "description": "Allocated heap size in bytes."
                        }
                    ],
                    "name": "getHeapUsage",
                    "experimental": true,
                    "description": "Returns the JavaScript heap usage.\nIt is the total usage of the corresponding isolate not scoped to a particular Runtime."
                },
                {
                    "returns": [
                        {
                            "items": {
                                "$ref": "PropertyDescriptor"
                            },
                            "type": "array",
                            "name": "result",
                            "description": "Object properties."
                        },
                        {
                            "type": "array",
                            "items": {
                                "$ref": "InternalPropertyDescriptor"
                            },
                            "optional": true,
                            "name": "internalProperties",
                            "description": "Internal object properties (only of the element itself)."
                        },
                        {
                            "$ref": "ExceptionDetails",
                            "optional": true,
                            "name": "exceptionDetails",
                            "description": "Exception details."
                        }
                    ],
                    "name": "getProperties",
                    "parameters": [
                        {
                            "$ref": "RemoteObjectId",
                            "name": "objectId",
                            "description": "Identifier of the object to return properties for."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "ownProperties",
                            "description": "If true, returns properties belonging only to the element itself, not to its prototype\nchain."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "accessorPropertiesOnly",
                            "experimental": true,
                            "description": "If true, returns accessor properties (with getter/setter) only; internal properties are not\nreturned either."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "generatePreview",
                            "experimental": true,
                            "description": "Whether preview should be generated for the results."
                        }
                    ],
                    "description": "Returns properties of a given object. Object group of the result is inherited from the target\nobject."
                },
                {
                    "returns": [
                        {
                            "items": {
                                "type": "string"
                            },
                            "type": "array",
                            "name": "names"
                        }
                    ],
                    "name": "globalLexicalScopeNames",
                    "parameters": [
                        {
                            "$ref": "ExecutionContextId",
                            "optional": true,
                            "name": "executionContextId",
                            "description": "Specifies in which execution context to lookup global scope variables."
                        }
                    ],
                    "description": "Returns all let, const and class variables from global scope."
                },
                {
                    "returns": [
                        {
                            "$ref": "RemoteObject",
                            "name": "objects",
                            "description": "Array with objects."
                        }
                    ],
                    "name": "queryObjects",
                    "parameters": [
                        {
                            "$ref": "RemoteObjectId",
                            "name": "prototypeObjectId",
                            "description": "Identifier of the prototype to return objects for."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "objectGroup",
                            "description": "Symbolic group name that can be used to release the results."
                        }
                    ]
                },
                {
                    "name": "releaseObject",
                    "parameters": [
                        {
                            "$ref": "RemoteObjectId",
                            "name": "objectId",
                            "description": "Identifier of the object to release."
                        }
                    ],
                    "description": "Releases remote object with given id."
                },
                {
                    "name": "releaseObjectGroup",
                    "parameters": [
                        {
                            "type": "string",
                            "name": "objectGroup",
                            "description": "Symbolic object group name."
                        }
                    ],
                    "description": "Releases all remote objects that belong to a given group."
                },
                {
                    "name": "runIfWaitingForDebugger",
                    "description": "Tells inspected instance to run if it was waiting for debugger to attach."
                },
                {
                    "returns": [
                        {
                            "$ref": "RemoteObject",
                            "name": "result",
                            "description": "Run result."
                        },
                        {
                            "$ref": "ExceptionDetails",
                            "optional": true,
                            "name": "exceptionDetails",
                            "description": "Exception details."
                        }
                    ],
                    "name": "runScript",
                    "parameters": [
                        {
                            "$ref": "ScriptId",
                            "name": "scriptId",
                            "description": "Id of the script to run."
                        },
                        {
                            "$ref": "ExecutionContextId",
                            "optional": true,
                            "name": "executionContextId",
                            "description": "Specifies in which execution context to perform script run. If the parameter is omitted the\nevaluation will be performed in the context of the inspected page."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "objectGroup",
                            "description": "Symbolic group name that can be used to release multiple objects."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "silent",
                            "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "includeCommandLineAPI",
                            "description": "Determines whether Command Line API should be available during the evaluation."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "returnByValue",
                            "description": "Whether the result is expected to be a JSON object which should be sent by value."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "generatePreview",
                            "description": "Whether preview should be generated for the result."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "awaitPromise",
                            "description": "Whether execution should `await` for resulting value and return once awaited promise is\nresolved."
                        }
                    ],
                    "description": "Runs script with given id in a given context."
                },
                {
                    "redirect": "Debugger",
                    "name": "setAsyncCallStackDepth",
                    "parameters": [
                        {
                            "type": "integer",
                            "name": "maxDepth",
                            "description": "Maximum depth of async call stacks. Setting to `0` will effectively disable collecting async\ncall stacks (default)."
                        }
                    ],
                    "description": "Enables or disables async call stacks tracking."
                },
                {
                    "parameters": [
                        {
                            "type": "boolean",
                            "name": "enabled"
                        }
                    ],
                    "name": "setCustomObjectFormatterEnabled",
                    "experimental": true
                },
                {
                    "parameters": [
                        {
                            "type": "integer",
                            "name": "size"
                        }
                    ],
                    "name": "setMaxCallStackSizeToCapture",
                    "experimental": true
                },
                {
                    "name": "terminateExecution",
                    "experimental": true,
                    "description": "Terminate current or next JavaScript execution.\nWill cancel the termination when the outer-most script execution ends."
                },
                {
                    "parameters": [
                        {
                            "type": "string",
                            "name": "name"
                        },
                        {
                            "optional": true,
                            "name": "executionContextId",
                            "$ref": "ExecutionContextId"
                        }
                    ],
                    "name": "addBinding",
                    "experimental": true,
                    "description": "If executionContextId is empty, adds binding with the given name on the\nglobal objects of all inspected contexts, including those created later,\nbindings survive reloads.\nIf executionContextId is specified, adds binding only on global object of\ngiven execution context.\nBinding function takes exactly one argument, this argument should be string,\nin case of any other input, function throws an exception.\nEach binding function call produces Runtime.bindingCalled notification."
                },
                {
                    "parameters": [
                        {
                            "type": "string",
                            "name": "name"
                        }
                    ],
                    "name": "removeBinding",
                    "experimental": true,
                    "description": "This method does not remove binding function from global object but\nunsubscribes current runtime agent from Runtime.bindingCalled notifications."
                }
            ],
            "domain": "Runtime",
            "description": "Runtime domain exposes JavaScript runtime by means of remote evaluation and mirror objects.\nEvaluation results are returned as mirror object that expose object type, string representation\nand unique identifier that can be used for further object reference. Original objects are\nmaintained in memory unless they are either explicitly released or are released along with the\nother objects in their object group.",
            "types": [
                {
                    "type": "string",
                    "id": "ScriptId",
                    "description": "Unique script identifier."
                },
                {
                    "type": "string",
                    "id": "RemoteObjectId",
                    "description": "Unique object identifier."
                },
                {
                    "type": "string",
                    "id": "UnserializableValue",
                    "description": "Primitive value which cannot be JSON-stringified. Includes values `-0`, `NaN`, `Infinity`,\n`-Infinity`, and bigint literals."
                },
                {
                    "properties": [
                        {
                            "enum": [
                                "object",
                                "function",
                                "undefined",
                                "string",
                                "number",
                                "boolean",
                                "symbol",
                                "bigint"
                            ],
                            "type": "string",
                            "name": "type",
                            "description": "Object type."
                        },
                        {
                            "type": "string",
                            "enum": [
                                "array",
                                "null",
                                "node",
                                "regexp",
                                "date",
                                "map",
                                "set",
                                "weakmap",
                                "weakset",
                                "iterator",
                                "generator",
                                "error",
                                "proxy",
                                "promise",
                                "typedarray",
                                "arraybuffer",
                                "dataview"
                            ],
                            "optional": true,
                            "name": "subtype",
                            "description": "Object subtype hint. Specified for `object` type values only."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "className",
                            "description": "Object class (constructor) name. Specified for `object` type values only."
                        },
                        {
                            "type": "any",
                            "optional": true,
                            "name": "value",
                            "description": "Remote object value in case of primitive values or JSON values (if it was requested)."
                        },
                        {
                            "$ref": "UnserializableValue",
                            "optional": true,
                            "name": "unserializableValue",
                            "description": "Primitive value which can not be JSON-stringified does not have `value`, but gets this\nproperty."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "description",
                            "description": "String representation of the object."
                        },
                        {
                            "$ref": "RemoteObjectId",
                            "optional": true,
                            "name": "objectId",
                            "description": "Unique object identifier (for non-primitive values)."
                        },
                        {
                            "$ref": "ObjectPreview",
                            "optional": true,
                            "name": "preview",
                            "experimental": true,
                            "description": "Preview containing abbreviated property values. Specified for `object` type values only."
                        },
                        {
                            "optional": true,
                            "name": "customPreview",
                            "experimental": true,
                            "$ref": "CustomPreview"
                        }
                    ],
                    "type": "object",
                    "id": "RemoteObject",
                    "description": "Mirror object referencing original JavaScript object."
                },
                {
                    "type": "object",
                    "id": "CustomPreview",
                    "experimental": true,
                    "properties": [
                        {
                            "type": "string",
                            "name": "header",
                            "description": "The JSON-stringified result of formatter.header(object, config) call.\nIt contains json ML array that represents RemoteObject."
                        },
                        {
                            "$ref": "RemoteObjectId",
                            "optional": true,
                            "name": "bodyGetterId",
                            "description": "If formatter returns true as a result of formatter.hasBody call then bodyGetterId will\ncontain RemoteObjectId for the function that returns result of formatter.body(object, config) call.\nThe result value is json ML array."
                        }
                    ]
                },
                {
                    "properties": [
                        {
                            "enum": [
                                "object",
                                "function",
                                "undefined",
                                "string",
                                "number",
                                "boolean",
                                "symbol",
                                "bigint"
                            ],
                            "type": "string",
                            "name": "type",
                            "description": "Object type."
                        },
                        {
                            "type": "string",
                            "enum": [
                                "array",
                                "null",
                                "node",
                                "regexp",
                                "date",
                                "map",
                                "set",
                                "weakmap",
                                "weakset",
                                "iterator",
                                "generator",
                                "error"
                            ],
                            "optional": true,
                            "name": "subtype",
                            "description": "Object subtype hint. Specified for `object` type values only."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "description",
                            "description": "String representation of the object."
                        },
                        {
                            "type": "boolean",
                            "name": "overflow",
                            "description": "True iff some of the properties or entries of the original object did not fit."
                        },
                        {
                            "items": {
                                "$ref": "PropertyPreview"
                            },
                            "type": "array",
                            "name": "properties",
                            "description": "List of the properties."
                        },
                        {
                            "type": "array",
                            "items": {
                                "$ref": "EntryPreview"
                            },
                            "optional": true,
                            "name": "entries",
                            "description": "List of the entries. Specified for `map` and `set` subtype values only."
                        }
                    ],
                    "type": "object",
                    "id": "ObjectPreview",
                    "experimental": true,
                    "description": "Object containing abbreviated remote object value."
                },
                {
                    "type": "object",
                    "id": "PropertyPreview",
                    "experimental": true,
                    "properties": [
                        {
                            "type": "string",
                            "name": "name",
                            "description": "Property name."
                        },
                        {
                            "enum": [
                                "object",
                                "function",
                                "undefined",
                                "string",
                                "number",
                                "boolean",
                                "symbol",
                                "accessor",
                                "bigint"
                            ],
                            "type": "string",
                            "name": "type",
                            "description": "Object type. Accessor means that the property itself is an accessor property."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "value",
                            "description": "User-friendly property value string."
                        },
                        {
                            "$ref": "ObjectPreview",
                            "optional": true,
                            "name": "valuePreview",
                            "description": "Nested value preview."
                        },
                        {
                            "type": "string",
                            "enum": [
                                "array",
                                "null",
                                "node",
                                "regexp",
                                "date",
                                "map",
                                "set",
                                "weakmap",
                                "weakset",
                                "iterator",
                                "generator",
                                "error"
                            ],
                            "optional": true,
                            "name": "subtype",
                            "description": "Object subtype hint. Specified for `object` type values only."
                        }
                    ]
                },
                {
                    "type": "object",
                    "id": "EntryPreview",
                    "experimental": true,
                    "properties": [
                        {
                            "$ref": "ObjectPreview",
                            "optional": true,
                            "name": "key",
                            "description": "Preview of the key. Specified for map-like collection entries."
                        },
                        {
                            "$ref": "ObjectPreview",
                            "name": "value",
                            "description": "Preview of the value."
                        }
                    ]
                },
                {
                    "properties": [
                        {
                            "type": "string",
                            "name": "name",
                            "description": "Property name or symbol description."
                        },
                        {
                            "$ref": "RemoteObject",
                            "optional": true,
                            "name": "value",
                            "description": "The value associated with the property."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "writable",
                            "description": "True if the value associated with the property may be changed (data descriptors only)."
                        },
                        {
                            "$ref": "RemoteObject",
                            "optional": true,
                            "name": "get",
                            "description": "A function which serves as a getter for the property, or `undefined` if there is no getter\n(accessor descriptors only)."
                        },
                        {
                            "$ref": "RemoteObject",
                            "optional": true,
                            "name": "set",
                            "description": "A function which serves as a setter for the property, or `undefined` if there is no setter\n(accessor descriptors only)."
                        },
                        {
                            "type": "boolean",
                            "name": "configurable",
                            "description": "True if the type of this property descriptor may be changed and if the property may be\ndeleted from the corresponding object."
                        },
                        {
                            "type": "boolean",
                            "name": "enumerable",
                            "description": "True if this property shows up during enumeration of the properties on the corresponding\nobject."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "wasThrown",
                            "description": "True if the result was thrown during the evaluation."
                        },
                        {
                            "type": "boolean",
                            "optional": true,
                            "name": "isOwn",
                            "description": "True if the property is owned for the object."
                        },
                        {
                            "$ref": "RemoteObject",
                            "optional": true,
                            "name": "symbol",
                            "description": "Property symbol object, if the property is of the `symbol` type."
                        }
                    ],
                    "type": "object",
                    "id": "PropertyDescriptor",
                    "description": "Object property descriptor."
                },
                {
                    "properties": [
                        {
                            "type": "string",
                            "name": "name",
                            "description": "Conventional property name."
                        },
                        {
                            "$ref": "RemoteObject",
                            "optional": true,
                            "name": "value",
                            "description": "The value associated with the property."
                        }
                    ],
                    "type": "object",
                    "id": "InternalPropertyDescriptor",
                    "description": "Object internal property descriptor. This property isn't normally visible in JavaScript code."
                },
                {
                    "properties": [
                        {
                            "type": "any",
                            "optional": true,
                            "name": "value",
                            "description": "Primitive value or serializable javascript object."
                        },
                        {
                            "$ref": "UnserializableValue",
                            "optional": true,
                            "name": "unserializableValue",
                            "description": "Primitive value which can not be JSON-stringified."
                        },
                        {
                            "$ref": "RemoteObjectId",
                            "optional": true,
                            "name": "objectId",
                            "description": "Remote object handle."
                        }
                    ],
                    "type": "object",
                    "id": "CallArgument",
                    "description": "Represents function call argument. Either remote object id `objectId`, primitive `value`,\nunserializable primitive value or neither of (for undefined) them should be specified."
                },
                {
                    "type": "integer",
                    "id": "ExecutionContextId",
                    "description": "Id of an execution context."
                },
                {
                    "properties": [
                        {
                            "$ref": "ExecutionContextId",
                            "name": "id",
                            "description": "Unique id of the execution context. It can be used to specify in which execution context\nscript evaluation should be performed."
                        },
                        {
                            "type": "string",
                            "name": "origin",
                            "description": "Execution context origin."
                        },
                        {
                            "type": "string",
                            "name": "name",
                            "description": "Human readable name describing given context."
                        },
                        {
                            "type": "object",
                            "optional": true,
                            "name": "auxData",
                            "description": "Embedder-specific auxiliary data."
                        }
                    ],
                    "type": "object",
                    "id": "ExecutionContextDescription",
                    "description": "Description of an isolated world."
                },
                {
                    "properties": [
                        {
                            "type": "integer",
                            "name": "exceptionId",
                            "description": "Exception id."
                        },
                        {
                            "type": "string",
                            "name": "text",
                            "description": "Exception text, which should be used together with exception object when available."
                        },
                        {
                            "type": "integer",
                            "name": "lineNumber",
                            "description": "Line number of the exception location (0-based)."
                        },
                        {
                            "type": "integer",
                            "name": "columnNumber",
                            "description": "Column number of the exception location (0-based)."
                        },
                        {
                            "$ref": "ScriptId",
                            "optional": true,
                            "name": "scriptId",
                            "description": "Script ID of the exception location."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "url",
                            "description": "URL of the exception location, to be used when the script was not reported."
                        },
                        {
                            "$ref": "StackTrace",
                            "optional": true,
                            "name": "stackTrace",
                            "description": "JavaScript stack trace if available."
                        },
                        {
                            "$ref": "RemoteObject",
                            "optional": true,
                            "name": "exception",
                            "description": "Exception object if available."
                        },
                        {
                            "$ref": "ExecutionContextId",
                            "optional": true,
                            "name": "executionContextId",
                            "description": "Identifier of the context where exception happened."
                        }
                    ],
                    "type": "object",
                    "id": "ExceptionDetails",
                    "description": "Detailed information about exception (or error) that was thrown during script compilation or\nexecution."
                },
                {
                    "type": "number",
                    "id": "Timestamp",
                    "description": "Number of milliseconds since epoch."
                },
                {
                    "type": "number",
                    "id": "TimeDelta",
                    "description": "Number of milliseconds."
                },
                {
                    "properties": [
                        {
                            "type": "string",
                            "name": "functionName",
                            "description": "JavaScript function name."
                        },
                        {
                            "$ref": "ScriptId",
                            "name": "scriptId",
                            "description": "JavaScript script id."
                        },
                        {
                            "type": "string",
                            "name": "url",
                            "description": "JavaScript script name or url."
                        },
                        {
                            "type": "integer",
                            "name": "lineNumber",
                            "description": "JavaScript script line number (0-based)."
                        },
                        {
                            "type": "integer",
                            "name": "columnNumber",
                            "description": "JavaScript script column number (0-based)."
                        }
                    ],
                    "type": "object",
                    "id": "CallFrame",
                    "description": "Stack entry for runtime errors and assertions."
                },
                {
                    "properties": [
                        {
                            "type": "string",
                            "optional": true,
                            "name": "description",
                            "description": "String label of this stack trace. For async traces this may be a name of the function that\ninitiated the async call."
                        },
                        {
                            "items": {
                                "$ref": "CallFrame"
                            },
                            "type": "array",
                            "name": "callFrames",
                            "description": "JavaScript function name."
                        },
                        {
                            "$ref": "StackTrace",
                            "optional": true,
                            "name": "parent",
                            "description": "Asynchronous JavaScript stack trace that preceded this stack, if available."
                        },
                        {
                            "$ref": "StackTraceId",
                            "optional": true,
                            "name": "parentId",
                            "experimental": true,
                            "description": "Asynchronous JavaScript stack trace that preceded this stack, if available."
                        }
                    ],
                    "type": "object",
                    "id": "StackTrace",
                    "description": "Call frames for assertions or error messages."
                },
                {
                    "type": "string",
                    "id": "UniqueDebuggerId",
                    "experimental": true,
                    "description": "Unique identifier of current debugger."
                },
                {
                    "properties": [
                        {
                            "type": "string",
                            "name": "id"
                        },
                        {
                            "optional": true,
                            "name": "debuggerId",
                            "$ref": "UniqueDebuggerId"
                        }
                    ],
                    "type": "object",
                    "id": "StackTraceId",
                    "experimental": true,
                    "description": "If `debuggerId` is set stack trace comes from another debugger and can be resolved there. This\nallows to track cross-debugger calls. See `Runtime.StackTrace` and `Debugger.paused` for usages."
                }
            ],
            "events": [
                {
                    "parameters": [
                        {
                            "type": "string",
                            "name": "name"
                        },
                        {
                            "type": "string",
                            "name": "payload"
                        },
                        {
                            "$ref": "ExecutionContextId",
                            "name": "executionContextId",
                            "description": "Identifier of the context where the call was made."
                        }
                    ],
                    "name": "bindingCalled",
                    "experimental": true,
                    "description": "Notification is issued every time when binding is called."
                },
                {
                    "name": "consoleAPICalled",
                    "parameters": [
                        {
                            "enum": [
                                "log",
                                "debug",
                                "info",
                                "error",
                                "warning",
                                "dir",
                                "dirxml",
                                "table",
                                "trace",
                                "clear",
                                "startGroup",
                                "startGroupCollapsed",
                                "endGroup",
                                "assert",
                                "profile",
                                "profileEnd",
                                "count",
                                "timeEnd"
                            ],
                            "type": "string",
                            "name": "type",
                            "description": "Type of the call."
                        },
                        {
                            "items": {
                                "$ref": "RemoteObject"
                            },
                            "type": "array",
                            "name": "args",
                            "description": "Call arguments."
                        },
                        {
                            "$ref": "ExecutionContextId",
                            "name": "executionContextId",
                            "description": "Identifier of the context where the call was made."
                        },
                        {
                            "$ref": "Timestamp",
                            "name": "timestamp",
                            "description": "Call timestamp."
                        },
                        {
                            "$ref": "StackTrace",
                            "optional": true,
                            "name": "stackTrace",
                            "description": "Stack trace captured when the call was made."
                        },
                        {
                            "type": "string",
                            "optional": true,
                            "name": "context",
                            "experimental": true,
                            "description": "Console context descriptor for calls on non-default console context (not console.*):\n'anonymous#unique-logger-id' for call on unnamed context, 'name#unique-logger-id' for call\non named context."
                        }
                    ],
                    "description": "Issued when console API was called."
                },
                {
                    "name": "exceptionRevoked",
                    "parameters": [
                        {
                            "type": "string",
                            "name": "reason",
                            "description": "Reason describing why exception was revoked."
                        },
                        {
                            "type": "integer",
                            "name": "exceptionId",
                            "description": "The id of revoked exception, as reported in `exceptionThrown`."
                        }
                    ],
                    "description": "Issued when unhandled exception was revoked."
                },
                {
                    "name": "exceptionThrown",
                    "parameters": [
                        {
                            "$ref": "Timestamp",
                            "name": "timestamp",
                            "description": "Timestamp of the exception."
                        },
                        {
                            "name": "exceptionDetails",
                            "$ref": "ExceptionDetails"
                        }
                    ],
                    "description": "Issued when exception was thrown and unhandled."
                },
                {
                    "name": "executionContextCreated",
                    "parameters": [
                        {
                            "$ref": "ExecutionContextDescription",
                            "name": "context",
                            "description": "A newly created execution context."
                        }
                    ],
                    "description": "Issued when new execution context is created."
                },
                {
                    "name": "executionContextDestroyed",
                    "parameters": [
                        {
                            "$ref": "ExecutionContextId",
                            "name": "executionContextId",
                            "description": "Id of the destroyed context"
                        }
                    ],
                    "description": "Issued when execution context is destroyed."
                },
                {
                    "name": "executionContextsCleared",
                    "description": "Issued when all executionContexts were cleared in browser"
                },
                {
                    "name": "inspectRequested",
                    "parameters": [
                        {
                            "name": "object",
                            "$ref": "RemoteObject"
                        },
                        {
                            "type": "object",
                            "name": "hints"
                        }
                    ],
                    "description": "Issued when object should be inspected (for example, as a result of inspect() command line API\ncall)."
                }
            ]
        },
        {
            "deprecated": true,
            "domain": "Schema",
            "commands": [
                {
                    "returns": [
                        {
                            "items": {
                                "$ref": "Domain"
                            },
                            "type": "array",
                            "name": "domains",
                            "description": "List of supported domains."
                        }
                    ],
                    "name": "getDomains",
                    "description": "Returns supported domains."
                }
            ],
            "description": "This domain is deprecated.",
            "types": [
                {
                    "properties": [
                        {
                            "type": "string",
                            "name": "name",
                            "description": "Domain name."
                        },
                        {
                            "type": "string",
                            "name": "version",
                            "description": "Domain version."
                        }
                    ],
                    "type": "object",
                    "id": "Domain",
                    "description": "Description of the protocol domain."
                }
            ]
        }
    ],
    "version": {
        "major": "1",
        "minor": "3"
    }
};

export const Protocol = { Description };
PK
!<Z���0chrome/remote/content/cdp/StreamRegistry.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
  UnsupportedError: "chrome://remote/content/cdp/Error.sys.mjs",
});

export class Stream {
  #path;
  #offset;
  #length;

  constructor(path) {
    this.#path = path;
    this.#offset = 0;
    this.#length = null;
  }

  async destroy() {
    await IOUtils.remove(this.#path);
  }

  async seek(seekTo) {
    // To keep compatibility with Chrome clip invalid offsets
    this.#offset = Math.max(0, Math.min(seekTo, await this.length()));
  }

  async readBytes(count) {
    const bytes = await IOUtils.read(this.#path, {
      offset: this.#offset,
      maxBytes: count,
    });
    this.#offset += bytes.length;
    return bytes;
  }

  async available() {
    const length = await this.length();
    return length - this.#offset;
  }

  async length() {
    if (this.#length === null) {
      const info = await IOUtils.stat(this.#path);
      this.#length = info.size;
    }

    return this.#length;
  }

  get path() {
    return this.#path;
  }
}

export class StreamRegistry {
  constructor() {
    // handle => stream
    this.streams = new Map();

    // Register an async shutdown blocker to ensure all open IO streams are
    // closed, and remaining temporary files removed. Needs to happen before
    // IOUtils has been shutdown.
    IOUtils.profileBeforeChange.addBlocker(
      "Remote Agent: Clean-up of open streams",
      async () => {
        await this.destructor();
      }
    );
  }

  async destructor() {
    for (const stream of this.streams.values()) {
      await stream.destroy();
    }

    this.streams.clear();
  }

  /**
   * Add a new stream to the registry.
   *
   * @param {Stream} stream
   *      The stream to use.
   *
   * @returns {string}
   *     Stream handle (uuid)
   */
  add(stream) {
    if (!(stream instanceof Stream)) {
      // Bug 1602731 - Implement support for blob
      throw new lazy.UnsupportedError(`Unknown stream type for ${stream}`);
    }

    const handle = lazy.generateUUID();

    this.streams.set(handle, stream);
    return handle;
  }

  /**
   * Get a stream from the registry.
   *
   * @param {string} handle
   *      Handle of the stream to retrieve.
   *
   * @returns {Stream}
   *      The requested stream.
   */
  get(handle) {
    const stream = this.streams.get(handle);

    if (!stream) {
      throw new TypeError(`Invalid stream handle`);
    }

    return stream;
  }

  /**
   * Remove a stream from the registry.
   *
   * @param {string} handle
   *      Handle of the stream to remove.
   *
   * @returns {boolean}
   *     true if successfully removed
   */
  async remove(handle) {
    const stream = this.get(handle);
    await stream.destroy();

    return this.streams.delete(handle);
  }
}
PK
!<��xMM>chrome/remote/content/cdp/domains/ContentProcessDomain.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Domain } from "chrome://remote/content/cdp/domains/Domain.sys.mjs";

export class ContentProcessDomain extends Domain {
  destructor() {
    super.destructor();
  }

  // helpers

  get content() {
    return this.session.content;
  }

  get docShell() {
    return this.session.docShell;
  }

  get chromeEventHandler() {
    return this.docShell.chromeEventHandler;
  }
}
PK
!<:��?chrome/remote/content/cdp/domains/ContentProcessDomains.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export const ContentProcessDomains = {};

// eslint-disable-next-line mozilla/lazy-getter-object-name
ChromeUtils.defineESModuleGetters(ContentProcessDomains, {
  DOM: "chrome://remote/content/cdp/domains/content/DOM.sys.mjs",
  Emulation: "chrome://remote/content/cdp/domains/content/Emulation.sys.mjs",
  Input: "chrome://remote/content/cdp/domains/content/Input.sys.mjs",
  Log: "chrome://remote/content/cdp/domains/content/Log.sys.mjs",
  Network: "chrome://remote/content/cdp/domains/content/Network.sys.mjs",
  Page: "chrome://remote/content/cdp/domains/content/Page.sys.mjs",
  Performance:
    "chrome://remote/content/cdp/domains/content/Performance.sys.mjs",
  Runtime: "chrome://remote/content/cdp/domains/content/Runtime.sys.mjs",
  Security: "chrome://remote/content/cdp/domains/content/Security.sys.mjs",
});
PK
!<*����0chrome/remote/content/cdp/domains/Domain.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class Domain {
  constructor(session) {
    this.session = session;
    this.name = this.constructor.name;

    this.eventListeners_ = new Set();
    this._requestCounter = 0;
  }

  destructor() {}

  emit(eventName, params = {}) {
    for (const listener of this.eventListeners_) {
      try {
        if (isEventHandler(listener)) {
          listener.onEvent(eventName, params);
        } else {
          listener.call(this, eventName, params);
        }
      } catch (e) {
        console.error(e);
      }
    }
  }

  /**
   * Execute the provided method in the child domain that has the same domain
   * name. eg. calling this.executeInChild from domains/parent/Input.sys.mjs will
   * attempt to execute the method in domains/content/Input.sys.mjs.
   *
   * This can only be called from parent domains managed by a TabSession.
   *
   * @param {string} method
   *        Name of the method to call on the child domain.
   * @param {object} params
   *        Optional parameters. Must be serializable.
   */
  executeInChild(method, params) {
    if (!this.session.executeInChild) {
      throw new Error(
        "executeInChild can only be used in Domains managed by a TabSession"
      );
    }
    this._requestCounter++;
    const id = this.name + "-" + this._requestCounter;
    return this.session.executeInChild(id, this.name, method, params);
  }

  addEventListener(listener) {
    if (typeof listener != "function" && !isEventHandler(listener)) {
      throw new TypeError();
    }
    this.eventListeners_.add(listener);
  }

  // static

  static implements(command) {
    return command && typeof this.prototype[command] == "function";
  }
}

function isEventHandler(listener) {
  return (
    listener && "onEvent" in listener && typeof listener.onEvent == "function"
  );
}
PK
!<欐�5chrome/remote/content/cdp/domains/DomainCache.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Domain: "chrome://remote/content/cdp/domains/Domain.sys.mjs",
  UnknownMethodError: "chrome://remote/content/cdp/Error.sys.mjs",
});

/**
 * Lazy domain instance cache.
 *
 * Domains are loaded into each target's realm, and consequently
 * there exists one domain cache per realm.  Domains are preregistered
 * with this cache and then constructed lazily upon request.
 *
 * @param {Session} session
 *     Session that domains should be associated with as they
 *     are constructed.
 * @param {Map.<string, string>} modules
 *     Table defining JS modules available to this domain cache.
 *     This should be a mapping between domain name
 *     and JS module path passed to ChromeUtils.import.
 */
export class DomainCache {
  constructor(session, modules) {
    this.session = session;
    this.modules = modules;
    this.instances = new Map();
  }

  /** Test if domain supports method. */
  domainSupportsMethod(name, method) {
    const domain = this.modules[name];
    if (domain) {
      return domain.implements(method);
    }
    return false;
  }

  /**
   * Gets the current instance of the domain, or creates a new one,
   * and associates it with the predefined session.
   *
   * @throws {UnknownMethodError}
   *     If domain is not preregistered with this domain cache.
   */
  get(name) {
    let inst = this.instances.get(name);
    if (!inst) {
      const Cls = this.modules[name];
      if (!Cls) {
        throw new lazy.UnknownMethodError(name);
      }
      if (!isConstructor(Cls)) {
        throw new TypeError("Domain cannot be constructed");
      }

      inst = new Cls(this.session);
      if (!(inst instanceof lazy.Domain)) {
        throw new TypeError("Instance not a domain");
      }

      inst.addEventListener(this.session);

      this.instances.set(name, inst);
    }

    return inst;
  }

  /**
   * Tells if a Domain of the given name is available
   */
  has(name) {
    return name in this.modules;
  }

  get size() {
    return this.instances.size;
  }

  /**
   * Execute the given command (function) of a given domain with the given parameters.
   * If the command doesn't exists, it will throw.
   * It returns the returned value of the command, which is most likely a promise.
   */
  execute(domain, command, params) {
    if (!this.domainSupportsMethod(domain, command)) {
      throw new lazy.UnknownMethodError(domain, command);
    }
    const inst = this.get(domain);
    return inst[command](params);
  }

  /** Calls destructor on each domain and clears the cache. */
  clear() {
    for (const inst of this.instances.values()) {
      inst.destructor();
    }
    this.instances.clear();
  }

  toString() {
    return `[object DomainCache ${this.size}]`;
  }
}

function isConstructor(obj) {
  return !!obj.prototype && !!obj.prototype.constructor.name;
}
PK
!<�X��==>chrome/remote/content/cdp/domains/ParentProcessDomains.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export const ParentProcessDomains = {};

// eslint-disable-next-line mozilla/lazy-getter-object-name
ChromeUtils.defineESModuleGetters(ParentProcessDomains, {
  Browser: "chrome://remote/content/cdp/domains/parent/Browser.sys.mjs",
  Emulation: "chrome://remote/content/cdp/domains/parent/Emulation.sys.mjs",
  Fetch: "chrome://remote/content/cdp/domains/parent/Fetch.sys.mjs",
  Input: "chrome://remote/content/cdp/domains/parent/Input.sys.mjs",
  IO: "chrome://remote/content/cdp/domains/parent/IO.sys.mjs",
  Network: "chrome://remote/content/cdp/domains/parent/Network.sys.mjs",
  Page: "chrome://remote/content/cdp/domains/parent/Page.sys.mjs",
  Security: "chrome://remote/content/cdp/domains/parent/Security.sys.mjs",
  SystemInfo: "chrome://remote/content/cdp/domains/parent/SystemInfo.sys.mjs",
  Target: "chrome://remote/content/cdp/domains/parent/Target.sys.mjs",
});
PK
!<;��.��5chrome/remote/content/cdp/domains/content/DOM.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ContentProcessDomain } from "chrome://remote/content/cdp/domains/ContentProcessDomain.sys.mjs";

export class DOM extends ContentProcessDomain {
  constructor(session) {
    super(session);
    this.enabled = false;
  }

  destructor() {
    this.disable();
  }

  // commands

  async enable() {
    if (!this.enabled) {
      this.enabled = true;
    }
  }

  /**
   * Describes node given its id.
   *
   * Does not require domain to be enabled. Does not start tracking any objects.
   *
   * @param {object} options
   * @param {number=} options.backendNodeId [not supported]
   *     Identifier of the backend node.
   * @param {number=} options.depth [not supported]
   *     The maximum depth at which children should be retrieved, defaults to 1.
   *     Use -1 for the entire subtree or provide an integer larger than 0.
   * @param {number=} options.nodeId [not supported]
   *     Identifier of the node.
   * @param {string} options.objectId
   *     JavaScript object id of the node wrapper.
   * @param {boolean=} options.pierce [not supported]
   *     Whether or not iframes and shadow roots should be traversed
   *     when returning the subtree, defaults to false.
   *
   * @returns {DOM.Node}
   *     Node description.
   */
  describeNode(options = {}) {
    const { objectId } = options;

    // Until nodeId/backendNodeId is supported force usage of the objectId
    if (!["string"].includes(typeof objectId)) {
      throw new TypeError("objectId: string value expected");
    }

    const Runtime = this.session.domains.get("Runtime");
    const debuggerObj = Runtime._getRemoteObject(objectId);
    if (!debuggerObj) {
      throw new Error("Could not find object with given id");
    }

    if (typeof debuggerObj.nodeId == "undefined") {
      throw new Error("Object id doesn't reference a Node");
    }

    const unsafeObj = debuggerObj.unsafeDereference();

    const attributes = [];
    if (unsafeObj.attributes) {
      // Flatten the list of attributes for name and value
      for (const attribute of unsafeObj.attributes) {
        attributes.push(attribute.name, attribute.value);
      }
    }

    let context = this.docShell.browsingContext;
    if (HTMLIFrameElement.isInstance(unsafeObj)) {
      context = unsafeObj.contentWindow.docShell.browsingContext;
    }

    const node = {
      nodeId: debuggerObj.nodeId,
      backendNodeId: debuggerObj.backendNodeId,
      nodeType: unsafeObj.nodeType,
      nodeName: unsafeObj.nodeName,
      localName: unsafeObj.localName,
      nodeValue: unsafeObj.nodeValue ? unsafeObj.nodeValue.toString() : "",
      childNodeCount: unsafeObj.childElementCount,
      attributes: attributes.length ? attributes : undefined,
      frameId: context.id.toString(),
    };

    return { node };
  }

  disable() {
    if (this.enabled) {
      this.enabled = false;
    }
  }

  getContentQuads(options = {}) {
    const { objectId } = options;
    const Runtime = this.session.domains.get("Runtime");
    const debuggerObj = Runtime._getRemoteObject(objectId);
    if (!debuggerObj) {
      throw new Error(`Cannot find object with id: ${objectId}`);
    }
    const unsafeObject = debuggerObj.unsafeDereference();
    if (!unsafeObject.getBoxQuads) {
      throw new Error("RemoteObject is not a node");
    }
    let quads = unsafeObject.getBoxQuads({ relativeTo: this.content.document });
    quads = quads.map(quad => {
      return [
        quad.p1.x,
        quad.p1.y,
        quad.p2.x,
        quad.p2.y,
        quad.p3.x,
        quad.p3.y,
        quad.p4.x,
        quad.p4.y,
      ].map(Math.round);
    });
    return { quads };
  }

  getBoxModel(options = {}) {
    const { objectId } = options;
    const Runtime = this.session.domains.get("Runtime");
    const debuggerObj = Runtime._getRemoteObject(objectId);
    if (!debuggerObj) {
      throw new Error(`Cannot find object with id: ${objectId}`);
    }
    const unsafeObject = debuggerObj.unsafeDereference();
    const bounding = unsafeObject.getBoundingClientRect();
    const model = {
      width: Math.round(bounding.width),
      height: Math.round(bounding.height),
    };
    for (const box of ["content", "padding", "border", "margin"]) {
      const quads = unsafeObject.getBoxQuads({
        box,
        relativeTo: this.content.document,
      });

      // getBoxQuads may return more than one element. In this case we have to compute the bounding box
      // of all these boxes.
      let bounding = {
        p1: { x: Infinity, y: Infinity },
        p2: { x: -Infinity, y: Infinity },
        p3: { x: -Infinity, y: -Infinity },
        p4: { x: Infinity, y: -Infinity },
      };
      quads.forEach(quad => {
        bounding = {
          p1: {
            x: Math.min(bounding.p1.x, quad.p1.x),
            y: Math.min(bounding.p1.y, quad.p1.y),
          },
          p2: {
            x: Math.max(bounding.p2.x, quad.p2.x),
            y: Math.min(bounding.p2.y, quad.p2.y),
          },
          p3: {
            x: Math.max(bounding.p3.x, quad.p3.x),
            y: Math.max(bounding.p3.y, quad.p3.y),
          },
          p4: {
            x: Math.min(bounding.p4.x, quad.p4.x),
            y: Math.max(bounding.p4.y, quad.p4.y),
          },
        };
      });

      model[box] = [
        bounding.p1.x,
        bounding.p1.y,
        bounding.p2.x,
        bounding.p2.y,
        bounding.p3.x,
        bounding.p3.y,
        bounding.p4.x,
        bounding.p4.y,
      ].map(Math.round);
    }
    return {
      model,
    };
  }

  /**
   * Resolves the JavaScript node object for a given NodeId or BackendNodeId.
   *
   * @param {object} options
   * @param {number} options.backendNodeId [required for now]
   *     Backend identifier of the node to resolve.
   * @param {number=} options.executionContextId
   *     Execution context in which to resolve the node.
   * @param {number=} options.nodeId [not supported]
   *     Id of the node to resolve.
   * @param {string=} options.objectGroup [not supported]
   *     Symbolic group name that can be used to release multiple objects.
   *
   * @returns {Runtime.RemoteObject}
   *     JavaScript object wrapper for given node.
   */
  resolveNode(options = {}) {
    const { backendNodeId, executionContextId } = options;

    // Until nodeId is supported force usage of the backendNodeId
    if (!["number"].includes(typeof backendNodeId)) {
      throw new TypeError("backendNodeId: number value expected");
    }
    if (!["undefined", "number"].includes(typeof executionContextId)) {
      throw new TypeError("executionContextId: integer value expected");
    }

    const Runtime = this.session.domains.get("Runtime");

    // Retrieve the node to resolve, and its context
    const debuggerObj = Runtime._getRemoteObjectByNodeId(backendNodeId);

    if (!debuggerObj) {
      throw new Error(`No node with given id found`);
    }

    // If execution context isn't specified use the default one for the node
    let context;
    if (typeof executionContextId != "undefined") {
      context = Runtime.contexts.get(executionContextId);
      if (!context) {
        throw new Error(`Node with given id does not belong to the document`);
      }
    } else {
      context = Runtime._getDefaultContextForWindow();
    }

    Runtime._setRemoteObject(debuggerObj, context);

    return {
      object: Runtime._serializeRemoteObject(debuggerObj, context.id),
    };
  }
}
PK
!<7%vn��;chrome/remote/content/cdp/domains/content/Emulation.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ContentProcessDomain } from "chrome://remote/content/cdp/domains/ContentProcessDomain.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AnimationFramePromise: "chrome://remote/content/shared/Sync.sys.mjs",
});

export class Emulation extends ContentProcessDomain {
  // commands

  /**
   * Internal methods: the following methods are not part of CDP;
   * note the _ prefix.
   */

  /**
   * Waits until the viewport has reached the new dimensions.
   */
  async _awaitViewportDimensions({ width, height }) {
    const win = this.content;
    let resized;

    // Updates for background tabs are throttled, and we also we have to make
    // sure that the new browser dimensions have been received by the content
    // process. As such wait for the next animation frame.
    await lazy.AnimationFramePromise(win);

    const checkBrowserSize = () => {
      if (win.innerWidth === width && win.innerHeight === height) {
        resized();
      }
    };

    return new Promise(resolve => {
      resized = resolve;

      win.addEventListener("resize", checkBrowserSize);

      // Trigger a layout flush in case none happened yet.
      checkBrowserSize();
    }).finally(() => {
      win.removeEventListener("resize", checkBrowserSize);
    });
  }
}
PK
!<k�T7chrome/remote/content/cdp/domains/content/Input.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ContentProcessDomain } from "chrome://remote/content/cdp/domains/ContentProcessDomain.sys.mjs";

export class Input extends ContentProcessDomain {
  constructor(session) {
    super(session);

    // Internal id used to track existing event handlers.
    this._eventId = 0;

    // Map of event id -> event handler promise.
    this._eventPromises = new Map();
  }

  /**
   * Internal methods: the following methods are not part of CDP;
   * note the _ prefix.
   */

  /**
   * Add an event listener in the content page for the provided eventName.
   * This method will return a unique handler id that can be used to wait
   * for the event.
   *
   * Example usage from a parent process domain:
   *
   *   const id = await this.executeInChild("_addContentEventListener", "click");
   *   // do something that triggers a click in content
   *   await this.executeInChild("_waitForContentEvent", id);
   */
  _addContentEventListener(eventName) {
    const eventPromise = new Promise(resolve => {
      this.chromeEventHandler.addEventListener(eventName, resolve, {
        mozSystemGroup: true,
        once: true,
      });
    });
    this._eventId++;
    this._eventPromises.set(this._eventId, eventPromise);
    return this._eventId;
  }

  /**
   * Wait for an event listener added via `addContentEventListener` to be fired.
   */
  async _waitForContentEvent(eventId) {
    const eventPromise = this._eventPromises.get(eventId);
    if (!eventPromise) {
      throw new Error("No event promise found for id " + eventId);
    }
    await eventPromise;
    this._eventPromises.delete(eventId);
  }
}
PK
!<Tє5chrome/remote/content/cdp/domains/content/Log.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ContentProcessDomain } from "chrome://remote/content/cdp/domains/ContentProcessDomain.sys.mjs";

const CONSOLE_MESSAGE_LEVEL_MAP = {
  [Ci.nsIConsoleMessage.debug]: "verbose",
  [Ci.nsIConsoleMessage.info]: "info",
  [Ci.nsIConsoleMessage.warn]: "warning",
  [Ci.nsIConsoleMessage.error]: "error",
};

export class Log extends ContentProcessDomain {
  constructor(session) {
    super(session);
    this.enabled = false;
  }

  destructor() {
    this.disable();

    super.destructor();
  }

  enable() {
    if (!this.enabled) {
      this.enabled = true;

      Services.console.registerListener(this);
    }
  }

  disable() {
    if (this.enabled) {
      this.enabled = false;

      Services.console.unregisterListener(this);
    }
  }

  _getLogCategory(category) {
    if (category.startsWith("CORS")) {
      return "network";
    } else if (category.includes("javascript")) {
      return "javascript";
    }

    return "other";
  }

  // nsIObserver

  /**
   * Takes all script error messages that do not have an exception attached,
   * and emits a "Log.entryAdded" event.
   *
   * @param {nsIConsoleMessage} message
   *     Message originating from the nsIConsoleService.
   */
  observe(message) {
    if (message instanceof Ci.nsIScriptError && !message.hasException) {
      let url;
      if (message.sourceName !== "debugger eval code") {
        url = message.sourceName;
      }

      const entry = {
        source: this._getLogCategory(message.category),
        level: CONSOLE_MESSAGE_LEVEL_MAP[message.logLevel],
        text: message.errorMessage,
        timestamp: message.timeStamp,
        url,
        lineNumber: message.lineNumber,
      };

      this.emit("Log.entryAdded", { entry });
    }
  }

  // XPCOM

  get QueryInterface() {
    return ChromeUtils.generateQI(["nsIConsoleListener"]);
  }
}
PK
!<]XJ�009chrome/remote/content/cdp/domains/content/Network.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ContentProcessDomain } from "chrome://remote/content/cdp/domains/ContentProcessDomain.sys.mjs";

export class Network extends ContentProcessDomain {
  // commands

  /**
   * Internal methods: the following methods are not part of CDP;
   * note the _ prefix.
   */

  _updateLoadFlags(flags) {
    this.docShell.defaultLoadFlags = flags;
  }
}
PK
!<�=��3�36chrome/remote/content/cdp/domains/content/Page.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ContentProcessDomain } from "chrome://remote/content/cdp/domains/ContentProcessDomain.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
});

const { LOAD_FLAGS_BYPASS_CACHE, LOAD_FLAGS_BYPASS_PROXY, LOAD_FLAGS_NONE } =
  Ci.nsIWebNavigation;

export class Page extends ContentProcessDomain {
  constructor(session) {
    super(session);

    this.enabled = false;
    this.lifecycleEnabled = false;
    // script id => { source, worldName }
    this.scriptsToEvaluateOnLoad = new Map();
    this.worldsToEvaluateOnLoad = new Set();

    // This map is used to keep a reference to the loader id for
    // those Page events, which do not directly rely on
    // Network events. This might be a temporary solution until
    // the Network observer could be queried for that. But right
    // now this lives in the parent process.
    this.frameIdToLoaderId = new Map();

    this._onFrameAttached = this._onFrameAttached.bind(this);
    this._onFrameDetached = this._onFrameDetached.bind(this);
    this._onFrameNavigated = this._onFrameNavigated.bind(this);
    this._onScriptLoaded = this._onScriptLoaded.bind(this);

    this.session.contextObserver.on("script-loaded", this._onScriptLoaded);
  }

  destructor() {
    this.setLifecycleEventsEnabled({ enabled: false });
    this.session.contextObserver.off("script-loaded", this._onScriptLoaded);
    this.disable();

    super.destructor();
  }

  // commands

  async enable() {
    if (!this.enabled) {
      this.session.contextObserver.on("frame-attached", this._onFrameAttached);
      this.session.contextObserver.on("frame-detached", this._onFrameDetached);
      this.session.contextObserver.on(
        "frame-navigated",
        this._onFrameNavigated
      );

      this.chromeEventHandler.addEventListener("readystatechange", this, {
        mozSystemGroup: true,
        capture: true,
      });
      this.chromeEventHandler.addEventListener("pagehide", this, {
        mozSystemGroup: true,
      });
      this.chromeEventHandler.addEventListener("unload", this, {
        mozSystemGroup: true,
        capture: true,
      });
      this.chromeEventHandler.addEventListener("DOMContentLoaded", this, {
        mozSystemGroup: true,
      });
      this.chromeEventHandler.addEventListener("hashchange", this, {
        mozSystemGroup: true,
        capture: true,
      });
      this.chromeEventHandler.addEventListener("load", this, {
        mozSystemGroup: true,
        capture: true,
      });
      this.chromeEventHandler.addEventListener("pageshow", this, {
        mozSystemGroup: true,
      });

      this.enabled = true;
    }
  }

  disable() {
    if (this.enabled) {
      this.session.contextObserver.off("frame-attached", this._onFrameAttached);
      this.session.contextObserver.off("frame-detached", this._onFrameDetached);
      this.session.contextObserver.off(
        "frame-navigated",
        this._onFrameNavigated
      );

      this.chromeEventHandler.removeEventListener("readystatechange", this, {
        mozSystemGroup: true,
        capture: true,
      });
      this.chromeEventHandler.removeEventListener("pagehide", this, {
        mozSystemGroup: true,
      });
      this.chromeEventHandler.removeEventListener("unload", this, {
        mozSystemGroup: true,
        capture: true,
      });
      this.chromeEventHandler.removeEventListener("DOMContentLoaded", this, {
        mozSystemGroup: true,
      });
      this.chromeEventHandler.removeEventListener("hashchange", this, {
        mozSystemGroup: true,
        capture: true,
      });
      this.chromeEventHandler.removeEventListener("load", this, {
        mozSystemGroup: true,
        capture: true,
      });
      this.chromeEventHandler.removeEventListener("pageshow", this, {
        mozSystemGroup: true,
      });
      this.enabled = false;
    }
  }

  async reload(options = {}) {
    const { ignoreCache } = options;
    let flags = LOAD_FLAGS_NONE;
    if (ignoreCache) {
      flags |= LOAD_FLAGS_BYPASS_CACHE;
      flags |= LOAD_FLAGS_BYPASS_PROXY;
    }
    this.docShell.reload(flags);
  }

  getFrameTree() {
    const getFrames = context => {
      const frameTree = {
        frame: this._getFrameDetails({ context }),
      };

      if (context.children.length) {
        const frames = [];
        for (const childContext of context.children) {
          frames.push(getFrames(childContext));
        }
        frameTree.childFrames = frames;
      }

      return frameTree;
    };

    return {
      frameTree: getFrames(this.docShell.browsingContext),
    };
  }

  /**
   * Enqueues given script to be evaluated in every frame upon creation
   *
   * If `worldName` is specified, creates an execution context with the given name
   * and evaluates given script in it.
   *
   * At this time, queued scripts do not get evaluated, hence `source` is marked as
   * "unsupported".
   *
   * @param {object} options
   * @param {string} options.source (not supported)
   * @param {string=} options.worldName
   * @returns {string} Page.ScriptIdentifier
   */
  addScriptToEvaluateOnNewDocument(options = {}) {
    const { source, worldName } = options;
    if (worldName) {
      this.worldsToEvaluateOnLoad.add(worldName);
    }
    const identifier = lazy.generateUUID();
    this.scriptsToEvaluateOnLoad.set(identifier, { worldName, source });

    return { identifier };
  }

  /**
   * Creates an isolated world for the given frame.
   *
   * Really it just creates an execution context with label "isolated".
   *
   * @param {object} options
   * @param {string} options.frameId
   *     Id of the frame in which the isolated world should be created.
   * @param {string=} options.worldName
   *     An optional name which is reported in the Execution Context.
   * @param {boolean=} options.grantUniversalAccess (not supported)
   *     This is a powerful option, use with caution.
   *
   * @returns {number} Runtime.ExecutionContextId
   *     Execution context of the isolated world.
   */
  createIsolatedWorld(options = {}) {
    const { frameId, worldName } = options;

    if (typeof frameId != "string") {
      throw new TypeError("frameId: string value expected");
    }

    if (!["undefined", "string"].includes(typeof worldName)) {
      throw new TypeError("worldName: string value expected");
    }

    const Runtime = this.session.domains.get("Runtime");
    const contexts = Runtime._getContextsForFrame(frameId);
    if (!contexts.length) {
      throw new Error("No frame for given id found");
    }

    const defaultContext = Runtime._getDefaultContextForWindow(
      contexts[0].windowId
    );
    const window = defaultContext.window;

    const executionContextId = Runtime._onContextCreated("context-created", {
      windowId: window.windowGlobalChild.innerWindowId,
      window,
      isDefault: false,
      contextName: worldName,
      contextType: "isolated",
    });

    return { executionContextId };
  }

  /**
   * Controls whether page will emit lifecycle events.
   *
   * @param {object} options
   * @param {boolean} options.enabled
   *     If true, starts emitting lifecycle events.
   */
  setLifecycleEventsEnabled(options = {}) {
    const { enabled } = options;

    this.lifecycleEnabled = enabled;
  }

  url() {
    return this.content.location.href;
  }

  _onFrameAttached(name, { frameId, window }) {
    const bc = BrowsingContext.get(frameId);

    // Don't emit for top-level browsing contexts
    if (!bc.parent) {
      return;
    }

    // TODO: Use a unique identifier for frames (bug 1605359)
    this.emit("Page.frameAttached", {
      frameId: frameId.toString(),
      parentFrameId: bc.parent.id.toString(),
      stack: null,
    });

    // Usually both events are emitted when the "pagehide" event is received.
    // But this wont happen for a new window or frame when the initial
    // about:blank page has already loaded, and is being replaced with the
    // final document.
    if (!window.document.isInitialDocument) {
      this.emit("Page.frameStartedLoading", { frameId: frameId.toString() });

      const loaderId = this.frameIdToLoaderId.get(frameId);
      const timestamp = Date.now() / 1000;
      this.emitLifecycleEvent(frameId, loaderId, "init", timestamp);
    }
  }

  _onFrameDetached(name, { frameId }) {
    const bc = BrowsingContext.get(frameId);

    // Don't emit for top-level browsing contexts
    if (!bc.parent) {
      return;
    }

    // TODO: Use a unique identifier for frames (bug 1605359)
    this.emit("Page.frameDetached", { frameId: frameId.toString() });
  }

  _onFrameNavigated(name, { frameId }) {
    const bc = BrowsingContext.get(frameId);

    this.emit("Page.frameNavigated", {
      frame: this._getFrameDetails({ context: bc }),
    });
  }

  /**
   * @param {string} name
   *     The event name.
   * @param {object=} options
   * @param {number} options.windowId
   *     The inner window id of the window the script has been loaded for.
   * @param {Window} options.window
   *     The window object of the document.
   */
  _onScriptLoaded(name, options = {}) {
    const { windowId, window } = options;

    const Runtime = this.session.domains.get("Runtime");
    for (const world of this.worldsToEvaluateOnLoad) {
      Runtime._onContextCreated("context-created", {
        windowId,
        window,
        isDefault: false,
        contextName: world,
        contextType: "isolated",
      });
    }
    // TODO evaluate each onNewDoc script in the appropriate world
  }

  emitLifecycleEvent(frameId, loaderId, name, timestamp) {
    if (this.lifecycleEnabled) {
      this.emit("Page.lifecycleEvent", {
        frameId: frameId.toString(),
        loaderId,
        name,
        timestamp,
      });
    }
  }

  handleEvent({ type, target }) {
    const timestamp = Date.now() / 1000;

    // Some events such as "hashchange" use the window as the target, while
    // others have a document.
    const win = Window.isInstance(target) ? target : target.defaultView;
    const frameId = win.docShell.browsingContext.id;
    const isFrame = !!win.docShell.browsingContext.parent;
    const loaderId = this.frameIdToLoaderId.get(frameId);
    const url = win.location.href;

    switch (type) {
      case "DOMContentLoaded":
        if (!isFrame) {
          this.emit("Page.domContentEventFired", { timestamp });
        }
        this.emitLifecycleEvent(
          frameId,
          loaderId,
          "DOMContentLoaded",
          timestamp
        );
        break;

      case "hashchange":
        this.emit("Page.navigatedWithinDocument", {
          frameId: frameId.toString(),
          url,
        });
        break;

      case "pagehide":
        // Maybe better to bound to "unload" once we can register for this event
        this.emit("Page.frameStartedLoading", { frameId: frameId.toString() });
        this.emitLifecycleEvent(frameId, loaderId, "init", timestamp);
        break;

      case "load":
        if (!isFrame) {
          this.emit("Page.loadEventFired", { timestamp });
        }
        this.emitLifecycleEvent(frameId, loaderId, "load", timestamp);

        // XXX this should most likely be sent differently
        this.emit("Page.frameStoppedLoading", { frameId: frameId.toString() });
        break;

      case "readystatechange":
        if (this.content.document.readyState === "loading") {
          this.emitLifecycleEvent(frameId, loaderId, "init", timestamp);
        }
    }
  }

  _updateLoaderId(data) {
    const { frameId, loaderId } = data;

    this.frameIdToLoaderId.set(frameId, loaderId);
  }

  _contentRect() {
    const docEl = this.content.document.documentElement;

    return {
      x: 0,
      y: 0,
      width: docEl.scrollWidth,
      height: docEl.scrollHeight,
    };
  }

  _devicePixelRatio() {
    return (
      this.content.browsingContext.overrideDPPX || this.content.devicePixelRatio
    );
  }

  _getFrameDetails({ context, id }) {
    const bc = context || BrowsingContext.get(id);
    const frame = bc.embedderElement;

    return {
      id: bc.id.toString(),
      parentId: bc.parent?.id.toString(),
      loaderId: this.frameIdToLoaderId.get(bc.id),
      url: bc.docShell.domWindow.location.href,
      name: frame?.id || frame?.name,
      securityOrigin: null,
      mimeType: null,
    };
  }

  _getScrollbarSize() {
    const scrollbarHeight = {};
    const scrollbarWidth = {};

    this.content.windowUtils.getScrollbarSize(
      false,
      scrollbarWidth,
      scrollbarHeight
    );

    return {
      width: scrollbarWidth.value,
      height: scrollbarHeight.value,
    };
  }

  _layoutViewport() {
    const scrollbarSize = this._getScrollbarSize();

    return {
      pageX: this.content.pageXOffset,
      pageY: this.content.pageYOffset,
      clientWidth: this.content.innerWidth - scrollbarSize.width,
      clientHeight: this.content.innerHeight - scrollbarSize.height,
    };
  }
}
PK
!<v6�خ�=chrome/remote/content/cdp/domains/content/Performance.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ContentProcessDomain } from "chrome://remote/content/cdp/domains/ContentProcessDomain.sys.mjs";

export class Performance extends ContentProcessDomain {
  constructor(session) {
    super(session);
    this.enabled = false;
  }

  destructor() {
    this.disable();

    super.destructor();
  }

  // commands

  async enable() {
    if (!this.enabled) {
      this.enabled = true;
    }
  }

  disable() {
    if (this.enabled) {
      this.enabled = false;
    }
  }
}
PK
!<���;iKiK9chrome/remote/content/cdp/domains/content/Runtime.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { addDebuggerToGlobal } from "resource://gre/modules/jsdebugger.sys.mjs";

import { ContentProcessDomain } from "chrome://remote/content/cdp/domains/ContentProcessDomain.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  executeSoon: "chrome://remote/content/shared/Sync.sys.mjs",
  isChromeFrame: "chrome://remote/content/shared/Stack.sys.mjs",
  ExecutionContext:
    "chrome://remote/content/cdp/domains/content/runtime/ExecutionContext.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "ConsoleAPIStorage", () => {
  return Cc["@mozilla.org/consoleAPI-storage;1"].getService(
    Ci.nsIConsoleAPIStorage
  );
});

// Import the `Debugger` constructor in the current scope
// eslint-disable-next-line mozilla/reject-globalThis-modification
addDebuggerToGlobal(globalThis);

const CONSOLE_API_LEVEL_MAP = {
  warn: "warning",
};

// Bug 1786299: Puppeteer needs specific error messages.
const ERROR_CONTEXT_NOT_FOUND = "Cannot find context with specified id";

class SetMap extends Map {
  constructor() {
    super();
    this._count = 1;
  }
  // Every key in the map is associated with a Set.
  // The first time `key` is used `obj.set(key, value)` maps `key` to
  // to `Set(value)`. Subsequent calls add more values to the Set for `key`.
  // Note that `obj.get(key)` will return undefined if there's no such key,
  // as in a regular Map.
  set(key, value) {
    const innerSet = this.get(key);
    if (innerSet) {
      innerSet.add(value);
    } else {
      super.set(key, new Set([value]));
    }
    this._count++;
    return this;
  }
  // used as ExecutionContext id
  get count() {
    return this._count;
  }
}

export class Runtime extends ContentProcessDomain {
  constructor(session) {
    super(session);
    this.enabled = false;

    // Map of all the ExecutionContext instances:
    // [id (Number) => ExecutionContext instance]
    this.contexts = new Map();
    // [innerWindowId (Number) => Set of ExecutionContext instances]
    this.innerWindowIdToContexts = new SetMap();

    this._onContextCreated = this._onContextCreated.bind(this);
    this._onContextDestroyed = this._onContextDestroyed.bind(this);

    // TODO Bug 1602083
    this.session.contextObserver.on("context-created", this._onContextCreated);
    this.session.contextObserver.on(
      "context-destroyed",
      this._onContextDestroyed
    );
  }

  destructor() {
    this.disable();

    this.session.contextObserver.off("context-created", this._onContextCreated);
    this.session.contextObserver.off(
      "context-destroyed",
      this._onContextDestroyed
    );

    super.destructor();
  }

  // commands

  async enable() {
    if (!this.enabled) {
      this.enabled = true;

      Services.console.registerListener(this);
      this.onConsoleLogEvent = this.onConsoleLogEvent.bind(this);
      lazy.ConsoleAPIStorage.addLogEventListener(
        this.onConsoleLogEvent,
        Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
      );

      // Spin the event loop in order to send the `executionContextCreated` event right
      // after we replied to `enable` request.
      lazy.executeSoon(() => {
        this._onContextCreated("context-created", {
          windowId: this.content.windowGlobalChild.innerWindowId,
          window: this.content,
          isDefault: true,
        });

        for (const message of lazy.ConsoleAPIStorage.getEvents()) {
          this.onConsoleLogEvent(message);
        }
      });
    }
  }

  disable() {
    if (this.enabled) {
      this.enabled = false;

      Services.console.unregisterListener(this);
      lazy.ConsoleAPIStorage.removeLogEventListener(this.onConsoleLogEvent);
    }
  }

  releaseObject(options = {}) {
    const { objectId } = options;

    let context = null;
    for (const ctx of this.contexts.values()) {
      if (ctx.hasRemoteObject(objectId)) {
        context = ctx;
        break;
      }
    }
    if (!context) {
      throw new Error(ERROR_CONTEXT_NOT_FOUND);
    }
    context.releaseObject(objectId);
  }

  /**
   * Calls function with given declaration on the given object.
   *
   * Object group of the result is inherited from the target object.
   *
   * @param {object} options
   * @param {string} options.functionDeclaration
   *     Declaration of the function to call.
   * @param {Array.<object>=} options.arguments
   *     Call arguments. All call arguments must belong to the same
   *     JavaScript world as the target object.
   * @param {boolean=} options.awaitPromise
   *     Whether execution should `await` for resulting value
   *     and return once awaited promise is resolved.
   * @param {number=} options.executionContextId
   *     Specifies execution context which global object will be used
   *     to call function on. Either executionContextId or objectId
   *     should be specified.
   * @param {string=} options.objectId
   *     Identifier of the object to call function on.
   *     Either objectId or executionContextId should be specified.
   * @param {boolean=} options.returnByValue
   *     Whether the result is expected to be a JSON object
   *     which should be sent by value.
   *
   * @returns {RemoteObject & { exeptionDetails?: ExceptionDetails }}
   */
  callFunctionOn(options = {}) {
    if (typeof options.functionDeclaration != "string") {
      throw new TypeError("functionDeclaration: string value expected");
    }
    if (
      typeof options.arguments != "undefined" &&
      !Array.isArray(options.arguments)
    ) {
      throw new TypeError("arguments: array value expected");
    }
    if (!["undefined", "boolean"].includes(typeof options.awaitPromise)) {
      throw new TypeError("awaitPromise: boolean value expected");
    }
    if (!["undefined", "number"].includes(typeof options.executionContextId)) {
      throw new TypeError("executionContextId: number value expected");
    }
    if (!["undefined", "string"].includes(typeof options.objectId)) {
      throw new TypeError("objectId: string value expected");
    }
    if (!["undefined", "boolean"].includes(typeof options.returnByValue)) {
      throw new TypeError("returnByValue: boolean value expected");
    }

    if (
      typeof options.executionContextId == "undefined" &&
      typeof options.objectId == "undefined"
    ) {
      throw new Error(
        "Either objectId or executionContextId must be specified"
      );
    }

    let context = null;
    // When an `objectId` is passed, we want to execute the function of a given object
    // So we first have to find its ExecutionContext
    if (options.objectId) {
      for (const ctx of this.contexts.values()) {
        if (ctx.hasRemoteObject(options.objectId)) {
          context = ctx;
          break;
        }
      }
    } else {
      context = this.contexts.get(options.executionContextId);
    }

    if (!context) {
      throw new Error(ERROR_CONTEXT_NOT_FOUND);
    }

    return context.callFunctionOn(
      options.functionDeclaration,
      options.arguments,
      options.returnByValue,
      options.awaitPromise,
      options.objectId
    );
  }

  /**
   * Evaluate expression on global object.
   *
   * @param {object} options
   * @param {string} options.expression
   *     Expression to evaluate.
   * @param {boolean=} options.awaitPromise
   *     Whether execution should `await` for resulting value
   *     and return once awaited promise is resolved.
   * @param {number=} options.contextId
   *     Specifies in which execution context to perform evaluation.
   *     If the parameter is omitted the evaluation will be performed
   *     in the context of the inspected page.
   * @param {boolean=} options.returnByValue
   *     Whether the result is expected to be a JSON object
   *     that should be sent by value. Defaults to false.
   * @param {boolean=} options.userGesture [unsupported]
   *     Whether execution should be treated as initiated by user in the UI.
   *
   * @returns {RemoteObject & { exeptionDetails?: ExceptionDetails }}
   *     The evaluation result, and optionally exception details.
   */
  evaluate(options = {}) {
    const {
      expression,
      awaitPromise = false,
      contextId,
      returnByValue = false,
    } = options;

    if (typeof expression != "string") {
      throw new Error("expression: string value expected");
    }
    if (!["undefined", "boolean"].includes(typeof options.awaitPromise)) {
      throw new TypeError("awaitPromise: boolean value expected");
    }
    if (typeof returnByValue != "boolean") {
      throw new Error("returnByValue: boolean value expected");
    }

    let context;
    if (typeof contextId != "undefined") {
      context = this.contexts.get(contextId);
      if (!context) {
        throw new Error(ERROR_CONTEXT_NOT_FOUND);
      }
    } else {
      context = this._getDefaultContextForWindow();
    }

    return context.evaluate(expression, awaitPromise, returnByValue);
  }

  getProperties(options = {}) {
    const { objectId, ownProperties } = options;

    for (const ctx of this.contexts.values()) {
      const debuggerObj = ctx.getRemoteObject(objectId);
      if (debuggerObj) {
        return ctx.getProperties({ objectId, ownProperties });
      }
    }
    return null;
  }

  /**
   * Internal methods: the following methods are not part of CDP;
   * note the _ prefix.
   */

  get _debugger() {
    if (this.__debugger) {
      return this.__debugger;
    }
    this.__debugger = new Debugger();
    return this.__debugger;
  }

  _buildExceptionStackTrace(stack) {
    const callFrames = [];

    while (
      stack &&
      stack.source !== "debugger eval code" &&
      !stack.source.startsWith("chrome://")
    ) {
      callFrames.push({
        functionName: stack.functionDisplayName,
        scriptId: stack.sourceId.toString(),
        url: stack.source,
        lineNumber: stack.line - 1,
        columnNumber: stack.column - 1,
      });
      stack = stack.parent || stack.asyncParent;
    }

    return {
      callFrames,
    };
  }

  _buildConsoleStackTrace(stack = []) {
    const callFrames = stack
      .filter(frame => !lazy.isChromeFrame(frame))
      .map(frame => {
        return {
          functionName: frame.functionName,
          scriptId: frame.sourceId.toString(),
          url: frame.filename,
          lineNumber: frame.lineNumber - 1,
          columnNumber: frame.columnNumber - 1,
        };
      });

    return {
      callFrames,
    };
  }

  _getRemoteObject(objectId) {
    for (const ctx of this.contexts.values()) {
      const debuggerObj = ctx.getRemoteObject(objectId);
      if (debuggerObj) {
        return debuggerObj;
      }
    }
    return null;
  }

  _serializeRemoteObject(debuggerObj, executionContextId) {
    const ctx = this.contexts.get(executionContextId);
    return ctx._toRemoteObject(debuggerObj);
  }

  _getRemoteObjectByNodeId(nodeId, executionContextId) {
    let debuggerObj = null;

    if (typeof executionContextId != "undefined") {
      const ctx = this.contexts.get(executionContextId);
      debuggerObj = ctx.getRemoteObjectByNodeId(nodeId);
    } else {
      for (const ctx of this.contexts.values()) {
        const obj = ctx.getRemoteObjectByNodeId(nodeId);
        if (obj) {
          debuggerObj = obj;
          break;
        }
      }
    }

    return debuggerObj;
  }

  _setRemoteObject(debuggerObj, context) {
    return context.setRemoteObject(debuggerObj);
  }

  _getDefaultContextForWindow(innerWindowId) {
    if (!innerWindowId) {
      innerWindowId = this.content.windowGlobalChild.innerWindowId;
    }
    const curContexts = this.innerWindowIdToContexts.get(innerWindowId);
    if (curContexts) {
      for (const ctx of curContexts) {
        if (ctx.isDefault) {
          return ctx;
        }
      }
    }
    return null;
  }

  _getContextsForFrame(frameId) {
    const frameContexts = [];
    for (const ctx of this.contexts.values()) {
      if (ctx.frameId == frameId) {
        frameContexts.push(ctx);
      }
    }
    return frameContexts;
  }

  _emitConsoleAPICalled(payload) {
    // Filter out messages that aren't coming from a valid inner window, or from
    // a different browser tab. Also messages of type "time", which are not
    // getting reported by Chrome.
    const curBrowserId = this.session.browsingContext.browserId;
    const win = Services.wm.getCurrentInnerWindowWithId(payload.innerWindowId);
    if (
      !win ||
      BrowsingContext.getFromWindow(win).browserId != curBrowserId ||
      payload.type === "time"
    ) {
      return;
    }

    const context = this._getDefaultContextForWindow();
    this.emit("Runtime.consoleAPICalled", {
      args: payload.arguments.map(arg => context._toRemoteObject(arg)),
      executionContextId: context?.id || 0,
      timestamp: payload.timestamp,
      type: payload.type,
      stackTrace: this._buildConsoleStackTrace(payload.stack),
    });
  }

  _emitExceptionThrown(payload) {
    // Filter out messages that aren't coming from a valid inner window, or from
    // a different browser tab. Also messages of type "time", which are not
    // getting reported by Chrome.
    const curBrowserId = this.session.browsingContext.browserId;
    const win = Services.wm.getCurrentInnerWindowWithId(payload.innerWindowId);
    if (!win || BrowsingContext.getFromWindow(win).browserId != curBrowserId) {
      return;
    }

    const context = this._getDefaultContextForWindow();
    this.emit("Runtime.exceptionThrown", {
      timestamp: payload.timestamp,
      exceptionDetails: {
        // Temporary placeholder to return a number.
        exceptionId: 0,
        text: payload.text,
        lineNumber: payload.lineNumber,
        columnNumber: payload.columnNumber,
        url: payload.url,
        stackTrace: this._buildExceptionStackTrace(payload.stack),
        executionContextId: context?.id || undefined,
      },
    });
  }

  /**
   * Helper method in order to instantiate the ExecutionContext for a given
   * DOM Window as well as emitting the related
   * `Runtime.executionContextCreated` event
   *
   * @param {string} name
   *     Event name
   * @param {object=} options
   * @param {number} options.windowId
   *     The inner window id of the newly instantiated document.
   * @param {Window} options.window
   *     The window object of the newly instantiated document.
   * @param {string=} options.contextName
   *     Human-readable name to describe the execution context.
   * @param {boolean=} options.isDefault
   *     Whether the execution context is the default one.
   * @param {string=} options.contextType
   *     "default" or "isolated"
   *
   * @returns {number} ID of created context
   */
  _onContextCreated(name, options = {}) {
    const {
      windowId,
      window,
      contextName = "",
      isDefault = true,
      contextType = "default",
    } = options;

    if (windowId === undefined) {
      throw new Error("windowId is required");
    }

    // allow only one default context per inner window
    if (isDefault && this.innerWindowIdToContexts.has(windowId)) {
      for (const ctx of this.innerWindowIdToContexts.get(windowId)) {
        if (ctx.isDefault) {
          return null;
        }
      }
    }

    const context = new lazy.ExecutionContext(
      this._debugger,
      window,
      this.innerWindowIdToContexts.count,
      isDefault
    );
    this.contexts.set(context.id, context);
    this.innerWindowIdToContexts.set(windowId, context);

    if (this.enabled) {
      this.emit("Runtime.executionContextCreated", {
        context: {
          id: context.id,
          origin: window.origin,
          name: contextName,
          auxData: {
            isDefault,
            frameId: context.frameId,
            type: contextType,
          },
        },
      });
    }

    return context.id;
  }

  /**
   * Helper method to destroy the ExecutionContext of the given id. Also emit
   * the related `Runtime.executionContextDestroyed` and
   * `Runtime.executionContextsCleared` events.
   * ContextObserver will call this method with either `id` or `frameId` argument
   * being set.
   *
   * @param {string} name
   *     Event name
   * @param {object=} options
   * @param {number} options.id
   *     The execution context id to destroy.
   * @param {number} options.windowId
   *     The inner-window id of the execution context to destroy.
   * @param {number} options.frameId
   *     The frame id of execution context to destroy.
   * Either `id` or `frameId` or `windowId` is passed.
   */
  _onContextDestroyed(name, { id, frameId, windowId }) {
    let contexts;
    if ([id, frameId, windowId].filter(id => !!id).length > 1) {
      throw new Error("Expects only *one* of id, frameId, windowId");
    }

    if (id) {
      contexts = [this.contexts.get(id)];
    } else if (frameId) {
      contexts = this._getContextsForFrame(frameId);
    } else {
      contexts = this.innerWindowIdToContexts.get(windowId) || [];
    }

    for (const ctx of contexts) {
      const isFrame = !!BrowsingContext.get(ctx.frameId).parent;

      ctx.destructor();
      this.contexts.delete(ctx.id);
      this.innerWindowIdToContexts.get(ctx.windowId).delete(ctx);

      if (this.enabled) {
        this.emit("Runtime.executionContextDestroyed", {
          executionContextId: ctx.id,
        });
      }

      if (this.innerWindowIdToContexts.get(ctx.windowId).size == 0) {
        this.innerWindowIdToContexts.delete(ctx.windowId);
        // Only emit when all the exeuction contexts were cleared for the
        // current browser / target, which means it should only be emitted
        // for a top-level browsing context reference.
        if (this.enabled && !isFrame) {
          this.emit("Runtime.executionContextsCleared");
        }
      }
    }
  }

  onConsoleLogEvent(message) {
    // From sendConsoleAPIMessage (toolkit/modules/Console.sys.mjs)
    this._emitConsoleAPICalled({
      arguments: message.arguments,
      innerWindowId: message.innerID,
      stack: message.stacktrace,
      timestamp: message.timeStamp,
      type: CONSOLE_API_LEVEL_MAP[message.level] || message.level,
    });
  }

  // nsIObserver

  /**
   * Takes a console message belonging to the current window and emits a
   * "exceptionThrown" event if it's a Javascript error, otherwise a
   * "consoleAPICalled" event.
   *
   * @param {nsIConsoleMessage} subject
   *     Console message.
   */
  observe(subject) {
    if (subject instanceof Ci.nsIScriptError && subject.hasException) {
      let entry = fromScriptError(subject);
      this._emitExceptionThrown(entry);
    }
  }

  // XPCOM

  get QueryInterface() {
    return ChromeUtils.generateQI(["nsIConsoleListener"]);
  }
}

function fromScriptError(error) {
  // From dom/bindings/nsIScriptError.idl
  return {
    innerWindowId: error.innerWindowID,
    columnNumber: error.columnNumber - 1,
    lineNumber: error.lineNumber - 1,
    stack: error.stack,
    text: error.errorMessage,
    timestamp: error.timeStamp,
    url: error.sourceName,
  };
}
PK
!<����:chrome/remote/content/cdp/domains/content/Security.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ContentProcessDomain } from "chrome://remote/content/cdp/domains/ContentProcessDomain.sys.mjs";

export class Security extends ContentProcessDomain {
  constructor(session) {
    super(session);
    this.enabled = false;
  }

  destructor() {
    this.disable();

    super.destructor();
  }

  // commands

  async enable() {
    if (!this.enabled) {
      this.enabled = true;
    }
  }

  disable() {
    if (this.enabled) {
      this.enabled = false;
    }
  }
}
PK
!<̭:-�@�@Jchrome/remote/content/cdp/domains/content/runtime/ExecutionContext.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
});

const TYPED_ARRAY_CLASSES = [
  "Uint8Array",
  "Uint8ClampedArray",
  "Uint16Array",
  "Uint32Array",
  "Int8Array",
  "Int16Array",
  "Int32Array",
  "Float32Array",
  "Float64Array",
];

// Bug 1786299: Puppeteer expects specific error messages.
const ERROR_CYCLIC_REFERENCE = "Object reference chain is too long";
const ERROR_CANNOT_RETURN_BY_VALUE = "Object couldn't be returned by value";

function randomInt() {
  return crypto.getRandomValues(new Uint32Array(1))[0];
}

/**
 * This class represent a debuggable context onto which we can evaluate Javascript.
 * This is typically a document, but it could also be a worker, an add-on, ... or
 * any kind of context involving JS scripts.
 *
 * @param {Debugger} dbg
 *   A Debugger instance that we can use to inspect the given global.
 * @param {GlobalObject} debuggee
 *   The debuggable context's global object. This is typically the document window
 *   object. But it can also be any global object, like a worker global scope object.
 */
export class ExecutionContext {
  constructor(dbg, debuggee, id, isDefault) {
    this._debugger = dbg;
    this._debuggee = this._debugger.addDebuggee(debuggee);

    // Here, we assume that debuggee is a window object and we will propably have
    // to adapt that once we cover workers or contexts that aren't a document.
    this.window = debuggee;
    this.windowId = this.window.windowGlobalChild.innerWindowId;
    this.id = id;
    this.frameId = this.window.browsingContext.id.toString();
    this.isDefault = isDefault;

    // objectId => Debugger.Object
    this._remoteObjects = new Map();
  }

  destructor() {
    this._debugger.removeDebuggee(this._debuggee);
  }

  get browsingContext() {
    return this.window.browsingContext;
  }

  hasRemoteObject(objectId) {
    return this._remoteObjects.has(objectId);
  }

  getRemoteObject(objectId) {
    return this._remoteObjects.get(objectId);
  }

  getRemoteObjectByNodeId(nodeId) {
    for (const value of this._remoteObjects.values()) {
      if (value.nodeId == nodeId) {
        return value;
      }
    }

    return null;
  }

  releaseObject(objectId) {
    return this._remoteObjects.delete(objectId);
  }

  /**
   * Add a new debuggerObj to the object cache.
   *
   * Whenever an object is returned as reference, a new entry is added
   * to the internal object cache. It means the same underlying object or node
   * can be represented via multiple references.
   */
  setRemoteObject(debuggerObj) {
    const objectId = lazy.generateUUID();

    // TODO: Wrap Symbol into an object,
    // which would allow us to set the objectId.
    if (typeof debuggerObj == "object") {
      debuggerObj.objectId = objectId;
    }

    // For node objects add an unique identifier.
    if (
      debuggerObj instanceof Debugger.Object &&
      Node.isInstance(debuggerObj.unsafeDereference())
    ) {
      debuggerObj.nodeId = randomInt();
      // We do not differentiate between backendNodeId and nodeId (yet)
      debuggerObj.backendNodeId = debuggerObj.nodeId;
    }

    this._remoteObjects.set(objectId, debuggerObj);

    return objectId;
  }

  /**
   * Evaluate a Javascript expression.
   *
   * @param {string} expression
   *   The JS expression to evaluate against the JS context.
   * @param {boolean} awaitPromise
   *     Whether execution should `await` for resulting value
   *     and return once awaited promise is resolved.
   * @param {boolean} returnByValue
   *     Whether the result is expected to be a JSON object
   *     that should be sent by value.
   *
   * @returns {object} A multi-form object depending if the execution
   *   succeed or failed. If the expression failed to evaluate,
   *   it will return an object with an `exceptionDetails` attribute
   *   matching the `ExceptionDetails` CDP type. Otherwise it will
   *   return an object with `result` attribute whose type is
   *   `RemoteObject` CDP type.
   */
  async evaluate(expression, awaitPromise, returnByValue) {
    let rv = this._debuggee.executeInGlobal(expression);
    if (!rv) {
      return {
        exceptionDetails: {
          text: "Evaluation terminated!",
        },
      };
    }

    if (rv.throw) {
      return this._returnError(rv.throw);
    }

    let result = rv.return;

    if (result && result.isPromise && awaitPromise) {
      if (result.promiseState === "fulfilled") {
        result = result.promiseValue;
      } else if (result.promiseState === "rejected") {
        return this._returnError(result.promiseReason);
      } else {
        try {
          const promiseResult = await result.unsafeDereference();
          result = this._debuggee.makeDebuggeeValue(promiseResult);
        } catch (e) {
          // The promise has been rejected
          return this._returnError(this._debuggee.makeDebuggeeValue(e));
        }
      }
    }

    if (returnByValue) {
      result = this._toRemoteObjectByValue(result);
    } else {
      result = this._toRemoteObject(result);
    }

    return { result };
  }

  /**
   * Given a Debugger.Object reference for an Exception, return a JSON object
   * describing the exception by following CDP ExceptionDetails specification.
   */
  _returnError(exception) {
    if (
      this._debuggee.executeInGlobalWithBindings("exception instanceof Error", {
        exception,
      }).return
    ) {
      const text = this._debuggee.executeInGlobalWithBindings(
        "exception.message",
        { exception }
      ).return;
      return {
        exceptionDetails: {
          text,
        },
      };
    }

    // If that isn't an Error, consider the exception as a JS value
    return {
      exceptionDetails: {
        exception: this._toRemoteObject(exception),
      },
    };
  }

  async callFunctionOn(
    functionDeclaration,
    callArguments = [],
    returnByValue = false,
    awaitPromise = false,
    objectId = null
  ) {
    // Map the given objectId to a JS reference.
    let thisArg = null;
    if (objectId) {
      thisArg = this.getRemoteObject(objectId);
      if (!thisArg) {
        throw new Error(`Unable to get target object with id: ${objectId}`);
      }
    }

    // First evaluate the function
    const fun = this._debuggee.executeInGlobal("(" + functionDeclaration + ")");
    if (!fun) {
      return {
        exceptionDetails: {
          text: "Evaluation terminated!",
        },
      };
    }
    if (fun.throw) {
      return this._returnError(fun.throw);
    }

    // Then map all input arguments, which are matching CDP's CallArguments type,
    // into JS values
    const args = callArguments.map(arg => this._fromCallArgument(arg));

    // Finally, call the function with these arguments
    const rv = fun.return.apply(thisArg, args);
    if (rv.throw) {
      return this._returnError(rv.throw);
    }

    let result = rv.return;

    if (result && result.isPromise && awaitPromise) {
      if (result.promiseState === "fulfilled") {
        result = result.promiseValue;
      } else if (result.promiseState === "rejected") {
        return this._returnError(result.promiseReason);
      } else {
        try {
          const promiseResult = await result.unsafeDereference();
          result = this._debuggee.makeDebuggeeValue(promiseResult);
        } catch (e) {
          // The promise has been rejected
          return this._returnError(this._debuggee.makeDebuggeeValue(e));
        }
      }
    }

    if (returnByValue) {
      result = this._toRemoteObjectByValue(result);
    } else {
      result = this._toRemoteObject(result);
    }

    return { result };
  }

  getProperties({ objectId, ownProperties }) {
    let debuggerObj = this.getRemoteObject(objectId);
    if (!debuggerObj) {
      throw new Error("Could not find object with given id");
    }

    const result = [];
    const serializeObject = (debuggerObj, isOwn) => {
      for (const propertyName of debuggerObj.getOwnPropertyNames()) {
        const descriptor = debuggerObj.getOwnPropertyDescriptor(propertyName);
        result.push({
          name: propertyName,

          configurable: descriptor.configurable,
          enumerable: descriptor.enumerable,
          writable: descriptor.writable,
          value: this._toRemoteObject(descriptor.value),
          get: descriptor.get
            ? this._toRemoteObject(descriptor.get)
            : undefined,
          set: descriptor.set
            ? this._toRemoteObject(descriptor.set)
            : undefined,

          isOwn,
        });
      }
    };

    // When `ownProperties` is set to true, we only iterate over own properties.
    // Otherwise, we also iterate over propreties inherited from the prototype chain.
    serializeObject(debuggerObj, true);

    if (!ownProperties) {
      while (true) {
        debuggerObj = debuggerObj.proto;
        if (!debuggerObj) {
          break;
        }
        serializeObject(debuggerObj, false);
      }
    }

    return {
      result,
    };
  }

  /**
   * Given a CDP `CallArgument`, return a JS value that represent this argument.
   * Note that `CallArgument` is actually very similar to `RemoteObject`
   */
  _fromCallArgument(arg) {
    if (arg.objectId) {
      if (!this.hasRemoteObject(arg.objectId)) {
        throw new Error("Could not find object with given id");
      }
      return this.getRemoteObject(arg.objectId);
    }

    if (arg.unserializableValue) {
      switch (arg.unserializableValue) {
        case "-0":
          return -0;
        case "Infinity":
          return Infinity;
        case "-Infinity":
          return -Infinity;
        case "NaN":
          return NaN;
        default:
          if (/^\d+n$/.test(arg.unserializableValue)) {
            // eslint-disable-next-line no-undef
            return BigInt(arg.unserializableValue.slice(0, -1));
          }
          throw new Error("Couldn't parse value object in call argument");
      }
    }

    return this._deserialize(arg.value);
  }

  /**
   * Given a JS value, create a copy of it within the debugee compartment.
   */
  _deserialize(obj) {
    if (typeof obj !== "object") {
      return obj;
    }
    const result = this._debuggee.executeInGlobalWithBindings(
      "JSON.parse(obj)",
      { obj: JSON.stringify(obj) }
    );
    if (result.throw) {
      throw new Error("Unable to deserialize object");
    }
    return result.return;
  }

  /**
   * Given a `Debugger.Object` object, return a JSON-serializable description of it
   * matching `RemoteObject` CDP type.
   *
   * @param {Debugger.Object} debuggerObj
   *  The object to serialize
   * @returns {RemoteObject}
   *  The serialized description of the given object
   */
  _toRemoteObject(debuggerObj) {
    const result = {};

    // First handle all non-primitive values which are going to be wrapped by the
    // Debugger API into Debugger.Object instances
    if (debuggerObj instanceof Debugger.Object) {
      const rawObj = debuggerObj.unsafeDereference();

      result.objectId = this.setRemoteObject(debuggerObj);
      result.type = typeof rawObj;

      // Map the Debugger API `class` attribute to CDP `subtype`
      const cls = debuggerObj.class;
      if (debuggerObj.isProxy) {
        result.subtype = "proxy";
      } else if (cls == "Array") {
        result.subtype = "array";
      } else if (cls == "RegExp") {
        result.subtype = "regexp";
      } else if (cls == "Date") {
        result.subtype = "date";
      } else if (cls == "Map") {
        result.subtype = "map";
      } else if (cls == "Set") {
        result.subtype = "set";
      } else if (cls == "WeakMap") {
        result.subtype = "weakmap";
      } else if (cls == "WeakSet") {
        result.subtype = "weakset";
      } else if (cls == "Error") {
        result.subtype = "error";
      } else if (cls == "Promise") {
        result.subtype = "promise";
      } else if (TYPED_ARRAY_CLASSES.includes(cls)) {
        result.subtype = "typedarray";
      } else if (Node.isInstance(rawObj)) {
        result.subtype = "node";
        result.className = ChromeUtils.getClassName(rawObj);
        result.description = rawObj.localName || rawObj.nodeName;
        if (rawObj.id) {
          result.description += `#${rawObj.id}`;
        }
      }
      return result;
    }

    // Now, handle all values that Debugger API isn't wrapping into Debugger.API.
    // This is all the primitive JS types.
    result.type = typeof debuggerObj;

    // Symbol and BigInt are primitive values but aren't serializable.
    // CDP expects them to be considered as objects, with an objectId to later inspect
    // them.
    if (result.type == "symbol") {
      result.description = debuggerObj.toString();
      result.objectId = this.setRemoteObject(debuggerObj);

      return result;
    }

    // A few primitive type can't be serialized and CDP has special case for them
    if (Object.is(debuggerObj, NaN)) {
      result.unserializableValue = "NaN";
    } else if (Object.is(debuggerObj, -0)) {
      result.unserializableValue = "-0";
    } else if (Object.is(debuggerObj, Infinity)) {
      result.unserializableValue = "Infinity";
    } else if (Object.is(debuggerObj, -Infinity)) {
      result.unserializableValue = "-Infinity";
    } else if (result.type == "bigint") {
      result.unserializableValue = `${debuggerObj}n`;
    }

    if (result.unserializableValue) {
      result.description = result.unserializableValue;
      return result;
    }

    // Otherwise, we serialize the primitive values as-is via `value` attribute
    result.value = debuggerObj;

    // null is special as it has a dedicated subtype
    if (debuggerObj === null) {
      result.subtype = "null";
    }

    return result;
  }

  /**
   * Given a `Debugger.Object` object, return a JSON-serializable description of it
   * matching `RemoteObject` CDP type.
   *
   * @param {Debugger.Object} debuggerObj
   *  The object to serialize
   * @returns {RemoteObject}
   *  The serialized description of the given object
   */
  _toRemoteObjectByValue(debuggerObj) {
    const type = typeof debuggerObj;

    if (type == "undefined") {
      return { type };
    }

    let unserializableValue;
    if (Object.is(debuggerObj, -0)) {
      unserializableValue = "-0";
    } else if (Object.is(debuggerObj, NaN)) {
      unserializableValue = "NaN";
    } else if (Object.is(debuggerObj, Infinity)) {
      unserializableValue = "Infinity";
    } else if (Object.is(debuggerObj, -Infinity)) {
      unserializableValue = "-Infinity";
    } else if (typeof debuggerObj == "bigint") {
      unserializableValue = `${debuggerObj}n`;
    }

    if (unserializableValue) {
      return {
        type,
        unserializableValue,
        description: unserializableValue,
      };
    }

    const value = this._serialize(debuggerObj);
    return {
      type: typeof value,
      value,
      description: value != null ? value.toString() : value,
    };
  }

  /**
   * Convert a given `Debugger.Object` to an object.
   *
   * @param {Debugger.Object} debuggerObj
   *  The object to convert
   *
   * @returns {object}
   *  The converted object
   */
  _serialize(debuggerObj) {
    const result = this._debuggee.executeInGlobalWithBindings(
      `
      JSON.stringify(e, (key, value) => {
        if (typeof value === "symbol") {
          // CDP cannot return Symbols
          throw new Error();
        }

        return value;
      });
    `,
      { e: debuggerObj }
    );
    if (result.throw) {
      const exception = this._toRawObject(result.throw);
      if (exception.message === "cyclic object value") {
        throw new Error(ERROR_CYCLIC_REFERENCE);
      }

      throw new Error(ERROR_CANNOT_RETURN_BY_VALUE);
    }

    return JSON.parse(result.return);
  }

  _toRawObject(maybeDebuggerObject) {
    if (maybeDebuggerObject instanceof Debugger.Object) {
      // Retrieve the referent for the provided Debugger.object.
      // See https://firefox-source-docs.mozilla.org/devtools-user/debugger-api/debugger.object/index.html
      const rawObject = maybeDebuggerObject.unsafeDereference();
      return Cu.waiveXrays(rawObject);
    }

    // If maybeDebuggerObject was not a Debugger.Object, it is a primitive value
    // which can be used as is.
    return maybeDebuggerObject;
  }
}
PK
!<(N�HH8chrome/remote/content/cdp/domains/parent/Browser.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Domain } from "chrome://remote/content/cdp/domains/Domain.sys.mjs";

export class Browser extends Domain {
  getVersion() {
    const { isHeadless } = Cc["@mozilla.org/gfx/info;1"].getService(
      Ci.nsIGfxInfo
    );
    const { userAgent } = Cc[
      "@mozilla.org/network/protocol;1?name=http"
    ].getService(Ci.nsIHttpProtocolHandler);
    return {
      jsVersion: Services.appinfo.version,
      protocolVersion: "1.3",
      product:
        (isHeadless ? "Headless" : "") +
        `${Services.appinfo.name}/${Services.appinfo.version}`,
      revision: Services.appinfo.sourceURL.split("/").pop(),
      userAgent,
    };
  }

  close() {
    // Notify all windows that an application quit has been requested.
    const cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
      Ci.nsISupportsPRBool
    );
    Services.obs.notifyObservers(cancelQuit, "quit-application-requested");

    // If the shutdown of the application is prevented force quit it instead.
    const mode = cancelQuit.data
      ? Ci.nsIAppStartup.eForceQuit
      : Ci.nsIAppStartup.eAttemptQuit;

    Services.startup.quit(mode);
  }
}
PK
!<��W�:chrome/remote/content/cdp/domains/parent/Emulation.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Domain } from "chrome://remote/content/cdp/domains/Domain.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
});

const MAX_WINDOW_SIZE = 10000000;

export class Emulation extends Domain {
  destructor() {
    this.setUserAgentOverride({ userAgent: "", platform: "" });

    super.destructor();
  }

  /**
   * Overrides the values of device screen dimensions.
   *
   * Values as modified are:
   *   - window.screen.width
   *   - window.screen.height
   *   - window.innerWidth
   *   - window.innerHeight
   *   - "device-width"/"device-height"-related CSS media query results
   *
   * @param {object} options
   * @param {number} options.width
   *     Overriding width value in pixels. 0 disables the override.
   * @param {number} options.height
   *     Overriding height value in pixels. 0 disables the override.
   * @param {number} options.deviceScaleFactor
   *     Overriding device scale factor value. 0 disables the override.
   * @param {number} options.mobile [not supported]
   *     Whether to emulate a mobile device. This includes viewport meta tag,
   *     overlay scrollbars, text autosizing and more.
   * @param {number} options.screenOrientation
   *     Screen orientation override [not supported]
   */
  async setDeviceMetricsOverride(options = {}) {
    const { width, height, deviceScaleFactor } = options;

    if (
      width < 0 ||
      width > MAX_WINDOW_SIZE ||
      height < 0 ||
      height > MAX_WINDOW_SIZE
    ) {
      throw new TypeError(
        `Width and height values must be positive, not greater than ${MAX_WINDOW_SIZE}`
      );
    }

    if (typeof deviceScaleFactor != "number") {
      throw new TypeError("deviceScaleFactor: number expected");
    }

    if (deviceScaleFactor < 0) {
      throw new TypeError("deviceScaleFactor: must be positive");
    }

    const { tab } = this.session.target;
    const { linkedBrowser: browser } = tab;

    const { browsingContext } = this.session.target;
    browsingContext.overrideDPPX = deviceScaleFactor;

    // With a value of 0 the current size is used
    const { layoutViewport } = await this.session.execute(
      this.session.id,
      "Page",
      "getLayoutMetrics"
    );

    const targetWidth = width > 0 ? width : layoutViewport.clientWidth;
    const targetHeight = height > 0 ? height : layoutViewport.clientHeight;

    browser.style.setProperty("min-width", targetWidth + "px");
    browser.style.setProperty("max-width", targetWidth + "px");
    browser.style.setProperty("min-height", targetHeight + "px");
    browser.style.setProperty("max-height", targetHeight + "px");

    // Wait until the viewport has been resized
    await this.executeInChild("_awaitViewportDimensions", {
      width: targetWidth,
      height: targetHeight,
    });
  }

  /**
   * Enables touch on platforms which do not support them.
   *
   * @param {object} options
   * @param {boolean} options.enabled
   *     Whether the touch event emulation should be enabled.
   * @param {number=} options.maxTouchPoints [not yet supported]
   *     Maximum touch points supported. Defaults to one.
   */
  async setTouchEmulationEnabled(options = {}) {
    const { enabled } = options;

    if (typeof enabled != "boolean") {
      throw new TypeError(
        "Invalid parameters (enabled: boolean value expected)"
      );
    }

    const { browsingContext } = this.session.target;
    if (enabled) {
      browsingContext.touchEventsOverride = "enabled";
    } else {
      browsingContext.touchEventsOverride = "none";
    }
  }

  /**
   * Allows overriding user agent with the given string.
   *
   * @param {object} options
   * @param {string} options.userAgent
   *     User agent to use.
   * @param {string=} options.acceptLanguage [not yet supported]
   *     Browser langugage to emulate.
   * @param {string=} options.platform
   *     The platform navigator.platform should return.
   */
  async setUserAgentOverride(options = {}) {
    const { userAgent, platform } = options;

    if (typeof userAgent != "string") {
      throw new TypeError(
        "Invalid parameters (userAgent: string value expected)"
      );
    }

    if (!["undefined", "string"].includes(typeof platform)) {
      throw new TypeError("platform: string value expected");
    }

    const { browsingContext } = this.session.target;

    if (!userAgent.length) {
      browsingContext.customUserAgent = null;
    } else if (this._isValidHTTPRequestHeaderValue(userAgent)) {
      browsingContext.customUserAgent = userAgent;
    } else {
      throw new TypeError("Invalid characters found in userAgent");
    }

    if (platform?.length > 0) {
      browsingContext.customPlatform = platform;
    } else {
      browsingContext.customPlatform = null;
    }
  }

  _isValidHTTPRequestHeaderValue(value) {
    try {
      const channel = lazy.NetUtil.newChannel({
        uri: "http://localhost",
        loadUsingSystemPrincipal: true,
      });
      channel.QueryInterface(Ci.nsIHttpChannel);
      channel.setRequestHeader("X-check", value, false);
      return true;
    } catch (e) {
      return false;
    }
  }
}
PK
!<X����6chrome/remote/content/cdp/domains/parent/Fetch.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Domain } from "chrome://remote/content/cdp/domains/Domain.sys.mjs";

// Note: For now this domain has only been added so that clients using CDP
// (like Selenium) don't break when trying to disable Fetch events.

export class Fetch extends Domain {
  constructor(session) {
    super(session);

    this.enabled = false;
  }

  destructor() {
    this.disable();

    super.destructor();
  }

  disable() {
    if (!this.enabled) {
      return;
    }

    this.enabled = false;
  }
}
PK
!<i��V��3chrome/remote/content/cdp/domains/parent/IO.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Domain } from "chrome://remote/content/cdp/domains/Domain.sys.mjs";
import { StreamRegistry } from "chrome://remote/content/cdp/StreamRegistry.sys.mjs";

const DEFAULT_CHUNK_SIZE = 10 * 1024 * 1024;

// Global singleton for managing open streams
export const streamRegistry = new StreamRegistry();

export class IO extends Domain {
  // commands

  /**
   * Close the stream, discard any temporary backing storage.
   *
   * @param {object} options
   * @param {string} options.handle
   *     Handle of the stream to close.
   */
  async close(options = {}) {
    const { handle } = options;

    if (typeof handle != "string") {
      throw new TypeError(`handle: string value expected`);
    }

    await streamRegistry.remove(handle);
  }

  /**
   * Read a chunk of the stream.
   *
   * @param {object} options
   * @param {string} options.handle
   *     Handle of the stream to read.
   * @param {number=} options.offset
   *     Seek to the specified offset before reading. If not specificed,
   *     proceed with offset following the last read.
   *     Some types of streams may only support sequential reads.
   * @param {number=} options.size
   *     Maximum number of bytes to read (left upon the agent
   *     discretion if not specified).
   *
   * @returns {object}
   *     Data that were read, including flags for base64-encoded, and end-of-file reached.
   */
  async read(options = {}) {
    const { handle, offset, size } = options;

    if (typeof handle != "string") {
      throw new TypeError(`handle: string value expected`);
    }

    const stream = streamRegistry.get(handle);

    if (typeof offset != "undefined") {
      if (typeof offset != "number") {
        throw new TypeError(`offset: integer value expected`);
      }

      await stream.seek(offset);
    }

    const remainingBytes = await stream.available();

    let chunkSize;
    if (typeof size != "undefined") {
      if (typeof size != "number") {
        throw new TypeError(`size: integer value expected`);
      }

      // Chromium currently crashes for negative sizes (https://bit.ly/2P6h0Fv),
      // but might behave similar to the offset and clip invalid values
      chunkSize = Math.max(0, Math.min(size, remainingBytes));
    } else {
      chunkSize = Math.min(DEFAULT_CHUNK_SIZE, remainingBytes);
    }

    const bytes = await stream.readBytes(chunkSize);

    // Each UCS2 character has an upper byte of 0 and a lower byte matching
    // the binary data. Using a loop here prevents us from hitting the browser's
    // internal `arguments.length` limit.
    const ARGS_MAX = 262144;
    const stringData = [];
    for (let i = 0; i < bytes.length; i += ARGS_MAX) {
      let argsChunk = Math.min(bytes.length, i + ARGS_MAX);
      stringData.push(
        String.fromCharCode.apply(null, bytes.slice(i, argsChunk))
      );
    }
    const data = btoa(stringData.join(""));

    return {
      data,
      base64Encoded: true,
      eof: remainingBytes - bytes.length == 0,
    };
  }
}
PK
!<}��??6chrome/remote/content/cdp/domains/parent/Input.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Domain } from "chrome://remote/content/cdp/domains/Domain.sys.mjs";

export class Input extends Domain {
  // commands

  /**
   * Simulate key events.
   *
   * @param {object} options
   *        - autoRepeat (not supported)
   *        - code (not supported)
   *        - key
   *        - isKeypad (not supported)
   *        - location (not supported)
   *        - modifiers
   *        - text (not supported)
   *        - type
   *        - unmodifiedText (not supported)
   *        - windowsVirtualKeyCode
   *        - nativeVirtualKeyCode (not supported)
   *        - keyIdentifier (not supported)
   *        - isSystemKey (not supported)
   */
  async dispatchKeyEvent(options = {}) {
    // missing code, text, unmodifiedText, autorepeat, location, iskeypad
    const { key, modifiers, type, windowsVirtualKeyCode } = options;
    const { alt, ctrl, meta, shift } = Input.Modifier;

    let domType;
    if (type == "keyDown" || type == "rawKeyDown") {
      // 'rawKeyDown' is passed as type by puppeteer for all non-text keydown events:
      // See https://github.com/GoogleChrome/puppeteer/blob/2d99d85976dcb28cc6e3bad4b6a00cd61a67a2cf/lib/Input.js#L52
      // For now we simply map rawKeyDown to keydown.
      domType = "keydown";
    } else if (type == "keyUp" || type == "char") {
      // 'char' is fired as a single key event. Behind the scenes it will trigger keydown,
      // keypress and keyup. `domType` will only be used as the event to wait for.
      domType = "keyup";
    } else {
      throw new Error(`Unknown key event type ${type}`);
    }

    const { browser } = this.session.target;
    const browserWindow = browser.ownerGlobal;

    const EventUtils = this._getEventUtils(browserWindow);
    const eventId = await this.executeInChild(
      "_addContentEventListener",
      domType
    );

    if (type == "char") {
      // type == "char" is used when doing `await page.keyboard.type( 'I’m a list' );`
      // the ’ character will be calling dispatchKeyEvent only once with type=char.
      EventUtils.synthesizeKey(key, {}, browserWindow);
    } else {
      // Non printable keys should be prefixed with `KEY_`
      const eventUtilsKey = key.length == 1 ? key : "KEY_" + key;
      const eventInfo = {
        keyCode: windowsVirtualKeyCode,
        type: domType,
        altKey: !!(modifiers & alt),
        ctrlKey: !!(modifiers & ctrl),
        metaKey: !!(modifiers & meta),
        shiftKey: !!(modifiers & shift),
      };
      EventUtils.synthesizeKey(eventUtilsKey, eventInfo, browserWindow);
    }

    await this.executeInChild("_waitForContentEvent", eventId);
  }

  /**
   * Simulate mouse events.
   *
   * @param {object} options
   * @param {string} options.type
   * @param {number} options.x
   * @param {number} options.y
   * @param {number} options.modifiers
   * @param {number} options.timestamp [Not Supported]
   * @param {string} options.button
   * @param {number} options.buttons [Not Supported]
   * @param {string} options.clickCount
   * @param {number} options.deltaX [Not Supported]
   * @param {number} options.deltaY [Not Supported]
   * @param {string} options.pointerType [Not Supported]
   */
  async dispatchMouseEvent(options = {}) {
    const { button, clickCount, modifiers, type, x, y } = options;
    const { alt, ctrl, meta, shift } = Input.Modifier;

    let domType;
    if (type === "mousePressed") {
      domType = "mousedown";
    } else if (type === "mouseReleased") {
      domType = "mouseup";
    } else if (type === "mouseMoved") {
      domType = "mousemove";
    } else {
      throw new Error(`Mouse type is not supported: ${type}`);
    }

    if (domType === "mousedown" && button === "right") {
      domType = "contextmenu";
    }

    const buttonID = Input.Button[button] || Input.Button.left;
    const { browser } = this.session.target;
    const currentWindow = browser.ownerGlobal;

    const EventUtils = this._getEventUtils(currentWindow);
    const eventId = await this.executeInChild(
      "_addContentEventListener",
      domType
    );

    EventUtils.synthesizeMouse(browser, x, y, {
      type: domType,
      button: buttonID,
      clickCount: clickCount || 1,
      altKey: !!(modifiers & alt),
      ctrlKey: !!(modifiers & ctrl),
      metaKey: !!(modifiers & meta),
      shiftKey: !!(modifiers & shift),
    });

    await this.executeInChild("_waitForContentEvent", eventId);
  }

  /**
   * Memoized EventUtils getter.
   */
  _getEventUtils(win) {
    if (!this._eventUtils) {
      this._eventUtils = {
        window: win,
        parent: win,
        _EU_Ci: Ci,
        _EU_Cc: Cc,
      };
      Services.scriptloader.loadSubScript(
        "chrome://remote/content/external/EventUtils.js",
        this._eventUtils
      );
    }
    return this._eventUtils;
  }
}

Input.Button = {
  left: 0,
  middle: 1,
  right: 2,
  back: 3,
  forward: 4,
};

Input.Modifier = {
  alt: 1,
  ctrl: 2,
  meta: 4,
  shift: 8,
};
PK
!<Be�]�:�:8chrome/remote/content/cdp/domains/parent/Network.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Domain } from "chrome://remote/content/cdp/domains/Domain.sys.mjs";

const MAX_COOKIE_EXPIRY = Number.MAX_SAFE_INTEGER;

const LOAD_CAUSE_STRINGS = {
  [Ci.nsIContentPolicy.TYPE_INVALID]: "Invalid",
  [Ci.nsIContentPolicy.TYPE_OTHER]: "Other",
  [Ci.nsIContentPolicy.TYPE_SCRIPT]: "Script",
  [Ci.nsIContentPolicy.TYPE_IMAGE]: "Img",
  [Ci.nsIContentPolicy.TYPE_STYLESHEET]: "Stylesheet",
  [Ci.nsIContentPolicy.TYPE_OBJECT]: "Object",
  [Ci.nsIContentPolicy.TYPE_DOCUMENT]: "Document",
  [Ci.nsIContentPolicy.TYPE_SUBDOCUMENT]: "Subdocument",
  [Ci.nsIContentPolicy.TYPE_PING]: "Ping",
  [Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST]: "Xhr",
  [Ci.nsIContentPolicy.TYPE_OBJECT_SUBREQUEST]: "ObjectSubdoc",
  [Ci.nsIContentPolicy.TYPE_DTD]: "Dtd",
  [Ci.nsIContentPolicy.TYPE_FONT]: "Font",
  [Ci.nsIContentPolicy.TYPE_MEDIA]: "Media",
  [Ci.nsIContentPolicy.TYPE_WEBSOCKET]: "Websocket",
  [Ci.nsIContentPolicy.TYPE_CSP_REPORT]: "Csp",
  [Ci.nsIContentPolicy.TYPE_XSLT]: "Xslt",
  [Ci.nsIContentPolicy.TYPE_BEACON]: "Beacon",
  [Ci.nsIContentPolicy.TYPE_FETCH]: "Fetch",
  [Ci.nsIContentPolicy.TYPE_IMAGESET]: "Imageset",
  [Ci.nsIContentPolicy.TYPE_WEB_MANIFEST]: "WebManifest",
  [Ci.nsIContentPolicy.TYPE_WEB_IDENTITY]: "Webidentity",
};

export class Network extends Domain {
  constructor(session) {
    super(session);
    this.enabled = false;

    this._onRequest = this._onRequest.bind(this);
    this._onResponse = this._onResponse.bind(this);
  }

  destructor() {
    this.disable();

    super.destructor();
  }

  enable() {
    if (this.enabled) {
      return;
    }
    this.enabled = true;
    this.session.networkObserver.startTrackingBrowserNetwork(
      this.session.target.browser
    );
    this.session.networkObserver.on("request", this._onRequest);
    this.session.networkObserver.on("response", this._onResponse);
  }

  disable() {
    if (!this.enabled) {
      return;
    }
    this.session.networkObserver.stopTrackingBrowserNetwork(
      this.session.target.browser
    );
    this.session.networkObserver.off("request", this._onRequest);
    this.session.networkObserver.off("response", this._onResponse);
    this.enabled = false;
  }

  /**
   * Deletes browser cookies with matching name and url or domain/path pair.
   *
   * @param {object} options
   * @param {string} options.name
   *     Name of the cookies to remove.
   * @param {string=} options.url
   *     If specified, deletes all the cookies with the given name
   *     where domain and path match provided URL.
   * @param {string=} options.domain
   *     If specified, deletes only cookies with the exact domain.
   * @param {string=} options.path
   *     If specified, deletes only cookies with the exact path.
   */
  async deleteCookies(options = {}) {
    const { domain, name, path = "/", url } = options;

    if (typeof name != "string") {
      throw new TypeError("name: string value expected");
    }

    if (!url && !domain) {
      throw new TypeError(
        "At least one of the url and domain needs to be specified"
      );
    }

    // Retrieve host. Check domain first because it has precedence.
    let hostname = domain || "";
    if (!hostname.length) {
      const cookieURL = new URL(url);
      if (!["http:", "https:"].includes(cookieURL.protocol)) {
        throw new TypeError("An http or https url must be specified");
      }
      hostname = cookieURL.hostname;
    }

    const cookiesFound = Services.cookies.getCookiesWithOriginAttributes(
      JSON.stringify({}),
      hostname
    );

    for (const cookie of cookiesFound) {
      if (cookie.name == name && cookie.path.startsWith(path)) {
        Services.cookies.remove(
          cookie.host,
          cookie.name,
          cookie.path,
          cookie.originAttributes
        );
      }
    }
  }

  /**
   * Activates emulation of network conditions.
   *
   * @param {object} options
   * @param {boolean} options.offline
   *     True to emulate internet disconnection.
   */
  emulateNetworkConditions(options = {}) {
    const { offline } = options;

    if (typeof offline != "boolean") {
      throw new TypeError("offline: boolean value expected");
    }

    Services.io.offline = offline;
  }

  /**
   * Returns all browser cookies.
   *
   * Depending on the backend support, will return detailed cookie information in the cookies field.
   *
   * @returns {Array<Cookie>}
   *     Array of cookie objects.
   */
  async getAllCookies() {
    const cookies = [];
    for (const cookie of Services.cookies.cookies) {
      cookies.push(_buildCookie(cookie));
    }

    return { cookies };
  }

  /**
   * Returns all browser cookies for the current URL.
   *
   * @param {object} options
   * @param {Array<string>=} options.urls
   *     The list of URLs for which applicable cookies will be fetched.
   *     Defaults to the currently open URL.
   *
   * @returns {Array<Cookie>}
   *     Array of cookie objects.
   */
  async getCookies(options = {}) {
    const { urls = this._getDefaultUrls() } = options;

    if (!Array.isArray(urls)) {
      throw new TypeError("urls: array expected");
    }

    for (const [index, url] of urls.entries()) {
      if (typeof url !== "string") {
        throw new TypeError(`urls: string value expected at index ${index}`);
      }
    }

    const cookies = [];
    for (let url of urls) {
      url = new URL(url);

      const secureProtocol = ["https:", "wss:"].includes(url.protocol);

      const cookiesFound = Services.cookies.getCookiesWithOriginAttributes(
        JSON.stringify({}),
        url.hostname
      );

      for (const cookie of cookiesFound) {
        // Ignore secure cookies for non-secure protocols
        if (cookie.isSecure && !secureProtocol) {
          continue;
        }

        // Ignore cookies which do not match the given path
        if (!url.pathname.startsWith(cookie.path)) {
          continue;
        }

        const builtCookie = _buildCookie(cookie);
        const duplicateCookie = cookies.some(value => {
          return (
            value.name === builtCookie.name &&
            value.path === builtCookie.path &&
            value.domain === builtCookie.domain
          );
        });

        if (duplicateCookie) {
          continue;
        }

        cookies.push(builtCookie);
      }
    }

    return { cookies };
  }

  /**
   * Sets a cookie with the given cookie data.
   *
   * Note that it may overwrite equivalent cookies if they exist.
   *
   * @param {object} cookie
   * @param {string} cookie.name
   *     Cookie name.
   * @param {string} cookie.value
   *     Cookie value.
   * @param {string=} cookie.domain
   *     Cookie domain.
   * @param {number=} cookie.expires
   *     Cookie expiration date, session cookie if not set.
   * @param {boolean=} cookie.httpOnly
   *     True if cookie is http-only.
   * @param {string=} cookie.path
   *     Cookie path.
   * @param {string=} cookie.sameSite
   *     Cookie SameSite type.
   * @param {boolean=} cookie.secure
   *     True if cookie is secure.
   * @param {string=} cookie.url
   *     The request-URI to associate with the setting of the cookie.
   *     This value can affect the default domain and path values of the
   *     created cookie.
   *
   * @returns {boolean}
   *     True if successfully set cookie.
   */
  setCookie(cookie) {
    if (typeof cookie.name != "string") {
      throw new TypeError("name: string value expected");
    }

    if (typeof cookie.value != "string") {
      throw new TypeError("value: string value expected");
    }

    if (
      typeof cookie.url == "undefined" &&
      typeof cookie.domain == "undefined"
    ) {
      throw new TypeError(
        "At least one of the url and domain needs to be specified"
      );
    }

    // Retrieve host. Check domain first because it has precedence.
    let hostname = cookie.domain || "";
    let cookieURL;
    let schemeType = Ci.nsICookie.SCHEME_UNSET;
    if (!hostname.length) {
      try {
        cookieURL = new URL(cookie.url);
      } catch (e) {
        return { success: false };
      }

      if (!["http:", "https:"].includes(cookieURL.protocol)) {
        throw new TypeError(`Invalid protocol ${cookieURL.protocol}`);
      }

      if (cookieURL.protocol == "https:") {
        cookie.secure = true;
        schemeType = Ci.nsICookie.SCHEME_HTTPS;
      } else {
        schemeType = Ci.nsICookie.SCHEME_HTTP;
      }

      hostname = cookieURL.hostname;
    }

    if (typeof cookie.path == "undefined") {
      cookie.path = "/";
    }

    let isSession = false;
    if (typeof cookie.expires == "undefined") {
      isSession = true;
      cookie.expires = MAX_COOKIE_EXPIRY;
    }

    const sameSiteMap = new Map([
      ["None", Ci.nsICookie.SAMESITE_NONE],
      ["Lax", Ci.nsICookie.SAMESITE_LAX],
      ["Strict", Ci.nsICookie.SAMESITE_STRICT],
    ]);

    let success = true;
    try {
      Services.cookies.add(
        hostname,
        cookie.path,
        cookie.name,
        cookie.value,
        cookie.secure,
        cookie.httpOnly || false,
        isSession,
        cookie.expires,
        {} /* originAttributes */,
        sameSiteMap.get(cookie.sameSite),
        schemeType
      );
    } catch (e) {
      success = false;
    }

    return { success };
  }

  /**
   * Sets given cookies.
   *
   * @param {object} options
   * @param {Array.<Cookie>} options.cookies
   *     Cookies to be set.
   */
  setCookies(options = {}) {
    const { cookies } = options;

    if (!Array.isArray(cookies)) {
      throw new TypeError("Invalid parameters (cookies: array expected)");
    }

    cookies.forEach(cookie => {
      const { success } = this.setCookie(cookie);
      if (!success) {
        throw new Error("Invalid cookie fields");
      }
    });
  }

  /**
   * Toggles ignoring cache for each request. If true, cache will not be used.
   *
   * @param {object} options
   * @param {boolean} options.cacheDisabled
   *     Cache disabled state.
   */
  async setCacheDisabled(options = {}) {
    const { cacheDisabled = false } = options;

    const { INHIBIT_CACHING, LOAD_BYPASS_CACHE, LOAD_NORMAL } = Ci.nsIRequest;

    let loadFlags = LOAD_NORMAL;
    if (cacheDisabled) {
      loadFlags = LOAD_BYPASS_CACHE | INHIBIT_CACHING;
    }

    await this.executeInChild("_updateLoadFlags", loadFlags);
  }

  /**
   * Allows overriding user agent with the given string.
   *
   * Redirected to Emulation.setUserAgentOverride.
   */
  setUserAgentOverride(options = {}) {
    const { id } = this.session;
    this.session.execute(id, "Emulation", "setUserAgentOverride", options);
  }

  _onRequest(eventName, httpChannel, data) {
    const wrappedChannel = ChannelWrapper.get(httpChannel);
    const urlFragment = httpChannel.URI.hasRef
      ? "#" + httpChannel.URI.ref
      : undefined;

    const request = {
      url: httpChannel.URI.specIgnoringRef,
      urlFragment,
      method: httpChannel.requestMethod,
      headers: headersAsObject(data.headers),
      postData: undefined,
      hasPostData: false,
      mixedContentType: undefined,
      initialPriority: undefined,
      referrerPolicy: undefined,
      isLinkPreload: false,
    };
    this.emit("Network.requestWillBeSent", {
      requestId: data.requestId,
      loaderId: data.loaderId,
      documentURL:
        wrappedChannel.documentURL || httpChannel.URI.specIgnoringRef,
      request,
      timestamp: Date.now() / 1000,
      wallTime: undefined,
      initiator: undefined,
      redirectResponse: undefined,
      type: LOAD_CAUSE_STRINGS[data.cause] || "unknown",
      frameId: data.frameId.toString(),
      hasUserGesture: undefined,
    });
  }

  _onResponse(eventName, httpChannel, data) {
    const wrappedChannel = ChannelWrapper.get(httpChannel);
    const headers = headersAsObject(data.headers);

    this.emit("Network.responseReceived", {
      requestId: data.requestId,
      loaderId: data.loaderId,
      timestamp: Date.now() / 1000,
      type: LOAD_CAUSE_STRINGS[data.cause] || "unknown",
      response: {
        url: httpChannel.URI.spec,
        status: data.status,
        statusText: data.statusText,
        headers,
        mimeType: wrappedChannel.contentType,
        requestHeaders: headersAsObject(data.requestHeaders),
        connectionReused: undefined,
        connectionId: undefined,
        remoteIPAddress: data.remoteIPAddress,
        remotePort: data.remotePort,
        fromDiskCache: data.fromCache,
        encodedDataLength: undefined,
        protocol: httpChannel.protocolVersion,
        securityDetails: data.securityDetails,
        // unknown, neutral, insecure, secure, info, insecure-broken
        securityState: "unknown",
      },
      frameId: data.frameId.toString(),
    });
  }

  /**
   * Creates an array of all Urls in the page context
   *
   * @returns {Array<string>=}
   */
  _getDefaultUrls() {
    const urls = this.session.target.browsingContext
      .getAllBrowsingContextsInSubtree()
      .map(context => context.currentURI.spec);

    return urls;
  }
}

/**
 * Creates a CDP Network.Cookie from our internal cookie values
 *
 * @param {nsICookie} cookie
 *
 * @returns {Network.Cookie}
 *      A CDP Cookie
 */
function _buildCookie(cookie) {
  const data = {
    name: cookie.name,
    value: cookie.value,
    domain: cookie.host,
    path: cookie.path,
    expires: cookie.isSession ? -1 : cookie.expiry,
    // The size is the combined length of both the cookie name and value
    size: cookie.name.length + cookie.value.length,
    httpOnly: cookie.isHttpOnly,
    secure: cookie.isSecure,
    session: cookie.isSession,
  };

  if (cookie.sameSite) {
    const sameSiteMap = new Map([
      [Ci.nsICookie.SAMESITE_LAX, "Lax"],
      [Ci.nsICookie.SAMESITE_STRICT, "Strict"],
    ]);

    data.sameSite = sameSiteMap.get(cookie.sameSite);
  }

  return data;
}

/**
 * Given a array of possibly repeating header names, merge the values for
 * duplicate headers into a comma-separated list, or in some cases a
 * newline-separated list.
 *
 * e.g. { "Cache-Control": "no-cache,no-store" }
 *
 * Based on
 * https://hg.mozilla.org/mozilla-central/file/56c09d42f411246e407fe30418c27e67a6a44d29/netwerk/protocol/http/nsHttpHeaderArray.h
 *
 * @param {Array} headers
 *    Array of {name, value}
 * @returns {object}
 *    Object where each key is a header name.
 */
function headersAsObject(headers) {
  const rv = {};
  headers.forEach(({ name, value }) => {
    name = name.toLowerCase();
    if (rv[name]) {
      const separator = [
        "set-cookie",
        "www-authenticate",
        "proxy-authenticate",
      ].includes(name)
        ? "\n"
        : ",";
      rv[name] += `${separator}${value}`;
    } else {
      rv[name] = value;
    }
  });
  return rv;
}
PK
!<��R�[a[a5chrome/remote/content/cdp/domains/parent/Page.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Domain } from "chrome://remote/content/cdp/domains/Domain.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs",

  DialogHandler:
    "chrome://remote/content/cdp/domains/parent/page/DialogHandler.sys.mjs",
  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
  PollPromise: "chrome://remote/content/shared/Sync.sys.mjs",
  print: "chrome://remote/content/shared/PDF.sys.mjs",
  streamRegistry: "chrome://remote/content/cdp/domains/parent/IO.sys.mjs",
  Stream: "chrome://remote/content/cdp/StreamRegistry.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
  UnsupportedError: "chrome://remote/content/cdp/Error.sys.mjs",
  windowManager: "chrome://remote/content/shared/WindowManager.sys.mjs",
});

const MAX_CANVAS_DIMENSION = 32767;
const MAX_CANVAS_AREA = 472907776;

const PRINT_MAX_SCALE_VALUE = 2.0;
const PRINT_MIN_SCALE_VALUE = 0.1;

const PDF_TRANSFER_MODES = {
  base64: "ReturnAsBase64",
  stream: "ReturnAsStream",
};

const TIMEOUT_SET_HISTORY_INDEX = 1000;

export class Page extends Domain {
  constructor(session) {
    super(session);

    this._onDialogLoaded = this._onDialogLoaded.bind(this);
    this._onRequest = this._onRequest.bind(this);

    this.enabled = false;

    this.session.networkObserver.startTrackingBrowserNetwork(
      this.session.target.browser
    );
    this.session.networkObserver.on("request", this._onRequest);
  }

  destructor() {
    // Flip a flag to avoid to disable the content domain from this.disable()
    this._isDestroyed = false;
    this.disable();

    this.session.networkObserver.off("request", this._onRequest);
    this.session.networkObserver.stopTrackingBrowserNetwork(
      this.session.target.browser
    );
    super.destructor();
  }

  // commands

  /**
   * Navigates current page to given URL.
   *
   * @param {object} options
   * @param {string} options.url
   *     destination URL
   * @param {string=} options.frameId
   *     frame id to navigate (not supported),
   *     if not specified navigate top frame
   * @param {string=} options.referrer
   *     referred URL (optional)
   * @param {string=} options.transitionType
   *     intended transition type
   * @returns {object}
   *         - frameId {string} frame id that has navigated (or failed to)
   *         - errorText {string=} error message if navigation has failed
   *         - loaderId {string} (not supported)
   */
  async navigate(options = {}) {
    const { url, frameId, referrer, transitionType } = options;
    if (typeof url != "string") {
      throw new TypeError("url: string value expected");
    }
    let validURL;
    try {
      validURL = Services.io.newURI(url);
    } catch (e) {
      throw new Error("Error: Cannot navigate to invalid URL");
    }
    const topFrameId = this.session.browsingContext.id.toString();
    if (frameId && frameId != topFrameId) {
      throw new lazy.UnsupportedError("frameId not supported");
    }

    const hitsNetwork = ["https", "http"].includes(validURL.scheme);
    let networkLessLoaderId;
    if (!hitsNetwork) {
      // This navigation will not hit the network, use a randomly generated id.
      networkLessLoaderId = lazy.generateUUID();

      // Update the content process map of loader ids.
      await this.executeInChild("_updateLoaderId", {
        frameId: this.session.browsingContext.id,
        loaderId: networkLessLoaderId,
      });
    }

    const currentURI = this.session.browsingContext.currentURI;

    const isSameDocumentNavigation =
      // The "host", "query" and "ref" getters can throw if the URLs are not
      // http/https, so verify first that both currentURI and validURL are
      // using http/https.
      hitsNetwork &&
      ["https", "http"].includes(currentURI.scheme) &&
      currentURI.host === validURL.host &&
      currentURI.query === validURL.query &&
      !!validURL.ref;

    const requestDone = new Promise(resolve => {
      if (isSameDocumentNavigation) {
        // Per CDP documentation, same-document navigations should not emit any
        // loader id (https://chromedevtools.github.io/devtools-protocol/tot/Page/#method-navigate)
        resolve({});
        return;
      }

      if (!hitsNetwork) {
        // This navigation will not hit the network, use a randomly generated id.
        resolve({ navigationRequestId: networkLessLoaderId });
        return;
      }
      let navigationRequestId, redirectedRequestId;
      const _onNavigationRequest = function (_type, _ch, data) {
        const {
          url: requestURL,
          requestId,
          redirectedFrom = null,
          isNavigationRequest,
        } = data;
        if (!isNavigationRequest) {
          return;
        }
        if (validURL.spec === requestURL) {
          navigationRequestId = redirectedRequestId = requestId;
        } else if (redirectedFrom === redirectedRequestId) {
          redirectedRequestId = requestId;
        }
      };

      const _onRequestFinished = function (_type, _ch, data) {
        const { requestId, errorCode } = data;
        if (
          redirectedRequestId !== requestId ||
          errorCode == "NS_BINDING_REDIRECTED"
        ) {
          // handle next request in redirection chain
          return;
        }
        this.session.networkObserver.off("request", _onNavigationRequest);
        this.session.networkObserver.off("requestfinished", _onRequestFinished);
        resolve({ errorCode, navigationRequestId });
      }.bind(this);

      this.session.networkObserver.on("request", _onNavigationRequest);
      this.session.networkObserver.on("requestfinished", _onRequestFinished);
    });

    const opts = {
      loadFlags: transitionToLoadFlag(transitionType),
      referrerURI: referrer,
      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
    };
    this.session.browsingContext.loadURI(validURL, opts);
    // clients expect loaderId == requestId for a document navigation request
    const { navigationRequestId: loaderId, errorCode } = await requestDone;
    const result = {
      frameId: topFrameId,
      loaderId,
    };
    if (errorCode) {
      result.errorText = errorCode;
    }
    return result;
  }

  /**
   * Capture page screenshot.
   *
   * @param {object} options
   * @param {Viewport=} options.clip
   *     Capture the screenshot of a given region only.
   * @param {string=} options.format
   *     Image compression format. Defaults to "png".
   * @param {number=} options.quality
   *     Compression quality from range [0..100] (jpeg only). Defaults to 80.
   *
   * @returns {string}
   *     Base64-encoded image data.
   */
  async captureScreenshot(options = {}) {
    const { clip, format = "png", quality = 80 } = options;

    if (options.fromSurface) {
      throw new lazy.UnsupportedError("fromSurface not supported");
    }

    let rect;
    let scale = await this.executeInChild("_devicePixelRatio");

    if (clip) {
      for (const prop of ["x", "y", "width", "height", "scale"]) {
        if (clip[prop] == undefined) {
          throw new TypeError(`clip.${prop}: double value expected`);
        }
      }

      const contentRect = await this.executeInChild("_contentRect");

      // For invalid scale values default to full page
      if (clip.scale <= 0) {
        Object.assign(clip, {
          x: 0,
          y: 0,
          width: contentRect.width,
          height: contentRect.height,
          scale: 1,
        });
      } else {
        if (clip.x < 0 || clip.x > contentRect.width - 1) {
          clip.x = 0;
        }
        if (clip.y < 0 || clip.y > contentRect.height - 1) {
          clip.y = 0;
        }
        if (clip.width <= 0) {
          clip.width = contentRect.width;
        }
        if (clip.height <= 0) {
          clip.height = contentRect.height;
        }
      }

      rect = new DOMRect(clip.x, clip.y, clip.width, clip.height);
      scale *= clip.scale;
    } else {
      // If no specific clipping region has been specified,
      // fallback to the layout (fixed) viewport, and the
      // default pixel ratio.
      const { pageX, pageY, clientWidth, clientHeight } =
        await this.executeInChild("_layoutViewport");

      rect = new DOMRect(pageX, pageY, clientWidth, clientHeight);
    }

    let canvasWidth = rect.width * scale;
    let canvasHeight = rect.height * scale;

    // Cap the screenshot size based on maximum allowed canvas sizes.
    // Using higher dimensions would trigger exceptions in Gecko.
    //
    // See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas#Maximum_canvas_size
    if (canvasWidth > MAX_CANVAS_DIMENSION) {
      rect.width = Math.floor(MAX_CANVAS_DIMENSION / scale);
      canvasWidth = rect.width * scale;
    }
    if (canvasHeight > MAX_CANVAS_DIMENSION) {
      rect.height = Math.floor(MAX_CANVAS_DIMENSION / scale);
      canvasHeight = rect.height * scale;
    }
    // If the area is larger, reduce the height to keep the full width.
    if (canvasWidth * canvasHeight > MAX_CANVAS_AREA) {
      rect.height = Math.floor(MAX_CANVAS_AREA / (canvasWidth * scale));
      canvasHeight = rect.height * scale;
    }

    const { browsingContext, window } = this.session.target;
    const snapshot = await browsingContext.currentWindowGlobal.drawSnapshot(
      rect,
      scale,
      "rgb(255,255,255)"
    );

    const canvas = window.document.createElementNS(
      "http://www.w3.org/1999/xhtml",
      "canvas"
    );
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;

    const ctx = canvas.getContext("2d");
    ctx.drawImage(snapshot, 0, 0);

    // Bug 1574935 - Huge dimensions can trigger an OOM because multiple copies
    // of the bitmap will exist in memory. Force the removal of the snapshot
    // because it is no longer needed.
    snapshot.close();

    const url = canvas.toDataURL(`image/${format}`, quality / 100);
    if (!url.startsWith(`data:image/${format}`)) {
      throw new lazy.UnsupportedError(`Unsupported MIME type: image/${format}`);
    }

    // only return the base64 encoded data without the data URL prefix
    const data = url.substring(url.indexOf(",") + 1);

    return { data };
  }

  async enable() {
    if (this.enabled) {
      return;
    }

    this.enabled = true;

    const { browser } = this.session.target;
    this._dialogHandler = new lazy.DialogHandler(browser);
    this._dialogHandler.on("dialog-loaded", this._onDialogLoaded);
    await this.executeInChild("enable");
  }

  async disable() {
    if (!this.enabled) {
      return;
    }

    this._dialogHandler.destructor();
    this._dialogHandler = null;
    this.enabled = false;

    if (!this._isDestroyed) {
      // Only call disable in the content domain if we are not destroying the domain.
      // If we are destroying the domain, the content domains will be destroyed
      // independently after firing the remote:destroy event.
      await this.executeInChild("disable");
    }
  }

  async bringToFront() {
    const { tab, window } = this.session.target;

    // Focus the window, and select the corresponding tab
    await lazy.windowManager.focusWindow(window);
    await lazy.TabManager.selectTab(tab);
  }

  /**
   * Return metrics relating to the layouting of the page.
   *
   * The returned object contains the following entries:
   *
   * layoutViewport:
   *     {number} pageX
   *         Horizontal offset relative to the document (CSS pixels)
   *     {number} pageY
   *         Vertical offset relative to the document (CSS pixels)
   *     {number} clientWidth
   *         Width (CSS pixels), excludes scrollbar if present
   *     {number} clientHeight
   *         Height (CSS pixels), excludes scrollbar if present
   *
   * visualViewport:
   *     {number} offsetX
   *         Horizontal offset relative to the layout viewport (CSS pixels)
   *     {number} offsetY
   *         Vertical offset relative to the layout viewport (CSS pixels)
   *     {number} pageX
   *         Horizontal offset relative to the document (CSS pixels)
   *     {number} pageY
   *         Vertical offset relative to the document (CSS pixels)
   *     {number} clientWidth
   *         Width (CSS pixels), excludes scrollbar if present
   *     {number} clientHeight
   *         Height (CSS pixels), excludes scrollbar if present
   *     {number} scale
   *         Scale relative to the ideal viewport (size at width=device-width)
   *     {number} zoom
   *         Page zoom factor (CSS to device independent pixels ratio)
   *
   * contentSize:
   *     {number} x
   *         X coordinate
   *     {number} y
   *         Y coordinate
   *     {number} width
   *         Width of scrollable area
   *     {number} height
   *         Height of scrollable area
   *
   * @returns {Promise<object>}
   *     Promise which resolves with an object with the following properties
   *     layoutViewport and contentSize
   */
  async getLayoutMetrics() {
    return {
      layoutViewport: await this.executeInChild("_layoutViewport"),
      contentSize: await this.executeInChild("_contentRect"),
    };
  }

  /**
   * Returns navigation history for the current page.
   *
   * @returns {Promise<object>}
   *     Promise which resolves with an object with the following properties
   *     currentIndex (number) and entries (Array<NavigationEntry>).
   */
  async getNavigationHistory() {
    const { window } = this.session.target;

    return new Promise(resolve => {
      function updateSessionHistory(sessionHistory) {
        const entries = sessionHistory.entries.map(entry => {
          return {
            id: entry.ID,
            url: entry.url,
            userTypedURL: entry.originalURI || entry.url,
            title: entry.title,
            // TODO: Bug 1609514
            transitionType: null,
          };
        });

        resolve({
          currentIndex: sessionHistory.index,
          entries,
        });
      }

      lazy.SessionStore.getSessionHistory(
        window.gBrowser.selectedTab,
        updateSessionHistory
      );
    });
  }

  /**
   * Interact with the currently opened JavaScript dialog (alert, confirm,
   * prompt) for this page. This will always close the dialog, either accepting
   * or rejecting it, with the optional prompt filled.
   *
   * @param {object} options
   * @param {boolean=} options.accept
   *    for "confirm", "prompt", "beforeunload" dialogs true will accept
   *    the dialog, false will cancel it. For "alert" dialogs, true or
   *    false closes the dialog in the same way.
   * @param {string=} options.promptText
   *    for "prompt" dialogs, used to fill the prompt input.
   */
  async handleJavaScriptDialog(options = {}) {
    const { accept, promptText } = options;

    if (!this.enabled) {
      throw new Error("Page domain is not enabled");
    }
    await this._dialogHandler.handleJavaScriptDialog({ accept, promptText });
  }

  /**
   * Navigates current page to the given history entry.
   *
   * @param {object} options
   * @param {number} options.entryId
   *    Unique id of the entry to navigate to.
   */
  async navigateToHistoryEntry(options = {}) {
    const { entryId } = options;

    const index = await this._getIndexForHistoryEntryId(entryId);

    if (index == null) {
      throw new Error("No entry with passed id");
    }

    const { window } = this.session.target;
    window.gBrowser.gotoIndex(index);

    // On some platforms the requested index isn't set immediately.
    await lazy.PollPromise(
      async (resolve, reject) => {
        const currentIndex = await this._getCurrentHistoryIndex();
        if (currentIndex == index) {
          resolve();
        } else {
          reject();
        }
      },
      { timeout: TIMEOUT_SET_HISTORY_INDEX }
    );
  }

  /**
   * Print page as PDF.
   *
   * @param {object} options
   * @param {boolean=} options.displayHeaderFooter
   *     Display header and footer. Defaults to false.
   * @param {string=} options.footerTemplate (not supported)
   *     HTML template for the print footer.
   * @param {string=} options.headerTemplate (not supported)
   *     HTML template for the print header. Should use the same format
   *     as the footerTemplate.
   * @param {boolean=} options.ignoreInvalidPageRanges
   *     Whether to silently ignore invalid but successfully parsed page ranges,
   *     such as '3-2'. Defaults to false.
   * @param {boolean=} options.landscape
   *     Paper orientation. Defaults to false.
   * @param {number=} options.marginBottom
   *     Bottom margin in inches. Defaults to 1cm (~0.4 inches).
   * @param {number=} options.marginLeft
   *     Left margin in inches. Defaults to 1cm (~0.4 inches).
   * @param {number=} options.marginRight
   *     Right margin in inches. Defaults to 1cm (~0.4 inches).
   * @param {number=} options.marginTop
   *     Top margin in inches. Defaults to 1cm (~0.4 inches).
   * @param {string=} options.pageRanges (not supported)
   *     Paper ranges to print, e.g., '1-5, 8, 11-13'.
   *     Defaults to the empty string, which means print all pages.
   * @param {number=} options.paperHeight
   *     Paper height in inches. Defaults to 11 inches.
   * @param {number=} options.paperWidth
   *     Paper width in inches. Defaults to 8.5 inches.
   * @param {boolean=} options.preferCSSPageSize
   *     Whether or not to prefer page size as defined by CSS.
   *     Defaults to false, in which case the content will be scaled
   *     to fit the paper size.
   * @param {boolean=} options.printBackground
   *     Print background graphics. Defaults to false.
   * @param {number=} options.scale
   *     Scale of the webpage rendering. Defaults to 1.
   * @param {string=} options.transferMode
   *     Return as base64-encoded string (ReturnAsBase64),
   *     or stream (ReturnAsStream). Defaults to ReturnAsBase64.
   *
   * @returns {Promise<{data:string, stream:Stream}>}
   *     Based on the transferMode setting data is a base64-encoded string,
   *     or stream is a Stream.
   */
  async printToPDF(options = {}) {
    const {
      displayHeaderFooter = false,
      // Bug 1601570 - Implement templates for header and footer
      // headerTemplate = "",
      // footerTemplate = "",
      landscape = false,
      marginBottom = 0.39,
      marginLeft = 0.39,
      marginRight = 0.39,
      marginTop = 0.39,
      // Bug 1601571 - Implement handling of page ranges
      // TODO: pageRanges = "",
      // TODO: ignoreInvalidPageRanges = false,
      paperHeight = 11.0,
      paperWidth = 8.5,
      preferCSSPageSize = false,
      printBackground = false,
      scale = 1.0,
      transferMode = PDF_TRANSFER_MODES.base64,
    } = options;

    if (marginBottom < 0) {
      throw new TypeError("marginBottom is negative");
    }
    if (marginLeft < 0) {
      throw new TypeError("marginLeft is negative");
    }
    if (marginRight < 0) {
      throw new TypeError("marginRight is negative");
    }
    if (marginTop < 0) {
      throw new TypeError("marginTop is negative");
    }
    if (scale < PRINT_MIN_SCALE_VALUE || scale > PRINT_MAX_SCALE_VALUE) {
      throw new TypeError("scale is outside [0.1 - 2] range");
    }
    if (paperHeight <= 0) {
      throw new TypeError("paperHeight is zero or negative");
    }
    if (paperWidth <= 0) {
      throw new TypeError("paperWidth is zero or negative");
    }

    const psService = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
      Ci.nsIPrintSettingsService
    );

    const printSettings = psService.createNewPrintSettings();
    printSettings.isInitializedFromPrinter = true;
    printSettings.isInitializedFromPrefs = true;
    printSettings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF;
    printSettings.printerName = "";
    printSettings.printSilent = true;

    printSettings.paperSizeUnit = Ci.nsIPrintSettings.kPaperSizeInches;
    printSettings.paperWidth = paperWidth;
    printSettings.paperHeight = paperHeight;

    // Override any os-specific unwriteable margins
    printSettings.unwriteableMarginTop = 0;
    printSettings.unwriteableMarginLeft = 0;
    printSettings.unwriteableMarginBottom = 0;
    printSettings.unwriteableMarginRight = 0;

    printSettings.marginBottom = marginBottom;
    printSettings.marginLeft = marginLeft;
    printSettings.marginRight = marginRight;
    printSettings.marginTop = marginTop;

    printSettings.printBGColors = printBackground;
    printSettings.printBGImages = printBackground;
    printSettings.scaling = scale;
    printSettings.shrinkToFit = preferCSSPageSize;

    if (!displayHeaderFooter) {
      printSettings.headerStrCenter = "";
      printSettings.headerStrLeft = "";
      printSettings.headerStrRight = "";
      printSettings.footerStrCenter = "";
      printSettings.footerStrLeft = "";
      printSettings.footerStrRight = "";
    }

    if (landscape) {
      printSettings.orientation = Ci.nsIPrintSettings.kLandscapeOrientation;
    }

    const retval = { data: null, stream: null };
    const { linkedBrowser } = this.session.target.tab;

    if (transferMode === PDF_TRANSFER_MODES.stream) {
      // If we are returning a stream, we write the PDF to disk so that we don't
      // keep (potentially very large) PDFs in memory. We can then stream them
      // to the client via the returned Stream.
      //
      // NOTE: This is a potentially premature optimization -- it might be fine
      // to keep these PDFs in memory, but we don't have specifics on how CDP is
      // used in the field so it is possible that leaving the PDFs in memory
      // could cause a regression.
      const path = await IOUtils.createUniqueFile(
        PathUtils.tempDir,
        "remote-agent.pdf"
      );

      printSettings.outputDestination =
        Ci.nsIPrintSettings.kOutputDestinationFile;
      printSettings.toFileName = path;

      await linkedBrowser.browsingContext.print(printSettings);

      retval.stream = lazy.streamRegistry.add(new lazy.Stream(path));
    } else {
      const binaryString = await lazy.print.printToBinaryString(
        linkedBrowser.browsingContext,
        printSettings
      );

      retval.data = btoa(binaryString);
    }

    return retval;
  }

  /**
   * Intercept file chooser requests and transfer control to protocol clients.
   *
   * When file chooser interception is enabled,
   * the native file chooser dialog is not shown.
   * Instead, a protocol event Page.fileChooserOpened is emitted.
   */
  setInterceptFileChooserDialog() {}

  _getCurrentHistoryIndex() {
    const { window } = this.session.target;

    return new Promise(resolve => {
      lazy.SessionStore.getSessionHistory(
        window.gBrowser.selectedTab,
        history => {
          resolve(history.index);
        }
      );
    });
  }

  _getIndexForHistoryEntryId(id) {
    const { window } = this.session.target;

    return new Promise(resolve => {
      function updateSessionHistory(sessionHistory) {
        sessionHistory.entries.forEach((entry, index) => {
          if (entry.ID == id) {
            resolve(index);
          }
        });

        resolve(null);
      }

      lazy.SessionStore.getSessionHistory(
        window.gBrowser.selectedTab,
        updateSessionHistory
      );
    });
  }

  /**
   * Emit the proper CDP event javascriptDialogOpening when a javascript dialog
   * opens for the current target.
   */
  _onDialogLoaded(e, data) {
    const { message, type } = data;
    // XXX: We rely on the common-dialog-loaded event (see DialogHandler.sys.mjs)
    // which is inconsistent with the name "javascriptDialogOpening".
    // For correctness we should rely on an event fired _before_ the prompt is
    // visible, such as DOMWillOpenModalDialog. However the payload of this
    // event does not contain enough data to populate javascriptDialogOpening.
    //
    // Since the event is fired asynchronously, this should not have an impact
    // on the actual tests relying on this API.
    this.emit("Page.javascriptDialogOpening", { message, type });
  }

  /**
   * Handles HTTP request to propagate loaderId to events emitted from
   * content process
   */
  _onRequest(_type, _ch, data) {
    if (!data.loaderId) {
      return;
    }
    this.executeInChild("_updateLoaderId", {
      loaderId: data.loaderId,
      frameId: data.frameId,
    });
  }
}

function transitionToLoadFlag(transitionType) {
  switch (transitionType) {
    case "reload":
      return Ci.nsIWebNavigation.LOAD_FLAGS_IS_REFRESH;
    case "link":
    default:
      return Ci.nsIWebNavigation.LOAD_FLAGS_IS_LINK;
  }
}
PK
!<��m��9chrome/remote/content/cdp/domains/parent/Security.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { Domain } from "chrome://remote/content/cdp/domains/Domain.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetters(lazy, {
  sss: ["@mozilla.org/ssservice;1", "nsISiteSecurityService"],
  certOverrideService: [
    "@mozilla.org/security/certoverride;1",
    "nsICertOverrideService",
  ],
});

const CERT_PINNING_ENFORCEMENT_PREF = "security.cert_pinning.enforcement_level";
const HSTS_PRELOAD_LIST_PREF = "network.stricttransportsecurity.preloadlist";

export class Security extends Domain {
  destructor() {
    this.setIgnoreCertificateErrors({ ignore: false });
  }

  /**
   * Enable/disable whether all certificate errors should be ignored
   *
   * @param {object} options
   * @param {boolean=} options.ignore
   *    if true, all certificate errors will be ignored.
   */
  setIgnoreCertificateErrors(options = {}) {
    const { ignore } = options;

    if (ignore) {
      // make it possible to register certificate overrides for domains
      // that use HSTS or HPKP
      Services.prefs.setBoolPref(HSTS_PRELOAD_LIST_PREF, false);
      Services.prefs.setIntPref(CERT_PINNING_ENFORCEMENT_PREF, 0);
    } else {
      Services.prefs.clearUserPref(HSTS_PRELOAD_LIST_PREF);
      Services.prefs.clearUserPref(CERT_PINNING_ENFORCEMENT_PREF);

      // clear collected HSTS and HPKP state
      lazy.sss.clearAll();
    }

    lazy.certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
      ignore
    );
  }
}
PK
!<ws�U��;chrome/remote/content/cdp/domains/parent/SystemInfo.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Domain } from "chrome://remote/content/cdp/domains/Domain.sys.mjs";

export class SystemInfo extends Domain {
  async getProcessInfo() {
    const procInfo = await ChromeUtils.requestProcInfo();

    // Add child processes
    const processInfo = procInfo.children.map(proc => ({
      type: this.#getProcessType(proc.type),
      id: proc.pid,
      cpuTime: this.#getCpuTime(proc.cpuTime),
    }));

    // Add parent process
    processInfo.unshift({
      type: "browser",
      id: procInfo.pid,
      cpuTime: this.#getCpuTime(procInfo.cpuTime),
    });

    return processInfo;
  }

  #getProcessType(type) {
    // Map internal types to CDP types if applicable
    switch (type) {
      case "gpu":
        return "GPU";

      case "web":
      case "webIsolated":
      case "privilegedabout":
        return "renderer";

      default:
        return type;
    }
  }

  #getCpuTime(cpuTime) {
    // cpuTime is tracked internally as nanoseconds, CDP is in seconds
    return cpuTime / 1000 / 1000 / 1000;
  }
}
PK
!<t��v��7chrome/remote/content/cdp/domains/parent/Target.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Domain } from "chrome://remote/content/cdp/domains/Domain.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ContextualIdentityService:
    "resource://gre/modules/ContextualIdentityService.sys.mjs",

  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
  TabSession: "chrome://remote/content/cdp/sessions/TabSession.sys.mjs",
  UserContextManager:
    "chrome://remote/content/shared/UserContextManager.sys.mjs",
  windowManager: "chrome://remote/content/shared/WindowManager.sys.mjs",
});

let browserContextIds = 1;

// Default filter from CDP specification
const defaultFilter = [
  { type: "browser", exclude: true },
  { type: "tab", exclude: true },
  {},
];

export class Target extends Domain {
  #browserContextIds;
  #discoverTargetFilter;

  constructor(session) {
    super(session);

    this.#browserContextIds = new Set();

    this._onTargetCreated = this._onTargetCreated.bind(this);
    this._onTargetDestroyed = this._onTargetDestroyed.bind(this);
  }

  getBrowserContexts() {
    const browserContextIds =
      lazy.ContextualIdentityService.getPublicUserContextIds().filter(id =>
        this.#browserContextIds.has(id)
      );

    return { browserContextIds };
  }

  createBrowserContext() {
    const identity = lazy.ContextualIdentityService.create(
      "remote-agent-" + browserContextIds++
    );

    this.#browserContextIds.add(identity.userContextId);
    return { browserContextId: identity.userContextId };
  }

  disposeBrowserContext(options = {}) {
    const { browserContextId } = options;

    lazy.ContextualIdentityService.remove(browserContextId);
    lazy.ContextualIdentityService.closeContainerTabs(browserContextId);

    this.#browserContextIds.delete(browserContextId);
  }

  getTargets(options = {}) {
    const { filter = defaultFilter } = options;
    const { targetList } = this.session.target;

    this._validateTargetFilter(filter);

    const targetInfos = [...targetList]
      .filter(target => this._filterIncludesTarget(target, filter))
      .map(target => this._getTargetInfo(target));

    return {
      targetInfos,
    };
  }

  setDiscoverTargets(options = {}) {
    const { discover, filter } = options;
    const { targetList } = this.session.target;

    if (typeof discover !== "boolean") {
      throw new TypeError("discover: boolean value expected");
    }

    if (discover === false && filter !== undefined) {
      throw new Error("filter: should not be present when discover is false");
    }

    // null filter should not be defaulted
    const targetFilter = filter === undefined ? defaultFilter : filter;
    this._validateTargetFilter(targetFilter);

    // Store active filter for filtering in event listeners (targetCreated, targetDestroyed, targetInfoChanged)
    this.#discoverTargetFilter = targetFilter;

    if (discover) {
      targetList.on("target-created", this._onTargetCreated);
      targetList.on("target-destroyed", this._onTargetDestroyed);

      for (const target of targetList) {
        this._onTargetCreated("target-created", target);
      }
    } else {
      targetList.off("target-created", this._onTargetCreated);
      targetList.off("target-destroyed", this._onTargetDestroyed);
    }
  }

  async createTarget(options = {}) {
    const { browserContextId, url } = options;

    if (typeof url !== "string") {
      throw new TypeError("url: string value expected");
    }

    let validURL;
    try {
      validURL = Services.io.newURI(url);
    } catch (e) {
      // If we failed to parse given URL, use about:blank instead
      validURL = Services.io.newURI("about:blank");
    }

    const { targetList, window } = this.session.target;
    const onTarget = targetList.once("target-created");
    const tab = await lazy.TabManager.addTab({
      focus: true,
      userContextId:
        // Bug 1878649: Use UserContextManager ids consistently in CDP.
        lazy.UserContextManager.getIdByInternalId(browserContextId),
      window,
    });

    const target = await onTarget;
    if (tab.linkedBrowser != target.browser) {
      throw new Error(
        "Unexpected tab opened: " + tab.linkedBrowser.currentURI.spec
      );
    }

    target.browsingContext.loadURI(validURL, {
      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
    });

    return { targetId: target.id };
  }

  async closeTarget(options = {}) {
    const { targetId } = options;
    const { targetList } = this.session.target;
    const target = targetList.getById(targetId);

    if (!target) {
      throw new Error(`Unable to find target with id '${targetId}'`);
    }

    await lazy.TabManager.removeTab(target.tab);
  }

  async activateTarget(options = {}) {
    const { targetId } = options;
    const { targetList, window } = this.session.target;
    const target = targetList.getById(targetId);

    if (!target) {
      throw new Error(`Unable to find target with id '${targetId}'`);
    }

    // Focus the window, and select the corresponding tab
    await lazy.windowManager.focusWindow(window);
    await lazy.TabManager.selectTab(target.tab);
  }

  attachToTarget(options = {}) {
    const { targetId } = options;
    const { targetList } = this.session.target;
    const target = targetList.getById(targetId);

    if (!target) {
      throw new Error(`Unable to find target with id '${targetId}'`);
    }

    const tabSession = new lazy.TabSession(
      this.session.connection,
      target,
      lazy.generateUUID()
    );
    this.session.connection.registerSession(tabSession);

    this._emitAttachedToTarget(target, tabSession);

    return {
      sessionId: tabSession.id,
    };
  }

  setAutoAttach() {}

  sendMessageToTarget(options = {}) {
    const { sessionId, message } = options;
    const { connection } = this.session;
    connection.sendMessageToTarget(sessionId, message);
  }

  /**
   * Internal methods: the following methods are not part of CDP;
   * note the _ prefix.
   */

  _emitAttachedToTarget(target, tabSession) {
    const targetInfo = this._getTargetInfo(target);
    this.emit("Target.attachedToTarget", {
      targetInfo,
      sessionId: tabSession.id,
      waitingForDebugger: false,
    });
  }

  _getTargetInfo(target) {
    const attached = [...this.session.connection.sessions.values()].some(
      session => session.target.id === target.id
    );

    return {
      targetId: target.id,
      type: target.type,
      title: target.title,
      url: target.url,
      attached,
      browserContextId: target.browserContextId,
    };
  }

  _filterIncludesTarget(target, filter) {
    for (const entry of filter) {
      if ([undefined, target.type].includes(entry.type)) {
        return !entry.exclude;
      }
    }

    return false;
  }

  _validateTargetFilter(filter) {
    if (!Array.isArray(filter)) {
      throw new TypeError("filter: array value expected");
    }

    for (const entry of filter) {
      if (entry === null || Array.isArray(entry) || typeof entry !== "object") {
        throw new TypeError("filter: object values expected in array");
      }

      if (!["undefined", "string"].includes(typeof entry.type)) {
        throw new TypeError("filter: type: string value expected");
      }

      if (!["undefined", "boolean"].includes(typeof entry.exclude)) {
        throw new TypeError("filter: exclude: boolean value expected");
      }
    }
  }

  _onTargetCreated(eventName, target) {
    if (!this._filterIncludesTarget(target, this.#discoverTargetFilter)) {
      return;
    }

    const targetInfo = this._getTargetInfo(target);
    this.emit("Target.targetCreated", { targetInfo });
  }

  _onTargetDestroyed(eventName, target) {
    if (!this._filterIncludesTarget(target, this.#discoverTargetFilter)) {
      return;
    }

    this.emit("Target.targetDestroyed", {
      targetId: target.id,
    });
  }
}
PK
!<�U���Cchrome/remote/content/cdp/domains/parent/page/DialogHandler.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
});

const DIALOG_TYPES = {
  ALERT: "alert",
  BEFOREUNLOAD: "beforeunload",
  CONFIRM: "confirm",
  PROMPT: "prompt",
};

/**
 * Helper dedicated to detect and interact with browser dialogs such as `alert`,
 * `confirm` etc. The current implementation only supports tabmodal dialogs,
 * not full window dialogs.
 *
 * Emits "dialog-loaded" when a javascript dialog is opened for the current
 * browser.
 *
 * @param {BrowserElement} browser
 */
export class DialogHandler {
  constructor(browser) {
    lazy.EventEmitter.decorate(this);
    this._dialog = null;
    this._browser = browser;

    this._onCommonDialogLoaded = this._onCommonDialogLoaded.bind(this);

    Services.obs.addObserver(
      this._onCommonDialogLoaded,
      "common-dialog-loaded"
    );
  }

  destructor() {
    this._dialog = null;
    this._pageTarget = null;

    Services.obs.removeObserver(
      this._onCommonDialogLoaded,
      "common-dialog-loaded"
    );
  }

  async handleJavaScriptDialog({ accept, promptText }) {
    if (!this._dialog) {
      throw new Error("No dialog available for handleJavaScriptDialog");
    }

    const type = this._getDialogType();
    if (promptText && type === "prompt") {
      this._dialog.ui.loginTextbox.value = promptText;
    }

    const onDialogClosed = new Promise(r => {
      this._browser.addEventListener("DOMModalDialogClosed", r, {
        once: true,
      });
    });

    // 0 corresponds to the OK callback, 1 to the CANCEL callback.
    if (accept) {
      this._dialog.ui.button0.click();
    } else {
      this._dialog.ui.button1.click();
    }

    await onDialogClosed;

    // Resetting dialog to null here might be racy and lead to errors if the
    // content page is triggering several prompts in a row.
    // See Bug 1569578.
    this._dialog = null;
  }

  _getDialogType() {
    const { inPermitUnload, promptType } = this._dialog.args;

    if (inPermitUnload) {
      return DIALOG_TYPES.BEFOREUNLOAD;
    }

    switch (promptType) {
      case "alert":
        return DIALOG_TYPES.ALERT;
      case "confirm":
        return DIALOG_TYPES.CONFIRM;
      case "prompt":
        return DIALOG_TYPES.PROMPT;
      default:
        throw new Error("Unsupported dialog type: " + promptType);
    }
  }

  _onCommonDialogLoaded(dialogWindow) {
    const dialogs =
      this._browser.tabDialogBox.getContentDialogManager().dialogs;
    const dialog = dialogs.find(d => d.frameContentWindow === dialogWindow);

    if (!dialog) {
      // The dialog is not for the current tab.
      return;
    }

    this._dialog = dialogWindow.Dialog;
    const message = this._dialog.args.text;
    const type = this._getDialogType();

    this.emit("dialog-loaded", { message, type });
  }
}
PK
!<�\����<chrome/remote/content/cdp/observers/ChannelEventSink.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ComponentUtils } from "resource://gre/modules/ComponentUtils.sys.mjs";

const Cm = Components.manager;

/**
 * This is a nsIChannelEventSink implementation that monitors channel redirects.
 * This has been forked from:
 * https://searchfox.org/mozilla-central/source/devtools/server/actors/network-monitor/channel-event-sink.js
 * The rest of this module is also more or less forking:
 * https://searchfox.org/mozilla-central/source/devtools/server/actors/network-monitor/network-observer.js
 * TODO(try to re-unify /remote/ with /devtools code)
 */
const SINK_CLASS_DESCRIPTION = "NetworkMonitor Channel Event Sink";
const SINK_CLASS_ID = Components.ID("{c2b4c83e-607a-405a-beab-0ef5dbfb7617}");
const SINK_CONTRACT_ID = "@mozilla.org/network/monitor/channeleventsink;1";
const SINK_CATEGORY_NAME = "net-channel-event-sinks";

function ChannelEventSink() {
  this.wrappedJSObject = this;
  this.collectors = new Set();
}

ChannelEventSink.prototype = {
  QueryInterface: ChromeUtils.generateQI(["nsIChannelEventSink"]),

  registerCollector(collector) {
    this.collectors.add(collector);
  },

  unregisterCollector(collector) {
    this.collectors.delete(collector);

    if (this.collectors.size == 0) {
      ChannelEventSinkFactory.unregister();
    }
  },

  // eslint-disable-next-line no-shadow
  asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
    for (const collector of this.collectors) {
      try {
        collector._onChannelRedirect(oldChannel, newChannel, flags);
      } catch (ex) {
        console.error(
          "StackTraceCollector.onChannelRedirect threw an exception",
          ex
        );
      }
    }
    callback.onRedirectVerifyCallback(Cr.NS_OK);
  },
};

export const ChannelEventSinkFactory =
  ComponentUtils.generateSingletonFactory(ChannelEventSink);

ChannelEventSinkFactory.register = function () {
  const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
  if (registrar.isCIDRegistered(SINK_CLASS_ID)) {
    return;
  }

  registrar.registerFactory(
    SINK_CLASS_ID,
    SINK_CLASS_DESCRIPTION,
    SINK_CONTRACT_ID,
    ChannelEventSinkFactory
  );

  Services.catMan.addCategoryEntry(
    SINK_CATEGORY_NAME,
    SINK_CONTRACT_ID,
    SINK_CONTRACT_ID,
    false,
    true
  );
};

ChannelEventSinkFactory.unregister = function () {
  const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
  registrar.unregisterFactory(SINK_CLASS_ID, ChannelEventSinkFactory);

  Services.catMan.deleteCategoryEntry(
    SINK_CATEGORY_NAME,
    SINK_CONTRACT_ID,
    false
  );
};

ChannelEventSinkFactory.getService = function () {
  // Make sure the ChannelEventSink service is registered before accessing it
  ChannelEventSinkFactory.register();

  return Cc[SINK_CONTRACT_ID].getService(Ci.nsIChannelEventSink)
    .wrappedJSObject;
};
PK
!<tw�^]];chrome/remote/content/cdp/observers/ContextObserver.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Helper class to coordinate Runtime and Page events.
 * Events have to be sent in the following order:
 *  - Runtime.executionContextDestroyed
 *  - Page.frameNavigated
 *  - Runtime.executionContextCreated
 *
 * This class also handles the special case of Pages going from/to the BF cache.
 * When you navigate to a new URL, the previous document may be stored in the BF Cache.
 * All its asynchronous operations are frozen (XHR, timeouts, ...) and a `pagehide` event
 * is fired for this document. We then navigate to the new URL.
 * If the user navigates back to the previous page, the page is resurected from the
 * cache. A `pageshow` event is fired and its asynchronous operations are resumed.
 *
 * When a page is in the BF Cache, we should consider it as frozen and shouldn't try
 * to execute any javascript. So that the ExecutionContext should be considered as
 * being destroyed and the document navigated.
 */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",

  executeSoon: "chrome://remote/content/shared/Sync.sys.mjs",
});

export class ContextObserver {
  constructor(chromeEventHandler) {
    this.chromeEventHandler = chromeEventHandler;
    lazy.EventEmitter.decorate(this);

    this._fissionEnabled = Services.appinfo.fissionAutostart;

    this.chromeEventHandler.addEventListener("DOMWindowCreated", this, {
      mozSystemGroup: true,
    });

    // Listen for pageshow and pagehide to track pages going in/out to/from the BF Cache
    this.chromeEventHandler.addEventListener("pageshow", this, {
      mozSystemGroup: true,
    });
    this.chromeEventHandler.addEventListener("pagehide", this, {
      mozSystemGroup: true,
    });

    Services.obs.addObserver(this, "document-element-inserted");
    Services.obs.addObserver(this, "inner-window-destroyed");

    // With Fission disabled the `DOMWindowCreated` event is fired too late.
    // Use the `webnavigation-create` notification instead.
    if (!this._fissionEnabled) {
      Services.obs.addObserver(this, "webnavigation-create");
    }
    Services.obs.addObserver(this, "webnavigation-destroy");
  }

  destructor() {
    this.chromeEventHandler.removeEventListener("DOMWindowCreated", this, {
      mozSystemGroup: true,
    });
    this.chromeEventHandler.removeEventListener("pageshow", this, {
      mozSystemGroup: true,
    });
    this.chromeEventHandler.removeEventListener("pagehide", this, {
      mozSystemGroup: true,
    });

    Services.obs.removeObserver(this, "document-element-inserted");
    Services.obs.removeObserver(this, "inner-window-destroyed");

    if (!this._fissionEnabled) {
      Services.obs.removeObserver(this, "webnavigation-create");
    }
    Services.obs.removeObserver(this, "webnavigation-destroy");
  }

  handleEvent({ type, target, persisted }) {
    const window = target.defaultView;
    const frameId = window.browsingContext.id;
    const id = window.windowGlobalChild.innerWindowId;

    switch (type) {
      case "DOMWindowCreated":
        // Do not pass `id` here as that's the new document ID instead of the old one
        // that is destroyed. Instead, pass the frameId and let the listener figure out
        // what ExecutionContext(s) to destroy.
        this.emit("context-destroyed", { frameId });

        // With Fission enabled the frame is attached early enough so that
        // expected network requests and responses are handles afterward.
        // Otherwise send the event when `webnavigation-create` is received.
        if (this._fissionEnabled) {
          this.emit("frame-attached", { frameId, window });
        }

        break;

      case "pageshow":
        // `persisted` is true when this is about a page being resurected from BF Cache
        if (!persisted) {
          return;
        }
        // XXX(ochameau) we might have to emit FrameNavigate here to properly handle BF Cache
        // scenario in Page domain events
        this.emit("context-created", { windowId: id, window });
        this.emit("script-loaded", { windowId: id, window });
        break;

      case "pagehide":
        // `persisted` is true when this is about a page being frozen into BF Cache
        if (!persisted) {
          return;
        }
        this.emit("context-destroyed", { windowId: id });
        break;
    }
  }

  observe(subject, topic) {
    switch (topic) {
      case "document-element-inserted":
        const window = subject.defaultView;

        // Ignore events without a window and those from other tabs
        if (
          !window ||
          window.docShell.chromeEventHandler !== this.chromeEventHandler
        ) {
          return;
        }

        // Send when the document gets attached to the window, and its location
        // is available.
        this.emit("frame-navigated", {
          frameId: window.browsingContext.id,
          window,
        });

        const id = window.windowGlobalChild.innerWindowId;
        this.emit("context-created", { windowId: id, window });
        // Delay script-loaded to allow context cleanup to happen first
        lazy.executeSoon(() => {
          this.emit("script-loaded", { windowId: id, window });
        });
        break;
      case "inner-window-destroyed":
        const windowId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
        this.emit("context-destroyed", { windowId });
        break;
      case "webnavigation-create":
        subject.QueryInterface(Ci.nsIDocShell);
        this.onDocShellCreated(subject);
        break;
      case "webnavigation-destroy":
        subject.QueryInterface(Ci.nsIDocShell);
        this.onDocShellDestroyed(subject);
        break;
    }
  }

  onDocShellCreated(docShell) {
    this.emit("frame-attached", {
      frameId: docShell.browsingContext.id,
      window: docShell.browsingContext.window,
    });
  }

  onDocShellDestroyed(docShell) {
    this.emit("frame-detached", {
      frameId: docShell.browsingContext.id,
    });
  }
}
PK
!<�U�JJ;chrome/remote/content/cdp/observers/NetworkObserver.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  CommonUtils: "resource://services-common/utils.sys.mjs",
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",

  ChannelEventSinkFactory:
    "chrome://remote/content/cdp/observers/ChannelEventSink.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gActivityDistributor",
  "@mozilla.org/network/http-activity-distributor;1",
  "nsIHttpActivityDistributor"
);

const CC = Components.Constructor;

ChromeUtils.defineLazyGetter(lazy, "BinaryInputStream", () => {
  return CC(
    "@mozilla.org/binaryinputstream;1",
    "nsIBinaryInputStream",
    "setInputStream"
  );
});

ChromeUtils.defineLazyGetter(lazy, "BinaryOutputStream", () => {
  return CC(
    "@mozilla.org/binaryoutputstream;1",
    "nsIBinaryOutputStream",
    "setOutputStream"
  );
});

ChromeUtils.defineLazyGetter(lazy, "StorageStream", () => {
  return CC("@mozilla.org/storagestream;1", "nsIStorageStream", "init");
});

// Cap response storage with 100Mb per tracked tab.
const MAX_RESPONSE_STORAGE_SIZE = 100 * 1024 * 1024;

export class NetworkObserver {
  constructor() {
    lazy.EventEmitter.decorate(this);
    this._browserSessionCount = new Map();
    lazy.gActivityDistributor.addObserver(this);
    lazy.ChannelEventSinkFactory.getService().registerCollector(this);

    this._redirectMap = new Map();

    // Request interception state.
    this._browserSuspendedChannels = new Map();
    this._extraHTTPHeaders = new Map();
    this._browserResponseStorages = new Map();

    this._onRequest = this._onRequest.bind(this);
    this._onExamineResponse = this._onResponse.bind(
      this,
      false /* fromCache */
    );
    this._onCachedResponse = this._onResponse.bind(this, true /* fromCache */);
  }

  dispose() {
    lazy.gActivityDistributor.removeObserver(this);
    lazy.ChannelEventSinkFactory.getService().unregisterCollector(this);

    Services.obs.removeObserver(this._onRequest, "http-on-modify-request");
    Services.obs.removeObserver(
      this._onExamineResponse,
      "http-on-examine-response"
    );
    Services.obs.removeObserver(
      this._onCachedResponse,
      "http-on-examine-cached-response"
    );
    Services.obs.removeObserver(
      this._onCachedResponse,
      "http-on-examine-merged-response"
    );
  }

  setExtraHTTPHeaders(browser, headers) {
    if (!headers) {
      this._extraHTTPHeaders.delete(browser);
    } else {
      this._extraHTTPHeaders.set(browser, headers);
    }
  }

  enableRequestInterception(browser) {
    if (!this._browserSuspendedChannels.has(browser)) {
      this._browserSuspendedChannels.set(browser, new Map());
    }
  }

  disableRequestInterception(browser) {
    const suspendedChannels = this._browserSuspendedChannels.get(browser);
    if (!suspendedChannels) {
      return;
    }
    this._browserSuspendedChannels.delete(browser);
    for (const channel of suspendedChannels.values()) {
      channel.resume();
    }
  }

  resumeSuspendedRequest(browser, requestId, headers) {
    const suspendedChannels = this._browserSuspendedChannels.get(browser);
    if (!suspendedChannels) {
      throw new Error(`Request interception is not enabled`);
    }
    const httpChannel = suspendedChannels.get(requestId);
    if (!httpChannel) {
      throw new Error(`Cannot find request "${requestId}"`);
    }
    if (headers) {
      // 1. Clear all previous headers.
      for (const header of requestHeaders(httpChannel)) {
        httpChannel.setRequestHeader(header.name, "", false /* merge */);
      }
      // 2. Set new headers.
      for (const header of headers) {
        httpChannel.setRequestHeader(
          header.name,
          header.value,
          false /* merge */
        );
      }
    }
    suspendedChannels.delete(requestId);
    httpChannel.resume();
  }

  getResponseBody(browser, requestId) {
    const responseStorage = this._browserResponseStorages.get(browser);
    if (!responseStorage) {
      throw new Error("Responses are not tracked for the given browser");
    }
    return responseStorage.getBase64EncodedResponse(requestId);
  }

  abortSuspendedRequest(browser, aRequestId) {
    const suspendedChannels = this._browserSuspendedChannels.get(browser);
    if (!suspendedChannels) {
      throw new Error(`Request interception is not enabled`);
    }
    const httpChannel = suspendedChannels.get(aRequestId);
    if (!httpChannel) {
      throw new Error(`Cannot find request "${aRequestId}"`);
    }
    suspendedChannels.delete(aRequestId);
    httpChannel.cancel(Cr.NS_ERROR_FAILURE);
    httpChannel.resume();
    this.emit("requestfailed", httpChannel, {
      requestId: requestId(httpChannel),
      errorCode: getNetworkErrorStatusText(httpChannel.status),
    });
  }

  _onChannelRedirect(oldChannel, newChannel) {
    // We can be called with any nsIChannel, but are interested only in HTTP channels
    try {
      oldChannel.QueryInterface(Ci.nsIHttpChannel);
      newChannel.QueryInterface(Ci.nsIHttpChannel);
    } catch (ex) {
      return;
    }

    const httpChannel = oldChannel.QueryInterface(Ci.nsIHttpChannel);
    const loadContext = getLoadContext(httpChannel);
    if (
      !loadContext ||
      !this._browserSessionCount.has(loadContext.topFrameElement)
    ) {
      return;
    }
    this._redirectMap.set(newChannel, oldChannel);
  }

  _onRequest(channel) {
    const httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
    const loadContext = getLoadContext(httpChannel);
    const browser = loadContext?.topFrameElement;
    if (!loadContext || !this._browserSessionCount.has(browser)) {
      return;
    }

    const extraHeaders = this._extraHTTPHeaders.get(browser);
    if (extraHeaders) {
      for (const header of extraHeaders) {
        httpChannel.setRequestHeader(
          header.name,
          header.value,
          false /* merge */
        );
      }
    }
    const causeType = httpChannel.loadInfo
      ? httpChannel.loadInfo.externalContentPolicyType
      : Ci.nsIContentPolicy.TYPE_OTHER;

    const suspendedChannels = this._browserSuspendedChannels.get(browser);
    if (suspendedChannels) {
      httpChannel.suspend();
      suspendedChannels.set(requestId(httpChannel), httpChannel);
    }

    const oldChannel = this._redirectMap.get(httpChannel);
    this._redirectMap.delete(httpChannel);

    // Install response body hooks.
    new ResponseBodyListener(this, browser, httpChannel);

    this.emit("request", httpChannel, {
      url: httpChannel.URI.spec,
      suspended: suspendedChannels ? true : undefined,
      requestId: requestId(httpChannel),
      redirectedFrom: oldChannel ? requestId(oldChannel) : undefined,
      postData: readRequestPostData(httpChannel),
      headers: requestHeaders(httpChannel),
      method: httpChannel.requestMethod,
      isNavigationRequest: httpChannel.isMainDocumentChannel,
      cause: causeType,
      causeString: causeTypeToString(causeType),
      frameId: this.frameId(httpChannel),
      // clients expect loaderId == requestId for document navigation
      loaderId: [
        Ci.nsIContentPolicy.TYPE_DOCUMENT,
        Ci.nsIContentPolicy.TYPE_SUBDOCUMENT,
      ].includes(causeType)
        ? requestId(httpChannel)
        : undefined,
    });
  }

  _onResponse(fromCache, httpChannel) {
    const loadContext = getLoadContext(httpChannel);
    if (
      !loadContext ||
      !this._browserSessionCount.has(loadContext.topFrameElement)
    ) {
      return;
    }
    httpChannel.QueryInterface(Ci.nsIHttpChannelInternal);
    const causeType = httpChannel.loadInfo
      ? httpChannel.loadInfo.externalContentPolicyType
      : Ci.nsIContentPolicy.TYPE_OTHER;
    let remoteIPAddress;
    let remotePort;
    try {
      remoteIPAddress = httpChannel.remoteAddress;
      remotePort = httpChannel.remotePort;
    } catch (e) {
      // remoteAddress is not defined for cached requests.
    }

    this.emit("response", httpChannel, {
      requestId: requestId(httpChannel),
      securityDetails: getSecurityDetails(httpChannel),
      fromCache,
      headers: responseHeaders(httpChannel),
      requestHeaders: requestHeaders(httpChannel),
      remoteIPAddress,
      remotePort,
      status: httpChannel.responseStatus,
      statusText: httpChannel.responseStatusText,
      cause: causeType,
      causeString: causeTypeToString(causeType),
      frameId: this.frameId(httpChannel),
      // clients expect loaderId == requestId for document navigation
      loaderId: [
        Ci.nsIContentPolicy.TYPE_DOCUMENT,
        Ci.nsIContentPolicy.TYPE_SUBDOCUMENT,
      ].includes(causeType)
        ? requestId(httpChannel)
        : undefined,
    });
  }

  _onResponseFinished(browser, httpChannel, body) {
    const responseStorage = this._browserResponseStorages.get(browser);
    if (!responseStorage) {
      return;
    }
    responseStorage.addResponseBody(httpChannel, body);
    this.emit("requestfinished", httpChannel, {
      requestId: requestId(httpChannel),
      errorCode: getNetworkErrorStatusText(httpChannel.status),
    });
  }

  isActive(browser) {
    return !!this._browserSessionCount.get(browser);
  }

  startTrackingBrowserNetwork(browser) {
    const value = this._browserSessionCount.get(browser) || 0;
    this._browserSessionCount.set(browser, value + 1);
    if (value === 0) {
      Services.obs.addObserver(this._onRequest, "http-on-modify-request");
      Services.obs.addObserver(
        this._onExamineResponse,
        "http-on-examine-response"
      );
      Services.obs.addObserver(
        this._onCachedResponse,
        "http-on-examine-cached-response"
      );
      Services.obs.addObserver(
        this._onCachedResponse,
        "http-on-examine-merged-response"
      );
      this._browserResponseStorages.set(
        browser,
        new ResponseStorage(
          MAX_RESPONSE_STORAGE_SIZE,
          MAX_RESPONSE_STORAGE_SIZE / 10
        )
      );
    }
    return () => this.stopTrackingBrowserNetwork(browser);
  }

  stopTrackingBrowserNetwork(browser) {
    const value = this._browserSessionCount.get(browser);
    if (value) {
      this._browserSessionCount.set(browser, value - 1);
    } else {
      this._browserSessionCount.delete(browser);
      this._browserResponseStorages.delete(browser);
      this.dispose();
    }
  }

  /**
   * Returns the frameId of the current httpChannel.
   */
  frameId(httpChannel) {
    const loadInfo = httpChannel.loadInfo;
    return loadInfo.frameBrowsingContext?.id || loadInfo.browsingContext.id;
  }
}

const protocolVersionNames = {
  [Ci.nsITransportSecurityInfo.TLS_VERSION_1]: "TLS 1",
  [Ci.nsITransportSecurityInfo.TLS_VERSION_1_1]: "TLS 1.1",
  [Ci.nsITransportSecurityInfo.TLS_VERSION_1_2]: "TLS 1.2",
  [Ci.nsITransportSecurityInfo.TLS_VERSION_1_3]: "TLS 1.3",
};

function getSecurityDetails(httpChannel) {
  const securityInfo = httpChannel.securityInfo;
  if (!securityInfo) {
    return null;
  }
  if (!securityInfo.serverCert) {
    return null;
  }
  return {
    protocol: protocolVersionNames[securityInfo.protocolVersion] || "<unknown>",
    subjectName: securityInfo.serverCert.commonName,
    issuer: securityInfo.serverCert.issuerCommonName,
    // Convert to seconds.
    validFrom: securityInfo.serverCert.validity.notBefore / 1000 / 1000,
    validTo: securityInfo.serverCert.validity.notAfter / 1000 / 1000,
  };
}

function readRequestPostData(httpChannel) {
  if (!(httpChannel instanceof Ci.nsIUploadChannel)) {
    return undefined;
  }
  const iStream = httpChannel.uploadStream;
  if (!iStream) {
    return undefined;
  }
  const isSeekableStream = iStream instanceof Ci.nsISeekableStream;

  let prevOffset;
  if (isSeekableStream) {
    prevOffset = iStream.tell();
    iStream.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
  }

  // Read data from the stream.
  let text;
  try {
    text = lazy.NetUtil.readInputStreamToString(iStream, iStream.available());
    const converter = Cc[
      "@mozilla.org/intl/scriptableunicodeconverter"
    ].createInstance(Ci.nsIScriptableUnicodeConverter);
    converter.charset = "UTF-8";
    text = converter.ConvertToUnicode(text);
  } catch (err) {
    text = undefined;
  }

  // Seek locks the file, so seek to the beginning only if necko hasn"t
  // read it yet, since necko doesn"t seek to 0 before reading (at lest
  // not till 459384 is fixed).
  if (isSeekableStream && prevOffset == 0) {
    iStream.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
  }
  return text;
}

function getLoadContext(httpChannel) {
  let loadContext = null;
  try {
    if (httpChannel.notificationCallbacks) {
      loadContext = httpChannel.notificationCallbacks.getInterface(
        Ci.nsILoadContext
      );
    }
  } catch (e) {}
  try {
    if (!loadContext && httpChannel.loadGroup) {
      loadContext = httpChannel.loadGroup.notificationCallbacks.getInterface(
        Ci.nsILoadContext
      );
    }
  } catch (e) {}
  return loadContext;
}

function requestId(httpChannel) {
  return String(httpChannel.channelId);
}

function requestHeaders(httpChannel) {
  const headers = [];
  httpChannel.visitRequestHeaders({
    visitHeader: (name, value) => headers.push({ name, value }),
  });
  return headers;
}

function responseHeaders(httpChannel) {
  const headers = [];
  httpChannel.visitResponseHeaders({
    visitHeader: (name, value) => headers.push({ name, value }),
  });
  return headers;
}

function causeTypeToString(causeType) {
  for (let key in Ci.nsIContentPolicy) {
    if (Ci.nsIContentPolicy[key] === causeType) {
      return key;
    }
  }
  return "TYPE_OTHER";
}

class ResponseStorage {
  constructor(maxTotalSize, maxResponseSize) {
    this._totalSize = 0;
    this._maxResponseSize = maxResponseSize;
    this._maxTotalSize = maxTotalSize;
    this._responses = new Map();
  }

  addResponseBody(httpChannel, body) {
    if (body.length > this._maxResponseSize) {
      this._responses.set(requestId, {
        evicted: true,
        body: "",
      });
      return;
    }
    let encodings = [];
    if (
      httpChannel instanceof Ci.nsIEncodedChannel &&
      httpChannel.contentEncodings &&
      !httpChannel.applyConversion
    ) {
      const encodingHeader = httpChannel.getResponseHeader("Content-Encoding");
      encodings = encodingHeader.split(/\s*\t*,\s*\t*/);
    }
    this._responses.set(requestId(httpChannel), { body, encodings });
    this._totalSize += body.length;
    if (this._totalSize > this._maxTotalSize) {
      for (let [, response] of this._responses) {
        this._totalSize -= response.body.length;
        response.body = "";
        response.evicted = true;
        if (this._totalSize < this._maxTotalSize) {
          break;
        }
      }
    }
  }

  getBase64EncodedResponse(requestId) {
    const response = this._responses.get(requestId);
    if (!response) {
      throw new Error(`Request "${requestId}" is not found`);
    }
    if (response.evicted) {
      return { base64body: "", evicted: true };
    }
    let result = response.body;
    if (response.encodings && response.encodings.length) {
      for (const encoding of response.encodings) {
        result = lazy.CommonUtils.convertString(
          result,
          encoding,
          "uncompressed"
        );
      }
    }
    return { base64body: btoa(result) };
  }
}

class ResponseBodyListener {
  constructor(networkObserver, browser, httpChannel) {
    this._networkObserver = networkObserver;
    this._browser = browser;
    this._httpChannel = httpChannel;
    this._chunks = [];
    this.QueryInterface = ChromeUtils.generateQI(["nsIStreamListener"]);
    httpChannel.QueryInterface(Ci.nsITraceableChannel);
    this.originalListener = httpChannel.setNewListener(this);
  }

  onDataAvailable(aRequest, aInputStream, aOffset, aCount) {
    const iStream = new lazy.BinaryInputStream(aInputStream);
    const sStream = new lazy.StorageStream(8192, aCount, null);
    const oStream = new lazy.BinaryOutputStream(sStream.getOutputStream(0));

    // Copy received data as they come.
    const data = iStream.readBytes(aCount);
    this._chunks.push(data);

    oStream.writeBytes(data, aCount);
    this.originalListener.onDataAvailable(
      aRequest,
      sStream.newInputStream(0),
      aOffset,
      aCount
    );
  }

  onStartRequest(aRequest) {
    this.originalListener.onStartRequest(aRequest);
  }

  onStopRequest(aRequest, aStatusCode) {
    this.originalListener.onStopRequest(aRequest, aStatusCode);
    const body = this._chunks.join("");
    delete this._chunks;
    this._networkObserver._onResponseFinished(
      this._browser,
      this._httpChannel,
      body
    );
  }
}

function getNetworkErrorStatusText(status) {
  if (!status) {
    return null;
  }
  for (const key of Object.keys(Cr)) {
    if (Cr[key] === status) {
      return key;
    }
  }
  // Security module. The following is taken from
  // https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/How_to_check_the_secruity_state_of_an_XMLHTTPRequest_over_SSL
  if ((status & 0xff0000) === 0x5a0000) {
    // NSS_SEC errors (happen below the base value because of negative vals)
    if (
      (status & 0xffff) <
      Math.abs(Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE)
    ) {
      // The bases are actually negative, so in our positive numeric space, we
      // need to subtract the base off our value.
      const nssErr =
        Math.abs(Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE) - (status & 0xffff);
      switch (nssErr) {
        case 11:
          return "SEC_ERROR_EXPIRED_CERTIFICATE";
        case 12:
          return "SEC_ERROR_REVOKED_CERTIFICATE";
        case 13:
          return "SEC_ERROR_UNKNOWN_ISSUER";
        case 20:
          return "SEC_ERROR_UNTRUSTED_ISSUER";
        case 21:
          return "SEC_ERROR_UNTRUSTED_CERT";
        case 36:
          return "SEC_ERROR_CA_CERT_INVALID";
        case 90:
          return "SEC_ERROR_INADEQUATE_KEY_USAGE";
        case 176:
          return "SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED";
        default:
          return "SEC_ERROR_UNKNOWN";
      }
    }
    const sslErr =
      Math.abs(Ci.nsINSSErrorsService.NSS_SSL_ERROR_BASE) - (status & 0xffff);
    switch (sslErr) {
      case 3:
        return "SSL_ERROR_NO_CERTIFICATE";
      case 4:
        return "SSL_ERROR_BAD_CERTIFICATE";
      case 8:
        return "SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE";
      case 9:
        return "SSL_ERROR_UNSUPPORTED_VERSION";
      case 12:
        return "SSL_ERROR_BAD_CERT_DOMAIN";
      default:
        return "SSL_ERROR_UNKNOWN";
    }
  }
  return "<unknown error>";
}
PK
!<�Ľ�:chrome/remote/content/cdp/observers/TargetObserver.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",

  EventPromise: "chrome://remote/content/shared/Sync.sys.mjs",
});

// TODO(ato):
//
// The DOM team is working on pulling browsing context related behaviour,
// such as window and tab handling, out of product code and into the platform.
// This will have implication for the remote agent,
// and as the platform gains support for product-independent events
// we can likely get rid of this entire module.

/**
 * Observe Firefox tabs as they open and close.
 *
 * "open" fires when a tab opens.
 * "close" fires when a tab closes.
 */
export class TabObserver {
  /**
   * @param {boolean?} [false] registerExisting
   *     Events will be fired for ChromeWIndows and their respective tabs
   *     at the time when the observer is started.
   */
  constructor({ registerExisting = false } = {}) {
    lazy.EventEmitter.decorate(this);

    this.registerExisting = registerExisting;

    this.onTabOpen = this.onTabOpen.bind(this);
    this.onTabClose = this.onTabClose.bind(this);
  }

  async start() {
    Services.wm.addListener(this);

    if (this.registerExisting) {
      // Start listening for events on already open windows
      for (const win of Services.wm.getEnumerator("navigator:browser")) {
        this._registerDOMWindow(win);
      }
    }
  }

  stop() {
    Services.wm.removeListener(this);

    // Stop listening for events on still opened windows
    for (const win of Services.wm.getEnumerator("navigator:browser")) {
      this._unregisterDOMWindow(win);
    }
  }

  // Event emitters

  onTabOpen({ target }) {
    this.emit("open", target);
  }

  onTabClose({ target }) {
    this.emit("close", target);
  }

  // Internal methods

  _registerDOMWindow(win) {
    for (const tab of win.gBrowser.tabs) {
      // a missing linkedBrowser means the tab is still initialising,
      // and a TabOpen event will fire once it is ready
      if (!tab.linkedBrowser) {
        continue;
      }

      this.onTabOpen({ target: tab });
    }

    win.gBrowser.tabContainer.addEventListener("TabOpen", this.onTabOpen);
    win.gBrowser.tabContainer.addEventListener("TabClose", this.onTabClose);
  }

  _unregisterDOMWindow(win) {
    for (const tab of win.gBrowser.tabs) {
      // a missing linkedBrowser means the tab is still initialising
      if (!tab.linkedBrowser) {
        continue;
      }

      // Emulate custom "TabClose" events because that event is not
      // fired for each of the tabs when the window closes.
      this.onTabClose({ target: tab });
    }

    win.gBrowser.tabContainer.removeEventListener("TabOpen", this.onTabOpen);
    win.gBrowser.tabContainer.removeEventListener("TabClose", this.onTabClose);
  }

  // nsIWindowMediatorListener

  async onOpenWindow(xulWindow) {
    const win = xulWindow.docShell.domWindow;

    await new lazy.EventPromise(win, "load");

    // Return early if it's not a browser window
    if (
      win.document.documentElement.getAttribute("windowtype") !=
      "navigator:browser"
    ) {
      return;
    }

    this._registerDOMWindow(win);
  }

  onCloseWindow(xulWindow) {
    const win = xulWindow.docShell.domWindow;

    // Return early if it's not a browser window
    if (
      win.document.documentElement.getAttribute("windowtype") !=
      "navigator:browser"
    ) {
      return;
    }

    this._unregisterDOMWindow(win);
  }

  // XPCOM

  get QueryInterface() {
    return ChromeUtils.generateQI(["nsIWindowMediatorListener"]);
  }
}
PK
!<�p�l��@chrome/remote/content/cdp/sessions/ContentProcessSession.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ContentProcessDomains:
    "chrome://remote/content/cdp/domains/ContentProcessDomains.sys.mjs",
  ContextObserver:
    "chrome://remote/content/cdp/observers/ContextObserver.sys.mjs",
  DomainCache: "chrome://remote/content/cdp/domains/DomainCache.sys.mjs",
});

export class ContentProcessSession {
  constructor(messageManager, browsingContext, content, docShell) {
    this.messageManager = messageManager;
    this.browsingContext = browsingContext;
    this.content = content;
    this.docShell = docShell;
    // Most children or sibling classes are going to assume that docShell
    // implements the following interface. So do the QI only once from here.
    this.docShell.QueryInterface(Ci.nsIWebNavigation);

    this.domains = new lazy.DomainCache(this, lazy.ContentProcessDomains);
    this.messageManager.addMessageListener("remote:request", this);
    this.messageManager.addMessageListener("remote:destroy", this);
  }

  destructor() {
    this._contextObserver?.destructor();

    this.messageManager.removeMessageListener("remote:request", this);
    this.messageManager.removeMessageListener("remote:destroy", this);
    this.domains.clear();
  }

  get contextObserver() {
    if (!this._contextObserver) {
      this._contextObserver = new lazy.ContextObserver(
        this.docShell.chromeEventHandler
      );
    }
    return this._contextObserver;
  }

  // Domain event listener

  onEvent(eventName, params) {
    this.messageManager.sendAsyncMessage("remote:event", {
      browsingContextId: this.browsingContext.id,
      event: {
        eventName,
        params,
      },
    });
  }

  // nsIMessageListener

  async receiveMessage({ name, data }) {
    const { browsingContextId } = data;

    // We may have more than one tab loaded in the same process,
    // and debug the two at the same time. We want to ensure not
    // mixing up the requests made against two such tabs.
    // Each tab is going to have its own frame script instance
    // and two communication channels are going to be set up via
    // the two message managers.
    if (browsingContextId != this.browsingContext.id) {
      return;
    }

    switch (name) {
      case "remote:request":
        try {
          const { id, domain, command, params } = data.request;

          const result = await this.domains.execute(domain, command, params);

          this.messageManager.sendAsyncMessage("remote:result", {
            browsingContextId,
            id,
            result,
          });
        } catch (e) {
          this.messageManager.sendAsyncMessage("remote:error", {
            browsingContextId,
            id: data.request.id,
            error: {
              name: e.name || "exception",
              message: e.message || String(e),
              stack: e.stack,
            },
          });
        }
        break;

      case "remote:destroy":
        this.destructor();
        break;
    }
  }
}
PK
!<�r��=chrome/remote/content/cdp/sessions/MainProcessSession.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Session } from "chrome://remote/content/cdp/sessions/Session.sys.mjs";

/**
 * A session, dedicated to the main process target.
 * For some reason, it doesn't need any specific code and can share the base Session class
 * aside TabSession.
 */
export class MainProcessSession extends Session {}
PK
!<��}�
�
2chrome/remote/content/cdp/sessions/Session.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  DomainCache: "chrome://remote/content/cdp/domains/DomainCache.sys.mjs",
  NetworkObserver:
    "chrome://remote/content/cdp/observers/NetworkObserver.sys.mjs",
  ParentProcessDomains:
    "chrome://remote/content/cdp/domains/ParentProcessDomains.sys.mjs",
});

/**
 * A session represents exactly one client WebSocket connection.
 *
 * Every new WebSocket connections is associated with one session that
 * deals with dispatching incoming command requests to the right
 * target, sending back responses, and propagating events originating
 * from domains.
 * Then, some subsequent Sessions may be created over a single WebSocket
 * connection. In this case, the subsequent session will have an `id`
 * being passed to their constructor and each packet of these sessions
 * will have a `sessionId` attribute in order to filter the packets
 * by session both on client and server side.
 */
export class Session {
  /**
   * @param {Connection} connection
   *        The connection used to communicate with the server.
   * @param {Target} target
   *        The target to which this session communicates with.
   * @param {number=} id
   *        If this session isn't the default one used for the HTTP endpoint we
   *        connected to, the session requires an id to distinguish it from the default
   *        one. This id is used to filter our request, responses and events between
   *        all active sessions. For now, this is only passed by `Target.attachToTarget()`.
   */
  constructor(connection, target, id) {
    this.connection = connection;
    this.target = target;
    this.id = id;

    this.domains = new lazy.DomainCache(this, lazy.ParentProcessDomains);
  }

  destructor() {
    if (
      this.networkObserver &&
      this.networkObserver.isActive(this.target.browser)
    ) {
      this.networkObserver.dispose();
    }
    this.domains.clear();
  }

  execute(id, domain, command, params) {
    return this.domains.execute(domain, command, params);
  }

  get networkObserver() {
    if (!this._networkObserver) {
      this._networkObserver = new lazy.NetworkObserver();
    }
    return this._networkObserver;
  }

  /**
   * Domains event listener. Called when an event is fired
   * by any Domain and has to be sent to the client.
   */
  onEvent(eventName, params) {
    this.connection.sendEvent(eventName, params, this.id);
  }

  toString() {
    return `[object ${this.constructor.name} ${this.connection.id}]`;
  }
}
PK
!<�Vۮ@@5chrome/remote/content/cdp/sessions/TabSession.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Session } from "chrome://remote/content/cdp/sessions/Session.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "chrome://remote/content/shared/Log.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.CDP)
);

/**
 * A session to communicate with a given tab
 */
export class TabSession extends Session {
  /**
   * @param {Connection} connection
   *        The connection used to communicate with the server.
   * @param {TabTarget} target
   *        The tab target to which this session communicates with.
   * @param {number=} id
   *        If this session isn't the default one used for the HTTP endpoint we
   *        connected to, the session requires an id to distinguish it from the default
   *        one. This id is used to filter our request, responses and events between
   *        all active sessions.
   *        For now, this is only passed by `Target.attachToTarget()`.
   *        Otherwise it will be undefined when you are connecting directly to
   *        a given Tab. i.e. connect directly to the WebSocket URL provided by
   *        /json/list HTTP endpoint.
   */
  constructor(connection, target, id) {
    super(connection, target, id);

    // Request id => { resolve, reject }
    this.requestPromises = new Map();

    this.registerFramescript(this.mm);

    this.target.browser.addEventListener("XULFrameLoaderCreated", this);
  }

  destructor() {
    super.destructor();

    this.requestPromises.clear();

    this.target.browser.removeEventListener("XULFrameLoaderCreated", this);

    // this.mm might be null if the browser of the TabTarget was already closed.
    // See Bug 1747301.
    this.mm?.sendAsyncMessage("remote:destroy", {
      browsingContextId: this.browsingContext.id,
    });

    this.mm?.removeMessageListener("remote:event", this);
    this.mm?.removeMessageListener("remote:result", this);
    this.mm?.removeMessageListener("remote:error", this);
  }

  execute(id, domain, command, params) {
    // Check if the domain and command is implemented in the parent
    // and execute it there. Otherwise forward the command to the content process
    // in order to try to execute it in the content process.
    if (this.domains.domainSupportsMethod(domain, command)) {
      return super.execute(id, domain, command, params);
    }
    return this.executeInChild(id, domain, command, params);
  }

  executeInChild(id, domain, command, params) {
    return new Promise((resolve, reject) => {
      // Save the promise's resolution and rejection handler in order to later
      // resolve this promise once we receive the reply back from the content process.
      this.requestPromises.set(id, { resolve, reject });

      this.mm.sendAsyncMessage("remote:request", {
        browsingContextId: this.browsingContext.id,
        request: { id, domain, command, params },
      });
    });
  }

  get mm() {
    return this.target.mm;
  }

  get browsingContext() {
    return this.target.browsingContext;
  }

  /**
   * Register the framescript and listeners for the given message manager.
   *
   * @param {MessageManager} messageManager
   *     The message manager to use.
   */
  registerFramescript(messageManager) {
    messageManager.loadFrameScript(
      "chrome://remote/content/cdp/sessions/frame-script.js",
      false
    );

    messageManager.addMessageListener("remote:event", this);
    messageManager.addMessageListener("remote:result", this);
    messageManager.addMessageListener("remote:error", this);
  }

  // Event handler
  handleEvent = function ({ target, type }) {
    switch (type) {
      case "XULFrameLoaderCreated":
        if (target === this.target.browser) {
          lazy.logger.trace("Remoteness change detected");
          this.registerFramescript(this.mm);
        }
        break;
    }
  };

  // nsIMessageListener

  receiveMessage({ name, data }) {
    const { id, result, event, error } = data;

    switch (name) {
      case "remote:result":
        const { resolve } = this.requestPromises.get(id);
        resolve(result);
        this.requestPromises.delete(id);
        break;

      case "remote:event":
        this.connection.sendEvent(event.eventName, event.params, this.id);
        break;

      case "remote:error":
        const { reject } = this.requestPromises.get(id);
        reject(error);
        this.requestPromises.delete(id);
        break;
    }
  }
}
PK
!<�n�2��2chrome/remote/content/cdp/sessions/frame-script.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/frame-script */

"use strict";

const { ContentProcessSession } = ChromeUtils.importESModule(
  "chrome://remote/content/cdp/sessions/ContentProcessSession.sys.mjs"
);

new ContentProcessSession(this, docShell.browsingContext, content, docShell);
PK
!<�+*��;chrome/remote/content/cdp/targets/MainProcessTarget.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Target } from "chrome://remote/content/cdp/targets/Target.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  MainProcessSession:
    "chrome://remote/content/cdp/sessions/MainProcessSession.sys.mjs",
  RemoteAgent: "chrome://remote/content/components/RemoteAgent.sys.mjs",
});

/**
 * The main process Target.
 *
 * Matches BrowserDevToolsAgentHost from chromium, and only support a couple of Domains:
 * https://cs.chromium.org/chromium/src/content/browser/devtools/browser_devtools_agent_host.cc?dr=CSs&g=0&l=80-91
 */
export class MainProcessTarget extends Target {
  /*
   * @param TargetList targetList
   */
  constructor(targetList) {
    super(targetList, lazy.MainProcessSession);

    this.type = "browser";

    // Define the HTTP path to query this target
    this.path = `/devtools/browser/${this.id}`;
  }

  get wsDebuggerURL() {
    const { host, port } = lazy.RemoteAgent;
    return `ws://${host}:${port}${this.path}`;
  }

  toString() {
    return `[object MainProcessTarget]`;
  }

  toJSON() {
    return {
      description: "Main process target",
      devtoolsFrontendUrl: "",
      faviconUrl: "",
      id: this.id,
      title: "Main process target",
      type: this.type,
      url: "",
      webSocketDebuggerUrl: this.wsDebuggerURL,
    };
  }
}
PK
!<�.��3chrome/remote/content/cdp/targets/TabTarget.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { Target } from "chrome://remote/content/cdp/targets/Target.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  RemoteAgent: "chrome://remote/content/components/RemoteAgent.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
  TabSession: "chrome://remote/content/cdp/sessions/TabSession.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "Favicons",
  "@mozilla.org/browser/favicon-service;1",
  "nsIFaviconService"
);

/**
 * Target for a local tab or a remoted frame.
 */
export class TabTarget extends Target {
  /**
   * @param {TargetList} targetList
   * @param {BrowserElement} browser
   */
  constructor(targetList, browser) {
    super(targetList, lazy.TabSession);

    this.browser = browser;

    // The tab target uses a unique id as shared with WebDriver to reference
    // a specific tab.
    this.id = lazy.TabManager.getIdForBrowser(browser);

    // Define the HTTP path to query this target
    this.path = `/devtools/page/${this.id}`;

    Services.obs.addObserver(this, "message-manager-disconnect");
  }

  destructor() {
    Services.obs.removeObserver(this, "message-manager-disconnect");
    super.destructor();
  }

  get browserContextId() {
    return parseInt(this.browser.getAttribute("usercontextid"));
  }

  get browsingContext() {
    return this.browser.browsingContext;
  }

  get mm() {
    return this.browser.messageManager;
  }

  get window() {
    return this.browser.ownerGlobal;
  }

  get tab() {
    return this.window.gBrowser.getTabForBrowser(this.browser);
  }

  /**
   * Determines if the content browser remains attached
   * to its parent chrome window.
   *
   * We determine this by checking if the <browser> element
   * is still attached to the DOM.
   *
   * @returns {boolean}
   *     True if target's browser is still attached,
   *     false if it has been disconnected.
   */
  get closed() {
    return !this.browser || !this.browser.isConnected;
  }

  get description() {
    return "";
  }

  get frontendURL() {
    return null;
  }

  /** @returns {Promise<string|null>} */
  get faviconUrl() {
    return new Promise(resolve => {
      lazy.Favicons.getFaviconURLForPage(this.browser.currentURI, url => {
        if (url) {
          resolve(url.spec);
        } else {
          resolve(null);
        }
      });
    });
  }

  get title() {
    return this.browsingContext.currentWindowGlobal.documentTitle;
  }

  get type() {
    return "page";
  }

  get url() {
    return this.browser.currentURI.spec;
  }

  get wsDebuggerURL() {
    const { host, port } = lazy.RemoteAgent;
    return `ws://${host}:${port}${this.path}`;
  }

  toString() {
    return `[object Target ${this.id}]`;
  }

  toJSON() {
    return {
      description: this.description,
      devtoolsFrontendUrl: this.frontendURL,
      // TODO(ato): toJSON cannot be marked async )-:
      faviconUrl: "",
      id: this.id,
      // Bug 1680817: Fails to encode some UTF-8 characters
      // title: this.title,
      type: this.type,
      url: this.url,
      webSocketDebuggerUrl: this.wsDebuggerURL,
    };
  }

  // nsIObserver

  observe(subject) {
    if (subject === this.mm && subject == "message-manager-disconnect") {
      // disconnect debugging target if <browser> is disconnected,
      // otherwise this is just a host process change
      if (this.closed) {
        this.disconnect();
      }
    }
  }

  // XPCOM

  get QueryInterface() {
    return ChromeUtils.generateQI(["nsIHttpRequestHandler", "nsIObserver"]);
  }
}
PK
!<|#�lII0chrome/remote/content/cdp/targets/Target.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  CDPConnection: "chrome://remote/content/cdp/CDPConnection.sys.mjs",
  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
  WebSocketHandshake:
    "chrome://remote/content/server/WebSocketHandshake.sys.mjs",
});

/**
 * Base class for all the targets.
 */
export class Target {
  /**
   * @param {TargetList} targetList
   * @param {Class} sessionClass
   */
  constructor(targetList, sessionClass) {
    // Save a reference to TargetList instance in order to expose it to:
    // domains/parent/Target.sys.mjs
    this.targetList = targetList;

    // When a new connection is made to this target,
    // we will instantiate a new session based on this given class.
    // The session class is specific to each target's kind and is passed
    // by the inheriting class.
    this.sessionClass = sessionClass;

    // There can be more than one connection if multiple clients connect to the remote agent.
    this.connections = new Set();
    this.id = lazy.generateUUID();
  }

  /**
   * Close all active connections made to this target.
   */
  destructor() {
    for (const conn of this.connections) {
      conn.close();
    }
  }

  // nsIHttpRequestHandler

  async handle(request, response) {
    const webSocket = await lazy.WebSocketHandshake.upgrade(request, response);
    const conn = new lazy.CDPConnection(webSocket, response._connection);
    const session = new this.sessionClass(conn, this);
    conn.registerSession(session);
    this.connections.add(conn);
  }

  // XPCOM

  get QueryInterface() {
    return ChromeUtils.generateQI(["nsIHttpRequestHandler"]);
  }
}
PK
!<x��M��4chrome/remote/content/cdp/targets/TargetList.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",

  MainProcessTarget:
    "chrome://remote/content/cdp/targets/MainProcessTarget.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
  TabObserver: "chrome://remote/content/cdp/observers/TargetObserver.sys.mjs",
  TabTarget: "chrome://remote/content/cdp/targets/TabTarget.sys.mjs",
});

export class TargetList {
  constructor() {
    // Target ID -> Target
    this._targets = new Map();

    lazy.EventEmitter.decorate(this);
  }

  /**
   * Start listing and listening for all the debuggable targets
   */
  async watchForTargets() {
    await this.watchForTabs();
  }

  unwatchForTargets() {
    this.unwatchForTabs();
  }

  /**
   * Watch for all existing and new tabs being opened.
   * So that we can create the related TabTarget instance for
   * each of them.
   */
  async watchForTabs() {
    if (this.tabObserver) {
      throw new Error("Targets is already watching for new tabs");
    }

    this.tabObserver = new lazy.TabObserver({ registerExisting: true });

    // Handle creation of tab targets for opened tabs.
    this.tabObserver.on("open", async (eventName, tab) => {
      const target = new lazy.TabTarget(this, tab.linkedBrowser);
      this.registerTarget(target);
    });

    // Handle removal of tab targets when tabs are closed.
    this.tabObserver.on("close", (eventName, tab) => {
      const browser = tab.linkedBrowser;

      // Ignore unloaded tabs.
      if (browser.browserId === 0) {
        return;
      }

      const id = lazy.TabManager.getIdForBrowser(browser);
      const target = Array.from(this._targets.values()).find(
        target => target.id == id
      );
      if (target) {
        this.destroyTarget(target);
      }
    });
    await this.tabObserver.start();
  }

  unwatchForTabs() {
    if (this.tabObserver) {
      this.tabObserver.stop();
      this.tabObserver = null;
    }
  }

  /**
   * To be called right after instantiating a new Target instance.
   * This will hold the new instance in the list and notify about
   * its creation.
   */
  registerTarget(target) {
    this._targets.set(target.id, target);
    this.emit("target-created", target);
  }

  /**
   * To be called when the debuggable target has been destroy.
   * So that we can notify it no longer exists and disconnect
   * all connecting made to debug it.
   */
  destroyTarget(target) {
    target.destructor();
    this._targets.delete(target.id);
    this.emit("target-destroyed", target);
  }

  /**
   * Destroy all the registered target of all kinds.
   * This will end up dropping all connections made to debug any of them.
   */
  destructor() {
    for (const target of this) {
      this.destroyTarget(target);
    }
    this._targets.clear();
    if (this.mainProcessTarget) {
      this.mainProcessTarget = null;
    }

    this.unwatchForTargets();
  }

  get size() {
    return this._targets.size;
  }

  /**
   * Get Target instance by target id
   *
   * @param {string} id
   *     Target id
   *
   * @returns {Target}
   */
  getById(id) {
    return this._targets.get(id);
  }

  /**
   * Get the Target instance for the main process.
   * This target is a singleton and only exposes a subset of domains.
   */
  getMainProcessTarget() {
    if (!this.mainProcessTarget) {
      this.mainProcessTarget = new lazy.MainProcessTarget(this);
      this.registerTarget(this.mainProcessTarget);
    }
    return this.mainProcessTarget;
  }

  *[Symbol.iterator]() {
    for (const target of this._targets.values()) {
      yield target;
    }
  }

  toJSON() {
    return [...this];
  }

  toString() {
    return `[object TargetList ${this.size}]`;
  }
}
PK
!<�a�a%a%3chrome/remote/content/components/Marionette.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Deferred: "chrome://remote/content/shared/Sync.sys.mjs",
  EnvironmentPrefs: "chrome://remote/content/marionette/prefs.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  MarionettePrefs: "chrome://remote/content/marionette/prefs.sys.mjs",
  RecommendedPreferences:
    "chrome://remote/content/shared/RecommendedPreferences.sys.mjs",
  TCPListener: "chrome://remote/content/marionette/server.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
);

ChromeUtils.defineLazyGetter(lazy, "textEncoder", () => new TextEncoder());

const NOTIFY_LISTENING = "marionette-listening";

// Complements -marionette flag for starting the Marionette server.
// We also set this if Marionette is running in order to start the server
// again after a Firefox restart.
const ENV_ENABLED = "MOZ_MARIONETTE";

// Besides starting based on existing prefs in a profile and a command
// line flag, we also support inheriting prefs out of an env var, and to
// start Marionette that way.
//
// This allows marionette prefs to persist when we do a restart into
// a different profile in order to test things like Firefox refresh.
// The environment variable itself, if present, is interpreted as a
// JSON structure, with the keys mapping to preference names in the
// "marionette." branch, and the values to the values of those prefs. So
// something like {"port": 4444} would result in the marionette.port
// pref being set to 4444.
const ENV_PRESERVE_PREFS = "MOZ_MARIONETTE_PREF_STATE_ACROSS_RESTARTS";

const isRemote =
  Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;

class MarionetteParentProcess {
  #browserStartupFinished;

  constructor() {
    this.server = null;
    this._activePortPath;

    this.classID = Components.ID("{786a1369-dca5-4adc-8486-33d23c88010a}");
    this.helpInfo = "  --marionette       Enable remote control server.\n";

    // Initially set the enabled state based on the environment variable.
    this.enabled = Services.env.exists(ENV_ENABLED);

    Services.ppmm.addMessageListener("Marionette:IsRunning", this);

    this.#browserStartupFinished = lazy.Deferred();
  }

  /**
   * A promise that resolves when the initial application window has been opened.
   *
   * @returns {Promise}
   *     Promise that resolves when the initial application window is open.
   */
  get browserStartupFinished() {
    return this.#browserStartupFinished.promise;
  }

  get enabled() {
    return this._enabled;
  }

  set enabled(value) {
    // Return early if Marionette is already marked as being enabled.
    // There is also no possibility to disable Marionette once it got enabled.
    if (this._enabled || !value) {
      return;
    }

    this._enabled = value;
    lazy.logger.info(`Marionette enabled`);
  }

  get running() {
    return !!this.server && this.server.alive;
  }

  receiveMessage({ name }) {
    switch (name) {
      case "Marionette:IsRunning":
        return this.running;

      default:
        lazy.logger.warn("Unknown IPC message to parent process: " + name);
        return null;
    }
  }

  handle(cmdLine) {
    // `handle` is called too late in certain cases (eg safe mode, see comment
    // above "command-line-startup"). So the marionette command line argument
    // will always be processed in `observe`.
    // However it still needs to be consumed by the command-line-handler API,
    // to avoid issues on macos.
    // TODO: remove after Bug 1724251 is fixed.
    cmdLine.handleFlag("marionette", false);
  }

  async observe(subject, topic) {
    if (this.enabled) {
      lazy.logger.trace(`Received observer notification ${topic}`);
    }

    switch (topic) {
      case "profile-after-change":
        Services.obs.addObserver(this, "command-line-startup");
        break;

      // In safe mode the command line handlers are getting parsed after the
      // safe mode dialog has been closed. To allow Marionette to start
      // earlier, use the CLI startup observer notification for
      // special-cased handlers, which gets fired before the dialog appears.
      case "command-line-startup":
        Services.obs.removeObserver(this, topic);

        this.enabled = subject.handleFlag("marionette", false);

        if (this.enabled) {
          // Marionette needs to be initialized before any window is shown.
          Services.obs.addObserver(this, "final-ui-startup");

          // We want to suppress the modal dialog that's shown
          // when starting up in safe-mode to enable testing.
          if (Services.appinfo.inSafeMode) {
            Services.obs.addObserver(this, "domwindowopened");
          }

          lazy.RecommendedPreferences.applyPreferences();

          // Only set preferences to preserve in a new profile
          // when Marionette is enabled.
          for (let [pref, value] of lazy.EnvironmentPrefs.from(
            ENV_PRESERVE_PREFS
          )) {
            switch (typeof value) {
              case "string":
                Services.prefs.setStringPref(pref, value);
                break;
              case "boolean":
                Services.prefs.setBoolPref(pref, value);
                break;
              case "number":
                Services.prefs.setIntPref(pref, value);
                break;
              default:
                throw new TypeError(`Invalid preference type: ${typeof value}`);
            }
          }
        }
        break;

      case "domwindowopened":
        Services.obs.removeObserver(this, topic);
        this.suppressSafeModeDialog(subject);
        break;

      case "final-ui-startup":
        Services.obs.removeObserver(this, topic);

        Services.obs.addObserver(this, "browser-idle-startup-tasks-finished");
        Services.obs.addObserver(this, "mail-idle-startup-tasks-finished");
        Services.obs.addObserver(this, "quit-application");

        await this.init();
        break;

      // Used to wait until the initial application window has been opened.
      case "browser-idle-startup-tasks-finished":
      case "mail-idle-startup-tasks-finished":
        Services.obs.removeObserver(
          this,
          "browser-idle-startup-tasks-finished"
        );
        Services.obs.removeObserver(this, "mail-idle-startup-tasks-finished");
        this.#browserStartupFinished.resolve();
        break;

      case "quit-application":
        Services.obs.removeObserver(this, topic);
        await this.uninit();
        break;
    }
  }

  suppressSafeModeDialog(win) {
    win.addEventListener(
      "load",
      () => {
        let dialog = win.document.getElementById("safeModeDialog");
        if (dialog) {
          // accept the dialog to start in safe-mode
          lazy.logger.trace("Safe mode detected, supressing dialog");
          win.setTimeout(() => {
            dialog.getButton("accept").click();
          });
        }
      },
      { once: true }
    );
  }

  async init() {
    if (!this.enabled || this.running) {
      lazy.logger.debug(
        `Init aborted (enabled=${this.enabled}, running=${this.running})`
      );
      return;
    }

    try {
      this.server = new lazy.TCPListener(lazy.MarionettePrefs.port);
      await this.server.start();
    } catch (e) {
      lazy.logger.fatal("Marionette server failed to start", e);
      Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
      return;
    }

    Services.env.set(ENV_ENABLED, "1");
    Services.obs.notifyObservers(this, NOTIFY_LISTENING, true);
    lazy.logger.debug("Marionette is listening");

    // Write Marionette port to MarionetteActivePort file within the profile.
    this._activePortPath = PathUtils.join(
      PathUtils.profileDir,
      "MarionetteActivePort"
    );

    const data = `${this.server.port}`;
    try {
      await IOUtils.write(this._activePortPath, lazy.textEncoder.encode(data));
    } catch (e) {
      lazy.logger.warn(
        `Failed to create ${this._activePortPath} (${e.message})`
      );
    }
  }

  async uninit() {
    if (this.running) {
      await this.server.stop();
      Services.obs.notifyObservers(this, NOTIFY_LISTENING);
      lazy.logger.debug("Marionette stopped listening");

      try {
        await IOUtils.remove(this._activePortPath);
      } catch (e) {
        lazy.logger.warn(
          `Failed to remove ${this._activePortPath} (${e.message})`
        );
      }
    }
  }

  get QueryInterface() {
    return ChromeUtils.generateQI([
      "nsICommandLineHandler",
      "nsIMarionette",
      "nsIObserver",
    ]);
  }
}

class MarionetteContentProcess {
  constructor() {
    this.classID = Components.ID("{786a1369-dca5-4adc-8486-33d23c88010a}");
  }

  get running() {
    let reply = Services.cpmm.sendSyncMessage("Marionette:IsRunning");
    if (!reply.length) {
      lazy.logger.warn("No reply from parent process");
      return false;
    }
    return reply[0];
  }

  get QueryInterface() {
    return ChromeUtils.generateQI(["nsIMarionette"]);
  }
}

export var Marionette;
if (isRemote) {
  Marionette = new MarionetteContentProcess();
} else {
  Marionette = new MarionetteParentProcess();
}

// This is used by the XPCOM codepath which expects a constructor
export const MarionetteFactory = function () {
  return Marionette;
};
PK
!<��8�84chrome/remote/content/components/RemoteAgent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  CDP: "chrome://remote/content/cdp/CDP.sys.mjs",
  Deferred: "chrome://remote/content/shared/Sync.sys.mjs",
  HttpServer: "chrome://remote/content/server/httpd.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  RecommendedPreferences:
    "chrome://remote/content/shared/RecommendedPreferences.sys.mjs",
  WebDriverBiDi: "chrome://remote/content/webdriver-bidi/WebDriverBiDi.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

ChromeUtils.defineLazyGetter(lazy, "activeProtocols", () => {
  const protocols = Services.prefs.getIntPref("remote.active-protocols");
  if (protocols < 1 || protocols > 3) {
    throw Error(`Invalid remote protocol identifier: ${protocols}`);
  }

  return protocols;
});

const WEBDRIVER_BIDI_ACTIVE = 0x1;
const CDP_ACTIVE = 0x2;

const DEFAULT_HOST = "localhost";
const DEFAULT_PORT = 9222;

const isRemote =
  Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
class RemoteAgentParentProcess {
  #allowHosts;
  #allowOrigins;
  #browserStartupFinished;
  #classID;
  #enabled;
  #host;
  #port;
  #server;

  #cdp;
  #webDriverBiDi;

  constructor() {
    this.#allowHosts = null;
    this.#allowOrigins = null;
    this.#browserStartupFinished = lazy.Deferred();
    this.#classID = Components.ID("{8f685a9d-8181-46d6-a71d-869289099c6d}");
    this.#enabled = false;

    // Configuration for httpd.js
    this.#host = DEFAULT_HOST;
    this.#port = DEFAULT_PORT;
    this.#server = null;

    // Supported protocols
    this.#cdp = null;
    this.#webDriverBiDi = null;

    Services.ppmm.addMessageListener("RemoteAgent:IsRunning", this);
  }

  get allowHosts() {
    if (this.#allowHosts !== null) {
      return this.#allowHosts;
    }

    if (this.#server) {
      // If the server is bound to a hostname, not an IP address, return it as
      // allowed host.
      const hostUri = Services.io.newURI(`https://${this.#host}`);
      if (!this.#isIPAddress(hostUri)) {
        return [RemoteAgent.host];
      }

      // Following Bug 1220810 localhost is guaranteed to resolve to a loopback
      // address (127.0.0.1 or ::1) unless network.proxy.allow_hijacking_localhost
      // is set to true, which should not be the case.
      const loopbackAddresses = ["127.0.0.1", "[::1]"];

      // If the server is bound to an IP address and this IP address is a localhost
      // loopback address, return localhost as allowed host.
      if (loopbackAddresses.includes(this.#host)) {
        return ["localhost"];
      }
    }

    // Otherwise return an empty array.
    return [];
  }

  get allowOrigins() {
    return this.#allowOrigins;
  }

  /**
   * A promise that resolves when the initial application window has been opened.
   *
   * @returns {Promise}
   *     Promise that resolves when the initial application window is open.
   */
  get browserStartupFinished() {
    return this.#browserStartupFinished.promise;
  }

  get cdp() {
    return this.#cdp;
  }

  get debuggerAddress() {
    if (!this.#server) {
      return "";
    }

    return `${this.#host}:${this.#port}`;
  }

  get enabled() {
    return this.#enabled;
  }

  get host() {
    return this.#host;
  }

  get port() {
    return this.#port;
  }

  get running() {
    return !!this.#server && !this.#server.isStopped();
  }

  get scheme() {
    return this.#server?.identity.primaryScheme;
  }

  get server() {
    return this.#server;
  }

  get webDriverBiDi() {
    return this.#webDriverBiDi;
  }

  /**
   * Handle the --remote-debugging-port command line argument.
   *
   * @param {nsICommandLine} cmdLine
   *     Instance of the command line interface.
   *
   * @returns {boolean}
   *     Return `true` if the command line argument has been found.
   */
  #handleRemoteDebuggingPortFlag(cmdLine) {
    let enabled = false;

    try {
      // Catch cases when the argument, and a port have been specified.
      const port = cmdLine.handleFlagWithParam("remote-debugging-port", false);
      if (port !== null) {
        enabled = true;

        // In case of an invalid port keep the default port
        const parsed = Number(port);
        if (!isNaN(parsed)) {
          this.#port = parsed;
        }
      }
    } catch (e) {
      // If no port has been given check for the existence of the argument.
      enabled = cmdLine.handleFlag("remote-debugging-port", false);
    }

    return enabled;
  }

  #handleAllowHostsFlag(cmdLine) {
    try {
      const hosts = cmdLine.handleFlagWithParam("remote-allow-hosts", false);
      return hosts.split(",");
    } catch (e) {
      return null;
    }
  }

  #handleAllowOriginsFlag(cmdLine) {
    try {
      const origins = cmdLine.handleFlagWithParam(
        "remote-allow-origins",
        false
      );
      return origins.split(",");
    } catch (e) {
      return null;
    }
  }

  /**
   * Check if the provided URI's host is an IP address.
   *
   * @param {nsIURI} uri
   *     The URI to check.
   * @returns {boolean}
   */
  #isIPAddress(uri) {
    try {
      // getBaseDomain throws an explicit error if the uri host is an IP address.
      Services.eTLD.getBaseDomain(uri);
    } catch (e) {
      return e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS;
    }
    return false;
  }

  async #listen(port) {
    if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
      throw Components.Exception(
        "May only be instantiated in parent process",
        Cr.NS_ERROR_LAUNCHED_CHILD_PROCESS
      );
    }

    if (this.running) {
      return;
    }

    // Try to resolve localhost to an IPv4  and / or IPv6 address so that the
    // server can be started on a given IP. Only fallback to use localhost if
    // the hostname cannot be resolved.
    //
    // Note: This doesn't force httpd.js to use the dual stack support.
    let isIPv4Host = false;
    try {
      const addresses = await this.#resolveHostname(DEFAULT_HOST);
      lazy.logger.trace(
        `Available local IP addresses: ${addresses.join(", ")}`
      );

      // Prefer IPv4 over IPv6 addresses.
      const addressesIPv4 = addresses.filter(value => !value.includes(":"));
      isIPv4Host = !!addressesIPv4.length;
      if (isIPv4Host) {
        this.#host = addressesIPv4[0];
      } else {
        this.#host = addresses.length ? addresses[0] : DEFAULT_HOST;
      }
    } catch (e) {
      this.#host = DEFAULT_HOST;

      lazy.logger.debug(
        `Failed to resolve hostname "localhost" to IP address: ${e.message}`
      );
    }

    // nsIServerSocket uses -1 for atomic port allocation
    if (port === 0) {
      port = -1;
    }

    try {
      // Bug 1783938: httpd.js refuses connections when started on a IPv4
      // address. As workaround start on localhost and add another identity
      // for that IP address.
      this.#server = new lazy.HttpServer();
      const host = isIPv4Host ? DEFAULT_HOST : this.#host;
      this.server._start(port, host);
      this.#port = this.server._port;

      if (isIPv4Host) {
        this.server.identity.add("http", this.#host, this.#port);
      }

      Services.obs.notifyObservers(null, "remote-listening", true);

      await Promise.all([this.#webDriverBiDi?.start(), this.#cdp?.start()]);
    } catch (e) {
      await this.#stop();
      lazy.logger.error(`Unable to start remote agent: ${e.message}`, e);
    }
  }

  /**
   * Resolves a hostname to one or more IP addresses.
   *
   * @param {string} hostname
   *
   * @returns {Array<string>}
   */
  #resolveHostname(hostname) {
    return new Promise((resolve, reject) => {
      let originalRequest;

      const onLookupCompleteListener = {
        onLookupComplete(request, record, status) {
          if (request === originalRequest) {
            if (!Components.isSuccessCode(status)) {
              reject({ message: ChromeUtils.getXPCOMErrorName(status) });
              return;
            }

            record.QueryInterface(Ci.nsIDNSAddrRecord);

            const addresses = [];
            while (record.hasMore()) {
              let addr = record.getNextAddrAsString();
              if (addr.includes(":") && !addr.startsWith("[")) {
                // Make sure that the IPv6 address is wrapped with brackets.
                addr = `[${addr}]`;
              }
              if (!addresses.includes(addr)) {
                // Sometimes there are duplicate records with the same IP.
                addresses.push(addr);
              }
            }

            resolve(addresses);
          }
        },
      };

      try {
        originalRequest = Services.dns.asyncResolve(
          hostname,
          Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
          Ci.nsIDNSService.RESOLVE_BYPASS_CACHE,
          null,
          onLookupCompleteListener,
          null, //Services.tm.mainThread,
          {} /* defaultOriginAttributes */
        );
      } catch (e) {
        reject({ message: e.message });
      }
    });
  }

  async #stop() {
    if (!this.running) {
      return;
    }

    // Stop each protocol before stopping the HTTP server.
    await this.#cdp?.stop();
    await this.#webDriverBiDi?.stop();

    try {
      await this.#server.stop();
      this.#server = null;
      Services.obs.notifyObservers(null, "remote-listening");
    } catch (e) {
      // this function must never fail
      lazy.logger.error("Unable to stop listener", e);
    }
  }

  handle(cmdLine) {
    // remote-debugging-port has to be consumed in nsICommandLineHandler:handle
    // to avoid issues on macos. See Marionette.sys.mjs::handle() for more details.
    // TODO: remove after Bug 1724251 is fixed.
    try {
      cmdLine.handleFlagWithParam("remote-debugging-port", false);
    } catch (e) {
      cmdLine.handleFlag("remote-debugging-port", false);
    }
  }

  async observe(subject, topic) {
    if (this.#enabled) {
      lazy.logger.trace(`Received observer notification ${topic}`);
    }

    switch (topic) {
      case "profile-after-change":
        Services.obs.addObserver(this, "command-line-startup");
        break;

      case "command-line-startup":
        Services.obs.removeObserver(this, topic);

        this.#enabled = this.#handleRemoteDebuggingPortFlag(subject);

        if (this.#enabled) {
          this.#allowHosts = this.#handleAllowHostsFlag(subject);
          this.#allowOrigins = this.#handleAllowOriginsFlag(subject);

          Services.obs.addObserver(this, "final-ui-startup");
          Services.obs.addObserver(this, "browser-idle-startup-tasks-finished");
          Services.obs.addObserver(this, "mail-idle-startup-tasks-finished");
          Services.obs.addObserver(this, "quit-application");

          // Apply the common set of preferences for all supported protocols
          lazy.RecommendedPreferences.applyPreferences();

          // With Bug 1717899 we will extend the lifetime of the Remote Agent to
          // the whole Firefox session, which will be identical to Marionette. For
          // now prevent logging if the component is not enabled during startup.
          if (
            (lazy.activeProtocols & WEBDRIVER_BIDI_ACTIVE) ===
            WEBDRIVER_BIDI_ACTIVE
          ) {
            this.#webDriverBiDi = new lazy.WebDriverBiDi(this);
            if (this.#enabled) {
              lazy.logger.debug("WebDriver BiDi enabled");
            }
          }

          if ((lazy.activeProtocols & CDP_ACTIVE) === CDP_ACTIVE) {
            this.#cdp = new lazy.CDP(this);
            if (this.#enabled) {
              lazy.logger.debug("CDP enabled");
            }
          }
        }
        break;

      case "final-ui-startup":
        Services.obs.removeObserver(this, topic);

        try {
          await this.#listen(this.#port);
        } catch (e) {
          throw Error(`Unable to start remote agent: ${e}`);
        }

        break;

      // Used to wait until the initial application window has been opened.
      case "browser-idle-startup-tasks-finished":
      case "mail-idle-startup-tasks-finished":
        Services.obs.removeObserver(
          this,
          "browser-idle-startup-tasks-finished"
        );
        Services.obs.removeObserver(this, "mail-idle-startup-tasks-finished");
        this.#browserStartupFinished.resolve();
        break;

      // Listen for application shutdown to also shutdown the Remote Agent
      // and a possible running instance of httpd.js.
      case "quit-application":
        Services.obs.removeObserver(this, topic);
        this.#stop();
        break;
    }
  }

  receiveMessage({ name }) {
    switch (name) {
      case "RemoteAgent:IsRunning":
        return this.running;

      default:
        lazy.logger.warn("Unknown IPC message to parent process: " + name);
        return null;
    }
  }

  // XPCOM

  get classID() {
    return this.#classID;
  }

  get helpInfo() {
    return `  --remote-debugging-port [<port>] Start the Firefox Remote Agent,
                     which is a low-level remote debugging interface used for WebDriver
                     BiDi and CDP. Defaults to port 9222.
  --remote-allow-hosts <hosts> Values of the Host header to allow for incoming requests.
                     Please read security guidelines at https://firefox-source-docs.mozilla.org/remote/Security.html
  --remote-allow-origins <origins> Values of the Origin header to allow for incoming requests.
                     Please read security guidelines at https://firefox-source-docs.mozilla.org/remote/Security.html\n`;
  }

  get QueryInterface() {
    return ChromeUtils.generateQI([
      "nsICommandLineHandler",
      "nsIObserver",
      "nsIRemoteAgent",
    ]);
  }
}

class RemoteAgentContentProcess {
  get running() {
    let reply = Services.cpmm.sendSyncMessage("RemoteAgent:IsRunning");
    if (!reply.length) {
      lazy.logger.warn("No reply from parent process");
      return false;
    }
    return reply[0];
  }

  get QueryInterface() {
    return ChromeUtils.generateQI(["nsIRemoteAgent"]);
  }
}

export var RemoteAgent;
if (isRemote) {
  RemoteAgent = new RemoteAgentContentProcess();
} else {
  RemoteAgent = new RemoteAgentParentProcess();
}

// This is used by the XPCOM codepath which expects a constructor
export var RemoteAgentFactory = function () {
  return RemoteAgent;
};
PK
!<eGT4T4,chrome/remote/content/external/EventUtils.js/* eslint-disable no-nested-ternary */
/**
 * EventUtils provides some utility methods for creating and sending DOM events.
 *
 *  When adding methods to this file, please add a performance test for it.
 */

// Certain functions assume this is loaded into browser window scope.
// This is modifiable because certain chrome tests create their own gBrowser.
/* global gBrowser:true */

// This file is used both in privileged and unprivileged contexts, so we have to
// be careful about our access to Components.interfaces. We also want to avoid
// naming collisions with anything that might be defined in the scope that imports
// this script.
//
// Even if the real |Components| doesn't exist, we might shim in a simple JS
// placebo for compat. An easy way to differentiate this from the real thing
// is whether the property is read-only or not.  The real |Components| property
// is read-only.
/* global _EU_Ci, _EU_Cc, _EU_Cu, _EU_ChromeUtils, _EU_OS */
window.__defineGetter__("_EU_Ci", function () {
  var c = Object.getOwnPropertyDescriptor(window, "Components");
  return c && c.value && !c.writable ? Ci : SpecialPowers.Ci;
});

window.__defineGetter__("_EU_Cc", function () {
  var c = Object.getOwnPropertyDescriptor(window, "Components");
  return c && c.value && !c.writable ? Cc : SpecialPowers.Cc;
});

window.__defineGetter__("_EU_Cu", function () {
  var c = Object.getOwnPropertyDescriptor(window, "Components");
  return c && c.value && !c.writable ? Cu : SpecialPowers.Cu;
});

window.__defineGetter__("_EU_ChromeUtils", function () {
  var c = Object.getOwnPropertyDescriptor(window, "ChromeUtils");
  return c && c.value && !c.writable ? ChromeUtils : SpecialPowers.ChromeUtils;
});

window.__defineGetter__("_EU_OS", function () {
  delete this._EU_OS;
  try {
    this._EU_OS = _EU_ChromeUtils.importESModule(
      "resource://gre/modules/AppConstants.sys.mjs"
    ).platform;
  } catch (ex) {
    this._EU_OS = null;
  }
  return this._EU_OS;
});

function _EU_isMac(aWindow = window) {
  if (window._EU_OS) {
    return window._EU_OS == "macosx";
  }
  if (aWindow) {
    try {
      return aWindow.navigator.platform.indexOf("Mac") > -1;
    } catch (ex) {}
  }
  return navigator.platform.indexOf("Mac") > -1;
}

function _EU_isWin(aWindow = window) {
  if (window._EU_OS) {
    return window._EU_OS == "win";
  }
  if (aWindow) {
    try {
      return aWindow.navigator.platform.indexOf("Win") > -1;
    } catch (ex) {}
  }
  return navigator.platform.indexOf("Win") > -1;
}

function _EU_isLinux(aWindow = window) {
  if (window._EU_OS) {
    return window._EU_OS == "linux";
  }
  if (aWindow) {
    try {
      return aWindow.navigator.platform.startsWith("Linux");
    } catch (ex) {}
  }
  return navigator.platform.startsWith("Linux");
}

function _EU_isAndroid(aWindow = window) {
  if (window._EU_OS) {
    return window._EU_OS == "android";
  }
  if (aWindow) {
    try {
      return aWindow.navigator.userAgent.includes("Android");
    } catch (ex) {}
  }
  return navigator.userAgent.includes("Android");
}

function _EU_maybeWrap(o) {
  // We're used in some contexts where there is no SpecialPowers and also in
  // some where it exists but has no wrap() method.  And this is somewhat
  // independent of whether window.Components is a thing...
  var haveWrap = false;
  try {
    haveWrap = SpecialPowers.wrap != undefined;
  } catch (e) {
    // Just leave it false.
  }
  if (!haveWrap) {
    // Not much we can do here.
    return o;
  }
  var c = Object.getOwnPropertyDescriptor(window, "Components");
  return c && c.value && !c.writable ? o : SpecialPowers.wrap(o);
}

function _EU_maybeUnwrap(o) {
  var haveWrap = false;
  try {
    haveWrap = SpecialPowers.unwrap != undefined;
  } catch (e) {
    // Just leave it false.
  }
  if (!haveWrap) {
    // Not much we can do here.
    return o;
  }
  var c = Object.getOwnPropertyDescriptor(window, "Components");
  return c && c.value && !c.writable ? o : SpecialPowers.unwrap(o);
}

function _EU_getPlatform() {
  if (_EU_isWin()) {
    return "windows";
  }
  if (_EU_isMac()) {
    return "mac";
  }
  if (_EU_isAndroid()) {
    return "android";
  }
  if (_EU_isLinux()) {
    return "linux";
  }
  return "unknown";
}

/**
 * promiseElementReadyForUserInput() dispatches mousemove events to aElement
 * and waits one of them for a while.  Then, returns "resolved" state when it's
 * successfully received.  Otherwise, if it couldn't receive mousemove event on
 * it, this throws an exception.  So, aElement must be an element which is
 * assumed non-collapsed visible element in the window.
 *
 * This is useful if you need to synthesize mouse events via the main process
 * but your test cannot check whether the element is now in APZ to deliver
 * a user input event.
 */
async function promiseElementReadyForUserInput(
  aElement,
  aWindow = window,
  aLogFunc = null
) {
  if (typeof aElement == "string") {
    aElement = aWindow.document.getElementById(aElement);
  }

  function waitForMouseMoveForHittest() {
    return new Promise(resolve => {
      let timeout;
      const onHit = () => {
        if (aLogFunc) {
          aLogFunc("mousemove received");
        }
        aWindow.clearInterval(timeout);
        resolve(true);
      };
      aElement.addEventListener("mousemove", onHit, {
        capture: true,
        once: true,
      });
      timeout = aWindow.setInterval(() => {
        if (aLogFunc) {
          aLogFunc("mousemove not received in this 300ms");
        }
        aElement.removeEventListener("mousemove", onHit, {
          capture: true,
        });
        resolve(false);
      }, 300);
      synthesizeMouseAtCenter(aElement, { type: "mousemove" }, aWindow);
    });
  }
  for (let i = 0; i < 20; i++) {
    if (await waitForMouseMoveForHittest()) {
      return Promise.resolve();
    }
  }
  throw new Error("The element or the window did not become interactive");
}

function getElement(id) {
  return typeof id == "string" ? document.getElementById(id) : id;
}

this.$ = this.getElement;

function computeButton(aEvent) {
  if (typeof aEvent.button != "undefined") {
    return aEvent.button;
  }
  return aEvent.type == "contextmenu" ? 2 : 0;
}

function computeButtons(aEvent, utils) {
  if (typeof aEvent.buttons != "undefined") {
    return aEvent.buttons;
  }

  if (typeof aEvent.button != "undefined") {
    return utils.MOUSE_BUTTONS_NOT_SPECIFIED;
  }

  if (typeof aEvent.type != "undefined" && aEvent.type != "mousedown") {
    return utils.MOUSE_BUTTONS_NO_BUTTON;
  }

  return utils.MOUSE_BUTTONS_NOT_SPECIFIED;
}

/**
 * Send a mouse event to the node aTarget (aTarget can be an id, or an
 * actual node) . The "event" passed in to aEvent is just a JavaScript
 * object with the properties set that the real mouse event object should
 * have. This includes the type of the mouse event. Pretty much all those
 * properties are optional.
 * E.g. to send an click event to the node with id 'node' you might do this:
 *
 * ``sendMouseEvent({type:'click'}, 'node');``
 */
function sendMouseEvent(aEvent, aTarget, aWindow) {
  if (
    ![
      "click",
      "contextmenu",
      "dblclick",
      "mousedown",
      "mouseup",
      "mouseover",
      "mouseout",
    ].includes(aEvent.type)
  ) {
    throw new Error(
      "sendMouseEvent doesn't know about event type '" + aEvent.type + "'"
    );
  }

  if (!aWindow) {
    aWindow = window;
  }

  if (typeof aTarget == "string") {
    aTarget = aWindow.document.getElementById(aTarget);
  }

  let dict = {
    bubbles: true,
    cancelable: true,
    view: aWindow,
    detail:
      aEvent.detail ||
      // eslint-disable-next-line no-nested-ternary
      (aEvent.type == "click" ||
      aEvent.type == "mousedown" ||
      aEvent.type == "mouseup"
        ? 1
        : aEvent.type == "dblclick"
        ? 2
        : 0),
    screenX: aEvent.screenX || 0,
    screenY: aEvent.screenY || 0,
    clientX: aEvent.clientX || 0,
    clientY: aEvent.clientY || 0,
    ctrlKey: aEvent.ctrlKey || false,
    altKey: aEvent.altKey || false,
    shiftKey: aEvent.shiftKey || false,
    metaKey: aEvent.metaKey || false,
    button: computeButton(aEvent),
    // FIXME: Set buttons
    relatedTarget: aEvent.relatedTarget || null,
  };

  let event =
    aEvent.type == "click" || aEvent.type == "contextmenu"
      ? new aWindow.PointerEvent(aEvent.type, dict)
      : new aWindow.MouseEvent(aEvent.type, dict);

  // If documentURIObject exists or `window` is a stub object, we're in
  // a chrome scope, so don't bother trying to go through SpecialPowers.
  if (!window.document || window.document.documentURIObject) {
    return aTarget.dispatchEvent(event);
  }
  return SpecialPowers.dispatchEvent(aWindow, aTarget, event);
}

function isHidden(aElement) {
  var box = aElement.getBoundingClientRect();
  return box.width == 0 && box.height == 0;
}

/**
 * Send a drag event to the node aTarget (aTarget can be an id, or an
 * actual node) . The "event" passed in to aEvent is just a JavaScript
 * object with the properties set that the real drag event object should
 * have. This includes the type of the drag event.
 */
function sendDragEvent(aEvent, aTarget, aWindow = window) {
  if (
    ![
      "drag",
      "dragstart",
      "dragend",
      "dragover",
      "dragenter",
      "dragleave",
      "drop",
    ].includes(aEvent.type)
  ) {
    throw new Error(
      "sendDragEvent doesn't know about event type '" + aEvent.type + "'"
    );
  }

  if (typeof aTarget == "string") {
    aTarget = aWindow.document.getElementById(aTarget);
  }

  /*
   * Drag event cannot be performed if the element is hidden, except 'dragend'
   * event where the element can becomes hidden after start dragging.
   */
  if (aEvent.type != "dragend" && isHidden(aTarget)) {
    var targetName = aTarget.nodeName;
    if ("id" in aTarget && aTarget.id) {
      targetName += "#" + aTarget.id;
    }
    throw new Error(`${aEvent.type} event target ${targetName} is hidden`);
  }

  var event = aWindow.document.createEvent("DragEvent");

  var typeArg = aEvent.type;
  var canBubbleArg = true;
  var cancelableArg = true;
  var viewArg = aWindow;
  var detailArg = aEvent.detail || 0;
  var screenXArg = aEvent.screenX || 0;
  var screenYArg = aEvent.screenY || 0;
  var clientXArg = aEvent.clientX || 0;
  var clientYArg = aEvent.clientY || 0;
  var ctrlKeyArg = aEvent.ctrlKey || false;
  var altKeyArg = aEvent.altKey || false;
  var shiftKeyArg = aEvent.shiftKey || false;
  var metaKeyArg = aEvent.metaKey || false;
  var buttonArg = computeButton(aEvent);
  var relatedTargetArg = aEvent.relatedTarget || null;
  var dataTransfer = aEvent.dataTransfer || null;

  event.initDragEvent(
    typeArg,
    canBubbleArg,
    cancelableArg,
    viewArg,
    detailArg,
    Math.round(screenXArg),
    Math.round(screenYArg),
    Math.round(clientXArg),
    Math.round(clientYArg),
    ctrlKeyArg,
    altKeyArg,
    shiftKeyArg,
    metaKeyArg,
    buttonArg,
    relatedTargetArg,
    dataTransfer
  );

  if (aEvent._domDispatchOnly) {
    return aTarget.dispatchEvent(event);
  }

  var utils = _getDOMWindowUtils(aWindow);
  return utils.dispatchDOMEventViaPresShellForTesting(aTarget, event);
}

/**
 * Send the char aChar to the focused element.  This method handles casing of
 * chars (sends the right charcode, and sends a shift key for uppercase chars).
 * No other modifiers are handled at this point.
 *
 * For now this method only works for ASCII characters and emulates the shift
 * key state on US keyboard layout.
 */
function sendChar(aChar, aWindow) {
  var hasShift;
  // Emulate US keyboard layout for the shiftKey state.
  switch (aChar) {
    case "!":
    case "@":
    case "#":
    case "$":
    case "%":
    case "^":
    case "&":
    case "*":
    case "(":
    case ")":
    case "_":
    case "+":
    case "{":
    case "}":
    case ":":
    case '"':
    case "|":
    case "<":
    case ">":
    case "?":
      hasShift = true;
      break;
    default:
      hasShift =
        aChar.toLowerCase() != aChar.toUpperCase() &&
        aChar == aChar.toUpperCase();
      break;
  }
  synthesizeKey(aChar, { shiftKey: hasShift }, aWindow);
}

/**
 * Send the string aStr to the focused element.
 *
 * For now this method only works for ASCII characters and emulates the shift
 * key state on US keyboard layout.
 */
function sendString(aStr, aWindow) {
  for (let i = 0; i < aStr.length; ++i) {
    // Do not split a surrogate pair to call synthesizeKey.  Dispatching two
    // sets of keydown and keyup caused by two calls of synthesizeKey is not
    // good behavior.  It could happen due to a bug, but a surrogate pair should
    // be introduced with one key press operation.  Therefore, calling it with
    // a surrogate pair is the right thing.
    // Note that TextEventDispatcher will consider whether a surrogate pair
    // should cause one or two keypress events automatically.  Therefore, we
    // don't need to check the related prefs here.
    if (
      (aStr.charCodeAt(i) & 0xfc00) == 0xd800 &&
      i + 1 < aStr.length &&
      (aStr.charCodeAt(i + 1) & 0xfc00) == 0xdc00
    ) {
      sendChar(aStr.substring(i, i + 2), aWindow);
      i++;
    } else {
      sendChar(aStr.charAt(i), aWindow);
    }
  }
}

/**
 * Send the non-character key aKey to the focused node.
 * The name of the key should be the part that comes after ``DOM_VK_`` in the
 * KeyEvent constant name for this key.
 * No modifiers are handled at this point.
 */
function sendKey(aKey, aWindow) {
  var keyName = "VK_" + aKey.toUpperCase();
  synthesizeKey(keyName, { shiftKey: false }, aWindow);
}

/**
 * Parse the key modifier flags from aEvent. Used to share code between
 * synthesizeMouse and synthesizeKey.
 */
function _parseModifiers(aEvent, aWindow = window) {
  var nsIDOMWindowUtils = _EU_Ci.nsIDOMWindowUtils;
  var mval = 0;
  if (aEvent.shiftKey) {
    mval |= nsIDOMWindowUtils.MODIFIER_SHIFT;
  }
  if (aEvent.ctrlKey) {
    mval |= nsIDOMWindowUtils.MODIFIER_CONTROL;
  }
  if (aEvent.altKey) {
    mval |= nsIDOMWindowUtils.MODIFIER_ALT;
  }
  if (aEvent.metaKey) {
    mval |= nsIDOMWindowUtils.MODIFIER_META;
  }
  if (aEvent.accelKey) {
    mval |= _EU_isMac(aWindow)
      ? nsIDOMWindowUtils.MODIFIER_META
      : nsIDOMWindowUtils.MODIFIER_CONTROL;
  }
  if (aEvent.altGrKey) {
    mval |= nsIDOMWindowUtils.MODIFIER_ALTGRAPH;
  }
  if (aEvent.capsLockKey) {
    mval |= nsIDOMWindowUtils.MODIFIER_CAPSLOCK;
  }
  if (aEvent.fnKey) {
    mval |= nsIDOMWindowUtils.MODIFIER_FN;
  }
  if (aEvent.fnLockKey) {
    mval |= nsIDOMWindowUtils.MODIFIER_FNLOCK;
  }
  if (aEvent.numLockKey) {
    mval |= nsIDOMWindowUtils.MODIFIER_NUMLOCK;
  }
  if (aEvent.scrollLockKey) {
    mval |= nsIDOMWindowUtils.MODIFIER_SCROLLLOCK;
  }
  if (aEvent.symbolKey) {
    mval |= nsIDOMWindowUtils.MODIFIER_SYMBOL;
  }
  if (aEvent.symbolLockKey) {
    mval |= nsIDOMWindowUtils.MODIFIER_SYMBOLLOCK;
  }

  return mval;
}

/**
 * Synthesize a mouse event on a target. The actual client point is determined
 * by taking the aTarget's client box and offseting it by aOffsetX and
 * aOffsetY. This allows mouse clicks to be simulated by calling this method.
 *
 * aEvent is an object which may contain the properties:
 *   `shiftKey`, `ctrlKey`, `altKey`, `metaKey`, `accessKey`, `clickCount`,
 *   `button`, `type`.
 *   For valid `type`s see nsIDOMWindowUtils' `sendMouseEvent`.
 *
 * If the type is specified, an mouse event of that type is fired. Otherwise,
 * a mousedown followed by a mouseup is performed.
 *
 * aWindow is optional, and defaults to the current window object.
 *
 * Returns whether the event had preventDefault() called on it.
 */
function synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow) {
  var rect = aTarget.getBoundingClientRect();
  return synthesizeMouseAtPoint(
    rect.left + aOffsetX,
    rect.top + aOffsetY,
    aEvent,
    aWindow
  );
}

/**
 * Synthesize one or more touches on aTarget. aTarget can be either Element
 * or Array of Elements.  aOffsetX, aOffsetY, aEvent.id, aEvent.rx, aEvent.ry,
 * aEvent.angle, aEvent.force, aEvent.tiltX, aEvent.tiltY and aEvent.twist can
 * be either Number or Array of Numbers (can be mixed).  If you specify array
 * to synthesize a multi-touch, you need to specify same length arrays.  If
 * you don't specify array to them, same values (or computed default values for
 * aEvent.id) are used for all touches.
 *
 * @param {Element | Element[]} aTarget The target element which you specify
 * relative offset from its top-left.
 * @param {Number | Number[]} aOffsetX The relative offset from left of aTarget.
 * @param {Number | Number[]} aOffsetY The relative offset from top of aTarget.
 * @param {Object} aEvent
 * type: The touch event type.  If undefined, "touchstart" and "touchend" will
 * be synthesized at same point.
 *
 * id: The touch id.  If you don't specify this, default touch id will be used
 * for first touch and further touch ids are the values incremented from the
 * first id.
 *
 * rx, ry: The radii of the touch.
 *
 * angle: The angle in degree.
 *
 * force: The force of the touch.  If the type is "touchend", this should be 0.
 * If unspecified, this is default to 0 for "touchend"  or 1 for the others.
 *
 * tiltX, tiltY: The tilt of the touch.
 *
 * twist: The twist of the touch.
 * @param {Window} aWindow Default to `window`.
 * @returns true if and only if aEvent.type is specified and default of the
 * event is prevented.
 */
function synthesizeTouch(
  aTarget,
  aOffsetX,
  aOffsetY,
  aEvent = {},
  aWindow = window
) {
  let rectX, rectY;
  if (Array.isArray(aTarget)) {
    let lastTarget, lastTargetRect;
    aTarget.forEach(target => {
      const rect =
        target == lastTarget ? lastTargetRect : target.getBoundingClientRect();
      rectX.push(rect.left);
      rectY.push(rect.top);
      lastTarget = target;
      lastTargetRect = rect;
    });
  } else {
    const rect = aTarget.getBoundingClientRect();
    rectX = [rect.left];
    rectY = [rect.top];
  }
  const offsetX = (() => {
    if (Array.isArray(aOffsetX)) {
      let ret = [];
      aOffsetX.forEach((value, index) => {
        ret.push(value + rectX[Math.min(index, rectX.length - 1)]);
      });
      return ret;
    }
    return aOffsetX + rectX[0];
  })();
  const offsetY = (() => {
    if (Array.isArray(aOffsetY)) {
      let ret = [];
      aOffsetY.forEach((value, index) => {
        ret.push(value + rectY[Math.min(index, rectY.length - 1)]);
      });
      return ret;
    }
    return aOffsetY + rectY[0];
  })();
  return synthesizeTouchAtPoint(offsetX, offsetY, aEvent, aWindow);
}

/**
 * Return the drag service.  Note that if we're in the headless mode, this
 * may return null because the service may be never instantiated (e.g., on
 * Linux).
 */
function getDragService() {
  try {
    return _EU_Cc["@mozilla.org/widget/dragservice;1"].getService(
      _EU_Ci.nsIDragService
    );
  } catch (e) {
    // If we're in the headless mode, the drag service may be never
    // instantiated.  In this case, an exception is thrown.  Let's ignore
    // any exceptions since without the drag service, nobody can create a
    // drag session.
    return null;
  }
}

/**
 * End drag session if there is.
 *
 * TODO: This should synthesize "drop" if necessary.
 *
 * @param left          X offset in the viewport
 * @param top           Y offset in the viewport
 * @param aEvent        The event data, the modifiers are applied to the
 *                      "dragend" event.
 * @param aWindow       The window.
 * @return              true if handled.  In this case, the caller should not
 *                      synthesize DOM events basically.
 */
function _maybeEndDragSession(left, top, aEvent, aWindow) {
  let utils = _getDOMWindowUtils(aWindow);
  const dragSession = utils.dragSession;
  if (!dragSession) {
    return false;
  }
  // FIXME: If dragSession.dragAction is not
  // nsIDragService.DRAGDROP_ACTION_NONE nor aEvent.type is not `keydown`, we
  // need to synthesize a "drop" event or call setDragEndPointForTests here to
  // set proper left/top to `dragend` event.
  try {
    dragSession.endDragSession(false, _parseModifiers(aEvent, aWindow));
  } catch (e) {}
  return true;
}

function _maybeSynthesizeDragOver(left, top, aEvent, aWindow) {
  let utils = _getDOMWindowUtils(aWindow);
  const dragSession = utils.dragSession;
  if (!dragSession) {
    return false;
  }
  const target = aWindow.document.elementFromPoint(left, top);
  if (target) {
    sendDragEvent(
      createDragEventObject(
        "dragover",
        target,
        aWindow,
        dragSession.dataTransfer,
        {
          accelKey: aEvent.accelKey,
          altKey: aEvent.altKey,
          altGrKey: aEvent.altGrKey,
          ctrlKey: aEvent.ctrlKey,
          metaKey: aEvent.metaKey,
          shiftKey: aEvent.shiftKey,
          capsLockKey: aEvent.capsLockKey,
          fnKey: aEvent.fnKey,
          fnLockKey: aEvent.fnLockKey,
          numLockKey: aEvent.numLockKey,
          scrollLockKey: aEvent.scrollLockKey,
          symbolKey: aEvent.symbolKey,
          symbolLockKey: aEvent.symbolLockKey,
        }
      ),
      target,
      aWindow
    );
  }
  return true;
}

/*
 * Synthesize a mouse event at a particular point in aWindow.
 *
 * aEvent is an object which may contain the properties:
 *   `shiftKey`, `ctrlKey`, `altKey`, `metaKey`, `accessKey`, `clickCount`,
 *   `button`, `type`.
 *   For valid `type`s see nsIDOMWindowUtils' `sendMouseEvent`.
 *
 * If the type is specified, an mouse event of that type is fired. Otherwise,
 * a mousedown followed by a mouseup is performed.
 *
 * aWindow is optional, and defaults to the current window object.
 */
function synthesizeMouseAtPoint(left, top, aEvent, aWindow = window) {
  if (aEvent.allowToHandleDragDrop) {
    if (aEvent.type == "mouseup" || !aEvent.type) {
      if (_maybeEndDragSession(left, top, aEvent, aWindow)) {
        return false;
      }
    } else if (aEvent.type == "mousemove") {
      if (_maybeSynthesizeDragOver(left, top, aEvent, aWindow)) {
        return false;
      }
    }
  }

  var utils = _getDOMWindowUtils(aWindow);
  var defaultPrevented = false;

  if (utils) {
    var button = computeButton(aEvent);
    var clickCount = aEvent.clickCount || 1;
    var modifiers = _parseModifiers(aEvent, aWindow);
    var pressure = "pressure" in aEvent ? aEvent.pressure : 0;

    // aWindow might be cross-origin from us.
    var MouseEvent = _EU_maybeWrap(aWindow).MouseEvent;

    // Default source to mouse.
    var inputSource =
      "inputSource" in aEvent
        ? aEvent.inputSource
        : MouseEvent.MOZ_SOURCE_MOUSE;
    // Compute a pointerId if needed.
    var id;
    if ("id" in aEvent) {
      id = aEvent.id;
    } else {
      var isFromPen = inputSource === MouseEvent.MOZ_SOURCE_PEN;
      id = isFromPen
        ? utils.DEFAULT_PEN_POINTER_ID
        : utils.DEFAULT_MOUSE_POINTER_ID;
    }

    var isDOMEventSynthesized =
      "isSynthesized" in aEvent ? aEvent.isSynthesized : true;
    var isWidgetEventSynthesized =
      "isWidgetEventSynthesized" in aEvent
        ? aEvent.isWidgetEventSynthesized
        : false;
    if ("type" in aEvent && aEvent.type) {
      defaultPrevented = utils.sendMouseEvent(
        aEvent.type,
        left,
        top,
        button,
        clickCount,
        modifiers,
        false,
        pressure,
        inputSource,
        isDOMEventSynthesized,
        isWidgetEventSynthesized,
        computeButtons(aEvent, utils),
        id
      );
    } else {
      utils.sendMouseEvent(
        "mousedown",
        left,
        top,
        button,
        clickCount,
        modifiers,
        false,
        pressure,
        inputSource,
        isDOMEventSynthesized,
        isWidgetEventSynthesized,
        computeButtons(Object.assign({ type: "mousedown" }, aEvent), utils),
        id
      );
      utils.sendMouseEvent(
        "mouseup",
        left,
        top,
        button,
        clickCount,
        modifiers,
        false,
        pressure,
        inputSource,
        isDOMEventSynthesized,
        isWidgetEventSynthesized,
        computeButtons(Object.assign({ type: "mouseup" }, aEvent), utils),
        id
      );
    }
  }

  return defaultPrevented;
}

/**
 * Synthesize one or more touches at the points. aLeft, aTop, aEvent.id,
 * aEvent.rx, aEvent.ry, aEvent.angle, aEvent.force, aEvent.tiltX, aEvent.tiltY
 * and aEvent.twist can be either Number or Array of Numbers (can be mixed).
 * If you specify array to synthesize a multi-touch, you need to specify same
 * length arrays.  If you don't specify array to them, same values are used for
 * all touches.
 *
 * @param {Element | Element[]} aTarget The target element which you specify
 * relative offset from its top-left.
 * @param {Number | Number[]} aOffsetX The relative offset from left of aTarget.
 * @param {Number | Number[]} aOffsetY The relative offset from top of aTarget.
 * @param {Object} aEvent
 * type: The touch event type.  If undefined, "touchstart" and "touchend" will
 * be synthesized at same point.
 *
 * id: The touch id.  If you don't specify this, default touch id will be used
 * for first touch and further touch ids are the values incremented from the
 * first id.
 *
 * rx, ry: The radii of the touch.
 *
 * angle: The angle in degree.
 *
 * force: The force of the touch.  If the type is "touchend", this should be 0.
 * If unspecified, this is default to 0 for "touchend"  or 1 for the others.
 *
 * tiltX, tiltY: The tilt of the touch.
 *
 * twist: The twist of the touch.
 * @param {Window} aWindow Default to `window`.
 * @returns true if and only if aEvent.type is specified and default of the
 * event is prevented.
 */
function synthesizeTouchAtPoint(aLeft, aTop, aEvent = {}, aWindow = window) {
  let utils = _getDOMWindowUtils(aWindow);
  if (!utils) {
    return false;
  }

  if (
    Array.isArray(aLeft) &&
    Array.isArray(aTop) &&
    aLeft.length != aTop.length
  ) {
    throw new Error(`aLeft and aTop should be same length array`);
  }

  const arrayLength = Array.isArray(aLeft)
    ? aLeft.length
    : Array.isArray(aTop)
    ? aTop.length
    : 1;

  function throwExceptionIfDifferentLengthArray(aArray, aName) {
    if (Array.isArray(aArray) && arrayLength !== aArray.length) {
      throw new Error(`${aName} is different length array`);
    }
  }
  const leftArray = (() => {
    if (Array.isArray(aLeft)) {
      return aLeft;
    }
    return new Array(arrayLength).fill(aLeft);
  })();
  const topArray = (() => {
    if (Array.isArray(aTop)) {
      throwExceptionIfDifferentLengthArray(aTop, "aTop");
      return aTop;
    }
    return new Array(arrayLength).fill(aTop);
  })();
  const idArray = (() => {
    if ("id" in aEvent && Array.isArray(aEvent.id)) {
      throwExceptionIfDifferentLengthArray(aEvent.id, "aEvent.id");
      return aEvent.id;
    }
    let id = aEvent.id || utils.DEFAULT_TOUCH_POINTER_ID;
    let ret = [];
    for (let i = 0; i < arrayLength; i++) {
      ret.push(id++);
    }
    return ret;
  })();
  function getSameLengthArrayOfEventProperty(aProperty, aDefaultValue) {
    if (aProperty in aEvent && Array.isArray(aEvent[aProperty])) {
      throwExceptionIfDifferentLengthArray(
        aEvent.rx,
        arrayLength,
        `aEvent.${aProperty}`
      );
      return aEvent[aProperty];
    }
    return new Array(arrayLength).fill(aEvent[aProperty] || aDefaultValue);
  }
  const rxArray = getSameLengthArrayOfEventProperty("rx", 1);
  const ryArray = getSameLengthArrayOfEventProperty("ry", 1);
  const angleArray = getSameLengthArrayOfEventProperty("angle", 0);
  const forceArray = getSameLengthArrayOfEventProperty(
    "force",
    aEvent.type === "touchend" ? 0 : 1
  );
  const tiltXArray = getSameLengthArrayOfEventProperty("tiltX", 0);
  const tiltYArray = getSameLengthArrayOfEventProperty("tiltY", 0);
  const twistArray = getSameLengthArrayOfEventProperty("twist", 0);

  const modifiers = _parseModifiers(aEvent, aWindow);

  const args = [
    idArray,
    leftArray,
    topArray,
    rxArray,
    ryArray,
    angleArray,
    forceArray,
    tiltXArray,
    tiltYArray,
    twistArray,
    modifiers,
  ];

  const sender =
    aEvent.mozInputSource === "pen" ? "sendTouchEventAsPen" : "sendTouchEvent";

  if ("type" in aEvent && aEvent.type) {
    return utils[sender](aEvent.type, ...args);
  }

  utils[sender]("touchstart", ...args);
  utils[sender]("touchend", ...args);
  return false;
}

// Call synthesizeMouse with coordinates at the center of aTarget.
function synthesizeMouseAtCenter(aTarget, aEvent, aWindow) {
  var rect = aTarget.getBoundingClientRect();
  return synthesizeMouse(
    aTarget,
    rect.width / 2,
    rect.height / 2,
    aEvent,
    aWindow
  );
}
function synthesizeTouchAtCenter(aTarget, aEvent = {}, aWindow = window) {
  var rect = aTarget.getBoundingClientRect();
  synthesizeTouchAtPoint(
    rect.left + rect.width / 2,
    rect.top + rect.height / 2,
    aEvent,
    aWindow
  );
}

/**
 * Synthesize a wheel event without flush layout at a particular point in
 * aWindow.
 *
 * aEvent is an object which may contain the properties:
 *   shiftKey, ctrlKey, altKey, metaKey, accessKey, deltaX, deltaY, deltaZ,
 *   deltaMode, lineOrPageDeltaX, lineOrPageDeltaY, isMomentum,
 *   isNoLineOrPageDelta, isCustomizedByPrefs, expectedOverflowDeltaX,
 *   expectedOverflowDeltaY
 *
 * deltaMode must be defined, others are ok even if undefined.
 *
 * expectedOverflowDeltaX and expectedOverflowDeltaY take integer value.  The
 * value is just checked as 0 or positive or negative.
 *
 * aWindow is optional, and defaults to the current window object.
 */
function synthesizeWheelAtPoint(aLeft, aTop, aEvent, aWindow = window) {
  var utils = _getDOMWindowUtils(aWindow);
  if (!utils) {
    return;
  }

  var modifiers = _parseModifiers(aEvent, aWindow);
  var options = 0;
  if (aEvent.isNoLineOrPageDelta) {
    options |= utils.WHEEL_EVENT_CAUSED_BY_NO_LINE_OR_PAGE_DELTA_DEVICE;
  }
  if (aEvent.isMomentum) {
    options |= utils.WHEEL_EVENT_CAUSED_BY_MOMENTUM;
  }
  if (aEvent.isCustomizedByPrefs) {
    options |= utils.WHEEL_EVENT_CUSTOMIZED_BY_USER_PREFS;
  }
  if (typeof aEvent.expectedOverflowDeltaX !== "undefined") {
    if (aEvent.expectedOverflowDeltaX === 0) {
      options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_ZERO;
    } else if (aEvent.expectedOverflowDeltaX > 0) {
      options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_POSITIVE;
    } else {
      options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_NEGATIVE;
    }
  }
  if (typeof aEvent.expectedOverflowDeltaY !== "undefined") {
    if (aEvent.expectedOverflowDeltaY === 0) {
      options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_ZERO;
    } else if (aEvent.expectedOverflowDeltaY > 0) {
      options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_POSITIVE;
    } else {
      options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_NEGATIVE;
    }
  }

  // Avoid the JS warnings "reference to undefined property"
  if (!aEvent.deltaX) {
    aEvent.deltaX = 0;
  }
  if (!aEvent.deltaY) {
    aEvent.deltaY = 0;
  }
  if (!aEvent.deltaZ) {
    aEvent.deltaZ = 0;
  }

  var lineOrPageDeltaX =
    // eslint-disable-next-line no-nested-ternary
    aEvent.lineOrPageDeltaX != null
      ? aEvent.lineOrPageDeltaX
      : aEvent.deltaX > 0
      ? Math.floor(aEvent.deltaX)
      : Math.ceil(aEvent.deltaX);
  var lineOrPageDeltaY =
    // eslint-disable-next-line no-nested-ternary
    aEvent.lineOrPageDeltaY != null
      ? aEvent.lineOrPageDeltaY
      : aEvent.deltaY > 0
      ? Math.floor(aEvent.deltaY)
      : Math.ceil(aEvent.deltaY);
  utils.sendWheelEvent(
    aLeft,
    aTop,
    aEvent.deltaX,
    aEvent.deltaY,
    aEvent.deltaZ,
    aEvent.deltaMode,
    modifiers,
    lineOrPageDeltaX,
    lineOrPageDeltaY,
    options
  );
}

/**
 * Synthesize a wheel event on a target. The actual client point is determined
 * by taking the aTarget's client box and offseting it by aOffsetX and
 * aOffsetY.
 *
 * aEvent is an object which may contain the properties:
 *   shiftKey, ctrlKey, altKey, metaKey, accessKey, deltaX, deltaY, deltaZ,
 *   deltaMode, lineOrPageDeltaX, lineOrPageDeltaY, isMomentum,
 *   isNoLineOrPageDelta, isCustomizedByPrefs, expectedOverflowDeltaX,
 *   expectedOverflowDeltaY
 *
 * deltaMode must be defined, others are ok even if undefined.
 *
 * expectedOverflowDeltaX and expectedOverflowDeltaY take integer value.  The
 * value is just checked as 0 or positive or negative.
 *
 * aWindow is optional, and defaults to the current window object.
 */
function synthesizeWheel(aTarget, aOffsetX, aOffsetY, aEvent, aWindow) {
  var rect = aTarget.getBoundingClientRect();
  synthesizeWheelAtPoint(
    rect.left + aOffsetX,
    rect.top + aOffsetY,
    aEvent,
    aWindow
  );
}

const _FlushModes = {
  FLUSH: 0,
  NOFLUSH: 1,
};

function _sendWheelAndPaint(
  aTarget,
  aOffsetX,
  aOffsetY,
  aEvent,
  aCallback,
  aFlushMode = _FlushModes.FLUSH,
  aWindow = window
) {
  var utils = _getDOMWindowUtils(aWindow);
  if (!utils) {
    return;
  }

  if (utils.isMozAfterPaintPending) {
    // If a paint is pending, then APZ may be waiting for a scroll acknowledgement
    // from the content thread. If we send a wheel event now, it could be ignored
    // by APZ (or its scroll offset could be overridden). To avoid problems we
    // just wait for the paint to complete.
    aWindow.waitForAllPaintsFlushed(function () {
      _sendWheelAndPaint(
        aTarget,
        aOffsetX,
        aOffsetY,
        aEvent,
        aCallback,
        aFlushMode,
        aWindow
      );
    });
    return;
  }

  var onwheel = function () {
    SpecialPowers.wrap(window).removeEventListener("wheel", onwheel, {
      mozSystemGroup: true,
    });

    // Wait one frame since the wheel event has not caused a refresh observer
    // to be added yet.
    setTimeout(function () {
      utils.advanceTimeAndRefresh(1000);

      if (!aCallback) {
        utils.advanceTimeAndRefresh(0);
        return;
      }

      var waitForPaints = function () {
        SpecialPowers.Services.obs.removeObserver(
          waitForPaints,
          "apz-repaints-flushed"
        );
        aWindow.waitForAllPaintsFlushed(function () {
          utils.restoreNormalRefresh();
          aCallback();
        });
      };

      SpecialPowers.Services.obs.addObserver(
        waitForPaints,
        "apz-repaints-flushed"
      );
      if (!utils.flushApzRepaints(aWindow)) {
        waitForPaints();
      }
    }, 0);
  };

  // Listen for the system wheel event, because it happens after all of
  // the other wheel events, including legacy events.
  SpecialPowers.wrap(aWindow).addEventListener("wheel", onwheel, {
    mozSystemGroup: true,
  });
  if (aFlushMode === _FlushModes.FLUSH) {
    synthesizeWheel(aTarget, aOffsetX, aOffsetY, aEvent, aWindow);
  } else {
    synthesizeWheelAtPoint(aOffsetX, aOffsetY, aEvent, aWindow);
  }
}

/**
 * This is a wrapper around synthesizeWheel that waits for the wheel event
 * to be dispatched and for the subsequent layout/paints to be flushed.
 *
 * This requires including paint_listener.js. Tests must call
 * DOMWindowUtils.restoreNormalRefresh() before finishing, if they use this
 * function.
 *
 * If no callback is provided, the caller is assumed to have its own method of
 * determining scroll completion and the refresh driver is not automatically
 * restored.
 */
function sendWheelAndPaint(
  aTarget,
  aOffsetX,
  aOffsetY,
  aEvent,
  aCallback,
  aWindow = window
) {
  _sendWheelAndPaint(
    aTarget,
    aOffsetX,
    aOffsetY,
    aEvent,
    aCallback,
    _FlushModes.FLUSH,
    aWindow
  );
}

/**
 * Similar to sendWheelAndPaint but without flushing layout for obtaining
 * ``aTarget`` position in ``aWindow`` before sending the wheel event.
 * ``aOffsetX`` and ``aOffsetY`` should be offsets against aWindow.
 */
function sendWheelAndPaintNoFlush(
  aTarget,
  aOffsetX,
  aOffsetY,
  aEvent,
  aCallback,
  aWindow = window
) {
  _sendWheelAndPaint(
    aTarget,
    aOffsetX,
    aOffsetY,
    aEvent,
    aCallback,
    _FlushModes.NOFLUSH,
    aWindow
  );
}

function synthesizeNativeTapAtCenter(
  aTarget,
  aLongTap = false,
  aCallback = null,
  aWindow = window
) {
  let rect = aTarget.getBoundingClientRect();
  return synthesizeNativeTap(
    aTarget,
    rect.width / 2,
    rect.height / 2,
    aLongTap,
    aCallback,
    aWindow
  );
}

function synthesizeNativeTap(
  aTarget,
  aOffsetX,
  aOffsetY,
  aLongTap = false,
  aCallback = null,
  aWindow = window
) {
  let utils = _getDOMWindowUtils(aWindow);
  if (!utils) {
    return;
  }

  let scale = aWindow.devicePixelRatio;
  let rect = aTarget.getBoundingClientRect();
  let x = (aWindow.mozInnerScreenX + rect.left + aOffsetX) * scale;
  let y = (aWindow.mozInnerScreenY + rect.top + aOffsetY) * scale;

  let observer = {
    observe: (subject, topic, data) => {
      if (aCallback && topic == "mouseevent") {
        aCallback(data);
      }
    },
  };
  utils.sendNativeTouchTap(x, y, aLongTap, observer);
}

/**
 * Similar to synthesizeMouse but generates a native widget level event
 * (so will actually move the "real" mouse cursor etc. Be careful because
 * this can impact later code as well! (e.g. with hover states etc.)
 *
 * @description There are 3 mutually exclusive ways of indicating the location of the
 * mouse event: set ``atCenter``, or pass ``offsetX`` and ``offsetY``,
 * or pass ``screenX`` and ``screenY``. Do not attempt to mix these.
 *
 * @param {object} aParams
 * @param {string} aParams.type "click", "mousedown", "mouseup" or "mousemove"
 * @param {Element} aParams.target Origin of offsetX and offsetY, must be an element
 * @param {Boolean} [aParams.atCenter]
 *        Instead of offsetX/Y, synthesize the event at center of `target`.
 * @param {Number} [aParams.offsetX]
 *        X offset in `target` (in CSS pixels if `scale` is "screenPixelsPerCSSPixel")
 * @param {Number} [aParams.offsetY]
 *        Y offset in `target` (in CSS pixels if `scale` is "screenPixelsPerCSSPixel")
 * @param {Number} [aParams.screenX]
 *        X offset in screen (in CSS pixels if `scale` is "screenPixelsPerCSSPixel"),
 *        Neither offsetX/Y nor atCenter must be set if this is set.
 * @param {Number} [aParams.screenY]
 *        Y offset in screen (in CSS pixels if `scale` is "screenPixelsPerCSSPixel"),
 *        Neither offsetX/Y nor atCenter must be set if this is set.
 * @param {String} [aParams.scale="screenPixelsPerCSSPixel"]
 *        If scale is "screenPixelsPerCSSPixel", devicePixelRatio will be used.
 *        If scale is "inScreenPixels", clientX/Y nor scaleX/Y are not adjusted with screenPixelsPerCSSPixel.
 * @param {Number} [aParams.button=0]
 *        Defaults to 0, if "click", "mousedown", "mouseup", set same value as DOM MouseEvent.button
 * @param {Object} [aParams.modifiers={}]
 *        Active modifiers, see `_parseNativeModifiers`
 * @param {Window} [aParams.win=window]
 *        The window to use its utils. Defaults to the window in which EventUtils.js is running.
 * @param {Element} [aParams.elementOnWidget=target]
 *        Defaults to target. If element under the point is in another widget from target's widget,
 *        e.g., when it's in a XUL <panel>, specify this.
 */
function synthesizeNativeMouseEvent(aParams, aCallback = null) {
  const {
    type,
    target,
    offsetX,
    offsetY,
    atCenter,
    screenX,
    screenY,
    scale = "screenPixelsPerCSSPixel",
    button = 0,
    modifiers = {},
    win = window,
    elementOnWidget = target,
  } = aParams;
  if (atCenter) {
    if (offsetX != undefined || offsetY != undefined) {
      throw Error(
        `atCenter is specified, but offsetX (${offsetX}) and/or offsetY (${offsetY}) are also specified`
      );
    }
    if (screenX != undefined || screenY != undefined) {
      throw Error(
        `atCenter is specified, but screenX (${screenX}) and/or screenY (${screenY}) are also specified`
      );
    }
    if (!target) {
      throw Error("atCenter is specified, but target is not specified");
    }
  } else if (offsetX != undefined && offsetY != undefined) {
    if (screenX != undefined || screenY != undefined) {
      throw Error(
        `offsetX/Y are specified, but screenX (${screenX}) and/or screenY (${screenY}) are also specified`
      );
    }
    if (!target) {
      throw Error(
        "offsetX and offsetY are specified, but target is not specified"
      );
    }
  } else if (screenX != undefined && screenY != undefined) {
    if (offsetX != undefined || offsetY != undefined) {
      throw Error(
        `screenX/Y are specified, but offsetX (${offsetX}) and/or offsetY (${offsetY}) are also specified`
      );
    }
  }
  const utils = _getDOMWindowUtils(win);
  if (!utils) {
    return;
  }

  const rect = target?.getBoundingClientRect();
  let resolution = 1.0;
  try {
    resolution = _getDOMWindowUtils(win.top).getResolution();
  } catch (e) {
    // XXX How to get mobile viewport scale on Fission+xorigin since
    //     window.top access isn't allowed due to cross-origin?
  }
  const scaleValue = (() => {
    if (scale === "inScreenPixels") {
      return 1.0;
    }
    if (scale === "screenPixelsPerCSSPixel") {
      return win.devicePixelRatio;
    }
    throw Error(`invalid scale value (${scale}) is specified`);
  })();
  // XXX mozInnerScreen might be invalid value on mobile viewport (Bug 1701546),
  //     so use window.top's mozInnerScreen. But this won't work fission+xorigin
  //     with mobile viewport until mozInnerScreen returns valid value with
  //     scale.
  const x = (() => {
    if (screenX != undefined) {
      return screenX * scaleValue;
    }
    let winInnerOffsetX = win.mozInnerScreenX;
    try {
      winInnerOffsetX =
        win.top.mozInnerScreenX +
        (win.mozInnerScreenX - win.top.mozInnerScreenX) * resolution;
    } catch (e) {
      // XXX fission+xorigin test throws permission denied since win.top is
      //     cross-origin.
    }
    return (
      (((atCenter ? rect.width / 2 : offsetX) + rect.left) * resolution +
        winInnerOffsetX) *
      scaleValue
    );
  })();
  const y = (() => {
    if (screenY != undefined) {
      return screenY * scaleValue;
    }
    let winInnerOffsetY = win.mozInnerScreenY;
    try {
      winInnerOffsetY =
        win.top.mozInnerScreenY +
        (win.mozInnerScreenY - win.top.mozInnerScreenY) * resolution;
    } catch (e) {
      // XXX fission+xorigin test throws permission denied since win.top is
      //     cross-origin.
    }
    return (
      (((atCenter ? rect.height / 2 : offsetY) + rect.top) * resolution +
        winInnerOffsetY) *
      scaleValue
    );
  })();
  const modifierFlags = _parseNativeModifiers(modifiers);

  const observer = {
    observe: (subject, topic, data) => {
      if (aCallback && topic == "mouseevent") {
        aCallback(data);
      }
    },
  };
  if (type === "click") {
    utils.sendNativeMouseEvent(
      x,
      y,
      utils.NATIVE_MOUSE_MESSAGE_BUTTON_DOWN,
      button,
      modifierFlags,
      elementOnWidget,
      function () {
        utils.sendNativeMouseEvent(
          x,
          y,
          utils.NATIVE_MOUSE_MESSAGE_BUTTON_UP,
          button,
          modifierFlags,
          elementOnWidget,
          observer
        );
      }
    );
    return;
  }
  utils.sendNativeMouseEvent(
    x,
    y,
    (() => {
      switch (type) {
        case "mousedown":
          return utils.NATIVE_MOUSE_MESSAGE_BUTTON_DOWN;
        case "mouseup":
          return utils.NATIVE_MOUSE_MESSAGE_BUTTON_UP;
        case "mousemove":
          return utils.NATIVE_MOUSE_MESSAGE_MOVE;
        default:
          throw Error(`Invalid type is specified: ${type}`);
      }
    })(),
    button,
    modifierFlags,
    elementOnWidget,
    observer
  );
}

function promiseNativeMouseEvent(aParams) {
  return new Promise(resolve => synthesizeNativeMouseEvent(aParams, resolve));
}

function synthesizeNativeMouseEventAndWaitForEvent(aParams, aCallback) {
  const listener = aParams.eventTargetToListen || aParams.target;
  const eventType = aParams.eventTypeToWait || aParams.type;
  listener.addEventListener(eventType, aCallback, {
    capture: true,
    once: true,
  });
  synthesizeNativeMouseEvent(aParams);
}

function promiseNativeMouseEventAndWaitForEvent(aParams) {
  return new Promise(resolve =>
    synthesizeNativeMouseEventAndWaitForEvent(aParams, resolve)
  );
}

/**
 * This is a wrapper around synthesizeNativeMouseEvent that waits for the mouse
 * event to be dispatched to the target content.
 *
 * This API is supposed to be used in those test cases that synthesize some
 * input events to chrome process and have some checks in content.
 */
function synthesizeAndWaitNativeMouseMove(
  aTarget,
  aOffsetX,
  aOffsetY,
  aCallback,
  aWindow = window
) {
  let browser = gBrowser.selectedTab.linkedBrowser;
  let mm = browser.messageManager;
  let { ContentTask } = _EU_ChromeUtils.importESModule(
    "resource://testing-common/ContentTask.sys.mjs"
  );

  let eventRegisteredPromise = new Promise(resolve => {
    mm.addMessageListener("Test:MouseMoveRegistered", function processed() {
      mm.removeMessageListener("Test:MouseMoveRegistered", processed);
      resolve();
    });
  });
  let eventReceivedPromise = ContentTask.spawn(
    browser,
    [aOffsetX, aOffsetY],
    ([clientX, clientY]) => {
      return new Promise(resolve => {
        addEventListener("mousemove", function onMouseMoveEvent(e) {
          if (e.clientX == clientX && e.clientY == clientY) {
            removeEventListener("mousemove", onMouseMoveEvent);
            resolve();
          }
        });
        sendAsyncMessage("Test:MouseMoveRegistered");
      });
    }
  );
  eventRegisteredPromise.then(() => {
    synthesizeNativeMouseEvent({
      type: "mousemove",
      target: aTarget,
      offsetX: aOffsetX,
      offsetY: aOffsetY,
      win: aWindow,
    });
  });
  return eventReceivedPromise;
}

/**
 * Synthesize a key event. It is targeted at whatever would be targeted by an
 * actual keypress by the user, typically the focused element.
 *
 * @param {String} aKey
 *        Should be either:
 *
 *        - key value (recommended).  If you specify a non-printable key name,
 *          prepend the ``KEY_`` prefix.  Otherwise, specifying a printable key, the
 *          key value should be specified.
 *
 *        - keyCode name starting with ``VK_`` (e.g., ``VK_RETURN``).  This is available
 *          only for compatibility with legacy API.  Don't use this with new tests.
 *
 * @param {Object} [aEvent]
 *        Optional event object with more specifics about the key event to
 *        synthesize.
 * @param {String} [aEvent.code]
 *        If you don't specify this explicitly, it'll be guessed from aKey
 *        of US keyboard layout.  Note that this value may be different
 *        between browsers.  For example, "Insert" is never set only on
 *        macOS since actual key operation won't cause this code value.
 *        In such case, the value becomes empty string.
 *        If you need to emulate non-US keyboard layout or virtual keyboard
 *        which doesn't emulate hardware key input, you should set this value
 *        to empty string explicitly.
 * @param {Number} [aEvent.repeat]
 *        If you emulate auto-repeat, you should set the count of repeat.
 *        This method will automatically synthesize keydown (and keypress).
 * @param {*} aEvent.location
 *        If you want to specify this, you can specify this explicitly.
 *        However, if you don't specify this value, it will be computed
 *        from code value.
 * @param {String} aEvent.type
 *        Basically, you shouldn't specify this.  Then, this function will
 *        synthesize keydown (, keypress) and keyup.
 *        If keydown is specified, this only fires keydown (and keypress if
 *        it should be fired).
 *        If keyup is specified, this only fires keyup.
 * @param {Number} aEvent.keyCode
 *        Must be 0 - 255 (0xFF). If this is specified explicitly,
 *        .keyCode value is initialized with this value.
 * @param {Window} aWindow
 *        Is optional and defaults to the current window object.
 * @param {Function} aCallback
 *        Is optional and can be used to receive notifications from TIP.
 *
 * @description
 * ``accelKey``, ``altKey``, ``altGraphKey``, ``ctrlKey``, ``capsLockKey``,
 * ``fnKey``, ``fnLockKey``, ``numLockKey``, ``metaKey``, ``scrollLockKey``,
 * ``shiftKey``, ``symbolKey``, ``symbolLockKey``
 * Basically, you shouldn't use these attributes.  nsITextInputProcessor
 * manages modifier key state when you synthesize modifier key events.
 * However, if some of these attributes are true, this function activates
 * the modifiers only during dispatching the key events.
 * Note that if some of these values are false, they are ignored (i.e.,
 * not inactivated with this function).
 *
 */
function synthesizeKey(aKey, aEvent = undefined, aWindow = window, aCallback) {
  const event = aEvent === undefined || aEvent === null ? {} : aEvent;
  let dispatchKeydown =
    !("type" in event) || event.type === "keydown" || !event.type;
  const dispatchKeyup =
    !("type" in event) || event.type === "keyup" || !event.type;

  if (dispatchKeydown && aKey == "KEY_Escape") {
    let eventForKeydown = Object.assign({}, JSON.parse(JSON.stringify(event)));
    eventForKeydown.type = "keydown";
    if (
      _maybeEndDragSession(
        // TODO: We should set the last dragover point instead
        0,
        0,
        eventForKeydown,
        aWindow
      )
    ) {
      if (!dispatchKeyup) {
        return;
      }
      // We don't need to dispatch only keydown event because it's consumed by
      // the drag session.
      dispatchKeydown = false;
    }
  }

  var TIP = _getTIP(aWindow, aCallback);
  if (!TIP) {
    return;
  }
  var KeyboardEvent = _getKeyboardEvent(aWindow);
  var modifiers = _emulateToActivateModifiers(TIP, event, aWindow);
  var keyEventDict = _createKeyboardEventDictionary(aKey, event, TIP, aWindow);
  var keyEvent = new KeyboardEvent("", keyEventDict.dictionary);

  try {
    if (dispatchKeydown) {
      TIP.keydown(keyEvent, keyEventDict.flags);
      if ("repeat" in event && event.repeat > 1) {
        keyEventDict.dictionary.repeat = true;
        var repeatedKeyEvent = new KeyboardEvent("", keyEventDict.dictionary);
        for (var i = 1; i < event.repeat; i++) {
          TIP.keydown(repeatedKeyEvent, keyEventDict.flags);
        }
      }
    }
    if (dispatchKeyup) {
      TIP.keyup(keyEvent, keyEventDict.flags);
    }
  } finally {
    _emulateToInactivateModifiers(TIP, modifiers, aWindow);
  }
}

/**
 * This is a wrapper around synthesizeKey that waits for the key event to be
 * dispatched to the target content. It returns a promise which is resolved
 * when the content receives the key event.
 *
 * This API is supposed to be used in those test cases that synthesize some
 * input events to chrome process and have some checks in content.
 */
function synthesizeAndWaitKey(
  aKey,
  aEvent,
  aWindow = window,
  checkBeforeSynthesize,
  checkAfterSynthesize
) {
  let browser = gBrowser.selectedTab.linkedBrowser;
  let mm = browser.messageManager;
  let keyCode = _createKeyboardEventDictionary(aKey, aEvent, null, aWindow)
    .dictionary.keyCode;
  let { ContentTask } = _EU_ChromeUtils.importESModule(
    "resource://testing-common/ContentTask.sys.mjs"
  );

  let keyRegisteredPromise = new Promise(resolve => {
    mm.addMessageListener("Test:KeyRegistered", function processed() {
      mm.removeMessageListener("Test:KeyRegistered", processed);
      resolve();
    });
  });
  // eslint-disable-next-line no-shadow
  let keyReceivedPromise = ContentTask.spawn(browser, keyCode, keyCode => {
    return new Promise(resolve => {
      addEventListener("keyup", function onKeyEvent(e) {
        if (e.keyCode == keyCode) {
          removeEventListener("keyup", onKeyEvent);
          resolve();
        }
      });
      sendAsyncMessage("Test:KeyRegistered");
    });
  });
  keyRegisteredPromise.then(() => {
    if (checkBeforeSynthesize) {
      checkBeforeSynthesize();
    }
    synthesizeKey(aKey, aEvent, aWindow);
    if (checkAfterSynthesize) {
      checkAfterSynthesize();
    }
  });
  return keyReceivedPromise;
}

function _parseNativeModifiers(aModifiers, aWindow = window) {
  let modifiers = 0;
  if (aModifiers.capsLockKey) {
    modifiers |= SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_CAPS_LOCK;
  }
  if (aModifiers.numLockKey) {
    modifiers |= SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_NUM_LOCK;
  }
  if (aModifiers.shiftKey) {
    modifiers |= SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_SHIFT_LEFT;
  }
  if (aModifiers.shiftRightKey) {
    modifiers |= SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_SHIFT_RIGHT;
  }
  if (aModifiers.ctrlKey) {
    modifiers |=
      SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_CONTROL_LEFT;
  }
  if (aModifiers.ctrlRightKey) {
    modifiers |=
      SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_CONTROL_RIGHT;
  }
  if (aModifiers.altKey) {
    modifiers |= SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_ALT_LEFT;
  }
  if (aModifiers.altRightKey) {
    modifiers |= SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_ALT_RIGHT;
  }
  if (aModifiers.metaKey) {
    modifiers |=
      SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_COMMAND_LEFT;
  }
  if (aModifiers.metaRightKey) {
    modifiers |=
      SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_COMMAND_RIGHT;
  }
  if (aModifiers.helpKey) {
    modifiers |= SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_HELP;
  }
  if (aModifiers.fnKey) {
    modifiers |= SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_FUNCTION;
  }
  if (aModifiers.numericKeyPadKey) {
    modifiers |=
      SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_NUMERIC_KEY_PAD;
  }

  if (aModifiers.accelKey) {
    modifiers |= _EU_isMac(aWindow)
      ? SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_COMMAND_LEFT
      : SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_CONTROL_LEFT;
  }
  if (aModifiers.accelRightKey) {
    modifiers |= _EU_isMac(aWindow)
      ? SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_COMMAND_RIGHT
      : SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_CONTROL_RIGHT;
  }
  if (aModifiers.altGrKey) {
    modifiers |= _EU_isMac(aWindow)
      ? SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_ALT_LEFT
      : SpecialPowers.Ci.nsIDOMWindowUtils.NATIVE_MODIFIER_ALT_GRAPH;
  }
  return modifiers;
}

// Mac: Any unused number is okay for adding new keyboard layout.
//      When you add new keyboard layout here, you need to modify
//      TISInputSourceWrapper::InitByLayoutID().
// Win: These constants can be found by inspecting registry keys under
//      HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Keyboard Layouts

const KEYBOARD_LAYOUT_ARABIC = {
  name: "Arabic",
  Mac: 6,
  Win: 0x00000401,
  hasAltGrOnWin: false,
};
_defineConstant("KEYBOARD_LAYOUT_ARABIC", KEYBOARD_LAYOUT_ARABIC);
const KEYBOARD_LAYOUT_ARABIC_PC = {
  name: "Arabic - PC",
  Mac: 7,
  Win: null,
  hasAltGrOnWin: false,
};
_defineConstant("KEYBOARD_LAYOUT_ARABIC_PC", KEYBOARD_LAYOUT_ARABIC_PC);
const KEYBOARD_LAYOUT_BRAZILIAN_ABNT = {
  name: "Brazilian ABNT",
  Mac: null,
  Win: 0x00000416,
  hasAltGrOnWin: true,
};
_defineConstant(
  "KEYBOARD_LAYOUT_BRAZILIAN_ABNT",
  KEYBOARD_LAYOUT_BRAZILIAN_ABNT
);
const KEYBOARD_LAYOUT_DVORAK_QWERTY = {
  name: "Dvorak-QWERTY",
  Mac: 4,
  Win: null,
  hasAltGrOnWin: false,
};
_defineConstant("KEYBOARD_LAYOUT_DVORAK_QWERTY", KEYBOARD_LAYOUT_DVORAK_QWERTY);
const KEYBOARD_LAYOUT_EN_US = {
  name: "US",
  Mac: 0,
  Win: 0x00000409,
  hasAltGrOnWin: false,
};
_defineConstant("KEYBOARD_LAYOUT_EN_US", KEYBOARD_LAYOUT_EN_US);
const KEYBOARD_LAYOUT_FRENCH = {
  name: "French",
  Mac: 8, // Some keys mapped different from PC, e.g., Digit6, Digit8, Equal, Slash and Backslash
  Win: 0x0000040c,
  hasAltGrOnWin: true,
};
_defineConstant("KEYBOARD_LAYOUT_FRENCH", KEYBOARD_LAYOUT_FRENCH);
const KEYBOARD_LAYOUT_FRENCH_PC = {
  name: "French-PC",
  Mac: 13, // Compatible with Windows
  Win: 0x0000040c,
  hasAltGrOnWin: true,
};
_defineConstant("KEYBOARD_LAYOUT_FRENCH_PC", KEYBOARD_LAYOUT_FRENCH_PC);
const KEYBOARD_LAYOUT_GREEK = {
  name: "Greek",
  Mac: 1,
  Win: 0x00000408,
  hasAltGrOnWin: true,
};
_defineConstant("KEYBOARD_LAYOUT_GREEK", KEYBOARD_LAYOUT_GREEK);
const KEYBOARD_LAYOUT_GERMAN = {
  name: "German",
  Mac: 2,
  Win: 0x00000407,
  hasAltGrOnWin: true,
};
_defineConstant("KEYBOARD_LAYOUT_GERMAN", KEYBOARD_LAYOUT_GERMAN);
const KEYBOARD_LAYOUT_HEBREW = {
  name: "Hebrew",
  Mac: 9,
  Win: 0x0000040d,
  hasAltGrOnWin: true,
};
_defineConstant("KEYBOARD_LAYOUT_HEBREW", KEYBOARD_LAYOUT_HEBREW);
const KEYBOARD_LAYOUT_JAPANESE = {
  name: "Japanese",
  Mac: null,
  Win: 0x00000411,
  hasAltGrOnWin: false,
};
_defineConstant("KEYBOARD_LAYOUT_JAPANESE", KEYBOARD_LAYOUT_JAPANESE);
const KEYBOARD_LAYOUT_KHMER = {
  name: "Khmer",
  Mac: null,
  Win: 0x00000453,
  hasAltGrOnWin: true,
}; // available on Win7 or later.
_defineConstant("KEYBOARD_LAYOUT_KHMER", KEYBOARD_LAYOUT_KHMER);
const KEYBOARD_LAYOUT_LITHUANIAN = {
  name: "Lithuanian",
  Mac: 10,
  Win: 0x00010427,
  hasAltGrOnWin: true,
};
_defineConstant("KEYBOARD_LAYOUT_LITHUANIAN", KEYBOARD_LAYOUT_LITHUANIAN);
const KEYBOARD_LAYOUT_NORWEGIAN = {
  name: "Norwegian",
  Mac: 11,
  Win: 0x00000414,
  hasAltGrOnWin: true,
};
_defineConstant("KEYBOARD_LAYOUT_NORWEGIAN", KEYBOARD_LAYOUT_NORWEGIAN);
const KEYBOARD_LAYOUT_RUSSIAN = {
  name: "Russian",
  Mac: null,
  Win: 0x00000419,
  hasAltGrOnWin: true, // No AltGr, but Ctrl + Alt + Digit8 introduces a char
};
_defineConstant("KEYBOARD_LAYOUT_RUSSIAN", KEYBOARD_LAYOUT_RUSSIAN);
const KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC = {
  name: "Russian - Mnemonic",
  Mac: null,
  Win: 0x00020419,
  hasAltGrOnWin: true,
}; // available on Win8 or later.
_defineConstant(
  "KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC",
  KEYBOARD_LAYOUT_RUSSIAN_MNEMONIC
);
const KEYBOARD_LAYOUT_SPANISH = {
  name: "Spanish",
  Mac: 12,
  Win: 0x0000040a,
  hasAltGrOnWin: true,
};
_defineConstant("KEYBOARD_LAYOUT_SPANISH", KEYBOARD_LAYOUT_SPANISH);
const KEYBOARD_LAYOUT_SWEDISH = {
  name: "Swedish",
  Mac: 3,
  Win: 0x0000041d,
  hasAltGrOnWin: true,
};
_defineConstant("KEYBOARD_LAYOUT_SWEDISH", KEYBOARD_LAYOUT_SWEDISH);
const KEYBOARD_LAYOUT_THAI = {
  name: "Thai",
  Mac: 5,
  Win: 0x0002041e,
  hasAltGrOnWin: false,
};
_defineConstant("KEYBOARD_LAYOUT_THAI", KEYBOARD_LAYOUT_THAI);

/**
 * synthesizeNativeKey() dispatches native key event on active window.
 * This is implemented only on Windows and Mac. Note that this function
 * dispatches the key event asynchronously and returns immediately. If a
 * callback function is provided, the callback will be called upon
 * completion of the key dispatch.
 *
 * @param aKeyboardLayout       One of KEYBOARD_LAYOUT_* defined above.
 * @param aNativeKeyCode        A native keycode value defined in
 *                              NativeKeyCodes.js.
 * @param aModifiers            Modifier keys.  If no modifire key is pressed,
 *                              this must be {}.  Otherwise, one or more items
 *                              referred in _parseNativeModifiers() must be
 *                              true.
 * @param aChars                Specify characters which should be generated
 *                              by the key event.
 * @param aUnmodifiedChars      Specify characters of unmodified (except Shift)
 *                              aChar value.
 * @param aCallback             If provided, this callback will be invoked
 *                              once the native keys have been processed
 *                              by Gecko. Will never be called if this
 *                              function returns false.
 * @return                      True if this function succeed dispatching
 *                              native key event.  Otherwise, false.
 */

function synthesizeNativeKey(
  aKeyboardLayout,
  aNativeKeyCode,
  aModifiers,
  aChars,
  aUnmodifiedChars,
  aCallback,
  aWindow = window
) {
  var utils = _getDOMWindowUtils(aWindow);
  if (!utils) {
    return false;
  }
  var nativeKeyboardLayout = null;
  if (_EU_isMac(aWindow)) {
    nativeKeyboardLayout = aKeyboardLayout.Mac;
  } else if (_EU_isWin(aWindow)) {
    nativeKeyboardLayout = aKeyboardLayout.Win;
  }
  if (nativeKeyboardLayout === null) {
    return false;
  }

  var observer = {
    observe(aSubject, aTopic, aData) {
      if (aCallback && aTopic == "keyevent") {
        aCallback(aData);
      }
    },
  };
  utils.sendNativeKeyEvent(
    nativeKeyboardLayout,
    aNativeKeyCode,
    _parseNativeModifiers(aModifiers, aWindow),
    aChars,
    aUnmodifiedChars,
    observer
  );
  return true;
}

var _gSeenEvent = false;

/**
 * Indicate that an event with an original target of aExpectedTarget and
 * a type of aExpectedEvent is expected to be fired, or not expected to
 * be fired.
 */
function _expectEvent(aExpectedTarget, aExpectedEvent, aTestName) {
  if (!aExpectedTarget || !aExpectedEvent) {
    return null;
  }

  _gSeenEvent = false;

  var type =
    aExpectedEvent.charAt(0) == "!"
      ? aExpectedEvent.substring(1)
      : aExpectedEvent;
  var eventHandler = function (event) {
    var epassed =
      !_gSeenEvent &&
      event.originalTarget == aExpectedTarget &&
      event.type == type;
    is(
      epassed,
      true,
      aTestName + " " + type + " event target " + (_gSeenEvent ? "twice" : "")
    );
    _gSeenEvent = true;
  };

  aExpectedTarget.addEventListener(type, eventHandler);
  return eventHandler;
}

/**
 * Check if the event was fired or not. The event handler aEventHandler
 * will be removed.
 */
function _checkExpectedEvent(
  aExpectedTarget,
  aExpectedEvent,
  aEventHandler,
  aTestName
) {
  if (aEventHandler) {
    var expectEvent = aExpectedEvent.charAt(0) != "!";
    var type = expectEvent ? aExpectedEvent : aExpectedEvent.substring(1);
    aExpectedTarget.removeEventListener(type, aEventHandler);
    var desc = type + " event";
    if (!expectEvent) {
      desc += " not";
    }
    is(_gSeenEvent, expectEvent, aTestName + " " + desc + " fired");
  }

  _gSeenEvent = false;
}

/**
 * Similar to synthesizeMouse except that a test is performed to see if an
 * event is fired at the right target as a result.
 *
 * aExpectedTarget - the expected originalTarget of the event.
 * aExpectedEvent - the expected type of the event, such as 'select'.
 * aTestName - the test name when outputing results
 *
 * To test that an event is not fired, use an expected type preceded by an
 * exclamation mark, such as '!select'. This might be used to test that a
 * click on a disabled element doesn't fire certain events for instance.
 *
 * aWindow is optional, and defaults to the current window object.
 */
function synthesizeMouseExpectEvent(
  aTarget,
  aOffsetX,
  aOffsetY,
  aEvent,
  aExpectedTarget,
  aExpectedEvent,
  aTestName,
  aWindow
) {
  var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName);
  synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow);
  _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName);
}

/**
 * Similar to synthesizeKey except that a test is performed to see if an
 * event is fired at the right target as a result.
 *
 * aExpectedTarget - the expected originalTarget of the event.
 * aExpectedEvent - the expected type of the event, such as 'select'.
 * aTestName - the test name when outputing results
 *
 * To test that an event is not fired, use an expected type preceded by an
 * exclamation mark, such as '!select'.
 *
 * aWindow is optional, and defaults to the current window object.
 */
function synthesizeKeyExpectEvent(
  key,
  aEvent,
  aExpectedTarget,
  aExpectedEvent,
  aTestName,
  aWindow
) {
  var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName);
  synthesizeKey(key, aEvent, aWindow);
  _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName);
}

function disableNonTestMouseEvents(aDisable) {
  var domutils = _getDOMWindowUtils();
  domutils.disableNonTestMouseEvents(aDisable);
}

function _getDOMWindowUtils(aWindow = window) {
  // Leave this here as something, somewhere, passes a falsy argument
  // to this, causing the |window| default argument not to get picked up.
  if (!aWindow) {
    aWindow = window;
  }

  // If documentURIObject exists or `window` is a stub object, we're in
  // a chrome scope, so don't bother trying to go through SpecialPowers.
  if (!aWindow.document || aWindow.document.documentURIObject) {
    return aWindow.windowUtils;
  }

  // we need parent.SpecialPowers for:
  //  layout/base/tests/test_reftests_with_caret.html
  //  chrome: toolkit/content/tests/chrome/test_findbar.xul
  //  chrome: toolkit/content/tests/chrome/test_popup_anchor.xul
  if ("SpecialPowers" in aWindow && aWindow.SpecialPowers != undefined) {
    return aWindow.SpecialPowers.getDOMWindowUtils(aWindow);
  }
  if (
    "SpecialPowers" in aWindow.parent &&
    aWindow.parent.SpecialPowers != undefined
  ) {
    return aWindow.parent.SpecialPowers.getDOMWindowUtils(aWindow);
  }

  // TODO: this is assuming we are in chrome space
  return aWindow.windowUtils;
}

function _defineConstant(name, value) {
  Object.defineProperty(this, name, {
    value,
    enumerable: true,
    writable: false,
  });
}

const COMPOSITION_ATTR_RAW_CLAUSE =
  _EU_Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE;
_defineConstant("COMPOSITION_ATTR_RAW_CLAUSE", COMPOSITION_ATTR_RAW_CLAUSE);
const COMPOSITION_ATTR_SELECTED_RAW_CLAUSE =
  _EU_Ci.nsITextInputProcessor.ATTR_SELECTED_RAW_CLAUSE;
_defineConstant(
  "COMPOSITION_ATTR_SELECTED_RAW_CLAUSE",
  COMPOSITION_ATTR_SELECTED_RAW_CLAUSE
);
const COMPOSITION_ATTR_CONVERTED_CLAUSE =
  _EU_Ci.nsITextInputProcessor.ATTR_CONVERTED_CLAUSE;
_defineConstant(
  "COMPOSITION_ATTR_CONVERTED_CLAUSE",
  COMPOSITION_ATTR_CONVERTED_CLAUSE
);
const COMPOSITION_ATTR_SELECTED_CLAUSE =
  _EU_Ci.nsITextInputProcessor.ATTR_SELECTED_CLAUSE;
_defineConstant(
  "COMPOSITION_ATTR_SELECTED_CLAUSE",
  COMPOSITION_ATTR_SELECTED_CLAUSE
);

var TIPMap = new WeakMap();

function _getTIP(aWindow, aCallback) {
  if (!aWindow) {
    aWindow = window;
  }
  var tip;
  if (TIPMap.has(aWindow)) {
    tip = TIPMap.get(aWindow);
  } else {
    tip = _EU_Cc["@mozilla.org/text-input-processor;1"].createInstance(
      _EU_Ci.nsITextInputProcessor
    );
    TIPMap.set(aWindow, tip);
  }
  if (!tip.beginInputTransactionForTests(aWindow, aCallback)) {
    tip = null;
    TIPMap.delete(aWindow);
  }
  return tip;
}

function _getKeyboardEvent(aWindow = window) {
  if (typeof KeyboardEvent != "undefined") {
    try {
      // See if the object can be instantiated; sometimes this yields
      // 'TypeError: can't access dead object' or 'KeyboardEvent is not a constructor'.
      new KeyboardEvent("", {});
      return KeyboardEvent;
    } catch (ex) {}
  }
  if (typeof content != "undefined" && "KeyboardEvent" in content) {
    return content.KeyboardEvent;
  }
  return aWindow.KeyboardEvent;
}

// eslint-disable-next-line complexity
function _guessKeyNameFromKeyCode(aKeyCode, aWindow = window) {
  var KeyboardEvent = _getKeyboardEvent(aWindow);
  switch (aKeyCode) {
    case KeyboardEvent.DOM_VK_CANCEL:
      return "Cancel";
    case KeyboardEvent.DOM_VK_HELP:
      return "Help";
    case KeyboardEvent.DOM_VK_BACK_SPACE:
      return "Backspace";
    case KeyboardEvent.DOM_VK_TAB:
      return "Tab";
    case KeyboardEvent.DOM_VK_CLEAR:
      return "Clear";
    case KeyboardEvent.DOM_VK_RETURN:
      return "Enter";
    case KeyboardEvent.DOM_VK_SHIFT:
      return "Shift";
    case KeyboardEvent.DOM_VK_CONTROL:
      return "Control";
    case KeyboardEvent.DOM_VK_ALT:
      return "Alt";
    case KeyboardEvent.DOM_VK_PAUSE:
      return "Pause";
    case KeyboardEvent.DOM_VK_EISU:
      return "Eisu";
    case KeyboardEvent.DOM_VK_ESCAPE:
      return "Escape";
    case KeyboardEvent.DOM_VK_CONVERT:
      return "Convert";
    case KeyboardEvent.DOM_VK_NONCONVERT:
      return "NonConvert";
    case KeyboardEvent.DOM_VK_ACCEPT:
      return "Accept";
    case KeyboardEvent.DOM_VK_MODECHANGE:
      return "ModeChange";
    case KeyboardEvent.DOM_VK_PAGE_UP:
      return "PageUp";
    case KeyboardEvent.DOM_VK_PAGE_DOWN:
      return "PageDown";
    case KeyboardEvent.DOM_VK_END:
      return "End";
    case KeyboardEvent.DOM_VK_HOME:
      return "Home";
    case KeyboardEvent.DOM_VK_LEFT:
      return "ArrowLeft";
    case KeyboardEvent.DOM_VK_UP:
      return "ArrowUp";
    case KeyboardEvent.DOM_VK_RIGHT:
      return "ArrowRight";
    case KeyboardEvent.DOM_VK_DOWN:
      return "ArrowDown";
    case KeyboardEvent.DOM_VK_SELECT:
      return "Select";
    case KeyboardEvent.DOM_VK_PRINT:
      return "Print";
    case KeyboardEvent.DOM_VK_EXECUTE:
      return "Execute";
    case KeyboardEvent.DOM_VK_PRINTSCREEN:
      return "PrintScreen";
    case KeyboardEvent.DOM_VK_INSERT:
      return "Insert";
    case KeyboardEvent.DOM_VK_DELETE:
      return "Delete";
    case KeyboardEvent.DOM_VK_WIN:
      return "OS";
    case KeyboardEvent.DOM_VK_CONTEXT_MENU:
      return "ContextMenu";
    case KeyboardEvent.DOM_VK_SLEEP:
      return "Standby";
    case KeyboardEvent.DOM_VK_F1:
      return "F1";
    case KeyboardEvent.DOM_VK_F2:
      return "F2";
    case KeyboardEvent.DOM_VK_F3:
      return "F3";
    case KeyboardEvent.DOM_VK_F4:
      return "F4";
    case KeyboardEvent.DOM_VK_F5:
      return "F5";
    case KeyboardEvent.DOM_VK_F6:
      return "F6";
    case KeyboardEvent.DOM_VK_F7:
      return "F7";
    case KeyboardEvent.DOM_VK_F8:
      return "F8";
    case KeyboardEvent.DOM_VK_F9:
      return "F9";
    case KeyboardEvent.DOM_VK_F10:
      return "F10";
    case KeyboardEvent.DOM_VK_F11:
      return "F11";
    case KeyboardEvent.DOM_VK_F12:
      return "F12";
    case KeyboardEvent.DOM_VK_F13:
      return "F13";
    case KeyboardEvent.DOM_VK_F14:
      return "F14";
    case KeyboardEvent.DOM_VK_F15:
      return "F15";
    case KeyboardEvent.DOM_VK_F16:
      return "F16";
    case KeyboardEvent.DOM_VK_F17:
      return "F17";
    case KeyboardEvent.DOM_VK_F18:
      return "F18";
    case KeyboardEvent.DOM_VK_F19:
      return "F19";
    case KeyboardEvent.DOM_VK_F20:
      return "F20";
    case KeyboardEvent.DOM_VK_F21:
      return "F21";
    case KeyboardEvent.DOM_VK_F22:
      return "F22";
    case KeyboardEvent.DOM_VK_F23:
      return "F23";
    case KeyboardEvent.DOM_VK_F24:
      return "F24";
    case KeyboardEvent.DOM_VK_NUM_LOCK:
      return "NumLock";
    case KeyboardEvent.DOM_VK_SCROLL_LOCK:
      return "ScrollLock";
    case KeyboardEvent.DOM_VK_VOLUME_MUTE:
      return "AudioVolumeMute";
    case KeyboardEvent.DOM_VK_VOLUME_DOWN:
      return "AudioVolumeDown";
    case KeyboardEvent.DOM_VK_VOLUME_UP:
      return "AudioVolumeUp";
    case KeyboardEvent.DOM_VK_META:
      return "Meta";
    case KeyboardEvent.DOM_VK_ALTGR:
      return "AltGraph";
    case KeyboardEvent.DOM_VK_PROCESSKEY:
      return "Process";
    case KeyboardEvent.DOM_VK_ATTN:
      return "Attn";
    case KeyboardEvent.DOM_VK_CRSEL:
      return "CrSel";
    case KeyboardEvent.DOM_VK_EXSEL:
      return "ExSel";
    case KeyboardEvent.DOM_VK_EREOF:
      return "EraseEof";
    case KeyboardEvent.DOM_VK_PLAY:
      return "Play";
    default:
      return "Unidentified";
  }
}

function _createKeyboardEventDictionary(
  aKey,
  aKeyEvent,
  aTIP = null,
  aWindow = window
) {
  var result = { dictionary: null, flags: 0 };
  var keyCodeIsDefined = "keyCode" in aKeyEvent;
  var keyCode =
    keyCodeIsDefined && aKeyEvent.keyCode >= 0 && aKeyEvent.keyCode <= 255
      ? aKeyEvent.keyCode
      : 0;
  var keyName = "Unidentified";
  var code = aKeyEvent.code;
  if (!aTIP) {
    aTIP = _getTIP(aWindow);
  }
  if (aKey.indexOf("KEY_") == 0) {
    keyName = aKey.substr("KEY_".length);
    result.flags |= _EU_Ci.nsITextInputProcessor.KEY_NON_PRINTABLE_KEY;
    if (code === undefined) {
      code = aTIP.computeCodeValueOfNonPrintableKey(
        keyName,
        aKeyEvent.location
      );
    }
  } else if (aKey.indexOf("VK_") == 0) {
    keyCode = _getKeyboardEvent(aWindow)["DOM_" + aKey];
    if (!keyCode) {
      throw new Error("Unknown key: " + aKey);
    }
    keyName = _guessKeyNameFromKeyCode(keyCode, aWindow);
    result.flags |= _EU_Ci.nsITextInputProcessor.KEY_NON_PRINTABLE_KEY;
    if (code === undefined) {
      code = aTIP.computeCodeValueOfNonPrintableKey(
        keyName,
        aKeyEvent.location
      );
    }
  } else if (aKey != "") {
    keyName = aKey;
    if (!keyCodeIsDefined) {
      keyCode = aTIP.guessKeyCodeValueOfPrintableKeyInUSEnglishKeyboardLayout(
        aKey,
        aKeyEvent.location
      );
    }
    if (!keyCode) {
      result.flags |= _EU_Ci.nsITextInputProcessor.KEY_KEEP_KEYCODE_ZERO;
    }
    result.flags |= _EU_Ci.nsITextInputProcessor.KEY_FORCE_PRINTABLE_KEY;
    if (code === undefined) {
      code = aTIP.guessCodeValueOfPrintableKeyInUSEnglishKeyboardLayout(
        keyName,
        aKeyEvent.location
      );
    }
  }
  var locationIsDefined = "location" in aKeyEvent;
  if (locationIsDefined && aKeyEvent.location === 0) {
    result.flags |= _EU_Ci.nsITextInputProcessor.KEY_KEEP_KEY_LOCATION_STANDARD;
  }
  if (aKeyEvent.doNotMarkKeydownAsProcessed) {
    result.flags |=
      _EU_Ci.nsITextInputProcessor.KEY_DONT_MARK_KEYDOWN_AS_PROCESSED;
  }
  if (aKeyEvent.markKeyupAsProcessed) {
    result.flags |= _EU_Ci.nsITextInputProcessor.KEY_MARK_KEYUP_AS_PROCESSED;
  }
  result.dictionary = {
    key: keyName,
    code,
    location: locationIsDefined ? aKeyEvent.location : 0,
    repeat: "repeat" in aKeyEvent ? aKeyEvent.repeat === true : false,
    keyCode,
  };
  return result;
}

function _emulateToActivateModifiers(aTIP, aKeyEvent, aWindow = window) {
  if (!aKeyEvent) {
    return null;
  }
  var KeyboardEvent = _getKeyboardEvent(aWindow);

  var modifiers = {
    normal: [
      { key: "Alt", attr: "altKey" },
      { key: "AltGraph", attr: "altGraphKey" },
      { key: "Control", attr: "ctrlKey" },
      { key: "Fn", attr: "fnKey" },
      { key: "Meta", attr: "metaKey" },
      { key: "Shift", attr: "shiftKey" },
      { key: "Symbol", attr: "symbolKey" },
      { key: _EU_isMac(aWindow) ? "Meta" : "Control", attr: "accelKey" },
    ],
    lockable: [
      { key: "CapsLock", attr: "capsLockKey" },
      { key: "FnLock", attr: "fnLockKey" },
      { key: "NumLock", attr: "numLockKey" },
      { key: "ScrollLock", attr: "scrollLockKey" },
      { key: "SymbolLock", attr: "symbolLockKey" },
    ],
  };

  for (let i = 0; i < modifiers.normal.length; i++) {
    if (!aKeyEvent[modifiers.normal[i].attr]) {
      continue;
    }
    if (aTIP.getModifierState(modifiers.normal[i].key)) {
      continue; // already activated.
    }
    let event = new KeyboardEvent("", { key: modifiers.normal[i].key });
    aTIP.keydown(
      event,
      aTIP.KEY_NON_PRINTABLE_KEY | aTIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT
    );
    modifiers.normal[i].activated = true;
  }
  for (let i = 0; i < modifiers.lockable.length; i++) {
    if (!aKeyEvent[modifiers.lockable[i].attr]) {
      continue;
    }
    if (aTIP.getModifierState(modifiers.lockable[i].key)) {
      continue; // already activated.
    }
    let event = new KeyboardEvent("", { key: modifiers.lockable[i].key });
    aTIP.keydown(
      event,
      aTIP.KEY_NON_PRINTABLE_KEY | aTIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT
    );
    aTIP.keyup(
      event,
      aTIP.KEY_NON_PRINTABLE_KEY | aTIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT
    );
    modifiers.lockable[i].activated = true;
  }
  return modifiers;
}

function _emulateToInactivateModifiers(aTIP, aModifiers, aWindow = window) {
  if (!aModifiers) {
    return;
  }
  var KeyboardEvent = _getKeyboardEvent(aWindow);
  for (let i = 0; i < aModifiers.normal.length; i++) {
    if (!aModifiers.normal[i].activated) {
      continue;
    }
    let event = new KeyboardEvent("", { key: aModifiers.normal[i].key });
    aTIP.keyup(
      event,
      aTIP.KEY_NON_PRINTABLE_KEY | aTIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT
    );
  }
  for (let i = 0; i < aModifiers.lockable.length; i++) {
    if (!aModifiers.lockable[i].activated) {
      continue;
    }
    if (!aTIP.getModifierState(aModifiers.lockable[i].key)) {
      continue; // who already inactivated this?
    }
    let event = new KeyboardEvent("", { key: aModifiers.lockable[i].key });
    aTIP.keydown(
      event,
      aTIP.KEY_NON_PRINTABLE_KEY | aTIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT
    );
    aTIP.keyup(
      event,
      aTIP.KEY_NON_PRINTABLE_KEY | aTIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT
    );
  }
}

/**
 * Synthesize a composition event and keydown event and keyup events unless
 * you prevent to dispatch them explicitly (see aEvent.key's explanation).
 *
 * Note that you shouldn't call this with "compositionstart" unless you need to
 * test compositionstart event which is NOT followed by compositionupdate
 * event immediately.  Typically, native IME starts composition with
 * a pair of keydown and keyup event and dispatch compositionstart and
 * compositionupdate (and non-standard text event) between them.  So, in most
 * cases, you should call synthesizeCompositionChange() directly.
 * If you call this with compositionstart, keyup event will be fired
 * immediately after compositionstart.  In other words, you should use
 * "compositionstart" only when you need to emulate IME which just starts
 * composition with compositionstart event but does not send composing text to
 * us until committing the composition.  This is behavior of some Chinese IMEs.
 *
 * @param aEvent               The composition event information.  This must
 *                             have |type| member.  The value must be
 *                             "compositionstart", "compositionend",
 *                             "compositioncommitasis" or "compositioncommit".
 *
 *                             And also this may have |data| and |locale| which
 *                             would be used for the value of each property of
 *                             the composition event.  Note that the |data| is
 *                             ignored if the event type is "compositionstart"
 *                             or "compositioncommitasis".
 *
 *                             If |key| is undefined, "keydown" and "keyup"
 *                             events which are marked as "processed by IME"
 *                             are dispatched.  If |key| is not null, "keydown"
 *                             and/or "keyup" events are dispatched (if the
 *                             |key.type| is specified as "keydown", only
 *                             "keydown" event is dispatched).  Otherwise,
 *                             i.e., if |key| is null, neither "keydown" nor
 *                             "keyup" event is dispatched.
 *
 *                             If |key.doNotMarkKeydownAsProcessed| is not true,
 *                             key value and keyCode value of "keydown" event
 *                             will be set to "Process" and DOM_VK_PROCESSKEY.
 *                             If |key.markKeyupAsProcessed| is true,
 *                             key value and keyCode value of "keyup" event
 *                             will be set to "Process" and DOM_VK_PROCESSKEY.
 * @param aWindow              Optional (If null, current |window| will be used)
 * @param aCallback            Optional (If non-null, use the callback for
 *                             receiving notifications to IME)
 */
function synthesizeComposition(aEvent, aWindow = window, aCallback) {
  var TIP = _getTIP(aWindow, aCallback);
  if (!TIP) {
    return;
  }
  var KeyboardEvent = _getKeyboardEvent(aWindow);
  var modifiers = _emulateToActivateModifiers(TIP, aEvent.key, aWindow);
  var keyEventDict = { dictionary: null, flags: 0 };
  var keyEvent = null;
  if (aEvent.key && typeof aEvent.key.key === "string") {
    keyEventDict = _createKeyboardEventDictionary(
      aEvent.key.key,
      aEvent.key,
      TIP,
      aWindow
    );
    keyEvent = new KeyboardEvent(
      // eslint-disable-next-line no-nested-ternary
      aEvent.key.type === "keydown"
        ? "keydown"
        : aEvent.key.type === "keyup"
        ? "keyup"
        : "",
      keyEventDict.dictionary
    );
  } else if (aEvent.key === undefined) {
    keyEventDict = _createKeyboardEventDictionary(
      "KEY_Process",
      {},
      TIP,
      aWindow
    );
    keyEvent = new KeyboardEvent("", keyEventDict.dictionary);
  }
  try {
    switch (aEvent.type) {
      case "compositionstart":
        TIP.startComposition(keyEvent, keyEventDict.flags);
        break;
      case "compositioncommitasis":
        TIP.commitComposition(keyEvent, keyEventDict.flags);
        break;
      case "compositioncommit":
        TIP.commitCompositionWith(aEvent.data, keyEvent, keyEventDict.flags);
        break;
    }
  } finally {
    _emulateToInactivateModifiers(TIP, modifiers, aWindow);
  }
}
/**
 * Synthesize eCompositionChange event which causes a DOM text event, may
 * cause compositionupdate event, and causes keydown event and keyup event
 * unless you prevent to dispatch them explicitly (see aEvent.key's
 * explanation).
 *
 * Note that if you call this when there is no composition, compositionstart
 * event will be fired automatically.  This is better than you use
 * synthesizeComposition("compositionstart") in most cases.  See the
 * explanation of synthesizeComposition().
 *
 * @param aEvent   The compositionchange event's information, this has
 *                 |composition| and |caret| members.  |composition| has
 *                 |string| and |clauses| members.  |clauses| must be array
 *                 object.  Each object has |length| and |attr|.  And |caret|
 *                 has |start| and |length|.  See the following tree image.
 *
 *                 aEvent
 *                   +-- composition
 *                   |     +-- string
 *                   |     +-- clauses[]
 *                   |           +-- length
 *                   |           +-- attr
 *                   +-- caret
 *                   |     +-- start
 *                   |     +-- length
 *                   +-- key
 *
 *                 Set the composition string to |composition.string|.  Set its
 *                 clauses information to the |clauses| array.
 *
 *                 When it's composing, set the each clauses' length to the
 *                 |composition.clauses[n].length|.  The sum of the all length
 *                 values must be same as the length of |composition.string|.
 *                 Set nsICompositionStringSynthesizer.ATTR_* to the
 *                 |composition.clauses[n].attr|.
 *
 *                 When it's not composing, set 0 to the
 *                 |composition.clauses[0].length| and
 *                 |composition.clauses[0].attr|.
 *
 *                 Set caret position to the |caret.start|. It's offset from
 *                 the start of the composition string.  Set caret length to
 *                 |caret.length|.  If it's larger than 0, it should be wide
 *                 caret.  However, current nsEditor doesn't support wide
 *                 caret, therefore, you should always set 0 now.
 *
 *                 If |key| is undefined, "keydown" and "keyup" events which
 *                 are marked as "processed by IME" are dispatched.  If |key|
 *                 is not null, "keydown" and/or "keyup" events are dispatched
 *                 (if the |key.type| is specified as "keydown", only "keydown"
 *                 event is dispatched).  Otherwise, i.e., if |key| is null,
 *                 neither "keydown" nor "keyup" event is dispatched.
 *                 If |key.doNotMarkKeydownAsProcessed| is not true, key value
 *                 and keyCode value of "keydown" event will be set to
 *                 "Process" and DOM_VK_PROCESSKEY.
 *                 If |key.markKeyupAsProcessed| is true key value and keyCode
 *                 value of "keyup" event will be set to "Process" and
 *                 DOM_VK_PROCESSKEY.
 *
 * @param aWindow  Optional (If null, current |window| will be used)
 * @param aCallback     Optional (If non-null, use the callback for receiving
 *                      notifications to IME)
 */
function synthesizeCompositionChange(aEvent, aWindow = window, aCallback) {
  var TIP = _getTIP(aWindow, aCallback);
  if (!TIP) {
    return;
  }
  var KeyboardEvent = _getKeyboardEvent(aWindow);

  if (
    !aEvent.composition ||
    !aEvent.composition.clauses ||
    !aEvent.composition.clauses[0]
  ) {
    return;
  }

  TIP.setPendingCompositionString(aEvent.composition.string);
  if (aEvent.composition.clauses[0].length) {
    for (var i = 0; i < aEvent.composition.clauses.length; i++) {
      switch (aEvent.composition.clauses[i].attr) {
        case TIP.ATTR_RAW_CLAUSE:
        case TIP.ATTR_SELECTED_RAW_CLAUSE:
        case TIP.ATTR_CONVERTED_CLAUSE:
        case TIP.ATTR_SELECTED_CLAUSE:
          TIP.appendClauseToPendingComposition(
            aEvent.composition.clauses[i].length,
            aEvent.composition.clauses[i].attr
          );
          break;
        case 0:
          // Ignore dummy clause for the argument.
          break;
        default:
          throw new Error("invalid clause attribute specified");
      }
    }
  }

  if (aEvent.caret) {
    TIP.setCaretInPendingComposition(aEvent.caret.start);
  }

  var modifiers = _emulateToActivateModifiers(TIP, aEvent.key, aWindow);
  try {
    var keyEventDict = { dictionary: null, flags: 0 };
    var keyEvent = null;
    if (aEvent.key && typeof aEvent.key.key === "string") {
      keyEventDict = _createKeyboardEventDictionary(
        aEvent.key.key,
        aEvent.key,
        TIP,
        aWindow
      );
      keyEvent = new KeyboardEvent(
        // eslint-disable-next-line no-nested-ternary
        aEvent.key.type === "keydown"
          ? "keydown"
          : aEvent.key.type === "keyup"
          ? "keyup"
          : "",
        keyEventDict.dictionary
      );
    } else if (aEvent.key === undefined) {
      keyEventDict = _createKeyboardEventDictionary(
        "KEY_Process",
        {},
        TIP,
        aWindow
      );
      keyEvent = new KeyboardEvent("", keyEventDict.dictionary);
    }
    TIP.flushPendingComposition(keyEvent, keyEventDict.flags);
  } finally {
    _emulateToInactivateModifiers(TIP, modifiers, aWindow);
  }
}

// Must be synchronized with nsIDOMWindowUtils.
const QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK = 0x0000;
const QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK = 0x0001;

const QUERY_CONTENT_FLAG_SELECTION_NORMAL = 0x0000;
const QUERY_CONTENT_FLAG_SELECTION_SPELLCHECK = 0x0002;
const QUERY_CONTENT_FLAG_SELECTION_IME_RAWINPUT = 0x0004;
const QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDRAWTEXT = 0x0008;
const QUERY_CONTENT_FLAG_SELECTION_IME_CONVERTEDTEXT = 0x0010;
const QUERY_CONTENT_FLAG_SELECTION_IME_SELECTEDCONVERTEDTEXT = 0x0020;
const QUERY_CONTENT_FLAG_SELECTION_ACCESSIBILITY = 0x0040;
const QUERY_CONTENT_FLAG_SELECTION_FIND = 0x0080;
const QUERY_CONTENT_FLAG_SELECTION_URLSECONDARY = 0x0100;
const QUERY_CONTENT_FLAG_SELECTION_URLSTRIKEOUT = 0x0200;

const QUERY_CONTENT_FLAG_OFFSET_RELATIVE_TO_INSERTION_POINT = 0x0400;

const SELECTION_SET_FLAG_USE_NATIVE_LINE_BREAK = 0x0000;
const SELECTION_SET_FLAG_USE_XP_LINE_BREAK = 0x0001;
const SELECTION_SET_FLAG_REVERSE = 0x0002;

/**
 * Synthesize a query text content event.
 *
 * @param aOffset  The character offset.  0 means the first character in the
 *                 selection root.
 * @param aLength  The length of getting text.  If the length is too long,
 *                 the extra length is ignored.
 * @param aIsRelative   Optional (If true, aOffset is relative to start of
 *                      composition if there is, or start of selection.)
 * @param aWindow  Optional (If null, current |window| will be used)
 * @return         An nsIQueryContentEventResult object.  If this failed,
 *                 the result might be null.
 */
function synthesizeQueryTextContent(aOffset, aLength, aIsRelative, aWindow) {
  var utils = _getDOMWindowUtils(aWindow);
  if (!utils) {
    return null;
  }
  var flags = QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK;
  if (aIsRelative === true) {
    flags |= QUERY_CONTENT_FLAG_OFFSET_RELATIVE_TO_INSERTION_POINT;
  }
  return utils.sendQueryContentEvent(
    utils.QUERY_TEXT_CONTENT,
    aOffset,
    aLength,
    0,
    0,
    flags
  );
}

/**
 * Synthesize a query selected text event.
 *
 * @param aSelectionType    Optional, one of QUERY_CONTENT_FLAG_SELECTION_*.
 *                          If null, QUERY_CONTENT_FLAG_SELECTION_NORMAL will
 *                          be used.
 * @param aWindow  Optional (If null, current |window| will be used)
 * @return         An nsIQueryContentEventResult object.  If this failed,
 *                 the result might be null.
 */
function synthesizeQuerySelectedText(aSelectionType, aWindow) {
  var utils = _getDOMWindowUtils(aWindow);
  var flags = QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK;
  if (aSelectionType) {
    flags |= aSelectionType;
  }

  return utils.sendQueryContentEvent(
    utils.QUERY_SELECTED_TEXT,
    0,
    0,
    0,
    0,
    flags
  );
}

/**
 * Synthesize a query caret rect event.
 *
 * @param aOffset  The caret offset.  0 means left side of the first character
 *                 in the selection root.
 * @param aWindow  Optional (If null, current |window| will be used)
 * @return         An nsIQueryContentEventResult object.  If this failed,
 *                 the result might be null.
 */
function synthesizeQueryCaretRect(aOffset, aWindow) {
  var utils = _getDOMWindowUtils(aWindow);
  if (!utils) {
    return null;
  }
  return utils.sendQueryContentEvent(
    utils.QUERY_CARET_RECT,
    aOffset,
    0,
    0,
    0,
    QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK
  );
}

/**
 * Synthesize a selection set event.
 *
 * @param aOffset  The character offset.  0 means the first character in the
 *                 selection root.
 * @param aLength  The length of the text.  If the length is too long,
 *                 the extra length is ignored.
 * @param aReverse If true, the selection is from |aOffset + aLength| to
 *                 |aOffset|.  Otherwise, from |aOffset| to |aOffset + aLength|.
 * @param aWindow  Optional (If null, current |window| will be used)
 * @return         True, if succeeded.  Otherwise false.
 */
async function synthesizeSelectionSet(
  aOffset,
  aLength,
  aReverse,
  aWindow = window
) {
  const utils = _getDOMWindowUtils(aWindow);
  if (!utils) {
    return false;
  }
  // eSetSelection event will be compared with selection cache in
  // IMEContentObserver, but it may have not been updated yet.  Therefore, we
  // need to flush pending things of IMEContentObserver.
  await new Promise(resolve =>
    aWindow.requestAnimationFrame(() => aWindow.requestAnimationFrame(resolve))
  );
  const flags = aReverse ? SELECTION_SET_FLAG_REVERSE : 0;
  return utils.sendSelectionSetEvent(aOffset, aLength, flags);
}

/**
 * Synthesize a query text rect event.
 *
 * @param aOffset  The character offset.  0 means the first character in the
 *                 selection root.
 * @param aLength  The length of the text.  If the length is too long,
 *                 the extra length is ignored.
 * @param aIsRelative   Optional (If true, aOffset is relative to start of
 *                      composition if there is, or start of selection.)
 * @param aWindow  Optional (If null, current |window| will be used)
 * @return         An nsIQueryContentEventResult object.  If this failed,
 *                 the result might be null.
 */
function synthesizeQueryTextRect(aOffset, aLength, aIsRelative, aWindow) {
  if (aIsRelative !== undefined && typeof aIsRelative !== "boolean") {
    throw new Error(
      "Maybe, you set Window object to the 3rd argument, but it should be a boolean value"
    );
  }
  var utils = _getDOMWindowUtils(aWindow);
  let flags = QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK;
  if (aIsRelative === true) {
    flags |= QUERY_CONTENT_FLAG_OFFSET_RELATIVE_TO_INSERTION_POINT;
  }
  return utils.sendQueryContentEvent(
    utils.QUERY_TEXT_RECT,
    aOffset,
    aLength,
    0,
    0,
    flags
  );
}

/**
 * Synthesize a query text rect array event.
 *
 * @param aOffset  The character offset.  0 means the first character in the
 *                 selection root.
 * @param aLength  The length of the text.  If the length is too long,
 *                 the extra length is ignored.
 * @param aWindow  Optional (If null, current |window| will be used)
 * @return         An nsIQueryContentEventResult object.  If this failed,
 *                 the result might be null.
 */
function synthesizeQueryTextRectArray(aOffset, aLength, aWindow) {
  var utils = _getDOMWindowUtils(aWindow);
  return utils.sendQueryContentEvent(
    utils.QUERY_TEXT_RECT_ARRAY,
    aOffset,
    aLength,
    0,
    0,
    QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK
  );
}

/**
 * Synthesize a query editor rect event.
 *
 * @param aWindow  Optional (If null, current |window| will be used)
 * @return         An nsIQueryContentEventResult object.  If this failed,
 *                 the result might be null.
 */
function synthesizeQueryEditorRect(aWindow) {
  var utils = _getDOMWindowUtils(aWindow);
  return utils.sendQueryContentEvent(
    utils.QUERY_EDITOR_RECT,
    0,
    0,
    0,
    0,
    QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK
  );
}

/**
 * Synthesize a character at point event.
 *
 * @param aX, aY   The offset in the client area of the DOM window.
 * @param aWindow  Optional (If null, current |window| will be used)
 * @return         An nsIQueryContentEventResult object.  If this failed,
 *                 the result might be null.
 */
function synthesizeCharAtPoint(aX, aY, aWindow) {
  var utils = _getDOMWindowUtils(aWindow);
  return utils.sendQueryContentEvent(
    utils.QUERY_CHARACTER_AT_POINT,
    0,
    0,
    aX,
    aY,
    QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK
  );
}

/**
 * INTERNAL USE ONLY
 * Create an event object to pass to sendDragEvent.
 *
 * @param aType          The string represents drag event type.
 * @param aDestElement   The element to fire the drag event, used to calculate
 *                       screenX/Y and clientX/Y.
 * @param aDestWindow    Optional; Defaults to the current window object.
 * @param aDataTransfer  dataTransfer for current drag session.
 * @param aDragEvent     The object contains properties to override the event
 *                       object
 * @return               An object to pass to sendDragEvent.
 */
function createDragEventObject(
  aType,
  aDestElement,
  aDestWindow,
  aDataTransfer,
  aDragEvent
) {
  var destRect = aDestElement.getBoundingClientRect();
  var destClientX = destRect.left + destRect.width / 2;
  var destClientY = destRect.top + destRect.height / 2;
  var destScreenX = aDestWindow.mozInnerScreenX + destClientX;
  var destScreenY = aDestWindow.mozInnerScreenY + destClientY;
  if ("clientX" in aDragEvent && !("screenX" in aDragEvent)) {
    destScreenX = aDestWindow.mozInnerScreenX + aDragEvent.clientX;
  }
  if ("clientY" in aDragEvent && !("screenY" in aDragEvent)) {
    destScreenY = aDestWindow.mozInnerScreenY + aDragEvent.clientY;
  }

  // Wrap only in plain mochitests
  let dataTransfer;
  if (aDataTransfer) {
    dataTransfer = _EU_maybeUnwrap(
      _EU_maybeWrap(aDataTransfer).mozCloneForEvent(aType)
    );

    // Copy over the drop effect. This isn't copied over by Clone, as it uses
    // more complex logic in the actual implementation (see
    // nsContentUtils::SetDataTransferInEvent for actual impl).
    dataTransfer.dropEffect = aDataTransfer.dropEffect;
  }

  return Object.assign(
    {
      type: aType,
      screenX: destScreenX,
      screenY: destScreenY,
      clientX: destClientX,
      clientY: destClientY,
      dataTransfer,
      _domDispatchOnly: aDragEvent._domDispatchOnly,
    },
    aDragEvent
  );
}

/**
 * Emulate a event sequence of dragstart, dragenter, and dragover.
 *
 * @param {Element} aSrcElement
 *        The element to use to start the drag.
 * @param {Element} aDestElement
 *        The element to fire the dragover, dragenter events
 * @param {Array}   aDragData
 *        The data to supply for the data transfer.
 *        This data is in the format:
 *
 *        [
 *          [
 *            {"type": value, "data": value },
 *            ...,
 *          ],
 *          ...
 *        ]
 *
 *        Pass null to avoid modifying dataTransfer.
 * @param {String} [aDropEffect="move"]
 *        The drop effect to set during the dragstart event, or 'move' if omitted.
 * @param {Window} [aWindow=window]
 *        The window in which the drag happens. Defaults to the window in which
 *        EventUtils.js is loaded.
 * @param {Window} [aDestWindow=aWindow]
 *        Used when aDestElement is in a different window than aSrcElement.
 *        Default is to match ``aWindow``.
 * @param {Object} [aDragEvent={}]
 *        Defaults to empty object. Overwrites an object passed to sendDragEvent.
 * @return {Array}
 *        A two element array, where the first element is the value returned
 *        from sendDragEvent for dragover event, and the second element is the
 *        dataTransfer for the current drag session.
 */
function synthesizeDragOver(
  aSrcElement,
  aDestElement,
  aDragData,
  aDropEffect,
  aWindow,
  aDestWindow,
  aDragEvent = {}
) {
  if (!aWindow) {
    aWindow = window;
  }
  if (!aDestWindow) {
    aDestWindow = aWindow;
  }

  // eslint-disable-next-line mozilla/use-services
  const obs = _EU_Cc["@mozilla.org/observer-service;1"].getService(
    _EU_Ci.nsIObserverService
  );
  let utils = _getDOMWindowUtils(aWindow);
  var sess = utils.dragSession;

  // This method runs before other callbacks, and acts as a way to inject the
  // initial drag data into the DataTransfer.
  function fillDrag(event) {
    if (aDragData) {
      for (var i = 0; i < aDragData.length; i++) {
        var item = aDragData[i];
        for (var j = 0; j < item.length; j++) {
          _EU_maybeWrap(event.dataTransfer).mozSetDataAt(
            item[j].type,
            item[j].data,
            i
          );
        }
      }
    }
    event.dataTransfer.dropEffect = aDropEffect || "move";
    event.preventDefault();
  }

  function trapDrag(subject, topic) {
    if (topic == "on-datatransfer-available") {
      sess.dataTransfer = _EU_maybeUnwrap(
        _EU_maybeWrap(subject).mozCloneForEvent("drop")
      );
      sess.dataTransfer.dropEffect = subject.dropEffect;
    }
  }

  // need to use real mouse action
  aWindow.addEventListener("dragstart", fillDrag, true);
  obs.addObserver(trapDrag, "on-datatransfer-available");
  synthesizeMouseAtCenter(aSrcElement, { type: "mousedown" }, aWindow);

  var rect = aSrcElement.getBoundingClientRect();
  var x = rect.width / 2;
  var y = rect.height / 2;
  synthesizeMouse(aSrcElement, x, y, { type: "mousemove" }, aWindow);
  synthesizeMouse(aSrcElement, x + 10, y + 10, { type: "mousemove" }, aWindow);
  aWindow.removeEventListener("dragstart", fillDrag, true);
  obs.removeObserver(trapDrag, "on-datatransfer-available");

  var dataTransfer = sess.dataTransfer;
  if (!dataTransfer) {
    throw new Error("No data transfer object after synthesizing the mouse!");
  }

  // The EventStateManager will fire our dragenter event if it needs to.
  var event = createDragEventObject(
    "dragover",
    aDestElement,
    aDestWindow,
    dataTransfer,
    aDragEvent
  );
  var result = sendDragEvent(event, aDestElement, aDestWindow);

  return [result, dataTransfer];
}

/**
 * Emulate the drop event and mouseup event.
 * This should be called after synthesizeDragOver.
 *
 * @param {*} aResult
 *        The first element of the array returned from ``synthesizeDragOver``.
 * @param {DataTransfer} aDataTransfer
 *        The second element of the array returned from ``synthesizeDragOver``.
 * @param {Element} aDestElement
 *        The element on which to fire the drop event.
 * @param {Window} [aDestWindow=window]
 *        The window in which the drop happens. Defaults to the window in which
 *        EventUtils.js is loaded.
 * @param {Object} [aDragEvent={}]
 *        Defaults to empty object. Overwrites an object passed to sendDragEvent.
 * @return {String}
 *        "none" if aResult is true, ``aDataTransfer.dropEffect`` otherwise.
 */
function synthesizeDropAfterDragOver(
  aResult,
  aDataTransfer,
  aDestElement,
  aDestWindow,
  aDragEvent = {}
) {
  if (!aDestWindow) {
    aDestWindow = window;
  }

  var effect = aDataTransfer.dropEffect;
  var event;

  if (aResult) {
    effect = "none";
  } else if (effect != "none") {
    event = createDragEventObject(
      "drop",
      aDestElement,
      aDestWindow,
      aDataTransfer,
      aDragEvent
    );
    sendDragEvent(event, aDestElement, aDestWindow);
  }
  // Don't run accessibility checks for this click, since we're not actually
  // clicking. It's just generated as part of the drop.
  // this.AccessibilityUtils might not be set if this isn't a browser test or
  // if a browser test has loaded its own copy of EventUtils for some reason.
  // In the latter case, the test probably shouldn't do that.
  this.AccessibilityUtils?.suppressClickHandling(true);
  synthesizeMouse(aDestElement, 2, 2, { type: "mouseup" }, aDestWindow);
  this.AccessibilityUtils?.suppressClickHandling(false);

  return effect;
}

/**
 * Emulate a drag and drop by emulating a dragstart and firing events dragenter,
 * dragover, and drop.
 *
 * @param {Element} aSrcElement
 *        The element to use to start the drag.
 * @param {Element} aDestElement
 *        The element to fire the dragover, dragenter events
 * @param {Array}   aDragData
 *        The data to supply for the data transfer.
 *        This data is in the format:
 *
 *            [
 *              [
 *                {"type": value, "data": value },
 *                ...,
 *              ],
 *              ...
 *            ]
 *
 *        Pass null to avoid modifying dataTransfer.
 * @param {String} [aDropEffect="move"]
 *        The drop effect to set during the dragstart event, or 'move' if omitted..
 * @param {Window} [aWindow=window]
 *        The window in which the drag happens. Defaults to the window in which
 *        EventUtils.js is loaded.
 * @param {Window} [aDestWindow=aWindow]
 *        Used when aDestElement is in a different window than aSrcElement.
 *        Default is to match ``aWindow``.
 * @param {Object} [aDragEvent={}]
 *        Defaults to empty object. Overwrites an object passed to sendDragEvent.
 * @return {String}
 *        The drop effect that was desired.
 */
function synthesizeDrop(
  aSrcElement,
  aDestElement,
  aDragData,
  aDropEffect,
  aWindow,
  aDestWindow,
  aDragEvent = {}
) {
  if (!aWindow) {
    aWindow = window;
  }
  if (!aDestWindow) {
    aDestWindow = aWindow;
  }

  var ds = _EU_Cc["@mozilla.org/widget/dragservice;1"].getService(
    _EU_Ci.nsIDragService
  );

  let dropAction;
  switch (aDropEffect) {
    case null:
    case undefined:
    case "move":
      dropAction = _EU_Ci.nsIDragService.DRAGDROP_ACTION_MOVE;
      break;
    case "copy":
      dropAction = _EU_Ci.nsIDragService.DRAGDROP_ACTION_COPY;
      break;
    case "link":
      dropAction = _EU_Ci.nsIDragService.DRAGDROP_ACTION_LINK;
      break;
    default:
      throw new Error(`${aDropEffect} is an invalid drop effect value`);
  }

  ds.startDragSessionForTests(aWindow, dropAction);

  try {
    var [result, dataTransfer] = synthesizeDragOver(
      aSrcElement,
      aDestElement,
      aDragData,
      aDropEffect,
      aWindow,
      aDestWindow,
      aDragEvent
    );
    return synthesizeDropAfterDragOver(
      result,
      dataTransfer,
      aDestElement,
      aDestWindow,
      aDragEvent
    );
  } finally {
    let srcWindowUtils = _getDOMWindowUtils(aWindow);
    const srcDragSession = srcWindowUtils.dragSession;
    srcDragSession.endDragSession(true, _parseModifiers(aDragEvent));
  }
}

function _getFlattenedTreeParentNode(aNode) {
  return _EU_maybeUnwrap(_EU_maybeWrap(aNode).flattenedTreeParentNode);
}

function _getInclusiveFlattenedTreeParentElement(aNode) {
  for (
    let inclusiveAncestor = aNode;
    inclusiveAncestor;
    inclusiveAncestor = _getFlattenedTreeParentNode(inclusiveAncestor)
  ) {
    if (inclusiveAncestor.nodeType == Node.ELEMENT_NODE) {
      return inclusiveAncestor;
    }
  }
  return null;
}

function _nodeIsFlattenedTreeDescendantOf(
  aPossibleDescendant,
  aPossibleAncestor
) {
  do {
    if (aPossibleDescendant == aPossibleAncestor) {
      return true;
    }
    aPossibleDescendant = _getFlattenedTreeParentNode(aPossibleDescendant);
  } while (aPossibleDescendant);
  return false;
}

function _computeSrcElementFromSrcSelection(aSrcSelection) {
  let srcElement = aSrcSelection.focusNode;
  while (_EU_maybeWrap(srcElement).isNativeAnonymous) {
    srcElement = _getFlattenedTreeParentNode(srcElement);
  }
  if (srcElement.nodeType !== Node.ELEMENT_NODE) {
    srcElement = _getInclusiveFlattenedTreeParentElement(srcElement);
  }
  return srcElement;
}

/**
 * Emulate a drag and drop by emulating a dragstart by mousedown and mousemove,
 * and firing events dragenter, dragover, drop, and dragend.
 * This does not modify dataTransfer and tries to emulate the plain drag and
 * drop as much as possible, compared to synthesizeDrop.
 * Note that if synthesized dragstart is canceled, this throws an exception
 * because in such case, Gecko does not start drag session.
 *
 * @param {Object} aParams
 * @param {Event} aParams.dragEvent
 *                The DnD events will be generated with modifiers specified with this.
 * @param {Element} aParams.srcElement
 *                The element to start dragging.  If srcSelection is
 *                set, this is computed for element at focus node.
 * @param {Selection|nil} aParams.srcSelection
 *                The selection to start to drag, set null if srcElement is set.
 * @param {Element|nil} aParams.destElement
 *                The element to drop on. Pass null to emulate a drop on an invalid target.
 * @param {Number} aParams.srcX
 *                The initial x coordinate inside srcElement or ignored if srcSelection is set.
 * @param {Number} aParams.srcY
 *                The initial y coordinate inside srcElement or ignored if srcSelection is set.
 * @param {Number} aParams.stepX
 *                The x-axis step for mousemove inside srcElement
 * @param {Number} aParams.stepY
 *                The y-axis step for mousemove inside srcElement
 * @param {Number} aParams.finalX
 *                The final x coordinate inside srcElement
 * @param {Number} aParams.finalY
 *                The final x coordinate inside srcElement
 * @param {Any} aParams.id
 *                The pointer event id
 * @param {Window} aParams.srcWindow
 *                The window for dispatching event on srcElement, defaults to the current window object.
 * @param {Window} aParams.destWindow
 *                The window for dispatching event on destElement, defaults to the current window object.
 * @param {Boolean} aParams.expectCancelDragStart
 *                Set to true if the test cancels "dragstart"
 * @param {Boolean} aParams.expectSrcElementDisconnected
 *                Set to true if srcElement will be disconnected and
 *                "dragend" event won't be fired.
 * @param {Function} aParams.logFunc
 *                Set function which takes one argument if you need to log rect of target.  E.g., `console.log`.
 */
// eslint-disable-next-line complexity
async function synthesizePlainDragAndDrop(aParams) {
  let {
    dragEvent = {},
    srcElement,
    srcSelection,
    destElement,
    srcX = 2,
    srcY = 2,
    stepX = 9,
    stepY = 9,
    finalX = srcX + stepX * 2,
    finalY = srcY + stepY * 2,
    id = _getDOMWindowUtils(window).DEFAULT_MOUSE_POINTER_ID,
    srcWindow = window,
    destWindow = window,
    expectCancelDragStart = false,
    expectSrcElementDisconnected = false,
    logFunc,
  } = aParams;
  // Don't modify given dragEvent object because we modify dragEvent below and
  // callers may use the object multiple times so that callers must not assume
  // that it'll be modified.
  if (aParams.dragEvent !== undefined) {
    dragEvent = Object.assign({}, aParams.dragEvent);
  }

  function rectToString(aRect) {
    return `left: ${aRect.left}, top: ${aRect.top}, right: ${aRect.right}, bottom: ${aRect.bottom}`;
  }

  let srcWindowUtils = _getDOMWindowUtils(srcWindow);
  let destWindowUtils = _getDOMWindowUtils(destWindow);

  if (logFunc) {
    logFunc("synthesizePlainDragAndDrop() -- START");
  }

  if (srcSelection) {
    srcElement = _computeSrcElementFromSrcSelection(srcSelection);
    let srcElementRect = srcElement.getBoundingClientRect();
    if (logFunc) {
      logFunc(
        `srcElement.getBoundingClientRect(): ${rectToString(srcElementRect)}`
      );
    }
    // Use last selection client rect because nsIDragSession.sourceNode is
    // initialized from focus node which is usually in last rect.
    let selectionRectList = srcSelection.getRangeAt(0).getClientRects();
    let lastSelectionRect = selectionRectList[selectionRectList.length - 1];
    if (logFunc) {
      logFunc(
        `srcSelection.getRangeAt(0).getClientRects()[${
          selectionRectList.length - 1
        }]: ${rectToString(lastSelectionRect)}`
      );
    }
    // Click at center of last selection rect.
    srcX = Math.floor(lastSelectionRect.left + lastSelectionRect.width / 2);
    srcY = Math.floor(lastSelectionRect.top + lastSelectionRect.height / 2);
    // Then, adjust srcX and srcY for making them offset relative to
    // srcElementRect because they will be used when we call synthesizeMouse()
    // with srcElement.
    srcX = Math.floor(srcX - srcElementRect.left);
    srcY = Math.floor(srcY - srcElementRect.top);
    // Finally, recalculate finalX and finalY with new srcX and srcY if they
    // are not specified by the caller.
    if (aParams.finalX === undefined) {
      finalX = srcX + stepX * 2;
    }
    if (aParams.finalY === undefined) {
      finalY = srcY + stepY * 2;
    }
  } else if (logFunc) {
    logFunc(
      `srcElement.getBoundingClientRect(): ${rectToString(
        srcElement.getBoundingClientRect()
      )}`
    );
  }

  const editingHost = (() => {
    if (!srcElement.matches(":read-write")) {
      return null;
    }
    let lastEditableElement = srcElement;
    for (
      let inclusiveAncestor =
        _getInclusiveFlattenedTreeParentElement(srcElement);
      inclusiveAncestor;
      inclusiveAncestor = _getInclusiveFlattenedTreeParentElement(
        _getFlattenedTreeParentNode(inclusiveAncestor)
      )
    ) {
      if (inclusiveAncestor.matches(":read-write")) {
        lastEditableElement = inclusiveAncestor;
        if (lastEditableElement == srcElement.ownerDocument.body) {
          break;
        }
      }
    }
    return lastEditableElement;
  })();
  try {
    srcWindowUtils.disableNonTestMouseEvents(true);

    await new Promise(r => setTimeout(r, 0));

    let mouseDownEvent;
    function onMouseDown(aEvent) {
      mouseDownEvent = aEvent;
      if (logFunc) {
        logFunc(
          `"${aEvent.type}" event is fired on ${
            aEvent.target
          } (composedTarget: ${_EU_maybeUnwrap(
            _EU_maybeWrap(aEvent).composedTarget
          )}`
        );
      }
      if (
        !_nodeIsFlattenedTreeDescendantOf(
          _EU_maybeUnwrap(_EU_maybeWrap(aEvent).composedTarget),
          srcElement
        )
      ) {
        // If srcX and srcY does not point in one of rects in srcElement,
        // "mousedown" target is not in srcElement.  Such case must not
        // be expected by this API users so that we should throw an exception
        // for making debugging easier.
        throw new Error(
          'event target of "mousedown" is not srcElement nor its descendant'
        );
      }
    }
    try {
      srcWindow.addEventListener("mousedown", onMouseDown, { capture: true });
      synthesizeMouse(
        srcElement,
        srcX,
        srcY,
        { type: "mousedown", id },
        srcWindow
      );
      if (logFunc) {
        logFunc(`mousedown at ${srcX}, ${srcY}`);
      }
      if (!mouseDownEvent) {
        throw new Error('"mousedown" event is not fired');
      }
    } finally {
      srcWindow.removeEventListener("mousedown", onMouseDown, {
        capture: true,
      });
    }

    let dragStartEvent;
    function onDragStart(aEvent) {
      dragStartEvent = aEvent;
      if (logFunc) {
        logFunc(`"${aEvent.type}" event is fired`);
      }
      if (
        !_nodeIsFlattenedTreeDescendantOf(
          _EU_maybeUnwrap(_EU_maybeWrap(aEvent).composedTarget),
          srcElement
        )
      ) {
        // If srcX and srcY does not point in one of rects in srcElement,
        // "dragstart" target is not in srcElement.  Such case must not
        // be expected by this API users so that we should throw an exception
        // for making debugging easier.
        throw new Error(
          'event target of "dragstart" is not srcElement nor its descendant'
        );
      }
    }
    let dragEnterEvent;
    function onDragEnterGenerated(aEvent) {
      dragEnterEvent = aEvent;
    }
    srcWindow.addEventListener("dragstart", onDragStart, { capture: true });
    srcWindow.addEventListener("dragenter", onDragEnterGenerated, {
      capture: true,
    });
    try {
      // Wait for the next event tick after each event dispatch, so that UI
      // elements (e.g. menu) work like the real user input.
      await new Promise(r => setTimeout(r, 0));

      srcX += stepX;
      srcY += stepY;
      synthesizeMouse(
        srcElement,
        srcX,
        srcY,
        { type: "mousemove", id },
        srcWindow
      );
      if (logFunc) {
        logFunc(`first mousemove at ${srcX}, ${srcY}`);
      }

      await new Promise(r => setTimeout(r, 0));

      srcX += stepX;
      srcY += stepY;
      synthesizeMouse(
        srcElement,
        srcX,
        srcY,
        { type: "mousemove", id },
        srcWindow
      );
      if (logFunc) {
        logFunc(`second mousemove at ${srcX}, ${srcY}`);
      }

      await new Promise(r => setTimeout(r, 0));

      if (!dragStartEvent) {
        throw new Error('"dragstart" event is not fired');
      }
    } finally {
      srcWindow.removeEventListener("dragstart", onDragStart, {
        capture: true,
      });
      srcWindow.removeEventListener("dragenter", onDragEnterGenerated, {
        capture: true,
      });
    }

    let srcSession = srcWindowUtils.dragSession;
    if (!srcSession) {
      if (expectCancelDragStart) {
        synthesizeMouse(
          srcElement,
          finalX,
          finalY,
          { type: "mouseup", id },
          srcWindow
        );
        return;
      }
      throw new Error("drag hasn't been started by the operation");
    } else if (expectCancelDragStart) {
      throw new Error("drag has been started by the operation");
    }

    if (destElement) {
      if (
        (srcElement != destElement && !dragEnterEvent) ||
        destElement != dragEnterEvent.target
      ) {
        if (logFunc) {
          logFunc(
            `destElement.getBoundingClientRect(): ${rectToString(
              destElement.getBoundingClientRect()
            )}`
          );
        }

        function onDragEnter(aEvent) {
          dragEnterEvent = aEvent;
          if (logFunc) {
            logFunc(`"${aEvent.type}" event is fired`);
          }
          if (aEvent.target != destElement) {
            throw new Error('event target of "dragenter" is not destElement');
          }
        }
        destWindow.addEventListener("dragenter", onDragEnter, {
          capture: true,
        });
        try {
          let event = createDragEventObject(
            "dragenter",
            destElement,
            destWindow,
            null,
            dragEvent
          );
          sendDragEvent(event, destElement, destWindow);
          if (!dragEnterEvent && !destElement.disabled) {
            throw new Error('"dragenter" event is not fired');
          }
          if (dragEnterEvent && destElement.disabled) {
            throw new Error(
              '"dragenter" event should not be fired on disable element'
            );
          }
        } finally {
          destWindow.removeEventListener("dragenter", onDragEnter, {
            capture: true,
          });
        }
      }

      let dragOverEvent;
      function onDragOver(aEvent) {
        dragOverEvent = aEvent;
        if (logFunc) {
          logFunc(`"${aEvent.type}" event is fired`);
        }
        if (aEvent.target != destElement) {
          throw new Error('event target of "dragover" is not destElement');
        }
      }
      destWindow.addEventListener("dragover", onDragOver, { capture: true });
      try {
        // dragover and drop are only fired to a valid drop target. If the
        // destElement parameter is null, this function is being used to
        // simulate a drag'n'drop over an invalid drop target.
        let event = createDragEventObject(
          "dragover",
          destElement,
          destWindow,
          null,
          dragEvent
        );
        sendDragEvent(event, destElement, destWindow);
        if (!dragOverEvent && !destElement.disabled) {
          throw new Error('"dragover" event is not fired');
        }
        if (dragEnterEvent && destElement.disabled) {
          throw new Error(
            '"dragover" event should not be fired on disable element'
          );
        }
      } finally {
        destWindow.removeEventListener("dragover", onDragOver, {
          capture: true,
        });
      }

      await new Promise(r => setTimeout(r, 0));

      // If there is not accept to drop the data, "drop" event shouldn't be
      // fired.
      // XXX nsIDragSession.canDrop is different only on Linux.  It must be
      //     a bug of gtk/nsDragService since it manages `mCanDrop` by itself.
      //     Thus, we should use nsIDragSession.dragAction instead.
      let destSession = destWindowUtils.dragSession;
      if (
        destSession.dragAction != _EU_Ci.nsIDragService.DRAGDROP_ACTION_NONE
      ) {
        let dropEvent;
        function onDrop(aEvent) {
          dropEvent = aEvent;
          if (logFunc) {
            logFunc(`"${aEvent.type}" event is fired`);
          }
          if (
            !_nodeIsFlattenedTreeDescendantOf(
              _EU_maybeUnwrap(_EU_maybeWrap(aEvent).composedTarget),
              destElement
            )
          ) {
            throw new Error(
              'event target of "drop" is not destElement nor its descendant'
            );
          }
        }
        destWindow.addEventListener("drop", onDrop, { capture: true });
        try {
          let event = createDragEventObject(
            "drop",
            destElement,
            destWindow,
            null,
            dragEvent
          );
          sendDragEvent(event, destElement, destWindow);
          if (!dropEvent && destSession.canDrop) {
            throw new Error('"drop" event is not fired');
          }
        } finally {
          destWindow.removeEventListener("drop", onDrop, { capture: true });
        }
        return;
      }
    }

    // Since we don't synthesize drop event, we need to set drag end point
    // explicitly for "dragEnd" event which will be fired by
    // endDragSession().
    dragEvent.clientX = srcElement.getBoundingClientRect().x + finalX;
    dragEvent.clientY = srcElement.getBoundingClientRect().y + finalY;
    let event = createDragEventObject(
      "dragend",
      srcElement,
      srcWindow,
      null,
      dragEvent
    );
    srcSession.setDragEndPointForTests(event.screenX, event.screenY);
  } finally {
    await new Promise(r => setTimeout(r, 0));

    if (srcWindowUtils.dragSession) {
      const sourceNode = srcWindowUtils.dragSession.sourceNode;
      let dragEndEvent;
      function onDragEnd(aEvent) {
        dragEndEvent = aEvent;
        if (logFunc) {
          logFunc(`"${aEvent.type}" event is fired`);
        }
        if (
          !_nodeIsFlattenedTreeDescendantOf(
            _EU_maybeUnwrap(_EU_maybeWrap(aEvent).composedTarget),
            srcElement
          ) &&
          _EU_maybeUnwrap(_EU_maybeWrap(aEvent).composedTarget) != editingHost
        ) {
          throw new Error(
            'event target of "dragend" is not srcElement nor its descendant'
          );
        }
        if (expectSrcElementDisconnected) {
          throw new Error(
            `"dragend" event shouldn't be fired when the source node is disconnected (the source node is ${
              sourceNode?.isConnected ? "connected" : "null or disconnected"
            })`
          );
        }
      }
      srcWindow.addEventListener("dragend", onDragEnd, { capture: true });
      try {
        srcWindowUtils.dragSession.endDragSession(
          true,
          _parseModifiers(dragEvent)
        );
        if (!expectSrcElementDisconnected && !dragEndEvent) {
          // eslint-disable-next-line no-unsafe-finally
          throw new Error(
            `"dragend" event is not fired by nsIDragSession.endDragSession()${
              srcWindowUtils.dragSession.sourceNode &&
              !srcWindowUtils.dragSession.sourceNode.isConnected
                ? "(sourceNode was disconnected)"
                : ""
            }`
          );
        }
      } finally {
        srcWindow.removeEventListener("dragend", onDragEnd, { capture: true });
      }
    }
    srcWindowUtils.disableNonTestMouseEvents(false);
    if (logFunc) {
      logFunc("synthesizePlainDragAndDrop() -- END");
    }
  }
}

function _checkDataTransferItems(aDataTransfer, aExpectedDragData) {
  try {
    // We must wrap only in plain mochitests, not chrome
    let dataTransfer = _EU_maybeWrap(aDataTransfer);
    if (!dataTransfer) {
      return null;
    }
    if (
      aExpectedDragData == null ||
      dataTransfer.mozItemCount != aExpectedDragData.length
    ) {
      return dataTransfer;
    }
    for (let i = 0; i < dataTransfer.mozItemCount; i++) {
      let dtTypes = dataTransfer.mozTypesAt(i);
      if (dtTypes.length != aExpectedDragData[i].length) {
        return dataTransfer;
      }
      for (let j = 0; j < dtTypes.length; j++) {
        if (dtTypes[j] != aExpectedDragData[i][j].type) {
          return dataTransfer;
        }
        let dtData = dataTransfer.mozGetDataAt(dtTypes[j], i);
        if (aExpectedDragData[i][j].eqTest) {
          if (
            !aExpectedDragData[i][j].eqTest(
              dtData,
              aExpectedDragData[i][j].data
            )
          ) {
            return dataTransfer;
          }
        } else if (aExpectedDragData[i][j].data != dtData) {
          return dataTransfer;
        }
      }
    }
  } catch (ex) {
    return ex;
  }
  return true;
}

/**
 * This callback type is used with ``synthesizePlainDragAndCancel()``.
 * It should compare ``actualData`` and ``expectedData`` and return
 * true if the two should be considered equal, false otherwise.
 *
 * @callback eqTest
 * @param {*} actualData
 * @param {*} expectedData
 * @return {boolean}
 */

/**
 * synthesizePlainDragAndCancel() synthesizes drag start with
 * synthesizePlainDragAndDrop(), but always cancel it with preventing default
 * of "dragstart".  Additionally, this checks whether the dataTransfer of
 * "dragstart" event has only expected items.
 *
 * @param {Object} aParams
 *        The params which is set to the argument of ``synthesizePlainDragAndDrop()``.
 * @param {Array} aExpectedDataTransferItems
 *        All expected dataTransfer items.
 *        This data is in the format:
 *
 *        [
 *          [
 *            {"type": value, "data": value, eqTest: function}
 *            ...,
 *          ],
 *          ...
 *        ]
 *
 *        This can also be null.
 *        You can optionally provide ``eqTest`` {@type eqTest} if the
 *        comparison to the expected data transfer items can't be done
 *        with x == y;
 * @return {boolean}
 *        true if aExpectedDataTransferItems matches with
 *        DragEvent.dataTransfer of "dragstart" event.
 *        Otherwise, the dataTransfer object (may be null) or
 *        thrown exception, NOT false.  Therefore, you shouldn't
 *        use.
 */
async function synthesizePlainDragAndCancel(
  aParams,
  aExpectedDataTransferItems
) {
  let srcElement = aParams.srcSelection
    ? _computeSrcElementFromSrcSelection(aParams.srcSelection)
    : aParams.srcElement;
  let result;
  function onDragStart(aEvent) {
    aEvent.preventDefault();
    result = _checkDataTransferItems(
      aEvent.dataTransfer,
      aExpectedDataTransferItems
    );
  }
  SpecialPowers.wrap(srcElement.ownerDocument).addEventListener(
    "dragstart",
    onDragStart,
    { capture: true, mozSystemGroup: true }
  );
  try {
    aParams.expectCancelDragStart = true;
    await synthesizePlainDragAndDrop(aParams);
  } finally {
    SpecialPowers.wrap(srcElement.ownerDocument).removeEventListener(
      "dragstart",
      onDragStart,
      { capture: true, mozSystemGroup: true }
    );
  }
  return result;
}

/**
 * Emulate a drag and drop by generating a dragstart from mousedown and mousemove,
 * then firing events dragover and drop (or dragleave if expectDragLeave is set).
 * This does not modify dataTransfer and tries to emulate the plain drag and
 * drop as much as possible, compared to synthesizeDrop and
 * synthesizePlainDragAndDrop.  MockDragService is used in place of the native
 * nsIDragService implementation.  All coordinates are in client space.
 *
 * @param {Object} aParams
 * @param {Window} aParams.sourceBrowsingCxt
 *                The BrowsingContext (possibly remote) that contains
 *                srcElement.
 * @param {Window} aParams.targetBrowsingCxt
 *                The BrowsingContext (possibly remote) that contains
 *                targetElement.  Default is sourceBrowsingCxt.
 * @param {Element} aParams.srcElement
 *                The element to drag.
 * @param {Element|nil} aParams.targetElement
 *                The element to drop on.
 * @param {Number} aParams.step
 *                The 2D step for mousemoves
 * @param {Boolean} aParams.expectCancelDragStart
 *                Set to true if srcElement is set up to cancel "dragstart"
 * @param {Number} aParams.cancel
 *                The 2D coord the mouse is moved to as the last step if
 *                expectCancelDragStart is set
 * @param {Boolean} aParams.expectSrcElementDisconnected
 *                Set to true if srcElement will be disconnected and
 *                "dragend" event won't be fired.
 * @param {Boolean} aParams.expectDragLeave
 *                Set to true if the drop event will be converted to a
 *                dragleave before it is sent (e.g. it was rejected by a
 *                content analysis check).
 * @param {Boolean} aParams.expectNoDragEvents
 *                Set to true if no mouse or drag events should be received
 *                on the source or target.
 * @param {Boolean} aParams.expectNoDragTargetEvents
 *                Set to true if the drag should be blocked from sending
 *                events to the target.
 * @param {Boolean} aParams.dropPromise
 *                A promise that the caller will resolve before we check
 *                that the drop has happened.  Default is a pre-resolved
 *                promise.
 * @param {String} aParms.contextLabel
 *                Label that will appear in each output message.  Useful to
 *                distinguish between concurrent calls.  Default is none.
 * @param {Boolean} aParams.throwOnExtraMessage
 *                Throw an exception in child process when an unexpected
 *                event is received.  Used for debugging.  Default is false.
 * @param {Function} aParams.record
 *                Four-parameter function that logs the results of a remote
 *                assertion.  The parameters are (condition, message, ignored,
 *                stack).  This is the type of the mochitest report function.
 * @param {Function} aParams.info
 *                One-parameter info logging function.  Default is console.log.
 *                This is the type of the mochitest info function.
 * @param {Object} aParams.dragController
 *                MockDragController that the function should use.  This
 *                function will automatically generate one if none is given.
 */
async function synthesizeMockDragAndDrop(aParams) {
  const {
    srcElement,
    targetElement,
    step = [5, 5],
    cancel = [0, 0],
    sourceBrowsingCxt,
    targetBrowsingCxt = sourceBrowsingCxt,
    expectCancelDragStart = false,
    expectSrcElementDisconnected = false,
    expectDragLeave = false,
    expectNoDragEvents = false,
    dropPromise = Promise.resolve(undefined),
    contextLabel = "",
    throwOnExtraMessage = false,
  } = aParams;

  let { dragController = null, expectNoDragTargetEvents = false } = aParams;

  // Configure test reporting functions
  const prefix = contextLabel ? `[${contextLabel}]| ` : "";
  const info = msg => {
    aParams.info(`${prefix}${msg}`);
  };
  const record = (cond, msg, _, stack) => {
    aParams.record(cond, `${prefix}${msg}`, null, stack);
  };
  const ok = (cond, msg) => {
    record(cond, msg, null, Components.stack.caller);
  };

  info("synthesizeMockDragAndDrop() -- START");

  // Validate parameters
  ok(sourceBrowsingCxt, "sourceBrowsingCxt was given");
  ok(
    sourceBrowsingCxt != targetBrowsingCxt || srcElement != targetElement,
    "sourceBrowsingCxt+Element cannot be the same as targetBrowsingCxt+Element"
  );

  // no drag implies no drag target
  expectNoDragTargetEvents |= expectNoDragEvents;

  // Returns true if one browsing context is an ancestor of the other.
  let browsingContextsAreRelated = function (cxt1, cxt2) {
    let cxt = cxt1;
    while (cxt) {
      if (cxt2 == cxt) {
        return true;
      }
      cxt = cxt.parent;
    }
    cxt = cxt2.parent;
    while (cxt) {
      if (cxt1 == cxt) {
        return true;
      }
      cxt = cxt.parent;
    }
    return false;
  };

  // The rules for accessing the dataTransfer from internal drags in Gecko
  // during drag event handlers are as follows:
  //
  // dragstart:
  //   Always grants read-write access
  // dragenter/dragover/dragleave:
  //   If dom.events.dataTransfer.protected.enabled is set:
  //     Read-only permission is granted if any of these holds:
  //       * The drag target's browsing context is the same as the drag
  //         source's (e.g. dragging inside of one frame on a web page).
  //       * The drag source and target are the same domain/principal and
  //         one has a browsing context that is an ancestor of the other
  //         (e.g. one is an iframe nested inside of the other).
  //       * The principal of the drag target element is privileged (not
  //         a content principal).
  //   Otherwise:
  //     Permission is never granted
  // drop:
  //   Always grants read-only permission
  // dragend:
  //   Read-only permission is granted if
  //   dom.events.dataTransfer.protected.enabled is set.
  //
  // dragstart and dragend are special because they target the drag-source,
  // not the drag-target.
  let expectProtectedDataTransferAccess =
    !SpecialPowers.getBoolPref("dom.events.dataTransfer.protected.enabled") &&
    browsingContextsAreRelated(targetBrowsingCxt, sourceBrowsingCxt);

  // expectProtectedDataTransferAccessDragendOnly overrides
  // expectProtectedDataTransferAccess when it is true
  let expectProtectedDataTransferAccessDragendOnly = !SpecialPowers.getBoolPref(
    "dom.events.dataTransfer.protected.enabled"
  );

  info(
    `expectProtectedDataTransferAccess: ${expectProtectedDataTransferAccess}`
  );
  info(
    `expectProtectedDataTransferAccessDragendOnly: ${expectProtectedDataTransferAccessDragendOnly}`
  );

  // Essentially the entire function is in a try block so that we can make sure
  // that the mock drag service is removed and non-test mouse events are
  // restored.
  const { MockRegistrar } = ChromeUtils.importESModule(
    "resource://testing-common/MockRegistrar.sys.mjs"
  );
  let dragServiceCid;
  let sourceCxt;
  let targetCxt;
  try {
    // Disable native mouse events to avoid external interference while the test
    // runs.  One call disables for all windows.
    _getDOMWindowUtils(sourceBrowsingCxt.ownerGlobal).disableNonTestMouseEvents(
      true
    );

    // Install mock drag service in main process.
    ok(
      Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT,
      "synthesizeMockDragAndDrop is only available in the main process"
    );

    if (!dragController) {
      info("No dragController was given so creating mock drag service");
      const oldDragService = SpecialPowers.Cc[
        "@mozilla.org/widget/dragservice;1"
      ].getService(SpecialPowers.Ci.nsIDragService);
      dragController = oldDragService.getMockDragController();
      dragServiceCid = MockRegistrar.register(
        "@mozilla.org/widget/dragservice;1",
        dragController.mockDragService
      );
      ok(dragServiceCid, "MockDragService was registered");
      // If the mock failed then don't continue or else we will trigger native
      // DND behavior.
      if (!dragServiceCid) {
        throw new Error("MockDragService failed to register");
      }
    }

    // Variables that are added to the child actor objects.
    const srcVars = {
      expectCancelDragStart,
      expectSrcElementDisconnected,
      expectNoDragEvents,
      expectProtectedDataTransferAccessDragendOnly,
      dragElementId: srcElement,
    };
    const targetVars = {
      expectDragLeave,
      expectNoDragTargetEvents,
      dragElementId: targetElement,
    };
    const bothVars = {
      contextLabel,
      throwOnExtraMessage,
      expectProtectedDataTransferAccess,
      relevantEvents: [
        "mousedown",
        "mouseup",
        "dragstart",
        "dragenter",
        "dragover",
        "drop",
        "dragleave",
        "dragend",
      ],
    };

    const makeDragSourceContext = async (aBC, aRemoteVars) => {
      let { DragSourceParentContext } = _EU_ChromeUtils.importESModule(
        "chrome://mochikit/content/tests/SimpleTest/DragSourceParentContext.sys.mjs"
      );

      let ret = new DragSourceParentContext(aBC, aRemoteVars, SpecialPowers);
      await ret.initialize();
      return ret;
    };

    const makeDragTargetContext = async (aBC, aRemoteVars) => {
      let { DragTargetParentContext } = _EU_ChromeUtils.importESModule(
        "chrome://mochikit/content/tests/SimpleTest/DragTargetParentContext.sys.mjs"
      );

      let ret = new DragTargetParentContext(aBC, aRemoteVars, SpecialPowers);
      await ret.initialize();
      return ret;
    };

    [sourceCxt, targetCxt] = await Promise.all([
      makeDragSourceContext(sourceBrowsingCxt, { ...srcVars, ...bothVars }),
      makeDragTargetContext(targetBrowsingCxt, {
        ...targetVars,
        ...bothVars,
      }),
    ]);

    // Get element positions in screen and client coords
    let srcPos = await sourceCxt.getElementPositions();
    let targetPos = await targetCxt.getElementPositions();
    info(
      `screenSrcPos: ${srcPos.screenPos} | screenTargetPos: ${targetPos.screenPos}`
    );

    // Send and verify the mousedown on src.
    if (!expectNoDragEvents) {
      sourceCxt.expect("mousedown");
    }

    // Take ceiling of ccoordinates to make sure that the integer coordinates
    // are over the element.
    let currentSrcScreenPos = [
      Math.ceil(srcPos.screenPos[0]),
      Math.ceil(srcPos.screenPos[1]),
    ];
    info(
      `sending mousedown at ${currentSrcScreenPos[0]}, ${currentSrcScreenPos[1]}`
    );
    dragController.sendEvent(
      sourceBrowsingCxt,
      Ci.nsIMockDragServiceController.eMouseDown,
      currentSrcScreenPos[0],
      currentSrcScreenPos[1]
    );
    info(`mousedown sent`);

    await sourceCxt.synchronize();

    await sourceCxt.checkMouseDown();

    let contentInvokedDragPromise;

    info("setting up content-invoked-drag observer and expecting dragstart");
    if (!expectNoDragEvents) {
      sourceCxt.expect("dragstart");
      // Set up observable for content-invoked-drag, which is sent when the
      // parent learns that content has begun a drag session.
      contentInvokedDragPromise = new Promise(cb => {
        Services.obs.addObserver(function observe() {
          info("content-invoked-drag observer received message");
          Services.obs.removeObserver(observe, "content-invoked-drag");
          cb();
        }, "content-invoked-drag");
      });
    }

    // It takes two mouse-moves to initiate a drag session.
    currentSrcScreenPos = [
      currentSrcScreenPos[0] + step[0],
      currentSrcScreenPos[1] + step[1],
    ];
    info(
      `first mousemove at ${currentSrcScreenPos[0]}, ${currentSrcScreenPos[1]}`
    );
    dragController.sendEvent(
      sourceBrowsingCxt,
      Ci.nsIMockDragServiceController.eMouseMove,
      currentSrcScreenPos[0],
      currentSrcScreenPos[1]
    );
    info(`first mousemove sent`);

    currentSrcScreenPos = [
      currentSrcScreenPos[0] + step[0],
      currentSrcScreenPos[1] + step[1],
    ];
    info(
      `second mousemove at ${currentSrcScreenPos[0]}, ${currentSrcScreenPos[1]}`
    );
    dragController.sendEvent(
      sourceBrowsingCxt,
      Ci.nsIMockDragServiceController.eMouseMove,
      currentSrcScreenPos[0],
      currentSrcScreenPos[1]
    );
    info(`second mousemove sent`);

    if (!expectNoDragEvents) {
      info("waiting for content-invoked-drag observable");
      await contentInvokedDragPromise;
      ok(true, "content-invoked-drag was received");
    }

    info("checking dragstart");
    await sourceCxt.checkDragStart();

    if (expectNoDragEvents) {
      ok(
        !_getDOMWindowUtils(sourceBrowsingCxt.ownerGlobal).dragSession,
        "Drag was properly blocked from starting."
      );
      return;
    }

    // Another move creates the drag session in the parent process (but we need
    // to wait for the src process to get there).
    info(`Moving to target element.`);
    let currentTargetScreenPos = [
      Math.ceil(targetPos.screenPos[0]),
      Math.ceil(targetPos.screenPos[1]),
    ];

    dragController.sendEvent(
      sourceBrowsingCxt,
      Ci.nsIMockDragServiceController.eMouseMove,
      currentTargetScreenPos[0],
      currentTargetScreenPos[1]
    );

    await sourceCxt.checkExpected();

    ok(
      _getDOMWindowUtils(sourceBrowsingCxt.ownerGlobal).dragSession,
      `Parent process source widget has drag session.`
    );

    if (expectCancelDragStart) {
      dragController.sendEvent(
        sourceBrowsingCxt,
        Ci.nsIMockDragServiceController.eMouseUp,
        cancel[0],
        cancel[1]
      );
      return;
    }

    currentTargetScreenPos = [
      currentTargetScreenPos[0] + step[0],
      currentTargetScreenPos[1] + step[1],
    ];

    // Send dragleave and dragenter only if we moved to another widget.
    // If we moved in the same widget then dragenter does not involve
    // the parent process.  This mirrors the native behavior.  Note that
    // these events are not forwarded to the content process -- they
    // are generated there by the EventStateManager when appropriate.
    if (
      sourceBrowsingCxt.top.embedderElement !==
      targetBrowsingCxt.top.embedderElement
    ) {
      // Dragging from widget to widget
      info("synthesizing dragleave and dragenter to enter new widget");

      dragController.sendEvent(
        sourceBrowsingCxt,
        Ci.nsIMockDragServiceController.eDragLeave,
        currentTargetScreenPos[0],
        currentTargetScreenPos[1]
      );

      dragController.sendEvent(
        targetBrowsingCxt,
        Ci.nsIMockDragServiceController.eDragEnter,
        currentTargetScreenPos[0],
        currentTargetScreenPos[1]
      );

      await sourceCxt.checkExpected();
      await targetCxt.checkExpected();
    }

    info("synthesizing dragover to generate dragenter in DOM");

    if (!expectNoDragTargetEvents) {
      targetCxt.expect("dragenter");
      targetCxt.expect("dragover");
    }

    currentTargetScreenPos = [
      currentTargetScreenPos[0] + step[0],
      currentTargetScreenPos[1] + step[1],
    ];
    dragController.sendEvent(
      targetBrowsingCxt,
      Ci.nsIMockDragServiceController.eDragOver,
      currentTargetScreenPos[0],
      currentTargetScreenPos[1]
    );

    await targetCxt.checkExpected();

    let expectedMessage = expectDragLeave ? "dragleave" : "drop";

    if (expectNoDragTargetEvents) {
      await targetCxt.checkHasDrag(false);
    } else {
      await targetCxt.checkSessionHasAction();
      targetCxt.expect(expectedMessage);
    }

    if (!expectSrcElementDisconnected) {
      await sourceCxt.checkHasDrag(true);
      sourceCxt.expect("dragend");
    }

    info(
      `issuing drop event that should be ` +
        `${
          !expectNoDragTargetEvents
            ? `received as a ${expectedMessage} event`
            : "ignored"
        }, followed by a dragend event`
    );

    currentTargetScreenPos = [
      currentTargetScreenPos[0] + step[0],
      currentTargetScreenPos[1] + step[1],
    ];
    dragController.sendEvent(
      targetBrowsingCxt,
      Ci.nsIMockDragServiceController.eDrop,
      currentTargetScreenPos[0],
      currentTargetScreenPos[1]
    );

    // Wait for any caller-supplied dropPromise before continuing.
    await dropPromise;

    if (!expectNoDragTargetEvents) {
      await targetCxt.checkDropOrDragLeave();
    } else {
      await targetCxt.checkExpected();
    }

    if (!expectSrcElementDisconnected) {
      await sourceCxt.checkDragEnd();
    } else {
      await sourceCxt.checkExpected();
    }

    ok(
      !_getDOMWindowUtils(sourceBrowsingCxt.ownerGlobal).dragSession,
      `Parent process source widget does not have a drag session.`
    );

    ok(
      !_getDOMWindowUtils(targetBrowsingCxt.ownerGlobal).dragSession,
      `Parent process target widget does not have a drag session.`
    );
  } catch (e) {
    // Any exception is a test failure.
    record(false, e.toString(), null, e.stack);
  } finally {
    if (sourceCxt) {
      await sourceCxt.cleanup();
    }
    if (targetCxt) {
      await targetCxt.cleanup();
    }

    if (dragServiceCid) {
      MockRegistrar.unregister(dragServiceCid);
    }

    _getDOMWindowUtils(sourceBrowsingCxt.ownerGlobal).disableNonTestMouseEvents(
      false
    );

    info("synthesizeMockDragAndDrop() -- END");
  }
}

class EventCounter {
  constructor(aTarget, aType, aOptions = {}) {
    this.target = aTarget;
    this.type = aType;
    this.options = aOptions;

    this.eventCount = 0;
    // Bug 1512817:
    // SpecialPowers is picky and needs to be passed an explicit reference to
    // the function to be called. To avoid having to bind "this", we therefore
    // define the method this way, via a property.
    this.handleEvent = () => {
      this.eventCount++;
    };

    SpecialPowers.wrap(aTarget).addEventListener(
      aType,
      this.handleEvent,
      aOptions
    );
  }

  unregister() {
    SpecialPowers.wrap(this.target).removeEventListener(
      this.type,
      this.handleEvent,
      this.options
    );
  }

  get count() {
    return this.eventCount;
  }
}
PK
!</��&gCgCGchrome/remote/content/marionette/actors/MarionetteCommandsChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-disable no-restricted-globals */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  accessibility:
    "chrome://remote/content/shared/webdriver/Accessibility.sys.mjs",
  action: "chrome://remote/content/shared/webdriver/Actions.sys.mjs",
  atom: "chrome://remote/content/marionette/atom.sys.mjs",
  dom: "chrome://remote/content/shared/DOM.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  evaluate: "chrome://remote/content/marionette/evaluate.sys.mjs",
  interaction: "chrome://remote/content/marionette/interaction.sys.mjs",
  json: "chrome://remote/content/marionette/json.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  sandbox: "chrome://remote/content/marionette/evaluate.sys.mjs",
  Sandboxes: "chrome://remote/content/marionette/evaluate.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
);

export class MarionetteCommandsChild extends JSWindowActorChild {
  #processActor;

  constructor() {
    super();

    this.#processActor = ChromeUtils.domProcessChild.getActor(
      "WebDriverProcessData"
    );

    // sandbox storage and name of the current sandbox
    this.sandboxes = new lazy.Sandboxes(() => this.document.defaultView);
    // State of the input actions. This is specific to contexts and sessions
    this.actionState = null;
  }

  get innerWindowId() {
    return this.manager.innerWindowId;
  }

  actorCreated() {
    lazy.logger.trace(
      `[${this.browsingContext.id}] MarionetteCommands actor created ` +
        `for window id ${this.innerWindowId}`
    );
  }

  didDestroy() {
    lazy.logger.trace(
      `[${this.browsingContext.id}] MarionetteCommands actor destroyed ` +
        `for window id ${this.innerWindowId}`
    );
  }

  async receiveMessage(msg) {
    if (!this.contentWindow) {
      throw new DOMException("Actor is no longer active", "InactiveActor");
    }

    try {
      let result;
      let waitForNextTick = false;

      const { name, data: serializedData } = msg;

      const data = lazy.json.deserialize(
        serializedData,
        this.#processActor.getNodeCache(),
        this.contentWindow.browsingContext
      );

      switch (name) {
        case "MarionetteCommandsParent:clearElement":
          this.clearElement(data);
          waitForNextTick = true;
          break;
        case "MarionetteCommandsParent:clickElement":
          result = await this.clickElement(data);
          waitForNextTick = true;
          break;
        case "MarionetteCommandsParent:executeScript":
          result = await this.executeScript(data);
          waitForNextTick = true;
          break;
        case "MarionetteCommandsParent:findElement":
          result = await this.findElement(data);
          break;
        case "MarionetteCommandsParent:findElements":
          result = await this.findElements(data);
          break;
        case "MarionetteCommandsParent:getActiveElement":
          result = await this.getActiveElement();
          break;
        case "MarionetteCommandsParent:getComputedLabel":
          result = await this.getComputedLabel(data);
          break;
        case "MarionetteCommandsParent:getComputedRole":
          result = await this.getComputedRole(data);
          break;
        case "MarionetteCommandsParent:getElementAttribute":
          result = await this.getElementAttribute(data);
          break;
        case "MarionetteCommandsParent:getElementProperty":
          result = await this.getElementProperty(data);
          break;
        case "MarionetteCommandsParent:getElementRect":
          result = await this.getElementRect(data);
          break;
        case "MarionetteCommandsParent:getElementTagName":
          result = await this.getElementTagName(data);
          break;
        case "MarionetteCommandsParent:getElementText":
          result = await this.getElementText(data);
          break;
        case "MarionetteCommandsParent:getElementValueOfCssProperty":
          result = await this.getElementValueOfCssProperty(data);
          break;
        case "MarionetteCommandsParent:getPageSource":
          result = await this.getPageSource();
          break;
        case "MarionetteCommandsParent:getScreenshotRect":
          result = await this.getScreenshotRect(data);
          break;
        case "MarionetteCommandsParent:getShadowRoot":
          result = await this.getShadowRoot(data);
          break;
        case "MarionetteCommandsParent:isElementDisplayed":
          result = await this.isElementDisplayed(data);
          break;
        case "MarionetteCommandsParent:isElementEnabled":
          result = await this.isElementEnabled(data);
          break;
        case "MarionetteCommandsParent:isElementSelected":
          result = await this.isElementSelected(data);
          break;
        case "MarionetteCommandsParent:performActions":
          result = await this.performActions(data);
          waitForNextTick = true;
          break;
        case "MarionetteCommandsParent:releaseActions":
          result = await this.releaseActions();
          break;
        case "MarionetteCommandsParent:sendKeysToElement":
          result = await this.sendKeysToElement(data);
          waitForNextTick = true;
          break;
        case "MarionetteCommandsParent:switchToFrame":
          result = await this.switchToFrame(data);
          waitForNextTick = true;
          break;
        case "MarionetteCommandsParent:switchToParentFrame":
          result = await this.switchToParentFrame();
          waitForNextTick = true;
          break;
      }

      // Inform the content process that the command has completed. It allows
      // it to process async follow-up tasks before the reply is sent.
      if (waitForNextTick) {
        await new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
      }

      const { seenNodeIds, serializedValue, hasSerializedWindows } =
        lazy.json.clone(result, this.#processActor.getNodeCache());

      // Because in WebDriver classic nodes can only be returned from the same
      // browsing context, we only need the seen unique ids as flat array.
      return {
        seenNodeIds: [...seenNodeIds.values()].flat(),
        serializedValue,
        hasSerializedWindows,
      };
    } catch (e) {
      // Always wrap errors as WebDriverError
      return { error: lazy.error.wrap(e).toJSON() };
    }
  }

  // Implementation of WebDriver commands

  /** Clear the text of an element.
   *
   * @param {object} options
   * @param {Element} options.elem
   */
  clearElement(options = {}) {
    const { elem } = options;

    lazy.interaction.clearElement(elem);
  }

  /**
   * Click an element.
   */
  async clickElement(options = {}) {
    const { capabilities, elem } = options;

    return lazy.interaction.clickElement(
      elem,
      capabilities["moz:accessibilityChecks"],
      capabilities["moz:webdriverClick"]
    );
  }

  /**
   * Executes a JavaScript function.
   */
  async executeScript(options = {}) {
    const { args, opts = {}, script } = options;

    let sb;
    if (opts.sandboxName) {
      sb = this.sandboxes.get(opts.sandboxName, opts.newSandbox);
    } else {
      sb = lazy.sandbox.createMutable(this.document.defaultView);
    }

    return lazy.evaluate.sandbox(sb, script, args, opts);
  }

  /**
   * Find an element in the current browsing context's document using the
   * given search strategy.
   *
   * @param {object=} options
   * @param {string} options.strategy
   * @param {string} options.selector
   * @param {object} options.opts
   * @param {Element} options.opts.startNode
   */
  async findElement(options = {}) {
    const { strategy, selector, opts } = options;

    opts.all = false;

    const container = { frame: this.document.defaultView };
    return lazy.dom.find(container, strategy, selector, opts);
  }

  /**
   * Find elements in the current browsing context's document using the
   * given search strategy.
   *
   * @param {object=} options
   * @param {string} options.strategy
   * @param {string} options.selector
   * @param {object} options.opts
   * @param {Element} options.opts.startNode
   */
  async findElements(options = {}) {
    const { strategy, selector, opts } = options;

    opts.all = true;

    const container = { frame: this.document.defaultView };
    return lazy.dom.find(container, strategy, selector, opts);
  }

  /**
   * Return the active element in the document.
   */
  async getActiveElement() {
    let elem = this.document.activeElement;
    if (!elem) {
      throw new lazy.error.NoSuchElementError();
    }

    return elem;
  }

  /**
   * Return the accessible label for a given element.
   */
  async getComputedLabel(options = {}) {
    const { elem } = options;

    return lazy.accessibility.getAccessibleName(elem);
  }

  /**
   * Return the accessible role for a given element.
   */
  async getComputedRole(options = {}) {
    const { elem } = options;

    return lazy.accessibility.getComputedRole(elem);
  }

  /**
   * Get the value of an attribute for the given element.
   */
  async getElementAttribute(options = {}) {
    const { name, elem } = options;

    if (lazy.dom.isBooleanAttribute(elem, name)) {
      if (elem.hasAttribute(name)) {
        return "true";
      }
      return null;
    }
    return elem.getAttribute(name);
  }

  /**
   * Get the value of a property for the given element.
   */
  async getElementProperty(options = {}) {
    const { name, elem } = options;

    // Waive Xrays to get unfiltered access to the untrusted element.
    const el = Cu.waiveXrays(elem);
    return typeof el[name] != "undefined" ? el[name] : null;
  }

  /**
   * Get the position and dimensions of the element.
   */
  async getElementRect(options = {}) {
    const { elem } = options;

    const rect = elem.getBoundingClientRect();
    return {
      x: rect.x + this.document.defaultView.pageXOffset,
      y: rect.y + this.document.defaultView.pageYOffset,
      width: rect.width,
      height: rect.height,
    };
  }

  /**
   * Get the tagName for the given element.
   */
  async getElementTagName(options = {}) {
    const { elem } = options;

    return elem.tagName.toLowerCase();
  }

  /**
   * Get the text content for the given element.
   */
  async getElementText(options = {}) {
    const { elem } = options;

    try {
      return await lazy.atom.getVisibleText(elem, this.document.defaultView);
    } catch (e) {
      lazy.logger.warn(`Atom getVisibleText failed: "${e.message}"`);

      // Fallback in case the atom implementation is broken.
      // As known so far this only happens for XML documents (bug 1794099).
      return elem.textContent;
    }
  }

  /**
   * Get the value of a css property for the given element.
   */
  async getElementValueOfCssProperty(options = {}) {
    const { name, elem } = options;

    const style = this.document.defaultView.getComputedStyle(elem);
    return style.getPropertyValue(name);
  }

  /**
   * Get the source of the current browsing context's document.
   */
  async getPageSource() {
    return this.document.documentElement.outerHTML;
  }

  /**
   * Returns the rect of the element to screenshot.
   *
   * Because the screen capture takes place in the parent process the dimensions
   * for the screenshot have to be determined in the appropriate child process.
   *
   * Also it takes care of scrolling an element into view if requested.
   *
   * @param {object} options
   * @param {Element} options.elem
   *     Optional element to take a screenshot of.
   * @param {boolean=} options.full
   *     True to take a screenshot of the entire document element.
   *     Defaults to true.
   * @param {boolean=} options.scroll
   *     When <var>elem</var> is given, scroll it into view.
   *     Defaults to true.
   *
   * @returns {DOMRect}
   *     The area to take a snapshot from.
   */
  async getScreenshotRect(options = {}) {
    const { elem, full = true, scroll = true } = options;
    const win = elem
      ? this.document.defaultView
      : this.browsingContext.top.window;

    let rect;

    if (elem) {
      if (scroll) {
        lazy.dom.scrollIntoView(elem);
      }
      rect = this.getElementRect({ elem });
    } else if (full) {
      const docEl = win.document.documentElement;
      rect = new DOMRect(0, 0, docEl.scrollWidth, docEl.scrollHeight);
    } else {
      // viewport
      rect = new DOMRect(
        win.pageXOffset,
        win.pageYOffset,
        win.innerWidth,
        win.innerHeight
      );
    }

    return rect;
  }

  /**
   * Return the shadowRoot attached to an element
   */
  async getShadowRoot(options = {}) {
    const { elem } = options;

    return lazy.dom.getShadowRoot(elem);
  }

  /**
   * Determine the element displayedness of the given web element.
   */
  async isElementDisplayed(options = {}) {
    const { capabilities, elem } = options;

    return lazy.interaction.isElementDisplayed(
      elem,
      capabilities["moz:accessibilityChecks"]
    );
  }

  /**
   * Check if element is enabled.
   */
  async isElementEnabled(options = {}) {
    const { capabilities, elem } = options;

    return lazy.interaction.isElementEnabled(
      elem,
      capabilities["moz:accessibilityChecks"]
    );
  }

  /**
   * Determine whether the referenced element is selected or not.
   */
  async isElementSelected(options = {}) {
    const { capabilities, elem } = options;

    return lazy.interaction.isElementSelected(
      elem,
      capabilities["moz:accessibilityChecks"]
    );
  }

  /**
   * Perform a series of grouped actions at the specified points in time.
   *
   * @param {object} options
   * @param {object} options.actions
   *     Array of objects with each representing an action sequence.
   * @param {object} options.capabilities
   *     Object with a list of WebDriver session capabilities.
   */
  async performActions(options = {}) {
    const { actions } = options;
    if (this.actionState === null) {
      this.actionState = new lazy.action.State();
    }
    let actionChain = lazy.action.Chain.fromJSON(this.actionState, actions);

    await actionChain.dispatch(this.actionState, this.document.defaultView);
    // Terminate the current wheel transaction if there is one. Wheel
    // transactions should not live longer than a single action chain.
    ChromeUtils.endWheelTransaction();
  }

  /**
   * The release actions command is used to release all the keys and pointer
   * buttons that are currently depressed. This causes events to be fired
   * as if the state was released by an explicit series of actions. It also
   * clears all the internal state of the virtual devices.
   */
  async releaseActions() {
    if (this.actionState === null) {
      return;
    }
    await this.actionState.release(this.document.defaultView);
    this.actionState = null;
  }

  /*
   * Send key presses to element after focusing on it.
   */
  async sendKeysToElement(options = {}) {
    const { capabilities, elem, text } = options;

    const opts = {
      strictFileInteractability: capabilities.strictFileInteractability,
      accessibilityChecks: capabilities["moz:accessibilityChecks"],
      webdriverClick: capabilities["moz:webdriverClick"],
    };

    return lazy.interaction.sendKeysToElement(elem, text, opts);
  }

  /**
   * Switch to the specified frame.
   *
   * @param {object=} options
   * @param {(number|Element)=} options.id
   *     If it's a number treat it as the index for all the existing frames.
   *     If it's an Element switch to this specific frame.
   *     If not specified or `null` switch to the top-level browsing context.
   */
  async switchToFrame(options = {}) {
    const { id } = options;

    const childContexts = this.browsingContext.children;
    let browsingContext;

    if (id == null) {
      browsingContext = this.browsingContext.top;
    } else if (typeof id == "number") {
      if (id < 0 || id >= childContexts.length) {
        throw new lazy.error.NoSuchFrameError(
          `Unable to locate frame with index: ${id}`
        );
      }
      browsingContext = childContexts[id];
    } else {
      const context = childContexts.find(context => {
        return context.embedderElement === id;
      });
      if (!context) {
        throw new lazy.error.NoSuchFrameError(
          `Unable to locate frame for element: ${id}`
        );
      }
      browsingContext = context;
    }

    // For in-process iframes the window global is lazy-loaded for optimization
    // reasons. As such force the currentWindowGlobal to be created so we always
    // have a window (bug 1691348).
    browsingContext.window;

    return { browsingContextId: browsingContext.id };
  }

  /**
   * Switch to the parent frame.
   */
  async switchToParentFrame() {
    const browsingContext = this.browsingContext.parent || this.browsingContext;

    return { browsingContextId: browsingContext.id };
  }
}
PK
!<�2V�y.y.Hchrome/remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  capture: "chrome://remote/content/shared/Capture.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  getSeenNodesForBrowsingContext:
    "chrome://remote/content/shared/webdriver/Session.sys.mjs",
  json: "chrome://remote/content/marionette/json.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
);

// Because Marionette supports a single session only we store its id
// globally so that the parent actor can access it.
let webDriverSessionId = null;

export class MarionetteCommandsParent extends JSWindowActorParent {
  #deferredDialogOpened;

  actorCreated() {
    this.#deferredDialogOpened = null;
  }

  async sendQuery(name, serializedValue) {
    const seenNodes = lazy.getSeenNodesForBrowsingContext(
      webDriverSessionId,
      this.manager.browsingContext
    );

    // return early if a dialog is opened
    this.#deferredDialogOpened = Promise.withResolvers();
    let {
      error,
      seenNodeIds,
      serializedValue: serializedResult,
      hasSerializedWindows,
    } = await Promise.race([
      super.sendQuery(name, serializedValue),
      this.#deferredDialogOpened.promise,
    ]).finally(() => {
      this.#deferredDialogOpened = null;
    });

    if (error) {
      const err = lazy.error.WebDriverError.fromJSON(error);
      this.#handleError(err, seenNodes);
    }

    // Update seen nodes for serialized element and shadow root nodes.
    seenNodeIds?.forEach(nodeId => seenNodes.add(nodeId));

    if (hasSerializedWindows) {
      // The serialized data contains WebWindow references that need to be
      // converted to unique identifiers.
      serializedResult = lazy.json.mapToNavigableIds(serializedResult);
    }

    return serializedResult;
  }

  /**
   * Handle WebDriver error and replace error type if necessary.
   *
   * @param {WebDriverError} error
   *     The WebDriver error to handle.
   * @param {Set<string>} seenNodes
   *     List of node ids already seen in this navigable.
   *
   * @throws {WebDriverError}
   *     The original or replaced WebDriver error.
   */
  #handleError(error, seenNodes) {
    // If an element hasn't been found during deserialization check if it
    // may be a stale reference.
    if (
      error instanceof lazy.error.NoSuchElementError &&
      error.data.elementId !== undefined &&
      seenNodes.has(error.data.elementId)
    ) {
      throw new lazy.error.StaleElementReferenceError(error);
    }

    // If a shadow root hasn't been found during deserialization check if it
    // may be a detached reference.
    if (
      error instanceof lazy.error.NoSuchShadowRootError &&
      error.data.shadowId !== undefined &&
      seenNodes.has(error.data.shadowId)
    ) {
      throw new lazy.error.DetachedShadowRootError(error);
    }

    throw error;
  }

  notifyDialogOpened() {
    if (this.#deferredDialogOpened) {
      this.#deferredDialogOpened.resolve({ data: null });
    }
  }

  // Proxying methods for WebDriver commands

  clearElement(webEl) {
    return this.sendQuery("MarionetteCommandsParent:clearElement", {
      elem: webEl,
    });
  }

  clickElement(webEl, capabilities) {
    return this.sendQuery("MarionetteCommandsParent:clickElement", {
      elem: webEl,
      capabilities: capabilities.toJSON(),
    });
  }

  async executeScript(script, args, opts) {
    return this.sendQuery("MarionetteCommandsParent:executeScript", {
      script,
      args: lazy.json.mapFromNavigableIds(args),
      opts,
    });
  }

  findElement(strategy, selector, opts) {
    return this.sendQuery("MarionetteCommandsParent:findElement", {
      strategy,
      selector,
      opts,
    });
  }

  findElements(strategy, selector, opts) {
    return this.sendQuery("MarionetteCommandsParent:findElements", {
      strategy,
      selector,
      opts,
    });
  }

  async getShadowRoot(webEl) {
    return this.sendQuery("MarionetteCommandsParent:getShadowRoot", {
      elem: webEl,
    });
  }

  async getActiveElement() {
    return this.sendQuery("MarionetteCommandsParent:getActiveElement");
  }

  async getComputedLabel(webEl) {
    return this.sendQuery("MarionetteCommandsParent:getComputedLabel", {
      elem: webEl,
    });
  }

  async getComputedRole(webEl) {
    return this.sendQuery("MarionetteCommandsParent:getComputedRole", {
      elem: webEl,
    });
  }

  async getElementAttribute(webEl, name) {
    return this.sendQuery("MarionetteCommandsParent:getElementAttribute", {
      elem: webEl,
      name,
    });
  }

  async getElementProperty(webEl, name) {
    return this.sendQuery("MarionetteCommandsParent:getElementProperty", {
      elem: webEl,
      name,
    });
  }

  async getElementRect(webEl) {
    return this.sendQuery("MarionetteCommandsParent:getElementRect", {
      elem: webEl,
    });
  }

  async getElementTagName(webEl) {
    return this.sendQuery("MarionetteCommandsParent:getElementTagName", {
      elem: webEl,
    });
  }

  async getElementText(webEl) {
    return this.sendQuery("MarionetteCommandsParent:getElementText", {
      elem: webEl,
    });
  }

  async getElementValueOfCssProperty(webEl, name) {
    return this.sendQuery(
      "MarionetteCommandsParent:getElementValueOfCssProperty",
      {
        elem: webEl,
        name,
      }
    );
  }

  async getPageSource() {
    return this.sendQuery("MarionetteCommandsParent:getPageSource");
  }

  async isElementDisplayed(webEl, capabilities) {
    return this.sendQuery("MarionetteCommandsParent:isElementDisplayed", {
      capabilities: capabilities.toJSON(),
      elem: webEl,
    });
  }

  async isElementEnabled(webEl, capabilities) {
    return this.sendQuery("MarionetteCommandsParent:isElementEnabled", {
      capabilities: capabilities.toJSON(),
      elem: webEl,
    });
  }

  async isElementSelected(webEl, capabilities) {
    return this.sendQuery("MarionetteCommandsParent:isElementSelected", {
      capabilities: capabilities.toJSON(),
      elem: webEl,
    });
  }

  async sendKeysToElement(webEl, text, capabilities) {
    return this.sendQuery("MarionetteCommandsParent:sendKeysToElement", {
      capabilities: capabilities.toJSON(),
      elem: webEl,
      text,
    });
  }

  async performActions(actions) {
    return this.sendQuery("MarionetteCommandsParent:performActions", {
      actions,
    });
  }

  async releaseActions() {
    return this.sendQuery("MarionetteCommandsParent:releaseActions");
  }

  async switchToFrame(id) {
    const { browsingContextId } = await this.sendQuery(
      "MarionetteCommandsParent:switchToFrame",
      { id }
    );

    return {
      browsingContext: BrowsingContext.get(browsingContextId),
    };
  }

  async switchToParentFrame() {
    const { browsingContextId } = await this.sendQuery(
      "MarionetteCommandsParent:switchToParentFrame"
    );

    return {
      browsingContext: BrowsingContext.get(browsingContextId),
    };
  }

  async takeScreenshot(webEl, format, full, scroll) {
    const rect = await this.sendQuery(
      "MarionetteCommandsParent:getScreenshotRect",
      {
        elem: webEl,
        full,
        scroll,
      }
    );

    // If no element has been specified use the top-level browsing context.
    // Otherwise use the browsing context from the currently selected frame.
    const browsingContext = webEl
      ? this.browsingContext
      : this.browsingContext.top;

    let canvas = await lazy.capture.canvas(
      browsingContext.topChromeWindow,
      browsingContext,
      rect.x,
      rect.y,
      rect.width,
      rect.height
    );

    switch (format) {
      case lazy.capture.Format.Hash:
        return lazy.capture.toHash(canvas);

      case lazy.capture.Format.Base64:
        return lazy.capture.toBase64(canvas);

      default:
        throw new TypeError(`Invalid capture format: ${format}`);
    }
  }
}

/**
 * Proxy that will dynamically create MarionetteCommands actors for a dynamically
 * provided browsing context until the method can be fully executed by the
 * JSWindowActor pair.
 *
 * @param {function(): BrowsingContext} browsingContextFn
 *     A function that returns the reference to the browsing context for which
 *     the query should run.
 */
export function getMarionetteCommandsActorProxy(browsingContextFn) {
  const MAX_ATTEMPTS = 10;

  /**
   * Methods which modify the content page cannot be retried safely.
   * See Bug 1673345.
   */
  const NO_RETRY_METHODS = [
    "clickElement",
    "executeScript",
    "performActions",
    "releaseActions",
    "sendKeysToElement",
  ];

  return new Proxy(
    {},
    {
      get(target, methodName) {
        return async (...args) => {
          let attempts = 0;
          while (true) {
            try {
              const browsingContext = browsingContextFn();
              if (!browsingContext) {
                throw new DOMException(
                  "No BrowsingContext found",
                  "NoBrowsingContext"
                );
              }

              // TODO: Scenarios where the window/tab got closed and
              // currentWindowGlobal is null will be handled in Bug 1662808.
              const actor =
                browsingContext.currentWindowGlobal.getActor(
                  "MarionetteCommands"
                );

              const result = await actor[methodName](...args);
              return result;
            } catch (e) {
              if (!["AbortError", "InactiveActor"].includes(e.name)) {
                // Only retry when the JSWindowActor pair gets destroyed, or
                // gets inactive eg. when the page is moved into bfcache.
                throw e;
              }

              if (NO_RETRY_METHODS.includes(methodName)) {
                const browsingContextId = browsingContextFn()?.id;
                lazy.logger.trace(
                  `[${browsingContextId}] Querying "${methodName}" failed with` +
                    ` ${e.name}, returning "null" as fallback`
                );
                return null;
              }

              if (++attempts > MAX_ATTEMPTS) {
                const browsingContextId = browsingContextFn()?.id;
                lazy.logger.trace(
                  `[${browsingContextId}] Querying "${methodName} "` +
                    `reached the limit of retry attempts (${MAX_ATTEMPTS})`
                );
                throw e;
              }

              lazy.logger.trace(
                `Retrying "${methodName}", attempt: ${attempts}`
              );
            }
          }
        };
      },
    }
  );
}

/**
 * Register the MarionetteCommands actor that holds all the commands.
 *
 * @param {string} sessionId
 *     The id of the current WebDriver session.
 */
export function registerCommandsActor(sessionId) {
  try {
    ChromeUtils.registerWindowActor("MarionetteCommands", {
      kind: "JSWindowActor",
      parent: {
        esModuleURI:
          "chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs",
      },
      child: {
        esModuleURI:
          "chrome://remote/content/marionette/actors/MarionetteCommandsChild.sys.mjs",
      },

      allFrames: true,
      includeChrome: true,
    });
  } catch (e) {
    if (e.name === "NotSupportedError") {
      lazy.logger.warn(`MarionetteCommands actor is already registered!`);
    } else {
      throw e;
    }
  }

  webDriverSessionId = sessionId;
}

export function unregisterCommandsActor() {
  webDriverSessionId = null;

  ChromeUtils.unregisterWindowActor("MarionetteCommands");
}
PK
!<�ޛՎ�Echrome/remote/content/marionette/actors/MarionetteEventsChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-disable no-restricted-globals */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "chrome://remote/content/shared/Log.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
);

export class MarionetteEventsChild extends JSWindowActorChild {
  get innerWindowId() {
    return this.manager.innerWindowId;
  }

  actorCreated() {
    // Prevent the logger from being created if the current log level
    // isn't set to 'trace'. This is important for a faster content process
    // creation when Marionette is running.
    if (lazy.Log.isTraceLevelOrOrMore) {
      lazy.logger.trace(
        `[${this.browsingContext.id}] MarionetteEvents actor created ` +
          `for window id ${this.innerWindowId}`
      );
    }
  }

  handleEvent({ target, type }) {
    if (!Services.cpmm.sharedData.get("MARIONETTE_EVENTS_ENABLED")) {
      // The parent process will set MARIONETTE_EVENTS_ENABLED to false when
      // the Marionette session ends to avoid unnecessary inter process
      // communications
      return;
    }

    // Ignore invalid combinations of load events and document's readyState.
    if (
      (type === "DOMContentLoaded" && target.readyState != "interactive") ||
      (type === "pageshow" && target.readyState != "complete")
    ) {
      lazy.logger.warn(
        `Ignoring event '${type}' because document has an invalid ` +
          `readyState of '${target.readyState}'.`
      );
      return;
    }

    switch (type) {
      case "beforeunload":
      case "DOMContentLoaded":
      case "hashchange":
      case "pagehide":
      case "pageshow":
      case "popstate":
        this.sendAsyncMessage("MarionetteEventsChild:PageLoadEvent", {
          browsingContext: this.browsingContext,
          documentURI: target.documentURI,
          readyState: target.readyState,
          type,
          windowId: this.innerWindowId,
        });
        break;
    }
  }
}
PK
!<�n����Fchrome/remote/content/marionette/actors/MarionetteEventsParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",

  Log: "chrome://remote/content/shared/Log.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
);

// Singleton to allow forwarding events to registered listeners.
export const EventDispatcher = {
  init() {
    lazy.EventEmitter.decorate(this);
  },
};

EventDispatcher.init();

export class MarionetteEventsParent extends JSWindowActorParent {
  async receiveMessage(msg) {
    const { name, data } = msg;

    let rv;
    switch (name) {
      case "MarionetteEventsChild:PageLoadEvent":
        EventDispatcher.emit("page-load", data);
        break;
    }

    return rv;
  }
}

// Flag to check if the MarionetteEvents actors have already been registed.
let eventsActorRegistered = false;

/**
 * Register Events actors to listen for page load events via EventDispatcher.
 */
function registerEventsActor() {
  if (eventsActorRegistered) {
    return;
  }

  try {
    // Register the JSWindowActor pair for events as used by Marionette
    ChromeUtils.registerWindowActor("MarionetteEvents", {
      kind: "JSWindowActor",
      parent: {
        esModuleURI:
          "chrome://remote/content/marionette/actors/MarionetteEventsParent.sys.mjs",
      },
      child: {
        esModuleURI:
          "chrome://remote/content/marionette/actors/MarionetteEventsChild.sys.mjs",
        events: {
          beforeunload: { capture: true },
          DOMContentLoaded: { mozSystemGroup: true },
          hashchange: { mozSystemGroup: true },
          pagehide: { mozSystemGroup: true },
          pageshow: { mozSystemGroup: true },
          // popstate doesn't bubble, as such use capturing phase
          popstate: { capture: true, mozSystemGroup: true },

          click: {},
          dblclick: {},
          unload: { capture: true, createActor: false },
        },
      },

      allFrames: true,
      includeChrome: true,
    });

    eventsActorRegistered = true;
  } catch (e) {
    if (e.name === "NotSupportedError") {
      lazy.logger.warn(`MarionetteEvents actor is already registered!`);
    } else {
      throw e;
    }
  }
}

/**
 * Enable MarionetteEvents actors to start forwarding page load events from the
 * child actor to the parent actor. Register the MarionetteEvents actor if necessary.
 */
export function enableEventsActor() {
  // sharedData is replicated across processes and will be checked by
  // MarionetteEventsChild before forward events to the parent actor.
  Services.ppmm.sharedData.set("MARIONETTE_EVENTS_ENABLED", true);
  // Request to immediately flush the data to the content processes to avoid races.
  Services.ppmm.sharedData.flush();

  registerEventsActor();
}

/**
 * Disable MarionetteEvents actors to stop forwarding page load events from the
 * child actor to the parent actor.
 */
export function disableEventsActor() {
  Services.ppmm.sharedData.set("MARIONETTE_EVENTS_ENABLED", false);
  Services.ppmm.sharedData.flush();
}
PK
!<�T�M��Fchrome/remote/content/marionette/actors/MarionetteReftestChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  setTimeout: "resource://gre/modules/Timer.sys.mjs",

  Log: "chrome://remote/content/shared/Log.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
);

/**
 * Child JSWindowActor to handle navigation for reftests relying on marionette.
 */
export class MarionetteReftestChild extends JSWindowActorChild {
  constructor() {
    super();

    // This promise will resolve with the URL recorded in the "load" event
    // handler. This URL will not be impacted by any hash modification that
    // might be performed by the test script.
    // The harness should be loaded before loading any test page, so the actors
    // should be registered before the "load" event is received for a test page.
    this._loadedURLPromise = new Promise(
      r => (this._resolveLoadedURLPromise = r)
    );
  }

  handleEvent(event) {
    if (event.type == "load") {
      const url = event.target.location.href;
      lazy.logger.debug(`Handle load event with URL ${url}`);
      this._resolveLoadedURLPromise(url);
    }
  }

  actorCreated() {
    lazy.logger.trace(
      `[${this.browsingContext.id}] Reftest actor created ` +
        `for window id ${this.manager.innerWindowId}`
    );
  }

  async receiveMessage(msg) {
    const { name, data } = msg;

    let result;
    switch (name) {
      case "MarionetteReftestParent:flushRendering":
        result = await this.flushRendering(data);
        break;
      case "MarionetteReftestParent:reftestWait":
        result = await this.reftestWait(data);
        break;
    }
    return result;
  }

  /**
   * Wait for a reftest page to be ready for screenshots:
   * - wait for the loadedURL to be available (see handleEvent)
   * - check if the URL matches the expected URL
   * - if present, wait for the "reftest-wait" classname to be removed from the
   *   document element
   *
   * @param {object} options
   * @param {string} options.url
   *        The expected test page URL
   * @param {boolean} options.useRemote
   *        True when using e10s
   * @param {boolean} options.warnOnOverflow
   *        True if we should check the content fits in the viewport.
   *        This isn't necessary for print reftests where we will render the full
   *        size of the paginated content.
   * @returns {boolean}
   *         Returns true when the correct page is loaded and ready for
   *         screenshots. Returns false if the page loaded bug does not have the
   *         expected URL.
   */
  async reftestWait(options = {}) {
    const { url, useRemote } = options;
    const loadedURL = await this._loadedURLPromise;
    if (loadedURL !== url) {
      lazy.logger.debug(
        `Window URL does not match the expected URL "${loadedURL}" !== "${url}"`
      );
      return false;
    }

    const documentElement = this.document.documentElement;
    const hasReftestWait = documentElement.classList.contains("reftest-wait");

    lazy.logger.debug("Waiting for event loop to spin");
    await new Promise(resolve => lazy.setTimeout(resolve, 0));

    await this.paintComplete({ useRemote, ignoreThrottledAnimations: true });

    if (hasReftestWait) {
      const event = new this.document.defaultView.Event("TestRendered", {
        bubbles: true,
      });
      documentElement.dispatchEvent(event);
      lazy.logger.info("Emitted TestRendered event");
      await this.reftestWaitRemoved();
      await this.paintComplete({ useRemote, ignoreThrottledAnimations: false });
    }
    if (
      options.warnOnOverflow &&
      (this.document.defaultView.innerWidth < documentElement.scrollWidth ||
        this.document.defaultView.innerHeight < documentElement.scrollHeight)
    ) {
      lazy.logger.warn(
        `${url} overflows viewport (width: ${documentElement.scrollWidth}, height: ${documentElement.scrollHeight})`
      );
    }
    return true;
  }

  paintComplete({ useRemote, ignoreThrottledAnimations }) {
    lazy.logger.debug("Waiting for rendering");
    let windowUtils = this.document.defaultView.windowUtils;
    return new Promise(resolve => {
      let maybeResolve = () => {
        this.flushRendering({ ignoreThrottledAnimations });
        if (useRemote) {
          // Flush display (paint)
          lazy.logger.debug("Force update of layer tree");
          windowUtils.updateLayerTree();
        }

        if (windowUtils.isMozAfterPaintPending) {
          lazy.logger.debug("isMozAfterPaintPending: true");
          this.document.defaultView.addEventListener(
            "MozAfterPaint",
            maybeResolve,
            {
              once: true,
            }
          );
        } else {
          // resolve at the start of the next frame in case of leftover paints
          lazy.logger.debug("isMozAfterPaintPending: false");
          this.document.defaultView.requestAnimationFrame(() => {
            this.document.defaultView.requestAnimationFrame(resolve);
          });
        }
      };
      maybeResolve();
    });
  }

  reftestWaitRemoved() {
    lazy.logger.debug("Waiting for reftest-wait removal");
    return new Promise(resolve => {
      const documentElement = this.document.documentElement;
      let observer = new this.document.defaultView.MutationObserver(() => {
        if (!documentElement.classList.contains("reftest-wait")) {
          observer.disconnect();
          lazy.logger.debug("reftest-wait removed");
          lazy.setTimeout(resolve, 0);
        }
      });
      if (documentElement.classList.contains("reftest-wait")) {
        observer.observe(documentElement, { attributes: true });
      } else {
        lazy.setTimeout(resolve, 0);
      }
    });
  }

  /**
   * Ensure layout is flushed in each frame
   *
   * @param {object} options
   * @param {boolean} options.ignoreThrottledAnimations Don't flush
   *        the layout of throttled animations. We can end up in a
   *        situation where flushing a throttled animation causes
   *        mozAfterPaint events even when all rendering we care about
   *        should have ceased. See
   *        https://searchfox.org/mozilla-central/rev/d58860eb739af613774c942c3bb61754123e449b/layout/tools/reftest/reftest-content.js#723-729
   *        for more detail.
   */
  flushRendering(options = {}) {
    let { ignoreThrottledAnimations } = options;
    lazy.logger.debug(
      `flushRendering ignoreThrottledAnimations:${ignoreThrottledAnimations}`
    );
    let anyPendingPaintsGeneratedInDescendants = false;

    let windowUtils = this.document.defaultView.windowUtils;

    function flushWindow(win) {
      let utils = win.windowUtils;
      let afterPaintWasPending = utils.isMozAfterPaintPending;

      let root = win.document.documentElement;
      if (root) {
        try {
          if (ignoreThrottledAnimations) {
            utils.flushLayoutWithoutThrottledAnimations();
          } else {
            root.getBoundingClientRect();
          }
        } catch (e) {
          lazy.logger.error("flushWindow failed", e);
        }
      }

      if (!afterPaintWasPending && utils.isMozAfterPaintPending) {
        anyPendingPaintsGeneratedInDescendants = true;
      }

      for (let i = 0; i < win.frames.length; ++i) {
        // Skip remote frames, flushRendering will be called on their individual
        // MarionetteReftest actor via _recursiveFlushRendering performed from
        // the topmost MarionetteReftest actor.
        if (!Cu.isRemoteProxy(win.frames[i])) {
          flushWindow(win.frames[i]);
        }
      }
    }
    flushWindow(this.document.defaultView);

    if (
      anyPendingPaintsGeneratedInDescendants &&
      !windowUtils.isMozAfterPaintPending
    ) {
      lazy.logger.error(
        "Descendant frame generated a MozAfterPaint event, " +
          "but the root document doesn't have one!"
      );
    }
  }
}
PK
!<8$���Gchrome/remote/content/marionette/actors/MarionetteReftestParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Parent JSWindowActor to handle navigation for reftests relying on marionette.
 */
export class MarionetteReftestParent extends JSWindowActorParent {
  /**
   * Wait for the expected URL to be loaded.
   *
   * @param {string} url
   *        The expected url.
   * @param {boolean} useRemote
   *        True if tests are running with e10s.
   * @param {boolean} warnOnOverflow
   *        True if we should check the content fits in the viewport.
   *        This isn't necessary for print reftests where we will render the full
   *        size of the paginated content.
   * @returns {boolean} true if the page is fully loaded with the expected url,
   *         false otherwise.
   */
  async reftestWait(url, useRemote, warnOnOverflow) {
    try {
      const isCorrectUrl = await this.sendQuery(
        "MarionetteReftestParent:reftestWait",
        {
          url,
          useRemote,
          warnOnOverflow,
        }
      );

      if (isCorrectUrl) {
        // Trigger flush rendering for all remote frames.
        await this._flushRenderingInSubtree({
          ignoreThrottledAnimations: false,
        });
      }

      return isCorrectUrl;
    } catch (e) {
      if (e.name === "AbortError") {
        // If the query is aborted, the window global is being destroyed, most
        // likely because a navigation happened.
        return false;
      }

      // Other errors should not be swallowed.
      throw e;
    }
  }

  /**
   * Call flushRendering on all browsing contexts in the subtree.
   * Each actor will flush rendering in all the same process frames.
   */
  async _flushRenderingInSubtree({ ignoreThrottledAnimations }) {
    const browsingContext = this.manager.browsingContext;
    const contexts = browsingContext.getAllBrowsingContextsInSubtree();

    await Promise.all(
      contexts.map(async context => {
        if (context === browsingContext) {
          // Skip the top browsing context, for which flushRendering is
          // already performed via the initial reftestWait call.
          return;
        }

        const windowGlobal = context.currentWindowGlobal;
        if (!windowGlobal) {
          // Bail out if there is no window attached to the current context.
          return;
        }

        if (!windowGlobal.isProcessRoot) {
          // Bail out if this window global is not a process root.
          // MarionetteReftestChild::flushRendering will flush all same process
          // frames, so we only need to call flushRendering on process roots.
          return;
        }

        const reftestActor = windowGlobal.getActor("MarionetteReftest");
        await reftestActor.sendQuery("MarionetteReftestParent:flushRendering", {
          ignoreThrottledAnimations,
        });
      })
    );
  }
}
PK
!<�n�KK.chrome/remote/content/marionette/addon.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
});

// from https://developer.mozilla.org/en-US/Add-ons/Add-on_Manager/AddonManager#AddonInstall_errors
const ERRORS = {
  [-1]: "ERROR_NETWORK_FAILURE: A network error occured.",
  [-2]: "ERROR_INCORRECT_HASH: The downloaded file did not match the expected hash.",
  [-3]: "ERROR_CORRUPT_FILE: The file appears to be corrupt.",
  [-4]: "ERROR_FILE_ACCESS: There was an error accessing the filesystem.",
  [-5]: "ERROR_SIGNEDSTATE_REQUIRED: The addon must be signed and isn't.",
  [-6]: "ERROR_UNEXPECTED_ADDON_TYPE: The downloaded add-on had a different type than expected (during an update).",
  [-7]: "ERROR_INCORRECT_ID: The addon did not have the expected ID (during an update).",
  [-8]: "ERROR_INVALID_DOMAIN: The addon install_origins does not list the 3rd party domain.",
  [-9]: "ERROR_UNEXPECTED_ADDON_VERSION: The downloaded add-on had a different version than expected (during an update).",
  [-10]: "ERROR_BLOCKLISTED: The add-on is blocklisted.",
  [-11]:
    "ERROR_INCOMPATIBLE: The add-on is incompatible (w.r.t. the compatibility range).",
  [-12]:
    "ERROR_UNSUPPORTED_ADDON_TYPE: The add-on type is not supported by the platform.",
};

async function installAddon(file) {
  let install = await lazy.AddonManager.getInstallForFile(file, null, {
    source: "internal",
  });

  if (install.error) {
    throw new lazy.error.UnknownError(ERRORS[install.error]);
  }

  return install.install().catch(() => {
    throw new lazy.error.UnknownError(ERRORS[install.error]);
  });
}

/** Installs addons by path and uninstalls by ID. */
export class Addon {
  /**
   * Install a Firefox addon.
   *
   * If the addon is restartless, it can be used right away.  Otherwise a
   * restart is required.
   *
   * Temporary addons will automatically be uninstalled on shutdown and
   * do not need to be signed, though they must be restartless.
   *
   * @param {string} path
   *     Full path to the extension package archive.
   * @param {boolean=} temporary
   *     True to install the addon temporarily, false (default) otherwise.
   *
   * @returns {Promise.<string>}
   *     Addon ID.
   *
   * @throws {UnknownError}
   *     If there is a problem installing the addon.
   */
  static async install(path, temporary = false) {
    let addon;
    let file;

    try {
      file = new lazy.FileUtils.File(path);
    } catch (e) {
      throw new lazy.error.UnknownError(`Expected absolute path: ${e}`, e);
    }

    if (!file.exists()) {
      throw new lazy.error.UnknownError(`No such file or directory: ${path}`);
    }

    try {
      if (temporary) {
        addon = await lazy.AddonManager.installTemporaryAddon(file);
      } else {
        addon = await installAddon(file);
      }
    } catch (e) {
      throw new lazy.error.UnknownError(
        `Could not install add-on: ${path}: ${e.message}`,
        e
      );
    }

    return addon.id;
  }

  /**
   * Uninstall a Firefox addon.
   *
   * If the addon is restartless it will be uninstalled right away.
   * Otherwise, Firefox must be restarted for the change to take effect.
   *
   * @param {string} id
   *     ID of the addon to uninstall.
   *
   * @returns {Promise}
   *
   * @throws {UnknownError}
   *     If there is a problem uninstalling the addon.
   */
  static async uninstall(id) {
    let candidate = await lazy.AddonManager.getAddonByID(id);
    if (candidate === null) {
      // `AddonManager.getAddonByID` never rejects but instead
      // returns `null` if the requested addon cannot be found.
      throw new lazy.error.UnknownError(`Addon ${id} is not installed`);
    }

    return new Promise(resolve => {
      let listener = {
        onOperationCancelled: addon => {
          if (addon.id === candidate.id) {
            lazy.AddonManager.removeAddonListener(listener);
            throw new lazy.error.UnknownError(
              `Uninstall of ${candidate.id} has been canceled`
            );
          }
        },

        onUninstalled: addon => {
          if (addon.id === candidate.id) {
            lazy.AddonManager.removeAddonListener(listener);
            resolve();
          }
        },
      };

      lazy.AddonManager.addAddonListener(listener);
      candidate.uninstall();
    });
  }
}
PK
!<`2�~~-chrome/remote/content/marionette/atom.sys.mjs// Copyright 2011-2017 Software Freedom Conservancy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  evaluate: "chrome://remote/content/marionette/evaluate.sys.mjs",
  sandbox: "chrome://remote/content/marionette/evaluate.sys.mjs",
});

/** @namespace */
export const atom = {};

// Follow the instructions to export all the atoms:
// https://firefox-source-docs.mozilla.org/testing/marionette/SeleniumAtoms.html
//
// Built from SHA1: 33c6b7841a59aaaad55744909c0600f066fd5593
const ATOMS = {
  getVisibleText: "function(){return (function(){var h=this||self;\nfunction aa(a){var b=typeof a;if(\"object\"==b)if(a){if(a instanceof Array)return\"array\";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if(\"[object Window]\"==c)return\"object\";if(\"[object Array]\"==c||\"number\"==typeof a.length&&\"undefined\"!=typeof a.splice&&\"undefined\"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable(\"splice\"))return\"array\";if(\"[object Function]\"==c||\"undefined\"!=typeof a.call&&\"undefined\"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable(\"call\"))return\"function\"}else return\"null\";else if(\"function\"==\nb&&\"undefined\"==typeof a.call)return\"object\";return b}function ba(a,b){function c(){}c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a};var ca=Array.prototype.indexOf?function(a,b){return Array.prototype.indexOf.call(a,b,void 0)}:function(a,b){if(\"string\"===typeof a)return\"string\"!==typeof b||1!=b.length?-1:a.indexOf(b,0);for(var c=0;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},m=Array.prototype.forEach?function(a,b){Array.prototype.forEach.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=\"string\"===typeof a?a.split(\"\"):a,e=0;e<c;e++)e in d&&b.call(void 0,d[e],e,a)},da=Array.prototype.map?function(a,b){return Array.prototype.map.call(a,\nb,void 0)}:function(a,b){for(var c=a.length,d=Array(c),e=\"string\"===typeof a?a.split(\"\"):a,f=0;f<c;f++)f in e&&(d[f]=b.call(void 0,e[f],f,a));return d},fa=Array.prototype.some?function(a,b){return Array.prototype.some.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=\"string\"===typeof a?a.split(\"\"):a,e=0;e<c;e++)if(e in d&&b.call(void 0,d[e],e,a))return!0;return!1},ha=Array.prototype.every?function(a,b){return Array.prototype.every.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=\"string\"===\ntypeof a?a.split(\"\"):a,e=0;e<c;e++)if(e in d&&!b.call(void 0,d[e],e,a))return!1;return!0};var ia={aliceblue:\"#f0f8ff\",antiquewhite:\"#faebd7\",aqua:\"#00ffff\",aquamarine:\"#7fffd4\",azure:\"#f0ffff\",beige:\"#f5f5dc\",bisque:\"#ffe4c4\",black:\"#000000\",blanchedalmond:\"#ffebcd\",blue:\"#0000ff\",blueviolet:\"#8a2be2\",brown:\"#a52a2a\",burlywood:\"#deb887\",cadetblue:\"#5f9ea0\",chartreuse:\"#7fff00\",chocolate:\"#d2691e\",coral:\"#ff7f50\",cornflowerblue:\"#6495ed\",cornsilk:\"#fff8dc\",crimson:\"#dc143c\",cyan:\"#00ffff\",darkblue:\"#00008b\",darkcyan:\"#008b8b\",darkgoldenrod:\"#b8860b\",darkgray:\"#a9a9a9\",darkgreen:\"#006400\",\ndarkgrey:\"#a9a9a9\",darkkhaki:\"#bdb76b\",darkmagenta:\"#8b008b\",darkolivegreen:\"#556b2f\",darkorange:\"#ff8c00\",darkorchid:\"#9932cc\",darkred:\"#8b0000\",darksalmon:\"#e9967a\",darkseagreen:\"#8fbc8f\",darkslateblue:\"#483d8b\",darkslategray:\"#2f4f4f\",darkslategrey:\"#2f4f4f\",darkturquoise:\"#00ced1\",darkviolet:\"#9400d3\",deeppink:\"#ff1493\",deepskyblue:\"#00bfff\",dimgray:\"#696969\",dimgrey:\"#696969\",dodgerblue:\"#1e90ff\",firebrick:\"#b22222\",floralwhite:\"#fffaf0\",forestgreen:\"#228b22\",fuchsia:\"#ff00ff\",gainsboro:\"#dcdcdc\",\nghostwhite:\"#f8f8ff\",gold:\"#ffd700\",goldenrod:\"#daa520\",gray:\"#808080\",green:\"#008000\",greenyellow:\"#adff2f\",grey:\"#808080\",honeydew:\"#f0fff0\",hotpink:\"#ff69b4\",indianred:\"#cd5c5c\",indigo:\"#4b0082\",ivory:\"#fffff0\",khaki:\"#f0e68c\",lavender:\"#e6e6fa\",lavenderblush:\"#fff0f5\",lawngreen:\"#7cfc00\",lemonchiffon:\"#fffacd\",lightblue:\"#add8e6\",lightcoral:\"#f08080\",lightcyan:\"#e0ffff\",lightgoldenrodyellow:\"#fafad2\",lightgray:\"#d3d3d3\",lightgreen:\"#90ee90\",lightgrey:\"#d3d3d3\",lightpink:\"#ffb6c1\",lightsalmon:\"#ffa07a\",\nlightseagreen:\"#20b2aa\",lightskyblue:\"#87cefa\",lightslategray:\"#778899\",lightslategrey:\"#778899\",lightsteelblue:\"#b0c4de\",lightyellow:\"#ffffe0\",lime:\"#00ff00\",limegreen:\"#32cd32\",linen:\"#faf0e6\",magenta:\"#ff00ff\",maroon:\"#800000\",mediumaquamarine:\"#66cdaa\",mediumblue:\"#0000cd\",mediumorchid:\"#ba55d3\",mediumpurple:\"#9370db\",mediumseagreen:\"#3cb371\",mediumslateblue:\"#7b68ee\",mediumspringgreen:\"#00fa9a\",mediumturquoise:\"#48d1cc\",mediumvioletred:\"#c71585\",midnightblue:\"#191970\",mintcream:\"#f5fffa\",mistyrose:\"#ffe4e1\",\nmoccasin:\"#ffe4b5\",navajowhite:\"#ffdead\",navy:\"#000080\",oldlace:\"#fdf5e6\",olive:\"#808000\",olivedrab:\"#6b8e23\",orange:\"#ffa500\",orangered:\"#ff4500\",orchid:\"#da70d6\",palegoldenrod:\"#eee8aa\",palegreen:\"#98fb98\",paleturquoise:\"#afeeee\",palevioletred:\"#db7093\",papayawhip:\"#ffefd5\",peachpuff:\"#ffdab9\",peru:\"#cd853f\",pink:\"#ffc0cb\",plum:\"#dda0dd\",powderblue:\"#b0e0e6\",purple:\"#800080\",red:\"#ff0000\",rosybrown:\"#bc8f8f\",royalblue:\"#4169e1\",saddlebrown:\"#8b4513\",salmon:\"#fa8072\",sandybrown:\"#f4a460\",seagreen:\"#2e8b57\",\nseashell:\"#fff5ee\",sienna:\"#a0522d\",silver:\"#c0c0c0\",skyblue:\"#87ceeb\",slateblue:\"#6a5acd\",slategray:\"#708090\",slategrey:\"#708090\",snow:\"#fffafa\",springgreen:\"#00ff7f\",steelblue:\"#4682b4\",tan:\"#d2b48c\",teal:\"#008080\",thistle:\"#d8bfd8\",tomato:\"#ff6347\",turquoise:\"#40e0d0\",violet:\"#ee82ee\",wheat:\"#f5deb3\",white:\"#ffffff\",whitesmoke:\"#f5f5f5\",yellow:\"#ffff00\",yellowgreen:\"#9acd32\"};var ja=\"backgroundColor borderTopColor borderRightColor borderBottomColor borderLeftColor color outlineColor\".split(\" \"),ka=/#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/,la=/^#(?:[0-9a-f]{3}){1,2}$/i,ma=/^(?:rgba)?\\((\\d{1,3}),\\s?(\\d{1,3}),\\s?(\\d{1,3}),\\s?(0|1|0\\.\\d*)\\)$/i,na=/^(?:rgb)?\\((0|[1-9]\\d{0,2}),\\s?(0|[1-9]\\d{0,2}),\\s?(0|[1-9]\\d{0,2})\\)$/i;function p(a,b){this.code=a;this.a=q[a]||u;this.message=b||\"\";a=this.a.replace(/((?:^|\\s+)[a-z])/g,function(c){return c.toUpperCase().replace(/^[\\s\\xa0]+/g,\"\")});b=a.length-5;if(0>b||a.indexOf(\"Error\",b)!=b)a+=\"Error\";this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||\"\"}ba(p,Error);var u=\"unknown error\",q={15:\"element not selectable\",11:\"element not visible\"};q[31]=u;q[30]=u;q[24]=\"invalid cookie domain\";q[29]=\"invalid element coordinates\";q[12]=\"invalid element state\";\nq[32]=\"invalid selector\";q[51]=\"invalid selector\";q[52]=\"invalid selector\";q[17]=\"javascript error\";q[405]=\"unsupported operation\";q[34]=\"move target out of bounds\";q[27]=\"no such alert\";q[7]=\"no such element\";q[8]=\"no such frame\";q[23]=\"no such window\";q[28]=\"script timeout\";q[33]=\"session not created\";q[10]=\"stale element reference\";q[21]=\"timeout\";q[25]=\"unable to set cookie\";q[26]=\"unexpected alert open\";q[13]=u;q[9]=\"unknown command\";function oa(a){var b=a.length-1;return 0<=b&&a.indexOf(\" \",b)==b}var v=String.prototype.trim?function(a){return a.trim()}:function(a){return/^[\\s\\xa0]*([\\s\\S]*?)[\\s\\xa0]*$/.exec(a)[1]};\nfunction pa(a,b){var c=0;a=v(String(a)).split(\".\");b=v(String(b)).split(\".\");for(var d=Math.max(a.length,b.length),e=0;0==c&&e<d;e++){var f=a[e]||\"\",g=b[e]||\"\";do{f=/(\\d*)(\\D*)(.*)/.exec(f)||[\"\",\"\",\"\",\"\"];g=/(\\d*)(\\D*)(.*)/.exec(g)||[\"\",\"\",\"\",\"\"];if(0==f[0].length&&0==g[0].length)break;c=w(0==f[1].length?0:parseInt(f[1],10),0==g[1].length?0:parseInt(g[1],10))||w(0==f[2].length,0==g[2].length)||w(f[2],g[2]);f=f[3];g=g[3]}while(0==c)}return c}function w(a,b){return a<b?-1:a>b?1:0};var x;a:{var qa=h.navigator;if(qa){var ra=qa.userAgent;if(ra){x=ra;break a}}x=\"\"}function z(a){return-1!=x.indexOf(a)};function B(){return z(\"Firefox\")||z(\"FxiOS\")}function C(){return(z(\"Chrome\")||z(\"CriOS\"))&&!z(\"Edge\")};function sa(a){return String(a).replace(/\\-([a-z])/g,function(b,c){return c.toUpperCase()})};function D(){return z(\"iPhone\")&&!z(\"iPod\")&&!z(\"iPad\")};function ta(a,b){var c=ua;return Object.prototype.hasOwnProperty.call(c,a)?c[a]:c[a]=b(a)};var va=z(\"Opera\"),E=z(\"Trident\")||z(\"MSIE\"),ya=z(\"Edge\"),za=z(\"Gecko\")&&!(-1!=x.toLowerCase().indexOf(\"webkit\")&&!z(\"Edge\"))&&!(z(\"Trident\")||z(\"MSIE\"))&&!z(\"Edge\"),Aa=-1!=x.toLowerCase().indexOf(\"webkit\")&&!z(\"Edge\");function Ba(){var a=h.document;return a?a.documentMode:void 0}var F;\na:{var G=\"\",I=function(){var a=x;if(za)return/rv:([^\\);]+)(\\)|;)/.exec(a);if(ya)return/Edge\\/([\\d\\.]+)/.exec(a);if(E)return/\\b(?:MSIE|rv)[: ]([^\\);]+)(\\)|;)/.exec(a);if(Aa)return/WebKit\\/(\\S+)/.exec(a);if(va)return/(?:Version)[ \\/]?(\\S+)/.exec(a)}();I&&(G=I?I[1]:\"\");if(E){var J=Ba();if(null!=J&&J>parseFloat(G)){F=String(J);break a}}F=G}var ua={};function Ca(a){return ta(a,function(){return 0<=pa(F,a)})}var Da;Da=h.document&&E?Ba():void 0;var Ea=B(),Fa=D()||z(\"iPod\"),Ga=z(\"iPad\"),Ha=z(\"Android\")&&!(C()||B()||z(\"Opera\")||z(\"Silk\")),Ia=C(),Ja=z(\"Safari\")&&!(C()||z(\"Coast\")||z(\"Opera\")||z(\"Edge\")||z(\"Edg/\")||z(\"OPR\")||B()||z(\"Silk\")||z(\"Android\"))&&!(D()||z(\"iPad\")||z(\"iPod\"));function K(a){return(a=a.exec(x))?a[1]:\"\"}(function(){if(Ea)return K(/Firefox\\/([0-9.]+)/);if(E||ya||va)return F;if(Ia)return D()||z(\"iPad\")||z(\"iPod\")?K(/CriOS\\/([0-9.]+)/):K(/Chrome\\/([0-9.]+)/);if(Ja&&!(D()||z(\"iPad\")||z(\"iPod\")))return K(/Version\\/([0-9.]+)/);if(Fa||Ga){var a=/Version\\/(\\S+).*Mobile\\/(\\S+)/.exec(x);if(a)return a[1]+\".\"+a[2]}else if(Ha)return(a=K(/Android\\s+([0-9.]+)/))?a:K(/Version\\/([0-9.]+)/);return\"\"})();var Ka;if(Ka=E)Ka=!(9<=Number(Da));var La=Ka;function L(a,b){this.x=void 0!==a?a:0;this.y=void 0!==b?b:0}L.prototype.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};L.prototype.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};L.prototype.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};function M(a,b){this.width=a;this.height=b}M.prototype.aspectRatio=function(){return this.width/this.height};M.prototype.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};M.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};M.prototype.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this};function Ma(a){for(;a&&1!=a.nodeType;)a=a.previousSibling;return a}function N(a){return 9==a.nodeType?a:a.ownerDocument||a.document}function Na(a,b){a&&(a=a.parentNode);for(var c=0;a;){if(b(a))return a;a=a.parentNode;c++}return null}function Oa(a){this.a=a||h.document||document};function O(a,b){b&&\"string\"!==typeof b&&(b=b.toString());return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)};function P(a,b,c,d){this.f=a;this.a=b;this.b=c;this.c=d}P.prototype.ceil=function(){this.f=Math.ceil(this.f);this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.c=Math.ceil(this.c);return this};P.prototype.floor=function(){this.f=Math.floor(this.f);this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.c=Math.floor(this.c);return this};P.prototype.round=function(){this.f=Math.round(this.f);this.a=Math.round(this.a);this.b=Math.round(this.b);this.c=Math.round(this.c);return this};function R(a,b,c,d){this.a=a;this.b=b;this.width=c;this.height=d}R.prototype.ceil=function(){this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};R.prototype.floor=function(){this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};\nR.prototype.round=function(){this.a=Math.round(this.a);this.b=Math.round(this.b);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};var Pa=\"function\"===typeof ShadowRoot;function S(a){for(a=a.parentNode;a&&1!=a.nodeType&&9!=a.nodeType&&11!=a.nodeType;)a=a.parentNode;return O(a)?a:null}\nfunction T(a,b){b=sa(b);if(\"float\"==b||\"cssFloat\"==b||\"styleFloat\"==b)b=La?\"styleFloat\":\"cssFloat\";a:{var c=b;var d=N(a);if(d.defaultView&&d.defaultView.getComputedStyle&&(d=d.defaultView.getComputedStyle(a,null))){c=d[c]||d.getPropertyValue(c)||\"\";break a}c=\"\"}a=c||Qa(a,b);if(null===a)a=null;else if(0<=ca(ja,b)){b:{var e=a.match(ma);if(e&&(b=Number(e[1]),c=Number(e[2]),d=Number(e[3]),e=Number(e[4]),0<=b&&255>=b&&0<=c&&255>=c&&0<=d&&255>=d&&0<=e&&1>=e)){b=[b,c,d,e];break b}b=null}if(!b)b:{if(d=a.match(na))if(b=\nNumber(d[1]),c=Number(d[2]),d=Number(d[3]),0<=b&&255>=b&&0<=c&&255>=c&&0<=d&&255>=d){b=[b,c,d,1];break b}b=null}if(!b)b:{b=a.toLowerCase();c=ia[b.toLowerCase()];if(!c&&(c=\"#\"==b.charAt(0)?b:\"#\"+b,4==c.length&&(c=c.replace(ka,\"#$1$1$2$2$3$3\")),!la.test(c))){b=null;break b}b=[parseInt(c.substr(1,2),16),parseInt(c.substr(3,2),16),parseInt(c.substr(5,2),16),1]}a=b?\"rgba(\"+b.join(\", \")+\")\":a}return a}\nfunction Qa(a,b){var c=a.currentStyle||a.style,d=c[b];void 0===d&&\"function\"==aa(c.getPropertyValue)&&(d=c.getPropertyValue(b));return\"inherit\"!=d?void 0!==d?d:null:(a=S(a))?Qa(a,b):null}\nfunction Ra(a,b,c){function d(g){var k=U(g);return 0<k.height&&0<k.width?!0:O(g,\"PATH\")&&(0<k.height||0<k.width)?(g=T(g,\"stroke-width\"),!!g&&0<parseInt(g,10)):\"hidden\"!=T(g,\"overflow\")&&fa(g.childNodes,function(y){return 3==y.nodeType||O(y)&&d(y)})}function e(g){return Sa(g)==V&&ha(g.childNodes,function(k){return!O(k)||e(k)||!d(k)})}if(!O(a))throw Error(\"Argument to isShown must be of type Element\");if(O(a,\"BODY\"))return!0;if(O(a,\"OPTION\")||O(a,\"OPTGROUP\"))return a=Na(a,function(g){return O(g,\"SELECT\")}),\n!!a&&Ra(a,!0,c);var f=Ta(a);if(f)return!!f.image&&0<f.rect.width&&0<f.rect.height&&Ra(f.image,b,c);if(O(a,\"INPUT\")&&\"hidden\"==a.type.toLowerCase()||O(a,\"NOSCRIPT\"))return!1;f=T(a,\"visibility\");return\"collapse\"!=f&&\"hidden\"!=f&&c(a)&&(b||0!=Ua(a))&&d(a)?!e(a):!1}\nfunction Va(a){function b(c){if(O(c)&&\"none\"==T(c,\"display\"))return!1;var d;if((d=c.parentNode)&&d.shadowRoot&&void 0!==c.assignedSlot)d=c.assignedSlot?c.assignedSlot.parentNode:null;else if(c.getDestinationInsertionPoints){var e=c.getDestinationInsertionPoints();0<e.length&&(d=e[e.length-1])}if(Pa&&d instanceof ShadowRoot){if(d.host.shadowRoot&&d.host.shadowRoot!==d)return!1;d=d.host}return!d||9!=d.nodeType&&11!=d.nodeType?d&&O(d,\"DETAILS\")&&!d.open&&!O(c,\"SUMMARY\")?!1:!!d&&b(d):!0}return Ra(a,!1,\nb)}var V=\"hidden\";\nfunction Sa(a){function b(l){function n(ea){if(ea==g)return!0;var wa=T(ea,\"display\");return 0==wa.lastIndexOf(\"inline\",0)||\"contents\"==wa||\"absolute\"==xa&&\"static\"==T(ea,\"position\")?!1:!0}var xa=T(l,\"position\");if(\"fixed\"==xa)return H=!0,l==g?null:g;for(l=S(l);l&&!n(l);)l=S(l);return l}function c(l){var n=l;if(\"visible\"==y)if(l==g&&k)n=k;else if(l==k)return{x:\"visible\",y:\"visible\"};n={x:T(n,\"overflow-x\"),y:T(n,\"overflow-y\")};l==g&&(n.x=\"visible\"==n.x?\"auto\":n.x,n.y=\"visible\"==n.y?\"auto\":n.y);return n}\nfunction d(l){if(l==g){var n=(new Oa(f)).a;l=n.scrollingElement?n.scrollingElement:Aa||\"CSS1Compat\"!=n.compatMode?n.body||n.documentElement:n.documentElement;n=n.parentWindow||n.defaultView;l=E&&Ca(\"10\")&&n.pageYOffset!=l.scrollTop?new L(l.scrollLeft,l.scrollTop):new L(n.pageXOffset||l.scrollLeft,n.pageYOffset||l.scrollTop)}else l=new L(l.scrollLeft,l.scrollTop);return l}var e=Wa(a),f=N(a),g=f.documentElement,k=f.body,y=T(g,\"overflow\"),H;for(a=b(a);a;a=b(a)){var r=c(a);if(\"visible\"!=r.x||\"visible\"!=\nr.y){var t=U(a);if(0==t.width||0==t.height)return V;var A=e.a<t.a,Q=e.b<t.b;if(A&&\"hidden\"==r.x||Q&&\"hidden\"==r.y)return V;if(A&&\"visible\"!=r.x||Q&&\"visible\"!=r.y){A=d(a);Q=e.b<t.b-A.y;if(e.a<t.a-A.x&&\"visible\"!=r.x||Q&&\"visible\"!=r.x)return V;e=Sa(a);return e==V?V:\"scroll\"}A=e.c>=t.a+t.width;t=e.f>=t.b+t.height;if(A&&\"hidden\"==r.x||t&&\"hidden\"==r.y)return V;if(A&&\"visible\"!=r.x||t&&\"visible\"!=r.y){if(H&&(r=d(a),e.c>=g.scrollWidth-r.x||e.a>=g.scrollHeight-r.y))return V;e=Sa(a);return e==V?V:\"scroll\"}}}return\"none\"}\nfunction U(a){var b=Ta(a);if(b)return b.rect;if(O(a,\"HTML\"))return a=N(a),a=((a?a.parentWindow||a.defaultView:window)||window).document,a=\"CSS1Compat\"==a.compatMode?a.documentElement:a.body,a=new M(a.clientWidth,a.clientHeight),new R(0,0,a.width,a.height);try{var c=a.getBoundingClientRect()}catch(d){return new R(0,0,0,0)}b=new R(c.left,c.top,c.right-c.left,c.bottom-c.top);E&&a.ownerDocument.body&&(a=N(a),b.a-=a.documentElement.clientLeft+a.body.clientLeft,b.b-=a.documentElement.clientTop+a.body.clientTop);\nreturn b}\nfunction Ta(a){var b=O(a,\"MAP\");if(!b&&!O(a,\"AREA\"))return null;var c=b?a:O(a.parentNode,\"MAP\")?a.parentNode:null,d=null,e=null;if(c&&c.name){d='*[usemap=\"#'+c.name+'\"]';c=N(c);var f;if(f=\"function\"!=aa(c.querySelector)&&E&&(E?0<=pa(Da,8):Ca(8))){f=c.querySelector;var g=typeof f;f=!(\"object\"==g&&null!=f||\"function\"==g)}if(f)throw Error(\"CSS selection is not supported\");if(!d)throw new p(32,\"No selector specified\");d=v(d);try{var k=c.querySelector(d)}catch(y){throw new p(32,\"An invalid or illegal selector was specified\");}if(d=\nk&&1==k.nodeType?k:null)e=U(d),b||\"default\"==a.shape.toLowerCase()||(a=Xa(a),b=Math.min(Math.max(a.a,0),e.width),k=Math.min(Math.max(a.b,0),e.height),e=new R(b+e.a,k+e.b,Math.min(a.width,e.width-b),Math.min(a.height,e.height-k)))}return{image:d,rect:e||new R(0,0,0,0)}}\nfunction Xa(a){var b=a.shape.toLowerCase();a=a.coords.split(\",\");if(\"rect\"==b&&4==a.length){b=a[0];var c=a[1];return new R(b,c,a[2]-b,a[3]-c)}if(\"circle\"==b&&3==a.length)return b=a[2],new R(a[0]-b,a[1]-b,2*b,2*b);if(\"poly\"==b&&2<a.length){b=a[0];c=a[1];for(var d=b,e=c,f=2;f+1<a.length;f+=2)b=Math.min(b,a[f]),d=Math.max(d,a[f]),c=Math.min(c,a[f+1]),e=Math.max(e,a[f+1]);return new R(b,c,d-b,e-c)}return new R(0,0,0,0)}function Wa(a){a=U(a);return new P(a.b,a.a+a.width,a.b+a.height,a.a)}\nfunction Ya(a){return a.replace(/^[^\\S\\xa0]+|[^\\S\\xa0]+$/g,\"\")}\nfunction Za(a,b,c){if(O(a,\"BR\"))b.push(\"\");else{var d=O(a,\"TD\"),e=T(a,\"display\"),f=!d&&!(0<=ca($a,e)),g=void 0!==a.previousElementSibling?a.previousElementSibling:Ma(a.previousSibling);g=g?T(g,\"display\"):\"\";var k=T(a,\"float\")||T(a,\"cssFloat\")||T(a,\"styleFloat\");!f||\"run-in\"==g&&\"none\"==k||/^[\\s\\xa0]*$/.test(b[b.length-1]||\"\")||b.push(\"\");var y=Va(a),H=null,r=null;y&&(H=T(a,\"white-space\"),r=T(a,\"text-transform\"));m(a.childNodes,function(t){c(t,b,y,H,r)});a=b[b.length-1]||\"\";!d&&\"table-cell\"!=e||!a||\noa(a)||(b[b.length-1]+=\" \");f&&\"run-in\"!=e&&!/^[\\s\\xa0]*$/.test(a)&&b.push(\"\")}}function ab(a,b){Za(a,b,function(c,d,e,f,g){3==c.nodeType&&e?bb(c,d,f,g):O(c)&&ab(c,d)})}var $a=\"inline inline-block inline-table none table-cell table-column table-column-group\".split(\" \");\nfunction bb(a,b,c,d){a=a.nodeValue.replace(/[\\u200b\\u200e\\u200f]/g,\"\");a=a.replace(/(\\r\\n|\\r|\\n)/g,\"\\n\");if(\"normal\"==c||\"nowrap\"==c)a=a.replace(/\\n/g,\" \");a=\"pre\"==c||\"pre-wrap\"==c?a.replace(/[ \\f\\t\\v\\u2028\\u2029]/g,\"\\u00a0\"):a.replace(/[ \\f\\t\\v\\u2028\\u2029]+/g,\" \");\"capitalize\"==d?a=a.replace(E?/(^|\\s|\\b)(\\S)/g:/(^|\\s|\\b)(\\S)/gu,function(e,f,g){return f+g.toUpperCase()}):\"uppercase\"==d?a=a.toUpperCase():\"lowercase\"==d&&(a=a.toLowerCase());c=b.pop()||\"\";oa(c)&&0==a.lastIndexOf(\" \",0)&&(a=a.substr(1));\nb.push(c+a)}function Ua(a){if(La){if(\"relative\"==T(a,\"position\"))return 1;a=T(a,\"filter\");return(a=a.match(/^alpha\\(opacity=(\\d*)\\)/)||a.match(/^progid:DXImageTransform.Microsoft.Alpha\\(Opacity=(\\d*)\\)/))?Number(a[1])/100:1}return cb(a)}function cb(a){var b=1,c=T(a,\"opacity\");c&&(b=Number(c));(a=S(a))&&(b*=cb(a));return b}\nfunction W(a,b,c,d,e){if(3==a.nodeType&&c)bb(a,b,d,e);else if(O(a))if(O(a,\"CONTENT\")||O(a,\"SLOT\")){for(var f=a;f.parentNode;)f=f.parentNode;f instanceof ShadowRoot?(f=O(a,\"CONTENT\")?a.getDistributedNodes():a.assignedNodes(),m(0<f.length?f:a.childNodes,function(g){W(g,b,c,d,e)})):db(a,b)}else if(O(a,\"SHADOW\")){for(f=a;f.parentNode;)f=f.parentNode;if(f instanceof ShadowRoot&&(a=f))for(a=a.olderShadowRoot;a;)m(a.childNodes,function(g){W(g,b,c,d,e)}),a=a.olderShadowRoot}else db(a,b)}\nfunction db(a,b){a.shadowRoot&&m(a.shadowRoot.childNodes,function(c){W(c,b,!0,null,null)});Za(a,b,function(c,d,e,f,g){var k=null;1==c.nodeType?k=c:3==c.nodeType&&(k=c);null!=k&&(null!=k.assignedSlot||k.getDestinationInsertionPoints&&0<k.getDestinationInsertionPoints().length)||W(c,d,e,f,g)})};function eb(a){var b=[];Pa?db(a,b):ab(a,b);a=da(b,Ya);return Ya(a.join(\"\\n\")).replace(/\\xa0/g,\" \")}var X=[\"_\"],Y=h;X[0]in Y||\"undefined\"==typeof Y.execScript||Y.execScript(\"var \"+X[0]);for(var Z;X.length&&(Z=X.shift());)X.length||void 0===eb?Y[Z]&&Y[Z]!==Object.prototype[Z]?Y=Y[Z]:Y=Y[Z]={}:Y[Z]=eb;; return this._.apply(null,arguments);}).apply({navigator:typeof window!='undefined'?window.navigator:null,document:typeof window!='undefined'?window.document:null}, arguments);}\n",
  isElementDisplayed: "function(){return (function(){var k=this||self;function aa(a){return\"string\"==typeof a}function ba(a,b){a=a.split(\".\");var c=k;a[0]in c||\"undefined\"==typeof c.execScript||c.execScript(\"var \"+a[0]);for(var d;a.length&&(d=a.shift());)a.length||void 0===b?c[d]&&c[d]!==Object.prototype[d]?c=c[d]:c=c[d]={}:c[d]=b}\nfunction ca(a){var b=typeof a;if(\"object\"==b)if(a){if(a instanceof Array)return\"array\";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if(\"[object Window]\"==c)return\"object\";if(\"[object Array]\"==c||\"number\"==typeof a.length&&\"undefined\"!=typeof a.splice&&\"undefined\"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable(\"splice\"))return\"array\";if(\"[object Function]\"==c||\"undefined\"!=typeof a.call&&\"undefined\"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable(\"call\"))return\"function\"}else return\"null\";\nelse if(\"function\"==b&&\"undefined\"==typeof a.call)return\"object\";return b}function da(a,b,c){return a.call.apply(a.bind,arguments)}function ea(a,b,c){if(!a)throw Error();if(2<arguments.length){var d=Array.prototype.slice.call(arguments,2);return function(){var e=Array.prototype.slice.call(arguments);Array.prototype.unshift.apply(e,d);return a.apply(b,e)}}return function(){return a.apply(b,arguments)}}\nfunction fa(a,b,c){Function.prototype.bind&&-1!=Function.prototype.bind.toString().indexOf(\"native code\")?fa=da:fa=ea;return fa.apply(null,arguments)}function ha(a,b){var c=Array.prototype.slice.call(arguments,1);return function(){var d=c.slice();d.push.apply(d,arguments);return a.apply(this,d)}}function l(a,b){function c(){}c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a};/*\n\n The MIT License\n\n Copyright (c) 2007 Cybozu Labs, Inc.\n Copyright (c) 2012 Google Inc.\n\n Permission is hereby granted, free of charge, to any person obtaining a copy\n of this software and associated documentation files (the \"Software\"), to\n deal in the Software without restriction, including without limitation the\n rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n sell copies of the Software, and to permit persons to whom the Software is\n furnished to do so, subject to the following conditions:\n\n The above copyright notice and this permission notice shall be included in\n all copies or substantial portions of the Software.\n\n THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n IN THE SOFTWARE.\n*/\nfunction ia(a,b,c){this.a=a;this.b=b||1;this.f=c||1};var ja=Array.prototype.indexOf?function(a,b){return Array.prototype.indexOf.call(a,b,void 0)}:function(a,b){if(\"string\"===typeof a)return\"string\"!==typeof b||1!=b.length?-1:a.indexOf(b,0);for(var c=0;c<a.length;c++)if(c in a&&a[c]===b)return c;return-1},n=Array.prototype.forEach?function(a,b){Array.prototype.forEach.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=\"string\"===typeof a?a.split(\"\"):a,e=0;e<c;e++)e in d&&b.call(void 0,d[e],e,a)},ka=Array.prototype.filter?function(a,b){return Array.prototype.filter.call(a,\nb,void 0)}:function(a,b){for(var c=a.length,d=[],e=0,f=\"string\"===typeof a?a.split(\"\"):a,g=0;g<c;g++)if(g in f){var h=f[g];b.call(void 0,h,g,a)&&(d[e++]=h)}return d},la=Array.prototype.reduce?function(a,b,c){return Array.prototype.reduce.call(a,b,c)}:function(a,b,c){var d=c;n(a,function(e,f){d=b.call(void 0,d,e,f,a)});return d},ma=Array.prototype.some?function(a,b){return Array.prototype.some.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=\"string\"===typeof a?a.split(\"\"):a,e=0;e<c;e++)if(e in\nd&&b.call(void 0,d[e],e,a))return!0;return!1},na=Array.prototype.every?function(a,b){return Array.prototype.every.call(a,b,void 0)}:function(a,b){for(var c=a.length,d=\"string\"===typeof a?a.split(\"\"):a,e=0;e<c;e++)if(e in d&&!b.call(void 0,d[e],e,a))return!1;return!0};function oa(a,b){a:{for(var c=a.length,d=\"string\"===typeof a?a.split(\"\"):a,e=0;e<c;e++)if(e in d&&b.call(void 0,d[e],e,a)){b=e;break a}b=-1}return 0>b?null:\"string\"===typeof a?a.charAt(b):a[b]}\nfunction pa(a){return Array.prototype.concat.apply([],arguments)}function qa(a,b,c){return 2>=arguments.length?Array.prototype.slice.call(a,b):Array.prototype.slice.call(a,b,c)};var ra=String.prototype.trim?function(a){return a.trim()}:function(a){return/^[\\s\\xa0]*([\\s\\S]*?)[\\s\\xa0]*$/.exec(a)[1]};function sa(a,b){return a<b?-1:a>b?1:0};var t;a:{var ta=k.navigator;if(ta){var ua=ta.userAgent;if(ua){t=ua;break a}}t=\"\"}function u(a){return-1!=t.indexOf(a)};function va(){return u(\"Firefox\")||u(\"FxiOS\")}function wa(){return(u(\"Chrome\")||u(\"CriOS\"))&&!u(\"Edge\")};function xa(a){return String(a).replace(/\\-([a-z])/g,function(b,c){return c.toUpperCase()})};function ya(){return u(\"iPhone\")&&!u(\"iPod\")&&!u(\"iPad\")};function za(a,b){var c=Aa;return Object.prototype.hasOwnProperty.call(c,a)?c[a]:c[a]=b(a)};var Ba=u(\"Opera\"),v=u(\"Trident\")||u(\"MSIE\"),Ca=u(\"Edge\"),Da=u(\"Gecko\")&&!(-1!=t.toLowerCase().indexOf(\"webkit\")&&!u(\"Edge\"))&&!(u(\"Trident\")||u(\"MSIE\"))&&!u(\"Edge\"),Ea=-1!=t.toLowerCase().indexOf(\"webkit\")&&!u(\"Edge\");function Fa(){var a=k.document;return a?a.documentMode:void 0}var Ga;\na:{var Ha=\"\",Ia=function(){var a=t;if(Da)return/rv:([^\\);]+)(\\)|;)/.exec(a);if(Ca)return/Edge\\/([\\d\\.]+)/.exec(a);if(v)return/\\b(?:MSIE|rv)[: ]([^\\);]+)(\\)|;)/.exec(a);if(Ea)return/WebKit\\/(\\S+)/.exec(a);if(Ba)return/(?:Version)[ \\/]?(\\S+)/.exec(a)}();Ia&&(Ha=Ia?Ia[1]:\"\");if(v){var Ja=Fa();if(null!=Ja&&Ja>parseFloat(Ha)){Ga=String(Ja);break a}}Ga=Ha}var Aa={};\nfunction Ka(a){return za(a,function(){for(var b=0,c=ra(String(Ga)).split(\".\"),d=ra(String(a)).split(\".\"),e=Math.max(c.length,d.length),f=0;0==b&&f<e;f++){var g=c[f]||\"\",h=d[f]||\"\";do{g=/(\\d*)(\\D*)(.*)/.exec(g)||[\"\",\"\",\"\",\"\"];h=/(\\d*)(\\D*)(.*)/.exec(h)||[\"\",\"\",\"\",\"\"];if(0==g[0].length&&0==h[0].length)break;b=sa(0==g[1].length?0:parseInt(g[1],10),0==h[1].length?0:parseInt(h[1],10))||sa(0==g[2].length,0==h[2].length)||sa(g[2],h[2]);g=g[3];h=h[3]}while(0==b)}return 0<=b})}var La;\nLa=k.document&&v?Fa():void 0;var x=v&&!(9<=Number(La)),Ma=v&&!(8<=Number(La));function Na(a,b,c,d){this.a=a;this.nodeName=c;this.nodeValue=d;this.nodeType=2;this.parentNode=this.ownerElement=b}function Oa(a,b){var c=Ma&&\"href\"==b.nodeName?a.getAttribute(b.nodeName,2):b.nodeValue;return new Na(b,a,b.nodeName,c)};function Pa(a){this.b=a;this.a=0}function Qa(a){a=a.match(Ra);for(var b=0;b<a.length;b++)Sa.test(a[b])&&a.splice(b,1);return new Pa(a)}var Ra=/\\$?(?:(?![0-9-\\.])(?:\\*|[\\w-\\.]+):)?(?![0-9-\\.])(?:\\*|[\\w-\\.]+)|\\/\\/|\\.\\.|::|\\d+(?:\\.\\d*)?|\\.\\d+|\"[^\"]*\"|'[^']*'|[!<>]=|\\s+|./g,Sa=/^\\s/;function y(a,b){return a.b[a.a+(b||0)]}function z(a){return a.b[a.a++]}function Ta(a){return a.b.length<=a.a};function Ua(a,b){this.x=void 0!==a?a:0;this.y=void 0!==b?b:0}Ua.prototype.ceil=function(){this.x=Math.ceil(this.x);this.y=Math.ceil(this.y);return this};Ua.prototype.floor=function(){this.x=Math.floor(this.x);this.y=Math.floor(this.y);return this};Ua.prototype.round=function(){this.x=Math.round(this.x);this.y=Math.round(this.y);return this};function Va(a,b){this.width=a;this.height=b}Va.prototype.aspectRatio=function(){return this.width/this.height};Va.prototype.ceil=function(){this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};Va.prototype.floor=function(){this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};Va.prototype.round=function(){this.width=Math.round(this.width);this.height=Math.round(this.height);return this};function Wa(a,b){if(!a||!b)return!1;if(a.contains&&1==b.nodeType)return a==b||a.contains(b);if(\"undefined\"!=typeof a.compareDocumentPosition)return a==b||!!(a.compareDocumentPosition(b)&16);for(;b&&a!=b;)b=b.parentNode;return b==a}\nfunction Xa(a,b){if(a==b)return 0;if(a.compareDocumentPosition)return a.compareDocumentPosition(b)&2?1:-1;if(v&&!(9<=Number(La))){if(9==a.nodeType)return-1;if(9==b.nodeType)return 1}if(\"sourceIndex\"in a||a.parentNode&&\"sourceIndex\"in a.parentNode){var c=1==a.nodeType,d=1==b.nodeType;if(c&&d)return a.sourceIndex-b.sourceIndex;var e=a.parentNode,f=b.parentNode;return e==f?Ya(a,b):!c&&Wa(e,b)?-1*Za(a,b):!d&&Wa(f,a)?Za(b,a):(c?a.sourceIndex:e.sourceIndex)-(d?b.sourceIndex:f.sourceIndex)}d=A(a);c=d.createRange();\nc.selectNode(a);c.collapse(!0);a=d.createRange();a.selectNode(b);a.collapse(!0);return c.compareBoundaryPoints(k.Range.START_TO_END,a)}function Za(a,b){var c=a.parentNode;if(c==b)return-1;for(;b.parentNode!=c;)b=b.parentNode;return Ya(b,a)}function Ya(a,b){for(;b=b.previousSibling;)if(b==a)return-1;return 1}function A(a){return 9==a.nodeType?a:a.ownerDocument||a.document}function $a(a,b){a&&(a=a.parentNode);for(var c=0;a;){if(b(a))return a;a=a.parentNode;c++}return null}\nfunction ab(a){this.a=a||k.document||document}ab.prototype.getElementsByTagName=function(a,b){return(b||this.a).getElementsByTagName(String(a))};function B(a){var b=null,c=a.nodeType;1==c&&(b=a.textContent,b=void 0==b||null==b?a.innerText:b,b=void 0==b||null==b?\"\":b);if(\"string\"!=typeof b)if(x&&\"title\"==a.nodeName.toLowerCase()&&1==c)b=a.text;else if(9==c||1==c){a=9==c?a.documentElement:a.firstChild;c=0;var d=[];for(b=\"\";a;){do 1!=a.nodeType&&(b+=a.nodeValue),x&&\"title\"==a.nodeName.toLowerCase()&&(b+=a.text),d[c++]=a;while(a=a.firstChild);for(;c&&!(a=d[--c].nextSibling););}}else b=a.nodeValue;return b}\nfunction C(a,b,c){if(null===b)return!0;try{if(!a.getAttribute)return!1}catch(d){return!1}Ma&&\"class\"==b&&(b=\"className\");return null==c?!!a.getAttribute(b):a.getAttribute(b,2)==c}function bb(a,b,c,d,e){return(x?cb:db).call(null,a,b,aa(c)?c:null,aa(d)?d:null,e||new E)}\nfunction cb(a,b,c,d,e){if(a instanceof F||8==a.b||c&&null===a.b){var f=b.all;if(!f)return e;a=eb(a);if(\"*\"!=a&&(f=b.getElementsByTagName(a),!f))return e;if(c){for(var g=[],h=0;b=f[h++];)C(b,c,d)&&g.push(b);f=g}for(h=0;b=f[h++];)\"*\"==a&&\"!\"==b.tagName||e.add(b);return e}gb(a,b,c,d,e);return e}\nfunction db(a,b,c,d,e){b.getElementsByName&&d&&\"name\"==c&&!v?(b=b.getElementsByName(d),n(b,function(f){a.a(f)&&e.add(f)})):b.getElementsByClassName&&d&&\"class\"==c?(b=b.getElementsByClassName(d),n(b,function(f){f.className==d&&a.a(f)&&e.add(f)})):a instanceof G?gb(a,b,c,d,e):b.getElementsByTagName&&(b=b.getElementsByTagName(a.f()),n(b,function(f){C(f,c,d)&&e.add(f)}));return e}\nfunction hb(a,b,c,d,e){var f;if((a instanceof F||8==a.b||c&&null===a.b)&&(f=b.childNodes)){var g=eb(a);if(\"*\"!=g&&(f=ka(f,function(h){return h.tagName&&h.tagName.toLowerCase()==g}),!f))return e;c&&(f=ka(f,function(h){return C(h,c,d)}));n(f,function(h){\"*\"==g&&(\"!\"==h.tagName||\"*\"==g&&1!=h.nodeType)||e.add(h)});return e}return ib(a,b,c,d,e)}function ib(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)C(b,c,d)&&a.a(b)&&e.add(b);return e}\nfunction gb(a,b,c,d,e){for(b=b.firstChild;b;b=b.nextSibling)C(b,c,d)&&a.a(b)&&e.add(b),gb(a,b,c,d,e)}function eb(a){if(a instanceof G){if(8==a.b)return\"!\";if(null===a.b)return\"*\"}return a.f()};function E(){this.b=this.a=null;this.l=0}function jb(a){this.f=a;this.a=this.b=null}function kb(a,b){if(!a.a)return b;if(!b.a)return a;var c=a.a;b=b.a;for(var d=null,e,f=0;c&&b;){e=c.f;var g=b.f;e==g||e instanceof Na&&g instanceof Na&&e.a==g.a?(e=c,c=c.a,b=b.a):0<Xa(c.f,b.f)?(e=b,b=b.a):(e=c,c=c.a);(e.b=d)?d.a=e:a.a=e;d=e;f++}for(e=c||b;e;)e.b=d,d=d.a=e,f++,e=e.a;a.b=d;a.l=f;return a}function lb(a,b){b=new jb(b);b.a=a.a;a.b?a.a.b=b:a.a=a.b=b;a.a=b;a.l++}\nE.prototype.add=function(a){a=new jb(a);a.b=this.b;this.a?this.b.a=a:this.a=this.b=a;this.b=a;this.l++};function mb(a){return(a=a.a)?a.f:null}function nb(a){return(a=mb(a))?B(a):\"\"}function H(a,b){return new ob(a,!!b)}function ob(a,b){this.f=a;this.b=(this.s=b)?a.b:a.a;this.a=null}function I(a){var b=a.b;if(null==b)return null;var c=a.a=b;a.b=a.s?b.b:b.a;return c.f};function J(a){this.i=a;this.b=this.g=!1;this.f=null}function K(a){return\"\\n  \"+a.toString().split(\"\\n\").join(\"\\n  \")}function pb(a,b){a.g=b}function qb(a,b){a.b=b}function N(a,b){a=a.a(b);return a instanceof E?+nb(a):+a}function O(a,b){a=a.a(b);return a instanceof E?nb(a):\"\"+a}function rb(a,b){a=a.a(b);return a instanceof E?!!a.l:!!a};function sb(a,b,c){J.call(this,a.i);this.c=a;this.h=b;this.o=c;this.g=b.g||c.g;this.b=b.b||c.b;this.c==tb&&(c.b||c.g||4==c.i||0==c.i||!b.f?b.b||b.g||4==b.i||0==b.i||!c.f||(this.f={name:c.f.name,u:b}):this.f={name:b.f.name,u:c})}l(sb,J);\nfunction ub(a,b,c,d,e){b=b.a(d);c=c.a(d);var f;if(b instanceof E&&c instanceof E){b=H(b);for(d=I(b);d;d=I(b))for(e=H(c),f=I(e);f;f=I(e))if(a(B(d),B(f)))return!0;return!1}if(b instanceof E||c instanceof E){b instanceof E?(e=b,d=c):(e=c,d=b);f=H(e);for(var g=typeof d,h=I(f);h;h=I(f)){switch(g){case \"number\":h=+B(h);break;case \"boolean\":h=!!B(h);break;case \"string\":h=B(h);break;default:throw Error(\"Illegal primitive type for comparison.\");}if(e==b&&a(h,d)||e==c&&a(d,h))return!0}return!1}return e?\"boolean\"==\ntypeof b||\"boolean\"==typeof c?a(!!b,!!c):\"number\"==typeof b||\"number\"==typeof c?a(+b,+c):a(b,c):a(+b,+c)}sb.prototype.a=function(a){return this.c.m(this.h,this.o,a)};sb.prototype.toString=function(){var a=\"Binary Expression: \"+this.c;a+=K(this.h);return a+=K(this.o)};function vb(a,b,c,d){this.I=a;this.D=b;this.i=c;this.m=d}vb.prototype.toString=function(){return this.I};var wb={};\nfunction P(a,b,c,d){if(wb.hasOwnProperty(a))throw Error(\"Binary operator already created: \"+a);a=new vb(a,b,c,d);return wb[a.toString()]=a}P(\"div\",6,1,function(a,b,c){return N(a,c)/N(b,c)});P(\"mod\",6,1,function(a,b,c){return N(a,c)%N(b,c)});P(\"*\",6,1,function(a,b,c){return N(a,c)*N(b,c)});P(\"+\",5,1,function(a,b,c){return N(a,c)+N(b,c)});P(\"-\",5,1,function(a,b,c){return N(a,c)-N(b,c)});P(\"<\",4,2,function(a,b,c){return ub(function(d,e){return d<e},a,b,c)});\nP(\">\",4,2,function(a,b,c){return ub(function(d,e){return d>e},a,b,c)});P(\"<=\",4,2,function(a,b,c){return ub(function(d,e){return d<=e},a,b,c)});P(\">=\",4,2,function(a,b,c){return ub(function(d,e){return d>=e},a,b,c)});var tb=P(\"=\",3,2,function(a,b,c){return ub(function(d,e){return d==e},a,b,c,!0)});P(\"!=\",3,2,function(a,b,c){return ub(function(d,e){return d!=e},a,b,c,!0)});P(\"and\",2,2,function(a,b,c){return rb(a,c)&&rb(b,c)});P(\"or\",1,2,function(a,b,c){return rb(a,c)||rb(b,c)});function xb(a,b){if(b.a.length&&4!=a.i)throw Error(\"Primary expression must evaluate to nodeset if filter has predicate(s).\");J.call(this,a.i);this.c=a;this.h=b;this.g=a.g;this.b=a.b}l(xb,J);xb.prototype.a=function(a){a=this.c.a(a);return yb(this.h,a)};xb.prototype.toString=function(){var a=\"Filter:\"+K(this.c);return a+=K(this.h)};function zb(a,b){if(b.length<a.C)throw Error(\"Function \"+a.j+\" expects at least\"+a.C+\" arguments, \"+b.length+\" given\");if(null!==a.B&&b.length>a.B)throw Error(\"Function \"+a.j+\" expects at most \"+a.B+\" arguments, \"+b.length+\" given\");a.H&&n(b,function(c,d){if(4!=c.i)throw Error(\"Argument \"+d+\" to function \"+a.j+\" is not of type Nodeset: \"+c);});J.call(this,a.i);this.v=a;this.c=b;pb(this,a.g||ma(b,function(c){return c.g}));qb(this,a.G&&!b.length||a.F&&!!b.length||ma(b,function(c){return c.b}))}\nl(zb,J);zb.prototype.a=function(a){return this.v.m.apply(null,pa(a,this.c))};zb.prototype.toString=function(){var a=\"Function: \"+this.v;if(this.c.length){var b=la(this.c,function(c,d){return c+K(d)},\"Arguments:\");a+=K(b)}return a};function Ab(a,b,c,d,e,f,g,h){this.j=a;this.i=b;this.g=c;this.G=d;this.F=!1;this.m=e;this.C=f;this.B=void 0!==g?g:f;this.H=!!h}Ab.prototype.toString=function(){return this.j};var Bb={};\nfunction Q(a,b,c,d,e,f,g,h){if(Bb.hasOwnProperty(a))throw Error(\"Function already created: \"+a+\".\");Bb[a]=new Ab(a,b,c,d,e,f,g,h)}Q(\"boolean\",2,!1,!1,function(a,b){return rb(b,a)},1);Q(\"ceiling\",1,!1,!1,function(a,b){return Math.ceil(N(b,a))},1);Q(\"concat\",3,!1,!1,function(a,b){return la(qa(arguments,1),function(c,d){return c+O(d,a)},\"\")},2,null);Q(\"contains\",2,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);return-1!=b.indexOf(a)},2);Q(\"count\",1,!1,!1,function(a,b){return b.a(a).l},1,1,!0);\nQ(\"false\",2,!1,!1,function(){return!1},0);Q(\"floor\",1,!1,!1,function(a,b){return Math.floor(N(b,a))},1);Q(\"id\",4,!1,!1,function(a,b){function c(h){if(x){var m=e.all[h];if(m){if(m.nodeType&&h==m.id)return m;if(m.length)return oa(m,function(w){return h==w.id})}return null}return e.getElementById(h)}var d=a.a,e=9==d.nodeType?d:d.ownerDocument;a=O(b,a).split(/\\s+/);var f=[];n(a,function(h){h=c(h);!h||0<=ja(f,h)||f.push(h)});f.sort(Xa);var g=new E;n(f,function(h){g.add(h)});return g},1);\nQ(\"lang\",2,!1,!1,function(){return!1},1);Q(\"last\",1,!0,!1,function(a){if(1!=arguments.length)throw Error(\"Function last expects ()\");return a.f},0);Q(\"local-name\",3,!1,!0,function(a,b){return(a=b?mb(b.a(a)):a.a)?a.localName||a.nodeName.toLowerCase():\"\"},0,1,!0);Q(\"name\",3,!1,!0,function(a,b){return(a=b?mb(b.a(a)):a.a)?a.nodeName.toLowerCase():\"\"},0,1,!0);Q(\"namespace-uri\",3,!0,!1,function(){return\"\"},0,1,!0);\nQ(\"normalize-space\",3,!1,!0,function(a,b){return(b?O(b,a):B(a.a)).replace(/[\\s\\xa0]+/g,\" \").replace(/^\\s+|\\s+$/g,\"\")},0,1);Q(\"not\",2,!1,!1,function(a,b){return!rb(b,a)},1);Q(\"number\",1,!1,!0,function(a,b){return b?N(b,a):+B(a.a)},0,1);Q(\"position\",1,!0,!1,function(a){return a.b},0);Q(\"round\",1,!1,!1,function(a,b){return Math.round(N(b,a))},1);Q(\"starts-with\",2,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);return 0==b.lastIndexOf(a,0)},2);Q(\"string\",3,!1,!0,function(a,b){return b?O(b,a):B(a.a)},0,1);\nQ(\"string-length\",1,!1,!0,function(a,b){return(b?O(b,a):B(a.a)).length},0,1);Q(\"substring\",3,!1,!1,function(a,b,c,d){c=N(c,a);if(isNaN(c)||Infinity==c||-Infinity==c)return\"\";d=d?N(d,a):Infinity;if(isNaN(d)||-Infinity===d)return\"\";c=Math.round(c)-1;var e=Math.max(c,0);a=O(b,a);return Infinity==d?a.substring(e):a.substring(e,c+Math.round(d))},2,3);Q(\"substring-after\",3,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);c=b.indexOf(a);return-1==c?\"\":b.substring(c+a.length)},2);\nQ(\"substring-before\",3,!1,!1,function(a,b,c){b=O(b,a);a=O(c,a);a=b.indexOf(a);return-1==a?\"\":b.substring(0,a)},2);Q(\"sum\",1,!1,!1,function(a,b){a=H(b.a(a));b=0;for(var c=I(a);c;c=I(a))b+=+B(c);return b},1,1,!0);Q(\"translate\",3,!1,!1,function(a,b,c,d){b=O(b,a);c=O(c,a);var e=O(d,a);a={};for(d=0;d<c.length;d++){var f=c.charAt(d);f in a||(a[f]=e.charAt(d))}c=\"\";for(d=0;d<b.length;d++)f=b.charAt(d),c+=f in a?a[f]:f;return c},3);Q(\"true\",2,!1,!1,function(){return!0},0);function G(a,b){this.h=a;this.c=void 0!==b?b:null;this.b=null;switch(a){case \"comment\":this.b=8;break;case \"text\":this.b=3;break;case \"processing-instruction\":this.b=7;break;case \"node\":break;default:throw Error(\"Unexpected argument\");}}function Cb(a){return\"comment\"==a||\"text\"==a||\"processing-instruction\"==a||\"node\"==a}G.prototype.a=function(a){return null===this.b||this.b==a.nodeType};G.prototype.f=function(){return this.h};\nG.prototype.toString=function(){var a=\"Kind Test: \"+this.h;null===this.c||(a+=K(this.c));return a};function Db(a){J.call(this,3);this.c=a.substring(1,a.length-1)}l(Db,J);Db.prototype.a=function(){return this.c};Db.prototype.toString=function(){return\"Literal: \"+this.c};function F(a,b){this.j=a.toLowerCase();a=\"*\"==this.j?\"*\":\"http://www.w3.org/1999/xhtml\";this.c=b?b.toLowerCase():a}F.prototype.a=function(a){var b=a.nodeType;if(1!=b&&2!=b)return!1;b=void 0!==a.localName?a.localName:a.nodeName;return\"*\"!=this.j&&this.j!=b.toLowerCase()?!1:\"*\"==this.c?!0:this.c==(a.namespaceURI?a.namespaceURI.toLowerCase():\"http://www.w3.org/1999/xhtml\")};F.prototype.f=function(){return this.j};\nF.prototype.toString=function(){return\"Name Test: \"+(\"http://www.w3.org/1999/xhtml\"==this.c?\"\":this.c+\":\")+this.j};function Eb(a){J.call(this,1);this.c=a}l(Eb,J);Eb.prototype.a=function(){return this.c};Eb.prototype.toString=function(){return\"Number: \"+this.c};function Fb(a,b){J.call(this,a.i);this.h=a;this.c=b;this.g=a.g;this.b=a.b;1==this.c.length&&(a=this.c[0],a.A||a.c!=Gb||(a=a.o,\"*\"!=a.f()&&(this.f={name:a.f(),u:null})))}l(Fb,J);function Hb(){J.call(this,4)}l(Hb,J);Hb.prototype.a=function(a){var b=new E;a=a.a;9==a.nodeType?b.add(a):b.add(a.ownerDocument);return b};Hb.prototype.toString=function(){return\"Root Helper Expression\"};function Ib(){J.call(this,4)}l(Ib,J);Ib.prototype.a=function(a){var b=new E;b.add(a.a);return b};Ib.prototype.toString=function(){return\"Context Helper Expression\"};\nfunction Jb(a){return\"/\"==a||\"//\"==a}Fb.prototype.a=function(a){var b=this.h.a(a);if(!(b instanceof E))throw Error(\"Filter expression must evaluate to nodeset.\");a=this.c;for(var c=0,d=a.length;c<d&&b.l;c++){var e=a[c],f=H(b,e.c.s);if(e.g||e.c!=Kb)if(e.g||e.c!=Lb){var g=I(f);for(b=e.a(new ia(g));null!=(g=I(f));)g=e.a(new ia(g)),b=kb(b,g)}else g=I(f),b=e.a(new ia(g));else{for(g=I(f);(b=I(f))&&(!g.contains||g.contains(b))&&b.compareDocumentPosition(g)&8;g=b);b=e.a(new ia(g))}}return b};\nFb.prototype.toString=function(){var a=\"Path Expression:\"+K(this.h);if(this.c.length){var b=la(this.c,function(c,d){return c+K(d)},\"Steps:\");a+=K(b)}return a};function Mb(a,b){this.a=a;this.s=!!b}\nfunction yb(a,b,c){for(c=c||0;c<a.a.length;c++)for(var d=a.a[c],e=H(b),f=b.l,g,h=0;g=I(e);h++){var m=a.s?f-h:h+1;g=d.a(new ia(g,m,f));if(\"number\"==typeof g)m=m==g;else if(\"string\"==typeof g||\"boolean\"==typeof g)m=!!g;else if(g instanceof E)m=0<g.l;else throw Error(\"Predicate.evaluate returned an unexpected type.\");if(!m){m=e;g=m.f;var w=m.a;if(!w)throw Error(\"Next must be called at least once before remove.\");var r=w.b;w=w.a;r?r.a=w:g.a=w;w?w.b=r:g.b=r;g.l--;m.a=null}}return b}\nMb.prototype.toString=function(){return la(this.a,function(a,b){return a+K(b)},\"Predicates:\")};function R(a,b,c,d){J.call(this,4);this.c=a;this.o=b;this.h=c||new Mb([]);this.A=!!d;b=this.h;b=0<b.a.length?b.a[0].f:null;a.J&&b&&(a=b.name,a=x?a.toLowerCase():a,this.f={name:a,u:b.u});a:{a=this.h;for(b=0;b<a.a.length;b++)if(c=a.a[b],c.g||1==c.i||0==c.i){a=!0;break a}a=!1}this.g=a}l(R,J);\nR.prototype.a=function(a){var b=a.a,c=this.f,d=null,e=null,f=0;c&&(d=c.name,e=c.u?O(c.u,a):null,f=1);if(this.A)if(this.g||this.c!=Nb)if(b=H((new R(Ob,new G(\"node\"))).a(a)),c=I(b))for(a=this.m(c,d,e,f);null!=(c=I(b));)a=kb(a,this.m(c,d,e,f));else a=new E;else a=bb(this.o,b,d,e),a=yb(this.h,a,f);else a=this.m(a.a,d,e,f);return a};R.prototype.m=function(a,b,c,d){a=this.c.v(this.o,a,b,c);return a=yb(this.h,a,d)};\nR.prototype.toString=function(){var a=\"Step:\"+K(\"Operator: \"+(this.A?\"//\":\"/\"));this.c.j&&(a+=K(\"Axis: \"+this.c));a+=K(this.o);if(this.h.a.length){var b=la(this.h.a,function(c,d){return c+K(d)},\"Predicates:\");a+=K(b)}return a};function Pb(a,b,c,d){this.j=a;this.v=b;this.s=c;this.J=d}Pb.prototype.toString=function(){return this.j};var Qb={};function S(a,b,c,d){if(Qb.hasOwnProperty(a))throw Error(\"Axis already created: \"+a);b=new Pb(a,b,c,!!d);return Qb[a]=b}\nS(\"ancestor\",function(a,b){for(var c=new E;b=b.parentNode;)a.a(b)&&lb(c,b);return c},!0);S(\"ancestor-or-self\",function(a,b){var c=new E;do a.a(b)&&lb(c,b);while(b=b.parentNode);return c},!0);\nvar Gb=S(\"attribute\",function(a,b){var c=new E,d=a.f();if(\"style\"==d&&x&&b.style)return c.add(new Na(b.style,b,\"style\",b.style.cssText)),c;var e=b.attributes;if(e)if(a instanceof G&&null===a.b||\"*\"==d)for(a=0;d=e[a];a++)x?d.nodeValue&&c.add(Oa(b,d)):c.add(d);else(d=e.getNamedItem(d))&&(x?d.nodeValue&&c.add(Oa(b,d)):c.add(d));return c},!1),Nb=S(\"child\",function(a,b,c,d,e){return(x?hb:ib).call(null,a,b,aa(c)?c:null,aa(d)?d:null,e||new E)},!1,!0);S(\"descendant\",bb,!1,!0);\nvar Ob=S(\"descendant-or-self\",function(a,b,c,d){var e=new E;C(b,c,d)&&a.a(b)&&e.add(b);return bb(a,b,c,d,e)},!1,!0),Kb=S(\"following\",function(a,b,c,d){var e=new E;do for(var f=b;f=f.nextSibling;)C(f,c,d)&&a.a(f)&&e.add(f),e=bb(a,f,c,d,e);while(b=b.parentNode);return e},!1,!0);S(\"following-sibling\",function(a,b){for(var c=new E;b=b.nextSibling;)a.a(b)&&c.add(b);return c},!1);S(\"namespace\",function(){return new E},!1);\nvar Rb=S(\"parent\",function(a,b){var c=new E;if(9==b.nodeType)return c;if(2==b.nodeType)return c.add(b.ownerElement),c;b=b.parentNode;a.a(b)&&c.add(b);return c},!1),Lb=S(\"preceding\",function(a,b,c,d){var e=new E,f=[];do f.unshift(b);while(b=b.parentNode);for(var g=1,h=f.length;g<h;g++){var m=[];for(b=f[g];b=b.previousSibling;)m.unshift(b);for(var w=0,r=m.length;w<r;w++)b=m[w],C(b,c,d)&&a.a(b)&&e.add(b),e=bb(a,b,c,d,e)}return e},!0,!0);\nS(\"preceding-sibling\",function(a,b){for(var c=new E;b=b.previousSibling;)a.a(b)&&lb(c,b);return c},!0);var Sb=S(\"self\",function(a,b){var c=new E;a.a(b)&&c.add(b);return c},!1);function Tb(a){J.call(this,1);this.c=a;this.g=a.g;this.b=a.b}l(Tb,J);Tb.prototype.a=function(a){return-N(this.c,a)};Tb.prototype.toString=function(){return\"Unary Expression: -\"+K(this.c)};function Ub(a){J.call(this,4);this.c=a;pb(this,ma(this.c,function(b){return b.g}));qb(this,ma(this.c,function(b){return b.b}))}l(Ub,J);Ub.prototype.a=function(a){var b=new E;n(this.c,function(c){c=c.a(a);if(!(c instanceof E))throw Error(\"Path expression must evaluate to NodeSet.\");b=kb(b,c)});return b};Ub.prototype.toString=function(){return la(this.c,function(a,b){return a+K(b)},\"Union Expression:\")};function Vb(a,b){this.a=a;this.b=b}function Yb(a){for(var b,c=[];;){T(a,\"Missing right hand side of binary expression.\");b=Zb(a);var d=z(a.a);if(!d)break;var e=(d=wb[d]||null)&&d.D;if(!e){a.a.a--;break}for(;c.length&&e<=c[c.length-1].D;)b=new sb(c.pop(),c.pop(),b);c.push(b,d)}for(;c.length;)b=new sb(c.pop(),c.pop(),b);return b}function T(a,b){if(Ta(a.a))throw Error(b);}function $b(a,b){a=z(a.a);if(a!=b)throw Error(\"Bad token, expected: \"+b+\" got: \"+a);}\nfunction ac(a){a=z(a.a);if(\")\"!=a)throw Error(\"Bad token: \"+a);}function bc(a){a=z(a.a);if(2>a.length)throw Error(\"Unclosed literal string\");return new Db(a)}\nfunction cc(a){var b=[];if(Jb(y(a.a))){var c=z(a.a);var d=y(a.a);if(\"/\"==c&&(Ta(a.a)||\".\"!=d&&\"..\"!=d&&\"@\"!=d&&\"*\"!=d&&!/(?![0-9])[\\w]/.test(d)))return new Hb;d=new Hb;T(a,\"Missing next location step.\");c=dc(a,c);b.push(c)}else{a:{c=y(a.a);d=c.charAt(0);switch(d){case \"$\":throw Error(\"Variable reference not allowed in HTML XPath\");case \"(\":z(a.a);c=Yb(a);T(a,'unclosed \"(\"');$b(a,\")\");break;case '\"':case \"'\":c=bc(a);break;default:if(isNaN(+c))if(!Cb(c)&&/(?![0-9])[\\w]/.test(d)&&\"(\"==y(a.a,1)){c=z(a.a);\nc=Bb[c]||null;z(a.a);for(d=[];\")\"!=y(a.a);){T(a,\"Missing function argument list.\");d.push(Yb(a));if(\",\"!=y(a.a))break;z(a.a)}T(a,\"Unclosed function argument list.\");ac(a);c=new zb(c,d)}else{c=null;break a}else c=new Eb(+z(a.a))}\"[\"==y(a.a)&&(d=new Mb(ec(a)),c=new xb(c,d))}if(c)if(Jb(y(a.a)))d=c;else return c;else c=dc(a,\"/\"),d=new Ib,b.push(c)}for(;Jb(y(a.a));)c=z(a.a),T(a,\"Missing next location step.\"),c=dc(a,c),b.push(c);return new Fb(d,b)}\nfunction dc(a,b){if(\"/\"!=b&&\"//\"!=b)throw Error('Step op should be \"/\" or \"//\"');if(\".\"==y(a.a)){var c=new R(Sb,new G(\"node\"));z(a.a);return c}if(\"..\"==y(a.a))return c=new R(Rb,new G(\"node\")),z(a.a),c;if(\"@\"==y(a.a)){var d=Gb;z(a.a);T(a,\"Missing attribute name\")}else if(\"::\"==y(a.a,1)){if(!/(?![0-9])[\\w]/.test(y(a.a).charAt(0)))throw Error(\"Bad token: \"+z(a.a));var e=z(a.a);d=Qb[e]||null;if(!d)throw Error(\"No axis with name: \"+e);z(a.a);T(a,\"Missing node name\")}else d=Nb;e=y(a.a);if(/(?![0-9])[\\w\\*]/.test(e.charAt(0)))if(\"(\"==\ny(a.a,1)){if(!Cb(e))throw Error(\"Invalid node type: \"+e);e=z(a.a);if(!Cb(e))throw Error(\"Invalid type name: \"+e);$b(a,\"(\");T(a,\"Bad nodetype\");var f=y(a.a).charAt(0),g=null;if('\"'==f||\"'\"==f)g=bc(a);T(a,\"Bad nodetype\");ac(a);e=new G(e,g)}else if(e=z(a.a),f=e.indexOf(\":\"),-1==f)e=new F(e);else{g=e.substring(0,f);if(\"*\"==g)var h=\"*\";else if(h=a.b(g),!h)throw Error(\"Namespace prefix not declared: \"+g);e=e.substr(f+1);e=new F(e,h)}else throw Error(\"Bad token: \"+z(a.a));a=new Mb(ec(a),d.s);return c||new R(d,\ne,a,\"//\"==b)}function ec(a){for(var b=[];\"[\"==y(a.a);){z(a.a);T(a,\"Missing predicate expression.\");var c=Yb(a);b.push(c);T(a,\"Unclosed predicate expression.\");$b(a,\"]\")}return b}function Zb(a){if(\"-\"==y(a.a))return z(a.a),new Tb(Zb(a));var b=cc(a);if(\"|\"!=y(a.a))a=b;else{for(b=[b];\"|\"==z(a.a);)T(a,\"Missing next union location path.\"),b.push(cc(a));a.a.a--;a=new Ub(b)}return a};function fc(a){switch(a.nodeType){case 1:return ha(gc,a);case 9:return fc(a.documentElement);case 11:case 10:case 6:case 12:return hc;default:return a.parentNode?fc(a.parentNode):hc}}function hc(){return null}function gc(a,b){if(a.prefix==b)return a.namespaceURI||\"http://www.w3.org/1999/xhtml\";var c=a.getAttributeNode(\"xmlns:\"+b);return c&&c.specified?c.value||null:a.parentNode&&9!=a.parentNode.nodeType?gc(a.parentNode,b):null};function ic(a,b){if(!a.length)throw Error(\"Empty XPath expression.\");a=Qa(a);if(Ta(a))throw Error(\"Invalid XPath expression.\");b?\"function\"==ca(b)||(b=fa(b.lookupNamespaceURI,b)):b=function(){return null};var c=Yb(new Vb(a,b));if(!Ta(a))throw Error(\"Bad token: \"+z(a));this.evaluate=function(d,e){d=c.a(new ia(d));return new U(d,e)}}\nfunction U(a,b){if(0==b)if(a instanceof E)b=4;else if(\"string\"==typeof a)b=2;else if(\"number\"==typeof a)b=1;else if(\"boolean\"==typeof a)b=3;else throw Error(\"Unexpected evaluation result.\");if(2!=b&&1!=b&&3!=b&&!(a instanceof E))throw Error(\"value could not be converted to the specified type\");this.resultType=b;switch(b){case 2:this.stringValue=a instanceof E?nb(a):\"\"+a;break;case 1:this.numberValue=a instanceof E?+nb(a):+a;break;case 3:this.booleanValue=a instanceof E?0<a.l:!!a;break;case 4:case 5:case 6:case 7:var c=\nH(a);var d=[];for(var e=I(c);e;e=I(c))d.push(e instanceof Na?e.a:e);this.snapshotLength=a.l;this.invalidIteratorState=!1;break;case 8:case 9:a=mb(a);this.singleNodeValue=a instanceof Na?a.a:a;break;default:throw Error(\"Unknown XPathResult type.\");}var f=0;this.iterateNext=function(){if(4!=b&&5!=b)throw Error(\"iterateNext called with wrong result type\");return f>=d.length?null:d[f++]};this.snapshotItem=function(g){if(6!=b&&7!=b)throw Error(\"snapshotItem called with wrong result type\");return g>=d.length||\n0>g?null:d[g]}}U.ANY_TYPE=0;U.NUMBER_TYPE=1;U.STRING_TYPE=2;U.BOOLEAN_TYPE=3;U.UNORDERED_NODE_ITERATOR_TYPE=4;U.ORDERED_NODE_ITERATOR_TYPE=5;U.UNORDERED_NODE_SNAPSHOT_TYPE=6;U.ORDERED_NODE_SNAPSHOT_TYPE=7;U.ANY_UNORDERED_NODE_TYPE=8;U.FIRST_ORDERED_NODE_TYPE=9;function jc(a){this.lookupNamespaceURI=fc(a)}\nfunction kc(a,b){a=a||k;var c=a.Document&&a.Document.prototype||a.document;if(!c.evaluate||b)a.XPathResult=U,c.evaluate=function(d,e,f,g){return(new ic(d,f)).evaluate(e,g)},c.createExpression=function(d,e){return new ic(d,e)},c.createNSResolver=function(d){return new jc(d)}}ba(\"wgxpath.install\",kc);ba(\"wgxpath.install\",kc);var lc={aliceblue:\"#f0f8ff\",antiquewhite:\"#faebd7\",aqua:\"#00ffff\",aquamarine:\"#7fffd4\",azure:\"#f0ffff\",beige:\"#f5f5dc\",bisque:\"#ffe4c4\",black:\"#000000\",blanchedalmond:\"#ffebcd\",blue:\"#0000ff\",blueviolet:\"#8a2be2\",brown:\"#a52a2a\",burlywood:\"#deb887\",cadetblue:\"#5f9ea0\",chartreuse:\"#7fff00\",chocolate:\"#d2691e\",coral:\"#ff7f50\",cornflowerblue:\"#6495ed\",cornsilk:\"#fff8dc\",crimson:\"#dc143c\",cyan:\"#00ffff\",darkblue:\"#00008b\",darkcyan:\"#008b8b\",darkgoldenrod:\"#b8860b\",darkgray:\"#a9a9a9\",darkgreen:\"#006400\",\ndarkgrey:\"#a9a9a9\",darkkhaki:\"#bdb76b\",darkmagenta:\"#8b008b\",darkolivegreen:\"#556b2f\",darkorange:\"#ff8c00\",darkorchid:\"#9932cc\",darkred:\"#8b0000\",darksalmon:\"#e9967a\",darkseagreen:\"#8fbc8f\",darkslateblue:\"#483d8b\",darkslategray:\"#2f4f4f\",darkslategrey:\"#2f4f4f\",darkturquoise:\"#00ced1\",darkviolet:\"#9400d3\",deeppink:\"#ff1493\",deepskyblue:\"#00bfff\",dimgray:\"#696969\",dimgrey:\"#696969\",dodgerblue:\"#1e90ff\",firebrick:\"#b22222\",floralwhite:\"#fffaf0\",forestgreen:\"#228b22\",fuchsia:\"#ff00ff\",gainsboro:\"#dcdcdc\",\nghostwhite:\"#f8f8ff\",gold:\"#ffd700\",goldenrod:\"#daa520\",gray:\"#808080\",green:\"#008000\",greenyellow:\"#adff2f\",grey:\"#808080\",honeydew:\"#f0fff0\",hotpink:\"#ff69b4\",indianred:\"#cd5c5c\",indigo:\"#4b0082\",ivory:\"#fffff0\",khaki:\"#f0e68c\",lavender:\"#e6e6fa\",lavenderblush:\"#fff0f5\",lawngreen:\"#7cfc00\",lemonchiffon:\"#fffacd\",lightblue:\"#add8e6\",lightcoral:\"#f08080\",lightcyan:\"#e0ffff\",lightgoldenrodyellow:\"#fafad2\",lightgray:\"#d3d3d3\",lightgreen:\"#90ee90\",lightgrey:\"#d3d3d3\",lightpink:\"#ffb6c1\",lightsalmon:\"#ffa07a\",\nlightseagreen:\"#20b2aa\",lightskyblue:\"#87cefa\",lightslategray:\"#778899\",lightslategrey:\"#778899\",lightsteelblue:\"#b0c4de\",lightyellow:\"#ffffe0\",lime:\"#00ff00\",limegreen:\"#32cd32\",linen:\"#faf0e6\",magenta:\"#ff00ff\",maroon:\"#800000\",mediumaquamarine:\"#66cdaa\",mediumblue:\"#0000cd\",mediumorchid:\"#ba55d3\",mediumpurple:\"#9370db\",mediumseagreen:\"#3cb371\",mediumslateblue:\"#7b68ee\",mediumspringgreen:\"#00fa9a\",mediumturquoise:\"#48d1cc\",mediumvioletred:\"#c71585\",midnightblue:\"#191970\",mintcream:\"#f5fffa\",mistyrose:\"#ffe4e1\",\nmoccasin:\"#ffe4b5\",navajowhite:\"#ffdead\",navy:\"#000080\",oldlace:\"#fdf5e6\",olive:\"#808000\",olivedrab:\"#6b8e23\",orange:\"#ffa500\",orangered:\"#ff4500\",orchid:\"#da70d6\",palegoldenrod:\"#eee8aa\",palegreen:\"#98fb98\",paleturquoise:\"#afeeee\",palevioletred:\"#db7093\",papayawhip:\"#ffefd5\",peachpuff:\"#ffdab9\",peru:\"#cd853f\",pink:\"#ffc0cb\",plum:\"#dda0dd\",powderblue:\"#b0e0e6\",purple:\"#800080\",red:\"#ff0000\",rosybrown:\"#bc8f8f\",royalblue:\"#4169e1\",saddlebrown:\"#8b4513\",salmon:\"#fa8072\",sandybrown:\"#f4a460\",seagreen:\"#2e8b57\",\nseashell:\"#fff5ee\",sienna:\"#a0522d\",silver:\"#c0c0c0\",skyblue:\"#87ceeb\",slateblue:\"#6a5acd\",slategray:\"#708090\",slategrey:\"#708090\",snow:\"#fffafa\",springgreen:\"#00ff7f\",steelblue:\"#4682b4\",tan:\"#d2b48c\",teal:\"#008080\",thistle:\"#d8bfd8\",tomato:\"#ff6347\",turquoise:\"#40e0d0\",violet:\"#ee82ee\",wheat:\"#f5deb3\",white:\"#ffffff\",whitesmoke:\"#f5f5f5\",yellow:\"#ffff00\",yellowgreen:\"#9acd32\"};var mc=\"backgroundColor borderTopColor borderRightColor borderBottomColor borderLeftColor color outlineColor\".split(\" \"),nc=/#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])/,oc=/^#(?:[0-9a-f]{3}){1,2}$/i,pc=/^(?:rgba)?\\((\\d{1,3}),\\s?(\\d{1,3}),\\s?(\\d{1,3}),\\s?(0|1|0\\.\\d*)\\)$/i,qc=/^(?:rgb)?\\((0|[1-9]\\d{0,2}),\\s?(0|[1-9]\\d{0,2}),\\s?(0|[1-9]\\d{0,2})\\)$/i;function rc(a,b){this.code=a;this.a=V[a]||sc;this.message=b||\"\";a=this.a.replace(/((?:^|\\s+)[a-z])/g,function(c){return c.toUpperCase().replace(/^[\\s\\xa0]+/g,\"\")});b=a.length-5;if(0>b||a.indexOf(\"Error\",b)!=b)a+=\"Error\";this.name=a;a=Error(this.message);a.name=this.name;this.stack=a.stack||\"\"}l(rc,Error);var sc=\"unknown error\",V={15:\"element not selectable\",11:\"element not visible\"};V[31]=sc;V[30]=sc;V[24]=\"invalid cookie domain\";V[29]=\"invalid element coordinates\";V[12]=\"invalid element state\";\nV[32]=\"invalid selector\";V[51]=\"invalid selector\";V[52]=\"invalid selector\";V[17]=\"javascript error\";V[405]=\"unsupported operation\";V[34]=\"move target out of bounds\";V[27]=\"no such alert\";V[7]=\"no such element\";V[8]=\"no such frame\";V[23]=\"no such window\";V[28]=\"script timeout\";V[33]=\"session not created\";V[10]=\"stale element reference\";V[21]=\"timeout\";V[25]=\"unable to set cookie\";V[26]=\"unexpected alert open\";V[13]=sc;V[9]=\"unknown command\";var tc=va(),uc=ya()||u(\"iPod\"),vc=u(\"iPad\"),wc=u(\"Android\")&&!(wa()||va()||u(\"Opera\")||u(\"Silk\")),xc=wa(),yc=u(\"Safari\")&&!(wa()||u(\"Coast\")||u(\"Opera\")||u(\"Edge\")||u(\"Edg/\")||u(\"OPR\")||va()||u(\"Silk\")||u(\"Android\"))&&!(ya()||u(\"iPad\")||u(\"iPod\"));function zc(a){return(a=a.exec(t))?a[1]:\"\"}(function(){if(tc)return zc(/Firefox\\/([0-9.]+)/);if(v||Ca||Ba)return Ga;if(xc)return ya()||u(\"iPad\")||u(\"iPod\")?zc(/CriOS\\/([0-9.]+)/):zc(/Chrome\\/([0-9.]+)/);if(yc&&!(ya()||u(\"iPad\")||u(\"iPod\")))return zc(/Version\\/([0-9.]+)/);if(uc||vc){var a=/Version\\/(\\S+).*Mobile\\/(\\S+)/.exec(t);if(a)return a[1]+\".\"+a[2]}else if(wc)return(a=zc(/Android\\s+([0-9.]+)/))?a:zc(/Version\\/([0-9.]+)/);return\"\"})();var Ac=v&&!(9<=Number(La));function W(a,b){b&&\"string\"!==typeof b&&(b=b.toString());return!!a&&1==a.nodeType&&(!b||a.tagName.toUpperCase()==b)};var Bc=function(){var a={K:\"http://www.w3.org/2000/svg\"};return function(b){return a[b]||null}}();\nfunction Cc(a,b){var c=A(a);if(!c.documentElement)return null;(v||wc)&&kc(c?c.parentWindow||c.defaultView:window);try{var d=c.createNSResolver?c.createNSResolver(c.documentElement):Bc;if(v&&!Ka(7))return c.evaluate.call(c,b,a,d,9,null);if(!v||9<=Number(La)){for(var e={},f=c.getElementsByTagName(\"*\"),g=0;g<f.length;++g){var h=f[g],m=h.namespaceURI;if(m&&!e[m]){var w=h.lookupPrefix(m);if(!w){var r=m.match(\".*/(\\\\w+)/?$\");w=r?r[1]:\"xhtml\"}e[m]=w}}var D={},L;for(L in e)D[e[L]]=L;d=function(M){return D[M]||\nnull}}try{return c.evaluate(b,a,d,9,null)}catch(M){if(\"TypeError\"===M.name)return d=c.createNSResolver?c.createNSResolver(c.documentElement):Bc,c.evaluate(b,a,d,9,null);throw M;}}catch(M){if(!Da||\"NS_ERROR_ILLEGAL_VALUE\"!=M.name)throw new rc(32,\"Unable to locate an element with the xpath expression \"+b+\" because of the following error:\\n\"+M);}}\nfunction Dc(a,b){var c=function(){var d=Cc(b,a);return d?d.singleNodeValue||null:b.selectSingleNode?(d=A(b),d.setProperty&&d.setProperty(\"SelectionLanguage\",\"XPath\"),b.selectSingleNode(a)):null}();if(null!==c&&(!c||1!=c.nodeType))throw new rc(32,'The result of the xpath expression \"'+a+'\" is: '+c+\". It should be an element.\");return c};function Ec(a,b,c,d){this.c=a;this.a=b;this.b=c;this.f=d}Ec.prototype.ceil=function(){this.c=Math.ceil(this.c);this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.f=Math.ceil(this.f);return this};Ec.prototype.floor=function(){this.c=Math.floor(this.c);this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.f=Math.floor(this.f);return this};Ec.prototype.round=function(){this.c=Math.round(this.c);this.a=Math.round(this.a);this.b=Math.round(this.b);this.f=Math.round(this.f);return this};function X(a,b,c,d){this.a=a;this.b=b;this.width=c;this.height=d}X.prototype.ceil=function(){this.a=Math.ceil(this.a);this.b=Math.ceil(this.b);this.width=Math.ceil(this.width);this.height=Math.ceil(this.height);return this};X.prototype.floor=function(){this.a=Math.floor(this.a);this.b=Math.floor(this.b);this.width=Math.floor(this.width);this.height=Math.floor(this.height);return this};\nX.prototype.round=function(){this.a=Math.round(this.a);this.b=Math.round(this.b);this.width=Math.round(this.width);this.height=Math.round(this.height);return this};var Fc=\"function\"===typeof ShadowRoot;function Gc(a){for(a=a.parentNode;a&&1!=a.nodeType&&9!=a.nodeType&&11!=a.nodeType;)a=a.parentNode;return W(a)?a:null}\nfunction Y(a,b){b=xa(b);if(\"float\"==b||\"cssFloat\"==b||\"styleFloat\"==b)b=Ac?\"styleFloat\":\"cssFloat\";a:{var c=b;var d=A(a);if(d.defaultView&&d.defaultView.getComputedStyle&&(d=d.defaultView.getComputedStyle(a,null))){c=d[c]||d.getPropertyValue(c)||\"\";break a}c=\"\"}a=c||Hc(a,b);if(null===a)a=null;else if(0<=ja(mc,b)){b:{var e=a.match(pc);if(e&&(b=Number(e[1]),c=Number(e[2]),d=Number(e[3]),e=Number(e[4]),0<=b&&255>=b&&0<=c&&255>=c&&0<=d&&255>=d&&0<=e&&1>=e)){b=[b,c,d,e];break b}b=null}if(!b)b:{if(d=a.match(qc))if(b=\nNumber(d[1]),c=Number(d[2]),d=Number(d[3]),0<=b&&255>=b&&0<=c&&255>=c&&0<=d&&255>=d){b=[b,c,d,1];break b}b=null}if(!b)b:{b=a.toLowerCase();c=lc[b.toLowerCase()];if(!c&&(c=\"#\"==b.charAt(0)?b:\"#\"+b,4==c.length&&(c=c.replace(nc,\"#$1$1$2$2$3$3\")),!oc.test(c))){b=null;break b}b=[parseInt(c.substr(1,2),16),parseInt(c.substr(3,2),16),parseInt(c.substr(5,2),16),1]}a=b?\"rgba(\"+b.join(\", \")+\")\":a}return a}\nfunction Hc(a,b){var c=a.currentStyle||a.style,d=c[b];void 0===d&&\"function\"==ca(c.getPropertyValue)&&(d=c.getPropertyValue(b));return\"inherit\"!=d?void 0!==d?d:null:(a=Gc(a))?Hc(a,b):null}\nfunction Ic(a,b,c){function d(g){var h=Jc(g);return 0<h.height&&0<h.width?!0:W(g,\"PATH\")&&(0<h.height||0<h.width)?(g=Y(g,\"stroke-width\"),!!g&&0<parseInt(g,10)):\"hidden\"!=Y(g,\"overflow\")&&ma(g.childNodes,function(m){return 3==m.nodeType||W(m)&&d(m)})}function e(g){return Kc(g)==Z&&na(g.childNodes,function(h){return!W(h)||e(h)||!d(h)})}if(!W(a))throw Error(\"Argument to isShown must be of type Element\");if(W(a,\"BODY\"))return!0;if(W(a,\"OPTION\")||W(a,\"OPTGROUP\"))return a=$a(a,function(g){return W(g,\"SELECT\")}),\n!!a&&Ic(a,!0,c);var f=Lc(a);if(f)return!!f.image&&0<f.rect.width&&0<f.rect.height&&Ic(f.image,b,c);if(W(a,\"INPUT\")&&\"hidden\"==a.type.toLowerCase()||W(a,\"NOSCRIPT\"))return!1;f=Y(a,\"visibility\");return\"collapse\"!=f&&\"hidden\"!=f&&c(a)&&(b||0!=Mc(a))&&d(a)?!e(a):!1}var Z=\"hidden\";\nfunction Kc(a){function b(p){function q(fb){if(fb==g)return!0;var Wb=Y(fb,\"display\");return 0==Wb.lastIndexOf(\"inline\",0)||\"contents\"==Wb||\"absolute\"==Xb&&\"static\"==Y(fb,\"position\")?!1:!0}var Xb=Y(p,\"position\");if(\"fixed\"==Xb)return w=!0,p==g?null:g;for(p=Gc(p);p&&!q(p);)p=Gc(p);return p}function c(p){var q=p;if(\"visible\"==m)if(p==g&&h)q=h;else if(p==h)return{x:\"visible\",y:\"visible\"};q={x:Y(q,\"overflow-x\"),y:Y(q,\"overflow-y\")};p==g&&(q.x=\"visible\"==q.x?\"auto\":q.x,q.y=\"visible\"==q.y?\"auto\":q.y);return q}\nfunction d(p){if(p==g){var q=(new ab(f)).a;p=q.scrollingElement?q.scrollingElement:Ea||\"CSS1Compat\"!=q.compatMode?q.body||q.documentElement:q.documentElement;q=q.parentWindow||q.defaultView;p=v&&Ka(\"10\")&&q.pageYOffset!=p.scrollTop?new Ua(p.scrollLeft,p.scrollTop):new Ua(q.pageXOffset||p.scrollLeft,q.pageYOffset||p.scrollTop)}else p=new Ua(p.scrollLeft,p.scrollTop);return p}var e=Nc(a),f=A(a),g=f.documentElement,h=f.body,m=Y(g,\"overflow\"),w;for(a=b(a);a;a=b(a)){var r=c(a);if(\"visible\"!=r.x||\"visible\"!=\nr.y){var D=Jc(a);if(0==D.width||0==D.height)return Z;var L=e.a<D.a,M=e.b<D.b;if(L&&\"hidden\"==r.x||M&&\"hidden\"==r.y)return Z;if(L&&\"visible\"!=r.x||M&&\"visible\"!=r.y){L=d(a);M=e.b<D.b-L.y;if(e.a<D.a-L.x&&\"visible\"!=r.x||M&&\"visible\"!=r.x)return Z;e=Kc(a);return e==Z?Z:\"scroll\"}L=e.f>=D.a+D.width;D=e.c>=D.b+D.height;if(L&&\"hidden\"==r.x||D&&\"hidden\"==r.y)return Z;if(L&&\"visible\"!=r.x||D&&\"visible\"!=r.y){if(w&&(r=d(a),e.f>=g.scrollWidth-r.x||e.a>=g.scrollHeight-r.y))return Z;e=Kc(a);return e==Z?Z:\"scroll\"}}}return\"none\"}\nfunction Jc(a){var b=Lc(a);if(b)return b.rect;if(W(a,\"HTML\"))return a=A(a),a=((a?a.parentWindow||a.defaultView:window)||window).document,a=\"CSS1Compat\"==a.compatMode?a.documentElement:a.body,a=new Va(a.clientWidth,a.clientHeight),new X(0,0,a.width,a.height);try{var c=a.getBoundingClientRect()}catch(d){return new X(0,0,0,0)}b=new X(c.left,c.top,c.right-c.left,c.bottom-c.top);v&&a.ownerDocument.body&&(a=A(a),b.a-=a.documentElement.clientLeft+a.body.clientLeft,b.b-=a.documentElement.clientTop+a.body.clientTop);\nreturn b}function Lc(a){var b=W(a,\"MAP\");if(!b&&!W(a,\"AREA\"))return null;var c=b?a:W(a.parentNode,\"MAP\")?a.parentNode:null,d=null,e=null;c&&c.name&&(d=Dc('/descendant::*[@usemap = \"#'+c.name+'\"]',A(c)))&&(e=Jc(d),b||\"default\"==a.shape.toLowerCase()||(a=Oc(a),b=Math.min(Math.max(a.a,0),e.width),c=Math.min(Math.max(a.b,0),e.height),e=new X(b+e.a,c+e.b,Math.min(a.width,e.width-b),Math.min(a.height,e.height-c))));return{image:d,rect:e||new X(0,0,0,0)}}\nfunction Oc(a){var b=a.shape.toLowerCase();a=a.coords.split(\",\");if(\"rect\"==b&&4==a.length){b=a[0];var c=a[1];return new X(b,c,a[2]-b,a[3]-c)}if(\"circle\"==b&&3==a.length)return b=a[2],new X(a[0]-b,a[1]-b,2*b,2*b);if(\"poly\"==b&&2<a.length){b=a[0];c=a[1];for(var d=b,e=c,f=2;f+1<a.length;f+=2)b=Math.min(b,a[f]),d=Math.max(d,a[f]),c=Math.min(c,a[f+1]),e=Math.max(e,a[f+1]);return new X(b,c,d-b,e-c)}return new X(0,0,0,0)}function Nc(a){a=Jc(a);return new Ec(a.b,a.a+a.width,a.b+a.height,a.a)}\nfunction Mc(a){if(Ac){if(\"relative\"==Y(a,\"position\"))return 1;a=Y(a,\"filter\");return(a=a.match(/^alpha\\(opacity=(\\d*)\\)/)||a.match(/^progid:DXImageTransform.Microsoft.Alpha\\(Opacity=(\\d*)\\)/))?Number(a[1])/100:1}return Pc(a)}function Pc(a){var b=1,c=Y(a,\"opacity\");c&&(b=Number(c));(a=Gc(a))&&(b*=Pc(a));return b};ba(\"_\",function(a,b){function c(d){if(W(d)&&\"none\"==Y(d,\"display\"))return!1;var e;if((e=d.parentNode)&&e.shadowRoot&&void 0!==d.assignedSlot)e=d.assignedSlot?d.assignedSlot.parentNode:null;else if(d.getDestinationInsertionPoints){var f=d.getDestinationInsertionPoints();0<f.length&&(e=f[f.length-1])}if(Fc&&e instanceof ShadowRoot){if(e.host.shadowRoot&&e.host.shadowRoot!==e)return!1;e=e.host}return!e||9!=e.nodeType&&11!=e.nodeType?e&&W(e,\"DETAILS\")&&!e.open&&!W(d,\"SUMMARY\")?!1:!!e&&c(e):!0}return Ic(a,\n!!b,c)});; return this._.apply(null,arguments);}).apply({navigator:typeof window!='undefined'?window.navigator:null,document:typeof window!='undefined'?window.document:null}, arguments);}\n",
};

atom.getVisibleText = async function (element, window) {
  return executeInContent("getVisibleText", element, window);
}

atom.isElementDisplayed = function (element, window) {
  return executeInContent("isElementDisplayed", element, window);
}

function executeInContent(name, element, window) {
  const sandbox = lazy.sandbox.createMutable(window);

  return lazy.evaluate.sandbox(
    sandbox,
    `return (${ATOMS[name]})(arguments[0]);`,
    [element]
  );
}
PK
!<�%��='='0chrome/remote/content/marionette/browser.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  EventPromise: "chrome://remote/content/shared/Sync.sys.mjs",
  MessageManagerDestroyedPromise:
    "chrome://remote/content/marionette/sync.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
  windowManager: "chrome://remote/content/shared/WindowManager.sys.mjs",
});

/** @namespace */
export const browser = {};

/**
 * Variations of Marionette contexts.
 *
 * Choosing a context through the <tt>Marionette:SetContext</tt>
 * command directs all subsequent browsing context scoped commands
 * to that context.
 *
 * @class Marionette.Context
 */
export class Context {
  /**
   * Gets the correct context from a string.
   *
   * @param {string} s
   *     Context string serialisation.
   *
   * @returns {Context}
   *     Context.
   *
   * @throws {TypeError}
   *     If <var>s</var> is not a context.
   */
  static fromString(s) {
    switch (s) {
      case "chrome":
        return Context.Chrome;

      case "content":
        return Context.Content;

      default:
        throw new TypeError(`Unknown context: ${s}`);
    }
  }
}

Context.Chrome = "chrome";
Context.Content = "content";

/**
 * Creates a browsing context wrapper.
 *
 * Browsing contexts handle interactions with the browser, according to
 * the current environment.
 */
browser.Context = class {
  /**
   * @param {ChromeWindow} window
   *     ChromeWindow that contains the top-level browsing context.
   * @param {GeckoDriver} driver
   *     Reference to driver instance.
   */
  constructor(window, driver) {
    this.window = window;
    this.driver = driver;

    // In Firefox this is <xul:tabbrowser> (not <xul:browser>!)
    // and MobileTabBrowser in GeckoView.
    this.tabBrowser = lazy.TabManager.getTabBrowser(this.window);

    // Used to set curFrameId upon new session
    this.newSession = true;

    // A reference to the tab corresponding to the current window handle,
    // if any.  Specifically, this.tab refers to the last tab that Marionette
    // switched to in this browser window. Note that this may not equal the
    // currently selected tab.  For example, if Marionette switches to tab
    // A, and then clicks on a button that opens a new tab B in the same
    // browser window, this.tab will still point to tab A, despite tab B
    // being the currently selected tab.
    this.tab = null;
  }

  /**
   * Returns the content browser for the currently selected tab.
   * If there is no tab selected, null will be returned.
   */
  get contentBrowser() {
    if (this.tab) {
      return lazy.TabManager.getBrowserForTab(this.tab);
    } else if (
      this.tabBrowser &&
      this.driver.isReftestBrowser(this.tabBrowser)
    ) {
      return this.tabBrowser;
    }

    return null;
  }

  get messageManager() {
    if (this.contentBrowser) {
      return this.contentBrowser.messageManager;
    }

    return null;
  }

  /**
   * Checks if the browsing context has been discarded.
   *
   * The browsing context will have been discarded if the content
   * browser, represented by the <code>&lt;xul:browser&gt;</code>,
   * has been detached.
   *
   * @returns {boolean}
   *     True if browsing context has been discarded, false otherwise.
   */
  get closed() {
    return this.contentBrowser === null;
  }

  /**
   * Gets the position and dimensions of the top-level browsing context.
   *
   * @returns {Map.<string, number>}
   *     Object with |x|, |y|, |width|, and |height| properties.
   */
  get rect() {
    return {
      x: this.window.screenX,
      y: this.window.screenY,
      width: this.window.outerWidth,
      height: this.window.outerHeight,
    };
  }

  /**
   * Close the current window.
   *
   * @returns {Promise}
   *     A promise which is resolved when the current window has been closed.
   */
  async closeWindow() {
    return lazy.windowManager.closeWindow(this.window);
  }

  /**
   * Focus the current window.
   *
   * @returns {Promise}
   *     A promise which is resolved when the current window has been focused.
   */
  async focusWindow() {
    await lazy.windowManager.focusWindow(this.window);

    // Also focus the currently selected tab if present.
    this.contentBrowser?.focus();
  }

  /**
   * Open a new browser window.
   *
   * @returns {Promise}
   *     A promise resolving to the newly created chrome window.
   */
  openBrowserWindow(focus = false, isPrivate = false) {
    return lazy.windowManager.openBrowserWindow({
      openerWindow: this.window,
      focus,
      isPrivate,
    });
  }

  /**
   * Close the current tab.
   *
   * @returns {Promise}
   *     A promise which is resolved when the current tab has been closed.
   *
   * @throws UnsupportedOperationError
   *     If tab handling for the current application isn't supported.
   */
  async closeTab() {
    // If the current window is not a browser then close it directly. Do the
    // same if only one remaining tab is open, or no tab selected at all.
    //
    // Note: For GeckoView there will always be a single tab only. But for
    // consistency with other platforms a specific condition has been added
    // below as well even it's not really used.
    if (
      !this.tabBrowser ||
      !this.tabBrowser.tabs ||
      this.tabBrowser.tabs.length === 1 ||
      !this.tab
    ) {
      return this.closeWindow();
    }

    let destroyed = new lazy.MessageManagerDestroyedPromise(
      this.messageManager
    );
    let tabClosed;

    if (lazy.AppInfo.isAndroid) {
      await lazy.TabManager.removeTab(this.tab);
    } else if (lazy.AppInfo.isFirefox) {
      tabClosed = new lazy.EventPromise(this.tab, "TabClose");
      await this.tabBrowser.removeTab(this.tab);
    } else {
      throw new lazy.error.UnsupportedOperationError(
        `closeTab() not supported for ${lazy.AppInfo.name}`
      );
    }

    return Promise.all([destroyed, tabClosed]);
  }

  /**
   * Open a new tab in the currently selected chrome window.
   */
  async openTab(focus = false) {
    let tab = null;

    // Bug 1795841 - For Firefox the TabManager cannot be used yet. As such
    // handle opening a tab differently for Android.
    if (lazy.AppInfo.isAndroid) {
      tab = await lazy.TabManager.addTab({ focus, window: this.window });
    } else if (lazy.AppInfo.isFirefox) {
      const opened = new lazy.EventPromise(this.window, "TabOpen");
      this.window.BrowserCommands.openTab({ url: "about:blank" });
      await opened;

      tab = this.tabBrowser.selectedTab;

      // The new tab is always selected by default. If focus is not wanted,
      // the previously tab needs to be selected again.
      if (!focus) {
        await lazy.TabManager.selectTab(this.tab);
      }
    } else {
      throw new lazy.error.UnsupportedOperationError(
        `openTab() not supported for ${lazy.AppInfo.name}`
      );
    }

    return tab;
  }

  /**
   * Set the current tab.
   *
   * @param {number=} index
   *     Tab index to switch to. If the parameter is undefined,
   *     the currently selected tab will be used.
   * @param {ChromeWindow=} window
   *     Switch to this window before selecting the tab.
   * @param {boolean=} focus
   *      A boolean value which determins whether to focus
   *      the window. Defaults to true.
   *
   * @returns {Tab}
   *     The selected tab.
   *
   * @throws UnsupportedOperationError
   *     If tab handling for the current application isn't supported.
   */
  async switchToTab(index, window = undefined, focus = true) {
    if (window) {
      this.window = window;
      this.tabBrowser = lazy.TabManager.getTabBrowser(this.window);
    }

    if (!this.tabBrowser || this.driver.isReftestBrowser(this.tabBrowser)) {
      return null;
    }

    if (typeof index == "undefined") {
      this.tab = this.tabBrowser.selectedTab;
    } else {
      this.tab = this.tabBrowser.tabs[index];
    }

    if (focus) {
      await lazy.TabManager.selectTab(this.tab);
    }

    // By accessing the content browser's message manager a new browsing
    // context is created for browserless tabs, which is needed to successfully
    // run the WebDriver's is browsing context open step. This is temporary
    // until we find a better solution on bug 1812258.
    this.messageManager;

    return this.tab;
  }

  /**
   * Registers a new frame, and sets its current frame id to this frame
   * if it is not already assigned, and if a) we already have a session
   * or b) we're starting a new session and it is the right start frame.
   */
  register() {
    if (!this.tabBrowser) {
      return;
    }

    // If we're setting up a new session on Firefox, we only process the
    // registration for this frame if it belongs to the current tab.
    if (!this.tab) {
      this.switchToTab();
    }
  }
};

/**
 * Marionette representation of the {@link ChromeWindow} window state.
 *
 * @enum {string}
 */
export const WindowState = {
  Maximized: "maximized",
  Minimized: "minimized",
  Normal: "normal",
  Fullscreen: "fullscreen",

  /**
   * Converts {@link Window.windowState} to WindowState.
   *
   * @param {number} windowState
   *     Attribute from {@link Window.windowState}.
   *
   * @returns {WindowState}
   *     JSON representation.
   *
   * @throws {TypeError}
   *     If <var>windowState</var> was unknown.
   */
  from(windowState) {
    switch (windowState) {
      case 1:
        return WindowState.Maximized;

      case 2:
        return WindowState.Minimized;

      case 3:
        return WindowState.Normal;

      case 4:
        return WindowState.Fullscreen;

      default:
        throw new TypeError(`Unknown window state: ${windowState}`);
    }
  },
};
PK
!<Z��55-chrome/remote/content/marionette/cert.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "sss",
  "@mozilla.org/ssservice;1",
  "nsISiteSecurityService"
);

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "certOverrideService",
  "@mozilla.org/security/certoverride;1",
  "nsICertOverrideService"
);

const CERT_PINNING_ENFORCEMENT_PREF = "security.cert_pinning.enforcement_level";
const HSTS_PRELOAD_LIST_PREF = "network.stricttransportsecurity.preloadlist";

/** @namespace */
export const allowAllCerts = {};

/**
 * Disable all security check and allow all certs.
 */
allowAllCerts.enable = function () {
  // make it possible to register certificate overrides for domains
  // that use HSTS or HPKP
  Services.prefs.setBoolPref(HSTS_PRELOAD_LIST_PREF, false);
  Services.prefs.setIntPref(CERT_PINNING_ENFORCEMENT_PREF, 0);

  lazy.certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
    true
  );
};

/**
 * Enable all security check.
 */
allowAllCerts.disable = function () {
  lazy.certOverrideService.setDisableAllSecurityChecksAndLetAttackersInterceptMyData(
    false
  );

  Services.prefs.clearUserPref(HSTS_PRELOAD_LIST_PREF);
  Services.prefs.clearUserPref(CERT_PINNING_ENFORCEMENT_PREF);

  // clear collected HSTS and HPKP state
  // through the site security service
  lazy.sss.clearAll();
};
PK
!<f9:�U#U#/chrome/remote/content/marionette/cookie.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
});

const IPV4_PORT_EXPR = /:\d+$/;

const SAMESITE_MAP = new Map([
  ["None", Ci.nsICookie.SAMESITE_NONE],
  ["Lax", Ci.nsICookie.SAMESITE_LAX],
  ["Strict", Ci.nsICookie.SAMESITE_STRICT],
]);

/** @namespace */
export const cookie = {
  manager: Services.cookies,
};

/**
 * @name Cookie
 *
 * @returns {Record<string, (number|boolean|string)>}
 */

/**
 * Unmarshal a JSON Object to a cookie representation.
 *
 * Effectively this will run validation checks on ``json``, which
 * will produce the errors expected by WebDriver if the input is
 * not valid.
 *
 * @param {Record<string, (number | boolean | string)>} json
 *     Cookie to be deserialised. ``name`` and ``value`` are required
 *     fields which must be strings.  The ``path`` and ``domain`` fields
 *     are optional, but must be a string if provided.  The ``secure``,
 *     and ``httpOnly`` are similarly optional, but must be booleans.
 *     Likewise, the ``expiry`` field is optional but must be
 *     unsigned integer.
 *
 * @returns {Cookie}
 *     Valid cookie object.
 *
 * @throws {InvalidArgumentError}
 *     If any of the properties are invalid.
 */
cookie.fromJSON = function (json) {
  let newCookie = {};

  lazy.assert.object(
    json,
    lazy.pprint`Expected "cookie" to be an object, got ${json}`
  );

  newCookie.name = lazy.assert.string(
    json.name,
    lazy.pprint`Expected cookie "name" to be a string, got ${json.name}`
  );
  newCookie.value = lazy.assert.string(
    json.value,
    lazy.pprint`Expected cookie "value" to be a string, got ${json.value}`
  );

  if (typeof json.path != "undefined") {
    newCookie.path = lazy.assert.string(
      json.path,
      lazy.pprint`Expected cookie "path" to be a string, got ${json.path}`
    );
  }
  if (typeof json.domain != "undefined") {
    newCookie.domain = lazy.assert.string(
      json.domain,
      lazy.pprint`Expected cookie "domain" to be a string, got ${json.domain}`
    );
  }
  if (typeof json.secure != "undefined") {
    newCookie.secure = lazy.assert.boolean(
      json.secure,
      lazy.pprint`Expected cookie "secure" to be a boolean, got ${json.secure}`
    );
  }
  if (typeof json.httpOnly != "undefined") {
    newCookie.httpOnly = lazy.assert.boolean(
      json.httpOnly,
      lazy.pprint`Expected cookie "httpOnly" to be a boolean, got ${json.httpOnly}`
    );
  }
  if (typeof json.expiry != "undefined") {
    newCookie.expiry = lazy.assert.positiveInteger(
      json.expiry,
      lazy.pprint`Expected cookie "expiry" to be a positive integer, got ${json.expiry}`
    );
  }
  if (typeof json.sameSite != "undefined") {
    const validOptions = Array.from(SAMESITE_MAP.keys());
    newCookie.sameSite = lazy.assert.in(
      json.sameSite,
      validOptions,
      `Expected cookie "sameSite" to be one of ${validOptions.toString()}, ` +
        lazy.pprint`got ${json.sameSite}`
    );
  }

  return newCookie;
};

/**
 * Insert cookie to the cookie store.
 *
 * @param {Cookie} newCookie
 *     Cookie to add.
 * @param {object} options
 * @param {string=} options.restrictToHost
 *     Perform test that ``newCookie``'s domain matches this.
 * @param {string=} options.protocol
 *     The protocol of the caller. It can be `http:` or `https:`.
 *
 * @throws {TypeError}
 *     If ``name``, ``value``, or ``domain`` are not present and
 *     of the correct type.
 * @throws {InvalidCookieDomainError}
 *     If ``restrictToHost`` is set and ``newCookie``'s domain does
 *     not match.
 * @throws {UnableToSetCookieError}
 *     If an error occurred while trying to save the cookie.
 */
cookie.add = function (
  newCookie,
  { restrictToHost = null, protocol = null } = {}
) {
  lazy.assert.string(
    newCookie.name,
    lazy.pprint`Expected cookie "name" to be a string, got ${newCookie.name}`
  );
  lazy.assert.string(
    newCookie.value,
    lazy.pprint`Expected cookie "value" to be a string, got ${newCookie.value}`
  );

  if (typeof newCookie.path == "undefined") {
    newCookie.path = "/";
  }

  let hostOnly = false;
  if (typeof newCookie.domain == "undefined") {
    hostOnly = true;
    newCookie.domain = restrictToHost;
  }
  lazy.assert.string(
    newCookie.domain,
    lazy.pprint`Expected cookie "domain" to be a string, got ${newCookie.domain}`
  );
  if (newCookie.domain.substring(0, 1) === ".") {
    newCookie.domain = newCookie.domain.substring(1);
  }

  if (typeof newCookie.secure == "undefined") {
    newCookie.secure = false;
  }
  if (typeof newCookie.httpOnly == "undefined") {
    newCookie.httpOnly = false;
  }
  if (typeof newCookie.expiry == "undefined") {
    // The XPCOM interface requires the expiry field even for session cookies.
    newCookie.expiry = Number.MAX_SAFE_INTEGER;
    newCookie.session = true;
  } else {
    newCookie.session = false;
  }
  newCookie.sameSite = SAMESITE_MAP.get(newCookie.sameSite || "None");

  let isIpAddress = false;
  try {
    Services.eTLD.getPublicSuffixFromHost(newCookie.domain);
  } catch (e) {
    switch (e.result) {
      case Cr.NS_ERROR_HOST_IS_IP_ADDRESS:
        isIpAddress = true;
        break;
      default:
        throw new lazy.error.InvalidCookieDomainError(newCookie.domain);
    }
  }

  if (!hostOnly && !isIpAddress) {
    // only store this as a domain cookie if the domain was specified in the
    // request and it wasn't an IP address.
    newCookie.domain = "." + newCookie.domain;
  }

  if (restrictToHost) {
    if (
      !restrictToHost.endsWith(newCookie.domain) &&
      "." + restrictToHost !== newCookie.domain &&
      restrictToHost !== newCookie.domain
    ) {
      throw new lazy.error.InvalidCookieDomainError(
        `Cookies may only be set ` +
          `for the current domain (${restrictToHost})`
      );
    }
  }

  let schemeType = Ci.nsICookie.SCHEME_UNSET;
  switch (protocol) {
    case "http:":
      schemeType = Ci.nsICookie.SCHEME_HTTP;
      break;
    case "https:":
      schemeType = Ci.nsICookie.SCHEME_HTTPS;
      break;
    default:
      // Any other protocol that is supported by the cookie service.
      break;
  }

  // remove port from domain, if present.
  // unfortunately this catches IPv6 addresses by mistake
  // TODO: Bug 814416
  newCookie.domain = newCookie.domain.replace(IPV4_PORT_EXPR, "");

  try {
    cookie.manager.add(
      newCookie.domain,
      newCookie.path,
      newCookie.name,
      newCookie.value,
      newCookie.secure,
      newCookie.httpOnly,
      newCookie.session,
      newCookie.expiry,
      {} /* origin attributes */,
      newCookie.sameSite,
      schemeType
    );
  } catch (e) {
    throw new lazy.error.UnableToSetCookieError(e);
  }
};

/**
 * Remove cookie from the cookie store.
 *
 * @param {Cookie} toDelete
 *     Cookie to remove.
 */
cookie.remove = function (toDelete) {
  cookie.manager.remove(
    toDelete.domain,
    toDelete.name,
    toDelete.path,
    {} /* originAttributes */
  );
};

/**
 * Iterates over the cookies for the current ``host``.  You may
 * optionally filter for specific paths on that ``host`` by specifying
 * a path in ``currentPath``.
 *
 * @param {string} host
 *     Hostname to retrieve cookies for.
 * @param {string=} [currentPath="/"] currentPath
 *     Optionally filter the cookies for ``host`` for the specific path.
 *     Defaults to ``/``, meaning all cookies for ``host`` are included.
 *
 * @returns {Iterable.<Cookie>}
 *     Iterator.
 */
cookie.iter = function* (host, currentPath = "/") {
  lazy.assert.string(
    host,
    lazy.pprint`Expected "host" to be a string, got ${host}`
  );
  lazy.assert.string(
    currentPath,
    lazy.pprint`Expected "currentPath" to be a string, got ${currentPath}`
  );

  const isForCurrentPath = path => currentPath.includes(path);

  let cookies = cookie.manager.getCookiesFromHost(host, {});
  for (let cookie of cookies) {
    // take the hostname and progressively shorten
    let hostname = host;
    do {
      if (
        (cookie.host == "." + hostname || cookie.host == hostname) &&
        isForCurrentPath(cookie.path)
      ) {
        let data = {
          name: cookie.name,
          value: cookie.value,
          path: cookie.path,
          domain: cookie.host,
          secure: cookie.isSecure,
          httpOnly: cookie.isHttpOnly,
        };

        if (!cookie.isSession) {
          data.expiry = cookie.expiry;
        }

        data.sameSite = [...SAMESITE_MAP].find(
          ([, value]) => cookie.sameSite === value
        )[0];

        yield data;
      }
      hostname = hostname.replace(/^.*?\./, "");
    } while (hostname.includes("."));
  }
};
PK
!<�=������/chrome/remote/content/marionette/driver.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Addon: "chrome://remote/content/marionette/addon.sys.mjs",
  AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  browser: "chrome://remote/content/marionette/browser.sys.mjs",
  capture: "chrome://remote/content/shared/Capture.sys.mjs",
  Context: "chrome://remote/content/marionette/browser.sys.mjs",
  cookie: "chrome://remote/content/marionette/cookie.sys.mjs",
  DebounceCallback: "chrome://remote/content/marionette/sync.sys.mjs",
  disableEventsActor:
    "chrome://remote/content/marionette/actors/MarionetteEventsParent.sys.mjs",
  dom: "chrome://remote/content/shared/DOM.sys.mjs",
  enableEventsActor:
    "chrome://remote/content/marionette/actors/MarionetteEventsParent.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  EventPromise: "chrome://remote/content/shared/Sync.sys.mjs",
  getMarionetteCommandsActorProxy:
    "chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs",
  IdlePromise: "chrome://remote/content/marionette/sync.sys.mjs",
  l10n: "chrome://remote/content/marionette/l10n.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  Marionette: "chrome://remote/content/components/Marionette.sys.mjs",
  MarionettePrefs: "chrome://remote/content/marionette/prefs.sys.mjs",
  modal: "chrome://remote/content/shared/Prompt.sys.mjs",
  navigate: "chrome://remote/content/marionette/navigate.sys.mjs",
  permissions: "chrome://remote/content/shared/Permissions.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
  print: "chrome://remote/content/shared/PDF.sys.mjs",
  PollPromise: "chrome://remote/content/shared/Sync.sys.mjs",
  PromptHandlers:
    "chrome://remote/content/shared/webdriver/UserPromptHandler.sys.mjs",
  PromptListener:
    "chrome://remote/content/shared/listeners/PromptListener.sys.mjs",
  PromptTypes:
    "chrome://remote/content/shared/webdriver/UserPromptHandler.sys.mjs",
  quit: "chrome://remote/content/shared/Browser.sys.mjs",
  reftest: "chrome://remote/content/marionette/reftest.sys.mjs",
  registerCommandsActor:
    "chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs",
  RemoteAgent: "chrome://remote/content/components/RemoteAgent.sys.mjs",
  ShadowRoot: "chrome://remote/content/marionette/web-reference.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
  TimedPromise: "chrome://remote/content/marionette/sync.sys.mjs",
  Timeouts: "chrome://remote/content/shared/webdriver/Capabilities.sys.mjs",
  unregisterCommandsActor:
    "chrome://remote/content/marionette/actors/MarionetteCommandsParent.sys.mjs",
  waitForInitialNavigationCompleted:
    "chrome://remote/content/shared/Navigate.sys.mjs",
  webauthn: "chrome://remote/content/marionette/webauthn.sys.mjs",
  WebDriverSession: "chrome://remote/content/shared/webdriver/Session.sys.mjs",
  WebElement: "chrome://remote/content/marionette/web-reference.sys.mjs",
  windowManager: "chrome://remote/content/shared/WindowManager.sys.mjs",
  WindowState: "chrome://remote/content/marionette/browser.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
);

const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

ChromeUtils.defineLazyGetter(
  lazy,
  "supportedStrategies",
  () =>
    new Set([
      lazy.dom.Strategy.ClassName,
      lazy.dom.Strategy.Selector,
      lazy.dom.Strategy.ID,
      lazy.dom.Strategy.Name,
      lazy.dom.Strategy.LinkText,
      lazy.dom.Strategy.PartialLinkText,
      lazy.dom.Strategy.TagName,
      lazy.dom.Strategy.XPath,
    ])
);

// Timeout used to abort fullscreen, maximize, and minimize
// commands if no window manager is present.
const TIMEOUT_NO_WINDOW_MANAGER = 5000;

// Observer topic to wait for until the browser window is ready.
const TOPIC_BROWSER_READY = "browser-delayed-startup-finished";
// Observer topic to perform clean up when application quit is requested.
const TOPIC_QUIT_APPLICATION_REQUESTED = "quit-application-requested";

/**
 * The Marionette WebDriver services provides a standard conforming
 * implementation of the W3C WebDriver specification.
 *
 * @see {@link https://w3c.github.io/webdriver/webdriver-spec.html}
 * @namespace driver
 */

/**
 * Implements (parts of) the W3C WebDriver protocol.  GeckoDriver lives
 * in chrome space and mediates calls to the current browsing context's actor.
 *
 * Throughout this prototype, functions with the argument <var>cmd</var>'s
 * documentation refers to the contents of the <code>cmd.parameter</code>
 * object.
 *
 * @class GeckoDriver
 *
 * @param {MarionetteServer} server
 *     The instance of Marionette server.
 */
export function GeckoDriver(server) {
  this._server = server;

  // WebDriver Session
  this._currentSession = null;

  // Flag to indicate a WebDriver HTTP session
  this._sessionConfigFlags = new Set([lazy.WebDriverSession.SESSION_FLAG_HTTP]);

  // Flag to indicate that the application is shutting down
  this._isShuttingDown = false;

  this.browsers = {};

  // points to current browser
  this.curBrowser = null;
  // top-most chrome window
  this.mainFrame = null;

  // Use content context by default
  this.context = lazy.Context.Content;

  // used for modal dialogs
  this.dialog = null;
  this.promptListener = null;
}

/**
 * The current context decides if commands are executed in chrome- or
 * content space.
 */
Object.defineProperty(GeckoDriver.prototype, "context", {
  get() {
    return this._context;
  },

  set(context) {
    this._context = lazy.Context.fromString(context);
  },
});

/**
 * The current WebDriver Session.
 */
Object.defineProperty(GeckoDriver.prototype, "currentSession", {
  get() {
    if (lazy.RemoteAgent.webDriverBiDi) {
      return lazy.RemoteAgent.webDriverBiDi.session;
    }

    return this._currentSession;
  },
});

/**
 * Returns the current URL of the ChromeWindow or content browser,
 * depending on context.
 *
 * @returns {URL}
 *     Read-only property containing the currently loaded URL.
 */
Object.defineProperty(GeckoDriver.prototype, "currentURL", {
  get() {
    const browsingContext = this.getBrowsingContext({ top: true });
    return new URL(browsingContext.currentWindowGlobal.documentURI.spec);
  },
});

/**
 * Returns the title of the ChromeWindow or content browser,
 * depending on context.
 *
 * @returns {string}
 *     Read-only property containing the title of the loaded URL.
 */
Object.defineProperty(GeckoDriver.prototype, "title", {
  get() {
    const browsingContext = this.getBrowsingContext({ top: true });
    return browsingContext.currentWindowGlobal.documentTitle;
  },
});

Object.defineProperty(GeckoDriver.prototype, "windowType", {
  get() {
    return this.curBrowser.window.document.documentElement.getAttribute(
      "windowtype"
    );
  },
});

GeckoDriver.prototype.QueryInterface = ChromeUtils.generateQI([
  "nsIObserver",
  "nsISupportsWeakReference",
]);

/**
 * Callback used to observe the closing of modal dialogs
 * during the session's lifetime.
 */
GeckoDriver.prototype.handleClosedModalDialog = function () {
  this.dialog = null;
};

/**
 * Callback used to observe the creation of new modal dialogs
 * during the session's lifetime.
 */
GeckoDriver.prototype.handleOpenModalDialog = function (eventName, data) {
  this.dialog = data.prompt;

  if (this.dialog.promptType === "beforeunload" && !this.currentSession?.bidi) {
    // Only implicitly accept the prompt when its not a BiDi session.
    lazy.logger.trace(`Implicitly accepted "beforeunload" prompt`);
    this.dialog.accept();
    return;
  }

  if (!this._isShuttingDown) {
    this.getActor().notifyDialogOpened(this.dialog);
  }
};

/**
 * Get the current URL.
 *
 * @param {object} options
 * @param {boolean=} options.top
 *     If set to true return the window's top-level URL,
 *     otherwise the one from the currently selected frame. Defaults to true.
 * @see https://w3c.github.io/webdriver/#get-current-url
 */
GeckoDriver.prototype._getCurrentURL = function (options = {}) {
  if (options.top === undefined) {
    options.top = true;
  }
  const browsingContext = this.getBrowsingContext(options);
  return new URL(browsingContext.currentURI.spec);
};

/**
 * Get the current "MarionetteCommands" parent actor.
 *
 * @param {object} options
 * @param {boolean=} options.top
 *     If set to true use the window's top-level browsing context for the actor,
 *     otherwise the one from the currently selected frame. Defaults to false.
 *
 * @returns {MarionetteCommandsParent}
 *     The parent actor.
 */
GeckoDriver.prototype.getActor = function (options = {}) {
  return lazy.getMarionetteCommandsActorProxy(() =>
    this.getBrowsingContext(options)
  );
};

/**
 * Get the selected BrowsingContext for the current context.
 *
 * @param {object} options
 * @param {Context=} options.context
 *     Context (content or chrome) for which to retrieve the browsing context.
 *     Defaults to the current one.
 * @param {boolean=} options.parent
 *     If set to true return the window's parent browsing context,
 *     otherwise the one from the currently selected frame. Defaults to false.
 * @param {boolean=} options.top
 *     If set to true return the window's top-level browsing context,
 *     otherwise the one from the currently selected frame. Defaults to false.
 *
 * @returns {BrowsingContext}
 *     The browsing context, or `null` if none is available
 */
GeckoDriver.prototype.getBrowsingContext = function (options = {}) {
  const { context = this.context, parent = false, top = false } = options;

  let browsingContext = null;
  if (context === lazy.Context.Chrome) {
    browsingContext = this.currentSession?.chromeBrowsingContext;
  } else {
    browsingContext = this.currentSession?.contentBrowsingContext;
  }

  if (browsingContext && parent) {
    browsingContext = browsingContext.parent;
  }

  if (browsingContext && top) {
    browsingContext = browsingContext.top;
  }

  return browsingContext;
};

/**
 * Get the currently selected window.
 *
 * It will return the outer {@link ChromeWindow} previously selected by
 * window handle through {@link #switchToWindow}, or the first window that
 * was registered.
 *
 * @param {object} options
 * @param {Context=} options.context
 *     Optional name of the context to use for finding the window.
 *     It will be required if a command always needs a specific context,
 *     whether which context is currently set. Defaults to the current
 *     context.
 *
 * @returns {ChromeWindow}
 *     The current top-level browsing context.
 */
GeckoDriver.prototype.getCurrentWindow = function (options = {}) {
  const { context = this.context } = options;

  let win = null;
  switch (context) {
    case lazy.Context.Chrome:
      if (this.curBrowser) {
        win = this.curBrowser.window;
      }
      break;

    case lazy.Context.Content:
      if (this.curBrowser && this.curBrowser.contentBrowser) {
        win = this.curBrowser.window;
      }
      break;
  }

  return win;
};

GeckoDriver.prototype.isReftestBrowser = function (element) {
  return (
    this._reftest &&
    element &&
    element.tagName === "xul:browser" &&
    element.parentElement &&
    element.parentElement.id === "reftest"
  );
};

/**
 * Create a new browsing context for window and add to known browsers.
 *
 * @param {ChromeWindow} win
 *     Window for which we will create a browsing context.
 *
 * @returns {string}
 *     Returns the unique server-assigned ID of the window.
 */
GeckoDriver.prototype.addBrowser = function (win) {
  let context = new lazy.browser.Context(win, this);
  let winId = lazy.windowManager.getIdForWindow(win);

  this.browsers[winId] = context;
  this.curBrowser = this.browsers[winId];
};

/**
 * Handles registration of new content browsers.  Depending on
 * their type they are either accepted or ignored.
 *
 * @param {XULBrowser} browserElement
 */
GeckoDriver.prototype.registerBrowser = function (browserElement) {
  // We want to ignore frames that are XUL browsers that aren't in the "main"
  // tabbrowser, but accept things on Fennec (which doesn't have a
  // xul:tabbrowser), and accept HTML iframes (because tests depend on it),
  // as well as XUL frames. Ideally this should be cleaned up and we should
  // keep track of browsers a different way.
  if (
    !lazy.AppInfo.isFirefox ||
    browserElement.namespaceURI != XUL_NS ||
    browserElement.nodeName != "browser" ||
    browserElement.getTabBrowser()
  ) {
    this.curBrowser.register(browserElement);
  }
};

/**
 * Create a new WebDriver session.
 *
 * @param {object} cmd
 * @param {Record<string, *>=} cmd.parameters
 *     JSON Object containing any of the recognised capabilities as listed
 *     on the `WebDriverSession` class.
 *
 * @returns {object}
 *     Session ID and capabilities offered by the WebDriver service.
 *
 * @throws {SessionNotCreatedError}
 *     If, for whatever reason, a session could not be created.
 */
GeckoDriver.prototype.newSession = async function (cmd) {
  if (this.currentSession) {
    throw new lazy.error.SessionNotCreatedError(
      "Maximum number of active sessions"
    );
  }

  const { parameters: capabilities } = cmd;

  try {
    if (lazy.RemoteAgent.webDriverBiDi) {
      // If the WebDriver BiDi protocol is active always use the Remote Agent
      // to handle the WebDriver session.
      await lazy.RemoteAgent.webDriverBiDi.createSession(
        capabilities,
        this._sessionConfigFlags
      );
    } else {
      // If it's not the case then Marionette itself needs to handle it, and
      // has to nullify the "webSocketUrl" capability.
      this._currentSession = new lazy.WebDriverSession(
        capabilities,
        this._sessionConfigFlags
      );
      this._currentSession.capabilities.delete("webSocketUrl");
    }

    // Don't wait for the initial window when Marionette is in windowless mode
    if (!this.currentSession.capabilities.get("moz:windowless")) {
      // Creating a WebDriver session too early can cause issues with
      // clients in not being able to find any available window handle.
      // Also when closing the application while it's still starting up can
      // cause shutdown hangs. As such Marionette will return a new session
      // once the initial application window has finished initializing.
      lazy.logger.debug(`Waiting for initial application window`);
      await lazy.Marionette.browserStartupFinished;

      const appWin =
        await lazy.windowManager.waitForInitialApplicationWindowLoaded();

      if (lazy.MarionettePrefs.clickToStart) {
        Services.prompt.alert(
          appWin,
          "",
          "Click to start execution of marionette tests"
        );
      }

      this.addBrowser(appWin);
      this.mainFrame = appWin;

      // Setup observer for modal dialogs
      this.promptListener = new lazy.PromptListener(() => this.curBrowser);
      this.promptListener.on("closed", this.handleClosedModalDialog.bind(this));
      this.promptListener.on("opened", this.handleOpenModalDialog.bind(this));
      this.promptListener.startListening();

      for (let win of lazy.windowManager.windows) {
        this.registerWindow(win, { registerBrowsers: true });
      }

      if (this.mainFrame) {
        this.currentSession.chromeBrowsingContext =
          this.mainFrame.browsingContext;
        this.mainFrame.focus();
      }

      if (this.curBrowser.tab) {
        const browsingContext = this.curBrowser.contentBrowser.browsingContext;
        this.currentSession.contentBrowsingContext = browsingContext;

        // Bug 1838381 - Only use a longer unload timeout for desktop, because
        // on Android only the initial document is loaded, and loading a
        // specific page during startup doesn't succeed.
        const options = {};
        if (!lazy.AppInfo.isAndroid) {
          options.unloadTimeout = 5000;
        }

        await lazy.waitForInitialNavigationCompleted(
          browsingContext.webProgress,
          options
        );

        this.curBrowser.contentBrowser.focus();
      }

      // Check if there is already an open dialog for the selected browser window.
      this.dialog = lazy.modal.findPrompt(this.curBrowser);
    }

    lazy.registerCommandsActor(this.currentSession.id);
    lazy.enableEventsActor();

    Services.obs.addObserver(this, TOPIC_BROWSER_READY);
  } catch (e) {
    throw new lazy.error.SessionNotCreatedError(e);
  }

  return {
    sessionId: this.currentSession.id,
    capabilities: this.currentSession.capabilities,
  };
};

/**
 * Start observing the specified window.
 *
 * @param {ChromeWindow} win
 *     Chrome window to register event listeners for.
 * @param {object=} options
 * @param {boolean=} options.registerBrowsers
 *     If true, register all content browsers of found tabs. Defaults to false.
 */
GeckoDriver.prototype.registerWindow = function (win, options = {}) {
  const { registerBrowsers = false } = options;
  const tabBrowser = lazy.TabManager.getTabBrowser(win);

  if (registerBrowsers && tabBrowser) {
    for (const tab of tabBrowser.tabs) {
      const contentBrowser = lazy.TabManager.getBrowserForTab(tab);
      this.registerBrowser(contentBrowser);
    }
  }

  // Listen for any kind of top-level process switch
  tabBrowser?.addEventListener("XULFrameLoaderCreated", this);
};

/**
 * Stop observing the specified window.
 *
 * @param {ChromeWindow} win
 *     Chrome window to unregister event listeners for.
 */
GeckoDriver.prototype.stopObservingWindow = function (win) {
  const tabBrowser = lazy.TabManager.getTabBrowser(win);

  tabBrowser?.removeEventListener("XULFrameLoaderCreated", this);
};

GeckoDriver.prototype.handleEvent = function ({ target, type }) {
  switch (type) {
    case "XULFrameLoaderCreated":
      if (target === this.curBrowser.contentBrowser) {
        lazy.logger.trace(
          "Remoteness change detected. Set new top-level browsing context " +
            `to ${target.browsingContext.id}`
        );

        this.currentSession.contentBrowsingContext = target.browsingContext;
      }
      break;
  }
};

GeckoDriver.prototype.observe = async function (subject, topic) {
  switch (topic) {
    case TOPIC_BROWSER_READY:
      this.registerWindow(subject);
      break;

    case TOPIC_QUIT_APPLICATION_REQUESTED:
      // Run Marionette specific cleanup steps before allowing
      // the application to shutdown
      await this._server.setAcceptConnections(false);
      this.deleteSession();
      break;
  }
};

/**
 * Send the current session's capabilities to the client.
 *
 * Capabilities informs the client of which WebDriver features are
 * supported by Firefox and Marionette.  They are immutable for the
 * length of the session.
 *
 * The return value is an immutable map of string keys
 * ("capabilities") to values, which may be of types boolean,
 * numerical or string.
 */
GeckoDriver.prototype.getSessionCapabilities = function () {
  return { capabilities: this.currentSession.capabilities };
};

/**
 * Sets the context of the subsequent commands.
 *
 * All subsequent requests to commands that in some way involve
 * interaction with a browsing context will target the chosen browsing
 * context.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.value
 *     Name of the context to be switched to.  Must be one of "chrome" or
 *     "content".
 *
 * @throws {InvalidArgumentError}
 *     If <var>value</var> is not a string.
 * @throws {WebDriverError}
 *     If <var>value</var> is not a valid browsing context.
 */
GeckoDriver.prototype.setContext = function (cmd) {
  let value = lazy.assert.string(
    cmd.parameters.value,
    lazy.pprint`Expected "value" to be a string, got ${cmd.parameters.value}`
  );

  this.context = value;
};

/**
 * Gets the context type that is Marionette's current target for
 * browsing context scoped commands.
 *
 * You may choose a context through the {@link #setContext} command.
 *
 * The default browsing context is {@link Context.Content}.
 *
 * @returns {Context}
 *     Current context.
 */
GeckoDriver.prototype.getContext = function () {
  return this.context;
};

/**
 * Executes a JavaScript function in the context of the current browsing
 * context, if in content space, or in chrome space otherwise, and returns
 * the return value of the function.
 *
 * It is important to note that if the <var>sandboxName</var> parameter
 * is left undefined, the script will be evaluated in a mutable sandbox,
 * causing any change it makes on the global state of the document to have
 * lasting side-effects.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.script
 *     Script to evaluate as a function body.
 * @param {Array.<(string|boolean|number|object|WebReference)>} cmd.parameters.args
 *     Arguments exposed to the script in <code>arguments</code>.
 *     The array items must be serialisable to the WebDriver protocol.
 * @param {string=} cmd.parameters.sandbox
 *     Name of the sandbox to evaluate the script in.  The sandbox is
 *     cached for later re-use on the same Window object if
 *     <var>newSandbox</var> is false.  If he parameter is undefined,
 *     the script is evaluated in a mutable sandbox.  If the parameter
 *     is "system", it will be evaluted in a sandbox with elevated system
 *     privileges, equivalent to chrome space.
 * @param {boolean=} cmd.parameters.newSandbox
 *     Forces the script to be evaluated in a fresh sandbox.  Note that if
 *     it is undefined, the script will normally be evaluted in a fresh
 *     sandbox.
 * @param {string=} cmd.parameters.filename
 *     Filename of the client's program where this script is evaluated.
 * @param {number=} cmd.parameters.line
 *     Line in the client's program where this script is evaluated.
 *
 * @returns {(string|boolean|number|object|WebReference)}
 *     Return value from the script, or null which signifies either the
 *     JavaScript notion of null or undefined.
 *
 * @throws {JavaScriptError}
 *     If an {@link Error} was thrown whilst evaluating the script.
 * @throws {NoSuchElementError}
 *     If an element that was passed as part of <var>args</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {ScriptTimeoutError}
 *     If the script was interrupted due to reaching the session's
 *     script timeout.
 * @throws {StaleElementReferenceError}
 *     If an element that was passed as part of <var>args</var> or that is
 *     returned as result has gone stale.
 */
GeckoDriver.prototype.executeScript = function (cmd) {
  let { script, args } = cmd.parameters;
  let opts = {
    script: cmd.parameters.script,
    args: cmd.parameters.args,
    sandboxName: cmd.parameters.sandbox,
    newSandbox: cmd.parameters.newSandbox,
    file: cmd.parameters.filename,
    line: cmd.parameters.line,
  };

  return this.execute_(script, args, opts);
};

/**
 * Executes a JavaScript function in the context of the current browsing
 * context, if in content space, or in chrome space otherwise, and returns
 * the object passed to the callback.
 *
 * The callback is always the last argument to the <var>arguments</var>
 * list passed to the function scope of the script.  It can be retrieved
 * as such:
 *
 * <pre><code>
 *     let callback = arguments[arguments.length - 1];
 *     callback("foo");
 *     // "foo" is returned
 * </code></pre>
 *
 * It is important to note that if the <var>sandboxName</var> parameter
 * is left undefined, the script will be evaluated in a mutable sandbox,
 * causing any change it makes on the global state of the document to have
 * lasting side-effects.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.script
 *     Script to evaluate as a function body.
 * @param {Array.<(string|boolean|number|object|WebReference)>} cmd.parameters.args
 *     Arguments exposed to the script in <code>arguments</code>.
 *     The array items must be serialisable to the WebDriver protocol.
 * @param {string=} cmd.parameters.sandbox
 *     Name of the sandbox to evaluate the script in.  The sandbox is
 *     cached for later re-use on the same Window object if
 *     <var>newSandbox</var> is false.  If the parameter is undefined,
 *     the script is evaluated in a mutable sandbox.  If the parameter
 *     is "system", it will be evaluted in a sandbox with elevated system
 *     privileges, equivalent to chrome space.
 * @param {boolean=} cmd.parameters.newSandbox
 *     Forces the script to be evaluated in a fresh sandbox.  Note that if
 *     it is undefined, the script will normally be evaluted in a fresh
 *     sandbox.
 * @param {string=} cmd.parameters.filename
 *     Filename of the client's program where this script is evaluated.
 * @param {number=} cmd.parameters.line
 *     Line in the client's program where this script is evaluated.
 *
 * @returns {(string|boolean|number|object|WebReference)}
 *     Return value from the script, or null which signifies either the
 *     JavaScript notion of null or undefined.
 *
 * @throws {JavaScriptError}
 *     If an Error was thrown whilst evaluating the script.
 * @throws {NoSuchElementError}
 *     If an element that was passed as part of <var>args</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {ScriptTimeoutError}
 *     If the script was interrupted due to reaching the session's
 *     script timeout.
 * @throws {StaleElementReferenceError}
 *     If an element that was passed as part of <var>args</var> or that is
 *     returned as result has gone stale.
 */
GeckoDriver.prototype.executeAsyncScript = function (cmd) {
  let { script, args } = cmd.parameters;
  let opts = {
    script: cmd.parameters.script,
    args: cmd.parameters.args,
    sandboxName: cmd.parameters.sandbox,
    newSandbox: cmd.parameters.newSandbox,
    file: cmd.parameters.filename,
    line: cmd.parameters.line,
    async: true,
  };

  return this.execute_(script, args, opts);
};

GeckoDriver.prototype.execute_ = async function (
  script,
  args = [],
  {
    sandboxName = null,
    newSandbox = false,
    file = "",
    line = 0,
    async = false,
  } = {}
) {
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  lazy.assert.string(
    script,
    lazy.pprint`Expected "script" to be a string, got ${script}`
  );
  lazy.assert.array(
    args,
    lazy.pprint`Expected "args" to be an array, got ${args}`
  );
  if (sandboxName !== null) {
    lazy.assert.string(
      sandboxName,
      lazy.pprint`Expected "sandboxName" to be a string, got ${sandboxName}`
    );
  }
  lazy.assert.boolean(
    newSandbox,
    lazy.pprint`Expected "newSandbox" to be boolean, got ${newSandbox}`
  );
  lazy.assert.string(
    file,
    lazy.pprint`Expected "file" to be a string, got ${file}`
  );
  lazy.assert.number(
    line,
    lazy.pprint`Expected "line" to be a number, got ${line}`
  );

  let opts = {
    timeout: this.currentSession.timeouts.script,
    sandboxName,
    newSandbox,
    file,
    line,
    async,
  };

  return this.getActor().executeScript(script, args, opts);
};

/**
 * Navigate to given URL.
 *
 * Navigates the current browsing context to the given URL and waits for
 * the document to load or the session's page timeout duration to elapse
 * before returning.
 *
 * The command will return with a failure if there is an error loading
 * the document or the URL is blocked.  This can occur if it fails to
 * reach host, the URL is malformed, or if there is a certificate issue
 * to name some examples.
 *
 * The document is considered successfully loaded when the
 * DOMContentLoaded event on the frame element associated with the
 * current window triggers and document.readyState is "complete".
 *
 * In chrome context it will change the current window's location to
 * the supplied URL and wait until document.readyState equals "complete"
 * or the page timeout duration has elapsed.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.url
 *     URL to navigate to.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 */
GeckoDriver.prototype.navigateTo = async function (cmd) {
  lazy.assert.content(this.context);
  const browsingContext = lazy.assert.open(
    this.getBrowsingContext({ top: true })
  );
  await this._handleUserPrompts();

  let validURL;
  try {
    validURL = new URL(cmd.parameters.url);
  } catch (e) {
    throw new lazy.error.InvalidArgumentError(`Malformed URL: ${e.message}`);
  }

  // Switch to the top-level browsing context before navigating
  this.currentSession.contentBrowsingContext = browsingContext;

  const loadEventExpected = lazy.navigate.isLoadEventExpected(
    this._getCurrentURL(),
    {
      future: validURL,
    }
  );

  await lazy.navigate.waitForNavigationCompleted(
    this,
    () => {
      lazy.navigate.navigateTo(browsingContext, validURL);
    },
    { loadEventExpected }
  );

  this.curBrowser.contentBrowser.focus();
};

/**
 * Get a string representing the current URL.
 *
 * On Desktop this returns a string representation of the URL of the
 * current top level browsing context.  This is equivalent to
 * document.location.href.
 *
 * When in the context of the chrome, this returns the canonical URL
 * of the current resource.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getCurrentUrl = async function () {
  lazy.assert.open(this.getBrowsingContext({ top: true }));
  await this._handleUserPrompts();

  return this._getCurrentURL().href;
};

/**
 * Gets the current title of the window.
 *
 * @returns {string}
 *     Document title of the top-level browsing context.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getTitle = async function () {
  lazy.assert.open(this.getBrowsingContext({ top: true }));
  await this._handleUserPrompts();

  return this.title;
};

/**
 * Gets the current type of the window.
 *
 * @returns {string}
 *     Type of window
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 */
GeckoDriver.prototype.getWindowType = function () {
  lazy.assert.open(this.getBrowsingContext({ top: true }));

  return this.windowType;
};

/**
 * Gets the page source of the content document.
 *
 * @returns {string}
 *     String serialisation of the DOM of the current browsing context's
 *     active document.
 *
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getPageSource = async function () {
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  return this.getActor().getPageSource();
};

/**
 * Cause the browser to traverse one step backward in the joint history
 * of the current browsing context.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 */
GeckoDriver.prototype.goBack = async function () {
  lazy.assert.content(this.context);
  const browsingContext = lazy.assert.open(
    this.getBrowsingContext({ top: true })
  );
  await this._handleUserPrompts();

  // If there is no history, just return
  if (!browsingContext.embedderElement?.canGoBack) {
    return;
  }

  await lazy.navigate.waitForNavigationCompleted(this, () => {
    browsingContext.goBack();
  });
};

/**
 * Cause the browser to traverse one step forward in the joint history
 * of the current browsing context.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 */
GeckoDriver.prototype.goForward = async function () {
  lazy.assert.content(this.context);
  const browsingContext = lazy.assert.open(
    this.getBrowsingContext({ top: true })
  );
  await this._handleUserPrompts();

  // If there is no history, just return
  if (!browsingContext.embedderElement?.canGoForward) {
    return;
  }

  await lazy.navigate.waitForNavigationCompleted(this, () => {
    browsingContext.goForward();
  });
};

/**
 * Causes the browser to reload the page in current top-level browsing
 * context.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 */
GeckoDriver.prototype.refresh = async function () {
  lazy.assert.content(this.context);
  const browsingContext = lazy.assert.open(
    this.getBrowsingContext({ top: true })
  );
  await this._handleUserPrompts();

  // Switch to the top-level browsing context before navigating
  this.currentSession.contentBrowsingContext = browsingContext;

  await lazy.navigate.waitForNavigationCompleted(this, () => {
    lazy.navigate.refresh(browsingContext);
  });
};

/**
 * Get the current window's handle. On desktop this typically corresponds
 * to the currently selected tab.
 *
 * For chrome scope it returns the window identifier for the current chrome
 * window for tests interested in managing the chrome window and tab separately.
 *
 * Return an opaque server-assigned identifier to this window that
 * uniquely identifies it within this Marionette instance.  This can
 * be used to switch to this window at a later point.
 *
 * @returns {string}
 *     Unique window handle.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 */
GeckoDriver.prototype.getWindowHandle = function () {
  lazy.assert.open(this.getBrowsingContext({ top: true }));

  if (this.context == lazy.Context.Chrome) {
    return lazy.windowManager.getIdForWindow(this.curBrowser.window);
  }
  return lazy.TabManager.getIdForBrowser(this.curBrowser.contentBrowser);
};

/**
 * Get a list of top-level browsing contexts. On desktop this typically
 * corresponds to the set of open tabs for browser windows, or the window
 * itself for non-browser chrome windows.
 *
 * For chrome scope it returns identifiers for each open chrome window for
 * tests interested in managing a set of chrome windows and tabs separately.
 *
 * Each window handle is assigned by the server and is guaranteed unique,
 * however the return array does not have a specified ordering.
 *
 * @returns {Array.<string>}
 *     Unique window handles.
 */
GeckoDriver.prototype.getWindowHandles = function () {
  if (this.context == lazy.Context.Chrome) {
    return lazy.windowManager.chromeWindowHandles.map(String);
  }
  return lazy.TabManager.allBrowserUniqueIds.map(String);
};

/**
 * Get the current position and size of the browser window currently in focus.
 *
 * Will return the current browser window size in pixels. Refers to
 * window outerWidth and outerHeight values, which include scroll bars,
 * title bars, etc.
 *
 * @returns {Record<string, number>}
 *     Object with |x| and |y| coordinates, and |width| and |height|
 *     of browser window.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getWindowRect = async function () {
  lazy.assert.open(this.getBrowsingContext({ top: true }));
  await this._handleUserPrompts();

  return this.curBrowser.rect;
};

/**
 * Set the window position and size of the browser on the operating
 * system window manager.
 *
 * The supplied `width` and `height` values refer to the window `outerWidth`
 * and `outerHeight` values, which include browser chrome and OS-level
 * window borders.
 *
 * @param {object} cmd
 * @param {number} cmd.parameters.x
 *     X coordinate of the top/left of the window that it will be
 *     moved to.
 * @param {number} cmd.parameters.y
 *     Y coordinate of the top/left of the window that it will be
 *     moved to.
 * @param {number} cmd.parameters.width
 *     Width to resize the window to.
 * @param {number} cmd.parameters.height
 *     Height to resize the window to.
 *
 * @returns {Record<string, number>}
 *     Object with `x` and `y` coordinates and `width` and `height`
 *     dimensions.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 * @throws {UnsupportedOperationError}
 *     Not applicable to application.
 */
GeckoDriver.prototype.setWindowRect = async function (cmd) {
  lazy.assert.desktop();
  lazy.assert.open(this.getBrowsingContext({ top: true }));
  await this._handleUserPrompts();

  const { x = null, y = null, width = null, height = null } = cmd.parameters;
  if (x !== null) {
    lazy.assert.integer(
      x,
      lazy.pprint`Expected "x" to be an integer value, got ${x}`
    );
  }
  if (y !== null) {
    lazy.assert.integer(
      y,
      lazy.pprint`Expected "y" to be an integer value, got ${y}`
    );
  }
  if (height !== null) {
    lazy.assert.positiveInteger(
      height,
      lazy.pprint`Expected "height" to be a positive integer value, got ${height}`
    );
  }
  if (width !== null) {
    lazy.assert.positiveInteger(
      width,
      lazy.pprint`Expected "width" to be a positive integer value, got ${width}`
    );
  }

  const win = this.getCurrentWindow();
  switch (lazy.WindowState.from(win.windowState)) {
    case lazy.WindowState.Fullscreen:
      await exitFullscreen(win);
      break;

    case lazy.WindowState.Maximized:
    case lazy.WindowState.Minimized:
      await restoreWindow(win);
      break;
  }

  function geometryMatches() {
    if (
      width !== null &&
      height !== null &&
      (win.outerWidth !== width || win.outerHeight !== height)
    ) {
      return false;
    }
    if (x !== null && y !== null && (win.screenX !== x || win.screenY !== y)) {
      return false;
    }
    lazy.logger.trace(`Requested window geometry matches`);
    return true;
  }

  if (!geometryMatches()) {
    // There might be more than one resize or MozUpdateWindowPos event due
    // to previous geometry changes, such as from restoreWindow(), so
    // wait longer if window geometry does not match.
    const options = { checkFn: geometryMatches, timeout: 500 };
    const promises = [];
    if (width !== null && height !== null) {
      promises.push(new lazy.EventPromise(win, "resize", options));
      win.resizeTo(width, height);
    }
    if (x !== null && y !== null) {
      promises.push(
        new lazy.EventPromise(win.windowRoot, "MozUpdateWindowPos", options)
      );
      win.moveTo(x, y);
    }
    try {
      await Promise.race(promises);
    } catch (e) {
      if (e instanceof lazy.error.TimeoutError) {
        // The operating system might not honor the move or resize, in which
        // case assume that geometry will have been adjusted "as close as
        // possible" to that requested.  There may be no event received if the
        // geometry is already as close as possible.
      } else {
        throw e;
      }
    }
  }

  return this.curBrowser.rect;
};

/**
 * Switch current top-level browsing context by name or server-assigned
 * ID.  Searches for windows by name, then ID.  Content windows take
 * precedence.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.handle
 *     Handle of the window to switch to.
 * @param {boolean=} cmd.parameters.focus
 *     A boolean value which determines whether to focus
 *     the window. Defaults to true.
 *
 * @throws {InvalidArgumentError}
 *     If <var>handle</var> is not a string or <var>focus</var> not a boolean.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 */
GeckoDriver.prototype.switchToWindow = async function (cmd) {
  const { focus = true, handle } = cmd.parameters;

  lazy.assert.string(
    handle,
    lazy.pprint`Expected "handle" to be a string, got ${handle}`
  );
  lazy.assert.boolean(
    focus,
    lazy.pprint`Expected "focus" to be a boolean, got ${focus}`
  );

  const found = lazy.windowManager.findWindowByHandle(handle);

  let selected = false;
  if (found) {
    try {
      await this.setWindowHandle(found, focus);
      selected = true;
    } catch (e) {
      lazy.logger.error(e);
    }
  }

  if (!selected) {
    throw new lazy.error.NoSuchWindowError(
      `Unable to locate window: ${handle}`
    );
  }
};

/**
 * Switch the marionette window to a given window. If the browser in
 * the window is unregistered, register that browser and wait for
 * the registration is complete. If |focus| is true then set the focus
 * on the window.
 *
 * @param {object} winProperties
 *     Object containing window properties such as returned from
 *     :js:func:`GeckoDriver#getWindowProperties`
 * @param {boolean=} focus
 *     A boolean value which determines whether to focus the window.
 *     Defaults to true.
 */
GeckoDriver.prototype.setWindowHandle = async function (
  winProperties,
  focus = true
) {
  if (!(winProperties.id in this.browsers)) {
    // Initialise Marionette if the current chrome window has not been seen
    // before. Also register the initial tab, if one exists.
    this.addBrowser(winProperties.win);
    this.mainFrame = winProperties.win;

    this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext;

    if (!winProperties.hasTabBrowser) {
      this.currentSession.contentBrowsingContext = null;
    } else {
      const tabBrowser = lazy.TabManager.getTabBrowser(winProperties.win);

      // For chrome windows such as a reftest window, `getTabBrowser` is not
      // a tabbrowser, it is the content browser which should be used here.
      const contentBrowser = tabBrowser.tabs
        ? tabBrowser.selectedBrowser
        : tabBrowser;

      this.currentSession.contentBrowsingContext =
        contentBrowser.browsingContext;
      this.registerBrowser(contentBrowser);
    }
  } else {
    // Otherwise switch to the known chrome window
    this.curBrowser = this.browsers[winProperties.id];
    this.mainFrame = this.curBrowser.window;

    // Activate the tab if it's a content window.
    let tab = null;
    if (winProperties.hasTabBrowser) {
      tab = await this.curBrowser.switchToTab(
        winProperties.tabIndex,
        winProperties.win,
        focus
      );
    }

    this.currentSession.chromeBrowsingContext = this.mainFrame.browsingContext;
    this.currentSession.contentBrowsingContext =
      tab?.linkedBrowser.browsingContext;
  }

  // Check for an existing dialog for the new window
  this.dialog = lazy.modal.findPrompt(this.curBrowser);

  // If there is an open window modal dialog the underlying chrome window
  // cannot be focused.
  if (focus && !this.dialog?.isWindowModal) {
    await this.curBrowser.focusWindow();
  }
};

/**
 * Set the current browsing context for future commands to the parent
 * of the current browsing context.
 *
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.switchToParentFrame = async function () {
  let browsingContext = this.getBrowsingContext();
  if (browsingContext && !browsingContext.parent) {
    return;
  }

  browsingContext = lazy.assert.open(browsingContext?.parent);

  this.currentSession.contentBrowsingContext = browsingContext;
};

/**
 * Switch to a given frame within the current window.
 *
 * @param {object} cmd
 * @param {(string | object)=} cmd.parameters.element
 *     A web element reference of the frame or its element id.
 * @param {number=} cmd.parameters.id
 *     The index of the frame to switch to.
 *     If both element and id are not defined, switch to top-level frame.
 *
 * @throws {NoSuchElementError}
 *     If element represented by reference <var>element</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {StaleElementReferenceError}
 *     If element represented by reference <var>element</var> has gone stale.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.switchToFrame = async function (cmd) {
  const { element: el, id } = cmd.parameters;

  if (typeof id == "number") {
    lazy.assert.unsignedShort(
      id,
      lazy.pprint`Expected "id" to be an unsigned short, got ${id}`
    );
  }

  const top = id == null && el == null;
  lazy.assert.open(this.getBrowsingContext({ top }));
  await this._handleUserPrompts();

  // Bug 1495063: Elements should be passed as WebReference reference
  let byFrame;
  if (typeof el == "string") {
    byFrame = lazy.WebElement.fromUUID(el).toJSON();
  } else if (el) {
    byFrame = el;
  }

  // If the current context changed during the switchToFrame call, attempt to
  // call switchToFrame again until the browsing context remains stable.
  // See https://bugzilla.mozilla.org/show_bug.cgi?id=1786640#c11
  let browsingContext;
  for (let i = 0; i < 5; i++) {
    const currentBrowsingContext = this.currentSession.contentBrowsingContext;
    ({ browsingContext } = await this.getActor({ top }).switchToFrame(
      byFrame || id
    ));

    if (currentBrowsingContext == this.currentSession.contentBrowsingContext) {
      break;
    }
  }

  this.currentSession.contentBrowsingContext = browsingContext;
};

GeckoDriver.prototype.getTimeouts = function () {
  return this.currentSession.timeouts;
};

/**
 * Set timeout for page loading, searching, and scripts.
 *
 * @param {object} cmd
 * @param {Record<string, number>} cmd.parameters
 *     Dictionary of timeout types and their new value, where all timeout
 *     types are optional.
 *
 * @throws {InvalidArgumentError}
 *     If timeout type key is unknown, or the value provided with it is
 *     not an integer.
 */
GeckoDriver.prototype.setTimeouts = function (cmd) {
  // merge with existing timeouts
  let merged = Object.assign(
    this.currentSession.timeouts.toJSON(),
    cmd.parameters
  );

  this.currentSession.timeouts = lazy.Timeouts.fromJSON(merged);
};

/**
 * Perform a series of grouped actions at the specified points in time.
 *
 * @param {object} cmd
 * @param {Array<?>} cmd.parameters.actions
 *     Array of objects that each represent an action sequence.
 *
 * @throws {NoSuchElementError}
 *     If an element that is used as part of the action chain is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {StaleElementReferenceError}
 *     If an element that is used as part of the action chain has gone stale.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 * @throws {UnsupportedOperationError}
 *     Not yet available in current context.
 */
GeckoDriver.prototype.performActions = async function (cmd) {
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  const actions = cmd.parameters.actions;
  await this.getActor().performActions(actions);
};

/**
 * Release all the keys and pointer buttons that are currently depressed.
 *
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 */
GeckoDriver.prototype.releaseActions = async function () {
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  await this.getActor().releaseActions();
};

/**
 * Find an element using the indicated search strategy.
 *
 * @param {object} cmd
 * @param {string=} cmd.parameters.element
 *     Web element reference ID to the element that will be used as start node.
 * @param {string} cmd.parameters.using
 *     Indicates which search method to use.
 * @param {string} cmd.parameters.value
 *     Value the client is looking for.
 *
 * @returns {WebElement}
 *     Return the found element.
 *
 * @throws {NoSuchElementError}
 *     If element represented by reference <var>element</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {StaleElementReferenceError}
 *     If element represented by reference <var>element</var> has gone stale.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.findElement = async function (cmd) {
  const { element: el, using, value } = cmd.parameters;

  if (!lazy.supportedStrategies.has(using)) {
    throw new lazy.error.InvalidSelectorError(
      `Strategy not supported: ${using}`
    );
  }

  lazy.assert.defined(value);
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  let startNode;
  if (typeof el != "undefined") {
    startNode = lazy.WebElement.fromUUID(el).toJSON();
  }

  let opts = {
    startNode,
    timeout: this.currentSession.timeouts.implicit,
    all: false,
  };

  return this.getActor().findElement(using, value, opts);
};

/**
 * Find an element within shadow root using the indicated search strategy.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.shadowRoot
 *     Shadow root reference ID.
 * @param {string} cmd.parameters.using
 *     Indicates which search method to use.
 * @param {string} cmd.parameters.value
 *     Value the client is looking for.
 *
 * @returns {WebElement}
 *     Return the found element.
 *
 * @throws {DetachedShadowRootError}
 *     If shadow root represented by reference <var>id</var> is
 *     no longer attached to the DOM.
 * @throws {NoSuchElementError}
 *     If the element which is looked for with <var>value</var> was
 *     not found.
 * @throws {NoSuchShadowRoot}
 *     If shadow root represented by reference <var>shadowRoot</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.findElementFromShadowRoot = async function (cmd) {
  const { shadowRoot, using, value } = cmd.parameters;

  if (!lazy.supportedStrategies.has(using)) {
    throw new lazy.error.InvalidSelectorError(
      `Strategy not supported: ${using}`
    );
  }

  lazy.assert.defined(value);
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  const opts = {
    all: false,
    startNode: lazy.ShadowRoot.fromUUID(shadowRoot).toJSON(),
    timeout: this.currentSession.timeouts.implicit,
  };

  return this.getActor().findElement(using, value, opts);
};

/**
 * Find elements using the indicated search strategy.
 *
 * @param {object} cmd
 * @param {string=} cmd.parameters.element
 *     Web element reference ID to the element that will be used as start node.
 * @param {string} cmd.parameters.using
 *     Indicates which search method to use.
 * @param {string} cmd.parameters.value
 *     Value the client is looking for.
 *
 * @returns {Array<WebElement>}
 *     Return the array of found elements.
 *
 * @throws {NoSuchElementError}
 *     If element represented by reference <var>element</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {StaleElementReferenceError}
 *     If element represented by reference <var>element</var> has gone stale.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.findElements = async function (cmd) {
  const { element: el, using, value } = cmd.parameters;

  if (!lazy.supportedStrategies.has(using)) {
    throw new lazy.error.InvalidSelectorError(
      `Strategy not supported: ${using}`
    );
  }

  lazy.assert.defined(value);
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  let startNode;
  if (typeof el != "undefined") {
    startNode = lazy.WebElement.fromUUID(el).toJSON();
  }

  let opts = {
    startNode,
    timeout: this.currentSession.timeouts.implicit,
    all: true,
  };

  return this.getActor().findElements(using, value, opts);
};

/**
 * Find elements within shadow root using the indicated search strategy.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.shadowRoot
 *     Shadow root reference ID.
 * @param {string} cmd.parameters.using
 *     Indicates which search method to use.
 * @param {string} cmd.parameters.value
 *     Value the client is looking for.
 *
 * @returns {Array<WebElement>}
 *     Return the array of found elements.
 *
 * @throws {DetachedShadowRootError}
 *     If shadow root represented by reference <var>id</var> is
 *     no longer attached to the DOM.
 * @throws {NoSuchShadowRoot}
 *     If shadow root represented by reference <var>shadowRoot</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.findElementsFromShadowRoot = async function (cmd) {
  const { shadowRoot, using, value } = cmd.parameters;

  if (!lazy.supportedStrategies.has(using)) {
    throw new lazy.error.InvalidSelectorError(
      `Strategy not supported: ${using}`
    );
  }

  lazy.assert.defined(value);
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  const opts = {
    all: true,
    startNode: lazy.ShadowRoot.fromUUID(shadowRoot).toJSON(),
    timeout: this.currentSession.timeouts.implicit,
  };

  return this.getActor().findElements(using, value, opts);
};

/**
 * Return the shadow root of an element in the document.
 *
 * @param {object} cmd
 * @param {id} cmd.parameters.id
 *     A web element id reference.
 * @returns {ShadowRoot}
 *     ShadowRoot of the element.
 *
 * @throws {InvalidArgumentError}
 *     If element <var>id</var> is not a string.
 * @throws {NoSuchElementError}
 *     If element represented by reference <var>id</var> is unknown.
 * @throws {NoSuchShadowRoot}
 *     Element does not have a shadow root attached.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {StaleElementReferenceError}
 *     If element represented by reference <var>id</var> has gone stale.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 * @throws {UnsupportedOperationError}
 *     Not available in chrome current context.
 */
GeckoDriver.prototype.getShadowRoot = async function (cmd) {
  // Bug 1743541: Add support for chrome scope.
  lazy.assert.content(this.context);
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  let id = lazy.assert.string(
    cmd.parameters.id,
    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
  );
  let webEl = lazy.WebElement.fromUUID(id).toJSON();

  return this.getActor().getShadowRoot(webEl);
};

/**
 * Return the active element in the document.
 *
 * @returns {WebReference}
 *     Active element of the current browsing context's document
 *     element, if the document element is non-null.
 *
 * @throws {NoSuchElementError}
 *     If the document does not have an active element, i.e. if
 *     its document element has been deleted.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 * @throws {UnsupportedOperationError}
 *     Not available in chrome context.
 */
GeckoDriver.prototype.getActiveElement = async function () {
  lazy.assert.content(this.context);
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  return this.getActor().getActiveElement();
};

/**
 * Send click event to element.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.id
 *     Reference ID to the element that will be clicked.
 *
 * @throws {InvalidArgumentError}
 *     If element <var>id</var> is not a string.
 * @throws {NoSuchElementError}
 *     If element represented by reference <var>id</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {StaleElementReferenceError}
 *     If element represented by reference <var>id</var> has gone stale.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.clickElement = async function (cmd) {
  const browsingContext = lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  let id = lazy.assert.string(
    cmd.parameters.id,
    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
  );
  let webEl = lazy.WebElement.fromUUID(id).toJSON();

  const actor = this.getActor();

  const loadEventExpected = lazy.navigate.isLoadEventExpected(
    this._getCurrentURL(),
    {
      browsingContext,
      target: await actor.getElementAttribute(webEl, "target"),
    }
  );

  await lazy.navigate.waitForNavigationCompleted(
    this,
    () => actor.clickElement(webEl, this.currentSession.capabilities),
    {
      loadEventExpected,
      // The click might trigger a navigation, so don't count on it.
      requireBeforeUnload: false,
    }
  );
};

/**
 * Get a given attribute of an element.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.id
 *     Web element reference ID to the element that will be inspected.
 * @param {string} cmd.parameters.name
 *     Name of the attribute which value to retrieve.
 *
 * @returns {string}
 *     Value of the attribute.
 *
 * @throws {InvalidArgumentError}
 *     If <var>id</var> or <var>name</var> are not strings.
 * @throws {NoSuchElementError}
 *     If element represented by reference <var>id</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {StaleElementReferenceError}
 *     If element represented by reference <var>id</var> has gone stale.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getElementAttribute = async function (cmd) {
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  const id = lazy.assert.string(
    cmd.parameters.id,
    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
  );
  const name = lazy.assert.string(
    cmd.parameters.name,
    lazy.pprint`Expected "name" to be a string, got ${cmd.parameters.name}`
  );
  const webEl = lazy.WebElement.fromUUID(id).toJSON();

  return this.getActor().getElementAttribute(webEl, name);
};

/**
 * Returns the value of a property associated with given element.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.id
 *     Web element reference ID to the element that will be inspected.
 * @param {string} cmd.parameters.name
 *     Name of the property which value to retrieve.
 *
 * @returns {string}
 *     Value of the property.
 *
 * @throws {InvalidArgumentError}
 *     If <var>id</var> or <var>name</var> are not strings.
 * @throws {NoSuchElementError}
 *     If element represented by reference <var>id</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {StaleElementReferenceError}
 *     If element represented by reference <var>id</var> has gone stale.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getElementProperty = async function (cmd) {
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  const id = lazy.assert.string(
    cmd.parameters.id,
    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
  );
  const name = lazy.assert.string(
    cmd.parameters.name,
    lazy.pprint`Expected "name" to be a string, got ${cmd.parameters.name}`
  );
  const webEl = lazy.WebElement.fromUUID(id).toJSON();

  return this.getActor().getElementProperty(webEl, name);
};

/**
 * Get the text of an element, if any.  Includes the text of all child
 * elements.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.id
 *     Reference ID to the element that will be inspected.
 *
 * @returns {string}
 *     Element's text "as rendered".
 *
 * @throws {InvalidArgumentError}
 *     If <var>id</var> is not a string.
 * @throws {NoSuchElementError}
 *     If element represented by reference <var>id</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {StaleElementReferenceError}
 *     If element represented by reference <var>id</var> has gone stale.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getElementText = async function (cmd) {
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  let id = lazy.assert.string(
    cmd.parameters.id,
    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
  );
  let webEl = lazy.WebElement.fromUUID(id).toJSON();

  return this.getActor().getElementText(webEl);
};

/**
 * Get the tag name of the element.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.id
 *     Reference ID to the element that will be inspected.
 *
 * @returns {string}
 *     Local tag name of element.
 *
 * @throws {InvalidArgumentError}
 *     If <var>id</var> is not a string.
 * @throws {NoSuchElementError}
 *     If element represented by reference <var>id</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {StaleElementReferenceError}
 *     If element represented by reference <var>id</var> has gone stale.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getElementTagName = async function (cmd) {
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  let id = lazy.assert.string(
    cmd.parameters.id,
    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
  );
  let webEl = lazy.WebElement.fromUUID(id).toJSON();

  return this.getActor().getElementTagName(webEl);
};

/**
 * Check if element is displayed.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.id
 *     Reference ID to the element that will be inspected.
 *
 * @returns {boolean}
 *     True if displayed, false otherwise.
 *
 * @throws {InvalidArgumentError}
 *     If <var>id</var> is not a string.
 * @throws {NoSuchElementError}
 *     If element represented by reference <var>id</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.isElementDisplayed = async function (cmd) {
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  let id = lazy.assert.string(
    cmd.parameters.id,
    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
  );
  let webEl = lazy.WebElement.fromUUID(id).toJSON();

  return this.getActor().isElementDisplayed(
    webEl,
    this.currentSession.capabilities
  );
};

/**
 * Return the property of the computed style of an element.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.id
 *     Reference ID to the element that will be checked.
 * @param {string} cmd.parameters.propertyName
 *     CSS rule that is being requested.
 *
 * @returns {string}
 *     Value of |propertyName|.
 *
 * @throws {InvalidArgumentError}
 *     If <var>id</var> or <var>propertyName</var> are not strings.
 * @throws {NoSuchElementError}
 *     If element represented by reference <var>id</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {StaleElementReferenceError}
 *     If element represented by reference <var>id</var> has gone stale.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getElementValueOfCssProperty = async function (cmd) {
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  let id = lazy.assert.string(
    cmd.parameters.id,
    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
  );
  let prop = lazy.assert.string(
    cmd.parameters.propertyName,
    lazy.pprint`Expected "propertyName" to be a string, got ${cmd.parameters.propertyName}`
  );
  let webEl = lazy.WebElement.fromUUID(id).toJSON();

  return this.getActor().getElementValueOfCssProperty(webEl, prop);
};

/**
 * Check if element is enabled.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.id
 *     Reference ID to the element that will be checked.
 *
 * @returns {boolean}
 *     True if enabled, false if disabled.
 *
 * @throws {InvalidArgumentError}
 *     If <var>id</var> is not a string.
 * @throws {NoSuchElementError}
 *     If element represented by reference <var>id</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {StaleElementReferenceError}
 *     If element represented by reference <var>id</var> has gone stale.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.isElementEnabled = async function (cmd) {
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  let id = lazy.assert.string(
    cmd.parameters.id,
    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
  );
  let webEl = lazy.WebElement.fromUUID(id).toJSON();

  return this.getActor().isElementEnabled(
    webEl,
    this.currentSession.capabilities
  );
};

/**
 * Check if element is selected.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.id
 *     Reference ID to the element that will be checked.
 *
 * @returns {boolean}
 *     True if selected, false if unselected.
 *
 * @throws {InvalidArgumentError}
 *     If <var>id</var> is not a string.
 * @throws {NoSuchElementError}
 *     If element represented by reference <var>id</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.isElementSelected = async function (cmd) {
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  let id = lazy.assert.string(
    cmd.parameters.id,
    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
  );
  let webEl = lazy.WebElement.fromUUID(id).toJSON();

  return this.getActor().isElementSelected(
    webEl,
    this.currentSession.capabilities
  );
};

/**
 * @throws {InvalidArgumentError}
 *     If <var>id</var> is not a string.
 * @throws {NoSuchElementError}
 *     If element represented by reference <var>id</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {StaleElementReferenceError}
 *     If element represented by reference <var>id</var> has gone stale.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.getElementRect = async function (cmd) {
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  let id = lazy.assert.string(
    cmd.parameters.id,
    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
  );
  let webEl = lazy.WebElement.fromUUID(id).toJSON();

  return this.getActor().getElementRect(webEl);
};

/**
 * Send key presses to element after focusing on it.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.id
 *     Reference ID to the element that will be checked.
 * @param {string} cmd.parameters.text
 *     Value to send to the element.
 *
 * @throws {InvalidArgumentError}
 *     If <var>id</var> or <var>text</var> are not strings.
 * @throws {NoSuchElementError}
 *     If element represented by reference <var>id</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {StaleElementReferenceError}
 *     If element represented by reference <var>id</var> has gone stale.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.sendKeysToElement = async function (cmd) {
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  let id = lazy.assert.string(
    cmd.parameters.id,
    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
  );
  let text = lazy.assert.string(
    cmd.parameters.text,
    lazy.pprint`Expected "text" to be a string, got ${cmd.parameters.text}`
  );
  let webEl = lazy.WebElement.fromUUID(id).toJSON();

  return this.getActor().sendKeysToElement(
    webEl,
    text,
    this.currentSession.capabilities
  );
};

/**
 * Clear the text of an element.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.id
 *     Reference ID to the element that will be cleared.
 *
 * @throws {InvalidArgumentError}
 *     If <var>id</var> is not a string.
 * @throws {NoSuchElementError}
 *     If element represented by reference <var>id</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {StaleElementReferenceError}
 *     If element represented by reference <var>id</var> has gone stale.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.clearElement = async function (cmd) {
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  let id = lazy.assert.string(
    cmd.parameters.id,
    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
  );
  let webEl = lazy.WebElement.fromUUID(id).toJSON();

  await this.getActor().clearElement(webEl);
};

/**
 * Add a single cookie to the cookie store associated with the active
 * document's address.
 *
 * @param {object} cmd
 * @param {Map.<string, (string|number|boolean)>} cmd.parameters.cookie
 *     Cookie object.
 *
 * @throws {InvalidCookieDomainError}
 *     If <var>cookie</var> is for a different domain than the active
 *     document's host.
 * @throws {NoSuchWindowError}
 *     Bbrowsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 */
GeckoDriver.prototype.addCookie = async function (cmd) {
  lazy.assert.content(this.context);
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  let { protocol, hostname } = this._getCurrentURL({ top: false });

  const networkSchemes = ["http:", "https:"];
  if (!networkSchemes.includes(protocol)) {
    throw new lazy.error.InvalidCookieDomainError("Document is cookie-averse");
  }

  let newCookie = lazy.cookie.fromJSON(cmd.parameters.cookie);

  lazy.cookie.add(newCookie, { restrictToHost: hostname, protocol });
};

/**
 * Get all the cookies for the current domain.
 *
 * This is the equivalent of calling <code>document.cookie</code> and
 * parsing the result.
 *
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 */
GeckoDriver.prototype.getCookies = async function () {
  lazy.assert.content(this.context);
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  let { hostname, pathname } = this._getCurrentURL({ top: false });
  return [...lazy.cookie.iter(hostname, pathname)];
};

/**
 * Delete all cookies that are visible to a document.
 *
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 */
GeckoDriver.prototype.deleteAllCookies = async function () {
  lazy.assert.content(this.context);
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  let { hostname, pathname } = this._getCurrentURL({ top: false });
  for (let toDelete of lazy.cookie.iter(hostname, pathname)) {
    lazy.cookie.remove(toDelete);
  }
};

/**
 * Delete a cookie by name.
 *
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 * @throws {UnsupportedOperationError}
 *     Not available in current context.
 */
GeckoDriver.prototype.deleteCookie = async function (cmd) {
  lazy.assert.content(this.context);
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  let { hostname, pathname } = this._getCurrentURL({ top: false });
  let name = lazy.assert.string(
    cmd.parameters.name,
    lazy.pprint`Expected "name" to be a string, got ${cmd.parameters.name}`
  );
  for (let c of lazy.cookie.iter(hostname, pathname)) {
    if (c.name === name) {
      lazy.cookie.remove(c);
    }
  }
};

/**
 * Open a new top-level browsing context.
 *
 * @param {object} cmd
 * @param {string=} cmd.parameters.type
 *     Optional type of the new top-level browsing context. Can be one of
 *     `tab` or `window`. Defaults to `tab`.
 * @param {boolean=} cmd.parameters.focus
 *     Optional flag if the new top-level browsing context should be opened
 *     in foreground (focused) or background (not focused). Defaults to false.
 * @param {boolean=} cmd.parameters.private
 *     Optional flag, which gets only evaluated for type `window`. True if the
 *     new top-level browsing context should be a private window.
 *     Defaults to false.
 *
 * @returns {Record<string, string>}
 *     Handle and type of the new browsing context.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.newWindow = async function (cmd) {
  lazy.assert.open(this.getBrowsingContext({ top: true }));
  await this._handleUserPrompts();

  let focus = false;
  if (typeof cmd.parameters.focus != "undefined") {
    focus = lazy.assert.boolean(
      cmd.parameters.focus,
      lazy.pprint`Expected "focus" to be a boolean, got ${cmd.parameters.focus}`
    );
  }

  let isPrivate = false;
  if (typeof cmd.parameters.private != "undefined") {
    isPrivate = lazy.assert.boolean(
      cmd.parameters.private,
      lazy.pprint`Expected "private" to be a boolean, got ${cmd.parameters.private}`
    );
  }

  let type;
  if (typeof cmd.parameters.type != "undefined") {
    type = lazy.assert.string(
      cmd.parameters.type,
      lazy.pprint`Expected "type" to be a string, got ${cmd.parameters.type}`
    );
  }

  // If an invalid or no type has been specified default to a tab.
  // On Android always use a new tab instead because the application has a
  // single window only.
  if (
    typeof type == "undefined" ||
    !["tab", "window"].includes(type) ||
    lazy.AppInfo.isAndroid
  ) {
    type = "tab";
  }

  let contentBrowser;

  switch (type) {
    case "window": {
      let win = await this.curBrowser.openBrowserWindow(focus, isPrivate);
      contentBrowser = lazy.TabManager.getTabBrowser(win).selectedBrowser;
      break;
    }
    default: {
      // To not fail if a new type gets added in the future, make opening
      // a new tab the default action.
      let tab = await this.curBrowser.openTab(focus);
      contentBrowser = lazy.TabManager.getBrowserForTab(tab);
    }
  }

  // Actors need the new window to be loaded to safely execute queries.
  // Wait until the initial page load has been finished.
  await lazy.waitForInitialNavigationCompleted(
    contentBrowser.browsingContext.webProgress,
    {
      unloadTimeout: 5000,
    }
  );

  const id = lazy.TabManager.getIdForBrowser(contentBrowser);

  return { handle: id.toString(), type };
};

/**
 * Close the currently selected tab/window.
 *
 * With multiple open tabs present the currently selected tab will
 * be closed.  Otherwise the window itself will be closed. If it is the
 * last window currently open, the window will not be closed to prevent
 * a shutdown of the application. Instead the returned list of window
 * handles is empty.
 *
 * @returns {Array.<string>}
 *     Unique window handles of remaining windows.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 */
GeckoDriver.prototype.close = async function () {
  lazy.assert.open(
    this.getBrowsingContext({ context: lazy.Context.Content, top: true })
  );
  await this._handleUserPrompts();

  // If there is only one window left, do not close unless windowless mode is
  // enabled. Instead return a faked empty array of window handles.
  // This will instruct geckodriver to terminate the application.
  if (
    lazy.TabManager.getTabCount() === 1 &&
    !this.currentSession.capabilities.get("moz:windowless")
  ) {
    return [];
  }

  await this.curBrowser.closeTab();
  this.currentSession.contentBrowsingContext = null;

  return lazy.TabManager.allBrowserUniqueIds.map(String);
};

/**
 * Close the currently selected chrome window.
 *
 * If it is the last window currently open, the chrome window will not be
 * closed to prevent a shutdown of the application. Instead the returned
 * list of chrome window handles is empty.
 *
 * @returns {Array.<string>}
 *     Unique chrome window handles of remaining chrome windows.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 */
GeckoDriver.prototype.closeChromeWindow = async function () {
  lazy.assert.desktop();
  lazy.assert.open(
    this.getBrowsingContext({ context: lazy.Context.Chrome, top: true })
  );

  let nwins = 0;

  // eslint-disable-next-line
  for (let _ of lazy.windowManager.windows) {
    nwins++;
  }

  // If there is only one window left, do not close unless windowless mode is
  // enabled. Instead return a faked empty array of window handles.
  // This will instruct geckodriver to terminate the application.
  if (nwins == 1 && !this.currentSession.capabilities.get("moz:windowless")) {
    return [];
  }

  await this.curBrowser.closeWindow();
  this.currentSession.chromeBrowsingContext = null;
  this.currentSession.contentBrowsingContext = null;

  return lazy.windowManager.chromeWindowHandles.map(String);
};

/** Delete Marionette session. */
GeckoDriver.prototype.deleteSession = function () {
  if (!this.currentSession) {
    return;
  }

  for (let win of lazy.windowManager.windows) {
    this.stopObservingWindow(win);
  }

  // reset to the top-most frame
  this.mainFrame = null;

  if (!this._isShuttingDown && this.promptListener) {
    // Do not stop the prompt listener when quitting the browser to
    // allow us to also accept beforeunload prompts during shutdown.
    this.promptListener.stopListening();
    this.promptListener = null;
  }

  try {
    Services.obs.removeObserver(this, TOPIC_BROWSER_READY);
  } catch (e) {
    lazy.logger.debug(`Failed to remove observer "${TOPIC_BROWSER_READY}"`);
  }

  // Always unregister actors after all other observers
  // and listeners have been removed.
  lazy.unregisterCommandsActor();
  // MarionetteEvents actors are only disabled to avoid IPC errors if there are
  // in flight events being forwarded from the content process to the parent
  // process.
  lazy.disableEventsActor();

  if (lazy.RemoteAgent.webDriverBiDi) {
    lazy.RemoteAgent.webDriverBiDi.deleteSession();
  } else {
    this.currentSession.destroy();
    this._currentSession = null;
  }
};

/**
 * Takes a screenshot of a web element, current frame, or viewport.
 *
 * The screen capture is returned as a lossless PNG image encoded as
 * a base 64 string.
 *
 * If called in the content context, the |id| argument is not null and
 * refers to a present and visible web element's ID, the capture area will
 * be limited to the bounding box of that element.  Otherwise, the capture
 * area will be the bounding box of the current frame.
 *
 * If called in the chrome context, the screenshot will always represent
 * the entire viewport.
 *
 * @param {object} cmd
 * @param {string=} cmd.parameters.id
 *     Optional web element reference to take a screenshot of.
 *     If undefined, a screenshot will be taken of the document element.
 * @param {boolean=} cmd.parameters.full
 *     True to take a screenshot of the entire document element. Is only
 *     considered if <var>id</var> is not defined. Defaults to true.
 * @param {boolean=} cmd.parameters.hash
 *     True if the user requests a hash of the image data. Defaults to false.
 * @param {boolean=} cmd.parameters.scroll
 *     Scroll to element if |id| is provided. Defaults to true.
 *
 * @returns {string}
 *     If <var>hash</var> is false, PNG image encoded as Base64 encoded
 *     string.  If <var>hash</var> is true, hex digest of the SHA-256
 *     hash of the Base64 encoded string.
 *
 * @throws {NoSuchElementError}
 *     If element represented by reference <var>id</var> is unknown.
 * @throws {NoSuchWindowError}
 *     Browsing context has been discarded.
 * @throws {StaleElementReferenceError}
 *     If element represented by reference <var>id</var> has gone stale.
 */
GeckoDriver.prototype.takeScreenshot = async function (cmd) {
  lazy.assert.open(this.getBrowsingContext({ top: true }));
  await this._handleUserPrompts();

  let { id, full, hash, scroll } = cmd.parameters;
  let format = hash ? lazy.capture.Format.Hash : lazy.capture.Format.Base64;

  full = typeof full == "undefined" ? true : full;
  scroll = typeof scroll == "undefined" ? true : scroll;

  let webEl = id ? lazy.WebElement.fromUUID(id).toJSON() : null;

  // Only consider full screenshot if no element has been specified
  full = webEl ? false : full;

  return this.getActor().takeScreenshot(webEl, format, full, scroll);
};

/**
 * Get the current browser orientation.
 *
 * Will return one of the valid primary orientation values
 * portrait-primary, landscape-primary, portrait-secondary, or
 * landscape-secondary.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 */
GeckoDriver.prototype.getScreenOrientation = function () {
  lazy.assert.mobile();
  lazy.assert.open(this.getBrowsingContext({ top: true }));

  const win = this.getCurrentWindow();

  return win.screen.orientation.type;
};

/**
 * Set the current browser orientation.
 *
 * The supplied orientation should be given as one of the valid
 * orientation values.  If the orientation is unknown, an error will
 * be raised.
 *
 * Valid orientations are "portrait" and "landscape", which fall
 * back to "portrait-primary" and "landscape-primary" respectively,
 * and "portrait-secondary" as well as "landscape-secondary".
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 */
GeckoDriver.prototype.setScreenOrientation = async function (cmd) {
  lazy.assert.mobile();
  lazy.assert.open(this.getBrowsingContext({ top: true }));

  const ors = [
    "portrait",
    "landscape",
    "portrait-primary",
    "landscape-primary",
    "portrait-secondary",
    "landscape-secondary",
  ];

  let or = String(cmd.parameters.orientation);
  lazy.assert.string(or, lazy.pprint`Expected "or" to be a string, got ${or}`);
  let mozOr = or.toLowerCase();
  if (!ors.includes(mozOr)) {
    throw new lazy.error.InvalidArgumentError(
      `Unknown screen orientation: ${or}`
    );
  }

  const win = this.getCurrentWindow();

  try {
    await win.screen.orientation.lock(mozOr);
  } catch (e) {
    throw new lazy.error.WebDriverError(
      `Unable to set screen orientation: ${or}`
    );
  }
};

/**
 * Synchronously minimizes the user agent window as if the user pressed
 * the minimize button.
 *
 * No action is taken if the window is already minimized.
 *
 * Not supported on Fennec.
 *
 * @returns {Record<string, number>}
 *     Window rect and window state.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 * @throws {UnsupportedOperationError}
 *     Not available for current application.
 */
GeckoDriver.prototype.minimizeWindow = async function () {
  lazy.assert.desktop();
  lazy.assert.open(this.getBrowsingContext({ top: true }));
  await this._handleUserPrompts();

  const win = this.getCurrentWindow();
  switch (lazy.WindowState.from(win.windowState)) {
    case lazy.WindowState.Fullscreen:
      await exitFullscreen(win);
      break;

    case lazy.WindowState.Maximized:
      await restoreWindow(win);
      break;
  }

  if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Minimized) {
    let cb;
    // Use a timed promise to abort if no window manager is present
    await new lazy.TimedPromise(
      resolve => {
        cb = new lazy.DebounceCallback(resolve);
        win.addEventListener("sizemodechange", cb);
        win.minimize();
      },
      { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
    );
    win.removeEventListener("sizemodechange", cb);
    await new lazy.IdlePromise(win);
  }

  return this.curBrowser.rect;
};

/**
 * Synchronously maximizes the user agent window as if the user pressed
 * the maximize button.
 *
 * No action is taken if the window is already maximized.
 *
 * Not supported on Fennec.
 *
 * @returns {Record<string, number>}
 *     Window rect.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 * @throws {UnsupportedOperationError}
 *     Not available for current application.
 */
GeckoDriver.prototype.maximizeWindow = async function () {
  lazy.assert.desktop();
  lazy.assert.open(this.getBrowsingContext({ top: true }));
  await this._handleUserPrompts();

  const win = this.getCurrentWindow();
  switch (lazy.WindowState.from(win.windowState)) {
    case lazy.WindowState.Fullscreen:
      await exitFullscreen(win);
      break;

    case lazy.WindowState.Minimized:
      await restoreWindow(win);
      break;
  }

  if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Maximized) {
    let cb;
    // Use a timed promise to abort if no window manager is present
    await new lazy.TimedPromise(
      resolve => {
        cb = new lazy.DebounceCallback(resolve);
        win.addEventListener("sizemodechange", cb);
        win.maximize();
      },
      { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
    );
    win.removeEventListener("sizemodechange", cb);
    await new lazy.IdlePromise(win);
  }

  return this.curBrowser.rect;
};

/**
 * Synchronously sets the user agent window to full screen as if the user
 * had done "View > Enter Full Screen".
 *
 * No action is taken if the window is already in full screen mode.
 *
 * Not supported on Fennec.
 *
 * @returns {Map.<string, number>}
 *     Window rect.
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 * @throws {UnsupportedOperationError}
 *     Not available for current application.
 */
GeckoDriver.prototype.fullscreenWindow = async function () {
  lazy.assert.desktop();
  lazy.assert.open(this.getBrowsingContext({ top: true }));
  await this._handleUserPrompts();

  const win = this.getCurrentWindow();
  switch (lazy.WindowState.from(win.windowState)) {
    case lazy.WindowState.Maximized:
    case lazy.WindowState.Minimized:
      await restoreWindow(win);
      break;
  }

  if (lazy.WindowState.from(win.windowState) != lazy.WindowState.Fullscreen) {
    let cb;
    // Use a timed promise to abort if no window manager is present
    await new lazy.TimedPromise(
      resolve => {
        cb = new lazy.DebounceCallback(resolve);
        win.addEventListener("sizemodechange", cb);
        win.fullScreen = true;
      },
      { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
    );
    win.removeEventListener("sizemodechange", cb);
  }
  await new lazy.IdlePromise(win);

  return this.curBrowser.rect;
};

/**
 * Dismisses a currently displayed modal dialogs, or returns no such alert if
 * no modal is displayed.
 *
 * @throws {NoSuchAlertError}
 *     If there is no current user prompt.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 */
GeckoDriver.prototype.dismissDialog = async function () {
  lazy.assert.open(this.getBrowsingContext({ top: true }));
  this._checkIfAlertIsPresent();

  const dialogClosed = this.promptListener.dialogClosed();
  this.dialog.dismiss();
  await dialogClosed;

  const win = this.getCurrentWindow();
  await new lazy.IdlePromise(win);
};

/**
 * Accepts a currently displayed dialog modal, or returns no such alert if
 * no modal is displayed.
 *
 * @throws {NoSuchAlertError}
 *     If there is no current user prompt.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 */
GeckoDriver.prototype.acceptDialog = async function () {
  lazy.assert.open(this.getBrowsingContext({ top: true }));
  this._checkIfAlertIsPresent();

  const dialogClosed = this.promptListener.dialogClosed();
  this.dialog.accept();
  await dialogClosed;

  const win = this.getCurrentWindow();
  await new lazy.IdlePromise(win);
};

/**
 * Returns the message shown in a currently displayed modal, or returns
 * a no such alert error if no modal is currently displayed.
 *
 * @throws {NoSuchAlertError}
 *     If there is no current user prompt.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 */
GeckoDriver.prototype.getTextFromDialog = async function () {
  lazy.assert.open(this.getBrowsingContext({ top: true }));
  this._checkIfAlertIsPresent();
  const text = await this.dialog.getText();
  return text;
};

/**
 * Set the user prompt's value field.
 *
 * Sends keys to the input field of a currently displayed modal, or
 * returns a no such alert error if no modal is currently displayed. If
 * a modal dialog is currently displayed but has no means for text input,
 * an element not visible error is returned.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.text
 *     Input to the user prompt's value field.
 *
 * @throws {ElementNotInteractableError}
 *     If the current user prompt is an alert or confirm.
 * @throws {NoSuchAlertError}
 *     If there is no current user prompt.
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnsupportedOperationError}
 *     If the current user prompt is something other than an alert,
 *     confirm, or a prompt.
 */
GeckoDriver.prototype.sendKeysToDialog = async function (cmd) {
  lazy.assert.open(this.getBrowsingContext({ top: true }));
  this._checkIfAlertIsPresent();

  let text = lazy.assert.string(
    cmd.parameters.text,
    lazy.pprint`Expected "text" to be a string, got ${cmd.parameters.text}`
  );
  let promptType = this.dialog.args.promptType;

  switch (promptType) {
    case "alert":
    case "confirm":
      throw new lazy.error.ElementNotInteractableError(
        `User prompt of type ${promptType} is not interactable`
      );
    case "prompt":
      break;
    default:
      await this.dismissDialog();
      throw new lazy.error.UnsupportedOperationError(
        `User prompt of type ${promptType} is not supported`
      );
  }
  this.dialog.text = text;
};

GeckoDriver.prototype._checkIfAlertIsPresent = function () {
  if (!this.dialog || !this.dialog.isOpen) {
    throw new lazy.error.NoSuchAlertError();
  }
};

GeckoDriver.prototype._handleUserPrompts = async function () {
  if (!this.dialog || !this.dialog.isOpen) {
    return;
  }

  const promptType = this.dialog.promptType;
  const textContent = await this.dialog.getText();

  if (promptType === "beforeunload" && !this.currentSession.bidi) {
    // In an HTTP-only session, this prompt will be automatically accepted.
    // Since this occurs asynchronously, we need to wait until it closes
    // to prevent race conditions, particularly in slow builds.
    await lazy.PollPromise((resolve, reject) => {
      this.dialog?.isOpen ? reject() : resolve();
    });
    return;
  }

  let type = lazy.PromptTypes.Default;
  switch (promptType) {
    case "alert":
      type = lazy.PromptTypes.Alert;
      break;
    case "beforeunload":
      type = lazy.PromptTypes.BeforeUnload;
      break;
    case "confirm":
      type = lazy.PromptTypes.Confirm;
      break;
    case "prompt":
      type = lazy.PromptTypes.Prompt;
      break;
  }

  const userPromptHandler = this.currentSession.userPromptHandler;
  const handlerConfig = userPromptHandler.getPromptHandler(type);

  switch (handlerConfig.handler) {
    case lazy.PromptHandlers.Accept:
      await this.acceptDialog();
      break;
    case lazy.PromptHandlers.Dismiss:
      await this.dismissDialog();
      break;
    case lazy.PromptHandlers.Ignore:
      break;
  }

  if (handlerConfig.notify) {
    throw new lazy.error.UnexpectedAlertOpenError(
      `Unexpected ${promptType} dialog detected. Performed handler "${handlerConfig.handler}"`,
      {
        text: textContent,
      }
    );
  }
};

/**
 * Enables or disables accepting new socket connections.
 *
 * By calling this method with `false` the server will not accept any
 * further connections, but existing connections will not be forcible
 * closed. Use `true` to re-enable accepting connections.
 *
 * Please note that when closing the connection via the client you can
 * end-up in a non-recoverable state if it hasn't been enabled before.
 *
 * This method is used for custom in application shutdowns via
 * marionette.quit() or marionette.restart(), like File -> Quit.
 *
 * @param {object} cmd
 * @param {boolean} cmd.parameters.value
 *     True if the server should accept new socket connections.
 */
GeckoDriver.prototype.acceptConnections = async function (cmd) {
  lazy.assert.boolean(
    cmd.parameters.value,
    lazy.pprint`Expected "value" to be a boolean, got ${cmd.parameters.value}`
  );
  await this._server.setAcceptConnections(cmd.parameters.value);
};

/**
 * Quits the application with the provided flags.
 *
 * Marionette will stop accepting new connections before ending the
 * current session, and finally attempting to quit the application.
 *
 * Optional {@link nsIAppStartup} flags may be provided as
 * an array of masks, and these will be combined by ORing
 * them with a bitmask.  The available masks are defined in
 * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIAppStartup.
 *
 * Crucially, only one of the *Quit flags can be specified. The |eRestart|
 * flag may be bit-wise combined with one of the *Quit flags to cause
 * the application to restart after it quits.
 *
 * @param {object} cmd
 * @param {Array.<string>=} cmd.parameters.flags
 *     Constant name of masks to pass to |Services.startup.quit|.
 *     If empty or undefined, |nsIAppStartup.eAttemptQuit| is used.
 * @param {boolean=} cmd.parameters.safeMode
 *     Optional flag to indicate that the application has to
 *     be restarted in safe mode.
 *
 * @returns {Record<string,boolean>}
 *     Dictionary containing information that explains the shutdown reason.
 *     The value for `cause` contains the shutdown kind like "shutdown" or
 *     "restart", while `forced` will indicate if it was a normal or forced
 *     shutdown of the application. "in_app" is always set to indicate that
 *     it is a shutdown triggered from within the application.
 *
 * @throws {InvalidArgumentError}
 *     If <var>flags</var> contains unknown or incompatible flags,
 *     for example multiple Quit flags.
 */
GeckoDriver.prototype.quit = async function (cmd) {
  const { flags = [], safeMode = false } = cmd.parameters;

  lazy.assert.array(
    flags,
    lazy.pprint`Expected "flags" to be an array, got ${flags}`
  );
  lazy.assert.boolean(
    safeMode,
    lazy.pprint`Expected "safeMode" to be a boolean, got ${safeMode}`
  );

  if (safeMode && !flags.includes("eRestart")) {
    throw new lazy.error.InvalidArgumentError(
      `"safeMode" only works with restart flag`
    );
  }

  // Register handler to run Marionette specific shutdown code.
  Services.obs.addObserver(this, TOPIC_QUIT_APPLICATION_REQUESTED);

  let quitApplicationResponse;
  try {
    this._isShuttingDown = true;
    quitApplicationResponse = await lazy.quit(
      flags,
      safeMode,
      this.currentSession.capabilities.get("moz:windowless")
    );
  } catch (e) {
    this._isShuttingDown = false;
    if (e instanceof TypeError) {
      throw new lazy.error.InvalidArgumentError(e.message);
    }
    throw new lazy.error.UnsupportedOperationError(e.message);
  } finally {
    Services.obs.removeObserver(this, TOPIC_QUIT_APPLICATION_REQUESTED);
  }

  return quitApplicationResponse;
};

GeckoDriver.prototype.installAddon = function (cmd) {
  lazy.assert.desktop();

  let path = cmd.parameters.path;
  let temp = cmd.parameters.temporary || false;
  if (
    typeof path == "undefined" ||
    typeof path != "string" ||
    typeof temp != "boolean"
  ) {
    throw new lazy.error.InvalidArgumentError();
  }

  return lazy.Addon.install(path, temp);
};

GeckoDriver.prototype.uninstallAddon = function (cmd) {
  lazy.assert.desktop();

  let id = cmd.parameters.id;
  if (typeof id == "undefined" || typeof id != "string") {
    throw new lazy.error.InvalidArgumentError();
  }

  return lazy.Addon.uninstall(id);
};

/**
 * Retrieve the localized string for the specified entity id.
 *
 * Example:
 *     localizeEntity(["chrome://branding/locale/brand.dtd"], "brandShortName")
 *
 * @param {object} cmd
 * @param {Array.<string>} cmd.parameters.urls
 *     Array of .dtd URLs.
 * @param {string} cmd.parameters.id
 *     The ID of the entity to retrieve the localized string for.
 *
 * @returns {string}
 *     The localized string for the requested entity.
 */
GeckoDriver.prototype.localizeEntity = function (cmd) {
  let { urls, id } = cmd.parameters;

  if (!Array.isArray(urls)) {
    throw new lazy.error.InvalidArgumentError(
      "Value of `urls` should be of type 'Array'"
    );
  }
  if (typeof id != "string") {
    throw new lazy.error.InvalidArgumentError(
      "Value of `id` should be of type 'string'"
    );
  }

  return lazy.l10n.localizeEntity(urls, id);
};

/**
 * Retrieve the localized string for the specified property id.
 *
 * Example:
 *
 *     localizeProperty(
 *         ["chrome://global/locale/findbar.properties"], "FastFind");
 *
 * @param {object} cmd
 * @param {Array.<string>} cmd.parameters.urls
 *     Array of .properties URLs.
 * @param {string} cmd.parameters.id
 *     The ID of the property to retrieve the localized string for.
 *
 * @returns {string}
 *     The localized string for the requested property.
 */
GeckoDriver.prototype.localizeProperty = function (cmd) {
  let { urls, id } = cmd.parameters;

  if (!Array.isArray(urls)) {
    throw new lazy.error.InvalidArgumentError(
      "Value of `urls` should be of type 'Array'"
    );
  }
  if (typeof id != "string") {
    throw new lazy.error.InvalidArgumentError(
      "Value of `id` should be of type 'string'"
    );
  }

  return lazy.l10n.localizeProperty(urls, id);
};

/**
 * Initialize the reftest mode
 */
GeckoDriver.prototype.setupReftest = async function (cmd) {
  if (this._reftest) {
    throw new lazy.error.UnsupportedOperationError(
      "Called reftest:setup with a reftest session already active"
    );
  }

  let {
    urlCount = {},
    screenshot = "unexpected",
    isPrint = false,
  } = cmd.parameters;
  if (!["always", "fail", "unexpected"].includes(screenshot)) {
    throw new lazy.error.InvalidArgumentError(
      "Value of `screenshot` should be 'always', 'fail' or 'unexpected'"
    );
  }

  this._reftest = new lazy.reftest.Runner(this);
  this._reftest.setup(urlCount, screenshot, isPrint);
};

/** Run a reftest. */
GeckoDriver.prototype.runReftest = function (cmd) {
  let { test, references, expected, timeout, width, height, pageRanges } =
    cmd.parameters;

  if (!this._reftest) {
    throw new lazy.error.UnsupportedOperationError(
      "Called reftest:run before reftest:start"
    );
  }

  lazy.assert.string(
    test,
    lazy.pprint`Expected "test" to be a string, got ${test}`
  );
  lazy.assert.string(
    expected,
    lazy.pprint`Expected "expected" to be a string, got ${expected}`
  );
  lazy.assert.array(
    references,
    lazy.pprint`Expected "references" to be an array, got ${references}`
  );

  return this._reftest.run(
    test,
    references,
    expected,
    timeout,
    pageRanges,
    width,
    height
  );
};

/**
 * End a reftest run.
 *
 * Closes the reftest window (without changing the current window handle),
 * and removes cached canvases.
 */
GeckoDriver.prototype.teardownReftest = function () {
  if (!this._reftest) {
    throw new lazy.error.UnsupportedOperationError(
      "Called reftest:teardown before reftest:start"
    );
  }

  this._reftest.teardown();
  this._reftest = null;
};

/**
 * Print page as PDF.
 *
 * @param {object} cmd
 * @param {boolean=} cmd.parameters.background
 *     Whether or not to print background colors and images.
 *     Defaults to false, which prints without background graphics.
 * @param {number=} cmd.parameters.margin.bottom
 *     Bottom margin in cm. Defaults to 1cm (~0.4 inches).
 * @param {number=} cmd.parameters.margin.left
 *     Left margin in cm. Defaults to 1cm (~0.4 inches).
 * @param {number=} cmd.parameters.margin.right
 *     Right margin in cm. Defaults to 1cm (~0.4 inches).
 * @param {number=} cmd.parameters.margin.top
 *     Top margin in cm. Defaults to 1cm (~0.4 inches).
 * @param {('landscape'|'portrait')=} cmd.parameters.options.orientation
 *     Paper orientation. Defaults to 'portrait'.
 * @param {Array.<string|number>=} cmd.parameters.pageRanges
 *     Paper ranges to print, e.g., ['1-5', 8, '11-13'].
 *     Defaults to the empty array, which means print all pages.
 * @param {number=} cmd.parameters.page.height
 *     Paper height in cm. Defaults to US letter height (27.94cm / 11 inches)
 * @param {number=} cmd.parameters.page.width
 *     Paper width in cm. Defaults to US letter width (21.59cm / 8.5 inches)
 * @param {number=} cmd.parameters.scale
 *     Scale of the webpage rendering. Defaults to 1.0.
 * @param {boolean=} cmd.parameters.shrinkToFit
 *     Whether or not to override page size as defined by CSS.
 *     Defaults to true, in which case the content will be scaled
 *     to fit the paper size.
 *
 * @returns {string}
 *     Base64 encoded PDF representing printed document
 *
 * @throws {NoSuchWindowError}
 *     Top-level browsing context has been discarded.
 * @throws {UnexpectedAlertOpenError}
 *     A modal dialog is open, blocking this operation.
 * @throws {UnsupportedOperationError}
 *     Not available in chrome context.
 */
GeckoDriver.prototype.print = async function (cmd) {
  lazy.assert.content(this.context);
  lazy.assert.open(this.getBrowsingContext({ top: true }));
  await this._handleUserPrompts();

  const settings = lazy.print.addDefaultSettings(cmd.parameters);
  for (const prop of ["top", "bottom", "left", "right"]) {
    lazy.assert.positiveNumber(
      settings.margin[prop],
      lazy.pprint`Expected "margin.${prop}" to be a positive number, got ${settings.margin[prop]}`
    );
  }
  for (const prop of ["width", "height"]) {
    lazy.assert.positiveNumber(
      settings.page[prop],
      lazy.pprint`Expected "page.${prop}" to be a positive number, got ${settings.page[prop]}`
    );
  }
  lazy.assert.positiveNumber(
    settings.scale,
    lazy.pprint`Expected "scale" to be a positive number, got ${settings.scale}`
  );
  lazy.assert.that(
    s =>
      s >= lazy.print.minScaleValue &&
      settings.scale <= lazy.print.maxScaleValue,
    lazy.pprint`scale ${settings.scale} is outside the range ${lazy.print.minScaleValue}-${lazy.print.maxScaleValue}`
  )(settings.scale);
  lazy.assert.boolean(
    settings.shrinkToFit,
    lazy.pprint`Expected "shrinkToFit" to be a boolean, got ${settings.shrinkToFit}`
  );
  lazy.assert.that(
    orientation => lazy.print.defaults.orientationValue.includes(orientation),
    lazy.pprint`orientation ${
      settings.orientation
    } doesn't match allowed values "${lazy.print.defaults.orientationValue.join(
      "/"
    )}"`
  )(settings.orientation);
  lazy.assert.boolean(
    settings.background,
    lazy.pprint`Expected "background" to be a boolean, got ${settings.background}`
  );
  lazy.assert.array(
    settings.pageRanges,
    lazy.pprint`Expected "pageRanges" to be an array, got ${settings.pageRanges}`
  );

  const browsingContext = this.curBrowser.tab.linkedBrowser.browsingContext;
  const printSettings = await lazy.print.getPrintSettings(settings);
  const binaryString = await lazy.print.printToBinaryString(
    browsingContext,
    printSettings
  );

  return btoa(binaryString);
};

GeckoDriver.prototype.addVirtualAuthenticator = function (cmd) {
  const {
    protocol,
    transport,
    hasResidentKey,
    hasUserVerification,
    isUserConsenting,
    isUserVerified,
  } = cmd.parameters;

  lazy.assert.string(
    protocol,
    lazy.pprint`Expected "protocol" to be a string, got ${protocol}`
  );
  lazy.assert.string(
    transport,
    lazy.pprint`Expected "transport" to be a string, got ${transport}`
  );
  lazy.assert.boolean(
    hasResidentKey,
    lazy.pprint`Expected "hasResidentKey" to be a boolean, got ${hasResidentKey}`
  );
  lazy.assert.boolean(
    hasUserVerification,
    lazy.pprint`Expected "hasUserVerification" to be a boolean, got ${hasUserVerification}`
  );
  lazy.assert.boolean(
    isUserConsenting,
    lazy.pprint`Expected "isUserConsenting" to be a boolean, got ${isUserConsenting}`
  );
  lazy.assert.boolean(
    isUserVerified,
    lazy.pprint`Expected "isUserVerified" to be a boolean, got ${isUserVerified}`
  );

  return lazy.webauthn.addVirtualAuthenticator(
    protocol,
    transport,
    hasResidentKey,
    hasUserVerification,
    isUserConsenting,
    isUserVerified
  );
};

GeckoDriver.prototype.removeVirtualAuthenticator = function (cmd) {
  const { authenticatorId } = cmd.parameters;

  lazy.assert.positiveInteger(
    authenticatorId,
    lazy.pprint`Expected "authenticatorId" to be a positiveInteger, got ${authenticatorId}`
  );

  lazy.webauthn.removeVirtualAuthenticator(authenticatorId);
};

GeckoDriver.prototype.addCredential = function (cmd) {
  const {
    authenticatorId,
    credentialId,
    isResidentCredential,
    rpId,
    privateKey,
    userHandle,
    signCount,
  } = cmd.parameters;

  lazy.assert.positiveInteger(
    authenticatorId,
    lazy.pprint`Expected "authenticatorId" to be a positiveInteger, got ${authenticatorId}`
  );
  lazy.assert.string(
    credentialId,
    lazy.pprint`Expected "credentialId" to be a string, got ${credentialId}`
  );
  lazy.assert.boolean(
    isResidentCredential,
    lazy.pprint`Expected "isResidentCredential" to be a boolean, got ${isResidentCredential}`
  );
  lazy.assert.string(
    rpId,
    lazy.pprint`Expected "rpId" to be a string, got ${rpId}`
  );
  lazy.assert.string(
    privateKey,
    lazy.pprint`Expected "privateKey" to be a string, got ${privateKey}`
  );
  if (userHandle) {
    lazy.assert.string(
      userHandle,
      lazy.pprint`Expected "userHandle" to be a string, got ${userHandle}`
    );
  }
  lazy.assert.number(
    signCount,
    lazy.pprint`Expected "signCount" to be a number, got ${signCount}`
  );

  lazy.webauthn.addCredential(
    authenticatorId,
    credentialId,
    isResidentCredential,
    rpId,
    privateKey,
    userHandle,
    signCount
  );
};

GeckoDriver.prototype.getCredentials = function (cmd) {
  const { authenticatorId } = cmd.parameters;

  lazy.assert.positiveInteger(
    authenticatorId,
    lazy.pprint`Expected "authenticatorId" to be a positiveInteger, got ${authenticatorId}`
  );

  return lazy.webauthn.getCredentials(authenticatorId);
};

GeckoDriver.prototype.removeCredential = function (cmd) {
  const { authenticatorId, credentialId } = cmd.parameters;

  lazy.assert.positiveInteger(
    authenticatorId,
    lazy.pprint`Expected "authenticatorId" to be a positiveInteger, got ${authenticatorId}`
  );
  lazy.assert.string(
    credentialId,
    lazy.pprint`Expected "credentialId" to be a string, got ${credentialId}`
  );

  lazy.webauthn.removeCredential(authenticatorId, credentialId);
};

GeckoDriver.prototype.removeAllCredentials = function (cmd) {
  const { authenticatorId } = cmd.parameters;

  lazy.assert.positiveInteger(
    authenticatorId,
    lazy.pprint`Expected "authenticatorId" to be a positiveInteger, got ${authenticatorId}`
  );

  lazy.webauthn.removeAllCredentials(authenticatorId);
};

GeckoDriver.prototype.setUserVerified = function (cmd) {
  const { authenticatorId, isUserVerified } = cmd.parameters;

  lazy.assert.positiveInteger(
    authenticatorId,
    lazy.pprint`Expected "authenticatorId" to be a positiveInteger, got ${authenticatorId}`
  );
  lazy.assert.boolean(
    isUserVerified,
    lazy.pprint`Expected "isUserVerified" to be a boolean, got ${isUserVerified}`
  );

  lazy.webauthn.setUserVerified(authenticatorId, isUserVerified);
};

GeckoDriver.prototype.setPermission = async function (cmd) {
  const { descriptor, state, oneRealm = false } = cmd.parameters;
  const browsingContext = lazy.assert.open(this.getBrowsingContext());

  lazy.permissions.validateDescriptor(descriptor);
  lazy.permissions.validateState(state);

  let params;
  try {
    params =
      await this.curBrowser.window.navigator.permissions.parseSetParameters({
        descriptor,
        state,
      });
  } catch (err) {
    throw new lazy.error.InvalidArgumentError(`setPermission: ${err.message}`);
  }

  lazy.assert.boolean(
    oneRealm,
    lazy.pprint`Expected "oneRealm" to be a boolean, got ${oneRealm}`
  );

  let origin = browsingContext.currentURI.prePath;

  // storage-access is a special case.
  if (descriptor.name === "storage-access") {
    origin = browsingContext.top.currentURI.prePath;

    params = {
      type: lazy.permissions.getStorageAccessPermissionsType(
        browsingContext.currentWindowGlobal.documentURI
      ),
    };
  }

  lazy.permissions.set(params, state, origin);
};

/**
 * Determines the Accessibility label for this element.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.id
 *     Web element reference ID to the element for which the accessibility label
 *     will be returned.
 *
 * @returns {string}
 *     The Accessibility label for this element
 */
GeckoDriver.prototype.getComputedLabel = async function (cmd) {
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  let id = lazy.assert.string(
    cmd.parameters.id,
    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
  );
  let webEl = lazy.WebElement.fromUUID(id).toJSON();

  return this.getActor().getComputedLabel(webEl);
};

/**
 * Determines the Accessibility role for this element.
 *
 * @param {object} cmd
 * @param {string} cmd.parameters.id
 *     Web element reference ID to the element for which the accessibility role
 *     will be returned.
 *
 * @returns {string}
 *     The Accessibility role for this element
 */
GeckoDriver.prototype.getComputedRole = async function (cmd) {
  lazy.assert.open(this.getBrowsingContext());
  await this._handleUserPrompts();

  let id = lazy.assert.string(
    cmd.parameters.id,
    lazy.pprint`Expected "id" to be a string, got ${cmd.parameters.id}`
  );
  let webEl = lazy.WebElement.fromUUID(id).toJSON();
  return this.getActor().getComputedRole(webEl);
};

GeckoDriver.prototype.commands = {
  // Marionette service
  "Marionette:AcceptConnections": GeckoDriver.prototype.acceptConnections,
  "Marionette:GetContext": GeckoDriver.prototype.getContext,
  "Marionette:GetScreenOrientation": GeckoDriver.prototype.getScreenOrientation,
  "Marionette:GetWindowType": GeckoDriver.prototype.getWindowType,
  "Marionette:Quit": GeckoDriver.prototype.quit,
  "Marionette:SetContext": GeckoDriver.prototype.setContext,
  "Marionette:SetScreenOrientation": GeckoDriver.prototype.setScreenOrientation,

  // Addon service
  "Addon:Install": GeckoDriver.prototype.installAddon,
  "Addon:Uninstall": GeckoDriver.prototype.uninstallAddon,

  // L10n service
  "L10n:LocalizeEntity": GeckoDriver.prototype.localizeEntity,
  "L10n:LocalizeProperty": GeckoDriver.prototype.localizeProperty,

  // Reftest service
  "reftest:setup": GeckoDriver.prototype.setupReftest,
  "reftest:run": GeckoDriver.prototype.runReftest,
  "reftest:teardown": GeckoDriver.prototype.teardownReftest,

  // WebDriver service
  "WebDriver:AcceptAlert": GeckoDriver.prototype.acceptDialog,
  // deprecated, no longer used since the geckodriver 0.30.0 release
  "WebDriver:AcceptDialog": GeckoDriver.prototype.acceptDialog,
  "WebDriver:AddCookie": GeckoDriver.prototype.addCookie,
  "WebDriver:Back": GeckoDriver.prototype.goBack,
  "WebDriver:CloseChromeWindow": GeckoDriver.prototype.closeChromeWindow,
  "WebDriver:CloseWindow": GeckoDriver.prototype.close,
  "WebDriver:DeleteAllCookies": GeckoDriver.prototype.deleteAllCookies,
  "WebDriver:DeleteCookie": GeckoDriver.prototype.deleteCookie,
  "WebDriver:DeleteSession": GeckoDriver.prototype.deleteSession,
  "WebDriver:DismissAlert": GeckoDriver.prototype.dismissDialog,
  "WebDriver:ElementClear": GeckoDriver.prototype.clearElement,
  "WebDriver:ElementClick": GeckoDriver.prototype.clickElement,
  "WebDriver:ElementSendKeys": GeckoDriver.prototype.sendKeysToElement,
  "WebDriver:ExecuteAsyncScript": GeckoDriver.prototype.executeAsyncScript,
  "WebDriver:ExecuteScript": GeckoDriver.prototype.executeScript,
  "WebDriver:FindElement": GeckoDriver.prototype.findElement,
  "WebDriver:FindElementFromShadowRoot":
    GeckoDriver.prototype.findElementFromShadowRoot,
  "WebDriver:FindElements": GeckoDriver.prototype.findElements,
  "WebDriver:FindElementsFromShadowRoot":
    GeckoDriver.prototype.findElementsFromShadowRoot,
  "WebDriver:Forward": GeckoDriver.prototype.goForward,
  "WebDriver:FullscreenWindow": GeckoDriver.prototype.fullscreenWindow,
  "WebDriver:GetActiveElement": GeckoDriver.prototype.getActiveElement,
  "WebDriver:GetAlertText": GeckoDriver.prototype.getTextFromDialog,
  "WebDriver:GetCapabilities": GeckoDriver.prototype.getSessionCapabilities,
  "WebDriver:GetComputedLabel": GeckoDriver.prototype.getComputedLabel,
  "WebDriver:GetComputedRole": GeckoDriver.prototype.getComputedRole,
  "WebDriver:GetCookies": GeckoDriver.prototype.getCookies,
  "WebDriver:GetCurrentURL": GeckoDriver.prototype.getCurrentUrl,
  "WebDriver:GetElementAttribute": GeckoDriver.prototype.getElementAttribute,
  "WebDriver:GetElementCSSValue":
    GeckoDriver.prototype.getElementValueOfCssProperty,
  "WebDriver:GetElementProperty": GeckoDriver.prototype.getElementProperty,
  "WebDriver:GetElementRect": GeckoDriver.prototype.getElementRect,
  "WebDriver:GetElementTagName": GeckoDriver.prototype.getElementTagName,
  "WebDriver:GetElementText": GeckoDriver.prototype.getElementText,
  "WebDriver:GetPageSource": GeckoDriver.prototype.getPageSource,
  "WebDriver:GetShadowRoot": GeckoDriver.prototype.getShadowRoot,
  "WebDriver:GetTimeouts": GeckoDriver.prototype.getTimeouts,
  "WebDriver:GetTitle": GeckoDriver.prototype.getTitle,
  "WebDriver:GetWindowHandle": GeckoDriver.prototype.getWindowHandle,
  "WebDriver:GetWindowHandles": GeckoDriver.prototype.getWindowHandles,
  "WebDriver:GetWindowRect": GeckoDriver.prototype.getWindowRect,
  "WebDriver:IsElementDisplayed": GeckoDriver.prototype.isElementDisplayed,
  "WebDriver:IsElementEnabled": GeckoDriver.prototype.isElementEnabled,
  "WebDriver:IsElementSelected": GeckoDriver.prototype.isElementSelected,
  "WebDriver:MinimizeWindow": GeckoDriver.prototype.minimizeWindow,
  "WebDriver:MaximizeWindow": GeckoDriver.prototype.maximizeWindow,
  "WebDriver:Navigate": GeckoDriver.prototype.navigateTo,
  "WebDriver:NewSession": GeckoDriver.prototype.newSession,
  "WebDriver:NewWindow": GeckoDriver.prototype.newWindow,
  "WebDriver:PerformActions": GeckoDriver.prototype.performActions,
  "WebDriver:Print": GeckoDriver.prototype.print,
  "WebDriver:Refresh": GeckoDriver.prototype.refresh,
  "WebDriver:ReleaseActions": GeckoDriver.prototype.releaseActions,
  "WebDriver:SendAlertText": GeckoDriver.prototype.sendKeysToDialog,
  "WebDriver:SetPermission": GeckoDriver.prototype.setPermission,
  "WebDriver:SetTimeouts": GeckoDriver.prototype.setTimeouts,
  "WebDriver:SetWindowRect": GeckoDriver.prototype.setWindowRect,
  "WebDriver:SwitchToFrame": GeckoDriver.prototype.switchToFrame,
  "WebDriver:SwitchToParentFrame": GeckoDriver.prototype.switchToParentFrame,
  "WebDriver:SwitchToWindow": GeckoDriver.prototype.switchToWindow,
  "WebDriver:TakeScreenshot": GeckoDriver.prototype.takeScreenshot,

  // WebAuthn
  "WebAuthn:AddVirtualAuthenticator":
    GeckoDriver.prototype.addVirtualAuthenticator,
  "WebAuthn:RemoveVirtualAuthenticator":
    GeckoDriver.prototype.removeVirtualAuthenticator,
  "WebAuthn:AddCredential": GeckoDriver.prototype.addCredential,
  "WebAuthn:GetCredentials": GeckoDriver.prototype.getCredentials,
  "WebAuthn:RemoveCredential": GeckoDriver.prototype.removeCredential,
  "WebAuthn:RemoveAllCredentials": GeckoDriver.prototype.removeAllCredentials,
  "WebAuthn:SetUserVerified": GeckoDriver.prototype.setUserVerified,
};

async function exitFullscreen(win) {
  let cb;
  // Use a timed promise to abort if no window manager is present
  await new lazy.TimedPromise(
    resolve => {
      cb = new lazy.DebounceCallback(resolve);
      win.addEventListener("sizemodechange", cb);
      win.fullScreen = false;
    },
    { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
  );
  win.removeEventListener("sizemodechange", cb);
  await new lazy.IdlePromise(win);
}

async function restoreWindow(win) {
  let cb;
  if (lazy.WindowState.from(win.windowState) == lazy.WindowState.Normal) {
    return;
  }
  // Use a timed promise to abort if no window manager is present
  await new lazy.TimedPromise(
    resolve => {
      cb = new lazy.DebounceCallback(resolve);
      win.addEventListener("sizemodechange", cb);
      win.restore();
    },
    { throws: null, timeout: TIMEOUT_NO_WINDOW_MANAGER }
  );
  win.removeEventListener("sizemodechange", cb);
  await new lazy.IdlePromise(win);
}
PK
!<�	e�'�'1chrome/remote/content/marionette/evaluate.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
});

const ARGUMENTS = "__webDriverArguments";
const CALLBACK = "__webDriverCallback";
const COMPLETE = "__webDriverComplete";
const DEFAULT_TIMEOUT = 10000; // ms
const FINISH = "finish";

/** @namespace */
export const evaluate = {};

/**
 * Evaluate a script in given sandbox.
 *
 * The the provided `script` will be wrapped in an anonymous function
 * with the `args` argument applied.
 *
 * The arguments provided by the `args<` argument are exposed
 * through the `arguments` object available in the script context,
 * and if the script is executed asynchronously with the `async`
 * option, an additional last argument that is synonymous to the
 * name `resolve` is appended, and can be accessed
 * through `arguments[arguments.length - 1]`.
 *
 * The `timeout` option specifies the duration for how long the
 * script should be allowed to run before it is interrupted and aborted.
 * An interrupted script will cause a {@link ScriptTimeoutError} to occur.
 *
 * The `async` option indicates that the script will not return
 * until the `resolve` callback is invoked,
 * which is analogous to the last argument of the `arguments` object.
 *
 * The `file` option is used in error messages to provide information
 * on the origin script file in the local end.
 *
 * The `line` option is used in error messages, along with `filename`,
 * to provide the line number in the origin script file on the local end.
 *
 * @param {nsISandbox} sb
 *     Sandbox the script will be evaluted in.
 * @param {string} script
 *     Script to evaluate.
 * @param {Array.<?>=} args
 *     A sequence of arguments to call the script with.
 * @param {object=} options
 * @param {boolean=} options.async
 *     Indicates if the script should return immediately or wait for
 *     the callback to be invoked before returning. Defaults to false.
 * @param {string=} options.file
 *     File location of the program in the client. Defaults to "dummy file".
 * @param {number=} options.line
 *     Line number of the program in the client. Defaults to 0.
 * @param {number=} options.timeout
 *     Duration in milliseconds before interrupting the script. Defaults to
 *     DEFAULT_TIMEOUT.
 *
 * @returns {Promise}
 *     A promise that when resolved will give you the return value from
 *     the script.  Note that the return value requires serialisation before
 *     it can be sent to the client.
 *
 * @throws {JavaScriptError}
 *   If an {@link Error} was thrown whilst evaluating the script.
 * @throws {ScriptTimeoutError}
 *   If the script was interrupted due to script timeout.
 */
evaluate.sandbox = function (
  sb,
  script,
  args = [],
  {
    async = false,
    file = "dummy file",
    line = 0,
    timeout = DEFAULT_TIMEOUT,
  } = {}
) {
  let unloadHandler;
  let marionetteSandbox = sandbox.create(sb.window);

  // timeout handler
  let scriptTimeoutID, timeoutPromise;
  if (timeout !== null) {
    timeoutPromise = new Promise((resolve, reject) => {
      scriptTimeoutID = setTimeout(() => {
        reject(
          new lazy.error.ScriptTimeoutError(`Timed out after ${timeout} ms`)
        );
      }, timeout);
    });
  }

  let promise = new Promise((resolve, reject) => {
    let src = "";
    sb[COMPLETE] = resolve;
    sb[ARGUMENTS] = sandbox.cloneInto(args, sb);

    // callback function made private
    // so that introspection is possible
    // on the arguments object
    if (async) {
      sb[CALLBACK] = sb[COMPLETE];
      src += `${ARGUMENTS}.push(rv => ${CALLBACK}(rv));`;
    }

    src += `(function() {
      ${script}
    }).apply(null, ${ARGUMENTS})`;

    unloadHandler = sandbox.cloneInto(
      () => reject(new lazy.error.JavaScriptError("Document was unloaded")),
      marionetteSandbox
    );
    marionetteSandbox.window.addEventListener("unload", unloadHandler);

    let promises = [
      Cu.evalInSandbox(
        src,
        sb,
        "1.8",
        file,
        line,
        /* enforceFilenameRestrictions */ false
      ),
      timeoutPromise,
    ];

    // Wait for the immediate result of calling evalInSandbox, or a timeout.
    // Only resolve the promise if the scriptPromise was resolved and is not
    // async, because the latter has to call resolve() itself.
    Promise.race(promises).then(
      value => {
        if (!async) {
          resolve(value);
        }
      },
      err => {
        reject(err);
      }
    );
  });

  // This block is mainly for async scripts, which escape the inner promise
  // when calling resolve() on their own. The timeout promise will be re-used
  // to break out after the initially setup timeout.
  return Promise.race([promise, timeoutPromise])
    .catch(err => {
      // Only raise valid errors for both the sync and async scripts.
      if (err instanceof lazy.error.ScriptTimeoutError) {
        throw err;
      }
      throw new lazy.error.JavaScriptError(err);
    })
    .finally(() => {
      clearTimeout(scriptTimeoutID);
      marionetteSandbox.window.removeEventListener("unload", unloadHandler);
    });
};

/**
 * `Cu.isDeadWrapper` does not return true for a dead sandbox that
 * was assosciated with and extension popup.  This provides a way to
 * still test for a dead object.
 *
 * @param {object} obj
 *     A potentially dead object.
 * @param {string} prop
 *     Name of a property on the object.
 *
 * @returns {boolean}
 *     True if <var>obj</var> is dead, false otherwise.
 */
evaluate.isDead = function (obj, prop) {
  try {
    obj[prop];
  } catch (e) {
    if (e.message.includes("dead object")) {
      return true;
    }
    throw e;
  }
  return false;
};

export const sandbox = {};

/**
 * Provides a safe way to take an object defined in a privileged scope and
 * create a structured clone of it in a less-privileged scope.  It returns
 * a reference to the clone.
 *
 * Unlike for {@link Components.utils.cloneInto}, `obj` may contain
 * functions and DOM elements.
 */
sandbox.cloneInto = function (obj, sb) {
  return Cu.cloneInto(obj, sb, { cloneFunctions: true, wrapReflectors: true });
};

/**
 * Augment given sandbox by an adapter that has an `exports` map
 * property, or a normal map, of function names and function references.
 *
 * @param {Sandbox} sb
 *     The sandbox to augment.
 * @param {object} adapter
 *     Object that holds an `exports` property, or a map, of function
 *     names and function references.
 *
 * @returns {Sandbox}
 *     The augmented sandbox.
 */
sandbox.augment = function (sb, adapter) {
  function* entries(obj) {
    for (let key of Object.keys(obj)) {
      yield [key, obj[key]];
    }
  }

  let funcs = adapter.exports || entries(adapter);
  for (let [name, func] of funcs) {
    sb[name] = func;
  }

  return sb;
};

/**
 * Creates a sandbox.
 *
 * @param {Window} win
 *     The DOM Window object.
 * @param {nsIPrincipal=} principal
 *     An optional, custom principal to prefer over the Window.  Useful if
 *     you need elevated security permissions.
 *
 * @returns {Sandbox}
 *     The created sandbox.
 */
sandbox.create = function (win, principal = null, opts = {}) {
  let p = principal || win;
  opts = Object.assign(
    {
      sameZoneAs: win,
      sandboxPrototype: win,
      wantComponents: true,
      wantXrays: true,
      wantGlobalProperties: ["ChromeUtils"],
    },
    opts
  );
  return new Cu.Sandbox(p, opts);
};

/**
 * Creates a mutable sandbox, where changes to the global scope
 * will have lasting side-effects.
 *
 * @param {Window} win
 *     The DOM Window object.
 *
 * @returns {Sandbox}
 *     The created sandbox.
 */
sandbox.createMutable = function (win) {
  let opts = {
    wantComponents: false,
    wantXrays: false,
  };
  // Note: We waive Xrays here to match potentially-accidental old behavior.
  return Cu.waiveXrays(sandbox.create(win, null, opts));
};

sandbox.createSystemPrincipal = function (win) {
  let principal = Cc["@mozilla.org/systemprincipal;1"].createInstance(
    Ci.nsIPrincipal
  );
  return sandbox.create(win, principal);
};

sandbox.createSimpleTest = function (win, harness) {
  let sb = sandbox.create(win);
  sb = sandbox.augment(sb, harness);
  sb[FINISH] = () => sb[COMPLETE](harness.generate_results());
  return sb;
};

/**
 * Sandbox storage.  When the user requests a sandbox by a specific name,
 * if one exists in the storage this will be used as long as its window
 * reference is still valid.
 *
 * @memberof evaluate
 */
export class Sandboxes {
  /**
   * @param {function(): Window} windowFn
   *     A function that returns the references to the current Window
   *     object.
   */
  constructor(windowFn) {
    this.windowFn_ = windowFn;
    this.boxes_ = new Map();
  }

  get window_() {
    return this.windowFn_();
  }

  /**
   * Factory function for getting a sandbox by name, or failing that,
   * creating a new one.
   *
   * If the sandbox' window does not match the provided window, a new one
   * will be created.
   *
   * @param {string} name
   *     The name of the sandbox to get or create.
   * @param {boolean=} [fresh=false] fresh
   *     Remove old sandbox by name first, if it exists.
   *
   * @returns {Sandbox}
   *     A used or fresh sandbox.
   */
  get(name = "default", fresh = false) {
    let sb = this.boxes_.get(name);
    if (sb) {
      if (fresh || evaluate.isDead(sb, "window") || sb.window != this.window_) {
        this.boxes_.delete(name);
        return this.get(name, false);
      }
    } else {
      if (name == "system") {
        sb = sandbox.createSystemPrincipal(this.window_);
      } else {
        sb = sandbox.create(this.window_);
      }
      this.boxes_.set(name, sb);
    }
    return sb;
  }

  /** Clears cache of sandboxes. */
  clear() {
    this.boxes_.clear();
  }
}
PK
!<%<O��X�X4chrome/remote/content/marionette/interaction.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-disable no-restricted-globals */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  setTimeout: "resource://gre/modules/Timer.sys.mjs",

  accessibility:
    "chrome://remote/content/shared/webdriver/Accessibility.sys.mjs",
  atom: "chrome://remote/content/marionette/atom.sys.mjs",
  dom: "chrome://remote/content/shared/DOM.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  event: "chrome://remote/content/shared/webdriver/Event.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
  TimedPromise: "chrome://remote/content/marionette/sync.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
);

// dragService may be null if it's in the headless mode (e.g., on Linux).
// It depends on the platform, though.
ChromeUtils.defineLazyGetter(lazy, "dragService", () => {
  try {
    return Cc["@mozilla.org/widget/dragservice;1"].getService(
      Ci.nsIDragService
    );
  } catch (e) {
    // If we're in the headless mode, the drag service may be never
    // instantiated.  In this case, an exception is thrown.  Let's ignore
    // any exceptions since without the drag service, nobody can create a
    // drag session.
    return null;
  }
});

/** XUL elements that support disabled attribute. */
const DISABLED_ATTRIBUTE_SUPPORTED_XUL = new Set([
  "ARROWSCROLLBOX",
  "BUTTON",
  "CHECKBOX",
  "COMMAND",
  "DESCRIPTION",
  "KEY",
  "KEYSET",
  "LABEL",
  "MENU",
  "MENUITEM",
  "MENULIST",
  "MENUSEPARATOR",
  "RADIO",
  "RADIOGROUP",
  "RICHLISTBOX",
  "RICHLISTITEM",
  "TAB",
  "TABS",
  "TOOLBARBUTTON",
  "TREE",
]);

/**
 * Common form controls that user can change the value property
 * interactively.
 */
const COMMON_FORM_CONTROLS = new Set(["input", "textarea", "select"]);

/**
 * Input elements that do not fire <tt>input</tt> and <tt>change</tt>
 * events when value property changes.
 */
const INPUT_TYPES_NO_EVENT = new Set([
  "checkbox",
  "radio",
  "file",
  "hidden",
  "image",
  "reset",
  "button",
  "submit",
]);

/** @namespace */
export const interaction = {};

/**
 * Interact with an element by clicking it.
 *
 * The element is scrolled into view before visibility- or interactability
 * checks are performed.
 *
 * Selenium-style visibility checks will be performed
 * if <var>specCompat</var> is false (default).  Otherwise
 * pointer-interactability checks will be performed.  If either of these
 * fail an {@link ElementNotInteractableError} is thrown.
 *
 * If <var>strict</var> is enabled (defaults to disabled), further
 * accessibility checks will be performed, and these may result in an
 * {@link ElementNotAccessibleError} being returned.
 *
 * When <var>el</var> is not enabled, an {@link InvalidElementStateError}
 * is returned.
 *
 * @param {(DOMElement|XULElement)} el
 *     Element to click.
 * @param {boolean=} [strict=false] strict
 *     Enforce strict accessibility tests.
 * @param {boolean=} [specCompat=false] specCompat
 *     Use WebDriver specification compatible interactability definition.
 *
 * @throws {ElementNotInteractableError}
 *     If either Selenium-style visibility check or
 *     pointer-interactability check fails.
 * @throws {ElementClickInterceptedError}
 *     If <var>el</var> is obscured by another element and a click would
 *     not hit, in <var>specCompat</var> mode.
 * @throws {ElementNotAccessibleError}
 *     If <var>strict</var> is true and element is not accessible.
 * @throws {InvalidElementStateError}
 *     If <var>el</var> is not enabled.
 */
interaction.clickElement = async function (
  el,
  strict = false,
  specCompat = false
) {
  const a11y = lazy.accessibility.get(strict);
  if (lazy.dom.isXULElement(el)) {
    await chromeClick(el, a11y);
  } else if (specCompat) {
    await webdriverClickElement(el, a11y);
  } else {
    lazy.logger.trace(`Using non spec-compatible element click`);
    await seleniumClickElement(el, a11y);
  }
};

async function webdriverClickElement(el, a11y) {
  const win = getWindow(el);

  // step 3
  if (el.localName == "input" && el.type == "file") {
    throw new lazy.error.InvalidArgumentError(
      "Cannot click <input type=file> elements"
    );
  }

  let containerEl = lazy.dom.getContainer(el);

  // step 4
  if (!lazy.dom.isInView(containerEl)) {
    lazy.dom.scrollIntoView(containerEl);
  }

  // step 5
  // TODO(ato): wait for containerEl to be in view

  // step 6
  // if we cannot bring the container element into the viewport
  // there is no point in checking if it is pointer-interactable
  if (!lazy.dom.isInView(containerEl)) {
    throw new lazy.error.ElementNotInteractableError(
      lazy.pprint`Element ${el} could not be scrolled into view`
    );
  }

  // step 7
  let rects = containerEl.getClientRects();
  let clickPoint = lazy.dom.getInViewCentrePoint(rects[0], win);

  if (lazy.dom.isObscured(containerEl)) {
    throw new lazy.error.ElementClickInterceptedError(
      null,
      {},
      containerEl,
      clickPoint
    );
  }

  let acc = await a11y.assertAccessible(el, true);
  a11y.assertVisible(acc, el, true);
  a11y.assertEnabled(acc, el, true);
  a11y.assertActionable(acc, el);

  // step 8
  if (el.localName == "option") {
    interaction.selectOption(el);
  } else {
    // Synthesize a pointerMove action.
    lazy.event.synthesizeMouseAtPoint(
      clickPoint.x,
      clickPoint.y,
      {
        type: "mousemove",
        allowToHandleDragDrop: true,
      },
      win
    );

    if (lazy.dragService?.getCurrentSession(win)) {
      // Special handling is required if the mousemove started a drag session.
      // In this case, mousedown event shouldn't be fired, and the mouseup should
      // end the session.  Therefore, we should synthesize only mouseup.
      lazy.event.synthesizeMouseAtPoint(
        clickPoint.x,
        clickPoint.y,
        {
          type: "mouseup",
          allowToHandleDragDrop: true,
        },
        win
      );
    } else {
      // step 9
      let clicked = interaction.flushEventLoop(containerEl);

      // Synthesize a pointerDown + pointerUp action.
      lazy.event.synthesizeMouseAtPoint(
        clickPoint.x,
        clickPoint.y,
        { allowToHandleDragDrop: true },
        win
      );

      await clicked;
    }
  }

  // step 10
  // if the click causes navigation, the post-navigation checks are
  // handled by navigate.js
}

async function chromeClick(el, a11y) {
  if (!(await lazy.dom.isEnabled(el))) {
    throw new lazy.error.InvalidElementStateError("Element is not enabled");
  }

  let acc = await a11y.assertAccessible(el, true);
  a11y.assertVisible(acc, el, true);
  a11y.assertEnabled(acc, el, true);
  a11y.assertActionable(acc, el);

  if (el.localName == "option") {
    interaction.selectOption(el);
  } else {
    el.click();
  }
}

async function seleniumClickElement(el, a11y) {
  let win = getWindow(el);

  let visibilityCheckEl = el;
  if (el.localName == "option") {
    visibilityCheckEl = lazy.dom.getContainer(el);
  }

  if (!(await lazy.dom.isVisible(visibilityCheckEl))) {
    throw new lazy.error.ElementNotInteractableError();
  }

  if (!(await lazy.dom.isEnabled(el))) {
    throw new lazy.error.InvalidElementStateError("Element is not enabled");
  }

  let acc = await a11y.assertAccessible(el, true);
  a11y.assertVisible(acc, el, true);
  a11y.assertEnabled(acc, el, true);
  a11y.assertActionable(acc, el);

  if (el.localName == "option") {
    interaction.selectOption(el);
  } else {
    let rects = el.getClientRects();
    let centre = lazy.dom.getInViewCentrePoint(rects[0], win);
    let opts = {};
    lazy.event.synthesizeMouseAtPoint(centre.x, centre.y, opts, win);
  }
}

/**
 * Select <tt>&lt;option&gt;</tt> element in a <tt>&lt;select&gt;</tt>
 * list.
 *
 * Because the dropdown list of select elements are implemented using
 * native widget technology, our trusted synthesised events are not able
 * to reach them.  Dropdowns are instead handled mimicking DOM events,
 * which for obvious reasons is not ideal, but at the current point in
 * time considered to be good enough.
 *
 * @param {HTMLOptionElement} el
 *     Option element to select.
 *
 * @throws {TypeError}
 *     If <var>el</var> is a XUL element or not an <tt>&lt;option&gt;</tt>
 *     element.
 * @throws {Error}
 *     If unable to find <var>el</var>'s parent <tt>&lt;select&gt;</tt>
 *     element.
 */
interaction.selectOption = function (el) {
  if (lazy.dom.isXULElement(el)) {
    throw new TypeError("XUL dropdowns not supported");
  }
  if (el.localName != "option") {
    throw new TypeError(lazy.pprint`Expected <option> element, got ${el}`);
  }

  let containerEl = lazy.dom.getContainer(el);

  lazy.event.mouseover(containerEl);
  lazy.event.mousemove(containerEl);
  lazy.event.mousedown(containerEl);
  containerEl.focus();

  if (!el.disabled) {
    // Clicking <option> in <select> should not be deselected if selected.
    // However, clicking one in a <select multiple> should toggle
    // selectedness the way holding down Control works.
    if (containerEl.multiple) {
      el.selected = !el.selected;
    } else if (!el.selected) {
      el.selected = true;
    }
    lazy.event.input(containerEl);
    lazy.event.change(containerEl);
  }

  lazy.event.mouseup(containerEl);
  lazy.event.click(containerEl);
  containerEl.blur();
};

/**
 * Clears the form control or the editable element, if required.
 *
 * Before clearing the element, it will attempt to scroll it into
 * view if it is not already in the viewport.  An error is raised
 * if the element cannot be brought into view.
 *
 * If the element is a submittable form control and it is empty
 * (it has no value or it has no files associated with it, in the
 * case it is a <code>&lt;input type=file&gt;</code> element) or
 * it is an editing host and its <code>innerHTML</code> content IDL
 * attribute is empty, this function acts as a no-op.
 *
 * @param {Element} el
 *     Element to clear.
 *
 * @throws {InvalidElementStateError}
 *     If element is disabled, read-only, non-editable, not a submittable
 *     element or not an editing host, or cannot be scrolled into view.
 */
interaction.clearElement = function (el) {
  if (lazy.dom.isDisabled(el)) {
    throw new lazy.error.InvalidElementStateError(
      lazy.pprint`Element is disabled: ${el}`
    );
  }
  if (lazy.dom.isReadOnly(el)) {
    throw new lazy.error.InvalidElementStateError(
      lazy.pprint`Element is read-only: ${el}`
    );
  }
  if (!lazy.dom.isEditable(el)) {
    throw new lazy.error.InvalidElementStateError(
      lazy.pprint`Unable to clear element that cannot be edited: ${el}`
    );
  }

  if (!lazy.dom.isInView(el)) {
    lazy.dom.scrollIntoView(el);
  }
  if (!lazy.dom.isInView(el)) {
    throw new lazy.error.ElementNotInteractableError(
      lazy.pprint`Element ${el} could not be scrolled into view`
    );
  }

  if (lazy.dom.isEditingHost(el)) {
    clearContentEditableElement(el);
  } else {
    clearResettableElement(el);
  }
};

function clearContentEditableElement(el) {
  if (el.innerHTML === "") {
    return;
  }
  el.focus();
  el.innerHTML = "";
  el.blur();
}

function clearResettableElement(el) {
  if (!lazy.dom.isMutableFormControl(el)) {
    throw new lazy.error.InvalidElementStateError(
      lazy.pprint`Not an editable form control: ${el}`
    );
  }

  let isEmpty;
  switch (el.type) {
    case "file":
      isEmpty = !el.files.length;
      break;

    default:
      isEmpty = el.value === "";
      break;
  }

  if (el.validity.valid && isEmpty) {
    return;
  }

  el.focus();
  el.value = "";
  lazy.event.change(el);
  el.blur();
}

/**
 * Waits until the event loop has spun enough times to process the
 * DOM events generated by clicking an element, or until the document
 * is unloaded.
 *
 * @param {Element} el
 *     Element that is expected to receive the click.
 *
 * @returns {Promise}
 *     Promise is resolved once <var>el</var> has been clicked
 *     (its <code>click</code> event fires), the document is unloaded,
 *     or a 500 ms timeout is reached.
 */
interaction.flushEventLoop = async function (el) {
  const win = el.ownerGlobal;
  let unloadEv, clickEv;

  let spinEventLoop = resolve => {
    unloadEv = resolve;
    clickEv = event => {
      lazy.logger.trace(`Received DOM event click for ${event.target}`);
      if (win.closed) {
        resolve();
      } else {
        lazy.setTimeout(resolve, 0);
      }
    };

    win.addEventListener("unload", unloadEv, { mozSystemGroup: true });
    el.addEventListener("click", clickEv, { mozSystemGroup: true });
  };
  let removeListeners = () => {
    // only one event fires
    win.removeEventListener("unload", unloadEv);
    el.removeEventListener("click", clickEv);
  };

  return new lazy.TimedPromise(spinEventLoop, {
    timeout: 500,
    throws: null,
  }).then(removeListeners);
};

/**
 * If <var>el<var> is a textual form control, or is contenteditable,
 * and no previous selection state exists, move the caret to the end
 * of the form control.
 *
 * The element has to be a <code>&lt;input type=text&gt;</code> or
 * <code>&lt;textarea&gt;</code> element, or have the contenteditable
 * attribute set, for the cursor to be moved.
 *
 * @param {Element} el
 *     Element to potential move the caret in.
 */
interaction.moveCaretToEnd = function (el) {
  if (!lazy.dom.isDOMElement(el)) {
    return;
  }

  let isTextarea = el.localName == "textarea";
  let isInputText = el.localName == "input" && el.type == "text";

  if (isTextarea || isInputText) {
    if (el.selectionEnd == 0) {
      let len = el.value.length;
      el.setSelectionRange(len, len);
    }
  } else if (el.isContentEditable) {
    let selection = getWindow(el).getSelection();
    selection.setPosition(el, el.childNodes.length);
  }
};

/**
 * Performs checks if <var>el</var> is keyboard-interactable.
 *
 * To decide if an element is keyboard-interactable various properties,
 * and computed CSS styles have to be evaluated. Whereby it has to be taken
 * into account that the element can be part of a container (eg. option),
 * and as such the container has to be checked instead.
 *
 * @param {Element} el
 *     Element to check.
 *
 * @returns {boolean}
 *     True if element is keyboard-interactable, false otherwise.
 */
interaction.isKeyboardInteractable = function (el) {
  const win = getWindow(el);

  // body and document element are always keyboard-interactable
  if (el.localName === "body" || el === win.document.documentElement) {
    return true;
  }

  // context menu popups do not take the focus from the document.
  const menuPopup = el.closest("menupopup");
  if (menuPopup) {
    if (menuPopup.state !== "open") {
      // closed menupopups are not keyboard interactable.
      return false;
    }

    const menuItem = el.closest("menuitem");
    if (menuItem) {
      // hidden or disabled menu items are not keyboard interactable.
      return !menuItem.disabled && !menuItem.hidden;
    }

    return true;
  }

  return Services.focus.elementIsFocusable(el, 0);
};

/**
 * Updates an `<input type=file>`'s file list with given `paths`.
 *
 * Hereby will the file list be appended with `paths` if the
 * element allows multiple files. Otherwise the list will be
 * replaced.
 *
 * @param {HTMLInputElement} el
 *     An `input type=file` element.
 * @param {Array.<string>} paths
 *     List of full paths to any of the files to be uploaded.
 *
 * @throws {InvalidArgumentError}
 *     If `path` doesn't exist.
 */
interaction.uploadFiles = async function (el, paths) {
  let files = [];

  if (el.hasAttribute("multiple")) {
    // for multiple file uploads new files will be appended
    files = Array.prototype.slice.call(el.files);
  } else if (paths.length > 1) {
    throw new lazy.error.InvalidArgumentError(
      lazy.pprint`Element ${el} doesn't accept multiple files`
    );
  }

  for (let path of paths) {
    let file;

    try {
      file = await File.createFromFileName(path);
    } catch (e) {
      throw new lazy.error.InvalidArgumentError("File not found: " + path);
    }

    files.push(file);
  }

  el.mozSetFileArray(files);
};

/**
 * Sets a form element's value.
 *
 * @param {DOMElement} el
 *     An form element, e.g. input, textarea, etc.
 * @param {string} value
 *     The value to be set.
 *
 * @throws {TypeError}
 *     If <var>el</var> is not an supported form element.
 */
interaction.setFormControlValue = function (el, value) {
  if (!COMMON_FORM_CONTROLS.has(el.localName)) {
    throw new TypeError("This function is for form elements only");
  }

  el.value = value;

  if (INPUT_TYPES_NO_EVENT.has(el.type)) {
    return;
  }

  lazy.event.input(el);
  lazy.event.change(el);
};

/**
 * Send keys to element.
 *
 * @param {DOMElement|XULElement} el
 *     Element to send key events to.
 * @param {Array.<string>} value
 *     Sequence of keystrokes to send to the element.
 * @param {object=} options
 * @param {boolean=} options.strictFileInteractability
 *     Run interactability checks on `<input type=file>` elements.
 * @param {boolean=} options.accessibilityChecks
 *     Enforce strict accessibility tests.
 * @param {boolean=} options.webdriverClick
 *     Use WebDriver specification compatible interactability definition.
 */
interaction.sendKeysToElement = async function (
  el,
  value,
  {
    strictFileInteractability = false,
    accessibilityChecks = false,
    webdriverClick = false,
  } = {}
) {
  const a11y = lazy.accessibility.get(accessibilityChecks);

  if (webdriverClick) {
    await webdriverSendKeysToElement(
      el,
      value,
      a11y,
      strictFileInteractability
    );
  } else {
    await legacySendKeysToElement(el, value, a11y);
  }
};

async function webdriverSendKeysToElement(
  el,
  value,
  a11y,
  strictFileInteractability
) {
  const win = getWindow(el);

  if (el.type !== "file" || strictFileInteractability) {
    let containerEl = lazy.dom.getContainer(el);

    if (!lazy.dom.isInView(containerEl)) {
      lazy.dom.scrollIntoView(containerEl);
    }

    // TODO: Wait for element to be keyboard-interactible
    if (!interaction.isKeyboardInteractable(containerEl)) {
      throw new lazy.error.ElementNotInteractableError(
        lazy.pprint`Element ${el} is not reachable by keyboard`
      );
    }

    if (win.document.activeElement !== containerEl) {
      containerEl.focus();
      // This validates the correct element types internally
      interaction.moveCaretToEnd(containerEl);
    }
  }

  let acc = await a11y.assertAccessible(el, true);
  a11y.assertActionable(acc, el);

  if (el.type == "file") {
    let paths = value.split("\n");
    await interaction.uploadFiles(el, paths);

    lazy.event.input(el);
    lazy.event.change(el);
  } else if (el.type == "date" || el.type == "time") {
    interaction.setFormControlValue(el, value);
  } else {
    lazy.event.sendKeys(value, win);
  }
}

async function legacySendKeysToElement(el, value, a11y) {
  const win = getWindow(el);

  if (el.type == "file") {
    el.focus();
    await interaction.uploadFiles(el, [value]);

    lazy.event.input(el);
    lazy.event.change(el);
  } else if (el.type == "date" || el.type == "time") {
    interaction.setFormControlValue(el, value);
  } else {
    let visibilityCheckEl = el;
    if (el.localName == "option") {
      visibilityCheckEl = lazy.dom.getContainer(el);
    }

    if (!(await lazy.dom.isVisible(visibilityCheckEl))) {
      throw new lazy.error.ElementNotInteractableError(
        "Element is not visible"
      );
    }

    let acc = await a11y.assertAccessible(el, true);
    a11y.assertActionable(acc, el);

    interaction.moveCaretToEnd(el);
    el.focus();
    lazy.event.sendKeys(value, win);
  }
}

/**
 * Determine the element displayedness of an element.
 *
 * @param {DOMElement|XULElement} el
 *     Element to determine displayedness of.
 * @param {boolean=} [strict=false] strict
 *     Enforce strict accessibility tests.
 *
 * @returns {boolean}
 *     True if element is displayed, false otherwise.
 */
interaction.isElementDisplayed = async function (el, strict = false) {
  let win = getWindow(el);
  let displayed = await lazy.atom.isElementDisplayed(el, win);

  let a11y = lazy.accessibility.get(strict);
  return a11y.assertAccessible(el).then(acc => {
    a11y.assertVisible(acc, el, displayed);
    return displayed;
  });
};

/**
 * Check if element is enabled.
 *
 * @param {DOMElement|XULElement} el
 *     Element to test if is enabled.
 *
 * @returns {boolean}
 *     True if enabled, false otherwise.
 */
interaction.isElementEnabled = async function (el, strict = false) {
  let enabled = true;
  let win = getWindow(el);

  if (lazy.dom.isXULElement(el)) {
    // check if XUL element supports disabled attribute
    if (DISABLED_ATTRIBUTE_SUPPORTED_XUL.has(el.tagName.toUpperCase())) {
      if (
        el.hasAttribute("disabled") &&
        el.getAttribute("disabled") === "true"
      ) {
        enabled = false;
      }
    }
  } else if (
    ["application/xml", "text/xml"].includes(win.document.contentType)
  ) {
    enabled = false;
  } else {
    enabled = await lazy.dom.isEnabled(el);
  }

  let a11y = lazy.accessibility.get(strict);
  return a11y.assertAccessible(el).then(acc => {
    a11y.assertEnabled(acc, el, enabled);
    return enabled;
  });
};

/**
 * Determines if the referenced element is selected or not, with
 * an additional accessibility check if <var>strict</var> is true.
 *
 * This operation only makes sense on input elements of the checkbox-
 * and radio button states, and option elements.
 *
 * @param {(DOMElement|XULElement)} el
 *     Element to test if is selected.
 * @param {boolean=} [strict=false] strict
 *     Enforce strict accessibility tests.
 *
 * @returns {boolean}
 *     True if element is selected, false otherwise.
 *
 * @throws {ElementNotAccessibleError}
 *     If <var>el</var> is not accessible when <var>strict</var> is true.
 */
interaction.isElementSelected = function (el, strict = false) {
  let selected = lazy.dom.isSelected(el);

  let a11y = lazy.accessibility.get(strict);
  return a11y.assertAccessible(el).then(acc => {
    a11y.assertSelected(acc, el, selected);
    return selected;
  });
};

function getWindow(el) {
  // eslint-disable-next-line mozilla/use-ownerGlobal
  return el.ownerDocument.defaultView;
}
PK
!<�U�D=D=-chrome/remote/content/marionette/json.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { WebFrame, WebWindow } from "./web-reference.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  dom: "chrome://remote/content/shared/DOM.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
  ShadowRoot: "chrome://remote/content/marionette/web-reference.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
  WebElement: "chrome://remote/content/marionette/web-reference.sys.mjs",
  WebFrame: "chrome://remote/content/marionette/web-reference.sys.mjs",
  WebReference: "chrome://remote/content/marionette/web-reference.sys.mjs",
  WebWindow: "chrome://remote/content/marionette/web-reference.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
);

/** @namespace */
export const json = {};

/**
 * Clone an object including collections.
 *
 * @param {object} value
 *     Object to be cloned.
 * @param {Set} seen
 *     List of objects already processed.
 * @param {Function} cloneAlgorithm
 *     The clone algorithm to invoke for individual list entries or object
 *     properties.
 *
 * @returns {object}
 *     The cloned object.
 */
function cloneObject(value, seen, cloneAlgorithm) {
  // Only proceed with cloning an object if it hasn't been seen yet.
  if (seen.has(value)) {
    throw new lazy.error.JavaScriptError("Cyclic object value");
  }
  seen.add(value);

  let result;

  if (lazy.dom.isCollection(value)) {
    result = [...value].map(entry => cloneAlgorithm(entry, seen));
  } else {
    // arbitrary objects
    result = {};
    for (let prop in value) {
      try {
        result[prop] = cloneAlgorithm(value[prop], seen);
      } catch (e) {
        if (e.result == Cr.NS_ERROR_NOT_IMPLEMENTED) {
          lazy.logger.debug(`Skipping ${prop}: ${e.message}`);
        } else {
          throw e;
        }
      }
    }
  }

  seen.delete(value);

  return result;
}

/**
 * Clone arbitrary objects to JSON-safe primitives that can be
 * transported across processes and over the Marionette protocol.
 *
 * The marshaling rules are as follows:
 *
 * - Primitives are returned as is.
 *
 * - Collections, such as `Array`, `NodeList`, `HTMLCollection`
 *   et al. are transformed to arrays and then recursed.
 *
 * - Elements and ShadowRoots that are not known WebReference's are added to
 *   the `NodeCache`. For both the associated unique web reference identifier
 *   is returned.
 *
 * - Objects with custom JSON representations, i.e. if they have
 *   a callable `toJSON` function, are returned verbatim.  This means
 *   their internal integrity _are not_ checked.  Be careful.
 *
 * - If a cyclic references is detected a JavaScriptError is thrown.
 *
 * @param {object} value
 *     Object to be cloned.
 * @param {NodeCache} nodeCache
 *     Node cache that holds already seen WebElement and ShadowRoot references.
 *
 * @returns {{
 *   seenNodeIds: Map<BrowsingContext, string[]>,
 *   serializedValue: any,
 *   hasSerializedWindows: boolean
 * }}
 *     Object that contains a list of browsing contexts each with a list of
 *     shared ids for collected elements and shadow root nodes, and second the
 *     same object as provided by `value` with the WebDriver classic supported
 *     DOM nodes replaced by WebReference's.
 *
 * @throws {JavaScriptError}
 *     If an object contains cyclic references.
 * @throws {StaleElementReferenceError}
 *     If the element has gone stale, indicating it is no longer
 *     attached to the DOM.
 */
json.clone = function (value, nodeCache) {
  const seenNodeIds = new Map();
  let hasSerializedWindows = false;

  function cloneJSON(value, seen) {
    if (seen === undefined) {
      seen = new Set();
    }

    if ([undefined, null].includes(value)) {
      return null;
    }

    const type = typeof value;

    if (["boolean", "number", "string"].includes(type)) {
      // Primitive values
      return value;
    }

    // Evaluation of code might take place in mutable sandboxes, which are
    // created to waive XRays by default. As such DOM nodes and windows
    // have to be unwaived before accessing properties like "ownerGlobal"
    // is possible.
    //
    // Until bug 1743788 is fixed there might be the possibility that more
    // objects might need to be unwaived as well.
    const isNode = Node.isInstance(value);
    const isWindow = Window.isInstance(value);
    if (isNode || isWindow) {
      value = Cu.unwaiveXrays(value);
    }

    if (isNode && lazy.dom.isElement(value)) {
      // Convert DOM elements to WebReference instances.

      if (lazy.dom.isStale(value)) {
        // Don't create a reference for stale elements.
        throw new lazy.error.StaleElementReferenceError(
          lazy.pprint`The element ${value} is no longer attached to the DOM`
        );
      }

      const nodeRef = nodeCache.getOrCreateNodeReference(value, seenNodeIds);

      return lazy.WebReference.from(value, nodeRef).toJSON();
    }

    if (isNode && lazy.dom.isShadowRoot(value)) {
      // Convert ShadowRoot instances to WebReference references.

      if (lazy.dom.isDetached(value)) {
        // Don't create a reference for detached shadow roots.
        throw new lazy.error.DetachedShadowRootError(
          lazy.pprint`The ShadowRoot ${value} is no longer attached to the DOM`
        );
      }

      const nodeRef = nodeCache.getOrCreateNodeReference(value, seenNodeIds);

      return lazy.WebReference.from(value, nodeRef).toJSON();
    }

    if (isWindow) {
      // Convert window instances to WebReference references.
      let reference;

      if (value.browsingContext.parent == null) {
        reference = new WebWindow(value.browsingContext.browserId.toString());
        hasSerializedWindows = true;
      } else {
        reference = new WebFrame(value.browsingContext.id.toString());
      }

      return reference.toJSON();
    }

    if (typeof value.toJSON == "function") {
      // custom JSON representation
      let unsafeJSON;
      try {
        unsafeJSON = value.toJSON();
      } catch (e) {
        throw new lazy.error.JavaScriptError(`toJSON() failed with: ${e}`);
      }

      return cloneJSON(unsafeJSON, seen);
    }

    // Collections and arbitrary objects
    return cloneObject(value, seen, cloneJSON);
  }

  return {
    seenNodeIds,
    serializedValue: cloneJSON(value, new Set()),
    hasSerializedWindows,
  };
};

/**
 * Deserialize an arbitrary object.
 *
 * @param {object} value
 *     Arbitrary object.
 * @param {NodeCache} nodeCache
 *     Node cache that holds already seen WebElement and ShadowRoot references.
 * @param {BrowsingContext} browsingContext
 *     The browsing context to check.
 *
 * @returns {object}
 *     Same object as provided by `value` with the WebDriver specific
 *     references replaced with real JavaScript objects.
 *
 * @throws {NoSuchElementError}
 *     If the WebElement reference has not been seen before.
 * @throws {StaleElementReferenceError}
 *     If the element is stale, indicating it is no longer attached to the DOM.
 */
json.deserialize = function (value, nodeCache, browsingContext) {
  function deserializeJSON(value, seen) {
    if (seen === undefined) {
      seen = new Set();
    }

    if (value === undefined || value === null) {
      return value;
    }

    switch (typeof value) {
      case "boolean":
      case "number":
      case "string":
      default:
        return value;

      case "object":
        if (lazy.WebReference.isReference(value)) {
          // Create a WebReference based on the WebElement identifier.
          const webRef = lazy.WebReference.fromJSON(value);

          if (webRef instanceof lazy.ShadowRoot) {
            return getKnownShadowRoot(browsingContext, webRef.uuid, nodeCache);
          }

          if (webRef instanceof lazy.WebElement) {
            return getKnownElement(browsingContext, webRef.uuid, nodeCache);
          }

          if (webRef instanceof lazy.WebFrame) {
            const browsingContext = BrowsingContext.get(webRef.uuid);

            if (browsingContext === null || browsingContext.parent === null) {
              throw new lazy.error.NoSuchWindowError(
                `Unable to locate frame with id: ${webRef.uuid}`
              );
            }

            return browsingContext.window;
          }

          if (webRef instanceof lazy.WebWindow) {
            const browsingContext = BrowsingContext.getCurrentTopByBrowserId(
              webRef.uuid
            );

            if (browsingContext === null) {
              throw new lazy.error.NoSuchWindowError(
                `Unable to locate window with id: ${webRef.uuid}`
              );
            }

            return browsingContext.window;
          }
        }

        return cloneObject(value, seen, deserializeJSON);
    }
  }

  return deserializeJSON(value, new Set());
};

/**
 * Convert unique navigable ids to internal browser ids.
 *
 * @param {object} serializedData
 *     The data to process.
 *
 * @returns {object}
 *     The processed data.
 */
json.mapFromNavigableIds = function (serializedData) {
  function _processData(data) {
    if (lazy.WebReference.isReference(data)) {
      const webRef = lazy.WebReference.fromJSON(data);

      if (webRef instanceof lazy.WebWindow) {
        const browser = lazy.TabManager.getBrowserById(webRef.uuid);
        if (browser) {
          webRef.uuid = browser?.browserId.toString();
          data = webRef.toJSON();
        }
      }
    } else if (typeof data === "object") {
      for (const entry in data) {
        data[entry] = _processData(data[entry]);
      }
    }

    return data;
  }

  return _processData(serializedData);
};

/**
 * Convert browser ids to unique navigable ids.
 *
 * @param {object} serializedData
 *     The data to process.
 *
 * @returns {object}
 *     The processed data.
 */
json.mapToNavigableIds = function (serializedData) {
  function _processData(data) {
    if (lazy.WebReference.isReference(data)) {
      const webRef = lazy.WebReference.fromJSON(data);
      if (webRef instanceof lazy.WebWindow) {
        const browsingContext = BrowsingContext.getCurrentTopByBrowserId(
          webRef.uuid
        );

        webRef.uuid = lazy.TabManager.getIdForBrowsingContext(browsingContext);
        data = webRef.toJSON();
      }
    } else if (typeof data == "object") {
      for (const entry in data) {
        data[entry] = _processData(data[entry]);
      }
    }

    return data;
  }

  return _processData(serializedData);
};

/**
 * Resolve element from specified web reference identifier.
 *
 * @param {BrowsingContext} browsingContext
 *     The browsing context to retrieve the element from.
 * @param {string} nodeId
 *     The WebReference uuid for a DOM element.
 * @param {NodeCache} nodeCache
 *     Node cache that holds already seen WebElement and ShadowRoot references.
 *
 * @returns {Element}
 *     The DOM element that the identifier was generated for.
 *
 * @throws {NoSuchElementError}
 *     If the element doesn't exist in the current browsing context.
 * @throws {StaleElementReferenceError}
 *     If the element has gone stale, indicating its node document is no
 *     longer the active document or it is no longer attached to the DOM.
 */
export function getKnownElement(browsingContext, nodeId, nodeCache) {
  if (!isNodeReferenceKnown(browsingContext, nodeId, nodeCache)) {
    throw new lazy.error.NoSuchElementError(
      `The element with the reference ${nodeId} is not known in the current browsing context`,
      { elementId: nodeId }
    );
  }

  const node = nodeCache.getNode(browsingContext, nodeId);

  // Ensure the node is of the correct Node type.
  if (node !== null && !lazy.dom.isElement(node)) {
    throw new lazy.error.NoSuchElementError(
      `The element with the reference ${nodeId} is not of type HTMLElement`
    );
  }

  // If null, which may be the case if the element has been unwrapped from a
  // weak reference, it is always considered stale.
  if (node === null || lazy.dom.isStale(node)) {
    throw new lazy.error.StaleElementReferenceError(
      `The element with the reference ${nodeId} ` +
        "is stale; either its node document is not the active document, " +
        "or it is no longer connected to the DOM"
    );
  }

  return node;
}

/**
 * Resolve ShadowRoot from specified web reference identifier.
 *
 * @param {BrowsingContext} browsingContext
 *     The browsing context to retrieve the shadow root from.
 * @param {string} nodeId
 *     The WebReference uuid for a ShadowRoot.
 * @param {NodeCache} nodeCache
 *     Node cache that holds already seen WebElement and ShadowRoot references.
 *
 * @returns {ShadowRoot}
 *     The ShadowRoot that the identifier was generated for.
 *
 * @throws {NoSuchShadowRootError}
 *     If the ShadowRoot doesn't exist in the current browsing context.
 * @throws {DetachedShadowRootError}
 *     If the ShadowRoot is detached, indicating its node document is no
 *     longer the active document or it is no longer attached to the DOM.
 */
export function getKnownShadowRoot(browsingContext, nodeId, nodeCache) {
  if (!isNodeReferenceKnown(browsingContext, nodeId, nodeCache)) {
    throw new lazy.error.NoSuchShadowRootError(
      `The shadow root with the reference ${nodeId} is not known in the current browsing context`,
      { shadowId: nodeId }
    );
  }

  const node = nodeCache.getNode(browsingContext, nodeId);

  // Ensure the node is of the correct Node type.
  if (node !== null && !lazy.dom.isShadowRoot(node)) {
    throw new lazy.error.NoSuchShadowRootError(
      `The shadow root with the reference ${nodeId} is not of type ShadowRoot`
    );
  }

  // If null, which may be the case if the element has been unwrapped from a
  // weak reference, it is always considered stale.
  if (node === null || lazy.dom.isDetached(node)) {
    throw new lazy.error.DetachedShadowRootError(
      `The shadow root with the reference ${nodeId} ` +
        "is detached; either its node document is not the active document, " +
        "or it is no longer connected to the DOM"
    );
  }

  return node;
}

/**
 * Determines if the node reference is known for the given browsing context.
 *
 * For WebDriver classic only nodes from the same browsing context are
 * allowed to be accessed.
 *
 * @param {BrowsingContext} browsingContext
 *     The browsing context the element has to be part of.
 * @param {ElementIdentifier} nodeId
 *     The WebElement reference identifier for a DOM element.
 * @param {NodeCache} nodeCache
 *     Node cache that holds already seen node references.
 *
 * @returns {boolean}
 *     True if the element is known in the given browsing context.
 */
function isNodeReferenceKnown(browsingContext, nodeId, nodeCache) {
  const nodeDetails = nodeCache.getReferenceDetails(nodeId);
  if (nodeDetails === null) {
    return false;
  }

  if (nodeDetails.isTopBrowsingContext) {
    // As long as Navigables are not available any cross-group navigation will
    // cause a swap of the current top-level browsing context. The only unique
    // identifier in such a case is the browser id the top-level browsing
    // context actually lives in.
    return nodeDetails.browserId === browsingContext.browserId;
  }

  return nodeDetails.browsingContextId === browsingContext.id;
}
PK
!<#4]��-chrome/remote/content/marionette/l10n.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * An API which allows Marionette to handle localized content.
 *
 * The localization (https://mzl.la/2eUMjyF) of UI elements in Gecko
 * based applications is done via entities and properties. For static
 * values entities are used, which are located in .dtd files. Whereby for
 * dynamically updated content the values come from .property files. Both
 * types of elements can be identifed via a unique id, and the translated
 * content retrieved.
 */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "domParser", () => {
  const parser = new DOMParser();
  parser.forceEnableDTD();
  return parser;
});

/** @namespace */
export const l10n = {};

/**
 * Retrieve the localized string for the specified entity id.
 *
 * Example:
 *     localizeEntity(["chrome://branding/locale/brand.dtd"], "brandShortName")
 *
 * @param {Array.<string>} urls
 *     Array of .dtd URLs.
 * @param {string} id
 *     The ID of the entity to retrieve the localized string for.
 *
 * @returns {string}
 *     The localized string for the requested entity.
 */
l10n.localizeEntity = function (urls, id) {
  // Build a string which contains all possible entity locations
  let locations = [];
  urls.forEach((url, index) => {
    locations.push(`<!ENTITY % dtd_${index} SYSTEM "${url}">%dtd_${index};`);
  });

  // Use the DOM parser to resolve the entity and extract its real value
  let header = `<?xml version="1.0"?><!DOCTYPE elem [${locations.join("")}]>`;
  let elem = `<elem id="elementID">&${id};</elem>`;
  let doc = lazy.domParser.parseFromString(header + elem, "text/xml");
  let element = doc.querySelector("elem[id='elementID']");

  if (element === null) {
    throw new lazy.error.NoSuchElementError(
      `Entity with id='${id}' hasn't been found`
    );
  }

  return element.textContent;
};

/**
 * Retrieve the localized string for the specified property id.
 *
 * Example:
 *
 *     localizeProperty(
 *         ["chrome://global/locale/findbar.properties"], "FastFind");
 *
 * @param {Array.<string>} urls
 *     Array of .properties URLs.
 * @param {string} id
 *     The ID of the property to retrieve the localized string for.
 *
 * @returns {string}
 *     The localized string for the requested property.
 */
l10n.localizeProperty = function (urls, id) {
  let property = null;

  for (let url of urls) {
    let bundle = Services.strings.createBundle(url);
    try {
      property = bundle.GetStringFromName(id);
      break;
    } catch (e) {}
  }

  if (property === null) {
    throw new lazy.error.NoSuchElementError(
      `Property with ID '${id}' hasn't been found`
    );
  }

  return property;
};
PK
!<n��t"t"0chrome/remote/content/marionette/message.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  truncate: "chrome://remote/content/shared/Format.sys.mjs",
});

/** Representation of the packets transproted over the wire. */
export class Message {
  /**
   * @param {number} messageID
   *     Message ID unique identifying this message.
   */
  constructor(messageID) {
    this.id = lazy.assert.integer(messageID);
  }

  toString() {
    function replacer(key, value) {
      if (typeof value === "string") {
        return lazy.truncate`${value}`;
      }
      return value;
    }

    return JSON.stringify(this.toPacket(), replacer);
  }

  /**
   * Converts a data packet into a {@link Command} or {@link Response}.
   *
   * @param {Array.<number, number, ?, ?>} data
   *     A four element array where the elements, in sequence, signifies
   *     message type, message ID, method name or error, and parameters
   *     or result.
   *
   * @returns {Message}
   *     Based on the message type, a {@link Command} or {@link Response}
   *     instance.
   *
   * @throws {TypeError}
   *     If the message type is not recognised.
   */
  static fromPacket(data) {
    const [type] = data;

    switch (type) {
      case Command.Type:
        return Command.fromPacket(data);

      case Response.Type:
        return Response.fromPacket(data);

      default:
        throw new TypeError(
          "Unrecognised message type in packet: " + JSON.stringify(data)
        );
    }
  }
}

/**
 * Messages may originate from either the server or the client.
 * Because the remote protocol is full duplex, both endpoints may be
 * the origin of both commands and responses.
 *
 * @enum
 * @see {@link Message}
 */
Message.Origin = {
  /** Indicates that the message originates from the client. */
  Client: 0,
  /** Indicates that the message originates from the server. */
  Server: 1,
};

/**
 * A command is a request from the client to run a series of remote end
 * steps and return a fitting response.
 *
 * The command can be synthesised from the message passed over the
 * Marionette socket using the {@link fromPacket} function.  The format of
 * a message is:
 *
 * <pre>
 *     [<var>type</var>, <var>id</var>, <var>name</var>, <var>params</var>]
 * </pre>
 *
 * where
 *
 * <dl>
 *   <dt><var>type</var> (integer)
 *   <dd>
 *     Must be zero (integer).  Zero means that this message is
 *     a command.
 *
 *   <dt><var>id</var> (integer)
 *   <dd>
 *     Integer used as a sequence number.  The server replies with
 *     the same ID for the response.
 *
 *   <dt><var>name</var> (string)
 *   <dd>
 *     String representing the command name with an associated set
 *     of remote end steps.
 *
 *   <dt><var>params</var> (JSON Object or null)
 *   <dd>
 *     Object of command function arguments.  The keys of this object
 *     must be strings, but the values can be arbitrary values.
 * </dl>
 *
 * A command has an associated message <var>id</var> that prevents
 * the dispatcher from sending responses in the wrong order.
 *
 * The command may also have optional error- and result handlers that
 * are called when the client returns with a response.  These are
 * <code>function onerror({Object})</code>,
 * <code>function onresult({Object})</code>, and
 * <code>function onresult({Response})</code>:
 *
 * @param {number} messageID
 *     Message ID unique identifying this message.
 * @param {string} name
 *     Command name.
 * @param {Record<string, ?>} params
 *     Command parameters.
 */
export class Command extends Message {
  constructor(messageID, name, params = {}) {
    super(messageID);

    this.name = lazy.assert.string(name);
    this.parameters = lazy.assert.object(params);

    this.onerror = null;
    this.onresult = null;

    this.origin = Message.Origin.Client;
    this.sent = false;
  }

  /**
   * Calls the error- or result handler associated with this command.
   * This function can be replaced with a custom response handler.
   *
   * @param {Response} resp
   *     The response to pass on to the result or error to the
   *     <code>onerror</code> or <code>onresult</code> handlers to.
   */
  onresponse(resp) {
    if (this.onerror && resp.error) {
      this.onerror(resp.error);
    } else if (this.onresult && resp.body) {
      this.onresult(resp.body);
    }
  }

  /**
   * Encodes the command to a packet.
   *
   * @returns {Array}
   *     Packet.
   */
  toPacket() {
    return [Command.Type, this.id, this.name, this.parameters];
  }

  /**
   * Converts a data packet into {@link Command}.
   *
   * @param {Array.<number, number, *, *>} payload
   *     A four element array where the elements, in sequence, signifies
   *     message type, message ID, command name, and parameters.
   *
   * @returns {Command}
   *     Representation of packet.
   *
   * @throws {TypeError}
   *     If the message type is not recognised.
   */
  static fromPacket(payload) {
    let [type, msgID, name, params] = payload;
    lazy.assert.that(n => n === Command.Type)(type);

    // if parameters are given but null, treat them as undefined
    if (params === null) {
      params = undefined;
    }

    return new Command(msgID, name, params);
  }
}

Command.Type = 0;

/**
 * @callback ResponseCallback
 *
 * @param {Response} resp
 *     Response to handle.
 */

/**
 * Represents the response returned from the remote end after execution
 * of its corresponding command.
 *
 * The response is a mutable object passed to each command for
 * modification through the available setters.  To send data in a response,
 * you modify the body property on the response.  The body property can
 * also be replaced completely.
 *
 * The response is sent implicitly by
 * {@link server.TCPConnection#execute when a command has finished
 * executing, and any modifications made subsequent to that will have
 * no effect.
 *
 * @param {number} messageID
 *     Message ID tied to the corresponding command request this is
 *     a response for.
 * @param {ResponseHandler} respHandler
 *     Function callback called on sending the response.
 */
export class Response extends Message {
  constructor(messageID, respHandler = () => {}) {
    super(messageID);

    this.respHandler_ = lazy.assert.callable(respHandler);

    this.error = null;
    this.body = { value: null };

    this.origin = Message.Origin.Server;
    this.sent = false;
  }

  /**
   * Sends response conditionally, given a predicate.
   *
   * @param {function(Response): boolean} predicate
   *     A predicate taking a Response object and returning a boolean.
   */
  sendConditionally(predicate) {
    if (predicate(this)) {
      this.send();
    }
  }

  /**
   * Sends response using the response handler provided on
   * construction.
   *
   * @throws {RangeError}
   *     If the response has already been sent.
   */
  send() {
    if (this.sent) {
      throw new RangeError("Response has already been sent: " + this);
    }
    this.respHandler_(this);
    this.sent = true;
  }

  /**
   * Send error to client.
   *
   * Turns the response into an error response, clears any previously
   * set body data, and sends it using the response handler provided
   * on construction.
   *
   * @param {Error} err
   *     The Error instance to send.
   *
   * @throws {Error}
   *     If <var>err</var> is not a {@link WebDriverError}, the error
   *     is propagated, i.e. rethrown.
   */
  sendError(err) {
    this.error = lazy.error.wrap(err).toJSON();
    this.body = null;
    this.send();

    // propagate errors which are implementation problems
    if (!lazy.error.isWebDriverError(err)) {
      throw err;
    }
  }

  /**
   * Encodes the response to a packet.
   *
   * @returns {Array}
   *     Packet.
   */
  toPacket() {
    return [Response.Type, this.id, this.error, this.body];
  }

  /**
   * Converts a data packet into {@link Response}.
   *
   * @param {Array.<number, number, ?, ?>} payload
   *     A four element array where the elements, in sequence, signifies
   *     message type, message ID, error, and result.
   *
   * @returns {Response}
   *     Representation of packet.
   *
   * @throws {TypeError}
   *     If the message type is not recognised.
   */
  static fromPacket(payload) {
    let [type, msgID, err, body] = payload;
    lazy.assert.that(n => n === Response.Type)(type);

    let resp = new Response(msgID);
    resp.error = lazy.assert.string(err);

    resp.body = body;
    return resp;
  }
}

Response.Type = 1;
PK
!<���6�61chrome/remote/content/marionette/navigate.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  EventDispatcher:
    "chrome://remote/content/marionette/actors/MarionetteEventsParent.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  PageLoadStrategy:
    "chrome://remote/content/shared/webdriver/Capabilities.sys.mjs",
  ProgressListener: "chrome://remote/content/shared/Navigate.sys.mjs",
  TimedPromise: "chrome://remote/content/marionette/sync.sys.mjs",
  truncate: "chrome://remote/content/shared/Format.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
);

// Timeouts used to check if a new navigation has been initiated.
const TIMEOUT_BEFOREUNLOAD_EVENT = 200;
const TIMEOUT_UNLOAD_EVENT = 5000;

/** @namespace */
export const navigate = {};

/**
 * Checks the value of readyState for the current page
 * load activity, and resolves the command if the load
 * has been finished. It also takes care of the selected
 * page load strategy.
 *
 * @param {PageLoadStrategy} pageLoadStrategy
 *     Strategy when navigation is considered as finished.
 * @param {object} eventData
 * @param {string} eventData.documentURI
 *     Current document URI of the document.
 * @param {string} eventData.readyState
 *     Current ready state of the document.
 *
 * @returns {boolean}
 *     True if the page load has been finished.
 */
function checkReadyState(pageLoadStrategy, eventData = {}) {
  const { documentURI, readyState } = eventData;

  const result = { error: null, finished: false };

  switch (readyState) {
    case "interactive":
      if (documentURI.startsWith("about:certerror")) {
        result.error = new lazy.error.InsecureCertificateError();
        result.finished = true;
      } else if (/about:.*(error)\?/.exec(documentURI)) {
        result.error = new lazy.error.UnknownError(
          `Reached error page: ${documentURI}`
        );
        result.finished = true;

        // Return early with a page load strategy of eager, and also
        // special-case about:blocked pages which should be treated as
        // non-error pages but do not raise a pageshow event. about:blank
        // is also treaded specifically here, because it gets temporary
        // loaded for new content processes, and we only want to rely on
        // complete loads for it.
      } else if (
        (pageLoadStrategy === lazy.PageLoadStrategy.Eager &&
          documentURI != "about:blank") ||
        /about:blocked\?/.exec(documentURI)
      ) {
        result.finished = true;
      }
      break;

    case "complete":
      result.finished = true;
      break;
  }

  return result;
}

/**
 * Determines if we expect to get a DOM load event (DOMContentLoaded)
 * on navigating to the <code>future</code> URL.
 *
 * @param {URL} current
 *     URL the browser is currently visiting.
 * @param {object} options
 * @param {BrowsingContext=} options.browsingContext
 *     The current browsing context. Needed for targets of _parent and _top.
 * @param {URL=} options.future
 *     Destination URL, if known.
 * @param {target=} options.target
 *     Link target, if known.
 *
 * @returns {boolean}
 *     Full page load would be expected if future is followed.
 *
 * @throws TypeError
 *     If <code>current</code> is not defined, or any of
 *     <code>current</code> or <code>future</code>  are invalid URLs.
 */
navigate.isLoadEventExpected = function (current, options = {}) {
  const { browsingContext, future, target } = options;

  if (typeof current == "undefined") {
    throw new TypeError("Expected at least one URL");
  }

  if (["_parent", "_top"].includes(target) && !browsingContext) {
    throw new TypeError(
      "Expected browsingContext when target is _parent or _top"
    );
  }

  // Don't wait if the navigation happens in a different browsing context
  if (
    target === "_blank" ||
    (target === "_parent" && browsingContext.parent) ||
    (target === "_top" && browsingContext.top != browsingContext)
  ) {
    return false;
  }

  // Assume we will go somewhere exciting
  if (typeof future == "undefined") {
    return true;
  }

  // Assume javascript:<whatever> will modify the current document
  // but this is not an entirely safe assumption to make,
  // considering it could be used to set window.location
  if (future.protocol == "javascript:") {
    return false;
  }

  // If hashes are present and identical
  if (
    current.href.includes("#") &&
    future.href.includes("#") &&
    current.hash === future.hash
  ) {
    return false;
  }

  return true;
};

/**
 * Load the given URL in the specified browsing context.
 *
 * @param {CanonicalBrowsingContext} browsingContext
 *     Browsing context to load the URL into.
 * @param {string} url
 *     URL to navigate to.
 */
navigate.navigateTo = async function (browsingContext, url) {
  const opts = {
    loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_IS_LINK,
    triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
    // Fake user activation.
    hasValidUserGestureActivation: true,
  };
  browsingContext.fixupAndLoadURIString(url, opts);
};

/**
 * Reload the page.
 *
 * @param {CanonicalBrowsingContext} browsingContext
 *     Browsing context to refresh.
 */
navigate.refresh = async function (browsingContext) {
  const flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
  browsingContext.reload(flags);
};

/**
 * Execute a callback and wait for a possible navigation to complete
 *
 * @param {GeckoDriver} driver
 *     Reference to driver instance.
 * @param {Function} callback
 *     Callback to execute that might trigger a navigation.
 * @param {object} options
 * @param {BrowsingContext=} options.browsingContext
 *     Browsing context to observe. Defaults to the current browsing context.
 * @param {boolean=} options.loadEventExpected
 *     If false, return immediately and don't wait for
 *     the navigation to be completed. Defaults to true.
 * @param {boolean=} options.requireBeforeUnload
 *     If false and no beforeunload event is fired, abort waiting
 *     for the navigation. Defaults to true.
 */
navigate.waitForNavigationCompleted = async function waitForNavigationCompleted(
  driver,
  callback,
  options = {}
) {
  const {
    browsingContextFn = driver.getBrowsingContext.bind(driver),
    loadEventExpected = true,
    requireBeforeUnload = true,
  } = options;

  const browsingContext = browsingContextFn();
  const chromeWindow = browsingContext.topChromeWindow;
  const pageLoadStrategy = driver.currentSession.pageLoadStrategy;

  // Return immediately if no load event is expected
  if (!loadEventExpected) {
    await callback();
    return Promise.resolve();
  }

  // When not waiting for page load events, do not return until the navigation has actually started.
  if (pageLoadStrategy === lazy.PageLoadStrategy.None) {
    const listener = new lazy.ProgressListener(browsingContext.webProgress, {
      resolveWhenStarted: true,
      waitForExplicitStart: true,
    });
    const navigated = listener.start();
    navigated.finally(() => {
      if (listener.isStarted) {
        listener.stop();
      }
    });

    await callback();
    await navigated;

    return Promise.resolve();
  }

  let rejectNavigation;
  let resolveNavigation;

  let browsingContextChanged = false;
  let seenBeforeUnload = false;
  let seenUnload = false;

  let unloadTimer;

  const checkDone = ({ finished, error }) => {
    if (finished) {
      if (error) {
        rejectNavigation(error);
      } else {
        resolveNavigation();
      }
    }
  };

  const onPromptClosed = (_, data) => {
    if (data.detail.promptType === "beforeunload" && !data.detail.accepted) {
      // If a beforeunload prompt is dismissed there will be no navigation.
      lazy.logger.trace(
        `Canceled page load listener because a beforeunload prompt was dismissed`
      );
      checkDone({ finished: true });
    }
  };

  const onPromptOpened = (_, data) => {
    if (data.prompt.promptType === "beforeunload") {
      // WebDriver HTTP basically doesn't know anything about beforeunload
      // prompts. As such we always ignore the prompt opened event.
      return;
    }

    lazy.logger.trace(
      `Canceled page load listener because a ${data.prompt.promptType} prompt opened`
    );
    checkDone({ finished: true });
  };

  const onTimer = () => {
    // For the command "Element Click" we want to detect a potential navigation
    // as early as possible. The `beforeunload` event is an indication for that
    // but could still cause the navigation to get aborted by the user. As such
    // wait a bit longer for the `unload` event to happen, which usually will
    // occur pretty soon after `beforeunload`.
    //
    // Note that with WebDriver BiDi enabled the `beforeunload` prompts might
    // not get implicitly accepted, so lets keep the timer around until we know
    // that it is really not required.
    if (seenBeforeUnload) {
      seenBeforeUnload = false;
      unloadTimer.initWithCallback(
        onTimer,
        TIMEOUT_UNLOAD_EVENT,
        Ci.nsITimer.TYPE_ONE_SHOT
      );

      // If no page unload has been detected, ensure to properly stop
      // the load listener, and return from the currently active command.
    } else if (!seenUnload) {
      lazy.logger.trace(
        "Canceled page load listener because no navigation " +
          "has been detected"
      );
      checkDone({ finished: true });
    }
  };

  const onNavigation = (eventName, data) => {
    const browsingContext = browsingContextFn();

    // Ignore events from other browsing contexts than the selected one.
    if (data.browsingContext != browsingContext) {
      return;
    }

    lazy.logger.trace(
      lazy.truncate`[${data.browsingContext.id}] Received event ${data.type} for ${data.documentURI}`
    );

    switch (data.type) {
      case "beforeunload":
        seenBeforeUnload = true;
        break;

      case "pagehide":
        seenUnload = true;
        break;

      case "hashchange":
      case "popstate":
        checkDone({ finished: true });
        break;

      case "DOMContentLoaded":
      case "pageshow": {
        // Don't require an unload event when a top-level browsing context
        // change occurred.
        if (!seenUnload && !browsingContextChanged) {
          return;
        }
        const result = checkReadyState(pageLoadStrategy, data);
        checkDone(result);
        break;
      }
    }
  };

  // In the case when the currently selected frame is closed,
  // there will be no further load events. Stop listening immediately.
  const onBrowsingContextDiscarded = (subject, topic, why) => {
    // If the BrowsingContext is being discarded to be replaced by another
    // context, we don't want to stop waiting for the pageload to complete, as
    // we will continue listening to the newly created context.
    if (subject == browsingContextFn() && why != "replace") {
      lazy.logger.trace(
        "Canceled page load listener " +
          `because browsing context with id ${subject.id} has been removed`
      );
      checkDone({ finished: true });
    }
  };

  // Detect changes to the top-level browsing context to not
  // necessarily require an unload event.
  const onBrowsingContextChanged = event => {
    if (event.target === driver.curBrowser.contentBrowser) {
      browsingContextChanged = true;
    }
  };

  const onUnload = () => {
    lazy.logger.trace(
      "Canceled page load listener " +
        "because the top-browsing context has been closed"
    );
    checkDone({ finished: true });
  };

  chromeWindow.addEventListener("TabClose", onUnload);
  chromeWindow.addEventListener("unload", onUnload);
  driver.curBrowser.tabBrowser?.addEventListener(
    "XULFrameLoaderCreated",
    onBrowsingContextChanged
  );
  driver.promptListener.on("closed", onPromptClosed);
  driver.promptListener.on("opened", onPromptOpened);
  Services.obs.addObserver(
    onBrowsingContextDiscarded,
    "browsing-context-discarded"
  );

  lazy.EventDispatcher.on("page-load", onNavigation);

  return new lazy.TimedPromise(
    async (resolve, reject) => {
      rejectNavigation = reject;
      resolveNavigation = resolve;

      try {
        await callback();

        // Certain commands like clickElement can cause a navigation. Setup a timer
        // to check if a "beforeunload" event has been emitted within the given
        // time frame. If not resolve the Promise.
        if (!requireBeforeUnload) {
          unloadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
          unloadTimer.initWithCallback(
            onTimer,
            TIMEOUT_BEFOREUNLOAD_EVENT,
            Ci.nsITimer.TYPE_ONE_SHOT
          );
        }
      } catch (e) {
        // Executing the callback above could destroy the actor pair before the
        // command returns. Such an error has to be ignored.
        if (e.name !== "AbortError") {
          checkDone({ finished: true, error: e });
        }
      }
    },
    {
      errorMessage: "Navigation timed out",
      timeout: driver.currentSession.timeouts.pageLoad,
    }
  ).finally(() => {
    // Clean-up all registered listeners and timers
    Services.obs.removeObserver(
      onBrowsingContextDiscarded,
      "browsing-context-discarded"
    );
    chromeWindow.removeEventListener("TabClose", onUnload);
    chromeWindow.removeEventListener("unload", onUnload);
    driver.curBrowser.tabBrowser?.removeEventListener(
      "XULFrameLoaderCreated",
      onBrowsingContextChanged
    );
    driver.promptListener?.off("closed", onPromptClosed);
    driver.promptListener?.off("opened", onPromptOpened);
    unloadTimer?.cancel();

    lazy.EventDispatcher.off("page-load", onNavigation);
  });
};
PK
!<�`��+�+0chrome/remote/content/marionette/packets.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  StreamUtils: "chrome://remote/content/marionette/stream-utils.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "unicodeConverter", () => {
  const unicodeConverter = Cc[
    "@mozilla.org/intl/scriptableunicodeconverter"
  ].createInstance(Ci.nsIScriptableUnicodeConverter);
  unicodeConverter.charset = "UTF-8";

  return unicodeConverter;
});

/**
 * Packets contain read / write functionality for the different packet types
 * supported by the debugging protocol, so that a transport can focus on
 * delivery and queue management without worrying too much about the specific
 * packet types.
 *
 * They are intended to be "one use only", so a new packet should be
 * instantiated for each incoming or outgoing packet.
 *
 * A complete Packet type should expose at least the following:
 *   read(stream, scriptableStream)
 *     Called when the input stream has data to read
 *   write(stream)
 *     Called when the output stream is ready to write
 *   get done()
 *     Returns true once the packet is done being read / written
 *   destroy()
 *     Called to clean up at the end of use
 */

const defer = function () {
  let deferred = {
    promise: new Promise((resolve, reject) => {
      deferred.resolve = resolve;
      deferred.reject = reject;
    }),
  };
  return deferred;
};

// The transport's previous check ensured the header length did not
// exceed 20 characters.  Here, we opt for the somewhat smaller, but still
// large limit of 1 TiB.
const PACKET_LENGTH_MAX = Math.pow(2, 40);

/**
 * A generic Packet processing object (extended by two subtypes below).
 *
 * @class
 */
export function Packet(transport) {
  this._transport = transport;
  this._length = 0;
}

/**
 * Attempt to initialize a new Packet based on the incoming packet header
 * we've received so far.  We try each of the types in succession, trying
 * JSON packets first since they are much more common.
 *
 * @param {string} header
 *     Packet header string to attempt parsing.
 * @param {DebuggerTransport} transport
 *     Transport instance that will own the packet.
 *
 * @returns {Packet}
 *     Parsed packet of the matching type, or null if no types matched.
 */
Packet.fromHeader = function (header, transport) {
  return (
    JSONPacket.fromHeader(header, transport) ||
    BulkPacket.fromHeader(header, transport)
  );
};

Packet.prototype = {
  get length() {
    return this._length;
  },

  set length(length) {
    if (length > PACKET_LENGTH_MAX) {
      throw new Error(
        "Packet length " +
          length +
          " exceeds the max length of " +
          PACKET_LENGTH_MAX
      );
    }
    this._length = length;
  },

  destroy() {
    this._transport = null;
  },
};

/**
 * With a JSON packet (the typical packet type sent via the transport),
 * data is transferred as a JSON packet serialized into a string,
 * with the string length prepended to the packet, followed by a colon
 * ([length]:[packet]). The contents of the JSON packet are specified in
 * the Remote Debugging Protocol specification.
 *
 * @param {DebuggerTransport} transport
 *     Transport instance that will own the packet.
 */
export function JSONPacket(transport) {
  Packet.call(this, transport);
  this._data = "";
  this._done = false;
}

/**
 * Attempt to initialize a new JSONPacket based on the incoming packet
 * header we've received so far.
 *
 * @param {string} header
 *     Packet header string to attempt parsing.
 * @param {DebuggerTransport} transport
 *     Transport instance that will own the packet.
 *
 * @returns {JSONPacket}
 *     Parsed packet, or null if it's not a match.
 */
JSONPacket.fromHeader = function (header, transport) {
  let match = this.HEADER_PATTERN.exec(header);

  if (!match) {
    return null;
  }

  let packet = new JSONPacket(transport);
  packet.length = +match[1];
  return packet;
};

JSONPacket.HEADER_PATTERN = /^(\d+):$/;

JSONPacket.prototype = Object.create(Packet.prototype);

Object.defineProperty(JSONPacket.prototype, "object", {
  /**
   * Gets the object (not the serialized string) being read or written.
   */
  get() {
    return this._object;
  },

  /**
   * Sets the object to be sent when write() is called.
   */
  set(object) {
    this._object = object;
    let data = JSON.stringify(object);
    this._data = lazy.unicodeConverter.ConvertFromUnicode(data);
    this.length = this._data.length;
  },
});

JSONPacket.prototype.read = function (stream, scriptableStream) {
  // Read in more packet data.
  this._readData(stream, scriptableStream);

  if (!this.done) {
    // Don't have a complete packet yet.
    return;
  }

  let json = this._data;
  try {
    json = lazy.unicodeConverter.ConvertToUnicode(json);
    this._object = JSON.parse(json);
  } catch (e) {
    let msg =
      "Error parsing incoming packet: " +
      json +
      " (" +
      e +
      " - " +
      e.stack +
      ")";
    console.error(msg);
    dump(msg + "\n");
    return;
  }

  this._transport._onJSONObjectReady(this._object);
};

JSONPacket.prototype._readData = function (stream, scriptableStream) {
  let bytesToRead = Math.min(
    this.length - this._data.length,
    stream.available()
  );
  this._data += scriptableStream.readBytes(bytesToRead);
  this._done = this._data.length === this.length;
};

JSONPacket.prototype.write = function (stream) {
  if (this._outgoing === undefined) {
    // Format the serialized packet to a buffer
    this._outgoing = this.length + ":" + this._data;
  }

  let written = stream.write(this._outgoing, this._outgoing.length);
  this._outgoing = this._outgoing.slice(written);
  this._done = !this._outgoing.length;
};

Object.defineProperty(JSONPacket.prototype, "done", {
  get() {
    return this._done;
  },
});

JSONPacket.prototype.toString = function () {
  return JSON.stringify(this._object, null, 2);
};

/**
 * With a bulk packet, data is transferred by temporarily handing over
 * the transport's input or output stream to the application layer for
 * writing data directly.  This can be much faster for large data sets,
 * and avoids various stages of copies and data duplication inherent in
 * the JSON packet type.  The bulk packet looks like:
 *
 *     bulk [actor] [type] [length]:[data]
 *
 * The interpretation of the data portion depends on the kind of actor and
 * the packet's type.  See the Remote Debugging Protocol Stream Transport
 * spec for more details.
 *
 * @param {DebuggerTransport} transport
 *     Transport instance that will own the packet.
 */
export function BulkPacket(transport) {
  Packet.call(this, transport);
  this._done = false;
  this._readyForWriting = defer();
}

/**
 * Attempt to initialize a new BulkPacket based on the incoming packet
 * header we've received so far.
 *
 * @param {string} header
 *     Packet header string to attempt parsing.
 * @param {DebuggerTransport} transport
 *     Transport instance that will own the packet.
 *
 * @returns {BulkPacket}
 *     Parsed packet, or null if it's not a match.
 */
BulkPacket.fromHeader = function (header, transport) {
  let match = this.HEADER_PATTERN.exec(header);

  if (!match) {
    return null;
  }

  let packet = new BulkPacket(transport);
  packet.header = {
    actor: match[1],
    type: match[2],
    length: +match[3],
  };
  return packet;
};

BulkPacket.HEADER_PATTERN = /^bulk ([^: ]+) ([^: ]+) (\d+):$/;

BulkPacket.prototype = Object.create(Packet.prototype);

BulkPacket.prototype.read = function (stream) {
  // Temporarily pause monitoring of the input stream
  this._transport.pauseIncoming();

  let deferred = defer();

  this._transport._onBulkReadReady({
    actor: this.actor,
    type: this.type,
    length: this.length,
    copyTo: output => {
      let copying = lazy.StreamUtils.copyStream(stream, output, this.length);
      deferred.resolve(copying);
      return copying;
    },
    stream,
    done: deferred,
  });

  // Await the result of reading from the stream
  deferred.promise.then(() => {
    this._done = true;
    this._transport.resumeIncoming();
  }, this._transport.close);

  // Ensure this is only done once
  this.read = () => {
    throw new Error("Tried to read() a BulkPacket's stream multiple times.");
  };
};

BulkPacket.prototype.write = function (stream) {
  if (this._outgoingHeader === undefined) {
    // Format the serialized packet header to a buffer
    this._outgoingHeader =
      "bulk " + this.actor + " " + this.type + " " + this.length + ":";
  }

  // Write the header, or whatever's left of it to write.
  if (this._outgoingHeader.length) {
    let written = stream.write(
      this._outgoingHeader,
      this._outgoingHeader.length
    );
    this._outgoingHeader = this._outgoingHeader.slice(written);
    return;
  }

  // Temporarily pause the monitoring of the output stream
  this._transport.pauseOutgoing();

  let deferred = defer();

  this._readyForWriting.resolve({
    copyFrom: input => {
      let copying = lazy.StreamUtils.copyStream(input, stream, this.length);
      deferred.resolve(copying);
      return copying;
    },
    stream,
    done: deferred,
  });

  // Await the result of writing to the stream
  deferred.promise.then(() => {
    this._done = true;
    this._transport.resumeOutgoing();
  }, this._transport.close);

  // Ensure this is only done once
  this.write = () => {
    throw new Error("Tried to write() a BulkPacket's stream multiple times.");
  };
};

Object.defineProperty(BulkPacket.prototype, "streamReadyForWriting", {
  get() {
    return this._readyForWriting.promise;
  },
});

Object.defineProperty(BulkPacket.prototype, "header", {
  get() {
    return {
      actor: this.actor,
      type: this.type,
      length: this.length,
    };
  },

  set(header) {
    this.actor = header.actor;
    this.type = header.type;
    this.length = header.length;
  },
});

Object.defineProperty(BulkPacket.prototype, "done", {
  get() {
    return this._done;
  },
});

BulkPacket.prototype.toString = function () {
  return "Bulk: " + JSON.stringify(this.header, null, 2);
};

/**
 * RawPacket is used to test the transport's error handling of malformed
 * packets, by writing data directly onto the stream.
 *
 * @param {DebuggerTransport} transport
 *     The transport instance that will own the packet.
 * @param {string} data
 *     The raw string to send out onto the stream.
 */
export function RawPacket(transport, data) {
  Packet.call(this, transport);
  this._data = data;
  this.length = data.length;
  this._done = false;
}

RawPacket.prototype = Object.create(Packet.prototype);

RawPacket.prototype.read = function () {
  // this has not yet been needed for testing
  throw new Error("Not implemented");
};

RawPacket.prototype.write = function (stream) {
  let written = stream.write(this._data, this._data.length);
  this._data = this._data.slice(written);
  this._done = !this._data.length;
};

Object.defineProperty(RawPacket.prototype, "done", {
  get() {
    return this._done;
  },
});
PK
!<d�S�11.chrome/remote/content/marionette/prefs.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const { PREF_BOOL, PREF_INT, PREF_INVALID, PREF_STRING } = Ci.nsIPrefBranch;

export class Branch {
  /**
   * @param {string=} branch
   *     Preference subtree.  Uses root tree given `null`.
   */
  constructor(branch) {
    this._branch = Services.prefs.getBranch(branch);
  }

  /**
   * Gets value of `pref` in its known type.
   *
   * @param {string} pref
   *     Preference name.
   * @param {*=} fallback
   *     Fallback value to return if `pref` does not exist.
   *
   * @returns {(string|boolean|number)}
   *     Value of `pref`, or the `fallback` value if `pref` does
   *     not exist.
   *
   * @throws {TypeError}
   *     If `pref` is not a recognised preference and no `fallback`
   *     value has been provided.
   */
  get(pref, fallback = null) {
    switch (this._branch.getPrefType(pref)) {
      case PREF_STRING:
        return this._branch.getStringPref(pref);

      case PREF_BOOL:
        return this._branch.getBoolPref(pref);

      case PREF_INT:
        return this._branch.getIntPref(pref);

      case PREF_INVALID:
      default:
        if (fallback != null) {
          return fallback;
        }
        throw new TypeError(`Unrecognised preference: ${pref}`);
    }
  }

  /**
   * Sets the value of `pref`.
   *
   * @param {string} pref
   *     Preference name.
   * @param {(string|boolean|number)} value
   *     `pref`'s new value.
   *
   * @throws {TypeError}
   *     If `value` is not the correct type for `pref`.
   */
  set(pref, value) {
    let typ;
    if (typeof value != "undefined" && value != null) {
      typ = value.constructor.name;
    }

    switch (typ) {
      case "String":
        // Unicode compliant
        return this._branch.setStringPref(pref, value);

      case "Boolean":
        return this._branch.setBoolPref(pref, value);

      case "Number":
        return this._branch.setIntPref(pref, value);

      default:
        throw new TypeError(`Illegal preference type value: ${typ}`);
    }
  }
}

/**
 * Provides shortcuts for lazily getting and setting typed Marionette
 * preferences.
 *
 * Some of Marionette's preferences are stored using primitive values
 * that internally are represented by complex types.
 *
 * Because we cannot trust the input of many of these preferences,
 * this class provides abstraction that lets us safely deal with
 * potentially malformed input.
 *
 * A further complication is that we cannot rely on `Preferences.sys.mjs`
 * in Marionette.  See https://bugzilla.mozilla.org/show_bug.cgi?id=1357517
 * for further details.
 */
class MarionetteBranch extends Branch {
  constructor(branch = "marionette.") {
    super(branch);
  }

  /**
   * The `marionette.debugging.clicktostart` preference delays
   * server startup until a modal dialogue has been clicked to allow
   * time for user to set breakpoints in the Browser Toolbox.
   *
   * @returns {boolean}
   */
  get clickToStart() {
    return this.get("debugging.clicktostart", false);
  }

  /**
   * The `marionette.port` preference, detailing which port
   * the TCP server should listen on.
   *
   * @returns {number}
   */
  get port() {
    return this.get("port", 2828);
  }

  set port(newPort) {
    this.set("port", newPort);
  }
}

/** Reads a JSON serialised blob stored in the environment. */
export class EnvironmentPrefs {
  /**
   * Reads the environment variable `key` and tries to parse it as
   * JSON Object, then provides an iterator over its keys and values.
   *
   * If the environment variable is not set, this function returns empty.
   *
   * @param {string} key
   *     Environment variable.
   *
   * @returns {Iterable.<string, (string|boolean|number)>}
   */
  static *from(key) {
    if (!Services.env.exists(key)) {
      return;
    }

    let prefs;
    try {
      prefs = JSON.parse(Services.env.get(key));
    } catch (e) {
      throw new TypeError(`Unable to parse prefs from ${key}`, e);
    }

    for (let prefName of Object.keys(prefs)) {
      yield [prefName, prefs[prefName]];
    }
  }
}

// There is a future potential of exposing this as Marionette.prefs.port
// if we introduce a Marionette.sys.mjs module.
export const MarionettePrefs = new MarionetteBranch();
PK
!<���PDD3chrome/remote/content/marionette/reftest-content.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/frame-script */

"use strict";

const { XPCOMUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/XPCOMUtils.sys.mjs"
);

XPCOMUtils.defineLazyScriptGetter(
  this,
  "PrintUtils",
  "chrome://global/content/printUtils.js"
);

// This is an implementation of nsIBrowserDOMWindow that handles only opening
// print browsers, because the "open a new window fallback" is just too slow
// in some cases and causes timeouts.
function BrowserDOMWindow() {}
BrowserDOMWindow.prototype = {
  QueryInterface: ChromeUtils.generateQI(["nsIBrowserDOMWindow"]),

  _maybeOpen(aOpenWindowInfo, aWhere) {
    if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_PRINT_BROWSER) {
      return PrintUtils.handleStaticCloneCreatedForPrint(aOpenWindowInfo);
    }
    return null;
  },

  createContentWindow(aURI, aOpenWindowInfo, aWhere) {
    return this._maybeOpen(aOpenWindowInfo, aWhere)?.browsingContext;
  },

  openURI(aURI, aOpenWindowInfo, aWhere) {
    return this._maybeOpen(aOpenWindowInfo, aWhere)?.browsingContext;
  },

  createContentWindowInFrame(aURI, aParams, aWhere) {
    return this._maybeOpen(aParams.openWindowInfo, aWhere);
  },

  openURIInFrame(aURI, aParams, aWhere) {
    return this._maybeOpen(aParams.openWindowInfo, aWhere);
  },

  canClose() {
    return true;
  },

  get tabCount() {
    return 1;
  },
};

window.browserDOMWindow = new BrowserDOMWindow();
PK
!<����k�k0chrome/remote/content/marionette/reftest.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
  E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",

  AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  capture: "chrome://remote/content/shared/Capture.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  navigate: "chrome://remote/content/marionette/navigate.sys.mjs",
  print: "chrome://remote/content/shared/PDF.sys.mjs",
  windowManager: "chrome://remote/content/shared/WindowManager.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
);

const XHTML_NS = "http://www.w3.org/1999/xhtml";
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

const SCREENSHOT_MODE = {
  unexpected: 0,
  fail: 1,
  always: 2,
};

const STATUS = {
  PASS: "PASS",
  FAIL: "FAIL",
  ERROR: "ERROR",
  TIMEOUT: "TIMEOUT",
};

const DEFAULT_REFTEST_WIDTH = 600;
const DEFAULT_REFTEST_HEIGHT = 600;

// reftest-print page dimensions in cm
const CM_PER_INCH = 2.54;
const DEFAULT_PAGE_WIDTH = 5 * CM_PER_INCH;
const DEFAULT_PAGE_HEIGHT = 3 * CM_PER_INCH;
const DEFAULT_PAGE_MARGIN = 0.5 * CM_PER_INCH;

// CSS 96 pixels per inch, compared to pdf.js default 72 pixels per inch
const DEFAULT_PDF_RESOLUTION = 96 / 72;

/**
 * Implements an fast runner for web-platform-tests format reftests
 * c.f. http://web-platform-tests.org/writing-tests/reftests.html.
 *
 * @namespace
 */
export const reftest = {};

/**
 * @memberof reftest
 * @class Runner
 */
reftest.Runner = class {
  constructor(driver) {
    this.driver = driver;
    this.canvasCache = new DefaultMap(undefined, () => new Map([[null, []]]));
    this.isPrint = null;
    this.windowUtils = null;
    this.lastURL = null;
    this.useRemoteTabs = lazy.AppInfo.browserTabsRemoteAutostart;
    this.useRemoteSubframes = lazy.AppInfo.fissionAutostart;
  }

  /**
   * Setup the required environment for running reftests.
   *
   * This will open a non-browser window in which the tests will
   * be loaded, and set up various caches for the reftest run.
   *
   * @param {Record<string, number>} urlCount
   *     Object holding a map of URL: number of times the URL
   *     will be opened during the reftest run, where that's
   *     greater than 1.
   * @param {string} screenshotMode
   *     String enum representing when screenshots should be taken
   */
  setup(urlCount, screenshotMode, isPrint = false) {
    this.isPrint = isPrint;

    lazy.assert.open(this.driver.getBrowsingContext({ top: true }));
    this.parentWindow = this.driver.getCurrentWindow();

    this.screenshotMode =
      SCREENSHOT_MODE[screenshotMode] || SCREENSHOT_MODE.unexpected;

    this.urlCount = Object.keys(urlCount || {}).reduce(
      (map, key) => map.set(key, urlCount[key]),
      new Map()
    );

    if (isPrint) {
      this.loadPdfJs();
    }

    ChromeUtils.registerWindowActor("MarionetteReftest", {
      kind: "JSWindowActor",
      parent: {
        esModuleURI:
          "chrome://remote/content/marionette/actors/MarionetteReftestParent.sys.mjs",
      },
      child: {
        esModuleURI:
          "chrome://remote/content/marionette/actors/MarionetteReftestChild.sys.mjs",
        events: {
          load: { mozSystemGroup: true, capture: true },
        },
      },
      allFrames: true,
    });
  }

  /**
   * Cleanup the environment once the reftest is finished.
   */
  teardown() {
    // Abort the current test if any.
    this.abort();

    // Unregister the JSWindowActors.
    ChromeUtils.unregisterWindowActor("MarionetteReftest");
  }

  async ensureWindow(timeout, width, height) {
    lazy.logger.debug(`ensuring we have a window ${width}x${height}`);

    if (this.reftestWin && !this.reftestWin.closed) {
      let browserRect = this.reftestWin.gBrowser.getBoundingClientRect();
      if (browserRect.width === width && browserRect.height === height) {
        return this.reftestWin;
      }
      lazy.logger.debug(`current: ${browserRect.width}x${browserRect.height}`);
    }

    let reftestWin;
    if (lazy.AppInfo.isAndroid) {
      lazy.logger.debug("Using current window");
      reftestWin = this.parentWindow;
      await lazy.navigate.waitForNavigationCompleted(this.driver, () => {
        const browsingContext = this.driver.getBrowsingContext();
        lazy.navigate.navigateTo(browsingContext, "about:blank");
      });
    } else {
      lazy.logger.debug("Using separate window");
      if (this.reftestWin && !this.reftestWin.closed) {
        this.reftestWin.close();
      }
      reftestWin = await this.openWindow(width, height);
    }

    this.setupWindow(reftestWin, width, height);
    this.windowUtils = reftestWin.windowUtils;
    this.reftestWin = reftestWin;

    let windowHandle = lazy.windowManager.getWindowProperties(reftestWin);
    await this.driver.setWindowHandle(windowHandle, true);

    const url = await this.driver._getCurrentURL();
    this.lastURL = url.href;
    lazy.logger.debug(`loaded initial URL: ${this.lastURL}`);

    let browserRect = reftestWin.gBrowser.getBoundingClientRect();
    lazy.logger.debug(`new: ${browserRect.width}x${browserRect.height}`);

    return reftestWin;
  }

  async openWindow(width, height) {
    lazy.assert.positiveInteger(width);
    lazy.assert.positiveInteger(height);

    let reftestWin = this.parentWindow.open(
      "chrome://remote/content/marionette/reftest.xhtml",
      "reftest",
      `chrome,height=${height},width=${width}`
    );

    await new Promise(resolve => {
      reftestWin.addEventListener("load", resolve, { once: true });
    });
    return reftestWin;
  }

  setupWindow(reftestWin, width, height) {
    let browser;
    if (lazy.AppInfo.isAndroid) {
      browser = reftestWin.document.getElementsByTagName("browser")[0];
      browser.setAttribute("remote", "false");
    } else {
      browser = reftestWin.document.createElementNS(XUL_NS, "xul:browser");
      browser.permanentKey = {};
      browser.setAttribute("id", "browser");
      browser.setAttribute("type", "content");
      browser.setAttribute("primary", "true");
      browser.setAttribute("remote", this.useRemoteTabs ? "true" : "false");
    }
    // Make sure the browser element is exactly the right size, no matter
    // what size our window is
    const windowStyle = `
      padding: 0px;
      margin: 0px;
      border:none;
      min-width: ${width}px; min-height: ${height}px;
      max-width: ${width}px; max-height: ${height}px;
      color-scheme: env(-moz-content-preferred-color-scheme);
    `;
    browser.setAttribute("style", windowStyle);

    if (!lazy.AppInfo.isAndroid) {
      let doc = reftestWin.document.documentElement;
      while (doc.firstChild) {
        doc.firstChild.remove();
      }
      doc.appendChild(browser);
    }
    if (reftestWin.BrowserApp) {
      reftestWin.BrowserApp = browser;
    }
    reftestWin.gBrowser = browser;
    return reftestWin;
  }

  async abort() {
    if (this.reftestWin && this.reftestWin != this.parentWindow) {
      await this.driver.closeChromeWindow();
      let parentHandle = lazy.windowManager.getWindowProperties(
        this.parentWindow
      );
      await this.driver.setWindowHandle(parentHandle);
    }
    this.reftestWin = null;
  }

  /**
   * Run a specific reftest.
   *
   * The assumed semantics are those of web-platform-tests where
   * references form a tree and each test must meet all the conditions
   * to reach one leaf node of the tree in order for the overall test
   * to pass.
   *
   * @param {string} testUrl
   *     URL of the test itself.
   * @param {Array.<Array>} references
   *     Array representing a tree of references to try.
   *
   *     Each item in the array represents a single reference node and
   *     has the form <code>[referenceUrl, references, relation]</code>,
   *     where <var>referenceUrl</var> is a string to the URL, relation
   *     is either <code>==</code> or <code>!=</code> depending on the
   *     type of reftest, and references is another array containing
   *     items of the same form, representing further comparisons treated
   *     as AND with the current item. Sibling entries are treated as OR.
   *
   *     For example with testUrl of T:
   *
   *     <pre><code>
   *       references = [[A, [[B, [], ==]], ==]]
   *       Must have T == A AND A == B to pass
   *
   *       references = [[A, [], ==], [B, [], !=]
   *       Must have T == A OR T != B
   *
   *       references = [[A, [[B, [], ==], [C, [], ==]], ==], [D, [], ]]
   *       Must have (T == A AND A == B) OR (T == A AND A == C) OR (T == D)
   *     </code></pre>
   *
   * @param {string} expected
   *     Expected test outcome (e.g. <tt>PASS</tt>, <tt>FAIL</tt>).
   * @param {number} timeout
   *     Test timeout in milliseconds.
   *
   * @returns {object}
   *     Result object with fields status, message and extra.
   */
  async run(
    testUrl,
    references,
    expected,
    timeout,
    pageRanges = {},
    width = DEFAULT_REFTEST_WIDTH,
    height = DEFAULT_REFTEST_HEIGHT
  ) {
    let timerId;

    let timeoutPromise = new Promise(resolve => {
      timerId = lazy.setTimeout(() => {
        resolve({ status: STATUS.TIMEOUT, message: null, extra: {} });
      }, timeout);
    });

    let testRunner = (async () => {
      let result;
      try {
        result = await this.runTest(
          testUrl,
          references,
          expected,
          timeout,
          pageRanges,
          width,
          height
        );
      } catch (e) {
        result = {
          status: STATUS.ERROR,
          message: String(e),
          stack: e.stack,
          extra: {},
        };
      }
      return result;
    })();

    let result = await Promise.race([testRunner, timeoutPromise]);
    lazy.clearTimeout(timerId);
    if (result.status === STATUS.TIMEOUT) {
      await this.abort();
    }

    return result;
  }

  async runTest(
    testUrl,
    references,
    expected,
    timeout,
    pageRanges,
    width,
    height
  ) {
    let win = await this.ensureWindow(timeout, width, height);

    function toBase64(screenshot) {
      let dataURL = screenshot.canvas.toDataURL();
      return dataURL.split(",")[1];
    }

    let result = {
      status: STATUS.FAIL,
      message: "",
      stack: null,
      extra: {},
    };

    let screenshotData = [];

    let stack = [];
    for (let i = references.length - 1; i >= 0; i--) {
      let item = references[i];
      stack.push([testUrl, ...item]);
    }

    let done = false;

    while (stack.length && !done) {
      let [lhsUrl, rhsUrl, references, relation, extras = {}] = stack.pop();
      result.message += `Testing ${lhsUrl} ${relation} ${rhsUrl}\n`;

      let comparison;
      try {
        comparison = await this.compareUrls(
          win,
          lhsUrl,
          rhsUrl,
          relation,
          timeout,
          pageRanges,
          extras
        );
      } catch (e) {
        comparison = {
          lhs: null,
          rhs: null,
          passed: false,
          error: e,
          msg: null,
        };
      }
      if (comparison.msg) {
        result.message += `${comparison.msg}\n`;
      }
      if (comparison.error !== null) {
        result.status = STATUS.ERROR;
        result.message += String(comparison.error);
        result.stack = comparison.error.stack;
      }

      function recordScreenshot() {
        let encodedLHS = comparison.lhs ? toBase64(comparison.lhs) : "";
        let encodedRHS = comparison.rhs ? toBase64(comparison.rhs) : "";
        screenshotData.push([
          { url: lhsUrl, screenshot: encodedLHS },
          relation,
          { url: rhsUrl, screenshot: encodedRHS },
        ]);
      }

      if (this.screenshotMode === SCREENSHOT_MODE.always) {
        recordScreenshot();
      }

      if (comparison.passed) {
        if (references.length) {
          for (let i = references.length - 1; i >= 0; i--) {
            let item = references[i];
            stack.push([rhsUrl, ...item]);
          }
        } else {
          // Reached a leaf node so all of one reference chain passed
          result.status = STATUS.PASS;
          if (
            this.screenshotMode <= SCREENSHOT_MODE.fail &&
            expected != result.status
          ) {
            recordScreenshot();
          }
          done = true;
        }
      } else if (!stack.length || result.status == STATUS.ERROR) {
        // If we don't have any alternatives to try then this will be
        // the last iteration, so save the failing screenshots if required.
        let isFail = this.screenshotMode === SCREENSHOT_MODE.fail;
        let isUnexpected = this.screenshotMode === SCREENSHOT_MODE.unexpected;
        if (isFail || (isUnexpected && expected != result.status)) {
          recordScreenshot();
        }
      }

      // Return any reusable canvases to the pool
      let cacheKey = width + "x" + height;
      let canvasPool = this.canvasCache.get(cacheKey).get(null);
      [comparison.lhs, comparison.rhs].map(screenshot => {
        if (screenshot !== null && screenshot.reuseCanvas) {
          canvasPool.push(screenshot.canvas);
        }
      });
      lazy.logger.debug(
        `Canvas pool (${cacheKey}) is of length ${canvasPool.length}`
      );
    }

    if (screenshotData.length) {
      // For now the tbpl formatter only accepts one screenshot, so just
      // return the last one we took.
      let lastScreenshot = screenshotData[screenshotData.length - 1];
      // eslint-disable-next-line camelcase
      result.extra.reftest_screenshots = lastScreenshot;
    }

    return result;
  }

  async compareUrls(
    win,
    lhsUrl,
    rhsUrl,
    relation,
    timeout,
    pageRanges,
    extras
  ) {
    lazy.logger.info(`Testing ${lhsUrl} ${relation} ${rhsUrl}`);

    if (relation !== "==" && relation != "!=") {
      throw new error.InvalidArgumentError(
        "Reftest operator should be '==' or '!='"
      );
    }

    let lhsIter, lhsCount, rhsIter, rhsCount;
    if (!this.isPrint) {
      // Take the reference screenshot first so that if we pause
      // we see the test rendering
      rhsIter = [await this.screenshot(win, rhsUrl, timeout)].values();
      lhsIter = [await this.screenshot(win, lhsUrl, timeout)].values();
      lhsCount = rhsCount = 1;
    } else {
      [rhsIter, rhsCount] = await this.screenshotPaginated(
        win,
        rhsUrl,
        timeout,
        pageRanges
      );
      [lhsIter, lhsCount] = await this.screenshotPaginated(
        win,
        lhsUrl,
        timeout,
        pageRanges
      );
    }

    let passed = null;
    let error = null;
    let pixelsDifferent = null;
    let maxDifferences = {};
    let msg = null;

    if (lhsCount != rhsCount) {
      passed = relation == "!=";
      if (!passed) {
        msg = `Got different numbers of pages; test has ${lhsCount}, ref has ${rhsCount}`;
      }
    }

    let lhs = null;
    let rhs = null;
    lazy.logger.debug(`Comparing ${lhsCount} pages`);
    if (passed === null) {
      for (let i = 0; i < lhsCount; i++) {
        lhs = (await lhsIter.next()).value;
        rhs = (await rhsIter.next()).value;
        lazy.logger.debug(
          `lhs canvas size ${lhs.canvas.width}x${lhs.canvas.height}`
        );
        lazy.logger.debug(
          `rhs canvas size ${rhs.canvas.width}x${rhs.canvas.height}`
        );
        if (
          lhs.canvas.width != rhs.canvas.width ||
          lhs.canvas.height != rhs.canvas.height
        ) {
          msg =
            `Got different page sizes; test is ` +
            `${lhs.canvas.width}x${lhs.canvas.height}px, ref is ` +
            `${rhs.canvas.width}x${rhs.canvas.height}px`;
          passed = false;
          break;
        }
        try {
          pixelsDifferent = this.windowUtils.compareCanvases(
            lhs.canvas,
            rhs.canvas,
            maxDifferences
          );
        } catch (e) {
          error = e;
          passed = false;
          break;
        }

        let areEqual = this.isAcceptableDifference(
          maxDifferences.value,
          pixelsDifferent,
          extras.fuzzy
        );
        lazy.logger.debug(
          `Page ${i + 1} maxDifferences: ${maxDifferences.value} ` +
            `pixelsDifferent: ${pixelsDifferent}`
        );
        lazy.logger.debug(
          `Page ${i + 1} ${areEqual ? "compare equal" : "compare unequal"}`
        );
        if (!areEqual) {
          if (relation == "==") {
            passed = false;
            msg =
              `Found ${pixelsDifferent} pixels different, ` +
              `maximum difference per channel ${maxDifferences.value}`;
            if (this.isPrint) {
              msg += ` on page ${i + 1}`;
            }
          } else {
            passed = true;
          }
          break;
        }
      }
    }

    // If passed isn't set we got to the end without finding differences
    if (passed === null) {
      if (relation == "==") {
        passed = true;
      } else {
        msg = `mismatch reftest has no differences`;
        passed = false;
      }
    }
    return { lhs, rhs, passed, error, msg };
  }

  isAcceptableDifference(maxDifference, pixelsDifferent, allowed) {
    if (!allowed) {
      lazy.logger.info(`No differences allowed`);
      return pixelsDifferent === 0;
    }
    let [allowedDiff, allowedPixels] = allowed;
    lazy.logger.info(
      `Allowed ${allowedPixels.join("-")} pixels different, ` +
        `maximum difference per channel ${allowedDiff.join("-")}`
    );
    return (
      (pixelsDifferent === 0 && allowedPixels[0] == 0) ||
      (maxDifference === 0 && allowedDiff[0] == 0) ||
      (maxDifference >= allowedDiff[0] &&
        maxDifference <= allowedDiff[1] &&
        (pixelsDifferent >= allowedPixels[0] ||
          pixelsDifferent <= allowedPixels[1]))
    );
  }

  ensureFocus(win) {
    const focusManager = Services.focus;
    if (focusManager.activeWindow != win) {
      win.focus();
    }
    this.driver.curBrowser.contentBrowser.focus();
  }

  updateBrowserRemotenessByURL(browser, url) {
    // We don't use remote tabs on Android.
    if (lazy.AppInfo.isAndroid) {
      return;
    }
    let oa = lazy.E10SUtils.predictOriginAttributes({ browser });
    let remoteType = lazy.E10SUtils.getRemoteTypeForURI(
      url,
      this.useRemoteTabs,
      this.useRemoteSubframes,
      lazy.E10SUtils.DEFAULT_REMOTE_TYPE,
      null,
      oa
    );

    // Only re-construct the browser if its remote type needs to change.
    if (browser.remoteType !== remoteType) {
      if (remoteType === lazy.E10SUtils.NOT_REMOTE) {
        browser.removeAttribute("remote");
        browser.removeAttribute("remoteType");
      } else {
        browser.setAttribute("remote", "true");
        browser.setAttribute("remoteType", remoteType);
      }

      browser.changeRemoteness({ remoteType });
      browser.construct();
    }
  }

  async loadTestUrl(win, url, timeout, warnOnOverflow = true) {
    const browsingContext = this.driver.getBrowsingContext({ top: true });
    const webProgress = browsingContext.webProgress;

    lazy.logger.debug(`Starting load of ${url}`);
    if (this.lastURL === url) {
      lazy.logger.debug(`Refreshing page`);
      await lazy.navigate.waitForNavigationCompleted(this.driver, () => {
        lazy.navigate.refresh(browsingContext);
      });
    } else {
      // HACK: DocumentLoadListener currently doesn't know how to
      // process-switch loads in a non-tabbed <browser>. We need to manually
      // set the browser's remote type in order to ensure that the load
      // happens in the correct process.
      //
      // See bug 1636169.
      this.updateBrowserRemotenessByURL(win.gBrowser, url);
      lazy.navigate.navigateTo(browsingContext, url);

      this.lastURL = url;
    }

    this.ensureFocus(win);

    // TODO: Move all the wait logic into the parent process (bug 1669787)
    let isReftestReady = false;
    while (!isReftestReady) {
      // Note: We cannot compare the URL here. Before the navigation is complete
      // currentWindowGlobal.documentURI.spec will still point to the old URL.
      const actor =
        webProgress.browsingContext.currentWindowGlobal.getActor(
          "MarionetteReftest"
        );
      isReftestReady = await actor.reftestWait(
        url,
        this.useRemoteTabs,
        warnOnOverflow
      );
    }
  }

  async screenshot(win, url, timeout) {
    // On windows the above doesn't *actually* set the window to be the
    // reftest size; but *does* set the content area to be the right size;
    // the window is given some extra borders that aren't explicable from CSS
    let browserRect = win.gBrowser.getBoundingClientRect();
    let canvas = null;
    let remainingCount = this.urlCount.get(url) || 1;
    let cache = remainingCount > 1;
    let cacheKey = browserRect.width + "x" + browserRect.height;
    lazy.logger.debug(
      `screenshot ${url} remainingCount: ` +
        `${remainingCount} cache: ${cache} cacheKey: ${cacheKey}`
    );
    let reuseCanvas = false;
    let sizedCache = this.canvasCache.get(cacheKey);
    if (sizedCache.has(url)) {
      lazy.logger.debug(`screenshot ${url} taken from cache`);
      canvas = sizedCache.get(url);
      if (!cache) {
        sizedCache.delete(url);
      }
    } else {
      let canvasPool = sizedCache.get(null);
      if (canvasPool.length) {
        lazy.logger.debug("reusing canvas from canvas pool");
        canvas = canvasPool.pop();
      } else {
        lazy.logger.debug("using new canvas");
        canvas = null;
      }
      reuseCanvas = !cache;

      let ctxInterface = win.CanvasRenderingContext2D;
      let flags =
        ctxInterface.DRAWWINDOW_DRAW_CARET |
        ctxInterface.DRAWWINDOW_DRAW_VIEW |
        ctxInterface.DRAWWINDOW_USE_WIDGET_LAYERS;

      if (
        !(
          0 <= browserRect.left &&
          0 <= browserRect.top &&
          win.innerWidth >= browserRect.width &&
          win.innerHeight >= browserRect.height
        )
      ) {
        lazy.logger.error(`Invalid window dimensions:
browserRect.left: ${browserRect.left}
browserRect.top: ${browserRect.top}
win.innerWidth: ${win.innerWidth}
browserRect.width: ${browserRect.width}
win.innerHeight: ${win.innerHeight}
browserRect.height: ${browserRect.height}`);
        throw new Error("Window has incorrect dimensions");
      }

      url = new URL(url).href; // normalize the URL

      await this.loadTestUrl(win, url, timeout);

      canvas = await lazy.capture.canvas(
        win,
        win.docShell.browsingContext,
        0, // left
        0, // top
        browserRect.width,
        browserRect.height,
        { canvas, flags, readback: true }
      );
    }
    if (
      canvas.width !== browserRect.width ||
      canvas.height !== browserRect.height
    ) {
      lazy.logger.warn(
        `Canvas dimensions changed to ${canvas.width}x${canvas.height}`
      );
      reuseCanvas = false;
      cache = false;
    }
    if (cache) {
      sizedCache.set(url, canvas);
    }
    this.urlCount.set(url, remainingCount - 1);
    return { canvas, reuseCanvas };
  }

  async screenshotPaginated(win, url, timeout, pageRanges) {
    url = new URL(url).href; // normalize the URL
    await this.loadTestUrl(win, url, timeout, false);

    const [width, height] = [DEFAULT_PAGE_WIDTH, DEFAULT_PAGE_HEIGHT];
    const margin = DEFAULT_PAGE_MARGIN;
    const settings = lazy.print.addDefaultSettings({
      page: {
        width,
        height,
      },
      margin: {
        left: margin,
        right: margin,
        top: margin,
        bottom: margin,
      },
      shrinkToFit: false,
      background: true,
    });
    const printSettings = lazy.print.getPrintSettings(settings);

    const binaryString = await lazy.print.printToBinaryString(
      win.gBrowser.browsingContext,
      printSettings
    );

    try {
      const pdf = await this.loadPdf(binaryString);
      let pages = this.getPages(pageRanges, url, pdf.numPages);
      return [this.renderPages(pdf, pages), pages.size];
    } catch (e) {
      lazy.logger.warn(`Loading of pdf failed`);
      throw e;
    }
  }

  async loadPdfJs() {
    // Ensure pdf.js is loaded in the opener window
    await new Promise((resolve, reject) => {
      const doc = this.parentWindow.document;
      const script = doc.createElement("script");
      script.type = "module";
      script.src = "resource://pdf.js/build/pdf.mjs";
      script.onload = resolve;
      script.onerror = () => reject(new Error("pdfjs load failed"));
      doc.documentElement.appendChild(script);
    });
    this.parentWindow.pdfjsLib.GlobalWorkerOptions.workerSrc =
      "resource://pdf.js/build/pdf.worker.mjs";
  }

  async loadPdf(data) {
    return this.parentWindow.pdfjsLib.getDocument({ data }).promise;
  }

  async *renderPages(pdf, pages) {
    let canvas = null;
    for (let pageNumber = 1; pageNumber <= pdf.numPages; pageNumber++) {
      if (!pages.has(pageNumber)) {
        lazy.logger.info(`Skipping page ${pageNumber}/${pdf.numPages}`);
        continue;
      }
      lazy.logger.info(`Rendering page ${pageNumber}/${pdf.numPages}`);
      let page = await pdf.getPage(pageNumber);
      let viewport = page.getViewport({ scale: DEFAULT_PDF_RESOLUTION });
      // Prepare canvas using PDF page dimensions
      if (canvas === null) {
        canvas = this.parentWindow.document.createElementNS(XHTML_NS, "canvas");
        canvas.height = viewport.height;
        canvas.width = viewport.width;
      }

      // Render PDF page into canvas context
      let context = canvas.getContext("2d");
      let renderContext = {
        canvasContext: context,
        viewport,
      };
      await page.render(renderContext).promise;
      yield { canvas, reuseCanvas: false };
    }
  }

  getPages(pageRanges, url, totalPages) {
    // Extract test id from URL without parsing
    let afterHost = url.slice(url.indexOf(":") + 3);
    afterHost = afterHost.slice(afterHost.indexOf("/"));
    const ranges = pageRanges[afterHost];
    let rv = new Set();

    if (!ranges) {
      for (let i = 1; i <= totalPages; i++) {
        rv.add(i);
      }
      return rv;
    }

    for (let rangePart of ranges) {
      if (rangePart.length === 1) {
        rv.add(rangePart[0]);
      } else {
        if (rangePart.length !== 2) {
          throw new Error(
            `Page ranges must be <int> or <int> '-' <int>, got ${rangePart}`
          );
        }
        let [lower, upper] = rangePart;
        if (lower === null) {
          lower = 1;
        }
        if (upper === null) {
          upper = totalPages;
        }
        for (let i = lower; i <= upper; i++) {
          rv.add(i);
        }
      }
    }
    return rv;
  }
};

class DefaultMap extends Map {
  constructor(iterable, defaultFactory) {
    super(iterable);
    this.defaultFactory = defaultFactory;
  }

  get(key) {
    if (this.has(key)) {
      return super.get(key);
    }

    let v = this.defaultFactory();
    this.set(key, v);
    return v;
  }
}
PK
!<�����.chrome/remote/content/marionette/reftest.xhtml<window
  id="reftest"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  hidechrome="true"
  style="background-color: white; overflow: hidden"
>
  <script src="reftest-content.js"></script>
</window>
PK
!<�E�y3y3/chrome/remote/content/marionette/server.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  Command: "chrome://remote/content/marionette/message.sys.mjs",
  DebuggerTransport: "chrome://remote/content/marionette/transport.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  GeckoDriver: "chrome://remote/content/marionette/driver.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  MarionettePrefs: "chrome://remote/content/marionette/prefs.sys.mjs",
  Message: "chrome://remote/content/marionette/message.sys.mjs",
  PollPromise: "chrome://remote/content/shared/Sync.sys.mjs",
  Response: "chrome://remote/content/marionette/message.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
);
ChromeUtils.defineLazyGetter(lazy, "ServerSocket", () => {
  return Components.Constructor(
    "@mozilla.org/network/server-socket;1",
    "nsIServerSocket",
    "initSpecialConnection"
  );
});

const { KeepWhenOffline, LoopbackOnly } = Ci.nsIServerSocket;

const PROTOCOL_VERSION = 3;

/**
 * Bootstraps Marionette and handles incoming client connections.
 *
 * Starting the Marionette server will open a TCP socket sporting the
 * debugger transport interface on the provided `port`.  For every
 * new connection, a {@link TCPConnection} is created.
 */
export class TCPListener {
  /**
   * @param {number} port
   *     Port for server to listen to.
   */
  constructor(port) {
    this.port = port;
    this.socket = null;
    this.conns = new Set();
    this.nextConnID = 0;
    this.alive = false;
  }

  /**
   * Function produces a {@link GeckoDriver}.
   *
   * Determines the application to initialise the driver with.
   *
   * @returns {GeckoDriver}
   *     A driver instance.
   */
  driverFactory() {
    return new lazy.GeckoDriver(this);
  }

  async setAcceptConnections(value) {
    if (value) {
      if (!this.socket) {
        await lazy.PollPromise(
          (resolve, reject) => {
            try {
              const flags = KeepWhenOffline | LoopbackOnly;
              const backlog = 1;
              this.socket = new lazy.ServerSocket(this.port, flags, backlog);
              resolve();
            } catch (e) {
              lazy.logger.debug(
                `Could not bind to port ${this.port} (${e.name})`
              );
              reject();
            }
          },
          { interval: 250, timeout: 5000 }
        );

        // Since PollPromise doesn't throw when timeout expires,
        // we can end up in the situation when the socket is undefined.
        if (!this.socket) {
          throw new Error(`Could not bind to port ${this.port}`);
        }

        this.port = this.socket.port;

        this.socket.asyncListen(this);
        lazy.logger.info(`Listening on port ${this.port}`);
      }
    } else if (this.socket) {
      // Note that closing the server socket will not close currently active
      // connections.
      this.socket.close();
      this.socket = null;
      lazy.logger.info(`Stopped listening on port ${this.port}`);
    }
  }

  /**
   * Bind this listener to {@link #port} and start accepting incoming
   * socket connections on {@link #onSocketAccepted}.
   *
   * The marionette.port preference will be populated with the value
   * of {@link #port}.
   */
  async start() {
    if (this.alive) {
      return;
    }

    // Start socket server and listening for connection attempts
    await this.setAcceptConnections(true);
    lazy.MarionettePrefs.port = this.port;
    this.alive = true;
  }

  async stop() {
    if (!this.alive) {
      return;
    }

    // Shutdown server socket, and no longer listen for new connections
    await this.setAcceptConnections(false);
    this.alive = false;
  }

  onSocketAccepted(serverSocket, clientSocket) {
    let input = clientSocket.openInputStream(0, 0, 0);
    let output = clientSocket.openOutputStream(0, 0, 0);
    let transport = new lazy.DebuggerTransport(input, output);

    // Only allow a single active WebDriver session at a time
    const hasActiveSession = [...this.conns].find(
      conn => !!conn.driver.currentSession
    );
    if (hasActiveSession) {
      lazy.logger.warn(
        "Connection attempt denied because an active session has been found"
      );

      // Ideally we should stop the server to listen for new connection
      // attempts, but the current architecture doesn't allow us to do that.
      // As such just close the transport if no further connections are allowed.
      transport.close();
      return;
    }

    let conn = new TCPConnection(
      this.nextConnID++,
      transport,
      this.driverFactory.bind(this)
    );
    conn.onclose = this.onConnectionClosed.bind(this);
    this.conns.add(conn);

    lazy.logger.debug(
      `Accepted connection ${conn.id} ` +
        `from ${clientSocket.host}:${clientSocket.port}`
    );
    conn.sayHello();
    transport.ready();
  }

  onConnectionClosed(conn) {
    lazy.logger.debug(`Closed connection ${conn.id}`);
    this.conns.delete(conn);
  }
}

/**
 * Marionette client connection.
 *
 * Dispatches packets received to their correct service destinations
 * and sends back the service endpoint's return values.
 *
 * @param {number} connID
 *     Unique identifier of the connection this dispatcher should handle.
 * @param {DebuggerTransport} transport
 *     Debugger transport connection to the client.
 * @param {function(): GeckoDriver} driverFactory
 *     Factory function that produces a {@link GeckoDriver}.
 */
export class TCPConnection {
  constructor(connID, transport, driverFactory) {
    this.id = connID;
    this.conn = transport;

    // transport hooks are TCPConnection#onPacket
    // and TCPConnection#onClosed
    this.conn.hooks = this;

    // callback for when connection is closed
    this.onclose = null;

    // last received/sent message ID
    this.lastID = 0;

    this.driver = driverFactory();
  }

  #log(msg) {
    let dir = msg.origin == lazy.Message.Origin.Client ? "->" : "<-";
    lazy.logger.debug(`${this.id} ${dir} ${msg.toString()}`);
  }

  /**
   * Debugger transport callback that cleans up
   * after a connection is closed.
   */
  onClosed() {
    this.driver.deleteSession();
    if (this.onclose) {
      this.onclose(this);
    }
  }

  /**
   * Callback that receives data packets from the client.
   *
   * If the message is a Response, we look up the command previously
   * issued to the client and run its callback, if any.  In case of
   * a Command, the corresponding is executed.
   *
   * @param {Array.<number, number, ?, ?>} data
   *     A four element array where the elements, in sequence, signifies
   *     message type, message ID, method name or error, and parameters
   *     or result.
   */
  onPacket(data) {
    // unable to determine how to respond
    if (!Array.isArray(data)) {
      let e = new TypeError(
        "Unable to unmarshal packet data: " + JSON.stringify(data)
      );
      lazy.error.report(e);
      return;
    }

    // return immediately with any error trying to unmarshal message
    let msg;
    try {
      msg = lazy.Message.fromPacket(data);
      msg.origin = lazy.Message.Origin.Client;
      this.#log(msg);
    } catch (e) {
      let resp = this.createResponse(data[1]);
      resp.sendError(e);
      return;
    }

    // execute new command
    if (msg instanceof lazy.Command) {
      (async () => {
        await this.execute(msg);
      })();
    } else {
      lazy.logger.fatal("Cannot process messages other than Command");
    }
  }

  /**
   * Executes a Marionette command and sends back a response when it
   * has finished executing.
   *
   * If the command implementation sends the response itself by calling
   * <code>resp.send()</code>, the response is guaranteed to not be
   * sent twice.
   *
   * Errors thrown in commands are marshaled and sent back, and if they
   * are not {@link WebDriverError} instances, they are additionally
   * propagated and reported to {@link Components.utils.reportError}.
   *
   * @param {Command} cmd
   *     Command to execute.
   */
  async execute(cmd) {
    let resp = this.createResponse(cmd.id);
    let sendResponse = () => resp.sendConditionally(resp => !resp.sent);
    let sendError = resp.sendError.bind(resp);

    await this.despatch(cmd, resp)
      .then(sendResponse, sendError)
      .catch(lazy.error.report);
  }

  /**
   * Despatches command to appropriate Marionette service.
   *
   * @param {Command} cmd
   *     Command to run.
   * @param {Response} resp
   *     Mutable response where the command's return value will be
   *     assigned.
   *
   * @throws {Error}
   *     A command's implementation may throw at any time.
   */
  async despatch(cmd, resp) {
    const startTime = Cu.now();

    let fn = this.driver.commands[cmd.name];
    if (typeof fn == "undefined") {
      throw new lazy.error.UnknownCommandError(cmd.name);
    }

    if (cmd.name != "WebDriver:NewSession") {
      lazy.assert.session(this.driver.currentSession);
    }

    let rv = await fn.bind(this.driver)(cmd);

    // Bug 1819029: Some older commands cannot return a response wrapped within
    // a value field because it would break compatibility with geckodriver and
    // Marionette client. It's unlikely that we are going to fix that.
    //
    // Warning: No more commands should be added to this list!
    const commandsNoValueResponse = [
      "Marionette:Quit",
      "WebDriver:FindElements",
      "WebDriver:FindElementsFromShadowRoot",
      "WebDriver:CloseChromeWindow",
      "WebDriver:CloseWindow",
      "WebDriver:FullscreenWindow",
      "WebDriver:GetCookies",
      "WebDriver:GetElementRect",
      "WebDriver:GetTimeouts",
      "WebDriver:GetWindowHandles",
      "WebDriver:GetWindowRect",
      "WebDriver:MaximizeWindow",
      "WebDriver:MinimizeWindow",
      "WebDriver:NewSession",
      "WebDriver:NewWindow",
      "WebDriver:SetWindowRect",
    ];

    if (rv != null) {
      // By default the Response' constructor sets the body to `{ value: null }`.
      // As such we only want to override the value if it's neither `null` nor
      // `undefined`.
      if (commandsNoValueResponse.includes(cmd.name)) {
        resp.body = rv;
      } else {
        resp.body.value = rv;
      }
    }

    if (Services.profiler?.IsActive()) {
      ChromeUtils.addProfilerMarker(
        "Marionette: Command",
        { startTime, category: "Remote-Protocol" },
        `${cmd.name} (${cmd.id})`
      );
    }
  }

  /**
   * Fail-safe creation of a new instance of {@link Response}.
   *
   * @param {number} msgID
   *     Message ID to respond to.  If it is not a number, -1 is used.
   *
   * @returns {Response}
   *     Response to the message with `msgID`.
   */
  createResponse(msgID) {
    if (typeof msgID != "number") {
      msgID = -1;
    }
    return new lazy.Response(msgID, this.send.bind(this));
  }

  sendError(err, cmdID) {
    let resp = new lazy.Response(cmdID, this.send.bind(this));
    resp.sendError(err);
  }

  /**
   * When a client connects we send across a JSON Object defining the
   * protocol level.
   *
   * This is the only message sent by Marionette that does not follow
   * the regular message format.
   */
  sayHello() {
    let whatHo = {
      applicationType: "gecko",
      marionetteProtocol: PROTOCOL_VERSION,
    };
    this.sendRaw(whatHo);
  }

  /**
   * Delegates message to client based on the provided  {@code cmdID}.
   * The message is sent over the debugger transport socket.
   *
   * The command ID is a unique identifier assigned to the client's request
   * that is used to distinguish the asynchronous responses.
   *
   * Whilst responses to commands are synchronous and must be sent in the
   * correct order.
   *
   * @param {Message} msg
   *     The command or response to send.
   */
  send(msg) {
    msg.origin = lazy.Message.Origin.Server;
    if (msg instanceof lazy.Response) {
      this.sendToClient(msg);
    } else {
      lazy.logger.fatal("Cannot send messages other than Response");
    }
  }

  // Low-level methods:

  /**
   * Send given response to the client over the debugger transport socket.
   *
   * @param {Response} resp
   *     The response to send back to the client.
   */
  sendToClient(resp) {
    this.sendMessage(resp);
  }

  /**
   * Marshal message to the Marionette message format and send it.
   *
   * @param {Message} msg
   *     The message to send.
   */
  sendMessage(msg) {
    this.#log(msg);
    let payload = msg.toPacket();
    this.sendRaw(payload);
  }

  /**
   * Send the given payload over the debugger transport socket to the
   * connected client.
   *
   * @param {Record<string, ?>} payload
   *     The payload to ship.
   */
  sendRaw(payload) {
    this.conn.send(payload);
  }

  toString() {
    return `[object TCPConnection ${this.id}]`;
  }
}
PK
!<��8DD5chrome/remote/content/marionette/stream-utils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "IOUtil",
  "@mozilla.org/io-util;1",
  "nsIIOUtil"
);

ChromeUtils.defineLazyGetter(lazy, "ScriptableInputStream", () => {
  return Components.Constructor(
    "@mozilla.org/scriptableinputstream;1",
    "nsIScriptableInputStream",
    "init"
  );
});

const BUFFER_SIZE = 0x8000;

/**
 * This helper function (and its companion object) are used by bulk
 * senders and receivers to read and write data in and out of other streams.
 * Functions that make use of this tool are passed to callers when it is
 * time to read or write bulk data.  It is highly recommended to use these
 * copier functions instead of the stream directly because the copier
 * enforces the agreed upon length. Since bulk mode reuses an existing
 * stream, the sender and receiver must write and read exactly the agreed
 * upon amount of data, or else the entire transport will be left in a
 * invalid state.  Additionally, other methods of stream copying (such as
 * NetUtil.asyncCopy) close the streams involved, which would terminate
 * the debugging transport, and so it is avoided here.
 *
 * Overall, this *works*, but clearly the optimal solution would be
 * able to just use the streams directly.  If it were possible to fully
 * implement nsIInputStream/nsIOutputStream in JS, wrapper streams could
 * be created to enforce the length and avoid closing, and consumers could
 * use familiar stream utilities like NetUtil.asyncCopy.
 *
 * The function takes two async streams and copies a precise number
 * of bytes from one to the other.  Copying begins immediately, but may
 * complete at some future time depending on data size.  Use the returned
 * promise to know when it's complete.
 *
 * @param {nsIAsyncInputStream} input
 *     Stream to copy from.
 * @param {nsIAsyncOutputStream} output
 *        Stream to copy to.
 * @param {number} length
 *        Amount of data that needs to be copied.
 *
 * @returns {Promise}
 *     Promise is resolved when copying completes or rejected if any
 *     (unexpected) errors occur.
 */
function copyStream(input, output, length) {
  let copier = new StreamCopier(input, output, length);
  return copier.copy();
}

/** @class */
function StreamCopier(input, output, length) {
  lazy.EventEmitter.decorate(this);
  this._id = StreamCopier._nextId++;
  this.input = input;
  // Save off the base output stream, since we know it's async as we've
  // required
  this.baseAsyncOutput = output;
  if (lazy.IOUtil.outputStreamIsBuffered(output)) {
    this.output = output;
  } else {
    this.output = Cc[
      "@mozilla.org/network/buffered-output-stream;1"
    ].createInstance(Ci.nsIBufferedOutputStream);
    this.output.init(output, BUFFER_SIZE);
  }
  this._length = length;
  this._amountLeft = length;
  this._deferred = {
    promise: new Promise((resolve, reject) => {
      this._deferred.resolve = resolve;
      this._deferred.reject = reject;
    }),
  };

  this._copy = this._copy.bind(this);
  this._flush = this._flush.bind(this);
  this._destroy = this._destroy.bind(this);

  // Copy promise's then method up to this object.
  //
  // Allows the copier to offer a promise interface for the simple succeed
  // or fail scenarios, but also emit events (due to the EventEmitter)
  // for other states, like progress.
  this.then = this._deferred.promise.then.bind(this._deferred.promise);
  this.then(this._destroy, this._destroy);

  // Stream ready callback starts as |_copy|, but may switch to |_flush|
  // at end if flushing would block the output stream.
  this._streamReadyCallback = this._copy;
}
StreamCopier._nextId = 0;

StreamCopier.prototype = {
  copy() {
    // Dispatch to the next tick so that it's possible to attach a progress
    // event listener, even for extremely fast copies (like when testing).
    Services.tm.currentThread.dispatch(() => {
      try {
        this._copy();
      } catch (e) {
        this._deferred.reject(e);
      }
    }, 0);
    return this;
  },

  _copy() {
    let bytesAvailable = this.input.available();
    let amountToCopy = Math.min(bytesAvailable, this._amountLeft);
    this._debug("Trying to copy: " + amountToCopy);

    let bytesCopied;
    try {
      bytesCopied = this.output.writeFrom(this.input, amountToCopy);
    } catch (e) {
      if (e.result == Cr.NS_BASE_STREAM_WOULD_BLOCK) {
        this._debug("Base stream would block, will retry");
        this._debug("Waiting for output stream");
        this.baseAsyncOutput.asyncWait(this, 0, 0, Services.tm.currentThread);
        return;
      }
      throw e;
    }

    this._amountLeft -= bytesCopied;
    this._debug("Copied: " + bytesCopied + ", Left: " + this._amountLeft);
    this._emitProgress();

    if (this._amountLeft === 0) {
      this._debug("Copy done!");
      this._flush();
      return;
    }

    this._debug("Waiting for input stream");
    this.input.asyncWait(this, 0, 0, Services.tm.currentThread);
  },

  _emitProgress() {
    this.emit("progress", {
      bytesSent: this._length - this._amountLeft,
      totalBytes: this._length,
    });
  },

  _flush() {
    try {
      this.output.flush();
    } catch (e) {
      if (
        e.result == Cr.NS_BASE_STREAM_WOULD_BLOCK ||
        e.result == Cr.NS_ERROR_FAILURE
      ) {
        this._debug("Flush would block, will retry");
        this._streamReadyCallback = this._flush;
        this._debug("Waiting for output stream");
        this.baseAsyncOutput.asyncWait(this, 0, 0, Services.tm.currentThread);
        return;
      }
      throw e;
    }
    this._deferred.resolve();
  },

  _destroy() {
    this._destroy = null;
    this._copy = null;
    this._flush = null;
    this.input = null;
    this.output = null;
  },

  // nsIInputStreamCallback
  onInputStreamReady() {
    this._streamReadyCallback();
  },

  // nsIOutputStreamCallback
  onOutputStreamReady() {
    this._streamReadyCallback();
  },

  _debug() {},
};

/**
 * Read from a stream, one byte at a time, up to the next
 * <var>delimiter</var> character, but stopping if we've read |count|
 * without finding it.  Reading also terminates early if there are less
 * than <var>count</var> bytes available on the stream.  In that case,
 * we only read as many bytes as the stream currently has to offer.
 *
 * @param {nsIInputStream} stream
 *     Input stream to read from.
 * @param {string} delimiter
 *     Character we're trying to find.
 * @param {number} count
 *     Max number of characters to read while searching.
 *
 * @returns {string}
 *     Collected data.  If the delimiter was found, this string will
 *     end with it.
 */
// TODO: This implementation could be removed if bug 984651 is fixed,
// which provides a native version of the same idea.
function delimitedRead(stream, delimiter, count) {
  let scriptableStream;
  if (stream instanceof Ci.nsIScriptableInputStream) {
    scriptableStream = stream;
  } else {
    scriptableStream = new lazy.ScriptableInputStream(stream);
  }

  let data = "";

  // Don't exceed what's available on the stream
  count = Math.min(count, stream.available());

  if (count <= 0) {
    return data;
  }

  let char;
  while (char !== delimiter && count > 0) {
    char = scriptableStream.readBytes(1);
    count--;
    data += char;
  }

  return data;
}

export const StreamUtils = {
  copyStream,
  delimitedRead,
};
PK
!<�n�9�?�?-chrome/remote/content/marionette/sync.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
);

const { TYPE_ONE_SHOT, TYPE_REPEATING_SLACK } = Ci.nsITimer;

const PROMISE_TIMEOUT = AppConstants.DEBUG ? 4500 : 1500;

/**
 * Dispatch a function to be executed on the main thread.
 *
 * @param {Function} func
 *     Function to be executed.
 */
export function executeSoon(func) {
  if (typeof func != "function") {
    throw new TypeError();
  }

  Services.tm.dispatchToMainThread(func);
}

/**
 * Runs a Promise-like function off the main thread until it is resolved
 * through ``resolve`` or ``rejected`` callbacks.  The function is
 * guaranteed to be run at least once, irregardless of the timeout.
 *
 * The ``func`` is evaluated every ``interval`` for as long as its
 * runtime duration does not exceed ``interval``.  Evaluations occur
 * sequentially, meaning that evaluations of ``func`` are queued if
 * the runtime evaluation duration of ``func`` is greater than ``interval``.
 *
 * ``func`` is given two arguments, ``resolve`` and ``reject``,
 * of which one must be called for the evaluation to complete.
 * Calling ``resolve`` with an argument indicates that the expected
 * wait condition was met and will return the passed value to the
 * caller.  Conversely, calling ``reject`` will evaluate ``func``
 * again until the ``timeout`` duration has elapsed or ``func`` throws.
 * The passed value to ``reject`` will also be returned to the caller
 * once the wait has expired.
 *
 * Usage::
 *
 *     let els = new PollPromise((resolve, reject) => {
 *       let res = document.querySelectorAll("p");
 *       if (res.length > 0) {
 *         resolve(Array.from(res));
 *       } else {
 *         reject([]);
 *       }
 *     }, {timeout: 1000});
 *
 * @param {Condition} func
 *     Function to run off the main thread.
 * @param {object=} options
 * @param {number=} options.timeout
 *     Desired timeout if wanted.  If 0 or less than the runtime evaluation
 *     time of ``func``, ``func`` is guaranteed to run at least once.
 *     Defaults to using no timeout.
 * @param {number=} options.interval
 *     Duration between each poll of ``func`` in milliseconds.
 *     Defaults to 10 milliseconds.
 *
 * @returns {Promise.<*>}
 *     Yields the value passed to ``func``'s
 *     ``resolve`` or ``reject`` callbacks.
 *
 * @throws {*}
 *     If ``func`` throws, its error is propagated.
 * @throws {TypeError}
 *     If `timeout` or `interval`` are not numbers.
 * @throws {RangeError}
 *     If `timeout` or `interval` are not unsigned integers.
 */
export function PollPromise(func, { timeout = null, interval = 10 } = {}) {
  const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);

  if (typeof func != "function") {
    throw new TypeError();
  }
  if (timeout != null && typeof timeout != "number") {
    throw new TypeError();
  }
  if (typeof interval != "number") {
    throw new TypeError();
  }
  if (
    (timeout && (!Number.isInteger(timeout) || timeout < 0)) ||
    !Number.isInteger(interval) ||
    interval < 0
  ) {
    throw new RangeError();
  }

  return new Promise((resolve, reject) => {
    let start, end;

    if (Number.isInteger(timeout)) {
      start = new Date().getTime();
      end = start + timeout;
    }

    let evalFn = () => {
      new Promise(func)
        .then(resolve, rejected => {
          if (lazy.error.isError(rejected)) {
            throw rejected;
          }

          // return if there is a timeout and set to 0,
          // allowing |func| to be evaluated at least once
          if (
            typeof end != "undefined" &&
            (start == end || new Date().getTime() >= end)
          ) {
            resolve(rejected);
          }
        })
        .catch(reject);
    };

    // the repeating slack timer waits |interval|
    // before invoking |evalFn|
    evalFn();

    timer.init(evalFn, interval, TYPE_REPEATING_SLACK);
  }).then(
    res => {
      timer.cancel();
      return res;
    },
    err => {
      timer.cancel();
      throw err;
    }
  );
}

/**
 * Represents the timed, eventual completion (or failure) of an
 * asynchronous operation, and its resulting value.
 *
 * In contrast to a regular Promise, it times out after ``timeout``.
 *
 * @param {Function} fn
 *     Function to run, which will have its ``reject``
 *     callback invoked after the ``timeout`` duration is reached.
 *     It is given two callbacks: ``resolve(value)`` and
 *     ``reject(error)``.
 * @param {object=} options
 * @param {string} options.errorMessage
 *     Message to use for the thrown error.
 * @param {number=} options.timeout
 *     ``condition``'s ``reject`` callback will be called
 *     after this timeout, given in milliseconds.
 *     By default 1500 ms in an optimised build and 4500 ms in
 *     debug builds.
 * @param {Error=} options.throws
 *     When the ``timeout`` is hit, this error class will be
 *     thrown.  If it is null, no error is thrown and the promise is
 *     instead resolved on timeout with a TimeoutError.
 *
 * @returns {Promise.<*>}
 *     Timed promise.
 *
 * @throws {TypeError}
 *     If `timeout` is not a number.
 * @throws {RangeError}
 *     If `timeout` is not an unsigned integer.
 */
export function TimedPromise(fn, options = {}) {
  const {
    errorMessage = "TimedPromise timed out",
    timeout = PROMISE_TIMEOUT,
    throws = lazy.error.TimeoutError,
  } = options;

  const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);

  if (typeof fn != "function") {
    throw new TypeError();
  }
  if (typeof timeout != "number") {
    throw new TypeError();
  }
  if (!Number.isInteger(timeout) || timeout < 0) {
    throw new RangeError();
  }

  return new Promise((resolve, reject) => {
    let trace;

    // Reject only if |throws| is given.  Otherwise it is assumed that
    // the user is OK with the promise timing out.
    let bail = () => {
      const message = `${errorMessage} after ${timeout} ms`;
      if (throws !== null) {
        let err = new throws(message);
        reject(err);
      } else {
        lazy.logger.warn(message, trace);
        resolve();
      }
    };

    trace = lazy.error.stack();
    timer.initWithCallback({ notify: bail }, timeout, TYPE_ONE_SHOT);

    try {
      fn(resolve, reject);
    } catch (e) {
      reject(e);
    }
  }).then(
    res => {
      timer.cancel();
      return res;
    },
    err => {
      timer.cancel();
      throw err;
    }
  );
}

/**
 * Pauses for the given duration.
 *
 * @param {number} timeout
 *     Duration to wait before fulfilling promise in milliseconds.
 *
 * @returns {Promise}
 *     Promise that fulfills when the `timeout` is elapsed.
 *
 * @throws {TypeError}
 *     If `timeout` is not a number.
 * @throws {RangeError}
 *     If `timeout` is not an unsigned integer.
 */
export function Sleep(timeout) {
  if (typeof timeout != "number") {
    throw new TypeError();
  }
  if (!Number.isInteger(timeout) || timeout < 0) {
    throw new RangeError();
  }

  return new Promise(resolve => {
    const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    timer.init(
      () => {
        // Bug 1663880 - Explicitely cancel the timer for now to prevent a hang
        timer.cancel();
        resolve();
      },
      timeout,
      TYPE_ONE_SHOT
    );
  });
}

/**
 * Detects when the specified message manager has been destroyed.
 *
 * One can observe the removal and detachment of a content browser
 * (`<xul:browser>`) or a chrome window by its message manager
 * disconnecting.
 *
 * When a browser is associated with a tab, this is safer than only
 * relying on the event `TabClose` which signalises the _intent to_
 * remove a tab and consequently would lead to the destruction of
 * the content browser and its browser message manager.
 *
 * When closing a chrome window it is safer than only relying on
 * the event 'unload' which signalises the _intent to_ close the
 * chrome window and consequently would lead to the destruction of
 * the window and its window message manager.
 *
 * @param {MessageListenerManager} messageManager
 *     The message manager to observe for its disconnect state.
 *     Use the browser message manager when closing a content browser,
 *     and the window message manager when closing a chrome window.
 *
 * @returns {Promise}
 *     A promise that resolves when the message manager has been destroyed.
 */
export function MessageManagerDestroyedPromise(messageManager) {
  return new Promise(resolve => {
    function observe(subject, topic) {
      lazy.logger.trace(`Received observer notification ${topic}`);

      if (subject == messageManager) {
        Services.obs.removeObserver(this, "message-manager-disconnect");
        resolve();
      }
    }

    Services.obs.addObserver(observe, "message-manager-disconnect");
  });
}

/**
 * Throttle until the main thread is idle and `window` has performed
 * an animation frame (in that order).
 *
 * @param {ChromeWindow} win
 *     Window to request the animation frame from.
 *
 * @returns {Promise}
 */
export function IdlePromise(win) {
  const animationFramePromise = new Promise(resolve => {
    executeSoon(() => {
      win.requestAnimationFrame(resolve);
    });
  });

  // Abort if the underlying window gets closed
  const windowClosedPromise = new PollPromise(resolve => {
    if (win.closed) {
      resolve();
    }
  });

  return Promise.race([animationFramePromise, windowClosedPromise]);
}

/**
 * Wraps a callback function, that, as long as it continues to be
 * invoked, will not be triggered.  The given function will be
 * called after the timeout duration is reached, after no more
 * events fire.
 *
 * This class implements the {@link EventListener} interface,
 * which means it can be used interchangably with `addEventHandler`.
 *
 * Debouncing events can be useful when dealing with e.g. DOM events
 * that fire at a high rate.  It is generally advisable to avoid
 * computationally expensive operations such as DOM modifications
 * under these circumstances.
 *
 * One such high frequenecy event is `resize` that can fire multiple
 * times before the window reaches its final dimensions.  In order
 * to delay an operation until the window has completed resizing,
 * it is possible to use this technique to only invoke the callback
 * after the last event has fired::
 *
 *     let cb = new DebounceCallback(event => {
 *       // fires after the final resize event
 *       console.log("resize", event);
 *     });
 *     window.addEventListener("resize", cb);
 *
 * Note that it is not possible to use this synchronisation primitive
 * with `addEventListener(..., {once: true})`.
 *
 * @param {function(Event): void} fn
 *     Callback function that is guaranteed to be invoked once only,
 *     after `timeout`.
 * @param {number=} [timeout = 250] timeout
 *     Time since last event firing, before `fn` will be invoked.
 */
export class DebounceCallback {
  constructor(fn, { timeout = 250 } = {}) {
    if (typeof fn != "function" || typeof timeout != "number") {
      throw new TypeError();
    }
    if (!Number.isInteger(timeout) || timeout < 0) {
      throw new RangeError();
    }

    this.fn = fn;
    this.timeout = timeout;
    this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  }

  handleEvent(ev) {
    this.timer.cancel();
    this.timer.initWithCallback(
      () => {
        this.timer.cancel();
        this.fn(ev);
      },
      this.timeout,
      TYPE_ONE_SHOT
    );
  }
}

/**
 * Wait for a message to be fired from a particular message manager.
 *
 * This method has been duplicated from BrowserTestUtils.sys.mjs.
 *
 * @param {nsIMessageManager} messageManager
 *     The message manager that should be used.
 * @param {string} messageName
 *     The message to wait for.
 * @param {object=} options
 *     Extra options.
 * @param {function(Message): boolean=} options.checkFn
 *     Called with the ``Message`` object as argument, should return ``true``
 *     if the message is the expected one, or ``false`` if it should be
 *     ignored and listening should continue. If not specified, the first
 *     message with the specified name resolves the returned promise.
 *
 * @returns {Promise.<object>}
 *     Promise which resolves to the data property of the received
 *     ``Message``.
 */
export function waitForMessage(
  messageManager,
  messageName,
  { checkFn = undefined } = {}
) {
  if (messageManager == null || !("addMessageListener" in messageManager)) {
    throw new TypeError();
  }
  if (typeof messageName != "string") {
    throw new TypeError();
  }
  if (checkFn && typeof checkFn != "function") {
    throw new TypeError();
  }

  return new Promise(resolve => {
    messageManager.addMessageListener(messageName, function onMessage(msg) {
      lazy.logger.trace(`Received ${messageName} for ${msg.target}`);
      if (checkFn && !checkFn(msg)) {
        return;
      }
      messageManager.removeMessageListener(messageName, onMessage);
      resolve(msg.data);
    });
  });
}

/**
 * Wait for the specified observer topic to be observed.
 *
 * This method has been duplicated from TestUtils.sys.mjs.
 *
 * Because this function is intended for testing, any error in checkFn
 * will cause the returned promise to be rejected instead of waiting for
 * the next notification, since this is probably a bug in the test.
 *
 * @param {string} topic
 *     The topic to observe.
 * @param {object=} options
 *     Extra options.
 * @param {function(string, object): boolean=} options.checkFn
 *     Called with ``subject``, and ``data`` as arguments, should return true
 *     if the notification is the expected one, or false if it should be
 *     ignored and listening should continue. If not specified, the first
 *     notification for the specified topic resolves the returned promise.
 * @param {number=} options.timeout
 *     Timeout duration in milliseconds, if provided.
 *     If specified, then the returned promise will be rejected with
 *     TimeoutError, if not already resolved, after this duration has elapsed.
 *     If not specified, then no timeout is used. Defaults to null.
 *
 * @returns {Promise.<Array<string, object>>}
 *     Promise which is either resolved to an array of ``subject``, and ``data``
 *     from the observed notification, or rejected with TimeoutError after
 *     options.timeout milliseconds if specified.
 *
 * @throws {TypeError}
 * @throws {RangeError}
 */
export function waitForObserverTopic(topic, options = {}) {
  const { checkFn = null, timeout = null } = options;
  if (typeof topic != "string") {
    throw new TypeError();
  }
  if (
    (checkFn != null && typeof checkFn != "function") ||
    (timeout !== null && typeof timeout != "number")
  ) {
    throw new TypeError();
  }
  if (timeout && (!Number.isInteger(timeout) || timeout < 0)) {
    throw new RangeError();
  }

  return new Promise((resolve, reject) => {
    let timer;

    function cleanUp() {
      Services.obs.removeObserver(observer, topic);
      timer?.cancel();
    }

    function observer(subject, topic, data) {
      lazy.logger.trace(`Received observer notification ${topic}`);
      try {
        if (checkFn && !checkFn(subject, data)) {
          return;
        }
        cleanUp();
        resolve({ subject, data });
      } catch (ex) {
        cleanUp();
        reject(ex);
      }
    }

    Services.obs.addObserver(observer, topic);

    if (timeout !== null) {
      timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
      timer.init(
        () => {
          cleanUp();
          reject(
            new lazy.error.TimeoutError(
              `waitForObserverTopic timed out after ${timeout} ms`
            )
          );
        },
        timeout,
        TYPE_ONE_SHOT
      );
    }
  });
}
PK
!<	a�`�@�@2chrome/remote/content/marionette/transport.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",

  BulkPacket: "chrome://remote/content/marionette/packets.sys.mjs",
  executeSoon: "chrome://remote/content/marionette/sync.sys.mjs",
  JSONPacket: "chrome://remote/content/marionette/packets.sys.mjs",
  Packet: "chrome://remote/content/marionette/packets.sys.mjs",
  StreamUtils: "chrome://remote/content/marionette/stream-utils.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "ScriptableInputStream", () => {
  return Components.Constructor(
    "@mozilla.org/scriptableinputstream;1",
    "nsIScriptableInputStream",
    "init"
  );
});

const flags = { wantVerbose: false, wantLogging: false };

const dumpv = flags.wantVerbose
  ? function (msg) {
      dump(msg + "\n");
    }
  : function () {};

const PACKET_HEADER_MAX = 200;

/**
 * An adapter that handles data transfers between the debugger client
 * and server. It can work with both nsIPipe and nsIServerSocket
 * transports so long as the properly created input and output streams
 * are specified.  (However, for intra-process connections,
 * LocalDebuggerTransport, below, is more efficient than using an nsIPipe
 * pair with DebuggerTransport.)
 *
 * @param {nsIAsyncInputStream} input
 *     The input stream.
 * @param {nsIAsyncOutputStream} output
 *     The output stream.
 *
 * Given a DebuggerTransport instance dt:
 * 1) Set dt.hooks to a packet handler object (described below).
 * 2) Call dt.ready() to begin watching for input packets.
 * 3) Call dt.send() / dt.startBulkSend() to send packets.
 * 4) Call dt.close() to close the connection, and disengage from
 *    the event loop.
 *
 * A packet handler is an object with the following methods:
 *
 * - onPacket(packet) - called when we have received a complete packet.
 *   |packet| is the parsed form of the packet --- a JavaScript value, not
 *   a JSON-syntax string.
 *
 * - onBulkPacket(packet) - called when we have switched to bulk packet
 *   receiving mode. |packet| is an object containing:
 *   actor:  Name of actor that will receive the packet
 *   type:   Name of actor's method that should be called on receipt
 *   length: Size of the data to be read
 *   stream: This input stream should only be used directly if you
 *             can ensure that you will read exactly |length| bytes and
 *             will not close the stream when reading is complete
 *   done:   If you use the stream directly (instead of |copyTo|
 *             below), you must signal completion by resolving/rejecting
 *             this deferred.  If it's rejected, the transport will
 *             be closed.  If an Error is supplied as a rejection value,
 *             it will be logged via |dump|.  If you do use |copyTo|,
 *             resolving is taken care of for you when copying completes.
 *   copyTo: A helper function for getting your data out of the
 *             stream that meets the stream handling requirements above,
 *             and has the following signature:
 *
 *             - params
 *                {nsIAsyncOutputStream} output
 *                  The stream to copy to.
 *             - returns {Promise}
 *                  The promise is resolved when copying completes or
 *                  rejected if any (unexpected) errors occur.  This object
 *                  also emits "progress" events for each chunk that is
 *                  copied.  See stream-utils.js.
 *
 * - onClosed(reason) - called when the connection is closed. |reason|
 *   is an optional nsresult or object, typically passed when the
 *   transport is closed due to some error in a underlying stream.
 *
 * See ./packets.js and the Remote Debugging Protocol specification for
 * more details on the format of these packets.
 *
 * @class
 */
export function DebuggerTransport(input, output) {
  lazy.EventEmitter.decorate(this);

  this._input = input;
  this._scriptableInput = new lazy.ScriptableInputStream(input);
  this._output = output;

  // The current incoming (possibly partial) header, which will determine
  // which type of Packet |_incoming| below will become.
  this._incomingHeader = "";
  // The current incoming Packet object
  this._incoming = null;
  // A queue of outgoing Packet objects
  this._outgoing = [];

  this.hooks = null;
  this.active = false;

  this._incomingEnabled = true;
  this._outgoingEnabled = true;

  this.close = this.close.bind(this);
}

DebuggerTransport.prototype = {
  /**
   * Transmit an object as a JSON packet.
   *
   * This method returns immediately, without waiting for the entire
   * packet to be transmitted, registering event handlers as needed to
   * transmit the entire packet. Packets are transmitted in the order they
   * are passed to this method.
   */
  send(object) {
    this.emit("send", object);

    let packet = new lazy.JSONPacket(this);
    packet.object = object;
    this._outgoing.push(packet);
    this._flushOutgoing();
  },

  /**
   * Transmit streaming data via a bulk packet.
   *
   * This method initiates the bulk send process by queuing up the header
   * data.  The caller receives eventual access to a stream for writing.
   *
   * N.B.: Do *not* attempt to close the stream handed to you, as it
   * will continue to be used by this transport afterwards.  Most users
   * should instead use the provided |copyFrom| function instead.
   *
   * @param {object} header
   *     This is modeled after the format of JSON packets above, but does
   *     not actually contain the data, but is instead just a routing
   *     header:
   *
   *       - actor:  Name of actor that will receive the packet
   *       - type:   Name of actor's method that should be called on receipt
   *       - length: Size of the data to be sent
   *
   * @returns {Promise}
   *     The promise will be resolved when you are allowed to write to
   *     the stream with an object containing:
   *
   *       - stream:   This output stream should only be used directly
   *                   if you can ensure that you will write exactly
   *                   |length| bytes and will not close the stream when
   *                    writing is complete.
   *       - done:     If you use the stream directly (instead of
   *                   |copyFrom| below), you must signal completion by
   *                   resolving/rejecting this deferred.  If it's
   *                   rejected, the transport will be closed.  If an
   *                   Error is supplied as a rejection value, it will
   *                   be logged via |dump|.  If you do use |copyFrom|,
   *                   resolving is taken care of for you when copying
   *                   completes.
   *       - copyFrom: A helper function for getting your data onto the
   *                   stream that meets the stream handling requirements
   *                   above, and has the following signature:
   *
   *                   - params
   *                     {nsIAsyncInputStream} input
   *                       The stream to copy from.
   *                   - returns {Promise}
   *                       The promise is resolved when copying completes
   *                       or rejected if any (unexpected) errors occur.
   *                       This object also emits "progress" events for
   *                       each chunkthat is copied.  See stream-utils.js.
   */
  startBulkSend(header) {
    this.emit("startbulksend", header);

    let packet = new lazy.BulkPacket(this);
    packet.header = header;
    this._outgoing.push(packet);
    this._flushOutgoing();
    return packet.streamReadyForWriting;
  },

  /**
   * Close the transport.
   *
   * @param {(nsresult|object)=} reason
   *     The status code or error message that corresponds to the reason
   *     for closing the transport (likely because a stream closed
   *     or failed).
   */
  close(reason) {
    this.emit("close", reason);

    this.active = false;
    this._input.close();
    this._scriptableInput.close();
    this._output.close();
    this._destroyIncoming();
    this._destroyAllOutgoing();
    if (this.hooks) {
      this.hooks.onClosed(reason);
      this.hooks = null;
    }
    if (reason) {
      dumpv("Transport closed: " + reason);
    } else {
      dumpv("Transport closed.");
    }
  },

  /**
   * The currently outgoing packet (at the top of the queue).
   */
  get _currentOutgoing() {
    return this._outgoing[0];
  },

  /**
   * Flush data to the outgoing stream.  Waits until the output
   * stream notifies us that it is ready to be written to (via
   * onOutputStreamReady).
   */
  _flushOutgoing() {
    if (!this._outgoingEnabled || this._outgoing.length === 0) {
      return;
    }

    // If the top of the packet queue has nothing more to send, remove it.
    if (this._currentOutgoing.done) {
      this._finishCurrentOutgoing();
    }

    if (this._outgoing.length) {
      let threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
      this._output.asyncWait(this, 0, 0, threadManager.currentThread);
    }
  },

  /**
   * Pause this transport's attempts to write to the output stream.
   * This is used when we've temporarily handed off our output stream for
   * writing bulk data.
   */
  pauseOutgoing() {
    this._outgoingEnabled = false;
  },

  /**
   * Resume this transport's attempts to write to the output stream.
   */
  resumeOutgoing() {
    this._outgoingEnabled = true;
    this._flushOutgoing();
  },

  // nsIOutputStreamCallback
  /**
   * This is called when the output stream is ready for more data to
   * be written.  The current outgoing packet will attempt to write some
   * amount of data, but may not complete.
   */
  onOutputStreamReady(stream) {
    if (!this._outgoingEnabled || this._outgoing.length === 0) {
      return;
    }

    try {
      this._currentOutgoing.write(stream);
    } catch (e) {
      if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
        this.close(e.result);
        return;
      }
      throw e;
    }

    this._flushOutgoing();
  },

  /**
   * Remove the current outgoing packet from the queue upon completion.
   */
  _finishCurrentOutgoing() {
    if (this._currentOutgoing) {
      this._currentOutgoing.destroy();
      this._outgoing.shift();
    }
  },

  /**
   * Clear the entire outgoing queue.
   */
  _destroyAllOutgoing() {
    for (let packet of this._outgoing) {
      packet.destroy();
    }
    this._outgoing = [];
  },

  /**
   * Initialize the input stream for reading. Once this method has been
   * called, we watch for packets on the input stream, and pass them to
   * the appropriate handlers via this.hooks.
   */
  ready() {
    this.active = true;
    this._waitForIncoming();
  },

  /**
   * Asks the input stream to notify us (via onInputStreamReady) when it is
   * ready for reading.
   */
  _waitForIncoming() {
    if (this._incomingEnabled) {
      let threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
      this._input.asyncWait(this, 0, 0, threadManager.currentThread);
    }
  },

  /**
   * Pause this transport's attempts to read from the input stream.
   * This is used when we've temporarily handed off our input stream for
   * reading bulk data.
   */
  pauseIncoming() {
    this._incomingEnabled = false;
  },

  /**
   * Resume this transport's attempts to read from the input stream.
   */
  resumeIncoming() {
    this._incomingEnabled = true;
    this._flushIncoming();
    this._waitForIncoming();
  },

  // nsIInputStreamCallback
  /**
   * Called when the stream is either readable or closed.
   */
  onInputStreamReady(stream) {
    try {
      while (
        stream.available() &&
        this._incomingEnabled &&
        this._processIncoming(stream, stream.available())
      ) {
        // Loop until there is nothing more to process
      }
      this._waitForIncoming();
    } catch (e) {
      if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
        this.close(e.result);
      } else {
        throw e;
      }
    }
  },

  /**
   * Process the incoming data.  Will create a new currently incoming
   * Packet if needed.  Tells the incoming Packet to read as much data
   * as it can, but reading may not complete.  The Packet signals that
   * its data is ready for delivery by calling one of this transport's
   * _on*Ready methods (see ./packets.js and the _on*Ready methods below).
   *
   * @returns {boolean}
   *     Whether incoming stream processing should continue for any
   *     remaining data.
   */
  _processIncoming(stream, count) {
    dumpv("Data available: " + count);

    if (!count) {
      dumpv("Nothing to read, skipping");
      return false;
    }

    try {
      if (!this._incoming) {
        dumpv("Creating a new packet from incoming");

        if (!this._readHeader(stream)) {
          // Not enough data to read packet type
          return false;
        }

        // Attempt to create a new Packet by trying to parse each possible
        // header pattern.
        this._incoming = lazy.Packet.fromHeader(this._incomingHeader, this);
        if (!this._incoming) {
          throw new Error(
            "No packet types for header: " + this._incomingHeader
          );
        }
      }

      if (!this._incoming.done) {
        // We have an incomplete packet, keep reading it.
        dumpv("Existing packet incomplete, keep reading");
        this._incoming.read(stream, this._scriptableInput);
      }
    } catch (e) {
      dump(`Error reading incoming packet: (${e} - ${e.stack})\n`);

      // Now in an invalid state, shut down the transport.
      this.close();
      return false;
    }

    if (!this._incoming.done) {
      // Still not complete, we'll wait for more data.
      dumpv("Packet not done, wait for more");
      return true;
    }

    // Ready for next packet
    this._flushIncoming();
    return true;
  },

  /**
   * Read as far as we can into the incoming data, attempting to build
   * up a complete packet header (which terminates with ":").  We'll only
   * read up to PACKET_HEADER_MAX characters.
   *
   * @returns {boolean}
   *     True if we now have a complete header.
   */
  _readHeader() {
    let amountToRead = PACKET_HEADER_MAX - this._incomingHeader.length;
    this._incomingHeader += lazy.StreamUtils.delimitedRead(
      this._scriptableInput,
      ":",
      amountToRead
    );
    if (flags.wantVerbose) {
      dumpv("Header read: " + this._incomingHeader);
    }

    if (this._incomingHeader.endsWith(":")) {
      if (flags.wantVerbose) {
        dumpv("Found packet header successfully: " + this._incomingHeader);
      }
      return true;
    }

    if (this._incomingHeader.length >= PACKET_HEADER_MAX) {
      throw new Error("Failed to parse packet header!");
    }

    // Not enough data yet.
    return false;
  },

  /**
   * If the incoming packet is done, log it as needed and clear the buffer.
   */
  _flushIncoming() {
    if (!this._incoming.done) {
      return;
    }
    if (flags.wantLogging) {
      dumpv("Got: " + this._incoming);
    }
    this._destroyIncoming();
  },

  /**
   * Handler triggered by an incoming JSONPacket completing it's |read|
   * method.  Delivers the packet to this.hooks.onPacket.
   */
  _onJSONObjectReady(object) {
    lazy.executeSoon(() => {
      // Ensure the transport is still alive by the time this runs.
      if (this.active) {
        this.emit("packet", object);
        this.hooks.onPacket(object);
      }
    });
  },

  /**
   * Handler triggered by an incoming BulkPacket entering the |read|
   * phase for the stream portion of the packet.  Delivers info about the
   * incoming streaming data to this.hooks.onBulkPacket.  See the main
   * comment on the transport at the top of this file for more details.
   */
  _onBulkReadReady(...args) {
    lazy.executeSoon(() => {
      // Ensure the transport is still alive by the time this runs.
      if (this.active) {
        this.emit("bulkpacket", ...args);
        this.hooks.onBulkPacket(...args);
      }
    });
  },

  /**
   * Remove all handlers and references related to the current incoming
   * packet, either because it is now complete or because the transport
   * is closing.
   */
  _destroyIncoming() {
    if (this._incoming) {
      this._incoming.destroy();
    }
    this._incomingHeader = "";
    this._incoming = null;
  },
};
PK
!<Q��!�!6chrome/remote/content/marionette/web-reference.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  dom: "chrome://remote/content/shared/DOM.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
});

/**
 * A web reference is an abstraction used to identify an element when
 * it is transported via the protocol, between remote- and local ends.
 *
 * In Marionette this abstraction can represent DOM elements,
 * WindowProxies, and XUL elements.
 */
export class WebReference {
  /**
   * @param {string} uuid
   *     Identifier that must be unique across all browsing contexts
   *     for the contract to be upheld.
   */
  constructor(uuid) {
    this.uuid = lazy.assert.string(
      uuid,
      lazy.pprint`Expected "uuid" to be a string, got ${uuid}`
    );
  }

  /**
   * Performs an equality check between this web element and
   * <var>other</var>.
   *
   * @param {WebReference} other
   *     Web element to compare with this.
   *
   * @returns {boolean}
   *     True if this and <var>other</var> are the same.  False
   *     otherwise.
   */
  is(other) {
    return other instanceof WebReference && this.uuid === other.uuid;
  }

  toString() {
    return `[object ${this.constructor.name} uuid=${this.uuid}]`;
  }

  /**
   * Returns a new {@link WebReference} reference for a DOM or XUL element,
   * <code>WindowProxy</code>, or <code>ShadowRoot</code>.
   *
   * @param {(Element|ShadowRoot|WindowProxy|MockXULElement)} node
   *     Node to construct a web element reference for.
   * @param {string=} uuid
   *     Optional unique identifier of the WebReference if already known.
   *     If not defined a new unique identifier will be created.
   *
   * @returns {WebReference}
   *     Web reference for <var>node</var>.
   *
   * @throws {InvalidArgumentError}
   *     If <var>node</var> is neither a <code>WindowProxy</code>,
   *     DOM or XUL element, or <code>ShadowRoot</code>.
   */
  static from(node, uuid) {
    if (uuid === undefined) {
      uuid = lazy.generateUUID();
    }

    if (lazy.dom.isShadowRoot(node) && !lazy.dom.isInPrivilegedDocument(node)) {
      // When we support Chrome Shadowroots we will need to
      // do a check here of shadowroot.host being in a privileged document
      // See Bug 1743541
      return new ShadowRoot(uuid);
    } else if (lazy.dom.isElement(node)) {
      return new WebElement(uuid);
    } else if (lazy.dom.isDOMWindow(node)) {
      if (node.parent === node) {
        return new WebWindow(uuid);
      }
      return new WebFrame(uuid);
    }

    throw new lazy.error.InvalidArgumentError(
      "Expected DOM window/element " + lazy.pprint`or XUL element, got: ${node}`
    );
  }

  /**
   * Unmarshals a JSON Object to one of {@link ShadowRoot}, {@link WebElement},
   * {@link WebFrame}, or {@link WebWindow}.
   *
   * @param {Record<string, string>} json
   *     Web reference, which is supposed to be a JSON Object
   *     where the key is one of the {@link WebReference} concrete
   *     classes' UUID identifiers.
   *
   * @returns {WebReference}
   *     Web reference for the JSON object.
   *
   * @throws {InvalidArgumentError}
   *     If <var>json</var> is not a web reference.
   */
  static fromJSON(json) {
    lazy.assert.object(
      json,
      lazy.pprint`Expected web reference to be an object, got ${json}`
    );
    if (json instanceof WebReference) {
      return json;
    }
    let keys = Object.keys(json);

    for (let key of keys) {
      switch (key) {
        case ShadowRoot.Identifier:
          return ShadowRoot.fromJSON(json);

        case WebElement.Identifier:
          return WebElement.fromJSON(json);

        case WebFrame.Identifier:
          return WebFrame.fromJSON(json);

        case WebWindow.Identifier:
          return WebWindow.fromJSON(json);
      }
    }

    throw new lazy.error.InvalidArgumentError(
      lazy.pprint`Expected web reference, got: ${json}`
    );
  }

  /**
   * Checks if <var>obj<var> is a {@link WebReference} reference.
   *
   * @param {Record<string, string>} obj
   *     Object that represents a {@link WebReference}.
   *
   * @returns {boolean}
   *     True if <var>obj</var> is a {@link WebReference}, false otherwise.
   */
  static isReference(obj) {
    if (Object.prototype.toString.call(obj) != "[object Object]") {
      return false;
    }

    if (
      ShadowRoot.Identifier in obj ||
      WebElement.Identifier in obj ||
      WebFrame.Identifier in obj ||
      WebWindow.Identifier in obj
    ) {
      return true;
    }
    return false;
  }
}

/**
 * Shadow Root elements are represented as shadow root references when they are
 * transported over the wire protocol
 */
export class ShadowRoot extends WebReference {
  toJSON() {
    return { [ShadowRoot.Identifier]: this.uuid };
  }

  static fromJSON(json) {
    const { Identifier } = ShadowRoot;

    if (!(Identifier in json)) {
      throw new lazy.error.InvalidArgumentError(
        lazy.pprint`Expected shadow root reference, got: ${json}`
      );
    }

    let uuid = json[Identifier];
    return new ShadowRoot(uuid);
  }

  /**
   * Constructs a {@link ShadowRoot} from a string <var>uuid</var>.
   *
   * This whole function is a workaround for the fact that clients
   * to Marionette occasionally pass <code>{id: <uuid>}</code> JSON
   * Objects instead of shadow root representations.
   *
   * @param {string} uuid
   *     UUID to be associated with the web reference.
   *
   * @returns {ShadowRoot}
   *     The shadow root reference.
   *
   * @throws {InvalidArgumentError}
   *     If <var>uuid</var> is not a string.
   */
  static fromUUID(uuid) {
    lazy.assert.string(
      uuid,
      lazy.pprint`Expected "uuid" to be a string, got: ${uuid}`
    );

    return new ShadowRoot(uuid);
  }
}

ShadowRoot.Identifier = "shadow-6066-11e4-a52e-4f735466cecf";

/**
 * DOM elements are represented as web elements when they are
 * transported over the wire protocol.
 */
export class WebElement extends WebReference {
  toJSON() {
    return { [WebElement.Identifier]: this.uuid };
  }

  static fromJSON(json) {
    const { Identifier } = WebElement;

    if (!(Identifier in json)) {
      throw new lazy.error.InvalidArgumentError(
        lazy.pprint`Expected web element reference, got: ${json}`
      );
    }

    let uuid = json[Identifier];
    return new WebElement(uuid);
  }

  /**
   * Constructs a {@link WebElement} from a string <var>uuid</var>.
   *
   * This whole function is a workaround for the fact that clients
   * to Marionette occasionally pass <code>{id: <uuid>}</code> JSON
   * Objects instead of web element representations.
   *
   * @param {string} uuid
   *     UUID to be associated with the web reference.
   *
   * @returns {WebElement}
   *     The web element reference.
   *
   * @throws {InvalidArgumentError}
   *     If <var>uuid</var> is not a string.
   */
  static fromUUID(uuid) {
    return new WebElement(uuid);
  }
}

WebElement.Identifier = "element-6066-11e4-a52e-4f735466cecf";

/**
 * Nested browsing contexts, such as the <code>WindowProxy</code>
 * associated with <tt>&lt;frame&gt;</tt> and <tt>&lt;iframe&gt;</tt>,
 * are represented as web frames over the wire protocol.
 */
export class WebFrame extends WebReference {
  toJSON() {
    return { [WebFrame.Identifier]: this.uuid };
  }

  static fromJSON(json) {
    if (!(WebFrame.Identifier in json)) {
      throw new lazy.error.InvalidArgumentError(
        lazy.pprint`Expected web frame reference, got: ${json}`
      );
    }
    let uuid = json[WebFrame.Identifier];
    return new WebFrame(uuid);
  }
}

WebFrame.Identifier = "frame-075b-4da1-b6ba-e579c2d3230a";

/**
 * Top-level browsing contexts, such as <code>WindowProxy</code>
 * whose <code>opener</code> is null, are represented as web windows
 * over the wire protocol.
 */
export class WebWindow extends WebReference {
  toJSON() {
    return { [WebWindow.Identifier]: this.uuid };
  }

  static fromJSON(json) {
    if (!(WebWindow.Identifier in json)) {
      throw new lazy.error.InvalidArgumentError(
        lazy.pprint`Expected web window reference, got: ${json}`
      );
    }
    let uuid = json[WebWindow.Identifier];
    return new WebWindow(uuid);
  }
}

WebWindow.Identifier = "window-fcc6-11e5-b4f8-330a88ab9d7f";
PK
!<o��;;1chrome/remote/content/marionette/webauthn.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "webauthnService",
  "@mozilla.org/webauthn/service;1",
  "nsIWebAuthnService"
);

/** @namespace */
export const webauthn = {};

/**
 * Add a virtual authenticator.
 *
 * @param {string} protocol one of "ctap1/u2f", "ctap2", "ctap2_1"
 * @param {string} transport one of "usb", "nfc", "ble", "smart-card",
 *                 "hybrid", "internal"
 * @param {boolean} hasResidentKey
 * @param {boolean} hasUserVerification
 * @param {boolean} isUserConsenting
 * @param {boolean} isUserVerified
 * @returns {id} the id of the added authenticator
 */
webauthn.addVirtualAuthenticator = function (
  protocol,
  transport,
  hasResidentKey,
  hasUserVerification,
  isUserConsenting,
  isUserVerified
) {
  return lazy.webauthnService.addVirtualAuthenticator(
    protocol,
    transport,
    hasResidentKey,
    hasUserVerification,
    isUserConsenting,
    isUserVerified
  );
};

/**
 * Removes a virtual authenticator.
 *
 * @param {id} authenticatorId the id of the virtual authenticator
 */
webauthn.removeVirtualAuthenticator = function (authenticatorId) {
  lazy.webauthnService.removeVirtualAuthenticator(authenticatorId);
};

/**
 * Adds a credential to a previously-added virtual authenticator.
 *
 * @param {id} authenticatorId the id of the virtual authenticator
 * @param {string} credentialId a probabilistically-unique byte sequence
 *                 identifying a public key credential source and its
 *                 authentication assertions (encoded using Base64url
 *                 Encoding).
 * @param {boolean} isResidentCredential if set to true, a client-side
 *                  discoverable credential is created. If set to false, a
 *                  server-side credential is created instead.
 * @param {string} rpId The Relying Party ID the credential is scoped to.
 * @param {string} privateKey An asymmetric key package containing a single
 *                 private key per RFC5958, encoded using Base64url Encoding.
 * @param {string} userHandle The userHandle associated to the credential
 *                 encoded using Base64url Encoding.
 * @param {number} signCount The initial value for a signature counter
 *                 associated to the public key credential source.
 */
webauthn.addCredential = function (
  authenticatorId,
  credentialId,
  isResidentCredential,
  rpId,
  privateKey,
  userHandle,
  signCount
) {
  lazy.webauthnService.addCredential(
    authenticatorId,
    credentialId,
    isResidentCredential,
    rpId,
    privateKey,
    userHandle,
    signCount
  );
};

/**
 * Gets all credentials from a virtual authenticator.
 *
 * @param {id} authenticatorId the id of the virtual authenticator
 * @returns {object} the credentials on the authenticator
 */
webauthn.getCredentials = function (authenticatorId) {
  return lazy.webauthnService.getCredentials(authenticatorId);
};

/**
 * Removes a credential from a virtual authenticator.
 *
 * @param {id} authenticatorId the id of the virtual authenticator
 * @param {string} credentialId the id of the credential
 */
webauthn.removeCredential = function (authenticatorId, credentialId) {
  lazy.webauthnService.removeCredential(authenticatorId, credentialId);
};

/**
 * Removes all credentials from a virtual authenticator.
 *
 * @param {id} authenticatorId the id of the virtual authenticator
 */
webauthn.removeAllCredentials = function (authenticatorId) {
  lazy.webauthnService.removeAllCredentials(authenticatorId);
};

/**
 * Sets the "isUserVerified" bit on a virtual authenticator.
 *
 * @param {id} authenticatorId the id of the virtual authenticator
 * @param {bool} isUserVerified the value to set the "isUserVerified" bit to
 */
webauthn.setUserVerified = function (authenticatorId, isUserVerified) {
  lazy.webauthnService.setUserVerified(authenticatorId, isUserVerified);
};
PK
!<�Q)�"�"7chrome/remote/content/server/WebSocketHandshake.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// This file is an XPCOM service-ified copy of ../devtools/server/socket/websocket-server.js.

const CC = Components.Constructor;

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  executeSoon: "chrome://remote/content/shared/Sync.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  RemoteAgent: "chrome://remote/content/components/RemoteAgent.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

ChromeUtils.defineLazyGetter(lazy, "CryptoHash", () => {
  return CC("@mozilla.org/security/hash;1", "nsICryptoHash", "initWithString");
});

ChromeUtils.defineLazyGetter(lazy, "threadManager", () => {
  return Cc["@mozilla.org/thread-manager;1"].getService();
});

/**
 * Allowed origins are exposed through 2 separate getters because while most
 * of the values should be valid URIs, `null` is also a valid origin and cannot
 * be converted to a URI. Call sites interested in checking for null should use
 * `allowedOrigins`, those interested in URIs should use `allowedOriginURIs`.
 */
ChromeUtils.defineLazyGetter(lazy, "allowedOrigins", () =>
  lazy.RemoteAgent.allowOrigins !== null ? lazy.RemoteAgent.allowOrigins : []
);

ChromeUtils.defineLazyGetter(lazy, "allowedOriginURIs", () => {
  return lazy.allowedOrigins
    .map(origin => {
      try {
        const originURI = Services.io.newURI(origin);
        // Make sure to read host/port/scheme as those getters could throw for
        // invalid URIs.
        return {
          host: originURI.host,
          port: originURI.port,
          scheme: originURI.scheme,
        };
      } catch (e) {
        return null;
      }
    })
    .filter(uri => uri !== null);
});

/**
 * Write a string of bytes to async output stream
 * and return promise that resolves once all data has been written.
 * Doesn't do any UTF-16/UTF-8 conversion.
 * The string is treated as an array of bytes.
 */
function writeString(output, data) {
  return new Promise((resolve, reject) => {
    const wait = () => {
      if (data.length === 0) {
        resolve();
        return;
      }

      output.asyncWait(
        () => {
          try {
            const written = output.write(data, data.length);
            data = data.slice(written);
            wait();
          } catch (ex) {
            reject(ex);
          }
        },
        0,
        0,
        lazy.threadManager.currentThread
      );
    };

    wait();
  });
}

/**
 * Write HTTP response with headers (array of strings) and body
 * to async output stream.
 */
function writeHttpResponse(output, headers, body = "") {
  headers.push(`Content-Length: ${body.length}`);

  const s = headers.join("\r\n") + `\r\n\r\n${body}`;
  return writeString(output, s);
}

/**
 * Check if the provided URI's host is an IP address.
 *
 * @param {nsIURI} uri
 *     The URI to check.
 * @returns {boolean}
 */
function isIPAddress(uri) {
  try {
    // getBaseDomain throws an explicit error if the uri host is an IP address.
    Services.eTLD.getBaseDomain(uri);
  } catch (e) {
    return e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS;
  }
  return false;
}

function isHostValid(hostHeader) {
  try {
    // Might throw both when calling newURI or when accessing the host/port.
    const hostUri = Services.io.newURI(`https://${hostHeader}`);
    const { host, port } = hostUri;
    const isHostnameValid =
      isIPAddress(hostUri) || lazy.RemoteAgent.allowHosts.includes(host);
    // For nsIURI a port value of -1 corresponds to the protocol's default port.
    const isPortValid = [-1, lazy.RemoteAgent.port].includes(port);
    return isHostnameValid && isPortValid;
  } catch (e) {
    return false;
  }
}

function isOriginValid(originHeader) {
  if (originHeader === undefined) {
    // Always accept no origin header.
    return true;
  }

  // Special case "null" origins, used for privacy sensitive or opaque origins.
  if (originHeader === "null") {
    return lazy.allowedOrigins.includes("null");
  }

  try {
    // Extract the host, port and scheme from the provided origin header.
    const { host, port, scheme } = Services.io.newURI(originHeader);
    // Check if any allowed origin matches the provided host, port and scheme.
    return lazy.allowedOriginURIs.some(
      uri => uri.host === host && uri.port === port && uri.scheme === scheme
    );
  } catch (e) {
    // Reject invalid origin headers
    return false;
  }
}

/**
 * Process the WebSocket handshake headers and return the key to be sent in
 * Sec-WebSocket-Accept response header.
 */
function processRequest({ requestLine, headers }) {
  if (!isOriginValid(headers.get("origin"))) {
    lazy.logger.debug(
      `Incorrect Origin header, allowed origins: [${lazy.allowedOrigins}]`
    );
    throw new Error(
      `The handshake request has incorrect Origin header ${headers.get(
        "origin"
      )}`
    );
  }

  if (!isHostValid(headers.get("host"))) {
    lazy.logger.debug(
      `Incorrect Host header, allowed hosts: [${lazy.RemoteAgent.allowHosts}]`
    );
    throw new Error(
      `The handshake request has incorrect Host header ${headers.get("host")}`
    );
  }

  const method = requestLine.split(" ")[0];
  if (method !== "GET") {
    throw new Error("The handshake request must use GET method");
  }

  const upgrade = headers.get("upgrade");
  if (!upgrade || upgrade.toLowerCase() !== "websocket") {
    throw new Error(
      `The handshake request has incorrect Upgrade header: ${upgrade}`
    );
  }

  const connection = headers.get("connection");
  if (
    !connection ||
    !connection
      .split(",")
      .map(t => t.trim().toLowerCase())
      .includes("upgrade")
  ) {
    throw new Error("The handshake request has incorrect Connection header");
  }

  const version = headers.get("sec-websocket-version");
  if (!version || version !== "13") {
    throw new Error(
      "The handshake request must have Sec-WebSocket-Version: 13"
    );
  }

  // Compute the accept key
  const key = headers.get("sec-websocket-key");
  if (!key) {
    throw new Error(
      "The handshake request must have a Sec-WebSocket-Key header"
    );
  }

  return { acceptKey: computeKey(key) };
}

function computeKey(key) {
  const str = `${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`;
  const data = Array.from(str, ch => ch.charCodeAt(0));
  const hash = new lazy.CryptoHash("sha1");
  hash.update(data, data.length);
  return hash.finish(true);
}

/**
 * Perform the server part of a WebSocket opening handshake
 * on an incoming connection.
 */
async function serverHandshake(request, output) {
  try {
    // Check and extract info from the request
    const { acceptKey } = processRequest(request);

    // Send response headers
    await writeHttpResponse(output, [
      "HTTP/1.1 101 Switching Protocols",
      "Server: httpd.js",
      "Upgrade: websocket",
      "Connection: Upgrade",
      `Sec-WebSocket-Accept: ${acceptKey}`,
    ]);
  } catch (error) {
    // Send error response in case of error
    await writeHttpResponse(
      output,
      [
        "HTTP/1.1 400 Bad Request",
        "Server: httpd.js",
        "Content-Type: text/plain",
      ],
      error.message
    );

    throw error;
  }
}

async function createWebSocket(transport, input, output) {
  const transportProvider = {
    setListener(upgradeListener) {
      // onTransportAvailable callback shouldn't be called synchronously
      lazy.executeSoon(() => {
        upgradeListener.onTransportAvailable(transport, input, output);
      });
    },
  };

  return new Promise((resolve, reject) => {
    const socket = WebSocket.createServerWebSocket(
      null,
      [],
      transportProvider,
      ""
    );
    socket.addEventListener("close", () => {
      input.close();
      output.close();
    });

    socket.onopen = () => resolve(socket);
    socket.onerror = err => reject(err);
  });
}

/** Upgrade an existing HTTP request from httpd.js to WebSocket. */
async function upgrade(request, response) {
  // handle response manually, allowing us to send arbitrary data
  response._powerSeized = true;

  const { transport, input, output } = response._connection;

  lazy.logger.info(
    `Perform WebSocket upgrade for incoming connection from ${transport.host}:${transport.port}`
  );

  const headers = new Map();
  for (let [key, values] of Object.entries(request._headers._headers)) {
    headers.set(key, values.join("\n"));
  }
  const convertedRequest = {
    requestLine: `${request.method} ${request.path}`,
    headers,
  };
  await serverHandshake(convertedRequest, output);

  return createWebSocket(transport, input, output);
}

export const WebSocketHandshake = { upgrade };
PK
!<�ȍxx7chrome/remote/content/server/WebSocketTransport.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// This is an XPCOM service-ified copy of ../devtools/shared/transport/websocket-transport.js.

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
});

export function WebSocketTransport(socket) {
  lazy.EventEmitter.decorate(this);

  this.active = false;
  this.hooks = null;
  this.socket = socket;
}

WebSocketTransport.prototype = {
  ready() {
    if (this.active) {
      return;
    }

    this.socket.addEventListener("message", this);
    this.socket.addEventListener("close", this);

    this.active = true;
  },

  send(object) {
    this.emit("send", object);
    if (this.socket) {
      this.socket.send(JSON.stringify(object));
    }
  },

  startBulkSend() {
    throw new Error("Bulk send is not supported by WebSocket transport");
  },

  /**
   * Force closing the active connection and WebSocket.
   */
  close() {
    if (!this.socket) {
      return;
    }
    this.emit("close");

    if (this.hooks) {
      this.hooks.onConnectionClose();
    }

    this.active = false;

    this.socket.removeEventListener("message", this);

    // Remove the listener that is used when the connection
    // is closed because we already emitted the 'close' event.
    // Instead listen for the closing of the WebSocket.
    this.socket.removeEventListener("close", this);
    this.socket.addEventListener("close", this.onSocketClose.bind(this));

    // Close socket with code `1000` for a normal closure.
    this.socket.close(1000);
  },

  /**
   * Callback for socket on close event,
   * it is used in case websocket was closed by the client.
   */
  onClose() {
    if (!this.socket) {
      return;
    }
    this.emit("close");

    if (this.hooks) {
      this.hooks.onConnectionClose();
      this.hooks.onSocketClose();
      this.hooks = null;
    }

    this.active = false;

    this.socket.removeEventListener("message", this);
    this.socket.removeEventListener("close", this);
    this.socket = null;
  },

  /**
   * Callback which is called when we can be sure that websocket is closed.
   */
  onSocketClose() {
    this.socket = null;

    if (this.hooks) {
      this.hooks.onSocketClose();
      this.hooks = null;
    }
  },

  handleEvent(event) {
    switch (event.type) {
      case "message":
        this.onMessage(event);
        break;
      case "close":
        this.onClose();
        break;
    }
  },

  onMessage({ data }) {
    if (typeof data !== "string") {
      throw new Error(
        "Binary messages are not supported by WebSocket transport"
      );
    }

    const object = JSON.parse(data);
    this.emit("packet", object);
    if (this.hooks) {
      this.hooks.onPacket(object);
    }
  },
};
PK
!<#�|��t�t*chrome/remote/content/server/httpd.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * An implementation of an HTTP server both as a loadable script and as an XPCOM
 * component.  See the accompanying README file for user documentation on
 * httpd.js.
 */

const CC = Components.Constructor;

const PR_UINT32_MAX = Math.pow(2, 32) - 1;

/** True if debugging output is enabled, false otherwise. */
var DEBUG = false; // non-const *only* so tweakable in server tests

/** True if debugging output should be timestamped. */
var DEBUG_TIMESTAMP = false; // non-const so tweakable in server tests

/**
 * Sets the debugging status, intended for tweaking in server tests.
 *
 * @param {boolean} debug
 *   Enables debugging output
 * @param {boolean} debugTimestamp
 *   Enables timestamping of the debugging output.
 */
export function setDebuggingStatus(debug, debugTimestamp) {
  DEBUG = debug;
  DEBUG_TIMESTAMP = debugTimestamp;
}

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

/**
 * Asserts that the given condition holds.  If it doesn't, the given message is
 * dumped, a stack trace is printed, and an exception is thrown to attempt to
 * stop execution (which unfortunately must rely upon the exception not being
 * accidentally swallowed by the code that uses it).
 */
function NS_ASSERT(cond, msg) {
  if (DEBUG && !cond) {
    dumpn("###!!!");
    dumpn("###!!! ASSERTION" + (msg ? ": " + msg : "!"));
    dumpn("###!!! Stack follows:");

    var stack = new Error().stack.split(/\n/);
    dumpn(
      stack
        .map(function (val) {
          return "###!!!   " + val;
        })
        .join("\n")
    );

    throw Components.Exception("", Cr.NS_ERROR_ABORT);
  }
}

/** Constructs an HTTP error object. */
export function HttpError(code, description) {
  this.code = code;
  this.description = description;
}

HttpError.prototype = {
  toString() {
    return this.code + " " + this.description;
  },
};

/**
 * Errors thrown to trigger specific HTTP server responses.
 */
export var HTTP_400 = new HttpError(400, "Bad Request");

export var HTTP_401 = new HttpError(401, "Unauthorized");
export var HTTP_402 = new HttpError(402, "Payment Required");
export var HTTP_403 = new HttpError(403, "Forbidden");
export var HTTP_404 = new HttpError(404, "Not Found");
export var HTTP_405 = new HttpError(405, "Method Not Allowed");
export var HTTP_406 = new HttpError(406, "Not Acceptable");
export var HTTP_407 = new HttpError(407, "Proxy Authentication Required");
export var HTTP_408 = new HttpError(408, "Request Timeout");
export var HTTP_409 = new HttpError(409, "Conflict");
export var HTTP_410 = new HttpError(410, "Gone");
export var HTTP_411 = new HttpError(411, "Length Required");
export var HTTP_412 = new HttpError(412, "Precondition Failed");
export var HTTP_413 = new HttpError(413, "Request Entity Too Large");
export var HTTP_414 = new HttpError(414, "Request-URI Too Long");
export var HTTP_415 = new HttpError(415, "Unsupported Media Type");
export var HTTP_417 = new HttpError(417, "Expectation Failed");
export var HTTP_500 = new HttpError(500, "Internal Server Error");
export var HTTP_501 = new HttpError(501, "Not Implemented");
export var HTTP_502 = new HttpError(502, "Bad Gateway");
export var HTTP_503 = new HttpError(503, "Service Unavailable");
export var HTTP_504 = new HttpError(504, "Gateway Timeout");
export var HTTP_505 = new HttpError(505, "HTTP Version Not Supported");

/** Creates a hash with fields corresponding to the values in arr. */
function array2obj(arr) {
  var obj = {};
  for (var i = 0; i < arr.length; i++) {
    obj[arr[i]] = arr[i];
  }
  return obj;
}

/** Returns an array of the integers x through y, inclusive. */
function range(x, y) {
  var arr = [];
  for (var i = x; i <= y; i++) {
    arr.push(i);
  }
  return arr;
}

/** An object (hash) whose fields are the numbers of all HTTP error codes. */
const HTTP_ERROR_CODES = array2obj(range(400, 417).concat(range(500, 505)));

/**
 * The character used to distinguish hidden files from non-hidden files, a la
 * the leading dot in Apache.  Since that mechanism also hides files from
 * easy display in LXR, ls output, etc. however, we choose instead to use a
 * suffix character.  If a requested file ends with it, we append another
 * when getting the file on the server.  If it doesn't, we just look up that
 * file.  Therefore, any file whose name ends with exactly one of the character
 * is "hidden" and available for use by the server.
 */
const HIDDEN_CHAR = "^";

/**
 * The file name suffix indicating the file containing overridden headers for
 * a requested file.
 */
const HEADERS_SUFFIX = HIDDEN_CHAR + "headers" + HIDDEN_CHAR;
const INFORMATIONAL_RESPONSE_SUFFIX =
  HIDDEN_CHAR + "informationalResponse" + HIDDEN_CHAR;

/** Type used to denote SJS scripts for CGI-like functionality. */
const SJS_TYPE = "sjs";

/** Base for relative timestamps produced by dumpn(). */
var firstStamp = 0;

/** dump(str) with a trailing "\n" -- only outputs if DEBUG. */
export function dumpn(str) {
  if (DEBUG) {
    var prefix = "HTTPD-INFO | ";
    if (DEBUG_TIMESTAMP) {
      if (firstStamp === 0) {
        firstStamp = Date.now();
      }

      var elapsed = Date.now() - firstStamp; // milliseconds
      var min = Math.floor(elapsed / 60000);
      var sec = (elapsed % 60000) / 1000;

      if (sec < 10) {
        prefix += min + ":0" + sec.toFixed(3) + " | ";
      } else {
        prefix += min + ":" + sec.toFixed(3) + " | ";
      }
    }

    dump(prefix + str + "\n");
  }
}

/** Dumps the current JS stack if DEBUG. */
function dumpStack() {
  // peel off the frames for dumpStack() and Error()
  var stack = new Error().stack.split(/\n/).slice(2);
  stack.forEach(dumpn);
}

/**
 * JavaScript constructors for commonly-used classes; precreating these is a
 * speedup over doing the same from base principles.  See the docs at
 * http://developer.mozilla.org/en/docs/Components.Constructor for details.
 */
const ServerSocket = CC(
  "@mozilla.org/network/server-socket;1",
  "nsIServerSocket",
  "init"
);
const ServerSocketIPv6 = CC(
  "@mozilla.org/network/server-socket;1",
  "nsIServerSocket",
  "initIPv6"
);
const ServerSocketDualStack = CC(
  "@mozilla.org/network/server-socket;1",
  "nsIServerSocket",
  "initDualStack"
);
const ScriptableInputStream = CC(
  "@mozilla.org/scriptableinputstream;1",
  "nsIScriptableInputStream",
  "init"
);
const Pipe = CC("@mozilla.org/pipe;1", "nsIPipe", "init");
const FileInputStream = CC(
  "@mozilla.org/network/file-input-stream;1",
  "nsIFileInputStream",
  "init"
);
const ConverterInputStream = CC(
  "@mozilla.org/intl/converter-input-stream;1",
  "nsIConverterInputStream",
  "init"
);
const WritablePropertyBag = CC(
  "@mozilla.org/hash-property-bag;1",
  "nsIWritablePropertyBag2"
);
const SupportsString = CC(
  "@mozilla.org/supports-string;1",
  "nsISupportsString"
);

/* These two are non-const only so a test can overwrite them. */
var BinaryInputStream = CC(
  "@mozilla.org/binaryinputstream;1",
  "nsIBinaryInputStream",
  "setInputStream"
);
var BinaryOutputStream = CC(
  "@mozilla.org/binaryoutputstream;1",
  "nsIBinaryOutputStream",
  "setOutputStream"
);

export function overrideBinaryStreamsForTests(
  inputStream,
  outputStream,
  responseSegmentSize
) {
  BinaryInputStream = inputStream;
  BinaryOutputStream = outputStream;
  Response.SEGMENT_SIZE = responseSegmentSize;
}

/**
 * Returns the RFC 822/1123 representation of a date.
 *
 * @param date : Number
 *   the date, in milliseconds from midnight (00:00:00), January 1, 1970 GMT
 * @returns string
 *   the representation of the given date
 */
function toDateString(date) {
  //
  // rfc1123-date = wkday "," SP date1 SP time SP "GMT"
  // date1        = 2DIGIT SP month SP 4DIGIT
  //                ; day month year (e.g., 02 Jun 1982)
  // time         = 2DIGIT ":" 2DIGIT ":" 2DIGIT
  //                ; 00:00:00 - 23:59:59
  // wkday        = "Mon" | "Tue" | "Wed"
  //              | "Thu" | "Fri" | "Sat" | "Sun"
  // month        = "Jan" | "Feb" | "Mar" | "Apr"
  //              | "May" | "Jun" | "Jul" | "Aug"
  //              | "Sep" | "Oct" | "Nov" | "Dec"
  //

  const wkdayStrings = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
  const monthStrings = [
    "Jan",
    "Feb",
    "Mar",
    "Apr",
    "May",
    "Jun",
    "Jul",
    "Aug",
    "Sep",
    "Oct",
    "Nov",
    "Dec",
  ];

  /**
   * Processes a date and returns the encoded UTC time as a string according to
   * the format specified in RFC 2616.
   *
   * @param date : Date
   *   the date to process
   * @returns string
   *   a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59"
   */
  function toTime(date) {
    var hrs = date.getUTCHours();
    var rv = hrs < 10 ? "0" + hrs : hrs;

    var mins = date.getUTCMinutes();
    rv += ":";
    rv += mins < 10 ? "0" + mins : mins;

    var secs = date.getUTCSeconds();
    rv += ":";
    rv += secs < 10 ? "0" + secs : secs;

    return rv;
  }

  /**
   * Processes a date and returns the encoded UTC date as a string according to
   * the date1 format specified in RFC 2616.
   *
   * @param date : Date
   *   the date to process
   * @returns string
   *   a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59"
   */
  function toDate1(date) {
    var day = date.getUTCDate();
    var month = date.getUTCMonth();
    var year = date.getUTCFullYear();

    var rv = day < 10 ? "0" + day : day;
    rv += " " + monthStrings[month];
    rv += " " + year;

    return rv;
  }

  date = new Date(date);

  const fmtString = "%wkday%, %date1% %time% GMT";
  var rv = fmtString.replace("%wkday%", wkdayStrings[date.getUTCDay()]);
  rv = rv.replace("%time%", toTime(date));
  return rv.replace("%date1%", toDate1(date));
}

/**
 * Instantiates a new HTTP server.
 */
export function nsHttpServer() {
  /** The port on which this server listens. */
  this._port = undefined;

  /** The socket associated with this. */
  this._socket = null;

  /** The handler used to process requests to this server. */
  this._handler = new ServerHandler(this);

  /** Naming information for this server. */
  this._identity = new ServerIdentity();

  /**
   * Indicates when the server is to be shut down at the end of the request.
   */
  this._doQuit = false;

  /**
   * True if the socket in this is closed (and closure notifications have been
   * sent and processed if the socket was ever opened), false otherwise.
   */
  this._socketClosed = true;

  /**
   * Used for tracking existing connections and ensuring that all connections
   * are properly cleaned up before server shutdown; increases by 1 for every
   * new incoming connection.
   */
  this._connectionGen = 0;

  /**
   * Hash of all open connections, indexed by connection number at time of
   * creation.
   */
  this._connections = {};
}

nsHttpServer.prototype = {
  // NSISERVERSOCKETLISTENER

  /**
   * Processes an incoming request coming in on the given socket and contained
   * in the given transport.
   *
   * @param socket : nsIServerSocket
   *   the socket through which the request was served
   * @param trans : nsISocketTransport
   *   the transport for the request/response
   * @see nsIServerSocketListener.onSocketAccepted
   */
  onSocketAccepted(socket, trans) {
    dumpn("*** onSocketAccepted(socket=" + socket + ", trans=" + trans + ")");

    dumpn(">>> new connection on " + trans.host + ":" + trans.port);

    const SEGMENT_SIZE = 8192;
    const SEGMENT_COUNT = 1024;
    try {
      var input = trans
        .openInputStream(0, SEGMENT_SIZE, SEGMENT_COUNT)
        .QueryInterface(Ci.nsIAsyncInputStream);
      var output = trans.openOutputStream(0, 0, 0);
    } catch (e) {
      dumpn("*** error opening transport streams: " + e);
      trans.close(Cr.NS_BINDING_ABORTED);
      return;
    }

    var connectionNumber = ++this._connectionGen;

    try {
      var conn = new Connection(
        input,
        output,
        this,
        socket.port,
        trans.port,
        connectionNumber,
        trans
      );
      var reader = new RequestReader(conn);

      // XXX add request timeout functionality here!

      // Note: must use main thread here, or we might get a GC that will cause
      //       threadsafety assertions.  We really need to fix XPConnect so that
      //       you can actually do things in multi-threaded JS.  :-(
      input.asyncWait(reader, 0, 0, Services.tm.mainThread);
    } catch (e) {
      // Assume this connection can't be salvaged and bail on it completely;
      // don't attempt to close it so that we can assert that any connection
      // being closed is in this._connections.
      dumpn("*** error in initial request-processing stages: " + e);
      trans.close(Cr.NS_BINDING_ABORTED);
      return;
    }

    this._connections[connectionNumber] = conn;
    dumpn("*** starting connection " + connectionNumber);
  },

  /**
   * Called when the socket associated with this is closed.
   *
   * @param socket : nsIServerSocket
   *   the socket being closed
   * @param status : nsresult
   *   the reason the socket stopped listening (NS_BINDING_ABORTED if the server
   *   was stopped using nsIHttpServer.stop)
   * @see nsIServerSocketListener.onStopListening
   */
  onStopListening(socket) {
    dumpn(">>> shutting down server on port " + socket.port);
    for (var n in this._connections) {
      if (!this._connections[n]._requestStarted) {
        this._connections[n].close();
      }
    }
    this._socketClosed = true;
    if (this._hasOpenConnections()) {
      dumpn("*** open connections!!!");
    }
    if (!this._hasOpenConnections()) {
      dumpn("*** no open connections, notifying async from onStopListening");

      // Notify asynchronously so that any pending teardown in stop() has a
      // chance to run first.
      var self = this;
      var stopEvent = {
        run() {
          dumpn("*** _notifyStopped async callback");
          self._notifyStopped();
        },
      };
      Services.tm.currentThread.dispatch(
        stopEvent,
        Ci.nsIThread.DISPATCH_NORMAL
      );
    }
  },

  // NSIHTTPSERVER

  //
  // see nsIHttpServer.start
  //
  start(port) {
    this._start(port, "localhost");
  },

  //
  // see nsIHttpServer.start_ipv6
  //
  start_ipv6(port) {
    this._start(port, "[::1]");
  },

  start_dualStack(port) {
    this._start(port, "[::1]", true);
  },

  _start(port, host, dualStack) {
    if (this._socket) {
      throw Components.Exception("", Cr.NS_ERROR_ALREADY_INITIALIZED);
    }

    this._port = port;
    this._doQuit = this._socketClosed = false;

    this._host = host;

    // The listen queue needs to be long enough to handle
    // network.http.max-persistent-connections-per-server or
    // network.http.max-persistent-connections-per-proxy concurrent
    // connections, plus a safety margin in case some other process is
    // talking to the server as well.
    var maxConnections =
      5 +
      Math.max(
        Services.prefs.getIntPref(
          "network.http.max-persistent-connections-per-server"
        ),
        Services.prefs.getIntPref(
          "network.http.max-persistent-connections-per-proxy"
        )
      );

    try {
      var loopback = true;
      if (
        this._host != "127.0.0.1" &&
        this._host != "localhost" &&
        this._host != "[::1]"
      ) {
        loopback = false;
      }

      // When automatically selecting a port, sometimes the chosen port is
      // "blocked" from clients. We don't want to use these ports because
      // tests will intermittently fail. So, we simply keep trying to to
      // get a server socket until a valid port is obtained. We limit
      // ourselves to finite attempts just so we don't loop forever.
      var socket;
      for (var i = 100; i; i--) {
        var temp = null;
        if (dualStack) {
          temp = new ServerSocketDualStack(this._port, maxConnections);
        } else if (this._host.includes(":")) {
          temp = new ServerSocketIPv6(
            this._port,
            loopback, // true = localhost, false = everybody
            maxConnections
          );
        } else {
          temp = new ServerSocket(
            this._port,
            loopback, // true = localhost, false = everybody
            maxConnections
          );
        }

        var allowed = Services.io.allowPort(temp.port, "http");
        if (!allowed) {
          dumpn(
            ">>>Warning: obtained ServerSocket listens on a blocked " +
              "port: " +
              temp.port
          );
        }

        if (!allowed && this._port == -1) {
          dumpn(">>>Throwing away ServerSocket with bad port.");
          temp.close();
          continue;
        }

        socket = temp;
        break;
      }

      if (!socket) {
        throw new Error(
          "No socket server available. Are there no available ports?"
        );
      }

      socket.asyncListen(this);
      this._port = socket.port;
      this._identity._initialize(socket.port, host, true, dualStack);
      this._socket = socket;
      dumpn(
        ">>> listening on port " +
          socket.port +
          ", " +
          maxConnections +
          " pending connections"
      );
    } catch (e) {
      dump("\n!!! could not start server on port " + port + ": " + e + "\n\n");
      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
    }
  },

  //
  // see nsIHttpServer.stop
  //
  stop(callback) {
    if (!this._socket) {
      throw Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
    }

    // If no argument was provided to stop, return a promise.
    let returnValue = undefined;
    if (!callback) {
      returnValue = new Promise(resolve => {
        callback = resolve;
      });
    }

    this._stopCallback =
      typeof callback === "function"
        ? callback
        : function () {
            callback.onStopped();
          };

    dumpn(">>> stopping listening on port " + this._socket.port);
    this._socket.close();
    this._socket = null;

    // We can't have this identity any more, and the port on which we're running
    // this server now could be meaningless the next time around.
    this._identity._teardown();

    this._doQuit = false;

    // socket-close notification and pending request completion happen async

    return returnValue;
  },

  //
  // see nsIHttpServer.registerFile
  //
  registerFile(path, file, handler) {
    if (file && (!file.exists() || file.isDirectory())) {
      throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
    }

    this._handler.registerFile(path, file, handler);
  },

  //
  // see nsIHttpServer.registerDirectory
  //
  registerDirectory(path, directory) {
    // XXX true path validation!
    if (
      path.charAt(0) != "/" ||
      path.charAt(path.length - 1) != "/" ||
      (directory && (!directory.exists() || !directory.isDirectory()))
    ) {
      throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
    }

    // XXX determine behavior of nonexistent /foo/bar when a /foo/bar/ mapping
    //     exists!

    this._handler.registerDirectory(path, directory);
  },

  //
  // see nsIHttpServer.registerPathHandler
  //
  registerPathHandler(path, handler) {
    this._handler.registerPathHandler(path, handler);
  },

  //
  // see nsIHttpServer.registerPrefixHandler
  //
  registerPrefixHandler(prefix, handler) {
    this._handler.registerPrefixHandler(prefix, handler);
  },

  //
  // see nsIHttpServer.registerErrorHandler
  //
  registerErrorHandler(code, handler) {
    this._handler.registerErrorHandler(code, handler);
  },

  //
  // see nsIHttpServer.setIndexHandler
  //
  setIndexHandler(handler) {
    this._handler.setIndexHandler(handler);
  },

  //
  // see nsIHttpServer.registerContentType
  //
  registerContentType(ext, type) {
    this._handler.registerContentType(ext, type);
  },

  get connectionNumber() {
    return this._connectionGen;
  },

  //
  // see nsIHttpServer.serverIdentity
  //
  get identity() {
    return this._identity;
  },

  //
  // see nsIHttpServer.getState
  //
  getState(path, k) {
    return this._handler._getState(path, k);
  },

  //
  // see nsIHttpServer.setState
  //
  setState(path, k, v) {
    return this._handler._setState(path, k, v);
  },

  //
  // see nsIHttpServer.getSharedState
  //
  getSharedState(k) {
    return this._handler._getSharedState(k);
  },

  //
  // see nsIHttpServer.setSharedState
  //
  setSharedState(k, v) {
    return this._handler._setSharedState(k, v);
  },

  //
  // see nsIHttpServer.getObjectState
  //
  getObjectState(k) {
    return this._handler._getObjectState(k);
  },

  //
  // see nsIHttpServer.setObjectState
  //
  setObjectState(k, v) {
    return this._handler._setObjectState(k, v);
  },

  get wrappedJSObject() {
    return this;
  },

  // NSISUPPORTS

  //
  // see nsISupports.QueryInterface
  //
  QueryInterface: ChromeUtils.generateQI([
    "nsIHttpServer",
    "nsIServerSocketListener",
  ]),

  // NON-XPCOM PUBLIC API

  /**
   * Returns true iff this server is not running (and is not in the process of
   * serving any requests still to be processed when the server was last
   * stopped after being run).
   */
  isStopped() {
    return this._socketClosed && !this._hasOpenConnections();
  },

  // PRIVATE IMPLEMENTATION

  /** True if this server has any open connections to it, false otherwise. */
  _hasOpenConnections() {
    //
    // If we have any open connections, they're tracked as numeric properties on
    // |this._connections|.  The non-standard __count__ property could be used
    // to check whether there are any properties, but standard-wise, even
    // looking forward to ES5, there's no less ugly yet still O(1) way to do
    // this.
    //
    for (var n in this._connections) {
      return true;
    }
    return false;
  },

  /** Calls the server-stopped callback provided when stop() was called. */
  _notifyStopped() {
    NS_ASSERT(this._stopCallback !== null, "double-notifying?");
    NS_ASSERT(!this._hasOpenConnections(), "should be done serving by now");

    //
    // NB: We have to grab this now, null out the member, *then* call the
    //     callback here, or otherwise the callback could (indirectly) futz with
    //     this._stopCallback by starting and immediately stopping this, at
    //     which point we'd be nulling out a field we no longer have a right to
    //     modify.
    //
    var callback = this._stopCallback;
    this._stopCallback = null;
    try {
      callback();
    } catch (e) {
      // not throwing because this is specified as being usually (but not
      // always) asynchronous
      dump("!!! error running onStopped callback: " + e + "\n");
    }
  },

  /**
   * Notifies this server that the given connection has been closed.
   *
   * @param connection : Connection
   *   the connection that was closed
   */
  _connectionClosed(connection) {
    NS_ASSERT(
      connection.number in this._connections,
      "closing a connection " +
        this +
        " that we never added to the " +
        "set of open connections?"
    );
    NS_ASSERT(
      this._connections[connection.number] === connection,
      "connection number mismatch?  " + this._connections[connection.number]
    );
    delete this._connections[connection.number];

    // Fire a pending server-stopped notification if it's our responsibility.
    if (!this._hasOpenConnections() && this._socketClosed) {
      this._notifyStopped();
    }
  },

  /**
   * Requests that the server be shut down when possible.
   */
  _requestQuit() {
    dumpn(">>> requesting a quit");
    dumpStack();
    this._doQuit = true;
  },
};

export var HttpServer = nsHttpServer;

export class NodeServer {
  // Executes command in the context of a node server.
  // See handler in moz-http2.js
  //
  // Example use:
  // let id = NodeServer.fork(); // id is a random string
  // await NodeServer.execute(id, `"hello"`)
  // > "hello"
  // await NodeServer.execute(id, `(() => "hello")()`)
  // > "hello"
  // await NodeServer.execute(id, `(() => var_defined_on_server)()`)
  // > "0"
  // await NodeServer.execute(id, `var_defined_on_server`)
  // > "0"
  // function f(param) { if (param) return param; return "bla"; }
  // await NodeServer.execute(id, f); // Defines the function on the server
  // await NodeServer.execute(id, `f()`) // executes defined function
  // > "bla"
  // let result = await NodeServer.execute(id, `f("test")`);
  // > "test"
  // await NodeServer.kill(id); // shuts down the server

  // Forks a new node server using moz-http2-child.js as a starting point
  static fork() {
    return this.sendCommand("", "/fork");
  }
  // Executes command in the context of the node server indicated by `id`
  static execute(id, command) {
    return this.sendCommand(command, `/execute/${id}`);
  }
  // Shuts down the server
  static kill(id) {
    return this.sendCommand("", `/kill/${id}`);
  }

  // Issues a request to the node server (handler defined in moz-http2.js)
  // This method should not be called directly.
  static sendCommand(command, path) {
    let h2Port = Services.env.get("MOZNODE_EXEC_PORT");
    if (!h2Port) {
      throw new Error("Could not find MOZNODE_EXEC_PORT");
    }

    let req = new XMLHttpRequest();
    const serverIP =
      AppConstants.platform == "android" ? "10.0.2.2" : "127.0.0.1";
    req.open("POST", `http://${serverIP}:${h2Port}${path}`);
    req.channel.QueryInterface(Ci.nsIHttpChannelInternal).bypassProxy = true;

    // Passing a function to NodeServer.execute will define that function
    // in node. It can be called in a later execute command.
    let isFunction = function (obj) {
      return !!(obj && obj.constructor && obj.call && obj.apply);
    };
    let payload = command;
    if (isFunction(command)) {
      payload = `${command.name} = ${command.toString()};`;
    }

    return new Promise((resolve, reject) => {
      req.onload = () => {
        let x = null;

        if (req.statusText != "OK") {
          reject(`XHR request failed: ${req.statusText}`);
          return;
        }

        try {
          x = JSON.parse(req.responseText);
        } catch (e) {
          reject(`Failed to parse ${req.responseText} - ${e}`);
          return;
        }

        if (x.error) {
          let e = new Error(x.error, "", 0);
          e.stack = x.errorStack;
          reject(e);
          return;
        }
        resolve(x.result);
      };
      req.onerror = e => {
        reject(e);
      };

      req.send(payload.toString());
    });
  }
}

//
// RFC 2396 section 3.2.2:
//
// host        = hostname | IPv4address
// hostname    = *( domainlabel "." ) toplabel [ "." ]
// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
// toplabel    = alpha | alpha *( alphanum | "-" ) alphanum
// IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit
//
// IPv6 addresses are notably lacking in the above definition of 'host'.
// RFC 2732 section 3 extends the host definition:
// host          = hostname | IPv4address | IPv6reference
// ipv6reference = "[" IPv6address "]"
//
// RFC 3986 supersedes RFC 2732 and offers a more precise definition of a IPv6
// address. For simplicity, the regexp below captures all canonical IPv6
// addresses (e.g. [::1]), but may also match valid non-canonical IPv6 addresses
// (e.g. [::127.0.0.1]) and even invalid bracketed addresses ([::], [99999::]).

const HOST_REGEX = new RegExp(
  "^(?:" +
    // *( domainlabel "." )
    "(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)*" +
    // toplabel [ "." ]
    "[a-z](?:[a-z0-9-]*[a-z0-9])?\\.?" +
    "|" +
    // IPv4 address
    "\\d+\\.\\d+\\.\\d+\\.\\d+" +
    "|" +
    // IPv6 addresses (e.g. [::1])
    "\\[[:0-9a-f]+\\]" +
    ")$",
  "i"
);

/**
 * Represents the identity of a server.  An identity consists of a set of
 * (scheme, host, port) tuples denoted as locations (allowing a single server to
 * serve multiple sites or to be used behind both HTTP and HTTPS proxies for any
 * host/port).  Any incoming request must be to one of these locations, or it
 * will be rejected with an HTTP 400 error.  One location, denoted as the
 * primary location, is the location assigned in contexts where a location
 * cannot otherwise be endogenously derived, such as for HTTP/1.0 requests.
 *
 * A single identity may contain at most one location per unique host/port pair;
 * other than that, no restrictions are placed upon what locations may
 * constitute an identity.
 */
function ServerIdentity() {
  /** The scheme of the primary location. */
  this._primaryScheme = "http";

  /** The hostname of the primary location. */
  this._primaryHost = "127.0.0.1";

  /** The port number of the primary location. */
  this._primaryPort = -1;

  /**
   * The current port number for the corresponding server, stored so that a new
   * primary location can always be set if the current one is removed.
   */
  this._defaultPort = -1;

  /**
   * Maps hosts to maps of ports to schemes, e.g. the following would represent
   * https://example.com:789/ and http://example.org/:
   *
   *   {
   *     "xexample.com": { 789: "https" },
   *     "xexample.org": { 80: "http" }
   *   }
   *
   * Note the "x" prefix on hostnames, which prevents collisions with special
   * JS names like "prototype".
   */
  this._locations = { xlocalhost: {} };
}
ServerIdentity.prototype = {
  // NSIHTTPSERVERIDENTITY

  //
  // see nsIHttpServerIdentity.primaryScheme
  //
  get primaryScheme() {
    if (this._primaryPort === -1) {
      throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED);
    }
    return this._primaryScheme;
  },

  //
  // see nsIHttpServerIdentity.primaryHost
  //
  get primaryHost() {
    if (this._primaryPort === -1) {
      throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED);
    }
    return this._primaryHost;
  },

  //
  // see nsIHttpServerIdentity.primaryPort
  //
  get primaryPort() {
    if (this._primaryPort === -1) {
      throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED);
    }
    return this._primaryPort;
  },

  //
  // see nsIHttpServerIdentity.add
  //
  add(scheme, host, port) {
    this._validate(scheme, host, port);

    var entry = this._locations["x" + host];
    if (!entry) {
      this._locations["x" + host] = entry = {};
    }

    entry[port] = scheme;
  },

  //
  // see nsIHttpServerIdentity.remove
  //
  remove(scheme, host, port) {
    this._validate(scheme, host, port);

    var entry = this._locations["x" + host];
    if (!entry) {
      return false;
    }

    var present = port in entry;
    delete entry[port];

    if (
      this._primaryScheme == scheme &&
      this._primaryHost == host &&
      this._primaryPort == port &&
      this._defaultPort !== -1
    ) {
      // Always keep at least one identity in existence at any time, unless
      // we're in the process of shutting down (the last condition above).
      this._primaryPort = -1;
      this._initialize(this._defaultPort, host, false);
    }

    return present;
  },

  //
  // see nsIHttpServerIdentity.has
  //
  has(scheme, host, port) {
    this._validate(scheme, host, port);

    return (
      "x" + host in this._locations &&
      scheme === this._locations["x" + host][port]
    );
  },

  //
  // see nsIHttpServerIdentity.has
  //
  getScheme(host, port) {
    this._validate("http", host, port);

    var entry = this._locations["x" + host];
    if (!entry) {
      return "";
    }

    return entry[port] || "";
  },

  //
  // see nsIHttpServerIdentity.setPrimary
  //
  setPrimary(scheme, host, port) {
    this._validate(scheme, host, port);

    this.add(scheme, host, port);

    this._primaryScheme = scheme;
    this._primaryHost = host;
    this._primaryPort = port;
  },

  // NSISUPPORTS

  //
  // see nsISupports.QueryInterface
  //
  QueryInterface: ChromeUtils.generateQI(["nsIHttpServerIdentity"]),

  // PRIVATE IMPLEMENTATION

  /**
   * Initializes the primary name for the corresponding server, based on the
   * provided port number.
   */
  _initialize(port, host, addSecondaryDefault, dualStack) {
    this._host = host;
    if (this._primaryPort !== -1) {
      this.add("http", host, port);
    } else {
      this.setPrimary("http", "localhost", port);
    }
    this._defaultPort = port;

    // Only add this if we're being called at server startup
    if (addSecondaryDefault && host != "127.0.0.1") {
      if (host.includes(":")) {
        this.add("http", "[::1]", port);
        if (dualStack) {
          this.add("http", "127.0.0.1", port);
        }
      } else {
        this.add("http", "127.0.0.1", port);
      }
    }
  },

  /**
   * Called at server shutdown time, unsets the primary location only if it was
   * the default-assigned location and removes the default location from the
   * set of locations used.
   */
  _teardown() {
    if (this._host != "127.0.0.1") {
      // Not the default primary location, nothing special to do here
      this.remove("http", "127.0.0.1", this._defaultPort);
    }

    // This is a *very* tricky bit of reasoning here; make absolutely sure the
    // tests for this code pass before you commit changes to it.
    if (
      this._primaryScheme == "http" &&
      this._primaryHost == this._host &&
      this._primaryPort == this._defaultPort
    ) {
      // Make sure we don't trigger the readding logic in .remove(), then remove
      // the default location.
      var port = this._defaultPort;
      this._defaultPort = -1;
      this.remove("http", this._host, port);

      // Ensure a server start triggers the setPrimary() path in ._initialize()
      this._primaryPort = -1;
    } else {
      // No reason not to remove directly as it's not our primary location
      this.remove("http", this._host, this._defaultPort);
    }
  },

  /**
   * Ensures scheme, host, and port are all valid with respect to RFC 2396.
   *
   * @throws NS_ERROR_ILLEGAL_VALUE
   *   if any argument doesn't match the corresponding production
   */
  _validate(scheme, host, port) {
    if (scheme !== "http" && scheme !== "https") {
      dumpn("*** server only supports http/https schemes: '" + scheme + "'");
      dumpStack();
      throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE);
    }
    if (!HOST_REGEX.test(host)) {
      dumpn("*** unexpected host: '" + host + "'");
      throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE);
    }
    if (port < 0 || port > 65535) {
      dumpn("*** unexpected port: '" + port + "'");
      throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE);
    }
  },
};

/**
 * Represents a connection to the server (and possibly in the future the thread
 * on which the connection is processed).
 *
 * @param input : nsIInputStream
 *   stream from which incoming data on the connection is read
 * @param output : nsIOutputStream
 *   stream to write data out the connection
 * @param server : nsHttpServer
 *   the server handling the connection
 * @param port : int
 *   the port on which the server is running
 * @param outgoingPort : int
 *   the outgoing port used by this connection
 * @param number : uint
 *   a serial number used to uniquely identify this connection
 */
function Connection(
  input,
  output,
  server,
  port,
  outgoingPort,
  number,
  transport
) {
  dumpn("*** opening new connection " + number + " on port " + outgoingPort);

  /** Stream of incoming data. */
  this.input = input;

  /** Stream for outgoing data. */
  this.output = output;

  /** The server associated with this request. */
  this.server = server;

  /** The port on which the server is running. */
  this.port = port;

  /** The outgoing poort used by this connection. */
  this._outgoingPort = outgoingPort;

  /** The serial number of this connection. */
  this.number = number;

  /** Reference to the underlying transport. */
  this.transport = transport;

  /**
   * The request for which a response is being generated, null if the
   * incoming request has not been fully received or if it had errors.
   */
  this.request = null;

  /** This allows a connection to disambiguate between a peer initiating a
   *  close and the socket being forced closed on shutdown.
   */
  this._closed = false;

  /** State variable for debugging. */
  this._processed = false;

  /** whether or not 1st line of request has been received */
  this._requestStarted = false;
}
Connection.prototype = {
  /** Closes this connection's input/output streams. */
  close() {
    if (this._closed) {
      return;
    }

    dumpn(
      "*** closing connection " + this.number + " on port " + this._outgoingPort
    );

    this.input.close();
    this.output.close();
    this._closed = true;

    var server = this.server;
    server._connectionClosed(this);

    // If an error triggered a server shutdown, act on it now
    if (server._doQuit) {
      server.stop(function () {
        /* not like we can do anything better */
      });
    }
  },

  /**
   * Initiates processing of this connection, using the data in the given
   * request.
   *
   * @param request : Request
   *   the request which should be processed
   */
  process(request) {
    NS_ASSERT(!this._closed && !this._processed);

    this._processed = true;

    this.request = request;
    this.server._handler.handleResponse(this);
  },

  /**
   * Initiates processing of this connection, generating a response with the
   * given HTTP error code.
   *
   * @param code : uint
   *   an HTTP code, so in the range [0, 1000)
   * @param request : Request
   *   incomplete data about the incoming request (since there were errors
   *   during its processing
   */
  processError(code, request) {
    NS_ASSERT(!this._closed && !this._processed);

    this._processed = true;
    this.request = request;
    this.server._handler.handleError(code, this);
  },

  /** Converts this to a string for debugging purposes. */
  toString() {
    return (
      "<Connection(" +
      this.number +
      (this.request ? ", " + this.request.path : "") +
      "): " +
      (this._closed ? "closed" : "open") +
      ">"
    );
  },

  requestStarted() {
    this._requestStarted = true;
  },
};

/** Returns an array of count bytes from the given input stream. */
function readBytes(inputStream, count) {
  return new BinaryInputStream(inputStream).readByteArray(count);
}

/** Request reader processing states; see RequestReader for details. */
const READER_IN_REQUEST_LINE = 0;
const READER_IN_HEADERS = 1;
const READER_IN_BODY = 2;
const READER_FINISHED = 3;

/**
 * Reads incoming request data asynchronously, does any necessary preprocessing,
 * and forwards it to the request handler.  Processing occurs in three states:
 *
 *   READER_IN_REQUEST_LINE     Reading the request's status line
 *   READER_IN_HEADERS          Reading headers in the request
 *   READER_IN_BODY             Reading the body of the request
 *   READER_FINISHED            Entire request has been read and processed
 *
 * During the first two stages, initial metadata about the request is gathered
 * into a Request object.  Once the status line and headers have been processed,
 * we start processing the body of the request into the Request.  Finally, when
 * the entire body has been read, we create a Response and hand it off to the
 * ServerHandler to be given to the appropriate request handler.
 *
 * @param connection : Connection
 *   the connection for the request being read
 */
function RequestReader(connection) {
  /** Connection metadata for this request. */
  this._connection = connection;

  /**
   * A container providing line-by-line access to the raw bytes that make up the
   * data which has been read from the connection but has not yet been acted
   * upon (by passing it to the request handler or by extracting request
   * metadata from it).
   */
  this._data = new LineData();

  /**
   * The amount of data remaining to be read from the body of this request.
   * After all headers in the request have been read this is the value in the
   * Content-Length header, but as the body is read its value decreases to zero.
   */
  this._contentLength = 0;

  /** The current state of parsing the incoming request. */
  this._state = READER_IN_REQUEST_LINE;

  /** Metadata constructed from the incoming request for the request handler. */
  this._metadata = new Request(connection.port);

  /**
   * Used to preserve state if we run out of line data midway through a
   * multi-line header.  _lastHeaderName stores the name of the header, while
   * _lastHeaderValue stores the value we've seen so far for the header.
   *
   * These fields are always either both undefined or both strings.
   */
  this._lastHeaderName = this._lastHeaderValue = undefined;
}
RequestReader.prototype = {
  // NSIINPUTSTREAMCALLBACK

  /**
   * Called when more data from the incoming request is available.  This method
   * then reads the available data from input and deals with that data as
   * necessary, depending upon the syntax of already-downloaded data.
   *
   * @param input : nsIAsyncInputStream
   *   the stream of incoming data from the connection
   */
  onInputStreamReady(input) {
    dumpn(
      "*** onInputStreamReady(input=" +
        input +
        ") on thread " +
        Services.tm.currentThread +
        " (main is " +
        Services.tm.mainThread +
        ")"
    );
    dumpn("*** this._state == " + this._state);

    // Handle cases where we get more data after a request error has been
    // discovered but *before* we can close the connection.
    var data = this._data;
    if (!data) {
      return;
    }

    try {
      data.appendBytes(readBytes(input, input.available()));
    } catch (e) {
      if (streamClosed(e)) {
        dumpn(
          "*** WARNING: unexpected error when reading from socket; will " +
            "be treated as if the input stream had been closed"
        );
        dumpn("*** WARNING: actual error was: " + e);
      }

      // We've lost a race -- input has been closed, but we're still expecting
      // to read more data.  available() will throw in this case, and since
      // we're dead in the water now, destroy the connection.
      dumpn(
        "*** onInputStreamReady called on a closed input, destroying " +
          "connection"
      );
      this._connection.close();
      return;
    }

    switch (this._state) {
      default:
        NS_ASSERT(false, "invalid state: " + this._state);
        break;

      case READER_IN_REQUEST_LINE:
        if (!this._processRequestLine()) {
          break;
        }
      /* fall through */

      case READER_IN_HEADERS:
        if (!this._processHeaders()) {
          break;
        }
      /* fall through */

      case READER_IN_BODY:
        this._processBody();
    }

    if (this._state != READER_FINISHED) {
      input.asyncWait(this, 0, 0, Services.tm.currentThread);
    }
  },

  //
  // see nsISupports.QueryInterface
  //
  QueryInterface: ChromeUtils.generateQI(["nsIInputStreamCallback"]),

  // PRIVATE API

  /**
   * Processes unprocessed, downloaded data as a request line.
   *
   * @returns boolean
   *   true iff the request line has been fully processed
   */
  _processRequestLine() {
    NS_ASSERT(this._state == READER_IN_REQUEST_LINE);

    // Servers SHOULD ignore any empty line(s) received where a Request-Line
    // is expected (section 4.1).
    var data = this._data;
    var line = {};
    var readSuccess;
    while ((readSuccess = data.readLine(line)) && line.value == "") {
      dumpn("*** ignoring beginning blank line...");
    }

    // if we don't have a full line, wait until we do
    if (!readSuccess) {
      return false;
    }

    // we have the first non-blank line
    try {
      this._parseRequestLine(line.value);
      this._state = READER_IN_HEADERS;
      this._connection.requestStarted();
      return true;
    } catch (e) {
      this._handleError(e);
      return false;
    }
  },

  /**
   * Processes stored data, assuming it is either at the beginning or in
   * the middle of processing request headers.
   *
   * @returns boolean
   *   true iff header data in the request has been fully processed
   */
  _processHeaders() {
    NS_ASSERT(this._state == READER_IN_HEADERS);

    // XXX things to fix here:
    //
    // - need to support RFC 2047-encoded non-US-ASCII characters

    try {
      var done = this._parseHeaders();
      if (done) {
        var request = this._metadata;

        // XXX this is wrong for requests with transfer-encodings applied to
        //     them, particularly chunked (which by its nature can have no
        //     meaningful Content-Length header)!
        this._contentLength = request.hasHeader("Content-Length")
          ? parseInt(request.getHeader("Content-Length"), 10)
          : 0;
        dumpn("_processHeaders, Content-length=" + this._contentLength);

        this._state = READER_IN_BODY;
      }
      return done;
    } catch (e) {
      this._handleError(e);
      return false;
    }
  },

  /**
   * Processes stored data, assuming it is either at the beginning or in
   * the middle of processing the request body.
   *
   * @returns boolean
   *   true iff the request body has been fully processed
   */
  _processBody() {
    NS_ASSERT(this._state == READER_IN_BODY);

    // XXX handle chunked transfer-coding request bodies!

    try {
      if (this._contentLength > 0) {
        var data = this._data.purge();
        var count = Math.min(data.length, this._contentLength);
        dumpn(
          "*** loading data=" +
            data +
            " len=" +
            data.length +
            " excess=" +
            (data.length - count)
        );
        data.length = count;

        var bos = new BinaryOutputStream(this._metadata._bodyOutputStream);
        bos.writeByteArray(data);
        this._contentLength -= count;
      }

      dumpn("*** remaining body data len=" + this._contentLength);
      if (this._contentLength == 0) {
        this._validateRequest();
        this._state = READER_FINISHED;
        this._handleResponse();
        return true;
      }

      return false;
    } catch (e) {
      this._handleError(e);
      return false;
    }
  },

  /**
   * Does various post-header checks on the data in this request.
   *
   * @throws : HttpError
   *   if the request was malformed in some way
   */
  _validateRequest() {
    NS_ASSERT(this._state == READER_IN_BODY);

    dumpn("*** _validateRequest");

    var metadata = this._metadata;
    var headers = metadata._headers;

    // 19.6.1.1 -- servers MUST report 400 to HTTP/1.1 requests w/o Host header
    var identity = this._connection.server.identity;
    if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1)) {
      if (!headers.hasHeader("Host")) {
        dumpn("*** malformed HTTP/1.1 or greater request with no Host header!");
        throw HTTP_400;
      }

      // If the Request-URI wasn't absolute, then we need to determine our host.
      // We have to determine what scheme was used to access us based on the
      // server identity data at this point, because the request just doesn't
      // contain enough data on its own to do this, sadly.
      if (!metadata._host) {
        var host, port;
        var hostPort = headers.getHeader("Host");
        var colon = hostPort.lastIndexOf(":");
        if (hostPort.lastIndexOf("]") > colon) {
          colon = -1;
        }
        if (colon < 0) {
          host = hostPort;
          port = "";
        } else {
          host = hostPort.substring(0, colon);
          port = hostPort.substring(colon + 1);
        }

        // NB: We allow an empty port here because, oddly, a colon may be
        //     present even without a port number, e.g. "example.com:"; in this
        //     case the default port applies.
        if (!HOST_REGEX.test(host) || !/^\d*$/.test(port)) {
          dumpn(
            "*** malformed hostname (" +
              hostPort +
              ") in Host " +
              "header, 400 time"
          );
          throw HTTP_400;
        }

        // If we're not given a port, we're stuck, because we don't know what
        // scheme to use to look up the correct port here, in general.  Since
        // the HTTPS case requires a tunnel/proxy and thus requires that the
        // requested URI be absolute (and thus contain the necessary
        // information), let's assume HTTP will prevail and use that.
        port = +port || 80;

        var scheme = identity.getScheme(host, port);
        if (!scheme) {
          dumpn(
            "*** unrecognized hostname (" +
              hostPort +
              ") in Host " +
              "header, 400 time"
          );
          throw HTTP_400;
        }

        metadata._scheme = scheme;
        metadata._host = host;
        metadata._port = port;
      }
    } else {
      NS_ASSERT(
        metadata._host === undefined,
        "HTTP/1.0 doesn't allow absolute paths in the request line!"
      );

      metadata._scheme = identity.primaryScheme;
      metadata._host = identity.primaryHost;
      metadata._port = identity.primaryPort;
    }

    NS_ASSERT(
      identity.has(metadata._scheme, metadata._host, metadata._port),
      "must have a location we recognize by now!"
    );
  },

  /**
   * Handles responses in case of error, either in the server or in the request.
   *
   * @param e
   *   the specific error encountered, which is an HttpError in the case where
   *   the request is in some way invalid or cannot be fulfilled; if this isn't
   *   an HttpError we're going to be paranoid and shut down, because that
   *   shouldn't happen, ever
   */
  _handleError(e) {
    // Don't fall back into normal processing!
    this._state = READER_FINISHED;

    var server = this._connection.server;
    if (e instanceof HttpError) {
      var code = e.code;
    } else {
      dumpn(
        "!!! UNEXPECTED ERROR: " +
          e +
          (e.lineNumber ? ", line " + e.lineNumber : "")
      );

      // no idea what happened -- be paranoid and shut down
      code = 500;
      server._requestQuit();
    }

    // make attempted reuse of data an error
    this._data = null;

    this._connection.processError(code, this._metadata);
  },

  /**
   * Now that we've read the request line and headers, we can actually hand off
   * the request to be handled.
   *
   * This method is called once per request, after the request line and all
   * headers and the body, if any, have been received.
   */
  _handleResponse() {
    NS_ASSERT(this._state == READER_FINISHED);

    // We don't need the line-based data any more, so make attempted reuse an
    // error.
    this._data = null;

    this._connection.process(this._metadata);
  },

  // PARSING

  /**
   * Parses the request line for the HTTP request associated with this.
   *
   * @param line : string
   *   the request line
   */
  _parseRequestLine(line) {
    NS_ASSERT(this._state == READER_IN_REQUEST_LINE);

    dumpn("*** _parseRequestLine('" + line + "')");

    var metadata = this._metadata;

    // clients and servers SHOULD accept any amount of SP or HT characters
    // between fields, even though only a single SP is required (section 19.3)
    var request = line.split(/[ \t]+/);
    if (!request || request.length != 3) {
      dumpn("*** No request in line");
      throw HTTP_400;
    }

    metadata._method = request[0];

    // get the HTTP version
    var ver = request[2];
    var match = ver.match(/^HTTP\/(\d+\.\d+)$/);
    if (!match) {
      dumpn("*** No HTTP version in line");
      throw HTTP_400;
    }

    // determine HTTP version
    try {
      metadata._httpVersion = new nsHttpVersion(match[1]);
      if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_0)) {
        throw new Error("unsupported HTTP version");
      }
    } catch (e) {
      // we support HTTP/1.0 and HTTP/1.1 only
      throw HTTP_501;
    }

    var fullPath = request[1];

    if (metadata._method == "CONNECT") {
      metadata._path = "CONNECT";
      metadata._scheme = "https";
      [metadata._host, metadata._port] = fullPath.split(":");
      return;
    }

    var serverIdentity = this._connection.server.identity;
    var scheme, host, port;

    if (fullPath.charAt(0) != "/") {
      // No absolute paths in the request line in HTTP prior to 1.1
      if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1)) {
        dumpn("*** Metadata version too low");
        throw HTTP_400;
      }

      try {
        var uri = Services.io.newURI(fullPath);
        fullPath = uri.pathQueryRef;
        scheme = uri.scheme;
        host = uri.asciiHost;
        if (host.includes(":")) {
          // If the host still contains a ":", then it is an IPv6 address.
          // IPv6 addresses-as-host are registered with brackets, so we need to
          // wrap the host in brackets because nsIURI's host lacks them.
          // This inconsistency in nsStandardURL is tracked at bug 1195459.
          host = `[${host}]`;
        }
        metadata._host = host;
        port = uri.port;
        if (port === -1) {
          if (scheme === "http") {
            port = 80;
          } else if (scheme === "https") {
            port = 443;
          } else {
            dumpn("*** Unknown scheme: " + scheme);
            throw HTTP_400;
          }
        }
      } catch (e) {
        // If the host is not a valid host on the server, the response MUST be a
        // 400 (Bad Request) error message (section 5.2).  Alternately, the URI
        // is malformed.
        dumpn("*** Threw when dealing with URI: " + e);
        throw HTTP_400;
      }

      if (
        !serverIdentity.has(scheme, host, port) ||
        fullPath.charAt(0) != "/"
      ) {
        dumpn("*** serverIdentity unknown or path does not start with '/'");
        throw HTTP_400;
      }
    }

    var splitter = fullPath.indexOf("?");
    if (splitter < 0) {
      // _queryString already set in ctor
      metadata._path = fullPath;
    } else {
      metadata._path = fullPath.substring(0, splitter);
      metadata._queryString = fullPath.substring(splitter + 1);
    }

    metadata._scheme = scheme;
    metadata._host = host;
    metadata._port = port;
  },

  /**
   * Parses all available HTTP headers in this until the header-ending CRLFCRLF,
   * adding them to the store of headers in the request.
   *
   * @throws
   *   HTTP_400 if the headers are malformed
   * @returns boolean
   *   true if all headers have now been processed, false otherwise
   */
  _parseHeaders() {
    NS_ASSERT(this._state == READER_IN_HEADERS);

    dumpn("*** _parseHeaders");

    var data = this._data;

    var headers = this._metadata._headers;
    var lastName = this._lastHeaderName;
    var lastVal = this._lastHeaderValue;

    var line = {};
    while (true) {
      dumpn("*** Last name: '" + lastName + "'");
      dumpn("*** Last val: '" + lastVal + "'");
      NS_ASSERT(
        !((lastVal === undefined) ^ (lastName === undefined)),
        lastName === undefined
          ? "lastVal without lastName?  lastVal: '" + lastVal + "'"
          : "lastName without lastVal?  lastName: '" + lastName + "'"
      );

      if (!data.readLine(line)) {
        // save any data we have from the header we might still be processing
        this._lastHeaderName = lastName;
        this._lastHeaderValue = lastVal;
        return false;
      }

      var lineText = line.value;
      dumpn("*** Line text: '" + lineText + "'");
      var firstChar = lineText.charAt(0);

      // blank line means end of headers
      if (lineText == "") {
        // we're finished with the previous header
        if (lastName) {
          try {
            headers.setHeader(lastName, lastVal, true);
          } catch (e) {
            dumpn("*** setHeader threw on last header, e == " + e);
            throw HTTP_400;
          }
        } else {
          // no headers in request -- valid for HTTP/1.0 requests
        }

        // either way, we're done processing headers
        this._state = READER_IN_BODY;
        return true;
      } else if (firstChar == " " || firstChar == "\t") {
        // multi-line header if we've already seen a header line
        if (!lastName) {
          dumpn("We don't have a header to continue!");
          throw HTTP_400;
        }

        // append this line's text to the value; starts with SP/HT, so no need
        // for separating whitespace
        lastVal += lineText;
      } else {
        // we have a new header, so set the old one (if one existed)
        if (lastName) {
          try {
            headers.setHeader(lastName, lastVal, true);
          } catch (e) {
            dumpn("*** setHeader threw on a header, e == " + e);
            throw HTTP_400;
          }
        }

        var colon = lineText.indexOf(":"); // first colon must be splitter
        if (colon < 1) {
          dumpn("*** No colon or missing header field-name");
          throw HTTP_400;
        }

        // set header name, value (to be set in the next loop, usually)
        lastName = lineText.substring(0, colon);
        lastVal = lineText.substring(colon + 1);
      } // empty, continuation, start of header
    } // while (true)
  },
};

/** The character codes for CR and LF. */
const CR = 0x0d,
  LF = 0x0a;

/**
 * Calculates the number of characters before the first CRLF pair in array, or
 * -1 if the array contains no CRLF pair.
 *
 * @param array : Array
 *   an array of numbers in the range [0, 256), each representing a single
 *   character; the first CRLF is the lowest index i where
 *   |array[i] == "\r".charCodeAt(0)| and |array[i+1] == "\n".charCodeAt(0)|,
 *   if such an |i| exists, and -1 otherwise
 * @param start : uint
 *   start index from which to begin searching in array
 * @returns int
 *   the index of the first CRLF if any were present, -1 otherwise
 */
function findCRLF(array, start) {
  for (var i = array.indexOf(CR, start); i >= 0; i = array.indexOf(CR, i + 1)) {
    if (array[i + 1] == LF) {
      return i;
    }
  }
  return -1;
}

/**
 * A container which provides line-by-line access to the arrays of bytes with
 * which it is seeded.
 */
export function LineData() {
  /** An array of queued bytes from which to get line-based characters. */
  this._data = [];

  /** Start index from which to search for CRLF. */
  this._start = 0;
}
LineData.prototype = {
  /**
   * Appends the bytes in the given array to the internal data cache maintained
   * by this.
   */
  appendBytes(bytes) {
    var count = bytes.length;
    var quantum = 262144; // just above half SpiderMonkey's argument-count limit
    if (count < quantum) {
      Array.prototype.push.apply(this._data, bytes);
      return;
    }

    // Large numbers of bytes may cause Array.prototype.push to be called with
    // more arguments than the JavaScript engine supports.  In that case append
    // bytes in fixed-size amounts until all bytes are appended.
    for (var start = 0; start < count; start += quantum) {
      var slice = bytes.slice(start, Math.min(start + quantum, count));
      Array.prototype.push.apply(this._data, slice);
    }
  },

  /**
   * Removes and returns a line of data, delimited by CRLF, from this.
   *
   * @param out
   *   an object whose "value" property will be set to the first line of text
   *   present in this, sans CRLF, if this contains a full CRLF-delimited line
   *   of text; if this doesn't contain enough data, the value of the property
   *   is undefined
   * @returns boolean
   *   true if a full line of data could be read from the data in this, false
   *   otherwise
   */
  readLine(out) {
    var data = this._data;
    var length = findCRLF(data, this._start);
    if (length < 0) {
      this._start = data.length;

      // But if our data ends in a CR, we have to back up one, because
      // the first byte in the next packet might be an LF and if we
      // start looking at data.length we won't find it.
      if (data.length && data[data.length - 1] === CR) {
        --this._start;
      }

      return false;
    }

    // Reset for future lines.
    this._start = 0;

    //
    // We have the index of the CR, so remove all the characters, including
    // CRLF, from the array with splice, and convert the removed array
    // (excluding the trailing CRLF characters) into the corresponding string.
    //
    var leading = data.splice(0, length + 2);
    var quantum = 262144;
    var line = "";
    for (var start = 0; start < length; start += quantum) {
      var slice = leading.slice(start, Math.min(start + quantum, length));
      line += String.fromCharCode.apply(null, slice);
    }

    out.value = line;
    return true;
  },

  /**
   * Removes the bytes currently within this and returns them in an array.
   *
   * @returns Array
   *   the bytes within this when this method is called
   */
  purge() {
    var data = this._data;
    this._data = [];
    return data;
  },
};

/**
 * Creates a request-handling function for an nsIHttpRequestHandler object.
 */
function createHandlerFunc(handler) {
  return function (metadata, response) {
    handler.handle(metadata, response);
  };
}

/**
 * The default handler for directories; writes an HTML response containing a
 * slightly-formatted directory listing.
 */
function defaultIndexHandler(metadata, response) {
  response.setHeader("Content-Type", "text/html;charset=utf-8", false);

  var path = htmlEscape(decodeURI(metadata.path));

  //
  // Just do a very basic bit of directory listings -- no need for too much
  // fanciness, especially since we don't have a style sheet in which we can
  // stick rules (don't want to pollute the default path-space).
  //

  var body =
    "<html>\
                <head>\
                  <title>" +
    path +
    "</title>\
                </head>\
                <body>\
                  <h1>" +
    path +
    '</h1>\
                  <ol style="list-style-type: none">';

  var directory = metadata.getProperty("directory");
  NS_ASSERT(directory && directory.isDirectory());

  var fileList = [];
  var files = directory.directoryEntries;
  while (files.hasMoreElements()) {
    var f = files.nextFile;
    let name = f.leafName;
    if (
      !f.isHidden() &&
      (name.charAt(name.length - 1) != HIDDEN_CHAR ||
        name.charAt(name.length - 2) == HIDDEN_CHAR)
    ) {
      fileList.push(f);
    }
  }

  fileList.sort(fileSort);

  for (var i = 0; i < fileList.length; i++) {
    var file = fileList[i];
    try {
      let name = file.leafName;
      if (name.charAt(name.length - 1) == HIDDEN_CHAR) {
        name = name.substring(0, name.length - 1);
      }
      var sep = file.isDirectory() ? "/" : "";

      // Note: using " to delimit the attribute here because encodeURIComponent
      //       passes through '.
      var item =
        '<li><a href="' +
        encodeURIComponent(name) +
        sep +
        '">' +
        htmlEscape(name) +
        sep +
        "</a></li>";

      body += item;
    } catch (e) {
      /* some file system error, ignore the file */
    }
  }

  body +=
    "    </ol>\
                </body>\
              </html>";

  response.bodyOutputStream.write(body, body.length);
}

/**
 * Sorts a and b (nsIFile objects) into an aesthetically pleasing order.
 */
function fileSort(a, b) {
  var dira = a.isDirectory(),
    dirb = b.isDirectory();

  if (dira && !dirb) {
    return -1;
  }
  if (dirb && !dira) {
    return 1;
  }

  var namea = a.leafName.toLowerCase(),
    nameb = b.leafName.toLowerCase();
  return nameb > namea ? -1 : 1;
}

/**
 * Converts an externally-provided path into an internal path for use in
 * determining file mappings.
 *
 * @param path
 *   the path to convert
 * @param encoded
 *   true if the given path should be passed through decodeURI prior to
 *   conversion
 * @throws URIError
 *   if path is incorrectly encoded
 */
function toInternalPath(path, encoded) {
  if (encoded) {
    path = decodeURI(path);
  }

  var comps = path.split("/");
  for (var i = 0, sz = comps.length; i < sz; i++) {
    var comp = comps[i];
    if (comp.charAt(comp.length - 1) == HIDDEN_CHAR) {
      comps[i] = comp + HIDDEN_CHAR;
    }
  }
  return comps.join("/");
}

const PERMS_READONLY = (4 << 6) | (4 << 3) | 4;

/**
 * Adds custom-specified headers for the given file to the given response, if
 * any such headers are specified.
 *
 * @param file
 *   the file on the disk which is to be written
 * @param metadata
 *   metadata about the incoming request
 * @param response
 *   the Response to which any specified headers/data should be written
 * @throws HTTP_500
 *   if an error occurred while processing custom-specified headers
 */
function maybeAddHeadersInternal(
  file,
  metadata,
  response,
  informationalResponse
) {
  var name = file.leafName;
  if (name.charAt(name.length - 1) == HIDDEN_CHAR) {
    name = name.substring(0, name.length - 1);
  }

  var headerFile = file.parent;
  if (!informationalResponse) {
    headerFile.append(name + HEADERS_SUFFIX);
  } else {
    headerFile.append(name + INFORMATIONAL_RESPONSE_SUFFIX);
  }

  if (!headerFile.exists()) {
    return;
  }

  const PR_RDONLY = 0x01;
  var fis = new FileInputStream(
    headerFile,
    PR_RDONLY,
    PERMS_READONLY,
    Ci.nsIFileInputStream.CLOSE_ON_EOF
  );

  try {
    var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0);
    lis.QueryInterface(Ci.nsIUnicharLineInputStream);

    var line = { value: "" };
    var more = lis.readLine(line);

    if (!more && line.value == "") {
      return;
    }

    // request line

    var status = line.value;
    if (status.indexOf("HTTP ") == 0) {
      status = status.substring(5);
      var space = status.indexOf(" ");
      var code, description;
      if (space < 0) {
        code = status;
        description = "";
      } else {
        code = status.substring(0, space);
        description = status.substring(space + 1, status.length);
      }

      if (!informationalResponse) {
        response.setStatusLine(
          metadata.httpVersion,
          parseInt(code, 10),
          description
        );
      } else {
        response.setInformationalResponseStatusLine(
          metadata.httpVersion,
          parseInt(code, 10),
          description
        );
      }

      line.value = "";
      more = lis.readLine(line);
    } else if (informationalResponse) {
      // An informational response must have a status line.
      return;
    }

    // headers
    while (more || line.value != "") {
      var header = line.value;
      var colon = header.indexOf(":");

      if (!informationalResponse) {
        response.setHeader(
          header.substring(0, colon),
          header.substring(colon + 1, header.length),
          false
        ); // allow overriding server-set headers
      } else {
        response.setInformationalResponseHeader(
          header.substring(0, colon),
          header.substring(colon + 1, header.length),
          false
        ); // allow overriding server-set headers
      }

      line.value = "";
      more = lis.readLine(line);
    }
  } catch (e) {
    dumpn("WARNING: error in headers for " + metadata.path + ": " + e);
    throw HTTP_500;
  } finally {
    fis.close();
  }
}

function maybeAddHeaders(file, metadata, response) {
  maybeAddHeadersInternal(file, metadata, response, false);
}

function maybeAddInformationalResponse(file, metadata, response) {
  maybeAddHeadersInternal(file, metadata, response, true);
}

/**
 * An object which handles requests for a server, executing default and
 * overridden behaviors as instructed by the code which uses and manipulates it.
 * Default behavior includes the paths / and /trace (diagnostics), with some
 * support for HTTP error pages for various codes and fallback to HTTP 500 if
 * those codes fail for any reason.
 *
 * @param server : nsHttpServer
 *   the server in which this handler is being used
 */
function ServerHandler(server) {
  // FIELDS

  /**
   * The nsHttpServer instance associated with this handler.
   */
  this._server = server;

  /**
   * A FileMap object containing the set of path->nsIFile mappings for
   * all directory mappings set in the server (e.g., "/" for /var/www/html/,
   * "/foo/bar/" for /local/path/, and "/foo/bar/baz/" for /local/path2).
   *
   * Note carefully: the leading and trailing "/" in each path (not file) are
   * removed before insertion to simplify the code which uses this.  You have
   * been warned!
   */
  this._pathDirectoryMap = new FileMap();

  /**
   * Custom request handlers for the server in which this resides.  Path-handler
   * pairs are stored as property-value pairs in this property.
   *
   * @see ServerHandler.prototype._defaultPaths
   */
  this._overridePaths = {};

  /**
   * Custom request handlers for the path prefixes on the server in which this
   * resides.  Path-handler pairs are stored as property-value pairs in this
   * property.
   *
   * @see ServerHandler.prototype._defaultPaths
   */
  this._overridePrefixes = {};

  /**
   * Custom request handlers for the error handlers in the server in which this
   * resides.  Path-handler pairs are stored as property-value pairs in this
   * property.
   *
   * @see ServerHandler.prototype._defaultErrors
   */
  this._overrideErrors = {};

  /**
   * Maps file extensions to their MIME types in the server, overriding any
   * mapping that might or might not exist in the MIME service.
   */
  this._mimeMappings = {};

  /**
   * The default handler for requests for directories, used to serve directories
   * when no index file is present.
   */
  this._indexHandler = defaultIndexHandler;

  /** Per-path state storage for the server. */
  this._state = {};

  /** Entire-server state storage. */
  this._sharedState = {};

  /** Entire-server state storage for nsISupports values. */
  this._objectState = {};
}
ServerHandler.prototype = {
  // PUBLIC API

  /**
   * Handles a request to this server, responding to the request appropriately
   * and initiating server shutdown if necessary.
   *
   * This method never throws an exception.
   *
   * @param connection : Connection
   *   the connection for this request
   */
  handleResponse(connection) {
    var request = connection.request;
    var response = new Response(connection);

    var path = request.path;
    dumpn("*** path == " + path);

    try {
      try {
        if (path in this._overridePaths) {
          // explicit paths first, then files based on existing directory mappings,
          // then (if the file doesn't exist) built-in server default paths
          dumpn("calling override for " + path);
          this._overridePaths[path](request, response);
        } else {
          var longestPrefix = "";
          for (let prefix in this._overridePrefixes) {
            if (
              prefix.length > longestPrefix.length &&
              path.substr(0, prefix.length) == prefix
            ) {
              longestPrefix = prefix;
            }
          }
          if (longestPrefix.length) {
            dumpn("calling prefix override for " + longestPrefix);
            this._overridePrefixes[longestPrefix](request, response);
          } else {
            this._handleDefault(request, response);
          }
        }
      } catch (e) {
        if (response.partiallySent()) {
          response.abort(e);
          return;
        }

        if (!(e instanceof HttpError)) {
          dumpn("*** unexpected error: e == " + e);
          throw HTTP_500;
        }
        if (e.code !== 404) {
          throw e;
        }

        dumpn("*** default: " + (path in this._defaultPaths));

        response = new Response(connection);
        if (path in this._defaultPaths) {
          this._defaultPaths[path](request, response);
        } else {
          throw HTTP_404;
        }
      }
    } catch (e) {
      if (response.partiallySent()) {
        response.abort(e);
        return;
      }

      var errorCode = "internal";

      try {
        if (!(e instanceof HttpError)) {
          throw e;
        }

        errorCode = e.code;
        dumpn("*** errorCode == " + errorCode);

        response = new Response(connection);
        if (e.customErrorHandling) {
          e.customErrorHandling(response);
        }
        this._handleError(errorCode, request, response);
        return;
      } catch (e2) {
        dumpn(
          "*** error handling " +
            errorCode +
            " error: " +
            "e2 == " +
            e2 +
            ", shutting down server"
        );

        connection.server._requestQuit();
        response.abort(e2);
        return;
      }
    }

    response.complete();
  },

  //
  // see nsIHttpServer.registerFile
  //
  registerFile(path, file, handler) {
    if (!file) {
      dumpn("*** unregistering '" + path + "' mapping");
      delete this._overridePaths[path];
      return;
    }

    dumpn("*** registering '" + path + "' as mapping to " + file.path);
    file = file.clone();

    var self = this;
    this._overridePaths[path] = function (request, response) {
      if (!file.exists()) {
        throw HTTP_404;
      }

      dumpn("*** responding '" + path + "' as mapping to " + file.path);

      response.setStatusLine(request.httpVersion, 200, "OK");
      if (typeof handler === "function") {
        handler(request, response);
      }
      self._writeFileResponse(request, file, response, 0, file.fileSize);
    };
  },

  //
  // see nsIHttpServer.registerPathHandler
  //
  registerPathHandler(path, handler) {
    if (!path.length) {
      throw Components.Exception(
        "Handler path cannot be empty",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    // XXX true path validation!
    if (path.charAt(0) != "/" && path != "CONNECT") {
      throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
    }

    this._handlerToField(handler, this._overridePaths, path);
  },

  //
  // see nsIHttpServer.registerPrefixHandler
  //
  registerPrefixHandler(path, handler) {
    // XXX true path validation!
    if (path.charAt(0) != "/" || path.charAt(path.length - 1) != "/") {
      throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
    }

    this._handlerToField(handler, this._overridePrefixes, path);
  },

  //
  // see nsIHttpServer.registerDirectory
  //
  registerDirectory(path, directory) {
    // strip off leading and trailing '/' so that we can use lastIndexOf when
    // determining exactly how a path maps onto a mapped directory --
    // conditional is required here to deal with "/".substring(1, 0) being
    // converted to "/".substring(0, 1) per the JS specification
    var key = path.length == 1 ? "" : path.substring(1, path.length - 1);

    // the path-to-directory mapping code requires that the first character not
    // be "/", or it will go into an infinite loop
    if (key.charAt(0) == "/") {
      throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
    }

    key = toInternalPath(key, false);

    if (directory) {
      dumpn("*** mapping '" + path + "' to the location " + directory.path);
      this._pathDirectoryMap.put(key, directory);
    } else {
      dumpn("*** removing mapping for '" + path + "'");
      this._pathDirectoryMap.put(key, null);
    }
  },

  //
  // see nsIHttpServer.registerErrorHandler
  //
  registerErrorHandler(err, handler) {
    if (!(err in HTTP_ERROR_CODES)) {
      dumpn(
        "*** WARNING: registering non-HTTP/1.1 error code " +
          "(" +
          err +
          ") handler -- was this intentional?"
      );
    }

    this._handlerToField(handler, this._overrideErrors, err);
  },

  //
  // see nsIHttpServer.setIndexHandler
  //
  setIndexHandler(handler) {
    if (!handler) {
      handler = defaultIndexHandler;
    } else if (typeof handler != "function") {
      handler = createHandlerFunc(handler);
    }

    this._indexHandler = handler;
  },

  //
  // see nsIHttpServer.registerContentType
  //
  registerContentType(ext, type) {
    if (!type) {
      delete this._mimeMappings[ext];
    } else {
      this._mimeMappings[ext] = headerUtils.normalizeFieldValue(type);
    }
  },

  // PRIVATE API

  /**
   * Sets or remove (if handler is null) a handler in an object with a key.
   *
   * @param handler
   *   a handler, either function or an nsIHttpRequestHandler
   * @param dict
   *   The object to attach the handler to.
   * @param key
   *   The field name of the handler.
   */
  _handlerToField(handler, dict, key) {
    // for convenience, handler can be a function if this is run from xpcshell
    if (typeof handler == "function") {
      dict[key] = handler;
    } else if (handler) {
      dict[key] = createHandlerFunc(handler);
    } else {
      delete dict[key];
    }
  },

  /**
   * Handles a request which maps to a file in the local filesystem (if a base
   * path has already been set; otherwise the 404 error is thrown).
   *
   * @param metadata : Request
   *   metadata for the incoming request
   * @param response : Response
   *   an uninitialized Response to the given request, to be initialized by a
   *   request handler
   * @throws HTTP_###
   *   if an HTTP error occurred (usually HTTP_404); note that in this case the
   *   calling code must handle post-processing of the response
   */
  _handleDefault(metadata, response) {
    dumpn("*** _handleDefault()");

    response.setStatusLine(metadata.httpVersion, 200, "OK");

    var path = metadata.path;
    NS_ASSERT(path.charAt(0) == "/", "invalid path: <" + path + ">");

    // determine the actual on-disk file; this requires finding the deepest
    // path-to-directory mapping in the requested URL
    var file = this._getFileForPath(path);

    // the "file" might be a directory, in which case we either serve the
    // contained index.html or make the index handler write the response
    if (file.exists() && file.isDirectory()) {
      file.append("index.html"); // make configurable?
      if (!file.exists() || file.isDirectory()) {
        metadata._ensurePropertyBag();
        metadata._bag.setPropertyAsInterface("directory", file.parent);
        this._indexHandler(metadata, response);
        return;
      }
    }

    // alternately, the file might not exist
    if (!file.exists()) {
      throw HTTP_404;
    }

    var start, end;
    if (
      metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1) &&
      metadata.hasHeader("Range") &&
      this._getTypeFromFile(file) !== SJS_TYPE
    ) {
      var rangeMatch = metadata
        .getHeader("Range")
        .match(/^bytes=(\d+)?-(\d+)?$/);
      if (!rangeMatch) {
        dumpn(
          "*** Range header bogosity: '" + metadata.getHeader("Range") + "'"
        );
        throw HTTP_400;
      }

      if (rangeMatch[1] !== undefined) {
        start = parseInt(rangeMatch[1], 10);
      }

      if (rangeMatch[2] !== undefined) {
        end = parseInt(rangeMatch[2], 10);
      }

      if (start === undefined && end === undefined) {
        dumpn(
          "*** More Range header bogosity: '" +
            metadata.getHeader("Range") +
            "'"
        );
        throw HTTP_400;
      }

      // No start given, so the end is really the count of bytes from the
      // end of the file.
      if (start === undefined) {
        start = Math.max(0, file.fileSize - end);
        end = file.fileSize - 1;
      }

      // start and end are inclusive
      if (end === undefined || end >= file.fileSize) {
        end = file.fileSize - 1;
      }

      if (start !== undefined && start >= file.fileSize) {
        var HTTP_416 = new HttpError(416, "Requested Range Not Satisfiable");
        HTTP_416.customErrorHandling = function (errorResponse) {
          maybeAddHeaders(file, metadata, errorResponse);
        };
        throw HTTP_416;
      }

      if (end < start) {
        response.setStatusLine(metadata.httpVersion, 200, "OK");
        start = 0;
        end = file.fileSize - 1;
      } else {
        response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
        var contentRange = "bytes " + start + "-" + end + "/" + file.fileSize;
        response.setHeader("Content-Range", contentRange);
      }
    } else {
      start = 0;
      end = file.fileSize - 1;
    }

    // finally...
    dumpn(
      "*** handling '" +
        path +
        "' as mapping to " +
        file.path +
        " from " +
        start +
        " to " +
        end +
        " inclusive"
    );
    this._writeFileResponse(metadata, file, response, start, end - start + 1);
  },

  /**
   * Writes an HTTP response for the given file, including setting headers for
   * file metadata.
   *
   * @param metadata : Request
   *   the Request for which a response is being generated
   * @param file : nsIFile
   *   the file which is to be sent in the response
   * @param response : Response
   *   the response to which the file should be written
   * @param offset: uint
   *   the byte offset to skip to when writing
   * @param count: uint
   *   the number of bytes to write
   */
  _writeFileResponse(metadata, file, response, offset, count) {
    const PR_RDONLY = 0x01;

    var type = this._getTypeFromFile(file);
    if (type === SJS_TYPE) {
      let fis = new FileInputStream(
        file,
        PR_RDONLY,
        PERMS_READONLY,
        Ci.nsIFileInputStream.CLOSE_ON_EOF
      );

      try {
        // If you update the list of imports, please update the list in
        // tools/lint/eslint/eslint-plugin-mozilla/lib/environments/sjs.js
        // as well.
        var s = Cu.Sandbox(Cu.getGlobalForObject({}), {
          wantGlobalProperties: [
            "atob",
            "btoa",
            "ChromeUtils",
            "IOUtils",
            "PathUtils",
            "TextDecoder",
            "TextEncoder",
            "URLSearchParams",
            "URL",
          ],
        });
        s.importFunction(dump, "dump");
        s.importFunction(Services, "Services");

        // Define a basic key-value state-preservation API across requests, with
        // keys initially corresponding to the empty string.
        var self = this;
        var path = metadata.path;
        s.importFunction(function getState(k) {
          return self._getState(path, k);
        });
        s.importFunction(function setState(k, v) {
          self._setState(path, k, v);
        });
        s.importFunction(function getSharedState(k) {
          return self._getSharedState(k);
        });
        s.importFunction(function setSharedState(k, v) {
          self._setSharedState(k, v);
        });
        s.importFunction(function getObjectState(k, callback) {
          callback(self._getObjectState(k));
        });
        s.importFunction(function setObjectState(k, v) {
          self._setObjectState(k, v);
        });
        s.importFunction(function registerPathHandler(p, h) {
          self.registerPathHandler(p, h);
        });

        // Make it possible for sjs files to access their location
        this._setState(path, "__LOCATION__", file.path);

        try {
          // Alas, the line number in errors dumped to console when calling the
          // request handler is simply an offset from where we load the SJS file.
          // Work around this in a reasonably non-fragile way by dynamically
          // getting the line number where we evaluate the SJS file.  Don't
          // separate these two lines!
          var line = new Error().lineNumber;
          let uri = Services.io.newFileURI(file);
          Services.scriptloader.loadSubScript(uri.spec, s);
        } catch (e) {
          dumpn("*** syntax error in SJS at " + file.path + ": " + e);
          throw HTTP_500;
        }

        try {
          s.handleRequest(metadata, response);
        } catch (e) {
          dump(
            "*** error running SJS at " +
              file.path +
              ": " +
              e +
              " on line " +
              (e instanceof Error
                ? e.lineNumber + " in httpd.js"
                : e.lineNumber - line) +
              "\n"
          );
          throw HTTP_500;
        }
      } finally {
        fis.close();
      }
    } else {
      try {
        response.setHeader(
          "Last-Modified",
          toDateString(file.lastModifiedTime),
          false
        );
      } catch (e) {
        /* lastModifiedTime threw, ignore */
      }

      response.setHeader("Content-Type", type, false);
      maybeAddInformationalResponse(file, metadata, response);
      maybeAddHeaders(file, metadata, response);
      // Allow overriding Content-Length
      try {
        response.getHeader("Content-Length");
      } catch (e) {
        response.setHeader("Content-Length", "" + count, false);
      }

      let fis = new FileInputStream(
        file,
        PR_RDONLY,
        PERMS_READONLY,
        Ci.nsIFileInputStream.CLOSE_ON_EOF
      );

      offset = offset || 0;
      count = count || file.fileSize;
      NS_ASSERT(offset === 0 || offset < file.fileSize, "bad offset");
      NS_ASSERT(count >= 0, "bad count");
      NS_ASSERT(offset + count <= file.fileSize, "bad total data size");

      try {
        if (offset !== 0) {
          // Seek (or read, if seeking isn't supported) to the correct offset so
          // the data sent to the client matches the requested range.
          if (fis instanceof Ci.nsISeekableStream) {
            fis.seek(Ci.nsISeekableStream.NS_SEEK_SET, offset);
          } else {
            new ScriptableInputStream(fis).read(offset);
          }
        }
      } catch (e) {
        fis.close();
        throw e;
      }

      let writeMore = function () {
        Services.tm.currentThread.dispatch(
          writeData,
          Ci.nsIThread.DISPATCH_NORMAL
        );
      };

      var input = new BinaryInputStream(fis);
      var output = new BinaryOutputStream(response.bodyOutputStream);
      var writeData = {
        run() {
          var chunkSize = Math.min(65536, count);
          count -= chunkSize;
          NS_ASSERT(count >= 0, "underflow");

          try {
            var data = input.readByteArray(chunkSize);
            NS_ASSERT(
              data.length === chunkSize,
              "incorrect data returned?  got " +
                data.length +
                ", expected " +
                chunkSize
            );
            output.writeByteArray(data);
            if (count === 0) {
              fis.close();
              response.finish();
            } else {
              writeMore();
            }
          } catch (e) {
            try {
              fis.close();
            } finally {
              response.finish();
            }
            throw e;
          }
        },
      };

      writeMore();

      // Now that we know copying will start, flag the response as async.
      response.processAsync();
    }
  },

  /**
   * Get the value corresponding to a given key for the given path for SJS state
   * preservation across requests.
   *
   * @param path : string
   *   the path from which the given state is to be retrieved
   * @param k : string
   *   the key whose corresponding value is to be returned
   * @returns string
   *   the corresponding value, which is initially the empty string
   */
  _getState(path, k) {
    var state = this._state;
    if (path in state && k in state[path]) {
      return state[path][k];
    }
    return "";
  },

  /**
   * Set the value corresponding to a given key for the given path for SJS state
   * preservation across requests.
   *
   * @param path : string
   *   the path from which the given state is to be retrieved
   * @param k : string
   *   the key whose corresponding value is to be set
   * @param v : string
   *   the value to be set
   */
  _setState(path, k, v) {
    if (typeof v !== "string") {
      throw new Error("non-string value passed");
    }
    var state = this._state;
    if (!(path in state)) {
      state[path] = {};
    }
    state[path][k] = v;
  },

  /**
   * Get the value corresponding to a given key for SJS state preservation
   * across requests.
   *
   * @param k : string
   *   the key whose corresponding value is to be returned
   * @returns string
   *   the corresponding value, which is initially the empty string
   */
  _getSharedState(k) {
    var state = this._sharedState;
    if (k in state) {
      return state[k];
    }
    return "";
  },

  /**
   * Set the value corresponding to a given key for SJS state preservation
   * across requests.
   *
   * @param k : string
   *   the key whose corresponding value is to be set
   * @param v : string
   *   the value to be set
   */
  _setSharedState(k, v) {
    if (typeof v !== "string") {
      throw new Error("non-string value passed");
    }
    this._sharedState[k] = v;
  },

  /**
   * Returns the object associated with the given key in the server for SJS
   * state preservation across requests.
   *
   * @param k : string
   *  the key whose corresponding object is to be returned
   * @returns nsISupports
   *  the corresponding object, or null if none was present
   */
  _getObjectState(k) {
    if (typeof k !== "string") {
      throw new Error("non-string key passed");
    }
    return this._objectState[k] || null;
  },

  /**
   * Sets the object associated with the given key in the server for SJS
   * state preservation across requests.
   *
   * @param k : string
   *  the key whose corresponding object is to be set
   * @param v : nsISupports
   *  the object to be associated with the given key; may be null
   */
  _setObjectState(k, v) {
    if (typeof k !== "string") {
      throw new Error("non-string key passed");
    }
    if (typeof v !== "object") {
      throw new Error("non-object value passed");
    }
    if (v && !("QueryInterface" in v)) {
      throw new Error(
        "must pass an nsISupports; use wrappedJSObject to ease " +
          "pain when using the server from JS"
      );
    }

    this._objectState[k] = v;
  },

  /**
   * Gets a content-type for the given file, first by checking for any custom
   * MIME-types registered with this handler for the file's extension, second by
   * asking the global MIME service for a content-type, and finally by failing
   * over to application/octet-stream.
   *
   * @param file : nsIFile
   *   the nsIFile for which to get a file type
   * @returns string
   *   the best content-type which can be determined for the file
   */
  _getTypeFromFile(file) {
    try {
      var name = file.leafName;
      var dot = name.lastIndexOf(".");
      if (dot > 0) {
        var ext = name.slice(dot + 1);
        if (ext in this._mimeMappings) {
          return this._mimeMappings[ext];
        }
      }
      return Cc["@mozilla.org/mime;1"]
        .getService(Ci.nsIMIMEService)
        .getTypeFromFile(file);
    } catch (e) {
      return "application/octet-stream";
    }
  },

  /**
   * Returns the nsIFile which corresponds to the path, as determined using
   * all registered path->directory mappings and any paths which are explicitly
   * overridden.
   *
   * @param path : string
   *   the server path for which a file should be retrieved, e.g. "/foo/bar"
   * @throws HttpError
   *   when the correct action is the corresponding HTTP error (i.e., because no
   *   mapping was found for a directory in path, the referenced file doesn't
   *   exist, etc.)
   * @returns nsIFile
   *   the file to be sent as the response to a request for the path
   */
  _getFileForPath(path) {
    // decode and add underscores as necessary
    try {
      path = toInternalPath(path, true);
    } catch (e) {
      dumpn("*** toInternalPath threw " + e);
      throw HTTP_400; // malformed path
    }

    // next, get the directory which contains this path
    var pathMap = this._pathDirectoryMap;

    // An example progression of tmp for a path "/foo/bar/baz/" might be:
    // "foo/bar/baz/", "foo/bar/baz", "foo/bar", "foo", ""
    var tmp = path.substring(1);
    while (true) {
      // do we have a match for current head of the path?
      var file = pathMap.get(tmp);
      if (file) {
        // XXX hack; basically disable showing mapping for /foo/bar/ when the
        //     requested path was /foo/bar, because relative links on the page
        //     will all be incorrect -- we really need the ability to easily
        //     redirect here instead
        if (
          tmp == path.substring(1) &&
          !!tmp.length &&
          tmp.charAt(tmp.length - 1) != "/"
        ) {
          file = null;
        } else {
          break;
        }
      }

      // if we've finished trying all prefixes, exit
      if (tmp == "") {
        break;
      }

      tmp = tmp.substring(0, tmp.lastIndexOf("/"));
    }

    // no mapping applies, so 404
    if (!file) {
      throw HTTP_404;
    }

    // last, get the file for the path within the determined directory
    var parentFolder = file.parent;
    var dirIsRoot = parentFolder == null;

    // Strategy here is to append components individually, making sure we
    // never move above the given directory; this allows paths such as
    // "<file>/foo/../bar" but prevents paths such as "<file>/../base-sibling";
    // this component-wise approach also means the code works even on platforms
    // which don't use "/" as the directory separator, such as Windows
    var leafPath = path.substring(tmp.length + 1);
    var comps = leafPath.split("/");
    for (var i = 0, sz = comps.length; i < sz; i++) {
      var comp = comps[i];

      if (comp == "..") {
        file = file.parent;
      } else if (comp == "." || comp == "") {
        continue;
      } else {
        file.append(comp);
      }

      if (!dirIsRoot && file.equals(parentFolder)) {
        throw HTTP_403;
      }
    }

    return file;
  },

  /**
   * Writes the error page for the given HTTP error code over the given
   * connection.
   *
   * @param errorCode : uint
   *   the HTTP error code to be used
   * @param connection : Connection
   *   the connection on which the error occurred
   */
  handleError(errorCode, connection) {
    var response = new Response(connection);

    dumpn("*** error in request: " + errorCode);

    this._handleError(errorCode, new Request(connection.port), response);
  },

  /**
   * Handles a request which generates the given error code, using the
   * user-defined error handler if one has been set, gracefully falling back to
   * the x00 status code if the code has no handler, and failing to status code
   * 500 if all else fails.
   *
   * @param errorCode : uint
   *   the HTTP error which is to be returned
   * @param metadata : Request
   *   metadata for the request, which will often be incomplete since this is an
   *   error
   * @param response : Response
   *   an uninitialized Response should be initialized when this method
   *   completes with information which represents the desired error code in the
   *   ideal case or a fallback code in abnormal circumstances (i.e., 500 is a
   *   fallback for 505, per HTTP specs)
   */
  _handleError(errorCode, metadata, response) {
    if (!metadata) {
      throw Components.Exception("", Cr.NS_ERROR_NULL_POINTER);
    }

    var errorX00 = errorCode - (errorCode % 100);

    try {
      if (!(errorCode in HTTP_ERROR_CODES)) {
        dumpn("*** WARNING: requested invalid error: " + errorCode);
      }

      // RFC 2616 says that we should try to handle an error by its class if we
      // can't otherwise handle it -- if that fails, we revert to handling it as
      // a 500 internal server error, and if that fails we throw and shut down
      // the server

      // actually handle the error
      try {
        if (errorCode in this._overrideErrors) {
          this._overrideErrors[errorCode](metadata, response);
        } else {
          this._defaultErrors[errorCode](metadata, response);
        }
      } catch (e) {
        if (response.partiallySent()) {
          response.abort(e);
          return;
        }

        // don't retry the handler that threw
        if (errorX00 == errorCode) {
          throw HTTP_500;
        }

        dumpn(
          "*** error in handling for error code " +
            errorCode +
            ", " +
            "falling back to " +
            errorX00 +
            "..."
        );
        response = new Response(response._connection);
        if (errorX00 in this._overrideErrors) {
          this._overrideErrors[errorX00](metadata, response);
        } else if (errorX00 in this._defaultErrors) {
          this._defaultErrors[errorX00](metadata, response);
        } else {
          throw HTTP_500;
        }
      }
    } catch (e) {
      if (response.partiallySent()) {
        response.abort();
        return;
      }

      // we've tried everything possible for a meaningful error -- now try 500
      dumpn(
        "*** error in handling for error code " +
          errorX00 +
          ", falling " +
          "back to 500..."
      );

      try {
        response = new Response(response._connection);
        if (500 in this._overrideErrors) {
          this._overrideErrors[500](metadata, response);
        } else {
          this._defaultErrors[500](metadata, response);
        }
      } catch (e2) {
        dumpn("*** multiple errors in default error handlers!");
        dumpn("*** e == " + e + ", e2 == " + e2);
        response.abort(e2);
        return;
      }
    }

    response.complete();
  },

  // FIELDS

  /**
   * This object contains the default handlers for the various HTTP error codes.
   */
  _defaultErrors: {
    400(metadata, response) {
      // none of the data in metadata is reliable, so hard-code everything here
      response.setStatusLine("1.1", 400, "Bad Request");
      response.setHeader("Content-Type", "text/plain;charset=utf-8", false);

      var body = "Bad request\n";
      response.bodyOutputStream.write(body, body.length);
    },
    403(metadata, response) {
      response.setStatusLine(metadata.httpVersion, 403, "Forbidden");
      response.setHeader("Content-Type", "text/html;charset=utf-8", false);

      var body =
        "<html>\
                    <head><title>403 Forbidden</title></head>\
                    <body>\
                      <h1>403 Forbidden</h1>\
                    </body>\
                  </html>";
      response.bodyOutputStream.write(body, body.length);
    },
    404(metadata, response) {
      response.setStatusLine(metadata.httpVersion, 404, "Not Found");
      response.setHeader("Content-Type", "text/html;charset=utf-8", false);

      var body =
        "<html>\
                    <head><title>404 Not Found</title></head>\
                    <body>\
                      <h1>404 Not Found</h1>\
                      <p>\
                        <span style='font-family: monospace;'>" +
        htmlEscape(metadata.path) +
        "</span> was not found.\
                      </p>\
                    </body>\
                  </html>";
      response.bodyOutputStream.write(body, body.length);
    },
    416(metadata, response) {
      response.setStatusLine(
        metadata.httpVersion,
        416,
        "Requested Range Not Satisfiable"
      );
      response.setHeader("Content-Type", "text/html;charset=utf-8", false);

      var body =
        "<html>\
                   <head>\
                    <title>416 Requested Range Not Satisfiable</title></head>\
                    <body>\
                     <h1>416 Requested Range Not Satisfiable</h1>\
                     <p>The byte range was not valid for the\
                        requested resource.\
                     </p>\
                    </body>\
                  </html>";
      response.bodyOutputStream.write(body, body.length);
    },
    500(metadata, response) {
      response.setStatusLine(
        metadata.httpVersion,
        500,
        "Internal Server Error"
      );
      response.setHeader("Content-Type", "text/html;charset=utf-8", false);

      var body =
        "<html>\
                    <head><title>500 Internal Server Error</title></head>\
                    <body>\
                      <h1>500 Internal Server Error</h1>\
                      <p>Something's broken in this server and\
                        needs to be fixed.</p>\
                    </body>\
                  </html>";
      response.bodyOutputStream.write(body, body.length);
    },
    501(metadata, response) {
      response.setStatusLine(metadata.httpVersion, 501, "Not Implemented");
      response.setHeader("Content-Type", "text/html;charset=utf-8", false);

      var body =
        "<html>\
                    <head><title>501 Not Implemented</title></head>\
                    <body>\
                      <h1>501 Not Implemented</h1>\
                      <p>This server is not (yet) Apache.</p>\
                    </body>\
                  </html>";
      response.bodyOutputStream.write(body, body.length);
    },
    505(metadata, response) {
      response.setStatusLine("1.1", 505, "HTTP Version Not Supported");
      response.setHeader("Content-Type", "text/html;charset=utf-8", false);

      var body =
        "<html>\
                    <head><title>505 HTTP Version Not Supported</title></head>\
                    <body>\
                      <h1>505 HTTP Version Not Supported</h1>\
                      <p>This server only supports HTTP/1.0 and HTTP/1.1\
                        connections.</p>\
                    </body>\
                  </html>";
      response.bodyOutputStream.write(body, body.length);
    },
  },

  /**
   * Contains handlers for the default set of URIs contained in this server.
   */
  _defaultPaths: {
    "/": function (metadata, response) {
      response.setStatusLine(metadata.httpVersion, 200, "OK");
      response.setHeader("Content-Type", "text/html;charset=utf-8", false);

      var body =
        "<html>\
                    <head><title>httpd.js</title></head>\
                    <body>\
                      <h1>httpd.js</h1>\
                      <p>If you're seeing this page, httpd.js is up and\
                        serving requests!  Now set a base path and serve some\
                        files!</p>\
                    </body>\
                  </html>";

      response.bodyOutputStream.write(body, body.length);
    },

    "/trace": function (metadata, response) {
      response.setStatusLine(metadata.httpVersion, 200, "OK");
      response.setHeader("Content-Type", "text/plain;charset=utf-8", false);

      var body =
        "Request-URI: " +
        metadata.scheme +
        "://" +
        metadata.host +
        ":" +
        metadata.port +
        metadata.path +
        "\n\n";
      body += "Request (semantically equivalent, slightly reformatted):\n\n";
      body += metadata.method + " " + metadata.path;

      if (metadata.queryString) {
        body += "?" + metadata.queryString;
      }

      body += " HTTP/" + metadata.httpVersion + "\r\n";

      var headEnum = metadata.headers;
      while (headEnum.hasMoreElements()) {
        var fieldName = headEnum
          .getNext()
          .QueryInterface(Ci.nsISupportsString).data;
        body += fieldName + ": " + metadata.getHeader(fieldName) + "\r\n";
      }

      response.bodyOutputStream.write(body, body.length);
    },
  },
};

/**
 * Maps absolute paths to files on the local file system (as nsILocalFiles).
 */
function FileMap() {
  /** Hash which will map paths to nsILocalFiles. */
  this._map = {};
}
FileMap.prototype = {
  // PUBLIC API

  /**
   * Maps key to a clone of the nsIFile value if value is non-null;
   * otherwise, removes any extant mapping for key.
   *
   * @param key : string
   *   string to which a clone of value is mapped
   * @param value : nsIFile
   *   the file to map to key, or null to remove a mapping
   */
  put(key, value) {
    if (value) {
      this._map[key] = value.clone();
    } else {
      delete this._map[key];
    }
  },

  /**
   * Returns a clone of the nsIFile mapped to key, or null if no such
   * mapping exists.
   *
   * @param key : string
   *   key to which the returned file maps
   * @returns nsIFile
   *   a clone of the mapped file, or null if no mapping exists
   */
  get(key) {
    var val = this._map[key];
    return val ? val.clone() : null;
  },
};

// Response CONSTANTS

// token       = *<any CHAR except CTLs or separators>
// CHAR        = <any US-ASCII character (0-127)>
// CTL         = <any US-ASCII control character (0-31) and DEL (127)>
// separators  = "(" | ")" | "<" | ">" | "@"
//             | "," | ";" | ":" | "\" | <">
//             | "/" | "[" | "]" | "?" | "="
//             | "{" | "}" | SP  | HT
const IS_TOKEN_ARRAY = [
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0, //   0
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0, //   8
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0, //  16
  0,
  0,
  0,
  0,
  0,
  0,
  0,
  0, //  24

  0,
  1,
  0,
  1,
  1,
  1,
  1,
  1, //  32
  0,
  0,
  1,
  1,
  0,
  1,
  1,
  0, //  40
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1, //  48
  1,
  1,
  0,
  0,
  0,
  0,
  0,
  0, //  56

  0,
  1,
  1,
  1,
  1,
  1,
  1,
  1, //  64
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1, //  72
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1, //  80
  1,
  1,
  1,
  0,
  0,
  0,
  1,
  1, //  88

  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1, //  96
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1, // 104
  1,
  1,
  1,
  1,
  1,
  1,
  1,
  1, // 112
  1,
  1,
  1,
  0,
  1,
  0,
  1,
]; // 120

/**
 * Determines whether the given character code is a CTL.
 *
 * @param code : uint
 *   the character code
 * @returns boolean
 *   true if code is a CTL, false otherwise
 */
function isCTL(code) {
  return (code >= 0 && code <= 31) || code == 127;
}

/**
 * Represents a response to an HTTP request, encapsulating all details of that
 * response.  This includes all headers, the HTTP version, status code and
 * explanation, and the entity itself.
 *
 * @param connection : Connection
 *   the connection over which this response is to be written
 */
function Response(connection) {
  /** The connection over which this response will be written. */
  this._connection = connection;

  /**
   * The HTTP version of this response; defaults to 1.1 if not set by the
   * handler.
   */
  this._httpVersion = nsHttpVersion.HTTP_1_1;

  /**
   * The HTTP code of this response; defaults to 200.
   */
  this._httpCode = 200;

  /**
   * The description of the HTTP code in this response; defaults to "OK".
   */
  this._httpDescription = "OK";

  /**
   * An nsIHttpHeaders object in which the headers in this response should be
   * stored.  This property is null after the status line and headers have been
   * written to the network, and it may be modified up until it is cleared,
   * except if this._finished is set first (in which case headers are written
   * asynchronously in response to a finish() call not preceded by
   * flushHeaders()).
   */
  this._headers = new nsHttpHeaders();

  /**
   * Informational response:
   * For example 103 Early Hint
   **/
  this._informationalResponseHttpVersion = nsHttpVersion.HTTP_1_1;
  this._informationalResponseHttpCode = 0;
  this._informationalResponseHttpDescription = "";
  this._informationalResponseHeaders = new nsHttpHeaders();
  this._informationalResponseSet = false;

  /**
   * Set to true when this response is ended (completely constructed if possible
   * and the connection closed); further actions on this will then fail.
   */
  this._ended = false;

  /**
   * A stream used to hold data written to the body of this response.
   */
  this._bodyOutputStream = null;

  /**
   * A stream containing all data that has been written to the body of this
   * response so far.  (Async handlers make the data contained in this
   * unreliable as a way of determining content length in general, but auxiliary
   * saved information can sometimes be used to guarantee reliability.)
   */
  this._bodyInputStream = null;

  /**
   * A stream copier which copies data to the network.  It is initially null
   * until replaced with a copier for response headers; when headers have been
   * fully sent it is replaced with a copier for the response body, remaining
   * so for the duration of response processing.
   */
  this._asyncCopier = null;

  /**
   * True if this response has been designated as being processed
   * asynchronously rather than for the duration of a single call to
   * nsIHttpRequestHandler.handle.
   */
  this._processAsync = false;

  /**
   * True iff finish() has been called on this, signaling that no more changes
   * to this may be made.
   */
  this._finished = false;

  /**
   * True iff powerSeized() has been called on this, signaling that this
   * response is to be handled manually by the response handler (which may then
   * send arbitrary data in response, even non-HTTP responses).
   */
  this._powerSeized = false;
}
Response.prototype = {
  // PUBLIC CONSTRUCTION API

  //
  // see nsIHttpResponse.bodyOutputStream
  //
  get bodyOutputStream() {
    if (this._finished) {
      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
    }

    if (!this._bodyOutputStream) {
      var pipe = new Pipe(
        true,
        false,
        Response.SEGMENT_SIZE,
        PR_UINT32_MAX,
        null
      );
      this._bodyOutputStream = pipe.outputStream;
      this._bodyInputStream = pipe.inputStream;
      if (this._processAsync || this._powerSeized) {
        this._startAsyncProcessor();
      }
    }

    return this._bodyOutputStream;
  },

  //
  // see nsIHttpResponse.write
  //
  write(data) {
    if (this._finished) {
      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
    }

    var dataAsString = String(data);
    this.bodyOutputStream.write(dataAsString, dataAsString.length);
  },

  //
  // see nsIHttpResponse.setStatusLine
  //
  setStatusLineInternal(httpVersion, code, description, informationalResponse) {
    if (this._finished || this._powerSeized) {
      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
    }

    if (!informationalResponse) {
      if (!this._headers) {
        throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
      }
    } else if (!this._informationalResponseHeaders) {
      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
    }
    this._ensureAlive();

    if (!(code >= 0 && code < 1000)) {
      throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
    }

    try {
      var httpVer;
      // avoid version construction for the most common cases
      if (!httpVersion || httpVersion == "1.1") {
        httpVer = nsHttpVersion.HTTP_1_1;
      } else if (httpVersion == "1.0") {
        httpVer = nsHttpVersion.HTTP_1_0;
      } else {
        httpVer = new nsHttpVersion(httpVersion);
      }
    } catch (e) {
      throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
    }

    // Reason-Phrase = *<TEXT, excluding CR, LF>
    // TEXT          = <any OCTET except CTLs, but including LWS>
    //
    // XXX this ends up disallowing octets which aren't Unicode, I think -- not
    //     much to do if description is IDL'd as string
    if (!description) {
      description = "";
    }
    for (var i = 0; i < description.length; i++) {
      if (isCTL(description.charCodeAt(i)) && description.charAt(i) != "\t") {
        throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
      }
    }

    // set the values only after validation to preserve atomicity
    if (!informationalResponse) {
      this._httpDescription = description;
      this._httpCode = code;
      this._httpVersion = httpVer;
    } else {
      this._informationalResponseSet = true;
      this._informationalResponseHttpDescription = description;
      this._informationalResponseHttpCode = code;
      this._informationalResponseHttpVersion = httpVer;
    }
  },

  //
  // see nsIHttpResponse.setStatusLine
  //
  setStatusLine(httpVersion, code, description) {
    this.setStatusLineInternal(httpVersion, code, description, false);
  },

  setInformationalResponseStatusLine(httpVersion, code, description) {
    this.setStatusLineInternal(httpVersion, code, description, true);
  },

  //
  // see nsIHttpResponse.setHeader
  //
  setHeader(name, value, merge) {
    if (!this._headers || this._finished || this._powerSeized) {
      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
    }
    this._ensureAlive();

    this._headers.setHeader(name, value, merge);
  },

  setInformationalResponseHeader(name, value, merge) {
    if (
      !this._informationalResponseHeaders ||
      this._finished ||
      this._powerSeized
    ) {
      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
    }
    this._ensureAlive();

    this._informationalResponseHeaders.setHeader(name, value, merge);
  },

  setHeaderNoCheck(name, value) {
    if (!this._headers || this._finished || this._powerSeized) {
      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
    }
    this._ensureAlive();

    this._headers.setHeaderNoCheck(name, value);
  },

  setInformationalHeaderNoCheck(name, value) {
    if (!this._headers || this._finished || this._powerSeized) {
      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
    }
    this._ensureAlive();

    this._informationalResponseHeaders.setHeaderNoCheck(name, value);
  },

  //
  // see nsIHttpResponse.processAsync
  //
  processAsync() {
    if (this._finished) {
      throw Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
    }
    if (this._powerSeized) {
      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
    }
    if (this._processAsync) {
      return;
    }
    this._ensureAlive();

    dumpn("*** processing connection " + this._connection.number + " async");
    this._processAsync = true;

    /*
     * Either the bodyOutputStream getter or this method is responsible for
     * starting the asynchronous processor and catching writes of data to the
     * response body of async responses as they happen, for the purpose of
     * forwarding those writes to the actual connection's output stream.
     * If bodyOutputStream is accessed first, calling this method will create
     * the processor (when it first is clear that body data is to be written
     * immediately, not buffered).  If this method is called first, accessing
     * bodyOutputStream will create the processor.  If only this method is
     * called, we'll write nothing, neither headers nor the nonexistent body,
     * until finish() is called.  Since that delay is easily avoided by simply
     * getting bodyOutputStream or calling write(""), we don't worry about it.
     */
    if (this._bodyOutputStream && !this._asyncCopier) {
      this._startAsyncProcessor();
    }
  },

  //
  // see nsIHttpResponse.seizePower
  //
  seizePower() {
    if (this._processAsync) {
      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
    }
    if (this._finished) {
      throw Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
    }
    if (this._powerSeized) {
      return;
    }
    this._ensureAlive();

    dumpn(
      "*** forcefully seizing power over connection " +
        this._connection.number +
        "..."
    );

    // Purge any already-written data without sending it.  We could as easily
    // swap out the streams entirely, but that makes it possible to acquire and
    // unknowingly use a stale reference, so we require there only be one of
    // each stream ever for any response to avoid this complication.
    if (this._asyncCopier) {
      this._asyncCopier.cancel(Cr.NS_BINDING_ABORTED);
    }
    this._asyncCopier = null;
    if (this._bodyOutputStream) {
      var input = new BinaryInputStream(this._bodyInputStream);
      var avail;
      while ((avail = input.available()) > 0) {
        input.readByteArray(avail);
      }
    }

    this._powerSeized = true;
    if (this._bodyOutputStream) {
      this._startAsyncProcessor();
    }
  },

  //
  // see nsIHttpResponse.finish
  //
  finish() {
    if (!this._processAsync && !this._powerSeized) {
      throw Components.Exception("", Cr.NS_ERROR_UNEXPECTED);
    }
    if (this._finished) {
      return;
    }

    dumpn("*** finishing connection " + this._connection.number);
    this._startAsyncProcessor(); // in case bodyOutputStream was never accessed
    if (this._bodyOutputStream) {
      this._bodyOutputStream.close();
    }
    this._finished = true;
  },

  // NSISUPPORTS

  //
  // see nsISupports.QueryInterface
  //
  QueryInterface: ChromeUtils.generateQI(["nsIHttpResponse"]),

  // POST-CONSTRUCTION API (not exposed externally)

  /**
   * The HTTP version number of this, as a string (e.g. "1.1").
   */
  get httpVersion() {
    this._ensureAlive();
    return this._httpVersion.toString();
  },

  /**
   * The HTTP status code of this response, as a string of three characters per
   * RFC 2616.
   */
  get httpCode() {
    this._ensureAlive();

    var codeString =
      (this._httpCode < 10 ? "0" : "") +
      (this._httpCode < 100 ? "0" : "") +
      this._httpCode;
    return codeString;
  },

  /**
   * The description of the HTTP status code of this response, or "" if none is
   * set.
   */
  get httpDescription() {
    this._ensureAlive();

    return this._httpDescription;
  },

  /**
   * The headers in this response, as an nsHttpHeaders object.
   */
  get headers() {
    this._ensureAlive();

    return this._headers;
  },

  //
  // see nsHttpHeaders.getHeader
  //
  getHeader(name) {
    this._ensureAlive();

    return this._headers.getHeader(name);
  },

  /**
   * Determines whether this response may be abandoned in favor of a newly
   * constructed response.  A response may be abandoned only if it is not being
   * sent asynchronously and if raw control over it has not been taken from the
   * server.
   *
   * @returns boolean
   *   true iff no data has been written to the network
   */
  partiallySent() {
    dumpn("*** partiallySent()");
    return this._processAsync || this._powerSeized;
  },

  /**
   * If necessary, kicks off the remaining request processing needed to be done
   * after a request handler performs its initial work upon this response.
   */
  complete() {
    dumpn("*** complete()");
    if (this._processAsync || this._powerSeized) {
      NS_ASSERT(
        this._processAsync ^ this._powerSeized,
        "can't both send async and relinquish power"
      );
      return;
    }

    NS_ASSERT(!this.partiallySent(), "completing a partially-sent response?");

    this._startAsyncProcessor();

    // Now make sure we finish processing this request!
    if (this._bodyOutputStream) {
      this._bodyOutputStream.close();
    }
  },

  /**
   * Abruptly ends processing of this response, usually due to an error in an
   * incoming request but potentially due to a bad error handler.  Since we
   * cannot handle the error in the usual way (giving an HTTP error page in
   * response) because data may already have been sent (or because the response
   * might be expected to have been generated asynchronously or completely from
   * scratch by the handler), we stop processing this response and abruptly
   * close the connection.
   *
   * @param e : Error
   *   the exception which precipitated this abort, or null if no such exception
   *   was generated
   * @param truncateConnection : Boolean
   *   ensures that we truncate the connection using an RST packet, so the
   *   client testing code is aware that an error occurred, otherwise it may
   *   consider the response as valid.
   */
  abort(e, truncateConnection = false) {
    dumpn("*** abort(<" + e + ">)");

    if (truncateConnection) {
      dumpn("*** truncate connection");
      this._connection.transport.setLinger(true, 0);
    }

    // This response will be ended by the processor if one was created.
    var copier = this._asyncCopier;
    if (copier) {
      // We dispatch asynchronously here so that any pending writes of data to
      // the connection will be deterministically written.  This makes it easier
      // to specify exact behavior, and it makes observable behavior more
      // predictable for clients.  Note that the correctness of this depends on
      // callbacks in response to _waitToReadData in WriteThroughCopier
      // happening asynchronously with respect to the actual writing of data to
      // bodyOutputStream, as they currently do; if they happened synchronously,
      // an event which ran before this one could write more data to the
      // response body before we get around to canceling the copier.  We have
      // tests for this in test_seizepower.js, however, and I can't think of a
      // way to handle both cases without removing bodyOutputStream access and
      // moving its effective write(data, length) method onto Response, which
      // would be slower and require more code than this anyway.
      Services.tm.currentThread.dispatch(
        {
          run() {
            dumpn("*** canceling copy asynchronously...");
            copier.cancel(Cr.NS_ERROR_UNEXPECTED);
          },
        },
        Ci.nsIThread.DISPATCH_NORMAL
      );
    } else {
      this.end();
    }
  },

  /**
   * Closes this response's network connection, marks the response as finished,
   * and notifies the server handler that the request is done being processed.
   */
  end() {
    NS_ASSERT(!this._ended, "ending this response twice?!?!");

    this._connection.close();
    if (this._bodyOutputStream) {
      this._bodyOutputStream.close();
    }

    this._finished = true;
    this._ended = true;
  },

  // PRIVATE IMPLEMENTATION

  /**
   * Sends the status line and headers of this response if they haven't been
   * sent and initiates the process of copying data written to this response's
   * body to the network.
   */
  _startAsyncProcessor() {
    dumpn("*** _startAsyncProcessor()");

    // Handle cases where we're being called a second time.  The former case
    // happens when this is triggered both by complete() and by processAsync(),
    // while the latter happens when processAsync() in conjunction with sent
    // data causes abort() to be called.
    if (this._asyncCopier || this._ended) {
      dumpn("*** ignoring second call to _startAsyncProcessor");
      return;
    }

    // Send headers if they haven't been sent already and should be sent, then
    // asynchronously continue to send the body.
    if (this._headers && !this._powerSeized) {
      this._sendHeaders();
      return;
    }

    this._headers = null;
    this._sendBody();
  },

  /**
   * Signals that all modifications to the response status line and headers are
   * complete and then sends that data over the network to the client.  Once
   * this method completes, a different response to the request that resulted
   * in this response cannot be sent -- the only possible action in case of
   * error is to abort the response and close the connection.
   */
  _sendHeaders() {
    dumpn("*** _sendHeaders()");

    NS_ASSERT(this._headers);
    NS_ASSERT(this._informationalResponseHeaders);
    NS_ASSERT(!this._powerSeized);

    var preambleData = [];

    // Informational response, e.g. 103
    if (this._informationalResponseSet) {
      // request-line
      let statusLine =
        "HTTP/" +
        this._informationalResponseHttpVersion +
        " " +
        this._informationalResponseHttpCode +
        " " +
        this._informationalResponseHttpDescription +
        "\r\n";
      preambleData.push(statusLine);

      // headers
      let headEnum = this._informationalResponseHeaders.enumerator;
      while (headEnum.hasMoreElements()) {
        let fieldName = headEnum
          .getNext()
          .QueryInterface(Ci.nsISupportsString).data;
        let values =
          this._informationalResponseHeaders.getHeaderValues(fieldName);
        for (let i = 0, sz = values.length; i < sz; i++) {
          preambleData.push(fieldName + ": " + values[i] + "\r\n");
        }
      }
      // end request-line/headers
      preambleData.push("\r\n");
    }

    // request-line
    var statusLine =
      "HTTP/" +
      this.httpVersion +
      " " +
      this.httpCode +
      " " +
      this.httpDescription +
      "\r\n";

    // header post-processing

    var headers = this._headers;
    headers.setHeader("Connection", "close", false);
    headers.setHeader("Server", "httpd.js", false);
    if (!headers.hasHeader("Date")) {
      headers.setHeader("Date", toDateString(Date.now()), false);
    }

    // Any response not being processed asynchronously must have an associated
    // Content-Length header for reasons of backwards compatibility with the
    // initial server, which fully buffered every response before sending it.
    // Beyond that, however, it's good to do this anyway because otherwise it's
    // impossible to test behaviors that depend on the presence or absence of a
    // Content-Length header.
    if (!this._processAsync) {
      dumpn("*** non-async response, set Content-Length");

      var bodyStream = this._bodyInputStream;
      var avail = bodyStream ? bodyStream.available() : 0;

      // XXX assumes stream will always report the full amount of data available
      headers.setHeader("Content-Length", "" + avail, false);
    }

    // construct and send response
    dumpn("*** header post-processing completed, sending response head...");

    // request-line
    preambleData.push(statusLine);

    // headers
    var headEnum = headers.enumerator;
    while (headEnum.hasMoreElements()) {
      var fieldName = headEnum
        .getNext()
        .QueryInterface(Ci.nsISupportsString).data;
      var values = headers.getHeaderValues(fieldName);
      for (var i = 0, sz = values.length; i < sz; i++) {
        preambleData.push(fieldName + ": " + values[i] + "\r\n");
      }
    }

    // end request-line/headers
    preambleData.push("\r\n");

    var preamble = preambleData.join("");

    var responseHeadPipe = new Pipe(true, false, 0, PR_UINT32_MAX, null);
    responseHeadPipe.outputStream.write(preamble, preamble.length);

    var response = this;
    var copyObserver = {
      onStartRequest() {
        dumpn("*** preamble copying started");
      },

      onStopRequest(request, statusCode) {
        dumpn(
          "*** preamble copying complete " +
            "[status=0x" +
            statusCode.toString(16) +
            "]"
        );

        if (!Components.isSuccessCode(statusCode)) {
          dumpn(
            "!!! header copying problems: non-success statusCode, " +
              "ending response"
          );

          response.end();
        } else {
          response._sendBody();
        }
      },

      QueryInterface: ChromeUtils.generateQI(["nsIRequestObserver"]),
    };

    this._asyncCopier = new WriteThroughCopier(
      responseHeadPipe.inputStream,
      this._connection.output,
      copyObserver,
      null
    );

    responseHeadPipe.outputStream.close();

    // Forbid setting any more headers or modifying the request line.
    this._headers = null;
  },

  /**
   * Asynchronously writes the body of the response (or the entire response, if
   * seizePower() has been called) to the network.
   */
  _sendBody() {
    dumpn("*** _sendBody");

    NS_ASSERT(!this._headers, "still have headers around but sending body?");

    // If no body data was written, we're done
    if (!this._bodyInputStream) {
      dumpn("*** empty body, response finished");
      this.end();
      return;
    }

    var response = this;
    var copyObserver = {
      onStartRequest() {
        dumpn("*** onStartRequest");
      },

      onStopRequest(request, statusCode) {
        dumpn("*** onStopRequest [status=0x" + statusCode.toString(16) + "]");

        if (statusCode === Cr.NS_BINDING_ABORTED) {
          dumpn("*** terminating copy observer without ending the response");
        } else {
          if (!Components.isSuccessCode(statusCode)) {
            dumpn("*** WARNING: non-success statusCode in onStopRequest");
          }

          response.end();
        }
      },

      QueryInterface: ChromeUtils.generateQI(["nsIRequestObserver"]),
    };

    dumpn("*** starting async copier of body data...");
    this._asyncCopier = new WriteThroughCopier(
      this._bodyInputStream,
      this._connection.output,
      copyObserver,
      null
    );
  },

  /** Ensures that this hasn't been ended. */
  _ensureAlive() {
    NS_ASSERT(!this._ended, "not handling response lifetime correctly");
  },
};

/**
 * Size of the segments in the buffer used in storing response data and writing
 * it to the socket.
 */
Response.SEGMENT_SIZE = 8192;

/** Serves double duty in WriteThroughCopier implementation. */
function notImplemented() {
  throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}

/** Returns true iff the given exception represents stream closure. */
function streamClosed(e) {
  return (
    e === Cr.NS_BASE_STREAM_CLOSED ||
    (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_CLOSED)
  );
}

/** Returns true iff the given exception represents a blocked stream. */
function wouldBlock(e) {
  return (
    e === Cr.NS_BASE_STREAM_WOULD_BLOCK ||
    (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_WOULD_BLOCK)
  );
}

/**
 * Copies data from source to sink as it becomes available, when that data can
 * be written to sink without blocking.
 *
 * @param source : nsIAsyncInputStream
 *   the stream from which data is to be read
 * @param sink : nsIAsyncOutputStream
 *   the stream to which data is to be copied
 * @param observer : nsIRequestObserver
 *   an observer which will be notified when the copy starts and finishes
 * @param context : nsISupports
 *   context passed to observer when notified of start/stop
 * @throws NS_ERROR_NULL_POINTER
 *   if source, sink, or observer are null
 */
export function WriteThroughCopier(source, sink, observer, context) {
  if (!source || !sink || !observer) {
    throw Components.Exception("", Cr.NS_ERROR_NULL_POINTER);
  }

  /** Stream from which data is being read. */
  this._source = source;

  /** Stream to which data is being written. */
  this._sink = sink;

  /** Observer watching this copy. */
  this._observer = observer;

  /** Context for the observer watching this. */
  this._context = context;

  /**
   * True iff this is currently being canceled (cancel has been called, the
   * callback may not yet have been made).
   */
  this._canceled = false;

  /**
   * False until all data has been read from input and written to output, at
   * which point this copy is completed and cancel() is asynchronously called.
   */
  this._completed = false;

  /** Required by nsIRequest, meaningless. */
  this.loadFlags = 0;
  /** Required by nsIRequest, meaningless. */
  this.loadGroup = null;
  /** Required by nsIRequest, meaningless. */
  this.name = "response-body-copy";

  /** Status of this request. */
  this.status = Cr.NS_OK;

  /** Arrays of byte strings waiting to be written to output. */
  this._pendingData = [];

  // start copying
  try {
    observer.onStartRequest(this);
    this._waitToReadData();
    this._waitForSinkClosure();
  } catch (e) {
    dumpn(
      "!!! error starting copy: " +
        e +
        ("lineNumber" in e ? ", line " + e.lineNumber : "")
    );
    dumpn(e.stack);
    this.cancel(Cr.NS_ERROR_UNEXPECTED);
  }
}
WriteThroughCopier.prototype = {
  /* nsISupports implementation */

  QueryInterface: ChromeUtils.generateQI([
    "nsIInputStreamCallback",
    "nsIOutputStreamCallback",
    "nsIRequest",
  ]),

  // NSIINPUTSTREAMCALLBACK

  /**
   * Receives a more-data-in-input notification and writes the corresponding
   * data to the output.
   *
   * @param input : nsIAsyncInputStream
   *   the input stream on whose data we have been waiting
   */
  onInputStreamReady(input) {
    if (this._source === null) {
      return;
    }

    dumpn("*** onInputStreamReady");

    //
    // Ordinarily we'll read a non-zero amount of data from input, queue it up
    // to be written and then wait for further callbacks.  The complications in
    // this method are the cases where we deviate from that behavior when errors
    // occur or when copying is drawing to a finish.
    //
    // The edge cases when reading data are:
    //
    //   Zero data is read
    //     If zero data was read, we're at the end of available data, so we can
    //     should stop reading and move on to writing out what we have (or, if
    //     we've already done that, onto notifying of completion).
    //   A stream-closed exception is thrown
    //     This is effectively a less kind version of zero data being read; the
    //     only difference is that we notify of completion with that result
    //     rather than with NS_OK.
    //   Some other exception is thrown
    //     This is the least kind result.  We don't know what happened, so we
    //     act as though the stream closed except that we notify of completion
    //     with the result NS_ERROR_UNEXPECTED.
    //

    var bytesWanted = 0,
      bytesConsumed = -1;
    try {
      input = new BinaryInputStream(input);

      bytesWanted = Math.min(input.available(), Response.SEGMENT_SIZE);
      dumpn("*** input wanted: " + bytesWanted);

      if (bytesWanted > 0) {
        var data = input.readByteArray(bytesWanted);
        bytesConsumed = data.length;
        this._pendingData.push(String.fromCharCode.apply(String, data));
      }

      dumpn("*** " + bytesConsumed + " bytes read");

      // Handle the zero-data edge case in the same place as all other edge
      // cases are handled.
      if (bytesWanted === 0) {
        throw Components.Exception("", Cr.NS_BASE_STREAM_CLOSED);
      }
    } catch (e) {
      let rv;
      if (streamClosed(e)) {
        dumpn("*** input stream closed");
        rv = bytesWanted === 0 ? Cr.NS_OK : Cr.NS_ERROR_UNEXPECTED;
      } else {
        dumpn("!!! unexpected error reading from input, canceling: " + e);
        rv = Cr.NS_ERROR_UNEXPECTED;
      }

      this._doneReadingSource(rv);
      return;
    }

    var pendingData = this._pendingData;

    NS_ASSERT(bytesConsumed > 0);
    NS_ASSERT(!!pendingData.length, "no pending data somehow?");
    NS_ASSERT(
      !!pendingData[pendingData.length - 1].length,
      "buffered zero bytes of data?"
    );

    NS_ASSERT(this._source !== null);

    // Reading has gone great, and we've gotten data to write now.  What if we
    // don't have a place to write that data, because output went away just
    // before this read?  Drop everything on the floor, including new data, and
    // cancel at this point.
    if (this._sink === null) {
      pendingData.length = 0;
      this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED);
      return;
    }

    // Okay, we've read the data, and we know we have a place to write it.  We
    // need to queue up the data to be written, but *only* if none is queued
    // already -- if data's already queued, the code that actually writes the
    // data will make sure to wait on unconsumed pending data.
    try {
      if (pendingData.length === 1) {
        this._waitToWriteData();
      }
    } catch (e) {
      dumpn(
        "!!! error waiting to write data just read, swallowing and " +
          "writing only what we already have: " +
          e
      );
      this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
      return;
    }

    // Whee!  We successfully read some data, and it's successfully queued up to
    // be written.  All that remains now is to wait for more data to read.
    try {
      this._waitToReadData();
    } catch (e) {
      dumpn("!!! error waiting to read more data: " + e);
      this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED);
    }
  },

  // NSIOUTPUTSTREAMCALLBACK

  /**
   * Callback when data may be written to the output stream without blocking, or
   * when the output stream has been closed.
   *
   * @param output : nsIAsyncOutputStream
   *   the output stream on whose writability we've been waiting, also known as
   *   this._sink
   */
  onOutputStreamReady(output) {
    if (this._sink === null) {
      return;
    }

    dumpn("*** onOutputStreamReady");

    var pendingData = this._pendingData;
    if (pendingData.length === 0) {
      // There's no pending data to write.  The only way this can happen is if
      // we're waiting on the output stream's closure, so we can respond to a
      // copying failure as quickly as possible (rather than waiting for data to
      // be available to read and then fail to be copied).  Therefore, we must
      // be done now -- don't bother to attempt to write anything and wrap
      // things up.
      dumpn("!!! output stream closed prematurely, ending copy");

      this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
      return;
    }

    NS_ASSERT(!!pendingData[0].length, "queued up an empty quantum?");

    //
    // Write out the first pending quantum of data.  The possible errors here
    // are:
    //
    //   The write might fail because we can't write that much data
    //     Okay, we've written what we can now, so re-queue what's left and
    //     finish writing it out later.
    //   The write failed because the stream was closed
    //     Discard pending data that we can no longer write, stop reading, and
    //     signal that copying finished.
    //   Some other error occurred.
    //     Same as if the stream were closed, but notify with the status
    //     NS_ERROR_UNEXPECTED so the observer knows something was wonky.
    //

    try {
      var quantum = pendingData[0];

      // XXX |quantum| isn't guaranteed to be ASCII, so we're relying on
      //     undefined behavior!  We're only using this because writeByteArray
      //     is unusably broken for asynchronous output streams; see bug 532834
      //     for details.
      var bytesWritten = output.write(quantum, quantum.length);
      if (bytesWritten === quantum.length) {
        pendingData.shift();
      } else {
        pendingData[0] = quantum.substring(bytesWritten);
      }

      dumpn("*** wrote " + bytesWritten + " bytes of data");
    } catch (e) {
      if (wouldBlock(e)) {
        NS_ASSERT(
          !!pendingData.length,
          "stream-blocking exception with no data to write?"
        );
        NS_ASSERT(
          !!pendingData[0].length,
          "stream-blocking exception with empty quantum?"
        );
        this._waitToWriteData();
        return;
      }

      if (streamClosed(e)) {
        dumpn("!!! output stream prematurely closed, signaling error...");
      } else {
        dumpn("!!! unknown error: " + e + ", quantum=" + quantum);
      }

      this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
      return;
    }

    // The day is ours!  Quantum written, now let's see if we have more data
    // still to write.
    try {
      if (pendingData.length) {
        this._waitToWriteData();
        return;
      }
    } catch (e) {
      dumpn("!!! unexpected error waiting to write pending data: " + e);
      this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
      return;
    }

    // Okay, we have no more pending data to write -- but might we get more in
    // the future?
    if (this._source !== null) {
      /*
       * If we might, then wait for the output stream to be closed.  (We wait
       * only for closure because we have no data to write -- and if we waited
       * for a specific amount of data, we would get repeatedly notified for no
       * reason if over time the output stream permitted more and more data to
       * be written to it without blocking.)
       */
      this._waitForSinkClosure();
    } else {
      /*
       * On the other hand, if we can't have more data because the input
       * stream's gone away, then it's time to notify of copy completion.
       * Victory!
       */
      this._sink = null;
      this._cancelOrDispatchCancelCallback(Cr.NS_OK);
    }
  },

  // NSIREQUEST

  /** Returns true if the cancel observer hasn't been notified yet. */
  isPending() {
    return !this._completed;
  },

  /** Not implemented, don't use! */
  suspend: notImplemented,
  /** Not implemented, don't use! */
  resume: notImplemented,

  /**
   * Cancels data reading from input, asynchronously writes out any pending
   * data, and causes the observer to be notified with the given error code when
   * all writing has finished.
   *
   * @param status : nsresult
   *   the status to pass to the observer when data copying has been canceled
   */
  cancel(status) {
    dumpn("*** cancel(" + status.toString(16) + ")");

    if (this._canceled) {
      dumpn("*** suppressing a late cancel");
      return;
    }

    this._canceled = true;
    this.status = status;

    // We could be in the middle of absolutely anything at this point.  Both
    // input and output might still be around, we might have pending data to
    // write, and in general we know nothing about the state of the world.  We
    // therefore must assume everything's in progress and take everything to its
    // final steady state (or so far as it can go before we need to finish
    // writing out remaining data).

    this._doneReadingSource(status);
  },

  // PRIVATE IMPLEMENTATION

  /**
   * Stop reading input if we haven't already done so, passing e as the status
   * when closing the stream, and kick off a copy-completion notice if no more
   * data remains to be written.
   *
   * @param e : nsresult
   *   the status to be used when closing the input stream
   */
  _doneReadingSource(e) {
    dumpn("*** _doneReadingSource(0x" + e.toString(16) + ")");

    this._finishSource(e);
    if (this._pendingData.length === 0) {
      this._sink = null;
    } else {
      NS_ASSERT(this._sink !== null, "null output?");
    }

    // If we've written out all data read up to this point, then it's time to
    // signal completion.
    if (this._sink === null) {
      NS_ASSERT(this._pendingData.length === 0, "pending data still?");
      this._cancelOrDispatchCancelCallback(e);
    }
  },

  /**
   * Stop writing output if we haven't already done so, discard any data that
   * remained to be sent, close off input if it wasn't already closed, and kick
   * off a copy-completion notice.
   *
   * @param e : nsresult
   *   the status to be used when closing input if it wasn't already closed
   */
  _doneWritingToSink(e) {
    dumpn("*** _doneWritingToSink(0x" + e.toString(16) + ")");

    this._pendingData.length = 0;
    this._sink = null;
    this._doneReadingSource(e);
  },

  /**
   * Completes processing of this copy: either by canceling the copy if it
   * hasn't already been canceled using the provided status, or by dispatching
   * the cancel callback event (with the originally provided status, of course)
   * if it already has been canceled.
   *
   * @param status : nsresult
   *   the status code to use to cancel this, if this hasn't already been
   *   canceled
   */
  _cancelOrDispatchCancelCallback(status) {
    dumpn("*** _cancelOrDispatchCancelCallback(" + status + ")");

    NS_ASSERT(this._source === null, "should have finished input");
    NS_ASSERT(this._sink === null, "should have finished output");
    NS_ASSERT(this._pendingData.length === 0, "should have no pending data");

    if (!this._canceled) {
      this.cancel(status);
      return;
    }

    var self = this;
    var event = {
      run() {
        dumpn("*** onStopRequest async callback");

        self._completed = true;
        try {
          self._observer.onStopRequest(self, self.status);
        } catch (e) {
          NS_ASSERT(
            false,
            "how are we throwing an exception here?  we control " +
              "all the callers!  " +
              e
          );
        }
      },
    };

    Services.tm.currentThread.dispatch(event, Ci.nsIThread.DISPATCH_NORMAL);
  },

  /**
   * Kicks off another wait for more data to be available from the input stream.
   */
  _waitToReadData() {
    dumpn("*** _waitToReadData");
    this._source.asyncWait(
      this,
      0,
      Response.SEGMENT_SIZE,
      Services.tm.mainThread
    );
  },

  /**
   * Kicks off another wait until data can be written to the output stream.
   */
  _waitToWriteData() {
    dumpn("*** _waitToWriteData");

    var pendingData = this._pendingData;
    NS_ASSERT(!!pendingData.length, "no pending data to write?");
    NS_ASSERT(!!pendingData[0].length, "buffered an empty write?");

    this._sink.asyncWait(
      this,
      0,
      pendingData[0].length,
      Services.tm.mainThread
    );
  },

  /**
   * Kicks off a wait for the sink to which data is being copied to be closed.
   * We wait for stream closure when we don't have any data to be copied, rather
   * than waiting to write a specific amount of data.  We can't wait to write
   * data because the sink might be infinitely writable, and if no data appears
   * in the source for a long time we might have to spin quite a bit waiting to
   * write, waiting to write again, &c.  Waiting on stream closure instead means
   * we'll get just one notification if the sink dies.  Note that when data
   * starts arriving from the sink we'll resume waiting for data to be written,
   * dropping this closure-only callback entirely.
   */
  _waitForSinkClosure() {
    dumpn("*** _waitForSinkClosure");

    this._sink.asyncWait(
      this,
      Ci.nsIAsyncOutputStream.WAIT_CLOSURE_ONLY,
      0,
      Services.tm.mainThread
    );
  },

  /**
   * Closes input with the given status, if it hasn't already been closed;
   * otherwise a no-op.
   *
   * @param status : nsresult
   *   status code use to close the source stream if necessary
   */
  _finishSource(status) {
    dumpn("*** _finishSource(" + status.toString(16) + ")");

    if (this._source !== null) {
      this._source.closeWithStatus(status);
      this._source = null;
    }
  },
};

/**
 * A container for utility functions used with HTTP headers.
 */
const headerUtils = {
  /**
   * Normalizes fieldName (by converting it to lowercase) and ensures it is a
   * valid header field name (although not necessarily one specified in RFC
   * 2616).
   *
   * @throws NS_ERROR_INVALID_ARG
   *   if fieldName does not match the field-name production in RFC 2616
   * @returns string
   *   fieldName converted to lowercase if it is a valid header, for characters
   *   where case conversion is possible
   */
  normalizeFieldName(fieldName) {
    if (fieldName == "") {
      dumpn("*** Empty fieldName");
      throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
    }

    for (var i = 0, sz = fieldName.length; i < sz; i++) {
      if (!IS_TOKEN_ARRAY[fieldName.charCodeAt(i)]) {
        dumpn(fieldName + " is not a valid header field name!");
        throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
      }
    }

    return fieldName.toLowerCase();
  },

  /**
   * Ensures that fieldValue is a valid header field value (although not
   * necessarily as specified in RFC 2616 if the corresponding field name is
   * part of the HTTP protocol), normalizes the value if it is, and
   * returns the normalized value.
   *
   * @param fieldValue : string
   *   a value to be normalized as an HTTP header field value
   * @throws NS_ERROR_INVALID_ARG
   *   if fieldValue does not match the field-value production in RFC 2616
   * @returns string
   *   fieldValue as a normalized HTTP header field value
   */
  normalizeFieldValue(fieldValue) {
    // field-value    = *( field-content | LWS )
    // field-content  = <the OCTETs making up the field-value
    //                  and consisting of either *TEXT or combinations
    //                  of token, separators, and quoted-string>
    // TEXT           = <any OCTET except CTLs,
    //                  but including LWS>
    // LWS            = [CRLF] 1*( SP | HT )
    //
    // quoted-string  = ( <"> *(qdtext | quoted-pair ) <"> )
    // qdtext         = <any TEXT except <">>
    // quoted-pair    = "\" CHAR
    // CHAR           = <any US-ASCII character (octets 0 - 127)>

    // Any LWS that occurs between field-content MAY be replaced with a single
    // SP before interpreting the field value or forwarding the message
    // downstream (section 4.2); we replace 1*LWS with a single SP
    var val = fieldValue.replace(/(?:(?:\r\n)?[ \t]+)+/g, " ");

    // remove leading/trailing LWS (which has been converted to SP)
    val = val.replace(/^ +/, "").replace(/ +$/, "");

    // that should have taken care of all CTLs, so val should contain no CTLs
    dumpn("*** Normalized value: '" + val + "'");
    for (var i = 0, len = val.length; i < len; i++) {
      if (isCTL(val.charCodeAt(i))) {
        dump("*** Char " + i + " has charcode " + val.charCodeAt(i));
        throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
      }
    }

    // XXX disallows quoted-pair where CHAR is a CTL -- will not invalidly
    //     normalize, however, so this can be construed as a tightening of the
    //     spec and not entirely as a bug
    return val;
  },
};

/**
 * Converts the given string into a string which is safe for use in an HTML
 * context.
 *
 * @param str : string
 *   the string to make HTML-safe
 * @returns string
 *   an HTML-safe version of str
 */
function htmlEscape(str) {
  // this is naive, but it'll work
  var s = "";
  for (var i = 0; i < str.length; i++) {
    s += "&#" + str.charCodeAt(i) + ";";
  }
  return s;
}

/**
 * Constructs an object representing an HTTP version (see section 3.1).
 *
 * @param versionString
 *   a string of the form "#.#", where # is an non-negative decimal integer with
 *   or without leading zeros
 * @throws
 *   if versionString does not specify a valid HTTP version number
 */
function nsHttpVersion(versionString) {
  var matches = /^(\d+)\.(\d+)$/.exec(versionString);
  if (!matches) {
    throw new Error("Not a valid HTTP version!");
  }

  /** The major version number of this, as a number. */
  this.major = parseInt(matches[1], 10);

  /** The minor version number of this, as a number. */
  this.minor = parseInt(matches[2], 10);

  if (
    isNaN(this.major) ||
    isNaN(this.minor) ||
    this.major < 0 ||
    this.minor < 0
  ) {
    throw new Error("Not a valid HTTP version!");
  }
}
nsHttpVersion.prototype = {
  /**
   * Returns the standard string representation of the HTTP version represented
   * by this (e.g., "1.1").
   */
  toString() {
    return this.major + "." + this.minor;
  },

  /**
   * Returns true if this represents the same HTTP version as otherVersion,
   * false otherwise.
   *
   * @param otherVersion : nsHttpVersion
   *   the version to compare against this
   */
  equals(otherVersion) {
    return this.major == otherVersion.major && this.minor == otherVersion.minor;
  },

  /** True if this >= otherVersion, false otherwise. */
  atLeast(otherVersion) {
    return (
      this.major > otherVersion.major ||
      (this.major == otherVersion.major && this.minor >= otherVersion.minor)
    );
  },
};

nsHttpVersion.HTTP_1_0 = new nsHttpVersion("1.0");
nsHttpVersion.HTTP_1_1 = new nsHttpVersion("1.1");

/**
 * An object which stores HTTP headers for a request or response.
 *
 * Note that since headers are case-insensitive, this object converts headers to
 * lowercase before storing them.  This allows the getHeader and hasHeader
 * methods to work correctly for any case of a header, but it means that the
 * values returned by .enumerator may not be equal case-sensitively to the
 * values passed to setHeader when adding headers to this.
 */
export function nsHttpHeaders() {
  /**
   * A hash of headers, with header field names as the keys and header field
   * values as the values.  Header field names are case-insensitive, but upon
   * insertion here they are converted to lowercase.  Header field values are
   * normalized upon insertion to contain no leading or trailing whitespace.
   *
   * Note also that per RFC 2616, section 4.2, two headers with the same name in
   * a message may be treated as one header with the same field name and a field
   * value consisting of the separate field values joined together with a "," in
   * their original order.  This hash stores multiple headers with the same name
   * in this manner.
   */
  this._headers = {};
}
nsHttpHeaders.prototype = {
  /**
   * Sets the header represented by name and value in this.
   *
   * @param name : string
   *   the header name
   * @param value : string
   *   the header value
   * @throws NS_ERROR_INVALID_ARG
   *   if name or value is not a valid header component
   */
  setHeader(fieldName, fieldValue, merge) {
    var name = headerUtils.normalizeFieldName(fieldName);
    var value = headerUtils.normalizeFieldValue(fieldValue);

    // The following three headers are stored as arrays because their real-world
    // syntax prevents joining individual headers into a single header using
    // ",".  See also <https://hg.mozilla.org/mozilla-central/diff/9b2a99adc05e/netwerk/protocol/http/src/nsHttpHeaderArray.cpp#l77>
    if (merge && name in this._headers) {
      if (
        name === "www-authenticate" ||
        name === "proxy-authenticate" ||
        name === "set-cookie"
      ) {
        this._headers[name].push(value);
      } else {
        this._headers[name][0] += "," + value;
        NS_ASSERT(
          this._headers[name].length === 1,
          "how'd a non-special header have multiple values?"
        );
      }
    } else {
      this._headers[name] = [value];
    }
  },

  setHeaderNoCheck(fieldName, fieldValue) {
    var name = headerUtils.normalizeFieldName(fieldName);
    var value = headerUtils.normalizeFieldValue(fieldValue);
    if (name in this._headers) {
      this._headers[name].push(value);
    } else {
      this._headers[name] = [value];
    }
  },

  /**
   * Returns the value for the header specified by this.
   *
   * @throws NS_ERROR_INVALID_ARG
   *   if fieldName does not constitute a valid header field name
   * @throws NS_ERROR_NOT_AVAILABLE
   *   if the given header does not exist in this
   * @returns string
   *   the field value for the given header, possibly with non-semantic changes
   *   (i.e., leading/trailing whitespace stripped, whitespace runs replaced
   *   with spaces, etc.) at the option of the implementation; multiple
   *   instances of the header will be combined with a comma, except for
   *   the three headers noted in the description of getHeaderValues
   */
  getHeader(fieldName) {
    return this.getHeaderValues(fieldName).join("\n");
  },

  /**
   * Returns the value for the header specified by fieldName as an array.
   *
   * @throws NS_ERROR_INVALID_ARG
   *   if fieldName does not constitute a valid header field name
   * @throws NS_ERROR_NOT_AVAILABLE
   *   if the given header does not exist in this
   * @returns [string]
   *   an array of all the header values in this for the given
   *   header name.  Header values will generally be collapsed
   *   into a single header by joining all header values together
   *   with commas, but certain headers (Proxy-Authenticate,
   *   WWW-Authenticate, and Set-Cookie) violate the HTTP spec
   *   and cannot be collapsed in this manner.  For these headers
   *   only, the returned array may contain multiple elements if
   *   that header has been added more than once.
   */
  getHeaderValues(fieldName) {
    var name = headerUtils.normalizeFieldName(fieldName);

    if (name in this._headers) {
      return this._headers[name];
    }
    throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
  },

  /**
   * Returns true if a header with the given field name exists in this, false
   * otherwise.
   *
   * @param fieldName : string
   *   the field name whose existence is to be determined in this
   * @throws NS_ERROR_INVALID_ARG
   *   if fieldName does not constitute a valid header field name
   * @returns boolean
   *   true if the header's present, false otherwise
   */
  hasHeader(fieldName) {
    var name = headerUtils.normalizeFieldName(fieldName);
    return name in this._headers;
  },

  /**
   * Returns a new enumerator over the field names of the headers in this, as
   * nsISupportsStrings.  The names returned will be in lowercase, regardless of
   * how they were input using setHeader (header names are case-insensitive per
   * RFC 2616).
   */
  get enumerator() {
    var headers = [];
    for (var i in this._headers) {
      var supports = new SupportsString();
      supports.data = i;
      headers.push(supports);
    }

    return new nsSimpleEnumerator(headers);
  },
};

/**
 * Constructs an nsISimpleEnumerator for the given array of items.
 *
 * @param items : Array
 *   the items, which must all implement nsISupports
 */
function nsSimpleEnumerator(items) {
  this._items = items;
  this._nextIndex = 0;
}
nsSimpleEnumerator.prototype = {
  hasMoreElements() {
    return this._nextIndex < this._items.length;
  },
  getNext() {
    if (!this.hasMoreElements()) {
      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
    }

    return this._items[this._nextIndex++];
  },
  [Symbol.iterator]() {
    return this._items.values();
  },
  QueryInterface: ChromeUtils.generateQI(["nsISimpleEnumerator"]),
};

/**
 * A representation of the data in an HTTP request.
 *
 * @param port : uint
 *   the port on which the server receiving this request runs
 */
function Request(port) {
  /** Method of this request, e.g. GET or POST. */
  this._method = "";

  /** Path of the requested resource; empty paths are converted to '/'. */
  this._path = "";

  /** Query string, if any, associated with this request (not including '?'). */
  this._queryString = "";

  /** Scheme of requested resource, usually http, always lowercase. */
  this._scheme = "http";

  /** Hostname on which the requested resource resides. */
  this._host = undefined;

  /** Port number over which the request was received. */
  this._port = port;

  var bodyPipe = new Pipe(false, false, 0, PR_UINT32_MAX, null);

  /** Stream from which data in this request's body may be read. */
  this._bodyInputStream = bodyPipe.inputStream;

  /** Stream to which data in this request's body is written. */
  this._bodyOutputStream = bodyPipe.outputStream;

  /**
   * The headers in this request.
   */
  this._headers = new nsHttpHeaders();

  /**
   * For the addition of ad-hoc properties and new functionality without having
   * to change nsIHttpRequest every time; currently lazily created, as its only
   * use is in directory listings.
   */
  this._bag = null;
}
Request.prototype = {
  // SERVER METADATA

  //
  // see nsIHttpRequest.scheme
  //
  get scheme() {
    return this._scheme;
  },

  //
  // see nsIHttpRequest.host
  //
  get host() {
    return this._host;
  },

  //
  // see nsIHttpRequest.port
  //
  get port() {
    return this._port;
  },

  // REQUEST LINE

  //
  // see nsIHttpRequest.method
  //
  get method() {
    return this._method;
  },

  //
  // see nsIHttpRequest.httpVersion
  //
  get httpVersion() {
    return this._httpVersion.toString();
  },

  //
  // see nsIHttpRequest.path
  //
  get path() {
    return this._path;
  },

  //
  // see nsIHttpRequest.queryString
  //
  get queryString() {
    return this._queryString;
  },

  // HEADERS

  //
  // see nsIHttpRequest.getHeader
  //
  getHeader(name) {
    return this._headers.getHeader(name);
  },

  //
  // see nsIHttpRequest.hasHeader
  //
  hasHeader(name) {
    return this._headers.hasHeader(name);
  },

  //
  // see nsIHttpRequest.headers
  //
  get headers() {
    return this._headers.enumerator;
  },

  //
  // see nsIPropertyBag.enumerator
  //
  get enumerator() {
    this._ensurePropertyBag();
    return this._bag.enumerator;
  },

  //
  // see nsIHttpRequest.headers
  //
  get bodyInputStream() {
    return this._bodyInputStream;
  },

  //
  // see nsIPropertyBag.getProperty
  //
  getProperty(name) {
    this._ensurePropertyBag();
    return this._bag.getProperty(name);
  },

  // NSISUPPORTS

  //
  // see nsISupports.QueryInterface
  //
  QueryInterface: ChromeUtils.generateQI(["nsIHttpRequest"]),

  // PRIVATE IMPLEMENTATION

  /** Ensures a property bag has been created for ad-hoc behaviors. */
  _ensurePropertyBag() {
    if (!this._bag) {
      this._bag = new WritablePropertyBag();
    }
  },
};
PK
!<h���gg,chrome/remote/content/shared/AppInfo.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const ID_FIREFOX = "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
const ID_THUNDERBIRD = "{3550f703-e582-4d05-9a08-453d09bdfdc6}";

/**
 * Extends Services.appinfo with further properties that are
 * used by different protocols as handled by the Remote Agent.
 *
 * @typedef {object} RemoteAgent.AppInfo
 * @property {boolean} isAndroid - Whether the application runs on Android.
 * @property {boolean} isLinux - Whether the application runs on Linux.
 * @property {boolean} isMac - Whether the application runs on Mac OS.
 * @property {boolean} isWindows - Whether the application runs on Windows.
 * @property {boolean} isFirefox - Whether the application is Firefox.
 * @property {boolean} isThunderbird - Whether the application is Thunderbird.
 *
 * @since 88
 */
export const AppInfo = new Proxy(
  {},
  {
    get(target, prop) {
      if (target.hasOwnProperty(prop)) {
        return target[prop];
      }

      return Services.appinfo[prop];
    },
  }
);

// Platform support

ChromeUtils.defineLazyGetter(AppInfo, "isAndroid", () => {
  return Services.appinfo.OS === "Android";
});

ChromeUtils.defineLazyGetter(AppInfo, "isLinux", () => {
  return Services.appinfo.OS === "Linux";
});

ChromeUtils.defineLazyGetter(AppInfo, "isMac", () => {
  return Services.appinfo.OS === "Darwin";
});

ChromeUtils.defineLazyGetter(AppInfo, "isWindows", () => {
  return Services.appinfo.OS === "WINNT";
});

// Application type

ChromeUtils.defineLazyGetter(AppInfo, "isFirefox", () => {
  return Services.appinfo.ID == ID_FIREFOX;
});

ChromeUtils.defineLazyGetter(AppInfo, "isThunderbird", () => {
  return Services.appinfo.ID == ID_THUNDERBIRD;
});

export function getTimeoutMultiplier() {
  if (
    AppConstants.DEBUG ||
    AppConstants.MOZ_CODE_COVERAGE ||
    AppConstants.ASAN
  ) {
    return 4;
  }
  if (AppConstants.TSAN) {
    return 8;
  }

  return 1;
}
PK
!<P)Pl
l
,chrome/remote/content/shared/Browser.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
  waitForObserverTopic: "chrome://remote/content/marionette/sync.sys.mjs",
});

/**
 * Quits the application with the provided flags.
 *
 * Optional {@link nsIAppStartup} flags may be provided as
 * an array of masks, and these will be combined by ORing
 * them with a bitmask. The available masks are defined in
 * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIAppStartup.
 *
 * Crucially, only one of the *Quit flags can be specified. The |eRestart|
 * flag may be bit-wise combined with one of the *Quit flags to cause
 * the application to restart after it quits.
 *
 * @param {Array.<string>=} flags
 *     Constant name of masks to pass to |Services.startup.quit|.
 *     If empty or undefined, |nsIAppStartup.eAttemptQuit| is used.
 * @param {boolean=} safeMode
 *     Optional flag to indicate that the application has to
 *     be restarted in safe mode.
 * @param {boolean=} isWindowless
 *     Optional flag to indicate that the browser was started in windowless mode.
 *
 * @returns {Record<string, boolean>}
 *     Dictionary containing information that explains the shutdown reason.
 *     The value for `cause` contains the shutdown kind like "shutdown" or
 *     "restart", while `forced` will indicate if it was a normal or forced
 *     shutdown of the application. "in_app" is always set to indicate that
 *     it is a shutdown triggered from within the application.
 */
export async function quit(flags = [], safeMode = false, isWindowless = false) {
  if (flags.includes("eSilently")) {
    if (!isWindowless) {
      throw new Error(
        `Silent restarts only allowed with "moz:windowless" capability set`
      );
    }
    if (!flags.includes("eRestart")) {
      throw new TypeError(`"silently" only works with restart flag`);
    }
  }

  const quits = ["eConsiderQuit", "eAttemptQuit", "eForceQuit"];

  let quitSeen;
  let mode = 0;
  if (flags.length) {
    for (let k of flags) {
      if (!(k in Ci.nsIAppStartup)) {
        throw new TypeError(lazy.pprint`Expected ${k} in ${Ci.nsIAppStartup}`);
      }

      if (quits.includes(k)) {
        if (quitSeen) {
          throw new TypeError(`${k} cannot be combined with ${quitSeen}`);
        }
        quitSeen = k;
      }

      mode |= Ci.nsIAppStartup[k];
    }
  }

  if (!quitSeen) {
    mode |= Ci.nsIAppStartup.eAttemptQuit;
  }

  // Notify all windows that an application quit has been requested.
  const cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
    Ci.nsISupportsPRBool
  );
  Services.obs.notifyObservers(cancelQuit, "quit-application-requested");

  // If the shutdown of the application is prevented force quit it instead.
  if (cancelQuit.data) {
    mode |= Ci.nsIAppStartup.eForceQuit;
  }

  // Delay response until the application is about to quit.
  const quitApplication = lazy.waitForObserverTopic("quit-application");

  if (safeMode) {
    Services.startup.restartInSafeMode(mode);
  } else {
    Services.startup.quit(mode);
  }

  return {
    cause: (await quitApplication).data,
    forced: cancelQuit.data,
    in_app: true,
  };
}
PK
!<\���||,chrome/remote/content/shared/Capture.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "chrome://remote/content/shared/Log.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

const CONTEXT_2D = "2d";
const BG_COLOUR = "rgb(255,255,255)";
const MAX_CANVAS_DIMENSION = 32767;
const MAX_CANVAS_AREA = 472907776;
const PNG_MIME = "image/png";
const XHTML_NS = "http://www.w3.org/1999/xhtml";

/**
 * Provides primitives to capture screenshots.
 *
 * @namespace
 */
export const capture = {};

capture.Format = {
  Base64: 0,
  Hash: 1,
};

/**
 * Draw a rectangle off the framebuffer.
 *
 * @param {DOMWindow} win
 *     The DOM window used for the framebuffer, and providing the interfaces
 *     for creating an HTMLCanvasElement.
 * @param {BrowsingContext} browsingContext
 *     The BrowsingContext from which the snapshot should be taken.
 * @param {number} left
 *     The left, X axis offset of the rectangle.
 * @param {number} top
 *     The top, Y axis offset of the rectangle.
 * @param {number} width
 *     The width dimension of the rectangle to paint.
 * @param {number} height
 *     The height dimension of the rectangle to paint.
 * @param {object=} options
 * @param {HTMLCanvasElement=} options.canvas
 *     Optional canvas to reuse for the screenshot.
 * @param {number=} options.flags
 *     Optional integer representing flags to pass to drawWindow; these
 *     are defined on CanvasRenderingContext2D.
 * @param {number=} options.dX
 *     Horizontal offset between the browser window and content area. Defaults to 0.
 * @param {number=} options.dY
 *     Vertical offset between the browser window and content area. Defaults to 0.
 * @param {boolean=} options.readback
 *     If true, read back a snapshot of the pixel data currently in the
 *     compositor/window. Defaults to false.
 *
 * @returns {HTMLCanvasElement}
 *     The canvas on which the selection from the window's framebuffer
 *     has been painted on.
 */
capture.canvas = async function (
  win,
  browsingContext,
  left,
  top,
  width,
  height,
  { canvas = null, flags = null, dX = 0, dY = 0, readback = false } = {}
) {
  // FIXME(bug 1761032): This looks a bit sketchy, overrideDPPX doesn't
  // influence rendering...
  const scale = browsingContext.overrideDPPX || win.devicePixelRatio;

  let canvasHeight = height * scale;
  let canvasWidth = width * scale;

  // Cap the screenshot size for width and height at 2^16 pixels,
  // which is the maximum allowed canvas size. Higher dimensions will
  // trigger exceptions in Gecko.
  if (canvasWidth > MAX_CANVAS_DIMENSION) {
    lazy.logger.warn(
      "Limiting screen capture width to maximum allowed " +
        MAX_CANVAS_DIMENSION +
        " pixels"
    );
    width = Math.floor(MAX_CANVAS_DIMENSION / scale);
    canvasWidth = width * scale;
  }

  if (canvasHeight > MAX_CANVAS_DIMENSION) {
    lazy.logger.warn(
      "Limiting screen capture height to maximum allowed " +
        MAX_CANVAS_DIMENSION +
        " pixels"
    );
    height = Math.floor(MAX_CANVAS_DIMENSION / scale);
    canvasHeight = height * scale;
  }

  // If the area is larger, reduce the height to keep the full width.
  if (canvasWidth * canvasHeight > MAX_CANVAS_AREA) {
    lazy.logger.warn(
      "Limiting screen capture area to maximum allowed " +
        MAX_CANVAS_AREA +
        " pixels"
    );
    height = Math.floor(MAX_CANVAS_AREA / (canvasWidth * scale));
    canvasHeight = height * scale;
  }

  if (canvas === null) {
    canvas = win.document.createElementNS(XHTML_NS, "canvas");
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;
  }

  const ctx = canvas.getContext(CONTEXT_2D);

  if (readback) {
    if (flags === null) {
      flags =
        ctx.DRAWWINDOW_DRAW_CARET |
        ctx.DRAWWINDOW_DRAW_VIEW |
        ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
    }

    // drawWindow doesn't take scaling into account.
    ctx.scale(scale, scale);
    ctx.drawWindow(win, left + dX, top + dY, width, height, BG_COLOUR, flags);
  } else {
    let rect = new DOMRect(left, top, width, height);
    let snapshot = await browsingContext.currentWindowGlobal.drawSnapshot(
      rect,
      scale,
      BG_COLOUR
    );

    ctx.drawImage(snapshot, 0, 0);

    // Bug 1574935 - Huge dimensions can trigger an OOM because multiple copies
    // of the bitmap will exist in memory. Force the removal of the snapshot
    // because it is no longer needed.
    snapshot.close();
  }

  return canvas;
};

/**
 * Encode the contents of an HTMLCanvasElement to a Base64 encoded string.
 *
 * @param {HTMLCanvasElement} canvas
 *     The canvas to encode.
 *
 * @returns {string}
 *     A Base64 encoded string.
 */
capture.toBase64 = function (canvas) {
  let u = canvas.toDataURL(PNG_MIME);
  return u.substring(u.indexOf(",") + 1);
};

/**
 * Hash the contents of an HTMLCanvasElement to a SHA-256 hex digest.
 *
 * @param {HTMLCanvasElement} canvas
 *     The canvas to encode.
 *
 * @returns {string}
 *     A hex digest of the SHA-256 hash of the base64 encoded string.
 */
capture.toHash = function (canvas) {
  let u = capture.toBase64(canvas);
  let buffer = new TextEncoder().encode(u);
  return crypto.subtle.digest("SHA-256", buffer).then(hash => hex(hash));
};

/**
 * Convert buffer into to hex.
 *
 * @param {ArrayBuffer} buffer
 *     The buffer containing the data to convert to hex.
 *
 * @returns {string}
 *     A hex digest of the input buffer.
 */
function hex(buffer) {
  let hexCodes = [];
  let view = new DataView(buffer);
  for (let i = 0; i < view.byteLength; i += 4) {
    let value = view.getUint32(i);
    let stringValue = value.toString(16);
    let padding = "00000000";
    let paddedValue = (padding + stringValue).slice(-padding.length);
    hexCodes.push(paddedValue);
  }
  return hexCodes.join("");
}
PK
!<}��:chrome/remote/content/shared/ChallengeHeaderParser.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Parse the parameter in a name/value pair and remove quotes.
 *
 * @param {string} paramValue
 *     A string representing a challenge parameter.
 *
 * @returns {object}
 *     An object with name and value string properties.
 */
function parseChallengeParameter(paramValue) {
  const [name, value] = paramValue.split("=");
  return { name, value: value?.replace(/["']/g, "") };
}

/**
 * Simple parser for authenticate (WWW-Authenticate or Proxy-Authenticate)
 * headers.
 *
 * Bug 1857847: Replace with Necko's ChallengeParser once exposed to JS.
 *
 * @param {string} headerValue
 *     The value of an authenticate header.
 *
 * @returns {Array<object>}
 *     Array of challenge objects containing two properties:
 *     - {string} scheme: The scheme for the challenge
 *     - {Array<object>} params: Array of { name, value } objects representing
 *       all the parameters of the challenge.
 */
export function parseChallengeHeader(headerValue) {
  const challenges = [];
  const parts = headerValue.split(",").map(part => part.trim());

  let scheme = null;
  let params = [];

  const schemeRegex = /^(\w+)(?:\s+(.*))?$/;
  for (const part of parts) {
    const matches = part.match(schemeRegex);
    if (matches !== null) {
      // This is a new scheme.
      if (scheme !== null) {
        // If we have a challenge recorded, add it to the array.
        challenges.push({ scheme, params });
      }

      // Reset the state for a new scheme.
      scheme = matches[1];
      params = [];
      if (matches[2]) {
        params.push(parseChallengeParameter(matches[2]));
      }
    } else {
      if (scheme === null) {
        // A scheme should always be found before parameters, this header
        // probably needs a more careful parsing solution.
        return [];
      }

      params.push(parseChallengeParameter(part));
    }
  }

  if (scheme !== null) {
    // If we have a challenge recorded, add it to the array.
    challenges.push({ scheme, params });
  }

  return challenges;
}
PK
!<���o_�_�(chrome/remote/content/shared/DOM.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  atom: "chrome://remote/content/marionette/atom.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  PollPromise: "chrome://remote/content/marionette/sync.sys.mjs",
});

const ORDERED_NODE_ITERATOR_TYPE = 5;
const FIRST_ORDERED_NODE_TYPE = 9;

const DOCUMENT_FRAGMENT_NODE = 11;
const ELEMENT_NODE = 1;

const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

/** XUL elements that support checked property. */
const XUL_CHECKED_ELS = new Set(["button", "checkbox", "toolbarbutton"]);

/** XUL elements that support selected property. */
const XUL_SELECTED_ELS = new Set([
  "menu",
  "menuitem",
  "menuseparator",
  "radio",
  "richlistitem",
  "tab",
]);

/**
 * This module provides shared functionality for dealing with DOM-
 * and web elements in Marionette.
 *
 * A web element is an abstraction used to identify an element when it
 * is transported across the protocol, between remote- and local ends.
 *
 * Each element has an associated web element reference (a UUID) that
 * uniquely identifies the the element across all browsing contexts. The
 * web element reference for every element representing the same element
 * is the same.
 *
 * @namespace
 */
export const dom = {};

dom.Strategy = {
  ClassName: "class name",
  Selector: "css selector",
  ID: "id",
  Name: "name",
  LinkText: "link text",
  PartialLinkText: "partial link text",
  TagName: "tag name",
  XPath: "xpath",
};

/**
 * Find a single element or a collection of elements starting at the
 * document root or a given node.
 *
 * If |timeout| is above 0, an implicit search technique is used.
 * This will wait for the duration of <var>timeout</var> for the
 * element to appear in the DOM.
 *
 * See the {@link dom.Strategy} enum for a full list of supported
 * search strategies that can be passed to <var>strategy</var>.
 *
 * @param {Record<string, WindowProxy>} container
 *     Window object.
 * @param {string} strategy
 *     Search strategy whereby to locate the element(s).
 * @param {string} selector
 *     Selector search pattern.  The selector must be compatible with
 *     the chosen search <var>strategy</var>.
 * @param {object=} options
 * @param {boolean=} options.all
 *     If true, a multi-element search selector is used and a sequence of
 *     elements will be returned, otherwise a single element. Defaults to false.
 * @param {Element=} options.startNode
 *     Element to use as the root of the search.
 * @param {number=} options.timeout
 *     Duration to wait before timing out the search.  If <code>all</code>
 *     is false, a {@link NoSuchElementError} is thrown if unable to
 *     find the element within the timeout duration.
 *
 * @returns {Promise.<(Element|Array.<Element>)>}
 *     Single element or a sequence of elements.
 *
 * @throws InvalidSelectorError
 *     If <var>strategy</var> is unknown.
 * @throws InvalidSelectorError
 *     If <var>selector</var> is malformed.
 * @throws NoSuchElementError
 *     If a single element is requested, this error will throw if the
 *     element is not found.
 */
dom.find = function (container, strategy, selector, options = {}) {
  const { all = false, startNode, timeout = 0 } = options;

  let searchFn;
  if (all) {
    searchFn = findElements.bind(this);
  } else {
    searchFn = findElement.bind(this);
  }

  return new Promise((resolve, reject) => {
    let findElements = new lazy.PollPromise(
      async (resolve, reject) => {
        try {
          let res = await find_(container, strategy, selector, searchFn, {
            all,
            startNode,
          });
          if (res.length) {
            resolve(Array.from(res));
          } else {
            reject([]);
          }
        } catch (e) {
          reject(e);
        }
      },
      { timeout }
    );

    findElements.then(foundEls => {
      // the following code ought to be moved into findElement
      // and findElements when bug 1254486 is addressed
      if (!all && (!foundEls || !foundEls.length)) {
        let msg = `Unable to locate element: ${selector}`;
        reject(new lazy.error.NoSuchElementError(msg));
      }

      if (all) {
        resolve(foundEls);
      }
      resolve(foundEls[0]);
    }, reject);
  });
};

async function find_(
  container,
  strategy,
  selector,
  searchFn,
  { startNode = null, all = false } = {}
) {
  let rootNode;

  if (dom.isShadowRoot(startNode)) {
    rootNode = startNode.ownerDocument;
  } else {
    rootNode = container.frame.document;
  }

  if (!startNode) {
    startNode = rootNode;
  }

  let res;
  try {
    res = await searchFn(strategy, selector, rootNode, startNode);
  } catch (e) {
    throw new lazy.error.InvalidSelectorError(
      `Given ${strategy} expression "${selector}" is invalid: ${e}`
    );
  }

  if (res) {
    if (all) {
      return res;
    }
    return [res];
  }
  return [];
}

/**
 * Find a single element by XPath expression.
 *
 * @param {Document} document
 *     Document root.
 * @param {Element} startNode
 *     Where in the DOM hiearchy to begin searching.
 * @param {string} expression
 *     XPath search expression.
 *
 * @returns {Node}
 *     First element matching <var>expression</var>.
 */
dom.findByXPath = function (document, startNode, expression) {
  let iter = document.evaluate(
    expression,
    startNode,
    null,
    FIRST_ORDERED_NODE_TYPE,
    null
  );
  return iter.singleNodeValue;
};

/**
 * Find elements by XPath expression.
 *
 * @param {Document} document
 *     Document root.
 * @param {Element} startNode
 *     Where in the DOM hierarchy to begin searching.
 * @param {string} expression
 *     XPath search expression.
 *
 * @returns {Iterable.<Node>}
 *     Iterator over nodes matching <var>expression</var>.
 */
dom.findByXPathAll = function* (document, startNode, expression) {
  let iter = document.evaluate(
    expression,
    startNode,
    null,
    ORDERED_NODE_ITERATOR_TYPE,
    null
  );
  let el = iter.iterateNext();
  while (el) {
    yield el;
    el = iter.iterateNext();
  }
};

/**
 * Find all hyperlinks descendant of <var>startNode</var> which
 * link text is <var>linkText</var>.
 *
 * @param {Element} startNode
 *     Where in the DOM hierarchy to begin searching.
 * @param {string} linkText
 *     Link text to search for.
 *
 * @returns {Iterable.<HTMLAnchorElement>}
 *     Sequence of link elements which text is <var>s</var>.
 */
dom.findByLinkText = function (startNode, linkText) {
  return filterLinks(startNode, async link => {
    const visibleText = await lazy.atom.getVisibleText(link, link.ownerGlobal);
    return visibleText.trim() === linkText;
  });
};

/**
 * Find all hyperlinks descendant of <var>startNode</var> which
 * link text contains <var>linkText</var>.
 *
 * @param {Element} startNode
 *     Where in the DOM hierachy to begin searching.
 * @param {string} linkText
 *     Link text to search for.
 *
 * @returns {Iterable.<HTMLAnchorElement>}
 *     Iterator of link elements which text containins
 *     <var>linkText</var>.
 */
dom.findByPartialLinkText = function (startNode, linkText) {
  return filterLinks(startNode, async link => {
    const visibleText = await lazy.atom.getVisibleText(link, link.ownerGlobal);

    return visibleText.includes(linkText);
  });
};

/**
 * Filters all hyperlinks that are descendant of <var>startNode</var>
 * by <var>predicate</var>.
 *
 * @param {Element} startNode
 *     Where in the DOM hierarchy to begin searching.
 * @param {function(HTMLAnchorElement): boolean} predicate
 *     Function that determines if given link should be included in
 *     return value or filtered away.
 *
 * @returns {Array.<HTMLAnchorElement>}
 *     Array of link elements matching <var>predicate</var>.
 */
async function filterLinks(startNode, predicate) {
  const links = [];

  for (const link of getLinks(startNode)) {
    if (await predicate(link)) {
      links.push(link);
    }
  }

  return links;
}

/**
 * Finds a single element.
 *
 * @param {dom.Strategy} strategy
 *     Selector strategy to use.
 * @param {string} selector
 *     Selector expression.
 * @param {Document} document
 *     Document root.
 * @param {Element=} startNode
 *     Optional Element from which to start searching.
 *
 * @returns {Element}
 *     Found element.
 *
 * @throws {InvalidSelectorError}
 *     If strategy <var>using</var> is not recognised.
 * @throws {Error}
 *     If selector expression <var>selector</var> is malformed.
 */
async function findElement(
  strategy,
  selector,
  document,
  startNode = undefined
) {
  switch (strategy) {
    case dom.Strategy.ID: {
      if (startNode.getElementById) {
        return startNode.getElementById(selector);
      }
      let expr = `.//*[@id="${selector}"]`;
      return dom.findByXPath(document, startNode, expr);
    }

    case dom.Strategy.Name: {
      if (startNode.getElementsByName) {
        return startNode.getElementsByName(selector)[0];
      }
      let expr = `.//*[@name="${selector}"]`;
      return dom.findByXPath(document, startNode, expr);
    }

    case dom.Strategy.ClassName:
      return startNode.getElementsByClassName(selector)[0];

    case dom.Strategy.TagName:
      return startNode.getElementsByTagName(selector)[0];

    case dom.Strategy.XPath:
      return dom.findByXPath(document, startNode, selector);

    case dom.Strategy.LinkText: {
      const links = getLinks(startNode);
      for (const link of links) {
        const visibleText = await lazy.atom.getVisibleText(
          link,
          link.ownerGlobal
        );
        if (visibleText.trim() === selector) {
          return link;
        }
      }
      return undefined;
    }

    case dom.Strategy.PartialLinkText: {
      const links = getLinks(startNode);
      for (const link of links) {
        const visibleText = await lazy.atom.getVisibleText(
          link,
          link.ownerGlobal
        );
        if (visibleText.includes(selector)) {
          return link;
        }
      }
      return undefined;
    }

    case dom.Strategy.Selector:
      try {
        return startNode.querySelector(selector);
      } catch (e) {
        throw new lazy.error.InvalidSelectorError(
          `${e.message}: "${selector}"`
        );
      }
  }

  throw new lazy.error.InvalidSelectorError(`No such strategy: ${strategy}`);
}

/**
 * Find multiple elements.
 *
 * @param {dom.Strategy} strategy
 *     Selector strategy to use.
 * @param {string} selector
 *     Selector expression.
 * @param {Document} document
 *     Document root.
 * @param {Element=} startNode
 *     Optional Element from which to start searching.
 *
 * @returns {Array.<Element>}
 *     Found elements.
 *
 * @throws {InvalidSelectorError}
 *     If strategy <var>strategy</var> is not recognised.
 * @throws {Error}
 *     If selector expression <var>selector</var> is malformed.
 */
async function findElements(
  strategy,
  selector,
  document,
  startNode = undefined
) {
  switch (strategy) {
    case dom.Strategy.ID:
      selector = `.//*[@id="${selector}"]`;

    // fall through
    case dom.Strategy.XPath:
      return [...dom.findByXPathAll(document, startNode, selector)];

    case dom.Strategy.Name:
      if (startNode.getElementsByName) {
        return startNode.getElementsByName(selector);
      }
      return [
        ...dom.findByXPathAll(document, startNode, `.//*[@name="${selector}"]`),
      ];

    case dom.Strategy.ClassName:
      return startNode.getElementsByClassName(selector);

    case dom.Strategy.TagName:
      return startNode.getElementsByTagName(selector);

    case dom.Strategy.LinkText:
      return [...(await dom.findByLinkText(startNode, selector))];

    case dom.Strategy.PartialLinkText:
      return [...(await dom.findByPartialLinkText(startNode, selector))];

    case dom.Strategy.Selector:
      return startNode.querySelectorAll(selector);

    default:
      throw new lazy.error.InvalidSelectorError(
        `No such strategy: ${strategy}`
      );
  }
}

function getLinks(startNode) {
  // DocumentFragment doesn't have `getElementsByTagName` so using `querySelectorAll`.
  if (dom.isShadowRoot(startNode)) {
    return startNode.querySelectorAll("a");
  }
  return startNode.getElementsByTagName("a");
}

/**
 * Finds the closest parent node of <var>startNode</var> matching a CSS
 * <var>selector</var> expression.
 *
 * @param {Node} startNode
 *     Cycle through <var>startNode</var>'s parent nodes in tree-order
 *     and return the first match to <var>selector</var>.
 * @param {string} selector
 *     CSS selector expression.
 *
 * @returns {Node=}
 *     First match to <var>selector</var>, or null if no match was found.
 */
dom.findClosest = function (startNode, selector) {
  let node = startNode;
  while (node.parentNode && node.parentNode.nodeType == ELEMENT_NODE) {
    node = node.parentNode;
    if (node.matches(selector)) {
      return node;
    }
  }
  return null;
};

/**
 * Determines if <var>obj<var> is an HTML or JS collection.
 *
 * @param {object} seq
 *     Type to determine.
 *
 * @returns {boolean}
 *     True if <var>seq</va> is a collection.
 */
dom.isCollection = function (seq) {
  switch (Object.prototype.toString.call(seq)) {
    case "[object Arguments]":
    case "[object Array]":
    case "[object DOMTokenList]":
    case "[object FileList]":
    case "[object HTMLAllCollection]":
    case "[object HTMLCollection]":
    case "[object HTMLFormControlsCollection]":
    case "[object HTMLOptionsCollection]":
    case "[object NodeList]":
      return true;

    default:
      return false;
  }
};

/**
 * Determines if <var>shadowRoot</var> is detached.
 *
 * A ShadowRoot is detached if its node document is not the active document
 * or if the element node referred to as its host is stale.
 *
 * @param {ShadowRoot} shadowRoot
 *     ShadowRoot to check for detached state.
 *
 * @returns {boolean}
 *     True if <var>shadowRoot</var> is detached, false otherwise.
 */
dom.isDetached = function (shadowRoot) {
  return !shadowRoot.ownerDocument.isActive() || dom.isStale(shadowRoot.host);
};

/**
 * Determines if <var>el</var> is stale.
 *
 * An element is stale if its node document is not the active document
 * or if it is not connected.
 *
 * @param {Element} el
 *     Element to check for staleness.
 *
 * @returns {boolean}
 *     True if <var>el</var> is stale, false otherwise.
 */
dom.isStale = function (el) {
  if (!el.ownerGlobal) {
    // Without a valid inner window the document is basically closed.
    return true;
  }

  return !el.ownerDocument.isActive() || !el.isConnected;
};

/**
 * Determine if <var>el</var> is selected or not.
 *
 * This operation only makes sense on
 * <tt>&lt;input type=checkbox&gt;</tt>,
 * <tt>&lt;input type=radio&gt;</tt>,
 * and <tt>&gt;option&gt;</tt> elements.
 *
 * @param {Element} el
 *     Element to test if selected.
 *
 * @returns {boolean}
 *     True if element is selected, false otherwise.
 */
dom.isSelected = function (el) {
  if (!el) {
    return false;
  }

  if (dom.isXULElement(el)) {
    if (XUL_CHECKED_ELS.has(el.tagName)) {
      return el.checked;
    } else if (XUL_SELECTED_ELS.has(el.tagName)) {
      return el.selected;
    }
  } else if (dom.isDOMElement(el)) {
    if (el.localName == "input" && ["checkbox", "radio"].includes(el.type)) {
      return el.checked;
    } else if (el.localName == "option") {
      return el.selected;
    }
  }

  return false;
};

/**
 * An element is considered read only if it is an
 * <code>&lt;input&gt;</code> or <code>&lt;textarea&gt;</code>
 * element whose <code>readOnly</code> content IDL attribute is set.
 *
 * @param {Element} el
 *     Element to test is read only.
 *
 * @returns {boolean}
 *     True if element is read only.
 */
dom.isReadOnly = function (el) {
  return (
    dom.isDOMElement(el) &&
    ["input", "textarea"].includes(el.localName) &&
    el.readOnly
  );
};

/**
 * An element is considered disabled if it is a an element
 * that can be disabled, or it belongs to a container group which
 * <code>disabled</code> content IDL attribute affects it.
 *
 * @param {Element} el
 *     Element to test for disabledness.
 *
 * @returns {boolean}
 *     True if element, or its container group, is disabled.
 */
dom.isDisabled = function (el) {
  if (!dom.isDOMElement(el)) {
    return false;
  }

  // Selenium expects that even an enabled "option" element that is a child
  // of a disabled "optgroup" or "select" element to be disabled.
  if (["optgroup", "option"].includes(el.localName) && !el.disabled) {
    const parent = dom.findClosest(el, "optgroup,select");
    return dom.isDisabled(parent);
  }

  return el.matches(":disabled");
};

/**
 * Denotes elements that can be used for typing and clearing.
 *
 * Elements that are considered WebDriver-editable are non-readonly
 * and non-disabled <code>&lt;input&gt;</code> elements in the Text,
 * Search, URL, Telephone, Email, Password, Date, Month, Date and
 * Time Local, Number, Range, Color, and File Upload states, and
 * <code>&lt;textarea&gt;</code> elements.
 *
 * @param {Element} el
 *     Element to test.
 *
 * @returns {boolean}
 *     True if editable, false otherwise.
 */
dom.isMutableFormControl = function (el) {
  if (!dom.isDOMElement(el)) {
    return false;
  }
  if (dom.isReadOnly(el) || dom.isDisabled(el)) {
    return false;
  }

  if (el.localName == "textarea") {
    return true;
  }

  if (el.localName != "input") {
    return false;
  }

  switch (el.type) {
    case "color":
    case "date":
    case "datetime-local":
    case "email":
    case "file":
    case "month":
    case "number":
    case "password":
    case "range":
    case "search":
    case "tel":
    case "text":
    case "time":
    case "url":
    case "week":
      return true;

    default:
      return false;
  }
};

/**
 * An editing host is a node that is either an HTML element with a
 * <code>contenteditable</code> attribute, or the HTML element child
 * of a document whose <code>designMode</code> is enabled.
 *
 * @param {Element} el
 *     Element to determine if is an editing host.
 *
 * @returns {boolean}
 *     True if editing host, false otherwise.
 */
dom.isEditingHost = function (el) {
  return (
    dom.isDOMElement(el) &&
    (el.isContentEditable || el.ownerDocument.designMode == "on")
  );
};

/**
 * Determines if an element is editable according to WebDriver.
 *
 * An element is considered editable if it is not read-only or
 * disabled, and one of the following conditions are met:
 *
 * <ul>
 * <li>It is a <code>&lt;textarea&gt;</code> element.
 *
 * <li>It is an <code>&lt;input&gt;</code> element that is not of
 * the <code>checkbox</code>, <code>radio</code>, <code>hidden</code>,
 * <code>submit</code>, <code>button</code>, or <code>image</code> types.
 *
 * <li>It is content-editable.
 *
 * <li>It belongs to a document in design mode.
 * </ul>
 *
 * @param {Element} el
 *     Element to test if editable.
 *
 * @returns {boolean}
 *     True if editable, false otherwise.
 */
dom.isEditable = function (el) {
  if (!dom.isDOMElement(el)) {
    return false;
  }

  if (dom.isReadOnly(el) || dom.isDisabled(el)) {
    return false;
  }

  return dom.isMutableFormControl(el) || dom.isEditingHost(el);
};

/**
 * This function generates a pair of coordinates relative to the viewport
 * given a target element and coordinates relative to that element's
 * top-left corner.
 *
 * @param {Node} node
 *     Target node.
 * @param {number=} xOffset
 *     Horizontal offset relative to target's top-left corner.
 *     Defaults to the centre of the target's bounding box.
 * @param {number=} yOffset
 *     Vertical offset relative to target's top-left corner.  Defaults to
 *     the centre of the target's bounding box.
 *
 * @returns {Record<string, number>}
 *     X- and Y coordinates.
 *
 * @throws TypeError
 *     If <var>xOffset</var> or <var>yOffset</var> are not numbers.
 */
dom.coordinates = function (node, xOffset = undefined, yOffset = undefined) {
  let box = node.getBoundingClientRect();

  if (typeof xOffset == "undefined" || xOffset === null) {
    xOffset = box.width / 2.0;
  }
  if (typeof yOffset == "undefined" || yOffset === null) {
    yOffset = box.height / 2.0;
  }

  if (typeof yOffset != "number" || typeof xOffset != "number") {
    throw new TypeError("Offset must be a number");
  }

  return {
    x: box.left + xOffset,
    y: box.top + yOffset,
  };
};

/**
 * This function returns true if the node is in the viewport.
 *
 * @param {Element} el
 *     Target element.
 * @param {number=} x
 *     Horizontal offset relative to target.  Defaults to the centre of
 *     the target's bounding box.
 * @param {number=} y
 *     Vertical offset relative to target.  Defaults to the centre of
 *     the target's bounding box.
 *
 * @returns {boolean}
 *     True if if <var>el</var> is in viewport, false otherwise.
 */
dom.inViewport = function (el, x = undefined, y = undefined) {
  let win = el.ownerGlobal;
  let c = dom.coordinates(el, x, y);
  let vp = {
    top: win.pageYOffset,
    left: win.pageXOffset,
    bottom: win.pageYOffset + win.innerHeight,
    right: win.pageXOffset + win.innerWidth,
  };

  return (
    vp.left <= c.x + win.pageXOffset &&
    c.x + win.pageXOffset <= vp.right &&
    vp.top <= c.y + win.pageYOffset &&
    c.y + win.pageYOffset <= vp.bottom
  );
};

/**
 * Gets the element's container element.
 *
 * An element container is defined by the WebDriver
 * specification to be an <tt>&lt;option&gt;</tt> element in a
 * <a href="https://html.spec.whatwg.org/#concept-element-contexts">valid
 * element context</a>, meaning that it has an ancestral element
 * that is either <tt>&lt;datalist&gt;</tt> or <tt>&lt;select&gt;</tt>.
 *
 * If the element does not have a valid context, its container element
 * is itself.
 *
 * @param {Element} el
 *     Element to get the container of.
 *
 * @returns {Element}
 *     Container element of <var>el</var>.
 */
dom.getContainer = function (el) {
  // Does <option> or <optgroup> have a valid context,
  // meaning is it a child of <datalist> or <select>?
  if (["option", "optgroup"].includes(el.localName)) {
    return dom.findClosest(el, "datalist,select") || el;
  }

  return el;
};

/**
 * An element is in view if it is a member of its own pointer-interactable
 * paint tree.
 *
 * This means an element is considered to be in view, but not necessarily
 * pointer-interactable, if it is found somewhere in the
 * <code>elementsFromPoint</code> list at <var>el</var>'s in-view
 * centre coordinates.
 *
 * Before running the check, we change <var>el</var>'s pointerEvents
 * style property to "auto", since elements without pointer events
 * enabled do not turn up in the paint tree we get from
 * document.elementsFromPoint.  This is a specialisation that is only
 * relevant when checking if the element is in view.
 *
 * @param {Element} el
 *     Element to check if is in view.
 *
 * @returns {boolean}
 *     True if <var>el</var> is inside the viewport, or false otherwise.
 */
dom.isInView = function (el) {
  let originalPointerEvents = el.style.pointerEvents;

  try {
    el.style.pointerEvents = "auto";
    const tree = dom.getPointerInteractablePaintTree(el);

    // Bug 1413493 - <tr> is not part of the returned paint tree yet. As
    // workaround check the visibility based on the first contained cell.
    if (el.localName === "tr" && el.cells && el.cells.length) {
      return tree.includes(el.cells[0]);
    }

    return tree.includes(el);
  } finally {
    el.style.pointerEvents = originalPointerEvents;
  }
};

/**
 * This function throws the visibility of the element error if the element is
 * not displayed or the given coordinates are not within the viewport.
 *
 * @param {Element} el
 *     Element to check if visible.
 * @param {number=} x
 *     Horizontal offset relative to target.  Defaults to the centre of
 *     the target's bounding box.
 * @param {number=} y
 *     Vertical offset relative to target.  Defaults to the centre of
 *     the target's bounding box.
 *
 * @returns {boolean}
 *     True if visible, false otherwise.
 */
dom.isVisible = async function (el, x = undefined, y = undefined) {
  let win = el.ownerGlobal;

  if (!(await lazy.atom.isElementDisplayed(el, win))) {
    return false;
  }

  if (el.tagName.toLowerCase() == "body") {
    return true;
  }

  if (!dom.inViewport(el, x, y)) {
    dom.scrollIntoView(el);
    if (!dom.inViewport(el)) {
      return false;
    }
  }
  return true;
};

/**
 * A pointer-interactable element is defined to be the first
 * non-transparent element, defined by the paint order found at the centre
 * point of its rectangle that is inside the viewport, excluding the size
 * of any rendered scrollbars.
 *
 * An element is obscured if the pointer-interactable paint tree at its
 * centre point is empty, or the first element in this tree is not an
 * inclusive descendant of itself.
 *
 * @param {DOMElement} el
 *     Element determine if is pointer-interactable.
 *
 * @returns {boolean}
 *     True if element is obscured, false otherwise.
 */
dom.isObscured = function (el) {
  let tree = dom.getPointerInteractablePaintTree(el);
  return !el.contains(tree[0]);
};

// TODO(ato): Only used by deprecated action API
// https://bugzil.la/1354578
/**
 * Calculates the in-view centre point of an element's client rect.
 *
 * The portion of an element that is said to be _in view_, is the
 * intersection of two squares: the first square being the initial
 * viewport, and the second a DOM element.  From this square we
 * calculate the in-view _centre point_ and convert it into CSS pixels.
 *
 * Although Gecko's system internals allow click points to be
 * given in floating point precision, the DOM operates in CSS pixels.
 * When the in-view centre point is later used to retrieve a coordinate's
 * paint tree, we need to ensure to operate in the same language.
 *
 * As a word of warning, there appears to be inconsistencies between
 * how `DOMElement.elementsFromPoint` and `DOMWindowUtils.sendMouseEvent`
 * internally rounds (ceils/floors) coordinates.
 *
 * @param {DOMRect} rect
 *     Element off a DOMRect sequence produced by calling
 *     `getClientRects` on an {@link Element}.
 * @param {WindowProxy} win
 *     Current window global.
 *
 * @returns {Map.<string, number>}
 *     X and Y coordinates that denotes the in-view centre point of
 *     `rect`.
 */
dom.getInViewCentrePoint = function (rect, win) {
  const { floor, max, min } = Math;

  // calculate the intersection of the rect that is inside the viewport
  let visible = {
    left: max(0, min(rect.x, rect.x + rect.width)),
    right: min(win.innerWidth, max(rect.x, rect.x + rect.width)),
    top: max(0, min(rect.y, rect.y + rect.height)),
    bottom: min(win.innerHeight, max(rect.y, rect.y + rect.height)),
  };

  // arrive at the centre point of the visible rectangle
  let x = (visible.left + visible.right) / 2.0;
  let y = (visible.top + visible.bottom) / 2.0;

  // convert to CSS pixels, as centre point can be float
  x = floor(x);
  y = floor(y);

  return { x, y };
};

/**
 * Produces a pointer-interactable elements tree from a given element.
 *
 * The tree is defined by the paint order found at the centre point of
 * the element's rectangle that is inside the viewport, excluding the size
 * of any rendered scrollbars.
 *
 * @param {DOMElement} el
 *     Element to determine if is pointer-interactable.
 *
 * @returns {Array.<DOMElement>}
 *     Sequence of elements in paint order.
 */
dom.getPointerInteractablePaintTree = function (el) {
  const win = el.ownerGlobal;
  const rootNode = el.getRootNode();

  // pointer-interactable elements tree, step 1
  if (!el.isConnected) {
    return [];
  }

  // steps 2-3
  let rects = el.getClientRects();
  if (!rects.length) {
    return [];
  }

  // step 4
  let centre = dom.getInViewCentrePoint(rects[0], win);

  // step 5
  return rootNode.elementsFromPoint(centre.x, centre.y);
};

// TODO(ato): Not implemented.
// In fact, it's not defined in the spec.
dom.isKeyboardInteractable = () => true;

/**
 * Attempts to scroll into view |el|.
 *
 * @param {DOMElement} el
 *     Element to scroll into view.
 */
dom.scrollIntoView = function (el) {
  if (el.scrollIntoView) {
    el.scrollIntoView({ block: "end", inline: "nearest" });
  }
};

/**
 * Ascertains whether <var>obj</var> is a DOM-, SVG-, or XUL element.
 *
 * @param {object} obj
 *     Object thought to be an <code>Element</code> or
 *     <code>XULElement</code>.
 *
 * @returns {boolean}
 *     True if <var>obj</var> is an element, false otherwise.
 */
dom.isElement = function (obj) {
  return dom.isDOMElement(obj) || dom.isXULElement(obj);
};

dom.isEnabled = function (el) {
  let enabled = false;

  if (el.ownerDocument.contentType !== "text/xml") {
    enabled = !dom.isDisabled(el);
  }

  return enabled;
};

/**
 * Returns the shadow root of an element.
 *
 * @param {Element} el
 *     Element thought to have a <code>shadowRoot</code>
 * @returns {ShadowRoot}
 *     Shadow root of the element.
 */
dom.getShadowRoot = function (el) {
  const shadowRoot = el.openOrClosedShadowRoot;
  if (!shadowRoot) {
    throw new lazy.error.NoSuchShadowRootError();
  }
  return shadowRoot;
};

/**
 * Ascertains whether <var>node</var> is a shadow root.
 *
 * @param {ShadowRoot} node
 *   The node that will be checked to see if it has a shadow root
 *
 * @returns {boolean}
 *     True if <var>node</var> is a shadow root, false otherwise.
 */
dom.isShadowRoot = function (node) {
  return (
    node &&
    node.nodeType === DOCUMENT_FRAGMENT_NODE &&
    node.containingShadowRoot == node
  );
};

/**
 * Ascertains whether <var>obj</var> is a DOM element.
 *
 * @param {object} obj
 *     Object to check.
 *
 * @returns {boolean}
 *     True if <var>obj</var> is a DOM element, false otherwise.
 */
dom.isDOMElement = function (obj) {
  return obj && obj.nodeType == ELEMENT_NODE && !dom.isXULElement(obj);
};

/**
 * Ascertains whether <var>obj</var> is a XUL element.
 *
 * @param {object} obj
 *     Object to check.
 *
 * @returns {boolean}
 *     True if <var>obj</var> is a XULElement, false otherwise.
 */
dom.isXULElement = function (obj) {
  return obj && obj.nodeType === ELEMENT_NODE && obj.namespaceURI === XUL_NS;
};

/**
 * Ascertains whether <var>node</var> is in a privileged document.
 *
 * @param {Node} node
 *     Node to check.
 *
 * @returns {boolean}
 *     True if <var>node</var> is in a privileged document,
 *     false otherwise.
 */
dom.isInPrivilegedDocument = function (node) {
  return !!node?.nodePrincipal?.isSystemPrincipal;
};

/**
 * Ascertains whether <var>obj</var> is a <code>WindowProxy</code>.
 *
 * @param {object} obj
 *     Object to check.
 *
 * @returns {boolean}
 *     True if <var>obj</var> is a DOM window.
 */
dom.isDOMWindow = function (obj) {
  // TODO(ato): This should use Object.prototype.toString.call(node)
  // but it's not clear how to write a good xpcshell test for that,
  // seeing as we stub out a WindowProxy.
  return (
    typeof obj == "object" &&
    obj !== null &&
    typeof obj.toString == "function" &&
    obj.toString() == "[object Window]" &&
    obj.self === obj
  );
};

const boolEls = {
  audio: ["autoplay", "controls", "loop", "muted"],
  button: ["autofocus", "disabled", "formnovalidate"],
  details: ["open"],
  dialog: ["open"],
  fieldset: ["disabled"],
  form: ["novalidate"],
  iframe: ["allowfullscreen"],
  img: ["ismap"],
  input: [
    "autofocus",
    "checked",
    "disabled",
    "formnovalidate",
    "multiple",
    "readonly",
    "required",
  ],
  keygen: ["autofocus", "disabled"],
  menuitem: ["checked", "default", "disabled"],
  ol: ["reversed"],
  optgroup: ["disabled"],
  option: ["disabled", "selected"],
  script: ["async", "defer"],
  select: ["autofocus", "disabled", "multiple", "required"],
  textarea: ["autofocus", "disabled", "readonly", "required"],
  track: ["default"],
  video: ["autoplay", "controls", "loop", "muted"],
};

/**
 * Tests if the attribute is a boolean attribute on element.
 *
 * @param {Element} el
 *     Element to test if <var>attr</var> is a boolean attribute on.
 * @param {string} attr
 *     Attribute to test is a boolean attribute.
 *
 * @returns {boolean}
 *     True if the attribute is boolean, false otherwise.
 */
dom.isBooleanAttribute = function (el, attr) {
  if (!dom.isDOMElement(el)) {
    return false;
  }

  // global boolean attributes that apply to all HTML elements,
  // except for custom elements
  const customElement = !el.localName.includes("-");
  if ((attr == "hidden" || attr == "itemscope") && customElement) {
    return true;
  }

  if (!boolEls.hasOwnProperty(el.localName)) {
    return false;
  }
  return boolEls[el.localName].includes(attr);
};
PK
!<3�ߞSS+chrome/remote/content/shared/Format.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "chrome://remote/content/shared/Log.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "truncateLog",
  "remote.log.truncate",
  false
);

const ELEMENT_NODE = 1;
const MAX_STRING_LENGTH = 250;

/**
 * Pretty-print values passed to template strings.
 *
 * Usage::
 *
 *     let bool = {value: true};
 *     pprint`Expected boolean, got ${bool}`;
 *     => 'Expected boolean, got [object Object] {"value": true}'
 *
 *     let htmlElement = document.querySelector("input#foo");
 *     pprint`Expected element ${htmlElement}`;
 *     => 'Expected element <input id="foo" class="bar baz" type="input">'
 *
 *     pprint`Current window: ${window}`;
 *     => '[object Window https://www.mozilla.org/]'
 */
export function pprint(ss, ...values) {
  function pretty(val) {
    let proto = Object.prototype.toString.call(val);
    if (
      typeof val == "object" &&
      val !== null &&
      "nodeType" in val &&
      val.nodeType === ELEMENT_NODE
    ) {
      return prettyElement(val);
    } else if (["[object Window]", "[object ChromeWindow]"].includes(proto)) {
      return prettyWindowGlobal(val);
    } else if (proto == "[object Attr]") {
      return prettyAttr(val);
    }
    return prettyObject(val);
  }

  function prettyElement(el) {
    let attrs = ["id", "class", "href", "name", "src", "type"];

    let idents = "";
    for (let attr of attrs) {
      if (el.hasAttribute(attr)) {
        idents += ` ${attr}="${el.getAttribute(attr)}"`;
      }
    }

    return `<${el.localName}${idents}>`;
  }

  function prettyWindowGlobal(win) {
    let proto = Object.prototype.toString.call(win);
    return `[${proto.substring(1, proto.length - 1)} ${win.location}]`;
  }

  function prettyAttr(obj) {
    return `[object Attr ${obj.name}="${obj.value}"]`;
  }

  function prettyObject(obj) {
    let proto = Object.prototype.toString.call(obj);
    let s = "";
    try {
      s = JSON.stringify(obj);
    } catch (e) {
      if (e instanceof TypeError) {
        s = `<${e.message}>`;
      } else {
        throw e;
      }
    }
    return `${proto} ${s}`;
  }

  let res = [];
  for (let i = 0; i < ss.length; i++) {
    res.push(ss[i]);
    if (i < values.length) {
      let s;
      try {
        s = pretty(values[i]);
      } catch (e) {
        lazy.logger.warn("Problem pretty printing:", e);
        s = typeof values[i];
      }
      res.push(s);
    }
  }
  return res.join("");
}

/**
 * Template literal that truncates string values in arbitrary objects.
 *
 * Given any object, the template will walk the object and truncate
 * any strings it comes across to a reasonable limit.  This is suitable
 * when you have arbitrary data and data integrity is not important.
 *
 * The strings are truncated in the middle so that the beginning and
 * the end is preserved.  This will make a long, truncated string look
 * like "X <...> Y", where X and Y are half the number of characters
 * of the maximum string length from either side of the string.
 *
 *
 * Usage::
 *
 *     truncate`Hello ${"x".repeat(260)}!`;
 *     // Hello xxx ... xxx!
 *
 * Functions named `toJSON` or `toString` on objects will be called.
 */
export function truncate(strings, ...values) {
  function walk(obj) {
    const typ = Object.prototype.toString.call(obj);

    switch (typ) {
      case "[object Undefined]":
      case "[object Null]":
      case "[object Boolean]":
      case "[object Number]":
        return obj;

      case "[object String]":
        if (lazy.truncateLog && obj.length > MAX_STRING_LENGTH) {
          let s1 = obj.substring(0, MAX_STRING_LENGTH / 2);
          let s2 = obj.substring(obj.length - MAX_STRING_LENGTH / 2);
          return `${s1} ... ${s2}`;
        }
        return obj;

      case "[object Array]":
        return obj.map(walk);

      // arbitrary object
      default:
        if (
          Object.getOwnPropertyNames(obj).includes("toString") &&
          typeof obj.toString == "function"
        ) {
          return walk(obj.toString());
        }

        let rv = {};
        for (let prop in obj) {
          rv[prop] = walk(obj[prop]);
        }
        return rv;
    }
  }

  let res = [];
  for (let i = 0; i < strings.length; ++i) {
    res.push(strings[i]);
    if (i < values.length) {
      let obj = walk(values[i]);
      let t = Object.prototype.toString.call(obj);
      if (t == "[object Array]" || t == "[object Object]") {
        res.push(JSON.stringify(obj));
      } else {
        res.push(obj);
      }
    }
  }
  return res.join("");
}
PK
!</ȴ�(chrome/remote/content/shared/Log.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Log as StdLog } from "resource://gre/modules/Log.sys.mjs";

const PREF_REMOTE_LOG_LEVEL = "remote.log.level";

const lazy = {};

// Lazy getter which returns a cached value of the remote log level. Should be
// used for static getters used to guard hot paths for logging, eg
// isTraceLevelOrMore.
ChromeUtils.defineLazyGetter(lazy, "logLevel", () =>
  Services.prefs.getCharPref(PREF_REMOTE_LOG_LEVEL, StdLog.Level.Fatal)
);

/** E10s compatible wrapper for the standard logger from Log.sys.mjs. */
export class Log {
  static TYPES = {
    CDP: "CDP",
    MARIONETTE: "Marionette",
    REMOTE_AGENT: "RemoteAgent",
    WEBDRIVER_BIDI: "WebDriver BiDi",
  };

  /**
   * Get a logger instance. For each provided type, a dedicated logger instance
   * will be returned, but all loggers are relying on the same preference.
   *
   * @param {string} type
   *     The type of logger to use. Protocol-specific modules should use the
   *     corresponding logger type. Eg. files under /marionette should use
   *     Log.TYPES.MARIONETTE.
   */
  static get(type = Log.TYPES.REMOTE_AGENT) {
    const logger = StdLog.repository.getLogger(type);
    if (!logger.ownAppenders.length) {
      logger.addAppender(new StdLog.DumpAppender());
      logger.manageLevelFromPref(PREF_REMOTE_LOG_LEVEL);
    }
    return logger;
  }

  /**
   * Check if the current log level matches the Debug log level, or any level
   * above that. This should be used to guard logger.debug calls and avoid
   * instanciating logger instances unnecessarily.
   */
  static get isDebugLevelOrMore() {
    // Debug is assigned 20, more verbose log levels have lower values.
    return StdLog.Level[lazy.logLevel] <= StdLog.Level.Debug;
  }

  /**
   * Check if the current log level matches the Trace log level, or any level
   * above that. This should be used to guard logger.trace calls and avoid
   * instanciating logger instances unnecessarily.
   */
  static get isTraceLevelOrMore() {
    // Trace is assigned 10, more verbose log levels have lower values.
    return StdLog.Level[lazy.logLevel] <= StdLog.Level.Trace;
  }

  /**
   * WARNING: This helper is incorrectly implemented and probably doesn't do
   * what you would expect.
   *
   * At the moment `verbose` will be true for the least verbose log levels:
   * INFO, WARN, ERROR and FATAL. Fixing the issue would lead to too much
   * additional log spam on CI so we will need to use another approach, and
   * probably to decouple it from the log level.
   *
   * See https://bugzilla.mozilla.org/show_bug.cgi?id=1828395
   */
  static get verbose() {
    // we can't use Preferences.sys.mjs before first paint,
    // see ../browser/base/content/test/performance/browser_startup.js
    const level = Services.prefs.getStringPref(PREF_REMOTE_LOG_LEVEL, "Info");
    return StdLog.Level[level] >= StdLog.Level.Info;
  }
}
PK
!<����5chrome/remote/content/shared/MobileTabBrowser.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  GeckoViewTabUtil: "resource://gre/modules/GeckoViewTestUtils.sys.mjs",

  windowManager: "chrome://remote/content/shared/WindowManager.sys.mjs",
});

// GeckoView shim for Desktop's gBrowser
export class MobileTabBrowser {
  constructor(window) {
    this.window = window;
  }

  get tabs() {
    return [this.window.tab];
  }

  get selectedTab() {
    return this.window.tab;
  }

  set selectedTab(tab) {
    if (tab != this.selectedTab) {
      throw new Error("GeckoView only supports a single tab");
    }

    // Synthesize a custom TabSelect event to indicate that a tab has been
    // selected even when we don't change it.
    const event = this.window.CustomEvent("TabSelect", {
      bubbles: true,
      cancelable: false,
      detail: {
        previousTab: this.selectedTab,
      },
    });
    this.window.document.dispatchEvent(event);
  }

  get selectedBrowser() {
    return this.selectedTab.linkedBrowser;
  }

  addEventListener() {
    this.window.addEventListener(...arguments);
  }

  /**
   * Create a new tab.
   *
   * @param {string} uriString
   *     The URI string to load within the newly opened tab.
   *
   * @returns {Promise<Tab>}
   *     The created tab.
   * @throws {Error}
   *     Throws an error if the tab cannot be created.
   */
  addTab(uriString) {
    return lazy.GeckoViewTabUtil.createNewTab(uriString);
  }

  getTabForBrowser(browser) {
    if (browser != this.selectedBrowser) {
      throw new Error("GeckoView only supports a single tab");
    }

    return this.selectedTab;
  }

  removeEventListener() {
    this.window.removeEventListener(...arguments);
  }

  removeTab(tab) {
    if (tab != this.selectedTab) {
      throw new Error("GeckoView only supports a single tab");
    }

    return lazy.windowManager.closeWindow(this.window);
  }
}
PK
!<�4���F�F-chrome/remote/content/shared/Navigate.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",

  Deferred: "chrome://remote/content/shared/Sync.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  NavigationListener:
    "chrome://remote/content/shared/listeners/NavigationListener.sys.mjs",
  PromptListener:
    "chrome://remote/content/shared/listeners/PromptListener.sys.mjs",
  truncate: "chrome://remote/content/shared/Format.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.REMOTE_AGENT)
);

// Define a custom multiplier to apply to the unload timer on various platforms.
// This multiplier should only reflect the navigation performance of the
// platform and not the overall performance.
ChromeUtils.defineLazyGetter(lazy, "UNLOAD_TIMEOUT_MULTIPLIER", () => {
  if (AppConstants.MOZ_CODE_COVERAGE) {
    // Navigation on ccov platforms can be extremely slow because new processes
    // need to be instrumented for coverage on startup.
    return 16;
  }

  if (AppConstants.ASAN || AppConstants.DEBUG || AppConstants.TSAN) {
    // Use an extended timeout on slow platforms.
    return 8;
  }

  return 1;
});

export const DEFAULT_UNLOAD_TIMEOUT = 200;

// Load flag for an error page from the DocShell (0x0001U << 16)
const LOAD_FLAG_ERROR_PAGE = 0x10000;

const STATE_START = Ci.nsIWebProgressListener.STATE_START;
const STATE_STOP = Ci.nsIWebProgressListener.STATE_STOP;

/**
 * Returns the multiplier used for the unload timer. Useful for tests which
 * assert the behavior of this timeout.
 */
export function getUnloadTimeoutMultiplier() {
  return lazy.UNLOAD_TIMEOUT_MULTIPLIER;
}

// Used to keep weak references of webProgressListeners alive.
const webProgressListeners = new Set();

/**
 * Wait until the initial load of the given WebProgress is done.
 *
 * @param {WebProgress} webProgress
 *     The WebProgress instance to observe.
 * @param {object=} options
 * @param {boolean=} options.resolveWhenStarted
 *     Flag to indicate that the Promise has to be resolved when the
 *     page load has been started. Otherwise wait until the page has
 *     finished loading. Defaults to `false`.
 * @param {number=} options.unloadTimeout
 *     Time to allow before the page gets unloaded. See ProgressListener options.
 * @returns {Promise}
 *     Promise which resolves when the page load is in the expected state.
 *     Values as returned:
 *       - {nsIURI} currentURI The current URI of the page
 *       - {nsIURI} targetURI Target URI of the navigation
 */
export async function waitForInitialNavigationCompleted(
  webProgress,
  options = {}
) {
  const { resolveWhenStarted = false, unloadTimeout } = options;

  const browsingContext = webProgress.browsingContext;

  // Start the listener right away to avoid race conditions.
  const listener = new ProgressListener(webProgress, {
    resolveWhenStarted,
    unloadTimeout,
  });
  const navigated = listener.start();

  // Right after a browsing context has been attached it could happen that
  // no window global has been set yet. Consider this as nothing has been
  // loaded yet.
  let isInitial = true;
  if (browsingContext.currentWindowGlobal) {
    isInitial = browsingContext.currentWindowGlobal.isInitialDocument;
  }

  const isLoadingDocument = listener.isLoadingDocument;
  lazy.logger.trace(
    lazy.truncate`[${browsingContext.id}] Wait for initial navigation: isInitial=${isInitial}, isLoadingDocument=${isLoadingDocument}`
  );

  // If the current document is not the initial "about:blank" and is also
  // no longer loading, assume the navigation is done and return.
  if (!isInitial && !isLoadingDocument) {
    lazy.logger.trace(
      lazy.truncate`[${browsingContext.id}] Document already finished loading: ${browsingContext.currentURI?.spec}`
    );

    // Will resolve the navigated promise.
    listener.stop();
  }

  try {
    await navigated;
  } catch (e) {
    // Ignore any error if the initial navigation failed.
    lazy.logger.debug(
      lazy.truncate`[${browsingContext.id}] Initial Navigation to ${listener.currentURI?.spec} failed: ${e}`
    );
  }

  return {
    currentURI: listener.currentURI,
    targetURI: listener.targetURI,
  };
}

/**
 * WebProgressListener to observe for page loads.
 */
export class ProgressListener {
  #expectNavigation;
  #resolveWhenStarted;
  #unloadTimeout;
  #waitForExplicitStart;
  #webProgress;

  #deferredNavigation;
  #errorName;
  #navigationId;
  #navigationListener;
  #promptListener;
  #seenStartFlag;
  #targetURI;
  #unloadTimerId;

  /**
   * Create a new WebProgressListener instance.
   *
   * @param {WebProgress} webProgress
   *     The web progress to attach the listener to.
   * @param {object=} options
   * @param {boolean=} options.expectNavigation
   *     Flag to indicate that a navigation is guaranteed to happen.
   *     When set to `true`, the ProgressListener will ignore options.unloadTimeout
   *     and will only resolve when the expected navigation happens.
   *     Defaults to `false`.
   * @param {NavigationManager=} options.navigationManager
   *     The NavigationManager where navigations for the current session are
   *     monitored.
   * @param {boolean=} options.resolveWhenStarted
   *     Flag to indicate that the Promise has to be resolved when the
   *     page load has been started. Otherwise wait until the page has
   *     finished loading. Defaults to `false`.
   * @param {string=} options.targetURI
   *     The target URI for the navigation.
   * @param {number=} options.unloadTimeout
   *     Time to allow before the page gets unloaded. Defaults to 200ms on
   *     regular platforms. A multiplier will be applied on slower platforms
   *     (eg. debug, ccov...).
   *     Ignored if options.expectNavigation is set to `true`
   * @param {boolean=} options.waitForExplicitStart
   *     Flag to indicate that the Promise can only resolve after receiving a
   *     STATE_START state change. In other words, if the webProgress is already
   *     navigating, the Promise will only resolve for the next navigation.
   *     Defaults to `false`.
   */
  constructor(webProgress, options = {}) {
    const {
      expectNavigation = false,
      navigationManager = null,
      resolveWhenStarted = false,
      targetURI,
      unloadTimeout = DEFAULT_UNLOAD_TIMEOUT,
      waitForExplicitStart = false,
    } = options;

    this.#expectNavigation = expectNavigation;
    this.#resolveWhenStarted = resolveWhenStarted;
    this.#unloadTimeout = unloadTimeout * lazy.UNLOAD_TIMEOUT_MULTIPLIER;
    this.#waitForExplicitStart = waitForExplicitStart;
    this.#webProgress = webProgress;

    this.#deferredNavigation = null;
    this.#errorName = null;
    this.#seenStartFlag = false;
    this.#targetURI = targetURI;
    this.#unloadTimerId = null;

    if (navigationManager !== null) {
      this.#navigationListener = new lazy.NavigationListener(navigationManager);
      this.#navigationListener.on(
        "navigation-failed",
        this.#onNavigationFailed
      );
      this.#navigationListener.startListening();
    }

    this.#promptListener = new lazy.PromptListener();
    this.#promptListener.on("opened", this.#onPromptOpened);
    this.#promptListener.startListening();
  }

  destroy() {
    this.#promptListener.stopListening();
    this.#promptListener.off("opened", this.#onPromptOpened);
    this.#promptListener.destroy();

    if (this.#navigationListener) {
      this.#navigationListener.stopListening();
      this.#navigationListener.off(
        "navigation-failed",
        this.#onNavigationFailed
      );
      this.#navigationListener.destroy();
    }
  }

  get #messagePrefix() {
    return `[${this.browsingContext.id}] ${this.constructor.name}`;
  }

  get browsingContext() {
    return this.#webProgress.browsingContext;
  }

  get currentURI() {
    return this.#webProgress.browsingContext.currentURI;
  }

  get documentURI() {
    return this.#webProgress.browsingContext.currentWindowGlobal.documentURI;
  }

  get isInitialDocument() {
    return this.#webProgress.browsingContext.currentWindowGlobal
      .isInitialDocument;
  }

  get isLoadingDocument() {
    return this.#webProgress.isLoadingDocument;
  }

  get isStarted() {
    return !!this.#deferredNavigation;
  }

  get loadType() {
    return this.#webProgress.loadType;
  }

  get targetURI() {
    return this.#targetURI;
  }

  #checkLoadingState(request, options = {}) {
    const { isStart = false, isStop = false, status = 0 } = options;

    this.#trace(
      `Loading state: isStart=${isStart} isStop=${isStop} status=0x${status.toString(
        16
      )}, loadType=0x${this.loadType.toString(16)}`
    );
    if (isStart && !this.#seenStartFlag) {
      this.#seenStartFlag = true;

      this.#targetURI = this.#getTargetURI(request);

      this.#trace(lazy.truncate`Started loading ${this.targetURI?.spec}`);

      if (this.#unloadTimerId !== null) {
        lazy.clearTimeout(this.#unloadTimerId);
        this.#trace("Cleared the unload timer");
        this.#unloadTimerId = null;
      }

      if (this.#resolveWhenStarted) {
        this.#trace("Request to stop listening when navigation started");
        this.stop();
        return;
      }
    }

    if (isStop && this.#seenStartFlag) {
      // Treat NS_ERROR_PARSED_DATA_CACHED as a success code
      // since navigation happened and content has been loaded.
      if (
        !Components.isSuccessCode(status) &&
        status != Cr.NS_ERROR_PARSED_DATA_CACHED
      ) {
        const errorName = ChromeUtils.getXPCOMErrorName(status);

        if (this.loadType & LOAD_FLAG_ERROR_PAGE) {
          // Wait for the next location change notification to ensure that the
          // real error page was loaded.
          this.#trace(`Error=${errorName}, wait for redirect to error page`);
          this.#errorName = errorName;
          return;
        }

        // Handle an aborted navigation. While for an initial document another
        // navigation to the real document will happen it's not the case for
        // normal documents. Here we need to stop the listener immediately.
        if (status == Cr.NS_BINDING_ABORTED && this.isInitialDocument) {
          this.#trace(
            "Ignore aborted navigation error to the initial document."
          );
          return;
        }

        this.stop({ error: new Error(errorName) });
        return;
      }

      // If a non initial page finished loading the navigation is done.
      if (!this.isInitialDocument) {
        this.stop();
        return;
      }

      // Otherwise wait for a potential additional page load.
      this.#trace(
        "Initial document loaded. Wait for a potential further navigation."
      );
      this.#seenStartFlag = false;
      this.#setUnloadTimer();
    }
  }

  #getErrorName(documentURI) {
    try {
      // Otherwise try to retrieve it from the document URI if it is an
      // error page like `about:neterror?e=contentEncodingError&u=http%3A//...`
      const regex = /about:.*error\?e=([^&]*)/;
      return documentURI.spec.match(regex)[1];
    } catch (e) {
      // Or return a generic name
      return "Address rejected";
    }
  }

  #getTargetURI(request) {
    try {
      return request.QueryInterface(Ci.nsIChannel).originalURI;
    } catch (e) {}

    return null;
  }

  #onNavigationFailed = (eventName, data) => {
    const { errorName, navigationId } = data;

    if (this.#navigationId === navigationId) {
      this.#trace(
        `Received "navigation-failed" event with error=${errorName}. Stopping the navigation.`
      );
      this.stop({ error: new Error(errorName) });
    }
  };

  #onPromptOpened = (eventName, data) => {
    const { prompt, contentBrowser } = data;
    const { promptType } = prompt;

    this.#trace(`A prompt of type=${promptType} is open`);
    // Prompt open events come for top level context,
    // that's why in case of navigation in iframe we also have to find
    // top level context to identify if this navigation is affected.
    const topLevelContext = this.browsingContext.top
      ? this.browsingContext.top
      : this.browsingContext;
    if (
      topLevelContext === contentBrowser.browsingContext &&
      promptType === "beforeunload" &&
      this.#resolveWhenStarted
    ) {
      this.#trace(
        "A beforeunload prompt is open in the context of the navigated context and resolveWhenStarted=true. " +
          "Stopping the navigation."
      );
      this.#seenStartFlag = true;
      this.stop();
    }
  };

  #setUnloadTimer() {
    if (this.#expectNavigation) {
      this.#trace("Skip setting the unload timer");
    } else {
      this.#trace(`Setting unload timer (${this.#unloadTimeout}ms)`);

      this.#unloadTimerId = lazy.setTimeout(() => {
        this.#trace(`No navigation detected: ${this.currentURI?.spec}`);
        // Assume the target is the currently loaded URI.
        this.#targetURI = this.currentURI;
        this.stop();
      }, this.#unloadTimeout);
    }
  }

  #trace(message) {
    lazy.logger.trace(lazy.truncate`${this.#messagePrefix} ${message}`);
  }

  onStateChange(progress, request, flag, status) {
    this.#checkLoadingState(request, {
      isStart: !!(flag & STATE_START),
      isStop: !!(flag & STATE_STOP),
      status,
    });
  }

  onLocationChange(progress, request, location, flag) {
    if (flag & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
      // If an error page has been loaded abort the navigation.
      const errorName = this.#errorName || this.#getErrorName(this.documentURI);
      this.#trace(
        lazy.truncate`Location=errorPage, error=${errorName}, url=${this.documentURI.spec}`
      );
      this.stop({ error: new Error(errorName) });
      return;
    }

    if (flag & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
      const stop = type => {
        this.#targetURI = location;
        this.#trace(`Location=${type}: ${this.#targetURI?.spec}`);
        this.stop();
      };

      if (location.hasRef) {
        // If the target URL contains a hash, handle the navigation as a
        // fragment navigation.
        stop("fragmentNavigated");
        return;
      }

      stop("sameDocument");
    }
  }

  /**
   * Start observing web progress changes.
   *
   * @param {string=} navigationId
   *     The UUID for the navigation.
   * @returns {Promise}
   *     A promise that will resolve when the navigation has been finished.
   */
  start(navigationId) {
    this.#navigationId = navigationId;

    if (this.#deferredNavigation) {
      throw new Error(`Progress listener already started`);
    }

    this.#trace(
      `Start: expectNavigation=${this.#expectNavigation} resolveWhenStarted=${
        this.#resolveWhenStarted
      } unloadTimeout=${this.#unloadTimeout} waitForExplicitStart=${
        this.#waitForExplicitStart
      }`
    );

    if (this.#webProgress.isLoadingDocument) {
      this.#targetURI = this.#getTargetURI(this.#webProgress.documentRequest);
      this.#trace(`Document already loading ${this.#targetURI?.spec}`);

      if (this.#resolveWhenStarted && !this.#waitForExplicitStart) {
        this.#trace(
          "Resolve on document loading if not waiting for a load or a new navigation"
        );
        return Promise.resolve();
      }
    }

    this.#deferredNavigation = new lazy.Deferred();

    // Enable all location change and state notifications to get informed about an upcoming load
    // as early as possible.
    this.#webProgress.addProgressListener(
      this,
      Ci.nsIWebProgress.NOTIFY_LOCATION | Ci.nsIWebProgress.NOTIFY_STATE_ALL
    );

    webProgressListeners.add(this);

    if (this.#webProgress.isLoadingDocument && !this.#waitForExplicitStart) {
      this.#checkLoadingState(this.#webProgress.documentRequest, {
        isStart: true,
      });
    } else {
      // If the document is not loading yet wait some time for the navigation
      // to be started.
      this.#setUnloadTimer();
    }

    return this.#deferredNavigation.promise;
  }

  /**
   * Stop observing web progress changes.
   *
   * @param {object=} options
   * @param {Error=} options.error
   *     If specified the navigation promise will be rejected with this error.
   */
  stop(options = {}) {
    const { error } = options;

    this.#trace(
      lazy.truncate`Stop: has error=${!!error} url=${this.currentURI.spec}`
    );

    if (!this.#deferredNavigation) {
      throw new Error("Progress listener not yet started");
    }

    lazy.clearTimeout(this.#unloadTimerId);
    this.#unloadTimerId = null;

    this.#webProgress.removeProgressListener(
      this,
      Ci.nsIWebProgress.NOTIFY_LOCATION | Ci.nsIWebProgress.NOTIFY_STATE_ALL
    );
    webProgressListeners.delete(this);

    if (!this.#targetURI) {
      // If no target URI has been set yet it should be the current URI
      this.#targetURI = this.browsingContext.currentURI;
    }

    if (error) {
      this.#deferredNavigation.reject(error);
    } else {
      this.#deferredNavigation.resolve();
    }

    this.#deferredNavigation = null;
  }

  /**
   * Stop the progress listener if and only if we already detected a navigation
   * start.
   *
   * @param {object=} options
   * @param {Error=} options.error
   *     If specified the navigation promise will be rejected with this error.
   */
  stopIfStarted(options) {
    this.#trace(`Stop if started: seenStartFlag=${this.#seenStartFlag}`);
    if (this.#seenStartFlag) {
      this.stop(options);
    }
  }

  toString() {
    return `[object ${this.constructor.name}]`;
  }

  get QueryInterface() {
    return ChromeUtils.generateQI([
      "nsIWebProgressListener",
      "nsISupportsWeakReference",
    ]);
  }
}
PK
!<ô�\IVIV6chrome/remote/content/shared/NavigationManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  BrowsingContextListener:
    "chrome://remote/content/shared/listeners/BrowsingContextListener.sys.mjs",
  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  PromptListener:
    "chrome://remote/content/shared/listeners/PromptListener.sys.mjs",
  registerNavigationListenerActor:
    "chrome://remote/content/shared/js-window-actors/NavigationListenerActor.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
  truncate: "chrome://remote/content/shared/Format.sys.mjs",
  unregisterNavigationListenerActor:
    "chrome://remote/content/shared/js-window-actors/NavigationListenerActor.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

/**
 * @typedef {object} BrowsingContextDetails
 * @property {string} browsingContextId - The browsing context id.
 * @property {string} browserId - The id of the Browser owning the browsing
 *     context.
 * @property {BrowsingContext=} context - The BrowsingContext itself, if
 *     available.
 * @property {boolean} isTopBrowsingContext - Whether the browsing context is
 *     top level.
 */

/**
 * @typedef {object} NavigationInfo
 * @property {boolean} finished - Whether the navigation is finished or not.
 * @property {string} navigationId - The UUID for the navigation.
 * @property {string} navigable - The UUID for the navigable.
 * @property {string} url - The target url for the navigation.
 */

/**
 * The NavigationRegistry is responsible for monitoring all navigations happening
 * in the browser.
 *
 * It relies on a JSWindowActor pair called NavigationListener{Parent|Child},
 * found under remote/shared/js-window-actors. As a simple overview, the
 * NavigationListenerChild will monitor navigations in all window globals using
 * content process WebProgressListener, and will forward each relevant update to
 * the NavigationListenerParent
 *
 * The NavigationRegistry singleton holds the map of navigations, from navigable
 * to NavigationInfo. It will also be called by NavigationListenerParent
 * whenever a navigation event happens.
 *
 * This singleton is not exported outside of this class, and consumers instead
 * need to use the NavigationManager class. The NavigationRegistry keeps track
 * of how many NavigationListener instances are currently listening in order to
 * know if the NavigationListenerActor should be registered or not.
 *
 * The NavigationRegistry exposes an API to retrieve the current or last
 * navigation for a given navigable, and also forwards events to notify about
 * navigation updates to individual NavigationManager instances.
 *
 * @class NavigationRegistry
 */
class NavigationRegistry extends EventEmitter {
  #contextListener;
  #managers;
  #navigations;
  #preRegisteredNavigationIds;
  #promptListener;

  constructor() {
    super();

    // Set of NavigationManager instances currently used.
    this.#managers = new Set();

    // Maps navigable id to NavigationInfo.
    this.#navigations = new Map();

    // Maps navigable id to navigation id. Only used to pre-register navigation
    // ids before the actual event is detected.
    this.#preRegisteredNavigationIds = new Map();

    this.#contextListener = new lazy.BrowsingContextListener();
    this.#contextListener.on("discarded", this.#onContextDiscarded);

    this.#promptListener = new lazy.PromptListener();
    this.#promptListener.on("closed", this.#onPromptClosed);
    this.#promptListener.on("opened", this.#onPromptOpened);
  }

  /**
   * Retrieve the last known navigation data for a given browsing context.
   *
   * @param {BrowsingContext} context
   *     The browsing context for which the navigation event was recorded.
   * @returns {NavigationInfo|null}
   *     The last known navigation data, or null.
   */
  getNavigationForBrowsingContext(context) {
    if (!lazy.TabManager.isValidCanonicalBrowsingContext(context)) {
      // Bail out if the provided context is not a valid CanonicalBrowsingContext
      // instance.
      return null;
    }

    const navigableId = lazy.TabManager.getIdForBrowsingContext(context);
    if (!this.#navigations.has(navigableId)) {
      return null;
    }

    return this.#navigations.get(navigableId);
  }

  /**
   * Start monitoring navigations in all browsing contexts. This will register
   * the NavigationListener JSWindowActor and will initialize them in all
   * existing browsing contexts.
   */
  startMonitoring(listener) {
    if (this.#managers.size == 0) {
      lazy.registerNavigationListenerActor();
      this.#contextListener.startListening();
      this.#promptListener.startListening();
    }

    this.#managers.add(listener);
  }

  /**
   * Stop monitoring navigations. This will unregister the NavigationListener
   * JSWindowActor and clear the information collected about navigations so far.
   */
  stopMonitoring(listener) {
    if (!this.#managers.has(listener)) {
      return;
    }

    this.#managers.delete(listener);
    if (this.#managers.size == 0) {
      this.#contextListener.stopListening();
      this.#promptListener.stopListening();
      lazy.unregisterNavigationListenerActor();
      // Clear the map.
      this.#navigations = new Map();
    }
  }

  /**
   * Called when a fragment navigation is recorded from the
   * NavigationListener actors.
   *
   * This entry point is only intended to be called from
   * NavigationListenerParent, to avoid setting up observers or listeners,
   * which are unnecessary since NavigationManager has to be a singleton.
   *
   * @param {object} data
   * @param {BrowsingContext} data.context
   *     The browsing context for which the navigation event was recorded.
   * @param {string} data.url
   *     The URL as string for the navigation.
   * @returns {NavigationInfo}
   *     The navigation created for this hash changed navigation.
   */
  notifyFragmentNavigated(data) {
    const { contextDetails, url } = data;

    const context = this.#getContextFromContextDetails(contextDetails);
    const navigableId = lazy.TabManager.getIdForBrowsingContext(context);

    const navigationId = this.#getOrCreateNavigationId(navigableId);
    const navigation = { finished: true, navigationId, url };
    this.#navigations.set(navigableId, navigation);

    // Hash change navigations are immediately done, fire a single event.
    this.emit("fragment-navigated", { navigationId, navigableId, url });

    return navigation;
  }
  /**
   * Called when a same-document navigation is recorded from the
   * NavigationListener actors.
   *
   * This entry point is only intended to be called from
   * NavigationListenerParent, to avoid setting up observers or listeners,
   * which are unnecessary since NavigationManager has to be a singleton.
   *
   * @param {object} data
   * @param {BrowsingContext} data.context
   *     The browsing context for which the navigation event was recorded.
   * @param {string} data.url
   *     The URL as string for the navigation.
   * @returns {NavigationInfo}
   *     The navigation created for this same-document navigation.
   */
  notifySameDocumentChanged(data) {
    const { contextDetails, url } = data;

    const context = this.#getContextFromContextDetails(contextDetails);
    const navigableId = lazy.TabManager.getIdForBrowsingContext(context);

    const navigationId = this.#getOrCreateNavigationId(navigableId);
    const navigation = { finished: true, navigationId, url };
    this.#navigations.set(navigableId, navigation);

    // Same document navigations are immediately done, fire a single event.

    this.emit("same-document-changed", { navigationId, navigableId, url });

    return navigation;
  }

  /**
   * Called when a navigation-failed event is recorded from the
   * NavigationListener actors.
   *
   * This entry point is only intended to be called from
   * NavigationListenerParent, to avoid setting up observers or listeners,
   * which are unnecessary since NavigationManager has to be a singleton.
   *
   * @param {object} data
   * @param {BrowsingContextDetails} data.contextDetails
   *     The details about the browsing context for this navigation.
   * @param {string} data.errorName
   *     The error message.
   * @param {string} data.url
   *     The URL as string for the navigation.
   * @returns {NavigationInfo}
   *     The created navigation or the ongoing navigation, if applicable.
   */
  notifyNavigationFailed(data) {
    const { contextDetails, errorName, url } = data;

    const context = this.#getContextFromContextDetails(contextDetails);
    const navigableId = lazy.TabManager.getIdForBrowsingContext(context);

    const navigation = this.#navigations.get(navigableId);

    if (!navigation) {
      lazy.logger.trace(
        lazy.truncate`[${navigableId}] No navigation found to fail for url: ${url}`
      );
      return null;
    }

    if (navigation.finished) {
      lazy.logger.trace(
        `[${navigableId}] Navigation already marked as finished, navigationId: ${navigation.navigationId}`
      );
      return navigation;
    }

    lazy.logger.trace(
      lazy.truncate`[${navigableId}] Navigation failed for url: ${url} (${navigation.navigationId})`
    );

    navigation.finished = true;

    this.emit("navigation-failed", {
      contextId: context.id,
      errorName,
      navigationId: navigation.navigationId,
      navigableId,
      url,
    });

    return navigation;
  }

  /**
   * Called when a navigation-started event is recorded from the
   * NavigationListener actors.
   *
   * This entry point is only intended to be called from
   * NavigationListenerParent, to avoid setting up observers or listeners,
   * which are unnecessary since NavigationManager has to be a singleton.
   *
   * @param {object} data
   * @param {BrowsingContextDetails} data.contextDetails
   *     The details about the browsing context for this navigation.
   * @param {string} data.url
   *     The URL as string for the navigation.
   * @returns {NavigationInfo}
   *     The created navigation or the ongoing navigation, if applicable.
   */
  notifyNavigationStarted(data) {
    const { contextDetails, url } = data;

    const context = this.#getContextFromContextDetails(contextDetails);
    const navigableId = lazy.TabManager.getIdForBrowsingContext(context);

    let navigation = this.#navigations.get(navigableId);
    if (navigation && !navigation.finished) {
      // Bug 1908952. As soon as we have support for the "url" field in case of beforeunload
      // prompt being open, we can remove "!navigation.url" check.
      if (!navigation.url || navigation.url === url) {
        // If we are already monitoring a navigation for this navigable and the same url,
        // for which we did not receive a navigation-stopped event, this navigation
        // is already tracked and we don't want to create another id & event.
        lazy.logger.trace(
          `[${navigableId}] Skipping already tracked navigation, navigationId: ${navigation.navigationId}`
        );
        return navigation;
      }

      lazy.logger.trace(
        `[${navigableId}] We're going to fail the navigation for url: ${navigation.url} (${navigation.navigationId}), ` +
          "since it was interrupted by a new navigation."
      );

      // If there is already a navigation in progress but with a different url,
      // it means that this navigation was interrupted by a new navigation.
      // Note: ideally we should monitor this using NS_BINDING_ABORTED,
      // but due to intermittent issues, when monitoring this in content processes,
      // we can't reliable use it.
      notifyNavigationFailed({
        contextDetails,
        errorName: "A new navigation interrupted an unfinished navigation",
        url: navigation.url,
      });
    }

    const navigationId = this.#getOrCreateNavigationId(navigableId);
    navigation = { finished: false, navigationId, url };
    this.#navigations.set(navigableId, navigation);

    lazy.logger.trace(
      lazy.truncate`[${navigableId}] Navigation started for url: ${url} (${navigationId})`
    );

    this.emit("navigation-started", { navigationId, navigableId, url });

    return navigation;
  }

  /**
   * Called when a navigation-stopped event is recorded from the
   * NavigationListener actors.
   *
   * @param {object} data
   * @param {BrowsingContextDetails} data.contextDetails
   *     The details about the browsing context for this navigation.
   * @param {string} data.url
   *     The URL as string for the navigation.
   * @returns {NavigationInfo}
   *     The stopped navigation if any, or null.
   */
  notifyNavigationStopped(data) {
    const { contextDetails, url } = data;

    const context = this.#getContextFromContextDetails(contextDetails);
    const navigableId = lazy.TabManager.getIdForBrowsingContext(context);

    const navigation = this.#navigations.get(navigableId);
    if (!navigation) {
      lazy.logger.trace(
        lazy.truncate`[${navigableId}] No navigation found to stop for url: ${url}`
      );
      return null;
    }

    if (navigation.finished) {
      lazy.logger.trace(
        `[${navigableId}] Navigation already marked as finished, navigationId: ${navigation.navigationId}`
      );
      return navigation;
    }

    lazy.logger.trace(
      lazy.truncate`[${navigableId}] Navigation finished for url: ${url} (${navigation.navigationId})`
    );

    navigation.finished = true;

    this.emit("navigation-stopped", {
      navigationId: navigation.navigationId,
      navigableId,
      url,
    });

    return navigation;
  }

  /**
   * Register a navigation id to be used for the next navigation for the
   * provided browsing context details.
   *
   * @param {object} data
   * @param {BrowsingContextDetails} data.contextDetails
   *     The details about the browsing context for this navigation.
   * @returns {string}
   *     The UUID created the upcoming navigation.
   */
  registerNavigationId(data) {
    const { contextDetails } = data;
    const context = this.#getContextFromContextDetails(contextDetails);
    const navigableId = lazy.TabManager.getIdForBrowsingContext(context);

    const navigationId = lazy.generateUUID();
    this.#preRegisteredNavigationIds.set(navigableId, navigationId);

    return navigationId;
  }

  #getContextFromContextDetails(contextDetails) {
    if (contextDetails.context) {
      return contextDetails.context;
    }

    return contextDetails.isTopBrowsingContext
      ? BrowsingContext.getCurrentTopByBrowserId(contextDetails.browserId)
      : BrowsingContext.get(contextDetails.browsingContextId);
  }

  #getOrCreateNavigationId(navigableId) {
    let navigationId;
    if (this.#preRegisteredNavigationIds.has(navigableId)) {
      navigationId = this.#preRegisteredNavigationIds.get(
        navigableId,
        navigationId
      );
      this.#preRegisteredNavigationIds.delete(navigableId);
    } else {
      navigationId = lazy.generateUUID();
    }
    return navigationId;
  }

  #onContextDiscarded = async (eventName, data = {}) => {
    const { browsingContext, why } = data;

    // Filter out top-level browsing contexts that are destroyed because of a
    // cross-group navigation.
    if (why === "replace") {
      return;
    }

    // TODO: Bug 1852941. We should also filter out events which are emitted
    // for DevTools frames.

    // Filter out notifications for chrome context until support gets
    // added (bug 1722679).
    if (!browsingContext.webProgress) {
      return;
    }

    const navigableId =
      lazy.TabManager.getIdForBrowsingContext(browsingContext);
    const navigation = this.#navigations.get(navigableId);

    // No need to fail navigation, if there is no navigation in progress.
    if (!navigation) {
      return;
    }

    notifyNavigationFailed({
      contextDetails: {
        context: browsingContext,
      },
      errorName: "Browsing context got discarded",
      url: navigation.url,
    });

    // If the navigable is discarded, we can safely clean up the navigation info.
    this.#navigations.delete(navigableId);
  };

  #onPromptClosed = (eventName, data) => {
    const { contentBrowser, detail } = data;
    const { accepted, promptType } = detail;

    // Send navigation failed event if beforeunload prompt was rejected.
    if (promptType === "beforeunload" && accepted === false) {
      const browsingContext = contentBrowser.browsingContext;

      notifyNavigationFailed({
        contextDetails: {
          context: browsingContext,
        },
        errorName: "Beforeunload prompt was rejected",
        // Bug 1908952. Add support for the "url" field.
      });
    }
  };

  #onPromptOpened = (eventName, data) => {
    const { contentBrowser, prompt } = data;
    const { promptType } = prompt;

    // We should start the navigation when beforeunload prompt is open.
    if (promptType === "beforeunload") {
      const browsingContext = contentBrowser.browsingContext;

      notifyNavigationStarted({
        contextDetails: {
          context: browsingContext,
        },
        // Bug 1908952. Add support for the "url" field.
      });
    }
  };
}

// Create a private NavigationRegistry singleton.
const navigationRegistry = new NavigationRegistry();

/**
 * See NavigationRegistry.notifyHashChanged.
 *
 * This entry point is only intended to be called from NavigationListenerParent,
 * to avoid setting up observers or listeners, which are unnecessary since
 * NavigationRegistry has to be a singleton.
 */
export function notifyFragmentNavigated(data) {
  return navigationRegistry.notifyFragmentNavigated(data);
}

/**
 * See NavigationRegistry.notifySameDocumentChanged.
 *
 * This entry point is only intended to be called from NavigationListenerParent,
 * to avoid setting up observers or listeners, which are unnecessary since
 * NavigationRegistry has to be a singleton.
 */
export function notifySameDocumentChanged(data) {
  return navigationRegistry.notifySameDocumentChanged(data);
}

/**
 * See NavigationRegistry.notifyNavigationFailed.
 *
 * This entry point is only intended to be called from NavigationListenerParent,
 * to avoid setting up observers or listeners, which are unnecessary since
 * NavigationRegistry has to be a singleton.
 */
export function notifyNavigationFailed(data) {
  return navigationRegistry.notifyNavigationFailed(data);
}

/**
 * See NavigationRegistry.notifyNavigationStarted.
 *
 * This entry point is only intended to be called from NavigationListenerParent,
 * to avoid setting up observers or listeners, which are unnecessary since
 * NavigationRegistry has to be a singleton.
 */
export function notifyNavigationStarted(data) {
  return navigationRegistry.notifyNavigationStarted(data);
}

/**
 * See NavigationRegistry.notifyNavigationStopped.
 *
 * This entry point is only intended to be called from NavigationListenerParent,
 * to avoid setting up observers or listeners, which are unnecessary since
 * NavigationRegistry has to be a singleton.
 */
export function notifyNavigationStopped(data) {
  return navigationRegistry.notifyNavigationStopped(data);
}

export function registerNavigationId(data) {
  return navigationRegistry.registerNavigationId(data);
}

/**
 * The NavigationManager exposes the NavigationRegistry data via a class which
 * needs to be individually instantiated by each consumer. This allow to track
 * how many consumers need navigation data at any point so that the
 * NavigationRegistry can register or unregister the underlying JSWindowActors
 * correctly.
 *
 * @fires navigation-started
 *    The NavigationManager emits "navigation-started" when a new navigation is
 *    detected, with the following object as payload:
 *      - {string} navigationId - The UUID for the navigation.
 *      - {string} navigableId - The UUID for the navigable.
 *      - {string} url - The target url for the navigation.
 * @fires navigation-stopped
 *    The NavigationManager emits "navigation-stopped" when a known navigation
 *    is stopped, with the following object as payload:
 *      - {string} navigationId - The UUID for the navigation.
 *      - {string} navigableId - The UUID for the navigable.
 *      - {string} url - The target url for the navigation.
 */
export class NavigationManager extends EventEmitter {
  #monitoring;

  constructor() {
    super();

    this.#monitoring = false;
  }

  destroy() {
    this.stopMonitoring();
  }

  getNavigationForBrowsingContext(context) {
    return navigationRegistry.getNavigationForBrowsingContext(context);
  }

  startMonitoring() {
    if (this.#monitoring) {
      return;
    }

    this.#monitoring = true;
    navigationRegistry.startMonitoring(this);
    navigationRegistry.on("fragment-navigated", this.#onNavigationEvent);
    navigationRegistry.on("navigation-failed", this.#onNavigationEvent);
    navigationRegistry.on("navigation-started", this.#onNavigationEvent);
    navigationRegistry.on("navigation-stopped", this.#onNavigationEvent);
    navigationRegistry.on("same-document-changed", this.#onNavigationEvent);
  }

  stopMonitoring() {
    if (!this.#monitoring) {
      return;
    }

    this.#monitoring = false;
    navigationRegistry.stopMonitoring(this);
    navigationRegistry.off("fragment-navigated", this.#onNavigationEvent);
    navigationRegistry.off("navigation-failed", this.#onNavigationEvent);
    navigationRegistry.off("navigation-started", this.#onNavigationEvent);
    navigationRegistry.off("navigation-stopped", this.#onNavigationEvent);
    navigationRegistry.off("same-document-changed", this.#onNavigationEvent);
  }

  #onNavigationEvent = (eventName, data) => {
    this.emit(eventName, data);
  };
}
PK
!<����8chrome/remote/content/shared/NetworkCacheManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  BrowsingContextListener:
    "chrome://remote/content/shared/listeners/BrowsingContextListener.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
});

/**
 * Enum of possible network cache behaviors.
 *
 * @readonly
 * @enum {CacheBehavior}
 */
export const CacheBehavior = {
  Default: "default",
  Bypass: "bypass",
};

/**
 * The NetworkCacheManager is responsible for managing the cache status (enabling/disabling cache)
 * for navigables. It's meant to be a singleton, and the consumers can use the exported
 * methods to change the cache status or perform the state cleanup.
 *
 * @class NetworkCacheManager
 */
class NetworkCacheManager {
  #contextListener;
  #defaultCacheBehavior;
  #navigableCacheBehaviorMap;

  constructor() {
    this.#contextListener = new lazy.BrowsingContextListener();
    this.#contextListener.on("attached", this.#onContextAttached);

    this.#defaultCacheBehavior = CacheBehavior.Default;
    // WeakMap from navigables to cache behavior settings (CacheBehavior).
    this.#navigableCacheBehaviorMap = new WeakMap();
  }

  destroy() {
    this.#contextListener.off("attached", this.#onContextAttached);
    this.#contextListener.destroy();

    this.cleanup();
  }

  #getLoadFlags(behavior) {
    return behavior === CacheBehavior.Bypass
      ? Ci.nsIRequest.LOAD_BYPASS_CACHE
      : Ci.nsIRequest.LOAD_NORMAL;
  }

  #getWeakMapSize(weakMap) {
    return ChromeUtils.nondeterministicGetWeakMapKeys(weakMap).length;
  }

  #onContextAttached = (eventName, data = {}) => {
    if (this.#defaultCacheBehavior === CacheBehavior.Bypass) {
      this.#setLoadFlagsForBrowsingContext(
        data.browsingContext,
        this.#getLoadFlags(CacheBehavior.Bypass)
      );
    }
  };

  #setDefaultCacheBehavior(behavior) {
    this.#defaultCacheBehavior = behavior;
    this.#navigableCacheBehaviorMap = new WeakMap();

    const loadFlags = this.#getLoadFlags(behavior);

    // Update cache settings for all existing navigables.
    for (const browser of lazy.TabManager.browsers) {
      this.#setLoadFlagsForBrowsingContext(browser.browsingContext, loadFlags);
    }

    // In case the cache is globally disabled we have to listen to all
    // newly attached contexts and update the cache behavior for them.
    if (this.#defaultCacheBehavior === CacheBehavior.Bypass) {
      this.#contextListener.startListening();
    } else {
      this.#contextListener.stopListening();
    }
  }

  #setLoadFlagsForBrowsingContext(browsingContext, loadFlags) {
    if (browsingContext.defaultLoadFlags !== loadFlags) {
      browsingContext.defaultLoadFlags = loadFlags;
    }
  }

  /**
   * Reset network cache behavior to the default.
   */
  cleanup() {
    this.#setDefaultCacheBehavior(CacheBehavior.Default);

    if (this.#getWeakMapSize(this.#navigableCacheBehaviorMap) === 0) {
      return;
    }

    const loadFlags = this.#getLoadFlags(CacheBehavior.Default);

    for (const browser of lazy.TabManager.browsers) {
      if (this.#navigableCacheBehaviorMap.has(browser.browsingContext)) {
        this.#setLoadFlagsForBrowsingContext(
          browser.browsingContext,
          loadFlags
        );
      }
    }

    this.#navigableCacheBehaviorMap = new WeakMap();
  }

  /**
   * Update network cache behavior to a provided value
   * and optionally specified contexts.
   *
   * @param {CacheBehavior} behavior
   *     An enum value to set the network cache behavior.
   * @param {Array<BrowsingContext>=} contexts
   *     The list of browsing contexts where the network cache
   *     behaviour should be updated.
   */
  updateCacheBehavior(behavior, contexts = null) {
    if (contexts === null) {
      this.#setDefaultCacheBehavior(behavior);
      return;
    }

    const loadFlags = this.#getLoadFlags(behavior);

    for (const context of contexts) {
      if (this.#navigableCacheBehaviorMap.get(context) === behavior) {
        continue;
      }

      this.#setLoadFlagsForBrowsingContext(context, loadFlags);

      if (behavior === CacheBehavior.Default) {
        this.#navigableCacheBehaviorMap.delete(context);
      } else {
        this.#navigableCacheBehaviorMap.set(context, behavior);
      }
    }
  }
}

// Create a private NetworkCacheManager singleton.
const networkCacheManager = new NetworkCacheManager();

export function updateCacheBehavior(behavior, contexts) {
  return networkCacheManager.updateCacheBehavior(behavior, contexts);
}

export function cleanupCacheBypassState() {
  return networkCacheManager.cleanup();
}
PK
!<qj�4ss>chrome/remote/content/shared/NetworkDecodedBodySizeMap.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class NetworkDecodedBodySizeMap {
  #channelIdToBodySizePromiseMap;

  constructor() {
    this.#channelIdToBodySizePromiseMap = new Map();
  }

  destroy() {
    this.#channelIdToBodySizePromiseMap = null;
  }

  async getDecodedBodySize(channelId) {
    if (!this.#channelIdToBodySizePromiseMap.has(channelId)) {
      const { promise, resolve } = Promise.withResolvers();
      this.#channelIdToBodySizePromiseMap.set(channelId, {
        promise,
        resolve,
      });

      await promise;
    }
    const mapEntry = this.#channelIdToBodySizePromiseMap.get(channelId);
    return mapEntry.decodedBodySize;
  }

  setDecodedBodySize(channelId, decodedBodySize) {
    if (!this.#channelIdToBodySizePromiseMap.has(channelId)) {
      const { promise, resolve } = Promise.withResolvers();
      this.#channelIdToBodySizePromiseMap.set(channelId, {
        decodedBodySize,
        promise,
        resolve,
      });
    }
    const mapEntry = this.#channelIdToBodySizePromiseMap.get(channelId);

    mapEntry.decodedBodySize = decodedBodySize;
    mapEntry.resolve(decodedBodySize);
  }

  delete(channelId) {
    this.#channelIdToBodySizePromiseMap.delete(channelId);
  }
}
PK
!<jR�
s1s13chrome/remote/content/shared/NetworkRequest.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  NetworkHelper:
    "resource://devtools/shared/network-observer/NetworkHelper.sys.mjs",
  NetworkUtils:
    "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs",

  notifyNavigationStarted:
    "chrome://remote/content/shared/NavigationManager.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
});

/**
 * The NetworkRequest class is a wrapper around the internal channel which
 * provides getters and methods closer to fetch's response concept
 * (https://fetch.spec.whatwg.org/#concept-response).
 */
export class NetworkRequest {
  #alreadyCompleted;
  #channel;
  #contextId;
  #eventRecord;
  #isDataURL;
  #navigationId;
  #navigationManager;
  #rawHeaders;
  #redirectCount;
  #requestId;
  #timedChannel;
  #wrappedChannel;

  /**
   *
   * @param {nsIChannel} channel
   *     The channel for the request.
   * @param {object} params
   * @param {NetworkEventRecord} params.networkEventRecord
   *     The NetworkEventRecord owning this NetworkRequest.
   * @param {NavigationManager} params.navigationManager
   *     The NavigationManager where navigations for the current session are
   *     monitored.
   * @param {string=} params.rawHeaders
   *     The request's raw (ie potentially compressed) headers
   */
  constructor(channel, params) {
    const { eventRecord, navigationManager, rawHeaders = "" } = params;

    this.#channel = channel;
    this.#eventRecord = eventRecord;
    this.#isDataURL = this.#channel instanceof Ci.nsIDataChannel;
    this.#navigationManager = navigationManager;
    this.#rawHeaders = rawHeaders;

    const currentTime = Date.now();
    this.#timedChannel =
      this.#channel instanceof Ci.nsITimedChannel
        ? this.#channel.QueryInterface(Ci.nsITimedChannel)
        : {
            redirectCount: 0,
            channelCreationTime: currentTime,
            redirectStartTime: 0,
            redirectEndTime: 0,
            domainLookupStartTime: currentTime,
            domainLookupEndTime: currentTime,
            connectStartTime: currentTime,
            connectEndTime: currentTime,
            secureConnectionStartTime: currentTime,
            requestStartTime: currentTime,
            responseStartTime: currentTime,
            responseEndTime: currentTime,
          };
    this.#wrappedChannel = ChannelWrapper.get(channel);

    this.#redirectCount = this.#timedChannel.redirectCount;
    // The wrappedChannel id remains identical across redirects, whereas
    // nsIChannel.channelId is different for each and every request.
    this.#requestId = this.#wrappedChannel.id.toString();

    this.#contextId = this.#getContextId();
    this.#navigationId = this.#getNavigationId();
  }

  get alreadyCompleted() {
    return this.#alreadyCompleted;
  }

  get channel() {
    return this.#channel;
  }

  get contextId() {
    return this.#contextId;
  }

  get errorText() {
    // TODO: Update with a proper error text. Bug 1873037.
    return ChromeUtils.getXPCOMErrorName(this.#channel.status);
  }

  get headersSize() {
    // TODO: rawHeaders will not be updated after modifying the headers via
    // request interception. Need to find another way to retrieve the
    // information dynamically.
    return this.#rawHeaders.length;
  }

  get isHttpChannel() {
    return this.#channel instanceof Ci.nsIHttpChannel;
  }

  get method() {
    return this.#isDataURL ? "GET" : this.#channel.requestMethod;
  }

  get navigationId() {
    return this.#navigationId;
  }

  get postDataSize() {
    const charset = lazy.NetworkUtils.getCharset(this.#channel);
    const sentBody = lazy.NetworkHelper.readPostTextFromRequest(
      this.#channel,
      charset
    );
    return sentBody ? sentBody.length : 0;
  }

  get redirectCount() {
    return this.#redirectCount;
  }

  get requestId() {
    return this.#requestId;
  }

  get serializedURL() {
    return this.#channel.URI.spec;
  }

  get wrappedChannel() {
    return this.#wrappedChannel;
  }

  set alreadyCompleted(value) {
    this.#alreadyCompleted = value;
  }

  /**
   * Add information about raw headers, collected from NetworkObserver events.
   *
   * @param {string} rawHeaders
   *     The raw headers.
   */
  addRawHeaders(rawHeaders) {
    this.#rawHeaders = rawHeaders || "";
  }

  /**
   * Clear a request header from the request's headers list.
   *
   * @param {string} name
   *     The header's name.
   */
  clearRequestHeader(name) {
    this.#channel.setRequestHeader(
      name, // aName
      "", // aValue="" as an empty value
      false // aMerge=false to force clearing the header
    );
  }

  /**
   * Retrieve the Fetch timings for the NetworkRequest.
   *
   * @returns {object}
   *     Object with keys corresponding to fetch timing names, and their
   *     corresponding values.
   */
  getFetchTimings() {
    const {
      channelCreationTime,
      redirectStartTime,
      redirectEndTime,
      dispatchFetchEventStartTime,
      cacheReadStartTime,
      domainLookupStartTime,
      domainLookupEndTime,
      connectStartTime,
      connectEndTime,
      secureConnectionStartTime,
      requestStartTime,
      responseStartTime,
      responseEndTime,
    } = this.#timedChannel;

    // fetchStart should be the post-redirect start time, which should be the
    // first non-zero timing from: dispatchFetchEventStart, cacheReadStart and
    // domainLookupStart. See https://www.w3.org/TR/navigation-timing-2/#processing-model
    const fetchStartTime =
      dispatchFetchEventStartTime ||
      cacheReadStartTime ||
      domainLookupStartTime;

    // Bug 1805478: Per spec, the origin time should match Performance API's
    // timeOrigin for the global which initiated the request. This is not
    // available in the parent process, so for now we will use 0.
    const timeOrigin = 0;

    return {
      timeOrigin,
      requestTime: this.#convertTimestamp(channelCreationTime, timeOrigin),
      redirectStart: this.#convertTimestamp(redirectStartTime, timeOrigin),
      redirectEnd: this.#convertTimestamp(redirectEndTime, timeOrigin),
      fetchStart: this.#convertTimestamp(fetchStartTime, timeOrigin),
      dnsStart: this.#convertTimestamp(domainLookupStartTime, timeOrigin),
      dnsEnd: this.#convertTimestamp(domainLookupEndTime, timeOrigin),
      connectStart: this.#convertTimestamp(connectStartTime, timeOrigin),
      connectEnd: this.#convertTimestamp(connectEndTime, timeOrigin),
      tlsStart: this.#convertTimestamp(secureConnectionStartTime, timeOrigin),
      tlsEnd: this.#convertTimestamp(connectEndTime, timeOrigin),
      requestStart: this.#convertTimestamp(requestStartTime, timeOrigin),
      responseStart: this.#convertTimestamp(responseStartTime, timeOrigin),
      responseEnd: this.#convertTimestamp(responseEndTime, timeOrigin),
    };
  }

  /**
   * Retrieve the list of headers for the NetworkRequest.
   *
   * @returns {Array.Array}
   *     Array of (name, value) tuples.
   */
  getHeadersList() {
    const headers = [];

    if (this.#channel instanceof Ci.nsIHttpChannel) {
      this.#channel.visitRequestHeaders({
        visitHeader(name, value) {
          // The `Proxy-Authorization` header even though it appears on the channel is not
          // actually sent to the server for non CONNECT requests after the HTTP/HTTPS tunnel
          // is setup by the proxy.
          if (name == "Proxy-Authorization") {
            return;
          }
          headers.push([name, value]);
        },
      });
    }

    if (this.#channel instanceof Ci.nsIDataChannel) {
      // Data channels have no request headers.
      return [];
    }

    if (this.#channel instanceof Ci.nsIFileChannel) {
      // File channels have no request headers.
      return [];
    }

    return headers;
  }

  /**
   * Set the request post body
   *
   * @param {string} body
   *     The body to set.
   */
  setRequestBody(body) {
    // Update the requestObserversCalled flag to allow modifying the request,
    // and reset once done.
    this.#channel.requestObserversCalled = false;

    try {
      this.#channel.QueryInterface(Ci.nsIUploadChannel2);
      const bodyStream = Cc[
        "@mozilla.org/io/string-input-stream;1"
      ].createInstance(Ci.nsIStringInputStream);
      bodyStream.setData(body, body.length);
      this.#channel.explicitSetUploadStream(
        bodyStream,
        null,
        -1,
        this.#channel.requestMethod,
        false
      );
    } finally {
      // Make sure to reset the flag once the modification was attempted.
      this.#channel.requestObserversCalled = true;
    }
  }

  /**
   * Set a request header
   *
   * @param {string} name
   *     The header's name.
   * @param {string} value
   *     The header's value.
   * @param {object} options
   * @param {boolean} options.merge
   *     True if the value should be merged with the existing value, false if it
   *     should override it. Defaults to false.
   */
  setRequestHeader(name, value, options) {
    const { merge = false } = options;
    this.#channel.setRequestHeader(name, value, merge);
  }

  /**
   * Update the request's method.
   *
   * @param {string} method
   *     The method to set.
   */
  setRequestMethod(method) {
    // Update the requestObserversCalled flag to allow modifying the request,
    // and reset once done.
    this.#channel.requestObserversCalled = false;

    try {
      this.#channel.requestMethod = method;
    } finally {
      // Make sure to reset the flag once the modification was attempted.
      this.#channel.requestObserversCalled = true;
    }
  }

  /**
   * Allows to bypass the actual network request and immediately respond with
   * the provided nsIReplacedHttpResponse.
   *
   * @param {nsIReplacedHttpResponse} replacedHttpResponse
   *     The replaced response to use.
   */
  setResponseOverride(replacedHttpResponse) {
    this.wrappedChannel.channel
      .QueryInterface(Ci.nsIHttpChannelInternal)
      .setResponseOverride(replacedHttpResponse);

    const rawHeaders = [];
    replacedHttpResponse.visitResponseHeaders({
      visitHeader(name, value) {
        rawHeaders.push(`${name}: ${value}`);
      },
    });

    // Setting an override bypasses the usual codepath for network responses.
    // There will be no notification about receiving a response.
    // However, there will be a notification about the end of the response.
    // Therefore, simulate a addResponseStart here to make sure we handle
    // addResponseContent properly.
    this.#eventRecord.prepareResponseStart({
      channel: this.#channel,
      fromCache: false,
      rawHeaders: rawHeaders.join("\n"),
    });
  }

  /**
   * Convert the provided request timing to a timing relative to the beginning
   * of the request. All timings are numbers representing high definition
   * timestamps.
   *
   * @param {number} timing
   *     High definition timestamp for a request timing relative from the time
   *     origin.
   * @param {number} requestTime
   *     High definition timestamp for the request start time relative from the
   *     time origin.
   *
   * @returns {number}
   *     High definition timestamp for the request timing relative to the start
   *     time of the request, or 0 if the provided timing was 0.
   */
  #convertTimestamp(timing, requestTime) {
    if (timing == 0) {
      return 0;
    }

    return timing - requestTime;
  }

  #getContextId() {
    const id = lazy.NetworkUtils.getChannelBrowsingContextID(this.#channel);
    const browsingContext = BrowsingContext.get(id);
    return lazy.TabManager.getIdForBrowsingContext(browsingContext);
  }

  #getNavigationId() {
    if (!this.#channel.isDocument) {
      return null;
    }

    const browsingContext = lazy.TabManager.getBrowsingContextById(
      this.#contextId
    );

    let navigation =
      this.#navigationManager.getNavigationForBrowsingContext(browsingContext);

    // `onBeforeRequestSent` might be too early for the NavigationManager.
    // If there is no ongoing navigation, create one ourselves.
    // TODO: Bug 1835704 to detect navigations earlier and avoid this.
    if (!navigation || navigation.finished) {
      navigation = lazy.notifyNavigationStarted({
        contextDetails: { context: browsingContext },
        url: this.serializedURL,
      });
    }

    return navigation ? navigation.navigationId : null;
  }
}
PK
!<��Y4chrome/remote/content/shared/NetworkResponse.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  NetworkUtils:
    "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs",
});

/**
 * The NetworkResponse class is a wrapper around the internal channel which
 * provides getters and methods closer to fetch's response concept
 * (https://fetch.spec.whatwg.org/#concept-response).
 */
export class NetworkResponse {
  #channel;
  #decodedBodySize;
  #encodedBodySize;
  #fromCache;
  #fromServiceWorker;
  #isDataURL;
  #headersTransmittedSize;
  #status;
  #statusMessage;
  #totalTransmittedSize;
  #wrappedChannel;

  /**
   *
   * @param {nsIChannel} channel
   *     The channel for the response.
   * @param {object} params
   * @param {boolean} params.fromCache
   *     Whether the response was read from the cache or not.
   * @param {boolean} params.fromServiceWorker
   *     Whether the response is coming from a service worker or not.
   * @param {string=} params.rawHeaders
   *     The response's raw (ie potentially compressed) headers
   */
  constructor(channel, params) {
    this.#channel = channel;
    const { fromCache, fromServiceWorker, rawHeaders = "" } = params;
    this.#fromCache = fromCache;
    this.#fromServiceWorker = fromServiceWorker;
    this.#isDataURL = this.#channel instanceof Ci.nsIDataChannel;
    this.#wrappedChannel = ChannelWrapper.get(channel);

    this.#decodedBodySize = 0;
    this.#encodedBodySize = 0;
    this.#headersTransmittedSize = rawHeaders.length;
    this.#totalTransmittedSize = rawHeaders.length;

    // See https://github.com/w3c/webdriver-bidi/issues/761
    // For 304 responses, the response will be replaced by the cached response
    // between responseStarted and responseCompleted, which will effectively
    // change the status and statusMessage.
    // Until the issue linked above has been discussed and closed, we will
    // cache the status/statusMessage in order to ensure consistent values
    // between responseStarted and responseCompleted.
    this.#status = this.#isDataURL ? 200 : this.#channel.responseStatus;
    this.#statusMessage = this.#isDataURL
      ? "OK"
      : this.#channel.responseStatusText;
  }

  get decodedBodySize() {
    return this.#decodedBodySize;
  }

  get encodedBodySize() {
    return this.#encodedBodySize;
  }

  get headersTransmittedSize() {
    return this.#headersTransmittedSize;
  }

  get fromCache() {
    return this.#fromCache;
  }

  get fromServiceWorker() {
    return this.#fromServiceWorker;
  }

  get protocol() {
    return lazy.NetworkUtils.getProtocol(this.#channel);
  }

  get serializedURL() {
    return this.#channel.URI.spec;
  }

  get status() {
    return this.#status;
  }

  get statusMessage() {
    return this.#statusMessage;
  }

  get totalTransmittedSize() {
    return this.#totalTransmittedSize;
  }

  /**
   * Clear a response header from the responses's headers list.
   *
   * @param {string} name
   *     The header's name.
   */
  clearResponseHeader(name) {
    this.#channel.setResponseHeader(
      name, // aName
      "", // aValue="" as an empty value
      false // aMerge=false to force clearing the header
    );
  }

  getComputedMimeType() {
    // TODO: DevTools NetworkObserver is computing a similar value in
    // addResponseContent, but uses an inconsistent implementation in
    // addResponseStart. This approach can only be used as early as in
    // addResponseHeaders. We should move this logic to the NetworkObserver and
    // expose mimeType in addResponseStart. Bug 1809670.
    let mimeType = "";

    try {
      if (this.#isDataURL) {
        mimeType = this.#channel.contentType;
      } else {
        mimeType = this.#wrappedChannel.contentType;
      }
      const contentCharset = this.#channel.contentCharset;
      if (contentCharset) {
        mimeType += `;charset=${contentCharset}`;
      }
    } catch (e) {
      // Ignore exceptions when reading contentType/contentCharset
    }

    return mimeType;
  }

  getHeadersList() {
    const headers = [];

    // According to the fetch spec for data URLs we can just hardcode
    // "Content-Type" header.
    if (this.#isDataURL) {
      headers.push(["Content-Type", this.#channel.contentType]);
    } else {
      this.#channel.visitResponseHeaders({
        visitHeader(name, value) {
          headers.push([name, value]);
        },
      });
    }

    return headers;
  }

  /**
   * Set a response header
   *
   * @param {string} name
   *     The header's name.
   * @param {string} value
   *     The header's value.
   * @param {object} options
   * @param {boolean} options.merge
   *     True if the value should be merged with the existing value, false if it
   *     should override it. Defaults to false.
   */
  setResponseHeader(name, value, options) {
    const { merge = false } = options;
    this.#channel.setResponseHeader(name, value, merge);
  }

  setResponseStatus(options) {
    let { status, statusText } = options;
    if (status === null) {
      status = this.#channel.responseStatus;
    }

    if (statusText === null) {
      statusText = this.#channel.responseStatusText;
    }

    this.#channel.setResponseStatus(status, statusText);

    // Update the cached status and statusMessage.
    this.#status = this.#channel.responseStatus;
    this.#statusMessage = this.#channel.responseStatusText;
  }

  /**
   * Set the various response sizes for this response. Depending on how the
   * completion was monitored (DevTools NetworkResponseListener or ChannelWrapper
   * event), sizes need to be retrieved differently.
   * There this is a simple setter and the actual logic to retrieve sizes is in
   * NetworkEventRecord.
   *
   * @param {object} sizes
   * @param {number} sizes.decodedBodySize
   *     The decoded body size.
   * @param {number} sizes.encodedBodySize
   *     The encoded body size.
   * @param {number} sizes.totalTransmittedSize
   *     The total transmitted size.
   */
  setResponseSizes(sizes) {
    const { decodedBodySize, encodedBodySize, totalTransmittedSize } = sizes;
    this.#decodedBodySize = decodedBodySize;
    this.#encodedBodySize = encodedBodySize;
    this.#totalTransmittedSize = totalTransmittedSize;
  }
}
PK
!<��~8��(chrome/remote/content/shared/PDF.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

export const print = {
  maxScaleValue: 2.0,
  minScaleValue: 0.1,
};

print.defaults = {
  // The size of the page in centimeters.
  page: {
    width: 21.59,
    height: 27.94,
  },
  margin: {
    top: 1.0,
    bottom: 1.0,
    left: 1.0,
    right: 1.0,
  },
  orientationValue: ["landscape", "portrait"],
};

print.addDefaultSettings = function (settings) {
  const {
    background = false,
    margin = {},
    orientation = "portrait",
    page = {},
    pageRanges = [],
    scale = 1.0,
    shrinkToFit = true,
  } = settings;

  lazy.assert.object(
    page,
    lazy.pprint`Expected "page" to be an object, got ${page}`
  );
  lazy.assert.object(
    margin,
    lazy.pprint`Expected "margin" to be an object, got ${margin}`
  );

  if (!("width" in page)) {
    page.width = print.defaults.page.width;
  }

  if (!("height" in page)) {
    page.height = print.defaults.page.height;
  }

  if (!("top" in margin)) {
    margin.top = print.defaults.margin.top;
  }

  if (!("bottom" in margin)) {
    margin.bottom = print.defaults.margin.bottom;
  }

  if (!("right" in margin)) {
    margin.right = print.defaults.margin.right;
  }

  if (!("left" in margin)) {
    margin.left = print.defaults.margin.left;
  }

  return {
    background,
    margin,
    orientation,
    page,
    pageRanges,
    scale,
    shrinkToFit,
  };
};

print.getPrintSettings = function (settings) {
  const psService = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
    Ci.nsIPrintSettingsService
  );

  let cmToInches = cm => cm / 2.54;
  const printSettings = psService.createNewPrintSettings();
  printSettings.isInitializedFromPrinter = true;
  printSettings.isInitializedFromPrefs = true;
  printSettings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF;
  printSettings.printerName = "marionette";
  printSettings.printSilent = true;

  // Setting the paperSizeUnit to kPaperSizeMillimeters doesn't work on mac
  printSettings.paperSizeUnit = Ci.nsIPrintSettings.kPaperSizeInches;
  printSettings.paperWidth = cmToInches(settings.page.width);
  printSettings.paperHeight = cmToInches(settings.page.height);
  printSettings.usePageRuleSizeAsPaperSize = true;

  printSettings.marginBottom = cmToInches(settings.margin.bottom);
  printSettings.marginLeft = cmToInches(settings.margin.left);
  printSettings.marginRight = cmToInches(settings.margin.right);
  printSettings.marginTop = cmToInches(settings.margin.top);

  printSettings.printBGColors = settings.background;
  printSettings.printBGImages = settings.background;
  printSettings.scaling = settings.scale;
  printSettings.shrinkToFit = settings.shrinkToFit;

  printSettings.headerStrCenter = "";
  printSettings.headerStrLeft = "";
  printSettings.headerStrRight = "";
  printSettings.footerStrCenter = "";
  printSettings.footerStrLeft = "";
  printSettings.footerStrRight = "";

  // Override any os-specific unwriteable margins
  printSettings.unwriteableMarginTop = 0;
  printSettings.unwriteableMarginLeft = 0;
  printSettings.unwriteableMarginBottom = 0;
  printSettings.unwriteableMarginRight = 0;

  if (settings.orientation === "landscape") {
    printSettings.orientation = Ci.nsIPrintSettings.kLandscapeOrientation;
  }

  if (settings.pageRanges?.length) {
    printSettings.pageRanges = parseRanges(settings.pageRanges);
  }

  return printSettings;
};

/**
 * Convert array of strings of the form ["1-3", "2-4", "7", "9-"] to an flat array of
 * limits, like [1, 4, 7, 7, 9, 2**31 - 1] (meaning 1-4, 7, 9-end)
 *
 * @param {Array.<string|number>} ranges
 *     Page ranges to print, e.g., ['1-5', '8', '11-13'].
 *     Defaults to the empty string, which means print all pages.
 *
 * @returns {Array.<number>}
 *     Even-length array containing page range limits
 */
function parseRanges(ranges) {
  const MAX_PAGES = 0x7fffffff;

  if (ranges.length === 0) {
    return [];
  }

  let allLimits = [];

  for (let range of ranges) {
    let limits;
    if (typeof range !== "string") {
      // We got a single integer so the limits are just that page
      lazy.assert.positiveInteger(
        range,
        lazy.pprint`Expected "range" to be a string or a positive integer, got ${range}`
      );
      limits = [range, range];
    } else {
      // We got a string presumably of the form <int> | <int>? "-" <int>?
      const msg = lazy.pprint`Expected "range" to be of the form <int> or <int>-<int>, got ${range}`;

      limits = range.split("-").map(x => x.trim());
      lazy.assert.that(o => [1, 2].includes(o.length), msg)(limits);

      // Single numbers map to a range with that page at the start and the end
      if (limits.length == 1) {
        limits.push(limits[0]);
      }

      // Need to check that both limits are strings consisting only of
      // decimal digits (or empty strings)
      const assertNumeric = lazy.assert.that(o => /^\d*$/.test(o), msg);
      limits.every(x => assertNumeric(x));

      // Convert from strings representing numbers to actual numbers
      // If we don't have an upper bound, choose something very large;
      // the print code will later truncate this to the number of pages
      limits = limits.map((limitStr, i) => {
        if (limitStr == "") {
          return i == 0 ? 1 : MAX_PAGES;
        }
        return parseInt(limitStr);
      });
    }
    lazy.assert.that(
      x => x[0] <= x[1],
      lazy.pprint`Expected "range" lower limit to be less than the upper limit, got ${range}`
    )(limits);

    allLimits.push(limits);
  }
  // Order by lower limit
  allLimits.sort((a, b) => a[0] - b[0]);
  let parsedRanges = [allLimits.shift()];
  for (let limits of allLimits) {
    let prev = parsedRanges[parsedRanges.length - 1];
    let prevMax = prev[1];
    let [min, max] = limits;
    if (min <= prevMax) {
      // min is inside previous range, so extend the max if needed
      if (max > prevMax) {
        prev[1] = max;
      }
    } else {
      // Otherwise we have a new range
      parsedRanges.push(limits);
    }
  }

  let rv = parsedRanges.flat();
  lazy.logger.debug(`Got page ranges [${rv.join(", ")}]`);
  return rv;
}

print.printToBinaryString = async function (browsingContext, printSettings) {
  // Create a stream to write to.
  const stream = Cc["@mozilla.org/storagestream;1"].createInstance(
    Ci.nsIStorageStream
  );
  stream.init(4096, 0xffffffff);

  printSettings.outputDestination =
    Ci.nsIPrintSettings.kOutputDestinationStream;
  printSettings.outputStream = stream.getOutputStream(0);

  await browsingContext.print(printSettings);

  const inputStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
    Ci.nsIBinaryInputStream
  );

  inputStream.setInputStream(stream.newInputStream(0));

  const available = inputStream.available();
  const bytes = inputStream.readBytes(available);

  stream.close();

  return bytes;
};
PK
!<�����0chrome/remote/content/shared/Permissions.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
});

/**
 * @typedef {string} PermissionState
 */

/**
 * Enum of possible permission states supported by permission APIs.
 *
 * @readonly
 * @enum {PermissionState}
 */
const PermissionState = {
  denied: "denied",
  granted: "granted",
  prompt: "prompt",
};

/** @namespace */
export const permissions = {};

/**
 * Get a permission type for the "storage-access" permission.
 *
 * @param {nsIURI} uri
 *     The URI to use for building the permission type.
 *
 * @returns {string} permissionType
 *    The permission type for the "storage-access" permission.
 */
permissions.getStorageAccessPermissionsType = function (uri) {
  const thirdPartyPrincipalSite = Services.eTLD.getSite(uri);
  return "3rdPartyFrameStorage^" + thirdPartyPrincipalSite;
};

/**
 * Set a permission given a permission descriptor, a permission state,
 * an origin.
 *
 * @param {PermissionDescriptor} descriptor
 *     The descriptor of the permission which will be updated.
 * @param {string} state
 *     State of the permission. It can be `granted`, `denied` or `prompt`.
 * @param {string} origin
 *     The origin which is used as a target for permission update.
 * @param {string=} userContextId
 *     The internal id of the user context which should be used as a target
 *     for permission update.
 *
 * @throws {UnsupportedOperationError}
 *     If <var>state</var> has unsupported value.
 */
permissions.set = function (descriptor, state, origin, userContextId) {
  let principal =
    Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin);

  if (userContextId) {
    principal = Services.scriptSecurityManager.principalWithOA(principal, {
      userContextId,
    });
  }

  switch (state) {
    case "granted": {
      Services.perms.addFromPrincipal(
        principal,
        descriptor.type,
        Services.perms.ALLOW_ACTION
      );
      return;
    }
    case "denied": {
      Services.perms.addFromPrincipal(
        principal,
        descriptor.type,
        Services.perms.DENY_ACTION
      );
      return;
    }
    case "prompt": {
      Services.perms.removeFromPrincipal(principal, descriptor.type);
      return;
    }
    default:
      throw new lazy.error.UnsupportedOperationError(
        "Unrecognized permission keyword for 'Set Permission' operation"
      );
  }
};

/**
 * Validate the permission descriptor.
 *
 * @param {PermissionDescriptor} descriptor
 *     The descriptor of the permission which will be validated.
 *
 * @throws {InvalidArgumentError}
 *     Raised if an argument is of an invalid type or value.
 * @throws {UnsupportedOperationError}
 *     If a permission with <var>descriptor</var> is not supported.
 */
permissions.validateDescriptor = function (descriptor) {
  lazy.assert.object(
    descriptor,
    lazy.pprint`Expected "descriptor" to be an object, got ${descriptor}`
  );
  const permissionName = descriptor.name;
  lazy.assert.string(
    permissionName,
    lazy.pprint`Expected descriptor "name" to be a string, got ${permissionName}`
  );

  // Bug 1609427: PermissionDescriptor for "camera" and "microphone" are not yet implemented.
  if (["camera", "microphone"].includes(permissionName)) {
    throw new lazy.error.UnsupportedOperationError(
      `"descriptor.name" "${permissionName}" is currently unsupported`
    );
  }
};

/**
 * Validate the permission state.
 *
 * @param {PermissionState} state
 *     The state of the permission which will be validated.
 *
 * @throws {InvalidArgumentError}
 *     Raised if an argument is of an invalid type or value.
 */
permissions.validateState = function (state) {
  const permissionStateTypes = Object.keys(PermissionState);
  lazy.assert.that(
    state => permissionStateTypes.includes(state),
    lazy.pprint`Expected "state" to be one of ${permissionStateTypes}, got ${state}`
  )(state);
};
PK
!<�ff+chrome/remote/content/shared/Prompt.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

const COMMON_DIALOG = "chrome://global/content/commonDialog.xhtml";

/** @namespace */
export const modal = {
  ACTION_CLOSED: "closed",
  ACTION_OPENED: "opened",
};

/**
 * Check for already existing modal or tab modal dialogs and
 * return the first one.
 *
 * @param {browser.Context} context
 *     Reference to the browser context to check for existent dialogs.
 *
 * @returns {modal.Dialog}
 *     Returns instance of the Dialog class, or `null` if no modal dialog
 *     is present.
 */
modal.findPrompt = function (context) {
  // First check if there is a modal dialog already present for the
  // current browser window.
  for (let win of Services.wm.getEnumerator(null)) {
    // TODO: Use BrowserWindowTracker.getTopWindow for modal dialogs without
    // an opener.
    if (
      win.document.documentURI === COMMON_DIALOG &&
      win.opener &&
      win.opener === context.window
    ) {
      lazy.logger.trace("Found open window modal prompt");
      return new modal.Dialog(win);
    }
  }

  if (lazy.AppInfo.isAndroid) {
    const geckoViewPrompts = context.window.prompts();
    if (geckoViewPrompts.length) {
      lazy.logger.trace("Found open GeckoView prompt");
      const prompt = geckoViewPrompts[0];
      return new modal.Dialog(prompt);
    }
  }

  const contentBrowser = context.contentBrowser;

  // If no modal dialog has been found yet, also check for tab and content modal
  // dialogs for the current tab.
  //
  // TODO: Find an adequate implementation for Firefox on Android (bug 1708105)
  if (contentBrowser?.tabDialogBox) {
    let dialogs = contentBrowser.tabDialogBox.getTabDialogManager().dialogs;
    if (dialogs.length) {
      lazy.logger.trace("Found open tab modal prompt");
      return new modal.Dialog(dialogs[0].frameContentWindow);
    }

    dialogs = contentBrowser.tabDialogBox.getContentDialogManager().dialogs;

    // Even with the dialog manager handing back a dialog, the `Dialog` property
    // gets lazily added. If it's not set yet, ignore the dialog for now.
    if (dialogs.length && dialogs[0].frameContentWindow.Dialog) {
      lazy.logger.trace("Found open content prompt");
      return new modal.Dialog(dialogs[0].frameContentWindow);
    }
  }
  return null;
};

/**
 * Represents a modal dialog.
 *
 * @param {DOMWindow} dialog
 *     DOMWindow of the dialog.
 */
modal.Dialog = class {
  #win;

  constructor(dialog) {
    this.#win = Cu.getWeakReference(dialog);
  }

  get args() {
    if (lazy.AppInfo.isAndroid) {
      return this.window.args;
    }
    let tm = this.tabModal;
    return tm ? tm.args : null;
  }

  get isOpen() {
    if (lazy.AppInfo.isAndroid) {
      return this.window !== null;
    }
    if (!this.ui) {
      return false;
    }
    return true;
  }

  get isWindowModal() {
    return [
      Services.prompt.MODAL_TYPE_WINDOW,
      Services.prompt.MODAL_TYPE_INTERNAL_WINDOW,
    ].includes(this.args.modalType);
  }

  get tabModal() {
    return this.window?.Dialog;
  }

  get promptType() {
    return this.args.inPermitUnload ? "beforeunload" : this.args.promptType;
  }

  get ui() {
    let tm = this.tabModal;
    return tm ? tm.ui : null;
  }

  /**
   * For Android, this returns a GeckoViewPrompter, which can be used to control prompts.
   * Otherwise, this returns the ChromeWindow associated with an open dialog window if
   * it is currently attached to the DOM.
   */
  get window() {
    if (this.#win) {
      let win = this.#win.get();
      if (win && (lazy.AppInfo.isAndroid || win.parent)) {
        return win;
      }
    }
    return null;
  }

  set text(inputText) {
    if (lazy.AppInfo.isAndroid) {
      this.window.setInputText(inputText);
    } else {
      // see toolkit/components/prompts/content/commonDialog.js
      let { loginTextbox } = this.ui;
      loginTextbox.value = inputText;
    }
  }

  accept() {
    if (lazy.AppInfo.isAndroid) {
      // GeckoView does not have a UI, so the methods are called directly
      this.window.acceptPrompt();
    } else {
      const { button0 } = this.ui;
      button0.click();
    }
  }

  dismiss() {
    if (lazy.AppInfo.isAndroid) {
      // GeckoView does not have a UI, so the methods are called directly
      this.window.dismissPrompt();
    } else {
      const { button0, button1 } = this.ui;
      (button1 ? button1 : button0).click();
    }
  }

  /**
   * Returns text of the prompt.
   *
   * @returns {string | Promise}
   *     Returns string on desktop and Promise on Android.
   */
  async getText() {
    if (lazy.AppInfo.isAndroid) {
      const textPromise = await this.window.getPromptText();
      return textPromise;
    }
    return this.ui.infoBody.textContent;
  }

  /**
   * Returns text of the prompt input.
   *
   * @returns {string}
   *     Returns string on desktop and Promise on Android.
   */
  async getInputText() {
    if (lazy.AppInfo.isAndroid) {
      const textPromise = await this.window.getInputText();
      return textPromise;
    }
    return this.ui.loginTextbox.value;
  }
};
PK
!<|��R%R%*chrome/remote/content/shared/Realm.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  addDebuggerToGlobal: "resource://gre/modules/jsdebugger.sys.mjs",

  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "dbg", () => {
  // eslint-disable-next-line mozilla/reject-globalThis-modification
  lazy.addDebuggerToGlobal(globalThis);
  return new Debugger();
});

/**
 * @typedef {string} RealmType
 */

/**
 * Enum of realm types.
 *
 * @readonly
 * @enum {RealmType}
 */
export const RealmType = {
  AudioWorklet: "audio-worklet",
  DedicatedWorker: "dedicated-worker",
  PaintWorklet: "paint-worklet",
  ServiceWorker: "service-worker",
  SharedWorker: "shared-worker",
  Window: "window",
  Worker: "worker",
  Worklet: "worklet",
};

/**
 * Base class that wraps any kind of WebDriver BiDi realm.
 */
export class Realm {
  #handleObjectMap;
  #id;

  constructor() {
    this.#id = lazy.generateUUID();

    // Map of unique handles (UUIDs) to objects belonging to this realm.
    this.#handleObjectMap = new Map();
  }

  destroy() {
    this.#handleObjectMap = null;
  }

  /**
   * Get the browsing context of the realm instance.
   */
  get browsingContext() {
    return null;
  }

  /**
   * Get the unique identifier of the realm instance.
   *
   * @returns {string} The unique identifier.
   */
  get id() {
    return this.#id;
  }

  /**
   * A getter to get a realm origin.
   *
   * It's required to be implemented in the sub class.
   */
  get origin() {
    throw new Error("Not implemented");
  }

  /**
   * Ensure the provided object can be used within this realm.

   * @param {object} obj
   *     Any non-primitive object.

   * @returns {object}
   *     An object usable in the current realm.
   */
  cloneIntoRealm(obj) {
    return obj;
  }

  /**
   * Remove the reference corresponding to the provided unique handle.
   *
   * @param {string} handle
   *     The unique handle of an object reference tracked in this realm.
   */
  removeObjectHandle(handle) {
    this.#handleObjectMap.delete(handle);
  }

  /**
   * Get a new unique handle for the provided object, creating a strong
   * reference on the object.
   *
   * @param {object} object
   *     Any non-primitive object.
   * @returns {string} The unique handle created for this strong reference.
   */
  getHandleForObject(object) {
    const handle = lazy.generateUUID();
    this.#handleObjectMap.set(handle, object);
    return handle;
  }

  /**
   * Get the basic realm information.
   *
   * @returns {BaseRealmInfo}
   */
  getInfo() {
    return {
      realm: this.#id,
      origin: this.origin,
    };
  }

  /**
   * Retrieve the object corresponding to the provided unique handle.
   *
   * @param {string} handle
   *     The unique handle of an object reference tracked in this realm.
   * @returns {object} object
   *     Any non-primitive object.
   */
  getObjectForHandle(handle) {
    return this.#handleObjectMap.get(handle);
  }
}

/**
 * Wrapper for Window realms including sandbox objects.
 */
export class WindowRealm extends Realm {
  #realmAutomationFeaturesEnabled;
  #globalObject;
  #globalObjectReference;
  #isSandbox;
  #sandboxName;
  #userActivationEnabled;
  #window;

  static type = RealmType.Window;

  /**
   *
   * @param {Window} window
   *     The window global to wrap.
   * @param {object} options
   * @param {string=} options.sandboxName
   *     Name of the sandbox to create if specified. Defaults to `null`.
   */
  constructor(window, options = {}) {
    const { sandboxName = null } = options;

    super();

    this.#isSandbox = sandboxName !== null;
    this.#sandboxName = sandboxName;
    this.#window = window;
    this.#globalObject = this.#isSandbox ? this.#createSandbox() : this.#window;
    this.#globalObjectReference = lazy.dbg.makeGlobalObjectReference(
      this.#globalObject
    );
    this.#realmAutomationFeaturesEnabled = false;
    this.#userActivationEnabled = false;
  }

  destroy() {
    if (this.#realmAutomationFeaturesEnabled) {
      lazy.dbg.disableAsyncStack(this.#globalObject);
      lazy.dbg.disableUnlimitedStacksCapturing(this.#globalObject);
      this.#realmAutomationFeaturesEnabled = false;
    }

    this.#globalObjectReference = null;
    this.#globalObject = null;
    this.#window = null;

    super.destroy();
  }

  get browsingContext() {
    return this.#window.browsingContext;
  }

  get globalObjectReference() {
    return this.#globalObjectReference;
  }

  get isSandbox() {
    return this.#isSandbox;
  }

  get origin() {
    return this.#window.origin;
  }

  get userActivationEnabled() {
    return this.#userActivationEnabled;
  }

  set userActivationEnabled(enable) {
    if (enable === this.#userActivationEnabled) {
      return;
    }

    const document = this.#window.document;
    if (enable) {
      document.notifyUserGestureActivation();
    } else {
      document.clearUserGestureActivation();
    }

    this.#userActivationEnabled = enable;
  }

  #createDebuggerObject(obj) {
    return this.#globalObjectReference.makeDebuggeeValue(obj);
  }

  #createSandbox() {
    const win = this.#window;
    const opts = {
      sameZoneAs: win,
      sandboxPrototype: win,
      wantComponents: false,
      wantXrays: true,
    };

    return new Cu.Sandbox(win, opts);
  }

  #enableRealmAutomationFeatures() {
    if (!this.#realmAutomationFeaturesEnabled) {
      lazy.dbg.enableAsyncStack(this.#globalObject);
      lazy.dbg.enableUnlimitedStacksCapturing(this.#globalObject);
      this.#realmAutomationFeaturesEnabled = true;
    }
  }

  /**
   * Clone the provided object into the scope of this Realm (either a window
   * global, or a sandbox).
   *
   * @param {object} obj
   *     Any non-primitive object.
   *
   * @returns {object}
   *     The cloned object.
   */
  cloneIntoRealm(obj) {
    return Cu.cloneInto(obj, this.#globalObject, { cloneFunctions: true });
  }

  /**
   * Evaluates a provided expression in the context of the current realm.
   *
   * @param {string} expression
   *     The expression to evaluate.
   *
   * @returns {object}
   *     - evaluationStatus {EvaluationStatus} One of "normal", "throw".
   *     - exceptionDetails {ExceptionDetails=} the details of the exception if
   *       the evaluation status was "throw".
   *     - result {RemoteValue=} the result of the evaluation serialized as a
   *       RemoteValue if the evaluation status was "normal".
   */
  executeInGlobal(expression) {
    this.#enableRealmAutomationFeatures();
    return this.#globalObjectReference.executeInGlobal(expression, {
      url: this.#window.document.baseURI,
    });
  }

  /**
   * Call a function in the context of the current realm.
   *
   * @param {string} functionDeclaration
   *     The body of the function to call.
   * @param {Array<object>} functionArguments
   *     The arguments to pass to the function call.
   * @param {object} thisParameter
   *     The value of the `this` keyword for the function call.
   *
   * @returns {object}
   *     - evaluationStatus {EvaluationStatus} One of "normal", "throw".
   *     - exceptionDetails {ExceptionDetails=} the details of the exception if
   *       the evaluation status was "throw".
   *     - result {RemoteValue=} the result of the evaluation serialized as a
   *       RemoteValue if the evaluation status was "normal".
   */
  executeInGlobalWithBindings(
    functionDeclaration,
    functionArguments,
    thisParameter
  ) {
    this.#enableRealmAutomationFeatures();
    const expression = `(${functionDeclaration}).apply(__bidi_this, __bidi_args)`;

    const args = this.cloneIntoRealm([]);
    for (const arg of functionArguments) {
      args.push(arg);
    }

    return this.#globalObjectReference.executeInGlobalWithBindings(
      expression,
      {
        __bidi_args: this.#createDebuggerObject(args),
        __bidi_this: this.#createDebuggerObject(thisParameter),
      },
      {
        url: this.#window.document.baseURI,
      }
    );
  }

  /**
   * Get the realm information.
   *
   * @returns {object}
   *     - context {BrowsingContext} The browsing context, associated with the realm.
   *     - id {string} The realm unique identifier.
   *     - origin {string} The serialization of an origin.
   *     - sandbox {string=} The name of the sandbox.
   *     - type {RealmType.Window} The window realm type.
   */
  getInfo() {
    const baseInfo = super.getInfo();
    const info = {
      ...baseInfo,
      context: this.#window.browsingContext,
      type: WindowRealm.type,
    };

    if (this.#isSandbox) {
      info.sandbox = this.#sandboxName;
    }

    return info;
  }

  /**
   * Log an error caused by a script evaluation.
   *
   * @param {string} message
   *     The error message.
   * @param {Stack} stack
   *     The JavaScript stack trace.
   */
  reportError(message, stack) {
    const { column, line } = stack;

    const scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
    const scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError);

    scriptError.initWithWindowID(
      message,
      this.#window.document.baseURI,
      line,
      column,
      Ci.nsIScriptError.errorFlag,
      "content javascript",
      this.#window.windowGlobalChild.innerWindowId
    );
    Services.console.logMessage(scriptError);
  }
}
PK
!<�j�@�>�>;chrome/remote/content/shared/RecommendedPreferences.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "chrome://remote/content/shared/Log.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "useRecommendedPrefs",
  "remote.prefs.recommended",
  false
);

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

// Ensure we are in the parent process.
if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
  throw new Error(
    "RecommendedPreferences should only be loaded in the parent process"
  );
}

// ALL CHANGES TO THIS LIST MUST HAVE REVIEW FROM A WEBDRIVER PEER!
//
// Preferences are set for automation on startup, unless
// remote.prefs.recommended has been set to false.
//
// Note: Clients do not always use the latest version of the application. As
// such backward compatibility has to be ensured at least for the last three
// releases.

// INSTRUCTIONS TO ADD A NEW PREFERENCE
//
// Preferences for remote control and automation can be set from several entry
// points:
// - remote/shared/RecommendedPreferences.sys.mjs
// - remote/test/puppeteer/packages/browsers/src/browser-data/firefox.ts
// - testing/geckodriver/src/prefs.rs
// - testing/marionette/client/marionette_driver/geckoinstance.py
//
// The preferences in `firefox.ts`, `prefs.rs` and `geckoinstance.py`
// will be applied before the application starts, and should typically be used
// for preferences which cannot be updated during the lifetime of the application.
//
// The preferences in `RecommendedPreferences.sys.mjs` are applied after
// the application has started, which means that the application must apply this
// change dynamically and behave correctly. Note that you can also define
// protocol specific preferences (CDP, WebDriver, ...) which are merged with the
// COMMON_PREFERENCES from `RecommendedPreferences.sys.mjs`.
//
// Additionally, users relying on the Marionette Python client (ie. using
// geckoinstance.py) set `remote.prefs.recommended = false`. This means that
// preferences from `RecommendedPreferences.sys.mjs` are not applied and have to
// be added to the list of preferences in that Python file. Note that there are
// several lists of preferences, either common or specific to a given application
// (Firefox Desktop, Fennec, Thunderbird).
//
// Depending on how users interact with the Remote Agent, they will use different
// combinations of preferences. So it's important to update the preferences files
// so that all users have the proper preferences.
//
// When adding a new preference, follow this guide to decide where to add it:
// - Add the preference to `geckoinstance.py`
// - If the preference has to be set before startup:
//   - Add the preference to `prefs.rs`
//   - Add the preference `browser-data/firefox.ts` in the puppeteer folder
//   - Create a PR to upstream the change on `browser-data/firefox.ts` to puppeteer
// - Otherwise, if the preference can be set after startup:
//   - Add the preference to `RecommendedPreferences.sys.mjs`
const COMMON_PREFERENCES = new Map([
  // Make sure Shield doesn't hit the network.
  ["app.normandy.api_url", ""],

  // Disable automatically upgrading Firefox
  //
  // Note: This preference should have already been set by the client when
  // creating the profile. But if not and to absolutely make sure that updates
  // of Firefox aren't downloaded and applied, enforce its presence.
  ["app.update.disabledForTesting", true],

  // Increase the APZ content response timeout in tests to 1 minute.
  // This is to accommodate the fact that test environments tends to be
  // slower than production environments (with the b2g emulator being
  // the slowest of them all), resulting in the production timeout value
  // sometimes being exceeded and causing false-positive test failures.
  //
  // (bug 1176798, bug 1177018, bug 1210465)
  ["apz.content_response_timeout", 60000],

  // Don't show the content blocking introduction panel.
  // We use a larger number than the default 22 to have some buffer
  // This can be removed once Firefox 69 and 68 ESR and are no longer supported.
  ["browser.contentblocking.introCount", 99],

  // Indicate that the download panel has been shown once so that
  // whichever download test runs first doesn't show the popup
  // inconsistently.
  ["browser.download.panel.shown", true],

  // Make sure newtab weather doesn't hit the network to retrieve weather data.
  [
    "browser.newtabpage.activity-stream.discoverystream.region-weather-config",
    "",
  ],

  // Make sure newtab wallpapers don't hit the network to retrieve wallpaper data.
  ["browser.newtabpage.activity-stream.newtabWallpapers.enabled", false],
  ["browser.newtabpage.activity-stream.newtabWallpapers.v2.enabled", false],

  // Make sure Topsites doesn't hit the network to retrieve sponsored tiles.
  ["browser.newtabpage.activity-stream.showSponsoredTopSites", false],

  // Always display a blank page
  ["browser.newtabpage.enabled", false],

  // Background thumbnails in particular cause grief, and disabling
  // thumbnails in general cannot hurt
  ["browser.pagethumbnails.capturing_disabled", true],

  // Disable geolocation ping(#1)
  ["browser.region.network.url", ""],

  // Disable safebrowsing components.
  //
  // These should also be set in the profile prior to starting Firefox,
  // as it is picked up at runtime.
  ["browser.safebrowsing.blockedURIs.enabled", false],
  ["browser.safebrowsing.downloads.enabled", false],
  ["browser.safebrowsing.malware.enabled", false],
  ["browser.safebrowsing.phishing.enabled", false],

  // Disable updates to search engines.
  //
  // Should be set in profile.
  ["browser.search.update", false],

  // Do not restore the last open set of tabs if the browser has crashed
  ["browser.sessionstore.resume_from_crash", false],

  // Don't check for the default web browser during startup.
  //
  // These should also be set in the profile prior to starting Firefox,
  // as it is picked up at runtime.
  ["browser.shell.checkDefaultBrowser", false],

  // Disable session restore infobar
  ["browser.startup.couldRestoreSession.count", -1],

  // Do not redirect user when a milstone upgrade of Firefox is detected
  ["browser.startup.homepage_override.mstone", "ignore"],

  // Unload the previously selected tab immediately
  ["browser.tabs.remote.unloadDelayMs", 0],

  // Don't unload tabs when available memory is running low
  ["browser.tabs.unloadOnLowMemory", false],

  // Do not warn when closing all open tabs
  ["browser.tabs.warnOnClose", false],

  // Do not warn when closing all other open tabs
  ["browser.tabs.warnOnCloseOtherTabs", false],

  // Do not warn when multiple tabs will be opened
  ["browser.tabs.warnOnOpen", false],

  // Don't show the Bookmarks Toolbar on any tab (the above pref that
  // disables the New Tab Page ends up showing the toolbar on about:blank).
  ["browser.toolbars.bookmarks.visibility", "never"],

  // Make sure Topsites doesn't hit the network to retrieve tiles from Contile.
  ["browser.topsites.contile.enabled", false],

  // Disable first run splash page on Windows 10
  ["browser.usedOnWindows10.introURL", ""],

  // Turn off Merino suggestions in the location bar so as not to trigger
  // network connections.
  ["browser.urlbar.merino.endpointURL", ""],

  // Turn off search suggestions in the location bar so as not to trigger
  // network connections.
  ["browser.urlbar.suggest.searches", false],

  // Do not warn on quitting Firefox
  ["browser.warnOnQuit", false],

  // Do not show datareporting policy notifications which can
  // interfere with tests
  [
    "datareporting.healthreport.documentServerURI",
    "http://%(server)s/dummy/healthreport/",
  ],
  ["datareporting.healthreport.logging.consoleEnabled", false],
  ["datareporting.healthreport.service.enabled", false],
  ["datareporting.healthreport.service.firstRun", false],
  ["datareporting.healthreport.uploadEnabled", false],
  ["datareporting.policy.dataSubmissionEnabled", false],
  ["datareporting.policy.dataSubmissionPolicyAccepted", false],
  ["datareporting.policy.dataSubmissionPolicyBypassNotification", true],

  // Disable popup-blocker
  ["dom.disable_open_during_load", false],

  // Enabling the support for File object creation in the content process
  ["dom.file.createInChild", true],

  // Disable delayed user input event handling
  ["dom.input_events.security.minNumTicks", 0],
  ["dom.input_events.security.minTimeElapsedInMS", 0],

  // Disable the ProcessHangMonitor
  ["dom.ipc.reportProcessHangs", false],

  // Disable slow script dialogues
  ["dom.max_chrome_script_run_time", 0],
  ["dom.max_script_run_time", 0],

  // Disable location change rate limitation
  ["dom.navigation.locationChangeRateLimit.count", 0],

  // DOM Push
  ["dom.push.connection.enabled", false],

  // Screen Orientation API
  ["dom.screenorientation.allow-lock", true],

  // Disable dialog abuse if alerts are triggered too quickly.
  ["dom.successive_dialog_time_limit", 0],

  // Only load extensions from the application and user profile
  // AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION
  //
  // Should be set in profile.
  ["extensions.autoDisableScopes", 0],
  ["extensions.enabledScopes", 5],

  // Disable metadata caching for installed add-ons by default
  ["extensions.getAddons.cache.enabled", false],

  // Disable installing any distribution extensions or add-ons.
  // Should be set in profile.
  ["extensions.installDistroAddons", false],

  // Turn off extension updates so they do not bother tests
  ["extensions.update.enabled", false],
  ["extensions.update.notifyUser", false],

  // Make sure opening about:addons will not hit the network
  ["extensions.getAddons.discovery.api_url", "data:, "],

  // Redirect various extension update URLs
  [
    "extensions.blocklist.detailsURL",
    "http://%(server)s/extensions-dummy/blocklistDetailsURL",
  ],
  [
    "extensions.blocklist.itemURL",
    "http://%(server)s/extensions-dummy/blocklistItemURL",
  ],
  ["extensions.hotfix.url", "http://%(server)s/extensions-dummy/hotfixURL"],
  ["extensions.systemAddon.update.enabled", false],
  [
    "extensions.update.background.url",
    "http://%(server)s/extensions-dummy/updateBackgroundURL",
  ],
  ["extensions.update.url", "http://%(server)s/extensions-dummy/updateURL"],

  // Make sure opening about: addons won't hit the network
  ["extensions.getAddons.discovery.api_url", "data:, "],
  [
    "extensions.getAddons.get.url",
    "http://%(server)s/extensions-dummy/repositoryGetURL",
  ],
  [
    "extensions.getAddons.search.browseURL",
    "http://%(server)s/extensions-dummy/repositoryBrowseURL",
  ],

  // Allow the application to have focus even it runs in the background
  ["focusmanager.testmode", true],

  // Disable useragent updates
  ["general.useragent.updates.enabled", false],

  // Disable geolocation ping(#2)
  ["geo.provider.network.url", ""],

  // Always use network provider for geolocation tests so we bypass the
  // macOS dialog raised by the corelocation provider
  ["geo.provider.testing", true],

  // Do not scan Wifi
  ["geo.wifi.scan", false],

  // Disable Firefox accounts ping
  ["identity.fxaccounts.auth.uri", "https://{server}/dummy/fxa"],

  // Disable connectivity service pings
  ["network.connectivity-service.enabled", false],

  // Do not prompt with long usernames or passwords in URLs
  ["network.http.phishy-userpass-length", 255],

  // Do not prompt for temporary redirects
  ["network.http.prompt-temp-redirect", false],

  // Do not automatically switch between offline and online
  ["network.manage-offline-status", false],

  // Make sure SNTP requests do not hit the network
  ["network.sntp.pools", "%(server)s"],

  // Privacy and Tracking Protection
  ["privacy.trackingprotection.enabled", false],

  // Used to check if recommended preferences are applied
  ["remote.prefs.recommended.applied", true],

  // Don't do network connections for mitm priming
  ["security.certerrors.mitm.priming.enabled", false],

  // Local documents have access to all other local documents,
  // including directory listings
  ["security.fileuri.strict_origin_policy", false],

  // Tests do not wait for the notification button security delay
  ["security.notification_enable_delay", 0],

  // Do not download intermediate certificates
  ["security.remote_settings.intermediates.enabled", false],

  // Ensure remote settings do not hit the network
  ["services.settings.server", "data:,#remote-settings-dummy/v1"],

  // Do not automatically fill sign-in forms with known usernames and
  // passwords
  ["signon.autofillForms", false],

  // Disable password capture, so that tests that include forms are not
  // influenced by the presence of the persistent doorhanger notification
  ["signon.rememberSignons", false],

  // Disable first-run welcome page
  ["startup.homepage_welcome_url", "about:blank"],
  ["startup.homepage_welcome_url.additional", ""],

  // Prevent starting into safe mode after application crashes
  ["toolkit.startup.max_resumed_crashes", -1],

  // Disable all telemetry pings
  ["toolkit.telemetry.server", "https://%(server)s/telemetry-dummy/"],

  // Disable window occlusion on Windows, which can prevent webdriver commands
  // such as WebDriver:FindElements from working properly (Bug 1802473).
  ["widget.windows.window_occlusion_tracking.enabled", false],
]);

export const RecommendedPreferences = {
  alteredPrefs: new Set(),

  isInitialized: false,

  /**
   * Apply the provided map of preferences.
   *
   * Note, that they will be automatically reset on application shutdown.
   *
   * @param {Map<string, object>=} preferences
   *     Map of preference name to preference value.
   */
  applyPreferences(preferences = new Map()) {
    if (!lazy.useRecommendedPrefs) {
      // If remote.prefs.recommended is set to false, do not set any preference
      // here. Needed for our Firefox CI.
      return;
    }

    // Only apply common recommended preferences on first call to
    // applyPreferences.
    if (!this.isInitialized) {
      // Merge common preferences and optionally provided preferences in a
      // single map. Hereby the extra preferences have higher priority.
      preferences = new Map([...COMMON_PREFERENCES, ...preferences]);

      Services.obs.addObserver(this, "quit-application");
      this.isInitialized = true;
    }

    for (const [k, v] of preferences) {
      if (!Services.prefs.prefHasUserValue(k)) {
        lazy.logger.debug(`Setting recommended pref ${k} to ${v}`);

        switch (typeof v) {
          case "string":
            Services.prefs.setStringPref(k, v);
            break;
          case "boolean":
            Services.prefs.setBoolPref(k, v);
            break;
          case "number":
            Services.prefs.setIntPref(k, v);
            break;
          default:
            throw new TypeError(`Invalid preference type: ${typeof v}`);
        }

        // Keep track all the altered preferences to restore them on
        // quit-application.
        this.alteredPrefs.add(k);
      }
    }
  },

  observe(subject, topic) {
    if (topic === "quit-application") {
      Services.obs.removeObserver(this, "quit-application");
      this.restoreAllPreferences();
    }
  },

  /**
   * Restore all the altered preferences.
   */
  restoreAllPreferences() {
    this.restorePreferences(this.alteredPrefs);
    this.isInitialized = false;
  },

  /**
   * Restore provided preferences.
   *
   * @param {Map} preferences
   *     Map of preferences that should be restored.
   */
  restorePreferences(preferences) {
    for (const k of preferences.keys()) {
      lazy.logger.debug(`Resetting recommended pref ${k}`);
      Services.prefs.clearUserPref(k);
      this.alteredPrefs.delete(k);
    }
  },
};
PK
!<}�����0chrome/remote/content/shared/RemoteError.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Base class for all remote protocol errors.
 */
export class RemoteError extends Error {
  get isRemoteError() {
    return true;
  }

  /**
   * Convert to a serializable object. Should be implemented by subclasses.
   */
  toJSON() {
    throw new Error("Not implemented");
  }
}
PK
!<~�C��*chrome/remote/content/shared/Stack.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * An object that contains details of a stack frame.
 *
 * @typedef {object} StackFrame
 * @see nsIStackFrame
 *
 * @property {string=} asyncCause
 *     Type of asynchronous call by which this frame was invoked.
 * @property {number} columnNumber
 *     The column number for this stack frame.
 * @property {string} filename
 *     The source URL for this stack frame.
 * @property {string} function
 *     SpiderMonkey’s inferred name for this stack frame’s function, or null.
 * @property {number} lineNumber
 *     The line number for this stack frame (starts with 1).
 * @property {number} sourceId
 *     The process-unique internal integer ID of this source.
 */

/**
 * Return a list of stack frames from the given stack.
 *
 * Convert stack objects to the JSON attributes expected by consumers.
 *
 * @param {object} stack
 *     The native stack object to process.
 *
 * @returns {Array<StackFrame>=}
 */
export function getFramesFromStack(stack) {
  if (!stack || (Cu && Cu.isDeadWrapper(stack))) {
    // If the global from which this error came from has been nuked,
    // stack is going to be a dead wrapper.
    return null;
  }

  const frames = [];
  while (stack) {
    frames.push({
      asyncCause: stack.asyncCause,
      columnNumber: stack.column,
      filename: stack.source,
      functionName: stack.functionDisplayName || "",
      lineNumber: stack.line,
      sourceId: stack.sourceId,
    });

    stack = stack.parent || stack.asyncParent;
  }

  return frames;
}

/**
 * Check if a frame is from chrome scope.
 *
 * @param {object} frame
 *     The frame to check
 *
 * @returns {boolean}
 *     True, if frame is from chrome scope
 */
export function isChromeFrame(frame) {
  return (
    frame.filename.startsWith("chrome://") ||
    frame.filename.startsWith("resource://")
  );
}
PK
!<���Q'Q')chrome/remote/content/shared/Sync.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
});

const { TYPE_ONE_SHOT, TYPE_REPEATING_SLACK } = Ci.nsITimer;

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.REMOTE_AGENT)
);

/**
 * Throttle until the `window` has performed an animation frame.
 *
 * @param {ChromeWindow} win
 *     Window to request the animation frame from.
 *
 * @returns {Promise}
 */
export function AnimationFramePromise(win) {
  const animationFramePromise = new Promise(resolve => {
    win.requestAnimationFrame(resolve);
  });

  // Abort if the underlying window gets closed
  const windowClosedPromise = new PollPromise(resolve => {
    if (win.closed) {
      resolve();
    }
  });

  return Promise.race([animationFramePromise, windowClosedPromise]);
}

/**
 * Create a helper object to defer a promise.
 *
 * @returns {object}
 *     An object that returns the following properties:
 *       - fulfilled Flag that indicates that the promise got resolved
 *       - pending Flag that indicates a not yet fulfilled/rejected promise
 *       - promise The actual promise
 *       - reject Callback to reject the promise
 *       - rejected Flag that indicates that the promise got rejected
 *       - resolve Callback to resolve the promise
 */
export function Deferred() {
  const deferred = {};

  deferred.promise = new Promise((resolve, reject) => {
    deferred.fulfilled = false;
    deferred.pending = true;
    deferred.rejected = false;

    deferred.resolve = (...args) => {
      deferred.fulfilled = true;
      deferred.pending = false;
      resolve(...args);
    };

    deferred.reject = (...args) => {
      deferred.pending = false;
      deferred.rejected = true;
      reject(...args);
    };
  });

  return deferred;
}

/**
 * Wait for an event to be fired on a specified element.
 *
 * The returned promise is guaranteed to not resolve before the
 * next event tick after the event listener is called, so that all
 * other event listeners for the element are executed before the
 * handler is executed.  For example:
 *
 *     const promise = new EventPromise(element, "myEvent");
 *     // same event tick here
 *     await promise;
 *     // next event tick here
 *
 * @param {Element} subject
 *     The element that should receive the event.
 * @param {string} eventName
 *     Case-sensitive string representing the event name to listen for.
 * @param {object=} options
 * @param {boolean=} options.capture
 *     Indicates the event will be despatched to this subject,
 *     before it bubbles down to any EventTarget beneath it in the
 *     DOM tree. Defaults to false.
 * @param {Function=} options.checkFn
 *     Called with the Event object as argument, should return true if the
 *     event is the expected one, or false if it should be ignored and
 *     listening should continue. If not specified, the first event with
 *     the specified name resolves the returned promise. Defaults to null.
 * @param {number=} options.timeout
 *     Timeout duration in milliseconds, if provided.
 *     If specified, then the returned promise will be rejected with
 *     TimeoutError, if not already resolved, after this duration has elapsed.
 *     If not specified, then no timeout is used. Defaults to null.
 * @param {boolean=} options.mozSystemGroup
 *     Determines whether to add listener to the system group. Defaults to
 *     false.
 * @param {boolean=} options.wantUntrusted
 *     Receive synthetic events despatched by web content. Defaults to false.
 *
 * @returns {Promise<Event>}
 *     Either fulfilled with the first described event, satisfying
 *     options.checkFn if specified, or rejected with TimeoutError after
 *     options.timeout milliseconds if specified.
 *
 * @throws {TypeError}
 * @throws {RangeError}
 */
export function EventPromise(subject, eventName, options = {}) {
  const {
    capture = false,
    checkFn = null,
    timeout = null,
    mozSystemGroup = false,
    wantUntrusted = false,
  } = options;
  if (
    !subject ||
    !("addEventListener" in subject) ||
    typeof eventName != "string" ||
    typeof capture != "boolean" ||
    (checkFn && typeof checkFn != "function") ||
    (timeout !== null && typeof timeout != "number") ||
    typeof mozSystemGroup != "boolean" ||
    typeof wantUntrusted != "boolean"
  ) {
    throw new TypeError();
  }
  if (timeout < 0) {
    throw new RangeError();
  }

  return new Promise((resolve, reject) => {
    let timer;

    function cleanUp() {
      subject.removeEventListener(eventName, listener, capture);
      timer?.cancel();
    }

    function listener(event) {
      lazy.logger.trace(`Received DOM event ${event.type} for ${event.target}`);
      try {
        if (checkFn && !checkFn(event)) {
          return;
        }
      } catch (e) {
        // Treat an exception in the callback as a falsy value
        lazy.logger.warn(`Event check failed: ${e.message}`);
      }

      cleanUp();
      executeSoon(() => resolve(event));
    }

    subject.addEventListener(eventName, listener, {
      capture,
      mozSystemGroup,
      wantUntrusted,
    });

    if (timeout !== null) {
      timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
      timer.init(
        () => {
          cleanUp();
          reject(
            new lazy.error.TimeoutError(
              `EventPromise timed out after ${timeout} ms`
            )
          );
        },
        timeout,
        TYPE_ONE_SHOT
      );
    }
  });
}

/**
 * Wait for the next tick in the event loop to execute a callback.
 *
 * @param {Function} fn
 *     Function to be executed.
 */
export function executeSoon(fn) {
  if (typeof fn != "function") {
    throw new TypeError();
  }

  Services.tm.dispatchToMainThread(fn);
}

/**
 * Runs a Promise-like function off the main thread until it is resolved
 * through ``resolve`` or ``rejected`` callbacks.  The function is
 * guaranteed to be run at least once, irregardless of the timeout.
 *
 * The ``func`` is evaluated every ``interval`` for as long as its
 * runtime duration does not exceed ``interval``.  Evaluations occur
 * sequentially, meaning that evaluations of ``func`` are queued if
 * the runtime evaluation duration of ``func`` is greater than ``interval``.
 *
 * ``func`` is given two arguments, ``resolve`` and ``reject``,
 * of which one must be called for the evaluation to complete.
 * Calling ``resolve`` with an argument indicates that the expected
 * wait condition was met and will return the passed value to the
 * caller.  Conversely, calling ``reject`` will evaluate ``func``
 * again until the ``timeout`` duration has elapsed or ``func`` throws.
 * The passed value to ``reject`` will also be returned to the caller
 * once the wait has expired.
 *
 * Usage::
 *
 *     let els = new PollPromise((resolve, reject) => {
 *       let res = document.querySelectorAll("p");
 *       if (res.length > 0) {
 *         resolve(Array.from(res));
 *       } else {
 *         reject([]);
 *       }
 *     }, {timeout: 1000});
 *
 * @param {Condition} func
 *     Function to run off the main thread.
 * @param {object=} options
 * @param {string=} options.errorMessage
 *     Message to use to send a warning if ``timeout`` is over.
 *     Defaults to `PollPromise timed out`.
 * @param {number=} options.timeout
 *     Desired timeout if wanted.  If 0 or less than the runtime evaluation
 *     time of ``func``, ``func`` is guaranteed to run at least once.
 *     Defaults to using no timeout.
 * @param {number=} options.interval
 *     Duration between each poll of ``func`` in milliseconds.
 *     Defaults to 10 milliseconds.
 *
 * @returns {Promise.<*>}
 *     Yields the value passed to ``func``'s
 *     ``resolve`` or ``reject`` callbacks.
 *
 * @throws {*}
 *     If ``func`` throws, its error is propagated.
 * @throws {TypeError}
 *     If `timeout` or `interval`` are not numbers.
 * @throws {RangeError}
 *     If `timeout` or `interval` are not unsigned integers.
 */
export function PollPromise(func, options = {}) {
  const {
    errorMessage = "PollPromise timed out",
    interval = 10,
    timeout = null,
  } = options;
  const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  let didTimeOut = false;

  if (typeof func != "function") {
    throw new TypeError();
  }
  if (timeout != null && typeof timeout != "number") {
    throw new TypeError();
  }
  if (typeof interval != "number") {
    throw new TypeError();
  }
  if (
    (timeout && (!Number.isInteger(timeout) || timeout < 0)) ||
    !Number.isInteger(interval) ||
    interval < 0
  ) {
    throw new RangeError();
  }

  return new Promise((resolve, reject) => {
    let start, end;

    if (Number.isInteger(timeout)) {
      start = new Date().getTime();
      end = start + timeout;
    }

    let evalFn = () => {
      new Promise(func)
        .then(resolve, rejected => {
          if (typeof rejected != "undefined") {
            throw rejected;
          }

          // return if there is a timeout and set to 0,
          // allowing |func| to be evaluated at least once
          if (
            typeof end != "undefined" &&
            (start == end || new Date().getTime() >= end)
          ) {
            didTimeOut = true;
            resolve(rejected);
          }
        })
        .catch(reject);
    };

    // the repeating slack timer waits |interval|
    // before invoking |evalFn|
    evalFn();

    timer.init(evalFn, interval, TYPE_REPEATING_SLACK);
  }).then(
    res => {
      if (didTimeOut) {
        lazy.logger.warn(`${errorMessage} after ${timeout} ms`);
      }
      timer.cancel();
      return res;
    },
    err => {
      timer.cancel();
      throw err;
    }
  );
}
PK
!<W�� 8 8/chrome/remote/content/shared/TabManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
  BrowsingContextListener:
    "chrome://remote/content/shared/listeners/BrowsingContextListener.sys.mjs",
  EventPromise: "chrome://remote/content/shared/Sync.sys.mjs",
  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
  MobileTabBrowser: "chrome://remote/content/shared/MobileTabBrowser.sys.mjs",
  UserContextManager:
    "chrome://remote/content/shared/UserContextManager.sys.mjs",
});

class TabManagerClass {
  #browserUniqueIds;
  #contextListener;
  #navigableIds;

  constructor() {
    // Maps browser's permanentKey to uuid: WeakMap.<Object, string>
    this.#browserUniqueIds = new WeakMap();

    // Maps browsing contexts to uuid: WeakMap.<BrowsingContext, string>.
    // It's required as a fallback, since in the case when a context was discarded
    // embedderElement is gone, and we cannot retrieve
    // the context id from this.#browserUniqueIds.
    this.#navigableIds = new WeakMap();

    this.#contextListener = new lazy.BrowsingContextListener();
    this.#contextListener.on("attached", this.#onContextAttached);
    this.#contextListener.startListening();

    this.browsers.forEach(browser => {
      if (this.isValidCanonicalBrowsingContext(browser.browsingContext)) {
        this.#navigableIds.set(
          browser.browsingContext,
          this.getIdForBrowsingContext(browser.browsingContext)
        );
      }
    });
  }

  /**
   * Retrieve all the browser elements from tabs as contained in open windows.
   *
   * @returns {Array<XULBrowser>}
   *     All the found <xul:browser>s. Will return an empty array if
   *     no windows and tabs can be found.
   */
  get browsers() {
    const browsers = [];

    for (const win of this.windows) {
      for (const tab of this.getTabsForWindow(win)) {
        const contentBrowser = this.getBrowserForTab(tab);
        if (contentBrowser !== null) {
          browsers.push(contentBrowser);
        }
      }
    }

    return browsers;
  }

  /**
   * Retrieve all the browser tabs in open windows.
   *
   * @returns {Array<Tab>}
   *     All the open browser tabs. Will return an empty list if tab browser
   *     is not available or tabs are undefined.
   */
  get tabs() {
    const tabs = [];

    for (const win of this.windows) {
      tabs.push(...this.getTabsForWindow(win));
    }

    return tabs;
  }

  /**
   * Retrieve all the open windows.
   *
   * @returns {Array<Window>}
   *     All the open windows. Will return an empty list if no window is open.
   */
  get windows() {
    const windows = [];

    for (const win of Services.wm.getEnumerator(null)) {
      if (win.closed) {
        continue;
      }
      windows.push(win);
    }

    return windows;
  }

  /**
   * Array of unique browser ids (UUIDs) for all content browsers of all
   * windows.
   *
   * TODO: Similarly to getBrowserById, we should improve the performance of
   * this getter in Bug 1750065.
   *
   * @returns {Array<string>}
   *     Array of UUIDs for all content browsers.
   */
  get allBrowserUniqueIds() {
    const browserIds = [];

    for (const win of this.windows) {
      // Only return handles for browser windows
      for (const tab of this.getTabsForWindow(win)) {
        const contentBrowser = this.getBrowserForTab(tab);
        const winId = this.getIdForBrowser(contentBrowser);
        if (winId !== null) {
          browserIds.push(winId);
        }
      }
    }

    return browserIds;
  }

  /**
   * Get the <code>&lt;xul:browser&gt;</code> for the specified tab.
   *
   * @param {Tab} tab
   *     The tab whose browser needs to be returned.
   *
   * @returns {XULBrowser}
   *     The linked browser for the tab or null if no browser can be found.
   */
  getBrowserForTab(tab) {
    if (tab && "linkedBrowser" in tab) {
      return tab.linkedBrowser;
    }

    return null;
  }

  /**
   * Return the tab browser for the specified chrome window.
   *
   * @param {ChromeWindow} win
   *     Window whose <code>tabbrowser</code> needs to be accessed.
   *
   * @returns {Tab}
   *     Tab browser or null if it's not a browser window.
   */
  getTabBrowser(win) {
    if (lazy.AppInfo.isAndroid) {
      return new lazy.MobileTabBrowser(win);
    } else if (lazy.AppInfo.isFirefox) {
      return win.gBrowser;
    }

    return null;
  }

  /**
   * Create a new tab.
   *
   * @param {object} options
   * @param {boolean=} options.focus
   *     Set to true if the new tab should be focused (selected). Defaults to
   *     false. `false` value is not properly supported on Android, additional
   *     focus of previously selected tab is required after initial navigation.
   * @param {Tab=} options.referenceTab
   *     The reference tab after which the new tab will be added. If no
   *     reference tab is provided, the new tab will be added after all the
   *     other tabs.
   * @param {string=} options.userContextId
   *     A user context id from UserContextManager.
   * @param {window=} options.window
   *     The window where the new tab will open. Defaults to Services.wm.getMostRecentWindow
   *     if no window is provided. Will be ignored if referenceTab is provided.
   */
  async addTab(options = {}) {
    let {
      focus = false,
      referenceTab = null,
      userContextId = null,
      window = Services.wm.getMostRecentWindow(null),
    } = options;

    let index;
    if (referenceTab != null) {
      // If a reference tab was specified, the window should be the window
      // owning the reference tab.
      window = this.getWindowForTab(referenceTab);
    }

    if (referenceTab != null) {
      index = this.getTabsForWindow(window).indexOf(referenceTab) + 1;
    }

    const tabBrowser = this.getTabBrowser(window);

    const tab = await tabBrowser.addTab("about:blank", {
      index,
      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
      userContextId: lazy.UserContextManager.getInternalIdById(userContextId),
    });

    if (focus) {
      await this.selectTab(tab);
    }

    return tab;
  }

  /**
   * Retrieve the browser element corresponding to the provided unique id,
   * previously generated via getIdForBrowser.
   *
   * TODO: To avoid creating strong references on browser elements and
   * potentially leaking those elements, this method loops over all windows and
   * all tabs. It should be replaced by a faster implementation in Bug 1750065.
   *
   * @param {string} id
   *     A browser unique id created by getIdForBrowser.
   * @returns {XULBrowser}
   *     The <xul:browser> corresponding to the provided id. Will return null if
   *     no matching browser element is found.
   */
  getBrowserById(id) {
    for (const win of this.windows) {
      for (const tab of this.getTabsForWindow(win)) {
        const contentBrowser = this.getBrowserForTab(tab);
        if (this.getIdForBrowser(contentBrowser) == id) {
          return contentBrowser;
        }
      }
    }
    return null;
  }

  /**
   * Retrieve the browsing context corresponding to the provided unique id.
   *
   * @param {string} id
   *     A browsing context unique id (created by getIdForBrowsingContext).
   * @returns {BrowsingContext=}
   *     The browsing context found for this id, null if none was found.
   */
  getBrowsingContextById(id) {
    const browser = this.getBrowserById(id);
    if (browser) {
      return browser.browsingContext;
    }

    return BrowsingContext.get(id);
  }

  /**
   * Retrieve the unique id for the given xul browser element. The id is a
   * dynamically generated uuid associated with the permanentKey property of the
   * given browser element. This method is preferable over getIdForBrowsingContext
   * in case of working with browser element of a tab, since we can not guarantee
   * that browsing context is attached to it.
   *
   * @param {XULBrowser} browserElement
   *     The <xul:browser> for which we want to retrieve the id.
   * @returns {string} The unique id for this browser.
   */
  getIdForBrowser(browserElement) {
    if (browserElement === null) {
      return null;
    }

    const key = browserElement.permanentKey;
    if (key === undefined) {
      return null;
    }

    if (!this.#browserUniqueIds.has(key)) {
      this.#browserUniqueIds.set(key, lazy.generateUUID());
    }
    return this.#browserUniqueIds.get(key);
  }

  /**
   * Retrieve the id of a Browsing Context.
   *
   * For a top-level browsing context a custom unique id will be returned.
   *
   * @param {BrowsingContext=} browsingContext
   *     The browsing context to get the id from.
   *
   * @returns {string}
   *     The id of the browsing context.
   */
  getIdForBrowsingContext(browsingContext) {
    if (!browsingContext) {
      return null;
    }

    if (!browsingContext.parent) {
      // Top-level browsing contexts have their own custom unique id.
      // If a context was discarded, embedderElement is already gone,
      // so use navigable id instead.
      return browsingContext.embedderElement
        ? this.getIdForBrowser(browsingContext.embedderElement)
        : this.#navigableIds.get(browsingContext);
    }

    return browsingContext.id.toString();
  }

  /**
   * Get the navigable for the given browsing context.
   *
   * Because Gecko doesn't support the Navigable concept in content
   * scope the content browser could be used to uniquely identify
   * top-level browsing contexts.
   *
   * @param {BrowsingContext} browsingContext
   *
   * @returns {BrowsingContext|XULBrowser} The navigable
   *
   * @throws {TypeError}
   *     If `browsingContext` is not a CanonicalBrowsingContext instance.
   */
  getNavigableForBrowsingContext(browsingContext) {
    if (!this.isValidCanonicalBrowsingContext(browsingContext)) {
      throw new TypeError(
        `Expected browsingContext to be a CanonicalBrowsingContext, got ${browsingContext}`
      );
    }

    if (browsingContext.isContent && browsingContext.parent === null) {
      return browsingContext.embedderElement;
    }

    return browsingContext;
  }

  getTabCount() {
    let count = 0;
    for (const win of this.windows) {
      // For browser windows count the tabs. Otherwise take the window itself.
      const tabsLength = this.getTabsForWindow(win).length;
      count += tabsLength ? tabsLength : 1;
    }
    return count;
  }

  /**
   * Retrieve the tab owning a Browsing Context.
   *
   * @param {BrowsingContext=} browsingContext
   *     The browsing context to get the tab from.
   *
   * @returns {Tab|null}
   *     The tab owning the Browsing Context.
   */
  getTabForBrowsingContext(browsingContext) {
    const browser = browsingContext?.top.embedderElement;
    if (!browser) {
      return null;
    }

    const tabBrowser = this.getTabBrowser(browser.ownerGlobal);
    return tabBrowser.getTabForBrowser(browser);
  }

  /**
   * Retrieve the list of tabs for a given window.
   *
   * @param {ChromeWindow} win
   *     Window whose <code>tabs</code> need to be returned.
   *
   * @returns {Array<Tab>}
   *     The list of tabs. Will return an empty list if tab browser is not available
   *     or tabs are undefined.
   */
  getTabsForWindow(win) {
    const tabBrowser = this.getTabBrowser(win);
    // For web-platform reftests a faked tabbrowser is used,
    // which does not actually have tabs.
    if (tabBrowser && tabBrowser.tabs) {
      return tabBrowser.tabs;
    }
    return [];
  }

  getWindowForTab(tab) {
    // `.linkedBrowser.ownerGlobal` works both with Firefox Desktop and Mobile.
    // Other accessors (eg `.ownerGlobal` or `.browser.ownerGlobal`) fail on one
    // of the platforms.
    return tab.linkedBrowser.ownerGlobal;
  }

  /**
   * Check if the given argument is a valid canonical browsing context and was not
   * discarded.
   *
   * @param {BrowsingContext} browsingContext
   *     The browsing context to check.
   *
   * @returns {boolean}
   *     True if the browsing context is valid, false otherwise.
   */
  isValidCanonicalBrowsingContext(browsingContext) {
    return (
      CanonicalBrowsingContext.isInstance(browsingContext) &&
      !browsingContext.isDiscarded
    );
  }

  /**
   * Remove the given tab.
   *
   * @param {Tab} tab
   *     Tab to remove.
   * @param {object=} options
   * @param {boolean=} options.skipPermitUnload
   *     Flag to indicate if a potential beforeunload prompt should be skipped
   *     when closing the tab. Defaults to false.
   */
  async removeTab(tab, options = {}) {
    const { skipPermitUnload = false } = options;

    if (!tab) {
      return;
    }

    const ownerWindow = this.getWindowForTab(tab);
    const tabBrowser = this.getTabBrowser(ownerWindow);
    await tabBrowser.removeTab(tab, {
      skipPermitUnload,
    });
  }

  /**
   * Select the given tab.
   *
   * @param {Tab} tab
   *     Tab to select.
   *
   * @returns {Promise}
   *     Promise that resolves when the given tab has been selected.
   */
  async selectTab(tab) {
    if (!tab) {
      return Promise.resolve();
    }

    const ownerWindow = this.getWindowForTab(tab);
    const tabBrowser = this.getTabBrowser(ownerWindow);

    if (tab === tabBrowser.selectedTab) {
      return Promise.resolve();
    }

    const selected = new lazy.EventPromise(ownerWindow, "TabSelect");
    tabBrowser.selectedTab = tab;

    await selected;

    // Sometimes at that point window is not focused.
    if (Services.focus.activeWindow != ownerWindow) {
      const activated = new lazy.EventPromise(ownerWindow, "activate");
      ownerWindow.focus();
      return activated;
    }

    return Promise.resolve();
  }

  supportsTabs() {
    return lazy.AppInfo.isAndroid || lazy.AppInfo.isFirefox;
  }

  #onContextAttached = (eventName, data = {}) => {
    const { browsingContext } = data;
    if (this.isValidCanonicalBrowsingContext(browsingContext)) {
      this.#navigableIds.set(
        browsingContext,
        this.getIdForBrowsingContext(browsingContext)
      );
    }
  };
}

// Expose a shared singleton.
export const TabManager = new TabManagerClass();
PK
!<Wp�0��)chrome/remote/content/shared/UUID.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Creates a unique UUID without enclosing curly brackets
 * Example: '86c832d2-cf1c-4001-b3e0-8628fdd41b29'
 *
 * @returns {string}
 *     The generated UUID as a string.
 */
export function generateUUID() {
  return Services.uuid.generateUUID().toString().slice(1, -1);
}
PK
!<���7chrome/remote/content/shared/UserContextManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ContextualIdentityService:
    "resource://gre/modules/ContextualIdentityService.sys.mjs",

  ContextualIdentityListener:
    "chrome://remote/content/shared/listeners/ContextualIdentityListener.sys.mjs",
  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
});

const DEFAULT_CONTEXT_ID = "default";
const DEFAULT_INTERNAL_ID = 0;

/**
 * A UserContextManager instance keeps track of all public user contexts and
 * maps their internal platform.
 *
 * This class is exported for test purposes. Otherwise the UserContextManager
 * singleton should be used.
 */
export class UserContextManagerClass {
  #contextualIdentityListener;
  #userContextIds;

  constructor() {
    // Map from internal ids (numbers) from the ContextualIdentityService to
    // opaque UUIDs (string).
    this.#userContextIds = new Map();

    // The default user context is always using 0 as internal user context id
    // and should be exposed as "default" instead of a randomly generated id.
    this.#userContextIds.set(DEFAULT_INTERNAL_ID, DEFAULT_CONTEXT_ID);

    // Register other (non-default) public contexts.
    lazy.ContextualIdentityService.getPublicIdentities().forEach(identity =>
      this.#registerIdentity(identity)
    );

    this.#contextualIdentityListener = new lazy.ContextualIdentityListener();
    this.#contextualIdentityListener.on("created", this.#onIdentityCreated);
    this.#contextualIdentityListener.on("deleted", this.#onIdentityDeleted);
    this.#contextualIdentityListener.startListening();
  }

  destroy() {
    this.#contextualIdentityListener.off("created", this.#onIdentityCreated);
    this.#contextualIdentityListener.off("deleted", this.#onIdentityDeleted);
    this.#contextualIdentityListener.destroy();

    this.#userContextIds = null;
  }

  /**
   * Retrieve the user context id corresponding to the default user context.
   *
   * @returns {string}
   *     The default user context id.
   */
  get defaultUserContextId() {
    return DEFAULT_CONTEXT_ID;
  }

  /**
   * Creates a new user context.
   *
   * @param {string} prefix
   *     The prefix to use for the name of the user context.
   *
   * @returns {string}
   *     The user context id of the new user context.
   */
  createContext(prefix = "remote") {
    // Prepare the opaque id and name beforehand.
    const userContextId = lazy.generateUUID();
    const name = `${prefix}-${userContextId}`;

    // Create the user context.
    const identity = lazy.ContextualIdentityService.create(name);
    const internalId = identity.userContextId;

    // An id has been set already by the contextual-identity-created observer.
    // Override it with `userContextId` to match the container name.
    this.#userContextIds.set(internalId, userContextId);

    return userContextId;
  }

  /**
   * Retrieve the user context id corresponding to the provided browsing context.
   *
   * @param {BrowsingContext} browsingContext
   *     The browsing context to get the user context id from.
   *
   * @returns {string}
   *     The corresponding user context id.
   *
   * @throws {TypeError}
   *     If `browsingContext` is not a CanonicalBrowsingContext instance.
   */
  getIdByBrowsingContext(browsingContext) {
    if (!CanonicalBrowsingContext.isInstance(browsingContext)) {
      throw new TypeError(
        `Expected browsingContext to be a CanonicalBrowsingContext, got ${browsingContext}`
      );
    }

    return this.getIdByInternalId(
      browsingContext.originAttributes.userContextId
    );
  }

  /**
   * Retrieve the user context id corresponding to the provided internal id.
   *
   * @param {number} internalId
   *     The internal user context id.
   *
   * @returns {string|null}
   *     The corresponding user context id or null if the user context does not
   *     exist.
   */
  getIdByInternalId(internalId) {
    if (this.#userContextIds.has(internalId)) {
      return this.#userContextIds.get(internalId);
    }
    return null;
  }

  /**
   * Retrieve the internal id corresponding to the provided user
   * context id.
   *
   * @param {string} userContextId
   *     The user context id.
   *
   * @returns {number|null}
   *     The internal user context id or null if the user context does not
   *     exist.
   */
  getInternalIdById(userContextId) {
    for (const [internalId, id] of this.#userContextIds) {
      if (userContextId == id) {
        return internalId;
      }
    }
    return null;
  }

  /**
   * Returns an array of all known user context ids.
   *
   * @returns {Array<string>}
   *     The array of user context ids.
   */
  getUserContextIds() {
    return Array.from(this.#userContextIds.values());
  }

  /**
   * Checks if the provided user context id is known by this UserContextManager.
   *
   * @param {string} userContextId
   *     The id of the user context to check.
   */
  hasUserContextId(userContextId) {
    return this.getUserContextIds().includes(userContextId);
  }

  /**
   * Removes a user context and closes all related container tabs.
   *
   * Note: When closing the related container tabs possible "beforeunload"
   * prompts will be ignored.
   *
   * @param {string} userContextId
   *     The id of the user context to remove.
   * @param {object=} options
   * @param {boolean=} options.closeContextTabs
   *     Pass true if the tabs owned by the user context should also be closed.
   *     Defaults to false.
   */
  removeUserContext(userContextId, options = {}) {
    const { closeContextTabs = false } = options;

    if (!this.hasUserContextId(userContextId)) {
      return;
    }

    const internalId = this.getInternalIdById(userContextId);
    if (closeContextTabs) {
      lazy.ContextualIdentityService.closeContainerTabs(internalId, {
        skipPermitUnload: true,
      });
    }
    lazy.ContextualIdentityService.remove(internalId);
  }

  #onIdentityCreated = (eventName, data) => {
    this.#registerIdentity(data.identity);
  };

  #onIdentityDeleted = (eventName, data) => {
    this.#userContextIds.delete(data.identity.userContextId);
  };

  #registerIdentity(identity) {
    // Note: the id for identities created via UserContextManagerClass.createContext
    // are overridden in createContext.
    this.#userContextIds.set(identity.userContextId, lazy.generateUUID());
  }
}

// Expose a shared singleton.
export const UserContextManager = new UserContextManagerClass();
PK
!<�`Q��8chrome/remote/content/shared/WebSocketConnection.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public

 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  truncate: "chrome://remote/content/shared/Format.sys.mjs",
  WebSocketTransport:
    "chrome://remote/content/server/WebSocketTransport.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "truncateLog",
  "remote.log.truncate",
  false
);

const MAX_LOG_LENGTH = 2500;

export class WebSocketConnection {
  /**
   * @param {WebSocket} webSocket
   *     The WebSocket server connection to wrap.
   * @param {Connection} httpdConnection
   *     Reference to the httpd.js's connection needed for clean-up.
   */
  constructor(webSocket, httpdConnection) {
    this.id = lazy.generateUUID();

    this.httpdConnection = httpdConnection;

    this.transport = new lazy.WebSocketTransport(webSocket);
    this.transport.hooks = this;
    this.transport.ready();

    lazy.logger.debug(`${this.constructor.name} ${this.id} accepted`);
  }

  #log(direction, data) {
    if (lazy.Log.isDebugLevelOrMore) {
      function replacer(key, value) {
        if (typeof value === "string") {
          return lazy.truncate`${value}`;
        }
        return value;
      }

      let payload = JSON.stringify(
        data,
        replacer,
        lazy.Log.verbose ? "\t" : null
      );

      if (lazy.truncateLog && payload.length > MAX_LOG_LENGTH) {
        // Even if we truncate individual values, the resulting message might be
        // huge if we are serializing big objects with many properties or items.
        // Truncate the overall message to avoid issues in logs.
        const truncated = payload.substring(0, MAX_LOG_LENGTH);
        payload = `${truncated} [... truncated after ${MAX_LOG_LENGTH} characters]`;
      }

      lazy.logger.debug(
        `${this.constructor.name} ${this.id} ${direction} ${payload}`
      );
    }
  }

  /**
   * Close the WebSocket connection.
   */
  close() {
    this.transport.close();
  }

  /**
   * Register a new Session to forward the messages to.
   *
   * Needs to be implemented in the sub class.
   */
  registerSession() {
    throw new Error("Not implemented");
  }

  /**
   * Send the JSON-serializable object to the client.
   *
   * @param {object} data
   *     The object to be sent.
   */
  send(data) {
    this.#log("<-", data);
    this.transport.send(data);
  }

  /**
   * Send an error back to the client.
   *
   * Needs to be implemented in the sub class.
   */
  sendError() {
    throw new Error("Not implemented");
  }

  /**
   * Send an event back to the client.
   *
   * Needs to be implemented in the sub class.
   */
  sendEvent() {
    throw new Error("Not implemented");
  }

  /**
   * Send the result of a call to a method back to the client.
   *
   * Needs to be implemented in the sub class.
   */
  sendResult() {
    throw new Error("Not implemented");
  }

  toString() {
    return `[object ${this.constructor.name} ${this.id}]`;
  }

  // Transport hooks

  /**
   * Called by the `transport` when the connection is closed.
   */
  onConnectionClose() {
    lazy.logger.debug(`${this.constructor.name} ${this.id} closed`);
  }

  /**
   * Called when the socket is closed.
   */
  onSocketClose() {
    // In addition to the WebSocket transport, we also have to close the
    // connection used internally within httpd.js. Otherwise the server doesn't
    // shut down correctly, and keeps these Connection instances alive.
    this.httpdConnection.close();
  }

  /**
   * Receive a packet from the WebSocket layer.
   *
   * This packet is sent by a WebSocket client and is meant to execute
   * a particular method.
   *
   * Needs to be implemented in the sub class.
   *
   * @param {object} packet
   *     JSON-serializable object sent by the client.
   */
  async onPacket(packet) {
    this.#log("->", packet);
  }
}
PK
!<�b�G$G$2chrome/remote/content/shared/WindowManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  URILoadingHelper: "resource:///modules/URILoadingHelper.sys.mjs",

  AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  EventPromise: "chrome://remote/content/shared/Sync.sys.mjs",
  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
  TimedPromise: "chrome://remote/content/marionette/sync.sys.mjs",
  UserContextManager:
    "chrome://remote/content/shared/UserContextManager.sys.mjs",
  waitForObserverTopic: "chrome://remote/content/marionette/sync.sys.mjs",
});

/**
 * Provides helpers to interact with Window objects.
 *
 * @class WindowManager
 */
class WindowManager {
  constructor() {
    // Maps ChromeWindow to uuid: WeakMap.<Object, string>
    this._chromeWindowHandles = new WeakMap();
  }

  get chromeWindowHandles() {
    const chromeWindowHandles = [];

    for (const win of this.windows) {
      chromeWindowHandles.push(this.getIdForWindow(win));
    }

    return chromeWindowHandles;
  }

  get windows() {
    return Services.wm.getEnumerator(null);
  }

  /**
   * Find a specific window matching the provided window handle.
   *
   * @param {string} handle
   *     The unique handle of either a chrome window or a content browser, as
   *     returned by :js:func:`#getIdForBrowser` or :js:func:`#getIdForWindow`.
   *
   * @returns {object} A window properties object,
   *     @see :js:func:`GeckoDriver#getWindowProperties`
   */
  findWindowByHandle(handle) {
    for (const win of this.windows) {
      // In case the wanted window is a chrome window, we are done.
      const chromeWindowId = this.getIdForWindow(win);
      if (chromeWindowId == handle) {
        return this.getWindowProperties(win);
      }

      // Otherwise check if the chrome window has a tab browser, and that it
      // contains a tab with the wanted window handle.
      const tabBrowser = lazy.TabManager.getTabBrowser(win);
      if (tabBrowser && tabBrowser.tabs) {
        for (let i = 0; i < tabBrowser.tabs.length; ++i) {
          let contentBrowser = lazy.TabManager.getBrowserForTab(
            tabBrowser.tabs[i]
          );
          let contentWindowId = lazy.TabManager.getIdForBrowser(contentBrowser);

          if (contentWindowId == handle) {
            return this.getWindowProperties(win, { tabIndex: i });
          }
        }
      }
    }

    return null;
  }

  /**
   * A set of properties describing a window and that should allow to uniquely
   * identify it. The described window can either be a Chrome Window or a
   * Content Window.
   *
   * @typedef {object} WindowProperties
   * @property {Window} win - The Chrome Window containing the window.
   *     When describing a Chrome Window, this is the window itself.
   * @property {string} id - The unique id of the containing Chrome Window.
   * @property {boolean} hasTabBrowser - `true` if the Chrome Window has a
   *     tabBrowser.
   * @property {number} tabIndex - Optional, the index of the specific tab
   *     within the window.
   */

  /**
   * Returns a WindowProperties object, that can be used with :js:func:`GeckoDriver#setWindowHandle`.
   *
   * @param {Window} win
   *     The Chrome Window for which we want to create a properties object.
   * @param {object} options
   * @param {number} options.tabIndex
   *     Tab index of a specific Content Window in the specified Chrome Window.
   * @returns {WindowProperties} A window properties object.
   */
  getWindowProperties(win, options = {}) {
    if (!Window.isInstance(win)) {
      throw new TypeError("Invalid argument, expected a Window object");
    }

    return {
      win,
      id: this.getIdForWindow(win),
      hasTabBrowser: !!lazy.TabManager.getTabBrowser(win),
      tabIndex: options.tabIndex,
    };
  }

  /**
   * Retrieves an id for the given chrome window. The id is a dynamically
   * generated uuid associated with the window object.
   *
   * @param {window} win
   *     The window object for which we want to retrieve the id.
   * @returns {string} The unique id for this chrome window.
   */
  getIdForWindow(win) {
    if (!this._chromeWindowHandles.has(win)) {
      this._chromeWindowHandles.set(win, lazy.generateUUID());
    }
    return this._chromeWindowHandles.get(win);
  }

  /**
   * Close the specified window.
   *
   * @param {window} win
   *     The window to close.
   * @returns {Promise}
   *     A promise which is resolved when the current window has been closed.
   */
  async closeWindow(win) {
    const destroyed = lazy.waitForObserverTopic("xul-window-destroyed", {
      checkFn: () => win && win.closed,
    });

    win.close();

    return destroyed;
  }

  /**
   * Focus the specified window.
   *
   * @param {window} win
   *     The window to focus.
   * @returns {Promise}
   *     A promise which is resolved when the window has been focused.
   */
  async focusWindow(win) {
    if (Services.focus.activeWindow != win) {
      let activated = new lazy.EventPromise(win, "activate");
      let focused = new lazy.EventPromise(win, "focus", { capture: true });

      win.focus();

      await Promise.all([activated, focused]);
    }
  }

  /**
   * Open a new browser window.
   *
   * @param {object=} options
   * @param {boolean=} options.focus
   *     If true, the opened window will receive the focus. Defaults to false.
   * @param {boolean=} options.isPrivate
   *     If true, the opened window will be a private window. Defaults to false.
   * @param {ChromeWindow=} options.openerWindow
   *     Use this window as the opener of the new window. Defaults to the
   *     topmost window.
   * @param {string=} options.userContextId
   *     The id of the user context which should own the initial tab of the new
   *     window.
   * @returns {Promise}
   *     A promise resolving to the newly created chrome window.
   */
  async openBrowserWindow(options = {}) {
    let {
      focus = false,
      isPrivate = false,
      openerWindow = null,
      userContextId = null,
    } = options;

    switch (lazy.AppInfo.name) {
      case "Firefox":
        if (openerWindow === null) {
          // If no opener was provided, fallback to the topmost window.
          openerWindow = Services.wm.getMostRecentBrowserWindow();
        }

        if (!openerWindow) {
          throw new lazy.error.UnsupportedOperationError(
            `openWindow() could not find a valid opener window`
          );
        }

        // Open new browser window, and wait until it is fully loaded.
        // Also wait for the window to be focused and activated to prevent a
        // race condition when promptly focusing to the original window again.
        const browser = await new Promise(resolveOnContentBrowserCreated =>
          lazy.URILoadingHelper.openTrustedLinkIn(
            openerWindow,
            "about:blank",
            "window",
            {
              private: isPrivate,
              resolveOnContentBrowserCreated,
              userContextId:
                lazy.UserContextManager.getInternalIdById(userContextId),
            }
          )
        );

        // TODO: Both for WebDriver BiDi and classic, opening a new window
        // should not run the focus steps. When focus is false we should avoid
        // focusing the new window completely. See Bug 1766329

        if (focus) {
          // Focus the currently selected tab.
          browser.focus();
        } else {
          // If the new window shouldn't get focused, set the
          // focus back to the opening window.
          await this.focusWindow(openerWindow);
        }

        return browser.ownerGlobal;

      default:
        throw new lazy.error.UnsupportedOperationError(
          `openWindow() not supported in ${lazy.AppInfo.name}`
        );
    }
  }

  /**
   * Wait until the initial application window has been opened and loaded.
   *
   * @returns {Promise<WindowProxy>}
   *     A promise that resolved to the application window.
   */
  waitForInitialApplicationWindowLoaded() {
    return new lazy.TimedPromise(
      async resolve => {
        // This call includes a fallback to "mail:3pane" as well.
        const win = Services.wm.getMostRecentBrowserWindow();

        const windowLoaded = lazy.waitForObserverTopic(
          "browser-delayed-startup-finished",
          {
            checkFn: subject => (win !== null ? subject == win : true),
          }
        );

        // The current window has already been finished loading.
        if (win && win.document.readyState == "complete") {
          resolve(win);
          return;
        }

        // Wait for the next browser/mail window to open and finished loading.
        const { subject } = await windowLoaded;
        resolve(subject);
      },
      {
        errorMessage: "No applicable application window found",
      }
    );
  }
}

// Expose a shared singleton.
export const windowManager = new WindowManager();
PK
!<Z��m��Mchrome/remote/content/shared/js-window-actors/NavigationListenerActor.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

let registered = false;
export function isNavigationListenerActorRegistered() {
  return registered;
}

/**
 * Register the NavigationListener actor that will keep track of all ongoing
 * navigations.
 */
export function registerNavigationListenerActor() {
  if (registered) {
    return;
  }

  try {
    ChromeUtils.registerWindowActor("NavigationListener", {
      kind: "JSWindowActor",
      parent: {
        esModuleURI:
          "chrome://remote/content/shared/js-window-actors/NavigationListenerParent.sys.mjs",
      },
      child: {
        esModuleURI:
          "chrome://remote/content/shared/js-window-actors/NavigationListenerChild.sys.mjs",
        events: {
          DOMWindowCreated: {},
        },
      },
      allFrames: true,
      messageManagerGroups: ["browsers"],
    });
    registered = true;

    // Ensure the navigation listener is started in existing contexts.
    for (const browser of lazy.TabManager.browsers) {
      if (!browser?.browsingContext) {
        continue;
      }

      for (const context of browser.browsingContext.getAllBrowsingContextsInSubtree()) {
        if (!context.currentWindowGlobal) {
          continue;
        }

        context.currentWindowGlobal
          .getActor("NavigationListener")
          // Note that "createActor" is not explicitly referenced in the child
          // actor, this is only used to trigger the creation of the actor.
          .sendAsyncMessage("createActor");
      }
    }
  } catch (e) {
    if (e.name === "NotSupportedError") {
      lazy.logger.warn(`NavigationListener actor is already registered!`);
    } else {
      throw e;
    }
  }
}

export function unregisterNavigationListenerActor() {
  if (!registered) {
    return;
  }
  ChromeUtils.unregisterWindowActor("NavigationListener");
  registered = false;
}
PK
!<[����Mchrome/remote/content/shared/js-window-actors/NavigationListenerChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  truncate: "chrome://remote/content/shared/Format.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

export class NavigationListenerChild extends JSWindowActorChild {
  #listener;
  #webProgress;

  constructor() {
    super();

    this.#listener = {
      onLocationChange: this.#onLocationChange,
      onStateChange: this.#onStateChange,
      QueryInterface: ChromeUtils.generateQI([
        "nsIWebProgressListener",
        "nsISupportsWeakReference",
      ]),
    };
    this.#webProgress = null;
  }

  actorCreated() {
    this.#webProgress = this.manager.browsingContext.docShell
      .QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIWebProgress);

    this.#webProgress.addProgressListener(
      this.#listener,
      Ci.nsIWebProgress.NOTIFY_LOCATION |
        Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT
    );
  }

  didDestroy() {
    try {
      this.#webProgress.removeProgressListener(this.#listener);
    } catch (e) {
      // Ignore potential errors if the window global was already destroyed.
    }
  }

  // Note: we rely on events and messages to trigger the actor creation, but
  // all the logic is in the actorCreated callback. The handleEvent and
  // receiveMessage methods are only there as placeholders to avoid errors.

  /**
   * See note above
   */
  handleEvent() {}

  /**
   * See note above
   */
  receiveMessage() {}

  /**
   * A browsing context might be replaced before reaching the parent process,
   * instead we serialize enough information to retrieve the navigable in the
   * parent process.
   *
   * If the browsing context is top level, then the browserId can be used to
   * find the browser element and the new browsing context.
   * Otherwise (frames) the browsing context should not be replaced and the
   * browsing context id should be enough to find the browsing context.
   *
   * @param {BrowsingContext} browsingContext
   *     The browsing context for which we want to get details.
   * @returns {object}
   *     An object that returns the following properties:
   *       - browserId: browser id for this browsing context
   *       - browsingContextId: browsing context id
   *       - isTopBrowsingContext: flag that indicates if the browsing context is
   *         top level
   */
  #getBrowsingContextDetails(browsingContext) {
    return {
      browserId: browsingContext.browserId,
      browsingContextId: browsingContext.id,
      isTopBrowsingContext: browsingContext.parent === null,
    };
  }

  #getTargetURI(request) {
    try {
      return request.QueryInterface(Ci.nsIChannel).originalURI;
    } catch (e) {}

    return null;
  }

  #onLocationChange = (progress, request, location, stateFlags) => {
    if (stateFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
      const context = progress.browsingContext;

      const payload = {
        contextDetails: this.#getBrowsingContextDetails(context),
        url: location.spec,
      };

      if (location.hasRef) {
        // If the target URL contains a hash, handle the navigation as a
        // fragment navigation.
        this.#trace(
          context.id,
          lazy.truncate`Location=fragmentNavigated: ${location.spec}`
        );

        this.sendAsyncMessage(
          "NavigationListenerChild:fragmentNavigated",
          payload
        );
        return;
      }

      this.#trace(
        context.id,
        lazy.truncate`Location=sameDocumentChanged: ${location.spec}`
      );

      this.sendAsyncMessage(
        "NavigationListenerChild:sameDocumentChanged",
        payload
      );
    }
  };

  #onStateChange = (progress, request, stateFlags, status) => {
    const context = progress.browsingContext;
    const targetURI = this.#getTargetURI(request);

    const isBindingAborted = status == Cr.NS_BINDING_ABORTED;
    const isStart = !!(stateFlags & Ci.nsIWebProgressListener.STATE_START);
    const isStop = !!(stateFlags & Ci.nsIWebProgressListener.STATE_STOP);

    if (lazy.Log.isTraceLevelOrMore) {
      const isNetwork = !!(
        stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK
      );
      this.#trace(
        context.id,
        `Loading state: flags: ${stateFlags}, status: ${status}, ` +
          ` isStart: ${isStart}, isStop: ${isStop}, isNetwork: ${isNetwork},` +
          ` isBindingAborted: ${isBindingAborted},` +
          lazy.truncate` targetURI: ${targetURI?.spec}`
      );
    }

    try {
      if (isStart) {
        this.sendAsyncMessage("NavigationListenerChild:navigationStarted", {
          contextDetails: this.#getBrowsingContextDetails(context),
          url: targetURI?.spec,
        });

        return;
      }

      if (isStop && !isBindingAborted) {
        // Skip NS_BINDING_ABORTED state changes as this can happen during a
        // browsing context + process change and we should get the real stop state
        // change from the correct process later.
        this.sendAsyncMessage("NavigationListenerChild:navigationStopped", {
          contextDetails: this.#getBrowsingContextDetails(context),
          status,
          url: targetURI?.spec,
        });
      }
    } catch (e) {
      if (e.name === "InvalidStateError") {
        // We'll arrive here if we no longer have our manager, so we can
        // just swallow this error.
        return;
      }
      throw e;
    }
  };

  #trace(contextId, message) {
    lazy.logger.trace(`[${contextId}] ${this.constructor.name} ${message}`);
  }
}
PK
!<N�8JNchrome/remote/content/shared/js-window-actors/NavigationListenerParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  notifyFragmentNavigated:
    "chrome://remote/content/shared/NavigationManager.sys.mjs",
  notifySameDocumentChanged:
    "chrome://remote/content/shared/NavigationManager.sys.mjs",
  notifyNavigationFailed:
    "chrome://remote/content/shared/NavigationManager.sys.mjs",
  notifyNavigationStarted:
    "chrome://remote/content/shared/NavigationManager.sys.mjs",
  notifyNavigationStopped:
    "chrome://remote/content/shared/NavigationManager.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

export class NavigationListenerParent extends JSWindowActorParent {
  async receiveMessage(message) {
    const { data, name } = message;

    try {
      const payload = {
        contextDetails: data.contextDetails,
        url: data.url,
      };

      switch (name) {
        case "NavigationListenerChild:fragmentNavigated": {
          lazy.notifyFragmentNavigated(payload);
          break;
        }
        case "NavigationListenerChild:sameDocumentChanged": {
          lazy.notifySameDocumentChanged(payload);
          break;
        }
        case "NavigationListenerChild:navigationStarted": {
          lazy.notifyNavigationStarted(payload);
          break;
        }
        case "NavigationListenerChild:navigationStopped": {
          const errorName = ChromeUtils.getXPCOMErrorName(data.status);
          if (this.#isContentBlocked(errorName)) {
            payload.errorName = errorName;
            lazy.notifyNavigationFailed(payload);
          } else {
            lazy.notifyNavigationStopped(payload);
          }
          break;
        }
        default:
          throw new Error("Unsupported message:" + name);
      }
    } catch (e) {
      if (e instanceof TypeError) {
        // Avoid error spam from errors due to unavailable browsing contexts.
        lazy.logger.trace(
          `Failed to handle a navigation listener message: ${e.message}`
        );
      } else {
        throw e;
      }
    }
  }

  #isContentBlocked(blockedReason) {
    return [
      // If content is blocked with e.g. CSP meta tag.
      "NS_ERROR_CONTENT_BLOCKED",
      // If a resource load was blocked because of the CSP header.
      "NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION",
      // If a resource load was blocked because of the Cross-Origin-Embedder-Policy header.
      "NS_ERROR_DOM_COEP_FAILED",
      // If a resource load was blocked because of the X-Frame-Options header.
      "NS_ERROR_XFO_VIOLATION",
    ].includes(blockedReason);
  }
}
PK
!<-�i�d	d	Hchrome/remote/content/shared/listeners/BeforeStopRequestListener.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
});

const OBSERVER_TOPIC_BEFORE_STOP_REQUEST = "http-on-before-stop-request";

/**
 * The BeforeStopRequestListener can be used to listen for
 * http-on-before-stop-request notifications emitted right before a network
 * channel is stopped. At this point the response should be completely decoded
 * and the channel decodedBodySize property should have the expected value.
 * This notification needs to be monitored in content processes, because
 * decodedBodySize is always set to 0 in the parent process.
 *
 * Example:
 * ```
 * const listener = new BeforeStopRequestListener();
 * listener.on("beforeStopRequest", onBeforeStopRequest);
 * listener.startListening();
 *
 * const onBeforeStopRequest = (eventName, data = {}) => {
 *   const { channel, decodedBodySize } = data;
 *   ...
 * };
 * ```
 *
 * @fires message
 *    The BeforeStopRequestListener emits "beforeStopRequest" events with the
 *    following object as payload:
 *      - {nsIHttpChannel} channel
 *            The channel for which the observer notification was emitted.
 *      - {number} decodedBodySize
 *            The decoded body size for the channel.
 */
export class BeforeStopRequestListener {
  #listening;

  /**
   * Create a new BeforeStopRequestListener instance.
   */
  constructor() {
    lazy.EventEmitter.decorate(this);

    this.#listening = false;
  }

  destroy() {
    this.stopListening();
  }

  observe(subject, topic) {
    switch (topic) {
      case OBSERVER_TOPIC_BEFORE_STOP_REQUEST: {
        const channel = subject.QueryInterface(Ci.nsIHttpChannel);
        this.emit("beforeStopRequest", {
          channel,
          decodedBodySize: channel.decodedBodySize,
        });
        break;
      }
    }
  }

  startListening() {
    if (this.#listening) {
      return;
    }

    Services.obs.addObserver(this, OBSERVER_TOPIC_BEFORE_STOP_REQUEST);

    this.#listening = true;
  }

  stopListening() {
    if (!this.#listening) {
      return;
    }

    Services.obs.removeObserver(this, OBSERVER_TOPIC_BEFORE_STOP_REQUEST);

    this.#listening = false;
  }
}
PK
!<6�4..Fchrome/remote/content/shared/listeners/BrowsingContextListener.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
});

const OBSERVER_TOPIC_ATTACHED = "browsing-context-attached";
const OBSERVER_TOPIC_DISCARDED = "browsing-context-discarded";

const OBSERVER_TOPIC_SET_EMBEDDER = "browsing-context-did-set-embedder";

/**
 * The BrowsingContextListener can be used to listen for notifications coming
 * from browsing contexts that get attached or discarded.
 *
 * Example:
 * ```
 * const listener = new BrowsingContextListener();
 * listener.on("attached", onAttached);
 * listener.startListening();
 *
 * const onAttached = (eventName, data = {}) => {
 *   const { browsingContext, why } = data;
 *   ...
 * };
 * ```
 *
 * @fires message
 *    The BrowsingContextListener emits "attached" and "discarded" events,
 *    with the following object as payload:
 *      - {BrowsingContext} browsingContext
 *            Browsing context the notification relates to.
 *      - {string} why
 *            Usually "attach" or "discard", but will contain "replace" if the
 *            browsing context gets replaced by a cross-group navigation.
 */
export class BrowsingContextListener {
  #listening;
  #topContextsToAttach;

  /**
   * Create a new BrowsingContextListener instance.
   */
  constructor() {
    lazy.EventEmitter.decorate(this);

    // A map that temporarily holds attached top-level browsing contexts until
    // their embedder element is set, which is required to successfully
    // retrieve a unique id for the content browser by the TabManager.
    this.#topContextsToAttach = new Map();

    this.#listening = false;
  }

  destroy() {
    this.stopListening();
    this.#topContextsToAttach = null;
  }

  observe(subject, topic, data) {
    switch (topic) {
      case OBSERVER_TOPIC_ATTACHED:
        // Delay emitting the event for top-level browsing contexts until
        // the embedder element has been set.
        if (!subject.parent) {
          this.#topContextsToAttach.set(subject, data);
          return;
        }

        this.emit("attached", { browsingContext: subject, why: data });
        break;

      case OBSERVER_TOPIC_DISCARDED:
        // Remove a recently attached top-level browsing context if it's
        // immediately discarded.
        if (this.#topContextsToAttach.has(subject)) {
          this.#topContextsToAttach.delete(subject);
        }

        this.emit("discarded", { browsingContext: subject, why: data });
        break;

      case OBSERVER_TOPIC_SET_EMBEDDER:
        const why = this.#topContextsToAttach.get(subject);
        if (why !== undefined) {
          this.emit("attached", { browsingContext: subject, why });
          this.#topContextsToAttach.delete(subject);
        }
        break;
    }
  }

  startListening() {
    if (this.#listening) {
      return;
    }

    Services.obs.addObserver(this, OBSERVER_TOPIC_ATTACHED);
    Services.obs.addObserver(this, OBSERVER_TOPIC_DISCARDED);
    Services.obs.addObserver(this, OBSERVER_TOPIC_SET_EMBEDDER);

    this.#listening = true;
  }

  stopListening() {
    if (!this.#listening) {
      return;
    }

    Services.obs.removeObserver(this, OBSERVER_TOPIC_ATTACHED);
    Services.obs.removeObserver(this, OBSERVER_TOPIC_DISCARDED);
    Services.obs.removeObserver(this, OBSERVER_TOPIC_SET_EMBEDDER);

    this.#topContextsToAttach.clear();

    this.#listening = false;
  }
}
PK
!<�@�
�
Achrome/remote/content/shared/listeners/ConsoleAPIListener.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "ConsoleAPIStorage", () => {
  return Cc["@mozilla.org/consoleAPI-storage;1"].getService(
    Ci.nsIConsoleAPIStorage
  );
});

/**
 * The ConsoleAPIListener can be used to listen for messages coming from console
 * API usage in a given windowGlobal, eg. console.log, console.error, ...
 *
 * Example:
 * ```
 * const listener = new ConsoleAPIListener(innerWindowId);
 * listener.on("message", onConsoleAPIMessage);
 * listener.startListening();
 *
 * const onConsoleAPIMessage = (eventName, data = {}) => {
 *   const { arguments: msgArguments, level, stacktrace, timeStamp } = data;
 *   ...
 * };
 * ```
 *
 * @fires message
 *    The ConsoleAPIListener emits "message" events, with the following object as
 *    payload:
 *      - {Array<Object>} arguments - Arguments as passed-in when the method was called.
 *      - {String} level - Importance, one of `info`, `warn`, `error`, `debug`, `trace`.
 *      - {Array<Object>} stacktrace - List of stack frames, starting from most recent.
 *      - {Number} timeStamp - Timestamp when the method was called.
 */
export class ConsoleAPIListener {
  #emittedMessages;
  #innerWindowId;
  #listening;

  /**
   * Create a new ConsoleAPIListener instance.
   *
   * @param {number} innerWindowId
   *     The inner window id to filter the messages for.
   */
  constructor(innerWindowId) {
    lazy.EventEmitter.decorate(this);

    this.#emittedMessages = new Set();
    this.#innerWindowId = innerWindowId;
    this.#listening = false;
  }

  destroy() {
    this.stopListening();
    this.#emittedMessages = null;
  }

  startListening() {
    if (this.#listening) {
      return;
    }

    lazy.ConsoleAPIStorage.addLogEventListener(
      this.#onConsoleAPIMessage,
      Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
    );

    // Emit cached messages after registering the listener, to make sure we
    // don't miss any message.
    this.#emitCachedMessages();

    this.#listening = true;
  }

  stopListening() {
    if (!this.#listening) {
      return;
    }

    lazy.ConsoleAPIStorage.removeLogEventListener(this.#onConsoleAPIMessage);
    this.#listening = false;
  }

  #emitCachedMessages() {
    const cachedMessages = lazy.ConsoleAPIStorage.getEvents(
      this.#innerWindowId
    );
    for (const message of cachedMessages) {
      this.#onConsoleAPIMessage(message);
    }
  }

  #onConsoleAPIMessage = message => {
    const messageObject = message.wrappedJSObject;

    // Bail if this message was already emitted, useful to filter out cached
    // messages already received by the consumer.
    if (this.#emittedMessages.has(messageObject)) {
      return;
    }

    this.#emittedMessages.add(messageObject);

    if (messageObject.innerID !== this.#innerWindowId) {
      // If the message doesn't match the innerWindowId of the current context
      // ignore it.
      return;
    }

    this.emit("message", {
      arguments: messageObject.arguments,
      level: messageObject.level,
      stacktrace: messageObject.stacktrace,
      timeStamp: messageObject.timeStamp,
    });
  };
}
PK
!<�%Yu��>chrome/remote/content/shared/listeners/ConsoleListener.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",

  getFramesFromStack: "chrome://remote/content/shared/Stack.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

/**
 * The ConsoleListener can be used to listen for console messages related to
 * Javascript errors, certain warnings which all happen within a specific
 * windowGlobal. Consumers can listen for the message types "error",
 * "warn" and "info".
 *
 * Example:
 * ```
 * const onJavascriptError = (eventName, data = {}) => {
 *   const { level, message, stacktrace, timestamp } = data;
 *   ...
 * };
 *
 * const listener = new ConsoleListener(innerWindowId);
 * listener.on("error", onJavascriptError);
 * listener.startListening();
 * ...
 * listener.stopListening();
 * ```
 *
 * @fires message
 *    The ConsoleListener emits "error", "warn" and "info" events, with the
 *    following object as payload:
 *      - {String} level - Importance, one of `info`, `warn`, `error`,
 *        `debug`, `trace`.
 *      - {String} message - Actual message from the console entry.
 *      - {Array<StackFrame>} stacktrace - List of stack frames,
 *        starting from most recent.
 *      - {Number} timeStamp - Timestamp when the method was called.
 */
export class ConsoleListener {
  #emittedMessages;
  #innerWindowId;
  #listening;

  /**
   * Create a new ConsoleListener instance.
   *
   * @param {number} innerWindowId
   *     The inner window id to filter the messages for.
   */
  constructor(innerWindowId) {
    lazy.EventEmitter.decorate(this);

    this.#emittedMessages = new Set();
    this.#innerWindowId = innerWindowId;
    this.#listening = false;
  }

  get listening() {
    return this.#listening;
  }

  destroy() {
    this.stopListening();
    this.#emittedMessages = null;
  }

  startListening() {
    if (this.#listening) {
      return;
    }

    Services.console.registerListener(this.#onConsoleMessage);

    // Emit cached messages after registering the listener, to make sure we
    // don't miss any message.
    this.#emitCachedMessages();

    this.#listening = true;
  }

  stopListening() {
    if (!this.#listening) {
      return;
    }

    Services.console.unregisterListener(this.#onConsoleMessage);
    this.#listening = false;
  }

  #emitCachedMessages() {
    const cachedMessages = Services.console.getMessageArray() || [];

    for (const message of cachedMessages) {
      this.#onConsoleMessage(message);
    }
  }

  #onConsoleMessage = message => {
    if (!(message instanceof Ci.nsIScriptError)) {
      // For now ignore basic nsIConsoleMessage instances, which are only
      // relevant to Chrome code and do not have a valid window reference.
      return;
    }

    // Bail if this message was already emitted, useful to filter out cached
    // messages already received by the consumer.
    if (this.#emittedMessages.has(message)) {
      return;
    }

    this.#emittedMessages.add(message);

    if (message.innerWindowID !== this.#innerWindowId) {
      // If the message doesn't match the innerWindowId of the current context
      // ignore it.
      return;
    }

    const { errorFlag, warningFlag, infoFlag } = Ci.nsIScriptError;
    let level;

    if ((message.flags & warningFlag) == warningFlag) {
      level = "warn";
    } else if ((message.flags & infoFlag) == infoFlag) {
      level = "info";
    } else if ((message.flags & errorFlag) == errorFlag) {
      level = "error";
    } else {
      lazy.logger.warn(
        `Not able to process console message with unknown flags ${message.flags}`
      );
      return;
    }

    // Send event when actively listening.
    this.emit(level, {
      level,
      message: message.errorMessage,
      stacktrace: lazy.getFramesFromStack(message.stack),
      timeStamp: message.timeStamp,
    });
  };

  get QueryInterface() {
    return ChromeUtils.generateQI(["nsIConsoleListener"]);
  }
}
PK
!<|~{ ;;Ichrome/remote/content/shared/listeners/ContextualIdentityListener.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
});

const OBSERVER_TOPIC_CREATED = "contextual-identity-created";
const OBSERVER_TOPIC_DELETED = "contextual-identity-deleted";

/**
 * The ContextualIdentityListener can be used to listen for notifications about
 * contextual identities (containers) being created or deleted.
 *
 * Example:
 * ```
 * const listener = new ContextualIdentityListener();
 * listener.on("created", onCreated);
 * listener.startListening();
 *
 * const onCreated = (eventName, data = {}) => {
 *   const { identity } = data;
 *   ...
 * };
 * ```
 *
 * @fires message
 *    The ContextualIdentityListener emits "created" and "deleted" events,
 *    with the following object as payload:
 *      - {object} identity
 *            The contextual identity which was created or deleted.
 */
export class ContextualIdentityListener {
  #listening;

  /**
   * Create a new BrowsingContextListener instance.
   */
  constructor() {
    lazy.EventEmitter.decorate(this);

    this.#listening = false;
  }

  destroy() {
    this.stopListening();
  }

  observe(subject, topic) {
    switch (topic) {
      case OBSERVER_TOPIC_CREATED:
        this.emit("created", { identity: subject.wrappedJSObject });
        break;

      case OBSERVER_TOPIC_DELETED:
        this.emit("deleted", { identity: subject.wrappedJSObject });
        break;
    }
  }

  startListening() {
    if (this.#listening) {
      return;
    }

    Services.obs.addObserver(this, OBSERVER_TOPIC_CREATED);
    Services.obs.addObserver(this, OBSERVER_TOPIC_DELETED);

    this.#listening = true;
  }

  stopListening() {
    if (!this.#listening) {
      return;
    }

    Services.obs.removeObserver(this, OBSERVER_TOPIC_CREATED);
    Services.obs.removeObserver(this, OBSERVER_TOPIC_DELETED);

    this.#listening = false;
  }
}
PK
!<���i
i
;chrome/remote/content/shared/listeners/LoadListener.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
});

/**
 * The LoadListener can be used to listen for load events.
 *
 * Example:
 * ```
 * const listener = new LoadListener();
 * listener.on("DOMContentLoaded", onDOMContentLoaded);
 * listener.startListening();
 *
 * const onDOMContentLoaded = (eventName, data = {}) => {
 *   const { target } = data;
 *   ...
 * };
 * ```
 *
 * @fires message
 *    The LoadListener emits "DOMContentLoaded" and "load" events,
 *    with the following object as payload:
 *      - {Document} target
 *            The target document.
 */
export class LoadListener {
  #abortController;
  #window;

  /**
   * Create a new LoadListener instance.
   */
  constructor(win) {
    lazy.EventEmitter.decorate(this);

    // Use an abort controller instead of removeEventListener because destroy
    // might be called close to the window global destruction.
    this.#abortController = null;

    this.#window = win;
  }

  destroy() {
    this.stopListening();
  }

  startListening() {
    if (this.#abortController) {
      return;
    }

    this.#abortController = new AbortController();

    // Events are attached to the windowRoot instead of the regular window to
    // avoid issues with document.open (Bug 1822772).
    this.#window.windowRoot.addEventListener(
      "DOMContentLoaded",
      this.#onDOMContentLoaded,
      {
        capture: true,
        mozSystemGroup: true,
        signal: this.#abortController.signal,
      }
    );

    this.#window.windowRoot.addEventListener("load", this.#onLoad, {
      capture: true,
      mozSystemGroup: true,
      signal: this.#abortController.signal,
    });
  }

  stopListening() {
    if (!this.#abortController) {
      return;
    }

    this.#abortController.abort();
    this.#abortController = null;
  }

  #onDOMContentLoaded = event => {
    // Check that this event was emitted for the relevant window, because events
    // from inner frames can bubble to the windowRoot.
    if (event.target.defaultView === this.#window) {
      this.emit("DOMContentLoaded", { target: event.target });
    }
  };

  #onLoad = event => {
    // Check that this event was emitted for the relevant window, because events
    // from inner frames can bubble to the windowRoot.
    if (event.target.defaultView === this.#window) {
      this.emit("load", { target: event.target });
    }
  };
}
PK
!<d�7gjjAchrome/remote/content/shared/listeners/NavigationListener.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
});

/**
 * The NavigationListener simply wraps a NavigationManager instance and exposes
 * it with a convenient listener API, more consistent with the rest of the
 * remote codebase. NavigationManager is a singleton per session so it can't
 * be instanciated for each and every consumer.
 *
 * Example:
 * ```
 * const onNavigationStarted = (eventName, data = {}) => {
 *   const { level, message, stacktrace, timestamp } = data;
 *   ...
 * };
 *
 * const listener = new NavigationListener(this.messageHandler.navigationManager);
 * listener.on("navigation-started", onNavigationStarted);
 * listener.startListening();
 * ...
 * listener.stopListening();
 * ```
 *
 * @fires message
 *     The NavigationListener emits "fragment-navigated", "navigation-started",
 *     "navigation-stopped", and "same-document-changed" events,
 *     with the following object as payload:
 *       - {string} navigationId - The UUID for the navigation.
 *       - {string} navigableId - The UUID for the navigable.
 *       - {string} url - The target url for the navigation.
 */
export class NavigationListener {
  #listening;
  #navigationManager;

  /**
   * Create a new NavigationListener instance.
   *
   * @param {NavigationManager} navigationManager
   *     The underlying NavigationManager for this listener.
   */
  constructor(navigationManager) {
    lazy.EventEmitter.decorate(this);

    this.#listening = false;
    this.#navigationManager = navigationManager;
  }

  get listening() {
    return this.#listening;
  }

  destroy() {
    this.stopListening();
  }

  startListening() {
    if (this.#listening) {
      return;
    }

    this.#navigationManager.on("fragment-navigated", this.#forwardEvent);
    this.#navigationManager.on("navigation-failed", this.#forwardEvent);
    this.#navigationManager.on("navigation-started", this.#forwardEvent);
    this.#navigationManager.on("navigation-stopped", this.#forwardEvent);
    this.#navigationManager.on("same-document-changed", this.#forwardEvent);

    this.#listening = true;
  }

  stopListening() {
    if (!this.#listening) {
      return;
    }

    this.#navigationManager.off("fragment-navigated", this.#forwardEvent);
    this.#navigationManager.off("navigation-failed", this.#forwardEvent);
    this.#navigationManager.off("navigation-started", this.#forwardEvent);
    this.#navigationManager.off("navigation-stopped", this.#forwardEvent);
    this.#navigationManager.off("same-document-changed", this.#forwardEvent);

    this.#listening = false;
  }

  #forwardEvent = (name, data) => {
    this.emit(name, data);
  };
}
PK
!<ox7�*�*Achrome/remote/content/shared/listeners/NetworkEventRecord.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  NetworkRequest: "chrome://remote/content/shared/NetworkRequest.sys.mjs",
  NetworkResponse: "chrome://remote/content/shared/NetworkResponse.sys.mjs",
  NetworkUtils:
    "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs",
});

/**
 * The NetworkEventRecord implements the interface expected from network event
 * owners for consumers of the DevTools NetworkObserver.
 *
 * The NetworkEventRecord emits the before-request-sent event on behalf of the
 * NetworkListener instance which created it.
 */
export class NetworkEventRecord {
  #decodedBodySizeMap;
  #fromCache;
  #networkEventsMap;
  #networkListener;
  #request;
  #response;
  #responseStartOverride;
  #wrappedChannel;

  /**
   *
   * @param {object} networkEvent
   *     The initial network event information (see createNetworkEvent() in
   *     NetworkUtils.sys.mjs).
   * @param {nsIChannel} channel
   *     The nsIChannel behind this network event.
   * @param {NetworkListener} networkListener
   *     The NetworkListener which created this NetworkEventRecord.
   * @param {NetworkDecodedBodySizeMap} decodedBodySizeMap
   *     Map from channelId to decoded body sizes. This information is read
   *     from all processes and aggregated in the parent process.
   * @param {NavigationManager} navigationManager
   *     The NavigationManager which belongs to the same session as this
   *     NetworkEventRecord.
   * @param {Map<string, NetworkEventRecord>} networkEventsMap
   *     The map between request id and NetworkEventRecord instance to complete
   *     the previous event in case of redirect.
   */
  constructor(
    networkEvent,
    channel,
    networkListener,
    decodedBodySizeMap,
    navigationManager,
    networkEventsMap
  ) {
    this.#request = new lazy.NetworkRequest(channel, {
      eventRecord: this,
      navigationManager,
      rawHeaders: networkEvent.rawHeaders,
    });
    this.#response = null;

    if (channel instanceof Ci.nsIChannel) {
      this.#wrappedChannel = ChannelWrapper.get(channel);
      this.#wrappedChannel.addEventListener("error", this.#onChannelCompleted);
      this.#wrappedChannel.addEventListener("stop", this.#onChannelCompleted);
    }

    this.#fromCache = networkEvent.fromCache;

    this.#decodedBodySizeMap = decodedBodySizeMap;
    this.#networkListener = networkListener;
    this.#networkEventsMap = networkEventsMap;

    if (this.#networkEventsMap.has(this.#requestId)) {
      const previousEvent = this.#networkEventsMap.get(this.#requestId);
      if (this.redirectCount != previousEvent.redirectCount) {
        // If redirect count is set, this is a redirect from the previous request.
        // notifyRedirect will complete the previous request.
        previousEvent.notifyRedirect();
      } else {
        // Otherwise if there is no redirect count or if it is identical to the
        // previously detected request, this is an authentication attempt.
        previousEvent.notifyAuthenticationAttempt();
      }
    }

    this.#networkEventsMap.set(this.#requestId, this);

    // NetworkObserver creates a network event when request headers have been
    // parsed.
    // According to the BiDi spec, we should emit beforeRequestSent when adding
    // request headers, see https://whatpr.org/fetch/1540.html#http-network-or-cache-fetch
    // step 8.17
    // Bug 1802181: switch the NetworkObserver to an event-based API.
    this.#emitBeforeRequestSent();

    // If the request is already blocked, we will not receive further updates,
    // emit a network.fetchError event immediately.
    if (networkEvent.blockedReason) {
      this.#emitFetchError();
    }
  }

  get #requestId() {
    return this.#request.requestId;
  }

  get redirectCount() {
    return this.#request.redirectCount;
  }

  /**
   * Add network request cache details.
   *
   * Required API for a NetworkObserver event owner.
   *
   * @param {object} options
   * @param {boolean} options.fromCache
   */
  addCacheDetails(options) {
    const { fromCache } = options;
    this.#fromCache = fromCache;
  }

  /**
   * Add network request raw headers.
   *
   * Required API for a NetworkObserver event owner.
   *
   * @param {object} options
   * @param {string} options.rawHeaders
   */
  addRawHeaders(options) {
    const { rawHeaders } = options;
    this.#request.addRawHeaders(rawHeaders);
  }

  /**
   * Add network request POST data.
   *
   * Required API for a NetworkObserver event owner.
   */
  addRequestPostData() {}

  /**
   * Add the initial network response information.
   *
   * Required API for a NetworkObserver event owner.
   *
   * @param {object} options
   * @param {nsIChannel} options.channel
   *     The channel.
   * @param {boolean} options.fromCache
   * @param {boolean} options.fromServiceWorker
   * @param {string} options.rawHeaders
   */
  addResponseStart(options) {
    const { channel, fromCache, fromServiceWorker, rawHeaders } = options;
    this.#response = new lazy.NetworkResponse(channel, {
      fromCache: this.#fromCache || !!fromCache,
      fromServiceWorker,
      rawHeaders,
    });

    // This should be triggered when all headers have been received, matching
    // the WebDriverBiDi response started trigger in `4.6. HTTP-network fetch`
    // from the fetch specification, based on the PR visible at
    // https://github.com/whatwg/fetch/pull/1540
    this.#emitResponseStarted();
  }

  /**
   * Add connection security information.
   *
   * Required API for a NetworkObserver event owner.
   *
   * Not used for RemoteAgent.
   */
  addSecurityInfo() {}

  /**
   * Add network event timings.
   *
   * Required API for a NetworkObserver event owner.
   *
   * Not used for RemoteAgent.
   */
  addEventTimings() {}

  /**
   * Add response cache entry.
   *
   * Required API for a NetworkObserver event owner.
   *
   * Not used for RemoteAgent.
   */
  addResponseCache() {}

  /**
   * Add response content.
   *
   * Required API for a NetworkObserver event owner.
   *
   * @param {object} responseContent
   *     An object which represents the response content.
   * @param {object} responseInfo
   *     Additional meta data about the response.
   */
  addResponseContent(responseContent, responseInfo) {
    if (
      // Ignore already completed requests.
      this.#request.alreadyCompleted ||
      // Ignore HTTP channels which are not service worker requests, they will
      // be handled via "error" and "stop" events, see #onChannelCompleted.
      (this.#request.isHttpChannel && !this.#response?.fromServiceWorker)
    ) {
      return;
    }

    const sizes = {
      decodedBodySize: responseContent.decodedBodySize,
      encodedBodySize: responseContent.bodySize,
      totalTransmittedSize: responseContent.transferredSize,
    };
    this.#handleRequestEnd(responseInfo.blockedReason, sizes);
  }

  /**
   * Add server timings.
   *
   * Required API for a NetworkObserver event owner.
   *
   * Not used for RemoteAgent.
   */
  addServerTimings() {}

  /**
   * Add service worker timings.
   *
   * Required API for a NetworkObserver event owner.
   *
   * Not used for RemoteAgent.
   */
  addServiceWorkerTimings() {}

  /**
   * Complete response in case of an authentication attempt.
   *
   * This method is required to be called on the previous event.
   */
  notifyAuthenticationAttempt() {
    // TODO: Bug 1899604, behavior might change based on spec issue
    // https://github.com/w3c/webdriver-bidi/issues/722

    // For now, in case of authentication attempts, we mark the current event as
    // completed and skip its responseCompleted event.
    // This way, only the last successful/failed authentication attempt will
    // emit a response completed event.
    this.#markRequestComplete();
  }

  /**
   * Complete response in case of redirect.
   *
   * This method is required to be called on the previous event.
   */
  notifyRedirect() {
    this.#emitResponseCompleted();
    this.#markRequestComplete();
  }

  onAuthPrompt(authDetails, authCallbacks) {
    this.#emitAuthRequired(authCallbacks);
  }

  prepareResponseStart(options) {
    this.#responseStartOverride = options;
  }

  #emitAuthRequired(authCallbacks) {
    this.#networkListener.emit("auth-required", {
      authCallbacks,
      request: this.#request,
      response: this.#response,
    });
  }

  #emitBeforeRequestSent() {
    this.#networkListener.emit("before-request-sent", {
      request: this.#request,
    });
  }

  #emitFetchError() {
    this.#networkListener.emit("fetch-error", {
      request: this.#request,
    });
  }

  #emitResponseCompleted() {
    this.#networkListener.emit("response-completed", {
      request: this.#request,
      response: this.#response,
    });
  }

  #emitResponseStarted() {
    this.#networkListener.emit("response-started", {
      request: this.#request,
      response: this.#response,
    });
  }

  #handleRequestEnd(blockedReason, sizes) {
    if (this.#responseStartOverride) {
      this.addResponseStart(this.#responseStartOverride);
    }

    if (blockedReason) {
      this.#emitFetchError();
    } else {
      // In the meantime, if the request was already completed, bail out here.
      if (this.#request.alreadyCompleted) {
        return;
      }
      this.#response.setResponseSizes(sizes);
      this.#emitResponseCompleted();
    }

    this.#markRequestComplete();
  }

  #markRequestComplete() {
    this.#request.alreadyCompleted = true;
    this.#networkEventsMap.delete(this.#requestId);
    this.#decodedBodySizeMap.delete(this.#request.channel.channelId);

    if (this.#wrappedChannel) {
      this.#wrappedChannel.removeEventListener(
        "error",
        this.#onChannelCompleted
      );
      this.#wrappedChannel.removeEventListener(
        "stop",
        this.#onChannelCompleted
      );
    }
  }

  #onChannelCompleted = async () => {
    if (this.#request.alreadyCompleted) {
      return;
    }

    const { blockedReason } = lazy.NetworkUtils.getBlockedReason(
      this.#request.channel,
      this.#response ? this.#response.fromCache : false
    );

    // TODO: Figure out a good default value for the decoded body size for non
    // http channels.
    // Blocked channels will emit a fetchError event which does not contain
    // sizes.
    const sizes = {};
    if (this.#request.isHttpChannel && !blockedReason) {
      sizes.decodedBodySize = await this.#decodedBodySizeMap.getDecodedBodySize(
        this.#request.channel.channelId
      );
      sizes.encodedBodySize = this.#request.channel.encodedBodySize;
      sizes.totalTransmittedSize = this.#request.channel.transferSize;
    }

    this.#handleRequestEnd(blockedReason, sizes);
  };
}
PK
!<C��U@@>chrome/remote/content/shared/listeners/NetworkListener.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
  NetworkObserver:
    "resource://devtools/shared/network-observer/NetworkObserver.sys.mjs",

  NetworkEventRecord:
    "chrome://remote/content/shared/listeners/NetworkEventRecord.sys.mjs",
});

/**
 * The NetworkListener listens to all network activity from the parent
 * process.
 *
 * Example:
 * ```
 * const listener = new NetworkListener();
 * listener.on("before-request-sent", onBeforeRequestSent);
 * listener.startListening();
 *
 * const onBeforeRequestSent = (eventName, data = {}) => {
 *   const { cntextId, redirectCount, requestData, requestId, timestamp } = data;
 *   ...
 * };
 * ```
 *
 * @fires before-request-sent
 *    The NetworkListener emits "before-request-sent" events, with the
 *    following object as payload:
 *      - {number} browsingContextId - The browsing context id of the browsing
 *        context where this request was performed.
 *      - {number} redirectCount - The request's redirect count.
 *      - {RequestData} requestData - The request's data as expected by
 *        WebDriver BiDi.
 *      - {string} requestId - The id of the request, consistent across
 *        redirects.
 *      - {number} timestamp - Timestamp when the event was generated.
 */
export class NetworkListener {
  #decodedBodySizeMap;
  #devtoolsNetworkObserver;
  #listening;
  #navigationManager;
  #networkEventsMap;

  constructor(navigationManager, decodedBodySizeMap) {
    lazy.EventEmitter.decorate(this);

    this.#listening = false;
    this.#decodedBodySizeMap = decodedBodySizeMap;
    this.#navigationManager = navigationManager;

    // This map is going to be used in NetworkEventRecord,
    // but because we need to have one instance of the map per session,
    // we have to create and store it here (since each session has a dedicated NetworkListener).
    this.#networkEventsMap = new Map();
  }

  destroy() {
    this.stopListening();
  }

  startListening() {
    if (this.#listening) {
      return;
    }

    this.#devtoolsNetworkObserver = new lazy.NetworkObserver({
      ignoreChannelFunction: this.#ignoreChannelFunction,
      onNetworkEvent: this.#onNetworkEvent,
    });

    // Enable the auth prompt listening to support the auth-required event and
    // phase.
    this.#devtoolsNetworkObserver.setAuthPromptListenerEnabled(true);

    this.#listening = true;
  }

  stopListening() {
    if (!this.#listening) {
      return;
    }

    this.#devtoolsNetworkObserver.destroy();
    this.#devtoolsNetworkObserver = null;

    this.#listening = false;
  }

  #ignoreChannelFunction = channel => {
    // Bug 1826210: Ignore file channels which don't support the same APIs as
    // regular HTTP channels.
    if (channel instanceof Ci.nsIFileChannel) {
      return true;
    }

    // Ignore chrome-privileged or DevTools-initiated requests
    if (
      channel.loadInfo?.loadingDocument === null &&
      (channel.loadInfo.loadingPrincipal ===
        Services.scriptSecurityManager.getSystemPrincipal() ||
        channel.loadInfo.isInDevToolsContext)
    ) {
      return true;
    }

    return false;
  };

  #onNetworkEvent = (networkEvent, channel) => {
    return new lazy.NetworkEventRecord(
      networkEvent,
      channel,
      this,
      this.#decodedBodySizeMap,
      this.#navigationManager,
      this.#networkEventsMap
    );
  };
}
PK
!<7=r� � =chrome/remote/content/shared/listeners/PromptListener.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  modal: "chrome://remote/content/shared/Prompt.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

/**
 * The PromptListener listens to the DialogObserver events.
 *
 * Example:
 * ```
 * const listener = new PromptListener();
 * listener.on("opened", onPromptOpened);
 * listener.startListening();
 *
 * const onPromptOpened = (eventName, data = {}) => {
 *   const { contentBrowser, prompt } = data;
 *   ...
 * };
 * ```
 *
 * @fires message
 *    The PromptListener emits "opened" events,
 *    with the following object as payload:
 *      - {XULBrowser} contentBrowser
 *            The <xul:browser> which hold the <var>prompt</var>.
 *      - {modal.Dialog} prompt
 *            Returns instance of the Dialog class.
 *
 *    The PromptListener emits "closed" events,
 *    with the following object as payload:
 *      - {XULBrowser} contentBrowser
 *            The <xul:browser> which is the target of the event.
 *      - {object} detail
 *        {boolean=} detail.accepted
 *            Returns true if a user prompt was accepted
 *            and false if it was dismissed.
 *        {string=} detail.userText
 *            The user text specified in a prompt.
 */
export class PromptListener {
  #curBrowserFn;
  #listening;

  constructor(curBrowserFn) {
    lazy.EventEmitter.decorate(this);

    // curBrowserFn is used only for Marionette (WebDriver classic).
    this.#curBrowserFn = curBrowserFn;
    this.#listening = false;
  }

  destroy() {
    this.stopListening();
  }

  /**
   * Waits for the prompt to be closed.
   *
   * @returns {Promise}
   *    Promise that resolves when the prompt is closed.
   */
  async dialogClosed() {
    return new Promise(resolve => {
      const dialogClosed = () => {
        this.off("closed", dialogClosed);
        resolve();
      };

      this.on("closed", dialogClosed);
    });
  }

  /**
   * Handles `DOMModalDialogClosed` events.
   */
  handleEvent(event) {
    lazy.logger.trace(`Received event ${event.type}`);

    const chromeWin = event.target.opener
      ? event.target.opener.ownerGlobal
      : event.target.ownerGlobal;
    const curBrowser = this.#curBrowserFn && this.#curBrowserFn();

    // For Marionette (WebDriver classic) we only care about events which come
    // the currently selected browser.
    if (curBrowser && chromeWin != curBrowser.window) {
      return;
    }

    let contentBrowser;
    if (lazy.AppInfo.isAndroid) {
      const tabBrowser = lazy.TabManager.getTabBrowser(event.target);
      // Since on Android we always have only one tab we can just check
      // the selected tab.
      const tab = tabBrowser.selectedTab;
      contentBrowser = lazy.TabManager.getBrowserForTab(tab);
    } else {
      contentBrowser = event.target;
    }

    const detail = {};

    // At the moment the event details are present for GeckoView and on desktop
    // only for Services.prompt.MODAL_TYPE_CONTENT prompts.
    if (event.detail) {
      const { areLeaving, promptType, value } = event.detail;
      // `areLeaving` returns undefined for alerts, for confirms and prompts
      // it returns true if a user prompt was accepted and false if it was dismissed.
      detail.accepted = areLeaving === undefined ? true : areLeaving;
      detail.promptType = promptType;
      if (value) {
        detail.userText = value;
      }
    }

    this.emit("closed", {
      contentBrowser,
      detail,
    });
  }

  /**
   * Observes the following notifications:
   * `common-dialog-loaded` - when a modal dialog loaded on desktop,
   * `domwindowopened` - when a new chrome window opened,
   * `geckoview-prompt-show` - when a modal dialog opened on Android.
   */
  observe(subject, topic) {
    lazy.logger.trace(`Received observer notification ${topic}`);

    let curBrowser = this.#curBrowserFn && this.#curBrowserFn();
    switch (topic) {
      case "common-dialog-loaded":
        if (curBrowser) {
          if (
            !this.#hasCommonDialog(
              curBrowser.contentBrowser,
              curBrowser.window,
              subject
            )
          ) {
            return;
          }
        } else {
          const chromeWin = subject.opener
            ? subject.opener.ownerGlobal
            : subject.ownerGlobal;

          for (const tab of lazy.TabManager.getTabsForWindow(chromeWin)) {
            const contentBrowser = lazy.TabManager.getBrowserForTab(tab);
            const window = lazy.TabManager.getWindowForTab(tab);

            if (this.#hasCommonDialog(contentBrowser, window, subject)) {
              curBrowser = {
                contentBrowser,
                window,
              };

              break;
            }
          }
        }

        this.emit("opened", {
          contentBrowser: curBrowser.contentBrowser,
          prompt: new lazy.modal.Dialog(subject),
        });

        break;

      case "domwindowopened":
        subject.addEventListener("DOMModalDialogClosed", this);
        break;

      case "geckoview-prompt-show":
        for (let win of Services.wm.getEnumerator(null)) {
          const subjectObject = subject.wrappedJSObject;
          const prompt = win
            .prompts()
            .find(item => item.getPromptId() == subjectObject.id);
          if (prompt) {
            const tabBrowser = lazy.TabManager.getTabBrowser(win);
            // Since on Android we always have only one tab we can just check
            // the selected tab.
            const tab = tabBrowser.selectedTab;
            const contentBrowser = lazy.TabManager.getBrowserForTab(tab);

            // Do not send the event if the curBrowser is specified,
            // and it's different from prompt browser.
            if (curBrowser && contentBrowser !== curBrowser.contentBrowser) {
              continue;
            }

            this.emit("opened", {
              contentBrowser,
              prompt: new lazy.modal.Dialog(prompt),
            });
            return;
          }
        }
        break;
    }
  }

  startListening() {
    if (this.#listening) {
      return;
    }

    this.#register();
    this.#listening = true;
  }

  stopListening() {
    if (!this.#listening) {
      return;
    }

    this.#unregister();
    this.#listening = false;
  }

  #hasCommonDialog(contentBrowser, window, prompt) {
    const modalType = prompt.Dialog.args.modalType;
    if (
      modalType === Services.prompt.MODAL_TYPE_TAB ||
      modalType === Services.prompt.MODAL_TYPE_CONTENT
    ) {
      // Find the container of the dialog in the parent document, and ensure
      // it is a descendant of the same container as the content browser.
      const container = contentBrowser.closest(".browserSidebarContainer");

      return container.contains(prompt.docShell.chromeEventHandler);
    }

    return prompt.ownerGlobal == window || prompt.opener?.ownerGlobal == window;
  }

  #register() {
    Services.obs.addObserver(this, "common-dialog-loaded");
    Services.obs.addObserver(this, "domwindowopened");
    Services.obs.addObserver(this, "geckoview-prompt-show");

    // Register event listener and save already open prompts for all already open windows.
    for (const win of Services.wm.getEnumerator(null)) {
      win.addEventListener("DOMModalDialogClosed", this);
    }
  }

  #unregister() {
    const removeObserver = observerName => {
      try {
        Services.obs.removeObserver(this, observerName);
      } catch (e) {
        lazy.logger.debug(`Failed to remove observer "${observerName}"`);
      }
    };

    for (const observerName of [
      "common-dialog-loaded",
      "domwindowopened",
      "geckoview-prompt-show",
    ]) {
      removeObserver(observerName);
    }

    // Unregister event listener for all open windows
    for (const win of Services.wm.getEnumerator(null)) {
      win.removeEventListener("DOMModalDialogClosed", this);
    }
  }
}
PK
!<n�2��:chrome/remote/content/shared/messagehandler/Errors.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { RemoteError } from "chrome://remote/content/shared/RemoteError.sys.mjs";

class MessageHandlerError extends RemoteError {
  /**
   * @param {(string|Error)=} x
   *     Optional string describing error situation or Error instance
   *     to propagate.
   */
  constructor(x) {
    super(x);
    this.name = this.constructor.name;
    this.status = "message handler error";

    // Error's ctor does not preserve x' stack
    if (typeof x?.stack !== "undefined") {
      this.stack = x.stack;
    }
  }

  get isMessageHandlerError() {
    return true;
  }

  /**
   * @returns {Record<string, string>}
   *     JSON serialisation of error prototype.
   */
  toJSON() {
    return {
      error: this.status,
      message: this.message || "",
      stacktrace: this.stack || "",
    };
  }

  /**
   * Unmarshals a JSON error representation to the appropriate MessageHandler
   * error type.
   *
   * @param {Record<string, string>} json
   *     Error object.
   *
   * @returns {Error}
   *     Error prototype.
   */
  static fromJSON(json) {
    if (typeof json.error == "undefined") {
      let s = JSON.stringify(json);
      throw new TypeError("Undeserialisable error type: " + s);
    }
    if (!STATUSES.has(json.error)) {
      throw new TypeError("Not of MessageHandlerError descent: " + json.error);
    }

    let cls = STATUSES.get(json.error);
    let err = new cls();
    if ("message" in json) {
      err.message = json.message;
    }
    if ("stacktrace" in json) {
      err.stack = json.stacktrace;
    }
    return err;
  }
}

/**
 * A command could not be handled by the message handler network.
 */
class UnsupportedCommandError extends MessageHandlerError {
  constructor(message) {
    super(message);
    this.status = "unsupported message handler command";
  }
}

const STATUSES = new Map([
  ["message handler error", MessageHandlerError],
  ["unsupported message handler command", UnsupportedCommandError],
]);

/** @namespace */
export const error = {
  MessageHandlerError,
  UnsupportedCommandError,
};
PK
!<{Xư2 2 Dchrome/remote/content/shared/messagehandler/EventsDispatcher.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ContextDescriptorType:
    "chrome://remote/content/shared/messagehandler/MessageHandler.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  SessionDataCategory:
    "chrome://remote/content/shared/messagehandler/sessiondata/SessionData.sys.mjs",
  SessionDataMethod:
    "chrome://remote/content/shared/messagehandler/sessiondata/SessionData.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

/**
 * Helper to listen to events which rely on SessionData.
 * In order to support the EventsDispatcher, a module emitting events should
 * subscribe and unsubscribe to those events based on SessionData updates
 * and should use the "event" SessionData category.
 */
export class EventsDispatcher {
  // The MessageHandler owning this EventsDispatcher.
  #messageHandler;

  /**
   * @typedef {object} EventListenerInfo
   * @property {ContextDescriptor} contextDescriptor
   *     The ContextDescriptor to which those callbacks are associated
   * @property {Set<Function>} callbacks
   *     The callbacks to trigger when an event matching the ContextDescriptor
   *     is received.
   */

  // Map from event name to map of strings (context keys) to EventListenerInfo.
  #listenersByEventName;

  /**
   * Create a new EventsDispatcher instance.
   *
   * @param {MessageHandler} messageHandler
   *     The MessageHandler owning this EventsDispatcher.
   */
  constructor(messageHandler) {
    this.#messageHandler = messageHandler;

    this.#listenersByEventName = new Map();
  }

  destroy() {
    for (const event of this.#listenersByEventName.keys()) {
      this.#messageHandler.off(event, this.#onMessageHandlerEvent);
    }

    this.#listenersByEventName = null;
  }

  /**
   * Check for existing listeners for a given event name and a given context.
   *
   * @param {string} name
   *     Name of the event to check.
   * @param {ContextInfo} contextInfo
   *     ContextInfo identifying the context to check.
   *
   * @returns {boolean}
   *     True if there is a registered listener matching the provided arguments.
   */
  hasListener(name, contextInfo) {
    if (!this.#listenersByEventName.has(name)) {
      return false;
    }

    const listeners = this.#listenersByEventName.get(name);
    for (const { contextDescriptor } of listeners.values()) {
      if (this.#matchesContext(contextInfo, contextDescriptor)) {
        return true;
      }
    }
    return false;
  }

  /**
   * Stop listening for an event relying on SessionData and relayed by the
   * message handler.
   *
   * @param {string} event
   *     Name of the event to unsubscribe from.
   * @param {ContextDescriptor} contextDescriptor
   *     Context descriptor for this event.
   * @param {Function} callback
   *     Event listener callback.
   * @returns {Promise}
   *     Promise which resolves when the event fully unsubscribed, including
   *     propagating the necessary session data.
   */
  async off(event, contextDescriptor, callback) {
    return this.update([{ event, contextDescriptor, callback, enable: false }]);
  }

  /**
   * Listen for an event relying on SessionData and relayed by the message
   * handler.
   *
   * @param {string} event
   *     Name of the event to subscribe to.
   * @param {ContextDescriptor} contextDescriptor
   *     Context descriptor for this event.
   * @param {Function} callback
   *     Event listener callback.
   * @returns {Promise}
   *     Promise which resolves when the event fully subscribed to, including
   *     propagating the necessary session data.
   */
  async on(event, contextDescriptor, callback) {
    return this.update([{ event, contextDescriptor, callback, enable: true }]);
  }

  /**
   * An object that holds information about subscription/unsubscription
   * of an event.
   *
   * @typedef Subscription
   *
   * @param {string} event
   *     Name of the event to subscribe/unsubscribe to.
   * @param {ContextDescriptor} contextDescriptor
   *     Context descriptor for this event.
   * @param {Function} callback
   *     Event listener callback.
   * @param {boolean} enable
   *     True, if we need to subscribe to an event.
   *     Otherwise false.
   */

  /**
   * Start or stop listening to a list of events relying on SessionData
   * and relayed by the message handler.
   *
   * @param {Array<Subscription>} subscriptions
   *     The list of information to subscribe/unsubscribe to.
   *
   * @returns {Promise}
   *     Promise which resolves when the events fully subscribed/unsubscribed to,
   *     including propagating the necessary session data.
   */
  async update(subscriptions) {
    const sessionDataItemUpdates = [];
    subscriptions.forEach(({ event, contextDescriptor, callback, enable }) => {
      if (enable) {
        // Setup listeners.
        if (!this.#listenersByEventName.has(event)) {
          this.#listenersByEventName.set(event, new Map());
          this.#messageHandler.on(event, this.#onMessageHandlerEvent);
        }

        const key = this.#getContextKey(contextDescriptor);
        const listeners = this.#listenersByEventName.get(event);
        if (listeners.has(key)) {
          const { callbacks } = listeners.get(key);
          callbacks.add(callback);
        } else {
          const callbacks = new Set([callback]);
          listeners.set(key, { callbacks, contextDescriptor });

          sessionDataItemUpdates.push({
            ...this.#getSessionDataItem(event, contextDescriptor),
            method: lazy.SessionDataMethod.Add,
          });
        }
      } else {
        // Remove listeners.
        const listeners = this.#listenersByEventName.get(event);
        if (!listeners) {
          return;
        }

        const key = this.#getContextKey(contextDescriptor);
        if (!listeners.has(key)) {
          return;
        }

        const { callbacks } = listeners.get(key);
        if (callbacks.has(callback)) {
          callbacks.delete(callback);
          if (callbacks.size === 0) {
            listeners.delete(key);
            if (listeners.size === 0) {
              this.#messageHandler.off(event, this.#onMessageHandlerEvent);
              this.#listenersByEventName.delete(event);
            }

            sessionDataItemUpdates.push({
              ...this.#getSessionDataItem(event, contextDescriptor),
              method: lazy.SessionDataMethod.Remove,
            });
          }
        }
      }
    });

    // Update all sessionData at once.
    await this.#messageHandler.updateSessionData(sessionDataItemUpdates);
  }

  #getContextKey(contextDescriptor) {
    const { id, type } = contextDescriptor;
    return `${type}-${id}`;
  }

  #getSessionDataItem(event, contextDescriptor) {
    const [moduleName] = event.split(".");
    return {
      moduleName,
      category: lazy.SessionDataCategory.Event,
      contextDescriptor,
      values: [event],
    };
  }

  #matchesContext(contextInfo, contextDescriptor) {
    if (contextDescriptor.type === lazy.ContextDescriptorType.All) {
      return true;
    }

    if (
      contextDescriptor.type === lazy.ContextDescriptorType.TopBrowsingContext
    ) {
      const eventBrowsingContext = lazy.TabManager.getBrowsingContextById(
        contextInfo.contextId
      );
      return eventBrowsingContext?.browserId === contextDescriptor.id;
    }

    return false;
  }

  #onMessageHandlerEvent = (name, event, contextInfo) => {
    const listeners = this.#listenersByEventName.get(name);
    for (const { callbacks, contextDescriptor } of listeners.values()) {
      if (!this.#matchesContext(contextInfo, contextDescriptor)) {
        continue;
      }

      for (const callback of callbacks) {
        try {
          callback(name, event);
        } catch (e) {
          lazy.logger.debug(
            `Error while executing callback for ${name}: ${e.message}`
          );
        }
      }
    }
  };
}
PK
!<2����+�+Bchrome/remote/content/shared/messagehandler/MessageHandler.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  error: "chrome://remote/content/shared/messagehandler/Errors.sys.mjs",
  EventsDispatcher:
    "chrome://remote/content/shared/messagehandler/EventsDispatcher.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  ModuleCache:
    "chrome://remote/content/shared/messagehandler/ModuleCache.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

/**
 * A ContextDescriptor object provides information to decide if a broadcast or
 * a session data item should be applied to a specific MessageHandler context.
 *
 * @typedef {object} ContextDescriptor
 * @property {ContextDescriptorType} type
 *     The type of context
 * @property {string=} id
 *     Unique id of a given context for the provided type.
 *     For ContextDescriptorType.All, id can be ommitted.
 *     For ContextDescriptorType.TopBrowsingContext, the id should be the
 *     browserId corresponding to a top-level browsing context.
 */

/**
 * Enum of ContextDescriptor types.
 *
 * @enum {string}
 */
export const ContextDescriptorType = {
  All: "All",
  TopBrowsingContext: "TopBrowsingContext",
};

/**
 * A ContextInfo identifies a given context that can be linked to a MessageHandler
 * instance. It should be used to identify events coming from this context.
 *
 * It can either be provided by the MessageHandler itself, when the event is
 * emitted from the context it relates to.
 *
 * Or it can be assembled manually, for instance when emitting an event which
 * relates to a window global from the root layer (eg browsingContext.contextCreated).
 *
 * @typedef {object} ContextInfo
 * @property {string} contextId
 *     Unique id of the MessageHandler corresponding to this context.
 * @property {string} type
 *     One of MessageHandler.type.
 */

/**
 * MessageHandler instances are dedicated to handle both Commands and Events
 * to enable automation and introspection for remote control protocols.
 *
 * MessageHandler instances are designed to form a network, where each instance
 * should allow to inspect a specific context (eg. a BrowsingContext, a Worker,
 * etc). Those instances might live in different processes and threads but
 * should be linked together by the usage of a single sessionId, shared by all
 * the instances of a single MessageHandler network.
 *
 * MessageHandler instances will be dynamically spawned depending on which
 * Command or which Event needs to be processed and should therefore not be
 * explicitly created by consumers, nor used directly.
 *
 * The only exception is the ROOT MessageHandler. This MessageHandler will be
 * the entry point to send commands to the rest of the network. It will also
 * emit all the relevant events captured by the network.
 *
 * However, even to create this ROOT MessageHandler, consumers should use the
 * RootMessageHandlerRegistry. This singleton will ensure that MessageHandler
 * instances are properly registered and can be retrieved based on a given
 * session id as well as some other context information.
 */
export class MessageHandler extends EventEmitter {
  #context;
  #contextId;
  #eventsDispatcher;
  #moduleCache;
  #registry;
  #sessionId;

  /**
   * Create a new MessageHandler instance.
   *
   * @param {string} sessionId
   *     ID of the session the handler is used for.
   * @param {object} context
   *     The context linked to this MessageHandler instance.
   * @param {MessageHandlerRegistry} registry
   *     The MessageHandlerRegistry which owns this MessageHandler instance.
   */
  constructor(sessionId, context, registry) {
    super();

    this.#moduleCache = new lazy.ModuleCache(this);

    this.#sessionId = sessionId;
    this.#context = context;
    this.#contextId = this.constructor.getIdFromContext(context);
    this.#eventsDispatcher = new lazy.EventsDispatcher(this);
    this.#registry = registry;
  }

  get context() {
    return this.#context;
  }

  get contextId() {
    return this.#contextId;
  }

  get eventsDispatcher() {
    return this.#eventsDispatcher;
  }

  get moduleCache() {
    return this.#moduleCache;
  }

  get name() {
    return [this.sessionId, this.constructor.type, this.contextId].join("-");
  }

  get registry() {
    return this.#registry;
  }

  get sessionId() {
    return this.#sessionId;
  }

  destroy() {
    lazy.logger.trace(
      `MessageHandler ${this.constructor.type} for session ${this.sessionId} is being destroyed`
    );
    this.#eventsDispatcher.destroy();
    this.#moduleCache.destroy();

    // At least the MessageHandlerRegistry will be expecting this event in order
    // to remove the instance from the registry when destroyed.
    this.emit("message-handler-destroyed", this);
  }

  /**
   * Emit a message handler event.
   *
   * Such events should bubble up to the root of a MessageHandler network.
   *
   * @param {string} name
   *     Name of the event. Protocol level events should be of the
   *     form [module name].[event name].
   * @param {object} data
   *     The event's data.
   * @param {ContextInfo=} contextInfo
   *     The event's context info, used to identify the origin of the event.
   *     If not provided, the context info of the current MessageHandler will be
   *     used.
   */
  emitEvent(name, data, contextInfo) {
    // If no contextInfo field is provided on the event, extract it from the
    // MessageHandler instance.
    contextInfo = contextInfo || this.#getContextInfo();

    // Events are emitted both under their own name for consumers listening to
    // a specific and as `message-handler-event` for consumers which need to
    // catch all events.
    this.emit(name, data, contextInfo);
    this.emit("message-handler-event", {
      name,
      contextInfo,
      data,
      sessionId: this.sessionId,
    });
  }

  /**
   * @typedef {object} CommandDestination
   * @property {string} type
   *     One of MessageHandler.type.
   * @property {string=} id
   *     Unique context identifier. The format depends on the type.
   *     For WINDOW_GLOBAL destinations, this is a browsing context id.
   *     Optional, should only be provided if `contextDescriptor` is missing.
   * @property {ContextDescriptor=} contextDescriptor
   *     Descriptor used to match several contexts, which will all receive the
   *     command.
   *     Optional, should only be provided if `id` is missing.
   */

  /**
   * @typedef {object} Command
   * @property {string} commandName
   *     The name of the command to execute.
   * @property {string} moduleName
   *     The name of the module.
   * @property {object} params
   *     Optional command parameters.
   * @property {CommandDestination} destination
   *     The destination describing a debuggable context.
   * @property {boolean=} retryOnAbort
   *     Optional. When true, commands will be retried upon AbortError, which
   *     can occur when the underlying JSWindowActor pair is destroyed.
   *     Defaults to `false`.
   */

  /**
   * Retrieve all module classes matching the moduleName and destination.
   * See `getAllModuleClasses` (ModuleCache.sys.mjs) for more details.
   *
   * @param {string} moduleName
   *     The name of the module.
   * @param {Destination} destination
   *     The destination.
   * @returns {Array.<class<Module>|null>}
   *     An array of Module classes.
   */
  getAllModuleClasses(moduleName, destination) {
    return this.#moduleCache.getAllModuleClasses(moduleName, destination);
  }

  /**
   * Handle a command, either in one of the modules owned by this MessageHandler
   * or in a another MessageHandler after forwarding the command.
   *
   * @param {Command} command
   *     The command that should be either handled in this layer or forwarded to
   *     the next layer leading to the destination.
   * @returns {Promise} A Promise that will resolve with the return value of the
   *     command once it has been executed.
   */
  handleCommand(command) {
    const { moduleName, commandName, params, destination } = command;
    lazy.logger.trace(
      `Received command ${moduleName}.${commandName} for destination ${destination.type}`
    );

    if (!this.supportsCommand(moduleName, commandName, destination)) {
      throw new lazy.error.UnsupportedCommandError(
        `${moduleName}.${commandName} not supported for destination ${destination?.type}`
      );
    }

    const module = this.#moduleCache.getModuleInstance(moduleName, destination);
    if (module && module.supportsMethod(commandName)) {
      return module[commandName](params, destination);
    }

    return this.forwardCommand(command);
  }

  toString() {
    return `[object ${this.constructor.name} ${this.name}]`;
  }

  /**
   * Execute the required initialization steps, inlcluding apply the initial session data items
   * provided to this MessageHandler on startup. Implementation is specific to each MessageHandler class.
   *
   * By default the implementation is a no-op.
   */
  async initialize() {}

  /**
   * Returns the module path corresponding to this MessageHandler class.
   *
   * Needs to be implemented in the sub class.
   */
  static get modulePath() {
    throw new Error("Not implemented");
  }

  /**
   * Returns the type corresponding to this MessageHandler class.
   *
   * Needs to be implemented in the sub class.
   */
  static get type() {
    throw new Error("Not implemented");
  }

  /**
   * Returns the id corresponding to a context compatible with this
   * MessageHandler class.
   *
   * Needs to be implemented in the sub class.
   */
  static getIdFromContext() {
    throw new Error("Not implemented");
  }

  /**
   * Forward a command to other MessageHandlers.
   *
   * Needs to be implemented in the sub class.
   */
  forwardCommand() {
    throw new Error("Not implemented");
  }

  /**
   * Check if contextDescriptor matches the context linked
   * to this MessageHandler instance.
   *
   * Needs to be implemented in the sub class.
   */
  matchesContext() {
    throw new Error("Not implemented");
  }

  /**
   * Check if the given command is supported in the module
   * for the destination
   *
   * @param {string} moduleName
   *     The name of the module.
   * @param {string} commandName
   *     The name of the command.
   * @param {Destination} destination
   *     The destination.
   * @returns {boolean}
   *     True if the command is supported.
   */
  supportsCommand(moduleName, commandName, destination) {
    return this.getAllModuleClasses(moduleName, destination).some(cls =>
      cls.supportsMethod(commandName)
    );
  }

  /**
   * Return the context information for this MessageHandler instance, which
   * can be used to identify the origin of an event.
   *
   * @returns {ContextInfo}
   *     The context information for this MessageHandler.
   */
  #getContextInfo() {
    return {
      contextId: this.contextId,
      type: this.constructor.type,
    };
  }
}
PK
!<I�SQ,,Jchrome/remote/content/shared/messagehandler/MessageHandlerRegistry.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  readSessionData:
    "chrome://remote/content/shared/messagehandler/sessiondata/SessionDataReader.sys.mjs",
  RootMessageHandler:
    "chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs",
  WindowGlobalMessageHandler:
    "chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

/**
 * Map of MessageHandler type to MessageHandler subclass.
 */
ChromeUtils.defineLazyGetter(
  lazy,
  "MessageHandlerClasses",
  () =>
    new Map([
      [lazy.RootMessageHandler.type, lazy.RootMessageHandler],
      [lazy.WindowGlobalMessageHandler.type, lazy.WindowGlobalMessageHandler],
    ])
);

/**
 * Get the MessageHandler subclass corresponding to the provided type.

 * @param {string} type
 *     MessageHandler type, one of MessageHandler.type.
 * @returns {Class}
 *     A MessageHandler subclass
 * @throws {Error}
 *      Throws if no MessageHandler subclass is found for the provided type.
 */
export function getMessageHandlerClass(type) {
  if (!lazy.MessageHandlerClasses.has(type)) {
    throw new Error(`No MessageHandler class available for type "${type}"`);
  }
  return lazy.MessageHandlerClasses.get(type);
}

/**
 * The MessageHandlerRegistry allows to create and retrieve MessageHandler
 * instances for different session ids.
 *
 * A MessageHandlerRegistry instance is bound to a specific MessageHandler type
 * and context. All MessageHandler instances created by the same registry will
 * use the type and context of the registry, but each will be associated to a
 * different session id.
 *
 * The registry is useful to retrieve the appropriate MessageHandler instance
 * after crossing a technical boundary (eg process, thread...).
 */
export class MessageHandlerRegistry extends EventEmitter {
  /*
   * @param {String} type
   *     MessageHandler type, one of MessageHandler.type.
   * @param {Object} context
   *     The context object, which depends on the type.
   */
  constructor(type, context) {
    super();

    this._messageHandlerClass = getMessageHandlerClass(type);
    this._context = context;
    this._type = type;

    /**
     * Map of session id to MessageHandler instance
     */
    this._messageHandlersMap = new Map();

    this._onMessageHandlerDestroyed =
      this._onMessageHandlerDestroyed.bind(this);
    this._onMessageHandlerEvent = this._onMessageHandlerEvent.bind(this);
  }

  /**
   * Create all message handlers for the current context, based on the content
   * of the session data.
   * This should typically be called when the context is ready to be used and
   * to receive/send commands.
   */
  createAllMessageHandlers() {
    const data = lazy.readSessionData();
    for (const [sessionId, sessionDataItems] of data) {
      // Create a message handler for this context for each active message
      // handler session.
      // TODO: In the future, to support debugging use cases we might want to
      // only create a message handler if there is relevant data.
      // For automation scenarios, this is less critical.
      this._createMessageHandler(sessionId, sessionDataItems);
    }
  }

  destroy() {
    this._messageHandlersMap.forEach(messageHandler => {
      messageHandler.destroy();
    });
  }

  /**
   * Retrieve all MessageHandler instances held in this registry, for all
   * session IDs.
   *
   * @returns {Iterable.<MessageHandler>}
   *     Iterator of MessageHandler instances
   */
  getAllMessageHandlers() {
    return this._messageHandlersMap.values();
  }

  /**
   * Retrieve an existing MessageHandler instance matching the provided session
   * id. Returns null if no MessageHandler was found.
   *
   * @param {string} sessionId
   *     ID of the session the handler is used for.
   * @returns {MessageHandler=}
   *     A MessageHandler instance, null if not found.
   */
  getExistingMessageHandler(sessionId) {
    return this._messageHandlersMap.get(sessionId);
  }

  /**
   * Retrieve the MessageHandler instance registered for the provided session
   * id. Will create and register a MessageHander if no instance was found.
   *
   * @param {string} sessionId
   *     ID of the session the handler is used for.
   * @returns {MessageHandler}
   *     A MessageHandler instance.
   */
  getOrCreateMessageHandler(sessionId) {
    let messageHandler = this.getExistingMessageHandler(sessionId);
    if (!messageHandler) {
      messageHandler = this._createMessageHandler(sessionId);
    }

    return messageHandler;
  }

  /**
   * Retrieve an already registered RootMessageHandler instance matching the
   * provided sessionId.
   *
   * @param {string} sessionId
   *     ID of the session the handler is used for.
   * @returns {RootMessageHandler}
   *     A RootMessageHandler instance.
   * @throws {Error}
   *     If no root MessageHandler can be found for the provided session id.
   */
  getRootMessageHandler(sessionId) {
    const rootMessageHandler = this.getExistingMessageHandler(
      sessionId,
      lazy.RootMessageHandler.type
    );
    if (!rootMessageHandler) {
      throw new Error(
        `Unable to find a root MessageHandler for session id ${sessionId}`
      );
    }
    return rootMessageHandler;
  }

  toString() {
    return `[object ${this.constructor.name}]`;
  }

  /**
   * Create a new MessageHandler instance.
   *
   * @param {string} sessionId
   *     ID of the session the handler will be used for.
   * @param {Array<SessionDataItem>=} sessionDataItems
   *     Optional array of session data items to be applied automatically to the
   *     MessageHandler.
   * @returns {MessageHandler}
   *     A new MessageHandler instance.
   */
  _createMessageHandler(sessionId, sessionDataItems) {
    const messageHandler = new this._messageHandlerClass(
      sessionId,
      this._context,
      this
    );

    messageHandler.on(
      "message-handler-destroyed",
      this._onMessageHandlerDestroyed
    );
    messageHandler.on("message-handler-event", this._onMessageHandlerEvent);

    messageHandler.initialize(sessionDataItems);

    this._messageHandlersMap.set(sessionId, messageHandler);

    lazy.logger.trace(
      `Created MessageHandler ${this._type} for session ${sessionId}`
    );

    return messageHandler;
  }

  // Event handlers

  _onMessageHandlerDestroyed(eventName, messageHandler) {
    messageHandler.off(
      "message-handler-destroyed",
      this._onMessageHandlerDestroyed
    );
    messageHandler.off("message-handler-event", this._onMessageHandlerEvent);
    this._messageHandlersMap.delete(messageHandler.sessionId);

    lazy.logger.trace(
      `Unregistered MessageHandler ${messageHandler.constructor.type} for session ${messageHandler.sessionId}`
    );
  }

  _onMessageHandlerEvent(eventName, messageHandlerEvent) {
    // The registry simply re-emits MessageHandler events so that consumers
    // don't have to attach listeners to individual MessageHandler instances.
    this.emit("message-handler-registry-event", messageHandlerEvent);
  }
}
PK
!<�ۂ�!!:chrome/remote/content/shared/messagehandler/Module.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "disabledExperimentalAPI", () => {
  return !Services.prefs.getBoolPref("remote.experimental.enabled");
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

export class Module {
  #messageHandler;

  /**
   * Create a new module instance.
   *
   * @param {MessageHandler} messageHandler
   *     The MessageHandler instance which owns this Module instance.
   */
  constructor(messageHandler) {
    this.#messageHandler = messageHandler;
  }

  /**
   * Clean-up the module instance.
   */
  destroy() {
    lazy.logger.warn(
      `Module ${this.constructor.name} is missing a destroy method`
    );
  }

  /**
   * Emit a message handler event.
   *
   * Such events should bubble up to the root of a MessageHandler network.
   *
   * @param {string} name
   *     Name of the event. Protocol level events should be of the
   *     form [module name].[event name].
   * @param {object} data
   *     The event's data.
   * @param {ContextInfo=} contextInfo
   *     The event's context info, see MessageHandler:emitEvent. Optional.
   */
  emitEvent(name, data, contextInfo) {
    this.messageHandler.emitEvent(name, data, contextInfo);
  }

  /**
   * Intercept an event and modify the payload.
   *
   * It's required to be implemented in windowglobal-in-root modules.
   *
   * @param {string} name
   *     Name of the event.
   * @param {object} _payload
   *    The event's payload.
   * @returns {object}
   *     The modified event payload.
   */
  interceptEvent(name, _payload) {
    throw new Error(
      `Could not intercept event ${name}, interceptEvent is not implemented in windowglobal-in-root module`
    );
  }

  /**
   * Assert if experimental commands are enabled.
   *
   * @param {string} methodName
   *     Name of the command.
   *
   * @throws {UnknownCommandError}
   *     If experimental commands are disabled.
   */
  assertExperimentalCommandsEnabled(methodName) {
    // TODO: 1778987. Move it to a BiDi specific place.
    if (lazy.disabledExperimentalAPI) {
      throw new lazy.error.UnknownCommandError(methodName);
    }
  }

  /**
   * Assert if experimental events are enabled.
   *
   * @param {string} moduleName
   *     Name of the module.
   *
   * @param {string} event
   *     Name of the event.
   *
   * @throws {InvalidArgumentError}
   *     If experimental events are disabled.
   */
  assertExperimentalEventsEnabled(moduleName, event) {
    // TODO: 1778987. Move it to a BiDi specific place.
    if (lazy.disabledExperimentalAPI) {
      throw new lazy.error.InvalidArgumentError(
        `Module ${moduleName} does not support event ${event}`
      );
    }
  }

  /**
   * Instance shortcut for supportsMethod to avoid reaching the constructor for
   * consumers which directly deal with an instance.
   */
  supportsMethod(methodName) {
    return this.constructor.supportsMethod(methodName);
  }

  get messageHandler() {
    return this.#messageHandler;
  }

  static get supportedEvents() {
    return [];
  }

  static supportsEvent(event) {
    return this.supportedEvents.includes(event);
  }

  static supportsMethod(methodName) {
    return typeof this.prototype[methodName] === "function";
  }
}
PK
!<���~''?chrome/remote/content/shared/messagehandler/ModuleCache.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  getMessageHandlerClass:
    "chrome://remote/content/shared/messagehandler/MessageHandlerRegistry.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  RootMessageHandler:
    "chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs",
});

const protocols = {
  bidi: {},
  test: {},
};
// eslint-disable-next-line mozilla/lazy-getter-object-name
ChromeUtils.defineESModuleGetters(protocols.bidi, {
  // Additional protocols might use a different registry for their modules,
  // in which case this will no longer be a constant but will instead depend on
  // the protocol owning the MessageHandler. See Bug 1722464.
  modules:
    "chrome://remote/content/webdriver-bidi/modules/ModuleRegistry.sys.mjs",
});
// eslint-disable-next-line mozilla/lazy-getter-object-name
ChromeUtils.defineESModuleGetters(protocols.test, {
  modules:
    "chrome://mochitests/content/browser/remote/shared/messagehandler/test/browser/resources/modules/ModuleRegistry.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

/**
 * ModuleCache instances are dedicated to lazily create and cache the instances
 * of all the modules related to a specific MessageHandler instance.
 *
 * ModuleCache also implements the logic to resolve the path to the file for a
 * given module, which depends both on the current MessageHandler context and on
 * the expected destination.
 *
 * In order to implement module logic in any context, separate module files
 * should be created for each situation. For instance, for a given module,
 * - ${MODULES_FOLDER}/root/{ModuleName}.sys.mjs contains the implementation for
 *   commands intended for the destination ROOT, and will be created for a ROOT
 *   MessageHandler only. Typically, they will run in the parent process.
 * - ${MODULES_FOLDER}/windowglobal/{ModuleName}.sys.mjs contains the implementation
 *   for commands intended for a WINDOW_GLOBAL destination, and will be created
 *   for a WINDOW_GLOBAL MessageHandler only. Those will usually run in a
 *   content process.
 * - ${MODULES_FOLDER}/windowglobal-in-root/{ModuleName}.sys.mjs also handles
 *   commands intended for a WINDOW_GLOBAL destination, but they will be created
 *   for the ROOT MessageHandler and will run in the parent process. This can be
 *   useful if some code has to be executed in the parent process, even though
 *   the final destination is a WINDOW_GLOBAL.
 * - And so on, as more MessageHandler types get added, more combinations will
 *   follow based on the same pattern:
 *   - {contextName}/{ModuleName}.sys.mjs
 *   - or {destinationType}-in-{currentType}/{ModuleName}.sys.mjs
 *
 * All those implementations are optional. If a module cannot be found, based on
 * the logic detailed above, the MessageHandler will assume that the command
 * should simply be forwarded to the next layer of the network.
 */
export class ModuleCache {
  #messageHandler;
  #messageHandlerType;
  #modules;
  #protocol;

  /*
   * @param {MessageHandler} messageHandler
   *     The MessageHandler instance which owns this ModuleCache instance.
   */
  constructor(messageHandler) {
    this.#messageHandler = messageHandler;
    this.#messageHandlerType = messageHandler.constructor.type;

    // Map of absolute module paths to module instances.
    this.#modules = new Map();

    // Use the module class from the WebDriverBiDi ModuleRegistry if we
    // are not using test modules.
    this.#protocol = Services.prefs.getBoolPref(
      "remote.messagehandler.modulecache.useBrowserTestRoot",
      false
    )
      ? protocols.test
      : protocols.bidi;
  }

  /**
   * Destroy all instantiated modules.
   */
  destroy() {
    this.#modules.forEach(module => module?.destroy());
  }

  /**
   * Retrieve all module classes matching the provided module name to reach the
   * provided destination, from the current context.
   *
   * This corresponds to the path a command can take to reach its destination.
   * A command's method must be implemented in one of the classes returned by
   * getAllModuleClasses in order to be successfully handled.
   *
   * @param {string} moduleName
   *     The name of the module.
   * @param {Destination} destination
   *     The destination.
   * @returns {Array<class<Module>|null>}
   *     An array of Module classes.
   */
  getAllModuleClasses(moduleName, destination) {
    const destinationType = destination.type;
    const classes = [
      this.#getModuleClass(
        moduleName,
        this.#messageHandlerType,
        destinationType
      ),
    ];

    // Bug 1733242: Extend the implementation of this method to handle workers.
    // It assumes layers have at most one level of nesting, for instance
    // "root -> windowglobal", but it wouldn't work for something such as
    // "root -> windowglobal -> worker".
    if (destinationType !== this.#messageHandlerType) {
      classes.push(
        this.#getModuleClass(moduleName, destinationType, destinationType)
      );
    }

    return classes.filter(cls => !!cls);
  }

  /**
   * Get a module instance corresponding to the provided moduleName and
   * destination. If no existing module can be found in the cache, ModuleCache
   * will attempt to import the module file and create a new instance, which
   * will then be cached and returned for subsequent calls.
   *
   * @param {string} moduleName
   *     The name of the module which should implement the command.
   * @param {CommandDestination} destination
   *     The destination of the command for which we need to instantiate a
   *     module. See MessageHandler.sys.mjs for the CommandDestination typedef.
   * @returns {object=}
   *     A module instance corresponding to the provided moduleName and
   *     destination, or null if it could not be instantiated.
   */
  getModuleInstance(moduleName, destination) {
    const key = `${moduleName}-${destination.type}`;

    if (this.#modules.has(key)) {
      // If there is already a cached instance (potentially null) for the
      // module name + destination type pair, return it.
      return this.#modules.get(key);
    }

    const ModuleClass = this.#getModuleClass(
      moduleName,
      this.#messageHandlerType,
      destination.type
    );

    let module = null;
    if (ModuleClass) {
      module = new ModuleClass(this.#messageHandler);
    }

    this.#modules.set(key, module);
    return module;
  }

  /**
   * Check if the given module exists for the destination.
   *
   * @param {string} moduleName
   *     The name of the module.
   * @param {Destination} destination
   *     The destination.
   * @returns {boolean}
   *     True if the module exists.
   */
  hasModuleClass(moduleName, destination) {
    const classes = this.getAllModuleClasses(moduleName, destination);
    return !!classes.length;
  }

  toString() {
    return `[object ${this.constructor.name} ${this.#messageHandler.name}]`;
  }

  /**
   * Retrieve the module class matching the provided module name and folder.
   *
   * @param {string} moduleName
   *     The name of the module to get the class for.
   * @param {string} originType
   *     The MessageHandler type from where the command comes.
   * @param {string} destinationType
   *     The MessageHandler type where the command should go to.
   * @returns {Class=}
   *     The class corresponding to the module name and folder, null if no match
   *     was found.
   * @throws {Error}
   *     If the provided module folder is unexpected.
   */
  #getModuleClass = function (moduleName, originType, destinationType) {
    if (
      destinationType === lazy.RootMessageHandler.type &&
      originType !== destinationType
    ) {
      // If we are trying to reach the root layer from a lower layer, no module
      // class should attempt to handle the command in the current layer and
      // the command should be forwarded unconditionally.
      return null;
    }

    const moduleFolder = this.#getModuleFolder(originType, destinationType);
    if (!this.#protocol.modules[moduleFolder]) {
      throw new Error(
        `Invalid module folder "${moduleFolder}", expected one of "${Object.keys(
          this.#protocol.modules
        )}"`
      );
    }

    let moduleClass = null;
    if (this.#protocol.modules[moduleFolder][moduleName]) {
      moduleClass = this.#protocol.modules[moduleFolder][moduleName];
    }

    // Module hit/miss logs generate a lot of spam. Only log if verbose is true.
    //
    // Note: Due to https://bugzilla.mozilla.org/show_bug.cgi?id=1828395
    // verbose is currently always false if the log level is trace.
    // If those logs are needed before the bug is fixed, temporarily remove the
    // condition.
    if (lazy.Log.verbose) {
      if (moduleClass) {
        lazy.logger.trace(
          `Module ${moduleFolder}/${moduleName}.sys.mjs found for ${destinationType}`
        );
      } else {
        lazy.logger.trace(
          `Module ${moduleFolder}/${moduleName}.sys.mjs not found for ${destinationType}`
        );
      }
    }

    return moduleClass;
  };

  #getModuleFolder(originType, destinationType) {
    const originPath = lazy.getMessageHandlerClass(originType).modulePath;
    if (originType === destinationType) {
      // If the command is targeting the current type, the module is expected to
      // be in eg "windowglobal/${moduleName}.sys.mjs".
      return originPath;
    }

    // If the command is targeting another type, the module is expected to
    // be in a composed folder eg "windowglobal-in-root/${moduleName}.sys.mjs".
    const destinationPath =
      lazy.getMessageHandlerClass(destinationType).modulePath;
    return `${destinationPath}-in-${originPath}`;
  }
}
PK
!<����Fchrome/remote/content/shared/messagehandler/RootMessageHandler.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { MessageHandler } from "chrome://remote/content/shared/messagehandler/MessageHandler.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  NavigationManager: "chrome://remote/content/shared/NavigationManager.sys.mjs",
  RootTransport:
    "chrome://remote/content/shared/messagehandler/transports/RootTransport.sys.mjs",
  SessionData:
    "chrome://remote/content/shared/messagehandler/sessiondata/SessionData.sys.mjs",
  SessionDataMethod:
    "chrome://remote/content/shared/messagehandler/sessiondata/SessionData.sys.mjs",
  WindowGlobalMessageHandler:
    "chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs",
});

/**
 * A RootMessageHandler is the root node of a MessageHandler network. It lives
 * in the parent process. It can forward commands to MessageHandlers in other
 * layers (at the moment WindowGlobalMessageHandlers in content processes).
 */
export class RootMessageHandler extends MessageHandler {
  #navigationManager;
  #realms;
  #rootTransport;
  #sessionData;

  /**
   * Returns the RootMessageHandler module path.
   *
   * @returns {string}
   */
  static get modulePath() {
    return "root";
  }

  /**
   * Returns the RootMessageHandler type.
   *
   * @returns {string}
   */
  static get type() {
    return "ROOT";
  }

  /**
   * The ROOT MessageHandler is unique for a given MessageHandler network
   * (ie for a given sessionId). Reuse the type as context id here.
   */
  static getIdFromContext() {
    return RootMessageHandler.type;
  }

  /**
   * Create a new RootMessageHandler instance.
   *
   * @param {string} sessionId
   *     ID of the session the handler is used for.
   */
  constructor(sessionId) {
    super(sessionId, null);

    this.#rootTransport = new lazy.RootTransport(this);
    this.#sessionData = new lazy.SessionData(this);
    this.#navigationManager = new lazy.NavigationManager();
    this.#navigationManager.startMonitoring();

    // Map with inner window ids as keys, and sets of realm ids, assosiated with
    // this window as values.
    this.#realms = new Map();
    // In the general case, we don't get notified that realms got destroyed,
    // because there is no communication between content and parent process at this moment,
    // so we have to listen to the this notification to clean up the internal
    // map and trigger the events.
    Services.obs.addObserver(this, "window-global-destroyed");
  }

  get navigationManager() {
    return this.#navigationManager;
  }

  get realms() {
    return this.#realms;
  }

  get sessionData() {
    return this.#sessionData;
  }

  destroy() {
    this.#sessionData.destroy();
    this.#navigationManager.destroy();

    Services.obs.removeObserver(this, "window-global-destroyed");
    this.#realms = null;

    super.destroy();
  }

  /**
   * Add new session data items of a given module, category and
   * contextDescriptor.
   *
   * Forwards the call to the SessionData instance owned by this
   * RootMessageHandler and propagates the information via a command to existing
   * MessageHandlers.
   */
  addSessionDataItem(sessionData = {}) {
    sessionData.method = lazy.SessionDataMethod.Add;
    return this.updateSessionData([sessionData]);
  }

  emitEvent(name, eventPayload, contextInfo) {
    // Intercept realm created and destroyed events to update internal map.
    if (name === "realm-created") {
      this.#onRealmCreated(eventPayload);
    }
    // We receive this events in the case of moving the page to BFCache.
    if (name === "windowglobal-pagehide") {
      this.#cleanUpRealmsForWindow(
        eventPayload.innerWindowId,
        eventPayload.context
      );
    }

    super.emitEvent(name, eventPayload, contextInfo);
  }

  /**
   * Emit a public protocol event. This event will be sent over to the client.
   *
   * @param {string} name
   *     Name of the event. Protocol level events should be of the
   *     form [module name].[event name].
   * @param {object} data
   *     The event's data.
   */
  emitProtocolEvent(name, data) {
    this.emit("message-handler-protocol-event", {
      name,
      data,
      sessionId: this.sessionId,
    });
  }

  /**
   * Forward the provided command to WINDOW_GLOBAL MessageHandlers via the
   * RootTransport.
   *
   * @param {Command} command
   *     The command to forward. See type definition in MessageHandler.js
   * @returns {Promise}
   *     Returns a promise that resolves with the result of the command.
   */
  forwardCommand(command) {
    switch (command.destination.type) {
      case lazy.WindowGlobalMessageHandler.type:
        return this.#rootTransport.forwardCommand(command);
      default:
        throw new Error(
          `Cannot forward command to "${command.destination.type}" from "${this.constructor.type}".`
        );
    }
  }

  matchesContext() {
    return true;
  }

  observe(subject, topic) {
    if (topic !== "window-global-destroyed") {
      return;
    }

    this.#cleanUpRealmsForWindow(
      subject.innerWindowId,
      subject.browsingContext
    );
  }

  /**
   * Remove session data items of a given module, category and
   * contextDescriptor.
   *
   * Forwards the call to the SessionData instance owned by this
   * RootMessageHandler and propagates the information via a command to existing
   * MessageHandlers.
   */
  removeSessionDataItem(sessionData = {}) {
    sessionData.method = lazy.SessionDataMethod.Remove;
    return this.updateSessionData([sessionData]);
  }

  /**
   * Update session data items of a given module, category and
   * contextDescriptor.
   *
   * Forwards the call to the SessionData instance owned by this
   * RootMessageHandler.
   */
  async updateSessionData(sessionData = []) {
    await this.#sessionData.updateSessionData(sessionData);
  }

  #cleanUpRealmsForWindow(innerWindowId, context) {
    const realms = this.#realms.get(innerWindowId);

    if (!realms) {
      return;
    }

    realms.forEach(realm => {
      this.#realms.get(innerWindowId).delete(realm);

      this.emitEvent("realm-destroyed", {
        context,
        realm,
      });
    });

    this.#realms.delete(innerWindowId);
  }

  #onRealmCreated = data => {
    const { innerWindowId, realmInfo } = data;

    if (!this.#realms.has(innerWindowId)) {
      this.#realms.set(innerWindowId, new Set());
    }

    this.#realms.get(innerWindowId).add(realmInfo.realm);
  };
}
PK
!<H6��((Nchrome/remote/content/shared/messagehandler/RootMessageHandlerRegistry.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { MessageHandlerRegistry } from "chrome://remote/content/shared/messagehandler/MessageHandlerRegistry.sys.mjs";

import { RootMessageHandler } from "chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs";

/**
 * In the parent process, only one Root MessageHandlerRegistry should ever be
 * created. All consumers can safely use this singleton to retrieve the Root
 * registry and from there either create or retrieve Root MessageHandler
 * instances for a specific session.
 */
export var RootMessageHandlerRegistry = new MessageHandlerRegistry(
  RootMessageHandler.type
);
PK
!<���Nchrome/remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import {
  ContextDescriptorType,
  MessageHandler,
} from "chrome://remote/content/shared/messagehandler/MessageHandler.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  getMessageHandlerFrameChildActor:
    "chrome://remote/content/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameChild.sys.mjs",
  RootMessageHandler:
    "chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs",
  WindowRealm: "chrome://remote/content/shared/Realm.sys.mjs",
});

/**
 * A WindowGlobalMessageHandler is dedicated to debugging a single window
 * global. It follows the lifecycle of the corresponding window global and will
 * therefore not survive any navigation. This MessageHandler cannot forward
 * commands further to other MessageHandlers and represents a leaf node in a
 * MessageHandler network.
 */
export class WindowGlobalMessageHandler extends MessageHandler {
  #innerWindowId;
  #realms;

  constructor() {
    super(...arguments);

    this.#innerWindowId = this.context.window.windowGlobalChild.innerWindowId;

    // Maps sandbox names to instances of window realms.
    this.#realms = new Map();
  }

  initialize(sessionDataItems) {
    // Create the default realm, it is mapped to an empty string sandbox name.
    this.#realms.set("", this.#createRealm());

    // This method, even though being async, is not awaited on purpose,
    // since for now the sessionDataItems are passed in response to an event in a for loop.
    this.#applyInitialSessionDataItems(sessionDataItems);

    // With the session data applied the handler is now ready to be used.
    this.emitEvent("window-global-handler-created", {
      contextId: this.contextId,
      innerWindowId: this.#innerWindowId,
    });
  }

  destroy() {
    for (const realm of this.#realms.values()) {
      realm.destroy();
    }
    this.emitEvent("windowglobal-pagehide", {
      context: this.context,
      innerWindowId: this.innerWindowId,
    });
    this.#realms = null;

    super.destroy();
  }

  /**
   * Returns the WindowGlobalMessageHandler module path.
   *
   * @returns {string}
   */
  static get modulePath() {
    return "windowglobal";
  }

  /**
   * Returns the WindowGlobalMessageHandler type.
   *
   * @returns {string}
   */
  static get type() {
    return "WINDOW_GLOBAL";
  }

  /**
   * For WINDOW_GLOBAL MessageHandlers, `context` is a BrowsingContext,
   * and BrowsingContext.id can be used as the context id.
   *
   * @param {BrowsingContext} context
   *     WindowGlobalMessageHandler contexts are expected to be
   *     BrowsingContexts.
   * @returns {string}
   *     The browsing context id.
   */
  static getIdFromContext(context) {
    return context.id;
  }

  get innerWindowId() {
    return this.#innerWindowId;
  }

  get realms() {
    return this.#realms;
  }

  get window() {
    return this.context.window;
  }

  #createRealm(sandboxName = null) {
    const realm = new lazy.WindowRealm(this.context.window, {
      sandboxName,
    });

    this.emitEvent("realm-created", {
      realmInfo: realm.getInfo(),
      innerWindowId: this.innerWindowId,
    });

    return realm;
  }

  #getRealmFromSandboxName(sandboxName = null) {
    if (sandboxName === null || sandboxName === "") {
      return this.#realms.get("");
    }

    if (this.#realms.has(sandboxName)) {
      return this.#realms.get(sandboxName);
    }

    const realm = this.#createRealm(sandboxName);

    this.#realms.set(sandboxName, realm);

    return realm;
  }

  async #applyInitialSessionDataItems(sessionDataItems) {
    if (!Array.isArray(sessionDataItems)) {
      return;
    }

    const destination = {
      type: WindowGlobalMessageHandler.type,
    };

    // Create a Map with the structure moduleName -> category -> relevant session data items.
    const structuredUpdates = new Map();
    for (const sessionDataItem of sessionDataItems) {
      const { category, contextDescriptor, moduleName } = sessionDataItem;

      if (!this.matchesContext(contextDescriptor)) {
        continue;
      }
      if (!structuredUpdates.has(moduleName)) {
        // Skip session data item if the module is not present
        // for the destination.
        if (!this.moduleCache.hasModuleClass(moduleName, destination)) {
          continue;
        }
        structuredUpdates.set(moduleName, new Map());
      }

      if (!structuredUpdates.get(moduleName).has(category)) {
        structuredUpdates.get(moduleName).set(category, new Set());
      }

      structuredUpdates.get(moduleName).get(category).add(sessionDataItem);
    }

    const sessionDataPromises = [];

    for (const [moduleName, categories] of structuredUpdates.entries()) {
      for (const [category, relevantSessionData] of categories.entries()) {
        sessionDataPromises.push(
          this.handleCommand({
            moduleName,
            commandName: "_applySessionData",
            params: {
              category,
              sessionData: Array.from(relevantSessionData),
            },
            destination,
          })
        );
      }
    }

    await Promise.all(sessionDataPromises);
  }

  forwardCommand(command) {
    switch (command.destination.type) {
      case lazy.RootMessageHandler.type:
        return lazy
          .getMessageHandlerFrameChildActor(this)
          .sendCommand(command, this.sessionId);
      default:
        throw new Error(
          `Cannot forward command to "${command.destination.type}" from "${this.constructor.type}".`
        );
    }
  }

  /**
   * If <var>realmId</var> is null or not provided get the realm for
   * a given <var>sandboxName</var>, otherwise find the realm
   * in the cache with the realm id equal given <var>realmId</var>.
   *
   * @param {object} options
   * @param {string|null=} options.realmId
   *     The realm id.
   * @param {string=} options.sandboxName
   *     The name of sandbox
   *
   * @returns {Realm}
   *     The realm object.
   */
  getRealm(options = {}) {
    const { realmId = null, sandboxName } = options;
    if (realmId === null) {
      return this.#getRealmFromSandboxName(sandboxName);
    }

    const realm = Array.from(this.#realms.values()).find(
      realm => realm.id === realmId
    );

    if (realm) {
      return realm;
    }

    throw new lazy.error.NoSuchFrameError(`Realm with id ${realmId} not found`);
  }

  matchesContext(contextDescriptor) {
    return (
      contextDescriptor.type === ContextDescriptorType.All ||
      (contextDescriptor.type === ContextDescriptorType.TopBrowsingContext &&
        contextDescriptor.id === this.context.browserId)
    );
  }

  /**
   * Send a command to the root MessageHandler.
   *
   * @param {Command} command
   *     The command to send to the root MessageHandler.
   * @returns {Promise}
   *     A promise which resolves with the return value of the command.
   */
  sendRootCommand(command) {
    return this.handleCommand({
      ...command,
      destination: {
        type: lazy.RootMessageHandler.type,
      },
    });
  }
}
PK
!<��|#2#2Kchrome/remote/content/shared/messagehandler/sessiondata/SessionData.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ContextDescriptorType:
    "chrome://remote/content/shared/messagehandler/MessageHandler.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  RootMessageHandler:
    "chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs",
  WindowGlobalMessageHandler:
    "chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

/**
 * @typedef {string} SessionDataCategory
 */

/**
 * Enum of session data categories.
 *
 * @readonly
 * @enum {SessionDataCategory}
 */
export const SessionDataCategory = {
  Event: "event",
  PreloadScript: "preload-script",
};

/**
 * @typedef {string} SessionDataMethod
 */

/**
 * Enum of session data methods.
 *
 * @readonly
 * @enum {SessionDataMethod}
 */
export const SessionDataMethod = {
  Add: "add",
  Remove: "remove",
};

export const SESSION_DATA_SHARED_DATA_KEY = "MessageHandlerSessionData";

// This is a map from session id to session data, which will be persisted and
// propagated to all processes using Services' sharedData.
// We have to store this as a unique object under a unique shared data key
// because new MessageHandlers in other processes will need to access this data
// without any notion of a specific session.
// This is a singleton.
const sessionDataMap = new Map();

/**
 * @typedef {object} SessionDataItem
 * @property {string} moduleName
 *     The name of the module responsible for this data item.
 * @property {SessionDataCategory} category
 *     The category of data. The supported categories depend on the module.
 * @property {(string|number|boolean)} value
 *     Value of the session data item.
 * @property {ContextDescriptor} contextDescriptor
 *     ContextDescriptor to which this session data applies.
 */

/**
 * @typedef SessionDataItemUpdate
 * @property {SessionDataMethod} method
 *     The way sessionData is updated.
 * @property {string} moduleName
 *     The name of the module responsible for this data item.
 * @property {SessionDataCategory} category
 *     The category of data. The supported categories depend on the module.
 * @property {Array<(string|number|boolean)>} values
 *     Values of the session data item update.
 * @property {ContextDescriptor} contextDescriptor
 *     ContextDescriptor to which this session data applies.
 */

/**
 * SessionData provides APIs to read and write the session data for a specific
 * ROOT message handler. It holds the session data as a property and acts as the
 * source of truth for this session data.
 *
 * The session data of a given message handler network should contain all the
 * information that might be needed to setup new contexts, for instance a list
 * of subscribed events, a list of breakpoints etc.
 *
 * The actual session data is an array of SessionDataItems. Example below:
 * ```
 * data: [
 *   {
 *     moduleName: "log",
 *     category: "event",
 *     value: "log.entryAdded",
 *     contextDescriptor: { type: "all" }
 *   },
 *   {
 *     moduleName: "browsingContext",
 *     category: "event",
 *     value: "browsingContext.contextCreated",
 *     contextDescriptor: { type: "browser-element", id: "7"}
 *   },
 *   {
 *     moduleName: "browsingContext",
 *     category: "event",
 *     value: "browsingContext.contextCreated",
 *     contextDescriptor: { type: "browser-element", id: "12"}
 *   },
 * ]
 * ```
 *
 * The session data will be persisted using Services.ppmm.sharedData, so that
 * new contexts living in different processes can also access the information
 * during their startup.
 *
 * This class should only be used from a ROOT MessageHandler, or from modules
 * owned by a ROOT MessageHandler. Other MessageHandlers should rely on
 * SessionDataReader's readSessionData to get read-only access to session data.
 *
 */
export class SessionData {
  constructor(messageHandler) {
    if (messageHandler.constructor.type != lazy.RootMessageHandler.type) {
      throw new Error(
        "SessionData should only be used from a ROOT MessageHandler"
      );
    }

    this._messageHandler = messageHandler;

    /*
     * The actual data for this session. This is an array of SessionDataItems.
     */
    this._data = [];
  }

  destroy() {
    // Update the sessionDataMap singleton.
    sessionDataMap.delete(this._messageHandler.sessionId);

    // Update sharedData and flush to force consistency.
    Services.ppmm.sharedData.set(SESSION_DATA_SHARED_DATA_KEY, sessionDataMap);
    Services.ppmm.sharedData.flush();
  }

  /**
   * Update session data items of a given module, category and
   * contextDescriptor.
   *
   * A SessionDataItem will be added or removed for each value of each update
   * in the provided array.
   *
   * Attempting to add a duplicate SessionDataItem or to remove an unknown
   * SessionDataItem will be silently skipped (no-op).
   *
   * The data will be persisted across processes at the end of this method.
   *
   * @param {Array<SessionDataItemUpdate>} sessionDataItemUpdates
   *     Array of session data item updates.
   *
   * @returns {Array<SessionDataItemUpdate>}
   *     The subset of session data item updates which want to be applied.
   */
  applySessionData(sessionDataItemUpdates = []) {
    // The subset of session data item updates, which are cleaned up from
    // duplicates and unknown items.
    let updates = [];
    for (const sessionDataItemUpdate of sessionDataItemUpdates) {
      const { category, contextDescriptor, method, moduleName, values } =
        sessionDataItemUpdate;
      const updatedValues = [];
      for (const value of values) {
        const item = { moduleName, category, contextDescriptor, value };

        if (method === SessionDataMethod.Add) {
          const hasItem = this._findIndex(item) != -1;

          if (!hasItem) {
            this._data.push(item);
            updatedValues.push(value);
          } else {
            lazy.logger.warn(
              `Duplicated session data item was not added: ${JSON.stringify(
                item
              )}`
            );
          }
        } else {
          const itemIndex = this._findIndex(item);

          if (itemIndex != -1) {
            // The item was found in the session data, remove it.
            this._data.splice(itemIndex, 1);
            updatedValues.push(value);
          } else {
            lazy.logger.warn(
              `Missing session data item was not removed: ${JSON.stringify(
                item
              )}`
            );
          }
        }
      }

      if (updatedValues.length) {
        updates.push({
          ...sessionDataItemUpdate,
          values: updatedValues,
        });
      }
    }
    // Persist the sessionDataMap.
    this._persist();

    return updates;
  }

  /**
   * Retrieve the SessionDataItems for a given module and type.
   *
   * @param {string} moduleName
   *     The name of the module responsible for this data item.
   * @param {string} category
   *     The session data category.
   * @param {ContextDescriptor=} contextDescriptor
   *     Optional context descriptor, to retrieve only session data items added
   *     for a specific context descriptor.
   * @returns {Array<SessionDataItem>}
   *     Array of SessionDataItems for the provided module and type.
   */
  getSessionData(moduleName, category, contextDescriptor) {
    return this._data.filter(
      item =>
        item.moduleName === moduleName &&
        item.category === category &&
        (!contextDescriptor ||
          this._isSameContextDescriptor(
            item.contextDescriptor,
            contextDescriptor
          ))
    );
  }

  /**
   * Update session data items of a given module, category and
   * contextDescriptor and propagate the information
   * via a command to existing MessageHandlers.
   *
   * @param {Array<SessionDataItemUpdate>} sessionDataItemUpdates
   *     Array of session data item updates.
   */
  async updateSessionData(sessionDataItemUpdates = []) {
    const updates = this.applySessionData(sessionDataItemUpdates);

    if (!updates.length) {
      // Avoid unnecessary broadcast if no items were updated.
      return;
    }

    // Create a Map with the structure moduleName -> category -> list of descriptors.
    const structuredUpdates = new Map();
    for (const { moduleName, category, contextDescriptor } of updates) {
      if (!structuredUpdates.has(moduleName)) {
        structuredUpdates.set(moduleName, new Map());
      }
      if (!structuredUpdates.get(moduleName).has(category)) {
        structuredUpdates.get(moduleName).set(category, new Set());
      }
      const descriptors = structuredUpdates.get(moduleName).get(category);
      // If there is at least one update for all contexts,
      // keep only this descriptor in the list of descriptors
      if (contextDescriptor.type === lazy.ContextDescriptorType.All) {
        structuredUpdates
          .get(moduleName)
          .set(category, new Set([contextDescriptor]));
      }
      // Add an individual descriptor if there is no descriptor for all contexts.
      else if (
        descriptors.size !== 1 ||
        Array.from(descriptors)[0]?.type !== lazy.ContextDescriptorType.All
      ) {
        descriptors.add(contextDescriptor);
      }
    }

    const rootDestination = {
      type: lazy.RootMessageHandler.type,
    };
    const sessionDataPromises = [];

    for (const [moduleName, categories] of structuredUpdates.entries()) {
      for (const [category, contextDescriptors] of categories.entries()) {
        // Find sessionData for the category and the moduleName.
        const relevantSessionData = this._data.filter(
          item => item.category == category && item.moduleName === moduleName
        );
        for (const contextDescriptor of contextDescriptors.values()) {
          const windowGlobalDestination = {
            type: lazy.WindowGlobalMessageHandler.type,
            contextDescriptor,
          };

          for (const destination of [
            windowGlobalDestination,
            rootDestination,
          ]) {
            // Only apply session data if the module is present for the destination.
            if (
              this._messageHandler.supportsCommand(
                moduleName,
                "_applySessionData",
                destination
              )
            ) {
              sessionDataPromises.push(
                this._messageHandler
                  .handleCommand({
                    moduleName,
                    commandName: "_applySessionData",
                    params: {
                      sessionData: relevantSessionData,
                      category,
                      contextDescriptor,
                    },
                    destination,
                  })
                  ?.catch(reason =>
                    lazy.logger.error(
                      `_applySessionData for module: ${moduleName} failed, reason: ${reason}`
                    )
                  )
              );
            }
          }
        }
      }
    }

    await Promise.allSettled(sessionDataPromises);
  }

  _isSameItem(item1, item2) {
    const descriptor1 = item1.contextDescriptor;
    const descriptor2 = item2.contextDescriptor;

    return (
      item1.moduleName === item2.moduleName &&
      item1.category === item2.category &&
      this._isSameContextDescriptor(descriptor1, descriptor2) &&
      this._isSameValue(item1.category, item1.value, item2.value)
    );
  }

  _isSameContextDescriptor(contextDescriptor1, contextDescriptor2) {
    if (contextDescriptor1.type === lazy.ContextDescriptorType.All) {
      // Ignore the id for type "all" since we made the id optional for this type.
      return contextDescriptor1.type === contextDescriptor2.type;
    }

    return (
      contextDescriptor1.type === contextDescriptor2.type &&
      contextDescriptor1.id === contextDescriptor2.id
    );
  }

  _isSameValue(category, value1, value2) {
    if (category === SessionDataCategory.PreloadScript) {
      return value1.script === value2.script;
    }

    return value1 === value2;
  }

  _findIndex(item) {
    return this._data.findIndex(_item => this._isSameItem(item, _item));
  }

  _persist() {
    // Update the sessionDataMap singleton.
    sessionDataMap.set(this._messageHandler.sessionId, this._data);

    // Update sharedData and flush to force consistency.
    Services.ppmm.sharedData.set(SESSION_DATA_SHARED_DATA_KEY, sessionDataMap);
    Services.ppmm.sharedData.flush();
  }
}
PK
!<�ǫ��Qchrome/remote/content/shared/messagehandler/sessiondata/SessionDataReader.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  SESSION_DATA_SHARED_DATA_KEY:
    "chrome://remote/content/shared/messagehandler/sessiondata/SessionData.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "sharedData", () => {
  const isInParent =
    Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;

  return isInParent ? Services.ppmm.sharedData : Services.cpmm.sharedData;
});

/**
 * Returns a snapshot of the session data map, which is cloned from the
 * sessionDataMap singleton of SessionData.sys.mjs.
 *
 *  @returns {Map.<string, Array<SessionDataItem>>}
 *     Map of session id to arrays of SessionDataItems.
 */
export const readSessionData = () =>
  lazy.sharedData.get(lazy.SESSION_DATA_SHARED_DATA_KEY) || new Map();
PK
!<��d�HHSchrome/remote/content/shared/messagehandler/transports/BrowsingContextUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

function isExtensionContext(browsingContext) {
  let principal;
  if (CanonicalBrowsingContext.isInstance(browsingContext)) {
    principal = browsingContext.currentWindowGlobal.documentPrincipal;
  } else {
    principal = browsingContext.window.document.nodePrincipal;
  }

  // In practice, note that the principal will never be an expanded principal.
  // The are only used for content scripts executed in a Sandbox, and do not
  // have a browsing context on their own.
  // But we still use this flag because there is no isAddonPrincipal flag.
  return principal.isAddonOrExpandedAddonPrincipal;
}

function isParentProcess(browsingContext) {
  if (CanonicalBrowsingContext.isInstance(browsingContext)) {
    return browsingContext.currentWindowGlobal.osPid === -1;
  }

  // If `browsingContext` is not a `CanonicalBrowsingContext`, then we are
  // necessarily in a content process page.
  return false;
}

/**
 * Check if the given browsing context is valid for the message handler
 * to use.
 *
 * @param {BrowsingContext} browsingContext
 *     The browsing context to check.
 * @param {object=} options
 * @param {string=} options.browserId
 *    The id of the browser to filter the browsing contexts by (optional).
 * @returns {boolean}
 *     True if the browsing context is valid, false otherwise.
 */
export function isBrowsingContextCompatible(browsingContext, options = {}) {
  const { browserId } = options;

  // If a browserId was provided, skip browsing contexts which are not
  // associated with this browserId.
  if (browserId !== undefined && browsingContext.browserId !== browserId) {
    return false;
  }

  // Skip:
  // - extension contexts until we support debugging webextensions, see Bug 1755014.
  // - privileged contexts until we support debugging Chrome context, see Bug 1713440.
  return (
    !isExtensionContext(browsingContext) && !isParentProcess(browsingContext)
  );
}
PK
!<��NLchrome/remote/content/shared/messagehandler/transports/RootTransport.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ContextDescriptorType:
    "chrome://remote/content/shared/messagehandler/MessageHandler.sys.mjs",
  isBrowsingContextCompatible:
    "chrome://remote/content/shared/messagehandler/transports/BrowsingContextUtils.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  MessageHandlerFrameActor:
    "chrome://remote/content/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameActor.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

const MAX_RETRY_ATTEMPTS = 10;

/**
 * RootTransport is intended to be used from a ROOT MessageHandler to communicate
 * with WINDOW_GLOBAL MessageHandlers via the MessageHandlerFrame JSWindow
 * actors.
 */
export class RootTransport {
  /**
   * @param {MessageHandler} messageHandler
   *     The MessageHandler instance which owns this RootTransport instance.
   */
  constructor(messageHandler) {
    this._messageHandler = messageHandler;

    // RootTransport will rely on the MessageHandlerFrame JSWindow actors.
    // Make sure they are registered when instanciating a RootTransport.
    lazy.MessageHandlerFrameActor.register();
  }

  /**
   * Forward the provided command to WINDOW_GLOBAL MessageHandlers via the
   * MessageHandlerFrame actors.
   *
   * @param {Command} command
   *     The command to forward. See type definition in MessageHandler.js
   * @returns {Promise}
   *     Returns a promise that resolves with the result of the command after
   *     being processed by WINDOW_GLOBAL MessageHandlers.
   */
  forwardCommand(command) {
    if (command.destination.id && command.destination.contextDescriptor) {
      throw new Error(
        "Invalid command destination with both 'id' and 'contextDescriptor' properties"
      );
    }

    // With an id given forward the command to only this specific destination.
    if (command.destination.id) {
      const browsingContext = BrowsingContext.get(command.destination.id);
      if (!browsingContext) {
        throw new Error(
          "Unable to find a BrowsingContext for id " + command.destination.id
        );
      }
      return this._sendCommandToBrowsingContext(command, browsingContext);
    }

    // ... otherwise broadcast to destinations matching the contextDescriptor.
    if (command.destination.contextDescriptor) {
      return this._broadcastCommand(command);
    }

    throw new Error(
      "Unrecognized command destination, missing 'id' or 'contextDescriptor' properties"
    );
  }

  _broadcastCommand(command) {
    const { contextDescriptor } = command.destination;
    const browsingContexts =
      this._getBrowsingContextsForDescriptor(contextDescriptor);

    return Promise.all(
      browsingContexts.map(async browsingContext => {
        try {
          return await this._sendCommandToBrowsingContext(
            command,
            browsingContext
          );
        } catch (e) {
          console.error(
            `Failed to broadcast a command to browsingContext ${browsingContext.id}`,
            e
          );
          return null;
        }
      })
    );
  }

  async _sendCommandToBrowsingContext(command, browsingContext) {
    const name = `${command.moduleName}.${command.commandName}`;

    // The browsing context might be destroyed by a navigation. Keep a reference
    // to the webProgress, which will persist, and always use it to retrieve the
    // currently valid browsing context.
    const webProgress = browsingContext.webProgress;

    const { retryOnAbort = false } = command;

    let attempts = 0;
    while (true) {
      try {
        return await webProgress.browsingContext.currentWindowGlobal
          .getActor("MessageHandlerFrame")
          .sendCommand(command, this._messageHandler.sessionId);
      } catch (e) {
        if (!retryOnAbort || e.name != "AbortError") {
          // Only retry if the command supports retryOnAbort and when the
          // JSWindowActor pair gets destroyed.
          throw e;
        }

        if (++attempts > MAX_RETRY_ATTEMPTS) {
          lazy.logger.trace(
            `RootTransport reached the limit of retry attempts (${MAX_RETRY_ATTEMPTS})` +
              ` for command ${name} and browsing context ${webProgress.browsingContext.id}.`
          );
          throw e;
        }

        lazy.logger.trace(
          `RootTransport retrying command ${name} for ` +
            `browsing context ${webProgress.browsingContext.id}, attempt: ${attempts}.`
        );
        await new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
      }
    }
  }

  toString() {
    return `[object ${this.constructor.name} ${this._messageHandler.name}]`;
  }

  _getBrowsingContextsForDescriptor(contextDescriptor) {
    const { id, type } = contextDescriptor;

    if (type === lazy.ContextDescriptorType.All) {
      return this._getBrowsingContexts();
    }

    if (type === lazy.ContextDescriptorType.TopBrowsingContext) {
      return this._getBrowsingContexts({ browserId: id });
    }

    // TODO: Handle other types of context descriptors.
    throw new Error(
      `Unsupported contextDescriptor type for broadcasting: ${type}`
    );
  }

  /**
   * Get all browsing contexts, optionally matching the provided options.
   *
   * @param {object} options
   * @param {string=} options.browserId
   *    The id of the browser to filter the browsing contexts by (optional).
   * @returns {Array<BrowsingContext>}
   *    The browsing contexts matching the provided options or all browsing contexts
   *    if no options are provided.
   */
  _getBrowsingContexts(options = {}) {
    // extract browserId from options
    const { browserId } = options;
    let browsingContexts = [];

    // Fetch all tab related browsing contexts for top-level windows.
    for (const { browsingContext } of lazy.TabManager.browsers) {
      if (lazy.isBrowsingContextCompatible(browsingContext, { browserId })) {
        browsingContexts = browsingContexts.concat(
          browsingContext.getAllBrowsingContextsInSubtree()
        );
      }
    }

    return browsingContexts;
  }
}
PK
!<!ޠ���hchrome/remote/content/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameActor.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ActorManagerParent: "resource://gre/modules/ActorManagerParent.sys.mjs",

  Log: "chrome://remote/content/shared/Log.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

const FRAME_ACTOR_CONFIG = {
  parent: {
    esModuleURI:
      "chrome://remote/content/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameParent.sys.mjs",
  },
  child: {
    esModuleURI:
      "chrome://remote/content/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameChild.sys.mjs",
    events: {
      DOMWindowCreated: {},
      pagehide: {},
      pageshow: {},
    },
  },
  allFrames: true,
  messageManagerGroups: ["browsers"],
};

/**
 * MessageHandlerFrameActor exposes a simple registration helper to lazily
 * register MessageHandlerFrame JSWindow actors.
 */
export const MessageHandlerFrameActor = {
  registered: false,

  register() {
    if (this.registered) {
      return;
    }

    lazy.ActorManagerParent.addJSWindowActors({
      MessageHandlerFrame: FRAME_ACTOR_CONFIG,
    });
    this.registered = true;
    lazy.logger.trace("Registered MessageHandlerFrame actors");
  },
};
PK
!<��;hchrome/remote/content/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  isBrowsingContextCompatible:
    "chrome://remote/content/shared/messagehandler/transports/BrowsingContextUtils.sys.mjs",
  MessageHandlerRegistry:
    "chrome://remote/content/shared/messagehandler/MessageHandlerRegistry.sys.mjs",
  WindowGlobalMessageHandler:
    "chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs",
});

/**
 * Map from MessageHandlerRegistry to MessageHandlerFrameChild actor. This will
 * allow a WindowGlobalMessageHandler to find the JSWindowActorChild instance to
 * use to send commands.
 */
const registryToActor = new WeakMap();

/**
 * Retrieve the MessageHandlerFrameChild which is linked to the provided
 * WindowGlobalMessageHandler instance.
 *
 * @param {WindowGlobalMessageHandler} messageHandler
 *     The WindowGlobalMessageHandler for which to get the JSWindowActor.
 * @returns {MessageHandlerFrameChild}
 *     The corresponding MessageHandlerFrameChild instance.
 */
export function getMessageHandlerFrameChildActor(messageHandler) {
  return registryToActor.get(messageHandler.registry);
}

/**
 * Child actor for the MessageHandlerFrame JSWindowActor. The
 * MessageHandlerFrame actor is used by RootTransport to communicate between
 * ROOT MessageHandlers and WINDOW_GLOBAL MessageHandlers.
 */
export class MessageHandlerFrameChild extends JSWindowActorChild {
  actorCreated() {
    this.type = lazy.WindowGlobalMessageHandler.type;
    this.context = this.manager.browsingContext;

    this._registry = new lazy.MessageHandlerRegistry(this.type, this.context);
    registryToActor.set(this._registry, this);

    this._onRegistryEvent = this._onRegistryEvent.bind(this);

    // MessageHandlerFrameChild is responsible for forwarding events from
    // WindowGlobalMessageHandler to the parent process.
    // Such events are re-emitted on the MessageHandlerRegistry to avoid
    // setting up listeners on individual MessageHandler instances.
    this._registry.on("message-handler-registry-event", this._onRegistryEvent);
  }

  handleEvent({ persisted, type }) {
    if (type == "DOMWindowCreated" || (type == "pageshow" && persisted)) {
      // When the window is created or is retrieved from BFCache, instantiate
      // a MessageHandler for all sessions which might need it.
      if (lazy.isBrowsingContextCompatible(this.manager.browsingContext)) {
        this._registry.createAllMessageHandlers();
      }
    } else if (type == "pagehide" && persisted) {
      // When the page is moved to BFCache, all the currently created message
      // handlers should be destroyed.
      this._registry.destroy();
    }
  }

  async receiveMessage(message) {
    if (message.name === "MessageHandlerFrameParent:sendCommand") {
      const { sessionId, command } = message.data;
      const messageHandler =
        this._registry.getOrCreateMessageHandler(sessionId);
      try {
        return await messageHandler.handleCommand(command);
      } catch (e) {
        if (e?.isRemoteError) {
          return {
            error: e.toJSON(),
            isMessageHandlerError: e.isMessageHandlerError,
          };
        }
        throw e;
      }
    }

    return null;
  }

  sendCommand(command, sessionId) {
    return this.sendQuery("MessageHandlerFrameChild:sendCommand", {
      command,
      sessionId,
    });
  }

  _onRegistryEvent(eventName, wrappedEvent) {
    this.sendAsyncMessage(
      "MessageHandlerFrameChild:messageHandlerEvent",
      wrappedEvent
    );
  }

  didDestroy() {
    this._registry.off("message-handler-registry-event", this._onRegistryEvent);
    this._registry.destroy();
  }
}
PK
!<]�G;��ichrome/remote/content/shared/messagehandler/transports/js-window-actors/MessageHandlerFrameParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  error: "chrome://remote/content/shared/messagehandler/Errors.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  RootMessageHandlerRegistry:
    "chrome://remote/content/shared/messagehandler/RootMessageHandlerRegistry.sys.mjs",
  WindowGlobalMessageHandler:
    "chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

ChromeUtils.defineLazyGetter(lazy, "WebDriverError", () => {
  return ChromeUtils.importESModule(
    "chrome://remote/content/shared/webdriver/Errors.sys.mjs"
  ).error.WebDriverError;
});

/**
 * Parent actor for the MessageHandlerFrame JSWindowActor. The
 * MessageHandlerFrame actor is used by RootTransport to communicate between
 * ROOT MessageHandlers and WINDOW_GLOBAL MessageHandlers.
 */
export class MessageHandlerFrameParent extends JSWindowActorParent {
  async receiveMessage(message) {
    switch (message.name) {
      case "MessageHandlerFrameChild:sendCommand": {
        return this.#handleSendCommandMessage(message.data);
      }
      case "MessageHandlerFrameChild:messageHandlerEvent": {
        return this.#handleMessageHandlerEventMessage(message.data);
      }
      default:
        throw new Error("Unsupported message:" + message.name);
    }
  }

  /**
   * Send a command to the corresponding MessageHandlerFrameChild actor via a
   * JSWindowActor query.
   *
   * @param {Command} command
   *     The command to forward. See type definition in MessageHandler.js
   * @param {string} sessionId
   *     ID of the session that sent the command.
   * @returns {Promise}
   *     Promise that will resolve with the result of query sent to the
   *     MessageHandlerFrameChild actor.
   */
  async sendCommand(command, sessionId) {
    const result = await this.sendQuery(
      "MessageHandlerFrameParent:sendCommand",
      {
        command,
        sessionId,
      }
    );

    if (result?.error) {
      if (result.isMessageHandlerError) {
        throw lazy.error.MessageHandlerError.fromJSON(result.error);
      }

      // TODO: Do not assume WebDriver is the session protocol, see Bug 1779026.
      throw lazy.WebDriverError.fromJSON(result.error);
    }

    return result;
  }

  async #handleMessageHandlerEventMessage(messageData) {
    const { name, contextInfo, data, sessionId } = messageData;
    const [moduleName] = name.split(".");

    // Re-emit the event on the RootMessageHandler.
    const messageHandler =
      lazy.RootMessageHandlerRegistry.getExistingMessageHandler(sessionId);
    // TODO: getModuleInstance expects a CommandDestination in theory,
    // but only uses the MessageHandler type in practice, see Bug 1776389.
    const module = messageHandler.moduleCache.getModuleInstance(moduleName, {
      type: lazy.WindowGlobalMessageHandler.type,
    });
    let eventPayload = data;

    // Modify an event payload if there is a special method in the targeted module.
    // If present it can be found in windowglobal-in-root module.
    if (module?.interceptEvent) {
      eventPayload = await module.interceptEvent(name, data);

      if (eventPayload === null) {
        lazy.logger.trace(
          `${moduleName}.interceptEvent returned null, skipping event: ${name}, data: ${data}`
        );
        return;
      }
      // Make sure that an event payload is returned.
      if (!eventPayload) {
        throw new Error(
          `${moduleName}.interceptEvent doesn't return the event payload`
        );
      }
    }
    messageHandler.emitEvent(name, eventPayload, contextInfo);
  }

  async #handleSendCommandMessage(messageData) {
    const { sessionId, command } = messageData;
    const messageHandler =
      lazy.RootMessageHandlerRegistry.getExistingMessageHandler(sessionId);
    try {
      return await messageHandler.handleCommand(command);
    } catch (e) {
      if (e?.isRemoteError) {
        return {
          error: e.toJSON(),
          isMessageHandlerError: e.isMessageHandlerError,
        };
      }
      throw e;
    }
  }
}
PK
!<mt4�9�9<chrome/remote/content/shared/webdriver/Accessibility.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  waitForObserverTopic: "chrome://remote/content/marionette/sync.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

ChromeUtils.defineLazyGetter(lazy, "service", () => {
  try {
    return Cc["@mozilla.org/accessibilityService;1"].getService(
      Ci.nsIAccessibilityService
    );
  } catch (e) {
    lazy.logger.warn("Accessibility module is not present");
    return undefined;
  }
});

/** @namespace */
export const accessibility = {
  get service() {
    return lazy.service;
  },
};

/**
 * Accessible states used to check element"s state from the accessiblity API
 * perspective.
 *
 * Note: if gecko is built with --disable-accessibility, the interfaces
 * are not defined. This is why we use getters instead to be able to use
 * these statically.
 */
accessibility.State = {
  get Unavailable() {
    return Ci.nsIAccessibleStates.STATE_UNAVAILABLE;
  },
  get Focusable() {
    return Ci.nsIAccessibleStates.STATE_FOCUSABLE;
  },
  get Selectable() {
    return Ci.nsIAccessibleStates.STATE_SELECTABLE;
  },
  get Selected() {
    return Ci.nsIAccessibleStates.STATE_SELECTED;
  },
};

/**
 * Accessible object roles that support some action.
 */
accessibility.ActionableRoles = new Set([
  "checkbutton",
  "check menu item",
  "check rich option",
  "combobox",
  "combobox option",
  "entry",
  "key",
  "link",
  "listbox option",
  "listbox rich option",
  "menuitem",
  "option",
  "outlineitem",
  "pagetab",
  "pushbutton",
  "radiobutton",
  "radio menu item",
  "rowheader",
  "slider",
  "spinbutton",
  "switch",
]);

/**
 * Factory function that constructs a new {@code accessibility.Checks}
 * object with enforced strictness or not.
 */
accessibility.get = function (strict = false) {
  return new accessibility.Checks(!!strict);
};

/**
 * Wait for the document accessibility state to be different from STATE_BUSY.
 *
 * @param {Document} doc
 *     The document to wait for.
 * @returns {Promise}
 *     A promise which resolves when the document's accessibility state is no
 *     longer busy.
 */
function waitForDocumentAccessibility(doc) {
  const documentAccessible = accessibility.service.getAccessibleFor(doc);
  const state = {};
  documentAccessible.getState(state, {});
  if ((state.value & Ci.nsIAccessibleStates.STATE_BUSY) == 0) {
    return Promise.resolve();
  }

  // Accessibility for the doc is busy, so wait for the state to change.
  return lazy.waitForObserverTopic("accessible-event", {
    checkFn: subject => {
      // If event type does not match expected type, skip the event.
      // If event's accessible does not match expected accessible,
      // skip the event.
      const event = subject.QueryInterface(Ci.nsIAccessibleEvent);
      return (
        event.eventType === Ci.nsIAccessibleEvent.EVENT_STATE_CHANGE &&
        event.accessible === documentAccessible
      );
    },
  });
}

/**
 * Retrieve the Accessible for the provided element.
 *
 * @param {Element} element
 *     The element for which we need to retrieve the accessible.
 *
 * @returns {nsIAccessible|null}
 *     The Accessible object corresponding to the provided element or null if
 *     the accessibility service is not available.
 */
accessibility.getAccessible = async function (element) {
  if (!accessibility.service) {
    return null;
  }

  // First, wait for accessibility to be ready for the element's document.
  await waitForDocumentAccessibility(element.ownerDocument);

  const acc = accessibility.service.getAccessibleFor(element);
  if (acc) {
    return acc;
  }

  // The Accessible doesn't exist yet. This can happen because a11y tree
  // mutations happen during refresh driver ticks. Stop the refresh driver from
  // doing its regular ticks and force two refresh driver ticks: the first to
  // let layout update and notify a11y, and the second to let a11y process
  // updates.
  const windowUtils = element.ownerGlobal.windowUtils;
  windowUtils.advanceTimeAndRefresh(0);
  windowUtils.advanceTimeAndRefresh(0);
  // Go back to normal refresh driver ticks.
  windowUtils.restoreNormalRefresh();
  return accessibility.service.getAccessibleFor(element);
};

/**
 * Retrieve the accessible name for the provided element.
 *
 * @param {Element} element
 *     The element for which we need to retrieve the accessible name.
 *
 * @returns {string}
 *     The accessible name.
 */
accessibility.getAccessibleName = async function (element) {
  const accessible = await accessibility.getAccessible(element);
  if (!accessible) {
    return "";
  }

  // If name is null (absent), expose the empty string.
  if (accessible.name === null) {
    return "";
  }

  return accessible.name;
};

/**
 * Compute the role for the provided element.
 *
 * @param {Element} element
 *     The element for which we need to compute the role.
 *
 * @returns {string}
 *     The computed role.
 */
accessibility.getComputedRole = async function (element) {
  const accessible = await accessibility.getAccessible(element);
  if (!accessible) {
    // If it's not in the a11y tree, it's probably presentational.
    return "none";
  }

  return accessible.computedARIARole;
};

/**
 * Component responsible for interacting with platform accessibility
 * API.
 *
 * Its methods serve as wrappers for testing content and chrome
 * accessibility as well as accessibility of user interactions.
 */
accessibility.Checks = class {
  /**
   * @param {boolean} strict
   *     Flag indicating whether the accessibility issue should be logged
   *     or cause an error to be thrown.  Default is to log to stdout.
   */
  constructor(strict) {
    this.strict = strict;
  }

  /**
   * Assert that the element has a corresponding accessible object, and retrieve
   * this accessible. Note that if the accessibility.Checks component was
   * created in non-strict mode, this helper will not attempt to resolve the
   * accessible at all and will simply return null.
   *
   * @param {DOMElement|XULElement} element
   *     Element to get the accessible object for.
   * @param {boolean=} mustHaveAccessible
   *     Flag indicating that the element must have an accessible object.
   *     Defaults to not require this.
   *
   * @returns {Promise.<nsIAccessible>}
   *     Promise with an accessibility object for the given element.
   */
  async assertAccessible(element, mustHaveAccessible = false) {
    if (!this.strict) {
      return null;
    }

    const accessible = await accessibility.getAccessible(element);
    if (!accessible && mustHaveAccessible) {
      this.error("Element does not have an accessible object", element);
    }

    return accessible;
  }

  /**
   * Test if the accessible has a role that supports some arbitrary
   * action.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object.
   *
   * @returns {boolean}
   *     True if an actionable role is found on the accessible, false
   *     otherwise.
   */
  isActionableRole(accessible) {
    return accessibility.ActionableRoles.has(
      accessibility.service.getStringRole(accessible.role)
    );
  }

  /**
   * Test if an accessible has at least one action that it supports.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object.
   *
   * @returns {boolean}
   *     True if the accessible has at least one supported action,
   *     false otherwise.
   */
  hasActionCount(accessible) {
    return accessible.actionCount > 0;
  }

  /**
   * Test if an accessible has a valid name.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object.
   *
   * @returns {boolean}
   *     True if the accessible has a non-empty valid name, or false if
   *     this is not the case.
   */
  hasValidName(accessible) {
    return accessible.name && accessible.name.trim();
  }

  /**
   * Test if an accessible has a {@code hidden} attribute.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object.
   *
   * @returns {boolean}
   *     True if the accessible object has a {@code hidden} attribute,
   *     false otherwise.
   */
  hasHiddenAttribute(accessible) {
    let hidden = false;
    try {
      hidden = accessible.attributes.getStringProperty("hidden");
    } catch (e) {}
    // if the property is missing, error will be thrown
    return hidden && hidden === "true";
  }

  /**
   * Verify if an accessible has a given state.
   * Test if an accessible has a given state.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object to test.
   * @param {number} stateToMatch
   *     State to match.
   *
   * @returns {boolean}
   *     True if |accessible| has |stateToMatch|, false otherwise.
   */
  matchState(accessible, stateToMatch) {
    let state = {};
    accessible.getState(state, {});
    return !!(state.value & stateToMatch);
  }

  /**
   * Test if an accessible is hidden from the user.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object.
   *
   * @returns {boolean}
   *     True if element is hidden from user, false otherwise.
   */
  isHidden(accessible) {
    if (!accessible) {
      return true;
    }

    while (accessible) {
      if (this.hasHiddenAttribute(accessible)) {
        return true;
      }
      accessible = accessible.parent;
    }
    return false;
  }

  /**
   * Test if the element's visible state corresponds to its accessibility
   * API visibility.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object.
   * @param {DOMElement|XULElement} element
   *     Element associated with |accessible|.
   * @param {boolean} visible
   *     Visibility state of |element|.
   *
   * @throws ElementNotAccessibleError
   *     If |element|'s visibility state does not correspond to
   *     |accessible|'s.
   */
  assertVisible(accessible, element, visible) {
    let hiddenAccessibility = this.isHidden(accessible);

    let message;
    if (visible && hiddenAccessibility) {
      message =
        "Element is not currently visible via the accessibility API " +
        "and may not be manipulated by it";
    } else if (!visible && !hiddenAccessibility) {
      message =
        "Element is currently only visible via the accessibility API " +
        "and can be manipulated by it";
    }
    this.error(message, element);
  }

  /**
   * Test if the element's unavailable accessibility state matches the
   * enabled state.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object.
   * @param {DOMElement|XULElement} element
   *     Element associated with |accessible|.
   * @param {boolean} enabled
   *     Enabled state of |element|.
   *
   * @throws ElementNotAccessibleError
   *     If |element|'s enabled state does not match |accessible|'s.
   */
  assertEnabled(accessible, element, enabled) {
    if (!accessible) {
      return;
    }

    let win = element.ownerGlobal;
    let disabledAccessibility = this.matchState(
      accessible,
      accessibility.State.Unavailable
    );
    let explorable =
      win.getComputedStyle(element).getPropertyValue("pointer-events") !==
      "none";

    let message;
    if (!explorable && !disabledAccessibility) {
      message =
        "Element is enabled but is not explorable via the " +
        "accessibility API";
    } else if (enabled && disabledAccessibility) {
      message = "Element is enabled but disabled via the accessibility API";
    } else if (!enabled && !disabledAccessibility) {
      message = "Element is disabled but enabled via the accessibility API";
    }
    this.error(message, element);
  }

  /**
   * Test if it is possible to activate an element with the accessibility
   * API.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object.
   * @param {DOMElement|XULElement} element
   *     Element associated with |accessible|.
   *
   * @throws ElementNotAccessibleError
   *     If it is impossible to activate |element| with |accessible|.
   */
  assertActionable(accessible, element) {
    if (!accessible) {
      return;
    }

    let message;
    if (!this.hasActionCount(accessible)) {
      message = "Element does not support any accessible actions";
    } else if (!this.isActionableRole(accessible)) {
      message =
        "Element does not have a correct accessibility role " +
        "and may not be manipulated via the accessibility API";
    } else if (!this.hasValidName(accessible)) {
      message = "Element is missing an accessible name";
    } else if (!this.matchState(accessible, accessibility.State.Focusable)) {
      message = "Element is not focusable via the accessibility API";
    }

    this.error(message, element);
  }

  /**
   * Test that an element's selected state corresponds to its
   * accessibility API selected state.
   *
   * @param {nsIAccessible} accessible
   *     Accessible object.
   * @param {DOMElement|XULElement} element
   *     Element associated with |accessible|.
   * @param {boolean} selected
   *     The |element|s selected state.
   *
   * @throws ElementNotAccessibleError
   *     If |element|'s selected state does not correspond to
   *     |accessible|'s.
   */
  assertSelected(accessible, element, selected) {
    if (!accessible) {
      return;
    }

    // element is not selectable via the accessibility API
    if (!this.matchState(accessible, accessibility.State.Selectable)) {
      return;
    }

    let selectedAccessibility = this.matchState(
      accessible,
      accessibility.State.Selected
    );

    let message;
    if (selected && !selectedAccessibility) {
      message =
        "Element is selected but not selected via the accessibility API";
    } else if (!selected && selectedAccessibility) {
      message =
        "Element is not selected but selected via the accessibility API";
    }
    this.error(message, element);
  }

  /**
   * Throw an error if strict accessibility checks are enforced and log
   * the error to the log.
   *
   * @param {string} message
   * @param {DOMElement|XULElement} element
   *     Element that caused an error.
   *
   * @throws ElementNotAccessibleError
   *     If |strict| is true.
   */
  error(message, element) {
    if (!message || !this.strict) {
      return;
    }
    if (element) {
      let { id, tagName, className } = element;
      message += `: id: ${id}, tagName: ${tagName}, className: ${className}`;
    }

    throw new lazy.error.ElementNotAccessibleError(message);
  }
};
PK
!<Kh�::6chrome/remote/content/shared/webdriver/Actions.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint no-dupe-keys:off */
/* eslint-disable no-restricted-globals */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
  dom: "chrome://remote/content/shared/DOM.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  event: "chrome://remote/content/shared/webdriver/Event.sys.mjs",
  keyData: "chrome://remote/content/shared/webdriver/KeyData.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
  Sleep: "chrome://remote/content/marionette/sync.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.MARIONETTE)
);

// TODO? With ES 2016 and Symbol you can make a safer approximation
// to an enum e.g. https://gist.github.com/xmlking/e86e4f15ec32b12c4689
/**
 * Implements WebDriver Actions API: a low-level interface for providing
 * virtualised device input to the web browser.
 *
 * Typical usage is to construct an action chain and then dispatch it:
 * const state = new action.State();
 * const chain = new action.Chain.fromJSON(state, protocolData);
 * await chain.dispatch(state, window);
 *
 * @namespace
 */
export const action = {};

// Max interval between two clicks that should result in a dblclick or a tripleclick (in ms)
export const CLICK_INTERVAL = 640;

/** Map from normalized key value to UI Events modifier key name */
const MODIFIER_NAME_LOOKUP = {
  Alt: "alt",
  Shift: "shift",
  Control: "ctrl",
  Meta: "meta",
};

/**
 * State associated with actions
 *
 * Typically each top-level browsing context in a session should have a single State object
 */
action.State = class {
  constructor() {
    this.clickTracker = new ClickTracker();
    /**
     * A map between input ID and the device state for that input
     * source, with one entry for each active input source.
     *
     * Maps string => InputSource
     */
    this.inputStateMap = new Map();

    /**
     * List of {@link Action} associated with current session.  Used to
     * manage dispatching events when resetting the state of the input sources.
     * Reset operations are assumed to be idempotent.
     */
    this.inputsToCancel = new TickActions();

    /**
     * Map between string input id and numeric pointer id
     */
    this.pointerIdMap = new Map();
  }

  toString() {
    return `[object ${this.constructor.name} ${JSON.stringify(this)}]`;
  }

  /**
   * Reset state stored in this object.
   * It is an error to use the State object after calling release().
   *
   * @param {WindowProxy} win Current window global.
   */
  async release(win) {
    this.inputsToCancel.reverse();
    await this.inputsToCancel.dispatch(this, win);
  }

  /**
   * Get the state for a given input source.
   *
   * @param {string} id Input source id.
   * @returns {InputSource} Input source state.
   */
  getInputSource(id) {
    return this.inputStateMap.get(id);
  }

  /**
   * Find or add state for an input source. The caller should verify
   * that the returned state is the expected type.
   *
   * @param {string} id Input source id.
   * @param {InputSource} newInputSource Input source state.
   */
  getOrAddInputSource(id, newInputSource) {
    let inputSource = this.getInputSource(id);

    if (inputSource === undefined) {
      this.inputStateMap.set(id, newInputSource);
      inputSource = newInputSource;
    }

    return inputSource;
  }

  /**
   * Iterate over all input states of a given type
   *
   * @param {string} type Input source type name (e.g. "pointer").
   * @returns {Iterator} Iterator over [id, input source].
   */
  *inputSourcesByType(type) {
    for (const [id, inputSource] of this.inputStateMap) {
      if (inputSource.type === type) {
        yield [id, inputSource];
      }
    }
  }

  /**
   * Get a numerical pointer id for a given pointer
   *
   * Pointer ids are positive integers. Mouse pointers are typically
   * ids 0 or 1. Non-mouse pointers never get assigned id < 2. Each
   * pointer gets a unique id.
   *
   * @param {string} id Pointer id.
   * @param {string} type Pointer type.
   * @returns {number} Numerical pointer id.
   */
  getPointerId(id, type) {
    let pointerId = this.pointerIdMap.get(id);

    if (pointerId === undefined) {
      // Reserve pointer ids 0 and 1 for mouse pointers
      const idValues = Array.from(this.pointerIdMap.values());

      if (type === "mouse") {
        for (const mouseId of [0, 1]) {
          if (!idValues.includes(mouseId)) {
            pointerId = mouseId;
            break;
          }
        }
      }

      if (pointerId === undefined) {
        pointerId = Math.max(1, ...idValues) + 1;
      }
      this.pointerIdMap.set(id, pointerId);
    }

    return pointerId;
  }
};

export class ClickTracker {
  #count;
  #lastButtonClicked;
  #timer;

  constructor() {
    this.#count = 0;
    this.#lastButtonClicked = null;
  }

  get count() {
    return this.#count;
  }

  #cancelTimer() {
    lazy.clearTimeout(this.#timer);
  }

  #startTimer() {
    this.#timer = lazy.setTimeout(this.reset.bind(this), CLICK_INTERVAL);
  }

  /**
   * Reset tracking mouse click counter.
   */
  reset() {
    this.#cancelTimer();
    this.#count = 0;
    this.#lastButtonClicked = null;
  }

  /**
   * Track |button| click to identify possible double or triple click.
   *
   * @param {number} button
   *     A positive integer that refers to a mouse button.
   */
  setClick(button) {
    this.#cancelTimer();

    if (
      this.#lastButtonClicked === null ||
      this.#lastButtonClicked === button
    ) {
      this.#count++;
    } else {
      this.#count = 1;
    }

    this.#lastButtonClicked = button;
    this.#startTimer();
  }
}

/**
 * Device state for an input source.
 */
class InputSource {
  #id;
  static type = null;

  constructor(id) {
    this.#id = id;
    this.type = this.constructor.type;
  }

  toString() {
    return `[object ${this.constructor.name} id: ${this.#id} type: ${
      this.type
    }]`;
  }

  /**
   * @param {State} state Actions state.
   * @param {Sequence} actionSequence Actions for a specific input source.
   *
   * @returns {InputSource}
   *     An {@link InputSource} object for the type of the
   *     {@link actionSequence}.
   *
   * @throws {InvalidArgumentError}
   *     If {@link actionSequence.type} is not valid.
   */
  static fromJSON(state, actionSequence) {
    const { id, type } = actionSequence;

    lazy.assert.string(
      id,
      lazy.pprint`Expected "id" to be a string, got ${id}`
    );

    const cls = inputSourceTypes.get(type);
    if (cls === undefined) {
      throw new lazy.error.InvalidArgumentError(
        lazy.pprint`Expected known action type, got ${type}`
      );
    }

    const sequenceInputSource = cls.fromJSON(state, actionSequence);
    const inputSource = state.getOrAddInputSource(id, sequenceInputSource);

    if (inputSource.type !== type) {
      throw new lazy.error.InvalidArgumentError(
        lazy.pprint`Expected input source ${id} to be ` +
          `type ${inputSource.type}, got ${type}`
      );
    }
  }
}

/**
 * Input state not associated with a specific physical device.
 */
class NullInputSource extends InputSource {
  static type = "none";

  static fromJSON(state, actionSequence) {
    const { id } = actionSequence;

    return new this(id);
  }
}

/**
 * Input state associated with a keyboard-type device.
 */
class KeyInputSource extends InputSource {
  static type = "key";

  constructor(id) {
    super(id);

    this.pressed = new Set();
    this.alt = false;
    this.shift = false;
    this.ctrl = false;
    this.meta = false;
  }

  static fromJSON(state, actionSequence) {
    const { id } = actionSequence;

    return new this(id);
  }

  /**
   * Update modifier state according to |key|.
   *
   * @param {string} key
   *     Normalized key value of a modifier key.
   * @param {boolean} value
   *     Value to set the modifier attribute to.
   *
   * @throws {InvalidArgumentError}
   *     If |key| is not a modifier.
   */
  setModState(key, value) {
    if (key in MODIFIER_NAME_LOOKUP) {
      this[MODIFIER_NAME_LOOKUP[key]] = value;
    } else {
      throw new lazy.error.InvalidArgumentError(
        lazy.pprint`Expected "key" to be one of ${Object.keys(
          MODIFIER_NAME_LOOKUP
        )}, got ${key}`
      );
    }
  }

  /**
   * Check whether |key| is pressed.
   *
   * @param {string} key
   *     Normalized key value.
   *
   * @returns {boolean}
   *     True if |key| is in set of pressed keys.
   */
  isPressed(key) {
    return this.pressed.has(key);
  }

  /**
   * Add |key| to the set of pressed keys.
   *
   * @param {string} key
   *     Normalized key value.
   *
   * @returns {boolean}
   *     True if |key| is in list of pressed keys.
   */
  press(key) {
    return this.pressed.add(key);
  }

  /**
   * Remove |key| from the set of pressed keys.
   *
   * @param {string} key
   *     Normalized key value.
   *
   * @returns {boolean}
   *     True if |key| was present before removal, false otherwise.
   */
  release(key) {
    return this.pressed.delete(key);
  }
}

/**
 * Input state associated with a pointer-type device.
 */
class PointerInputSource extends InputSource {
  static type = "pointer";

  /**
   * @param {string} id InputSource id.
   * @param {Pointer} pointer Object representing the specific pointer
   * type associated with this input source.
   */
  constructor(id, pointer) {
    super(id);

    this.pointer = pointer;
    this.x = 0;
    this.y = 0;
    this.pressed = new Set();
  }

  /**
   * Check whether |button| is pressed.
   *
   * @param {number} button
   *     Positive integer that refers to a mouse button.
   *
   * @returns {boolean}
   *     True if |button| is in set of pressed buttons.
   */
  isPressed(button) {
    lazy.assert.positiveInteger(
      button,
      lazy.pprint`Expected "button" to be a positive integer, got ${button}`
    );
    return this.pressed.has(button);
  }

  /**
   * Add |button| to the set of pressed keys.
   *
   * @param {number} button
   *     Positive integer that refers to a mouse button.
   *
   * @returns {Set}
   *     Set of pressed buttons.
   */
  press(button) {
    lazy.assert.positiveInteger(
      button,
      lazy.pprint`Expected "button" to be a positive integer, got ${button}`
    );
    this.pressed.add(button);
  }

  /**
   * Remove |button| from the set of pressed buttons.
   *
   * @param {number} button
   *     A positive integer that refers to a mouse button.
   *
   * @returns {boolean}
   *     True if |button| was present before removals, false otherwise.
   */
  release(button) {
    lazy.assert.positiveInteger(
      button,
      lazy.pprint`Expected "button" to be a positive integer, got ${button}`
    );
    return this.pressed.delete(button);
  }

  static fromJSON(state, actionSequence) {
    const { id, parameters } = actionSequence;
    let pointerType = "mouse";

    if (parameters !== undefined) {
      lazy.assert.object(
        parameters,
        lazy.pprint`Expected "parameters" to be an object, got ${parameters}`
      );

      if (parameters.pointerType !== undefined) {
        pointerType = lazy.assert.string(
          parameters.pointerType,
          lazy.pprint(
            `Expected "pointerType" to be a string, got ${parameters.pointerType}`
          )
        );

        if (!["mouse", "pen", "touch"].includes(pointerType)) {
          throw new lazy.error.InvalidArgumentError(
            lazy.pprint`Expected "pointerType" to be one of "mouse", "pen", or "touch"`
          );
        }
      }
    }

    const pointerId = state.getPointerId(id, pointerType);
    const pointer = Pointer.fromJSON(pointerId, pointerType);

    return new this(id, pointer);
  }
}

/**
 * Input state associated with a wheel-type device.
 */
class WheelInputSource extends InputSource {
  static type = "wheel";

  static fromJSON(state, actionSequence) {
    const { id } = actionSequence;

    return new this(id);
  }
}

const inputSourceTypes = new Map();
for (const cls of [
  NullInputSource,
  KeyInputSource,
  PointerInputSource,
  WheelInputSource,
]) {
  inputSourceTypes.set(cls.type, cls);
}

/**
 * Representation of a coordinate origin
 */
class Origin {
  /**
   * Viewport coordinates of the origin of this coordinate system.
   *
   * This is overridden in subclasses to provide a class-specific origin.
   */
  getOriginCoordinates() {
    throw new Error(
      `originCoordinates not defined for ${this.constructor.name}`
    );
  }

  /**
   * Convert [x, y] coordinates to viewport coordinates
   *
   * @param {InputSource} inputSource - State of the current input device
   * @param {Array<number>} coords - [x, y] coordinate of target relative to origin
   * @param {WindowProxy} win - Current window global
   */
  getTargetCoordinates(inputSource, coords, win) {
    const [x, y] = coords;
    const origin = this.getOriginCoordinates(inputSource, win);

    return [origin.x + x, origin.y + y];
  }

  /**
   * @param {Element|string=} origin - Type of orgin, one of "viewport", "pointer", element or undefined.
   *
   * @returns {Origin} - An origin object representing the origin.
   *
   * @throws {InvalidArgumentError}
   *     If <code>origin</code> isn't a valid origin.
   */
  static fromJSON(origin) {
    if (origin === undefined || origin === "viewport") {
      return new ViewportOrigin();
    }
    if (origin === "pointer") {
      return new PointerOrigin();
    }
    if (lazy.dom.isElement(origin)) {
      return new ElementOrigin(origin);
    }

    throw new lazy.error.InvalidArgumentError(
      `Expected "origin" to be undefined, "viewport", "pointer", ` +
        lazy.pprint`or an element, got: ${origin}`
    );
  }
}

class ViewportOrigin extends Origin {
  getOriginCoordinates() {
    return { x: 0, y: 0 };
  }
}

class PointerOrigin extends Origin {
  getOriginCoordinates(inputSource) {
    return { x: inputSource.x, y: inputSource.y };
  }
}

class ElementOrigin extends Origin {
  /**
   * @param {Element} element - The element providing the coordinate origin.
   */
  constructor(element) {
    super();

    this.element = element;
  }

  getOriginCoordinates(inputSource, win) {
    const clientRects = this.element.getClientRects();

    // The spec doesn't handle this case; https://github.com/w3c/webdriver/issues/1642
    if (!clientRects.length) {
      throw new lazy.error.MoveTargetOutOfBoundsError(
        lazy.pprint`Origin element ${this.element} is not displayed`
      );
    }

    return lazy.dom.getInViewCentrePoint(clientRects[0], win);
  }
}

/**
 * Repesents the behaviour of a single input source at a single
 * point in time.
 *
 * @param {string} id - Input source ID.
 */
class Action {
  /** Type of the input source associated with this action */
  static type = null;
  /** Type of action specific to the input source */
  static subtype = null;
  /** Whether this kind of action affects the overall duration of a tick */
  affectsWallClockTime = false;

  constructor(id) {
    this.id = id;
    this.type = this.constructor.type;
    this.subtype = this.constructor.subtype;
  }

  toString() {
    return `[${this.constructor.name} ${this.type}:${this.subtype}]`;
  }

  /**
   * Dispatch the action to the relevant window.
   *
   * This is overridden by subclasses to implement the type-specific
   * dispatch of the action.
   *
   * @returns {Promise} - Promise that is resolved once the action is complete.
   */
  dispatch() {
    throw new Error(
      `Action subclass ${this.constructor.name} must override dispatch()`
    );
  }

  /**
   * @param {string} type - Input source type.
   * @param {string} id - Input source id.
   * @param {object} actionItem - Object representing a single action.
   *
   * @returns {Action} - An action that can be dispatched.
   *
   * @throws {InvalidArgumentError}
   *     If any <code>actionSequence</code> or <code>actionItem</code>
   *     attributes are invalid.
   */
  static fromJSON(type, id, actionItem) {
    lazy.assert.object(
      actionItem,
      lazy.pprint`Expected "action" to be an object, got ${actionItem}`
    );

    const subtype = actionItem.type;
    const subtypeMap = actionTypes.get(type);

    if (subtypeMap === undefined) {
      throw new lazy.error.InvalidArgumentError(
        lazy.pprint`Expected known action type, got ${type}`
      );
    }

    let cls = subtypeMap.get(subtype);
    // Non-device specific actions can happen for any action type
    if (cls === undefined) {
      cls = actionTypes.get("none").get(subtype);
    }
    if (cls === undefined) {
      throw new lazy.error.InvalidArgumentError(
        lazy.pprint`Expected known subtype for type ${type}, got ${subtype}`
      );
    }

    return cls.fromJSON(id, actionItem);
  }
}

/**
 * Action not associated with a specific input device.
 */
class NullAction extends Action {
  static type = "none";
}

/**
 * Action that waits for a given duration.
 *
 * @param {string} id - Input source ID.
 * @param {object} options - Named arguments.
 * @param {number} options.duration - Time to pause, in ms.
 */
class PauseAction extends NullAction {
  static subtype = "pause";
  affectsWallClockTime = true;

  constructor(id, options) {
    super(id);

    const { duration } = options;
    this.duration = duration;
  }

  /**
   * Dispatch pause action
   *
   * @param {State} state - Actions state.
   * @param {InputSource} inputSource - State of the current input device.
   * @param {number} tickDuration - Length of the current tick, in ms.
   * @returns {Promise} - Promise that is resolved once the action is complete.
   */
  dispatch(state, inputSource, tickDuration) {
    const ms = this.duration ?? tickDuration;

    lazy.logger.trace(
      ` Dispatch ${this.constructor.name} with ${this.id} ${ms}`
    );

    return lazy.Sleep(ms);
  }

  static fromJSON(id, actionItem) {
    const { duration } = actionItem;

    if (duration !== undefined) {
      lazy.assert.positiveInteger(
        duration,
        lazy.pprint`Expected "duration" to be a positive integer, got ${duration}`
      );
    }

    return new this(id, { duration });
  }
}

/**
 * Action associated with a keyboard input device
 *
 * @param {string} id - Input source ID.
 * @param {object} options - Named arguments.
 * @param {string} options.value - Key character.
 */
class KeyAction extends Action {
  static type = "key";

  constructor(id, options) {
    super(id);

    const { value } = options;
    this.value = value;
  }

  getEventData(inputSource) {
    let value = this.value;

    if (inputSource.shift) {
      value = lazy.keyData.getShiftedKey(value);
    }

    return new KeyEventData(value);
  }

  static fromJSON(id, actionItem) {
    const { value } = actionItem;

    lazy.assert.string(
      value,
      'Expected "value" to be a string that represents single code point ' +
        lazy.pprint`or grapheme cluster, got ${value}`
    );

    let segmenter = new Intl.Segmenter();
    lazy.assert.that(v => {
      let graphemeIterator = segmenter.segment(v)[Symbol.iterator]();
      // We should have exactly one grapheme cluster, so the first iterator
      // value must be defined, but the second one must be undefined
      return (
        graphemeIterator.next().value !== undefined &&
        graphemeIterator.next().value === undefined
      );
    }, `Expected "value" to be a string that represents single code point or grapheme cluster, got "${value}"`)(
      value
    );

    return new this(id, { value });
  }
}

/**
 * Action equivalent to pressing a key on a keyboard.
 *
 * @param {string} id - Input source ID.
 * @param {string} value - Key character.
 */
class KeyDownAction extends KeyAction {
  static subtype = "keyDown";

  dispatch(state, inputSource, tickDuration, win) {
    lazy.logger.trace(
      `Dispatch ${this.constructor.name} with ${this.id} ${this.value}`
    );

    return new Promise(resolve => {
      const keyEvent = this.getEventData(inputSource);
      keyEvent.repeat = inputSource.isPressed(keyEvent.key);
      inputSource.press(keyEvent.key);

      if (keyEvent.key in MODIFIER_NAME_LOOKUP) {
        inputSource.setModState(keyEvent.key, true);
      }

      // Append a copy of |a| with keyUp subtype
      state.inputsToCancel.push(new KeyUpAction(this.id, this));
      keyEvent.update(state, inputSource);
      lazy.event.sendKeyDown(keyEvent, win);

      resolve();
    });
  }
}

/**
 * Action equivalent to releasing a key on a keyboard.
 *
 * @param {string} id - Input source ID.
 * @param {string} value - Key character.
 */
class KeyUpAction extends KeyAction {
  static subtype = "keyUp";

  dispatch(state, inputSource, tickDuration, win) {
    lazy.logger.trace(
      `Dispatch ${this.constructor.name} with ${this.id} ${this.value}`
    );

    return new Promise(resolve => {
      const keyEvent = this.getEventData(inputSource);

      if (!inputSource.isPressed(keyEvent.key)) {
        resolve();
        return;
      }

      if (keyEvent.key in MODIFIER_NAME_LOOKUP) {
        inputSource.setModState(keyEvent.key, false);
      }

      inputSource.release(keyEvent.key);
      keyEvent.update(state, inputSource);

      lazy.event.sendKeyUp(keyEvent, win);
      resolve();
    });
  }
}

/**
 * Action associated with a pointer input device
 *
 * @param {string} id - Input source ID.
 * @param {object} options - Named arguments.
 * @param {number=} options.width - Pointer width in pixels.
 * @param {number=} options.height - Pointer height in pixels.
 * @param {number=} options.pressure - Pointer pressure.
 * @param {number=} options.tangentialPressure - Pointer tangential pressure.
 * @param {number=} options.tiltX - Pointer X tilt angle.
 * @param {number=} options.tiltX - Pointer Y tilt angle.
 * @param {number=} options.twist - Pointer twist angle.
 * @param {number=} options.altitudeAngle - Pointer altitude angle.
 * @param {number=} options.azimuthAngle - Pointer azimuth angle.
 */
class PointerAction extends Action {
  static type = "pointer";

  constructor(id, options) {
    super(id);
    const {
      width,
      height,
      pressure,
      tangentialPressure,
      tiltX,
      tiltY,
      twist,
      altitudeAngle,
      azimuthAngle,
    } = options;
    this.width = width;
    this.height = height;
    this.pressure = pressure;
    this.tangentialPressure = tangentialPressure;
    this.tiltX = tiltX;
    this.tiltY = tiltY;
    this.twist = twist;
    this.altitudeAngle = altitudeAngle;
    this.azimuthAngle = azimuthAngle;
  }

  /**
   * Validate properties common to all pointer types
   *
   * @param {object} actionItem - Object representing a single action.
   */
  static validateCommon(actionItem) {
    const {
      width,
      height,
      pressure,
      tangentialPressure,
      tiltX,
      tiltY,
      twist,
      altitudeAngle,
      azimuthAngle,
    } = actionItem;
    if (width !== undefined) {
      lazy.assert.positiveInteger(
        width,
        lazy.pprint`Expected "width" to be a positive integer, got ${width}`
      );
    }
    if (height !== undefined) {
      lazy.assert.positiveInteger(
        height,
        lazy.pprint`Expected "height" to be a positive integer, got ${height}`
      );
    }
    if (pressure !== undefined) {
      lazy.assert.numberInRange(
        pressure,
        [0, 1],
        lazy.pprint`Expected "pressure" to be in range 0 to 1, got ${pressure}`
      );
    }
    if (tangentialPressure !== undefined) {
      lazy.assert.numberInRange(
        tangentialPressure,
        [-1, 1],
        'Expected "tangentialPressure" to be in range -1 to 1, ' +
          lazy.pprint`got ${tangentialPressure}`
      );
    }
    if (tiltX !== undefined) {
      lazy.assert.integerInRange(
        tiltX,
        [-90, 90],
        lazy.pprint`Expected "tiltX" to be in range -90 to 90, got ${tiltX}`
      );
    }
    if (tiltY !== undefined) {
      lazy.assert.integerInRange(
        tiltY,
        [-90, 90],
        lazy.pprint`Expected "tiltY" to be in range -90 to 90, got ${tiltY}`
      );
    }
    if (twist !== undefined) {
      lazy.assert.integerInRange(
        twist,
        [0, 359],
        lazy.pprint`Expected "twist" to be in range 0 to 359, got ${twist}`
      );
    }
    if (altitudeAngle !== undefined) {
      lazy.assert.numberInRange(
        altitudeAngle,
        [0, Math.PI / 2],
        'Expected "altitudeAngle" to be in range 0 to ${Math.PI / 2}, ' +
          lazy.pprint`got ${altitudeAngle}`
      );
    }
    if (azimuthAngle !== undefined) {
      lazy.assert.numberInRange(
        azimuthAngle,
        [0, 2 * Math.PI],
        'Expected "azimuthAngle" to be in range 0 to ${2 * Math.PI}, ' +
          lazy.pprint`got ${azimuthAngle}`
      );
    }

    return {
      width,
      height,
      pressure,
      tangentialPressure,
      tiltX,
      tiltY,
      twist,
      altitudeAngle,
      azimuthAngle,
    };
  }
}

/**
 * Action associated with a pointer input device being depressed.
 *
 * @param {string} id - Input source ID.
 * @param {object} options - Named arguments.
 * @param {number} options.button - Button being pressed. For devices without buttons (e.g. touch), this should be 0.
 * @param {number=} options.width - Pointer width in pixels.
 * @param {number=} options.height - Pointer height in pixels.
 * @param {number=} options.pressure - Pointer pressure.
 * @param {number=} options.tangentialPressure - Pointer tangential pressure.
 * @param {number=} options.tiltX - Pointer X tilt angle.
 * @param {number=} options.tiltX - Pointer Y tilt angle.
 * @param {number=} options.twist - Pointer twist angle.
 * @param {number=} options.altitudeAngle - Pointer altitude angle.
 * @param {number=} options.azimuthAngle - Pointer azimuth angle.
 */
class PointerDownAction extends PointerAction {
  static subtype = "pointerDown";

  constructor(id, options) {
    super(id, options);

    const { button } = options;
    this.button = button;
  }

  dispatch(state, inputSource, tickDuration, win) {
    lazy.logger.trace(
      `Dispatch ${this.constructor.name} ${inputSource.pointer.type} with id: ${this.id} button: ${this.button}`
    );

    return new Promise(resolve => {
      if (inputSource.isPressed(this.button)) {
        resolve();
        return;
      }

      inputSource.press(this.button);
      // Append a copy of |a| with pointerUp subtype
      state.inputsToCancel.push(new PointerUpAction(this.id, this));
      inputSource.pointer.pointerDown(state, inputSource, this, win);
      resolve();
    });
  }

  static fromJSON(id, actionItem) {
    const { button } = actionItem;
    const props = PointerAction.validateCommon(actionItem);

    lazy.assert.positiveInteger(
      button,
      lazy.pprint`Expected "button" to be a positive integer, got ${button}`
    );

    props.button = button;

    return new this(id, props);
  }
}

/**
 * Action associated with a pointer input device being released.
 *
 * @param {string} id - Input source ID.
 * @param {object} options - Named arguments.
 * @param {number} options.button - Button being released. For devices without buttons (e.g. touch), this should be 0.
 * @param {number=} options.width - Pointer width in pixels.
 * @param {number=} options.height - Pointer height in pixels.
 * @param {number=} options.pressure - Pointer pressure.
 * @param {number=} options.tangentialPressure - Pointer tangential pressure.
 * @param {number=} options.tiltX - Pointer X tilt angle.
 * @param {number=} options.tiltX - Pointer Y tilt angle.
 * @param {number=} options.twist - Pointer twist angle.
 * @param {number=} options.altitudeAngle - Pointer altitude angle.
 * @param {number=} options.azimuthAngle - Pointer azimuth angle.
 */
class PointerUpAction extends PointerAction {
  static subtype = "pointerUp";

  constructor(id, options) {
    super(id, options);

    const { button } = options;
    this.button = button;
  }

  dispatch(state, inputSource, tickDuration, win) {
    lazy.logger.trace(
      `Dispatch ${this.constructor.name} ${inputSource.pointer.type} with id: ${this.id} button: ${this.button}`
    );

    return new Promise(resolve => {
      if (!inputSource.isPressed(this.button)) {
        resolve();
        return;
      }

      inputSource.release(this.button);
      inputSource.pointer.pointerUp(state, inputSource, this, win);

      resolve();
    });
  }

  static fromJSON(id, actionItem) {
    const { button } = actionItem;
    const props = PointerAction.validateCommon(actionItem);

    lazy.assert.positiveInteger(
      button,
      lazy.pprint`Expected "button" to be a positive integer, got ${button}`
    );

    props.button = button;

    return new this(id, props);
  }
}

/**
 * Action associated with a pointer input device being moved.
 *
 * @param {string} id - Input source ID.
 * @param {object} options - Named arguments.
 * @param {number=} options.width - Pointer width in pixels.
 * @param {number=} options.height - Pointer height in pixels.
 * @param {number=} options.pressure - Pointer pressure.
 * @param {number=} options.tangentialPressure - Pointer tangential pressure.
 * @param {number=} options.tiltX - Pointer X tilt angle.
 * @param {number=} options.tiltX - Pointer Y tilt angle.
 * @param {number=} options.twist - Pointer twist angle.
 * @param {number=} options.altitudeAngle - Pointer altitude angle.
 * @param {number=} options.azimuthAngle - Pointer azimuth angle.
 * @param {number=} options.duration - Duration of move in ms.
 * @param {Origin} options.origin - Origin of target coordinates.
 * @param {number} options.x - X value of target coordinates.
 * @param {number} options.y - Y value of target coordinates.
 */
class PointerMoveAction extends PointerAction {
  static subtype = "pointerMove";
  affectsWallClockTime = true;

  constructor(id, options) {
    super(id, options);

    const { duration, origin, x, y } = options;
    this.duration = duration;
    this.origin = origin;
    this.x = x;
    this.y = y;
  }

  dispatch(state, inputSource, tickDuration, win) {
    lazy.logger.trace(
      `Dispatch ${this.constructor.name} ${inputSource.pointer.type} with id: ${this.id} x: ${this.x} y: ${this.y}`
    );

    const target = this.origin.getTargetCoordinates(
      inputSource,
      [this.x, this.y],
      win
    );

    assertInViewPort(target, win);

    return moveOverTime(
      [[inputSource.x, inputSource.y]],
      [target],
      this.duration ?? tickDuration,
      target => this.performPointerMoveStep(state, inputSource, target, win)
    );
  }

  /**
   * Perform one part of a pointer move corresponding to a specific emitted event.
   *
   * @param {State} state - Actions state.
   * @param {InputSource} inputSource - State of the current input device.
   * @param {Array<Array<number>>} targets - Array of [x, y] arrays
   * specifying the viewport coordinates to move to.
   * @param {WindowProxy} win - Current window global.
   */
  performPointerMoveStep(state, inputSource, targets, win) {
    if (targets.length !== 1) {
      throw new Error(
        "PointerMoveAction.performPointerMoveStep requires a single target"
      );
    }

    const target = targets[0];
    lazy.logger.trace(
      `PointerMoveAction.performPointerMoveStep ${JSON.stringify(target)}`
    );
    if (target[0] == inputSource.x && target[1] == inputSource.y) {
      return;
    }

    inputSource.pointer.pointerMove(
      state,
      inputSource,
      this,
      target[0],
      target[1],
      win
    );

    inputSource.x = target[0];
    inputSource.y = target[1];
  }

  static fromJSON(id, actionItem) {
    const { duration, origin, x, y } = actionItem;

    if (duration !== undefined) {
      lazy.assert.positiveInteger(
        duration,
        lazy.pprint`Expected "duration" to be a positive integer, got ${duration}`
      );
    }

    const originObject = Origin.fromJSON(origin);
    lazy.assert.integer(
      x,
      lazy.pprint`Expected "x" to be an integer, got ${x}`
    );
    lazy.assert.integer(
      y,
      lazy.pprint`Expected "y" to be an integer, got ${y}`
    );
    const props = PointerAction.validateCommon(actionItem);

    props.duration = duration;
    props.origin = originObject;
    props.x = x;
    props.y = y;

    return new this(id, props);
  }
}

/**
 * Action associated with a wheel input device
 *
 */
class WheelAction extends Action {
  static type = "wheel";
}

/**
 * Action associated with scrolling a scroll wheel
 *
 * @param {number} duration - Duration of scroll in ms.
 * @param {Origin} origin - Origin of target coordinates.
 * @param {number} x - X value of scroll coordinates.
 * @param {number} y - Y value of scroll coordinates.
 * @param {number} deltaX - Number of CSS pixels to scroll in X direction.
 * @param {number} deltaY - Number of CSS pixels to scroll in Y direction
 */
class WheelScrollAction extends WheelAction {
  static subtype = "scroll";
  affectsWallClockTime = true;

  constructor(id, { duration, origin, x, y, deltaX, deltaY }) {
    super(id);

    this.duration = duration;
    this.origin = origin;
    this.x = x;
    this.y = y;
    this.deltaX = deltaX;
    this.deltaY = deltaY;
  }

  static fromJSON(id, actionItem) {
    const { duration, origin, x, y, deltaX, deltaY } = actionItem;

    if (duration !== undefined) {
      lazy.assert.positiveInteger(
        duration,
        lazy.pprint`Expected "duration" to be a positive integer, got ${duration}`
      );
    }

    const originObject = Origin.fromJSON(origin);
    if (originObject instanceof PointerOrigin) {
      throw new lazy.error.InvalidArgumentError(
        `"pointer" origin not supported for "wheel" input source.`
      );
    }

    lazy.assert.integer(
      x,
      lazy.pprint`Expected "x" to be an Integer, got ${x}`
    );
    lazy.assert.integer(
      y,
      lazy.pprint`Expected "y" to be an Integer, got ${y}`
    );
    lazy.assert.integer(
      deltaX,
      lazy.pprint`Expected "deltaX" to be an Integer, got ${deltaX}`
    );
    lazy.assert.integer(
      deltaY,
      lazy.pprint`Expected "deltaY" to be an Integer, got ${deltaY}`
    );

    return new this(id, {
      duration,
      origin: originObject,
      x,
      y,
      deltaX,
      deltaY,
    });
  }

  async dispatch(state, inputSource, tickDuration, win) {
    lazy.logger.trace(
      `Dispatch ${this.constructor.name} with id: ${this.id} deltaX: ${this.deltaX} deltaY: ${this.deltaY}`
    );

    const scrollCoordinates = this.origin.getTargetCoordinates(
      inputSource,
      [this.x, this.y],
      win
    );
    assertInViewPort(scrollCoordinates, win);

    const startX = 0;
    const startY = 0;
    // This is an action-local state that holds the amount of scroll completed
    const deltaPosition = [startX, startY];

    await moveOverTime(
      [[startX, startY]],
      [[this.deltaX, this.deltaY]],
      this.duration ?? tickDuration,
      deltaTarget =>
        this.performOneWheelScroll(
          state,
          scrollCoordinates,
          deltaPosition,
          deltaTarget,
          win
        )
    );
  }

  /**
   * Perform one part of a wheel scroll corresponding to a specific emitted event.
   *
   * @param {State} state - Actions state.
   * @param {Array<number>} scrollCoordinates - [x, y] viewport coordinates of the scroll.
   * @param {Array<number>} deltaPosition - [deltaX, deltaY] coordinates of the scroll before this event.
   * @param {Array<Array<number>>} deltaTargets - Array of [deltaX, deltaY] coordinates to scroll to.
   * @param {WindowProxy} win - Current window global.
   */
  performOneWheelScroll(
    state,
    scrollCoordinates,
    deltaPosition,
    deltaTargets,
    win
  ) {
    if (deltaTargets.length !== 1) {
      throw new Error("Can only scroll one wheel at a time");
    }
    if (deltaPosition[0] == this.deltaX && deltaPosition[1] == this.deltaY) {
      return;
    }

    const deltaTarget = deltaTargets[0];
    const deltaX = deltaTarget[0] - deltaPosition[0];
    const deltaY = deltaTarget[1] - deltaPosition[1];
    const eventData = new WheelEventData({
      deltaX,
      deltaY,
      deltaZ: 0,
    });
    eventData.update(state);

    lazy.event.synthesizeWheelAtPoint(
      scrollCoordinates[0],
      scrollCoordinates[1],
      eventData,
      win
    );

    // Update the current scroll position for the caller
    deltaPosition[0] = deltaTarget[0];
    deltaPosition[1] = deltaTarget[1];
  }
}

/**
 * Group of actions representing behaviour of all touch pointers during a single tick.
 *
 * For touch pointers, we need to call into the platform once with all
 * the actions so that they are regarded as simultaneous. This means
 * we don't use the `dispatch()` method on the underlying actions, but
 * instead use one on this group object.
 */
class TouchActionGroup {
  static type = null;

  constructor() {
    this.type = this.constructor.type;
    this.actions = new Map();
  }

  static forType(type) {
    const cls = touchActionGroupTypes.get(type);

    return new cls();
  }

  /**
   * Add action corresponding to a specific pointer to the group.
   *
   * @param {InputSource} inputSource - State of the current input device.
   * @param {Action} action - Action to add to the group
   */
  addPointer(inputSource, action) {
    if (action.subtype !== this.type) {
      throw new Error(
        `Added action of unexpected type, got ${action.subtype}, expected ${this.type}`
      );
    }

    this.actions.set(action.id, [inputSource, action]);
  }

  /**
   * Dispatch the action group to the relevant window.
   *
   * This is overridden by subclasses to implement the type-specific
   * dispatch of the action.
   *
   * @returns {Promise} - Promise that is resolved once the action is complete.
   */
  dispatch() {
    throw new Error(
      "TouchActionGroup subclass missing dispatch implementation"
    );
  }
}

/**
 * Group of actions representing behaviour of all touch pointers
 * depressed during a single tick.
 */
class PointerDownTouchActionGroup extends TouchActionGroup {
  static type = "pointerDown";

  dispatch(state, inputSource, tickDuration, win) {
    lazy.logger.trace(
      `Dispatch ${this.constructor.name} with ${Array.from(
        this.actions.values()
      ).map(x => x[1].id)}`
    );

    return new Promise(resolve => {
      if (inputSource !== null) {
        throw new Error(
          "Expected null inputSource for PointerDownTouchActionGroup.dispatch"
        );
      }

      // Only include pointers that are not already depressed
      const actions = Array.from(this.actions.values()).filter(
        ([actionInputSource, action]) =>
          !actionInputSource.isPressed(action.button)
      );

      if (actions.length) {
        const eventData = new MultiTouchEventData("touchstart");

        for (const [actionInputSource, action] of actions) {
          // Skip if already pressed
          eventData.addPointerEventData(actionInputSource, action);
          actionInputSource.press(action.button);
          // Append a copy of |action| with pointerUp subtype
          state.inputsToCancel.push(new PointerUpAction(action.id, action));
          eventData.update(state, actionInputSource);
        }

        // Touch start events must include all depressed touch pointers
        for (const [id, pointerInputSource] of state.inputSourcesByType(
          "pointer"
        )) {
          if (
            pointerInputSource.pointer.type === "touch" &&
            !this.actions.has(id) &&
            pointerInputSource.isPressed(0)
          ) {
            eventData.addPointerEventData(pointerInputSource, {});
            eventData.update(state, pointerInputSource);
          }
        }
        lazy.event.synthesizeMultiTouch(eventData, win);
      }

      resolve();
    });
  }
}

/**
 * Group of actions representing behaviour of all touch pointers
 * released during a single tick.
 */
class PointerUpTouchActionGroup extends TouchActionGroup {
  static type = "pointerUp";

  dispatch(state, inputSource, tickDuration, win) {
    lazy.logger.trace(
      `Dispatch ${this.constructor.name} with ${Array.from(
        this.actions.values()
      ).map(x => x[1].id)}`
    );

    return new Promise(resolve => {
      if (inputSource !== null) {
        throw new Error(
          "Expected null inputSource for PointerUpTouchActionGroup.dispatch"
        );
      }

      // Only include pointers that are not already depressed
      const actions = Array.from(this.actions.values()).filter(
        ([actionInputSource, action]) =>
          actionInputSource.isPressed(action.button)
      );

      if (actions.length) {
        const eventData = new MultiTouchEventData("touchend");
        for (const [actionInputSource, action] of actions) {
          eventData.addPointerEventData(actionInputSource, action);
          actionInputSource.release(action.button);
          eventData.update(state, actionInputSource);
        }
        lazy.event.synthesizeMultiTouch(eventData, win);
      }

      resolve();
    });
  }
}

/**
 * Group of actions representing behaviour of all touch pointers
 * moved during a single tick.
 */
class PointerMoveTouchActionGroup extends TouchActionGroup {
  static type = "pointerMove";

  dispatch(state, inputSource, tickDuration, win) {
    lazy.logger.trace(
      `Dispatch ${this.constructor.name} with ${Array.from(this.actions).map(
        x => x[1].id
      )}`
    );
    if (inputSource !== null) {
      throw new Error(
        "Expected null inputSource for PointerMoveTouchActionGroup.dispatch"
      );
    }

    let startCoords = [];
    let targetCoords = [];

    for (const [actionInputSource, action] of this.actions.values()) {
      const target = action.origin.getTargetCoordinates(
        actionInputSource,
        [action.x, action.y],
        win
      );

      assertInViewPort(target, win);
      startCoords.push([actionInputSource.x, actionInputSource.y]);
      targetCoords.push(target);
    }

    // Touch move events must include all depressed touch pointers, even if they are static
    // This can end up generating pointermove events even for static pointers, but Gecko
    // seems to generate a lot of pointermove events anyway, so this seems like the lesser
    // problem.
    // See https://bugzilla.mozilla.org/show_bug.cgi?id=1779206
    const staticTouchPointers = [];
    for (const [id, pointerInputSource] of state.inputSourcesByType(
      "pointer"
    )) {
      if (
        pointerInputSource.pointer.type === "touch" &&
        !this.actions.has(id) &&
        pointerInputSource.isPressed(0)
      ) {
        staticTouchPointers.push(pointerInputSource);
      }
    }

    return moveOverTime(
      startCoords,
      targetCoords,
      this.duration ?? tickDuration,
      currentTargetCoords =>
        this.performPointerMoveStep(
          state,
          staticTouchPointers,
          currentTargetCoords,
          win
        )
    );
  }

  /**
   * Perform one part of a pointer move corresponding to a specific emitted event.
   *
   * @param {State} state - Actions state.
   * @param {Array.<PointerInputSource>} staticTouchPointers
   *      Array of PointerInputSource objects for pointers that aren't involved in
   *      the touch move.
   * @param {Array.<Array>} targetCoords
   *      Array of [x, y] arrays specifying the viewport coordinates to move to.
   * @param {WindowProxy} win - Current window global.
   */
  performPointerMoveStep(state, staticTouchPointers, targetCoords, win) {
    if (targetCoords.length !== this.actions.size) {
      throw new Error("Expected one target per pointer");
    }

    const perPointerData = Array.from(this.actions.values()).map(
      ([inputSource, action], i) => {
        const target = targetCoords[i];
        return [inputSource, action, target];
      }
    );
    const reachedTarget = perPointerData.every(
      ([inputSource, , target]) =>
        target[0] === inputSource.x && target[1] === inputSource.y
    );

    if (reachedTarget) {
      return;
    }

    const eventData = new MultiTouchEventData("touchmove");
    for (const [inputSource, action, target] of perPointerData) {
      inputSource.x = target[0];
      inputSource.y = target[1];
      eventData.addPointerEventData(inputSource, action);
      eventData.update(state, inputSource);
    }

    for (const inputSource of staticTouchPointers) {
      eventData.addPointerEventData(inputSource, {});
      eventData.update(state, inputSource);
    }

    lazy.event.synthesizeMultiTouch(eventData, win);
  }
}

const touchActionGroupTypes = new Map();
for (const cls of [
  PointerDownTouchActionGroup,
  PointerUpTouchActionGroup,
  PointerMoveTouchActionGroup,
]) {
  touchActionGroupTypes.set(cls.type, cls);
}

/**
 * Split a transition from startCoord to targetCoord linearly over duration.
 *
 * startCoords and targetCoords are lists of [x,y] positions in some space
 * (e.g. screen position or scroll delta). This function will linearly
 * interpolate intermediate positions, sending out roughly one event
 * per frame to simulate moving between startCoord and targetCoord in
 * a time of tickDuration milliseconds. The callback function is
 * responsible for actually emitting the event, given the current
 * position in the coordinate space.
 *
 * @param {Array.<Array>} startCoords
 *    Array of initial [x, y] coordinates for each input source involved
 *    in the move.
 * @param {Array.<Array>} targetCoords
 *    Array of target [x, y] coordinates for each input source involved
 *    in the move.
 * @param {number} duration - Time in ms the move will take.
 * @param {Function} callback
 *    Function that actually performs the move. This takes a single parameter
 *    which is an array of [x, y] coordinates corresponding to the move
 *    targets.
 */
async function moveOverTime(startCoords, targetCoords, duration, callback) {
  lazy.logger.trace(
    `moveOverTime start: ${startCoords} target: ${targetCoords} duration: ${duration}`
  );

  if (startCoords.length !== targetCoords.length) {
    throw new Error(
      "Expected equal number of start coordinates and target coordinates"
    );
  }

  if (
    !startCoords.every(item => item.length == 2) ||
    !targetCoords.every(item => item.length == 2)
  ) {
    throw new Error(
      "Expected start coordinates target coordinates to be Array of multiple [x,y] coordinates."
    );
  }

  if (duration === 0) {
    // transition to destination in one step
    callback(targetCoords);
    return;
  }

  const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  // interval between transitions in ms, based on common vsync
  const fps60 = 17;

  const distances = targetCoords.map((targetCoord, i) => {
    const startCoord = startCoords[i];
    return [targetCoord[0] - startCoord[0], targetCoord[1] - startCoord[1]];
  });
  const ONE_SHOT = Ci.nsITimer.TYPE_ONE_SHOT;
  const startTime = Date.now();
  const transitions = (async () => {
    // wait |fps60| ms before performing first incremental transition
    await new Promise(resolveTimer =>
      timer.initWithCallback(resolveTimer, fps60, ONE_SHOT)
    );

    let durationRatio = Math.floor(Date.now() - startTime) / duration;
    const epsilon = fps60 / duration / 10;
    while (1 - durationRatio > epsilon) {
      const intermediateTargets = startCoords.map((startCoord, i) => {
        let distance = distances[i];
        return [
          Math.floor(durationRatio * distance[0] + startCoord[0]),
          Math.floor(durationRatio * distance[1] + startCoord[1]),
        ];
      });
      callback(intermediateTargets);
      // wait |fps60| ms before performing next transition
      await new Promise(resolveTimer =>
        timer.initWithCallback(resolveTimer, fps60, ONE_SHOT)
      );

      durationRatio = Math.floor(Date.now() - startTime) / duration;
    }
  })();

  await transitions;

  // perform last transitionafter all incremental moves are resolved and
  // durationRatio is close enough to 1
  callback(targetCoords);
}

const actionTypes = new Map();
for (const cls of [
  KeyDownAction,
  KeyUpAction,
  PauseAction,
  PointerDownAction,
  PointerUpAction,
  PointerMoveAction,
  WheelScrollAction,
]) {
  if (!actionTypes.has(cls.type)) {
    actionTypes.set(cls.type, new Map());
  }
  actionTypes.get(cls.type).set(cls.subtype, cls);
}

/**
 * Implementation of the behaviour of a specific type of pointer
 */
class Pointer {
  /** Type of pointer */
  static type = null;

  constructor(id) {
    this.id = id;
    this.type = this.constructor.type;
  }

  /**
   * Implementation of depressing the pointer.
   */
  pointerDown() {
    throw new Error(`Unimplemented pointerDown for pointerType ${this.type}`);
  }

  /**
   * Implementation of releasing the pointer.
   */
  pointerUp() {
    throw new Error(`Unimplemented pointerUp for pointerType ${this.type}`);
  }

  /**
   * Implementation of moving the pointer.
   */
  pointerMove() {
    throw new Error(`Unimplemented pointerMove for pointerType ${this.type}`);
  }

  /**
   * @param {number} pointerId - Numeric pointer id.
   * @param {string} pointerType - Pointer type.
   * @returns {Pointer} - The pointer class for {@link pointerType}
   *
   * @throws {InvalidArgumentError} - If {@link pointerType} is not a valid pointer type.
   */
  static fromJSON(pointerId, pointerType) {
    const cls = pointerTypes.get(pointerType);

    if (cls === undefined) {
      throw new lazy.error.InvalidArgumentError(
        'Expected "pointerType" type to be one of ' +
          lazy.pprint`${pointerTypes}, got ${pointerType}`
      );
    }

    return new cls(pointerId);
  }
}

/**
 * Implementation of mouse pointer behaviour
 */
class MousePointer extends Pointer {
  static type = "mouse";

  pointerDown(state, inputSource, action, win) {
    const mouseEvent = new MouseEventData("mousedown", {
      button: action.button,
    });
    mouseEvent.update(state, inputSource);

    if (mouseEvent.ctrlKey) {
      if (lazy.AppInfo.isMac) {
        mouseEvent.button = 2;
        state.clickTracker.reset();
      }
    } else {
      mouseEvent.clickCount = state.clickTracker.count + 1;
    }

    lazy.event.synthesizeMouseAtPoint(
      inputSource.x,
      inputSource.y,
      mouseEvent,
      win
    );

    if (
      lazy.event.MouseButton.isSecondary(mouseEvent.button) ||
      (mouseEvent.ctrlKey && lazy.AppInfo.isMac)
    ) {
      const contextMenuEvent = { ...mouseEvent, type: "contextmenu" };
      lazy.event.synthesizeMouseAtPoint(
        inputSource.x,
        inputSource.y,
        contextMenuEvent,
        win
      );
    }
  }

  pointerUp(state, inputSource, action, win) {
    const mouseEvent = new MouseEventData("mouseup", {
      button: action.button,
    });
    mouseEvent.update(state, inputSource);

    state.clickTracker.setClick(action.button);
    mouseEvent.clickCount = state.clickTracker.count;

    lazy.event.synthesizeMouseAtPoint(
      inputSource.x,
      inputSource.y,
      mouseEvent,
      win
    );
  }

  pointerMove(state, inputSource, action, targetX, targetY, win) {
    const mouseEvent = new MouseEventData("mousemove");
    mouseEvent.update(state, inputSource);

    lazy.event.synthesizeMouseAtPoint(targetX, targetY, mouseEvent, win);

    state.clickTracker.reset();
  }
}

/*
 * The implementation here is empty because touch actions have to go via the
 * TouchActionGroup. So if we end up calling these methods that's a bug in
 * the code.
 */
class TouchPointer extends Pointer {
  static type = "touch";
}

/*
 * Placeholder for future pen type pointer support.
 */
class PenPointer extends Pointer {
  static type = "pen";
}

const pointerTypes = new Map();
for (const cls of [MousePointer, TouchPointer, PenPointer]) {
  pointerTypes.set(cls.type, cls);
}

/**
 * Represents a series of ticks, specifying which actions to perform at
 * each tick.
 */
action.Chain = class extends Array {
  toString() {
    return `[chain ${super.toString()}]`;
  }

  /**
   * Dispatch the action chain to the relevant window.
   *
   * @param {State} state - Actions state.
   * @param {WindowProxy} win - Current window global.
   * @returns {Promise} - Promise that is resolved once the action
   * chain is complete.
   */
  dispatch(state, win) {
    let i = 1;

    const chainEvents = (async () => {
      for (const tickActions of this) {
        lazy.logger.trace(`Dispatching tick ${i++}/${this.length}`);
        await tickActions.dispatch(state, win);
      }
    })();

    // Reset the current click tracker counter. We shouldn't be able to simulate
    // a double click with multiple action chains.
    state.clickTracker.reset();

    return chainEvents;
  }

  /**
   * @param {State} state - Actions state.
   * @param {Array.<object>} actions - Array of objects that each
   * represent an action sequence.
   * @returns {action.Chain} - Object that allows dispatching a chain
   * of actions.
   * @throws {InvalidArgumentError} - If actions doesn't correspond to
   * a valid action chain.
   */
  static fromJSON(state, actions) {
    lazy.assert.array(
      actions,
      lazy.pprint`Expected "actions" to be an array, got ${actions}`
    );

    const actionsByTick = new this();
    for (const actionSequence of actions) {
      lazy.assert.object(
        actionSequence,
        'Expected "actions" item to be an object, ' +
          lazy.pprint`got ${actionSequence}`
      );

      const inputSourceActions = Sequence.fromJSON(state, actionSequence);

      for (let i = 0; i < inputSourceActions.length; i++) {
        // new tick
        if (actionsByTick.length < i + 1) {
          actionsByTick.push(new TickActions());
        }
        actionsByTick[i].push(inputSourceActions[i]);
      }
    }

    return actionsByTick;
  }
};

/**
 * Represents the action for each input device to perform in a single tick.
 */
class TickActions extends Array {
  /**
   * Tick duration in milliseconds.
   *
   * @returns {number} - Longest action duration in |tickActions| if any, or 0.
   */
  getDuration() {
    let max = 0;

    for (const action of this) {
      if (action.affectsWallClockTime && action.duration) {
        max = Math.max(action.duration, max);
      }
    }

    return max;
  }

  /**
   * Dispatch sequence of actions for this tick.
   *
   * This creates a Promise for one tick that resolves once the Promise
   * for each tick-action is resolved, which takes at least |tickDuration|
   * milliseconds.  The resolved set of events for each tick is followed by
   * firing of pending DOM events.
   *
   * Note that the tick-actions are dispatched in order, but they may have
   * different durations and therefore may not end in the same order.
   *
   * @param {State} state - Actions state.
   * @param {WindowProxy} win - Current window global.
   *
   * @returns {Promise} - Promise that resolves when tick is complete.
   */
  dispatch(state, win) {
    const tickDuration = this.getDuration();
    const tickActions = this.groupTickActions(state);
    const pendingEvents = tickActions.map(([inputSource, action]) =>
      action.dispatch(state, inputSource, tickDuration, win)
    );

    return Promise.all(pendingEvents);
  }

  /**
   * Group together actions from input sources that have to be
   * dispatched together.
   *
   * The actual transformation here is to group together touch pointer
   * actions into {@link TouchActionGroup} instances.
   *
   * @param {State} state - Actions state.
   * @returns {Array.<Array.<InputSource?,Action|TouchActionGroup>>}
   *    Array of pairs. For ungrouped actions each element is
   *    [InputSource, Action] For touch actions there are multiple
   *    pointers handled at once, so the first item of the array is
   *    null, meaning the group has to perform its own handling of the
   *    relevant state, and the second element is a TouuchActionGroup.
   */
  groupTickActions(state) {
    const touchActions = new Map();
    const actions = [];

    for (const action of this) {
      const inputSource = state.getInputSource(action.id);
      if (action.type == "pointer" && inputSource.pointer.type === "touch") {
        lazy.logger.debug(
          `Grouping action ${action.type} ${action.id} ${action.subtype}`
        );
        let group = touchActions.get(action.subtype);
        if (group === undefined) {
          group = TouchActionGroup.forType(action.subtype);
          touchActions.set(action.subtype, group);
          actions.push([null, group]);
        }
        group.addPointer(inputSource, action);
      } else {
        actions.push([inputSource, action]);
      }
    }

    return actions;
  }
}

/**
 * Represents one input source action sequence; this is essentially an
 * |Array.<Action>|.
 *
 * This is a temporary object only used when constructing an {@link
 * action.Chain}.
 */
class Sequence extends Array {
  toString() {
    return `[sequence ${super.toString()}]`;
  }

  /**
   * @param {State} state - Actions state.
   * @param {object} actionSequence
   *     Protocol representation of the actions for a specific input source.
   * @returns {Array.<Array>} - Array of [InputSource?,Action|TouchActionGroup]
   */
  static fromJSON(state, actionSequence) {
    // used here to validate 'type' in addition to InputSource type below
    const { id, type, actions } = actionSequence;

    // type and id get validated in InputSource.fromJSON
    lazy.assert.array(
      actions,
      'Expected "actionSequence.actions" to be an array, ' +
        lazy.pprint`got ${actionSequence.actions}`
    );

    // This sets the input state in the global state map, if it's new
    InputSource.fromJSON(state, actionSequence);

    const sequence = new this();
    for (const actionItem of actions) {
      sequence.push(Action.fromJSON(type, id, actionItem));
    }

    return sequence;
  }
}

/**
 * Representation of an input event
 */
class InputEventData {
  constructor() {
    this.altKey = false;
    this.shiftKey = false;
    this.ctrlKey = false;
    this.metaKey = false;
  }

  /**
   * Update the input data based on global and input state
   */
  update() {}

  toString() {
    return `${this.constructor.name} ${JSON.stringify(this)}`;
  }
}

/**
 * Representation of a key input event
 *
 * @param {string} rawKey - Key value.
 */
class KeyEventData extends InputEventData {
  constructor(rawKey) {
    super();
    const { key, code, location, printable } = lazy.keyData.getData(rawKey);

    this.key = key;
    this.code = code;
    this.location = location;
    this.printable = printable;
    this.repeat = false;
    // keyCode will be computed by event.sendKeyDown
  }

  update(state, inputSource) {
    this.altKey = inputSource.alt;
    this.shiftKey = inputSource.shift;
    this.ctrlKey = inputSource.ctrl;
    this.metaKey = inputSource.meta;
  }
}

/**
 * Representation of a pointer input event
 *
 * @param {string} type - Event type.
 */
class PointerEventData extends InputEventData {
  constructor(type) {
    super();

    this.type = type;
    this.buttons = 0;
  }

  update(state, inputSource) {
    // set modifier properties based on whether any corresponding keys are
    // pressed on any key input source
    for (const [, otherInputSource] of state.inputSourcesByType("key")) {
      this.altKey = otherInputSource.alt || this.altKey;
      this.ctrlKey = otherInputSource.ctrl || this.ctrlKey;
      this.metaKey = otherInputSource.meta || this.metaKey;
      this.shiftKey = otherInputSource.shift || this.shiftKey;
    }
    const allButtons = Array.from(inputSource.pressed);
    this.buttons = allButtons.reduce(
      (a, i) => a + PointerEventData.getButtonFlag(i),
      0
    );
  }

  /**
   * Return a flag for buttons which indicates a button is pressed.
   *
   * @param {integer} button - Mouse button number.
   */
  static getButtonFlag(button) {
    switch (button) {
      case 1:
        return 4;
      case 2:
        return 2;
      default:
        return Math.pow(2, button);
    }
  }
}

/**
 * Representation of a mouse input event
 *
 * @param {string} type - Event type.
 * @param {object=} options
 * @param {number} options.button - Mouse button number.
 */
class MouseEventData extends PointerEventData {
  constructor(type, options = {}) {
    super(type);

    const { button = 0 } = options;

    this.button = button;
    this.buttons = 0;

    // Some WPTs try to synthesize DnD only with mouse events.  However,
    // Gecko waits DnD events directly and non-WPT-tests use Gecko specific
    // test API to synthesize DnD.  Therefore, we want new path only for
    // synthesized events coming from the webdriver.
    this.allowToHandleDragDrop = true;
  }

  update(state, inputSource) {
    super.update(state, inputSource);

    this.id = inputSource.pointer.id;
  }
}

/**
 * Representation of a wheel scroll event
 *
 * @param {object} options
 * @param {number} options.deltaX - Scroll delta X.
 * @param {number} options.deltaY - Scroll delta Y.
 * @param {number} options.deltaY - Scroll delta Z (current always 0).
 * @param {number=} options.deltaMode - Scroll delta mode (current always 0).
 */
class WheelEventData extends InputEventData {
  constructor(options) {
    super();

    const { deltaX, deltaY, deltaZ, deltaMode = 0 } = options;

    this.deltaX = deltaX;
    this.deltaY = deltaY;
    this.deltaZ = deltaZ;
    this.deltaMode = deltaMode;

    this.altKey = false;
    this.ctrlKey = false;
    this.metaKey = false;
    this.shiftKey = false;
  }

  update(state) {
    // set modifier properties based on whether any corresponding keys are
    // pressed on any key input source
    for (const [, otherInputSource] of state.inputSourcesByType("key")) {
      this.altKey = otherInputSource.alt || this.altKey;
      this.ctrlKey = otherInputSource.ctrl || this.ctrlKey;
      this.metaKey = otherInputSource.meta || this.metaKey;
      this.shiftKey = otherInputSource.shift || this.shiftKey;
    }
  }
}

/**
 * Representation of a multitouch event
 *
 * @param {string} type - Event type.
 */
class MultiTouchEventData extends PointerEventData {
  #setGlobalState;

  constructor(type) {
    super(type);

    this.id = [];
    this.x = [];
    this.y = [];
    this.rx = [];
    this.ry = [];
    this.angle = [];
    this.force = [];
    this.tiltx = [];
    this.tilty = [];
    this.twist = [];
    this.#setGlobalState = false;
  }

  /**
   * Add the data from one pointer to the event.
   *
   * @param {InputSource} inputSource - State of the pointer.
   * @param {PointerAction} action - Action for the pointer.
   */
  addPointerEventData(inputSource, action) {
    this.x.push(inputSource.x);
    this.y.push(inputSource.y);
    this.id.push(inputSource.pointer.id);
    this.rx.push(action.width || 1);
    this.ry.push(action.height || 1);
    this.angle.push(0);
    this.force.push(action.pressure || (this.type === "touchend" ? 0 : 1));
    this.tiltx.push(action.tiltX || 0);
    this.tilty.push(action.tiltY || 0);
    this.twist.push(action.twist || 0);
  }

  update(state, inputSource) {
    // We call update once per input source, but only want to update global state once.
    // Instead of introducing a new lifecycle method, or changing the API to allow multiple
    // input sources in a single call, use a small bit of state to avoid repeatedly setting
    // global state.
    if (!this.#setGlobalState) {
      // set modifier properties based on whether any corresponding keys are
      // pressed on any key input source
      for (const [, otherInputSource] of state.inputSourcesByType("key")) {
        this.altKey = otherInputSource.alt || this.altKey;
        this.ctrlKey = otherInputSource.ctrl || this.ctrlKey;
        this.metaKey = otherInputSource.meta || this.metaKey;
        this.shiftKey = otherInputSource.shift || this.shiftKey;
      }
      this.#setGlobalState = true;
    }

    // Note that we currently emit Touch events that don't have this property
    // but pointer events should have a `buttons` property, so we'll compute it
    // anyway.
    const allButtons = Array.from(inputSource.pressed);
    this.buttons =
      this.buttons |
      allButtons.reduce((a, i) => a + PointerEventData.getButtonFlag(i), 0);
  }
}

// helpers

/**
 * Assert that target is in the viewport of win.
 *
 * @param {Array.<number>} target - [x, y] coordinates of target
 * relative to viewport.
 * @param {WindowProxy} win - target window.
 * @throws {MoveTargetOutOfBoundsError} - If target is outside the
 * viewport.
 */
function assertInViewPort(target, win) {
  const [x, y] = target;

  lazy.assert.number(
    x,
    lazy.pprint`Expected "x" to be finite number, got ${x}`
  );
  lazy.assert.number(
    y,
    lazy.pprint`Expected "y" to be finite number, got ${y}`
  );

  // Viewport includes scrollbars if rendered.
  if (x < 0 || y < 0 || x > win.innerWidth || y > win.innerHeight) {
    throw new lazy.error.MoveTargetOutOfBoundsError(
      `Move target (${x}, ${y}) is out of bounds of viewport dimensions ` +
        `(${win.innerWidth}, ${win.innerHeight})`
    );
  }
}
PK
!<N0�8+5+55chrome/remote/content/shared/webdriver/Assert.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
});

/**
 * Shorthands for common assertions made in WebDriver.
 *
 * @namespace
 */
export const assert = {};

/**
 * Asserts that WebDriver has an active session.
 *
 * @param {WebDriverSession} session
 *     WebDriver session instance.
 * @param {string=} msg
 *     Custom error message.
 *
 * @throws {InvalidSessionIDError}
 *     If session does not exist, or has an invalid id.
 */
assert.session = function (session, msg = "") {
  msg = msg || "WebDriver session does not exist, or is not active";
  assert.that(
    session => session && typeof session.id == "string",
    msg,
    lazy.error.InvalidSessionIDError
  )(session);
};

/**
 * Asserts that the current browser is Firefox Desktop.
 *
 * @param {string=} msg
 *     Custom error message.
 *
 * @throws {UnsupportedOperationError}
 *     If current browser is not Firefox.
 */
assert.firefox = function (msg = "") {
  msg = msg || "Only supported in Firefox";
  assert.that(
    isFirefox => isFirefox,
    msg,
    lazy.error.UnsupportedOperationError
  )(lazy.AppInfo.isFirefox);
};

/**
 * Asserts that the current application is Firefox Desktop or Thunderbird.
 *
 * @param {string=} msg
 *     Custom error message.
 *
 * @throws {UnsupportedOperationError}
 *     If current application is not running on desktop.
 */
assert.desktop = function (msg = "") {
  msg = msg || "Only supported in desktop applications";
  assert.that(
    isDesktop => isDesktop,
    msg,
    lazy.error.UnsupportedOperationError
  )(!lazy.AppInfo.isAndroid);
};

/**
 * Asserts that the current application runs on Android.
 *
 * @param {string=} msg
 *     Custom error message.
 *
 * @throws {UnsupportedOperationError}
 *     If current application is not running on Android.
 */
assert.mobile = function (msg = "") {
  msg = msg || "Only supported on Android";
  assert.that(
    isAndroid => isAndroid,
    msg,
    lazy.error.UnsupportedOperationError
  )(lazy.AppInfo.isAndroid);
};

/**
 * Asserts that the current <var>context</var> is content.
 *
 * @param {string} context
 *     Context to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @returns {string}
 *     <var>context</var> is returned unaltered.
 *
 * @throws {UnsupportedOperationError}
 *     If <var>context</var> is not content.
 */
assert.content = function (context, msg = "") {
  msg = msg || "Only supported in content context";
  assert.that(
    c => c.toString() == "content",
    msg,
    lazy.error.UnsupportedOperationError
  )(context);
};

/**
 * Asserts that the {@link CanonicalBrowsingContext} is open.
 *
 * @param {CanonicalBrowsingContext} browsingContext
 *     Canonical browsing context to check.
 * @param {string=} msg
 *     Custom error message.
 *
 * @returns {CanonicalBrowsingContext}
 *     <var>browsingContext</var> is returned unaltered.
 *
 * @throws {NoSuchWindowError}
 *     If <var>browsingContext</var> is no longer open.
 */
assert.open = function (browsingContext, msg = "") {
  msg = msg || "Browsing context has been discarded";
  return assert.that(
    browsingContext => {
      if (!browsingContext?.currentWindowGlobal) {
        return false;
      }

      if (browsingContext.isContent && !browsingContext.top.embedderElement) {
        return false;
      }

      return true;
    },
    msg,
    lazy.error.NoSuchWindowError
  )(browsingContext);
};

/**
 * Asserts that there is no current user prompt.
 *
 * @param {modal.Dialog} dialog
 *     Reference to current dialogue.
 * @param {string=} msg
 *     Custom error message.
 *
 * @throws {UnexpectedAlertOpenError}
 *     If there is a user prompt.
 */
assert.noUserPrompt = function (dialog, msg = "") {
  assert.that(
    d => d === null || typeof d == "undefined",
    msg,
    lazy.error.UnexpectedAlertOpenError
  )(dialog);
};

/**
 * Asserts that <var>obj</var> is defined.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @returns {?}
 *     <var>obj</var> is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If <var>obj</var> is not defined.
 */
assert.defined = function (obj, msg = "") {
  msg = msg || lazy.pprint`Expected ${obj} to be defined`;
  return assert.that(o => typeof o != "undefined", msg)(obj);
};

/**
 * Asserts that <var>obj</var> is a finite number.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @returns {number}
 *     <var>obj</var> is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If <var>obj</var> is not a number.
 */
assert.number = function (obj, msg = "") {
  msg = msg || lazy.pprint`Expected ${obj} to be finite number`;
  return assert.that(Number.isFinite, msg)(obj);
};

/**
 * Asserts that <var>obj</var> is a positive number.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @returns {number}
 *     <var>obj</var> is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If <var>obj</var> is not a positive integer.
 */
assert.positiveNumber = function (obj, msg = "") {
  assert.number(obj, msg);
  msg = msg || lazy.pprint`Expected ${obj} to be >= 0`;
  return assert.that(n => n >= 0, msg)(obj);
};

/**
 * Asserts that <var>obj</var> is a number in the inclusive range <var>lower</var> to <var>upper</var>.
 *
 * @param {?} obj
 *     Value to test.
 * @param {Array<number>} range
 *     Array range [lower, upper]
 * @param {string=} msg
 *     Custom error message.
 *
 * @returns {number}
 *     <var>obj</var> is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If <var>obj</var> is not a number in the specified range.
 */
assert.numberInRange = function (obj, range, msg = "") {
  const [lower, upper] = range;
  assert.number(obj, msg);
  msg = msg || lazy.pprint`Expected ${obj} to be >= ${lower} and <= ${upper}`;
  return assert.that(n => n >= lower && n <= upper, msg)(obj);
};

/**
 * Asserts that <var>obj</var> is callable.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @returns {Function}
 *     <var>obj</var> is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If <var>obj</var> is not callable.
 */
assert.callable = function (obj, msg = "") {
  msg = msg || lazy.pprint`${obj} is not callable`;
  return assert.that(o => typeof o == "function", msg)(obj);
};

/**
 * Asserts that <var>obj</var> is an unsigned short number.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @returns {number}
 *     <var>obj</var> is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If <var>obj</var> is not an unsigned short.
 */
assert.unsignedShort = function (obj, msg = "") {
  msg = msg || lazy.pprint`Expected ${obj} to be >= 0 and < 65536`;
  return assert.that(n => n >= 0 && n < 65536, msg)(obj);
};

/**
 * Asserts that <var>obj</var> is an integer.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @returns {number}
 *     <var>obj</var> is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If <var>obj</var> is not an integer.
 */
assert.integer = function (obj, msg = "") {
  msg = msg || lazy.pprint`Expected ${obj} to be an integer`;
  return assert.that(Number.isSafeInteger, msg)(obj);
};

/**
 * Asserts that <var>obj</var> is a positive integer.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @returns {number}
 *     <var>obj</var> is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If <var>obj</var> is not a positive integer.
 */
assert.positiveInteger = function (obj, msg = "") {
  assert.integer(obj, msg);
  msg = msg || lazy.pprint`Expected ${obj} to be >= 0`;
  return assert.that(n => n >= 0, msg)(obj);
};

/**
 * Asserts that <var>obj</var> is an integer in the inclusive range <var>lower</var> to <var>upper</var>.
 *
 * @param {?} obj
 *     Value to test.
 * @param {Array<number>} range
 *     Array range [lower, upper]
 * @param {string=} msg
 *     Custom error message.
 *
 * @returns {number}
 *     <var>obj</var> is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If <var>obj</var> is not a number in the specified range.
 */
assert.integerInRange = function (obj, range, msg = "") {
  const [lower, upper] = range;
  assert.integer(obj, msg);
  msg = msg || lazy.pprint`Expected ${obj} to be >= ${lower} and <= ${upper}`;
  return assert.that(n => n >= lower && n <= upper, msg)(obj);
};

/**
 * Asserts that <var>obj</var> is a boolean.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @returns {boolean}
 *     <var>obj</var> is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If <var>obj</var> is not a boolean.
 */
assert.boolean = function (obj, msg = "") {
  msg = msg || lazy.pprint`Expected ${obj} to be boolean`;
  return assert.that(b => typeof b == "boolean", msg)(obj);
};

/**
 * Asserts that <var>obj</var> is a string.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @returns {string}
 *     <var>obj</var> is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If <var>obj</var> is not a string.
 */
assert.string = function (obj, msg = "") {
  msg = msg || lazy.pprint`Expected ${obj} to be a string`;
  return assert.that(s => typeof s == "string", msg)(obj);
};

/**
 * Asserts that <var>obj</var> is an object.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @returns {object}
 *     obj| is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If <var>obj</var> is not an object.
 */
assert.object = function (obj, msg = "") {
  msg = msg || lazy.pprint`Expected ${obj} to be an object`;
  return assert.that(o => {
    // unable to use instanceof because LHS and RHS may come from
    // different globals
    let s = Object.prototype.toString.call(o);
    return s == "[object Object]" || s == "[object nsJSIID]";
  }, msg)(obj);
};

/**
 * Asserts that <var>obj</var> is an instance of a specified class.
 * <var>constructor</var> should have a static isInstance method implemented.
 *
 * @param {?} obj
 *     Value to test.
 * @param {?} constructor
 *     Class constructor.
 * @param {string=} msg
 *     Custom error message.
 *
 * @returns {object}
 *     <var>obj</var> is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If <var>obj</var> is not an instance of a specified class.
 */
assert.isInstance = function (obj, constructor, msg = "") {
  assert.object(obj, msg);
  assert.object(constructor.prototype, msg);

  msg =
    msg ||
    lazy.pprint`Expected ${obj} to be an instance of ${constructor.name}`;
  return assert.that(
    o => Object.hasOwn(constructor, "isInstance") && constructor.isInstance(o),
    msg
  )(obj);
};

/**
 * Asserts that <var>prop</var> is in <var>obj</var>.
 *
 * @param {?} prop
 *     An array element or own property to test if is in <var>obj</var>.
 * @param {?} obj
 *     An array or an Object that is being tested.
 * @param {string=} msg
 *     Custom error message.
 *
 * @returns {?}
 *     The array element, or the value of <var>obj</var>'s own property
 *     <var>prop</var>.
 *
 * @throws {InvalidArgumentError}
 *     If the <var>obj</var> was an array and did not contain <var>prop</var>.
 *     Otherwise if <var>prop</var> is not in <var>obj</var>, or <var>obj</var>
 *     is not an object.
 */
assert.in = function (prop, obj, msg = "") {
  if (Array.isArray(obj)) {
    assert.that(p => obj.includes(p), msg)(prop);
    return prop;
  }
  assert.object(obj, msg);
  msg = msg || lazy.pprint`Expected ${prop} in ${obj}`;
  assert.that(p => obj.hasOwnProperty(p), msg)(prop);
  return obj[prop];
};

/**
 * Asserts that <var>obj</var> is an Array.
 *
 * @param {?} obj
 *     Value to test.
 * @param {string=} msg
 *     Custom error message.
 *
 * @returns {object}
 *     <var>obj</var> is returned unaltered.
 *
 * @throws {InvalidArgumentError}
 *     If <var>obj</var> is not an Array.
 */
assert.array = function (obj, msg = "") {
  msg = msg || lazy.pprint`Expected ${obj} to be an Array`;
  return assert.that(Array.isArray, msg)(obj);
};

/**
 * Returns a function that is used to assert the |predicate|.
 *
 * @param {function(?): boolean} predicate
 *     Evaluated on calling the return value of this function.  If its
 *     return value of the inner function is false, <var>error</var>
 *     is thrown with <var>message</var>.
 * @param {string=} message
 *     Custom error message.
 * @param {Error=} err
 *     Custom error type by its class.
 *
 * @returns {function(?): ?}
 *     Function that takes and returns the passed in value unaltered,
 *     and which may throw <var>error</var> with <var>message</var>
 *     if <var>predicate</var> evaluates to false.
 */
assert.that = function (
  predicate,
  message = "",
  err = lazy.error.InvalidArgumentError
) {
  return obj => {
    if (!predicate(obj)) {
      throw new err(message);
    }
    return obj;
  };
};
PK
!<��>�'t't;chrome/remote/content/shared/webdriver/Capabilities.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
  RemoteAgent: "chrome://remote/content/components/RemoteAgent.sys.mjs",
  UserPromptHandler:
    "chrome://remote/content/shared/webdriver/UserPromptHandler.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "debuggerAddress", () => {
  return lazy.RemoteAgent.running && lazy.RemoteAgent.cdp
    ? lazy.remoteAgent.debuggerAddress
    : null;
});

ChromeUtils.defineLazyGetter(lazy, "isHeadless", () => {
  return Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo).isHeadless;
});

ChromeUtils.defineLazyGetter(lazy, "remoteAgent", () => {
  return Cc["@mozilla.org/remote/agent;1"].createInstance(Ci.nsIRemoteAgent);
});

ChromeUtils.defineLazyGetter(lazy, "userAgent", () => {
  return Cc["@mozilla.org/network/protocol;1?name=http"].getService(
    Ci.nsIHttpProtocolHandler
  ).userAgent;
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "shutdownTimeout",
  "toolkit.asyncshutdown.crash_timeout"
);

// List of capabilities which are only relevant for Webdriver Classic.
export const WEBDRIVER_CLASSIC_CAPABILITIES = [
  "pageLoadStrategy",
  "strictFileInteractability",
  "timeouts",
  "webSocketUrl",

  // Gecko specific capabilities
  "moz:accessibilityChecks",
  "moz:debuggerAddress",
  "moz:firefoxOptions",
  "moz:webdriverClick",

  // Extension capabilities
  "webauthn:extension:credBlob",
  "webauthn:extension:largeBlob",
  "webauthn:extension:prf",
  "webauthn:extension:uvm",
  "webauthn:virtualAuthenticators",
];

/** Representation of WebDriver session timeouts. */
export class Timeouts {
  constructor() {
    // disabled
    this.implicit = 0;
    // five minutes
    this.pageLoad = 300000;
    // 30 seconds
    this.script = 30000;
  }

  toString() {
    return "[object Timeouts]";
  }

  /** Marshals timeout durations to a JSON Object. */
  toJSON() {
    return {
      implicit: this.implicit,
      pageLoad: this.pageLoad,
      script: this.script,
    };
  }

  static fromJSON(json) {
    lazy.assert.object(
      json,
      lazy.pprint`Expected "timeouts" to be an object, got ${json}`
    );
    let t = new Timeouts();

    for (let [type, ms] of Object.entries(json)) {
      switch (type) {
        case "implicit":
          t.implicit = lazy.assert.positiveInteger(
            ms,
            `Expected "${type}" to be a positive integer, ` +
              lazy.pprint`got ${ms}`
          );
          break;

        case "script":
          if (ms !== null) {
            lazy.assert.positiveInteger(
              ms,
              `Expected "${type}" to be a positive integer, ` +
                lazy.pprint`got ${ms}`
            );
          }
          t.script = ms;
          break;

        case "pageLoad":
          t.pageLoad = lazy.assert.positiveInteger(
            ms,
            `Expected "${type}" to be a positive integer, ` +
              lazy.pprint`got ${ms}`
          );
          break;

        default:
          throw new lazy.error.InvalidArgumentError(
            `Unrecognized timeout: ${type}`
          );
      }
    }

    return t;
  }
}

/**
 * Enum of page loading strategies.
 *
 * @enum
 */
export const PageLoadStrategy = {
  /** No page load strategy.  Navigation will return immediately. */
  None: "none",
  /**
   * Eager, causing navigation to complete when the document reaches
   * the <code>interactive</code> ready state.
   */
  Eager: "eager",
  /**
   * Normal, causing navigation to return when the document reaches the
   * <code>complete</code> ready state.
   */
  Normal: "normal",
};

/** Proxy configuration object representation. */
export class Proxy {
  /** @class */
  constructor() {
    this.proxyType = null;
    this.httpProxy = null;
    this.httpProxyPort = null;
    this.noProxy = null;
    this.sslProxy = null;
    this.sslProxyPort = null;
    this.socksProxy = null;
    this.socksProxyPort = null;
    this.socksVersion = null;
    this.proxyAutoconfigUrl = null;
  }

  /**
   * Sets Firefox proxy settings.
   *
   * @returns {boolean}
   *     True if proxy settings were updated as a result of calling this
   *     function, or false indicating that this function acted as
   *     a no-op.
   */
  init() {
    switch (this.proxyType) {
      case "autodetect":
        Services.prefs.setIntPref("network.proxy.type", 4);
        return true;

      case "direct":
        Services.prefs.setIntPref("network.proxy.type", 0);
        return true;

      case "manual":
        Services.prefs.setIntPref("network.proxy.type", 1);

        if (this.httpProxy) {
          Services.prefs.setStringPref("network.proxy.http", this.httpProxy);
          if (Number.isInteger(this.httpProxyPort)) {
            Services.prefs.setIntPref(
              "network.proxy.http_port",
              this.httpProxyPort
            );
          }
        }

        if (this.sslProxy) {
          Services.prefs.setStringPref("network.proxy.ssl", this.sslProxy);
          if (Number.isInteger(this.sslProxyPort)) {
            Services.prefs.setIntPref(
              "network.proxy.ssl_port",
              this.sslProxyPort
            );
          }
        }

        if (this.socksProxy) {
          Services.prefs.setStringPref("network.proxy.socks", this.socksProxy);
          if (Number.isInteger(this.socksProxyPort)) {
            Services.prefs.setIntPref(
              "network.proxy.socks_port",
              this.socksProxyPort
            );
          }
          if (this.socksVersion) {
            Services.prefs.setIntPref(
              "network.proxy.socks_version",
              this.socksVersion
            );
          }
        }

        if (this.noProxy) {
          Services.prefs.setStringPref(
            "network.proxy.no_proxies_on",
            this.noProxy.join(", ")
          );
        }
        return true;

      case "pac":
        Services.prefs.setIntPref("network.proxy.type", 2);
        Services.prefs.setStringPref(
          "network.proxy.autoconfig_url",
          this.proxyAutoconfigUrl
        );
        return true;

      case "system":
        Services.prefs.setIntPref("network.proxy.type", 5);
        return true;

      default:
        return false;
    }
  }

  /**
   * @param {Record<string, ?>} json
   *     JSON Object to unmarshal.
   *
   * @throws {InvalidArgumentError}
   *     When proxy configuration is invalid.
   */
  static fromJSON(json) {
    function stripBracketsFromIpv6Hostname(hostname) {
      return hostname.includes(":")
        ? hostname.replace(/[\[\]]/g, "")
        : hostname;
    }

    // Parse hostname and optional port from host
    function fromHost(scheme, host) {
      lazy.assert.string(
        host,
        lazy.pprint`Expected proxy "host" to be a string, got ${host}`
      );

      if (host.includes("://")) {
        throw new lazy.error.InvalidArgumentError(`${host} contains a scheme`);
      }

      let url;
      try {
        // To parse the host a scheme has to be added temporarily.
        // If the returned value for the port is an empty string it
        // could mean no port or the default port for this scheme was
        // specified. In such a case parse again with a different
        // scheme to ensure we filter out the default port.
        url = new URL("http://" + host);
        if (url.port == "") {
          url = new URL("https://" + host);
        }
      } catch (e) {
        throw new lazy.error.InvalidArgumentError(e.message);
      }

      let hostname = stripBracketsFromIpv6Hostname(url.hostname);

      // If the port hasn't been set, use the default port of
      // the selected scheme (except for socks which doesn't have one).
      let port = parseInt(url.port);
      if (!Number.isInteger(port)) {
        if (scheme === "socks") {
          port = null;
        } else {
          port = Services.io.getDefaultPort(scheme);
        }
      }

      if (
        url.username != "" ||
        url.password != "" ||
        url.pathname != "/" ||
        url.search != "" ||
        url.hash != ""
      ) {
        throw new lazy.error.InvalidArgumentError(
          `${host} was not of the form host[:port]`
        );
      }

      return [hostname, port];
    }

    let p = new Proxy();
    if (typeof json == "undefined" || json === null) {
      return p;
    }

    lazy.assert.object(
      json,
      lazy.pprint`Expected "proxy" to be an object, got ${json}`
    );

    lazy.assert.in(
      "proxyType",
      json,
      lazy.pprint`Expected "proxyType" in "proxy" object, got ${json}`
    );
    p.proxyType = lazy.assert.string(
      json.proxyType,
      lazy.pprint`Expected "proxyType" to be a string, got ${json.proxyType}`
    );

    switch (p.proxyType) {
      case "autodetect":
      case "direct":
      case "system":
        break;

      case "pac":
        p.proxyAutoconfigUrl = lazy.assert.string(
          json.proxyAutoconfigUrl,
          `Expected "proxyAutoconfigUrl" to be a string, ` +
            lazy.pprint`got ${json.proxyAutoconfigUrl}`
        );
        break;

      case "manual":
        if (typeof json.ftpProxy != "undefined") {
          throw new lazy.error.InvalidArgumentError(
            "Since Firefox 90 'ftpProxy' is no longer supported"
          );
        }
        if (typeof json.httpProxy != "undefined") {
          [p.httpProxy, p.httpProxyPort] = fromHost("http", json.httpProxy);
        }
        if (typeof json.sslProxy != "undefined") {
          [p.sslProxy, p.sslProxyPort] = fromHost("https", json.sslProxy);
        }
        if (typeof json.socksProxy != "undefined") {
          [p.socksProxy, p.socksProxyPort] = fromHost("socks", json.socksProxy);
          p.socksVersion = lazy.assert.positiveInteger(
            json.socksVersion,
            lazy.pprint`Expected "socksVersion" to be a positive integer, got ${json.socksVersion}`
          );
        }
        if (typeof json.noProxy != "undefined") {
          let entries = lazy.assert.array(
            json.noProxy,
            lazy.pprint`Expected "noProxy" to be an array, got ${json.noProxy}`
          );
          p.noProxy = entries.map(entry => {
            lazy.assert.string(
              entry,
              lazy.pprint`Expected "noProxy" entry to be a string, got ${entry}`
            );
            return stripBracketsFromIpv6Hostname(entry);
          });
        }
        break;

      default:
        throw new lazy.error.InvalidArgumentError(
          `Invalid type of proxy: ${p.proxyType}`
        );
    }

    return p;
  }

  /**
   * @returns {Record<string, (number | string)>}
   *     JSON serialisation of proxy object.
   */
  toJSON() {
    function addBracketsToIpv6Hostname(hostname) {
      return hostname.includes(":") ? `[${hostname}]` : hostname;
    }

    function toHost(hostname, port) {
      if (!hostname) {
        return null;
      }

      // Add brackets around IPv6 addresses
      hostname = addBracketsToIpv6Hostname(hostname);

      if (port != null) {
        return `${hostname}:${port}`;
      }

      return hostname;
    }

    let excludes = this.noProxy;
    if (excludes) {
      excludes = excludes.map(addBracketsToIpv6Hostname);
    }

    return marshal({
      proxyType: this.proxyType,
      httpProxy: toHost(this.httpProxy, this.httpProxyPort),
      noProxy: excludes,
      sslProxy: toHost(this.sslProxy, this.sslProxyPort),
      socksProxy: toHost(this.socksProxy, this.socksProxyPort),
      socksVersion: this.socksVersion,
      proxyAutoconfigUrl: this.proxyAutoconfigUrl,
    });
  }

  toString() {
    return "[object Proxy]";
  }
}

export class Capabilities extends Map {
  /**
   * WebDriver session capabilities representation.
   *
   * @param {boolean} isBidi
   *     Flag indicating that it is a WebDriver BiDi session. Defaults to false.
   */
  constructor(isBidi = false) {
    // Default values for capabilities supported by both WebDriver protocols
    const defaults = [
      ["acceptInsecureCerts", false],
      ["browserName", getWebDriverBrowserName()],
      ["browserVersion", lazy.AppInfo.version],
      ["platformName", getWebDriverPlatformName()],
      ["proxy", new Proxy()],
      ["unhandledPromptBehavior", new lazy.UserPromptHandler()],
      ["userAgent", lazy.userAgent],

      // Gecko specific capabilities
      ["moz:buildID", lazy.AppInfo.appBuildID],
      ["moz:headless", lazy.isHeadless],
      ["moz:platformVersion", Services.sysinfo.getProperty("version")],
      ["moz:processID", lazy.AppInfo.processID],
      ["moz:profile", maybeProfile()],
      ["moz:shutdownTimeout", lazy.shutdownTimeout],
    ];

    if (!isBidi) {
      // HTTP-only capabilities
      defaults.push(
        ["pageLoadStrategy", PageLoadStrategy.Normal],
        ["timeouts", new Timeouts()],
        ["setWindowRect", !lazy.AppInfo.isAndroid],
        ["strictFileInteractability", false],

        ["moz:accessibilityChecks", false],
        ["moz:debuggerAddress", lazy.debuggerAddress],
        ["moz:webdriverClick", true],
        ["moz:windowless", false]
      );
    }

    super(defaults);
  }

  /**
   * @param {string} key
   *     Capability key.
   * @param {(string|number|boolean)} value
   *     JSON-safe capability value.
   */
  set(key, value) {
    if (key === "timeouts" && !(value instanceof Timeouts)) {
      throw new TypeError();
    } else if (key === "proxy" && !(value instanceof Proxy)) {
      throw new TypeError();
    }

    return super.set(key, value);
  }

  toString() {
    return "[object Capabilities]";
  }

  /**
   * JSON serialization of capabilities object.
   *
   * @returns {Record<string, ?>}
   */
  toJSON() {
    let marshalled = marshal(this);

    // Always return the proxy capability even if it's empty
    if (!("proxy" in marshalled)) {
      marshalled.proxy = {};
    }

    marshalled.timeouts = super.get("timeouts");
    marshalled.unhandledPromptBehavior = super.get("unhandledPromptBehavior");

    return marshalled;
  }

  /**
   * Unmarshal a JSON object representation of WebDriver capabilities.
   *
   * @param {Record<string, *>=} json
   *     WebDriver capabilities.
   * @param {boolean=} isBidi
   *     Flag indicating that it is a WebDriver BiDi session. Defaults to false.
   *
   * @returns {Capabilities}
   *     Internal representation of WebDriver capabilities.
   */
  static fromJSON(json, isBidi = false) {
    if (typeof json == "undefined" || json === null) {
      json = {};
    }
    lazy.assert.object(
      json,
      lazy.pprint`Expected "capabilities" to be an object, got ${json}"`
    );

    const capabilities = new Capabilities(isBidi);

    // TODO: Bug 1823907. We can start using here spec compliant method `validate`,
    // as soon as `desiredCapabilities` and `requiredCapabilities` are not supported.
    for (let [k, v] of Object.entries(json)) {
      if (isBidi && WEBDRIVER_CLASSIC_CAPABILITIES.includes(k)) {
        // Ignore any WebDriver classic capability for a WebDriver BiDi session.
        continue;
      }

      switch (k) {
        case "acceptInsecureCerts":
          lazy.assert.boolean(
            v,
            `Expected "${k}" to be a boolean, ` + lazy.pprint`got ${v}`
          );
          break;

        case "pageLoadStrategy":
          lazy.assert.string(
            v,
            `Expected "${k}" to be a string, ` + lazy.pprint`got ${v}`
          );
          if (!Object.values(PageLoadStrategy).includes(v)) {
            throw new lazy.error.InvalidArgumentError(
              "Unknown page load strategy: " + v
            );
          }
          break;

        case "proxy":
          v = Proxy.fromJSON(v);
          break;

        case "setWindowRect":
          lazy.assert.boolean(
            v,
            `Expected "${k}" to be a boolean, ` + lazy.pprint`got ${v}`
          );
          if (!lazy.AppInfo.isAndroid && !v) {
            throw new lazy.error.InvalidArgumentError(
              "setWindowRect cannot be disabled"
            );
          } else if (lazy.AppInfo.isAndroid && v) {
            throw new lazy.error.InvalidArgumentError(
              "setWindowRect is only supported on desktop"
            );
          }
          break;

        case "timeouts":
          v = Timeouts.fromJSON(v);
          break;

        case "strictFileInteractability":
          v = lazy.assert.boolean(
            v,
            `Expected "${k}" to be a boolean, ` + lazy.pprint`got ${v}`
          );
          break;

        case "unhandledPromptBehavior":
          v = lazy.UserPromptHandler.fromJSON(v);
          break;

        case "webSocketUrl":
          lazy.assert.boolean(
            v,
            `Expected "${k}" to be a boolean, ` + lazy.pprint`got ${v}`
          );

          if (!v) {
            throw new lazy.error.InvalidArgumentError(
              `Expected "${k}" to be true, ` + lazy.pprint`got ${v}`
            );
          }
          break;

        case "webauthn:virtualAuthenticators":
          lazy.assert.boolean(
            v,
            `Expected "${k}" to be a boolean, ` + lazy.pprint`got ${v}`
          );
          break;

        case "webauthn:extension:uvm":
          lazy.assert.boolean(
            v,
            `Expected "${k}" to be a boolean, ` + lazy.pprint`got ${v}`
          );
          break;

        case "webauthn:extension:prf":
          lazy.assert.boolean(
            v,
            `Expected "${k}" to be a boolean, ` + lazy.pprint`got ${v}`
          );
          break;

        case "webauthn:extension:largeBlob":
          lazy.assert.boolean(
            v,
            `Expected "${k}" to be a boolean, ` + lazy.pprint`got ${v}`
          );
          break;

        case "webauthn:extension:credBlob":
          lazy.assert.boolean(
            v,
            `Expected "${k}" to be a boolean, ` + lazy.pprint`got ${v}`
          );
          break;

        case "moz:accessibilityChecks":
          lazy.assert.boolean(
            v,
            `Expected "${k}" to be a boolean, ` + lazy.pprint`got ${v}`
          );
          break;

        // Don't set the value because it's only used to return the address
        // of the Remote Agent's debugger (HTTP server).
        case "moz:debuggerAddress":
          continue;

        case "moz:webdriverClick":
          lazy.assert.boolean(
            v,
            `Expected "${k}" to be a boolean, ` + lazy.pprint`got ${v}`
          );
          break;

        case "moz:windowless":
          lazy.assert.boolean(
            v,
            `Expected "${k}" to be a boolean, ` + lazy.pprint`got ${v}`
          );

          // Only supported on MacOS
          if (v && !lazy.AppInfo.isMac) {
            throw new lazy.error.InvalidArgumentError(
              "moz:windowless only supported on MacOS"
            );
          }
          break;
      }
      capabilities.set(k, v);
    }

    return capabilities;
  }

  /**
   * Validate WebDriver capability.
   *
   * @param {string} name
   *    The name of capability.
   * @param {string} value
   *    The value of capability.
   *
   * @throws {InvalidArgumentError}
   *   If <var>value</var> doesn't pass validation,
   *   which depends on <var>name</var>.
   *
   * @returns {string}
   *     The validated capability value.
   */
  static validate(name, value) {
    if (value === null) {
      return value;
    }
    switch (name) {
      case "acceptInsecureCerts":
        lazy.assert.boolean(
          value,
          `Expected "${name}" to be a boolean, ` + lazy.pprint`got ${value}`
        );
        return value;

      case "browserName":
      case "browserVersion":
      case "platformName":
        return lazy.assert.string(
          value,
          `Expected "${name}" to be a string, ` + lazy.pprint`got ${value}`
        );

      case "pageLoadStrategy":
        lazy.assert.string(
          value,
          `Expected "${name}" to be a string, ` + lazy.pprint`got ${value}`
        );
        if (!Object.values(PageLoadStrategy).includes(value)) {
          throw new lazy.error.InvalidArgumentError(
            "Unknown page load strategy: " + value
          );
        }
        return value;

      case "proxy":
        return Proxy.fromJSON(value);

      case "strictFileInteractability":
        return lazy.assert.boolean(
          value,
          `Expected "${name}" to be a boolean, ` + lazy.pprint`got ${value}`
        );

      case "timeouts":
        return Timeouts.fromJSON(value);

      case "unhandledPromptBehavior":
        return lazy.UserPromptHandler.fromJSON(value);

      case "webSocketUrl":
        lazy.assert.boolean(
          value,
          `Expected "${name}" to be a boolean, ` + lazy.pprint`got ${value}`
        );

        if (!value) {
          throw new lazy.error.InvalidArgumentError(
            `Expected "${name}" to be true, ` + lazy.pprint`got ${value}`
          );
        }
        return value;

      case "webauthn:virtualAuthenticators":
        lazy.assert.boolean(
          value,
          `Expected "${name}" to be a boolean, ` + lazy.pprint`got ${value}`
        );
        return value;

      case "webauthn:extension:uvm":
        lazy.assert.boolean(
          value,
          `Expected "${name}" to be a boolean, ` + lazy.pprint`got ${value}`
        );
        return value;

      case "webauthn:extension:largeBlob":
        lazy.assert.boolean(
          value,
          `Expected "${name}" to be a boolean, ` + lazy.pprint`got ${value}`
        );
        return value;

      case "moz:firefoxOptions":
        return lazy.assert.object(
          value,
          `Expected "${name}" to be an object, ` + lazy.pprint`got ${value}`
        );

      case "moz:accessibilityChecks":
        return lazy.assert.boolean(
          value,
          `Expected "${name}" to be a boolean, ` + lazy.pprint`got ${value}`
        );

      case "moz:webdriverClick":
        return lazy.assert.boolean(
          value,
          `Expected "${name}" to be a boolean, ` + lazy.pprint`got ${value}`
        );

      case "moz:windowless":
        lazy.assert.boolean(
          value,
          `Expected "${name}" to be a boolean, ` + lazy.pprint`got ${value}`
        );

        // Only supported on MacOS
        if (value && !lazy.AppInfo.isMac) {
          throw new lazy.error.InvalidArgumentError(
            "moz:windowless only supported on MacOS"
          );
        }
        return value;

      case "moz:debuggerAddress":
        return lazy.assert.boolean(
          value,
          `Expected "${name}" to be a boolean, ` + lazy.pprint`got ${value}`
        );

      default:
        lazy.assert.string(
          name,
          `Expected capability "name" to be a string, ` +
            lazy.pprint`got ${name}`
        );
        if (name.includes(":")) {
          const [prefix] = name.split(":");
          if (prefix !== "moz") {
            return value;
          }
        }
        throw new lazy.error.InvalidArgumentError(
          `${name} is not the name of a known capability or extension capability`
        );
    }
  }
}

function getWebDriverBrowserName() {
  // Similar to chromedriver which reports "chrome" as browser name for all
  // WebView apps, we will report "firefox" for all GeckoView apps.
  if (lazy.AppInfo.isAndroid) {
    return "firefox";
  }

  return lazy.AppInfo.name?.toLowerCase();
}

function getWebDriverPlatformName() {
  let name = Services.sysinfo.getProperty("name");

  if (lazy.AppInfo.isAndroid) {
    return "android";
  }

  switch (name) {
    case "Windows_NT":
      return "windows";

    case "Darwin":
      return "mac";

    default:
      return name.toLowerCase();
  }
}

// Specialisation of |JSON.stringify| that produces JSON-safe object
// literals, dropping empty objects and entries which values are undefined
// or null. Objects are allowed to produce their own JSON representations
// by implementing a |toJSON| function.
function marshal(obj) {
  let rv = Object.create(null);

  function* iter(mapOrObject) {
    if (mapOrObject instanceof Map) {
      for (const [k, v] of mapOrObject) {
        yield [k, v];
      }
    } else {
      for (const k of Object.keys(mapOrObject)) {
        yield [k, mapOrObject[k]];
      }
    }
  }

  for (let [k, v] of iter(obj)) {
    // Skip empty values when serialising to JSON.
    if (typeof v == "undefined" || v === null) {
      continue;
    }

    // Recursively marshal objects that are able to produce their own
    // JSON representation.
    if (typeof v.toJSON == "function") {
      v = marshal(v.toJSON());

      // Or do the same for object literals.
    } else if (isObject(v)) {
      v = marshal(v);
    }

    // And finally drop (possibly marshaled) objects which have no
    // entries.
    if (!isObjectEmpty(v)) {
      rv[k] = v;
    }
  }

  return rv;
}

function isObject(obj) {
  return Object.prototype.toString.call(obj) == "[object Object]";
}

function isObjectEmpty(obj) {
  return isObject(obj) && Object.keys(obj).length === 0;
}

// Services.dirsvc is not accessible from JSWindowActor child,
// but we should not panic about that.
function maybeProfile() {
  try {
    return Services.dirsvc.get("ProfD", Ci.nsIFile).path;
  } catch (e) {
    return "<protected>";
  }
}

/**
 * Merge WebDriver capabilities.
 *
 * @see https://w3c.github.io/webdriver/#dfn-merging-capabilities
 *
 * @param {object} primary
 *     Required capabilities which need to be merged with <var>secondary</var>.
 * @param {object=} secondary
 *     Secondary capabilities.
 *
 * @returns {object} Merged capabilities.
 *
 * @throws {InvalidArgumentError}
 *     If <var>primary</var> and <var>secondary</var> have the same keys.
 */
export function mergeCapabilities(primary, secondary) {
  const result = { ...primary };

  if (secondary === undefined) {
    return result;
  }

  Object.entries(secondary).forEach(([name, value]) => {
    if (primary[name] !== undefined) {
      // Since at the moment we always pass as `primary` `alwaysMatch` object
      // and as `secondary` an item from `firstMatch` array from `capabilities`,
      // we can make this error message more specific.
      throw new lazy.error.InvalidArgumentError(
        `firstMatch key ${name} shadowed a value in alwaysMatch`
      );
    }
    result[name] = value;
  });

  return result;
}

/**
 * Validate WebDriver capabilities.
 *
 * @see https://w3c.github.io/webdriver/#dfn-validate-capabilities
 *
 * @param {object} capabilities
 *     Capabilities which need to be validated.
 *
 * @returns {object} Validated capabilities.
 *
 * @throws {InvalidArgumentError}
 *     If <var>capabilities</var> is not an object.
 */
export function validateCapabilities(capabilities) {
  lazy.assert.object(
    capabilities,
    lazy.pprint`Expected "capabilities" to be an object, got ${capabilities}`
  );

  const result = {};

  Object.entries(capabilities).forEach(([name, value]) => {
    const deserialized = Capabilities.validate(name, value);
    if (deserialized !== null) {
      if (["proxy", "timeouts", "unhandledPromptBehavior"].includes(name)) {
        // Return pure values for objects that will be setup during session creation.
        result[name] = value;
      } else {
        result[name] = deserialized;
      }
    }
  });

  return result;
}

/**
 * Process WebDriver capabilities.
 *
 * @see https://w3c.github.io/webdriver/#processing-capabilities
 *
 * @param {object} params
 * @param {object} params.capabilities
 *     Capabilities which need to be processed.
 *
 * @returns {object} Processed capabilities.
 *
 * @throws {InvalidArgumentError}
 *     If <var>capabilities</var> do not satisfy the criteria.
 */
export function processCapabilities(params) {
  const { capabilities } = params;
  lazy.assert.object(
    capabilities,
    lazy.pprint`Expected "capabilities" to be an object, got ${capabilities}`
  );

  let {
    alwaysMatch: requiredCapabilities = {},
    firstMatch: allFirstMatchCapabilities = [{}],
  } = capabilities;

  requiredCapabilities = validateCapabilities(requiredCapabilities);

  lazy.assert.array(
    allFirstMatchCapabilities,
    lazy.pprint`Expected "firstMatch" to be an array, got ${allFirstMatchCapabilities}`
  );
  lazy.assert.that(
    firstMatch => firstMatch.length >= 1,
    lazy.pprint`Expected "firstMatch" to be an array of length 1 or greater, got ${allFirstMatchCapabilities}`
  )(allFirstMatchCapabilities);

  const validatedFirstMatchCapabilities =
    allFirstMatchCapabilities.map(validateCapabilities);

  const mergedCapabilities = [];
  validatedFirstMatchCapabilities.forEach(firstMatchCapabilities => {
    const merged = mergeCapabilities(
      requiredCapabilities,
      firstMatchCapabilities
    );
    mergedCapabilities.push(merged);
  });

  // TODO: Bug 1836288. Implement the capability matching logic
  // for "browserName", "browserVersion", "platformName", and
  // "unhandledPromptBehavior" features,
  // for now we can just pick the first merged capability.
  const matchedCapabilities = mergedCapabilities[0];

  return matchedCapabilities;
}
PK
!<c��c�c5chrome/remote/content/shared/webdriver/Errors.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { RemoteError } from "chrome://remote/content/shared/RemoteError.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
});

const ERRORS = new Set([
  "DetachedShadowRootError",
  "ElementClickInterceptedError",
  "ElementNotAccessibleError",
  "ElementNotInteractableError",
  "InsecureCertificateError",
  "InvalidArgumentError",
  "InvalidCookieDomainError",
  "InvalidElementStateError",
  "InvalidSelectorError",
  "InvalidSessionIDError",
  "JavaScriptError",
  "MoveTargetOutOfBoundsError",
  "NoSuchAlertError",
  "NoSuchElementError",
  "NoSuchFrameError",
  "NoSuchHandleError",
  "NoSuchHistoryEntryError",
  "NoSuchInterceptError",
  "NoSuchNodeError",
  "NoSuchRequestError",
  "NoSuchScriptError",
  "NoSuchShadowRootError",
  "NoSuchUserContextError",
  "NoSuchWindowError",
  "ScriptTimeoutError",
  "SessionNotCreatedError",
  "StaleElementReferenceError",
  "TimeoutError",
  "UnableToCaptureScreen",
  "UnableToSetCookieError",
  "UnableToSetFileInputError",
  "UnexpectedAlertOpenError",
  "UnknownCommandError",
  "UnknownError",
  "UnsupportedOperationError",
  "WebDriverError",
]);

const BUILTIN_ERRORS = new Set([
  "Error",
  "EvalError",
  "InternalError",
  "RangeError",
  "ReferenceError",
  "SyntaxError",
  "TypeError",
  "URIError",
]);

/** @namespace */
export const error = {
  /**
   * Check if ``val`` is an instance of the ``Error`` prototype.
   *
   * Because error objects may originate from different globals, comparing
   * the prototype of the left hand side with the prototype property from
   * the right hand side, which is what ``instanceof`` does, will not work.
   * If the LHS and RHS come from different globals, this check will always
   * fail because the two objects will not have the same identity.
   *
   * Therefore it is not safe to use ``instanceof`` in any multi-global
   * situation, e.g. in content across multiple ``Window`` objects or anywhere
   * in chrome scope.
   *
   * This function also contains a special check if ``val`` is an XPCOM
   * ``nsIException`` because they are special snowflakes and may indeed
   * cause Firefox to crash if used with ``instanceof``.
   *
   * @param {*} val
   *     Any value that should be undergo the test for errorness.
   * @returns {boolean}
   *     True if error, false otherwise.
   */
  isError(val) {
    if (val === null || typeof val != "object") {
      return false;
    } else if (val instanceof Ci.nsIException) {
      return true;
    }

    // DOMRectList errors on string comparison
    try {
      let proto = Object.getPrototypeOf(val);
      return BUILTIN_ERRORS.has(proto.toString());
    } catch (e) {
      return false;
    }
  },

  /**
   * Checks if ``obj`` is an object in the :js:class:`WebDriverError`
   * prototypal chain.
   *
   * @param {*} obj
   *     Arbitrary object to test.
   *
   * @returns {boolean}
   *     True if ``obj`` is of the WebDriverError prototype chain,
   *     false otherwise.
   */
  isWebDriverError(obj) {
    // Don't use "instanceof" to compare error objects because of possible
    // problems when the other instance was created in a different global and
    // as such won't have the same prototype object.
    return error.isError(obj) && "name" in obj && ERRORS.has(obj.name);
  },

  /**
   * Ensures error instance is a :js:class:`WebDriverError`.
   *
   * If the given error is already in the WebDriverError prototype
   * chain, ``err`` is returned unmodified.  If it is not, it is wrapped
   * in :js:class:`UnknownError`.
   *
   * @param {Error} err
   *     Error to conditionally turn into a WebDriverError.
   *
   * @returns {WebDriverError}
   *     If ``err`` is a WebDriverError, it is returned unmodified.
   *     Otherwise an UnknownError type is returned.
   */
  wrap(err) {
    if (error.isWebDriverError(err)) {
      return err;
    }
    return new UnknownError(err);
  },

  /**
   * Unhandled error reporter.  Dumps the error and its stacktrace to console,
   * and reports error to the Browser Console.
   */
  report(err) {
    let msg = "Marionette threw an error: " + error.stringify(err);
    dump(msg + "\n");
    console.error(msg);
  },

  /**
   * Prettifies an instance of Error and its stacktrace to a string.
   */
  stringify(err) {
    try {
      let s = err.toString();
      if ("stack" in err) {
        s += "\n" + err.stack;
      }
      return s;
    } catch (e) {
      return "<unprintable error>";
    }
  },

  /** Create a stacktrace to the current line in the program. */
  stack() {
    let trace = new Error().stack;
    let sa = trace.split("\n");
    sa = sa.slice(1);
    let rv = "stacktrace:\n" + sa.join("\n");
    return rv.trimEnd();
  },
};

/**
 * WebDriverError is the prototypal parent of all WebDriver errors.
 * It should not be used directly, as it does not correspond to a real
 * error in the specification.
 */
class WebDriverError extends RemoteError {
  /**
   * Base error for WebDriver protocols.
   *
   * @param {(string|Error)=} obj
   *     Optional string describing error situation or Error instance
   *     to propagate.
   * @param {object=} data
   *     Additional error data helpful in diagnosing the error.
   */
  constructor(obj, data = {}) {
    super(obj);

    this.name = this.constructor.name;
    this.status = "webdriver error";
    this.data = data;

    // Error's ctor does not preserve x' stack
    if (error.isError(obj)) {
      this.stack = obj.stack;
    }

    if (error.isWebDriverError(obj)) {
      this.message = obj.message;
      this.data = obj.data;
    }
  }

  /**
   * @returns {Record<string, string>}
   *     JSON serialisation of error prototype.
   */
  toJSON() {
    const result = {
      error: this.status,
      message: this.message || "",
      stacktrace: this.stack || "",
    };

    // Only add the field if additional data has been specified.
    if (Object.keys(this.data).length) {
      result.data = this.data;
    }

    return result;
  }

  /**
   * Unmarshals a JSON error representation to the appropriate Marionette
   * error type.
   *
   * @param {Record<string, string>} json
   *     Error object.
   *
   * @returns {Error}
   *     Error prototype.
   */
  static fromJSON(json) {
    if (typeof json.error == "undefined") {
      let s = JSON.stringify(json);
      throw new TypeError("Undeserialisable error type: " + s);
    }
    if (!STATUSES.has(json.error)) {
      throw new TypeError("Not of WebDriverError descent: " + json.error);
    }

    let cls = STATUSES.get(json.error);
    let err = new cls();
    if ("message" in json) {
      err.message = json.message;
    }
    if ("stacktrace" in json) {
      err.stack = json.stacktrace;
    }
    if ("data" in json) {
      err.data = json.data;
    }

    return err;
  }
}

/**
 * The Gecko a11y API indicates that the element is not accessible.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class ElementNotAccessibleError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "element not accessible";
  }
}

/**
 * An element click could not be completed because the element receiving
 * the events is obscuring the element that was requested clicked.
 *
 * @param {string=} message
 *     Optional string describing error situation. Will be replaced if both
 *     `data.obscuredEl` and `data.coords` are provided.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 * @param {Element=} obscuredEl
 *     Element obscuring the element receiving the click.  Providing this
 *     is not required, but will produce a nicer error message.
 * @param {Map.<string, number>=} coords
 *     Original click location.  Providing this is not required, but
 *     will produce a nicer error message.
 */
class ElementClickInterceptedError extends WebDriverError {
  constructor(message, data = {}, obscuredEl = undefined, coords = undefined) {
    let obscuredElDetails = null;
    let overlayingElDetails = null;

    if (obscuredEl && coords) {
      const doc = obscuredEl.ownerDocument;
      const overlayingEl = doc.elementFromPoint(coords.x, coords.y);

      obscuredElDetails = lazy.pprint`${obscuredEl}`;
      overlayingElDetails = lazy.pprint`${overlayingEl}`;

      switch (obscuredEl.style.pointerEvents) {
        case "none":
          message =
            `Element ${obscuredElDetails} is not clickable ` +
            `at point (${coords.x},${coords.y}) ` +
            `because it does not have pointer events enabled, ` +
            `and element ${overlayingElDetails} ` +
            `would receive the click instead`;
          break;

        default:
          message =
            `Element ${obscuredElDetails} is not clickable ` +
            `at point (${coords.x},${coords.y}) ` +
            `because another element ${overlayingElDetails} ` +
            `obscures it`;
          break;
      }
    }

    if (coords) {
      data.coords = coords;
    }
    if (obscuredElDetails) {
      data.obscuredElement = obscuredElDetails;
    }
    if (overlayingElDetails) {
      data.overlayingElement = overlayingElDetails;
    }

    super(message, data);
    this.status = "element click intercepted";
  }
}

/**
 * A command could not be completed because the element is not pointer-
 * or keyboard interactable.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class ElementNotInteractableError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "element not interactable";
  }
}

/**
 * Navigation caused the user agent to hit a certificate warning, which
 * is usually the result of an expired or invalid TLS certificate.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class InsecureCertificateError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "insecure certificate";
  }
}

/**
 * The arguments passed to a command are either invalid or malformed.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class InvalidArgumentError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "invalid argument";
  }
}

/**
 * An illegal attempt was made to set a cookie under a different
 * domain than the current page.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class InvalidCookieDomainError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "invalid cookie domain";
  }
}

/**
 * A command could not be completed because the element is in an
 * invalid state, e.g. attempting to clear an element that isn't both
 * editable and resettable.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class InvalidElementStateError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "invalid element state";
  }
}

/**
 * Argument was an invalid selector.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class InvalidSelectorError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "invalid selector";
  }
}

/**
 * Occurs if the given session ID is not in the list of active sessions,
 * meaning the session either does not exist or that it's not active.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class InvalidSessionIDError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "invalid session id";
  }
}

/**
 * An error occurred whilst executing JavaScript supplied by the user.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class JavaScriptError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "javascript error";
  }
}

/**
 * The target for mouse interaction is not in the browser's viewport
 * and cannot be brought into that viewport.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class MoveTargetOutOfBoundsError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "move target out of bounds";
  }
}

/**
 * An attempt was made to operate on a modal dialog when one was
 * not open.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class NoSuchAlertError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "no such alert";
  }
}

/**
 * An element could not be located on the page using the given
 * search parameters.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class NoSuchElementError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "no such element";
  }
}

/**
 * A command tried to remove an unknown preload script.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class NoSuchScriptError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "no such script";
  }
}

/**
 * A shadow root was not attached to the element.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class NoSuchShadowRootError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "no such shadow root";
  }
}

/**
 * A shadow root is no longer attached to the document.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class DetachedShadowRootError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "detached shadow root";
  }
}

/**
 * A command to switch to a frame could not be satisfied because
 * the frame could not be found.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class NoSuchFrameError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "no such frame";
  }
}

/**
 * The handle of a strong object reference could not be found.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class NoSuchHandleError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "no such handle";
  }
}

/**
 * The entry of the history could not be found.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class NoSuchHistoryEntryError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "no such history entry";
  }
}

/**
 * Tried to remove an unknown network intercept.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class NoSuchInterceptError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "no such intercept";
  }
}

/**
 * A node as given by its unique shared id could not be found within the cache
 * of known nodes.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class NoSuchNodeError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "no such node";
  }
}

/**
 * Tried to continue an unknown request.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class NoSuchRequestError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "no such request";
  }
}

/**
 * A command tried to reference an unknown user context (containers in Firefox).
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class NoSuchUserContextError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "no such user context";
  }
}

/**
 * A command to switch to a window could not be satisfied because
 * the window could not be found.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class NoSuchWindowError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "no such window";
  }
}

/**
 * A script did not complete before its timeout expired.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class ScriptTimeoutError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "script timeout";
  }
}

/**
 * A new session could not be created.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class SessionNotCreatedError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "session not created";
  }
}

/**
 * A command failed because the referenced element is no longer
 * attached to the DOM.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class StaleElementReferenceError extends WebDriverError {
  constructor(message, options = {}) {
    super(message, options);
    this.status = "stale element reference";
  }
}

/**
 * An operation did not complete before its timeout expired.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class TimeoutError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "timeout";
  }
}

/**
 * A command to set a cookie's value could not be satisfied.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class UnableToSetCookieError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "unable to set cookie";
  }
}

/**
 * A command to set a file could not be satisfied.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class UnableToSetFileInputError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "unable to set file input";
  }
}

/**
 * A command to capture a screenshot could not be satisfied.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class UnableToCaptureScreen extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "unable to capture screen";
  }
}

/**
 * A modal dialog was open, blocking this operation.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class UnexpectedAlertOpenError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "unexpected alert open";
  }
}

/**
 * A command could not be executed because the remote end is not
 * aware of it.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class UnknownCommandError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "unknown command";
  }
}

/**
 * An unknown error occurred in the remote end while processing
 * the command.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class UnknownError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "unknown error";
  }
}

/**
 * Indicates that a command that should have executed properly
 * cannot be supported for some reason.
 *
 * @param {string=} message
 *     Optional string describing error situation.
 * @param {object=} data
 *     Additional error data helpful in diagnosing the error.
 */
class UnsupportedOperationError extends WebDriverError {
  constructor(message, data = {}) {
    super(message, data);
    this.status = "unsupported operation";
  }
}

const STATUSES = new Map([
  ["detached shadow root", DetachedShadowRootError],
  ["element click intercepted", ElementClickInterceptedError],
  ["element not accessible", ElementNotAccessibleError],
  ["element not interactable", ElementNotInteractableError],
  ["insecure certificate", InsecureCertificateError],
  ["invalid argument", InvalidArgumentError],
  ["invalid cookie domain", InvalidCookieDomainError],
  ["invalid element state", InvalidElementStateError],
  ["invalid selector", InvalidSelectorError],
  ["invalid session id", InvalidSessionIDError],
  ["javascript error", JavaScriptError],
  ["move target out of bounds", MoveTargetOutOfBoundsError],
  ["no such alert", NoSuchAlertError],
  ["no such element", NoSuchElementError],
  ["no such frame", NoSuchFrameError],
  ["no such handle", NoSuchHandleError],
  ["no such history entry", NoSuchHistoryEntryError],
  ["no such intercept", NoSuchInterceptError],
  ["no such node", NoSuchNodeError],
  ["no such request", NoSuchRequestError],
  ["no such script", NoSuchScriptError],
  ["no such shadow root", NoSuchShadowRootError],
  ["no such user context", NoSuchUserContextError],
  ["no such window", NoSuchWindowError],
  ["script timeout", ScriptTimeoutError],
  ["session not created", SessionNotCreatedError],
  ["stale element reference", StaleElementReferenceError],
  ["timeout", TimeoutError],
  ["unable to capture screen", UnableToCaptureScreen],
  ["unable to set cookie", UnableToSetCookieError],
  ["unable to set file input", UnableToSetFileInputError],
  ["unexpected alert open", UnexpectedAlertOpenError],
  ["unknown command", UnknownCommandError],
  ["unknown error", UnknownError],
  ["unsupported operation", UnsupportedOperationError],
  ["webdriver error", WebDriverError],
]);

// Errors must be expored on the local this scope so that the
// EXPORTED_SYMBOLS and the ChromeUtils.import("foo") machinery sees them.
// We could assign each error definition directly to |this|, but
// because they are Error prototypes this would mess up their names.
for (let cls of STATUSES.values()) {
  error[cls.name] = cls;
}
PK
!<	���;;4chrome/remote/content/shared/webdriver/Event.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-disable no-restricted-globals */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  keyData: "chrome://remote/content/shared/webdriver/KeyData.sys.mjs",
});

/** Provides functionality for creating and sending DOM events. */
export const event = {};

const _eventUtils = new WeakMap();

function _getEventUtils(win) {
  if (!_eventUtils.has(win)) {
    const eventUtilsObject = {
      window: win,
      parent: win,
      _EU_Ci: Ci,
      _EU_Cc: Cc,
    };
    Services.scriptloader.loadSubScript(
      "chrome://remote/content/external/EventUtils.js",
      eventUtilsObject
    );
    _eventUtils.set(win, eventUtilsObject);
  }
  return _eventUtils.get(win);
}

event.MouseEvents = {
  click: 0,
  dblclick: 1,
  mousedown: 2,
  mouseup: 3,
  mouseover: 4,
  mouseout: 5,
};

event.Modifiers = {
  shiftKey: 0,
  ctrlKey: 1,
  altKey: 2,
  metaKey: 3,
};

event.MouseButton = {
  isPrimary(button) {
    return button === 0;
  },
  isAuxiliary(button) {
    return button === 1;
  },
  isSecondary(button) {
    return button === 2;
  },
};

/**
 * Synthesise a mouse event at a point.
 *
 * If the type is specified in opts, an mouse event of that type is
 * fired. Otherwise, a mousedown followed by a mouseup is performed.
 *
 * @param {number} left
 *     Offset from viewport left, in CSS pixels
 * @param {number} top
 *     Offset from viewport top, in CSS pixels
 * @param {object} opts
 *     Object which may contain the properties "shiftKey", "ctrlKey",
 *     "altKey", "metaKey", "accessKey", "clickCount", "button", and
 *     "type".
 * @param {Window} win
 *     Window object.
 *
 * @returns {boolean} defaultPrevented
 */
event.synthesizeMouseAtPoint = function (left, top, opts, win) {
  return _getEventUtils(win).synthesizeMouseAtPoint(left, top, opts, win);
};

/**
 * Synthesise a touch event at a point.
 *
 * If the type is specified in opts, a touch event of that type is
 * fired. Otherwise, a touchstart followed by a touchend is performed.
 *
 * @param {number} left
 *     Offset from viewport left, in CSS pixels
 * @param {number} top
 *     Offset from viewport top, in CSS pixels
 * @param {object} opts
 *     Object which may contain the properties "id", "rx", "ry", "angle",
 *     "force", "shiftKey", "ctrlKey", "altKey", "metaKey", "accessKey",
 *     "type".
 * @param {Window} win
 *     Window object.
 *
 * @returns {boolean} defaultPrevented
 */
event.synthesizeTouchAtPoint = function (left, top, opts, win) {
  return _getEventUtils(win).synthesizeTouchAtPoint(left, top, opts, win);
};

/**
 * Synthesise a wheel scroll event at a point.
 *
 * @param {number} left
 *     Offset from viewport left, in CSS pixels
 * @param {number} top
 *     Offset from viewport top, in CSS pixels
 * @param {object} opts
 *     Object which may contain the properties "shiftKey", "ctrlKey",
 *     "altKey", "metaKey", "accessKey", "deltaX", "deltaY", "deltaZ",
 *     "deltaMode", "lineOrPageDeltaX", "lineOrPageDeltaY", "isMomentum",
 *     "isNoLineOrPageDelta", "isCustomizedByPrefs", "expectedOverflowDeltaX",
 *     "expectedOverflowDeltaY"
 * @param {Window} win
 *     Window object.
 */
event.synthesizeWheelAtPoint = function (left, top, opts, win) {
  const dpr = win.devicePixelRatio;

  // All delta properties expect the value in device pixels while the
  // WebDriver specification uses CSS pixels.
  if (typeof opts.deltaX !== "undefined") {
    opts.deltaX *= dpr;
  }
  if (typeof opts.deltaY !== "undefined") {
    opts.deltaY *= dpr;
  }
  if (typeof opts.deltaZ !== "undefined") {
    opts.deltaZ *= dpr;
  }

  return _getEventUtils(win).synthesizeWheelAtPoint(left, top, opts, win);
};

event.synthesizeMultiTouch = function (opts, win) {
  const modifiers = _getEventUtils(win)._parseModifiers(opts);
  win.windowUtils.sendTouchEvent(
    opts.type,
    opts.id,
    opts.x,
    opts.y,
    opts.rx,
    opts.ry,
    opts.angle,
    opts.force,
    opts.tiltx,
    opts.tilty,
    opts.twist,
    modifiers
  );
};

/**
 * Synthesize a keydown event for a single key.
 *
 * @param {object} key
 *     Key data as returned by keyData.getData
 * @param {Window} win
 *     Window object.
 */
event.sendKeyDown = function (key, win) {
  event.sendSingleKey(key, win, "keydown");
};

/**
 * Synthesize a keyup event for a single key.
 *
 * @param {object} key
 *     Key data as returned by keyData.getData
 * @param {Window} win
 *     Window object.
 */
event.sendKeyUp = function (key, win) {
  event.sendSingleKey(key, win, "keyup");
};

/**
 * Synthesize a key event for a single key.
 *
 * @param {object} key
 *     Key data as returned by keyData.getData
 * @param {Window} win
 *     Window object.
 * @param {string=} type
 *     Event to emit. By default the full keydown/keypressed/keyup event
 *     sequence is emitted.
 */
event.sendSingleKey = function (key, win, type = null) {
  let keyValue = key.key;
  if (!key.printable) {
    keyValue = `KEY_${keyValue}`;
  }
  const event = {
    code: key.code,
    location: key.location,
    altKey: key.altKey ?? false,
    shiftKey: key.shiftKey ?? false,
    ctrlKey: key.ctrlKey ?? false,
    metaKey: key.metaKey ?? false,
    repeat: key.repeat ?? false,
  };
  if (type) {
    event.type = type;
  }
  _getEventUtils(win).synthesizeKey(keyValue, event, win);
};

/**
 * Send a string as a series of keypresses.
 *
 * @param {string} keyString
 *     Sequence of characters to send as key presses
 * @param {Window} win
 *     Window object
 */
event.sendKeys = function (keyString, win) {
  const modifiers = {};
  for (let modifier in event.Modifiers) {
    modifiers[modifier] = false;
  }

  for (let keyValue of keyString) {
    // keyValue will contain enough to represent the UTF-16 encoding of a single abstract character
    // i.e. either a single scalar value, or a surrogate pair
    if (modifiers.shiftKey) {
      keyValue = lazy.keyData.getShiftedKey(keyValue);
    }
    const data = lazy.keyData.getData(keyValue);
    const key = { ...data, ...modifiers };
    if (data.modifier) {
      // Negating the state of the modifier here is not spec compliant but
      // makes us compatible to Chrome's behavior for now. That's fine unless
      // we know the correct behavior.
      //
      // @see: https://github.com/w3c/webdriver/issues/1734
      modifiers[data.modifier] = !modifiers[data.modifier];
    }
    event.sendSingleKey(key, win);
  }
};

event.sendEvent = function (eventType, el, modifiers = {}, opts = {}) {
  opts.canBubble = opts.canBubble || true;

  let doc = el.ownerDocument || el.document;
  let ev = doc.createEvent("Event");

  ev.shiftKey = modifiers.shift;
  ev.metaKey = modifiers.meta;
  ev.altKey = modifiers.alt;
  ev.ctrlKey = modifiers.ctrl;

  ev.initEvent(eventType, opts.canBubble, true);
  el.dispatchEvent(ev);
};

event.mouseover = function (el, modifiers = {}, opts = {}) {
  return event.sendEvent("mouseover", el, modifiers, opts);
};

event.mousemove = function (el, modifiers = {}, opts = {}) {
  return event.sendEvent("mousemove", el, modifiers, opts);
};

event.mousedown = function (el, modifiers = {}, opts = {}) {
  return event.sendEvent("mousedown", el, modifiers, opts);
};

event.mouseup = function (el, modifiers = {}, opts = {}) {
  return event.sendEvent("mouseup", el, modifiers, opts);
};

event.cancel = function (el, modifiers = {}, opts = {}) {
  return event.sendEvent("cancel", el, modifiers, opts);
};

event.click = function (el, modifiers = {}, opts = {}) {
  return event.sendEvent("click", el, modifiers, opts);
};

event.change = function (el, modifiers = {}, opts = {}) {
  return event.sendEvent("change", el, modifiers, opts);
};

event.input = function (el, modifiers = {}, opts = {}) {
  return event.sendEvent("input", el, modifiers, opts);
};
PK
!<�2���'�'6chrome/remote/content/shared/webdriver/KeyData.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const KEY_DATA = {
  " ": { code: "Space" },
  "!": { code: "Digit1", shifted: true },
  "#": { code: "Digit3", shifted: true },
  $: { code: "Digit4", shifted: true },
  "%": { code: "Digit5", shifted: true },
  "&": { code: "Digit7", shifted: true },
  "'": { code: "Quote" },
  "(": { code: "Digit9", shifted: true },
  ")": { code: "Digit0", shifted: true },
  "*": { code: "Digit8", shifted: true },
  "+": { code: "Equal", shifted: true },
  ",": { code: "Comma" },
  "-": { code: "Minus" },
  ".": { code: "Period" },
  "/": { code: "Slash" },
  0: { code: "Digit0" },
  1: { code: "Digit1" },
  2: { code: "Digit2" },
  3: { code: "Digit3" },
  4: { code: "Digit4" },
  5: { code: "Digit5" },
  6: { code: "Digit6" },
  7: { code: "Digit7" },
  8: { code: "Digit8" },
  9: { code: "Digit9" },
  ":": { code: "Semicolon", shifted: true },
  ";": { code: "Semicolon" },
  "<": { code: "Comma", shifted: true },
  "=": { code: "Equal" },
  ">": { code: "Period", shifted: true },
  "?": { code: "Slash", shifted: true },
  "@": { code: "Digit2", shifted: true },
  A: { code: "KeyA", shifted: true },
  B: { code: "KeyB", shifted: true },
  C: { code: "KeyC", shifted: true },
  D: { code: "KeyD", shifted: true },
  E: { code: "KeyE", shifted: true },
  F: { code: "KeyF", shifted: true },
  G: { code: "KeyG", shifted: true },
  H: { code: "KeyH", shifted: true },
  I: { code: "KeyI", shifted: true },
  J: { code: "KeyJ", shifted: true },
  K: { code: "KeyK", shifted: true },
  L: { code: "KeyL", shifted: true },
  M: { code: "KeyM", shifted: true },
  N: { code: "KeyN", shifted: true },
  O: { code: "KeyO", shifted: true },
  P: { code: "KeyP", shifted: true },
  Q: { code: "KeyQ", shifted: true },
  R: { code: "KeyR", shifted: true },
  S: { code: "KeyS", shifted: true },
  T: { code: "KeyT", shifted: true },
  U: { code: "KeyU", shifted: true },
  V: { code: "KeyV", shifted: true },
  W: { code: "KeyW", shifted: true },
  X: { code: "KeyX", shifted: true },
  Y: { code: "KeyY", shifted: true },
  Z: { code: "KeyZ", shifted: true },
  "[": { code: "BracketLeft" },
  '"': { code: "Quote", shifted: true },
  "\\": { code: "Backslash" },
  "]": { code: "BracketRight" },
  "^": { code: "Digit6", shifted: true },
  _: { code: "Minus", shifted: true },
  "`": { code: "Backquote" },
  a: { code: "KeyA" },
  b: { code: "KeyB" },
  c: { code: "KeyC" },
  d: { code: "KeyD" },
  e: { code: "KeyE" },
  f: { code: "KeyF" },
  g: { code: "KeyG" },
  h: { code: "KeyH" },
  i: { code: "KeyI" },
  j: { code: "KeyJ" },
  k: { code: "KeyK" },
  l: { code: "KeyL" },
  m: { code: "KeyM" },
  n: { code: "KeyN" },
  o: { code: "KeyO" },
  p: { code: "KeyP" },
  q: { code: "KeyQ" },
  r: { code: "KeyR" },
  s: { code: "KeyS" },
  t: { code: "KeyT" },
  u: { code: "KeyU" },
  v: { code: "KeyV" },
  w: { code: "KeyW" },
  x: { code: "KeyX" },
  y: { code: "KeyY" },
  z: { code: "KeyZ" },
  "{": { code: "BracketLeft", shifted: true },
  "|": { code: "Backslash", shifted: true },
  "}": { code: "BracketRight", shifted: true },
  "~": { code: "Backquote", shifted: true },
  "\uE000": { key: "Unidentified", printable: false },
  "\uE001": { key: "Cancel", printable: false },
  "\uE002": { code: "Help", key: "Help", printable: false },
  "\uE003": { code: "Backspace", key: "Backspace", printable: false },
  "\uE004": { code: "Tab", key: "Tab", printable: false },
  "\uE005": { code: "", key: "Clear", printable: false },
  "\uE006": { code: "Enter", key: "Enter", printable: false },
  "\uE007": {
    code: "NumpadEnter",
    key: "Enter",
    location: 1,
    printable: false,
  },
  "\uE008": {
    code: "ShiftLeft",
    key: "Shift",
    location: 1,
    modifier: "shiftKey",
    printable: false,
  },
  "\uE009": {
    code: "ControlLeft",
    key: "Control",
    location: 1,
    modifier: "ctrlKey",
    printable: false,
  },
  "\uE00A": {
    code: "AltLeft",
    key: "Alt",
    location: 1,
    modifier: "altKey",
    printable: false,
  },
  "\uE00B": { code: "Pause", key: "Pause", printable: false },
  "\uE00C": { code: "Escape", key: "Escape", printable: false },
  "\uE00D": { code: "Space", key: " ", shifted: true },
  "\uE00E": { code: "PageUp", key: "PageUp", printable: false },
  "\uE00F": { code: "PageDown", key: "PageDown", printable: false },
  "\uE010": { code: "End", key: "End", printable: false },
  "\uE011": { code: "Home", key: "Home", printable: false },
  "\uE012": { code: "ArrowLeft", key: "ArrowLeft", printable: false },
  "\uE013": { code: "ArrowUp", key: "ArrowUp", printable: false },
  "\uE014": { code: "ArrowRight", key: "ArrowRight", printable: false },
  "\uE015": { code: "ArrowDown", key: "ArrowDown", printable: false },
  "\uE016": { code: "Insert", key: "Insert", printable: false },
  "\uE017": { code: "Delete", key: "Delete", printable: false },
  "\uE018": { code: "", key: ";" },
  "\uE019": { code: "NumpadEqual", key: "=", location: 3 },
  "\uE01A": { code: "Numpad0", key: "0", location: 3 },
  "\uE01B": { code: "Numpad1", key: "1", location: 3 },
  "\uE01C": { code: "Numpad2", key: "2", location: 3 },
  "\uE01D": { code: "Numpad3", key: "3", location: 3 },
  "\uE01E": { code: "Numpad4", key: "4", location: 3 },
  "\uE01F": { code: "Numpad5", key: "5", location: 3 },
  "\uE020": { code: "Numpad6", key: "6", location: 3 },
  "\uE021": { code: "Numpad7", key: "7", location: 3 },
  "\uE022": { code: "Numpad8", key: "8", location: 3 },
  "\uE023": { code: "Numpad9", key: "9", location: 3 },
  "\uE024": { code: "NumpadMultiply", key: "*", location: 3 },
  "\uE025": { code: "NumpadAdd", key: "+", location: 3 },
  "\uE026": { code: "NumpadComma", key: ",", location: 3 },
  "\uE027": { code: "NumpadSubtract", key: "-", location: 3 },
  "\uE028": { code: "NumpadDecimal", key: ".", location: 3 },
  "\uE029": { code: "NumpadDivide", key: "/", location: 3 },
  "\uE031": { code: "F1", key: "F1", printable: false },
  "\uE032": { code: "F2", key: "F2", printable: false },
  "\uE033": { code: "F3", key: "F3", printable: false },
  "\uE034": { code: "F4", key: "F4", printable: false },
  "\uE035": { code: "F5", key: "F5", printable: false },
  "\uE036": { code: "F6", key: "F6", printable: false },
  "\uE037": { code: "F7", key: "F7", printable: false },
  "\uE038": { code: "F8", key: "F8", printable: false },
  "\uE039": { code: "F9", key: "F9", printable: false },
  "\uE03A": { code: "F10", key: "F10", printable: false },
  "\uE03B": { code: "F11", key: "F11", printable: false },
  "\uE03C": { code: "F12", key: "F12", printable: false },
  "\uE03D": {
    code: "MetaLeft",
    key: "Meta",
    location: 1,
    modifier: "metaKey",
    printable: false,
  },
  "\uE040": { code: "", key: "ZenkakuHankaku", printable: false },
  "\uE050": {
    code: "ShiftRight",
    key: "Shift",
    location: 2,
    modifier: "shiftKey",
    printable: false,
  },
  "\uE051": {
    code: "ControlRight",
    key: "Control",
    location: 2,
    modifier: "ctrlKey",
    printable: false,
  },
  "\uE052": {
    code: "AltRight",
    key: "Alt",
    location: 2,
    modifier: "altKey",
    printable: false,
  },
  "\uE053": {
    code: "MetaRight",
    key: "Meta",
    location: 2,
    modifier: "metaKey",
    printable: false,
  },
  "\uE054": {
    code: "Numpad9",
    key: "PageUp",
    location: 3,
    printable: false,
    shifted: true,
  },
  "\uE055": {
    code: "Numpad3",
    key: "PageDown",
    location: 3,
    printable: false,
    shifted: true,
  },
  "\uE056": {
    code: "Numpad1",
    key: "End",
    location: 3,
    printable: false,
    shifted: true,
  },
  "\uE057": {
    code: "Numpad7",
    key: "Home",
    location: 3,
    printable: false,
    shifted: true,
  },
  "\uE058": {
    code: "Numpad4",
    key: "ArrowLeft",
    location: 3,
    printable: false,
    shifted: true,
  },
  "\uE059": {
    code: "Numpad8",
    key: "ArrowUp",
    location: 3,
    printable: false,
    shifted: true,
  },
  "\uE05A": {
    code: "Numpad6",
    key: "ArrowRight",
    location: 3,
    printable: false,
    shifted: true,
  },
  "\uE05B": {
    code: "Numpad2",
    key: "ArrowDown",
    location: 3,
    printable: false,
    shifted: true,
  },
  "\uE05C": {
    code: "Numpad0",
    key: "Insert",
    location: 3,
    printable: false,
    shifted: true,
  },
  "\uE05D": {
    code: "NumpadDecimal",
    key: "Delete",
    location: 3,
    printable: false,
    shifted: true,
  },
};

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "SHIFT_DATA", () => {
  // Initalize the shift mapping
  const shiftData = new Map();
  const byCode = new Map();
  for (let [key, props] of Object.entries(KEY_DATA)) {
    if (props.code) {
      if (!byCode.has(props.code)) {
        byCode.set(props.code, [null, null]);
      }
      byCode.get(props.code)[props.shifted ? 1 : 0] = key;
    }
  }
  for (let [unshifted, shifted] of byCode.values()) {
    if (unshifted !== null && shifted !== null) {
      shiftData.set(unshifted, shifted);
    }
  }
  return shiftData;
});

export const keyData = {
  /**
   * Get key event data for a given key character.
   *
   * @param {string} rawKey
   *     Key for which to get data. This can either be the key codepoint
   *     itself or one of the codepoints in the range U+E000-U+E05D that
   *     WebDriver uses to represent keys not corresponding directly to
   *     a codepoint.
   * @returns {object} Key event data object.
   */
  getData(rawKey) {
    let keyData = { key: rawKey, location: 0, printable: true, shifted: false };
    if (KEY_DATA.hasOwnProperty(rawKey)) {
      keyData = { ...keyData, ...KEY_DATA[rawKey] };
    }
    return keyData;
  },

  /**
   * Get shifted key character for a given key character.
   *
   * For characters unaffected by the shift key, this returns the input.
   *
   * @param {string} rawKey Key for which to get shifted key.
   * @returns {string} Key string to use when the shift modifier is set.
   */
  getShiftedKey(rawKey) {
    return lazy.SHIFT_DATA.get(rawKey) ?? rawKey;
  },
};
PK
!<8[�{cc8chrome/remote/content/shared/webdriver/NodeCache.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
});

/**
 * @typedef {object} NodeReferenceDetails
 * @property {number} browserId
 * @property {number} browsingContextGroupId
 * @property {number} browsingContextId
 * @property {boolean} isTopBrowsingContext
 * @property {WeakRef} nodeWeakRef
 */

/**
 * The class provides a mapping between DOM nodes and a unique node references.
 * Supported types of nodes are Element and ShadowRoot.
 */
export class NodeCache {
  #nodeIdMap;
  #seenNodesMap;

  constructor() {
    // node => node id
    this.#nodeIdMap = new WeakMap();

    // Reverse map for faster lookup requests of node references. Values do
    // not only contain the resolved DOM node but also further details like
    // browsing context information.
    //
    // node id => node details
    this.#seenNodesMap = new Map();
  }

  /**
   * Get the number of nodes in the cache.
   */
  get size() {
    return this.#seenNodesMap.size;
  }

  /**
   * Get or if not yet existent create a unique reference for an Element or
   * ShadowRoot node.
   *
   * @param {Node} node
   *    The node to be added.
   * @param {Map<BrowsingContext, Array<string>>} seenNodeIds
   *     Map of browsing contexts to their seen node ids during the current
   *     serialization.
   *
   * @returns {string}
   *     The unique node reference for the DOM node.
   */
  getOrCreateNodeReference(node, seenNodeIds) {
    if (!Node.isInstance(node)) {
      throw new TypeError(`Failed to create node reference for ${node}`);
    }

    let nodeId;
    if (this.#nodeIdMap.has(node)) {
      // For already known nodes return the cached node id.
      nodeId = this.#nodeIdMap.get(node);
    } else {
      // Bug 1820734: For some Node types like `CDATA` no `ownerGlobal`
      // property is available, and as such they cannot be deserialized
      // right now.
      const browsingContext = node.ownerGlobal?.browsingContext;

      // For not yet cached nodes generate a unique id without curly braces.
      nodeId = lazy.generateUUID();

      const details = {
        browserId: browsingContext?.browserId,
        browsingContextGroupId: browsingContext?.group.id,
        browsingContextId: browsingContext?.id,
        isTopBrowsingContext: browsingContext?.parent === null,
        nodeWeakRef: Cu.getWeakReference(node),
      };

      this.#nodeIdMap.set(node, nodeId);
      this.#seenNodesMap.set(nodeId, details);

      // Also add the information for the node id and its correlated browsing
      // context to allow the parent process to update the seen nodes.
      if (!seenNodeIds.has(browsingContext)) {
        seenNodeIds.set(browsingContext, []);
      }
      seenNodeIds.get(browsingContext).push(nodeId);
    }

    return nodeId;
  }

  /**
   * Clear known DOM nodes.
   *
   * @param {object=} options
   * @param {boolean=} options.all
   *     Clear all references from any browsing context. Defaults to false.
   * @param {BrowsingContext=} options.browsingContext
   *     Clear all references living in that browsing context.
   */
  clear(options = {}) {
    const { all = false, browsingContext } = options;

    if (all) {
      this.#nodeIdMap = new WeakMap();
      this.#seenNodesMap.clear();
      return;
    }

    if (browsingContext) {
      for (const [nodeId, identifier] of this.#seenNodesMap.entries()) {
        const { browsingContextId, nodeWeakRef } = identifier;
        const node = nodeWeakRef.get();

        if (browsingContextId === browsingContext.id) {
          this.#nodeIdMap.delete(node);
          this.#seenNodesMap.delete(nodeId);
        }
      }

      return;
    }

    throw new Error(`Requires "browsingContext" or "all" to be set.`);
  }

  /**
   * Get a DOM node by its unique reference.
   *
   * @param {BrowsingContext} browsingContext
   *     The browsing context the node should be part of.
   * @param {string} nodeId
   *     The unique node reference of the DOM node.
   *
   * @returns {Node|null}
   *     The DOM node that the unique identifier was generated for or
   *     `null` if the node does not exist anymore.
   */
  getNode(browsingContext, nodeId) {
    const nodeDetails = this.getReferenceDetails(nodeId);

    // Check that the node reference is known, and is associated with a
    // browsing context that shares the same browsing context group.
    if (
      nodeDetails === null ||
      nodeDetails.browsingContextGroupId !== browsingContext.group.id
    ) {
      return null;
    }

    if (nodeDetails.nodeWeakRef) {
      return nodeDetails.nodeWeakRef.get();
    }

    return null;
  }

  /**
   * Get detailed information for the node reference.
   *
   * @param {string} nodeId
   *
   * @returns {NodeReferenceDetails}
   *     Node details like: browsingContextId
   */
  getReferenceDetails(nodeId) {
    const details = this.#seenNodesMap.get(nodeId);

    return details !== undefined ? details : null;
  }
}
PK
!<�f��/>/>6chrome/remote/content/shared/webdriver/Session.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  accessibility:
    "chrome://remote/content/shared/webdriver/Accessibility.sys.mjs",
  allowAllCerts: "chrome://remote/content/marionette/cert.sys.mjs",
  Capabilities: "chrome://remote/content/shared/webdriver/Capabilities.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  registerProcessDataActor:
    "chrome://remote/content/shared/webdriver/process-actors/WebDriverProcessDataParent.sys.mjs",
  RootMessageHandler:
    "chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs",
  RootMessageHandlerRegistry:
    "chrome://remote/content/shared/messagehandler/RootMessageHandlerRegistry.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
  unregisterProcessDataActor:
    "chrome://remote/content/shared/webdriver/process-actors/WebDriverProcessDataParent.sys.mjs",
  WebDriverBiDiConnection:
    "chrome://remote/content/webdriver-bidi/WebDriverBiDiConnection.sys.mjs",
  WebSocketHandshake:
    "chrome://remote/content/server/WebSocketHandshake.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

// Global singleton that holds active WebDriver sessions
const webDriverSessions = new Map();

/**
 * @typedef {Set} SessionConfigurationFlags
 *     A set of flags defining the features of a WebDriver session. It can be
 *     empty or contain entries as listed below. External specifications may
 *     define additional flags, or create sessions without the HTTP flag.
 *
 *     <dl>
 *       <dt><code>"bidi"</code> (string)
 *       <dd>Flag indicating a WebDriver BiDi session.
 *       <dt><code>"http"</code> (string)
 *       <dd>Flag indicating a WebDriver classic (HTTP) session.
 *     </dl>
 */

/**
 * Representation of WebDriver session.
 */
export class WebDriverSession {
  #bidi;
  #capabilities;
  #connections;
  #http;
  #id;
  #messageHandler;
  #path;

  static SESSION_FLAG_BIDI = "bidi";
  static SESSION_FLAG_HTTP = "http";

  /**
   * Construct a new WebDriver session.
   *
   * It is expected that the caller performs the necessary checks on
   * the requested capabilities to be WebDriver conforming.  The WebDriver
   * service offered by Marionette does not match or negotiate capabilities
   * beyond type- and bounds checks.
   *
   * <h3>Capabilities</h3>
   *
   * <dl>
   *  <dt><code>acceptInsecureCerts</code> (boolean)
   *  <dd>Indicates whether untrusted and self-signed TLS certificates
   *   are implicitly trusted on navigation for the duration of the session.
   *
   *  <dt><code>pageLoadStrategy</code> (string)
   *  <dd>(HTTP only) The page load strategy to use for the current session.  Must be
   *   one of "<tt>none</tt>", "<tt>eager</tt>", and "<tt>normal</tt>".
   *
   *  <dt><code>proxy</code> (Proxy object)
   *  <dd>Defines the proxy configuration.
   *
   *  <dt><code>setWindowRect</code> (boolean)
   *  <dd>(HTTP only) Indicates whether the remote end supports all of the resizing
   *   and repositioning commands.
   *
   *  <dt><code>strictFileInteractability</code> (boolean)
   *  <dd>(HTTP only) Defines the current session’s strict file interactability.
   *
   *  <dt><code>timeouts</code> (Timeouts object)
   *  <dd>(HTTP only) Describes the timeouts imposed on certain session operations.
   *
   *  <dt><code>unhandledPromptBehavior</code> (string)
   *  <dd>Describes the current session’s user prompt handler.  Must be one of
   *   "<tt>accept</tt>", "<tt>accept and notify</tt>", "<tt>dismiss</tt>",
   *   "<tt>dismiss and notify</tt>", and "<tt>ignore</tt>".  Defaults to the
   *   "<tt>dismiss and notify</tt>" state.
   *
   *  <dt><code>moz:accessibilityChecks</code> (boolean)
   *  <dd>(HTTP only) Run a11y checks when clicking elements.
   *
   *  <dt><code>moz:debuggerAddress</code> (boolean)
   *  <dd>Indicate that the Chrome DevTools Protocol (CDP) has to be enabled.
   *
   *  <dt><code>moz:webdriverClick</code> (boolean)
   *  <dd>(HTTP only) Use a WebDriver conforming <i>WebDriver::ElementClick</i>.
   * </dl>
   *
   * <h4>WebAuthn</h4>
   *
   * <dl>
   *  <dt><code>webauthn:virtualAuthenticators</code> (boolean)
   *  <dd>Indicates whether the endpoint node supports all Virtual
   *   Authenticators commands.
   *
   *  <dt><code>webauthn:extension:uvm</code> (boolean)
   *  <dd>Indicates whether the endpoint node WebAuthn WebDriver
   *   implementation supports the User Verification Method extension.
   *
   *  <dt><code>webauthn:extension:prf</code> (boolean)
   *  <dd>Indicates whether the endpoint node WebAuthn WebDriver
   *   implementation supports the prf extension.
   *
   *  <dt><code>webauthn:extension:largeBlob</code> (boolean)
   *  <dd>Indicates whether the endpoint node WebAuthn WebDriver implementation
   *   supports the largeBlob extension.
   *
   *  <dt><code>webauthn:extension:credBlob</code> (boolean)
   *  <dd>Indicates whether the endpoint node WebAuthn WebDriver implementation
   *   supports the credBlob extension.
   * </dl>
   *
   * <h4>Timeouts object</h4>
   *
   * <dl>
   *  <dt><code>script</code> (number)
   *  <dd>Determines when to interrupt a script that is being evaluates.
   *
   *  <dt><code>pageLoad</code> (number)
   *  <dd>Provides the timeout limit used to interrupt navigation of the
   *   browsing context.
   *
   *  <dt><code>implicit</code> (number)
   *  <dd>Gives the timeout of when to abort when locating an element.
   * </dl>
   *
   * <h4>Proxy object</h4>
   *
   * <dl>
   *  <dt><code>proxyType</code> (string)
   *  <dd>Indicates the type of proxy configuration.  Must be one
   *   of "<tt>pac</tt>", "<tt>direct</tt>", "<tt>autodetect</tt>",
   *   "<tt>system</tt>", or "<tt>manual</tt>".
   *
   *  <dt><code>proxyAutoconfigUrl</code> (string)
   *  <dd>Defines the URL for a proxy auto-config file if
   *   <code>proxyType</code> is equal to "<tt>pac</tt>".
   *
   *  <dt><code>httpProxy</code> (string)
   *  <dd>Defines the proxy host for HTTP traffic when the
   *   <code>proxyType</code> is "<tt>manual</tt>".
   *
   *  <dt><code>noProxy</code> (string)
   *  <dd>Lists the address for which the proxy should be bypassed when
   *   the <code>proxyType</code> is "<tt>manual</tt>".  Must be a JSON
   *   List containing any number of any of domains, IPv4 addresses, or IPv6
   *   addresses.
   *
   *  <dt><code>sslProxy</code> (string)
   *  <dd>Defines the proxy host for encrypted TLS traffic when the
   *   <code>proxyType</code> is "<tt>manual</tt>".
   *
   *  <dt><code>socksProxy</code> (string)
   *  <dd>Defines the proxy host for a SOCKS proxy traffic when the
   *   <code>proxyType</code> is "<tt>manual</tt>".
   *
   *  <dt><code>socksVersion</code> (string)
   *  <dd>Defines the SOCKS proxy version when the <code>proxyType</code> is
   *   "<tt>manual</tt>".  It must be any integer between 0 and 255
   *   inclusive.
   * </dl>
   *
   * <h3>Example</h3>
   *
   * Input:
   *
   * <pre><code>
   *     {"capabilities": {"acceptInsecureCerts": true}}
   * </code></pre>
   *
   * @param {Record<string, *>=} capabilities
   *     JSON Object containing any of the recognized capabilities listed
   *     above.
   * @param {SessionConfigurationFlags} flags
   *     Session configuration flags.
   * @param {WebDriverBiDiConnection=} connection
   *     An optional existing WebDriver BiDi connection to associate with the
   *     new session.
   *
   * @throws {SessionNotCreatedError}
   *     If, for whatever reason, a session could not be created.
   */
  constructor(capabilities, flags, connection) {
    // WebSocket connections that use this session. This also accounts for
    // possible disconnects due to network outages, which require clients
    // to reconnect.
    this.#connections = new Set();

    this.#id = lazy.generateUUID();

    // Flags for WebDriver session features
    this.#bidi = flags.has(WebDriverSession.SESSION_FLAG_BIDI);
    this.#http = flags.has(WebDriverSession.SESSION_FLAG_HTTP);

    if (this.#bidi == this.#http) {
      // Initially a WebDriver session can either be HTTP or BiDi. An upgrade of a
      // HTTP session to offer BiDi features is done after the constructor is run.
      throw new lazy.error.SessionNotCreatedError(
        `Initially the WebDriver session needs to be either HTTP or BiDi (bidi=${
          this.#bidi
        }, http=${this.#http})`
      );
    }

    // Define the HTTP path to query this session via WebDriver BiDi
    this.#path = `/session/${this.#id}`;

    try {
      this.#capabilities = lazy.Capabilities.fromJSON(capabilities, this.#bidi);
    } catch (e) {
      throw new lazy.error.SessionNotCreatedError(e);
    }

    if (this.proxy.init()) {
      lazy.logger.info(
        `Proxy settings initialized: ${JSON.stringify(this.proxy)}`
      );
    }

    if (this.acceptInsecureCerts) {
      lazy.logger.warn(
        "TLS certificate errors will be ignored for this session"
      );
      lazy.allowAllCerts.enable();
    }

    // If we are testing accessibility with marionette, start a11y service in
    // chrome first. This will ensure that we do not have any content-only
    // services hanging around.
    if (this.a11yChecks && lazy.accessibility.service) {
      lazy.logger.info("Preemptively starting accessibility service in Chrome");
    }

    // If a connection without an associated session has been specified
    // immediately register the newly created session for it.
    if (connection) {
      connection.registerSession(this);
      this.#connections.add(connection);
    }

    // Maps a Navigable (browsing context or content browser for top-level
    // browsing contexts) to a Set of nodeId's.
    this.navigableSeenNodes = new WeakMap();

    lazy.registerProcessDataActor();

    webDriverSessions.set(this.#id, this);
  }

  destroy() {
    webDriverSessions.delete(this.#id);

    lazy.unregisterProcessDataActor();

    this.navigableSeenNodes = null;

    lazy.allowAllCerts.disable();

    // Close all open connections which unregister themselves.
    this.#connections.forEach(connection => connection.close());
    if (this.#connections.size > 0) {
      lazy.logger.warn(
        `Failed to close ${this.#connections.size} WebSocket connections`
      );
    }

    // Destroy the dedicated MessageHandler instance if we created one.
    if (this.#messageHandler) {
      this.#messageHandler.off(
        "message-handler-protocol-event",
        this._onMessageHandlerProtocolEvent
      );
      this.#messageHandler.destroy();
    }
  }

  get a11yChecks() {
    return this.#capabilities.get("moz:accessibilityChecks");
  }

  get acceptInsecureCerts() {
    return this.#capabilities.get("acceptInsecureCerts");
  }

  get bidi() {
    return this.#bidi;
  }

  set bidi(value) {
    this.#bidi = value;
  }

  get capabilities() {
    return this.#capabilities;
  }

  get http() {
    return this.#http;
  }

  get id() {
    return this.#id;
  }

  get messageHandler() {
    if (!this.#messageHandler) {
      this.#messageHandler =
        lazy.RootMessageHandlerRegistry.getOrCreateMessageHandler(this.#id);
      this._onMessageHandlerProtocolEvent =
        this._onMessageHandlerProtocolEvent.bind(this);
      this.#messageHandler.on(
        "message-handler-protocol-event",
        this._onMessageHandlerProtocolEvent
      );
    }

    return this.#messageHandler;
  }

  get pageLoadStrategy() {
    return this.#capabilities.get("pageLoadStrategy");
  }

  get path() {
    return this.#path;
  }

  get proxy() {
    return this.#capabilities.get("proxy");
  }

  get strictFileInteractability() {
    return this.#capabilities.get("strictFileInteractability");
  }

  get timeouts() {
    return this.#capabilities.get("timeouts");
  }

  set timeouts(timeouts) {
    this.#capabilities.set("timeouts", timeouts);
  }

  get userPromptHandler() {
    return this.#capabilities.get("unhandledPromptBehavior");
  }

  get webSocketUrl() {
    return this.#capabilities.get("webSocketUrl");
  }

  async execute(module, command, params) {
    // XXX: At the moment, commands do not describe consistently their destination,
    // so we will need a translation step based on a specific command and its params
    // in order to extract a destination that can be understood by the MessageHandler.
    //
    // For now, an option is to send all commands to ROOT, and all BiDi MessageHandler
    // modules will therefore need to implement this translation step in the root
    // implementation of their module.
    const destination = {
      type: lazy.RootMessageHandler.type,
    };
    if (!this.messageHandler.supportsCommand(module, command, destination)) {
      throw new lazy.error.UnknownCommandError(`${module}.${command}`);
    }

    return this.messageHandler.handleCommand({
      moduleName: module,
      commandName: command,
      params,
      destination,
    });
  }

  /**
   * Remove the specified WebDriver BiDi connection.
   *
   * @param {WebDriverBiDiConnection} connection
   */
  removeConnection(connection) {
    if (this.#connections.has(connection)) {
      this.#connections.delete(connection);
    } else {
      lazy.logger.warn("Trying to remove a connection that doesn't exist.");
    }
  }

  toString() {
    return `[object ${this.constructor.name} ${this.#id}]`;
  }

  // nsIHttpRequestHandler

  /**
   * Handle new WebSocket connection requests.
   *
   * WebSocket clients will attempt to connect to this session at
   * `/session/:id`.  Hereby a WebSocket upgrade will automatically
   * be performed.
   *
   * @param {Request} request
   *     HTTP request (httpd.js)
   * @param {Response} response
   *     Response to an HTTP request (httpd.js)
   */
  async handle(request, response) {
    const webSocket = await lazy.WebSocketHandshake.upgrade(request, response);
    const conn = new lazy.WebDriverBiDiConnection(
      webSocket,
      response._connection
    );
    conn.registerSession(this);
    this.#connections.add(conn);
  }

  _onMessageHandlerProtocolEvent(eventName, messageHandlerEvent) {
    const { name, data } = messageHandlerEvent;
    this.#connections.forEach(connection => connection.sendEvent(name, data));
  }

  // XPCOM

  get QueryInterface() {
    return ChromeUtils.generateQI(["nsIHttpRequestHandler"]);
  }
}

/**
 * Get the list of seen nodes for the given browsing context unique to a
 * WebDriver session.
 *
 * @param {string} sessionId
 *     The id of the WebDriver session to use.
 * @param {BrowsingContext} browsingContext
 *     Browsing context the node is part of.
 *
 * @returns {Set}
 *     The list of seen nodes.
 */
export function getSeenNodesForBrowsingContext(sessionId, browsingContext) {
  if (!lazy.TabManager.isValidCanonicalBrowsingContext(browsingContext)) {
    // If browsingContext is not a valid Browsing Context, return an empty set.
    return new Set();
  }

  const navigable =
    lazy.TabManager.getNavigableForBrowsingContext(browsingContext);
  const session = getWebDriverSessionById(sessionId);

  if (!session.navigableSeenNodes.has(navigable)) {
    // The navigable hasn't been seen yet.
    session.navigableSeenNodes.set(navigable, new Set());
  }

  return session.navigableSeenNodes.get(navigable);
}

/**
 *
 * @param {string} sessionId
 *     The ID of the WebDriver session to retrieve.
 *
 * @returns {WebDriverSession|undefined}
 *     The WebDriver session or undefined if the id is not known.
 */
export function getWebDriverSessionById(sessionId) {
  return webDriverSessions.get(sessionId);
}
PK
!<y��n4n49chrome/remote/content/shared/webdriver/URLPattern.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
});

/**
 * Parsed pattern to use for URL matching.
 *
 * @typedef {object} ParsedURLPattern
 * @property {string|null} protocol
 *     The protocol, for instance "https".
 * @property {string|null} hostname
 *     The hostname, for instance "example.com".
 * @property {string|null} port
 *     The serialized port. Empty string for default ports of special schemes.
 * @property {string|null} path
 *     The path, starting with "/".
 * @property {string|null} search
 *     The search query string, without the leading "?"
 */

/**
 * Subset of properties extracted from a parsed URL.
 *
 * @typedef {object} ParsedURL
 * @property {string=} host
 * @property {string|Array<string>} path
 *     Either a string if the path is an opaque path, or an array of strings
 *     (path segments).
 * @property {number=} port
 * @property {string=} query
 * @property {string=} scheme
 */

/**
 * Enum of URLPattern types.
 *
 * @readonly
 * @enum {URLPatternType}
 */
const URLPatternType = {
  Pattern: "pattern",
  String: "string",
};

const supportedURLPatternTypes = Object.values(URLPatternType);

const SPECIAL_SCHEMES = ["file", "http", "https", "ws", "wss"];
const DEFAULT_PORTS = {
  file: null,
  http: 80,
  https: 443,
  ws: 80,
  wss: 443,
};

/**
 * Check if a given URL pattern is compatible with the provided URL.
 *
 * Implements https://w3c.github.io/webdriver-bidi/#match-url-pattern
 *
 * @param {ParsedURLPattern} urlPattern
 *     The URL pattern to match.
 * @param {string} url
 *     The string representation of a URL to test against the pattern.
 *
 * @returns {boolean}
 *     True if the pattern is compatible with the provided URL, false otherwise.
 */
export function matchURLPattern(urlPattern, url) {
  const parsedURL = parseURL(url);

  if (urlPattern.protocol !== null && urlPattern.protocol != parsedURL.scheme) {
    return false;
  }

  if (urlPattern.hostname !== null && urlPattern.hostname != parsedURL.host) {
    return false;
  }

  if (urlPattern.port !== null && urlPattern.port != serializePort(parsedURL)) {
    return false;
  }

  if (
    urlPattern.pathname !== null &&
    urlPattern.pathname != serializePath(parsedURL)
  ) {
    return false;
  }

  if (urlPattern.search !== null) {
    const urlQuery = parsedURL.query === null ? "" : parsedURL.query;
    if (urlPattern.search != urlQuery) {
      return false;
    }
  }

  return true;
}

/**
 * Parse a URLPattern into a parsed pattern object which can be used to match
 * URLs using `matchURLPattern`.
 *
 * Implements https://w3c.github.io/webdriver-bidi/#parse-url-pattern
 *
 * @param {URLPattern} pattern
 *     The pattern to parse.
 *
 * @returns {ParsedURLPattern}
 *     The parsed URL pattern.
 *
 * @throws {InvalidArgumentError}
 *     Raised if an argument is of an invalid type or value.
 * @throws {UnsupportedOperationError}
 *     Raised if the pattern uses a protocol not supported by Firefox.
 */
export function parseURLPattern(pattern) {
  lazy.assert.object(
    pattern,
    lazy.pprint`Expected URL pattern to be an object, got ${pattern}`
  );

  let hasProtocol = true;
  let hasHostname = true;
  let hasPort = true;
  let hasPathname = true;
  let hasSearch = true;

  let patternUrl;
  switch (pattern.type) {
    case URLPatternType.Pattern:
      patternUrl = "";
      if ("protocol" in pattern) {
        patternUrl += parseProtocol(pattern.protocol);
      } else {
        hasProtocol = false;
        patternUrl += "http";
      }

      const scheme = patternUrl.toLowerCase();
      patternUrl += ":";
      if (SPECIAL_SCHEMES.includes(scheme)) {
        patternUrl += "//";
      }

      if ("hostname" in pattern) {
        patternUrl += parseHostname(pattern.hostname, scheme);
      } else {
        if (scheme != "file") {
          patternUrl += "placeholder";
        }
        hasHostname = false;
      }

      if ("port" in pattern) {
        patternUrl += parsePort(pattern.port);
      } else {
        hasPort = false;
      }

      if ("pathname" in pattern) {
        patternUrl += parsePathname(pattern.pathname);
      } else {
        hasPathname = false;
      }

      if ("search" in pattern) {
        patternUrl += parseSearch(pattern.search);
      } else {
        hasSearch = false;
      }
      break;
    case URLPatternType.String:
      lazy.assert.string(
        pattern.pattern,
        lazy.pprint`Expected URL pattern "pattern" to be a string, got ${pattern.pattern}`
      );
      patternUrl = unescapeUrlPattern(pattern.pattern);
      break;
    default:
      throw new lazy.error.InvalidArgumentError(
        `Expected "urlPattern" type to be one of ${supportedURLPatternTypes}, got ${pattern.type}`
      );
  }

  if (!URL.canParse(patternUrl)) {
    throw new lazy.error.InvalidArgumentError(
      `Unable to parse URL "${patternUrl}"`
    );
  }

  let parsedURL;
  try {
    parsedURL = parseURL(patternUrl);
  } catch (e) {
    throw new lazy.error.InvalidArgumentError(
      `Failed to parse URL "${patternUrl}"`
    );
  }

  if (hasProtocol && !SPECIAL_SCHEMES.includes(parsedURL.scheme)) {
    throw new lazy.error.UnsupportedOperationError(
      `URL pattern did not specify a supported protocol (one of ${SPECIAL_SCHEMES}), got ${parsedURL.scheme}`
    );
  }

  return {
    protocol: hasProtocol ? parsedURL.scheme : null,
    hostname: hasHostname ? parsedURL.host : null,
    port: hasPort ? serializePort(parsedURL) : null,
    pathname:
      hasPathname && parsedURL.path.length ? serializePath(parsedURL) : null,
    search: hasSearch ? parsedURL.query || "" : null,
  };
}

/**
 * Parse the hostname property of a URLPatternPattern.
 *
 * @param {string} hostname
 *     A hostname property.
 * @param {string} scheme
 *     The scheme for the URLPatternPattern.
 *
 * @returns {string}
 *     The parsed property.
 *
 * @throws {InvalidArgumentError}
 *     Raised if an argument is of an invalid type or value.
 */
function parseHostname(hostname, scheme) {
  if (typeof hostname != "string" || hostname == "") {
    throw new lazy.error.InvalidArgumentError(
      `Expected URLPattern "hostname" to be a non-empty string, got ${hostname}`
    );
  }

  if (scheme == "file") {
    throw new lazy.error.InvalidArgumentError(
      `URLPattern with "file" scheme cannot specify a hostname, got ${hostname}`
    );
  }

  hostname = unescapeUrlPattern(hostname);

  const forbiddenHostnameCharacters = ["/", "?", "#"];
  let insideBrackets = false;
  for (const codepoint of hostname) {
    if (
      forbiddenHostnameCharacters.includes(codepoint) ||
      (!insideBrackets && codepoint == ":")
    ) {
      throw new lazy.error.InvalidArgumentError(
        `URL pattern "hostname" contained a forbidden character, got "${hostname}"`
      );
    }

    if (codepoint == "[") {
      insideBrackets = true;
    } else if (codepoint == "]") {
      insideBrackets = false;
    }
  }

  return hostname;
}

/**
 * Parse the pathname property of a URLPatternPattern.
 *
 * @param {string} pathname
 *     A pathname property.
 *
 * @returns {string}
 *     The parsed property.
 *
 * @throws {InvalidArgumentError}
 *     Raised if an argument is of an invalid type or value.
 */
function parsePathname(pathname) {
  lazy.assert.string(
    pathname,
    lazy.pprint`Expected URL pattern "pathname" to be a string, got ${pathname}`
  );

  pathname = unescapeUrlPattern(pathname);
  if (!pathname.startsWith("/")) {
    pathname = `/${pathname}`;
  }

  if (pathname.includes("?") || pathname.includes("#")) {
    throw new lazy.error.InvalidArgumentError(
      `URL pattern "pathname" contained a forbidden character, got "${pathname}"`
    );
  }

  return pathname;
}

/**
 * Parse the port property of a URLPatternPattern.
 *
 * @param {string} port
 *     A port property.
 *
 * @returns {string}
 *     The parsed property.
 *
 * @throws {InvalidArgumentError}
 *     Raised if an argument is of an invalid type or value.
 */
function parsePort(port) {
  if (typeof port != "string" || port == "") {
    throw new lazy.error.InvalidArgumentError(
      `Expected URLPattern "port" to be a non-empty string, got ${port}`
    );
  }

  port = unescapeUrlPattern(port);

  const isNumber = /^\d*$/.test(port);
  if (!isNumber) {
    throw new lazy.error.InvalidArgumentError(
      `URL pattern "port" is not a valid number, got "${port}"`
    );
  }

  return `:${port}`;
}

/**
 * Parse the protocol property of a URLPatternPattern.
 *
 * @param {string} protocol
 *     A protocol property.
 *
 * @returns {string}
 *     The parsed property.
 *
 * @throws {InvalidArgumentError}
 *     Raised if an argument is of an invalid type or value.
 */
function parseProtocol(protocol) {
  if (typeof protocol != "string" || protocol == "") {
    throw new lazy.error.InvalidArgumentError(
      `Expected URLPattern "protocol" to be a non-empty string, got ${protocol}`
    );
  }

  protocol = unescapeUrlPattern(protocol);
  if (!/^[a-zA-Z0-9+-.]*$/.test(protocol)) {
    throw new lazy.error.InvalidArgumentError(
      `URL pattern "protocol" contained a forbidden character, got "${protocol}"`
    );
  }

  return protocol;
}

/**
 * Parse the search property of a URLPatternPattern.
 *
 * @param {string} search
 *     A search property.
 *
 * @returns {string}
 *     The parsed property.
 *
 * @throws {InvalidArgumentError}
 *     Raised if an argument is of an invalid type or value.
 */
function parseSearch(search) {
  lazy.assert.string(
    search,
    lazy.pprint`Expected URL pattern "search" to be a string, got ${search}`
  );

  search = unescapeUrlPattern(search);
  if (!search.startsWith("?")) {
    search = `?${search}`;
  }

  if (search.includes("#")) {
    throw new lazy.error.InvalidArgumentError(
      `Expected URLPattern "search" to never contain "#", got ${search}`
    );
  }

  return search;
}

/**
 * Parse a string URL. This tries to be close to Basic URL Parser, however since
 * this is not currently implemented in Firefox and URL parsing has many edge
 * cases, it does not try to be a faithful implementation.
 *
 * Edge cases which are not supported are mostly about non-special URLs, which
 * in practice should not be observable in automation.
 *
 * @param {string} url
 *     The string based URL to parse.
 * @returns {ParsedURL}
 *     The parsed URL.
 */
function parseURL(url) {
  const urlObj = new URL(url);
  const uri = urlObj.URI;

  return {
    scheme: uri.scheme,
    // Note: Use urlObj instead of uri for hostname:
    // nsIURI removes brackets from ipv6 hostnames (eg [::1] becomes ::1).
    host: urlObj.hostname,
    path: uri.filePath,
    // Note: Use urlObj instead of uri for port:
    // nsIURI throws on the port getter for non-special schemes.
    port: urlObj.port != "" ? Number(uri.port) : null,
    query: uri.hasQuery ? uri.query : null,
  };
}

/**
 * Serialize the path of a parsed URL.
 *
 * @see https://pr-preview.s3.amazonaws.com/w3c/webdriver-bidi/pull/429.html#parse-url-pattern
 *
 * @param {ParsedURL} url
 *     A parsed url.
 *
 * @returns {string}
 *     The serialized path
 */
function serializePath(url) {
  // Check for opaque path
  if (typeof url.path == "string") {
    return url.path;
  }

  let serialized = "";
  for (const segment of url.path) {
    serialized += `/${segment}`;
  }

  return serialized;
}

/**
 * Serialize the port of a parsed URL.
 *
 * @see https://pr-preview.s3.amazonaws.com/w3c/webdriver-bidi/pull/429.html#parse-url-pattern
 *
 * @param {ParsedURL} url
 *     A parsed url.
 *
 * @returns {string}
 *     The serialized port
 */
function serializePort(url) {
  let port = null;
  if (
    SPECIAL_SCHEMES.includes(url.scheme) &&
    DEFAULT_PORTS[url.scheme] !== null &&
    (url.port === null || url.port == DEFAULT_PORTS[url.scheme])
  ) {
    port = "";
  } else if (url.port !== null) {
    port = `${url.port}`;
  }

  return port;
}

/**
 * Unescape and check a pattern string against common forbidden characters.
 *
 * @see https://pr-preview.s3.amazonaws.com/w3c/webdriver-bidi/pull/429.html#unescape-url-pattern
 *
 * @param {string} pattern
 *     Either a full URLPatternString pattern or a property of a URLPatternPattern.
 *
 * @returns {string}
 *     The unescaped pattern
 *
 * @throws {InvalidArgumentError}
 *     Raised if an argument is of an invalid type or value.
 */
function unescapeUrlPattern(pattern) {
  const forbiddenCharacters = ["(", ")", "*", "{", "}"];
  const escapeCharacter = "\\";

  let isEscaped = false;
  let result = "";

  for (const codepoint of Array.from(pattern)) {
    if (!isEscaped) {
      if (forbiddenCharacters.includes(codepoint)) {
        throw new lazy.error.InvalidArgumentError(
          `URL pattern contained an unescaped forbidden character ${codepoint}`
        );
      }

      if (codepoint == escapeCharacter) {
        isEscaped = true;
        continue;
      }
    }

    result += codepoint;
    isEscaped = false;
  }

  return result;
}
PK
!<�Kg�!!@chrome/remote/content/shared/webdriver/UserPromptHandler.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
});

/**
 * @typedef {string} PromptHandlers
 */

/**
 * Enum of known prompt handlers.
 *
 * @readonly
 * @enum {PromptHandlers}
 *
 * @see https://w3c.github.io/webdriver/#dfn-known-prompt-handlers
 */
export const PromptHandlers = {
  /** All simple dialogs encountered should be accepted. */
  Accept: "accept",
  /**
   * All simple dialogs encountered should be accepted, and an error
   * returned that the dialog was handled.
   */
  AcceptAndNotify: "accept and notify",
  /** All simple dialogs encountered should be dismissed. */
  Dismiss: "dismiss",
  /**
   * All simple dialogs encountered should be dismissed, and an error
   * returned that the dialog was handled.
   */
  DismissAndNotify: "dismiss and notify",
  /** All simple dialogs encountered should be left to the user to handle. */
  Ignore: "ignore",
};

/**
 * @typedef {string} PromptTypes
 */

/**
 * Enum of valid prompt types.
 *
 * @readonly
 * @enum {PromptTypes}
 *
 * @see https://w3c.github.io/webdriver/#dfn-valid-prompt-types
 */
export const PromptTypes = {
  // A simple alert dialog
  Alert: "alert",
  // A simple beforeunload dialog. If no handler is set it falls back to the
  // `accept` handler to keep backward compatibility with WebDriver classic,
  // which doesn't customize this prompt type.
  BeforeUnload: "beforeUnload",
  // A simple confirm dialog
  Confirm: "confirm",
  // Default value when no specific handler is configured. Can only be set when
  // specifying the unhandlePromptBehavior capability with a map containing a
  // "default" entry. See FALLBACK_DEFAULT_PROMPT_TYPE.
  Default: "default",
  // A simple prompt dialog
  Prompt: "prompt",
};

/**
 * Internal prompt type used when the unhandledPromptBehavior capability is
 * set as a string. The "fallbackDefault" type will apply to "alert", "confirm"
 * and "prompt" prompts, but will not apply to "beforeUnload" prompts.
 */
const FALLBACK_DEFAULT_PROMPT_TYPE = "fallbackDefault";

export class PromptHandlerConfiguration {
  #handler;
  #notify;

  /**
   * Create an instance of a prompt handler configuration.
   *
   * @param {string} handler
   *     Handler to use for the user prompt. One of "accept", "dismiss" or "ignore".
   * @param {boolean} notify
   *     Flag to indicate if the user needs to be notified when the dialog was
   *     handled.
   *
   * @see https://w3c.github.io/webdriver/#dfn-prompt-handler-configuration
   */
  constructor(handler, notify) {
    this.#handler = handler;
    this.#notify = notify;
  }

  get handler() {
    return this.#handler;
  }

  get notify() {
    return this.#notify;
  }

  toString() {
    return "[object PromptHandlerConfiguration]";
  }

  /**
   * JSON serialization of the prompt handler configuration object.
   *
   * @returns {Record<string, ?>} json
   *
   * @see https://w3c.github.io/webdriver/#dfn-serialize-a-prompt-handler-configuration
   */
  toJSON() {
    let serialized = this.#handler;
    if (["accept", "dismiss"].includes(serialized) && this.#notify) {
      serialized += " and notify";
    }

    return serialized;
  }
}

export class UserPromptHandler {
  #promptTypeToHandlerMap;

  constructor() {
    // Note: this map is null until update-the-user-prompt-handler initializes
    // it.
    this.#promptTypeToHandlerMap = null;
  }

  get activePromptHandlers() {
    return this.#promptTypeToHandlerMap;
  }

  get PromptHandlers() {
    return PromptHandlers;
  }

  get PromptTypes() {
    return PromptTypes;
  }

  /**
   * Unmarshal a JSON object representation of the unhandledPromptBehavior capability.
   *
   * @param {Record<string, ?>} json
   *     JSON Object to unmarshal.
   *
   * @throws {InvalidArgumentError}
   *     When the value of the unhandledPromptBehavior capability is invalid.
   *
   * @returns {UserPromptHandler}
   *
   * @see https://w3c.github.io/webdriver/#dfn-deserialize-as-an-unhandled-prompt-behavior
   */
  static fromJSON(json) {
    let isStringValue = false;
    let value = json;
    if (typeof value === "string") {
      // A single specified prompt behavior or for WebDriver classic.
      value = { [FALLBACK_DEFAULT_PROMPT_TYPE]: value };
      isStringValue = true;
    }

    lazy.assert.object(
      value,
      lazy.pprint`Expected "unhandledPromptBehavior" to be an object, got ${value}`
    );

    const userPromptHandlerCapability = new Map();
    for (let [promptType, handler] of Object.entries(value)) {
      if (!isStringValue) {
        const validPromptTypes = Object.values(PromptTypes);
        lazy.assert.in(
          promptType,
          validPromptTypes,
          lazy.pprint`Expected "promptType" to be one of ${validPromptTypes}, got ${promptType}`
        );
      }

      const knownPromptHandlers = Object.values(PromptHandlers);
      lazy.assert.in(
        handler,
        knownPromptHandlers,
        lazy.pprint`Expected "handler" to be one of ${knownPromptHandlers}, got ${handler}`
      );

      let notify = false;
      switch (handler) {
        case PromptHandlers.AcceptAndNotify:
          handler = PromptHandlers.Accept;
          notify = true;
          break;
        case PromptHandlers.DismissAndNotify:
          handler = PromptHandlers.Dismiss;
          notify = true;
          break;
        case PromptHandlers.Ignore:
          notify = true;
          break;
      }

      const configuration = new PromptHandlerConfiguration(handler, notify);
      userPromptHandlerCapability.set(promptType, configuration);
    }
    const userPromptHandler = new UserPromptHandler();
    userPromptHandler.update(userPromptHandlerCapability);
    return userPromptHandler;
  }

  /**
   * Get the handler for the given prompt type.
   *
   * @param {string} promptType
   *     The prompt type to retrieve the handler for.
   *
   * @returns {PromptHandlerConfiguration}
   *
   * @see https://w3c.github.io/webdriver/#dfn-get-the-prompt-handler
   */
  getPromptHandler(promptType) {
    let handlers;

    if (this.#promptTypeToHandlerMap === null) {
      handlers = new Map();
    } else {
      handlers = this.#promptTypeToHandlerMap;
    }

    if (handlers.has(promptType)) {
      return handlers.get(promptType);
    }

    if (handlers.has(PromptTypes.Default)) {
      return handlers.get(PromptTypes.Default);
    }

    if (promptType === PromptTypes.BeforeUnload) {
      return new PromptHandlerConfiguration(PromptHandlers.Accept, false);
    }

    if (handlers.has(FALLBACK_DEFAULT_PROMPT_TYPE)) {
      return handlers.get(FALLBACK_DEFAULT_PROMPT_TYPE);
    }

    return new PromptHandlerConfiguration(PromptHandlers.Dismiss, true);
  }

  /**
   * Updates the prompt handler configuration for a given requested prompt
   * handler map.
   *
   * @param {Map} requestedPromptHandler
   *     The request prompt handler configuration map.
   *
   * @see https://w3c.github.io/webdriver/#dfn-update-the-user-prompt-handler
   */
  update(requestedPromptHandler) {
    if (this.#promptTypeToHandlerMap === null) {
      this.#promptTypeToHandlerMap = new Map();
    }

    for (const [promptType, handler] of requestedPromptHandler) {
      this.#promptTypeToHandlerMap.set(promptType, handler);
    }
  }

  /**
   * JSON serialization of the user prompt handler object.
   *
   * @returns {Record<string, ?>} json
   *
   * @see https://w3c.github.io/webdriver/#dfn-serialize-the-user-prompt-handler
   */
  toJSON() {
    if (this.#promptTypeToHandlerMap === null) {
      // Fallback to "dismiss and notify" if no handler is set
      return PromptHandlers.DismissAndNotify;
    }

    if (
      this.#promptTypeToHandlerMap.size === 1 &&
      this.#promptTypeToHandlerMap.has(FALLBACK_DEFAULT_PROMPT_TYPE)
    ) {
      return this.#promptTypeToHandlerMap
        .get(FALLBACK_DEFAULT_PROMPT_TYPE)
        .toJSON();
    }

    const serialized = {};
    for (const [key, value] of this.#promptTypeToHandlerMap.entries()) {
      serialized[key] = value.toJSON();
    }

    return serialized;
  }

  toString() {
    return "[object UserPromptHandler]";
  }
}
PK
!<���?�	�	Wchrome/remote/content/shared/webdriver/process-actors/WebDriverProcessDataChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  NodeCache: "chrome://remote/content/shared/webdriver/NodeCache.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

// Observer to clean-up element references for closed browsing contexts.
class BrowsingContextObserver {
  constructor(actor) {
    this.actor = actor;
  }

  async observe(subject, topic) {
    if (topic === "browsing-context-discarded") {
      this.actor.cleanUp({ browsingContext: subject });
    }
  }
}

export class WebDriverProcessDataChild extends JSProcessActorChild {
  #browsingContextObserver;
  #nodeCache;

  constructor() {
    super();

    // For now have a single reference store only. Once multiple WebDriver
    // sessions are supported, it needs to be hashed by the session id.
    this.#nodeCache = new lazy.NodeCache();

    // Register observer to cleanup element references when a browsing context
    // gets destroyed.
    this.#browsingContextObserver = new BrowsingContextObserver(this);
    Services.obs.addObserver(
      this.#browsingContextObserver,
      "browsing-context-discarded"
    );
  }

  actorCreated() {
    lazy.logger.trace(
      `WebDriverProcessData actor created for PID ${Services.appinfo.processID}`
    );
  }

  didDestroy() {
    Services.obs.removeObserver(
      this.#browsingContextObserver,
      "browsing-context-discarded"
    );
  }

  /**
   * Clean up all the process specific data.
   *
   * @param {object=} options
   * @param {BrowsingContext=} options.browsingContext
   *     If specified only clear data living in that browsing context.
   */
  cleanUp(options = {}) {
    const { browsingContext = null } = options;

    this.#nodeCache.clear({ browsingContext });
  }

  /**
   * Get the node cache.
   *
   * @returns {NodeCache}
   *     The cache containing DOM node references.
   */
  getNodeCache() {
    return this.#nodeCache;
  }

  async receiveMessage(msg) {
    switch (msg.name) {
      case "WebDriverProcessDataParent:CleanUp":
        return this.cleanUp(msg.data);
      default:
        return Promise.reject(
          new Error(`Unexpected message received: ${msg.name}`)
        );
    }
  }
}
PK
!<�Ƚ�==Xchrome/remote/content/shared/webdriver/process-actors/WebDriverProcessDataParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "chrome://remote/content/shared/Log.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());

/**
 * Register the WebDriverProcessData actor that holds session data.
 */
export function registerProcessDataActor() {
  try {
    ChromeUtils.registerProcessActor("WebDriverProcessData", {
      kind: "JSProcessActor",
      child: {
        esModuleURI:
          "chrome://remote/content/shared/webdriver/process-actors/WebDriverProcessDataChild.sys.mjs",
      },
      includeParent: true,
    });
  } catch (e) {
    if (e.name === "NotSupportedError") {
      lazy.logger.warn(`WebDriverProcessData actor is already registered!`);
    } else {
      throw e;
    }
  }
}

export function unregisterProcessDataActor() {
  ChromeUtils.unregisterProcessActor("WebDriverProcessData");
}
PK
!<����||>chrome/remote/content/webdriver-bidi/NewSessionHandler.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  WebDriverBiDiConnection:
    "chrome://remote/content/webdriver-bidi/WebDriverBiDiConnection.sys.mjs",
  WebSocketHandshake:
    "chrome://remote/content/server/WebSocketHandshake.sys.mjs",
});

/**
 * httpd.js JSON handler for direct BiDi connections.
 */
export class WebDriverNewSessionHandler {
  /**
   * Construct a new JSON handler.
   *
   * @param {WebDriverBiDi} webDriverBiDi
   *     Reference to the WebDriver BiDi protocol implementation.
   */
  constructor(webDriverBiDi) {
    this.webDriverBiDi = webDriverBiDi;
  }

  // nsIHttpRequestHandler

  /**
   * Handle new direct WebSocket connection requests.
   *
   * WebSocket clients not using the WebDriver BiDi opt-in mechanism via the
   * WebDriver HTTP implementation will attempt to directly connect at
   * `/session`.  Hereby a WebSocket upgrade will automatically be performed.
   *
   * @param {Request} request
   *     HTTP request (httpd.js)
   * @param {Response} response
   *     Response to an HTTP request (httpd.js)
   */
  async handle(request, response) {
    const webSocket = await lazy.WebSocketHandshake.upgrade(request, response);
    const conn = new lazy.WebDriverBiDiConnection(
      webSocket,
      response._connection
    );

    this.webDriverBiDi.addSessionlessConnection(conn);
  }

  // XPCOM

  get QueryInterface() {
    return ChromeUtils.generateQI(["nsIHttpRequestHandler"]);
  }
}
PK
!<���ֱx�x8chrome/remote/content/webdriver-bidi/RemoteValue.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  dom: "chrome://remote/content/shared/DOM.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.WEBDRIVER_BIDI)
);

/**
 * @typedef {object} IncludeShadowTreeMode
 */

/**
 * Enum of include shadow tree modes supported by the serialization.
 *
 * @readonly
 * @enum {IncludeShadowTreeMode}
 */
export const IncludeShadowTreeMode = {
  All: "all",
  None: "none",
  Open: "open",
};

/**
 * @typedef {object} OwnershipModel
 */

/**
 * Enum of ownership models supported by the serialization.
 *
 * @readonly
 * @enum {OwnershipModel}
 */
export const OwnershipModel = {
  None: "none",
  Root: "root",
};

/**
 * Extra options for deserializing remote values.
 *
 * @typedef {object} ExtraDeserializationOptions
 *
 * @property {NodeCache=} nodeCache
 *     The cache containing DOM node references.
 * @property {Function=} emitScriptMessage
 *     The function to emit "script.message" event.
 */

/**
 * Extra options for serializing remote values.
 *
 * @typedef {object} ExtraSerializationOptions
 *
 * @property {NodeCache=} nodeCache
 *     The cache containing DOM node references.
 * @property {Map<BrowsingContext, Array<string>>} seenNodeIds
 *     Map of browsing contexts to their seen node ids during the current
 *     serialization.
 */

/**
 * An object which holds the information of how
 * ECMAScript objects should be serialized.
 *
 * @typedef {object} SerializationOptions
 *
 * @property {number} [maxDomDepth=0]
 *     Depth of a serialization of DOM Nodes. Defaults to 0.
 * @property {number} [maxObjectDepth=null]
 *     Depth of a serialization of objects. Defaults to null.
 * @property {IncludeShadowTreeMode} [includeShadowTree=IncludeShadowTreeMode.None]
 *     Mode of a serialization of shadow dom. Defaults to "none".
 */

const TYPED_ARRAY_CLASSES = [
  "Uint8Array",
  "Uint8ClampedArray",
  "Uint16Array",
  "Uint32Array",
  "Int8Array",
  "Int16Array",
  "Int32Array",
  "Float32Array",
  "Float64Array",
  "BigInt64Array",
  "BigUint64Array",
];

/**
 * Build the serialized RemoteValue.
 *
 * @returns {object}
 *     An object with a mandatory `type` property, and optional `handle`,
 *     depending on the OwnershipModel, used for the serialization and
 *     on the value's type.
 */
function buildSerialized(type, handle = null) {
  const serialized = { type };

  if (handle !== null) {
    serialized.handle = handle;
  }

  return serialized;
}

/**
 * Helper to deserialize value list.
 *
 * @see https://w3c.github.io/webdriver-bidi/#deserialize-value-list
 *
 * @param {Array} serializedValueList
 *     List of serialized values.
 * @param {Realm} realm
 *     The Realm in which the value is deserialized.
 * @param {ExtraDeserializationOptions} extraOptions
 *     Extra Remote Value deserialization options.
 *
 * @returns {Array} List of deserialized values.
 *
 * @throws {InvalidArgumentError}
 *     If <var>serializedValueList</var> is not an array.
 */
function deserializeValueList(serializedValueList, realm, extraOptions) {
  lazy.assert.array(
    serializedValueList,
    lazy.pprint`Expected "serializedValueList" to be an array, got ${serializedValueList}`
  );

  const deserializedValues = [];

  for (const item of serializedValueList) {
    deserializedValues.push(deserialize(item, realm, extraOptions));
  }

  return deserializedValues;
}

/**
 * Helper to deserialize key-value list.
 *
 * @see https://w3c.github.io/webdriver-bidi/#deserialize-key-value-list
 *
 * @param {Array} serializedKeyValueList
 *     List of serialized key-value.
 * @param {Realm} realm
 *     The Realm in which the value is deserialized.
 * @param {ExtraDeserializationOptions} extraOptions
 *     Extra Remote Value deserialization options.
 *
 * @returns {Array} List of deserialized key-value.
 *
 * @throws {InvalidArgumentError}
 *     If <var>serializedKeyValueList</var> is not an array or
 *     not an array of key-value arrays.
 */
function deserializeKeyValueList(serializedKeyValueList, realm, extraOptions) {
  lazy.assert.array(
    serializedKeyValueList,
    lazy.pprint`Expected "serializedKeyValueList" to be an array, got ${serializedKeyValueList}`
  );

  const deserializedKeyValueList = [];

  for (const serializedKeyValue of serializedKeyValueList) {
    if (!Array.isArray(serializedKeyValue) || serializedKeyValue.length != 2) {
      throw new lazy.error.InvalidArgumentError(
        `Expected key-value pair to be an array with 2 elements, got ${serializedKeyValue}`
      );
    }
    const [serializedKey, serializedValue] = serializedKeyValue;
    const deserializedKey =
      typeof serializedKey == "string"
        ? serializedKey
        : deserialize(serializedKey, realm, extraOptions);
    const deserializedValue = deserialize(serializedValue, realm, extraOptions);

    deserializedKeyValueList.push([deserializedKey, deserializedValue]);
  }

  return deserializedKeyValueList;
}

/**
 * Deserialize a Node as referenced by the shared unique reference.
 *
 * This unique reference can be shared by WebDriver clients with the WebDriver
 * classic implementation (Marionette) if the reference is for an Element or
 * ShadowRoot.
 *
 * @param {string} sharedRef
 *     Shared unique reference of the Node.
 * @param {Realm} realm
 *     The Realm in which the value is deserialized.
 * @param {ExtraDeserializationOptions} extraOptions
 *     Extra Remote Value deserialization options.
 *
 * @returns {Node} The deserialized DOM node.
 */
function deserializeSharedReference(sharedRef, realm, extraOptions) {
  const { nodeCache } = extraOptions;

  const browsingContext = realm.browsingContext;
  if (!browsingContext) {
    throw new lazy.error.NoSuchNodeError("Realm isn't a Window global");
  }

  const node = nodeCache.getNode(browsingContext, sharedRef);

  if (node === null) {
    throw new lazy.error.NoSuchNodeError(
      `The node with the reference ${sharedRef} is not known`
    );
  }

  // Bug 1819902: Instead of a browsing context check compare the origin
  const isSameBrowsingContext = sharedRef => {
    const nodeDetails = nodeCache.getReferenceDetails(sharedRef);

    if (nodeDetails.isTopBrowsingContext && browsingContext.parent === null) {
      // As long as Navigables are not available any cross-group navigation will
      // cause a swap of the current top-level browsing context. The only unique
      // identifier in such a case is the browser id the top-level browsing
      // context actually lives in.
      return nodeDetails.browserId === browsingContext.browserId;
    }

    return nodeDetails.browsingContextId === browsingContext.id;
  };

  if (!isSameBrowsingContext(sharedRef)) {
    return null;
  }

  return node;
}

/**
 * Deserialize a local value.
 *
 * @see https://w3c.github.io/webdriver-bidi/#deserialize-local-value
 *
 * @param {object} serializedValue
 *     Value of any type to be deserialized.
 * @param {Realm} realm
 *     The Realm in which the value is deserialized.
 * @param {ExtraDeserializationOptions} extraOptions
 *     Extra Remote Value deserialization options.
 *
 * @returns {object} Deserialized representation of the value.
 */
export function deserialize(serializedValue, realm, extraOptions) {
  const { handle, sharedId, type, value } = serializedValue;

  // With a shared id present deserialize as node reference.
  if (sharedId !== undefined) {
    lazy.assert.string(
      sharedId,
      lazy.pprint`Expected "sharedId" to be a string, got ${sharedId}`
    );

    return deserializeSharedReference(sharedId, realm, extraOptions);
  }

  // With a handle present deserialize as remote reference.
  if (handle !== undefined) {
    lazy.assert.string(
      handle,
      lazy.pprint`Expected "handle" to be a string, got ${handle}`
    );

    const object = realm.getObjectForHandle(handle);
    if (!object) {
      throw new lazy.error.NoSuchHandleError(
        `Unable to find an object reference for "handle" ${handle}`
      );
    }

    return object;
  }

  lazy.assert.string(
    type,
    lazy.pprint`Expected "type" to be a string, got ${type}`
  );

  // Primitive protocol values
  switch (type) {
    case "undefined":
      return undefined;
    case "null":
      return null;
    case "string":
      lazy.assert.string(
        value,
        lazy.pprint`Expected "value" to be a string, got ${value}`
      );
      return value;
    case "number":
      // If value is already a number return its value.
      if (typeof value === "number") {
        return value;
      }

      // Otherwise it has to be one of the special strings
      lazy.assert.in(
        value,
        ["NaN", "-0", "Infinity", "-Infinity"],
        lazy.pprint`Expected "value" to be one of "NaN", "-0", "Infinity", "-Infinity", got ${value}`
      );
      return Number(value);
    case "boolean":
      lazy.assert.boolean(
        value,
        lazy.pprint`Expected "value" to be a boolean, got ${value}`
      );
      return value;
    case "bigint":
      lazy.assert.string(
        value,
        lazy.pprint`Expected "value" to be a string, got ${value}`
      );
      try {
        return BigInt(value);
      } catch (e) {
        throw new lazy.error.InvalidArgumentError(
          `Failed to deserialize value as BigInt: ${value}`
        );
      }

    // Script channel
    case "channel": {
      const channel = message =>
        extraOptions.emitScriptMessage(realm, value, message);
      return realm.cloneIntoRealm(channel);
    }

    // Non-primitive protocol values
    case "array":
      const array = realm.cloneIntoRealm([]);
      deserializeValueList(value, realm, extraOptions).forEach(v =>
        array.push(v)
      );
      return array;
    case "date":
      // We want to support only Date Time String format,
      // check if the value follows it.
      if (!ChromeUtils.isISOStyleDate(value)) {
        throw new lazy.error.InvalidArgumentError(
          `Expected "value" for Date to be a Date Time string, got ${value}`
        );
      }

      return realm.cloneIntoRealm(new Date(value));
    case "map":
      const map = realm.cloneIntoRealm(new Map());
      deserializeKeyValueList(value, realm, extraOptions).forEach(([k, v]) =>
        map.set(k, v)
      );

      return map;
    case "object":
      const object = realm.cloneIntoRealm({});
      deserializeKeyValueList(value, realm, extraOptions).forEach(
        ([k, v]) => (object[k] = v)
      );
      return object;
    case "regexp":
      lazy.assert.object(
        value,
        lazy.pprint`Expected "value" for RegExp to be an object, got ${value}`
      );
      const { pattern, flags } = value;
      lazy.assert.string(
        pattern,
        lazy.pprint`Expected "pattern" for RegExp to be a string, got ${pattern}`
      );
      if (flags !== undefined) {
        lazy.assert.string(
          flags,
          lazy.pprint`Expected "flags" for RegExp to be a string, got ${flags}`
        );
      }
      try {
        return realm.cloneIntoRealm(new RegExp(pattern, flags));
      } catch (e) {
        throw new lazy.error.InvalidArgumentError(
          `Failed to deserialize value as RegExp: ${value}`
        );
      }
    case "set":
      const set = realm.cloneIntoRealm(new Set());
      deserializeValueList(value, realm, extraOptions).forEach(v => set.add(v));
      return set;
  }

  lazy.logger.warn(`Unsupported type for local value ${type}`);
  return undefined;
}

/**
 * Helper to retrieve the handle id for a given object, for the provided realm
 * and ownership type.
 *
 * See https://w3c.github.io/webdriver-bidi/#handle-for-an-object
 *
 * @param {Realm} realm
 *     The Realm from which comes the value being serialized.
 * @param {OwnershipModel} ownershipType
 *     The ownership model to use for this serialization.
 * @param {object} object
 *     The object being serialized.
 *
 * @returns {string} The unique handle id for the object. Will be null if the
 *     Ownership type is "none".
 */
function getHandleForObject(realm, ownershipType, object) {
  if (ownershipType === OwnershipModel.None) {
    return null;
  }
  return realm.getHandleForObject(object);
}

/**
 * Gets or creates a new shared unique reference for the DOM node.
 *
 * This unique reference can be shared by WebDriver clients with the WebDriver
 * classic implementation (Marionette) if the reference is for an Element or
 * ShadowRoot.
 *
 * @param {Node} node
 *    Node to create the unique reference for.
 * @param {ExtraSerializationOptions} extraOptions
 *     Extra Remote Value serialization options.
 *
 * @returns {string}
 *    Shared unique reference for the Node.
 */
function getSharedIdForNode(node, extraOptions) {
  const { nodeCache, seenNodeIds } = extraOptions;

  if (!Node.isInstance(node)) {
    return null;
  }

  const browsingContext = node.ownerGlobal.browsingContext;
  if (!browsingContext) {
    return null;
  }

  return nodeCache.getOrCreateNodeReference(node, seenNodeIds);
}

/**
 * Helper to serialize an Array-like object.
 *
 * @see https://w3c.github.io/webdriver-bidi/#serialize-an-array-like
 *
 * @param {string} production
 *     Type of object
 * @param {string} handleId
 *     The unique id of the <var>value</var>.
 * @param {boolean} knownObject
 *     Indicates if the <var>value</var> has already been serialized.
 * @param {object} value
 *     The Array-like object to serialize.
 * @param {SerializationOptions} serializationOptions
 *     Options which define how ECMAScript objects should be serialized.
 * @param {OwnershipModel} ownershipType
 *     The ownership model to use for this serialization.
 * @param {Map} serializationInternalMap
 *     Map of internal ids.
 * @param {Realm} realm
 *     The Realm from which comes the value being serialized.
 * @param {ExtraSerializationOptions} extraOptions
 *     Extra Remote Value serialization options.
 *
 * @returns {object} Object for serialized values.
 */
function serializeArrayLike(
  production,
  handleId,
  knownObject,
  value,
  serializationOptions,
  ownershipType,
  serializationInternalMap,
  realm,
  extraOptions
) {
  const serialized = buildSerialized(production, handleId);
  setInternalIdsIfNeeded(serializationInternalMap, serialized, value);

  if (!knownObject && serializationOptions.maxObjectDepth !== 0) {
    serialized.value = serializeList(
      value,
      serializationOptions,
      ownershipType,
      serializationInternalMap,
      realm,
      extraOptions
    );
  }

  return serialized;
}

/**
 * Helper to serialize as a list.
 *
 * @see https://w3c.github.io/webdriver-bidi/#serialize-as-a-list
 *
 * @param {Iterable} iterable
 *     List of values to be serialized.
 * @param {SerializationOptions} serializationOptions
 *     Options which define how ECMAScript objects should be serialized.
 * @param {OwnershipModel} ownershipType
 *     The ownership model to use for this serialization.
 * @param {Map} serializationInternalMap
 *     Map of internal ids.
 * @param {Realm} realm
 *     The Realm from which comes the value being serialized.
 * @param {ExtraSerializationOptions} extraOptions
 *     Extra Remote Value serialization options.
 *
 * @returns {Array} List of serialized values.
 */
function serializeList(
  iterable,
  serializationOptions,
  ownershipType,
  serializationInternalMap,
  realm,
  extraOptions
) {
  const { maxObjectDepth } = serializationOptions;
  const serialized = [];
  const childSerializationOptions = {
    ...serializationOptions,
  };
  if (maxObjectDepth !== null) {
    childSerializationOptions.maxObjectDepth = maxObjectDepth - 1;
  }

  for (const item of iterable) {
    serialized.push(
      serialize(
        item,
        childSerializationOptions,
        ownershipType,
        serializationInternalMap,
        realm,
        extraOptions
      )
    );
  }

  return serialized;
}

/**
 * Helper to serialize as a mapping.
 *
 * @see https://w3c.github.io/webdriver-bidi/#serialize-as-a-mapping
 *
 * @param {Iterable} iterable
 *     List of values to be serialized.
 * @param {SerializationOptions} serializationOptions
 *     Options which define how ECMAScript objects should be serialized.
 * @param {OwnershipModel} ownershipType
 *     The ownership model to use for this serialization.
 * @param {Map} serializationInternalMap
 *     Map of internal ids.
 * @param {Realm} realm
 *     The Realm from which comes the value being serialized.
 * @param {ExtraSerializationOptions} extraOptions
 *     Extra Remote Value serialization options.
 *
 * @returns {Array} List of serialized values.
 */
function serializeMapping(
  iterable,
  serializationOptions,
  ownershipType,
  serializationInternalMap,
  realm,
  extraOptions
) {
  const { maxObjectDepth } = serializationOptions;
  const serialized = [];
  const childSerializationOptions = {
    ...serializationOptions,
  };
  if (maxObjectDepth !== null) {
    childSerializationOptions.maxObjectDepth = maxObjectDepth - 1;
  }

  for (const [key, item] of iterable) {
    const serializedKey =
      typeof key == "string"
        ? key
        : serialize(
            key,
            childSerializationOptions,
            ownershipType,
            serializationInternalMap,
            realm,
            extraOptions
          );
    const serializedValue = serialize(
      item,
      childSerializationOptions,
      ownershipType,
      serializationInternalMap,
      realm,
      extraOptions
    );

    serialized.push([serializedKey, serializedValue]);
  }

  return serialized;
}

/**
 * Helper to serialize as a Node.
 *
 * @param {Node} node
 *     Node to be serialized.
 * @param {SerializationOptions} serializationOptions
 *     Options which define how ECMAScript objects should be serialized.
 * @param {OwnershipModel} ownershipType
 *     The ownership model to use for this serialization.
 * @param {Map} serializationInternalMap
 *     Map of internal ids.
 * @param {Realm} realm
 *     The Realm from which comes the value being serialized.
 * @param {ExtraSerializationOptions} extraOptions
 *     Extra Remote Value serialization options.
 *
 * @returns {object} Serialized value.
 */
function serializeNode(
  node,
  serializationOptions,
  ownershipType,
  serializationInternalMap,
  realm,
  extraOptions
) {
  const { includeShadowTree, maxDomDepth } = serializationOptions;
  const isAttribute = Attr.isInstance(node);
  const isElement = Element.isInstance(node);

  const serialized = {
    nodeType: node.nodeType,
  };

  if (node.nodeValue !== null) {
    serialized.nodeValue = node.nodeValue;
  }

  if (isElement || isAttribute) {
    serialized.localName = node.localName;
    serialized.namespaceURI = node.namespaceURI;
  }

  serialized.childNodeCount = node.childNodes.length;
  if (
    maxDomDepth !== 0 &&
    (!lazy.dom.isShadowRoot(node) ||
      (includeShadowTree === IncludeShadowTreeMode.Open &&
        node.mode === "open") ||
      includeShadowTree === IncludeShadowTreeMode.All)
  ) {
    const children = [];
    const childSerializationOptions = {
      ...serializationOptions,
    };
    if (maxDomDepth !== null) {
      childSerializationOptions.maxDomDepth = maxDomDepth - 1;
    }
    for (const child of node.childNodes) {
      children.push(
        serialize(
          child,
          childSerializationOptions,
          ownershipType,
          serializationInternalMap,
          realm,
          extraOptions
        )
      );
    }

    serialized.children = children;
  }

  if (isElement) {
    serialized.attributes = [...node.attributes].reduce((map, attr) => {
      map[attr.name] = attr.value;
      return map;
    }, {});

    const shadowRoot = node.openOrClosedShadowRoot;
    serialized.shadowRoot = null;
    if (shadowRoot !== null) {
      serialized.shadowRoot = serialize(
        shadowRoot,
        serializationOptions,
        ownershipType,
        serializationInternalMap,
        realm,
        extraOptions
      );
    }
  }

  if (lazy.dom.isShadowRoot(node)) {
    serialized.mode = node.mode;
  }

  return serialized;
}

/**
 * Serialize a value as a remote value.
 *
 * @see https://w3c.github.io/webdriver-bidi/#serialize-as-a-remote-value
 *
 * @param {object} value
 *     Value of any type to be serialized.
 * @param {SerializationOptions} serializationOptions
 *     Options which define how ECMAScript objects should be serialized.
 * @param {OwnershipModel} ownershipType
 *     The ownership model to use for this serialization.
 * @param {Map} serializationInternalMap
 *     Map of internal ids.
 * @param {Realm} realm
 *     The Realm from which comes the value being serialized.
 * @param {ExtraSerializationOptions} extraOptions
 *     Extra Remote Value serialization options.
 *
 * @returns {object} Serialized representation of the value.
 */
export function serialize(
  value,
  serializationOptions,
  ownershipType,
  serializationInternalMap,
  realm,
  extraOptions
) {
  const { maxObjectDepth } = serializationOptions;
  const type = typeof value;

  // Primitive protocol values
  if (type == "undefined") {
    return { type };
  } else if (Object.is(value, null)) {
    return { type: "null" };
  } else if (Object.is(value, NaN)) {
    return { type: "number", value: "NaN" };
  } else if (Object.is(value, -0)) {
    return { type: "number", value: "-0" };
  } else if (Object.is(value, Infinity)) {
    return { type: "number", value: "Infinity" };
  } else if (Object.is(value, -Infinity)) {
    return { type: "number", value: "-Infinity" };
  } else if (type == "bigint") {
    return { type, value: value.toString() };
  } else if (["boolean", "number", "string"].includes(type)) {
    return { type, value };
  }

  const handleId = getHandleForObject(realm, ownershipType, value);
  const knownObject = serializationInternalMap.has(value);

  // Set the OwnershipModel to use for all complex object serializations.
  ownershipType = OwnershipModel.None;

  // Remote values

  // symbols are primitive JS values which can only be serialized
  // as remote values.
  if (type == "symbol") {
    return buildSerialized("symbol", handleId);
  }

  // All other remote values are non-primitives and their
  // className can be extracted with ChromeUtils.getClassName
  const className = ChromeUtils.getClassName(value);
  if (["Array", "HTMLCollection", "NodeList"].includes(className)) {
    return serializeArrayLike(
      className.toLowerCase(),
      handleId,
      knownObject,
      value,
      serializationOptions,
      ownershipType,
      serializationInternalMap,
      realm,
      extraOptions
    );
  } else if (className == "RegExp") {
    const serialized = buildSerialized("regexp", handleId);
    serialized.value = { pattern: value.source, flags: value.flags };
    return serialized;
  } else if (className == "Date") {
    const serialized = buildSerialized("date", handleId);
    serialized.value = value.toISOString();
    return serialized;
  } else if (className == "Map") {
    const serialized = buildSerialized("map", handleId);
    setInternalIdsIfNeeded(serializationInternalMap, serialized, value);

    if (!knownObject && maxObjectDepth !== 0) {
      serialized.value = serializeMapping(
        value.entries(),
        serializationOptions,
        ownershipType,
        serializationInternalMap,
        realm,
        extraOptions
      );
    }
    return serialized;
  } else if (className == "Set") {
    const serialized = buildSerialized("set", handleId);
    setInternalIdsIfNeeded(serializationInternalMap, serialized, value);

    if (!knownObject && maxObjectDepth !== 0) {
      serialized.value = serializeList(
        value.values(),
        serializationOptions,
        ownershipType,
        serializationInternalMap,
        realm,
        extraOptions
      );
    }
    return serialized;
  } else if (
    ["ArrayBuffer", "Function", "Promise", "WeakMap", "WeakSet"].includes(
      className
    )
  ) {
    return buildSerialized(className.toLowerCase(), handleId);
  } else if (className.includes("Generator")) {
    return buildSerialized("generator", handleId);
  } else if (lazy.error.isError(value)) {
    return buildSerialized("error", handleId);
  } else if (Cu.isProxy(value)) {
    return buildSerialized("proxy", handleId);
  } else if (TYPED_ARRAY_CLASSES.includes(className)) {
    return buildSerialized("typedarray", handleId);
  } else if (Node.isInstance(value)) {
    const serialized = buildSerialized("node", handleId);

    value = Cu.unwaiveXrays(value);

    // Get or create the shared id for WebDriver classic compat from the node.
    const sharedId = getSharedIdForNode(value, extraOptions);
    if (sharedId !== null) {
      serialized.sharedId = sharedId;
    }

    setInternalIdsIfNeeded(serializationInternalMap, serialized, value);

    if (!knownObject) {
      serialized.value = serializeNode(
        value,
        serializationOptions,
        ownershipType,
        serializationInternalMap,
        realm,
        extraOptions
      );
    }

    return serialized;
  } else if (Window.isInstance(value)) {
    const serialized = buildSerialized("window", handleId);
    const window = Cu.unwaiveXrays(value);

    if (window.browsingContext.parent == null) {
      serialized.value = {
        context: window.browsingContext.browserId.toString(),
        isTopBrowsingContext: true,
      };
    } else {
      serialized.value = {
        context: window.browsingContext.id.toString(),
      };
    }

    return serialized;
  } else if (ChromeUtils.isDOMObject(value)) {
    const serialized = buildSerialized("object", handleId);
    return serialized;
  }

  // Otherwise serialize the JavaScript object as generic object.
  const serialized = buildSerialized("object", handleId);
  setInternalIdsIfNeeded(serializationInternalMap, serialized, value);

  if (!knownObject && maxObjectDepth !== 0) {
    serialized.value = serializeMapping(
      Object.entries(value),
      serializationOptions,
      ownershipType,
      serializationInternalMap,
      realm,
      extraOptions
    );
  }
  return serialized;
}

/**
 * Set default serialization options.
 *
 * @param {SerializationOptions} options
 *    Options which define how ECMAScript objects should be serialized.
 * @returns {SerializationOptions}
 *    Serialiation options with default value added.
 */
export function setDefaultSerializationOptions(options = {}) {
  const serializationOptions = { ...options };
  if (!("maxDomDepth" in serializationOptions)) {
    serializationOptions.maxDomDepth = 0;
  }
  if (!("maxObjectDepth" in serializationOptions)) {
    serializationOptions.maxObjectDepth = null;
  }
  if (!("includeShadowTree" in serializationOptions)) {
    serializationOptions.includeShadowTree = IncludeShadowTreeMode.None;
  }

  return serializationOptions;
}

/**
 * Set default values and assert if serialization options have
 * expected types.
 *
 * @param {SerializationOptions} options
 *    Options which define how ECMAScript objects should be serialized.
 * @returns {SerializationOptions}
 *    Serialiation options with default value added.
 */
export function setDefaultAndAssertSerializationOptions(options = {}) {
  lazy.assert.object(
    options,
    lazy.pprint`Expected "options" to be an object, got ${options}`
  );

  const serializationOptions = setDefaultSerializationOptions(options);

  const { includeShadowTree, maxDomDepth, maxObjectDepth } =
    serializationOptions;

  if (maxDomDepth !== null) {
    lazy.assert.positiveInteger(
      maxDomDepth,
      lazy.pprint`Expected "maxDomDepth" to be a positive integer or null, got ${maxDomDepth}`
    );
  }
  if (maxObjectDepth !== null) {
    lazy.assert.positiveInteger(
      maxObjectDepth,
      lazy.pprint`Expected "maxObjectDepth" to be a positive integer or null, got ${maxObjectDepth}`
    );
  }
  const includeShadowTreeModesValues = Object.values(IncludeShadowTreeMode);
  lazy.assert.that(
    includeShadowTree =>
      includeShadowTreeModesValues.includes(includeShadowTree),
    `Expected "includeShadowTree" to be one of ${includeShadowTreeModesValues}, ` +
      lazy.pprint`got ${includeShadowTree}`
  )(includeShadowTree);

  return serializationOptions;
}

/**
 * Set the internalId property of a provided serialized RemoteValue,
 * and potentially of a previously created serialized RemoteValue,
 * corresponding to the same provided object.
 *
 * @see https://w3c.github.io/webdriver-bidi/#set-internal-ids-if-needed
 *
 * @param {Map} serializationInternalMap
 *     Map of objects to remote values.
 * @param {object} remoteValue
 *     A serialized RemoteValue for the provided object.
 * @param {object} object
 *     Object of any type to be serialized.
 */
function setInternalIdsIfNeeded(serializationInternalMap, remoteValue, object) {
  if (!serializationInternalMap.has(object)) {
    // If the object was not tracked yet in the current serialization, add
    // a new entry in the serialization internal map. An internal id will only
    // be generated if the same object is encountered again.
    serializationInternalMap.set(object, remoteValue);
  } else {
    // This is at least the second time this object is encountered, retrieve the
    // original remote value stored for this object.
    const previousRemoteValue = serializationInternalMap.get(object);

    if (!previousRemoteValue.internalId) {
      // If the original remote value has no internal id yet, generate a uuid
      // and update the internalId of the original remote value with it.
      previousRemoteValue.internalId = lazy.generateUUID();
    }

    // Copy the internalId of the original remote value to the new remote value.
    remoteValue.internalId = previousRemoteValue.internalId;
  }
}

/**
 * Safely stringify a value.
 *
 * @param {object} obj
 *     Value of any type to be stringified.
 *
 * @returns {string} String representation of the value.
 */
export function stringify(obj) {
  let text;
  try {
    text =
      obj !== null && typeof obj === "object" ? obj.toString() : String(obj);
  } catch (e) {
    // The error-case will also be handled in `finally {}`.
  } finally {
    if (typeof text != "string") {
      text = Object.prototype.toString.apply(obj);
    }
  }

  return text;
}
PK
!<�LkN��:chrome/remote/content/webdriver-bidi/WebDriverBiDi.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  cleanupCacheBypassState:
    "chrome://remote/content/shared/NetworkCacheManager.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  RecommendedPreferences:
    "chrome://remote/content/shared/RecommendedPreferences.sys.mjs",
  WebDriverNewSessionHandler:
    "chrome://remote/content/webdriver-bidi/NewSessionHandler.sys.mjs",
  WebDriverSession: "chrome://remote/content/shared/webdriver/Session.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.WEBDRIVER_BIDI)
);
ChromeUtils.defineLazyGetter(lazy, "textEncoder", () => new TextEncoder());

const RECOMMENDED_PREFS = new Map([
  // Enables permission isolation by user context.
  // It should be enabled by default in Nightly in the scope of the bug 1641584.
  ["permissions.isolateBy.userContext", true],
]);

/**
 * Entry class for the WebDriver BiDi support.
 *
 * @see https://w3c.github.io/webdriver-bidi
 */
export class WebDriverBiDi {
  #agent;
  #bidiServerPath;
  #running;
  #session;
  #sessionlessConnections;

  /**
   * Creates a new instance of the WebDriverBiDi class.
   *
   * @param {RemoteAgent} agent
   *     Reference to the Remote Agent instance.
   */
  constructor(agent) {
    this.#agent = agent;
    this.#running = false;

    this.#bidiServerPath;
    this.#session = null;
    this.#sessionlessConnections = new Set();
  }

  get address() {
    return `ws://${this.#agent.host}:${this.#agent.port}`;
  }

  get session() {
    return this.#session;
  }

  #newSessionAlgorithm(session, flags) {
    if (!this.#agent.running) {
      // With the Remote Agent not running WebDriver BiDi is not supported.
      return;
    }

    if (flags.has(lazy.WebDriverSession.SESSION_FLAG_BIDI)) {
      // It's already a WebDriver BiDi session.
      return;
    }

    const webSocketUrl = session.capabilities.get("webSocketUrl");
    if (webSocketUrl === undefined) {
      return;
    }

    // Start listening for BiDi connections.
    this.#agent.server.registerPathHandler(session.path, session);
    lazy.logger.debug(`Registered session handler: ${session.path}`);

    session.capabilities.set("webSocketUrl", `${this.address}${session.path}`);

    session.bidi = true;
    flags.add("bidi");
  }

  /**
   * Add a new connection that is not yet attached to a WebDriver session.
   *
   * @param {WebDriverBiDiConnection} connection
   *     The connection without an associated WebDriver session.
   */
  addSessionlessConnection(connection) {
    this.#sessionlessConnections.add(connection);
  }

  /**
   * Create a new WebDriver session.
   *
   * @param {Record<string, *>=} capabilities
   *     JSON Object containing any of the recognised capabilities as listed
   *     on the `WebDriverSession` class.
   * @param {Set} flags
   *     Session configuration flags.
   * @param {WebDriverBiDiConnection=} sessionlessConnection
   *     Optional connection that is not yet associated with a WebDriver
   *     session, and has to be associated with the new WebDriver session.
   *
   * @returns {Record<string, Capabilities>}
   *     Object containing the current session ID, and all its capabilities.
   *
   * @throws {SessionNotCreatedError}
   *     If, for whatever reason, a session could not be created.
   */
  async createSession(capabilities, flags, sessionlessConnection) {
    if (this.#session) {
      throw new lazy.error.SessionNotCreatedError(
        "Maximum number of active sessions"
      );
    }

    this.#session = new lazy.WebDriverSession(
      capabilities,
      flags,
      sessionlessConnection
    );

    // Run new session steps for WebDriver BiDi.
    this.#newSessionAlgorithm(this.#session, flags);

    if (sessionlessConnection) {
      // Connection is now registered with a WebDriver session
      this.#sessionlessConnections.delete(sessionlessConnection);
    }

    if (this.#session.bidi) {
      // Creating a WebDriver BiDi session too early can cause issues with
      // clients in not being able to find any available browsing context.
      // Also when closing the application while it's still starting up can
      // cause shutdown hangs. As such WebDriver BiDi will return a new session
      // once the initial application window has finished initializing.
      lazy.logger.debug(`Waiting for initial application window`);
      await this.#agent.browserStartupFinished;
    }

    return {
      sessionId: this.#session.id,
      capabilities: this.#session.capabilities,
    };
  }

  /**
   * Delete the current WebDriver session.
   */
  deleteSession() {
    if (!this.#session) {
      return;
    }

    // When the Remote Agent is listening, and a BiDi WebSocket is active,
    // unregister the path handler for the session.
    if (this.#agent.running && this.#session.capabilities.get("webSocketUrl")) {
      this.#agent.server.registerPathHandler(this.#session.path, null);
      lazy.logger.debug(`Unregistered session handler: ${this.#session.path}`);
    }

    // For multiple session check first if the last session was closed.
    lazy.cleanupCacheBypassState();

    this.#session.destroy();
    this.#session = null;
  }

  /**
   * Retrieve the readiness state of the remote end, regarding the creation of
   * new WebDriverBiDi sessions.
   *
   * See https://w3c.github.io/webdriver-bidi/#command-session-status
   *
   * @returns {object}
   *     The readiness state.
   */
  getSessionReadinessStatus() {
    if (this.#session) {
      // We currently only support one session, see Bug 1720707.
      return {
        ready: false,
        message: "Session already started",
      };
    }

    return {
      ready: true,
      message: "",
    };
  }

  /**
   * Starts the WebDriver BiDi support.
   */
  async start() {
    if (this.#running) {
      return;
    }

    this.#running = true;

    lazy.RecommendedPreferences.applyPreferences(RECOMMENDED_PREFS);

    // Install a HTTP handler for direct WebDriver BiDi connection requests.
    this.#agent.server.registerPathHandler(
      "/session",
      new lazy.WebDriverNewSessionHandler(this)
    );

    Cu.printStderr(`WebDriver BiDi listening on ${this.address}\n`);

    try {
      // Write WebSocket connection details to the WebDriverBiDiServer.json file
      // located within the application's profile.
      this.#bidiServerPath = PathUtils.join(
        PathUtils.profileDir,
        "WebDriverBiDiServer.json"
      );

      const data = {
        ws_host: this.#agent.host,
        ws_port: this.#agent.port,
      };

      await IOUtils.write(
        this.#bidiServerPath,
        lazy.textEncoder.encode(JSON.stringify(data, undefined, "  "))
      );
    } catch (e) {
      lazy.logger.warn(
        `Failed to create ${this.#bidiServerPath} (${e.message})`
      );
    }
  }

  /**
   * Stops the WebDriver BiDi support.
   */
  async stop() {
    if (!this.#running) {
      return;
    }

    try {
      await IOUtils.remove(this.#bidiServerPath);
    } catch (e) {
      lazy.logger.warn(
        `Failed to remove ${this.#bidiServerPath} (${e.message})`
      );
    }

    try {
      // Close open session
      this.deleteSession();
      this.#agent.server.registerPathHandler("/session", null);

      // Close all open session-less connections
      this.#sessionlessConnections.forEach(connection => connection.close());
      this.#sessionlessConnections.clear();
    } catch (e) {
      lazy.logger.error("Failed to stop protocol", e);
    } finally {
      this.#running = false;
    }
  }
}
PK
!<��y"y"Dchrome/remote/content/webdriver-bidi/WebDriverBiDiConnection.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { WebSocketConnection } from "chrome://remote/content/shared/WebSocketConnection.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
  processCapabilities:
    "chrome://remote/content/shared/webdriver/Capabilities.sys.mjs",
  quit: "chrome://remote/content/shared/Browser.sys.mjs",
  RemoteAgent: "chrome://remote/content/components/RemoteAgent.sys.mjs",
  WebDriverSession: "chrome://remote/content/shared/webdriver/Session.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.WEBDRIVER_BIDI)
);

export class WebDriverBiDiConnection extends WebSocketConnection {
  #sessionConfigFlags;

  /**
   * @param {WebSocket} webSocket
   *     The WebSocket server connection to wrap.
   * @param {Connection} httpdConnection
   *     Reference to the httpd.js's connection needed for clean-up.
   */
  constructor(webSocket, httpdConnection) {
    super(webSocket, httpdConnection);

    // Each connection has only a single associated WebDriver session.
    this.session = null;

    this.#sessionConfigFlags = new Set([
      lazy.WebDriverSession.SESSION_FLAG_BIDI,
    ]);
  }

  /**
   * Perform required steps to end the session.
   */
  endSession() {
    // TODO Bug 1838269. Implement session ending logic
    // for the case of classic + bidi session.
    // We currently only support one session, see Bug 1720707.
    lazy.RemoteAgent.webDriverBiDi.deleteSession();
  }

  /**
   * Register a new WebDriver Session to forward the messages to.
   *
   * @param {Session} session
   *     The WebDriverSession to register.
   */
  registerSession(session) {
    if (this.session) {
      throw new lazy.error.UnknownError(
        "A WebDriver session has already been set"
      );
    }

    this.session = session;
    lazy.logger.debug(
      `Connection ${this.id} attached to session ${session.id}`
    );
  }

  /**
   * Unregister the already set WebDriver session.
   */
  unregisterSession() {
    if (!this.session) {
      return;
    }

    this.session.removeConnection(this);
    this.session = null;
  }

  /**
   * Send an error back to the WebDriver BiDi client.
   *
   * @param {number} id
   *     Id of the packet which lead to an error.
   * @param {Error} err
   *     Error object with `status`, `message` and `stack` attributes.
   */
  sendError(id, err) {
    const webDriverError = lazy.error.wrap(err);

    this.send({
      type: "error",
      id,
      error: webDriverError.status,
      message: webDriverError.message,
      stacktrace: webDriverError.stack,
    });
  }

  /**
   * Send an event coming from a module to the WebDriver BiDi client.
   *
   * @param {string} method
   *     The event name. This is composed by a module name, a dot character
   *     followed by the event name, e.g. `log.entryAdded`.
   * @param {object} params
   *     A JSON-serializable object, which is the payload of this event.
   */
  sendEvent(method, params) {
    this.send({ type: "event", method, params });

    if (Services.profiler?.IsActive()) {
      ChromeUtils.addProfilerMarker(
        "BiDi: Event",
        { category: "Remote-Protocol" },
        method
      );
    }
  }

  /**
   * Send the result of a call to a module's method back to the
   * WebDriver BiDi client.
   *
   * @param {number} id
   *     The request id being sent by the client to call the module's method.
   * @param {object} result
   *     A JSON-serializable object, which is the actual result.
   */
  sendResult(id, result) {
    result = typeof result !== "undefined" ? result : {};
    this.send({ type: "success", id, result });
  }

  observe(subject, topic) {
    switch (topic) {
      case "quit-application-requested":
        this.endSession();
        break;
    }
  }

  // Transport hooks

  /**
   * Called by the `transport` when the connection is closed.
   */
  onConnectionClose() {
    this.unregisterSession();

    super.onConnectionClose();
  }

  /**
   * Receive a packet from the WebSocket layer.
   *
   * This packet is sent by a WebDriver BiDi client and is meant to execute
   * a particular method on a given module.
   *
   * @param {object} packet
   *     JSON-serializable object sent by the client
   */
  async onPacket(packet) {
    super.onPacket(packet);

    const { id, method, params } = packet;
    const startTime = Cu.now();

    try {
      // First check for mandatory field in the command packet
      lazy.assert.positiveInteger(
        id,
        lazy.pprint`Expected "id" to be a postive integer, got ${id}`
      );
      lazy.assert.string(
        method,
        lazy.pprint`Expected "method" to be a string, got ${method}`
      );
      lazy.assert.object(
        params,
        lazy.pprint`Expected "params" to be an object, got ${params}`
      );

      // Extract the module and the command name out of `method` attribute
      const { module, command } = splitMethod(method);
      let result;

      // Handle static commands first
      if (module === "session" && command === "new") {
        const processedCapabilities = lazy.processCapabilities(params);

        result = await lazy.RemoteAgent.webDriverBiDi.createSession(
          processedCapabilities,
          this.#sessionConfigFlags,
          this
        );

        // Until the spec (see: https://github.com/w3c/webdriver/issues/1834)
        // is updated to specify what should be the default value for bidi session,
        // remove this capability from the return value when it's not provided by a client.
        if (!("unhandledPromptBehavior" in processedCapabilities)) {
          // We don't want to modify the original `capabilities` field
          // because it points to an original Capabilities object used by the session.
          // Since before the result is sent to a client we're going anyway to call
          // `JSON.stringify` on `result` which will call `toJSON` method recursively,
          // we can call it already here for the `capabilities` property
          // to update only the command response object.
          result.capabilities = result.capabilities.toJSON();
          delete result.capabilities.unhandledPromptBehavior;
        }
      } else if (module === "session" && command === "status") {
        result = lazy.RemoteAgent.webDriverBiDi.getSessionReadinessStatus();
      } else {
        lazy.assert.session(this.session);

        // Bug 1741854 - Workaround to deny internal methods to be called
        if (command.startsWith("_")) {
          throw new lazy.error.UnknownCommandError(method);
        }

        // Finally, instruct the session to execute the command
        result = await this.session.execute(module, command, params);
      }

      this.sendResult(id, result);

      // Session clean up.
      if (module === "session" && command === "end") {
        this.endSession();
      }
      // Close the browser.
      // TODO Bug 1842018. Refactor this part to return the response
      // when the quitting of the browser is finished.
      else if (module === "browser" && command === "close") {
        // Register handler to run WebDriver BiDi specific shutdown code.
        Services.obs.addObserver(this, "quit-application-requested");

        // TODO Bug 1836282. Add as the third argument "moz:windowless" capability
        // from the session, when this capability is supported by Webdriver BiDi.
        await lazy.quit(["eForceQuit"], false);

        Services.obs.removeObserver(this, "quit-application-requested");
      }
    } catch (e) {
      this.sendError(id, e);
    }

    if (Services.profiler?.IsActive()) {
      ChromeUtils.addProfilerMarker(
        "BiDi: Command",
        { startTime, category: "Remote-Protocol" },
        `${method} (${id})`
      );
    }
  }
}

/**
 * Splits a WebDriver BiDi method into module and command components.
 *
 * @param {string} method
 *     Name of the method to split, e.g. "session.subscribe".
 *
 * @returns {Record<string, string>}
 *     Object with the module ("session") and command ("subscribe")
 *     as properties.
 */
export function splitMethod(method) {
  const parts = method.split(".");

  if (parts.length != 2 || !parts[0].length || !parts[1].length) {
    throw new TypeError(`Invalid method format: '${method}'`);
  }

  return {
    module: parts[0],
    command: parts[1],
  };
}
PK
!<a�Y�>chrome/remote/content/webdriver-bidi/modules/Intercept.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  getSeenNodesForBrowsingContext:
    "chrome://remote/content/shared/webdriver/Session.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
});

/**
 * The serialization of JavaScript objects in the content process might produce
 * extra data that needs to be transfered and then processed by the parent
 * process. This extra data is part of the payload as returned by commands
 * and events and can contain the following:
 *
 *     - {Map<BrowsingContext, Array<string>>} seenNodeIds
 *         DOM nodes that need to be added to the navigable seen nodes map.
 *
 * @param {string} sessionId
 *     Id of the WebDriver session
 * @param {object} payload
 *     Payload of the response for the command and event that might contain
 *     a `_extraData` field.
 *
 * @returns {object}
 *     The payload with the extra data removed if it was present.
 */
export function processExtraData(sessionId, payload) {
  // Process extra data if present and delete it from the payload
  if ("_extraData" in payload) {
    const { seenNodeIds } = payload._extraData;

    // Updates the seen nodes for the current session and browsing context.
    seenNodeIds?.forEach((nodeIds, browsingContext) => {
      const seenNodes = lazy.getSeenNodesForBrowsingContext(
        sessionId,
        browsingContext
      );

      nodeIds.forEach(nodeId => seenNodes.add(nodeId));
    });

    delete payload._extraData;
  }

  // Find serialized WindowProxy and resolve browsing context to a navigable id.
  if (payload?.result) {
    payload.result = addContextIdToSerializedWindow(payload.result);
  } else if (payload.exceptionDetails) {
    payload.exceptionDetails = addContextIdToSerializedWindow(
      payload.exceptionDetails
    );
  }

  return payload;
}

function addContextIdToSerializedWindow(serialized) {
  if (serialized.value) {
    switch (serialized.type) {
      case "array":
      case "htmlcollection":
      case "nodelist":
      case "set": {
        serialized.value = serialized.value.map(value =>
          addContextIdToSerializedWindow(value)
        );
        break;
      }

      case "map":
      case "object": {
        serialized.value = serialized.value.map(([key, value]) => [
          key,
          addContextIdToSerializedWindow(value),
        ]);
        break;
      }

      case "window": {
        if (serialized.value.isTopBrowsingContext) {
          const browsingContext = BrowsingContext.getCurrentTopByBrowserId(
            serialized.value.context
          );

          serialized.value = {
            context: lazy.TabManager.getIdForBrowsingContext(browsingContext),
          };
        }
        break;
      }
    }
  } else if (serialized.exception) {
    serialized.exception = addContextIdToSerializedWindow(serialized.exception);
  }

  return serialized;
}
PK
!<��f�		Cchrome/remote/content/webdriver-bidi/modules/ModuleRegistry.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export const modules = {
  root: {},
  "windowglobal-in-root": {},
  windowglobal: {},
};

// eslint-disable-next-line mozilla/lazy-getter-object-name
ChromeUtils.defineESModuleGetters(modules.root, {
  browser:
    "chrome://remote/content/webdriver-bidi/modules/root/browser.sys.mjs",
  browsingContext:
    "chrome://remote/content/webdriver-bidi/modules/root/browsingContext.sys.mjs",
  input: "chrome://remote/content/webdriver-bidi/modules/root/input.sys.mjs",
  log: "chrome://remote/content/webdriver-bidi/modules/root/log.sys.mjs",
  network:
    "chrome://remote/content/webdriver-bidi/modules/root/network.sys.mjs",
  permissions:
    "chrome://remote/content/webdriver-bidi/modules/root/permissions.sys.mjs",
  script: "chrome://remote/content/webdriver-bidi/modules/root/script.sys.mjs",
  session:
    "chrome://remote/content/webdriver-bidi/modules/root/session.sys.mjs",
  storage:
    "chrome://remote/content/webdriver-bidi/modules/root/storage.sys.mjs",
});

// eslint-disable-next-line mozilla/lazy-getter-object-name
ChromeUtils.defineESModuleGetters(modules["windowglobal-in-root"], {
  browsingContext:
    "chrome://remote/content/webdriver-bidi/modules/windowglobal-in-root/browsingContext.sys.mjs",
  log: "chrome://remote/content/webdriver-bidi/modules/windowglobal-in-root/log.sys.mjs",
  network:
    "chrome://remote/content/webdriver-bidi/modules/windowglobal-in-root/network.sys.mjs",
  script:
    "chrome://remote/content/webdriver-bidi/modules/windowglobal-in-root/script.sys.mjs",
});

// eslint-disable-next-line mozilla/lazy-getter-object-name
ChromeUtils.defineESModuleGetters(modules.windowglobal, {
  browsingContext:
    "chrome://remote/content/webdriver-bidi/modules/windowglobal/browsingContext.sys.mjs",
  input:
    "chrome://remote/content/webdriver-bidi/modules/windowglobal/input.sys.mjs",
  log: "chrome://remote/content/webdriver-bidi/modules/windowglobal/log.sys.mjs",
  network:
    "chrome://remote/content/webdriver-bidi/modules/windowglobal/network.sys.mjs",
  script:
    "chrome://remote/content/webdriver-bidi/modules/windowglobal/script.sys.mjs",
});
PK
!<(�<��Cchrome/remote/content/webdriver-bidi/modules/RootBiDiModule.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  WindowGlobalMessageHandler:
    "chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs",
});

export class RootBiDiModule extends Module {
  /**
   * Emits an event for a specific browsing context.
   *
   * @param {string} browsingContextId
   *     The ID of the browsing context to which the event should be emitted.
   * @param {string} eventName
   *     The name of the event to be emitted.
   * @param {object} eventPayload
   *     The payload to be sent with the event.
   * @returns {boolean}
   *     Returns `true` if the event was successfully emitted, otherwise `false`.
   */
  _emitEventForBrowsingContext(browsingContextId, eventName, eventPayload) {
    // This event is emitted from the parent process but for a given browsing
    // context. Set the event's contextInfo to the message handler corresponding
    // to this browsing context.
    const contextInfo = {
      contextId: browsingContextId,
      type: lazy.WindowGlobalMessageHandler.type,
    };
    return this.emitEvent(eventName, eventPayload, contextInfo);
  }

  /**
   * Checks if there is a listener for a specific event and context information.
   *
   * @param {string} eventName
   *     The name of the event to check for listeners.
   * @param {ContextInfo} contextInfo
   *     The context information to check for listeners within.
   * @returns {boolean}
   *     Returns `true` if there is at least one listener for the specified event and context, otherwise `false`.
   */
  _hasListener(eventName, contextInfo) {
    return this.messageHandler.eventsDispatcher.hasListener(
      eventName,
      contextInfo
    );
  }
}
PK
!< �Y��	�	Kchrome/remote/content/webdriver-bidi/modules/WindowGlobalBiDiModule.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  deserialize: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
  serialize: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
});

/**
 * Base class for all WindowGlobal BiDi MessageHandler modules.
 */
export class WindowGlobalBiDiModule extends Module {
  get #nodeCache() {
    return this.#processActor.getNodeCache();
  }

  get #processActor() {
    return ChromeUtils.domProcessChild.getActor("WebDriverProcessData");
  }

  /**
   * Wrapper to deserialize a local / remote value.
   *
   * @param {object} serializedValue
   *     Value of any type to be deserialized.
   * @param {Realm} realm
   *     The Realm in which the value is deserialized.
   * @param {ExtraSerializationOptions=} extraOptions
   *     Extra Remote Value deserialization options.
   *
   * @returns {object}
   *     Deserialized representation of the value.
   */
  deserialize(serializedValue, realm, extraOptions = {}) {
    extraOptions.nodeCache = this.#nodeCache;

    return lazy.deserialize(serializedValue, realm, extraOptions);
  }

  /**
   * Wrapper to serialize a value as a remote value.
   *
   * @param {object} value
   *     Value of any type to be serialized.
   * @param {SerializationOptions} serializationOptions
   *     Options which define how ECMAScript objects should be serialized.
   * @param {OwnershipModel} ownershipType
   *     The ownership model to use for this serialization.
   * @param {Realm} realm
   *     The Realm from which comes the value being serialized.
   * @param {ExtraSerializationOptions} extraOptions
   *     Extra Remote Value serialization options.
   *
   * @returns {object}
   *     Promise that resolves to the serialized representation of the value.
   */
  serialize(
    value,
    serializationOptions,
    ownershipType,
    realm,
    extraOptions = {}
  ) {
    const { nodeCache = this.#nodeCache, seenNodeIds = new Map() } =
      extraOptions;

    const serializedValue = lazy.serialize(
      value,
      serializationOptions,
      ownershipType,
      new Map(),
      realm,
      { nodeCache, seenNodeIds }
    );

    return serializedValue;
  }
}
PK
!<:��=��Achrome/remote/content/webdriver-bidi/modules/root/browser.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { RootBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/RootBiDiModule.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  getWebDriverSessionById:
    "chrome://remote/content/shared/webdriver/Session.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
  UserContextManager:
    "chrome://remote/content/shared/UserContextManager.sys.mjs",
});

/**
 * An object that holds information about a user context.
 *
 * @typedef UserContextInfo
 *
 * @property {string} userContext
 *     The id of the user context.
 */

/**
 * Return value for the getUserContexts command.
 *
 * @typedef GetUserContextsResult
 *
 * @property {Array<UserContextInfo>} userContexts
 *     Array of UserContextInfo for the current user contexts.
 */

class BrowserModule extends RootBiDiModule {
  constructor(messageHandler) {
    super(messageHandler);
  }

  destroy() {}

  /**
   * Commands
   */

  /**
   * Terminate all WebDriver sessions and clean up automation state in the
   * remote browser instance.
   *
   * The actual session clean-up and closing the browser will happen later
   * in WebDriverBiDiConnection class.
   */
  async close() {
    const session = lazy.getWebDriverSessionById(this.messageHandler.sessionId);

    // TODO Bug 1838269. Enable browser.close command for the case of classic + bidi session, when
    // session ending for this type of session is supported.
    if (session.http) {
      throw new lazy.error.UnsupportedOperationError(
        "Closing the browser in a session started with WebDriver classic" +
          ' is not supported. Use the WebDriver classic "Delete Session"' +
          " command instead which will also close the browser."
      );
    }

    // Close all open top-level browsing contexts by not prompting for beforeunload.
    for (const tab of lazy.TabManager.tabs) {
      lazy.TabManager.removeTab(tab, { skipPermitUnload: true });
    }
  }

  /**
   * Creates a user context.
   *
   * @returns {UserContextInfo}
   *     UserContextInfo object for the created user context.
   */
  async createUserContext() {
    const userContextId = lazy.UserContextManager.createContext("webdriver");
    return { userContext: userContextId };
  }

  /**
   * Returns the list of available user contexts.
   *
   * @returns {GetUserContextsResult}
   *     Object containing an array of UserContextInfo.
   */
  async getUserContexts() {
    const userContexts = lazy.UserContextManager.getUserContextIds().map(
      userContextId => ({
        userContext: userContextId,
      })
    );

    return { userContexts };
  }

  /**
   * Closes a user context and all browsing contexts in it without running
   * beforeunload handlers.
   *
   * @param {object=} options
   * @param {string} options.userContext
   *     Id of the user context to close.
   *
   * @throws {InvalidArgumentError}
   *     Raised if an argument is of an invalid type or value.
   * @throws {NoSuchUserContextError}
   *     Raised if the user context id could not be found.
   */
  async removeUserContext(options = {}) {
    const { userContext: userContextId } = options;

    lazy.assert.string(
      userContextId,
      lazy.pprint`Expected "userContext" to be a string, got ${userContextId}`
    );

    if (userContextId === lazy.UserContextManager.defaultUserContextId) {
      throw new lazy.error.InvalidArgumentError(
        `Default user context cannot be removed`
      );
    }

    if (!lazy.UserContextManager.hasUserContextId(userContextId)) {
      throw new lazy.error.NoSuchUserContextError(
        `User Context with id ${userContextId} was not found`
      );
    }
    lazy.UserContextManager.removeUserContext(userContextId, {
      closeContextTabs: true,
    });
  }
}

// To export the class as lower-case
export const browser = BrowserModule;
PK
!<��,�+�+�Ichrome/remote/content/webdriver-bidi/modules/root/browsingContext.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { RootBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/RootBiDiModule.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  BrowsingContextListener:
    "chrome://remote/content/shared/listeners/BrowsingContextListener.sys.mjs",
  capture: "chrome://remote/content/shared/Capture.sys.mjs",
  ContextDescriptorType:
    "chrome://remote/content/shared/messagehandler/MessageHandler.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  EventPromise: "chrome://remote/content/shared/Sync.sys.mjs",
  getTimeoutMultiplier: "chrome://remote/content/shared/AppInfo.sys.mjs",
  getWebDriverSessionById:
    "chrome://remote/content/shared/webdriver/Session.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  modal: "chrome://remote/content/shared/Prompt.sys.mjs",
  registerNavigationId:
    "chrome://remote/content/shared/NavigationManager.sys.mjs",
  NavigationListener:
    "chrome://remote/content/shared/listeners/NavigationListener.sys.mjs",
  PollPromise: "chrome://remote/content/shared/Sync.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
  print: "chrome://remote/content/shared/PDF.sys.mjs",
  ProgressListener: "chrome://remote/content/shared/Navigate.sys.mjs",
  PromptListener:
    "chrome://remote/content/shared/listeners/PromptListener.sys.mjs",
  setDefaultAndAssertSerializationOptions:
    "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
  UserContextManager:
    "chrome://remote/content/shared/UserContextManager.sys.mjs",
  waitForInitialNavigationCompleted:
    "chrome://remote/content/shared/Navigate.sys.mjs",
  WindowGlobalMessageHandler:
    "chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs",
  windowManager: "chrome://remote/content/shared/WindowManager.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.WEBDRIVER_BIDI)
);

// Maximal window dimension allowed when emulating a viewport.
const MAX_WINDOW_SIZE = 10000000;

/**
 * @typedef {string} ClipRectangleType
 */

/**
 * Enum of possible clip rectangle types supported by the
 * browsingContext.captureScreenshot command.
 *
 * @readonly
 * @enum {ClipRectangleType}
 */
export const ClipRectangleType = {
  Box: "box",
  Element: "element",
};

/**
 * @typedef {object} CreateType
 */

/**
 * Enum of types supported by the browsingContext.create command.
 *
 * @readonly
 * @enum {CreateType}
 */
const CreateType = {
  tab: "tab",
  window: "window",
};

/**
 * @typedef {string} LocatorType
 */

/**
 * Enum of types supported by the browsingContext.locateNodes command.
 *
 * @readonly
 * @enum {LocatorType}
 */
export const LocatorType = {
  accessibility: "accessibility",
  css: "css",
  innerText: "innerText",
  xpath: "xpath",
};

/**
 * @typedef {string} OriginType
 */

/**
 * Enum of origin type supported by the
 * browsingContext.captureScreenshot command.
 *
 * @readonly
 * @enum {OriginType}
 */
export const OriginType = {
  document: "document",
  viewport: "viewport",
};

const TIMEOUT_SET_HISTORY_INDEX = 1000;

/**
 * Enum of user prompt types supported by the browsingContext.handleUserPrompt
 * command, these types can be retrieved from `dialog.args.promptType`.
 *
 * @readonly
 * @enum {UserPromptType}
 */
const UserPromptType = {
  alert: "alert",
  confirm: "confirm",
  prompt: "prompt",
  beforeunload: "beforeunload",
};

/**
 * An object that contains details of a viewport.
 *
 * @typedef {object} Viewport
 *
 * @property {number} height
 *     The height of the viewport.
 * @property {number} width
 *     The width of the viewport.
 */

/**
 * @typedef {string} WaitCondition
 */

/**
 * Wait conditions supported by WebDriver BiDi for navigation.
 *
 * @enum {WaitCondition}
 */
const WaitCondition = {
  None: "none",
  Interactive: "interactive",
  Complete: "complete",
};

class BrowsingContextModule extends RootBiDiModule {
  #contextListener;
  #navigationListener;
  #promptListener;
  #subscribedEvents;

  /**
   * Create a new module instance.
   *
   * @param {MessageHandler} messageHandler
   *     The MessageHandler instance which owns this Module instance.
   */
  constructor(messageHandler) {
    super(messageHandler);

    this.#contextListener = new lazy.BrowsingContextListener();
    this.#contextListener.on("attached", this.#onContextAttached);
    this.#contextListener.on("discarded", this.#onContextDiscarded);

    this.#navigationListener = new lazy.NavigationListener(
      this.messageHandler.navigationManager
    );
    this.#navigationListener.on(
      "fragment-navigated",
      this.#onFragmentNavigated
    );
    this.#navigationListener.on("navigation-failed", this.#onNavigationFailed);
    this.#navigationListener.on(
      "navigation-started",
      this.#onNavigationStarted
    );

    // Create the prompt listener and listen to "closed" and "opened" events.
    this.#promptListener = new lazy.PromptListener();
    this.#promptListener.on("closed", this.#onPromptClosed);
    this.#promptListener.on("opened", this.#onPromptOpened);

    // Set of event names which have active subscriptions.
    this.#subscribedEvents = new Set();

    // Treat the event of moving a page to BFCache as context discarded event for iframes.
    this.messageHandler.on("windowglobal-pagehide", this.#onPageHideEvent);
  }

  destroy() {
    this.#contextListener.off("attached", this.#onContextAttached);
    this.#contextListener.off("discarded", this.#onContextDiscarded);
    this.#contextListener.destroy();

    this.#navigationListener.off(
      "fragment-navigated",
      this.#onFragmentNavigated
    );
    this.#navigationListener.off("navigation-failed", this.#onNavigationFailed);
    this.#navigationListener.off(
      "navigation-started",
      this.#onNavigationStarted
    );
    this.#navigationListener.destroy();

    this.#promptListener.off("closed", this.#onPromptClosed);
    this.#promptListener.off("opened", this.#onPromptOpened);
    this.#promptListener.destroy();

    this.#subscribedEvents = null;

    this.messageHandler.off("windowglobal-pagehide", this.#onPageHideEvent);
  }

  /**
   * Activates and focuses the given top-level browsing context.
   *
   * @param {object=} options
   * @param {string} options.context
   *     Id of the browsing context.
   *
   * @throws {InvalidArgumentError}
   *     Raised if an argument is of an invalid type or value.
   * @throws {NoSuchFrameError}
   *     If the browsing context cannot be found.
   */
  async activate(options = {}) {
    const { context: contextId } = options;

    lazy.assert.string(
      contextId,
      lazy.pprint`Expected "context" to be a string, got ${contextId}`
    );
    const context = this.#getBrowsingContext(contextId);

    if (context.parent) {
      throw new lazy.error.InvalidArgumentError(
        `Browsing Context with id ${contextId} is not top-level`
      );
    }

    const targetTab = lazy.TabManager.getTabForBrowsingContext(context);
    const targetWindow = lazy.TabManager.getWindowForTab(targetTab);
    const selectedTab = lazy.TabManager.getTabBrowser(targetWindow).selectedTab;

    const activated = [
      lazy.windowManager.focusWindow(targetWindow),
      lazy.TabManager.selectTab(targetTab),
    ];

    if (targetTab !== selectedTab && !lazy.AppInfo.isAndroid) {
      // We need to wait until the "document.visibilityState" of the currently
      // selected tab in the target window is marked as "hidden".
      //
      // Bug 1884142: It's not supported on Android for the TestRunner package.
      const selectedBrowser = lazy.TabManager.getBrowserForTab(selectedTab);
      activated.push(
        this.#waitForVisibilityChange(selectedBrowser.browsingContext)
      );
    }

    await Promise.all(activated);
  }

  /**
   * Used as an argument for browsingContext.captureScreenshot command, as one of the available variants
   * {BoxClipRectangle} or {ElementClipRectangle}, to represent a target of the command.
   *
   * @typedef ClipRectangle
   */

  /**
   * Used as an argument for browsingContext.captureScreenshot command
   * to represent a box which is going to be a target of the command.
   *
   * @typedef BoxClipRectangle
   *
   * @property {ClipRectangleType} [type=ClipRectangleType.Box]
   * @property {number} x
   * @property {number} y
   * @property {number} width
   * @property {number} height
   */

  /**
   * Used as an argument for browsingContext.captureScreenshot command
   * to represent an element which is going to be a target of the command.
   *
   * @typedef ElementClipRectangle
   *
   * @property {ClipRectangleType} [type=ClipRectangleType.Element]
   * @property {SharedReference} element
   */

  /**
   * Capture a base64-encoded screenshot of the provided browsing context.
   *
   * @param {object=} options
   * @param {string} options.context
   *     Id of the browsing context to screenshot.
   * @param {ClipRectangle=} options.clip
   *     A box or an element of which a screenshot should be taken.
   *     If not present, take a screenshot of the whole viewport.
   * @param {OriginType=} options.origin
   *
   * @throws {NoSuchFrameError}
   *     If the browsing context cannot be found.
   */
  async captureScreenshot(options = {}) {
    const {
      clip = null,
      context: contextId,
      origin = OriginType.viewport,
    } = options;

    lazy.assert.string(
      contextId,
      lazy.pprint`Expected "context" to be a string, got ${contextId}`
    );
    const context = this.#getBrowsingContext(contextId);

    const originTypeValues = Object.values(OriginType);
    lazy.assert.that(
      value => originTypeValues.includes(value),
      `Expected "origin" to be one of ${originTypeValues}, ` +
        lazy.pprint`got ${origin}`
    )(origin);

    if (clip !== null) {
      lazy.assert.object(
        clip,
        lazy.pprint`Expected "clip" to be an object, got ${clip}`
      );

      const { type } = clip;
      switch (type) {
        case ClipRectangleType.Box: {
          const { x, y, width, height } = clip;

          lazy.assert.number(
            x,
            lazy.pprint`Expected "x" to be a number, got ${x}`
          );
          lazy.assert.number(
            y,
            lazy.pprint`Expected "y" to be a number, got ${y}`
          );
          lazy.assert.number(
            width,
            lazy.pprint`Expected "width" to be a number, got ${width}`
          );
          lazy.assert.number(
            height,
            lazy.pprint`Expected "height" to be a number, got ${height}`
          );

          break;
        }

        case ClipRectangleType.Element: {
          const { element } = clip;

          lazy.assert.object(
            element,
            lazy.pprint`Expected "element" to be an object, got ${element}`
          );

          break;
        }

        default:
          throw new lazy.error.InvalidArgumentError(
            `Expected "type" to be one of ${Object.values(
              ClipRectangleType
            )}, ` + lazy.pprint`got ${type}`
          );
      }
    }

    const rect = await this.messageHandler.handleCommand({
      moduleName: "browsingContext",
      commandName: "_getScreenshotRect",
      destination: {
        type: lazy.WindowGlobalMessageHandler.type,
        id: context.id,
      },
      params: {
        clip,
        origin,
      },
      retryOnAbort: true,
    });

    if (rect.width === 0 || rect.height === 0) {
      throw new lazy.error.UnableToCaptureScreen(
        `The dimensions of requested screenshot are incorrect, got width: ${rect.width} and height: ${rect.height}.`
      );
    }

    const canvas = await lazy.capture.canvas(
      context.topChromeWindow,
      context,
      rect.x,
      rect.y,
      rect.width,
      rect.height
    );

    return {
      data: lazy.capture.toBase64(canvas),
    };
  }

  /**
   * Close the provided browsing context.
   *
   * @param {object=} options
   * @param {string} options.context
   *     Id of the browsing context to close.
   * @param {boolean=} options.promptUnload
   *     Flag to indicate if a potential beforeunload prompt should be shown
   *     when closing the browsing context. Defaults to false.
   *
   * @throws {NoSuchFrameError}
   *     If the browsing context cannot be found.
   * @throws {InvalidArgumentError}
   *     If the browsing context is not a top-level one.
   */
  async close(options = {}) {
    const { context: contextId, promptUnload = false } = options;

    lazy.assert.string(
      contextId,
      lazy.pprint`Expected "context" to be a string, got ${contextId}`
    );

    lazy.assert.boolean(
      promptUnload,
      lazy.pprint`Expected "promptUnload" to be a boolean, got ${promptUnload}`
    );

    const context = lazy.TabManager.getBrowsingContextById(contextId);
    if (!context) {
      throw new lazy.error.NoSuchFrameError(
        `Browsing Context with id ${contextId} not found`
      );
    }

    if (context.parent) {
      throw new lazy.error.InvalidArgumentError(
        `Browsing Context with id ${contextId} is not top-level`
      );
    }

    if (lazy.TabManager.getTabCount() === 1) {
      // The behavior when closing the very last tab is currently unspecified.
      // As such behave like Marionette and don't allow closing it.
      // See: https://github.com/w3c/webdriver-bidi/issues/187
      return;
    }

    const tab = lazy.TabManager.getTabForBrowsingContext(context);
    await lazy.TabManager.removeTab(tab, { skipPermitUnload: !promptUnload });
  }

  /**
   * Create a new browsing context using the provided type "tab" or "window".
   *
   * @param {object=} options
   * @param {boolean=} options.background
   *     Whether the tab/window should be open in the background. Defaults to false,
   *     which means that the tab/window will be open in the foreground.
   * @param {string=} options.referenceContext
   *     Id of the top-level browsing context to use as reference.
   *     If options.type is "tab", the new tab will open in the same window as
   *     the reference context, and will be added next to the reference context.
   *     If options.type is "window", the reference context is ignored.
   * @param {CreateType} options.type
   *     Type of browsing context to create.
   * @param {string=} options.userContext
   *     The id of the user context which should own the browsing context.
   *     Defaults to the default user context.
   *
   * @throws {InvalidArgumentError}
   *     If the browsing context is not a top-level one.
   * @throws {NoSuchFrameError}
   *     If the browsing context cannot be found.
   */
  async create(options = {}) {
    const {
      background = false,
      referenceContext: referenceContextId = null,
      type: typeHint,
      userContext: userContextId = null,
    } = options;

    if (![CreateType.tab, CreateType.window].includes(typeHint)) {
      throw new lazy.error.InvalidArgumentError(
        `Expected "type" to be one of ${Object.values(CreateType)}, ` +
          lazy.pprint`got ${typeHint}`
      );
    }

    lazy.assert.boolean(
      background,
      lazy.pprint`Expected "background" to be a boolean, got ${background}`
    );

    let referenceContext = null;
    if (referenceContextId !== null) {
      lazy.assert.string(
        referenceContextId,
        lazy.pprint`Expected "referenceContext" to be a string, got ${referenceContextId}`
      );

      referenceContext =
        lazy.TabManager.getBrowsingContextById(referenceContextId);
      if (!referenceContext) {
        throw new lazy.error.NoSuchFrameError(
          `Browsing Context with id ${referenceContextId} not found`
        );
      }

      if (referenceContext.parent) {
        throw new lazy.error.InvalidArgumentError(
          `referenceContext with id ${referenceContextId} is not a top-level browsing context`
        );
      }
    }

    let userContext = lazy.UserContextManager.defaultUserContextId;
    if (referenceContext !== null) {
      userContext =
        lazy.UserContextManager.getIdByBrowsingContext(referenceContext);
    }

    if (userContextId !== null) {
      lazy.assert.string(
        userContextId,
        lazy.pprint`Expected "userContext" to be a string, got ${userContextId}`
      );

      if (!lazy.UserContextManager.hasUserContextId(userContextId)) {
        throw new lazy.error.NoSuchUserContextError(
          `User Context with id ${userContextId} was not found`
        );
      }

      userContext = userContextId;

      if (
        lazy.AppInfo.isAndroid &&
        userContext != lazy.UserContextManager.defaultUserContextId
      ) {
        throw new lazy.error.UnsupportedOperationError(
          `browsingContext.create with non-default "userContext" not supported for ${lazy.AppInfo.name}`
        );
      }
    }

    let browser;

    // Since each tab in GeckoView has its own Gecko instance running,
    // which means also its own window object, for Android we will need to focus
    // a previously focused window in case of opening the tab in the background.
    const previousWindow = Services.wm.getMostRecentBrowserWindow();
    const previousTab =
      lazy.TabManager.getTabBrowser(previousWindow).selectedTab;

    // On Android there is only a single window allowed. As such fallback to
    // open a new tab instead.
    const type = lazy.AppInfo.isAndroid ? "tab" : typeHint;
    let waitForVisibilityChangePromise;
    switch (type) {
      case "window": {
        const newWindow = await lazy.windowManager.openBrowserWindow({
          focus: !background,
          userContextId: userContext,
        });
        browser = lazy.TabManager.getTabBrowser(newWindow).selectedBrowser;
        break;
      }
      case "tab": {
        if (!lazy.TabManager.supportsTabs()) {
          throw new lazy.error.UnsupportedOperationError(
            `browsingContext.create with type "tab" not supported in ${lazy.AppInfo.name}`
          );
        }

        // The window to open the new tab in.
        let window = Services.wm.getMostRecentWindow(null);

        let referenceTab;
        if (referenceContext !== null) {
          referenceTab =
            lazy.TabManager.getTabForBrowsingContext(referenceContext);
          window = lazy.TabManager.getWindowForTab(referenceTab);
        }

        if (!background && !lazy.AppInfo.isAndroid) {
          // When opening a new foreground tab we need to wait until the
          // "document.visibilityState" of the currently selected tab in this
          // window is marked as "hidden".
          //
          // Bug 1884142: It's not supported on Android for the TestRunner package.
          const selectedTab = lazy.TabManager.getTabBrowser(window).selectedTab;

          // Create the promise immediately, but await it later in parallel with
          // waitForInitialNavigationCompleted.
          waitForVisibilityChangePromise = this.#waitForVisibilityChange(
            lazy.TabManager.getBrowserForTab(selectedTab).browsingContext
          );
        }

        const tab = await lazy.TabManager.addTab({
          focus: !background,
          referenceTab,
          userContextId: userContext,
        });
        browser = lazy.TabManager.getBrowserForTab(tab);
      }
    }

    await Promise.all([
      lazy.waitForInitialNavigationCompleted(
        browser.browsingContext.webProgress,
        {
          unloadTimeout: 5000,
        }
      ),
      waitForVisibilityChangePromise,
    ]);

    // The tab on Android is always opened in the foreground,
    // so we need to select the previous tab,
    // and we have to wait until is fully loaded.
    // TODO: Bug 1845559. This workaround can be removed,
    // when the API to create a tab for Android supports the background option.
    if (lazy.AppInfo.isAndroid && background) {
      await lazy.windowManager.focusWindow(previousWindow);
      await lazy.TabManager.selectTab(previousTab);
    }

    // Force a reflow by accessing `clientHeight` (see Bug 1847044).
    browser.parentElement.clientHeight;

    return {
      context: lazy.TabManager.getIdForBrowser(browser),
    };
  }

  /**
   * An object that holds the WebDriver Bidi browsing context information.
   *
   * @typedef BrowsingContextInfo
   *
   * @property {string} context
   *     The id of the browsing context.
   * @property {string=} parent
   *     The parent of the browsing context if it's the root browsing context
   *     of the to be processed browsing context tree.
   * @property {string} url
   *     The current documents location.
   * @property {string} userContext
   *     The id of the user context owning this browsing context.
   * @property {Array<BrowsingContextInfo>=} children
   *     List of child browsing contexts. Only set if maxDepth hasn't been
   *     reached yet.
   */

  /**
   * An object that holds the WebDriver Bidi browsing context tree information.
   *
   * @typedef BrowsingContextGetTreeResult
   *
   * @property {Array<BrowsingContextInfo>} contexts
   *     List of child browsing contexts.
   */

  /**
   * Returns a tree of all browsing contexts that are descendents of the
   * given context, or all top-level contexts when no root is provided.
   *
   * @param {object=} options
   * @param {number=} options.maxDepth
   *     Depth of the browsing context tree to traverse. If not specified
   *     the whole tree is returned.
   * @param {string=} options.root
   *     Id of the root browsing context.
   *
   * @returns {BrowsingContextGetTreeResult}
   *     Tree of browsing context information.
   * @throws {NoSuchFrameError}
   *     If the browsing context cannot be found.
   */
  getTree(options = {}) {
    const { maxDepth = null, root: rootId = null } = options;

    if (maxDepth !== null) {
      lazy.assert.positiveInteger(
        maxDepth,
        lazy.pprint`Expected "maxDepth" to be a positive integer, got ${maxDepth}`
      );
    }

    let contexts;
    if (rootId !== null) {
      // With a root id specified return the context info for itself
      // and the full tree.
      lazy.assert.string(
        rootId,
        lazy.pprint`Expected "root" to be a string, got ${rootId}`
      );
      contexts = [this.#getBrowsingContext(rootId)];
    } else {
      // Return all top-level browsing contexts.
      contexts = lazy.TabManager.browsers.map(
        browser => browser.browsingContext
      );
    }

    const contextsInfo = contexts.map(context => {
      return this.#getBrowsingContextInfo(context, { maxDepth });
    });

    return { contexts: contextsInfo };
  }

  /**
   * Closes an open prompt.
   *
   * @param {object=} options
   * @param {string} options.context
   *     Id of the browsing context.
   * @param {boolean=} options.accept
   *     Whether user prompt should be accepted or dismissed.
   *     Defaults to true.
   * @param {string=} options.userText
   *     Input to the user prompt's value field.
   *     Defaults to an empty string.
   *
   * @throws {InvalidArgumentError}
   *     Raised if an argument is of an invalid type or value.
   * @throws {NoSuchAlertError}
   *     If there is no current user prompt.
   * @throws {NoSuchFrameError}
   *     If the browsing context cannot be found.
   * @throws {UnsupportedOperationError}
   *     Raised when the command is called for "beforeunload" prompt.
   */
  async handleUserPrompt(options = {}) {
    const { accept = true, context: contextId, userText = "" } = options;

    lazy.assert.string(
      contextId,
      lazy.pprint`Expected "context" to be a string, got ${contextId}`
    );

    const context = this.#getBrowsingContext(contextId);

    lazy.assert.boolean(
      accept,
      lazy.pprint`Expected "accept" to be a boolean, got ${accept}`
    );

    lazy.assert.string(
      userText,
      lazy.pprint`Expected "userText" to be a string, got ${userText}`
    );

    const tab = lazy.TabManager.getTabForBrowsingContext(context);
    const browser = lazy.TabManager.getBrowserForTab(tab);
    const window = lazy.TabManager.getWindowForTab(tab);
    const dialog = lazy.modal.findPrompt({
      window,
      contentBrowser: browser,
    });

    const closePrompt = async callback => {
      const dialogClosed = new lazy.EventPromise(
        window,
        "DOMModalDialogClosed"
      );
      callback();
      await dialogClosed;
    };

    if (dialog && dialog.isOpen) {
      switch (dialog.promptType) {
        case UserPromptType.alert:
          await closePrompt(() => dialog.accept());
          return;

        case UserPromptType.beforeunload:
        case UserPromptType.confirm:
          await closePrompt(() => {
            if (accept) {
              dialog.accept();
            } else {
              dialog.dismiss();
            }
          });

          return;

        case UserPromptType.prompt:
          await closePrompt(() => {
            if (accept) {
              dialog.text = userText;
              dialog.accept();
            } else {
              dialog.dismiss();
            }
          });

          return;

        default:
          throw new lazy.error.UnsupportedOperationError(
            `Prompts of type "${dialog.promptType}" are not supported`
          );
      }
    }

    throw new lazy.error.NoSuchAlertError();
  }

  /**
   * Used as an argument for browsingContext.locateNodes command, as one of the available variants
   * {AccessibilityLocator}, {CssLocator}, {InnerTextLocator} or {XPathLocator}, to represent a way of how lookup of nodes
   * is going to be performed.
   *
   * @typedef Locator
   */

  /**
   * Used as a value argument for browsingContext.locateNodes command
   * in case of a lookup by accessibility attributes.
   *
   * @typedef AccessibilityLocatorValue
   *
   * @property {string=} name
   * @property {string=} role
   */

  /**
   * Used as an argument for browsingContext.locateNodes command
   * to represent a lookup by accessibility attributes.
   *
   * @typedef AccessibilityLocator
   *
   * @property {LocatorType} [type=LocatorType.accessibility]
   * @property {AccessibilityLocatorValue} value
   */

  /**
   * Used as an argument for browsingContext.locateNodes command
   * to represent a lookup by css selector.
   *
   * @typedef CssLocator
   *
   * @property {LocatorType} [type=LocatorType.css]
   * @property {string} value
   */

  /**
   * Used as an argument for browsingContext.locateNodes command
   * to represent a lookup by inner text.
   *
   * @typedef InnerTextLocator
   *
   * @property {LocatorType} [type=LocatorType.innerText]
   * @property {string} value
   * @property {boolean=} ignoreCase
   * @property {("full"|"partial")=} matchType
   * @property {number=} maxDepth
   */

  /**
   * Used as an argument for browsingContext.locateNodes command
   * to represent a lookup by xpath.
   *
   * @typedef XPathLocator
   *
   * @property {LocatorType} [type=LocatorType.xpath]
   * @property {string} value
   */

  /**
   * Returns a list of all nodes matching
   * the specified locator.
   *
   * @param {object} options
   * @param {string} options.context
   *     Id of the browsing context.
   * @param {Locator} options.locator
   *     The type of lookup which is going to be used.
   * @param {number=} options.maxNodeCount
   *     The maximum amount of nodes which is going to be returned.
   *     Defaults to return all the found nodes.
   * @property {SerializationOptions=} serializationOptions
   *     An object which holds the information of how the DOM nodes
   *     should be serialized.
   * @property {Array<SharedReference>=} startNodes
   *     A list of references to nodes, which are used as
   *     starting points for lookup.
   *
   * @throws {InvalidArgumentError}
   *     Raised if an argument is of an invalid type or value.
   * @throws {InvalidSelectorError}
   *     Raised if a locator value is invalid.
   * @throws {NoSuchFrameError}
   *     If the browsing context cannot be found.
   * @throws {UnsupportedOperationError}
   *     Raised when unsupported lookup types are used.
   */
  async locateNodes(options = {}) {
    const {
      context: contextId,
      locator,
      maxNodeCount = null,
      serializationOptions,
      startNodes = null,
    } = options;

    lazy.assert.string(
      contextId,
      lazy.pprint`Expected "context" to be a string, got ${contextId}`
    );

    const context = this.#getBrowsingContext(contextId);

    lazy.assert.object(
      locator,
      lazy.pprint`Expected "locator" to be an object, got ${locator}`
    );

    const locatorTypes = Object.values(LocatorType);

    lazy.assert.that(
      locatorType => locatorTypes.includes(locatorType),
      `Expected "locator.type" to be one of ${locatorTypes}, ` +
        lazy.pprint`got ${locator.type}`
    )(locator.type);

    if (
      [LocatorType.css, LocatorType.innerText, LocatorType.xpath].includes(
        locator.type
      )
    ) {
      lazy.assert.string(
        locator.value,
        `Expected "locator.value" of "locator.type" "${locator.type}" to be a string, ` +
          lazy.pprint`got ${locator.value}`
      );
    }
    if (locator.type == LocatorType.accessibility) {
      lazy.assert.object(
        locator.value,
        `Expected "locator.value" of "locator.type" "${locator.type}" to be an object, ` +
          lazy.pprint`got ${locator.value}`
      );

      const { name = null, role = null } = locator.value;
      if (name !== null) {
        lazy.assert.string(
          locator.value.name,
          `Expected "locator.value.name" of "locator.type" "${locator.type}" to be a string, ` +
            lazy.pprint`got ${name}`
        );
      }
      if (role !== null) {
        lazy.assert.string(
          locator.value.role,
          `Expected "locator.value.role" of "locator.type" "${locator.type}" to be a string, ` +
            lazy.pprint`got ${role}`
        );
      }
    }

    if (
      ![LocatorType.accessibility, LocatorType.css, LocatorType.xpath].includes(
        locator.type
      )
    ) {
      throw new lazy.error.UnsupportedOperationError(
        `"locator.type" argument with value: ${locator.type} is not supported yet.`
      );
    }

    if (maxNodeCount != null) {
      const maxNodeCountErrorMsg = lazy.pprint`Expected "maxNodeCount" to be an integer and greater than 0, got ${maxNodeCount}`;
      lazy.assert.that(maxNodeCount => {
        lazy.assert.integer(maxNodeCount, maxNodeCountErrorMsg);
        return maxNodeCount > 0;
      }, maxNodeCountErrorMsg)(maxNodeCount);
    }

    const serializationOptionsWithDefaults =
      lazy.setDefaultAndAssertSerializationOptions(serializationOptions);

    if (startNodes != null) {
      lazy.assert.that(startNodes => {
        lazy.assert.array(
          startNodes,
          lazy.pprint`Expected "startNodes" to be an array, got ${startNodes}`
        );
        return !!startNodes.length;
      }, lazy.pprint`Expected "startNodes" to have at least one element, got ${startNodes}`)(
        startNodes
      );
    }

    const result = await this.messageHandler.forwardCommand({
      moduleName: "browsingContext",
      commandName: "_locateNodes",
      destination: {
        type: lazy.WindowGlobalMessageHandler.type,
        id: context.id,
      },
      params: {
        locator,
        maxNodeCount,
        serializationOptions: serializationOptionsWithDefaults,
        startNodes,
      },
    });

    return {
      nodes: result.serializedNodes,
    };
  }

  /**
   * An object that holds the WebDriver Bidi navigation information.
   *
   * @typedef BrowsingContextNavigateResult
   *
   * @property {string} navigation
   *     Unique id for this navigation.
   * @property {string} url
   *     The requested or reached URL.
   */

  /**
   * Navigate the given context to the provided url, with the provided wait condition.
   *
   * @param {object=} options
   * @param {string} options.context
   *     Id of the browsing context to navigate.
   * @param {string} options.url
   *     Url for the navigation.
   * @param {WaitCondition=} options.wait
   *     Wait condition for the navigation, one of "none", "interactive", "complete".
   *
   * @returns {BrowsingContextNavigateResult}
   *     Navigation result.
   *
   * @throws {InvalidArgumentError}
   *     Raised if an argument is of an invalid type or value.
   * @throws {NoSuchFrameError}
   *     If the browsing context for context cannot be found.
   */
  async navigate(options = {}) {
    const { context: contextId, url, wait = WaitCondition.None } = options;

    lazy.assert.string(
      contextId,
      lazy.pprint`Expected "context" to be a string, got ${contextId}`
    );

    lazy.assert.string(
      url,
      lazy.pprint`Expected "url" to be string, got ${url}`
    );

    const waitConditions = Object.values(WaitCondition);
    if (!waitConditions.includes(wait)) {
      throw new lazy.error.InvalidArgumentError(
        `Expected "wait" to be one of ${waitConditions}, ` +
          lazy.pprint`got ${wait}`
      );
    }

    const context = this.#getBrowsingContext(contextId);

    // webProgress will be stable even if the context navigates, retrieve it
    // immediately before doing any asynchronous call.
    const webProgress = context.webProgress;

    const base = await this.messageHandler.handleCommand({
      moduleName: "browsingContext",
      commandName: "_getBaseURL",
      destination: {
        type: lazy.WindowGlobalMessageHandler.type,
        id: context.id,
      },
      retryOnAbort: true,
    });

    let targetURI;
    try {
      const baseURI = Services.io.newURI(base);
      targetURI = Services.io.newURI(url, null, baseURI);
    } catch (e) {
      throw new lazy.error.InvalidArgumentError(
        `Expected "url" to be a valid URL (${e.message})`
      );
    }

    return this.#awaitNavigation(
      webProgress,
      () => {
        context.loadURI(targetURI, {
          loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_IS_LINK,
          triggeringPrincipal:
            Services.scriptSecurityManager.getSystemPrincipal(),
          hasValidUserGestureActivation: true,
        });
      },
      {
        targetURI,
        wait,
      }
    );
  }

  /**
   * An object that holds the information about margins
   * for Webdriver BiDi browsingContext.print command.
   *
   * @typedef BrowsingContextPrintMarginParameters
   *
   * @property {number=} bottom
   *     Bottom margin in cm. Defaults to 1cm (~0.4 inches).
   * @property {number=} left
   *     Left margin in cm. Defaults to 1cm (~0.4 inches).
   * @property {number=} right
   *     Right margin in cm. Defaults to 1cm (~0.4 inches).
   * @property {number=} top
   *     Top margin in cm. Defaults to 1cm (~0.4 inches).
   */

  /**
   * An object that holds the information about paper size
   * for Webdriver BiDi browsingContext.print command.
   *
   * @typedef BrowsingContextPrintPageParameters
   *
   * @property {number=} height
   *     Paper height in cm. Defaults to US letter height (27.94cm / 11 inches).
   * @property {number=} width
   *     Paper width in cm. Defaults to US letter width (21.59cm / 8.5 inches).
   */

  /**
   * Used as return value for Webdriver BiDi browsingContext.print command.
   *
   * @typedef BrowsingContextPrintResult
   *
   * @property {string} data
   *     Base64 encoded PDF representing printed document.
   */

  /**
   * Creates a paginated PDF representation of a document
   * of the provided browsing context, and returns it
   * as a Base64-encoded string.
   *
   * @param {object=} options
   * @param {string} options.context
   *     Id of the browsing context.
   * @param {boolean=} options.background
   *     Whether or not to print background colors and images.
   *     Defaults to false, which prints without background graphics.
   * @param {BrowsingContextPrintMarginParameters=} options.margin
   *     Paper margins.
   * @param {('landscape'|'portrait')=} options.orientation
   *     Paper orientation. Defaults to 'portrait'.
   * @param {BrowsingContextPrintPageParameters=} options.page
   *     Paper size.
   * @param {Array<number|string>=} options.pageRanges
   *     Paper ranges to print, e.g., ['1-5', 8, '11-13'].
   *     Defaults to the empty array, which means print all pages.
   * @param {number=} options.scale
   *     Scale of the webpage rendering. Defaults to 1.0.
   * @param {boolean=} options.shrinkToFit
   *     Whether or not to override page size as defined by CSS.
   *     Defaults to true, in which case the content will be scaled
   *     to fit the paper size.
   *
   * @returns {BrowsingContextPrintResult}
   *
   * @throws {InvalidArgumentError}
   *     Raised if an argument is of an invalid type or value.
   * @throws {NoSuchFrameError}
   *     If the browsing context cannot be found.
   */
  async print(options = {}) {
    const {
      context: contextId,
      background,
      margin,
      orientation,
      page,
      pageRanges,
      scale,
      shrinkToFit,
    } = options;

    lazy.assert.string(
      contextId,
      lazy.pprint`Expected "context" to be a string, got ${contextId}`
    );
    const context = this.#getBrowsingContext(contextId);

    const settings = lazy.print.addDefaultSettings({
      background,
      margin,
      orientation,
      page,
      pageRanges,
      scale,
      shrinkToFit,
    });

    for (const prop of ["top", "bottom", "left", "right"]) {
      lazy.assert.positiveNumber(
        settings.margin[prop],
        `Expected "margin.${prop}" to be a positive number, ` +
          lazy.pprint`got ${settings.margin[prop]}`
      );
    }
    for (const prop of ["width", "height"]) {
      lazy.assert.positiveNumber(
        settings.page[prop],
        `Expected "page.${prop}" to be a positive number, ` +
          lazy.pprint`got ${settings.page[prop]}`
      );
    }
    lazy.assert.positiveNumber(
      settings.scale,
      `Expected "scale" to be a positive number, ` +
        lazy.pprint`got ${settings.scale}`
    );
    lazy.assert.that(
      scale =>
        scale >= lazy.print.minScaleValue && scale <= lazy.print.maxScaleValue,
      `scale ${settings.scale} is outside the range ${lazy.print.minScaleValue}-${lazy.print.maxScaleValue}`
    )(settings.scale);
    lazy.assert.boolean(
      settings.shrinkToFit,
      lazy.pprint`Expected "shrinkToFit" to be a boolean, got ${settings.shrinkToFit}`
    );
    lazy.assert.that(
      orientation => lazy.print.defaults.orientationValue.includes(orientation),
      `Expected "orientation" to be one of ${lazy.print.defaults.orientationValue}", ` +
        lazy.pprint`got {settings.orientation}`
    )(settings.orientation);
    lazy.assert.boolean(
      settings.background,
      lazy.pprint`Expected "background" to be a boolean, got ${settings.background}`
    );
    lazy.assert.array(
      settings.pageRanges,
      lazy.pprint`Expected "pageRanges" to be an array, got ${settings.pageRanges}`
    );

    const printSettings = await lazy.print.getPrintSettings(settings);
    const binaryString = await lazy.print.printToBinaryString(
      context,
      printSettings
    );

    return {
      data: btoa(binaryString),
    };
  }

  /**
   * Reload the given context's document, with the provided wait condition.
   *
   * @param {object=} options
   * @param {string} options.context
   *     Id of the browsing context to navigate.
   * @param {bool=} options.ignoreCache
   *     If true ignore the browser cache. [Not yet supported]
   * @param {WaitCondition=} options.wait
   *     Wait condition for the navigation, one of "none", "interactive", "complete".
   *
   * @returns {BrowsingContextNavigateResult}
   *     Navigation result.
   *
   * @throws {InvalidArgumentError}
   *     Raised if an argument is of an invalid type or value.
   * @throws {NoSuchFrameError}
   *     If the browsing context for context cannot be found.
   */
  async reload(options = {}) {
    const {
      context: contextId,
      ignoreCache,
      wait = WaitCondition.None,
    } = options;

    lazy.assert.string(
      contextId,
      lazy.pprint`Expected "context" to be a string, got ${contextId}`
    );

    if (typeof ignoreCache != "undefined") {
      throw new lazy.error.UnsupportedOperationError(
        `Argument "ignoreCache" is not supported yet.`
      );
    }

    const waitConditions = Object.values(WaitCondition);
    if (!waitConditions.includes(wait)) {
      throw new lazy.error.InvalidArgumentError(
        `Expected "wait" to be one of ${waitConditions}, ` +
          lazy.pprint`got ${wait}`
      );
    }

    const context = this.#getBrowsingContext(contextId);

    // webProgress will be stable even if the context navigates, retrieve it
    // immediately before doing any asynchronous call.
    const webProgress = context.webProgress;

    return this.#awaitNavigation(
      webProgress,
      () => {
        context.reload(Ci.nsIWebNavigation.LOAD_FLAGS_NONE);
      },
      { wait }
    );
  }

  /**
   * Set the top-level browsing context's viewport to a given dimension.
   *
   * @param {object=} options
   * @param {string} options.context
   *     Id of the browsing context.
   * @param {(number|null)=} options.devicePixelRatio
   *     A value to override device pixel ratio, or `null` to reset it to
   *     the original value. Different values will not cause the rendering to change,
   *     only image srcsets and media queries will be applied as if DPR is redefined.
   * @param {(Viewport|null)=} options.viewport
   *     Dimensions to set the viewport to, or `null` to reset it
   *     to the original dimensions.
   *
   * @throws {InvalidArgumentError}
   *     Raised if an argument is of an invalid type or value.
   * @throws {UnsupportedOperationError}
   *     Raised when the command is called on Android.
   */
  async setViewport(options = {}) {
    const { context: contextId, devicePixelRatio, viewport } = options;

    if (lazy.AppInfo.isAndroid) {
      // Bug 1840084: Add Android support for modifying the viewport.
      throw new lazy.error.UnsupportedOperationError(
        `Command not yet supported for ${lazy.AppInfo.name}`
      );
    }

    lazy.assert.string(
      contextId,
      lazy.pprint`Expected "context" to be a string, got ${contextId}`
    );

    const context = this.#getBrowsingContext(contextId);
    if (context.parent) {
      throw new lazy.error.InvalidArgumentError(
        `Browsing Context with id ${contextId} is not top-level`
      );
    }

    const browser = context.embedderElement;
    const currentHeight = browser.clientHeight;
    const currentWidth = browser.clientWidth;

    let targetHeight, targetWidth;
    if (viewport === undefined) {
      // Don't modify the viewport's size.
      targetHeight = currentHeight;
      targetWidth = currentWidth;
    } else if (viewport === null) {
      // Reset viewport to the original dimensions.
      targetHeight = browser.parentElement.clientHeight;
      targetWidth = browser.parentElement.clientWidth;

      browser.style.removeProperty("height");
      browser.style.removeProperty("width");
    } else {
      lazy.assert.object(
        viewport,
        lazy.pprint`Expected "viewport" to be an object, got ${viewport}`
      );

      const { height, width } = viewport;
      targetHeight = lazy.assert.positiveInteger(
        height,
        lazy.pprint`Expected viewport's "height" to be a positive integer, got ${height}`
      );
      targetWidth = lazy.assert.positiveInteger(
        width,
        lazy.pprint`Expected viewport's "width" to be a positive integer, got ${width}`
      );

      if (targetHeight > MAX_WINDOW_SIZE || targetWidth > MAX_WINDOW_SIZE) {
        throw new lazy.error.UnsupportedOperationError(
          `"width" or "height" cannot be larger than ${MAX_WINDOW_SIZE} px`
        );
      }

      browser.style.setProperty("height", targetHeight + "px");
      browser.style.setProperty("width", targetWidth + "px");
    }

    if (devicePixelRatio !== undefined) {
      if (devicePixelRatio !== null) {
        lazy.assert.number(
          devicePixelRatio,
          lazy.pprint`Expected "devicePixelRatio" to be a number or null, got ${devicePixelRatio}`
        );
        lazy.assert.that(
          devicePixelRatio => devicePixelRatio > 0,
          lazy.pprint`Expected "devicePixelRatio" to be greater than 0, got ${devicePixelRatio}`
        )(devicePixelRatio);

        context.overrideDPPX = devicePixelRatio;
      } else {
        // Will reset to use the global default scaling factor.
        context.overrideDPPX = 0;
      }
    }

    if (targetHeight !== currentHeight || targetWidth !== currentWidth) {
      // Wait until the viewport has been resized
      await this.messageHandler.forwardCommand({
        moduleName: "browsingContext",
        commandName: "_awaitViewportDimensions",
        destination: {
          type: lazy.WindowGlobalMessageHandler.type,
          id: context.id,
        },
        params: {
          height: targetHeight,
          width: targetWidth,
        },
      });
    }
  }

  /**
   * Traverses the history of a given context by a given delta.
   *
   * @param {object=} options
   * @param {string} options.context
   *     Id of the browsing context.
   * @param {number} options.delta
   *     The number of steps we have to traverse.
   *
   * @throws {InvalidArgumentError}
   *     Raised if an argument is of an invalid type or value.
   * @throws {NoSuchFrameException}
   *     When a context is not available.
   * @throws {NoSuchHistoryEntryError}
   *     When a requested history entry does not exist.
   */
  async traverseHistory(options = {}) {
    const { context: contextId, delta } = options;

    lazy.assert.string(
      contextId,
      lazy.pprint`Expected "context" to be a string, got ${contextId}`
    );

    const context = this.#getBrowsingContext(contextId);

    lazy.assert.integer(
      delta,
      lazy.pprint`Expected "delta" to be an integer, got ${delta}`
    );

    const sessionHistory = context.sessionHistory;
    const allSteps = sessionHistory.count;
    const currentIndex = sessionHistory.index;
    const targetIndex = currentIndex + delta;
    const validEntry = targetIndex >= 0 && targetIndex < allSteps;

    if (!validEntry) {
      throw new lazy.error.NoSuchHistoryEntryError(
        `History entry with delta ${delta} not found`
      );
    }

    context.goToIndex(targetIndex);

    // On some platforms the requested index isn't set immediately.
    await lazy.PollPromise(
      (resolve, reject) => {
        if (sessionHistory.index == targetIndex) {
          resolve();
        } else {
          reject();
        }
      },
      {
        errorMessage: `History was not updated for index "${targetIndex}"`,
        timeout: TIMEOUT_SET_HISTORY_INDEX * lazy.getTimeoutMultiplier(),
      }
    );
  }

  /**
   * Start and await a navigation on the provided BrowsingContext. Returns a
   * promise which resolves when the navigation is done according to the provided
   * navigation strategy.
   *
   * @param {WebProgress} webProgress
   *     The WebProgress instance to observe for this navigation.
   * @param {Function} startNavigationFn
   *     A callback that starts a navigation.
   * @param {object} options
   * @param {string=} options.targetURI
   *     The target URI for the navigation.
   * @param {WaitCondition} options.wait
   *     The WaitCondition to use to wait for the navigation.
   *
   * @returns {Promise<BrowsingContextNavigateResult>}
   *     A Promise that resolves to navigate results when the navigation is done.
   */
  async #awaitNavigation(webProgress, startNavigationFn, options) {
    const { targetURI, wait } = options;

    const context = webProgress.browsingContext;
    const browserId = context.browserId;

    const resolveWhenStarted = wait === WaitCondition.None;
    const listener = new lazy.ProgressListener(webProgress, {
      expectNavigation: true,
      navigationManager: this.messageHandler.navigationManager,
      resolveWhenStarted,
      targetURI,
      // In case the webprogress is already navigating, always wait for an
      // explicit start flag.
      waitForExplicitStart: true,
    });

    const onDocumentInteractive = (evtName, wrappedEvt) => {
      if (webProgress.browsingContext.id !== wrappedEvt.contextId) {
        // Ignore load events for unrelated browsing contexts.
        return;
      }

      if (wrappedEvt.readyState === "interactive") {
        listener.stopIfStarted();
      }
    };

    const contextDescriptor = {
      type: lazy.ContextDescriptorType.TopBrowsingContext,
      id: browserId,
    };

    // For the Interactive wait condition, resolve as soon as
    // the document becomes interactive.
    if (wait === WaitCondition.Interactive) {
      await this.messageHandler.eventsDispatcher.on(
        "browsingContext._documentInteractive",
        contextDescriptor,
        onDocumentInteractive
      );
    }

    const navigationId = lazy.registerNavigationId({
      contextDetails: { context: webProgress.browsingContext },
    });
    const navigated = listener.start(navigationId);

    try {
      await startNavigationFn();
      await navigated;

      let url;
      if (wait === WaitCondition.None) {
        // If wait condition is None, the navigation resolved before the current
        // context has navigated.
        url = listener.targetURI.spec;
      } else {
        url = listener.currentURI.spec;
      }

      return {
        navigation: navigationId,
        url,
      };
    } finally {
      if (listener.isStarted) {
        listener.stop();
      }

      if (wait === WaitCondition.Interactive) {
        await this.messageHandler.eventsDispatcher.off(
          "browsingContext._documentInteractive",
          contextDescriptor,
          onDocumentInteractive
        );
      }
    }
  }

  /**
   * Retrieves a browsing context based on its id.
   *
   * @param {number} contextId
   *     Id of the browsing context.
   * @returns {BrowsingContext=}
   *     The browsing context or null if <var>contextId</var> is null.
   * @throws {NoSuchFrameError}
   *     If the browsing context cannot be found.
   */
  #getBrowsingContext(contextId) {
    // The WebDriver BiDi specification expects null to be
    // returned if no browsing context id has been specified.
    if (contextId === null) {
      return null;
    }

    const context = lazy.TabManager.getBrowsingContextById(contextId);
    if (context === null) {
      throw new lazy.error.NoSuchFrameError(
        `Browsing Context with id ${contextId} not found`
      );
    }

    return context;
  }

  /**
   * Get the WebDriver BiDi browsing context information.
   *
   * @param {BrowsingContext} context
   *     The browsing context to get the information from.
   * @param {object=} options
   * @param {boolean=} options.isRoot
   *     Flag that indicates if this browsing context is the root of all the
   *     browsing contexts to be returned. Defaults to true.
   * @param {number=} options.maxDepth
   *     Depth of the browsing context tree to traverse. If not specified
   *     the whole tree is returned.
   * @returns {BrowsingContextInfo}
   *     The information about the browsing context.
   */
  #getBrowsingContextInfo(context, options = {}) {
    const { isRoot = true, maxDepth = null } = options;

    let children = null;
    if (maxDepth === null || maxDepth > 0) {
      children = context.children.map(context =>
        this.#getBrowsingContextInfo(context, {
          maxDepth: maxDepth === null ? maxDepth : maxDepth - 1,
          isRoot: false,
        })
      );
    }

    const userContext = lazy.UserContextManager.getIdByBrowsingContext(context);
    const originalOpener =
      context.crossGroupOpener !== null
        ? lazy.TabManager.getIdForBrowsingContext(context.crossGroupOpener)
        : null;
    const contextInfo = {
      children,
      context: lazy.TabManager.getIdForBrowsingContext(context),
      // TODO: Bug 1904641. If a browsing context was not tracked in TabManager,
      // because it was created and discarded before the WebDriver BiDi session was
      // started, we get undefined as id for this browsing context.
      // We should remove this condition, when we can provide a correct id here.
      originalOpener: originalOpener === undefined ? null : originalOpener,
      url: context.currentURI.spec,
      userContext,
    };

    if (isRoot) {
      // Only emit the parent id for the top-most browsing context.
      const parentId = lazy.TabManager.getIdForBrowsingContext(context.parent);
      contextInfo.parent = parentId;
    }

    return contextInfo;
  }

  #onContextAttached = async (eventName, data = {}) => {
    if (this.#subscribedEvents.has("browsingContext.contextCreated")) {
      const { browsingContext, why } = data;

      // Filter out top-level browsing contexts that are created because of a
      // cross-group navigation.
      if (why === "replace") {
        return;
      }

      // TODO: Bug 1852941. We should also filter out events which are emitted
      // for DevTools frames.

      // Filter out notifications for chrome context until support gets
      // added (bug 1722679).
      if (!browsingContext.webProgress) {
        return;
      }

      const browsingContextInfo = this.#getBrowsingContextInfo(
        browsingContext,
        {
          maxDepth: 0,
        }
      );

      this._emitEventForBrowsingContext(
        browsingContext.id,
        "browsingContext.contextCreated",
        browsingContextInfo
      );
    }
  };

  #onContextDiscarded = async (eventName, data = {}) => {
    if (this.#subscribedEvents.has("browsingContext.contextDestroyed")) {
      const { browsingContext, why } = data;

      // Filter out top-level browsing contexts that are destroyed because of a
      // cross-group navigation.
      if (why === "replace") {
        return;
      }

      // TODO: Bug 1852941. We should also filter out events which are emitted
      // for DevTools frames.

      // Filter out notifications for chrome context until support gets
      // added (bug 1722679).
      if (!browsingContext.webProgress) {
        return;
      }

      // If this event is for a child context whose top or parent context is also destroyed,
      // we don't need to send it, in this case the event for the top/parent context is enough.
      if (
        browsingContext.parent &&
        (browsingContext.top.isDiscarded || browsingContext.parent.isDiscarded)
      ) {
        return;
      }

      const browsingContextInfo = this.#getBrowsingContextInfo(
        browsingContext,
        {
          maxDepth: 0,
        }
      );

      this._emitEventForBrowsingContext(
        browsingContext.id,
        "browsingContext.contextDestroyed",
        browsingContextInfo
      );
    }
  };

  #onFragmentNavigated = async (eventName, data) => {
    const { navigationId, navigableId, url } = data;
    const context = this.#getBrowsingContext(navigableId);

    if (this.#subscribedEvents.has("browsingContext.fragmentNavigated")) {
      const browsingContextInfo = {
        context: navigableId,
        navigation: navigationId,
        timestamp: Date.now(),
        url,
      };
      this._emitEventForBrowsingContext(
        context.id,
        "browsingContext.fragmentNavigated",
        browsingContextInfo
      );
    }
  };

  #onPromptClosed = async (eventName, data) => {
    if (this.#subscribedEvents.has("browsingContext.userPromptClosed")) {
      const { contentBrowser, detail } = data;
      const contextId = lazy.TabManager.getIdForBrowser(contentBrowser);

      if (contextId === null) {
        return;
      }

      const params = {
        context: contextId,
        accepted: detail.accepted,
        type: detail.promptType,
        userText: detail.userText,
      };
      this._emitEventForBrowsingContext(
        contextId,
        "browsingContext.userPromptClosed",
        params
      );
    }
  };

  #onPromptOpened = async (eventName, data) => {
    if (this.#subscribedEvents.has("browsingContext.userPromptOpened")) {
      const { contentBrowser, prompt } = data;
      const type = prompt.promptType;

      // Do not send opened event for unsupported prompt types.
      if (!(type in UserPromptType)) {
        lazy.logger.trace(`Prompt type "${type}" not supported`);
        return;
      }

      const contextId = lazy.TabManager.getIdForBrowser(contentBrowser);

      const session = lazy.getWebDriverSessionById(
        this.messageHandler.sessionId
      );
      const handlerConfig = session.userPromptHandler.getPromptHandler(type);

      const eventPayload = {
        context: contextId,
        handler: handlerConfig.handler,
        message: await prompt.getText(),
        type,
      };

      if (type === "prompt") {
        eventPayload.defaultValue = await prompt.getInputText();
      }

      this._emitEventForBrowsingContext(
        contextId,
        "browsingContext.userPromptOpened",
        eventPayload
      );
    }
  };

  #onNavigationFailed = async (eventName, data) => {
    const { navigableId, navigationId, url, contextId } = data;

    if (this.#subscribedEvents.has("browsingContext.navigationFailed")) {
      const eventPayload = {
        context: navigableId,
        navigation: navigationId,
        timestamp: Date.now(),
        url,
      };
      this._emitEventForBrowsingContext(
        contextId,
        "browsingContext.navigationFailed",
        eventPayload
      );
    }
  };

  #onNavigationStarted = async (eventName, data) => {
    const { navigableId, navigationId, url } = data;
    const context = this.#getBrowsingContext(navigableId);

    if (this.#subscribedEvents.has("browsingContext.navigationStarted")) {
      const eventPayload = {
        context: navigableId,
        navigation: navigationId,
        timestamp: Date.now(),
        url,
      };
      this._emitEventForBrowsingContext(
        context.id,
        "browsingContext.navigationStarted",
        eventPayload
      );
    }
  };

  #onPageHideEvent = (name, eventPayload) => {
    const { context } = eventPayload;
    if (context.parent) {
      this.#onContextDiscarded("windowglobal-pagehide", {
        browsingContext: context,
      });
    }
  };

  #stopListeningToContextEvent(event) {
    this.#subscribedEvents.delete(event);

    const hasContextEvent =
      this.#subscribedEvents.has("browsingContext.contextCreated") ||
      this.#subscribedEvents.has("browsingContext.contextDestroyed");

    if (!hasContextEvent) {
      this.#contextListener.stopListening();
    }
  }

  #stopListeningToNavigationEvent(event) {
    this.#subscribedEvents.delete(event);

    const hasNavigationEvent =
      this.#subscribedEvents.has("browsingContext.fragmentNavigated") ||
      this.#subscribedEvents.has("browsingContext.navigationFailed") ||
      this.#subscribedEvents.has("browsingContext.navigationStarted");

    if (!hasNavigationEvent) {
      this.#navigationListener.stopListening();
    }
  }

  #stopListeningToPromptEvent(event) {
    this.#subscribedEvents.delete(event);

    const hasPromptEvent =
      this.#subscribedEvents.has("browsingContext.userPromptClosed") ||
      this.#subscribedEvents.has("browsingContext.userPromptOpened");

    if (!hasPromptEvent) {
      this.#promptListener.stopListening();
    }
  }

  #subscribeEvent(event) {
    switch (event) {
      case "browsingContext.contextCreated":
      case "browsingContext.contextDestroyed": {
        this.#contextListener.startListening();
        this.#subscribedEvents.add(event);
        break;
      }
      case "browsingContext.fragmentNavigated":
      case "browsingContext.navigationFailed":
      case "browsingContext.navigationStarted": {
        this.#navigationListener.startListening();
        this.#subscribedEvents.add(event);
        break;
      }
      case "browsingContext.userPromptClosed":
      case "browsingContext.userPromptOpened": {
        this.#promptListener.startListening();
        this.#subscribedEvents.add(event);
        break;
      }
    }
  }

  #unsubscribeEvent(event) {
    switch (event) {
      case "browsingContext.contextCreated":
      case "browsingContext.contextDestroyed": {
        this.#stopListeningToContextEvent(event);
        break;
      }
      case "browsingContext.fragmentNavigated":
      case "browsingContext.navigationFailed":
      case "browsingContext.navigationStarted": {
        this.#stopListeningToNavigationEvent(event);
        break;
      }
      case "browsingContext.userPromptClosed":
      case "browsingContext.userPromptOpened": {
        this.#stopListeningToPromptEvent(event);
        break;
      }
    }
  }

  #waitForVisibilityChange(browsingContext) {
    return this.messageHandler.forwardCommand({
      moduleName: "browsingContext",
      commandName: "_awaitVisibilityState",
      destination: {
        type: lazy.WindowGlobalMessageHandler.type,
        id: browsingContext.id,
      },
      params: {
        value: "hidden",
      },
      retryOnAbort: true,
    });
  }

  /**
   * Internal commands
   */

  _applySessionData(params) {
    // TODO: Bug 1775231. Move this logic to a shared module or an abstract
    // class.
    const { category } = params;
    if (category === "event") {
      const filteredSessionData = params.sessionData.filter(item =>
        this.messageHandler.matchesContext(item.contextDescriptor)
      );
      for (const event of this.#subscribedEvents.values()) {
        const hasSessionItem = filteredSessionData.some(
          item => item.value === event
        );
        // If there are no session items for this context, we should unsubscribe from the event.
        if (!hasSessionItem) {
          this.#unsubscribeEvent(event);
        }
      }

      // Subscribe to all events, which have an item in SessionData.
      for (const { value } of filteredSessionData) {
        this.#subscribeEvent(value);
      }
    }
  }

  static get supportedEvents() {
    return [
      "browsingContext.contextCreated",
      "browsingContext.contextDestroyed",
      "browsingContext.domContentLoaded",
      "browsingContext.fragmentNavigated",
      "browsingContext.load",
      "browsingContext.navigationFailed",
      "browsingContext.navigationStarted",
      "browsingContext.userPromptClosed",
      "browsingContext.userPromptOpened",
    ];
  }
}

export const browsingContext = BrowsingContextModule;
PK
!<4'%?chrome/remote/content/webdriver-bidi/modules/root/input.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { RootBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/RootBiDiModule.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
  WindowGlobalMessageHandler:
    "chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs",
});

class InputModule extends RootBiDiModule {
  destroy() {}

  async performActions(options = {}) {
    const { actions, context: contextId } = options;

    lazy.assert.string(
      contextId,
      lazy.pprint`Expected "context" to be a string, got ${contextId}`
    );

    const context = lazy.TabManager.getBrowsingContextById(contextId);
    if (!context) {
      throw new lazy.error.NoSuchFrameError(
        `Browsing context with id ${contextId} not found`
      );
    }

    // Bug 1821460: Fetch top-level browsing context.

    await this.messageHandler.forwardCommand({
      moduleName: "input",
      commandName: "performActions",
      destination: {
        type: lazy.WindowGlobalMessageHandler.type,
        id: context.id,
      },
      params: {
        actions,
      },
    });

    return {};
  }

  /**
   * Reset the input state in the provided browsing context.
   *
   * @param {object=} options
   * @param {string} options.context
   *     Id of the browsing context to reset the input state.
   *
   * @throws {InvalidArgumentError}
   *     If <var>context</var> is not valid type.
   * @throws {NoSuchFrameError}
   *     If the browsing context cannot be found.
   */
  async releaseActions(options = {}) {
    const { context: contextId } = options;

    lazy.assert.string(
      contextId,
      lazy.pprint`Expected "context" to be a string, got ${contextId}`
    );

    const context = lazy.TabManager.getBrowsingContextById(contextId);
    if (!context) {
      throw new lazy.error.NoSuchFrameError(
        `Browsing context with id ${contextId} not found`
      );
    }

    // Bug 1821460: Fetch top-level browsing context.

    await this.messageHandler.forwardCommand({
      moduleName: "input",
      commandName: "releaseActions",
      destination: {
        type: lazy.WindowGlobalMessageHandler.type,
        id: context.id,
      },
      params: {},
    });

    return {};
  }

  /**
   * Sets the file property of a given input element with type file to a set of file paths.
   *
   * @param {object=} options
   * @param {string} options.context
   *     Id of the browsing context to set the file property
   *     of a given input element.
   * @param {SharedReference} options.element
   *     A reference to a node, which is used as
   *     a target for setting files.
   * @param {Array<string>} options.files
   *     A list of file paths which should be set.
   *
   * @throws {InvalidArgumentError}
   *     Raised if an argument is of an invalid type or value.
   * @throws {NoSuchElementError}
   *     If the input element cannot be found.
   * @throws {NoSuchFrameError}
   *     If the browsing context cannot be found.
   * @throws {UnableToSetFileInputError}
   *     If the set of file paths was not set to the input element.
   */
  async setFiles(options = {}) {
    const { context: contextId, element, files } = options;

    lazy.assert.string(
      contextId,
      lazy.pprint`Expected "context" to be a string, got ${contextId}`
    );

    const context = lazy.TabManager.getBrowsingContextById(contextId);
    if (!context) {
      throw new lazy.error.NoSuchFrameError(
        `Browsing context with id ${contextId} not found`
      );
    }

    lazy.assert.array(
      files,
      lazy.pprint`Expected "files" to be an array, got ${files}`
    );

    for (const file of files) {
      lazy.assert.string(
        file,
        lazy.pprint`Expected an element of "files" to be a string, got ${file}`
      );
    }

    await this.messageHandler.forwardCommand({
      moduleName: "input",
      commandName: "setFiles",
      destination: {
        type: lazy.WindowGlobalMessageHandler.type,
        id: context.id,
      },
      params: { element, files },
    });
  }

  static get supportedEvents() {
    return [];
  }
}

export const input = InputModule;
PK
!<�k ���=chrome/remote/content/webdriver-bidi/modules/root/log.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { RootBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/RootBiDiModule.sys.mjs";

class LogModule extends RootBiDiModule {
  destroy() {}

  static get supportedEvents() {
    return ["log.entryAdded"];
  }
}

export const log = LogModule;
PK
!<և+�'�'�Achrome/remote/content/webdriver-bidi/modules/root/network.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { RootBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/RootBiDiModule.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  BeforeStopRequestListener:
    "chrome://remote/content/shared/listeners/BeforeStopRequestListener.sys.mjs",
  CacheBehavior: "chrome://remote/content/shared/NetworkCacheManager.sys.mjs",
  NetworkDecodedBodySizeMap:
    "chrome://remote/content/shared/NetworkDecodedBodySizeMap.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
  Log: "chrome://remote/content/shared/Log.sys.mjs",
  matchURLPattern:
    "chrome://remote/content/shared/webdriver/URLPattern.sys.mjs",
  NetworkListener:
    "chrome://remote/content/shared/listeners/NetworkListener.sys.mjs",
  parseChallengeHeader:
    "chrome://remote/content/shared/ChallengeHeaderParser.sys.mjs",
  parseURLPattern:
    "chrome://remote/content/shared/webdriver/URLPattern.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
  truncate: "chrome://remote/content/shared/Format.sys.mjs",
  updateCacheBehavior:
    "chrome://remote/content/shared/NetworkCacheManager.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", () =>
  lazy.Log.get(lazy.Log.TYPES.WEBDRIVER_BIDI)
);

/**
 * @typedef {object} AuthChallenge
 * @property {string} scheme
 * @property {string} realm
 */

/**
 * @typedef {object} AuthCredentials
 * @property {'password'} type
 * @property {string} username
 * @property {string} password
 */

/**
 * @typedef {object} BaseParameters
 * @property {string=} context
 * @property {Array<string>?} intercepts
 * @property {boolean} isBlocked
 * @property {Navigation=} navigation
 * @property {number} redirectCount
 * @property {RequestData} request
 * @property {number} timestamp
 */

/**
 * @typedef {object} BlockedRequest
 * @property {NetworkEventRecord} networkEventRecord
 * @property {InterceptPhase} phase
 */

/**
 * Enum of possible BytesValue types.
 *
 * @readonly
 * @enum {BytesValueType}
 */
export const BytesValueType = {
  Base64: "base64",
  String: "string",
};

/**
 * @typedef {object} BytesValue
 * @property {BytesValueType} type
 * @property {string} value
 */

/**
 * Enum of possible continueWithAuth actions.
 *
 * @readonly
 * @enum {ContinueWithAuthAction}
 */
const ContinueWithAuthAction = {
  Cancel: "cancel",
  Default: "default",
  ProvideCredentials: "provideCredentials",
};

/**
 * @typedef {object} Cookie
 * @property {string} domain
 * @property {number=} expires
 * @property {boolean} httpOnly
 * @property {string} name
 * @property {string} path
 * @property {SameSite} sameSite
 * @property {boolean} secure
 * @property {number} size
 * @property {BytesValue} value
 */

/**
 * @typedef {object} CookieHeader
 * @property {string} name
 * @property {BytesValue} value
 */

/**
 * @typedef {object} FetchTimingInfo
 * @property {number} timeOrigin
 * @property {number} requestTime
 * @property {number} redirectStart
 * @property {number} redirectEnd
 * @property {number} fetchStart
 * @property {number} dnsStart
 * @property {number} dnsEnd
 * @property {number} connectStart
 * @property {number} connectEnd
 * @property {number} tlsStart
 * @property {number} requestStart
 * @property {number} responseStart
 * @property {number} responseEnd
 */

/**
 * @typedef {object} Header
 * @property {string} name
 * @property {BytesValue} value
 */

/**
 * @typedef {string} InitiatorType
 */

/**
 * Enum of possible initiator types.
 *
 * @readonly
 * @enum {InitiatorType}
 */
const InitiatorType = {
  Other: "other",
  Parser: "parser",
  Preflight: "preflight",
  Script: "script",
};

/**
 * @typedef {object} Initiator
 * @property {InitiatorType} type
 * @property {number=} columnNumber
 * @property {number=} lineNumber
 * @property {string=} request
 * @property {StackTrace=} stackTrace
 */

/**
 * Enum of intercept phases.
 *
 * @readonly
 * @enum {InterceptPhase}
 */
const InterceptPhase = {
  AuthRequired: "authRequired",
  BeforeRequestSent: "beforeRequestSent",
  ResponseStarted: "responseStarted",
};

/**
 * @typedef {object} InterceptProperties
 * @property {Array<InterceptPhase>} phases
 * @property {Array<URLPattern>} urlPatterns
 */

/**
 * @typedef {object} RequestData
 * @property {number|null} bodySize
 *     Defaults to null.
 * @property {Array<Cookie>} cookies
 * @property {Array<Header>} headers
 * @property {number} headersSize
 * @property {string} method
 * @property {string} request
 * @property {FetchTimingInfo} timings
 * @property {string} url
 */

/**
 * @typedef {object} BeforeRequestSentParametersProperties
 * @property {Initiator} initiator
 */

/* eslint-disable jsdoc/valid-types */
/**
 * Parameters for the BeforeRequestSent event
 *
 * @typedef {BaseParameters & BeforeRequestSentParametersProperties} BeforeRequestSentParameters
 */
/* eslint-enable jsdoc/valid-types */

/**
 * @typedef {object} ResponseContent
 * @property {number|null} size
 *     Defaults to null.
 */

/**
 * @typedef {object} ResponseData
 * @property {string} url
 * @property {string} protocol
 * @property {number} status
 * @property {string} statusText
 * @property {boolean} fromCache
 * @property {Array<Header>} headers
 * @property {string} mimeType
 * @property {number} bytesReceived
 * @property {number|null} headersSize
 *     Defaults to null.
 * @property {number|null} bodySize
 *     Defaults to null.
 * @property {ResponseContent} content
 * @property {Array<AuthChallenge>=} authChallenges
 */

/**
 * @typedef {object} ResponseStartedParametersProperties
 * @property {ResponseData} response
 */

/* eslint-disable jsdoc/valid-types */
/**
 * Parameters for the ResponseStarted event
 *
 * @typedef {BaseParameters & ResponseStartedParametersProperties} ResponseStartedParameters
 */
/* eslint-enable jsdoc/valid-types */

/**
 * @typedef {object} ResponseCompletedParametersProperties
 * @property {ResponseData} response
 */

/**
 * Enum of possible sameSite values.
 *
 * @readonly
 * @enum {SameSite}
 */
const SameSite = {
  Lax: "lax",
  None: "none",
  Strict: "strict",
};

/**
 * @typedef {object} SetCookieHeader
 * @property {string} name
 * @property {BytesValue} value
 * @property {string=} domain
 * @property {boolean=} httpOnly
 * @property {string=} expiry
 * @property {number=} maxAge
 * @property {string=} path
 * @property {SameSite=} sameSite
 * @property {boolean=} secure
 */

/**
 * @typedef {object} URLPatternPattern
 * @property {'pattern'} type
 * @property {string=} protocol
 * @property {string=} hostname
 * @property {string=} port
 * @property {string=} pathname
 * @property {string=} search
 */

/**
 * @typedef {object} URLPatternString
 * @property {'string'} type
 * @property {string} pattern
 */

/**
 * @typedef {(URLPatternPattern|URLPatternString)} URLPattern
 */

/* eslint-disable jsdoc/valid-types */
/**
 * Parameters for the ResponseCompleted event
 *
 * @typedef {BaseParameters & ResponseCompletedParametersProperties} ResponseCompletedParameters
 */
/* eslint-enable jsdoc/valid-types */

// @see https://searchfox.org/mozilla-central/rev/527d691a542ccc0f333e36689bd665cb000360b2/netwerk/protocol/http/HttpBaseChannel.cpp#2083-2088
const IMMUTABLE_RESPONSE_HEADERS = [
  "content-encoding",
  "content-length",
  "content-type",
  "trailer",
  "transfer-encoding",
];

class NetworkModule extends RootBiDiModule {
  #beforeStopRequestListener;
  #blockedRequests;
  #decodedBodySizeMap;
  #interceptMap;
  #networkListener;
  #subscribedEvents;

  constructor(messageHandler) {
    super(messageHandler);

    // Map of request id to BlockedRequest
    this.#blockedRequests = new Map();

    // Map of intercept id to InterceptProperties
    this.#interceptMap = new Map();

    // Set of event names which have active subscriptions
    this.#subscribedEvents = new Set();

    this.#decodedBodySizeMap = new lazy.NetworkDecodedBodySizeMap();

    this.#networkListener = new lazy.NetworkListener(
      this.messageHandler.navigationManager,
      this.#decodedBodySizeMap
    );
    this.#networkListener.on("auth-required", this.#onAuthRequired);
    this.#networkListener.on("before-request-sent", this.#onBeforeRequestSent);
    this.#networkListener.on("fetch-error", this.#onFetchError);
    this.#networkListener.on("response-completed", this.#onResponseEvent);
    this.#networkListener.on("response-started", this.#onResponseEvent);

    this.#beforeStopRequestListener = new lazy.BeforeStopRequestListener();
    this.#beforeStopRequestListener.on(
      "beforeStopRequest",
      this.#onBeforeStopRequest
    );
  }

  destroy() {
    this.#networkListener.off("auth-required", this.#onAuthRequired);
    this.#networkListener.off("before-request-sent", this.#onBeforeRequestSent);
    this.#networkListener.off("fetch-error", this.#onFetchError);
    this.#networkListener.off("response-completed", this.#onResponseEvent);
    this.#networkListener.off("response-started", this.#onResponseEvent);
    this.#networkListener.destroy();

    this.#beforeStopRequestListener.off(
      "beforeStopRequest",
      this.#onBeforeStopRequest
    );
    this.#beforeStopRequestListener.destroy();

    this.#decodedBodySizeMap.destroy();

    this.#decodedBodySizeMap = null;
    this.#blockedRequests = null;
    this.#interceptMap = null;
    this.#subscribedEvents = null;
  }

  /**
   * Adds a network intercept, which allows to intercept and modify network
   * requests and responses.
   *
   * The network intercept will be created for the provided phases
   * (InterceptPhase) and for specific url patterns. When a network event
   * corresponding to an intercept phase has a URL which matches any url pattern
   * of any intercept, the request will be suspended.
   *
   * @param {object=} options
   * @param {Array<string>=} options.contexts
   *     The list of browsing context ids where this intercept should be used.
   *     Optional, defaults to null.
   * @param {Array<InterceptPhase>} options.phases
   *     The phases where this intercept should be checked.
   * @param {Array<URLPattern>=} options.urlPatterns
   *     The URL patterns for this intercept. Optional, defaults to empty array.
   *
   * @returns {object}
   *     An object with the following property:
   *     - intercept {string} The unique id of the network intercept.
   *
   * @throws {InvalidArgumentError}
   *     Raised if an argument is of an invalid type or value.
   */
  addIntercept(options = {}) {
    const { contexts = null, phases, urlPatterns = [] } = options;

    if (contexts !== null) {
      lazy.assert.array(
        contexts,
        `Expected "contexts" to be an array, got ${contexts}`
      );

      if (!options.contexts.length) {
        throw new lazy.error.InvalidArgumentError(
          `Expected "contexts" to contain at least one item, got an empty array`
        );
      }

      for (const contextId of contexts) {
        lazy.assert.string(
          contextId,
          `Expected elements of "contexts" to be a string, got ${contextId}`
        );
        const context = this.#getBrowsingContext(contextId);

        if (context.parent) {
          throw new lazy.error.InvalidArgumentError(
            `Context with id ${contextId} is not a top-level browsing context`
          );
        }
      }
    }

    lazy.assert.array(
      phases,
      `Expected "phases" to be an array, got ${phases}`
    );

    if (!options.phases.length) {
      throw new lazy.error.InvalidArgumentError(
        `Expected "phases" to contain at least one phase, got an empty array`
      );
    }

    const supportedInterceptPhases = Object.values(InterceptPhase);
    for (const phase of phases) {
      if (!supportedInterceptPhases.includes(phase)) {
        throw new lazy.error.InvalidArgumentError(
          `Expected "phases" values to be one of ${supportedInterceptPhases}, got ${phase}`
        );
      }
    }

    lazy.assert.array(
      urlPatterns,
      `Expected "urlPatterns" to be an array, got ${urlPatterns}`
    );

    const parsedPatterns = urlPatterns.map(urlPattern =>
      lazy.parseURLPattern(urlPattern)
    );

    const interceptId = lazy.generateUUID();
    this.#interceptMap.set(interceptId, {
      contexts,
      phases,
      urlPatterns: parsedPatterns,
    });

    return {
      intercept: interceptId,
    };
  }

  /**
   * Continues a request that is blocked by a network intercept at the
   * beforeRequestSent phase.
   *
   * @param {object=} options
   * @param {string} options.request
   *     The id of the blocked request that should be continued.
   * @param {BytesValue=} options.body
   *     Optional BytesValue to replace the body of the request.
   * @param {Array<CookieHeader>=} options.cookies
   *     Optional array of cookie header values to replace the cookie header of
   *     the request.
   * @param {Array<Header>=} options.headers
   *     Optional array of headers to replace the headers of the request.
   *     request.
   * @param {string=} options.method
   *     Optional string to replace the method of the request.
   * @param {string=} options.url [unsupported]
   *     Optional string to replace the url of the request. If the provided url
   *     is not a valid URL, an InvalidArgumentError will be thrown.
   *     Support will be added in https://bugzilla.mozilla.org/show_bug.cgi?id=1898158
   *
   * @throws {InvalidArgumentError}
   *     Raised if an argument is of an invalid type or value.
   * @throws {NoSuchRequestError}
   *     Raised if the request id does not match any request in the blocked
   *     requests map.
   */
  async continueRequest(options = {}) {
    const {
      body = null,
      cookies = null,
      headers = null,
      method = null,
      url = null,
      request: requestId,
    } = options;

    lazy.assert.string(
      requestId,
      `Expected "request" to be a string, got ${requestId}`
    );

    if (body !== null) {
      this.#assertBytesValue(
        body,
        lazy.truncate`Expected "body" to be a network.BytesValue, got ${body}`
      );
    }

    if (cookies !== null) {
      lazy.assert.array(
        cookies,
        `Expected "cookies" to be an array got ${cookies}`
      );

      for (const cookie of cookies) {
        this.#assertHeader(
          cookie,
          `Expected values in "cookies" to be network.CookieHeader, got ${cookie}`
        );
      }
    }

    let deserializedHeaders = [];
    if (headers !== null) {
      deserializedHeaders = this.#deserializeHeaders(headers);
    }

    if (method !== null) {
      lazy.assert.string(
        method,
        `Expected "method" to be a string, got ${method}`
      );
      lazy.assert.that(
        value => this.#isValidHttpToken(value),
        `Expected "method" to be a valid HTTP token, got ${method}`
      )(method);
    }

    if (url !== null) {
      lazy.assert.string(url, `Expected "url" to be a string, got ${url}`);

      throw new lazy.error.UnsupportedOperationError(
        `"url" not supported yet in network.continueRequest`
      );
    }

    if (!this.#blockedRequests.has(requestId)) {
      throw new lazy.error.NoSuchRequestError(
        `Blocked request with id ${requestId} not found`
      );
    }

    const { phase, request, resolveBlockedEvent } =
      this.#blockedRequests.get(requestId);

    if (phase !== InterceptPhase.BeforeRequestSent) {
      throw new lazy.error.InvalidArgumentError(
        `Expected blocked request to be in "beforeRequestSent" phase, got ${phase}`
      );
    }

    if (method !== null) {
      request.setRequestMethod(method);
    }

    if (headers !== null) {
      // Delete all existing request headers.
      request.getHeadersList().forEach(([name]) => {
        request.clearRequestHeader(name);
      });

      // Set all headers specified in the headers parameter.
      for (const [name, value] of deserializedHeaders) {
        request.setRequestHeader(name, value, { merge: true });
      }
    }

    if (cookies !== null) {
      let cookieHeader = "";
      for (const cookie of cookies) {
        if (cookieHeader != "") {
          cookieHeader += ";";
        }
        cookieHeader += this.#serializeCookieHeader(cookie);
      }

      let foundCookieHeader = false;
      const requestHeaders = request.getHeadersList();
      for (const [name] of requestHeaders) {
        if (name.toLowerCase() == "cookie") {
          // If there is already a cookie header, use merge: false to override
          // the value.
          request.setRequestHeader(name, cookieHeader, { merge: false });
          foundCookieHeader = true;
          break;
        }
      }

      if (!foundCookieHeader) {
        request.setRequestHeader("Cookie", cookieHeader, { merge: false });
      }
    }

    if (body !== null) {
      const value = deserializeBytesValue(body);
      request.setRequestBody(value);
    }

    request.wrappedChannel.resume();

    resolveBlockedEvent();
  }

  /**
   * Continues a response that is blocked by a network intercept at the
   * responseStarted or authRequired phase.
   *
   * @param {object=} options
   * @param {string} options.request
   *     The id of the blocked request that should be continued.
   * @param {Array<SetCookieHeader>=} options.cookies [unsupported]
   *     Optional array of set-cookie header values to replace the set-cookie
   *     headers of the response.
   * @param {AuthCredentials=} options.credentials
   *     Optional AuthCredentials to use.
   * @param {Array<Header>=} options.headers [unsupported]
   *     Optional array of header values to replace the headers of the response.
   * @param {string=} options.reasonPhrase [unsupported]
   *     Optional string to replace the status message of the response.
   * @param {number=} options.statusCode [unsupported]
   *     Optional number to replace the status code of the response.
   *
   * @throws {InvalidArgumentError}
   *     Raised if an argument is of an invalid type or value.
   * @throws {NoSuchRequestError}
   *     Raised if the request id does not match any request in the blocked
   *     requests map.
   */
  async continueResponse(options = {}) {
    const {
      cookies = null,
      credentials = null,
      headers = null,
      reasonPhrase = null,
      request: requestId,
      statusCode = null,
    } = options;

    lazy.assert.string(
      requestId,
      `Expected "request" to be a string, got ${requestId}`
    );

    if (cookies !== null) {
      lazy.assert.array(
        cookies,
        `Expected "cookies" to be an array got ${cookies}`
      );

      for (const cookie of cookies) {
        this.#assertSetCookieHeader(cookie);
      }
    }

    if (credentials !== null) {
      this.#assertAuthCredentials(credentials);
    }

    let deserializedHeaders = [];
    if (headers !== null) {
      // For existing responses, are unable to update some response headers,
      // so we skip them for the time being and log a warning.
      // Bug 1914351 should remove this limitation.
      deserializedHeaders = this.#deserializeHeaders(headers).filter(
        ([name]) => {
          if (IMMUTABLE_RESPONSE_HEADERS.includes(name.toLowerCase())) {
            lazy.logger.warn(
              `network.continueResponse cannot currently modify the header "${name}", skipping (see Bug 1914351).`
            );
            return false;
          }
          return true;
        }
      );
    }

    if (reasonPhrase !== null) {
      lazy.assert.string(
        reasonPhrase,
        `Expected "reasonPhrase" to be a string, got ${reasonPhrase}`
      );
    }

    if (statusCode !== null) {
      lazy.assert.positiveInteger(
        statusCode,
        `Expected "statusCode" to be a positive integer, got ${statusCode}`
      );
    }

    if (!this.#blockedRequests.has(requestId)) {
      throw new lazy.error.NoSuchRequestError(
        `Blocked request with id ${requestId} not found`
      );
    }

    const { authCallbacks, phase, request, resolveBlockedEvent, response } =
      this.#blockedRequests.get(requestId);

    if (headers !== null) {
      // Delete all existing response headers.
      response
        .getHeadersList()
        .filter(
          ([name]) =>
            // All headers in IMMUTABLE_RESPONSE_HEADERS cannot be changed and
            // will lead to a NS_ERROR_ILLEGAL_VALUE error.
            // Bug 1914351 should remove this limitation.
            !IMMUTABLE_RESPONSE_HEADERS.includes(name.toLowerCase())
        )
        .forEach(([name]) => response.clearResponseHeader(name));

      for (const [name, value] of deserializedHeaders) {
        response.setResponseHeader(name, value, { merge: true });
      }
    }

    if (cookies !== null) {
      for (const cookie of cookies) {
        const headerValue = this.#serializeSetCookieHeader(cookie);
        response.setResponseHeader("Set-Cookie", headerValue, { merge: true });
      }
    }

    if (statusCode !== null || reasonPhrase !== null) {
      response.setResponseStatus({
        status: statusCode,
        statusText: reasonPhrase,
      });
    }

    if (
      phase !== InterceptPhase.ResponseStarted &&
      phase !== InterceptPhase.AuthRequired
    ) {
      throw new lazy.error.InvalidArgumentError(
        `Expected blocked request to be in "responseStarted" or "authRequired" phase, got ${phase}`
      );
    }

    if (phase === InterceptPhase.AuthRequired) {
      // Requests blocked in the AuthRequired phase should be resumed using
      // authCallbacks.
      if (credentials !== null) {
        await authCallbacks.provideAuthCredentials(
          credentials.username,
          credentials.password
        );
      } else {
        await authCallbacks.provideAuthCredentials();
      }
    } else {
      request.wrappedChannel.resume();
    }

    resolveBlockedEvent();
  }

  /**
   * Continues a response that is blocked by a network intercept at the
   * authRequired phase.
   *
   * @param {object=} options
   * @param {string} options.request
   *     The id of the blocked request that should be continued.
   * @param {string} options.action
   *     The continueWithAuth action, one of ContinueWithAuthAction.
   * @param {AuthCredentials=} options.credentials
   *     The credentials to use for the ContinueWithAuthAction.ProvideCredentials
   *     action.
   *
   * @throws {InvalidArgumentError}
   *     Raised if an argument is of an invalid type or value.
   * @throws {NoSuchRequestError}
   *     Raised if the request id does not match any request in the blocked
   *     requests map.
   */
  async continueWithAuth(options = {}) {
    const { action, credentials, request: requestId } = options;

    lazy.assert.string(
      requestId,
      `Expected "request" to be a string, got ${requestId}`
    );

    if (!Object.values(ContinueWithAuthAction).includes(action)) {
      throw new lazy.error.InvalidArgumentError(
        `Expected "action" to be one of ${Object.values(
          ContinueWithAuthAction
        )} got ${action}`
      );
    }

    if (action == ContinueWithAuthAction.ProvideCredentials) {
      this.#assertAuthCredentials(credentials);
    }

    if (!this.#blockedRequests.has(requestId)) {
      throw new lazy.error.NoSuchRequestError(
        `Blocked request with id ${requestId} not found`
      );
    }

    const { authCallbacks, phase, resolveBlockedEvent } =
      this.#blockedRequests.get(requestId);

    if (phase !== InterceptPhase.AuthRequired) {
      throw new lazy.error.InvalidArgumentError(
        `Expected blocked request to be in "authRequired" phase, got ${phase}`
      );
    }

    switch (action) {
      case ContinueWithAuthAction.Cancel: {
        authCallbacks.cancelAuthPrompt();
        break;
      }
      case ContinueWithAuthAction.Default: {
        authCallbacks.forwardAuthPrompt();
        break;
      }
      case ContinueWithAuthAction.ProvideCredentials: {
        await authCallbacks.provideAuthCredentials(
          credentials.username,
          credentials.password
        );

        break;
      }
    }

    resolveBlockedEvent();
  }

  /**
   * Fails a request that is blocked by a network intercept.
   *
   * @param {object=} options
   * @param {string} options.request
   *     The id of the blocked request that should be continued.
   *
   * @throws {InvalidArgumentError}
   *     Raised if an argument is of an invalid type or value.
   * @throws {NoSuchRequestError}
   *     Raised if the request id does not match any request in the blocked
   *     requests map.
   */
  async failRequest(options = {}) {
    const { request: requestId } = options;

    lazy.assert.string(
      requestId,
      `Expected "request" to be a string, got ${requestId}`
    );

    if (!this.#blockedRequests.has(requestId)) {
      throw new lazy.error.NoSuchRequestError(
        `Blocked request with id ${requestId} not found`
      );
    }

    const { phase, request, resolveBlockedEvent } =
      this.#blockedRequests.get(requestId);

    if (phase === InterceptPhase.AuthRequired) {
      throw new lazy.error.InvalidArgumentError(
        `Expected blocked request not to be in "authRequired" phase`
      );
    }

    request.wrappedChannel.resume();
    request.wrappedChannel.cancel(
      Cr.NS_ERROR_ABORT,
      Ci.nsILoadInfo.BLOCKING_REASON_WEBDRIVER_BIDI
    );

    resolveBlockedEvent();
  }

  /**
   * Continues a request that’s blocked by a network intercept, by providing a
   * complete response.
   *
   * @param {object=} options
   * @param {string} options.request
   *     The id of the blocked request for which the response should be
   *     provided.
   * @param {BytesValue=} options.body
   *     Optional BytesValue to replace the body of the response.
   *     For now, only supported for requests blocked in beforeRequestSent.
   * @param {Array<SetCookieHeader>=} options.cookies
   *     Optional array of set-cookie header values to use for the provided
   *     response.
   *     For now, only supported for requests blocked in beforeRequestSent.
   * @param {Array<Header>=} options.headers
   *     Optional array of header values to use for the provided
   *     response.
   *     For now, only supported for requests blocked in beforeRequestSent.
   * @param {string=} options.reasonPhrase
   *     Optional string to use as the status message for the provided response.
   *     For now, only supported for requests blocked in beforeRequestSent.
   * @param {number=} options.statusCode
   *     Optional number to use as the status code for the provided response.
   *     For now, only supported for requests blocked in beforeRequestSent.
   *
   * @throws {InvalidArgumentError}
   *     Raised if an argument is of an invalid type or value.
   * @throws {NoSuchRequestError}
   *     Raised if the request id does not match any request in the blocked
   *     requests map.
   */
  async provideResponse(options = {}) {
    const {
      body = null,
      cookies = null,
      headers = null,
      reasonPhrase = null,
      request: requestId,
      statusCode = null,
    } = options;

    lazy.assert.string(
      requestId,
      `Expected "request" to be a string, got ${requestId}`
    );

    if (body !== null) {
      this.#assertBytesValue(
        body,
        `Expected "body" to be a network.BytesValue, got ${body}`
      );
    }

    if (cookies !== null) {
      lazy.assert.array(
        cookies,
        `Expected "cookies" to be an array got ${cookies}`
      );

      for (const cookie of cookies) {
        this.#assertSetCookieHeader(cookie);
      }
    }

    let deserializedHeaders = [];
    if (headers !== null) {
      deserializedHeaders = this.#deserializeHeaders(headers);
    }

    if (reasonPhrase !== null) {
      lazy.assert.string(
        reasonPhrase,
        `Expected "reasonPhrase" to be a string, got ${reasonPhrase}`
      );
    }

    if (statusCode !== null) {
      lazy.assert.positiveInteger(
        statusCode,
        `Expected "statusCode" to be a positive integer, got ${statusCode}`
      );
    }

    if (!this.#blockedRequests.has(requestId)) {
      throw new lazy.error.NoSuchRequestError(
        `Blocked request with id ${requestId} not found`
      );
    }

    const { authCallbacks, phase, request, resolveBlockedEvent } =
      this.#blockedRequests.get(requestId);

    // Handle optional arguments for the beforeRequestSent phase.
    // TODO: Support optional arguments in all phases, see Bug 1901055.
    if (phase === InterceptPhase.BeforeRequestSent) {
      // Create a new response.
      const replacedHttpResponse = Cc[
        "@mozilla.org/network/replaced-http-response;1"
      ].createInstance(Ci.nsIReplacedHttpResponse);

      if (statusCode !== null) {
        replacedHttpResponse.responseStatus = statusCode;
      }

      if (reasonPhrase !== null) {
        replacedHttpResponse.responseStatusText = reasonPhrase;
      }

      if (body !== null) {
        replacedHttpResponse.responseBody = deserializeBytesValue(body);
      }

      if (headers !== null) {
        for (const [name, value] of deserializedHeaders) {
          replacedHttpResponse.setResponseHeader(name, value, true);
        }
      }

      if (cookies !== null) {
        for (const cookie of cookies) {
          const headerValue = this.#serializeSetCookieHeader(cookie);
          replacedHttpResponse.setResponseHeader(
            "Set-Cookie",
            headerValue,
            true
          );
        }
      }

      request.setResponseOverride(replacedHttpResponse);
      request.wrappedChannel.resume();
    } else {
      if (body !== null) {
        throw new lazy.error.UnsupportedOperationError(
          `The "body" parameter is only supported for the beforeRequestSent phase at the moment`
        );
      }

      if (cookies !== null) {
        throw new lazy.error.UnsupportedOperationError(
          `The "cookies" parameter is only supported for the beforeRequestSent phase at the moment`
        );
      }

      if (headers !== null) {
        throw new lazy.error.UnsupportedOperationError(
          `The "headers" parameter is only supported for the beforeRequestSent phase at the moment`
        );
      }

      if (reasonPhrase !== null) {
        throw new lazy.error.UnsupportedOperationError(
          `The "reasonPhrase" parameter is only supported for the beforeRequestSent phase at the moment`
        );
      }

      if (statusCode !== null) {
        throw new lazy.error.UnsupportedOperationError(
          `The "statusCode" parameter is only supported for the beforeRequestSent phase at the moment`
        );
      }

      if (phase === InterceptPhase.AuthRequired) {
        // AuthRequired with no optional argument, resume the authentication.
        await authCallbacks.provideAuthCredentials();
      } else {
        // Any phase other than AuthRequired with no optional argument, resume the
        // request.
        request.wrappedChannel.resume();
      }
    }

    resolveBlockedEvent();
  }

  /**
   * Removes an existing network intercept.
   *
   * @param {object=} options
   * @param {string} options.intercept
   *     The id of the intercept to remove.
   *
   * @throws {InvalidArgumentError}
   *     Raised if an argument is of an invalid type or value.
   * @throws {NoSuchInterceptError}
   *     Raised if the intercept id could not be found in the internal intercept
   *     map.
   */
  removeIntercept(options = {}) {
    const { intercept } = options;

    lazy.assert.string(
      intercept,
      `Expected "intercept" to be a string, got ${intercept}`
    );

    if (!this.#interceptMap.has(intercept)) {
      throw new lazy.error.NoSuchInterceptError(
        `Network intercept with id ${intercept} not found`
      );
    }

    this.#interceptMap.delete(intercept);
  }

  /**
   * Configures the network cache behavior for certain requests.
   *
   * @param {object=} options
   * @param {CacheBehavior} options.cacheBehavior
   *     An enum value to set the network cache behavior.
   * @param {Array<string>=} options.contexts
   *     The list of browsing context ids where the network cache
   *     behavior should be updated.
   *
   * @throws {InvalidArgumentError}
   *     Raised if an argument is of an invalid type or value.
   * @throws {NoSuchFrameError}
   *     If the browsing context cannot be found.
   */
  setCacheBehavior(options = {}) {
    const { cacheBehavior: behavior, contexts: contextIds = null } = options;

    if (!Object.values(lazy.CacheBehavior).includes(behavior)) {
      throw new lazy.error.InvalidArgumentError(
        `Expected "cacheBehavior" to be one of ${Object.values(
          lazy.CacheBehavior
        )}` + lazy.pprint` got ${behavior}`
      );
    }

    if (contextIds === null) {
      // Set the default behavior if no specific context is specified.
      lazy.updateCacheBehavior(behavior);
      return;
    }

    lazy.assert.array(
      contextIds,
      lazy.pprint`Expected "contexts" to be an array, got ${contextIds}`
    );

    if (!contextIds.length) {
      throw new lazy.error.InvalidArgumentError(
        'Expected "contexts" to contain at least one item, got an empty array'
      );
    }

    const contexts = new Set();
    for (const contextId of contextIds) {
      lazy.assert.string(
        contextId,
        lazy.pprint`Expected elements of "contexts" to be a string, got ${contextId}`
      );
      const context = this.#getBrowsingContext(contextId);

      if (context.parent) {
        throw new lazy.error.InvalidArgumentError(
          lazy.pprint`Context with id ${contextId} is not a top-level browsing context`
        );
      }

      contexts.add(context);
    }

    lazy.updateCacheBehavior(behavior, contexts);
  }

  /**
   * Add a new request in the blockedRequests map.
   *
   * @param {string} requestId
   *     The request id.
   * @param {InterceptPhase} phase
   *     The phase where the request is blocked.
   * @param {object=} options
   * @param {object=} options.authCallbacks
   *     Only defined for requests blocked in the authRequired phase.
   *     Provides callbacks to handle the authentication.
   * @param {nsIChannel=} options.requestChannel
   *     The request channel.
   * @param {nsIChannel=} options.responseChannel
   *     The response channel.
   */
  #addBlockedRequest(requestId, phase, options = {}) {
    const { authCallbacks, request, response } = options;
    const { promise: blockedEventPromise, resolve: resolveBlockedEvent } =
      Promise.withResolvers();

    this.#blockedRequests.set(requestId, {
      authCallbacks,
      request,
      response,
      resolveBlockedEvent,
      phase,
    });

    blockedEventPromise.finally(() => {
      this.#blockedRequests.delete(requestId);
    });
  }

  #assertAuthCredentials(credentials) {
    lazy.assert.object(
      credentials,
      `Expected "credentials" to be an object, got ${credentials}`
    );

    if (credentials.type !== "password") {
      throw new lazy.error.InvalidArgumentError(
        `Expected credentials "type" to be "password" got ${credentials.type}`
      );
    }

    lazy.assert.string(
      credentials.username,
      `Expected credentials "username" to be a string, got ${credentials.username}`
    );
    lazy.assert.string(
      credentials.password,
      `Expected credentials "password" to be a string, got ${credentials.password}`
    );
  }

  #assertBytesValue(obj, msg) {
    lazy.assert.object(obj, msg);
    lazy.assert.string(obj.value, msg);
    lazy.assert.in(obj.type, Object.values(BytesValueType), msg);
  }

  #assertHeader(value, msg) {
    lazy.assert.object(value, msg);
    lazy.assert.string(value.name, msg);
    this.#assertBytesValue(value.value, msg);
  }

  #assertSetCookieHeader(setCookieHeader) {
    lazy.assert.object(
      setCookieHeader,
      `Expected set-cookie header to be an object, got ${setCookieHeader}`
    );

    const {
      name,
      value,
      domain = null,
      httpOnly = null,
      expiry = null,
      maxAge = null,
      path = null,
      sameSite = null,
      secure = null,
    } = setCookieHeader;

    lazy.assert.string(
      name,
      `Expected set-cookie header "name" to be a string, got ${name}`
    );

    this.#assertBytesValue(
      value,
      `Expected set-cookie header "value" to be a BytesValue, got ${name}`
    );

    if (domain !== null) {
      lazy.assert.string(
        domain,
        `Expected set-cookie header "domain" to be a string, got ${domain}`
      );
    }
    if (httpOnly !== null) {
      lazy.assert.boolean(
        httpOnly,
        `Expected set-cookie header "httpOnly" to be a boolean, got ${httpOnly}`
      );
    }
    if (expiry !== null) {
      lazy.assert.string(
        expiry,
        `Expected set-cookie header "expiry" to be a string, got ${expiry}`
      );
    }
    if (maxAge !== null) {
      lazy.assert.integer(
        maxAge,
        `Expected set-cookie header "maxAge" to be an integer, got ${maxAge}`
      );
    }
    if (path !== null) {
      lazy.assert.string(
        path,
        `Expected set-cookie header "path" to be a string, got ${path}`
      );
    }
    if (sameSite !== null) {
      lazy.assert.in(
        sameSite,
        Object.values(SameSite),
        `Expected set-cookie header "sameSite" to be one of ${Object.values(
          SameSite
        )}, got ${sameSite}`
      );
    }
    if (secure !== null) {
      lazy.assert.boolean(
        secure,
        `Expected set-cookie header "secure" to be a boolean, got ${secure}`
      );
    }
  }

  #deserializeHeader(protocolHeader) {
    const name = protocolHeader.name;
    const value = deserializeBytesValue(protocolHeader.value);
    return [name, value];
  }

  #deserializeHeaders(headers) {
    const deserializedHeaders = [];
    lazy.assert.array(
      headers,
      lazy.pprint`Expected "headers" to be an array got ${headers}`
    );

    for (const header of headers) {
      this.#assertHeader(
        header,
        lazy.pprint`Expected values in "headers" to be network.Header, got ${header}`
      );

      // Deserialize headers immediately to validate the value
      const deserializedHeader = this.#deserializeHeader(header);
      lazy.assert.that(
        value => this.#isValidHttpToken(value),
        lazy.pprint`Expected "header" name to be a valid HTTP token, got ${deserializedHeader[0]}`
      )(deserializedHeader[0]);
      lazy.assert.that(
        value => this.#isValidHeaderValue(value),
        lazy.pprint`Expected "header" value to be a valid header value, got ${deserializedHeader[1]}`
      )(deserializedHeader[1]);

      deserializedHeaders.push(deserializedHeader);
    }

    return deserializedHeaders;
  }

  #extractChallenges(response) {
    let headerName;

    // Using case-insensitive match for header names, so we use the lowercase
    // version of the "WWW-Authenticate" / "Proxy-Authenticate" strings.
    if (response.status === 401) {
      headerName = "www-authenticate";
    } else if (response.status === 407) {
      headerName = "proxy-authenticate";
    } else {
      return null;
    }

    const challenges = [];

    for (const [name, value] of response.getHeadersList()) {
      if (name.toLowerCase() === headerName) {
        // A single header can contain several challenges.
        const headerChallenges = lazy.parseChallengeHeader(value);
        for (const headerChallenge of headerChallenges) {
          const realmParam = headerChallenge.params.find(
            param => param.name == "realm"
          );
          const realm = realmParam ? realmParam.value : undefined;
          const challenge = {
            scheme: headerChallenge.scheme,
            realm,
          };
          challenges.push(challenge);
        }
      }
    }

    return challenges;
  }

  #getBrowsingContext(contextId) {
    const context = lazy.TabManager.getBrowsingContextById(contextId);
    if (context === null) {
      throw new lazy.error.NoSuchFrameError(
        `Browsing Context with id ${contextId} not found`
      );
    }

    if (!context.currentWindowGlobal) {
      throw new lazy.error.NoSuchFrameError(
        `No window found for BrowsingContext with id ${contextId}`
      );
    }

    return context;
  }

  #getNetworkIntercepts(event, request, topContextId) {
    const intercepts = [];

    let phase;
    switch (event) {
      case "network.beforeRequestSent":
        phase = InterceptPhase.BeforeRequestSent;
        break;
      case "network.responseStarted":
        phase = InterceptPhase.ResponseStarted;
        break;
      case "network.authRequired":
        phase = InterceptPhase.AuthRequired;
        break;
      case "network.responseCompleted":
        // The network.responseCompleted event does not match any interception
        // phase. Return immediately.
        return intercepts;
    }

    const url = request.serializedURL;
    for (const [interceptId, intercept] of this.#interceptMap) {
      if (
        intercept.contexts !== null &&
        !intercept.contexts.includes(topContextId)
      ) {
        // Skip this intercept if the event's context does not match the list
        // of contexts for this intercept.
        continue;
      }

      if (intercept.phases.includes(phase)) {
        const urlPatterns = intercept.urlPatterns;
        if (
          !urlPatterns.length ||
          urlPatterns.some(pattern => lazy.matchURLPattern(pattern, url))
        ) {
          intercepts.push(interceptId);
        }
      }
    }

    return intercepts;
  }

  #getRequestData(request) {
    const requestId = request.requestId;

    // "Let url be the result of running the URL serializer with request’s URL"
    // request.serializedURL is already serialized.
    const url = request.serializedURL;
    const method = request.method;

    const bodySize = request.postDataSize;
    const headersSize = request.headersSize;
    const headers = [];
    const cookies = [];

    for (const [name, value] of request.getHeadersList()) {
      headers.push(this.#serializeHeader(name, value));
      if (name.toLowerCase() == "cookie") {
        // TODO: Retrieve the actual cookies from the cookie store.
        const headerCookies = value.split(";");
        for (const cookie of headerCookies) {
          const equal = cookie.indexOf("=");
          const cookieName = cookie.substr(0, equal);
          const cookieValue = cookie.substr(equal + 1);
          const serializedCookie = this.#serializeHeader(
            unescape(cookieName.trim()),
            unescape(cookieValue.trim())
          );
          cookies.push(serializedCookie);
        }
      }
    }

    const timings = request.getFetchTimings();

    return {
      request: requestId,
      url,
      method,
      bodySize,
      headersSize,
      headers,
      cookies,
      timings,
    };
  }

  #getResponseContentInfo(response) {
    return {
      size: response.decodedBodySize,
    };
  }

  #getResponseData(response) {
    const url = response.serializedURL;
    const protocol = response.protocol;
    const status = response.status;
    const statusText = response.statusMessage;
    // TODO: Ideally we should have a `isCacheStateLocal` getter
    // const fromCache = response.isCacheStateLocal();
    const fromCache = response.fromCache;
    const mimeType = response.getComputedMimeType();
    const headers = [];
    for (const [name, value] of response.getHeadersList()) {
      headers.push(this.#serializeHeader(name, value));
    }

    const bytesReceived = response.totalTransmittedSize;
    const headersSize = response.headersTransmittedSize;
    const bodySize = response.encodedBodySize;
    const content = this.#getResponseContentInfo(response);
    const authChallenges = this.#extractChallenges(response);

    const params = {
      url,
      protocol,
      status,
      statusText,
      fromCache,
      headers,
      mimeType,
      bytesReceived,
      headersSize,
      bodySize,
      content,
    };

    if (authChallenges !== null) {
      params.authChallenges = authChallenges;
    }

    return params;
  }

  #getSuspendMarkerText(requestData, phase) {
    return `Request (id: ${requestData.request}) suspended by WebDriver BiDi in ${phase} phase`;
  }

  #isValidHeaderValue(value) {
    if (!value.length) {
      return true;
    }

    // For non-empty strings check against:
    // - leading or trailing tabs & spaces
    // - new lines and null bytes
    const chars = value.split("");
    const tabOrSpace = [" ", "\t"];
    const forbiddenChars = ["\r", "\n", "\0"];
    return (
      !tabOrSpace.includes(chars.at(0)) &&
      !tabOrSpace.includes(chars.at(-1)) &&
      forbiddenChars.every(c => !chars.includes(c))
    );
  }

  /**
   * This helper is adapted from a C++ validation helper in nsHttp.cpp.
   *
   * @see https://searchfox.org/mozilla-central/rev/445a6e86233c733c5557ef44e1d33444adaddefc/netwerk/protocol/http/nsHttp.cpp#169
   */
  #isValidHttpToken(token) {
    // prettier-ignore
    // This array corresponds to all char codes between 0 and 127, which is the
    // range of supported char codes for HTTP tokens. Within this range,
    // accepted char codes are marked with a 1, forbidden char codes with a 0.
    const validTokenMap = [
      0, 0, 0, 0, 0, 0, 0, 0,  //   0
      0, 0, 0, 0, 0, 0, 0, 0,  //   8
      0, 0, 0, 0, 0, 0, 0, 0,  //  16
      0, 0, 0, 0, 0, 0, 0, 0,  //  24

      0, 1, 0, 1, 1, 1, 1, 1,  //  32
      0, 0, 1, 1, 0, 1, 1, 0,  //  40
      1, 1, 1, 1, 1, 1, 1, 1,  //  48
      1, 1, 0, 0, 0, 0, 0, 0,  //  56

      0, 1, 1, 1, 1, 1, 1, 1,  //  64
      1, 1, 1, 1, 1, 1, 1, 1,  //  72
      1, 1, 1, 1, 1, 1, 1, 1,  //  80
      1, 1, 1, 0, 0, 0, 1, 1,  //  88

      1, 1, 1, 1, 1, 1, 1, 1,  //  96
      1, 1, 1, 1, 1, 1, 1, 1,  // 104
      1, 1, 1, 1, 1, 1, 1, 1,  // 112
      1, 1, 1, 0, 1, 0, 1, 0   // 120
    ];

    if (!token.length) {
      return false;
    }
    return token
      .split("")
      .map(s => s.charCodeAt(0))
      .every(c => validTokenMap[c]);
  }

  #onAuthRequired = (name, data) => {
    const { authCallbacks, request, response } = data;

    let isBlocked = false;
    try {
      const browsingContext = lazy.TabManager.getBrowsingContextById(
        request.contextId
      );
      if (!browsingContext) {
        // Do not emit events if the context id does not match any existing
        // browsing context.
        return;
      }

      const protocolEventName = "network.authRequired";

      const isListening = this._hasListener(protocolEventName, {
        contextId: request.contextId,
      });
      if (!isListening) {
        // If there are no listeners subscribed to this event and this context,
        // bail out.
        return;
      }

      const baseParameters = this.#processNetworkEvent(
        protocolEventName,
        request
      );

      const responseData = this.#getResponseData(response);
      const authRequiredEvent = {
        ...baseParameters,
        response: responseData,
      };

      this._emitEventForBrowsingContext(
        browsingContext.id,
        protocolEventName,
        authRequiredEvent
      );

      if (authRequiredEvent.isBlocked) {
        isBlocked = true;

        // requestChannel.suspend() is not needed here because the request is
        // already blocked on the authentication prompt notification until
        // one of the authCallbacks is called.
        this.#addBlockedRequest(
          authRequiredEvent.request.request,
          InterceptPhase.AuthRequired,
          {
            authCallbacks,
            request,
            response,
          }
        );
      }
    } finally {
      if (!isBlocked) {
        // If the request was not blocked, forward the auth prompt notification
        // to the next consumer.
        authCallbacks.forwardAuthPrompt();
      }
    }
  };

  #onBeforeRequestSent = (name, data) => {
    const { request } = data;

    const browsingContext = lazy.TabManager.getBrowsingContextById(
      request.contextId
    );
    if (!browsingContext) {
      // Do not emit events if the context id does not match any existing
      // browsing context.
      return;
    }

    const protocolEventName = "network.beforeRequestSent";

    const isListening = this._hasListener(protocolEventName, {
      contextId: request.contextId,
    });
    if (!isListening) {
      // If there are no listeners subscribed to this event and this context,
      // bail out.
      return;
    }

    const baseParameters = this.#processNetworkEvent(
      protocolEventName,
      request
    );

    // Bug 1805479: Handle the initiator, including stacktrace details.
    const initiator = {
      type: InitiatorType.Other,
    };

    const beforeRequestSentEvent = {
      ...baseParameters,
      initiator,
    };

    this._emitEventForBrowsingContext(
      browsingContext.id,
      protocolEventName,
      beforeRequestSentEvent
    );
    if (beforeRequestSentEvent.isBlocked) {
      // TODO: Requests suspended in beforeRequestSent still reach the server at
      // the moment. https://bugzilla.mozilla.org/show_bug.cgi?id=1849686
      request.wrappedChannel.suspend(
        this.#getSuspendMarkerText(request, "beforeRequestSent")
      );

      this.#addBlockedRequest(
        beforeRequestSentEvent.request.request,
        InterceptPhase.BeforeRequestSent,
        {
          request,
        }
      );
    }
  };

  #onBeforeStopRequest = (event, data) => {
    this.#decodedBodySizeMap.setDecodedBodySize(
      data.channel.channelId,
      data.decodedBodySize
    );
  };

  #onFetchError = (name, data) => {
    const { request } = data;

    const browsingContext = lazy.TabManager.getBrowsingContextById(
      request.contextId
    );
    if (!browsingContext) {
      // Do not emit events if the context id does not match any existing
      // browsing context.
      return;
    }

    const protocolEventName = "network.fetchError";

    const isListening = this._hasListener(protocolEventName, {
      contextId: request.contextId,
    });
    if (!isListening) {
      // If there are no listeners subscribed to this event and this context,
      // bail out.
      return;
    }

    const baseParameters = this.#processNetworkEvent(
      protocolEventName,
      request
    );

    const fetchErrorEvent = {
      ...baseParameters,
      errorText: request.errorText,
    };

    this._emitEventForBrowsingContext(
      browsingContext.id,
      protocolEventName,
      fetchErrorEvent
    );
  };

  #onResponseEvent = async (name, data) => {
    const { request, response } = data;

    const browsingContext = lazy.TabManager.getBrowsingContextById(
      request.contextId
    );
    if (!browsingContext) {
      // Do not emit events if the context id does not match any existing
      // browsing context.
      return;
    }

    const protocolEventName =
      name === "response-started"
        ? "network.responseStarted"
        : "network.responseCompleted";

    const isListening = this._hasListener(protocolEventName, {
      contextId: request.contextId,
    });
    if (!isListening) {
      // If there are no listeners subscribed to this event and this context,
      // bail out.
      return;
    }

    const baseParameters = this.#processNetworkEvent(
      protocolEventName,
      request
    );

    const responseData = this.#getResponseData(response);

    const responseEvent = {
      ...baseParameters,
      response: responseData,
    };

    this._emitEventForBrowsingContext(
      browsingContext.id,
      protocolEventName,
      responseEvent
    );

    if (
      protocolEventName === "network.responseStarted" &&
      responseEvent.isBlocked
    ) {
      request.wrappedChannel.suspend(
        this.#getSuspendMarkerText(request, "responseStarted")
      );

      this.#addBlockedRequest(
        responseEvent.request.request,
        InterceptPhase.ResponseStarted,
        {
          request,
          response,
        }
      );
    }
  };

  #processNetworkEvent(event, request) {
    const requestData = this.#getRequestData(request);
    const navigation = request.navigationId;
    let contextId = null;
    let topContextId = null;
    if (request.contextId) {
      // Retrieve the top browsing context id for this network event.
      contextId = request.contextId;
      const browsingContext = lazy.TabManager.getBrowsingContextById(contextId);
      topContextId = lazy.TabManager.getIdForBrowsingContext(
        browsingContext.top
      );
    }

    const intercepts = this.#getNetworkIntercepts(event, request, topContextId);
    const redirectCount = request.redirectCount;
    const timestamp = Date.now();
    const isBlocked = !!intercepts.length;
    const params = {
      context: contextId,
      isBlocked,
      navigation,
      redirectCount,
      request: requestData,
      timestamp,
    };

    if (isBlocked) {
      params.intercepts = intercepts;
    }

    return params;
  }

  #serializeCookieHeader(cookieHeader) {
    const name = cookieHeader.name;
    const value = deserializeBytesValue(cookieHeader.value);
    return `${name}=${value}`;
  }

  #serializeHeader(name, value) {
    return {
      name,
      value: this.#serializeStringAsBytesValue(value),
    };
  }

  #serializeSetCookieHeader(setCookieHeader) {
    const {
      name,
      value,
      domain = null,
      httpOnly = null,
      expiry = null,
      maxAge = null,
      path = null,
      sameSite = null,
      secure = null,
    } = setCookieHeader;

    let headerValue = `${name}=${deserializeBytesValue(value)}`;

    if (expiry !== null) {
      headerValue += `;Expires=${expiry}`;
    }
    if (maxAge !== null) {
      headerValue += `;Max-Age=${maxAge}`;
    }
    if (domain !== null) {
      headerValue += `;Domain=${domain}`;
    }
    if (path !== null) {
      headerValue += `;Path=${path}`;
    }
    if (secure === true) {
      headerValue += `;Secure`;
    }
    if (httpOnly === true) {
      headerValue += `;HttpOnly`;
    }
    if (sameSite !== null) {
      headerValue += `;SameSite=${sameSite}`;
    }
    return headerValue;
  }

  /**
   * Serialize a string value as BytesValue.
   *
   * Note: This does not attempt to fully implement serialize protocol bytes
   * (https://w3c.github.io/webdriver-bidi/#serialize-protocol-bytes) as the
   * header values read from the Channel are already serialized as strings at
   * the moment.
   *
   * @param {string} value
   *     The value to serialize.
   */
  #serializeStringAsBytesValue(value) {
    // TODO: For now, we handle all headers and cookies with the "string" type.
    // See Bug 1835216 to add support for "base64" type and handle non-utf8
    // values.
    return {
      type: BytesValueType.String,
      value,
    };
  }

  #startListening(event) {
    if (this.#subscribedEvents.size == 0) {
      this.#networkListener.startListening();
      this.#beforeStopRequestListener.startListening();
    }
    this.#subscribedEvents.add(event);
  }

  #stopListening(event) {
    this.#subscribedEvents.delete(event);
    if (this.#subscribedEvents.size == 0) {
      this.#networkListener.stopListening();
      this.#beforeStopRequestListener.stopListening();
    }
  }

  #subscribeEvent(event) {
    if (this.constructor.supportedEvents.includes(event)) {
      this.#startListening(event);
    }
  }

  #unsubscribeEvent(event) {
    if (this.constructor.supportedEvents.includes(event)) {
      this.#stopListening(event);
    }
  }

  /**
   * Internal commands
   */

  _applySessionData(params) {
    // TODO: Bug 1775231. Move this logic to a shared module or an abstract
    // class.
    const { category } = params;
    if (category === "event") {
      const filteredSessionData = params.sessionData.filter(item =>
        this.messageHandler.matchesContext(item.contextDescriptor)
      );
      for (const event of this.#subscribedEvents.values()) {
        const hasSessionItem = filteredSessionData.some(
          item => item.value === event
        );
        // If there are no session items for this context, we should unsubscribe from the event.
        if (!hasSessionItem) {
          this.#unsubscribeEvent(event);
        }
      }

      // Subscribe to all events, which have an item in SessionData.
      for (const { value } of filteredSessionData) {
        this.#subscribeEvent(value);
      }
    }
  }

  _setDecodedBodySize(params) {
    const { channelId, decodedBodySize } = params;
    this.#decodedBodySizeMap.setDecodedBodySize(channelId, decodedBodySize);
  }

  static get supportedEvents() {
    return [
      "network.authRequired",
      "network.beforeRequestSent",
      "network.fetchError",
      "network.responseCompleted",
      "network.responseStarted",
    ];
  }
}

/**
 * Deserialize a network BytesValue.
 *
 * @param {BytesValue} bytesValue
 *     The BytesValue to deserialize.
 * @returns {string}
 *     The deserialized value.
 */
export function deserializeBytesValue(bytesValue) {
  const { type, value } = bytesValue;

  if (type === BytesValueType.String) {
    return value;
  }

  // For type === BytesValueType.Base64.
  return atob(value);
}

export const network = NetworkModule;
PK
!<����Echrome/remote/content/webdriver-bidi/modules/root/permissions.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { RootBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/RootBiDiModule.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  permissions: "chrome://remote/content/shared/Permissions.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
  UserContextManager:
    "chrome://remote/content/shared/UserContextManager.sys.mjs",
});

class PermissionsModule extends RootBiDiModule {
  constructor(messageHandler) {
    super(messageHandler);
  }

  destroy() {}

  /**
   * An object that holds the information about permission descriptor
   * for Webdriver BiDi permissions.setPermission command.
   *
   * @typedef PermissionDescriptor
   *
   * @property {string} name
   *    The name of the permission.
   */

  /**
   * Set to a given permission descriptor a given state on a provided origin.
   *
   * @param {object=} options
   * @param {PermissionDescriptor} options.descriptor
   *     The descriptor of the permission which will be updated.
   * @param {PermissionState} options.state
   *     The state which will be set to the permission.
   * @param {string} options.origin
   *    The origin which is used as a target for permission update.
   * @param {string=} options.userContext [unsupported]
   *    The id of the user context which should be used as a target
   *    for permission update.
   *
   * @throws {InvalidArgumentError}
   *     Raised if an argument is of an invalid type or value.
   * @throws {UnsupportedOperationError}
   *     Raised when unsupported permissions are set or <var>userContext</var>
   *     argument is used.
   */
  async setPermission(options = {}) {
    const {
      descriptor,
      state,
      origin,
      userContext: userContextId = null,
    } = options;

    lazy.permissions.validateDescriptor(descriptor);

    const permissionName = descriptor.name;

    if (permissionName === "storage-access") {
      // TODO: Bug 1895457. Add support for "storage-access" permission.
      throw new lazy.error.UnsupportedOperationError(
        `"descriptor.name" "${permissionName}" is currently unsupported`
      );
    }

    lazy.permissions.validateState(state);

    lazy.assert.string(
      origin,
      lazy.pprint`Expected "origin" to be a string, got ${origin}`
    );
    lazy.assert.that(
      origin => URL.canParse(origin),
      lazy.pprint`Expected "origin" to be a valid URL, got ${origin}`
    )(origin);

    let userContext;
    if (userContextId !== null) {
      lazy.assert.string(
        userContextId,
        lazy.pprint`Expected "userContext" to be a string, got ${userContextId}`
      );

      if (!lazy.UserContextManager.hasUserContextId(userContextId)) {
        throw new lazy.error.NoSuchUserContextError(
          `User Context with id ${userContextId} was not found`
        );
      }

      userContext = lazy.UserContextManager.getInternalIdById(userContextId);
    }

    const activeWindow = Services.wm.getMostRecentBrowserWindow();
    let typedDescriptor;
    try {
      typedDescriptor = activeWindow.navigator.permissions.parseSetParameters({
        descriptor,
        state,
      });
    } catch (err) {
      throw new lazy.error.InvalidArgumentError(
        `The conversion of "descriptor" was not successful: ${err.message}`
      );
    }

    lazy.permissions.set(typedDescriptor, state, origin, userContext);
  }
}

export const permissions = PermissionsModule;
PK
!<pM�+(o(o@chrome/remote/content/webdriver-bidi/modules/root/script.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { RootBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/RootBiDiModule.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  ContextDescriptorType:
    "chrome://remote/content/shared/messagehandler/MessageHandler.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  generateUUID: "chrome://remote/content/shared/UUID.sys.mjs",
  OwnershipModel: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
  processExtraData:
    "chrome://remote/content/webdriver-bidi/modules/Intercept.sys.mjs",
  RealmType: "chrome://remote/content/shared/Realm.sys.mjs",
  SessionDataMethod:
    "chrome://remote/content/shared/messagehandler/sessiondata/SessionData.sys.mjs",
  setDefaultAndAssertSerializationOptions:
    "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
  WindowGlobalMessageHandler:
    "chrome://remote/content/shared/messagehandler/WindowGlobalMessageHandler.sys.mjs",
});

/**
 * @typedef {string} ScriptEvaluateResultType
 */

/**
 * Enum of possible evaluation result types.
 *
 * @readonly
 * @enum {ScriptEvaluateResultType}
 */
const ScriptEvaluateResultType = {
  Exception: "exception",
  Success: "success",
};

class ScriptModule extends RootBiDiModule {
  #preloadScriptMap;
  #subscribedEvents;

  constructor(messageHandler) {
    super(messageHandler);

    // Map in which the keys are UUIDs, and the values are structs
    // with an item named expression, which is a string,
    // and an item named sandbox which is a string or null.
    this.#preloadScriptMap = new Map();

    // Set of event names which have active subscriptions.
    this.#subscribedEvents = new Set();
  }

  destroy() {
    this.#preloadScriptMap = null;
    this.#subscribedEvents = null;
  }

  /**
   * Used as return value for script.addPreloadScript command.
   *
   * @typedef AddPreloadScriptResult
   *
   * @property {string} script
   *    The unique id associated with added preload script.
   */

  /**
   * @typedef ChannelProperties
   *
   * @property {string} channel
   *     The channel id.
   * @property {SerializationOptions=} serializationOptions
   *     An object which holds the information of how the result of evaluation
   *     in case of ECMAScript objects should be serialized.
   * @property {OwnershipModel=} ownership
   *     The ownership model to use for the results of this evaluation. Defaults
   *     to `OwnershipModel.None`.
   */

  /**
   * Represents a channel used to send custom messages from preload script
   * to clients.
   *
   * @typedef ChannelValue
   *
   * @property {'channel'} type
   * @property {ChannelProperties} value
   */

  /**
   * Adds a preload script, which runs on creation of a new Window,
   * before any author-defined script have run.
   *
   * @param {object=} options
   * @param {Array<ChannelValue>=} options.arguments
   *     The arguments to pass to the function call.
   * @param {Array<string>=} options.contexts
   *     The list of the browsing context ids.
   * @param {string} options.functionDeclaration
   *     The expression to evaluate.
   * @param {string=} options.sandbox
   *     The name of the sandbox. If the value is null or empty
   *     string, the default realm will be used.
   *
   * @returns {AddPreloadScriptResult}
   *
   * @throws {InvalidArgumentError}
   *     If any of the arguments does not have the expected type.
   */
  async addPreloadScript(options = {}) {
    const {
      arguments: commandArguments = [],
      contexts: contextIds = null,
      functionDeclaration,
      sandbox = null,
    } = options;
    let contexts = null;

    if (contextIds != null) {
      lazy.assert.array(
        contextIds,
        lazy.pprint`Expected "contexts" to be an array, got ${contextIds}`
      );
      lazy.assert.that(
        contexts => !!contexts.length,
        lazy.pprint`Expected "contexts" array to have at least one item, got ${contextIds}`
      )(contextIds);

      contexts = new Set();
      for (const contextId of contextIds) {
        lazy.assert.string(
          contextId,
          lazy.pprint`Expected elements of "contexts" to be a string, got ${contextId}`
        );
        const context = this.#getBrowsingContext(contextId);

        if (context.parent) {
          throw new lazy.error.InvalidArgumentError(
            `Context with id ${contextId} is not a top-level browsing context`
          );
        }

        contexts.add(context.browserId);
      }
    }

    lazy.assert.string(
      functionDeclaration,
      lazy.pprint`Expected "functionDeclaration" to be a string, got ${functionDeclaration}`
    );

    if (sandbox != null) {
      lazy.assert.string(
        sandbox,
        lazy.pprint`Expected "sandbox" to be a string, got ${sandbox}`
      );
    }

    lazy.assert.array(
      commandArguments,
      lazy.pprint`Expected "arguments" to be an array, got ${commandArguments}`
    );
    commandArguments.forEach(({ type, value }) => {
      lazy.assert.that(
        t => t === "channel",
        lazy.pprint`Expected argument "type" to be "channel", got ${type}`
      )(type);
      this.#assertChannelArgument(value);
    });

    const script = lazy.generateUUID();
    const preloadScript = {
      arguments: commandArguments,
      contexts,
      functionDeclaration,
      sandbox,
    };

    this.#preloadScriptMap.set(script, preloadScript);

    const preloadScriptDataItem = {
      category: "preload-script",
      moduleName: "script",
      values: [
        {
          ...preloadScript,
          script,
        },
      ],
    };

    if (contexts === null) {
      await this.messageHandler.addSessionDataItem({
        ...preloadScriptDataItem,
        contextDescriptor: {
          type: lazy.ContextDescriptorType.All,
        },
      });
    } else {
      const preloadScriptDataItems = [];
      for (const id of contexts) {
        preloadScriptDataItems.push({
          ...preloadScriptDataItem,
          contextDescriptor: {
            type: lazy.ContextDescriptorType.TopBrowsingContext,
            id,
          },
          method: lazy.SessionDataMethod.Add,
        });
      }

      await this.messageHandler.updateSessionData(preloadScriptDataItems);
    }

    return { script };
  }

  /**
   * Used to represent a frame of a JavaScript stack trace.
   *
   * @typedef StackFrame
   *
   * @property {number} columnNumber
   * @property {string} functionName
   * @property {number} lineNumber
   * @property {string} url
   */

  /**
   * Used to represent a JavaScript stack at a point in script execution.
   *
   * @typedef StackTrace
   *
   * @property {Array<StackFrame>} callFrames
   */

  /**
   * Used to represent a JavaScript exception.
   *
   * @typedef ExceptionDetails
   *
   * @property {number} columnNumber
   * @property {RemoteValue} exception
   * @property {number} lineNumber
   * @property {StackTrace} stackTrace
   * @property {string} text
   */

  /**
   * Used as return value for script.evaluate, as one of the available variants
   * {ScriptEvaluateResultException} or {ScriptEvaluateResultSuccess}.
   *
   * @typedef ScriptEvaluateResult
   */

  /**
   * Used as return value for script.evaluate when the script completes with a
   * thrown exception.
   *
   * @typedef ScriptEvaluateResultException
   *
   * @property {ExceptionDetails} exceptionDetails
   * @property {string} realm
   * @property {ScriptEvaluateResultType} [type=ScriptEvaluateResultType.Exception]
   */

  /**
   * Used as return value for script.evaluate when the script completes
   * normally.
   *
   * @typedef ScriptEvaluateResultSuccess
   *
   * @property {string} realm
   * @property {RemoteValue} result
   * @property {ScriptEvaluateResultType} [type=ScriptEvaluateResultType.Success]
   */

  /**
   * Calls a provided function with given arguments and scope in the provided
   * target, which is either a realm or a browsing context.
   *
   * @param {object=} options
   * @param {Array<RemoteValue>=} options.arguments
   *     The arguments to pass to the function call.
   * @param {boolean} options.awaitPromise
   *     Determines if the command should wait for the return value of the
   *     expression to resolve, if this return value is a Promise.
   * @param {string} options.functionDeclaration
   *     The expression to evaluate.
   * @param {OwnershipModel=} options.resultOwnership
   *     The ownership model to use for the results of this evaluation. Defaults
   *     to `OwnershipModel.None`.
   * @param {SerializationOptions=} options.serializationOptions
   *     An object which holds the information of how the result of evaluation
   *     in case of ECMAScript objects should be serialized.
   * @param {object} options.target
   *     The target for the evaluation, which either matches the definition for
   *     a RealmTarget or for ContextTarget.
   * @param {RemoteValue=} options.this
   *     The value of the this keyword for the function call.
   * @param {boolean=} options.userActivation
   *     Determines whether execution should be treated as initiated by user.
   *     Defaults to `false`.
   *
   * @returns {ScriptEvaluateResult}
   *
   * @throws {InvalidArgumentError}
   *     If any of the arguments does not have the expected type.
   * @throws {NoSuchFrameError}
   *     If the target cannot be found.
   */
  async callFunction(options = {}) {
    const {
      arguments: commandArguments = null,
      awaitPromise,
      functionDeclaration,
      resultOwnership = lazy.OwnershipModel.None,
      serializationOptions,
      target = {},
      this: thisParameter = null,
      userActivation = false,
    } = options;

    lazy.assert.string(
      functionDeclaration,
      lazy.pprint`Expected "functionDeclaration" to be a string, got ${functionDeclaration}`
    );

    lazy.assert.boolean(
      awaitPromise,
      lazy.pprint`Expected "awaitPromise" to be a boolean, got ${awaitPromise}`
    );

    lazy.assert.boolean(
      userActivation,
      lazy.pprint`Expected "userActivation" to be a boolean, got ${userActivation}`
    );

    this.#assertResultOwnership(resultOwnership);

    if (commandArguments != null) {
      lazy.assert.array(
        commandArguments,
        lazy.pprint`Expected "arguments" to be an array, got ${commandArguments}`
      );
      commandArguments.forEach(({ type, value }) => {
        if (type === "channel") {
          this.#assertChannelArgument(value);
        }
      });
    }

    const { contextId, realmId, sandbox } = this.#assertTarget(target);
    const context = await this.#getContextFromTarget({ contextId, realmId });
    const serializationOptionsWithDefaults =
      lazy.setDefaultAndAssertSerializationOptions(serializationOptions);
    const evaluationResult = await this.messageHandler.forwardCommand({
      moduleName: "script",
      commandName: "callFunctionDeclaration",
      destination: {
        type: lazy.WindowGlobalMessageHandler.type,
        id: context.id,
      },
      params: {
        awaitPromise,
        commandArguments,
        functionDeclaration,
        realmId,
        resultOwnership,
        sandbox,
        serializationOptions: serializationOptionsWithDefaults,
        thisParameter,
        userActivation,
      },
    });

    return this.#buildReturnValue(evaluationResult);
  }

  /**
   * The script.disown command disowns the given handles. This does not
   * guarantee the handled object will be garbage collected, as there can be
   * other handles or strong ECMAScript references.
   *
   * @param {object=} options
   * @param {Array<string>} options.handles
   *     Array of handle ids to disown.
   * @param {object} options.target
   *     The target owning the handles, which either matches the definition for
   *     a RealmTarget or for ContextTarget.
   */
  async disown(options = {}) {
    const { handles, target = {} } = options;

    lazy.assert.array(
      handles,
      lazy.pprint`Expected "handles" to be an array, got ${handles}`
    );
    handles.forEach(handle => {
      lazy.assert.string(
        handle,
        lazy.pprint`Expected "handles" to be an array of strings, got ${handle}`
      );
    });

    const { contextId, realmId, sandbox } = this.#assertTarget(target);
    const context = await this.#getContextFromTarget({ contextId, realmId });
    await this.messageHandler.forwardCommand({
      moduleName: "script",
      commandName: "disownHandles",
      destination: {
        type: lazy.WindowGlobalMessageHandler.type,
        id: context.id,
      },
      params: {
        handles,
        realmId,
        sandbox,
      },
    });
  }

  /**
   * Evaluate a provided expression in the provided target, which is either a
   * realm or a browsing context.
   *
   * @param {object=} options
   * @param {boolean} options.awaitPromise
   *     Determines if the command should wait for the return value of the
   *     expression to resolve, if this return value is a Promise.
   * @param {string} options.expression
   *     The expression to evaluate.
   * @param {OwnershipModel=} options.resultOwnership
   *     The ownership model to use for the results of this evaluation. Defaults
   *     to `OwnershipModel.None`.
   * @param {SerializationOptions=} options.serializationOptions
   *     An object which holds the information of how the result of evaluation
   *     in case of ECMAScript objects should be serialized.
   * @param {object} options.target
   *     The target for the evaluation, which either matches the definition for
   *     a RealmTarget or for ContextTarget.
   * @param {boolean=} options.userActivation
   *     Determines whether execution should be treated as initiated by user.
   *     Defaults to `false`.
   *
   * @returns {ScriptEvaluateResult}
   *
   * @throws {InvalidArgumentError}
   *     If any of the arguments does not have the expected type.
   * @throws {NoSuchFrameError}
   *     If the target cannot be found.
   */
  async evaluate(options = {}) {
    const {
      awaitPromise,
      expression: source,
      resultOwnership = lazy.OwnershipModel.None,
      serializationOptions,
      target = {},
      userActivation = false,
    } = options;

    lazy.assert.string(
      source,
      lazy.pprint`Expected "expression" to be a string, got ${source}`
    );

    lazy.assert.boolean(
      awaitPromise,
      lazy.pprint`Expected "awaitPromise" to be a boolean, got ${awaitPromise}`
    );

    lazy.assert.boolean(
      userActivation,
      lazy.pprint`Expected "userActivation" to be a boolean, got ${userActivation}`
    );

    this.#assertResultOwnership(resultOwnership);

    const { contextId, realmId, sandbox } = this.#assertTarget(target);
    const context = await this.#getContextFromTarget({ contextId, realmId });
    const serializationOptionsWithDefaults =
      lazy.setDefaultAndAssertSerializationOptions(serializationOptions);
    const evaluationResult = await this.messageHandler.forwardCommand({
      moduleName: "script",
      commandName: "evaluateExpression",
      destination: {
        type: lazy.WindowGlobalMessageHandler.type,
        id: context.id,
      },
      params: {
        awaitPromise,
        expression: source,
        realmId,
        resultOwnership,
        sandbox,
        serializationOptions: serializationOptionsWithDefaults,
        userActivation,
      },
    });

    return this.#buildReturnValue(evaluationResult);
  }

  /**
   * An object that holds basic information about a realm.
   *
   * @typedef BaseRealmInfo
   *
   * @property {string} id
   *     The realm unique identifier.
   * @property {string} origin
   *     The serialization of an origin.
   */

  /**
   *
   * @typedef WindowRealmInfoProperties
   *
   * @property {string} context
   *     The browsing context id, associated with the realm.
   * @property {string=} sandbox
   *     The name of the sandbox. If the value is null or empty
   *     string, the default realm will be returned.
   * @property {RealmType.Window} type
   *     The window realm type.
   */

  /* eslint-disable jsdoc/valid-types */
  /**
   * An object that holds information about a window realm.
   *
   * @typedef {BaseRealmInfo & WindowRealmInfoProperties} WindowRealmInfo
   */
  /* eslint-enable jsdoc/valid-types */

  /**
   * An object that holds information about a realm.
   *
   * @typedef {WindowRealmInfo} RealmInfo
   */

  /**
   * An object that holds a list of realms.
   *
   * @typedef ScriptGetRealmsResult
   *
   * @property {Array<RealmInfo>} realms
   *     List of realms.
   */

  /**
   * Returns a list of all realms, optionally filtered to realms
   * of a specific type, or to the realms associated with
   * a specified browsing context.
   *
   * @param {object=} options
   * @param {string=} options.context
   *     The id of the browsing context to filter
   *     only realms associated with it. If not provided, return realms
   *     associated with all browsing contexts.
   * @param {RealmType=} options.type
   *     Type of realm to filter.
   *     If not provided, return realms of all types.
   *
   * @returns {ScriptGetRealmsResult}
   *
   * @throws {InvalidArgumentError}
   *     If any of the arguments does not have the expected type.
   * @throws {NoSuchFrameError}
   *     If the context cannot be found.
   */
  async getRealms(options = {}) {
    const { context: contextId = null, type = null } = options;
    const destination = {};

    if (contextId !== null) {
      lazy.assert.string(
        contextId,
        lazy.pprint`Expected "context" to be a string, got ${contextId}`
      );
      destination.id = this.#getBrowsingContext(contextId).id;
    } else {
      destination.contextDescriptor = {
        type: lazy.ContextDescriptorType.All,
      };
    }

    if (type !== null) {
      const supportedRealmTypes = Object.values(lazy.RealmType);
      if (!supportedRealmTypes.includes(type)) {
        throw new lazy.error.InvalidArgumentError(
          `Expected "type" to be one of ${supportedRealmTypes}, got ${type}`
        );
      }

      // Remove this check when other realm types are supported
      if (type !== lazy.RealmType.Window) {
        throw new lazy.error.UnsupportedOperationError(
          `Unsupported "type": ${type}. Only "type" ${lazy.RealmType.Window} is currently supported.`
        );
      }
    }

    return { realms: await this.#getRealmInfos(destination) };
  }

  /**
   * Removes a preload script.
   *
   * @param {object=} options
   * @param {string} options.script
   *     The unique id associated with a preload script.
   *
   * @throws {InvalidArgumentError}
   *     If any of the arguments does not have the expected type.
   * @throws {NoSuchScriptError}
   *     If the script cannot be found.
   */
  async removePreloadScript(options = {}) {
    const { script } = options;

    lazy.assert.string(
      script,
      lazy.pprint`Expected "script" to be a string, got ${script}`
    );

    if (!this.#preloadScriptMap.has(script)) {
      throw new lazy.error.NoSuchScriptError(
        `Preload script with id ${script} not found`
      );
    }

    const preloadScript = this.#preloadScriptMap.get(script);
    const sessionDataItem = {
      category: "preload-script",
      moduleName: "script",
      values: [
        {
          ...preloadScript,
          script,
        },
      ],
    };

    if (preloadScript.contexts === null) {
      await this.messageHandler.removeSessionDataItem({
        ...sessionDataItem,
        contextDescriptor: {
          type: lazy.ContextDescriptorType.All,
        },
      });
    } else {
      const sessionDataItemToUpdate = [];
      for (const id of preloadScript.contexts) {
        sessionDataItemToUpdate.push({
          ...sessionDataItem,
          contextDescriptor: {
            type: lazy.ContextDescriptorType.TopBrowsingContext,
            id,
          },
          method: lazy.SessionDataMethod.Remove,
        });
      }

      await this.messageHandler.updateSessionData(sessionDataItemToUpdate);
    }

    this.#preloadScriptMap.delete(script);
  }

  #assertChannelArgument(value) {
    lazy.assert.object(
      value,
      lazy.pprint`Expected channel argument to be an object, got ${value}`
    );
    const {
      channel,
      ownership = lazy.OwnershipModel.None,
      serializationOptions,
    } = value;
    lazy.assert.string(
      channel,
      lazy.pprint`Expected channel argument "channel" to be a string, got ${channel}`
    );
    lazy.setDefaultAndAssertSerializationOptions(serializationOptions);
    lazy.assert.that(
      ownership =>
        [lazy.OwnershipModel.None, lazy.OwnershipModel.Root].includes(
          ownership
        ),
      `Expected channel argument "ownership" to be one of ${Object.values(
        lazy.OwnershipModel
      )}, ` + lazy.pprint`got ${ownership}`
    )(ownership);

    return true;
  }

  #assertResultOwnership(resultOwnership) {
    if (
      ![lazy.OwnershipModel.None, lazy.OwnershipModel.Root].includes(
        resultOwnership
      )
    ) {
      throw new lazy.error.InvalidArgumentError(
        `Expected "resultOwnership" to be one of ${Object.values(
          lazy.OwnershipModel
        )}, ` + lazy.pprint`got ${resultOwnership}`
      );
    }
  }

  #assertTarget(target) {
    lazy.assert.object(
      target,
      lazy.pprint`Expected "target" to be an object, got ${target}`
    );

    const { context: contextId = null, sandbox = null } = target;
    let { realm: realmId = null } = target;

    if (contextId != null) {
      lazy.assert.string(
        contextId,
        lazy.pprint`Expected target "context" to be a string, got ${contextId}`
      );

      if (sandbox != null) {
        lazy.assert.string(
          sandbox,
          lazy.pprint`Expected target "sandbox" to be a string, got ${sandbox}`
        );
      }

      // Ignore realm if context is provided.
      realmId = null;
    } else if (realmId != null) {
      lazy.assert.string(
        realmId,
        lazy.pprint`Expected target "realm" to be a string, got ${realmId}`
      );
    } else {
      throw new lazy.error.InvalidArgumentError(`No context or realm provided`);
    }

    return { contextId, realmId, sandbox };
  }

  #buildReturnValue(evaluationResult) {
    evaluationResult = lazy.processExtraData(
      this.messageHandler.sessionId,
      evaluationResult
    );

    const rv = { realm: evaluationResult.realmId };
    switch (evaluationResult.evaluationStatus) {
      // TODO: Compare with EvaluationStatus.Normal after Bug 1774444 is fixed.
      case "normal":
        rv.type = ScriptEvaluateResultType.Success;
        rv.result = evaluationResult.result;
        break;
      // TODO: Compare with EvaluationStatus.Throw after Bug 1774444 is fixed.
      case "throw":
        rv.type = ScriptEvaluateResultType.Exception;
        rv.exceptionDetails = evaluationResult.exceptionDetails;
        break;
      default:
        throw new lazy.error.UnsupportedOperationError(
          `Unsupported evaluation status ${evaluationResult.evaluationStatus}`
        );
    }
    return rv;
  }

  #getBrowsingContext(contextId) {
    const context = lazy.TabManager.getBrowsingContextById(contextId);
    if (context === null) {
      throw new lazy.error.NoSuchFrameError(
        `Browsing Context with id ${contextId} not found`
      );
    }

    if (!context.currentWindowGlobal) {
      throw new lazy.error.NoSuchFrameError(
        `No window found for BrowsingContext with id ${contextId}`
      );
    }

    return context;
  }

  async #getContextFromTarget({ contextId, realmId }) {
    if (contextId !== null) {
      return this.#getBrowsingContext(contextId);
    }

    const destination = {
      contextDescriptor: {
        type: lazy.ContextDescriptorType.All,
      },
    };
    const realms = await this.#getRealmInfos(destination);
    const realm = realms.find(realm => realm.realm == realmId);

    if (realm && realm.context !== null) {
      return this.#getBrowsingContext(realm.context);
    }

    throw new lazy.error.NoSuchFrameError(`Realm with id ${realmId} not found`);
  }

  async #getRealmInfos(destination) {
    let realms = await this.messageHandler.forwardCommand({
      moduleName: "script",
      commandName: "getWindowRealms",
      destination: {
        type: lazy.WindowGlobalMessageHandler.type,
        ...destination,
      },
    });

    const isBroadcast = !!destination.contextDescriptor;
    if (!isBroadcast) {
      realms = [realms];
    }

    return realms
      .flat()
      .map(realm => {
        // Resolve browsing context to a TabManager id.
        realm.context = lazy.TabManager.getIdForBrowsingContext(realm.context);
        return realm;
      })
      .filter(realm => realm.context !== null);
  }

  #onRealmCreated = (eventName, { realmInfo }) => {
    // Resolve browsing context to a TabManager id.
    const context = lazy.TabManager.getIdForBrowsingContext(realmInfo.context);
    const browsingContextId = realmInfo.context.id;

    // Do not emit the event, if the browsing context is gone.
    if (context === null) {
      return;
    }

    realmInfo.context = context;
    this._emitEventForBrowsingContext(
      browsingContextId,
      "script.realmCreated",
      realmInfo
    );
  };

  #onRealmDestroyed = (eventName, { realm, context }) => {
    this._emitEventForBrowsingContext(context.id, "script.realmDestroyed", {
      realm,
    });
  };

  #startListingOnRealmCreated() {
    if (!this.#subscribedEvents.has("script.realmCreated")) {
      this.messageHandler.on("realm-created", this.#onRealmCreated);
    }
  }

  #stopListingOnRealmCreated() {
    if (this.#subscribedEvents.has("script.realmCreated")) {
      this.messageHandler.off("realm-created", this.#onRealmCreated);
    }
  }

  #startListingOnRealmDestroyed() {
    if (!this.#subscribedEvents.has("script.realmDestroyed")) {
      this.messageHandler.on("realm-destroyed", this.#onRealmDestroyed);
    }
  }

  #stopListingOnRealmDestroyed() {
    if (this.#subscribedEvents.has("script.realmDestroyed")) {
      this.messageHandler.off("realm-destroyed", this.#onRealmDestroyed);
    }
  }

  #subscribeEvent(event) {
    switch (event) {
      case "script.realmCreated": {
        this.#startListingOnRealmCreated();
        this.#subscribedEvents.add(event);
        break;
      }
      case "script.realmDestroyed": {
        this.#startListingOnRealmDestroyed();
        this.#subscribedEvents.add(event);
        break;
      }
    }
  }

  #unsubscribeEvent(event) {
    switch (event) {
      case "script.realmCreated": {
        this.#stopListingOnRealmCreated();
        this.#subscribedEvents.delete(event);
        break;
      }
      case "script.realmDestroyed": {
        this.#stopListingOnRealmDestroyed();
        this.#subscribedEvents.delete(event);
        break;
      }
    }
  }

  _applySessionData(params) {
    // TODO: Bug 1775231. Move this logic to a shared module or an abstract
    // class.
    const { category } = params;
    if (category === "event") {
      const filteredSessionData = params.sessionData.filter(item =>
        this.messageHandler.matchesContext(item.contextDescriptor)
      );
      for (const event of this.#subscribedEvents.values()) {
        const hasSessionItem = filteredSessionData.some(
          item => item.value === event
        );
        // If there are no session items for this context, we should unsubscribe from the event.
        if (!hasSessionItem) {
          this.#unsubscribeEvent(event);
        }
      }

      // Subscribe to all events, which have an item in SessionData.
      for (const { value } of filteredSessionData) {
        this.#subscribeEvent(value);
      }
    }
  }

  static get supportedEvents() {
    return ["script.message", "script.realmCreated", "script.realmDestroyed"];
  }
}

export const script = ScriptModule;
PK
!<��X]D9D9Achrome/remote/content/webdriver-bidi/modules/root/session.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { RootBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/RootBiDiModule.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  ContextDescriptorType:
    "chrome://remote/content/shared/messagehandler/MessageHandler.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  getWebDriverSessionById:
    "chrome://remote/content/shared/webdriver/Session.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
  RootMessageHandler:
    "chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
});

class SessionModule extends RootBiDiModule {
  #browsingContextIdEventMap;
  #globalEventSet;

  constructor(messageHandler) {
    super(messageHandler);

    // Map with top-level browsing context id keys and values
    // that are a set of event names for events
    // that are enabled in the given browsing context.
    // TODO: Bug 1804417. Use navigable instead of browsing context id.
    this.#browsingContextIdEventMap = new Map();

    // Set of event names which are strings of the form [moduleName].[eventName]
    // for events that are enabled for all browsing contexts.
    // We should only add an actual event listener on the MessageHandler the
    // first time an event is subscribed to.
    this.#globalEventSet = new Set();
  }

  destroy() {
    this.#browsingContextIdEventMap = null;
    this.#globalEventSet = null;
  }

  /**
   * Commands
   */

  /**
   * End the current session.
   *
   * Session clean up will happen later in WebDriverBiDiConnection class.
   */
  async end() {
    const session = lazy.getWebDriverSessionById(this.messageHandler.sessionId);

    if (session.http) {
      throw new lazy.error.UnsupportedOperationError(
        "Ending a session started with WebDriver classic is not supported." +
          ' Use the WebDriver classic "Delete Session" command instead.'
      );
    }
  }

  /**
   * Enable certain events either globally, or for a list of browsing contexts.
   *
   * @param {object=} params
   * @param {Array<string>} params.events
   *     List of events to subscribe to.
   * @param {Array<string>=} params.contexts
   *     Optional list of top-level browsing context ids
   *     to subscribe the events for.
   *
   * @throws {InvalidArgumentError}
   *     If <var>events</var> or <var>contexts</var> are not valid types.
   */
  async subscribe(params = {}) {
    const { events, contexts: contextIds = null } = params;

    // Check input types until we run schema validation.
    this.#assertNonEmptyArrayWithStrings(events, "events");

    if (contextIds !== null) {
      this.#assertNonEmptyArrayWithStrings(contextIds, "contexts");
    }

    const listeners = this.#updateEventMap(events, contextIds, true);

    // TODO: Bug 1801284. Add subscribe priority sorting of subscribeStepEvents (step 4 to 6, and 8).

    // Subscribe to the relevant engine-internal events.
    await this.messageHandler.eventsDispatcher.update(listeners);
  }

  /**
   * Disable certain events either globally, or for a list of browsing contexts.
   *
   * @param {object=} params
   * @param {Array<string>} params.events
   *     List of events to unsubscribe from.
   * @param {Array<string>=} params.contexts
   *     Optional list of top-level browsing context ids
   *     to unsubscribe the events from.
   *
   * @throws {InvalidArgumentError}
   *     If <var>events</var> or <var>contexts</var> are not valid types.
   */
  async unsubscribe(params = {}) {
    const { events, contexts: contextIds = null } = params;

    // Check input types until we run schema validation.
    this.#assertNonEmptyArrayWithStrings(events, "events");
    if (contextIds !== null) {
      this.#assertNonEmptyArrayWithStrings(contextIds, "contexts");
    }

    const listeners = this.#updateEventMap(events, contextIds, false);

    // Unsubscribe from the relevant engine-internal events.
    await this.messageHandler.eventsDispatcher.update(listeners);
  }

  #assertModuleSupportsEvent(moduleName, event) {
    const rootModuleClass = this.#getRootModuleClass(moduleName);
    if (!rootModuleClass?.supportsEvent(event)) {
      throw new lazy.error.InvalidArgumentError(
        `${event} is not a valid event name`
      );
    }
  }

  #assertNonEmptyArrayWithStrings(array, variableName) {
    lazy.assert.array(
      array,
      `Expected "${variableName}" to be an array, ` + lazy.pprint`got ${array}`
    );
    lazy.assert.that(
      array => !!array.length,
      `Expected "${variableName}" array to have at least one item, ` +
        lazy.pprint`got ${array}`
    )(array);
    array.forEach(item => {
      lazy.assert.string(
        item,
        `Expected elements of "${variableName}" to be a string, ` +
          lazy.pprint`got ${item}`
      );
    });
  }

  #getBrowserIdForContextId(contextId) {
    const context = lazy.TabManager.getBrowsingContextById(contextId);
    if (!context) {
      throw new lazy.error.NoSuchFrameError(
        `Browsing context with id ${contextId} not found`
      );
    }

    return context.browserId;
  }

  #getRootModuleClass(moduleName) {
    // Modules which support event subscriptions should have a root module
    // defining supported events.
    const rootDestination = { type: lazy.RootMessageHandler.type };
    const moduleClasses = this.messageHandler.getAllModuleClasses(
      moduleName,
      rootDestination
    );

    if (!moduleClasses.length) {
      throw new lazy.error.InvalidArgumentError(
        `Module ${moduleName} does not exist`
      );
    }

    return moduleClasses[0];
  }

  #getTopBrowsingContextId(contextId) {
    const context = lazy.TabManager.getBrowsingContextById(contextId);
    if (!context) {
      throw new lazy.error.NoSuchFrameError(
        `Browsing context with id ${contextId} not found`
      );
    }
    const topContext = context.top;
    return lazy.TabManager.getIdForBrowsingContext(topContext);
  }

  /**
   * Obtain a set of events based on the given event name.
   *
   * Could contain a period for a specific event,
   * or just the module name for all events.
   *
   * @param {string} event
   *     Name of the event to process.
   *
   * @returns {Set<string>}
   *     A Set with the expanded events in the form of `<module>.<event>`.
   *
   * @throws {InvalidArgumentError}
   *     If <var>event</var> does not reference a valid event.
   */
  #obtainEvents(event) {
    const events = new Set();

    // Check if a period is present that splits the event name into the module,
    // and the actual event. Hereby only care about the first found instance.
    const index = event.indexOf(".");
    if (index >= 0) {
      const [moduleName] = event.split(".");
      this.#assertModuleSupportsEvent(moduleName, event);
      events.add(event);
    } else {
      // Interpret the name as module, and register all its available events
      const rootModuleClass = this.#getRootModuleClass(event);
      const supportedEvents = rootModuleClass?.supportedEvents;

      for (const eventName of supportedEvents) {
        events.add(eventName);
      }
    }

    return events;
  }

  /**
   * Obtain a list of event enabled browsing context ids.
   *
   * @see https://w3c.github.io/webdriver-bidi/#event-enabled-browsing-contexts
   *
   * @param {string} eventName
   *     The name of the event.
   *
   * @returns {Set<string>} The set of browsing context.
   */
  #obtainEventEnabledBrowsingContextIds(eventName) {
    const contextIds = new Set();
    for (const [
      contextId,
      events,
    ] of this.#browsingContextIdEventMap.entries()) {
      if (events.has(eventName)) {
        // Check that a browsing context still exists for a given id
        const context = lazy.TabManager.getBrowsingContextById(contextId);
        if (context) {
          contextIds.add(contextId);
        }
      }
    }

    return contextIds;
  }

  #onMessageHandlerEvent = (name, event) => {
    this.messageHandler.emitProtocolEvent(name, event);
  };

  /**
   * Update global event state for top-level browsing contexts.
   *
   * @see https://w3c.github.io/webdriver-bidi/#update-the-event-map
   *
   * @param {Array<string>} requestedEventNames
   *     The list of the event names to run the update for.
   * @param {Array<string>|null} browsingContextIds
   *     The list of the browsing context ids to update or null.
   * @param {boolean} enabled
   *     True, if events have to be enabled. Otherwise false.
   *
   * @returns {Array<Subscription>} subscriptions
   *     The list of information to subscribe/unsubscribe to.
   *
   * @throws {InvalidArgumentError}
   *     If failed unsubscribe from event from <var>requestedEventNames</var> for
   *     browsing context id from <var>browsingContextIds</var>, if present.
   */
  #updateEventMap(requestedEventNames, browsingContextIds, enabled) {
    const globalEventSet = new Set(this.#globalEventSet);
    const eventMap = structuredClone(this.#browsingContextIdEventMap);

    const eventNames = new Set();

    requestedEventNames.forEach(name => {
      this.#obtainEvents(name).forEach(event => eventNames.add(event));
    });
    const enabledEvents = new Map();
    const subscriptions = [];

    if (browsingContextIds === null) {
      // Subscribe or unsubscribe events for all browsing contexts.
      if (enabled) {
        // Subscribe to each event.

        // Get the list of all top level browsing context ids.
        const allTopBrowsingContextIds = lazy.TabManager.allBrowserUniqueIds;

        for (const eventName of eventNames) {
          if (!globalEventSet.has(eventName)) {
            const alreadyEnabledContextIds =
              this.#obtainEventEnabledBrowsingContextIds(eventName);
            globalEventSet.add(eventName);
            for (const contextId of alreadyEnabledContextIds) {
              eventMap.get(contextId).delete(eventName);

              // Since we're going to subscribe to all top-level
              // browsing context ids to not have duplicate subscriptions,
              // we have to unsubscribe from already subscribed.
              subscriptions.push({
                event: eventName,
                contextDescriptor: {
                  type: lazy.ContextDescriptorType.TopBrowsingContext,
                  id: this.#getBrowserIdForContextId(contextId),
                },
                callback: this.#onMessageHandlerEvent,
                enable: false,
              });
            }

            // Get a list of all top-level browsing context ids
            // that are not contained in alreadyEnabledContextIds.
            const newlyEnabledContextIds = allTopBrowsingContextIds.filter(
              contextId => !alreadyEnabledContextIds.has(contextId)
            );

            enabledEvents.set(eventName, newlyEnabledContextIds);

            subscriptions.push({
              event: eventName,
              contextDescriptor: {
                type: lazy.ContextDescriptorType.All,
              },
              callback: this.#onMessageHandlerEvent,
              enable: true,
            });
          }
        }
      } else {
        // Unsubscribe each event which has a global subscription.
        for (const eventName of eventNames) {
          if (globalEventSet.has(eventName)) {
            globalEventSet.delete(eventName);

            subscriptions.push({
              event: eventName,
              contextDescriptor: {
                type: lazy.ContextDescriptorType.All,
              },
              callback: this.#onMessageHandlerEvent,
              enable: false,
            });
          } else {
            throw new lazy.error.InvalidArgumentError(
              `Failed to unsubscribe from event ${eventName}`
            );
          }
        }
      }
    } else {
      // Subscribe or unsubscribe events for given list of browsing context ids.
      const targets = new Map();
      for (const contextId of browsingContextIds) {
        const topLevelContextId = this.#getTopBrowsingContextId(contextId);
        if (!eventMap.has(topLevelContextId)) {
          eventMap.set(topLevelContextId, new Set());
        }
        targets.set(topLevelContextId, eventMap.get(topLevelContextId));
      }

      for (const eventName of eventNames) {
        // Do nothing if we want to subscribe,
        // but the event has already a global subscription.
        if (enabled && this.#globalEventSet.has(eventName)) {
          continue;
        }
        for (const [contextId, target] of targets.entries()) {
          // Subscribe if an event doesn't have a subscription for a specific context id.
          if (enabled && !target.has(eventName)) {
            target.add(eventName);
            if (!enabledEvents.has(eventName)) {
              enabledEvents.set(eventName, new Set());
            }
            enabledEvents.get(eventName).add(contextId);

            subscriptions.push({
              event: eventName,
              contextDescriptor: {
                type: lazy.ContextDescriptorType.TopBrowsingContext,
                id: this.#getBrowserIdForContextId(contextId),
              },
              callback: this.#onMessageHandlerEvent,
              enable: true,
            });
          } else if (!enabled) {
            // Unsubscribe from each event for a specific context id if the event has a subscription.
            if (target.has(eventName)) {
              target.delete(eventName);

              subscriptions.push({
                event: eventName,
                contextDescriptor: {
                  type: lazy.ContextDescriptorType.TopBrowsingContext,
                  id: this.#getBrowserIdForContextId(contextId),
                },
                callback: this.#onMessageHandlerEvent,
                enable: false,
              });
            } else {
              throw new lazy.error.InvalidArgumentError(
                `Failed to unsubscribe from event ${eventName} for context ${contextId}`
              );
            }
          }
        }
      }
    }

    this.#globalEventSet = globalEventSet;
    this.#browsingContextIdEventMap = eventMap;

    return subscriptions;
  }
}

// To export the class as lower-case
export const session = SessionModule;
PK
!<+"���m�mAchrome/remote/content/webdriver-bidi/modules/root/storage.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { RootBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/RootBiDiModule.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  BytesValueType:
    "chrome://remote/content/webdriver-bidi/modules/root/network.sys.mjs",
  deserializeBytesValue:
    "chrome://remote/content/webdriver-bidi/modules/root/network.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
  UserContextManager:
    "chrome://remote/content/shared/UserContextManager.sys.mjs",
});

const PREF_COOKIE_BEHAVIOR = "network.cookie.cookieBehavior";
const PREF_COOKIE_OPTIN_PARTITIONING =
  "network.cookie.cookieBehavior.optInPartitioning";

// This is a static preference, so it cannot be modified during runtime and we can cache its value.
ChromeUtils.defineLazyGetter(lazy, "cookieBehaviorOptInPartitioning", () =>
  Services.prefs.getBoolPref(PREF_COOKIE_OPTIN_PARTITIONING)
);

const CookieFieldsMapping = {
  domain: "host",
  expiry: "expiry",
  httpOnly: "isHttpOnly",
  name: "name",
  path: "path",
  sameSite: "sameSite",
  secure: "isSecure",
  size: "size",
  value: "value",
};

const MAX_COOKIE_EXPIRY = Number.MAX_SAFE_INTEGER;

/**
 * Enum of possible partition types supported by the
 * storage.getCookies command.
 *
 * @readonly
 * @enum {PartitionType}
 */
const PartitionType = {
  Context: "context",
  StorageKey: "storageKey",
};

const PartitionKeyAttributes = ["sourceOrigin", "userContext"];

/**
 * Enum of possible SameSite types supported by the
 * storage.getCookies command.
 *
 * @readonly
 * @enum {SameSiteType}
 */
const SameSiteType = {
  [Ci.nsICookie.SAMESITE_NONE]: "none",
  [Ci.nsICookie.SAMESITE_LAX]: "lax",
  [Ci.nsICookie.SAMESITE_STRICT]: "strict",
};

class StorageModule extends RootBiDiModule {
  destroy() {}

  /**
   * Used as an argument for storage.getCookies command
   * to represent fields which should be used to filter the output
   * of the command.
   *
   * @typedef CookieFilter
   *
   * @property {string=} domain
   * @property {number=} expiry
   * @property {boolean=} httpOnly
   * @property {string=} name
   * @property {string=} path
   * @property {SameSiteType=} sameSite
   * @property {boolean=} secure
   * @property {number=} size
   * @property {Network.BytesValueType=} value
   */

  /**
   * Used as an argument for storage.getCookies command as one of the available variants
   * {BrowsingContextPartitionDescriptor} or {StorageKeyPartitionDescriptor}, to represent
   * fields should be used to build a partition key.
   *
   * @typedef PartitionDescriptor
   */

  /**
   * @typedef BrowsingContextPartitionDescriptor
   *
   * @property {PartitionType} [type=PartitionType.context]
   * @property {string} context
   */

  /**
   * @typedef StorageKeyPartitionDescriptor
   *
   * @property {PartitionType} [type=PartitionType.storageKey]
   * @property {string=} sourceOrigin
   * @property {string=} userContext
   */

  /**
   * @typedef PartitionKey
   *
   * @property {string=} sourceOrigin
   * @property {string=} userContext
   */

  /**
   * An object that holds the result of storage.getCookies command.
   *
   * @typedef GetCookiesResult
   *
   * @property {Array<Cookie>} cookies
   *    List of cookies.
   * @property {PartitionKey} partitionKey
   *    An object which represent the partition key which was used
   *    to retrieve the cookies.
   */

  /**
   * Remove zero or more cookies which match a set of provided parameters.
   *
   * @param {object=} options
   * @param {CookieFilter=} options.filter
   *     An object which holds field names and values, which
   *     should be used to filter the output of the command.
   * @param {PartitionDescriptor=} options.partition
   *     An object which holds the information which
   *     should be used to build a partition key.
   *
   * @returns {PartitionKey}
   *     An object with the partition key which was used to
   *     retrieve cookies which had to be removed.
   * @throws {InvalidArgumentError}
   *     If the provided arguments are not valid.
   * @throws {NoSuchFrameError}
   *     If the provided browsing context cannot be found.
   */
  async deleteCookies(options = {}) {
    let { filter = {} } = options;
    const { partition: partitionSpec = null } = options;

    this.#assertPartition(partitionSpec);
    filter = this.#assertCookieFilter(filter);

    const partitionKey = this.#expandStoragePartitionSpec(partitionSpec);
    const store = this.#getTheCookieStore(partitionKey);
    const cookies = this.#getMatchingCookies(store, filter);

    for (const cookie of cookies) {
      Services.cookies.remove(
        cookie.host,
        cookie.name,
        cookie.path,
        cookie.originAttributes
      );
    }

    return { partitionKey: this.#formatPartitionKey(partitionKey) };
  }

  /**
   * Retrieve zero or more cookies which match a set of provided parameters.
   *
   * @param {object=} options
   * @param {CookieFilter=} options.filter
   *     An object which holds field names and values, which
   *     should be used to filter the output of the command.
   * @param {PartitionDescriptor=} options.partition
   *     An object which holds the information which
   *     should be used to build a partition key.
   *
   * @returns {GetCookiesResult}
   *     An object which holds a list of retrieved cookies and
   *     the partition key which was used.
   * @throws {InvalidArgumentError}
   *     If the provided arguments are not valid.
   * @throws {NoSuchFrameError}
   *     If the provided browsing context cannot be found.
   */
  async getCookies(options = {}) {
    let { filter = {} } = options;
    const { partition: partitionSpec = null } = options;

    this.#assertPartition(partitionSpec);
    filter = this.#assertCookieFilter(filter);

    const partitionKey = this.#expandStoragePartitionSpec(partitionSpec);
    const store = this.#getTheCookieStore(partitionKey);
    const cookies = this.#getMatchingCookies(store, filter);
    const serializedCookies = [];

    for (const cookie of cookies) {
      serializedCookies.push(this.#serializeCookie(cookie));
    }

    return {
      cookies: serializedCookies,
      partitionKey: this.#formatPartitionKey(partitionKey),
    };
  }

  /**
   * An object representation of the cookie which should be set.
   *
   * @typedef PartialCookie
   *
   * @property {string} domain
   * @property {number=} expiry
   * @property {boolean=} httpOnly
   * @property {string} name
   * @property {string=} path
   * @property {SameSiteType=} sameSite
   * @property {boolean=} secure
   * @property {number=} size
   * @property {Network.BytesValueType} value
   */

  /**
   * Create a new cookie in a cookie store.
   *
   * @param {object=} options
   * @param {PartialCookie} options.cookie
   *     An object representation of the cookie which
   *     should be set.
   * @param {PartitionDescriptor=} options.partition
   *     An object which holds the information which
   *     should be used to build a partition key.
   *
   * @returns {PartitionKey}
   *     An object with the partition key which was used to
   *     add the cookie.
   * @throws {InvalidArgumentError}
   *     If the provided arguments are not valid.
   * @throws {NoSuchFrameError}
   *     If the provided browsing context cannot be found.
   * @throws {UnableToSetCookieError}
   *     If the cookie was not added.
   */
  async setCookie(options = {}) {
    const { cookie: cookieSpec, partition: partitionSpec = null } = options;
    lazy.assert.object(
      cookieSpec,
      lazy.pprint`Expected "cookie" to be an object, got ${cookieSpec}`
    );

    const {
      domain,
      expiry = null,
      httpOnly = null,
      name,
      path = null,
      sameSite = null,
      secure = null,
      value,
    } = cookieSpec;
    this.#assertCookie({
      domain,
      expiry,
      httpOnly,
      name,
      path,
      sameSite,
      secure,
      value,
    });
    this.#assertPartition(partitionSpec);

    const partitionKey = this.#expandStoragePartitionSpec(partitionSpec);

    // The cookie store is defined by originAttributes.
    const originAttributes = this.#getOriginAttributes(partitionKey, domain);

    // The cookie value is a network.BytesValue.
    const deserializedValue = lazy.deserializeBytesValue(value);

    // The XPCOM interface requires to be specified if a cookie is session.
    const isSession = expiry === null;

    let schemeType;
    if (secure) {
      schemeType = Ci.nsICookie.SCHEME_HTTPS;
    } else {
      schemeType = Ci.nsICookie.SCHEME_HTTP;
    }

    try {
      Services.cookies.add(
        domain,
        path === null ? "/" : path,
        name,
        deserializedValue,
        secure === null ? false : secure,
        httpOnly === null ? false : httpOnly,
        isSession,
        // The XPCOM interface requires the expiry field even for session cookies.
        expiry === null ? MAX_COOKIE_EXPIRY : expiry,
        originAttributes,
        this.#getSameSitePlatformProperty(sameSite),
        schemeType
      );
    } catch (e) {
      throw new lazy.error.UnableToSetCookieError(e);
    }

    return {
      partitionKey: this.#formatPartitionKey(partitionKey, originAttributes),
    };
  }

  #assertCookie(cookie) {
    lazy.assert.object(
      cookie,
      lazy.pprint`Expected "cookie" to be an object, got ${cookie}`
    );

    const { domain, expiry, httpOnly, name, path, sameSite, secure, value } =
      cookie;

    lazy.assert.string(
      domain,
      lazy.pprint`Expected cookie "domain" to be a string, got ${domain}`
    );

    lazy.assert.string(
      name,
      lazy.pprint`Expected cookie "name" to be a string, got ${name}`
    );

    this.#assertValue(value);

    if (expiry !== null) {
      lazy.assert.positiveInteger(
        expiry,
        lazy.pprint`Expected cookie "expiry" to be a positive integer, got ${expiry}`
      );
    }

    if (httpOnly !== null) {
      lazy.assert.boolean(
        httpOnly,
        lazy.pprint`Expected cookie "httpOnly" to be a boolean, got ${httpOnly}`
      );
    }

    if (path !== null) {
      lazy.assert.string(
        path,
        lazy.pprint`Expected cookie "path" to be a string, got ${path}`
      );
    }

    this.#assertSameSite(sameSite);

    if (secure !== null) {
      lazy.assert.boolean(
        secure,
        lazy.pprint`Expected cookie "secure" to be a boolean, got ${secure}`
      );
    }
  }

  #assertCookieFilter(filter) {
    lazy.assert.object(
      filter,
      lazy.pprint`Expected "filter" to be an object, got ${filter}`
    );

    const {
      domain = null,
      expiry = null,
      httpOnly = null,
      name = null,
      path = null,
      sameSite = null,
      secure = null,
      size = null,
      value = null,
    } = filter;

    if (domain !== null) {
      lazy.assert.string(
        domain,
        lazy.pprint`Expected filter "domain" to be a string, got ${domain}`
      );
    }

    if (expiry !== null) {
      lazy.assert.positiveInteger(
        expiry,
        lazy.pprint`Expected filter "expiry" to be a positive integer, got ${expiry}`
      );
    }

    if (httpOnly !== null) {
      lazy.assert.boolean(
        httpOnly,
        lazy.pprint`Expected filter "httpOnly" to be a boolean, got ${httpOnly}`
      );
    }

    if (name !== null) {
      lazy.assert.string(
        name,
        lazy.pprint`Expected filter "name" to be a string, got ${name}`
      );
    }

    if (path !== null) {
      lazy.assert.string(
        path,
        lazy.pprint`Expected filter "path" to be a string, got ${path}`
      );
    }

    this.#assertSameSite(sameSite, "filter.sameSite");

    if (secure !== null) {
      lazy.assert.boolean(
        secure,
        lazy.pprint`Expected filter "secure" to be a boolean, got ${secure}`
      );
    }

    if (size !== null) {
      lazy.assert.positiveInteger(
        size,
        lazy.pprint`Expected filter "size" to be a positive integer, got ${size}`
      );
    }

    if (value !== null) {
      this.#assertValue(value, "filter.value");
    }

    return {
      domain,
      expiry,
      httpOnly,
      name,
      path,
      sameSite,
      secure,
      size,
      value,
    };
  }

  #assertPartition(partitionSpec) {
    if (partitionSpec === null) {
      return;
    }
    lazy.assert.object(
      partitionSpec,
      lazy.pprint`Expected "partition" to be an object, got ${partitionSpec}`
    );

    const { type } = partitionSpec;
    lazy.assert.string(
      type,
      lazy.pprint`Expected partition "type" to be a string, got ${type}`
    );

    switch (type) {
      case PartitionType.Context: {
        const { context } = partitionSpec;
        lazy.assert.string(
          context,
          lazy.pprint`Expected partition "context" to be a string, got ${context}`
        );

        break;
      }

      case PartitionType.StorageKey: {
        const { sourceOrigin = null, userContext = null } = partitionSpec;
        if (sourceOrigin !== null) {
          lazy.assert.string(
            sourceOrigin,
            lazy.pprint`Expected partition "sourceOrigin" to be a string, got ${sourceOrigin}`
          );
          lazy.assert.that(
            sourceOrigin => URL.canParse(sourceOrigin),
            lazy.pprint`Expected partition "sourceOrigin" to be a valid URL, got ${sourceOrigin}`
          )(sourceOrigin);

          const url = new URL(sourceOrigin);
          lazy.assert.that(
            url => url.pathname === "/" && url.hash === "" && url.search === "",
            lazy.pprint`Expected partition "sourceOrigin" to contain only origin, got ${sourceOrigin}`
          )(url);
        }
        if (userContext !== null) {
          lazy.assert.string(
            userContext,
            lazy.pprint`Expected partition "userContext" to be a string, got ${userContext}`
          );

          if (!lazy.UserContextManager.hasUserContextId(userContext)) {
            throw new lazy.error.NoSuchUserContextError(
              `User Context with id ${userContext} was not found`
            );
          }
        }
        break;
      }

      default: {
        throw new lazy.error.InvalidArgumentError(
          `Expected "partition.type" to be one of ${Object.values(
            PartitionType
          )}, got ${type}`
        );
      }
    }
  }

  #assertSameSite(sameSite, fieldName = "sameSite") {
    if (sameSite !== null) {
      const sameSiteTypeValue = Object.values(SameSiteType);
      lazy.assert.in(
        sameSite,
        sameSiteTypeValue,
        `Expected "${fieldName}" to be one of ${sameSiteTypeValue}, ` +
          lazy.pprint`got ${sameSite}`
      );
    }
  }

  #assertValue(value, fieldName = "value") {
    lazy.assert.object(
      value,
      `Expected "${fieldName}" to be an object, ` + lazy.pprint`got ${value}`
    );

    const { type, value: protocolBytesValue } = value;

    const bytesValueTypeValue = Object.values(lazy.BytesValueType);
    lazy.assert.in(
      type,
      bytesValueTypeValue,
      `Expected ${fieldName} "type" to be one of ${bytesValueTypeValue}, ` +
        lazy.pprint`got ${type}`
    );

    lazy.assert.string(
      protocolBytesValue,
      `Expected ${fieldName} "value" to be string, ` +
        lazy.pprint`got ${protocolBytesValue}`
    );
  }

  /**
   * Deserialize filter.
   *
   * @see https://w3c.github.io/webdriver-bidi/#deserialize-filter
   */
  #deserializeFilter(filter) {
    const deserializedFilter = {};
    for (const [fieldName, value] of Object.entries(filter)) {
      if (value === null) {
        continue;
      }

      const deserializedName = CookieFieldsMapping[fieldName];
      let deserializedValue;

      switch (deserializedName) {
        case "sameSite":
          deserializedValue = this.#getSameSitePlatformProperty(value);
          break;

        case "value":
          deserializedValue = lazy.deserializeBytesValue(value);
          break;

        default:
          deserializedValue = value;
      }

      deserializedFilter[deserializedName] = deserializedValue;
    }

    return deserializedFilter;
  }

  /**
   * Build a partition key.
   *
   * @see https://w3c.github.io/webdriver-bidi/#expand-a-storage-partition-spec
   */
  #expandStoragePartitionSpec(partitionSpec) {
    if (partitionSpec === null) {
      partitionSpec = {};
    }

    if (partitionSpec.type === PartitionType.Context) {
      const { context: contextId } = partitionSpec;
      const browsingContext = this.#getBrowsingContext(contextId);
      const principal = Services.scriptSecurityManager.createContentPrincipal(
        browsingContext.currentURI,
        {}
      );

      // Define browsing context’s associated storage partition as combination of user context id
      // and the origin of the document in this browsing context. We also add here `isThirdPartyURI`
      // which is required to filter out third-party cookies in case they are not allowed.
      return {
        // In case we have the browsing context of an iframe here, we perform a check
        // if the URI of the top context is considered third-party to the URI of the iframe principal.
        // It's considered a third-party if base domains or hosts (in case one or both base domains
        // can not be determined) do not match.
        isThirdPartyURI: browsingContext.parent
          ? principal.isThirdPartyURI(browsingContext.top.currentURI)
          : false,
        sourceOrigin: browsingContext.currentURI.prePath,
        userContext: browsingContext.originAttributes.userContextId,
      };
    }

    const partitionKey = {};
    for (const keyName of PartitionKeyAttributes) {
      if (keyName in partitionSpec) {
        // Retrieve a platform user context id.
        if (keyName === "userContext") {
          partitionKey[keyName] = lazy.UserContextManager.getInternalIdById(
            partitionSpec.userContext
          );
        } else {
          partitionKey[keyName] = partitionSpec[keyName];
        }
      }
    }

    return partitionKey;
  }

  /**
   * Prepare the partition key in the right format for returning to a client.
   */
  #formatPartitionKey(partitionKey, originAttributes) {
    if ("userContext" in partitionKey) {
      // Exchange platform id for Webdriver BiDi id for the user context to return it to the client.
      partitionKey.userContext = lazy.UserContextManager.getIdByInternalId(
        partitionKey.userContext
      );
    }

    // If sourceOrigin matches the cookie domain we don't set the partitionKey
    // in the setCookie command. In that case we should also remove sourceOrigin
    // from the returned partitionKey.
    if (
      originAttributes &&
      "sourceOrigin" in partitionKey &&
      originAttributes.partitionKey === ""
    ) {
      delete partitionKey.sourceOrigin;
    }

    // This key is not used for partitioning and was required to only filter out third-party cookies.
    delete partitionKey.isThirdPartyURI;

    return partitionKey;
  }

  /**
   * Retrieves a browsing context based on its id.
   *
   * @param {number} contextId
   *     Id of the browsing context.
   * @returns {BrowsingContext}
   *     The browsing context.
   * @throws {NoSuchFrameError}
   *     If the browsing context cannot be found.
   */
  #getBrowsingContext(contextId) {
    const context = lazy.TabManager.getBrowsingContextById(contextId);
    if (context === null) {
      throw new lazy.error.NoSuchFrameError(
        `Browsing Context with id ${contextId} not found`
      );
    }

    return context;
  }

  /**
   * Since cookies retrieved from the platform API
   * always contain expiry even for session cookies,
   * we should check ourselves if it's a session cookie
   * and do not return expiry in case it is.
   */
  #getCookieExpiry(cookie) {
    const { expiry, isSession } = cookie;
    return isSession ? null : expiry;
  }

  #getCookieSize(cookie) {
    const { name, value } = cookie;
    return name.length + value.length;
  }

  /**
   * Filter and serialize given cookies with provided filter.
   *
   * @see https://w3c.github.io/webdriver-bidi/#get-matching-cookies
   */
  #getMatchingCookies(cookieStore, filter) {
    const cookies = [];
    const deserializedFilter = this.#deserializeFilter(filter);

    for (const storedCookie of cookieStore) {
      if (this.#matchCookie(storedCookie, deserializedFilter)) {
        cookies.push(storedCookie);
      }
    }
    return cookies;
  }

  /**
   * Prepare the data in the required for platform API format.
   */
  #getOriginAttributes(partitionKey, domain) {
    const originAttributes = {};

    if (partitionKey.sourceOrigin) {
      if (
        "isThirdPartyURI" in partitionKey &&
        domain &&
        !this.#shouldIncludePartitionedCookies() &&
        partitionKey.sourceOrigin !== "about:"
      ) {
        // This is a workaround until CHIPS support is enabled (see Bug 1898253).
        // It handles the "context" type partitioning of the `setCookie` command
        // (when domain is provided) and if partitioned cookies are disabled,
        // but ignore `about` pаges.
        const principal =
          Services.scriptSecurityManager.createContentPrincipalFromOrigin(
            partitionKey.sourceOrigin
          );

        // Do not set partition key if the cookie domain matches the `sourceOrigin`.
        if (principal.host.endsWith(domain)) {
          originAttributes.partitionKey = "";
        } else {
          originAttributes.partitionKey = ChromeUtils.getPartitionKeyFromURL(
            partitionKey.sourceOrigin
          );
        }
      } else {
        originAttributes.partitionKey = ChromeUtils.getPartitionKeyFromURL(
          partitionKey.sourceOrigin
        );
      }
    }
    if ("userContext" in partitionKey) {
      originAttributes.userContextId = partitionKey.userContext;
    }

    return originAttributes;
  }

  #getSameSitePlatformProperty(sameSite) {
    switch (sameSite) {
      case "lax": {
        return Ci.nsICookie.SAMESITE_LAX;
      }
      case "strict": {
        return Ci.nsICookie.SAMESITE_STRICT;
      }
    }

    return Ci.nsICookie.SAMESITE_NONE;
  }

  /**
   * Return a cookie store of the storage partition for a given storage partition key.
   *
   * The implementation differs here from the spec, since in gecko there is no
   * direct way to get all the cookies for a given partition key.
   *
   * @see https://w3c.github.io/webdriver-bidi/#get-the-cookie-store
   */
  #getTheCookieStore(storagePartitionKey) {
    let store = [];

    // Prepare the data in the format required for the platform API.
    const originAttributes = this.#getOriginAttributes(storagePartitionKey);

    // Retrieve the cookies which exactly match a built partition attributes.
    const cookiesWithOriginAttributes =
      Services.cookies.getCookiesWithOriginAttributes(
        JSON.stringify(originAttributes)
      );

    const isFirstPartyOrCrossSiteAllowed =
      !storagePartitionKey.isThirdPartyURI ||
      this.#shouldIncludeCrossSiteCookie();

    // Check if we accessing the first party storage or cross-site cookies are allowed.
    if (isFirstPartyOrCrossSiteAllowed) {
      // In case we want to get the cookies for a certain `sourceOrigin`,
      // we have to separately retrieve cookies for a hostname built from `sourceOrigin`,
      // and with `partitionKey` equal an empty string to retrieve the cookies that which were set
      // by this hostname but without `partitionKey`, e.g. with `document.cookie`.
      if (storagePartitionKey.sourceOrigin) {
        const url = new URL(storagePartitionKey.sourceOrigin);
        const hostname = url.hostname;

        const principal = Services.scriptSecurityManager.createContentPrincipal(
          Services.io.newURI(url),
          {}
        );
        const isSecureProtocol = principal.isOriginPotentiallyTrustworthy;

        // We want to keep `userContext` id here, if it's present,
        // but set the `partitionKey` to an empty string.
        const cookiesMatchingHostname =
          Services.cookies.getCookiesWithOriginAttributes(
            JSON.stringify({ ...originAttributes, partitionKey: "" }),
            hostname
          );
        for (const cookie of cookiesMatchingHostname) {
          // Ignore secure cookies for non-secure protocols.
          if (cookie.isSecure && !isSecureProtocol) {
            continue;
          }
          store.push(cookie);
        }
      }

      store = store.concat(cookiesWithOriginAttributes);
    }
    // If we're trying to access the store in the third party context and
    // the preferences imply that we shouldn't include cross site cookies,
    // but we should include partitioned cookies, add only partitioned cookies.
    else if (this.#shouldIncludePartitionedCookies()) {
      for (const cookie of cookiesWithOriginAttributes) {
        if (cookie.isPartitioned) {
          store.push(cookie);
        }
      }
    }

    return store;
  }

  /**
   * Match a provided cookie with provided filter.
   *
   * @see https://w3c.github.io/webdriver-bidi/#match-cookie
   */
  #matchCookie(storedCookie, filter) {
    for (const [fieldName, value] of Object.entries(filter)) {
      // Since we set `null` to not specified values, we have to check for `null` here
      // and not match on these values.
      if (value === null) {
        continue;
      }

      let storedCookieValue = storedCookie[fieldName];

      // The platform represantation of cookie doesn't contain a size field,
      // so we have to calculate it to match.
      if (fieldName === "size") {
        storedCookieValue = this.#getCookieSize(storedCookie);
      }

      if (storedCookieValue !== value) {
        return false;
      }
    }

    return true;
  }

  /**
   * Serialize a cookie.
   *
   * @see https://w3c.github.io/webdriver-bidi/#serialize-cookie
   */
  #serializeCookie(storedCookie) {
    const cookie = {};
    for (const [serializedName, cookieName] of Object.entries(
      CookieFieldsMapping
    )) {
      switch (serializedName) {
        case "expiry": {
          const expiry = this.#getCookieExpiry(storedCookie);
          if (expiry !== null) {
            cookie.expiry = expiry;
          }
          break;
        }

        case "sameSite":
          cookie.sameSite = SameSiteType[storedCookie.sameSite];
          break;

        case "size":
          cookie.size = this.#getCookieSize(storedCookie);
          break;

        case "value":
          // Bug 1879309. Add support for non-UTF8 cookies,
          // when a byte representation of value is available.
          // For now, use a value field, which is returned as a string.
          cookie.value = {
            type: lazy.BytesValueType.String,
            value: storedCookie.value,
          };
          break;

        default:
          cookie[serializedName] = storedCookie[cookieName];
      }
    }

    return cookie;
  }

  #shouldIncludeCrossSiteCookie() {
    const cookieBehavior = Services.prefs.getIntPref(PREF_COOKIE_BEHAVIOR);

    if (
      cookieBehavior === Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN ||
      cookieBehavior ===
        Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN
    ) {
      return false;
    }

    return true;
  }

  #shouldIncludePartitionedCookies() {
    const cookieBehavior = Services.prefs.getIntPref(PREF_COOKIE_BEHAVIOR);

    return (
      cookieBehavior ===
        Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN &&
      lazy.cookieBehaviorOptInPartitioning
    );
  }
}

export const storage = StorageModule;
PK
!<�����A�AQchrome/remote/content/webdriver-bidi/modules/windowglobal/browsingContext.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { WindowGlobalBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/WindowGlobalBiDiModule.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  accessibility:
    "chrome://remote/content/shared/webdriver/Accessibility.sys.mjs",
  AnimationFramePromise: "chrome://remote/content/shared/Sync.sys.mjs",
  assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
  ClipRectangleType:
    "chrome://remote/content/webdriver-bidi/modules/root/browsingContext.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  LoadListener: "chrome://remote/content/shared/listeners/LoadListener.sys.mjs",
  LocatorType:
    "chrome://remote/content/webdriver-bidi/modules/root/browsingContext.sys.mjs",
  OriginType:
    "chrome://remote/content/webdriver-bidi/modules/root/browsingContext.sys.mjs",
  OwnershipModel: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
  PollPromise: "chrome://remote/content/shared/Sync.sys.mjs",
  pprint: "chrome://remote/content/shared/Format.sys.mjs",
});

const DOCUMENT_FRAGMENT_NODE = 11;
const DOCUMENT_NODE = 9;
const ELEMENT_NODE = 1;

const ORDERED_NODE_SNAPSHOT_TYPE = 7;

class BrowsingContextModule extends WindowGlobalBiDiModule {
  #loadListener;
  #subscribedEvents;

  constructor(messageHandler) {
    super(messageHandler);

    // Setup the LoadListener as early as possible.
    this.#loadListener = new lazy.LoadListener(this.messageHandler.window);
    this.#loadListener.on("DOMContentLoaded", this.#onDOMContentLoaded);
    this.#loadListener.on("load", this.#onLoad);

    // Set of event names which have active subscriptions.
    this.#subscribedEvents = new Set();
  }

  destroy() {
    this.#loadListener.destroy();
    this.#subscribedEvents = null;
  }

  /**
   * Collect nodes using accessibility attributes.
   *
   * @see https://w3c.github.io/webdriver-bidi/#collect-nodes-using-accessibility-attributes
   */
  async #collectNodesUsingAccessibilityAttributes(
    contextNodes,
    selector,
    maxReturnedNodeCount,
    returnedNodes
  ) {
    if (returnedNodes === null) {
      returnedNodes = [];
    }

    for (const contextNode of contextNodes) {
      let match = true;

      if (contextNode.nodeType === ELEMENT_NODE) {
        if ("role" in selector) {
          const role = await lazy.accessibility.getComputedRole(contextNode);

          if (selector.role !== role) {
            match = false;
          }
        }

        if ("name" in selector) {
          const name = await lazy.accessibility.getAccessibleName(contextNode);
          if (selector.name !== name) {
            match = false;
          }
        }
      } else {
        match = false;
      }

      if (match) {
        if (
          maxReturnedNodeCount !== null &&
          returnedNodes.length === maxReturnedNodeCount
        ) {
          break;
        }
        returnedNodes.push(contextNode);
      }

      const childNodes = [...contextNode.children];

      await this.#collectNodesUsingAccessibilityAttributes(
        childNodes,
        selector,
        maxReturnedNodeCount,
        returnedNodes
      );
    }

    return returnedNodes;
  }

  #getNavigationInfo(data) {
    // Note: the navigation id is collected in the parent-process and will be
    // added via event interception by the windowglobal-in-root module.
    return {
      context: this.messageHandler.context,
      timestamp: Date.now(),
      url: data.target.URL,
    };
  }

  #getOriginRectangle(origin) {
    const win = this.messageHandler.window;

    if (origin === lazy.OriginType.viewport) {
      const viewport = win.visualViewport;
      // Until it's clarified in the scope of the issue:
      // https://github.com/w3c/webdriver-bidi/issues/592
      // if we should take into account scrollbar dimensions, when calculating
      // the viewport size, we match the behavior of WebDriver Classic,
      // meaning we include scrollbar dimensions.
      return new DOMRect(
        viewport.pageLeft,
        viewport.pageTop,
        win.innerWidth,
        win.innerHeight
      );
    }

    const documentElement = win.document.documentElement;
    return new DOMRect(
      0,
      0,
      documentElement.scrollWidth,
      documentElement.scrollHeight
    );
  }

  /**
   * Locate nodes using accessibility attributes.
   *
   * @see https://w3c.github.io/webdriver-bidi/#locate-nodes-using-accessibility-attributes
   */
  async #locateNodesUsingAccessibilityAttributes(
    contextNodes,
    selector,
    maxReturnedNodeCount
  ) {
    if (!("role" in selector) && !("name" in selector)) {
      throw new lazy.error.InvalidSelectorError(
        "Locating nodes by accessibility attributes requires `role` or `name` arguments"
      );
    }

    return this.#collectNodesUsingAccessibilityAttributes(
      contextNodes,
      selector,
      maxReturnedNodeCount,
      null
    );
  }

  /**
   * Locate nodes using css selector.
   *
   * @see https://w3c.github.io/webdriver-bidi/#locate-nodes-using-css
   */
  #locateNodesUsingCss(contextNodes, selector, maxReturnedNodeCount) {
    const returnedNodes = [];

    for (const contextNode of contextNodes) {
      let elements;
      try {
        elements = contextNode.querySelectorAll(selector);
      } catch (e) {
        throw new lazy.error.InvalidSelectorError(
          `${e.message}: "${selector}"`
        );
      }

      if (maxReturnedNodeCount === null) {
        returnedNodes.push(...elements);
      } else {
        for (const element of elements) {
          returnedNodes.push(element);

          if (returnedNodes.length === maxReturnedNodeCount) {
            return returnedNodes;
          }
        }
      }
    }

    return returnedNodes;
  }

  /**
   * Locate nodes using XPath.
   *
   * @see https://w3c.github.io/webdriver-bidi/#locate-nodes-using-xpath
   */
  #locateNodesUsingXPath(contextNodes, selector, maxReturnedNodeCount) {
    const returnedNodes = [];

    for (const contextNode of contextNodes) {
      let evaluationResult;
      try {
        evaluationResult = this.messageHandler.window.document.evaluate(
          selector,
          contextNode,
          null,
          ORDERED_NODE_SNAPSHOT_TYPE,
          null
        );
      } catch (e) {
        const errorMessage = `${e.message}: "${selector}"`;
        if (DOMException.isInstance(e) && e.name === "SyntaxError") {
          throw new lazy.error.InvalidSelectorError(errorMessage);
        }

        throw new lazy.error.UnknownError(errorMessage);
      }

      for (let index = 0; index < evaluationResult.snapshotLength; index++) {
        const node = evaluationResult.snapshotItem(index);
        returnedNodes.push(node);

        if (
          maxReturnedNodeCount !== null &&
          returnedNodes.length === maxReturnedNodeCount
        ) {
          return returnedNodes;
        }
      }
    }

    return returnedNodes;
  }

  /**
   * Normalize rectangle. This ensures that the resulting rect has
   * positive width and height dimensions.
   *
   * @see https://w3c.github.io/webdriver-bidi/#normalise-rect
   *
   * @param {DOMRect} rect
   *     An object which describes the size and position of a rectangle.
   *
   * @returns {DOMRect} Normalized rectangle.
   */
  #normalizeRect(rect) {
    let { x, y, width, height } = rect;

    if (width < 0) {
      x += width;
      width = -width;
    }

    if (height < 0) {
      y += height;
      height = -height;
    }

    return new DOMRect(x, y, width, height);
  }

  #onDOMContentLoaded = (eventName, data) => {
    if (this.#subscribedEvents.has("browsingContext._documentInteractive")) {
      this.messageHandler.emitEvent("browsingContext._documentInteractive", {
        baseURL: data.target.baseURI,
        contextId: this.messageHandler.contextId,
        documentURL: data.target.URL,
        innerWindowId: this.messageHandler.innerWindowId,
        readyState: data.target.readyState,
      });
    }

    if (this.#subscribedEvents.has("browsingContext.domContentLoaded")) {
      this.emitEvent(
        "browsingContext.domContentLoaded",
        this.#getNavigationInfo(data)
      );
    }
  };

  #onLoad = (eventName, data) => {
    if (this.#subscribedEvents.has("browsingContext.load")) {
      this.emitEvent("browsingContext.load", this.#getNavigationInfo(data));
    }
  };

  /**
   * Create a new rectangle which will be an intersection of
   * rectangles specified as arguments.
   *
   * @see https://w3c.github.io/webdriver-bidi/#rectangle-intersection
   *
   * @param {DOMRect} rect1
   *     An object which describes the size and position of a rectangle.
   * @param {DOMRect} rect2
   *     An object which describes the size and position of a rectangle.
   *
   * @returns {DOMRect} Rectangle, representing an intersection of <var>rect1</var> and <var>rect2</var>.
   */
  #rectangleIntersection(rect1, rect2) {
    rect1 = this.#normalizeRect(rect1);
    rect2 = this.#normalizeRect(rect2);

    const x_min = Math.max(rect1.x, rect2.x);
    const x_max = Math.min(rect1.x + rect1.width, rect2.x + rect2.width);

    const y_min = Math.max(rect1.y, rect2.y);
    const y_max = Math.min(rect1.y + rect1.height, rect2.y + rect2.height);

    const width = Math.max(x_max - x_min, 0);
    const height = Math.max(y_max - y_min, 0);

    return new DOMRect(x_min, y_min, width, height);
  }

  #startListening() {
    if (this.#subscribedEvents.size == 0) {
      this.#loadListener.startListening();
    }
  }

  #stopListening() {
    if (this.#subscribedEvents.size == 0) {
      this.#loadListener.stopListening();
    }
  }

  #subscribeEvent(event) {
    switch (event) {
      case "browsingContext._documentInteractive":
        this.#startListening();
        this.#subscribedEvents.add("browsingContext._documentInteractive");
        break;
      case "browsingContext.domContentLoaded":
        this.#startListening();
        this.#subscribedEvents.add("browsingContext.domContentLoaded");
        break;
      case "browsingContext.load":
        this.#startListening();
        this.#subscribedEvents.add("browsingContext.load");
        break;
    }
  }

  #unsubscribeEvent(event) {
    switch (event) {
      case "browsingContext._documentInteractive":
        this.#subscribedEvents.delete("browsingContext._documentInteractive");
        break;
      case "browsingContext.domContentLoaded":
        this.#subscribedEvents.delete("browsingContext.domContentLoaded");
        break;
      case "browsingContext.load":
        this.#subscribedEvents.delete("browsingContext.load");
        break;
    }

    this.#stopListening();
  }

  /**
   * Internal commands
   */

  _applySessionData(params) {
    // TODO: Bug 1775231. Move this logic to a shared module or an abstract
    // class.
    const { category } = params;
    if (category === "event") {
      const filteredSessionData = params.sessionData.filter(item =>
        this.messageHandler.matchesContext(item.contextDescriptor)
      );
      for (const event of this.#subscribedEvents.values()) {
        const hasSessionItem = filteredSessionData.some(
          item => item.value === event
        );
        // If there are no session items for this context, we should unsubscribe from the event.
        if (!hasSessionItem) {
          this.#unsubscribeEvent(event);
        }
      }

      // Subscribe to all events, which have an item in SessionData.
      for (const { value } of filteredSessionData) {
        this.#subscribeEvent(value);
      }
    }
  }

  /**
   * Waits until the viewport has reached the new dimensions.
   *
   * @param {object} options
   * @param {number} options.height
   *     Expected height the viewport will resize to.
   * @param {number} options.width
   *     Expected width the viewport will resize to.
   *
   * @returns {Promise}
   *     Promise that resolves when the viewport has been resized.
   */
  async _awaitViewportDimensions(options) {
    const { height, width } = options;

    const win = this.messageHandler.window;
    let resized;

    // Updates for background tabs are throttled, and we also have to make
    // sure that the new browser dimensions have been received by the content
    // process. As such wait for the next animation frame.
    await lazy.AnimationFramePromise(win);

    const checkBrowserSize = () => {
      if (win.innerWidth === width && win.innerHeight === height) {
        resized();
      }
    };

    return new Promise(resolve => {
      resized = resolve;

      win.addEventListener("resize", checkBrowserSize);

      // Trigger a layout flush in case none happened yet.
      checkBrowserSize();
    }).finally(() => {
      win.removeEventListener("resize", checkBrowserSize);
    });
  }

  /**
   * Waits until the visibility state of the document has the expected value.
   *
   * @param {object} options
   * @param {number} options.value
   *     Expected value of the visibility state.
   *
   * @returns {Promise}
   *     Promise that resolves when the visibility state has the expected value.
   */
  async _awaitVisibilityState(options) {
    const { value } = options;
    const win = this.messageHandler.window;

    await lazy.PollPromise((resolve, reject) => {
      if (win.document.visibilityState === value) {
        resolve();
      } else {
        reject();
      }
    });
  }

  _getBaseURL() {
    return this.messageHandler.window.document.baseURI;
  }

  _getScreenshotRect(params = {}) {
    const { clip, origin } = params;

    const originRect = this.#getOriginRectangle(origin);
    let clipRect = originRect;

    if (clip !== null) {
      switch (clip.type) {
        case lazy.ClipRectangleType.Box: {
          clipRect = new DOMRect(
            clip.x + originRect.x,
            clip.y + originRect.y,
            clip.width,
            clip.height
          );
          break;
        }

        case lazy.ClipRectangleType.Element: {
          const realm = this.messageHandler.getRealm();
          const element = this.deserialize(clip.element, realm);
          const viewportRect = this.#getOriginRectangle(
            lazy.OriginType.viewport
          );
          const elementRect = element.getBoundingClientRect();

          clipRect = new DOMRect(
            elementRect.x + viewportRect.x,
            elementRect.y + viewportRect.y,
            elementRect.width,
            elementRect.height
          );
          break;
        }
      }
    }

    return this.#rectangleIntersection(originRect, clipRect);
  }

  async _locateNodes(params = {}) {
    const { locator, maxNodeCount, serializationOptions, startNodes } = params;

    const realm = this.messageHandler.getRealm();

    const contextNodes = [];
    if (startNodes === null) {
      contextNodes.push(this.messageHandler.window.document.documentElement);
    } else {
      for (const serializedStartNode of startNodes) {
        const startNode = this.deserialize(serializedStartNode, realm);
        lazy.assert.that(
          startNode =>
            Node.isInstance(startNode) &&
            [DOCUMENT_FRAGMENT_NODE, DOCUMENT_NODE, ELEMENT_NODE].includes(
              startNode.nodeType
            ),
          lazy.pprint`Expected an item of "startNodes" to be an Element, got ${startNode}`
        )(startNode);

        contextNodes.push(startNode);
      }
    }

    let returnedNodes;
    switch (locator.type) {
      case lazy.LocatorType.accessibility: {
        returnedNodes = await this.#locateNodesUsingAccessibilityAttributes(
          contextNodes,
          locator.value,
          maxNodeCount
        );
        break;
      }
      case lazy.LocatorType.css: {
        returnedNodes = this.#locateNodesUsingCss(
          contextNodes,
          locator.value,
          maxNodeCount
        );
        break;
      }
      case lazy.LocatorType.xpath: {
        returnedNodes = this.#locateNodesUsingXPath(
          contextNodes,
          locator.value,
          maxNodeCount
        );
        break;
      }
    }

    const serializedNodes = [];
    const seenNodeIds = new Map();
    for (const returnedNode of returnedNodes) {
      serializedNodes.push(
        this.serialize(
          returnedNode,
          serializationOptions,
          lazy.OwnershipModel.None,
          realm,
          { seenNodeIds }
        )
      );
    }

    return {
      serializedNodes,
      _extraData: { seenNodeIds },
    };
  }
}

export const browsingContext = BrowsingContextModule;
PK
!<��Ev��Gchrome/remote/content/webdriver-bidi/modules/windowglobal/input.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { WindowGlobalBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/WindowGlobalBiDiModule.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  action: "chrome://remote/content/shared/webdriver/Actions.sys.mjs",
  dom: "chrome://remote/content/shared/DOM.sys.mjs",
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  event: "chrome://remote/content/shared/webdriver/Event.sys.mjs",
});

class InputModule extends WindowGlobalBiDiModule {
  #actionState;

  constructor(messageHandler) {
    super(messageHandler);

    this.#actionState = null;
  }

  destroy() {}

  async performActions(options) {
    const { actions } = options;
    if (this.#actionState === null) {
      this.#actionState = new lazy.action.State();
    }

    await this.#deserializeActionOrigins(actions);
    const actionChain = lazy.action.Chain.fromJSON(this.#actionState, actions);

    await actionChain.dispatch(this.#actionState, this.messageHandler.window);

    // Terminate the current wheel transaction if there is one. Wheel
    // transactions should not live longer than a single action chain.
    ChromeUtils.endWheelTransaction();
  }

  async releaseActions() {
    if (this.#actionState === null) {
      return;
    }
    await this.#actionState.release(this.messageHandler.window);
    this.#actionState = null;
  }

  async setFiles(options) {
    const { element: sharedReference, files } = options;

    const element = await this.#deserializeElementSharedReference(
      sharedReference
    );

    if (
      !HTMLInputElement.isInstance(element) ||
      element.type !== "file" ||
      element.disabled
    ) {
      throw new lazy.error.UnableToSetFileInputError(
        `Element needs to be an <input> element with type "file" and not disabled`
      );
    }

    if (files.length > 1 && !element.hasAttribute("multiple")) {
      throw new lazy.error.UnableToSetFileInputError(
        `Element should have an attribute "multiple" set when trying to set more than 1 file`
      );
    }

    const fileObjects = [];
    for (const file of files) {
      try {
        fileObjects.push(await File.createFromFileName(file));
      } catch (e) {
        throw new lazy.error.UnsupportedOperationError(
          `Failed to add file ${file} (${e})`
        );
      }
    }

    const selectedFiles = Array.from(element.files);

    const intersection = fileObjects.filter(fileObject =>
      selectedFiles.some(
        selectedFile =>
          // Compare file fields to identify if the files are equal.
          // TODO: Bug 1883856. Add check for full path or use a different way
          // to compare files when it's available.
          selectedFile.name === fileObject.name &&
          selectedFile.size === fileObject.size &&
          selectedFile.type === fileObject.type
      )
    );

    if (
      intersection.length === selectedFiles.length &&
      selectedFiles.length === fileObjects.length
    ) {
      lazy.event.cancel(element);
    } else {
      element.mozSetFileArray(fileObjects);

      lazy.event.input(element);
      lazy.event.change(element);
    }
  }

  /**
   * In the provided array of input.SourceActions, replace all origins matching
   * the input.ElementOrigin production with the Element corresponding to this
   * origin.
   *
   * Note that this method replaces the content of the `actions` in place, and
   * does not return a new array.
   *
   * @param {Array<input.SourceActions>} actions
   *     The array of SourceActions to deserialize.
   * @returns {Promise}
   *     A promise which resolves when all ElementOrigin origins have been
   *     deserialized.
   */
  async #deserializeActionOrigins(actions) {
    const promises = [];

    if (!Array.isArray(actions)) {
      // Silently ignore invalid action chains because they are fully parsed later.
      return Promise.resolve();
    }

    for (const actionsByTick of actions) {
      if (!Array.isArray(actionsByTick?.actions)) {
        // Silently ignore invalid actions because they are fully parsed later.
        return Promise.resolve();
      }

      for (const action of actionsByTick.actions) {
        if (action?.origin?.type === "element") {
          promises.push(
            (async () => {
              action.origin = await this.#deserializeElementSharedReference(
                action.origin.element
              );
            })()
          );
        }
      }
    }

    return Promise.all(promises);
  }

  async #deserializeElementSharedReference(sharedReference) {
    if (typeof sharedReference?.sharedId !== "string") {
      throw new lazy.error.InvalidArgumentError(
        `Expected "element" to be a SharedReference, got: ${sharedReference}`
      );
    }

    const realm = this.messageHandler.getRealm();

    const element = this.deserialize(sharedReference, realm);
    if (!lazy.dom.isElement(element)) {
      throw new lazy.error.NoSuchElementError(
        `No element found for shared id: ${sharedReference.sharedId}`
      );
    }

    return element;
  }
}

export const input = InputModule;
PK
!<��A0��Echrome/remote/content/webdriver-bidi/modules/windowglobal/log.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { WindowGlobalBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/WindowGlobalBiDiModule.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ConsoleAPIListener:
    "chrome://remote/content/shared/listeners/ConsoleAPIListener.sys.mjs",
  ConsoleListener:
    "chrome://remote/content/shared/listeners/ConsoleListener.sys.mjs",
  isChromeFrame: "chrome://remote/content/shared/Stack.sys.mjs",
  OwnershipModel: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
  setDefaultSerializationOptions:
    "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
});

class LogModule extends WindowGlobalBiDiModule {
  #consoleAPIListener;
  #consoleMessageListener;
  #subscribedEvents;

  constructor(messageHandler) {
    super(messageHandler);

    // Create the console-api listener and listen on "message" events.
    this.#consoleAPIListener = new lazy.ConsoleAPIListener(
      this.messageHandler.innerWindowId
    );
    this.#consoleAPIListener.on("message", this.#onConsoleAPIMessage);

    // Create the console listener and listen on error messages.
    this.#consoleMessageListener = new lazy.ConsoleListener(
      this.messageHandler.innerWindowId
    );
    this.#consoleMessageListener.on("error", this.#onJavaScriptError);

    // Set of event names which have active subscriptions.
    this.#subscribedEvents = new Set();
  }

  destroy() {
    this.#consoleAPIListener.off("message", this.#onConsoleAPIMessage);
    this.#consoleAPIListener.destroy();
    this.#consoleMessageListener.off("error", this.#onJavaScriptError);
    this.#consoleMessageListener.destroy();

    this.#subscribedEvents = null;
  }

  #buildSource(realm) {
    return {
      realm: realm.id,
      context: this.messageHandler.context,
    };
  }

  /**
   * Map the internal stacktrace representation to a WebDriver BiDi
   * compatible one.
   *
   * Currently chrome frames will be filtered out until chrome scope
   * is supported (bug 1722679).
   *
   * @param {Array<StackFrame>=} stackTrace
   *     Stack frames to process.
   *
   * @returns {object=} Object, containing the list of frames as `callFrames`.
   */
  #buildStackTrace(stackTrace) {
    if (stackTrace == undefined) {
      return undefined;
    }

    const callFrames = stackTrace
      .filter(frame => !lazy.isChromeFrame(frame))
      .map(frame => {
        return {
          columnNumber: frame.columnNumber - 1,
          functionName: frame.functionName,
          lineNumber: frame.lineNumber - 1,
          url: frame.filename,
        };
      });

    return { callFrames };
  }

  #getLogEntryLevelFromConsoleMethod(method) {
    switch (method) {
      case "assert":
      case "error":
        return "error";
      case "debug":
      case "trace":
        return "debug";
      case "warn":
        return "warn";
      default:
        return "info";
    }
  }

  #onConsoleAPIMessage = (eventName, data = {}) => {
    const {
      // `arguments` cannot be used as variable name in functions
      arguments: messageArguments,
      // `level` corresponds to the console method used
      level: method,
      stacktrace,
      timeStamp,
    } = data;

    // Step numbers below refer to the specifications at
    //   https://w3c.github.io/webdriver-bidi/#event-log-entryAdded

    // Translate the console message method to a log.LogEntry level
    const logEntrylevel = this.#getLogEntryLevelFromConsoleMethod(method);

    // Use the message's timeStamp or fallback on the current time value.
    const timestamp = timeStamp || Date.now();

    // Start assembling the text representation of the message.
    let text = "";

    // Formatters have already been applied at this points.
    // message.arguments corresponds to the "formatted args" from the
    // specifications.

    // Concatenate all formatted arguments in text
    // TODO: For m1 we only support string arguments, so we rely on the builtin
    // toString for each argument which will be available in message.arguments.
    const args = messageArguments || [];
    text += args.map(String).join(" ");

    const defaultRealm = this.messageHandler.getRealm();
    const serializedArgs = [];
    const seenNodeIds = new Map();

    // Serialize each arg as remote value.
    for (const arg of args) {
      // Note that we can pass a default realm for now since realms are only
      // involved when creating object references, which will not happen with
      // OwnershipModel.None. This will be revisited in Bug 1742589.
      serializedArgs.push(
        this.serialize(
          Cu.waiveXrays(arg),
          lazy.setDefaultSerializationOptions(),
          lazy.OwnershipModel.None,
          defaultRealm,
          { seenNodeIds }
        )
      );
    }

    // Set source to an object which contains realm and browsing context.
    // TODO: Bug 1742589. Use an actual realm from which the event came from.
    const source = this.#buildSource(defaultRealm);

    // Set stack trace only for certain methods.
    let stackTrace;
    if (["assert", "error", "trace", "warn"].includes(method)) {
      stackTrace = this.#buildStackTrace(stacktrace);
    }

    // Build the ConsoleLogEntry
    const entry = {
      type: "console",
      method,
      source,
      args: serializedArgs,
      level: logEntrylevel,
      text,
      timestamp,
      stackTrace,
      _extraData: { seenNodeIds },
    };

    // TODO: Those steps relate to:
    // - emitting associated BrowsingContext. See log.entryAdded full support
    //   in https://bugzilla.mozilla.org/show_bug.cgi?id=1724669#c0
    // - handling cases where session doesn't exist or the event is not
    //   monitored. The implementation differs from the spec here because we
    //   only react to events if there is a session & if the session subscribed
    //   to those events.

    this.emitEvent("log.entryAdded", entry);
  };

  #onJavaScriptError = (eventName, data = {}) => {
    const { level, message, stacktrace, timeStamp } = data;
    const defaultRealm = this.messageHandler.getRealm();

    // Build the JavascriptLogEntry
    const entry = {
      type: "javascript",
      level,
      // TODO: Bug 1742589. Use an actual realm from which the event came from.
      source: this.#buildSource(defaultRealm),
      text: message,
      timestamp: timeStamp || Date.now(),
      stackTrace: this.#buildStackTrace(stacktrace),
    };

    this.emitEvent("log.entryAdded", entry);
  };

  #subscribeEvent(event) {
    if (event === "log.entryAdded") {
      this.#consoleAPIListener.startListening();
      this.#consoleMessageListener.startListening();
      this.#subscribedEvents.add(event);
    }
  }

  #unsubscribeEvent(event) {
    if (event === "log.entryAdded") {
      this.#consoleAPIListener.stopListening();
      this.#consoleMessageListener.stopListening();
      this.#subscribedEvents.delete(event);
    }
  }

  /**
   * Internal commands
   */

  _applySessionData(params) {
    // TODO: Bug 1775231. Move this logic to a shared module or an abstract
    // class.
    const { category } = params;
    if (category === "event") {
      const filteredSessionData = params.sessionData.filter(item =>
        this.messageHandler.matchesContext(item.contextDescriptor)
      );
      for (const event of this.#subscribedEvents.values()) {
        const hasSessionItem = filteredSessionData.some(
          item => item.value === event
        );
        // If there are no session items for this context, we should unsubscribe from the event.
        if (!hasSessionItem) {
          this.#unsubscribeEvent(event);
        }
      }

      // Subscribe to all events, which have an item in SessionData.
      for (const { value } of filteredSessionData) {
        this.#subscribeEvent(value);
      }
    }
  }
}

export const log = LogModule;
PK
!<�U��||Ichrome/remote/content/webdriver-bidi/modules/windowglobal/network.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { WindowGlobalBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/WindowGlobalBiDiModule.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  BeforeStopRequestListener:
    "chrome://remote/content/shared/listeners/BeforeStopRequestListener.sys.mjs",
});

class NetworkModule extends WindowGlobalBiDiModule {
  #beforeStopRequestListener;
  #subscribedEvents;

  constructor(messageHandler) {
    super(messageHandler);

    this.#beforeStopRequestListener = new lazy.BeforeStopRequestListener();
    this.#beforeStopRequestListener.on(
      "beforeStopRequest",
      this.#onBeforeStopRequest
    );

    // Set of event names which have active subscriptions.
    this.#subscribedEvents = new Set();
  }

  destroy() {
    this.#beforeStopRequestListener.destroy();
    this.#subscribedEvents = null;
  }

  #onBeforeStopRequest = (event, data) => {
    this.messageHandler.emitEvent("network._beforeStopRequest", {
      channelId: data.channel.channelId,
      contextId: this.messageHandler.contextId,
      decodedBodySize: data.decodedBodySize,
    });
  };

  #startListening() {
    if (this.#subscribedEvents.size == 0) {
      this.#beforeStopRequestListener.startListening();
    }
  }

  #stopListening() {
    if (this.#subscribedEvents.size == 0) {
      this.#beforeStopRequestListener.stopListening();
    }
  }

  #subscribeEvent(event) {
    switch (event) {
      case "network.responseCompleted":
        this.#startListening();
        this.#subscribedEvents.add("network.responseCompleted");
        break;
    }
  }

  #unsubscribeEvent(event) {
    switch (event) {
      case "network.responseCompleted":
        this.#subscribedEvents.delete("network.responseCompleted");
        break;
    }

    this.#stopListening();
  }

  /**
   * Internal commands
   */

  _applySessionData(params) {
    // TODO: Bug 1775231. Move this logic to a shared module or an abstract
    // class.
    const { category } = params;
    if (category === "event") {
      const filteredSessionData = params.sessionData.filter(item =>
        this.messageHandler.matchesContext(item.contextDescriptor)
      );
      for (const event of this.#subscribedEvents.values()) {
        const hasSessionItem = filteredSessionData.some(
          item => item.value === event
        );
        // If there are no session items for this context, we should unsubscribe from the event.
        if (!hasSessionItem) {
          this.#unsubscribeEvent(event);
        }
      }

      // Subscribe to all events, which have an item in SessionData.
      for (const { value } of filteredSessionData) {
        this.#subscribeEvent(value);
      }
    }
  }
}

export const network = NetworkModule;
PK
!<�9|D;;Hchrome/remote/content/webdriver-bidi/modules/windowglobal/script.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { WindowGlobalBiDiModule } from "chrome://remote/content/webdriver-bidi/modules/WindowGlobalBiDiModule.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs",
  getFramesFromStack: "chrome://remote/content/shared/Stack.sys.mjs",
  isChromeFrame: "chrome://remote/content/shared/Stack.sys.mjs",
  OwnershipModel: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
  setDefaultSerializationOptions:
    "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
  stringify: "chrome://remote/content/webdriver-bidi/RemoteValue.sys.mjs",
});

/**
 * @typedef {string} EvaluationStatus
 */

/**
 * Enum of possible evaluation states.
 *
 * @readonly
 * @enum {EvaluationStatus}
 */
const EvaluationStatus = {
  Normal: "normal",
  Throw: "throw",
};

class ScriptModule extends WindowGlobalBiDiModule {
  #observerListening;
  #preloadScripts;

  constructor(messageHandler) {
    super(messageHandler);

    // Set of structs with an item named expression, which is a string,
    // and an item named sandbox which is a string or null.
    this.#preloadScripts = new Set();
  }

  destroy() {
    this.#preloadScripts = null;

    this.#stopObserving();
  }

  observe(subject, topic) {
    if (topic !== "document-element-inserted") {
      return;
    }

    const window = subject?.defaultView;

    // Ignore events without a window and those from other tabs.
    if (window === this.messageHandler.window) {
      this.#evaluatePreloadScripts();
    }
  }

  #buildExceptionDetails(
    exception,
    stack,
    realm,
    resultOwnership,
    seenNodeIds
  ) {
    exception = this.#toRawObject(exception);

    // A stacktrace is mandatory to build exception details and a missing stack
    // means we encountered an unexpected issue. Throw with an explicit error.
    if (!stack) {
      throw new Error(
        `Missing stack, unable to build exceptionDetails for exception: ${lazy.stringify(
          exception
        )}`
      );
    }

    const frames = lazy.getFramesFromStack(stack) || [];
    const callFrames = frames
      // Remove chrome/internal frames
      .filter(frame => !lazy.isChromeFrame(frame))
      // Translate frames from getFramesFromStack to frames expected by
      // WebDriver BiDi.
      .map(frame => {
        return {
          columnNumber: frame.columnNumber - 1,
          functionName: frame.functionName,
          lineNumber: frame.lineNumber - 1,
          url: frame.filename,
        };
      });

    return {
      columnNumber: stack.column - 1,
      exception: this.serialize(
        exception,
        lazy.setDefaultSerializationOptions(),
        resultOwnership,
        realm,
        { seenNodeIds }
      ),
      lineNumber: stack.line - 1,
      stackTrace: { callFrames },
      text: lazy.stringify(exception),
    };
  }

  async #buildReturnValue(
    rv,
    realm,
    awaitPromise,
    resultOwnership,
    serializationOptions
  ) {
    let evaluationStatus, exception, result, stack;

    if ("return" in rv) {
      evaluationStatus = EvaluationStatus.Normal;
      if (
        awaitPromise &&
        // Only non-primitive return values are wrapped in Debugger.Object.
        rv.return instanceof Debugger.Object &&
        rv.return.isPromise
      ) {
        try {
          // Force wrapping the promise resolution result in a Debugger.Object
          // wrapper for consistency with the synchronous codepath.
          const asyncResult = await rv.return.unsafeDereference();
          result = realm.globalObjectReference.makeDebuggeeValue(asyncResult);
        } catch (asyncException) {
          evaluationStatus = EvaluationStatus.Throw;
          exception =
            realm.globalObjectReference.makeDebuggeeValue(asyncException);

          // If the returned promise was rejected by calling its reject callback
          // the stack will be available on promiseResolutionSite.
          // Otherwise, (eg. rejected Promise chained with a then() call) we
          // fallback on the promiseAllocationSite.
          stack =
            rv.return.promiseResolutionSite || rv.return.promiseAllocationSite;
        }
      } else {
        // rv.return is a Debugger.Object or a primitive.
        result = rv.return;
      }
    } else if ("throw" in rv) {
      // rv.throw will be set if the evaluation synchronously failed, either if
      // the script contains a syntax error or throws an exception.
      evaluationStatus = EvaluationStatus.Throw;
      exception = rv.throw;
      stack = rv.stack;
    }

    const seenNodeIds = new Map();
    switch (evaluationStatus) {
      case EvaluationStatus.Normal:
        const dataSuccess = this.serialize(
          this.#toRawObject(result),
          serializationOptions,
          resultOwnership,
          realm,
          { seenNodeIds }
        );

        return {
          evaluationStatus,
          realmId: realm.id,
          result: dataSuccess,
          _extraData: { seenNodeIds },
        };
      case EvaluationStatus.Throw:
        const dataThrow = this.#buildExceptionDetails(
          exception,
          stack,
          realm,
          resultOwnership,
          seenNodeIds
        );

        return {
          evaluationStatus,
          exceptionDetails: dataThrow,
          realmId: realm.id,
          _extraData: { seenNodeIds },
        };
      default:
        throw new lazy.error.UnsupportedOperationError(
          `Unsupported completion value for expression evaluation`
        );
    }
  }

  /**
   * Emit "script.message" event with provided data.
   *
   * @param {Realm} realm
   * @param {ChannelProperties} channelProperties
   * @param {RemoteValue} message
   */
  #emitScriptMessage = (realm, channelProperties, message) => {
    const {
      channel,
      ownership: ownershipType = lazy.OwnershipModel.None,
      serializationOptions,
    } = channelProperties;

    const seenNodeIds = new Map();
    const data = this.serialize(
      this.#toRawObject(message),
      lazy.setDefaultSerializationOptions(serializationOptions),
      ownershipType,
      realm,
      { seenNodeIds }
    );

    this.emitEvent("script.message", {
      channel,
      data,
      source: this.#getSource(realm),
      _extraData: { seenNodeIds },
    });
  };

  #evaluatePreloadScripts() {
    let resolveBlockerPromise;
    const blockerPromise = new Promise(resolve => {
      resolveBlockerPromise = resolve;
    });

    // Block script parsing.
    this.messageHandler.window.document.blockParsing(blockerPromise);
    for (const script of this.#preloadScripts.values()) {
      const {
        arguments: commandArguments,
        functionDeclaration,
        sandbox,
      } = script;
      const realm = this.messageHandler.getRealm({ sandboxName: sandbox });
      const deserializedArguments = commandArguments.map(arg =>
        this.deserialize(arg, realm, {
          emitScriptMessage: this.#emitScriptMessage,
        })
      );
      const rv = realm.executeInGlobalWithBindings(
        functionDeclaration,
        deserializedArguments
      );

      if ("throw" in rv) {
        const exception = this.#toRawObject(rv.throw);
        realm.reportError(lazy.stringify(exception), rv.stack);
      }
    }

    // Continue script parsing.
    resolveBlockerPromise();
  }

  #getSource(realm) {
    return {
      realm: realm.id,
      context: this.messageHandler.context,
    };
  }

  #startObserving() {
    if (!this.#observerListening) {
      Services.obs.addObserver(this, "document-element-inserted");
      this.#observerListening = true;
    }
  }

  #stopObserving() {
    if (this.#observerListening) {
      Services.obs.removeObserver(this, "document-element-inserted");
      this.#observerListening = false;
    }
  }

  #toRawObject(maybeDebuggerObject) {
    if (maybeDebuggerObject instanceof Debugger.Object) {
      // Retrieve the referent for the provided Debugger.object.
      // See https://firefox-source-docs.mozilla.org/devtools-user/debugger-api/debugger.object/index.html
      const rawObject = maybeDebuggerObject.unsafeDereference();

      // TODO: Getters for Maps and Sets iterators return "Opaque" objects and
      // are not iterable. RemoteValue.sys.mjs' serializer should handle calling
      // waiveXrays on Maps/Sets/... and then unwaiveXrays on entries but since
      // we serialize with maxDepth=1, calling waiveXrays once on the root
      // object allows to return correctly serialized values.
      return Cu.waiveXrays(rawObject);
    }

    // If maybeDebuggerObject was not a Debugger.Object, it is a primitive value
    // which can be used as is.
    return maybeDebuggerObject;
  }

  /**
   * Call a function in the current window global.
   *
   * @param {object} options
   * @param {boolean} options.awaitPromise
   *     Determines if the command should wait for the return value of the
   *     expression to resolve, if this return value is a Promise.
   * @param {Array<RemoteValue>=} options.commandArguments
   *     The arguments to pass to the function call.
   * @param {string} options.functionDeclaration
   *     The body of the function to call.
   * @param {string=} options.realmId
   *     The id of the realm.
   * @param {OwnershipModel} options.resultOwnership
   *     The ownership model to use for the results of this evaluation.
   * @param {string=} options.sandbox
   *     The name of the sandbox.
   * @param {SerializationOptions=} options.serializationOptions
   *     An object which holds the information of how the result of evaluation
   *     in case of ECMAScript objects should be serialized.
   * @param {RemoteValue=} options.thisParameter
   *     The value of the this keyword for the function call.
   * @param {boolean=} options.userActivation
   *     Determines whether execution should be treated as initiated by user.
   *
   * @returns {object}
   *     - evaluationStatus {EvaluationStatus} One of "normal", "throw".
   *     - exceptionDetails {ExceptionDetails=} the details of the exception if
   *     the evaluation status was "throw".
   *     - result {RemoteValue=} the result of the evaluation serialized as a
   *     RemoteValue if the evaluation status was "normal".
   */
  async callFunctionDeclaration(options) {
    const {
      awaitPromise,
      commandArguments = null,
      functionDeclaration,
      realmId = null,
      resultOwnership,
      sandbox: sandboxName = null,
      serializationOptions,
      thisParameter = null,
      userActivation,
    } = options;

    const realm = this.messageHandler.getRealm({ realmId, sandboxName });

    const deserializedArguments =
      commandArguments !== null
        ? commandArguments.map(arg =>
            this.deserialize(arg, realm, {
              emitScriptMessage: this.#emitScriptMessage,
            })
          )
        : [];

    const deserializedThis =
      thisParameter !== null
        ? this.deserialize(thisParameter, realm, {
            emitScriptMessage: this.#emitScriptMessage,
          })
        : null;

    realm.userActivationEnabled = userActivation;

    const rv = realm.executeInGlobalWithBindings(
      functionDeclaration,
      deserializedArguments,
      deserializedThis
    );

    return this.#buildReturnValue(
      rv,
      realm,
      awaitPromise,
      resultOwnership,
      serializationOptions
    );
  }

  /**
   * Delete the provided handles from the realm corresponding to the provided
   * sandbox name.
   *
   * @param {object=} options
   * @param {Array<string>} options.handles
   *     Array of handle ids to disown.
   * @param {string=} options.realmId
   *     The id of the realm.
   * @param {string=} options.sandbox
   *     The name of the sandbox.
   */
  disownHandles(options) {
    const { handles, realmId = null, sandbox: sandboxName = null } = options;
    const realm = this.messageHandler.getRealm({ realmId, sandboxName });
    for (const handle of handles) {
      realm.removeObjectHandle(handle);
    }
  }

  /**
   * Evaluate a provided expression in the current window global.
   *
   * @param {object} options
   * @param {boolean} options.awaitPromise
   *     Determines if the command should wait for the return value of the
   *     expression to resolve, if this return value is a Promise.
   * @param {string} options.expression
   *     The expression to evaluate.
   * @param {string=} options.realmId
   *     The id of the realm.
   * @param {OwnershipModel} options.resultOwnership
   *     The ownership model to use for the results of this evaluation.
   * @param {string=} options.sandbox
   *     The name of the sandbox.
   * @param {boolean=} options.userActivation
   *     Determines whether execution should be treated as initiated by user.
   *
   * @returns {object}
   *     - evaluationStatus {EvaluationStatus} One of "normal", "throw".
   *     - exceptionDetails {ExceptionDetails=} the details of the exception if
   *     the evaluation status was "throw".
   *     - result {RemoteValue=} the result of the evaluation serialized as a
   *     RemoteValue if the evaluation status was "normal".
   */
  async evaluateExpression(options) {
    const {
      awaitPromise,
      expression,
      realmId = null,
      resultOwnership,
      sandbox: sandboxName = null,
      serializationOptions,
      userActivation,
    } = options;

    const realm = this.messageHandler.getRealm({ realmId, sandboxName });

    realm.userActivationEnabled = userActivation;

    const rv = realm.executeInGlobal(expression);

    return this.#buildReturnValue(
      rv,
      realm,
      awaitPromise,
      resultOwnership,
      serializationOptions
    );
  }

  /**
   * Get realms for the current window global.
   *
   * @returns {Array<object>}
   *     - context {BrowsingContext} The browsing context, associated with the realm.
   *     - origin {string} The serialization of an origin.
   *     - realm {string} The realm unique identifier.
   *     - sandbox {string=} The name of the sandbox.
   *     - type {RealmType.Window} The window realm type.
   */
  getWindowRealms() {
    return Array.from(this.messageHandler.realms.values()).map(realm => {
      const { context, origin, realm: id, sandbox, type } = realm.getInfo();
      return { context, origin, realm: id, sandbox, type };
    });
  }

  /**
   * Internal commands
   */

  _applySessionData(params) {
    if (params.category === "preload-script") {
      this.#preloadScripts = new Set();
      for (const item of params.sessionData) {
        if (this.messageHandler.matchesContext(item.contextDescriptor)) {
          this.#preloadScripts.add(item.value);
        }
      }

      if (this.#preloadScripts.size) {
        this.#startObserving();
      }
    }
  }
}

export const script = ScriptModule;
PK
!<�~�%%Ychrome/remote/content/webdriver-bidi/modules/windowglobal-in-root/browsingContext.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
});

class BrowsingContextModule extends Module {
  destroy() {}

  interceptEvent(name, payload) {
    if (
      name == "browsingContext.domContentLoaded" ||
      name == "browsingContext.load"
    ) {
      const browsingContext = payload.context;
      if (!lazy.TabManager.isValidCanonicalBrowsingContext(browsingContext)) {
        // Discard events for invalid browsing contexts.
        return null;
      }

      // Resolve browsing context to a Navigable id.
      payload.context =
        lazy.TabManager.getIdForBrowsingContext(browsingContext);

      // Resolve navigation id.
      const navigation =
        this.messageHandler.navigationManager.getNavigationForBrowsingContext(
          browsingContext
        );
      payload.navigation = navigation ? navigation.navigationId : null;
    }

    return payload;
  }
}

export const browsingContext = BrowsingContextModule;
PK
!<��&�Mchrome/remote/content/webdriver-bidi/modules/windowglobal-in-root/log.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  processExtraData:
    "chrome://remote/content/webdriver-bidi/modules/Intercept.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
});

class LogModule extends Module {
  destroy() {}

  interceptEvent(name, payload) {
    if (name == "log.entryAdded") {
      const browsingContext = payload.source.context;
      if (!lazy.TabManager.isValidCanonicalBrowsingContext(browsingContext)) {
        // Discard events for invalid browsing contexts.
        return null;
      }

      // Resolve browsing context to a Navigable id.
      payload.source.context =
        lazy.TabManager.getIdForBrowsingContext(browsingContext);

      payload = lazy.processExtraData(this.messageHandler.sessionId, payload);
    }

    return payload;
  }
}

export const log = LogModule;
PK
!<(��ooQchrome/remote/content/webdriver-bidi/modules/windowglobal-in-root/network.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  RootMessageHandler:
    "chrome://remote/content/shared/messagehandler/RootMessageHandler.sys.mjs",
});

class NetworkModule extends Module {
  destroy() {}

  interceptEvent(name, payload) {
    if (name == "network._beforeStopRequest") {
      const { channelId, decodedBodySize } = payload;
      this.messageHandler.handleCommand({
        moduleName: "network",
        commandName: "_setDecodedBodySize",
        params: { channelId, decodedBodySize },
        destination: {
          type: lazy.RootMessageHandler.type,
        },
      });
    }

    // The _beforeStopRequest event is only used in order to feed the
    // decodedBodySize map in the parent process. Return null to swallow the
    // event.
    return null;
  }
}

export const network = NetworkModule;
PK
!<J43���Pchrome/remote/content/webdriver-bidi/modules/windowglobal-in-root/script.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Module } from "chrome://remote/content/shared/messagehandler/Module.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  processExtraData:
    "chrome://remote/content/webdriver-bidi/modules/Intercept.sys.mjs",
  TabManager: "chrome://remote/content/shared/TabManager.sys.mjs",
});

class ScriptModule extends Module {
  destroy() {}

  interceptEvent(name, payload) {
    if (name == "script.message") {
      const browsingContext = payload.source.context;
      if (!lazy.TabManager.isValidCanonicalBrowsingContext(browsingContext)) {
        // Discard events for invalid browsing contexts.
        return null;
      }

      // Resolve browsing context to a Navigable id.
      payload.source.context =
        lazy.TabManager.getIdForBrowsingContext(browsingContext);

      payload = lazy.processExtraData(this.messageHandler.sessionId, payload);
    }

    return payload;
  }
}

export const script = ScriptModule;
PK
!<�����$modules/AboutCertViewerChild.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { RemotePageChild } from "resource://gre/actors/RemotePageChild.sys.mjs";

export class AboutCertViewerChild extends RemotePageChild {}
PK
!<Aϡ]]%modules/AboutCertViewerParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const TYPE_CA = 1;
const TYPE_USER = 2;
const TYPE_EMAIL = 4;
const TYPE_SERVER = 8;

export class AboutCertViewerParent extends JSWindowActorParent {
  getCertificates() {
    let certs = {
      [TYPE_CA]: [],
      [TYPE_USER]: [],
      [TYPE_EMAIL]: [],
      [TYPE_SERVER]: [],
    };
    let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
      Ci.nsIX509CertDB
    );
    let certcache = certdb.getCerts();
    for (let cert of certcache) {
      for (let certType of Object.keys(certs).map(Number)) {
        if (certType & cert.certType) {
          certs[certType].push({
            displayName: cert.displayName,
            derb64: cert.getBase64DERString(),
          });
        }
      }
    }
    return certs;
  }

  receiveMessage(aMessage) {
    switch (aMessage.name) {
      case "getCertificates":
        return this.getCertificates();
    }

    return undefined;
  }
}
PK
!<MShf44modules/AboutPagesUtils.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * vim: sw=2 ts=2 sts=2 expandtab
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export const AboutPagesUtils = {};

ChromeUtils.defineLazyGetter(AboutPagesUtils, "visibleAboutUrls", () => {
  const urls = [];
  const rx = /@mozilla.org\/network\/protocol\/about;1\?what\=(.*)$/;
  for (const cid in Cc) {
    const result = cid.match(rx);
    if (!result) {
      continue;
    }
    const [, aboutType] = result;
    try {
      const am = Cc[cid].getService(Ci.nsIAboutModule);
      const uri = Services.io.newURI(`about:${aboutType}`);
      const flags = am.getURIFlags(uri);
      if (!(flags & Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT)) {
        urls.push(`about:${aboutType}`);
      }
    } catch (e) {
      // getService() might have thrown if the component doesn't actually
      // implement nsIAboutModule
    }
  }
  urls.sort();
  return urls;
});
PK
!<��)r����modules/AboutReader.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ReaderMode } from "resource://gre/modules/ReaderMode.sys.mjs";

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};
let gScrollPositions = new Map();
let lastSelectedTheme = "auto";
let improvedTextMenuEnabled = Services.prefs.getBoolPref(
  "reader.improved_text_menu.enabled",
  false
);

ChromeUtils.defineESModuleGetters(lazy, {
  AsyncPrefs: "resource://gre/modules/AsyncPrefs.sys.mjs",
  NarrateControls: "resource://gre/modules/narrate/NarrateControls.sys.mjs",
});

ChromeUtils.defineLazyGetter(
  lazy,
  "numberFormat",
  () => new Services.intl.NumberFormat(undefined)
);
ChromeUtils.defineLazyGetter(
  lazy,
  "pluralRules",
  () => new Services.intl.PluralRules(undefined)
);
ChromeUtils.defineLazyGetter(
  lazy,
  "l10n",
  () => new Localization(["toolkit/about/aboutReader.ftl"], true)
);

const FONT_TYPE_L10N_IDS = {
  serif: "about-reader-font-type-serif",
  "sans-serif": "about-reader-font-type-sans-serif",
  monospace: "about-reader-font-type-monospace",
};

const FONT_WEIGHT_L10N_IDS = {
  light: "about-reader-font-weight-light",
  regular: "about-reader-font-weight-regular",
  bold: "about-reader-font-weight-bold",
};

const DEFAULT_TEXT_LAYOUT = {
  fontSize: 5,
  fontType: "sans-serif",
  fontWeight: "regular",
  contentWidth: 3,
  lineSpacing: 4,
  characterSpacing: 1,
  wordSpacing: 1,
  textAlignment: "start",
};

const COLORSCHEME_L10N_IDS = {
  auto: "about-reader-color-auto-theme",
  light: "about-reader-color-light-theme",
  dark: "about-reader-color-dark-theme",
  sepia: "about-reader-color-sepia-theme",
  contrast: "about-reader-color-contrast-theme",
  gray: "about-reader-color-gray-theme",
};

const CUSTOM_THEME_COLOR_INPUTS = [
  "foreground",
  "background",
  "unvisited-links",
  "visited-links",
  "selection-highlight",
];

const COLORS_MENU_TABS = ["fxtheme", "customtheme"];

const DEFAULT_COLORS = {
  background: "#FFFFFF",
  foreground: "#14151A",
  "unvisited-links": "#0060DF",
  "visited-links": "#321C64",
  "selection-highlight": "#FFFFCC",
};

Services.telemetry.setEventRecordingEnabled("readermode", true);

const zoomOnCtrl =
  Services.prefs.getIntPref("mousewheel.with_control.action", 3) == 3;
const zoomOnMeta =
  Services.prefs.getIntPref("mousewheel.with_meta.action", 1) == 3;
const isAppLocaleRTL = Services.locale.isAppLocaleRTL;

export var AboutReader = function (
  actor,
  articlePromise,
  docContentType = "document",
  docTitle = ""
) {
  let win = actor.contentWindow;
  let url = this._getOriginalUrl(win);
  if (
    !(
      url.startsWith("http://") ||
      url.startsWith("https://") ||
      url.startsWith("file://")
    )
  ) {
    let errorMsg =
      "Only http://, https:// and file:// URLs can be loaded in about:reader.";
    if (Services.prefs.getBoolPref("reader.errors.includeURLs")) {
      errorMsg += " Tried to load: " + url + ".";
    }
    console.error(errorMsg);
    win.location.href = "about:blank";
    return;
  }

  let doc = win.document;
  if (isAppLocaleRTL) {
    doc.dir = "rtl";
  }
  doc.documentElement.setAttribute("platform", AppConstants.platform);

  doc.title = docTitle;

  this._actor = actor;

  this._docRef = Cu.getWeakReference(doc);
  this._winRef = Cu.getWeakReference(win);
  this._innerWindowId = win.windowGlobalChild.innerWindowId;

  this._article = null;
  this._languagePromise = new Promise(resolve => {
    this._foundLanguage = resolve;
  });

  if (articlePromise) {
    this._articlePromise = articlePromise;
  }

  this._headerElementRef = Cu.getWeakReference(
    doc.querySelector(".reader-header")
  );
  this._domainElementRef = Cu.getWeakReference(
    doc.querySelector(".reader-domain")
  );
  this._titleElementRef = Cu.getWeakReference(
    doc.querySelector(".reader-title")
  );
  this._readTimeElementRef = Cu.getWeakReference(
    doc.querySelector(".reader-estimated-time")
  );
  this._creditsElementRef = Cu.getWeakReference(
    doc.querySelector(".reader-credits")
  );
  this._contentElementRef = Cu.getWeakReference(
    doc.querySelector(".moz-reader-content")
  );
  this._toolbarContainerElementRef = Cu.getWeakReference(
    doc.querySelector(".toolbar-container")
  );
  this._toolbarElementRef = Cu.getWeakReference(
    doc.querySelector(".reader-controls")
  );
  this._messageElementRef = Cu.getWeakReference(
    doc.querySelector(".reader-message")
  );
  this._containerElementRef = Cu.getWeakReference(
    doc.querySelector(".container")
  );

  doc.addEventListener("mousedown", this);
  doc.addEventListener("keydown", this);
  doc.addEventListener("click", this);
  doc.addEventListener("blur", this, true);
  doc.addEventListener("touchstart", this);

  win.addEventListener("pagehide", this);
  win.addEventListener("resize", this);
  win.addEventListener("wheel", this, { passive: false });

  this.colorSchemeMediaList = win.matchMedia("(prefers-color-scheme: dark)");
  this.colorSchemeMediaList.addEventListener("change", this);

  this.forcedColorsMediaList = win.matchMedia("(forced-colors)");
  this.forcedColorsMediaList.addEventListener("change", this);

  this._topScrollChange = this._topScrollChange.bind(this);
  this._intersectionObs = new win.IntersectionObserver(this._topScrollChange, {
    root: null,
    threshold: [0, 1],
  });
  this._intersectionObs.observe(doc.querySelector(".top-anchor"));

  Services.obs.addObserver(this, "inner-window-destroyed");

  this._setupButton("close-button", this._onReaderClose.bind(this));

  // we're ready for any external setup, send a signal for that.
  this._actor.sendAsyncMessage("Reader:OnSetup");

  // set up segmented tab controls for colors menu.
  this._setupColorsTabs(
    COLORS_MENU_TABS,
    this._handleColorsTabClick.bind(this)
  );

  // fetch color scheme values from prefs.
  let colorsMenuColorSchemeValues = JSON.parse(
    Services.prefs.getCharPref("reader.color_scheme.values")
  );
  // remove contrast and gray options from regular menu.
  let colorSchemeValues = [...colorsMenuColorSchemeValues];
  colorSchemeValues.splice(colorSchemeValues.length - 2, 2);

  let colorsMenuColorSchemeOptions = colorsMenuColorSchemeValues.map(value => ({
    l10nId: COLORSCHEME_L10N_IDS[value],
    groupName: "color-scheme",
    value,
    itemClass: value + "-button",
  }));

  let colorSchemeOptions = colorSchemeValues.map(value => ({
    l10nId: COLORSCHEME_L10N_IDS[value],
    groupName: "color-scheme",
    value,
    itemClass: value + "-button",
  }));
  let colorScheme = Services.prefs.getCharPref("reader.color_scheme");

  if (Services.prefs.getBoolPref("reader.colors_menu.enabled", false)) {
    doc.getElementById("regular-color-scheme").hidden = true;
    doc.getElementById("custom-colors-color-scheme").hidden = false;

    this._setupSegmentedButton(
      "colors-menu-color-scheme-buttons",
      colorsMenuColorSchemeOptions,
      colorScheme,
      this._setColorSchemePref.bind(this)
    );
    this._setupCustomColors(
      CUSTOM_THEME_COLOR_INPUTS,
      "custom-colors-selection",
      "about-reader-custom-colors"
    );
    this._setupButton(
      "custom-colors-reset-button",
      this._resetCustomColors.bind(this)
    );
    this._handleThemeFocus();
  } else {
    this._setupSegmentedButton(
      "color-scheme-buttons",
      colorSchemeOptions,
      colorScheme,
      this._setColorSchemePref.bind(this)
    );
  }

  this._setColorSchemePref(colorScheme);

  let fontTypeOptions = [
    {
      l10nId: "about-reader-font-type-sans-serif",
      groupName: "font-type",
      value: "sans-serif",
      itemClass: "sans-serif-button",
    },
    {
      l10nId: "about-reader-font-type-serif",
      groupName: "font-type",
      value: "serif",
      itemClass: "serif-button",
    },
  ];

  // TODO: Move font type pref getting alongside other prefs when old menu is retired.
  let fontType = Services.prefs.getCharPref("reader.font_type", "sans-serif");

  // Differentiates between the tick mark labels for width vs spacing controls
  // for localization purposes.
  const [standardSpacingLabel, wideSpacingLabel] = lazy.l10n.formatMessagesSync(
    [
      "about-reader-slider-label-spacing-standard",
      "about-reader-slider-label-spacing-wide",
    ]
  );

  let contentWidthSliderOptions = {
    min: 1,
    max: 9,
    ticks: 9,
    tickLabels: `[]`,
    l10nId: "about-reader-content-width-label",
    icon: "chrome://global/skin/reader/content-width-20.svg",
    telemetryId: "content-width-slider",
  };

  let lineSpacingSliderOptions = {
    min: 1,
    max: 9,
    ticks: 9,
    tickLabels: `[]`,
    l10nId: "about-reader-line-spacing-label",
    icon: "chrome://global/skin/reader/line-spacing-20.svg",
    telemetryId: "line-spacing-slider",
  };

  let characterSpacingSliderOptions = {
    min: 1,
    max: 9,
    ticks: 9,
    tickLabels: `["${standardSpacingLabel.value}", "${wideSpacingLabel.value}"]`,
    l10nId: "about-reader-character-spacing-label",
    icon: "chrome://global/skin/reader/character-spacing-20.svg",
    telemetryId: "character-spacing-slider",
  };

  let wordSpacingSliderOptions = {
    min: 1,
    max: 9,
    ticks: 9,
    tickLabels: `["${standardSpacingLabel.value}", "${wideSpacingLabel.value}"]`,
    l10nId: "about-reader-word-spacing-label",
    icon: "chrome://global/skin/reader/word-spacing-20.svg",
    telemetryId: "word-spacing-slider",
  };

  let textAlignmentOptions = [
    {
      l10nId: "about-reader-text-alignment-left",
      groupName: "text-alignment",
      value: "left",
      itemClass: "left-align-button",
    },
    {
      l10nId: "about-reader-text-alignment-center",
      groupName: "text-alignment",
      value: "center",
      itemClass: "center-align-button",
    },
    {
      l10nId: "about-reader-text-alignment-right",
      groupName: "text-alignment",
      value: "right",
      itemClass: "right-align-button",
    },
  ];

  // If the page is rtl, reverse order of text alignment options.
  if (isAppLocaleRTL) {
    textAlignmentOptions = textAlignmentOptions.reverse();
  }

  if (improvedTextMenuEnabled) {
    doc.getElementById("regular-text-menu").hidden = true;
    doc.getElementById("improved-text-menu").hidden = false;

    let selectorFontTypeValues = ["sans-serif", "serif", "monospace"];
    try {
      selectorFontTypeValues = JSON.parse(
        Services.prefs.getCharPref("reader.font_type.values")
      );
    } catch (e) {
      console.error(
        "There was an error fetching the font type values pref: ",
        e.message
      );
    }
    this._setupSelector(
      "font-type",
      selectorFontTypeValues,
      fontType,
      this._setFontTypeSelector.bind(this),
      FONT_TYPE_L10N_IDS
    );
    this._setFontTypeSelector(fontType);

    let fontWeightValues = ["regular", "light", "bold"];
    try {
      fontWeightValues = JSON.parse(
        Services.prefs.getCharPref("reader.font_weight.values")
      );
    } catch (e) {
      console.error(
        "There was an error fetching the font weight values pref: ",
        e.message
      );
    }
    let fontWeight = Services.prefs.getCharPref(
      "reader.font_weight",
      "regular"
    );
    this._setupSelector(
      "font-weight",
      fontWeightValues,
      fontWeight,
      this._setFontWeight.bind(this),
      FONT_WEIGHT_L10N_IDS
    );
    this._setFontWeight(fontWeight);

    let contentWidth = Services.prefs.getIntPref("reader.content_width", 3);
    this._setupSlider(
      "content-width",
      contentWidthSliderOptions,
      contentWidth,
      this._setContentWidthSlider.bind(this)
    );
    this._setContentWidthSlider(contentWidth);

    let lineSpacing = Services.prefs.getIntPref("reader.line_height", 4);
    this._setupSlider(
      "line-spacing",
      lineSpacingSliderOptions,
      lineSpacing,
      this._setLineSpacing.bind(this)
    );
    this._setLineSpacing(lineSpacing);

    let characterSpacing = Services.prefs.getIntPref(
      "reader.character_spacing",
      1
    );
    this._setupSlider(
      "character-spacing",
      characterSpacingSliderOptions,
      characterSpacing,
      this._setCharacterSpacing.bind(this)
    );
    this._setCharacterSpacing(characterSpacing);

    let wordSpacing = Services.prefs.getIntPref("reader.word_spacing", 1);
    this._setupSlider(
      "word-spacing",
      wordSpacingSliderOptions,
      wordSpacing,
      this._setWordSpacing.bind(this)
    );
    this._setWordSpacing(wordSpacing);

    let textAlignment = Services.prefs.getCharPref(
      "reader.text_alignment",
      "start"
    );
    this._setupSegmentedButton(
      "text-alignment-buttons",
      textAlignmentOptions,
      textAlignment,
      this._setTextAlignment.bind(this)
    );
    this._setTextAlignment(textAlignment);

    this._setupButton(
      "text-layout-reset-button",
      this._resetTextLayout.bind(this)
    );

    this._handleTextLayoutFocus();
  } else {
    this._setupSegmentedButton(
      "font-type-buttons",
      fontTypeOptions,
      fontType,
      this._setFontType.bind(this)
    );

    this._setupContentWidthButtons();

    this._setupLineHeightButtons();

    this._setFontType(fontType);
  }

  this._setupFontSizeButtons();

  if (win.speechSynthesis && Services.prefs.getBoolPref("narrate.enabled")) {
    new lazy.NarrateControls(win, this._languagePromise);
  }

  this._loadArticle(docContentType);
};

AboutReader.prototype = {
  _BLOCK_IMAGES_SELECTOR:
    ".content p > img:only-child, " +
    ".content p > a:only-child > img:only-child, " +
    ".content .wp-caption img, " +
    ".content figure img",

  _TABLES_SELECTOR: ".content table",

  FONT_SIZE_MIN: 1,

  FONT_SIZE_LEGACY_MAX: 9,

  FONT_SIZE_MAX: 15,

  FONT_SIZE_EXTENDED_VALUES: [32, 40, 56, 72, 96, 128],

  get _doc() {
    return this._docRef.get();
  },

  get _win() {
    return this._winRef.get();
  },

  get _headerElement() {
    return this._headerElementRef.get();
  },

  get _domainElement() {
    return this._domainElementRef.get();
  },

  get _titleElement() {
    return this._titleElementRef.get();
  },

  get _readTimeElement() {
    return this._readTimeElementRef.get();
  },

  get _creditsElement() {
    return this._creditsElementRef.get();
  },

  get _contentElement() {
    return this._contentElementRef.get();
  },

  get _toolbarElement() {
    return this._toolbarElementRef.get();
  },

  get _toolbarContainerElement() {
    return this._toolbarContainerElementRef.get();
  },

  get _messageElement() {
    return this._messageElementRef.get();
  },

  get _containerElement() {
    return this._containerElementRef.get();
  },

  get _isToolbarVertical() {
    if (this._toolbarVertical !== undefined) {
      return this._toolbarVertical;
    }
    return (this._toolbarVertical = Services.prefs.getBoolPref(
      "reader.toolbar.vertical"
    ));
  },

  receiveMessage({ data, name }) {
    const doc = this._doc;
    switch (name) {
      case "Reader:AddButton": {
        if (data.id && data.image && !doc.getElementsByClassName(data.id)[0]) {
          let btn = doc.createElement("button");
          btn.dataset.buttonid = data.id;
          btn.dataset.telemetryId = `reader-${data.telemetryId}`;
          btn.className = "toolbar-button " + data.id;
          btn.setAttribute("aria-labelledby", "label-" + data.id);
          let tip = doc.createElement("span");
          tip.className = "hover-label";
          tip.id = "label-" + data.id;
          doc.l10n.setAttributes(tip, data.l10nId);
          btn.append(tip);
          btn.style.backgroundImage = "url('" + data.image + "')";
          if (data.width && data.height) {
            btn.style.backgroundSize = `${data.width}px ${data.height}px`;
          }
          let tb = this._toolbarElement;
          tb.appendChild(btn);
          this._setupButton(data.id, button => {
            this._actor.sendAsyncMessage(
              "Reader:Clicked-" + button.dataset.buttonid,
              { article: this._article }
            );
          });
        }
        break;
      }
      case "Reader:RemoveButton": {
        if (data.id) {
          let btn = doc.getElementsByClassName(data.id)[0];
          if (btn) {
            btn.remove();
          }
        }
        break;
      }
      case "Reader:ZoomIn": {
        this._changeFontSize(+1);
        break;
      }
      case "Reader:ZoomOut": {
        this._changeFontSize(-1);
        break;
      }
      case "Reader:ResetZoom": {
        this._resetFontSize();
        break;
      }
    }
  },

  handleEvent(aEvent) {
    // To avoid buttons that are programmatically clicked being counted twice,
    // and account for controls that don't fire click events, define a set of
    // blur only telemetry ids.
    const blurTelemetryIds = new Set([
      "colors-menu-custom-tab",
      "left-align-button",
      "font-type-selector",
      "font-weight-selector",
    ]);

    if (!aEvent.isTrusted) {
      return;
    }

    let target = aEvent.target;
    switch (aEvent.type) {
      case "touchstart":
      /* fall through */
      case "mousedown":
        if (
          !target.closest(".dropdown-popup") &&
          // Skip handling the toggle button here becase
          // the dropdown will get toggled with the 'click' event.
          !target.classList.contains("dropdown-toggle")
        ) {
          this._closeDropdowns();
        }
        break;
      case "keydown":
        if (aEvent.keyCode == 27) {
          this._closeDropdowns();
        }
        break;
      case "click": {
        let clickTelemetryId =
          target.attributes.getNamedItem(`data-telemetry-id`)?.value;

        if (clickTelemetryId && !blurTelemetryIds.has(clickTelemetryId)) {
          Services.telemetry.recordEvent(
            "readermode",
            "button",
            "click",
            null,
            {
              label: clickTelemetryId,
            }
          );
        }

        if (target.classList.contains("dropdown-toggle")) {
          this._toggleDropdownClicked(aEvent);
        }
        break;
      }
      case "blur":
        if (HTMLElement.isInstance(target)) {
          let blurTelemetryId =
            target.attributes.getNamedItem(`data-telemetry-id`)?.value;

          if (blurTelemetryId && blurTelemetryIds.has(blurTelemetryId)) {
            Services.telemetry.recordEvent(
              "readermode",
              "button",
              "click",
              null,
              {
                label: blurTelemetryId,
              }
            );
          }
        }
        break;
      case "scroll": {
        let lastHeight = this._lastHeight;
        let { windowUtils } = this._win;
        this._lastHeight = windowUtils.getBoundsWithoutFlushing(
          this._doc.body
        ).height;
        // Only close dropdowns if the scroll events are not a result of line
        // height / font-size changes that caused a page height change.
        // Prevent dropdowns from closing when scrolling within the popup.
        let mouseInDropdown = !!this._doc.querySelector(".dropdown.open:hover");
        if (lastHeight == this._lastHeight && !mouseInDropdown) {
          this._closeDropdowns(true);
        }

        break;
      }
      case "resize":
        this._updateImageMargins();
        this._scheduleToolbarOverlapHandler();
        break;

      case "wheel": {
        let doZoom =
          (aEvent.ctrlKey && zoomOnCtrl) || (aEvent.metaKey && zoomOnMeta);
        if (!doZoom) {
          return;
        }
        aEvent.preventDefault();

        // Throttle events to once per 150ms. This avoids excessively fast zooming.
        if (aEvent.timeStamp <= this._zoomBackoffTime) {
          return;
        }
        this._zoomBackoffTime = aEvent.timeStamp + 150;

        // Determine the direction of the delta (we don't care about its size);
        // This code is adapted from normalizeWheelEventDelta in
        // toolkit/components/pdfjs/content/web/viewer.mjs
        let delta = Math.abs(aEvent.deltaX) + Math.abs(aEvent.deltaY);
        let angle = Math.atan2(aEvent.deltaY, aEvent.deltaX);
        if (-0.25 * Math.PI < angle && angle < 0.75 * Math.PI) {
          delta = -delta;
        }

        if (delta > 0) {
          this._changeFontSize(+1);
        } else if (delta < 0) {
          this._changeFontSize(-1);
        }
        break;
      }

      case "pagehide":
        this._closeDropdowns();
        this._saveScrollPosition();

        this._actor.readerModeHidden();
        this.clearActor();

        // Disconnect and delete IntersectionObservers to prevent memory leaks:

        this._intersectionObs.unobserve(this._doc.querySelector(".top-anchor"));

        delete this._intersectionObs;

        break;

      case "change": {
        let colorScheme;
        if (this.forcedColorsMediaList.matches) {
          colorScheme = "hcm";
        } else {
          colorScheme = Services.prefs.getCharPref("reader.color_scheme");
          // We should be changing the color scheme in relation to a preference change
          // if the user has the color scheme preference set to "Auto".
          if (colorScheme == "auto") {
            colorScheme = this.colorSchemeMediaList.matches ? "dark" : "light";
          }
        }
        this._setColorScheme(colorScheme);

        break;
      }
    }
  },

  clearActor() {
    this._actor = null;
  },

  _onReaderClose() {
    if (this._actor) {
      this._actor.closeReaderMode();
    }
  },

  async _resetFontSize() {
    await lazy.AsyncPrefs.reset("reader.font_size");
    let currentSize = Services.prefs.getIntPref("reader.font_size");
    this._setFontSize(currentSize);
  },

  _setFontSize(newFontSize) {
    this._fontSize = Math.min(
      this.FONT_SIZE_MAX,
      Math.max(this.FONT_SIZE_MIN, newFontSize)
    );
    let size;
    if (this._fontSize > this.FONT_SIZE_LEGACY_MAX) {
      // -1 because we're indexing into a 0-indexed array, so the first value
      // over the legacy max should be 0, the next 1, etc.
      let index = this._fontSize - this.FONT_SIZE_LEGACY_MAX - 1;
      size = this.FONT_SIZE_EXTENDED_VALUES[index];
    } else {
      size = 10 + 2 * this._fontSize;
    }

    let readerBody = this._doc.body;
    readerBody.style.setProperty("--font-size", size + "px");
    return lazy.AsyncPrefs.set("reader.font_size", this._fontSize);
  },

  _setupFontSizeButtons() {
    let plusButton, minusButton;

    if (improvedTextMenuEnabled) {
      plusButton = this._doc.querySelector(".text-size-plus-button");
      minusButton = this._doc.querySelector(".text-size-minus-button");
    } else {
      plusButton = this._doc.querySelector(".plus-button");
      minusButton = this._doc.querySelector(".minus-button");
    }

    let currentSize = Services.prefs.getIntPref("reader.font_size");
    this._setFontSize(currentSize);
    this._updateFontSizeButtonControls();

    plusButton.addEventListener(
      "click",
      event => {
        if (!event.isTrusted) {
          return;
        }
        this._changeFontSize(+1);
      },
      true
    );

    minusButton.addEventListener(
      "click",
      event => {
        if (!event.isTrusted) {
          return;
        }
        this._changeFontSize(-1);
      },
      true
    );
  },

  _updateFontSizeButtonControls() {
    let plusButton, minusButton;
    let currentSize = this._fontSize;

    if (improvedTextMenuEnabled) {
      plusButton = this._doc.querySelector(".text-size-plus-button");
      minusButton = this._doc.querySelector(".text-size-minus-button");
    } else {
      plusButton = this._doc.querySelector(".plus-button");
      minusButton = this._doc.querySelector(".minus-button");
      let fontValue = this._doc.querySelector(".font-size-value");
      fontValue.textContent = currentSize;
    }

    if (currentSize === this.FONT_SIZE_MIN) {
      minusButton.setAttribute("disabled", true);
    } else {
      minusButton.removeAttribute("disabled");
    }
    if (currentSize === this.FONT_SIZE_MAX) {
      plusButton.setAttribute("disabled", true);
    } else {
      plusButton.removeAttribute("disabled");
    }
  },

  _changeFontSize(changeAmount) {
    let currentSize =
      Services.prefs.getIntPref("reader.font_size") + changeAmount;
    this._setFontSize(currentSize);
    this._updateFontSizeButtonControls();
    this._scheduleToolbarOverlapHandler();
  },

  _setContentWidth(newContentWidth) {
    this._contentWidth = newContentWidth;
    this._displayContentWidth(newContentWidth);
    let width = 20 + 5 * (this._contentWidth - 1) + "em";
    this._doc.body.style.setProperty("--content-width", width);
    this._scheduleToolbarOverlapHandler();
    return lazy.AsyncPrefs.set("reader.content_width", this._contentWidth);
  },

  _displayContentWidth(currentContentWidth) {
    let contentWidthValue = this._doc.querySelector(".content-width-value");
    contentWidthValue.textContent = currentContentWidth;
  },

  _setupContentWidthButtons() {
    const CONTENT_WIDTH_MIN = 1;
    const CONTENT_WIDTH_MAX = 9;

    let currentContentWidth = Services.prefs.getIntPref("reader.content_width");
    currentContentWidth = Math.max(
      CONTENT_WIDTH_MIN,
      Math.min(CONTENT_WIDTH_MAX, currentContentWidth)
    );

    this._displayContentWidth(currentContentWidth);

    let plusButton = this._doc.querySelector(".content-width-plus-button");
    let minusButton = this._doc.querySelector(".content-width-minus-button");

    function updateControls() {
      if (currentContentWidth === CONTENT_WIDTH_MIN) {
        minusButton.setAttribute("disabled", true);
      } else {
        minusButton.removeAttribute("disabled");
      }
      if (currentContentWidth === CONTENT_WIDTH_MAX) {
        plusButton.setAttribute("disabled", true);
      } else {
        plusButton.removeAttribute("disabled");
      }
    }

    updateControls();
    this._setContentWidth(currentContentWidth);

    plusButton.addEventListener(
      "click",
      event => {
        if (!event.isTrusted) {
          return;
        }
        event.stopPropagation();

        if (currentContentWidth >= CONTENT_WIDTH_MAX) {
          return;
        }

        currentContentWidth++;
        updateControls();
        this._setContentWidth(currentContentWidth);
      },
      true
    );

    minusButton.addEventListener(
      "click",
      event => {
        if (!event.isTrusted) {
          return;
        }
        event.stopPropagation();

        if (currentContentWidth <= CONTENT_WIDTH_MIN) {
          return;
        }

        currentContentWidth--;
        updateControls();
        this._setContentWidth(currentContentWidth);
      },
      true
    );
  },

  _setLineHeight(newLineHeight) {
    this._displayLineHeight(newLineHeight);
    let height = 1 + 0.2 * (newLineHeight - 1) + "em";
    this._containerElement.style.setProperty("--line-height", height);
    return lazy.AsyncPrefs.set("reader.line_height", newLineHeight);
  },

  _displayLineHeight(currentLineHeight) {
    let lineHeightValue = this._doc.querySelector(".line-height-value");
    lineHeightValue.textContent = currentLineHeight;
  },

  _setupLineHeightButtons() {
    const LINE_HEIGHT_MIN = 1;
    const LINE_HEIGHT_MAX = 9;

    let currentLineHeight = Services.prefs.getIntPref("reader.line_height");
    currentLineHeight = Math.max(
      LINE_HEIGHT_MIN,
      Math.min(LINE_HEIGHT_MAX, currentLineHeight)
    );

    this._displayLineHeight(currentLineHeight);

    let plusButton = this._doc.querySelector(".line-height-plus-button");
    let minusButton = this._doc.querySelector(".line-height-minus-button");

    function updateControls() {
      if (currentLineHeight === LINE_HEIGHT_MIN) {
        minusButton.setAttribute("disabled", true);
      } else {
        minusButton.removeAttribute("disabled");
      }
      if (currentLineHeight === LINE_HEIGHT_MAX) {
        plusButton.setAttribute("disabled", true);
      } else {
        plusButton.removeAttribute("disabled");
      }
    }

    updateControls();
    this._setLineHeight(currentLineHeight);

    plusButton.addEventListener(
      "click",
      event => {
        if (!event.isTrusted) {
          return;
        }
        event.stopPropagation();

        if (currentLineHeight >= LINE_HEIGHT_MAX) {
          return;
        }

        currentLineHeight++;
        updateControls();
        this._setLineHeight(currentLineHeight);
      },
      true
    );

    minusButton.addEventListener(
      "click",
      event => {
        if (!event.isTrusted) {
          return;
        }
        event.stopPropagation();

        if (currentLineHeight <= LINE_HEIGHT_MIN) {
          return;
        }

        currentLineHeight--;
        updateControls();
        this._setLineHeight(currentLineHeight);
      },
      true
    );
  },

  _setupSelector(id, options, initialValue, callback, l10nIds) {
    let doc = this._doc;
    let selector = doc.getElementById(`${id}-selector`);

    options.forEach(option => {
      let selectorOption = doc.createElement("option");
      let presetl10nId = l10nIds[option];
      if (presetl10nId) {
        doc.l10n.setAttributes(selectorOption, presetl10nId);
      } else {
        selectorOption.text = option;
      }
      selectorOption.value = option;
      selector.appendChild(selectorOption);
      if (option == initialValue) {
        selectorOption.setAttribute("selected", true);
      }
    });

    selector.addEventListener("change", e => {
      callback(e.target.value);
    });
  },

  _setFontTypeSelector(newFontType) {
    if (newFontType === "sans-serif") {
      this._doc.documentElement.style.setProperty(
        "--font-family",
        "Helvetica, Arial, sans-serif"
      );
    } else if (newFontType === "serif") {
      this._doc.documentElement.style.setProperty(
        "--font-family",
        'Georgia, "Times New Roman", serif'
      );
    } else if (newFontType === "monospace") {
      this._doc.documentElement.style.setProperty(
        "--font-family",
        '"Courier New", Courier, monospace'
      );
    } else {
      this._doc.documentElement.style.setProperty(
        "--font-family",
        `"${newFontType}"`
      );
    }

    lazy.AsyncPrefs.set("reader.font_type", newFontType);
  },

  _setFontWeight(newFontWeight) {
    if (newFontWeight === "light") {
      this._doc.documentElement.style.setProperty("--font-weight", "lighter");
    } else if (newFontWeight === "regular") {
      this._doc.documentElement.style.setProperty("--font-weight", "normal");
    } else if (newFontWeight === "bold") {
      this._doc.documentElement.style.setProperty("--font-weight", "bolder");
    }

    lazy.AsyncPrefs.set("reader.font_weight", newFontWeight);
  },

  _setupSlider(id, options, initialValue, callback) {
    let doc = this._doc;
    let slider = doc.createElement("moz-slider");

    slider.setAttribute("min", options.min);
    slider.setAttribute("max", options.max);
    slider.setAttribute("value", initialValue);
    slider.setAttribute("ticks", options.ticks);
    slider.setAttribute("tick-labels", options.tickLabels);
    slider.setAttribute("data-l10n-id", options.l10nId);
    slider.setAttribute("data-l10n-attrs", "label");
    slider.setAttribute("slider-icon", options.icon);
    slider.setAttribute("data-telemetry-id", options.telemetryId);

    slider.addEventListener("slider-changed", e => {
      callback(e.detail);
    });

    let sliderContainer = doc.getElementById(`${id}-slider`);
    sliderContainer.appendChild(slider);
  },

  // Rename this function to setContentWidth when the old menu is retired.
  _setContentWidthSlider(newContentWidth) {
    // We map the slider range [1-9] to 20-60em.
    let width = 20 + 5 * (newContentWidth - 1) + "em";
    this._doc.body.style.setProperty("--content-width", width);
    this._scheduleToolbarOverlapHandler();
    return lazy.AsyncPrefs.set(
      "reader.content_width",
      parseInt(newContentWidth)
    );
  },

  _setLineSpacing(newLineSpacing) {
    // We map the slider range [1-9] to 1-2.6em.
    let spacing = 1 + 0.2 * (newLineSpacing - 1) + "em";
    this._containerElement.style.setProperty("--line-height", spacing);
    return lazy.AsyncPrefs.set("reader.line_height", parseInt(newLineSpacing));
  },

  _setCharacterSpacing(newCharSpacing) {
    // We map the slider range [1-9] to 0-0.24em.
    let spacing = (newCharSpacing - 1) * 0.03;
    this._containerElement.style.setProperty(
      "--letter-spacing",
      `${parseFloat(spacing).toFixed(2)}em`
    );
    lazy.AsyncPrefs.set("reader.character_spacing", parseInt(newCharSpacing));
  },

  _setWordSpacing(newWordSpacing) {
    // We map the slider range [1-9] to 0-0.4em.
    let spacing = (newWordSpacing - 1) * 0.05;
    this._containerElement.style.setProperty(
      "--word-spacing",
      `${parseFloat(spacing).toFixed(2)}em`
    );
    lazy.AsyncPrefs.set("reader.word_spacing", parseInt(newWordSpacing));
  },

  _setTextAlignment(newTextAlignment) {
    if (this._textAlignment === newTextAlignment) {
      return false;
    }

    const blockImageMarginRight = {
      left: "auto",
      center: "auto",
      right: "0",
      start: "unset",
    };

    const blockImageMarginLeft = {
      left: "0",
      center: "auto",
      right: "auto",
      start: "unset",
    };

    if (newTextAlignment === "start") {
      let startAlignButton;
      if (isAppLocaleRTL) {
        startAlignButton = this._doc.querySelector(".right-align-button");
      } else {
        startAlignButton = this._doc.querySelector(".left-align-button");
      }
      startAlignButton.click();
    }

    this._containerElement.style.setProperty(
      "--text-alignment",
      newTextAlignment
    );

    this._containerElement.style.setProperty(
      "--block-img-margin-right",
      blockImageMarginRight[newTextAlignment]
    );

    this._containerElement.style.setProperty(
      "--block-img-margin-left",
      blockImageMarginLeft[newTextAlignment]
    );

    lazy.AsyncPrefs.set("reader.text_alignment", newTextAlignment);
    return true;
  },

  async _resetTextLayout() {
    let doc = this._doc;
    const initial = DEFAULT_TEXT_LAYOUT;
    const changeEvent = new Event("change", { bubbles: true });

    this._resetFontSize();
    let plusButton = this._doc.querySelector(".text-size-plus-button");
    let minusButton = this._doc.querySelector(".text-size-minus-button");
    plusButton.removeAttribute("disabled");
    minusButton.removeAttribute("disabled");

    let fontType = doc.getElementById("font-type-selector");
    fontType.value = initial.fontType;
    fontType.dispatchEvent(changeEvent);

    let fontWeight = doc.getElementById("font-weight-selector");
    fontWeight.value = initial.fontWeight;
    fontWeight.dispatchEvent(changeEvent);

    let contentWidth = doc.querySelector("#content-width-slider moz-slider");
    contentWidth.setAttribute("value", initial.contentWidth);
    this._setContentWidthSlider(initial.contentWidth);

    let lineSpacing = doc.querySelector("#line-spacing-slider moz-slider");
    lineSpacing.setAttribute("value", initial.lineSpacing);
    this._setLineSpacing(initial.lineSpacing);

    let characterSpacing = doc.querySelector(
      "#character-spacing-slider moz-slider"
    );
    characterSpacing.setAttribute("value", initial.characterSpacing);
    this._setCharacterSpacing(initial.characterSpacing);

    let wordSpacing = doc.querySelector("#word-spacing-slider moz-slider");
    wordSpacing.setAttribute("value", initial.wordSpacing);
    this._setWordSpacing(initial.wordSpacing);

    this._setTextAlignment(initial.textAlignment);
  },

  _handleTextLayoutFocus() {
    // Retain focus inside the menu panel.
    let doc = this._doc;
    let accordion = doc.querySelector("#about-reader-advanced-layout");
    let advancedHeader = doc.querySelector(".accordion-header");
    let textResetButton = doc.querySelector(".text-layout-reset-button");
    let textFirstFocusable = doc.querySelector(".text-size-minus-button");

    textResetButton.addEventListener("keydown", e => {
      if (e.key === "Tab" && !e.shiftKey) {
        e.preventDefault();
        textFirstFocusable.focus();
      }
    });
    advancedHeader.addEventListener("keydown", e => {
      if (!accordion.hasAttribute("open") && e.key === "Tab" && !e.shiftKey) {
        e.preventDefault();
        textFirstFocusable.focus();
      }
    });
  },

  _setColorScheme(newColorScheme) {
    // There's nothing to change if the new color scheme is the same as our current scheme.
    if (this._colorScheme === newColorScheme) {
      return;
    }

    let bodyClasses = this._doc.body.classList;

    if (this._colorScheme) {
      bodyClasses.remove(this._colorScheme);
    }

    if (!this._win.matchMedia("(forced-colors)").matches) {
      if (newColorScheme === "auto") {
        this._colorScheme = this.colorSchemeMediaList.matches
          ? "dark"
          : "light";
      } else {
        this._colorScheme = newColorScheme;
      }
    } else {
      this._colorScheme = "hcm";
    }

    if (this._colorScheme == "custom") {
      const colorInputs = this._doc.querySelectorAll("color-input");
      colorInputs.forEach(input => {
        // Set document body styles to pref values.
        let property = input.getAttribute("prop-name");
        let pref = `reader.custom_colors.${property}`;
        let customColor = Services.prefs.getStringPref(pref, "");
        // If customColor is truthy, set the value from pref.
        if (customColor) {
          let cssProp = `--custom-theme-${property}`;
          this._doc.body.style.setProperty(cssProp, customColor);
        }
      });
    }

    bodyClasses.add(this._colorScheme);
  },

  // Pref values include "auto", "dark", "light", "sepia",
  // "gray", "contrast", and "custom"
  _setColorSchemePref(colorSchemePref, fromInputEvent = false) {
    // The input event for the last selected segmented button is fired
    // upon loading a reader article in the same session. To prevent it
    // from overwriting custom colors, we return false.
    const colorsMenuEnabled = Services.prefs.getBoolPref(
      "reader.colors_menu.enabled",
      false
    );
    if (colorsMenuEnabled && this._colorScheme == "custom" && fromInputEvent) {
      lastSelectedTheme = colorSchemePref;
      return false;
    }
    this._setColorScheme(colorSchemePref);

    lazy.AsyncPrefs.set("reader.color_scheme", colorSchemePref);
    return true;
  },

  _setFontType(newFontType) {
    if (this._fontType === newFontType) {
      return false;
    }

    let bodyClasses = this._doc.body.classList;

    if (this._fontType) {
      bodyClasses.remove(this._fontType);
    }

    this._fontType = newFontType;
    bodyClasses.add(this._fontType);

    lazy.AsyncPrefs.set("reader.font_type", this._fontType);

    return true;
  },

  async _loadArticle(docContentType = "document") {
    let url = this._getOriginalUrl();
    this._showProgressDelayed();

    let article;
    if (this._articlePromise) {
      article = await this._articlePromise;
    }

    if (!article) {
      try {
        article = await ReaderMode.downloadAndParseDocument(
          url,
          { ...this._doc.nodePrincipal?.originAttributes },
          docContentType
        );
      } catch (e) {
        if (e?.newURL && this._actor) {
          await this._actor.sendQuery("RedirectTo", {
            newURL: e.newURL,
            article: e.article,
          });

          let readerURL = "about:reader?url=" + encodeURIComponent(e.newURL);
          this._win.location.replace(readerURL);
          return;
        }
      }
    }

    if (!this._actor) {
      return;
    }

    // Replace the loading message with an error message if there's a failure.
    // Users are supposed to navigate away by themselves (because we cannot
    // remove ourselves from session history.)
    if (!article) {
      this._showError();
      return;
    }

    this._showContent(article);
  },

  async _requestFavicon() {
    let iconDetails = await this._actor.sendQuery("Reader:FaviconRequest", {
      url: this._article.url,
      preferredWidth: 16 * this._win.devicePixelRatio,
    });

    if (iconDetails) {
      this._loadFavicon(iconDetails.url, iconDetails.faviconUrl);
    }
  },

  _loadFavicon(url, faviconUrl) {
    if (this._article.url !== url) {
      return;
    }

    let doc = this._doc;

    let link = doc.createElement("link");
    link.rel = "shortcut icon";
    link.href = faviconUrl;

    doc.getElementsByTagName("head")[0].appendChild(link);
  },

  _updateImageMargins() {
    let windowWidth = this._win.innerWidth;
    let bodyWidth = this._doc.body.clientWidth;

    let setImageMargins = function (img) {
      img.classList.add("moz-reader-block-img");

      // If the image is at least as wide as the window, make it fill edge-to-edge on mobile.
      if (img.naturalWidth >= windowWidth) {
        img.setAttribute("moz-reader-full-width", true);
      } else {
        img.removeAttribute("moz-reader-full-width");
      }

      // If the image is at least half as wide as the body, center it on desktop.
      if (img.naturalWidth >= bodyWidth / 2) {
        img.setAttribute("moz-reader-center", true);
      } else {
        img.removeAttribute("moz-reader-center");
      }
    };

    let imgs = this._doc.querySelectorAll(this._BLOCK_IMAGES_SELECTOR);
    for (let i = imgs.length; --i >= 0; ) {
      let img = imgs[i];

      if (img.naturalWidth > 0) {
        setImageMargins(img);
      } else {
        img.onload = function () {
          setImageMargins(img);
        };
      }
    }
  },

  _updateWideTables() {
    let windowWidth = this._win.innerWidth;

    // Avoid horizontal overflow in the document by making tables that are wider than half browser window's size
    // by making it scrollable.
    let tables = this._doc.querySelectorAll(this._TABLES_SELECTOR);
    for (let i = tables.length; --i >= 0; ) {
      let table = tables[i];
      let rect = table.getBoundingClientRect();
      let tableWidth = rect.width;

      if (windowWidth / 2 <= tableWidth) {
        table.classList.add("moz-reader-wide-table");
      }
    }
  },

  _maybeSetTextDirection: function Read_maybeSetTextDirection(article) {
    // Set the article's "dir" on the contents.
    // If no direction is specified, the contents should automatically be LTR
    // regardless of the UI direction to avoid inheriting the parent's direction
    // if the UI is RTL.
    this._containerElement.dir = article.dir || "ltr";

    // The native locale could be set differently than the article's text direction.
    this._readTimeElement.dir = isAppLocaleRTL ? "rtl" : "ltr";

    // This is used to mirror the line height buttons in the toolbar, when relevant.
    this._toolbarElement.setAttribute("articledir", article.dir || "ltr");
  },

  _showError() {
    this._headerElement.classList.remove("reader-show-element");
    this._contentElement.classList.remove("reader-show-element");

    this._doc.l10n.setAttributes(
      this._messageElement,
      "about-reader-load-error"
    );
    this._doc.l10n.setAttributes(
      this._doc.getElementById("reader-title"),
      "about-reader-load-error"
    );
    this._messageElement.style.display = "block";

    this._doc.documentElement.dataset.isError = true;

    this._error = true;

    this._doc.dispatchEvent(
      new this._win.CustomEvent("AboutReaderContentError", {
        bubbles: true,
        cancelable: false,
      })
    );
  },

  // This function is the JS version of Java's StringUtils.stripCommonSubdomains.
  _stripHost(host) {
    if (!host) {
      return host;
    }

    let start = 0;

    if (host.startsWith("www.")) {
      start = 4;
    } else if (host.startsWith("m.")) {
      start = 2;
    } else if (host.startsWith("mobile.")) {
      start = 7;
    }

    return host.substring(start);
  },

  _showContent(article) {
    this._messageElement.classList.remove("reader-show-element");

    this._article = article;

    this._domainElement.href = article.url;
    let articleUri = Services.io.newURI(article.url);

    try {
      this._domainElement.textContent = this._stripHost(articleUri.host);
    } catch (ex) {
      let url = this._actor.document.URL;
      url = url.substring(url.indexOf("%2F") + 6);
      url = url.substring(0, url.indexOf("%2F"));

      this._domainElement.textContent = url;
    }

    this._creditsElement.textContent = article.byline;

    this._titleElement.textContent = article.title;

    const slow = article.readingTimeMinsSlow;
    const fast = article.readingTimeMinsFast;
    const fastStr = lazy.numberFormat.format(fast);
    const readTimeRange = lazy.numberFormat.formatRange(fast, slow);
    this._doc.l10n.setAttributes(
      this._readTimeElement,
      "about-reader-estimated-read-time",
      {
        range: fast === slow ? `~${fastStr}` : `${readTimeRange}`,
        rangePlural:
          fast === slow
            ? lazy.pluralRules.select(fast)
            : lazy.pluralRules.selectRange(fast, slow),
      }
    );

    // If a document title was not provided in the constructor, we'll fall back
    // to using the article title.
    if (!this._doc.title) {
      this._doc.title = article.title;
    }

    this._containerElement.setAttribute("lang", article.lang);

    this._headerElement.classList.add("reader-show-element");

    let parserUtils = Cc["@mozilla.org/parserutils;1"].getService(
      Ci.nsIParserUtils
    );
    let contentFragment = parserUtils.parseFragment(
      article.content,
      Ci.nsIParserUtils.SanitizerDropForms |
        Ci.nsIParserUtils.SanitizerAllowStyle,
      false,
      articleUri,
      this._contentElement
    );
    this._contentElement.innerHTML = "";
    this._contentElement.appendChild(contentFragment);
    this._maybeSetTextDirection(article);
    this._foundLanguage(article.language);

    this._contentElement.classList.add("reader-show-element");
    this._updateImageMargins();
    this._updateWideTables();

    this._requestFavicon();
    this._doc.body.classList.add("loaded");

    this._goToReference(articleUri.ref);
    this._getScrollPosition();

    Services.obs.notifyObservers(this._win, "AboutReader:Ready");

    this._doc.dispatchEvent(
      new this._win.CustomEvent("AboutReaderContentReady", {
        bubbles: true,
        cancelable: false,
      })
    );
  },

  _hideContent() {
    this._headerElement.classList.remove("reader-show-element");
    this._contentElement.classList.remove("reader-show-element");
  },

  _showProgressDelayed() {
    this._win.setTimeout(() => {
      // No need to show progress if the article has been loaded,
      // if the window has been unloaded, or if there was an error
      // trying to load the article.
      if (this._article || !this._actor || this._error) {
        return;
      }

      this._headerElement.classList.remove("reader-show-element");
      this._contentElement.classList.remove("reader-show-element");

      this._doc.l10n.setAttributes(
        this._messageElement,
        "about-reader-loading"
      );
      this._messageElement.classList.add("reader-show-element");
    }, 300);
  },

  /**
   * Returns the original article URL for this about:reader view.
   */
  _getOriginalUrl(win) {
    let url = win ? win.location.href : this._win.location.href;
    return ReaderMode.getOriginalUrl(url) || url;
  },

  _setupSegmentedButton(id, options, initialValue, callback) {
    let doc = this._doc;
    let segmentedButton = doc.getElementsByClassName(id)[0];

    for (let option of options) {
      let radioButton = doc.createElement("input");
      radioButton.id = "radio-item" + option.itemClass;
      radioButton.type = "radio";
      radioButton.classList.add("radio-button");
      radioButton.name = option.groupName;
      radioButton.setAttribute("data-telemetry-id", option.itemClass);
      segmentedButton.appendChild(radioButton);

      let item = doc.createElement("label");
      item.htmlFor = radioButton.id;
      item.classList.add(option.itemClass);
      doc.l10n.setAttributes(item, option.l10nId);

      segmentedButton.appendChild(item);

      radioButton.addEventListener(
        "input",
        function (aEvent) {
          if (!aEvent.isTrusted) {
            return;
          }

          let labels = segmentedButton.children;
          for (let label of labels) {
            label.removeAttribute("checked");
          }

          let setOption = callback(option.value, true);
          if (setOption) {
            aEvent.target.setAttribute("checked", "true");
            aEvent.target.nextElementSibling.setAttribute("checked", "true");
          }
        },
        true
      );

      if (option.value === initialValue) {
        radioButton.setAttribute("checked", "true");
        item.setAttribute("checked", "true");
      }
    }
  },

  _setupButton(id, callback) {
    let button = this._doc.querySelector("." + id);
    button.removeAttribute("hidden");
    button.addEventListener(
      "click",
      function (aEvent) {
        if (!aEvent.isTrusted) {
          return;
        }

        let btn = aEvent.target;
        callback(btn);
      },
      true
    );
  },

  _handleColorsTabClick(option) {
    let doc = this._doc;
    let deck = doc.querySelector("named-deck");
    if (option == deck.getAttribute("selected-view")) {
      return;
    }

    if (option == "customtheme") {
      this._setColorSchemePref("custom");
      lazy.AsyncPrefs.set("reader.color_scheme", "custom");

      // Store the last selected preset theme button.
      const colorSchemePresets = doc.querySelector(
        ".colors-menu-color-scheme-buttons"
      );
      const labels = colorSchemePresets.querySelectorAll("label");
      labels.forEach(label => {
        if (label.hasAttribute("checked")) {
          lastSelectedTheme = label.className.split("-")[0];
        }
      });
    } else if (option == "fxtheme") {
      this._setColorSchemePref(lastSelectedTheme);
      lazy.AsyncPrefs.set("reader.color_scheme", lastSelectedTheme);
      // set the last selected button to checked.
      const colorSchemePresets = doc.querySelector(
        ".colors-menu-color-scheme-buttons"
      );
      const labels = colorSchemePresets.querySelectorAll("label");
      labels.forEach(label => {
        if (label.className == `${lastSelectedTheme}-button`) {
          label.setAttribute("checked", "true");
          label.previousElementSibling.setAttribute("checked", "true");
        }
      });
    }
  },

  _setupColorsTabs(options, callback) {
    let doc = this._doc;
    let colorScheme = Services.prefs.getCharPref("reader.color_scheme");
    for (let option of options) {
      let tabButton = doc.getElementById(`tabs-deck-button-${option}`);
      // Open custom theme tab if color scheme is set to custom.
      if (option == "customtheme" && colorScheme == "custom") {
        tabButton.click();
      }
      tabButton.addEventListener(
        "click",
        function (aEvent) {
          if (!aEvent.isTrusted) {
            return;
          }

          callback(option);
        },
        true
      );
    }
  },

  _setupColorInput(prop) {
    let doc = this._doc;
    let input = doc.createElement("color-input");
    input.setAttribute("prop-name", prop);
    let labelL10nId = `about-reader-custom-colors-${prop}`;
    input.setAttribute("data-l10n-id", labelL10nId);
    input.setAttribute("data-telemetry-id", `custom-color-picker-${prop}`);

    let pref = `reader.custom_colors.${prop}`;
    let customColor = Services.prefs.getStringPref(pref, "");
    // Set the swatch color from prefs if one has been set.
    if (customColor) {
      input.setAttribute("color", customColor);
    } else {
      let defaultColor = DEFAULT_COLORS[prop];
      input.setAttribute("color", defaultColor);
    }

    // Attach event listener to update the pref and page colors on input.
    input.addEventListener("color-picked", e => {
      const cssPropToUpdate = `--custom-theme-${prop}`;
      this._doc.body.style.setProperty(cssPropToUpdate, e.detail);

      const prefToUpdate = `reader.custom_colors.${prop}`;
      lazy.AsyncPrefs.set(prefToUpdate, e.detail);
    });

    return input;
  },

  _setupCustomColors(options, id) {
    let doc = this._doc;
    const list = doc.getElementsByClassName(id)[0];

    for (let option of options) {
      let listItem = doc.createElement("li");
      let colorInput = this._setupColorInput(option);
      listItem.appendChild(colorInput);
      list.appendChild(listItem);
    }
  },

  _resetCustomColors() {
    // Need to reset prefs, page colors, and color inputs.
    const colorInputs = this._doc.querySelectorAll("color-input");
    colorInputs.forEach(input => {
      let property = input.getAttribute("prop-name");
      let pref = `reader.custom_colors.${property}`;
      lazy.AsyncPrefs.set(pref, "");

      // Set css props to empty strings so they use fallback value.
      let cssProp = `--custom-theme-${property}`;
      this._doc.body.style.setProperty(cssProp, "");

      let defaultColor = DEFAULT_COLORS[property];
      input.setAttribute("color", defaultColor);
    });
  },

  _handleThemeFocus() {
    // Retain focus inside the menu panel.
    let doc = this._doc;
    let themeButtons = doc.querySelector(".colors-menu-color-scheme-buttons");
    let defaultThemeFirstFocusable = doc.querySelector(
      "#tabs-deck-button-fxtheme"
    );
    let themeResetButton = doc.querySelector(".custom-colors-reset-button");
    let customThemeFirstFocusable = doc.querySelector(
      "#tabs-deck-button-customtheme"
    );

    themeButtons.addEventListener("keydown", e => {
      if (e.key === "Tab" && !e.shiftKey) {
        e.preventDefault();
        defaultThemeFirstFocusable.focus();
      }
    });
    themeResetButton.addEventListener("keydown", e => {
      if (e.key === "Tab" && !e.shiftKey) {
        e.preventDefault();
        customThemeFirstFocusable.focus();
      }
    });
  },

  _toggleDropdownClicked(event) {
    let dropdown = event.target.closest(".dropdown");

    if (!dropdown) {
      return;
    }

    event.stopPropagation();

    if (dropdown.classList.contains("open")) {
      this._closeDropdowns();
    } else {
      this._openDropdown(dropdown);
    }
  },

  /*
   * If the ReaderView banner font-dropdown is closed, open it.
   */
  _openDropdown(dropdown) {
    if (dropdown.classList.contains("open")) {
      return;
    }

    this._closeDropdowns();

    // Get the height of the doc and start handling scrolling:
    let { windowUtils } = this._win;
    this._lastHeight = windowUtils.getBoundsWithoutFlushing(
      this._doc.body
    ).height;
    this._doc.addEventListener("scroll", this);

    dropdown.classList.add("open");
    this._toolbarElement.classList.add("dropdown-open");

    this._toolbarContainerElement.classList.add("dropdown-open");
    this._toggleToolbarFixedPosition(true);
  },

  /*
   * If the ReaderView has open dropdowns, close them. If we are closing the
   * dropdowns because the page is scrolling, allow popups to stay open with
   * the keep-open class.
   */
  _closeDropdowns(scrolling) {
    let selector = ".dropdown.open";
    if (scrolling) {
      selector += ":not(.keep-open)";
    }

    let openDropdowns = this._doc.querySelectorAll(selector);
    let haveOpenDropdowns = openDropdowns.length;
    for (let dropdown of openDropdowns) {
      dropdown.classList.remove("open");
    }
    this._toolbarElement.classList.remove("dropdown-open");

    if (haveOpenDropdowns) {
      this._toolbarContainerElement.classList.remove("dropdown-open");
      this._toggleToolbarFixedPosition(false);
    }

    // Stop handling scrolling:
    this._doc.removeEventListener("scroll", this);
  },

  _toggleToolbarFixedPosition(shouldBeFixed) {
    let el = this._toolbarContainerElement;
    let fontSize = this._doc.body.style.getPropertyValue("--font-size");
    let contentWidth = this._doc.body.style.getPropertyValue("--content-width");
    if (shouldBeFixed) {
      el.style.setProperty("--font-size", fontSize);
      el.style.setProperty("--content-width", contentWidth);
      el.classList.add("transition-location");
    } else {
      let expectTransition =
        el.style.getPropertyValue("--font-size") != fontSize ||
        el.style.getPropertyValue("--content-width") != contentWidth;
      if (expectTransition) {
        el.addEventListener(
          "transitionend",
          () => el.classList.remove("transition-location"),
          { once: true }
        );
      } else {
        el.classList.remove("transition-location");
      }
      el.style.removeProperty("--font-size");
      el.style.removeProperty("--content-width");
      el.classList.remove("overlaps");
    }
  },

  _scheduleToolbarOverlapHandler() {
    if (this._enqueuedToolbarOverlapHandler) {
      return;
    }
    this._enqueuedToolbarOverlapHandler = this._win.requestAnimationFrame(
      () => {
        this._win.setTimeout(() => this._toolbarOverlapHandler(), 0);
      }
    );
  },

  _toolbarOverlapHandler() {
    delete this._enqueuedToolbarOverlapHandler;
    // Ensure the dropdown is still open to avoid racing with that changing.
    if (this._toolbarContainerElement.classList.contains("dropdown-open")) {
      let { windowUtils } = this._win;
      let toolbarBounds = windowUtils.getBoundsWithoutFlushing(
        this._toolbarElement.parentNode
      );
      let textBounds = windowUtils.getBoundsWithoutFlushing(
        this._containerElement
      );
      let overlaps = false;
      if (isAppLocaleRTL) {
        overlaps = textBounds.right > toolbarBounds.left;
      } else {
        overlaps = textBounds.left < toolbarBounds.right;
      }
      this._toolbarContainerElement.classList.toggle("overlaps", overlaps);
    }
  },

  _topScrollChange(entries) {
    if (!entries.length) {
      return;
    }
    // If we don't intersect the item at the top of the document, we're
    // scrolled down:
    let scrolled = !entries[entries.length - 1].isIntersecting;
    let tbc = this._toolbarContainerElement;
    tbc.classList.toggle("scrolled", scrolled);
  },

  /*
   * Scroll reader view to a reference
   */
  _goToReference(ref) {
    if (ref) {
      if (this._doc.readyState == "complete") {
        this._win.location.hash = ref;
      } else {
        this._win.addEventListener(
          "load",
          () => {
            this._win.location.hash = ref;
          },
          { once: true }
        );
      }
    }
  },

  _scrollToSavedPosition(pos) {
    this._win.scrollTo({
      top: pos,
      left: 0,
      behavior: "auto",
    });
    gScrollPositions.delete(this._win.location.href);
  },

  /*
   * Save reader view vertical scroll position
   */
  _saveScrollPosition() {
    let scrollTop = this._doc.documentElement.scrollTop;
    gScrollPositions.set(this._win.location.href, scrollTop);
  },

  /*
   * Scroll reader view to a saved position
   */
  _getScrollPosition() {
    let scrollPosition = gScrollPositions.get(this._win.location.href);
    if (scrollPosition !== undefined) {
      if (this._doc.readyState == "complete") {
        this._scrollToSavedPosition(scrollPosition);
      } else {
        this._win.addEventListener(
          "load",
          () => {
            this._scrollToSavedPosition(scrollPosition);
          },
          { once: true }
        );
      }
    }
  },
};
PK
!<:2Ymodules/AbuseReporter.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

// Maximum length of the string properties sent to the API endpoint.
const MAX_STRING_LENGTH = 255;

const AMO_SUPPORTED_ADDON_TYPES = [
  "extension",
  "theme",
  "sitepermission",
  "dictionary",
];

const PREF_ADDON_ABUSE_REPORT_URL = "extensions.addonAbuseReport.url";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  ClientID: "resource://gre/modules/ClientID.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "ADDON_ABUSE_REPORT_URL",
  PREF_ADDON_ABUSE_REPORT_URL
);

const ERROR_TYPES = Object.freeze([
  "ERROR_CLIENT",
  "ERROR_NETWORK",
  "ERROR_SERVER",
  "ERROR_UNKNOWN",
]);

export class AbuseReportError extends Error {
  constructor(errorType, errorInfo = undefined) {
    if (!ERROR_TYPES.includes(errorType)) {
      throw new Error(`Unexpected AbuseReportError type "${errorType}"`);
    }

    let message = errorInfo ? `${errorType} - ${errorInfo}` : errorType;

    super(message);
    this.name = "AbuseReportError";
    this.errorType = errorType;
    this.errorInfo = errorInfo;
  }
}

/**
 * Create an error info string from a fetch response object.
 *
 * @param {Response} response
 *        A fetch response object to convert into an errorInfo string.
 *
 * @returns {Promise<string>}
 *          The errorInfo string to be included in an AbuseReportError.
 */
async function responseToErrorInfo(response) {
  return JSON.stringify({
    status: response.status,
    responseText: await response.text().catch(() => ""),
  });
}

/**
 * A singleton used to manage abuse reports for add-ons.
 */
export const AbuseReporter = {
  getAMOFormURL({ addonId }) {
    return Services.urlFormatter
      .formatURLPref("extensions.abuseReport.amoFormURL")
      .replace(/%addonID%/g, addonId);
  },

  isSupportedAddonType(addonType) {
    return AMO_SUPPORTED_ADDON_TYPES.includes(addonType);
  },

  /**
   * Send an add-on abuse report using the AMO API. The data passed to this
   * method might be augmented with report data known by Firefox.
   *
   * @param {string} addonId
   * @param {{[key: string]: string|null}} data
   *        Abuse report data to be submitting to the AMO API along with the
   *        additional abuse report data known by Firefox.
   * @param {object} [options]
   * @param {string} [options.authorization]
   *        An optional value of an Authorization HTTP header to be set on the
   *        submission request.
   *
   * @returns {Promise<object>} Return a promise that resolves to the JSON AMO
   *          API response (or an error when something went wrong).
   */
  async sendAbuseReport(addonId, data, options = {}) {
    const rejectReportError = async (errorType, { response } = {}) => {
      // Leave errorInfo empty if there is no response or fails to be converted
      // into an error info object.
      const errorInfo = response
        ? await responseToErrorInfo(response).catch(() => undefined)
        : undefined;

      throw new AbuseReportError(errorType, errorInfo);
    };

    let abuseReport = { addon: addonId, ...data };

    // If the add-on is installed, augment the data with internal report data.
    const addon = await lazy.AddonManager.getAddonByID(addonId);
    if (addon) {
      const metadata = await AbuseReporter.getReportData(addon);
      abuseReport = { ...abuseReport, ...metadata };
    }

    const headers = { "Content-Type": "application/json" };
    if (options?.authorization?.length) {
      headers.authorization = options.authorization;
    }

    let response;
    try {
      response = await fetch(lazy.ADDON_ABUSE_REPORT_URL, {
        method: "POST",
        credentials: "omit",
        referrerPolicy: "no-referrer",
        headers,
        body: JSON.stringify(abuseReport),
      });
    } catch (err) {
      Cu.reportError(err);
      return rejectReportError("ERROR_NETWORK");
    }

    if (response.ok && response.status >= 200 && response.status < 400) {
      return response.json();
    }

    if (response.status >= 400 && response.status < 500) {
      return rejectReportError("ERROR_CLIENT", { response });
    }

    if (response.status >= 500 && response.status < 600) {
      return rejectReportError("ERROR_SERVER", { response });
    }

    return rejectReportError("ERROR_UNKNOWN", { response });
  },

  /**
   * Helper function that retrieves from an addon object all the data to send
   * as part of the submission request, besides the `reason`, `message` which are
   * going to be received from the submit method of the report object returned
   * by `createAbuseReport`.
   * (See https://addons-server.readthedocs.io/en/latest/topics/api/abuse.html)
   *
   * @param {AddonWrapper} addon
   *        The addon object to collect the detail from.
   *
   * @return {object}
   *         An object that contains the collected details.
   */
  async getReportData(addon) {
    const truncateString = text =>
      typeof text == "string" ? text.slice(0, MAX_STRING_LENGTH) : text;

    // Normalize addon_install_source and addon_install_method values
    // as expected by the server API endpoint. Returns null if the
    // value is not a string.
    const normalizeValue = text =>
      typeof text == "string"
        ? text.toLowerCase().replace(/[- :]/g, "_")
        : null;

    const installInfo = addon.installTelemetryInfo || {};

    const data = {
      addon: addon.id,
      addon_version: addon.version,
      addon_name: truncateString(addon.name),
      addon_summary: truncateString(addon.description),
      addon_install_origin:
        addon.sourceURI && truncateString(addon.sourceURI.spec),
      install_date: addon.installDate && addon.installDate.toISOString(),
      addon_install_source: normalizeValue(installInfo.source),
      addon_install_source_url:
        installInfo.sourceURL && truncateString(installInfo.sourceURL),
      addon_install_method: normalizeValue(installInfo.method),
    };

    switch (addon.signedState) {
      case lazy.AddonManager.SIGNEDSTATE_BROKEN:
        data.addon_signature = "broken";
        break;
      case lazy.AddonManager.SIGNEDSTATE_UNKNOWN:
        data.addon_signature = "unknown";
        break;
      case lazy.AddonManager.SIGNEDSTATE_MISSING:
        data.addon_signature = "missing";
        break;
      case lazy.AddonManager.SIGNEDSTATE_PRELIMINARY:
        data.addon_signature = "preliminary";
        break;
      case lazy.AddonManager.SIGNEDSTATE_SIGNED:
        data.addon_signature = "signed";
        break;
      case lazy.AddonManager.SIGNEDSTATE_SYSTEM:
        data.addon_signature = "system";
        break;
      case lazy.AddonManager.SIGNEDSTATE_PRIVILEGED:
        data.addon_signature = "privileged";
        break;
      case lazy.AddonManager.SIGNEDSTATE_NOT_REQUIRED:
        data.addon_signature = "not_required";
        break;
      default:
        data.addon_signature = `unknown: ${addon.signedState}`;
    }

    // Set "curated" as addon_signature on recommended addons
    // (addon.isRecommended internally checks that the addon is also
    // signed correctly).
    if (addon.isRecommended) {
      data.addon_signature = "curated";
    }

    data.client_id = await lazy.ClientID.getClientIdHash();

    data.app = AppConstants.platform === "android" ? "android" : "firefox";
    data.appversion = Services.appinfo.version;
    data.lang = Services.locale.appLocaleAsBCP47;
    data.operating_system = AppConstants.platform;
    data.operating_system_version = Services.sysinfo.getProperty("version");

    return data;
  },
};
PK
!<gx��L�L"modules/ActorManagerParent.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This module handles JavaScript-implemented JSWindowActors, registered through DOM IPC
 * infrastructure, and are fission-compatible.
 */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

/**
 * Fission-compatible JSProcess implementations.
 * Each actor options object takes the form of a ProcessActorOptions dictionary.
 * Detailed documentation of these options is in dom/docs/ipc/jsactors.rst,
 * available at https://firefox-source-docs.mozilla.org/dom/ipc/jsactors.html
 */
let JSPROCESSACTORS = {
  AsyncPrefs: {
    parent: {
      esModuleURI: "resource://gre/modules/AsyncPrefs.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/modules/AsyncPrefs.sys.mjs",
    },
  },

  ContentPrefs: {
    parent: {
      esModuleURI: "resource://gre/modules/ContentPrefServiceParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/modules/ContentPrefServiceChild.sys.mjs",
    },
  },

  ExtensionContent: {
    child: {
      esModuleURI: "resource://gre/modules/ExtensionContent.sys.mjs",
    },
    includeParent: true,
  },

  HPKEConfigManager: {
    remoteTypes: ["privilegedabout"],
    parent: {
      esModuleURI: "resource://gre/modules/HPKEConfigManager.sys.mjs",
    },
  },

  ProcessConduits: {
    parent: {
      esModuleURI: "resource://gre/modules/ConduitsParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/modules/ConduitsChild.sys.mjs",
    },
  },
};

/**
 * Fission-compatible JSWindowActor implementations.
 * Each actor options object takes the form of a WindowActorOptions dictionary.
 * Detailed documentation of these options is in dom/docs/ipc/jsactors.rst,
 * available at https://firefox-source-docs.mozilla.org/dom/ipc/jsactors.html
 */
let JSWINDOWACTORS = {
  AboutCertViewer: {
    parent: {
      esModuleURI: "resource://gre/modules/AboutCertViewerParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/modules/AboutCertViewerChild.sys.mjs",

      events: {
        DOMDocElementInserted: { capture: true },
      },
    },

    matches: ["about:certificate"],
  },

  AboutHttpsOnlyError: {
    parent: {
      esModuleURI: "resource://gre/actors/AboutHttpsOnlyErrorParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/AboutHttpsOnlyErrorChild.sys.mjs",
      events: {
        DOMDocElementInserted: {},
      },
    },
    matches: ["about:httpsonlyerror?*"],
    allFrames: true,
  },

  AboutTranslations: {
    parent: {
      esModuleURI: "resource://gre/actors/AboutTranslationsParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/AboutTranslationsChild.sys.mjs",
      events: {
        // Run the actor before any content of the page appears to inject functions.
        DOMDocElementInserted: {},
        DOMContentLoaded: {},
        // Used to show and hide the translations button.
        pageshow: { mozSystemGroup: true },
        pagehide: { mozSystemGroup: true },
      },
    },
    matches: ["about:translations"],
    remoteTypes: ["privilegedabout"],
    enablePreference: "browser.translations.enable",
  },

  AudioPlayback: {
    parent: {
      esModuleURI: "resource://gre/actors/AudioPlaybackParent.sys.mjs",
    },

    child: {
      esModuleURI: "resource://gre/actors/AudioPlaybackChild.sys.mjs",
      observers: ["audio-playback"],
    },

    allFrames: true,
  },

  AutoComplete: {
    parent: {
      esModuleURI: "resource://gre/actors/AutoCompleteParent.sys.mjs",
      // These two messages are also used, but are currently synchronous calls
      // through the per-process message manager.
      // "AutoComplete:GetSelectedIndex",
      // "AutoComplete:SelectBy"
    },

    child: {
      esModuleURI: "resource://gre/actors/AutoCompleteChild.sys.mjs",
    },

    allFrames: true,
  },

  Autoplay: {
    parent: {
      esModuleURI: "resource://gre/actors/AutoplayParent.sys.mjs",
    },

    child: {
      esModuleURI: "resource://gre/actors/AutoplayChild.sys.mjs",
      events: {
        GloballyAutoplayBlocked: {},
      },
    },

    allFrames: true,
  },

  AutoScroll: {
    parent: {
      esModuleURI: "resource://gre/actors/AutoScrollParent.sys.mjs",
    },

    child: {
      esModuleURI: "resource://gre/actors/AutoScrollChild.sys.mjs",
      events: {
        mousedown: { capture: true, mozSystemGroup: true },
      },
    },

    allFrames: true,
  },

  BackgroundThumbnails: {
    child: {
      esModuleURI: "resource://gre/actors/BackgroundThumbnailsChild.sys.mjs",
      events: {
        DOMDocElementInserted: { capture: true },
      },
    },
    messageManagerGroups: ["thumbnails"],
  },

  BrowserElement: {
    parent: {
      esModuleURI: "resource://gre/actors/BrowserElementParent.sys.mjs",
    },

    child: {
      esModuleURI: "resource://gre/actors/BrowserElementChild.sys.mjs",
      events: {
        DOMWindowClose: {},
      },
    },

    allFrames: true,
  },

  Conduits: {
    parent: {
      esModuleURI: "resource://gre/modules/ConduitsParent.sys.mjs",
    },

    child: {
      esModuleURI: "resource://gre/modules/ConduitsChild.sys.mjs",
    },

    allFrames: true,
  },

  Controllers: {
    parent: {
      esModuleURI: "resource://gre/actors/ControllersParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/ControllersChild.sys.mjs",
    },

    allFrames: true,
  },

  CookieBanner: {
    parent: {
      esModuleURI: "resource://gre/actors/CookieBannerParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/CookieBannerChild.sys.mjs",
      events: {
        DOMContentLoaded: {},
        load: { capture: true },
      },
    },
    // Only need handle cookie banners for HTTP/S scheme.
    matches: ["https://*/*", "http://*/*"],
    // Only handle banners for browser tabs (including sub-frames).
    messageManagerGroups: ["browsers"],
    // Cookie banners can be shown in sub-frames so we need to include them.
    allFrames: true,
    // Holds lazy pref getters.
    _prefs: {},
    // Remember current register state to avoid duplicate calls to register /
    // unregister.
    _isRegistered: false,
    onAddActor(register, unregister) {
      // Register / unregister on pref changes.
      let onPrefChange = () => {
        if (
          this._prefs["cookiebanners.bannerClicking.enabled"] &&
          (this._prefs["cookiebanners.service.mode"] != 0 ||
            this._prefs["cookiebanners.service.mode.privateBrowsing"] != 0)
        ) {
          if (!this._isRegistered) {
            register();
            this._isRegistered = true;
          }
        } else if (this._isRegistered) {
          unregister();
          this._isRegistered = false;
        }
      };

      // Add lazy pref getters with pref observers so we can dynamically enable
      // or disable the actor.
      [
        "cookiebanners.bannerClicking.enabled",
        "cookiebanners.service.mode",
        "cookiebanners.service.mode.privateBrowsing",
      ].forEach(prefName => {
        XPCOMUtils.defineLazyPreferenceGetter(
          this._prefs,
          prefName,
          prefName,
          null,
          onPrefChange
        );
      });

      // Check initial state.
      onPrefChange();
    },
  },

  ExtFind: {
    child: {
      esModuleURI: "resource://gre/actors/ExtFindChild.sys.mjs",
    },

    allFrames: true,
  },

  FindBar: {
    parent: {
      esModuleURI: "resource://gre/actors/FindBarParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/FindBarChild.sys.mjs",
      events: {
        keypress: { mozSystemGroup: true },
      },
    },

    allFrames: true,
    messageManagerGroups: ["browsers", "test"],
  },

  // This is the actor that responds to requests from the find toolbar and
  // searches for matches and highlights them.
  Finder: {
    child: {
      esModuleURI: "resource://gre/actors/FinderChild.sys.mjs",
    },

    allFrames: true,
  },

  FormHistory: {
    parent: {
      esModuleURI: "resource://gre/actors/FormHistoryParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/FormHistoryChild.sys.mjs",
      events: {
        DOMFormBeforeSubmit: {},
      },
    },

    allFrames: true,
  },

  FormHandler: {
    parent: {
      esModuleURI: "resource://gre/actors/FormHandlerParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/FormHandlerChild.sys.mjs",
      events: {
        DOMFormBeforeSubmit: { createActor: false },
      },
    },

    allFrames: true,
  },

  InlineSpellChecker: {
    parent: {
      esModuleURI: "resource://gre/actors/InlineSpellCheckerParent.sys.mjs",
    },

    child: {
      esModuleURI: "resource://gre/actors/InlineSpellCheckerChild.sys.mjs",
    },

    allFrames: true,
  },

  KeyPressEventModelChecker: {
    child: {
      esModuleURI:
        "resource://gre/actors/KeyPressEventModelCheckerChild.sys.mjs",
      events: {
        CheckKeyPressEventModel: { capture: true, mozSystemGroup: true },
      },
    },

    allFrames: true,
  },

  LoginManager: {
    parent: {
      esModuleURI: "resource://gre/modules/LoginManagerParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/modules/LoginManagerChild.sys.mjs",
      events: {
        "form-submission-detected": { createActor: false },
        "before-form-submission": { createActor: false },
        DOMFormHasPassword: {},
        DOMPossibleUsernameInputAdded: {},
        DOMInputPasswordAdded: {},
      },
    },

    allFrames: true,
    messageManagerGroups: ["browsers", "webext-browsers", ""],
  },

  ManifestMessages: {
    child: {
      esModuleURI: "resource://gre/modules/ManifestMessagesChild.sys.mjs",
    },
  },

  // A single process (shared with translations) that manages machine learning engines.
  MLEngine: {
    parent: {
      esModuleURI: "resource://gre/actors/MLEngineParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/MLEngineChild.sys.mjs",
      events: {
        DOMContentLoaded: { createActor: true },
      },
    },
    includeChrome: true,
    matches: ["chrome://global/content/ml/MLEngine.html"],
    enablePreference: "browser.ml.enable",
  },

  NetError: {
    parent: {
      esModuleURI: "resource://gre/actors/NetErrorParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/NetErrorChild.sys.mjs",
      events: {
        DOMDocElementInserted: {},
        click: {},
      },
    },

    matches: ["about:certerror?*", "about:neterror?*"],
    allFrames: true,
  },

  PictureInPictureLauncher: {
    parent: {
      esModuleURI: "resource://gre/modules/PictureInPicture.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/PictureInPictureChild.sys.mjs",
      events: {
        MozTogglePictureInPicture: { capture: true },
      },
    },

    allFrames: true,
  },

  PictureInPicture: {
    parent: {
      esModuleURI: "resource://gre/modules/PictureInPicture.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/PictureInPictureChild.sys.mjs",
    },

    allFrames: true,
  },

  PictureInPictureToggle: {
    parent: {
      esModuleURI: "resource://gre/modules/PictureInPicture.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/PictureInPictureChild.sys.mjs",
      events: {
        UAWidgetSetupOrChange: {},
        contextmenu: { capture: true },
      },
    },

    allFrames: true,
  },

  PopupBlocking: {
    parent: {
      esModuleURI: "resource://gre/actors/PopupBlockingParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/PopupBlockingChild.sys.mjs",
      events: {
        DOMPopupBlocked: { capture: true },
        // Only listen for the `pageshow` event after the actor has already been
        // created for some other reason.
        pageshow: { createActor: false },
      },
    },
    allFrames: true,
  },

  Printing: {
    parent: {
      esModuleURI: "resource://gre/actors/PrintingParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/PrintingChild.sys.mjs",
      events: {
        PrintingError: { capture: true },
        printPreviewUpdate: { capture: true },
      },
    },
  },

  PrintingSelection: {
    child: {
      esModuleURI: "resource://gre/actors/PrintingSelectionChild.sys.mjs",
    },
    allFrames: true,
  },

  PurgeSessionHistory: {
    child: {
      esModuleURI: "resource://gre/actors/PurgeSessionHistoryChild.sys.mjs",
    },
    allFrames: true,
  },

  ReportBrokenSite: {
    parent: {
      esModuleURI: "resource://gre/actors/ReportBrokenSiteParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/ReportBrokenSiteChild.sys.mjs",
    },
    matches: [
      "http://*/*",
      "https://*/*",
      "about:certerror?*",
      "about:neterror?*",
    ],
    messageManagerGroups: ["browsers"],
    allFrames: true,
  },

  // This actor is available for all pages that one can
  // view the source of, however it won't be created until a
  // request to view the source is made via the message
  // 'ViewSource:LoadSource' or 'ViewSource:LoadSourceWithSelection'.
  ViewSource: {
    child: {
      esModuleURI: "resource://gre/actors/ViewSourceChild.sys.mjs",
    },

    allFrames: true,
  },

  // This actor is for the view-source page itself.
  ViewSourcePage: {
    parent: {
      esModuleURI: "resource://gre/actors/ViewSourcePageParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/ViewSourcePageChild.sys.mjs",
      events: {
        pageshow: { capture: true },
        click: {},
      },
    },

    matches: ["view-source:*"],
    allFrames: true,
  },

  WebChannel: {
    parent: {
      esModuleURI: "resource://gre/actors/WebChannelParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/WebChannelChild.sys.mjs",
      events: {
        WebChannelMessageToChrome: { capture: true, wantUntrusted: true },
      },
    },

    allFrames: true,
  },

  Thumbnails: {
    child: {
      esModuleURI: "resource://gre/actors/ThumbnailsChild.sys.mjs",
    },
  },

  // Determines if a page can be translated, and coordinates communication with the
  // translations engine.
  Translations: {
    parent: {
      esModuleURI: "resource://gre/actors/TranslationsParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/TranslationsChild.sys.mjs",
      events: {
        DOMContentLoaded: {},
      },
    },
    matches: [
      "http://*/*",
      "https://*/*",
      "file:///*",

      // The actor is explicitly loaded by this page,
      // so it needs to be allowed for it.
      "about:translations",
    ],
    enablePreference: "browser.translations.enable",
  },

  // A single process that controls all of the translations.
  TranslationsEngine: {
    parent: {
      esModuleURI: "resource://gre/actors/TranslationsEngineParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/TranslationsEngineChild.sys.mjs",
      events: {
        DOMContentLoaded: { createActor: true },
      },
    },
    includeChrome: true,
    matches: ["chrome://global/content/translations/translations-engine.html"],
    enablePreference: "browser.translations.enable",
  },

  UAWidgets: {
    child: {
      esModuleURI: "resource://gre/actors/UAWidgetsChild.sys.mjs",
      events: {
        UAWidgetSetupOrChange: {},
        UAWidgetTeardown: {},
      },
    },

    includeChrome: true,
    allFrames: true,
  },

  UnselectedTabHover: {
    parent: {
      esModuleURI: "resource://gre/actors/UnselectedTabHoverParent.sys.mjs",
    },
    child: {
      esModuleURI: "resource://gre/actors/UnselectedTabHoverChild.sys.mjs",
      events: {
        "UnselectedTabHover:Enable": {},
        "UnselectedTabHover:Disable": {},
      },
    },

    allFrames: true,
  },
};

/**
 * Note that turning on page data collection for snapshots currently disables
 * collection of generic page info for normal history entries. See bug 1740234.
 */
if (!Services.prefs.getBoolPref("browser.pagedata.enabled", false)) {
  JSWINDOWACTORS.ContentMeta = {
    parent: {
      esModuleURI: "resource://gre/actors/ContentMetaParent.sys.mjs",
    },

    child: {
      esModuleURI: "resource://gre/actors/ContentMetaChild.sys.mjs",
      events: {
        DOMContentLoaded: {},
        DOMMetaAdded: { createActor: false },
      },
    },

    messageManagerGroups: ["browsers"],
  };
}

if (AppConstants.platform != "android") {
  // Note that GeckoView has another implementation in mobile/android/actors.
  JSWINDOWACTORS.Select = {
    parent: {
      esModuleURI: "resource://gre/actors/SelectParent.sys.mjs",
    },

    child: {
      esModuleURI: "resource://gre/actors/SelectChild.sys.mjs",
      events: {
        mozshowdropdown: {},
        "mozshowdropdown-sourcetouch": {},
        mozhidedropdown: { mozSystemGroup: true },
      },
    },

    includeChrome: true,
    allFrames: true,
  };

  // Note that GeckoView handles MozOpenDateTimePicker in GeckoViewPrompt.
  JSWINDOWACTORS.DateTimePicker = {
    parent: {
      esModuleURI: "resource://gre/actors/DateTimePickerParent.sys.mjs",
    },

    child: {
      esModuleURI: "resource://gre/actors/DateTimePickerChild.sys.mjs",
      events: {
        MozOpenDateTimePicker: {},
        MozUpdateDateTimePicker: {},
        MozCloseDateTimePicker: {},
      },
    },

    includeChrome: true,
    allFrames: true,
  };
}

export var ActorManagerParent = {
  _addActors(actors, kind) {
    let register, unregister;
    switch (kind) {
      case "JSProcessActor":
        register = ChromeUtils.registerProcessActor;
        unregister = ChromeUtils.unregisterProcessActor;
        break;
      case "JSWindowActor":
        register = ChromeUtils.registerWindowActor;
        unregister = ChromeUtils.unregisterWindowActor;
        break;
      default:
        throw new Error("Invalid JSActor kind " + kind);
    }
    for (let [actorName, actor] of Object.entries(actors)) {
      // The actor defines its own register/unregister logic.
      if (actor.onAddActor) {
        actor.onAddActor(
          () => register(actorName, actor),
          () => unregister(actorName, actor)
        );
        continue;
      }

      // If enablePreference is set, only register the actor while the
      // preference is set to true.
      if (actor.enablePreference) {
        let actorNameProp = actorName + "_Preference";
        XPCOMUtils.defineLazyPreferenceGetter(
          this,
          actorNameProp,
          actor.enablePreference,
          false,
          (prefName, prevValue, isEnabled) => {
            if (isEnabled) {
              register(actorName, actor);
            } else {
              unregister(actorName, actor);
            }
            if (actor.onPreferenceChanged) {
              actor.onPreferenceChanged(prefName, prevValue, isEnabled);
            }
          }
        );
        if (!this[actorNameProp]) {
          continue;
        }
      }

      register(actorName, actor);
    }
  },

  addJSProcessActors(actors) {
    this._addActors(actors, "JSProcessActor");
  },
  addJSWindowActors(actors) {
    this._addActors(actors, "JSWindowActor");
  },
};

ActorManagerParent.addJSProcessActors(JSPROCESSACTORS);
ActorManagerParent.addJSWindowActors(JSWINDOWACTORS);
PK
!<�,�OOmodules/AddonManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Cannot use Services.appinfo here, or else xpcshell-tests will blow up, as
// most tests later register different nsIAppInfo implementations, which
// wouldn't be reflected in Services.appinfo anymore, as the lazy getter
// underlying it would have been initialized if we used it here.
if ("@mozilla.org/xre/app-info;1" in Cc) {
  // eslint-disable-next-line mozilla/use-services
  let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
  if (runtime.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
    // Refuse to run in child processes.
    throw new Error("You cannot use the AddonManager in child processes!");
  }
}

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const MOZ_COMPATIBILITY_NIGHTLY = ![
  "aurora",
  "beta",
  "release",
  "esr",
].includes(AppConstants.MOZ_UPDATE_CHANNEL);

const INTL_LOCALES_CHANGED = "intl:app-locales-changed";

const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion";
const PREF_EM_UPDATE_ENABLED = "extensions.update.enabled";
const PREF_EM_LAST_APP_VERSION = "extensions.lastAppVersion";
const PREF_EM_LAST_PLATFORM_VERSION = "extensions.lastPlatformVersion";
const PREF_EM_AUTOUPDATE_DEFAULT = "extensions.update.autoUpdateDefault";
const PREF_EM_STRICT_COMPATIBILITY = "extensions.strictCompatibility";
const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity";
const PREF_SYS_ADDON_UPDATE_ENABLED = "extensions.systemAddon.update.enabled";
const PREF_REMOTESETTINGS_DISABLED = "extensions.remoteSettings.disabled";
const PREF_USE_REMOTE = "extensions.webextensions.remote";

const PREF_MIN_WEBEXT_PLATFORM_VERSION =
  "extensions.webExtensionsMinPlatformVersion";
const PREF_WEBAPI_TESTING = "extensions.webapi.testing";
const PREF_EM_POSTDOWNLOAD_THIRD_PARTY =
  "extensions.postDownloadThirdPartyPrompt";

const UPDATE_REQUEST_VERSION = 2;

const BRANCH_REGEXP = /^([^\.]+\.[0-9]+[a-z]*).*/gi;
const PREF_EM_CHECK_COMPATIBILITY_BASE = "extensions.checkCompatibility";
var PREF_EM_CHECK_COMPATIBILITY = MOZ_COMPATIBILITY_NIGHTLY
  ? PREF_EM_CHECK_COMPATIBILITY_BASE + ".nightly"
  : undefined;

const WEBAPI_INSTALL_HOSTS =
  AppConstants.MOZ_APP_NAME !== "thunderbird"
    ? ["addons.mozilla.org"]
    : ["addons.thunderbird.net"];
const WEBAPI_TEST_INSTALL_HOSTS =
  AppConstants.MOZ_APP_NAME !== "thunderbird"
    ? ["addons.allizom.org", "addons-dev.allizom.org", "example.com"]
    : ["addons-stage.thunderbird.net", "example.com"];

const AMO_ATTRIBUTION_ALLOWED_SOURCES = ["amo", "disco", "rtamo"];
const AMO_ATTRIBUTION_DATA_KEYS = [
  "utm_campaign",
  "utm_content",
  "utm_medium",
  "utm_source",
];
const AMO_ATTRIBUTION_DATA_MAX_LENGTH = 40;

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

// This global is overridden by xpcshell tests, and therefore cannot be
// a const.
import { AsyncShutdown as realAsyncShutdown } from "resource://gre/modules/AsyncShutdown.sys.mjs";

var AsyncShutdown = realAsyncShutdown;

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AbuseReporter: "resource://gre/modules/AbuseReporter.sys.mjs",
  AddonRepository: "resource://gre/modules/addons/AddonRepository.sys.mjs",
  Extension: "resource://gre/modules/Extension.sys.mjs",
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
  TelemetryTimestamps: "resource://gre/modules/TelemetryTimestamps.sys.mjs",
  isGatedPermissionType:
    "resource://gre/modules/addons/siteperms-addon-utils.sys.mjs",
  isKnownPublicSuffix:
    "resource://gre/modules/addons/siteperms-addon-utils.sys.mjs",
  isPrincipalInSitePermissionsBlocklist:
    "resource://gre/modules/addons/siteperms-addon-utils.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "WEBEXT_POSTDOWNLOAD_THIRD_PARTY",
  PREF_EM_POSTDOWNLOAD_THIRD_PARTY,
  false
);

// Initialize the WebExtension process script service as early as possible,
// since it needs to be able to track things like new frameLoader globals that
// are created before other framework code has been initialized.
Services.ppmm.loadProcessScript(
  "resource://gre/modules/extensionProcessScriptLoader.js",
  true
);

const INTEGER = /^[1-9]\d*$/;

const CATEGORY_PROVIDER_MODULE = "addon-provider-module";

import { Log } from "resource://gre/modules/Log.sys.mjs";
// Configure a logger at the parent 'addons' level to format
// messages for all the modules under addons.*
const PARENT_LOGGER_ID = "addons";
var parentLogger = Log.repository.getLogger(PARENT_LOGGER_ID);
parentLogger.level = Log.Level.Warn;
var formatter = new Log.BasicFormatter();
// Set parent logger (and its children) to append to
// the Javascript section of the Browser Console
parentLogger.addAppender(new Log.ConsoleAppender(formatter));

// Create a new logger (child of 'addons' logger)
// for use by the Addons Manager
const LOGGER_ID = "addons.manager";
var logger = Log.repository.getLogger(LOGGER_ID);

// Provide the ability to enable/disable logging
// messages at runtime.
// If the "extensions.logging.enabled" preference is
// missing or 'false', messages at the WARNING and higher
// severity should be logged to the JS console and standard error.
// If "extensions.logging.enabled" is set to 'true', messages
// at DEBUG and higher should go to JS console and standard error.
const PREF_LOGGING_ENABLED = "extensions.logging.enabled";
const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";

const UNNAMED_PROVIDER = "<unnamed-provider>";
function providerName(aProvider) {
  return aProvider.name || UNNAMED_PROVIDER;
}

// A reference to XPIProvider. This should only be used to access properties or
// methods that are independent of XPIProvider startup.
var gXPIProvider;

/**
 * Preference listener which listens for a change in the
 * "extensions.logging.enabled" preference and changes the logging level of the
 * parent 'addons' level logger accordingly.
 */
var PrefObserver = {
  init() {
    Services.prefs.addObserver(PREF_LOGGING_ENABLED, this);
    Services.obs.addObserver(this, "xpcom-shutdown");
    this.observe(null, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, PREF_LOGGING_ENABLED);
  },

  observe(aSubject, aTopic) {
    if (aTopic == "xpcom-shutdown") {
      Services.prefs.removeObserver(PREF_LOGGING_ENABLED, this);
      Services.obs.removeObserver(this, "xpcom-shutdown");
    } else if (aTopic == NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) {
      let debugLogEnabled = Services.prefs.getBoolPref(
        PREF_LOGGING_ENABLED,
        false
      );
      if (debugLogEnabled) {
        parentLogger.level = Log.Level.Debug;
      } else {
        parentLogger.level = Log.Level.Warn;
      }
    }
  },
};

PrefObserver.init();

/**
 * Calls a callback method consuming any thrown exception. Any parameters after
 * the callback parameter will be passed to the callback.
 *
 * @param  aCallback
 *         The callback method to call
 */
function safeCall(aCallback, ...aArgs) {
  try {
    aCallback.apply(null, aArgs);
  } catch (e) {
    logger.warn("Exception calling callback", e);
  }
}

/**
 * Report an exception thrown by a provider API method.
 */
function reportProviderError(aProvider, aMethod, aError) {
  let method = `provider ${providerName(aProvider)}.${aMethod}`;
  AddonManagerPrivate.recordException("AMI", method, aError);
  logger.error("Exception calling " + method, aError);
}

/**
 * Calls a method on a provider if it exists and consumes any thrown exception.
 * Any parameters after the aDefault parameter are passed to the provider's method.
 *
 * @param  aProvider
 *         The provider to call
 * @param  aMethod
 *         The method name to call
 * @param  aDefault
 *         A default return value if the provider does not implement the named
 *         method or throws an error.
 * @return the return value from the provider, or aDefault if the provider does not
 *         implement method or throws an error
 */
function callProvider(aProvider, aMethod, aDefault, ...aArgs) {
  if (!(aMethod in aProvider)) {
    return aDefault;
  }

  try {
    return aProvider[aMethod].apply(aProvider, aArgs);
  } catch (e) {
    reportProviderError(aProvider, aMethod, e);
    return aDefault;
  }
}

/**
 * Calls a method on a provider if it exists and consumes any thrown exception.
 * Parameters after aMethod are passed to aProvider.aMethod().
 * If the provider does not implement the method, or the method throws, calls
 * the callback with 'undefined'.
 *
 * @param  aProvider
 *         The provider to call
 * @param  aMethod
 *         The method name to call
 */
async function promiseCallProvider(aProvider, aMethod, ...aArgs) {
  if (!(aMethod in aProvider)) {
    return undefined;
  }
  try {
    return aProvider[aMethod].apply(aProvider, aArgs);
  } catch (e) {
    reportProviderError(aProvider, aMethod, e);
    return undefined;
  }
}

/**
 * Gets the currently selected locale for display.
 * @return  the selected locale or "en-US" if none is selected
 */
function getLocale() {
  return Services.locale.requestedLocale || "en-US";
}

const WEB_EXPOSED_ADDON_PROPERTIES = [
  "id",
  "version",
  "type",
  "name",
  "description",
  "isActive",
];

function webAPIForAddon(addon) {
  if (!addon) {
    return null;
  }

  // These web-exposed Addon properties (see AddonManager.webidl)
  // just come directly from an Addon object.
  let result = {};
  for (let prop of WEB_EXPOSED_ADDON_PROPERTIES) {
    result[prop] = addon[prop];
  }

  // These properties are computed.
  result.isEnabled = !addon.userDisabled;
  result.canUninstall = Boolean(
    addon.permissions & AddonManager.PERM_CAN_UNINSTALL
  );

  return result;
}

/**
 * Listens for a browser changing origin and cancels the installs that were
 * started by it.
 */
function BrowserListener(aBrowser, aInstallingPrincipal, aInstall) {
  this.browser = aBrowser;
  this.messageManager = this.browser.messageManager;
  this.principal = aInstallingPrincipal;
  this.install = aInstall;

  aBrowser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
  Services.obs.addObserver(this, "message-manager-close", true);

  aInstall.addListener(this);

  this.registered = true;
}

BrowserListener.prototype = {
  browser: null,
  install: null,
  registered: false,

  unregister() {
    if (!this.registered) {
      return;
    }
    this.registered = false;

    Services.obs.removeObserver(this, "message-manager-close");
    // The browser may have already been detached
    if (this.browser.removeProgressListener) {
      this.browser.removeProgressListener(this);
    }

    this.install.removeListener(this);
    this.install = null;
  },

  cancelInstall() {
    try {
      this.install.cancel();
    } catch (e) {
      // install may have already failed or been cancelled, ignore these
    }
  },

  observe(subject) {
    if (subject != this.messageManager) {
      return;
    }

    // The browser's message manager has closed and so the browser is
    // going away, cancel the install
    this.cancelInstall();
  },

  onLocationChange() {
    if (
      this.browser.contentPrincipal &&
      this.principal.subsumes(this.browser.contentPrincipal)
    ) {
      return;
    }

    // The browser has navigated to a new origin so cancel the install
    this.cancelInstall();
  },

  onDownloadCancelled() {
    this.unregister();
  },

  onDownloadFailed() {
    this.unregister();
  },

  onInstallFailed() {
    this.unregister();
  },

  onInstallEnded() {
    this.unregister();
  },

  QueryInterface: ChromeUtils.generateQI([
    "nsISupportsWeakReference",
    "nsIWebProgressListener",
    "nsIObserver",
  ]),
};

/**
 * This represents an author of an add-on (e.g. creator or developer)
 *
 * @param  aName
 *         The name of the author
 * @param  aURL
 *         The URL of the author's profile page
 */
function AddonAuthor(aName, aURL) {
  this.name = aName;
  this.url = aURL;
}

AddonAuthor.prototype = {
  name: null,
  url: null,

  // Returns the author's name, defaulting to the empty string
  toString() {
    return this.name || "";
  },
};

/**
 * This represents an screenshot for an add-on
 *
 * @param  aURL
 *         The URL to the full version of the screenshot
 * @param  aWidth
 *         The width in pixels of the screenshot
 * @param  aHeight
 *         The height in pixels of the screenshot
 * @param  aThumbnailURL
 *         The URL to the thumbnail version of the screenshot
 * @param  aThumbnailWidth
 *         The width in pixels of the thumbnail version of the screenshot
 * @param  aThumbnailHeight
 *         The height in pixels of the thumbnail version of the screenshot
 * @param  aCaption
 *         The caption of the screenshot
 */
function AddonScreenshot(
  aURL,
  aWidth,
  aHeight,
  aThumbnailURL,
  aThumbnailWidth,
  aThumbnailHeight,
  aCaption
) {
  this.url = aURL;
  if (aWidth) {
    this.width = aWidth;
  }
  if (aHeight) {
    this.height = aHeight;
  }
  if (aThumbnailURL) {
    this.thumbnailURL = aThumbnailURL;
  }
  if (aThumbnailWidth) {
    this.thumbnailWidth = aThumbnailWidth;
  }
  if (aThumbnailHeight) {
    this.thumbnailHeight = aThumbnailHeight;
  }
  if (aCaption) {
    this.caption = aCaption;
  }
}

AddonScreenshot.prototype = {
  url: null,
  width: null,
  height: null,
  thumbnailURL: null,
  thumbnailWidth: null,
  thumbnailHeight: null,
  caption: null,

  // Returns the screenshot URL, defaulting to the empty string
  toString() {
    return this.url || "";
  },
};

var gStarted = false;
var gStartedPromise = Promise.withResolvers();
var gStartupComplete = false;
var gCheckCompatibility = true;
var gStrictCompatibility = true;
var gCheckUpdateSecurityDefault = true;
var gCheckUpdateSecurity = gCheckUpdateSecurityDefault;
var gUpdateEnabled = true;
var gAutoUpdateDefault = true;
var gWebExtensionsMinPlatformVersion = "";
var gFinalShutdownBarrier = null;
var gBeforeShutdownBarrier = null;
var gRepoShutdownState = "";
var gShutdownInProgress = false;
var gBrowserUpdated = null;

export var AMTelemetry;
export var AMRemoteSettings;
export var AMBrowserExtensionsImport;

/**
 * This is the real manager, kept here rather than in AddonManager to keep its
 * contents hidden from API users.
 * @class
 * @lends AddonManager
 */
var AddonManagerInternal = {
  managerListeners: new Set(),
  installListeners: new Set(),
  addonListeners: new Set(),
  pendingProviders: new Set(),
  providers: new Set(),
  providerShutdowns: new Map(),
  typesByProvider: new Map(),
  startupChanges: {},
  // Store telemetry details per addon provider
  telemetryDetails: {},
  upgradeListeners: new Map(),
  externalExtensionLoaders: new Map(),

  recordTimestamp(name, value) {
    lazy.TelemetryTimestamps.add(name, value);
  },

  /**
   * Start up a provider, and register its shutdown hook if it has one
   *
   * @param {string} aProvider - An add-on provider.
   * @param {boolean} aAppChanged - Whether or not the app version has changed since last session.
   * @param {string} aOldAppVersion - Previous application version, if changed.
   * @param {string} aOldPlatformVersion - Previous platform version, if changed.
   *
   * @private
   */
  _startProvider(aProvider, aAppChanged, aOldAppVersion, aOldPlatformVersion) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    logger.debug(`Starting provider: ${providerName(aProvider)}`);
    callProvider(
      aProvider,
      "startup",
      null,
      aAppChanged,
      aOldAppVersion,
      aOldPlatformVersion
    );
    if ("shutdown" in aProvider) {
      let name = providerName(aProvider);
      let AMProviderShutdown = () => {
        // If the provider has been unregistered, it will have been removed from
        // this.providers. If it hasn't been unregistered, then this is a normal
        // shutdown - and we move it to this.pendingProviders in case we're
        // running in a test that will start AddonManager again.
        if (this.providers.has(aProvider)) {
          this.providers.delete(aProvider);
          this.pendingProviders.add(aProvider);
        }

        return new Promise(resolve => {
          logger.debug("Calling shutdown blocker for " + name);
          resolve(aProvider.shutdown());
        }).catch(err => {
          logger.warn("Failure during shutdown of " + name, err);
          AddonManagerPrivate.recordException(
            "AMI",
            "Async shutdown of " + name,
            err
          );
        });
      };
      logger.debug("Registering shutdown blocker for " + name);
      this.providerShutdowns.set(aProvider, AMProviderShutdown);
      AddonManagerPrivate.finalShutdown.addBlocker(name, AMProviderShutdown);
    }

    this.pendingProviders.delete(aProvider);
    this.providers.add(aProvider);
    logger.debug(`Provider finished startup: ${providerName(aProvider)}`);
  },

  _getProviderByName(aName) {
    for (let provider of this.providers) {
      if (providerName(provider) == aName) {
        return provider;
      }
    }
    return undefined;
  },

  /**
   * Initializes the AddonManager, loading any known providers and initializing
   * them.
   */
  startup() {
    try {
      if (gStarted) {
        return;
      }

      this.recordTimestamp("AMI_startup_begin");

      // Enable the addonsManager telemetry event category.
      AMTelemetry.init();

      // Enable the AMRemoteSettings client.
      AMRemoteSettings.init();

      // clear this for xpcshell test restarts
      for (let provider in this.telemetryDetails) {
        delete this.telemetryDetails[provider];
      }

      let appChanged = undefined;

      let oldAppVersion = null;
      try {
        oldAppVersion = Services.prefs.getCharPref(PREF_EM_LAST_APP_VERSION);
        appChanged = Services.appinfo.version != oldAppVersion;
      } catch (e) {}

      gBrowserUpdated = appChanged;

      let oldPlatformVersion = Services.prefs.getCharPref(
        PREF_EM_LAST_PLATFORM_VERSION,
        ""
      );

      if (appChanged !== false) {
        logger.debug("Application has been upgraded");
        Services.prefs.setCharPref(
          PREF_EM_LAST_APP_VERSION,
          Services.appinfo.version
        );
        Services.prefs.setCharPref(
          PREF_EM_LAST_PLATFORM_VERSION,
          Services.appinfo.platformVersion
        );
        Services.prefs.setIntPref(
          PREF_BLOCKLIST_PINGCOUNTVERSION,
          appChanged === undefined ? 0 : -1
        );
      }

      if (!MOZ_COMPATIBILITY_NIGHTLY) {
        PREF_EM_CHECK_COMPATIBILITY =
          PREF_EM_CHECK_COMPATIBILITY_BASE +
          "." +
          Services.appinfo.version.replace(BRANCH_REGEXP, "$1");
      }

      gCheckCompatibility = Services.prefs.getBoolPref(
        PREF_EM_CHECK_COMPATIBILITY,
        gCheckCompatibility
      );
      Services.prefs.addObserver(PREF_EM_CHECK_COMPATIBILITY, this);

      gStrictCompatibility = Services.prefs.getBoolPref(
        PREF_EM_STRICT_COMPATIBILITY,
        gStrictCompatibility
      );
      Services.prefs.addObserver(PREF_EM_STRICT_COMPATIBILITY, this);

      let defaultBranch = Services.prefs.getDefaultBranch("");
      gCheckUpdateSecurityDefault = defaultBranch.getBoolPref(
        PREF_EM_CHECK_UPDATE_SECURITY,
        gCheckUpdateSecurityDefault
      );

      gCheckUpdateSecurity = Services.prefs.getBoolPref(
        PREF_EM_CHECK_UPDATE_SECURITY,
        gCheckUpdateSecurity
      );
      Services.prefs.addObserver(PREF_EM_CHECK_UPDATE_SECURITY, this);

      gUpdateEnabled = Services.prefs.getBoolPref(
        PREF_EM_UPDATE_ENABLED,
        gUpdateEnabled
      );
      Services.prefs.addObserver(PREF_EM_UPDATE_ENABLED, this);

      gAutoUpdateDefault = Services.prefs.getBoolPref(
        PREF_EM_AUTOUPDATE_DEFAULT,
        gAutoUpdateDefault
      );
      Services.prefs.addObserver(PREF_EM_AUTOUPDATE_DEFAULT, this);

      gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(
        PREF_MIN_WEBEXT_PLATFORM_VERSION,
        gWebExtensionsMinPlatformVersion
      );
      Services.prefs.addObserver(PREF_MIN_WEBEXT_PLATFORM_VERSION, this);

      // Watch for changes to PREF_REMOTESETTINGS_DISABLED.
      Services.prefs.addObserver(PREF_REMOTESETTINGS_DISABLED, this);

      // Watch for language changes, refresh the addon cache when it changes.
      Services.obs.addObserver(this, INTL_LOCALES_CHANGED);

      // Watch for changes in the `AMBrowserExtensionsImport` singleton.
      Services.obs.addObserver(this, AMBrowserExtensionsImport.TOPIC_CANCELLED);
      Services.obs.addObserver(this, AMBrowserExtensionsImport.TOPIC_COMPLETE);
      Services.obs.addObserver(this, AMBrowserExtensionsImport.TOPIC_PENDING);

      // Ensure all default providers have had a chance to register themselves.
      const { XPIExports } = ChromeUtils.importESModule(
        "resource://gre/modules/addons/XPIExports.sys.mjs"
      );
      gXPIProvider = XPIExports.XPIProvider;
      gXPIProvider.registerProvider();

      // Load any providers registered in the category manager
      for (let { entry, value: url } of Services.catMan.enumerateCategory(
        CATEGORY_PROVIDER_MODULE
      )) {
        try {
          ChromeUtils.importESModule(url);
          logger.debug(`Loaded provider scope for ${url}`);
        } catch (e) {
          AddonManagerPrivate.recordException(
            "AMI",
            "provider " + url + " load failed",
            e
          );
          logger.error(
            "Exception loading provider " +
              entry +
              ' from category "' +
              url +
              '"',
            e
          );
        }
      }

      // Register our shutdown handler with the AsyncShutdown manager
      gBeforeShutdownBarrier = new AsyncShutdown.Barrier(
        "AddonManager: Waiting to start provider shutdown."
      );
      gFinalShutdownBarrier = new AsyncShutdown.Barrier(
        "AddonManager: Waiting for providers to shut down."
      );
      AsyncShutdown.profileBeforeChange.addBlocker(
        "AddonManager: shutting down.",
        this.shutdownManager.bind(this),
        { fetchState: this.shutdownState.bind(this) }
      );

      // Once we start calling providers we must allow all normal methods to work.
      gStarted = true;

      for (let provider of this.pendingProviders) {
        this._startProvider(
          provider,
          appChanged,
          oldAppVersion,
          oldPlatformVersion
        );
      }

      // If this is a new profile just pretend that there were no changes
      if (appChanged === undefined) {
        for (let type in this.startupChanges) {
          delete this.startupChanges[type];
        }
      }

      gStartupComplete = true;
      gStartedPromise.resolve();
      this.recordTimestamp("AMI_startup_end");
    } catch (e) {
      logger.error("startup failed", e);
      AddonManagerPrivate.recordException("AMI", "startup failed", e);
      gStartedPromise.reject("startup failed");
    }

    // Disable the quarantined domains feature if the system add-on has been
    // disabled in a previous version.
    if (
      Services.prefs.getBoolPref(
        "extensions.webextensions.addons-restricted-domains@mozilla.com.disabled",
        false
      )
    ) {
      Services.prefs.setBoolPref(
        "extensions.quarantinedDomains.enabled",
        false
      );
      logger.debug(
        "Disabled quarantined domains because the system add-on was disabled"
      );
    }

    Glean.extensions.useRemotePolicy.set(
      WebExtensionPolicy.useRemoteWebExtensions
    );
    Glean.extensions.useRemotePref.set(
      Services.prefs.getBoolPref(PREF_USE_REMOTE)
    );
    Services.prefs.addObserver(PREF_USE_REMOTE, this);

    logger.debug("Completed startup sequence");
    this.callManagerListeners("onStartup");
  },

  /**
   * Registers a new AddonProvider.
   *
   * @param {string} aProvider -The provider to register
   * @param {string[]} [aTypes] - An optional array of add-on types
   */
  registerProvider(aProvider, aTypes) {
    if (!aProvider || typeof aProvider != "object") {
      throw Components.Exception(
        "aProvider must be specified",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (aTypes && !Array.isArray(aTypes)) {
      throw Components.Exception(
        "aTypes must be an array or null",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    this.pendingProviders.add(aProvider);

    if (aTypes) {
      this.typesByProvider.set(aProvider, new Set(aTypes));
    }

    // If we're registering after startup call this provider's startup.
    if (gStarted) {
      this._startProvider(aProvider);
    }
  },

  /**
   * Unregisters an AddonProvider.
   *
   * @param  aProvider
   *         The provider to unregister
   * @return Whatever the provider's 'shutdown' method returns (if anything).
   *         For providers that have async shutdown methods returning Promises,
   *         the caller should wait for that Promise to resolve.
   */
  unregisterProvider(aProvider) {
    if (!aProvider || typeof aProvider != "object") {
      throw Components.Exception(
        "aProvider must be specified",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    this.providers.delete(aProvider);
    // The test harness will unregister XPIProvider *after* shutdown, which is
    // after the provider will have been moved from providers to
    // pendingProviders.
    this.pendingProviders.delete(aProvider);

    this.typesByProvider.delete(aProvider);

    // If we're unregistering after startup but before shutting down,
    // remove the blocker for this provider's shutdown and call it.
    // If we're already shutting down, just let gFinalShutdownBarrier
    // call it to avoid races.
    if (gStarted && !gShutdownInProgress) {
      logger.debug(
        "Unregistering shutdown blocker for " + providerName(aProvider)
      );
      let shutter = this.providerShutdowns.get(aProvider);
      if (shutter) {
        this.providerShutdowns.delete(aProvider);
        gFinalShutdownBarrier.client.removeBlocker(shutter);
        return shutter();
      }
    }
    return undefined;
  },

  /**
   * Mark a provider as safe to access via AddonManager APIs, before its
   * startup has completed.
   *
   * Normally a provider isn't marked as safe until after its (synchronous)
   * startup() method has returned. Until a provider has been marked safe,
   * it won't be used by any of the AddonManager APIs. markProviderSafe()
   * allows a provider to mark itself as safe during its startup; this can be
   * useful if the provider wants to perform tasks that block startup, which
   * happen after its required initialization tasks and therefore when the
   * provider is in a safe state.
   *
   * @param aProvider Provider object to mark safe
   */
  markProviderSafe(aProvider) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (!aProvider || typeof aProvider != "object") {
      throw Components.Exception(
        "aProvider must be specified",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (!this.pendingProviders.has(aProvider)) {
      return;
    }

    this.pendingProviders.delete(aProvider);
    this.providers.add(aProvider);
  },

  /**
   * Calls a method on all registered providers if it exists and consumes any
   * thrown exception. Return values are ignored. Any parameters after the
   * method parameter are passed to the provider's method.
   * WARNING: Do not use for asynchronous calls; callProviders() does not
   * invoke callbacks if provider methods throw synchronous exceptions.
   *
   * @param  aMethod
   *         The method name to call
   */
  callProviders(aMethod, ...aArgs) {
    if (!aMethod || typeof aMethod != "string") {
      throw Components.Exception(
        "aMethod must be a non-empty string",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    let providers = [...this.providers];
    for (let provider of providers) {
      try {
        if (aMethod in provider) {
          provider[aMethod].apply(provider, aArgs);
        }
      } catch (e) {
        reportProviderError(provider, aMethod, e);
      }
    }
  },

  /**
   * Report the current state of asynchronous shutdown
   */
  shutdownState() {
    let state = [];
    for (let barrier of [gBeforeShutdownBarrier, gFinalShutdownBarrier]) {
      if (barrier) {
        state.push({ name: barrier.client.name, state: barrier.state });
      }
    }
    state.push({
      name: "AddonRepository: async shutdown",
      state: gRepoShutdownState,
    });
    return state;
  },

  /**
   * Shuts down the addon manager and all registered providers, this must clean
   * up everything in order for automated tests to fake restarts.
   * @return Promise{null} that resolves when all providers and dependent modules
   *                       have finished shutting down
   */
  async shutdownManager() {
    logger.debug("before shutdown");
    try {
      await gBeforeShutdownBarrier.wait();
    } catch (e) {
      Cu.reportError(e);
    }

    logger.debug("shutdown");
    this.callManagerListeners("onShutdown");

    if (!gStartupComplete) {
      gStartedPromise.reject("shutting down");
    }

    gRepoShutdownState = "pending";
    gShutdownInProgress = true;

    // Clean up listeners
    Services.prefs.removeObserver(PREF_EM_CHECK_COMPATIBILITY, this);
    Services.prefs.removeObserver(PREF_EM_STRICT_COMPATIBILITY, this);
    Services.prefs.removeObserver(PREF_EM_CHECK_UPDATE_SECURITY, this);
    Services.prefs.removeObserver(PREF_EM_UPDATE_ENABLED, this);
    Services.prefs.removeObserver(PREF_EM_AUTOUPDATE_DEFAULT, this);
    Services.prefs.removeObserver(PREF_REMOTESETTINGS_DISABLED, this);

    Services.obs.removeObserver(this, INTL_LOCALES_CHANGED);

    Services.obs.removeObserver(
      this,
      AMBrowserExtensionsImport.TOPIC_CANCELLED
    );
    Services.obs.removeObserver(this, AMBrowserExtensionsImport.TOPIC_COMPLETE);
    Services.obs.removeObserver(this, AMBrowserExtensionsImport.TOPIC_PENDING);

    AMRemoteSettings.shutdown();

    let savedError = null;
    // Only shut down providers if they've been started.
    if (gStarted) {
      try {
        await gFinalShutdownBarrier.wait();
      } catch (err) {
        savedError = err;
        logger.error("Failure during wait for shutdown barrier", err);
        AddonManagerPrivate.recordException(
          "AMI",
          "Async shutdown of AddonManager providers",
          err
        );
      }
    }
    gXPIProvider = null;

    // Shut down AddonRepository after providers (if any).
    try {
      gRepoShutdownState = "in progress";
      await lazy.AddonRepository.shutdown();
      gRepoShutdownState = "done";
    } catch (err) {
      savedError = err;
      logger.error("Failure during AddonRepository shutdown", err);
      AddonManagerPrivate.recordException(
        "AMI",
        "Async shutdown of AddonRepository",
        err
      );
    }

    logger.debug("Async provider shutdown done");
    this.managerListeners.clear();
    this.installListeners.clear();
    this.addonListeners.clear();
    this.providerShutdowns.clear();
    for (let type in this.startupChanges) {
      delete this.startupChanges[type];
    }
    gStarted = false;
    gStartedPromise = Promise.withResolvers();
    gStartupComplete = false;
    gFinalShutdownBarrier = null;
    gBeforeShutdownBarrier = null;
    gShutdownInProgress = false;
    if (savedError) {
      throw savedError;
    }
  },

  /**
   * Notified when a preference we're interested in has changed.
   */
  observe(aSubject, aTopic, aData) {
    switch (aTopic) {
      case INTL_LOCALES_CHANGED: {
        // Asynchronously fetch and update the addons cache.
        lazy.AddonRepository.backgroundUpdateCheck();
        return;
      }

      case AMBrowserExtensionsImport.TOPIC_CANCELLED:
      case AMBrowserExtensionsImport.TOPIC_COMPLETE:
      case AMBrowserExtensionsImport.TOPIC_PENDING:
        this.callManagerListeners("onBrowserExtensionsImportChanged");
        return;
    }

    switch (aData) {
      case PREF_EM_CHECK_COMPATIBILITY: {
        let oldValue = gCheckCompatibility;
        gCheckCompatibility = Services.prefs.getBoolPref(
          PREF_EM_CHECK_COMPATIBILITY,
          true
        );

        this.callManagerListeners("onCompatibilityModeChanged");

        if (gCheckCompatibility != oldValue) {
          this.updateAddonAppDisabledStates();
        }

        break;
      }
      case PREF_EM_STRICT_COMPATIBILITY: {
        let oldValue = gStrictCompatibility;
        gStrictCompatibility = Services.prefs.getBoolPref(
          PREF_EM_STRICT_COMPATIBILITY,
          true
        );

        this.callManagerListeners("onCompatibilityModeChanged");

        if (gStrictCompatibility != oldValue) {
          this.updateAddonAppDisabledStates();
        }

        break;
      }
      case PREF_EM_CHECK_UPDATE_SECURITY: {
        let oldValue = gCheckUpdateSecurity;
        gCheckUpdateSecurity = Services.prefs.getBoolPref(
          PREF_EM_CHECK_UPDATE_SECURITY,
          true
        );

        this.callManagerListeners("onCheckUpdateSecurityChanged");

        if (gCheckUpdateSecurity != oldValue) {
          this.updateAddonAppDisabledStates();
        }

        break;
      }
      case PREF_EM_UPDATE_ENABLED: {
        gUpdateEnabled = Services.prefs.getBoolPref(
          PREF_EM_UPDATE_ENABLED,
          true
        );

        this.callManagerListeners("onUpdateModeChanged");
        break;
      }
      case PREF_EM_AUTOUPDATE_DEFAULT: {
        gAutoUpdateDefault = Services.prefs.getBoolPref(
          PREF_EM_AUTOUPDATE_DEFAULT,
          true
        );

        this.callManagerListeners("onUpdateModeChanged");
        break;
      }
      case PREF_MIN_WEBEXT_PLATFORM_VERSION: {
        gWebExtensionsMinPlatformVersion = Services.prefs.getCharPref(
          PREF_MIN_WEBEXT_PLATFORM_VERSION
        );
        break;
      }
      case PREF_REMOTESETTINGS_DISABLED: {
        if (Services.prefs.getBoolPref(PREF_REMOTESETTINGS_DISABLED, false)) {
          AMRemoteSettings.shutdown();
        } else {
          AMRemoteSettings.init();
        }
        break;
      }
      case PREF_USE_REMOTE: {
        Glean.extensions.useRemotePref.set(
          Services.prefs.getBoolPref(PREF_USE_REMOTE)
        );
        break;
      }
    }
  },

  /**
   * Replaces %...% strings in an addon url (update and updateInfo) with
   * appropriate values.
   *
   * @param  aAddon
   *         The Addon representing the add-on
   * @param  aUri
   *         The string representation of the URI to escape
   * @param  aAppVersion
   *         The optional application version to use for %APP_VERSION%
   * @return The appropriately escaped URI.
   */
  escapeAddonURI(aAddon, aUri, aAppVersion) {
    if (!aAddon || typeof aAddon != "object") {
      throw Components.Exception(
        "aAddon must be an Addon object",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (!aUri || typeof aUri != "string") {
      throw Components.Exception(
        "aUri must be a non-empty string",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (aAppVersion && typeof aAppVersion != "string") {
      throw Components.Exception(
        "aAppVersion must be a string or null",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    var addonStatus =
      aAddon.userDisabled || aAddon.softDisabled
        ? "userDisabled"
        : "userEnabled";

    if (!aAddon.isCompatible) {
      addonStatus += ",incompatible";
    }

    let { blocklistState } = aAddon;
    if (blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
      addonStatus += ",blocklisted";
    }
    if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
      addonStatus += ",softblocked";
    }

    let params = new Map(
      Object.entries({
        ITEM_ID: aAddon.id,
        ITEM_VERSION: aAddon.version,
        ITEM_STATUS: addonStatus,
        APP_ID: Services.appinfo.ID,
        APP_VERSION: aAppVersion ? aAppVersion : Services.appinfo.version,
        REQ_VERSION: UPDATE_REQUEST_VERSION,
        APP_OS: Services.appinfo.OS,
        APP_ABI: Services.appinfo.XPCOMABI,
        APP_LOCALE: getLocale(),
        CURRENT_APP_VERSION: Services.appinfo.version,
      })
    );

    let uri = aUri.replace(/%([A-Z_]+)%/g, (m0, m1) => params.get(m1) || m0);

    // escape() does not properly encode + symbols in any embedded FVF strings.
    return uri.replace(/\+/g, "%2B");
  },

  _updatePromptHandler(info) {
    let oldPerms = info.existingAddon.userPermissions;
    if (!oldPerms) {
      // Updating from a legacy add-on, just let it proceed
      return Promise.resolve();
    }

    let newPerms = info.addon.userPermissions;

    let difference = lazy.Extension.comparePermissions(oldPerms, newPerms);

    // If there are no new permissions, just go ahead with the update
    if (!difference.origins.length && !difference.permissions.length) {
      return Promise.resolve();
    }

    return new Promise((resolve, reject) => {
      let subject = {
        wrappedJSObject: {
          addon: info.addon,
          permissions: difference,
          resolve,
          reject,
          // Reference to the related AddonInstall object (used in AMTelemetry to
          // link the recorded event to the other events from the same install flow).
          install: info.install,
        },
      };
      Services.obs.notifyObservers(subject, "webextension-update-permissions");
    });
  },

  // Returns true if System Addons should be updated
  systemUpdateEnabled() {
    if (!Services.prefs.getBoolPref(PREF_SYS_ADDON_UPDATE_ENABLED)) {
      return false;
    }
    if (Services.policies && !Services.policies.isAllowed("SysAddonUpdate")) {
      return false;
    }
    return true;
  },

  /**
   * Performs a background update check by starting an update for all add-ons
   * that can be updated.
   * @return Promise{null} Resolves when the background update check is complete
   *                       (the resulting addon installations may still be in progress).
   */
  backgroundUpdateCheck() {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    let buPromise = (async () => {
      logger.debug("Background update check beginning");

      Services.obs.notifyObservers(null, "addons-background-update-start");

      if (this.updateEnabled) {
        // Keep track of all the async add-on updates happening in parallel
        let updates = [];

        let allAddons = await this.getAllAddons();

        // Repopulate repository cache first, to ensure compatibility overrides
        // are up to date before checking for addon updates.
        await lazy.AddonRepository.backgroundUpdateCheck();

        for (let addon of allAddons) {
          // Check all add-ons for updates so that any compatibility updates will
          // be applied

          if (!(addon.permissions & AddonManager.PERM_CAN_UPGRADE)) {
            continue;
          }

          updates.push(
            new Promise(resolve => {
              addon.findUpdates(
                {
                  onUpdateAvailable(aAddon, aInstall) {
                    // Start installing updates when the add-on can be updated and
                    // background updates should be applied.
                    logger.debug("Found update for add-on ${id}", aAddon);
                    if (AddonManager.shouldAutoUpdate(aAddon)) {
                      // XXX we really should resolve when this install is done,
                      // not when update-available check completes, no?
                      logger.debug(`Starting upgrade install of ${aAddon.id}`);
                      aInstall.promptHandler = (...args) =>
                        AddonManagerInternal._updatePromptHandler(...args);
                      aInstall.install();
                    }
                  },

                  onUpdateFinished: aAddon => {
                    logger.debug("onUpdateFinished for ${id}", aAddon);
                    resolve();
                  },
                },
                AddonManager.UPDATE_WHEN_PERIODIC_UPDATE
              );
            })
          );
        }
        Services.obs.notifyObservers(
          null,
          "addons-background-updates-found",
          updates.length
        );
        await Promise.all(updates);
      }

      if (AddonManagerInternal.systemUpdateEnabled()) {
        try {
          await AddonManagerInternal._getProviderByName(
            "XPIProvider"
          ).updateSystemAddons();
        } catch (e) {
          logger.warn("Failed to update system addons", e);
        }
      }

      logger.debug("Background update check complete");
      Services.obs.notifyObservers(null, "addons-background-update-complete");
    })();
    // Fork the promise chain so we can log the error and let our caller see it too.
    buPromise.catch(e => logger.warn("Error in background update", e));
    return buPromise;
  },

  /**
   * Adds a add-on to the list of detected changes for this startup. If
   * addStartupChange is called multiple times for the same add-on in the same
   * startup then only the most recent change will be remembered.
   *
   * @param  aType
   *         The type of change as a string. Providers can define their own
   *         types of changes or use the existing defined STARTUP_CHANGE_*
   *         constants
   * @param  aID
   *         The ID of the add-on
   */
  addStartupChange(aType, aID) {
    if (!aType || typeof aType != "string") {
      throw Components.Exception(
        "aType must be a non-empty string",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (!aID || typeof aID != "string") {
      throw Components.Exception(
        "aID must be a non-empty string",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (gStartupComplete) {
      return;
    }
    logger.debug("Registering startup change '" + aType + "' for " + aID);

    // Ensure that an ID is only listed in one type of change
    for (let type in this.startupChanges) {
      this.removeStartupChange(type, aID);
    }

    if (!(aType in this.startupChanges)) {
      this.startupChanges[aType] = [];
    }
    this.startupChanges[aType].push(aID);
  },

  /**
   * Removes a startup change for an add-on.
   *
   * @param  aType
   *         The type of change
   * @param  aID
   *         The ID of the add-on
   */
  removeStartupChange(aType, aID) {
    if (!aType || typeof aType != "string") {
      throw Components.Exception(
        "aType must be a non-empty string",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (!aID || typeof aID != "string") {
      throw Components.Exception(
        "aID must be a non-empty string",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (gStartupComplete) {
      return;
    }

    if (!(aType in this.startupChanges)) {
      return;
    }

    this.startupChanges[aType] = this.startupChanges[aType].filter(
      aItem => aItem != aID
    );
  },

  /**
   * Calls all registered AddonManagerListeners with an event. Any parameters
   * after the method parameter are passed to the listener.
   *
   * @param  aMethod
   *         The method on the listeners to call
   */
  callManagerListeners(aMethod, ...aArgs) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (!aMethod || typeof aMethod != "string") {
      throw Components.Exception(
        "aMethod must be a non-empty string",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    let managerListeners = new Set(this.managerListeners);
    for (let listener of managerListeners) {
      try {
        if (aMethod in listener) {
          listener[aMethod].apply(listener, aArgs);
        }
      } catch (e) {
        logger.warn(
          "AddonManagerListener threw exception when calling " + aMethod,
          e
        );
      }
    }
  },

  /**
   * Calls all registered InstallListeners with an event. Any parameters after
   * the extraListeners parameter are passed to the listener.
   *
   * @param  aMethod
   *         The method on the listeners to call
   * @param  aExtraListeners
   *         An optional array of extra InstallListeners to also call
   * @return false if any of the listeners returned false, true otherwise
   */
  callInstallListeners(aMethod, aExtraListeners, ...aArgs) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (!aMethod || typeof aMethod != "string") {
      throw Components.Exception(
        "aMethod must be a non-empty string",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (aExtraListeners && !Array.isArray(aExtraListeners)) {
      throw Components.Exception(
        "aExtraListeners must be an array or null",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    let result = true;
    let listeners;
    if (aExtraListeners) {
      listeners = new Set(
        aExtraListeners.concat(Array.from(this.installListeners))
      );
    } else {
      listeners = new Set(this.installListeners);
    }

    for (let listener of listeners) {
      try {
        if (aMethod in listener) {
          if (listener[aMethod].apply(listener, aArgs) === false) {
            result = false;
          }
        }
      } catch (e) {
        logger.warn(
          "InstallListener threw exception when calling " + aMethod,
          e
        );
      }
    }
    return result;
  },

  /**
   * Calls all registered AddonListeners with an event. Any parameters after
   * the method parameter are passed to the listener.
   *
   * @param  aMethod
   *         The method on the listeners to call
   */
  callAddonListeners(aMethod, ...aArgs) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (!aMethod || typeof aMethod != "string") {
      throw Components.Exception(
        "aMethod must be a non-empty string",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    let addonListeners = new Set(this.addonListeners);
    for (let listener of addonListeners) {
      try {
        if (aMethod in listener) {
          listener[aMethod].apply(listener, aArgs);
        }
      } catch (e) {
        logger.warn("AddonListener threw exception when calling " + aMethod, e);
      }
    }
  },

  /**
   * Notifies all providers that an add-on has been enabled when that type of
   * add-on only supports a single add-on being enabled at a time. This allows
   * the providers to disable theirs if necessary.
   *
   * @param  aID
   *         The ID of the enabled add-on
   * @param  aType
   *         The type of the enabled add-on
   * @param  aPendingRestart
   *         A boolean indicating if the change will only take place the next
   *         time the application is restarted
   */
  async notifyAddonChanged(aID, aType, aPendingRestart) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (aID && typeof aID != "string") {
      throw Components.Exception(
        "aID must be a string or null",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (!aType || typeof aType != "string") {
      throw Components.Exception(
        "aType must be a non-empty string",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    // Temporary hack until bug 520124 lands.
    // We can get here during synchronous startup, at which point it's
    // considered unsafe (and therefore disallowed by AddonManager.sys.mjs) to
    // access providers that haven't been initialized yet. Since this is when
    // XPIProvider is starting up, XPIProvider can't access itself via APIs
    // going through AddonManager.sys.mjs. Thankfully, this is the only use
    // of this API, and we know it's safe to use this API with both
    // providers; so we have this hack to allow bypassing the normal
    // safetey guard.
    // The notifyAddonChanged/addonChanged API will be unneeded and therefore
    // removed by bug 520124, so this is a temporary quick'n'dirty hack.
    let providers = [...this.providers, ...this.pendingProviders];
    for (let provider of providers) {
      let result = callProvider(
        provider,
        "addonChanged",
        null,
        aID,
        aType,
        aPendingRestart
      );
      if (result) {
        await result;
      }
    }
  },

  /**
   * Notifies all providers they need to update the appDisabled property for
   * their add-ons in response to an application change such as a blocklist
   * update.
   */
  updateAddonAppDisabledStates() {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    this.callProviders("updateAddonAppDisabledStates");
  },

  /**
   * Notifies all providers that the repository has updated its data for
   * installed add-ons.
   */
  updateAddonRepositoryData() {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    return (async () => {
      for (let provider of this.providers) {
        await promiseCallProvider(provider, "updateAddonRepositoryData");
      }

      // only tests should care about this
      Services.obs.notifyObservers(null, "TEST:addon-repository-data-updated");
    })();
  },

  /**
   * Asynchronously gets an AddonInstall for a URL.
   *
   * @param  aUrl
   *         The string represenation of the URL where the add-on is located
   * @param  {Object} [aOptions = {}]
   *         Additional options for this install
   * @param  {string} [aOptions.hash]
   *         An optional hash of the add-on
   * @param  {string} [aOptions.name]
   *         An optional placeholder name while the add-on is being downloaded
   * @param  {string|Object} [aOptions.icons]
   *         Optional placeholder icons while the add-on is being downloaded
   * @param  {string} [aOptions.version]
   *         An optional placeholder version while the add-on is being downloaded
   * @param  {XULElement} [aOptions.browser]
   *         An optional <browser> element for download permissions prompts.
   * @param  {nsIPrincipal} [aOptions.triggeringPrincipal]
   *         The principal which is attempting to install the add-on.
   * @param  {Object} [aOptions.telemetryInfo]
   *         An optional object which provides details about the installation source
   *         included in the addon manager telemetry events.
   * @throws if aUrl is not specified or if an optional argument of
   *         an improper type is passed.
   */
  async getInstallForURL(aUrl, aOptions = {}) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (!aUrl || typeof aUrl != "string") {
      throw Components.Exception(
        "aURL must be a non-empty string",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (aOptions.hash && typeof aOptions.hash != "string") {
      throw Components.Exception(
        "hash must be a string or null",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (aOptions.name && typeof aOptions.name != "string") {
      throw Components.Exception(
        "name must be a string or null",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (aOptions.icons) {
      if (typeof aOptions.icons == "string") {
        aOptions.icons = { 32: aOptions.icons };
      } else if (typeof aOptions.icons != "object") {
        throw Components.Exception(
          "icons must be a string, an object or null",
          Cr.NS_ERROR_INVALID_ARG
        );
      }
    } else {
      aOptions.icons = {};
    }

    if (aOptions.version && typeof aOptions.version != "string") {
      throw Components.Exception(
        "version must be a string or null",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (aOptions.browser && !Element.isInstance(aOptions.browser)) {
      throw Components.Exception(
        "aOptions.browser must be an Element or null",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    for (let provider of this.providers) {
      let install = await promiseCallProvider(
        provider,
        "getInstallForURL",
        aUrl,
        aOptions
      );
      if (install) {
        return install;
      }
    }

    return null;
  },

  /**
   * Asynchronously gets an AddonInstall for an nsIFile.
   *
   * @param  aFile
   *         The nsIFile where the add-on is located
   * @param  aMimetype
   *         An optional mimetype hint for the add-on
   * @param  aTelemetryInfo
   *         An optional object which provides details about the installation source
   *         included in the addon manager telemetry events.
   * @param  aUseSystemLocation
   *         If true the addon is installed into the system profile location.
   * @throws if the aFile or aCallback arguments are not specified
   */
  getInstallForFile(aFile, aMimetype, aTelemetryInfo, aUseSystemLocation) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (!(aFile instanceof Ci.nsIFile)) {
      throw Components.Exception(
        "aFile must be a nsIFile",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (aMimetype && typeof aMimetype != "string") {
      throw Components.Exception(
        "aMimetype must be a string or null",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    return (async () => {
      for (let provider of this.providers) {
        let install = await promiseCallProvider(
          provider,
          "getInstallForFile",
          aFile,
          aTelemetryInfo,
          aUseSystemLocation
        );

        if (install) {
          return install;
        }
      }

      return null;
    })();
  },

  /**
   * Get a SitePermsAddonInstall instance.
   *
   * @param  {Element} aBrowser: The optional browser element that started the install
   * @param {nsIPrincipal} aInstallingPrincipal
   * @param {String} aSitePerm
   * @returns {Promise<SitePermsAddonInstall|null>} The promise will resolve with null if there
   *         are no provider with a getSitePermsAddonInstallForWebpage method. In practice,
   *         this should only be the case when SitePermsAddonProvider is not enabled,
   *         i.e. when dom.sitepermsaddon-provider.enabled is false.
   * @throws {Components.Exception} Will throw an error if:
   *         - the AddonManager is not initialized
   *         - `aInstallingPrincipal` is not a nsIPrincipal
   *         - `aInstallingPrincipal` scheme is not https
   *         - `aInstallingPrincipal` is a public etld
   *         - `aInstallingPrincipal` is a plain ip address
   *         - `aInstallingPrincipal` is in the blocklist
   *         - `aSitePerm` is not a gated permission
   *         - `aBrowser` is not null and not an element
   */
  async getSitePermsAddonInstallForWebpage(
    aBrowser,
    aInstallingPrincipal,
    aSitePerm
  ) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (
      !aInstallingPrincipal ||
      !(aInstallingPrincipal instanceof Ci.nsIPrincipal)
    ) {
      throw Components.Exception(
        "aInstallingPrincipal must be a nsIPrincipal",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (aBrowser && !Element.isInstance(aBrowser)) {
      throw Components.Exception(
        "aBrowser must be an Element, or null",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (lazy.isPrincipalInSitePermissionsBlocklist(aInstallingPrincipal)) {
      throw Components.Exception(
        `SitePermsAddons can't be installed`,
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    // Block install from null principal.
    // /!\ We need to do this check before checking if this is a remote origin iframe,
    // otherwise isThirdPartyPrincipal might throw.
    if (aInstallingPrincipal.isNullPrincipal) {
      throw Components.Exception(
        `SitePermsAddons can't be installed from sandboxed subframes`,
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    // Block install from remote origin iframe
    if (
      aBrowser &&
      aBrowser.contentPrincipal.isThirdPartyPrincipal(aInstallingPrincipal)
    ) {
      throw Components.Exception(
        `SitePermsAddons can't be installed from cross origin subframes`,
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (aInstallingPrincipal.isIpAddress) {
      throw Components.Exception(
        `SitePermsAddons install disallowed when the host is an IP address`,
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    // Gated APIs should probably not be available on non-secure origins,
    // but let's double check here.
    if (aInstallingPrincipal.scheme !== "https") {
      throw Components.Exception(
        `SitePermsAddons can only be installed from secure origins`,
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    // Install origin cannot be on a known etld (e.g. github.io).
    if (lazy.isKnownPublicSuffix(aInstallingPrincipal.siteOriginNoSuffix)) {
      throw Components.Exception(
        `SitePermsAddon can't be installed from public eTLDs ${aInstallingPrincipal.siteOriginNoSuffix}`,
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (!lazy.isGatedPermissionType(aSitePerm)) {
      throw Components.Exception(
        `"${aSitePerm}" is not a gated permission`,
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    for (let provider of this.providers) {
      let install = await promiseCallProvider(
        provider,
        "getSitePermsAddonInstallForWebpage",
        aInstallingPrincipal,
        aSitePerm
      );
      if (install) {
        return install;
      }
    }

    return null;
  },

  /**
   * Uninstall an addon from the system profile location.
   *
   * @param {string} aID
   *         The ID of the addon to remove.
   * @returns A promise that resolves when the addon is uninstalled.
   */
  uninstallSystemProfileAddon(aID) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }
    return AddonManagerInternal._getProviderByName(
      "XPIProvider"
    ).uninstallSystemProfileAddon(aID);
  },

  /**
   * Asynchronously gets all current AddonInstalls optionally limiting to a list
   * of types.
   *
   * @param  aTypes
   *         An optional array of types to retrieve. Each type is a string name
   * @throws If the aCallback argument is not specified
   */
  getInstallsByTypes(aTypes) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (aTypes && !Array.isArray(aTypes)) {
      throw Components.Exception(
        "aTypes must be an array or null",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    return (async () => {
      let installs = [];

      for (let provider of this.providers) {
        let providerInstalls = await promiseCallProvider(
          provider,
          "getInstallsByTypes",
          aTypes
        );

        if (providerInstalls) {
          installs.push(...providerInstalls);
        }
      }

      return installs;
    })();
  },

  /**
   * Asynchronously gets all current AddonInstalls.
   */
  getAllInstalls() {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    return this.getInstallsByTypes(null);
  },

  /**
   * Checks whether installation is enabled for a particular mimetype.
   *
   * @param  aMimetype
   *         The mimetype to check
   * @return true if installation is enabled for the mimetype
   */
  isInstallEnabled(aMimetype) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (!aMimetype || typeof aMimetype != "string") {
      throw Components.Exception(
        "aMimetype must be a non-empty string",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    let providers = [...this.providers];
    for (let provider of providers) {
      if (
        callProvider(provider, "supportsMimetype", false, aMimetype) &&
        callProvider(provider, "isInstallEnabled")
      ) {
        return true;
      }
    }
    return false;
  },

  /**
   * Checks whether a particular source is allowed to install add-ons of a
   * given mimetype.
   *
   * @param  aMimetype
   *         The mimetype of the add-on
   * @param  aInstallingPrincipal
   *         The nsIPrincipal that initiated the install
   * @return true if the source is allowed to install this mimetype
   */
  isInstallAllowed(aMimetype, aInstallingPrincipal) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (!aMimetype || typeof aMimetype != "string") {
      throw Components.Exception(
        "aMimetype must be a non-empty string",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (
      !aInstallingPrincipal ||
      !(aInstallingPrincipal instanceof Ci.nsIPrincipal)
    ) {
      throw Components.Exception(
        "aInstallingPrincipal must be a nsIPrincipal",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (
      this.isInstallAllowedByPolicy(
        aInstallingPrincipal,
        null,
        true /* explicit */
      )
    ) {
      return true;
    }

    let providers = [...this.providers];
    for (let provider of providers) {
      if (
        callProvider(provider, "supportsMimetype", false, aMimetype) &&
        callProvider(provider, "isInstallAllowed", null, aInstallingPrincipal)
      ) {
        return true;
      }
    }
    return false;
  },

  /**
   * Checks whether a particular source is allowed to install add-ons based
   * on policy.
   *
   * @param  aInstallingPrincipal
   *         The nsIPrincipal that initiated the install
   * @param  aInstall
   *         The AddonInstall to be installed
   * @param  explicit
   *         If this is set, we only return true if the source is explicitly
   *         blocked via policy.
   *
   * @return boolean
   *         By default, returns true if the source is blocked by policy
   *         or there is no policy.
   *         If explicit is set, only returns true of the source is
   *         blocked by policy, false otherwise. This is needed for
   *         handling inverse cases.
   */
  isInstallAllowedByPolicy(aInstallingPrincipal, aInstall, explicit) {
    if (Services.policies) {
      let extensionSettings = Services.policies.getExtensionSettings("*");
      if (extensionSettings && extensionSettings.install_sources) {
        if (
          (!aInstall ||
            Services.policies.allowedInstallSource(aInstall.sourceURI)) &&
          (!aInstallingPrincipal ||
            !aInstallingPrincipal.URI ||
            Services.policies.allowedInstallSource(aInstallingPrincipal.URI))
        ) {
          return true;
        }
        return false;
      }
    }
    return !explicit;
  },

  installNotifyObservers(
    aTopic,
    aBrowser,
    aUri,
    aInstall,
    aInstallFn,
    aCancelFn
  ) {
    let info = {
      wrappedJSObject: {
        browser: aBrowser,
        originatingURI: aUri,
        installs: [aInstall],
        install: aInstallFn,
        cancel: aCancelFn,
      },
    };
    Services.obs.notifyObservers(info, aTopic);
  },

  startInstall(browser, url, install) {
    this.installNotifyObservers("addon-install-started", browser, url, install);

    // Local installs may already be in a failed state in which case
    // we won't get any further events, detect those cases now.
    if (
      install.state == AddonManager.STATE_DOWNLOADED &&
      install.addon.appDisabled
    ) {
      install.cancel();
      this.installNotifyObservers(
        "addon-install-failed",
        browser,
        url,
        install
      );
      return;
    }

    let self = this;
    let listener = {
      onDownloadCancelled() {
        install.removeListener(listener);
      },

      onDownloadFailed() {
        install.removeListener(listener);
        self.installNotifyObservers(
          "addon-install-failed",
          browser,
          url,
          install
        );
      },

      onDownloadEnded() {
        if (install.addon.appDisabled) {
          // App disabled items are not compatible and so fail to install.
          install.removeListener(listener);
          install.cancel();
          self.installNotifyObservers(
            "addon-install-failed",
            browser,
            url,
            install
          );
        }
      },

      onInstallCancelled() {
        install.removeListener(listener);
      },

      onInstallFailed() {
        install.removeListener(listener);
        self.installNotifyObservers(
          "addon-install-failed",
          browser,
          url,
          install
        );
      },

      onInstallEnded() {
        install.removeListener(listener);

        // If installing a theme that is disabled and can be enabled
        // then enable it
        if (
          install.addon.type == "theme" &&
          !!install.addon.userDisabled &&
          !install.addon.appDisabled
        ) {
          install.addon.enable();
        }

        let subject = {
          wrappedJSObject: { target: browser, addon: install.addon },
        };
        Services.obs.notifyObservers(subject, "webextension-install-notify");
      },
    };

    install.addListener(listener);

    // Start downloading if it hasn't already begun
    install.install();
  },

  /**
   * Starts installation of a SitePermsAddonInstall notifying the registered
   * web install listener of a blocked or started install.
   *
   * @param  aBrowser
   *         The optional browser element that started the install
   * @param  aInstallingPrincipal
   *         The nsIPrincipal that initiated the install
   * @param  aPermission
   *         The permission to install
   * @returns {Promise} A promise that will resolve when the user installs the addon.
   *         The promise will reject if the user blocked the install, or if the addon
   *         can't be installed (e.g. the principal isn't supported).
   * @throws {Components.Exception} Will throw an error if the AddonManager is not initialized
   *         or if `aInstallingPrincipal` is not a nsIPrincipal.
   */
  async installSitePermsAddonFromWebpage(
    aBrowser,
    aInstallingPrincipal,
    aPermission
  ) {
    const synthAddonInstall =
      await AddonManagerInternal.getSitePermsAddonInstallForWebpage(
        aBrowser,
        aInstallingPrincipal,
        aPermission
      );
    const promiseInstall = new Promise((resolve, reject) => {
      const installListener = {
        onInstallFailed() {
          synthAddonInstall.removeListener(installListener);
          reject(new Error("Install Failed"));
        },

        onInstallCancelled() {
          synthAddonInstall.removeListener(installListener);
          reject(new Error("Install Cancelled"));
        },

        onInstallEnded() {
          synthAddonInstall.removeListener(installListener);
          resolve();
        },
      };
      synthAddonInstall.addListener(installListener);
    });

    let startInstall = () => {
      AddonManagerInternal.setupPromptHandler(
        aBrowser,
        aInstallingPrincipal.URI,
        synthAddonInstall,
        true,
        "SitePermissionAddonPrompt"
      );

      AddonManagerInternal.startInstall(
        aBrowser,
        aInstallingPrincipal.URI,
        synthAddonInstall
      );
    };

    startInstall();

    return promiseInstall;
  },

  /**
   * Starts installation of an AddonInstall notifying the registered
   * web install listener of a blocked or started install.
   *
   * @param  aMimetype
   *         The mimetype of the add-on being installed
   * @param  aBrowser
   *         The optional browser element that started the install
   * @param  aInstallingPrincipal
   *         The nsIPrincipal that initiated the install
   * @param  aInstall
   *         The AddonInstall to be installed
   * @param  [aDetails]
   *         Additional optional details
   * @param  [aDetails.hasCrossOriginAncestor]
   *         Boolean value set to true if any of cross-origin ancestors of the triggering frame
   *         (if set to true the installation will be denied).
   */
  installAddonFromWebpage(
    aMimetype,
    aBrowser,
    aInstallingPrincipal,
    aInstall,
    aDetails
  ) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (!aMimetype || typeof aMimetype != "string") {
      throw Components.Exception(
        "aMimetype must be a non-empty string",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (aBrowser && !Element.isInstance(aBrowser)) {
      throw Components.Exception(
        "aSource must be an Element, or null",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (
      !aInstallingPrincipal ||
      !(aInstallingPrincipal instanceof Ci.nsIPrincipal)
    ) {
      throw Components.Exception(
        "aInstallingPrincipal must be a nsIPrincipal",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    // When a chrome in-content UI has loaded a <browser> inside to host a
    // website we want to do our security checks on the inner-browser but
    // notify front-end that install events came from the top browser (the
    // main tab's browser).
    // aBrowser is null in GeckoView.
    let topBrowser = aBrowser?.browsingContext.top.embedderElement;
    try {
      // Use fullscreenElement to check for DOM fullscreen, while still allowing
      // macOS fullscreen, which still has a browser chrome.
      if (topBrowser && topBrowser.ownerDocument.fullscreenElement) {
        // Addon installation and the resulting notifications should be
        // blocked in DOM fullscreen for security and usability reasons.
        // Installation prompts in fullscreen can trick the user into
        // installing unwanted addons.
        // In fullscreen the notification box does not have a clear
        // visual association with its parent anymore.
        aInstall.cancel();

        this.installNotifyObservers(
          "addon-install-fullscreen-blocked",
          topBrowser,
          aInstallingPrincipal.URI,
          aInstall
        );
        return;
      } else if (!this.isInstallEnabled(aMimetype)) {
        aInstall.cancel();

        this.installNotifyObservers(
          "addon-install-disabled",
          topBrowser,
          aInstallingPrincipal.URI,
          aInstall
        );
        return;
      } else if (
        !this.isInstallAllowedByPolicy(
          aInstallingPrincipal,
          aInstall,
          false /* explicit */
        )
      ) {
        aInstall.cancel();

        this.installNotifyObservers(
          "addon-install-policy-blocked",
          topBrowser,
          aInstallingPrincipal.URI,
          aInstall
        );
        return;
      } else if (
        // Block the install request if the triggering frame does have any cross-origin
        // ancestor.
        aDetails?.hasCrossOriginAncestor ||
        // Block the install if triggered by a null principal.
        aInstallingPrincipal.isNullPrincipal ||
        (aBrowser &&
          (!aBrowser.contentPrincipal ||
            // When we attempt to handle an XPI load immediately after a
            // process switch, the DocShell it's being loaded into will have
            // a null principal, since it won't have been initialized yet.
            // Allowing installs in this case is relatively safe, since
            // there isn't much to gain by spoofing an install request from
            // a null principal in any case. This exception can be removed
            // once content handlers are triggered by DocumentChannel in the
            // parent process.
            !(
              aBrowser.contentPrincipal.isNullPrincipal ||
              aInstallingPrincipal.subsumes(aBrowser.contentPrincipal)
            )))
      ) {
        aInstall.cancel();

        this.installNotifyObservers(
          "addon-install-origin-blocked",
          topBrowser,
          aInstallingPrincipal.URI,
          aInstall
        );
        return;
      }

      if (aBrowser) {
        // The install may start now depending on the web install listener,
        // listen for the browser navigating to a new origin and cancel the
        // install in that case.
        new BrowserListener(aBrowser, aInstallingPrincipal, aInstall);
      }

      let startInstall = source => {
        AddonManagerInternal.setupPromptHandler(
          aBrowser,
          aInstallingPrincipal.URI,
          aInstall,
          true,
          source
        );

        AddonManagerInternal.startInstall(
          aBrowser,
          aInstallingPrincipal.URI,
          aInstall
        );
      };

      let installAllowed = this.isInstallAllowed(
        aMimetype,
        aInstallingPrincipal
      );
      let installPerm = Services.perms.testPermissionFromPrincipal(
        aInstallingPrincipal,
        "install"
      );

      if (installAllowed) {
        startInstall("AMO");
      } else if (installPerm === Ci.nsIPermissionManager.DENY_ACTION) {
        // Block without prompt
        aInstall.cancel();
        this.installNotifyObservers(
          "addon-install-blocked-silent",
          topBrowser,
          aInstallingPrincipal.URI,
          aInstall
        );
      } else if (!lazy.WEBEXT_POSTDOWNLOAD_THIRD_PARTY) {
        // Block with prompt
        this.installNotifyObservers(
          "addon-install-blocked",
          topBrowser,
          aInstallingPrincipal.URI,
          aInstall,
          () => startInstall("other"),
          () => aInstall.cancel()
        );
      } else {
        // We download the addon and validate whether a 3rd party
        // install prompt should be shown using e.g. recommended
        // state and install_origins.
        logger.info(`Addon download before validation.`);
        startInstall("other");
      }
    } catch (e) {
      // In the event that the weblistener throws during instantiation or when
      // calling onWebInstallBlocked or onWebInstallRequested the
      // install should get cancelled.
      logger.warn("Failure calling web installer", e);
      aInstall.cancel();
    }
  },

  /**
   * Starts installation of an AddonInstall created from add-ons manager
   * front-end code (e.g., drag-and-drop of xpis or "Install Add-on from File"
   *
   * @param  browser
   *         The browser element where the installation was initiated
   * @param  uri
   *         The URI of the page where the installation was initiated
   * @param  install
   *         The AddonInstall to be installed
   */
  installAddonFromAOM(browser, uri, install) {
    if (!this.isInstallAllowedByPolicy(null, install)) {
      install.cancel();

      this.installNotifyObservers(
        "addon-install-policy-blocked",
        browser,
        install.sourceURI,
        install
      );
      return;
    }
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    AddonManagerInternal.setupPromptHandler(
      browser,
      uri,
      install,
      true,
      "local"
    );
    AddonManagerInternal.startInstall(browser, uri, install);
  },

  /**
   * Adds a new InstallListener if the listener is not already registered.
   *
   * @param  aListener
   *         The InstallListener to add
   */
  addInstallListener(aListener) {
    if (!aListener || typeof aListener != "object") {
      throw Components.Exception(
        "aListener must be a InstallListener object",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    this.installListeners.add(aListener);
  },

  /**
   * Removes an InstallListener if the listener is registered.
   *
   * @param  aListener
   *         The InstallListener to remove
   */
  removeInstallListener(aListener) {
    if (!aListener || typeof aListener != "object") {
      throw Components.Exception(
        "aListener must be a InstallListener object",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    this.installListeners.delete(aListener);
  },
  /**
   * Adds new or overrides existing UpgradeListener.
   *
   * @param  aInstanceID
   *         The instance ID of an addon to register a listener for.
   * @param  aCallback
   *         The callback to invoke when updates are available for this addon.
   * @throws if there is no addon matching the instanceID
   */
  addUpgradeListener(aInstanceID, aCallback) {
    if (!aInstanceID || typeof aInstanceID != "symbol") {
      throw Components.Exception(
        "aInstanceID must be a symbol",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (!aCallback || typeof aCallback != "function") {
      throw Components.Exception(
        "aCallback must be a function",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    let addonId = this.syncGetAddonIDByInstanceID(aInstanceID);
    if (!addonId) {
      throw Error(`No addon matching instanceID: ${String(aInstanceID)}`);
    }
    logger.debug(`Registering upgrade listener for ${addonId}`);
    this.upgradeListeners.set(addonId, aCallback);
  },

  /**
   * Removes an UpgradeListener if the listener is registered.
   *
   * @param  aInstanceID
   *         The instance ID of the addon to remove
   */
  removeUpgradeListener(aInstanceID) {
    if (!aInstanceID || typeof aInstanceID != "symbol") {
      throw Components.Exception(
        "aInstanceID must be a symbol",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    let addonId = this.syncGetAddonIDByInstanceID(aInstanceID);
    if (!addonId) {
      throw Error(`No addon for instanceID: ${aInstanceID}`);
    }
    if (this.upgradeListeners.has(addonId)) {
      this.upgradeListeners.delete(addonId);
    } else {
      throw Error(`No upgrade listener registered for addon ID: ${addonId}`);
    }
  },

  addExternalExtensionLoader(loader) {
    this.externalExtensionLoaders.set(loader.name, loader);
  },

  /**
   * Installs a temporary add-on from a local file or directory.
   *
   * @param  aFile
   *         An nsIFile for the file or directory of the add-on to be
   *         temporarily installed.
   * @returns a Promise that rejects if the add-on is not a valid restartless
   *          add-on or if the same ID is already temporarily installed.
   */
  installTemporaryAddon(aFile) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (!(aFile instanceof Ci.nsIFile)) {
      throw Components.Exception(
        "aFile must be a nsIFile",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    return AddonManagerInternal._getProviderByName(
      "XPIProvider"
    ).installTemporaryAddon(aFile);
  },

  /**
   * Installs an add-on from a built-in location
   *  (ie a resource: url referencing assets shipped with the application)
   *
   * @param  aBase
   *         A string containing the base URL.  Must be a resource: URL.
   * @returns a Promise that resolves when the addon is installed.
   */
  installBuiltinAddon(aBase) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    return AddonManagerInternal._getProviderByName(
      "XPIProvider"
    ).installBuiltinAddon(aBase);
  },

  /**
   * Like `installBuiltinAddon`, but only installs the addon at `aBase`
   * if an existing built-in addon with the ID `aID` and version doesn't
   * already exist.
   *
   * @param {string} aID
   *        The ID of the add-on being registered.
   * @param {string} aVersion
   *        The version of the add-on being registered.
   * @param {string} aBase
   *        A string containing the base URL.  Must be a resource: URL.
   * @returns a Promise that resolves when the addon is installed.
   */
  maybeInstallBuiltinAddon(aID, aVersion, aBase) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    return AddonManagerInternal._getProviderByName(
      "XPIProvider"
    ).maybeInstallBuiltinAddon(aID, aVersion, aBase);
  },

  syncGetAddonIDByInstanceID(aInstanceID) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (!aInstanceID || typeof aInstanceID != "symbol") {
      throw Components.Exception(
        "aInstanceID must be a Symbol()",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    return AddonManagerInternal._getProviderByName(
      "XPIProvider"
    ).getAddonIDByInstanceID(aInstanceID);
  },

  /**
   * Gets an icon from the icon set provided by the add-on
   * that is closest to the specified size.
   *
   * The optional window parameter will be used to determine
   * the screen resolution and select a more appropriate icon.
   * Calling this method with 48px on retina screens will try to
   * match an icon of size 96px.
   *
   * @param  aAddon
   *         An addon object, meaning:
   *         An object with either an icons property that is a key-value list
   *         of icon size and icon URL, or an object having an iconURL property.
   * @param  aSize
   *         Ideal icon size in pixels
   * @param  aWindow
   *         Optional window object for determining the correct scale.
   * @return {String} The absolute URL of the icon or null if the addon doesn't have icons
   */
  getPreferredIconURL(aAddon, aSize, aWindow = undefined) {
    if (aWindow && aWindow.devicePixelRatio) {
      aSize *= aWindow.devicePixelRatio;
    }

    let icons = aAddon.icons;

    // certain addon-types only have iconURLs
    if (!icons) {
      icons = {};
      if (aAddon.iconURL) {
        icons[32] = aAddon.iconURL;
        icons[48] = aAddon.iconURL;
      }
    }

    // quick return if the exact size was found
    if (icons[aSize]) {
      return icons[aSize];
    }

    let bestSize = null;

    for (let size of Object.keys(icons)) {
      if (!INTEGER.test(size)) {
        throw Components.Exception(
          "Invalid icon size, must be an integer",
          Cr.NS_ERROR_ILLEGAL_VALUE
        );
      }

      size = parseInt(size, 10);

      if (!bestSize) {
        bestSize = size;
        continue;
      }

      if (size > aSize && bestSize > aSize) {
        // If both best size and current size are larger than the wanted size then choose
        // the one closest to the wanted size
        bestSize = Math.min(bestSize, size);
      } else {
        // Otherwise choose the largest of the two so we'll prefer sizes as close to below aSize
        // or above aSize
        bestSize = Math.max(bestSize, size);
      }
    }

    return icons[bestSize] || null;
  },

  /**
   * Asynchronously gets an add-on with a specific ID.
   *
   * @type {function}
   * @param  {string} aID
   *         The ID of the add-on to retrieve
   * @returns {Promise} resolves with the found Addon or null if no such add-on exists. Never rejects.
   * @throws if the aID argument is not specified
   */
  getAddonByID(aID) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (!aID || typeof aID != "string") {
      throw Components.Exception(
        "aID must be a non-empty string",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    let promises = Array.from(this.providers, p =>
      promiseCallProvider(p, "getAddonByID", aID)
    );
    return Promise.all(promises).then(aAddons => {
      return aAddons.find(a => !!a) || null;
    });
  },

  /**
   * Asynchronously get an add-on with a specific Sync GUID.
   *
   * @param  aGUID
   *         String GUID of add-on to retrieve
   * @throws if the aGUID argument is not specified
   */
  getAddonBySyncGUID(aGUID) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (!aGUID || typeof aGUID != "string") {
      throw Components.Exception(
        "aGUID must be a non-empty string",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    return (async () => {
      for (let provider of this.providers) {
        let addon = await promiseCallProvider(
          provider,
          "getAddonBySyncGUID",
          aGUID
        );

        if (addon) {
          return addon;
        }
      }

      return null;
    })();
  },

  /**
   * Asynchronously gets an array of add-ons.
   *
   * @param  aIDs
   *         The array of IDs to retrieve
   * @return {Promise}
   * @resolves The array of found add-ons.
   * @rejects  Never
   * @throws if the aIDs argument is not specified
   */
  getAddonsByIDs(aIDs) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (!Array.isArray(aIDs)) {
      throw Components.Exception(
        "aIDs must be an array",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    let promises = aIDs.map(a => AddonManagerInternal.getAddonByID(a));
    return Promise.all(promises);
  },

  /**
   * Asynchronously gets add-ons of specific types.
   *
   * @param  aTypes
   *         An optional array of types to retrieve. Each type is a string name
   */
  getAddonsByTypes(aTypes) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (aTypes && !Array.isArray(aTypes)) {
      throw Components.Exception(
        "aTypes must be an array or null",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    return (async () => {
      let addons = [];

      for (let provider of this.providers) {
        let providerAddons = await promiseCallProvider(
          provider,
          "getAddonsByTypes",
          aTypes
        );

        if (providerAddons) {
          addons.push(...providerAddons);
        }
      }

      return addons;
    })();
  },

  /**
   * Gets active add-ons of specific types.
   *
   * This is similar to getAddonsByTypes() but it may return a limited
   * amount of information about only active addons.  Consequently, it
   * can be implemented by providers using only immediately available
   * data as opposed to getAddonsByTypes which may require I/O).
   *
   * @param  aTypes
   *         An optional array of types to retrieve. Each type is a string name
   *
   * @resolve {addons: Array, fullData: bool}
   *          fullData is true if addons contains all the data we have on those
   *          addons. It is false if addons only contains partial data.
   */
  async getActiveAddons(aTypes) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (aTypes && !Array.isArray(aTypes)) {
      throw Components.Exception(
        "aTypes must be an array or null",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    let addons = [],
      fullData = true;

    for (let provider of this.providers) {
      let providerAddons, providerFullData;
      if ("getActiveAddons" in provider) {
        ({ addons: providerAddons, fullData: providerFullData } =
          await callProvider(provider, "getActiveAddons", null, aTypes));
      } else {
        providerAddons = await promiseCallProvider(
          provider,
          "getAddonsByTypes",
          aTypes
        );
        providerAddons = providerAddons.filter(a => a.isActive);
        providerFullData = true;
      }

      if (providerAddons) {
        addons.push(...providerAddons);
        fullData = fullData && providerFullData;
      }
    }

    return { addons, fullData };
  },

  /**
   * Asynchronously gets all installed add-ons.
   */
  getAllAddons() {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    return this.getAddonsByTypes(null);
  },

  /**
   * Adds a new AddonManagerListener if the listener is not already registered.
   *
   * @param {AddonManagerListener} aListener
   *         The listener to add
   */
  addManagerListener(aListener) {
    if (!aListener || typeof aListener != "object") {
      throw Components.Exception(
        "aListener must be an AddonManagerListener object",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    this.managerListeners.add(aListener);
  },

  /**
   * Removes an AddonManagerListener if the listener is registered.
   *
   * @param {AddonManagerListener} aListener
   *         The listener to remove
   */
  removeManagerListener(aListener) {
    if (!aListener || typeof aListener != "object") {
      throw Components.Exception(
        "aListener must be an AddonManagerListener object",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    this.managerListeners.delete(aListener);
  },

  /**
   * Adds a new AddonListener if the listener is not already registered.
   *
   * @param {AddonManagerListener} aListener
   *        The AddonListener to add.
   */
  addAddonListener(aListener) {
    if (!aListener || typeof aListener != "object") {
      throw Components.Exception(
        "aListener must be an AddonListener object",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    this.addonListeners.add(aListener);
  },

  /**
   * Removes an AddonListener if the listener is registered.
   *
   * @param {object}  aListener
   *         The AddonListener to remove
   */
  removeAddonListener(aListener) {
    if (!aListener || typeof aListener != "object") {
      throw Components.Exception(
        "aListener must be an AddonListener object",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    this.addonListeners.delete(aListener);
  },

  /**
   * @param {string} addonType
   * @returns {boolean}
   *          Whether there is a provider that provides the given addon type.
   */
  hasAddonType(addonType) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    for (let addonTypes of this.typesByProvider.values()) {
      if (addonTypes.has(addonType)) {
        return true;
      }
    }
    return false;
  },

  get autoUpdateDefault() {
    return gAutoUpdateDefault;
  },

  set autoUpdateDefault(aValue) {
    aValue = !!aValue;
    if (aValue != gAutoUpdateDefault) {
      Services.prefs.setBoolPref(PREF_EM_AUTOUPDATE_DEFAULT, aValue);
    }
  },

  get checkCompatibility() {
    return gCheckCompatibility;
  },

  set checkCompatibility(aValue) {
    aValue = !!aValue;
    if (aValue != gCheckCompatibility) {
      if (!aValue) {
        Services.prefs.setBoolPref(PREF_EM_CHECK_COMPATIBILITY, false);
      } else {
        Services.prefs.clearUserPref(PREF_EM_CHECK_COMPATIBILITY);
      }
    }
  },

  get strictCompatibility() {
    return gStrictCompatibility;
  },

  set strictCompatibility(aValue) {
    aValue = !!aValue;
    if (aValue != gStrictCompatibility) {
      Services.prefs.setBoolPref(PREF_EM_STRICT_COMPATIBILITY, aValue);
    }
  },

  get checkUpdateSecurityDefault() {
    return gCheckUpdateSecurityDefault;
  },

  get checkUpdateSecurity() {
    return gCheckUpdateSecurity;
  },

  set checkUpdateSecurity(aValue) {
    aValue = !!aValue;
    if (aValue != gCheckUpdateSecurity) {
      if (aValue != gCheckUpdateSecurityDefault) {
        Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, aValue);
      } else {
        Services.prefs.clearUserPref(PREF_EM_CHECK_UPDATE_SECURITY);
      }
    }
  },

  get updateEnabled() {
    return gUpdateEnabled;
  },

  set updateEnabled(aValue) {
    aValue = !!aValue;
    if (aValue != gUpdateEnabled) {
      Services.prefs.setBoolPref(PREF_EM_UPDATE_ENABLED, aValue);
    }
  },

  /**
   * Verify whether we need to show the 3rd party install prompt.
   *
   * Bypass the third party install prompt if this is an install:
   *   - is an install from a recognized source
   *   - is a an addon that can bypass the panel, such as a recommended addon
   *
   * @param {browser}      browser browser user is installing from
   * @param {nsIURI}       url     URI for the principal of the installing source
   * @param {AddonInstallWrapper} install
   * @param {Object}       info    information such as addon wrapper
   * @param {AddonWrapper} info.addon
   * @param {string}       source  simplified string describing source of install and is
   *                               generated based on the installing principal and checking
   *                               against site permissions and enterprise policy.
   *                               It may be one of "AMO", "local" or "other".
   * @returns {Promise}            Rejected when the installation should not proceed.
   */
  _verifyThirdPartyInstall(browser, url, install, info, source) {
    // If we are not post-download processing, this panel was already shown.
    // Otherwise, if this is from AMO or local, bypass the prompt.
    if (
      !lazy.WEBEXT_POSTDOWNLOAD_THIRD_PARTY ||
      ["AMO", "local"].includes(source)
    ) {
      return Promise.resolve();
    }

    // verify both the installing source and the xpi url are allowed.
    if (
      !info.addon.validInstallOrigins({
        installFrom: url,
        source: install.sourceURI,
      })
    ) {
      install.error = AddonManager.ERROR_INVALID_DOMAIN;
      return Promise.reject();
    }

    // Some addons such as recommended addons do not result in this prompt.
    if (info.addon.canBypassThirdParyInstallPrompt) {
      return Promise.resolve();
    }

    return new Promise((resolve, reject) => {
      this.installNotifyObservers(
        "addon-install-blocked",
        browser,
        url,
        install,
        resolve,
        reject
      );
    });
  },

  setupPromptHandler(browser, url, install, requireConfirm, source) {
    install.promptHandler = info =>
      new Promise((resolve, reject) => {
        this._verifyThirdPartyInstall(browser, url, install, info, source)
          .then(() => {
            // All installs end up in this callback when the add-on is available
            // for installation.  There are numerous different things that can
            // happen from here though.  For webextensions, if the application
            // implements webextension permission prompts, those always take
            // precedence.
            // If this add-on is not a webextension or if the application does not
            // implement permission prompts, no confirmation is displayed for
            // installs created from about:addons (in which case requireConfirm
            // is false).
            // In the remaining cases, a confirmation prompt is displayed but the
            // application may override it either by implementing the
            // "@mozilla.org/addons/web-install-prompt;1" contract or by setting
            // the customConfirmationUI preference and responding to the
            // "addon-install-confirmation" notification.  If the application
            // does not implement its own prompt, use the built-in xul dialog.
            if (info.addon.installPermissions) {
              let subject = {
                wrappedJSObject: {
                  target: browser,
                  info: Object.assign({ resolve, reject, source }, info),
                },
              };
              subject.wrappedJSObject.info.permissions =
                info.addon.installPermissions;
              Services.obs.notifyObservers(
                subject,
                "webextension-permission-prompt"
              );
            } else if (info.addon.sitePermissions) {
              // Handle prompting for DOM permissions in SitePermission addons.
              let { sitePermissions, siteOrigin } = info.addon;
              let subject = {
                wrappedJSObject: {
                  target: browser,
                  info: Object.assign(
                    { resolve, reject, source, sitePermissions, siteOrigin },
                    info
                  ),
                },
              };
              Services.obs.notifyObservers(
                subject,
                "webextension-permission-prompt"
              );
            } else if (requireConfirm) {
              // The methods below all want to call the install() or cancel()
              // method on the provided AddonInstall object to either accept
              // or reject the confirmation.  Fit that into our promise-based
              // control flow by wrapping the install object.  However,
              // xpInstallConfirm.xul matches the install object it is passed
              // with the argument passed to an InstallListener, so give it
              // access to the underlying object through the .wrapped property.
              let proxy = new Proxy(install, {
                get(target, property) {
                  if (property == "install") {
                    return resolve;
                  } else if (property == "cancel") {
                    return reject;
                  } else if (property == "wrapped") {
                    return target;
                  }
                  let result = target[property];
                  return typeof result == "function"
                    ? result.bind(target)
                    : result;
                },
              });

              // Check for a custom installation prompt that may be provided by the
              // applicaton
              if ("@mozilla.org/addons/web-install-prompt;1" in Cc) {
                try {
                  let prompt = Cc[
                    "@mozilla.org/addons/web-install-prompt;1"
                  ].getService(Ci.amIWebInstallPrompt);
                  prompt.confirm(browser, url, [proxy]);
                  return;
                } catch (e) {}
              }

              this.installNotifyObservers(
                "addon-install-confirmation",
                browser,
                url,
                proxy
              );
            } else {
              resolve();
            }
          })
          .catch(e => {
            // Error is undefined if the promise was rejected.
            if (e) {
              Cu.reportError(`Install prompt handler error: ${e}`);
            }
            reject();
          });
      });
  },

  webAPI: {
    // installs maps integer ids to AddonInstall instances.
    installs: new Map(),
    nextInstall: 0,

    sendEvent: null,
    setEventHandler(fn) {
      this.sendEvent = fn;
    },

    async getAddonByID(target, id) {
      return webAPIForAddon(await AddonManager.getAddonByID(id));
    },

    // helper to copy (and convert) the properties we care about
    copyProps(install, obj) {
      obj.state = AddonManager.stateToString(install.state);
      obj.error = AddonManager.errorToString(install.error);
      obj.progress = install.progress;
      obj.maxProgress = install.maxProgress;
    },

    forgetInstall(id) {
      let info = this.installs.get(id);
      if (!info) {
        throw new Error(`forgetInstall cannot find ${id}`);
      }
      info.install.removeListener(info.listener);
      this.installs.delete(id);
    },

    createInstall(target, options) {
      // Throw an appropriate error if the given URL is not valid
      // as an installation source.  Return silently if it is okay.
      function checkInstallUri(uri) {
        if (Services.policies && !Services.policies.allowedInstallSource(uri)) {
          // eslint-disable-next-line no-throw-literal
          return {
            success: false,
            code: "addon-install-policy-blocked",
            message: `Install from ${uri.spec} not permitted by policy`,
          };
        }

        if (WEBAPI_INSTALL_HOSTS.includes(uri.host)) {
          return { success: true };
        }
        if (
          Services.prefs.getBoolPref(PREF_WEBAPI_TESTING, false) &&
          WEBAPI_TEST_INSTALL_HOSTS.includes(uri.host)
        ) {
          return { success: true };
        }

        // eslint-disable-next-line no-throw-literal
        return {
          success: false,
          code: "addon-install-webapi-blocked",
          message: `Install from ${uri.host} not permitted`,
        };
      }

      const makeListener = (id, mm) => {
        const events = [
          "onDownloadStarted",
          "onDownloadProgress",
          "onDownloadEnded",
          "onDownloadCancelled",
          "onDownloadFailed",
          "onInstallStarted",
          "onInstallEnded",
          "onInstallCancelled",
          "onInstallFailed",
        ];

        let listener = {};
        let installPromise = new Promise((resolve, reject) => {
          events.forEach(event => {
            listener[event] = (install, addon) => {
              let data = { event, id };
              AddonManager.webAPI.copyProps(install, data);
              this.sendEvent(mm, data);
              if (event == "onInstallEnded") {
                resolve(addon);
              } else if (
                event == "onDownloadFailed" ||
                event == "onInstallFailed"
              ) {
                reject({ message: "install failed" });
              } else if (
                event == "onDownloadCancelled" ||
                event == "onInstallCancelled"
              ) {
                reject({ message: "install cancelled" });
              } else if (event == "onDownloadEnded") {
                if (install.addon.appDisabled) {
                  // App disabled items are not compatible and so fail to install
                  install.cancel();
                  AddonManagerInternal.installNotifyObservers(
                    "addon-install-failed",
                    target,
                    Services.io.newURI(options.url),
                    install
                  );
                }
              }
            };
          });
        });

        // We create the promise here since this is where we're setting
        // up the InstallListener, but if the install is never started,
        // no handlers will be attached so make sure we terminate errors.
        installPromise.catch(() => {});

        return { listener, installPromise };
      };

      let uri;
      try {
        uri = Services.io.newURI(options.url);
        const { success, code, message } = checkInstallUri(uri);
        if (!success) {
          let info = {
            wrappedJSObject: {
              browser: target,
              originatingURI: uri,
              installs: [],
            },
          };
          Cu.reportError(`${code}: ${message}`);
          Services.obs.notifyObservers(info, code);
          return Promise.reject({ code, message });
        }
      } catch (err) {
        // Reject Components.Exception errors (e.g. NS_ERROR_MALFORMED_URI) as is.
        if (err instanceof Components.Exception) {
          return Promise.reject({ message: err.message });
        }
        return Promise.reject({
          message: "Install Failed on unexpected error",
        });
      }

      return AddonManagerInternal.getInstallForURL(options.url, {
        browser: target,
        triggeringPrincipal: options.triggeringPrincipal,
        hash: options.hash,
        telemetryInfo: {
          source: AddonManager.getInstallSourceFromHost(options.sourceHost),
          sourceURL: options.sourceURL,
          method: "amWebAPI",
        },
      }).then(install => {
        let requireConfirm = true;
        if (
          target.contentDocument &&
          target.contentDocument.nodePrincipal.isSystemPrincipal
        ) {
          requireConfirm = false;
        }
        AddonManagerInternal.setupPromptHandler(
          target,
          null,
          install,
          requireConfirm,
          "AMO"
        );

        let id = this.nextInstall++;
        let { listener, installPromise } = makeListener(
          id,
          target.messageManager
        );
        install.addListener(listener);

        this.installs.set(id, {
          install,
          target,
          listener,
          installPromise,
          messageManager: target.messageManager,
        });

        let result = { id };
        this.copyProps(install, result);
        return result;
      });
    },

    async sendAbuseReport(target, addonId, data, options) {
      return lazy.AbuseReporter.sendAbuseReport(addonId, data, options);
    },

    async addonUninstall(target, id) {
      let addon = await AddonManager.getAddonByID(id);
      if (!addon) {
        return false;
      }

      if (!(addon.permissions & AddonManager.PERM_CAN_UNINSTALL)) {
        return Promise.reject({ message: "Addon cannot be uninstalled" });
      }

      try {
        addon.uninstall();
        return true;
      } catch (err) {
        Cu.reportError(err);
        return false;
      }
    },

    async addonSetEnabled(target, id, value) {
      let addon = await AddonManager.getAddonByID(id);
      if (!addon) {
        throw new Error(`No such addon ${id}`);
      }

      if (value) {
        await addon.enable();
      } else {
        await addon.disable();
      }
    },

    async addonInstallDoInstall(target, id) {
      let state = this.installs.get(id);
      if (!state) {
        throw new Error(`invalid id ${id}`);
      }

      let addon = await state.install.install();

      if (addon.type == "theme" && !addon.appDisabled) {
        await addon.enable();
      }

      await new Promise(resolve => {
        let subject = {
          wrappedJSObject: { target, addon, callback: resolve },
        };
        Services.obs.notifyObservers(subject, "webextension-install-notify");
      });
    },

    addonInstallCancel(target, id) {
      let state = this.installs.get(id);
      if (!state) {
        return Promise.reject(`invalid id ${id}`);
      }
      return Promise.resolve(state.install.cancel());
    },

    clearInstalls(ids) {
      for (let id of ids) {
        this.forgetInstall(id);
      }
    },

    clearInstallsFrom(mm) {
      for (let [id, info] of this.installs) {
        if (info.messageManager == mm) {
          this.forgetInstall(id);
        }
      }
    },
  },
};

/**
 * Should not be used outside of core Mozilla code. This is a private API for
 * the startup and platform integration code to use. Refer to the methods on
 * AddonManagerInternal for documentation however note that these methods are
 * subject to change at any time.
 */
export var AddonManagerPrivate = {
  startup() {
    AddonManagerInternal.startup();
  },

  addonIsActive(addonId) {
    return AddonManagerInternal._getProviderByName("XPIProvider").addonIsActive(
      addonId
    );
  },

  /**
   * Gets an array of add-ons which were side-loaded prior to the last
   * startup, and are currently disabled.
   *
   * @returns {Promise<Array<Addon>>}
   */
  getNewSideloads() {
    return AddonManagerInternal._getProviderByName(
      "XPIProvider"
    ).getNewSideloads();
  },

  get browserUpdated() {
    return gBrowserUpdated;
  },

  registerProvider(aProvider, aTypes) {
    AddonManagerInternal.registerProvider(aProvider, aTypes);
  },

  unregisterProvider(aProvider) {
    AddonManagerInternal.unregisterProvider(aProvider);
  },

  /**
   * Get a list of addon types that was passed to registerProvider for the
   * provider with the given name.
   *
   * @param {string} aProviderName
   * @returns {Array<string>}
   */
  getAddonTypesByProvider(aProviderName) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    for (let [provider, addonTypes] of AddonManagerInternal.typesByProvider) {
      if (providerName(provider) === aProviderName) {
        // Return an array because methods such as getAddonsByTypes expect
        // aTypes to be an array.
        return Array.from(addonTypes);
      }
    }
    throw Components.Exception(
      `No addonTypes found for provider: ${aProviderName}`,
      Cr.NS_ERROR_INVALID_ARG
    );
  },

  markProviderSafe(aProvider) {
    AddonManagerInternal.markProviderSafe(aProvider);
  },

  backgroundUpdateCheck() {
    return AddonManagerInternal.backgroundUpdateCheck();
  },

  backgroundUpdateTimerHandler() {
    // Don't return the promise here, since the caller doesn't care.
    AddonManagerInternal.backgroundUpdateCheck();
  },

  addStartupChange(aType, aID) {
    AddonManagerInternal.addStartupChange(aType, aID);
  },

  removeStartupChange(aType, aID) {
    AddonManagerInternal.removeStartupChange(aType, aID);
  },

  notifyAddonChanged(aID, aType, aPendingRestart) {
    return AddonManagerInternal.notifyAddonChanged(aID, aType, aPendingRestart);
  },

  updateAddonAppDisabledStates() {
    AddonManagerInternal.updateAddonAppDisabledStates();
  },

  updateAddonRepositoryData() {
    return AddonManagerInternal.updateAddonRepositoryData();
  },

  callInstallListeners(...aArgs) {
    return AddonManagerInternal.callInstallListeners.apply(
      AddonManagerInternal,
      aArgs
    );
  },

  callAddonListeners(...aArgs) {
    AddonManagerInternal.callAddonListeners.apply(AddonManagerInternal, aArgs);
  },

  AddonAuthor,

  AddonScreenshot,

  get BOOTSTRAP_REASONS() {
    // BOOTSTRAP_REASONS is a set of constants, and may be accessed before the
    // provider has fully been started.
    // See https://bugzilla.mozilla.org/show_bug.cgi?id=1760146#c1
    return gXPIProvider.BOOTSTRAP_REASONS;
  },

  setAddonStartupData(addonId, startupData) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    // TODO bug 1761079: Ensure that XPIProvider is available before calling it.
    gXPIProvider.setStartupData(addonId, startupData);
  },

  unregisterDictionaries(aDicts) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    // TODO bug 1761093: Use _getProviderByName instead of gXPIProvider.
    gXPIProvider.unregisterDictionaries(aDicts);
  },

  recordTimestamp(name, value) {
    AddonManagerInternal.recordTimestamp(name, value);
  },

  _simpleMeasures: {},
  recordSimpleMeasure(name, value) {
    this._simpleMeasures[name] = value;
  },

  recordException(aModule, aContext, aException) {
    let report = {
      module: aModule,
      context: aContext,
    };

    if (typeof aException == "number") {
      report.message = Components.Exception("", aException).name;
    } else {
      report.message = aException.toString();
      if (aException.fileName) {
        report.file = aException.fileName;
        report.line = aException.lineNumber;
      }
    }

    this._simpleMeasures.exception = report;
  },

  getSimpleMeasures() {
    return this._simpleMeasures;
  },

  getTelemetryDetails() {
    return AddonManagerInternal.telemetryDetails;
  },

  setTelemetryDetails(aProvider, aDetails) {
    AddonManagerInternal.telemetryDetails[aProvider] = aDetails;
  },

  // Start a timer, record a simple measure of the time interval when
  // timer.done() is called
  simpleTimer(aName) {
    let startTime = Cu.now();
    return {
      done: () =>
        this.recordSimpleMeasure(aName, Math.round(Cu.now() - startTime)),
    };
  },

  async recordTiming(name, task) {
    let timer = this.simpleTimer(name);
    try {
      return await task();
    } finally {
      timer.done();
    }
  },

  /**
   * Helper to call update listeners when no update is available.
   *
   * This can be used as an implementation for Addon.findUpdates() when
   * no update mechanism is available.
   */
  callNoUpdateListeners(addon, listener) {
    if ("onNoCompatibilityUpdateAvailable" in listener) {
      safeCall(listener.onNoCompatibilityUpdateAvailable.bind(listener), addon);
    }
    if ("onNoUpdateAvailable" in listener) {
      safeCall(listener.onNoUpdateAvailable.bind(listener), addon);
    }
    if ("onUpdateFinished" in listener) {
      safeCall(listener.onUpdateFinished.bind(listener), addon);
    }
  },

  get webExtensionsMinPlatformVersion() {
    return gWebExtensionsMinPlatformVersion;
  },

  hasUpgradeListener(aId) {
    return AddonManagerInternal.upgradeListeners.has(aId);
  },

  getUpgradeListener(aId) {
    return AddonManagerInternal.upgradeListeners.get(aId);
  },

  get externalExtensionLoaders() {
    return AddonManagerInternal.externalExtensionLoaders;
  },

  /**
   * Predicate that returns true if we think the given extension ID
   * might have been generated by XPIProvider.
   */
  isTemporaryInstallID(extensionId) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }

    if (!extensionId || typeof extensionId != "string") {
      throw Components.Exception(
        "extensionId must be a string",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    return AddonManagerInternal._getProviderByName(
      "XPIProvider"
    ).isTemporaryInstallID(extensionId);
  },

  isDBLoaded() {
    let provider = AddonManagerInternal._getProviderByName("XPIProvider");
    return provider ? provider.isDBLoaded : false;
  },

  get databaseReady() {
    let provider = AddonManagerInternal._getProviderByName("XPIProvider");
    return provider ? provider.databaseReady : new Promise(() => {});
  },

  /**
   * Async shutdown barrier which blocks the completion of add-on
   * manager shutdown. This should generally only be used by add-on
   * providers (i.e., XPIProvider) to complete their final shutdown
   * tasks.
   */
  get finalShutdown() {
    return gFinalShutdownBarrier.client;
  },

  // Used by tests to call repo shutdown.
  overrideAddonRepository(mockRepo) {
    lazy.AddonRepository = mockRepo;
  },

  // Used by tests to shut down AddonManager.
  overrideAsyncShutdown(mockAsyncShutdown) {
    AsyncShutdown = mockAsyncShutdown;
  },
};

/**
 * This is the public API that UI and developers should be calling. All methods
 * just forward to AddonManagerInternal.
 * @class
 */
export var AddonManager = {
  // Map used to convert the known install source hostnames into the value to set into the
  // telemetry events.
  _installHostSource: new Map([
    ["addons.mozilla.org", "amo"],
    ["discovery.addons.mozilla.org", "disco"],
  ]),

  // Constants for the AddonInstall.state property
  // These will show up as AddonManager.STATE_* (eg, STATE_AVAILABLE)
  _states: new Map([
    // The install is available for download.
    ["STATE_AVAILABLE", 0],
    // The install is being downloaded.
    ["STATE_DOWNLOADING", 1],
    // The install is checking the update for compatibility information.
    ["STATE_CHECKING_UPDATE", 2],
    // The install is downloaded and ready to install.
    ["STATE_DOWNLOADED", 3],
    // The download failed.
    ["STATE_DOWNLOAD_FAILED", 4],
    // The install may not proceed until the user accepts a prompt
    ["STATE_AWAITING_PROMPT", 5],
    // Any prompts are done
    ["STATE_PROMPTS_DONE", 6],
    // The install has been postponed.
    ["STATE_POSTPONED", 7],
    // The install is ready to be applied.
    ["STATE_READY", 8],
    // The add-on is being installed.
    ["STATE_INSTALLING", 9],
    // The add-on has been installed.
    ["STATE_INSTALLED", 10],
    // The install failed.
    ["STATE_INSTALL_FAILED", 11],
    // The install has been cancelled.
    ["STATE_CANCELLED", 12],
  ]),

  // Constants representing different types of errors while downloading an
  // add-on as a preparation for installation.
  // These will show up as AddonManager.ERROR_* (eg, ERROR_NETWORK_FAILURE)
  // The _errors codes are translated to text for a panel in browser-addons.js.
  // The localized messages are located in extensionsUI.ftl.
  // Errors with the "Updates only:" prefix are not translated
  // because the error is dumped to the console instead of a panel.
  _errors: new Map([
    // The download failed due to network problems.
    ["ERROR_NETWORK_FAILURE", -1],
    // The downloaded file did not match the provided hash.
    ["ERROR_INCORRECT_HASH", -2],
    // The downloaded file seems to be corrupted in some way.
    ["ERROR_CORRUPT_FILE", -3],
    // An error occurred trying to write to the filesystem.
    ["ERROR_FILE_ACCESS", -4],
    // The add-on must be signed and isn't.
    ["ERROR_SIGNEDSTATE_REQUIRED", -5],
    // Updates only: The downloaded add-on had a different type than expected.
    ["ERROR_UNEXPECTED_ADDON_TYPE", -6],
    // Updates only: The addon did not have the expected ID.
    ["ERROR_INCORRECT_ID", -7],
    // The addon install_origins does not list the 3rd party domain.
    ["ERROR_INVALID_DOMAIN", -8],
    // Updates only: The downloaded add-on had a different version than expected.
    ["ERROR_UNEXPECTED_ADDON_VERSION", -9],
    // The add-on is blocklisted.
    ["ERROR_BLOCKLISTED", -10],
    // The add-on is incompatible (w.r.t. the compatibility range).
    ["ERROR_INCOMPATIBLE", -11],
    // The add-on type is not supported by the platform.
    ["ERROR_UNSUPPORTED_ADDON_TYPE", -12],
    // The add-on can only be installed via enterprise policy.
    ["ERROR_ADMIN_INSTALL_ONLY", -13],
  ]),
  // The update check timed out
  ERROR_TIMEOUT: -1,
  // There was an error while downloading the update information.
  ERROR_DOWNLOAD_ERROR: -2,
  // The update information was malformed in some way.
  ERROR_PARSE_ERROR: -3,
  // The update information was not in any known format.
  ERROR_UNKNOWN_FORMAT: -4,
  // The update information was not correctly signed or there was an SSL error.
  ERROR_SECURITY_ERROR: -5,
  // The update was cancelled
  ERROR_CANCELLED: -6,
  // These must be kept in sync with AddonUpdateChecker.
  // No error was encountered.
  UPDATE_STATUS_NO_ERROR: 0,
  // The update check timed out
  UPDATE_STATUS_TIMEOUT: -1,
  // There was an error while downloading the update information.
  UPDATE_STATUS_DOWNLOAD_ERROR: -2,
  // The update information was malformed in some way.
  UPDATE_STATUS_PARSE_ERROR: -3,
  // The update information was not in any known format.
  UPDATE_STATUS_UNKNOWN_FORMAT: -4,
  // The update information was not correctly signed or there was an SSL error.
  UPDATE_STATUS_SECURITY_ERROR: -5,
  // The update was cancelled.
  UPDATE_STATUS_CANCELLED: -6,
  // Constants to indicate why an update check is being performed
  // Update check has been requested by the user.
  UPDATE_WHEN_USER_REQUESTED: 1,
  // Update check is necessary to see if the Addon is compatibile with a new
  // version of the application.
  UPDATE_WHEN_NEW_APP_DETECTED: 2,
  // Update check is necessary because a new application has been installed.
  UPDATE_WHEN_NEW_APP_INSTALLED: 3,
  // Update check is a regular background update check.
  UPDATE_WHEN_PERIODIC_UPDATE: 16,
  // Update check is needed to check an Addon that is being installed.
  UPDATE_WHEN_ADDON_INSTALLED: 17,

  // Constants for operations in Addon.pendingOperations
  // Indicates that the Addon has no pending operations.
  PENDING_NONE: 0,
  // Indicates that the Addon will be enabled after the application restarts.
  PENDING_ENABLE: 1,
  // Indicates that the Addon will be disabled after the application restarts.
  PENDING_DISABLE: 2,
  // Indicates that the Addon will be uninstalled after the application restarts.
  PENDING_UNINSTALL: 4,
  // Indicates that the Addon will be installed after the application restarts.
  PENDING_INSTALL: 8,
  PENDING_UPGRADE: 16,

  // Constants for operations in Addon.operationsRequiringRestart
  // Indicates that restart isn't required for any operation.
  OP_NEEDS_RESTART_NONE: 0,
  // Indicates that restart is required for enabling the addon.
  OP_NEEDS_RESTART_ENABLE: 1,
  // Indicates that restart is required for disabling the addon.
  OP_NEEDS_RESTART_DISABLE: 2,
  // Indicates that restart is required for uninstalling the addon.
  OP_NEEDS_RESTART_UNINSTALL: 4,
  // Indicates that restart is required for installing the addon.
  OP_NEEDS_RESTART_INSTALL: 8,

  // Constants for permissions in Addon.permissions.
  // Indicates that the Addon can be uninstalled.
  PERM_CAN_UNINSTALL: 1,
  // Indicates that the Addon can be enabled by the user.
  PERM_CAN_ENABLE: 2,
  // Indicates that the Addon can be disabled by the user.
  PERM_CAN_DISABLE: 4,
  // Indicates that the Addon can be upgraded.
  PERM_CAN_UPGRADE: 8,
  // Indicates that the Addon can be set to be allowed/disallowed
  // in private browsing windows.
  PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS: 32,
  // Indicates that internal APIs can uninstall the add-on, even if the
  // front-end cannot.
  PERM_API_CAN_UNINSTALL: 64,

  // General descriptions of where items are installed.
  // Installed in this profile.
  SCOPE_PROFILE: 1,
  // Installed for all of this user's profiles.
  SCOPE_USER: 2,
  // Installed and owned by the application.
  SCOPE_APPLICATION: 4,
  // Installed for all users of the computer.
  SCOPE_SYSTEM: 8,
  // Installed temporarily
  SCOPE_TEMPORARY: 16,
  // The combination of all scopes.
  SCOPE_ALL: 31,

  // Constants for Addon.applyBackgroundUpdates.
  // Indicates that the Addon should not update automatically.
  AUTOUPDATE_DISABLE: 0,
  // Indicates that the Addon should update automatically only if
  // that's the global default.
  AUTOUPDATE_DEFAULT: 1,
  // Indicates that the Addon should update automatically.
  AUTOUPDATE_ENABLE: 2,

  // Constants for how Addon options should be shown.
  // Options will be displayed in a new tab, if possible
  OPTIONS_TYPE_TAB: 3,
  // Similar to OPTIONS_TYPE_INLINE, but rather than generating inline
  // options from a specially-formatted XUL file, the contents of the
  // file are simply displayed in an inline <browser> element.
  OPTIONS_TYPE_INLINE_BROWSER: 5,

  // Constants for displayed or hidden options notifications
  // Options notification will be displayed
  OPTIONS_NOTIFICATION_DISPLAYED: "addon-options-displayed",
  // Options notification will be hidden
  OPTIONS_NOTIFICATION_HIDDEN: "addon-options-hidden",

  // Constants for getStartupChanges, addStartupChange and removeStartupChange
  // Add-ons that were detected as installed during startup. Doesn't include
  // add-ons that were pending installation the last time the application ran.
  STARTUP_CHANGE_INSTALLED: "installed",
  // Add-ons that were detected as changed during startup. This includes an
  // add-on moving to a different location, changing version or just having
  // been detected as possibly changed.
  STARTUP_CHANGE_CHANGED: "changed",
  // Add-ons that were detected as uninstalled during startup. Doesn't include
  // add-ons that were pending uninstallation the last time the application ran.
  STARTUP_CHANGE_UNINSTALLED: "uninstalled",
  // Add-ons that were detected as disabled during startup, normally because of
  // an application change making an add-on incompatible. Doesn't include
  // add-ons that were pending being disabled the last time the application ran.
  STARTUP_CHANGE_DISABLED: "disabled",
  // Add-ons that were detected as enabled during startup, normally because of
  // an application change making an add-on compatible. Doesn't include
  // add-ons that were pending being enabled the last time the application ran.
  STARTUP_CHANGE_ENABLED: "enabled",

  // Constants for Addon.signedState. Any states that should cause an add-on
  // to be unusable in builds that require signing should have negative values.
  // Add-on signing is not required, e.g. because the pref is disabled.
  SIGNEDSTATE_NOT_REQUIRED: undefined,
  // Add-on is signed but signature verification has failed.
  SIGNEDSTATE_BROKEN: -2,
  // Add-on may be signed but by an certificate that doesn't chain to our
  // our trusted certificate.
  SIGNEDSTATE_UNKNOWN: -1,
  // Add-on is unsigned.
  SIGNEDSTATE_MISSING: 0,
  // Add-on is preliminarily reviewed.
  SIGNEDSTATE_PRELIMINARY: 1,
  // Add-on is fully reviewed.
  SIGNEDSTATE_SIGNED: 2,
  // Add-on is system add-on.
  SIGNEDSTATE_SYSTEM: 3,
  // Add-on is signed with a "Mozilla Extensions" certificate
  SIGNEDSTATE_PRIVILEGED: 4,

  get __AddonManagerInternal__() {
    return AppConstants.DEBUG ? AddonManagerInternal : undefined;
  },

  /** Boolean indicating whether AddonManager startup has completed. */
  get isReady() {
    return gStartupComplete && !gShutdownInProgress;
  },

  /**
   * A promise that is resolved when the AddonManager startup has completed.
   * This may be rejected if startup of the AddonManager is not successful, or
   * if shutdown is started before the AddonManager has finished starting.
   */
  get readyPromise() {
    return gStartedPromise.promise;
  },

  /** @constructor */
  init() {
    this._stateToString = new Map();
    for (let [name, value] of this._states) {
      this[name] = value;
      this._stateToString.set(value, name);
    }
    this._errorToString = new Map();
    for (let [name, value] of this._errors) {
      this[name] = value;
      this._errorToString.set(value, name);
    }
  },

  stateToString(state) {
    return this._stateToString.get(state);
  },

  errorToString(err) {
    return err ? this._errorToString.get(err) : null;
  },

  getInstallSourceFromHost(host) {
    if (this._installHostSource.has(host)) {
      return this._installHostSource.get(host);
    }

    if (WEBAPI_TEST_INSTALL_HOSTS.includes(host)) {
      return "test-host";
    }

    return "unknown";
  },

  getInstallForURL(aUrl, aOptions) {
    return AddonManagerInternal.getInstallForURL(aUrl, aOptions);
  },

  getInstallForFile(
    aFile,
    aMimetype,
    aTelemetryInfo,
    aUseSystemLocation = false
  ) {
    return AddonManagerInternal.getInstallForFile(
      aFile,
      aMimetype,
      aTelemetryInfo,
      aUseSystemLocation
    );
  },

  uninstallSystemProfileAddon(aID) {
    return AddonManagerInternal.uninstallSystemProfileAddon(aID);
  },

  stageLangpacksForAppUpdate(appVersion, platformVersion) {
    return AddonManagerInternal._getProviderByName(
      "XPIProvider"
    ).stageLangpacksForAppUpdate(appVersion, platformVersion);
  },

  /**
   * Gets an array of add-on IDs that changed during the most recent startup.
   *
   * @param  aType
   *         The type of startup change to get
   * @return An array of add-on IDs
   */
  getStartupChanges(aType) {
    if (!(aType in AddonManagerInternal.startupChanges)) {
      return [];
    }
    return AddonManagerInternal.startupChanges[aType].slice(0);
  },

  getAddonByID(aID) {
    return AddonManagerInternal.getAddonByID(aID);
  },

  getAddonBySyncGUID(aGUID) {
    return AddonManagerInternal.getAddonBySyncGUID(aGUID);
  },

  getAddonsByIDs(aIDs) {
    return AddonManagerInternal.getAddonsByIDs(aIDs);
  },

  getAddonsByTypes(aTypes) {
    return AddonManagerInternal.getAddonsByTypes(aTypes);
  },

  getActiveAddons(aTypes) {
    return AddonManagerInternal.getActiveAddons(aTypes);
  },

  getAllAddons() {
    return AddonManagerInternal.getAllAddons();
  },

  getInstallsByTypes(aTypes) {
    return AddonManagerInternal.getInstallsByTypes(aTypes);
  },

  getAllInstalls() {
    return AddonManagerInternal.getAllInstalls();
  },

  isInstallEnabled(aType) {
    return AddonManagerInternal.isInstallEnabled(aType);
  },

  isInstallAllowed(aType, aInstallingPrincipal) {
    return AddonManagerInternal.isInstallAllowed(aType, aInstallingPrincipal);
  },

  installSitePermsAddonFromWebpage(
    aBrowser,
    aInstallingPrincipal,
    aPermission
  ) {
    return AddonManagerInternal.installSitePermsAddonFromWebpage(
      aBrowser,
      aInstallingPrincipal,
      aPermission
    );
  },

  installAddonFromWebpage(
    aType,
    aBrowser,
    aInstallingPrincipal,
    aInstall,
    details
  ) {
    AddonManagerInternal.installAddonFromWebpage(
      aType,
      aBrowser,
      aInstallingPrincipal,
      aInstall,
      details
    );
  },

  installAddonFromAOM(aBrowser, aUri, aInstall) {
    AddonManagerInternal.installAddonFromAOM(aBrowser, aUri, aInstall);
  },

  installTemporaryAddon(aDirectory) {
    return AddonManagerInternal.installTemporaryAddon(aDirectory);
  },

  installBuiltinAddon(aBase) {
    return AddonManagerInternal.installBuiltinAddon(aBase);
  },

  maybeInstallBuiltinAddon(aID, aVersion, aBase) {
    return AddonManagerInternal.maybeInstallBuiltinAddon(aID, aVersion, aBase);
  },

  addManagerListener(aListener) {
    AddonManagerInternal.addManagerListener(aListener);
  },

  removeManagerListener(aListener) {
    AddonManagerInternal.removeManagerListener(aListener);
  },

  addInstallListener(aListener) {
    AddonManagerInternal.addInstallListener(aListener);
  },

  removeInstallListener(aListener) {
    AddonManagerInternal.removeInstallListener(aListener);
  },

  getUpgradeListener(aId) {
    return AddonManagerInternal.upgradeListeners.get(aId);
  },

  addUpgradeListener(aInstanceID, aCallback) {
    AddonManagerInternal.addUpgradeListener(aInstanceID, aCallback);
  },

  removeUpgradeListener(aInstanceID) {
    return AddonManagerInternal.removeUpgradeListener(aInstanceID);
  },

  addExternalExtensionLoader(loader) {
    return AddonManagerInternal.addExternalExtensionLoader(loader);
  },

  addAddonListener(aListener) {
    AddonManagerInternal.addAddonListener(aListener);
  },

  removeAddonListener(aListener) {
    AddonManagerInternal.removeAddonListener(aListener);
  },

  hasAddonType(addonType) {
    return AddonManagerInternal.hasAddonType(addonType);
  },

  hasProvider(name) {
    if (!gStarted) {
      throw Components.Exception(
        "AddonManager is not initialized",
        Cr.NS_ERROR_NOT_INITIALIZED
      );
    }
    return !!AddonManagerInternal._getProviderByName(name);
  },

  /**
   * Determines whether an Addon should auto-update or not.
   *
   * @param  aAddon
   *         The Addon representing the add-on
   * @return true if the addon should auto-update, false otherwise.
   */
  shouldAutoUpdate(aAddon) {
    if (!aAddon || typeof aAddon != "object") {
      throw Components.Exception(
        "aAddon must be specified",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    // Special case colorway built-in themes being migrated to an AMO installed theme
    // when an update was found and:
    //
    // - `extensions.update.enable` is set to true (and so add-on updates are still
    //    being checked automatically on the background)
    // - `extensions.update.autoUpdateDefault` is set to false (likely because the
    //    user has disabled auto-applying add-ons updates in about:addons to review
    //    extensions changelogs before accepting an update, e.g. to avoid unexpected
    //    issues that a new version of an extension may be introducing in the update)
    //
    // TODO(Bug 1815898): remove this special case along with other AOM/XPIProvider
    // special cases introduced for colorways themes or colorways migration.
    if (aAddon.isBuiltinColorwayTheme) {
      return true;
    }

    if (!("applyBackgroundUpdates" in aAddon)) {
      return false;
    }
    if (!(aAddon.permissions & AddonManager.PERM_CAN_UPGRADE)) {
      return false;
    }
    if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_ENABLE) {
      return true;
    }
    if (aAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE) {
      return false;
    }
    return this.autoUpdateDefault;
  },

  get checkCompatibility() {
    return AddonManagerInternal.checkCompatibility;
  },

  set checkCompatibility(aValue) {
    AddonManagerInternal.checkCompatibility = aValue;
  },

  get strictCompatibility() {
    return AddonManagerInternal.strictCompatibility;
  },

  set strictCompatibility(aValue) {
    AddonManagerInternal.strictCompatibility = aValue;
  },

  get checkUpdateSecurityDefault() {
    return AddonManagerInternal.checkUpdateSecurityDefault;
  },

  get checkUpdateSecurity() {
    return AddonManagerInternal.checkUpdateSecurity;
  },

  set checkUpdateSecurity(aValue) {
    AddonManagerInternal.checkUpdateSecurity = aValue;
  },

  get updateEnabled() {
    return AddonManagerInternal.updateEnabled;
  },

  set updateEnabled(aValue) {
    AddonManagerInternal.updateEnabled = aValue;
  },

  get autoUpdateDefault() {
    return AddonManagerInternal.autoUpdateDefault;
  },

  set autoUpdateDefault(aValue) {
    AddonManagerInternal.autoUpdateDefault = aValue;
  },

  escapeAddonURI(aAddon, aUri, aAppVersion) {
    return AddonManagerInternal.escapeAddonURI(aAddon, aUri, aAppVersion);
  },

  getPreferredIconURL(aAddon, aSize, aWindow = undefined) {
    return AddonManagerInternal.getPreferredIconURL(aAddon, aSize, aWindow);
  },

  get webAPI() {
    return AddonManagerInternal.webAPI;
  },

  /**
   * Async shutdown barrier which blocks the start of AddonManager
   * shutdown. Callers should add blockers to this barrier if they need
   * to complete add-on manager operations before it shuts down.
   */
  get beforeShutdown() {
    return gBeforeShutdownBarrier.client;
  },
};

/**
 * Manage AddonManager settings propagated over RemoteSettings synced data.
 *
 * See :doc:`AMRemoteSettings Overview <AMRemoteSettings-overview>`.
 *
 * .. warning::
 *   Before landing any change to ``AMRemoteSettings`` or the format expected for the
 *   remotely controlled settings (on the service or Firefos side), please read the
 *   documentation page linked above and make sure to keep the JSON Schema described
 *   and controlled settings groups included in that documentation page in sync with
 *   the one actually set on the RemoteSettings service side.
 */
AMRemoteSettings = {
  RS_COLLECTION: "addons-manager-settings",

  /**
   * RemoteSettings settings group map.
   *
   * .. note::
   *   Please keep in sync the "Controlled Settings Groups" documentation from
   *   :doc:`AMRemoteSettings Overview <AMRemoteSettings-overview>` in sync with
   *   the settings groups defined here.
   */
  RS_ENTRIES_MAP: {
    installTriggerDeprecation: [
      "extensions.InstallTriggerImpl.enabled",
      "extensions.InstallTrigger.enabled",
    ],
    quarantinedDomains: ["extensions.quarantinedDomains.list"],
  },

  client: null,
  onSync: null,
  promiseStartup: null,

  init() {
    try {
      if (!this.promiseStartup) {
        // Creating a promise to resolved when the browser startup was completed,
        // used to process the existing entries (if any) after the startup is completed
        // and to only to it ones.
        this.promiseStartup = new Promise(resolve => {
          function observer() {
            resolve();
            Services.obs.removeObserver(
              observer,
              "browser-delayed-startup-finished"
            );
          }
          Services.obs.addObserver(
            observer,
            "browser-delayed-startup-finished"
          );
        });
      }

      if (Services.prefs.getBoolPref(PREF_REMOTESETTINGS_DISABLED, false)) {
        return;
      }

      if (!this.client) {
        this.client = lazy.RemoteSettings(this.RS_COLLECTION);
        this.onSync = this.processEntries.bind(this);
        this.client.on("sync", this.onSync);
        // Process existing entries if any, once the browser has been fully initialized.
        this.promiseStartup.then(() => this.processEntries());
      }
    } catch (err) {
      logger.error("Failure to initialize AddonManager RemoteSettings", err);
    }
  },

  shutdown() {
    try {
      if (this.client) {
        this.client.off("sync", this.onSync);
        this.client = null;
        this.onSync = null;
      }
      this.promiseStartup = null;
    } catch (err) {
      logger.error("Failure on shutdown AddonManager RemoteSettings", err);
    }
  },

  /**
   * Process all the settings groups that are included in the collection entry with ``"id"`` set to ``"AddonManagerSettings"``
   * (if any).
   *
   * .. note::
   *   This method may need to be updated if the preference value type is not yet expected by this method
   *   (which means that it would be ignored until handled explicitly).
   */
  async processEntries() {
    const entries = await this.client.get({ syncIfEmpty: false }).catch(err => {
      logger.error("Failure to process AddonManager RemoteSettings", err);
      return [];
    });

    const processEntryPref = (entryId, groupName, prefName, prefValue) => {
      try {
        logger.debug(
          `Process AddonManager RemoteSettings "${entryId}" - "${groupName}": ${prefName}`
        );

        // Support for controlling boolean and string AddonManager settings.
        switch (typeof prefValue) {
          case "boolean":
            Services.prefs.setBoolPref(prefName, prefValue);
            break;
          case "string":
            Services.prefs.setStringPref(prefName, prefValue);
            break;
          default:
            throw new Error(`Unexpected type ${typeof prefValue}`);
        }

        // Notify observers about the pref set from AMRemoteSettings.
        Services.obs.notifyObservers(
          { entryId, groupName, prefName, prefValue },
          "am-remote-settings-setpref"
        );
      } catch (e) {
        logger.error(
          `Failed to process AddonManager RemoteSettings "${entryId}" - "${groupName}": ${prefName}`,
          e
        );
      }
    };

    for (const entry of entries) {
      logger.debug(`Processing AddonManager RemoteSettings "${entry.id}"`);

      for (const [groupName, prefs] of Object.entries(this.RS_ENTRIES_MAP)) {
        const data = entry[groupName];
        if (!data) {
          continue;
        }

        for (const pref of prefs) {
          // Skip the pref if it is not included in the remote settings data.
          if (!(pref in data)) {
            continue;
          }

          processEntryPref(entry.id, groupName, pref, data[pref]);
        }
      }
    }
  },
};

/**
 * Listens to the AddonManager install and addon events and send telemetry events.
 */
AMTelemetry = {
  telemetrySetupDone: false,

  init() {
    // Enable the addonsManager telemetry event category before the AddonManager
    // has completed its startup, otherwise telemetry events recorded during the
    // AddonManager/XPIProvider startup will not be recorded.
    Services.telemetry.setEventRecordingEnabled("addonsManager", true);
  },

  // This method is called by the AddonManager, once it has been started, so that we can
  // init the telemetry event category and start listening for the events related to the
  // addons installation and management.
  onStartup() {
    if (this.telemetrySetupDone) {
      return;
    }

    this.telemetrySetupDone = true;

    Services.obs.addObserver(this, "addon-install-origin-blocked");
    Services.obs.addObserver(this, "addon-install-disabled");
    Services.obs.addObserver(this, "addon-install-blocked");

    AddonManager.addInstallListener(this);
    AddonManager.addAddonListener(this);
  },

  // Observer Service notification callback.

  observe(subject, topic) {
    switch (topic) {
      case "addon-install-blocked": {
        const { installs } = subject.wrappedJSObject;
        this.recordInstallEvent(installs[0], { step: "site_warning" });
        break;
      }
      case "addon-install-origin-blocked": {
        const { installs } = subject.wrappedJSObject;
        this.recordInstallEvent(installs[0], { step: "site_blocked" });
        break;
      }
      case "addon-install-disabled": {
        const { installs } = subject.wrappedJSObject;
        this.recordInstallEvent(installs[0], {
          step: "install_disabled_warning",
        });
        break;
      }
    }
  },

  // AddonManager install listener callbacks.

  onNewInstall(install) {
    this.recordInstallEvent(install, { step: "started" });
  },

  onInstallCancelled(install) {
    this.recordInstallEvent(install, { step: "cancelled" });
  },

  onInstallPostponed(install) {
    this.recordInstallEvent(install, { step: "postponed" });
  },

  onInstallFailed(install) {
    this.recordInstallEvent(install, { step: "failed" });
  },

  onInstallEnded(install) {
    this.recordInstallEvent(install, { step: "completed" });
    // Skip install_stats events for install objects related to.
    // add-on updates.
    if (!install.existingAddon) {
      this.recordInstallStatsEvent(install);
    }
  },

  onDownloadStarted(install) {
    this.recordInstallEvent(install, { step: "download_started" });
  },

  onDownloadCancelled(install) {
    this.recordInstallEvent(install, { step: "cancelled" });
  },

  onDownloadEnded(install) {
    let download_time = Math.round(Cu.now() - install.downloadStartedAt);
    this.recordInstallEvent(install, {
      step: "download_completed",
      download_time,
    });
  },

  onDownloadFailed(install) {
    let download_time = Math.round(Cu.now() - install.downloadStartedAt);
    this.recordInstallEvent(install, {
      step: "download_failed",
      download_time,
    });
  },

  // Addon listeners callbacks.

  onUninstalled(addon) {
    this.recordManageEvent(addon, "uninstall");
  },

  onEnabled(addon) {
    this.recordManageEvent(addon, "enable");
  },

  onDisabled(addon) {
    this.recordManageEvent(addon, "disable");
  },

  // Internal helpers methods.

  /**
   * Get a trimmed version of the given string if it is longer than 80 chars.
   *
   * @param {string} str
   *        The original string content.
   *
   * @returns {string}
   *          The trimmed version of the string when longer than 80 chars, or the given string
   *          unmodified otherwise.
   */
  getTrimmedString(str) {
    if (str.length <= 80) {
      return str;
    }

    const length = str.length;

    // Trim the string to prevent a flood of warnings messages logged internally by recordEvent,
    // the trimmed version is going to be composed by the first 40 chars and the last 37 and 3 dots
    // that joins the two parts, to visually indicate that the string has been trimmed.
    return `${str.slice(0, 40)}...${str.slice(length - 37, length)}`;
  },

  /**
   * Retrieve the addonId for the given AddonInstall instance.
   *
   * @param {AddonInstall} install
   *        The AddonInstall instance to retrieve the addonId from.
   *
   * @returns {string | null}
   *          The addonId for the given AddonInstall instance (if any).
   */
  getAddonIdFromInstall(install) {
    // Returns the id of the extension that is being installed, as soon as the
    // addon is available in the AddonInstall instance (after being downloaded
    // and validated successfully).
    if (install.addon) {
      return install.addon.id;
    }

    // While updating an addon, the existing addon can be
    // used to retrieve the addon id since the first update event.
    if (install.existingAddon) {
      return install.existingAddon.id;
    }

    return null;
  },

  /**
   * Retrieve the telemetry event's object property value for the given
   * AddonInstall instance.
   *
   * @param {AddonInstall} install
   *        The AddonInstall instance to retrieve the event object from.
   *
   * @returns {string}
   *          The object for the given AddonInstall instance.
   */
  getEventObjectFromInstall(install) {
    let addonType;

    if (install.type) {
      // The AddonInstall wrapper already provides a type (if it was known when the
      // install object has been created).
      addonType = install.type;
    } else if (install.addon) {
      // The install flow has reached a step that has an addon instance which we can
      // check to know the extension type (e.g. after download for the DownloadAddonInstall).
      addonType = install.addon.type;
    } else if (install.existingAddon) {
      // The install flow is an update and we can look the existingAddon to check which was
      // the add-on type that is being installed.
      addonType = install.existingAddon.type;
    }

    return this.getEventObjectFromAddonType(addonType);
  },

  /**
   * Retrieve the telemetry event source for the given AddonInstall instance.
   *
   * @param {AddonInstall} install
   *        The AddonInstall instance to retrieve the source from.
   *
   * @returns {Object | null}
   *          The telemetry infor ({source, method}) from the given AddonInstall instance.
   */
  getInstallTelemetryInfo(install) {
    if (install.installTelemetryInfo) {
      return install.installTelemetryInfo;
    } else if (
      install.existingAddon &&
      install.existingAddon.installTelemetryInfo
    ) {
      // Get the install source from the existing addon (e.g. for an extension update).
      return install.existingAddon.installTelemetryInfo;
    }

    return null;
  },

  /**
   * Get the telemetry event's object property for the given addon type
   *
   * @param {string} addonType
   *        The addon type to convert into the related telemetry event object.
   *
   * @returns {string}
   *          The object for the given addon type.
   */
  getEventObjectFromAddonType(addonType) {
    // NOTE: Telemetry events' object maximum length is 20 chars (See https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/collection/events.html#limits)
    // and the value needs to matching the "^[a-zA-Z][a-zA-Z0-9_.]*[a-zA-Z0-9]$" pattern.
    switch (addonType) {
      case undefined:
        return "unknown";
      case "extension":
      case "theme":
      case "locale":
      case "dictionary":
      case "sitepermission":
        return addonType;
      default:
        // Currently this should only include gmp-plugins ("plugin").
        return "other";
    }
  },

  convertToString(value) {
    if (value == null) {
      // Convert null and undefined to empty strings.
      return "";
    }
    switch (typeof value) {
      case "string":
        return value;
      case "boolean":
        return value ? "1" : "0";
    }
    return String(value);
  },

  /**
   * Return the UTM parameters found in `sourceURL` for AMO attribution data.
   *
   * @param {string} sourceURL
   *        The source URL from where the add-on has been installed.
   *
   * @returns {object}
   *          An object containing the attribution data for AMO if any. Keys
   *          are defined in `AMO_ATTRIBUTION_DATA_KEYS`. Values are strings.
   */
  parseAttributionDataForAMO(sourceURL) {
    let searchParams;

    try {
      searchParams = new URL(sourceURL).searchParams;
    } catch {
      return {};
    }

    const utmKeys = [...searchParams.keys()].filter(key =>
      AMO_ATTRIBUTION_DATA_KEYS.includes(key)
    );

    return utmKeys.reduce((params, key) => {
      let value = searchParams.get(key);
      if (typeof value === "string") {
        value = value.slice(0, AMO_ATTRIBUTION_DATA_MAX_LENGTH);
      }

      return { ...params, [key]: value };
    }, {});
  },

  /**
   * Record an "install stats" event when the source is included in
   * `AMO_ATTRIBUTION_ALLOWED_SOURCES`.
   *
   * @param {AddonInstall} install
   *        The AddonInstall instance to record an install_stats event for.
   */
  recordInstallStatsEvent(install) {
    const telemetryInfo = this.getInstallTelemetryInfo(install);

    if (!AMO_ATTRIBUTION_ALLOWED_SOURCES.includes(telemetryInfo?.source)) {
      return;
    }

    const method = "install_stats";
    const object = this.getEventObjectFromInstall(install);
    const addonId = this.getAddonIdFromInstall(install);

    if (!addonId) {
      Cu.reportError(
        "Missing addonId when trying to record an install_stats event"
      );
      return;
    }

    let extra = {
      addon_id: this.getTrimmedString(addonId),
    };

    if (
      telemetryInfo?.source === "amo" &&
      typeof telemetryInfo?.sourceURL === "string"
    ) {
      extra = {
        ...extra,
        ...this.parseAttributionDataForAMO(telemetryInfo.sourceURL),
      };
    }

    if (
      telemetryInfo?.source === "disco" &&
      typeof telemetryInfo?.taarRecommended === "boolean"
    ) {
      extra = {
        ...extra,
        taar_based: this.convertToString(telemetryInfo.taarRecommended),
      };
    }

    this.recordEvent({ method, object, value: install.hashedAddonId, extra });
    Glean.addonsManager.installStats.record(
      this.formatExtraVars({
        addon_id: extra.addon_id,
        addon_type: object,
        taar_based: extra.taar_based,
        utm_campaign: extra.utm_campaign,
        utm_content: extra.utm_content,
        utm_medium: extra.utm_medium,
        utm_source: extra.utm_source,
      })
    );
  },

  /**
   * Convert all the telemetry event's extra_vars into strings, if needed.
   *
   * @param {object} extraVars
   * @returns {object} The formatted extra vars.
   */
  formatExtraVars(extraVars) {
    // All the extra_vars in a telemetry event have to be strings.
    for (var [key, value] of Object.entries(extraVars)) {
      if (value == undefined) {
        delete extraVars[key];
      } else {
        extraVars[key] = this.convertToString(value);
      }
    }

    return extraVars;
  },

  /**
   * Record an install or update event for the given AddonInstall instance.
   *
   * @param {AddonInstall} install
   *        The AddonInstall instance to record an install or update event for.
   * @param {object} extraVars
   *        The additional extra_vars to include in the recorded event.
   * @param {string} extraVars.step
   *        The current step in the install or update flow.
   * @param {string} extraVars.download_time
   *        The number of ms needed to download the extension.
   * @param {string} extraVars.num_strings
   *        The number of permission description string for the extension
   *        permission doorhanger.
   */
  recordInstallEvent(install, extraVars) {
    // Early exit if AMTelemetry's telemetry setup has not been done yet.
    if (!this.telemetrySetupDone) {
      return;
    }

    let extra = {};

    let telemetryInfo = this.getInstallTelemetryInfo(install);
    if (telemetryInfo && typeof telemetryInfo.source === "string") {
      extra.source = telemetryInfo.source;
    }

    if (extra.source === "internal") {
      // Do not record the telemetry event for installation sources
      // that are marked as "internal".
      return;
    }

    // Also include the install source's method when applicable (e.g. install events with
    // source "about:addons" may have "install-from-file" or "url" as their source method).
    if (telemetryInfo && typeof telemetryInfo.method === "string") {
      extra.method = telemetryInfo.method;
    }

    let addonId = this.getAddonIdFromInstall(install);
    let object = this.getEventObjectFromInstall(install);

    let installId = String(install.installId);
    let eventMethod = install.existingAddon ? "update" : "install";

    if (addonId) {
      extra.addon_id = this.getTrimmedString(addonId);
    }

    if (install.error) {
      extra.error = AddonManager.errorToString(install.error);
    }

    if (
      eventMethod === "install" &&
      Services.prefs.getBoolPref("extensions.install_origins.enabled", true)
    ) {
      // This is converted to "1" / "0".
      extra.install_origins = Array.isArray(install.addon?.installOrigins);
    }

    if (eventMethod === "update") {
      // For "update" telemetry events, also include an extra var which determine
      // if the update has been requested by the user.
      extra.updated_from = install.isUserRequestedUpdate ? "user" : "app";
    }

    // All the extra vars in a telemetry event have to be strings.
    extra = this.formatExtraVars({ ...extraVars, ...extra });

    this.recordEvent({ method: eventMethod, object, value: installId, extra });
    Glean.addonsManager[eventMethod]?.record(
      this.formatExtraVars({
        addon_id: extra.addon_id,
        addon_type: object,
        install_id: installId,
        download_time: extra.download_time,
        error: extra.error,
        source: extra.source,
        source_method: extra.method,
        num_strings: extra.num_strings,
        updated_from: extra.updated_from,
        install_origins: extra.install_origins,
        step: extra.step,
      })
    );
  },

  /**
   * Record a manage event for the given addon.
   *
   * @param {AddonWrapper} addon
   *        The AddonWrapper instance.
   * @param {object} extraVars
   *        The additional extra_vars to include in the recorded event.
   * @param {string} extraVars.num_strings
   *        The number of permission description string for the extension
   *        permission doorhanger.
   */
  recordManageEvent(addon, method, extraVars) {
    // Early exit if AMTelemetry's telemetry setup has not been done yet.
    if (!this.telemetrySetupDone) {
      return;
    }

    let extra = {};

    if (addon.installTelemetryInfo) {
      if ("source" in addon.installTelemetryInfo) {
        extra.source = addon.installTelemetryInfo.source;
      }

      // Also include the install source's method when applicable (e.g. install events with
      // source "about:addons" may have "install-from-file" or "url" as their source method).
      if ("method" in addon.installTelemetryInfo) {
        extra.method = addon.installTelemetryInfo.method;
      }
    }

    if (extra.source === "internal") {
      // Do not record the telemetry event for installation sources
      // that are marked as "internal".
      return;
    }

    let object = this.getEventObjectFromAddonType(addon.type);
    let value = this.getTrimmedString(addon.id);

    extra = { ...extraVars, ...extra };

    let hasExtraVars = !!Object.keys(extra).length;
    extra = this.formatExtraVars(extra);

    this.recordEvent({
      method,
      object,
      value,
      extra: hasExtraVars ? extra : null,
    });
    Glean.addonsManager.manage.record(
      this.formatExtraVars({
        method,
        addon_id: value,
        addon_type: object,
        source: extra.source,
        source_method: extra.method,
        num_strings: extra.num_strings,
      })
    );
  },

  /**
   * @params {object} opts
   * @params {nsIURI} opts.displayURI
   */
  recordSuspiciousSiteEvent({ displayURI }) {
    let site = displayURI?.displayHost ?? "(unknown)";
    this.recordEvent({
      method: "reportSuspiciousSite",
      object: "suspiciousSite",
      value: site,
      extra: {},
    });
    Glean.addonsManager.reportSuspiciousSite.record(
      this.formatExtraVars({ suspicious_site: site })
    );
  },

  recordEvent({ method, object, value, extra }) {
    if (typeof value != "string") {
      // The value must be a string or null, make sure it's valid so sending
      // the event doesn't fail.
      value = null;
    }
    try {
      Services.telemetry.recordEvent(
        "addonsManager",
        method,
        object,
        value,
        extra
      );
    } catch (err) {
      // If the telemetry throws just log the error so it doesn't break any
      // functionality.
      Cu.reportError(err);
    }
  },
};

/**
 * AMBrowserExtensionsImport is used by the migration wizard to import/install
 * Firefox add-ons based on a set of non-Firefox browser extensions.
 */
AMBrowserExtensionsImport = {
  TELEMETRY_SOURCE: "browser-import",
  TOPIC_CANCELLED: "webextension-imported-addons-cancelled",
  TOPIC_COMPLETE: "webextension-imported-addons-complete",
  TOPIC_PENDING: "webextension-imported-addons-pending",

  // AddonId => AddonInstall
  _pendingInstallsMap: new Map(),
  _importInProgress: false,
  _canCompleteOrCancelInstalls: false,
  // Prompt handler set on the AddonInstall instances part of the imports
  // (which currently makes sure we are not prompting for permissions when the
  // imported addons are being downloaded, staged and then installed).
  _installPromptHandler: () => {},
  // Optionally override the `AddonRepository`, mainly for testing purposes.
  _addonRepository: null,

  get hasPendingImportedAddons() {
    return !!this._pendingInstallsMap.size;
  },

  get importedAddonIDs() {
    return Array.from(this._pendingInstallsMap.keys());
  },

  get canCompleteOrCancelInstalls() {
    return this._canCompleteOrCancelInstalls && this.hasPendingImportedAddons;
  },

  get addonRepository() {
    return this._addonRepository || lazy.AddonRepository;
  },

  /**
   * Stage an install for each add-on mapped to a browser extension ID in the
   * list of IDs passed to this method.
   *
   * @param {string} browserId A browser identifier.
   * @param {Array<string} extensionIDs A list of non-Firefox extension IDs.
   * @returns {Promise<object>} The return value is an object with data for
   *                            the caller.
   * @throws {Error} When there are pending imported add-ons.
   */
  async stageInstalls(browserId, extensionIDs) {
    // In case we have an import in progress, we throw so that the caller knows
    // that there is already an import in progress, which it may want to either
    // cancel or complete.
    if (this._importInProgress) {
      throw new Error(
        "Cannot stage installs because there are pending imported add-ons"
      );
    }
    this._importInProgress = true;
    this._canCompleteOrCancelInstalls = false;

    let importedAddons = [];
    // We first retrieve a list of `AddonSearchResult`, which are the Firefox
    // add-ons mapped to the list of extension IDs passed to this method. We
    // might not have as many mapped add-ons as extension IDs because not all
    // browser extensions will be mapped to Firefox add-ons.
    try {
      let matchedIDs = [];
      let unmatchedIDs = [];

      ({
        addons: importedAddons,
        matchedIDs,
        unmatchedIDs,
      } = await this.addonRepository.getMappedAddons(browserId, extensionIDs));

      Glean.browserMigration.matchedExtensions.set(matchedIDs);
      Glean.browserMigration.unmatchedExtensions.set(unmatchedIDs);
    } catch (err) {
      Cu.reportError(err);
    }

    const alreadyInstalledIDs = (await AddonManager.getAllAddons()).map(
      addon => addon.id
    );

    const { _pendingInstallsMap } = this;

    const results = await Promise.allSettled(
      // For each add-on to import, we create an `AddonInstall` instance and we
      // start the install process until we reach the "downloaded ended" step.
      // At this point, we call `postpone()`, and we are done when the add-on
      // install has been postponed.
      importedAddons
        // Do not import add-ons already installed.
        .filter(({ id }) => !alreadyInstalledIDs.includes(id))
        .map(async ({ id, sourceURI, name, version, icons }) => {
          let addonInstall;

          try {
            addonInstall = await AddonManager.getInstallForURL(sourceURI.spec, {
              name,
              version,
              icons,
              telemetryInfo: { source: this.TELEMETRY_SOURCE },
              promptHandler: this._installPromptHandler,
            });
          } catch (err) {
            return Promise.reject(err);
          }

          return new Promise((resolve, reject) => {
            const rejectWithMessage = err => () => reject(new Error(err));

            addonInstall.addListener({
              onDownloadEnded(install) {
                install
                  .postpone(null, /* requiresRestart */ false)
                  .then(_pendingInstallsMap.set(id, install));
              },

              onInstallPostponed() {
                resolve();
              },

              onDownloadCancelled: rejectWithMessage("Download cancelled"),
              onDownloadFailed: rejectWithMessage("Download failed"),
              onInstallCancelled: rejectWithMessage("Install cancelled"),
              onInstallFailed: rejectWithMessage("Install failed"),
            });

            addonInstall.install();
          });
        })
    );
    this._reportErrors(results);

    // All the imported add-ons should have been staged for install at this
    // point, unless there was no add-on mapped OR some errors.
    const { importedAddonIDs } = this;

    this._canCompleteOrCancelInstalls = !!importedAddonIDs.length;
    this._importInProgress = !!importedAddonIDs.length;

    if (importedAddonIDs.length) {
      Services.obs.notifyObservers(null, this.TOPIC_PENDING);
    }

    return { importedAddonIDs };
  },

  /**
   * Finalize the installation of the add-ons for which we staged their install.
   *
   * @returns {Promise<void>}
   * @throws {Error} When there is no import in progress.
   */
  async completeInstalls() {
    if (!this._importInProgress) {
      throw new Error("No import in progress");
    }

    const results = await Promise.allSettled(
      Array.from(this._pendingInstallsMap.values()).map(install => {
        return install.continuePostponedInstall();
      })
    );
    this._reportErrors(results);
    this._clearInternalState();

    Services.obs.notifyObservers(null, this.TOPIC_COMPLETE);
  },

  /**
   * Cancel the installation of the add-ons for which we staged their install.
   *
   * @returns {Promise<void>}
   * @throws {Error} When there is no import in progress.
   */
  async cancelInstalls() {
    if (!this._importInProgress) {
      throw new Error("No import in progress");
    }

    const results = await Promise.allSettled(
      Array.from(this._pendingInstallsMap.values()).map(install => {
        return install.cancel();
      })
    );
    this._reportErrors(results);
    this._clearInternalState();

    Services.obs.notifyObservers(null, this.TOPIC_CANCELLED);
  },

  _reportErrors(results) {
    results
      .filter(result => result.status === "rejected")
      .forEach(result => Cu.reportError(result.reason));
  },

  _clearInternalState() {
    this._pendingInstallsMap.clear();
    this._importInProgress = false;
    this._canCompleteOrCancelInstalls = false;
  },
};

AddonManager.init();

// Setup the AMTelemetry once the AddonManager has been started.
AddonManager.addManagerListener(AMTelemetry);
Object.freeze(AddonManagerInternal);
Object.freeze(AddonManagerPrivate);
Object.freeze(AddonManager);
PK
!<��g�44modules/AppConstants.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * AppConstants is a set of immutable constants that are defined at build time.
 * These should not depend on any other JavaScript module.
 */
export var AppConstants = Object.freeze({
  // See this wiki page for more details about channel specific build
  // defines: https://wiki.mozilla.org/Platform/Channel-specific_build_defines
  NIGHTLY_BUILD:
  false,

  RELEASE_OR_BETA:
  true,

  EARLY_BETA_OR_EARLIER:
  false,

  IS_ESR:
  false,

  ACCESSIBILITY:
  true,

  // Official corresponds, roughly, to whether this build is performed
  // on Mozilla's continuous integration infrastructure. You should
  // disable developer-only functionality when this flag is set.
  MOZILLA_OFFICIAL:
  true,

  MOZ_OFFICIAL_BRANDING:
  true,

  MOZ_DEV_EDITION:
  false,

  MOZ_SERVICES_SYNC:
  false,

  MOZ_DATA_REPORTING:
  true,

  MOZ_SANDBOX:
  true,

  MOZ_TELEMETRY_REPORTING:
  true,

  MOZ_UPDATER:
  false,

  MOZ_WEBRTC:
  true,

  MOZ_WIDGET_GTK:
  true,

  MOZ_WMF_CDM:
  false,

  XP_UNIX:
  true,

// NOTE! XP_LINUX has to go after MOZ_WIDGET_ANDROID otherwise Android
// builds will be misidentified as linux.
  platform:
  "linux",

// Most of our frontend code assumes that any desktop Unix platform
// is "linux". Add the distinction for code that needs it.
  unixstyle:
    "linux",

  isPlatformAndVersionAtLeast(platform, version) {
    let platformVersion = Services.sysinfo.getProperty("version");
    return platform == this.platform &&
           Services.vc.compare(platformVersion, version) >= 0;
  },

  isPlatformAndVersionAtMost(platform, version) {
    let platformVersion = Services.sysinfo.getProperty("version");
    return platform == this.platform &&
           Services.vc.compare(platformVersion, version) <= 0;
  },

  MOZ_CRASHREPORTER:
  true,

  MOZ_NORMANDY:
  true,

  MOZ_MAINTENANCE_SERVICE:
  false,

  MOZ_BACKGROUNDTASKS:
  true,

  MOZ_UPDATE_AGENT:
  false,

  MOZ_BITS_DOWNLOAD:
  false,

  DEBUG:
  false,

  ASAN:
  false,

  ASAN_REPORTER:
  false,

  TSAN:
  false,

  MOZ_SYSTEM_NSS:
  false,

  MOZ_PLACES:
  true,

  MOZ_REQUIRE_SIGNING:
  true,

  MOZ_UNSIGNED_APP_SCOPE:
  false,

  MOZ_UNSIGNED_SYSTEM_SCOPE:
  false,

  MOZ_ALLOW_ADDON_SIDELOAD:
  false,

  MOZ_WEBEXT_WEBIDL_ENABLED:
  false,

  MENUBAR_CAN_AUTOHIDE:
  true,

  MOZ_GECKOVIEW_HISTORY:
  false,

  MOZ_GECKO_PROFILER:
  true,

  DLL_PREFIX: "lib",
  DLL_SUFFIX: ".so",

  MOZ_APP_NAME: "firefox",
  MOZ_APP_BASENAME: "Firefox",
  // N.b.: you almost certainly want brandShortName/brand-short-name:
  // MOZ_APP_DISPLAYNAME should only be used for static user-visible
  // fields (e.g., DLL properties, Mac Bundle name, or similar).
  MOZ_APP_DISPLAYNAME_DO_NOT_USE: "Firefox",
  MOZ_APP_VERSION: "131.0",
  MOZ_APP_VERSION_DISPLAY: "131.0",
  MOZ_BUILDID: "20240924195713",
  MOZ_BUILD_APP: "browser",
  MOZ_MACBUNDLE_ID: "org.mozilla.firefox",
  MOZ_MACBUNDLE_NAME: "Firefox.app",
  MOZ_UPDATE_CHANNEL: "release",
  MOZ_WIDGET_TOOLKIT: "gtk",

  DEBUG_JS_MODULES: "",

  MOZ_BING_API_CLIENTID: "no-bing-api-clientid",
  MOZ_BING_API_KEY: "no-bing-api-key",
  MOZ_GOOGLE_LOCATION_SERVICE_API_KEY: "AIzaSyB2h2OuRcUgy5N-5hsZqiPW6sH3n_rptiQ",
  MOZ_GOOGLE_SAFEBROWSING_API_KEY: "AIzaSyC7jsptDS3am4tPx4r3nxis7IMjBc5Dovo",
  MOZ_MOZILLA_API_KEY: "no-mozilla-api-key",

  BROWSER_CHROME_URL: "chrome://browser/content/browser.xhtml",

  OMNIJAR_NAME: "omni.ja",

  // URL to the hg revision this was built from (e.g.
  // "https://hg.mozilla.org/mozilla-central/rev/6256ec9113c1")
  // On unofficial builds, this is an empty string.
  SOURCE_REVISION_URL: "https://hg.mozilla.org/releases/mozilla-release/rev/61268a890b3c86ab4f5cfd7c6e1e3d14cc68f0b6",

  HAVE_USR_LIB64_DIR:
    false,

  HAVE_SHELL_SERVICE:
    true,

  MOZ_CODE_COVERAGE:
    false,

  TELEMETRY_PING_FORMAT_VERSION: 4,

  ENABLE_WEBDRIVER:
    true,

  REMOTE_SETTINGS_SERVER_URL:
    "https://firefox.settings.services.mozilla.com/v1",

  REMOTE_SETTINGS_VERIFY_SIGNATURE:
    true,

  REMOTE_SETTINGS_DEFAULT_BUCKET:
    "main",

  MOZ_GLEAN_ANDROID:
    false,

  MOZ_JXL:
    false,


  MOZ_SYSTEM_POLICIES:
    true,

  MOZ_SELECTABLE_PROFILES:
    false,

  SQLITE_LIBRARY_FILENAME:
  "libmozsqlite3.so",

  // Returns true for CN region build when distibution id set as 'MozillaOnline'
  isChinaRepack() {
    return (
      Services.prefs
      .getDefaultBranch("")
      .getCharPref("distribution.id", "default") === "MozillaOnline"
    );
  },
});
PK
!<�^S��$modules/AppMenuNotifications.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

function AppMenuNotification(id, mainAction, secondaryAction, options = {}) {
  this.id = id;
  this.mainAction = mainAction;
  this.secondaryAction = secondaryAction;
  this.options = options;
  this.dismissed = this.options.dismissed || false;
}

export var AppMenuNotifications = {
  _notifications: [],
  _hasInitialized: false,

  get notifications() {
    return Array.from(this._notifications);
  },

  _lazyInit() {
    if (!this._hasInitialized) {
      Services.obs.addObserver(this, "xpcom-shutdown");
      Services.obs.addObserver(this, "appMenu-notifications-request");
    }
  },

  uninit() {
    Services.obs.removeObserver(this, "xpcom-shutdown");
    Services.obs.removeObserver(this, "appMenu-notifications-request");
  },

  observe(subject, topic) {
    switch (topic) {
      case "xpcom-shutdown":
        this.uninit();
        break;
      case "appMenu-notifications-request":
        if (this._notifications.length) {
          Services.obs.notifyObservers(null, "appMenu-notifications", "init");
        }
        break;
    }
  },

  get activeNotification() {
    if (this._notifications.length) {
      const doorhanger = this._notifications.find(
        n => !n.dismissed && !n.options.badgeOnly
      );
      return doorhanger || this._notifications[0];
    }

    return null;
  },

  showNotification(id, mainAction, secondaryAction, options = {}) {
    let notification = new AppMenuNotification(
      id,
      mainAction,
      secondaryAction,
      options
    );
    let existingIndex = this._notifications.findIndex(n => n.id == id);
    if (existingIndex != -1) {
      this._notifications.splice(existingIndex, 1);
    }

    // We don't want to clobber doorhanger notifications just to show a badge,
    // so don't dismiss any of them and the badge will show once the doorhanger
    // gets resolved.
    if (!options.badgeOnly && !options.dismissed) {
      this._notifications.forEach(n => {
        n.dismissed = true;
      });
    }

    // Since notifications are generally somewhat pressing, the ideal case is that
    // we never have two notifications at once. However, in the event that we do,
    // it's more likely that the older notification has been sitting around for a
    // bit, and so we don't want to hide the new notification behind it. Thus,
    // we want our notifications to behave like a stack instead of a queue.
    this._notifications.unshift(notification);

    this._lazyInit();
    this._updateNotifications();
    return notification;
  },

  showBadgeOnlyNotification(id) {
    return this.showNotification(id, null, null, { badgeOnly: true });
  },

  removeNotification(id) {
    let notifications;
    if (typeof id == "string") {
      notifications = this._notifications.filter(n => n.id == id);
    } else {
      // If it's not a string, assume RegExp
      notifications = this._notifications.filter(n => id.test(n.id));
    }
    // _updateNotifications can be expensive if it forces attachment of XBL
    // bindings that haven't been used yet, so return early if we haven't found
    // any notification to remove, as callers may expect this removeNotification
    // method to be a no-op for non-existent notifications.
    if (!notifications.length) {
      return;
    }

    notifications.forEach(n => {
      this._removeNotification(n);
    });
    this._updateNotifications();
  },

  dismissNotification(id) {
    let notifications;
    if (typeof id == "string") {
      notifications = this._notifications.filter(n => n.id == id);
    } else {
      // If it's not a string, assume RegExp
      notifications = this._notifications.filter(n => id.test(n.id));
    }

    notifications.forEach(n => {
      n.dismissed = true;
      if (n.options.onDismissed) {
        n.options.onDismissed();
      }
    });
    this._updateNotifications();
  },

  callMainAction(win, notification, fromDoorhanger) {
    let action = notification.mainAction;
    this._callAction(win, notification, action, fromDoorhanger);
  },

  callSecondaryAction(win, notification) {
    let action = notification.secondaryAction;
    this._callAction(win, notification, action, true);
  },

  _callAction(win, notification, action, fromDoorhanger) {
    let dismiss = true;
    if (action) {
      try {
        action.callback(win, fromDoorhanger);
      } catch (error) {
        console.error(error);
      }

      dismiss = action.dismiss;
    }

    if (dismiss) {
      notification.dismissed = true;
    } else {
      this._removeNotification(notification);
    }

    this._updateNotifications();
  },

  _removeNotification(notification) {
    // This notification may already be removed, in which case let's just ignore.
    let notifications = this._notifications;
    if (!notifications) {
      return;
    }

    var index = notifications.indexOf(notification);
    if (index == -1) {
      return;
    }

    // Remove the notification
    notifications.splice(index, 1);
  },

  _updateNotifications() {
    Services.obs.notifyObservers(null, "appMenu-notifications", "update");
  },
};
PK
!<!b�o��modules/AsyncPrefs.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const kInChildProcess =
  Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;

const kAllowedPrefs = new Set([
  // NB: please leave the testing prefs at the top, and sort the rest alphabetically if you add
  // anything.
  "testing.allowed-prefs.some-bool-pref",
  "testing.allowed-prefs.some-char-pref",
  "testing.allowed-prefs.some-int-pref",

  "browser.contentblocking.report.hide_vpn_banner",
  "browser.contentblocking.report.show_mobile_app",

  "browser.shopping.experience2023.optedIn",
  "browser.shopping.experience2023.active",
  "browser.shopping.experience2023.ads.userEnabled",
  "browser.shopping.experience2023.autoOpen.enabled",
  "browser.shopping.experience2023.autoOpen.userEnabled",
  "browser.shopping.experience2023.showKeepSidebarClosedMessage",
  "browser.shopping.experience2023.sidebarClosedCount",

  "narrate.rate",
  "narrate.voice",

  "reader.font_size",
  "reader.font_type",
  "reader.font_weight",
  "reader.color_scheme",
  "reader.content_width",
  "reader.line_height",
  "reader.text_alignment",
  "reader.character_spacing",
  "reader.word_spacing",
  "reader.custom_colors.foreground",
  "reader.custom_colors.background",
  "reader.custom_colors.unvisited-links",
  "reader.custom_colors.visited-links",
  "reader.custom_colors.selection-highlight",

  "security.tls.version.enable-deprecated",
  "security.xfocsp.errorReporting.automatic",

  "network.trr.display_fallback_warning",
]);

const kPrefTypeMap = new Map([
  ["boolean", Services.prefs.PREF_BOOL],
  ["number", Services.prefs.PREF_INT],
  ["string", Services.prefs.PREF_STRING],
]);

function maybeReturnErrorForReset(pref) {
  if (!kAllowedPrefs.has(pref)) {
    return `Resetting pref ${pref} from content is not allowed.`;
  }
  return false;
}

function maybeReturnErrorForSet(pref, value) {
  if (!kAllowedPrefs.has(pref)) {
    return `Setting pref ${pref} from content is not allowed.`;
  }

  let valueType = typeof value;
  if (!kPrefTypeMap.has(valueType)) {
    return `Can't set pref ${pref} to value of type ${valueType}.`;
  }
  let prefType = Services.prefs.getPrefType(pref);
  if (
    prefType != Services.prefs.PREF_INVALID &&
    prefType != kPrefTypeMap.get(valueType)
  ) {
    return `Can't set pref ${pref} to a value with type ${valueType} that doesn't match the pref's type ${prefType}.`;
  }
  return false;
}

export class AsyncPrefsChild extends JSProcessActorChild {
  set(pref, value) {
    let error = maybeReturnErrorForSet(pref, value);
    if (error) {
      return Promise.reject(error);
    }

    return this.sendQuery("AsyncPrefs:SetPref", {
      pref,
      value,
    });
  }

  reset(pref) {
    let error = maybeReturnErrorForReset(pref);
    if (error) {
      return Promise.reject(error);
    }

    return this.sendQuery("AsyncPrefs:ResetPref", { pref });
  }
}

export var AsyncPrefs = {
  set(pref, value) {
    if (kInChildProcess) {
      return ChromeUtils.domProcessChild
        .getActor("AsyncPrefs")
        .set(pref, value);
    }
    return AsyncPrefsParent.set(pref, value);
  },

  reset(pref) {
    if (kInChildProcess) {
      return ChromeUtils.domProcessChild.getActor("AsyncPrefs").reset(pref);
    }
    return AsyncPrefsParent.reset(pref);
  },
};

const methodForType = {
  number: "setIntPref",
  boolean: "setBoolPref",
  string: "setCharPref",
};

export class AsyncPrefsParent extends JSProcessActorParent {
  static set(pref, value) {
    let error = maybeReturnErrorForSet(pref, value);
    if (error) {
      return Promise.reject(error);
    }
    let methodToUse = methodForType[typeof value];
    try {
      Services.prefs[methodToUse](pref, value);
    } catch (ex) {
      console.error(ex);
      return Promise.reject(ex.message);
    }

    return Promise.resolve(value);
  }

  static reset(pref) {
    let error = maybeReturnErrorForReset(pref);
    if (error) {
      return Promise.reject(error);
    }

    try {
      Services.prefs.clearUserPref(pref);
    } catch (ex) {
      console.error(ex);
      return Promise.reject(ex.message);
    }

    return Promise.resolve();
  }

  receiveMessage(msg) {
    if (msg.name == "AsyncPrefs:SetPref") {
      return AsyncPrefsParent.set(msg.data.pref, msg.data.value);
    }
    return AsyncPrefsParent.reset(msg.data.pref);
  }
}
PK
!<�-�d�d�modules/AsyncShutdown.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Managing safe shutdown of asynchronous services.
 *
 * Firefox shutdown is composed of phases that take place
 * sequentially. Typically, each shutdown phase removes some
 * capabilities from the application. For instance, at the end of
 * phase profileBeforeChange, no service is permitted to write to the
 * profile directory (with the exception of Telemetry). Consequently,
 * if any service has requested I/O to the profile directory before or
 * during phase profileBeforeChange, the system must be informed that
 * these requests need to be completed before the end of phase
 * profileBeforeChange. Failing to inform the system of this
 * requirement can (and has been known to) cause data loss.
 *
 * Example: At some point during shutdown, the Add-On Manager needs to
 * ensure that all add-ons have safely written their data to disk,
 * before writing its own data. Since the data is saved to the
 * profile, this must be completed during phase profileBeforeChange.
 *
 * AsyncShutdown.profileBeforeChange.addBlocker(
 *   "Add-on manager: shutting down",
 *   function condition() {
 *     // Do things.
 *     // Perform I/O that must take place during phase profile-before-change
 *     return promise;
 *   }
 * });
 *
 * In this example, function |condition| will be called at some point
 * during phase profileBeforeChange and phase profileBeforeChange
 * itself is guaranteed to not terminate until |promise| is either
 * resolved or rejected.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gDebug",
  "@mozilla.org/xpcom/debug;1",
  "nsIDebug2"
);

// `true` if this is a content process, `false` otherwise.
// It would be nicer to go through `Services.appinfo`, but some tests need to be
// able to replace that field with a custom implementation before it is first
// called.
const isContent =
  // eslint-disable-next-line mozilla/use-services
  Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).processType ==
  Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;

// Display timeout warnings after 10 seconds
const DELAY_WARNING_MS = 10 * 1000;

// Crash the process if shutdown is really too long
// (allowing for sleep).
const PREF_DELAY_CRASH_MS = "toolkit.asyncshutdown.crash_timeout";
var DELAY_CRASH_MS = Services.prefs.getIntPref(PREF_DELAY_CRASH_MS, 60 * 1000); // One minute
Services.prefs.addObserver(PREF_DELAY_CRASH_MS, function () {
  DELAY_CRASH_MS = Services.prefs.getIntPref(PREF_DELAY_CRASH_MS);
});

/**
 * Any addBlocker calls that failed. We add this into barrier wait
 * crash annotations to help with debugging. When we fail to add
 * shutdown blockers that can break shutdown. We track these globally
 * rather than per barrier because one failure mode is when the
 * barrier has already finished by the time addBlocker is invoked -
 * but the failure to add the blocker may result in later barriers
 * waiting indefinitely, so the debug information is still useful
 * for those later barriers. See bug 1801674 for more context.
 */
let gBrokenAddBlockers = [];

/**
 * A set of Promise that supports waiting.
 *
 * Promise items may be added or removed during the wait. The wait will
 * resolve once all Promise items have been resolved or removed.
 */
function PromiseSet() {
  /**
   * key: the Promise passed pass the client of the `PromiseSet`.
   * value: an indirection on top of `key`, as an object with
   *   the following fields:
   *   - indirection: a Promise resolved if `key` is resolved or
   *     if `resolve` is called
   *   - resolve: a function used to resolve the indirection.
   */
  this._indirections = new Map();
  // Once all the tracked promises have been resolved we are done. Once Wait()
  // resolves, it should not be possible anymore to add further promises.
  // This covers for a possibly rare case, where something may try to add a
  // blocker after wait() is done, that would never be awaited for.
  this._done = false;
}
PromiseSet.prototype = {
  /**
   * Wait until all Promise have been resolved or removed.
   *
   * Note that calling `wait()` causes Promise to be removed from the
   * Set once they are resolved.
   * @param {function} onDoneCb invoked synchronously once all the entries
   * have been handled and no new entries will be accepted.
   * @return {Promise} Resolved once all Promise have been resolved or removed,
   * or rejected after at least one Promise has rejected.
   */
  wait(onDoneCb) {
    // Pick an arbitrary element in the map, if any exists.
    let entry = this._indirections.entries().next();
    if (entry.done) {
      // No indirections left, we are done.
      this._done = true;
      onDoneCb();
      return Promise.resolve();
    }

    let [, indirection] = entry.value;
    let promise = indirection.promise;
    promise = promise.then(() =>
      // At this stage, the entry has been cleaned up.
      this.wait(onDoneCb)
    );
    return promise;
  },

  /**
   * Add a new Promise to the set.
   *
   * Calls to wait (including ongoing calls) will only return once
   * `key` has either resolved or been removed.
   */
  add(key) {
    if (this._done) {
      throw new Error("Wait is complete, cannot add further promises.");
    }
    this._ensurePromise(key);
    let indirection = Promise.withResolvers();
    key
      .then(
        x => {
          // Clean up immediately.
          // This needs to be done before the call to `resolve`, otherwise
          // `wait()` may loop forever.
          this._indirections.delete(key);
          indirection.resolve(x);
        },
        err => {
          this._indirections.delete(key);
          indirection.reject(err);
        }
      )
      .finally(() => {
        this._indirections.delete(key);
        // Normally the promise is resolved or rejected, but if its global
        // goes away, only finally may be invoked. In all the other cases this
        // is a no-op since the promise has been fulfilled already.
        indirection.reject(
          new Error("Promise not fulfilled, did it lost its global?")
        );
      });
    this._indirections.set(key, indirection);
  },

  /**
   * Remove a Promise from the set.
   *
   * Calls to wait (including ongoing calls) will ignore this promise,
   * unless it is added again.
   */
  delete(key) {
    this._ensurePromise(key);
    let value = this._indirections.get(key);
    if (!value) {
      return false;
    }
    this._indirections.delete(key);
    value.resolve();
    return true;
  },

  _ensurePromise(key) {
    if (!key || typeof key != "object") {
      throw new Error("Expected an object");
    }
    if (!("then" in key) || typeof key.then != "function") {
      throw new Error("Expected a Promise");
    }
  },
};

/**
 * Display a warning.
 *
 * As this code is generally used during shutdown, there are chances
 * that the UX will not be available to display warnings on the
 * console. We therefore use dump() rather than Cu.reportError().
 */
function log(msg, prefix = "", error = null) {
  try {
    dump(prefix + msg + "\n");
    if (error) {
      dump(prefix + error + "\n");
      if (typeof error == "object" && "stack" in error) {
        dump(prefix + error.stack + "\n");
      }
    }
  } catch (ex) {
    dump("INTERNAL ERROR in AsyncShutdown: cannot log message.\n");
  }
}
const PREF_DEBUG_LOG = "toolkit.asyncshutdown.log";
var DEBUG_LOG = Services.prefs.getBoolPref(PREF_DEBUG_LOG, false);
Services.prefs.addObserver(PREF_DEBUG_LOG, function () {
  DEBUG_LOG = Services.prefs.getBoolPref(PREF_DEBUG_LOG);
});

function debug(msg, error = null) {
  if (DEBUG_LOG) {
    log(msg, "DEBUG: ", error);
  }
}
function warn(msg, error = null) {
  log(msg, "WARNING: ", error);
}
function fatalerr(msg, error = null) {
  log(msg, "FATAL ERROR: ", error);
}

// Utility function designed to get the current state of execution
// of a blocker.
// We are a little paranoid here to ensure that in case of evaluation
// error we do not block the AsyncShutdown.
function safeGetState(fetchState) {
  if (!fetchState) {
    return "(none)";
  }
  let data, string;
  try {
    // Evaluate fetchState(), normalize the result into something that we can
    // safely stringify or upload.
    let state = fetchState();
    if (!state) {
      return "(none)";
    }
    string = JSON.stringify(state);
    data = JSON.parse(string);
    // Simplify the rest of the code by ensuring that we can simply
    // concatenate the result to a message.
    if (data && typeof data == "object") {
      data.toString = function () {
        return string;
      };
    }
    return data;
  } catch (ex) {
    // Make sure that this causes test failures
    Promise.reject(ex);

    if (string) {
      return string;
    }
    try {
      return "Error getting state: " + ex + " at " + ex.stack;
    } catch (ex2) {
      return "Error getting state but could not display error";
    }
  }
}

/**
 * Countdown for a given duration, skipping beats if the computer is too busy,
 * sleeping or otherwise unavailable.
 *
 * @param {number} delay An approximate delay to wait in milliseconds (rounded
 * up to the closest second).
 *
 * @return Deferred
 */
function looseTimer(delay) {
  let DELAY_BEAT = 1000;
  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  let beats = Math.ceil(delay / DELAY_BEAT);
  let deferred = Promise.withResolvers();
  timer.initWithCallback(
    function () {
      if (beats <= 0) {
        deferred.resolve();
      }
      --beats;
    },
    DELAY_BEAT,
    Ci.nsITimer.TYPE_REPEATING_PRECISE_CAN_SKIP
  );
  // Ensure that the timer is both canceled once we are done with it
  // and not garbage-collected until then.
  deferred.promise.then(
    () => timer.cancel(),
    () => timer.cancel()
  );
  return deferred;
}

/**
 * Given an nsIStackFrame object, find the caller filename, line number,
 * and stack if necessary, and return them as an object.
 *
 * @param {nsIStackFrame} topFrame Top frame of the call stack.
 * @param {string} filename Pre-supplied filename or null if unknown.
 * @param {number} lineNumber Pre-supplied line number or null if unknown.
 * @param {string} stack Pre-supplied stack or null if unknown.
 *
 * @return object
 */
function getOrigin(topFrame, filename = null, lineNumber = null, stack = null) {
  try {
    // Determine the filename and line number of the caller.
    let frame = topFrame;

    for (; frame && frame.filename == topFrame.filename; frame = frame.caller) {
      // Climb up the stack
    }

    if (filename == null) {
      filename = frame ? frame.filename : "?";
    }
    if (lineNumber == null) {
      lineNumber = frame ? frame.lineNumber : 0;
    }
    if (stack == null) {
      // Now build the rest of the stack as a string.
      stack = [];
      while (frame != null) {
        stack.push(frame.filename + ":" + frame.name + ":" + frame.lineNumber);
        frame = frame.caller;
      }
    }

    return {
      filename,
      lineNumber,
      stack,
    };
  } catch (ex) {
    return {
      filename: "<internal error: could not get origin>",
      lineNumber: -1,
      stack: "<internal error: could not get origin>",
    };
  }
}

/**
 * {string} topic -> phase
 */
var gPhases = new Map();

export var AsyncShutdown = {
  /**
   * Access function getPhase. For testing purposes only.
   */
  get _getPhase() {
    let accepted = Services.prefs.getBoolPref(
      "toolkit.asyncshutdown.testing",
      false
    );
    if (accepted) {
      return getPhase;
    }
    return undefined;
  },

  /**
   * This constant is used as the amount of milliseconds to allow shutdown to be
   * blocked until we crash the process forcibly and is read from the
   * 'toolkit.asyncshutdown.crash_timeout' pref.
   */
  get DELAY_CRASH_MS() {
    return DELAY_CRASH_MS;
  },
};

/**
 * Register a new phase.
 *
 * @param {string} topic The notification topic for this Phase.
 * @see {https://developer.mozilla.org/en-US/docs/Observer_Notifications}
 */
function getPhase(topic) {
  let phase = gPhases.get(topic);
  if (phase) {
    return phase;
  }
  let spinner = new Spinner(topic);
  phase = Object.freeze({
    /**
     * Register a blocker for the completion of a phase.
     *
     * @param {string} name The human-readable name of the blocker. Used
     * for debugging/error reporting. Please make sure that the name
     * respects the following model: "Some Service: some action in progress" -
     * for instance "OS.File: flushing all pending I/O";
     * @param {function|promise|*} condition A condition blocking the
     * completion of the phase. Generally, this is a function
     * returning a promise. This function is evaluated during the
     * phase and the phase is guaranteed to not terminate until the
     * resulting promise is either resolved or rejected. If
     * |condition| is not a function but another value |v|, it behaves
     * as if it were a function returning |v|.
     * @param {object*} details Optionally, an object with details
     * that may be useful for error reporting, as a subset of of the following
     * fields:
     * - fetchState (strongly recommended) A function returning
     *    information about the current state of the blocker as an
     *    object. Used for providing more details when logging errors or
     *    crashing.
     * - stack. A string containing stack information. This module can
     *    generally infer stack information if it is not provided.
     * - lineNumber A number containing the line number for the caller.
     *    This module can generally infer this information if it is not
     *    provided.
     * - filename A string containing the filename for the caller. This
     *    module can generally infer  the information if it is not provided.
     *
     * Examples:
     * AsyncShutdown.profileBeforeChange.addBlocker("Module: just a promise",
     *      promise); // profileBeforeChange will not complete until
     *                // promise is resolved or rejected
     *
     * AsyncShutdown.profileBeforeChange.addBlocker("Module: a callback",
     *     function callback() {
     *       // ...
     *       // Execute this code during profileBeforeChange
     *       return promise;
     *       // profileBeforeChange will not complete until promise
     *       // is resolved or rejected
     * });
     *
     * AsyncShutdown.profileBeforeChange.addBlocker("Module: trivial callback",
     *     function callback() {
     *       // ...
     *       // Execute this code during profileBeforeChange
     *       // No specific guarantee about completion of profileBeforeChange
     * });
     */
    addBlocker(name, condition, details = null) {
      spinner.addBlocker(name, condition, details);
    },
    /**
     * Remove the blocker for a condition.
     *
     * If several blockers have been registered for the same
     * condition, remove all these blockers. If no blocker has been
     * registered for this condition, this is a noop.
     *
     * @return {boolean} true if a blocker has been removed, false
     * otherwise. Note that a result of false may mean either that
     * the blocker has never been installed or that the phase has
     * completed and the blocker has already been resolved.
     */
    removeBlocker(condition) {
      return spinner.removeBlocker(condition);
    },

    get name() {
      return spinner.name;
    },

    get isClosed() {
      return spinner.isClosed;
    },

    /**
     * Trigger the phase without having to broadcast a
     * notification. For testing purposes only.
     */
    get _trigger() {
      let accepted = Services.prefs.getBoolPref(
        "toolkit.asyncshutdown.testing",
        false
      );
      if (accepted) {
        return () => spinner.observe();
      }
      return undefined;
    },
  });
  gPhases.set(topic, phase);
  return phase;
}

/**
 * Utility class used to spin the event loop until all blockers for a
 * Phase are satisfied.
 *
 * @param {string} topic The xpcom notification for that phase.
 */
function Spinner(topic) {
  this._barrier = new Barrier(topic);
  this._topic = topic;
  Services.obs.addObserver(this, topic);
}

Spinner.prototype = {
  /**
   * Register a new condition for this phase.
   *
   * See the documentation of `addBlocker` in property `client`
   * of instances of `Barrier`.
   */
  addBlocker(name, condition, details) {
    this._barrier.client.addBlocker(name, condition, details);
  },
  /**
   * Remove the blocker for a condition.
   *
   * See the documentation of `removeBlocker` in rpoperty `client`
   * of instances of `Barrier`
   *
   * @return {boolean} true if a blocker has been removed, false
   * otherwise. Note that a result of false may mean either that
   * the blocker has never been installed or that the phase has
   * completed and the blocker has already been resolved.
   */
  removeBlocker(condition) {
    return this._barrier.client.removeBlocker(condition);
  },

  get name() {
    return this._barrier.client.name;
  },

  get isClosed() {
    return this._barrier.client.isClosed;
  },

  // nsIObserver.observe
  observe() {
    let topic = this._topic;
    debug(`Starting phase ${topic}`);
    Services.obs.removeObserver(this, topic);

    // Setup the promise that will signal our phase's end.
    let isPhaseEnd = false;
    try {
      this._barrier
        .wait({
          warnAfterMS: DELAY_WARNING_MS,
          crashAfterMS: DELAY_CRASH_MS,
        })
        .finally(() => {
          isPhaseEnd = true;
        });
    } catch (ex) {
      debug("Error waiting for notification");
      throw ex;
    }

    // Now, spin the event loop. In case of a hang we will just crash without
    // ever leaving this loop.
    debug("Spinning the event loop");
    Services.tm.spinEventLoopUntil(
      `AsyncShutdown Spinner for ${topic}`,
      () => isPhaseEnd
    );

    debug(`Finished phase ${topic}`);
  },
};

/**
 * A mechanism used to register blockers that prevent some action from
 * happening.
 *
 * An instance of |Barrier| provides a capability |client| that
 * clients can use to register blockers. The barrier is resolved once
 * all registered blockers have been resolved. The owner of the
 * |Barrier| may wait for the resolution of the barrier and obtain
 * information on which blockers have not been resolved yet.
 *
 * @param {string} name The name of the blocker. Used mainly for error-
 *     reporting.
 */
function Barrier(name) {
  if (!name) {
    throw new TypeError("Instances of Barrier need a (non-empty) name");
  }

  /**
   * The set of all Promise for which we need to wait before the barrier
   * is lifted. Note that this set may be changed while we are waiting.
   *
   * Set to `null` once the wait is complete.
   */
  this._waitForMe = new PromiseSet();

  /**
   * A map from conditions, as passed by users during the call to `addBlocker`,
   * to `promise`, as present in `this._waitForMe`.
   *
   * Used to let users perform cleanup through `removeBlocker`.
   * Set to `null` once the wait is complete.
   *
   * Key: condition (any, as passed by user)
   * Value: promise used as a key in `this._waitForMe`. Note that there is
   * no guarantee that the key is still present in `this._waitForMe`.
   */
  this._conditionToPromise = new Map();

  /**
   * A map from Promise, as present in `this._waitForMe` or
   * `this._conditionToPromise`, to information on blockers.
   *
   * Key: Promise (as present in this._waitForMe or this._conditionToPromise).
   * Value:  {
   *  trigger: function,
   *  promise,
   *  name,
   *  fetchState: function,
   *  stack,
   *  filename,
   *  lineNumber
   * };
   */
  this._promiseToBlocker = new Map();

  /**
   * The name of the barrier.
   */
  if (typeof name != "string") {
    throw new TypeError("The name of the barrier must be a string");
  }
  this._name = name;

  /**
   * A cache for the promise returned by wait().
   */
  this._promise = null;

  /**
   * `true` once we have started waiting.
   */
  this._isStarted = false;

  /**
   * `true` once we're done and won't accept any new blockers.
   */
  this._isClosed = false;

  /**
   * The capability of adding blockers. This object may safely be returned
   * or passed to clients.
   */
  this.client = {
    /**
     * The name of the barrier owning this client.
     */
    get name() {
      return name;
    },

    /**
     * Register a blocker for the completion of this barrier.
     *
     * @param {string} name The human-readable name of the blocker. Used
     * for debugging/error reporting. Please make sure that the name
     * respects the following model: "Some Service: some action in progress" -
     * for instance "OS.File: flushing all pending I/O";
     * @param {function|promise|*} condition A condition blocking the
     * completion of the phase. Generally, this is a function
     * returning a promise. This function is evaluated during the
     * phase and the phase is guaranteed to not terminate until the
     * resulting promise is either resolved or rejected. If
     * |condition| is not a function but another value |v|, it behaves
     * as if it were a function returning |v|.
     * @param {object*} details Optionally, an object with details
     * that may be useful for error reporting, as a subset of of the following
     * fields:
     * - fetchState (strongly recommended) A function returning
     *    information about the current state of the blocker as an
     *    object. Used for providing more details when logging errors or
     *    crashing.
     * - stack. A string containing stack information. This module can
     *    generally infer stack information if it is not provided.
     * - lineNumber A number containing the line number for the caller.
     *    This module can generally infer this information if it is not
     *    provided.
     * - filename A string containing the filename for the caller. This
     *    module can generally infer  the information if it is not provided.
     */
    addBlocker: (name, condition, details) => {
      if (typeof name != "string") {
        gBrokenAddBlockers.push("No-name blocker");
        throw new TypeError("Expected a human-readable name as first argument");
      }
      if (details && typeof details == "function") {
        details = {
          fetchState: details,
        };
      } else if (!details) {
        details = {};
      }
      if (typeof details != "object") {
        gBrokenAddBlockers.push(`${name} - invalid details`);
        throw new TypeError(
          "Expected an object as third argument to `addBlocker`, got " + details
        );
      }
      if (!this._waitForMe) {
        gBrokenAddBlockers.push(`${name} - ${this._name} finished`);
        throw new Error(
          `Phase "${this._name}" is finished, it is too late to register completion condition "${name}"`
        );
      }
      debug(`Adding blocker ${name} for phase ${this._name}`);

      try {
        this.client._internalAddBlocker(name, condition, details);
      } catch (ex) {
        gBrokenAddBlockers.push(`${name} - ${ex.message}`);
        throw ex;
      }
    },

    _internalAddBlocker: (
      name,
      condition,
      { fetchState = null, filename = null, lineNumber = null, stack = null }
    ) => {
      if (fetchState != null && typeof fetchState != "function") {
        throw new TypeError("Expected a function for option `fetchState`");
      }

      // Split the condition between a trigger function and a promise.

      // The function to call to notify the blocker that we have started waiting.
      // This function returns a promise resolved/rejected once the
      // condition is complete, and never throws.
      let trigger;

      // A promise resolved once the condition is complete.
      let promise;
      if (typeof condition == "function") {
        promise = new Promise((resolve, reject) => {
          trigger = () => {
            try {
              resolve(condition());
            } catch (ex) {
              reject(ex);
            }
          };
        });
      } else {
        // If `condition` is not a function, `trigger` is not particularly
        // interesting, and `condition` needs to be normalized to a promise.
        trigger = () => {};
        promise = Promise.resolve(condition);
      }

      // Make sure that `promise` never rejects.
      promise = promise
        .catch(error => {
          let msg = `A blocker encountered an error while we were waiting.
          Blocker:  ${name}
          Phase: ${this._name}
          State: ${safeGetState(fetchState)}`;
          warn(msg, error);

          // The error should remain uncaught, to ensure that it
          // still causes tests to fail.
          Promise.reject(error);
        })
        // Added as a last line of defense, in case `warn`, `this._name` or
        // `safeGetState` somehow throws an error.
        .catch(() => {});

      let topFrame = null;
      if (filename == null || lineNumber == null || stack == null) {
        topFrame = Components.stack;
      }

      let blocker = {
        trigger,
        promise,
        name,
        fetchState,
        getOrigin: () => getOrigin(topFrame, filename, lineNumber, stack),
      };

      this._waitForMe.add(promise);
      this._promiseToBlocker.set(promise, blocker);
      this._conditionToPromise.set(condition, promise);

      // As conditions may hold lots of memory, we attempt to cleanup
      // as soon as we are done (which might be in the next tick, if
      // we have been passed a resolved promise).
      promise = promise.then(() => {
        debug(`Completed blocker ${name} for phase ${this._name}`);
        this._removeBlocker(condition);
      });

      if (this._isStarted) {
        // The wait has already started. The blocker should be
        // notified asap. We do it out of band as clients probably
        // expect `addBlocker` to return immediately.
        Promise.resolve().then(trigger);
      }
    },

    /**
     * Remove the blocker for a condition.
     *
     * If several blockers have been registered for the same
     * condition, remove all these blockers. If no blocker has been
     * registered for this condition, this is a noop.
     *
     * @return {boolean} true if at least one blocker has been
     * removed, false otherwise.
     */
    removeBlocker: condition => {
      return this._removeBlocker(condition);
    },
  };

  /**
   * Whether this client still accepts new blockers.
   */
  Object.defineProperty(this.client, "isClosed", {
    get: () => {
      return this._isClosed;
    },
  });
}
Barrier.prototype = Object.freeze({
  /**
   * The current state of the barrier, as a JSON-serializable object
   * designed for error-reporting.
   */
  get state() {
    if (!this._isStarted) {
      return "Not started";
    }
    if (!this._waitForMe) {
      return "Complete";
    }
    let frozen = [];
    for (let blocker of this._promiseToBlocker.values()) {
      let { name, fetchState } = blocker;
      let { stack, filename, lineNumber } = blocker.getOrigin();
      frozen.push({
        name,
        state: safeGetState(fetchState),
        filename,
        lineNumber,
        stack,
      });
    }
    return frozen;
  },

  /**
   * Wait until all currently registered blockers are complete.
   *
   * Once this method has been called, any attempt to register a new blocker
   * for this barrier will cause an error.
   *
   * Successive calls to this method always return the same value.
   *
   * @param {object=}  options Optionally, an object  that may contain
   * the following fields:
   *  {number} warnAfterMS If provided and > 0, print a warning if the barrier
   *   has not been resolved after the given number of milliseconds.
   *  {number} crashAfterMS If provided and > 0, crash the process if the barrier
   *   has not been resolved after the give number of milliseconds (rounded up
   *   to the next second). To avoid crashing simply because the computer is busy
   *   or going to sleep, we actually wait for ceil(crashAfterMS/1000) successive
   *   periods of at least one second. Upon crashing, if a crash reporter is present,
   *   prepare a crash report with the state of this barrier.
   *
   *
   * @return {Promise} A promise satisfied once all blockers are complete.
   */
  wait(options = {}) {
    // This method only implements caching on top of _wait()
    if (this._promise) {
      return this._promise;
    }
    return (this._promise = this._wait(options));
  },
  _wait(options) {
    // Sanity checks
    if (this._isStarted) {
      throw new TypeError("Internal error: already started " + this._name);
    }
    if (
      !this._waitForMe ||
      !this._conditionToPromise ||
      !this._promiseToBlocker
    ) {
      throw new TypeError("Internal error: already finished " + this._name);
    }

    let topic = this._name;

    // Notify blockers
    for (let blocker of this._promiseToBlocker.values()) {
      blocker.trigger(); // We have guarantees that this method will never throw
    }

    this._isStarted = true;

    // Now, wait
    let promise = this._waitForMe.wait(() => {
      this._isClosed = true;
    });

    promise = promise.catch(function onError(error) {
      // I don't think that this can happen.
      // However, let's be overcautious with async/shutdown error reporting.
      let msg =
        "An uncaught error appeared while completing the phase." +
        " Phase: " +
        topic;
      warn(msg, error);
    });

    promise = promise.then(() => {
      // Cleanup memory
      this._waitForMe = null;
      this._promiseToBlocker = null;
      this._conditionToPromise = null;
    });

    // Now handle warnings and crashes
    let warnAfterMS = DELAY_WARNING_MS;
    if (options && "warnAfterMS" in options) {
      if (
        typeof options.warnAfterMS == "number" ||
        options.warnAfterMS == null
      ) {
        // Change the delay or deactivate warnAfterMS
        warnAfterMS = options.warnAfterMS;
      } else {
        throw new TypeError("Wrong option value for warnAfterMS");
      }
    }

    if (warnAfterMS && warnAfterMS > 0) {
      // If the promise takes too long to be resolved/rejected,
      // we need to notify the user.
      let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
      timer.initWithCallback(
        () => {
          let msg =
            "At least one completion condition is taking too long to complete." +
            " Conditions: " +
            JSON.stringify(this.state) +
            " Barrier: " +
            topic;
          warn(msg);
        },
        warnAfterMS,
        Ci.nsITimer.TYPE_ONE_SHOT
      );

      promise = promise.then(function onSuccess() {
        timer.cancel();
        // As a side-effect, this prevents |timer| from
        // being garbage-collected too early.
      });
    }

    let crashAfterMS = DELAY_CRASH_MS;
    if (options && "crashAfterMS" in options) {
      if (
        typeof options.crashAfterMS == "number" ||
        options.crashAfterMS == null
      ) {
        // Change the delay or deactivate crashAfterMS
        crashAfterMS = options.crashAfterMS;
      } else {
        throw new TypeError("Wrong option value for crashAfterMS");
      }
    }

    if (crashAfterMS > 0) {
      let timeToCrash = null;

      // If after |crashAfterMS| milliseconds (adjusted to take into
      // account sleep and otherwise busy computer) we have not finished
      // this shutdown phase, we assume that the shutdown is somehow
      // frozen, presumably deadlocked. At this stage, the only thing we
      // can do to avoid leaving the user's computer in an unstable (and
      // battery-sucking) situation is report the issue and crash.
      timeToCrash = looseTimer(crashAfterMS);
      timeToCrash.promise.then(
        () => {
          // Report the problem as best as we can, then crash.
          let state = this.state;

          // If you change the following message, please make sure
          // that any information on the topic and state appears
          // within the first 200 characters of the message. This
          // helps automatically sort oranges.
          let msg =
            "AsyncShutdown timeout in " +
            topic +
            " Conditions: " +
            JSON.stringify(state) +
            " At least one completion condition failed to complete" +
            " within a reasonable amount of time. Causing a crash to" +
            " ensure that we do not leave the user with an unresponsive" +
            " process draining resources.";
          fatalerr(msg);
          if (gBrokenAddBlockers.length) {
            fatalerr(
              "Broken addBlocker calls: " + JSON.stringify(gBrokenAddBlockers)
            );
          }
          if (Services.appinfo.crashReporterEnabled) {
            Services.appinfo.annotateCrashReport(
              "AsyncShutdownTimeout",
              JSON.stringify(this._gatherCrashReportTimeoutData(topic, state))
            );
          } else {
            warn("No crash reporter available");
          }

          // To help sorting out bugs, we want to make sure that the
          // call to nsIDebug2.abort points to a guilty client, rather
          // than to AsyncShutdown itself. We pick a client that is
          // still blocking and use its filename/lineNumber,
          // which have been determined during the call to `addBlocker`.
          let filename = "?";
          let lineNumber = -1;
          for (let blocker of this._promiseToBlocker.values()) {
            ({ filename, lineNumber } = blocker.getOrigin());
            break;
          }
          lazy.gDebug.abort(filename, lineNumber);
        },
        function onSatisfied() {
          // The promise has been rejected, which means that we have satisfied
          // all completion conditions.
        }
      );

      promise = promise.then(
        function () {
          timeToCrash.reject();
        } /* No error is possible here*/
      );
    }

    return promise;
  },

  _gatherCrashReportTimeoutData(phase, conditions) {
    let data = { phase, conditions };
    if (gBrokenAddBlockers.length) {
      data.brokenAddBlockers = gBrokenAddBlockers;
    }
    return data;
  },

  _removeBlocker(condition) {
    if (
      !this._waitForMe ||
      !this._promiseToBlocker ||
      !this._conditionToPromise
    ) {
      // We have already cleaned up everything.
      return false;
    }

    let promise = this._conditionToPromise.get(condition);
    if (!promise) {
      // The blocker has already been removed
      return false;
    }
    this._conditionToPromise.delete(condition);
    this._promiseToBlocker.delete(promise);
    return this._waitForMe.delete(promise);
  },
});

// List of well-known phases
// Ideally, phases should be registered from the component that decides
// when they start/stop. For compatibility with existing startup/shutdown
// mechanisms, we register a few phases here.

// Parent process
if (!isContent) {
  AsyncShutdown.profileChangeTeardown = getPhase("profile-change-teardown");
  AsyncShutdown.profileBeforeChange = getPhase("profile-before-change");
  AsyncShutdown.sendTelemetry = getPhase("profile-before-change-telemetry");
}

// Notifications that fire in the parent and content process, but should
// only have phases in the parent process.
if (!isContent) {
  AsyncShutdown.quitApplicationGranted = getPhase("quit-application-granted");
}

// Don't add a barrier for content-child-shutdown because this
// makes it easier to cause shutdown hangs.

// All processes
AsyncShutdown.webWorkersShutdown = getPhase("web-workers-shutdown");
AsyncShutdown.xpcomWillShutdown = getPhase("xpcom-will-shutdown");

AsyncShutdown.Barrier = Barrier;

Object.freeze(AsyncShutdown);
PK
!<���گ�(modules/AutoCompleteSimpleSearch.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

/**
 * See nsIAutoCompleteSimpleSearch
 */
export class AutoCompleteSimpleSearch {
  constructor() {
    this.classID = Components.ID("{dc185a77-ba88-4caa-8f16-465253f7599a}");
    this.QueryInterface = ChromeUtils.generateQI([
      "nsIAutoCompleteSimpleSearch",
    ]);

    let initialState = Cc[
      "@mozilla.org/autocomplete/simple-result;1"
    ].createInstance(Ci.nsIAutoCompleteSimpleResult);
    initialState.setDefaultIndex(0);
    initialState.setSearchResult(Ci.nsIAutoCompleteResult.RESULT_NOMATCH);
    this.overrideNextResult(initialState);
  }

  _result = null;

  /**
   * See nsIAutoCompleteSimpleSearch
   */
  overrideNextResult(result) {
    this._result = result;
  }

  /**
   * See nsIAutoCompleteSearch
   */
  startSearch(searchString, searchParam, previousResult, listener) {
    let result = this._result;
    result.setSearchString(searchString);
    listener.onSearchResult(this, result);
  }

  /**
   * See nsIAutoCompleteSearch
   */
  stopSearch() {}
}
PK
!<_:��>>&modules/BTPRemoteExceptionList.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
});

// Name of the RemoteSettings collection containing the rules.
const COLLECTION_NAME = "bounce-tracking-protection-exceptions";

export class BTPRemoteExceptionList {
  classId = Components.ID("{06F13674-FB28-4DFC-BF25-342C83705B2F}");
  QueryInterface = ChromeUtils.generateQI(["nsIBTPRemoteExceptionList"]);

  #rs = null;
  // Stores the this-wrapped on-sync callback so it can be unregistered on
  // shutdown.
  #onSyncCallback = null;

  // Weak reference to nsIBounceTrackingProtection.
  #btp = null;

  constructor() {
    this.#rs = lazy.RemoteSettings(COLLECTION_NAME);
  }

  async init(bounceTrackingProtection) {
    if (!bounceTrackingProtection) {
      throw new Error("Missing required argument bounceTrackingProtection");
    }
    // Get a weak ref to BounceTrackingProtection to avoid a reference cycle.
    this.#btp = Cu.getWeakReference(bounceTrackingProtection);

    await this.importAllExceptions();

    // Register callback for collection changes.
    // Only register if not already registered.
    if (!this.#onSyncCallback) {
      this.#onSyncCallback = this.onSync.bind(this);
      this.#rs.on("sync", this.#onSyncCallback);
    }
  }

  shutdown() {
    // Unregister callback for collection changes.
    if (this.#onSyncCallback) {
      this.#rs.off("sync", this.#onSyncCallback);
      this.#onSyncCallback = null;
    }
  }

  /**
   * Called for remote settings "sync" events.
   */
  onSync({ data: { created = [], updated = [], deleted = [] } }) {
    // Check if feature is still active before attempting to send updates.
    let btp = this.#btp?.get();
    if (!btp) {
      return;
    }

    let siteHostsToRemove = deleted.map(ex => ex.siteHost);
    let siteHostsToAdd = created.map(ex => ex.siteHost);

    updated.forEach(ex => {
      // We only care about site host changes.
      if (ex.old.siteHost == ex.new.siteHost) {
        return;
      }
      siteHostsToRemove.push(ex.old.siteHost);
      siteHostsToAdd.push(ex.new.siteHost);
    });

    if (siteHostsToRemove.length) {
      btp.removeSiteHostExceptions(siteHostsToRemove);
    }
    if (siteHostsToAdd.length) {
      btp.addSiteHostExceptions(siteHostsToAdd);
    }
  }

  async importAllExceptions() {
    try {
      let exceptions = await this.#rs.get();
      if (!exceptions.length) {
        return;
      }
      this.onSync({ data: { created: exceptions } });
    } catch (error) {
      console.error(
        "Error while importing BTP exceptions from RemoteSettings",
        error
      );
    }
  }
}
PK
!<'�K��b�b$modules/BackgroundPageThumbs.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const DEFAULT_CAPTURE_TIMEOUT = 30000; // ms
// For testing, the above timeout is excessive, and makes our tests overlong.
const TESTING_CAPTURE_TIMEOUT = 5000; // ms

const DESTROY_BROWSER_TIMEOUT = 60000; // ms

// Let the page settle for this amount of milliseconds before capturing to allow
// for any in-page changes or redirects.
const SETTLE_WAIT_TIME = 2500;
// For testing, the above timeout is excessive, and makes our tests overlong.
const TESTING_SETTLE_WAIT_TIME = 0;

const TELEMETRY_HISTOGRAM_ID_PREFIX = "FX_THUMBNAILS_BG_";

const ABOUT_NEWTAB_SEGREGATION_PREF =
  "privacy.usercontext.about_newtab_segregation.enabled";

import {
  PageThumbs,
  PageThumbsStorage,
} from "resource://gre/modules/PageThumbs.sys.mjs";

// possible FX_THUMBNAILS_BG_CAPTURE_DONE_REASON_2 telemetry values
const TEL_CAPTURE_DONE_OK = 0;
const TEL_CAPTURE_DONE_TIMEOUT = 1;
// 2 and 3 were used when we had special handling for private-browsing.
const TEL_CAPTURE_DONE_CRASHED = 4;
const TEL_CAPTURE_DONE_BAD_URI = 5;
const TEL_CAPTURE_DONE_LOAD_FAILED = 6;
const TEL_CAPTURE_DONE_IMAGE_ZERO_DIMENSION = 7;

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ContextualIdentityService:
    "resource://gre/modules/ContextualIdentityService.sys.mjs",
});

export const BackgroundPageThumbs = {
  /**
   * Asynchronously captures a thumbnail of the given URL.
   *
   * The page is loaded anonymously, and plug-ins are disabled.
   *
   * @param url      The URL to capture.
   * @param options  An optional object that configures the capture.  Its
   *                 properties are the following, and all are optional:
   * @opt onDone     A function that will be asynchronously called when the
   *                 capture is complete or times out.  It's called as
   *                   onDone(url),
   *                 where `url` is the captured URL.
   * @opt timeout    The capture will time out after this many milliseconds have
   *                 elapsed after the capture has progressed to the head of
   *                 the queue and started.  Defaults to 30000 (30 seconds).
   * @opt isImage    If true, backgroundPageThumbsContent will attempt to render
   *                 the url directly to canvas. Note that images will mostly get
   *                 detected and rendered as such anyway, but this will ensure it.
   * @opt targetWidth The target width when capturing an image.
   * @opt backgroundColor The background colour when capturing an image.
   * @opt dontStore  If set to true, the image blob won't be stored to disk, an
   *                 object will instead be passed as third argument to onDone:
   *                 {
   *                   data: an ArrayBuffer containing the data
   *                   contentType: the data content-type
   *                   originalUrl: the originally requested url
   *                   currentUrl: the final url after redirects
   *                 }
   * @opt contentType can be set to an image contentType for the capture,
   *                  defaults to PageThumbs.contentType.
   */
  capture(url, options = {}) {
    if (!PageThumbs._prefEnabled()) {
      if (options.onDone) {
        schedule(() => options.onDone(url));
      }
      return;
    }
    this._captureQueue = this._captureQueue || [];
    this._capturesByURL = this._capturesByURL || new Map();

    tel("QUEUE_SIZE_ON_CAPTURE", this._captureQueue.length);

    // We want to avoid duplicate captures for the same URL.  If there is an
    // existing one, we just add the callback to that one and we are done.
    let existing = this._capturesByURL.get(url);
    if (existing) {
      if (options.onDone) {
        existing.doneCallbacks.push(options.onDone);
      }
      // The queue is already being processed, so nothing else to do...
      return;
    }
    let cap = new Capture(url, this._onCaptureOrTimeout.bind(this), options);
    this._captureQueue.push(cap);
    this._capturesByURL.set(url, cap);
    this._processCaptureQueue();
  },

  /**
   * Asynchronously captures a thumbnail of the given URL if one does not
   * already exist.  Otherwise does nothing.
   *
   * @param url      The URL to capture.
   * @param options  An optional object that configures the capture.  See
   *                 capture() for description.
   *   unloadingPromise This option is resolved when the calling context is
   *                    unloading, so things can be cleaned up to avoid leak.
   * @return {Promise} A Promise that resolves when this task completes
   */
  async captureIfMissing(url, options = {}) {
    // Short circuit this function if pref is enabled, or else we leak observers.
    // See Bug 1400562
    if (!PageThumbs._prefEnabled()) {
      if (options.onDone) {
        options.onDone(url);
      }
      return url;
    }
    // The fileExistsForURL call is an optimization, potentially but unlikely
    // incorrect, and no big deal when it is.  After the capture is done, we
    // atomically test whether the file exists before writing it.
    let exists = await PageThumbsStorage.fileExistsForURL(url);
    if (exists) {
      if (options.onDone) {
        options.onDone(url);
      }
      return url;
    }
    let thumbPromise = new Promise((resolve, reject) => {
      let observe = (subject, topic, data) => {
        if (data === url) {
          switch (topic) {
            case "page-thumbnail:create":
              resolve();
              break;
            case "page-thumbnail:error":
              reject(new Error("page-thumbnail:error"));
              break;
          }
          cleanup();
        }
      };
      Services.obs.addObserver(observe, "page-thumbnail:create");
      Services.obs.addObserver(observe, "page-thumbnail:error");

      // Make sure to clean up to avoid leaks by removing observers when
      // observed or when our caller is unloading
      function cleanup() {
        if (observe) {
          Services.obs.removeObserver(observe, "page-thumbnail:create");
          Services.obs.removeObserver(observe, "page-thumbnail:error");
          observe = null;
        }
      }
      if (options.unloadingPromise) {
        options.unloadingPromise.then(cleanup);
      }
    });
    try {
      this.capture(url, options);
      await thumbPromise;
    } catch (err) {
      if (options.onDone) {
        options.onDone(url);
      }
      throw err;
    }
    return url;
  },

  /**
   * Tell the service that the thumbnail browser should be recreated at next
   * call of _ensureBrowser().
   */
  renewThumbnailBrowser() {
    this._renewThumbBrowser = true;
  },

  get useFissionBrowser() {
    return Services.appinfo.fissionAutostart;
  },

  /**
   * Ensures that initialization of the thumbnail browser's parent window has
   * begun.
   *
   * @return  True if the parent window is completely initialized and can be
   *          used, and false if initialization has started but not completed.
   */
  _ensureParentWindowReady() {
    if (this._parentWin) {
      // Already fully initialized.
      return true;
    }
    if (this._startedParentWinInit) {
      // Already started initializing.
      return false;
    }

    this._startedParentWinInit = true;

    // Create a windowless browser and load our hosting
    // (privileged) document in it.
    const flags = this.useFissionBrowser
      ? Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW |
        Ci.nsIWebBrowserChrome.CHROME_FISSION_WINDOW
      : 0;
    let wlBrowser = Services.appShell.createWindowlessBrowser(true, flags);
    wlBrowser.QueryInterface(Ci.nsIInterfaceRequestor);
    let webProgress = wlBrowser.getInterface(Ci.nsIWebProgress);
    this._listener = {
      QueryInterface: ChromeUtils.generateQI([
        "nsIWebProgressListener",
        "nsIWebProgressListener2",
        "nsISupportsWeakReference",
      ]),
    };
    this._listener.onStateChange = (wbp, request, stateFlags) => {
      if (!request) {
        return;
      }
      if (
        stateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
        stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK
      ) {
        webProgress.removeProgressListener(this._listener);
        delete this._listener;
        // Get the window reference via the document.
        this._parentWin = wlBrowser.document.defaultView;
        this._processCaptureQueue();
      }
    };
    webProgress.addProgressListener(
      this._listener,
      Ci.nsIWebProgress.NOTIFY_STATE_ALL
    );
    let loadURIOptions = {
      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
    };
    wlBrowser.loadURI(
      Services.io.newURI("chrome://global/content/backgroundPageThumbs.xhtml"),
      loadURIOptions
    );
    this._windowlessContainer = wlBrowser;

    return false;
  },

  _init() {
    Services.prefs.addObserver(ABOUT_NEWTAB_SEGREGATION_PREF, this);
    Services.obs.addObserver(this, "profile-before-change");
  },

  observe(subject, topic, data) {
    if (topic == "profile-before-change") {
      this._destroy();
    } else if (
      topic == "nsPref:changed" &&
      data == ABOUT_NEWTAB_SEGREGATION_PREF
    ) {
      BackgroundPageThumbs.renewThumbnailBrowser();
    }
  },

  /**
   * Destroys the service.  Queued and pending captures will never complete, and
   * their consumer callbacks will never be called.
   */
  _destroy() {
    if (this._captureQueue) {
      this._captureQueue.forEach(cap => cap.destroy());
    }
    this._destroyBrowser();
    if (this._windowlessContainer) {
      this._windowlessContainer.close();
    }
    delete this._captureQueue;
    delete this._windowlessContainer;
    delete this._startedParentWinInit;
    delete this._parentWin;
    delete this._listener;
  },

  QueryInterface: ChromeUtils.generateQI([
    Ci.nsIWebProgressListener,
    Ci.nsIWebProgressListener2,
    Ci.nsISupportsWeakReference,
  ]),

  onStateChange(wbp, request, stateFlags, status) {
    if (!request || !wbp.isTopLevel) {
      return;
    }

    if (
      stateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
      stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK
    ) {
      // If about:blank is being loaded after a capture, move on
      // to the next capture, otherwise ignore about:blank loads.
      if (
        request instanceof Ci.nsIChannel &&
        request.URI.spec == "about:blank"
      ) {
        if (this._expectingAboutBlank) {
          this._expectingAboutBlank = false;
          if (this._captureQueue.length) {
            this._processCaptureQueue();
          }
        }
        return;
      }

      if (!this._captureQueue.length) {
        return;
      }

      let currentCapture = this._captureQueue[0];
      if (
        Components.isSuccessCode(status) ||
        status === Cr.NS_BINDING_ABORTED
      ) {
        this._thumbBrowser.ownerGlobal.requestIdleCallback(() => {
          currentCapture.pageLoaded(this._thumbBrowser);
        });
      } else {
        currentCapture._done(
          this._thumbBrowser,
          null,
          currentCapture.timedOut
            ? TEL_CAPTURE_DONE_TIMEOUT
            : TEL_CAPTURE_DONE_LOAD_FAILED
        );
      }
    }
  },

  /**
   * Creates the thumbnail browser if it doesn't already exist.
   */
  _ensureBrowser() {
    if (this._thumbBrowser && !this._renewThumbBrowser) {
      return;
    }

    this._destroyBrowser();
    this._renewThumbBrowser = false;

    let browser = this._parentWin.document.createXULElement("browser");
    browser.setAttribute("type", "content");
    browser.setAttribute("remote", "true");
    if (this.useFissionBrowser) {
      browser.setAttribute("maychangeremoteness", "true");
    }
    browser.setAttribute("disableglobalhistory", "true");
    browser.setAttribute("messagemanagergroup", "thumbnails");
    browser.setAttribute("manualactiveness", "true");

    if (Services.prefs.getBoolPref(ABOUT_NEWTAB_SEGREGATION_PREF)) {
      // Use the private container for thumbnails.
      let privateIdentity = lazy.ContextualIdentityService.getPrivateIdentity(
        "userContextIdInternal.thumbnail"
      );
      browser.setAttribute("usercontextid", privateIdentity.userContextId);
    }

    // Size the browser.  Make its aspect ratio the same as the canvases' that
    // the thumbnails are drawn into; the canvases' aspect ratio is the same as
    // the screen's, so use that.  Aim for a size in the ballpark of 1024x768.
    let [swidth, sheight] = [{}, {}];
    Cc["@mozilla.org/gfx/screenmanager;1"]
      .getService(Ci.nsIScreenManager)
      .primaryScreen.GetRectDisplayPix({}, {}, swidth, sheight);
    let bwidth = Math.min(1024, swidth.value);
    // Setting the width and height attributes doesn't work -- the resulting
    // thumbnails are blank and transparent -- but setting the style does.
    browser.style.width = bwidth + "px";
    browser.style.height = (bwidth * sheight.value) / swidth.value + "px";
    browser.style.colorScheme = "env(-moz-content-preferred-color-scheme)";

    this._parentWin.document.documentElement.appendChild(browser);

    browser.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
    browser.mute();

    // an event that is sent if the remote process crashes - no need to remove
    // it as we want it to be there as long as the browser itself lives.
    browser.addEventListener("oop-browser-crashed", event => {
      if (!event.isTopFrame) {
        // It was a subframe that crashed. We'll ignore this.
        return;
      }

      console.error("BackgroundThumbnails remote process crashed - recovering");
      this._destroyBrowser();
      let curCapture = this._captureQueue.length ? this._captureQueue[0] : null;
      // we could retry the pending capture, but it's possible the crash
      // was due directly to it, so trying again might just crash again.
      // We could keep a flag to indicate if it previously crashed, but
      // "resetting" the capture requires more work - so for now, we just
      // discard it.
      if (curCapture) {
        // Continue queue processing by calling curCapture._done().  Do it after
        // this crashed listener returns, though.  A new browser will be created
        // immediately (on the same stack as the _done call stack) if there are
        // any more queued-up captures, and that seems to mess up the new
        // browser's message manager if it happens on the same stack as the
        // listener.  Trying to send a message to the manager in that case
        // throws NS_ERROR_NOT_INITIALIZED.
        Services.tm.dispatchToMainThread(() => {
          curCapture._done(browser, null, TEL_CAPTURE_DONE_CRASHED);
        });
      }
      // else: we must have been idle and not currently doing a capture (eg,
      // maybe a GC or similar crashed) - so there's no need to attempt a
      // queue restart - the next capture request will set everything up.
    });

    this._thumbBrowser = browser;
    browser.docShellIsActive = false;
  },

  _destroyBrowser() {
    if (!this._thumbBrowser) {
      return;
    }
    this._expectingAboutBlank = false;
    this._thumbBrowser.remove();
    delete this._thumbBrowser;
  },

  async _loadAboutBlank() {
    if (this._expectingAboutBlank) {
      return;
    }

    this._expectingAboutBlank = true;

    let loadURIOptions = {
      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
      loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_STOP_CONTENT,
    };
    this._thumbBrowser.loadURI(
      Services.io.newURI("about:blank"),
      loadURIOptions
    );
  },

  /**
   * Starts the next capture if the queue is not empty and the service is fully
   * initialized.
   */
  _processCaptureQueue() {
    if (!this._captureQueue.length) {
      if (this._thumbBrowser) {
        BackgroundPageThumbs._loadAboutBlank();
      }
      return;
    }

    if (
      this._captureQueue[0].pending ||
      !this._ensureParentWindowReady() ||
      this._expectingAboutBlank
    ) {
      return;
    }

    // Ready to start the first capture in the queue.
    this._ensureBrowser();
    this._captureQueue[0].start(this._thumbBrowser);
    if (this._destroyBrowserTimer) {
      this._destroyBrowserTimer.cancel();
      delete this._destroyBrowserTimer;
    }
  },

  /**
   * Called when the current capture completes or fails (eg, times out, remote
   * process crashes.)
   */
  _onCaptureOrTimeout(capture, reason) {
    // Since timeouts start as an item is being processed, only the first
    // item in the queue can be passed to this method.
    if (capture !== this._captureQueue[0]) {
      throw new Error("The capture should be at the head of the queue.");
    }

    this._captureQueue.shift();
    this._capturesByURL.delete(capture.url);
    if (reason != TEL_CAPTURE_DONE_OK) {
      Services.obs.notifyObservers(null, "page-thumbnail:error", capture.url);
    }

    // Start the destroy-browser timer *before* processing the capture queue.
    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    timer.initWithCallback(
      this._destroyBrowser.bind(this),
      this._destroyBrowserTimeout,
      Ci.nsITimer.TYPE_ONE_SHOT
    );
    this._destroyBrowserTimer = timer;

    this._processCaptureQueue();
  },

  _destroyBrowserTimeout: DESTROY_BROWSER_TIMEOUT,
};

BackgroundPageThumbs._init();

/**
 * Represents a single capture request in the capture queue.
 *
 * @param url              The URL to capture.
 * @param captureCallback  A function you want called when the capture
 *                         completes.
 * @param options          The capture options.
 */
function Capture(url, captureCallback, options) {
  this.url = url;
  this.captureCallback = captureCallback;
  this.redirectTimer = null;
  this.timedOut = false;
  this.options = options;
  this.id = Capture.nextID++;
  this.creationDate = new Date();
  this.doneCallbacks = [];
  if (options.onDone) {
    this.doneCallbacks.push(options.onDone);
  }
}

Capture.prototype = {
  get pending() {
    return !!this._timeoutTimer;
  },

  /**
   * Sends a message to the content script to start the capture.
   *
   * @param browser  The thumbnail browser.
   */
  start(browser) {
    this.startDate = new Date();
    tel("CAPTURE_QUEUE_TIME_MS", this.startDate - this.creationDate);

    let fallbackTimeout = Cu.isInAutomation
      ? TESTING_CAPTURE_TIMEOUT
      : DEFAULT_CAPTURE_TIMEOUT;

    // timeout timer
    let timeout =
      typeof this.options.timeout == "number"
        ? this.options.timeout
        : fallbackTimeout;
    this._timeoutTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    this._timeoutTimer.initWithCallback(
      this,
      timeout,
      Ci.nsITimer.TYPE_ONE_SHOT
    );

    this._browser = browser;

    if (!browser.browsingContext) {
      return;
    }

    this._pageLoadStartTime = new Date();

    BackgroundPageThumbs._expectingAboutBlank = false;

    let thumbnailsActor = browser.browsingContext.currentWindowGlobal.getActor(
      "BackgroundThumbnails"
    );
    thumbnailsActor
      .sendQuery("Browser:Thumbnail:LoadURL", {
        url: this.url,
      })
      .then(
        success => {
          // If it failed, then this was likely a bad url. If successful,
          // BackgroundPageThumbs.onStateChange will call _done() after the
          // load has completed.
          if (!success) {
            this._done(browser, null, TEL_CAPTURE_DONE_BAD_URI);
          }
        },
        () => {
          // The query can fail when a crash occurs while loading. The error causes
          // thumbnail crash tests to fail with an uninteresting error message.
        }
      );
  },

  readBlob: function readBlob(blob) {
    return new Promise((resolve, reject) => {
      let reader = new FileReader();
      reader.onloadend = function onloadend() {
        if (reader.readyState != FileReader.DONE) {
          reject(reader.error);
        } else {
          resolve(reader.result);
        }
      };
      reader.readAsArrayBuffer(blob);
    });
  },

  async pageLoaded(aBrowser) {
    if (this.timedOut) {
      this._done(aBrowser, null, TEL_CAPTURE_DONE_TIMEOUT);
      return;
    }

    let waitTime = Cu.isInAutomation
      ? TESTING_SETTLE_WAIT_TIME
      : SETTLE_WAIT_TIME;

    // There was additional activity, so restart the wait timer
    if (this.redirectTimer) {
      this.redirectTimer.delay = waitTime;
      return;
    }

    // The requested page has loaded or stopped/aborted, so capture the page
    // soon but first let it settle in case of in-page redirects
    await new Promise(resolve => {
      this.redirectTimer = Cc["@mozilla.org/timer;1"].createInstance(
        Ci.nsITimer
      );
      this.redirectTimer.init(resolve, waitTime, Ci.nsITimer.TYPE_ONE_SHOT);
    });

    this.redirectTimer = null;

    let pageLoadTime = new Date() - this._pageLoadStartTime;
    let canvasDrawStartTime = new Date();

    let canvas = PageThumbs.createCanvas(aBrowser.ownerGlobal, 1, 1);
    try {
      await PageThumbs.captureToCanvas(
        aBrowser,
        canvas,
        {
          isBackgroundThumb: true,
          isImage: this.options.isImage,
          backgroundColor: this.options.backgroundColor,
        },
        true
      );
    } catch (ex) {
      this._done(
        aBrowser,
        null,
        ex == "IMAGE_ZERO_DIMENSION"
          ? TEL_CAPTURE_DONE_IMAGE_ZERO_DIMENSION
          : TEL_CAPTURE_DONE_LOAD_FAILED
      );
      return;
    }

    let canvasDrawTime = new Date() - canvasDrawStartTime;

    let contentType =
      (this.options.dontStore && this.options.contentType) ||
      PageThumbs.contentType;
    let imageData = await new Promise(resolve => {
      canvas.toBlob(blob => {
        resolve(blob, contentType);
      }, contentType);
    });

    this._done(aBrowser, imageData, TEL_CAPTURE_DONE_OK, {
      CAPTURE_PAGE_LOAD_TIME_MS: pageLoadTime,
      CAPTURE_CANVAS_DRAW_TIME_MS: canvasDrawTime,
    });
  },

  /**
   * The only intended external use of this method is by the service when it's
   * uninitializing and doing things like destroying the thumbnail browser.  In
   * that case the consumer's completion callback will never be called.
   */
  destroy() {
    // This method may be called for captures that haven't started yet, so
    // guard against not yet having _timeoutTimer, _msgMan etc properties...
    if (this._timeoutTimer) {
      this._timeoutTimer.cancel();
      delete this._timeoutTimer;
    }
    delete this.captureCallback;
    delete this.doneCallbacks;
    delete this.options;
  },

  // Called when the timeout timer fires.
  notify() {
    this.timedOut = true;
    this._browser.stop();
  },

  _done(browser, imageData, reason, telemetry) {
    // Note that _done will be called only once, by either receiveMessage or
    // notify, since it calls destroy here, which cancels the timeout timer and
    // removes the didCapture message listener.
    let { captureCallback, doneCallbacks, options } = this;
    this.destroy();

    if (typeof reason != "number") {
      throw new Error("A done reason must be given.");
    }

    tel("CAPTURE_DONE_REASON_2", reason);

    if (telemetry) {
      // Telemetry is currently disabled in the content process (bug 680508).
      for (let id in telemetry) {
        tel(id, telemetry[id]);
      }
    }

    let done = (info = null) => {
      captureCallback(this, reason);
      for (let callback of doneCallbacks) {
        try {
          callback.call(options, this.url, reason, info);
        } catch (err) {
          console.error(err);
        }
      }

      if (Services.prefs.getBoolPref(ABOUT_NEWTAB_SEGREGATION_PREF)) {
        // Clear the data in the private container for thumbnails.
        let privateIdentity = lazy.ContextualIdentityService.getPrivateIdentity(
          "userContextIdInternal.thumbnail"
        );
        if (privateIdentity) {
          Services.clearData.deleteDataFromOriginAttributesPattern({
            userContextId: privateIdentity.userContextId,
          });
        }
      }
    };

    if (!imageData) {
      done();
      return;
    }

    this.readBlob(imageData).then(buffer => {
      if (options.dontStore) {
        done({
          data: buffer,
          originalUrl: this.url,
          finalUrl: browser.currentURI.spec,
          contentType: options.contentType || PageThumbs.contentType,
        });
      } else {
        PageThumbs._store(this.url, browser.currentURI.spec, buffer, true).then(
          done,
          done
        );
      }
    });
  },
};

Capture.nextID = 0;

/**
 * Adds a value to one of this module's telemetry histograms.
 *
 * @param histogramID  This is prefixed with this module's ID.
 * @param value        The value to add.
 */
function tel(histogramID, value) {
  let id = TELEMETRY_HISTOGRAM_ID_PREFIX + histogramID;
  Services.telemetry.getHistogramById(id).add(value);
}

function schedule(callback) {
  Services.tm.dispatchToMainThread(callback);
}
PK
!<��z�++&modules/BackgroundTasksManager.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
  DevToolsSocketStatus:
    "resource://devtools/shared/security/DevToolsSocketStatus.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  let { ConsoleAPI } = ChromeUtils.importESModule(
    "resource://gre/modules/Console.sys.mjs"
  );
  let consoleOptions = {
    // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
    // messages during development. See LOG_LEVELS in Console.sys.mjs for details.
    maxLogLevel: "error",
    maxLogLevelPref: "toolkit.backgroundtasks.loglevel",
    prefix: "BackgroundTasksManager",
  };
  return new ConsoleAPI(consoleOptions);
});

ChromeUtils.defineLazyGetter(lazy, "DevToolsStartup", () => {
  return Cc["@mozilla.org/devtools/startup-clh;1"].getService(
    Ci.nsICommandLineHandler
  ).wrappedJSObject;
});

// The default timing settings can be overriden by the preferences
// toolkit.backgroundtasks.defaultTimeoutSec and
// toolkit.backgroundtasks.defaultMinTaskRuntimeMS for all background tasks
// and individually per module by
// export const backgroundTaskTimeoutSec = X;
// export const backgroundTaskMinRuntimeMS = Y;
let timingSettings = {
  minTaskRuntimeMS: 500,
  maxTaskRuntimeSec: 600, // 10 minutes.
};

// Map resource://testing-common/ to the shared test modules directory.  This is
// a transliteration of `register_modules_protocol_handler` from
// https://searchfox.org/mozilla-central/rev/f081504642a115cb8236bea4d8250e5cb0f39b02/testing/xpcshell/head.js#358-389.
function registerModulesProtocolHandler() {
  let _TESTING_MODULES_URI = Services.env.get(
    "XPCSHELL_TESTING_MODULES_URI",
    ""
  );
  if (!_TESTING_MODULES_URI) {
    return false;
  }

  let protocolHandler = Services.io
    .getProtocolHandler("resource")
    .QueryInterface(Ci.nsIResProtocolHandler);

  protocolHandler.setSubstitution(
    "testing-common",
    Services.io.newURI(_TESTING_MODULES_URI)
  );
  // Log loudly so that when testing, we always actually use the
  // console logging mechanism and therefore deterministically load that code.
  lazy.log.error(
    `Substitution set: resource://testing-common aliases ${_TESTING_MODULES_URI}`
  );

  return true;
}

function locationsForBackgroundTaskNamed(name) {
  const subModules = [
    "resource:///modules", // App-specific first.
    "resource://gre/modules", // Toolkit/general second.
  ];

  if (registerModulesProtocolHandler()) {
    subModules.push("resource://testing-common"); // Test-only third.
  }

  let locations = [];
  for (const subModule of subModules) {
    let URI = `${subModule}/backgroundtasks/BackgroundTask_${name}.sys.mjs`;
    locations.push(URI);
  }

  return locations;
}

/**
 * Find an ES module named like `backgroundtasks/BackgroundTask_${name}.sys.mjs`,
 * import it, and return the whole module.
 *
 * When testing, allow to load from `XPCSHELL_TESTING_MODULES_URI`,
 * which is registered at `resource://testing-common`, the standard
 * location for test-only modules.
 *
 * @return {Object} The imported module.
 * @throws NS_ERROR_NOT_AVAILABLE if a background task with the given `name` is
 * not found.
 */
function findBackgroundTaskModule(name) {
  for (const URI of locationsForBackgroundTaskNamed(name)) {
    lazy.log.debug(`Looking for background task at URI: ${URI}`);

    try {
      const taskModule = ChromeUtils.importESModule(URI);
      lazy.log.info(`Found background task at URI: ${URI}`);
      return taskModule;
    } catch (ex) {
      if (ex.result != Cr.NS_ERROR_FILE_NOT_FOUND) {
        throw ex;
      }
    }
  }

  lazy.log.warn(`No backgroundtask named '${name}' registered`);
  throw new Components.Exception(
    `No backgroundtask named '${name}' registered`,
    Cr.NS_ERROR_NOT_AVAILABLE
  );
}

export class BackgroundTasksManager {
  get helpInfo() {
    const bts = Cc["@mozilla.org/backgroundtasks;1"].getService(
      Ci.nsIBackgroundTasks
    );

    if (bts.isBackgroundTaskMode) {
      return lazy.DevToolsStartup.jsdebuggerHelpInfo;
    }

    return "";
  }

  handle(commandLine) {
    const bts = Cc["@mozilla.org/backgroundtasks;1"].getService(
      Ci.nsIBackgroundTasks
    );

    if (!bts.isBackgroundTaskMode) {
      lazy.log.info(
        `${Services.appinfo.processID}: !isBackgroundTaskMode, exiting`
      );
      return;
    }

    const name = bts.backgroundTaskName();
    lazy.log.info(
      `${Services.appinfo.processID}: Preparing to run background task named '${name}'` +
        ` (with ${commandLine.length} arguments)`
    );

    if (!("@mozilla.org/devtools/startup-clh;1" in Cc)) {
      return;
    }

    // Check this before the devtools startup flow handles and removes it.
    const CASE_INSENSITIVE = false;
    if (
      commandLine.findFlag("jsdebugger", CASE_INSENSITIVE) < 0 &&
      commandLine.findFlag("start-debugger-server", CASE_INSENSITIVE) < 0
    ) {
      lazy.log.info(
        `${Services.appinfo.processID}: No devtools flag found; not preparing devtools thread`
      );
      return;
    }

    const waitFlag =
      commandLine.findFlag("wait-for-jsdebugger", CASE_INSENSITIVE) != -1;
    if (waitFlag) {
      function onDevtoolsThreadReady(subject, topic) {
        lazy.log.info(
          `${Services.appinfo.processID}: Setting breakpoints for background task named '${name}'` +
            ` (with ${commandLine.length} arguments)`
        );

        const threadActor = subject.wrappedJSObject;
        threadActor.setBreakpointOnLoad(locationsForBackgroundTaskNamed(name));

        Services.obs.removeObserver(onDevtoolsThreadReady, topic);
      }

      Services.obs.addObserver(onDevtoolsThreadReady, "devtools-thread-ready");
    }

    const DevToolsStartup = Cc[
      "@mozilla.org/devtools/startup-clh;1"
    ].getService(Ci.nsICommandLineHandler);
    DevToolsStartup.handle(commandLine);
  }

  async runBackgroundTaskNamed(name, commandLine) {
    function addMarker(markerName) {
      return ChromeUtils.addProfilerMarker(markerName, undefined, name);
    }
    addMarker("BackgroundTasksManager:AfterRunBackgroundTaskNamed");

    lazy.log.info(
      `${Services.appinfo.processID}: Running background task named '${name}'` +
        ` (with ${commandLine.length} arguments)`
    );
    lazy.log.debug(
      `${Services.appinfo.processID}: Background task using profile` +
        ` '${Services.dirsvc.get("ProfD", Ci.nsIFile).path}'`
    );

    let exitCode = EXIT_CODE.NOT_FOUND;
    try {
      let taskModule = findBackgroundTaskModule(name);
      addMarker("BackgroundTasksManager:AfterFindRunBackgroundTask");

      // Get timing configuration. First check for default preferences,
      // then for per module overrides.
      timingSettings.minTaskRuntimeMS = Services.prefs.getIntPref(
        "toolkit.backgroundtasks.defaultMinTaskRuntimeMS",
        timingSettings.minTaskRuntimeMS
      );
      if (taskModule.backgroundTaskMinRuntimeMS) {
        timingSettings.minTaskRuntimeMS = taskModule.backgroundTaskMinRuntimeMS;
      }
      timingSettings.maxTaskRuntimeSec = Services.prefs.getIntPref(
        "toolkit.backgroundtasks.defaultTimeoutSec",
        timingSettings.maxTaskRuntimeSec
      );
      if (taskModule.backgroundTaskTimeoutSec) {
        timingSettings.maxTaskRuntimeSec = taskModule.backgroundTaskTimeoutSec;
      }
      try {
        let minimumReached = false;
        let minRuntime;
        let maxRuntime;
        if (lazy.DevToolsSocketStatus.hasSocketOpened()) {
          lazy.log.info(
            `Setting background task timeout period to indefinite because a DevTools server is listening.`
          );
          minimumReached = true;
          maxRuntime = new Promise(() => {});
        } else {
          minRuntime = new Promise(resolve =>
            lazy.setTimeout(() => {
              minimumReached = true;
              resolve(true);
            }, timingSettings.minTaskRuntimeMS)
          );
          maxRuntime = new Promise(resolve =>
            lazy.setTimeout(() => {
              lazy.log.error(`Background task named '${name}' timed out`);
              resolve(EXIT_CODE.TIMEOUT);
            }, timingSettings.maxTaskRuntimeSec * 1000)
          );
        }
        exitCode = await Promise.race([
          maxRuntime,
          taskModule.runBackgroundTask(commandLine),
        ]);
        if (!minimumReached) {
          lazy.log.debug(
            `Backgroundtask named '${name}' waiting for minimum runtime.`
          );
          await minRuntime;
        }
        lazy.log.info(
          `Backgroundtask named '${name}' completed with exit code ${exitCode}`
        );
      } catch (e) {
        lazy.log.error(`Backgroundtask named '${name}' threw exception`, e);
        exitCode = EXIT_CODE.EXCEPTION;
      }
    } finally {
      addMarker("BackgroundTasksManager:AfterAwaitRunBackgroundTask");

      lazy.log.info(`Invoking Services.startup.quit(..., ${exitCode})`);
      Services.startup.quit(Ci.nsIAppStartup.eForceQuit, exitCode);
    }

    return exitCode;
  }

  classID = Components.ID("{4d48c536-e16f-4699-8f9c-add4f28f92f0}");
  QueryInterface = ChromeUtils.generateQI([
    "nsIBackgroundTasksManager",
    "nsICommandLineHandler",
  ]);
}

/**
 * Background tasks should standard exit code conventions where 0 denotes
 * success and non-zero denotes failure and/or an error.  In addition, since
 * background tasks have limited channels to communicate with consumers, the
 * special values `NOT_FOUND` (integer 2) and `THREW_EXCEPTION` (integer 3) are
 * distinguished.
 *
 * If you extend this to add background task-specific exit codes, use exit codes
 * greater than 10 to allow for additional shared exit codes to be added here.
 * Exit codes should be between 0 and 127 to be safe across platforms.
 */
export const EXIT_CODE = {
  /**
   * The task succeeded.
   *
   * The `runBackgroundTask(...)` promise resolved to 0.
   */
  SUCCESS: 0,

  /**
   * The task with the specified name could not be found or imported.
   *
   * The corresponding `runBackgroundTask` method could not be found.
   */
  NOT_FOUND: 2,

  /**
   * The task failed with an uncaught exception.
   *
   * The `runBackgroundTask(...)` promise rejected with an exception.
   */
  EXCEPTION: 3,

  /**
   * The task took too long and timed out.
   *
   * The default timeout is controlled by the pref:
   * "toolkit.backgroundtasks.defaultTimeoutSec", but tasks can override this
   * by exporting a non-zero `backgroundTaskTimeoutSec` value.
   */
  TIMEOUT: 4,

  /**
   * The last exit code reserved by this structure.  Use codes larger than this
   * code for background task-specific exit codes.
   */
  LAST_RESERVED: 10,
};
PK
!<�e�S55$modules/BackgroundTasksUtils.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  let { ConsoleAPI } = ChromeUtils.importESModule(
    "resource://gre/modules/Console.sys.mjs"
  );
  let consoleOptions = {
    // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
    // messages during development. See LOG_LEVELS in Console.sys.mjs for details.
    maxLogLevel: "error",
    maxLogLevelPref: "toolkit.backgroundtasks.loglevel",
    prefix: "BackgroundTasksUtils",
  };
  return new ConsoleAPI(consoleOptions);
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "ProfileService",
  "@mozilla.org/toolkit/profile-service;1",
  "nsIToolkitProfileService"
);

ChromeUtils.defineESModuleGetters(lazy, {
  ASRouter:
    // eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
    "resource:///modules/asrouter/ASRouter.sys.mjs",
  ASRouterDefaultConfig:
    // eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
    "resource:///modules/asrouter/ASRouterDefaultConfig.sys.mjs",

  ExperimentManager: "resource://nimbus/lib/ExperimentManager.sys.mjs",

  RemoteSettingsExperimentLoader:
    "resource://nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs",
});

class CannotLockProfileError extends Error {
  constructor(message) {
    super(message);
    this.name = "CannotLockProfileError";
  }
}

export var BackgroundTasksUtils = {
  // Manage our own default profile that can be overridden for testing.  It's
  // easier to do this here rather than using the profile service itself.
  _defaultProfileInitialized: false,
  _defaultProfile: null,

  getDefaultProfile() {
    if (!this._defaultProfileInitialized) {
      this._defaultProfileInitialized = true;
      // This is all test-only.
      let defaultProfilePath = Services.env.get(
        "MOZ_BACKGROUNDTASKS_DEFAULT_PROFILE_PATH"
      );
      let noDefaultProfile = Services.env.get(
        "MOZ_BACKGROUNDTASKS_NO_DEFAULT_PROFILE"
      );
      if (defaultProfilePath) {
        lazy.log.info(
          `getDefaultProfile: using default profile path ${defaultProfilePath}`
        );
        var tmpd = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
        tmpd.initWithPath(defaultProfilePath);
        // Sadly this writes to `profiles.ini`, but there's little to be done.
        this._defaultProfile = lazy.ProfileService.createProfile(
          tmpd,
          `MOZ_BACKGROUNDTASKS_DEFAULT_PROFILE_PATH-${Date.now()}`
        );
      } else if (noDefaultProfile) {
        lazy.log.info(`getDefaultProfile: setting default profile to null`);
        this._defaultProfile = null;
      } else {
        try {
          lazy.log.info(
            `getDefaultProfile: using ProfileService.defaultProfile`
          );
          this._defaultProfile = lazy.ProfileService.defaultProfile;
        } catch (e) {}
      }
    }
    return this._defaultProfile;
  },

  hasDefaultProfile() {
    return this.getDefaultProfile() != null;
  },

  currentProfileIsDefaultProfile() {
    let defaultProfile = this.getDefaultProfile();
    let currentProfile = lazy.ProfileService.currentProfile;
    // This comparison needs to accommodate null on both sides.
    let isDefaultProfile = defaultProfile && currentProfile == defaultProfile;
    return isDefaultProfile;
  },

  _throwIfNotLocked(lock) {
    if (!(lock instanceof Ci.nsIProfileLock)) {
      throw new Error("Passed lock was not an instance of nsIProfileLock");
    }

    try {
      // In release builds, `.directory` throws NS_ERROR_NOT_INITIALIZED when
      // unlocked.  In debug builds, `.directory` when the profile is not locked
      // will crash via `NS_ERROR`.
      if (lock.directory) {
        return;
      }
    } catch (e) {
      if (
        !(
          e instanceof Ci.nsIException &&
          e.result == Cr.NS_ERROR_NOT_INITIALIZED
        )
      ) {
        throw e;
      }
    }
    throw new Error("Profile is not locked");
  },

  /**
   * Locks the given profile and provides the path to it to the callback.
   * The callback should return a promise and once settled the profile is
   * unlocked and then the promise returned back to the caller of this function.
   *
   * @template T
   * @param {(lock: nsIProfileLock) => Promise<T>} callback
   * @param {nsIToolkitProfile} [profile] defaults to default profile
   * @return {Promise<T>}
   */
  async withProfileLock(callback, profile = this.getDefaultProfile()) {
    if (!profile) {
      throw new Error("No default profile exists");
    }

    let lock;
    try {
      lock = profile.lock({});
      lazy.log.info(
        `withProfileLock: locked profile at ${lock.directory.path}`
      );
    } catch (e) {
      throw new CannotLockProfileError(`Cannot lock profile: ${e}`);
    }

    try {
      // We must await to ensure any logging is displayed after the callback resolves.
      return await callback(lock);
    } finally {
      try {
        lazy.log.info(
          `withProfileLock: unlocking profile at ${lock.directory.path}`
        );
        lock.unlock();
        lazy.log.info(`withProfileLock: unlocked profile`);
      } catch (e) {
        lazy.log.warn(`withProfileLock: error unlocking profile`, e);
      }
    }
  },

  /**
   * Reads the preferences from "prefs.js" out of a profile, optionally
   * returning only names satisfying a given predicate.
   *
   * If no `lock` is given, the default profile is locked and the preferences
   * read from it.  If `lock` is given, read from the given lock's directory.
   *
   * @param {(name: string) => boolean} [predicate] a predicate to filter
   * preferences by; if not given, all preferences are accepted.
   * @param {nsIProfileLock} [lock] optional lock to use
   * @returns {object} with keys that are string preference names and values
   * that are string|number|boolean preference values.
   */
  async readPreferences(predicate = null, lock = null) {
    if (!lock) {
      return this.withProfileLock(profileLock =>
        this.readPreferences(predicate, profileLock)
      );
    }

    this._throwIfNotLocked(lock);
    lazy.log.info(`readPreferences: profile is locked`);

    let prefs = {};
    let addPref = (kind, name, value) => {
      if (predicate && !predicate(name)) {
        return;
      }
      prefs[name] = value;
    };

    // We ignore any "user.js" file, since usage is low and doing otherwise
    // requires implementing a bit more of `nsIPrefsService` than feels safe.
    let prefsFile = lock.directory.clone();
    prefsFile.append("prefs.js");
    lazy.log.info(`readPreferences: will parse prefs ${prefsFile.path}`);

    let data = await IOUtils.read(prefsFile.path);
    lazy.log.debug(
      `readPreferences: parsing prefs from buffer of length ${data.length}`
    );

    Services.prefs.parsePrefsFromBuffer(
      data,
      {
        onStringPref: addPref,
        onIntPref: addPref,
        onBoolPref: addPref,
        onError(message) {
          // Firefox itself manages "prefs.js", so errors should be infrequent.
          lazy.log.error(message);
        },
      },
      prefsFile.path
    );

    lazy.log.debug(`readPreferences: parsed prefs from buffer`, prefs);
    return prefs;
  },

  /**
   * Reads the snapshotted Firefox Messaging System targeting out of a profile.
   *
   * If no `lock` is given, the default profile is locked and the preferences
   * read from it.  If `lock` is given, read from the given lock's directory.
   *
   * @param {nsIProfileLock} [lock] optional lock to use
   * @returns {object}
   */
  async readFirefoxMessagingSystemTargetingSnapshot(lock = null) {
    if (!lock) {
      return this.withProfileLock(profileLock =>
        this.readFirefoxMessagingSystemTargetingSnapshot(profileLock)
      );
    }

    this._throwIfNotLocked(lock);

    let snapshotFile = lock.directory.clone();
    snapshotFile.append("targeting.snapshot.json");

    lazy.log.info(
      `readFirefoxMessagingSystemTargetingSnapshot: will read Firefox Messaging ` +
        `System targeting snapshot from ${snapshotFile.path}`
    );

    return IOUtils.readJSON(snapshotFile.path);
  },

  /**
   * Reads the Telemetry Client ID out of a profile.
   *
   * If no `lock` is given, the default profile is locked and the preferences
   * read from it.  If `lock` is given, read from the given lock's directory.
   *
   * @param {nsIProfileLock} [lock] optional lock to use
   * @returns {string}
   */
  async readTelemetryClientID(lock = null) {
    if (!lock) {
      return this.withProfileLock(profileLock =>
        this.readTelemetryClientID(profileLock)
      );
    }

    this._throwIfNotLocked(lock);

    let stateFile = lock.directory.clone();
    stateFile.append("datareporting");
    stateFile.append("state.json");

    lazy.log.info(
      `readPreferences: will read Telemetry client ID from ${stateFile.path}`
    );

    let state = await IOUtils.readJSON(stateFile.path);

    return state.clientID;
  },

  /**
   * Enable the Nimbus experimentation framework.
   *
   * @param {nsICommandLine} commandLine if given, accept command line parameters
   *                                     like `--url about:studies?...` or
   *                                     `--url file:path/to.json` to explicitly
   *                                     opt-on to experiment branches.
   * @param {object} defaultProfile      snapshot of Firefox Messaging System
   *                                     targeting from default browsing profile.
   */
  async enableNimbus(commandLine, defaultProfile = {}) {
    try {
      await lazy.ExperimentManager.onStartup({ defaultProfile });
    } catch (err) {
      lazy.log.error("Failed to initialize ExperimentManager:", err);
      throw err;
    }

    try {
      await lazy.RemoteSettingsExperimentLoader.init({ forceSync: true });
    } catch (err) {
      lazy.log.error(
        "Failed to initialize RemoteSettingsExperimentLoader:",
        err
      );
      throw err;
    }

    // Allow manual explicit opt-in to experiment branches to facilitate testing.
    //
    // Process command line arguments, like
    // `--url about:studies?optin_slug=nalexander-ms-test1&optin_branch=treatment-a&optin_collection=nimbus-preview`
    // or
    // `--url file:///Users/nalexander/Mozilla/gecko/experiment.json?optin_branch=treatment-a`.
    let ar;
    while ((ar = commandLine?.handleFlagWithParam("url", false))) {
      let uri = commandLine.resolveURI(ar);
      const params = new URLSearchParams(uri.query);

      if (uri.schemeIs("about") && uri.filePath == "studies") {
        // Allow explicit opt-in.  In the future, we might take this pref from
        // the default browsing profile.
        Services.prefs.setBoolPref("nimbus.debug", true);

        const data = {
          slug: params.get("optin_slug"),
          branch: params.get("optin_branch"),
          collection: params.get("optin_collection"),
        };
        await lazy.RemoteSettingsExperimentLoader.optInToExperiment(data);
        lazy.log.info(`Opted in to experiment: ${JSON.stringify(data)}`);
      }

      if (uri.schemeIs("file")) {
        let branchSlug = params.get("optin_branch");
        let path = decodeURIComponent(uri.filePath);
        let response = await fetch(uri.spec);
        let recipe = await response.json();
        if (recipe.permissions) {
          // Saved directly from Experimenter, there's a top-level `data`.  Hand
          // written, that's not the norm.
          recipe = recipe.data;
        }
        let branch = recipe.branches.find(b => b.slug == branchSlug);

        lazy.ExperimentManager.forceEnroll(recipe, branch);
        lazy.log.info(`Forced enrollment into: ${path}, branch: ${branchSlug}`);
      }
    }
  },

  /**
   * Enable the Firefox Messaging System and, when successfully initialized,
   * trigger a message with trigger id `backgroundTask`.
   *
   * @param {object} defaultProfile - snapshot of Firefox Messaging System
   *                                  targeting from default browsing profile.
   */
  async enableFirefoxMessagingSystem(defaultProfile = {}) {
    function logArgs(tag, ...args) {
      lazy.log.debug(`FxMS invoked ${tag}: ${JSON.stringify(args)}`);
    }

    let { messageHandler, router, createStorage } =
      lazy.ASRouterDefaultConfig();

    if (!router.initialized) {
      const storage = await createStorage();
      await router.init({
        storage,
        // Background tasks never send legacy telemetry.
        sendTelemetry: logArgs.bind(null, "sendTelemetry"),
        dispatchCFRAction: messageHandler.handleCFRAction.bind(messageHandler),
        // There's no child process involved in background tasks, so swallow all
        // of these messages.
        clearChildMessages: logArgs.bind(null, "clearChildMessages"),
        clearChildProviders: logArgs.bind(null, "clearChildProviders"),
        updateAdminState: () => {},
      });
    }

    await lazy.ASRouter.waitForInitialized;

    const triggerId = "backgroundTask";
    await lazy.ASRouter.sendTriggerMessage({
      browser: null,
      id: triggerId,
      context: {
        defaultProfile,
      },
    });
    lazy.log.info(
      "Triggered Firefox Messaging System with trigger id 'backgroundTask'"
    );
  },
};
PK
!<}�XaXamodules/Bits.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This module is used to interact with the Windows BITS component (Background
 * Intelligent Transfer Service). This functionality cannot be used unless on
 * Windows.
 *
 * The reason for this file's existence is that the interfaces in nsIBits.idl
 * are asynchronous, but are unable to use Promises because they are implemented
 * in Rust, which does not yet support Promises. This file functions as a layer
 * between the Rust and the JS that provides access to the functionality
 * provided by nsIBits via Promises rather than callbacks.
 */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

// This conditional prevents errors if this file is imported from operating
// systems other than Windows. This is purely for convenient importing, because
// attempting to use anything in this file on platforms other than Windows will
// result in an error.
if (AppConstants.MOZ_BITS_DOWNLOAD) {
  XPCOMUtils.defineLazyServiceGetter(
    lazy,
    "gBits",
    "@mozilla.org/bits;1",
    "nsIBits"
  );
}

// This value exists to mitigate a very unlikely problem: If a BITS method
// catastrophically fails, it may never call its callback. This would result in
// methods in this file returning promises that never resolve. This could, in
// turn, lead to download code hanging altogether rather than being able to
// report errors and utilize fallback mechanisms.
// This problem is mitigated by giving these promises a timeout, the length of
// which will be determined by this value.
const kBitsMethodTimeoutMs = 10 * 60 * 1000; // 10 minutes

/**
 * This class will wrap the errors returned by the nsIBits interface to make
 * them more uniform and more easily consumable.
 *
 * The values of stored by this error type are entirely numeric. This should
 * make them easier to consume with JS and telemetry, but does make them fairly
 * unreadable. nsIBits.idl will need to be referenced to look up what errors
 * the values correspond to.
 *
 * The type of BitsError.code is dependent on the value of BitsError.codeType.
 * It may be null, a number (corresponding to an nsresult or hresult value),
 * a string, or an exception.
 */
export class BitsError extends Error {
  // If codeType == "none", code may be unspecified.
  constructor(type, action, stage, codeType, code) {
    let message =
      `${BitsError.name} {type: ${type}, action: ${action}, ` +
      `stage: ${stage}`;
    switch (codeType) {
      case lazy.gBits.ERROR_CODE_TYPE_NONE:
        code = null;
        message += ", codeType: none}";
        break;
      case lazy.gBits.ERROR_CODE_TYPE_NSRESULT:
        message += `, codeType: nsresult, code: ${code}}`;
        break;
      case lazy.gBits.ERROR_CODE_TYPE_HRESULT:
        message += `, codeType: hresult, code: ${code}}`;
        break;
      case lazy.gBits.ERROR_CODE_TYPE_STRING:
        message += `, codeType: string, code: ${JSON.stringify(code)}}`;
        break;
      case lazy.gBits.ERROR_CODE_TYPE_EXCEPTION:
        message += `, codeType: exception, code: ${code}}`;
        break;
      default:
        message += ", codeType: invalid}";
        break;
    }
    super(message);

    this.type = type;
    this.action = action;
    this.stage = stage;
    this.codeType = codeType;
    this.code = code;
    this.name = this.constructor.name;
    this.succeeded = false;
  }
}

// These specializations exist to make them easier to construct since they may
// need to be constructed outside of this file.
export class BitsVerificationError extends BitsError {
  constructor() {
    super(
      Ci.nsIBits.ERROR_TYPE_VERIFICATION_FAILURE,
      Ci.nsIBits.ERROR_ACTION_NONE,
      Ci.nsIBits.ERROR_STAGE_VERIFICATION,
      Ci.nsIBits.ERROR_CODE_TYPE_NONE
    );
  }
}

export class BitsUnknownError extends BitsError {
  constructor() {
    super(
      Ci.nsIBits.ERROR_TYPE_UNKNOWN,
      Ci.nsIBits.ERROR_ACTION_UNKNOWN,
      Ci.nsIBits.ERROR_STAGE_UNKNOWN,
      Ci.nsIBits.ERROR_CODE_TYPE_NONE
    );
  }
}

/**
 * Returns a timer object. If the timer expires, reject will be called with
 * a BitsError error. The timer's cancel method should be called if the promise
 * resolves or rejects without the timeout expiring.
 */
function makeTimeout(reject, errorAction) {
  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  timer.initWithCallback(
    () => {
      let error = new BitsError(
        lazy.gBits.ERROR_TYPE_METHOD_TIMEOUT,
        errorAction,
        lazy.gBits.ERROR_STAGE_UNKNOWN,
        lazy.gBits.ERROR_CODE_TYPE_NONE
      );
      reject(error);
    },
    kBitsMethodTimeoutMs,
    Ci.nsITimer.TYPE_ONE_SHOT
  );
  return timer;
}

/**
 * This function does all of the wrapping and error handling for an async
 * BitsRequest method. This allows the implementations for those methods to
 * simply call this function with a closure that executes appropriate
 * nsIBitsRequest method.
 *
 * Specifically, this function takes an nsBitsErrorAction and a function.
 * The nsBitsErrorAction will be used when constructing a BitsError, if the
 * wrapper encounters an error.
 * The function will be passed the callback function that should be passed to
 * the nsIBitsRequest method.
 */
async function requestPromise(errorAction, actionFn) {
  return new Promise((resolve, reject) => {
    let timer = makeTimeout(reject, errorAction);

    let callback = {
      QueryInterface: ChromeUtils.generateQI(["nsIBitsCallback"]),
      success() {
        timer.cancel();
        resolve();
      },
      failure(type, action, stage) {
        timer.cancel();
        let error = new BitsError(
          type,
          action,
          stage,
          lazy.gBits.ERROR_CODE_TYPE_NONE
        );
        reject(error);
      },
      failureNsresult(type, action, stage, code) {
        timer.cancel();
        let error = new BitsError(
          type,
          action,
          stage,
          lazy.gBits.ERROR_CODE_TYPE_NSRESULT,
          code
        );
        reject(error);
      },
      failureHresult(type, action, stage, code) {
        timer.cancel();
        let error = new BitsError(
          type,
          action,
          stage,
          lazy.gBits.ERROR_CODE_TYPE_HRESULT,
          code
        );
        reject(error);
      },
      failureString(type, action, stage, message) {
        timer.cancel();
        let error = new BitsError(
          type,
          action,
          stage,
          lazy.gBits.ERROR_CODE_TYPE_STRING,
          message
        );
        reject(error);
      },
    };

    try {
      actionFn(callback);
    } catch (e) {
      let error = new BitsError(
        lazy.gBits.ERROR_TYPE_METHOD_THREW,
        errorAction,
        lazy.gBits.ERROR_STAGE_PRETASK,
        lazy.gBits.ERROR_CODE_TYPE_EXCEPTION,
        e
      );
      reject(error);
    }
  });
}

/**
 * This class is a wrapper around nsIBitsRequest that converts functions taking
 * callbacks to asynchronous functions. This class implements nsIRequest.
 *
 * Note that once the request has been shutdown, calling methods on it will
 * cause an exception to be thrown. The request will be shutdown automatically
 * when the BITS job is successfully completed or cancelled. If the request is
 * no longer needed, but the transfer is still in progress, the shutdown method
 * should be called manually to prevent memory leaks.
 * Getter methods (except loadGroup and loadFlags) should continue to be
 * accessible, even after shutdown.
 */
export class BitsRequest {
  constructor(request) {
    this._request = request;
    this._request.QueryInterface(Ci.nsIBitsRequest);
  }

  /**
   * This function releases the Rust request that backs this wrapper. Calling
   * any method on this request after calling release will result in a BitsError
   * being thrown.
   *
   * This step is important, because otherwise a cycle exists that the cycle
   * collector doesn't know about. To break this cycle, either the Rust request
   * needs to let go of the observer, or the JS request wrapper needs to let go
   * of the Rust request (which is what we do here).
   *
   * Once there is support for cycle collection of cycles that extend through
   * Rust, this function may no longer be necessary.
   */
  shutdown() {
    if (this.hasShutdown) {
      return;
    }
    // Cache some values before we shut down so they are still available
    this._name = this._request.name;
    this._status = this._request.status;
    this._bitsId = this._request.bitsId;
    this._transferError = this._request.transferError;

    this._request = null;
  }

  /**
   * Allows consumers to determine if this request has been shutdown.
   */
  get hasShutdown() {
    return !this._request;
  }

  /**
   * This is the nsIRequest implementation. Since this._request is an
   * nsIRequest, these functions just call the corresponding method on it.
   *
   * Note that nsIBitsRequest does not yet properly implement load groups or
   * load flags. This class will still forward those calls, but they will have
   * not succeed.
   */
  get name() {
    if (!this._request) {
      return this._name;
    }
    return this._request.name;
  }
  isPending() {
    if (!this._request) {
      return false;
    }
    return this._request.isPending();
  }
  get status() {
    if (!this._request) {
      return this._status;
    }
    return this._request.status;
  }
  cancel(status) {
    return this.cancelAsync(status);
  }
  suspend() {
    if (!this._request) {
      throw new BitsError(
        Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN,
        Ci.nsIBits.ERROR_ACTION_SUSPEND,
        Ci.nsIBits.ERROR_STAGE_PRETASK,
        Ci.nsIBits.ERROR_CODE_TYPE_NONE
      );
    }
    return this._request.suspend();
  }
  resume() {
    if (!this._request) {
      throw new BitsError(
        Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN,
        Ci.nsIBits.ERROR_ACTION_RESUME,
        Ci.nsIBits.ERROR_STAGE_PRETASK,
        Ci.nsIBits.ERROR_CODE_TYPE_NONE
      );
    }
    return this._request.resume();
  }
  get loadGroup() {
    if (!this._request) {
      throw new BitsError(
        Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN,
        Ci.nsIBits.ERROR_ACTION_NONE,
        Ci.nsIBits.ERROR_STAGE_PRETASK,
        Ci.nsIBits.ERROR_CODE_TYPE_NONE
      );
    }
    return this._request.loadGroup;
  }
  set loadGroup(group) {
    if (!this._request) {
      throw new BitsError(
        Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN,
        Ci.nsIBits.ERROR_ACTION_NONE,
        Ci.nsIBits.ERROR_STAGE_PRETASK,
        Ci.nsIBits.ERROR_CODE_TYPE_NONE
      );
    }
    this._request.loadGroup = group;
  }
  get loadFlags() {
    if (!this._request) {
      throw new BitsError(
        Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN,
        Ci.nsIBits.ERROR_ACTION_NONE,
        Ci.nsIBits.ERROR_STAGE_PRETASK,
        Ci.nsIBits.ERROR_CODE_TYPE_NONE
      );
    }
    return this._request.loadFlags;
  }
  set loadFlags(flags) {
    if (!this._request) {
      throw new BitsError(
        Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN,
        Ci.nsIBits.ERROR_ACTION_NONE,
        Ci.nsIBits.ERROR_STAGE_PRETASK,
        Ci.nsIBits.ERROR_CODE_TYPE_NONE
      );
    }
    this._request.loadFlags = flags;
  }

  /**
   * This function wraps nsIBitsRequest::bitsId.
   */
  get bitsId() {
    if (!this._request) {
      return this._bitsId;
    }
    return this._request.bitsId;
  }

  /**
   * This function wraps nsIBitsRequest::transferError.
   *
   * Instead of simply returning the nsBitsErrorType value, however, it returns
   * a BitsError object, or null.
   */
  get transferError() {
    let result;
    if (this._request) {
      result = this._request.transferError;
    } else {
      result = this._transferError;
    }
    if (result == Ci.nsIBits.ERROR_TYPE_SUCCESS) {
      return null;
    }
    return new BitsError(
      result,
      Ci.nsIBits.ERROR_ACTION_NONE,
      Ci.nsIBits.ERROR_STAGE_MONITOR,
      Ci.nsIBits.ERROR_CODE_TYPE_NONE
    );
  }

  /**
   * This function wraps nsIBitsRequest::changeMonitorInterval.
   *
   * Instead of taking a callback, the function is asynchronous.
   * This method either resolves with no data, or rejects with a BitsError.
   */
  async changeMonitorInterval(monitorIntervalMs) {
    if (!this._request) {
      throw new BitsError(
        Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN,
        Ci.nsIBits.ERROR_ACTION_CHANGE_MONITOR_INTERVAL,
        Ci.nsIBits.ERROR_STAGE_PRETASK,
        Ci.nsIBits.ERROR_CODE_TYPE_NONE
      );
    }
    let action = lazy.gBits.ERROR_ACTION_CHANGE_MONITOR_INTERVAL;
    return requestPromise(action, callback => {
      this._request.changeMonitorInterval(monitorIntervalMs, callback);
    });
  }

  /**
   * This function wraps nsIBitsRequest::cancelAsync.
   *
   * Instead of taking a callback, the function is asynchronous.
   * This method either resolves with no data, or rejects with a BitsError.
   *
   * Adds a default status of NS_ERROR_ABORT if one is not provided.
   */
  async cancelAsync(status) {
    if (!this._request) {
      throw new BitsError(
        Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN,
        Ci.nsIBits.ERROR_ACTION_CANCEL,
        Ci.nsIBits.ERROR_STAGE_PRETASK,
        Ci.nsIBits.ERROR_CODE_TYPE_NONE
      );
    }
    if (status === undefined) {
      status = Cr.NS_ERROR_ABORT;
    }
    let action = lazy.gBits.ERROR_ACTION_CANCEL;
    return requestPromise(action, callback => {
      this._request.cancelAsync(status, callback);
    }).then(() => this.shutdown());
  }

  /**
   * This function wraps nsIBitsRequest::setPriorityHigh.
   *
   * Instead of taking a callback, the function is asynchronous.
   * This method either resolves with no data, or rejects with a BitsError.
   */
  async setPriorityHigh() {
    if (!this._request) {
      throw new BitsError(
        Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN,
        Ci.nsIBits.ERROR_ACTION_SET_PRIORITY,
        Ci.nsIBits.ERROR_STAGE_PRETASK,
        Ci.nsIBits.ERROR_CODE_TYPE_NONE
      );
    }
    let action = lazy.gBits.ERROR_ACTION_SET_PRIORITY;
    return requestPromise(action, callback => {
      this._request.setPriorityHigh(callback);
    });
  }

  /**
   * This function wraps nsIBitsRequest::setPriorityLow.
   *
   * Instead of taking a callback, the function is asynchronous.
   * This method either resolves with no data, or rejects with a BitsError.
   */
  async setPriorityLow() {
    if (!this._request) {
      throw new BitsError(
        Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN,
        Ci.nsIBits.ERROR_ACTION_SET_PRIORITY,
        Ci.nsIBits.ERROR_STAGE_PRETASK,
        Ci.nsIBits.ERROR_CODE_TYPE_NONE
      );
    }
    let action = lazy.gBits.ERROR_ACTION_SET_PRIORITY;
    return requestPromise(action, callback => {
      this._request.setPriorityLow(callback);
    });
  }

  /**
   * This function wraps nsIBitsRequest::setNoProgressTimeout.
   *
   * Instead of taking a callback, the function is asynchronous.
   * This method either resolves with no data, or rejects with a BitsError.
   */
  async setNoProgressTimeout(timeoutSecs) {
    if (!this._request) {
      throw new BitsError(
        Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN,
        Ci.nsIBits.ERROR_ACTION_SET_NO_PROGRESS_TIMEOUT,
        Ci.nsIBits.ERROR_STAGE_PRETASK,
        Ci.nsIBits.ERROR_CODE_TYPE_NONE
      );
    }
    let action = lazy.gBits.ERROR_ACTION_SET_NO_PROGRESS_TIMEOUT;
    return requestPromise(action, callback => {
      this._request.setNoProgressTimeout(timeoutSecs, callback);
    });
  }

  /**
   * This function wraps nsIBitsRequest::complete.
   *
   * Instead of taking a callback, the function is asynchronous.
   * This method either resolves with no data, or rejects with a BitsError.
   */
  async complete() {
    if (!this._request) {
      throw new BitsError(
        Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN,
        Ci.nsIBits.ERROR_ACTION_COMPLETE,
        Ci.nsIBits.ERROR_STAGE_PRETASK,
        Ci.nsIBits.ERROR_CODE_TYPE_NONE
      );
    }
    let action = lazy.gBits.ERROR_ACTION_COMPLETE;
    return requestPromise(action, callback => {
      this._request.complete(callback);
    }).then(() => this.shutdown());
  }

  /**
   * This function wraps nsIBitsRequest::suspendAsync.
   *
   * Instead of taking a callback, the function is asynchronous.
   * This method either resolves with no data, or rejects with a BitsError.
   */
  async suspendAsync() {
    if (!this._request) {
      throw new BitsError(
        Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN,
        Ci.nsIBits.ERROR_ACTION_SUSPEND,
        Ci.nsIBits.ERROR_STAGE_PRETASK,
        Ci.nsIBits.ERROR_CODE_TYPE_NONE
      );
    }
    let action = lazy.gBits.ERROR_ACTION_SUSPEND;
    return requestPromise(action, callback => {
      this._request.suspendAsync(callback);
    });
  }

  /**
   * This function wraps nsIBitsRequest::resumeAsync.
   *
   * Instead of taking a callback, the function is asynchronous.
   * This method either resolves with no data, or rejects with a BitsError.
   */
  async resumeAsync() {
    if (!this._request) {
      throw new BitsError(
        Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN,
        Ci.nsIBits.ERROR_ACTION_RESUME,
        Ci.nsIBits.ERROR_STAGE_PRETASK,
        Ci.nsIBits.ERROR_CODE_TYPE_NONE
      );
    }
    let action = lazy.gBits.ERROR_ACTION_RESUME;
    return requestPromise(action, callback => {
      this._request.resumeAsync(callback);
    });
  }
}

BitsRequest.prototype.QueryInterface = ChromeUtils.generateQI(["nsIRequest"]);

/**
 * This function does all of the wrapping and error handling for an async
 * Bits Service method. This allows the implementations for those methods to
 * simply call this function with a closure that executes appropriate
 * nsIBits method.
 *
 * Specifically, this function takes an nsBitsErrorAction, an observer and a
 * function.
 * The nsBitsErrorAction will be used when constructing a BitsError, if the
 * wrapper encounters an error.
 * The observer should be the one that the caller passed to the Bits Interface
 * method. It will be wrapped so that its methods are passed a BitsRequest
 * rather than an nsIBitsRequest.
 * The function will be passed the callback function and the wrapped observer,
 * both of which should be passed to the nsIBitsRequest method.
 */
async function servicePromise(errorAction, observer, actionFn) {
  return new Promise((resolve, reject) => {
    if (!observer) {
      let error = new BitsError(
        lazy.gBits.ERROR_TYPE_NULL_ARGUMENT,
        errorAction,
        lazy.gBits.ERROR_STAGE_PRETASK,
        lazy.gBits.ERROR_CODE_TYPE_NONE
      );
      reject(error);
      return;
    }
    try {
      observer.QueryInterface(Ci.nsIRequestObserver);
    } catch (e) {
      let error = new BitsError(
        lazy.gBits.ERROR_TYPE_INVALID_ARGUMENT,
        errorAction,
        lazy.gBits.ERROR_STAGE_PRETASK,
        lazy.gBits.ERROR_CODE_TYPE_EXCEPTION,
        e
      );
      reject(error);
      return;
    }
    let isProgressEventSink = false;
    try {
      observer.QueryInterface(Ci.nsIProgressEventSink);
      isProgressEventSink = true;
    } catch (e) {}

    // Check if we are not late in creating new requests.
    if (
      Services &&
      Services.startup &&
      Services.startup.isInOrBeyondShutdownPhase(
        Ci.nsIAppStartup.SHUTDOWN_PHASE_APPSHUTDOWNCONFIRMED
      )
    ) {
      let error = new BitsError(
        lazy.gBits.ERROR_TYPE_BROWSER_SHUTTING_DOWN,
        errorAction,
        lazy.gBits.ERROR_STAGE_PRETASK,
        lazy.gBits.ERROR_CODE_TYPE_NONE
      );
      reject(error);
      return;
    }

    // This will be set to the BitsRequest (wrapping the nsIBitsRequest), once
    // it is available. This prevents a new wrapper from having to be made every
    // time an observer function is called.
    let wrappedRequest;

    let wrappedObserver = {
      onStartRequest: function wrappedObserver_onStartRequest(request) {
        if (!wrappedRequest) {
          wrappedRequest = new BitsRequest(request);
        }
        observer.onStartRequest(wrappedRequest);
      },
      onStopRequest: function wrappedObserver_onStopRequest(request, status) {
        if (!wrappedRequest) {
          wrappedRequest = new BitsRequest(request);
        }
        observer.onStopRequest(wrappedRequest, status);
      },
      onProgress: function wrappedObserver_onProgress(
        request,
        progress,
        progressMax
      ) {
        if (isProgressEventSink) {
          if (!wrappedRequest) {
            wrappedRequest = new BitsRequest(request);
          }
          observer.onProgress(wrappedRequest, progress, progressMax);
        }
      },
      onStatus: function wrappedObserver_onStatus(request, status, statusArg) {
        if (isProgressEventSink) {
          if (!wrappedRequest) {
            wrappedRequest = new BitsRequest(request);
          }
          observer.onStatus(wrappedRequest, status, statusArg);
        }
      },
      QueryInterface: ChromeUtils.generateQI([
        "nsIRequestObserver",
        "nsIProgressEventSink",
      ]),
    };

    let timer = makeTimeout(reject, errorAction);
    let callback = {
      QueryInterface: ChromeUtils.generateQI(["nsIBitsNewRequestCallback"]),
      success(request) {
        timer.cancel();
        if (!wrappedRequest) {
          wrappedRequest = new BitsRequest(request);
        }
        resolve(wrappedRequest);
      },
      failure(type, action, stage) {
        timer.cancel();
        let error = new BitsError(
          type,
          action,
          stage,
          lazy.gBits.ERROR_CODE_TYPE_NONE
        );
        reject(error);
      },
      failureNsresult(type, action, stage, code) {
        timer.cancel();
        let error = new BitsError(
          type,
          action,
          stage,
          lazy.gBits.ERROR_CODE_TYPE_NSRESULT,
          code
        );
        reject(error);
      },
      failureHresult(type, action, stage, code) {
        timer.cancel();
        let error = new BitsError(
          type,
          action,
          stage,
          lazy.gBits.ERROR_CODE_TYPE_HRESULT,
          code
        );
        reject(error);
      },
      failureString(type, action, stage, message) {
        timer.cancel();
        let error = new BitsError(
          type,
          action,
          stage,
          lazy.gBits.ERROR_CODE_TYPE_STRING,
          message
        );
        reject(error);
      },
    };

    try {
      actionFn(wrappedObserver, callback);
    } catch (e) {
      let error = new BitsError(
        lazy.gBits.ERROR_TYPE_METHOD_THREW,
        errorAction,
        lazy.gBits.ERROR_STAGE_PRETASK,
        lazy.gBits.ERROR_CODE_TYPE_EXCEPTION,
        e
      );
      reject(error);
    }
  });
}

export var Bits = {
  /**
   * This function wraps nsIBits::initialized.
   */
  get initialized() {
    return lazy.gBits.initialized;
  },

  /**
   * This function wraps nsIBits::init.
   */
  init(jobName, savePathPrefix, monitorTimeoutMs) {
    return lazy.gBits.init(jobName, savePathPrefix, monitorTimeoutMs);
  },

  /**
   * This function wraps nsIBits::startDownload.
   *
   * Instead of taking a callback, the function is asynchronous.
   * This method either resolves with a BitsRequest (which is also an
   * nsIRequest), or rejects with a BitsError.
   */
  async startDownload(
    downloadURL,
    saveRelPath,
    proxy,
    noProgressTimeoutSecs,
    monitorIntervalMs,
    observer,
    context
  ) {
    let action = lazy.gBits.ERROR_ACTION_START_DOWNLOAD;
    return servicePromise(action, observer, (wrappedObserver, callback) => {
      lazy.gBits.startDownload(
        downloadURL,
        saveRelPath,
        proxy,
        noProgressTimeoutSecs,
        monitorIntervalMs,
        wrappedObserver,
        context,
        callback
      );
    });
  },

  /**
   * This function wraps nsIBits::monitorDownload.
   *
   * Instead of taking a callback, the function is asynchronous.
   * This method either resolves with a BitsRequest (which is also an
   * nsIRequest), or rejects with a BitsError.
   */
  async monitorDownload(id, monitorIntervalMs, observer, context) {
    let action = lazy.gBits.ERROR_ACTION_MONITOR_DOWNLOAD;
    return servicePromise(action, observer, (wrappedObserver, callback) => {
      lazy.gBits.monitorDownload(
        id,
        monitorIntervalMs,
        wrappedObserver,
        context,
        callback
      );
    });
  },
};
PK
!<�%|ƾƾmodules/Blocklist.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint "valid-jsdoc": [2, {requireReturn: false}] */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs",
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
  jexlFilterFunc: "resource://services-settings/remote-settings.sys.mjs",
});

const CascadeFilter = Components.Constructor(
  "@mozilla.org/cascade-filter;1",
  "nsICascadeFilter",
  "setFilterData"
);

// The whole ID should be surrounded by literal ().
// IDs may contain alphanumerics, _, -, {}, @ and a literal '.'
// They may also contain backslashes (needed to escape the {} and dot)
// We filter out backslash escape sequences (like `\w`) separately
// (see kEscapeSequences).
const kIdSubRegex =
  "\\([" +
  "\\\\" + // note: just a backslash, but between regex and string it needs escaping.
  "\\w .{}@-]+\\)";

// prettier-ignore
// Find regular expressions of the form:
// /^((id1)|(id2)|(id3)|...|(idN))$/
// The outer set of parens enclosing the entire list of IDs is optional.
const kIsMultipleIds = new RegExp(
  // Start with literal sequence /^(
  //  (the `(` is optional)
  "^/\\^\\(?" +
    // Then at least one ID in parens ().
    kIdSubRegex +
    // Followed by any number of IDs in () separated by pipes.
    // Note: using a non-capturing group because we don't care about the value.
    "(?:\\|" + kIdSubRegex + ")*" +
  // Finally, we need to end with literal sequence )$/
  //  (the leading `)` is optional like at the start)
  "\\)?\\$/$"
);

// Check for a backslash followed by anything other than a literal . or curlies
const kEscapeSequences = /\\[^.{}]/;

// Used to remove the following 3 things:
// leading literal /^(
//    plus an optional (
// any backslash
// trailing literal )$/
//    plus an optional ) before the )$/
const kRegExpRemovalRegExp = /^\/\^\(\(?|\\|\)\)?\$\/$/g;

// In order to block add-ons, their type should be part of this list. There
// may be additional requirements such as requiring the add-on to be signed.
// See the uses of kXPIAddonTypes before introducing new addon types or
// providers that differ from the existing types.
ChromeUtils.defineLazyGetter(lazy, "kXPIAddonTypes", () => {
  // In practice, this result is equivalent to ALL_XPI_TYPES in
  // XPIProvider.sys.mjs.
  // "plugin" (from GMPProvider.sys.mjs) is intentionally omitted, as we decided to
  // not support blocklisting of GMP plugins in bug 1086668.
  return lazy.AddonManagerPrivate.getAddonTypesByProvider("XPIProvider");
});

// For a given input string matcher, produce either a string to compare with,
// a regular expression, or a set of strings to compare with.
function processMatcher(str) {
  if (!str.startsWith("/")) {
    return str;
  }
  // Process regexes matching multiple IDs into a set.
  if (kIsMultipleIds.test(str) && !kEscapeSequences.test(str)) {
    // Remove the regexp gunk at the start and end of the string, as well
    // as all backslashes, and split by )|( to leave the list of IDs.
    return new Set(str.replace(kRegExpRemovalRegExp, "").split(")|("));
  }
  let lastSlash = str.lastIndexOf("/");
  let pattern = str.slice(1, lastSlash);
  let flags = str.slice(lastSlash + 1);
  return new RegExp(pattern, flags);
}

// Returns true if the addonProps object passes the constraints set by matches.
// (For every non-null property in matches, the same key must exist in
// addonProps and its value must match)
function doesAddonEntryMatch(matches, addonProps) {
  for (let [key, value] of Object.entries(matches)) {
    if (value === null || value === undefined) {
      continue;
    }
    if (addonProps[key]) {
      // If this property matches (member of the set, matches regex, or
      // an exact string match), continue to look at the other properties of
      // the `matches` object.
      // If not, return false immediately.
      if (value.has && value.has(addonProps[key])) {
        continue;
      }
      if (value.test && value.test(addonProps[key])) {
        continue;
      }
      if (typeof value == "string" && value === addonProps[key]) {
        continue;
      }
    }
    // If we get here, the property doesn't match, so this entry doesn't match.
    return false;
  }
  // If we get here, all the properties must have matched.
  return true;
}

const TOOLKIT_ID = "toolkit@mozilla.org";
const PREF_BLOCKLIST_ITEM_URL = "extensions.blocklist.itemURL";
const PREF_BLOCKLIST_ADDONITEM_URL = "extensions.blocklist.addonItemURL";
const PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled";
const PREF_BLOCKLIST_LEVEL = "extensions.blocklist.level";
const PREF_BLOCKLIST_USE_MLBF = "extensions.blocklist.useMLBF";
const PREF_EM_LOGGING_ENABLED = "extensions.logging.enabled";
const DEFAULT_SEVERITY = 3;
const DEFAULT_LEVEL = 2;
const MAX_BLOCK_LEVEL = 3;

const BLOCKLIST_BUCKET = "blocklists";

const BlocklistTelemetry = {
  init() {
    // Used by BlocklistTelemetry.recordAddonBlockChangeTelemetry.
    Services.telemetry.setEventRecordingEnabled("blocklist", true);
  },

  /**
   * Record the RemoteSettings Blocklist lastModified server time into the
   * "blocklist.lastModified_rs keyed scalar (or "Missing Date" when unable
   * to retrieve a valid timestamp).
   *
   * @param {string} blocklistType
   *        The blocklist type that has been updated ("addons" or "addons_mlbf");
   *        the "gfx" blocklist is not covered by this telemetry).
   * @param {RemoteSettingsClient} remoteSettingsClient
   *        The RemoteSettings client to retrieve the lastModified timestamp from.
   */
  async recordRSBlocklistLastModified(blocklistType, remoteSettingsClient) {
    // In some tests overrides ensureInitialized and remoteSettingsClient
    // can be undefined, and in that case we don't want to record any
    // telemetry scalar.
    if (!remoteSettingsClient) {
      return;
    }

    let lastModified = await remoteSettingsClient.getLastModified();
    if (blocklistType === "addons_mlbf") {
      BlocklistTelemetry.recordTimeScalar(
        "lastModified_rs_" + blocklistType,
        lastModified
      );
      BlocklistTelemetry.recordGleanDateTime(
        Glean.blocklist.lastModifiedRsAddonsMblf,
        lastModified
      );
    }
  },

  /**
   * Record a timestamp in telemetry as a UTC string or "Missing Date" if the
   * input is not a valid timestamp.
   *
   * @param {string} telemetryKey
   *        The part of after "blocklist.", as defined in Scalars.yaml.
   * @param {number} time
   *        A timestamp to record. If invalid, "Missing Date" will be recorded.
   */
  recordTimeScalar(telemetryKey, time) {
    if (time > 0) {
      // convert from timestamp in ms into UTC datetime string, so it is going
      // to be record in the same format previously used by blocklist.lastModified_xml.
      let dateString = new Date(time).toUTCString();
      Services.telemetry.scalarSet("blocklist." + telemetryKey, dateString);
    } else {
      Services.telemetry.scalarSet("blocklist." + telemetryKey, "Missing Date");
    }
  },

  /**
   * Records a glean datetime if time is > than 0, otherwise 0 is submitted.
   *
   * @param {nsIGleanDatetime} gleanTelemetry
   *        A glean telemetry datetime object.
   * @param {number} time
   *        A timestamp to record.
   */
  recordGleanDateTime(gleanTelemetry, time) {
    if (time > 0) {
      // Glean date times are provided in nanoseconds, `getTime()` yields
      // milliseconds (after the Unix epoch).
      gleanTelemetry.set(time * 1000);
    } else {
      gleanTelemetry.set(0);
    }
  },

  /**
   * Record whether an add-on is blocked and the parameters that guided the
   * decision to block or unblock the add-on.
   *
   * @param {AddonWrapper|object} addon
   *        The blocked or unblocked add-on. Not necessarily installed.
   *        Could be an object with the id, version and blocklistState
   *        properties when the AddonWrapper is not available (e.g. during
   *        update checks).
   * @param {string} reason
   *        The reason for recording the event,
   *        "addon_install", "addon_update", "addon_update_check",
   *        "addon_db_modified", "blocklist_update".
   */
  recordAddonBlockChangeTelemetry(addon, reason) {
    // Reduce the timer resolution for anonymity.
    let hoursSinceInstall = -1;
    if (reason === "blocklist_update" || reason === "addon_db_modified") {
      hoursSinceInstall = Math.round(
        (Date.now() - addon.installDate.getTime()) / 3600000
      );
    }

    const value = addon.id;
    const extra = {
      blocklistState: `${addon.blocklistState}`,
      addon_version: addon.version,
      signed_date: `${addon.signedDate?.getTime() || 0}`,
      hours_since: `${hoursSinceInstall}`,

      ...ExtensionBlocklistMLBF.getBlocklistMetadataForTelemetry(),
    };
    Glean.blocklist.addonBlockChange.record({
      value,
      object: reason,
      blocklist_state: extra.blocklistState,
      addon_version: extra.addon_version,
      signed_date: extra.signed_date,
      hours_since: extra.hours_since,
      mlbf_last_time: extra.mlbf_last_time,
      mlbf_generation: extra.mlbf_generation,
      mlbf_source: extra.mlbf_source,
    });

    Services.telemetry.recordEvent(
      "blocklist",
      "addonBlockChange",
      reason,
      value,
      extra
    );
  },
};

const Utils = {
  /**
   * Checks whether this entry is valid for the current OS and ABI.
   * If the entry has an "os" property then the current OS must appear in
   * its comma separated list for it to be valid. Similarly for the
   * xpcomabi property.
   *
   * @param {Object} item
   *        The blocklist item.
   * @returns {bool}
   *        Whether the entry matches the current OS.
   */
  matchesOSABI(item) {
    if (item.os) {
      let os = item.os.split(",");
      if (!os.includes(lazy.gAppOS)) {
        return false;
      }
    }

    if (item.xpcomabi) {
      let xpcomabi = item.xpcomabi.split(",");
      if (!xpcomabi.includes(lazy.gApp.XPCOMABI)) {
        return false;
      }
    }
    return true;
  },

  /**
   * Checks if a version is higher than or equal to the minVersion (if provided)
   * and lower than or equal to the maxVersion (if provided).
   * @param {string} version
   *        The version to test.
   * @param {string?} minVersion
   *        The minimum version. If null it is assumed that version is always
   *        larger.
   * @param {string?} maxVersion
   *        The maximum version. If null it is assumed that version is always
   *        smaller.
   * @returns {boolean}
   *        Whether the item matches the range.
   */
  versionInRange(version, minVersion, maxVersion) {
    if (minVersion && Services.vc.compare(version, minVersion) < 0) {
      return false;
    }
    if (maxVersion && Services.vc.compare(version, maxVersion) > 0) {
      return false;
    }
    return true;
  },

  /**
   * Tests if this versionRange matches the item specified, and has a matching
   * targetApplication id and version.
   * @param {Object} versionRange
   *        The versionRange to check against
   * @param {string} itemVersion
   *        The version of the actual addon/plugin to test for.
   * @param {string} appVersion
   *        The version of the application to test for.
   * @param {string} toolkitVersion
   *        The version of toolkit to check for.
   * @returns {boolean}
   *        True if this version range covers the item and app/toolkit version given.
   */
  versionsMatch(versionRange, itemVersion, appVersion, toolkitVersion) {
    // Some platforms have no version for plugins, these don't match if there
    // was a min/maxVersion provided
    if (!itemVersion && (versionRange.minVersion || versionRange.maxVersion)) {
      return false;
    }

    // Check if the item version matches
    if (
      !this.versionInRange(
        itemVersion,
        versionRange.minVersion,
        versionRange.maxVersion
      )
    ) {
      return false;
    }

    // Check if the application or toolkit version matches
    for (let tA of versionRange.targetApplication) {
      if (
        tA.guid == lazy.gAppID &&
        this.versionInRange(appVersion, tA.minVersion, tA.maxVersion)
      ) {
        return true;
      }
      if (
        tA.guid == TOOLKIT_ID &&
        this.versionInRange(toolkitVersion, tA.minVersion, tA.maxVersion)
      ) {
        return true;
      }
    }
    return false;
  },

  /**
   * Given a blocklist JS object entry, ensure it has a versionRange property, where
   * each versionRange property has a valid severity property
   * and at least 1 valid targetApplication.
   * If it didn't have a valid targetApplication array before and/or it was empty,
   * fill it with an entry with null min/maxVersion properties, which will match
   * every version.
   *
   * If there *are* targetApplications, if any of them don't have a guid property,
   * assign them the current app's guid.
   *
   * @param {Object} entry
   *                 blocklist entry object.
   */
  ensureVersionRangeIsSane(entry) {
    if (!entry.versionRange.length) {
      entry.versionRange.push({});
    }
    for (let vr of entry.versionRange) {
      if (!vr.hasOwnProperty("severity")) {
        vr.severity = DEFAULT_SEVERITY;
      }
      if (!Array.isArray(vr.targetApplication)) {
        vr.targetApplication = [];
      }
      if (!vr.targetApplication.length) {
        vr.targetApplication.push({ minVersion: null, maxVersion: null });
      }
      vr.targetApplication.forEach(tA => {
        if (!tA.guid) {
          tA.guid = lazy.gAppID;
        }
      });
    }
  },

  /**
   * Create a blocklist URL for the given blockID
   * @param {String} id the blockID to use
   * @returns {String} the blocklist URL.
   */
  _createBlocklistURL(id) {
    let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL);
    return url.replace(/%blockID%/g, id);
  },
};

/**
 * This custom filter function is used to limit the entries returned
 * by `RemoteSettings("...").get()` depending on the target app information
 * defined on entries.
 *
 * Note that this is async because `jexlFilterFunc` is async.
 *
 * @param {Object} entry a Remote Settings record
 * @param {Object} environment the JEXL environment object.
 * @returns {Object} The entry if it matches, `null` otherwise.
 */
async function targetAppFilter(entry, environment) {
  // If the entry has a JEXL filter expression, it should prevail.
  // The legacy target app mechanism will be kept in place for old entries.
  // See https://bugzilla.mozilla.org/show_bug.cgi?id=1463377
  const { filter_expression } = entry;
  if (filter_expression) {
    return lazy.jexlFilterFunc(entry, environment);
  }

  // Keep entries without target information.
  if (!("versionRange" in entry)) {
    return entry;
  }

  const { versionRange } = entry;

  // Everywhere in this method, we avoid checking the minVersion, because
  // we want to retain items whose minVersion is higher than the current
  // app version, so that we have the items around for app updates.

  // Gfx blocklist has a specific versionRange object, which is not a list.
  if (!Array.isArray(versionRange)) {
    const { maxVersion = "*" } = versionRange;
    const matchesRange =
      Services.vc.compare(lazy.gApp.version, maxVersion) <= 0;
    return matchesRange ? entry : null;
  }

  // Iterate the targeted applications, at least one of them must match.
  // If no target application, keep the entry.
  if (!versionRange.length) {
    return entry;
  }
  for (const vr of versionRange) {
    const { targetApplication = [] } = vr;
    if (!targetApplication.length) {
      return entry;
    }
    for (const ta of targetApplication) {
      const { guid } = ta;
      if (!guid) {
        return entry;
      }
      const { maxVersion = "*" } = ta;
      if (
        guid == lazy.gAppID &&
        Services.vc.compare(lazy.gApp.version, maxVersion) <= 0
      ) {
        return entry;
      }
      if (
        guid == "toolkit@mozilla.org" &&
        Services.vc.compare(Services.appinfo.platformVersion, maxVersion) <= 0
      ) {
        return entry;
      }
    }
  }
  // Skip this entry.
  return null;
}

/**
 * The Graphics blocklist implementation. The JSON objects for graphics blocks look
 * something like:
 *
 * {
 *  "blockID": "g35",
 *  "os": "WINNT 6.1",
 *  "vendor": "0xabcd",
 *  "devices": [
 *    "0x2783",
 *    "0x1234",
 *  ],
 *  "feature": " DIRECT2D ",
 *  "featureStatus": " BLOCKED_DRIVER_VERSION ",
 *  "driverVersion": " 8.52.322.2202 ",
 *  "driverVersionComparator": " LESS_THAN ",
 *  "versionRange": {"minVersion": "5.0", "maxVersion: "25.0"},
 * }
 *
 * The RemoteSetttings client takes care of filtering out versions that don't apply.
 * The code here stores entries in memory and sends them to the gfx component in
 * serialized text form, using ',', '\t' and '\n' as separators.
 */
const GfxBlocklistRS = {
  _ensureInitialized() {
    if (this._initialized || !gBlocklistEnabled) {
      return;
    }
    this._initialized = true;
    this._client = lazy.RemoteSettings("gfx", {
      bucketName: BLOCKLIST_BUCKET,
      filterFunc: targetAppFilter,
    });
    this.checkForEntries = this.checkForEntries.bind(this);
    this._client.on("sync", this.checkForEntries);
  },

  shutdown() {
    if (this._client) {
      this._client.off("sync", this.checkForEntries);
    }
  },

  sync() {
    this._ensureInitialized();
    return this._client.sync();
  },

  async checkForEntries() {
    this._ensureInitialized();
    if (!gBlocklistEnabled) {
      return []; // return value expected by tests.
    }
    let entries = await this._client.get().catch(ex => Cu.reportError(ex));
    // Handle error silently. This can happen if our request to fetch data is aborted,
    // e.g. by application shutdown.
    if (!entries) {
      return [];
    }
    // Trim helper (spaces, tabs, no-break spaces..)
    const trim = s =>
      (s || "").replace(/(^[\s\uFEFF\xA0]+)|([\s\uFEFF\xA0]+$)/g, "");

    entries = entries.map(entry => {
      let props = [
        "blockID",
        "driverVersion",
        "driverVersionMax",
        "driverVersionComparator",
        "feature",
        "featureStatus",
        "os",
        "vendor",
        "devices",
      ];
      let rv = {};
      for (let p of props) {
        let val = entry[p];
        // Ignore falsy values or empty arrays.
        if (!val || (Array.isArray(val) && !val.length)) {
          continue;
        }
        if (typeof val == "string") {
          val = trim(val);
        } else if (p == "devices") {
          let invalidDevices = [];
          let validDevices = [];
          // We serialize the array of devices as a comma-separated string, so
          // we need to ensure that none of the entries contain commas, also in
          // the future.
          val.forEach(v =>
            v.includes(",") ? invalidDevices.push(v) : validDevices.push(v)
          );
          for (let dev of invalidDevices) {
            const e = new Error(
              `Block ${entry.blockID} contains unsupported device: ${dev}`
            );
            Cu.reportError(e);
          }
          if (!validDevices) {
            continue;
          }
          val = validDevices;
        }
        rv[p] = val;
      }
      if (entry.versionRange) {
        rv.versionRange = {
          minVersion: trim(entry.versionRange.minVersion) || "0",
          maxVersion: trim(entry.versionRange.maxVersion) || "*",
        };
      }
      return rv;
    });
    if (entries.length) {
      let sortedProps = [
        "blockID",
        "devices",
        "driverVersion",
        "driverVersionComparator",
        "driverVersionMax",
        "feature",
        "featureStatus",
        "hardware",
        "manufacturer",
        "model",
        "os",
        "osversion",
        "product",
        "vendor",
        "versionRange",
      ];
      // Notify `GfxInfoBase`, by passing a string serialization.
      let payload = [];
      for (let gfxEntry of entries) {
        let entryLines = [];
        for (let key of sortedProps) {
          if (gfxEntry[key]) {
            let value = gfxEntry[key];
            if (Array.isArray(value)) {
              value = value.join(",");
            } else if (value.maxVersion) {
              // Both minVersion and maxVersion are always set on each entry.
              value = value.minVersion + "," + value.maxVersion;
            }
            entryLines.push(key + ":" + value);
          }
        }
        payload.push(entryLines.join("\t"));
      }
      Services.obs.notifyObservers(
        null,
        "blocklist-data-gfxItems",
        payload.join("\n")
      );
    }
    // The return value is only used by tests.
    return entries;
  },
};

/**
 * The extensions blocklist implementation. The JSON objects for extension
 * blocks look something like:
 *
 * {
 *   "guid": "someguid@addons.mozilla.org",
 *   "prefs": ["i.am.a.pref.that.needs.resetting"],
 *   "schema": 1480349193877,
 *   "blockID": "i12345",
 *   "details": {
 *     "bug": "https://bugzilla.mozilla.org/show_bug.cgi?id=1234567",
 *     "who": "All Firefox users who have this add-on installed. If you wish to continue using this add-on, you can enable it in the Add-ons Manager.",
 *     "why": "This add-on is in violation of the <a href=\"https://developer.mozilla.org/en-US/Add-ons/Add-on_guidelines\">Add-on Guidelines</a>, using multiple add-on IDs and potentially doing other unwanted activities.",
 *     "name": "Some pretty name",
 *     "created": "2019-05-06T19:52:20Z"
 *   },
 *   "enabled": true,
 *   "versionRange": [
 *     {
 *       "severity": 1,
 *       "maxVersion": "*",
 *       "minVersion": "0",
 *       "targetApplication": []
 *     }
 *   ],
 *   "id": "<unique guid>",
 *   "last_modified": 1480349215672,
 * }
 *
 * This is a legacy format, and implements deprecated operations (bug 1620580).
 * ExtensionBlocklistMLBF supersedes this implementation.
 */
const ExtensionBlocklistRS = {
  async _ensureEntries() {
    this.ensureInitialized();
    if (!this._entries && gBlocklistEnabled) {
      await this._updateEntries();
    }
  },

  async _updateEntries() {
    if (!gBlocklistEnabled) {
      this._entries = [];
      return;
    }
    this._entries = await this._client.get().catch(ex => Cu.reportError(ex));
    // Handle error silently. This can happen if our request to fetch data is aborted,
    // e.g. by application shutdown.
    if (!this._entries) {
      this._entries = [];
      return;
    }
    this._entries.forEach(entry => {
      entry.matches = {};
      if (entry.guid) {
        entry.matches.id = processMatcher(entry.guid);
      }
      for (let key of EXTENSION_BLOCK_FILTERS) {
        if (key == "id" || !entry[key]) {
          continue;
        }
        entry.matches[key] = processMatcher(entry[key]);
      }
      Utils.ensureVersionRangeIsSane(entry);
    });

    BlocklistTelemetry.recordRSBlocklistLastModified("addons", this._client);
  },

  async _filterItem(entry, environment) {
    if (!(await targetAppFilter(entry, environment))) {
      return null;
    }
    if (!Utils.matchesOSABI(entry)) {
      return null;
    }
    // Need something to filter on - at least a guid or name (either could be a regex):
    if (!entry.guid && !entry.name) {
      let blockID = entry.blockID || entry.id;
      Cu.reportError(new Error(`Nothing to filter add-on item ${blockID} on`));
      return null;
    }
    return entry;
  },

  sync() {
    this.ensureInitialized();
    return this._client.sync();
  },

  ensureInitialized() {
    if (!gBlocklistEnabled || this._initialized) {
      return;
    }
    this._initialized = true;
    this._client = lazy.RemoteSettings("addons", {
      bucketName: BLOCKLIST_BUCKET,
      filterFunc: this._filterItem,
    });
    this._onUpdate = this._onUpdate.bind(this);
    this._client.on("sync", this._onUpdate);
  },

  shutdown() {
    if (this._client) {
      this._client.off("sync", this._onUpdate);
      this._didShutdown = true;
    }
  },

  // Called when the blocklist implementation is changed via a pref.
  undoShutdown() {
    if (this._didShutdown) {
      this._client.on("sync", this._onUpdate);
      this._didShutdown = false;
    }
  },

  async _onUpdate() {
    let oldEntries = this._entries || [];
    await this.ensureInitialized();
    await this._updateEntries();

    let addons = await lazy.AddonManager.getAddonsByTypes(lazy.kXPIAddonTypes);
    for (let addon of addons) {
      let oldState = addon.blocklistState;
      if (addon.updateBlocklistState) {
        await addon.updateBlocklistState(false);
      } else if (oldEntries) {
        let oldEntry = this._getEntry(addon, oldEntries);
        oldState = oldEntry
          ? oldEntry.state
          : Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
      } else {
        oldState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
      }
      let state = addon.blocklistState;

      LOG(
        "Blocklist state for " +
          addon.id +
          " changed from " +
          oldState +
          " to " +
          state
      );

      // We don't want to re-warn about add-ons
      if (state == oldState) {
        continue;
      }

      // Ensure that softDisabled is false if the add-on is not soft blocked
      if (state != Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
        await addon.setSoftDisabled(false);
      }

      // If an add-on has dropped from hard to soft blocked just mark it as
      // soft disabled and don't warn about it.
      if (
        state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED &&
        oldState == Ci.nsIBlocklistService.STATE_BLOCKED
      ) {
        await addon.setSoftDisabled(true);
      }

      if (
        state == Ci.nsIBlocklistService.STATE_BLOCKED ||
        state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED
      ) {
        // Mark it as softblocked if necessary. Note that we avoid setting
        // softDisabled at the same time as userDisabled to make it clear
        // which was the original cause of the add-on becoming disabled in a
        // way that the user can change.
        if (
          state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED &&
          !addon.userDisabled
        ) {
          await addon.setSoftDisabled(true);
        }
        // It's a block. We must reset certain preferences.
        let entry = this._getEntry(addon, this._entries);
        if (entry.prefs && entry.prefs.length) {
          for (let pref of entry.prefs) {
            Services.prefs.clearUserPref(pref);
          }
        }
      }
    }

    lazy.AddonManagerPrivate.updateAddonAppDisabledStates();
  },

  async getState(addon, appVersion, toolkitVersion) {
    let entry = await this.getEntry(addon, appVersion, toolkitVersion);
    return entry ? entry.state : Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
  },

  async getEntry(addon, appVersion, toolkitVersion) {
    await this._ensureEntries();
    return this._getEntry(addon, this._entries, appVersion, toolkitVersion);
  },

  _getEntry(addon, addonEntries, appVersion, toolkitVersion) {
    if (!gBlocklistEnabled || !addon) {
      return null;
    }

    // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
    if (!appVersion && !lazy.gApp.version) {
      return null;
    }

    if (!appVersion) {
      appVersion = lazy.gApp.version;
    }
    if (!toolkitVersion) {
      toolkitVersion = lazy.gApp.platformVersion;
    }

    let addonProps = {};
    for (let key of EXTENSION_BLOCK_FILTERS) {
      addonProps[key] = addon[key];
    }
    if (addonProps.creator) {
      addonProps.creator = addonProps.creator.name;
    }

    for (let entry of addonEntries) {
      // First check if it matches our properties. If not, just skip to the next item.
      if (!doesAddonEntryMatch(entry.matches, addonProps)) {
        continue;
      }
      // If those match, check the app or toolkit version works:
      for (let versionRange of entry.versionRange) {
        if (
          Utils.versionsMatch(
            versionRange,
            addon.version,
            appVersion,
            toolkitVersion
          )
        ) {
          let blockID = entry.blockID || entry.id;
          return {
            state:
              versionRange.severity >= gBlocklistLevel
                ? Ci.nsIBlocklistService.STATE_BLOCKED
                : Ci.nsIBlocklistService.STATE_SOFTBLOCKED,
            url: Utils._createBlocklistURL(blockID),
            prefs: entry.prefs || [],
          };
        }
      }
    }
    return null;
  },
};

/**
 * The extensions blocklist implementation, the third version.
 *
 * The current blocklist is represented by a multi-level bloom filter (MLBF)
 * (aka "Cascade Bloom Filter") that works like a set, i.e. supports a has()
 * operation, except it is probabilistic. The MLBF is 100% accurate for known
 * entries and unreliable for unknown entries.  When the backend generates the
 * MLBF, all known add-ons are recorded, including their block state. Unknown
 * add-ons are identified by their signature date being newer than the MLBF's
 * generation time, and they are considered to not be blocked.
 *
 * Legacy blocklists used to distinguish between "soft block" and "hard block",
 * but the current blocklist only supports one type of block ("hard block").
 * After checking the blocklist states, any previous "soft blocked" addons will
 * either be (hard) blocked or unblocked based on the blocklist.
 *
 * The MLBF is attached to a RemoteSettings record, as follows:
 *
 * {
 *   "generation_time": 1585692000000,
 *   "attachment": { ... RemoteSettings attachment ... }
 *   "attachment_type": "bloomfilter-base",
 * }
 *
 * The collection can also contain stashes:
 *
 * {
 *   "stash_time": 1585692000001,
 *   "stash": {
 *     "blocked": [ "addonid:1.0", ... ],
 *     "unblocked": [ "addonid:1.0", ... ]
 *  }
 *
 * Stashes can be used to update the blocklist without forcing the whole MLBF
 * to be downloaded again. These stashes are applied on top of the base MLBF.
 */
const ExtensionBlocklistMLBF = {
  RS_ATTACHMENT_ID: "addons-mlbf.bin",

  async _fetchMLBF(record) {
    // |record| may be unset. In that case, the MLBF dump is used instead
    // (provided that the client has been built with it included).
    let hash = record?.attachment.hash;
    if (this._mlbfData && hash && this._mlbfData.cascadeHash === hash) {
      // MLBF not changed, save the efforts of downloading the data again.

      // Although the MLBF has not changed, the time in the record has. This
      // means that the MLBF is known to provide accurate results for add-ons
      // that were signed after the previously known date (but before the newly
      // given date). To ensure that add-ons in this time range are also blocked
      // as expected, update the cached generationTime.
      if (record.generation_time > this._mlbfData.generationTime) {
        this._mlbfData.generationTime = record.generation_time;
      }
      return this._mlbfData;
    }
    const {
      buffer,
      record: actualRecord,
      _source: rsAttachmentSource,
    } = await this._client.attachments.download(record, {
      attachmentId: this.RS_ATTACHMENT_ID,
      fallbackToCache: true,
      fallbackToDump: true,
    });
    return {
      cascadeHash: actualRecord.attachment.hash,
      cascadeFilter: new CascadeFilter(new Uint8Array(buffer)),
      // Note: generation_time is semantically distinct from last_modified.
      // generation_time is compared with the signing date of the add-on, so it
      // should be in sync with the signing service's clock.
      // In contrast, last_modified does not have such strong requirements.
      generationTime: actualRecord.generation_time,
      // Used for telemetry.
      rsAttachmentSource,
    };
  },

  async _updateMLBF(forceUpdate = false) {
    // The update process consists of fetching the collection, followed by
    // potentially multiple network requests. As long as the collection has not
    // been changed, repeated update requests can be coalesced. But when the
    // collection has been updated, all pending update requests should await the
    // new update request instead of the previous one.
    if (!forceUpdate && this._updatePromise) {
      return this._updatePromise;
    }
    const isUpdateReplaced = () => this._updatePromise != updatePromise;
    const updatePromise = (async () => {
      if (!gBlocklistEnabled) {
        this._mlbfData = null;
        this._stashes = null;
        return;
      }
      let records = await this._client.get();
      if (isUpdateReplaced()) {
        return;
      }

      let mlbfRecords = records
        .filter(r => r.attachment)
        // Newest attachments first.
        .sort((a, b) => b.generation_time - a.generation_time);
      const mlbfRecord = mlbfRecords.find(
        r => r.attachment_type == "bloomfilter-base"
      );
      this._stashes = records
        .filter(({ stash }) => {
          return (
            // Exclude non-stashes, e.g. MLBF attachments.
            stash &&
            // Sanity check for type.
            Array.isArray(stash.blocked) &&
            Array.isArray(stash.unblocked)
          );
        })
        // Sort by stash time - newest first.
        .sort((a, b) => b.stash_time - a.stash_time)
        .map(({ stash, stash_time }) => ({
          blocked: new Set(stash.blocked),
          unblocked: new Set(stash.unblocked),
          stash_time,
        }));

      let mlbf = await this._fetchMLBF(mlbfRecord);
      // When a MLBF dump is packaged with the browser, mlbf will always be
      // non-null at this point.
      if (isUpdateReplaced()) {
        return;
      }
      this._mlbfData = mlbf;
    })()
      .catch(e => {
        Cu.reportError(e);
      })
      .then(() => {
        if (!isUpdateReplaced()) {
          this._updatePromise = null;
          this._recordPostUpdateTelemetry();
        }
        return this._updatePromise;
      });
    this._updatePromise = updatePromise;
    return updatePromise;
  },

  // Update the telemetry of the blocklist. This is always called, even if
  // the update request failed (e.g. due to network errors or data corruption).
  _recordPostUpdateTelemetry() {
    BlocklistTelemetry.recordRSBlocklistLastModified(
      "addons_mlbf",
      this._client
    );
    Glean.blocklist.mlbfSource.set(
      this._mlbfData?.rsAttachmentSource || "unknown"
    );
    BlocklistTelemetry.recordTimeScalar(
      "mlbf_generation_time",
      this._mlbfData?.generationTime
    );
    BlocklistTelemetry.recordGleanDateTime(
      Glean.blocklist.mlbfGenerationTime,
      this._mlbfData?.generationTime
    );
    // stashes has conveniently already been sorted by stash_time, newest first.
    let stashes = this._stashes || [];
    BlocklistTelemetry.recordTimeScalar(
      "mlbf_stash_time_oldest",
      stashes[stashes.length - 1]?.stash_time
    );
    BlocklistTelemetry.recordTimeScalar(
      "mlbf_stash_time_newest",
      stashes[0]?.stash_time
    );
    BlocklistTelemetry.recordGleanDateTime(
      Glean.blocklist.mlbfStashTimeOldest,
      stashes[stashes.length - 1]?.stash_time
    );

    BlocklistTelemetry.recordGleanDateTime(
      Glean.blocklist.mlbfStashTimeNewest,
      stashes[0]?.stash_time
    );
  },

  // Used by BlocklistTelemetry.recordAddonBlockChangeTelemetry.
  getBlocklistMetadataForTelemetry() {
    // Blocklist telemetry can only be reported when a blocklist decision
    // has been made. That implies that the blocklist has been loaded, so
    // ExtensionBlocklistMLBF should have been initialized.
    // (except when the blocklist is disabled, or blocklist v2 is used)
    const generationTime = this._mlbfData?.generationTime ?? 0;

    // Keys to include in the blocklist.addonBlockChange telemetry event.
    return {
      mlbf_last_time:
        // stashes are sorted, newest first. Stashes are newer than the MLBF.
        `${this._stashes?.[0]?.stash_time ?? generationTime}`,
      mlbf_generation: `${generationTime}`,
      mlbf_source: this._mlbfData?.rsAttachmentSource ?? "unknown",
    };
  },

  ensureInitialized() {
    if (!gBlocklistEnabled || this._initialized) {
      return;
    }
    this._initialized = true;
    this._client = lazy.RemoteSettings("addons-bloomfilters", {
      bucketName: BLOCKLIST_BUCKET,
      // Prevent the attachment for being pruned, since its ID does
      // not match any record.
      keepAttachmentsIds: [this.RS_ATTACHMENT_ID],
    });
    this._onUpdate = this._onUpdate.bind(this);
    this._client.on("sync", this._onUpdate);
  },

  shutdown() {
    if (this._client) {
      this._client.off("sync", this._onUpdate);
      this._didShutdown = true;
    }
  },

  // Called when the blocklist implementation is changed via a pref.
  undoShutdown() {
    if (this._didShutdown) {
      this._client.on("sync", this._onUpdate);
      this._didShutdown = false;
    }
  },

  async _onUpdate() {
    this.ensureInitialized();
    await this._updateMLBF(true);

    let addons = await lazy.AddonManager.getAddonsByTypes(lazy.kXPIAddonTypes);
    for (let addon of addons) {
      let oldState = addon.blocklistState;
      await addon.updateBlocklistState(false);
      let state = addon.blocklistState;

      LOG(
        "Blocklist state for " +
          addon.id +
          " changed from " +
          oldState +
          " to " +
          state
      );

      // We don't want to re-warn about add-ons
      if (state == oldState) {
        continue;
      }

      // Ensure that softDisabled is false if the add-on is not soft blocked
      // (by a previous implementation of the blocklist).
      if (state != Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
        await addon.setSoftDisabled(false);
      }

      BlocklistTelemetry.recordAddonBlockChangeTelemetry(
        addon,
        "blocklist_update"
      );
    }

    lazy.AddonManagerPrivate.updateAddonAppDisabledStates();
  },

  async getState(addon) {
    let state = await this.getEntry(addon);
    return state ? state.state : Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
  },

  async getEntry(addon) {
    if (!this._stashes) {
      this.ensureInitialized();
      await this._updateMLBF(false);
    } else if (this._updatePromise) {
      // _stashes has been initialized, but the initialization of _mlbfData is
      // still pending.
      await this._updatePromise;
    }

    let blockKey = addon.id + ":" + addon.version;

    // _stashes will be unset if !gBlocklistEnabled.
    if (this._stashes) {
      // Stashes are ordered by newest first.
      for (let stash of this._stashes) {
        // blocked and unblocked do not have overlapping entries.
        if (stash.blocked.has(blockKey)) {
          return this._createBlockEntry(addon);
        }
        if (stash.unblocked.has(blockKey)) {
          return null;
        }
      }
    }

    // signedDate is a Date if the add-on is signed, null if not signed,
    // undefined if it's an addon update descriptor instead of an addon wrapper.
    let { signedDate } = addon;
    if (!signedDate) {
      // The MLBF does not apply to unsigned add-ons.
      return null;
    }

    if (!this._mlbfData) {
      // This could happen in theory in any of the following cases:
      // - the blocklist is disabled.
      // - The RemoteSettings backend served a malformed MLBF.
      // - The RemoteSettings backend is unreachable, and this client was built
      //   without including a dump of the MLBF.
      //
      // ... in other words, this is unlikely to happen in practice.
      return null;
    }
    let { cascadeFilter, generationTime } = this._mlbfData;
    if (!cascadeFilter.has(blockKey)) {
      // Add-on not blocked or unknown.
      return null;
    }
    // Add-on blocked, or unknown add-on inadvertently labeled as blocked.

    let { signedState } = addon;
    if (
      signedState !== lazy.AddonManager.SIGNEDSTATE_PRELIMINARY &&
      signedState !== lazy.AddonManager.SIGNEDSTATE_SIGNED
    ) {
      // The block decision can only be relied upon for known add-ons, i.e.
      // signed via AMO. Anything else is unknown and ignored:
      //
      // - SIGNEDSTATE_SYSTEM and SIGNEDSTATE_PRIVILEGED are signed
      //   independently of AMO.
      //
      // - SIGNEDSTATE_NOT_REQUIRED already has an early return above due to
      //   signedDate being unset for these kinds of add-ons.
      //
      // - SIGNEDSTATE_BROKEN, SIGNEDSTATE_UNKNOWN and SIGNEDSTATE_MISSING
      //   means that the signature cannot be relied upon. It is equivalent to
      //   removing the signature from the XPI file, which already causes them
      //   to be disabled on release builds (where MOZ_REQUIRE_SIGNING=true).
      return null;
    }

    if (signedDate.getTime() > generationTime) {
      // The bloom filter only reports 100% accurate results for known add-ons.
      // Since the add-on was unknown when the bloom filter was generated, the
      // block decision is incorrect and should be treated as unblocked.
      return null;
    }

    if (AppConstants.NIGHTLY_BUILD && addon.type === "locale") {
      // Only Mozilla can create langpacks with a valid signature.
      // Langpacks for Release, Beta and ESR are submitted to AMO.
      // DevEd does not support external langpacks (bug 1563923), only builtins.
      //   (and built-in addons are not subjected to the blocklist).
      // Langpacks for Nightly are not known to AMO, so the MLBF cannot be used.
      return null;
    }

    return this._createBlockEntry(addon);
  },

  _createBlockEntry(addon) {
    return {
      state: Ci.nsIBlocklistService.STATE_BLOCKED,
      url: this.createBlocklistURL(addon.id, addon.version),
    };
  },

  createBlocklistURL(id, version) {
    let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ADDONITEM_URL);
    return url.replace(/%addonID%/g, id).replace(/%addonVersion%/g, version);
  },
};

const EXTENSION_BLOCK_FILTERS = [
  "id",
  "name",
  "creator",
  "homepageURL",
  "updateURL",
];

var gLoggingEnabled = null;
var gBlocklistEnabled = true;
var gBlocklistLevel = DEFAULT_LEVEL;

/**
 * @class nsIBlocklistPrompt
 *
 * nsIBlocklistPrompt is used, if available, by the default implementation of
 * nsIBlocklistService to display a confirmation UI to the user before blocking
 * extensions/plugins.
 */
/**
 * @method prompt
 *
 * Prompt the user about newly blocked addons. The prompt is then resposible
 * for soft-blocking any addons that need to be afterwards
 *
 * @param {object[]} aAddons
 *         An array of addons and plugins that are blocked. These are javascript
 *         objects with properties:
 *          name    - the plugin or extension name,
 *          version - the version of the extension or plugin,
 *          icon    - the plugin or extension icon,
 *          disable - can be used by the nsIBlocklistPrompt to allows users to decide
 *                    whether a soft-blocked add-on should be disabled,
 *          blocked - true if the item is hard-blocked, false otherwise,
 *          item    - the Addon object
 */

// It is not possible to use the one in Services since it will not successfully
// QueryInterface nsIXULAppInfo in xpcshell tests due to other code calling
// Services.appinfo before the nsIXULAppInfo is created by the tests.
ChromeUtils.defineLazyGetter(lazy, "gApp", function () {
  // eslint-disable-next-line mozilla/use-services
  let appinfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
  try {
    appinfo.QueryInterface(Ci.nsIXULAppInfo);
  } catch (ex) {
    // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
    if (
      !(ex instanceof Components.Exception) ||
      ex.result != Cr.NS_NOINTERFACE
    ) {
      throw ex;
    }
  }
  return appinfo;
});

ChromeUtils.defineLazyGetter(lazy, "gAppID", function () {
  return lazy.gApp.ID;
});
ChromeUtils.defineLazyGetter(lazy, "gAppOS", function () {
  return lazy.gApp.OS;
});

/**
 * Logs a string to the error console.
 * @param {string} string
 *        The string to write to the error console..
 */
function LOG(string) {
  if (gLoggingEnabled) {
    dump("*** " + string + "\n");
    Services.console.logStringMessage(string);
  }
}

export let Blocklist = {
  _init() {
    Services.obs.addObserver(this, "xpcom-shutdown");
    gLoggingEnabled = Services.prefs.getBoolPref(
      PREF_EM_LOGGING_ENABLED,
      false
    );
    gBlocklistEnabled = Services.prefs.getBoolPref(
      PREF_BLOCKLIST_ENABLED,
      true
    );
    gBlocklistLevel = Math.min(
      Services.prefs.getIntPref(PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
      MAX_BLOCK_LEVEL
    );
    this._chooseExtensionBlocklistImplementationFromPref();
    Services.prefs.addObserver("extensions.blocklist.", this);
    Services.prefs.addObserver(PREF_EM_LOGGING_ENABLED, this);
    BlocklistTelemetry.init();
  },
  isLoaded: true,

  shutdown() {
    GfxBlocklistRS.shutdown();
    this.ExtensionBlocklist.shutdown();

    Services.obs.removeObserver(this, "xpcom-shutdown");
    Services.prefs.removeObserver("extensions.blocklist.", this);
    Services.prefs.removeObserver(PREF_EM_LOGGING_ENABLED, this);
  },

  observe(subject, topic, prefName) {
    switch (topic) {
      case "xpcom-shutdown":
        this.shutdown();
        break;
      case "nsPref:changed":
        switch (prefName) {
          case PREF_EM_LOGGING_ENABLED:
            gLoggingEnabled = Services.prefs.getBoolPref(
              PREF_EM_LOGGING_ENABLED,
              false
            );
            break;
          case PREF_BLOCKLIST_ENABLED:
            gBlocklistEnabled = Services.prefs.getBoolPref(
              PREF_BLOCKLIST_ENABLED,
              true
            );
            this._blocklistUpdated();
            break;
          case PREF_BLOCKLIST_LEVEL:
            gBlocklistLevel = Math.min(
              Services.prefs.getIntPref(PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
              MAX_BLOCK_LEVEL
            );
            this._blocklistUpdated();
            break;
          case PREF_BLOCKLIST_USE_MLBF:
            let oldImpl = this.ExtensionBlocklist;
            this._chooseExtensionBlocklistImplementationFromPref();
            // The implementation may be unchanged when the pref is ignored.
            if (oldImpl != this.ExtensionBlocklist && oldImpl._initialized) {
              oldImpl.shutdown();
              this.ExtensionBlocklist.undoShutdown();
              this.ExtensionBlocklist._onUpdate();
            } // else neither has been initialized yet. Wait for it to happen.
            break;
        }
        break;
    }
  },

  loadBlocklistAsync() {
    // Need to ensure we notify gfx of new stuff.
    // Geckoview calls this for each new tab (bug 1730026), so ensure we only
    // check for entries when first initialized.
    if (!GfxBlocklistRS._initialized) {
      GfxBlocklistRS.checkForEntries();
    }
    this.ExtensionBlocklist.ensureInitialized();
  },

  getAddonBlocklistState(addon, appVersion, toolkitVersion) {
    // NOTE: appVersion/toolkitVersion are only used by ExtensionBlocklistRS.
    return this.ExtensionBlocklist.getState(addon, appVersion, toolkitVersion);
  },

  getAddonBlocklistEntry(addon, appVersion, toolkitVersion) {
    // NOTE: appVersion/toolkitVersion are only used by ExtensionBlocklistRS.
    return this.ExtensionBlocklist.getEntry(addon, appVersion, toolkitVersion);
  },

  recordAddonBlockChangeTelemetry(addon, reason) {
    BlocklistTelemetry.recordAddonBlockChangeTelemetry(addon, reason);
  },
  // TODO bug 1649906: Remove blocklist v2 (dead code).
  allowDeprecatedBlocklistV2: false,

  _chooseExtensionBlocklistImplementationFromPref() {
    if (
      this.allowDeprecatedBlocklistV2 &&
      !Services.prefs.getBoolPref(PREF_BLOCKLIST_USE_MLBF, false)
    ) {
      this.ExtensionBlocklist = ExtensionBlocklistRS;
    } else {
      this.ExtensionBlocklist = ExtensionBlocklistMLBF;
    }
  },

  _blocklistUpdated() {
    this.ExtensionBlocklist._onUpdate();
  },
};

Blocklist._init();

// Allow tests to reach implementation objects.
export const BlocklistPrivate = {
  BlocklistTelemetry,
  ExtensionBlocklistMLBF,
  ExtensionBlocklistRS,
  GfxBlocklistRS,
};
PK
!<��[����!modules/BookmarkHTMLUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file works on the old-style "bookmarks.html" file.  It includes
 * functions to import and export existing bookmarks to this file format.
 *
 * Format
 * ------
 *
 * Primary heading := h1
 *   Old version used this to set attributes on the bookmarks RDF root, such
 *   as the last modified date. We only use H1 to check for the attribute
 *   PLACES_ROOT, which tells us that this hierarchy root is the places root.
 *   For backwards compatibility, if we don't find this, we assume that the
 *   hierarchy is rooted at the bookmarks menu.
 * Heading := any heading other than h1
 *   Old version used this to set attributes on the current container. We only
 *   care about the content of the heading container, which contains the title
 *   of the bookmark container.
 * Bookmark := a
 *   HREF is the destination of the bookmark
 *   FEEDURL is the URI of the RSS feed. This is deprecated and no more
 *   supported, but some old files may still contain it.
 *   LAST_CHARSET is stored as an annotation so that the next time we go to
 *     that page we remember the user's preference.
 *   ICON will be stored in the favicon service
 *   ICON_URI is new for places bookmarks.html, it refers to the original
 *     URI of the favicon so we don't have to make up favicon URLs.
 *   Text of the <a> container is the name of the bookmark
 *   Ignored: LAST_VISIT, ID (writing out non-RDF IDs can confuse Firefox 2)
 * Bookmark comment := dd
 *   This affects the previosly added bookmark
 * Separator := hr
 *   Insert a separator into the current container
 * The folder hierarchy is defined by <dl>/<ul>/<menu> (the old importing code
 *     handles all these cases, when we write, use <dl>).
 *
 * Overall design
 * --------------
 *
 * We need to emulate a recursive parser. A "Bookmark import frame" is created
 * corresponding to each folder we encounter. These are arranged in a stack,
 * and contain all the state we need to keep track of.
 *
 * A frame is created when we find a heading, which defines a new container.
 * The frame also keeps track of the nesting of <DL>s, (in well-formed
 * bookmarks files, these will have a 1-1 correspondence with frames, but we
 * try to be a little more flexible here). When the nesting count decreases
 * to 0, then we know a frame is complete and to pop back to the previous
 * frame.
 *
 * Note that a lot of things happen when tags are CLOSED because we need to
 * get the text from the content of the tag. For example, link and heading tags
 * both require the content (= title) before actually creating it.
 */

import { NetUtil } from "resource://gre/modules/NetUtil.sys.mjs";

import { FileUtils } from "resource://gre/modules/FileUtils.sys.mjs";
import { PlacesUtils } from "resource://gre/modules/PlacesUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PlacesBackups: "resource://gre/modules/PlacesBackups.sys.mjs",
});

const Container_Normal = 0;
const Container_Toolbar = 1;
const Container_Menu = 2;
const Container_Unfiled = 3;
const Container_Places = 4;

const MICROSEC_PER_SEC = 1000000;

const EXPORT_INDENT = "    "; // four spaces

function base64EncodeString(aString) {
  let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
    Ci.nsIStringInputStream
  );
  stream.setData(aString, aString.length);
  let encoder = Cc["@mozilla.org/scriptablebase64encoder;1"].createInstance(
    Ci.nsIScriptableBase64Encoder
  );
  return encoder.encodeToString(stream, aString.length);
}

/**
 * Provides HTML escaping for use in HTML attributes and body of the bookmarks
 * file, compatible with the old bookmarks system.
 */
function escapeHtmlEntities(aText) {
  return (aText || "")
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

/**
 * Provides URL escaping for use in HTML attributes of the bookmarks file,
 * compatible with the old bookmarks system.
 */
function escapeUrl(aText) {
  return (aText || "").replace(/"/g, "%22");
}

function notifyObservers(aTopic, aInitialImport) {
  Services.obs.notifyObservers(
    null,
    aTopic,
    aInitialImport ? "html-initial" : "html"
  );
}

export var BookmarkHTMLUtils = Object.freeze({
  /**
   * Loads the current bookmarks hierarchy from a "bookmarks.html" file.
   *
   * @param aSpec
   *        String containing the "file:" URI for the existing "bookmarks.html"
   *        file to be loaded.
   * @param [options.replace]
   *        Whether we should erase existing bookmarks before loading.
   *        Defaults to `false`.
   * @param [options.source]
   *        The bookmark change source, used to determine the sync status for
   *        imported bookmarks. Defaults to `RESTORE` if `replace = true`, or
   *        `IMPORT` otherwise.
   *
   * @returns {Promise<number>} The number of imported bookmarks, not including
   *                           folders and separators.
   * @resolves When the new bookmarks have been created.
   * @rejects JavaScript exception.
   */
  async importFromURL(
    aSpec,
    {
      replace: aInitialImport = false,
      source: aSource = aInitialImport
        ? PlacesUtils.bookmarks.SOURCES.RESTORE
        : PlacesUtils.bookmarks.SOURCES.IMPORT,
    } = {}
  ) {
    let bookmarkCount;
    notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_BEGIN, aInitialImport);
    try {
      let importer = new BookmarkImporter(aInitialImport, aSource);
      bookmarkCount = await importer.importFromURL(aSpec);

      notifyObservers(
        PlacesUtils.TOPIC_BOOKMARKS_RESTORE_SUCCESS,
        aInitialImport
      );
    } catch (ex) {
      console.error(`Failed to import bookmarks from ${aSpec}:`, ex);
      notifyObservers(
        PlacesUtils.TOPIC_BOOKMARKS_RESTORE_FAILED,
        aInitialImport
      );
      throw ex;
    }
    return bookmarkCount;
  },

  /**
   * Loads the current bookmarks hierarchy from a "bookmarks.html" file.
   *
   * @param aFilePath
   *        OS.File path string of the "bookmarks.html" file to be loaded.
   * @param [options.replace]
   *        Whether we should erase existing bookmarks before loading.
   *        Defaults to `false`.
   * @param [options.source]
   *        The bookmark change source, used to determine the sync status for
   *        imported bookmarks. Defaults to `RESTORE` if `replace = true`, or
   *        `IMPORT` otherwise.
   *
   * @returns {Promise<number>} The number of imported bookmarks, not including
   *                            folders and separators
   * @resolves When the new bookmarks have been created.
   * @rejects JavaScript exception.
   */
  async importFromFile(
    aFilePath,
    {
      replace: aInitialImport = false,
      source: aSource = aInitialImport
        ? PlacesUtils.bookmarks.SOURCES.RESTORE
        : PlacesUtils.bookmarks.SOURCES.IMPORT,
    } = {}
  ) {
    let bookmarkCount;
    notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_BEGIN, aInitialImport);
    try {
      if (!(await IOUtils.exists(aFilePath))) {
        throw new Error(
          "Cannot import from nonexisting html file: " + aFilePath
        );
      }
      let importer = new BookmarkImporter(aInitialImport, aSource);
      bookmarkCount = await importer.importFromURL(
        PathUtils.toFileURI(aFilePath)
      );

      notifyObservers(
        PlacesUtils.TOPIC_BOOKMARKS_RESTORE_SUCCESS,
        aInitialImport
      );
    } catch (ex) {
      console.error(`Failed to import bookmarks from ${aFilePath}:`, ex);
      notifyObservers(
        PlacesUtils.TOPIC_BOOKMARKS_RESTORE_FAILED,
        aInitialImport
      );
      throw ex;
    }
    return bookmarkCount;
  },

  /**
   * Saves the current bookmarks hierarchy to a "bookmarks.html" file.
   *
   * @param aFilePath
   *        OS.File path string for the "bookmarks.html" file to be created.
   *
   * @return {Promise}
   * @resolves To the exported bookmarks count when the file has been created.
   * @rejects JavaScript exception.
   */
  async exportToFile(aFilePath) {
    let [bookmarks, count] = await lazy.PlacesBackups.getBookmarksTree();
    let startTime = Date.now();

    // Report the time taken to convert the tree to HTML.
    let exporter = new BookmarkExporter(bookmarks);
    await exporter.exportToFile(aFilePath);

    try {
      Services.telemetry
        .getHistogramById("PLACES_EXPORT_TOHTML_MS")
        .add(Date.now() - startTime);
    } catch (ex) {
      console.error("Unable to report telemetry.");
    }

    return count;
  },

  get defaultPath() {
    try {
      return Services.prefs.getCharPref("browser.bookmarks.file");
    } catch (ex) {}
    return PathUtils.join(PathUtils.profileDir, "bookmarks.html");
  },
});

function Frame(aFolder) {
  this.folder = aFolder;

  /**
   * How many <dl>s have been nested. Each frame/container should start
   * with a heading, and is then followed by a <dl>, <ul>, or <menu>. When
   * that list is complete, then it is the end of this container and we need
   * to pop back up one level for new items. If we never get an open tag for
   * one of these things, we should assume that the container is empty and
   * that things we find should be siblings of it. Normally, these <dl>s won't
   * be nested so this will be 0 or 1.
   */
  this.containerNesting = 0;

  /**
   * when we find a heading tag, it actually affects the title of the NEXT
   * container in the list. This stores that heading tag and whether it was
   * special. 'consumeHeading' resets this._
   */
  this.lastContainerType = Container_Normal;

  /**
   * this contains the text from the last begin tag until now. It is reset
   * at every begin tag. We can check it when we see a </a>, or </h3>
   * to see what the text content of that node should be.
   */
  this.previousText = "";

  /**
   * true when we hit a <dd>, which contains the description for the preceding
   * <a> tag. We can't just check for </dd> like we can for </a> or </h3>
   * because if there is a sub-folder, it is actually a child of the <dd>
   * because the tag is never explicitly closed. If this is true and we see a
   * new open tag, that means to commit the description to the previous
   * bookmark.
   *
   * Additional weirdness happens when the previous <dt> tag contains a <h3>:
   * this means there is a new folder with the given description, and whose
   * children are contained in the following <dl> list.
   *
   * This is handled in openContainer(), which commits previous text if
   * necessary.
   */
  this.inDescription = false;

  /**
   * contains the URL of the previous bookmark created. This is used so that
   * when we encounter a <dd>, we know what bookmark to associate the text with.
   * This is cleared whenever we hit a <h3>, so that we know NOT to save this
   * with a bookmark, but to keep it until
   */
  this.previousLink = null;

  /**
   * Contains a reference to the last created bookmark or folder object.
   */
  this.previousItem = null;

  /**
   * Contains the date-added and last-modified-date of an imported item.
   * Used to override the values set by insertBookmark, createFolder, etc.
   */
  this.previousDateAdded = null;
  this.previousLastModifiedDate = null;
}

function BookmarkImporter(aInitialImport, aSource) {
  this._isImportDefaults = aInitialImport;
  this._source = aSource;

  // This root is where we construct the bookmarks tree into, following the format
  // of the imported file.
  // If we're doing an initial import, the non-menu roots will be created as
  // children of this root, so in _getBookmarkTrees we'll split them out.
  // If we're not doing an initial import, everything gets imported under the
  // bookmark menu folder, so there won't be any need for _getBookmarkTrees to
  // do separation.
  this._bookmarkTree = {
    type: PlacesUtils.bookmarks.TYPE_FOLDER,
    guid: PlacesUtils.bookmarks.menuGuid,
    children: [],
  };

  this._frames = [];
  this._frames.push(new Frame(this._bookmarkTree));
}

BookmarkImporter.prototype = {
  _safeTrim: function safeTrim(aStr) {
    return aStr ? aStr.trim() : aStr;
  },

  get _curFrame() {
    return this._frames[this._frames.length - 1];
  },

  get _previousFrame() {
    return this._frames[this._frames.length - 2];
  },

  /**
   * This is called when there is a new folder found. The folder takes the
   * name from the previous frame's heading.
   */
  _newFrame: function newFrame() {
    let frame = this._curFrame;
    let containerTitle = frame.previousText;
    frame.previousText = "";
    let containerType = frame.lastContainerType;

    let folder = {
      children: [],
      type: PlacesUtils.bookmarks.TYPE_FOLDER,
    };

    switch (containerType) {
      case Container_Normal:
        // This can only be a sub-folder so no need to set a guid here.
        folder.title = containerTitle;
        break;
      case Container_Places:
        folder.guid = PlacesUtils.bookmarks.rootGuid;
        break;
      case Container_Menu:
        folder.guid = PlacesUtils.bookmarks.menuGuid;
        break;
      case Container_Unfiled:
        folder.guid = PlacesUtils.bookmarks.unfiledGuid;
        break;
      case Container_Toolbar:
        folder.guid = PlacesUtils.bookmarks.toolbarGuid;
        break;
      default:
        // NOT REACHED
        throw new Error("Unknown bookmark container type!");
    }

    frame.folder.children.push(folder);

    if (frame.previousDateAdded != null) {
      folder.dateAdded = frame.previousDateAdded;
      frame.previousDateAdded = null;
    }

    if (frame.previousLastModifiedDate != null) {
      folder.lastModified = frame.previousLastModifiedDate;
      frame.previousLastModifiedDate = null;
    }

    if (
      !folder.hasOwnProperty("dateAdded") &&
      folder.hasOwnProperty("lastModified")
    ) {
      folder.dateAdded = folder.lastModified;
    }

    frame.previousItem = folder;

    this._frames.push(new Frame(folder));
  },

  /**
   * Handles <hr> as a separator.
   *
   * @note Separators may have a title in old html files, though Places dropped
   *       support for them.
   *       We also don't import ADD_DATE or LAST_MODIFIED for separators because
   *       pre-Places bookmarks did not support them.
   */
  _handleSeparator: function handleSeparator() {
    let frame = this._curFrame;

    let separator = {
      type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
    };
    frame.folder.children.push(separator);
    frame.previousItem = separator;
  },

  /**
   * Called for h2,h3,h4,h5,h6. This just stores the correct information in
   * the current frame; the actual new frame corresponding to the container
   * associated with the heading will be created when the tag has been closed
   * and we know the title (we don't know to create a new folder or to merge
   * with an existing one until we have the title).
   */
  _handleHeadBegin: function handleHeadBegin(aElt) {
    let frame = this._curFrame;

    // after a heading, a previous bookmark is not applicable (for example, for
    // the descriptions contained in a <dd>). Neither is any previous head type
    frame.previousLink = null;
    frame.lastContainerType = Container_Normal;

    // It is syntactically possible for a heading to appear after another heading
    // but before the <dl> that encloses that folder's contents.  This should not
    // happen in practice, as the file will contain "<dl></dl>" sequence for
    // empty containers.
    //
    // Just to be on the safe side, if we encounter
    //   <h3>FOO</h3>
    //   <h3>BAR</h3>
    //   <dl>...content 1...</dl>
    //   <dl>...content 2...</dl>
    // we'll pop the stack when we find the h3 for BAR, treating that as an
    // implicit ending of the FOO container. The output will be FOO and BAR as
    // siblings. If there's another <dl> following (as in "content 2"), those
    // items will be treated as further siblings of FOO and BAR
    // This special frame popping business, of course, only happens when our
    // frame array has more than one element so we can avoid situations where
    // we don't have a frame to parse into anymore.
    if (frame.containerNesting == 0 && this._frames.length > 1) {
      this._frames.pop();
    }

    // We have to check for some attributes to see if this is a "special"
    // folder, which will have different creation rules when the end tag is
    // processed.
    if (aElt.hasAttribute("personal_toolbar_folder")) {
      if (this._isImportDefaults) {
        frame.lastContainerType = Container_Toolbar;
      }
    } else if (aElt.hasAttribute("bookmarks_menu")) {
      if (this._isImportDefaults) {
        frame.lastContainerType = Container_Menu;
      }
    } else if (aElt.hasAttribute("unfiled_bookmarks_folder")) {
      if (this._isImportDefaults) {
        frame.lastContainerType = Container_Unfiled;
      }
    } else if (aElt.hasAttribute("places_root")) {
      if (this._isImportDefaults) {
        frame.lastContainerType = Container_Places;
      }
    } else {
      let addDate = aElt.getAttribute("add_date");
      if (addDate) {
        frame.previousDateAdded =
          this._convertImportedDateToInternalDate(addDate);
      }
      let modDate = aElt.getAttribute("last_modified");
      if (modDate) {
        frame.previousLastModifiedDate =
          this._convertImportedDateToInternalDate(modDate);
      }
    }
    this._curFrame.previousText = "";
  },

  /*
   * Handles "<a" tags by creating a new bookmark. The title of the bookmark
   * will be the text content, which will be stuffed in previousText for us
   * and which will be saved by handleLinkEnd
   */
  _handleLinkBegin: function handleLinkBegin(aElt) {
    let frame = this._curFrame;

    frame.previousItem = null;
    frame.previousText = ""; // Will hold link text, clear it.

    // Get the attributes we care about.
    let href = this._safeTrim(aElt.getAttribute("href"));
    let icon = this._safeTrim(aElt.getAttribute("icon"));
    let iconUri = this._safeTrim(aElt.getAttribute("icon_uri"));
    let lastCharset = this._safeTrim(aElt.getAttribute("last_charset"));
    let keyword = this._safeTrim(aElt.getAttribute("shortcuturl"));
    let postData = this._safeTrim(aElt.getAttribute("post_data"));
    let dateAdded = this._safeTrim(aElt.getAttribute("add_date"));
    let lastModified = this._safeTrim(aElt.getAttribute("last_modified"));
    let tags = this._safeTrim(aElt.getAttribute("tags"));

    // Ignore <a> tags that have no href.
    try {
      frame.previousLink = Services.io.newURI(href).spec;
    } catch (e) {
      frame.previousLink = null;
      return;
    }

    let bookmark = {};

    // Only set the url for bookmarks.
    if (frame.previousLink) {
      bookmark.url = frame.previousLink;
    }

    if (dateAdded) {
      bookmark.dateAdded = this._convertImportedDateToInternalDate(dateAdded);
    }
    // Save bookmark's last modified date.
    if (lastModified) {
      bookmark.lastModified =
        this._convertImportedDateToInternalDate(lastModified);
    }

    if (!dateAdded && lastModified) {
      bookmark.dateAdded = bookmark.lastModified;
    }

    if (tags) {
      bookmark.tags = tags
        .split(",")
        .filter(
          aTag =>
            !!aTag.length && aTag.length <= PlacesUtils.bookmarks.MAX_TAG_LENGTH
        );

      // If we end up with none, then delete the property completely.
      if (!bookmark.tags.length) {
        delete bookmark.tags;
      }
    }

    if (lastCharset) {
      bookmark.charset = lastCharset;
    }

    if (keyword) {
      bookmark.keyword = keyword;
    }

    if (postData) {
      bookmark.postData = postData;
    }

    if (icon) {
      bookmark.icon = icon;
    }

    if (iconUri) {
      bookmark.iconUri = iconUri;
    }

    // Add bookmark to the tree.
    frame.folder.children.push(bookmark);
    frame.previousItem = bookmark;
  },

  _handleContainerBegin: function handleContainerBegin() {
    this._curFrame.containerNesting++;
  },

  /**
   * Our "indent" count has decreased, and when we hit 0 that means that this
   * container is complete and we need to pop back to the outer frame. Never
   * pop the toplevel frame
   */
  _handleContainerEnd: function handleContainerEnd() {
    let frame = this._curFrame;
    if (frame.containerNesting > 0) {
      frame.containerNesting--;
    }
    if (this._frames.length > 1 && frame.containerNesting == 0) {
      this._frames.pop();
    }
  },

  /**
   * Creates the new frame for this heading now that we know the name of the
   * container (tokens since the heading open tag will have been placed in
   * previousText).
   */
  _handleHeadEnd: function handleHeadEnd() {
    this._newFrame();
  },

  /**
   * Saves the title for the given bookmark.
   */
  _handleLinkEnd: function handleLinkEnd() {
    let frame = this._curFrame;
    frame.previousText = frame.previousText.trim();

    if (frame.previousItem != null) {
      frame.previousItem.title = frame.previousText;
    }

    frame.previousText = "";
  },

  _openContainer: function openContainer(aElt) {
    if (aElt.namespaceURI != "http://www.w3.org/1999/xhtml") {
      return;
    }
    switch (aElt.localName) {
      case "h2":
      case "h3":
      case "h4":
      case "h5":
      case "h6":
        this._handleHeadBegin(aElt);
        break;
      case "a":
        this._handleLinkBegin(aElt);
        break;
      case "dl":
      case "ul":
      case "menu":
        this._handleContainerBegin();
        break;
      case "dd":
        this._curFrame.inDescription = true;
        break;
      case "hr":
        this._handleSeparator(aElt);
        break;
    }
  },

  _closeContainer: function closeContainer(aElt) {
    let frame = this._curFrame;

    // Although we no longer support importing descriptions, we still need to
    // clear any previous text, so that it doesn't get swallowed into other elements.
    if (frame.inDescription) {
      frame.previousText = "";
      frame.inDescription = false;
    }

    if (aElt.namespaceURI != "http://www.w3.org/1999/xhtml") {
      return;
    }
    switch (aElt.localName) {
      case "dl":
      case "ul":
      case "menu":
        this._handleContainerEnd();
        break;
      case "dt":
        break;
      case "h1":
        // ignore
        break;
      case "h2":
      case "h3":
      case "h4":
      case "h5":
      case "h6":
        this._handleHeadEnd();
        break;
      case "a":
        this._handleLinkEnd();
        break;
      default:
        break;
    }
  },

  _appendText: function appendText(str) {
    this._curFrame.previousText += str;
  },

  /**
   * Converts a string date in seconds to a date object
   */
  _convertImportedDateToInternalDate:
    function convertImportedDateToInternalDate(aDate) {
      try {
        if (aDate && !isNaN(aDate)) {
          return new Date(parseInt(aDate) * 1000); // in bookmarks.html this value is in seconds
        }
      } catch (ex) {
        // Do nothing.
      }
      return new Date();
    },

  _walkTreeForImport(aDoc) {
    if (!aDoc) {
      return;
    }

    let current = aDoc;
    let next;
    for (;;) {
      switch (current.nodeType) {
        case current.ELEMENT_NODE:
          this._openContainer(current);
          break;
        case current.TEXT_NODE:
          this._appendText(current.data);
          break;
      }
      if ((next = current.firstChild)) {
        current = next;
        continue;
      }
      for (;;) {
        if (current.nodeType == current.ELEMENT_NODE) {
          this._closeContainer(current);
        }
        if (current == aDoc) {
          return;
        }
        if ((next = current.nextSibling)) {
          current = next;
          break;
        }
        current = current.parentNode;
      }
    }
  },

  /**
   * Returns the bookmark tree(s) from the importer. These are suitable for
   * passing to PlacesUtils.bookmarks.insertTree().
   *
   * @returns {Array} An array of bookmark trees.
   */
  _getBookmarkTrees() {
    // If we're not importing defaults, then everything gets imported under the
    // Bookmarks menu.
    if (!this._isImportDefaults) {
      return [this._bookmarkTree];
    }

    // If we are importing defaults, we need to separate out the top-level
    // default folders into separate items, for the caller to pass into insertTree.
    let bookmarkTrees = [this._bookmarkTree];

    // The children of this "root" element will contain normal children of the
    // bookmark menu as well as the places roots. Hence, we need to filter out
    // the separate roots, but keep the children that are relevant to the
    // bookmark menu.
    this._bookmarkTree.children = this._bookmarkTree.children.filter(child => {
      if (
        child.guid &&
        PlacesUtils.bookmarks.userContentRoots.includes(child.guid)
      ) {
        bookmarkTrees.push(child);
        return false;
      }
      return true;
    });

    return bookmarkTrees;
  },

  /**
   * Imports the bookmarks from the importer into the places database.
   *
   * @param {BookmarkImporter} importer The importer from which to get the
   *                                    bookmark information.
   * @returns {number} The number of imported bookmarks, not including
   *                   folders and separators
   */
  async _importBookmarks() {
    if (this._isImportDefaults) {
      await PlacesUtils.bookmarks.eraseEverything();
    }

    let bookmarksTrees = this._getBookmarkTrees();
    let bookmarkCount = 0;
    for (let tree of bookmarksTrees) {
      if (!tree.children.length) {
        continue;
      }

      // Give the tree the source.
      tree.source = this._source;
      let bookmarks = await PlacesUtils.bookmarks.insertTree(tree, {
        fixupOrSkipInvalidEntries: true,
      });
      // We want to count only bookmarks, not folders or separators
      bookmarkCount += bookmarks.filter(
        bookmark => bookmark.type == PlacesUtils.bookmarks.TYPE_BOOKMARK
      ).length;
      insertFaviconsForTree(tree);
    }
    return bookmarkCount;
  },

  /**
   * Imports data into the places database from the supplied url.
   *
   * @param {String} href The url to import data from.
   * @returns {number} The number of imported bookmarks, not including
   *                   folders and separators.
   */
  async importFromURL(href) {
    let data = await fetchData(href);

    if (this._isImportDefaults && data) {
      // Localize default bookmarks.  Find rel="localization" links and manually
      // localize using them.
      let hrefs = [];
      let links = data.head.querySelectorAll("link[rel='localization']");
      for (let link of links) {
        if (link.getAttribute("href")) {
          // We need the text, not the fully qualified URL, so we use `getAttribute`.
          hrefs.push(link.getAttribute("href"));
        }
      }

      if (hrefs.length) {
        let domLoc = new DOMLocalization(hrefs);
        await domLoc.translateFragment(data.body);
      }
    }

    this._walkTreeForImport(data);
    return this._importBookmarks();
  },
};

function BookmarkExporter(aBookmarksTree) {
  // Create a map of the roots.
  let rootsMap = new Map();
  for (let child of aBookmarksTree.children) {
    if (child.root) {
      rootsMap.set(child.root, child);
      // Also take the opportunity to get the correctly localised title for the
      // root.
      child.title = PlacesUtils.bookmarks.getLocalizedTitle(child);
    }
  }

  // For backwards compatibility reasons the bookmarks menu is the root, while
  // the bookmarks toolbar and unfiled bookmarks will be child items.
  this._root = rootsMap.get("bookmarksMenuFolder");

  for (let key of ["toolbarFolder", "unfiledBookmarksFolder"]) {
    let root = rootsMap.get(key);
    if (root.children && root.children.length) {
      if (!this._root.children) {
        this._root.children = [];
      }
      this._root.children.push(root);
    }
  }
}

BookmarkExporter.prototype = {
  exportToFile: function exportToFile(aFilePath) {
    return (async () => {
      // Create a file that can be accessed by the current user only.
      let out = FileUtils.openAtomicFileOutputStream(
        new FileUtils.File(aFilePath)
      );
      try {
        // We need a buffered output stream for performance.  See bug 202477.
        let bufferedOut = Cc[
          "@mozilla.org/network/buffered-output-stream;1"
        ].createInstance(Ci.nsIBufferedOutputStream);
        bufferedOut.init(out, 4096);
        try {
          // Write bookmarks in UTF-8.
          this._converterOut = Cc[
            "@mozilla.org/intl/converter-output-stream;1"
          ].createInstance(Ci.nsIConverterOutputStream);
          this._converterOut.init(bufferedOut, "utf-8");
          try {
            this._writeHeader();
            await this._writeContainer(this._root);
            // Retain the target file on success only.
            bufferedOut.QueryInterface(Ci.nsISafeOutputStream).finish();
          } finally {
            this._converterOut.close();
            this._converterOut = null;
          }
        } finally {
          bufferedOut.close();
        }
      } finally {
        out.close();
      }
    })();
  },

  _converterOut: null,

  _write(aText) {
    this._converterOut.writeString(aText || "");
  },

  _writeAttribute(aName, aValue) {
    this._write(" " + aName + '="' + aValue + '"');
  },

  _writeLine(aText) {
    this._write(aText + "\n");
  },

  _writeHeader() {
    this._writeLine("<!DOCTYPE NETSCAPE-Bookmark-file-1>");
    this._writeLine("<!-- This is an automatically generated file.");
    this._writeLine("     It will be read and overwritten.");
    this._writeLine("     DO NOT EDIT! -->");
    this._writeLine(
      '<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">'
    );
    this._writeLine(`<meta http-equiv="Content-Security-Policy"
      content="default-src 'self'; script-src 'none'; img-src data: *; object-src 'none'"></meta>`);
    this._writeLine("<TITLE>Bookmarks</TITLE>");
  },

  async _writeContainer(aItem, aIndent = "") {
    if (aItem == this._root) {
      this._writeLine("<H1>" + escapeHtmlEntities(this._root.title) + "</H1>");
      this._writeLine("");
    } else {
      this._write(aIndent + "<DT><H3");
      this._writeDateAttributes(aItem);

      if (aItem.root === "toolbarFolder") {
        this._writeAttribute("PERSONAL_TOOLBAR_FOLDER", "true");
      } else if (aItem.root === "unfiledBookmarksFolder") {
        this._writeAttribute("UNFILED_BOOKMARKS_FOLDER", "true");
      }
      this._writeLine(">" + escapeHtmlEntities(aItem.title) + "</H3>");
    }

    this._writeLine(aIndent + "<DL><p>");
    if (aItem.children) {
      await this._writeContainerContents(aItem, aIndent);
    }
    if (aItem == this._root) {
      this._writeLine(aIndent + "</DL>");
    } else {
      this._writeLine(aIndent + "</DL><p>");
    }
  },

  async _writeContainerContents(aItem, aIndent) {
    let localIndent = aIndent + EXPORT_INDENT;

    for (let child of aItem.children) {
      if (child.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) {
        await this._writeContainer(child, localIndent);
      } else if (child.type == PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR) {
        this._writeSeparator(child, localIndent);
      } else {
        await this._writeItem(child, localIndent);
      }
    }
  },

  _writeSeparator(aItem, aIndent) {
    this._write(aIndent + "<HR");
    // We keep exporting separator titles, but don't support them anymore.
    if (aItem.title) {
      this._writeAttribute("NAME", escapeHtmlEntities(aItem.title));
    }
    this._write(">");
  },

  async _writeItem(aItem, aIndent) {
    try {
      NetUtil.newURI(aItem.uri);
    } catch (ex) {
      // If the item URI is invalid, skip the item instead of failing later.
      return;
    }

    this._write(aIndent + "<DT><A");
    this._writeAttribute("HREF", escapeUrl(aItem.uri));
    this._writeDateAttributes(aItem);
    await this._writeFaviconAttribute(aItem);

    if (aItem.keyword) {
      this._writeAttribute("SHORTCUTURL", escapeHtmlEntities(aItem.keyword));
      if (aItem.postData) {
        this._writeAttribute("POST_DATA", escapeHtmlEntities(aItem.postData));
      }
    }

    if (aItem.charset) {
      this._writeAttribute("LAST_CHARSET", escapeHtmlEntities(aItem.charset));
    }
    if (aItem.tags) {
      this._writeAttribute("TAGS", escapeHtmlEntities(aItem.tags));
    }
    this._writeLine(">" + escapeHtmlEntities(aItem.title) + "</A>");
  },

  _writeDateAttributes(aItem) {
    if (aItem.dateAdded) {
      this._writeAttribute(
        "ADD_DATE",
        Math.floor(aItem.dateAdded / MICROSEC_PER_SEC)
      );
    }
    if (aItem.lastModified) {
      this._writeAttribute(
        "LAST_MODIFIED",
        Math.floor(aItem.lastModified / MICROSEC_PER_SEC)
      );
    }
  },

  async _writeFaviconAttribute(aItem) {
    if (!aItem.iconUri) {
      return;
    }
    let favicon;
    try {
      favicon = await PlacesUtils.promiseFaviconData(aItem.uri);
    } catch (ex) {
      console.error("Unexpected Error trying to fetch icon data");
      return;
    }

    this._writeAttribute("ICON_URI", escapeUrl(favicon.uri.spec));

    if (!favicon.uri.schemeIs("chrome") && favicon.dataLen > 0) {
      let faviconContents =
        "data:image/png;base64," +
        base64EncodeString(String.fromCharCode.apply(String, favicon.data));
      this._writeAttribute("ICON", faviconContents);
    }
  },
};

/**
 * Handles inserting favicons into the database for a bookmark node.
 * It is assumed the node has already been inserted into the bookmarks
 * database.
 *
 * @param {Object} node The bookmark node for icons to be inserted.
 */
function insertFaviconForNode(node) {
  if (node.icon) {
    try {
      PlacesUtils.favicons.setFaviconForPage(
        Services.io.newURI(node.url),
        // Create a fake favicon URI to use (FIXME: bug 523932)
        Services.io.newURI("fake-favicon-uri:" + node.url),
        Services.io.newURI(node.icon)
      );
    } catch (ex) {
      console.error("Failed to import favicon data:", ex);
    }
  }

  if (!node.iconUri) {
    return;
  }

  try {
    PlacesUtils.favicons.setAndFetchFaviconForPage(
      Services.io.newURI(node.url),
      Services.io.newURI(node.iconUri),
      false,
      PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
      null,
      Services.scriptSecurityManager.getSystemPrincipal()
    );
  } catch (ex) {
    console.error("Failed to import favicon URI:" + ex);
  }
}

/**
 * Handles inserting favicons into the database for a bookmark tree - a node
 * and its children.
 *
 * It is assumed the nodes have already been inserted into the bookmarks
 * database.
 *
 * @param {Object} nodeTree The bookmark node tree for icons to be inserted.
 */
function insertFaviconsForTree(nodeTree) {
  insertFaviconForNode(nodeTree);

  if (nodeTree.children) {
    for (let child of nodeTree.children) {
      insertFaviconsForTree(child);
    }
  }
}

/**
 * Handles fetching data from a URL.
 *
 * @param {String} href The url to fetch data from.
 * @return {Promise} Returns a promise that is resolved with the data once
 *                   the fetch is complete, or is rejected if it fails.
 */
function fetchData(href) {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.onload = () => {
      resolve(xhr.responseXML);
    };
    xhr.onabort =
      xhr.onerror =
      xhr.ontimeout =
        () => {
          reject(new Error("xmlhttprequest failed"));
        };
    xhr.open("GET", href);
    xhr.responseType = "document";
    xhr.overrideMimeType("text/html");
    xhr.send();
  });
}
PK
!<���E�E!modules/BookmarkJSONUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { PlacesUtils } from "resource://gre/modules/PlacesUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PlacesBackups: "resource://gre/modules/PlacesBackups.sys.mjs",
});

// This is used to translate old folder pseudonyms in queries with their newer
// guids.
const OLD_BOOKMARK_QUERY_TRANSLATIONS = {
  PLACES_ROOT: PlacesUtils.bookmarks.rootGuid,
  BOOKMARKS_MENU: PlacesUtils.bookmarks.menuGuid,
  TAGS: PlacesUtils.bookmarks.tagsGuid,
  UNFILED_BOOKMARKS: PlacesUtils.bookmarks.unfiledGuid,
  TOOLBAR: PlacesUtils.bookmarks.toolbarGuid,
  MOBILE_BOOKMARKS: PlacesUtils.bookmarks.mobileGuid,
};

export var BookmarkJSONUtils = {
  /**
   * Import bookmarks from a url.
   *
   * @param {string} aSpec
   *        url of the bookmark data.
   * @param {boolean} [options.replace]
   *        Whether we should erase existing bookmarks before importing.
   * @param {PlacesUtils.bookmarks.SOURCES} [options.source]
   *        The bookmark change source, used to determine the sync status for
   *        imported bookmarks. Defaults to `RESTORE` if `replace = true`, or
   *        `IMPORT` otherwise.
   *
   * @returns {Promise<number>} The number of imported bookmarks, not including
   *                            folders and separators.
   * @resolves When the new bookmarks have been created.
   * @rejects JavaScript exception.
   */
  async importFromURL(
    aSpec,
    {
      replace: aReplace = false,
      source: aSource = aReplace
        ? PlacesUtils.bookmarks.SOURCES.RESTORE
        : PlacesUtils.bookmarks.SOURCES.IMPORT,
    } = {}
  ) {
    notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_BEGIN, aReplace);
    let bookmarkCount = 0;
    try {
      let importer = new BookmarkImporter(aReplace, aSource);
      bookmarkCount = await importer.importFromURL(aSpec);

      notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_SUCCESS, aReplace);
    } catch (ex) {
      console.error(`Failed to restore bookmarks from ${aSpec}:`, ex);
      notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_FAILED, aReplace);
      throw ex;
    }
    return bookmarkCount;
  },

  /**
   * Restores bookmarks and tags from a JSON file.
   *
   * @param aFilePath
   *        OS.File path string of bookmarks in JSON or JSONlz4 format to be restored.
   * @param [options.replace]
   *        Whether we should erase existing bookmarks before importing.
   * @param [options.source]
   *        The bookmark change source, used to determine the sync status for
   *        imported bookmarks. Defaults to `RESTORE` if `replace = true`, or
   *        `IMPORT` otherwise.
   *
   * @returns {Promise<number>} The number of imported bookmarks, not including
   *                            folders and separators.
   * @resolves When the new bookmarks have been created.
   * @rejects JavaScript exception.
   */
  async importFromFile(
    aFilePath,
    {
      replace: aReplace = false,
      source: aSource = aReplace
        ? PlacesUtils.bookmarks.SOURCES.RESTORE
        : PlacesUtils.bookmarks.SOURCES.IMPORT,
    } = {}
  ) {
    notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_BEGIN, aReplace);
    let bookmarkCount = 0;
    try {
      if (!(await IOUtils.exists(aFilePath))) {
        throw new Error("Cannot restore from nonexisting json file");
      }

      let importer = new BookmarkImporter(aReplace, aSource);
      if (aFilePath.endsWith("jsonlz4")) {
        bookmarkCount = await importer.importFromCompressedFile(aFilePath);
      } else {
        bookmarkCount = await importer.importFromURL(
          PathUtils.toFileURI(aFilePath)
        );
      }
      notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_SUCCESS, aReplace);
    } catch (ex) {
      console.error(`Failed to restore bookmarks from ${aFilePath}:`, ex);
      notifyObservers(PlacesUtils.TOPIC_BOOKMARKS_RESTORE_FAILED, aReplace);
      throw ex;
    }
    return bookmarkCount;
  },

  /**
   * Serializes bookmarks using JSON, and writes to the supplied file path.
   *
   * @param {path} aFilePath
   *   Path string for the bookmarks file to be created.
   * @param {object} [aOptions]
   * @param {string} [failIfHashIs]
   *   If the generated file would have the same hash defined here, will reject
   *   with ex.becauseSameHash
   * @param {boolean} [compress]
   *   If true, writes file using lz4 compression
   * @return {Promise}
   * @resolves once the file has been created, to an object with the
   *           following properties:
   *            - count: number of exported bookmarks
   *            - hash: file hash for contents comparison
   * @rejects JavaScript exception.
   */
  async exportToFile(aFilePath, aOptions = {}) {
    let [bookmarks, count] = await lazy.PlacesBackups.getBookmarksTree();
    let startTime = Date.now();
    let jsonString = JSON.stringify(bookmarks);
    // Report the time taken to convert the tree to JSON.
    try {
      Services.telemetry
        .getHistogramById("PLACES_BACKUPS_TOJSON_MS")
        .add(Date.now() - startTime);
    } catch (ex) {
      console.error("Unable to report telemetry.");
    }

    // Use "base64url" as this may be part of a filename.
    let hash = PlacesUtils.sha256(jsonString, { format: "base64url" });

    if (hash === aOptions.failIfHashIs) {
      let e = new Error("Hash conflict");
      e.becauseSameHash = true;
      throw e;
    }

    // Do not write to the tmp folder, otherwise if it has a different
    // filesystem writeAtomic will fail.  Eventual dangling .tmp files should
    // be cleaned up by the caller.
    await IOUtils.writeUTF8(aFilePath, jsonString, {
      compress: aOptions.compress,
      tmpPath: PathUtils.join(aFilePath + ".tmp"),
    });
    return { count, hash };
  },
};

function BookmarkImporter(aReplace, aSource) {
  this._replace = aReplace;
  this._source = aSource;
}
BookmarkImporter.prototype = {
  /**
   * Import bookmarks from a url.
   *
   * @param {string} aSpec
   *        url of the bookmark data.
   *
   * @returns {Promise<number>} The number of imported bookmarks, not including
   *                            folders and separators.
   * @resolves When the new bookmarks have been created.
   * @rejects JavaScript exception.
   */
  async importFromURL(spec) {
    if (!spec.startsWith("chrome://") && !spec.startsWith("file://")) {
      throw new Error(
        "importFromURL can only be used with chrome:// and file:// URLs"
      );
    }
    let nodes = await (await fetch(spec)).json();

    if (!nodes.children || !nodes.children.length) {
      return 0;
    }

    return this.import(nodes);
  },

  /**
   * Import bookmarks from a compressed file.
   *
   * @param aFilePath
   *        OS.File path string of the bookmark data.
   *
   * @returns {Promise<number>} The number of imported bookmarks, not including
   *                           folders and separators.
   * @resolves When the new bookmarks have been created.
   * @rejects JavaScript exception.
   */
  importFromCompressedFile: async function BI_importFromCompressedFile(
    aFilePath
  ) {
    // We read as UTF8 rather than JSON, as PlacesUtils.unwrapNodes expects
    // a JSON string.
    let result = await IOUtils.readUTF8(aFilePath, { decompress: true });
    return this.importFromJSON(result);
  },

  /**
   * Import bookmarks from a JSON string.
   *
   * @param {String} aString JSON string of serialized bookmark data.
   * @returns {Promise<number>} The number of imported bookmarks, not including
   *                            folders and separators.
   * @resolves When the new bookmarks have been created.
   * @rejects JavaScript exception.
   */
  async importFromJSON(aString) {
    let nodes = PlacesUtils.unwrapNodes(
      aString,
      PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER
    );

    if (!nodes.length || !nodes[0].children || !nodes[0].children.length) {
      return 0;
    }

    return this.import(nodes[0]);
  },

  async import(rootNode) {
    // Change to rootNode.children as we don't import the root, and also filter
    // out any obsolete "tagsFolder" sections.
    let nodes = rootNode.children.filter(node => node.root !== "tagsFolder");

    // If we're replacing, then erase existing bookmarks first.
    if (this._replace) {
      await PlacesUtils.bookmarks.eraseEverything({ source: this._source });
    }

    let folderIdToGuidMap = {};

    // Now do some cleanup on the imported nodes so that the various guids
    // match what we need for insertTree, and we also have mappings of folders
    // so we can repair any searches after inserting the bookmarks (see bug 824502).
    for (let node of nodes) {
      if (!node.children || !node.children.length) {
        continue;
      } // Nothing to restore for this root

      // Ensure we set the source correctly.
      node.source = this._source;

      // Translate the node for insertTree.
      let folders = translateTreeTypes(node);

      folderIdToGuidMap = Object.assign(folderIdToGuidMap, folders);
    }

    let bookmarkCount = 0;
    // Now we can add the actual nodes to the database.
    for (let node of nodes) {
      // Drop any nodes without children, we can't insert them.
      if (!node.children || !node.children.length) {
        continue;
      }

      // Drop any roots whose guid we don't recognise - we don't support anything
      // apart from the built-in roots.
      if (!PlacesUtils.bookmarks.userContentRoots.includes(node.guid)) {
        continue;
      }

      fixupSearchQueries(node, folderIdToGuidMap);

      let bookmarks = await PlacesUtils.bookmarks.insertTree(node, {
        fixupOrSkipInvalidEntries: true,
      });
      // We want to count only bookmarks, not folders or separators
      bookmarkCount += bookmarks.filter(
        bookmark => bookmark.type == PlacesUtils.bookmarks.TYPE_BOOKMARK
      ).length;
      // Now add any favicons.
      try {
        insertFaviconsForTree(node);
      } catch (ex) {
        console.error("Failed to insert favicons:", ex);
      }
    }
    return bookmarkCount;
  },
};

function notifyObservers(topic, replace) {
  Services.obs.notifyObservers(null, topic, replace ? "json" : "json-append");
}

/**
 * Iterates through a node, fixing up any place: URL queries that are found. This
 * replaces any old (pre Firefox 62) queries that contain "folder=<id>" parts with
 * "parent=<guid>".
 *
 * @param {Object} aNode The node to search.
 * @param {Array} aFolderIdMap An array mapping of old folder IDs to new folder GUIDs.
 */
function fixupSearchQueries(aNode, aFolderIdMap) {
  if (aNode.url && aNode.url.startsWith("place:")) {
    aNode.url = fixupQuery(aNode.url, aFolderIdMap);
  }
  if (aNode.children) {
    for (let child of aNode.children) {
      fixupSearchQueries(child, aFolderIdMap);
    }
  }
}

/**
 * Replaces imported folder ids with their local counterparts in a place: URI.
 *
 * @param   {String} aQueryURL
 *          A place: URI with folder ids.
 * @param   {Object} aFolderIdMap
 *          An array mapping of old folder IDs to new folder GUIDs.
 * @return {String} the fixed up URI if all matched. If some matched, it returns
 *         the URI with only the matching folders included. If none matched
 *         it returns the input URI unchanged.
 */
function fixupQuery(aQueryURL, aFolderIdMap) {
  let invalid = false;
  let convert = function (str, existingFolderId) {
    let guid;
    if (
      Object.keys(OLD_BOOKMARK_QUERY_TRANSLATIONS).includes(existingFolderId)
    ) {
      guid = OLD_BOOKMARK_QUERY_TRANSLATIONS[existingFolderId];
    } else {
      guid = aFolderIdMap[existingFolderId];
      if (!guid) {
        invalid = true;
        return `invalidOldParentId=${existingFolderId}`;
      }
    }
    return `parent=${guid}`;
  };

  let url = aQueryURL.replace(/folder=([A-Za-z0-9_]+)/g, convert);
  if (invalid) {
    // One or more of the folders don't exist, cause an empty query so that
    // we don't try to display the whole database.
    url += "&excludeItems=1";
  }
  return url;
}

/**
 * A mapping of root folder names to Guids. To help fixupRootFolderGuid.
 */
const rootToFolderGuidMap = {
  placesRoot: PlacesUtils.bookmarks.rootGuid,
  bookmarksMenuFolder: PlacesUtils.bookmarks.menuGuid,
  unfiledBookmarksFolder: PlacesUtils.bookmarks.unfiledGuid,
  toolbarFolder: PlacesUtils.bookmarks.toolbarGuid,
  mobileFolder: PlacesUtils.bookmarks.mobileGuid,
};

/**
 * Updates a bookmark node from the json version to the places GUID. This
 * will only change GUIDs for the built-in folders. Other folders will remain
 * unchanged.
 *
 * @param {Object} A bookmark node that is updated with the new GUID if necessary.
 */
function fixupRootFolderGuid(node) {
  if (!node.guid && node.root && node.root in rootToFolderGuidMap) {
    node.guid = rootToFolderGuidMap[node.root];
  }
}

/**
 * Translates the JSON types for a node and its children into Places compatible
 * types. Also handles updating of other parameters e.g. dateAdded and lastModified.
 *
 * @param {Object} node A node to be updated. If it contains children, they will
 *                      be updated as well.
 * @return {Array} An array containing two items:
 *       - {Object} A map of current folder ids to GUIDS
 *       - {Array} An array of GUIDs for nodes that contain query URIs
 */
function translateTreeTypes(node) {
  let folderIdToGuidMap = {};

  // Do the uri fixup first, so we can be consistent in this function.
  if (node.uri) {
    node.url = node.uri;
    delete node.uri;
  }

  switch (node.type) {
    case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER: {
      node.type = PlacesUtils.bookmarks.TYPE_FOLDER;

      // Older type mobile folders have a random guid with an annotation. We need
      // to make sure those go into the proper mobile folder.
      let isMobileFolder =
        node.annos &&
        node.annos.some(anno => anno.name == PlacesUtils.MOBILE_ROOT_ANNO);
      if (isMobileFolder) {
        node.guid = PlacesUtils.bookmarks.mobileGuid;
      } else {
        // In case the Guid is broken, we need to fix it up.
        fixupRootFolderGuid(node);
      }

      // Record the current id and the guid so that we can update any search
      // queries later.
      folderIdToGuidMap[node.id] = node.guid;
      break;
    }
    case PlacesUtils.TYPE_X_MOZ_PLACE:
      node.type = PlacesUtils.bookmarks.TYPE_BOOKMARK;
      break;
    case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR:
      node.type = PlacesUtils.bookmarks.TYPE_SEPARATOR;
      if ("title" in node) {
        delete node.title;
      }
      break;
    default:
      // No need to throw/reject here, insertTree will remove this node automatically.
      console.error("Unexpected bookmark type", node.type);
      break;
  }

  if (node.dateAdded) {
    node.dateAdded = PlacesUtils.toDate(node.dateAdded);
  }

  if (node.lastModified) {
    let lastModified = PlacesUtils.toDate(node.lastModified);
    // Ensure we get a last modified date that's later or equal to the dateAdded
    // so that we don't upset the Bookmarks API.
    if (lastModified >= node.dateAdded) {
      node.lastModified = lastModified;
    } else {
      delete node.lastModified;
    }
  }

  if (node.tags) {
    // Separate any tags into an array, and ignore any that are too long.
    node.tags = node.tags
      .split(",")
      .filter(
        aTag =>
          !!aTag.length && aTag.length <= PlacesUtils.bookmarks.MAX_TAG_LENGTH
      );

    // If we end up with none, then delete the property completely.
    if (!node.tags.length) {
      delete node.tags;
    }
  }

  // Sometimes postData can be null, so delete it to make the validators happy.
  if (node.postData == null) {
    delete node.postData;
  }

  // Now handle any children.
  if (!node.children) {
    return folderIdToGuidMap;
  }

  // First sort the children by index.
  node.children = node.children.sort((a, b) => {
    return a.index - b.index;
  });

  // Now do any adjustments required for the children.
  for (let child of node.children) {
    let folders = translateTreeTypes(child);
    folderIdToGuidMap = Object.assign(folderIdToGuidMap, folders);
  }

  return folderIdToGuidMap;
}

/**
 * Handles inserting favicons into the database for a bookmark node.
 * It is assumed the node has already been inserted into the bookmarks
 * database.
 *
 * @param {Object} node The bookmark node for icons to be inserted.
 */
function insertFaviconForNode(node) {
  if (node.icon) {
    try {
      PlacesUtils.favicons.setFaviconForPage(
        Services.io.newURI(node.url),
        // Create a fake faviconURI to use (FIXME: bug 523932)
        Services.io.newURI("fake-favicon-uri:" + node.url),
        Services.io.newURI(node.icon)
      );
    } catch (ex) {
      console.error("Failed to import favicon data:", ex);
    }
  }

  if (!node.iconUri) {
    return;
  }

  try {
    PlacesUtils.favicons.setAndFetchFaviconForPage(
      Services.io.newURI(node.url),
      Services.io.newURI(node.iconUri),
      false,
      PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE,
      null,
      Services.scriptSecurityManager.getSystemPrincipal()
    );
  } catch (ex) {
    console.error("Failed to import favicon URI:" + ex);
  }
}

/**
 * Handles inserting favicons into the database for a bookmark tree - a node
 * and its children.
 *
 * It is assumed the nodes have already been inserted into the bookmarks
 * database.
 *
 * @param {Object} nodeTree The bookmark node tree for icons to be inserted.
 */
function insertFaviconsForTree(nodeTree) {
  insertFaviconForNode(nodeTree);

  if (nodeTree.children) {
    for (let child of nodeTree.children) {
      insertFaviconsForTree(child);
    }
  }
}
PK
!<,���IImodules/BookmarkList.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
});

const OBSERVER_DEBOUNCE_RATE_MS = 500;
const OBSERVER_DEBOUNCE_TIMEOUT_MS = 5000;

/**
 * A collection of bookmarks that internally stays up-to-date in order to
 * efficiently query whether certain URLs are bookmarked.
 */
export class BookmarkList {
  /**
   * The set of hashed URLs that need to be fetched from the database.
   *
   * @type {Set<string>}
   */
  #urlsToFetch = new Set();

  /**
   * The function to call when changes are made.
   *
   * @type {function}
   */
  #observer;

  /**
   * Cached mapping of hashed URLs to how many bookmarks they are used in.
   *
   * @type {Map<string, number>}
   */
  #bookmarkCount = new Map();

  /**
   * Cached mapping of bookmark GUIDs to their respective URL hashes.
   *
   * @type {Map<string, string>}
   */
  #guidToUrl = new Map();

  /**
   * @type {DeferredTask}
   */
  #observerTask;

  /**
   * Construct a new BookmarkList.
   *
   * @param {string[]} urls
   *   The initial set of URLs to track.
   * @param {function} [observer]
   *   The function to call when changes are made.
   * @param {number} [debounceRate]
   *   Time between observer executions, in milliseconds.
   * @param {number} [debounceTimeout]
   *   The maximum time to wait for an idle callback, in milliseconds.
   */
  constructor(urls, observer, debounceRate, debounceTimeout) {
    this.setTrackedUrls(urls);
    this.#observer = observer;
    this.handlePlacesEvents = this.handlePlacesEvents.bind(this);
    this.addListeners(debounceRate, debounceTimeout);
  }

  /**
   * Add places listeners to this bookmark list. The observer (if one was
   * provided) will be called after processing any events.
   *
   * @param {number} [debounceRate]
   *   Time between observer executions, in milliseconds.
   * @param {number} [debounceTimeout]
   *   The maximum time to wait for an idle callback, in milliseconds.
   */
  addListeners(
    debounceRate = OBSERVER_DEBOUNCE_RATE_MS,
    debounceTimeout = OBSERVER_DEBOUNCE_TIMEOUT_MS
  ) {
    lazy.PlacesUtils.observers.addListener(
      ["bookmark-added", "bookmark-removed", "bookmark-url-changed"],
      this.handlePlacesEvents
    );
    this.#observerTask = new lazy.DeferredTask(
      () => this.#observer?.(),
      debounceRate,
      debounceTimeout
    );
  }

  /**
   * Update the set of URLs to track.
   *
   * @param {string[]} urls
   */
  async setTrackedUrls(urls) {
    const updatedBookmarkCount = new Map();
    for (const url of urls) {
      // Use cached value if possible. Otherwise, it must be fetched from db.
      const urlHash = lazy.PlacesUtils.history.hashURL(url);
      const count = this.#bookmarkCount.get(urlHash);
      if (count != undefined) {
        updatedBookmarkCount.set(urlHash, count);
      } else {
        this.#urlsToFetch.add(urlHash);
      }
    }
    this.#bookmarkCount = updatedBookmarkCount;

    const updateGuidToUrl = new Map();
    for (const [guid, urlHash] of this.#guidToUrl.entries()) {
      if (updatedBookmarkCount.has(urlHash)) {
        updateGuidToUrl.set(guid, urlHash);
      }
    }
    this.#guidToUrl = updateGuidToUrl;
  }

  /**
   * Check whether the given URL is bookmarked.
   *
   * @param {string} url
   * @returns {boolean}
   *   The result, or `undefined` if the URL is not tracked.
   */
  async isBookmark(url) {
    if (this.#urlsToFetch.size) {
      await this.#fetchTrackedUrls();
    }
    const urlHash = lazy.PlacesUtils.history.hashURL(url);
    const count = this.#bookmarkCount.get(urlHash);
    return count != undefined ? Boolean(count) : count;
  }

  /**
   * Run the database query and populate the bookmarks cache with the URLs
   * that are waiting to be fetched.
   */
  async #fetchTrackedUrls() {
    const urls = [...this.#urlsToFetch];
    this.#urlsToFetch = new Set();
    for (const urlHash of urls) {
      this.#bookmarkCount.set(urlHash, 0);
    }
    const db = await lazy.PlacesUtils.promiseDBConnection();
    for (const chunk of lazy.PlacesUtils.chunkArray(urls, db.variableLimit)) {
      // Note that this query does not *explicitly* filter out tags, but we
      // should not expect to find any, unless the db is somehow malformed.
      const sql = `SELECT b.guid, p.url_hash
        FROM moz_bookmarks b
        JOIN moz_places p
        ON b.fk = p.id
        WHERE p.url_hash IN (${Array(chunk.length).fill("?").join(",")})`;
      const rows = await db.executeCached(sql, chunk);
      for (const row of rows) {
        this.#cacheBookmark(
          row.getResultByName("guid"),
          row.getResultByName("url_hash")
        );
      }
    }
  }

  /**
   * Handle bookmark events and update the cache accordingly.
   *
   * @param {PlacesEvent[]} events
   */
  async handlePlacesEvents(events) {
    let cacheUpdated = false;
    let needsFetch = false;
    for (const { guid, type, url } of events) {
      const urlHash = lazy.PlacesUtils.history.hashURL(url);
      if (this.#urlsToFetch.has(urlHash)) {
        needsFetch = true;
        continue;
      }
      const isUrlTracked = this.#bookmarkCount.has(urlHash);
      switch (type) {
        case "bookmark-added":
          if (isUrlTracked) {
            this.#cacheBookmark(guid, urlHash);
            cacheUpdated = true;
          }
          break;
        case "bookmark-removed":
          if (isUrlTracked) {
            this.#removeCachedBookmark(guid, urlHash);
            cacheUpdated = true;
          }
          break;
        case "bookmark-url-changed": {
          const oldUrlHash = this.#guidToUrl.get(guid);
          if (oldUrlHash) {
            this.#removeCachedBookmark(guid, oldUrlHash);
            cacheUpdated = true;
          }
          if (isUrlTracked) {
            this.#cacheBookmark(guid, urlHash);
            cacheUpdated = true;
          }
          break;
        }
      }
    }
    if (needsFetch) {
      await this.#fetchTrackedUrls();
      cacheUpdated = true;
    }
    if (cacheUpdated) {
      this.#observerTask.arm();
    }
  }

  /**
   * Remove places listeners from this bookmark list. URLs are no longer
   * tracked.
   *
   * In order to resume tracking, you must call `setTrackedUrls()` followed by
   * `addListeners()`.
   */
  removeListeners() {
    lazy.PlacesUtils.observers.removeListener(
      ["bookmark-added", "bookmark-removed", "bookmark-url-changed"],
      this.handlePlacesEvents
    );
    if (!this.#observerTask.isFinalized) {
      this.#observerTask.disarm();
      this.#observerTask.finalize();
    }
    this.setTrackedUrls([]);
  }

  /**
   * Store a bookmark in the cache.
   *
   * @param {string} guid
   * @param {string} urlHash
   */
  #cacheBookmark(guid, urlHash) {
    const count = this.#bookmarkCount.get(urlHash);
    this.#bookmarkCount.set(urlHash, count + 1);
    this.#guidToUrl.set(guid, urlHash);
  }

  /**
   * Remove a bookmark from the cache.
   *
   * @param {string} guid
   * @param {string} urlHash
   */
  #removeCachedBookmark(guid, urlHash) {
    const count = this.#bookmarkCount.get(urlHash);
    this.#bookmarkCount.set(urlHash, count - 1);
    this.#guidToUrl.delete(guid);
  }
}
PK
!<dO�O�modules/Bookmarks.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This module provides an asynchronous API for managing bookmarks.
 *
 * Bookmarks are organized in a tree structure, and include URLs, folders and
 * separators.  Multiple bookmarks for the same URL are allowed.
 *
 * Note that if you are handling bookmarks operations in the UI, you should
 * not use this API directly, but rather use PlacesTransactions, so that
 * any operation is undo/redo-able.
 *
 * Each bookmark-item is represented by an object having the following
 * properties:
 *
 *  - guid (string)
 *      The globally unique identifier of the item.
 *  - parentGuid (string)
 *      The globally unique identifier of the folder containing the item.
 *      This will be an empty string for the Places root folder.
 *  - index (number)
 *      The 0-based position of the item in the parent folder.
 *  - dateAdded (Date)
 *      The time at which the item was added.
 *  - lastModified (Date)
 *      The time at which the item was last modified.
 *  - type (number)
 *      The item's type, either TYPE_BOOKMARK, TYPE_FOLDER or TYPE_SEPARATOR.
 *
 *  The following properties are only valid for URLs or folders.
 *
 *  - title (string)
 *      The item's title, if any.  Empty titles and null titles are considered
 *      the same. Titles longer than DB_TITLE_LENGTH_MAX will be truncated.
 *
 *  The following properties are only valid for URLs:
 *
 *  - url (URL, href or nsIURI)
 *      The item's URL.  Note that while input objects can contains either
 *      an URL object, an href string, or an nsIURI, output objects will always
 *      contain an URL object.
 *      An URL cannot be longer than DB_URL_LENGTH_MAX, methods will throw if a
 *      longer value is provided.
 *
 * Each successful operation notifies through the PlacesObservers
 * interface.  To listen to such notifications you must register using
 * PlacesUtils.observers.addListener and PlacesUtils.observers.removeListener
 * methods.
 * Note that bookmark addition or order changes won't notify bookmark-moved for
 * items that have their indexes changed.
 * Similarly, lastModified changes not done explicitly (like changing another
 * property) won't fire a bookmark-time-changed notification.
 * @see PlacesObservers
 */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
  PlacesSyncUtils: "resource://gre/modules/PlacesSyncUtils.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
});

const MATCH_ANYWHERE_UNMODIFIED =
  Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE_UNMODIFIED;
const BEHAVIOR_BOOKMARK = Ci.mozIPlacesAutoComplete.BEHAVIOR_BOOKMARK;

export var Bookmarks = Object.freeze({
  /**
   * Item's type constants.
   * These should stay consistent with nsINavBookmarksService.idl
   */
  TYPE_BOOKMARK: 1,
  TYPE_FOLDER: 2,
  TYPE_SEPARATOR: 3,

  /**
   * Sync status constants, stored for each item.
   */
  SYNC_STATUS: {
    UNKNOWN: Ci.nsINavBookmarksService.SYNC_STATUS_UNKNOWN,
    NEW: Ci.nsINavBookmarksService.SYNC_STATUS_NEW,
    NORMAL: Ci.nsINavBookmarksService.SYNC_STATUS_NORMAL,
  },

  /**
   * Default index used to append a bookmark-item at the end of a folder.
   * This should stay consistent with nsINavBookmarksService.idl
   */
  DEFAULT_INDEX: -1,

  /**
   * Maximum length of a tag.
   * Any tag above this length is rejected.
   */
  MAX_TAG_LENGTH: 100,

  /**
   * Bookmark change source constants, passed as optional properties and
   * forwarded to observers. See nsINavBookmarksService.idl for an explanation.
   */
  SOURCES: {
    DEFAULT: Ci.nsINavBookmarksService.SOURCE_DEFAULT,
    SYNC: Ci.nsINavBookmarksService.SOURCE_SYNC,
    IMPORT: Ci.nsINavBookmarksService.SOURCE_IMPORT,
    SYNC_REPARENT_REMOVED_FOLDER_CHILDREN:
      Ci.nsINavBookmarksService.SOURCE_SYNC_REPARENT_REMOVED_FOLDER_CHILDREN,
    RESTORE: Ci.nsINavBookmarksService.SOURCE_RESTORE,
    RESTORE_ON_STARTUP: Ci.nsINavBookmarksService.SOURCE_RESTORE_ON_STARTUP,
  },

  /**
   * Special GUIDs associated with bookmark roots.
   * It's guaranteed that the roots will always have these guids.
   */
  rootGuid: "root________",
  menuGuid: "menu________",
  toolbarGuid: "toolbar_____",
  unfiledGuid: "unfiled_____",
  mobileGuid: "mobile______",

  // With bug 424160, tags will stop being bookmarks, thus this root will
  // be removed.  Do not rely on this, rather use the tagging service API.
  tagsGuid: "tags________",

  /**
   * The GUIDs of the user content root folders that we support, for easy access
   * as a set.
   */
  userContentRoots: [
    "toolbar_____",
    "menu________",
    "unfiled_____",
    "mobile______",
  ],

  /**
   * GUID associated with bookmarks that haven't been saved to the database yet.
   */
  unsavedGuid: "new_________",

  /**
   * GUIDs associated with virtual queries that are used for displaying bookmark
   * folders in the left pane.
   */
  virtualMenuGuid: "menu_______v",
  virtualToolbarGuid: "toolbar____v",
  virtualUnfiledGuid: "unfiled____v",
  virtualMobileGuid: "mobile_____v",

  /**
   * Checks if a guid is a virtual root.
   *
   * @param {String} guid The guid of the item to look for.
   * @returns {Boolean} true if guid is a virtual root, false otherwise.
   */
  isVirtualRootItem(guid) {
    return (
      guid == lazy.PlacesUtils.bookmarks.virtualMenuGuid ||
      guid == lazy.PlacesUtils.bookmarks.virtualToolbarGuid ||
      guid == lazy.PlacesUtils.bookmarks.virtualUnfiledGuid ||
      guid == lazy.PlacesUtils.bookmarks.virtualMobileGuid
    );
  },

  /**
   * Returns the title to use on the UI for a bookmark item. Root folders
   * in the database don't store fully localised versions of the title. To
   * get those this function should be called.
   *
   * Hence, this function should only be called if a root folder object is
   * likely to be displayed to the user.
   *
   * @param {Object} info An object representing a bookmark-item.
   * @returns {String} The correct string.
   * @throws {Error} If the guid in PlacesUtils.bookmarks.userContentRoots is
   *                 not supported.
   */
  getLocalizedTitle(info) {
    if (!lazy.PlacesUtils.bookmarks.userContentRoots.includes(info.guid)) {
      return info.title;
    }

    switch (info.guid) {
      case lazy.PlacesUtils.bookmarks.toolbarGuid:
        return lazy.PlacesUtils.getString("BookmarksToolbarFolderTitle");
      case lazy.PlacesUtils.bookmarks.menuGuid:
        return lazy.PlacesUtils.getString("BookmarksMenuFolderTitle");
      case lazy.PlacesUtils.bookmarks.unfiledGuid:
        return lazy.PlacesUtils.getString("OtherBookmarksFolderTitle");
      case lazy.PlacesUtils.bookmarks.mobileGuid:
        return lazy.PlacesUtils.getString("MobileBookmarksFolderTitle");
      default:
        throw new Error(
          `Unsupported guid ${info.guid} passed to getLocalizedTitle!`
        );
    }
  },

  /**
   * Inserts a bookmark-item into the bookmarks tree.
   *
   * For creating a bookmark, the following set of properties is required:
   *  - type
   *  - parentGuid
   *  - url, only for bookmarked URLs
   *
   * If an index is not specified, it defaults to appending.
   * It's also possible to pass a non-existent GUID to force creation of an
   * item with the given GUID, but unless you have a very sound reason, such as
   * an undo manager implementation or synchronization, don't do that.
   *
   * Note that any known properties that don't apply to the specific item type
   * cause an exception.
   *
   * @param info
   *        object representing a bookmark-item.
   *
   * @return {Promise} resolved when the creation is complete.
   * @resolves to an object representing the created bookmark.
   * @rejects if it's not possible to create the requested bookmark.
   * @throws if the arguments are invalid.
   */
  insert(info) {
    let now = new Date();
    let addedTime = (info && info.dateAdded) || now;
    let modTime = addedTime;
    if (addedTime > now) {
      modTime = now;
    }
    let insertInfo = validateBookmarkObject("Bookmarks.sys.mjs: insert", info, {
      type: { defaultValue: this.TYPE_BOOKMARK },
      index: { defaultValue: this.DEFAULT_INDEX },
      url: {
        requiredIf: b => b.type == this.TYPE_BOOKMARK,
        validIf: b => b.type == this.TYPE_BOOKMARK,
      },
      parentGuid: {
        required: true,
        // Inserting into the root folder is not allowed unless it's testing.
        validIf: b =>
          lazy.PlacesUtils.isInAutomation || b.parentGuid != this.rootGuid,
      },
      title: {
        defaultValue: "",
        validIf: b =>
          b.type == this.TYPE_BOOKMARK ||
          b.type == this.TYPE_FOLDER ||
          b.title === "",
      },
      dateAdded: { defaultValue: addedTime },
      lastModified: {
        defaultValue: modTime,
        validIf: b =>
          b.lastModified >= now ||
          (b.dateAdded && b.lastModified >= b.dateAdded),
      },
      source: { defaultValue: this.SOURCES.DEFAULT },
    });

    return (async () => {
      // Ensure the parent exists.
      let parent = await fetchBookmark({ guid: insertInfo.parentGuid });
      if (!parent) {
        throw new Error("parentGuid must be valid");
      }

      // Set index in the appending case.
      if (
        insertInfo.index == this.DEFAULT_INDEX ||
        insertInfo.index > parent._childCount
      ) {
        insertInfo.index = parent._childCount;
      }

      let item = await insertBookmark(insertInfo, parent);
      let itemDetailMap = await getBookmarkDetailMap([item.guid]);
      let itemDetail = itemDetailMap.get(item.guid);

      // Pass tagging information for the observers to skip over these notifications when needed.
      let isTagging = parent._parentId == lazy.PlacesUtils.tagsFolderId;
      let isTagsFolder = parent._id == lazy.PlacesUtils.tagsFolderId;
      let url = "";
      if (item.type == Bookmarks.TYPE_BOOKMARK) {
        url = item.url.href;
      }

      const notifications = [
        new PlacesBookmarkAddition({
          id: itemDetail.id,
          url,
          itemType: item.type,
          parentId: parent._id,
          index: item.index,
          title: item.title,
          dateAdded: item.dateAdded,
          guid: item.guid,
          parentGuid: item.parentGuid,
          source: item.source,
          isTagging: isTagging || isTagsFolder,
          tags: itemDetail.tags,
          frecency: itemDetail.frecency,
          hidden: itemDetail.hidden,
          visitCount: itemDetail.visitCount,
          lastVisitDate: itemDetail.lastVisitDate,
          targetFolderGuid: itemDetail.targetFolderGuid,
          targetFolderItemId: itemDetail.targetFolderItemId,
          targetFolderTitle: itemDetail.targetFolderTitle,
        }),
      ];

      // If it's a tag, notify bookmark-tags-changed event to all bookmarks for this URL.
      if (isTagging) {
        for (let entry of await fetchBookmarksByURL(item, {
          concurrent: true,
        })) {
          notifications.push(
            new PlacesBookmarkTags({
              id: entry._id,
              itemType: entry.type,
              url,
              guid: entry.guid,
              parentGuid: entry.parentGuid,
              tags: entry._tags,
              lastModified: entry.lastModified,
              source: item.source,
              isTagging: false,
            })
          );
        }
      }

      PlacesObservers.notifyListeners(notifications);

      // Remove non-enumerable properties.
      delete item.source;
      return Object.assign({}, item);
    })();
  },

  /**
   * Inserts a bookmark-tree into the existing bookmarks tree.
   *
   * All the specified folders and bookmarks will be inserted as new, even
   * if duplicates. There's no merge support at this time.
   *
   * The input should be of the form:
   * {
   *   guid: "<some-existing-guid-to-use-as-parent>",
   *   source: "<some valid source>", (optional)
   *   children: [
   *     ... valid bookmark objects.
   *   ]
   * }
   *
   * Children will be appended to any existing children of the parent
   * that is specified. The source specified on the root of the tree
   * will be used for all the items inserted. Any indices or custom parentGuids
   * set on children will be ignored and overwritten.
   *
   * @param {Object} tree
   *        object representing a tree of bookmark items to insert.
   * @param {Object} options [optional]
   *        object with properties representing options.  Current options are:
   *         - fixupOrSkipInvalidEntries: makes the insert more lenient to
   *           mistakes in the input tree.  Properties of an entry that are
   *           fixable will be corrected, otherwise the entry will be skipped.
   *           This is particularly convenient for import/restore operations,
   *           but should not be abused for common inserts, since it may hide
   *           bugs in the calling code.
   *
   * @return {Promise} resolved when the creation is complete.
   * @resolves to an array of objects representing the created bookmark(s).
   * @rejects if it's not possible to create the requested bookmark.
   * @throws if the arguments are invalid.
   */
  insertTree(tree, options) {
    if (!tree || typeof tree != "object") {
      throw new Error("Should be provided a valid tree object.");
    }
    if (!Array.isArray(tree.children) || !tree.children.length) {
      throw new Error("Should have a non-zero number of children to insert.");
    }
    if (!lazy.PlacesUtils.isValidGuid(tree.guid)) {
      throw new Error(
        `The parent guid is not valid (${tree.guid} ${tree.title}).`
      );
    }
    if (tree.guid == this.rootGuid) {
      throw new Error("Can't insert into the root.");
    }
    if (tree.guid == this.tagsGuid) {
      throw new Error("Can't use insertTree to insert tags.");
    }
    if (
      tree.hasOwnProperty("source") &&
      !Object.values(this.SOURCES).includes(tree.source)
    ) {
      throw new Error("Can't use source value " + tree.source);
    }
    if (options && typeof options != "object") {
      throw new Error("Options should be a valid object");
    }
    let fixupOrSkipInvalidEntries =
      options && !!options.fixupOrSkipInvalidEntries;

    // Serialize the tree into an array of items to insert into the db.
    let insertInfos = [];
    let urlsThatMightNeedPlaces = [];

    // We want to use the same 'last added' time for all the entries
    // we import (so they won't differ by a few ms based on where
    // they are in the tree, and so we don't needlessly construct
    // multiple dates).
    let fallbackLastAdded = new Date();

    const { TYPE_BOOKMARK, TYPE_FOLDER, SOURCES } = this;

    // Reuse the 'source' property for all the entries.
    let source = tree.source || SOURCES.DEFAULT;

    // This is recursive.
    function appendInsertionInfoForInfoArray(infos, indexToUse, parentGuid) {
      // We want to keep the index of items that will be inserted into the root
      // NULL, and then use a subquery to select the right index, to avoid
      // races where other consumers might add items between when we determine
      // the index and when we insert. However, the validator does not allow
      // NULL values in in the index, so we fake it while validating and then
      // correct later. Keep track of whether we're doing this:
      let shouldUseNullIndices = false;
      if (indexToUse === null) {
        shouldUseNullIndices = true;
        indexToUse = 0;
      }

      // When a folder gets an item added, its last modified date is updated
      // to be equal to the date we added the item (if that date is newer).
      // Because we're inserting a tree, we keep track of this date for the
      // loop, updating it for inserted items as well as from any subfolders
      // we insert.
      let lastAddedForParent = new Date(0);
      for (let info of infos) {
        // Ensure to use the same date for dateAdded and lastModified, even if
        // dateAdded may be imposed by the caller.
        let time = (info && info.dateAdded) || fallbackLastAdded;
        let insertInfo = {
          guid: { defaultValue: lazy.PlacesUtils.history.makeGuid() },
          type: { defaultValue: TYPE_BOOKMARK },
          url: {
            requiredIf: b => b.type == TYPE_BOOKMARK,
            validIf: b => b.type == TYPE_BOOKMARK,
          },
          parentGuid: { replaceWith: parentGuid }, // Set the correct parent guid.
          title: {
            defaultValue: "",
            validIf: b =>
              b.type == TYPE_BOOKMARK ||
              b.type == TYPE_FOLDER ||
              b.title === "",
          },
          dateAdded: {
            defaultValue: time,
            validIf: b => !b.lastModified || b.dateAdded <= b.lastModified,
          },
          lastModified: {
            defaultValue: time,
            validIf: b =>
              (!b.dateAdded && b.lastModified >= time) ||
              (b.dateAdded && b.lastModified >= b.dateAdded),
          },
          index: { replaceWith: indexToUse++ },
          source: { replaceWith: source },
          keyword: { validIf: b => b.type == TYPE_BOOKMARK },
          charset: { validIf: b => b.type == TYPE_BOOKMARK },
          postData: { validIf: b => b.type == TYPE_BOOKMARK },
          tags: { validIf: b => b.type == TYPE_BOOKMARK },
          children: {
            validIf: b => b.type == TYPE_FOLDER && Array.isArray(b.children),
          },
        };
        if (fixupOrSkipInvalidEntries) {
          insertInfo.guid.fixup = b =>
            (b.guid = lazy.PlacesUtils.history.makeGuid());
          insertInfo.dateAdded.fixup = insertInfo.lastModified.fixup = b =>
            (b.lastModified = b.dateAdded = fallbackLastAdded);
        }
        try {
          insertInfo = validateBookmarkObject(
            "Bookmarks.sys.mjs: insertTree",
            info,
            insertInfo
          );
        } catch (ex) {
          if (fixupOrSkipInvalidEntries) {
            indexToUse--;
            continue;
          } else {
            throw ex;
          }
        }

        if (shouldUseNullIndices) {
          insertInfo.index = null;
        }
        // Store the URL if this is a bookmark, so we can ensure we create an
        // entry in moz_places for it.
        if (insertInfo.type == Bookmarks.TYPE_BOOKMARK) {
          urlsThatMightNeedPlaces.push(insertInfo.url);
        }

        insertInfos.push(insertInfo);
        // Process any children. We have to use info.children here rather than
        // insertInfo.children because validateBookmarkObject doesn't copy over
        // the children ref, as the default bookmark validators object doesn't
        // know about children.
        if (info.children) {
          // start children of this item off at index 0.
          let childrenLastAdded = appendInsertionInfoForInfoArray(
            info.children,
            0,
            insertInfo.guid
          );
          if (childrenLastAdded > insertInfo.lastModified) {
            insertInfo.lastModified = childrenLastAdded;
          }
          if (childrenLastAdded > lastAddedForParent) {
            lastAddedForParent = childrenLastAdded;
          }
        }

        // Ensure we track what time to update the parent to.
        if (insertInfo.dateAdded > lastAddedForParent) {
          lastAddedForParent = insertInfo.dateAdded;
        }
      }
      return lastAddedForParent;
    }

    // We want to validate synchronously, but we can't know the index at which
    // we're inserting into the parent. We just use NULL instead,
    // and the SQL query with which we insert will update it as necessary.
    let lastAddedForParent = appendInsertionInfoForInfoArray(
      tree.children,
      null,
      tree.guid
    );

    // appendInsertionInfoForInfoArray will remove invalid items and may leave
    // us with nothing to insert, if so, just return early.
    if (!insertInfos.length) {
      return [];
    }

    return (async function () {
      let treeParent = await fetchBookmark({ guid: tree.guid });
      if (!treeParent) {
        throw new Error("The parent you specified doesn't exist.");
      }

      if (treeParent._parentId == lazy.PlacesUtils.tagsFolderId) {
        throw new Error("Can't use insertTree to insert tags.");
      }

      await insertBookmarkTree(
        insertInfos,
        source,
        treeParent,
        urlsThatMightNeedPlaces,
        lastAddedForParent
      );

      // Now update the indices of root items in the objects we return.
      // These may be wrong if someone else modified the table between
      // when we fetched the parent and inserted our items, but the actual
      // inserts will have been correct, and we don't want to query the DB
      // again if we don't have to. bug 1347230 covers improving this.
      let rootIndex = treeParent._childCount;
      for (let insertInfo of insertInfos) {
        if (insertInfo.parentGuid == tree.guid) {
          insertInfo.index += rootIndex++;
        }
      }

      let itemDetailMap = await getBookmarkDetailMap(
        insertInfos.map(info => info.guid)
      );

      let notifications = [];
      for (let i = 0; i < insertInfos.length; i++) {
        let item = insertInfos[i];
        let itemDetail = itemDetailMap.get(item.guid);

        // For sub-folders, we need to make sure their children have the correct parent ids.
        let parentId;
        if (item.parentGuid === treeParent.guid) {
          // This is a direct child of the tree parent, so we can use the
          // existing parent's id.
          parentId = treeParent._id;
        } else {
          // This is a parent folder that's been updated, so we need to
          // use the new item id.
          parentId = itemDetail.parentId;
        }

        let url = "";
        if (item.type == Bookmarks.TYPE_BOOKMARK) {
          url = URL.isInstance(item.url) ? item.url.href : item.url;
        }

        notifications.push(
          new PlacesBookmarkAddition({
            id: itemDetail.id,
            url,
            itemType: item.type,
            parentId,
            index: item.index,
            title: item.title,
            dateAdded: item.dateAdded,
            guid: item.guid,
            parentGuid: item.parentGuid,
            source: item.source,
            isTagging: false,
            tags: itemDetail.tags,
            frecency: itemDetail.frecency,
            hidden: itemDetail.hidden,
            visitCount: itemDetail.visitCount,
            lastVisitDate: itemDetail.lastVisitDate,
            targetFolderGuid: itemDetail.targetFolderGuid,
            targetFolderItemId: itemDetail.targetFolderItemId,
            targetFolderTitle: itemDetail.targetFolderTitle,
          })
        );

        try {
          await handleBookmarkItemSpecialData(itemDetail.id, item);
        } catch (ex) {
          // This is not critical, regardless the bookmark has been created
          // and we should continue notifying the next ones.
          console.error(
            "An error occured while handling special bookmark data:",
            ex
          );
        }

        // Remove non-enumerable properties.
        delete item.source;

        insertInfos[i] = Object.assign({}, item);
      }

      if (notifications.length) {
        PlacesObservers.notifyListeners(notifications);
      }

      return insertInfos;
    })();
  },

  /**
   * Updates a bookmark-item.
   *
   * Only set the properties which should be changed (undefined properties
   * won't be taken into account).
   * Moreover, the item's type or dateAdded cannot be changed, since they are
   * immutable after creation.  Trying to change them will reject.
   *
   * Note that any known properties that don't apply to the specific item type
   * cause an exception.
   *
   * @param info
   *        object representing a bookmark-item, as defined above.
   *
   * @return {Promise} resolved when the update is complete.
   * @resolves to an object representing the updated bookmark.
   * @rejects if it's not possible to update the given bookmark.
   * @throws if the arguments are invalid.
   */
  update(info) {
    // The info object is first validated here to ensure it's consistent, then
    // it's compared to the existing item to remove any properties that don't
    // need to be updated.
    let updateInfo = validateBookmarkObject("Bookmarks.sys.mjs: update", info, {
      guid: { required: true },
      index: {
        requiredIf: b => b.hasOwnProperty("parentGuid"),
        validIf: b => b.index >= 0 || b.index == this.DEFAULT_INDEX,
      },
      parentGuid: { validIf: b => b.parentGuid != this.rootGuid },
      source: { defaultValue: this.SOURCES.DEFAULT },
    });

    // There should be at last one more property in addition to guid and source.
    if (Object.keys(updateInfo).length < 3) {
      throw new Error("Not enough properties to update");
    }

    return (async () => {
      // Ensure the item exists.
      let item = await fetchBookmark(updateInfo);
      if (!item) {
        throw new Error("No bookmarks found for the provided GUID");
      }
      if (updateInfo.hasOwnProperty("type") && updateInfo.type != item.type) {
        throw new Error("The bookmark type cannot be changed");
      }

      // Remove any property that will stay the same.
      removeSameValueProperties(updateInfo, item);
      // Check if anything should still be updated.
      if (Object.keys(updateInfo).length < 3) {
        // Remove non-enumerable properties.
        return Object.assign({}, item);
      }
      const now = new Date();
      let lastModifiedDefault = now;
      // In the case where `dateAdded` is specified, but `lastModified` is not,
      // we only update `lastModified` if it is older than the new `dateAdded`.
      if (!("lastModified" in updateInfo) && "dateAdded" in updateInfo) {
        lastModifiedDefault = new Date(
          Math.max(item.lastModified, updateInfo.dateAdded)
        );
      }
      updateInfo = validateBookmarkObject(
        "Bookmarks.sys.mjs: update",
        updateInfo,
        {
          url: { validIf: () => item.type == this.TYPE_BOOKMARK },
          title: {
            validIf: () =>
              [this.TYPE_BOOKMARK, this.TYPE_FOLDER].includes(item.type),
          },
          lastModified: {
            defaultValue: lastModifiedDefault,
            validIf: b =>
              b.lastModified >= now ||
              b.lastModified >= (b.dateAdded || item.dateAdded),
          },
          dateAdded: { defaultValue: item.dateAdded },
        }
      );

      return lazy.PlacesUtils.withConnectionWrapper(
        "Bookmarks.sys.mjs: update",
        async db => {
          let parent;
          if (updateInfo.hasOwnProperty("parentGuid")) {
            if (lazy.PlacesUtils.isRootItem(item.guid)) {
              throw new Error("It's not possible to move Places root folders.");
            }
            if (item.type == this.TYPE_FOLDER) {
              // Make sure we are not moving a folder into itself or one of its
              // descendants.
              let rows = await db.executeCached(
                `WITH RECURSIVE
               descendants(did) AS (
                 VALUES(:id)
                 UNION ALL
                 SELECT id FROM moz_bookmarks
                 JOIN descendants ON parent = did
                 WHERE type = :type
               )
               SELECT guid FROM moz_bookmarks
               WHERE id IN descendants
              `,
                { id: item._id, type: this.TYPE_FOLDER }
              );
              if (
                rows
                  .map(r => r.getResultByName("guid"))
                  .includes(updateInfo.parentGuid)
              ) {
                throw new Error(
                  "Cannot insert a folder into itself or one of its descendants"
                );
              }
            }

            parent = await fetchBookmark({ guid: updateInfo.parentGuid });
            if (!parent) {
              throw new Error("No bookmarks found for the provided parentGuid");
            }
          }

          if (updateInfo.hasOwnProperty("index")) {
            if (lazy.PlacesUtils.isRootItem(item.guid)) {
              throw new Error("It's not possible to move Places root folders.");
            }
            // If at this point we don't have a parent yet, we are moving into
            // the same container.  Thus we know it exists.
            if (!parent) {
              parent = await fetchBookmark({ guid: item.parentGuid });
            }

            if (
              updateInfo.index >= parent._childCount ||
              updateInfo.index == this.DEFAULT_INDEX
            ) {
              updateInfo.index = parent._childCount;

              // Fix the index when moving within the same container.
              if (parent.guid == item.parentGuid) {
                updateInfo.index--;
              }
            }
          }

          let syncChangeDelta =
            lazy.PlacesSyncUtils.bookmarks.determineSyncChangeDelta(
              info.source
            );

          let updatedItem = await db.executeTransaction(async function () {
            let updatedItem = await updateBookmark(
              db,
              updateInfo,
              item,
              item.index,
              parent,
              syncChangeDelta
            );
            if (parent) {
              await setAncestorsLastModified(
                db,
                parent.guid,
                updatedItem.lastModified,
                syncChangeDelta
              );
            }
            return updatedItem;
          });

          const notifications = [];

          // For lastModified, we only care about the original input, since we
          // should not notify implicit lastModified changes.
          if (
            (info.hasOwnProperty("lastModified") &&
              updateInfo.hasOwnProperty("lastModified") &&
              item.lastModified != updatedItem.lastModified) ||
            (info.hasOwnProperty("dateAdded") &&
              updateInfo.hasOwnProperty("dateAdded") &&
              item.dateAdded != updatedItem.dateAdded)
          ) {
            let isTagging = updatedItem.parentGuid == Bookmarks.tagsGuid;
            if (!isTagging) {
              if (!parent) {
                parent = await fetchBookmark({ guid: updatedItem.parentGuid });
              }
              isTagging = parent.parentGuid === Bookmarks.tagsGuid;
            }

            notifications.push(
              new PlacesBookmarkTime({
                id: updatedItem._id,
                itemType: updatedItem.type,
                url: updatedItem.url?.href,
                guid: updatedItem.guid,
                parentGuid: updatedItem.parentGuid,
                dateAdded: updatedItem.dateAdded,
                lastModified: updatedItem.lastModified,
                source: updatedItem.source,
                isTagging,
              })
            );
          }

          if (updateInfo.hasOwnProperty("title")) {
            let isTagging = updatedItem.parentGuid == Bookmarks.tagsGuid;
            if (!isTagging) {
              if (!parent) {
                parent = await fetchBookmark({ guid: updatedItem.parentGuid });
              }
              isTagging = parent.parentGuid === Bookmarks.tagsGuid;
            }

            notifications.push(
              new PlacesBookmarkTitle({
                id: updatedItem._id,
                itemType: updatedItem.type,
                url: updatedItem.url?.href,
                guid: updatedItem.guid,
                parentGuid: updatedItem.parentGuid,
                title: updatedItem.title,
                lastModified: updatedItem.lastModified,
                source: updatedItem.source,
                isTagging,
              })
            );

            // If we're updating a tag, we must notify all the tagged bookmarks
            // about the change.
            if (isTagging) {
              for (let entry of await fetchBookmarksByTags(
                { tags: [updatedItem.title] },
                { concurrent: true }
              )) {
                notifications.push(
                  new PlacesBookmarkTags({
                    id: entry._id,
                    itemType: entry.type,
                    url: entry.url,
                    guid: entry.guid,
                    parentGuid: entry.parentGuid,
                    tags: entry._tags,
                    lastModified: entry.lastModified,
                    source: updatedItem.source,
                    isTagging: false,
                  })
                );
              }
            }
          }
          if (updateInfo.hasOwnProperty("url")) {
            await lazy.PlacesUtils.keywords.reassign(
              item.url,
              updatedItem.url,
              updatedItem.source
            );

            let isTagging = updatedItem.parentGuid == Bookmarks.tagsGuid;
            if (!isTagging) {
              if (!parent) {
                parent = await fetchBookmark({ guid: updatedItem.parentGuid });
              }
              isTagging = parent.parentGuid === Bookmarks.tagsGuid;
            }

            notifications.push(
              new PlacesBookmarkUrl({
                id: updatedItem._id,
                itemType: updatedItem.type,
                url: updatedItem.url.href,
                guid: updatedItem.guid,
                parentGuid: updatedItem.parentGuid,
                source: updatedItem.source,
                isTagging,
                lastModified: updatedItem.lastModified,
              })
            );
          }
          // If the item was moved, notify bookmark-moved.
          if (
            item.parentGuid != updatedItem.parentGuid ||
            item.index != updatedItem.index
          ) {
            let details = (await getBookmarkDetailMap([updatedItem.guid])).get(
              updatedItem.guid
            );
            notifications.push(
              new PlacesBookmarkMoved({
                id: updatedItem._id,
                itemType: updatedItem.type,
                url: updatedItem.url && updatedItem.url.href,
                guid: updatedItem.guid,
                parentGuid: updatedItem.parentGuid,
                source: updatedItem.source,
                index: updatedItem.index,
                oldParentGuid: item.parentGuid,
                oldIndex: item.index,
                isTagging:
                  updatedItem.parentGuid === Bookmarks.tagsGuid ||
                  parent.parentGuid === Bookmarks.tagsGuid,
                title: updatedItem.title,
                tags: details.tags,
                frecency: details.frecency,
                hidden: details.hidden,
                visitCount: details.visitCount,
                dateAdded: updatedItem.dateAdded ?? Date.now(),
                lastVisitDate: details.lastVisitDate,
              })
            );
          }

          if (notifications.length) {
            PlacesObservers.notifyListeners(notifications);
          }

          // Remove non-enumerable properties.
          delete updatedItem.source;
          return Object.assign({}, updatedItem);
        }
      );
    })();
  },

  /**
   * Moves multiple bookmark-items to a specific folder.
   *
   * If you are only updating/moving a single bookmark, use update() instead.
   *
   * @param {Array} guids
   *        An array of GUIDs representing the bookmarks to move.
   * @param {String} parentGuid
   *        Optional, the parent GUID to move the bookmarks to.
   * @param {Integer} index
   *        The index to move the bookmarks to. If this is -1, the bookmarks
   *        will be appended to the folder.
   * @param {Integer} source
   *        One of the Bookmarks.SOURCES.* options, representing the source of
   *        this change.
   *
   * @return {Promise} resolved when the move is complete.
   * @resolves to an array of objects representing the moved bookmarks.
   * @rejects if it's not possible to move the given bookmark(s).
   * @throws if the arguments are invalid.
   */
  moveToFolder(guids, parentGuid, index, source) {
    if (!Array.isArray(guids) || guids.length < 1) {
      throw new Error("guids should be an array of at least one item");
    }
    if (!guids.every(guid => lazy.PlacesUtils.isValidGuid(guid))) {
      throw new Error("Expected only valid GUIDs to be passed.");
    }
    if (parentGuid && !lazy.PlacesUtils.isValidGuid(parentGuid)) {
      throw new Error("parentGuid should be a valid GUID");
    }
    if (parentGuid == lazy.PlacesUtils.bookmarks.rootGuid) {
      throw new Error("Cannot move bookmarks into root.");
    }
    if (typeof index != "number" || index < this.DEFAULT_INDEX) {
      throw new Error(
        `index should be a number greater than ${this.DEFAULT_INDEX}`
      );
    }

    if (!source) {
      source = this.SOURCES.DEFAULT;
    }

    return (async () => {
      let updateInfos = [];
      let syncChangeDelta =
        lazy.PlacesSyncUtils.bookmarks.determineSyncChangeDelta(source);

      await lazy.PlacesUtils.withConnectionWrapper(
        "Bookmarks.sys.mjs: moveToFolder",
        async db => {
          const lastModified = new Date();

          let targetParentGuid = parentGuid || undefined;

          for (let guid of guids) {
            // Ensure the item exists.
            let existingItem = await fetchBookmark({ guid }, { db });
            if (!existingItem) {
              throw new Error("No bookmarks found for the provided GUID");
            }

            if (parentGuid) {
              // We're moving to a different folder.
              if (existingItem.type == this.TYPE_FOLDER) {
                // Make sure we are not moving a folder into itself or one of its
                // descendants.
                let rows = await db.executeCached(
                  `WITH RECURSIVE
                 descendants(did) AS (
                   VALUES(:id)
                   UNION ALL
                   SELECT id FROM moz_bookmarks
                   JOIN descendants ON parent = did
                   WHERE type = :type
                 )
                 SELECT guid FROM moz_bookmarks
                 WHERE id IN descendants
                `,
                  { id: existingItem._id, type: this.TYPE_FOLDER }
                );
                if (
                  rows.map(r => r.getResultByName("guid")).includes(parentGuid)
                ) {
                  throw new Error(
                    "Cannot insert a folder into itself or one of its descendants"
                  );
                }
              }
            } else if (!targetParentGuid) {
              targetParentGuid = existingItem.parentGuid;
            } else if (existingItem.parentGuid != targetParentGuid) {
              throw new Error(
                "All bookmarks should be in the same folder if no parent is specified"
              );
            }

            updateInfos.push({ existingItem, currIndex: existingItem.index });
          }

          let newParent = await fetchBookmark(
            { guid: targetParentGuid },
            { db }
          );

          if (newParent._grandParentId == lazy.PlacesUtils.tagsFolderId) {
            throw new Error("Can't move to a tags folder");
          }

          let newParentChildCount = newParent._childCount;

          await db.executeTransaction(async () => {
            // Now that we have all the existing items, we can do the actual updates.
            for (let i = 0; i < updateInfos.length; i++) {
              let info = updateInfos[i];
              if (index != this.DEFAULT_INDEX) {
                // If we're dropping on the same folder, then we may need to adjust
                // the index to insert at the correct place.
                if (info.existingItem.parentGuid == newParent.guid) {
                  if (index > info.existingItem.index) {
                    // If we're dragging down, we need to go one lower to insert at
                    // the real point as moving the element changes the index of
                    // everything below by 1.
                    index--;
                  } else if (index == info.existingItem.index) {
                    // This isn't moving so we skip it, but copy the data so we have
                    // an easy way for the notifications to check.
                    info.updatedItem = { ...info.existingItem };
                    continue;
                  }
                }
              }

              // Never let the index go higher than the max count of the folder.
              if (index == this.DEFAULT_INDEX || index >= newParentChildCount) {
                index = newParentChildCount;

                // If this is moving within the same folder, then we need to drop the
                // index by one to compensate for "removing" it, then re-inserting.
                if (info.existingItem.parentGuid == newParent.guid) {
                  index--;
                }
              }

              info.updatedItem = await updateBookmark(
                db,
                { lastModified, index },
                info.existingItem,
                info.currIndex,
                newParent,
                syncChangeDelta
              );
              info.newParent = newParent;

              // For items moving within the same folder, we have to keep track
              // of their indexes. Otherwise we run the risk of not correctly
              // updating the indexes of other items in the folder.
              // This section simulates the database write in moveBookmark, which
              // allows us to avoid re-reading the database.
              if (info.existingItem.parentGuid == newParent.guid) {
                let sign = index < info.currIndex ? 1 : -1;
                for (let j = 0; j < updateInfos.length; j++) {
                  if (j == i) {
                    continue;
                  }
                  if (
                    updateInfos[j].currIndex >=
                      Math.min(info.currIndex, index) &&
                    updateInfos[j].currIndex <= Math.max(info.currIndex, index)
                  ) {
                    updateInfos[j].currIndex += sign;
                  }
                }
              }
              info.currIndex = index;

              // We only bump the parent count if we're moving from a different folder.
              if (info.existingItem.parentGuid != newParent.guid) {
                newParentChildCount++;
              }
              index++;
            }

            await setAncestorsLastModified(
              db,
              newParent.guid,
              lastModified,
              syncChangeDelta
            );
          });
        }
      );

      const notifications = [];
      let detailsMap = await getBookmarkDetailMap(
        updateInfos.map(({ updatedItem }) => updatedItem.guid)
      );
      // Updates complete, time to notify everyone.
      for (let { updatedItem, existingItem, newParent } of updateInfos) {
        // If the item was moved, notify bookmark-moved.
        // We use the updatedItem.index here, rather than currIndex, as the views
        // need to know where we inserted the item as opposed to where it ended
        // up.
        if (
          existingItem.parentGuid != updatedItem.parentGuid ||
          existingItem.index != updatedItem.index
        ) {
          let details = detailsMap.get(updatedItem.guid);
          notifications.push(
            new PlacesBookmarkMoved({
              id: updatedItem._id,
              itemType: updatedItem.type,
              url: existingItem.url,
              guid: updatedItem.guid,
              parentGuid: updatedItem.parentGuid,
              source,
              index: updatedItem.index,
              oldParentGuid: existingItem.parentGuid,
              oldIndex: existingItem.index,
              isTagging:
                updatedItem.parentGuid === Bookmarks.tagsGuid ||
                newParent.parentGuid === Bookmarks.tagsGuid,
              title: updatedItem.title,
              tags: details.tags,
              frecency: details.frecency,
              hidden: details.hidden,
              visitCount: details.visitCount,
              dateAdded: updatedItem.dateAdded,
              lastVisitDate: details.lastVisitDate,
            })
          );
        }
        // Remove non-enumerable properties.
        delete updatedItem.source;
      }

      if (notifications.length) {
        PlacesObservers.notifyListeners(notifications);
      }

      return updateInfos.map(updateInfo =>
        Object.assign({}, updateInfo.updatedItem)
      );
    })();
  },

  /**
   * Removes one or more bookmark-items.
   *
   * @param guidOrInfo This may be:
   *        - The globally unique identifier of the item to remove
   *        - an object representing the item, as defined above
   *        - an array of objects representing the items to be removed
   * @param {Object} [options={}]
   *        Additional options that can be passed to the function.
   *        Currently supports the following properties:
   *         - preventRemovalOfNonEmptyFolders: Causes an exception to be
   *           thrown when attempting to remove a folder that is not empty.
   *         - source: The change source, forwarded to all bookmark observers.
   *           Defaults to nsINavBookmarksService::SOURCE_DEFAULT.
   *
   * @return {Promise}
   * @resolves when the removal is complete
   * @rejects if the provided guid doesn't match any existing bookmark.
   * @throws if the arguments are invalid.
   */
  remove(guidOrInfo, options = {}) {
    let infos = guidOrInfo;
    if (!infos) {
      throw new Error("Input should be a valid object");
    }
    if (!Array.isArray(guidOrInfo)) {
      if (typeof guidOrInfo != "object") {
        infos = [{ guid: guidOrInfo }];
      } else {
        infos = [guidOrInfo];
      }
    }

    if (!("source" in options)) {
      options.source = Bookmarks.SOURCES.DEFAULT;
    }

    let removeInfos = [];
    for (let info of infos) {
      // Disallow removing the root folders.
      if (
        [
          Bookmarks.rootGuid,
          Bookmarks.menuGuid,
          Bookmarks.toolbarGuid,
          Bookmarks.unfiledGuid,
          Bookmarks.tagsGuid,
          Bookmarks.mobileGuid,
        ].includes(info.guid)
      ) {
        throw new Error("It's not possible to remove Places root folders.");
      }

      // Even if we ignore any other unneeded property, we still validate any
      // known property to reduce likelihood of hidden bugs.
      let removeInfo = validateBookmarkObject(
        "Bookmarks.sys.mjs: remove",
        info
      );
      removeInfos.push(removeInfo);
    }

    return (async function () {
      let removeItems = [];
      for (let info of removeInfos) {
        // We must be able to remove a bookmark even if it has an invalid url.
        // In that case the item won't have a url property.
        let item = await fetchBookmark(info, { ignoreInvalidURLs: true });
        if (!item) {
          throw new Error("No bookmarks found for the provided GUID.");
        }

        removeItems.push(item);
      }

      await removeBookmarks(removeItems, options);

      // Notify bookmark-removed to listeners.
      let notifications = [];

      for (let item of removeItems) {
        let isUntagging = item._grandParentId == lazy.PlacesUtils.tagsFolderId;
        let url = "";
        if (item.type == Bookmarks.TYPE_BOOKMARK) {
          url = item.hasOwnProperty("url") ? item.url.href : null;
        }

        notifications.push(
          new PlacesBookmarkRemoved({
            id: item._id,
            url,
            title: item.title,
            itemType: item.type,
            parentId: item._parentId,
            index: item.index,
            guid: item.guid,
            parentGuid: item.parentGuid,
            source: options.source,
            isTagging: isUntagging,
            isDescendantRemoval: false,
          })
        );

        if (isUntagging) {
          for (let entry of await fetchBookmarksByURL(item, {
            concurrent: true,
          })) {
            notifications.push(
              new PlacesBookmarkTags({
                id: entry._id,
                itemType: entry.type,
                url,
                guid: entry.guid,
                parentGuid: entry.parentGuid,
                tags: entry._tags,
                lastModified: entry.lastModified,
                source: options.source,
                isTagging: false,
              })
            );
          }
        }
      }

      PlacesObservers.notifyListeners(notifications);
    })();
  },

  /**
   * Removes ALL bookmarks, resetting the bookmarks storage to an empty tree.
   *
   * Note that roots are preserved, only their children will be removed.
   *
   * @param {Object} [options={}]
   *        Additional options. Currently supports the following properties:
   *         - source: The change source, forwarded to all bookmark observers.
   *           Defaults to nsINavBookmarksService::SOURCE_DEFAULT.
   *
   * @return {Promise} resolved when the removal is complete.
   * @resolves once the removal is complete.
   */
  eraseEverything(options = {}) {
    if (!options.source) {
      options.source = Bookmarks.SOURCES.DEFAULT;
    }

    return lazy.PlacesUtils.withConnectionWrapper(
      "Bookmarks.sys.mjs: eraseEverything",
      async function (db) {
        let urls;
        await db.executeTransaction(async function () {
          urls = await removeFoldersContents(
            db,
            Bookmarks.userContentRoots,
            options
          );
          const time = lazy.PlacesUtils.toPRTime(new Date());
          const syncChangeDelta =
            lazy.PlacesSyncUtils.bookmarks.determineSyncChangeDelta(
              options.source
            );
          for (let folderGuid of Bookmarks.userContentRoots) {
            await db.executeCached(
              `UPDATE moz_bookmarks SET lastModified = :time,
                                        syncChangeCounter = syncChangeCounter + :syncChangeDelta
               WHERE id IN (SELECT id FROM moz_bookmarks WHERE guid = :folderGuid )
              `,
              { folderGuid, time, syncChangeDelta }
            );
          }

          await lazy.PlacesSyncUtils.bookmarks.resetSyncMetadata(
            db,
            options.source
          );
        });

        if (urls?.length) {
          await lazy.PlacesUtils.keywords.eraseEverything();
        }
      }
    );
  },

  /**
   * Returns a list of recently bookmarked items.
   * Only includes actual bookmarks. Excludes folders, separators and queries.
   *
   * @param {integer} numberOfItems
   *        The maximum number of bookmark items to return.
   *
   * @return {Promise} resolved when the listing is complete.
   * @resolves to an array of recent bookmark-items.
   * @rejects if an error happens while querying.
   */
  getRecent(numberOfItems) {
    if (numberOfItems === undefined) {
      throw new Error("numberOfItems argument is required");
    }
    if (typeof numberOfItems !== "number" || numberOfItems % 1 !== 0) {
      throw new Error("numberOfItems argument must be an integer");
    }
    if (numberOfItems <= 0) {
      throw new Error("numberOfItems argument must be greater than zero");
    }

    return fetchRecentBookmarks(numberOfItems);
  },

  /**
   * Fetches information about a bookmark-item.
   *
   * REMARK: any successful call to this method resolves to a single
   *         bookmark-item (or null), even when multiple bookmarks may exist
   *         (e.g. fetching by url).  If you wish to retrieve all of the
   *         bookmarks for a given match, use the callback instead.
   *
   * Input can be either a guid or an object with one, and only one, of these
   * filtering properties set:
   *  - guid
   *      retrieves the item with the specified guid.
   *  - parentGuid and index
   *      retrieves the item by its position.
   *  - url
   *      retrieves the most recent bookmark having the given URL.
   *      To retrieve ALL of the bookmarks for that URL, you must pass in an
   *      onResult callback, that will be invoked once for each found bookmark.
   *  - guidPrefix
   *      retrieves the most recent item with the specified guid prefix.
   *      To retrieve ALL of the bookmarks for that guid prefix, you must pass
   *      in an onResult callback, that will be invoked once for each bookmark.
   *  - tags
   *      Retrieves the most recent item with all the specified tags.
   *      The tags are matched in a case-insensitive way.
   *      To retrieve ALL of the bookmarks having these tags, pass in an
   *      onResult callback, that will be invoked once for each bookmark.
   *      Note, there can be multiple bookmarks for the same url, if you need
   *      unique tagged urls you can filter duplicates by accumulating in a Set.
   *
   * @param guidOrInfo
   *        The globally unique identifier of the item to fetch, or an
   *        object representing it, as defined above.
   * @param onResult [optional]
   *        Callback invoked for each found bookmark.
   * @param options [optional]
   *        an optional object whose properties describe options for the fetch:
   *         - concurrent: fetches concurrently to any writes, returning results
   *                       faster. On the negative side, it may return stale
   *                       information missing the currently ongoing write.
   *         - includePath: additionally fetches the path for the bookmarks.
   *                        This is a potentially expensive operation.  When
   *                        set to true, the path property is set on results
   *                        containing an array of {title, guid} objects
   *                        ordered from root to leaf.
   *         - includeItemIds:
   *             include .itemId and .parentId in the results.
   *             ALWAYS USE THE GUIDs instead of these, unless it's _really_
   *             necessary to get them, e.g. when sending Places notifications.
   *
   * @return {Promise} resolved when the fetch is complete.
   * @resolves to an object representing the found item, as described above, or
   *           an array of such objects.  if no item is found, the returned
   *           promise is resolved to null.
   * @rejects if an error happens while fetching.
   * @throws if the arguments are invalid.
   *
   * @note Any unknown property in the info object is ignored.  Known properties
   *       may be overwritten.
   */
  async fetch(guidOrInfo, onResult = null, options = {}) {
    if (onResult && typeof onResult != "function") {
      throw new Error("onResult callback must be a valid function");
    }
    let info = guidOrInfo;
    if (!info) {
      throw new Error("Input should be a valid object");
    }
    if (typeof info != "object") {
      info = { guid: guidOrInfo };
    } else if (Object.keys(info).length == 1) {
      // Just a faster code path.
      if (
        !["url", "guid", "parentGuid", "index", "guidPrefix", "tags"].includes(
          Object.keys(info)[0]
        )
      ) {
        throw new Error(`Unexpected number of conditions provided: 0`);
      }
    } else {
      // Only one condition at a time can be provided.
      let conditionsCount = [
        v => v.hasOwnProperty("guid"),
        v => v.hasOwnProperty("parentGuid") && v.hasOwnProperty("index"),
        v => v.hasOwnProperty("url"),
        v => v.hasOwnProperty("guidPrefix"),
        v => v.hasOwnProperty("tags"),
      ].reduce((old, fn) => (old + fn(info)) | 0, 0);
      if (conditionsCount != 1) {
        throw new Error(
          `Unexpected number of conditions provided: ${conditionsCount}`
        );
      }
    }

    // Create a new options object with just the support properties, because
    // we may augment it and hand it down to other methods.
    options = {
      concurrent: !!options.concurrent,
      includePath: !!options.includePath,
      includeItemIds: !!options.includeItemIds,
    };

    let behavior = {};
    if (info.hasOwnProperty("parentGuid") || info.hasOwnProperty("index")) {
      behavior = {
        parentGuid: { requiredIf: b => b.hasOwnProperty("index") },
        index: {
          validIf: b =>
            (typeof b.index == "number" && b.index >= 0) ||
            b.index == this.DEFAULT_INDEX,
        },
      };
    }

    // Even if we ignore any other unneeded property, we still validate any
    // known property to reduce likelihood of hidden bugs.
    let fetchInfo = validateBookmarkObject(
      "Bookmarks.sys.mjs: fetch",
      info,
      behavior
    );

    let results;
    if (fetchInfo.hasOwnProperty("url")) {
      results = await fetchBookmarksByURL(fetchInfo, options);
    } else if (fetchInfo.hasOwnProperty("guid")) {
      results = await fetchBookmark(fetchInfo, options);
    } else if (fetchInfo.hasOwnProperty("parentGuid")) {
      if (fetchInfo.hasOwnProperty("index")) {
        results = await fetchBookmarkByPosition(fetchInfo, options);
      } else {
        results = await fetchBookmarksByParentGUID(fetchInfo, options);
      }
    } else if (fetchInfo.hasOwnProperty("guidPrefix")) {
      results = await fetchBookmarksByGUIDPrefix(fetchInfo, options);
    } else if (fetchInfo.hasOwnProperty("tags")) {
      results = await fetchBookmarksByTags(fetchInfo, options);
    }

    if (!results) {
      return null;
    }

    if (!Array.isArray(results)) {
      results = [results];
    }
    // Remove non-enumerable properties.
    results = results.map(r => {
      if (r.type == this.TYPE_FOLDER) {
        r.childCount = r._childCount;
      }
      if (options.includeItemIds) {
        r.itemId = r._id;
        r.parentId = r._parentId;
      }
      return Object.assign({}, r);
    });

    if (options.includePath) {
      for (let result of results) {
        let folderPath = await retrieveFullBookmarkPath(result.parentGuid);
        if (folderPath) {
          result.path = folderPath;
        }
      }
    }

    // Ideally this should handle an incremental behavior and thus be invoked
    // while we fetch.  Though, the likelihood of 2 or more bookmarks for the
    // same match is very low, so it's not worth the added code complication.
    if (onResult) {
      for (let result of results) {
        try {
          onResult(result);
        } catch (ex) {
          console.error(ex);
        }
      }
    }

    return results[0];
  },

  /**
   * Retrieves an object representation of a bookmark-item, along with all of
   * its descendants, if any.
   *
   * Each node in the tree is an object that extends the item representation
   * described above with some additional properties:
   *
   *  - [deprecated] itemId (number)
   *      the item's id.  Defined only if aOptions.includeItemIds is set.
   *  - [deprecated] parentId (number)
   *      the item's parent id.  Defined only if aOptions.includeItemIds is set.
   *  - annos (array)
   *      the item's annotations.  This is not set if there are no annotations
   *      set for the item.
   *
   * The root object of the tree also has the following properties set:
   *  - itemsCount (number, not enumerable)
   *      the number of items, including the root item itself, which are
   *      represented in the resolved object.
   *
   * Bookmarked URLs may also have the following properties:
   *  - tags (string)
   *      csv string of the bookmark's tags, if any.
   *  - charset (string)
   *      the last known charset of the bookmark, if any.
   *  - iconurl (URL)
   *      the bookmark's favicon URL, if any.
   *
   * Folders may also have the following properties:
   *  - children (array)
   *      the folder's children information, each of them having the same set of
   *      properties as above.
   *
   * @param [optional] guid
   *        the topmost item to be queried.  If it's not passed, the Places
   *        root folder is queried: that is, you get a representation of the
   *        entire bookmarks hierarchy.
   * @param [optional] options
   *        Options for customizing the query behavior, in the form of an
   *        object with any of the following properties:
   *         - excludeItemsCallback: a function for excluding items, along with
   *           their descendants.  Given an item object (that has everything set
   *           apart its potential children data), it should return true if the
   *           item should be excluded.  Once an item is excluded, the function
   *           isn't called for any of its descendants.  This isn't called for
   *           the root item.
   *           WARNING: since the function may be called for each item, using
   *           this option can slow down the process significantly if the
   *           callback does anything that's not relatively trivial.  It is
   *           highly recommended to avoid any synchronous I/O or DB queries.
   *         - includeItemIds: opt-in to include the deprecated id property.
   *           Use it if you must. It'll be removed once the switch to guids is
   *           complete.
   *
   * @return {Promise} resolved when the fetch is complete.
   * @resolves to an object that represents either a single item or a
   *           bookmarks tree.  if guid points to a non-existent item, the
   *           returned promise is resolved to null.
   * @rejects if an error happens while fetching.
   * @throws if the arguments are invalid.
   */
  // TODO must implement these methods yet:
  // PlacesUtils.promiseBookmarksTree()
  fetchTree() {
    throw new Error("Not yet implemented");
  },

  /**
   * Fetch all the existing tags, sorted alphabetically.
   * @return {Promise} resolves to an array of objects representing tags, when
   *         fetching is complete.
   *         Each object looks like {
   *           name: the name of the tag,
   *           count: number of bookmarks with this tag
   *         }
   */
  async fetchTags() {
    // TODO: Once the tagging API is implemented in Bookmarks.sys.mjs, we can cache
    // the list of tags, instead of querying every time.
    let db = await lazy.PlacesUtils.promiseDBConnection();
    let rows = await db.executeCached(
      `
      SELECT b.title AS name, count(*) AS count
      FROM moz_bookmarks b
      JOIN moz_bookmarks p ON b.parent = p.id
      JOIN moz_bookmarks c ON c.parent = b.id
      WHERE p.guid = :tagsGuid
      GROUP BY name
      ORDER BY name COLLATE nocase ASC
    `,
      { tagsGuid: this.tagsGuid }
    );
    return rows.map(r => ({
      name: r.getResultByName("name"),
      count: r.getResultByName("count"),
    }));
  },

  /**
   * Reorders contents of a folder based on a provided array of GUIDs.
   *
   * @param parentGuid
   *        The globally unique identifier of the folder whose contents should
   *        be reordered.
   * @param orderedChildrenGuids
   *        Ordered array of the children's GUIDs.  If this list contains
   *        non-existing entries they will be ignored.  If the list is
   *        incomplete, and the current child list is already in order with
   *        respect to orderedChildrenGuids, no change is made. Otherwise, the
   *        new items are appended but maintain their current order relative to
   *        eachother.
   * @param {Object} [options={}]
   *        Additional options. Currently supports the following properties:
   *         - lastModified: The last modified time to use for the folder and
               reordered children. Defaults to the current time.
   *         - source: The change source, forwarded to all bookmark observers.
   *           Defaults to nsINavBookmarksService::SOURCE_DEFAULT.
   *
   * @return {Promise} resolved when reordering is complete.
   * @rejects if an error happens while reordering.
   * @throws if the arguments are invalid.
   */
  reorder(parentGuid, orderedChildrenGuids, options = {}) {
    let info = { guid: parentGuid };
    info = validateBookmarkObject("Bookmarks.sys.mjs: reorder", info, {
      guid: { required: true },
    });

    if (!Array.isArray(orderedChildrenGuids) || !orderedChildrenGuids.length) {
      throw new Error("Must provide a sorted array of children GUIDs.");
    }
    try {
      orderedChildrenGuids.forEach(lazy.PlacesUtils.BOOKMARK_VALIDATORS.guid);
    } catch (ex) {
      throw new Error("Invalid GUID found in the sorted children array.");
    }

    options.source =
      "source" in options
        ? lazy.PlacesUtils.BOOKMARK_VALIDATORS.source(options.source)
        : Bookmarks.SOURCES.DEFAULT;
    options.lastModified =
      "lastModified" in options
        ? lazy.PlacesUtils.BOOKMARK_VALIDATORS.lastModified(
            options.lastModified
          )
        : new Date();

    return (async () => {
      let parent = await fetchBookmark(info);
      if (!parent || parent.type != this.TYPE_FOLDER) {
        throw new Error("No folder found for the provided GUID.");
      }
      if (parent._childCount == 0) {
        return;
      }

      let sortedChildren = await reorderChildren(
        parent,
        orderedChildrenGuids,
        options
      );

      const notifications = [];
      let detailsMap = await getBookmarkDetailMap(
        sortedChildren.map(c => c.guid)
      );
      for (let child of sortedChildren) {
        let details = detailsMap.get(child.guid);
        notifications.push(
          new PlacesBookmarkMoved({
            id: child._id,
            itemType: child.type,
            url: child.url?.href,
            guid: child.guid,
            parentGuid: child.parentGuid,
            source: options.source,
            index: child.index,
            oldParentGuid: child.parentGuid,
            oldIndex: child._oldIndex,
            isTagging:
              child.parentGuid === Bookmarks.tagsGuid ||
              parent.parentGuid === Bookmarks.tagsGuid,
            title: child.title,
            tags: details.tags,
            frecency: details.frecency,
            hidden: details.hidden,
            visitCount: details.visitCount,
            dateAdded: child.dateAdded,
            lastVisitDate: details.lastVisitDate,
          })
        );
      }
      if (notifications.length) {
        PlacesObservers.notifyListeners(notifications);
      }
    })();
  },

  /**
   * Searches a list of bookmark-items by a search term, url or title.
   *
   * IMPORTANT:
   * This is intended as an interim API for the web-extensions implementation.
   * It will be removed as soon as we have a new querying API.
   *
   * Note also that this used to exclude separators but no longer does so.
   *
   * If you just want to search bookmarks by URL, use .fetch() instead.
   *
   * @param query
   *        Either a string to use as search term, or an object
   *        containing any of these keys: query, title or url with the
   *        corresponding string to match as value.
   *        The url property can be either a string or an nsIURI.
   *
   * @return {Promise} resolved when the search is complete.
   * @resolves to an array of found bookmark-items.
   * @rejects if an error happens while searching.
   * @throws if the arguments are invalid.
   *
   * @note Any unknown property in the query object is ignored.
   *       Known properties may be overwritten.
   */
  search(query) {
    if (!query) {
      throw new Error("Query object is required");
    }
    if (typeof query === "string") {
      query = { query };
    }
    if (typeof query !== "object") {
      throw new Error("Query must be an object or a string");
    }
    if (query.query && typeof query.query !== "string") {
      throw new Error("Query option must be a string");
    }
    if (query.title && typeof query.title !== "string") {
      throw new Error("Title option must be a string");
    }

    if (query.url) {
      if (typeof query.url === "string" || URL.isInstance(query.url)) {
        query.url = new URL(query.url).href;
      } else if (query.url instanceof Ci.nsIURI) {
        query.url = query.url.spec;
      } else {
        throw new Error("Url option must be a string or a URL object");
      }
    }

    return queryBookmarks(query);
  },
});

// Globals.

// Update implementation.

/**
 * Updates a single bookmark in the database. This should be called from within
 * a transaction.
 *
 * @param {Object} db The pre-existing database connection.
 * @param {Object} info A bookmark-item structure with new properties.
 * @param {Object} item A bookmark-item structure representing the existing bookmark.
 * @param {Integer} oldIndex The index of the item in the old parent.
 * @param {Object} newParent The new parent folder (note: this may be the same as)
 *                           the existing folder.
 * @param {Integer} syncChangeDelta The change delta to be applied.
 */
async function updateBookmark(
  db,
  info,
  item,
  oldIndex,
  newParent,
  syncChangeDelta
) {
  let tuples = new Map();
  tuples.set("lastModified", {
    value: lazy.PlacesUtils.toPRTime(info.lastModified),
  });
  if (info.hasOwnProperty("title")) {
    tuples.set("title", {
      value: info.title,
      fragment: `title = NULLIF(:title, '')`,
    });
  }
  if (info.hasOwnProperty("dateAdded")) {
    tuples.set("dateAdded", {
      value: lazy.PlacesUtils.toPRTime(info.dateAdded),
    });
  }

  if (info.hasOwnProperty("url")) {
    // Ensure a page exists in moz_places for this URL.
    await lazy.PlacesUtils.maybeInsertPlace(db, info.url);
    // Update tuples for the update query.
    tuples.set("url", {
      value: info.url.href,
      fragment:
        "fk = (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url)",
    });
  }

  let newIndex = info.hasOwnProperty("index") ? info.index : item.index;
  if (newParent) {
    // For simplicity, update the index regardless.
    tuples.set("position", { value: newIndex });

    // For moving within the same parent, we've already updated the indexes.
    if (newParent.guid == item.parentGuid) {
      // Moving inside the original container.
      // When moving "up", add 1 to each index in the interval.
      // Otherwise when moving down, we subtract 1.
      // Only the parent needs a sync change, which is handled in
      // `setAncestorsLastModified`.
      await db.executeCached(
        `UPDATE moz_bookmarks
           SET position = CASE WHEN :newIndex < :currIndex
             THEN position + 1
             ELSE position - 1
           END
           WHERE parent = :newParentId
             AND position BETWEEN :lowIndex AND :highIndex
          `,
        {
          newIndex,
          currIndex: oldIndex,
          newParentId: newParent._id,
          lowIndex: Math.min(oldIndex, newIndex),
          highIndex: Math.max(oldIndex, newIndex),
        }
      );
    } else {
      // Moving across different containers. In this case, both parents and
      // the child need sync changes. `setAncestorsLastModified`, below and in
      // `update` and `moveToFolder`, handles the parents. The `needsSyncChange`
      // check below handles the child.
      tuples.set("parent", { value: newParent._id });
      await db.executeCached(
        `UPDATE moz_bookmarks SET position = position - 1
         WHERE parent = :oldParentId
           AND position >= :oldIndex
        `,
        { oldParentId: item._parentId, oldIndex }
      );
      await db.executeCached(
        `UPDATE moz_bookmarks SET position = position + 1
         WHERE parent = :newParentId
           AND position >= :newIndex
        `,
        { newParentId: newParent._id, newIndex }
      );

      await setAncestorsLastModified(
        db,
        item.parentGuid,
        info.lastModified,
        syncChangeDelta
      );
    }
  }

  if (syncChangeDelta) {
    // Sync stores child indices in the parent's record, so we only bump the
    // item's counter if we're updating at least one more property in
    // addition to the index, last modified time, and dateAdded.
    let sizeThreshold = 1;
    if (newIndex != oldIndex) {
      ++sizeThreshold;
    }
    if (tuples.has("dateAdded")) {
      ++sizeThreshold;
    }
    let needsSyncChange = tuples.size > sizeThreshold;
    if (needsSyncChange) {
      tuples.set("syncChangeDelta", {
        value: syncChangeDelta,
        fragment: "syncChangeCounter = syncChangeCounter + :syncChangeDelta",
      });
    }
  }

  let isTagging = item._grandParentId == lazy.PlacesUtils.tagsFolderId;
  if (isTagging) {
    // If we're updating a tag entry, bump the sync change counter for
    // bookmarks with the tagged URL.
    await lazy.PlacesSyncUtils.bookmarks.addSyncChangesForBookmarksWithURL(
      db,
      item.url,
      syncChangeDelta
    );
    if (info.hasOwnProperty("url")) {
      // Changing the URL of a tag entry is equivalent to untagging the
      // old URL and tagging the new one, so we bump the change counter
      // for the new URL here.
      await lazy.PlacesSyncUtils.bookmarks.addSyncChangesForBookmarksWithURL(
        db,
        info.url,
        syncChangeDelta
      );
    }
  }

  let isChangingTagFolder = item._parentId == lazy.PlacesUtils.tagsFolderId;
  if (isChangingTagFolder && syncChangeDelta) {
    // If we're updating a tag folder (for example, changing a tag's title),
    // bump the change counter for all tagged bookmarks.
    await db.executeCached(
      `
      UPDATE moz_bookmarks SET
        syncChangeCounter = syncChangeCounter + :syncChangeDelta
      WHERE type = :type AND
            fk = (SELECT fk FROM moz_bookmarks WHERE parent = :parent)
      `,
      { syncChangeDelta, type: Bookmarks.TYPE_BOOKMARK, parent: item._id }
    );
  }

  await db.executeCached(
    `UPDATE moz_bookmarks
     SET ${Array.from(tuples.keys())
       .map(v => tuples.get(v).fragment || `${v} = :${v}`)
       .join(", ")}
     WHERE guid = :guid
    `,
    Object.assign(
      { guid: item.guid },
      [...tuples.entries()].reduce((p, c) => {
        p[c[0]] = c[1].value;
        return p;
      }, {})
    )
  );

  if (newParent) {
    if (newParent.guid == item.parentGuid) {
      // Mark all affected separators as changed
      // Also bumps the change counter if the item itself is a separator
      const startIndex = Math.min(newIndex, oldIndex);
      await adjustSeparatorsSyncCounter(
        db,
        newParent._id,
        startIndex,
        syncChangeDelta
      );
    } else {
      // Mark all affected separators as changed
      await adjustSeparatorsSyncCounter(
        db,
        item._parentId,
        oldIndex,
        syncChangeDelta
      );
      await adjustSeparatorsSyncCounter(
        db,
        newParent._id,
        newIndex,
        syncChangeDelta
      );
    }
  }

  // If the parent changed, update related non-enumerable properties.
  let additionalParentInfo = {};
  if (newParent) {
    additionalParentInfo.parentGuid = newParent.guid;
    Object.defineProperty(additionalParentInfo, "_parentId", {
      value: newParent._id,
      enumerable: false,
    });
    Object.defineProperty(additionalParentInfo, "_grandParentId", {
      value: newParent._parentId,
      enumerable: false,
    });
  }

  return mergeIntoNewObject(item, info, additionalParentInfo);
}

// Insert implementation.

function insertBookmark(item, parent) {
  return lazy.PlacesUtils.withConnectionWrapper(
    "Bookmarks.sys.mjs: insertBookmark",
    async function (db) {
      // If a guid was not provided, generate one, so we won't need to fetch the
      // bookmark just after having created it.
      let hasExistingGuid = item.hasOwnProperty("guid");
      if (!hasExistingGuid) {
        item.guid = lazy.PlacesUtils.history.makeGuid();
      }

      let isTagging = parent._parentId == lazy.PlacesUtils.tagsFolderId;

      await db.executeTransaction(async function transaction() {
        if (item.type == Bookmarks.TYPE_BOOKMARK) {
          // Ensure a page exists in moz_places for this URL.
          // The IGNORE conflict can trigger on `guid`.
          await lazy.PlacesUtils.maybeInsertPlace(db, item.url);
        }

        // Adjust indices.
        await db.executeCached(
          `UPDATE moz_bookmarks SET position = position + 1
         WHERE parent = :parent
         AND position >= :index
        `,
          { parent: parent._id, index: item.index }
        );

        let syncChangeDelta =
          lazy.PlacesSyncUtils.bookmarks.determineSyncChangeDelta(item.source);
        let syncStatus =
          lazy.PlacesSyncUtils.bookmarks.determineInitialSyncStatus(
            item.source
          );

        // Insert the bookmark into the database.
        await db.executeCached(
          `INSERT INTO moz_bookmarks (fk, type, parent, position, title,
                                    dateAdded, lastModified, guid,
                                    syncChangeCounter, syncStatus)
         VALUES (CASE WHEN :url ISNULL THEN NULL ELSE (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url) END,
                 :type, :parent, :index, NULLIF(:title, ''), :date_added,
                 :last_modified, :guid, :syncChangeCounter, :syncStatus)
        `,
          {
            url: item.hasOwnProperty("url") ? item.url.href : null,
            type: item.type,
            parent: parent._id,
            index: item.index,
            title: item.title,
            date_added: lazy.PlacesUtils.toPRTime(item.dateAdded),
            last_modified: lazy.PlacesUtils.toPRTime(item.lastModified),
            guid: item.guid,
            syncChangeCounter: syncChangeDelta,
            syncStatus,
          }
        );

        // Mark all affected separators as changed
        await adjustSeparatorsSyncCounter(
          db,
          parent._id,
          item.index + 1,
          syncChangeDelta
        );

        if (hasExistingGuid) {
          // Remove stale tombstones if we're reinserting an item.
          await db.executeCached(
            `DELETE FROM moz_bookmarks_deleted WHERE guid = :guid`,
            { guid: item.guid }
          );
        }

        if (isTagging) {
          // New tag entry; bump the change counter for bookmarks with the
          // tagged URL.
          await lazy.PlacesSyncUtils.bookmarks.addSyncChangesForBookmarksWithURL(
            db,
            item.url,
            syncChangeDelta
          );
        }

        await setAncestorsLastModified(
          db,
          item.parentGuid,
          item.dateAdded,
          syncChangeDelta
        );
      });

      return item;
    }
  );
}

function insertBookmarkTree(items, source, parent, urls, lastAddedForParent) {
  return lazy.PlacesUtils.withConnectionWrapper(
    "Bookmarks.sys.mjs: insertBookmarkTree",
    async function (db) {
      await db.executeTransaction(async function transaction() {
        await lazy.PlacesUtils.maybeInsertManyPlaces(db, urls);

        let syncChangeDelta =
          lazy.PlacesSyncUtils.bookmarks.determineSyncChangeDelta(source);
        let syncStatus =
          lazy.PlacesSyncUtils.bookmarks.determineInitialSyncStatus(source);

        let rootId = parent._id;

        items = items.map(item => ({
          url: item.url && item.url.href,
          type: item.type,
          parentGuid: item.parentGuid,
          index: item.index,
          title: item.title,
          date_added: lazy.PlacesUtils.toPRTime(item.dateAdded),
          last_modified: lazy.PlacesUtils.toPRTime(item.lastModified),
          guid: item.guid,
          syncChangeCounter: syncChangeDelta,
          syncStatus,
          rootId,
        }));
        await db.executeCached(
          `INSERT INTO moz_bookmarks (fk, type, parent, position, title,
                                    dateAdded, lastModified, guid,
                                    syncChangeCounter, syncStatus)
         VALUES (CASE WHEN :url ISNULL THEN NULL ELSE (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url) END, :type,
         (SELECT id FROM moz_bookmarks WHERE guid = :parentGuid),
         IFNULL(:index, (SELECT count(*) FROM moz_bookmarks WHERE parent = :rootId)),
         NULLIF(:title, ''), :date_added, :last_modified, :guid,
         :syncChangeCounter, :syncStatus)`,
          items
        );

        // Remove stale tombstones for new items.
        for (let chunk of lazy.PlacesUtils.chunkArray(
          items,
          db.variableLimit
        )) {
          await db.executeCached(
            `DELETE FROM moz_bookmarks_deleted
             WHERE guid IN (${lazy.PlacesUtils.sqlBindPlaceholders(chunk)})`,
            chunk.map(item => item.guid)
          );
        }

        await setAncestorsLastModified(
          db,
          parent.guid,
          lastAddedForParent,
          syncChangeDelta
        );
      });

      return items;
    }
  );
}

/**
 * Handles special data on a bookmark, e.g. annotations, keywords, tags, charsets,
 * inserting the data into the appropriate place.
 *
 * @param {Integer} itemId The ID of the item within the bookmarks database.
 * @param {Object} item The bookmark item with possible special data to be inserted.
 */
async function handleBookmarkItemSpecialData(itemId, item) {
  if ("keyword" in item && item.keyword) {
    try {
      await lazy.PlacesUtils.keywords.insert({
        keyword: item.keyword,
        url: item.url,
        postData: item.postData,
        source: item.source,
      });
    } catch (ex) {
      console.error(
        `Failed to insert keyword "${item.keyword} for ${item.url}":`,
        ex
      );
    }
  }
  if ("tags" in item) {
    try {
      lazy.PlacesUtils.tagging.tagURI(
        lazy.NetUtil.newURI(item.url),
        item.tags,
        item.source
      );
    } catch (ex) {
      // Invalid tag child, skip it.
      console.error(
        `Unable to set tags "${item.tags.join(", ")}" for ${item.url}:`,
        ex
      );
    }
  }
  if ("charset" in item && item.charset) {
    try {
      // UTF-8 is the default. If we are passed the value then set it to null,
      // to ensure any charset is removed from the database.
      let charset = item.charset;
      if (item.charset.toLowerCase() == "utf-8") {
        charset = null;
      }

      await lazy.PlacesUtils.history.update({
        url: item.url,
        annotations: new Map([[lazy.PlacesUtils.CHARSET_ANNO, charset]]),
      });
    } catch (ex) {
      console.error(
        `Failed to set charset "${item.charset}" for ${item.url}:`,
        ex
      );
    }
  }
}

// Query implementation.

async function queryBookmarks(info) {
  let queryParams = {
    tags_folder: lazy.PlacesUtils.tagsFolderId,
  };
  // We're searching for bookmarks, so exclude tags.
  let queryString = "WHERE b.parent <> :tags_folder";
  queryString += " AND p.parent <> :tags_folder";

  if (info.title) {
    queryString += " AND b.title = :title";
    queryParams.title = info.title;
  }

  if (info.url) {
    queryString += " AND h.url_hash = hash(:url) AND h.url = :url";
    queryParams.url = info.url;
  }

  if (info.query) {
    queryString +=
      " AND AUTOCOMPLETE_MATCH(:query, h.url, b.title, NULL, NULL, 1, 1, NULL, :matchBehavior, :searchBehavior, NULL) ";
    queryParams.query = info.query;
    queryParams.matchBehavior = MATCH_ANYWHERE_UNMODIFIED;
    queryParams.searchBehavior = BEHAVIOR_BOOKMARK;
  }

  return lazy.PlacesUtils.withConnectionWrapper(
    "Bookmarks.sys.mjs: queryBookmarks",
    async function (db) {
      // _id, _childCount, _grandParentId and _parentId fields
      // are required to be in the result by the converting function
      // hence setting them to NULL
      let rows = await db.executeCached(
        `SELECT b.guid, IFNULL(p.guid, '') AS parentGuid, b.position AS 'index',
                b.dateAdded, b.lastModified, b.type,
                IFNULL(b.title, '') AS title, h.url AS url, b.parent, p.parent,
                NULL AS _id,
                NULL AS _childCount,
                NULL AS _grandParentId,
                NULL AS _parentId,
                NULL AS _syncStatus
         FROM moz_bookmarks b
         LEFT JOIN moz_bookmarks p ON p.id = b.parent
         LEFT JOIN moz_places h ON h.id = b.fk
         ${queryString}
        `,
        queryParams
      );

      return rowsToItemsArray(rows);
    }
  );
}

/**
 * Internal fetch implementation.
 * @param {object} info
 *        The bookmark item to remove.
 * @param {object} options
 *        An options object supporting the following properties:
 * @param {object} [options.concurrent]
 *        Whether to use the concurrent read-only connection.
 * @param {object} [options.db]
 *        A specific connection to be used.
 * @param {object} [options.ignoreInvalidURLs]
 *        Whether invalid URLs should be ignored or throw an exception.
 *
 */
async function fetchBookmark(info, options = {}) {
  let query = async function (db) {
    let rows = await db.executeCached(
      `SELECT b.guid, IFNULL(p.guid, '') AS parentGuid, b.position AS 'index',
              b.dateAdded, b.lastModified, b.type, IFNULL(b.title, '') AS title,
              h.url AS url, b.id AS _id, b.parent AS _parentId,
              (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
              p.parent AS _grandParentId, b.syncStatus AS _syncStatus
       FROM moz_bookmarks b
       LEFT JOIN moz_bookmarks p ON p.id = b.parent
       LEFT JOIN moz_places h ON h.id = b.fk
       WHERE b.guid = :guid
      `,
      { guid: info.guid }
    );

    return rows.length
      ? rowsToItemsArray(rows, !!options.ignoreInvalidURLs)[0]
      : null;
  };
  if (options.concurrent) {
    let db = await lazy.PlacesUtils.promiseDBConnection();
    return query(db);
  }
  if (options.db) {
    return query(options.db);
  }
  return lazy.PlacesUtils.withConnectionWrapper(
    "Bookmarks.sys.mjs: fetchBookmark",
    query
  );
}

async function fetchBookmarkByPosition(info, options = {}) {
  let query = async function (db) {
    let index = info.index == Bookmarks.DEFAULT_INDEX ? null : info.index;
    let rows = await db.executeCached(
      `SELECT b.guid, IFNULL(p.guid, '') AS parentGuid, b.position AS 'index',
              b.dateAdded, b.lastModified, b.type, IFNULL(b.title, '') AS title,
              h.url AS url, b.id AS _id, b.parent AS _parentId,
              (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
              p.parent AS _grandParentId, b.syncStatus AS _syncStatus
       FROM moz_bookmarks b
       LEFT JOIN moz_bookmarks p ON p.id = b.parent
       LEFT JOIN moz_places h ON h.id = b.fk
       WHERE p.guid = :parentGuid
       AND b.position = IFNULL(:index, (SELECT count(*) - 1
                                        FROM moz_bookmarks
                                        WHERE parent = p.id))
      `,
      { parentGuid: info.parentGuid, index }
    );

    return rows.length ? rowsToItemsArray(rows)[0] : null;
  };
  if (options.concurrent) {
    let db = await lazy.PlacesUtils.promiseDBConnection();
    return query(db);
  }
  return lazy.PlacesUtils.withConnectionWrapper(
    "Bookmarks.sys.mjs: fetchBookmarkByPosition",
    query
  );
}

async function fetchBookmarksByTags(info, options = {}) {
  let query = async function (db) {
    let rows = await db.executeCached(
      `SELECT b.guid, IFNULL(p.guid, '') AS parentGuid, b.position AS 'index',
              b.dateAdded, b.lastModified, b.type, IFNULL(b.title, '') AS title,
              h.url AS url, b.id AS _id, b.parent AS _parentId,
              NULL AS _childCount,
              p.parent AS _grandParentId, b.syncStatus AS _syncStatus,
              (SELECT group_concat(pp.title ORDER BY pp.title)
               FROM moz_bookmarks bb
               JOIN moz_bookmarks pp ON pp.id = bb.parent
               JOIN moz_bookmarks gg ON gg.id = pp.parent
               WHERE bb.fk = h.id
               AND gg.guid = '${Bookmarks.tagsGuid}'
              ) AS _tags
       FROM moz_bookmarks b
       JOIN moz_bookmarks p ON p.id = b.parent
       JOIN moz_bookmarks g ON g.id = p.parent
       JOIN moz_places h ON h.id = b.fk
       WHERE g.guid <> '${Bookmarks.tagsGuid}'
       AND b.fk IN (
          SELECT b2.fk FROM moz_bookmarks b2
          JOIN moz_bookmarks p2 ON p2.id = b2.parent
          JOIN moz_bookmarks g2 ON g2.id = p2.parent
          WHERE g2.guid = '${Bookmarks.tagsGuid}'
          AND lower(p2.title) IN (
            ${new Array(info.tags.length).fill("?").join(",")}
          )
          GROUP BY b2.fk HAVING count(*) = ${info.tags.length}
       )
       ORDER BY b.lastModified DESC
      `,
      info.tags.map(t => t.toLowerCase())
    );

    return rows.length ? rowsToItemsArray(rows) : null;
  };

  if (options.concurrent) {
    let db = await lazy.PlacesUtils.promiseDBConnection();
    return query(db);
  }
  return lazy.PlacesUtils.withConnectionWrapper(
    "Bookmarks.sys.mjs: fetchBookmarksByTags",
    query
  );
}

async function fetchBookmarksByGUIDPrefix(info, options = {}) {
  let query = async function (db) {
    let rows = await db.executeCached(
      `SELECT b.guid, IFNULL(p.guid, '') AS parentGuid, b.position AS 'index',
              b.dateAdded, b.lastModified, b.type, IFNULL(b.title, '') AS title,
              h.url AS url, b.id AS _id, b.parent AS _parentId,
              (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
              p.parent AS _grandParentId, b.syncStatus AS _syncStatus
       FROM moz_bookmarks b
       LEFT JOIN moz_bookmarks p ON p.id = b.parent
       LEFT JOIN moz_places h ON h.id = b.fk
       WHERE b.guid LIKE :guidPrefix
       ORDER BY b.lastModified DESC
      `,
      { guidPrefix: info.guidPrefix + "%" }
    );

    return rows.length ? rowsToItemsArray(rows) : null;
  };

  if (options.concurrent) {
    let db = await lazy.PlacesUtils.promiseDBConnection();
    return query(db);
  }
  return lazy.PlacesUtils.withConnectionWrapper(
    "Bookmarks.sys.mjs: fetchBookmarksByGUIDPrefix",
    query
  );
}

async function fetchBookmarksByURL(info, options = {}) {
  let query = async function (db) {
    let rows = await db.executeCached(
      `/* do not warn (bug no): not worth to add an index */
      SELECT b.guid, IFNULL(p.guid, '') AS parentGuid, b.position AS 'index',
              b.dateAdded, b.lastModified, b.type, IFNULL(b.title, '') AS title,
              h.url AS url, b.id AS _id, b.parent AS _parentId,
              NULL AS _childCount, /* Unused for now */
              p.parent AS _grandParentId, b.syncStatus AS _syncStatus,
              (SELECT group_concat(pp.title ORDER BY pp.title)
               FROM moz_bookmarks bb
               JOIN moz_bookmarks pp ON bb.parent = pp.id
               JOIN moz_bookmarks gg ON pp.parent = gg.id
               WHERE bb.fk = h.id
               AND gg.guid = '${Bookmarks.tagsGuid}'
              ) AS _tags
      FROM moz_bookmarks b
      JOIN moz_bookmarks p ON p.id = b.parent
      JOIN moz_places h ON h.id = b.fk
      WHERE h.url_hash = hash(:url) AND h.url = :url
      AND _grandParentId <> :tagsFolderId
      ORDER BY b.lastModified DESC
      `,
      {
        url: info.url.href,
        tagsFolderId: lazy.PlacesUtils.tagsFolderId,
      }
    );

    return rows.length ? rowsToItemsArray(rows) : null;
  };

  if (options.concurrent) {
    let db = await lazy.PlacesUtils.promiseDBConnection();
    return query(db);
  }
  return lazy.PlacesUtils.withConnectionWrapper(
    "Bookmarks.sys.mjs: fetchBookmarksByURL",
    query
  );
}

async function fetchBookmarksByParentGUID(info, options = {}) {
  let query = async function (db) {
    let rows = await db.executeCached(
      `SELECT b.guid, IFNULL(p.guid, '') AS parentGuid, b.position AS 'index',
              b.dateAdded, b.lastModified, b.type, IFNULL(b.title, '') AS title,
              h.url AS url,
              b.id AS _id,
              b.parent AS _parentId,
              (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
              NULL AS _grandParentId,
              NULL AS _syncStatus
       FROM moz_bookmarks b
       LEFT JOIN moz_bookmarks p ON p.id = b.parent
       LEFT JOIN moz_places h ON h.id = b.fk
       WHERE p.guid = :parentGuid
       ORDER BY b.position ASC
      `,
      { parentGuid: info.parentGuid }
    );

    return rows.length ? rowsToItemsArray(rows) : null;
  };

  if (options.concurrent) {
    let db = await lazy.PlacesUtils.promiseDBConnection();
    return query(db);
  }
  return lazy.PlacesUtils.withConnectionWrapper(
    "Bookmarks.sys.mjs: fetchBookmarksByParentGUID",
    query
  );
}

function fetchRecentBookmarks(numberOfItems) {
  return lazy.PlacesUtils.withConnectionWrapper(
    "Bookmarks.sys.mjs: fetchRecentBookmarks",
    async function (db) {
      let rows = await db.executeCached(
        `SELECT b.guid, IFNULL(p.guid, '') AS parentGuid, b.position AS 'index',
                b.dateAdded, b.lastModified, b.type,
                IFNULL(b.title, '') AS title, h.url AS url, b.id AS _id,
                b.parent AS _parentId, NULL AS _childCount,
                NULL AS _grandParentId, NULL AS _syncStatus
        FROM moz_bookmarks b
        JOIN moz_bookmarks p ON p.id = b.parent
        JOIN moz_places h ON h.id = b.fk
        WHERE p.parent <> :tagsFolderId
        AND b.type = :type
        AND url_hash NOT BETWEEN hash("place", "prefix_lo")
                              AND hash("place", "prefix_hi")
        ORDER BY b.dateAdded DESC, b.ROWID DESC
        LIMIT :numberOfItems
        `,
        {
          tagsFolderId: lazy.PlacesUtils.tagsFolderId,
          type: Bookmarks.TYPE_BOOKMARK,
          numberOfItems,
        }
      );

      return rows.length ? rowsToItemsArray(rows) : [];
    }
  );
}

async function fetchBookmarksByParent(db, info) {
  let rows = await db.executeCached(
    `SELECT b.guid, IFNULL(p.guid, '') AS parentGuid, b.position AS 'index',
            b.dateAdded, b.lastModified, b.type, IFNULL(b.title, '') AS title,
            h.url AS url, b.id AS _id, b.parent AS _parentId,
            (SELECT count(*) FROM moz_bookmarks WHERE parent = b.id) AS _childCount,
            p.parent AS _grandParentId, b.syncStatus AS _syncStatus
     FROM moz_bookmarks b
     LEFT JOIN moz_bookmarks p ON p.id = b.parent
     LEFT JOIN moz_places h ON h.id = b.fk
     WHERE p.guid = :parentGuid
     ORDER BY b.position ASC
    `,
    { parentGuid: info.parentGuid }
  );

  return rowsToItemsArray(rows);
}

// Remove implementation.

function removeBookmarks(items, options) {
  return lazy.PlacesUtils.withConnectionWrapper(
    "Bookmarks.sys.mjs: removeBookmarks",
    async function (db) {
      let urls = [];

      await db.executeTransaction(async function transaction() {
        // We use a map for its de-duplication properties.
        let parents = new Map();
        let syncChangeDelta =
          lazy.PlacesSyncUtils.bookmarks.determineSyncChangeDelta(
            options.source
          );

        for (let item of items) {
          parents.set(item.parentGuid, item._parentId);

          // If it's a folder, remove its contents first.
          if (item.type == Bookmarks.TYPE_FOLDER) {
            if (
              options.preventRemovalOfNonEmptyFolders &&
              item._childCount > 0
            ) {
              throw new Error("Cannot remove a non-empty folder.");
            }
            urls = urls.concat(
              await removeFoldersContents(db, [item.guid], options)
            );
          }
        }

        for (let chunk of lazy.PlacesUtils.chunkArray(
          items,
          db.variableLimit
        )) {
          // Remove the bookmarks.
          await db.executeCached(
            `DELETE FROM moz_bookmarks
             WHERE guid IN (${lazy.PlacesUtils.sqlBindPlaceholders(chunk)})`,
            chunk.map(item => item.guid)
          );
        }

        for (let [parentGuid, parentId] of parents.entries()) {
          // Now recalculate the positions.
          await db.executeCached(
            `WITH positions(id, pos, seq) AS (
            SELECT id, position AS pos,
                   (row_number() OVER (ORDER BY position)) - 1 AS seq
            FROM moz_bookmarks
            WHERE parent = :parentId
          )
          UPDATE moz_bookmarks
            SET position = (SELECT seq FROM positions WHERE positions.id = moz_bookmarks.id)
            WHERE id IN (SELECT id FROM positions WHERE seq <> pos)
        `,
            { parentId }
          );

          // Mark this parent as changed.
          await setAncestorsLastModified(
            db,
            parentGuid,
            new Date(),
            syncChangeDelta
          );
        }

        for (let i = 0; i < items.length; i++) {
          const item = items[i];
          // For the notifications, we may need to adjust indexes if there are more
          // than one of the same item in the folder. This makes sure that we notify
          // the index of the item when it was removed, rather than the original index.
          for (let j = i + 1; j < items.length; j++) {
            if (
              items[j]._parentId == item._parentId &&
              items[j].index > item.index
            ) {
              items[j].index--;
            }
          }
          if (item._grandParentId == lazy.PlacesUtils.tagsFolderId) {
            // If we're removing a tag entry, increment the change counter for all
            // bookmarks with the tagged URL.
            await lazy.PlacesSyncUtils.bookmarks.addSyncChangesForBookmarksWithURL(
              db,
              item.url,
              syncChangeDelta
            );
          }

          await adjustSeparatorsSyncCounter(
            db,
            item._parentId,
            item.index,
            syncChangeDelta
          );
        }

        // Write tombstones for the removed items.
        await insertTombstones(db, items, syncChangeDelta);
      });

      urls = urls.concat(items.map(item => item.url).filter(item => item));
      if (urls.length) {
        await lazy.PlacesUtils.keywords.removeFromURLsIfNotBookmarked(urls);
      }
    }
  );
}

// Reorder implementation.

function reorderChildren(parent, orderedChildrenGuids, options) {
  return lazy.PlacesUtils.withConnectionWrapper(
    "Bookmarks.sys.mjs: reorderChildren",
    db =>
      db.executeTransaction(async function () {
        // Fetch old indices for the notifications.
        const oldIndices = new Map();
        (
          await db.executeCached(
            `SELECT guid, position FROM moz_bookmarks WHERE parent = :parentId`,
            { parentId: parent._id }
          )
        ).forEach(r =>
          oldIndices.set(
            r.getResultByName("guid"),
            r.getResultByName("position")
          )
        );
        // By the time the caller collects guids and the time reorder is invoked
        // new bookmarks may appear, and the passed-in list becomes incomplete.
        // To avoid unnecessary work then skip reorder if children are already
        // in the requested sort order.
        let lastIndex = 0,
          needReorder = false;
        for (let guid of orderedChildrenGuids) {
          let requestedIndex = oldIndices.get(guid);
          if (requestedIndex === undefined) {
            // doesn't exist, just ignore it.
            continue;
          }
          if (requestedIndex < lastIndex) {
            needReorder = true;
            break;
          }
          lastIndex = requestedIndex;
        }
        if (!needReorder) {
          return [];
        }

        const valuesFragment = orderedChildrenGuids
          .map((g, i) => `("${g}", ${i})`)
          .join();
        await db.execute(
          `UPDATE moz_bookmarks
           SET position = sorted.pos,
               lastModified = :lastModified
           FROM (
             WITH fixed(guid, pos) AS (
               VALUES ${valuesFragment}
             )
             SELECT b.id,
                    row_number() OVER (ORDER BY CASE WHEN fixed.pos IS NULL THEN 1 ELSE 0 END ASC, fixed.pos ASC, position ASC) - 1 AS pos
             FROM moz_bookmarks b
             LEFT JOIN fixed ON b.guid = fixed.guid
             WHERE parent = :parentId
           ) AS sorted
           WHERE sorted.id = moz_bookmarks.id`,
          {
            parentId: parent._id,
            lastModified: lazy.PlacesUtils.toPRTime(options.lastModified),
          }
        );

        let syncChangeDelta =
          lazy.PlacesSyncUtils.bookmarks.determineSyncChangeDelta(
            options.source
          );
        await setAncestorsLastModified(
          db,
          parent.guid,
          options.lastModified,
          syncChangeDelta
        );
        // Fetch bookmarks for the notifications, adding _oldIndex to each.
        return (
          await fetchBookmarksByParent(db, {
            parentGuid: parent.guid,
          })
        ).map(c => {
          // We're not returning these objects to the caller, but just in case
          // we'd decide to do it in the future, make sure this will be removed.
          // See rowsToItemsArray() for additional details.
          Object.defineProperty(c, "_oldIndex", {
            value: oldIndices.get(c.guid) || 0,
            enumerable: false,
            configurable: true,
          });
          return c;
        });
      })
  );
}

// Helpers.

/**
 * Merges objects into a new object, included non-enumerable properties.
 *
 * @param sources
 *        source objects to merge.
 * @return a new object including all properties from the source objects.
 */
function mergeIntoNewObject(...sources) {
  let dest = {};
  for (let src of sources) {
    for (let prop of Object.getOwnPropertyNames(src)) {
      Object.defineProperty(
        dest,
        prop,
        Object.getOwnPropertyDescriptor(src, prop)
      );
    }
  }
  return dest;
}

/**
 * Remove properties that have the same value across two bookmark objects.
 *
 * @param dest
 *        destination bookmark object.
 * @param src
 *        source bookmark object.
 * @return a cleaned up bookmark object.
 * @note "guid" is never removed.
 */
function removeSameValueProperties(dest, src) {
  for (let prop in dest) {
    let remove = false;
    switch (prop) {
      case "lastModified":
      case "dateAdded":
        remove =
          src.hasOwnProperty(prop) &&
          dest[prop].getTime() == src[prop].getTime();
        break;
      case "url":
        remove = src.hasOwnProperty(prop) && dest[prop].href == src[prop].href;
        break;
      default:
        remove = dest[prop] == src[prop];
    }
    if (remove && prop != "guid") {
      delete dest[prop];
    }
  }
}

/**
 * Convert an array of mozIStorageRow objects to an array of bookmark objects.
 *
 * @param {Array} rows
 *        the array of mozIStorageRow objects.
 * @param {Boolean} ignoreInvalidURLs
 *        whether to ignore invalid urls (leaving the url property undefined)
 *        or throw.
 * @return an array of bookmark objects.
 */
function rowsToItemsArray(rows, ignoreInvalidURLs = false) {
  return rows.map(row => {
    let item = {};
    for (let prop of ["guid", "index", "type", "title"]) {
      item[prop] = row.getResultByName(prop);
    }
    for (let prop of ["dateAdded", "lastModified"]) {
      let value = row.getResultByName(prop);
      if (value) {
        item[prop] = lazy.PlacesUtils.toDate(value);
      }
    }
    let parentGuid = row.getResultByName("parentGuid");
    if (parentGuid) {
      item.parentGuid = parentGuid;
    }
    let url = row.getResultByName("url");
    if (url) {
      try {
        item.url = new URL(url);
      } catch (ex) {
        if (!ignoreInvalidURLs) {
          throw ex;
        }
      }
    }

    // All the private properties below this point should not be returned to the
    // API consumer, thus they are non-enumerable and removed through
    // Object.assign just before the object is returned.
    // Configurable is set to support mergeIntoNewObject overwrites.

    for (let prop of [
      "_id",
      "_parentId",
      "_childCount",
      "_grandParentId",
      "_syncStatus",
    ]) {
      let val = row.getResultByName(prop);
      if (val !== null) {
        Object.defineProperty(item, prop, {
          value: val,
          enumerable: false,
          configurable: true,
        });
      }
    }

    try {
      let tags = row.getResultByName("_tags");
      Object.defineProperty(item, "_tags", {
        value: tags
          ? tags
              .split(",")
              .sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
          : [],
        enumerable: false,
        configurable: true,
      });
    } catch (ex) {
      // `tags` not fetched, don't add it.
    }

    return item;
  });
}

function validateBookmarkObject(name, input, behavior) {
  return lazy.PlacesUtils.validateItemProperties(
    name,
    lazy.PlacesUtils.BOOKMARK_VALIDATORS,
    input,
    behavior
  );
}

/**
 * Updates lastModified for all the ancestors of a given folder GUID.
 *
 * @param db
 *        the Sqlite.sys.mjs connection handle.
 * @param folderGuid
 *        the GUID of the folder whose ancestors should be updated.
 * @param time
 *        a Date object to use for the update.
 *
 * @note the folder itself is also updated.
 */
var setAncestorsLastModified = async function (
  db,
  folderGuid,
  time,
  syncChangeDelta
) {
  await db.executeCached(
    `WITH RECURSIVE
     ancestors(aid) AS (
       SELECT id FROM moz_bookmarks WHERE guid = :guid
       UNION ALL
       SELECT parent FROM moz_bookmarks
       JOIN ancestors ON id = aid
       WHERE type = :type
     )
     UPDATE moz_bookmarks SET lastModified = :time
     WHERE id IN ancestors
    `,
    {
      guid: folderGuid,
      type: Bookmarks.TYPE_FOLDER,
      time: lazy.PlacesUtils.toPRTime(time),
    }
  );

  if (syncChangeDelta) {
    // Flag the folder as having a change.
    await db.executeCached(
      `
      UPDATE moz_bookmarks SET
        syncChangeCounter = syncChangeCounter + :syncChangeDelta
      WHERE guid = :guid`,
      { guid: folderGuid, syncChangeDelta }
    );
  }
};

/**
 * Remove all descendants of one or more bookmark folders.
 *
 * @param {Object} db
 *        the Sqlite.sys.mjs connection handle.
 * @param {Array} folderGuids
 *        array of folder guids.
 * @return {Array}
 *         An array of the affected urls.
 */
var removeFoldersContents = async function (db, folderGuids, options) {
  let syncChangeDelta = lazy.PlacesSyncUtils.bookmarks.determineSyncChangeDelta(
    options.source
  );

  let itemsRemoved = [];
  for (let folderGuid of folderGuids) {
    let rows = await db.executeCached(
      `WITH RECURSIVE
       descendants(did) AS (
         SELECT b.id FROM moz_bookmarks b
         JOIN moz_bookmarks p ON b.parent = p.id
         WHERE p.guid = :folderGuid
         UNION ALL
         SELECT id FROM moz_bookmarks
         JOIN descendants ON parent = did
       )
       SELECT b.id AS _id, b.parent AS _parentId, b.position AS 'index',
              b.type, url, b.guid, p.guid AS parentGuid, b.dateAdded,
              b.lastModified, IFNULL(b.title, '') AS title,
              p.parent AS _grandParentId, NULL AS _childCount,
              b.syncStatus AS _syncStatus
       FROM descendants
       /* The usage of CROSS JOIN is not random, it tells the optimizer
          to retain the original rows order, so the hierarchy is respected */
       CROSS JOIN moz_bookmarks b ON did = b.id
       JOIN moz_bookmarks p ON p.id = b.parent
       LEFT JOIN moz_places h ON b.fk = h.id`,
      { folderGuid }
    );

    itemsRemoved = itemsRemoved.concat(rowsToItemsArray(rows, true));

    await db.executeCached(
      `WITH RECURSIVE
       descendants(did) AS (
         SELECT b.id FROM moz_bookmarks b
         JOIN moz_bookmarks p ON b.parent = p.id
         WHERE p.guid = :folderGuid
         UNION ALL
         SELECT id FROM moz_bookmarks
         JOIN descendants ON parent = did
       )
       DELETE FROM moz_bookmarks WHERE id IN descendants`,
      { folderGuid }
    );
  }

  // Write tombstones for removed items.
  await insertTombstones(db, itemsRemoved, syncChangeDelta);

  // Bump the change counter for all tagged bookmarks when removing tag
  // folders.
  await addSyncChangesForRemovedTagFolders(db, itemsRemoved, syncChangeDelta);

  // TODO (Bug 1087576): this may leave orphan tags behind.

  // Send onItemRemoved notifications to listeners.
  // TODO (Bug 1087580): for the case of eraseEverything, this should send a
  // single clear bookmarks notification rather than notifying for each
  // bookmark.

  // Notify listeners in reverse order to serve children before parents.
  let { source = Bookmarks.SOURCES.DEFAULT } = options;
  let notifications = [];
  for (let item of itemsRemoved.reverse()) {
    let isUntagging = item._grandParentId == lazy.PlacesUtils.tagsFolderId;
    let url = "";
    if (item.type == Bookmarks.TYPE_BOOKMARK) {
      url = item.hasOwnProperty("url") ? item.url.href : null;
    }
    notifications.push(
      new PlacesBookmarkRemoved({
        id: item._id,
        url,
        title: item.title,
        parentId: item._parentId,
        index: item.index,
        itemType: item.type,
        guid: item.guid,
        parentGuid: item.parentGuid,
        source,
        isTagging: isUntagging,
        isDescendantRemoval:
          !lazy.PlacesUtils.bookmarks.userContentRoots.includes(
            item.parentGuid
          ),
      })
    );

    if (isUntagging) {
      for (let entry of await fetchBookmarksByURL(item, true)) {
        notifications.push(
          new PlacesBookmarkTags({
            id: entry._id,
            itemType: entry.type,
            url,
            guid: entry.guid,
            parentGuid: entry.parentGuid,
            tags: entry._tags,
            lastModified: entry.lastModified,
            source,
            isTagging: false,
          })
        );
      }
    }
  }

  if (notifications.length) {
    PlacesObservers.notifyListeners(notifications);
  }

  return itemsRemoved.filter(item => "url" in item).map(item => item.url);
};

// Indicates whether we should write a tombstone for an item that has been
// uploaded to the server. We ignore "NEW" and "UNKNOWN" items: "NEW" items
// haven't been uploaded yet, and "UNKNOWN" items need a full reconciliation
// with the server.
function needsTombstone(item) {
  return item._syncStatus == Bookmarks.SYNC_STATUS.NORMAL;
}

// Inserts tombstones for removed synced items.
function insertTombstones(db, itemsRemoved, syncChangeDelta) {
  if (!syncChangeDelta) {
    return Promise.resolve();
  }
  let syncedItems = itemsRemoved.filter(needsTombstone);
  if (!syncedItems.length) {
    return Promise.resolve();
  }
  let dateRemoved = lazy.PlacesUtils.toPRTime(Date.now());
  let valuesTable = syncedItems
    .map(
      item => `(
    ${JSON.stringify(item.guid)},
    ${dateRemoved}
  )`
    )
    .join(",");
  return db.execute(`
    INSERT INTO moz_bookmarks_deleted (guid, dateRemoved)
    VALUES ${valuesTable}`);
}

// Bumps the change counter for all bookmarks with URLs referenced in removed
// tag folders.
var addSyncChangesForRemovedTagFolders = async function (
  db,
  itemsRemoved,
  syncChangeDelta
) {
  if (!syncChangeDelta) {
    return;
  }
  for (let item of itemsRemoved) {
    let isUntagging = item._grandParentId == lazy.PlacesUtils.tagsFolderId;
    if (isUntagging) {
      await lazy.PlacesSyncUtils.bookmarks.addSyncChangesForBookmarksWithURL(
        db,
        item.url,
        syncChangeDelta
      );
    }
  }
};

function adjustSeparatorsSyncCounter(
  db,
  parentId,
  startIndex,
  syncChangeDelta
) {
  if (!syncChangeDelta) {
    return Promise.resolve();
  }

  return db.executeCached(
    `
    UPDATE moz_bookmarks
    SET syncChangeCounter = syncChangeCounter + :delta
    WHERE parent = :parent AND position >= :start_index
      AND type = :item_type
    `,
    {
      delta: syncChangeDelta,
      parent: parentId,
      start_index: startIndex,
      item_type: Bookmarks.TYPE_SEPARATOR,
    }
  );
}

/**
 * Return the full path, from parent to root folder, of a bookmark.
 *
 * @param guid
 *        The globally unique identifier of the item to determine the full
 *        bookmark path for.
 * @param options [optional]
 *        an optional object whose properties describe options for the query:
 *         - concurrent:  Queries concurrently to any writes, returning results
 *                        faster. On the negative side, it may return stale
 *                        information missing the currently ongoing write.
 *         - db:          A specific connection to be used.
 * @return {Promise} resolved when the query is complete.
 * @resolves to an array of {guid, title} objects that represent the full path
 *           from parent to root for the passed in bookmark.
 * @rejects if an error happens while querying.
 */
async function retrieveFullBookmarkPath(guid, options = {}) {
  let query = async function (db) {
    let rows = await db.executeCached(
      `WITH RECURSIVE parents(guid, _id, _parent, title) AS
          (SELECT guid, id AS _id, parent AS _parent,
                  IFNULL(title, '') AS title
           FROM moz_bookmarks
           WHERE guid = :pguid
           UNION ALL
           SELECT b.guid, b.id AS _id, b.parent AS _parent,
                  IFNULL(b.title, '') AS title
           FROM moz_bookmarks b
           INNER JOIN parents ON b.id=parents._parent)
        SELECT * FROM parents WHERE guid != :rootGuid;
      `,
      { pguid: guid, rootGuid: lazy.PlacesUtils.bookmarks.rootGuid }
    );

    return rows.reverse().map(r => ({
      guid: r.getResultByName("guid"),
      title: r.getResultByName("title"),
    }));
  };

  if (options.concurrent) {
    let db = await lazy.PlacesUtils.promiseDBConnection();
    return query(db);
  }
  return lazy.PlacesUtils.withConnectionWrapper(
    "Bookmarks.sys.mjs: retrieveFullBookmarkPath",
    query
  );
}

/**
 * Get detail of bookmarks of given GUID as Map.
 *
 * @param {Array} aGuids An array of item GUIDs.
 * @return {Promise}
 * @resolves to Map of bookmark details. The key is guid.
 */
async function getBookmarkDetailMap(aGuids) {
  return lazy.PlacesUtils.withConnectionWrapper(
    "Bookmarks.geBookmarkDetailMap",
    async db => {
      let entries = new Map();
      for (let chunk of lazy.PlacesUtils.chunkArray(aGuids, db.variableLimit)) {
        await db.executeCached(
          `
            SELECT
              b.guid,
              b.id,
              b.parent,
              IFNULL(h.frecency, 0),
              IFNULL(h.hidden, 0),
              IFNULL(h.visit_count, 0),
              h.last_visit_date,
              (
                SELECT group_concat(pp.title ORDER BY pp.title)
                FROM moz_bookmarks bb
                JOIN moz_bookmarks pp ON pp.id = bb.parent
                JOIN moz_bookmarks gg ON gg.id = pp.parent
                WHERE bb.fk = h.id
                AND gg.guid = '${Bookmarks.tagsGuid}'
              ),
              t.guid, t.id, t.title
            FROM moz_bookmarks b
            LEFT JOIN moz_places h ON h.id = b.fk
            LEFT JOIN moz_bookmarks t ON t.guid = target_folder_guid(h.url)
            WHERE b.guid IN (${lazy.PlacesUtils.sqlBindPlaceholders(chunk)})
            `,
          chunk,
          row => {
            const lastVisitDate = row.getResultByIndex(6);
            entries.set(row.getResultByIndex(0), {
              id: row.getResultByIndex(1),
              parentId: row.getResultByIndex(2),
              frecency: row.getResultByIndex(3),
              hidden: row.getResultByIndex(4),
              visitCount: row.getResultByIndex(5),
              lastVisitDate: lastVisitDate
                ? lazy.PlacesUtils.toDate(lastVisitDate).getTime()
                : null,
              tags: row.getResultByIndex(7),
              targetFolderGuid: row.getResultByIndex(8),
              targetFolderItemId: row.getResultByIndex(9),
              targetFolderTitle: row.getResultByIndex(10),
            });
          }
        );
      }
      return entries;
    }
  );
}
PK
!<�	���%modules/BrowserTelemetryUtils.sys.mjs/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export var BrowserTelemetryUtils = {
  recordSiteOriginTelemetry(aWindows, aIsGeckoView) {
    Services.tm.idleDispatchToMainThread(() => {
      this._recordSiteOriginTelemetry(aWindows, aIsGeckoView);
    });
  },

  computeSiteOriginCount(aWindows, aIsGeckoView) {
    // Geckoview and Desktop work differently. On desktop, aBrowser objects
    // holds an array of tabs which we can use to get the <browser> objects.
    // In Geckoview, it is apps' responsibility to keep track of the tabs, so
    // there isn't an easy way for us to get the tabs.
    let tabs = [];
    if (aIsGeckoView) {
      // To get all active windows; Each tab has its own window
      tabs = aWindows;
    } else {
      for (const win of aWindows) {
        tabs = tabs.concat(win.gBrowser.tabs);
      }
    }

    let topLevelBCs = [];

    for (const tab of tabs) {
      let browser;
      if (aIsGeckoView) {
        browser = tab.browser;
      } else {
        browser = tab.linkedBrowser;
      }

      if (browser.browsingContext) {
        // This is the top level browsingContext
        topLevelBCs.push(browser.browsingContext);
      }
    }

    return CanonicalBrowsingContext.countSiteOrigins(topLevelBCs);
  },

  _recordSiteOriginTelemetry(aWindows, aIsGeckoView) {
    let currentTime = Date.now();

    // default is 5 minutes
    if (!this.min_interval) {
      this.min_interval = Services.prefs.getIntPref(
        "telemetry.number_of_site_origin.min_interval",
        300000
      );
    }

    let originCount = this.computeSiteOriginCount(aWindows, aIsGeckoView);

    // Discard the first load because most of the time the first load only has 1
    // tab and 1 window open, so it is useless to report it.
    if (!this._lastRecordSiteOrigin) {
      this._lastRecordSiteOrigin = currentTime;
    } else if (currentTime >= this._lastRecordSiteOrigin + this.min_interval) {
      this._lastRecordSiteOrigin = currentTime;

      Glean.geckoview.documentSiteOrigins.accumulateSingleSample(originCount);
    }
  },
};
PK
!<
�=�jWjWmodules/BrowserUtils.sys.mjs/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  ReaderMode: "resource://gre/modules/ReaderMode.sys.mjs",
  Region: "resource://gre/modules/Region.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "CatManListenerManager", () => {
  const CatManListenerManager = {
    cachedModules: {},
    cachedListeners: {},
    // All 3 category manager notifications will have the category name
    // as the `data` part of the observer notification.
    observe(_subject, _topic, categoryName) {
      delete this.cachedListeners[categoryName];
    },
    /**
     * Fetch and parse category manager consumers for a given category name.
     * Will use cachedListeners for the given category name if they exist.
     */
    getListeners(categoryName) {
      if (Object.hasOwn(this.cachedListeners, categoryName)) {
        return this.cachedListeners[categoryName];
      }
      let rv = Array.from(
        Services.catMan.enumerateCategory(categoryName),
        ({ data: module, value }) => {
          try {
            let [objName, method] = value.split(".");
            if (!Object.hasOwn(this.cachedModules, module)) {
              this.cachedModules[module] = ChromeUtils.importESModule(module);
            }
            let fn = async (...args) => {
              try {
                // This await doesn't do much as the caller won't await us,
                // but means we can catch and report any exceptions.
                await this.cachedModules[module][objName][method](...args);
              } catch (ex) {
                console.error(
                  `Error in processing ${categoryName} for ${objName}`
                );
                console.error(ex);
              }
            };
            return fn;
          } catch (ex) {
            console.error(
              `Error processing category manifest for ${module}: ${value}`,
              ex
            );
            return null;
          }
        }
      );
      // Remove any null entries.
      rv = rv.filter(l => !!l);
      this.cachedListeners[categoryName] = rv;
      return rv;
    },
  };
  Services.obs.addObserver(
    CatManListenerManager,
    "xpcom-category-entry-removed"
  );
  Services.obs.addObserver(CatManListenerManager, "xpcom-category-entry-added");
  Services.obs.addObserver(CatManListenerManager, "xpcom-category-cleared");
  return CatManListenerManager;
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "INVALID_SHAREABLE_SCHEMES",
  "services.sync.engine.tabs.filteredSchemes",
  "",
  null,
  val => {
    return new Set(val.split("|"));
  }
);

ChromeUtils.defineLazyGetter(lazy, "gLocalization", () => {
  return new Localization(["toolkit/global/browser-utils.ftl"], true);
});

function stringPrefToSet(prefVal) {
  return new Set(
    prefVal
      .toLowerCase()
      .split(/\s*,\s*/g) // split on commas, ignoring whitespace
      .filter(v => !!v) // discard any falsey values
  );
}

export var BrowserUtils = {
  /**
   * Return or create a principal with the content of one, and the originAttributes
   * of an existing principal (e.g. on a docshell, where the originAttributes ought
   * not to change, that is, we should keep the userContextId, privateBrowsingId,
   * etc. the same when changing the principal).
   *
   * @param principal
   *        The principal whose content/null/system-ness we want.
   * @param existingPrincipal
   *        The principal whose originAttributes we want, usually the current
   *        principal of a docshell.
   * @return an nsIPrincipal that matches the content/null/system-ness of the first
   *         param, and the originAttributes of the second.
   */
  principalWithMatchingOA(principal, existingPrincipal) {
    // Don't care about system principals:
    if (principal.isSystemPrincipal) {
      return principal;
    }

    // If the originAttributes already match, just return the principal as-is.
    if (existingPrincipal.originSuffix == principal.originSuffix) {
      return principal;
    }

    let secMan = Services.scriptSecurityManager;
    if (principal.isContentPrincipal) {
      return secMan.principalWithOA(
        principal,
        existingPrincipal.originAttributes
      );
    }

    if (principal.isNullPrincipal) {
      return secMan.createNullPrincipal(existingPrincipal.originAttributes);
    }
    throw new Error(
      "Can't change the originAttributes of an expanded principal!"
    );
  },

  /**
   * Returns true if |mimeType| is text-based, or false otherwise.
   *
   * @param mimeType
   *        The MIME type to check.
   */
  mimeTypeIsTextBased(mimeType) {
    return (
      mimeType.startsWith("text/") ||
      mimeType.endsWith("+xml") ||
      mimeType.endsWith("+json") ||
      mimeType == "application/x-javascript" ||
      mimeType == "application/javascript" ||
      mimeType == "application/json" ||
      mimeType == "application/xml"
    );
  },

  /**
   * Returns true if we can show a find bar, including FAYT, for the specified
   * document location. The location must not be in a blocklist of specific
   * "about:" pages for which find is disabled.
   *
   * This can be called from the parent process or from content processes.
   */
  canFindInPage(location) {
    return (
      !location.startsWith("about:preferences") &&
      !location.startsWith("about:settings") &&
      !location.startsWith("about:logins") &&
      !location.startsWith("about:firefoxview")
    );
  },

  isFindbarVisible(docShell) {
    const FINDER_SYS_MJS = "resource://gre/modules/Finder.sys.mjs";
    return (
      Cu.isESModuleLoaded(FINDER_SYS_MJS) &&
      ChromeUtils.importESModule(FINDER_SYS_MJS).Finder.isFindbarVisible(
        docShell
      )
    );
  },

  /**
   * Returns a Promise which resolves when the given observer topic has been
   * observed.
   *
   * @param {string} topic
   *        The topic to observe.
   * @param {function(nsISupports, string)} [test]
   *        An optional test function which, when called with the
   *        observer's subject and data, should return true if this is the
   *        expected notification, false otherwise.
   * @returns {Promise<object>}
   */
  promiseObserved(topic, test = () => true) {
    return new Promise(resolve => {
      let observer = (subject, topic, data) => {
        if (test(subject, data)) {
          Services.obs.removeObserver(observer, topic);
          resolve({ subject, data });
        }
      };
      Services.obs.addObserver(observer, topic);
    });
  },

  formatURIStringForDisplay(uriString, options = {}) {
    try {
      return this.formatURIForDisplay(Services.io.newURI(uriString), options);
    } catch (ex) {
      return uriString;
    }
  },

  formatURIForDisplay(uri, options = {}) {
    let { showInsecureHTTP = false } = options;
    switch (uri.scheme) {
      case "view-source": {
        let innerURI = uri.spec.substring("view-source:".length);
        return this.formatURIStringForDisplay(innerURI, options);
      }
      case "http":
      // Fall through.
      case "https": {
        let host = uri.displayHostPort;
        if (!showInsecureHTTP && host.startsWith("www.")) {
          host = Services.eTLD.getSchemelessSite(uri);
        }
        if (showInsecureHTTP && uri.scheme == "http") {
          return "http://" + host;
        }
        return host;
      }
      case "about":
        return "about:" + uri.filePath;
      case "blob":
        try {
          let url = new URL(uri.specIgnoringRef);
          // _If_ we find a non-null origin, report that.
          if (url.origin && url.origin != "null") {
            return this.formatURIStringForDisplay(url.origin, options);
          }
          // otherwise, fall through...
        } catch (ex) {
          console.error("Invalid blob URI passed to formatURIForDisplay: ", ex);
        }
      /* For blob URIs without an origin, fall through and use the data URI
       * logic (shows just "(data)", localized). */
      case "data":
        return lazy.gLocalization.formatValueSync("browser-utils-url-data");
      case "moz-extension": {
        let policy = WebExtensionPolicy.getByURI(uri);
        return lazy.gLocalization.formatValueSync(
          "browser-utils-url-extension",
          { extension: policy?.name.trim() || uri.spec }
        );
      }
      case "chrome":
      case "resource":
      case "jar":
      case "file":
      default:
        try {
          let url = uri.QueryInterface(Ci.nsIURL);
          // Just the filename if we have one:
          if (url.fileName) {
            return url.fileName;
          }
          // We won't get a filename for a path that looks like:
          // /foo/bar/baz/
          // So try the directory name:
          if (url.directory) {
            let parts = url.directory.split("/");
            // Pop off any empty bits at the end:
            let last;
            while (!last && parts.length) {
              last = parts.pop();
            }
            if (last) {
              return last;
            }
          }
        } catch (ex) {
          console.error(ex);
        }
    }
    return uri.asciiHost || uri.spec;
  },

  // Given a URL returns a (possibly transformed) URL suitable for sharing, or null if
  // no such URL can be obtained.
  getShareableURL(url) {
    if (!url) {
      return null;
    }

    // Carve out an exception for about:reader.
    if (url.spec.startsWith("about:reader?")) {
      url = Services.io.newURI(lazy.ReaderMode.getOriginalUrl(url.spec));
    }
    // Disallow sharing URLs with more than 65535 characters.
    if (url.spec.length > 65535) {
      return null;
    }
    // Use the same preference as synced tabs to disable what kind
    // of tabs we can send to another device
    return lazy.INVALID_SHAREABLE_SCHEMES.has(url.scheme) ? null : url;
  },

  /**
   * Extracts linkNode and href for a click event.
   *
   * @param event
   *        The click event.
   * @return [href, linkNode, linkPrincipal].
   *
   * @note linkNode will be null if the click wasn't on an anchor
   *       element. This includes SVG links, because callers expect |node|
   *       to behave like an <a> element, which SVG links (XLink) don't.
   */
  hrefAndLinkNodeForClickEvent(event) {
    // We should get a window off the event, and bail if not:
    let content = event.view || event.composedTarget?.ownerGlobal;
    if (!content?.HTMLAnchorElement) {
      return null;
    }
    function isHTMLLink(aNode) {
      // Be consistent with what nsContextMenu.js does.
      return (
        (content.HTMLAnchorElement.isInstance(aNode) && aNode.href) ||
        (content.HTMLAreaElement.isInstance(aNode) && aNode.href) ||
        content.HTMLLinkElement.isInstance(aNode)
      );
    }

    let node = event.composedTarget;
    while (node && !isHTMLLink(node)) {
      node = node.flattenedTreeParentNode;
    }

    if (node) {
      return [node.href, node, node.ownerDocument.nodePrincipal];
    }

    // If there is no linkNode, try simple XLink.
    let href, baseURI;
    node = event.composedTarget;
    while (node && !href) {
      if (
        node.nodeType == content.Node.ELEMENT_NODE &&
        (node.localName == "a" ||
          node.namespaceURI == "http://www.w3.org/1998/Math/MathML")
      ) {
        href =
          node.getAttribute("href") ||
          node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
        if (href) {
          baseURI = node.ownerDocument.baseURIObject;
          break;
        }
      }
      node = node.flattenedTreeParentNode;
    }

    // In case of XLink, we don't return the node we got href from since
    // callers expect <a>-like elements.
    // Note: makeURI() will throw if aUri is not a valid URI.
    return [
      href ? Services.io.newURI(href, null, baseURI).spec : null,
      null,
      node && node.ownerDocument.nodePrincipal,
    ];
  },

  /**
   * whereToOpenLink() looks at an event to decide where to open a link.
   *
   * The event may be a mouse event (click, double-click, middle-click) or keypress event (enter).
   *
   * On Windows, the modifiers are:
   * Ctrl        new tab, selected
   * Shift       new window
   * Ctrl+Shift  new tab, in background
   * Alt         save
   *
   * Middle-clicking is the same as Ctrl+clicking (it opens a new tab).
   *
   * Exceptions:
   * - Alt is ignored for menu items selected using the keyboard so you don't accidentally save stuff.
   *    (Currently, the Alt isn't sent here at all for menu items, but that will change in bug 126189.)
   * - Alt is hard to use in context menus, because pressing Alt closes the menu.
   * - Alt can't be used on the bookmarks toolbar because Alt is used for "treat this as something draggable".
   * - The button is ignored for the middle-click-paste-URL feature, since it's always a middle-click.
   *
   * @param e {Event|Object} Event or JSON Object
   * @param ignoreButton {Boolean}
   * @param ignoreAlt {Boolean}
   * @returns {"current" | "tabshifted" | "tab" | "save" | "window"}
   */
  whereToOpenLink(e, ignoreButton, ignoreAlt) {
    // This method must treat a null event like a left click without modifier keys (i.e.
    // e = { shiftKey:false, ctrlKey:false, metaKey:false, altKey:false, button:0 })
    // for compatibility purposes.
    if (!e) {
      return "current";
    }

    e = this.getRootEvent(e);

    var shift = e.shiftKey;
    var ctrl = e.ctrlKey;
    var meta = e.metaKey;
    var alt = e.altKey && !ignoreAlt;

    // ignoreButton allows "middle-click paste" to use function without always opening in a new window.
    let middle = !ignoreButton && e.button == 1;
    let middleUsesTabs = Services.prefs.getBoolPref(
      "browser.tabs.opentabfor.middleclick",
      true
    );
    let middleUsesNewWindow = Services.prefs.getBoolPref(
      "middlemouse.openNewWindow",
      false
    );

    // Don't do anything special with right-mouse clicks.  They're probably clicks on context menu items.

    // See also nsWindowWatcher::GetWindowOpenLocation in
    // toolkit/components/windowwatcher/nsWindowWatcher.cpp

    var metaKey = AppConstants.platform == "macosx" ? meta : ctrl;
    if (metaKey || (middle && middleUsesTabs)) {
      return shift ? "tabshifted" : "tab";
    }

    if (alt && Services.prefs.getBoolPref("browser.altClickSave", false)) {
      return "save";
    }

    if (shift || (middle && !middleUsesTabs && middleUsesNewWindow)) {
      return "window";
    }

    return "current";
  },

  // Utility function to check command events for potential middle-click events
  // from checkForMiddleClick and unwrap them.
  getRootEvent(aEvent) {
    // Part of the fix for Bug 1523813.
    // Middle-click events arrive here wrapped in different numbers (1-2) of
    // command events, depending on the button originally clicked.
    if (!aEvent) {
      return aEvent;
    }
    let tempEvent = aEvent;
    while (tempEvent.sourceEvent) {
      if (tempEvent.sourceEvent.button == 1) {
        aEvent = tempEvent.sourceEvent;
        break;
      }
      tempEvent = tempEvent.sourceEvent;
    }
    return aEvent;
  },

  /**
   * Invoke all the category manager consumers of a given JS consumer.
   * Similar to the (C++-only) NS_CreateServicesFromCategory in that it'll
   * abstract away the actual work of invoking the modules/services.
   * Different in that it's JS-only and will invoke methods in modules
   * instead of using XPCOM services.
   */
  callModulesFromCategory(categoryName, ...args) {
    for (let listener of lazy.CatManListenerManager.getListeners(
      categoryName
    )) {
      // Note that we deliberately do not await anything here.
      listener(...args);
    }
  },

  /**
   * An enumeration of the promotion types that can be passed to shouldShowPromo
   */
  PromoType: {
    DEFAULT: 0, // invalid
    VPN: 1,
    RELAY: 2,
    FOCUS: 3,
    PIN: 4,
    COOKIE_BANNERS: 5,
  },

  /**
   * Should a given promo be shown to the user now, based on things including:
   *
   *  current region
   *  home region
   *  where ads for a particular thing are allowed
   *  where they are illegal
   *  in what regions is the thing being promoted supported?
   *  whether there is an active enterprise policy
   *  settings of specific preferences related to this promo
   *
   * @param {BrowserUtils.PromoType} promoType - What promo are we checking on?
   *
   * @return {boolean} - should we display this promo now or not?
   */
  shouldShowPromo(promoType) {
    switch (promoType) {
      case this.PromoType.VPN:
      case this.PromoType.FOCUS:
      case this.PromoType.PIN:
      case this.PromoType.RELAY:
      case this.PromoType.COOKIE_BANNERS:
        break;
      default:
        throw new Error("Unknown promo type: ", promoType);
    }

    const info = PromoInfo[promoType];
    const promoEnabled =
      !info.enabledPref || Services.prefs.getBoolPref(info.enabledPref, true);

    const homeRegion = lazy.Region.home || "";
    const currentRegion = lazy.Region.current || "";

    let inSupportedRegion = true;
    if ("supportedRegions" in info.lazyStringSetPrefs) {
      const supportedRegions =
        info.lazyStringSetPrefs.supportedRegions.lazyValue;
      inSupportedRegion =
        supportedRegions.has(currentRegion.toLowerCase()) ||
        supportedRegions.has(homeRegion.toLowerCase());
    }

    const avoidAdsRegions =
      info.lazyStringSetPrefs.disallowedRegions?.lazyValue;

    // Don't show promo if there's an active enterprise policy
    const noActivePolicy =
      info.showForEnterprise ||
      !Services.policies ||
      Services.policies.status !== Services.policies.ACTIVE;

    // Promos may add custom checks that must pass.
    const passedExtraCheck = !info.extraCheck || info.extraCheck();

    return (
      promoEnabled &&
      !avoidAdsRegions?.has(homeRegion.toLowerCase()) &&
      !avoidAdsRegions?.has(currentRegion.toLowerCase()) &&
      !info.illegalRegions.includes(homeRegion.toLowerCase()) &&
      !info.illegalRegions.includes(currentRegion.toLowerCase()) &&
      inSupportedRegion &&
      noActivePolicy &&
      passedExtraCheck
    );
  },

  /**
   * @deprecated in favor of shouldShowPromo
   */
  shouldShowVPNPromo() {
    return this.shouldShowPromo(this.PromoType.VPN);
  },

  // Return true if Send to Device emails are supported for user's locale
  sendToDeviceEmailsSupported() {
    const userLocale = Services.locale.appLocaleAsBCP47.toLowerCase();
    return this.emailSupportedLocales.has(userLocale);
  },
};

/**
 * A table of promos used by shouldShowPromo to decide whether or not to show.
 * Each entry defines the criteria for a given promo, and also houses lazy
 * getters for specified string set preferences.
 */
let PromoInfo = {
  [BrowserUtils.PromoType.VPN]: {
    enabledPref: "browser.vpn_promo.enabled",
    lazyStringSetPrefs: {
      supportedRegions: {
        name: "browser.contentblocking.report.vpn_regions",
        default:
          "ca,my,nz,sg,gb,gg,im,io,je,uk,vg,as,mp,pr,um,us,vi,de,fr,at,be,ch,es,it,ie,nl,se,fi,bg,cy,cz,dk,ee,hr,hu,lt,lu,lv,mt,pl,pt,ro,si,sk",
      },
      disallowedRegions: {
        name: "browser.vpn_promo.disallowed_regions",
        default: "ae,by,cn,cu,iq,ir,kp,om,ru,sd,sy,tm,tr",
      },
    },
    //See https://github.com/search?q=repo%3Amozilla%2Fbedrock+VPN_EXCLUDED_COUNTRY_CODES&type=code
    illegalRegions: [
      "ae",
      "by",
      "cn",
      "cu",
      "iq",
      "ir",
      "kp",
      "om",
      "ru",
      "sd",
      "sy",
      "tm",
      "tr",
    ],
  },
  [BrowserUtils.PromoType.FOCUS]: {
    enabledPref: "browser.promo.focus.enabled",
    lazyStringSetPrefs: {
      // there are no particular limitions to where it is "supported",
      // so we leave out the supported pref
      disallowedRegions: {
        name: "browser.promo.focus.disallowed_regions",
        default: "cn",
      },
    },
    illegalRegions: ["cn"],
  },
  [BrowserUtils.PromoType.PIN]: {
    enabledPref: "browser.promo.pin.enabled",
    lazyStringSetPrefs: {},
    illegalRegions: [],
  },
  [BrowserUtils.PromoType.RELAY]: {
    lazyStringSetPrefs: {},
    illegalRegions: [],
    // Returns true if user is using the FxA "production" instance, or returns
    // false for custom FxA instance (such as accounts.firefox.com.cn for the
    // China repack) which doesn't support authentication for addons like Relay.
    extraCheck: () =>
      !Services.prefs.getCharPref("identity.fxaccounts.autoconfig.uri", "") &&
      [
        "identity.fxaccounts.remote.root",
        "identity.fxaccounts.auth.uri",
        "identity.fxaccounts.remote.oauth.uri",
        "identity.fxaccounts.remote.profile.uri",
        "identity.fxaccounts.remote.pairing.uri",
        "identity.sync.tokenserver.uri",
      ].every(pref => !Services.prefs.prefHasUserValue(pref)),
  },
  [BrowserUtils.PromoType.COOKIE_BANNERS]: {
    enabledPref: "browser.promo.cookiebanners.enabled",
    lazyStringSetPrefs: {},
    illegalRegions: [],
    showForEnterprise: true,
  },
};

/*
 * Finish setting up the PromoInfo data structure by attaching lazy prefs getters
 * as specified in the structure. (the object for each pref in the lazyStringSetPrefs
 * gets a `lazyValue` property attached to it).
 */
for (let promo of Object.values(PromoInfo)) {
  for (let prefObj of Object.values(promo.lazyStringSetPrefs)) {
    XPCOMUtils.defineLazyPreferenceGetter(
      prefObj,
      "lazyValue",
      prefObj.name,
      prefObj.default,
      null,
      stringPrefToSet
    );
  }
}

XPCOMUtils.defineLazyPreferenceGetter(
  BrowserUtils,
  "navigationRequireUserInteraction",
  "browser.navigation.requireUserInteraction",
  false
);

XPCOMUtils.defineLazyPreferenceGetter(
  BrowserUtils,
  "emailSupportedLocales",
  "browser.send_to_device_locales",
  "de,en-GB,en-US,es-AR,es-CL,es-ES,es-MX,fr,id,pl,pt-BR,ru,zh-TW",
  null,
  stringPrefToSet
);
PK
!<a=�`k
k
modules/CSV.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * A Class to parse CSV files
 */

const QUOTATION_MARK = '"';
const LINE_BREAKS = ["\r", "\n"];
const EOL = {};

class ParsingFailedException extends Error {
  constructor(message) {
    super(message ? message : `Stopped parsing because of wrong csv format`);
  }
}

export class CSV {
  /**
   * Parses a csv formated string into rows split into [headerLine, parsedLines].
   * The csv string format has to follow RFC 4180, otherwise the parsing process is stopped and a ParsingFailedException is thrown, e.g.:
   * (wrong format => right format):
   * 'abc"def' => 'abc""def'
   * abc,def => "abc,def"
   *
   * @param {string} text
   * @param {string} delimiter a comma for CSV files and a tab for TSV files
   * @returns {Array[]} headerLine: column names (first line of text), parsedLines: Array of Login Objects with column name as properties and login data as values.
   */
  static parse(text, delimiter) {
    let headerline = [];
    let parsedLines = [];

    for (let row of this.mapValuesToRows(this.readCSV(text, delimiter))) {
      if (!headerline.length) {
        headerline = row;
      } else {
        let login = {};
        row.forEach((attr, i) => (login[headerline[i]] = attr));
        parsedLines.push(login);
      }
    }
    return [headerline, parsedLines];
  }
  static *readCSV(text, delimiter) {
    function maySkipMultipleLineBreaks() {
      while (LINE_BREAKS.includes(text[current])) {
        current++;
      }
    }
    function readUntilSingleQuote() {
      const start = ++current;
      while (current < text.length) {
        if (text[current] === QUOTATION_MARK) {
          if (text[current + 1] !== QUOTATION_MARK) {
            const result = text.slice(start, current).replaceAll('""', '"');
            current++;
            return result;
          }
          current++;
        }
        current++;
      }
      throw new ParsingFailedException();
    }
    function readUntilDelimiterOrNewLine() {
      const start = current;
      while (current < text.length) {
        if (text[current] === delimiter) {
          const result = text.slice(start, current);
          current++;
          return result;
        } else if (LINE_BREAKS.includes(text[current])) {
          const result = text.slice(start, current);
          return result;
        }
        current++;
      }
      return text.slice(start);
    }
    let current = 0;
    maySkipMultipleLineBreaks();

    while (current < text.length) {
      if (LINE_BREAKS.includes(text[current])) {
        maySkipMultipleLineBreaks();
        yield EOL;
      }

      let quotedValue = "";
      let value = "";

      if (text[current] === QUOTATION_MARK) {
        quotedValue = readUntilSingleQuote();
      }

      value = readUntilDelimiterOrNewLine();

      if (quotedValue && value) {
        throw new ParsingFailedException();
      }

      yield quotedValue ? quotedValue : value;
    }
  }

  static *mapValuesToRows(values) {
    let row = [];
    for (const value of values) {
      if (value === EOL) {
        yield row;
        row = [];
      } else {
        row.push(value);
      }
    }
    if (!(row.length === 1 && row[0] === "") && row.length) {
      yield row;
    }
  }
}
PK
!<
����C�Cmodules/CaptiveDetect.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const DEBUG = false; // set to true to show debug messages

const kCAPTIVEPORTALDETECTOR_CID = Components.ID(
  "{d9cd00ba-aa4d-47b1-8792-b1fe0cd35060}"
);

const kOpenCaptivePortalLoginEvent = "captive-portal-login";
const kAbortCaptivePortalLoginEvent = "captive-portal-login-abort";
const kCaptivePortalLoginSuccessEvent = "captive-portal-login-success";
const kCaptivePortalCheckComplete = "captive-portal-check-complete";

function URLFetcher(url, timeout) {
  let self = this;
  let xhr = new XMLHttpRequest();
  xhr.open("GET", url, true);
  // Prevent the request from reading from the cache.
  xhr.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
  // Prevent the request from writing to the cache.
  xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
  // Prevent privacy leaks
  xhr.channel.loadFlags |= Ci.nsIRequest.LOAD_ANONYMOUS;
  // Use the system's resolver for this check
  xhr.channel.setTRRMode(Ci.nsIRequest.TRR_DISABLED_MODE);
  // We except this from being classified
  xhr.channel.loadFlags |= Ci.nsIChannel.LOAD_BYPASS_URL_CLASSIFIER;
  // Prevent HTTPS-Only Mode from upgrading the request.
  xhr.channel.loadInfo.httpsOnlyStatus |= Ci.nsILoadInfo.HTTPS_ONLY_EXEMPT;
  // Allow deprecated HTTP request from SystemPrincipal
  xhr.channel.loadInfo.allowDeprecatedSystemRequests = true;

  // We don't want to follow _any_ redirects
  xhr.channel.QueryInterface(Ci.nsIHttpChannel).redirectionLimit = 0;

  // bug 1666072 - firefox.com returns a HSTS header triggering a https upgrade
  // but the upgrade triggers an internal redirect causing an incorrect locked
  // portal notification. We exclude CP detection from STS.
  xhr.channel.QueryInterface(Ci.nsIHttpChannel).allowSTS = false;

  // The Cache-Control header is only interpreted by proxies and the
  // final destination. It does not help if a resource is already
  // cached locally.
  xhr.setRequestHeader("Cache-Control", "no-cache");
  // HTTP/1.0 servers might not implement Cache-Control and
  // might only implement Pragma: no-cache
  xhr.setRequestHeader("Pragma", "no-cache");

  xhr.timeout = timeout;
  xhr.ontimeout = function () {
    self.ontimeout();
  };
  xhr.onerror = function () {
    self.onerror();
  };
  xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
      if (self._isAborted) {
        return;
      }
      if (xhr.status === 200) {
        self.onsuccess(xhr.responseText);
      } else if (xhr.status) {
        self.onredirectorerror(xhr.status);
      } else if (
        xhr.channel &&
        xhr.channel.status == Cr.NS_ERROR_REDIRECT_LOOP
      ) {
        // For some redirects we don't get a status, so we need to check it
        // this way. This only works because we set the redirectionLimit to 0.
        self.onredirectorerror(300);
        // No need to invoke the onerror callback, we handled it here.
        xhr.onerror = null;
      }
    }
  };
  xhr.send();
  this._xhr = xhr;
}

URLFetcher.prototype = {
  _isAborted: false,
  ontimeout() {},
  onerror() {},
  abort() {
    if (!this._isAborted) {
      this._isAborted = true;
      this._xhr.abort();
    }
  },
  usedProxy() {
    try {
      if (
        this._xhr &&
        this._xhr.channel &&
        this._xhr.channel.QueryInterface(Ci.nsIHttpChannelInternal).isProxyUsed
      ) {
        return true;
      }
    } catch (e) {}

    return false;
  },
};

function LoginObserver(captivePortalDetector) {
  const LOGIN_OBSERVER_STATE_DETACHED = 0; /* Should not monitor network activity since no ongoing login procedure */
  const LOGIN_OBSERVER_STATE_IDLE = 1; /* No network activity currently, waiting for a longer enough idle period */
  const LOGIN_OBSERVER_STATE_BURST = 2; /* Network activity is detected, probably caused by a login procedure */
  const LOGIN_OBSERVER_STATE_VERIFY_NEEDED = 3; /* Verifing network accessiblity is required after a long enough idle */
  const LOGIN_OBSERVER_STATE_VERIFYING = 4; /* LoginObserver is probing if public network is available */

  let state = LOGIN_OBSERVER_STATE_DETACHED;

  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  let activityDistributor = Cc[
    "@mozilla.org/network/http-activity-distributor;1"
  ].getService(Ci.nsIHttpActivityDistributor);
  let urlFetcher = null;

  let pageCheckingDone = function pageCheckingDone() {
    if (state === LOGIN_OBSERVER_STATE_VERIFYING) {
      urlFetcher = null;
      // Finish polling the canonical site, switch back to idle state and
      // waiting for next burst
      state = LOGIN_OBSERVER_STATE_IDLE;
      timer.initWithCallback(
        observer,
        captivePortalDetector._pollingTime,
        timer.TYPE_ONE_SHOT
      );
    }
  };

  let checkPageContent = function checkPageContent() {
    debug("checking if public network is available after the login procedure");

    urlFetcher = new URLFetcher(
      captivePortalDetector._canonicalSiteURL,
      captivePortalDetector._maxWaitingTime
    );
    urlFetcher.ontimeout = pageCheckingDone;
    urlFetcher.onerror = pageCheckingDone;
    urlFetcher.onsuccess = function (content) {
      if (captivePortalDetector.validateContent(content)) {
        urlFetcher = null;
        captivePortalDetector.executeCallback(true);
      } else {
        pageCheckingDone();
      }
    };
    urlFetcher.onredirectorerror = pageCheckingDone;
  };

  // Public interface of LoginObserver
  let observer = {
    QueryInterface: ChromeUtils.generateQI([
      "nsIHttpActivityObserver",
      "nsITimerCallback",
    ]),

    attach: function attach() {
      if (state === LOGIN_OBSERVER_STATE_DETACHED) {
        activityDistributor.addObserver(this);
        state = LOGIN_OBSERVER_STATE_IDLE;
        timer.initWithCallback(
          this,
          captivePortalDetector._pollingTime,
          timer.TYPE_ONE_SHOT
        );
        debug("attach HttpObserver for login activity");
      }
    },

    detach: function detach() {
      if (state !== LOGIN_OBSERVER_STATE_DETACHED) {
        if (urlFetcher) {
          urlFetcher.abort();
          urlFetcher = null;
        }
        activityDistributor.removeObserver(this);
        timer.cancel();
        state = LOGIN_OBSERVER_STATE_DETACHED;
        debug("detach HttpObserver for login activity");
      }
    },

    /*
     * Treat all HTTP transactions as captive portal login activities.
     */
    observeActivity: function observeActivity(
      aHttpChannel,
      aActivityType,
      aActivitySubtype
    ) {
      if (
        aActivityType ===
          Ci.nsIHttpActivityObserver.ACTIVITY_TYPE_HTTP_TRANSACTION &&
        aActivitySubtype ===
          Ci.nsIHttpActivityObserver.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE
      ) {
        switch (state) {
          case LOGIN_OBSERVER_STATE_IDLE:
          case LOGIN_OBSERVER_STATE_VERIFY_NEEDED:
            state = LOGIN_OBSERVER_STATE_BURST;
            break;
          default:
            break;
        }
      }
    },

    /*
     * Check if login activity is finished according to HTTP burst.
     */
    notify: function notify() {
      switch (state) {
        case LOGIN_OBSERVER_STATE_BURST:
          // Wait while network stays idle for a short period
          state = LOGIN_OBSERVER_STATE_VERIFY_NEEDED;
        // Fall through to start polling timer
        case LOGIN_OBSERVER_STATE_IDLE:
        // Just fall through to perform a captive portal check.
        case LOGIN_OBSERVER_STATE_VERIFY_NEEDED:
          // Polling the canonical website since network stays idle for a while
          state = LOGIN_OBSERVER_STATE_VERIFYING;
          checkPageContent();
          break;

        default:
          break;
      }
    },
  };

  return observer;
}

export function CaptivePortalDetector() {
  // Load preference
  this._canonicalSiteURL = null;
  this._canonicalSiteExpectedContent = null;

  try {
    this._canonicalSiteURL = Services.prefs.getCharPref(
      "captivedetect.canonicalURL"
    );
    this._canonicalSiteExpectedContent = Services.prefs.getCharPref(
      "captivedetect.canonicalContent"
    );
  } catch (e) {
    debug("canonicalURL or canonicalContent not set.");
  }

  this._maxWaitingTime = Services.prefs.getIntPref(
    "captivedetect.maxWaitingTime"
  );
  this._pollingTime = Services.prefs.getIntPref("captivedetect.pollingTime");
  this._maxRetryCount = Services.prefs.getIntPref(
    "captivedetect.maxRetryCount"
  );
  debug(
    "Load Prefs {site=" +
      this._canonicalSiteURL +
      ",content=" +
      this._canonicalSiteExpectedContent +
      ",time=" +
      this._maxWaitingTime +
      "max-retry=" +
      this._maxRetryCount +
      "}"
  );

  // Create HttpObserver for monitoring the login procedure
  this._loginObserver = LoginObserver(this);

  this._nextRequestId = 0;
  this._runningRequest = null;
  this._requestQueue = []; // Maintain a progress table, store callbacks and the ongoing XHR
  this._interfaceNames = {}; // Maintain names of the requested network interfaces

  debug(
    "CaptiveProtalDetector initiated, waiting for network connection established"
  );
}

CaptivePortalDetector.prototype = {
  classID: kCAPTIVEPORTALDETECTOR_CID,
  QueryInterface: ChromeUtils.generateQI(["nsICaptivePortalDetector"]),

  // nsICaptivePortalDetector
  checkCaptivePortal: function checkCaptivePortal(aInterfaceName, aCallback) {
    if (!this._canonicalSiteURL) {
      throw Components.Exception("No canonical URL set up.");
    }

    // Prevent multiple requests on a single network interface
    if (this._interfaceNames[aInterfaceName]) {
      throw Components.Exception(
        "Do not allow multiple request on one interface: " + aInterfaceName
      );
    }

    let request = { interfaceName: aInterfaceName };
    if (aCallback) {
      let callback = aCallback.QueryInterface(Ci.nsICaptivePortalCallback);
      request.callback = callback;
      request.retryCount = 0;
    }
    this._addRequest(request);
  },

  abort: function abort(aInterfaceName) {
    debug("abort for " + aInterfaceName);
    this._removeRequest(aInterfaceName);
  },

  finishPreparation: function finishPreparation(aInterfaceName) {
    debug('finish preparation phase for interface "' + aInterfaceName + '"');
    if (
      !this._runningRequest ||
      this._runningRequest.interfaceName !== aInterfaceName
    ) {
      debug("invalid finishPreparation for " + aInterfaceName);
      throw Components.Exception(
        "only first request is allowed to invoke |finishPreparation|"
      );
    }

    this._startDetection();
  },

  cancelLogin: function cancelLogin(eventId) {
    debug('login canceled by user for request "' + eventId + '"');
    // Captive portal login procedure is canceled by user
    if (
      this._runningRequest &&
      this._runningRequest.hasOwnProperty("eventId")
    ) {
      let id = this._runningRequest.eventId;
      if (eventId === id) {
        this.executeCallback(false);
      }
    }
  },

  _applyDetection: function _applyDetection() {
    debug("enter applyDetection(" + this._runningRequest.interfaceName + ")");

    // Execute network interface preparation
    if (this._runningRequest.hasOwnProperty("callback")) {
      this._runningRequest.callback.prepare();
    } else {
      this._startDetection();
    }
  },

  _startDetection: function _startDetection() {
    debug(
      "startDetection {site=" +
        this._canonicalSiteURL +
        ",content=" +
        this._canonicalSiteExpectedContent +
        ",time=" +
        this._maxWaitingTime +
        "}"
    );
    let self = this;

    let urlFetcher = new URLFetcher(
      this._canonicalSiteURL,
      this._maxWaitingTime
    );

    let mayRetry = this._mayRetry.bind(this);

    urlFetcher.ontimeout = mayRetry;
    urlFetcher.onerror = mayRetry;
    urlFetcher.onsuccess = function (content) {
      if (urlFetcher.usedProxy()) {
        // Don't trigger if channel used proxy.
        self.executeCallback(true);
        return;
      }
      if (self.validateContent(content)) {
        self.executeCallback(true);
      } else {
        // Content of the canonical website has been overwrite
        self._startLogin();
      }
    };
    urlFetcher.onredirectorerror = function (status) {
      if (urlFetcher.usedProxy()) {
        // Don't trigger if channel used proxy.
        self.executeCallback(true);
        return;
      }
      if (status >= 300 && status <= 399) {
        // The canonical website has been redirected to an unknown location
        self._startLogin();
      } else if (status === 511) {
        // Got a RFC 6585 "Network Authentication Required" error page
        self._startLogin();
      } else {
        mayRetry();
      }
    };

    this._runningRequest.urlFetcher = urlFetcher;
  },

  _startLogin: function _startLogin() {
    let id = this._allocateRequestId();
    let details = {
      type: kOpenCaptivePortalLoginEvent,
      id,
      url: this._canonicalSiteURL,
    };
    this._loginObserver.attach();
    this._runningRequest.eventId = id;
    this._sendEvent(kOpenCaptivePortalLoginEvent, details);
  },

  _mayRetry: function _mayRetry() {
    if (
      this._runningRequest &&
      this._runningRequest.retryCount++ < this._maxRetryCount
    ) {
      debug(
        "retry-Detection: " +
          this._runningRequest.retryCount +
          "/" +
          this._maxRetryCount
      );
      this._startDetection();
    } else {
      this.executeCallback(false);
    }
  },

  executeCallback: function executeCallback(success) {
    if (this._runningRequest) {
      debug("callback executed");
      if (this._runningRequest.hasOwnProperty("callback")) {
        this._runningRequest.callback.complete(success);
      }

      // Only when the request has a event id and |success| is true
      // do we need to notify the login-success event.
      if (this._runningRequest.hasOwnProperty("eventId") && success) {
        let details = {
          type: kCaptivePortalLoginSuccessEvent,
          id: this._runningRequest.eventId,
        };
        this._sendEvent(kCaptivePortalLoginSuccessEvent, details);
      }

      // Continue the following request
      this._runningRequest.complete = true;
      this._removeRequest(this._runningRequest.interfaceName);
    }
  },

  _sendEvent: function _sendEvent(topic, details) {
    debug('sendEvent "' + JSON.stringify(details) + '"');
    Services.obs.notifyObservers(this, topic, JSON.stringify(details));
  },

  validateContent: function validateContent(content) {
    debug("received content: " + content);
    let valid = content === this._canonicalSiteExpectedContent;
    // We need a way to indicate that a check has been performed, and if we are
    // still in a captive portal.
    this._sendEvent(kCaptivePortalCheckComplete, !valid);
    return valid;
  },

  _allocateRequestId: function _allocateRequestId() {
    let newId = this._nextRequestId++;
    return newId.toString();
  },

  _runNextRequest: function _runNextRequest() {
    let nextRequest = this._requestQueue.shift();
    if (nextRequest) {
      this._runningRequest = nextRequest;
      this._applyDetection();
    }
  },

  _addRequest: function _addRequest(request) {
    this._interfaceNames[request.interfaceName] = true;
    this._requestQueue.push(request);
    if (!this._runningRequest) {
      this._runNextRequest();
    }
  },

  _removeRequest: function _removeRequest(aInterfaceName) {
    if (!this._interfaceNames[aInterfaceName]) {
      return;
    }

    delete this._interfaceNames[aInterfaceName];

    if (
      this._runningRequest &&
      this._runningRequest.interfaceName === aInterfaceName
    ) {
      this._loginObserver.detach();

      if (!this._runningRequest.complete) {
        // Abort the user login procedure
        if (this._runningRequest.hasOwnProperty("eventId")) {
          let details = {
            type: kAbortCaptivePortalLoginEvent,
            id: this._runningRequest.eventId,
          };
          this._sendEvent(kAbortCaptivePortalLoginEvent, details);
        }

        // Abort the ongoing HTTP request
        if (this._runningRequest.hasOwnProperty("urlFetcher")) {
          this._runningRequest.urlFetcher.abort();
        }
      }

      debug("remove running request");
      this._runningRequest = null;

      // Continue next pending reqeust if the ongoing one has been aborted
      this._runNextRequest();
      return;
    }

    // Check if a pending request has been aborted
    for (let i = 0; i < this._requestQueue.length; i++) {
      if (this._requestQueue[i].interfaceName == aInterfaceName) {
        this._requestQueue.splice(i, 1);

        debug(
          "remove pending request #" +
            i +
            ", remaining " +
            this._requestQueue.length
        );
        break;
      }
    }
  },
};

var debug;
if (DEBUG) {
  // eslint-disable-next-line no-global-assign
  debug = function (s) {
    dump("-*- CaptivePortalDetector component: " + s + "\n");
  };
} else {
  // eslint-disable-next-line no-global-assign
  debug = function () {};
}
PK
!<�7�� modules/ClearDataService.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Downloads: "resource://gre/modules/Downloads.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
  ServiceWorkerCleanUp: "resource://gre/modules/ServiceWorkerCleanUp.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "sas",
  "@mozilla.org/storage/activity-service;1",
  "nsIStorageActivityService"
);
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "TrackingDBService",
  "@mozilla.org/tracking-db-service;1",
  "nsITrackingDBService"
);
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "IdentityCredentialStorageService",
  "@mozilla.org/browser/identity-credential-storage-service;1",
  "nsIIdentityCredentialStorageService"
);
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "bounceTrackingProtection",
  "@mozilla.org/bounce-tracking-protection;1",
  "nsIBounceTrackingProtection"
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "bounceTrackingProtectionMode",
  "privacy.bounceTrackingProtection.mode",
  Ci.nsIBounceTrackingProtection.MODE_DISABLED
);

/**
 * Test if host, OriginAttributes or principal belong to a baseDomain. Also
 * considers partitioned storage by inspecting OriginAttributes partitionKey.
 * @param options
 * @param {string} [options.host] - Optional host to compare to base domain.
 * @param {object} [options.originAttributes] - Optional origin attributes to
 * inspect for aBaseDomain. If omitted, partitionKey will not be matched.
 * @param {nsIPrincipal} [options.principal] - Optional principal to compare to
 * base domain.
 * @param {string} aBaseDomain - Domain to check for. Must be a valid, non-empty
 * baseDomain string.
 * @returns {boolean} Whether the host, originAttributes or principal matches
 * the base domain.
 */
function hasBaseDomain(
  { host = null, originAttributes = null, principal = null },
  aBaseDomain
) {
  if (!aBaseDomain) {
    throw new Error("Missing baseDomain.");
  }
  if (!host && !originAttributes && !principal) {
    throw new Error(
      "Missing host, originAttributes or principal to match with baseDomain."
    );
  }
  if (principal && (host || originAttributes)) {
    throw new Error(
      "Can only pass either principal or host and originAttributes."
    );
  }

  if (host && Services.eTLD.hasRootDomain(host, aBaseDomain)) {
    return true;
  }

  if (principal?.baseDomain == aBaseDomain) {
    return true;
  }

  originAttributes = originAttributes || principal?.originAttributes;
  if (!originAttributes) {
    return false;
  }

  return ChromeUtils.originAttributesMatchPattern(originAttributes, {
    partitionKeyPattern: { baseDomain: aBaseDomain },
  });
}

/**
 * Compute the base domain from a given host. This is a wrapper around
 * Services.eTLD.getBaseDomainFromHost which also supports IP addresses and
 * hosts such as "localhost" which are considered valid base domains for
 * principals and data storage.
 * @param {string} aDomainOrHost - Domain or host to be converted. May already
 * be a valid base domain.
 * @returns {string} Base domain of the given host. Returns aDomainOrHost if
 * already a base domain.
 */
function getBaseDomainWithFallback(aDomainOrHost) {
  let result = aDomainOrHost;
  try {
    result = Services.eTLD.getBaseDomainFromHost(aDomainOrHost);
  } catch (e) {
    if (
      e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS ||
      e.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
    ) {
      // For these 2 expected errors, just take the host as the result.
      // - NS_ERROR_HOST_IS_IP_ADDRESS: the host is in ipv4/ipv6.
      // - NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS: not enough domain parts to extract.
      result = aDomainOrHost;
    } else {
      throw e;
    }
  }
  return result;
}

// Here is a list of methods cleaners may implement. These methods must return a
// Promise object.
// * deleteAll() - this method _must_ exist. When called, it deletes all the
//                 data owned by the cleaner.
// * deleteByPrincipal() -  this method _must_ exist.
// * deleteByBaseDomain() - this method _must_ exist.
// * deleteByHost() - this method is implemented only if the cleaner knows
//                    how to delete data by host + originAttributes pattern. If
//                    not implemented, deleteAll() will be used as fallback.
// * deleteByRange() - this method is implemented only if the cleaner knows how
//                    to delete data by time range. It receives 2 time range
//                    parameters: aFrom/aTo. If not implemented, deleteAll() is
//                    used as fallback.
// * deleteByLocalFiles() - this method removes data held for local files and
//                          other hostless origins. If not implemented,
//                          **no fallback is used**, as for a number of
//                          cleaners, no such data will ever exist and
//                          therefore clearing it does not make sense.
// * deleteByOriginAttributes() - this method is implemented only if the cleaner
//                                knows how to delete data by originAttributes
//                                pattern.
// * cleanupAfterDeletionAtShutdown() - this method is implemented only if the
//                                      cleaner needs a separate step after
//                                      deletion. No-op if not implemented.
//                                      Currently called via
//                                      Sanitizer.maybeSanitizeSessionPrincipals().

const CookieCleaner = {
  deleteByLocalFiles(aOriginAttributes) {
    return new Promise(aResolve => {
      Services.cookies.removeCookiesFromExactHost(
        "",
        JSON.stringify(aOriginAttributes)
      );
      aResolve();
    });
  },

  deleteByHost(aHost, aOriginAttributes) {
    return new Promise(aResolve => {
      Services.cookies.removeCookiesFromExactHost(
        aHost,
        JSON.stringify(aOriginAttributes)
      );
      aResolve();
    });
  },

  deleteByPrincipal(aPrincipal) {
    // Fall back to clearing by host and OA pattern. This will over-clear, since
    // any properties that are not explicitly set in aPrincipal.originAttributes
    // will be wildcard matched.
    return this.deleteByHost(aPrincipal.host, aPrincipal.originAttributes);
  },

  async deleteByBaseDomain(aDomain) {
    Services.cookies.cookies
      .filter(({ rawHost, originAttributes }) =>
        hasBaseDomain({ host: rawHost, originAttributes }, aDomain)
      )
      .forEach(cookie => {
        Services.cookies.removeCookiesFromExactHost(
          cookie.rawHost,
          JSON.stringify(cookie.originAttributes)
        );
      });
  },

  deleteByRange(aFrom) {
    return Services.cookies.removeAllSince(aFrom);
  },

  deleteByOriginAttributes(aOriginAttributesString) {
    return new Promise(aResolve => {
      try {
        Services.cookies.removeCookiesWithOriginAttributes(
          aOriginAttributesString
        );
      } catch (ex) {}
      aResolve();
    });
  },

  deleteAll() {
    return new Promise(aResolve => {
      Services.cookies.removeAll();
      aResolve();
    });
  },
};

// A cleaner for clearing cookie banner handling exceptions.
const CookieBannerExceptionCleaner = {
  async deleteAll() {
    try {
      Services.cookieBanners.removeAllDomainPrefs(false);
    } catch (e) {
      // Don't throw an error if the cookie banner handling is disabled.
      if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
        throw e;
      }
    }
  },

  async deleteByPrincipal(aPrincipal) {
    try {
      Services.cookieBanners.removeDomainPref(aPrincipal.URI, false);
    } catch (e) {
      // Don't throw an error if the cookie banner handling is disabled.
      if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
        throw e;
      }
    }
  },

  async deleteByBaseDomain(aDomain) {
    try {
      Services.cookieBanners.removeDomainPref(
        Services.io.newURI("https://" + aDomain),
        false
      );
    } catch (e) {
      // Don't throw an error if the cookie banner handling is disabled.
      if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
        throw e;
      }
    }
  },

  async deleteByHost(aHost, aOriginAttributes) {
    try {
      let isPrivate =
        !!aOriginAttributes.privateBrowsingId &&
        aOriginAttributes.privateBrowsingId !==
          Services.scriptSecurityManager.DEFAULT_PRIVATE_BROWSING_ID;

      Services.cookieBanners.removeDomainPref(
        Services.io.newURI("https://" + aHost),
        isPrivate
      );
    } catch (e) {
      // Don't throw an error if the cookie banner handling is disabled.
      if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
        throw e;
      }
    }
  },
};

// A cleaner for cleaning cookie banner handling executed records.
const CookieBannerExecutedRecordCleaner = {
  async deleteAll() {
    try {
      Services.cookieBanners.removeAllExecutedRecords(false);
    } catch (e) {
      // Don't throw an error if the cookie banner handling is disabled.
      if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
        throw e;
      }
    }
  },

  async deleteByPrincipal(aPrincipal) {
    try {
      Services.cookieBanners.removeExecutedRecordForSite(
        aPrincipal.baseDomain,
        false
      );
    } catch (e) {
      // Don't throw an error if the cookie banner handling is disabled.
      if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
        throw e;
      }
    }
  },

  async deleteByBaseDomain(aDomain) {
    try {
      Services.cookieBanners.removeExecutedRecordForSite(aDomain, false);
    } catch (e) {
      // Don't throw an error if the cookie banner handling is disabled.
      if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
        throw e;
      }
    }
  },

  async deleteByHost(aHost, aOriginAttributes) {
    try {
      let isPrivate =
        !!aOriginAttributes.privateBrowsingId &&
        aOriginAttributes.privateBrowsingId !==
          Services.scriptSecurityManager.DEFAULT_PRIVATE_BROWSING_ID;

      Services.cookieBanners.removeExecutedRecordForSite(aHost, isPrivate);
    } catch (e) {
      // Don't throw error if the cookie banner handling is disabled.
      if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
        throw e;
      }
    }
  },
};

// A cleaner for cleaning fingerprinting protection states.
const FingerprintingProtectionStateCleaner = {
  async deleteAll() {
    Services.rfp.cleanAllRandomKeys();
  },

  async deleteByPrincipal(aPrincipal) {
    Services.rfp.cleanRandomKeyByPrincipal(aPrincipal);
  },

  async deleteByBaseDomain(aDomain) {
    Services.rfp.cleanRandomKeyByDomain(aDomain);
  },

  async deleteByHost(aHost, aOriginAttributesPattern) {
    Services.rfp.cleanRandomKeyByHost(
      aHost,
      JSON.stringify(aOriginAttributesPattern)
    );
  },

  async deleteByOriginAttributes(aOriginAttributesString) {
    Services.rfp.cleanRandomKeyByOriginAttributesPattern(
      aOriginAttributesString
    );
  },
};

const CertCleaner = {
  async deleteByHost(aHost, aOriginAttributes) {
    let overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(
      Ci.nsICertOverrideService
    );

    overrideService.clearValidityOverride(aHost, -1, aOriginAttributes);
  },

  deleteByPrincipal(aPrincipal) {
    return this.deleteByHost(aPrincipal.host, aPrincipal.originAttributes);
  },

  async deleteByBaseDomain(aBaseDomain) {
    let overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(
      Ci.nsICertOverrideService
    );
    overrideService
      .getOverrides()
      .filter(({ asciiHost }) =>
        hasBaseDomain({ host: asciiHost }, aBaseDomain)
      )
      .forEach(({ asciiHost, port }) =>
        overrideService.clearValidityOverride(asciiHost, port, {})
      );
  },

  async deleteAll() {
    let overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(
      Ci.nsICertOverrideService
    );

    overrideService.clearAllOverrides();
  },
};

const NetworkCacheCleaner = {
  async deleteByHost(aHost, aOriginAttributes) {
    // Delete data from both HTTP and HTTPS sites.
    let httpURI = Services.io.newURI("http://" + aHost);
    let httpsURI = Services.io.newURI("https://" + aHost);
    let httpPrincipal = Services.scriptSecurityManager.createContentPrincipal(
      httpURI,
      aOriginAttributes
    );
    let httpsPrincipal = Services.scriptSecurityManager.createContentPrincipal(
      httpsURI,
      aOriginAttributes
    );

    Services.cache2.clearOrigin(httpPrincipal);
    Services.cache2.clearOrigin(httpsPrincipal);
  },

  async deleteByBaseDomain(aBaseDomain) {
    Services.cache2.clearBaseDomain(aBaseDomain);
  },

  deleteByPrincipal(aPrincipal) {
    return new Promise(aResolve => {
      Services.cache2.clearOrigin(aPrincipal);
      aResolve();
    });
  },

  deleteByOriginAttributes(aOriginAttributesString) {
    return new Promise(aResolve => {
      Services.cache2.clearOriginAttributes(aOriginAttributesString);
      aResolve();
    });
  },

  deleteAll() {
    return new Promise(aResolve => {
      Services.cache2.clear();
      aResolve();
    });
  },
};

const CSSCacheCleaner = {
  async deleteByHost(aHost, aOriginAttributes) {
    // Delete data from both HTTP and HTTPS sites.
    let httpURI = Services.io.newURI("http://" + aHost);
    let httpsURI = Services.io.newURI("https://" + aHost);
    let httpPrincipal = Services.scriptSecurityManager.createContentPrincipal(
      httpURI,
      aOriginAttributes
    );
    let httpsPrincipal = Services.scriptSecurityManager.createContentPrincipal(
      httpsURI,
      aOriginAttributes
    );

    ChromeUtils.clearStyleSheetCacheByPrincipal(httpPrincipal);
    ChromeUtils.clearStyleSheetCacheByPrincipal(httpsPrincipal);
  },

  async deleteByPrincipal(aPrincipal) {
    ChromeUtils.clearStyleSheetCacheByPrincipal(aPrincipal);
  },

  async deleteByBaseDomain(aBaseDomain) {
    ChromeUtils.clearStyleSheetCacheByBaseDomain(aBaseDomain);
  },

  async deleteAll() {
    ChromeUtils.clearStyleSheetCache();
  },
};

const JSCacheCleaner = {
  async deleteByHost(aHost, aOriginAttributes) {
    // Delete data from both HTTP and HTTPS sites.
    let httpURI = Services.io.newURI("http://" + aHost);
    let httpsURI = Services.io.newURI("https://" + aHost);
    let httpPrincipal = Services.scriptSecurityManager.createContentPrincipal(
      httpURI,
      aOriginAttributes
    );
    let httpsPrincipal = Services.scriptSecurityManager.createContentPrincipal(
      httpsURI,
      aOriginAttributes
    );

    ChromeUtils.clearScriptCacheByPrincipal(httpPrincipal);
    ChromeUtils.clearScriptCacheByPrincipal(httpsPrincipal);
  },

  async deleteByPrincipal(aPrincipal) {
    ChromeUtils.clearScriptCacheByPrincipal(aPrincipal);
  },

  async deleteByBaseDomain(aBaseDomain) {
    ChromeUtils.clearScriptCacheByBaseDomain(aBaseDomain);
  },

  async deleteAll() {
    ChromeUtils.clearScriptCache();
  },
};

const ImageCacheCleaner = {
  async deleteByHost(aHost, aOriginAttributes) {
    let imageCache = Cc["@mozilla.org/image/tools;1"]
      .getService(Ci.imgITools)
      .getImgCacheForDocument(null);

    // Delete data from both HTTP and HTTPS sites.
    let httpURI = Services.io.newURI("http://" + aHost);
    let httpsURI = Services.io.newURI("https://" + aHost);
    let httpPrincipal = Services.scriptSecurityManager.createContentPrincipal(
      httpURI,
      aOriginAttributes
    );
    let httpsPrincipal = Services.scriptSecurityManager.createContentPrincipal(
      httpsURI,
      aOriginAttributes
    );

    imageCache.removeEntriesFromPrincipalInAllProcesses(httpPrincipal);
    imageCache.removeEntriesFromPrincipalInAllProcesses(httpsPrincipal);
  },

  async deleteByPrincipal(aPrincipal) {
    let imageCache = Cc["@mozilla.org/image/tools;1"]
      .getService(Ci.imgITools)
      .getImgCacheForDocument(null);
    imageCache.removeEntriesFromPrincipalInAllProcesses(aPrincipal);
  },

  async deleteByBaseDomain(aBaseDomain) {
    let imageCache = Cc["@mozilla.org/image/tools;1"]
      .getService(Ci.imgITools)
      .getImgCacheForDocument(null);
    imageCache.removeEntriesFromBaseDomainInAllProcesses(aBaseDomain);
  },

  deleteAll() {
    return new Promise(aResolve => {
      let imageCache = Cc["@mozilla.org/image/tools;1"]
        .getService(Ci.imgITools)
        .getImgCacheForDocument(null);
      imageCache.clearCache(false); // true=chrome, false=content
      aResolve();
    });
  },
};

const DownloadsCleaner = {
  async _deleteInternal({ hostOrBaseDomain, principal, originAttributes }) {
    originAttributes = originAttributes || principal?.originAttributes || {};

    let list = await lazy.Downloads.getList(lazy.Downloads.ALL);
    list.removeFinished(({ source }) => {
      if (
        "userContextId" in originAttributes &&
        "userContextId" in source &&
        originAttributes.userContextId != source.userContextId
      ) {
        return false;
      }
      if (
        "privateBrowsingId" in originAttributes &&
        !!originAttributes.privateBrowsingId != source.isPrivate
      ) {
        return false;
      }

      let entryURI = Services.io.newURI(source.url);
      if (hostOrBaseDomain) {
        return Services.eTLD.hasRootDomain(entryURI.host, hostOrBaseDomain);
      }
      if (principal) {
        return principal.equalsURI(entryURI);
      }
      return false;
    });
  },

  async deleteByHost(aHost, aOriginAttributes) {
    // Clearing by host also clears associated subdomains.
    return this._deleteInternal({
      hostOrBaseDomain: aHost,
      originAttributes: aOriginAttributes,
    });
  },

  deleteByPrincipal(aPrincipal) {
    return this._deleteInternal({ principal: aPrincipal });
  },

  async deleteByBaseDomain(aBaseDomain) {
    return this._deleteInternal({ hostOrBaseDomain: aBaseDomain });
  },

  deleteByRange(aFrom, aTo) {
    // Convert microseconds back to milliseconds for date comparisons.
    let rangeBeginMs = aFrom / 1000;
    let rangeEndMs = aTo / 1000;

    return lazy.Downloads.getList(lazy.Downloads.ALL).then(aList => {
      aList.removeFinished(
        aDownload =>
          aDownload.startTime >= rangeBeginMs &&
          aDownload.startTime <= rangeEndMs
      );
    });
  },

  deleteAll() {
    return lazy.Downloads.getList(lazy.Downloads.ALL).then(aList => {
      aList.removeFinished(null);
    });
  },
};

const PasswordsCleaner = {
  deleteByHost(aHost) {
    // Clearing by host also clears associated subdomains.
    return this._deleteInternal(aLogin =>
      Services.eTLD.hasRootDomain(aLogin.hostname, aHost)
    );
  },

  deleteByPrincipal(aPrincipal) {
    // Login origins don't contain any origin attributes.
    return this._deleteInternal(
      aLogin => aLogin.origin == aPrincipal.originNoSuffix
    );
  },

  deleteByBaseDomain(aBaseDomain) {
    return this._deleteInternal(aLogin =>
      Services.eTLD.hasRootDomain(aLogin.hostname, aBaseDomain)
    );
  },

  deleteAll() {
    return this._deleteInternal(() => true);
  },

  async _deleteInternal(aCb) {
    try {
      let logins = await Services.logins.getAllLogins();
      for (let login of logins) {
        if (aCb(login)) {
          Services.logins.removeLogin(login);
        }
      }
    } catch (ex) {
      // XXXehsan: is there a better way to do this rather than this
      // hacky comparison?
      if (
        !ex.message.includes("User canceled Master Password entry") &&
        ex.result != Cr.NS_ERROR_NOT_IMPLEMENTED
      ) {
        throw new Error("Exception occured in clearing passwords: " + ex);
      }
    }
  },
};

const MediaDevicesCleaner = {
  async deleteByRange(aFrom) {
    let mediaMgr = Cc["@mozilla.org/mediaManagerService;1"].getService(
      Ci.nsIMediaManagerService
    );
    mediaMgr.sanitizeDeviceIds(aFrom);
  },

  // TODO: We should call the MediaManager to clear by principal, rather than
  // over-clearing for user requests or bailing out for programmatic calls.
  async deleteByPrincipal(aPrincipal, aIsUserRequest) {
    if (!aIsUserRequest) {
      return;
    }
    await this.deleteAll();
  },

  // TODO: Same as above, but for base domain.
  async deleteByBaseDomain(aBaseDomain, aIsUserRequest) {
    if (!aIsUserRequest) {
      return;
    }
    await this.deleteAll();
  },

  async deleteAll() {
    let mediaMgr = Cc["@mozilla.org/mediaManagerService;1"].getService(
      Ci.nsIMediaManagerService
    );
    mediaMgr.sanitizeDeviceIds(null);
  },
};

const QuotaCleaner = {
  /**
   * Clear quota storage for matching principals.
   * @param {function} filterFn - Filter function which is passed a principal.
   * Return true to clear storage for given principal or false to skip it.
   * @returns {Promise} - Resolves once all matching items have been cleared.
   * Rejects on error.
   */
  async _qmsClearStoragesForPrincipalsMatching(filterFn) {
    // Clearing quota storage by first getting all entry origins and then
    // iterating over them is not ideal, since we can not ensure an entirely
    // consistent clearing state. Between fetching the origins and clearing
    // them, additional entries could be added. This means we could end up with
    // stray entries after the clearing operation. To fix this we would need to
    // move the clearing code to the QuotaManager itself which could either
    // prevent new writes while clearing or clean up any additional entries
    // which get written during the clearing operation.
    // Performance is also not ideal, since we iterate over storage multiple
    // times for this two step process.
    // See Bug 1719195.
    let origins = await new Promise((resolve, reject) => {
      Services.qms.listOrigins().callback = request => {
        if (request.resultCode != Cr.NS_OK) {
          reject({ message: "Deleting quota storages failed" });
          return;
        }
        resolve(request.result);
      };
    });

    let clearPromises = origins
      // Parse origins into principals.
      .map(Services.scriptSecurityManager.createContentPrincipalFromOrigin)
      // Filter out principals that don't match the filterFn.
      .filter(filterFn)
      // Clear quota storage by principal and collect the promises.
      .map(
        principal =>
          new Promise((resolve, reject) => {
            let clearRequest =
              Services.qms.clearStoragesForPrincipal(principal);
            clearRequest.callback = () => {
              if (clearRequest.resultCode != Cr.NS_OK) {
                reject({ message: "Deleting quota storages failed" });
                return;
              }
              resolve();
            };
          })
      );
    return Promise.all(clearPromises);
  },

  deleteByPrincipal(aPrincipal) {
    // localStorage: The legacy LocalStorage implementation that will
    // eventually be removed depends on this observer notification to clear by
    // principal.
    Services.obs.notifyObservers(
      null,
      "extension:purge-localStorage",
      aPrincipal.host
    );

    // Clear sessionStorage
    Services.sessionStorage.clearStoragesForOrigin(aPrincipal);

    // ServiceWorkers: they must be removed before cleaning QuotaManager.
    return lazy.ServiceWorkerCleanUp.removeFromPrincipal(aPrincipal)
      .then(
        _ => /* exceptionThrown = */ false,
        _ => /* exceptionThrown = */ true
      )
      .then(exceptionThrown => {
        // QuotaManager: In the event of a failure, we call reject to propagate
        // the error upwards.
        return new Promise((aResolve, aReject) => {
          let req = Services.qms.clearStoragesForPrincipal(aPrincipal);
          req.callback = () => {
            if (exceptionThrown || req.resultCode != Cr.NS_OK) {
              aReject({ message: "Delete by principal failed" });
            } else {
              aResolve();
            }
          };
        });
      });
  },

  async deleteByBaseDomain(aBaseDomain) {
    // localStorage: The legacy LocalStorage implementation that will
    // eventually be removed depends on this observer notification to clear by
    // host.  Some other subsystems like Reporting headers depend on this too.
    Services.obs.notifyObservers(
      null,
      "extension:purge-localStorage",
      aBaseDomain
    );

    // Clear sessionStorage
    Services.obs.notifyObservers(
      null,
      "browser:purge-sessionStorage",
      aBaseDomain
    );

    // Clear third-party storage partitioned under aBaseDomain.
    // This notification is forwarded via the StorageObserver and consumed only
    // by the SessionStorageManager and (legacy) LocalStorageManager.
    // There is a similar (legacy) notification "clear-origin-attributes-data"
    // which additionally clears data across various other storages unrelated to
    // the QuotaCleaner.
    Services.obs.notifyObservers(
      null,
      "dom-storage:clear-origin-attributes-data",
      JSON.stringify({ partitionKeyPattern: { baseDomain: aBaseDomain } })
    );

    // ServiceWorkers must be removed before cleaning QuotaManager. We store
    // potential errors so we can re-throw later, once all operations have
    // completed.
    let swCleanupError;
    try {
      await lazy.ServiceWorkerCleanUp.removeFromBaseDomain(aBaseDomain);
    } catch (error) {
      swCleanupError = error;
    }

    await this._qmsClearStoragesForPrincipalsMatching(principal =>
      hasBaseDomain({ principal }, aBaseDomain)
    );

    // Re-throw any service worker cleanup errors.
    if (swCleanupError) {
      throw swCleanupError;
    }
  },

  async deleteByHost(aHost) {
    // XXX: The aOriginAttributes is expected to always be empty({}). Maybe have
    // a debug assertion here to ensure that?

    // localStorage: The legacy LocalStorage implementation that will
    // eventually be removed depends on this observer notification to clear by
    // host.  Some other subsystems like Reporting headers depend on this too.
    Services.obs.notifyObservers(null, "extension:purge-localStorage", aHost);

    // Clear sessionStorage
    Services.obs.notifyObservers(null, "browser:purge-sessionStorage", aHost);

    // ServiceWorkers must be removed before cleaning QuotaManager. We store any
    // errors so we can re-throw later once all operations have completed.
    let swCleanupError;
    try {
      await lazy.ServiceWorkerCleanUp.removeFromHost(aHost);
    } catch (error) {
      swCleanupError = error;
    }

    await this._qmsClearStoragesForPrincipalsMatching(principal => {
      try {
        // deleteByHost has the semantics that "foo.example.com" should be
        // wiped if we are provided an aHost of "example.com".
        return Services.eTLD.hasRootDomain(principal.host, aHost);
      } catch (e) {
        // There is no host for the given principal.
        return false;
      }
    });

    // Re-throw any service worker cleanup errors.
    if (swCleanupError) {
      throw swCleanupError;
    }
  },

  deleteByRange(aFrom, aTo) {
    let principals = lazy.sas
      .getActiveOrigins(aFrom, aTo)
      .QueryInterface(Ci.nsIArray);

    let promises = [];
    for (let i = 0; i < principals.length; ++i) {
      let principal = principals.queryElementAt(i, Ci.nsIPrincipal);

      if (
        !principal.schemeIs("http") &&
        !principal.schemeIs("https") &&
        !principal.schemeIs("file")
      ) {
        continue;
      }

      promises.push(this.deleteByPrincipal(principal));
    }

    return Promise.all(promises);
  },

  deleteByOriginAttributes(aOriginAttributesString) {
    // The legacy LocalStorage implementation that will eventually be removed.
    // And it should've been cleared while notifying observers with
    // clear-origin-attributes-data.

    return lazy.ServiceWorkerCleanUp.removeFromOriginAttributes(
      aOriginAttributesString
    )
      .then(
        _ => /* exceptionThrown = */ false,
        _ => /* exceptionThrown = */ true
      )
      .then(() => {
        // QuotaManager: In the event of a failure, we call reject to propagate
        // the error upwards.
        return new Promise((aResolve, aReject) => {
          let req = Services.qms.clearStoragesForOriginAttributesPattern(
            aOriginAttributesString
          );
          req.callback = () => {
            if (req.resultCode == Cr.NS_OK) {
              aResolve();
            } else {
              aReject({ message: "Delete by origin attributes failed" });
            }
          };
        });
      });
  },

  async deleteAll() {
    // localStorage
    Services.obs.notifyObservers(null, "extension:purge-localStorage");

    // sessionStorage
    Services.obs.notifyObservers(null, "browser:purge-sessionStorage");

    // ServiceWorkers must be removed before cleaning QuotaManager. We store any
    // errors so we can re-throw later once all operations have completed.
    let swCleanupError;
    try {
      await lazy.ServiceWorkerCleanUp.removeAll();
    } catch (error) {
      swCleanupError = error;
    }

    await this._qmsClearStoragesForPrincipalsMatching(
      principal =>
        principal.schemeIs("http") ||
        principal.schemeIs("https") ||
        principal.schemeIs("file")
    );

    // Re-throw any service worker cleanup errors.
    if (swCleanupError) {
      throw swCleanupError;
    }
  },

  async cleanupAfterDeletionAtShutdown() {
    const toBeRemovedDir = PathUtils.join(
      PathUtils.profileDir,
      Services.prefs.getStringPref("dom.quotaManager.storageName"),
      "to-be-removed"
    );

    if (
      !AppConstants.MOZ_BACKGROUNDTASKS ||
      !Services.prefs.getBoolPref("dom.quotaManager.backgroundTask.enabled")
    ) {
      await IOUtils.remove(toBeRemovedDir, { recursive: true });
      return;
    }

    const runner = Cc["@mozilla.org/backgroundtasksrunner;1"].getService(
      Ci.nsIBackgroundTasksRunner
    );

    runner.removeDirectoryInDetachedProcess(
      toBeRemovedDir,
      "",
      "0",
      "*", // wildcard
      "Quota"
    );
  },
};

const PredictorNetworkCleaner = {
  async deleteAll() {
    // Predictive network data - like cache, no way to clear this per
    // domain, so just trash it all
    let np = Cc["@mozilla.org/network/predictor;1"].getService(
      Ci.nsINetworkPredictor
    );
    np.reset();
  },

  // TODO: We should call the NetworkPredictor to clear by principal, rather
  // than over-clearing for user requests or bailing out for programmatic calls.
  async deleteByPrincipal(aPrincipal, aIsUserRequest) {
    if (!aIsUserRequest) {
      return;
    }
    await this.deleteAll();
  },

  // TODO: Same as above, but for base domain.
  async deleteByBaseDomain(aBaseDomain, aIsUserRequest) {
    if (!aIsUserRequest) {
      return;
    }
    await this.deleteAll();
  },
};

const PushNotificationsCleaner = {
  /**
   * Clear entries for aDomain including subdomains of aDomain.
   * @param {string} aDomain - Domain to clear data for.
   * @returns {Promise} a promise which resolves once data has been cleared.
   */
  _deleteByRootDomain(aDomain) {
    if (!Services.prefs.getBoolPref("dom.push.enabled", false)) {
      return Promise.resolve();
    }

    return new Promise((aResolve, aReject) => {
      let push = Cc["@mozilla.org/push/Service;1"].getService(
        Ci.nsIPushService
      );
      // ClearForDomain also clears subdomains.
      push.clearForDomain(aDomain, aStatus => {
        if (!Components.isSuccessCode(aStatus)) {
          aReject();
        } else {
          aResolve();
        }
      });
    });
  },

  deleteByHost(aHost) {
    // Will also clear entries for subdomains of aHost. Data is cleared across
    // all origin attributes.
    return this._deleteByRootDomain(aHost);
  },

  deleteByPrincipal(aPrincipal) {
    // Will also clear entries for subdomains of the principal host. Data is
    // cleared across all origin attributes.
    return this._deleteByRootDomain(aPrincipal.host);
  },

  deleteByBaseDomain(aBaseDomain) {
    return this._deleteByRootDomain(aBaseDomain);
  },

  deleteAll() {
    if (!Services.prefs.getBoolPref("dom.push.enabled", false)) {
      return Promise.resolve();
    }

    return new Promise((aResolve, aReject) => {
      let push = Cc["@mozilla.org/push/Service;1"].getService(
        Ci.nsIPushService
      );
      push.clearForDomain("*", aStatus => {
        if (!Components.isSuccessCode(aStatus)) {
          aReject();
        } else {
          aResolve();
        }
      });
    });
  },
};

const StorageAccessCleaner = {
  // This is a special function to implement deleteUserInteractionForClearingHistory.
  async deleteExceptPrincipals(aPrincipalsWithStorage, aFrom) {
    // We compare by base domain in order to simulate the behavior
    // from purging, Consider a scenario where the user is logged
    // into sub.example.com but the cookies are on example.com. In this
    // case, we will remove the user interaction for sub.example.com
    // because its principal does not match the one with storage.
    let baseDomainsWithStorage = new Set();
    for (let principal of aPrincipalsWithStorage) {
      baseDomainsWithStorage.add(principal.baseDomain);
    }
    for (let perm of Services.perms.getAllByTypeSince(
      "storageAccessAPI",
      // The permission manager uses milliseconds instead of microseconds
      aFrom / 1000
    )) {
      if (!baseDomainsWithStorage.has(perm.principal.baseDomain)) {
        Services.perms.removePermission(perm);
      }
    }
  },

  async deleteByPrincipal(aPrincipal) {
    return Services.perms.removeFromPrincipal(aPrincipal, "storageAccessAPI");
  },

  _deleteInternal(filter) {
    Services.perms.all
      .filter(({ type }) => type == "storageAccessAPI")
      .filter(filter)
      .forEach(perm => {
        try {
          Services.perms.removePermission(perm);
        } catch (ex) {
          console.error(ex);
        }
      });
  },

  async deleteByHost(aHost) {
    // Clearing by host also clears associated subdomains.
    this._deleteInternal(({ principal }) => {
      let toBeRemoved = false;
      try {
        toBeRemoved = Services.eTLD.hasRootDomain(principal.host, aHost);
      } catch (ex) {}
      return toBeRemoved;
    });
  },

  async deleteByBaseDomain(aBaseDomain) {
    this._deleteInternal(
      ({ principal }) => principal.baseDomain == aBaseDomain
    );
  },

  async deleteByRange(aFrom) {
    Services.perms.removeByTypeSince("storageAccessAPI", aFrom / 1000);
  },

  async deleteAll() {
    Services.perms.removeByType("storageAccessAPI");
  },
};

const HistoryCleaner = {
  deleteByHost(aHost) {
    if (!AppConstants.MOZ_PLACES) {
      return Promise.resolve();
    }
    return lazy.PlacesUtils.history.removeByFilter({ host: "." + aHost });
  },

  deleteByPrincipal(aPrincipal) {
    if (!AppConstants.MOZ_PLACES) {
      return Promise.resolve();
    }
    return lazy.PlacesUtils.history.removeByFilter({ host: aPrincipal.host });
  },

  deleteByBaseDomain(aBaseDomain) {
    return this.deleteByHost(aBaseDomain, {});
  },

  deleteByRange(aFrom, aTo) {
    if (!AppConstants.MOZ_PLACES) {
      return Promise.resolve();
    }
    return lazy.PlacesUtils.history.removeVisitsByFilter({
      beginDate: new Date(aFrom / 1000),
      endDate: new Date(aTo / 1000),
    });
  },

  deleteAll() {
    if (!AppConstants.MOZ_PLACES) {
      return Promise.resolve();
    }
    return lazy.PlacesUtils.history.clear();
  },
};

const SessionHistoryCleaner = {
  async deleteByHost(aHost) {
    // Session storage and history also clear subdomains of aHost.
    Services.obs.notifyObservers(null, "browser:purge-sessionStorage", aHost);
    Services.obs.notifyObservers(
      null,
      "browser:purge-session-history-for-domain",
      aHost
    );
  },

  deleteByPrincipal(aPrincipal) {
    return this.deleteByHost(aPrincipal.host, aPrincipal.originAttributes);
  },

  deleteByBaseDomain(aBaseDomain) {
    return this.deleteByHost(aBaseDomain, {});
  },

  async deleteByRange(aFrom) {
    Services.obs.notifyObservers(
      null,
      "browser:purge-session-history",
      String(aFrom)
    );
  },

  async deleteAll() {
    Services.obs.notifyObservers(null, "browser:purge-session-history");
  },
};

const AuthTokensCleaner = {
  // TODO: Bug 1726742
  async deleteByPrincipal(aPrincipal, aIsUserRequest) {
    if (!aIsUserRequest) {
      return;
    }
    await this.deleteAll();
  },

  // TODO: Bug 1726742
  async deleteByBaseDomain(aBaseDomain, aIsUserRequest) {
    if (!aIsUserRequest) {
      return;
    }
    await this.deleteAll();
  },

  async deleteAll() {
    let sdr = Cc["@mozilla.org/security/sdr;1"].getService(
      Ci.nsISecretDecoderRing
    );
    sdr.logoutAndTeardown();
  },
};

const AuthCacheCleaner = {
  // TODO: Bug 1726743
  async deleteByPrincipal(aPrincipal, aIsUserRequest) {
    if (!aIsUserRequest) {
      return;
    }
    await this.deleteAll();
  },

  // TODO: Bug 1726743
  async deleteByBaseDomain(aBaseDomain, aIsUserRequest) {
    if (!aIsUserRequest) {
      return;
    }
    await this.deleteAll();
  },

  deleteAll() {
    return new Promise(aResolve => {
      Services.obs.notifyObservers(null, "net:clear-active-logins");
      aResolve();
    });
  },
};

// helper functions for Permission cleaners
const SHUTDOWN_EXCEPTION_PERMISSION = "cookie";

function deleteSingleInternalPerm(
  { baseDomain, host },
  perm,
  skipThirdPartyStoragePerms = false
) {
  let toBeRemoved;

  if (baseDomain) {
    toBeRemoved = perm.principal.baseDomain == baseDomain;
  } else {
    try {
      toBeRemoved = Services.eTLD.hasRootDomain(perm.principal.host, host);
    } catch (ex) {
      return;
    }
  }

  if (
    !skipThirdPartyStoragePerms &&
    !toBeRemoved &&
    (perm.type.startsWith("3rdPartyStorage^") ||
      perm.type.startsWith("3rdPartyFrameStorage^"))
  ) {
    let parts = perm.type.split("^");
    let uri;
    try {
      uri = Services.io.newURI(parts[1]);
    } catch (ex) {
      return;
    }

    toBeRemoved = Services.eTLD.hasRootDomain(uri.host, baseDomain || host);
  }

  if (!toBeRemoved) {
    return;
  }

  try {
    Services.perms.removePermission(perm);
  } catch (ex) {
    // Ignore entry
  }
}

const ShutdownExceptionsCleaner = {
  /**
   * Delete permissions by either base domain or host.
   * Clearing by host also clears associated subdomains.
   * For example, clearing "example.com" will also clear permissions for
   * "test.example.com" and "another.test.example.com".
   * @param options
   * @param {string} options.baseDomain - Base domain to delete permissions for.
   * @param {string} options.host - Host to delete permissions for.
   */
  async _deleteInternal({ baseDomain, host }) {
    for (let perm of Services.perms.all) {
      if (SHUTDOWN_EXCEPTION_PERMISSION != perm.type) {
        continue;
      }

      deleteSingleInternalPerm({ baseDomain, host }, perm, true);
    }
  },

  deleteByHost(aHost) {
    return this._deleteInternal({ host: aHost });
  },

  deleteByPrincipal(aPrincipal) {
    return this.deleteByHost(aPrincipal.host, aPrincipal.originAttributes);
  },

  deleteByBaseDomain(aBaseDomain) {
    return this._deleteInternal({ baseDomain: aBaseDomain });
  },

  async deleteByRange(aFrom) {
    Services.perms.removeByTypeSince(
      SHUTDOWN_EXCEPTION_PERMISSION,
      aFrom / 1000
    );
  },

  async deleteByOriginAttributes(aOriginAttributesString) {
    Services.perms.removePermissionsWithAttributes(
      aOriginAttributesString,
      [SHUTDOWN_EXCEPTION_PERMISSION],
      []
    );
  },

  async deleteAll() {
    Services.perms.removeByType(SHUTDOWN_EXCEPTION_PERMISSION);
  },
};

const PermissionsCleaner = {
  /**
   * Delete permissions by either base domain or host.
   * Clearing by host also clears associated subdomains.
   * For example, clearing "example.com" will also clear permissions for
   * "test.example.com" and "another.test.example.com".
   * @param options
   * @param {string} options.baseDomain - Base domain to delete permissions for.
   * @param {string} options.host - Host to delete permissions for.
   */
  async _deleteInternal({ baseDomain, host }) {
    for (let perm of Services.perms.all) {
      // skip shutdown exception permission because it is handled by ShutDownExceptionsCleaner
      if (SHUTDOWN_EXCEPTION_PERMISSION == perm.type) {
        continue;
      }

      deleteSingleInternalPerm({ baseDomain, host }, perm);
    }
  },

  deleteByHost(aHost) {
    return this._deleteInternal({ host: aHost });
  },

  deleteByPrincipal(aPrincipal) {
    return this.deleteByHost(aPrincipal.host, aPrincipal.originAttributes);
  },

  deleteByBaseDomain(aBaseDomain) {
    return this._deleteInternal({ baseDomain: aBaseDomain });
  },

  async deleteByRange(aFrom) {
    Services.perms.removeAllSinceWithTypeExceptions(aFrom / 1000, [
      SHUTDOWN_EXCEPTION_PERMISSION,
    ]);
  },

  async deleteByOriginAttributes(aOriginAttributesString) {
    Services.perms.removePermissionsWithAttributes(
      aOriginAttributesString,
      [],
      [SHUTDOWN_EXCEPTION_PERMISSION]
    );
  },

  async deleteAll() {
    Services.perms.removeAllExceptTypes([SHUTDOWN_EXCEPTION_PERMISSION]);
  },
};

const PreferencesCleaner = {
  deleteByHost(aHost) {
    // Also clears subdomains of aHost.
    return new Promise((aResolve, aReject) => {
      let cps2 = Cc["@mozilla.org/content-pref/service;1"].getService(
        Ci.nsIContentPrefService2
      );
      cps2.removeBySubdomain(aHost, null, {
        handleCompletion: aReason => {
          if (aReason === cps2.COMPLETE_ERROR) {
            aReject();
          } else {
            aResolve();
          }
        },
        handleError() {},
      });
    });
  },

  deleteByPrincipal(aPrincipal) {
    return this.deleteByHost(aPrincipal.host, aPrincipal.originAttributes);
  },

  deleteByBaseDomain(aBaseDomain) {
    return this.deleteByHost(aBaseDomain, {});
  },

  async deleteByRange(aFrom) {
    let cps2 = Cc["@mozilla.org/content-pref/service;1"].getService(
      Ci.nsIContentPrefService2
    );
    cps2.removeAllDomainsSince(aFrom / 1000, null);
  },

  async deleteAll() {
    let cps2 = Cc["@mozilla.org/content-pref/service;1"].getService(
      Ci.nsIContentPrefService2
    );
    cps2.removeAllDomains(null);
  },
};

const ClientAuthRememberCleaner = {
  async deleteByHost(aHost, aOriginAttributes) {
    let cars = Cc[
      "@mozilla.org/security/clientAuthRememberService;1"
    ].getService(Ci.nsIClientAuthRememberService);

    cars.deleteDecisionsByHost(aHost, aOriginAttributes);
  },

  deleteByPrincipal(aPrincipal) {
    return this.deleteByHost(aPrincipal.host, aPrincipal.originAttributes);
  },

  async deleteByBaseDomain(aDomain) {
    let cars = Cc[
      "@mozilla.org/security/clientAuthRememberService;1"
    ].getService(Ci.nsIClientAuthRememberService);

    cars
      .getDecisions()
      .filter(({ asciiHost, entryKey }) => {
        // Get the origin attributes which are in the third component of the
        // entryKey. ',' is used as the delimiter.
        let originSuffixEncoded = entryKey.split(",")[2];
        let originAttributes;

        if (originSuffixEncoded) {
          try {
            // Decoding the suffix or parsing the origin attributes can fail. In
            // this case we won't match the partitionKey, but we can still match
            // the asciiHost.
            let originSuffix = decodeURIComponent(originSuffixEncoded);
            originAttributes =
              ChromeUtils.CreateOriginAttributesFromOriginSuffix(originSuffix);
          } catch (e) {
            console.error(e);
          }
        }

        return hasBaseDomain(
          {
            host: asciiHost,
            originAttributes,
          },
          aDomain
        );
      })
      .forEach(({ entryKey }) => cars.forgetRememberedDecision(entryKey));
  },

  async deleteAll() {
    let cars = Cc[
      "@mozilla.org/security/clientAuthRememberService;1"
    ].getService(Ci.nsIClientAuthRememberService);
    cars.clearRememberedDecisions();
  },
};

const HSTSCleaner = {
  async deleteByHost(aHost, aOriginAttributes) {
    let sss = Cc["@mozilla.org/ssservice;1"].getService(
      Ci.nsISiteSecurityService
    );
    let uri = Services.io.newURI("https://" + aHost);
    sss.resetState(
      uri,
      aOriginAttributes,
      Ci.nsISiteSecurityService.RootDomain
    );
  },

  /**
   * Adds brackets to a site if it's an IPv6 address.
   * @param {string} aSite - (schemeless) site which may be an IPv6.
   * @returns {string} bracketed IPv6 or site if site is not an IPv6.
   */
  _maybeFixIpv6Site(aSite) {
    // Not an IPv6 or already has brackets.
    if (!aSite.includes(":") || aSite[0] == "[") {
      return aSite;
    }
    return `[${aSite}]`;
  },

  deleteByPrincipal(aPrincipal) {
    return this.deleteByHost(aPrincipal.host, aPrincipal.originAttributes);
  },

  async deleteByBaseDomain(aDomain) {
    let sss = Cc["@mozilla.org/ssservice;1"].getService(
      Ci.nsISiteSecurityService
    );

    // Add brackets to IPv6 sites to ensure URI creation succeeds.
    let uri = Services.io.newURI("https://" + this._maybeFixIpv6Site(aDomain));
    sss.resetState(uri, {}, Ci.nsISiteSecurityService.BaseDomain);
  },

  async deleteAll() {
    // Clear site security settings - no support for ranges in this
    // interface either, so we clearAll().
    let sss = Cc["@mozilla.org/ssservice;1"].getService(
      Ci.nsISiteSecurityService
    );
    sss.clearAll();
  },
};

const EMECleaner = {
  async deleteByHost(aHost, aOriginAttributes) {
    let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"].getService(
      Ci.mozIGeckoMediaPluginChromeService
    );
    mps.forgetThisSite(aHost, JSON.stringify(aOriginAttributes));
  },

  deleteByPrincipal(aPrincipal) {
    return this.deleteByHost(aPrincipal.host, aPrincipal.originAttributes);
  },

  async deleteByBaseDomain(aBaseDomain) {
    let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"].getService(
      Ci.mozIGeckoMediaPluginChromeService
    );
    mps.forgetThisBaseDomain(aBaseDomain);
  },

  deleteAll() {
    // Not implemented.
    return Promise.resolve();
  },
};

const ReportsCleaner = {
  deleteByHost(aHost) {
    // Also clears subdomains of aHost.
    return new Promise(aResolve => {
      Services.obs.notifyObservers(null, "reporting:purge-host", aHost);
      aResolve();
    });
  },

  deleteByPrincipal(aPrincipal) {
    return this.deleteByHost(aPrincipal.host, aPrincipal.originAttributes);
  },

  deleteByBaseDomain(aBaseDomain) {
    return this.deleteByHost(aBaseDomain, {});
  },

  deleteAll() {
    return new Promise(aResolve => {
      Services.obs.notifyObservers(null, "reporting:purge-all");
      aResolve();
    });
  },
};

const ContentBlockingCleaner = {
  deleteAll() {
    return lazy.TrackingDBService.clearAll();
  },

  async deleteByPrincipal(aPrincipal, aIsUserRequest) {
    if (!aIsUserRequest) {
      return;
    }
    await this.deleteAll();
  },

  async deleteByBaseDomain(aBaseDomain, aIsUserRequest) {
    if (!aIsUserRequest) {
      return;
    }
    await this.deleteAll();
  },

  deleteByRange(aFrom) {
    return lazy.TrackingDBService.clearSince(aFrom);
  },
};

/**
 * The about:home startup cache, if it exists, might contain information
 * about where the user has been, or what they've downloaded.
 */
const AboutHomeStartupCacheCleaner = {
  async deleteByPrincipal(aPrincipal, aIsUserRequest) {
    if (!aIsUserRequest) {
      return;
    }
    await this.deleteAll();
  },

  async deleteByBaseDomain(aBaseDomain, aIsUserRequest) {
    if (!aIsUserRequest) {
      return;
    }
    await this.deleteAll();
  },

  deleteAll() {
    // This cleaner only makes sense on Firefox desktop, which is the only
    // application that uses the about:home startup cache.
    if (!AppConstants.MOZ_BUILD_APP == "browser") {
      return Promise.resolve();
    }

    return new Promise((aResolve, aReject) => {
      let lci = Services.loadContextInfo.default;
      let storage = Services.cache2.diskCacheStorage(lci);
      let uri = Services.io.newURI("about:home");
      try {
        storage.asyncDoomURI(uri, "", {
          onCacheEntryDoomed(aResult) {
            if (
              Components.isSuccessCode(aResult) ||
              aResult == Cr.NS_ERROR_NOT_AVAILABLE
            ) {
              aResolve();
            } else {
              aReject({
                message: "asyncDoomURI for about:home failed",
              });
            }
          },
        });
      } catch (e) {
        aReject({
          message: "Failed to doom about:home startup cache entry",
        });
      }
    });
  },
};

const PreflightCacheCleaner = {
  // TODO: Bug 1727141: We should call the cache to clear by principal, rather
  // than over-clearing for user requests or bailing out for programmatic calls.
  async deleteByPrincipal(aPrincipal, aIsUserRequest) {
    if (!aIsUserRequest) {
      return;
    }
    await this.deleteAll();
  },

  // TODO: Bug 1727141 (see deleteByPrincipal).
  async deleteByBaseDomain(aBaseDomain, aIsUserRequest) {
    if (!aIsUserRequest) {
      return;
    }
    await this.deleteAll();
  },

  async deleteAll() {
    Cc[`@mozilla.org/network/protocol;1?name=http`]
      .getService(Ci.nsIHttpProtocolHandler)
      .clearCORSPreflightCache();
  },
};

const IdentityCredentialStorageCleaner = {
  async deleteAll() {
    if (
      Services.prefs.getBoolPref(
        "dom.security.credentialmanagement.identity.enabled",
        false
      )
    ) {
      lazy.IdentityCredentialStorageService.clear();
    }
  },

  async deleteByPrincipal(aPrincipal) {
    if (
      Services.prefs.getBoolPref(
        "dom.security.credentialmanagement.identity.enabled",
        false
      )
    ) {
      lazy.IdentityCredentialStorageService.deleteFromPrincipal(aPrincipal);
    }
  },

  async deleteByBaseDomain(aBaseDomain, aIsUserRequest) {
    if (!aIsUserRequest) {
      return;
    }
    if (
      Services.prefs.getBoolPref(
        "dom.security.credentialmanagement.identity.enabled",
        false
      )
    ) {
      lazy.IdentityCredentialStorageService.deleteFromBaseDomain(aBaseDomain);
    }
  },

  async deleteByRange(aFrom, aTo) {
    if (
      Services.prefs.getBoolPref(
        "dom.security.credentialmanagement.identity.enabled",
        false
      )
    ) {
      lazy.IdentityCredentialStorageService.deleteFromTimeRange(aFrom, aTo);
    }
  },

  async deleteByHost(aHost, aOriginAttributes) {
    if (
      Services.prefs.getBoolPref(
        "dom.security.credentialmanagement.identity.enabled",
        false
      )
    ) {
      // Delete data from both HTTP and HTTPS sites.
      let httpURI = Services.io.newURI("http://" + aHost);
      let httpsURI = Services.io.newURI("https://" + aHost);
      let httpPrincipal = Services.scriptSecurityManager.createContentPrincipal(
        httpURI,
        aOriginAttributes
      );
      let httpsPrincipal =
        Services.scriptSecurityManager.createContentPrincipal(
          httpsURI,
          aOriginAttributes
        );
      lazy.IdentityCredentialStorageService.deleteFromPrincipal(httpPrincipal);
      lazy.IdentityCredentialStorageService.deleteFromPrincipal(httpsPrincipal);
    }
  },

  async deleteByOriginAttributes(aOriginAttributesString) {
    if (
      Services.prefs.getBoolPref(
        "dom.security.credentialmanagement.identity.enabled",
        false
      )
    ) {
      lazy.IdentityCredentialStorageService.deleteFromOriginAttributesPattern(
        aOriginAttributesString
      );
    }
  },
};

const BounceTrackingProtectionStateCleaner = {
  async deleteAll() {
    if (
      lazy.bounceTrackingProtectionMode ==
      Ci.nsIBounceTrackingProtection.MODE_DISABLED
    ) {
      return;
    }
    await lazy.bounceTrackingProtection.clearAll();
  },

  async deleteByPrincipal(aPrincipal) {
    if (
      lazy.bounceTrackingProtectionMode ==
      Ci.nsIBounceTrackingProtection.MODE_DISABLED
    ) {
      return;
    }
    let { baseDomain, originAttributes } = aPrincipal;
    await lazy.bounceTrackingProtection.clearBySiteHostAndOA(
      baseDomain,
      originAttributes
    );
  },

  async deleteByBaseDomain(aBaseDomain) {
    if (
      lazy.bounceTrackingProtectionMode ==
      Ci.nsIBounceTrackingProtection.MODE_DISABLED
    ) {
      return;
    }
    await lazy.bounceTrackingProtection.clearBySiteHost(aBaseDomain);
  },

  async deleteByRange(aFrom, aTo) {
    if (
      lazy.bounceTrackingProtectionMode ==
      Ci.nsIBounceTrackingProtection.MODE_DISABLED
    ) {
      return;
    }
    await lazy.bounceTrackingProtection.clearByTimeRange(aFrom, aTo);
  },

  async deleteByHost(aHost) {
    if (
      lazy.bounceTrackingProtectionMode ==
      Ci.nsIBounceTrackingProtection.MODE_DISABLED
    ) {
      return;
    }
    let baseDomain = getBaseDomainWithFallback(aHost);
    await lazy.bounceTrackingProtection.clearBySiteHost(baseDomain);
  },

  async deleteByOriginAttributes(aOriginAttributesPatternString) {
    if (
      lazy.bounceTrackingProtectionMode ==
      Ci.nsIBounceTrackingProtection.MODE_DISABLED
    ) {
      return;
    }
    await lazy.bounceTrackingProtection.clearByOriginAttributesPattern(
      aOriginAttributesPatternString
    );
  },
};

const StoragePermissionsCleaner = {
  async deleteByRange(aFrom) {
    // We lack the ability to clear by range, but can clear from a certain time to now
    // Convert aFrom from microseconds to ms
    Services.perms.removeByTypeSince("storage-access", aFrom / 1000);

    let persistentStoragePermissions = Services.perms.getAllByTypeSince(
      "persistent-storage",
      aFrom / 1000
    );
    persistentStoragePermissions.forEach(perm => {
      // If it is an Addon Principal, do nothing.
      // We want their persistant-storage permissions to remain (Bug 1907732)
      if (this._isAddonPrincipal(perm.principal)) {
        return;
      }
      Services.perms.removePermission(perm);
    });
  },

  async deleteByPrincipal(aPrincipal) {
    Services.perms.removeFromPrincipal(aPrincipal, "storage-access");

    // Only remove persistent-storage if it is not an extension principal (Bug 1907732)
    if (!this._isAddonPrincipal(aPrincipal)) {
      Services.perms.removeFromPrincipal(aPrincipal, "persistent-storage");
    }
  },

  async deleteByHost(aHost) {
    let permissions = this._getStoragePermissions();
    for (let perm of permissions) {
      if (Services.eTLD.hasRootDomain(perm.principal.host, aHost)) {
        Services.perms.removePermission(perm);
      }
    }
  },

  async deleteByBaseDomain(aBaseDomain) {
    let permissions = this._getStoragePermissions();
    for (let perm of permissions) {
      if (perm.principal.baseDomain == aBaseDomain) {
        Services.perms.removePermission(perm);
      }
    }
  },

  async deleteByLocalFiles() {
    let permissions = this._getStoragePermissions();
    for (let perm of permissions) {
      if (perm.principal.schemeIs("file")) {
        Services.perms.removePermission(perm);
      }
    }
  },

  async deleteAll() {
    Services.perms.removeByType("storage-access");

    // We don't want to clear the persistent-storage permission from addons (Bug 1907732)
    let persistentStoragePermissions = Services.perms.getAllByTypes([
      "persistent-storage",
    ]);
    persistentStoragePermissions.forEach(perm => {
      if (this._isAddonPrincipal(perm.principal)) {
        return;
      }

      Services.perms.removePermission(perm);
    });
  },

  _getStoragePermissions() {
    let storagePermissions = Services.perms.getAllByTypes([
      "storage-access",
      "persistent-storage",
    ]);

    return storagePermissions.filter(
      permission =>
        !this._isAddonPrincipal(permission.principal) ||
        permission.type == "storage-access"
    );
  },

  _isAddonPrincipal(aPrincipal) {
    return (
      // AddonPolicy() returns a WebExtensionPolicy that has been registered before,
      // typically during extension startup. Since Disabled or uninstalled add-ons
      // don't appear there, we should use schemeIs instead
      aPrincipal.schemeIs("moz-extension")
    );
  },
};

// Here the map of Flags-Cleaners.
const FLAGS_MAP = [
  {
    flag: Ci.nsIClearDataService.CLEAR_CERT_EXCEPTIONS,
    cleaners: [CertCleaner],
  },

  { flag: Ci.nsIClearDataService.CLEAR_COOKIES, cleaners: [CookieCleaner] },

  {
    flag: Ci.nsIClearDataService.CLEAR_NETWORK_CACHE,
    cleaners: [NetworkCacheCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_IMAGE_CACHE,
    cleaners: [ImageCacheCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_CSS_CACHE,
    cleaners: [CSSCacheCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_JS_CACHE,
    cleaners: [JSCacheCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_CLIENT_AUTH_REMEMBER_SERVICE,
    cleaners: [ClientAuthRememberCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_DOWNLOADS,
    cleaners: [DownloadsCleaner, AboutHomeStartupCacheCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_PASSWORDS,
    cleaners: [PasswordsCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_MEDIA_DEVICES,
    cleaners: [MediaDevicesCleaner],
  },

  { flag: Ci.nsIClearDataService.CLEAR_DOM_QUOTA, cleaners: [QuotaCleaner] },

  {
    flag: Ci.nsIClearDataService.CLEAR_PREDICTOR_NETWORK_DATA,
    cleaners: [PredictorNetworkCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_DOM_PUSH_NOTIFICATIONS,
    cleaners: [PushNotificationsCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_HISTORY,
    cleaners: [HistoryCleaner, AboutHomeStartupCacheCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_SESSION_HISTORY,
    cleaners: [SessionHistoryCleaner, AboutHomeStartupCacheCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_AUTH_TOKENS,
    cleaners: [AuthTokensCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_AUTH_CACHE,
    cleaners: [AuthCacheCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_SITE_PERMISSIONS,
    cleaners: [PermissionsCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_CONTENT_PREFERENCES,
    cleaners: [PreferencesCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_HSTS,
    cleaners: [HSTSCleaner],
  },

  { flag: Ci.nsIClearDataService.CLEAR_EME, cleaners: [EMECleaner] },

  { flag: Ci.nsIClearDataService.CLEAR_REPORTS, cleaners: [ReportsCleaner] },

  {
    flag: Ci.nsIClearDataService.CLEAR_STORAGE_ACCESS,
    cleaners: [StorageAccessCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_CONTENT_BLOCKING_RECORDS,
    cleaners: [ContentBlockingCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_PREFLIGHT_CACHE,
    cleaners: [PreflightCacheCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_CREDENTIAL_MANAGER_STATE,
    cleaners: [IdentityCredentialStorageCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXCEPTION,
    cleaners: [CookieBannerExceptionCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD,
    cleaners: [CookieBannerExecutedRecordCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_FINGERPRINTING_PROTECTION_STATE,
    cleaners: [FingerprintingProtectionStateCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_BOUNCE_TRACKING_PROTECTION_STATE,
    cleaners: [BounceTrackingProtectionStateCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_STORAGE_PERMISSIONS,
    cleaners: [StoragePermissionsCleaner],
  },

  {
    flag: Ci.nsIClearDataService.CLEAR_SHUTDOWN_EXCEPTIONS,
    cleaners: [ShutdownExceptionsCleaner],
  },
];

export function ClearDataService() {
  this._initialize();
}

ClearDataService.prototype = Object.freeze({
  classID: Components.ID("{0c06583d-7dd8-4293-b1a5-912205f779aa}"),
  QueryInterface: ChromeUtils.generateQI(["nsIClearDataService"]),

  _initialize() {
    // Let's start all the service we need to cleanup data.

    // This is mainly needed for GeckoView that doesn't start QMS on startup
    // time.
    if (!Services.qms) {
      console.error("Failed initializiation of QuotaManagerService.");
    }
  },

  deleteDataFromLocalFiles(aIsUserRequest, aFlags, aCallback) {
    if (!aCallback) {
      return Cr.NS_ERROR_INVALID_ARG;
    }

    return this._deleteInternal(aFlags, aCallback, aCleaner => {
      // Some of the 'Cleaners' do not support clearing data for
      // local files. Ignore those.
      if (aCleaner.deleteByLocalFiles) {
        // A generic originAttributes dictionary.
        return aCleaner.deleteByLocalFiles({});
      }
      return Promise.resolve();
    });
  },

  deleteDataFromHost(aHost, aIsUserRequest, aFlags, aCallback) {
    if (!aHost || !aCallback) {
      return Cr.NS_ERROR_INVALID_ARG;
    }

    return this._deleteInternal(aFlags, aCallback, aCleaner => {
      // Some of the 'Cleaners' do not support to delete by principal. Let's
      // use deleteAll() as fallback.
      if (aCleaner.deleteByHost) {
        // A generic originAttributes dictionary.
        return aCleaner.deleteByHost(aHost, {});
      }
      // The user wants to delete data. Let's remove as much as we can.
      if (aIsUserRequest) {
        return aCleaner.deleteAll();
      }
      // We don't want to delete more than what is strictly required.
      return Promise.resolve();
    });
  },

  deleteDataFromBaseDomain(aDomainOrHost, aIsUserRequest, aFlags, aCallback) {
    if (!aDomainOrHost || !aCallback) {
      return Cr.NS_ERROR_INVALID_ARG;
    }
    // We may throw here if aDomainOrHost can't be converted to a base domain.
    let baseDomain;

    try {
      baseDomain = getBaseDomainWithFallback(aDomainOrHost);
    } catch (e) {
      return Cr.NS_ERROR_FAILURE;
    }

    return this._deleteInternal(aFlags, aCallback, aCleaner =>
      aCleaner.deleteByBaseDomain(baseDomain, aIsUserRequest)
    );
  },

  deleteDataFromPrincipal(aPrincipal, aIsUserRequest, aFlags, aCallback) {
    if (!aPrincipal || !aCallback) {
      return Cr.NS_ERROR_INVALID_ARG;
    }

    return this._deleteInternal(aFlags, aCallback, aCleaner =>
      aCleaner.deleteByPrincipal(aPrincipal, aIsUserRequest)
    );
  },

  deleteDataInTimeRange(aFrom, aTo, aIsUserRequest, aFlags, aCallback) {
    if (aFrom > aTo || !aCallback) {
      return Cr.NS_ERROR_INVALID_ARG;
    }

    return this._deleteInternal(aFlags, aCallback, aCleaner => {
      // Some of the 'Cleaners' do not support to delete by range. Let's use
      // deleteAll() as fallback.
      if (aCleaner.deleteByRange) {
        return aCleaner.deleteByRange(aFrom, aTo);
      }
      // The user wants to delete data. Let's remove as much as we can.
      if (aIsUserRequest) {
        return aCleaner.deleteAll();
      }
      // We don't want to delete more than what is strictly required.
      return Promise.resolve();
    });
  },

  deleteData(aFlags, aCallback) {
    if (!aCallback) {
      return Cr.NS_ERROR_INVALID_ARG;
    }

    return this._deleteInternal(aFlags, aCallback, aCleaner => {
      return aCleaner.deleteAll();
    });
  },

  deleteDataFromOriginAttributesPattern(aPattern, aCallback) {
    if (!aPattern) {
      return Cr.NS_ERROR_INVALID_ARG;
    }

    let patternString = JSON.stringify(aPattern);
    // XXXtt remove clear-origin-attributes-data entirely
    Services.obs.notifyObservers(
      null,
      "clear-origin-attributes-data",
      patternString
    );

    if (!aCallback) {
      aCallback = {
        onDataDeleted: () => {},
      };
    }
    return this._deleteInternal(
      Ci.nsIClearDataService.CLEAR_ALL,
      aCallback,
      aCleaner => {
        if (aCleaner.deleteByOriginAttributes) {
          return aCleaner.deleteByOriginAttributes(patternString);
        }

        // We don't want to delete more than what is strictly required.
        return Promise.resolve();
      }
    );
  },

  deleteUserInteractionForClearingHistory(
    aPrincipalsWithStorage,
    aFrom,
    aCallback
  ) {
    if (!aCallback) {
      return Cr.NS_ERROR_INVALID_ARG;
    }

    StorageAccessCleaner.deleteExceptPrincipals(aPrincipalsWithStorage, aFrom)
      .then(() => {
        aCallback.onDataDeleted(0);
      })
      .catch(() => {
        // This is part of clearing storageAccessAPI permissions, thus return
        // an appropriate error flag.
        aCallback.onDataDeleted(Ci.nsIClearDataService.CLEAR_PERMISSIONS);
      });
    return Cr.NS_OK;
  },

  cleanupAfterDeletionAtShutdown(aFlags, aCallback) {
    return this._deleteInternal(aFlags, aCallback, async aCleaner => {
      if (aCleaner.cleanupAfterDeletionAtShutdown) {
        await aCleaner.cleanupAfterDeletionAtShutdown();
      }
    });
  },

  // This internal method uses aFlags against FLAGS_MAP in order to retrieve a
  // list of 'Cleaners'. For each of them, the aHelper callback retrieves a
  // promise object. All these promise objects are resolved before calling
  // onDataDeleted.
  _deleteInternal(aFlags, aCallback, aHelper) {
    let resultFlags = 0;
    let promises = FLAGS_MAP.filter(c => aFlags & c.flag).map(c => {
      return Promise.all(
        c.cleaners.map(cleaner => {
          return aHelper(cleaner).catch(e => {
            console.error(e);
            resultFlags |= c.flag;
          });
        })
      );
      // Let's collect the failure in resultFlags.
    });
    Promise.all(promises).then(() => {
      aCallback.onDataDeleted(resultFlags);
    });
    return Cr.NS_OK;
  },
});
PK
!<U��F!C!Cmodules/ClientID.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Log } from "resource://gre/modules/Log.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "ClientID::";
// Must match ID in TelemetryUtils
const CANARY_CLIENT_ID = "c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0";
const CANARY_PROFILE_GROUP_ID = "decafdec-afde-cafd-ecaf-decafdecafde";

const DRS_STATE_VERSION = 2;

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  CommonUtils: "resource://services-common/utils.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "CryptoHash", () => {
  return Components.Constructor(
    "@mozilla.org/security/hash;1",
    "nsICryptoHash",
    "initWithString"
  );
});

ChromeUtils.defineLazyGetter(lazy, "gDatareportingPath", () => {
  return PathUtils.join(
    Services.dirsvc.get("ProfD", Ci.nsIFile).path,
    "datareporting"
  );
});

ChromeUtils.defineLazyGetter(lazy, "gStateFilePath", () => {
  return PathUtils.join(lazy.gDatareportingPath, "state.json");
});

const PREF_CACHED_CLIENTID = "toolkit.telemetry.cachedClientID";
const PREF_CACHED_PROFILEGROUPID = "toolkit.telemetry.cachedProfileGroupID";

/**
 * Checks if the string is a valid UUID (without braces).
 *
 * @param {String} id A string containing an ID.
 * @return {Boolean} True when the ID has valid format, or False otherwise.
 */
function isValidUUID(id) {
  const UUID_REGEX =
    /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
  return UUID_REGEX.test(id);
}

export var ClientID = Object.freeze({
  /**
   * This returns a promise resolving to the stable client ID we use for
   * data reporting.
   *
   * @return {Promise<string>} The stable client ID.
   */
  getClientID() {
    return ClientIDImpl.getClientID();
  },

  /**
   * This returns a promise resolving to the stable profile group ID we use for
   * data reporting.
   *
   * @return {Promise<string>} The stable profile group ID.
   */
  getProfileGroupID() {
    return ClientIDImpl.getProfileGroupID();
  },

  /**
   * Asynchronously updates the stable profile group ID we use for data
   * reporting.
   *
   * @param {String} id A string containing the profile group ID.
   * @return {Promise} Resolves when the ID is updated.
   */
  setProfileGroupID(id) {
    return ClientIDImpl.setProfileGroupID(id);
  },

  /**
   * Get the client id synchronously without hitting the disk.
   * This returns:
   *  - the current on-disk client id if it was already loaded
   *  - the client id that we cached into preferences (if any)
   *  - null otherwise
   */
  getCachedClientID() {
    return ClientIDImpl.getCachedClientID();
  },

  /**
   * Get the profile group ID synchronously without hitting the disk.
   * This returns:
   *  - the current on-disk profile group ID if it was already loaded
   *  - the profile group ID that we cached into preferences (if any)
   *  - null otherwise
   */
  getCachedProfileGroupID() {
    return ClientIDImpl.getCachedProfileGroupID();
  },

  async getClientIdHash() {
    return ClientIDImpl.getClientIdHash();
  },

  /**
   * Sets the client ID and profile group ID to the canary (known) identifiers,
   * writing it to disk and updating the cached version.
   *
   * Use `resetIdentifiers` to clear the existing identifiers and generate new,
   * random ones if required.
   *
   * @return {Promise<void>}
   */
  setCanaryIdentifiers() {
    return ClientIDImpl.setCanaryIdentifiers();
  },

  /**
   * Assigns new random values to client ID and profile group ID. Should only be
   * used if a reset is explicitly requested by the user.
   *
   * @return {Promise<void>}
   */
  resetIdentifiers() {
    return ClientIDImpl.resetIdentifiers();
  },

  /**
   * Only used for testing. Invalidates the cached client ID and profile group
   * ID so that they are read again from file, but doesn't remove the existing
   * IDs from disk.
   */
  _reset() {
    return ClientIDImpl._reset();
  },
});

var ClientIDImpl = {
  _clientID: null,
  _clientIDHash: null,
  _profileGroupID: null,
  _loadClientIdTask: null,
  _saveDataReportingStateTask: null,
  _resetIdentifiersTask: null,
  _logger: null,

  _loadDataReportingState() {
    if (this._loadClientIdTask) {
      return this._loadClientIdTask;
    }

    this._loadClientIdTask = this._doLoadDataReportingState();
    let clear = () => (this._loadClientIdTask = null);
    this._loadClientIdTask.then(clear, clear);
    return this._loadClientIdTask;
  },

  /**
   * Load the client ID and profile group ID from the DataReporting Service
   * state file. If either is missing, we generate a new one.
   */
  async _doLoadDataReportingState() {
    this._log.trace(`_doLoadDataReportingState`);
    // If there's a removal in progress, let's wait for it
    await this._resetIdentifiersTask;

    // Try to load the client id from the DRS state file.
    let hasCurrentClientID = false;
    let hasCurrentProfileGroupID = false;
    try {
      let state = await IOUtils.readJSON(lazy.gStateFilePath);
      if (state) {
        if (!("version" in state)) {
          // Old version, clear out any previously generated profile group ID.
          delete state.profileGroupID;
        }

        hasCurrentClientID = this.updateClientID(state.clientID);
        hasCurrentProfileGroupID = this.updateProfileGroupID(
          state.profileGroupID
        );

        if (!hasCurrentProfileGroupID && hasCurrentClientID) {
          // A pre-existing profile should be assigned the existing client ID.
          hasCurrentProfileGroupID = this.updateProfileGroupID(this._clientID);

          if (hasCurrentProfileGroupID) {
            this._saveDataReportingStateTask = this._saveDataReportingState();
            await this._saveDataReportingStateTask;
          }
        }

        if (hasCurrentClientID && hasCurrentProfileGroupID) {
          this._log.trace(
            `_doLoadDataReportingState: Client and Group IDs loaded from state.`
          );
          return {
            clientID: this._clientID,
            profileGroupID: this._profileGroupID,
          };
        }
      }
    } catch (e) {
      // fall through to next option
    }

    // Absent or broken state file? Check prefs as last resort.
    if (!hasCurrentClientID) {
      const cachedID = this.getCachedClientID();
      // Calling `updateClientID` with `null` logs an error, which breaks tests.
      if (cachedID) {
        hasCurrentClientID = this.updateClientID(cachedID);
      }
    }

    if (!hasCurrentProfileGroupID) {
      const cachedID = this.getCachedProfileGroupID();
      // Calling `updateProfileGroupID` with `null` logs an error, which breaks tests.
      if (cachedID) {
        hasCurrentProfileGroupID = this.updateProfileGroupID(cachedID);
      }
    }

    // We're missing the ID from the DRS state file and prefs.
    // Generate a new one.
    if (!hasCurrentClientID) {
      this.updateClientID(lazy.CommonUtils.generateUUID());
    }

    if (!hasCurrentProfileGroupID) {
      this.updateProfileGroupID(lazy.CommonUtils.generateUUID());
    }

    this._saveDataReportingStateTask = this._saveDataReportingState();

    // Wait on persisting the id. Otherwise failure to save the ID would result in
    // the client creating and subsequently sending multiple IDs to the server.
    // This would appear as multiple clients submitting similar data, which would
    // result in orphaning.
    await this._saveDataReportingStateTask;

    this._log.trace(
      "_doLoadDataReportingState: client and profile group IDs loaded and persisted."
    );
    return {
      clientID: this._clientID,
      profileGroupID: this._profileGroupID,
    };
  },

  /**
   * Save the client and profile group IDs to the DRS state file.
   *
   * @return {Promise} A promise resolved when the DRS state file is saved to disk.
   */
  async _saveDataReportingState() {
    try {
      this._log.trace(`_saveDataReportingState`);
      let obj = {
        version: DRS_STATE_VERSION,
        clientID: this._clientID,
        profileGroupID: this._profileGroupID,
      };
      await IOUtils.makeDirectory(lazy.gDatareportingPath);
      await IOUtils.writeJSON(lazy.gStateFilePath, obj, {
        tmpPath: `${lazy.gStateFilePath}.tmp`,
      });
      this._saveDataReportingStateTask = null;
    } catch (ex) {
      if (!DOMException.isInstance(ex) || ex.name !== "AbortError") {
        throw ex;
      }
    }
  },

  /**
   * This returns a promise resolving to the stable client ID we use for
   * data reporting (FHR & Telemetry).
   *
   * @return {Promise<string>} The stable client ID.
   */
  async getClientID() {
    if (!this._clientID) {
      let { clientID } = await this._loadDataReportingState();
      if (AppConstants.platform != "android") {
        Glean.legacyTelemetry.clientId.set(clientID);
      }
      return clientID;
    }

    return Promise.resolve(this._clientID);
  },

  /**
   * This returns a promise resolving to the stable profile group ID we use for
   * data reporting (FHR & Telemetry).
   *
   * @return {Promise<string>} The stable profile group ID.
   */
  async getProfileGroupID() {
    if (!this._profileGroupID) {
      let { profileGroupID } = await this._loadDataReportingState();
      if (AppConstants.platform != "android") {
        Glean.legacyTelemetry.profileGroupId.set(profileGroupID);
      }
      return profileGroupID;
    }

    return this._profileGroupID;
  },

  /**
   * Asynchronously updates the stable profile group ID we use for data reporting
   * (FHR & Telemetry).
   *
   * @param {String} id A string containing the profile group ID.
   * @return {Promise} Resolves when the ID is updated.
   */
  async setProfileGroupID(id) {
    // Make sure that we have loaded the client ID. Do this before updating the
    // profile group ID as loading would clobber that.
    if (!this._clientID) {
      await this._loadDataReportingState();
    }

    if (!(await this.updateProfileGroupID(id))) {
      this._log.error(
        "setProfileGroupID - invalid profile group ID passed, not updating"
      );
      throw new Error("Invalid profile group ID");
    }

    // If there is already a save in progress, wait for it to complete.
    await this._saveDataReportingStateTask;

    this._saveDataReportingStateTask = this._saveDataReportingState();
    await this._saveDataReportingStateTask;
  },

  /**
   * Get the client id synchronously without hitting the disk.
   * This returns:
   *  - the current on-disk client id if it was already loaded
   *  - the client id that we cached into preferences (if any)
   *  - null otherwise
   */
  getCachedClientID() {
    if (this._clientID) {
      // Already loaded the client id from disk.
      return this._clientID;
    }

    // If the client id cache contains a value of the wrong type,
    // reset the pref. We need to do this before |getStringPref| since
    // it will just return |null| in that case and we won't be able
    // to distinguish between the missing pref and wrong type cases.
    if (
      Services.prefs.prefHasUserValue(PREF_CACHED_CLIENTID) &&
      Services.prefs.getPrefType(PREF_CACHED_CLIENTID) !=
        Ci.nsIPrefBranch.PREF_STRING
    ) {
      this._log.error(
        "getCachedClientID - invalid client id type in preferences, resetting"
      );
      Services.prefs.clearUserPref(PREF_CACHED_CLIENTID);
    }

    // Not yet loaded, return the cached client id if we have one.
    let id = Services.prefs.getStringPref(PREF_CACHED_CLIENTID, null);
    if (id === null) {
      return null;
    }
    if (!isValidUUID(id)) {
      this._log.error(
        "getCachedClientID - invalid client id in preferences, resetting",
        id
      );
      Services.prefs.clearUserPref(PREF_CACHED_CLIENTID);
      return null;
    }
    return id;
  },

  /**
   * Get the profile group ID synchronously without hitting the disk.
   * This returns:
   *  - the current on-disk profile group ID if it was already loaded
   *  - the profile group ID that we cached into preferences (if any)
   *  - null otherwise
   */
  getCachedProfileGroupID() {
    if (this._profileGroupID) {
      // Already loaded the profile group ID from disk.
      return this._profileGroupID;
    }

    // If the profile group ID cache contains a value of the wrong type,
    // reset the pref. We need to do this before |getStringPref| since
    // it will just return |null| in that case and we won't be able
    // to distinguish between the missing pref and wrong type cases.
    if (
      Services.prefs.prefHasUserValue(PREF_CACHED_PROFILEGROUPID) &&
      Services.prefs.getPrefType(PREF_CACHED_PROFILEGROUPID) !=
        Ci.nsIPrefBranch.PREF_STRING
    ) {
      this._log.error(
        "getCachedProfileGroupID - invalid profile group ID type in preferences, resetting"
      );
      Services.prefs.clearUserPref(PREF_CACHED_PROFILEGROUPID);
    }

    // Not yet loaded, return the cached profile group ID if we have one.
    let id = Services.prefs.getStringPref(PREF_CACHED_PROFILEGROUPID, null);
    if (id === null) {
      return null;
    }
    if (!isValidUUID(id)) {
      this._log.error(
        "getCachedProfileGroupID - invalid profile group ID in preferences, resetting",
        id
      );
      Services.prefs.clearUserPref(PREF_CACHED_PROFILEGROUPID);
      return null;
    }
    return id;
  },

  async getClientIdHash() {
    if (!this._clientIDHash) {
      let byteArr = new TextEncoder().encode(await this.getClientID());
      let hash = new lazy.CryptoHash("sha256");
      hash.update(byteArr, byteArr.length);
      this._clientIDHash = lazy.CommonUtils.bytesAsHex(hash.finish(false));
    }
    return this._clientIDHash;
  },

  /**
   * Resets the module. This is for testing only.
   */
  async _reset() {
    await this._loadClientIdTask;
    await this._saveDataReportingStateTask;
    this._clientID = null;
    this._clientIDHash = null;
    this._profileGroupID = null;
  },

  async setCanaryIdentifiers() {
    this._log.trace("setCanaryIdentifiers");
    this.updateClientID(CANARY_CLIENT_ID);
    this.updateProfileGroupID(CANARY_PROFILE_GROUP_ID);

    this._saveDataReportingStateTask = this._saveDataReportingState();
    await this._saveDataReportingStateTask;
    return this._clientID;
  },

  async _doResetIdentifiers() {
    this._log.trace("_doResetIdentifiers");

    // Reset the cached client ID.
    this.updateClientID(lazy.CommonUtils.generateUUID());
    this._clientIDHash = null;

    // Reset the cached profile group ID.
    this.updateProfileGroupID(lazy.CommonUtils.generateUUID());

    // If there is a save in progress, wait for it to complete.
    await this._saveDataReportingStateTask;

    // Save the new identifiers to disk.
    this._saveDataReportingStateTask = this._saveDataReportingState();
    await this._saveDataReportingStateTask;
  },

  async resetIdentifiers() {
    this._log.trace("resetIdentifiers");

    // Wait for the removal.
    // Asynchronous calls to getClientID will also be blocked on this.
    this._resetIdentifiersTask = this._doResetIdentifiers();
    let clear = () => (this._resetIdentifiersTask = null);
    this._resetIdentifiersTask.then(clear, clear);

    await this._resetIdentifiersTask;
  },

  /**
   * Sets the client id to the given value and updates the value cached in
   * preferences only if the given id is a valid UUID.
   *
   * @param {String} id A string containing the client ID.
   * @return {Boolean} True when the client ID has valid format, or False
   * otherwise.
   */
  updateClientID(id) {
    if (!isValidUUID(id)) {
      this._log.error("updateClientID - invalid client ID", id);
      return false;
    }

    this._clientID = id;
    if (AppConstants.platform != "android") {
      Glean.legacyTelemetry.clientId.set(id);
    }

    this._clientIDHash = null;
    Services.prefs.setStringPref(PREF_CACHED_CLIENTID, this._clientID);
    return true;
  },

  /**
   * Sets the profile group ID to the given value and updates the value cached
   * in preferences only if the given id is a valid UUID.
   *
   * @param {String} id A string containing the profile group ID.
   * @return {Boolean} True when the profile group ID has valid format, or False
   * otherwise.
   */
  updateProfileGroupID(id) {
    if (!isValidUUID(id)) {
      this._log.error("updateProfileGroupID - invalid profile group ID", id);
      return false;
    }

    this._profileGroupID = id;
    if (AppConstants.platform != "android") {
      Glean.legacyTelemetry.profileGroupId.set(id);
    }

    Services.prefs.setStringPref(
      PREF_CACHED_PROFILEGROUPID,
      this._profileGroupID
    );
    return true;
  },

  /**
   * A helper for getting access to telemetry logger.
   */
  get _log() {
    if (!this._logger) {
      this._logger = Log.repository.getLoggerWithMessagePrefix(
        LOGGER_NAME,
        LOGGER_PREFIX
      );
    }

    return this._logger;
  },
};
PK
!<������$modules/ClipboardContextMenu.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  PromptUtils: "resource://gre/modules/PromptUtils.sys.mjs",
});

export var ClipboardContextMenu = {
  MENU_POPUP_ID: "clipboardReadPasteMenuPopup",

  // EventListener interface.
  handleEvent(aEvent) {
    switch (aEvent.type) {
      case "command": {
        this.onCommand();
        break;
      }
      case "popuphiding": {
        this.onPopupHiding();
        break;
      }
      case "keydown": {
        this.onKeyDown(aEvent);
        break;
      }
    }
  },

  _pasteMenuItemClicked: false,

  onCommand() {
    // onPopupHiding is responsible for returning result by calling onComplete
    // function.
    this._pasteMenuItemClicked = true;
  },

  onPopupHiding() {
    // Remove the listeners before potentially sending the async message
    // below, because that might throw.
    this._removeMenupopupEventListeners();
    this._clearDelayTimer();
    this._stopWatchingForSpammyActivation();

    this._menupopup = null;
    this._menuitem = null;

    let propBag = lazy.PromptUtils.objectToPropBag({
      ok: this._pasteMenuItemClicked,
    });
    this._pendingRequest.resolve(propBag);

    // A result has already been responded to. Reset the state to properly
    // handle further click or dismiss events.
    this._pasteMenuItemClicked = false;
    this._pendingRequest = null;
  },

  _lastBeepTime: 0,

  onKeyDown(aEvent) {
    if (!this._menuitem.disabled) {
      return;
    }

    let accesskey = this._menuitem.getAttribute("accesskey");
    if (
      aEvent.key == accesskey.toLowerCase() ||
      aEvent.key == accesskey.toUpperCase()
    ) {
      if (Date.now() - this._lastBeepTime > 1000) {
        Cc["@mozilla.org/sound;1"].getService(Ci.nsISound).beep();
        this._lastBeepTime = Date.now();
      }
      this._refreshDelayTimer();
    }
  },

  _menupopup: null,
  _menuitem: null,
  _pendingRequest: null,

  confirmUserPaste(aWindowContext) {
    return new Promise((resolve, reject) => {
      if (!aWindowContext) {
        reject(
          Components.Exception("Null window context.", Cr.NS_ERROR_INVALID_ARG)
        );
        return;
      }

      let { document } = aWindowContext.browsingContext.topChromeWindow;
      if (!document) {
        reject(
          Components.Exception(
            "Unable to get chrome document.",
            Cr.NS_ERROR_FAILURE
          )
        );
        return;
      }

      if (this._pendingRequest) {
        reject(
          Components.Exception(
            "There is an ongoing request.",
            Cr.NS_ERROR_FAILURE
          )
        );
        return;
      }

      this._pendingRequest = { resolve, reject };
      this._menupopup = this._getMenupopup(document);
      this._menuitem = this._menupopup.firstElementChild;
      this._addMenupopupEventListeners();

      let mouseXInCSSPixels = {};
      let mouseYInCSSPixels = {};
      document.ownerGlobal.windowUtils.getLastOverWindowPointerLocationInCSSPixels(
        mouseXInCSSPixels,
        mouseYInCSSPixels
      );

      this._menuitem.disabled = true;
      this._startWatchingForSpammyActivation();
      // `openPopup` is a no-op if the popup is already opened.
      // That property is used when `navigator.clipboard.readText()` or
      // `navigator.clipboard.read()`is called from two different frames, e.g.
      // an iframe and the top level frame. In that scenario, the two frames
      // correspond to different `navigator.clipboard` instances. When
      // `readText()` or `read()` is called from both frames, an actor pair is
      // instantiated for each of them. Both actor parents will call `openPopup`
      // on the same `_menupopup` object. If the popup is already open,
      // `openPopup` is a no-op. When the popup is clicked or dismissed both
      // actor parents will receive the corresponding event.
      this._menupopup.openPopup(null, {
        isContextMenu: true,
        position: "overlap",
        x: mouseXInCSSPixels.value,
        y: mouseYInCSSPixels.value,
      });

      this._refreshDelayTimer(document);
    });
  },

  _addMenupopupEventListeners() {
    this._menupopup.addEventListener("command", this);
    this._menupopup.addEventListener("popuphiding", this);
  },

  _removeMenupopupEventListeners() {
    this._menupopup.removeEventListener("command", this);
    this._menupopup.removeEventListener("popuphiding", this);
  },

  _createMenupopup(aChromeDoc) {
    let menuitem = aChromeDoc.createXULElement("menuitem");
    menuitem.id = "clipboardReadPasteMenuItem";
    aChromeDoc.l10n.setAttributes(menuitem, "text-action-paste");

    let menupopup = aChromeDoc.createXULElement("menupopup");
    menupopup.id = this.MENU_POPUP_ID;
    menupopup.appendChild(menuitem);
    return menupopup;
  },

  _getMenupopup(aChromeDoc) {
    let menupopup = aChromeDoc.getElementById(this.MENU_POPUP_ID);
    if (menupopup == null) {
      menupopup = this._createMenupopup(aChromeDoc);
      const parent =
        aChromeDoc.querySelector("popupset") || aChromeDoc.documentElement;
      parent.appendChild(menupopup);
    }

    return menupopup;
  },

  _startWatchingForSpammyActivation() {
    let doc = this._menuitem.ownerDocument;
    doc.addEventListener("keydown", this, {
      capture: true,
      mozSystemGroup: true,
    });
  },

  _stopWatchingForSpammyActivation() {
    let doc = this._menuitem.ownerDocument;
    doc.removeEventListener("keydown", this, {
      capture: true,
      mozSystemGroup: true,
    });
  },

  _delayTimer: null,

  _clearDelayTimer() {
    if (this._delayTimer) {
      let window = this._menuitem.ownerGlobal;
      window.clearTimeout(this._delayTimer);
      this._delayTimer = null;
    }
  },

  _refreshDelayTimer() {
    this._clearDelayTimer();

    let window = this._menuitem.ownerGlobal;
    let delay = Services.prefs.getIntPref("security.dialog_enable_delay");
    this._delayTimer = window.setTimeout(() => {
      this._menuitem.disabled = false;
      this._stopWatchingForSpammyActivation();
      this._delayTimer = null;
    }, delay);
  },
};
PK
!<�o`5rrmodules/Color.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * A list of minimum contrast ratio's per WCAG 2.0 conformance level.
 * Please refer to section 1.4.3 of the WCAG 2.0 spec at http://www.w3.org/TR/WCAG20/.
 * Simply put:
 * A = Large text only.
 * AA = Regular sized text or large text in enhanced contrast mode.
 * AAA = Regular sized text in enhanced contrast mode.
 *
 * @type {Object}
 */
const CONTRAST_RATIO_LEVELS = {
  A: 3,
  AA: 4.5,
  AAA: 7,
};

/**
 * For text legibility on any background color, you need to determine which text
 * color - black or white - will yield the highest contrast ratio.
 * Since you're always comparing `contrastRatio(bgcolor, black) >
 * contrastRatio(bgcolor, white) ? <use black> : <use white>`, we can greatly
 * simplify the calculation to the following constant.
 *
 * @type {Number}
 */
const CONTRAST_BRIGHTTEXT_THRESHOLD = Math.sqrt(1.05 * 0.05) - 0.05;

/**
 * Color class, which describes a color.
 * In the future, this object may be extended to allow for conversions between
 * different color formats and notations, support transparency.
 *
 * @param {Number} r Red color component
 * @param {Number} g Green color component
 * @param {Number} b Blue color component
 */
export class Color {
  constructor(r, g, b) {
    this.r = r;
    this.g = g;
    this.b = b;
  }

  /**
   * Formula from W3C's WCAG 2.0 spec's relative luminance, section 1.4.1,
   * http://www.w3.org/TR/WCAG20/.
   *
   * @return {Number} Relative luminance, represented as number between 0 and 1.
   */
  get relativeLuminance() {
    let colorArr = [this.r, this.g, this.b].map(color => {
      color = parseInt(color, 10);
      if (color <= 10) {
        return color / 255 / 12.92;
      }
      return Math.pow((color / 255 + 0.055) / 1.055, 2.4);
    });
    return colorArr[0] * 0.2126 + colorArr[1] * 0.7152 + colorArr[2] * 0.0722;
  }

  /**
   * @return {Boolean} TRUE if you need to use a bright color (e.g. 'white'), when
   *                   this color is set as the background.
   */
  get useBrightText() {
    return this.relativeLuminance <= CONTRAST_BRIGHTTEXT_THRESHOLD;
  }

  /**
   * Get the contrast ratio between the current color and a second other color.
   * A common use case is to express the difference between a foreground and a
   * background color in numbers.
   * Formula from W3C's WCAG 2.0 spec's contrast ratio, section 1.4.1,
   * http://www.w3.org/TR/WCAG20/.
   *
   * @param  {Color}  otherColor Color instance to calculate the contrast with
   * @return {Number} Contrast ratios can range from 1 to 21, commonly written
   *                  as 1:1 to 21:1.
   */
  contrastRatio(otherColor) {
    if (!(otherColor instanceof Color)) {
      throw new TypeError("The first argument should be an instance of Color");
    }

    let luminance = this.relativeLuminance;
    let otherLuminance = otherColor.relativeLuminance;
    return (
      (Math.max(luminance, otherLuminance) + 0.05) /
      (Math.min(luminance, otherLuminance) + 0.05)
    );
  }

  /**
   * Method to check if the contrast ratio between two colors is high enough to
   * be discernable.
   *
   * @param  {Color}  otherColor Color instance to calculate the contrast with
   * @param  {String} [level]    WCAG conformance level that maps to the minimum
   *                             required contrast ratio. Defaults to 'AA'
   * @return {Boolean}
   */
  isContrastRatioAcceptable(otherColor, level = "AA") {
    return this.contrastRatio(otherColor) > CONTRAST_RATIO_LEVELS[level];
  }
}
PK
!<��ѭ,�,modules/CommonDialog.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  EnableDelayHelper: "resource://gre/modules/PromptUtils.sys.mjs",
});

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

export class CommonDialog {
  constructor(args, ui) {
    this.args = args;
    this.ui = ui;
    this.initialFocusPromise = new Promise(resolve => {
      this.initialFocusResolver = resolve;
    });
  }

  static DEFAULT_APP_ICON_CSS = `image-set(url("chrome://branding/content/icon16.png") 1x,
    url("chrome://branding/content/icon32.png") 2x,
    url("chrome://branding/content/icon64.png") 4x)`;

  args = null;
  ui = null;

  hasInputField = true;
  numButtons = undefined;
  iconClass = undefined;
  soundID = undefined;
  focusTimer = null;
  initialFocusPromise = null;
  initialFocusResolver = null;

  /**
   * @param [commonDialogEl] - Dialog element from commonDialog.xhtml.
   */
  async onLoad(commonDialogEl) {
    let isEmbedded = !!commonDialogEl.ownerGlobal.docShell.chromeEventHandler;

    switch (this.args.promptType) {
      case "alert":
      case "alertCheck":
        this.hasInputField = false;
        this.numButtons = 1;
        this.iconClass = ["alert-icon"];
        this.soundID = Ci.nsISound.EVENT_ALERT_DIALOG_OPEN;
        break;
      case "confirmCheck":
      case "confirm":
        this.hasInputField = false;
        this.numButtons = 2;
        this.iconClass = ["question-icon"];
        this.soundID = Ci.nsISound.EVENT_CONFIRM_DIALOG_OPEN;
        break;
      case "confirmEx":
        var numButtons = 0;
        if (this.args.button0Label) {
          numButtons++;
        }
        if (this.args.button1Label) {
          numButtons++;
        }
        if (this.args.button2Label) {
          numButtons++;
        }
        if (this.args.button3Label) {
          numButtons++;
        }
        if (numButtons == 0) {
          throw new Error("A dialog with no buttons? Can not haz.");
        }
        this.numButtons = numButtons;
        this.hasInputField = false;
        this.iconClass = ["question-icon"];
        this.soundID = Ci.nsISound.EVENT_CONFIRM_DIALOG_OPEN;
        break;
      case "prompt":
        this.numButtons = 2;
        this.iconClass = ["question-icon"];
        this.soundID = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
        this.initTextbox("login", this.args.value);
        // Clear the label, since this isn't really a username prompt.
        this.ui.loginLabel.setAttribute("value", "");
        // Ensure the labeling for the prompt is correct.
        this.ui.loginTextbox.setAttribute("aria-labelledby", "infoBody");
        break;
      case "promptUserAndPass":
        this.numButtons = 2;
        this.iconClass = ["authentication-icon", "question-icon"];
        this.soundID = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
        this.initTextbox("login", this.args.user);
        this.initTextbox("password1", this.args.pass);
        break;
      case "promptPassword":
        this.numButtons = 2;
        this.iconClass = ["authentication-icon", "question-icon"];
        this.soundID = Ci.nsISound.EVENT_PROMPT_DIALOG_OPEN;
        this.initTextbox("password1", this.args.pass);
        // Clear the label, since the message presumably indicates its purpose.
        this.ui.password1Label.setAttribute("value", "");
        break;
      default:
        console.error(
          "commonDialog opened for unknown type: ",
          this.args.promptType
        );
        throw new Error("unknown dialog type");
    }

    commonDialogEl.setAttribute("windowtype", "prompt:" + this.args.promptType);

    // set the document title
    let title = this.args.title;
    let infoTitle = this.ui.infoTitle;
    infoTitle.appendChild(infoTitle.ownerDocument.createTextNode(title));

    // After making these preventative checks, we can determine to show it if we're on
    // macOS (where there is no titlebar) or if the prompt is a common dialog document
    // and has been embedded (has a chromeEventHandler).
    infoTitle.hidden = !(AppConstants.platform === "macosx" || isEmbedded);

    commonDialogEl.ownerDocument.title = title;

    // Set button labels and visibility
    //
    // This assumes that button0 defaults to a visible "ok" button, and
    // button1 defaults to a visible "cancel" button. The other 2 buttons
    // have no default labels (and are hidden).
    switch (this.numButtons) {
      case 4:
        this.setLabelForNode(this.ui.button3, this.args.button3Label);
        this.ui.button3.hidden = false;
      // fall through
      case 3:
        this.setLabelForNode(this.ui.button2, this.args.button2Label);
        this.ui.button2.hidden = false;
      // fall through
      case 2:
        // Defaults to a visible "cancel" button
        if (this.args.button1Label) {
          this.setLabelForNode(this.ui.button1, this.args.button1Label);
        }
        break;

      case 1:
        this.ui.button1.hidden = true;
        break;
    }
    // Defaults to a visible "ok" button
    if (this.args.button0Label) {
      this.setLabelForNode(this.ui.button0, this.args.button0Label);
    }

    // display the main text
    let croppedMessage = "";
    if (this.args.text) {
      // Bug 317334 - crop string length as a workaround.
      croppedMessage = this.args.text.substr(0, 10000);
      // TabModalPrompts don't have an infoRow to hide / not hide here, so
      // guard on that here so long as they are in use.
      if (this.ui.infoRow) {
        this.ui.infoRow.hidden = false;
      }
    }
    let infoBody = this.ui.infoBody;
    infoBody.appendChild(infoBody.ownerDocument.createTextNode(croppedMessage));

    let label = this.args.checkLabel;
    if (label) {
      // Only show the checkbox if label has a value.
      this.ui.checkboxContainer.hidden = false;
      this.ui.checkboxContainer.clientTop; // style flush to assure binding is attached
      this.setLabelForNode(this.ui.checkbox, label);
      this.ui.checkbox.checked = this.args.checked;
    }

    // set the icon
    let icon = this.ui.infoIcon;
    if (icon) {
      this.iconClass.forEach(el => icon.classList.add(el));
    }

    // set default result to cancelled
    this.args.ok = false;
    this.args.buttonNumClicked = 1;

    // Set the default button
    let b = this.args.defaultButtonNum || 0;
    commonDialogEl.defaultButton = ["accept", "cancel", "extra1", "extra2"][b];

    if (!isEmbedded && !this.ui.promptContainer?.hidden) {
      // Set default focus and select textbox contents if applicable. If we're
      // embedded SubDialogManager will call setDefaultFocus for us.
      this.setDefaultFocus(true);
    }

    if (this.args.enableDelay) {
      this.delayHelper = new lazy.EnableDelayHelper({
        disableDialog: () => this.setButtonsEnabledState(false),
        enableDialog: () => this.setButtonsEnabledState(true),
        focusTarget: this.ui.focusTarget,
      });
    }

    // Play a sound (unless we're showing a content prompt -- don't want those
    //               to feel like OS prompts).
    try {
      if (this.soundID && !this.args.openedWithTabDialog) {
        Cc["@mozilla.org/sound;1"]
          .getService(Ci.nsISound)
          .playEventSound(this.soundID);
      }
    } catch (e) {
      console.error("Couldn't play common dialog event sound: ", e);
    }

    if (isEmbedded) {
      // If we delayed default focus above, wait for it to be ready before
      // sending the notification.
      await this.initialFocusPromise;
    }
    Services.obs.notifyObservers(this.ui.prompt, "common-dialog-loaded");
  }

  setLabelForNode(aNode, aLabel) {
    // This is for labels which may contain embedded access keys.
    // If we end in (&X) where X represents the access key, optionally preceded
    // by spaces and/or followed by the ':' character, store the access key and
    // remove the access key placeholder + leading spaces from the label.
    // Otherwise a character preceded by one but not two &s is the access key.
    // Store it and remove the &.

    // Note that if you change the following code, see the comment of
    // nsTextBoxFrame::UpdateAccessTitle.
    var accessKey = null;
    if (/ *\(\&([^&])\)(:?)$/.test(aLabel)) {
      aLabel = RegExp.leftContext + RegExp.$2;
      accessKey = RegExp.$1;
    } else if (/^([^&]*)\&(([^&]).*$)/.test(aLabel)) {
      aLabel = RegExp.$1 + RegExp.$2;
      accessKey = RegExp.$3;
    }

    // && is the magic sequence to embed an & in your label.
    aLabel = aLabel.replace(/\&\&/g, "&");
    aNode.label = aLabel;

    // XXXjag bug 325251
    // Need to set this after aNode.setAttribute("value", aLabel);
    if (accessKey) {
      aNode.accessKey = accessKey;
    }
  }

  initTextbox(aName, aValue) {
    this.ui[aName + "Container"].hidden = false;
    this.ui[aName + "Textbox"].setAttribute(
      "value",
      aValue !== null ? aValue : ""
    );
  }

  setButtonsEnabledState(enabled) {
    this.ui.button0.disabled = !enabled;
    // button1 (cancel) remains enabled.
    this.ui.button2.disabled = !enabled;
    this.ui.button3.disabled = !enabled;
  }

  setDefaultFocus(isInitialLoad) {
    let b = this.args.defaultButtonNum || 0;
    let button = this.ui["button" + b];

    if (!this.hasInputField) {
      let isOSX = "nsILocalFileMac" in Ci;
      // If the infoRow exists and is is hidden, then the infoBody is also hidden,
      // which means it can't be focused. At that point, we fall back to focusing
      // the default button, regardless of platform.
      if (isOSX && !(this.ui.infoRow && this.ui.infoRow.hidden)) {
        this.ui.infoBody.focus();
      } else {
        button.focus({ focusVisible: false });
      }
    } else if (this.args.promptType == "promptPassword") {
      // When the prompt is initialized, focus and select the textbox
      // contents. Afterwards, only focus the textbox.
      if (isInitialLoad) {
        this.ui.password1Textbox.select();
      } else {
        this.ui.password1Textbox.focus();
      }
    } else if (isInitialLoad) {
      this.ui.loginTextbox.select();
    } else {
      this.ui.loginTextbox.focus();
    }

    if (isInitialLoad) {
      this.initialFocusResolver();
    }
  }

  onCheckbox() {
    this.args.checked = this.ui.checkbox.checked;
  }

  onButton0() {
    this.args.promptActive = false;
    this.args.ok = true;
    this.args.buttonNumClicked = 0;

    let username = this.ui.loginTextbox.value;
    let password = this.ui.password1Textbox.value;

    // Return textfield values
    switch (this.args.promptType) {
      case "prompt":
        this.args.value = username;
        break;
      case "promptUserAndPass":
        this.args.user = username;
        this.args.pass = password;
        break;
      case "promptPassword":
        this.args.pass = password;
        break;
    }
  }

  onButton1() {
    this.args.promptActive = false;
    this.args.buttonNumClicked = 1;
  }

  onButton2() {
    this.args.promptActive = false;
    this.args.buttonNumClicked = 2;
  }

  onButton3() {
    this.args.promptActive = false;
    this.args.buttonNumClicked = 3;
  }

  abortPrompt() {
    this.args.promptActive = false;
    this.args.promptAborted = true;
  }
}
PK
!<6?rBBmodules/ComponentUtils.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * vim: sw=2 ts=2 sts=2 et filetype=javascript
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Deprecated utilities for JavaScript components loaded by the JS component
 * loader.
 */

const nsIFactoryQI = ChromeUtils.generateQI(["nsIFactory"]);

export var ComponentUtils = {
  /**
   * DEPRECATED!
   *
   * Generates a singleton nsIFactory implementation that can be used as
   * an argument to nsIComponentRegistrar.registerFactory.
   *
   * @param {Function} aServiceConstructor
   *        Constructor function of the component.
   */
  generateSingletonFactory(aServiceConstructor) {
    return {
      _instance: null,
      createInstance(aIID) {
        if (this._instance === null) {
          this._instance = new aServiceConstructor();
        }
        return this._instance.QueryInterface(aIID);
      },
      QueryInterface: nsIFactoryQI,
    };
  },
};
PK
!<�4���modules/ConduitsChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This @file implements the child side of Conduits, an abstraction over
 * Fission IPC for extension API subject.  See {@link ConduitsParent.sys.mjs}
 * for more details about the overall design.
 *
 * @typedef {object} MessageData
 * @property {ConduitID} [target]
 * @property {ConduitID} [sender]
 * @property {boolean} query
 * @property {object} arg
 *
 * @typedef {import("ConduitsParent.sys.mjs").ConduitAddress} ConduitAddress
 * @typedef {import("ConduitsParent.sys.mjs").ConduitID} ConduitID
 */

/**
 * Base class for both child (Point) and parent (Broadcast) side of conduits,
 * handles setting up send/receive method stubs.
 */
export class BaseConduit {
  /**
   * @param {object} subject
   * @param {ConduitAddress} address
   */
  constructor(subject, address) {
    this.subject = subject;
    this.address = address;
    this.id = address.id;

    for (let name of address.send || []) {
      this[`send${name}`] = this._send.bind(this, name, false);
    }
    for (let name of address.query || []) {
      this[`query${name}`] = this._send.bind(this, name, true);
    }

    this.recv = new Map();
    for (let name of address.recv || []) {
      let method = this.subject[`recv${name}`];
      if (!method) {
        throw new Error(`recv${name} not found for conduit ${this.id}`);
      }
      this.recv.set(name, method.bind(this.subject));
    }
  }

  /**
   * Internal, uses the actor to send the message/query.
   *
   * @param {string} method
   * @param {boolean} query Flag indicating a response is expected.
   * @param {JSWindowActorChild|JSWindowActorParent} actor
   * @param {MessageData} data
   * @returns {Promise?}
   */
  _doSend(method, query, actor, data) {
    if (query) {
      return actor.sendQuery(method, data);
    }
    actor.sendAsyncMessage(method, data);
  }

  /**
   * Internal @abstract, used by sendX stubs.
   *
   * @param {string} _name
   * @param {boolean} _query
   */
  _send(_name, _query, ..._args) {
    throw new Error(`_send not implemented for ${this.constructor.name}`);
  }

  /**
   * Internal, calls the specific recvX method based on the message.
   *
   * @param {string} name Message/method name.
   * @param {object} arg  Message data, the one and only method argument.
   * @param {object} meta Metadata about the method call.
   */
  async _recv(name, arg, meta) {
    let method = this.recv.get(name);
    if (!method) {
      throw new Error(`recv${name} not found for conduit ${this.id}`);
    }
    try {
      return await method(arg, meta);
    } catch (e) {
      if (meta.query) {
        return Promise.reject(e);
      }
      Cu.reportError(e);
    }
  }
}

/**
 * Child side conduit, can only send/receive point-to-point messages via the
 * one specific ConduitsChild actor.
 */
export class PointConduit extends BaseConduit {
  /** @type {ConduitGen} */
  constructor(subject, address, actor) {
    super(subject, address);
    this.actor = actor;
    this.actor.sendAsyncMessage("ConduitOpened", { arg: address });
  }

  /**
   * Internal, sends messages via the actor, used by sendX stubs.
   *
   * @param {string} method
   * @param {boolean} query
   * @param {object?} arg
   * @returns {Promise?}
   */
  _send(method, query, arg = {}) {
    if (!this.actor) {
      throw new Error(`send${method} on closed conduit ${this.id}`);
    }
    let sender = this.id;
    return super._doSend(method, query, this.actor, { arg, query, sender });
  }

  /**
   * Closes the conduit from further IPC, notifies the parent side by default.
   *
   * @param {boolean} silent
   */
  close(silent = false) {
    let { actor } = this;
    if (actor) {
      this.actor = null;
      actor.conduits.delete(this.id);
      if (!silent) {
        // Catch any exceptions that can occur if the conduit is closed while
        // the actor is being destroyed due to the containing browser being closed.
        // This should be treated as if the silent flag was passed.
        try {
          actor.sendAsyncMessage("ConduitClosed", { sender: this.id });
        } catch (ex) {}
      }
    }
    this.closeCallback?.();
    this.closeCallback = null;
  }

  /**
   * Set the callback to be called when the conduit is closed.
   *
   * @param {Function} callback
   */
  setCloseCallback(callback) {
    this.closeCallback = callback;
  }
}

/**
 * Implements the child side of the Conduits actor, manages conduit lifetimes.
 */
export class ConduitsChild extends JSWindowActorChild {
  constructor() {
    super();
    this.conduits = new Map();
  }

  /**
   * Public entry point a child-side subject uses to open a conduit.
   *
   * @type {ConduitGen}
   */
  openConduit(subject, address) {
    let conduit = new PointConduit(subject, address, this);
    this.conduits.set(conduit.id, conduit);
    return conduit;
  }

  /**
   * JSWindowActor method, routes the message to the target subject.
   *
   * @param {object} options
   * @param {string} options.name
   * @param {MessageData | MessageData[]} options.data
   * @returns {Promise?}
   */
  receiveMessage({ name, data }) {
    // Batch of webRequest events, run each and return results, ignoring errors.
    if (Array.isArray(data)) {
      let run = data => this.receiveMessage({ name, data });
      return Promise.all(data.map(data => run(data).catch(Cu.reportError)));
    }

    let { target, arg, query, sender } = data;
    let conduit = this.conduits.get(target);
    if (!conduit) {
      throw new Error(`${name} for closed conduit ${target}: ${uneval(arg)}`);
    }
    return conduit._recv(name, arg, { sender, query, actor: this });
  }

  /**
   * JSWindowActor method, ensure cleanup.
   */
  didDestroy() {
    for (let conduit of this.conduits.values()) {
      conduit.close(true);
    }
    this.conduits.clear();
  }
}

/**
 * Child side of the Conduits process actor.  Same code as JSWindowActor.
 */
export class ProcessConduitsChild extends JSProcessActorChild {
  constructor() {
    super();
    this.conduits = new Map();
  }

  openConduit = ConduitsChild.prototype.openConduit;
  receiveMessage = ConduitsChild.prototype.receiveMessage;
  didDestroy = ConduitsChild.prototype.didDestroy;
}
PK
!<-Cm��:�:modules/ConduitsParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This @file implements the parent side of Conduits, an abstraction over
 * Fission IPC for extension Contexts, API managers, Ports/Messengers, and
 * other types of "subjects" participating in implementation of extension APIs.
 *
 * Additionally, knowledge about conduits from all child processes is gathered
 * here, and used together with the full CanonicalBrowsingContext tree to route
 * IPC messages and queries directly to the right subjects.
 *
 * Each Conduit is tied to one subject, attached to a ConduitAddress descriptor,
 * and exposes an API for sending/receiving via an actor, or multiple actors in
 * case of the parent process.
 *
 * @typedef {number|string} ConduitID
 *
 * @typedef {object} ConduitAddress
 * @property {ConduitID} [id] Globally unique across all processes.
 * @property {string[]} [recv]
 * @property {string[]} [send]
 * @property {string[]} [query]
 * @property {string[]} [cast]
 *
 * @property {*} [actor]
 * @property {boolean} [verified]
 * @property {string} [url]
 * @property {number} [frameId]
 * @property {string} [workerScriptURL]
 * @property {number} [workerDescriptorId]
 * @property {string} [extensionId]
 * @property {string} [envType]
 * @property {string} [instanceId]
 * @property {number} [portId]
 * @property {boolean} [native]
 * @property {boolean} [source]
 * @property {string} [reportOnClosed]
 *
 * Lists of recvX, sendX, queryX and castX methods this subject will use.
 *
 * @typedef {"messenger"|"port"|"tab"} BroadcastKind
 * Kinds of broadcast targeting filters.
 *
 * @example
 * ```js
 * {
 *    init(actor) {
 *      this.conduit = actor.openConduit(this, {
 *        id: this.id,
 *        recv: ["recvAddNumber"],
 *        send: ["sendNumberUpdate"],
 *      });
 *    },
 *
 *    recvAddNumber({ num }, { actor, sender }) {
 *      num += 1;
 *      this.conduit.sendNumberUpdate(sender.id, { num });
 *    }
 * }
 * ```
 */

import { BaseConduit } from "resource://gre/modules/ConduitsChild.sys.mjs";
import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";
import { WebNavigationFrames } from "resource://gre/modules/WebNavigationFrames.sys.mjs";

const { DefaultWeakMap, ExtensionError } = ExtensionUtils;

const BATCH_TIMEOUT_MS = 250;
const ADDON_ENV = new Set(["addon_child", "devtools_child"]);

/**
 * Internal, keeps track of all parent and remote (child) conduits.
 */
const Hub = {
  /** @type {Map<ConduitID, ConduitAddress>} Info about all child conduits. */
  remotes: new Map(),

  /** @type {Map<ConduitID, BroadcastConduit>} All open parent conduits. */
  conduits: new Map(),

  /** @type {Map<string, BroadcastConduit>} Parent conduits by recvMethod. */
  byMethod: new Map(),

  /** @type {WeakMap<ConduitsParent, Set<ConduitAddress>>} Conduits by actor. */
  byActor: new DefaultWeakMap(() => new Set()),

  /** @type {Map<string, BroadcastConduit>} */
  reportOnClosed: new Map(),

  /**
   * Save info about a new parent conduit, register it as a global listener.
   *
   * @param {BroadcastConduit} conduit
   */
  openConduit(conduit) {
    this.conduits.set(conduit.id, conduit);
    for (let name of conduit.address.recv || []) {
      if (this.byMethod.get(name)) {
        // For now, we only allow one parent conduit handling each recv method.
        throw new Error(`Duplicate BroadcastConduit method name recv${name}`);
      }
      this.byMethod.set(name, conduit);
    }
  },

  /**
   * Cleanup.
   *
   * @param {BroadcastConduit} conduit
   */
  closeConduit({ id, address }) {
    this.conduits.delete(id);
    for (let name of address.recv || []) {
      this.byMethod.delete(name);
    }
  },

  /**
   * Confirm that a remote conduit comes from an extension background
   * service worker.
   *
   * @see ExtensionPolicyService::CheckParentFrames
   * @param {ConduitAddress} remote
   * @returns {boolean}
   */
  verifyWorkerEnv({ actor, extensionId, workerScriptURL }) {
    const addonPolicy = WebExtensionPolicy.getByID(extensionId);
    if (!addonPolicy) {
      throw new Error(`No WebExtensionPolicy found for ${extensionId}`);
    }
    if (actor.manager.remoteType !== addonPolicy.extension.remoteType) {
      throw new Error(
        `Bad ${extensionId} process: ${actor.manager.remoteType}`
      );
    }
    if (!addonPolicy.isManifestBackgroundWorker(workerScriptURL)) {
      throw new Error(
        `Bad ${extensionId} background service worker script url: ${workerScriptURL}`
      );
    }
    return true;
  },

  /**
   * Confirm that a remote conduit comes from an extension page or
   * an extension background service worker.
   *
   * @see ExtensionPolicyService::CheckParentFrames
   * @param {ConduitAddress} remote
   * @returns {boolean}
   */
  verifyEnv({ actor, envType, extensionId, ...rest }) {
    if (!extensionId || !ADDON_ENV.has(envType)) {
      return false;
    }

    // ProcessConduit related to a background service worker context.
    if (actor.manager && actor.manager instanceof Ci.nsIDOMProcessParent) {
      return this.verifyWorkerEnv({ actor, envType, extensionId, ...rest });
    }

    let windowGlobal = actor.manager;

    while (windowGlobal) {
      let { browsingContext: bc, documentPrincipal: prin } = windowGlobal;

      if (prin.addonId !== extensionId) {
        throw new Error(`Bad ${extensionId} principal: ${prin.URI.spec}`);
      }
      if (bc.currentRemoteType !== prin.addonPolicy.extension.remoteType) {
        throw new Error(`Bad ${extensionId} process: ${bc.currentRemoteType}`);
      }

      if (!bc.parent) {
        return true;
      }
      windowGlobal = bc.embedderWindowGlobal;
    }
    throw new Error(`Missing WindowGlobalParent for ${extensionId}`);
  },

  /**
   * Fill in common address fields knowable from the parent process.
   *
   * @param {ConduitAddress} address
   * @param {ConduitsParent} actor
   */
  fillInAddress(address, actor) {
    address.actor = actor;
    address.verified = this.verifyEnv(address);
    if (JSWindowActorParent.isInstance(actor)) {
      address.frameId = WebNavigationFrames.getFrameId(actor.browsingContext);
      address.url = actor.manager.documentURI?.spec;
    } else {
      // Background service worker contexts do not have an associated frame
      // and there is no browsingContext to retrieve the expected url from.
      //
      // WorkerContextChild sent in the address part of the ConduitOpened request
      // the worker script URL as address.workerScriptURL, and so we can use that
      // as the address.url too.
      address.frameId = -1;
      address.url = address.workerScriptURL;
    }
  },

  /**
   * Save info about a new remote conduit.
   *
   * @param {ConduitAddress} address
   * @param {ConduitsParent} actor
   */
  recvConduitOpened(address, actor) {
    this.fillInAddress(address, actor);
    this.remotes.set(address.id, address);
    this.byActor.get(actor).add(address);
  },

  /**
   * Notifies listeners and cleans up after the remote conduit is closed.
   *
   * @param {ConduitAddress} remote
   */
  recvConduitClosed(remote) {
    this.remotes.delete(remote.id);
    this.byActor.get(remote.actor).delete(remote);

    remote.actor = null;
    for (let [key, conduit] of Hub.reportOnClosed.entries()) {
      if (remote[key]) {
        conduit.subject.recvConduitClosed(remote);
      }
    }
  },

  /**
   * Close all remote conduits when the actor goes away.
   *
   * @param {ConduitsParent} actor
   */
  actorClosed(actor) {
    for (let remote of this.byActor.get(actor)) {
      // When a Port is closed, we notify the other side, but it might share
      // an actor, so we shouldn't sendQeury() in that case (see bug 1623976).
      this.remotes.delete(remote.id);
    }
    for (let remote of this.byActor.get(actor)) {
      this.recvConduitClosed(remote);
    }
    this.byActor.delete(actor);
  },
};

/**
 * Parent side conduit, registers as a global listeners for certain messages,
 * and can target specific child conduits when sending.
 */
export class BroadcastConduit extends BaseConduit {
  /**
   * @param {object} subject
   * @param {ConduitAddress} address
   */
  constructor(subject, address) {
    super(subject, address);

    // Create conduit.castX() bidings.
    for (let name of address.cast || []) {
      this[`cast${name}`] = this._cast.bind(this, name);
    }

    // Wants to know when conduits with a specific attribute are closed.
    // `subject.recvConduitClosed(address)` method will be called.
    if (address.reportOnClosed) {
      Hub.reportOnClosed.set(address.reportOnClosed, this);
    }

    this.open = true;
    Hub.openConduit(this);
  }

  /**
   * Internal, sends a message to a specific conduit, used by sendX stubs.
   *
   * @param {string} method
   * @param {boolean} query
   * @param {ConduitID} target
   * @param {object?} arg
   * @returns {Promise<any>}
   */
  _send(method, query, target, arg = {}) {
    if (!this.open) {
      throw new Error(`send${method} on closed conduit ${this.id}`);
    }

    let sender = this.id;
    let { actor } = Hub.remotes.get(target);

    if (method === "RunListener" && arg.path.startsWith("webRequest.")) {
      return actor.batch(method, { target, arg, query, sender });
    }
    return super._doSend(method, query, actor, { target, arg, query, sender });
  }

  /**
   * Broadcasts a method call to all conduits of kind that satisfy filtering by
   * kind-specific properties from arg, returns an array of response promises.
   *
   * @param {string} method
   * @param {BroadcastKind} kind
   * @param {object} arg
   * @returns {Promise<any[]> | Promise<Response>}
   */
  _cast(method, kind, arg) {
    let filters = {
      // Target Ports by portId and side (connect caller/onConnect receiver).
      port: remote =>
        remote.portId === arg.portId &&
        (arg.source == null || remote.source === arg.source),

      // Target Messengers in extension pages by extensionId and envType.
      messenger: r =>
        r.verified &&
        r.id !== arg.sender.contextId &&
        r.extensionId === arg.extensionId &&
        r.recv.includes(method) &&
        // TODO: Bug 1453343 - get rid of this:
        (r.envType === "addon_child" || arg.sender.envType !== "content_child"),

      // Target Messengers by extensionId, tabId (topBC) and frameId.
      tab: remote =>
        remote.extensionId === arg.extensionId &&
        remote.actor.manager.browsingContext?.top.id === arg.topBC &&
        (arg.frameId == null || remote.frameId === arg.frameId) &&
        remote.recv.includes(method),

      // Target Messengers by extensionId.
      extension: remote => remote.instanceId === arg.instanceId,
    };

    let targets = Array.from(Hub.remotes.values()).filter(filters[kind]);
    let promises = targets.map(c => this._send(method, true, c.id, arg));

    return arg.firstResponse
      ? this._raceResponses(promises)
      : Promise.allSettled(promises);
  }

  /**
   * Custom Promise.race() function that ignores certain resolutions and errors.
   *
   * @typedef {{response?: any, received?: boolean}} Response
   *
   * @param {Promise<Response>[]} promises
   * @returns {Promise<Response?>}
   */
  _raceResponses(promises) {
    return new Promise((resolve, reject) => {
      let result;
      promises.map(p =>
        p
          .then(value => {
            if (value.response) {
              // We have an explicit response, resolve immediately.
              resolve(value);
            } else if (value.received) {
              // Message was received, but no response.
              // Resolve with this only if there is no other explicit response.
              result = value;
            }
          })
          .catch(err => {
            // Forward errors that are exposed to extension, but ignore
            // internal errors such as actor destruction and DataCloneError.
            if (err instanceof ExtensionError || err?.mozWebExtLocation) {
              reject(err);
            } else {
              Cu.reportError(err);
            }
          })
      );
      // Ensure resolving when there are no responses.
      Promise.allSettled(promises).then(() => resolve(result));
    });
  }

  async close() {
    this.open = false;
    Hub.closeConduit(this);
  }
}

/**
 * Implements the parent side of the Conduits actor.
 */
export class ConduitsParent extends JSWindowActorParent {
  constructor() {
    super();
    this.batchData = [];
    this.batchPromise = null;
    this.batchResolve = null;
    this.timerActive = false;
  }

  /**
   * Group webRequest events to send them as a batch, reducing IPC overhead.
   *
   * @param {string} name
   * @param {import("ConduitsChild.sys.mjs").MessageData} data
   * @returns {Promise<object>}
   */
  batch(name, data) {
    let pos = this.batchData.length;
    this.batchData.push(data);

    let sendNow = idleDispatch => {
      if (this.batchData.length && this.manager) {
        this.batchResolve(this.sendQuery(name, this.batchData));
      } else {
        this.batchResolve([]);
      }
      this.batchData = [];
      this.timerActive = !idleDispatch;
    };

    if (!pos) {
      this.batchPromise = new Promise(r => (this.batchResolve = r));
      if (!this.timerActive) {
        ChromeUtils.idleDispatch(sendNow, { timeout: BATCH_TIMEOUT_MS });
        this.timerActive = true;
      }
    }

    if (data.arg.urgentSend) {
      // If this is an urgent blocking event, run this batch right away.
      sendNow(false);
    }

    return this.batchPromise.then(results => results[pos]);
  }

  /**
   * JSWindowActor method, routes the message to the target subject.
   *
   * @param {object} options
   * @param {string} options.name
   * @param {import("ConduitsChild.sys.mjs").MessageData} options.data
   * @returns {Promise?}
   */
  async receiveMessage({ name, data: { arg, query, sender } }) {
    if (name === "ConduitOpened") {
      return Hub.recvConduitOpened(arg, this);
    }

    let remote = Hub.remotes.get(sender);
    if (!remote || remote.actor !== this) {
      throw new Error(`Unknown sender or wrong actor for recv${name}`);
    }

    if (name === "ConduitClosed") {
      return Hub.recvConduitClosed(remote);
    }

    let conduit = Hub.byMethod.get(name);
    if (!conduit) {
      throw new Error(`Parent conduit for recv${name} not found`);
    }

    return conduit._recv(name, arg, { actor: this, query, sender: remote });
  }

  /**
   * JSWindowActor method, ensure cleanup.
   */
  didDestroy() {
    Hub.actorClosed(this);
  }
}

/**
 * Parent side of the Conduits process actor.  Same code as JSWindowActor.
 */
export class ProcessConduitsParent extends JSProcessActorParent {
  receiveMessage = ConduitsParent.prototype.receiveMessage;
  didDestroy = ConduitsParent.prototype.didDestroy;
}
PK
!<G2�g*X*Xmodules/Console.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Define a 'console' API to roughly match the implementation provided by
 * Firebug.
 * This module helps cases where code is shared between the web and Firefox.
 * See also Browser.sys.mjs for an implementation of other web constants to help
 * sharing code between the web and firefox;
 *
 * The API is only be a rough approximation for 3 reasons:
 * - The Firebug console API is implemented in many places with differences in
 *   the implementations, so there isn't a single reference to adhere to
 * - The Firebug console is a rich display compared with dump(), so there will
 *   be many things that we can't replicate
 * - The primary use of this API is debugging and error logging so the perfect
 *   implementation isn't always required (or even well defined)
 */

var gTimerRegistry = new Map();

/**
 * String utility to ensure that strings are a specified length. Strings
 * that are too long are truncated to the max length and the last char is
 * set to "_". Strings that are too short are padded with spaces.
 *
 * @param {string} aStr
 *        The string to format to the correct length
 * @param {number} aMaxLen
 *        The maximum allowed length of the returned string
 * @param {number} aMinLen (optional)
 *        The minimum allowed length of the returned string. If undefined,
 *        then aMaxLen will be used
 * @param {object} aOptions (optional)
 *        An object allowing format customization. Allowed customizations:
 *          'truncate' - can take the value "start" to truncate strings from
 *             the start as opposed to the end or "center" to truncate
 *             strings in the center.
 *          'align' - takes an alignment when padding is needed for MinLen,
 *             either "start" or "end".  Defaults to "start".
 * @return {string}
 *        The original string formatted to fit the specified lengths
 */
function fmt(aStr, aMaxLen, aMinLen, aOptions) {
  if (aMinLen == null) {
    aMinLen = aMaxLen;
  }
  if (aStr == null) {
    aStr = "";
  }
  if (aStr.length > aMaxLen) {
    if (aOptions && aOptions.truncate == "start") {
      return "_" + aStr.substring(aStr.length - aMaxLen + 1);
    } else if (aOptions && aOptions.truncate == "center") {
      let start = aStr.substring(0, aMaxLen / 2);

      let end = aStr.substring(aStr.length - aMaxLen / 2 + 1);
      return start + "_" + end;
    }
    return aStr.substring(0, aMaxLen - 1) + "_";
  }
  if (aStr.length < aMinLen) {
    let padding = Array(aMinLen - aStr.length + 1).join(" ");
    aStr = aOptions.align === "end" ? padding + aStr : aStr + padding;
  }
  return aStr;
}

/**
 * Utility to extract the constructor name of an object.
 * Object.toString gives: "[object ?????]"; we want the "?????".
 *
 * @param {object} aObj
 *        The object from which to extract the constructor name
 * @return {string}
 *        The constructor name
 */
function getCtorName(aObj) {
  if (aObj === null) {
    return "null";
  }
  if (aObj === undefined) {
    return "undefined";
  }
  if (aObj.constructor && aObj.constructor.name) {
    return aObj.constructor.name;
  }
  // If that fails, use Objects toString which sometimes gives something
  // better than 'Object', and at least defaults to Object if nothing better
  return Object.prototype.toString.call(aObj).slice(8, -1);
}

/**
 * Indicates whether an object is a JS or `Components.Exception` error.
 *
 * @param {object} aThing
          The object to check
 * @return {boolean}
          Is this object an error?
 */
function isError(aThing) {
  return (
    aThing &&
    ((typeof aThing.name == "string" && aThing.name.startsWith("NS_ERROR_")) ||
      getCtorName(aThing).endsWith("Error"))
  );
}

/**
 * A single line stringification of an object designed for use by humans
 *
 * @param {any} aThing
 *        The object to be stringified
 * @param {boolean} aAllowNewLines
 * @return {string}
 *        A single line representation of aThing, which will generally be at
 *        most 80 chars long
 */
function stringify(aThing, aAllowNewLines) {
  if (aThing === undefined) {
    return "undefined";
  }

  if (aThing === null) {
    return "null";
  }

  if (isError(aThing)) {
    return "Message: " + aThing;
  }

  if (typeof aThing == "object") {
    let type = getCtorName(aThing);
    if (Element.isInstance(aThing)) {
      return debugElement(aThing);
    }
    type = type == "Object" ? "" : type + " ";
    let json;
    try {
      json = JSON.stringify(aThing);
    } catch (ex) {
      // Can't use a real ellipsis here, because cmd.exe isn't unicode-enabled
      json = "{" + Object.keys(aThing).join(":..,") + ":.., }";
    }
    return type + json;
  }

  if (typeof aThing == "function") {
    return aThing.toString().replace(/\s+/g, " ");
  }

  let str = aThing.toString();
  if (!aAllowNewLines) {
    str = str.replace(/\n/g, "|");
  }
  return str;
}

/**
 * Create a simple debug representation of a given element.
 *
 * @param {Element} aElement
 *        The element to debug
 * @return {string}
 *        A simple single line representation of aElement
 */
function debugElement(aElement) {
  return (
    "<" +
    aElement.tagName +
    (aElement.id ? "#" + aElement.id : "") +
    (aElement.className && aElement.className.split
      ? "." + aElement.className.split(" ").join(" .")
      : "") +
    ">"
  );
}

/**
 * A multi line stringification of an object, designed for use by humans
 *
 * @param {any} aThing
 *        The object to be stringified
 * @return {string}
 *        A multi line representation of aThing
 */
function log(aThing) {
  if (aThing === null) {
    return "null\n";
  }

  if (aThing === undefined) {
    return "undefined\n";
  }

  if (typeof aThing == "object") {
    let reply = "";
    let type = getCtorName(aThing);
    if (type == "Map") {
      reply += "Map\n";
      for (let [key, value] of aThing) {
        reply += logProperty(key, value);
      }
    } else if (type == "Set") {
      let i = 0;
      reply += "Set\n";
      for (let value of aThing) {
        reply += logProperty("" + i, value);
        i++;
      }
    } else if (isError(aThing)) {
      reply += "  Message: " + aThing + "\n";
      if (aThing.stack) {
        reply += "  Stack:\n";
        var frame = aThing.stack;
        while (frame) {
          reply += "    " + frame + "\n";
          frame = frame.caller;
        }
      }
    } else if (Element.isInstance(aThing)) {
      reply += "  " + debugElement(aThing) + "\n";
    } else {
      let keys = Object.getOwnPropertyNames(aThing);
      if (keys.length) {
        reply += type + "\n";
        keys.forEach(function (aProp) {
          reply += logProperty(aProp, aThing[aProp]);
        });
      } else {
        reply += type + "\n";
        let root = aThing;
        let logged = [];
        while (root != null) {
          let properties = Object.keys(root);
          properties.sort();
          properties.forEach(function (property) {
            if (!(property in logged)) {
              logged[property] = property;
              reply += logProperty(property, aThing[property]);
            }
          });

          root = Object.getPrototypeOf(root);
          if (root != null) {
            reply += "  - prototype " + getCtorName(root) + "\n";
          }
        }
      }
    }

    return reply;
  }

  return "  " + aThing.toString() + "\n";
}

/**
 * Helper for log() which converts a property/value pair into an output
 * string
 *
 * @param {string} aProp
 *        The name of the property to include in the output string
 * @param {object} aValue
 *        Value assigned to aProp to be converted to a single line string
 * @return {string}
 *        Multi line output string describing the property/value pair
 */
function logProperty(aProp, aValue) {
  let reply = "";
  if (aProp == "stack" && typeof value == "string") {
    let trace = parseStack(aValue);
    reply += formatTrace(trace);
  } else {
    reply += "    - " + aProp + " = " + stringify(aValue) + "\n";
  }
  return reply;
}

const LOG_LEVELS = {
  all: Number.MIN_VALUE,
  debug: 2,
  log: 3,
  info: 3,
  clear: 3,
  trace: 3,
  timeEnd: 3,
  time: 3,
  assert: 3,
  group: 3,
  groupEnd: 3,
  profile: 3,
  profileEnd: 3,
  dir: 3,
  dirxml: 3,
  warn: 4,
  error: 5,
  off: Number.MAX_VALUE,
};

/**
 * Helper to tell if a console message of `aLevel` type
 * should be logged in stdout and sent to consoles given
 * the current maximum log level being defined in `console.maxLogLevel`
 *
 * @param {string} aLevel
 *        Console message log level
 * @param {string} aMaxLevel {string}
 *        String identifier (See LOG_LEVELS for possible
 *        values) that allows to filter which messages
 *        are logged based on their log level
 * @return {boolean}
 *        Should this message be logged or not?
 */
function shouldLog(aLevel, aMaxLevel) {
  return LOG_LEVELS[aMaxLevel] <= LOG_LEVELS[aLevel];
}

/**
 * Parse a stack trace, returning an array of stack frame objects, where
 * each has filename/lineNumber/functionName members
 *
 * @param {string} aStack
 *        The serialized stack trace
 * @return {object[]}
 *        Array of { file: "...", line: NNN, call: "..." } objects
 */
function parseStack(aStack) {
  let trace = [];
  aStack.split("\n").forEach(function (line) {
    if (!line) {
      return;
    }
    let at = line.lastIndexOf("@");
    let posn = line.substring(at + 1);
    trace.push({
      filename: posn.split(":")[0],
      lineNumber: posn.split(":")[1],
      functionName: line.substring(0, at),
    });
  });
  return trace;
}

/**
 * Format a frame coming from Components.stack such that it can be used by the
 * Browser Console, via ConsoleAPIStorage notifications.
 *
 * @param {object} aFrame
 *        The stack frame from which to begin the walk.
 * @param {number=0} aMaxDepth
 *        Maximum stack trace depth. Default is 0 - no depth limit.
 * @return {object[]}
 *         An array of {filename, lineNumber, functionName, language} objects.
 *         These objects follow the same format as other ConsoleAPIStorage
 *         messages.
 */
function getStack(aFrame, aMaxDepth = 0) {
  if (!aFrame) {
    aFrame = Components.stack.caller;
  }
  let trace = [];
  while (aFrame) {
    trace.push({
      filename: aFrame.filename,
      lineNumber: aFrame.lineNumber,
      functionName: aFrame.name,
      language: aFrame.language,
    });
    if (aMaxDepth == trace.length) {
      break;
    }
    aFrame = aFrame.caller;
  }
  return trace;
}

/**
 * Take the output from parseStack() and convert it to nice readable
 * output
 *
 * @param {object[]} aTrace
 *        Array of trace objects as created by parseStack()
 * @return {string} Multi line report of the stack trace
 */
function formatTrace(aTrace) {
  let reply = "";
  aTrace.forEach(function (frame) {
    reply +=
      fmt(frame.filename, 20, 20, { truncate: "start" }) +
      " " +
      fmt(frame.lineNumber, 5, 5) +
      " " +
      fmt(frame.functionName, 75, 0, { truncate: "center" }) +
      "\n";
  });
  return reply;
}

/**
 * Create a new timer by recording the current time under the specified name.
 *
 * @param {string} aName
 *        The name of the timer.
 * @param {number} [aTimestamp=Date.now()]
 *        Optional timestamp that tells when the timer was originally started.
 * @return {object}
 *         The name property holds the timer name and the started property
 *         holds the time the timer was started. In case of error, it returns
 *         an object with the single property "error" that contains the key
 *         for retrieving the localized error message.
 */
function startTimer(aName, aTimestamp) {
  let key = aName.toString();
  if (!gTimerRegistry.has(key)) {
    gTimerRegistry.set(key, aTimestamp || Date.now());
  }
  return { name: aName, started: gTimerRegistry.get(key) };
}

/**
 * Stop the timer with the specified name and retrieve the elapsed time.
 *
 * @param {string} aName
 *        The name of the timer.
 * @param {number} [aTimestamp=Date.now()]
 *        Optional timestamp that tells when the timer was originally stopped.
 * @return {object}
 *         The name property holds the timer name and the duration property
 *         holds the number of milliseconds since the timer was started.
 */
function stopTimer(aName, aTimestamp) {
  let key = aName.toString();
  let duration = (aTimestamp || Date.now()) - gTimerRegistry.get(key);
  gTimerRegistry.delete(key);
  return { name: aName, duration };
}

/**
 * Dump a new message header to stdout by taking care of adding an eventual
 * prefix
 *
 * @param {object} aConsole
 *        ConsoleAPI instance
 * @param {string} aLevel
 *        The string identifier for the message log level
 * @param {string} aMessage
 *        The string message to print to stdout
 */
function dumpMessage(aConsole, aLevel, aMessage) {
  aConsole.dump(
    "console." +
      aLevel +
      ": " +
      (aConsole.prefix ? aConsole.prefix + ": " : "") +
      aMessage +
      "\n"
  );
}

/**
 * Create a function which will output a concise level of output when used
 * as a logging function
 *
 * @param {string} aLevel
 *        A prefix to all output generated from this function detailing the
 *        level at which output occurred
 * @return {function}
 *        A logging function
 * @see createMultiLineDumper()
 */
function createDumper(aLevel) {
  return function () {
    if (!shouldLog(aLevel, this.maxLogLevel)) {
      return;
    }
    let args = Array.prototype.slice.call(arguments, 0);
    let frame = getStack(Components.stack.caller, 1)[0];
    sendConsoleAPIMessage(this, aLevel, frame, args);
    let data = args.map(function (arg) {
      return stringify(arg, true);
    });
    dumpMessage(this, aLevel, data.join(" "));
  };
}

/**
 * Create a function which will output more detailed level of output when
 * used as a logging function
 *
 * @param {string} aLevel
 *        A prefix to all output generated from this function detailing the
 *        level at which output occurred
 * @return {function}
 *        A logging function
 * @see createDumper()
 */
function createMultiLineDumper(aLevel) {
  return function () {
    if (!shouldLog(aLevel, this.maxLogLevel)) {
      return;
    }
    dumpMessage(this, aLevel, "");
    let args = Array.prototype.slice.call(arguments, 0);
    let frame = getStack(Components.stack.caller, 1)[0];
    sendConsoleAPIMessage(this, aLevel, frame, args);
    args.forEach(function (arg) {
      this.dump(log(arg));
    }, this);
  };
}

/**
 * Send a Console API message. This function will send a notification through
 * the nsIConsoleAPIStorage service.
 *
 * @param {object} aConsole
 *        The instance of ConsoleAPI performing the logging.
 * @param {string} aLevel
 *        Message severity level. This is usually the name of the console method
 *        that was called.
 * @param {object} aFrame
 *        The youngest stack frame coming from Components.stack, as formatted by
 *        getStack().
 * @param {array} aArgs
 *        The arguments given to the console method.
 * @param {object} aOptions
 *        Object properties depend on the console method that was invoked:
 *        - timer: for time() and timeEnd(). Holds the timer information.
 *        - groupName: for group(), groupCollapsed() and groupEnd().
 *        - stacktrace: for trace(). Holds the array of stack frames as given by
 *        getStack().
 */
function sendConsoleAPIMessage(aConsole, aLevel, aFrame, aArgs, aOptions = {}) {
  let consoleEvent = {
    ID: "jsm",
    innerID: aConsole.innerID || aFrame.filename,
    consoleID: aConsole.consoleID,
    level: aLevel,
    filename: aFrame.filename,
    lineNumber: aFrame.lineNumber,
    functionName: aFrame.functionName,
    timeStamp: Date.now(),
    arguments: aArgs,
    prefix: aConsole.prefix,
    chromeContext: true,
  };

  consoleEvent.wrappedJSObject = consoleEvent;

  switch (aLevel) {
    case "trace":
      consoleEvent.stacktrace = aOptions.stacktrace;
      break;
    case "time":
    case "timeEnd":
      consoleEvent.timer = aOptions.timer;
      break;
    case "group":
    case "groupCollapsed":
    case "groupEnd":
      try {
        consoleEvent.groupName = Array.prototype.join.call(aArgs, " ");
      } catch (ex) {
        console.error(ex);
        console.error(ex.stack);
        return;
      }
      break;
  }

  let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"].getService(
    Ci.nsIConsoleAPIStorage
  );
  if (ConsoleAPIStorage) {
    ConsoleAPIStorage.recordEvent("jsm", consoleEvent);
  }
}

/**
 * This creates a console object that somewhat replicates Firebug's console
 * object
 *
 * @param {object} aConsoleOptions
 *        Optional dictionary with a set of runtime console options:
 *        - prefix {string} : An optional prefix string to be printed before
 *                            the actual logged message
 *        - maxLogLevel {string} : String identifier (See LOG_LEVELS for
 *                            possible values) that allows to filter which
 *                            messages are logged based on their log level.
 *                            If falsy value, all messages will be logged.
 *                            If wrong value that doesn't match any key of
 *                            LOG_LEVELS, no message will be logged
 *        - maxLogLevelPref {string} : String pref name which contains the
 *                            level to use for maxLogLevel. If the pref doesn't
 *                            exist or gets removed, the maxLogLevel will default
 *                            to the value passed to this constructor (or "all"
 *                            if it wasn't specified).
 *        - dump {function} : An optional function to intercept all strings
 *                            written to stdout
 *        - innerID {string}: An ID representing the source of the message.
 *                            Normally the inner ID of a DOM window.
 *        - consoleID {string} : String identified for the console, this will
 *                            be passed through the console notifications
 * @return {object}
 *        A console API instance object
 */
export function ConsoleAPI(aConsoleOptions = {}) {
  // Normalize console options to set default values
  // in order to avoid runtime checks on each console method call.
  this.dump = aConsoleOptions.dump || dump;
  this.prefix = aConsoleOptions.prefix || "";
  this.maxLogLevel = aConsoleOptions.maxLogLevel;
  this.innerID = aConsoleOptions.innerID || null;
  this.consoleID = aConsoleOptions.consoleID || "";

  // Setup maxLogLevelPref watching
  let updateMaxLogLevel = () => {
    if (
      Services.prefs.getPrefType(aConsoleOptions.maxLogLevelPref) ==
      Services.prefs.PREF_STRING
    ) {
      this._maxLogLevel = Services.prefs
        .getCharPref(aConsoleOptions.maxLogLevelPref)
        .toLowerCase();
    } else {
      this._maxLogLevel = this._maxExplicitLogLevel;
    }
  };

  if (aConsoleOptions.maxLogLevelPref) {
    updateMaxLogLevel();
    Services.prefs.addObserver(
      aConsoleOptions.maxLogLevelPref,
      updateMaxLogLevel
    );
  }

  // Bind all the functions to this object.
  for (let prop in this) {
    if (typeof this[prop] === "function") {
      this[prop] = this[prop].bind(this);
    }
  }
}

ConsoleAPI.prototype = {
  /**
   * The last log level that was specified via the constructor or setter. This
   * is used as a fallback if the pref doesn't exist or is removed.
   */
  _maxExplicitLogLevel: null,
  /**
   * The current log level via all methods of setting (pref or via the API).
   */
  _maxLogLevel: null,
  debug: createMultiLineDumper("debug"),
  assert: createDumper("assert"),
  log: createDumper("log"),
  info: createDumper("info"),
  warn: createDumper("warn"),
  error: createMultiLineDumper("error"),
  exception: createMultiLineDumper("error"),

  trace: function Console_trace() {
    if (!shouldLog("trace", this.maxLogLevel)) {
      return;
    }
    let args = Array.prototype.slice.call(arguments, 0);
    let trace = getStack(Components.stack.caller);
    sendConsoleAPIMessage(this, "trace", trace[0], args, { stacktrace: trace });
    dumpMessage(this, "trace", "\n" + formatTrace(trace));
  },
  clear: function Console_clear() {},

  dir: createMultiLineDumper("dir"),
  dirxml: createMultiLineDumper("dirxml"),
  group: createDumper("group"),
  groupEnd: createDumper("groupEnd"),

  time: function Console_time() {
    if (!shouldLog("time", this.maxLogLevel)) {
      return;
    }
    let args = Array.prototype.slice.call(arguments, 0);
    let frame = getStack(Components.stack.caller, 1)[0];
    let timer = startTimer(args[0]);
    sendConsoleAPIMessage(this, "time", frame, args, { timer });
    dumpMessage(this, "time", "'" + timer.name + "' @ " + new Date());
  },

  timeEnd: function Console_timeEnd() {
    if (!shouldLog("timeEnd", this.maxLogLevel)) {
      return;
    }
    let args = Array.prototype.slice.call(arguments, 0);
    let frame = getStack(Components.stack.caller, 1)[0];
    let timer = stopTimer(args[0]);
    sendConsoleAPIMessage(this, "timeEnd", frame, args, { timer });
    dumpMessage(
      this,
      "timeEnd",
      "'" + timer.name + "' " + timer.duration + "ms"
    );
  },

  profile(profileName) {
    if (!shouldLog("profile", this.maxLogLevel)) {
      return;
    }
    Services.obs.notifyObservers(
      {
        wrappedJSObject: {
          action: "profile",
          arguments: [profileName],
          chromeContext: true,
        },
      },
      "console-api-profiler"
    );
    dumpMessage(this, "profile", `'${profileName}'`);
  },

  profileEnd(profileName) {
    if (!shouldLog("profileEnd", this.maxLogLevel)) {
      return;
    }
    Services.obs.notifyObservers(
      {
        wrappedJSObject: {
          action: "profileEnd",
          arguments: [profileName],
          chromeContext: true,
        },
      },
      "console-api-profiler"
    );
    dumpMessage(this, "profileEnd", `'${profileName}'`);
  },

  get maxLogLevel() {
    return this._maxLogLevel || "all";
  },

  set maxLogLevel(aValue) {
    this._maxLogLevel = this._maxExplicitLogLevel = aValue;
  },

  shouldLog(aLevel) {
    return shouldLog(aLevel, this.maxLogLevel);
  },
};
PK
!<;ͳ���!modules/ConsoleAPIStorage.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const STORAGE_MAX_EVENTS = 1000;

var _consoleStorage = new Map();

// NOTE: these listeners used to just be added as observers and notified via
// Services.obs.notifyObservers. However, that has enough overhead to be a
// problem for this. Using an explicit global array is much cheaper, and
// should be equivalent.
var _logEventListeners = [];

const CONSOLEAPISTORAGE_CID = Components.ID(
  "{96cf7855-dfa9-4c6d-8276-f9705b4890f2}"
);

/**
 * The ConsoleAPIStorage is meant to cache window.console API calls for later
 * reuse by other components when needed. For example, the Web Console code can
 * display the cached messages when it opens for the active tab.
 *
 * ConsoleAPI messages are stored as they come from the ConsoleAPI code, with
 * all their properties. They are kept around until the inner window object that
 * created the messages is destroyed. Messages are indexed by the inner window
 * ID.
 *
 * Usage:
 *    let ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
 *                              .getService(Ci.nsIConsoleAPIStorage);
 *
 *    // Get the cached events array for the window you want (use the inner
 *    // window ID).
 *    let events = ConsoleAPIStorage.getEvents(innerWindowID);
 *    events.forEach(function(event) { ... });
 *
 *    // Clear the events for the given inner window ID.
 *    ConsoleAPIStorage.clearEvents(innerWindowID);
 */
export function ConsoleAPIStorageService() {
  this.init();
}

ConsoleAPIStorageService.prototype = {
  classID: CONSOLEAPISTORAGE_CID,
  QueryInterface: ChromeUtils.generateQI([
    "nsIConsoleAPIStorage",
    "nsIObserver",
  ]),

  observe: function CS_observe(aSubject, aTopic) {
    if (aTopic == "xpcom-shutdown") {
      Services.obs.removeObserver(this, "xpcom-shutdown");
      Services.obs.removeObserver(this, "inner-window-destroyed");
      Services.obs.removeObserver(this, "memory-pressure");
    } else if (aTopic == "inner-window-destroyed") {
      let innerWindowID = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
      this.clearEvents(innerWindowID + "");
    } else if (aTopic == "memory-pressure") {
      this.clearEvents();
    }
  },

  /** @private */
  init: function CS_init() {
    Services.obs.addObserver(this, "xpcom-shutdown");
    Services.obs.addObserver(this, "inner-window-destroyed");
    Services.obs.addObserver(this, "memory-pressure");
  },

  /**
   * Get the events array by inner window ID or all events from all windows.
   *
   * @param string [aId]
   *        Optional, the inner window ID for which you want to get the array of
   *        cached events.
   * @returns array
   *          The array of cached events for the given window. If no |aId| is
   *          given this function returns all of the cached events, from any
   *          window.
   */
  getEvents: function CS_getEvents(aId) {
    if (aId != null) {
      return (_consoleStorage.get(aId) || []).slice(0);
    }

    let result = [];

    for (let [, events] of _consoleStorage) {
      result.push.apply(result, events);
    }

    return result.sort(function (a, b) {
      return a.timeStamp - b.timeStamp;
    });
  },

  /**
   * Adds a listener to be notified of log events.
   *
   * @param jsval [aListener]
   *        A JS listener which will be notified with the message object when
   *        a log event occurs.
   * @param nsIPrincipal [aPrincipal]
   *        The principal of the listener - used to determine if we need to
   *        clone the message before forwarding it.
   */
  addLogEventListener: function CS_addLogEventListener(aListener, aPrincipal) {
    // If our listener has a less-privileged principal than us, then they won't
    // be able to access the log event object which was populated for our
    // scope. Accordingly we need to clone it for these listeners.
    //
    // XXX: AFAICT these listeners which we need to clone messages for are all
    // tests. Alternative solutions are welcome.
    const clone = !aPrincipal.subsumes(
      Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal)
    );
    _logEventListeners.push({
      callback: aListener,
      clone,
    });
  },

  /**
   * Removes a listener added with `addLogEventListener`.
   *
   * @param jsval [aListener]
   *        A JS listener which was added with `addLogEventListener`.
   */
  removeLogEventListener: function CS_removeLogEventListener(aListener) {
    const index = _logEventListeners.findIndex(l => l.callback === aListener);
    if (index != -1) {
      _logEventListeners.splice(index, 1);
    } else {
      console.error(
        "Attempted to remove a log event listener that does not exist."
      );
    }
  },

  /**
   * Record an event associated with the given window ID.
   *
   * @param string aId
   *        The ID of the inner window for which the event occurred or "jsm" for
   *        messages logged from JavaScript modules..
   * @param object aEvent
   *        A JavaScript object you want to store.
   */
  recordEvent: function CS_recordEvent(aId, aEvent) {
    if (!_consoleStorage.has(aId)) {
      _consoleStorage.set(aId, []);
    }

    let storage = _consoleStorage.get(aId);

    storage.push(aEvent);

    // truncate
    if (storage.length > STORAGE_MAX_EVENTS) {
      storage.shift();
    }

    for (let { callback, clone } of _logEventListeners) {
      try {
        if (clone) {
          callback(Cu.cloneInto(aEvent, callback));
        } else {
          callback(aEvent);
        }
      } catch (e) {
        // A failing listener should not prevent from calling other listeners.
        console.error(e);
      }
    }
  },

  /**
   * Clear storage data for the given window.
   *
   * @param string [aId]
   *        Optional, the inner window ID for which you want to clear the
   *        messages. If this is not specified all of the cached messages are
   *        cleared, from all window objects.
   */
  clearEvents: function CS_clearEvents(aId) {
    if (aId != null) {
      _consoleStorage.delete(aId);
    } else {
      _consoleStorage.clear();
      Services.obs.notifyObservers(null, "console-storage-reset");
    }
  },
};
PK
!<.�� O(O('modules/ContentAreaDropListener.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// This component is used for handling dragover and drop of urls.
//
// It checks to see whether a drop of a url is allowed. For instance, a url
// cannot be dropped if it is not a valid uri or the source of the drag cannot
// access the uri. This prevents, for example, a source document from tricking
// the user into dragging a chrome url.

export function ContentAreaDropListener() {}

ContentAreaDropListener.prototype = {
  classID: Components.ID("{1f34bc80-1bc7-11d6-a384-d705dd0746fc}"),
  QueryInterface: ChromeUtils.generateQI(["nsIDroppedLinkHandler"]),

  _addLink(links, url, name, type) {
    links.push({ url, name, type });
  },

  _addLinksFromItem(links, dt, i) {
    let types = dt.mozTypesAt(i);
    let type, data;

    type = "text/uri-list";
    if (types.contains(type)) {
      data = dt.mozGetDataAt(type, i);
      if (data) {
        let urls = data.split("\n");
        for (let url of urls) {
          // lines beginning with # are comments
          if (url.startsWith("#")) {
            continue;
          }
          url = url.replace(/^\s+|\s+$/g, "");
          this._addLink(links, url, url, type);
        }
        return;
      }
    }

    type = "text/x-moz-url";
    if (types.contains(type)) {
      data = dt.mozGetDataAt(type, i);
      if (data) {
        let lines = data.split("\n");
        for (let i = 0, length = lines.length; i < length; i += 2) {
          this._addLink(links, lines[i], lines[i + 1], type);
        }
        return;
      }
    }

    for (let type of ["text/plain", "text/x-moz-text-internal"]) {
      if (types.contains(type)) {
        data = dt.mozGetDataAt(type, i);
        if (data) {
          let lines = data.replace(/^\s+|\s+$/gm, "").split("\n");
          if (!lines.length) {
            return;
          }

          // For plain text, there are 2 cases:
          //   * if there is at least one URI:
          //       Add all URIs, ignoring non-URI lines, so that all URIs
          //       are opened in tabs.
          //   * if there's no URI:
          //       Add the entire text as a single entry, so that the entire
          //       text is searched.
          let hasURI = false;
          // We don't care whether we are in a private context, because we are
          // only using fixedURI and thus there's no risk to use the wrong
          // search engine.
          let flags =
            Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
            Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
          for (let line of lines) {
            let info = Services.uriFixup.getFixupURIInfo(line, flags);
            if (info.fixedURI) {
              // Use the original line here, and let the caller decide
              // whether to perform fixup or not.
              hasURI = true;
              this._addLink(links, line, line, type);
            }
          }

          if (!hasURI) {
            this._addLink(links, data, data, type);
          }
          return;
        }
      }
    }

    // For shortcuts, we want to check for the file type last, so that the
    // url pointed to in one of the url types is found first before the file
    // type, which points to the actual file.
    let files = dt.files;
    if (files && i < files.length) {
      this._addLink(
        links,
        PathUtils.toFileURI(files[i].mozFullPath),
        files[i].name,
        "application/x-moz-file"
      );
    }
  },

  _getDropLinks(dt) {
    let links = [];
    for (let i = 0; i < dt.mozItemCount; i++) {
      this._addLinksFromItem(links, dt, i);
    }
    return links;
  },

  _validateURI(dataTransfer, uriString, disallowInherit, triggeringPrincipal) {
    if (!uriString) {
      return "";
    }

    // Strip leading and trailing whitespace, then try to create a
    // URI from the dropped string. If that succeeds, we're
    // dropping a URI and we need to do a security check to make
    // sure the source document can load the dropped URI.
    uriString = uriString.replace(/^\s*|\s*$/g, "");

    // Apply URI fixup so that this validation prevents bad URIs even if the
    // similar fixup is applied later, especialy fixing typos up will convert
    // non-URI to URI.
    // We don't know if the uri comes from a private context, but luckily we
    // are only using fixedURI, so there's no risk to use the wrong search
    // engine.
    let fixupFlags =
      Ci.nsIURIFixup.FIXUP_FLAG_FIX_SCHEME_TYPOS |
      Ci.nsIURIFixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
    let info = Services.uriFixup.getFixupURIInfo(uriString, fixupFlags);
    if (!info.fixedURI || info.keywordProviderName) {
      // Loading a keyword search should always be fine for all cases.
      return uriString;
    }
    let uri = info.fixedURI;

    let secMan = Services.scriptSecurityManager;
    let flags = secMan.STANDARD;
    if (disallowInherit) {
      flags |= secMan.DISALLOW_INHERIT_PRINCIPAL;
    }

    secMan.checkLoadURIWithPrincipal(triggeringPrincipal, uri, flags);

    // Once we validated, return the URI after fixup, instead of the original
    // uriString.
    return uri.spec;
  },

  _getTriggeringPrincipalFromDataTransfer(
    aDataTransfer,
    fallbackToSystemPrincipal
  ) {
    let sourceNode = aDataTransfer.mozSourceNode;
    if (
      sourceNode &&
      (sourceNode.localName !== "browser" ||
        sourceNode.namespaceURI !==
          "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul")
    ) {
      // Use sourceNode's principal only if the sourceNode is not browser.
      //
      // If sourceNode is browser, the actual triggering principal may be
      // differ than sourceNode's principal, since sourceNode's principal is
      // top level document's one and the drag may be triggered from a frame
      // with different principal.
      if (sourceNode.nodePrincipal) {
        return sourceNode.nodePrincipal;
      }
    }

    // First, fallback to mozTriggeringPrincipalURISpec that is set when the
    // drop comes from another content process.
    let principalURISpec = aDataTransfer.mozTriggeringPrincipalURISpec;
    if (!principalURISpec) {
      // Fallback to either system principal or file principal, supposing
      // the drop comes from outside of the browser, so that drops of file
      // URIs are always allowed.
      //
      // TODO: Investigate and describe the difference between them,
      //       or use only one principal. (Bug 1367038)
      if (fallbackToSystemPrincipal) {
        return Services.scriptSecurityManager.getSystemPrincipal();
      }

      principalURISpec = "file:///";
    }
    return Services.scriptSecurityManager.createContentPrincipal(
      Services.io.newURI(principalURISpec),
      {}
    );
  },

  getTriggeringPrincipal(aEvent) {
    let dataTransfer = aEvent.dataTransfer;
    return this._getTriggeringPrincipalFromDataTransfer(dataTransfer, true);
  },

  getCsp(aEvent) {
    let sourceNode = aEvent.dataTransfer.mozSourceNode;
    if (aEvent.dataTransfer.mozCSP !== null) {
      return aEvent.dataTransfer.mozCSP;
    }

    if (
      sourceNode &&
      (sourceNode.localName !== "browser" ||
        sourceNode.namespaceURI !==
          "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul")
    ) {
      // Use sourceNode's csp only if the sourceNode is not browser.
      //
      // If sourceNode is browser, the actual triggering csp may be differ than sourceNode's csp,
      // since sourceNode's csp is top level document's one and the drag may be triggered from a
      // frame with different csp.
      return sourceNode.csp;
    }
    return null;
  },

  canDropLink(aEvent, aAllowSameDocument) {
    if (this._eventTargetIsDisabled(aEvent)) {
      return false;
    }

    let dataTransfer = aEvent.dataTransfer;
    let types = dataTransfer.types;
    if (
      !types.includes("application/x-moz-file") &&
      !types.includes("text/x-moz-url") &&
      !types.includes("text/uri-list") &&
      !types.includes("text/x-moz-text-internal") &&
      !types.includes("text/plain")
    ) {
      return false;
    }

    if (aAllowSameDocument) {
      return true;
    }

    // If this is an external drag, allow drop.
    let sourceTopWC = dataTransfer.sourceTopWindowContext;
    if (!sourceTopWC) {
      return true;
    }

    // If drag source and drop target are in the same top window, don't allow.
    let eventWC =
      aEvent.originalTarget.ownerGlobal.browsingContext.currentWindowContext;
    if (eventWC && sourceTopWC == eventWC.topWindowContext) {
      return false;
    }

    return true;
  },

  dropLinks(aEvent, aDisallowInherit) {
    if (aEvent && this._eventTargetIsDisabled(aEvent)) {
      return [];
    }

    let dataTransfer = aEvent.dataTransfer;
    let links = this._getDropLinks(dataTransfer);
    let triggeringPrincipal = this._getTriggeringPrincipalFromDataTransfer(
      dataTransfer,
      false
    );

    for (let link of links) {
      try {
        link.url = this._validateURI(
          dataTransfer,
          link.url,
          aDisallowInherit,
          triggeringPrincipal
        );
      } catch (ex) {
        // Prevent the drop entirely if any of the links are invalid even if
        // one of them is valid.
        aEvent.stopPropagation();
        aEvent.preventDefault();
        throw ex;
      }
    }

    return links;
  },

  validateURIsForDrop(aEvent, aURIs, aDisallowInherit) {
    let dataTransfer = aEvent.dataTransfer;
    let triggeringPrincipal = this._getTriggeringPrincipalFromDataTransfer(
      dataTransfer,
      false
    );

    for (let uri of aURIs) {
      this._validateURI(
        dataTransfer,
        uri,
        aDisallowInherit,
        triggeringPrincipal
      );
    }
  },

  queryLinks(aDataTransfer) {
    return this._getDropLinks(aDataTransfer);
  },

  _eventTargetIsDisabled(aEvent) {
    let ownerDoc = aEvent.originalTarget.ownerDocument;
    if (!ownerDoc || !ownerDoc.defaultView) {
      return false;
    }

    return ownerDoc.defaultView.windowUtils.isNodeDisabledForEvents(
      aEvent.originalTarget
    );
  },
};
PK
!<��$���(modules/ContentBlockingAllowList.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

/**
 * A helper module to manage the Content Blocking Allow List.
 *
 * This module provides a couple of utility APIs for adding or
 * removing a given browser object to the Content Blocking allow
 * list.
 */
export const ContentBlockingAllowList = {
  _observingLastPBContext: false,

  _maybeSetupLastPBContextObserver() {
    if (!this._observingLastPBContext) {
      this._observer = {
        QueryInterface: ChromeUtils.generateQI([
          "nsIObserver",
          "nsISupportsWeakReference",
        ]),

        observe(subject, topic) {
          if (topic == "last-pb-context-exited") {
            Services.perms.removeByType("trackingprotection-pb");
          }
        },
      };
      Services.obs.addObserver(this._observer, "last-pb-context-exited", true);
      this._observingLastPBContext = true;
    }
  },

  _basePrincipalForAntiTrackingCommon(browser) {
    let principal =
      browser.browsingContext.currentWindowGlobal
        ?.contentBlockingAllowListPrincipal;
    // We can only use content principals for this purpose.
    if (!principal || !principal.isContentPrincipal) {
      return null;
    }
    return principal;
  },

  _permissionTypeFor(browser) {
    return lazy.PrivateBrowsingUtils.isBrowserPrivate(browser)
      ? "trackingprotection-pb"
      : "trackingprotection";
  },

  _expiryFor(browser) {
    return lazy.PrivateBrowsingUtils.isBrowserPrivate(browser)
      ? Ci.nsIPermissionManager.EXPIRE_SESSION
      : Ci.nsIPermissionManager.EXPIRE_NEVER;
  },

  /**
   * Returns false if this module cannot handle the current document loaded in
   * the browser object.  This can happen for example for about: or file:
   * documents.
   */
  canHandle(browser) {
    return this._basePrincipalForAntiTrackingCommon(browser) != null;
  },

  /**
   * Add the given browser object to the Content Blocking allow list.
   */
  add(browser) {
    // Start observing PB last-context-exit notification to do the needed cleanup.
    this._maybeSetupLastPBContextObserver();

    let prin = this._basePrincipalForAntiTrackingCommon(browser);
    let type = this._permissionTypeFor(browser);
    let expire = this._expiryFor(browser);
    Services.perms.addFromPrincipal(
      prin,
      type,
      Services.perms.ALLOW_ACTION,
      expire
    );
  },

  /**
   * Remove the given browser object from the Content Blocking allow list.
   */
  remove(browser) {
    let prin = this._basePrincipalForAntiTrackingCommon(browser);
    let type = this._permissionTypeFor(browser);
    Services.perms.removeFromPrincipal(prin, type);
  },

  /**
   * Returns true if the current browser has loaded a document that is on the
   * Content Blocking allow list.
   */
  includes(browser) {
    let prin = this._basePrincipalForAntiTrackingCommon(browser);
    let type = this._permissionTypeFor(browser);
    return (
      Services.perms.testExactPermissionFromPrincipal(prin, type) ==
      Services.perms.ALLOW_ACTION
    );
  },
};
PK
!<ۮ����#modules/ContentDOMReference.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This module holds weak references to DOM elements that exist within the
 * current content process, and converts them to a unique identifier that can be
 * passed between processes. The identifer, if received by the same content process
 * that issued it, can then be converted back into the DOM element (presuming the
 * element hasn't had all of its other references dropped).
 *
 * The hope is that this module can eliminate the need for passing CPOW references
 * between processes during runtime.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "finalizationService",
  "@mozilla.org/toolkit/finalizationwitness;1",
  "nsIFinalizationWitnessService"
);

/**
 * @typedef {number} ElementID
 * @typedef {Object} ElementIdentifier
 */

const FINALIZATION_TOPIC = "content-dom-reference-finalized";

// A WeakMap which ties finalization witness objects to the lifetime of the DOM
// nodes they're meant to witness. When the DOM node in the map key is
// finalized, the WeakMap stops holding the finalization witness in its value
// alive, which alerts our observer that the element has been destroyed.
const finalizerRoots = new WeakMap();

/**
 * An identifier generated by ContentDOMReference is a unique pair of BrowsingContext
 * ID and a numeric ID. gRegistry maps BrowsingContext's to an object with the following
 * properties:
 *
 *   IDToElement:
 *     A Map of IDs to WeakReference's to the elements they refer to.
 *
 *   elementToID:
 *     A WeakMap from a DOM element to an ID that refers to it.
 */
var gRegistry = new WeakMap();

export var ContentDOMReference = {
  _init() {
    Services.obs.addObserver(this, FINALIZATION_TOPIC);
  },

  observe(subject, topic, data) {
    if (topic !== FINALIZATION_TOPIC) {
      throw new Error("Unexpected observer topic");
    }

    let identifier = JSON.parse(data);
    this._revoke(identifier);
  },

  /**
   * Generate and return an identifier for a given DOM element.
   *
   * @param {Element} element The DOM element to generate the identifier for.
   * @return {ElementIdentifier} The identifier for the DOM element that can be passed between
   * processes as a message.
   */
  get(element) {
    if (!element) {
      throw new Error(
        "Can't create a ContentDOMReference identifier for " +
          "non-existant nodes."
      );
    }

    let browsingContext = BrowsingContext.getFromWindow(element.ownerGlobal);
    let mappings = gRegistry.get(browsingContext);
    if (!mappings) {
      mappings = {
        IDToElement: new Map(),
        elementToID: new WeakMap(),
      };
      gRegistry.set(browsingContext, mappings);
    }

    let id = mappings.elementToID.get(element);
    if (id) {
      // We already had this element registered, so return the pre-existing ID.
      return { browsingContextId: browsingContext.id, id };
    }

    // We must be registering a new element at this point.
    id = Math.random();
    mappings.elementToID.set(element, id);
    mappings.IDToElement.set(id, Cu.getWeakReference(element));

    let identifier = { browsingContextId: browsingContext.id, id };

    finalizerRoots.set(
      element,
      lazy.finalizationService.make(
        FINALIZATION_TOPIC,
        JSON.stringify(identifier)
      )
    );

    return identifier;
  },

  /**
   * Resolves an identifier back into the DOM Element that it was generated from.
   *
   * @param {ElementIdentifier} The identifier generated via ContentDOMReference.get for a
   * DOM element.
   * @return {Element} The DOM element that the identifier was generated for, or
   * null if the element does not still exist.
   */
  resolve(identifier) {
    let browsingContext = BrowsingContext.get(identifier.browsingContextId);
    let { id } = identifier;
    return this._resolveIDToElement(browsingContext, id);
  },

  /**
   * Removes an identifier from the registry so that subsequent attempts
   * to resolve it will result in null. This is done automatically when the
   * target node is GCed.
   *
   * @param {ElementIdentifier} The identifier to revoke, issued by ContentDOMReference.get for
   * a DOM element.
   */
  _revoke(identifier) {
    let browsingContext = BrowsingContext.get(identifier.browsingContextId);
    let { id } = identifier;

    let mappings = gRegistry.get(browsingContext);
    if (!mappings) {
      return;
    }

    mappings.IDToElement.delete(id);
  },

  /**
   * Private helper function that resolves a BrowsingContext and ID (the
   * pair that makes up an identifier) to a DOM element.
   *
   * @param {BrowsingContext} browsingContext The BrowsingContext that was hosting
   * the DOM element at the time that the identifier was generated.
   * @param {ElementID} id The ID generated for the DOM element.
   *
   * @return {Element} The DOM element that the identifier was generated for, or
   * null if the element does not still exist.
   */
  _resolveIDToElement(browsingContext, id) {
    let mappings = gRegistry.get(browsingContext);
    if (!mappings) {
      return null;
    }

    let weakReference = mappings.IDToElement.get(id);
    if (!weakReference) {
      return null;
    }

    return weakReference.get();
  },
};

ContentDOMReference._init();
PK
!<��+��9�9&modules/ContentDispatchChooser.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Constants

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { E10SUtils } from "resource://gre/modules/E10SUtils.sys.mjs";

const DIALOG_URL_APP_CHOOSER =
  "chrome://mozapps/content/handling/appChooser.xhtml";
const DIALOG_URL_PERMISSION =
  "chrome://mozapps/content/handling/permissionDialog.xhtml";

const gPrefs = {};
XPCOMUtils.defineLazyPreferenceGetter(
  gPrefs,
  "promptForExternal",
  "network.protocol-handler.prompt-from-external",
  true
);

const PROTOCOL_HANDLER_OPEN_PERM_KEY = "open-protocol-handler";
const PERMISSION_KEY_DELIMITER = "^";

export class nsContentDispatchChooser {
  /**
   * Prompt the user to open an external application.
   * If the triggering principal doesn't have permission to open apps for the
   * protocol of aURI, we show a permission prompt first.
   * If the caller has permission and a preferred handler is set, we skip the
   * dialogs and directly open the handler.
   * @param {nsIHandlerInfo} aHandler - Info about protocol and handlers.
   * @param {nsIURI} aURI - URI to be handled.
   * @param {nsIPrincipal} [aPrincipal] - Principal which triggered the load.
   * @param {BrowsingContext} [aBrowsingContext] - Context of the load.
   * @param {bool} [aTriggeredExternally] - Whether the load came from outside
   * this application.
   */
  async handleURI(
    aHandler,
    aURI,
    aPrincipal,
    aBrowsingContext,
    aTriggeredExternally = false
  ) {
    let callerHasPermission = this._hasProtocolHandlerPermission(
      aHandler.type,
      aPrincipal,
      aTriggeredExternally
    );

    // Force showing the dialog for links passed from outside the application.
    // This avoids infinite loops, see bug 1678255, bug 1667468, etc.
    if (
      aTriggeredExternally &&
      gPrefs.promptForExternal &&
      // ... unless we intend to open the link with a website or extension:
      !(
        aHandler.preferredAction == Ci.nsIHandlerInfo.useHelperApp &&
        aHandler.preferredApplicationHandler instanceof Ci.nsIWebHandlerApp
      )
    ) {
      aHandler.alwaysAskBeforeHandling = true;
    }

    if ("mailto" === aURI.scheme) {
      Glean.protocolhandlerMailto.visit.record({
        triggered_externally: aTriggeredExternally,
      });
    }

    // Skip the dialog if a preferred application is set and the caller has
    // permission.
    if (
      callerHasPermission &&
      !aHandler.alwaysAskBeforeHandling &&
      (aHandler.preferredAction == Ci.nsIHandlerInfo.useHelperApp ||
        aHandler.preferredAction == Ci.nsIHandlerInfo.useSystemDefault)
    ) {
      try {
        aHandler.launchWithURI(aURI, aBrowsingContext);
        return;
      } catch (error) {
        // We are not supposed to ask, but when file not found the user most likely
        // uninstalled the application which handles the uri so we will continue
        // by application chooser dialog.
        if (error.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
          aHandler.alwaysAskBeforeHandling = true;
        } else {
          throw error;
        }
      }
    }

    let shouldOpenHandler = false;

    try {
      shouldOpenHandler = await this._prompt(
        aHandler,
        aPrincipal,
        callerHasPermission,
        aBrowsingContext,
        aURI
      );
    } catch (error) {
      console.error(error.message);
    }

    if (!shouldOpenHandler) {
      return;
    }

    // Site was granted permission and user chose to open application.
    // Launch the external handler.
    aHandler.launchWithURI(aURI, aBrowsingContext);
  }

  /**
   * Get the name of the application set to handle the the protocol.
   * @param {nsIHandlerInfo} aHandler - Info about protocol and handlers.
   * @returns {string|null} - Human readable handler name or null if the user
   * is expected to set a handler.
   */
  _getHandlerName(aHandler) {
    if (aHandler.alwaysAskBeforeHandling) {
      return null;
    }
    if (
      aHandler.preferredAction == Ci.nsIHandlerInfo.useSystemDefault &&
      aHandler.hasDefaultHandler
    ) {
      return aHandler.defaultDescription;
    }
    return aHandler.preferredApplicationHandler?.name;
  }

  /**
   * Show permission or/and app chooser prompt.
   * @param {nsIHandlerInfo} aHandler - Info about protocol and handlers.
   * @param {nsIPrincipal} aPrincipal - Principal which triggered the load.
   * @param {boolean} aHasPermission - Whether the caller has permission to
   * open the protocol.
   * @param {BrowsingContext} [aBrowsingContext] - Context associated with the
   * protocol navigation.
   */
  async _prompt(aHandler, aPrincipal, aHasPermission, aBrowsingContext, aURI) {
    let shouldOpenHandler = false;
    let resetHandlerChoice = false;
    let updateHandlerData = false;

    const isStandardProtocol = E10SUtils.STANDARD_SAFE_PROTOCOLS.includes(
      aURI.scheme
    );
    const {
      hasDefaultHandler,
      preferredApplicationHandler,
      alwaysAskBeforeHandling,
    } = aHandler;

    // This will skip the app chooser dialog flow unless the user explicitly opts to choose
    // another app in the permission dialog.
    if (
      !isStandardProtocol &&
      hasDefaultHandler &&
      preferredApplicationHandler == null &&
      alwaysAskBeforeHandling
    ) {
      aHandler.alwaysAskBeforeHandling = false;
      updateHandlerData = true;
    }

    // If caller does not have permission, prompt the user.
    if (!aHasPermission) {
      let canPersistPermission = this._isSupportedPrincipal(aPrincipal);

      let outArgs = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
        Ci.nsIWritablePropertyBag
      );
      // Whether the permission request was granted
      outArgs.setProperty("granted", false);
      // If the user wants to select a new application for the protocol.
      // This will cause us to show the chooser dialog, even if an app is set.
      outArgs.setProperty("resetHandlerChoice", null);
      // If the we should store the permission and not prompt again for it.
      outArgs.setProperty("remember", null);

      await this._openDialog(
        DIALOG_URL_PERMISSION,
        {
          handler: aHandler,
          principal: aPrincipal,
          browsingContext: aBrowsingContext,
          outArgs,
          canPersistPermission,
          preferredHandlerName: this._getHandlerName(aHandler),
        },
        aBrowsingContext
      );
      if (!outArgs.getProperty("granted")) {
        // User denied request
        return false;
      }

      // Check if user wants to set a new application to handle the protocol.
      resetHandlerChoice = outArgs.getProperty("resetHandlerChoice");

      // If the user wants to select a new app we don't persist the permission.
      if (!resetHandlerChoice && aPrincipal) {
        let remember = outArgs.getProperty("remember");
        this._updatePermission(aPrincipal, aHandler.type, remember);
      }

      shouldOpenHandler = true;
    }

    // Prompt if the user needs to make a handler choice for the protocol.
    if (aHandler.alwaysAskBeforeHandling || resetHandlerChoice) {
      // User has not set a preferred application to handle this protocol scheme.
      // Open the application chooser dialog
      let outArgs = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
        Ci.nsIWritablePropertyBag
      );
      outArgs.setProperty("openHandler", false);
      outArgs.setProperty("preferredAction", aHandler.preferredAction);
      outArgs.setProperty(
        "preferredApplicationHandler",
        aHandler.preferredApplicationHandler
      );
      outArgs.setProperty(
        "alwaysAskBeforeHandling",
        aHandler.alwaysAskBeforeHandling
      );
      let usePrivateBrowsing = aBrowsingContext?.usePrivateBrowsing;
      await this._openDialog(
        DIALOG_URL_APP_CHOOSER,
        {
          handler: aHandler,
          outArgs,
          usePrivateBrowsing,
          enableButtonDelay: aHasPermission,
        },
        aBrowsingContext
      );

      shouldOpenHandler = outArgs.getProperty("openHandler");

      // If the user accepted the dialog, apply their selection.
      if (shouldOpenHandler) {
        for (let prop of [
          "preferredAction",
          "preferredApplicationHandler",
          "alwaysAskBeforeHandling",
        ]) {
          aHandler[prop] = outArgs.getProperty(prop);
        }
        updateHandlerData = true;
      }
    }

    if (updateHandlerData) {
      // Store handler data
      Cc["@mozilla.org/uriloader/handler-service;1"]
        .getService(Ci.nsIHandlerService)
        .store(aHandler);
    }

    return shouldOpenHandler;
  }

  /**
   * Test if a given principal has the open-protocol-handler permission for a
   * specific protocol.
   * @param {string} scheme - Scheme of the protocol.
   * @param {nsIPrincipal} aPrincipal - Principal to test for permission.
   * @returns {boolean} - true if permission is set, false otherwise.
   */
  _hasProtocolHandlerPermission(scheme, aPrincipal, aTriggeredExternally) {
    // Permission disabled by pref
    if (!nsContentDispatchChooser.isPermissionEnabled) {
      return true;
    }

    // If a handler is set to open externally by default we skip the dialog.
    if (
      Services.prefs.getBoolPref(
        "network.protocol-handler.external." + scheme,
        false
      )
    ) {
      return true;
    }

    if (
      !aPrincipal ||
      (aPrincipal.isSystemPrincipal && !aTriggeredExternally)
    ) {
      return false;
    }

    let key = this._getSkipProtoDialogPermissionKey(scheme);
    return (
      Services.perms.testPermissionFromPrincipal(aPrincipal, key) ===
      Services.perms.ALLOW_ACTION
    );
  }

  /**
   * Get open-protocol-handler permission key for a protocol.
   * @param {string} aProtocolScheme - Scheme of the protocol.
   * @returns {string} - Permission key.
   */
  _getSkipProtoDialogPermissionKey(aProtocolScheme) {
    return (
      PROTOCOL_HANDLER_OPEN_PERM_KEY +
      PERMISSION_KEY_DELIMITER +
      aProtocolScheme
    );
  }

  /**
   * Opens a dialog as a SubDialog on tab level.
   * If we don't have a BrowsingContext or tab level dialogs are not supported,
   * we will fallback to a standalone window.
   * @param {string} aDialogURL - URL of the dialog to open.
   * @param {Object} aDialogArgs - Arguments passed to the dialog.
   * @param {BrowsingContext} [aBrowsingContext] - BrowsingContext associated
   * with the tab the dialog is associated with.
   */
  async _openDialog(aDialogURL, aDialogArgs, aBrowsingContext) {
    // Make the app chooser dialog resizable
    let resizable = `resizable=${
      aDialogURL == DIALOG_URL_APP_CHOOSER ? "yes" : "no"
    }`;

    if (aBrowsingContext) {
      let window = aBrowsingContext.topChromeWindow;
      if (!window) {
        throw new Error(
          "Can't show external protocol dialog. BrowsingContext has no chrome window associated."
        );
      }

      let { topFrameElement } = aBrowsingContext;
      if (topFrameElement?.tagName != "browser") {
        throw new Error(
          "Can't show external protocol dialog. BrowsingContext has no browser associated."
        );
      }

      // If the app does not support window.gBrowser or getTabDialogBox(),
      // fallback to the standalone application chooser window.
      let getTabDialogBox = window.gBrowser?.getTabDialogBox;
      if (getTabDialogBox) {
        return getTabDialogBox(topFrameElement).open(
          aDialogURL,
          {
            features: resizable,
            allowDuplicateDialogs: false,
            keepOpenSameOriginNav: true,
          },
          aDialogArgs
        ).closedPromise;
      }
    }

    // If we don't have a BrowsingContext, we need to show a standalone window.
    let win = Services.ww.openWindow(
      null,
      aDialogURL,
      null,
      `chrome,dialog=yes,centerscreen,${resizable}`,
      aDialogArgs
    );

    // Wait until window is closed.
    return new Promise(resolve => {
      win.addEventListener("unload", function onUnload(event) {
        if (event.target.location != aDialogURL) {
          return;
        }
        win.removeEventListener("unload", onUnload);
        resolve();
      });
    });
  }

  /**
   * Update the open-protocol-handler permission for the site which triggered
   * the dialog. Sites with this permission may skip this dialog.
   * @param {nsIPrincipal} aPrincipal - subject to update the permission for.
   * @param {string} aScheme - Scheme of protocol to allow.
   * @param {boolean} aAllow - Whether to set / unset the permission.
   */
  _updatePermission(aPrincipal, aScheme, aAllow) {
    // If enabled, store open-protocol-handler permission for content principals.
    if (
      !nsContentDispatchChooser.isPermissionEnabled ||
      aPrincipal.isSystemPrincipal ||
      !this._isSupportedPrincipal(aPrincipal)
    ) {
      return;
    }

    let principal = aPrincipal;

    // If this action was triggered by an extension content script then set the
    // permission on the extension's principal.
    let addonPolicy = aPrincipal.contentScriptAddonPolicy;
    if (addonPolicy) {
      principal = Services.scriptSecurityManager.principalWithOA(
        addonPolicy.extension.principal,
        principal.originAttributes
      );
    }

    let permKey = this._getSkipProtoDialogPermissionKey(aScheme);
    if (aAllow) {
      Services.perms.addFromPrincipal(
        principal,
        permKey,
        Services.perms.ALLOW_ACTION,
        Services.perms.EXPIRE_NEVER
      );
    } else {
      Services.perms.removeFromPrincipal(principal, permKey);
    }
  }

  /**
   * Determine if we can use a principal to store permissions.
   * @param {nsIPrincipal} aPrincipal - Principal to test.
   * @returns {boolean} - true if we can store permissions, false otherwise.
   */
  _isSupportedPrincipal(aPrincipal) {
    if (!aPrincipal) {
      return false;
    }

    // If this is an add-on content script then we will be able to store
    // permissions against the add-on's principal.
    if (aPrincipal.contentScriptAddonPolicy) {
      return true;
    }

    return ["http", "https", "moz-extension", "file"].some(scheme =>
      aPrincipal.schemeIs(scheme)
    );
  }
}

nsContentDispatchChooser.prototype.classID = Components.ID(
  "e35d5067-95bc-4029-8432-e8f1e431148d"
);
nsContentDispatchChooser.prototype.QueryInterface = ChromeUtils.generateQI([
  "nsIContentDispatchChooser",
]);

XPCOMUtils.defineLazyPreferenceGetter(
  nsContentDispatchChooser,
  "isPermissionEnabled",
  "security.external_protocol_requires_permission",
  true
);
PK
!<1Z`¥¥#modules/ContentPrefService2.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import {
  ContentPref,
  cbHandleCompletion,
  cbHandleError,
  cbHandleResult,
} from "resource://gre/modules/ContentPrefUtils.sys.mjs";

import { ContentPrefStore } from "resource://gre/modules/ContentPrefStore.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
});

const CACHE_MAX_GROUP_ENTRIES = 100;

const GROUP_CLAUSE = `
  SELECT id
  FROM groups
  WHERE name = :group OR
        (:includeSubdomains AND name LIKE :pattern ESCAPE '/')
`;

export function ContentPrefService2() {
  if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
    return ChromeUtils.importESModule(
      "resource://gre/modules/ContentPrefServiceChild.sys.mjs"
    ).ContentPrefServiceChild;
  }

  Services.obs.addObserver(this, "last-pb-context-exited");

  // Observe shutdown so we can shut down the database connection.
  Services.obs.addObserver(this, "profile-before-change");
}

const cache = new ContentPrefStore();
cache.set = function CPS_cache_set() {
  Object.getPrototypeOf(this).set.apply(this, arguments);
  let groupCount = this._groups.size;
  if (groupCount >= CACHE_MAX_GROUP_ENTRIES) {
    // Clean half of the entries
    for (let [group, name] of this) {
      this.remove(group, name);
      groupCount--;
      if (groupCount < CACHE_MAX_GROUP_ENTRIES / 2) {
        break;
      }
    }
  }
};

const privModeStorage = new ContentPrefStore();

function executeStatementsInTransaction(conn, stmts) {
  return conn.executeTransaction(async () => {
    let rows = [];
    for (let { sql, params, cachable } of stmts) {
      let execute = cachable ? conn.executeCached : conn.execute;
      let stmtRows = await execute.call(conn, sql, params);
      rows = rows.concat(stmtRows);
    }
    return rows;
  });
}

/**
 * Helper function to extract a non-empty group from a URI.
 * @param {nsIURI} uri The URI to extract from.
 * @returns {string} a non-empty group.
 * @throws if a non-empty group cannot be extracted.
 */
function nonEmptyGroupFromURI(uri) {
  if (uri.schemeIs("blob")) {
    // blob: URLs are generated internally for a specific browser instance,
    // thus storing settings for them would be pointless. Though in most cases
    // it's possible to extract an origin from them and use it as the group.
    let embeddedURL = new URL(URL.fromURI(uri).origin);
    if (/^https?:$/.test(embeddedURL.protocol)) {
      return embeddedURL.host;
    }
    if (embeddedURL.origin) {
      // Keep the protocol if it's not http(s), to avoid mixing up settings
      // for different protocols, e.g. resource://moz.com and https://moz.com.
      return embeddedURL.origin;
    }
  }
  if (uri.host) {
    // Accessing the host property of the URI will throw an exception
    // if the URI is of a type that doesn't have a host property.
    // Otherwise, we manually throw an exception if the host is empty,
    // since the effect is the same (we can't derive a group from it).
    return uri.host;
  }
  // If reach this point, we'd have an empty group.
  throw new Error(`Can't derive non-empty CPS group from ${uri.spec}`);
}

function HostnameGrouper_group(aURI) {
  try {
    return nonEmptyGroupFromURI(aURI);
  } catch (ex) {
    // If we don't have a host, then use the entire URI (minus the query,
    // reference, and hash, if possible) as the group.  This means that URIs
    // like about:mozilla and about:blank will be considered separate groups,
    // but at least they'll be grouped somehow.

    // This also means that each individual file: URL will be considered
    // its own group.  This seems suboptimal, but so does treating the entire
    // file: URL space as a single group (especially if folks start setting
    // group-specific capabilities prefs).

    // XXX Is there something better we can do here?

    try {
      return aURI.prePath + aURI.filePath;
    } catch (ex) {
      return aURI.spec;
    }
  }
}

ContentPrefService2.prototype = {
  // XPCOM Plumbing

  classID: Components.ID("{e3f772f3-023f-4b32-b074-36cf0fd5d414}"),

  // Destruction

  _destroy() {
    Services.obs.removeObserver(this, "profile-before-change");
    Services.obs.removeObserver(this, "last-pb-context-exited");

    // Delete references to XPCOM components to make sure we don't leak them
    // (although we haven't observed leakage in tests).  Also delete references
    // in _observers and _genericObservers to avoid cycles with those that
    // refer to us and don't remove themselves from those observer pools.
    delete this._observers;
    delete this._genericObservers;
  },

  // in-memory cache and private-browsing stores

  _cache: cache,
  _pbStore: privModeStorage,

  _connPromise: null,

  get conn() {
    if (this._connPromise) {
      return this._connPromise;
    }

    return (this._connPromise = (async () => {
      let conn;
      try {
        conn = await this._getConnection();
      } catch (e) {
        this.log("Failed to establish database connection: " + e);
        throw e;
      }
      return conn;
    })());
  },

  // nsIContentPrefService

  getByName: function CPS2_getByName(name, context, callback) {
    checkNameArg(name);
    checkCallbackArg(callback, true);

    // Some prefs may be in both the database and the private browsing store.
    // Notify the caller of such prefs only once, using the values from private
    // browsing.
    let pbPrefs = new ContentPrefStore();
    if (context && context.usePrivateBrowsing) {
      for (let [sgroup, sname, val] of this._pbStore) {
        if (sname == name) {
          pbPrefs.set(sgroup, sname, val);
        }
      }
    }

    let stmt1 = this._stmt(`
      SELECT groups.name AS grp, prefs.value AS value
      FROM prefs
      JOIN settings ON settings.id = prefs.settingID
      JOIN groups ON groups.id = prefs.groupID
      WHERE settings.name = :name
    `);
    stmt1.params.name = name;

    let stmt2 = this._stmt(`
      SELECT NULL AS grp, prefs.value AS value
      FROM prefs
      JOIN settings ON settings.id = prefs.settingID
      WHERE settings.name = :name AND prefs.groupID ISNULL
    `);
    stmt2.params.name = name;

    this._execStmts([stmt1, stmt2], {
      onRow: row => {
        let grp = row.getResultByName("grp");
        let val = row.getResultByName("value");
        this._cache.set(grp, name, val);
        if (!pbPrefs.has(grp, name)) {
          cbHandleResult(callback, new ContentPref(grp, name, val));
        }
      },
      onDone: (reason, ok) => {
        if (ok) {
          for (let [pbGroup, pbName, pbVal] of pbPrefs) {
            cbHandleResult(callback, new ContentPref(pbGroup, pbName, pbVal));
          }
        }
        cbHandleCompletion(callback, reason);
      },
      onError: nsresult => {
        cbHandleError(callback, nsresult);
      },
    });
  },

  getByDomainAndName: function CPS2_getByDomainAndName(
    group,
    name,
    context,
    callback
  ) {
    checkGroupArg(group);
    this._get(group, name, false, context, callback);
  },

  getBySubdomainAndName: function CPS2_getBySubdomainAndName(
    group,
    name,
    context,
    callback
  ) {
    checkGroupArg(group);
    this._get(group, name, true, context, callback);
  },

  getGlobal: function CPS2_getGlobal(name, context, callback) {
    this._get(null, name, false, context, callback);
  },

  _get: function CPS2__get(group, name, includeSubdomains, context, callback) {
    group = this._parseGroup(group);
    checkNameArg(name);
    checkCallbackArg(callback, true);

    // Some prefs may be in both the database and the private browsing store.
    // Notify the caller of such prefs only once, using the values from private
    // browsing.
    let pbPrefs = new ContentPrefStore();
    if (context && context.usePrivateBrowsing) {
      for (let [sgroup, val] of this._pbStore.match(
        group,
        name,
        includeSubdomains
      )) {
        pbPrefs.set(sgroup, name, val);
      }
    }

    this._execStmts([this._commonGetStmt(group, name, includeSubdomains)], {
      onRow: row => {
        let grp = row.getResultByName("grp");
        let val = row.getResultByName("value");
        this._cache.set(grp, name, val);
        if (!pbPrefs.has(group, name)) {
          cbHandleResult(callback, new ContentPref(grp, name, val));
        }
      },
      onDone: (reason, ok, gotRow) => {
        if (ok) {
          if (!gotRow) {
            this._cache.set(group, name, undefined);
          }
          for (let [pbGroup, pbName, pbVal] of pbPrefs) {
            cbHandleResult(callback, new ContentPref(pbGroup, pbName, pbVal));
          }
        }
        cbHandleCompletion(callback, reason);
      },
      onError: nsresult => {
        cbHandleError(callback, nsresult);
      },
    });
  },

  _commonGetStmt: function CPS2__commonGetStmt(group, name, includeSubdomains) {
    let stmt = group
      ? this._stmtWithGroupClause(
          group,
          includeSubdomains,
          `
        SELECT groups.name AS grp, prefs.value AS value
        FROM prefs
        JOIN settings ON settings.id = prefs.settingID
        JOIN groups ON groups.id = prefs.groupID
        WHERE settings.name = :name AND prefs.groupID IN (${GROUP_CLAUSE})
      `
        )
      : this._stmt(`
        SELECT NULL AS grp, prefs.value AS value
        FROM prefs
        JOIN settings ON settings.id = prefs.settingID
        WHERE settings.name = :name AND prefs.groupID ISNULL
      `);
    stmt.params.name = name;
    return stmt;
  },

  _stmtWithGroupClause: function CPS2__stmtWithGroupClause(
    group,
    includeSubdomains,
    sql
  ) {
    let stmt = this._stmt(sql, false);
    stmt.params.group = group;
    stmt.params.includeSubdomains = includeSubdomains || false;
    stmt.params.pattern =
      "%." + (group == null ? null : group.replace(/\/|%|_/g, "/$&"));
    return stmt;
  },

  getCachedByDomainAndName: function CPS2_getCachedByDomainAndName(
    group,
    name,
    context
  ) {
    checkGroupArg(group);
    let prefs = this._getCached(group, name, false, context);
    return prefs[0] || null;
  },

  getCachedBySubdomainAndName: function CPS2_getCachedBySubdomainAndName(
    group,
    name,
    context
  ) {
    checkGroupArg(group);
    return this._getCached(group, name, true, context);
  },

  getCachedGlobal: function CPS2_getCachedGlobal(name, context) {
    let prefs = this._getCached(null, name, false, context);
    return prefs[0] || null;
  },

  _getCached: function CPS2__getCached(
    group,
    name,
    includeSubdomains,
    context
  ) {
    group = this._parseGroup(group);
    checkNameArg(name);

    let storesToCheck = [this._cache];
    if (context && context.usePrivateBrowsing) {
      storesToCheck.push(this._pbStore);
    }

    let outStore = new ContentPrefStore();
    storesToCheck.forEach(function (store) {
      for (let [sgroup, val] of store.match(group, name, includeSubdomains)) {
        outStore.set(sgroup, name, val);
      }
    });

    let prefs = [];
    for (let [sgroup, sname, val] of outStore) {
      prefs.push(new ContentPref(sgroup, sname, val));
    }
    return prefs;
  },

  set: function CPS2_set(group, name, value, context, callback) {
    checkGroupArg(group);
    this._set(group, name, value, context, callback);
  },

  setGlobal: function CPS2_setGlobal(name, value, context, callback) {
    this._set(null, name, value, context, callback);
  },

  _set: function CPS2__set(group, name, value, context, callback) {
    group = this._parseGroup(group);
    checkNameArg(name);
    checkValueArg(value);
    checkCallbackArg(callback, false);

    if (context && context.usePrivateBrowsing) {
      this._pbStore.set(group, name, value);
      this._schedule(function () {
        cbHandleCompletion(callback, Ci.nsIContentPrefCallback2.COMPLETE_OK);
        this._notifyPrefSet(group, name, value, context.usePrivateBrowsing);
      });
      return;
    }

    // Invalidate the cached value so consumers accessing the cache between now
    // and when the operation finishes don't get old data.
    this._cache.remove(group, name);

    let stmts = [];

    // Create the setting if it doesn't exist.
    let stmt = this._stmt(`
      INSERT OR IGNORE INTO settings (id, name)
      VALUES((SELECT id FROM settings WHERE name = :name), :name)
    `);
    stmt.params.name = name;
    stmts.push(stmt);

    // Create the group if it doesn't exist.
    if (group) {
      stmt = this._stmt(`
        INSERT OR IGNORE INTO groups (id, name)
        VALUES((SELECT id FROM groups WHERE name = :group), :group)
      `);
      stmt.params.group = group;
      stmts.push(stmt);
    }

    // Finally create or update the pref.
    if (group) {
      stmt = this._stmt(`
        INSERT OR REPLACE INTO prefs (id, groupID, settingID, value, timestamp)
        VALUES(
          (SELECT prefs.id
           FROM prefs
           JOIN groups ON groups.id = prefs.groupID
           JOIN settings ON settings.id = prefs.settingID
           WHERE groups.name = :group AND settings.name = :name),
          (SELECT id FROM groups WHERE name = :group),
          (SELECT id FROM settings WHERE name = :name),
          :value,
          :now
        )
      `);
      stmt.params.group = group;
    } else {
      stmt = this._stmt(`
        INSERT OR REPLACE INTO prefs (id, groupID, settingID, value, timestamp)
        VALUES(
          (SELECT prefs.id
           FROM prefs
           JOIN settings ON settings.id = prefs.settingID
           WHERE prefs.groupID IS NULL AND settings.name = :name),
          NULL,
          (SELECT id FROM settings WHERE name = :name),
          :value,
          :now
        )
      `);
    }
    stmt.params.name = name;
    stmt.params.value = value;
    stmt.params.now = Date.now() / 1000;
    stmts.push(stmt);

    this._execStmts(stmts, {
      onDone: (reason, ok) => {
        if (ok) {
          this._cache.setWithCast(group, name, value);
        }
        cbHandleCompletion(callback, reason);
        if (ok) {
          this._notifyPrefSet(
            group,
            name,
            value,
            context && context.usePrivateBrowsing
          );
        }
      },
      onError: nsresult => {
        cbHandleError(callback, nsresult);
      },
    });
  },

  removeByDomainAndName: function CPS2_removeByDomainAndName(
    group,
    name,
    context,
    callback
  ) {
    checkGroupArg(group);
    this._remove(group, name, false, context, callback);
  },

  removeBySubdomainAndName: function CPS2_removeBySubdomainAndName(
    group,
    name,
    context,
    callback
  ) {
    checkGroupArg(group);
    this._remove(group, name, true, context, callback);
  },

  removeGlobal: function CPS2_removeGlobal(name, context, callback) {
    this._remove(null, name, false, context, callback);
  },

  _remove: function CPS2__remove(
    group,
    name,
    includeSubdomains,
    context,
    callback
  ) {
    group = this._parseGroup(group);
    checkNameArg(name);
    checkCallbackArg(callback, false);

    // Invalidate the cached values so consumers accessing the cache between now
    // and when the operation finishes don't get old data.
    for (let sgroup of this._cache.matchGroups(group, includeSubdomains)) {
      this._cache.remove(sgroup, name);
    }

    let stmts = [];

    // First get the matching prefs.
    stmts.push(this._commonGetStmt(group, name, includeSubdomains));

    // Delete the matching prefs.
    let stmt = this._stmtWithGroupClause(
      group,
      includeSubdomains,
      `
      DELETE FROM prefs
      WHERE settingID = (SELECT id FROM settings WHERE name = :name) AND
            CASE typeof(:group)
            WHEN 'null' THEN prefs.groupID IS NULL
            ELSE prefs.groupID IN (${GROUP_CLAUSE})
            END
    `
    );
    stmt.params.name = name;
    stmts.push(stmt);

    stmts = stmts.concat(this._settingsAndGroupsCleanupStmts());

    let prefs = new ContentPrefStore();

    let isPrivate = context && context.usePrivateBrowsing;
    this._execStmts(stmts, {
      onRow: row => {
        let grp = row.getResultByName("grp");
        prefs.set(grp, name, undefined);
        this._cache.set(grp, name, undefined);
      },
      onDone: (reason, ok) => {
        if (ok) {
          this._cache.set(group, name, undefined);
          if (isPrivate) {
            for (let [sgroup] of this._pbStore.match(
              group,
              name,
              includeSubdomains
            )) {
              prefs.set(sgroup, name, undefined);
              this._pbStore.remove(sgroup, name);
            }
          }
        }
        cbHandleCompletion(callback, reason);
        if (ok) {
          for (let [sgroup, ,] of prefs) {
            this._notifyPrefRemoved(sgroup, name, isPrivate);
          }
        }
      },
      onError: nsresult => {
        cbHandleError(callback, nsresult);
      },
    });
  },

  // Deletes settings and groups that are no longer used.
  _settingsAndGroupsCleanupStmts() {
    // The NOTNULL term in the subquery of the second statment is needed because of
    // SQLite's weird IN behavior vis-a-vis NULLs.  See http://sqlite.org/lang_expr.html.
    return [
      this._stmt(`
        DELETE FROM settings
        WHERE id NOT IN (SELECT DISTINCT settingID FROM prefs)
      `),
      this._stmt(`
        DELETE FROM groups WHERE id NOT IN (
          SELECT DISTINCT groupID FROM prefs WHERE groupID NOTNULL
        )
      `),
    ];
  },

  removeByDomain: function CPS2_removeByDomain(group, context, callback) {
    checkGroupArg(group);
    this._removeByDomain(group, false, context, callback);
  },

  removeBySubdomain: function CPS2_removeBySubdomain(group, context, callback) {
    checkGroupArg(group);
    this._removeByDomain(group, true, context, callback);
  },

  removeAllGlobals: function CPS2_removeAllGlobals(context, callback) {
    this._removeByDomain(null, false, context, callback);
  },

  _removeByDomain: function CPS2__removeByDomain(
    group,
    includeSubdomains,
    context,
    callback
  ) {
    group = this._parseGroup(group);
    checkCallbackArg(callback, false);

    // Invalidate the cached values so consumers accessing the cache between now
    // and when the operation finishes don't get old data.
    for (let sgroup of this._cache.matchGroups(group, includeSubdomains)) {
      this._cache.removeGroup(sgroup);
    }

    let stmts = [];

    // First get the matching prefs, then delete groups and prefs that reference
    // deleted groups.
    if (group) {
      stmts.push(
        this._stmtWithGroupClause(
          group,
          includeSubdomains,
          `
        SELECT groups.name AS grp, settings.name AS name
        FROM prefs
        JOIN settings ON settings.id = prefs.settingID
        JOIN groups ON groups.id = prefs.groupID
        WHERE prefs.groupID IN (${GROUP_CLAUSE})
      `
        )
      );
      stmts.push(
        this._stmtWithGroupClause(
          group,
          includeSubdomains,
          `DELETE FROM groups WHERE id IN (${GROUP_CLAUSE})`
        )
      );
      stmts.push(
        this._stmt(`
        DELETE FROM prefs
        WHERE groupID NOTNULL AND groupID NOT IN (SELECT id FROM groups)
      `)
      );
    } else {
      stmts.push(
        this._stmt(`
        SELECT NULL AS grp, settings.name AS name
        FROM prefs
        JOIN settings ON settings.id = prefs.settingID
        WHERE prefs.groupID IS NULL
      `)
      );
      stmts.push(this._stmt("DELETE FROM prefs WHERE groupID IS NULL"));
    }

    // Finally delete settings that are no longer referenced.
    stmts.push(
      this._stmt(`
      DELETE FROM settings
      WHERE id NOT IN (SELECT DISTINCT settingID FROM prefs)
    `)
    );

    let prefs = new ContentPrefStore();

    let isPrivate = context && context.usePrivateBrowsing;
    this._execStmts(stmts, {
      onRow: row => {
        let grp = row.getResultByName("grp");
        let name = row.getResultByName("name");
        prefs.set(grp, name, undefined);
        this._cache.set(grp, name, undefined);
      },
      onDone: (reason, ok) => {
        if (ok && isPrivate) {
          for (let [sgroup, sname] of this._pbStore) {
            if (
              !group ||
              (!includeSubdomains && group == sgroup) ||
              (includeSubdomains &&
                sgroup &&
                this._pbStore.groupsMatchIncludingSubdomains(group, sgroup))
            ) {
              prefs.set(sgroup, sname, undefined);
              this._pbStore.remove(sgroup, sname);
            }
          }
        }
        cbHandleCompletion(callback, reason);
        if (ok) {
          for (let [sgroup, sname] of prefs) {
            this._notifyPrefRemoved(sgroup, sname, isPrivate);
          }
        }
      },
      onError: nsresult => {
        cbHandleError(callback, nsresult);
      },
    });
  },

  _removeAllDomainsSince: function CPS2__removeAllDomainsSince(
    since,
    context,
    callback
  ) {
    checkCallbackArg(callback, false);

    since /= 1000;

    // Invalidate the cached values so consumers accessing the cache between now
    // and when the operation finishes don't get old data.
    // Invalidate all the group cache because we don't know which groups will be removed.
    this._cache.removeAllGroups();

    let stmts = [];

    // Get prefs that are about to be removed to notify about their removal.
    let stmt = this._stmt(`
      SELECT groups.name AS grp, settings.name AS name
      FROM prefs
      JOIN settings ON settings.id = prefs.settingID
      JOIN groups ON groups.id = prefs.groupID
      WHERE timestamp >= :since
    `);
    stmt.params.since = since;
    stmts.push(stmt);

    // Do the actual remove.
    stmt = this._stmt(`
      DELETE FROM prefs WHERE groupID NOTNULL AND timestamp >= :since
    `);
    stmt.params.since = since;
    stmts.push(stmt);

    // Cleanup no longer used values.
    stmts = stmts.concat(this._settingsAndGroupsCleanupStmts());

    let prefs = new ContentPrefStore();
    let isPrivate = context && context.usePrivateBrowsing;
    this._execStmts(stmts, {
      onRow: row => {
        let grp = row.getResultByName("grp");
        let name = row.getResultByName("name");
        prefs.set(grp, name, undefined);
        this._cache.set(grp, name, undefined);
      },
      onDone: (reason, ok) => {
        // This nukes all the groups in _pbStore since we don't have their timestamp
        // information.
        if (ok && isPrivate) {
          for (let [sgroup, sname] of this._pbStore) {
            if (sgroup) {
              prefs.set(sgroup, sname, undefined);
            }
          }
          this._pbStore.removeAllGroups();
        }
        cbHandleCompletion(callback, reason);
        if (ok) {
          for (let [sgroup, sname] of prefs) {
            this._notifyPrefRemoved(sgroup, sname, isPrivate);
          }
        }
      },
      onError: nsresult => {
        cbHandleError(callback, nsresult);
      },
    });
  },

  removeAllDomainsSince: function CPS2_removeAllDomainsSince(
    since,
    context,
    callback
  ) {
    this._removeAllDomainsSince(since, context, callback);
  },

  removeAllDomains: function CPS2_removeAllDomains(context, callback) {
    this._removeAllDomainsSince(0, context, callback);
  },

  removeByName: function CPS2_removeByName(name, context, callback) {
    checkNameArg(name);
    checkCallbackArg(callback, false);

    // Invalidate the cached values so consumers accessing the cache between now
    // and when the operation finishes don't get old data.
    for (let [group, sname] of this._cache) {
      if (sname == name) {
        this._cache.remove(group, name);
      }
    }

    let stmts = [];

    // First get the matching prefs.  Include null if any of those prefs are
    // global.
    let stmt = this._stmt(`
      SELECT groups.name AS grp
      FROM prefs
      JOIN settings ON settings.id = prefs.settingID
      JOIN groups ON groups.id = prefs.groupID
      WHERE settings.name = :name
      UNION
      SELECT NULL AS grp
      WHERE EXISTS (
        SELECT prefs.id
        FROM prefs
        JOIN settings ON settings.id = prefs.settingID
        WHERE settings.name = :name AND prefs.groupID IS NULL
      )
    `);
    stmt.params.name = name;
    stmts.push(stmt);

    // Delete the target settings.
    stmt = this._stmt("DELETE FROM settings WHERE name = :name");
    stmt.params.name = name;
    stmts.push(stmt);

    // Delete prefs and groups that are no longer used.
    stmts.push(
      this._stmt(
        "DELETE FROM prefs WHERE settingID NOT IN (SELECT id FROM settings)"
      )
    );
    stmts.push(
      this._stmt(`
      DELETE FROM groups WHERE id NOT IN (
        SELECT DISTINCT groupID FROM prefs WHERE groupID NOTNULL
      )
    `)
    );

    let prefs = new ContentPrefStore();
    let isPrivate = context && context.usePrivateBrowsing;

    this._execStmts(stmts, {
      onRow: row => {
        let grp = row.getResultByName("grp");
        prefs.set(grp, name, undefined);
        this._cache.set(grp, name, undefined);
      },
      onDone: (reason, ok) => {
        if (ok && isPrivate) {
          for (let [sgroup, sname] of this._pbStore) {
            if (sname === name) {
              prefs.set(sgroup, name, undefined);
              this._pbStore.remove(sgroup, name);
            }
          }
        }
        cbHandleCompletion(callback, reason);
        if (ok) {
          for (let [sgroup, ,] of prefs) {
            this._notifyPrefRemoved(sgroup, name, isPrivate);
          }
        }
      },
      onError: nsresult => {
        cbHandleError(callback, nsresult);
      },
    });
  },

  /**
   * Returns the cached mozIStorageAsyncStatement for the given SQL.  If no such
   * statement is cached, one is created and cached.
   *
   * @param sql  The SQL query string.
   * @return     The cached, possibly new, statement.
   */
  _stmt: function CPS2__stmt(sql, cachable = true) {
    return {
      sql,
      cachable,
      params: {},
    };
  },

  /**
   * Executes some async statements.
   *
   * @param stmts      An array of mozIStorageAsyncStatements.
   * @param callbacks  An object with the following methods:
   *                   onRow(row) (optional)
   *                     Called once for each result row.
   *                     row: A mozIStorageRow.
   *                   onDone(reason, reasonOK, didGetRow) (required)
   *                     Called when done.
   *                     reason: A nsIContentPrefService2.COMPLETE_* value.
   *                     reasonOK: reason == nsIContentPrefService2.COMPLETE_OK.
   *                     didGetRow: True if onRow was ever called.
   *                   onError(nsresult) (optional)
   *                     Called on error.
   *                     nsresult: The error code.
   */
  _execStmts: async function CPS2__execStmts(stmts, callbacks) {
    let conn = await this.conn;
    let rows;
    let ok = true;
    try {
      rows = await executeStatementsInTransaction(conn, stmts);
    } catch (e) {
      ok = false;
      if (callbacks.onError) {
        try {
          callbacks.onError(e);
        } catch (e) {
          console.error(e);
        }
      } else {
        console.error(e);
      }
    }

    if (rows && callbacks.onRow) {
      for (let row of rows) {
        try {
          callbacks.onRow(row);
        } catch (e) {
          console.error(e);
        }
      }
    }

    try {
      callbacks.onDone(
        ok
          ? Ci.nsIContentPrefCallback2.COMPLETE_OK
          : Ci.nsIContentPrefCallback2.COMPLETE_ERROR,
        ok,
        rows && !!rows.length
      );
    } catch (e) {
      console.error(e);
    }
  },

  /**
   * Parses the domain (the "group", to use the database's term) from the given
   * string.
   *
   * @param groupStr  Assumed to be either a string or falsey.
   * @return          If groupStr is a valid URL string, returns the domain of
   *                  that URL.  If groupStr is some other nonempty string,
   *                  returns groupStr itself.  Otherwise returns null.
   *                  The return value is truncated at GROUP_NAME_MAX_LENGTH.
   */
  _parseGroup: function CPS2__parseGroup(groupStr) {
    if (!groupStr) {
      return null;
    }
    try {
      var groupURI = Services.io.newURI(groupStr);
      groupStr = HostnameGrouper_group(groupURI);
    } catch (err) {}
    return groupStr.substring(
      0,
      Ci.nsIContentPrefService2.GROUP_NAME_MAX_LENGTH - 1
    );
  },

  _schedule: function CPS2__schedule(fn) {
    Services.tm.dispatchToMainThread(fn.bind(this));
  },

  // A hash of arrays of observers, indexed by setting name.
  _observers: new Map(),

  // An array of generic observers, which observe all settings.
  _genericObservers: new Set(),

  addObserverForName(aName, aObserver) {
    let observers;
    if (aName) {
      observers = this._observers.get(aName);
      if (!observers) {
        observers = new Set();
        this._observers.set(aName, observers);
      }
    } else {
      observers = this._genericObservers;
    }

    observers.add(aObserver);
  },

  removeObserverForName(aName, aObserver) {
    let observers;
    if (aName) {
      observers = this._observers.get(aName);
      if (!observers) {
        return;
      }
    } else {
      observers = this._genericObservers;
    }

    observers.delete(aObserver);
  },

  /**
   * Construct a list of observers to notify about a change to some setting,
   * putting setting-specific observers before before generic ones, so observers
   * that initialize individual settings (like the page style controller)
   * execute before observers that display multiple settings and depend on them
   * being initialized first (like the content prefs sidebar).
   */
  _getObservers(aName) {
    let genericObserverList = Array.from(this._genericObservers);
    if (aName) {
      let observersForName = this._observers.get(aName);
      if (observersForName) {
        return Array.from(observersForName).concat(genericObserverList);
      }
    }
    return genericObserverList;
  },

  /**
   * Notify all observers about the removal of a preference.
   */
  _notifyPrefRemoved: function ContentPrefService__notifyPrefRemoved(
    aGroup,
    aName,
    aIsPrivate
  ) {
    for (var observer of this._getObservers(aName)) {
      try {
        observer.onContentPrefRemoved(aGroup, aName, aIsPrivate);
      } catch (ex) {
        console.error(ex);
      }
    }
  },

  /**
   * Notify all observers about a preference change.
   */
  _notifyPrefSet: function ContentPrefService__notifyPrefSet(
    aGroup,
    aName,
    aValue,
    aIsPrivate
  ) {
    for (var observer of this._getObservers(aName)) {
      try {
        observer.onContentPrefSet(aGroup, aName, aValue, aIsPrivate);
      } catch (ex) {
        console.error(ex);
      }
    }
  },

  extractDomain: function CPS2_extractDomain(str) {
    return this._parseGroup(str);
  },

  /**
   * Tests use this as a backchannel by calling it directly.
   *
   * @param subj   This value depends on topic.
   * @param topic  The backchannel "method" name.
   */
  observe: function CPS2_observe(subj, topic) {
    switch (topic) {
      case "profile-before-change":
        this._destroy();
        break;
      case "last-pb-context-exited":
        this._pbStore.removeAll();
        break;
      case "test:reset":
        let fn = subj.QueryInterface(Ci.xpcIJSWeakReference).get();
        this._reset(fn);
        break;
      case "test:db":
        let obj = subj.QueryInterface(Ci.xpcIJSWeakReference).get();
        obj.value = this.conn;
        break;
    }
  },

  /**
   * Removes all state from the service.  Used by tests.
   *
   * @param callback  A function that will be called when done.
   */
  async _reset(callback) {
    this._pbStore.removeAll();
    this._cache.removeAll();

    this._observers = new Map();
    this._genericObservers = new Set();

    let tables = ["prefs", "groups", "settings"];
    let stmts = tables.map(t => this._stmt(`DELETE FROM ${t}`));
    this._execStmts(stmts, {
      onDone: () => {
        callback();
      },
    });
  },

  QueryInterface: ChromeUtils.generateQI([
    "nsIContentPrefService2",
    "nsIObserver",
  ]),

  // Database Creation & Access

  _dbVersion: 6,

  _dbSchema: {
    tables: {
      groups:
        "id           INTEGER PRIMARY KEY, \
                   name         TEXT NOT NULL",

      settings:
        "id           INTEGER PRIMARY KEY, \
                   name         TEXT NOT NULL",

      prefs:
        "id           INTEGER PRIMARY KEY, \
                   groupID      INTEGER REFERENCES groups(id), \
                   settingID    INTEGER NOT NULL REFERENCES settings(id), \
                   value        BLOB, \
                   timestamp    INTEGER NOT NULL DEFAULT 0", // Storage in seconds, API in ms. 0 for migrated values.
    },
    indices: {
      groups_idx: {
        table: "groups",
        columns: ["name"],
      },
      settings_idx: {
        table: "settings",
        columns: ["name"],
      },
      prefs_idx: {
        table: "prefs",
        columns: ["timestamp", "groupID", "settingID"],
      },
    },
  },

  _debugLog: false,

  log: function CPS2_log(aMessage) {
    if (this._debugLog) {
      Services.console.logStringMessage("ContentPrefService2: " + aMessage);
    }
  },

  async _getConnection(aAttemptNum = 0) {
    if (
      Services.startup.isInOrBeyondShutdownPhase(
        Ci.nsIAppStartup.SHUTDOWN_PHASE_APPSHUTDOWN
      )
    ) {
      throw new Error("Can't open content prefs, we're in shutdown.");
    }
    let path = PathUtils.join(PathUtils.profileDir, "content-prefs.sqlite");
    let conn;
    let resetAndRetry = async e => {
      if (e.result != Cr.NS_ERROR_FILE_CORRUPTED) {
        throw e;
      }

      if (aAttemptNum >= this.MAX_ATTEMPTS) {
        if (conn) {
          await conn.close();
        }
        this.log("Establishing connection failed too many times. Giving up.");
        throw e;
      }

      try {
        await this._failover(conn, path);
      } catch (e) {
        console.error(e);
        throw e;
      }
      return this._getConnection(++aAttemptNum);
    };
    try {
      conn = await lazy.Sqlite.openConnection({
        path,
        incrementalVacuum: true,
        vacuumOnIdle: true,
      });
      try {
        lazy.Sqlite.shutdown.addBlocker(
          "Closing ContentPrefService2 connection.",
          () => conn.close()
        );
      } catch (ex) {
        // Uh oh, we failed to add a shutdown blocker. Close the connection
        // anyway, but make sure that doesn't throw.
        try {
          await conn?.close();
        } catch (ex) {
          console.error(ex);
        }
        return null;
      }
    } catch (e) {
      console.error(e);
      return resetAndRetry(e);
    }

    try {
      await this._dbMaybeInit(conn);
    } catch (e) {
      console.error(e);
      return resetAndRetry(e);
    }

    // Turn off disk synchronization checking to reduce disk churn and speed up
    // operations when prefs are changed rapidly (such as when a user repeatedly
    // changes the value of the browser zoom setting for a site).
    //
    // Note: this could cause database corruption if the OS crashes or machine
    // loses power before the data gets written to disk, but this is considered
    // a reasonable risk for the not-so-critical data stored in this database.
    await conn.execute("PRAGMA synchronous = OFF");

    return conn;
  },

  async _failover(aConn, aPath) {
    this.log("Cleaning up DB file - close & remove & backup.");
    if (aConn) {
      await aConn.close();
    }
    let uniquePath = await IOUtils.createUniqueFile(
      PathUtils.parent(aPath),
      PathUtils.filename(aPath) + ".corrupt",
      0o600
    );
    await IOUtils.copy(aPath, uniquePath);
    await IOUtils.remove(aPath);
    this.log("Completed DB cleanup.");
  },

  _dbMaybeInit: async function CPS2__dbMaybeInit(aConn) {
    let version = parseInt(await aConn.getSchemaVersion(), 10);
    this.log("Schema version: " + version);

    if (version == 0) {
      await this._dbCreateSchema(aConn);
    } else if (version != this._dbVersion) {
      await this._dbMigrate(aConn, version, this._dbVersion);
    }
  },

  _createTable: async function CPS2__createTable(aConn, aName) {
    let tSQL = this._dbSchema.tables[aName];
    this.log("Creating table " + aName + " with " + tSQL);
    await aConn.execute(`CREATE TABLE ${aName} (${tSQL})`);
  },

  _createIndex: async function CPS2__createTable(aConn, aName) {
    let index = this._dbSchema.indices[aName];
    let statement =
      "CREATE INDEX IF NOT EXISTS " +
      aName +
      " ON " +
      index.table +
      "(" +
      index.columns.join(", ") +
      ")";
    await aConn.execute(statement);
  },

  _dbCreateSchema: async function CPS2__dbCreateSchema(aConn) {
    await aConn.executeTransaction(async () => {
      this.log("Creating DB -- tables");
      for (let name in this._dbSchema.tables) {
        await this._createTable(aConn, name);
      }

      this.log("Creating DB -- indices");
      for (let name in this._dbSchema.indices) {
        await this._createIndex(aConn, name);
      }

      await aConn.setSchemaVersion(this._dbVersion);
    });
  },

  _dbMigrate: async function CPS2__dbMigrate(aConn, aOldVersion, aNewVersion) {
    /**
     * Migrations should follow the template rules in bug 1074817 comment 3 which are:
     * 1. Migration should be incremental and non-breaking.
     * 2. It should be idempotent because one can downgrade an upgrade again.
     * On downgrade:
     * 1. Decrement schema version so that upgrade runs the migrations again.
     */
    await aConn.executeTransaction(async () => {
      for (let i = aOldVersion; i < aNewVersion; i++) {
        let migrationName = "_dbMigrate" + i + "To" + (i + 1);
        if (typeof this[migrationName] != "function") {
          throw new Error(
            "no migrator function from version " +
              aOldVersion +
              " to version " +
              aNewVersion
          );
        }
        await this[migrationName](aConn);
      }
      await aConn.setSchemaVersion(aNewVersion);
    });
  },

  _dbMigrate1To2: async function CPS2___dbMigrate1To2(aConn) {
    await aConn.execute("ALTER TABLE groups RENAME TO groupsOld");
    await this._createTable(aConn, "groups");
    await aConn.execute(`
      INSERT INTO groups (id, name)
      SELECT id, name FROM groupsOld
    `);

    await aConn.execute("DROP TABLE groupers");
    await aConn.execute("DROP TABLE groupsOld");
  },

  _dbMigrate2To3: async function CPS2__dbMigrate2To3(aConn) {
    for (let name in this._dbSchema.indices) {
      await this._createIndex(aConn, name);
    }
  },

  _dbMigrate3To4: async function CPS2__dbMigrate3To4(aConn) {
    // Add timestamp column if it does not exist yet. This operation is idempotent.
    try {
      await aConn.execute("SELECT timestamp FROM prefs");
    } catch (e) {
      await aConn.execute(
        "ALTER TABLE prefs ADD COLUMN timestamp INTEGER NOT NULL DEFAULT 0"
      );
    }

    // To modify prefs_idx drop it and create again.
    await aConn.execute("DROP INDEX IF EXISTS prefs_idx");
    for (let name in this._dbSchema.indices) {
      await this._createIndex(aConn, name);
    }
  },

  async _dbMigrate4To5(conn) {
    // This is a data migration for browser.download.lastDir. While it may not
    // affect all consumers, it's simpler and safer to do it here than elsewhere.
    await conn.execute(`
      DELETE FROM prefs
      WHERE id IN (
        SELECT p.id FROM prefs p
        JOIN groups g ON g.id = p.groupID
        JOIN settings s ON s.id = p.settingID
        WHERE s.name = 'browser.download.lastDir'
          AND (
          (g.name BETWEEN 'data:' AND 'data:' || X'FFFF') OR
          (g.name BETWEEN 'file:' AND 'file:' || X'FFFF')
        )
      )
    `);
    await conn.execute(`
      DELETE FROM groups WHERE NOT EXISTS (
        SELECT 1 FROM prefs WHERE groupId = groups.id
      )
    `);
    // Trim group names longer than MAX_GROUP_LENGTH.
    await conn.execute(
      `
      UPDATE groups
      SET name = substr(name, 0, :maxlen)
      WHERE LENGTH(name) > :maxlen
      `,
      {
        maxlen: Ci.nsIContentPrefService2.GROUP_NAME_MAX_LENGTH,
      }
    );
  },

  async _dbMigrate5To6(conn) {
    // This is a data migration for blob: URIs, as we started storing their
    // origin when possible.
    // Rather than trying to migrate blob URIs to their origins, that would
    // require multiple steps for a tiny benefit (they never worked anyway),
    // just remove them, as they are one-time generated URLs unlikely to be
    // useful in the future. New inserted blobs will do the right thing.
    await conn.execute(`
      DELETE FROM prefs
      WHERE id IN (
        SELECT p.id FROM prefs p
        JOIN groups g ON g.id = p.groupID
        AND g.name BETWEEN 'blob:' AND 'blob:' || X'FFFF'
      )
    `);
    await conn.execute(`
      DELETE FROM groups WHERE NOT EXISTS (
        SELECT 1 FROM prefs WHERE groupId = groups.id
      )
    `);
  },
};

function checkGroupArg(group) {
  if (!group || typeof group != "string") {
    throw invalidArg("domain must be nonempty string.");
  }
}

function checkNameArg(name) {
  if (!name || typeof name != "string") {
    throw invalidArg("name must be nonempty string.");
  }
}

function checkValueArg(value) {
  if (value === undefined) {
    throw invalidArg("value must not be undefined.");
  }
}

function checkCallbackArg(callback, required) {
  if (callback && !(callback instanceof Ci.nsIContentPrefCallback2)) {
    throw invalidArg("callback must be an nsIContentPrefCallback2.");
  }
  if (!callback && required) {
    throw invalidArg("callback must be given.");
  }
}

function invalidArg(msg) {
  return Components.Exception(msg, Cr.NS_ERROR_INVALID_ARG);
}

// XPCOM Plumbing
PK
!<��Y���'modules/ContentPrefServiceChild.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import {
  ContentPref,
  _methodsCallableFromChild,
  cbHandleCompletion,
  cbHandleError,
  cbHandleResult,
  safeCallback,
} from "resource://gre/modules/ContentPrefUtils.sys.mjs";

// We only need one bit of information out of the context.
function contextArg(context) {
  return context && context.usePrivateBrowsing
    ? { usePrivateBrowsing: true }
    : null;
}

function NYI() {
  throw new Error("Do not add any new users of these functions");
}

function CallbackCaller(callback) {
  this._callback = callback;
}

CallbackCaller.prototype = {
  handleResult(contentPref) {
    cbHandleResult(
      this._callback,
      new ContentPref(contentPref.domain, contentPref.name, contentPref.value)
    );
  },

  handleError(result) {
    cbHandleError(this._callback, result);
  },

  handleCompletion(reason) {
    cbHandleCompletion(this._callback, reason);
  },
};

export class ContentPrefsChild extends JSProcessActorChild {
  constructor() {
    super();

    // Map from pref name -> set of observers
    this._observers = new Map();

    // Map from random ID string -> CallbackCaller, per request
    this._requests = new Map();
  }

  _getRandomId() {
    return Services.uuid.generateUUID().toString();
  }

  receiveMessage(msg) {
    let data = msg.data;
    let callback;
    switch (msg.name) {
      case "ContentPrefs:HandleResult":
        callback = this._requests.get(data.requestId);
        callback.handleResult(data.contentPref);
        break;

      case "ContentPrefs:HandleError":
        callback = this._requests.get(data.requestId);
        callback.handleError(data.error);
        break;

      case "ContentPrefs:HandleCompletion":
        callback = this._requests.get(data.requestId);
        this._requests.delete(data.requestId);
        callback.handleCompletion(data.reason);
        break;

      case "ContentPrefs:NotifyObservers": {
        let observerList = this._observers.get(data.name);
        if (!observerList) {
          break;
        }

        for (let observer of observerList) {
          safeCallback(observer, data.callback, data.args);
        }

        break;
      }
    }
  }

  callFunction(call, args, callback) {
    let requestId = this._getRandomId();
    let data = { call, args, requestId };

    this._requests.set(requestId, new CallbackCaller(callback));
    this.sendAsyncMessage("ContentPrefs:FunctionCall", data);
  }

  addObserverForName(name, observer) {
    let set = this._observers.get(name);
    if (!set) {
      set = new Set();

      // This is the first observer for this name. Start listening for changes
      // to it.
      this.sendAsyncMessage("ContentPrefs:AddObserverForName", {
        name,
      });
      this._observers.set(name, set);
    }

    set.add(observer);
  }

  removeObserverForName(name, observer) {
    let set = this._observers.get(name);
    if (!set) {
      return;
    }

    set.delete(observer);
    if (set.size === 0) {
      // This was the last observer for this name. Stop listening for changes.
      this.sendAsyncMessage("ContentPrefs:RemoveObserverForName", {
        name,
      });

      this._observers.delete(name);
    }
  }
}

export var ContentPrefServiceChild = {
  QueryInterface: ChromeUtils.generateQI(["nsIContentPrefService2"]),

  addObserverForName: (name, observer) => {
    ChromeUtils.domProcessChild
      .getActor("ContentPrefs")
      .addObserverForName(name, observer);
  },
  removeObserverForName: (name, observer) => {
    ChromeUtils.domProcessChild
      .getActor("ContentPrefs")
      .removeObserverForName(name, observer);
  },

  getCachedByDomainAndName: NYI,
  getCachedBySubdomainAndName: NYI,
  getCachedGlobal: NYI,
  extractDomain: NYI,
};

function forwardMethodToParent(method, signature, ...args) {
  // Ignore superfluous arguments
  args = args.slice(0, signature.length);

  // Process context argument for forwarding
  let contextIndex = signature.indexOf("context");
  if (contextIndex > -1) {
    args[contextIndex] = contextArg(args[contextIndex]);
  }
  // Take out the callback argument, if present.
  let callbackIndex = signature.indexOf("callback");
  let callback = null;
  if (callbackIndex > -1 && args.length > callbackIndex) {
    callback = args.splice(callbackIndex, 1)[0];
  }

  let actor = ChromeUtils.domProcessChild.getActor("ContentPrefs");
  actor.callFunction(method, args, callback);
}

for (let [method, signature] of _methodsCallableFromChild) {
  ContentPrefServiceChild[method] = forwardMethodToParent.bind(
    ContentPrefServiceChild,
    method,
    signature
  );
}
PK
!<�YS0�� modules/ContentPrefStore.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

export function ContentPrefStore() {
  this._groups = new Map();
  this._globalNames = new Map();
}

ContentPrefStore.prototype = {
  set: function CPS_set(group, name, val) {
    if (group) {
      if (!this._groups.has(group)) {
        this._groups.set(group, new Map());
      }
      this._groups.get(group).set(name, val);
    } else {
      this._globalNames.set(name, val);
    }
  },

  setWithCast: function CPS_setWithCast(group, name, val) {
    if (typeof val == "boolean") {
      val = val ? 1 : 0;
    } else if (val === undefined) {
      val = null;
    }
    this.set(group, name, val);
  },

  has: function CPS_has(group, name) {
    if (group) {
      return this._groups.has(group) && this._groups.get(group).has(name);
    }
    return this._globalNames.has(name);
  },

  get: function CPS_get(group, name) {
    if (group && this._groups.has(group)) {
      return this._groups.get(group).get(name);
    }
    return this._globalNames.get(name);
  },

  remove: function CPS_remove(group, name) {
    if (group) {
      if (this._groups.has(group)) {
        this._groups.get(group).delete(name);
        if (this._groups.get(group).size == 0) {
          this._groups.delete(group);
        }
      }
    } else {
      this._globalNames.delete(name);
    }
  },

  removeGroup: function CPS_removeGroup(group) {
    if (group) {
      this._groups.delete(group);
    } else {
      this._globalNames.clear();
    }
  },

  removeAllGroups: function CPS_removeAllGroups() {
    this._groups.clear();
  },

  removeAll: function CPS_removeAll() {
    this.removeAllGroups();
    this._globalNames.clear();
  },

  groupsMatchIncludingSubdomains: function CPS_groupsMatchIncludingSubdomains(
    group,
    group2
  ) {
    let idx = group2.indexOf(group);
    return (
      idx == group2.length - group.length &&
      (idx == 0 || group2[idx - 1] == ".")
    );
  },

  *[Symbol.iterator]() {
    for (let [group, names] of this._groups) {
      for (let [name, val] of names) {
        yield [group, name, val];
      }
    }
    for (let [name, val] of this._globalNames) {
      yield [null, name, val];
    }
  },

  *match(group, name, includeSubdomains) {
    for (let sgroup of this.matchGroups(group, includeSubdomains)) {
      if (this.has(sgroup, name)) {
        yield [sgroup, this.get(sgroup, name)];
      }
    }
  },

  *matchGroups(group, includeSubdomains) {
    if (group) {
      if (includeSubdomains) {
        for (let [sgroup, ,] of this) {
          if (sgroup) {
            if (this.groupsMatchIncludingSubdomains(group, sgroup)) {
              yield sgroup;
            }
          }
        }
      } else if (this._groups.has(group)) {
        yield group;
      }
    } else if (this._globalNames.size) {
      yield null;
    }
  },
};
PK
!<Y�#�� modules/ContentPrefUtils.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export function ContentPref(domain, name, value) {
  this.domain = domain;
  this.name = name;
  this.value = value;
}

ContentPref.prototype = {
  QueryInterface: ChromeUtils.generateQI(["nsIContentPref"]),
};

export function cbHandleResult(callback, pref) {
  safeCallback(callback, "handleResult", [pref]);
}

export function cbHandleCompletion(callback, reason) {
  safeCallback(callback, "handleCompletion", [reason]);
}

export function cbHandleError(callback, nsresult) {
  safeCallback(callback, "handleError", [nsresult]);
}

export function safeCallback(callbackObj, methodName, args) {
  if (!callbackObj || typeof callbackObj[methodName] != "function") {
    return;
  }
  try {
    callbackObj[methodName].apply(callbackObj, args);
  } catch (err) {
    console.error(err);
  }
}

export const _methodsCallableFromChild = Object.freeze([
  ["getByName", ["name", "context", "callback"]],
  ["getByDomainAndName", ["domain", "name", "context", "callback"]],
  ["getBySubdomainAndName", ["domain", "name", "context", "callback"]],
  ["getGlobal", ["name", "context", "callback"]],
  ["set", ["domain", "name", "value", "context", "callback"]],
  ["setGlobal", ["name", "value", "context", "callback"]],
  ["removeByDomainAndName", ["domain", "name", "context", "callback"]],
  ["removeBySubdomainAndName", ["domain", "name", "context", "callback"]],
  ["removeGlobal", ["name", "context", "callback"]],
  ["removeByDomain", ["domain", "context", "callback"]],
  ["removeBySubdomain", ["domain", "context", "callback"]],
  ["removeByName", ["name", "context", "callback"]],
  ["removeAllDomains", ["context", "callback"]],
  ["removeAllGlobals", ["context", "callback"]],
]);
PK
!<��@m�*�*'modules/CookieBannerListService.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

ChromeUtils.defineESModuleGetters(lazy, {
  JsonSchema: "resource://gre/modules/JsonSchema.sys.mjs",
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "DEFAULT_EXPIRY_RELATIVE",
  "cookiebanners.cookieInjector.defaultExpiryRelative"
);

const PREF_SKIP_REMOTE_SETTINGS =
  "cookiebanners.listService.testSkipRemoteSettings";
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "TEST_SKIP_REMOTE_SETTINGS",
  PREF_SKIP_REMOTE_SETTINGS
);

const PREF_TEST_RULES = "cookiebanners.listService.testRules";
XPCOMUtils.defineLazyPreferenceGetter(lazy, "testRulesPref", PREF_TEST_RULES);

// Name of the RemoteSettings collection containing the rules.
const COLLECTION_NAME = "cookie-banner-rules-list";

ChromeUtils.defineLazyGetter(lazy, "logConsole", () => {
  return console.createInstance({
    prefix: "CookieBannerListService",
    maxLogLevelPref: "cookiebanners.listService.logLevel",
  });
});

// Lazy getter for the JSON schema of cookie banner rules. It is used for
// validation of rules defined by pref.
ChromeUtils.defineLazyGetter(lazy, "CookieBannerRuleSchema", async () => {
  let response = await fetch(
    "chrome://global/content/cookiebanners/CookieBannerRule.schema.json"
  );
  if (!response.ok) {
    lazy.logConsole.error("Fetch for CookieBannerRuleSchema failed", response);
    throw new Error("Failed to fetch CookieBannerRuleSchema.");
  }
  return response.json();
});

/**
 * See nsICookieBannerListService
 */
export class CookieBannerListService {
  classId = Components.ID("{1d8d9470-97d3-4885-a108-44a5c4fb36e2}");
  QueryInterface = ChromeUtils.generateQI(["nsICookieBannerListService"]);

  // RemoteSettings collection holding the cookie banner rules.
  #rs = null;
  // Stores the this-wrapped on-sync callback so it can be unregistered on
  // shutdown.
  #onSyncCallback = null;

  constructor() {
    this.#rs = lazy.RemoteSettings(COLLECTION_NAME);
  }

  async init() {
    lazy.logConsole.debug("init");

    await this.importAllRules();

    // Register listener to import rules when test pref changes.
    Services.prefs.addObserver(PREF_TEST_RULES, this);
    Services.prefs.addObserver(PREF_SKIP_REMOTE_SETTINGS, this);

    // Register callback for collection changes.
    // Only register if not already registered.
    if (!this.#onSyncCallback) {
      this.#onSyncCallback = this.onSync.bind(this);
      this.#rs.on("sync", this.#onSyncCallback);
    }
  }

  initForTest() {
    return this.init();
  }

  async importAllRules() {
    lazy.logConsole.debug("importAllRules");

    try {
      let rules = await this.#rs.get();

      // While getting rules from RemoteSettings the enabled state of the
      // feature could have changed. Ensure the service is still enabled before
      // attempting to import rules.
      if (!Services.cookieBanners.isEnabled) {
        lazy.logConsole.warn("Skip import nsICookieBannerService is disabled");
        return;
      }
      if (!lazy.TEST_SKIP_REMOTE_SETTINGS) {
        this.#importRules(rules);
      }
    } catch (error) {
      lazy.logConsole.error(
        "Error while importing cookie banner rules from RemoteSettings",
        error
      );
    }

    // We import test rules, even if fetching rules from RemoteSettings failed.
    await this.#importTestRules();
  }

  shutdown() {
    lazy.logConsole.debug("shutdown");

    // Unregister callback for collection changes.
    if (this.#onSyncCallback) {
      this.#rs.off("sync", this.#onSyncCallback);
      this.#onSyncCallback = null;
    }

    Services.prefs.removeObserver(PREF_TEST_RULES, this);
    Services.prefs.removeObserver(PREF_SKIP_REMOTE_SETTINGS, this);
  }

  /**
   * Called for remote settings "sync" events.
   */
  onSync({ data: { created, updated, deleted } }) {
    if (lazy.TEST_SKIP_REMOTE_SETTINGS) {
      return;
    }
    lazy.logConsole.debug("onSync", { created, updated, deleted });

    // Remove deleted rules.
    this.#removeRules(deleted);

    // Import new rules and override updated rules.
    this.#importRules(created.concat(updated.map(u => u.new)));

    // Re-import test rules in case they need to overwrite existing rules or a test rule was deleted above.
    this.#importTestRules();
  }

  observe(subject, topic, prefName) {
    if (prefName != PREF_TEST_RULES && prefName != PREF_SKIP_REMOTE_SETTINGS) {
      return;
    }

    // When the test rules update we need to clear all rules and import them
    // again. This is required because we don't have a mechanism for deleting
    // specific test rules.
    // Also reimport when rule import from RemoteSettings is enabled / disabled.
    // Passing `doImport = false` since we trigger the import ourselves.
    Services.cookieBanners.resetRules(false);
    this.importAllRules();

    // Reset executed records (private and normal browsing) for easier testing
    // of rules.
    Services.cookieBanners.removeAllExecutedRecords(false);
    Services.cookieBanners.removeAllExecutedRecords(true);
  }

  #removeRules(rules = []) {
    lazy.logConsole.debug("removeRules", rules);

    // For each js rule, construct a temporary nsICookieBannerRule to pass into
    // Services.cookieBanners.removeRule. For removal only domain and id are
    // relevant.
    rules
      .map(({ id, domain, domains }) => {
        // Provide backwards-compatibility with single-domain rules.
        if (domain) {
          domains = [domain];
        }

        let rule = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
          Ci.nsICookieBannerRule
        );
        rule.id = id;
        rule.domains = domains;
        return rule;
      })
      .forEach(r => {
        Services.cookieBanners.removeRule(r);

        // Clear the fact if we have reported telemetry for the domain. So, we
        // can collect again with the updated rules.
        Services.cookieBanners.resetDomainTelemetryRecord(r.domain);
      });
  }

  #importRules(rules) {
    lazy.logConsole.debug("importRules", rules);

    rules.forEach(({ id, domain, domains, cookies, click }) => {
      // Provide backwards-compatibility with single-domain rules.
      if (domain) {
        domains = [domain];
      }

      let rule = Cc["@mozilla.org/cookie-banner-rule;1"].createInstance(
        Ci.nsICookieBannerRule
      );
      rule.id = id;
      rule.domains = domains;

      // Import the cookie rule.
      this.#importCookieRule(rule, cookies);

      // Import the click rule.
      this.#importClickRule(rule, click);

      Services.cookieBanners.insertRule(rule);

      // Clear the fact if we have reported telemetry for the domain. Note that
      // this function could handle rule update and the initial rule import. In
      // both cases, we should clear to make sure we will collect with the
      // latest rules.
      Services.cookieBanners.resetDomainTelemetryRecord(domain);
    });
  }

  async #importTestRules() {
    lazy.logConsole.debug("importTestRules");

    if (!Services.prefs.prefHasUserValue(PREF_TEST_RULES)) {
      lazy.logConsole.debug(
        "Skip importing test rules: Pref has default value."
      );
      return;
    }

    // Parse array of rules from pref value string as JSON.
    let testRules;
    try {
      testRules = JSON.parse(lazy.testRulesPref);
    } catch (error) {
      lazy.logConsole.error(
        `Failed to parse test rules JSON string. Make sure ${PREF_TEST_RULES} contains valid JSON. ${error?.name}`,
        error
      );
      return;
    }

    // Ensure we have an array we can iterate over and not an object.
    if (!Array.isArray(testRules)) {
      lazy.logConsole.error(
        "Failed to parse test rules JSON String: Not an array."
      );
      return;
    }

    // Validate individual array elements (rules) via the schema defined in
    // CookieBannerRule.schema.json.
    let schema = await lazy.CookieBannerRuleSchema;
    let validator = new lazy.JsonSchema.Validator(schema);
    let validatedTestRules = [];

    let i = 0;
    for (let rule of testRules) {
      let { valid, errors } = validator.validate(rule);

      if (!valid) {
        lazy.logConsole.error(
          `Skipping invalid test rule at index ${i}. Errors: ${JSON.stringify(
            errors,
            null,
            2
          )}`
        );
        lazy.logConsole.debug("Test rule validation error", rule, errors);

        i += 1;
        continue;
      }

      // Only import rules if they are valid.
      validatedTestRules.push(rule);
      i += 1;
    }

    this.#importRules(validatedTestRules);
  }

  #importCookieRule(rule, cookies) {
    // Skip rules that don't have cookies.
    if (!cookies) {
      return;
    }

    // Import opt-in and opt-out cookies if defined.
    for (let category of ["optOut", "optIn"]) {
      if (!cookies[category]) {
        continue;
      }

      let isOptOut = category == "optOut";

      for (let c of cookies[category]) {
        let { expiryRelative } = c;
        if (expiryRelative == null || expiryRelative <= 0) {
          expiryRelative = lazy.DEFAULT_EXPIRY_RELATIVE;
        }

        rule.addCookie(
          isOptOut,
          c.name,
          c.value,
          // The following fields are optional and may not be defined by the
          // rule.
          // If unset, host falls back to ".<domain>" internally.
          c.host,
          c.path || "/",
          expiryRelative,
          c.unsetValue,
          c.isSecure,
          c.isHTTPOnly,
          // Default injected cookies to session expiry.
          c.isSession ?? true,
          c.sameSite,
          c.schemeMap
        );
      }
    }
  }

  /**
   * Converts runContext string field to nsIClickRule::RunContext
   * @param {('top'|'child'|'all')} runContextStr - Run context as string.
   * @returns nsIClickRule::RunContext representation.
   */
  #runContextStrToNative(runContextStr) {
    let strToNative = {
      top: Ci.nsIClickRule.RUN_TOP,
      child: Ci.nsIClickRule.RUN_CHILD,
      all: Ci.nsIClickRule.RUN_ALL,
    };

    // Default to RUN_TOP;
    return strToNative[runContextStr] ?? Ci.nsIClickRule.RUN_TOP;
  }

  #importClickRule(rule, click) {
    // Skip importing the rule if there is no click object or the click rule is
    // empty - it doesn't have the mandatory presence attribute.
    if (!click || !click.presence) {
      return;
    }

    rule.addClickRule(
      click.presence,
      click.skipPresenceVisibilityCheck,
      this.#runContextStrToNative(click.runContext),
      click.hide,
      click.optOut,
      click.optIn
    );
  }
}
PK
!<�{�*

modules/CoveragePing.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Log } from "resource://gre/modules/Log.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  CommonUtils: "resource://services-common/utils.sys.mjs",
  ServiceRequest: "resource://gre/modules/ServiceRequest.sys.mjs",
  UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
});

const COVERAGE_VERSION = "2";

const COVERAGE_ENABLED_PREF = "toolkit.coverage.enabled";
const LOG_LEVEL_PREF = "toolkit.coverage.log-level";
const OPT_OUT_PREF = "toolkit.coverage.opt-out";
const ALREADY_RUN_PREF = `toolkit.coverage.already-run.v${COVERAGE_VERSION}`;
const COVERAGE_UUID_PREF = `toolkit.coverage.uuid.v${COVERAGE_VERSION}`;
const TELEMETRY_ENABLED_PREF = "datareporting.healthreport.uploadEnabled";
const REPORTING_ENDPOINT_BASE_PREF = `toolkit.coverage.endpoint.base`;
const REPORTING_ENDPOINT = "submit/coverage/coverage";
const PING_SUBMISSION_TIMEOUT = 30 * 1000; // 30 seconds

const log = Log.repository.getLogger("Telemetry::CoveragePing");
log.level = Services.prefs.getIntPref(LOG_LEVEL_PREF, Log.Level.Error);
log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));

export var CoveragePing = Object.freeze({
  async startup() {
    if (!Services.prefs.getBoolPref(COVERAGE_ENABLED_PREF, false)) {
      log.debug("coverage not enabled");
      return;
    }

    if (Services.prefs.getBoolPref(OPT_OUT_PREF, false)) {
      log.debug("user has set opt-out pref");
      return;
    }

    if (Services.prefs.getBoolPref(ALREADY_RUN_PREF, false)) {
      log.debug("already run on this profile");
      return;
    }

    if (!Services.prefs.getCharPref(REPORTING_ENDPOINT_BASE_PREF, null)) {
      log.error("no endpoint base set");
      return;
    }

    try {
      await this.reportTelemetrySetting();
    } catch (e) {
      log.error("unable to upload payload", e);
    }
  },

  // NOTE - this does not use existing Telemetry code or honor Telemetry opt-out prefs,
  // by design. It also sends no identifying data like the client ID. See the "coverage ping"
  // documentation for details.
  reportTelemetrySetting() {
    const enabled = Services.prefs.getBoolPref(TELEMETRY_ENABLED_PREF, false);

    const payload = {
      appVersion: Services.appinfo.version,
      appUpdateChannel: lazy.UpdateUtils.getUpdateChannel(false),
      osName: Services.sysinfo.getProperty("name"),
      osVersion: Services.sysinfo.getProperty("version"),
      telemetryEnabled: enabled,
    };

    let cachedUuid = Services.prefs.getCharPref(COVERAGE_UUID_PREF, null);
    if (!cachedUuid) {
      // Totally random UUID, just for detecting duplicates.
      cachedUuid = lazy.CommonUtils.generateUUID();
      Services.prefs.setCharPref(COVERAGE_UUID_PREF, cachedUuid);
    }

    let reportingEndpointBase = Services.prefs.getCharPref(
      REPORTING_ENDPOINT_BASE_PREF,
      null
    );

    let endpoint = `${reportingEndpointBase}/${REPORTING_ENDPOINT}/${COVERAGE_VERSION}/${cachedUuid}`;

    log.debug(`putting to endpoint ${endpoint} with payload:`, payload);

    let deferred = Promise.withResolvers();

    let request = new lazy.ServiceRequest({ mozAnon: true });
    request.mozBackgroundRequest = true;
    request.timeout = PING_SUBMISSION_TIMEOUT;

    request.open("PUT", endpoint, true);
    request.overrideMimeType("text/plain");
    request.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
    request.setRequestHeader("Date", new Date().toUTCString());

    let errorhandler = event => {
      let failure = event.type;
      log.error(`error making request to ${endpoint}: ${failure}`);
      deferred.reject(event);
    };

    request.onerror = errorhandler;
    request.ontimeout = errorhandler;
    request.onabort = errorhandler;

    request.onloadend = event => {
      let status = request.status;
      let statusClass = status - (status % 100);
      let success = false;

      if (statusClass === 200) {
        // We can treat all 2XX as success.
        log.info(`successfully submitted, status: ${status}`);
        success = true;
      } else if (statusClass === 400) {
        // 4XX means that something with the request was broken.

        // TODO: we should handle this better, but for now we should avoid resubmitting
        // broken requests by pretending success.
        success = true;
        log.error(
          `error submitting to ${endpoint}, status: ${status} - ping request broken?`
        );
      } else if (statusClass === 500) {
        // 5XX means there was a server-side error and we should try again later.
        log.error(
          `error submitting to ${endpoint}, status: ${status} - server error, should retry later`
        );
      } else {
        // We received an unexpected status code.
        log.error(
          `error submitting to ${endpoint}, status: ${status}, type: ${event.type}`
        );
      }

      if (success) {
        Services.prefs.setBoolPref(ALREADY_RUN_PREF, true);
        log.debug(`result from PUT: ${request.responseText}`);
        deferred.resolve();
      } else {
        deferred.reject(event);
      }
    };

    request.send(JSON.stringify(payload));

    return deferred.promise;
  },
});
PK
!<o���F�F�modules/CrashManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { setTimeout } from "resource://gre/modules/Timer.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "resource://gre/modules/Log.sys.mjs",
  TelemetryController: "resource://gre/modules/TelemetryController.sys.mjs",
});

/**
 * How long to wait after application startup before crash event files are
 * automatically aggregated.
 *
 * We defer aggregation for performance reasons, as we don't want too many
 * services competing for I/O immediately after startup.
 */
const AGGREGATE_STARTUP_DELAY_MS = 57000;

const MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;

// Converts Date to days since UNIX epoch.
// The implementation does not account for leap seconds.
export function dateToDays(date) {
  return Math.floor(date.getTime() / MILLISECONDS_IN_DAY);
}

/**
 * Get a field from the specified object and remove it.
 *
 * @param obj {Object} The object holding the field
 * @param field {String} The name of the field to be parsed and removed
 *
 * @returns {String} the field contents as a string, null if none was found
 */
function getAndRemoveField(obj, field) {
  let value = null;

  if (field in obj) {
    value = obj[field];
    delete obj[field];
  }

  return value;
}

/**
 * Parse the string stored in the specified field as JSON and then remove the
 * field from the object.
 *
 * @param obj {Object} The object holding the field
 * @param field {String} The name of the field to be parsed and removed
 *
 * @returns {Object} the parsed object, null if none was found
 */
function parseAndRemoveField(obj, field) {
  let value = null;

  if (field in obj) {
    try {
      value = JSON.parse(obj[field]);
    } catch (e) {
      console.error(e);
    }

    delete obj[field];
  }

  return value;
}

/**
 * Convert a legacy Telemetry `StackTraces` layout to that expected by Glean.
 *
 * @param stackTraces {Object} The legacy Telemetry StackTraces object.
 *
 * @returns {Object} The stack traces layout expected by the Glean crash.stackTraces metric.
 */
function stackTracesLegacyToGlean(stackTraces) {
  let ret = {};
  // Change "status" to "error", only populate if an error occurred.
  if ("status" in stackTraces && stackTraces.status !== "OK") {
    ret.error = stackTraces.status;
  }

  // Change "crash_info" to flattened individual fields.
  ret.crash_type = stackTraces.crash_info?.type;
  ret.crash_address = stackTraces.crash_info?.address;
  ret.crash_thread = stackTraces.crash_info?.crashing_thread;

  ret.main_module = stackTraces.main_module;

  // Rename modules[].{base_addr,end_addr}
  if ("modules" in stackTraces) {
    ret.modules = stackTraces.modules.map(module => ({
      base_address: module.base_addr,
      end_address: module.end_addr,
      code_id: module.code_id,
      debug_file: module.debug_file,
      debug_id: module.debug_id,
      filename: module.filename,
      version: module.version,
    }));
  }

  if ("threads" in stackTraces) {
    ret.threads = stackTraces.threads.map(thread => ({
      frames: thread.frames.map(frame => ({
        module_index: frame.module_index,
        ip: frame.ip,
        trust: frame.trust,
      })),
    }));
  }

  return ret;
}

/**
 * Convert a legacy Telemetry `AsyncShutdownTimeout` value to that expected by Glean.
 *
 * @param value {String} The legacy Telemetry value.
 *
 * @returns {Object} The object appropriate for being `.set()` on the Glean
 * crash.asyncShutdownTimeout metric.
 */
function asyncShutdownTimeoutLegacyToGlean(value) {
  let obj = JSON.parse(value);
  // The conditions object isn't a consistent shape, so we just store it as a serialized string.
  obj.conditions = JSON.stringify(obj.conditions);
  // Change camelCase to snake_case
  obj.broken_add_blockers = obj.brokenAddBlockers;
  delete obj.brokenAddBlockers;
  return obj;
}

/**
 * Convert a legacy Telemetry `QuotaManagerShutdownTimeout` value to that expected by Glean.
 *
 * @param value {String} The legacy Telemetry value.
 *
 * @returns {Array} The array appropriate for being `.set()` on the Glean
 * crash.quotaManagerShutdownTimeout metric.
 */
function quotaManagerShutdownTimeoutLegacyToGlean(value) {
  // The Glean metric is an array of the lines.
  return value.split("\n");
}

/**
 * A gateway to crash-related data.
 *
 * This type is generic and can be instantiated any number of times.
 * However, most applications will typically only have one instance
 * instantiated and that instance will point to profile and user appdata
 * directories.
 *
 * Instances are created by passing an object with properties.
 * Recognized properties are:
 *
 *   pendingDumpsDir (string) (required)
 *     Where dump files that haven't been uploaded are located.
 *
 *   submittedDumpsDir (string) (required)
 *     Where records of uploaded dumps are located.
 *
 *   eventsDirs (array)
 *     Directories (defined as strings) where events files are written. This
 *     instance will collects events from files in the directories specified.
 *
 *   storeDir (string)
 *     Directory we will use for our data store. This instance will write
 *     data files into the directory specified.
 *
 *   telemetryStoreSizeKey (string)
 *     Telemetry histogram to report store size under.
 */
export var CrashManager = function (options) {
  for (let k in options) {
    let value = options[k];

    switch (k) {
      case "pendingDumpsDir":
      case "submittedDumpsDir":
      case "eventsDirs":
      case "storeDir":
        let key = "_" + k;
        delete this[key];
        Object.defineProperty(this, key, { value });
        break;
      case "telemetryStoreSizeKey":
        this._telemetryStoreSizeKey = value;
        break;

      default:
        throw new Error("Unknown property in options: " + k);
    }
  }

  // Promise for in-progress aggregation operation. We store it on the
  // object so it can be returned for in-progress operations.
  this._aggregatePromise = null;

  // Map of crash ID / promise tuples used to track adding new crashes.
  this._crashPromises = new Map();

  // Promise for the crash ping used only for testing.
  this._pingPromise = null;

  // The CrashStore currently attached to this object.
  this._store = null;

  // A Task to retrieve the store. This is needed to avoid races when
  // _getStore() is called multiple times in a short interval.
  this._getStoreTask = null;

  // The timer controlling the expiration of the CrashStore instance.
  this._storeTimer = null;

  // This is a semaphore that prevents the store from being freed by our
  // timer-based resource freeing mechanism.
  this._storeProtectedCount = 0;
};

CrashManager.prototype = Object.freeze({
  // gen_CrashManager.py will input the proper process map informations.
  
  processTypes: {
    // A crash in the main process.
    0: "main",
    // A crash in the content process.
    2: "content",
    // A crash in the ipdlunittest process.
    3: "ipdlunittest",
    // A crash in the gmplugin process.
    4: "gmplugin",
    // A crash in the gpu process.
    5: "gpu",
    // A crash in the vr process.
    6: "vr",
    // A crash in the rdd process.
    7: "rdd",
    // A crash in the socket process.
    8: "socket",
    // A crash in the sandboxbroker process.
    9: "sandboxbroker",
    // A crash in the forkserver process.
    10: "forkserver",
    // A crash in the utility process.
    11: "utility",
  },

  // A real crash.
  CRASH_TYPE_CRASH: "crash",

  // A hang.
  CRASH_TYPE_HANG: "hang",

  // Submission result values.
  SUBMISSION_RESULT_OK: "ok",
  SUBMISSION_RESULT_FAILED: "failed",

  DUMP_REGEX:
    /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.dmp$/i,
  SUBMITTED_REGEX:
    /^bp-(?:hr-)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.txt$/i,
  ALL_REGEX: /^(.*)$/,

  // How long the store object should persist in memory before being
  // automatically garbage collected.
  STORE_EXPIRATION_MS: 60 * 1000,

  // Number of days after which a crash with no activity will get purged.
  PURGE_OLDER_THAN_DAYS: 180,

  // The following are return codes for individual event file processing.
  // File processed OK.
  EVENT_FILE_SUCCESS: "ok",
  // The event appears to be malformed.
  EVENT_FILE_ERROR_MALFORMED: "malformed",
  // The event is obsolete.
  EVENT_FILE_ERROR_OBSOLETE: "obsolete",
  // The type of event is unknown.
  EVENT_FILE_ERROR_UNKNOWN_EVENT: "unknown-event",

  _lazyGetDir(field, path, leaf) {
    delete this[field];
    let value = PathUtils.join(path, leaf);
    Object.defineProperty(this, field, { value });
    return value;
  },

  get _crDir() {
    return this._lazyGetDir(
      "_crDir",
      Services.dirsvc.get("UAppData", Ci.nsIFile).path,
      "Crash Reports"
    );
  },

  get _storeDir() {
    return this._lazyGetDir(
      "_storeDir",
      Services.dirsvc.get("ProfD", Ci.nsIFile).path,
      "crashes"
    );
  },

  get _pendingDumpsDir() {
    return this._lazyGetDir("_pendingDumpsDir", this._crDir, "pending");
  },

  get _submittedDumpsDir() {
    return this._lazyGetDir("_submittedDumpsDir", this._crDir, "submitted");
  },

  get _eventsDirs() {
    delete this._eventsDirs;
    let value = [
      PathUtils.join(this._crDir, "events"),
      PathUtils.join(this._storeDir, "events"),
    ];
    Object.defineProperty(this, "_eventsDirs", { value });
    return value;
  },

  /**
   * Obtain a list of all dumps pending upload.
   *
   * The returned value is a promise that resolves to an array of objects
   * on success. Each element in the array has the following properties:
   *
   *   id (string)
   *      The ID of the crash (a UUID).
   *
   *   path (string)
   *      The filename of the crash (<UUID.dmp>)
   *
   *   date (Date)
   *      When this dump was created
   *
   * The returned arry is sorted by the modified time of the file backing
   * the entry, oldest to newest.
   *
   * @return Promise<Array>
   */
  pendingDumps() {
    return this._getDirectoryEntries(this._pendingDumpsDir, this.DUMP_REGEX);
  },

  /**
   * Obtain a list of all dump files corresponding to submitted crashes.
   *
   * The returned value is a promise that resolves to an Array of
   * objects. Each object has the following properties:
   *
   *   path (string)
   *     The path of the file this entry comes from.
   *
   *   id (string)
   *     The crash UUID.
   *
   *   date (Date)
   *     The (estimated) date this crash was submitted.
   *
   * The returned array is sorted by the modified time of the file backing
   * the entry, oldest to newest.
   *
   * @return Promise<Array>
   */
  submittedDumps() {
    return this._getDirectoryEntries(
      this._submittedDumpsDir,
      this.SUBMITTED_REGEX
    );
  },

  /**
   * Aggregates "loose" events files into the unified "database."
   *
   * This function should be called periodically to collect metadata from
   * all events files into the central data store maintained by this manager.
   *
   * Once events have been stored in the backing store the corresponding
   * source files are deleted.
   *
   * Only one aggregation operation is allowed to occur at a time. If this
   * is called when an existing aggregation is in progress, the promise for
   * the original call will be returned.
   *
   * @return promise<int> The number of event files that were examined.
   */
  aggregateEventsFiles() {
    if (this._aggregatePromise) {
      return this._aggregatePromise;
    }

    return (this._aggregatePromise = (async () => {
      if (this._aggregatePromise) {
        return this._aggregatePromise;
      }

      try {
        let unprocessedFiles = await this._getUnprocessedEventsFiles();

        let deletePaths = [];
        let needsSave = false;

        this._storeProtectedCount++;
        for (let entry of unprocessedFiles) {
          try {
            let result = await this._processEventFile(entry);

            switch (result) {
              case this.EVENT_FILE_SUCCESS:
                needsSave = true;
              // Fall through.

              case this.EVENT_FILE_ERROR_MALFORMED:
              case this.EVENT_FILE_ERROR_OBSOLETE:
                deletePaths.push(entry.path);
                break;

              case this.EVENT_FILE_ERROR_UNKNOWN_EVENT:
                break;

              default:
                console.error(
                  "Unhandled crash event file return code. Please " +
                    "file a bug: ",
                  result
                );
            }
          } catch (ex) {
            if (DOMException.isInstance(ex)) {
              this._log.warn("I/O error reading " + entry.path, ex);
            } else {
              // We should never encounter an exception. This likely represents
              // a coding error because all errors should be detected and
              // converted to return codes.
              //
              // If we get here, report the error and delete the source file
              // so we don't see it again.
              console.error(
                "Exception when processing crash event file: " +
                  lazy.Log.exceptionStr(ex)
              );
              deletePaths.push(entry.path);
            }
          }
        }

        if (needsSave) {
          let store = await this._getStore();
          await store.save();
        }

        for (let path of deletePaths) {
          try {
            await IOUtils.remove(path);
          } catch (ex) {
            this._log.warn("Error removing event file (" + path + ")", ex);
          }
        }

        return unprocessedFiles.length;
      } finally {
        this._aggregatePromise = false;
        this._storeProtectedCount--;
      }
    })());
  },

  /**
   * Prune old crash data.
   *
   * @param date
   *        (Date) The cutoff point for pruning. Crashes without data newer
   *        than this will be pruned.
   */
  pruneOldCrashes(date) {
    return (async () => {
      let store = await this._getStore();
      store.pruneOldCrashes(date);
      await store.save();
    })();
  },

  /**
   * Run tasks that should be periodically performed.
   */
  runMaintenanceTasks() {
    return (async () => {
      await this.aggregateEventsFiles();

      let offset = this.PURGE_OLDER_THAN_DAYS * MILLISECONDS_IN_DAY;
      await this.pruneOldCrashes(new Date(Date.now() - offset));
    })();
  },

  /**
   * Schedule maintenance tasks for some point in the future.
   *
   * @param delay
   *        (integer) Delay in milliseconds when maintenance should occur.
   */
  scheduleMaintenance(delay) {
    let deferred = Promise.withResolvers();

    setTimeout(() => {
      this.runMaintenanceTasks().then(deferred.resolve, deferred.reject);
    }, delay);

    return deferred.promise;
  },

  /**
   * Record the occurrence of a crash.
   *
   * This method skips event files altogether and writes directly and
   * immediately to the manager's data store.
   *
   * @param processType (string) One of the PROCESS_TYPE constants.
   * @param crashType (string) One of the CRASH_TYPE constants.
   * @param id (string) Crash ID. Likely a UUID.
   * @param date (Date) When the crash occurred.
   * @param metadata (dictionary) Crash metadata, may be empty.
   *
   * @return promise<null> Resolved when the store has been saved.
   */
  addCrash(processType, crashType, id, date, metadata) {
    let promise = (async () => {
      if (!this.isValidProcessType(processType)) {
        console.error(
          "Unhandled process type. Please file a bug: '",
          processType,
          "'. Ignore in the context of " +
            "test_crash_manager.js:test_addCrashWrong()."
        );
        return;
      }

      let store = await this._getStore();
      if (store.addCrash(processType, crashType, id, date, metadata)) {
        await store.save();
      }

      let deferred = this._crashPromises.get(id);

      if (deferred) {
        this._crashPromises.delete(id);
        deferred.resolve();
      }

      if (this.isPingAllowed(processType)) {
        this._sendCrashPing("crash", id, processType, date, metadata);
      }
    })();

    return promise;
  },

  /**
   * Check that the processType parameter is a valid one:
   *  - it is a string
   *  - it is listed in this.processTypes
   *
   * @param processType (string) Process type to evaluate
   *
   * @return boolean True or false depending whether it is a legit one
   */
  isValidProcessType(processType) {
    if (typeof processType !== "string") {
      return false;
    }

    for (const pt of Object.values(this.processTypes)) {
      if (pt === processType) {
        return true;
      }
    }

    return false;
  },

  /**
   * Check that processType is allowed to send a ping
   *
   * @param processType (string) Process type to check for
   *
   * @return boolean True or False depending on whether ping is allowed
   **/
  isPingAllowed(processType) {
    // gen_CrashManager.py will input the proper process pings informations.

    let processPings = {
      
      "main": true,
      "content": true,
      "ipdlunittest": false,
      "gmplugin": true,
      "gpu": true,
      "vr": true,
      "rdd": true,
      "socket": true,
      "sandboxbroker": true,
      "forkserver": true,
      "utility": true,
    };

    // Should not even reach this because of isValidProcessType() but just in
    // case we try to be cautious
    if (!(processType in processPings)) {
      return false;
    }

    return processPings[processType];
  },

  /**
   * Returns a promise that is resolved only the crash with the specified id
   * has been fully recorded.
   *
   * @param id (string) Crash ID. Likely a UUID.
   *
   * @return promise<null> Resolved when the crash is present.
   */
  async ensureCrashIsPresent(id) {
    let store = await this._getStore();
    let crash = store.getCrash(id);

    if (crash) {
      return Promise.resolve();
    }

    let deferred = Promise.withResolvers();

    this._crashPromises.set(id, deferred);
    return deferred.promise;
  },

  /**
   * Record the remote ID for a crash.
   *
   * @param crashID (string) Crash ID. Likely a UUID.
   * @param remoteID (Date) Server/Breakpad ID.
   *
   * @return boolean True if the remote ID was recorded.
   */
  async setRemoteCrashID(crashID, remoteID) {
    let store = await this._getStore();
    if (store.setRemoteCrashID(crashID, remoteID)) {
      await store.save();
    }
  },

  /**
   * Generate a submission ID for use with addSubmission{Attempt,Result}.
   */
  generateSubmissionID() {
    return "sub-" + Services.uuid.generateUUID().toString().slice(1, -1);
  },

  /**
   * Record the occurrence of a submission attempt for a crash.
   *
   * @param crashID (string) Crash ID. Likely a UUID.
   * @param submissionID (string) Submission ID. Likely a UUID.
   * @param date (Date) When the attempt occurred.
   *
   * @return boolean True if the attempt was recorded and false if not.
   */
  async addSubmissionAttempt(crashID, submissionID, date) {
    let store = await this._getStore();
    if (store.addSubmissionAttempt(crashID, submissionID, date)) {
      await store.save();
    }
  },

  /**
   * Record the occurrence of a submission result for a crash.
   *
   * @param crashID (string) Crash ID. Likely a UUID.
   * @param submissionID (string) Submission ID. Likely a UUID.
   * @param date (Date) When the submission result was obtained.
   * @param result (string) One of the SUBMISSION_RESULT constants.
   *
   * @return boolean True if the result was recorded and false if not.
   */
  async addSubmissionResult(crashID, submissionID, date, result) {
    let store = await this._getStore();
    if (store.addSubmissionResult(crashID, submissionID, date, result)) {
      await store.save();
    }
  },

  /**
   * Set the classification of a crash.
   *
   * @param crashID (string) Crash ID. Likely a UUID.
   * @param classifications (array) Crash classifications.
   *
   * @return boolean True if the data was recorded and false if not.
   */
  async setCrashClassifications(crashID, classifications) {
    let store = await this._getStore();
    if (store.setCrashClassifications(crashID, classifications)) {
      await store.save();
    }
  },

  /**
   * Obtain the paths of all unprocessed events files.
   *
   * The promise-resolved array is sorted by file mtime, oldest to newest.
   */
  _getUnprocessedEventsFiles() {
    return (async () => {
      try {
        let entries = [];

        for (let dir of this._eventsDirs) {
          for (let e of await this._getDirectoryEntries(dir, this.ALL_REGEX)) {
            entries.push(e);
          }
        }

        entries.sort((a, b) => {
          return a.date - b.date;
        });

        return entries;
      } catch (e) {
        console.error(e);
        return [];
      }
    })();
  },

  // See docs/crash-events.rst for the file format specification.
  _processEventFile(entry) {
    return (async () => {
      let data = await IOUtils.read(entry.path);
      let store = await this._getStore();

      let decoder = new TextDecoder();
      data = decoder.decode(data);

      let type, time;
      let start = 0;
      for (let i = 0; i < 2; i++) {
        let index = data.indexOf("\n", start);
        if (index == -1) {
          return this.EVENT_FILE_ERROR_MALFORMED;
        }

        let sub = data.substring(start, index);
        switch (i) {
          case 0:
            type = sub;
            break;
          case 1:
            time = sub;
            try {
              time = parseInt(time, 10);
            } catch (ex) {
              return this.EVENT_FILE_ERROR_MALFORMED;
            }
        }

        start = index + 1;
      }
      let date = new Date(time * 1000);
      let payload = data.substring(start);

      return this._handleEventFilePayload(store, entry, type, date, payload);
    })();
  },

  _filterAnnotations(annotations) {
    let filteredAnnotations = {};

    for (let line in annotations) {
      try {
        if (Services.appinfo.isAnnotationAllowedForPing(line)) {
          filteredAnnotations[line] = annotations[line];
        }
      } catch (e) {
        // Silently drop unknown annotations
      }
    }

    return filteredAnnotations;
  },

  /**
   * Submit a Glean crash ping with the given parameters.
   *
   * @param {string} reason - the reason for the crash ping, one of: "crash", "event_found"
   * @param {string} type - the process type (from {@link processTypes})
   * @param {DateTime} date - the time of the crash (or the closest time after it)
   * @param {string} minidumpHash - the hash of the minidump file, if any
   * @param {object} stackTraces - the object containing stack trace information
   * @param {object} metadata - the object of Telemetry crash metadata
   */
  _submitGleanCrashPing(
    reason,
    type,
    date,
    minidumpHash,
    stackTraces,
    metadata
  ) {
    if (stackTraces) {
      // Glean.crash.stack_traces has a slightly different shape than Telemetry
      stackTraces = stackTracesLegacyToGlean(stackTraces);

      // FIXME: Glean should probably accept an empty object here. Some tests
      // pass { status: "OK" }, which ends up being removed by the above logic.
      if (Object.keys(stackTraces).length) {
        // FIXME: ?. a temporary workaround for bug 1900442
        Glean.crash.stackTraces?.set(stackTraces);
      }
    }

    Glean.crash.processType.set(type);
    Glean.crash.time.set(date.getTime() * 1000);
    Glean.crash.minidumpSha256Hash.set(minidumpHash);

    // Convert Telemetry environment values to Glean metrics

    const cap = Symbol("capitalize");
    // Types such as quantity and string which can simply be `.set()`.
    const generic = Symbol("generic");
    const bool = Symbol("bool");
    const string = Symbol("string");
    const semicolon_list = Symbol("semicolon-separated string");
    const comma_list = Symbol("comma-separated string");
    const seconds = Symbol("seconds (floating)");

    class Typed {
      constructor(type, metaKey) {
        this.type = type;
        this.metaKey = metaKey;
      }
    }
    function t(type, metaKey) {
      return new Typed(type, metaKey);
    }

    const fieldMapping = {
      crash: {
        appChannel: "ReleaseChannel",
        appDisplayVersion: "Version",
        appBuild: "BuildID",
        asyncShutdownTimeout: t(asyncShutdownTimeoutLegacyToGlean, cap),
        backgroundTaskName: cap,
        eventLoopNestingLevel: cap,
        fontName: cap,
        gpuProcessLaunch: "GPUProcessLaunchCount",
        ipcChannelError: "ipc_channel_error",
        isGarbageCollecting: cap,
        mainThreadRunnableName: cap,
        mozCrashReason: cap,
        profilerChildShutdownPhase: cap,
        quotaManagerShutdownTimeout: t(
          quotaManagerShutdownTimeoutLegacyToGlean,
          cap
        ),
        remoteType: cap,
        shutdownProgress: cap,
        startup: t(bool, "StartupCrash"),
      },
      crashWindows: {
        errorReporting: t(bool, "WindowsErrorReporting"),
        fileDialogErrorCode: t(string, "WindowsFileDialogErrorCode"),
      },
      dllBlocklist: {
        list: t(semicolon_list, "BlockedDllList"),
        initFailed: t(bool, "BlocklistInitFailed"),
        user32LoadedBefore: t(bool, "User32BeforeBlocklist"),
      },
      environment: {
        experimentalFeatures: t(comma_list, cap),
        headlessMode: t(bool, cap),
        uptime: t(seconds, "UptimeTS"),
      },
      memory: {
        availableCommit: "AvailablePageFile",
        availablePhysical: "AvailablePhysicalMemory",
        availableSwap: "AvailableSwapMemory",
        availableVirtual: "AvailableVirtualMemory",
        lowPhysical: "LowPhysicalMemoryEvents",
        oomAllocationSize: "OOMAllocationSize",
        purgeablePhysical: "PurgeablePhysicalMemory",
        systemUsePercentage: "SystemMemoryUsePercentage",
        texture: "TextureUsage",
        totalPageFile: cap,
        totalPhysical: "TotalPhysicalMemory",
        totalVirtual: "TotalVirtualMemory",
      },
      windows: {
        packageFamilyName: "WindowsPackageFamilyName",
      },
    };

    function gleanSet(root, mapping) {
      for (const key in mapping) {
        let value = mapping[key];
        if (
          typeof value === "string" ||
          value === cap ||
          value instanceof Typed
        ) {
          // Get type and metadata key
          let type = generic;
          let metadataKey = value;
          if (value instanceof Typed) {
            type = value.type;
            metadataKey = value.metaKey;
          }
          // Elaborate metadata key if set to capitilize the Glean key.
          if (metadataKey === cap) {
            metadataKey = key.charAt(0).toUpperCase() + key.slice(1);
          }

          // If the metadata key is set, set the Glean metric.
          if (metadataKey in metadata) {
            let metaValue = metadata[metadataKey];

            if (type === seconds) {
              metaValue = parseFloat(metaValue) * 1e3;
              root[key].setRaw(metaValue);
              continue;
            }

            // Interpret types prior to calling `set`.
            if (type === bool) {
              metaValue = metaValue === "1";
            } else if (type === string) {
              metaValue = metaValue.toString();
            } else if (type === semicolon_list) {
              metaValue = metaValue.split(";").filter(x => x);
            } else if (type === comma_list) {
              metaValue = metaValue.split(",").filter(x => x);
            } else if (typeof type === "function") {
              // `object` metric transformation
              metaValue = type(metaValue);
            }
            // FIXME: ?. a temporary workaround for bug 1900442
            root[key]?.set(metaValue);
          }
        } else {
          gleanSet(root[key], value);
        }
      }
    }

    gleanSet(Glean, fieldMapping);

    GleanPings.crash.submit(reason);
  },

  /**
   * Send a crash ping.
   *
   * @param {string} reason - the reason for the crash ping, one of: "crash", "event_found"
   * @param {string} crashId - the crash identifier
   * @param {string} type - the process type (from {@link processTypes})
   * @param {DateTime} date - the time of the crash (or the closest time after it)
   * @param {object} metadata - Telemetry crash metadata
   */
  _sendCrashPing(reason, crashId, type, date, metadata = {}) {
    // If we have a saved environment, use it. Otherwise report
    // the current environment.
    let reportMeta = Cu.cloneInto(metadata, {});
    let crashEnvironment = parseAndRemoveField(
      reportMeta,
      "TelemetryEnvironment"
    );
    let sessionId = getAndRemoveField(reportMeta, "TelemetrySessionId");
    let stackTraces = getAndRemoveField(reportMeta, "StackTraces");
    let minidumpSha256Hash = getAndRemoveField(
      reportMeta,
      "MinidumpSha256Hash"
    );
    // If CrashPingUUID is present then a Telemetry ping was generated by the
    // crashreporter for this crash so we only need to send the Glean ping.
    let onlyGlean = getAndRemoveField(reportMeta, "CrashPingUUID");

    // Filter the remaining annotations to remove privacy-sensitive ones
    reportMeta = this._filterAnnotations(reportMeta);

    // Glean crash pings should not be sent on Android: they are handled
    // separately in lib-crash for Fenix (and potentially other GeckoView
    // users).
    if (AppConstants.platform !== "android") {
      this._submitGleanCrashPing(
        reason,
        type,
        date,
        minidumpSha256Hash,
        stackTraces,
        reportMeta
      );
    }

    if (onlyGlean) {
      return;
    }

    this._pingPromise = lazy.TelemetryController.submitExternalPing(
      "crash",
      {
        version: 1,
        crashDate: date.toISOString().slice(0, 10), // YYYY-MM-DD
        crashTime: date.toISOString().slice(0, 13) + ":00:00.000Z", // per-hour resolution
        sessionId,
        crashId,
        minidumpSha256Hash,
        processType: type,
        stackTraces,
        metadata: reportMeta,
        hasCrashEnvironment: crashEnvironment !== null,
      },
      {
        addClientId: true,
        addEnvironment: true,
        overrideEnvironment: crashEnvironment,
      }
    );
  },

  _handleEventFilePayload(store, entry, type, date, payload) {
    // The payload types and formats are documented in docs/crash-events.rst.
    // Do not change the format of an existing type. Instead, invent a new
    // type.
    // DO NOT ADD NEW TYPES WITHOUT DOCUMENTING!
    let lines = payload.split("\n");

    switch (type) {
      case "crash.main.1":
      case "crash.main.2":
        return this.EVENT_FILE_ERROR_OBSOLETE;

      case "crash.main.3":
        let crashID = lines[0];
        let metadata = JSON.parse(lines[1]);
        store.addCrash(
          this.processTypes[Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT],
          this.CRASH_TYPE_CRASH,
          crashID,
          date,
          metadata
        );

        this._sendCrashPing(
          "event_found",
          crashID,
          this.processTypes[Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT],
          date,
          metadata
        );

        break;

      case "crash.submission.1":
        if (lines.length == 3) {
          let [crashID, result, remoteID] = lines;
          store.addCrash(
            this.processTypes[Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT],
            this.CRASH_TYPE_CRASH,
            crashID,
            date
          );

          let submissionID = this.generateSubmissionID();
          let succeeded = result === "true";
          store.addSubmissionAttempt(crashID, submissionID, date);
          store.addSubmissionResult(
            crashID,
            submissionID,
            date,
            succeeded
              ? this.SUBMISSION_RESULT_OK
              : this.SUBMISSION_RESULT_FAILED
          );
          if (succeeded) {
            store.setRemoteCrashID(crashID, remoteID);
          }
        } else {
          return this.EVENT_FILE_ERROR_MALFORMED;
        }
        break;

      default:
        return this.EVENT_FILE_ERROR_UNKNOWN_EVENT;
    }

    return this.EVENT_FILE_SUCCESS;
  },

  /**
   * The resolved promise is an array of objects with the properties:
   *
   *   path -- String filename
   *   id -- regexp.match()[1] (likely the crash ID)
   *   date -- Date mtime of the file
   */
  _getDirectoryEntries(path, re) {
    return (async function () {
      let children = await IOUtils.getChildren(path);
      let entries = [];

      for (const entry of children) {
        let stat = await IOUtils.stat(entry);
        if (stat.type == "directory") {
          continue;
        }

        let filename = PathUtils.filename(entry);
        let match = re.exec(filename);
        if (!match) {
          continue;
        }
        entries.push({
          path: entry,
          id: match[1],
          date: stat.lastModified,
        });
      }

      entries.sort((a, b) => {
        return a.date - b.date;
      });

      return entries;
    })();
  },

  _getStore() {
    if (this._getStoreTask) {
      return this._getStoreTask;
    }

    return (this._getStoreTask = (async () => {
      try {
        if (!this._store) {
          await IOUtils.makeDirectory(this._storeDir, {
            permissions: 0o700,
          });

          let store = new CrashStore(
            this._storeDir,
            this._telemetryStoreSizeKey
          );
          await store.load();

          this._store = store;
          this._storeTimer = Cc["@mozilla.org/timer;1"].createInstance(
            Ci.nsITimer
          );
        }

        // The application can go long periods without interacting with the
        // store. Since the store takes up resources, we automatically "free"
        // the store after inactivity so resources can be returned to the
        // system. We do this via a timer and a mechanism that tracks when the
        // store is being accessed.
        this._storeTimer.cancel();

        // This callback frees resources from the store unless the store
        // is protected from freeing by some other process.
        let timerCB = () => {
          if (this._storeProtectedCount) {
            this._storeTimer.initWithCallback(
              timerCB,
              this.STORE_EXPIRATION_MS,
              this._storeTimer.TYPE_ONE_SHOT
            );
            return;
          }

          // We kill the reference that we hold. GC will kill it later. If
          // someone else holds a reference, that will prevent GC until that
          // reference is gone.
          this._store = null;
          this._storeTimer = null;
        };

        this._storeTimer.initWithCallback(
          timerCB,
          this.STORE_EXPIRATION_MS,
          this._storeTimer.TYPE_ONE_SHOT
        );

        return this._store;
      } finally {
        this._getStoreTask = null;
      }
    })());
  },

  /**
   * Obtain information about all known crashes.
   *
   * Returns an array of CrashRecord instances. Instances are read-only.
   */
  getCrashes() {
    return (async () => {
      let store = await this._getStore();

      return store.crashes;
    })();
  },

  getCrashCountsByDay() {
    return (async () => {
      let store = await this._getStore();

      return store._countsByDay;
    })();
  },
});

var gCrashManager;

/**
 * Interface to storage of crash data.
 *
 * This type handles storage of crash metadata. It exists as a separate type
 * from the crash manager for performance reasons: since all crash metadata
 * needs to be loaded into memory for access, we wish to easily dispose of all
 * associated memory when this data is no longer needed. Having an isolated
 * object whose references can easily be lost faciliates that simple disposal.
 *
 * When metadata is updated, the caller must explicitly persist the changes
 * to disk. This prevents excessive I/O during updates.
 *
 * The store has a mechanism for ensuring it doesn't grow too large. A ceiling
 * is placed on the number of daily events that can occur for events that can
 * occur with relatively high frequency. If we've reached
 * the high water mark and new data arrives, it's silently dropped.
 * However, the count of actual events is always preserved. This allows
 * us to report on the severity of problems beyond the storage threshold.
 *
 * Main process crashes are excluded from limits because they are both
 * important and should be rare.
 *
 * @param storeDir (string)
 *        Directory the store should be located in.
 * @param telemetrySizeKey (string)
 *        The telemetry histogram that should be used to store the size
 *        of the data file.
 */
export function CrashStore(storeDir, telemetrySizeKey) {
  this._storeDir = storeDir;
  this._telemetrySizeKey = telemetrySizeKey;

  this._storePath = PathUtils.join(storeDir, "store.json.mozlz4");

  // Holds the read data from disk.
  this._data = null;

  // Maps days since UNIX epoch to a Map of event types to counts.
  // This data structure is populated when the JSON file is loaded
  // and is also updated when new events are added.
  this._countsByDay = new Map();
}

CrashStore.prototype = Object.freeze({
  // Maximum number of events to store per day. This establishes a
  // ceiling on the per-type/per-day records that will be stored.
  HIGH_WATER_DAILY_THRESHOLD: 500,

  /**
   * Reset all data.
   */
  reset() {
    this._data = {
      v: 1,
      crashes: new Map(),
      corruptDate: null,
    };
    this._countsByDay = new Map();
  },

  /**
   * Load data from disk.
   *
   * @return Promise
   */
  load() {
    return (async () => {
      // Loading replaces data.
      this.reset();

      try {
        let decoder = new TextDecoder();
        let data = await IOUtils.read(this._storePath, { decompress: true });
        data = JSON.parse(decoder.decode(data));

        if (data.corruptDate) {
          this._data.corruptDate = new Date(data.corruptDate);
        }

        // actualCounts is used to validate that the derived counts by
        // days stored in the payload matches up to actual data.
        let actualCounts = new Map();

        // In the past, submissions were stored as separate crash records
        // with an id of e.g. "someID-submission". If we find IDs ending
        // with "-submission", we will need to convert the data to be stored
        // as actual submissions.
        //
        // The old way of storing submissions was used from FF33 - FF34. We
        // drop this old data on the floor.
        for (let id in data.crashes) {
          if (id.endsWith("-submission")) {
            continue;
          }

          let crash = data.crashes[id];
          let denormalized = this._denormalize(crash);

          denormalized.submissions = new Map();
          if (crash.submissions) {
            for (let submissionID in crash.submissions) {
              let submission = crash.submissions[submissionID];
              denormalized.submissions.set(
                submissionID,
                this._denormalize(submission)
              );
            }
          }

          this._data.crashes.set(id, denormalized);

          let key =
            dateToDays(denormalized.crashDate) + "-" + denormalized.type;
          actualCounts.set(key, (actualCounts.get(key) || 0) + 1);

          // If we have an OOM size, count the crash as an OOM in addition to
          // being a main process crash.
          if (
            denormalized.metadata &&
            denormalized.metadata.OOMAllocationSize
          ) {
            let oomKey = key + "-oom";
            actualCounts.set(oomKey, (actualCounts.get(oomKey) || 0) + 1);
          }
        }

        // The validation in this loop is arguably not necessary. We perform
        // it as a defense against unknown bugs.
        for (let dayKey in data.countsByDay) {
          let day = parseInt(dayKey, 10);
          for (let type in data.countsByDay[day]) {
            this._ensureCountsForDay(day);

            let count = data.countsByDay[day][type];
            let key = day + "-" + type;

            // If the payload says we have data for a given day but we
            // don't, the payload is wrong. Ignore it.
            if (!actualCounts.has(key)) {
              continue;
            }

            // If we encountered more data in the payload than what the
            // data structure says, use the proper value.
            count = Math.max(count, actualCounts.get(key));

            this._countsByDay.get(day).set(type, count);
          }
        }
      } catch (ex) {
        // Missing files (first use) are allowed.
        if (!DOMException.isInstance(ex) || ex.name != "NotFoundError") {
          // If we can't load for any reason, mark a corrupt date in the instance
          // and swallow the error.
          //
          // The marking of a corrupted file is intentionally not persisted to
          // disk yet. Instead, we wait until the next save(). This is to give
          // non-permanent failures the opportunity to recover on their own.
          this._data.corruptDate = new Date();
        }
      }
    })();
  },

  /**
   * Save data to disk.
   *
   * @return Promise<null>
   */
  save() {
    return (async () => {
      if (!this._data) {
        return;
      }

      let normalized = {
        // The version should be incremented whenever the format
        // changes.
        v: 1,
        // Maps crash IDs to objects defining the crash.
        crashes: {},
        // Maps days since UNIX epoch to objects mapping event types to
        // counts. This is a mirror of this._countsByDay. e.g.
        // {
        //    15000: {
        //        "main-crash": 2,
        //        "plugin-crash": 1
        //    }
        // }
        countsByDay: {},

        // When the store was last corrupted.
        corruptDate: null,
      };

      if (this._data.corruptDate) {
        normalized.corruptDate = this._data.corruptDate.getTime();
      }

      for (let [id, crash] of this._data.crashes) {
        let c = this._normalize(crash);

        c.submissions = {};
        for (let [submissionID, submission] of crash.submissions) {
          c.submissions[submissionID] = this._normalize(submission);
        }

        normalized.crashes[id] = c;
      }

      for (let [day, m] of this._countsByDay) {
        normalized.countsByDay[day] = {};
        for (let [type, count] of m) {
          normalized.countsByDay[day][type] = count;
        }
      }

      let encoder = new TextEncoder();
      let data = encoder.encode(JSON.stringify(normalized));
      let size = await IOUtils.write(this._storePath, data, {
        tmpPath: this._storePath + ".tmp",
        compress: true,
      });
      if (this._telemetrySizeKey) {
        Services.telemetry.getHistogramById(this._telemetrySizeKey).add(size);
      }
    })();
  },

  /**
   * Normalize an object into one fit for serialization.
   *
   * This function along with _denormalize() serve to hack around the
   * default handling of Date JSON serialization because Date serialization
   * is undefined by JSON.
   *
   * Fields ending with "Date" are assumed to contain Date instances.
   * We convert these to milliseconds since epoch on output and back to
   * Date on input.
   */
  _normalize(o) {
    let normalized = {};

    for (let k in o) {
      let v = o[k];
      if (v && k.endsWith("Date")) {
        normalized[k] = v.getTime();
      } else {
        normalized[k] = v;
      }
    }

    return normalized;
  },

  /**
   * Convert a serialized object back to its native form.
   */
  _denormalize(o) {
    let n = {};

    for (let k in o) {
      let v = o[k];
      if (v && k.endsWith("Date")) {
        n[k] = new Date(parseInt(v, 10));
      } else {
        n[k] = v;
      }
    }

    return n;
  },

  /**
   * Prune old crash data.
   *
   * Crashes without recent activity are pruned from the store so the
   * size of the store is not unbounded. If there is activity on a crash,
   * that activity will keep the crash and all its data around for longer.
   *
   * @param date
   *        (Date) The cutoff at which data will be pruned. If an entry
   *        doesn't have data newer than this, it will be pruned.
   */
  pruneOldCrashes(date) {
    for (let crash of this.crashes) {
      let newest = crash.newestDate;
      if (!newest || newest.getTime() < date.getTime()) {
        this._data.crashes.delete(crash.id);
      }
    }
  },

  /**
   * Date the store was last corrupted and required a reset.
   *
   * May be null (no corruption has ever occurred) or a Date instance.
   */
  get corruptDate() {
    return this._data.corruptDate;
  },

  /**
   * The number of distinct crashes tracked.
   */
  get crashesCount() {
    return this._data.crashes.size;
  },

  /**
   * All crashes tracked.
   *
   * This is an array of CrashRecord.
   */
  get crashes() {
    let crashes = [];
    for (let [, crash] of this._data.crashes) {
      crashes.push(new CrashRecord(crash));
    }

    return crashes;
  },

  /**
   * Obtain a particular crash from its ID.
   *
   * A CrashRecord will be returned if the crash exists. null will be returned
   * if the crash is unknown.
   */
  getCrash(id) {
    for (let crash of this.crashes) {
      if (crash.id == id) {
        return crash;
      }
    }

    return null;
  },

  _ensureCountsForDay(day) {
    if (!this._countsByDay.has(day)) {
      this._countsByDay.set(day, new Map());
    }
  },

  /**
   * Ensure the crash record is present in storage.
   *
   * Returns the crash record if we're allowed to store it or null
   * if we've hit the high water mark.
   *
   * @param processType
   *        (string) One of the PROCESS_TYPE constants.
   * @param crashType
   *        (string) One of the CRASH_TYPE constants.
   * @param id
   *        (string) The crash ID.
   * @param date
   *        (Date) When this crash occurred.
   * @param metadata
   *        (dictionary) Crash metadata, may be empty.
   *
   * @return null | object crash record
   */
  _ensureCrashRecord(processType, crashType, id, date, metadata) {
    if (!id) {
      // Crashes are keyed on ID, so it's not really helpful to store crashes
      // without IDs.
      return null;
    }

    let type = processType + "-" + crashType;

    if (!this._data.crashes.has(id)) {
      let day = dateToDays(date);
      this._ensureCountsForDay(day);

      let count = (this._countsByDay.get(day).get(type) || 0) + 1;
      this._countsByDay.get(day).set(type, count);

      if (
        count > this.HIGH_WATER_DAILY_THRESHOLD &&
        processType !=
          CrashManager.prototype.processTypes[
            Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT
          ]
      ) {
        return null;
      }

      // If we have an OOM size, count the crash as an OOM in addition to
      // being a main process crash.
      if (metadata && metadata.OOMAllocationSize) {
        let oomType = type + "-oom";
        let oomCount = (this._countsByDay.get(day).get(oomType) || 0) + 1;
        this._countsByDay.get(day).set(oomType, oomCount);
      }

      this._data.crashes.set(id, {
        id,
        remoteID: null,
        type,
        crashDate: date,
        submissions: new Map(),
        classifications: [],
        metadata,
      });
    }

    let crash = this._data.crashes.get(id);
    crash.type = type;
    crash.crashDate = date;

    return crash;
  },

  /**
   * Record the occurrence of a crash.
   *
   * @param processType (string) One of the PROCESS_TYPE constants.
   * @param crashType (string) One of the CRASH_TYPE constants.
   * @param id (string) Crash ID. Likely a UUID.
   * @param date (Date) When the crash occurred.
   * @param metadata (dictionary) Crash metadata, may be empty.
   *
   * @return boolean True if the crash was recorded and false if not.
   */
  addCrash(processType, crashType, id, date, metadata) {
    return !!this._ensureCrashRecord(
      processType,
      crashType,
      id,
      date,
      metadata
    );
  },

  /**
   * @return boolean True if the remote ID was recorded and false if not.
   */
  setRemoteCrashID(crashID, remoteID) {
    let crash = this._data.crashes.get(crashID);
    if (!crash || !remoteID) {
      return false;
    }

    crash.remoteID = remoteID;
    return true;
  },

  /**
   * @param processType (string) One of the PROCESS_TYPE constants.
   * @param crashType (string) One of the CRASH_TYPE constants.
   *
   * @return array of crashes
   */
  getCrashesOfType(processType, crashType) {
    let crashes = [];
    for (let crash of this.crashes) {
      if (crash.isOfType(processType, crashType)) {
        crashes.push(crash);
      }
    }

    return crashes;
  },

  /**
   * Ensure the submission record is present in storage.
   * @returns [submission, crash]
   */
  _ensureSubmissionRecord(crashID, submissionID) {
    let crash = this._data.crashes.get(crashID);
    if (!crash || !submissionID) {
      return null;
    }

    if (!crash.submissions.has(submissionID)) {
      crash.submissions.set(submissionID, {
        requestDate: null,
        responseDate: null,
        result: null,
      });
    }

    return [crash.submissions.get(submissionID), crash];
  },

  /**
   * @return boolean True if the attempt was recorded.
   */
  addSubmissionAttempt(crashID, submissionID, date) {
    let [submission, crash] = this._ensureSubmissionRecord(
      crashID,
      submissionID
    );
    if (!submission) {
      return false;
    }

    submission.requestDate = date;
    Services.telemetry
      .getKeyedHistogramById("PROCESS_CRASH_SUBMIT_ATTEMPT")
      .add(crash.type, 1);
    return true;
  },

  /**
   * @return boolean True if the response was recorded.
   */
  addSubmissionResult(crashID, submissionID, date, result) {
    let crash = this._data.crashes.get(crashID);
    if (!crash || !submissionID) {
      return false;
    }
    let submission = crash.submissions.get(submissionID);
    if (!submission) {
      return false;
    }

    submission.responseDate = date;
    submission.result = result;
    Services.telemetry
      .getKeyedHistogramById("PROCESS_CRASH_SUBMIT_SUCCESS")
      .add(crash.type, result == "ok");
    return true;
  },

  /**
   * @return boolean True if the classifications were set.
   */
  setCrashClassifications(crashID, classifications) {
    let crash = this._data.crashes.get(crashID);
    if (!crash) {
      return false;
    }

    crash.classifications = classifications;
    return true;
  },
});

/**
 * Represents an individual crash with metadata.
 *
 * This is a wrapper around the low-level anonymous JS objects that define
 * crashes. It exposes a consistent and helpful API.
 *
 * Instances of this type should only be constructured inside this module,
 * not externally. The constructor is not considered a public API.
 *
 * @param o (object)
 *        The crash's entry from the CrashStore.
 */
function CrashRecord(o) {
  this._o = o;
}

CrashRecord.prototype = Object.freeze({
  get id() {
    return this._o.id;
  },

  get remoteID() {
    return this._o.remoteID;
  },

  get crashDate() {
    return this._o.crashDate;
  },

  /**
   * Obtain the newest date in this record.
   *
   * This is a convenience getter. The returned value is used to determine when
   * to expire a record.
   */
  get newestDate() {
    // We currently only have 1 date, so this is easy.
    return this._o.crashDate;
  },

  get oldestDate() {
    return this._o.crashDate;
  },

  get type() {
    return this._o.type;
  },

  isOfType(processType, crashType) {
    return processType + "-" + crashType == this.type;
  },

  get submissions() {
    return this._o.submissions;
  },

  get classifications() {
    return this._o.classifications;
  },

  get metadata() {
    return this._o.metadata;
  },
});

ChromeUtils.defineLazyGetter(CrashManager, "_log", () =>
  lazy.Log.repository.getLogger("Crashes.CrashManager")
);

/**
 * Obtain the global CrashManager instance used by the running application.
 *
 * CrashManager is likely only ever instantiated once per application lifetime.
 * The main reason it's implemented as a reusable type is to facilitate testing.
 */
ChromeUtils.defineLazyGetter(CrashManager, "Singleton", function () {
  if (gCrashManager) {
    return gCrashManager;
  }

  gCrashManager = new CrashManager({
    telemetryStoreSizeKey: "CRASH_STORE_COMPRESSED_BYTES",
  });

  // Automatically aggregate event files shortly after startup. This
  // ensures it happens with some frequency.
  //
  // There are performance considerations here. While this is doing
  // work and could negatively impact performance, the amount of work
  // is kept small per run by periodically aggregating event files.
  // Furthermore, well-behaving installs should not have much work
  // here to do. If there is a lot of work, that install has bigger
  // issues beyond reduced performance near startup.
  gCrashManager.scheduleMaintenance(AGGREGATE_STARTUP_DELAY_MS);

  return gCrashManager;
});

export function getCrashManager() {
  return CrashManager.Singleton;
}

/**
 * Used for tests to check the crash manager is created on profile creation.
 *
 * @returns {CrashManager}
 */
export function getCrashManagerNoCreate() {
  return gCrashManager;
}
PK
!<;M6Amodules/CrashMonitor.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Crash Monitor
 *
 * Monitors execution of a program to detect possible crashes. After
 * program termination, the monitor can be queried during the next run
 * to determine whether the last run exited cleanly or not.
 *
 * The monitoring is done by registering and listening for special
 * notifications, or checkpoints, known to be sent by the monitored
 * program as different stages in the execution are reached. As they
 * are observed, these notifications are written asynchronously to a
 * checkpoint file.
 *
 * During next program startup the crash monitor reads the checkpoint
 * file from the last session. If notifications are missing, a crash
 * has likely happened. By inspecting the notifications present, it is
 * possible to determine what stages were reached in the program
 * before the crash.
 *
 * Note that since the file is written asynchronously it is possible
 * that a received notification is lost if the program crashes right
 * after a checkpoint, but before crash monitor has been able to write
 * it to disk. Thus, while the presence of a notification in the
 * checkpoint file tells us that the corresponding stage was reached
 * during the last run, the absence of a notification after a crash
 * does not necessarily tell us that the checkpoint wasn't reached.
 */

import { PrivateBrowsingUtils } from "resource://gre/modules/PrivateBrowsingUtils.sys.mjs";

const SESSIONSTORE_WINDOWS_RESTORED_TOPIC = "sessionstore-windows-restored";
const SESSIONSTORE_FINAL_STATE_WRITE_COMPLETE_TOPIC =
  "sessionstore-final-state-write-complete";

const NOTIFICATIONS = [
  "final-ui-startup",
  SESSIONSTORE_WINDOWS_RESTORED_TOPIC,
  "quit-application-granted",
  "quit-application",
  "profile-change-net-teardown",
  "profile-change-teardown",
  SESSIONSTORE_FINAL_STATE_WRITE_COMPLETE_TOPIC,
];

const SHUTDOWN_PHASES = ["profile-before-change"];

var CrashMonitorInternal = {
  /**
   * Notifications received during the current session.
   *
   * Object where a property with a value of |true| means that the
   * notification of the same name has been received at least once by
   * the CrashMonitor during this session. Notifications that have not
   * yet been received are not present as properties. |NOTIFICATIONS|
   * lists the notifications tracked by the CrashMonitor.
   */
  checkpoints: {},

  /**
   * A deferred promise that resolves when all checkpoints have been written.
   */
  sessionStoreFinalWriteComplete: Promise.withResolvers(),

  /**
   * Notifications received during previous session.
   *
   * Available after |loadPreviousCheckpoints|. Promise which resolves
   * to an object containing a set of properties, where a property
   * with a value of |true| means that the notification with the same
   * name as the property name was received at least once last
   * session.
   */
  previousCheckpoints: null,

  /**
   * Path to checkpoint file.
   *
   * Each time a new notification is received, this file is written to
   * disc to reflect the information in |checkpoints|.
   */
  path: PathUtils.join(
    Services.dirsvc.get("ProfD", Ci.nsIFile).path,
    "sessionCheckpoints.json"
  ),

  /**
   * Load checkpoints from previous session asynchronously.
   *
   * @return {Promise} A promise that resolves/rejects once loading is complete
   */
  loadPreviousCheckpoints() {
    this.previousCheckpoints = (async function () {
      let notifications;
      try {
        notifications = await IOUtils.readJSON(CrashMonitorInternal.path);
      } catch (ex) {
        // Ignore file not found errors, but report all others.
        if (ex.name !== "NotFoundError") {
          console.error("Error while loading crash monitor data:", ex.message);
        }
        return null;
      }

      // If `notifications` isn't an object, then the monitor data isn't valid.
      if (Object(notifications) !== notifications) {
        console.error(
          "Error while parsing crash monitor data: invalid monitor data"
        );
        return null;
      }

      return Object.freeze(notifications);
    })();

    return this.previousCheckpoints;
  },
};

export var CrashMonitor = {
  /**
   * Notifications received during previous session.
   *
   * Return object containing the set of notifications received last
   * session as keys with values set to |true|.
   *
   * @return {Promise} A promise resolving to previous checkpoints
   */
  get previousCheckpoints() {
    if (!CrashMonitorInternal.initialized) {
      throw new Error(
        "CrashMonitor must be initialized before getting previous checkpoints"
      );
    }

    return CrashMonitorInternal.previousCheckpoints;
  },

  /**
   * Initialize CrashMonitor.
   *
   * Should only be called from the CrashMonitor XPCOM component.
   *
   * @return {Promise}
   */
  init() {
    if (CrashMonitorInternal.initialized) {
      throw new Error("CrashMonitor.init() must only be called once!");
    }

    let promise = CrashMonitorInternal.loadPreviousCheckpoints();
    // Add "profile-after-change" to checkpoint as this method is
    // called after receiving it
    CrashMonitorInternal.checkpoints["profile-after-change"] = true;

    NOTIFICATIONS.forEach(function (aTopic) {
      Services.obs.addObserver(this, aTopic);
    }, this);

    // Add shutdown blocker for profile-before-change
    IOUtils.profileBeforeChange.addBlocker(
      "CrashMonitor: Writing notifications to file after receiving profile-before-change and awaiting all checkpoints written",
      async () => {
        await this.writeCheckpoint("profile-before-change");

        // If SessionStore has not initialized, we don't want to wait for
        // checkpoints that we won't hit, or we'll crash the browser during
        // async shutdown.
        if (
          !PrivateBrowsingUtils.permanentPrivateBrowsing &&
          CrashMonitorInternal.checkpoints[SESSIONSTORE_WINDOWS_RESTORED_TOPIC]
        ) {
          await CrashMonitorInternal.sessionStoreFinalWriteComplete.promise;
        }
      },
      () => CrashMonitorInternal.checkpoints
    );

    CrashMonitorInternal.initialized = true;
    return promise;
  },

  /**
   * Handle registered notifications.
   *
   * Update checkpoint file for every new notification received.
   */
  observe(aSubject, aTopic) {
    this.writeCheckpoint(aTopic);

    if (
      NOTIFICATIONS.every(elem => elem in CrashMonitorInternal.checkpoints) &&
      SHUTDOWN_PHASES.every(elem => elem in CrashMonitorInternal.checkpoints)
    ) {
      // All notifications received, unregister observers
      NOTIFICATIONS.forEach(function (aTopic) {
        Services.obs.removeObserver(this, aTopic);
      }, this);
    }

    if (aTopic === SESSIONSTORE_FINAL_STATE_WRITE_COMPLETE_TOPIC) {
      CrashMonitorInternal.sessionStoreFinalWriteComplete.resolve();
    }
  },

  async writeCheckpoint(aCheckpoint) {
    if (!(aCheckpoint in CrashMonitorInternal.checkpoints)) {
      // If this is the first time this notification is received,
      // remember it and write it to file
      CrashMonitorInternal.checkpoints[aCheckpoint] = true;

      /* Write to the checkpoint file asynchronously, off the main
       * thread, for performance reasons. Note that this means
       * that there's not a 100% guarantee that the file will be
       * written by the time the notification completes. The
       * exception is profile-before-change which has a shutdown
       * blocker. */
      await IOUtils.writeJSON(
        CrashMonitorInternal.path,
        CrashMonitorInternal.checkpoints,
        {
          tmpPath: CrashMonitorInternal.path + ".tmp",
        }
      );
    }
  },
};

Object.freeze(CrashMonitor);
PK
!<�Ư�	�	modules/CrashReports.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export var CrashReports = {
  pendingDir: null,
  reportsDir: null,
  submittedDir: null,
  getReports: function CrashReports_getReports() {
    let reports = [];

    try {
      // Ignore any non http/https urls
      if (!/^https?:/i.test(Services.prefs.getCharPref("breakpad.reportURL"))) {
        return reports;
      }
    } catch (e) {}

    if (this.submittedDir.exists() && this.submittedDir.isDirectory()) {
      let entries = this.submittedDir.directoryEntries;
      while (entries.hasMoreElements()) {
        let file = entries.nextFile;
        let leaf = file.leafName;
        if (leaf.startsWith("bp-") && leaf.endsWith(".txt")) {
          let entry = {
            id: leaf.slice(0, -4),
            date: file.lastModifiedTime,
            pending: false,
          };
          reports.push(entry);
        }
      }
    }

    if (this.pendingDir.exists() && this.pendingDir.isDirectory()) {
      let uuidRegex =
        /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
      let entries = this.pendingDir.directoryEntries;
      while (entries.hasMoreElements()) {
        let file = entries.nextFile;
        let leaf = file.leafName;
        let id = leaf.slice(0, -4);
        if (leaf.endsWith(".dmp") && uuidRegex.test(id)) {
          let entry = {
            id,
            date: file.lastModifiedTime,
            pending: true,
          };
          reports.push(entry);
        }
      }
    }

    // Sort reports descending by date
    return reports.sort((a, b) => b.date - a.date);
  },
};

function CrashReports_pendingDir() {
  let pendingDir = Services.dirsvc.get("UAppData", Ci.nsIFile);
  pendingDir.append("Crash Reports");
  pendingDir.append("pending");
  return pendingDir;
}

function CrashReports_reportsDir() {
  let reportsDir = Services.dirsvc.get("UAppData", Ci.nsIFile);
  reportsDir.append("Crash Reports");
  return reportsDir;
}

function CrashReports_submittedDir() {
  let submittedDir = Services.dirsvc.get("UAppData", Ci.nsIFile);
  submittedDir.append("Crash Reports");
  submittedDir.append("submitted");
  return submittedDir;
}

CrashReports.pendingDir = CrashReports_pendingDir();
CrashReports.reportsDir = CrashReports_reportsDir();
CrashReports.submittedDir = CrashReports_submittedDir();
PK
!<+���]]modules/CrashService.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { AsyncShutdown } from "resource://gre/modules/AsyncShutdown.sys.mjs";

// Set to true if the application is quitting
var gQuitting = false;

// Tracks all the running instances of the minidump-analyzer
var gRunningProcesses = new Set();

/**
 * Run the minidump-analyzer with the given options unless we're already
 * shutting down or the main process has been instructed to shut down in the
 * case a content process crashes. Minidump analysis can take a while so we
 * don't want to block shutdown waiting for it.
 */
async function maybeRunMinidumpAnalyzer(minidumpPath, allThreads) {
  let shutdown = Services.env.exists("MOZ_CRASHREPORTER_SHUTDOWN");

  if (gQuitting || shutdown) {
    return;
  }

  await runMinidumpAnalyzer(minidumpPath, allThreads).catch(e =>
    console.error(e)
  );
}

function getMinidumpAnalyzerPath() {
  const binSuffix = AppConstants.platform === "win" ? ".exe" : "";
  const exeName = "minidump-analyzer" + binSuffix;

  let exe = Services.dirsvc.get("GreBinD", Ci.nsIFile);
  exe.append(exeName);

  return exe;
}

/**
 * Run the minidump analyzer tool to gather stack traces from the minidump. The
 * stack traces will be stored in the .extra file under the StackTraces= entry.
 *
 * @param minidumpPath {string} The path to the minidump file
 * @param allThreads {bool} Gather stack traces for all threads, not just the
 *                   crashing thread.
 *
 * @returns {Promise} A promise that gets resolved once minidump analysis has
 *          finished.
 */
function runMinidumpAnalyzer(minidumpPath, allThreads) {
  return new Promise((resolve, reject) => {
    try {
      let exe = getMinidumpAnalyzerPath();
      let args = [minidumpPath];
      let process = Cc["@mozilla.org/process/util;1"].createInstance(
        Ci.nsIProcess
      );
      process.init(exe);
      process.startHidden = true;
      process.noShell = true;

      if (allThreads) {
        args.unshift("--full");
      }

      process.runAsync(args, args.length, (subject, topic) => {
        switch (topic) {
          case "process-finished":
            gRunningProcesses.delete(process);
            resolve();
            break;
          case "process-failed":
            gRunningProcesses.delete(process);
            resolve();
            break;
          default:
            reject(new Error("Unexpected topic received " + topic));
            break;
        }
      });

      gRunningProcesses.add(process);
    } catch (e) {
      reject(e);
    }
  });
}

/**
 * Computes the SHA256 hash of a minidump file
 *
 * @param minidumpPath {string} The path to the minidump file
 *
 * @returns {Promise} A promise that resolves to the hash value of the
 *          minidump.
 */
function computeMinidumpHash(minidumpPath) {
  return (async function () {
    try {
      let minidumpData = await IOUtils.read(minidumpPath);
      let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
        Ci.nsICryptoHash
      );
      hasher.init(hasher.SHA256);
      hasher.update(minidumpData, minidumpData.length);

      let hashBin = hasher.finish(false);
      let hash = "";

      for (let i = 0; i < hashBin.length; i++) {
        // Every character in the hash string contains a byte of the hash data
        hash += ("0" + hashBin.charCodeAt(i).toString(16)).slice(-2);
      }

      return hash;
    } catch (e) {
      console.error(e);
      return null;
    }
  })();
}

/**
 * Process the given .extra file and return the annotations it contains in an
 * object.
 *
 * @param extraPath {string} The path to the .extra file
 *
 * @return {Promise} A promise that resolves to an object holding the crash
 *         annotations.
 */
function processExtraFile(extraPath) {
  return (async function () {
    try {
      let decoder = new TextDecoder();
      let extraData = await IOUtils.read(extraPath);

      return JSON.parse(decoder.decode(extraData));
    } catch (e) {
      console.error(e);
      return {};
    }
  })();
}

/**
 * This component makes crash data available throughout the application.
 *
 * It is a service because some background activity will eventually occur.
 */
export function CrashService() {
  Services.obs.addObserver(this, "quit-application");
}

CrashService.prototype = Object.freeze({
  classID: Components.ID("{92668367-1b17-4190-86b2-1061b2179744}"),
  QueryInterface: ChromeUtils.generateQI(["nsICrashService", "nsIObserver"]),

  async addCrash(processType, crashType, id) {
    if (processType === Ci.nsIXULRuntime.PROCESS_TYPE_IPDLUNITTEST) {
      return;
    }

    processType = Services.crashmanager.processTypes[processType];

    let allThreads = false;

    switch (crashType) {
      case Ci.nsICrashService.CRASH_TYPE_CRASH:
        crashType = Services.crashmanager.CRASH_TYPE_CRASH;
        break;
      case Ci.nsICrashService.CRASH_TYPE_HANG:
        crashType = Services.crashmanager.CRASH_TYPE_HANG;
        allThreads = true;
        break;
      default:
        throw new Error("Unrecognized CRASH_TYPE: " + crashType);
    }

    let minidumpPath = Services.appinfo.getMinidumpForID(id).path;
    let extraPath = Services.appinfo.getExtraFileForID(id).path;
    let metadata = {};
    let hash = null;

    await maybeRunMinidumpAnalyzer(minidumpPath, allThreads);
    metadata = await processExtraFile(extraPath);
    hash = await computeMinidumpHash(minidumpPath);

    if (hash) {
      metadata.MinidumpSha256Hash = hash;
    }

    let blocker = Services.crashmanager.addCrash(
      processType,
      crashType,
      id,
      new Date(),
      metadata
    );

    AsyncShutdown.profileBeforeChange.addBlocker(
      "CrashService waiting for content crash ping to be sent",
      blocker
    );

    blocker.then(AsyncShutdown.profileBeforeChange.removeBlocker(blocker));

    await blocker;
  },

  observe(subject, topic) {
    switch (topic) {
      case "profile-after-change":
        // Side-effect is the singleton is instantiated.
        Services.crashmanager;
        break;
      case "quit-application":
        gQuitting = true;
        gRunningProcesses.forEach(process => {
          try {
            process.kill();
          } catch (e) {
            // If the process has already quit then kill() fails, but since
            // this failure is benign it is safe to silently ignore it.
          }
          Services.obs.notifyObservers(null, "test-minidump-analyzer-killed");
        });
        break;
    }
  },
});
PK
!<g��EHEHmodules/CrashSubmit.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const SUCCESS = "success";
const FAILED = "failed";
const SUBMITTING = "submitting";

const UUID_REGEX =
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
const SUBMISSION_REGEX =
  /^bp-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;

function getDir(name) {
  let uAppDataPath = Services.dirsvc.get("UAppData", Ci.nsIFile).path;
  return PathUtils.join(uAppDataPath, "Crash Reports", name);
}

async function writeFileAsync(dirName, fileName, data) {
  let dirPath = getDir(dirName);
  let filePath = PathUtils.join(dirPath, fileName);
  await IOUtils.makeDirectory(dirPath, { permissions: 0o700 });
  await IOUtils.writeUTF8(filePath, data);
}

function getPendingMinidump(id) {
  let pendingDir = getDir("pending");

  return [".dmp", ".extra", ".memory.json.gz"].map(suffix => {
    return PathUtils.join(pendingDir, `${id}${suffix}`);
  });
}

async function writeSubmittedReportAsync(crashID, viewURL) {
  // Since we're basically re-implementing (with async) part of the
  // crashreporter client here, we'll use the strings we need from the
  // crashreporter fluent file.
  const l10n = new Localization(["crashreporter/crashreporter.ftl"]);
  let data = await l10n.formatValue("crashreporter-crash-identifier", {
    id: crashID,
  });
  if (viewURL) {
    data +=
      "\n" +
      (await l10n.formatValue("crashreporter-crash-details", { url: viewURL }));
  }

  await writeFileAsync("submitted", `${crashID}.txt`, data);
}

// the Submitter class represents an individual submission.
function Submitter(id, recordSubmission, noThrottle, extraExtraKeyVals) {
  this.id = id;
  this.recordSubmission = recordSubmission;
  this.noThrottle = noThrottle;
  this.additionalDumps = [];
  this.extraKeyVals = extraExtraKeyVals;
  // mimic deferred Promise behavior
  this.submitStatusPromise = new Promise((resolve, reject) => {
    this.resolveSubmitStatusPromise = resolve;
    this.rejectSubmitStatusPromise = reject;
  });
}

Submitter.prototype = {
  submitSuccess: async function Submitter_submitSuccess(ret) {
    // Write out the details file to submitted
    await writeSubmittedReportAsync(ret.CrashID, ret.ViewURL);

    try {
      let toDelete = [this.dump, this.extra];

      if (this.memory) {
        toDelete.push(this.memory);
      }

      for (let entry of this.additionalDumps) {
        toDelete.push(entry.dump);
      }

      await Promise.all(
        toDelete.map(path => {
          return IOUtils.remove(path, { ignoreAbsent: true });
        })
      );
    } catch (ex) {
      console.error(ex);
    }

    this.notifyStatus(SUCCESS, ret);
    this.cleanup();
  },

  cleanup: function Submitter_cleanup() {
    // drop some references just to be nice
    this.iframe = null;
    this.dump = null;
    this.extra = null;
    this.memory = null;
    this.additionalDumps = null;
    // remove this object from the list of active submissions
    let idx = CrashSubmit._activeSubmissions.indexOf(this);
    if (idx != -1) {
      CrashSubmit._activeSubmissions.splice(idx, 1);
    }
  },

  parseResponse: function Submitter_parseResponse(response) {
    let parsedResponse = {};

    for (let line of response.split("\n")) {
      let data = line.split("=");

      if (
        (data.length == 2 &&
          data[0] == "CrashID" &&
          SUBMISSION_REGEX.test(data[1])) ||
        data[0] == "ViewURL"
      ) {
        parsedResponse[data[0]] = data[1];
      }
    }

    return parsedResponse;
  },

  submitForm: function Submitter_submitForm() {
    if (!("ServerURL" in this.extraKeyVals)) {
      return false;
    }
    let serverURL = this.extraKeyVals.ServerURL;
    delete this.extraKeyVals.ServerURL;

    // Override the submission URL from the environment
    let envOverride = Services.env.get("MOZ_CRASHREPORTER_URL");
    if (envOverride != "") {
      serverURL = envOverride;
    }

    let xhr = new XMLHttpRequest();
    xhr.open("POST", serverURL, true);

    let formData = new FormData();

    // tell the server not to throttle this if requested
    this.extraKeyVals.Throttleable = this.noThrottle ? "0" : "1";

    // add the data
    let payload = Object.assign({}, this.extraKeyVals);
    let json = new Blob([JSON.stringify(payload)], {
      type: "application/json",
    });
    formData.append("extra", json);

    // add the minidumps
    let promises = [
      File.createFromFileName(this.dump, {
        type: "application/octet-stream",
      }).then(file => {
        formData.append("upload_file_minidump", file);
      }),
    ];

    if (this.memory) {
      promises.push(
        File.createFromFileName(this.memory, {
          type: "application/gzip",
        }).then(file => {
          formData.append("memory_report", file);
        })
      );
    }

    if (this.additionalDumps.length) {
      let names = [];
      for (let i of this.additionalDumps) {
        names.push(i.name);
        promises.push(
          File.createFromFileName(i.dump, {
            type: "application/octet-stream",
          }).then(file => {
            formData.append("upload_file_minidump_" + i.name, file);
          })
        );
      }
    }

    let manager = Services.crashmanager;
    let submissionID = manager.generateSubmissionID();

    xhr.addEventListener("readystatechange", () => {
      if (xhr.readyState == 4) {
        let ret =
          xhr.status === 200 ? this.parseResponse(xhr.responseText) : {};
        let failmsg;
        if (xhr.status !== 200) {
          const xhrStatus = code => {
            switch (code) {
              case 400:
                return xhr.responseText;

              case 413:
                return "Discarded=post_body_too_large";

              default:
                return "Discarded=unknown_error";
            }
          };
          let err = xhrStatus(xhr.status);
          if (err.length && err.startsWith("Discarded=")) {
            // Place the error code after, otherwise JS will complain we start
            // with a number when dealing with the telemetry value on JS side
            const errMsg = `${err.split("Discarded=")[1]}_${xhr.status}`;
            Glean.crashSubmission.collectorErrors[errMsg].add();
            failmsg = `received bad response: ${xhr.status} ${err}`;
          }
          if (xhr.status === 0) {
            Glean.crashSubmission.channelStatus[xhr.channel.status].add();
          }
        }
        let submitted = !!ret.CrashID;
        let p = Promise.resolve();

        if (this.recordSubmission) {
          let result = submitted
            ? manager.SUBMISSION_RESULT_OK
            : manager.SUBMISSION_RESULT_FAILED;
          p = manager.addSubmissionResult(
            this.id,
            submissionID,
            new Date(),
            result
          );
          if (submitted) {
            manager.setRemoteCrashID(this.id, ret.CrashID);
          }
        }

        p.then(() => {
          if (submitted) {
            this.submitSuccess(ret);
          } else {
            this.notifyStatus(
              FAILED,
              failmsg || "did not receive a crash ID in server response"
            );
            this.cleanup();
          }
        });
      }
    });

    let p = Promise.all(promises);
    let id = this.id;

    if (this.recordSubmission) {
      p = p.then(() => {
        return manager.addSubmissionAttempt(id, submissionID, new Date());
      });
    }
    p.then(() => {
      xhr.send(formData);
    });
    return true;
  },

  // `ret` is determined based on `status`:
  // * `SUCCESS` - `ret` should be an object with submission details.
  // * `FAILED` - `ret` should be a string with additional information about
  //   the failure.
  notifyStatus: function Submitter_notify(status, ret) {
    let propBag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
      Ci.nsIWritablePropertyBag2
    );
    propBag.setPropertyAsAString("minidumpID", this.id);
    if (status == SUCCESS) {
      propBag.setPropertyAsAString("serverCrashID", ret.CrashID);
    }

    let extraKeyValsBag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
      Ci.nsIWritablePropertyBag2
    );
    for (let key in this.extraKeyVals) {
      extraKeyValsBag.setPropertyAsAString(key, this.extraKeyVals[key]);
    }
    propBag.setPropertyAsInterface("extra", extraKeyValsBag);

    Services.obs.notifyObservers(propBag, "crash-report-status", status);

    switch (status) {
      case SUCCESS:
        this.resolveSubmitStatusPromise(ret.CrashID);
        Glean.crashSubmission.success.add(1);
        break;
      case FAILED:
        this.rejectSubmitStatusPromise(`${FAILED}: ${ret}`);
        Glean.crashSubmission.failure.add(1);
        break;
      default:
      // no callbacks invoked.
    }
  },

  readAnnotations: async function Submitter_readAnnotations(extra) {
    // These annotations are used only by the crash reporter client and should
    // not be submitted to Socorro.
    const strippedAnnotations = [
      "StackTraces",
      "TelemetryClientId",
      "TelemetryProfileGroupId",
      "TelemetrySessionId",
      "TelemetryServerURL",
    ];
    let extraKeyVals = await IOUtils.readJSON(extra);

    this.extraKeyVals = { ...extraKeyVals, ...this.extraKeyVals };
    strippedAnnotations.forEach(key => delete this.extraKeyVals[key]);
  },

  submit: async function Submitter_submit() {
    if (this.recordSubmission) {
      await Services.crashmanager.ensureCrashIsPresent(this.id);
    }

    let [dump, extra, memory] = getPendingMinidump(this.id);
    let [dumpExists, extraExists, memoryExists] = await Promise.all([
      IOUtils.exists(dump),
      IOUtils.exists(extra),
      IOUtils.exists(memory),
    ]);

    if (!dumpExists || !extraExists) {
      this.notifyStatus(
        FAILED,
        `missing ${!dumpExists ? "dump" : "extra"} file`
      );
      this.cleanup();
      return this.submitStatusPromise;
    }

    this.dump = dump;
    this.extra = extra;
    this.memory = memoryExists ? memory : null;
    await this.readAnnotations(extra);

    let additionalDumps = [];

    if ("additional_minidumps" in this.extraKeyVals) {
      let dumpsExistsPromises = [];
      let names = this.extraKeyVals.additional_minidumps.split(",");

      for (let name of names) {
        let [dump /* , extra, memory */] = getPendingMinidump(
          this.id + "-" + name
        );

        dumpsExistsPromises.push(IOUtils.exists(dump));
        additionalDumps.push({ name, dump });
      }

      let dumpsExist = await Promise.all(dumpsExistsPromises);
      let allDumpsExist = dumpsExist.every(exists => exists);

      if (!allDumpsExist) {
        this.notifyStatus(
          FAILED,
          "one or more additional minidumps are missing"
        );
        this.cleanup();
        return this.submitStatusPromise;
      }
    }

    this.notifyStatus(SUBMITTING);
    this.additionalDumps = additionalDumps;

    if (!(await this.submitForm())) {
      this.notifyStatus(
        FAILED,
        "no url available to which to send crash reports"
      );
      this.cleanup();
    }

    return this.submitStatusPromise;
  },
};

// ===================================
// External API goes here
export var CrashSubmit = {
  // A set of strings representing how a user submitted a given crash
  SUBMITTED_FROM_AUTO: "Auto",
  SUBMITTED_FROM_INFOBAR: "Infobar",
  SUBMITTED_FROM_ABOUT_CRASHES: "AboutCrashes",
  SUBMITTED_FROM_CRASH_TAB: "CrashedTab",

  /**
   * Submit the crash report named id.dmp from the "pending" directory.
   *
   * @param id
   *        Filename (minus .dmp extension) of the minidump to submit.
   * @param submittedFrom
   *        One of the SUBMITTED_FROM_* constants representing how the
   *        user submitted this crash.
   * @param params
   *        An object containing any of the following optional parameters:
   *        - recordSubmission
   *          If true, a submission event is recorded in CrashManager.
   *        - noThrottle
   *          If true, this crash report should be submitted with
   *          the Throttleable annotation set to "0" indicating that
   *          it should be processed right away. This should be set
   *          when the report is being submitted and the user expects
   *          to see the results immediately. Defaults to false.
   *        - extraExtraKeyVals
   *          An object whose key-value pairs will be merged with the data from
   *          the ".extra" file submitted with the report.  The properties of
   *          this object will override properties of the same name in the
   *          .extra file.
   *
   *  @return a Promise that is fulfilled with the server crash ID when the
   *          submission succeeds and rejected otherwise.
   */
  submit: function CrashSubmit_submit(id, submittedFrom, params) {
    params = params || {};
    let recordSubmission = false;
    let noThrottle = false;
    let extraExtraKeyVals = {};

    if ("recordSubmission" in params) {
      recordSubmission = params.recordSubmission;
    }

    if ("noThrottle" in params) {
      noThrottle = params.noThrottle;
    }

    if ("extraExtraKeyVals" in params) {
      extraExtraKeyVals = params.extraExtraKeyVals;
    }

    extraExtraKeyVals.SubmittedFrom = submittedFrom;

    let submitter = new Submitter(
      id,
      recordSubmission,
      noThrottle,
      extraExtraKeyVals
    );
    CrashSubmit._activeSubmissions.push(submitter);
    return submitter.submit();
  },

  /**
   * Delete the minidup from the "pending" directory.
   *
   * @param id
   *        Filename (minus .dmp extension) of the minidump to delete.
   *
   * @return a Promise that is fulfilled when the minidump is deleted and
   *         rejected otherwise
   */
  delete: async function CrashSubmit_delete(id) {
    await Promise.all(
      getPendingMinidump(id).map(path => {
        return IOUtils.remove(path);
      })
    );
  },

  /**
   * Add a .dmg.ignore file along side the .dmp file to indicate that the user
   * shouldn't be prompted to submit this crash report again.
   *
   * @param id
   *        Filename (minus .dmp extension) of the report to ignore
   *
   * @return a Promise that is fulfilled when (if) the .dmg.ignore is created
   *         and rejected otherwise.
   */
  ignore: async function CrashSubmit_ignore(id) {
    let [dump /* , extra, memory */] = getPendingMinidump(id);
    const ignorePath = `${dump}.ignore`;
    await IOUtils.writeUTF8(ignorePath, "", { mode: "create" });
  },

  /**
   * Get the list of pending crash IDs, excluding those marked to be ignored
   * @param minFileDate
   *     A Date object. Any files last modified before that date will be ignored
   *
   * @return a Promise that is fulfilled with an array of string, each
   *         being an ID as expected to be passed to submit() or ignore()
   */
  pendingIDs: async function CrashSubmit_pendingIDs(minFileDate) {
    let ids = [];
    let pendingDir = getDir("pending");

    if (!(await IOUtils.exists(pendingDir))) {
      return ids;
    }

    let children;
    try {
      children = await IOUtils.getChildren(pendingDir);
    } catch (ex) {
      console.error(ex);
      throw ex;
    }

    try {
      const entries = Object.create(null);
      const ignored = Object.create(null);

      for (const child of children) {
        const info = await IOUtils.stat(child);

        if (info.type !== "directory") {
          const name = PathUtils.filename(child);
          const matches = name.match(/(.+)\.dmp$/);
          if (matches) {
            const id = matches[1];

            if (UUID_REGEX.test(id)) {
              entries[id] = info;
            }
          } else {
            // maybe it's a .ignore file
            const matchesIgnore = name.match(/(.+)\.dmp.ignore$/);
            if (matchesIgnore) {
              const id = matchesIgnore[1];

              if (UUID_REGEX.test(id)) {
                ignored[id] = true;
              }
            }
          }
        }
      }

      for (const [id, info] of Object.entries(entries)) {
        const accessDate = new Date(info.lastAccessed);
        if (!(id in ignored) && accessDate > minFileDate) {
          ids.push(id);
        }
      }
    } catch (ex) {
      console.error(ex);
      throw ex;
    }

    return ids;
  },

  /**
   * Prune the saved dumps.
   *
   * @return a Promise that is fulfilled when the daved dumps are deleted and
   *         rejected otherwise
   */
  pruneSavedDumps: async function CrashSubmit_pruneSavedDumps() {
    const KEEP = 10;

    let dirEntries = [];
    let pendingDir = getDir("pending");

    let children;
    try {
      children = await IOUtils.getChildren(pendingDir);
    } catch (ex) {
      if (DOMException.isInstance(ex) && ex.name === "NotFoundError") {
        return [];
      }

      throw ex;
    }

    for (const path of children) {
      let infoPromise;
      try {
        infoPromise = IOUtils.stat(path);
      } catch (ex) {
        console.error(ex);
        throw ex;
      }

      const name = PathUtils.filename(path);

      if (name.match(/(.+)\.extra$/)) {
        dirEntries.push({
          name,
          path,
          infoPromise,
        });
      }
    }

    dirEntries.sort(async (a, b) => {
      let dateA = (await a.infoPromise).lastModified;
      let dateB = (await b.infoPromise).lastModified;

      if (dateA < dateB) {
        return -1;
      }

      if (dateB < dateA) {
        return 1;
      }

      return 0;
    });

    if (dirEntries.length > KEEP) {
      let toDelete = [];

      for (let i = 0; i < dirEntries.length - KEEP; ++i) {
        let extra = dirEntries[i];
        let matches = extra.leafName.match(/(.+)\.extra$/);

        if (matches) {
          let pathComponents = PathUtils.split(extra.path);
          pathComponents[pathComponents.length - 1] = matches[1];
          let path = PathUtils.join(...pathComponents);

          toDelete.push(extra.path, `${path}.dmp`, `${path}.memory.json.gz`);
        }
      }

      await Promise.all(
        toDelete.map(path => {
          return IOUtils.remove(path, { ignoreAbsent: true });
        })
      );
    }
  },

  // List of currently active submit objects
  _activeSubmissions: [],
};
PK
!<�Dɐa+a+(modules/CredentialChooserService.sys.mjs/**
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "localization", () => {
  return new Localization(["preview/credentialChooser.ftl"], true);
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "IDNService",
  "@mozilla.org/network/idn-service;1",
  "nsIIDNService"
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "TESTING_MODE",
  "dom.security.credentialmanagement.chooser.testing.enabled",
  false
);

/**
 * Set an image element's src attribute to a data: url of the favicon for a
 * given origin, defaulting to the browser default favicon.
 *
 * @param {HTMLImageElement} icon The image Element that should have source be the icon result.
 * @param {string} origin The origin whose favicon should be used.
 */
async function setIconToFavicon(icon, origin) {
  try {
    let iconData = await lazy.PlacesUtils.promiseFaviconData(origin);
    icon.src = iconData.uri.spec;
  } catch {
    icon.src = "chrome://global/skin/icons/defaultFavicon.svg";
  }
}

/**
 * Class implementing the nsICredentialChooserService.
 *
 * This class shows UI to the user for the Credential Chooser for the
 * Credential Management API.
 *
 * @class CredentialChooserService
 */
export class CredentialChooserService {
  classID = Components.ID("{673ddc19-03e2-4b30-a868-06297e8fed89}");
  QueryInterface = ChromeUtils.generateQI(["nsICredentialChooserService"]);

  /**
   * @typedef {object} CredentialArgument
   * @property {string} id - The unique identifier for the credential.
   * @property {string} type - The type of the credential.
   * @property {string} [origin] - The origin associated to the credential.
   * @property {UIHints} [uiHints] - UI hints for the credential.
   */

  /**
   * @typedef {object} UIHints
   * @property {string} [name] - The display name for the credential.
   * @property {string} [iconURL] - The data URL of the icon for the credential.
   * @property {number} [expiresAfter] - The expiration time for the UI hint.
   */

  /**
   * This function displays the credential chooser UI, allowing the user to make an identity choice.
   * Once the user makes a choice from the credentials provided, or dismisses the prompt, we will
   * call the callback with that credential, or null in the case of a dismiss.
   *
   * We also support UI-less testing via choices provided by picking any credential with ID 'wpt-pick-me'
   * if the preference 'dom.security.credentialmanagement.chooser.testing.enabled' is true.
   *
   * @param {BrowsingContext} browsingContext The browsing context of the window calling the Credential Management API.
   * @param {Array<CredentialArgument>} credentials The credentials the user should choose from.
   * @param {nsICredentialChosenCallback} callback A callback to return the user's credential choice to.
   * @returns {nsresult}
   */
  async showCredentialChooser(browsingContext, credentials, callback) {
    if (!callback) {
      callback = { notify: () => {} };
    }
    if (!credentials.length) {
      return Cr.NS_ERROR_INVALID_ARG;
    }

    // If we are not an active BC, return no choice and bail out.
    if (!browsingContext?.currentWindowContext?.isActiveInTab) {
      callback.notify(null);
      return Cr.NS_OK;
    }

    if (lazy.TESTING_MODE) {
      let match = credentials.find(cred => cred.id == "wpt-pick-me");
      if (match) {
        if (browsingContext.currentWindowGlobal?.documentPrincipal) {
          Services.perms.addFromPrincipal(
            browsingContext.currentWindowGlobal.documentPrincipal,
            "credential-allow-silent-access^" + match.origin,
            Ci.nsIPermissionManager.ALLOW_ACTION,
            Ci.nsIPermissionManager.EXPIRE_SESSION
          );
          Services.perms.addFromPrincipal(
            browsingContext.currentWindowGlobal.documentPrincipal,
            "credential-allow-silent-access",
            Ci.nsIPermissionManager.ALLOW_ACTION,
            Ci.nsIPermissionManager.EXPIRE_SESSION
          );
        }
        callback.notify("wpt-pick-me");
      } else {
        callback.notify(null);
      }
      return Cr.NS_OK;
    }

    let browser = browsingContext.topFrameElement;
    if (browser?.tagName != "browser") {
      return Cr.NS_ERROR_INVALID_ARG;
    }

    let headerTextElement = browser.ownerDocument.getElementById(
      "credential-chooser-header-text"
    );
    let host = browser.ownerGlobal.gIdentityHandler.getHostForDisplay();
    browser.ownerDocument.l10n.setAttributes(
      headerTextElement,
      "credential-chooser-header",
      {
        host,
      }
    );

    let faviconPromises = [];

    let localizationPromise = lazy.localization.formatMessages([
      { id: "credential-chooser-sign-in-button" },
      { id: "credential-chooser-cancel-button" },
    ]);

    let listBox = browser.ownerDocument.getElementById(
      "credential-chooser-entry-selector-container"
    );
    while (listBox.firstChild) {
      listBox.removeChild(listBox.lastChild);
    }
    let itemTemplate = browser.ownerDocument.getElementById(
      "template-credential-entry-list-item"
    );
    for (let [index, credential] of credentials.entries()) {
      let newItem = itemTemplate.content.firstElementChild.cloneNode(true);
      // Add the new radio button, including pre-selection and the callback
      let [radio] = newItem.getElementsByClassName(
        "identity-credential-list-item-radio"
      );
      radio.value = index;
      if (index == 0) {
        radio.checked = true;
      }

      let providerURL = new URL(credential.origin);
      let displayDomain = lazy.IDNService.convertToDisplayIDN(
        providerURL.host,
        {}
      );

      let [primary] = newItem.getElementsByClassName(
        "identity-credential-list-item-label-primary"
      );
      let [secondary] = newItem.getElementsByClassName(
        "identity-credential-list-item-label-secondary"
      );
      let [icon] = newItem.getElementsByClassName(
        "identity-credential-list-item-icon"
      );

      if (
        credential.uiHints &&
        (credential.uiHints.expiresAfter == null ||
          credential.uiHints.expiresAfter > 0)
      ) {
        primary.textContent = credential.uiHints.name;
        browser.ownerDocument.l10n.setAttributes(
          secondary,
          "credential-chooser-host-descriptor",
          {
            provider: displayDomain,
          }
        );
        secondary.hidden = false;
        icon.src = credential.uiHints.iconURL;
      } else {
        let doneWithFavicon = setIconToFavicon(icon, credential.origin);
        faviconPromises.push(doneWithFavicon);
        browser.ownerDocument.l10n.setAttributes(
          primary,
          "credential-chooser-identity",
          {
            provider: displayDomain,
          }
        );
      }

      // Add the item to the DOM!
      listBox.append(newItem);
    }

    // wait for the labels to be localized before showing the panel
    let [accept, cancel] = await localizationPromise;
    let cancelLabel = cancel.attributes.find(x => x.name == "label").value;
    let cancelKey = cancel.attributes.find(x => x.name == "accesskey").value;
    let acceptLabel = accept.attributes.find(x => x.name == "label").value;
    let acceptKey = accept.attributes.find(x => x.name == "accesskey").value;

    // wait for icons to be set to prevent favicon jank
    await Promise.all(faviconPromises);

    // Construct the necessary arguments for notification behavior
    let options = {
      hideClose: true,
      removeOnDismissal: true,
      eventCallback: (topic, _nextRemovalReason, _isCancel) => {
        if (topic == "removed") {
          callback.notify(null);
        }
      },
    };
    let mainAction = {
      label: acceptLabel,
      accessKey: acceptKey,
      callback() {
        let result = listBox.querySelector(
          ".identity-credential-list-item-radio:checked"
        ).value;
        if (browsingContext.currentWindowGlobal?.documentPrincipal) {
          Services.perms.addFromPrincipal(
            browsingContext.currentWindowGlobal.documentPrincipal,
            "credential-allow-silent-access^" +
              credentials[parseInt(result)].origin,
            Ci.nsIPermissionManager.ALLOW_ACTION,
            Ci.nsIPermissionManager.EXPIRE_SESSION
          );
          Services.perms.addFromPrincipal(
            browsingContext.currentWindowGlobal.documentPrincipal,
            "credential-allow-silent-access",
            Ci.nsIPermissionManager.ALLOW_ACTION,
            Ci.nsIPermissionManager.EXPIRE_SESSION
          );
        }
        callback.notify(credentials[parseInt(result, 10)].id);
      },
    };
    let secondaryActions = [
      {
        label: cancelLabel,
        accessKey: cancelKey,
        callback() {
          callback.notify(null);
        },
      },
    ];
    browser.ownerGlobal.PopupNotifications.show(
      browser,
      "credential-chooser",
      "",
      "identity-credential-notification-icon",
      mainAction,
      secondaryActions,
      options
    );

    return Cr.NS_OK;
  }

  /**
   * Dismiss the credential chooser dialog for this browsing context's window.
   *
   * @param {BrowsingContext} browsingContext - The top browsing context of the window calling the Credential Management API
   */
  cancelCredentialChooser(browsingContext) {
    let browser = browsingContext.top.embedderElement;
    if (browser?.tagName != "browser") {
      return;
    }
    let notification = browser.ownerGlobal.PopupNotifications.getNotification(
      "credential-chooser",
      browser
    );
    if (notification) {
      browser.ownerGlobal.PopupNotifications.remove(notification, true);
    }
  }

  /**
   * A service function to help any UI. Fetches and serializes images to
   * data urls, which can be used in chrome UI.
   *
   * @param {Window} window - Window which should perform the fetch
   * @param {nsIURI} uri - Icon location to be fetched from
   * @returns {Promise<string, Error>} The data URI encoded as a string representing the icon that was loaded
   */
  async fetchImageToDataURI(window, uri) {
    if (uri.protocol === "data:") {
      return uri.href;
    }
    let request = new window.Request(uri.spec, { mode: "cors" });
    request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_IMAGE);
    let blob;
    let response = await window.fetch(request);
    if (!response.ok) {
      return Promise.reject(new Error("HTTP failure on Fetch"));
    }
    blob = await response.blob();
    return new Promise((resolve, reject) => {
      let reader = new FileReader();
      reader.onloadend = () => resolve(reader.result);
      reader.onerror = reject;
      reader.readAsDataURL(blob);
    });
  }
}
PK
!<z����modules/Credentials.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This module implements client-side key stretching for use in Firefox
 * Accounts account creation and login.
 *
 * See https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol
 */

import { Log } from "resource://gre/modules/Log.sys.mjs";

import { CryptoUtils } from "resource://services-crypto/utils.sys.mjs";

import { CommonUtils } from "resource://services-common/utils.sys.mjs";

const PROTOCOL_VERSION = "identity.mozilla.com/picl/v1/";
const PBKDF2_ROUNDS = 1000;
const STRETCHED_PW_LENGTH_BYTES = 32;
const HKDF_SALT = CommonUtils.hexToBytes("00");
const HKDF_LENGTH = 32;

// loglevel preference should be one of: "FATAL", "ERROR", "WARN", "INFO",
// "CONFIG", "DEBUG", "TRACE" or "ALL". We will be logging error messages by
// default.
const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel";
let LOG_LEVEL = Log.Level.Error;
try {
  LOG_LEVEL =
    Services.prefs.getPrefType(PREF_LOG_LEVEL) ==
      Ci.nsIPrefBranch.PREF_STRING &&
    Services.prefs.getStringPref(PREF_LOG_LEVEL);
} catch (e) {}

var log = Log.repository.getLogger("Identity.FxAccounts");
log.level = LOG_LEVEL;
log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));

export var Credentials = Object.freeze({
  /**
   * Make constants accessible to tests
   */
  constants: {
    PROTOCOL_VERSION,
    PBKDF2_ROUNDS,
    STRETCHED_PW_LENGTH_BYTES,
    HKDF_SALT,
    HKDF_LENGTH,
  },

  /**
   * KW function from https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol
   *
   * keyWord derivation for use as a salt.
   *
   *
   *   @param {String} context  String for use in generating salt
   *
   *   @return {bitArray} the salt
   *
   * Note that PROTOCOL_VERSION does not refer in any way to the version of the
   * Firefox Accounts API.
   */
  keyWord(context) {
    return CommonUtils.stringToBytes(PROTOCOL_VERSION + context);
  },

  /**
   * KWE function from https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol
   *
   * keyWord extended with a name and an email.
   *
   *   @param {String} name The name of the salt
   *   @param {String} email The email of the user.
   *
   *   @return {bitArray} the salt combination with the namespace
   *
   * Note that PROTOCOL_VERSION does not refer in any way to the version of the
   * Firefox Accounts API.
   */
  keyWordExtended(name, email) {
    return CommonUtils.stringToBytes(PROTOCOL_VERSION + name + ":" + email);
  },

  setup(emailInput, passwordInput, options = {}) {
    return new Promise(resolve => {
      log.debug("setup credentials for " + emailInput);

      let hkdfSalt = options.hkdfSalt || HKDF_SALT;
      let hkdfLength = options.hkdfLength || HKDF_LENGTH;
      let stretchedPWLength =
        options.stretchedPassLength || STRETCHED_PW_LENGTH_BYTES;
      let pbkdf2Rounds = options.pbkdf2Rounds || PBKDF2_ROUNDS;

      let result = {};

      let password = CommonUtils.encodeUTF8(passwordInput);
      let salt = this.keyWordExtended("quickStretch", emailInput);

      let runnable = async () => {
        let start = Date.now();
        let quickStretchedPW = await CryptoUtils.pbkdf2Generate(
          password,
          salt,
          pbkdf2Rounds,
          stretchedPWLength
        );

        result.quickStretchedPW = quickStretchedPW;

        result.authPW = await CryptoUtils.hkdfLegacy(
          quickStretchedPW,
          hkdfSalt,
          this.keyWord("authPW"),
          hkdfLength
        );

        result.unwrapBKey = await CryptoUtils.hkdfLegacy(
          quickStretchedPW,
          hkdfSalt,
          this.keyWord("unwrapBkey"),
          hkdfLength
        );

        log.debug("Credentials set up after " + (Date.now() - start) + " ms");
        resolve(result);
      };

      Services.tm.dispatchToMainThread(runnable);
      log.debug("Dispatched thread for credentials setup crypto work");
    });
  },
});
PK
!<=L�
�7�7modules/CreditCard.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// The list of known and supported credit card network ids ("types")
// This list mirrors the networks from dom/payments/BasicCardPayment.cpp
// and is defined by https://www.w3.org/Payments/card-network-ids
const SUPPORTED_NETWORKS = Object.freeze([
  "amex",
  "cartebancaire",
  "diners",
  "discover",
  "jcb",
  "mastercard",
  "mir",
  "unionpay",
  "visa",
]);

// This lists stores lower cased variations of popular credit card network
// names for matching against strings.
export const NETWORK_NAMES = {
  "american express": "amex",
  "master card": "mastercard",
  "union pay": "unionpay",
};

// Based on https://en.wikipedia.org/wiki/Payment_card_number
//
// Notice:
//   - CarteBancaire (`4035`, `4360`) is now recognized as Visa.
//   - UnionPay (`63--`) is now recognized as Discover.
// This means that the order matters.
// First we'll try to match more specific card,
// and if that doesn't match we'll test against the more generic range.
const CREDIT_CARD_IIN = [
  { type: "amex", start: 34, end: 34, len: 15 },
  { type: "amex", start: 37, end: 37, len: 15 },
  { type: "cartebancaire", start: 4035, end: 4035, len: 16 },
  { type: "cartebancaire", start: 4360, end: 4360, len: 16 },
  // We diverge from Wikipedia here, because Diners card
  // support length of 14-19.
  { type: "diners", start: 300, end: 305, len: [14, 19] },
  { type: "diners", start: 3095, end: 3095, len: [14, 19] },
  { type: "diners", start: 36, end: 36, len: [14, 19] },
  { type: "diners", start: 38, end: 39, len: [14, 19] },
  { type: "discover", start: 6011, end: 6011, len: [16, 19] },
  { type: "discover", start: 622126, end: 622925, len: [16, 19] },
  { type: "discover", start: 624000, end: 626999, len: [16, 19] },
  { type: "discover", start: 628200, end: 628899, len: [16, 19] },
  { type: "discover", start: 64, end: 65, len: [16, 19] },
  { type: "jcb", start: 3528, end: 3589, len: [16, 19] },
  { type: "mastercard", start: 2221, end: 2720, len: 16 },
  { type: "mastercard", start: 51, end: 55, len: 16 },
  { type: "mir", start: 2200, end: 2204, len: 16 },
  { type: "unionpay", start: 62, end: 62, len: [16, 19] },
  { type: "unionpay", start: 81, end: 81, len: [16, 19] },
  { type: "visa", start: 4, end: 4, len: 16 },
].sort((a, b) => b.start - a.start);

export class CreditCard {
  /**
   * A CreditCard object represents a credit card, with
   * number, name, expiration, network, and CCV.
   * The number is the only required information when creating
   * an object, all other members are optional. The number
   * is validated during construction and will throw if invalid.
   *
   * @param {string} name, optional
   * @param {string} number
   * @param {string} expirationString, optional
   * @param {string|number} expirationMonth, optional
   * @param {string|number} expirationYear, optional
   * @param {string} network, optional
   * @param {string|number} ccv, optional
   * @param {string} encryptedNumber, optional
   * @throws if number is an invalid credit card number
   */
  constructor({
    name,
    number,
    expirationString,
    expirationMonth,
    expirationYear,
    network,
    ccv,
    encryptedNumber,
  }) {
    this._name = name;
    this._unmodifiedNumber = number;
    this._encryptedNumber = encryptedNumber;
    this._ccv = ccv;
    this.number = number;
    let { month, year } = CreditCard.normalizeExpiration({
      expirationString,
      expirationMonth,
      expirationYear,
    });
    this._expirationMonth = month;
    this._expirationYear = year;
    this.network = network;
  }

  set name(value) {
    this._name = value;
  }

  set expirationMonth(value) {
    if (typeof value == "undefined") {
      this._expirationMonth = undefined;
      return;
    }
    this._expirationMonth = CreditCard.normalizeExpirationMonth(value);
  }

  get expirationMonth() {
    return this._expirationMonth;
  }

  set expirationYear(value) {
    if (typeof value == "undefined") {
      this._expirationYear = undefined;
      return;
    }
    this._expirationYear = CreditCard.normalizeExpirationYear(value);
  }

  get expirationYear() {
    return this._expirationYear;
  }

  set expirationString(value) {
    let { month, year } = CreditCard.parseExpirationString(value);
    this.expirationMonth = month;
    this.expirationYear = year;
  }

  set ccv(value) {
    this._ccv = value;
  }

  get number() {
    return this._number;
  }

  /**
   * Sets the number member of a CreditCard object. If the number
   * is not valid according to the Luhn algorithm then the member
   * will get set to the empty string before throwing an exception.
   *
   * @param {string} value
   * @throws if the value is an invalid credit card number
   */
  set number(value) {
    if (value) {
      let normalizedNumber = CreditCard.normalizeCardNumber(value);
      // Based on the information on wiki[1], the shortest valid length should be
      // 12 digits (Maestro).
      // [1] https://en.wikipedia.org/wiki/Payment_card_number
      normalizedNumber = normalizedNumber.match(/^\d{12,}$/)
        ? normalizedNumber
        : "";
      this._number = normalizedNumber;
    } else {
      this._number = "";
    }

    if (value && !this.isValidNumber()) {
      this._number = "";
      throw new Error("Invalid credit card number");
    }
  }

  get network() {
    return this._network;
  }

  set network(value) {
    this._network = value || undefined;
  }

  // Implements the Luhn checksum algorithm as described at
  // http://wikipedia.org/wiki/Luhn_algorithm
  // Number digit lengths vary with network, but should fall within 12-19 range. [2]
  // More details at https://en.wikipedia.org/wiki/Payment_card_number
  isValidNumber() {
    if (!this._number) {
      return false;
    }

    // Remove dashes and whitespace
    const number = CreditCard.normalizeCardNumber(this._number);

    const len = number.length;
    if (len < 12 || len > 19) {
      return false;
    }

    if (!/^\d+$/.test(number)) {
      return false;
    }

    let total = 0;
    for (let i = 0; i < len; i++) {
      let ch = parseInt(number[len - i - 1], 10);
      if (i % 2 == 1) {
        // Double it, add digits together if > 10
        ch *= 2;
        if (ch > 9) {
          ch -= 9;
        }
      }
      total += ch;
    }
    return total % 10 == 0;
  }

  /**
   * Normalizes a credit card number.
   * @param {string} number
   * @return {string | null}
   * @memberof CreditCard
   */
  static normalizeCardNumber(number) {
    if (!number) {
      return null;
    }
    return number.replace(/[\-\s]/g, "");
  }

  /**
   * Attempts to match the number against known network identifiers.
   *
   * @param {string} ccNumber Credit card number with no spaces or special characters in it.
   *
   * @returns {string|null}
   */
  static getType(ccNumber) {
    if (!ccNumber) {
      return null;
    }

    for (let i = 0; i < CREDIT_CARD_IIN.length; i++) {
      const range = CREDIT_CARD_IIN[i];
      if (typeof range.len == "number") {
        if (range.len != ccNumber.length) {
          continue;
        }
      } else if (
        ccNumber.length < range.len[0] ||
        ccNumber.length > range.len[1]
      ) {
        continue;
      }

      const prefixLength = Math.floor(Math.log10(range.start)) + 1;
      const prefix = parseInt(ccNumber.substring(0, prefixLength), 10);
      if (prefix >= range.start && prefix <= range.end) {
        return range.type;
      }
    }
    return null;
  }

  /**
   * Attempts to retrieve a card network identifier based
   * on a name.
   *
   * @param {string|undefined|null} name
   *
   * @returns {string|null}
   */
  static getNetworkFromName(name) {
    if (!name) {
      return null;
    }
    let lcName = name.trim().toLowerCase().normalize("NFKC");
    if (SUPPORTED_NETWORKS.includes(lcName)) {
      return lcName;
    }
    for (let term in NETWORK_NAMES) {
      if (lcName.includes(term)) {
        return NETWORK_NAMES[term];
      }
    }
    return null;
  }

  /**
   * Returns true if the card number is valid and the
   * expiration date has not passed. Otherwise false.
   *
   * @returns {boolean}
   */
  isValid() {
    if (!this.isValidNumber()) {
      return false;
    }

    let currentDate = new Date();
    let currentYear = currentDate.getFullYear();
    if (this._expirationYear > currentYear) {
      return true;
    }

    // getMonth is 0-based, so add 1 because credit cards are 1-based
    let currentMonth = currentDate.getMonth() + 1;
    return (
      this._expirationYear == currentYear &&
      this._expirationMonth >= currentMonth
    );
  }

  get maskedNumber() {
    return CreditCard.getMaskedNumber(this._number);
  }

  get longMaskedNumber() {
    return CreditCard.getLongMaskedNumber(this._number);
  }

  /**
   * Get credit card display label. It should display masked numbers, the
   * cardholder's name, and the expiration date, separated by a commas.
   * In addition, the card type is provided in the accessibility label.
   */
  static getLabelInfo({ number, name, month, year, type }) {
    let formatSelector = ["number"];
    if (name) {
      formatSelector.push("name");
    }
    if (month && year) {
      formatSelector.push("expiration");
    }
    let stringId = `credit-card-label-${formatSelector.join("-")}-2`;
    return {
      id: stringId,
      args: {
        number: CreditCard.getMaskedNumber(number),
        name,
        month: month?.toString(),
        year: year?.toString(),
        type,
      },
    };
  }

  /**
   *
   * Please use getLabelInfo above, as it allows for localization.
   * @deprecated
   */
  static getLabel({ number, name }) {
    let parts = [];

    if (number) {
      parts.push(CreditCard.getMaskedNumber(number));
    }
    if (name) {
      parts.push(name);
    }
    return parts.join(", ");
  }

  static normalizeExpirationMonth(month) {
    month = parseInt(month, 10);
    if (isNaN(month) || month < 1 || month > 12) {
      return undefined;
    }
    return month;
  }

  static normalizeExpirationYear(year) {
    year = parseInt(year, 10);
    if (isNaN(year) || year < 0) {
      return undefined;
    }
    if (year < 100) {
      year += 2000;
    }
    return year;
  }

  static parseExpirationString(expirationString) {
    let rules = [
      {
        regex: /(?:^|\D)(\d{2})(\d{2})(?!\d)/,
      },
      {
        regex: /(?:^|\D)(\d{4})[-/](\d{1,2})(?!\d)/,
        yearIndex: 0,
        monthIndex: 1,
      },
      {
        regex: /(?:^|\D)(\d{1,2})[-/](\d{4})(?!\d)/,
        yearIndex: 1,
        monthIndex: 0,
      },
      {
        regex: /(?:^|\D)(\d{1,2})[-/](\d{1,2})(?!\d)/,
      },
      {
        regex: /(?:^|\D)(\d{2})(\d{2})(?!\d)/,
      },
    ];

    expirationString = expirationString.replaceAll(" ", "");
    for (let rule of rules) {
      let result = rule.regex.exec(expirationString);
      if (!result) {
        continue;
      }

      let year, month;
      const parsedResults = [parseInt(result[1], 10), parseInt(result[2], 10)];
      if (!rule.yearIndex || !rule.monthIndex) {
        month = parsedResults[0];
        if (month > 12) {
          year = parsedResults[0];
          month = parsedResults[1];
        } else {
          year = parsedResults[1];
        }
      } else {
        year = parsedResults[rule.yearIndex];
        month = parsedResults[rule.monthIndex];
      }

      if (month >= 1 && month <= 12 && (year < 100 || year > 2000)) {
        return { month, year };
      }
    }
    return { month: undefined, year: undefined };
  }

  static normalizeExpiration({
    expirationString,
    expirationMonth,
    expirationYear,
  }) {
    // Only prefer the string version if missing one or both parsed formats.
    let parsedExpiration = {};
    if (expirationString && (!expirationMonth || !expirationYear)) {
      parsedExpiration = CreditCard.parseExpirationString(expirationString);
    }
    return {
      month: CreditCard.normalizeExpirationMonth(
        parsedExpiration.month || expirationMonth
      ),
      year: CreditCard.normalizeExpirationYear(
        parsedExpiration.year || expirationYear
      ),
    };
  }

  static formatMaskedNumber(maskedNumber) {
    return "*".repeat(4) + maskedNumber.substr(-4);
  }

  static getMaskedNumber(number) {
    return "*".repeat(4) + " " + number.substr(-4);
  }

  static getLongMaskedNumber(number) {
    return "*".repeat(number.length - 4) + number.substr(-4);
  }

  static getCreditCardLogo(network) {
    const PATH = "chrome://formautofill/content/";
    const THIRD_PARTY_PATH = PATH + "third-party/";
    switch (network) {
      case "amex":
        return THIRD_PARTY_PATH + "cc-logo-amex.png";
      case "cartebancaire":
        return THIRD_PARTY_PATH + "cc-logo-cartebancaire.png";
      case "diners":
        return THIRD_PARTY_PATH + "cc-logo-diners.svg";
      case "discover":
        return THIRD_PARTY_PATH + "cc-logo-discover.png";
      case "jcb":
        return THIRD_PARTY_PATH + "cc-logo-jcb.svg";
      case "mastercard":
        return THIRD_PARTY_PATH + "cc-logo-mastercard.svg";
      case "mir":
        return THIRD_PARTY_PATH + "cc-logo-mir.svg";
      case "unionpay":
        return THIRD_PARTY_PATH + "cc-logo-unionpay.svg";
      case "visa":
        return THIRD_PARTY_PATH + "cc-logo-visa.svg";
      default:
        return PATH + "icon-credit-card-generic.svg";
    }
  }

  /*
   * Validates the number according to the Luhn algorithm. This
   * method does not throw an exception if the number is invalid.
   */
  static isValidNumber(number) {
    try {
      new CreditCard({ number });
    } catch (ex) {
      return false;
    }
    return true;
  }

  static isValidNetwork(network) {
    return SUPPORTED_NETWORKS.includes(network);
  }

  static getSupportedNetworks() {
    return SUPPORTED_NETWORKS;
  }

  /**
   * Localised names for supported networks are available in
   * `browser/preferences/formAutofill.ftl`.
   */
  static getNetworkL10nId(network) {
    return this.isValidNetwork(network)
      ? `autofill-card-network-${network}`
      : null;
  }
}
PK
!<�hL��&modules/CustomElementsListener.sys.mjs/* -*-  indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Set up Custom Elements for XUL and XHTML documents before anything else
// happens. Anything loaded here should be considered part of core XUL functionality.
// Any window-specific elements can be registered via <script> tags at the
// top of individual documents.
Services.obs.addObserver(
  {
    observe(doc) {
      if (
        doc.nodePrincipal.isSystemPrincipal &&
        (doc.contentType == "application/xhtml+xml" ||
          doc.contentType == "text/html") &&
        // People shouldn't be using our built-in custom elements in
        // system-principal about:blank anyway, and trying to support that
        // causes responsiveness regressions.  So let's not support it.
        doc.URL != "about:blank"
      ) {
        Services.scriptloader.loadSubScript(
          "chrome://global/content/customElements.js",
          doc.ownerGlobal
        );
      }
    },
  },
  "document-element-inserted"
);
PK
!<EaYp*p*"modules/DAPTelemetrySender.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { HPKEConfigManager } from "resource://gre/modules/HPKEConfigManager.sys.mjs";

let lazy = {};

ChromeUtils.defineLazyGetter(lazy, "logConsole", function () {
  return console.createInstance({
    prefix: "DAPTelemetrySender",
    maxLogLevelPref: "toolkit.telemetry.dap.logLevel",
  });
});
ChromeUtils.defineESModuleGetters(lazy, {
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
  DAPVisitCounter: "resource://gre/modules/DAPVisitCounter.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
  ObliviousHTTP: "resource://gre/modules/ObliviousHTTP.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gDapEndpoint",
  "toolkit.telemetry.dap.leader.url"
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gLeaderHpke",
  "toolkit.telemetry.dap.leader.hpke"
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gHelperHpke",
  "toolkit.telemetry.dap.helper.hpke"
);

/**
 * The purpose of this singleton is to handle sending of DAP telemetry data.
 * The current DAP draft standard is available here:
 * https://github.com/ietf-wg-ppm/draft-ietf-ppm-dap
 *
 * The specific purpose of this singleton is to make the necessary calls to fetch to do networking.
 */

export const DAPTelemetrySender = new (class {
  async startup() {
    await lazy.NimbusFeatures.dapTelemetry.ready();
    if (!lazy.NimbusFeatures.dapTelemetry.getVariable("enabled")) {
      return;
    }

    lazy.logConsole.debug("Performing DAP startup");

    if (lazy.NimbusFeatures.dapTelemetry.getVariable("visitCountingEnabled")) {
      lazy.DAPVisitCounter.startup();
    }

    if (lazy.NimbusFeatures.dapTelemetry.getVariable("task1Enabled")) {
      let tasks = [];
      lazy.logConsole.debug("Task 1 is enabled.");
      let task1_id =
        lazy.NimbusFeatures.dapTelemetry.getVariable("task1TaskId");
      if (task1_id !== undefined && task1_id != "") {
        /** @typedef { 'u8' | 'vecu8' | 'vecu16' } measurementtype */

        /**
         * @typedef {object} Task
         * @property {string} id - The task ID, base 64 encoded.
         * @property {number} time_precision - Timestamps (in s) are rounded to the nearest multiple of this.
         * @property {measurementtype} measurement_type - Defines measurements and aggregations used by this task. Effectively specifying the VDAF.
         */
        let task = {
          // this is testing task 1
          id: task1_id,
          time_precision: 300,
          measurement_type: "vecu8",
        };
        tasks.push(task);

        lazy.setTimeout(
          () => this.timedSendTestReports(tasks),
          this.timeout_value()
        );

        lazy.NimbusFeatures.dapTelemetry.onUpdate(async () => {
          if (typeof this.counters !== "undefined") {
            await this.sendTestReports(tasks, { reason: "nimbus-update" });
          }
        });
      }

      this._asyncShutdownBlocker = async () => {
        lazy.logConsole.debug(`Sending on shutdown.`);
        // Shorter timeout to prevent crashing due to blocking shutdown
        await this.sendTestReports(tasks, {
          timeout: 2_000,
          reason: "shutdown",
        });
      };

      lazy.AsyncShutdown.quitApplicationGranted.addBlocker(
        "DAPTelemetrySender: sending data",
        this._asyncShutdownBlocker
      );
    }
  }

  async sendTestReports(tasks, options = {}) {
    for (let task of tasks) {
      let measurement;
      if (task.measurement_type == "u8") {
        measurement = 3;
      } else if (task.measurement_type == "vecu8") {
        measurement = new Uint8Array(20);
        let r = Math.floor(Math.random() * 10);
        measurement[r] += 1;
        measurement[19] += 1;
      }

      await this.sendDAPMeasurement(task, measurement, options);
    }
  }

  async timedSendTestReports(tasks) {
    lazy.logConsole.debug("Sending on timer.");
    await this.sendTestReports(tasks);
    lazy.setTimeout(
      () => this.timedSendTestReports(tasks),
      this.timeout_value()
    );
  }

  timeout_value() {
    const MINUTE = 60 * 1000;
    return MINUTE * (9 + Math.random() * 2); // 9 - 11 minutes
  }

  /**
   * Internal testing function to verify the DAP aggregator keys match current
   * values advertised by servers.
   */
  async checkHpkeKeys() {
    async function check_key(url, expected) {
      let response = await fetch(url + "/hpke_config");
      let body = await response.arrayBuffer();
      let actual = ChromeUtils.base64URLEncode(body, { pad: false });
      if (actual != expected) {
        throw new Error(`HPKE for ${url} does not match`);
      }
    }
    await Promise.allSettled([
      await check_key(
        Services.prefs.getStringPref("toolkit.telemetry.dap.leader.url"),
        Services.prefs.getStringPref("toolkit.telemetry.dap.leader.hpke")
      ),
      await check_key(
        Services.prefs.getStringPref("toolkit.telemetry.dap.helper.url"),
        Services.prefs.getStringPref("toolkit.telemetry.dap.helper.hpke")
      ),
    ]);
  }

  /**
   * Creates a DAP report for a specific task from a measurement and sends it.
   *
   * @param {Task} task
   *   Definition of the task for which the measurement was taken.
   * @param {number|Array<Number>} measurement
   *   The measured value for which a report is generated.
   * @param {object} options
   * @param {number} options.timeout
   *   The timeout for request in milliseconds. Defaults to 30s.
   * @param {string} options.reason
   *   A string to indicate the reason for triggering a submission. This is
   *   currently ignored and not recorded.
   * @param {string} options.ohttp_relay
   * @param {Uint8Array} options.ohttp_hpke
   *   If an OHTTP relay is specified, the reports are uploaded over OHTTP.
   */
  async sendDAPMeasurement(task, measurement, options = {}) {
    try {
      const controller = new AbortController();
      lazy.setTimeout(() => controller.abort(), options.timeout ?? 30_000);

      let keys = {
        leader_hpke: HPKEConfigManager.decodeKey(lazy.gLeaderHpke),
        helper_hpke: HPKEConfigManager.decodeKey(lazy.gHelperHpke),
      };

      let report = this.generateReport(task, measurement, keys);

      await this.sendReport(
        lazy.gDapEndpoint,
        task.id,
        report,
        controller.signal,
        options
      );
    } catch (e) {
      if (e.name === "AbortError") {
        lazy.logConsole.error("Aborted DAP report generation: ", e);
      } else {
        lazy.logConsole.error("DAP report generation failed: " + e);
      }

      throw e;
    }
  }

  /*
   * @typedef {object} AggregatorKeys
   * @property {Uint8Array} leader_hpke - The leader's DAP HPKE key.
   * @property {Uint8Array} helper_hpke - The helper's DAP HPKE key.
   */

  /**
   * Generates the encrypted DAP report.
   *
   * @param {Task} task
   *   Definition of the task for which the measurement was taken.
   * @param {number|Array<number>} measurement
   *   The measured value for which a report is generated.
   * @param {AggregatorKeys} keys
   *   The DAP encryption keys for each aggregator.
   *
   * @returns {ArrayBuffer} The generated binary report data.
   */
  generateReport(task, measurement, keys) {
    let encoder = null;
    switch (task.measurement_type) {
      case "u8":
        encoder = Services.DAPTelemetry.GetReportU8;
        break;
      case "vecu8":
        encoder = Services.DAPTelemetry.GetReportVecU8;
        break;
      case "vecu16":
        encoder = Services.DAPTelemetry.GetReportVecU16;
        break;
      default:
        throw new Error(
          `Unknown measurement type for task ${task.id}: ${task.measurement_type}`
        );
    }

    let task_id = new Uint8Array(
      ChromeUtils.base64URLDecode(task.id, { padding: "ignore" })
    );

    let reportOut = {};
    encoder(
      keys.leader_hpke,
      keys.helper_hpke,
      measurement,
      task_id,
      task.time_precision,
      reportOut
    );
    return new Uint8Array(reportOut.value).buffer;
  }

  /**
   * Sends a report to the leader.
   *
   * @param {string} leader_endpoint
   *   The URL for the leader.
   * @param {string} task_id
   *   Base64 encoded task_id as it appears in the upload path.
   * @param {ArrayBuffer} report
   *   Raw bytes of the TLS encoded report.
   * @param {AbortSignal} abortSignal
   *   Can be used to cancel network requests. Does not cancel computation.
   * @param {object} options
   * @param {string} options.ohttp_relay
   * @param {Uint8Array} options.ohttp_hpke
   *   If an OHTTP relay is specified, the reports are uploaded over OHTTP. In
   *   this case, the OHTTP and DAP keys must be provided and this code will not
   *   attempt to fetch them.
   *
   * @returns Promise
   * @resolves {undefined} Once the attempt to send the report completes, whether or not it was successful.
   */
  async sendReport(leader_endpoint, task_id, report, abortSignal, options) {
    const upload_path = leader_endpoint + "/tasks/" + task_id + "/reports";
    try {
      let requestOptions = {
        method: "PUT",
        headers: { "Content-Type": "application/dap-report" },
        body: report,
        signal: abortSignal,
      };
      let response;
      if (options.ohttp_relay) {
        response = await lazy.ObliviousHTTP.ohttpRequest(
          options.ohttp_relay,
          options.ohttp_hpke,
          upload_path,
          requestOptions
        );
      } else {
        response = await fetch(upload_path, requestOptions);
      }

      if (response.status != 200) {
        const content_type = response.headers.get("content-type");
        if (content_type && content_type === "application/json") {
          // A JSON error from the DAP server.
          let error = await response.json();
          throw new Error(
            `Sending failed. HTTP response: ${response.status} ${response.statusText}. Error: ${error.type} ${error.title}`
          );
        } else {
          // A different error, e.g. from a load-balancer.
          let error = await response.text();
          throw new Error(
            `Sending failed. HTTP response: ${response.status} ${response.statusText}. Error: ${error}`
          );
        }
      } else {
        lazy.logConsole.debug("DAP report sent");
      }
    } catch (err) {
      if (err.name === "AbortError") {
        lazy.logConsole.error("Aborted DAP report sending: ", err);
      } else {
        lazy.logConsole.error("Failed to send report: ", err);
      }

      throw err;
    }
  }
})();
PK
!<"�b��modules/DAPVisitCounter.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { DAPTelemetrySender } from "./DAPTelemetrySender.sys.mjs";

let lazy = {};

ChromeUtils.defineLazyGetter(lazy, "logConsole", function () {
  return console.createInstance({
    prefix: "DAPVisitCounter",
    maxLogLevelPref: "toolkit.telemetry.dap.logLevel",
  });
});
ChromeUtils.defineESModuleGetters(lazy, {
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

export const DAPVisitCounter = new (class {
  startup() {
    this._asyncShutdownBlocker = async () => {
      lazy.logConsole.debug(`Sending on shutdown.`);
      await this.send(2 * 1000, "shutdown");
    };

    lazy.AsyncShutdown.quitApplicationGranted.addBlocker(
      "DAPVisitCounter: sending data",
      this._asyncShutdownBlocker
    );

    const listener = events => {
      // Even using the event.hidden flag there mayb be some double counting
      // here. It would have to be fixed in the Places API.
      for (const event of events) {
        lazy.logConsole.debug(`Visited: ${event.url}`);
        if (event.hidden) {
          continue;
        }
        for (const counter of this.counters) {
          for (const pattern of counter.patterns) {
            if (pattern.matches(event.url)) {
              lazy.logConsole.debug(`${pattern.pattern} matched!`);
              counter.count += 1;
            }
          }
        }
      }
    };

    lazy.NimbusFeatures.dapTelemetry.onUpdate(async () => {
      if (typeof this.counters !== "undefined") {
        await this.send(30 * 1000, "nimbus-update");
      }
      this.initialize_counters();
    });

    if (typeof this.counters === "undefined") {
      this.initialize_counters();
    }

    lazy.PlacesUtils.observers.addListener(["page-visited"], listener);

    lazy.setTimeout(() => this.timed_send(), this.timeout_value());
  }

  initialize_counters() {
    let experiments = lazy.NimbusFeatures.dapTelemetry.getVariable(
      "visitCountingExperimentList"
    );

    this.counters = [];
    // This allows two different formats for distributing the URLs for the
    // experiment. The experiments get quite large and over 4096 bytes they
    // result in a warning (when mirrored in a pref as in this case).
    if (Array.isArray(experiments)) {
      for (const experiment of experiments) {
        let counter = { experiment, count: 0, patterns: [] };
        this.counters.push(counter);
        for (const url of experiment.urls) {
          let mpattern = new MatchPattern(url);
          counter.patterns.push(mpattern);
        }
      }
    } else {
      for (const [task, urls] of Object.entries(experiments)) {
        for (const [idx, url] of urls.entries()) {
          const fullUrl = `*://${url}/*`;

          this.counters.push({
            experiment: {
              task_id: task,
              task_veclen: 20,
              bucket: idx,
            },
            count: 0,
            patterns: [new MatchPattern(fullUrl)],
          });
        }
      }
    }
  }

  async timed_send() {
    lazy.logConsole.debug("Sending on timer.");
    await this.send(30 * 1000, "periodic");
    lazy.setTimeout(() => this.timed_send(), this.timeout_value());
  }

  timeout_value() {
    const MINUTE = 60 * 1000;
    return MINUTE * (9 + Math.random() * 2); // 9 - 11 minutes
  }

  async send(timeout, reason) {
    let collected_measurements = new Map();
    for (const counter of this.counters) {
      if (!collected_measurements.has(counter.experiment.task_id)) {
        collected_measurements.set(
          counter.experiment.task_id,
          new Uint8Array(counter.experiment.task_veclen)
        );
      }
      collected_measurements.get(counter.experiment.task_id)[
        counter.experiment.bucket
      ] = counter.count;
      counter.count = 0;
    }

    let send_promises = [];
    for (const [task_id, measurement] of collected_measurements) {
      let task = {
        id: task_id,
        time_precision: 60,
        measurement_type: "vecu8",
      };

      send_promises.push(
        DAPTelemetrySender.sendDAPMeasurement(task, measurement, {
          timeout,
          reason,
        })
      );
    }

    try {
      await Promise.all(send_promises);
    } catch (e) {
      lazy.logConsole.error("Failed to send report: ", e);
    }
  }

  show() {
    for (const counter of this.counters) {
      lazy.logConsole.info(
        `Experiment: ${counter.experiment.url} -> ${counter.count}`
      );
    }
    return this.counters;
  }
})();
PK
!<�K�9!9!#modules/DateTimePickerPanel.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export var DateTimePickerPanel = class {
  constructor(element) {
    this.element = element;

    this.TIME_PICKER_WIDTH = "13em";
    this.TIME_PICKER_HEIGHT = "22em";
    this.DATE_PICKER_WIDTH = "24em";
    this.DATE_PICKER_HEIGHT = "27em";
  }

  get dateTimePopupFrame() {
    let frame = this.element.querySelector("#dateTimePopupFrame");
    if (!frame) {
      frame = this.element.ownerDocument.createXULElement("iframe");
      frame.id = "dateTimePopupFrame";
      this.element.appendChild(frame);
    }
    return frame;
  }

  openPicker(type, rect, detail) {
    if (type == "datetime-local") {
      type = "date";
    }
    this.type = type;
    this.pickerState = {};
    // TODO: Resize picker according to content zoom level
    this.element.style.fontSize = "10px";
    switch (type) {
      case "time": {
        this.detail = detail;
        this.dateTimePopupFrame.addEventListener("load", this, true);
        this.dateTimePopupFrame.setAttribute(
          "src",
          "chrome://global/content/timepicker.xhtml"
        );
        this.dateTimePopupFrame.style.width = this.TIME_PICKER_WIDTH;
        this.dateTimePopupFrame.style.height = this.TIME_PICKER_HEIGHT;
        break;
      }
      case "date": {
        this.detail = detail;
        this.dateTimePopupFrame.addEventListener("load", this, true);
        this.dateTimePopupFrame.setAttribute(
          "src",
          "chrome://global/content/datepicker.xhtml"
        );
        this.dateTimePopupFrame.style.width = this.DATE_PICKER_WIDTH;
        this.dateTimePopupFrame.style.height = this.DATE_PICKER_HEIGHT;
        break;
      }
    }
    this.element.openPopupAtScreenRect(
      "after_start",
      rect.left,
      rect.top,
      rect.width,
      rect.height,
      false,
      false
    );
  }

  closePicker(clear) {
    if (clear) {
      this.element.dispatchEvent(new CustomEvent("DateTimePickerValueCleared"));
    }
    this.pickerState = {};
    this.type = undefined;
    this.dateTimePopupFrame.removeEventListener("load", this, true);
    this.dateTimePopupFrame.contentDocument.removeEventListener(
      "message",
      this
    );
    this.dateTimePopupFrame.setAttribute("src", "");
    this.element.hidePopup();
  }

  setPopupValue(data) {
    switch (this.type) {
      case "time": {
        this.postMessageToPicker({
          name: "PickerSetValue",
          detail: data.value,
        });
        break;
      }
      case "date": {
        const { year, month, day } = data.value;
        this.postMessageToPicker({
          name: "PickerSetValue",
          detail: {
            year,
            // Month value from input box starts from 1 instead of 0
            month: month == undefined ? undefined : month - 1,
            day,
          },
        });
        break;
      }
    }
  }

  initPicker(detail) {
    let locale = new Services.intl.Locale(
      Services.locale.webExposedLocales[0],
      {
        calendar: "gregory",
      }
    ).toString();

    // Workaround for bug 1418061, while we wait for resolution of
    // http://bugs.icu-project.org/trac/ticket/13592: drop the PT region code,
    // because it results in "abbreviated" day names that are too long;
    // the region-less "pt" locale has shorter forms that are better here.
    locale = locale.replace(/^pt-PT/i, "pt");

    const dir = Services.locale.isAppLocaleRTL ? "rtl" : "ltr";

    switch (this.type) {
      case "time": {
        const { hour, minute } = detail.value;
        const format = detail.format || "12";

        this.postMessageToPicker({
          name: "PickerInit",
          detail: {
            hour,
            minute,
            format,
            locale,
            min: detail.min,
            max: detail.max,
            step: detail.step,
          },
        });
        break;
      }
      case "date": {
        const { year, month, day } = detail.value;
        const { firstDayOfWeek, weekends } = this.getCalendarInfo(locale);

        const monthDisplayNames = new Services.intl.DisplayNames(locale, {
          type: "month",
          style: "short",
          calendar: "gregory",
        });
        const monthStrings = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].map(
          month => monthDisplayNames.of(month)
        );

        const weekdayDisplayNames = new Services.intl.DisplayNames(locale, {
          type: "weekday",
          style: "abbreviated",
          calendar: "gregory",
        });
        const weekdayStrings = [
          // Weekdays starting Sunday (7) to Saturday (6).
          7, 1, 2, 3, 4, 5, 6,
        ].map(weekday => weekdayDisplayNames.of(weekday));

        this.postMessageToPicker({
          name: "PickerInit",
          detail: {
            year,
            // Month value from input box starts from 1 instead of 0
            month: month == undefined ? undefined : month - 1,
            day,
            firstDayOfWeek,
            weekends,
            monthStrings,
            weekdayStrings,
            locale,
            dir,
            min: detail.min,
            max: detail.max,
            step: detail.step,
            stepBase: detail.stepBase,
          },
        });
        break;
      }
    }
  }

  /**
   * @param {Boolean} passAllValues: Pass spinner values regardless if they've been set/changed or not
   */
  setInputBoxValue(passAllValues) {
    switch (this.type) {
      case "time": {
        const { hour, minute, isHourSet, isMinuteSet, isDayPeriodSet } =
          this.pickerState;
        const isAnyValueSet = isHourSet || isMinuteSet || isDayPeriodSet;
        if (passAllValues && isAnyValueSet) {
          this.sendPickerValueChanged({ hour, minute });
        } else {
          this.sendPickerValueChanged({
            hour: isHourSet || isDayPeriodSet ? hour : undefined,
            minute: isMinuteSet ? minute : undefined,
          });
        }
        break;
      }
      case "date": {
        this.sendPickerValueChanged(this.pickerState);
        break;
      }
    }
  }

  sendPickerValueChanged(value) {
    switch (this.type) {
      case "time": {
        this.element.dispatchEvent(
          new CustomEvent("DateTimePickerValueChanged", {
            detail: {
              hour: value.hour,
              minute: value.minute,
            },
          })
        );
        break;
      }
      case "date": {
        this.element.dispatchEvent(
          new CustomEvent("DateTimePickerValueChanged", {
            detail: {
              year: value.year,
              // Month value from input box starts from 1 instead of 0
              month: value.month == undefined ? undefined : value.month + 1,
              day: value.day,
            },
          })
        );
        break;
      }
    }
  }

  getCalendarInfo(locale) {
    const calendarInfo = Services.intl.getCalendarInfo(locale);

    // Day of week from calendarInfo starts from 1 as Monday to 7 as Sunday,
    // so they need to be mapped to JavaScript convention with 0 as Sunday
    // and 6 as Saturday
    function toDateWeekday(day) {
      return day === 7 ? 0 : day;
    }

    let firstDayOfWeek = toDateWeekday(calendarInfo.firstDayOfWeek),
      weekend = calendarInfo.weekend;

    let weekends = weekend.map(toDateWeekday);

    return {
      firstDayOfWeek,
      weekends,
    };
  }

  handleEvent(aEvent) {
    switch (aEvent.type) {
      case "load": {
        this.initPicker(this.detail);
        this.dateTimePopupFrame.contentWindow.addEventListener("message", this);
        break;
      }
      case "message": {
        this.handleMessage(aEvent);
        break;
      }
    }
  }

  handleMessage(aEvent) {
    if (
      !this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal
    ) {
      return;
    }

    switch (aEvent.data.name) {
      case "PickerPopupChanged": {
        this.pickerState = aEvent.data.detail;
        this.setInputBoxValue();
        break;
      }
      case "ClosePopup": {
        this.closePicker(aEvent.data.detail);
        break;
      }
    }
  }

  postMessageToPicker(data) {
    if (
      this.dateTimePopupFrame.contentDocument.nodePrincipal.isSystemPrincipal
    ) {
      this.dateTimePopupFrame.contentWindow.postMessage(data, "*");
    }
  }
};
PK
!<W��4UUmodules/DefaultCLH.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const nsICommandLineHandler = Ci.nsICommandLineHandler;
const nsIPrefBranch = Ci.nsIPrefBranch;
const nsIWindowWatcher = Ci.nsIWindowWatcher;
const nsIProperties = Ci.nsIProperties;
const nsIFile = Ci.nsIFile;
const nsISimpleEnumerator = Ci.nsISimpleEnumerator;

/**
 * This file provides a generic default command-line handler.
 *
 * It opens the chrome window specified by the pref "toolkit.defaultChromeURI"
 * with the flags specified by the pref "toolkit.defaultChromeFeatures"
 * or "chrome,dialog=no,all" is it is not available.
 * The arguments passed to the window are the nsICommandLine instance.
 *
 * It doesn't do anything if the pref "toolkit.defaultChromeURI" is unset.
 */

function getDirectoryService() {
  return Cc["@mozilla.org/file/directory_service;1"].getService(nsIProperties);
}

export function nsDefaultCLH() {}
nsDefaultCLH.prototype = {
  classID: Components.ID("{6ebc941a-f2ff-4d56-b3b6-f7d0b9d73344}"),

  /* nsISupports */

  QueryInterface: ChromeUtils.generateQI([nsICommandLineHandler]),

  /* nsICommandLineHandler */

  handle: function clh_handle(cmdLine) {
    var printDir;
    while ((printDir = cmdLine.handleFlagWithParam("print-xpcom-dir", false))) {
      var out = 'print-xpcom-dir("' + printDir + '"): ';
      try {
        out += getDirectoryService().get(printDir, nsIFile).path;
      } catch (e) {
        out += "<Not Provided>";
      }

      dump(out + "\n");
      console.error(out);
    }

    var printDirList;
    while (
      (printDirList = cmdLine.handleFlagWithParam("print-xpcom-dirlist", false))
    ) {
      out = 'print-xpcom-dirlist("' + printDirList + '"): ';
      try {
        for (let file of getDirectoryService().get(
          printDirList,
          nsISimpleEnumerator
        )) {
          out += file.path + ";";
        }
      } catch (e) {
        out += "<Not Provided>";
      }

      dump(out + "\n");
      console.error(out);
    }

    if (cmdLine.handleFlag("silent", false)) {
      cmdLine.preventDefault = true;
    }

    if (cmdLine.preventDefault) {
      return;
    }

    var prefs =
      Cc["@mozilla.org/preferences-service;1"].getService(nsIPrefBranch);

    try {
      var singletonWindowType = prefs.getCharPref(
        "toolkit.singletonWindowType"
      );

      var win = Services.wm.getMostRecentWindow(singletonWindowType);
      if (win) {
        win.focus();
        cmdLine.preventDefault = true;
        return;
      }
    } catch (e) {}

    // if the pref is missing, ignore the exception
    try {
      var chromeURI = prefs.getCharPref("toolkit.defaultChromeURI");

      var flags = prefs.getCharPref(
        "toolkit.defaultChromeFeatures",
        "chrome,dialog=no,all"
      );

      var wwatch =
        Cc["@mozilla.org/embedcomp/window-watcher;1"].getService(
          nsIWindowWatcher
        );
      wwatch.openWindow(null, chromeURI, "_blank", flags, cmdLine);
    } catch (e) {}
  },

  helpInfo: "",
};
PK
!<�D���1�1modules/DeferredTask.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Sets up a function or an asynchronous task whose execution can be triggered
 * after a defined delay.  Multiple attempts to run the task before the delay
 * has passed are coalesced.  The task cannot be re-entered while running, but
 * can be executed again after a previous run finished.
 *
 * A common use case occurs when a data structure should be saved into a file
 * every time the data changes, using asynchronous calls, and multiple changes
 * to the data may happen within a short time:
 *
 *   let saveDeferredTask = new DeferredTask(async function() {
 *     await OS.File.writeAtomic(...);
 *     // Any uncaught exception will be reported.
 *   }, 2000);
 *
 *   // The task is ready, but will not be executed until requested.
 *
 * The "arm" method can be used to start the internal timer that will result in
 * the eventual execution of the task.  Multiple attempts to arm the timer don't
 * introduce further delays:
 *
 *   saveDeferredTask.arm();
 *
 *   // The task will be executed in 2 seconds from now.
 *
 *   await waitOneSecond();
 *   saveDeferredTask.arm();
 *
 *   // The task will be executed in 1 second from now.
 *
 * The timer can be disarmed to reset the delay, or just to cancel execution:
 *
 *   saveDeferredTask.disarm();
 *   saveDeferredTask.arm();
 *
 *   // The task will be executed in 2 seconds from now.
 *
 * When the internal timer fires and the execution of the task starts, the task
 * cannot be canceled anymore.  It is however possible to arm the timer again
 * during the execution of the task, in which case the task will need to finish
 * before the timer is started again, thus guaranteeing a time of inactivity
 * between executions that is at least equal to the provided delay.
 *
 * The "finalize" method can be used to ensure that the task terminates
 * properly.  The promise it returns is resolved only after the last execution
 * of the task is finished.  To guarantee that the task is executed for the
 * last time, the method prevents any attempt to arm the timer again.
 *
 * If the timer is already armed when the "finalize" method is called, then the
 * task is executed immediately.  If the task was already running at this point,
 * then one last execution from start to finish will happen again, immediately
 * after the current execution terminates.  If the timer is not armed, the
 * "finalize" method only ensures that any running task terminates.
 *
 * For example, during shutdown, you may want to ensure that any pending write
 * is processed, using the latest version of the data if the timer is armed:
 *
 *   AsyncShutdown.profileBeforeChange.addBlocker(
 *     "Example service: shutting down",
 *     () => saveDeferredTask.finalize()
 *   );
 *
 * Instead, if you are going to delete the saved data from disk anyways, you
 * might as well prevent any pending write from starting, while still ensuring
 * that any write that is currently in progress terminates, so that the file is
 * not in use anymore:
 *
 *   saveDeferredTask.disarm();
 *   saveDeferredTask.finalize().then(() => OS.File.remove(...))
 *                              .then(null, Components.utils.reportError);
 */

// Globals

const Timer = Components.Constructor(
  "@mozilla.org/timer;1",
  "nsITimer",
  "initWithCallback"
);

// DeferredTask

/**
 * Sets up a task whose execution can be triggered after a delay.
 *
 * @param aTaskFn
 *        Function to execute.  If the function returns a promise, the task is
 *        not considered complete until that promise resolves.  This
 *        task is never re-entered while running.
 * @param aDelayMs
 *        Time between executions, in milliseconds.  Multiple attempts to run
 *        the task before the delay has passed are coalesced.  This time of
 *        inactivity is guaranteed to pass between multiple executions of the
 *        task, except on finalization, when the task may restart immediately
 *        after the previous execution finished.
 * @param aIdleTimeoutMs
 *        The maximum time to wait for an idle slot on the main thread after
 *        aDelayMs have elapsed. If omitted, waits indefinitely for an idle
 *        callback.
 */
export var DeferredTask = function (aTaskFn, aDelayMs, aIdleTimeoutMs) {
  this._taskFn = aTaskFn;
  this._delayMs = aDelayMs;
  this._timeoutMs = aIdleTimeoutMs;
  this._caller = new Error().stack.split("\n", 2)[1];
  let markerString = `delay: ${aDelayMs}ms`;
  if (aIdleTimeoutMs) {
    markerString += `, idle timeout: ${aIdleTimeoutMs}`;
  }
  ChromeUtils.addProfilerMarker(
    "DeferredTask",
    { captureStack: true },
    markerString
  );
};

DeferredTask.prototype = {
  /**
   * Function to execute.
   */
  _taskFn: null,

  /**
   * Time between executions, in milliseconds.
   */
  _delayMs: null,

  /**
   * Indicates whether the task is currently requested to start again later,
   * regardless of whether it is currently running.
   */
  get isArmed() {
    return this._armed;
  },
  _armed: false,

  /**
   * Indicates whether the task is currently running.  This is always true when
   * read from code inside the task function, but can also be true when read
   * from external code, in case the task is an asynchronous function.
   */
  get isRunning() {
    return !!this._runningPromise;
  },

  /**
   * Promise resolved when the current execution of the task terminates, or null
   * if the task is not currently running.
   */
  _runningPromise: null,

  /**
   * nsITimer used for triggering the task after a delay, or null in case the
   * task is running or there is no task scheduled for execution.
   */
  _timer: null,

  /**
   * Actually starts the timer with the delay specified on construction.
   */
  _startTimer() {
    let callback, timer;
    if (this._timeoutMs === 0) {
      callback = () => this._timerCallback();
    } else {
      callback = () => {
        this._startIdleDispatch(() => {
          // _timer could have changed by now:
          // - to null if disarm() or finalize() has been called.
          // - to a new nsITimer if disarm() was called, followed by arm().
          // In either case, don't invoke _timerCallback any more.
          if (this._timer === timer) {
            this._timerCallback();
          }
        }, this._timeoutMs);
      };
    }
    timer = new Timer(callback, this._delayMs, Ci.nsITimer.TYPE_ONE_SHOT);
    this._timer = timer;
  },

  /**
   * Dispatches idle task. Can be overridden for testing by test_DeferredTask.
   */
  _startIdleDispatch(callback, timeout) {
    ChromeUtils.idleDispatch(callback, { timeout });
  },

  /**
   * Requests the execution of the task after the delay specified on
   * construction.  Multiple calls don't introduce further delays.  If the task
   * is running, the delay will start when the current execution finishes.
   *
   * The task will always be executed on a different tick of the event loop,
   * even if the delay specified on construction is zero.  Multiple "arm" calls
   * within the same tick of the event loop are guaranteed to result in a single
   * execution of the task.
   *
   * @note By design, this method doesn't provide a way for the caller to detect
   *       when the next execution terminates, or collect a result.  In fact,
   *       doing that would often result in duplicate processing or logging.  If
   *       a special operation or error logging is needed on completion, it can
   *       be better handled from within the task itself, for example using a
   *       try/catch/finally clause in the task.  The "finalize" method can be
   *       used in the common case of waiting for completion on shutdown.
   */
  arm() {
    if (this._finalized) {
      throw new Error("Unable to arm timer, the object has been finalized.");
    }

    this._armed = true;

    // In case the timer callback is running, do not create the timer now,
    // because this will be handled by the timer callback itself.  Also, the
    // timer is not restarted in case it is already running.
    if (!this._runningPromise && !this._timer) {
      this._startTimer();
    }
  },

  /**
   * Cancels any request for a delayed the execution of the task, though the
   * task itself cannot be canceled in case it is already running.
   *
   * This method stops any currently running timer, thus the delay will restart
   * from its original value in case the "arm" method is called again.
   */
  disarm() {
    this._armed = false;
    if (this._timer) {
      // Calling the "cancel" method and discarding the timer reference makes
      // sure that the timer callback will not be called later, even if the
      // timer thread has already posted the timer event on the main thread.
      this._timer.cancel();
      this._timer = null;
    }
  },

  /**
   * Ensures that any pending task is executed from start to finish, while
   * preventing any attempt to arm the timer again.
   *
   * - If the task is running and the timer is armed, then one last execution
   *   from start to finish will happen again, immediately after the current
   *   execution terminates, then the returned promise will be resolved.
   * - If the task is running and the timer is not armed, the returned promise
   *   will be resolved when the current execution terminates.
   * - If the task is not running and the timer is armed, then the task is
   *   started immediately, and the returned promise resolves when the new
   *   execution terminates.
   * - If the task is not running and the timer is not armed, the method returns
   *   a resolved promise.
   *
   * @return {Promise}
   * @resolves After the last execution of the task is finished.
   * @rejects Never.
   */
  finalize() {
    if (this._finalized) {
      throw new Error("The object has been already finalized.");
    }
    this._finalized = true;

    // If the timer is armed, it means that the task is not running but it is
    // scheduled for execution.  Cancel the timer and run the task immediately,
    // so we don't risk blocking async shutdown longer than necessary.
    if (this._timer) {
      this.disarm();
      this._timerCallback();
    }

    // Wait for the operation to be completed, or resolve immediately.
    if (this._runningPromise) {
      return this._runningPromise;
    }
    return Promise.resolve();
  },
  _finalized: false,

  /**
   * Whether the DeferredTask has been finalized, and it cannot be armed anymore.
   */
  get isFinalized() {
    return this._finalized;
  },

  /**
   * Timer callback used to run the delayed task.
   */
  _timerCallback() {
    let runningDeferred = Promise.withResolvers();

    // All these state changes must occur at the same time directly inside the
    // timer callback, to prevent race conditions and to ensure that all the
    // methods behave consistently even if called from inside the task.  This
    // means that the assignment of "this._runningPromise" must complete before
    // the task gets a chance to start.
    this._timer = null;
    this._armed = false;
    this._runningPromise = runningDeferred.promise;

    runningDeferred.resolve(
      (async () => {
        // Execute the provided function asynchronously.
        await this._runTask();

        // Now that the task has finished, we check the state of the object to
        // determine if we should restart the task again.
        if (this._armed) {
          if (!this._finalized) {
            this._startTimer();
          } else {
            // Execute the task again immediately, for the last time.  The isArmed
            // property should return false while the task is running, and should
            // remain false after the last execution terminates.
            this._armed = false;
            await this._runTask();
          }
        }

        // Indicate that the execution of the task has finished.  This happens
        // synchronously with the previous state changes in the function.
        this._runningPromise = null;
      })().catch(console.error)
    );
  },

  /**
   * Executes the associated task and catches exceptions.
   */
  async _runTask() {
    let startTime = Cu.now();
    try {
      await this._taskFn();
    } catch (ex) {
      console.error(ex);
    } finally {
      ChromeUtils.addProfilerMarker(
        "DeferredTask",
        { startTime },
        this._caller
      );
    }
  },
};
PK
!<U��n�nmodules/DownloadHistory.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Provides access to downloads from previous sessions on platforms that store
 * them in a different location than session downloads.
 *
 * This module works with objects that are compatible with Download, while using
 * the Places interfaces internally. Some of the Places objects may also be
 * exposed to allow the consumers to integrate with history view commands.
 */

import { DownloadList } from "resource://gre/modules/DownloadList.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Downloads: "resource://gre/modules/Downloads.sys.mjs",
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
});

// Places query used to retrieve all history downloads for the related list.
const HISTORY_PLACES_QUERY = `place:transition=${Ci.nsINavHistoryService.TRANSITION_DOWNLOAD}&sort=${Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING}`;
const DESTINATIONFILEURI_ANNO = "downloads/destinationFileURI";
const METADATA_ANNO = "downloads/metaData";

const METADATA_STATE_FINISHED = 1;
const METADATA_STATE_FAILED = 2;
const METADATA_STATE_CANCELED = 3;
const METADATA_STATE_PAUSED = 4;
const METADATA_STATE_BLOCKED_PARENTAL = 6;
const METADATA_STATE_DIRTY = 8;

/**
 * Provides methods to retrieve downloads from previous sessions and store
 * downloads for future sessions.
 */
export let DownloadHistory = {
  /**
   * Retrieves the main DownloadHistoryList object which provides a unified view
   * on downloads from both previous browsing sessions and this session.
   *
   * @param type
   *        Determines which type of downloads from this session should be
   *        included in the list. This is Downloads.PUBLIC by default, but can
   *        also be Downloads.PRIVATE or Downloads.ALL.
   * @param maxHistoryResults
   *        Optional number that limits the amount of results the history query
   *        may return.
   *
   * @return {Promise}
   * @resolves The requested DownloadHistoryList object.
   * @rejects JavaScript exception.
   */
  async getList({ type = lazy.Downloads.PUBLIC, maxHistoryResults } = {}) {
    await DownloadCache.ensureInitialized();

    let key = `${type}|${maxHistoryResults ? maxHistoryResults : -1}`;
    if (!this._listPromises[key]) {
      this._listPromises[key] = lazy.Downloads.getList(type).then(list => {
        // When the amount of history downloads is capped, we request the list in
        // descending order, to make sure that the list can apply the limit.
        let query =
          HISTORY_PLACES_QUERY +
          (maxHistoryResults ? `&maxResults=${maxHistoryResults}` : "");

        return new DownloadHistoryList(list, query);
      });
    }

    return this._listPromises[key];
  },

  /**
   * This object is populated with one key for each type of download list that
   * can be returned by the getList method. The values are promises that resolve
   * to DownloadHistoryList objects.
   */
  _listPromises: {},

  async addDownloadToHistory(download) {
    if (
      download.source.isPrivate ||
      !lazy.PlacesUtils.history.canAddURI(
        lazy.PlacesUtils.toURI(download.source.url)
      )
    ) {
      return;
    }

    await DownloadCache.addDownload(download);

    await this._updateHistoryListData(download.source.url);
  },

  /**
   * Stores new detailed metadata for the given download in history. This is
   * normally called after a download finishes, fails, or is canceled.
   *
   * Failed or canceled downloads with partial data are not stored as paused,
   * because the information from the session download is required for resuming.
   *
   * @param download
   *        Download object whose metadata should be updated. If the object
   *        represents a private download, the call has no effect.
   */
  async updateMetaData(download) {
    if (
      download.source.isPrivate ||
      !download.stopped ||
      !lazy.PlacesUtils.history.canAddURI(
        lazy.PlacesUtils.toURI(download.source.url)
      )
    ) {
      return;
    }

    let state = METADATA_STATE_CANCELED;
    if (download.succeeded) {
      state = METADATA_STATE_FINISHED;
    } else if (download.error) {
      if (download.error.becauseBlockedByParentalControls) {
        state = METADATA_STATE_BLOCKED_PARENTAL;
      } else if (download.error.becauseBlockedByReputationCheck) {
        state = METADATA_STATE_DIRTY;
      } else {
        state = METADATA_STATE_FAILED;
      }
    }

    let metaData = {
      state,
      deleted: download.deleted,
      endTime: download.endTime,
    };
    if (download.succeeded) {
      metaData.fileSize = download.target.size;
    }

    // The verdict may still be present even if the download succeeded.
    if (download.error && download.error.reputationCheckVerdict) {
      metaData.reputationCheckVerdict = download.error.reputationCheckVerdict;
    }

    // This should be executed before any async parts, to ensure the cache is
    // updated before any notifications are activated.
    await DownloadCache.setMetadata(download.source.url, metaData);

    await this._updateHistoryListData(download.source.url);
  },

  async _updateHistoryListData(sourceUrl) {
    for (let key of Object.getOwnPropertyNames(this._listPromises)) {
      let downloadHistoryList = await this._listPromises[key];
      downloadHistoryList.updateForMetaDataChange(
        sourceUrl,
        DownloadCache.get(sourceUrl)
      );
    }
  },
};

/**
 * This cache exists:
 * - in order to optimize the load of DownloadsHistoryList, when Places
 *   annotations for history downloads must be read. In fact, annotations are
 *   stored in a single table, and reading all of them at once is much more
 *   efficient than an individual query.
 * - to avoid needing to do asynchronous reading of the database during download
 *   list updates, which are designed to be synchronous (to improve UI
 *   responsiveness).
 *
 * The cache is initialized the first time DownloadHistory.getList is called, or
 * when data is added.
 */
let DownloadCache = {
  _data: new Map(),
  _initializePromise: null,

  /**
   * Initializes the cache, loading the data from the places database.
   *
   * @return {Promise} Returns a promise that is resolved once the
   *                   initialization is complete.
   */
  ensureInitialized() {
    if (this._initializePromise) {
      return this._initializePromise;
    }
    this._initializePromise = (async () => {
      const placesObserver = new PlacesWeakCallbackWrapper(
        this.handlePlacesEvents.bind(this)
      );
      PlacesObservers.addListener(
        ["history-cleared", "page-removed"],
        placesObserver
      );

      let pageAnnos = await lazy.PlacesUtils.history.fetchAnnotatedPages([
        METADATA_ANNO,
        DESTINATIONFILEURI_ANNO,
      ]);

      let metaDataPages = pageAnnos.get(METADATA_ANNO);
      if (metaDataPages) {
        for (let { uri, content } of metaDataPages) {
          try {
            this._data.set(uri.href, JSON.parse(content));
          } catch (ex) {
            // Do nothing - JSON.parse could throw.
          }
        }
      }

      let destinationFilePages = pageAnnos.get(DESTINATIONFILEURI_ANNO);
      if (destinationFilePages) {
        for (let { uri, content } of destinationFilePages) {
          let newData = this.get(uri.href);
          newData.targetFileSpec = content;
          this._data.set(uri.href, newData);
        }
      }
    })();

    return this._initializePromise;
  },

  /**
   * This returns an object containing the meta data for the supplied URL.
   *
   * @param {String} url The url to get the meta data for.
   * @return {Object|null} Returns an empty object if there is no meta data found, or
   *                       an object containing the meta data. The meta data
   *                       will look like:
   *
   * { targetFileSpec, state, deleted, endTime, fileSize, ... }
   *
   * The targetFileSpec property is the value of "downloads/destinationFileURI",
   * while the other properties are taken from "downloads/metaData". Any of the
   * properties may be missing from the object.
   */
  get(url) {
    return this._data.get(url) || {};
  },

  /**
   * Adds a download to the cache and the places database.
   *
   * @param {Download} download The download to add to the database and cache.
   */
  async addDownload(download) {
    await this.ensureInitialized();

    let targetFile = new lazy.FileUtils.File(download.target.path);
    let targetUri = Services.io.newFileURI(targetFile);

    // This should be executed before any async parts, to ensure the cache is
    // updated before any notifications are activated.
    // Note: this intentionally overwrites any metadata as this is
    // the start of a new download.
    this._data.set(download.source.url, { targetFileSpec: targetUri.spec });

    let originalPageInfo = await lazy.PlacesUtils.history.fetch(
      download.source.url
    );

    let pageInfo = await lazy.PlacesUtils.history.insert({
      url: download.source.url,
      // In case we are downloading a file that does not correspond to a web
      // page for which the title is present, we populate the otherwise empty
      // history title with the name of the destination file, to allow it to be
      // visible and searchable in history results.
      title:
        (originalPageInfo && originalPageInfo.title) || targetFile.leafName,
      visits: [
        {
          // The start time is always available when we reach this point.
          date: download.startTime,
          transition: lazy.PlacesUtils.history.TRANSITIONS.DOWNLOAD,
          referrer: download.source.referrerInfo
            ? download.source.referrerInfo.originalReferrer
            : null,
        },
      ],
    });

    await lazy.PlacesUtils.history.update({
      annotations: new Map([["downloads/destinationFileURI", targetUri.spec]]),
      // XXX Bug 1479445: We shouldn't have to supply both guid and url here,
      // but currently we do.
      guid: pageInfo.guid,
      url: pageInfo.url,
    });
  },

  /**
   * Sets the metadata for a given url. If the cache already contains meta data
   * for the given url, it will be overwritten (note: the targetFileSpec will be
   * maintained).
   *
   * @param {String} url The url to set the meta data for.
   * @param {Object} metadata The new metaData to save in the cache.
   */
  async setMetadata(url, metadata) {
    await this.ensureInitialized();

    // This should be executed before any async parts, to ensure the cache is
    // updated before any notifications are activated.
    let existingData = this.get(url);
    let newData = { ...metadata };
    if ("targetFileSpec" in existingData) {
      newData.targetFileSpec = existingData.targetFileSpec;
    }
    this._data.set(url, newData);

    try {
      await lazy.PlacesUtils.history.update({
        annotations: new Map([[METADATA_ANNO, JSON.stringify(metadata)]]),
        url,
      });
    } catch (ex) {
      console.error(ex);
    }
  },

  QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]),

  handlePlacesEvents(events) {
    for (const event of events) {
      switch (event.type) {
        case "history-cleared": {
          this._data.clear();
          break;
        }
        case "page-removed": {
          if (event.isRemovedFromStore) {
            this._data.delete(event.url);
          }
          break;
        }
      }
    }
  },
};

/**
 * Represents a download from the browser history. This object implements part
 * of the interface of the Download object.
 *
 * While Download objects are shared between the public DownloadList and all the
 * DownloadHistoryList instances, multiple HistoryDownload objects referring to
 * the same item can be created for different DownloadHistoryList instances.
 *
 * @param placesNode
 *        The Places node from which the history download should be initialized.
 */
class HistoryDownload {
  constructor(placesNode) {
    this.placesNode = placesNode;

    // History downloads should get the referrer from Places (bug 829201).
    this.source = {
      url: placesNode.uri,
      isPrivate: false,
    };
    this.target = {
      path: undefined,
      exists: false,
      size: undefined,
    };

    // In case this download cannot obtain its end time from the Places metadata,
    // use the time from the Places node, that is the start time of the download.
    this.endTime = placesNode.time / 1000;
  }

  /**
   * DownloadSlot containing this history download.
   *
   * @type {DownloadSlot}
   */
  slot = null;

  /**
   * History downloads are never in progress.
   *
   * @type {Boolean}
   */
  stopped = true;

  /**
   * No percentage indication is shown for history downloads.
   *
   * @type {Boolean}
   */
  hasProgress = false;

  /**
   * History downloads cannot be restarted using their partial data, even if
   * they are indicated as paused in their Places metadata. The only way is to
   * use the information from a persisted session download, that will be shown
   * instead of the history download. In case this session download is not
   * available, we show the history download as canceled, not paused.
   *
   * @type {Boolean}
   */
  hasPartialData = false;

  /**
   * Pushes information from Places metadata into this object.
   */
  updateFromMetaData(metaData) {
    try {
      this.target.path = Cc["@mozilla.org/network/protocol;1?name=file"]
        .getService(Ci.nsIFileProtocolHandler)
        .getFileFromURLSpec(metaData.targetFileSpec).path;
    } catch (ex) {
      this.target.path = undefined;
    }

    if ("state" in metaData) {
      this.succeeded = metaData.state == METADATA_STATE_FINISHED;
      this.canceled =
        metaData.state == METADATA_STATE_CANCELED ||
        metaData.state == METADATA_STATE_PAUSED;
      this.endTime = metaData.endTime;
      this.deleted = metaData.deleted;

      // Recreate partial error information from the state saved in history.
      if (metaData.state == METADATA_STATE_FAILED) {
        this.error = { message: "History download failed." };
      } else if (metaData.state == METADATA_STATE_BLOCKED_PARENTAL) {
        this.error = { becauseBlockedByParentalControls: true };
      } else if (metaData.state == METADATA_STATE_DIRTY) {
        this.error = {
          becauseBlockedByReputationCheck: true,
          reputationCheckVerdict: metaData.reputationCheckVerdict || "",
        };
      } else {
        this.error = null;
      }

      // Normal history downloads are assumed to exist until the user interface
      // is refreshed, at which point these values may be updated.
      this.target.exists = true;
      this.target.size = metaData.fileSize;
    } else {
      // Metadata might be missing from a download that has started but hasn't
      // stopped already. Normally, this state is overridden with the one from
      // the corresponding in-progress session download. But if the browser is
      // terminated abruptly and additionally the file with information about
      // in-progress downloads is lost, we may end up using this state. We use
      // the failed state to allow the download to be restarted.
      //
      // On the other hand, if the download is missing the target file
      // annotation as well, it is just a very old one, and we can assume it
      // succeeded.
      this.succeeded = !this.target.path;
      this.error = this.target.path ? { message: "Unstarted download." } : null;
      this.canceled = false;
      this.deleted = false;

      // These properties may be updated if the user interface is refreshed.
      this.target.exists = false;
      this.target.size = undefined;
    }
  }

  /**
   * This method may be called when deleting a history download.
   */
  async finalize() {}

  /**
   * This method mimicks the "refresh" method of session downloads.
   */
  async refresh() {
    try {
      this.target.size = (await IOUtils.stat(this.target.path)).size;
      this.target.exists = true;
    } catch (ex) {
      // We keep the known file size from the metadata, if any.
      this.target.exists = false;
    }

    this.slot.list._notifyAllViews("onDownloadChanged", this);
  }

  /**
   * This method mimicks the "manuallyRemoveData" method of session downloads.
   */
  async manuallyRemoveData() {
    let { path } = this.target;
    if (this.target.path && this.succeeded) {
      // Temp files are made "read-only" by DownloadIntegration.downloadDone, so
      // reset the permission bits to read/write. This won't be necessary after
      // bug 1733587 since Downloads won't ever be temporary.
      await IOUtils.setPermissions(path, 0o660);
      await IOUtils.remove(path, { ignoreAbsent: true });
    }
    this.deleted = true;
    await this.refresh();
  }
}

/**
 * Represents one item in the list of public session and history downloads.
 *
 * The object may contain a session download, a history download, or both. When
 * both a history and a session download are present, the session download gets
 * priority and its information is accessed.
 *
 * @param list
 *        The DownloadHistoryList that owns this DownloadSlot object.
 */
class DownloadSlot {
  constructor(list) {
    this.list = list;
  }

  /**
   * Download object representing the session download contained in this slot.
   */
  sessionDownload = null;
  _historyDownload = null;

  /**
   * HistoryDownload object contained in this slot.
   */
  get historyDownload() {
    return this._historyDownload;
  }

  set historyDownload(historyDownload) {
    this._historyDownload = historyDownload;
    if (historyDownload) {
      historyDownload.slot = this;
    }
  }

  /**
   * Returns the Download or HistoryDownload object for displaying information
   * and executing commands in the user interface.
   */
  get download() {
    return this.sessionDownload || this.historyDownload;
  }
}

/**
 * Represents an ordered collection of DownloadSlot objects containing a merged
 * view on session downloads and history downloads. Views on this list will
 * receive notifications for changes to both types of downloads.
 *
 * Downloads in this list are sorted from oldest to newest, with all session
 * downloads after all the history downloads. When a new history download is
 * added and the list also contains session downloads, the insertBefore option
 * of the onDownloadAdded notification refers to the first session download.
 *
 * The list of downloads cannot be modified using the DownloadList methods.
 *
 * @param publicList
 *        Underlying DownloadList containing public downloads.
 * @param place
 *        Places query used to retrieve history downloads.
 */
class DownloadHistoryList extends DownloadList {
  constructor(publicList, place) {
    super();

    // While "this._slots" contains all the data in order, the other properties
    // provide fast access for the most common operations.
    this._slots = [];
    this._slotsForUrl = new Map();
    this._slotForDownload = new WeakMap();

    // Start the asynchronous queries to retrieve history and session downloads.
    publicList.addView(this).catch(console.error);
    let query = {},
      options = {};
    lazy.PlacesUtils.history.queryStringToQuery(place, query, options);

    // NB: The addObserver call sets our nsINavHistoryResultObserver.result.
    let result = lazy.PlacesUtils.history.executeQuery(
      query.value,
      options.value
    );
    result.addObserver(this);

    // Our history result observer is long lived for fast shared views, so free
    // the reference on shutdown to prevent leaks.
    Services.obs.addObserver(() => {
      this.result = null;
    }, "quit-application-granted");
  }

  /**
   * This is set when executing the Places query.
   */
  _result = null;

  /**
   * Index of the first slot that contains a session download. This is equal to
   * the length of the list when there are no session downloads.
   *
   * @type {Number}
   */
  _firstSessionSlotIndex = 0;

  get result() {
    return this._result;
  }

  set result(result) {
    if (this._result == result) {
      return;
    }

    if (this._result) {
      this._result.removeObserver(this);
      this._result.root.containerOpen = false;
    }

    this._result = result;

    if (this._result) {
      this._result.root.containerOpen = true;
    }
  }

  /**
   * Updates the download history item when the meta data or destination file
   * changes.
   *
   * @param {String} sourceUrl The sourceUrl which was updated.
   * @param {Object} metaData The new meta data for the sourceUrl.
   */
  updateForMetaDataChange(sourceUrl, metaData) {
    let slotsForUrl = this._slotsForUrl.get(sourceUrl);
    if (!slotsForUrl) {
      return;
    }

    for (let slot of slotsForUrl) {
      if (slot.sessionDownload) {
        // The visible data doesn't change, so we don't have to notify views.
        return;
      }
      slot.historyDownload.updateFromMetaData(metaData);
      this._notifyAllViews("onDownloadChanged", slot.download);
    }
  }

  _insertSlot({ slot, index, slotsForUrl }) {
    // Add the slot to the ordered array.
    this._slots.splice(index, 0, slot);
    this._downloads.splice(index, 0, slot.download);
    if (!slot.sessionDownload) {
      this._firstSessionSlotIndex++;
    }

    // Add the slot to the fast access maps.
    slotsForUrl.add(slot);
    this._slotsForUrl.set(slot.download.source.url, slotsForUrl);

    // Add the associated view items.
    this._notifyAllViews("onDownloadAdded", slot.download, {
      insertBefore: this._downloads[index + 1],
    });
  }

  _removeSlot({ slot, slotsForUrl }) {
    // Remove the slot from the ordered array.
    let index = this._slots.indexOf(slot);
    this._slots.splice(index, 1);
    this._downloads.splice(index, 1);
    if (this._firstSessionSlotIndex > index) {
      this._firstSessionSlotIndex--;
    }

    // Remove the slot from the fast access maps.
    slotsForUrl.delete(slot);
    if (slotsForUrl.size == 0) {
      this._slotsForUrl.delete(slot.download.source.url);
    }

    // Remove the associated view items.
    this._notifyAllViews("onDownloadRemoved", slot.download);
  }

  /**
   * Ensures that the information about a history download is stored in at least
   * one slot, adding a new one at the end of the list if necessary.
   *
   * A reference to the same Places node will be stored in the HistoryDownload
   * object for all the DownloadSlot objects associated with the source URL.
   *
   * @param placesNode
   *        The Places node that represents the history download.
   */
  _insertPlacesNode(placesNode) {
    let slotsForUrl = this._slotsForUrl.get(placesNode.uri) || new Set();

    // If there are existing slots associated with this URL, we only have to
    // ensure that the Places node reference is kept updated in case the more
    // recent Places notification contained a different node object.
    if (slotsForUrl.size > 0) {
      for (let slot of slotsForUrl) {
        if (!slot.historyDownload) {
          slot.historyDownload = new HistoryDownload(placesNode);
        } else {
          slot.historyDownload.placesNode = placesNode;
        }
      }
      return;
    }

    // If there are no existing slots for this URL, we have to create a new one.
    // Since the history download is visible in the slot, we also have to update
    // the object using the Places metadata.
    let historyDownload = new HistoryDownload(placesNode);
    historyDownload.updateFromMetaData(DownloadCache.get(placesNode.uri));
    let slot = new DownloadSlot(this);
    slot.historyDownload = historyDownload;
    this._insertSlot({ slot, slotsForUrl, index: this._firstSessionSlotIndex });
  }

  // nsINavHistoryResultObserver
  containerStateChanged(node) {
    this.invalidateContainer(node);
  }

  // nsINavHistoryResultObserver
  invalidateContainer(container) {
    this._notifyAllViews("onDownloadBatchStarting");

    // Remove all the current slots containing only history downloads.
    for (let index = this._slots.length - 1; index >= 0; index--) {
      let slot = this._slots[index];
      if (slot.sessionDownload) {
        // The visible data doesn't change, so we don't have to notify views.
        slot.historyDownload = null;
      } else {
        let slotsForUrl = this._slotsForUrl.get(slot.download.source.url);
        this._removeSlot({ slot, slotsForUrl });
      }
    }

    // Add new slots or reuse existing ones for history downloads.
    for (let index = container.childCount - 1; index >= 0; --index) {
      try {
        this._insertPlacesNode(container.getChild(index));
      } catch (ex) {
        console.error(ex);
      }
    }

    this._notifyAllViews("onDownloadBatchEnded");
  }

  // nsINavHistoryResultObserver
  nodeInserted(parent, placesNode) {
    this._insertPlacesNode(placesNode);
  }

  // nsINavHistoryResultObserver
  nodeRemoved(parent, placesNode) {
    let slotsForUrl = this._slotsForUrl.get(placesNode.uri);
    for (let slot of slotsForUrl) {
      if (slot.sessionDownload) {
        // The visible data doesn't change, so we don't have to notify views.
        slot.historyDownload = null;
      } else {
        this._removeSlot({ slot, slotsForUrl });
      }
    }
  }

  // nsINavHistoryResultObserver
  nodeIconChanged() {}
  nodeTitleChanged() {}
  nodeKeywordChanged() {}
  nodeDateAddedChanged() {}
  nodeLastModifiedChanged() {}
  nodeHistoryDetailsChanged() {}
  nodeTagsChanged() {}
  sortingChanged() {}
  nodeMoved() {}
  nodeURIChanged() {}
  batching() {}

  // DownloadList callback
  onDownloadAdded(download) {
    let url = download.source.url;
    let slotsForUrl = this._slotsForUrl.get(url) || new Set();

    // For every source URL, there can be at most one slot containing a history
    // download without an associated session download. If we find one, then we
    // can reuse it for the current session download, although we have to move
    // it together with the other session downloads.
    let slot = [...slotsForUrl][0];
    if (slot && !slot.sessionDownload) {
      // Remove the slot because we have to change its position.
      this._removeSlot({ slot, slotsForUrl });
    } else {
      slot = new DownloadSlot(this);
    }
    slot.sessionDownload = download;
    this._insertSlot({ slot, slotsForUrl, index: this._slots.length });
    this._slotForDownload.set(download, slot);
  }

  // DownloadList callback
  onDownloadChanged(download) {
    let slot = this._slotForDownload.get(download);
    this._notifyAllViews("onDownloadChanged", slot.download);
  }

  // DownloadList callback
  onDownloadRemoved(download) {
    let url = download.source.url;
    let slotsForUrl = this._slotsForUrl.get(url);
    let slot = this._slotForDownload.get(download);
    this._removeSlot({ slot, slotsForUrl });

    this._slotForDownload.delete(download);

    // If there was only one slot for this source URL and it also contained a
    // history download, we should resurrect it in the correct area of the list.
    if (slotsForUrl.size == 0 && slot.historyDownload) {
      // We have one download slot containing both a session download and a
      // history download, and we are now removing the session download.
      // Previously, we did not use the Places metadata because it was obscured
      // by the session download. Since this is no longer the case, we have to
      // read the latest metadata before resurrecting the history download.
      slot.historyDownload.updateFromMetaData(DownloadCache.get(url));
      slot.sessionDownload = null;
      // Place the resurrected history slot after all the session slots.
      this._insertSlot({
        slot,
        slotsForUrl,
        index: this._firstSessionSlotIndex,
      });
    }
  }

  // DownloadList
  add() {
    throw new Error("Not implemented.");
  }

  // DownloadList
  remove() {
    throw new Error("Not implemented.");
  }

  // DownloadList
  removeFinished() {
    throw new Error("Not implemented.");
  }
}
PK
!<?^�Fbbmodules/DownloadLastDir.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * The behavior implemented by gDownloadLastDir is documented here.
 *
 * In normal browsing sessions, gDownloadLastDir uses the browser.download.lastDir
 * preference to store the last used download directory. The first time the user
 * switches into the private browsing mode, the last download directory is
 * preserved to the pref value, but if the user switches to another directory
 * during the private browsing mode, that directory is not stored in the pref,
 * and will be merely kept in memory.  When leaving the private browsing mode,
 * this in-memory value will be discarded, and the last download directory
 * will be reverted to the pref value.
 *
 * Both the pref and the in-memory value will be cleared when clearing the
 * browsing history.  This effectively changes the last download directory
 * to the default download directory on each platform.
 *
 * If passed a URI, the last used directory is also stored with that URI in the
 * content preferences database. This can be disabled by setting the pref
 * browser.download.lastDir.savePerSite to false.
 */

const LAST_DIR_PREF = "browser.download.lastDir";
const SAVE_PER_SITE_PREF = LAST_DIR_PREF + ".savePerSite";
const nsIFile = Ci.nsIFile;

import { PrivateBrowsingUtils } from "resource://gre/modules/PrivateBrowsingUtils.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "cps2",
  "@mozilla.org/content-pref/service;1",
  "nsIContentPrefService2"
);

let nonPrivateLoadContext = Cu.createLoadContext();
let privateLoadContext = Cu.createPrivateLoadContext();

var observer = {
  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
  ]),

  observe(aSubject, aTopic) {
    switch (aTopic) {
      case "last-pb-context-exited":
        gDownloadLastDirFile = null;
        break;
      case "browser:purge-session-history":
        gDownloadLastDirFile = null;
        if (Services.prefs.prefHasUserValue(LAST_DIR_PREF)) {
          Services.prefs.clearUserPref(LAST_DIR_PREF);
        }
        // Ensure that purging session history causes both the session-only PB cache
        // and persistent prefs to be cleared.
        let promises = [
          new Promise(resolve =>
            lazy.cps2.removeByName(LAST_DIR_PREF, nonPrivateLoadContext, {
              handleCompletion: resolve,
            })
          ),
          new Promise(resolve =>
            lazy.cps2.removeByName(LAST_DIR_PREF, privateLoadContext, {
              handleCompletion: resolve,
            })
          ),
        ];
        // This is for testing purposes.
        if (aSubject && typeof subject == "object") {
          aSubject.promise = Promise.all(promises);
        }
        break;
    }
  },
};

Services.obs.addObserver(observer, "last-pb-context-exited", true);
Services.obs.addObserver(observer, "browser:purge-session-history", true);

function readLastDirPref() {
  try {
    return Services.prefs.getComplexValue(LAST_DIR_PREF, nsIFile);
  } catch (e) {
    return null;
  }
}

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "isContentPrefEnabled",
  SAVE_PER_SITE_PREF,
  true
);

var gDownloadLastDirFile = readLastDirPref();

export class DownloadLastDir {
  // aForcePrivate is only used when aWindow is null.
  constructor(aWindow, aForcePrivate) {
    let isPrivate = false;
    if (aWindow === null) {
      isPrivate =
        aForcePrivate || PrivateBrowsingUtils.permanentPrivateBrowsing;
    } else {
      let loadContext = aWindow.docShell.QueryInterface(Ci.nsILoadContext);
      isPrivate = loadContext.usePrivateBrowsing;
    }

    // We always use a fake load context because we may not have one (i.e.,
    // in the aWindow == null case) and because the load context associated
    // with aWindow may disappear by the time we need it. This approach is
    // safe because we only care about the private browsing state. All the
    // rest of the load context isn't of interest to the content pref service.
    this.fakeContext = isPrivate ? privateLoadContext : nonPrivateLoadContext;
  }

  isPrivate() {
    return this.fakeContext.usePrivateBrowsing;
  }

  // compat shims
  get file() {
    return this.#getLastFile();
  }
  set file(val) {
    this.setFile(null, val);
  }

  cleanupPrivateFile() {
    gDownloadLastDirFile = null;
  }

  #getLastFile() {
    if (gDownloadLastDirFile && !gDownloadLastDirFile.exists()) {
      gDownloadLastDirFile = null;
    }

    if (this.isPrivate()) {
      if (!gDownloadLastDirFile) {
        gDownloadLastDirFile = readLastDirPref();
      }
      return gDownloadLastDirFile;
    }
    return readLastDirPref();
  }

  async getFileAsync(aURI) {
    let plainPrefFile = this.#getLastFile();
    if (!aURI || !lazy.isContentPrefEnabled) {
      return plainPrefFile;
    }

    return new Promise(resolve => {
      lazy.cps2.getByDomainAndName(
        this.#cpsGroupFromURL(aURI),
        LAST_DIR_PREF,
        this.fakeContext,
        {
          _result: null,
          handleResult(aResult) {
            this._result = aResult;
          },
          handleCompletion(aReason) {
            let file = plainPrefFile;
            if (
              aReason == Ci.nsIContentPrefCallback2.COMPLETE_OK &&
              this._result instanceof Ci.nsIContentPref
            ) {
              try {
                file = Cc["@mozilla.org/file/local;1"].createInstance(
                  Ci.nsIFile
                );
                file.initWithPath(this._result.value);
              } catch (e) {
                file = plainPrefFile;
              }
            }
            resolve(file);
          },
        }
      );
    });
  }

  setFile(aURI, aFile) {
    if (aURI && lazy.isContentPrefEnabled) {
      if (aFile instanceof Ci.nsIFile) {
        lazy.cps2.set(
          this.#cpsGroupFromURL(aURI),
          LAST_DIR_PREF,
          aFile.path,
          this.fakeContext
        );
      } else {
        lazy.cps2.removeByDomainAndName(
          this.#cpsGroupFromURL(aURI),
          LAST_DIR_PREF,
          this.fakeContext
        );
      }
    }
    if (this.isPrivate()) {
      if (aFile instanceof Ci.nsIFile) {
        gDownloadLastDirFile = aFile.clone();
      } else {
        gDownloadLastDirFile = null;
      }
    } else if (aFile instanceof Ci.nsIFile) {
      Services.prefs.setComplexValue(LAST_DIR_PREF, nsIFile, aFile);
    } else if (Services.prefs.prefHasUserValue(LAST_DIR_PREF)) {
      Services.prefs.clearUserPref(LAST_DIR_PREF);
    }
  }

  /**
   * Pre-processor to extract a domain name to be used with the content-prefs
   * service. This specially handles data and file URIs so that the download
   * dirs are recalled in a more consistent way:
   *  - all file:/// URIs share the same folder
   *  - data: URIs share a folder per mime-type. If a mime-type is not
   *    specified text/plain is assumed.
   *  - blob: URIs share the same folder as their origin. This is done by
   *    ContentPrefs already, so we just let the url fall-through.
   * In any other case the original URL is returned as a string and ContentPrefs
   * will do its usual parsing.
   *
   * @param {string|nsIURI|URL} url The URL to parse
   * @returns {string} the domain name to use, or the original url.
   */
  #cpsGroupFromURL(url) {
    if (typeof url == "string") {
      url = new URL(url);
    } else if (url instanceof Ci.nsIURI) {
      url = URL.fromURI(url);
    }
    if (!URL.isInstance(url)) {
      return url;
    }
    if (url.protocol == "data:") {
      return url.href.match(/^data:[^;,]*/i)[0].replace(/:$/, ":text/plain");
    }
    if (url.protocol == "file:") {
      return "file:///";
    }
    return url.href;
  }
}
PK
!<��R=?=?modules/DownloadLegacy.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This component implements the XPCOM interfaces required for integration with
 * the legacy download components.
 *
 * New code is expected to use the "Downloads.sys.mjs" module directly, without
 * going through the interfaces implemented in this XPCOM component.  These
 * interfaces are only maintained for backwards compatibility with components
 * that still work synchronously on the main thread.
 */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  DownloadError: "resource://gre/modules/DownloadCore.sys.mjs",
  Downloads: "resource://gre/modules/Downloads.sys.mjs",
});

/**
 * nsITransfer implementation that provides a bridge to a Download object.
 *
 * Legacy downloads work differently than the JavaScript implementation.  In the
 * latter, the caller only provides the properties for the Download object and
 * the entire process is handled by the "start" method.  In the legacy
 * implementation, the caller must create a separate object to execute the
 * download, and then make the download visible to the user by hooking it up to
 * an nsITransfer instance.
 *
 * Since nsITransfer instances may be created before the download system is
 * initialized, and initialization as well as other operations are asynchronous,
 * this implementation is able to delay all progress and status notifications it
 * receives until the associated Download object is finally created.
 *
 * Conversely, the DownloadLegacySaver object can also receive execution and
 * cancellation requests asynchronously, before or after it is connected to
 * this nsITransfer instance.  For that reason, those requests are communicated
 * in a potentially deferred way, using promise objects.
 *
 * The component that executes the download implements nsICancelable to receive
 * cancellation requests, but after cancellation it cannot be reused again.
 *
 * Since the components that execute the download may be different and they
 * don't always give consistent results, this bridge takes care of enforcing the
 * expectations, for example by ensuring the target file exists when the
 * download is successful, even if the source has a size of zero bytes.
 */
export function DownloadLegacyTransfer() {
  this._promiseDownload = new Promise(r => (this._resolveDownload = r));
}

DownloadLegacyTransfer.prototype = {
  classID: Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}"),

  QueryInterface: ChromeUtils.generateQI([
    "nsIWebProgressListener",
    "nsIWebProgressListener2",
    "nsITransfer",
  ]),

  // nsIWebProgressListener
  onStateChange: function DLT_onStateChange(
    aWebProgress,
    aRequest,
    aStateFlags,
    aStatus
  ) {
    if (!Components.isSuccessCode(aStatus)) {
      this._componentFailed = true;
    }

    if (
      aStateFlags & Ci.nsIWebProgressListener.STATE_START &&
      aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK
    ) {
      let blockedByParentalControls = false;
      // If it is a failed download, aRequest.responseStatus doesn't exist.
      // (missing file on the server, network failure to download)
      try {
        // If the request's response has been blocked by Windows Parental Controls
        // with an HTTP 450 error code, we must cancel the request synchronously.
        blockedByParentalControls =
          aRequest instanceof Ci.nsIHttpChannel &&
          aRequest.responseStatus == 450;
      } catch (e) {
        if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
          aRequest.cancel(Cr.NS_BINDING_ABORTED);
        }
      }

      if (blockedByParentalControls) {
        aRequest.cancel(Cr.NS_BINDING_ABORTED);
      }

      // The main request has just started.  Wait for the associated Download
      // object to be available before notifying.
      this._promiseDownload
        .then(download => {
          // If the request was blocked, now that we have the download object we
          // should set a flag that can be retrieved later when handling the
          // cancellation so that the proper error can be thrown.
          if (blockedByParentalControls) {
            download._blockedByParentalControls = true;
          }

          download.saver.onTransferStarted(aRequest);

          // To handle asynchronous cancellation properly, we should hook up the
          // handler only after we have been notified that the main request
          // started.  We will wait until the main request stopped before
          // notifying that the download has been canceled.  Since the request has
          // not completed yet, deferCanceled is guaranteed to be set.
          return download.saver.deferCanceled.promise.then(() => {
            // Only cancel if the object executing the download is still running.
            if (this._cancelable && !this._componentFailed) {
              this._cancelable.cancel(Cr.NS_ERROR_ABORT);
            }
          });
        })
        .catch(console.error);
    } else if (
      aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
      aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK
    ) {
      // The last file has been received, or the download failed.  Wait for the
      // associated Download object to be available before notifying.
      this._promiseDownload
        .then(download => {
          // At this point, the hash has been set and we need to copy it to the
          // DownloadSaver.
          if (Components.isSuccessCode(aStatus)) {
            download.saver.setSha256Hash(this._sha256Hash);
            download.saver.setSignatureInfo(this._signatureInfo);
            download.saver.setRedirects(this._redirects);
          }
          download.saver.onTransferFinished(aStatus);
        })
        .catch(console.error);

      // Release the reference to the component executing the download.
      this._cancelable = null;
    }
  },

  // nsIWebProgressListener
  onProgressChange: function DLT_onProgressChange(
    aWebProgress,
    aRequest,
    aCurSelfProgress,
    aMaxSelfProgress,
    aCurTotalProgress,
    aMaxTotalProgress
  ) {
    this.onProgressChange64(
      aWebProgress,
      aRequest,
      aCurSelfProgress,
      aMaxSelfProgress,
      aCurTotalProgress,
      aMaxTotalProgress
    );
  },

  onLocationChange() {},

  // nsIWebProgressListener
  onStatusChange(webProgress, request, status, message) {
    // The status change may optionally be received in addition to the state
    // change, but if no network request actually started, it is possible that
    // we only receive a status change with an error status code.
    if (!Components.isSuccessCode(status)) {
      this._componentFailed = true;

      // Wait for the associated Download object to be available.
      this._promiseDownload
        .then(download => {
          download.saver.onTransferFinished(status, message);
        })
        .catch(console.error);
    }
  },

  onSecurityChange() {},

  onContentBlockingEvent() {},

  // nsIWebProgressListener2
  onProgressChange64: function DLT_onProgressChange64(
    aWebProgress,
    aRequest,
    aCurSelfProgress,
    aMaxSelfProgress,
    aCurTotalProgress,
    aMaxTotalProgress
  ) {
    // Since this progress function is invoked frequently, we use a slightly
    // more complex solution that optimizes the case where we already have an
    // associated Download object, avoiding the Promise overhead.
    if (this._download) {
      this._hasDelayedProgress = false;
      this._download.saver.onProgressBytes(
        aCurTotalProgress,
        aMaxTotalProgress
      );
      return;
    }

    // If we don't have a Download object yet, store the most recent progress
    // notification to send later. We must do this because there is no guarantee
    // that a future notification will be sent if the download stalls.
    this._delayedCurTotalProgress = aCurTotalProgress;
    this._delayedMaxTotalProgress = aMaxTotalProgress;

    // Do not enqueue multiple callbacks for the progress report.
    if (this._hasDelayedProgress) {
      return;
    }
    this._hasDelayedProgress = true;

    this._promiseDownload
      .then(download => {
        // Check whether an immediate progress report has been already processed
        // before we could send the delayed progress report.
        if (!this._hasDelayedProgress) {
          return;
        }
        download.saver.onProgressBytes(
          this._delayedCurTotalProgress,
          this._delayedMaxTotalProgress
        );
      })
      .catch(console.error);
  },
  _hasDelayedProgress: false,
  _delayedCurTotalProgress: 0,
  _delayedMaxTotalProgress: 0,

  // nsIWebProgressListener2
  onRefreshAttempted: function DLT_onRefreshAttempted() {
    // Indicate that refreshes and redirects are allowed by default.  However,
    // note that download components don't usually call this method at all.
    return true;
  },

  // nsITransfer
  init: function DLT_init(
    aSource,
    aSourceOriginalURI,
    aTarget,
    aDisplayName,
    aMIMEInfo,
    aStartTime,
    aTempFile,
    aCancelable,
    aIsPrivate,
    aDownloadClassification,
    aReferrerInfo,
    aOpenDownloadsListOnStart
  ) {
    return this._nsITransferInitInternal(
      aSource,
      aSourceOriginalURI,
      aTarget,
      aDisplayName,
      aMIMEInfo,
      aStartTime,
      aTempFile,
      aCancelable,
      aIsPrivate,
      aDownloadClassification,
      aReferrerInfo,
      aOpenDownloadsListOnStart
    );
  },

  // nsITransfer
  initWithBrowsingContext(
    aSource,
    aTarget,
    aDisplayName,
    aMIMEInfo,
    aStartTime,
    aTempFile,
    aCancelable,
    aIsPrivate,
    aDownloadClassification,
    aReferrerInfo,
    aOpenDownloadsListOnStart,
    aBrowsingContext,
    aHandleInternally,
    aHttpChannel
  ) {
    let browsingContextId;
    let userContextId;
    if (aBrowsingContext && aBrowsingContext.currentWindowGlobal) {
      browsingContextId = aBrowsingContext.id;
      let windowGlobal = aBrowsingContext.currentWindowGlobal;
      let originAttributes = windowGlobal.documentPrincipal.originAttributes;
      userContextId = originAttributes.userContextId;
    }
    return this._nsITransferInitInternal(
      aSource,
      null,
      aTarget,
      aDisplayName,
      aMIMEInfo,
      aStartTime,
      aTempFile,
      aCancelable,
      aIsPrivate,
      aDownloadClassification,
      aReferrerInfo,
      aOpenDownloadsListOnStart,
      userContextId,
      browsingContextId,
      aHandleInternally,
      aHttpChannel
    );
  },

  _nsITransferInitInternal(
    aSource,
    aSourceOriginalURI,
    aTarget,
    aDisplayName,
    aMIMEInfo,
    aStartTime,
    aTempFile,
    aCancelable,
    isPrivate,
    aDownloadClassification,
    referrerInfo,
    openDownloadsListOnStart = true,
    userContextId = 0,
    browsingContextId = 0,
    handleInternally = false,
    aHttpChannel = null
  ) {
    this._cancelable = aCancelable;
    let launchWhenSucceeded = false,
      contentType = null,
      launcherPath = null;

    if (aMIMEInfo instanceof Ci.nsIMIMEInfo) {
      launchWhenSucceeded =
        aMIMEInfo.preferredAction != Ci.nsIMIMEInfo.saveToDisk;
      contentType = aMIMEInfo.type;

      let appHandler = aMIMEInfo.preferredApplicationHandler;
      if (
        aMIMEInfo.preferredAction == Ci.nsIMIMEInfo.useHelperApp &&
        appHandler instanceof Ci.nsILocalHandlerApp
      ) {
        launcherPath = appHandler.executable.path;
      }
    }
    // Create a new Download object associated to a DownloadLegacySaver, and
    // wait for it to be available.  This operation may cause the entire
    // download system to initialize before the object is created.
    let authHeader = null;
    if (aHttpChannel) {
      try {
        authHeader = aHttpChannel.getRequestHeader("Authorization");
      } catch (e) {}
    }
    let serialisedDownload = {
      source: {
        url: aSource.spec,
        originalUrl: aSourceOriginalURI && aSourceOriginalURI.spec,
        isPrivate,
        userContextId,
        browsingContextId,
        referrerInfo,
        authHeader,
      },
      target: {
        path: aTarget.QueryInterface(Ci.nsIFileURL).file.path,
        partFilePath: aTempFile && aTempFile.path,
      },
      saver: "legacy",
      launchWhenSucceeded,
      contentType,
      launcherPath,
      handleInternally,
      openDownloadsListOnStart,
    };

    // In case the Download was classified as insecure/dangerous
    // it is already canceled, so we need to generate and attach the
    // corresponding error to the download.
    if (aDownloadClassification == Ci.nsITransfer.DOWNLOAD_POTENTIALLY_UNSAFE) {
      Services.telemetry
        .getKeyedHistogramById("DOWNLOADS_USER_ACTION_ON_BLOCKED_DOWNLOAD")
        .add(lazy.DownloadError.BLOCK_VERDICT_INSECURE, 0);

      serialisedDownload.errorObj = {
        becauseBlockedByReputationCheck: true,
        reputationCheckVerdict: lazy.DownloadError.BLOCK_VERDICT_INSECURE,
      };
      // hasBlockedData needs to be true
      // because the unblock UI is hidden if there is
      // no data to be unblocked.
      serialisedDownload.hasBlockedData = true;
      // We cannot use the legacy saver here, as the original channel
      // is already closed. A copy saver would create a new channel once
      // start() is called.
      serialisedDownload.saver = "copy";

      // Since the download is canceled already, we do not need to keep refrences
      this._download = null;
      this._cancelable = null;
    }

    lazy.Downloads.createDownload(serialisedDownload)
      .then(async aDownload => {
        // Legacy components keep partial data when they use a ".part" file.
        if (aTempFile) {
          aDownload.tryToKeepPartialData = true;
        }

        // Start the download before allowing it to be controlled.  Ignore errors.
        aDownload.start().catch(() => {});

        // Start processing all the other events received through nsITransfer.
        this._download = aDownload;
        this._resolveDownload(aDownload);

        // Add the download to the list, allowing it to be seen and canceled.
        await (await lazy.Downloads.getList(lazy.Downloads.ALL)).add(aDownload);
        if (serialisedDownload.errorObj) {
          // In case we added an already canceled dummy download
          // we need to manually trigger a change event
          // as all the animations for finishing downloads are
          // listening on onChange.
          aDownload._notifyChange();
        }
      })
      .catch(console.error);
  },

  get downloadPromise() {
    return this._promiseDownload;
  },

  setSha256Hash(hash) {
    this._sha256Hash = hash;
  },

  setSignatureInfo(signatureInfo) {
    this._signatureInfo = signatureInfo;
  },

  setRedirects(redirects) {
    this._redirects = redirects;
  },

  /**
   * Download object associated with this nsITransfer instance. This is not
   * available immediately when the nsITransfer instance is created.
   */
  _download: null,

  /**
   * Promise that resolves to the Download object associated with this
   * nsITransfer instance after the _resolveDownload method is invoked.
   *
   * Waiting on this promise using "then" ensures that the callbacks are invoked
   * in the correct order even if enqueued before the object is available.
   */
  _promiseDownload: null,
  _resolveDownload: null,

  /**
   * Reference to the component that is executing the download.  This component
   * allows cancellation through its nsICancelable interface.
   */
  _cancelable: null,

  /**
   * Indicates that the component that executes the download has notified a
   * failure condition.  In this case, we should never use the component methods
   * that cancel the download.
   */
  _componentFailed: false,

  /**
   * Save the SHA-256 hash in raw bytes of the downloaded file.
   */
  _sha256Hash: null,

  /**
   * Save the signature info in a serialized protobuf of the downloaded file.
   */
  _signatureInfo: null,
};
PK
!<d@
modules/DownloadPaths.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Provides methods for giving names and paths to files being downloaded.
 */

export var DownloadPaths = {
  /**
   * Sanitizes an arbitrary string via mimeSvc.validateFileNameForSaving.
   *
   * If the filename being validated is one that was returned from a
   * file picker, pass false for compressWhitespaces and true for
   * allowInvalidFilenames. Otherwise, the default values of the arguments
   * should generally be used.
   *
   * @param {string} leafName The full leaf name to sanitize
   * @param {boolean} [compressWhitespaces] Whether consecutive whitespaces
   *        should be compressed. The default value is true.
   * @param {boolean} [allowInvalidFilenames] Allow invalid and dangerous
   *        filenames and extensions as is.
   * @param {boolean} [allowDirectoryNames] Allow invalid or dangerous file
   *        names if the name is a valid and safe directory name.
   */
  sanitize(
    leafName,
    {
      compressWhitespaces = true,
      allowInvalidFilenames = false,
      allowDirectoryNames = false,
    } = {}
  ) {
    const mimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);

    let flags = mimeSvc.VALIDATE_SANITIZE_ONLY | mimeSvc.VALIDATE_DONT_TRUNCATE;
    if (!compressWhitespaces) {
      flags |= mimeSvc.VALIDATE_DONT_COLLAPSE_WHITESPACE;
    }
    if (allowInvalidFilenames) {
      flags |= mimeSvc.VALIDATE_ALLOW_INVALID_FILENAMES;
    }
    if (allowDirectoryNames) {
      flags |= mimeSvc.VALIDATE_ALLOW_DIRECTORY_NAMES;
    }
    return mimeSvc.validateFileNameForSaving(leafName, "", flags);
  },

  /**
   * Creates a uniquely-named file starting from the name of the provided file.
   * If a file with the provided name already exists, the function attempts to
   * create nice alternatives, like "base(1).ext" (instead of "base-1.ext").
   *
   * If a unique name cannot be found, the function throws the XPCOM exception
   * NS_ERROR_FILE_TOO_BIG. Other exceptions, like NS_ERROR_FILE_ACCESS_DENIED,
   * can also be expected.
   *
   * @param templateFile
   *        nsIFile whose leaf name is going to be used as a template. The
   *        provided object is not modified.
   *
   * @return A new instance of an nsIFile object pointing to the newly created
   *         empty file. On platforms that support permission bits, the file is
   *         created with permissions 644.
   */
  createNiceUniqueFile(templateFile) {
    // Work on a clone of the provided template file object.
    let curFile = templateFile.clone().QueryInterface(Ci.nsIFile);
    let [base, ext] = DownloadPaths.splitBaseNameAndExtension(curFile.leafName);
    // Try other file names, for example "base(1).txt" or "base(1).tar.gz",
    // only if the file name initially set already exists.
    for (let i = 1; i < 10000 && curFile.exists(); i++) {
      curFile.leafName = base + "(" + i + ")" + ext;
    }
    // At this point we hand off control to createUnique, which will create the
    // file with the name we chose, if it is valid. If not, createUnique will
    // attempt to modify it again, for example it will shorten very long names
    // that can't be created on some platforms, and for which a normal call to
    // nsIFile.create would result in NS_ERROR_FILE_NOT_FOUND. This can result
    // very rarely in strange names like "base(9999).tar-1.gz" or "ba-1.gz".
    curFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
    return curFile;
  },

  /**
   * Separates the base name from the extension in a file name, recognizing some
   * double extensions like ".tar.gz".
   *
   * @param leafName
   *        The full leaf name to be parsed. Be careful when processing names
   *        containing leading or trailing dots or spaces.
   *
   * @return [base, ext]
   *         The base name of the file, which can be empty, and its extension,
   *         which always includes the leading dot unless it's an empty string.
   *         Concatenating the two items always results in the original name.
   */
  splitBaseNameAndExtension(leafName) {
    // The following regular expression is built from these key parts:
    //  .*?                      Matches the base name non-greedily.
    //  \.[A-Z0-9]{1,3}          Up to three letters or numbers preceding a
    //                           double extension.
    //  \.(?:gz|bz2|Z)           The second part of common double extensions.
    //  \.[^.]*                  Matches any extension or a single trailing dot.
    let [, base, ext] = /(.*?)(\.[A-Z0-9]{1,3}\.(?:gz|bz2|Z)|\.[^.]*)?$/i.exec(
      leafName
    );
    // Return an empty string instead of undefined if no extension is found.
    return [base, ext || ""];
  },
};
PK
!<kr�.�� modules/DownloadUIHelper.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Provides functions to handle status and messages in the user interface.
 */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

// BrowserWindowTracker and PrivateBrowsingUtils are only used when opening downloaded files into a browser window
ChromeUtils.defineESModuleGetters(lazy, {
  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

ChromeUtils.defineLazyGetter(
  lazy,
  "l10n",
  () => new Localization(["toolkit/downloads/downloadUI.ftl"], true)
);

/**
 * Provides functions to handle status and messages in the user interface.
 */
export var DownloadUIHelper = {
  /**
   * Returns an object that can be used to display prompts related to downloads.
   *
   * The prompts may be either anchored to a specified window, or anchored to
   * the most recently active window, for example if the prompt is displayed in
   * response to global notifications that are not associated with any window.
   *
   * @param aParent
   *        If specified, should reference the nsIDOMWindow to which the prompts
   *        should be attached.  If omitted, the prompts will be attached to the
   *        most recently active window.
   *
   * @return A DownloadPrompter object.
   */
  getPrompter(aParent) {
    return new DownloadPrompter(aParent || null);
  },

  /**
   * Open the given file as a file: URI in the active window
   *
   * @param nsIFile file         The downloaded file
   * @param options.chromeWindow Optional chrome window where we could open the file URI
   * @param options.openWhere    String indicating how to open the URI.
   *                             One of "window", "tab", "tabshifted"
   * @param options.isPrivate    Open in private window or not
   * @param options.browsingContextId BrowsingContext ID of the initiating document
   * @param options.userContextId UserContextID of the initiating document
   */
  loadFileIn(
    file,
    {
      chromeWindow: browserWin,
      openWhere = "tab",
      isPrivate,
      userContextId = 0,
      browsingContextId = 0,
    } = {}
  ) {
    let fileURI = Services.io.newFileURI(file);
    let allowPrivate =
      isPrivate || lazy.PrivateBrowsingUtils.permanentPrivateBrowsing;

    if (
      !browserWin ||
      browserWin.document.documentElement.getAttribute("windowtype") !==
        "navigator:browser"
    ) {
      // we'll need a private window for a private download, or if we're in private-only mode
      // but otherwise we want to open files in a non-private window
      browserWin = lazy.BrowserWindowTracker.getTopWindow({
        private: allowPrivate,
      });
    }
    // if there is no suitable browser window, we'll need to open one and ignore any other `openWhere` value
    // this can happen if the library dialog is the only open window
    if (!browserWin) {
      // There is no browser window open, so open a new one.
      let args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
      let strURI = Cc["@mozilla.org/supports-string;1"].createInstance(
        Ci.nsISupportsString
      );
      strURI.data = fileURI.spec;
      args.appendElement(strURI);
      let features = "chrome,dialog=no,all";
      if (isPrivate) {
        features += ",private";
      }
      browserWin = Services.ww.openWindow(
        null,
        AppConstants.BROWSER_CHROME_URL,
        null,
        features,
        args
      );
      return;
    }

    // a browser window will have the helpers from utilityOverlay.js
    let browsingContext = browserWin?.BrowsingContext.get(browsingContextId);
    browserWin.openTrustedLinkIn(fileURI.spec, openWhere, {
      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
      private: isPrivate,
      userContextId,
      openerBrowser: browsingContext?.top?.embedderElement,
    });
  },
};

/**
 * Allows displaying prompts related to downloads.
 *
 * @param aParent
 *        The nsIDOMWindow to which prompts should be attached, or null to
 *        attach prompts to the most recently active window.
 */
var DownloadPrompter = function (aParent) {
  this._prompter = Services.ww.getNewPrompter(aParent);
};

DownloadPrompter.prototype = {
  /**
   * Constants with the different type of prompts.
   */
  ON_QUIT: "prompt-on-quit",
  ON_OFFLINE: "prompt-on-offline",
  ON_LEAVE_PRIVATE_BROWSING: "prompt-on-leave-private-browsing",

  /**
   * nsIPrompt instance for displaying messages.
   */
  _prompter: null,

  /**
   * Displays a warning message box that informs that the specified file is
   * executable, and asks whether the user wants to launch it.
   *
   * @param path
   *        String containing the full path to the file to be opened.
   *
   * @resolves Boolean indicating whether the launch operation can continue.
   */
  async confirmLaunchExecutable(path) {
    const kPrefSkipConfirm = "browser.download.skipConfirmLaunchExecutable";

    // Always launch in case we have no prompter implementation.
    if (!this._prompter) {
      return true;
    }

    try {
      if (Services.prefs.getBoolPref(kPrefSkipConfirm)) {
        return true;
      }
    } catch (ex) {
      // If the preference does not exist, continue with the prompt.
    }

    const title = lazy.l10n.formatValueSync(
      "download-ui-file-executable-security-warning-title"
    );
    const message = lazy.l10n.formatValueSync(
      "download-ui-file-executable-security-warning",
      { executable: PathUtils.filename(path) }
    );
    return this._prompter.confirm(title, message);
  },

  /**
   * Displays a warning message box that informs that there are active
   * downloads, and asks whether the user wants to cancel them or not.
   *
   * @param aDownloadsCount
   *        The current downloads count.
   * @param aPromptType
   *        The type of prompt notification depending on the observer.
   *
   * @return False to cancel the downloads and continue, true to abort the
   *         operation.
   */
  confirmCancelDownloads: function DP_confirmCancelDownload(
    aDownloadsCount,
    aPromptType
  ) {
    // Always continue in case we have no prompter implementation, or if there
    // are no active downloads.
    if (!this._prompter || aDownloadsCount <= 0) {
      return false;
    }

    let message, cancelButton;

    switch (aPromptType) {
      case this.ON_QUIT:
        message =
          AppConstants.platform == "macosx"
            ? "download-ui-confirm-quit-cancel-downloads-mac"
            : "download-ui-confirm-quit-cancel-downloads";
        cancelButton = "download-ui-dont-quit-button";
        break;

      case this.ON_OFFLINE:
        message = "download-ui-confirm-offline-cancel-downloads";
        cancelButton = "download-ui-dont-go-offline-button";
        break;

      case this.ON_LEAVE_PRIVATE_BROWSING:
        message =
          "download-ui-confirm-leave-private-browsing-windows-cancel-downloads";
        cancelButton = "download-ui-dont-leave-private-browsing-button";
        break;
    }

    const buttonFlags =
      Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0 +
      Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1;

    let rv = this._prompter.confirmEx(
      lazy.l10n.formatValueSync("download-ui-confirm-title"),
      lazy.l10n.formatValueSync(message, { downloadsCount: aDownloadsCount }),
      buttonFlags,
      lazy.l10n.formatValueSync("download-ui-cancel-downloads-ok", {
        downloadsCount: aDownloadsCount,
      }),
      lazy.l10n.formatValueSync(cancelButton),
      null,
      null,
      {}
    );
    return rv == 1;
  },
};
PK
!<�Vre�M�Mmodules/DownloadUtils.sys.mjs/* vim: sw=2 ts=2 sts=2 expandtab filetype=javascript
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This module provides the DownloadUtils object which contains useful methods
 * for downloads such as displaying file sizes, transfer times, and download
 * locations.
 *
 * List of methods:
 *
 * [string status, double newLast]
 * getDownloadStatus(int aCurrBytes, [optional] int aMaxBytes,
 *                   [optional] double aSpeed, [optional] double aLastSec)
 *
 * string progress
 * getTransferTotal(int aCurrBytes, [optional] int aMaxBytes)
 *
 * [string timeLeft, double newLast]
 * getTimeLeft(double aSeconds, [optional] double aLastSec)
 *
 * [string dateCompact, string dateComplete]
 * getReadableDates(Date aDate, [optional] Date aNow)
 *
 * [string displayHost, string fullHost]
 * getURIHost(string aURIString)
 *
 * [string convertedBytes, string units]
 * convertByteUnits(int aBytes)
 *
 * [int time, string units, int subTime, string subUnits]
 * convertTimeUnits(double aSecs)
 */

const MS_PER_DAY = 24 * 60 * 60 * 1000;

const BYTE_UNITS = [
  "download-utils-bytes",
  "download-utils-kilobyte",
  "download-utils-megabyte",
  "download-utils-gigabyte",
];

const TIME_UNITS = [
  "download-utils-short-seconds",
  "download-utils-short-minutes",
  "download-utils-short-hours",
  "download-utils-short-days",
];

// These are the maximum values for seconds, minutes, hours corresponding
// with TIME_UNITS without the last item
const TIME_SIZES = [60, 60, 24];

var localeNumberFormatCache = new Map();
function getLocaleNumberFormat(fractionDigits) {
  if (!localeNumberFormatCache.has(fractionDigits)) {
    localeNumberFormatCache.set(
      fractionDigits,
      new Services.intl.NumberFormat(undefined, {
        maximumFractionDigits: fractionDigits,
        minimumFractionDigits: fractionDigits,
      })
    );
  }
  return localeNumberFormatCache.get(fractionDigits);
}

const l10n = new Localization(["toolkit/downloads/downloadUtils.ftl"], true);

// Keep track of at most this many second/lastSec pairs so that multiple calls
// to getTimeLeft produce the same time left
const kCachedLastMaxSize = 10;
var gCachedLast = [];

export var DownloadUtils = {
  /**
   * Generate a full status string for a download given its current progress,
   * total size, speed, last time remaining
   *
   * @param aCurrBytes
   *        Number of bytes transferred so far
   * @param [optional] aMaxBytes
   *        Total number of bytes or -1 for unknown
   * @param [optional] aSpeed
   *        Current transfer rate in bytes/sec or -1 for unknown
   * @param [optional] aLastSec
   *        Last time remaining in seconds or Infinity for unknown
   * @return A pair: [download status text, new value of "last seconds"]
   */
  getDownloadStatus: function DU_getDownloadStatus(
    aCurrBytes,
    aMaxBytes,
    aSpeed,
    aLastSec
  ) {
    let [transfer, timeLeft, newLast, normalizedSpeed] =
      this._deriveTransferRate(aCurrBytes, aMaxBytes, aSpeed, aLastSec);

    let [rate, unit] = DownloadUtils.convertByteUnits(normalizedSpeed);

    let status;
    if (rate === "Infinity") {
      // Infinity download speed doesn't make sense. Show a localized phrase instead.
      status = l10n.formatValueSync("download-utils-status-infinite-rate", {
        transfer,
        timeLeft,
      });
    } else {
      status = l10n.formatValueSync("download-utils-status", {
        transfer,
        rate,
        unit,
        timeLeft,
      });
    }
    return [status, newLast];
  },

  /**
   * Generate a status string for a download given its current progress,
   * total size, speed, last time remaining. The status string contains the
   * time remaining, as well as the total bytes downloaded. Unlike
   * getDownloadStatus, it does not include the rate of download.
   *
   * @param aCurrBytes
   *        Number of bytes transferred so far
   * @param [optional] aMaxBytes
   *        Total number of bytes or -1 for unknown
   * @param [optional] aSpeed
   *        Current transfer rate in bytes/sec or -1 for unknown
   * @param [optional] aLastSec
   *        Last time remaining in seconds or Infinity for unknown
   * @return A pair: [download status text, new value of "last seconds"]
   */
  getDownloadStatusNoRate: function DU_getDownloadStatusNoRate(
    aCurrBytes,
    aMaxBytes,
    aSpeed,
    aLastSec
  ) {
    let [transfer, timeLeft, newLast] = this._deriveTransferRate(
      aCurrBytes,
      aMaxBytes,
      aSpeed,
      aLastSec
    );

    let status = l10n.formatValueSync("download-utils-status-no-rate", {
      transfer,
      timeLeft,
    });
    return [status, newLast];
  },

  /**
   * Helper function that returns a transfer string, a time remaining string,
   * and a new value of "last seconds".
   * @param aCurrBytes
   *        Number of bytes transferred so far
   * @param [optional] aMaxBytes
   *        Total number of bytes or -1 for unknown
   * @param [optional] aSpeed
   *        Current transfer rate in bytes/sec or -1 for unknown
   * @param [optional] aLastSec
   *        Last time remaining in seconds or Infinity for unknown
   * @return A triple: [amount transferred string, time remaining string,
   *                    new value of "last seconds"]
   */
  _deriveTransferRate: function DU__deriveTransferRate(
    aCurrBytes,
    aMaxBytes,
    aSpeed,
    aLastSec
  ) {
    if (aMaxBytes == null) {
      aMaxBytes = -1;
    }
    if (aSpeed == null) {
      aSpeed = -1;
    }
    if (aLastSec == null) {
      aLastSec = Infinity;
    }

    // Calculate the time remaining if we have valid values
    let seconds =
      aSpeed > 0 && aMaxBytes > 0 ? (aMaxBytes - aCurrBytes) / aSpeed : -1;

    let transfer = DownloadUtils.getTransferTotal(aCurrBytes, aMaxBytes);
    let [timeLeft, newLast] = DownloadUtils.getTimeLeft(seconds, aLastSec);
    return [transfer, timeLeft, newLast, aSpeed];
  },

  /**
   * Generate the transfer progress string to show the current and total byte
   * size. Byte units will be as large as possible and the same units for
   * current and max will be suppressed for the former.
   *
   * @param aCurrBytes
   *        Number of bytes transferred so far
   * @param [optional] aMaxBytes
   *        Total number of bytes or -1 for unknown
   * @return The transfer progress text
   */
  getTransferTotal: function DU_getTransferTotal(aCurrBytes, aMaxBytes) {
    if (aMaxBytes == null) {
      aMaxBytes = -1;
    }

    let [progress, progressUnits] = DownloadUtils.convertByteUnits(aCurrBytes);
    let [total, totalUnits] = DownloadUtils.convertByteUnits(aMaxBytes);

    // Figure out which byte progress string to display
    let name;
    if (aMaxBytes < 0) {
      name = "download-utils-transfer-no-total";
    } else if (progressUnits == totalUnits) {
      name = "download-utils-transfer-same-units";
    } else {
      name = "download-utils-transfer-diff-units";
    }

    return l10n.formatValueSync(name, {
      progress,
      progressUnits,
      total,
      totalUnits,
    });
  },

  /**
   * Generate a "time left" string given an estimate on the time left and the
   * last time. The extra time is used to give a better estimate on the time to
   * show. Both the time values are doubles instead of integers to help get
   * sub-second accuracy for current and future estimates.
   *
   * @param aSeconds
   *        Current estimate on number of seconds left for the download
   * @param [optional] aLastSec
   *        Last time remaining in seconds or Infinity for unknown
   * @return A pair: [time left text, new value of "last seconds"]
   */
  getTimeLeft: function DU_getTimeLeft(aSeconds, aLastSec) {
    let nf = new Services.intl.NumberFormat();
    if (aLastSec == null) {
      aLastSec = Infinity;
    }

    if (aSeconds < 0) {
      return [l10n.formatValueSync("download-utils-time-unknown"), aLastSec];
    }

    // Try to find a cached lastSec for the given second
    aLastSec = gCachedLast.reduce(
      (aResult, aItem) => (aItem[0] == aSeconds ? aItem[1] : aResult),
      aLastSec
    );

    // Add the current second/lastSec pair unless we have too many
    gCachedLast.push([aSeconds, aLastSec]);
    if (gCachedLast.length > kCachedLastMaxSize) {
      gCachedLast.shift();
    }

    // Apply smoothing only if the new time isn't a huge change -- e.g., if the
    // new time is more than half the previous time; this is useful for
    // downloads that start/resume slowly
    if (aSeconds > aLastSec / 2) {
      // Apply hysteresis to favor downward over upward swings
      // 30% of down and 10% of up (exponential smoothing)
      let diff = aSeconds - aLastSec;
      aSeconds = aLastSec + (diff < 0 ? 0.3 : 0.1) * diff;

      // If the new time is similar, reuse something close to the last seconds,
      // but subtract a little to provide forward progress
      let diffPct = (diff / aLastSec) * 100;
      if (Math.abs(diff) < 5 || Math.abs(diffPct) < 5) {
        aSeconds = aLastSec - (diff < 0 ? 0.4 : 0.2);
      }
    }

    // Decide what text to show for the time
    let timeLeft;
    if (aSeconds < 4) {
      // Be friendly in the last few seconds
      timeLeft = l10n.formatValueSync("download-utils-time-few-seconds");
    } else {
      // Convert the seconds into its two largest units to display
      let [time1, unit1, time2, unit2] =
        DownloadUtils.convertTimeUnits(aSeconds);

      const pair1 = l10n.formatValueSync("download-utils-time-pair", {
        time: nf.format(time1),
        unit: unit1,
      });

      // Only show minutes for under 1 hour unless there's a few minutes left;
      // or the second pair is 0.
      if ((aSeconds < 3600 && time1 >= 4) || time2 == 0) {
        timeLeft = l10n.formatValueSync("download-utils-time-left-single", {
          time: pair1,
        });
      } else {
        // We've got 2 pairs of times to display
        const pair2 = l10n.formatValueSync("download-utils-time-pair", {
          time: nf.format(time2),
          unit: unit2,
        });
        timeLeft = l10n.formatValueSync("download-utils-time-left-double", {
          time1: pair1,
          time2: pair2,
        });
      }
    }

    return [timeLeft, aSeconds];
  },

  /**
   * Converts a Date object to two readable formats, one compact, one complete.
   * The compact format is relative to the current date, and is not an accurate
   * representation. For example, only the time is displayed for today. The
   * complete format always includes both the date and the time, excluding the
   * seconds, and is often shown when hovering the cursor over the compact
   * representation.
   *
   * @param aDate
   *        Date object representing the date and time to format. It is assumed
   *        that this value represents a past date.
   * @param [optional] aNow
   *        Date object representing the current date and time. The real date
   *        and time of invocation is used if this parameter is omitted.
   * @return A pair: [compact text, complete text]
   */
  getReadableDates: function DU_getReadableDates(aDate, aNow) {
    if (!aNow) {
      aNow = new Date();
    }

    // Figure out when today begins
    let today = new Date(aNow.getFullYear(), aNow.getMonth(), aNow.getDate());

    let dateTimeCompact;
    let dateTimeFull;

    // Figure out if the time is from today, yesterday, this week, etc.
    if (aDate >= today) {
      let dts = new Services.intl.DateTimeFormat(undefined, {
        timeStyle: "short",
      });
      dateTimeCompact = dts.format(aDate);
    } else if (today - aDate < MS_PER_DAY) {
      // After yesterday started, show yesterday
      dateTimeCompact = l10n.formatValueSync("download-utils-yesterday");
    } else if (today - aDate < 6 * MS_PER_DAY) {
      // After last week started, show day of week
      dateTimeCompact = aDate.toLocaleDateString(undefined, {
        weekday: "long",
      });
    } else {
      // Show month/day
      dateTimeCompact = aDate.toLocaleString(undefined, {
        month: "long",
        day: "numeric",
      });
    }

    const dtOptions = { dateStyle: "long", timeStyle: "short" };
    dateTimeFull = new Services.intl.DateTimeFormat(
      undefined,
      dtOptions
    ).format(aDate);

    return [dateTimeCompact, dateTimeFull];
  },

  /**
   * Get the appropriate display host string for a URI string depending on if
   * the URI has an eTLD + 1, is an IP address, a local file, or other protocol
   *
   * @param aURIString
   *        The URI string to try getting an eTLD + 1, etc.
   * @return A pair: [display host for the URI string, full host name]
   */
  getURIHost: function DU_getURIHost(aURIString) {
    let idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
      Ci.nsIIDNService
    );

    // Get a URI that knows about its components
    let uri;
    try {
      uri = Services.io.newURI(aURIString);
    } catch (ex) {
      return ["", ""];
    }

    // Get the inner-most uri for schemes like jar:
    if (uri instanceof Ci.nsINestedURI) {
      uri = uri.innermostURI;
    }

    if (uri.scheme == "blob") {
      let origin = new URL(uri.spec).origin;
      // Origin can be "null" for blob URIs from a sandbox.
      if (origin != "null") {
        // `newURI` can throw (like for null) and throwing here breaks...
        // a lot of stuff. So let's avoid doing that in case there are other
        // edgecases we're missing here.
        try {
          uri = Services.io.newURI(origin);
        } catch (ex) {
          console.error(ex);
        }
      }
    }

    let fullHost;
    try {
      // Get the full host name; some special URIs fail (data: jar:)
      fullHost = uri.host;
    } catch (e) {
      fullHost = "";
    }

    let displayHost;
    try {
      // This might fail if it's an IP address or doesn't have more than 1 part
      let baseDomain = Services.eTLD.getBaseDomain(uri);

      // Convert base domain for display
      displayHost = idnService.convertToDisplayIDN(baseDomain);
    } catch (e) {
      // Default to the host name
      displayHost = fullHost;
    }

    // Check if we need to show something else for the host
    if (uri.scheme == "file") {
      // Display special text for file protocol
      displayHost = l10n.formatValueSync("download-utils-done-file-scheme");
      fullHost = displayHost;
    } else if (!displayHost.length) {
      // Got nothing; show the scheme (data: about: moz-icon:)
      displayHost = l10n.formatValueSync("download-utils-done-scheme", {
        scheme: uri.scheme,
      });
      fullHost = displayHost;
    } else if (uri.port != -1) {
      // Tack on the port if it's not the default port
      let port = ":" + uri.port;
      displayHost += port;
      fullHost += port;
    }

    return [displayHost, fullHost];
  },

  /**
   * Converts a number of bytes to the appropriate unit that results in an
   * internationalized number that needs fewer than 4 digits.
   *
   * @param aBytes
   *        Number of bytes to convert
   * @return A pair: [new value with 3 sig. figs., its unit]
   */
  convertByteUnits: function DU_convertByteUnits(aBytes) {
    let unitIndex = 0;

    // Convert to next unit if it needs 4 digits (after rounding), but only if
    // we know the name of the next unit
    while (aBytes >= 999.5 && unitIndex < BYTE_UNITS.length - 1) {
      aBytes /= 1024;
      unitIndex++;
    }

    // Get rid of insignificant bits by truncating to 1 or 0 decimal points
    // 0 -> 0; 1.2 -> 1.2; 12.3 -> 12.3; 123.4 -> 123; 234.5 -> 235
    // added in bug 462064: (unitIndex != 0) makes sure that no decimal digit for bytes appears when aBytes < 100
    let fractionDigits = aBytes > 0 && aBytes < 100 && unitIndex != 0 ? 1 : 0;

    // Don't try to format Infinity values using NumberFormat.
    if (aBytes === Infinity) {
      aBytes = "Infinity";
    } else {
      aBytes = getLocaleNumberFormat(fractionDigits).format(aBytes);
    }

    return [aBytes, l10n.formatValueSync(BYTE_UNITS[unitIndex])];
  },

  /**
   * Converts a number of seconds to the two largest units. Time values are
   * whole numbers, and units have the correct plural/singular form.
   *
   * @param aSecs
   *        Seconds to convert into the appropriate 2 units
   * @return 4-item array [first value, its unit, second value, its unit]
   */
  convertTimeUnits: function DU_convertTimeUnits(aSecs) {
    let time = aSecs;
    let scale = 1;
    let unitIndex = 0;

    // Keep converting to the next unit while we have units left and the
    // current one isn't the largest unit possible
    while (unitIndex < TIME_SIZES.length && time >= TIME_SIZES[unitIndex]) {
      time /= TIME_SIZES[unitIndex];
      scale *= TIME_SIZES[unitIndex];
      unitIndex++;
    }

    let value = convertTimeUnitsValue(time);
    let units = convertTimeUnitsUnits(value, unitIndex);

    let extra = aSecs - value * scale;
    let nextIndex = unitIndex - 1;

    // Convert the extra time to the next largest unit
    for (let index = 0; index < nextIndex; index++) {
      extra /= TIME_SIZES[index];
    }

    let value2 = convertTimeUnitsValue(extra);
    let units2 = convertTimeUnitsUnits(value2, nextIndex);

    return [value, units, value2, units2];
  },

  /**
   * Converts a number of seconds to "downloading file opens in X" status.
   * @param aSeconds
   *        Seconds to convert into the time format.
   * @return status object, example:
   *  status = {
   *      l10n: {
   *        id: "downloading-file-opens-in-minutes-and-seconds",
   *        args: { minutes: 2, seconds: 30 },
   *      },
   *   };
   */
  getFormattedTimeStatus: function DU_getFormattedTimeStatus(aSeconds) {
    aSeconds = Math.floor(aSeconds);
    let l10n;
    if (!isFinite(aSeconds) || aSeconds < 0) {
      l10n = {
        id: "downloading-file-opens-in-some-time-2",
      };
    } else if (aSeconds < 60) {
      l10n = {
        id: "downloading-file-opens-in-seconds-2",
        args: { seconds: aSeconds },
      };
    } else if (aSeconds < 3600) {
      let minutes = Math.floor(aSeconds / 60);
      let seconds = aSeconds % 60;
      l10n = seconds
        ? {
            args: { seconds, minutes },
            id: "downloading-file-opens-in-minutes-and-seconds-2",
          }
        : { args: { minutes }, id: "downloading-file-opens-in-minutes-2" };
    } else {
      let hours = Math.floor(aSeconds / 3600);
      let minutes = Math.floor((aSeconds % 3600) / 60);
      l10n = {
        args: { hours, minutes },
        id: "downloading-file-opens-in-hours-and-minutes-2",
      };
    }
    return { l10n };
  },
};

/**
 * Private helper for convertTimeUnits that gets the display value of a time
 *
 * @param aTime
 *        Time value for display
 * @return An integer value for the time rounded down
 */
function convertTimeUnitsValue(aTime) {
  return Math.floor(aTime);
}

/**
 * Private helper for convertTimeUnits that gets the display units of a time
 *
 * @param timeValue
 *        Time value for display
 * @param aIndex
 *        Index into gStr.timeUnits for the appropriate unit
 * @return The appropriate plural form of the unit for the time
 */
function convertTimeUnitsUnits(timeValue, aIndex) {
  // Negative index would be an invalid unit, so just give empty
  if (aIndex < 0) {
    return "";
  }

  return l10n.formatValueSync(TIME_UNITS[aIndex], { timeValue });
}

/**
 * Private helper function to log errors to the error console and command line
 *
 * @param aMsg
 *        Error message to log or an array of strings to concat
 */
// function log(aMsg) {
//   let msg = "DownloadUtils.sys.mjs: " + (aMsg.join ? aMsg.join("") : aMsg);
//   Services.console.logStringMessage(msg);
//   dump(msg + "\n");
// }
PK
!<�2~'�f�fmodules/E10SUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "useSeparateFileUriProcess",
  "browser.tabs.remote.separateFileUriProcess",
  false
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "useSeparatePrivilegedAboutContentProcess",
  "browser.tabs.remote.separatePrivilegedContentProcess",
  false
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "separatePrivilegedMozillaWebContentProcess",
  "browser.tabs.remote.separatePrivilegedMozillaWebContentProcess",
  false
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "separatedMozillaDomains",
  "browser.tabs.remote.separatedMozillaDomains",
  "",
  false,
  val => val.split(",")
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "useCrossOriginOpenerPolicy",
  "browser.tabs.remote.useCrossOriginOpenerPolicy",
  false
);
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "serializationHelper",
  "@mozilla.org/network/serialization-helper;1",
  "nsISerializationHelper"
);
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "extProtService",
  "@mozilla.org/uriloader/external-protocol-service;1",
  "nsIExternalProtocolService"
);

function getOriginalReaderModeURI(aURI) {
  try {
    let searchParams = new URLSearchParams(aURI.query);
    if (searchParams.has("url")) {
      return Services.io.newURI(searchParams.get("url"));
    }
  } catch (e) {}
  return null;
}

const NOT_REMOTE = null;

// These must match the similar ones in RemoteTypes.h, ProcInfo.h, ChromeUtils.webidl and ChromeUtils.cpp
const WEB_REMOTE_TYPE = "web";
const FISSION_WEB_REMOTE_TYPE = "webIsolated";
const WEB_REMOTE_COOP_COEP_TYPE_PREFIX = "webCOOP+COEP=";
const FILE_REMOTE_TYPE = "file";
const EXTENSION_REMOTE_TYPE = "extension";
const PRIVILEGEDABOUT_REMOTE_TYPE = "privilegedabout";
const PRIVILEGEDMOZILLA_REMOTE_TYPE = "privilegedmozilla";
const SERVICEWORKER_REMOTE_TYPE = "webServiceWorker";

// This must start with the WEB_REMOTE_TYPE above.
const DEFAULT_REMOTE_TYPE = WEB_REMOTE_TYPE;

// This list is duplicated between Navigator.cpp and here because navigator
// is not accessible in this context. Please update both if the list changes.
const kSafeSchemes = [
  "bitcoin",
  "ftp",
  "ftps",
  "geo",
  "im",
  "irc",
  "ircs",
  "magnet",
  "mailto",
  "matrix",
  "mms",
  "news",
  "nntp",
  "openpgp4fpr",
  "sftp",
  "sip",
  "sms",
  "smsto",
  "ssh",
  "tel",
  "urn",
  "webcal",
  "wtai",
  "xmpp",
];

const STANDARD_SAFE_PROTOCOLS = kSafeSchemes;

// Note that even if the scheme fits the criteria for a web-handled scheme
// (ie it is compatible with the checks registerProtocolHandler uses), it may
// not be web-handled - it could still be handled via the OS by another app.
function hasPotentiallyWebHandledScheme({ scheme }) {
  // Note that `scheme` comes from a URI object so is already lowercase.
  if (kSafeSchemes.includes(scheme)) {
    return true;
  }
  if (!scheme.startsWith("web+") || scheme.length < 5) {
    return false;
  }
  // Check the rest of the scheme only consists of ascii a-z chars
  return /^[a-z]+$/.test(scheme.substr("web+".length));
}

function validatedWebRemoteType(
  aPreferredRemoteType,
  aTargetUri,
  aCurrentUri,
  aResultPrincipal,
  aRemoteSubframes,
  aOriginAttributes = {}
) {
  // To load into the Privileged Mozilla Content Process you must be https,
  // and be an exact match or a subdomain of an allowlisted domain.
  // This code is duplicated in ProcessIolation.cpp, please update both.
  if (
    lazy.separatePrivilegedMozillaWebContentProcess &&
    aTargetUri.asciiHost &&
    aTargetUri.scheme == "https" &&
    lazy.separatedMozillaDomains.some(function (val) {
      return (
        aTargetUri.asciiHost == val || aTargetUri.asciiHost.endsWith("." + val)
      );
    })
  ) {
    return PRIVILEGEDMOZILLA_REMOTE_TYPE;
  }

  // If we're in the parent and we were passed a web-handled scheme,
  // transform it now to avoid trying to load it in the wrong process.
  if (aRemoteSubframes && hasPotentiallyWebHandledScheme(aTargetUri)) {
    if (
      Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT &&
      Services.appinfo.remoteType.startsWith(FISSION_WEB_REMOTE_TYPE + "=")
    ) {
      // If we're in a child process, assume we're OK to load this non-web
      // URL for now. We'll either load it externally or re-evaluate once
      // we know the "real" URL to which we'll redirect.
      return Services.appinfo.remoteType;
    }

    // This doesn't work (throws) in the child - see
    // https://bugzilla.mozilla.org/show_bug.cgi?id=1589082
    // Even if it did, it'd cause sync IPC
    // ( https://bugzilla.mozilla.org/show_bug.cgi?id=1589085 ), and this code
    // can get called several times per page load so that seems like something
    // we'd want to avoid.
    let handlerInfo = lazy.extProtService.getProtocolHandlerInfo(
      aTargetUri.scheme
    );
    try {
      if (!handlerInfo.alwaysAskBeforeHandling) {
        let app = handlerInfo.preferredApplicationHandler;
        app.QueryInterface(Ci.nsIWebHandlerApp);
        // If we get here, the default handler is a web app.
        // Target to the origin of that web app:
        let uriStr = app.uriTemplate.replace(/%s/, aTargetUri.spec);
        aTargetUri = Services.io.newURI(uriStr);
      }
    } catch (ex) {
      // It's not strange for this to throw, we just ignore it and fall through.
    }
  }

  // If the domain is allow listed to allow it to use file:// URIs, then we have
  // to run it in a file content process, in case it uses file:// sub-resources.
  const sm = Services.scriptSecurityManager;
  if (sm.inFileURIAllowlist(aTargetUri)) {
    return FILE_REMOTE_TYPE;
  }

  // If we're within a fission window, extract site information from the URI in
  // question, and use it to generate an isolated origin.
  if (aRemoteSubframes) {
    let originAttributes = {};
    // Only use specific properties of OriginAttributes in our remoteType
    let { userContextId, privateBrowsingId, geckoViewSessionContextId } =
      aOriginAttributes;
    originAttributes = {
      userContextId,
      privateBrowsingId,
      geckoViewSessionContextId,
    };

    // Get a principal to use for isolation.
    let targetPrincipal;
    if (aResultPrincipal) {
      targetPrincipal = sm.principalWithOA(aResultPrincipal, originAttributes);
    } else {
      targetPrincipal = sm.createContentPrincipal(aTargetUri, originAttributes);
    }

    // If this is a special webCOOP+COEP= remote type that matches the
    // principal's siteOrigin, we don't want to override it with webIsolated=
    // as it's already isolated.
    if (
      aPreferredRemoteType &&
      aPreferredRemoteType.startsWith(
        `${WEB_REMOTE_COOP_COEP_TYPE_PREFIX}${targetPrincipal.siteOrigin}`
      )
    ) {
      return aPreferredRemoteType;
    }

    return `${FISSION_WEB_REMOTE_TYPE}=${targetPrincipal.siteOrigin}`;
    // else fall through and probably return WEB_REMOTE_TYPE
  }

  if (!aPreferredRemoteType) {
    return WEB_REMOTE_TYPE;
  }

  if (aPreferredRemoteType.startsWith(WEB_REMOTE_TYPE)) {
    return aPreferredRemoteType;
  }

  return WEB_REMOTE_TYPE;
}

export var E10SUtils = {
  DEFAULT_REMOTE_TYPE,
  NOT_REMOTE,
  WEB_REMOTE_TYPE,
  WEB_REMOTE_COOP_COEP_TYPE_PREFIX,
  FILE_REMOTE_TYPE,
  EXTENSION_REMOTE_TYPE,
  PRIVILEGEDABOUT_REMOTE_TYPE,
  PRIVILEGEDMOZILLA_REMOTE_TYPE,
  FISSION_WEB_REMOTE_TYPE,
  SERVICEWORKER_REMOTE_TYPE,
  STANDARD_SAFE_PROTOCOLS,

  /**
   * @param aURI The URI of the about page
   * @return The instance of the nsIAboutModule related to this uri
   */
  getAboutModule(aURL) {
    // Needs to match NS_GetAboutModuleName
    let moduleName = aURL.pathQueryRef.replace(/[#?].*/, "").toLowerCase();
    let contract = "@mozilla.org/network/protocol/about;1?what=" + moduleName;
    try {
      return Cc[contract].getService(Ci.nsIAboutModule);
    } catch (e) {
      // Either the about module isn't defined or it is broken. In either case
      // ignore it.
      return null;
    }
  },

  useCrossOriginOpenerPolicy() {
    return lazy.useCrossOriginOpenerPolicy;
  },

  _log: null,
  _uriStr: function uriStr(aUri) {
    return aUri ? aUri.spec : "undefined";
  },

  log: function log() {
    if (!this._log) {
      this._log = console.createInstance({
        prefix: "ProcessSwitch",
        maxLogLevel: "Error", // Change to "Debug" the process switching code
      });

      this._log.debug("Setup logger");
    }

    return this._log;
  },

  /**
   * Serialize csp data.
   *
   * @param {nsIContentSecurity} csp. The csp to serialize.
   * @return {String} The base64 encoded csp data.
   */
  serializeCSP(csp) {
    let serializedCSP = null;

    try {
      if (csp) {
        serializedCSP = lazy.serializationHelper.serializeToString(csp);
      }
    } catch (e) {
      this.log().error(`Failed to serialize csp '${csp}' ${e}`);
    }
    return serializedCSP;
  },

  /**
   * Deserialize a base64 encoded csp (serialized with
   * Utils::serializeCSP).
   *
   * @param {String} csp_b64 A base64 encoded serialized csp.
   * @return {nsIContentSecurityPolicy} A deserialized csp.
   */
  deserializeCSP(csp_b64) {
    if (!csp_b64) {
      return null;
    }

    try {
      let csp = lazy.serializationHelper.deserializeObject(csp_b64);
      csp.QueryInterface(Ci.nsIContentSecurityPolicy);
      return csp;
    } catch (e) {
      this.log().error(`Failed to deserialize csp_b64 '${csp_b64}' ${e}`);
    }
    return null;
  },

  canLoadURIInRemoteType(
    aURL,
    aRemoteSubframes,
    aRemoteType = DEFAULT_REMOTE_TYPE,
    aOriginAttributes = {}
  ) {
    // aRemoteType cannot be undefined, as that would cause it to default to
    // `DEFAULT_REMOTE_TYPE`. This means any falsy remote types are
    // intentionally `NOT_REMOTE`.

    return (
      aRemoteType ==
      this.getRemoteTypeForURI(
        aURL,
        true,
        aRemoteSubframes,
        aRemoteType,
        null,
        aOriginAttributes
      )
    );
  },

  getRemoteTypeForURI(
    aURL,
    aMultiProcess,
    aRemoteSubframes,
    aPreferredRemoteType = DEFAULT_REMOTE_TYPE,
    aCurrentUri,
    aOriginAttributes = {}
  ) {
    if (!aMultiProcess) {
      return NOT_REMOTE;
    }

    // loadURI in browser.js treats null as about:blank
    if (!aURL) {
      aURL = "about:blank";
    }

    let uri;
    try {
      uri = Services.uriFixup.getFixupURIInfo(aURL).preferredURI;
    } catch (e) {
      // If we have an invalid URI, it's still possible that it might get
      // fixed-up into a valid URI later on. However, we don't want to return
      // aPreferredRemoteType here, in case the URI gets fixed-up into
      // something that wouldn't normally run in that process.
      return DEFAULT_REMOTE_TYPE;
    }

    return this.getRemoteTypeForURIObject(uri, {
      multiProcess: aMultiProcess,
      remoteSubFrames: aRemoteSubframes,
      preferredRemoteType: aPreferredRemoteType,
      currentURI: aCurrentUri,
      originAttributes: aOriginAttributes,
    });
  },

  getRemoteTypeForURIObject(aURI, options) {
    let {
      multiProcess = Services.appinfo.browserTabsRemoteAutostart,
      remoteSubFrames = Services.appinfo.fissionAutostart,
      preferredRemoteType = DEFAULT_REMOTE_TYPE,
      currentURI = null,
      resultPrincipal = null,
      originAttributes = {},
    } = options;
    if (!multiProcess) {
      return NOT_REMOTE;
    }

    switch (aURI.scheme) {
      case "javascript":
        // javascript URIs can load in any, they apply to the current document.
        return preferredRemoteType;

      case "data":
      case "blob":
        // We need data: and blob: URIs to load in any remote process, because
        // they need to be able to load in whatever is the current process
        // unless it is non-remote. In that case we don't want to load them in
        // the parent process, so we load them in the default remote process,
        // which is sandboxed and limits any risk.
        return preferredRemoteType == NOT_REMOTE
          ? DEFAULT_REMOTE_TYPE
          : preferredRemoteType;

      case "file":
        return lazy.useSeparateFileUriProcess
          ? FILE_REMOTE_TYPE
          : DEFAULT_REMOTE_TYPE;

      case "about":
        let module = this.getAboutModule(aURI);
        // If the module doesn't exist then an error page will be loading, that
        // should be ok to load in any process
        if (!module) {
          return preferredRemoteType;
        }

        let flags = module.getURIFlags(aURI);
        if (flags & Ci.nsIAboutModule.URI_MUST_LOAD_IN_EXTENSION_PROCESS) {
          return WebExtensionPolicy.useRemoteWebExtensions
            ? EXTENSION_REMOTE_TYPE
            : NOT_REMOTE;
        }

        if (flags & Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD) {
          if (
            flags & Ci.nsIAboutModule.URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS &&
            (lazy.useSeparatePrivilegedAboutContentProcess ||
              aURI.filePath == "logins" ||
              // Force about:welcome and about:home into the privileged content process to
              // workaround code coverage test failures which result from the
              // workaround in bug 161269. Once that bug is fixed for real,
              // the about:welcome and about:home case below can be removed.
              aURI.filePath == "welcome" ||
              aURI.filePath == "home")
          ) {
            return PRIVILEGEDABOUT_REMOTE_TYPE;
          }

          // When loading about:reader, try to display the document in the same
          // web remote type as the document it's loading.
          if (aURI.filePath == "reader") {
            let readerModeURI = getOriginalReaderModeURI(aURI);
            if (readerModeURI) {
              let innerRemoteType = this.getRemoteTypeForURIObject(
                readerModeURI,
                options
              );
              if (
                innerRemoteType &&
                innerRemoteType.startsWith(WEB_REMOTE_TYPE)
              ) {
                return innerRemoteType;
              }
            }
          }

          return DEFAULT_REMOTE_TYPE;
        }

        // If the about page can load in parent or child, it should be safe to
        // load in any remote type.
        if (flags & Ci.nsIAboutModule.URI_CAN_LOAD_IN_CHILD) {
          return preferredRemoteType;
        }

        return NOT_REMOTE;

      case "chrome":
        let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(
          Ci.nsIXULChromeRegistry
        );
        if (chromeReg.mustLoadURLRemotely(aURI)) {
          return DEFAULT_REMOTE_TYPE;
        }

        if (
          chromeReg.canLoadURLRemotely(aURI) &&
          preferredRemoteType != NOT_REMOTE
        ) {
          return DEFAULT_REMOTE_TYPE;
        }

        return NOT_REMOTE;

      case "moz-extension":
        // Extension iframes should load in the same process
        // as their outer frame, but that's handled elsewhere.
        return WebExtensionPolicy.useRemoteWebExtensions
          ? EXTENSION_REMOTE_TYPE
          : NOT_REMOTE;

      case "imap":
      case "mailbox":
      case "news":
      case "nntp":
      case "snews":
        // Protocols used by Thunderbird to display email messages.
        return NOT_REMOTE;

      default:
        // WebExtensions may set up protocol handlers for protocol names
        // beginning with ext+.  These may redirect to http(s) pages or to
        // moz-extension pages.  We can't actually tell here where one of
        // these pages will end up loading but Talos tests use protocol
        // handlers that redirect to extension pages that rely on this
        // behavior so a pageloader frame script is injected correctly.
        // Protocols that redirect to http(s) will just flip back to a
        // regular content process after the redirect.
        if (aURI.scheme.startsWith("ext+")) {
          return WebExtensionPolicy.useRemoteWebExtensions
            ? EXTENSION_REMOTE_TYPE
            : NOT_REMOTE;
        }

        // For any other nested URIs, we use the innerURI to determine the
        // remote type. In theory we should use the innermost URI, but some URIs
        // have fake inner URIs (e.g. about URIs with inner moz-safe-about) and
        // if such URIs are wrapped in other nested schemes like view-source:,
        // we don't want to "skip" past "about:" by going straight to the
        // innermost URI. Any URIs like this will need to be handled in the
        // cases above, so we don't still end up using the fake inner URI here.
        if (aURI instanceof Ci.nsINestedURI) {
          let innerURI = aURI.QueryInterface(Ci.nsINestedURI).innerURI;
          return this.getRemoteTypeForURIObject(innerURI, options);
        }

        var log = this.log();
        log.debug("validatedWebRemoteType()");
        log.debug(`  aPreferredRemoteType: ${preferredRemoteType}`);
        log.debug(`  aTargetUri: ${this._uriStr(aURI)}`);
        log.debug(`  aCurrentUri: ${this._uriStr(currentURI)}`);
        var remoteType = validatedWebRemoteType(
          preferredRemoteType,
          aURI,
          currentURI,
          resultPrincipal,
          remoteSubFrames,
          originAttributes
        );
        log.debug(`  validatedWebRemoteType() returning: ${remoteType}`);
        return remoteType;
    }
  },

  makeInputStream(data) {
    if (typeof data == "string") {
      let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
        Ci.nsISupportsCString
      );
      stream.data = data;
      return stream; // XPConnect will QI this to nsIInputStream for us.
    }

    let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
      Ci.nsISupportsCString
    );
    stream.data = data.content;

    if (data.headers) {
      let mimeStream = Cc[
        "@mozilla.org/network/mime-input-stream;1"
      ].createInstance(Ci.nsIMIMEInputStream);

      mimeStream.setData(stream);
      for (let [name, value] of data.headers) {
        mimeStream.addHeader(name, value);
      }
      return mimeStream;
    }

    return stream; // XPConnect will QI this to nsIInputStream for us.
  },

  /**
   * Serialize principal data.
   *
   * @param {nsIPrincipal} principal The principal to serialize.
   * @return {String} The serialized principal data.
   */
  serializePrincipal(principal) {
    let serializedPrincipal = null;

    try {
      if (principal) {
        serializedPrincipal =
          Services.scriptSecurityManager.principalToJSON(principal);
      }
    } catch (e) {
      this.log().error(`Failed to serialize principal '${principal}' ${e}`);
    }

    return serializedPrincipal;
  },

  /**
   * Deserialize a principal (serialized with serializePrincipal).
   *
   * @param {String} serializedPincipal A serialized principal.
   * @return {nsIPrincipal} A deserialized principal.
   */
  deserializePrincipal(serializedPincipal, fallbackPrincipalCallback = null) {
    if (!serializedPincipal) {
      if (!fallbackPrincipalCallback) {
        this.log().warn(
          "No principal passed to deserializePrincipal and no fallbackPrincipalCallback"
        );
        return null;
      }

      return fallbackPrincipalCallback();
    }

    try {
      let principal;
      // The current JSON representation of principal is not stored as base64. We start by checking
      // if the serialized data starts with '{' to determine if we're using the new JSON representation.
      // If it doesn't we try the two legacy formats, old JSON and nsISerializable.
      if (serializedPincipal.startsWith("{")) {
        principal =
          Services.scriptSecurityManager.JSONToPrincipal(serializedPincipal);
      } else {
        // Both the legacy and legacy  JSON representation of principals are stored as base64
        // The legacy JSON kind are the only ones that will start with "{" when decoded.
        // We check here for the legacy JSON serialized, if it doesn't start with that continue using nsISerializable.
        // JSONToPrincipal accepts a *non* base64 encoded string and returns a principal or a null.
        let tmpa = atob(serializedPincipal);
        if (tmpa.startsWith("{")) {
          principal = Services.scriptSecurityManager.JSONToPrincipal(tmpa);
        } else {
          principal =
            lazy.serializationHelper.deserializeObject(serializedPincipal);
        }
      }
      principal.QueryInterface(Ci.nsIPrincipal);
      return principal;
    } catch (e) {
      this.log().error(
        `Failed to deserialize serializedPincipal '${serializedPincipal}' ${e}`
      );
    }
    if (!fallbackPrincipalCallback) {
      this.log().warn(
        "No principal passed to deserializePrincipal and no fallbackPrincipalCallback"
      );
      return null;
    }
    return fallbackPrincipalCallback();
  },

  /**
   * Serialize cookieJarSettings.
   *
   * @param {nsICookieJarSettings} cookieJarSettings The cookieJarSettings to
   *   serialize.
   * @return {String} The base64 encoded cookieJarSettings data.
   */
  serializeCookieJarSettings(cookieJarSettings) {
    let serialized = null;
    if (cookieJarSettings) {
      try {
        serialized =
          lazy.serializationHelper.serializeToString(cookieJarSettings);
      } catch (e) {
        this.log().error(
          `Failed to serialize cookieJarSettings '${cookieJarSettings}' ${e}`
        );
      }
    }
    return serialized;
  },

  /**
   * Deserialize a base64 encoded cookieJarSettings
   *
   * @param {String} cookieJarSettings_b64 A base64 encoded serialized cookieJarSettings.
   * @return {nsICookieJarSettings} A deserialized cookieJarSettings.
   */
  deserializeCookieJarSettings(cookieJarSettings_b64) {
    let deserialized = null;
    if (cookieJarSettings_b64) {
      try {
        deserialized = lazy.serializationHelper.deserializeObject(
          cookieJarSettings_b64
        );
        deserialized.QueryInterface(Ci.nsICookieJarSettings);
      } catch (e) {
        this.log().error(
          `Failed to deserialize cookieJarSettings_b64 '${cookieJarSettings_b64}' ${e}`
        );
      }
    }
    return deserialized;
  },

  wrapHandlingUserInput(aWindow, aIsHandling, aCallback) {
    var handlingUserInput;
    try {
      handlingUserInput = aWindow.windowUtils.setHandlingUserInput(aIsHandling);
      aCallback();
    } finally {
      handlingUserInput.destruct();
    }
  },

  /**
   * Serialize referrerInfo.
   *
   * @param {nsIReferrerInfo} The referrerInfo to serialize.
   * @return {String} The base64 encoded referrerInfo.
   */
  serializeReferrerInfo(referrerInfo) {
    let serialized = null;
    if (referrerInfo) {
      try {
        serialized = lazy.serializationHelper.serializeToString(referrerInfo);
      } catch (e) {
        this.log().error(
          `Failed to serialize referrerInfo '${referrerInfo}' ${e}`
        );
      }
    }
    return serialized;
  },
  /**
   * Deserialize a base64 encoded referrerInfo
   *
   * @param {String} referrerInfo_b64 A base64 encoded serialized referrerInfo.
   * @return {nsIReferrerInfo} A deserialized referrerInfo.
   */
  deserializeReferrerInfo(referrerInfo_b64) {
    let deserialized = null;
    if (referrerInfo_b64) {
      try {
        deserialized =
          lazy.serializationHelper.deserializeObject(referrerInfo_b64);
        deserialized.QueryInterface(Ci.nsIReferrerInfo);
      } catch (e) {
        this.log().error(
          `Failed to deserialize referrerInfo_b64 '${referrerInfo_b64}' ${e}`
        );
      }
    }
    return deserialized;
  },

  /**
   * Returns the pids for a remote browser and its remote subframes.
   */
  getBrowserPids(aBrowser, aRemoteSubframes) {
    if (!aBrowser.isRemoteBrowser || !aBrowser.frameLoader) {
      return [];
    }
    let tabPid = aBrowser.frameLoader.remoteTab.osPid;
    let pids = new Set();
    if (aRemoteSubframes) {
      let stack = [aBrowser.browsingContext];
      while (stack.length) {
        let bc = stack.pop();
        stack.push(...bc.children);
        if (bc.currentWindowGlobal) {
          let pid = bc.currentWindowGlobal.osPid;
          if (pid != tabPid) {
            pids.add(pid);
          }
        }
      }
    }
    return [tabPid, ...pids];
  },

  /**
   * The suffix after a `=` in a remoteType is dynamic, and used to control the
   * process pool to use. The C++ version of this method is mozilla::dom::RemoteTypePrefix().
   */
  remoteTypePrefix(aRemoteType) {
    return aRemoteType.split("=")[0];
  },

  /**
   * There are various types of remote types that are for web content processes, but
   * they all start with "web". The C++ version of this method is
   * mozilla::dom::IsWebRemoteType().
   */
  isWebRemoteType(aRemoteType) {
    return aRemoteType.startsWith(WEB_REMOTE_TYPE);
  },

  /**
   * Assemble or predict originAttributes from available arguments.
   */
  predictOriginAttributes({
    window,
    browser,
    userContextId,
    geckoViewSessionContextId,
    privateBrowsingId,
  }) {
    if (browser) {
      if (browser.browsingContext) {
        return browser.browsingContext.originAttributes;
      }
      if (!window) {
        window = browser.contentDocument?.defaultView;
      }
      if (!userContextId) {
        userContextId = browser.getAttribute("usercontextid") || 0;
      }
      if (!geckoViewSessionContextId) {
        geckoViewSessionContextId =
          browser.getAttribute("geckoViewSessionContextId") || "";
      }
    }

    if (window && !privateBrowsingId) {
      privateBrowsingId = window.browsingContext.usePrivateBrowsing ? 1 : 0;
    }
    return { privateBrowsingId, userContextId, geckoViewSessionContextId };
  },
};

ChromeUtils.defineLazyGetter(
  E10SUtils,
  "SERIALIZED_SYSTEMPRINCIPAL",
  function () {
    return btoa(
      E10SUtils.serializePrincipal(
        Services.scriptSecurityManager.getSystemPrincipal()
      )
    );
  }
);
PK
!<��>>>"modules/EnterprisePolicies.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export function EnterprisePolicies() {
  // eslint-disable-next-line mozilla/use-services
  const appinfo = Cc["@mozilla.org/xre/app-info;1"].getService(
    Ci.nsIXULRuntime
  );
  if (appinfo.processType == appinfo.PROCESS_TYPE_DEFAULT) {
    const { EnterprisePoliciesManager } = ChromeUtils.importESModule(
      "resource://gre/modules/EnterprisePoliciesParent.sys.mjs"
    );
    return new EnterprisePoliciesManager();
  }
  const { EnterprisePoliciesManagerContent } = ChromeUtils.importESModule(
    "resource://gre/modules/EnterprisePoliciesContent.sys.mjs"
  );
  return new EnterprisePoliciesManagerContent();
}
PK
!<�'���)modules/EnterprisePoliciesContent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class EnterprisePoliciesManagerContent {
  get status() {
    return (
      Services.cpmm.sharedData.get("EnterprisePolicies:Status") ||
      Ci.nsIEnterprisePolicies.INACTIVE
    );
  }

  isAllowed(feature) {
    let disallowedFeatures = Services.cpmm.sharedData.get(
      "EnterprisePolicies:DisallowedFeatures"
    );
    return !(disallowedFeatures && disallowedFeatures.has(feature));
  }
}

EnterprisePoliciesManagerContent.prototype.QueryInterface =
  ChromeUtils.generateQI(["nsIEnterprisePolicies"]);
PK
!<w~O�!V!V(modules/EnterprisePoliciesParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  JsonSchemaValidator:
    "resource://gre/modules/components-utils/JsonSchemaValidator.sys.mjs",
  Policies: "resource:///modules/policies/Policies.sys.mjs",
  WindowsGPOParser: "resource://gre/modules/policies/WindowsGPOParser.sys.mjs",
  macOSPoliciesParser:
    "resource://gre/modules/policies/macOSPoliciesParser.sys.mjs",
});

// This is the file that will be searched for in the
// ${InstallDir}/distribution folder.
const POLICIES_FILENAME = "policies.json";

// When true browser policy is loaded per-user from
// /run/user/$UID/appname
const PREF_PER_USER_DIR = "toolkit.policies.perUserDir";
// For easy testing, modify the helpers/sample.json file,
// and set PREF_ALTERNATE_PATH in firefox.js as:
// /your/repo/browser/components/enterprisepolicies/helpers/sample.json
const PREF_ALTERNATE_PATH = "browser.policies.alternatePath";
// For testing GPO, you can set an alternate location in testing
const PREF_ALTERNATE_GPO = "browser.policies.alternateGPO";

// For testing, we may want to set PREF_ALTERNATE_PATH to point to a file
// relative to the test root directory. In order to enable this, the string
// below may be placed at the beginning of that preference value and it will
// be replaced with the path to the test root directory.
const MAGIC_TEST_ROOT_PREFIX = "<test-root>";
const PREF_TEST_ROOT = "mochitest.testRoot";

const PREF_LOGLEVEL = "browser.policies.loglevel";

// To force disallowing enterprise-only policies during tests
const PREF_DISALLOW_ENTERPRISE = "browser.policies.testing.disallowEnterprise";

// To allow for cleaning up old policies
const PREF_POLICIES_APPLIED = "browser.policies.applied";

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  let { ConsoleAPI } = ChromeUtils.importESModule(
    "resource://gre/modules/Console.sys.mjs"
  );
  return new ConsoleAPI({
    prefix: "Enterprise Policies",
    // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
    // messages during development. See LOG_LEVELS in Console.sys.mjs for details.
    maxLogLevel: "error",
    maxLogLevelPref: PREF_LOGLEVEL,
  });
});

const isXpcshell = Services.env.exists("XPCSHELL_TEST_PROFILE_DIR");

// We're only testing for empty objects, not
// empty strings or empty arrays.
function isEmptyObject(obj) {
  if (typeof obj != "object" || Array.isArray(obj)) {
    return false;
  }
  for (let key of Object.keys(obj)) {
    if (!isEmptyObject(obj[key])) {
      return false;
    }
  }
  return true;
}

export function EnterprisePoliciesManager() {
  Services.obs.addObserver(this, "profile-after-change", true);
  Services.obs.addObserver(this, "final-ui-startup", true);
  Services.obs.addObserver(this, "sessionstore-windows-restored", true);
  Services.obs.addObserver(this, "EnterprisePolicies:Restart", true);
}

EnterprisePoliciesManager.prototype = {
  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
    "nsIEnterprisePolicies",
  ]),

  _initialize() {
    if (Services.prefs.getBoolPref(PREF_POLICIES_APPLIED, false)) {
      if ("_cleanup" in lazy.Policies) {
        let policyImpl = lazy.Policies._cleanup;

        for (let timing of Object.keys(this._callbacks)) {
          let policyCallback = policyImpl[timing];
          if (policyCallback) {
            this._schedulePolicyCallback(
              timing,
              policyCallback.bind(
                policyImpl,
                this /* the EnterprisePoliciesManager */
              )
            );
          }
        }
      }
      Services.prefs.clearUserPref(PREF_POLICIES_APPLIED);
    }

    let provider = this._chooseProvider();

    if (provider.failed) {
      this.status = Ci.nsIEnterprisePolicies.FAILED;
      this._reportEnterpriseTelemetry();
      return;
    }

    if (!provider.hasPolicies) {
      this.status = Ci.nsIEnterprisePolicies.INACTIVE;
      this._reportEnterpriseTelemetry();
      return;
    }

    this.status = Ci.nsIEnterprisePolicies.ACTIVE;
    this._parsedPolicies = {};
    this._reportEnterpriseTelemetry(provider.policies);
    this._activatePolicies(provider.policies);

    Services.prefs.setBoolPref(PREF_POLICIES_APPLIED, true);
  },

  _reportEnterpriseTelemetry(policies = {}) {
    let excludedDistributionIDs = [
      "mozilla-mac-eol-esr115",
      "mozilla-win-eol-esr115",
    ];
    let distroId = Services.prefs
      .getDefaultBranch(null)
      .getCharPref("distribution.id", "");

    let policiesLength = Object.keys(policies).length;

    Services.telemetry.scalarSet("policies.count", policiesLength);

    let isEnterprise =
      // As we migrate folks to ESR for other reasons (deprecating an OS),
      // we need to add checks here for distribution IDs.
      (AppConstants.IS_ESR && !excludedDistributionIDs.includes(distroId)) ||
      // If there are multiple policies then its enterprise.
      policiesLength > 1 ||
      // If ImportEnterpriseRoots isn't the only policy then it's enterprise.
      (policiesLength && !policies.Certificates?.ImportEnterpriseRoots);

    Services.telemetry.scalarSet("policies.is_enterprise", isEnterprise);
  },

  _chooseProvider() {
    let platformProvider = null;
    if (AppConstants.platform == "win" && AppConstants.MOZ_SYSTEM_POLICIES) {
      platformProvider = new WindowsGPOPoliciesProvider();
    } else if (
      AppConstants.platform == "macosx" &&
      AppConstants.MOZ_SYSTEM_POLICIES
    ) {
      platformProvider = new macOSPoliciesProvider();
    }
    let jsonProvider = new JSONPoliciesProvider();
    if (platformProvider && platformProvider.hasPolicies) {
      if (jsonProvider.hasPolicies) {
        return new CombinedProvider(platformProvider, jsonProvider);
      }
      return platformProvider;
    }
    return jsonProvider;
  },

  _activatePolicies(unparsedPolicies) {
    let { schema } = ChromeUtils.importESModule(
      "resource:///modules/policies/schema.sys.mjs"
    );

    for (let policyName of Object.keys(unparsedPolicies)) {
      let policySchema = schema.properties[policyName];
      let policyParameters = unparsedPolicies[policyName];

      if (!policySchema) {
        lazy.log.error(`Unknown policy: ${policyName}`);
        continue;
      }

      if (policySchema.enterprise_only && !areEnterpriseOnlyPoliciesAllowed()) {
        lazy.log.error(`Policy ${policyName} is only allowed on ESR`);
        continue;
      }

      let { valid: parametersAreValid, parsedValue: parsedParameters } =
        lazy.JsonSchemaValidator.validate(policyParameters, policySchema, {
          allowAdditionalProperties: true,
        });

      if (!parametersAreValid) {
        lazy.log.error(`Invalid parameters specified for ${policyName}.`);
        continue;
      }

      let policyImpl = lazy.Policies[policyName];

      if (policyImpl.validate && !policyImpl.validate(parsedParameters)) {
        lazy.log.error(
          `Parameters for ${policyName} did not validate successfully.`
        );
        continue;
      }

      this._parsedPolicies[policyName] = parsedParameters;

      for (let timing of Object.keys(this._callbacks)) {
        let policyCallback = policyImpl[timing];
        if (policyCallback) {
          this._schedulePolicyCallback(
            timing,
            policyCallback.bind(
              policyImpl,
              this /* the EnterprisePoliciesManager */,
              parsedParameters
            )
          );
        }
      }
    }
  },

  _callbacks: {
    // The earliest that a policy callback can run. This will
    // happen right after the Policy Engine itself has started,
    // and before the Add-ons Manager has started.
    onBeforeAddons: [],

    // This happens after all the initialization related to
    // the profile has finished (prefs, places database, etc.).
    onProfileAfterChange: [],

    // Just before the first browser window gets created.
    onBeforeUIStartup: [],

    // Called after all windows from the last session have been
    // restored (or the default window and homepage tab, if the
    // session is not being restored).
    // The content of the tabs themselves have not necessarily
    // finished loading.
    onAllWindowsRestored: [],
  },

  _schedulePolicyCallback(timing, callback) {
    this._callbacks[timing].push(callback);
  },

  _runPoliciesCallbacks(timing) {
    let callbacks = this._callbacks[timing];
    while (callbacks.length) {
      let callback = callbacks.shift();
      try {
        callback();
      } catch (ex) {
        lazy.log.error("Error running ", callback, `for ${timing}:`, ex);
      }
    }
  },

  async _restart() {
    DisallowedFeatures = {};

    Services.ppmm.sharedData.delete("EnterprisePolicies:Status");
    Services.ppmm.sharedData.delete("EnterprisePolicies:DisallowedFeatures");

    this._status = Ci.nsIEnterprisePolicies.UNINITIALIZED;
    this._parsedPolicies = undefined;
    for (let timing of Object.keys(this._callbacks)) {
      this._callbacks[timing] = [];
    }

    // Simulate the startup process. This step-by-step is a bit ugly but it
    // tries to emulate the same behavior as of a normal startup.
    let notifyTopicOnIdle = topic =>
      new Promise(resolve => {
        ChromeUtils.idleDispatch(() => {
          this.observe(null, topic, "");
          resolve();
        });
      });
    await notifyTopicOnIdle("policies-startup");
    await notifyTopicOnIdle("profile-after-change");
    await notifyTopicOnIdle("final-ui-startup");
    await notifyTopicOnIdle("sessionstore-windows-restored");
  },

  // nsIObserver implementation
  observe: function BG_observe(subject, topic) {
    switch (topic) {
      case "policies-startup":
        // Before the first set of policy callbacks runs, we must
        // initialize the service.
        this._initialize();

        this._runPoliciesCallbacks("onBeforeAddons");
        break;

      case "profile-after-change":
        this._runPoliciesCallbacks("onProfileAfterChange");
        break;

      case "final-ui-startup":
        this._runPoliciesCallbacks("onBeforeUIStartup");
        break;

      case "sessionstore-windows-restored":
        this._runPoliciesCallbacks("onAllWindowsRestored");

        // After the last set of policy callbacks ran, notify the test observer.
        Services.obs.notifyObservers(
          null,
          "EnterprisePolicies:AllPoliciesApplied"
        );
        break;

      case "EnterprisePolicies:Restart":
        this._restart().then(null, console.error);
        break;
    }
  },

  disallowFeature(feature, neededOnContentProcess = false) {
    DisallowedFeatures[feature] = neededOnContentProcess;

    // NOTE: For optimization purposes, only features marked as needed
    // on content process will be passed onto the child processes.
    if (neededOnContentProcess) {
      Services.ppmm.sharedData.set(
        "EnterprisePolicies:DisallowedFeatures",
        new Set(
          Object.keys(DisallowedFeatures).filter(key => DisallowedFeatures[key])
        )
      );
    }
  },

  // ------------------------------
  // public nsIEnterprisePolicies members
  // ------------------------------

  _status: Ci.nsIEnterprisePolicies.UNINITIALIZED,

  set status(val) {
    this._status = val;
    if (val != Ci.nsIEnterprisePolicies.INACTIVE) {
      Services.ppmm.sharedData.set("EnterprisePolicies:Status", val);
    }
  },

  get status() {
    return this._status;
  },

  isAllowed: function BG_sanitize(feature) {
    return !(feature in DisallowedFeatures);
  },

  getActivePolicies() {
    return this._parsedPolicies;
  },

  setSupportMenu(supportMenu) {
    SupportMenu = supportMenu;
  },

  getSupportMenu() {
    return SupportMenu;
  },

  setExtensionPolicies(extensionPolicies) {
    ExtensionPolicies = extensionPolicies;
  },

  getExtensionPolicy(extensionID) {
    if (ExtensionPolicies && extensionID in ExtensionPolicies) {
      return ExtensionPolicies[extensionID];
    }
    return null;
  },

  setExtensionSettings(extensionSettings) {
    ExtensionSettings = extensionSettings;
    if (
      "*" in extensionSettings &&
      "install_sources" in extensionSettings["*"]
    ) {
      InstallSources = new MatchPatternSet(
        extensionSettings["*"].install_sources
      );
    }
  },

  getExtensionSettings(extensionID) {
    let settings = null;
    if (ExtensionSettings) {
      if (extensionID in ExtensionSettings) {
        settings = ExtensionSettings[extensionID];
      } else if ("*" in ExtensionSettings) {
        settings = ExtensionSettings["*"];
      }
    }
    return settings;
  },

  mayInstallAddon(addon) {
    // See https://dev.chromium.org/administrators/policy-list-3/extension-settings-full
    if (!ExtensionSettings) {
      return true;
    }
    if (addon.id in ExtensionSettings) {
      if ("installation_mode" in ExtensionSettings[addon.id]) {
        switch (ExtensionSettings[addon.id].installation_mode) {
          case "blocked":
            return false;
          default:
            return true;
        }
      }
    }
    if ("*" in ExtensionSettings) {
      if (
        ExtensionSettings["*"].installation_mode &&
        ExtensionSettings["*"].installation_mode == "blocked"
      ) {
        return false;
      }
      if ("allowed_types" in ExtensionSettings["*"]) {
        return ExtensionSettings["*"].allowed_types.includes(addon.type);
      }
    }
    return true;
  },

  allowedInstallSource(uri) {
    return InstallSources ? InstallSources.matches(uri) : true;
  },

  isExemptExecutableExtension(url, extension) {
    let urlObject;
    try {
      urlObject = new URL(url);
    } catch (e) {
      return false;
    }
    let { hostname } = urlObject;
    let exemptArray =
      this.getActivePolicies()
        ?.ExemptDomainFileTypePairsFromFileTypeDownloadWarnings;
    if (!hostname || !extension || !exemptArray) {
      return false;
    }
    extension = extension.toLowerCase();
    let domains = exemptArray
      .filter(item => item.file_extension.toLowerCase() == extension)
      .map(item => item.domains)
      .flat();
    for (let domain of domains) {
      if (Services.eTLD.hasRootDomain(hostname, domain)) {
        return true;
      }
    }
    return false;
  },
};

let DisallowedFeatures = {};
let SupportMenu = null;
let ExtensionPolicies = null;
let ExtensionSettings = null;
let InstallSources = null;

/**
 * areEnterpriseOnlyPoliciesAllowed
 *
 * Checks whether the policies marked as enterprise_only in the
 * schema are allowed to run on this browser.
 *
 * This is meant to only allow policies to run on ESR, but in practice
 * we allow it to run on channels different than release, to allow
 * these policies to be tested on pre-release channels.
 *
 * @returns {Bool} Whether the policy can run.
 */
function areEnterpriseOnlyPoliciesAllowed() {
  if (Cu.isInAutomation || isXpcshell) {
    if (Services.prefs.getBoolPref(PREF_DISALLOW_ENTERPRISE, false)) {
      // This is used as an override to test the "enterprise_only"
      // functionality itself on tests.
      return false;
    }
    return true;
  }

  return (
    AppConstants.IS_ESR ||
    AppConstants.MOZ_DEV_EDITION ||
    AppConstants.NIGHTLY_BUILD
  );
}

/*
 * JSON PROVIDER OF POLICIES
 *
 * This is a platform-agnostic provider which looks for
 * policies specified through a policies.json file stored
 * in the installation's distribution folder.
 */

class JSONPoliciesProvider {
  constructor() {
    this._policies = null;
    this._readData();
  }

  get hasPolicies() {
    return this._policies !== null && !isEmptyObject(this._policies);
  }

  get policies() {
    return this._policies;
  }

  get failed() {
    return this._failed;
  }

  _getConfigurationFile() {
    let configFile = null;

    if (AppConstants.platform == "linux" && AppConstants.MOZ_SYSTEM_POLICIES) {
      let systemConfigFile = Services.dirsvc.get("SysConfD", Ci.nsIFile);
      systemConfigFile.append("policies");
      systemConfigFile.append(POLICIES_FILENAME);
      if (systemConfigFile.exists()) {
        return systemConfigFile;
      }
    }

    try {
      let perUserPath = Services.prefs.getBoolPref(PREF_PER_USER_DIR, false);
      if (perUserPath) {
        configFile = Services.dirsvc.get("XREUserRunTimeDir", Ci.nsIFile);
      } else {
        configFile = Services.dirsvc.get("XREAppDist", Ci.nsIFile);
      }
      configFile.append(POLICIES_FILENAME);
    } catch (ex) {
      // Getting the correct directory will fail in xpcshell tests. This should
      // be handled the same way as if the configFile simply does not exist.
    }

    let alternatePath = Services.prefs.getStringPref(PREF_ALTERNATE_PATH, "");

    // Check if we are in automation *before* we use the synchronous
    // nsIFile.exists() function or allow the config file to be overriden
    // An alternate policy path can also be used in Nightly builds (for
    // testing purposes), but the Background Update Agent will be unable to
    // detect the alternate policy file so the DisableAppUpdate policy may not
    // work as expected.
    if (
      alternatePath &&
      (Cu.isInAutomation || AppConstants.NIGHTLY_BUILD || isXpcshell) &&
      (!configFile || !configFile.exists())
    ) {
      if (alternatePath.startsWith(MAGIC_TEST_ROOT_PREFIX)) {
        // Intentionally not using a default value on this pref lookup. If no
        // test root is set, we are not currently testing and this function
        // should throw rather than returning something.
        let testRoot = Services.prefs.getStringPref(PREF_TEST_ROOT);
        let relativePath = alternatePath.substring(
          MAGIC_TEST_ROOT_PREFIX.length
        );
        if (AppConstants.platform == "win") {
          relativePath = relativePath.replace(/\//g, "\\");
        }
        alternatePath = testRoot + relativePath;
      }

      configFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
      configFile.initWithPath(alternatePath);
    }

    return configFile;
  }

  _readData() {
    let configFile = this._getConfigurationFile();
    if (!configFile) {
      // Do nothing, _policies will remain null
      return;
    }
    try {
      let data = Cu.readUTF8File(configFile);
      if (data) {
        lazy.log.debug(`policies.json path = ${configFile.path}`);
        lazy.log.debug(`policies.json content = ${data}`);
        this._policies = JSON.parse(data).policies;

        if (!this._policies) {
          lazy.log.error("Policies file doesn't contain a 'policies' object");
          this._failed = true;
        }
      }
    } catch (ex) {
      if (
        ex instanceof Components.Exception &&
        ex.result == Cr.NS_ERROR_FILE_NOT_FOUND
      ) {
        // Do nothing, _policies will remain null
      } else if (ex instanceof SyntaxError) {
        lazy.log.error(`Error parsing JSON file: ${ex}`);
        this._failed = true;
      } else {
        lazy.log.error(`Error reading JSON file: ${ex}`);
        this._failed = true;
      }
    }
  }
}

class WindowsGPOPoliciesProvider {
  constructor() {
    this._policies = null;

    let wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
      Ci.nsIWindowsRegKey
    );

    // Machine policies override user policies, so we read
    // user policies first and then replace them if necessary.
    this._readData(wrk, wrk.ROOT_KEY_CURRENT_USER);
    // We don't access machine policies in testing
    if (!Cu.isInAutomation && !isXpcshell) {
      this._readData(wrk, wrk.ROOT_KEY_LOCAL_MACHINE);
    }
  }

  get hasPolicies() {
    return this._policies !== null && !isEmptyObject(this._policies);
  }

  get policies() {
    return this._policies;
  }

  get failed() {
    return this._failed;
  }

  _readData(wrk, root) {
    try {
      let regLocation = "SOFTWARE\\Policies";
      if (Cu.isInAutomation || isXpcshell) {
        try {
          regLocation = Services.prefs.getStringPref(PREF_ALTERNATE_GPO);
        } catch (e) {}
      }
      wrk.open(root, regLocation, wrk.ACCESS_READ);
      if (wrk.hasChild("Mozilla\\" + Services.appinfo.name)) {
        lazy.log.debug(
          `root = ${
            root == wrk.ROOT_KEY_CURRENT_USER
              ? "HKEY_CURRENT_USER"
              : "HKEY_LOCAL_MACHINE"
          }`
        );
        this._policies = lazy.WindowsGPOParser.readPolicies(
          wrk,
          this._policies
        );
      }
      wrk.close();
    } catch (e) {
      lazy.log.error("Unable to access registry - ", e);
    }
  }
}

class macOSPoliciesProvider {
  constructor() {
    this._policies = null;
    let prefReader = Cc["@mozilla.org/mac-preferences-reader;1"].createInstance(
      Ci.nsIMacPreferencesReader
    );
    if (!prefReader.policiesEnabled()) {
      return;
    }
    this._policies = lazy.macOSPoliciesParser.readPolicies(prefReader);
  }

  get hasPolicies() {
    return this._policies !== null && Object.keys(this._policies).length;
  }

  get policies() {
    return this._policies;
  }

  get failed() {
    return this._failed;
  }
}

class CombinedProvider {
  constructor(primaryProvider, secondaryProvider) {
    // Combine policies with primaryProvider taking precedence.
    // We only do this for top level policies.
    this._policies = primaryProvider._policies;
    for (let policyName of Object.keys(secondaryProvider.policies)) {
      if (!(policyName in this._policies)) {
        this._policies[policyName] = secondaryProvider.policies[policyName];
      }
    }
  }

  get hasPolicies() {
    // Combined provider always has policies.
    return true;
  }

  get policies() {
    return this._policies;
  }

  get failed() {
    // Combined provider never fails.
    return false;
  }
}
PK
!<�̜M--modules/EventEmitter.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export function EventEmitter() {}

let loggingEnabled = Services.prefs.getBoolPref("toolkit.dump.emit");
Services.prefs.addObserver("toolkit.dump.emit", {
  observe: () => {
    loggingEnabled = Services.prefs.getBoolPref("toolkit.dump.emit");
  },
});

/**
 * Decorate an object with event emitter functionality.
 *
 * @param Object objectToDecorate
 *        Bind all public methods of EventEmitter to
 *        the objectToDecorate object.
 */
EventEmitter.decorate = function (objectToDecorate) {
  let emitter = new EventEmitter();
  objectToDecorate.on = emitter.on.bind(emitter);
  objectToDecorate.off = emitter.off.bind(emitter);
  objectToDecorate.once = emitter.once.bind(emitter);
  objectToDecorate.emit = emitter.emit.bind(emitter);
};

function describeNthCaller(n) {
  let caller = Components.stack;
  // Do one extra iteration to skip this function.
  while (n >= 0) {
    --n;
    caller = caller.caller;
  }

  let func = caller.name;
  let file = caller.filename;
  if (file.includes(" -> ")) {
    file = caller.filename.split(/ -> /)[1];
  }
  let path = file + ":" + caller.lineNumber;

  return func + "() -> " + path;
}

EventEmitter.prototype = {
  /**
   * Connect a listener.
   *
   * @param string event
   *        The event name to which we're connecting.
   * @param function listener
   *        Called when the event is fired.
   */
  on(event, listener) {
    if (!this._eventEmitterListeners) {
      this._eventEmitterListeners = new Map();
    }
    if (!this._eventEmitterListeners.has(event)) {
      this._eventEmitterListeners.set(event, []);
    }
    this._eventEmitterListeners.get(event).push(listener);
  },

  /**
   * Listen for the next time an event is fired.
   *
   * @param string event
   *        The event name to which we're connecting.
   * @param function listener
   *        (Optional) Called when the event is fired. Will be called at most
   *        one time.
   * @return promise
   *        A promise which is resolved when the event next happens. The
   *        resolution value of the promise is the first event argument. If
   *        you need access to second or subsequent event arguments (it's rare
   *        that this is needed) then use listener
   */
  once(event, listener) {
    return new Promise(resolve => {
      let handler = (_, first, ...rest) => {
        this.off(event, handler);
        if (listener) {
          listener(event, first, ...rest);
        }
        resolve(first);
      };

      handler._originalListener = listener;
      this.on(event, handler);
    });
  },

  /**
   * Remove a previously-registered event listener.  Works for events
   * registered with either on or once.
   *
   * @param string event
   *        The event name whose listener we're disconnecting.
   * @param function listener
   *        The listener to remove.
   */
  off(event, listener) {
    if (!this._eventEmitterListeners) {
      return;
    }
    let listeners = this._eventEmitterListeners.get(event);
    if (listeners) {
      this._eventEmitterListeners.set(
        event,
        listeners.filter(l => {
          return l !== listener && l._originalListener !== listener;
        })
      );
    }
  },

  /**
   * Emit an event.  All arguments to this method will
   * be sent to listener functions.
   */
  emit(event) {
    this.logEvent(event, arguments);

    if (
      !this._eventEmitterListeners ||
      !this._eventEmitterListeners.has(event)
    ) {
      return;
    }

    let originalListeners = this._eventEmitterListeners.get(event);
    for (let listener of this._eventEmitterListeners.get(event)) {
      // If the object was destroyed during event emission, stop
      // emitting.
      if (!this._eventEmitterListeners) {
        break;
      }

      // If listeners were removed during emission, make sure the
      // event handler we're going to fire wasn't removed.
      if (
        originalListeners === this._eventEmitterListeners.get(event) ||
        this._eventEmitterListeners.get(event).some(l => l === listener)
      ) {
        try {
          listener.apply(null, arguments);
        } catch (ex) {
          console.error(ex);
        }
      }
    }
  },

  logEvent(event, args) {
    if (!loggingEnabled) {
      return;
    }

    let description = describeNthCaller(2);

    let argOut = "(";
    if (args.length === 1) {
      argOut += event;
    }

    let out = "EMITTING: ";

    // We need this try / catch to prevent any dead object errors.
    try {
      for (let i = 1; i < args.length; i++) {
        if (i === 1) {
          argOut = "(" + event + ", ";
        } else {
          argOut += ", ";
        }

        let arg = args[i];
        argOut += arg;

        if (arg && arg.nodeName) {
          argOut += " (" + arg.nodeName;
          if (arg.id) {
            argOut += "#" + arg.id;
          }
          if (arg.className) {
            argOut += "." + arg.className;
          }
          argOut += ")";
        }
      }
    } catch (e) {
      // Object is dead so the toolbox is most likely shutting down,
      // do nothing.
    }

    argOut += ")";
    out += "emit" + argOut + " from " + description + "\n";

    dump(out);
  },
};
PK
!<j��dd!modules/ExtHandlerService.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const {
  saveToDisk,
  alwaysAsk,
  useHelperApp,
  handleInternally,
  useSystemDefault,
} = Ci.nsIHandlerInfo;

const TOPIC_PDFJS_HANDLER_CHANGED = "pdfjs:handlerChanged";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  kHandlerList: "resource://gre/modules/handlers/HandlerList.sys.mjs",
  kHandlerListVersion: "resource://gre/modules/handlers/HandlerList.sys.mjs",
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
  JSONFile: "resource://gre/modules/JSONFile.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "externalProtocolService",
  "@mozilla.org/uriloader/external-protocol-service;1",
  "nsIExternalProtocolService"
);
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "MIMEService",
  "@mozilla.org/mime;1",
  "nsIMIMEService"
);

export function HandlerService() {
  // Observe handlersvc-json-replace so we can switch to the datasource
  Services.obs.addObserver(this, "handlersvc-json-replace", true);
}

HandlerService.prototype = {
  QueryInterface: ChromeUtils.generateQI([
    "nsISupportsWeakReference",
    "nsIHandlerService",
    "nsIObserver",
  ]),

  __store: null,
  get _store() {
    if (!this.__store) {
      this.__store = new lazy.JSONFile({
        path: PathUtils.join(
          Services.dirsvc.get("ProfD", Ci.nsIFile).path,
          "handlers.json"
        ),
        dataPostProcessor: this._dataPostProcessor.bind(this),
      });
    }

    // Always call this even if this.__store was set, since it may have been
    // set by asyncInit, which might not have completed yet.
    this._ensureStoreInitialized();
    return this.__store;
  },

  __storeInitialized: false,
  _ensureStoreInitialized() {
    if (!this.__storeInitialized) {
      this.__storeInitialized = true;
      this.__store.ensureDataReady();

      this._injectDefaultProtocolHandlersIfNeeded();
      this._migrateProtocolHandlersIfNeeded();

      Services.obs.notifyObservers(null, "handlersvc-store-initialized");
    }
  },

  _dataPostProcessor(data) {
    return data.defaultHandlersVersion
      ? data
      : {
          defaultHandlersVersion: {},
          mimeTypes: {},
          schemes: {},
          isDownloadsImprovementsAlreadyMigrated: false,
        };
  },

  /**
   * Injects new default protocol handlers if the version in the preferences is
   * newer than the one in the data store.
   */
  _injectDefaultProtocolHandlersIfNeeded() {
    try {
      let defaultHandlersVersion = Services.prefs.getIntPref(
        "gecko.handlerService.defaultHandlersVersion",
        0
      );
      if (defaultHandlersVersion < lazy.kHandlerListVersion) {
        this._injectDefaultProtocolHandlers();
        Services.prefs.setIntPref(
          "gecko.handlerService.defaultHandlersVersion",
          lazy.kHandlerListVersion
        );
        // Now save the result:
        this._store.saveSoon();
      }
    } catch (ex) {
      console.error(ex);
    }
  },

  _injectDefaultProtocolHandlers() {
    let locale = Services.locale.appLocaleAsBCP47;

    // Initialize handlers to default and update based on locale.
    let localeHandlers = lazy.kHandlerList.default;
    if (lazy.kHandlerList[locale]) {
      for (let scheme in lazy.kHandlerList[locale].schemes) {
        localeHandlers.schemes[scheme] =
          lazy.kHandlerList[locale].schemes[scheme];
      }
    }

    // Now, we're going to cheat. Terribly. The idiologically correct way
    // of implementing the following bit of code would be to fetch the
    // handler info objects from the protocol service, manipulate those,
    // and then store each of them.
    // However, that's expensive. It causes us to talk to the OS about
    // default apps, which causes the OS to go hit the disk.
    // All we're trying to do is insert some web apps into the list. We
    // don't care what's already in the file, we just want to do the
    // equivalent of appending into the database. So let's just go do that:
    for (let scheme of Object.keys(localeHandlers.schemes)) {
      if (scheme == "mailto" && AppConstants.MOZ_APP_NAME == "thunderbird") {
        // Thunderbird IS a mailto handler, it doesn't need handlers added.
        continue;
      }

      let existingSchemeInfo = this._store.data.schemes[scheme];
      if (!existingSchemeInfo) {
        // Haven't seen this scheme before. Default to asking which app the
        // user wants to use:
        existingSchemeInfo = {
          // Signal to future readers that we didn't ask the OS anything.
          // When the entry is first used, get the info from the OS.
          stubEntry: true,
          // The first item in the list is the preferred handler, and
          // there isn't one, so we fill in null:
          handlers: [null],
        };
        this._store.data.schemes[scheme] = existingSchemeInfo;
      }
      let { handlers } = existingSchemeInfo;
      for (let newHandler of localeHandlers.schemes[scheme].handlers) {
        if (!newHandler.uriTemplate) {
          console.error(
            `Ignoring protocol handler for ${scheme} without a uriTemplate!`
          );
          continue;
        }
        if (!newHandler.uriTemplate.startsWith("https://")) {
          console.error(
            `Ignoring protocol handler for ${scheme} with insecure template URL ${newHandler.uriTemplate}.`
          );
          continue;
        }
        if (!newHandler.uriTemplate.toLowerCase().includes("%s")) {
          console.error(
            `Ignoring protocol handler for ${scheme} with invalid template URL ${newHandler.uriTemplate}.`
          );
          continue;
        }
        // If there is already a handler registered with the same template
        // URL, ignore the new one:
        let matchingTemplate = handler =>
          handler && handler.uriTemplate == newHandler.uriTemplate;
        if (!handlers.some(matchingTemplate)) {
          handlers.push(newHandler);
        }
      }
    }
  },

  /**
   * Execute any migrations. Migrations are defined here for any changes or removals for
   * existing handlers. Additions are still handled via the localized prefs infrastructure.
   *
   * This depends on the browser.handlers.migrations pref being set by migrateUI in
   * nsBrowserGlue (for Fx Desktop) or similar mechanisms for other products.
   * This is a comma-separated list of identifiers of migrations that need running.
   * This avoids both re-running older migrations and keeping an additional
   * pref around permanently.
   */
  _migrateProtocolHandlersIfNeeded() {
    const kMigrations = {
      "30boxes": () => {
        const k30BoxesRegex =
          /^https?:\/\/(?:www\.)?30boxes.com\/external\/widget/i;
        let webcalHandler =
          lazy.externalProtocolService.getProtocolHandlerInfo("webcal");
        if (this.exists(webcalHandler)) {
          this.fillHandlerInfo(webcalHandler, "");
          let shouldStore = false;
          // First remove 30boxes from possible handlers.
          let handlers = webcalHandler.possibleApplicationHandlers;
          for (let i = handlers.length - 1; i >= 0; i--) {
            let app = handlers.queryElementAt(i, Ci.nsIHandlerApp);
            if (
              app instanceof Ci.nsIWebHandlerApp &&
              k30BoxesRegex.test(app.uriTemplate)
            ) {
              shouldStore = true;
              handlers.removeElementAt(i);
            }
          }
          // Then remove as a preferred handler.
          if (webcalHandler.preferredApplicationHandler) {
            let app = webcalHandler.preferredApplicationHandler;
            if (
              app instanceof Ci.nsIWebHandlerApp &&
              k30BoxesRegex.test(app.uriTemplate)
            ) {
              webcalHandler.preferredApplicationHandler = null;
              shouldStore = true;
            }
          }
          // Then store, if we changed anything.
          if (shouldStore) {
            this.store(webcalHandler);
          }
        }
      },
      // See https://bugzilla.mozilla.org/show_bug.cgi?id=1526890 for context.
      "secure-mail": () => {
        const kSubstitutions = new Map([
          [
            "http://compose.mail.yahoo.co.jp/ym/Compose?To=%s",
            "https://mail.yahoo.co.jp/compose/?To=%s",
          ],
          [
            "http://www.inbox.lv/rfc2368/?value=%s",
            "https://mail.inbox.lv/compose?to=%s",
          ],
          [
            "http://poczta.interia.pl/mh/?mailto=%s",
            "https://poczta.interia.pl/mh/?mailto=%s",
          ],
          [
            "http://win.mail.ru/cgi-bin/sentmsg?mailto=%s",
            "https://e.mail.ru/cgi-bin/sentmsg?mailto=%s",
          ],
        ]);

        function maybeReplaceURL(app) {
          if (app instanceof Ci.nsIWebHandlerApp) {
            let { uriTemplate } = app;
            let sub = kSubstitutions.get(uriTemplate);
            if (sub) {
              app.uriTemplate = sub;
              return true;
            }
          }
          return false;
        }
        let mailHandler =
          lazy.externalProtocolService.getProtocolHandlerInfo("mailto");
        if (this.exists(mailHandler)) {
          this.fillHandlerInfo(mailHandler, "");
          let handlers = mailHandler.possibleApplicationHandlers;
          let shouldStore = false;
          for (let i = handlers.length - 1; i >= 0; i--) {
            let app = handlers.queryElementAt(i, Ci.nsIHandlerApp);
            // Note: will evaluate the RHS because it's a binary rather than
            // logical or.
            shouldStore |= maybeReplaceURL(app);
          }
          // Then check the preferred handler.
          if (mailHandler.preferredApplicationHandler) {
            shouldStore |= maybeReplaceURL(
              mailHandler.preferredApplicationHandler
            );
          }
          // Then store, if we changed anything. Note that store() handles
          // duplicates, so we don't have to.
          if (shouldStore) {
            this.store(mailHandler);
          }
        }
      },
    };
    let migrationsToRun = Services.prefs.getCharPref(
      "browser.handlers.migrations",
      ""
    );
    migrationsToRun = migrationsToRun ? migrationsToRun.split(",") : [];
    for (let migration of migrationsToRun) {
      migration.trim();
      try {
        kMigrations[migration]();
      } catch (ex) {
        console.error(ex);
      }
    }

    if (migrationsToRun.length) {
      Services.prefs.clearUserPref("browser.handlers.migrations");
    }
  },

  _onDBChange() {
    return (async () => {
      if (this.__store) {
        await this.__store.finalize();
      }
      this.__store = null;
      this.__storeInitialized = false;
    })().catch(console.error);
  },

  // nsIObserver
  observe(subject, topic) {
    if (topic != "handlersvc-json-replace") {
      return;
    }
    let promise = this._onDBChange();
    promise.then(() => {
      Services.obs.notifyObservers(null, "handlersvc-json-replace-complete");
    });
  },

  // nsIHandlerService
  asyncInit() {
    if (!this.__store) {
      this.__store = new lazy.JSONFile({
        path: PathUtils.join(
          Services.dirsvc.get("ProfD", Ci.nsIFile).path,
          "handlers.json"
        ),
        dataPostProcessor: this._dataPostProcessor.bind(this),
      });
      this.__store
        .load()
        .then(() => {
          // __store can be null if we called _onDBChange in the mean time.
          if (this.__store) {
            this._ensureStoreInitialized();
          }
        })
        .catch(console.error);
    }
  },

  // nsIHandlerService
  enumerate() {
    let handlers = Cc["@mozilla.org/array;1"].createInstance(
      Ci.nsIMutableArray
    );
    for (let [type, typeInfo] of Object.entries(this._store.data.mimeTypes)) {
      let primaryExtension = typeInfo.extensions?.[0] ?? null;
      let handler = lazy.MIMEService.getFromTypeAndExtension(
        type,
        primaryExtension
      );
      handlers.appendElement(handler);
    }
    for (let type of Object.keys(this._store.data.schemes)) {
      // nsIExternalProtocolService.getProtocolHandlerInfo can be expensive
      // on Windows, so we return a proxy to delay retrieving the nsIHandlerInfo
      // until one of its properties is accessed.
      //
      // Note: our caller still needs to yield periodically when iterating
      // the enumerator and accessing handler properties to avoid monopolizing
      // the main thread.
      //
      let handler = new Proxy(
        {
          QueryInterface: ChromeUtils.generateQI(["nsIHandlerInfo"]),
          type,
          get _handlerInfo() {
            delete this._handlerInfo;
            return (this._handlerInfo =
              lazy.externalProtocolService.getProtocolHandlerInfo(type));
          },
        },
        {
          get(target, name) {
            return target[name] || target._handlerInfo[name];
          },
          set(target, name, value) {
            target._handlerInfo[name] = value;
          },
        }
      );
      handlers.appendElement(handler);
    }
    return handlers.enumerate(Ci.nsIHandlerInfo);
  },

  // nsIHandlerService
  store(handlerInfo) {
    let handlerList = this._getHandlerListByHandlerInfoType(handlerInfo);

    // Retrieve an existing entry if present, instead of creating a new one, so
    // that we preserve unknown properties for forward compatibility.
    let storedHandlerInfo = handlerList[handlerInfo.type];
    if (!storedHandlerInfo) {
      storedHandlerInfo = {};
      handlerList[handlerInfo.type] = storedHandlerInfo;
    }

    // Only a limited number of preferredAction values is allowed.
    if (
      handlerInfo.preferredAction == saveToDisk ||
      handlerInfo.preferredAction == useSystemDefault ||
      handlerInfo.preferredAction == handleInternally ||
      // For files (ie mimetype rather than protocol handling info), ensure
      // we can store the "always ask" state, too:
      (handlerInfo.preferredAction == alwaysAsk &&
        this._isMIMEInfo(handlerInfo))
    ) {
      storedHandlerInfo.action = handlerInfo.preferredAction;
    } else {
      storedHandlerInfo.action = useHelperApp;
    }

    if (handlerInfo.alwaysAskBeforeHandling) {
      storedHandlerInfo.ask = true;
    } else {
      delete storedHandlerInfo.ask;
    }

    // Build a list of unique nsIHandlerInfo instances to process later.
    let handlers = [];
    if (handlerInfo.preferredApplicationHandler) {
      handlers.push(handlerInfo.preferredApplicationHandler);
    }
    for (let handler of handlerInfo.possibleApplicationHandlers.enumerate(
      Ci.nsIHandlerApp
    )) {
      // If the caller stored duplicate handlers, we save them only once.
      if (!handlers.some(h => h.equals(handler))) {
        handlers.push(handler);
      }
    }

    // If any of the nsIHandlerInfo instances cannot be serialized, it is not
    // included in the final list. The first element is always the preferred
    // handler, or null if there is none.
    let serializableHandlers = handlers
      .map(h => this.handlerAppToSerializable(h))
      .filter(h => h);
    if (serializableHandlers.length) {
      if (!handlerInfo.preferredApplicationHandler) {
        serializableHandlers.unshift(null);
      }
      storedHandlerInfo.handlers = serializableHandlers;
    } else {
      delete storedHandlerInfo.handlers;
    }

    if (this._isMIMEInfo(handlerInfo)) {
      let extensions = storedHandlerInfo.extensions || [];
      for (let extension of handlerInfo.getFileExtensions()) {
        extension = extension.toLowerCase();
        // If the caller stored duplicate extensions, we save them only once.
        if (!extensions.includes(extension)) {
          extensions.push(extension);
        }
      }
      if (extensions.length) {
        storedHandlerInfo.extensions = extensions;
      } else {
        delete storedHandlerInfo.extensions;
      }
    }

    // If we're saving *anything*, it stops being a stub:
    delete storedHandlerInfo.stubEntry;

    this._store.saveSoon();

    // Now notify PDF.js. This is hacky, but a lot better than expecting all
    // the consumers to do it...
    if (handlerInfo.type == "application/pdf") {
      Services.obs.notifyObservers(null, TOPIC_PDFJS_HANDLER_CHANGED);
    }

    if (handlerInfo.type == "mailto") {
      Services.obs.notifyObservers(null, "mailto::onClearCache");
    }
  },

  // nsIHandlerService
  fillHandlerInfo(handlerInfo, overrideType) {
    let type = overrideType || handlerInfo.type;
    let storedHandlerInfo =
      this._getHandlerListByHandlerInfoType(handlerInfo)[type];
    if (!storedHandlerInfo) {
      throw new Components.Exception(
        "handlerSvc fillHandlerInfo: don't know this type",
        Cr.NS_ERROR_NOT_AVAILABLE
      );
    }

    let isStub = !!storedHandlerInfo.stubEntry;
    // In the normal case, this is not a stub, so we can just read stored info
    // and write to the handlerInfo object we were passed.
    if (!isStub) {
      handlerInfo.preferredAction = storedHandlerInfo.action;
      handlerInfo.alwaysAskBeforeHandling = !!storedHandlerInfo.ask;
    } else {
      // If we've got a stub, ensure the defaults are still set:
      lazy.externalProtocolService.setProtocolHandlerDefaults(
        handlerInfo,
        handlerInfo.hasDefaultHandler
      );
      if (
        handlerInfo.preferredAction == alwaysAsk &&
        handlerInfo.alwaysAskBeforeHandling
      ) {
        // `store` will default to `useHelperApp` because `alwaysAsk` is
        // not one of the 3 recognized options; for compatibility, do
        // the same here.
        handlerInfo.preferredAction = useHelperApp;
      }
    }
    // If it *is* a stub, don't override alwaysAskBeforeHandling or the
    // preferred actions. Instead, just append the stored handlers, without
    // overriding the preferred app, and then schedule a task to store proper
    // info for this handler.
    this._appendStoredHandlers(handlerInfo, storedHandlerInfo.handlers, isStub);

    if (this._isMIMEInfo(handlerInfo) && storedHandlerInfo.extensions) {
      for (let extension of storedHandlerInfo.extensions) {
        handlerInfo.appendExtension(extension);
      }
    } else if (this._mockedHandler) {
      this._insertMockedHandler(handlerInfo);
    }
  },

  /**
   * Private method to inject stored handler information into an nsIHandlerInfo
   * instance.
   * @param handlerInfo           the nsIHandlerInfo instance to write to
   * @param storedHandlers        the stored handlers
   * @param keepPreferredApp      whether to keep the handlerInfo's
   *                              preferredApplicationHandler or override it
   *                              (default: false, ie override it)
   */
  _appendStoredHandlers(handlerInfo, storedHandlers, keepPreferredApp) {
    // If the first item is not null, it is also the preferred handler. Since
    // we cannot modify the stored array, use a boolean to keep track of this.
    let isFirstItem = true;
    for (let handler of storedHandlers || [null]) {
      let handlerApp = this.handlerAppFromSerializable(handler || {});
      if (isFirstItem) {
        isFirstItem = false;
        // Do not overwrite the preferred app unless that's allowed
        if (!keepPreferredApp) {
          handlerInfo.preferredApplicationHandler = handlerApp;
        }
      }
      if (handlerApp) {
        handlerInfo.possibleApplicationHandlers.appendElement(handlerApp);
      }
    }
  },

  /**
   * @param handler
   *        A nsIHandlerApp handler app
   * @returns  Serializable representation of a handler app object.
   */
  handlerAppToSerializable(handler) {
    if (handler instanceof Ci.nsILocalHandlerApp) {
      return {
        name: handler.name,
        path: handler.executable.path,
      };
    } else if (handler instanceof Ci.nsIWebHandlerApp) {
      return {
        name: handler.name,
        uriTemplate: handler.uriTemplate,
      };
    } else if (handler instanceof Ci.nsIDBusHandlerApp) {
      return {
        name: handler.name,
        service: handler.service,
        method: handler.method,
        objectPath: handler.objectPath,
        dBusInterface: handler.dBusInterface,
      };
    } else if (handler instanceof Ci.nsIGIOMimeApp) {
      return {
        name: handler.name,
        command: handler.command,
      };
    }
    // If the handler is an unknown handler type, return null.
    // Android default application handler is the case.
    return null;
  },

  /**
   * @param handlerObj
   *        Serializable representation of a handler object.
   * @returns  {nsIHandlerApp}  the handler app, if any; otherwise null
   */
  handlerAppFromSerializable(handlerObj) {
    let handlerApp;
    if ("path" in handlerObj) {
      try {
        let file = new lazy.FileUtils.File(handlerObj.path);
        if (!file.exists()) {
          return null;
        }
        handlerApp = Cc[
          "@mozilla.org/uriloader/local-handler-app;1"
        ].createInstance(Ci.nsILocalHandlerApp);
        handlerApp.executable = file;
      } catch (ex) {
        return null;
      }
    } else if ("uriTemplate" in handlerObj) {
      handlerApp = Cc[
        "@mozilla.org/uriloader/web-handler-app;1"
      ].createInstance(Ci.nsIWebHandlerApp);
      handlerApp.uriTemplate = handlerObj.uriTemplate;
    } else if ("service" in handlerObj) {
      handlerApp = Cc[
        "@mozilla.org/uriloader/dbus-handler-app;1"
      ].createInstance(Ci.nsIDBusHandlerApp);
      handlerApp.service = handlerObj.service;
      handlerApp.method = handlerObj.method;
      handlerApp.objectPath = handlerObj.objectPath;
      handlerApp.dBusInterface = handlerObj.dBusInterface;
    } else if ("command" in handlerObj && "@mozilla.org/gio-service;1" in Cc) {
      try {
        handlerApp = Cc["@mozilla.org/gio-service;1"]
          .getService(Ci.nsIGIOService)
          .createAppFromCommand(handlerObj.command, handlerObj.name);
      } catch (ex) {
        return null;
      }
    } else {
      return null;
    }

    handlerApp.name = handlerObj.name;
    return handlerApp;
  },

  /**
   * The function returns a reference to the "mimeTypes" or "schemes" object
   * based on which type of handlerInfo is provided.
   */
  _getHandlerListByHandlerInfoType(handlerInfo) {
    return this._isMIMEInfo(handlerInfo)
      ? this._store.data.mimeTypes
      : this._store.data.schemes;
  },

  /**
   * Determines whether an nsIHandlerInfo instance represents a MIME type.
   */
  _isMIMEInfo(handlerInfo) {
    // We cannot rely only on the instanceof check because on Android both MIME
    // types and protocols are instances of nsIMIMEInfo. We still do the check
    // so that properties of nsIMIMEInfo become available to the callers.
    return (
      handlerInfo instanceof Ci.nsIMIMEInfo && handlerInfo.type.includes("/")
    );
  },

  // nsIHandlerService
  exists(handlerInfo) {
    return (
      handlerInfo.type in this._getHandlerListByHandlerInfoType(handlerInfo)
    );
  },

  // nsIHandlerService
  remove(handlerInfo) {
    delete this._getHandlerListByHandlerInfoType(handlerInfo)[handlerInfo.type];
    this._store.saveSoon();
  },

  // nsIHandlerService
  getTypeFromExtension(fileExtension) {
    let extension = fileExtension.toLowerCase();
    let mimeTypes = this._store.data.mimeTypes;
    for (let type of Object.keys(mimeTypes)) {
      if (
        mimeTypes[type].extensions &&
        mimeTypes[type].extensions.includes(extension)
      ) {
        return type;
      }
    }
    return "";
  },

  _mockedHandler: null,
  _mockedProtocol: null,

  _insertMockedHandler(handlerInfo) {
    if (handlerInfo.type == this._mockedProtocol) {
      handlerInfo.preferredApplicationHandler = this._mockedHandler;
      handlerInfo.possibleApplicationHandlers.insertElementAt(
        this._mockedHandler,
        0
      );
    }
  },

  // test-only: mock the handler instance for a particular protocol/scheme
  mockProtocolHandler(protocol) {
    if (!protocol) {
      this._mockedProtocol = null;
      this._mockedHandler = null;
      return;
    }
    this._mockedProtocol = protocol;
    this._mockedHandler = {
      QueryInterface: ChromeUtils.generateQI([Ci.nsILocalHandlerApp]),
      launchWithURI(uri) {
        Services.obs.notifyObservers(uri, "mocked-protocol-handler");
      },
      name: "Mocked handler",
      detailedDescription: "Mocked handler for tests",
      equals(x) {
        return x == this;
      },
      get executable() {
        if (AppConstants.platform == "macosx") {
          // We need an app path that isn't us, nor in our app bundle, and
          // Apple no longer allows us to read the default-shipped apps
          // in /Applications/ - except for Safari, it would appear!
          let f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
          f.initWithPath("/Applications/Safari.app");
          return f;
        }
        return Services.dirsvc.get("XCurProcD", Ci.nsIFile);
      },
      parameterCount: 0,
      clearParameters() {},
      appendParameter() {},
      getParameter() {},
      parameterExists() {
        return false;
      },
    };
  },
};
PK
!<[/��modules/Extension.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This file is the main entry point for extensions. When an extension
 * loads, its bootstrap.js file creates a Extension instance
 * and calls .startup() on it. It calls .shutdown() when the extension
 * unloads. Extension manages any extension-specific state in
 * the chrome process.
 *
 * TODO(rpl): we are current restricting the extensions to a single process
 * (set as the current default value of the "dom.ipc.processCount.extension"
 * preference), if we switch to use more than one extension process, we have to
 * be sure that all the browser's frameLoader are associated to the same process,
 * e.g. by enabling the `maychangeremoteness` attribute, and/or setting
 * `initialBrowsingContextGroupId` attribute to the correct value.
 *
 * At that point we are going to keep track of the existing browsers associated to
 * a webextension to ensure that they are all running in the same process (and we
 * are also going to do the same with the browser element provided to the
 * addon debugging Remote Debugging actor, e.g. because the addon has been
 * reloaded by the user, we have to  ensure that the new extension pages are going
 * to run in the same process of the existing addon debugging browser element).
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { ExtensionCommon } from "resource://gre/modules/ExtensionCommon.sys.mjs";
import { ExtensionParent } from "resource://gre/modules/ExtensionParent.sys.mjs";
import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";
import { Log } from "resource://gre/modules/Log.sys.mjs";

/** @type {Lazy} */
const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs",
  AddonSettings: "resource://gre/modules/addons/AddonSettings.sys.mjs",
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
  ExtensionDNR: "resource://gre/modules/ExtensionDNR.sys.mjs",
  ExtensionDNRStore: "resource://gre/modules/ExtensionDNRStore.sys.mjs",
  ExtensionMenus: "resource://gre/modules/ExtensionMenus.sys.mjs",
  ExtensionPermissions: "resource://gre/modules/ExtensionPermissions.sys.mjs",
  ExtensionPreferencesManager:
    "resource://gre/modules/ExtensionPreferencesManager.sys.mjs",
  ExtensionProcessScript:
    "resource://gre/modules/ExtensionProcessScript.sys.mjs",
  ExtensionScriptingStore:
    "resource://gre/modules/ExtensionScriptingStore.sys.mjs",
  ExtensionStorage: "resource://gre/modules/ExtensionStorage.sys.mjs",
  ExtensionStorageIDB: "resource://gre/modules/ExtensionStorageIDB.sys.mjs",
  ExtensionTelemetry: "resource://gre/modules/ExtensionTelemetry.sys.mjs",
  LightweightThemeManager:
    "resource://gre/modules/LightweightThemeManager.sys.mjs",
  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
  SITEPERMS_ADDON_TYPE:
    "resource://gre/modules/addons/siteperms-addon-utils.sys.mjs",
  Schemas: "resource://gre/modules/Schemas.sys.mjs",
  ServiceWorkerCleanUp: "resource://gre/modules/ServiceWorkerCleanUp.sys.mjs",
  extensionStorageSync: "resource://gre/modules/ExtensionStorageSync.sys.mjs",
  PERMISSION_L10N: "resource://gre/modules/ExtensionPermissionMessages.sys.mjs",
  permissionToL10nId:
    "resource://gre/modules/ExtensionPermissionMessages.sys.mjs",
  QuarantinedDomains: "resource://gre/modules/ExtensionPermissions.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "resourceProtocol", () =>
  Services.io
    .getProtocolHandler("resource")
    .QueryInterface(Ci.nsIResProtocolHandler)
);

XPCOMUtils.defineLazyServiceGetters(lazy, {
  aomStartup: [
    "@mozilla.org/addons/addon-manager-startup;1",
    "amIAddonManagerStartup",
  ],
  spellCheck: ["@mozilla.org/spellchecker/engine;1", "mozISpellCheckingEngine"],
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "processCount",
  "dom.ipc.processCount.extension"
);

// Temporary pref to be turned on when ready.
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "userContextIsolation",
  "extensions.userContextIsolation.enabled",
  false
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "userContextIsolationDefaultRestricted",
  "extensions.userContextIsolation.defaults.restricted",
  "[]"
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "dnrEnabled",
  "extensions.dnr.enabled",
  true
);

// This pref modifies behavior for MV2.  MV3 is enabled regardless.
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "eventPagesEnabled",
  "extensions.eventPages.enabled"
);

// This pref is used to check if storage.sync is still the Kinto-based backend
// (GeckoView should be the only one still using it).
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "storageSyncOldKintoBackend",
  "webextensions.storage.sync.kinto",
  false
);

// Deprecation of browser_style, through .supported & .same_as_mv2 prefs:
// - true true  = warn only: deprecation message only (no behavioral changes).
// - true false = deprecate: default to false, even if default was true in MV2.
// - false      = remove: always use false, even when true is specified.
//                (if .same_as_mv2 is set, also warn if the default changed)
// Deprecation plan: https://bugzilla.mozilla.org/show_bug.cgi?id=1827910#c1
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "browserStyleMV3supported",
  "extensions.browser_style_mv3.supported",
  false
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "browserStyleMV3sameAsMV2",
  "extensions.browser_style_mv3.same_as_mv2",
  false
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "processCrashThreshold",
  "extensions.webextensions.crash.threshold",
  // The default number of times an extension process is allowed to crash
  // within a timeframe.
  5
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "processCrashTimeframe",
  "extensions.webextensions.crash.timeframe",
  // The default timeframe used to count crashes, in milliseconds.
  30 * 1000
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "installIncludesOrigins",
  "extensions.originControls.grantByDefault",
  false
);

var {
  GlobalManager,
  IconDetails,
  ParentAPIManager,
  StartupCache,
  apiManager: Management,
} = ExtensionParent;

export { Management };

const { getUniqueId, promiseTimeout } = ExtensionUtils;

const { EventEmitter, redefineGetter, updateAllowedOrigins } = ExtensionCommon;

ChromeUtils.defineLazyGetter(
  lazy,
  "LocaleData",
  () => ExtensionCommon.LocaleData
);

ChromeUtils.defineLazyGetter(lazy, "NO_PROMPT_PERMISSIONS", async () => {
  // Wait until all extension API schemas have been loaded and parsed.
  await Management.lazyInit();
  return new Set(
    lazy.Schemas.getPermissionNames([
      "PermissionNoPrompt",
      "OptionalPermissionNoPrompt",
      "PermissionPrivileged",
    ])
  );
});

const { sharedData } = Services.ppmm;

const PRIVATE_ALLOWED_PERMISSION = "internal:privateBrowsingAllowed";
const SVG_CONTEXT_PROPERTIES_PERMISSION =
  "internal:svgContextPropertiesAllowed";

// The userContextID reserved for the extension storage (its purpose is ensuring that the IndexedDB
// storage used by the browser.storage.local API is not directly accessible from the extension code,
// it is defined and reserved as "userContextIdInternal.webextStorageLocal" in ContextualIdentityService.sys.mjs).
const WEBEXT_STORAGE_USER_CONTEXT_ID = -1 >>> 0;

// The maximum time to wait for extension child shutdown blockers to complete.
const CHILD_SHUTDOWN_TIMEOUT_MS = 8000;

// Permissions that are only available to privileged extensions.
const PRIVILEGED_PERMS = new Set([
  "activityLog",
  "mozillaAddons",
  "networkStatus",
  "normandyAddonStudy",
  "telemetry",
]);

const PRIVILEGED_PERMS_ANDROID_ONLY = new Set([
  "geckoViewAddons",
  "nativeMessagingFromContent",
  "nativeMessaging",
]);

const PRIVILEGED_PERMS_DESKTOP_ONLY = new Set(["normandyAddonStudy"]);

if (AppConstants.platform == "android") {
  for (const perm of PRIVILEGED_PERMS_ANDROID_ONLY) {
    PRIVILEGED_PERMS.add(perm);
  }
}

if (
  AppConstants.MOZ_APP_NAME != "firefox" ||
  AppConstants.platform == "android"
) {
  for (const perm of PRIVILEGED_PERMS_DESKTOP_ONLY) {
    PRIVILEGED_PERMS.delete(perm);
  }
}

// Message included in warnings and errors related to privileged permissions and
// privileged manifest properties. Provides a link to the firefox-source-docs.mozilla.org
// section related to developing and sign Privileged Add-ons.
const PRIVILEGED_ADDONS_DEVDOCS_MESSAGE =
  "See https://mzl.la/3NS9KJd for more details about how to develop a privileged add-on.";

const INSTALL_AND_UPDATE_STARTUP_REASONS = new Set([
  "ADDON_INSTALL",
  "ADDON_UPGRADE",
  "ADDON_DOWNGRADE",
]);

const PROTOCOL_HANDLER_OPEN_PERM_KEY = "open-protocol-handler";
const PERMISSION_KEY_DELIMITER = "^";

// These are used for manipulating jar entry paths, which always use Unix
// separators (originally copied from `ospath_unix.jsm` as part of the "OS.Path
// to PathUtils" migration).

/**
 * Return the final part of the path.
 * The final part of the path is everything after the last "/".
 */
function basename(path) {
  return path.slice(path.lastIndexOf("/") + 1);
}

/**
 * Return the directory part of the path.
 * The directory part of the path is everything before the last
 * "/". If the last few characters of this part are also "/",
 * they are ignored.
 *
 * If the path contains no directory, return ".".
 */
function dirname(path) {
  let index = path.lastIndexOf("/");
  if (index == -1) {
    return ".";
  }
  while (index >= 0 && path[index] == "/") {
    --index;
  }
  return path.slice(0, index + 1);
}

// Returns true if the extension is owned by Mozilla (is either privileged,
// using one of the @mozilla.com/@mozilla.org protected addon id suffixes).
//
// This method throws if the extension's startupReason is not one of the
// expected ones (either ADDON_INSTALL, ADDON_UPGRADE or ADDON_DOWNGRADE).
//
// TODO(Bug 1835787): Consider to remove the restriction based on the
// startupReason now that the recommendationState property is always
// included in the addonData with any of the startupReason.
function isMozillaExtension(extension) {
  const { addonData, id, isPrivileged, startupReason } = extension;

  if (!INSTALL_AND_UPDATE_STARTUP_REASONS.has(startupReason)) {
    throw new Error(
      `isMozillaExtension called with unexpected startupReason: ${startupReason}`
    );
  }

  if (isPrivileged) {
    return true;
  }

  if (id.endsWith("@mozilla.com") || id.endsWith("@mozilla.org")) {
    return true;
  }

  // This check is a subset of what is being checked in AddonWrapper's
  // recommendationStates (states expire dates for line extensions are
  // not considered important in determining that the extension is
  // provided by mozilla, and so they are omitted here on purpose).
  const isMozillaLineExtension =
    addonData.recommendationState?.states?.includes("line");
  const isSigned =
    addonData.signedState > lazy.AddonManager.SIGNEDSTATE_MISSING;

  return isSigned && isMozillaLineExtension;
}

/**
 * Classify an individual permission from a webextension manifest
 * as a host/origin permission, an api permission, or a regular permission.
 *
 * @param {string} perm  The permission string to classify
 * @param {boolean} restrictSchemes
 * @param {boolean} isPrivileged whether or not the webextension is privileged
 *
 * @returns {object}
 *          An object with exactly one of the following properties:
 *          "origin" to indicate this is a host/origin permission.
 *          "api" to indicate this is an api permission
 *                (as used for webextensions experiments).
 *          "permission" to indicate this is a regular permission.
 *          "invalid" to indicate that the given permission cannot be used.
 */
function classifyPermission(perm, restrictSchemes, isPrivileged) {
  let match = /^(\w+)(?:\.(\w+)(?:\.\w+)*)?$/.exec(perm);
  if (!match) {
    try {
      let { pattern } = new MatchPattern(perm, {
        restrictSchemes,
        ignorePath: true,
      });
      return { origin: pattern };
    } catch (e) {
      return { invalid: perm };
    }
  } else if (match[1] == "experiments" && match[2]) {
    return { api: match[2] };
  } else if (!isPrivileged && PRIVILEGED_PERMS.has(match[1])) {
    return { invalid: perm, privileged: true };
  } else if (perm.startsWith("declarativeNetRequest") && !lazy.dnrEnabled) {
    return { invalid: perm };
  }
  return { permission: perm };
}

const LOGGER_ID_BASE = "addons.webextension.";
const UUID_MAP_PREF = "extensions.webextensions.uuids";
const LEAVE_STORAGE_PREF = "extensions.webextensions.keepStorageOnUninstall";
const LEAVE_UUID_PREF = "extensions.webextensions.keepUuidOnUninstall";

const COMMENT_REGEXP = new RegExp(
  String.raw`
    ^
    (
      (?:
        [^"\n] |
        " (?:[^"\\\n] | \\.)* "
      )*?
    )

    //.*
  `.replace(/\s+/g, ""),
  "gm"
);

// All moz-extension URIs use a machine-specific UUID rather than the
// extension's own ID in the host component. This makes it more
// difficult for web pages to detect whether a user has a given add-on
// installed (by trying to load a moz-extension URI referring to a
// web_accessible_resource from the extension). UUIDMap.get()
// returns the UUID for a given add-on ID.
var UUIDMap = {
  _read() {
    let pref = Services.prefs.getStringPref(UUID_MAP_PREF, "{}");
    try {
      return JSON.parse(pref);
    } catch (e) {
      Cu.reportError(`Error parsing ${UUID_MAP_PREF}.`);
      return {};
    }
  },

  _write(map) {
    Services.prefs.setStringPref(UUID_MAP_PREF, JSON.stringify(map));
  },

  get(id, create = true) {
    let map = this._read();

    if (id in map) {
      return map[id];
    }

    let uuid = null;
    if (create) {
      uuid = Services.uuid.generateUUID().number;
      uuid = uuid.slice(1, -1); // Strip { and } off the UUID.

      map[id] = uuid;
      this._write(map);
    }
    return uuid;
  },

  remove(id) {
    let map = this._read();
    delete map[id];
    this._write(map);
  },
};

function clearCacheForExtensionPrincipal(principal, clearAll = false) {
  if (!principal.schemeIs("moz-extension")) {
    return Promise.reject(new Error("Unexpected non extension principal"));
  }

  // TODO(Bug 1750053): replace the two specific flags with a "clear all caches one"
  // (along with covering the other kind of cached data with tests).
  const clearDataFlags = clearAll
    ? Ci.nsIClearDataService.CLEAR_ALL_CACHES
    : Ci.nsIClearDataService.CLEAR_IMAGE_CACHE |
      Ci.nsIClearDataService.CLEAR_CSS_CACHE |
      Ci.nsIClearDataService.CLEAR_JS_CACHE;

  return new Promise(resolve =>
    Services.clearData.deleteDataFromPrincipal(
      principal,
      false,
      clearDataFlags,
      () => resolve()
    )
  );
}

/**
 * Observer AddonManager events and translate them into extension events,
 * as well as handle any last cleanup after uninstalling an extension.
 */
var ExtensionAddonObserver = {
  initialized: false,

  init() {
    if (!this.initialized) {
      lazy.AddonManager.addAddonListener(this);
      this.initialized = true;
    }
  },

  // AddonTestUtils will call this as necessary.
  uninit() {
    if (this.initialized) {
      lazy.AddonManager.removeAddonListener(this);
      this.initialized = false;
    }
  },

  onEnabling(addon) {
    if (addon.type !== "extension") {
      return;
    }
    Management._callHandlers([addon.id], "enabling", "onEnabling");
  },

  onDisabled(addon) {
    if (addon.type !== "extension") {
      return;
    }
    if (Services.appinfo.inSafeMode) {
      // Ensure ExtensionPreferencesManager updates its data and
      // modules can run any disable logic they need to.  We only
      // handle safeMode here because there is a bunch of additional
      // logic that happens in Extension.shutdown when running in
      // normal mode.
      Management._callHandlers([addon.id], "disable", "onDisable");
    }
  },

  onUninstalling(addon) {
    let extension = GlobalManager.extensionMap.get(addon.id);
    if (extension) {
      // Let any other interested listeners respond
      // (e.g., display the uninstall URL)
      Management.emit("uninstalling", extension);
    }
  },

  onUninstalled(addon) {
    this.clearOnUninstall(addon.id);
  },

  /**
   * Clears persistent state from the add-on post install.
   *
   * @param {string} addonId The ID of the addon that has been uninstalled.
   */
  clearOnUninstall(addonId) {
    const tasks = [];
    function addShutdownBlocker(name, promise) {
      lazy.AsyncShutdown.profileChangeTeardown.addBlocker(name, promise);
      tasks.push({ name, promise });
    }
    function notifyUninstallTaskObservers() {
      Management.emit("cleanupAfterUninstall", addonId, tasks);
    }

    // Cleanup anything that is used by non-extension addon types
    // since only extensions have uuid's.
    addShutdownBlocker(
      `Clear ExtensionPermissions for ${addonId}`,
      lazy.ExtensionPermissions.removeAll(addonId)
    );

    lazy.QuarantinedDomains.clearUserPref(addonId);

    let uuid = UUIDMap.get(addonId, false);
    if (!uuid) {
      notifyUninstallTaskObservers();
      return;
    }

    let baseURI = Services.io.newURI(`moz-extension://${uuid}/`);
    let principal = Services.scriptSecurityManager.createContentPrincipal(
      baseURI,
      {}
    );

    // Clear all cached resources (e.g. CSS and images);
    addShutdownBlocker(
      `Clear cache for ${addonId}`,
      clearCacheForExtensionPrincipal(principal, /* clearAll */ true)
    );

    // Clear all the registered service workers for the extension
    // principal (the one that may have been registered through the
    // manifest.json file and the ones that may have been registered
    // from an extension page through the service worker API).
    //
    // Any stored data would be cleared below (if the pref
    // "extensions.webextensions.keepStorageOnUninstall has not been
    // explicitly set to true, which is usually only done in
    // tests and by some extensions developers for testing purpose).
    //
    // TODO: ServiceWorkerCleanUp may go away once Bug 1183245
    // is fixed, and so this may actually go away, replaced by
    // marking the registration as disabled or to be removed on
    // shutdown (where we do know if the extension is shutting
    // down because is being uninstalled) and then cleared from
    // the persisted serviceworker registration on the next
    // startup.
    addShutdownBlocker(
      `Clear ServiceWorkers for ${addonId}`,
      lazy.ServiceWorkerCleanUp.removeFromPrincipal(principal)
    );

    // Clear the persisted menus created with the menus/contextMenus API (if any).
    addShutdownBlocker(
      `Clear menus store for ${addonId}`,
      lazy.ExtensionMenus.clearPersistedMenusOnUninstall(addonId)
    );

    // Clear the persisted dynamic content scripts created with the scripting
    // API (if any).
    addShutdownBlocker(
      `Clear scripting store for ${addonId}`,
      lazy.ExtensionScriptingStore.clearOnUninstall(addonId)
    );

    // Clear the DNR API's rules data persisted on disk (if any).
    addShutdownBlocker(
      `Clear declarativeNetRequest store for ${addonId}`,
      lazy.ExtensionDNRStore.clearOnUninstall(uuid)
    );

    if (!Services.prefs.getBoolPref(LEAVE_STORAGE_PREF, false)) {
      // Clear browser.storage.local backends.
      addShutdownBlocker(
        `Clear Extension Storage ${addonId} (File Backend)`,
        lazy.ExtensionStorage.clear(addonId, { shouldNotifyListeners: false })
      );

      // Clear browser.storage.sync rust-based backend.
      // (storage.sync clearOnUninstall will resolve and log an error on the
      // browser console in case of unexpected failures).
      if (!lazy.storageSyncOldKintoBackend) {
        addShutdownBlocker(
          `Clear Extension StorageSync ${addonId}`,
          lazy.extensionStorageSync.clearOnUninstall(addonId)
        );
      }

      // Clear any IndexedDB and Cache API storage created by the extension.
      // If LSNG is enabled, this also clears localStorage.
      Services.qms.clearStoragesForPrincipal(principal);

      // Clear any storage.local data stored in the IDBBackend.
      let storagePrincipal =
        Services.scriptSecurityManager.createContentPrincipal(baseURI, {
          userContextId: WEBEXT_STORAGE_USER_CONTEXT_ID,
        });
      Services.qms.clearStoragesForPrincipal(storagePrincipal);

      lazy.ExtensionStorageIDB.clearMigratedExtensionPref(addonId);

      // If LSNG is not enabled, we need to clear localStorage explicitly using
      // the old API.
      if (!Services.domStorageManager.nextGenLocalStorageEnabled) {
        // Clear localStorage created by the extension
        let storage = Services.domStorageManager.getStorage(
          null,
          principal,
          principal
        );
        if (storage) {
          storage.clear();
        }
      }

      // Remove any permissions related to the unlimitedStorage permission
      // if we are also removing all the data stored by the extension.
      Services.perms.removeFromPrincipal(
        principal,
        "WebExtensions-unlimitedStorage"
      );
      Services.perms.removeFromPrincipal(principal, "persistent-storage");
    }

    // Clear any protocol handler permissions granted to this add-on.
    let permissions = Services.perms.getAllWithTypePrefix(
      PROTOCOL_HANDLER_OPEN_PERM_KEY + PERMISSION_KEY_DELIMITER
    );
    for (let perm of permissions) {
      if (perm.principal.equalsURI(baseURI)) {
        Services.perms.removePermission(perm);
      }
    }

    if (!Services.prefs.getBoolPref(LEAVE_UUID_PREF, false)) {
      // Clear the entry in the UUID map
      UUIDMap.remove(addonId);
    }

    notifyUninstallTaskObservers();
  },

  onPropertyChanged(addon, properties) {
    let extension = GlobalManager.extensionMap.get(addon.id);
    if (extension && properties.includes("quarantineIgnoredByUser")) {
      extension.ignoreQuarantine = addon.quarantineIgnoredByUser;
      extension.policy.ignoreQuarantine = addon.quarantineIgnoredByUser;

      extension.setSharedData("", extension.serialize());
      Services.ppmm.sharedData.flush();

      extension.emit("update-ignore-quarantine");
      extension.broadcast("Extension:UpdateIgnoreQuarantine", {
        id: extension.id,
        ignoreQuarantine: addon.quarantineIgnoredByUser,
      });
    }
  },
};

ExtensionAddonObserver.init();

/**
 * Observer ExtensionProcess crashes and notify all the extensions
 * using a Management event named "extension-process-crash".
 */
export var ExtensionProcessCrashObserver = {
  initialized: false,

  // For Android apps we initially consider the app as always starting
  // in the background, then we expect to be setting it to foreground
  // when GeckoView LifecycleListener onResume method is called on the
  // Android app first startup. After the application has got on the
  // foreground for the first time then onPause/onResumed LifecycleListener
  // are called, the application-foreground/-background topics will be
  // notified to Gecko and this flag will be updated accordingly.
  _appInForeground: AppConstants.platform !== "android",
  _isAndroid: AppConstants.platform === "android",
  _processSpawningDisabled: false,

  // Technically there is at most one child extension process,
  // but we may need to adjust this assumption to account for more
  // than one if that ever changes in the future.
  currentProcessChildID: undefined,
  lastCrashedProcessChildID: undefined,
  QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),

  // Collect the timestamps of the crashes happened over the last
  // `processCrashTimeframe` milliseconds.
  lastCrashTimestamps: [],

  logger: Log.repository.getLogger("addons.process-crash-observer"),

  init() {
    if (!this.initialized) {
      Services.obs.addObserver(this, "ipc:content-created");
      Services.obs.addObserver(this, "process-type-set");
      Services.obs.addObserver(this, "ipc:content-shutdown");
      if (this._isAndroid) {
        Services.obs.addObserver(this, "geckoview-initial-foreground");
        Services.obs.addObserver(this, "application-foreground");
        Services.obs.addObserver(this, "application-background");
      }
      this.initialized = true;
    }
  },

  uninit() {
    if (this.initialized) {
      try {
        Services.obs.removeObserver(this, "ipc:content-created");
        Services.obs.removeObserver(this, "process-type-set");
        Services.obs.removeObserver(this, "ipc:content-shutdown");
        if (this._isAndroid) {
          Services.obs.removeObserver(this, "geckoview-initial-foreground");
          Services.obs.removeObserver(this, "application-foreground");
          Services.obs.removeObserver(this, "application-background");
        }
      } catch (err) {
        // Removing the observer may fail if they are not registered anymore,
        // this shouldn't happen in practice, but let's still log the error
        // in case it does.
        Cu.reportError(err);
      }
      this.initialized = false;
    }
  },

  observe(subject, topic, data) {
    let childID = data;
    switch (topic) {
      case "geckoview-initial-foreground":
        this._appInForeground = true;
        this.logger.debug(
          `Detected Android application moved in the foreground (geckoview-initial-foreground)`
        );
        break;
      case "application-foreground":
      // Intentional fall-through
      case "application-background":
        this._appInForeground = topic === "application-foreground";
        this.logger.debug(
          `Detected Android application moved in the ${
            this._appInForeground ? "foreground" : "background"
          }`
        );
        if (this._appInForeground) {
          Management.emit("application-foreground", {
            appInForeground: this._appInForeground,
            childID: this.currentProcessChildID,
            processSpawningDisabled: this.processSpawningDisabled,
          });
        }
        break;
      case "process-type-set":
      // Intentional fall-through
      case "ipc:content-created": {
        let pp = subject.QueryInterface(Ci.nsIDOMProcessParent);
        if (pp.remoteType === "extension") {
          this.currentProcessChildID = childID;
          Glean.extensions.processEvent[
            this.appInForeground ? "created_fg" : "created_bg"
          ].add(1);
        }
        break;
      }
      case "ipc:content-shutdown": {
        if (Services.startup.shuttingDown) {
          // The application is shutting down, don't bother
          // signaling process crashes anymore.
          return;
        }
        if (this.currentProcessChildID !== childID) {
          // Ignore non-extension child process shutdowns.
          return;
        }

        // At this point we are sure that the current extension
        // process is gone, and so even if the process did shutdown
        // cleanly instead of crashing, we can clear the property
        // that keeps track of the current extension process childID.
        this.currentProcessChildID = undefined;

        subject.QueryInterface(Ci.nsIPropertyBag2);
        if (!subject.get("abnormal")) {
          // Ignore non-abnormal child process shutdowns.
          return;
        }

        this.lastCrashedProcessChildID = childID;

        const now = Cu.now();
        // Filter crash timestamps older than processCrashTimeframe.
        this.lastCrashTimestamps = this.lastCrashTimestamps.filter(
          timestamp => now - timestamp < lazy.processCrashTimeframe
        );
        // Push the new timeframe.
        this.lastCrashTimestamps.push(now);
        // Set the flag that disable process spawning when we exceed the
        // `processCrashThreshold`.
        this._processSpawningDisabled =
          this.lastCrashTimestamps.length > lazy.processCrashThreshold;

        this.logger.debug(
          `Extension process crashed ${this.lastCrashTimestamps.length} times over the last ${lazy.processCrashTimeframe}ms`
        );

        const { appInForeground } = this;

        if (this.processSpawningDisabled) {
          if (appInForeground) {
            Glean.extensions.processEvent.crashed_over_threshold_fg.add(1);
          } else {
            Glean.extensions.processEvent.crashed_over_threshold_bg.add(1);
          }
          this.logger.warn(
            `Extension process respawning disabled because it crashed too often in the last ${lazy.processCrashTimeframe}ms (${this.lastCrashTimestamps.length} > ${lazy.processCrashThreshold}).`
          );
        }

        Glean.extensions.processEvent[
          appInForeground ? "crashed_fg" : "crashed_bg"
        ].add(1);
        Management.emit("extension-process-crash", {
          childID,
          processSpawningDisabled: this.processSpawningDisabled,
          appInForeground,
        });
        break;
      }
    }
  },

  enableProcessSpawning() {
    const crashCounter = this.lastCrashTimestamps.length;
    this.lastCrashTimestamps = [];
    this.logger.debug(`reset crash counter (was ${crashCounter})`);
    this._processSpawningDisabled = false;
    Management.emit("extension-enable-process-spawning");
  },

  get appInForeground() {
    // Only account for application in the background for
    // android builds.
    return this._isAndroid ? this._appInForeground : true;
  },

  get processSpawningDisabled() {
    return this._processSpawningDisabled;
  },
};

ExtensionProcessCrashObserver.init();

const manifestTypes = new Map([
  ["theme", "manifest.ThemeManifest"],
  ["locale", "manifest.WebExtensionLangpackManifest"],
  ["dictionary", "manifest.WebExtensionDictionaryManifest"],
  ["extension", "manifest.WebExtensionManifest"],
]);

/**
 * Represents the data contained in an extension, contained either
 * in a directory or a zip file, which may or may not be installed.
 * This class implements the functionality of the Extension class,
 * primarily related to manifest parsing and localization, which is
 * useful prior to extension installation or initialization.
 *
 * No functionality of this class is guaranteed to work before
 * `loadManifest` has been called, and completed.
 */
export class ExtensionData {
  /**
   * Note: These fields are only available and meant to be used on Extension
   * instances, declared here because methods from this class reference them.
   */
  /** @type {object} TODO: move to the Extension class, bug 1871094. */
  addonData;
  /** @type {nsIURI} */
  baseURI;
  /** @type {nsIPrincipal} */
  principal;
  /** @type {boolean} */
  temporarilyInstalled;

  constructor(rootURI, isPrivileged = false) {
    this.rootURI = rootURI;
    this.resourceURL = rootURI.spec;
    this.isPrivileged = isPrivileged;

    this.manifest = null;
    this.type = null;
    this.id = null;
    this.uuid = null;
    this.localeData = null;
    this.fluentL10n = null;
    this._promiseLocales = null;

    this.apiNames = new Set();
    this.dependencies = new Set();
    this.permissions = new Set();

    this.startupData = null;

    this.errors = [];
    this.warnings = [];
    this.eventPagesEnabled = lazy.eventPagesEnabled;
  }

  /**
   * A factory function that allows the construction of ExtensionData, with
   * the isPrivileged flag computed asynchronously.
   *
   * @param {object} options
   * @param {nsIURI} options.rootURI
   *  The URI pointing to the extension root.
   * @param {function(type, id): boolean} options.checkPrivileged
   *  An (async) function that takes the addon type and addon ID and returns
   *  whether the given add-on is privileged.
   * @param {boolean} options.temporarilyInstalled
   *  whether the given add-on is installed as temporary.
   * @returns {Promise<ExtensionData>}
   */
  static async constructAsync({
    rootURI,
    checkPrivileged,
    temporarilyInstalled,
  }) {
    let extension = new ExtensionData(rootURI);
    // checkPrivileged depends on the extension type and id.
    await extension.initializeAddonTypeAndID();
    let { type, id } = extension;
    extension.isPrivileged = await checkPrivileged(type, id);
    extension.temporarilyInstalled = temporarilyInstalled;
    return extension;
  }

  static getIsPrivileged({ signedState, builtIn, temporarilyInstalled }) {
    return (
      signedState === lazy.AddonManager.SIGNEDSTATE_PRIVILEGED ||
      signedState === lazy.AddonManager.SIGNEDSTATE_SYSTEM ||
      builtIn ||
      (lazy.AddonSettings.EXPERIMENTS_ENABLED && temporarilyInstalled)
    );
  }

  get builtinMessages() {
    return null;
  }

  get logger() {
    let id = this.id || "<unknown>";
    return Log.repository.getLogger(LOGGER_ID_BASE + id);
  }

  /**
   * Report an error about the extension's manifest file.
   *
   * @param {string} message The error message
   */
  manifestError(message) {
    this.packagingError(`Reading manifest: ${message}`);
  }

  /**
   * Report a warning about the extension's manifest file.
   *
   * @param {string} message The warning message
   */
  manifestWarning(message) {
    this.packagingWarning(`Reading manifest: ${message}`);
  }

  // Report an error about the extension's general packaging.
  packagingError(message) {
    this.errors.push(message);
    this.logError(message);
  }

  packagingWarning(message) {
    this.warnings.push(message);
    this.logWarning(message);
  }

  logWarning(message) {
    this._logMessage(message, "warn");
  }

  logError(message) {
    this._logMessage(message, "error");
  }

  _logMessage(message, severity) {
    this.logger[severity](`Loading extension '${this.id}': ${message}`);
  }

  ensureNoErrors() {
    if (this.errors.length) {
      // startup() repeatedly checks whether there are errors after parsing the
      // extension/manifest before proceeding with starting up.
      throw new Error(this.errors.join("\n"));
    }
  }

  /**
   * Returns the moz-extension: URL for the given path within this
   * extension.
   *
   * Must not be called unless either the `id` or `uuid` property has
   * already been set.
   *
   * @param {string} path The path portion of the URL.
   * @returns {string}
   */
  getURL(path = "") {
    if (!(this.id || this.uuid)) {
      throw new Error(
        "getURL may not be called before an `id` or `uuid` has been set"
      );
    }
    if (!this.uuid) {
      this.uuid = UUIDMap.get(this.id);
    }
    return `moz-extension://${this.uuid}/${path}`;
  }

  /**
   * Discovers the file names within a directory or JAR file.
   *
   * @param {string} path
   *   The path to the directory or jar file to look at.
   * @param {boolean} [directoriesOnly]
   *   If true, this will return only the directories present within the directory.
   * @returns {Promise<string[]>}
   *   An array of names of files/directories (only the name, not the path).
   */
  async _readDirectory(path, directoriesOnly = false) {
    if (this.rootURI instanceof Ci.nsIFileURL) {
      let uri = Services.io.newURI("./" + path, null, this.rootURI);
      let fullPath = uri.QueryInterface(Ci.nsIFileURL).file.path;

      let results = [];
      try {
        let children = await IOUtils.getChildren(fullPath);
        for (let child of children) {
          if (
            !directoriesOnly ||
            (await IOUtils.stat(child)).type == "directory"
          ) {
            results.push(PathUtils.filename(child));
          }
        }
      } catch (ex) {
        // Fall-through, return what we have.
      }
      return results;
    }

    let uri = this.rootURI.QueryInterface(Ci.nsIJARURI);

    // Append the sub-directory path to the base JAR URI and normalize the
    // result.
    let entry = `${uri.JAREntry}/${path}/`
      .replace(/\/\/+/g, "/")
      .replace(/^\//, "");
    uri = Services.io.newURI(`jar:${uri.JARFile.spec}!/${entry}`);

    let results = [];
    for (let name of lazy.aomStartup.enumerateJARSubtree(uri)) {
      if (!name.startsWith(entry)) {
        throw new Error("Unexpected ZipReader entry");
      }

      // The enumerator returns the full path of all entries.
      // Trim off the leading path, and filter out entries from
      // subdirectories.
      name = name.slice(entry.length);
      if (
        name &&
        !/\/./.test(name) &&
        (!directoriesOnly || name.endsWith("/"))
      ) {
        results.push(name.replace("/", ""));
      }
    }

    return results;
  }

  readJSON(path) {
    return new Promise((resolve, reject) => {
      let uri = this.rootURI.resolve(`./${path}`);

      lazy.NetUtil.asyncFetch(
        { uri, loadUsingSystemPrincipal: true },
        (inputStream, status) => {
          if (!Components.isSuccessCode(status)) {
            // Convert status code to a string
            let e = Components.Exception("", status);
            reject(new Error(`Error while loading '${uri}' (${e.name})`));
            return;
          }
          try {
            let text = lazy.NetUtil.readInputStreamToString(
              inputStream,
              inputStream.available(),
              { charset: "utf-8" }
            );

            text = text.replace(COMMENT_REGEXP, "$1");

            resolve(JSON.parse(text));
          } catch (e) {
            reject(e);
          }
        }
      );
    });
  }

  get restrictSchemes() {
    return !(this.isPrivileged && this.hasPermission("mozillaAddons"));
  }

  get optionsPageProperties() {
    let page = this.manifest.options_ui?.page ?? this.manifest.options_page;
    if (!page) {
      return null;
    }
    return {
      page,
      open_in_tab: this.manifest.options_ui
        ? this.manifest.options_ui.open_in_tab ?? false
        : true,
      // `options_ui.browser_style` is assigned the proper default value
      // (true for MV2 and false for MV3 when not explicitly set),
      // in `#parseBrowserStyleInManifest` (called when we are loading
      // and parse manifest data from the `parseManifest` method).
      browser_style: this.manifest.options_ui?.browser_style ?? false,
    };
  }

  /**
   * Given an array of host and permissions, generate a structured permissions object
   * that contains seperate host origins and permissions arrays.
   *
   * @param {Array} permissionsArray
   * @param {Array} [hostPermissions]
   * @returns {object} permissions object
   */
  permissionsObject(permissionsArray = [], hostPermissions = []) {
    let permissions = new Set();
    let origins = new Set();
    let { restrictSchemes, isPrivileged } = this;

    for (let perm of permissionsArray.concat(hostPermissions)) {
      let type = classifyPermission(perm, restrictSchemes, isPrivileged);
      if (type.origin) {
        origins.add(perm);
      } else if (type.permission) {
        permissions.add(perm);
      }
    }

    return {
      permissions,
      origins,
    };
  }

  /**
   * Returns an object representing any capabilities that the extension
   * has access to based on fixed properties in the manifest.  The result
   * includes the contents of the "permissions" property as well as other
   * capabilities that are derived from manifest fields that users should
   * be informed of (e.g., origins where content scripts are injected).
   *
   * For MV3 extensions with origin controls, this does not include origins.
   */
  getRequiredPermissions() {
    if (this.type !== "extension") {
      return null;
    }

    let { permissions } = this.permissionsObject(this.manifest.permissions);

    if (
      this.manifest.devtools_page &&
      !this.manifest.optional_permissions.includes("devtools")
    ) {
      permissions.add("devtools");
    }

    return {
      permissions: Array.from(permissions),
      origins: this.originControls ? [] : this.getManifestOrigins(),
    };
  }

  /**
   * @returns {string[]} all origins that are referenced in manifest via
   * permissions, host_permissions, or content_scripts keys.
   */
  getManifestOrigins() {
    if (this.type !== "extension") {
      return null;
    }

    let { origins } = this.permissionsObject(
      this.manifest.permissions,
      this.manifest.host_permissions
    );

    for (let entry of this.manifest.content_scripts || []) {
      for (let origin of entry.matches) {
        origins.add(origin);
      }
    }

    return Array.from(origins);
  }

  /**
   * @returns {MatchPatternSet} MatchPatternSet for only the origins that are
   * referenced in manifest via permissions, host_permissions, or content_scripts keys.
   */
  getManifestOriginsMatchPatternSet() {
    if (this.type !== "extension") {
      return null;
    }
    if (this._manifestOriginsMatchPatternSet) {
      return this._manifestOriginsMatchPatternSet;
    }
    this._manifestOriginsMatchPatternSet = new MatchPatternSet(
      this.getManifestOrigins(),
      {
        restrictSchemes: this.restrictSchemes,
        ignorePath: true,
      }
    );
    return this._manifestOriginsMatchPatternSet;
  }

  /**
   * Returns additional permissions that extensions is requesting based on its
   * manifest. For now, this is host_permissions (and content scripts) in mv3.
   */
  getRequestedPermissions() {
    if (this.type !== "extension") {
      return null;
    }
    if (this.originControls && lazy.installIncludesOrigins) {
      return { permissions: [], origins: this.getManifestOrigins() };
    }
    return { permissions: [], origins: [] };
  }

  /**
   * Returns optional permissions from the manifest, including host permissions
   * if originControls is true.
   */
  get manifestOptionalPermissions() {
    if (this.type !== "extension") {
      return null;
    }

    let { permissions, origins } = this.permissionsObject(
      this.manifest.optional_permissions,
      this.manifest.optional_host_permissions
    );
    if (this.originControls) {
      for (let origin of this.getManifestOrigins()) {
        origins.add(origin);
      }
    }

    return {
      permissions: Array.from(permissions),
      origins: Array.from(origins),
    };
  }

  /**
   * Returns an object representing all capabilities this extension has
   * access to, including fixed ones from the manifest as well as dynamically
   * granted permissions.
   */
  get activePermissions() {
    if (this.type !== "extension") {
      return null;
    }

    let result = {
      origins: this.allowedOrigins.patterns
        .map(matcher => matcher.pattern)
        // moz-extension://id/* is always added to allowedOrigins, but it
        // is not a valid host permission in the API. So, remove it.
        .filter(pattern => !pattern.startsWith("moz-extension:")),
      apis: [...this.apiNames],
    };

    const EXP_PATTERN = /^experiments\.\w+/;
    result.permissions = [...this.permissions].filter(
      p => !result.origins.includes(p) && !EXP_PATTERN.test(p)
    );
    return result;
  }

  // Returns whether the front end should prompt for this permission
  static async shouldPromptFor(permission) {
    return !(await lazy.NO_PROMPT_PERMISSIONS).has(permission);
  }

  // Compute the difference between two sets of permissions, suitable
  // for presenting to the user.
  static comparePermissions(oldPermissions, newPermissions) {
    let oldMatcher = new MatchPatternSet(oldPermissions.origins, {
      restrictSchemes: false,
    });
    return {
      // formatPermissionStrings ignores any scheme, so only look at the domain.
      origins: newPermissions.origins.filter(
        perm =>
          !oldMatcher.subsumesDomain(
            new MatchPattern(perm, { restrictSchemes: false })
          )
      ),
      permissions: newPermissions.permissions.filter(
        perm => !oldPermissions.permissions.includes(perm)
      ),
    };
  }

  // Return those permissions in oldPermissions that also exist in newPermissions.
  static intersectPermissions(oldPermissions, newPermissions) {
    let matcher = new MatchPatternSet(newPermissions.origins, {
      restrictSchemes: false,
    });

    return {
      origins: oldPermissions.origins.filter(perm =>
        matcher.subsumesDomain(
          new MatchPattern(perm, { restrictSchemes: false })
        )
      ),
      permissions: oldPermissions.permissions.filter(perm =>
        newPermissions.permissions.includes(perm)
      ),
    };
  }

  /**
   * When updating the addon, find and migrate permissions that have moved from required
   * to optional.  This also handles any updates required for permission removal.
   *
   * @param {string} id The id of the addon being updated
   * @param {object} oldPermissions
   * @param {object} oldOptionalPermissions
   * @param {object} newPermissions
   * @param {object} newOptionalPermissions
   */
  static async migratePermissions(
    id,
    oldPermissions,
    oldOptionalPermissions,
    newPermissions,
    newOptionalPermissions
  ) {
    let migrated = ExtensionData.intersectPermissions(
      oldPermissions,
      newOptionalPermissions
    );
    // If a permission is optional in this version and was mandatory in the previous
    // version, it was already accepted by the user at install time so add it to the
    // list of granted optional permissions now.
    await lazy.ExtensionPermissions.add(id, migrated);

    // Now we need to update ExtensionPreferencesManager, removing any settings
    // for old permissions that no longer exist.
    let permSet = new Set(
      newPermissions.permissions.concat(newOptionalPermissions.permissions)
    );
    let oldPerms = oldPermissions.permissions.concat(
      oldOptionalPermissions.permissions
    );

    let removed = oldPerms.filter(x => !permSet.has(x));
    // Force the removal here to ensure the settings are removed prior
    // to startup.  This will remove both required or optional permissions,
    // whereas the call from within ExtensionPermissions would only result
    // in a removal for optional permissions that were removed.
    await lazy.ExtensionPreferencesManager.removeSettingsForPermissions(
      id,
      removed
    );

    // Remove any optional permissions that have been removed from the manifest.
    await lazy.ExtensionPermissions.remove(id, {
      permissions: removed,
      origins: [],
    });
  }

  canUseAPIExperiment() {
    return (
      this.type == "extension" &&
      (this.isPrivileged ||
        // TODO(Bug 1771341): Allowing the "experiment_apis" property when only
        // AddonSettings.EXPERIMENTS_ENABLED is true is currently needed to allow,
        // while running under automation, the test harness extensions (like mochikit
        // and specialpowers) to use that privileged manifest property.
        lazy.AddonSettings.EXPERIMENTS_ENABLED)
    );
  }

  canUseThemeExperiment() {
    return (
      ["extension", "theme"].includes(this.type) &&
      (this.isPrivileged ||
        // "theme_experiment" MDN docs are currently explicitly mentioning this is expected
        // to be allowed also for non-signed extensions installed non-temporarily on builds
        // where the signature checks can be disabled).
        //
        // NOTE: be careful to don't regress "theme_experiment" (see Bug 1773076) while changing
        // AddonSettings.EXPERIMENTS_ENABLED (e.g. as part of fixing Bug 1771341).
        lazy.AddonSettings.EXPERIMENTS_ENABLED)
    );
  }

  get manifestVersion() {
    return this.manifest.manifest_version;
  }

  get persistentBackground() {
    let { manifest } = this;
    if (
      !manifest.background ||
      (manifest.background.service_worker &&
        WebExtensionPolicy.backgroundServiceWorkerEnabled) ||
      this.manifestVersion > 2
    ) {
      return false;
    }
    // V2 addons can only use event pages if the pref is also flipped and
    // persistent is explicilty set to false.
    return !this.eventPagesEnabled || manifest.background.persistent;
  }

  /**
   * backgroundState can be starting, running, suspending or stopped.
   * It is undefined if the extension has no background page.
   * See ext-backgroundPage.js for more details.
   *
   * @param {string} state starting, running, suspending or stopped
   */
  set backgroundState(state) {
    this._backgroundState = state;
  }

  get backgroundState() {
    return this._backgroundState;
  }

  async getExtensionVersionWithoutValidation() {
    return (await this.readJSON("manifest.json")).version;
  }

  /**
   * Load a locale and return a localized manifest.  The extension must
   * be initialized, and manifest parsed prior to calling.
   *
   * @param {string} locale to load, if necessary.
   * @returns {Promise<object>} normalized manifest.
   */
  async getLocalizedManifest(locale) {
    if (!this.type || !this.localeData) {
      throw new Error("The extension has not been initialized.");
    }
    // Upon update or reinstall, the Extension.manifest may be read from
    // StartupCache.manifest, however rawManifest is *not*.  We need the
    // raw manifest in order to get a localized manifest.
    if (!this.rawManifest) {
      this.rawManifest = await this.readJSON("manifest.json");
    }

    if (!this.localeData.has(locale)) {
      // Locales are not avialable until some additional
      // initialization is done.  We could just call initAllLocales,
      // but that is heavy handed, especially when we likely only
      // need one out of 20.
      let locales = await this.promiseLocales();
      if (locales.get(locale)) {
        await this.initLocale(locale);
      }
      if (!this.localeData.has(locale)) {
        throw new Error(`The extension does not contain the locale ${locale}`);
      }
    }
    let normalized = await this._getNormalizedManifest(locale);
    if (normalized.error) {
      throw new Error(normalized.error);
    }
    return normalized.value;
  }

  async _getNormalizedManifest(locale) {
    let manifestType = manifestTypes.get(this.type);

    let context = {
      url: this.baseURI && this.baseURI.spec,
      principal: this.principal,
      logError: error => {
        this.manifestWarning(error);
      },
      preprocessors: {},
      manifestVersion: this.manifestVersion,
      // We introduced this context param in Bug 1831417.
      ignoreUnrecognizedProperties: false,
    };

    if (this.fluentL10n || this.localeData) {
      context.preprocessors.localize = value => this.localize(value, locale);
    }

    return lazy.Schemas.normalize(this.rawManifest, manifestType, context);
  }

  #parseBrowserStyleInManifest(manifest, manifestKey, defaultValueInMV2) {
    const obj = manifest[manifestKey];
    if (!obj) {
      return;
    }
    const browserStyleIsVoid = obj.browser_style == null;
    obj.browser_style ??= defaultValueInMV2;
    if (this.manifestVersion < 3 || !obj.browser_style) {
      // MV2 (true or false), or MV3 (false set explicitly or default false).
      // No changes in observed behavior, return now to avoid logspam.
      return;
    }
    // Now there are two cases (MV3 only):
    // - browser_style was not specified, but defaults to true.
    // - browser_style was set to true by the extension.
    //
    // These will eventually be deprecated. For the deprecation plan, see
    // https://bugzilla.mozilla.org/show_bug.cgi?id=1827910#c1
    let warning;
    if (!lazy.browserStyleMV3supported) {
      obj.browser_style = false;
      if (browserStyleIsVoid && !lazy.browserStyleMV3sameAsMV2) {
        // defaultValueInMV2 is true, but there was no intent to use these
        // defaults. Don't warn.
        return;
      }
      warning = `"browser_style:true" is no longer supported in Manifest Version 3.`;
    } else {
      warning = `"browser_style:true" has been deprecated in Manifest Version 3 and will be unsupported in the near future.`;
    }
    if (browserStyleIsVoid) {
      warning += ` While "${manifestKey}.browser_style" was not explicitly specified in manifest.json, its default value was true.`;
      if (!lazy.browserStyleMV3sameAsMV2) {
        obj.browser_style = false;
        warning += ` The default value of "${manifestKey}.browser_style" has changed from true to false in Manifest Version 3.`;
      } else {
        warning += ` Its default will change to false in Manifest Version 3 starting from Firefox 115.`;
      }
    }

    this.manifestWarning(
      `Warning processing ${manifestKey}.browser_style: ${warning}`
    );
  }

  async initializeAddonTypeAndID() {
    if (this.type) {
      // Already initialized.
      return;
    }
    this.rawManifest = await this.readJSON("manifest.json");
    let manifest = this.rawManifest;

    if (manifest.theme) {
      this.type = "theme";
    } else if (manifest.langpack_id) {
      this.type = "locale";
    } else if (manifest.dictionaries) {
      this.type = "dictionary";
    } else {
      this.type = "extension";
    }

    if (!this.id) {
      let bss =
        manifest.browser_specific_settings?.gecko ||
        manifest.applications?.gecko;
      let id = bss?.id;
      // This is a basic type check.
      // When parseManifest is called, the ID is validated more thoroughly
      // because the id is defined to be an ExtensionID type in
      // toolkit/components/extensions/schemas/manifest.json
      if (typeof id == "string") {
        this.id = id;
      }
    }
  }

  // eslint-disable-next-line complexity
  async parseManifest() {
    await Promise.all([this.initializeAddonTypeAndID(), Management.lazyInit()]);

    let manifest = this.rawManifest;
    this.manifest = manifest;

    if (manifest.default_locale) {
      await this.initLocale();
    }

    if (manifest.l10n_resources) {
      if (this.isPrivileged) {
        // TODO (Bug 1733466): For historical reasons fluent isn't being used to
        // localize manifest properties read from the add-on manager (e.g., author,
        // homepage, etc.), the changes introduced by Bug 1734987 does now ensure
        // that isPrivileged will be set while parsing the manifest and so this
        // can be now supported but requires some additional changes, being tracked
        // by Bug 1733466.
        if (this.constructor != ExtensionData) {
          this.fluentL10n = new Localization(manifest.l10n_resources, true);
        }
      } else if (this.temporarilyInstalled) {
        this.manifestError(
          `Using 'l10n_resources' requires a privileged add-on. ` +
            PRIVILEGED_ADDONS_DEVDOCS_MESSAGE
        );
      } else {
        // Warn but don't make this fatal.
        this.manifestWarning(
          "Ignoring l10n_resources in unprivileged extension"
        );
      }
    }

    let normalized = await this._getNormalizedManifest();
    if (normalized.error) {
      this.manifestError(normalized.error);
      return null;
    }

    manifest = normalized.value;

    // `browser_specific_settings` is the recommended key to use in the
    // manifest, and the only possible choice in MV3+. For MV2 extensions, we
    // still allow `applications`, though. Because `applications` used to be
    // the only key in the distant past, most internal code is written using
    // applications. That's why we end up re-assigning `browser_specific_settings`
    // to `applications` below.
    //
    // Also, when a MV3+ extension specifies `applications`, the key isn't
    // recognized and therefore filtered out from the normalized manifest as
    // part of the JSONSchema normalization.
    if (manifest.browser_specific_settings?.gecko) {
      if (manifest.applications) {
        this.manifestWarning(
          `"applications" property ignored and overridden by "browser_specific_settings"`
        );
      }
      manifest.applications = manifest.browser_specific_settings;
    }

    // On Android, override the browser specific settings with those found in
    // `bss.gecko_android`, if any.
    //
    // It is also worth noting that the `gecko_android` key in `applications`
    // is marked as "unsupported" in the JSON schema.
    if (
      AppConstants.platform == "android" &&
      manifest.browser_specific_settings?.gecko_android
    ) {
      const { strict_min_version, strict_max_version } =
        manifest.browser_specific_settings.gecko_android;

      // When the manifest doesn't define `browser_specific_settings.gecko`, it
      // is still possible to reach this block but `manifest.applications`
      // won't be defined yet.
      if (!manifest?.applications) {
        manifest.applications = {
          // All properties should be optional in `gecko` so we omit them here.
          gecko: {},
        };
      }

      if (strict_min_version?.length) {
        manifest.applications.gecko.strict_min_version = strict_min_version;
      }

      if (strict_max_version?.length) {
        manifest.applications.gecko.strict_max_version = strict_max_version;
      }
    }

    if (
      this.manifestVersion < 3 &&
      manifest.background &&
      !this.eventPagesEnabled &&
      !manifest.background.persistent
    ) {
      this.logWarning("Event pages are not currently supported.");
    }

    if (
      this.isPrivileged &&
      manifest.hidden &&
      (manifest.action || manifest.browser_action || manifest.page_action)
    ) {
      this.manifestError(
        "Cannot use browser and/or page actions in hidden add-ons"
      );
    }

    // manifest.options_page opens the extension page in a new tab
    // and so we will not need to special handling browser_style.
    if (manifest.options_ui) {
      if (manifest.options_ui.open_in_tab) {
        // browser_style:true has no effect when open_in_tab is true.
        manifest.options_ui.browser_style = false;
      } else {
        this.#parseBrowserStyleInManifest(manifest, "options_ui", true);
      }
    }
    if (this.manifestVersion < 3) {
      this.#parseBrowserStyleInManifest(manifest, "browser_action", false);
    } else {
      this.#parseBrowserStyleInManifest(manifest, "action", false);
    }
    this.#parseBrowserStyleInManifest(manifest, "page_action", false);
    if (AppConstants.MOZ_BUILD_APP === "browser") {
      this.#parseBrowserStyleInManifest(manifest, "sidebar_action", true);
    }

    let apiNames = new Set();
    let dependencies = new Set();
    let originPermissions = new Set();
    let permissions = new Set();
    let webAccessibleResources = [];

    let schemaPromises = new Map();

    // Note: this.id and this.type were computed in initializeAddonTypeAndID.
    // The format of `this.id` was confirmed to be a valid extensionID by the
    // Schema validation as part of the _getNormalizedManifest() call.
    let result = {
      apiNames,
      dependencies,
      id: this.id,
      manifest,
      modules: null,
      // Whether to treat all origin permissions (including content scripts)
      // from the manifestas as optional, and enable users to control them.
      originControls: this.manifestVersion >= 3,
      originPermissions,
      permissions,
      schemaURLs: null,
      type: this.type,
      webAccessibleResources,
    };

    if (this.type === "extension") {
      let { isPrivileged } = this;
      let restrictSchemes = !(
        isPrivileged && manifest.permissions.includes("mozillaAddons")
      );

      // Privileged and temporary extensions still get OriginControls, but
      // can have host permissions automatically granted during install.
      // For all other cases, ensure granted_host_permissions is false.
      if (!isPrivileged && !this.temporarilyInstalled) {
        manifest.granted_host_permissions = false;
      }

      let host_permissions = manifest.host_permissions ?? [];

      for (let perm of manifest.permissions.concat(host_permissions)) {
        if (perm === "geckoProfiler" && !isPrivileged) {
          const acceptedExtensions = Services.prefs.getStringPref(
            "extensions.geckoProfiler.acceptedExtensionIds",
            ""
          );
          if (!acceptedExtensions.split(",").includes(this.id)) {
            this.manifestError(
              "Only specific extensions are allowed to access the geckoProfiler."
            );
            continue;
          }
        }

        let type = classifyPermission(perm, restrictSchemes, isPrivileged);
        if (type.origin) {
          perm = type.origin;
          if (!result.originControls) {
            originPermissions.add(perm);
          }
        } else if (type.api) {
          apiNames.add(type.api);
        } else if (type.invalid) {
          // If EXPERIMENTS_ENABLED is not enabled prevent the install
          // to ensure developer awareness.
          if (this.temporarilyInstalled && type.privileged) {
            this.manifestError(
              `Using the privileged permission '${perm}' requires a privileged add-on. ` +
                PRIVILEGED_ADDONS_DEVDOCS_MESSAGE
            );
            continue;
          }
          this.manifestWarning(`Invalid extension permission: ${perm}`);
          continue;
        }

        // Unfortunately, we treat <all_urls> as an API permission as well.
        if (!type.origin || (perm === "<all_urls>" && !result.originControls)) {
          permissions.add(perm);
        }
      }

      if (this.id) {
        // An extension always gets permission to its own url.
        let matcher = new MatchPattern(this.getURL(), { ignorePath: true });
        originPermissions.add(matcher.pattern);

        // Apply optional permissions
        let perms = await lazy.ExtensionPermissions.get(this.id);
        for (let perm of perms.permissions) {
          permissions.add(perm);
        }
        for (let origin of perms.origins) {
          originPermissions.add(origin);
        }
      }

      for (let api of apiNames) {
        dependencies.add(`${api}@experiments.addons.mozilla.org`);
      }

      let moduleData = data => ({
        url: this.rootURI.resolve(data.script),
        events: data.events,
        paths: data.paths,
        scopes: data.scopes,
      });

      let computeModuleInit = (scope, modules) => {
        let manager = new ExtensionCommon.SchemaAPIManager(scope);
        return manager.initModuleJSON([modules]);
      };

      result.contentScripts = [];
      for (let options of manifest.content_scripts || []) {
        let { match_about_blank, match_origin_as_fallback } = options;
        if (match_origin_as_fallback !== null) {
          // match_about_blank is ignored when match_origin_as_fallback is set.
          // When match_about_blank=true and match_origin_as_fallback=false,
          // then match_about_blank should be treated as false.
          match_about_blank = false;
        }
        result.contentScripts.push({
          allFrames: options.all_frames,
          matchAboutBlank: match_about_blank,
          matchOriginAsFallback: match_origin_as_fallback,
          frameID: options.frame_id,
          runAt: options.run_at,
          world: options.world,

          matches: options.matches,
          excludeMatches: options.exclude_matches || [],
          includeGlobs: options.include_globs,
          excludeGlobs: options.exclude_globs,

          jsPaths: options.js || [],
          cssPaths: options.css || [],
        });
      }

      if (manifest.experiment_apis) {
        if (this.canUseAPIExperiment()) {
          let parentModules = {};
          let childModules = {};

          for (let [name, data] of Object.entries(manifest.experiment_apis)) {
            let schema = this.getURL(data.schema);

            if (!schemaPromises.has(schema)) {
              schemaPromises.set(
                schema,
                this.readJSON(data.schema).then(json =>
                  lazy.Schemas.processSchema(json)
                )
              );
            }

            if (data.parent) {
              parentModules[name] = moduleData(data.parent);
            }

            if (data.child) {
              childModules[name] = moduleData(data.child);
            }
          }

          result.modules = {
            child: computeModuleInit("addon_child", childModules),
            parent: computeModuleInit("addon_parent", parentModules),
          };
        } else if (this.temporarilyInstalled) {
          // Hard error for un-privileged temporary installs using experimental apis.
          this.manifestError(
            `Using 'experiment_apis' requires a privileged add-on. ` +
              PRIVILEGED_ADDONS_DEVDOCS_MESSAGE
          );
        } else {
          this.manifestWarning(
            `Using experimental APIs requires a privileged add-on.`
          );
        }
      }

      // Normalize all patterns to contain a single leading /
      if (manifest.web_accessible_resources) {
        // Normalize into V3 objects
        let wac =
          this.manifestVersion >= 3
            ? manifest.web_accessible_resources
            : [{ resources: manifest.web_accessible_resources }];
        webAccessibleResources.push(
          ...wac.map(obj => {
            obj.resources = obj.resources.map(path =>
              path.replace(/^\/*/, "/")
            );
            return obj;
          })
        );
      }
    } else if (this.type == "locale") {
      // Langpack startup is performance critical, so we want to compute as much
      // as possible here to make startup not trigger async DB reads.
      // We'll store the four items below in the startupData.

      // 1. Compute the chrome resources to be registered for this langpack.
      const platform = AppConstants.platform;
      const chromeEntries = [];
      for (const [language, entry] of Object.entries(manifest.languages)) {
        for (const [alias, path] of Object.entries(
          entry.chrome_resources || {}
        )) {
          if (typeof path === "string") {
            chromeEntries.push(["locale", alias, language, path]);
          } else if (platform in path) {
            // If the path is not a string, it's an object with path per
            // platform where the keys are taken from AppConstants.platform
            chromeEntries.push(["locale", alias, language, path[platform]]);
          }
        }
      }

      // 2. Compute langpack ID.
      const productCodeName = AppConstants.MOZ_BUILD_APP.replace("/", "-");

      // The result path looks like this:
      //   Firefox - `langpack-pl-browser`
      //   Fennec - `langpack-pl-mobile-android`
      const langpackId = `langpack-${manifest.langpack_id}-${productCodeName}`;

      // 3. Compute L10nRegistry sources for this langpack.
      const l10nRegistrySources = {};

      // Check if there's a root directory `/localization` in the langpack.
      // If there is one, add it with the name `toolkit` as a FileSource.
      const entries = await this._readDirectory("localization");
      if (entries.length) {
        l10nRegistrySources.toolkit = "";
      }

      // Add any additional sources listed in the manifest
      if (manifest.sources) {
        for (const [sourceName, { base_path }] of Object.entries(
          manifest.sources
        )) {
          l10nRegistrySources[sourceName] = base_path;
        }
      }

      // 4. Save the list of languages handled by this langpack.
      const languages = Object.keys(manifest.languages);

      this.startupData = {
        chromeEntries,
        langpackId,
        l10nRegistrySources,
        languages,
      };
    } else if (this.type == "dictionary") {
      let dictionaries = {};
      for (let [lang, path] of Object.entries(manifest.dictionaries)) {
        path = path.replace(/^\/+/, "");

        let dir = dirname(path);
        if (dir === ".") {
          dir = "";
        }
        let leafName = basename(path);
        let affixPath = leafName.slice(0, -3) + "aff";

        let entries = await this._readDirectory(dir);
        if (!entries.includes(leafName)) {
          this.manifestError(
            `Invalid dictionary path specified for '${lang}': ${path}`
          );
        }
        if (!entries.includes(affixPath)) {
          this.manifestError(
            `Invalid dictionary path specified for '${lang}': Missing affix file: ${path}`
          );
        }

        dictionaries[lang] = path;
      }

      this.startupData = { dictionaries };
    }

    if (schemaPromises.size) {
      let schemas = new Map();
      for (let [url, promise] of schemaPromises) {
        schemas.set(url, await promise);
      }
      result.schemaURLs = schemas;
    }

    return result;
  }

  // Reads the extension's |manifest.json| file, and stores its
  // parsed contents in |this.manifest|.
  async loadManifest() {
    let [manifestData] = await Promise.all([
      this.parseManifest(),
      Management.lazyInit(),
    ]);

    if (!manifestData) {
      return;
    }

    // Do not override the add-on id that has been already assigned.
    if (!this.id) {
      this.id = manifestData.id;
    }

    this.manifest = manifestData.manifest;
    this.apiNames = manifestData.apiNames;
    this.contentScripts = manifestData.contentScripts;
    this.dependencies = manifestData.dependencies;
    this.permissions = manifestData.permissions;
    this.schemaURLs = manifestData.schemaURLs;
    this.type = manifestData.type;

    this.modules = manifestData.modules;

    this.apiManager = this.getAPIManager();
    await this.apiManager.lazyInit();

    this.webAccessibleResources = manifestData.webAccessibleResources;

    this.originControls = manifestData.originControls;
    this.allowedOrigins = new MatchPatternSet(manifestData.originPermissions, {
      restrictSchemes: this.restrictSchemes,
    });

    return this.manifest;
  }

  hasPermission(perm, includeOptional = false) {
    // If the permission is a "manifest property" permission, we check if the extension
    // does have the required property in its manifest.
    let manifest_ = "manifest:";
    if (perm.startsWith(manifest_)) {
      // Handle nested "manifest property" permission (e.g. as in "manifest:property.nested").
      let value = this.manifest;
      for (let prop of perm.substr(manifest_.length).split(".")) {
        if (!value) {
          break;
        }
        value = value[prop];
      }

      return value != null;
    }

    if (this.permissions.has(perm)) {
      return true;
    }

    if (includeOptional && this.manifest.optional_permissions.includes(perm)) {
      return true;
    }

    return false;
  }

  getAPIManager() {
    /** @type {(InstanceType<typeof ExtensionCommon.LazyAPIManager>)[]} */
    let apiManagers = [Management];

    for (let id of this.dependencies) {
      let policy = WebExtensionPolicy.getByID(id);
      if (policy) {
        if (policy.extension.experimentAPIManager) {
          apiManagers.push(policy.extension.experimentAPIManager);
        } else if (AppConstants.DEBUG) {
          Cu.reportError(`Cannot find experimental API exported from ${id}`);
        }
      }
    }

    if (this.modules) {
      this.experimentAPIManager = new ExtensionCommon.LazyAPIManager(
        "main",
        this.modules.parent,
        this.schemaURLs
      );

      apiManagers.push(this.experimentAPIManager);
    }

    if (apiManagers.length == 1) {
      return apiManagers[0];
    }

    return new ExtensionCommon.MultiAPIManager("main", apiManagers.reverse());
  }

  localizeMessage(...args) {
    return this.localeData.localizeMessage(...args);
  }

  localize(str, locale) {
    // If the extension declares fluent resources in the manifest, try
    // first to localize with fluent.  Also use the original webextension
    // method (_locales/xx.json) so extensions can migrate bit by bit.
    // Note also that fluent keys typically use hyphense, so hyphens are
    // allowed in the __MSG_foo__ keys used by fluent, though they are
    // not allowed in the keys used for json translations.
    if (this.fluentL10n) {
      str = str.replace(/__MSG_([-A-Za-z0-9@_]+?)__/g, (matched, message) => {
        let translation = this.fluentL10n.formatValueSync(message);
        return translation !== undefined ? translation : matched;
      });
    }
    if (this.localeData) {
      str = this.localeData.localize(str, locale);
    }
    return str;
  }

  // If a "default_locale" is specified in that manifest, returns it
  // as a Gecko-compatible locale string. Otherwise, returns null.
  get defaultLocale() {
    if (this.manifest.default_locale != null) {
      return this.normalizeLocaleCode(this.manifest.default_locale);
    }

    return null;
  }

  // Returns true if an addon is builtin to Firefox or
  // distributed via Normandy into a system location.
  get isAppProvided() {
    return this.addonData.builtIn || this.addonData.isSystem;
  }

  get isHidden() {
    return (
      this.addonData.locationHidden ||
      (this.isPrivileged && this.manifest.hidden)
    );
  }

  // Normalizes a Chrome-compatible locale code to the appropriate
  // Gecko-compatible variant. Currently, this means simply
  // replacing underscores with hyphens.
  normalizeLocaleCode(locale) {
    return locale.replace(/_/g, "-");
  }

  // Reads the locale file for the given Gecko-compatible locale code, and
  // stores its parsed contents in |this.localeMessages.get(locale)|.
  async readLocaleFile(locale) {
    let locales = await this.promiseLocales();
    let dir = locales.get(locale) || locale;
    let file = `_locales/${dir}/messages.json`;

    try {
      let messages = await this.readJSON(file);
      return this.localeData.addLocale(locale, messages, this);
    } catch (e) {
      this.packagingError(`Loading locale file ${file}: ${e}`);
      return new Map();
    }
  }

  async _promiseLocaleMap() {
    let locales = new Map();

    let entries = await this._readDirectory("_locales", true);
    for (let name of entries) {
      let locale = this.normalizeLocaleCode(name);
      locales.set(locale, name);
    }

    return locales;
  }

  _setupLocaleData(locales) {
    if (this.localeData) {
      return this.localeData.locales;
    }

    this.localeData = new lazy.LocaleData({
      defaultLocale: this.defaultLocale,
      locales,
      builtinMessages: this.builtinMessages,
    });

    return locales;
  }

  // Reads the list of locales available in the extension, and returns a
  // Promise which resolves to a Map upon completion.
  // Each map key is a Gecko-compatible locale code, and each value is the
  // "_locales" subdirectory containing that locale:
  //
  // Map(gecko-locale-code -> locale-directory-name)
  promiseLocales() {
    if (!this._promiseLocales) {
      this._promiseLocales = (async () => {
        let locales = this._promiseLocaleMap();
        return this._setupLocaleData(locales);
      })();
    }

    return this._promiseLocales;
  }

  // Reads the locale messages for all locales, and returns a promise which
  // resolves to a Map of locale messages upon completion. Each key in the map
  // is a Gecko-compatible locale code, and each value is a locale data object
  // as returned by |readLocaleFile|.
  async initAllLocales() {
    let locales = await this.promiseLocales();

    await Promise.all(
      Array.from(locales.keys(), locale => this.readLocaleFile(locale))
    );

    let defaultLocale = this.defaultLocale;
    if (defaultLocale) {
      if (!locales.has(defaultLocale)) {
        this.manifestError(
          'Value for "default_locale" property must correspond to ' +
            'a directory in "_locales/". Not found: ' +
            JSON.stringify(`_locales/${this.manifest.default_locale}/`)
        );
      }
    } else if (locales.size) {
      this.manifestError(
        'The "default_locale" property is required when a ' +
          '"_locales/" directory is present.'
      );
    }

    return this.localeData.messages;
  }

  // Reads the locale file for the given Gecko-compatible locale code, or the
  // default locale if no locale code is given, and sets it as the currently
  // selected locale on success.
  //
  // Pre-loads the default locale for fallback message processing, regardless
  // of the locale specified.
  //
  // If no locales are unavailable, resolves to |null|.
  async initLocale(locale = this.defaultLocale) {
    if (locale == null) {
      return null;
    }

    let promises = [this.readLocaleFile(locale)];

    let { defaultLocale } = this;
    if (locale != defaultLocale && !this.localeData.has(defaultLocale)) {
      promises.push(this.readLocaleFile(defaultLocale));
    }

    let results = await Promise.all(promises);

    this.localeData.selectedLocale = locale;
    return results[0];
  }

  /**
   * @param {string} origin
   * @returns {boolean}       If this is one of the "all sites" permission.
   */
  static isAllSitesPermission(origin) {
    try {
      let info = ExtensionData.classifyOriginPermissions([origin], true);
      return !!info.allUrls;
    } catch (e) {
      // Passed string is not an origin permission.
      return false;
    }
  }

  /**
   * @typedef {object} HostPermissions
   * @param {string} allUrls   permission used to obtain all urls access
   * @param {Set} wildcards    set contains permissions with wildcards
   * @param {Set} sites        set contains explicit host permissions
   * @param {Map} wildcardsMap mapping origin wildcards to labels
   * @param {Map} sitesMap     mapping origin patterns to labels
   */

  /**
   * Classify host permissions
   *
   * @param {Array<string>} origins
   *                        permission origins
   * @param {boolean}       ignoreNonWebSchemes
   *                        return only these schemes: *, http, https, ws, wss
   *
   * @returns {HostPermissions}
   */
  static classifyOriginPermissions(origins = [], ignoreNonWebSchemes = false) {
    let allUrls = null,
      wildcards = new Set(),
      sites = new Set(),
      // TODO: use map.values() instead of these sets.  Note: account for two
      // match patterns producing the same permission string, see bug 1765828.
      wildcardsMap = new Map(),
      sitesMap = new Map();

    // https://searchfox.org/mozilla-central/rev/6f6cf28107/toolkit/components/extensions/MatchPattern.cpp#235
    const wildcardSchemes = ["*", "http", "https", "ws", "wss"];

    for (let permission of origins) {
      if (permission == "<all_urls>") {
        allUrls = permission;
        continue;
      }

      // Privileged extensions may request access to "about:"-URLs, such as
      // about:reader.
      let match = /^([a-z*]+):\/\/([^/]*)\/|^about:/.exec(permission);
      if (!match) {
        throw new Error(`Unparseable host permission ${permission}`);
      }

      // Note: the scheme is ignored in the permission warnings. If this ever
      // changes, update the comparePermissions method as needed.
      let [, scheme, host] = match;
      if (ignoreNonWebSchemes && !wildcardSchemes.includes(scheme)) {
        continue;
      }

      if (!host || host == "*") {
        if (!allUrls) {
          allUrls = permission;
        }
      } else if (host.startsWith("*.")) {
        wildcards.add(host.slice(2));
        // Using MatchPattern to normalize the pattern string.
        let pat = new MatchPattern(permission, { ignorePath: true });
        wildcardsMap.set(pat.pattern, `${scheme}://${host.slice(2)}`);
      } else {
        sites.add(host);
        let pat = new MatchPattern(permission, {
          ignorePath: true,
          // Safe because used just for normalization, not for granting access.
          restrictSchemes: false,
        });
        sitesMap.set(pat.pattern, `${scheme}://${host}`);
      }
    }
    return { allUrls, wildcards, sites, wildcardsMap, sitesMap };
  }

  /**
   * @typedef {object} Permissions
   * @property {Array<string>} origins Origin permissions.
   * @property {Array<string>} permissions Regular (non-origin) permissions.
   */

  /**
   * Formats all the strings for a permissions dialog/notification.
   *
   * @param {object} info Information about the permissions being requested.
   *
   * @param {object} [info.addon] Optional information about the addon.
   * @param {Permissions} [info.optionalPermissions]
   *                      Optional permissions listed in the manifest.
   * @param {Permissions} info.permissions Requested permissions.
   * @param {string} info.siteOrigin
   * @param {Array<string>} [info.sitePermissions]
   * @param {boolean} info.unsigned
   *                  True if the prompt is for installing an unsigned addon.
   * @param {string} info.type
   *                 The type of prompt being shown.  May be one of "update",
   *                 "sideload", "optional", or omitted for a regular
   *                 install prompt.
   * @param {object} options
   * @param {boolean} [options.collapseOrigins]
   *                  Wether to limit the number of displayed host permissions.
   *                  Default is false.
   * @param {boolean} [options.buildOptionalOrigins]
   *                  Wether to build optional origins Maps for permission
   *                  controls.  Defaults to false.
   *
   * @returns {object} An object with properties containing localized strings
   *                   for various elements of a permission dialog. The "header"
   *                   property on this object is the notification header text
   *                   and it has the string "<>" as a placeholder for the
   *                   addon name.
   *
   *                   "object.msgs" is an array of localized strings describing required permissions
   *
   *                   "object.optionalPermissions" is a map of permission name to localized
   *                   strings describing the permission.
   *
   *                   "object.optionalOrigins" is a map of a host permission to localized strings
   *                   describing the host permission, where appropriate.  Currently only
   *                   all url style permissions are included.
   */
  static formatPermissionStrings(
    {
      addon,
      optionalPermissions,
      permissions,
      siteOrigin,
      sitePermissions,
      type,
      unsigned,
    },
    { collapseOrigins = false, buildOptionalOrigins = false } = {}
  ) {
    const l10n = lazy.PERMISSION_L10N;

    const msgIds = [];
    const headerArgs = { extension: "<>" };
    let acceptId = "webext-perms-add";
    let cancelId = "webext-perms-cancel";

    const result = {
      msgs: [],
      optionalPermissions: {},
      optionalOrigins: {},
      text: "",
      listIntro: "",
    };

    // To keep the label & accesskey in sync for localizations,
    // they need to be stored as attributes of the same Fluent message.
    // This unpacks them into the shape expected of them in `result`.
    function setAcceptCancel(acceptId, cancelId) {
      const haveAccessKeys = AppConstants.platform !== "android";

      const [accept, cancel] = l10n.formatMessagesSync([
        { id: acceptId },
        { id: cancelId },
      ]);

      for (let { name, value } of accept.attributes) {
        if (name === "label") {
          result.acceptText = value;
        } else if (name === "accesskey" && haveAccessKeys) {
          result.acceptKey = value;
        }
      }

      for (let { name, value } of cancel.attributes) {
        if (name === "label") {
          result.cancelText = value;
        } else if (name === "accesskey" && haveAccessKeys) {
          result.cancelKey = value;
        }
      }
    }

    // Synthetic addon install can only grant access to a single permission so we can have
    // a less-generic message than addons with site permissions.
    // NOTE: this is used as part of the synthetic addon install flow implemented for the
    // SitePermissionAddonProvider.
    // FIXME
    if (addon?.type === lazy.SITEPERMS_ADDON_TYPE) {
      // We simplify the origin to make it more user friendly. The origin is assured to be
      // available because the SitePermsAddon install is always expected to be triggered
      // from a website, making the siteOrigin always available through the installing principal.
      headerArgs.hostname = new URL(siteOrigin).hostname;

      // messages are specific to the type of gated permission being installed
      const headerId =
        sitePermissions[0] === "midi-sysex"
          ? "webext-site-perms-header-with-gated-perms-midi-sysex"
          : "webext-site-perms-header-with-gated-perms-midi";
      result.header = l10n.formatValueSync(headerId, headerArgs);

      // We use the same string for midi and midi-sysex, and don't support any
      // other types of site permission add-ons. So we just hard-code the
      // descriptor for now. See bug 1826747.
      result.text = l10n.formatValueSync(
        "webext-site-perms-description-gated-perms-midi"
      );

      setAcceptCancel(acceptId, cancelId);
      return result;
    }

    // NOTE: this is used as part of the synthetic addon implemented for the
    // SitePermissionAddonProvider to render the site permissions in the
    // about:addon detail view for the synthetic addon entries.
    if (sitePermissions) {
      for (let permission of sitePermissions) {
        let permMsg;
        switch (permission) {
          case "midi":
            permMsg = l10n.formatValueSync("webext-site-perms-midi");
            break;
          case "midi-sysex":
            permMsg = l10n.formatValueSync("webext-site-perms-midi-sysex");
            break;
          default:
            Cu.reportError(
              `site_permission ${permission} missing readable text property`
            );
            // We must never have a DOM api permission that is hidden so in
            // the case of any error, we'll use the plain permission string.
            // test_ext_sitepermissions.js tests for no missing messages, this
            // is just an extra fallback.
            permMsg = permission;
        }
        result.msgs.push(permMsg);
      }

      // We simplify the origin to make it more user friendly.  The origin is
      // assured to be available via schema requirement.
      headerArgs.hostname = new URL(siteOrigin).hostname;

      const headerId = unsigned
        ? "webext-site-perms-header-unsigned-with-perms"
        : "webext-site-perms-header-with-perms";
      result.header = l10n.formatValueSync(headerId, headerArgs);
      setAcceptCancel(acceptId, cancelId);
      return result;
    }

    if (permissions) {
      // First classify our host permissions
      let { allUrls, wildcards, sites } =
        ExtensionData.classifyOriginPermissions(permissions.origins);

      // Format the host permissions.  If we have a wildcard for all urls,
      // a single string will suffice.  Otherwise, show domain wildcards
      // first, then individual host permissions.
      if (allUrls) {
        msgIds.push("webext-perms-host-description-all-urls");
      } else {
        // Formats a list of host permissions.  If we have 4 or fewer, display
        // them all, otherwise display the first 3 followed by an item that
        // says "...plus N others"
        const addMessages = (set, l10nId, moreL10nId) => {
          if (collapseOrigins && set.size > 4) {
            for (let domain of Array.from(set).slice(0, 3)) {
              msgIds.push({ id: l10nId, args: { domain } });
            }
            msgIds.push({
              id: moreL10nId,
              args: { domainCount: set.size - 3 },
            });
          } else {
            for (let domain of set) {
              msgIds.push({ id: l10nId, args: { domain } });
            }
          }
        };

        addMessages(
          wildcards,
          "webext-perms-host-description-wildcard",
          "webext-perms-host-description-too-many-wildcards"
        );
        addMessages(
          sites,
          "webext-perms-host-description-one-site",
          "webext-perms-host-description-too-many-sites"
        );
      }

      // Finally, show remaining permissions, in the same order as AMO.
      // The permissions are sorted alphabetically by the permission
      // string to match AMO.
      // Show the native messaging permission first if it is present.
      const NATIVE_MSG_PERM = "nativeMessaging";
      const permissionsSorted = permissions.permissions.sort((a, b) => {
        if (a === NATIVE_MSG_PERM) {
          return -1;
        } else if (b === NATIVE_MSG_PERM) {
          return 1;
        }
        return a < b ? -1 : 1;
      });
      for (let permission of permissionsSorted) {
        const l10nId = lazy.permissionToL10nId(permission);
        // We deliberately do not include all permissions in the prompt.
        // So if we don't find one then just skip it.
        if (l10nId) {
          msgIds.push(l10nId);
        }
      }
    }

    if (optionalPermissions) {
      // Generate a map of permission names to permission strings for optional
      // permissions.  The key is necessary to handle toggling those permissions.
      const opKeys = [];
      const opL10nIds = [];
      for (let permission of optionalPermissions.permissions) {
        const l10nId = lazy.permissionToL10nId(permission);
        // We deliberately do not include all permissions in the prompt.
        // So if we don't find one then just skip it.
        if (l10nId) {
          opKeys.push(permission);
          opL10nIds.push(l10nId);
        }
      }
      if (opKeys.length) {
        const opRes = l10n.formatValuesSync(opL10nIds);
        for (let i = 0; i < opKeys.length; ++i) {
          result.optionalPermissions[opKeys[i]] = opRes[i];
        }
      }

      const { allUrls, sitesMap, wildcardsMap } =
        ExtensionData.classifyOriginPermissions(
          optionalPermissions.origins,
          true
        );
      const ooKeys = [];
      const ooL10nIds = [];
      if (allUrls) {
        ooKeys.push(allUrls);
        ooL10nIds.push("webext-perms-host-description-all-urls");
      }

      // Current UX controls are meant for developer testing with mv3.
      if (buildOptionalOrigins) {
        for (let [pattern, domain] of wildcardsMap.entries()) {
          ooKeys.push(pattern);
          ooL10nIds.push({
            id: "webext-perms-host-description-wildcard",
            args: { domain },
          });
        }
        for (let [pattern, domain] of sitesMap.entries()) {
          ooKeys.push(pattern);
          ooL10nIds.push({
            id: "webext-perms-host-description-one-site",
            args: { domain },
          });
        }
      }

      if (ooKeys.length) {
        const res = l10n.formatValuesSync(ooL10nIds);
        for (let i = 0; i < res.length; ++i) {
          result.optionalOrigins[ooKeys[i]] = res[i];
        }
      }
    }

    let headerId;
    switch (type) {
      case "sideload":
        headerId = "webext-perms-sideload-header";
        acceptId = "webext-perms-sideload-enable";
        cancelId = "webext-perms-sideload-cancel";
        result.text = l10n.formatValueSync(
          msgIds.length
            ? "webext-perms-sideload-text"
            : "webext-perms-sideload-text-no-perms"
        );
        break;
      case "update":
        headerId = "webext-perms-update-text";
        acceptId = "webext-perms-update-accept";
        break;
      case "optional":
        headerId = "webext-perms-optional-perms-header";
        acceptId = "webext-perms-optional-perms-allow";
        cancelId = "webext-perms-optional-perms-deny";
        result.listIntro = l10n.formatValueSync(
          "webext-perms-optional-perms-list-intro"
        );
        break;
      default:
        if (msgIds.length) {
          headerId = unsigned
            ? "webext-perms-header-unsigned-with-perms"
            : "webext-perms-header-with-perms";
        } else {
          headerId = unsigned
            ? "webext-perms-header-unsigned"
            : "webext-perms-header";
        }
    }

    result.header = l10n.formatValueSync(headerId, headerArgs);
    result.msgs = l10n.formatValuesSync(msgIds);
    setAcceptCancel(acceptId, cancelId);
    return result;
  }
}

const PROXIED_EVENTS = new Set([
  "test-harness-message",
  "background-script-suspend",
  "background-script-suspend-canceled",
  "background-script-suspend-ignored",
]);

class BootstrapScope {
  install() {}
  uninstall(data) {
    lazy.AsyncShutdown.profileChangeTeardown.addBlocker(
      `Uninstalling add-on: ${data.id}`,
      Management.emit("uninstall", { id: data.id }).then(() => {
        Management.emit("uninstall-complete", { id: data.id });
      })
    );
  }

  fetchState() {
    if (this.extension) {
      return { state: this.extension.state };
    }
    return null;
  }

  async update(data, reason) {
    // For updates that happen during startup, such as sideloads
    // and staged updates, the extension startupReason will be
    // APP_STARTED.  In some situations, such as background and
    // persisted listeners, we also need to know that the addon
    // was updated.
    this.updateReason = BootstrapScope.BOOTSTRAP_REASON_MAP[reason];
    // Retain any previously granted permissions that may have migrated
    // into the optional list.
    if (data.oldPermissions) {
      // New permissions may be null, ensure we have an empty
      // permission set in that case.
      let emptyPermissions = { permissions: [], origins: [] };
      await ExtensionData.migratePermissions(
        data.id,
        data.oldPermissions,
        data.oldOptionalPermissions,
        data.userPermissions || emptyPermissions,
        data.optionalPermissions || emptyPermissions
      );
    }

    return Management.emit("update", {
      id: data.id,
      resourceURI: data.resourceURI,
      isPrivileged: data.isPrivileged,
    });
  }

  startup(data, reason) {
    // eslint-disable-next-line no-use-before-define
    this.extension = new Extension(
      data,
      BootstrapScope.BOOTSTRAP_REASON_MAP[reason],
      this.updateReason
    );
    return this.extension.startup();
  }

  async shutdown(data, reason) {
    let result = await this.extension.shutdown(
      BootstrapScope.BOOTSTRAP_REASON_MAP[reason]
    );
    this.extension = null;
    return result;
  }

  static get BOOTSTRAP_REASON_MAP() {
    const BR = lazy.AddonManagerPrivate.BOOTSTRAP_REASONS;
    const value = Object.freeze({
      [BR.APP_STARTUP]: "APP_STARTUP",
      [BR.APP_SHUTDOWN]: "APP_SHUTDOWN",
      [BR.ADDON_ENABLE]: "ADDON_ENABLE",
      [BR.ADDON_DISABLE]: "ADDON_DISABLE",
      [BR.ADDON_INSTALL]: "ADDON_INSTALL",
      [BR.ADDON_UNINSTALL]: "ADDON_UNINSTALL",
      [BR.ADDON_UPGRADE]: "ADDON_UPGRADE",
      [BR.ADDON_DOWNGRADE]: "ADDON_DOWNGRADE",
    });
    return redefineGetter(this, "BOOTSTRAP_REASON_TO_STRING_MAP", value);
  }
}

class DictionaryBootstrapScope extends BootstrapScope {
  install() {}
  uninstall() {}

  startup(data) {
    // eslint-disable-next-line no-use-before-define
    this.dictionary = new Dictionary(data);
    return this.dictionary.startup();
  }

  async shutdown(data, reason) {
    this.dictionary.shutdown(BootstrapScope.BOOTSTRAP_REASON_MAP[reason]);
    this.dictionary = null;
  }
}

class LangpackBootstrapScope extends BootstrapScope {
  install() {}
  uninstall() {}
  async update() {}

  startup(data) {
    // eslint-disable-next-line no-use-before-define
    this.langpack = new Langpack(data);
    return this.langpack.startup();
  }

  async shutdown(data, reason) {
    this.langpack.shutdown(BootstrapScope.BOOTSTRAP_REASON_MAP[reason]);
    this.langpack = null;
  }
}

let activeExtensionIDs = new Set();

let pendingExtensions = new Map();

/**
 * This class is the main representation of an active WebExtension
 * in the main process.
 *
 * @augments ExtensionData
 */
export class Extension extends ExtensionData {
  /** @type {Map<string, Map<string, any>>} */
  persistentListeners;

  /** @type {import("ExtensionShortcuts.sys.mjs").ExtensionShortcuts} */
  shortcuts;

  /**
   * Extension's TabManager, initialized at "startup" event of Management.
   *
   * @type {TabManagerBase}
   */
  tabManager;

  /**
   * Extension's WindowManager, initialized at "startup" event of Management.
   *
   * @type {WindowManagerBase}
   */
  windowManager;

  /** @type {(options?: { ignoreDevToolsAttached?: boolean, disableResetIdleForTest?: boolean }) => Promise} */
  terminateBackground;

  constructor(addonData, startupReason, updateReason) {
    super(addonData.resourceURI, addonData.isPrivileged);

    this.startupStates = new Set();
    this.state = "Not started";
    this.userContextIsolation = lazy.userContextIsolation;

    this.sharedDataKeys = new Set();

    this.uuid = UUIDMap.get(addonData.id);
    this.instanceId = getUniqueId();

    this.MESSAGE_EMIT_EVENT = `Extension:EmitEvent:${this.instanceId}`;
    Services.ppmm.addMessageListener(this.MESSAGE_EMIT_EVENT, this);

    if (addonData.cleanupFile) {
      Services.obs.addObserver(this, "xpcom-shutdown");
      this.cleanupFile = addonData.cleanupFile || null;
      delete addonData.cleanupFile;
    }

    if (addonData.TEST_NO_ADDON_MANAGER) {
      this.dontSaveStartupData = true;
    }
    if (addonData.TEST_NO_DELAYED_STARTUP) {
      this.testNoDelayedStartup = true;
    }

    this.addonData = addonData;
    this.startupData = addonData.startupData || {};
    this.startupReason = startupReason;
    this.updateReason = updateReason;
    this.temporarilyInstalled = !!addonData.temporarilyInstalled;

    if (
      updateReason ||
      ["ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(startupReason)
    ) {
      this.startupClearCachePromise = StartupCache.clearAddonData(addonData.id);
    }

    this.remote = !WebExtensionPolicy.isExtensionProcess;
    this.remoteType = this.remote ? lazy.E10SUtils.EXTENSION_REMOTE_TYPE : null;

    if (this.remote && lazy.processCount !== 1) {
      throw new Error(
        "Out-of-process WebExtensions are not supported with multiple child processes"
      );
    }

    // This is filled in the first time an extension child is created.
    this.parentMessageManager = null;

    this.id = addonData.id;
    this.version = addonData.version;
    this.baseURL = this.getURL("");
    this.baseURI = Services.io.newURI(this.baseURL).QueryInterface(Ci.nsIURL);
    this.principal = this.createPrincipal();

    // Privileged extensions and any extensions with a recommendation state are
    // exempt from the quarantined domains.
    // NOTE: privileged extensions are also exempted from quarantined domains
    // by the WebExtensionPolicy internal logic and so ignoreQuarantine set to
    // false for a privileged extension does not make any difference in
    // practice (but we still set the ignoreQuarantine flag here accordingly
    // to the expected behavior for consistency).
    this.ignoreQuarantine =
      addonData.isPrivileged ||
      !!addonData.recommendationState?.states?.length ||
      lazy.QuarantinedDomains.isUserAllowedAddonId(this.id);

    this.views = new Set();
    this._backgroundPageFrameLoader = null;

    this.onStartup = null;

    this.hasShutdown = false;
    this.onShutdown = new Set();

    this.uninstallURL = null;

    this.allowedOrigins = null;
    this._optionalOrigins = null;
    this.webAccessibleResources = null;

    this.registeredContentScripts = new Map();

    this.emitter = new EventEmitter();

    /* eslint-disable mozilla/balanced-listeners */
    this.on("add-permissions", (ignoreEvent, permissions) => {
      for (let perm of permissions.permissions) {
        this.permissions.add(perm);
      }
      this.policy.permissions = Array.from(this.permissions);

      updateAllowedOrigins(this.policy, permissions.origins, /* isAdd */ true);
      this.allowedOrigins = this.policy.allowedOrigins;

      if (this.policy.active) {
        this.setSharedData("", this.serialize());
        Services.ppmm.sharedData.flush();
        this.broadcast("Extension:UpdatePermissions", {
          id: this.id,
          origins: permissions.origins,
          permissions: permissions.permissions,
          add: true,
        });
      }

      this.cachePermissions();
      this.updatePermissions();
    });

    this.on("remove-permissions", (ignoreEvent, permissions) => {
      for (let perm of permissions.permissions) {
        this.permissions.delete(perm);
      }
      this.policy.permissions = Array.from(this.permissions);

      updateAllowedOrigins(this.policy, permissions.origins, /* isAdd */ false);
      this.allowedOrigins = this.policy.allowedOrigins;

      if (this.policy.active) {
        this.setSharedData("", this.serialize());
        Services.ppmm.sharedData.flush();
        this.broadcast("Extension:UpdatePermissions", {
          id: this.id,
          origins: permissions.origins,
          permissions: permissions.permissions,
          add: false,
        });
      }

      this.cachePermissions();
      this.updatePermissions();
    });
    /* eslint-enable mozilla/balanced-listeners */
  }

  set state(startupState) {
    this.startupStates.clear();
    this.startupStates.add(startupState);
  }

  get state() {
    return `${Array.from(this.startupStates).join(", ")}`;
  }

  async addStartupStatePromise(name, fn) {
    this.startupStates.add(name);
    try {
      await fn();
    } finally {
      this.startupStates.delete(name);
    }
  }

  // Some helpful properties added elsewhere:

  static getBootstrapScope() {
    return new BootstrapScope();
  }

  get browsingContextGroupId() {
    return this.policy.browsingContextGroupId;
  }

  get groupFrameLoader() {
    let frameLoader = this._backgroundPageFrameLoader;
    for (let view of this.views) {
      if (view.viewType === "background" && view.xulBrowser) {
        return view.xulBrowser.frameLoader;
      }
      if (!frameLoader && view.xulBrowser) {
        frameLoader = view.xulBrowser.frameLoader;
      }
    }
    return frameLoader || ExtensionParent.DebugUtils.getFrameLoader(this.id);
  }

  get backgroundContext() {
    for (let view of this.views) {
      if (view.isBackgroundContext) {
        return view;
      }
    }
    return undefined;
  }

  on(hook, f) {
    return this.emitter.on(hook, f);
  }

  off(hook, f) {
    return this.emitter.off(hook, f);
  }

  once(hook, f) {
    return this.emitter.once(hook, f);
  }

  emit(event, ...args) {
    if (PROXIED_EVENTS.has(event)) {
      Services.ppmm.broadcastAsyncMessage(this.MESSAGE_EMIT_EVENT, {
        event,
        args,
      });
    }

    return this.emitter.emit(event, ...args);
  }

  receiveMessage({ name, data }) {
    if (name === this.MESSAGE_EMIT_EVENT) {
      this.emitter.emit(data.event, ...data.args);
    }
  }

  testMessage(...args) {
    this.emit("test-harness-message", ...args);
  }

  createPrincipal(uri = this.baseURI, originAttributes = {}) {
    return Services.scriptSecurityManager.createContentPrincipal(
      uri,
      originAttributes
    );
  }

  // Checks that the given URL is a child of our baseURI.
  isExtensionURL(url) {
    let uri = Services.io.newURI(url);

    let common = this.baseURI.getCommonBaseSpec(uri);
    return common == this.baseURL;
  }

  checkLoadURI(uri, options = {}) {
    return ExtensionCommon.checkLoadURI(uri, this.principal, options);
  }

  // Note: use checkLoadURI instead of checkLoadURL if you already have a URI.
  checkLoadURL(url, options = {}) {
    // As an optimization, if the URL starts with the extension's base URL,
    // don't do any further checks. It's always allowed to load it.
    if (url.startsWith(this.baseURL)) {
      return true;
    }

    return ExtensionCommon.checkLoadURL(url, this.principal, options);
  }

  async promiseLocales() {
    let locales = await StartupCache.locales.get(
      [this.id, "@@all_locales"],
      () => this._promiseLocaleMap()
    );

    return this._setupLocaleData(locales);
  }

  readLocaleFile(locale) {
    return StartupCache.locales
      .get([this.id, this.version, locale], () => super.readLocaleFile(locale))
      .then(result => {
        this.localeData.messages.set(locale, result);
      });
  }

  get manifestCacheKey() {
    return [this.id, this.version, Services.locale.appLocaleAsBCP47];
  }

  saveStartupData() {
    if (this.dontSaveStartupData) {
      return;
    }
    lazy.AddonManagerPrivate.setAddonStartupData(this.id, this.startupData);
  }

  async parseManifest() {
    await this.startupClearCachePromise;
    return StartupCache.manifests.get(this.manifestCacheKey, () =>
      super.parseManifest()
    );
  }

  async cachePermissions() {
    let manifestData = await this.parseManifest();

    manifestData.originPermissions = this.allowedOrigins.patterns.map(
      pat => pat.pattern
    );
    manifestData.permissions = this.permissions;
    return StartupCache.manifests.set(this.manifestCacheKey, manifestData);
  }

  async loadManifest() {
    let manifest = await super.loadManifest();

    this.ensureNoErrors();

    return manifest;
  }

  get extensionPageCSP() {
    const { content_security_policy } = this.manifest;
    // While only manifest v3 should contain an object,
    // we'll remain lenient here.
    if (
      content_security_policy &&
      typeof content_security_policy === "object"
    ) {
      return content_security_policy.extension_pages;
    }
    return content_security_policy;
  }

  get backgroundScripts() {
    return this.manifest.background?.scripts;
  }

  get backgroundTypeModule() {
    return this.manifest.background?.type === "module";
  }

  get backgroundWorkerScript() {
    return this.manifest.background?.service_worker;
  }

  get optionalPermissions() {
    return this.manifest.optional_permissions;
  }

  get privateBrowsingAllowed() {
    return this.policy.privateBrowsingAllowed;
  }

  canAccessWindow(window) {
    return this.policy.canAccessWindow(window);
  }

  // TODO bug 1699481: move this logic to WebExtensionPolicy
  canAccessContainer(userContextId) {
    userContextId = userContextId ?? 0; // firefox-default has userContextId as 0.
    let defaultRestrictedContainers = JSON.parse(
      lazy.userContextIsolationDefaultRestricted
    );
    let extensionRestrictedContainers = JSON.parse(
      Services.prefs.getStringPref(
        `extensions.userContextIsolation.${this.id}.restricted`,
        "[]"
      )
    );
    if (
      extensionRestrictedContainers.includes(userContextId) ||
      defaultRestrictedContainers.includes(userContextId)
    ) {
      return false;
    }

    return true;
  }

  // Representation of the extension to send to content
  // processes. This should include anything the content process might
  // need.
  serialize() {
    return {
      id: this.id,
      uuid: this.uuid,
      name: this.name,
      type: this.type,
      manifestVersion: this.manifestVersion,
      extensionPageCSP: this.extensionPageCSP,
      instanceId: this.instanceId,
      resourceURL: this.resourceURL,
      contentScripts: this.contentScripts,
      webAccessibleResources: this.webAccessibleResources,
      allowedOrigins: this.allowedOrigins.patterns.map(pat => pat.pattern),
      permissions: this.permissions,
      optionalPermissions: this.optionalPermissions,
      isPrivileged: this.isPrivileged,
      ignoreQuarantine: this.ignoreQuarantine,
      temporarilyInstalled: this.temporarilyInstalled,
    };
  }

  /**
   * Extended serialized data which is only needed in the extensions process,
   * and is never deserialized in web content processes.
   * Keep in sync with @see {ExtensionChild}.
   */
  serializeExtended() {
    return {
      backgroundScripts: this.backgroundScripts,
      backgroundWorkerScript: this.backgroundWorkerScript,
      backgroundTypeModule: this.backgroundTypeModule,
      childModules: this.modules && this.modules.child,
      dependencies: this.dependencies,
      persistentBackground: this.persistentBackground,
      schemaURLs: this.schemaURLs,
    };
  }

  broadcast(msg, data) {
    return new Promise(resolve => {
      let { ppmm } = Services;
      let children = new Set();
      for (let i = 0; i < ppmm.childCount; i++) {
        children.add(ppmm.getChildAt(i));
      }

      let maybeResolve;
      function listener(data) {
        children.delete(data.target);
        maybeResolve();
      }
      function observer(subject) {
        children.delete(subject);
        maybeResolve();
      }

      maybeResolve = () => {
        if (children.size === 0) {
          ppmm.removeMessageListener(msg + "Complete", listener);
          Services.obs.removeObserver(observer, "message-manager-close");
          Services.obs.removeObserver(observer, "message-manager-disconnect");
          resolve();
        }
      };
      ppmm.addMessageListener(msg + "Complete", listener, true);
      Services.obs.addObserver(observer, "message-manager-close");
      Services.obs.addObserver(observer, "message-manager-disconnect");

      ppmm.broadcastAsyncMessage(msg, data);
    });
  }

  setSharedData(key, value) {
    key = `extension/${this.id}/${key}`;
    this.sharedDataKeys.add(key);

    sharedData.set(key, value);
  }

  getSharedData(key) {
    key = `extension/${this.id}/${key}`;
    return sharedData.get(key);
  }

  initSharedData() {
    this.setSharedData("", this.serialize());
    this.setSharedData("extendedData", this.serializeExtended());
    this.setSharedData("locales", this.localeData.serialize());
    this.setSharedData("manifest", this.manifest);
    this.updateContentScripts();
  }

  updateContentScripts() {
    this.setSharedData("contentScripts", this.registeredContentScripts);
  }

  runManifest(manifest) {
    let promises = [];
    let addPromise = (name, fn) => {
      promises.push(this.addStartupStatePromise(name, fn));
    };

    for (let directive in manifest) {
      if (manifest[directive] !== null) {
        addPromise(`asyncEmitManifestEntry("${directive}")`, () =>
          Management.asyncEmitManifestEntry(this, directive)
        );
      }
    }

    activeExtensionIDs.add(this.id);
    sharedData.set("extensions/activeIDs", activeExtensionIDs);

    pendingExtensions.delete(this.id);
    sharedData.set("extensions/pending", pendingExtensions);

    Services.ppmm.sharedData.flush();
    this.broadcast("Extension:Startup", this.id);

    return Promise.all(promises);
  }

  /**
   * Call the close() method on the given object when this extension
   * is shut down.  This can happen during browser shutdown, or when
   * an extension is manually disabled or uninstalled.
   *
   * @param {object} obj
   *        An object on which to call the close() method when this
   *        extension is shut down.
   */
  callOnClose(obj) {
    this.onShutdown.add(obj);
  }

  forgetOnClose(obj) {
    this.onShutdown.delete(obj);
  }

  get builtinMessages() {
    return new Map([["@@extension_id", this.uuid]]);
  }

  // Reads the locale file for the given Gecko-compatible locale code, or if
  // no locale is given, the available locale closest to the UI locale.
  // Sets the currently selected locale on success.
  async initLocale(locale = undefined) {
    if (locale === undefined) {
      let locales = await this.promiseLocales();

      let matches = Services.locale.negotiateLanguages(
        Services.locale.appLocalesAsBCP47,
        Array.from(locales.keys()),
        this.defaultLocale
      );

      locale = matches[0];
    }

    return super.initLocale(locale);
  }

  /**
   * Clear cached resources associated to the extension principal
   * when an extension is installed (in case we were unable to do that at
   * uninstall time) or when it is being upgraded or downgraded.
   *
   * @param {string|undefined} reason
   *        BOOTSTRAP_REASON string, if provided. The value is expected to be
   *        `undefined` for extension objects without a corresponding AddonManager
   *        addon wrapper (e.g. test extensions created using `ExtensionTestUtils`
   *        without `useAddonManager` optional property).
   *
   * @returns {Promise<void>}
   *        Promise resolved when the nsIClearDataService async method call
   *        has been completed.
   */
  async clearCache(reason) {
    switch (reason) {
      case "ADDON_INSTALL":
      case "ADDON_UPGRADE":
      case "ADDON_DOWNGRADE":
        return clearCacheForExtensionPrincipal(this.principal);
    }
  }

  /**
   * Update site permissions as necessary.
   *
   * @param {string} [reason]
   *        If provided, this is a BOOTSTRAP_REASON string.  If reason is undefined,
   *        addon permissions are being added or removed that may effect the site permissions.
   */
  updatePermissions(reason) {
    const { principal } = this;

    const testPermission = perm =>
      Services.perms.testPermissionFromPrincipal(principal, perm);

    const addUnlimitedStoragePermissions = () => {
      // Set the indexedDB permission and a custom "WebExtensions-unlimitedStorage" to
      // remember that the permission hasn't been selected manually by the user.
      Services.perms.addFromPrincipal(
        principal,
        "WebExtensions-unlimitedStorage",
        Services.perms.ALLOW_ACTION
      );
      Services.perms.addFromPrincipal(
        principal,
        "persistent-storage",
        Services.perms.ALLOW_ACTION
      );
    };

    // Only update storage permissions when the extension changes in
    // some way.
    if (reason !== "APP_STARTUP" && reason !== "APP_SHUTDOWN") {
      if (this.hasPermission("unlimitedStorage")) {
        addUnlimitedStoragePermissions();
      } else {
        // Remove the indexedDB permission if it has been enabled using the
        // unlimitedStorage WebExtensions permissions.
        Services.perms.removeFromPrincipal(
          principal,
          "WebExtensions-unlimitedStorage"
        );
        Services.perms.removeFromPrincipal(principal, "persistent-storage");
      }
    } else if (
      reason === "APP_STARTUP" &&
      this.hasPermission("unlimitedStorage") &&
      testPermission("persistent-storage") !== Services.perms.ALLOW_ACTION
    ) {
      // If the extension does have the unlimitedStorage permission, but the
      // expected site permissions are missing during the app startup, then
      // add them back (See Bug 1454192).
      addUnlimitedStoragePermissions();
    }

    // Never change geolocation permissions at shutdown, since it uses a
    // session-only permission.
    if (reason !== "APP_SHUTDOWN") {
      if (this.hasPermission("geolocation")) {
        if (testPermission("geo") === Services.perms.UNKNOWN_ACTION) {
          Services.perms.addFromPrincipal(
            principal,
            "geo",
            Services.perms.ALLOW_ACTION,
            Services.perms.EXPIRE_SESSION
          );
        }
      } else if (
        reason !== "APP_STARTUP" &&
        testPermission("geo") === Services.perms.ALLOW_ACTION
      ) {
        Services.perms.removeFromPrincipal(principal, "geo");
      }
    }
  }

  async startup() {
    this.state = "Startup";

    // readyPromise is resolved with the policy upon success,
    // and with null if startup was interrupted.
    /** @type {callback} */
    let resolveReadyPromise;
    let readyPromise = new Promise(resolve => {
      resolveReadyPromise = resolve;
    });

    // Create a temporary policy object for the devtools and add-on
    // manager callers that depend on it being available early.
    this.policy = new WebExtensionPolicy({
      id: this.id,
      mozExtensionHostname: this.uuid,
      baseURL: this.resourceURL,
      isPrivileged: this.isPrivileged,
      ignoreQuarantine: this.ignoreQuarantine,
      temporarilyInstalled: this.temporarilyInstalled,
      allowedOrigins: new MatchPatternSet([]),
      localizeCallback: () => "",
      readyPromise,
    });

    this.policy.extension = this;
    if (!WebExtensionPolicy.getByID(this.id)) {
      this.policy.active = true;
    }

    pendingExtensions.set(this.id, {
      mozExtensionHostname: this.uuid,
      baseURL: this.resourceURL,
      isPrivileged: this.isPrivileged,
      ignoreQuarantine: this.ignoreQuarantine,
    });
    sharedData.set("extensions/pending", pendingExtensions);

    if (
      // Cannot use this.type because we haven't parsed the manifest yet.
      this.addonData.type === "theme" &&
      this.startupData.lwtData &&
      this.startupReason == "APP_STARTUP"
    ) {
      // Avoid FOUC at browser startup by setting the fallback theme data as
      // soon as the static theme is starting. Not doing so can result in a
      // FOUC because loadManifest + runManifest (and other steps) are async.
      lazy.LightweightThemeManager.fallbackThemeData = this.startupData.lwtData;
    }

    lazy.ExtensionTelemetry.extensionStartup.stopwatchStart(this);
    try {
      this.state = "Startup: Loading manifest";
      await this.loadManifest();
      this.state = "Startup: Loaded manifest";

      if (!this.hasShutdown) {
        this.state = "Startup: Init locale";
        await this.initLocale();
        this.state = "Startup: Initted locale";
      }

      this.ensureNoErrors();

      if (this.hasShutdown) {
        // Startup was interrupted and shutdown() has taken care of unloading
        // the extension and running cleanup logic.
        return;
      }

      await this.clearCache(this.startupReason);
      this._setupStartupPermissions();

      GlobalManager.init(this);

      if (this.hasPermission("scripting")) {
        this.state = "Startup: Initialize scripting store";
        // We have to await here because `initSharedData` depends on the data
        // fetched from the scripting store. This has to be done early because
        // we need the data to run the content scripts in existing pages at
        // startup.
        try {
          await lazy.ExtensionScriptingStore.initExtension(this);
          this.state = "Startup: Scripting store initialized";
        } catch (err) {
          this.logError(`Failed to initialize scripting store: ${err}`);
        }
      }

      this.initSharedData();

      this.policy.active = false;
      this.policy = lazy.ExtensionProcessScript.initExtension(this);
      this.policy.extension = this;

      this.updatePermissions(this.startupReason);

      // Select the storage.local backend if it is already known,
      // and start the data migration if needed.
      if (this.hasPermission("storage")) {
        if (!lazy.ExtensionStorageIDB.isBackendEnabled) {
          this.setSharedData("storageIDBBackend", false);
        } else if (lazy.ExtensionStorageIDB.isMigratedExtension(this)) {
          this.setSharedData("storageIDBBackend", true);
          this.setSharedData(
            "storageIDBPrincipal",
            lazy.ExtensionStorageIDB.getStoragePrincipal(this)
          );
        } else if (
          this.startupReason === "ADDON_INSTALL" &&
          !Services.prefs.getBoolPref(LEAVE_STORAGE_PREF, false)
        ) {
          // If the extension has been just installed, set it as migrated,
          // because there will not be any data to migrate.
          lazy.ExtensionStorageIDB.setMigratedExtensionPref(this, true);
          this.setSharedData("storageIDBBackend", true);
          this.setSharedData(
            "storageIDBPrincipal",
            lazy.ExtensionStorageIDB.getStoragePrincipal(this)
          );
        }
      }

      // Initialize DNR for the extension, only if the extension
      // has the required DNR permissions and without blocking
      // the extension startup on DNR being fully initialized.
      if (
        this.hasPermission("declarativeNetRequest") ||
        this.hasPermission("declarativeNetRequestWithHostAccess")
      ) {
        lazy.ExtensionDNR.ensureInitialized(this);
      }

      resolveReadyPromise(this.policy);

      // The "startup" Management event sent on the extension instance itself
      // is emitted just before the Management "startup" event,
      // and it is used to run code that needs to be executed before
      // any of the "startup" listeners.
      this.emit("startup", this);

      this.startupStates.clear();
      await Promise.all([
        this.addStartupStatePromise("Startup: Emit startup", () =>
          Management.emit("startup", this)
        ),
        this.addStartupStatePromise("Startup: Run manifest", () =>
          this.runManifest(this.manifest)
        ),
      ]);
      this.state = "Startup: Ran manifest";

      Management.emit("ready", this);
      this.emit("ready");

      this.state = "Startup: Complete";
    } catch (e) {
      this.state = `Startup: Error: ${e}`;

      Cu.reportError(e);

      if (this.policy) {
        this.policy.active = false;
      }

      this.cleanupGeneratedFile();

      throw e;
    } finally {
      lazy.ExtensionTelemetry.extensionStartup.stopwatchFinish(this);
      // Mark readyPromise as resolved in case it has not happened before,
      // e.g. due to an early return or an error.
      resolveReadyPromise(null);
    }
  }

  // Setup initial permissions on extension startup based on manifest
  // and potentially previous manifest and permissions values. None of
  // the ExtensionPermissions.add/remove() calls are are awaited here
  // because we update the in-memory representation at the same time.
  _setupStartupPermissions() {
    // If we add/remove permissions conditionally based on startupReason,
    // we need to update the cache, or changes will be lost after restart.
    let updateCache = false;

    // We automatically add permissions to system/built-in extensions.
    // Extensions expliticy stating not_allowed will never get permission.
    let isAllowed = this.permissions.has(PRIVATE_ALLOWED_PERMISSION);
    if (this.manifest.incognito === "not_allowed") {
      // If an extension previously had permission, but upgrades/downgrades to
      // a version that specifies "not_allowed" in manifest, remove the
      // permission.
      if (isAllowed) {
        lazy.ExtensionPermissions.remove(this.id, {
          permissions: [PRIVATE_ALLOWED_PERMISSION],
          origins: [],
        });
        this.permissions.delete(PRIVATE_ALLOWED_PERMISSION);
      }
    } else if (!isAllowed && this.isPrivileged && !this.temporarilyInstalled) {
      // Add to EP so it is preserved after ADDON_INSTALL.
      lazy.ExtensionPermissions.add(this.id, {
        permissions: [PRIVATE_ALLOWED_PERMISSION],
        origins: [],
      });
      this.permissions.add(PRIVATE_ALLOWED_PERMISSION);
    }

    // Allow other extensions to access static themes in private browsing windows
    // (See Bug 1790115).
    if (this.type === "theme") {
      this.permissions.add(PRIVATE_ALLOWED_PERMISSION);
    }

    // We only want to update the SVG_CONTEXT_PROPERTIES_PERMISSION during
    // install and upgrade/downgrade startups.
    if (INSTALL_AND_UPDATE_STARTUP_REASONS.has(this.startupReason)) {
      if (isMozillaExtension(this)) {
        // Add to EP so it is preserved after ADDON_INSTALL.
        lazy.ExtensionPermissions.add(this.id, {
          permissions: [SVG_CONTEXT_PROPERTIES_PERMISSION],
          origins: [],
        });
        this.permissions.add(SVG_CONTEXT_PROPERTIES_PERMISSION);
      } else {
        lazy.ExtensionPermissions.remove(this.id, {
          permissions: [SVG_CONTEXT_PROPERTIES_PERMISSION],
          origins: [],
        });
        this.permissions.delete(SVG_CONTEXT_PROPERTIES_PERMISSION);
      }
      updateCache = true;
    }

    // Ensure devtools permission is set.
    if (
      this.manifest.devtools_page &&
      !this.manifest.optional_permissions.includes("devtools")
    ) {
      lazy.ExtensionPermissions.add(this.id, {
        permissions: ["devtools"],
        origins: [],
      });
      this.permissions.add("devtools");
    }

    if (
      this.originControls &&
      this.startupReason === "ADDON_INSTALL" &&
      (this.manifest.granted_host_permissions || lazy.installIncludesOrigins)
    ) {
      let origins = this.getManifestOrigins();
      lazy.ExtensionPermissions.add(this.id, { permissions: [], origins });
      updateCache = true;

      let allowed = this.allowedOrigins.patterns.map(p => p.pattern);
      this.allowedOrigins = new MatchPatternSet(origins.concat(allowed), {
        restrictSchemes: this.restrictSchemes,
        ignorePath: true,
      });
    }

    if (updateCache) {
      this.cachePermissions();
    }
  }

  cleanupGeneratedFile() {
    if (!this.cleanupFile) {
      return;
    }

    let file = this.cleanupFile;
    this.cleanupFile = null;

    Services.obs.removeObserver(this, "xpcom-shutdown");

    return this.broadcast("Extension:FlushJarCache", { path: file.path })
      .then(() => {
        // We can't delete this file until everyone using it has
        // closed it (because Windows is dumb). So we wait for all the
        // child processes (including the parent) to flush their JAR
        // caches. These caches may keep the file open.
        file.remove(false);
      })
      .catch(Cu.reportError);
  }

  async shutdown(reason) {
    this.state = "Shutdown";

    this.hasShutdown = true;

    if (!this.policy) {
      return;
    }

    if (
      this.hasPermission("storage") &&
      lazy.ExtensionStorageIDB.selectedBackendPromises.has(this)
    ) {
      this.state = "Shutdown: Storage";

      // Wait the data migration to complete.
      try {
        await lazy.ExtensionStorageIDB.selectedBackendPromises.get(this);
      } catch (err) {
        Cu.reportError(
          `Error while waiting for extension data migration on shutdown: ${this.policy.debugName} - ${err.message}::${err.stack}`
        );
      }
      this.state = "Shutdown: Storage complete";
    }

    if (this.rootURI instanceof Ci.nsIJARURI) {
      this.state = "Shutdown: Flush jar cache";
      let file = this.rootURI.JARFile.QueryInterface(Ci.nsIFileURL).file;
      Services.ppmm.broadcastAsyncMessage("Extension:FlushJarCache", {
        path: file.path,
      });
      this.state = "Shutdown: Flushed jar cache";
    }

    const isAppShutdown = reason === "APP_SHUTDOWN";
    if (this.cleanupFile || !isAppShutdown) {
      StartupCache.clearAddonData(this.id);
    }

    activeExtensionIDs.delete(this.id);
    sharedData.set("extensions/activeIDs", activeExtensionIDs);

    for (let key of this.sharedDataKeys) {
      sharedData.delete(key);
    }

    Services.ppmm.removeMessageListener(this.MESSAGE_EMIT_EVENT, this);

    this.updatePermissions(reason);

    // The service worker registrations related to the extensions are unregistered
    // only when the extension is not shutting down as part of the application
    // shutdown (a previously registered service worker is expected to stay
    // active across browser restarts), the service worker may have been
    // registered through the manifest.json background.service_worker property
    // or from an extension page through the service worker API if allowed
    // through the about:config pref.
    if (!isAppShutdown) {
      this.state = "Shutdown: ServiceWorkers";
      // TODO: ServiceWorkerCleanUp may go away once Bug 1183245 is fixed.
      await lazy.ServiceWorkerCleanUp.removeFromPrincipal(this.principal);
      this.state = "Shutdown: ServiceWorkers completed";
    }

    if (!this.manifest) {
      this.state = "Shutdown: Complete: No manifest";
      this.policy.active = false;

      return this.cleanupGeneratedFile();
    }

    GlobalManager.uninit(this);

    for (let obj of this.onShutdown) {
      obj.close();
    }

    ParentAPIManager.shutdownExtension(this.id, reason);

    Management.emit("shutdown", this);
    this.emit("shutdown", isAppShutdown);

    const TIMED_OUT = Symbol();

    this.state = "Shutdown: Emit shutdown";
    let result = await Promise.race([
      this.broadcast("Extension:Shutdown", { id: this.id }),
      promiseTimeout(CHILD_SHUTDOWN_TIMEOUT_MS).then(() => TIMED_OUT),
    ]);
    this.state = `Shutdown: Emitted shutdown: ${result === TIMED_OUT}`;
    if (result === TIMED_OUT) {
      Cu.reportError(
        `Timeout while waiting for extension child to shutdown: ${this.policy.debugName}`
      );
    }

    this.policy.active = false;

    this.state = `Shutdown: Complete (${this.cleanupFile})`;
    return this.cleanupGeneratedFile();
  }

  observe(subject, topic) {
    if (topic === "xpcom-shutdown") {
      this.cleanupGeneratedFile();
    }
  }

  get name() {
    return this.manifest.name;
  }

  get optionalOrigins() {
    if (this._optionalOrigins == null) {
      let { origins } = this.manifestOptionalPermissions;
      this._optionalOrigins = new MatchPatternSet(origins, {
        restrictSchemes: this.restrictSchemes,
        ignorePath: true,
      });
    }
    return this._optionalOrigins;
  }

  get hasBrowserActionUI() {
    return this.manifest.browser_action || this.manifest.action;
  }

  getPreferredIcon(size = 16) {
    return IconDetails.getPreferredIcon(this.manifest.icons ?? {}, this, size)
      .icon;
  }
}

export class Dictionary extends ExtensionData {
  constructor(addonData) {
    super(addonData.resourceURI);
    this.id = addonData.id;
    this.startupData = addonData.startupData;
  }

  static getBootstrapScope() {
    return new DictionaryBootstrapScope();
  }

  async startup() {
    this.dictionaries = {};
    for (let [lang, path] of Object.entries(this.startupData.dictionaries)) {
      let uri = Services.io.newURI(
        path.slice(0, -4) + ".aff",
        null,
        this.rootURI
      );
      this.dictionaries[lang] = uri;

      lazy.spellCheck.addDictionary(lang, uri);
    }

    Management.emit("ready", this);
  }

  async shutdown(reason) {
    if (reason !== "APP_SHUTDOWN") {
      lazy.AddonManagerPrivate.unregisterDictionaries(this.dictionaries);
    }
  }
}

export class Langpack extends ExtensionData {
  constructor(addonData) {
    super(addonData.resourceURI);
    this.startupData = addonData.startupData;
    this.manifestCacheKey = [addonData.id, addonData.version];
  }

  static getBootstrapScope() {
    return new LangpackBootstrapScope();
  }

  async promiseLocales() {
    let locales = await StartupCache.locales.get(
      [this.id, "@@all_locales"],
      () => this._promiseLocaleMap()
    );

    return this._setupLocaleData(locales);
  }

  parseManifest() {
    return StartupCache.manifests.get(this.manifestCacheKey, () =>
      super.parseManifest()
    );
  }

  async startup() {
    this.chromeRegistryHandle = null;
    if (this.startupData.chromeEntries.length) {
      const manifestURI = Services.io.newURI(
        "manifest.json",
        null,
        this.rootURI
      );
      this.chromeRegistryHandle = lazy.aomStartup.registerChrome(
        manifestURI,
        this.startupData.chromeEntries
      );
    }

    const langpackId = this.startupData.langpackId;
    const l10nRegistrySources = this.startupData.l10nRegistrySources;

    lazy.resourceProtocol.setSubstitution(langpackId, this.rootURI);

    const fileSources = Object.entries(l10nRegistrySources).map(entry => {
      const [sourceName, basePath] = entry;
      return new L10nFileSource(
        `${sourceName}-${langpackId}`,
        langpackId,
        this.startupData.languages,
        `resource://${langpackId}/${basePath}localization/{locale}/`
      );
    });

    L10nRegistry.getInstance().registerSources(fileSources);

    Services.obs.notifyObservers(
      { wrappedJSObject: { langpack: this } },
      "webextension-langpack-startup"
    );
  }

  async shutdown(reason) {
    if (reason === "APP_SHUTDOWN") {
      // If we're shutting down, let's not bother updating the state of each
      // system.
      return;
    }

    const sourcesToRemove = Object.keys(
      this.startupData.l10nRegistrySources
    ).map(sourceName => `${sourceName}-${this.startupData.langpackId}`);
    L10nRegistry.getInstance().removeSources(sourcesToRemove);

    if (this.chromeRegistryHandle) {
      this.chromeRegistryHandle.destruct();
      this.chromeRegistryHandle = null;
    }

    lazy.resourceProtocol.setSubstitution(this.startupData.langpackId, null);
  }
}

// Exported for testing purposes.
export { ExtensionAddonObserver, PRIVILEGED_PERMS };
PK
!<��'T'T modules/ExtensionActions.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

const { ExtensionError } = ExtensionUtils;

import { ExtensionParent } from "resource://gre/modules/ExtensionParent.sys.mjs";

const { IconDetails, StartupCache } = ExtensionParent;

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "MV2_ACTION_POPURL_RESTRICTED",
  "extensions.manifestV2.actionsPopupURLRestricted",
  false
);

function parseColor(color, kind) {
  if (typeof color == "string") {
    let rgba = InspectorUtils.colorToRGBA(color);
    if (!rgba) {
      throw new ExtensionError(`Invalid badge ${kind} color: "${color}"`);
    }
    color = [rgba.r, rgba.g, rgba.b, Math.round(rgba.a * 255)];
  }
  return color;
}

/** Common base class for Page and Browser actions. */
class PanelActionBase {
  constructor(options, tabContext, extension) {
    this.tabContext = tabContext;
    this.extension = extension;

    // These are always defined on the action
    this.defaults = {
      enabled: true,
      title: options.default_title || extension.name,
      popup: options.default_popup || "",
      icon: null,
    };
    this.globals = Object.create(this.defaults);

    // eslint-disable-next-line mozilla/balanced-listeners
    this.tabContext.on("location-change", this.handleLocationChange.bind(this));

    // eslint-disable-next-line mozilla/balanced-listeners
    this.tabContext.on("tab-select", (evt, tab) => {
      this.updateOnChange(tab);
    });

    // When preloading a popup we temporarily grant active tab permissions to
    // the preloaded popup. If we don't end up opening we need to clear this
    // permission when clearing the popup.
    this.activeTabForPreload = null;
  }

  onShutdown() {
    this.tabContext.shutdown();
  }

  setPropertyFromDetails(details, prop, value) {
    return this.setProperty(this.getTargetFromDetails(details), prop, value);
  }

  /**
   * Set a global, window specific or tab specific property.
   *
   * @param {NativeTab|ChromeWindow|null} target
   *        A NativeTab tab, a ChromeWindow, or null for the global data.
   * @param {string} prop
   *        String property to set. Should should be one of "icon", "title", "badgeText",
   *        "popup", "badgeBackgroundColor", "badgeTextColor" or "enabled".
   * @param {string} value
   *        Value for prop.
   * @returns {object}
   *        The object to which the property has been set.
   */
  setProperty(target, prop, value) {
    let values = this.getContextData(target);
    if (value === null) {
      delete values[prop];
    } else {
      values[prop] = value;
    }

    this.updateOnChange(target);
    return values;
  }

  /**
   * Gets the data associated with a tab, window, or the global one.
   *
   * @param {NativeTab|ChromeWindow|null} target
   *        A NativeTab tab, a ChromeWindow, or null for the global data.
   * @returns {object}
   *        The icon, title, badge, etc. associated with the target.
   */
  getContextData(target) {
    if (target) {
      return this.tabContext.get(target);
    }
    return this.globals;
  }

  /**
   * Retrieve the value of a global, window specific or tab specific property.
   *
   * @param {NativeTab|ChromeWindow|null} target
   *        A NativeTab tab, a ChromeWindow, or null for the global data.
   * @param {string} prop
   *        Name of property to retrieve. Should should be one of "icon",
   *        "title", "badgeText", "popup", "badgeBackgroundColor" or "enabled".
   * @returns {any} value
   *          Value of prop.
   */
  getProperty(target, prop) {
    return this.getContextData(target)[prop];
  }

  getPropertyFromDetails(details, prop) {
    return this.getProperty(this.getTargetFromDetails(details), prop);
  }

  enable(tabId) {
    this.setPropertyFromDetails({ tabId }, "enabled", true);
  }

  disable(tabId) {
    this.setPropertyFromDetails({ tabId }, "enabled", false);
  }

  getIcon(details = {}) {
    return this.getPropertyFromDetails(details, "icon");
  }

  normalizeIcon(details, extension, context) {
    let icon = IconDetails.normalize(details, extension, context);
    if (!Object.keys(icon).length) {
      return null;
    }
    return icon;
  }

  /**
   * Updates the `tabData` for any location change, however it only updates the button
   * when the selected tab has a location change, or the selected tab has changed.
   *
   * @param {string} eventType
   *        The type of the event, should be "location-change".
   * @param {NativeTab} tab
   *        The tab whose location changed, or which has become selected.
   * @param {boolean} [fromBrowse]
   *        - `true` if navigation occurred in `tab`.
   *        - `false` if the location changed but no navigation occurred, e.g. due to
               a hash change or `history.pushState`.
   *        - Omitted if TabSelect has occurred, tabData does not need to be updated.
   */
  handleLocationChange(eventType, tab, fromBrowse) {
    if (fromBrowse) {
      this.tabContext.clear(tab);
    }
  }

  /**
   * Gets the popup url for a given tab.
   *
   * @param {NativeTab} tab
   *        The tab the popup refers to.
   * @param {boolean} strict
   *        If errors should be thrown if a URL is not available.
   * @returns {string}
   *        The popup URL if a popup is present, undefined otherwise.
   */
  getPopupUrl(tab, strict = false) {
    if (!this.isShownForTab(tab)) {
      if (strict) {
        throw new ExtensionError("Popup is disabled");
      }

      return undefined;
    }
    let popupUrl = this.getProperty(tab, "popup");

    if (strict && !popupUrl) {
      throw new ExtensionError("No popup URL is set");
    }

    return popupUrl;
  }

  /**
   * Grants activeTab permission for a tab when preloading the popup.
   *
   * Will clear any existing activeTab permissions previously granted for any
   * other tab.
   *
   * @param {NativeTab} tab
   *        The tab that should be granted activeTab permission for. Set to
   *        null to clear previously granted activeTab permission.
   */
  setActiveTabForPreload(tab = null) {
    let oldTab = this.activeTabForPreload;
    if (oldTab === tab) {
      return;
    }
    this.activeTabForPreload = tab;
    if (tab) {
      this.extension.tabManager.addActiveTabPermission(tab);
    }
    if (oldTab) {
      this.extension.tabManager.revokeActiveTabPermission(oldTab);
    }
  }

  /**
   * Triggers this action and sends the appropriate event if needed.
   *
   * @param {NativeTab} tab
   *        The tab on which the action was fired.
   * @param {object} clickInfo
   *        Extra data passed to the second parameter to the action API's
   *        onClicked event.
   * @returns {string}
   *        the popup URL if a popup should be open, undefined otherwise.
   */
  triggerClickOrPopup(tab, clickInfo = undefined) {
    if (!this.isShownForTab(tab)) {
      return null;
    }

    // Now that the action is actually being triggered we can clear any
    // existing preloaded activeTab permission.
    this.setActiveTabForPreload(null);
    this.extension.tabManager.addActiveTabPermission(tab);
    this.extension.tabManager.activateScripts(tab);

    let popupUrl = this.getProperty(tab, "popup");
    // The "click" event is only dispatched when the popup is not shown. This
    // is done for compatibility with the Google Chrome onClicked extension
    // API.
    if (!popupUrl) {
      this.dispatchClick(tab, clickInfo);
    }
    this.updateOnChange(tab);
    return popupUrl;
  }

  api(context) {
    let { extension } = context;
    return {
      setTitle: details => {
        this.setPropertyFromDetails(details, "title", details.title);
      },
      getTitle: details => {
        return this.getPropertyFromDetails(details, "title");
      },
      setIcon: details => {
        details.iconType = "browserAction";
        this.setPropertyFromDetails(
          details,
          "icon",
          this.normalizeIcon(details, extension, context)
        );
      },
      setPopup: details => {
        // Note: Chrome resolves arguments to setIcon relative to the calling
        // context, but resolves arguments to setPopup relative to the extension
        // root.
        // For internal consistency, we currently resolve both relative to the
        // calling context.
        let url = details.popup && context.uri.resolve(details.popup);

        if (url && !context.checkLoadURL(url)) {
          return Promise.reject({ message: `Access denied for URL ${url}` });
        }

        // On manifest_version 3 is mandatory for the resolved URI to belong to the
        // current extension (see Bug 1760608).
        //
        // The same restriction is extended  extend to MV2 extensions if the
        // "extensions.manifestV2.actionsPopupURLRestricted" preference is set to true.
        //
        // (Currently set to true by default on GeckoView builds, where the set of
        // extensions supported is limited to a small set and so less risks of
        // unexpected regressions for the existing extensions).
        if (
          url &&
          !url.startsWith(extension.baseURI.spec) &&
          (context.extension.manifestVersion >= 3 ||
            lazy.MV2_ACTION_POPURL_RESTRICTED)
        ) {
          return Promise.reject({ message: `Access denied for URL ${url}` });
        }

        this.setPropertyFromDetails(details, "popup", url);
      },
      getPopup: details => {
        return this.getPropertyFromDetails(details, "popup");
      },
    };
  }

  // Override these

  /**
   * Update the toolbar button when the extension changes the icon, title, url, etc.
   * If it only changes a parameter for a single tab, `target` will be that tab.
   * If it only changes a parameter for a single window, `target` will be that window.
   * Otherwise `target` will be null.
   *
   * @param {NativeTab|ChromeWindow} [_target]
   *        Browser tab or browser chrome window, may be null.
   */
  updateOnChange(_target) {}

  /**
   * Get tab object from tabId.
   *
   * @param {string} _tabId
   *        Internal id of the tab to get.
   * @returns {NativeTab}
   */
  getTab(_tabId) {
    throw new Error("Not implemented.");
  }

  /**
   * Get window object from windowId
   *
   * @param {string} _windowId
   *        Internal id of the window to get.
   * @returns {ChromeWindow}
   */
  getWindow(_windowId) {
    throw new Error("Not implemented.");
  }

  /**
   * Gets the target object corresponding to the `details` parameter of the various
   * get* and set* API methods.
   *
   * @param {object} _details
   *        An object with optional `tabId` or `windowId` properties.
   * @param {number} [_details.tabId]
   * @param {number} [_details.windowId]
   * @throws if both `tabId` and `windowId` are specified, or if they are invalid.
   * @returns {NativeTab|ChromeWindow|null}
   *        If a `tabId` was specified, the corresponding NativeTab tab.
   *        If a `windowId` was specified, the corresponding ChromeWindow.
   *        Otherwise, `null`.
   */
  getTargetFromDetails(_details) {
    throw new Error("Not Implemented");
  }

  /**
   * Triggers a click event.
   *
   * @param {NativeTab} _tab
   *        The tab where this event should be fired.
   * @param {object} _clickInfo
   *        Extra data passed to the second parameter to the action API's
   *        onClicked event.
   */
  dispatchClick(_tab, _clickInfo) {}

  /**
   * Checks whether this action is shown.
   *
   * @param {NativeTab} _tab
   *        The tab to be checked
   * @returns {boolean}
   */
  isShownForTab(_tab) {
    return false;
  }
}

export class PageActionBase extends PanelActionBase {
  constructor(tabContext, extension) {
    const options = extension.manifest.page_action;
    super(options, tabContext, extension);

    // `enabled` can have three different values:
    // - `false`. This means the page action is not shown.
    //   It's set as default if show_matches is empty. Can also be set in a tab via
    //   `pageAction.hide(tabId)`, e.g. in order to override show_matches.
    // - `true`. This means the page action is shown.
    //   It's never set as default because <all_urls> doesn't really match all URLs
    //   (e.g. "about:" URLs). But can be set in a tab via `pageAction.show(tabId)`.
    // - `undefined`.
    //   This is the default value when there are some patterns in show_matches.
    //   Can't be set as a tab-specific value.
    let enabled, showMatches, hideMatches;
    let show_matches = options.show_matches || [];
    let hide_matches = options.hide_matches || [];
    if (!show_matches.length) {
      // Always hide by default. No need to do any pattern matching.
      enabled = false;
    } else {
      // Might show or hide depending on the URL. Enable pattern matching.
      const { restrictSchemes } = extension;
      showMatches = new MatchPatternSet(show_matches, { restrictSchemes });
      hideMatches = new MatchPatternSet(hide_matches, { restrictSchemes });
    }

    this.defaults = {
      ...this.defaults,
      enabled,
      showMatches,
      hideMatches,
      pinned: options.pinned,
    };
    this.globals = Object.create(this.defaults);
  }

  handleLocationChange(eventType, tab, fromBrowse) {
    super.handleLocationChange(eventType, tab, fromBrowse);
    if (fromBrowse === false) {
      // Clear pattern matching cache when URL changes.
      let tabData = this.tabContext.get(tab);
      if (tabData.patternMatching !== undefined) {
        tabData.patternMatching = undefined;
      }
    }

    if (tab.selected) {
      // isShownForTab will do pattern matching (if necessary) and store the result
      // so that updateButton knows whether the page action should be shown.
      this.isShownForTab(tab);
      this.updateOnChange(tab);
    }
  }

  // Checks whether the tab action is shown when the specified tab becomes active.
  // Does pattern matching if necessary, and caches the result as a tab-specific value.
  // @param {NativeTab} tab
  //        The tab to be checked
  // @return boolean
  isShownForTab(tab) {
    let tabData = this.getContextData(tab);

    // If there is a "show" value, return it. Can be due to show(), hide() or empty show_matches.
    if (tabData.enabled !== undefined) {
      return tabData.enabled;
    }

    // Otherwise pattern matching must have been configured. Do it, caching the result.
    if (tabData.patternMatching === undefined) {
      let uri = tab.linkedBrowser.currentURI;
      tabData.patternMatching =
        tabData.showMatches.matches(uri) && !tabData.hideMatches.matches(uri);
    }
    return tabData.patternMatching;
  }

  async loadIconData() {
    const { extension } = this;
    const options = extension.manifest.page_action;
    this.defaults.icon = await StartupCache.get(
      extension,
      ["pageAction", "default_icon"],
      () =>
        this.normalizeIcon(
          { path: options.default_icon || "" },
          extension,
          null
        )
    );
  }

  getPinned() {
    return this.globals.pinned;
  }

  getTargetFromDetails({ tabId }) {
    // PageActionBase doesn't support |windowId|
    if (tabId != null) {
      return this.getTab(tabId);
    }
    return null;
  }

  api(context) {
    return {
      ...super.api(context),
      show: (...args) => this.enable(...args),
      hide: (...args) => this.disable(...args),
      isShown: ({ tabId }) => {
        let tab = this.getTab(tabId);
        return this.isShownForTab(tab);
      },
    };
  }
}

export class BrowserActionBase extends PanelActionBase {
  constructor(tabContext, extension) {
    const options =
      extension.manifest.browser_action || extension.manifest.action;
    super(options, tabContext, extension);

    let default_area =
      Services.policies?.getExtensionSettings(extension.id)?.default_area ||
      options.default_area ||
      "menupanel";

    this.defaults = {
      ...this.defaults,
      badgeText: "",
      badgeBackgroundColor: [0xd9, 0, 0, 255],
      badgeDefaultColor: [255, 255, 255, 255],
      badgeTextColor: null,
      default_area,
    };
    this.globals = Object.create(this.defaults);

    // eslint-disable-next-line mozilla/balanced-listeners
    extension.on("add-permissions", () => this.updateOnChange());
    // eslint-disable-next-line mozilla/balanced-listeners
    extension.on("remove-permissions", () => this.updateOnChange());
    // eslint-disable-next-line mozilla/balanced-listeners
    extension.on("update-ignore-quarantine", () => this.updateOnChange());
  }

  async loadIconData() {
    const { extension } = this;
    const options =
      extension.manifest.browser_action || extension.manifest.action;
    this.defaults.icon = await StartupCache.get(
      extension,
      ["browserAction", "default_icon"],
      () =>
        IconDetails.normalize(
          {
            path: options.default_icon || extension.manifest.icons,
            iconType: "browserAction",
            themeIcons: options.theme_icons,
          },
          extension
        )
    );
  }

  handleLocationChange(eventType, tab, fromBrowse) {
    super.handleLocationChange(eventType, tab, fromBrowse);
    if (fromBrowse) {
      this.updateOnChange(tab);
    }
  }

  getTargetFromDetails({ tabId, windowId }) {
    if (tabId != null && windowId != null) {
      throw new ExtensionError(
        "Only one of tabId and windowId can be specified."
      );
    }
    if (tabId != null) {
      return this.getTab(tabId);
    } else if (windowId != null) {
      return this.getWindow(windowId);
    }
    return null;
  }

  getDefaultArea() {
    return this.globals.default_area;
  }

  /**
   * Determines the text badge color to be used in a tab, window, or globally.
   *
   * @typedef {number[]} ColorArray from schemas/browser_action.json.
   *
   * @param {object} values
   *        The values associated with the tab or window, or global values.
   * @returns {ColorArray}
   */
  getTextColor(values) {
    // If a text color has been explicitly provided, use it.
    let { badgeTextColor } = values;
    if (badgeTextColor) {
      return badgeTextColor;
    }

    // Otherwise, check if the default color to be used has been cached previously.
    let { badgeDefaultColor } = values;
    if (badgeDefaultColor) {
      return badgeDefaultColor;
    }

    // Choose a color among white and black, maximizing contrast with background
    // according to https://www.w3.org/TR/WCAG20-TECHS/G18.html#G18-procedure
    let [r, g, b] = values.badgeBackgroundColor
      .slice(0, 3)
      .map(function (channel) {
        channel /= 255;
        if (channel <= 0.03928) {
          return channel / 12.92;
        }
        return ((channel + 0.055) / 1.055) ** 2.4;
      });
    let lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;

    // The luminance is 0 for black, 1 for white, and `lum` for the background color.
    // Since `0 <= lum`, the contrast ratio for black is `c0 = (lum + 0.05) / 0.05`.
    // Since `lum <= 1`, the contrast ratio for white is `c1 = 1.05 / (lum + 0.05)`.
    // We want to maximize contrast, so black is chosen if `c1 < c0`, that is, if
    // `1.05 * 0.05 < (L + 0.05) ** 2`. Otherwise white is chosen.
    let channel = 1.05 * 0.05 < (lum + 0.05) ** 2 ? 0 : 255;
    let result = [channel, channel, channel, 255];

    // Cache the result as high as possible in the prototype chain
    while (!Object.getOwnPropertyDescriptor(values, "badgeDefaultColor")) {
      values = Object.getPrototypeOf(values);
    }
    values.badgeDefaultColor = result;
    return result;
  }

  isShownForTab(tab) {
    return this.getProperty(tab, "enabled");
  }

  api(context) {
    return {
      ...super.api(context),
      enable: (...args) => this.enable(...args),
      disable: (...args) => this.disable(...args),
      isEnabled: details => {
        return this.getPropertyFromDetails(details, "enabled");
      },
      setBadgeText: details => {
        this.setPropertyFromDetails(details, "badgeText", details.text);
      },
      getBadgeText: details => {
        return this.getPropertyFromDetails(details, "badgeText");
      },
      setBadgeBackgroundColor: details => {
        let color = parseColor(details.color, "background");
        let values = this.setPropertyFromDetails(
          details,
          "badgeBackgroundColor",
          color
        );
        if (color === null) {
          // Let the default text color inherit after removing background color
          delete values.badgeDefaultColor;
        } else {
          // Invalidate a cached default color calculated with the old background
          values.badgeDefaultColor = null;
        }
      },
      getBadgeBackgroundColor: details => {
        return this.getPropertyFromDetails(details, "badgeBackgroundColor");
      },
      setBadgeTextColor: details => {
        let color = parseColor(details.color, "text");
        this.setPropertyFromDetails(details, "badgeTextColor", color);
      },
      getBadgeTextColor: details => {
        let target = this.getTargetFromDetails(details);
        let values = this.getContextData(target);
        return this.getTextColor(values);
      },
    };
  }
}
PK
!<�j�0R
R
$modules/ExtensionActivityLog.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "tabTracker", () => {
  return lazy.ExtensionParent.apiManager.global.tabTracker;
});

var { DefaultMap } = ExtensionUtils;

const MSG_SET_ENABLED = "Extension:ActivityLog:SetEnabled";
const MSG_LOG = "Extension:ActivityLog:DoLog";

export const ExtensionActivityLog = {
  initialized: false,

  // id => Set(callbacks)
  listeners: new DefaultMap(() => new Set()),
  watchedIds: new Set(),

  init() {
    if (this.initialized) {
      return;
    }

    this.initialized = true;

    Services.ppmm.sharedData.set("extensions/logging", this.watchedIds);

    Services.ppmm.addMessageListener(MSG_LOG, this);
  },

  /**
   * Notify all listeners of an extension activity.
   *
   * @param {string} id The ID of the extension that caused the activity.
   * @param {string} viewType The view type the activity is in.
   * @param {string} type The type of the activity.
   * @param {string} name The API name or path.
   * @param {object} data Activity specific data.
   * @param {Date} [timeStamp] The timestamp for the activity.
   */
  log(id, viewType, type, name, data, timeStamp) {
    if (!this.initialized) {
      return;
    }
    let callbacks = this.listeners.get(id);
    if (callbacks) {
      if (!timeStamp) {
        timeStamp = new Date();
      }

      for (let callback of callbacks) {
        try {
          callback({ id, viewType, timeStamp, type, name, data });
        } catch (e) {
          Cu.reportError(e);
        }
      }
    }
  },

  addListener(id, callback) {
    this.init();
    let callbacks = this.listeners.get(id);
    if (callbacks.size === 0) {
      this.watchedIds.add(id);
      Services.ppmm.sharedData.set("extensions/logging", this.watchedIds);
      Services.ppmm.sharedData.flush();
      Services.ppmm.broadcastAsyncMessage(MSG_SET_ENABLED, { id, value: true });
    }
    callbacks.add(callback);
  },

  removeListener(id, callback) {
    let callbacks = this.listeners.get(id);
    if (callbacks.size > 0) {
      callbacks.delete(callback);
      if (callbacks.size === 0) {
        this.watchedIds.delete(id);
        Services.ppmm.sharedData.set("extensions/logging", this.watchedIds);
        Services.ppmm.sharedData.flush();
        Services.ppmm.broadcastAsyncMessage(MSG_SET_ENABLED, {
          id,
          value: false,
        });
      }
    }
  },

  receiveMessage({ name, data }) {
    if (name === MSG_LOG) {
      let { viewType, browsingContextId } = data;
      if (browsingContextId && (!viewType || viewType == "tab")) {
        let browser =
          BrowsingContext.get(browsingContextId).top.embedderElement;
        let browserData = lazy.tabTracker.getBrowserData(browser);
        if (browserData && browserData.tabId !== undefined) {
          data.data.tabId = browserData.tabId;
        }
      }
      this.log(
        data.id,
        data.viewType,
        data.type,
        data.name,
        data.data,
        new Date(data.timeStamp)
      );
    }
  },
};
PK
!<�uȤ�w�wmodules/ExtensionChild.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file handles addon logic that is independent of the chrome process and
 * may run in all web content and extension processes.
 *
 * Don't put contentscript logic here, use ExtensionContent.sys.mjs instead.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

/** @type {Lazy} */
const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "finalizationService",
  "@mozilla.org/toolkit/finalizationwitness;1",
  "nsIFinalizationWitnessService"
);

ChromeUtils.defineESModuleGetters(lazy, {
  ExtensionContent: "resource://gre/modules/ExtensionContent.sys.mjs",
  ExtensionPageChild: "resource://gre/modules/ExtensionPageChild.sys.mjs",
  ExtensionProcessScript:
    "resource://gre/modules/ExtensionProcessScript.sys.mjs",
  NativeApp: "resource://gre/modules/NativeMessaging.sys.mjs",
});

import { ExtensionCommon } from "resource://gre/modules/ExtensionCommon.sys.mjs";
import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

const { DefaultMap, ExtensionError, LimitedSet, getUniqueId } = ExtensionUtils;

const {
  redefineGetter,
  EventEmitter,
  EventManager,
  LocalAPIImplementation,
  LocaleData,
  NoCloneSpreadArgs,
  SchemaAPIInterface,
  withHandlingUserInput,
} = ExtensionCommon;

const { sharedData } = Services.cpmm;

const MSG_SET_ENABLED = "Extension:ActivityLog:SetEnabled";
const MSG_LOG = "Extension:ActivityLog:DoLog";

export const ExtensionActivityLogChild = {
  _initialized: false,
  enabledExtensions: new Set(),

  init() {
    if (this._initialized) {
      return;
    }
    this._initialized = true;

    Services.cpmm.addMessageListener(MSG_SET_ENABLED, this);

    this.enabledExtensions = new Set(
      Services.cpmm.sharedData.get("extensions/logging")
    );
  },

  receiveMessage({ name, data }) {
    if (name === MSG_SET_ENABLED) {
      if (data.value) {
        this.enabledExtensions.add(data.id);
      } else {
        this.enabledExtensions.delete(data.id);
      }
    }
  },

  async log(context, type, name, data) {
    this.init();
    let { id } = context.extension;
    if (this.enabledExtensions.has(id)) {
      this._sendActivity({
        timeStamp: Date.now(),
        id,
        viewType: context.viewType,
        type,
        name,
        data,
        browsingContextId: context.browsingContextId,
      });
    }
  },

  _sendActivity(data) {
    Services.cpmm.sendAsyncMessage(MSG_LOG, data);
  },
};

// A helper to allow us to distinguish trusted errors from unsanitized errors.
// Extensions can create plain objects with arbitrary properties (such as
// mozWebExtLocation), but not create instances of ExtensionErrorHolder.
class ExtensionErrorHolder {
  constructor(trustedErrorObject) {
    this.trustedErrorObject = trustedErrorObject;
  }
}

/**
 * A finalization witness helper that wraps a sendMessage response and
 * guarantees to either get the promise resolved, or rejected when the
 * wrapped promise goes out of scope.
 */
const StrongPromise = {
  stillAlive: new Map(),

  wrap(promise, location) {
    let id = String(getUniqueId());
    let witness = lazy.finalizationService.make(
      "extensions-onMessage-witness",
      id
    );

    return new Promise((resolve, reject) => {
      this.stillAlive.set(id, { reject, location });
      promise.then(resolve, reject).finally(() => {
        this.stillAlive.delete(id);
        witness.forget();
      });
    });
  },

  observe(subject, topic, id) {
    let message = "Promised response from onMessage listener went out of scope";
    let { reject, location } = this.stillAlive.get(id);
    reject(new ExtensionErrorHolder({ message, mozWebExtLocation: location }));
    this.stillAlive.delete(id);
  },
};
Services.obs.addObserver(StrongPromise, "extensions-onMessage-witness");

// Simple single-event emitter-like helper, exposes the EventManager api.
export class SimpleEventAPI extends EventManager {
  constructor(context, name) {
    let fires = new Set();
    let register = fire => {
      fires.add(fire);
      fire.location = context.getCaller();
      return () => fires.delete(fire);
    };
    super({ context, name, register });
    this.fires = fires;
  }
  /** @returns {any} */
  emit(...args) {
    return [...this.fires].map(fire => fire.asyncWithoutClone(...args));
  }
}

// runtime.OnMessage event helper, handles custom async/sendResponse logic.
export class MessageEvent extends SimpleEventAPI {
  emit(holder, sender) {
    if (!this.fires.size || !this.context.active) {
      return { received: false };
    }

    sender = Cu.cloneInto(sender, this.context.cloneScope);
    let message = holder.deserialize(this.context.cloneScope);

    let responses = [...this.fires]
      .map(fire => this.wrapResponse(fire, message, sender))
      .filter(x => x !== undefined);

    return !responses.length
      ? { received: true, response: false }
      : Promise.race(responses).then(
          value => ({ response: true, value }),
          error => Promise.reject(this.unwrapOrSanitizeError(error))
        );
  }

  unwrapOrSanitizeError(error) {
    if (error instanceof ExtensionErrorHolder) {
      return error.trustedErrorObject;
    }
    // If not a wrapped error, sanitize it and convert to ExtensionError, so
    // that context.normalizeError will use the error message.
    return new ExtensionError(error?.message ?? "An unexpected error occurred");
  }

  wrapResponse(fire, message, sender) {
    let response, sendResponse;
    let promise = new Promise(resolve => {
      sendResponse = Cu.exportFunction(value => {
        resolve(value);
        response = promise;
      }, this.context.cloneScope);
    });

    let result;
    try {
      result = fire.raw(message, sender, sendResponse);
    } catch (e) {
      return Promise.reject(e);
    }
    if (
      result &&
      typeof result === "object" &&
      Cu.getClassName(result, true) === "Promise" &&
      this.context.principal.subsumes(Cu.getObjectPrincipal(result))
    ) {
      return StrongPromise.wrap(result, fire.location);
    } else if (result === true) {
      return StrongPromise.wrap(promise, fire.location);
    }
    return response;
  }
}

function holdMessage(name, anonymizedName, data, native = null) {
  if (native && AppConstants.platform !== "android") {
    data = lazy.NativeApp.encodeMessage(native.context, data);
  }
  return new StructuredCloneHolder(name, anonymizedName, data);
}

// Implements the runtime.Port extension API object.
export class Port {
  /**
   * @param {BaseContext} context The context that owns this port.
   * @param {number} portId Uniquely identifies this port's channel.
   * @param {string} name Arbitrary port name as defined by the addon.
   * @param {boolean} native Is this a Port for native messaging.
   * @param {object} sender The `Port.sender` property.
   */
  constructor(context, portId, name, native, sender) {
    this.context = context;
    this.name = name;
    this.sender = sender;
    this.holdMessage = native
      ? (name, anonymizedName, data) =>
          holdMessage(name, anonymizedName, data, this)
      : holdMessage;
    this.conduit = context.openConduit(this, {
      portId,
      native,
      source: !sender,
      recv: ["PortMessage", "PortDisconnect"],
      send: ["PortMessage"],
    });
    this.initEventManagers();
  }

  initEventManagers() {
    const { context } = this;
    this.onMessage = new SimpleEventAPI(context, "Port.onMessage");
    this.onDisconnect = new SimpleEventAPI(context, "Port.onDisconnect");
  }

  getAPI() {
    // Public Port object handed to extensions from `connect()` and `onConnect`.
    return {
      name: this.name,
      sender: this.sender,
      error: null,
      onMessage: this.onMessage.api(),
      onDisconnect: this.onDisconnect.api(),
      postMessage: this.sendPortMessage.bind(this),
      disconnect: () => this.conduit.close(),
    };
  }

  recvPortMessage({ holder }) {
    this.onMessage.emit(holder.deserialize(this.api), this.api);
  }

  recvPortDisconnect({ error = null }) {
    this.conduit.close();
    if (this.context.active) {
      this.api.error = error && this.context.normalizeError(error);
      this.onDisconnect.emit(this.api);
    }
  }

  sendPortMessage(json) {
    if (this.conduit.actor) {
      return this.conduit.sendPortMessage({
        holder: this.holdMessage(
          `Port/${this.context.extension.id}/sendPortMessage/${this.name}`,
          `Port/${this.context.extension.id}/sendPortMessage/<anonymized>`,
          json
        ),
      });
    }
    throw new this.context.Error("Attempt to postMessage on disconnected port");
  }

  get api() {
    const scope = this.context.cloneScope;
    const value = Cu.cloneInto(this.getAPI(), scope, { cloneFunctions: true });
    return redefineGetter(this, "api", value);
  }
}

/**
 * Each extension context gets its own Messenger object. It handles the
 * basics of sendMessage, onMessage, connect and onConnect.
 */
export class Messenger {
  constructor(context) {
    this.context = context;
    this.conduit = context.openConduit(this, {
      childId: context.childManager.id,
      query: ["NativeMessage", "RuntimeMessage", "PortConnect"],
      recv: ["RuntimeMessage", "PortConnect"],
    });
    this.initEventManagers();
  }

  initEventManagers() {
    const { context } = this;
    this.onConnect = new SimpleEventAPI(context, "runtime.onConnect");
    this.onConnectEx = new SimpleEventAPI(context, "runtime.onConnectExternal");
    this.onMessage = new MessageEvent(context, "runtime.onMessage");
    this.onMessageEx = new MessageEvent(context, "runtime.onMessageExternal");
  }

  sendNativeMessage(nativeApp, json) {
    let holder = holdMessage(
      `Messenger/${this.context.extension.id}/sendNativeMessage/${nativeApp}`,
      null,
      json,
      this
    );
    return this.conduit.queryNativeMessage({ nativeApp, holder });
  }

  sendRuntimeMessage({ extensionId, message, callback, ...args }) {
    let response = this.conduit.queryRuntimeMessage({
      extensionId: extensionId || this.context.extension.id,
      holder: holdMessage(
        `Messenger/${this.context.extension.id}/sendRuntimeMessage`,
        null,
        message
      ),
      ...args,
    });
    // If |response| is a rejected promise, the value will be sanitized by
    // wrapPromise, according to the rules of context.normalizeError.
    return this.context.wrapPromise(response, callback);
  }

  connect({ name, native, ...args }) {
    let portId = getUniqueId();
    let port = new Port(this.context, portId, name, !!native);
    this.conduit
      .queryPortConnect({ portId, name, native, ...args })
      .catch(error => port.recvPortDisconnect({ error }));
    return port.api;
  }

  recvPortConnect({ extensionId, portId, name, sender }) {
    let event = sender.id === extensionId ? this.onConnect : this.onConnectEx;
    if (this.context.active && event.fires.size) {
      let port = new Port(this.context, portId, name, false, sender);
      return event.emit(port.api).length;
    }
  }

  recvRuntimeMessage({ extensionId, holder, sender }) {
    let event = sender.id === extensionId ? this.onMessage : this.onMessageEx;
    return event.emit(holder, sender);
  }
}

// For test use only.
var ExtensionManager = {
  extensions: new Map(),
};

/**
 * Represents an extension instance in the child process.
 * Corresponds to the @see {Extension} instance in the parent.
 */
export class ExtensionChild extends EventEmitter {
  constructor(policy) {
    super();

    this.policy = policy;
    // Set a weak reference to this instance on the WebExtensionPolicy expando properties
    // (because it makes it easier to reach the extension instance from the policy object
    // without leaking it due to a circular dependency keeping it alive).
    this.policy.weakExtension = Cu.getWeakReference(this);

    this.instanceId = policy.instanceId;
    this.optionalPermissions = policy.optionalPermissions;

    if (WebExtensionPolicy.isExtensionProcess) {
      // Keep in sync with serializeExtended in Extension.sys.mjs
      let ed = this.getSharedData("extendedData");
      this.backgroundScripts = ed.backgroundScripts;
      this.backgroundWorkerScript = ed.backgroundWorkerScript;
      this.childModules = ed.childModules;
      this.dependencies = ed.dependencies;
      this.persistentBackground = ed.persistentBackground;
      this.schemaURLs = ed.schemaURLs;
    }

    this.MESSAGE_EMIT_EVENT = `Extension:EmitEvent:${this.instanceId}`;
    Services.cpmm.addMessageListener(this.MESSAGE_EMIT_EVENT, this);

    this.apiManager = this.getAPIManager();

    this._manifest = null;
    this._localeData = null;

    this.baseURI = Services.io.newURI(`moz-extension://${this.uuid}/`);
    this.baseURL = this.baseURI.spec;

    this.principal = Services.scriptSecurityManager.createContentPrincipal(
      this.baseURI,
      {}
    );

    // Only used in addon processes.
    this.blockedParsingDocuments = new WeakSet();
    this.views = new Set();

    // Only used for devtools views.
    this.devtoolsViews = new Set();

    ExtensionManager.extensions.set(this.id, this);
  }

  get id() {
    return this.policy.id;
  }

  get uuid() {
    return this.policy.mozExtensionHostname;
  }

  get permissions() {
    return new Set(this.policy.permissions);
  }

  get allowedOrigins() {
    return this.policy.allowedOrigins;
  }

  getSharedData(key) {
    return sharedData.get(`extension/${this.id}/${key}`);
  }

  get localeData() {
    if (!this._localeData) {
      this._localeData = new LocaleData(this.getSharedData("locales"));
    }
    return this._localeData;
  }

  get manifest() {
    if (!this._manifest) {
      this._manifest = this.getSharedData("manifest");
    }
    return this._manifest;
  }

  get manifestVersion() {
    return this.manifest.manifest_version;
  }

  get privateBrowsingAllowed() {
    return this.policy.privateBrowsingAllowed;
  }

  canAccessWindow(window) {
    return this.policy.canAccessWindow(window);
  }

  getAPIManager() {
    /** @type {InstanceType<typeof ExtensionCommon.LazyAPIManager>[]} */
    let apiManagers = [lazy.ExtensionPageChild.apiManager];

    if (this.dependencies) {
      for (let id of this.dependencies) {
        let extension = lazy.ExtensionProcessScript.getExtensionChild(id);
        if (extension) {
          apiManagers.push(extension.experimentAPIManager);
        }
      }
    }

    if (this.childModules) {
      this.experimentAPIManager = new ExtensionCommon.LazyAPIManager(
        "addon",
        this.childModules,
        this.schemaURLs
      );

      apiManagers.push(this.experimentAPIManager);
    }

    if (apiManagers.length == 1) {
      return apiManagers[0];
    }

    return new ExtensionCommon.MultiAPIManager("addon", apiManagers.reverse());
  }

  shutdown() {
    ExtensionManager.extensions.delete(this.id);
    lazy.ExtensionContent.shutdownExtension(this);
    Services.cpmm.removeMessageListener(this.MESSAGE_EMIT_EVENT, this);
    this.emit("shutdown");
  }

  getContext(window) {
    return lazy.ExtensionContent.getContext(this, window);
  }

  // Implementation of runtime.getURL / extension.getURL.
  // ExtensionData.prototype.getURL has a similar signature and return value.
  getURL(path = "") {
    if (path.startsWith(this.baseURL)) {
      // Historically, when the input is an already-resolved extension URL,
      // we return the parsed version of it as-is.
      return path;
    }
    if (path.startsWith("/")) {
      // this.baseURL already contains a "/".
      path = path.slice(1);
    }
    return this.baseURL + path;
  }

  emit(event, ...args) {
    Services.cpmm.sendAsyncMessage(this.MESSAGE_EMIT_EVENT, { event, args });
    return super.emit(event, ...args);
  }

  // TODO(Bug 1768471): consider folding this back into emit if we will change it to
  // return a value as EventEmitter and Extension emit methods do.
  emitLocalWithResult(event, ...args) {
    return super.emit(event, ...args);
  }

  receiveMessage({ name, data }) {
    if (name === this.MESSAGE_EMIT_EVENT) {
      super.emit(data.event, ...data.args);
    }
  }

  localizeMessage(...args) {
    return this.localeData.localizeMessage(...args);
  }

  localize(...args) {
    return this.localeData.localize(...args);
  }

  hasPermission(perm) {
    // If the permission is a "manifest property" permission, we check if the extension
    // does have the required property in its manifest.
    let manifest_ = "manifest:";
    if (perm.startsWith(manifest_)) {
      // Handle nested "manifest property" permission (e.g. as in "manifest:property.nested").
      let value = this.manifest;
      for (let prop of perm.substr(manifest_.length).split(".")) {
        if (!value) {
          break;
        }
        value = value[prop];
      }

      return value != null;
    }
    return this.permissions.has(perm);
  }

  trackBlockedParsingDocument(doc) {
    this.blockedParsingDocuments.add(doc);
  }

  untrackBlockedParsingDocument(doc) {
    this.blockedParsingDocuments.delete(doc);
  }

  hasContextBlockedParsingDocument(extContext) {
    return this.blockedParsingDocuments.has(extContext.contentWindow?.document);
  }
}

/**
 * An object that runs an remote implementation of an API.
 */
export class ProxyAPIImplementation extends SchemaAPIInterface {
  /**
   * @param {string} namespace The full path to the namespace that contains the
   *     `name` member. This may contain dots, e.g. "storage.local".
   * @param {string} name The name of the method or property.
   * @param {ChildAPIManager} childApiManager The owner of this implementation.
   * @param {boolean} alreadyLogged Whether the child already logged the event.
   */
  constructor(namespace, name, childApiManager, alreadyLogged = false) {
    super();
    this.path = `${namespace}.${name}`;
    this.childApiManager = childApiManager;
    this.alreadyLogged = alreadyLogged;
  }

  revoke() {
    let map = this.childApiManager.listeners.get(this.path);
    for (let listener of map.listeners.keys()) {
      this.removeListener(listener);
    }

    this.path = null;
    this.childApiManager = null;
  }

  callFunctionNoReturn(args) {
    this.childApiManager.callParentFunctionNoReturn(this.path, args);
  }

  callAsyncFunction(args, callback, requireUserInput) {
    const context = this.childApiManager.context;
    const isHandlingUserInput =
      context.contentWindow?.windowUtils?.isHandlingUserInput;
    if (requireUserInput) {
      if (!isHandlingUserInput) {
        let err = new context.cloneScope.Error(
          `${this.path} may only be called from a user input handler`
        );
        return context.wrapPromise(Promise.reject(err), callback);
      }
    }
    return this.childApiManager.callParentAsyncFunction(
      this.path,
      args,
      callback,
      {
        alreadyLogged: this.alreadyLogged,
        isHandlingUserInput,
      }
    );
  }

  addListener(listener, args) {
    let map = this.childApiManager.listeners.get(this.path);

    if (map.listeners.has(listener)) {
      // TODO: Called with different args?
      return;
    }

    let id = getUniqueId();

    map.ids.set(id, listener);
    map.listeners.set(listener, id);

    this.childApiManager.conduit.sendAddListener({
      childId: this.childApiManager.id,
      listenerId: id,
      path: this.path,
      args,
      alreadyLogged: this.alreadyLogged,
    });
  }

  removeListener(listener) {
    let map = this.childApiManager.listeners.get(this.path);

    if (!map.listeners.has(listener)) {
      return;
    }

    let id = map.listeners.get(listener);
    map.listeners.delete(listener);
    map.ids.delete(id);
    map.removedIds.add(id);

    this.childApiManager.conduit.sendRemoveListener({
      childId: this.childApiManager.id,
      listenerId: id,
      path: this.path,
      alreadyLogged: this.alreadyLogged,
    });
  }

  hasListener(listener) {
    let map = this.childApiManager.listeners.get(this.path);
    return map.listeners.has(listener);
  }
}

export class ChildLocalAPIImplementation extends LocalAPIImplementation {
  constructor(pathObj, namespace, name, childApiManager) {
    super(pathObj, name, childApiManager.context);
    this.childApiManagerId = childApiManager.id;
    this.fullname = `${namespace}.${name}`;
  }

  /**
   * Call the given function and also log the call as appropriate
   * (i.e., with activity logging and/or profiler markers)
   *
   * @param {Function} callable The actual implementation to invoke.
   * @param {Array} args Arguments to the function call.
   * @returns {any} The return result of callable.
   */
  callAndLog(callable, args) {
    this.context.logActivity("api_call", this.fullname, { args });
    let start = Cu.now();
    try {
      return callable();
    } finally {
      ChromeUtils.addProfilerMarker(
        "ExtensionChild",
        { startTime: start },
        `${this.context.extension.id}, api_call: ${this.fullname}`
      );
    }
  }

  callFunction(args) {
    return this.callAndLog(() => super.callFunction(args), args);
  }

  callFunctionNoReturn(args) {
    return this.callAndLog(() => super.callFunctionNoReturn(args), args);
  }

  callAsyncFunction(args, callback, requireUserInput) {
    return this.callAndLog(
      () => super.callAsyncFunction(args, callback, requireUserInput),
      args
    );
  }
}

// We create one instance of this class for every extension context that
// needs to use remote APIs. It uses the the JSWindowActor and
// JSProcessActor Conduits actors (see ConduitsChild.sys.mjs) to communicate
// with the ParentAPIManager singleton in ExtensionParent.sys.mjs.
// It handles asynchronous function calls as well as event listeners.
export class ChildAPIManager {
  constructor(context, messageManager, localAPICan, contextData) {
    this.context = context;
    this.messageManager = messageManager;
    this.url = contextData.url;

    // The root namespace of all locally implemented APIs. If an extension calls
    // an API that does not exist in this object, then the implementation is
    // delegated to the ParentAPIManager.
    this.localApis = localAPICan.root;
    this.apiCan = localAPICan;
    this.schema = this.apiCan.apiManager.schema;

    this.id = `${context.extension.id}.${context.contextId}`;

    this.conduit = context.openConduit(this, {
      childId: this.id,
      send: [
        "CreateProxyContext",
        "ContextLoaded",
        "APICall",
        "AddListener",
        "RemoveListener",
      ],
      recv: ["CallResult", "RunListener", "StreamFilterSuspendCancel"],
    });

    this.conduit.sendCreateProxyContext({
      childId: this.id,
      extensionId: context.extension.id,
      principal: context.principal,
      ...contextData,
    });

    this.listeners = new DefaultMap(() => ({
      ids: new Map(),
      listeners: new Map(),
      removedIds: new LimitedSet(10),
    }));

    // Map[callId -> Deferred]
    this.callPromises = new Map();

    this.permissionsChangedCallbacks = new Set();
    this.updatePermissions = null;
    if (this.context.extension.optionalPermissions.length) {
      this.updatePermissions = () => {
        for (let callback of this.permissionsChangedCallbacks) {
          try {
            callback();
          } catch (err) {
            Cu.reportError(err);
          }
        }
      };
      this.context.extension.on("update-permissions", this.updatePermissions);
    }
  }

  inject(obj) {
    this.schema.inject(obj, this);
  }

  recvCallResult(data) {
    let deferred = this.callPromises.get(data.callId);
    this.callPromises.delete(data.callId);
    if ("error" in data) {
      deferred.reject(data.error);
    } else {
      let result = data.result.deserialize(this.context.cloneScope);

      deferred.resolve(new NoCloneSpreadArgs(result));
    }
  }

  recvRunListener(data) {
    let map = this.listeners.get(data.path);
    let listener = map.ids.get(data.listenerId);

    if (listener) {
      if (!this.context.active) {
        Services.console.logStringMessage(
          `Ignored listener for inactive context at childId=${data.childId} path=${data.path} listenerId=${data.listenerId}\n`
        );
        return;
      }

      let args = data.args.deserialize(this.context.cloneScope);
      let fire = () => this.context.applySafeWithoutClone(listener, args);
      return Promise.resolve(
        data.handlingUserInput
          ? withHandlingUserInput(this.context.contentWindow, fire)
          : fire()
      ).then(result => {
        if (result !== undefined) {
          return new StructuredCloneHolder(
            `ChildAPIManager/${this.context.extension.id}/${data.path}`,
            null,
            result,
            this.context.cloneScope
          );
        }
        return result;
      });
    }
    if (!map.removedIds.has(data.listenerId)) {
      Services.console.logStringMessage(
        `Unknown listener at childId=${data.childId} path=${data.path} listenerId=${data.listenerId}\n`
      );
    }
  }

  async recvStreamFilterSuspendCancel() {
    const promise = this.context.extension.emitLocalWithResult(
      "internal:stream-filter-suspend-cancel"
    );
    // if all listeners throws emitLocalWithResult returns undefined.
    if (!promise) {
      return false;
    }

    return promise.then(results =>
      results.some(hasActiveStreamFilter => hasActiveStreamFilter === true)
    );
  }

  /**
   * Call a function in the parent process and ignores its return value.
   *
   * @param {string} path The full name of the method, e.g. "tabs.create".
   * @param {Array} args The parameters for the function.
   */
  callParentFunctionNoReturn(path, args) {
    this.conduit.sendAPICall({ childId: this.id, path, args });
  }

  /**
   * Calls a function in the parent process and returns its result
   * asynchronously.
   *
   * @param {string} path The full name of the method, e.g. "tabs.create".
   * @param {Array} args The parameters for the function.
   * @param {callback} [callback] The callback to be called when the
   *      function completes.
   * @param {object} [options] Extra options.
   * @returns {Promise|undefined} Must be void if `callback` is set, and a
   *     promise otherwise. The promise is resolved when the function completes.
   */
  callParentAsyncFunction(path, args, callback, options = {}) {
    let callId = getUniqueId();
    let deferred = Promise.withResolvers();
    this.callPromises.set(callId, deferred);

    let {
      // Any child api that calls into a parent function will have already
      // logged the api_call.  Flag it so the parent doesn't log again.
      alreadyLogged = true,
      // Propagating the isHAndlingUserInput flag to the API call handler
      // executed on the parent process side.
      isHandlingUserInput = false,
    } = options;

    // TODO: conduit.queryAPICall()
    this.conduit.sendAPICall({
      childId: this.id,
      callId,
      path,
      args,
      options: { alreadyLogged, isHandlingUserInput },
    });
    return this.context.wrapPromise(deferred.promise, callback);
  }

  /**
   * Create a proxy for an event in the parent process. The returned event
   * object shares its internal state with other instances. For instance, if
   * `removeListener` is used on a listener that was added on another object
   * through `addListener`, then the event is unregistered.
   *
   * @param {string} path The full name of the event, e.g. "tabs.onCreated".
   * @returns {object} An object with the addListener, removeListener and
   *   hasListener methods. See SchemaAPIInterface for documentation.
   */
  getParentEvent(path) {
    let parts = path.split(".");

    let name = parts.pop();
    let namespace = parts.join(".");

    let impl = new ProxyAPIImplementation(namespace, name, this, true);
    return {
      addListener: (listener, ...args) => impl.addListener(listener, args),
      removeListener: listener => impl.removeListener(listener),
      hasListener: listener => impl.hasListener(listener),
    };
  }

  close() {
    // Reports CONDUIT_CLOSED on the parent side.
    this.conduit.close();

    if (this.updatePermissions) {
      this.context.extension.off("update-permissions", this.updatePermissions);
    }
  }

  get cloneScope() {
    return this.context.cloneScope;
  }

  get principal() {
    return this.context.principal;
  }

  get manifestVersion() {
    return this.context.manifestVersion;
  }

  shouldInject(namespace, name, allowedContexts) {
    // Do not generate content script APIs, unless explicitly allowed.
    if (
      this.context.envType === "content_child" &&
      !allowedContexts.includes("content")
    ) {
      return false;
    }

    // Do not generate devtools APIs, unless explicitly allowed.
    if (
      this.context.envType === "devtools_child" &&
      !allowedContexts.includes("devtools")
    ) {
      return false;
    }

    // Do not generate devtools APIs, unless explicitly allowed.
    if (
      this.context.envType !== "devtools_child" &&
      allowedContexts.includes("devtools_only")
    ) {
      return false;
    }

    // Do not generate content_only APIs, unless explicitly allowed.
    if (
      this.context.envType !== "content_child" &&
      allowedContexts.includes("content_only")
    ) {
      return false;
    }

    return true;
  }

  getImplementation(namespace, name) {
    this.apiCan.findAPIPath(`${namespace}.${name}`);
    let obj = this.apiCan.findAPIPath(namespace);

    if (obj && name in obj) {
      return new ChildLocalAPIImplementation(obj, namespace, name, this);
    }

    return this.getFallbackImplementation(namespace, name);
  }

  getFallbackImplementation(namespace, name) {
    // No local API found, defer implementation to the parent.
    return new ProxyAPIImplementation(namespace, name, this);
  }

  hasPermission(permission) {
    return this.context.extension.hasPermission(permission);
  }

  isPermissionRevokable(permission) {
    return this.context.extension.optionalPermissions.includes(permission);
  }

  setPermissionsChangedCallback(callback) {
    this.permissionsChangedCallbacks.add(callback);
  }
}
PK
!<����+modules/ExtensionChildDevToolsUtils.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * @file
 * This module contains utilities for interacting with DevTools
 * from the child process.
 */

import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs";

// Create a variable to hold the cached ThemeChangeObserver which does not
// get created until a devtools context has been created.
let themeChangeObserver;

/**
 * An observer that watches for changes to the devtools theme and provides
 * that information to the devtools.panels.themeName API property, as well as
 * emits events for the devtools.panels.onThemeChanged event. It also caches
 * the current value of devtools.themeName.
 */
class ThemeChangeObserver extends EventEmitter {
  constructor(themeName, onDestroyed) {
    super();
    this.themeName = themeName;
    this.onDestroyed = onDestroyed;
    this.contexts = new Set();

    Services.cpmm.addMessageListener("Extension:DevToolsThemeChanged", this);
  }

  addContext(context) {
    if (this.contexts.has(context)) {
      throw new Error(
        "addContext on the ThemeChangeObserver was called more than once" +
          " for the context."
      );
    }

    context.callOnClose({
      close: () => this.onContextClosed(context),
    });

    this.contexts.add(context);
  }

  onContextClosed(context) {
    this.contexts.delete(context);

    if (this.contexts.size === 0) {
      this.destroy();
    }
  }

  onThemeChanged(themeName) {
    // Update the cached themeName and emit an event for the API.
    this.themeName = themeName;
    this.emit("themeChanged", themeName);
  }

  receiveMessage({ name, data }) {
    if (name === "Extension:DevToolsThemeChanged") {
      this.onThemeChanged(data.themeName);
    }
  }

  destroy() {
    Services.cpmm.removeMessageListener("Extension:DevToolsThemeChanged", this);
    this.onDestroyed();
    this.onDestroyed = null;
    this.contexts.clear();
    this.contexts = null;
  }
}

export var ExtensionChildDevToolsUtils = {
  /**
   * Creates an cached instance of the ThemeChangeObserver class and
   * initializes it with the current themeName. This cached instance is
   * destroyed when all of the contexts added to it are closed.
   *
   * @param {string} themeName The name of the current devtools theme.
   * @param {import("ExtensionPageChild.sys.mjs").DevToolsContextChild} context
   *        The newly created devtools page context.
   */
  initThemeChangeObserver(themeName, context) {
    if (!themeChangeObserver) {
      themeChangeObserver = new ThemeChangeObserver(themeName, function () {
        themeChangeObserver = null;
      });
    }
    themeChangeObserver.addContext(context);
  },

  /**
   * Returns the cached instance of ThemeChangeObserver.
   *
   * @returns {ThemeChangeObserver} The cached instance of ThemeChangeObserver.
   */
  getThemeChangeObserver() {
    if (!themeChangeObserver) {
      throw new Error(
        "A ThemeChangeObserver must be created before being retrieved."
      );
    }
    return themeChangeObserver;
  },
};
PK
!<� v vmodules/ExtensionCommon.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This module contains utilities and base classes for logic which is
 * common between the parent and child process, and in particular
 * between ExtensionParent.sys.mjs and ExtensionChild.sys.mjs.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

/** @type {Lazy} */
const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ConsoleAPI: "resource://gre/modules/Console.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
  SchemaRoot: "resource://gre/modules/Schemas.sys.mjs",
  Schemas: "resource://gre/modules/Schemas.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "styleSheetService",
  "@mozilla.org/content/style-sheet-service;1",
  "nsIStyleSheetService"
);

const ScriptError = Components.Constructor(
  "@mozilla.org/scripterror;1",
  "nsIScriptError",
  "initWithWindowID"
);

import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

var {
  DefaultMap,
  DefaultWeakMap,
  ExtensionError,
  filterStack,
  getInnerWindowID,
  getUniqueId,
} = ExtensionUtils;

function getConsole() {
  return new lazy.ConsoleAPI({
    maxLogLevelPref: "extensions.webextensions.log.level",
    prefix: "WebExtensions",
  });
}

// Run a function and report exceptions.
function runSafeSyncWithoutClone(f, ...args) {
  try {
    return f(...args);
  } catch (e) {
    // This method is called with `this` unbound and it doesn't have
    // access to a BaseContext instance and so we can't check if `e`
    // is an instance of the extension context's Error constructor
    // (like we do in BaseContext applySafeWithoutClone method).
    dump(
      `Extension error: ${e} ${e?.fileName} ${
        e?.lineNumber
      }\n[[Exception stack\n${
        e?.stack ? filterStack(e) : undefined
      }Current stack\n${filterStack(Error())}]]\n`
    );
    Cu.reportError(e);
  }
}

// Return true if the given value is an instance of the given
// native type.
function instanceOf(value, type) {
  return (
    value &&
    typeof value === "object" &&
    ChromeUtils.getClassName(value) === type
  );
}

/**
 * Convert any of several different representations of a date/time to a Date object.
 * Accepts several formats:
 * a Date object, an ISO8601 string, or a number of milliseconds since the epoch as
 * either a number or a string.
 *
 * @param {Date|string|number} date
 *      The date to convert.
 * @returns {Date}
 *      A Date object
 */
function normalizeTime(date) {
  // Of all the formats we accept the "number of milliseconds since the epoch as a string"
  // is an outlier, everything else can just be passed directly to the Date constructor.
  return new Date(
    typeof date == "string" && /^\d+$/.test(date) ? parseInt(date, 10) : date
  );
}

function withHandlingUserInput(window, callable) {
  let handle = window.windowUtils.setHandlingUserInput(true);
  try {
    return callable();
  } finally {
    handle.destruct();
  }
}

/**
 * Defines a lazy getter for the given property on the given object. The
 * first time the property is accessed, the return value of the getter
 * is defined on the current `this` object with the given property name.
 * Importantly, this means that a lazy getter defined on an object
 * prototype will be invoked separately for each object instance that
 * it's accessed on.
 *
 * Note: for better type inference, prefer redefineGetter() below.
 *
 * @param {object} object
 *        The prototype object on which to define the getter.
 * @param {string | symbol} prop
 *        The property name for which to define the getter.
 * @param {callback} getter
 *        The function to call in order to generate the final property
 *        value.
 */
function defineLazyGetter(object, prop, getter) {
  Object.defineProperty(object, prop, {
    enumerable: true,
    configurable: true,
    get() {
      return redefineGetter(this, prop, getter.call(this), true);
    },
    set(value) {
      redefineGetter(this, prop, value, true);
    },
  });
}

/**
 * A more type-inference friendly version of defineLazyGetter() above.
 * Call it from a real getter (and setter) for your class or object.
 * On first run, it will redefine the property with the final value.
 *
 * @template Value
 * @param {object} object
 * @param {string | symbol} key
 * @param {Value} value
 * @returns {Value}
 */
function redefineGetter(object, key, value, writable = false) {
  Object.defineProperty(object, key, {
    enumerable: true,
    configurable: true,
    writable,
    value,
  });
  return value;
}

function checkLoadURI(uri, principal, options) {
  let ssm = Services.scriptSecurityManager;

  let flags = ssm.STANDARD;
  if (!options.allowScript) {
    flags |= ssm.DISALLOW_SCRIPT;
  }
  if (!options.allowInheritsPrincipal) {
    flags |= ssm.DISALLOW_INHERIT_PRINCIPAL;
  }
  if (options.dontReportErrors) {
    flags |= ssm.DONT_REPORT_ERRORS;
  }

  try {
    ssm.checkLoadURIWithPrincipal(principal, uri, flags);
  } catch (e) {
    return false;
  }
  return true;
}

function checkLoadURL(url, principal, options) {
  try {
    return checkLoadURI(Services.io.newURI(url), principal, options);
  } catch (e) {
    return false; // newURI threw.
  }
}

function makeWidgetId(id) {
  id = id.toLowerCase();
  // FIXME: This allows for collisions.
  return id.replace(/[^a-z0-9_-]/g, "_");
}

/**
 * A sentinel class to indicate that an array of values should be
 * treated as an array when used as a promise resolution value, but as a
 * spread expression (...args) when passed to a callback.
 */
class SpreadArgs extends Array {
  constructor(args) {
    super();
    this.push(...args);
  }
}

/**
 * Like SpreadArgs, but also indicates that the array values already
 * belong to the target compartment, and should not be cloned before
 * being passed.
 *
 * The `unwrappedValues` property contains an Array object which belongs
 * to the target compartment, and contains the same unwrapped values
 * passed the NoCloneSpreadArgs constructor.
 */
class NoCloneSpreadArgs {
  constructor(args) {
    this.unwrappedValues = args;
  }

  [Symbol.iterator]() {
    return this.unwrappedValues[Symbol.iterator]();
  }
}

const LISTENERS = Symbol("listeners");
const ONCE_MAP = Symbol("onceMap");

export class EventEmitter {
  constructor() {
    this[LISTENERS] = new Map();
    this[ONCE_MAP] = new WeakMap();
  }

  /**
   * Checks whether there is some listener for the given event.
   *
   * @param {string} event
   *       The name of the event to listen for.
   * @returns {boolean}
   */
  has(event) {
    return this[LISTENERS].has(event);
  }

  /**
   * Adds the given function as a listener for the given event.
   *
   * The listener function may optionally return a Promise which
   * resolves when it has completed all operations which event
   * dispatchers may need to block on.
   *
   * @param {string} event
   *       The name of the event to listen for.
   * @param {function(string, ...any): any} listener
   *        The listener to call when events are emitted.
   */
  on(event, listener) {
    let listeners = this[LISTENERS].get(event);
    if (!listeners) {
      listeners = new Set();
      this[LISTENERS].set(event, listeners);
    }

    listeners.add(listener);
  }

  /**
   * Removes the given function as a listener for the given event.
   *
   * @param {string} event
   *       The name of the event to stop listening for.
   * @param {function(string, ...any): any} listener
   *        The listener function to remove.
   */
  off(event, listener) {
    let set = this[LISTENERS].get(event);
    if (set) {
      set.delete(listener);
      set.delete(this[ONCE_MAP].get(listener));
      if (!set.size) {
        this[LISTENERS].delete(event);
      }
    }
  }

  /**
   * Adds the given function as a listener for the given event once.
   *
   * @param {string} event
   *       The name of the event to listen for.
   * @param {function(string, ...any): any} listener
   *        The listener to call when events are emitted.
   */
  once(event, listener) {
    let wrapper = (event, ...args) => {
      this.off(event, wrapper);
      this[ONCE_MAP].delete(listener);

      return listener(event, ...args);
    };
    this[ONCE_MAP].set(listener, wrapper);

    this.on(event, wrapper);
  }

  /**
   * Triggers all listeners for the given event. If any listeners return
   * a value, returns a promise which resolves when all returned
   * promises have resolved. Otherwise, returns undefined.
   *
   * @param {string} event
   *       The name of the event to emit.
   * @param {any} args
   *        Arbitrary arguments to pass to the listener functions, after
   *        the event name.
   * @returns {Promise?}
   */
  emit(event, ...args) {
    let listeners = this[LISTENERS].get(event);

    if (listeners) {
      let promises = [];

      for (let listener of listeners) {
        try {
          let result = listener(event, ...args);
          if (result !== undefined) {
            promises.push(result);
          }
        } catch (e) {
          Cu.reportError(e);
        }
      }

      if (promises.length) {
        return Promise.all(promises);
      }
    }
  }
}

/**
 * Base class for WebExtension APIs.  Each API creates a new class
 * that inherits from this class, the derived class is instantiated
 * once for each extension that uses the API.
 */
export class ExtensionAPI extends EventEmitter {
  constructor(extension) {
    super();

    this.extension = extension;

    extension.once("shutdown", (what, isAppShutdown) => {
      if (this.onShutdown) {
        this.onShutdown(isAppShutdown);
      }
      this.extension = null;
    });
  }

  destroy() {}

  /** @param {string} _entryName */
  onManifestEntry(_entryName) {}

  /** @param {boolean} _isAppShutdown */
  onShutdown(_isAppShutdown) {}

  /** @param {BaseContext} _context */
  getAPI(_context) {
    throw new Error("Not Implemented");
  }

  /** @param {string} _id */
  static onDisable(_id) {}

  /** @param {string} _id */
  static onUninstall(_id) {}

  /**
   * @param {string} _id
   * @param {object} _manifest
   */
  static onUpdate(_id, _manifest) {}
}

/**
 * Subclass to add APIs commonly used with persistent events.
 * If a namespace uses events, it should use this subclass.
 *
 * this.apiNamespace = class extends ExtensionAPIPersistent {};
 */
class ExtensionAPIPersistent extends ExtensionAPI {
  /** @type {Record<string, callback>} */
  PERSISTENT_EVENTS;

  /**
   * Check for event entry.
   *
   * @param {string} event The event name e.g. onStateChanged
   * @returns {boolean}
   */
  hasEventRegistrar(event) {
    return (
      this.PERSISTENT_EVENTS && Object.hasOwn(this.PERSISTENT_EVENTS, event)
    );
  }

  /**
   * Get the event registration fuction
   *
   * @param {string} event The event name e.g. onStateChanged
   * @returns {Function} register is used to start the listener
   *                     register returns an object containing
   *                     a convert and unregister function.
   */
  getEventRegistrar(event) {
    if (this.hasEventRegistrar(event)) {
      return this.PERSISTENT_EVENTS[event].bind(this);
    }
  }

  /**
   * Used when instantiating an EventManager instance to register the listener.
   *
   * @param {object}      options         Options used for event registration
   * @param {BaseContext} options.context Extension Context passed when creating an EventManager instance.
   * @param {string}      options.event   The eAPI vent name.
   * @param {Function}    options.fire    The function passed to the listener to fire the event.
   * @param {Array<any>}  params          An optional array of parameters received along with the
   *                                      addListener request.
   * @returns {Function}                  The unregister function used in the EventManager.
   */
  registerEventListener(options, params) {
    const apiRegistar = this.getEventRegistrar(options.event);
    return apiRegistar?.(options, params).unregister;
  }

  /**
   * Used to prime a listener for when the background script is not running.
   *
   * @param {string} event The event name e.g. onStateChanged or captiveURL.onChange.
   * @param {Function} fire The function passed to the listener to fire the event.
   * @param {Array} params Params passed to the event listener.
   * @param {boolean} isInStartup unused here but passed for subclass use.
   * @returns {object} the unregister and convert functions used in the EventManager.
   */
  primeListener(event, fire, params, isInStartup) {
    const apiRegistar = this.getEventRegistrar(event);
    return apiRegistar?.({ fire, isInStartup }, params);
  }
}

/**
 * This class contains the information we have about an individual
 * extension.  It is never instantiated directly, instead subclasses
 * for each type of process extend this class and add members that are
 * relevant for that process.
 *
 * @abstract
 */
export class BaseContext {
  /** @type {boolean} */
  isTopContext;
  /** @type {string} */
  viewType;

  constructor(envType, extension) {
    this.envType = envType;
    this.onClose = new Set();
    this.checkedLastError = false;
    this._lastError = null;
    this.contextId = getUniqueId();
    this.unloaded = false;
    this.extension = extension;
    this.manifestVersion = extension.manifestVersion;
    this.jsonSandbox = null;
    this.active = true;
    this.incognito = null;
    this.messageManager = null;
    this.contentWindow = null;
    this.innerWindowID = 0;

    // These two properties are assigned in ContentScriptContextChild subclass
    // to keep a copy of the content script sandbox Error and Promise globals
    // (which are used by the WebExtensions internals) before any extension
    // content script code had any chance to redefine them.
    this.cloneScopeError = null;
    this.cloneScopePromise = null;
  }

  get isProxyContextParent() {
    return false;
  }

  get Error() {
    // Return the copy stored in the context instance (when the context is an instance of
    // ContentScriptContextChild or the global from extension page window otherwise).
    return this.cloneScopeError || this.cloneScope.Error;
  }

  get Promise() {
    // Return the copy stored in the context instance (when the context is an instance of
    // ContentScriptContextChild or the global from extension page window otherwise).
    return this.cloneScopePromise || this.cloneScope.Promise;
  }

  get privateBrowsingAllowed() {
    return this.extension.privateBrowsingAllowed;
  }

  get isBackgroundContext() {
    if (this.viewType === "background") {
      if (this.isProxyContextParent) {
        return !!this.isTopContext; // Set in ExtensionPageContextParent.
      }
      const { contentWindow } = this;
      return !!contentWindow && contentWindow.top === contentWindow;
    }
    return this.viewType === "background_worker";
  }

  /**
   * Whether the extension context is using the WebIDL bindings for the
   * WebExtensions APIs.
   * To be overridden in subclasses (e.g. WorkerContextChild) and to be
   * optionally used in ExtensionAPI classes to customize the behavior of the
   * API when the calls to the extension API are originated from the WebIDL
   * bindings.
   */
  get useWebIDLBindings() {
    return false;
  }

  canAccessWindow(window) {
    return this.extension.canAccessWindow(window);
  }

  canAccessContainer(userContextId) {
    return this.extension.canAccessContainer(userContextId);
  }

  /**
   * Opens a conduit linked to this context, populating related address fields.
   * Only available in child contexts with an associated contentWindow.
   *
   * @type {ConduitGen}
   */
  openConduit(subject, address) {
    let wgc = this.contentWindow.windowGlobalChild;
    let conduit = wgc.getActor("Conduits").openConduit(subject, {
      id: subject.id || getUniqueId(),
      extensionId: this.extension.id,
      envType: this.envType,
      ...address,
    });
    this.callOnClose(conduit);
    conduit.setCloseCallback(() => {
      this.forgetOnClose(conduit);
    });
    return conduit;
  }

  setContentWindow(contentWindow) {
    if (!this.canAccessWindow(contentWindow)) {
      throw new Error(
        "BaseContext attempted to load when extension is not allowed due to incognito settings."
      );
    }

    this.innerWindowID = getInnerWindowID(contentWindow);
    this.messageManager = contentWindow.docShell.messageManager;

    if (this.incognito == null) {
      this.incognito =
        lazy.PrivateBrowsingUtils.isContentWindowPrivate(contentWindow);
    }

    let wgc = contentWindow.windowGlobalChild;
    Object.defineProperty(this, "active", {
      configurable: true,
      enumerable: true,
      get: () => wgc.isCurrentGlobal && !wgc.windowContext.isInBFCache,
    });
    Object.defineProperty(this, "contentWindow", {
      configurable: true,
      enumerable: true,
      get: () => (this.active ? wgc.browsingContext.window : null),
    });
    this.callOnClose({
      close: () => {
        // Allow other "close" handlers to use these properties, until the next tick.
        Promise.resolve().then(() => {
          Object.defineProperty(this, "contentWindow", { value: null });
          Object.defineProperty(this, "active", { value: false });
          wgc = null;
        });
      },
    });
  }

  // All child contexts must implement logActivity.  This is handled if the child
  // context subclasses ExtensionBaseContextChild.  ProxyContextParent overrides
  // this with a noop for parent contexts.
  logActivity(_type, _name, _data) {
    throw new Error(`Not implemented for ${this.envType}`);
  }

  /** @type {object} */
  get cloneScope() {
    throw new Error("Not implemented");
  }

  /** @type {nsIPrincipal} */
  get principal() {
    throw new Error("Not implemented");
  }

  runSafe(callback, ...args) {
    return this.applySafe(callback, args);
  }

  runSafeWithoutClone(callback, ...args) {
    return this.applySafeWithoutClone(callback, args);
  }

  applySafe(callback, args, caller) {
    if (this.unloaded) {
      Cu.reportError("context.runSafe called after context unloaded", caller);
    } else if (!this.active) {
      Cu.reportError(
        "context.runSafe called while context is inactive",
        caller
      );
    } else {
      try {
        let { cloneScope } = this;
        args = args.map(arg => Cu.cloneInto(arg, cloneScope));
      } catch (e) {
        Cu.reportError(e);
        dump(
          `runSafe failure: cloning into ${
            this.cloneScope
          }: ${e}\n\n${filterStack(Error())}`
        );
      }

      return this.applySafeWithoutClone(callback, args, caller);
    }
  }

  applySafeWithoutClone(callback, args, caller) {
    if (this.unloaded) {
      Cu.reportError(
        "context.runSafeWithoutClone called after context unloaded",
        caller
      );
    } else if (!this.active) {
      Cu.reportError(
        "context.runSafeWithoutClone called while context is inactive",
        caller
      );
    } else {
      try {
        return Reflect.apply(callback, null, args);
      } catch (e) {
        // An extension listener may as well be throwing an object that isn't
        // an instance of Error, in that case we have to use fallbacks for the
        // error message, fileName, lineNumber and columnNumber properties.
        const isError = e instanceof this.Error;
        let message;
        let fileName;
        let lineNumber;
        let columnNumber;

        if (isError) {
          message = `${e.name}: ${e.message}`;
          lineNumber = e.lineNumber;
          columnNumber = e.columnNumber;
          fileName = e.fileName;
        } else {
          message = `uncaught exception: ${e}`;

          try {
            // TODO(Bug 1810582): the following fallback logic may go away once
            // we introduced a better way to capture and log the exception in
            // the right window and in all cases (included when the extension
            // code is raising undefined or an object that isn't an instance of
            // the Error constructor).
            //
            // Fallbacks for the error location:
            // - the callback location if it is registered directly from the
            //   extension code (and not wrapped by the child/ext-APINAMe.js
            //   implementation, like e.g. browser.storage, browser.devtools.network
            //   are doing and browser.menus).
            // - if the location of the extension callback is not directly
            //   available (e.g. browser.storage onChanged events, and similarly
            //   for browser.devtools.network and browser.menus events):
            //   - the extension page url if the context is an extension page
            //   - the extension base url if the context is a content script
            const cbLoc = Cu.getFunctionSourceLocation(callback);
            fileName = cbLoc.filename;
            lineNumber = cbLoc.lineNumber ?? lineNumber;

            const extBaseUrl = this.extension.baseURI.resolve("/");
            if (fileName.startsWith(extBaseUrl)) {
              fileName = cbLoc.filename;
              lineNumber = cbLoc.lineNumber ?? lineNumber;
            } else {
              fileName = this.contentWindow?.location?.href;
              if (!fileName || !fileName.startsWith(extBaseUrl)) {
                fileName = extBaseUrl;
              }
            }
          } catch {
            // Ignore errors on retrieving the callback source location.
          }
        }

        dump(
          `Extension error: ${message} ${fileName} ${lineNumber}\n[[Exception stack\n${
            isError ? filterStack(e) : undefined
          }Current stack\n${filterStack(Error())}]]\n`
        );

        // If the error is coming from an extension context associated
        // to a window (e.g. an extension page or extension content script).
        //
        // TODO(Bug 1810574): for the background service worker we will need to do
        // something similar, but not tied to the innerWindowID because there
        // wouldn't be one set for extension contexts related to the
        // background service worker.
        //
        // TODO(Bug 1810582): change the error associated to the innerWindowID to also
        // include a full stack from the original error.
        if (!this.isProxyContextParent && this.contentWindow) {
          Services.console.logMessage(
            new ScriptError(
              message,
              fileName,
              lineNumber,
              columnNumber,
              Ci.nsIScriptError.errorFlag,
              "content javascript",
              this.innerWindowID
            )
          );
        }
        // Also report the original error object (because it also includes
        // the full error stack).
        Cu.reportError(e);
      }
    }
  }

  checkLoadURL(url, options = {}) {
    // As an optimization, f the URL starts with the extension's base URL,
    // don't do any further checks. It's always allowed to load it.
    if (url.startsWith(this.extension.baseURL)) {
      return true;
    }

    return checkLoadURL(url, this.principal, options);
  }

  /**
   * Safely call JSON.stringify() on an object that comes from an
   * extension.
   *
   * @param {[any, callback?, number?]} args for JSON.stringify()
   * @returns {string} The stringified representation of obj
   */
  jsonStringify(...args) {
    if (!this.jsonSandbox) {
      this.jsonSandbox = Cu.Sandbox(this.principal, {
        sameZoneAs: this.cloneScope,
        wantXrays: false,
      });
    }

    return Cu.waiveXrays(this.jsonSandbox.JSON).stringify(...args);
  }

  callOnClose(obj) {
    this.onClose.add(obj);
  }

  forgetOnClose(obj) {
    this.onClose.delete(obj);
  }

  get lastError() {
    this.checkedLastError = true;
    return this._lastError;
  }

  set lastError(val) {
    this.checkedLastError = false;
    this._lastError = val;
  }

  /**
   * Normalizes the given error object for use by the target scope. If
   * the target is an error object which belongs to that scope, it is
   * returned as-is. If it is an ordinary object with a `message`
   * property, it is converted into an error belonging to the target
   * scope. If it is an Error object which does *not* belong to the
   * clone scope, it is reported, and converted to an unexpected
   * exception error.
   *
   * @param {Error|object} error
   * @param {nsIStackFrame?} [caller]
   * @returns {Error}
   */
  normalizeError(error, caller) {
    if (error instanceof this.Error) {
      return error;
    }
    let message, fileName;
    if (error && typeof error === "object") {
      const isPlain = ChromeUtils.getClassName(error) === "Object";
      if (isPlain && error.mozWebExtLocation) {
        caller = error.mozWebExtLocation;
      }
      if (isPlain && caller && (error.mozWebExtLocation || !error.fileName)) {
        caller = Cu.cloneInto(caller, this.cloneScope);
        return ChromeUtils.createError(error.message, caller);
      }

      if (
        isPlain ||
        error instanceof ExtensionError ||
        this.principal.subsumes(Cu.getObjectPrincipal(error))
      ) {
        message = error.message;
        fileName = error.fileName;
      }
    }

    if (!message) {
      Cu.reportError(error);
      message = "An unexpected error occurred";
    }
    return new this.Error(message, fileName);
  }

  /**
   * Sets the value of `.lastError` to `error`, calls the given
   * callback, and reports an error if the value has not been checked
   * when the callback returns.
   *
   * @param {object} error An object with a `message` property. May
   *     optionally be an `Error` object belonging to the target scope.
   * @param {nsIStackFrame?} caller
   *        The optional caller frame which triggered this callback, to be used
   *        in error reporting.
   * @param {Function} callback The callback to call.
   * @returns {*} The return value of callback.
   */
  withLastError(error, caller, callback) {
    this.lastError = this.normalizeError(error);
    try {
      return callback();
    } finally {
      if (!this.checkedLastError) {
        Cu.reportError(`Unchecked lastError value: ${this.lastError}`, caller);
      }
      this.lastError = null;
    }
  }

  /**
   * Captures the most recent stack frame which belongs to the extension.
   *
   * @returns {nsIStackFrame?}
   */
  getCaller() {
    return ChromeUtils.getCallerLocation(this.principal);
  }

  /**
   * Wraps the given promise so it can be safely returned to extension
   * code in this context.
   *
   * If `callback` is provided, however, it is used as a completion
   * function for the promise, and no promise is returned. In this case,
   * the callback is called when the promise resolves or rejects. In the
   * latter case, `lastError` is set to the rejection value, and the
   * callback function must check `browser.runtime.lastError` or
   * `extension.runtime.lastError` in order to prevent it being reported
   * to the console.
   *
   * @param {Promise} promise The promise with which to wrap the
   *     callback. May resolve to a `SpreadArgs` instance, in which case
   *     each element will be used as a separate argument.
   *
   *     Unless the promise object belongs to the cloneScope global, its
   *     resolution value is cloned into cloneScope prior to calling the
   *     `callback` function or resolving the wrapped promise.
   *
   * @param {Function} [callback] The callback function to wrap
   *
   * @returns {Promise|undefined} If callback is null, a promise object
   *     belonging to the target scope. Otherwise, undefined.
   */
  wrapPromise(promise, callback = null) {
    let caller = this.getCaller();
    let applySafe = this.applySafe.bind(this);
    if (Cu.getGlobalForObject(promise) === this.cloneScope) {
      applySafe = this.applySafeWithoutClone.bind(this);
    }

    if (callback) {
      promise.then(
        args => {
          if (this.unloaded) {
            Cu.reportError(`Promise resolved after context unloaded\n`, caller);
          } else if (!this.active) {
            Cu.reportError(
              `Promise resolved while context is inactive\n`,
              caller
            );
          } else if (args instanceof NoCloneSpreadArgs) {
            this.applySafeWithoutClone(callback, args.unwrappedValues, caller);
          } else if (args instanceof SpreadArgs) {
            applySafe(callback, args, caller);
          } else {
            applySafe(callback, [args], caller);
          }
        },
        error => {
          this.withLastError(error, caller, () => {
            if (this.unloaded) {
              Cu.reportError(
                `Promise rejected after context unloaded\n`,
                caller
              );
            } else if (!this.active) {
              Cu.reportError(
                `Promise rejected while context is inactive\n`,
                caller
              );
            } else {
              this.applySafeWithoutClone(callback, [], caller);
            }
          });
        }
      );
    } else {
      return new this.Promise((resolve, reject) => {
        promise.then(
          value => {
            if (this.unloaded) {
              Cu.reportError(
                `Promise resolved after context unloaded\n`,
                caller
              );
            } else if (!this.active) {
              Cu.reportError(
                `Promise resolved while context is inactive\n`,
                caller
              );
            } else if (value instanceof NoCloneSpreadArgs) {
              let values = value.unwrappedValues;
              this.applySafeWithoutClone(
                resolve,
                values.length == 1 ? [values[0]] : [values],
                caller
              );
            } else if (value instanceof SpreadArgs) {
              applySafe(resolve, value.length == 1 ? value : [value], caller);
            } else {
              applySafe(resolve, [value], caller);
            }
          },
          value => {
            if (this.unloaded) {
              Cu.reportError(
                `Promise rejected after context unloaded: ${
                  value && value.message
                }\n`,
                caller
              );
            } else if (!this.active) {
              Cu.reportError(
                `Promise rejected while context is inactive: ${
                  value && value.message
                }\n`,
                caller
              );
            } else {
              this.applySafeWithoutClone(
                reject,
                [this.normalizeError(value, caller)],
                caller
              );
            }
          }
        );
      });
    }
  }

  unload() {
    this.unloaded = true;

    for (let obj of this.onClose) {
      obj.close();
    }
    this.onClose.clear();
  }

  /**
   * A simple proxy for unload(), for use with callOnClose().
   */
  close() {
    this.unload();
  }
}

/**
 * An object that runs the implementation of a schema API. Instantiations of
 * this interfaces are used by Schemas.sys.mjs.
 *
 * @interface
 */
export class SchemaAPIInterface {
  /**
   * Calls this as a function that returns its return value.
   *
   * @abstract
   * @param {Array} _args The parameters for the function.
   * @returns {*} The return value of the invoked function.
   */
  callFunction(_args) {
    throw new Error("Not implemented");
  }

  /**
   * Calls this as a function and ignores its return value.
   *
   * @abstract
   * @param {Array} _args The parameters for the function.
   */
  callFunctionNoReturn(_args) {
    throw new Error("Not implemented");
  }

  /**
   * Calls this as a function that completes asynchronously.
   *
   * @abstract
   * @param {Array} _args The parameters for the function.
   * @param {callback} [_callback] The callback to be called when the function
   *     completes.
   * @param {boolean} [_requireUserInput=false] If true, the function should
   *                  fail if the browser is not currently handling user input.
   * @returns {Promise|undefined} Must be void if `callback` is set, and a
   *     promise otherwise. The promise is resolved when the function completes.
   */
  callAsyncFunction(_args, _callback, _requireUserInput) {
    throw new Error("Not implemented");
  }

  /**
   * Retrieves the value of this as a property.
   *
   * @abstract
   * @returns {*} The value of the property.
   */
  getProperty() {
    throw new Error("Not implemented");
  }

  /**
   * Assigns the value to this as property.
   *
   * @abstract
   * @param {string} _value The new value of the property.
   */
  setProperty(_value) {
    throw new Error("Not implemented");
  }

  /**
   * Registers a `listener` to this as an event.
   *
   * @abstract
   * @param {Function} _listener The callback to be called when the event fires.
   * @param {Array} _args Extra parameters for EventManager.addListener.
   * @see EventManager.addListener
   */
  addListener(_listener, _args) {
    throw new Error("Not implemented");
  }

  /**
   * Checks whether `listener` is listening to this as an event.
   *
   * @abstract
   * @param {Function} _listener The event listener.
   * @returns {boolean} Whether `listener` is registered with this as an event.
   * @see EventManager.hasListener
   */
  hasListener(_listener) {
    throw new Error("Not implemented");
  }

  /**
   * Unregisters `listener` from this as an event.
   *
   * @abstract
   * @param {Function} _listener The event listener.
   * @see EventManager.removeListener
   */
  removeListener(_listener) {
    throw new Error("Not implemented");
  }

  /**
   * Revokes the implementation object, and prevents any further method
   * calls from having external effects.
   *
   * @abstract
   */
  revoke() {
    throw new Error("Not implemented");
  }
}

/**
 * An object that runs a locally implemented API.
 */
class LocalAPIImplementation extends SchemaAPIInterface {
  /**
   * Constructs an implementation of the `name` method or property of `pathObj`.
   *
   * @param {object} pathObj The object containing the member with name `name`.
   * @param {string} name The name of the implemented member.
   * @param {BaseContext} context The context in which the schema is injected.
   */
  constructor(pathObj, name, context) {
    super();
    this.pathObj = pathObj;
    this.name = name;
    this.context = context;
  }

  revoke() {
    if (this.pathObj[this.name][lazy.Schemas.REVOKE]) {
      this.pathObj[this.name][lazy.Schemas.REVOKE]();
    }

    this.pathObj = null;
    this.name = null;
    this.context = null;
  }

  callFunction(args) {
    try {
      return this.pathObj[this.name](...args);
    } catch (e) {
      throw this.context.normalizeError(e);
    }
  }

  callFunctionNoReturn(args) {
    try {
      this.pathObj[this.name](...args);
    } catch (e) {
      throw this.context.normalizeError(e);
    }
  }

  callAsyncFunction(args, callback, requireUserInput) {
    let promise;
    try {
      if (requireUserInput) {
        if (!this.context.contentWindow.windowUtils.isHandlingUserInput) {
          throw new ExtensionError(
            `${this.name} may only be called from a user input handler`
          );
        }
      }
      promise = this.pathObj[this.name](...args) || Promise.resolve();
    } catch (e) {
      promise = Promise.reject(e);
    }
    return this.context.wrapPromise(promise, callback);
  }

  getProperty() {
    return this.pathObj[this.name];
  }

  setProperty(value) {
    this.pathObj[this.name] = value;
  }

  addListener(listener, args) {
    try {
      this.pathObj[this.name].addListener.call(null, listener, ...args);
    } catch (e) {
      throw this.context.normalizeError(e);
    }
  }

  hasListener(listener) {
    return this.pathObj[this.name].hasListener.call(null, listener);
  }

  removeListener(listener) {
    this.pathObj[this.name].removeListener.call(null, listener);
  }
}

// Recursively copy properties from source to dest.
function deepCopy(dest, source) {
  for (let prop in source) {
    let desc = Object.getOwnPropertyDescriptor(source, prop);
    if (typeof desc.value == "object") {
      if (!(prop in dest)) {
        dest[prop] = {};
      }
      deepCopy(dest[prop], source[prop]);
    } else {
      Object.defineProperty(dest, prop, desc);
    }
  }
}

function getChild(map, key) {
  let child = map.children.get(key);
  if (!child) {
    child = {
      modules: new Set(),
      children: new Map(),
    };

    map.children.set(key, child);
  }
  return child;
}

function getPath(map, path) {
  for (let key of path) {
    map = getChild(map, key);
  }
  return map;
}

function mergePaths(dest, source) {
  for (let name of source.modules) {
    dest.modules.add(name);
  }

  for (let [name, child] of source.children.entries()) {
    mergePaths(getChild(dest, name), child);
  }
}

/**
 * Manages loading and accessing a set of APIs for a specific extension
 * context.
 *
 * @param {BaseContext} context
 *        The context to manage APIs for.
 * @param {SchemaAPIManager} apiManager
 *        The API manager holding the APIs to manage.
 * @param {object} root
 *        The root object into which APIs will be injected.
 */
class CanOfAPIs {
  constructor(context, apiManager, root) {
    this.context = context;
    this.scopeName = context.envType;
    this.apiManager = apiManager;
    this.root = root;

    this.apiPaths = new Map();

    this.apis = new Map();
  }

  /**
   * Synchronously loads and initializes an ExtensionAPI instance.
   *
   * @param {string} name
   *        The name of the API to load.
   */
  loadAPI(name) {
    if (this.apis.has(name)) {
      return;
    }

    let { extension } = this.context;

    let api = this.apiManager.getAPI(name, extension, this.scopeName);
    if (!api) {
      return;
    }

    this.apis.set(name, api);

    deepCopy(this.root, api.getAPI(this.context));
  }

  /**
   * Asynchronously loads and initializes an ExtensionAPI instance.
   *
   * @param {string} name
   *        The name of the API to load.
   */
  async asyncLoadAPI(name) {
    if (this.apis.has(name)) {
      return;
    }

    let { extension } = this.context;
    if (!lazy.Schemas.checkPermissions(name, extension)) {
      return;
    }

    let api = await this.apiManager.asyncGetAPI(
      name,
      extension,
      this.scopeName
    );
    // Check again, because async;
    if (this.apis.has(name)) {
      return;
    }

    this.apis.set(name, api);

    deepCopy(this.root, api.getAPI(this.context));
  }

  /**
   * Finds the API at the given path from the root object, and
   * synchronously loads the API that implements it if it has not
   * already been loaded.
   *
   * @param {string} path
   *        The "."-separated path to find.
   * @returns {*}
   */
  findAPIPath(path) {
    if (this.apiPaths.has(path)) {
      return this.apiPaths.get(path);
    }

    let obj = this.root;
    let modules = this.apiManager.modulePaths;

    let parts = path.split(".");
    for (let [i, key] of parts.entries()) {
      if (!obj) {
        return;
      }
      modules = getChild(modules, key);

      for (let name of modules.modules) {
        if (!this.apis.has(name)) {
          this.loadAPI(name);
        }
      }

      if (!(key in obj) && i < parts.length - 1) {
        obj[key] = {};
      }
      obj = obj[key];
    }

    this.apiPaths.set(path, obj);
    return obj;
  }

  /**
   * Finds the API at the given path from the root object, and
   * asynchronously loads the API that implements it if it has not
   * already been loaded.
   *
   * @param {string} path
   *        The "."-separated path to find.
   * @returns {Promise<*>}
   */
  async asyncFindAPIPath(path) {
    if (this.apiPaths.has(path)) {
      return this.apiPaths.get(path);
    }

    let obj = this.root;
    let modules = this.apiManager.modulePaths;

    let parts = path.split(".");
    for (let [i, key] of parts.entries()) {
      if (!obj) {
        return;
      }
      modules = getChild(modules, key);

      for (let name of modules.modules) {
        if (!this.apis.has(name)) {
          await this.asyncLoadAPI(name);
        }
      }

      if (!(key in obj) && i < parts.length - 1) {
        obj[key] = {};
      }

      if (typeof obj[key] === "function") {
        obj = obj[key].bind(obj);
      } else {
        obj = obj[key];
      }
    }

    this.apiPaths.set(path, obj);
    return obj;
  }
}

/**
 * @class APIModule
 * @abstract
 *
 * @property {string} url
 *       The URL of the script which contains the module's
 *       implementation. This script must define a global property
 *       matching the modules name, which must be a class constructor
 *       which inherits from {@link ExtensionAPI}.
 *
 * @property {string} schema
 *       The URL of the JSON schema which describes the module's API.
 *
 * @property {Array<string>} scopes
 *       The list of scope names into which the API may be loaded.
 *
 * @property {Array<string>} manifest
 *       The list of top-level manifest properties which will trigger
 *       the module to be loaded, and its `onManifestEntry` method to be
 *       called.
 *
 * @property {Array<string>} events
 *       The list events which will trigger the module to be loaded, and
 *       its appropriate event handler method to be called. Currently
 *       only accepts "startup".
 *
 * @property {Array<string>} permissions
 *       An optional list of permissions, any of which must be present
 *       in order for the module to load.
 *
 * @property {Array<Array<string>>} paths
 *       A list of paths from the root API object which, when accessed,
 *       will cause the API module to be instantiated and injected.
 */

/**
 * This object loads the ext-*.js scripts that define the extension API.
 *
 * This class instance is shared with the scripts that it loads, so that the
 * ext-*.js scripts and the instantiator can communicate with each other.
 */
class SchemaAPIManager extends EventEmitter {
  /**
   * @param {string} processType
   *     "main" - The main, one and only chrome browser process.
   *     "addon" - An addon process.
   *     "content" - A content process.
   *     "devtools" - A devtools process.
   * @param {import("Schemas.sys.mjs").SchemaInject} [schema]
   */
  constructor(processType, schema) {
    super();
    this.processType = processType;
    this.global = null;
    if (schema) {
      this.schema = schema;
    }

    this.modules = new Map();
    this.modulePaths = { children: new Map(), modules: new Set() };
    this.manifestKeys = new Map();
    this.eventModules = new DefaultMap(() => new Set());
    this.settingsModules = new Set();

    this._modulesJSONLoaded = false;

    this.schemaURLs = new Map();

    this.apis = new DefaultWeakMap(() => new Map());

    this._scriptScopes = [];
  }

  onStartup(extension) {
    let promises = [];
    for (let apiName of this.eventModules.get("startup")) {
      promises.push(
        extension.apiManager.asyncGetAPI(apiName, extension).then(api => {
          if (api) {
            api.onStartup();
          }
        })
      );
    }

    return Promise.all(promises);
  }

  async loadModuleJSON(urls) {
    let promises = urls.map(url => fetch(url).then(resp => resp.json()));

    return this.initModuleJSON(await Promise.all(promises));
  }

  initModuleJSON(blobs) {
    for (let json of blobs) {
      this.registerModules(json);
    }

    this._modulesJSONLoaded = true;

    return new StructuredCloneHolder("SchemaAPIManager/initModuleJSON", null, {
      modules: this.modules,
      modulePaths: this.modulePaths,
      manifestKeys: this.manifestKeys,
      eventModules: this.eventModules,
      settingsModules: this.settingsModules,
      schemaURLs: this.schemaURLs,
    });
  }

  initModuleData(moduleData) {
    if (!this._modulesJSONLoaded) {
      let data = moduleData.deserialize({}, true);

      this.modules = data.modules;
      this.modulePaths = data.modulePaths;
      this.manifestKeys = data.manifestKeys;
      this.eventModules = new DefaultMap(() => new Set(), data.eventModules);
      this.settingsModules = new Set(data.settingsModules);
      this.schemaURLs = data.schemaURLs;
    }

    this._modulesJSONLoaded = true;
  }

  /**
   * Registers a set of ExtensionAPI modules to be lazily loaded and
   * managed by this manager.
   *
   * @param {object} obj
   *        An object containing property for eacy API module to be
   *        registered. Each value should be an object implementing the
   *        APIModule interface.
   */
  registerModules(obj) {
    for (let [name, details] of Object.entries(obj)) {
      details.namespaceName = name;

      if (this.modules.has(name)) {
        throw new Error(`Module '${name}' already registered`);
      }
      this.modules.set(name, details);

      if (details.schema) {
        let content =
          details.scopes &&
          (details.scopes.includes("content_parent") ||
            details.scopes.includes("content_child"));
        this.schemaURLs.set(details.schema, { content });
      }

      for (let event of details.events || []) {
        this.eventModules.get(event).add(name);
      }

      if (details.settings) {
        this.settingsModules.add(name);
      }

      for (let key of details.manifest || []) {
        if (this.manifestKeys.has(key)) {
          throw new Error(
            `Manifest key '${key}' already registered by '${this.manifestKeys.get(
              key
            )}'`
          );
        }

        this.manifestKeys.set(key, name);
      }

      for (let path of details.paths || []) {
        getPath(this.modulePaths, path).modules.add(name);
      }
    }
  }

  /**
   * Emits an `onManifestEntry` event for the top-level manifest entry
   * on all relevant {@link ExtensionAPI} instances for the given
   * extension.
   *
   * The API modules will be synchronously loaded if they have not been
   * loaded already.
   *
   * @param {Extension} extension
   *        The extension for which to emit the events.
   * @param {string} entry
   *        The name of the top-level manifest entry.
   *
   * @returns {*}
   */
  emitManifestEntry(extension, entry) {
    let apiName = this.manifestKeys.get(entry);
    if (apiName) {
      let api = extension.apiManager.getAPI(apiName, extension);
      return api.onManifestEntry(entry);
    }
  }
  /**
   * Emits an `onManifestEntry` event for the top-level manifest entry
   * on all relevant {@link ExtensionAPI} instances for the given
   * extension.
   *
   * The API modules will be asynchronously loaded if they have not been
   * loaded already.
   *
   * @param {Extension} extension
   *        The extension for which to emit the events.
   * @param {string} entry
   *        The name of the top-level manifest entry.
   *
   * @returns {Promise<*>}
   */
  async asyncEmitManifestEntry(extension, entry) {
    let apiName = this.manifestKeys.get(entry);
    if (apiName) {
      let api = await extension.apiManager.asyncGetAPI(apiName, extension);
      return api.onManifestEntry(entry);
    }
  }

  /**
   * Returns the {@link ExtensionAPI} instance for the given API module,
   * for the given extension, in the given scope, synchronously loading
   * and instantiating it if necessary.
   *
   * @param {string} name
   *        The name of the API module to load.
   * @param {Extension} extension
   *        The extension for which to load the API.
   * @param {string} [scope = null]
   *        The scope type for which to retrieve the API, or null if not
   *        being retrieved for a particular scope.
   *
   * @returns {ExtensionAPI?}
   */
  getAPI(name, extension, scope = null) {
    if (!this._checkGetAPI(name, extension, scope)) {
      return;
    }

    let apis = this.apis.get(extension);
    if (apis.has(name)) {
      return apis.get(name);
    }

    let module = this.loadModule(name);

    let api = new module(extension);
    apis.set(name, api);
    return api;
  }
  /**
   * Returns the {@link ExtensionAPI} instance for the given API module,
   * for the given extension, in the given scope, asynchronously loading
   * and instantiating it if necessary.
   *
   * @param {string} name
   *        The name of the API module to load.
   * @param {Extension} extension
   *        The extension for which to load the API.
   * @param {string} [scope = null]
   *        The scope type for which to retrieve the API, or null if not
   *        being retrieved for a particular scope.
   *
   * @returns {Promise<ExtensionAPI>?}
   */
  async asyncGetAPI(name, extension, scope = null) {
    if (!this._checkGetAPI(name, extension, scope)) {
      return;
    }

    let apis = this.apis.get(extension);
    if (apis.has(name)) {
      return apis.get(name);
    }

    let module = await this.asyncLoadModule(name);

    // Check again, because async.
    if (apis.has(name)) {
      return apis.get(name);
    }

    let api = new module(extension);
    apis.set(name, api);
    return api;
  }

  /**
   * Synchronously loads an API module, if not already loaded, and
   * returns its ExtensionAPI constructor.
   *
   * @param {string} name
   *        The name of the module to load.
   * @returns {typeof ExtensionAPI}
   */
  loadModule(name) {
    let module = this.modules.get(name);
    if (module.loaded) {
      return this.global[name];
    }

    this._checkLoadModule(module, name);

    this.initGlobal();

    Services.scriptloader.loadSubScript(module.url, this.global);

    module.loaded = true;

    return this.global[name];
  }
  /**
   * aSynchronously loads an API module, if not already loaded, and
   * returns its ExtensionAPI constructor.
   *
   * @param {string} name
   *        The name of the module to load.
   *
   * @returns {Promise<typeof ExtensionAPI>}
   */
  asyncLoadModule(name) {
    let module = this.modules.get(name);
    if (module.loaded) {
      return Promise.resolve(this.global[name]);
    }
    if (module.asyncLoaded) {
      return module.asyncLoaded;
    }

    this._checkLoadModule(module, name);

    module.asyncLoaded = ChromeUtils.compileScript(module.url).then(script => {
      // In some rare cases, loadModule() may have been called since we started
      // the async compileScript call. In that case, return the result that we
      // already got from loadModule.
      if (!module.loaded) {
        this.initGlobal();
        script.executeInGlobal(this.global);

        module.loaded = true;
      }

      return this.global[name];
    });

    return module.asyncLoaded;
  }

  asyncLoadSettingsModules() {
    return Promise.all(
      Array.from(this.settingsModules).map(apiName =>
        this.asyncLoadModule(apiName)
      )
    );
  }

  getModule(name) {
    return this.modules.get(name);
  }

  /**
   * Checks whether the given API module may be loaded for the given
   * extension, in the given scope.
   *
   * @param {string} name
   *        The name of the API module to check.
   * @param {Extension} extension
   *        The extension for which to check the API.
   * @param {string} [scope = null]
   *        The scope type for which to check the API, or null if not
   *        being checked for a particular scope.
   *
   * @returns {boolean}
   *        Whether the module may be loaded.
   */
  _checkGetAPI(name, extension, scope = null) {
    let module = this.getModule(name);
    if (!module) {
      // A module may not exist for a particular manifest version, but
      // we allow keys in the manifest.  An example is pageAction.
      return false;
    }

    if (
      module.permissions &&
      !module.permissions.some(perm => extension.hasPermission(perm))
    ) {
      return false;
    }

    if (!scope) {
      return true;
    }

    if (!module.scopes.includes(scope)) {
      return false;
    }

    if (!lazy.Schemas.checkPermissions(module.namespaceName, extension)) {
      return false;
    }

    return true;
  }

  _checkLoadModule(module, name) {
    if (!module) {
      throw new Error(`Module '${name}' does not exist`);
    }
    if (this.global && this.global[name]) {
      throw new Error(
        `Module '${name}' conflicts with existing global property`
      );
    }
  }

  /**
   * Create a global object that is used as the shared global for all ext-*.js
   * scripts that are loaded via `loadScript`.
   *
   * @returns {object} A sandbox that is used as the global by `loadScript`.
   */
  _createExtGlobal() {
    let global = Cu.Sandbox(
      Services.scriptSecurityManager.getSystemPrincipal(),
      {
        wantXrays: false,
        wantGlobalProperties: ["ChromeUtils"],
        sandboxName: `Namespace of ext-*.js scripts for ${this.processType} (from: resource://gre/modules/ExtensionCommon.sys.mjs)`,
      }
    );

    Object.assign(global, {
      AppConstants,
      Cc,
      ChromeWorker,
      Ci,
      Cr,
      Cu,
      ExtensionAPI,
      ExtensionAPIPersistent,
      ExtensionCommon,
      FileReader,
      Glean,
      GleanPings,
      IOUtils,
      MatchGlob,
      MatchPattern,
      MatchPatternSet,
      OffscreenCanvas,
      PathUtils,
      Services,
      StructuredCloneHolder,
      WebExtensionPolicy,
      XPCOMUtils,
      extensions: this,
      global,
    });

    ChromeUtils.defineLazyGetter(global, "console", getConsole);
    // eslint-disable-next-line mozilla/lazy-getter-object-name
    ChromeUtils.defineESModuleGetters(global, {
      ExtensionUtils: "resource://gre/modules/ExtensionUtils.sys.mjs",
    });

    return global;
  }

  initGlobal() {
    if (!this.global) {
      this.global = this._createExtGlobal();
    }
  }

  /**
   * Load an ext-*.js script. The script runs in its own scope, if it wishes to
   * share state with another script it can assign to the `global` variable. If
   * it wishes to communicate with this API manager, use `extensions`.
   *
   * @param {string} scriptUrl The URL of the ext-*.js script.
   */
  loadScript(scriptUrl) {
    // Create the object in the context of the sandbox so that the script runs
    // in the sandbox's context instead of here.
    let scope = Cu.createObjectIn(this.global);

    Services.scriptloader.loadSubScript(scriptUrl, scope);

    // Save the scope to avoid it being garbage collected.
    this._scriptScopes.push(scope);
  }
}

class LazyAPIManager extends SchemaAPIManager {
  constructor(processType, moduleData, schemaURLs) {
    super(processType);

    /** @type {Promise | boolean} */
    this.initialized = false;

    this.initModuleData(moduleData);

    this.schemaURLs = schemaURLs;
  }

  lazyInit() {}
}

defineLazyGetter(LazyAPIManager.prototype, "schema", function () {
  let root = new lazy.SchemaRoot(lazy.Schemas.rootSchema, this.schemaURLs);
  root.parseSchemas();
  return root;
});

class MultiAPIManager extends SchemaAPIManager {
  constructor(processType, children) {
    super(processType);

    this.initialized = false;

    this.children = children;
  }

  async lazyInit() {
    if (!this.initialized) {
      this.initialized = true;

      for (let child of this.children) {
        if (child.lazyInit) {
          let res = child.lazyInit();
          if (res && typeof res.then === "function") {
            await res;
          }
        }

        mergePaths(this.modulePaths, child.modulePaths);
      }
    }
  }

  onStartup(extension) {
    return Promise.all(this.children.map(child => child.onStartup(extension)));
  }

  getModule(name) {
    for (let child of this.children) {
      if (child.modules.has(name)) {
        return child.modules.get(name);
      }
    }
  }

  loadModule(name) {
    for (let child of this.children) {
      if (child.modules.has(name)) {
        return child.loadModule(name);
      }
    }
  }

  asyncLoadModule(name) {
    for (let child of this.children) {
      if (child.modules.has(name)) {
        return child.asyncLoadModule(name);
      }
    }
  }
}

defineLazyGetter(MultiAPIManager.prototype, "schema", function () {
  let bases = this.children.map(child => child.schema);

  // All API manager schema roots should derive from the global schema root,
  // so it doesn't need its own entry.
  if (bases[bases.length - 1] === lazy.Schemas) {
    bases.pop();
  }

  if (bases.length === 1) {
    bases = bases[0];
  }
  return new lazy.SchemaRoot(bases, new Map());
});

export function LocaleData(data) {
  this.defaultLocale = data.defaultLocale;
  this.selectedLocale = data.selectedLocale;
  this.locales = data.locales || new Map();
  this.warnedMissingKeys = new Set();

  /**
   * Map(locale-name -> Map(message-key -> localized-string))
   *
   * Contains a key for each loaded locale, each of which is a
   * Map of message keys to their localized strings.
   *
   * @type {Map<string, Map<string, string>>}
   */
  this.messages = data.messages || new Map();

  if (data.builtinMessages) {
    this.messages.set(this.BUILTIN, data.builtinMessages);
  }
}

LocaleData.prototype = {
  // Representation of the object to send to content processes. This
  // should include anything the content process might need.
  serialize() {
    return {
      defaultLocale: this.defaultLocale,
      selectedLocale: this.selectedLocale,
      messages: this.messages,
      locales: this.locales,
    };
  },

  BUILTIN: "@@BUILTIN_MESSAGES",

  has(locale) {
    return this.messages.has(locale);
  },

  // https://developer.chrome.com/extensions/i18n
  localizeMessage(message, substitutions = [], options = {}) {
    let defaultOptions = {
      defaultValue: "",
      cloneScope: null,
    };

    let locales = this.availableLocales;
    if (options.locale) {
      locales = new Set(
        [this.BUILTIN, options.locale, this.defaultLocale].filter(locale =>
          this.messages.has(locale)
        )
      );
    }

    options = Object.assign(defaultOptions, options);

    // Message names are case-insensitive, so normalize them to lower-case.
    message = message.toLowerCase();
    for (let locale of locales) {
      let messages = this.messages.get(locale);
      if (messages.has(message)) {
        let str = messages.get(message);

        if (!str.includes("$")) {
          return str;
        }

        if (!Array.isArray(substitutions)) {
          substitutions = [substitutions];
        }

        let replacer = (matched, index, dollarSigns) => {
          if (index) {
            // This is not quite Chrome-compatible. Chrome consumes any number
            // of digits following the $, but only accepts 9 substitutions. We
            // accept any number of substitutions.
            index = parseInt(index, 10) - 1;
            return index in substitutions ? substitutions[index] : "";
          }
          // For any series of contiguous `$`s, the first is dropped, and
          // the rest remain in the output string.
          return dollarSigns;
        };
        return str.replace(/\$(?:([1-9]\d*)|(\$+))/g, replacer);
      }
    }

    // Check for certain pre-defined messages.
    if (message == "@@ui_locale") {
      return this.uiLocale;
    } else if (message.startsWith("@@bidi_")) {
      let rtl = Services.locale.isAppLocaleRTL;

      if (message == "@@bidi_dir") {
        return rtl ? "rtl" : "ltr";
      } else if (message == "@@bidi_reversed_dir") {
        return rtl ? "ltr" : "rtl";
      } else if (message == "@@bidi_start_edge") {
        return rtl ? "right" : "left";
      } else if (message == "@@bidi_end_edge") {
        return rtl ? "left" : "right";
      }
    }

    if (!this.warnedMissingKeys.has(message)) {
      let error = `Unknown localization message ${message}`;
      if (options.cloneScope) {
        error = new options.cloneScope.Error(error);
      }
      Cu.reportError(error);
      this.warnedMissingKeys.add(message);
    }
    return options.defaultValue;
  },

  // Localize a string, replacing all |__MSG_(.*)__| tokens with the
  // matching string from the current locale, as determined by
  // |this.selectedLocale|.
  //
  // This may not be called before calling either |initLocale| or
  // |initAllLocales|.
  localize(str, locale = this.selectedLocale) {
    if (!str) {
      return str;
    }

    return str.replace(/__MSG_([A-Za-z0-9@_]+?)__/g, (matched, message) => {
      return this.localizeMessage(message, [], {
        locale,
        defaultValue: matched,
      });
    });
  },

  // Validates the contents of a locale JSON file, normalizes the
  // messages into a Map of message key -> localized string pairs.
  addLocale(locale, messages, extension) {
    let result = new Map();

    let isPlainObject = obj =>
      obj &&
      typeof obj === "object" &&
      ChromeUtils.getClassName(obj) === "Object";

    // Chrome does not document the semantics of its localization
    // system very well. It handles replacements by pre-processing
    // messages, replacing |$[a-zA-Z0-9@_]+$| tokens with the value of their
    // replacements. Later, it processes the resulting string for
    // |$[0-9]| replacements.
    //
    // Again, it does not document this, but it accepts any number
    // of sequential |$|s, and replaces them with that number minus
    // 1. It also accepts |$| followed by any number of sequential
    // digits, but refuses to process a localized string which
    // provides more than 9 substitutions.
    if (!isPlainObject(messages)) {
      extension.packagingError(`Invalid locale data for ${locale}`);
      return result;
    }

    for (let key of Object.keys(messages)) {
      let msg = messages[key];

      if (!isPlainObject(msg) || typeof msg.message != "string") {
        extension.packagingError(
          `Invalid locale message data for ${locale}, message ${JSON.stringify(
            key
          )}`
        );
        continue;
      }

      // Substitutions are case-insensitive, so normalize all of their names
      // to lower-case.
      let placeholders = new Map();
      if ("placeholders" in msg && isPlainObject(msg.placeholders)) {
        for (let key of Object.keys(msg.placeholders)) {
          placeholders.set(key.toLowerCase(), msg.placeholders[key]);
        }
      }

      let replacer = (match, name) => {
        let replacement = placeholders.get(name.toLowerCase());
        if (isPlainObject(replacement) && "content" in replacement) {
          return replacement.content;
        }
        return "";
      };

      let value = msg.message.replace(/\$([A-Za-z0-9@_]+)\$/g, replacer);

      // Message names are also case-insensitive, so normalize them to lower-case.
      result.set(key.toLowerCase(), value);
    }

    this.messages.set(locale, result);
    return result;
  },

  get acceptLanguages() {
    let result = Services.prefs.getComplexValue(
      "intl.accept_languages",
      Ci.nsIPrefLocalizedString
    ).data;
    return result.split(/\s*,\s*/g);
  },

  get uiLocale() {
    return Services.locale.appLocaleAsBCP47;
  },

  get availableLocales() {
    const locales = [this.BUILTIN, this.selectedLocale, this.defaultLocale];
    const value = new Set(locales.filter(locale => this.messages.has(locale)));
    return redefineGetter(this, "availableLocales", value);
  },
};

/**
 * This is a generic class for managing event listeners.
 *
 * @example
 * new EventManager({
 *   context,
 *   name: "api.subAPI",
 *   register:  fire => {
 *     let listener = (...) => {
 *       // Fire any listeners registered with addListener.
 *       fire.async(arg1, arg2);
 *     };
 *     // Register the listener.
 *     SomehowRegisterListener(listener);
 *     return () => {
 *       // Return a way to unregister the listener.
 *       SomehowUnregisterListener(listener);
 *     };
 *   }
 * }).api()
 *
 * The result is an object with addListener, removeListener, and
 * hasListener methods. `context` is an add-on scope (either an
 * ExtensionContext in the chrome process or ExtensionContext in a
 * content process).
 */
class EventManager {
  /*
   * A persistent event must provide module and name.  Additionally the
   * module must implement primeListeners in the ExtensionAPI class.
   *
   * A startup blocking event must also add the startupBlocking flag in
   * ext-toolkit.json or ext-browser.json.
   *
   * Listeners synchronously added from a background extension context
   * will be persisted, for a persistent background script only the
   * "startup blocking" events will be persisted.
   *
   * EventManager instances created in a child process can't persist any listener.
   *
   * @param {object} params
   *        Parameters that control this EventManager.
   * @param {BaseContext} params.context
   *        An object representing the extension instance using this event.
   * @param {string} params.module
   *        The API module name, required for persistent events.
   * @param {string} params.event
   *        The API event name, required for persistent events.
   * @param {ExtensionAPI} params.extensionApi
   *        The API intance.  If the API uses the ExtensionAPIPersistent class, some simplification is
   *        possible by passing the api (self or this) and the internal register function will be used.
   * @param {string} [params.name]
   *        A name used only for debugging.  If not provided, name is built from module and event.
   * @param {functon} params.register
   *        A function called whenever a new listener is added.
   * @param {boolean} [params.inputHandling=false]
   *        If true, the "handling user input" flag is set while handlers
   *        for this event are executing.
   */
  constructor(params) {
    let {
      context,
      module,
      event,
      name,
      register,
      extensionApi,
      inputHandling = false,
      resetIdleOnEvent = true,
    } = params;
    this.context = context;
    this.module = module;
    this.event = event;
    this.name = name;
    this.register = register;
    this.inputHandling = inputHandling;
    this.resetIdleOnEvent = resetIdleOnEvent;

    const isBackgroundParent =
      this.context.envType === "addon_parent" &&
      this.context.isBackgroundContext;

    // TODO(Bug 1844041): ideally we should restrict resetIdleOnEvent to
    // EventManager instances that belongs to the event page, but along
    // with that we should consider if calling sendMessage from an event
    // page should also reset idle timer, and so in the shorter term
    // here we are allowing listeners from other extension pages to
    // also reset the idle timer.
    const isAddonContext = ["addon_parent", "addon_child"].includes(
      this.context.envType
    );

    // Avoid resetIdleOnEvent overhead by only consider it when applicable.
    if (!isAddonContext || context.extension.persistentBackground) {
      this.resetIdleOnEvent = false;
    }

    if (!name) {
      this.name = `${module}.${event}`;
    }

    if (!this.register && extensionApi instanceof ExtensionAPIPersistent) {
      this.register = (fire, ...params) => {
        return extensionApi.registerEventListener(
          { context, event, fire },
          params
        );
      };
    }
    if (!this.register) {
      throw new Error(
        `EventManager requires register method for ${this.name}.`
      );
    }

    this.canPersistEvents = module && event && isBackgroundParent;

    if (this.canPersistEvents) {
      let { extension } = context;
      if (extension.persistentBackground) {
        // Persistent backgrounds will only persist startup blocking APIs.
        let api_module = extension.apiManager.getModule(this.module);
        if (!api_module?.startupBlocking) {
          this.canPersistEvents = false;
        }
      } else {
        // Event pages will persist all APIs that implement primeListener.
        // The api is already loaded so this does not have performance effect.
        let api = extension.apiManager.getAPI(
          this.module,
          extension,
          "addon_parent"
        );

        // If the api doesn't implement primeListener we do not persist the events.
        if (!api?.primeListener) {
          this.canPersistEvents = false;
        }
      }
    }

    this.unregister = new Map();
    this.remove = new Map();
  }

  /*
   * Information about listeners to persistent events is associated with
   * the extension to which they belong.  Any extension thas has such
   * listeners has a property called `persistentListeners` that is a
   * 3-level Map:
   *
   * - the first 2 keys are the module name (e.g., webRequest)
   *   and the name of the event within the module (e.g., onBeforeRequest).
   *
   * - the third level of the map is used to track multiple listeners for
   *   the same event, these listeners are distinguished by the extra arguments
   *   passed to addListener()
   *
   * - for quick lookups, the key to the third Map is the result of calling
   *   uneval() on the array of extra arguments.
   *
   * - the value stored in the Map or persistent listeners we keep in memory
   *   is a plain object with:
   *   - a property called `params` that is the original (ie, not uneval()ed)
   *     extra arguments to addListener()
   *   - and a property called `listeners` that is an array of plain object
   *     each representing a listener to be primed and a `primeId` autoincremented
   *     integer that represents each of the primed listeners that belongs to the
   *     group listeners with the same set of extra params.
   *   - a `nextPrimeId` property keeps track of the numeric primeId that should
   *     be assigned to new persistent listeners added for the same event and
   *     same set of extra params.
   *
   * For a primed listener (i.e., the stub listener created during browser startup
   * before the extension background page is started, and after an event page is
   * suspended on idle), the object will be later populated (by the callers of
   * EventManager.primeListeners) with an additional `primed` property that serves
   * as a placeholder listener, collecting all events that got emitted while the
   * background page was not yet started, and eventually replaced by a callback
   * registered from the extension code, once the background page scripts have been
   * executed (or dropped if the background page scripts do not register the same
   * listener anymore).
   *
   * @param {Extension} extension
   * @returns {boolean} True if the extension had any persistent listeners.
   */
  static _initPersistentListeners(extension) {
    if (extension.persistentListeners) {
      return !!extension.persistentListeners.size;
    }

    let listeners = new DefaultMap(() => new DefaultMap(() => new Map()));
    extension.persistentListeners = listeners;

    let persistentListeners = extension.startupData?.persistentListeners;
    if (!persistentListeners) {
      return false;
    }

    let found = false;
    for (let [module, savedModuleEntry] of Object.entries(
      persistentListeners
    )) {
      for (let [event, savedEventEntry] of Object.entries(savedModuleEntry)) {
        for (let paramList of savedEventEntry) {
          /* Before Bug 1795801 (Firefox < 113) each entry was related to a listener
           * registered with a different set of extra params (and so only one listener
           * could be persisted for the same set of extra params)
           *
           * After Bug 1795801 (Firefox >= 113) each entry still represents a listener
           * registered for that event, but multiple listeners registered with the same
           * set of extra params will be captured as multiple entries in the
           * paramsList array.
           *
           * NOTE: persisted listeners are stored in the startupData part of the Addon DB
           * and are expected to be preserved across Firefox and Addons upgrades and downgrades
           * (unlike the WebExtensions startupCache data which is cleared when Firefox or the
           * addon is updated) and so we are taking special care about forward and backward
           * compatibility of the persistentListeners on-disk format:
           *
           * - forward compatibility: when this new version of this startupData loading logic
           *   is loading the old persistentListeners on-disk format:
           *   - on the first run only one listener will be primed for each of the extra params
           *     recorded in the startupData (same as in older Firefox versions)
           *     and Bug 1795801 will still be hit, but once the background
           *     context is started once the startupData will be updated to
           *     include each of the listeners (indipendently if the set of
           *     extra params is the same as another listener already been
           *     persisted).
           *   - after the first run, all listeners will be primed separately, even if the extra
           *     params are the same as other listeners already primed, and so
           *     each of the listener will receive the pending events collected
           *     by their related primed listener and Bug 1795801 not to be hit anymore.
           *
           * - backward compatibility: when the old version of this startupData loading logic
           *   (https://searchfox.org/mozilla-central/rev/cd2121e7d8/toolkit/components/extensions/ExtensionCommon.jsm#2360-2371)
           *   is loading the new persistentListeners on-disk format, the last
           *   entry with the same set of extra params will be eventually overwritting the
           *   entry for another primed listener with the same extra params, Bug 1795801 will still
           *   be hit, but no actual change in behavior is expected.
           */
          let key = uneval(paramList);
          const eventEntry = listeners.get(module).get(event);

          if (eventEntry.has(key)) {
            const keyEntry = eventEntry.get(key);
            let primeId = keyEntry.nextPrimeId;
            keyEntry.listeners.push({ primeId });
            keyEntry.nextPrimeId++;
          } else {
            eventEntry.set(key, {
              params: paramList,
              nextPrimeId: 1,
              listeners: [{ primeId: 0 }],
            });
          }
          found = true;
        }
      }
    }
    return found;
  }

  // Extract just the information needed at startup for all persistent
  // listeners, and arrange for it to be saved.  This should be called
  // whenever the set of persistent listeners for an extension changes.
  static _writePersistentListeners(extension) {
    let startupListeners = {};
    for (let [module, moduleEntry] of extension.persistentListeners) {
      startupListeners[module] = {};
      for (let [event, eventEntry] of moduleEntry) {
        // Turn the per-event entries from the format they are being kept
        // in memory:
        //
        //   [
        //     { params: paramList1, listeners: [listener1, listener2, ...] },
        //     { params: paramList2, listeners: [listener3, listener3, ...] },
        //     ...
        //   ]
        //
        // into the format used for storing them on disk (in the startupData),
        // which is an array of the params for each listener (with the param list
        // included as many times as many listeners are persisted for the same
        // set of params):
        //
        //   [paramList1, paramList1, ..., paramList2, paramList2, ...]
        //
        // This format will also work as expected on older Firefox versions where
        // only one listener was being persisted for each set of params.
        startupListeners[module][event] = Array.from(
          eventEntry.values()
        ).flatMap(keyEntry => keyEntry.listeners.map(() => keyEntry.params));
      }
    }

    extension.startupData.persistentListeners = startupListeners;
    extension.saveStartupData();
  }

  // Set up "primed" event listeners for any saved event listeners
  // in an extension's startup data.
  // This function is only called during browser startup, it stores details
  // about all primed listeners in the extension's persistentListeners Map.
  static primeListeners(extension, isInStartup = false) {
    if (!EventManager._initPersistentListeners(extension)) {
      return;
    }

    for (let [module, moduleEntry] of extension.persistentListeners) {
      // If we're in startup, we only want to continue attempting to prime a
      // subset of events that should be startup blocking.
      if (isInStartup) {
        let api_module = extension.apiManager.getModule(module);
        if (!api_module.startupBlocking) {
          continue;
        }
      }

      let api = extension.apiManager.getAPI(module, extension, "addon_parent");

      // If an extension is upgraded and a permission, such as webRequest, is
      // removed, we will have been called but the API is no longer available.
      if (!api?.primeListener) {
        // The runtime module no longer implements primed listeners, drop them.
        extension.persistentListeners.delete(module);
        EventManager._writePersistentListeners(extension);
        continue;
      }
      for (let [event, eventEntry] of moduleEntry) {
        for (let [key, { params, listeners }] of eventEntry) {
          for (let listener of listeners) {
            // Reset the `listener.added` flag by setting it to `false` while
            // re-priming the listeners because the event page has suspended
            // and the previous converted listener is no longer listening.
            const listenerWasAdded = listener.added;
            listener.added = false;
            listener.params = params;
            let primed = { pendingEvents: [] };

            let fireEvent = (...args) =>
              new Promise((resolve, reject) => {
                if (!listener.primed) {
                  reject(
                    new Error(
                      `primed listener ${module}.${event} not re-registered`
                    )
                  );
                  return;
                }
                primed.pendingEvents.push({ args, resolve, reject });
                extension.emit("background-script-event");
              });

            let fire = {
              wakeup: () => extension.wakeupBackground(),
              sync: fireEvent,
              async: fireEvent,
              // fire.async for ProxyContextParent is already not cloning.
              raw: fireEvent,
            };

            try {
              let handler = api.primeListener(
                event,
                fire,
                listener.params,
                isInStartup
              );
              if (handler) {
                listener.primed = primed;
                Object.assign(primed, handler);
              }
            } catch (e) {
              Cu.reportError(
                `Error priming listener ${module}.${event}: ${e} :: ${e.stack}`
              );
              // Force this listener to be cleared.
              listener.error = true;
            }

            // If an attempt to prime a listener failed, ensure it is cleared now.
            // If a module is a startup blocking module, not all listeners may
            // get primed during early startup.  For that reason, we don't clear
            // persisted listeners during early startup.  At the end of background
            // execution any listeners that were not renewed will be cleared.
            //
            // TODO(Bug 1797474): consider priming runtime.onStartup and
            // avoid to special handling it here.
            if (
              listener.error ||
              (!isInStartup &&
                !(
                  (`${module}.${event}` === "runtime.onStartup" &&
                    listenerWasAdded) ||
                  listener.primed
                ))
            ) {
              EventManager.clearPersistentListener(
                extension,
                module,
                event,
                key,
                listener.primeId
              );
            }
          }
        }
      }
    }
  }

  /**
   * This is called as a result of background script startup-finished and shutdown.
   *
   * After startup, it removes any remaining primed listeners.  These exist if the
   * listener was not renewed during startup.  In this case the persisted listener
   * data is also removed.
   *
   * During shutdown, care should be taken to set clearPersistent to false.
   * persisted listener data should NOT be cleared during shutdown.
   *
   * @param {Extension} extension
   * @param {boolean} clearPersistent whether the persisted listener data should be cleared.
   */
  static clearPrimedListeners(extension, clearPersistent = true) {
    if (!extension.persistentListeners) {
      return;
    }

    for (let [module, moduleEntry] of extension.persistentListeners) {
      for (let [event, eventEntry] of moduleEntry) {
        for (let [key, { listeners }] of eventEntry) {
          for (let listener of listeners) {
            let { primed, added, primeId } = listener;
            // When a primed listener is added or renewed during initial
            // background execution we set an added flag.  If it was primed
            // when added, primed is set to null.
            if (added) {
              continue;
            }

            if (primed) {
              // When a primed listener was not renewed, primed will still be truthy.
              // These need to be cleared on shutdown (important for event pages), but
              // we only clear the persisted listener data after the startup of a background.
              // Release any pending events and unregister the primed handler.
              listener.primed = null;

              for (let evt of primed.pendingEvents) {
                evt.reject(new Error("listener not re-registered"));
              }
              primed.unregister();
            }

            // Clear any persisted events that were not renewed, should typically
            // only be done at the end of the background page load.
            if (clearPersistent) {
              EventManager.clearPersistentListener(
                extension,
                module,
                event,
                key,
                primeId
              );
            }
          }
        }
      }
    }
  }

  // Record the fact that there is a listener for the given event in
  // the given extension.  `args` is an Array containing any extra
  // arguments that were passed to addListener().
  static savePersistentListener(extension, module, event, args = []) {
    EventManager._initPersistentListeners(extension);
    let key = uneval(args);
    const eventEntry = extension.persistentListeners.get(module).get(event);

    let primeId;
    if (!eventEntry.has(key)) {
      // when writing, only args are written, other properties are dropped
      primeId = 0;
      eventEntry.set(key, {
        params: args,
        listeners: [{ added: true, primeId }],
        nextPrimeId: 1,
      });
    } else {
      const keyEntry = eventEntry.get(key);
      primeId = keyEntry.nextPrimeId;
      keyEntry.listeners.push({ added: true, primeId });
      keyEntry.nextPrimeId = primeId + 1;
    }

    EventManager._writePersistentListeners(extension);
    return [module, event, key, primeId];
  }

  // Remove the record for the given event listener from the extension's
  // startup data.  `key` must be a string, the result of calling uneval()
  // on the array of extra arguments originally passed to addListener().
  static clearPersistentListener(
    extension,
    module,
    event,
    key = uneval([]),
    primeId = undefined
  ) {
    let eventEntry = extension.persistentListeners.get(module).get(event);

    let keyEntry = eventEntry.get(key);

    if (primeId != undefined && keyEntry) {
      keyEntry.listeners = keyEntry.listeners.filter(
        listener => listener.primeId !== primeId
      );
    }

    if (primeId == undefined || keyEntry?.listeners.length === 0) {
      eventEntry.delete(key);
      if (eventEntry.size == 0) {
        let moduleEntry = extension.persistentListeners.get(module);
        moduleEntry.delete(event);
        if (moduleEntry.size == 0) {
          extension.persistentListeners.delete(module);
        }
      }
    }

    EventManager._writePersistentListeners(extension);
  }

  addListener(callback, ...args) {
    if (this.unregister.has(callback)) {
      return;
    }
    this.context.logActivity("api_call", `${this.name}.addListener`, { args });

    let shouldFire = () => {
      if (this.context.unloaded) {
        dump(`${this.name} event fired after context unloaded.\n`);
      } else if (!this.context.active) {
        dump(`${this.name} event fired while context is inactive.\n`);
      } else if (this.unregister.has(callback)) {
        return true;
      }
      return false;
    };

    let { extension } = this.context;
    const resetIdle = () => {
      if (this.resetIdleOnEvent) {
        extension?.emit("background-script-reset-idle", {
          reason: "event",
          eventName: this.name,
        });
      }
    };

    let fire = {
      // Bug 1754866 fire.sync doesn't match documentation.
      sync: (...args) => {
        if (shouldFire()) {
          resetIdle();
          let result = this.context.applySafe(callback, args);
          this.context.logActivity("api_event", this.name, { args, result });
          return result;
        }
      },
      async: (...args) => {
        return Promise.resolve().then(() => {
          if (shouldFire()) {
            resetIdle();
            let result = this.context.applySafe(callback, args);
            this.context.logActivity("api_event", this.name, { args, result });
            return result;
          }
        });
      },
      raw: (...args) => {
        if (!shouldFire()) {
          throw new Error("Called raw() on unloaded/inactive context");
        }
        resetIdle();
        let result = Reflect.apply(callback, null, args);
        this.context.logActivity("api_event", this.name, { args, result });
        return result;
      },
      asyncWithoutClone: (...args) => {
        return Promise.resolve().then(() => {
          if (shouldFire()) {
            resetIdle();
            let result = this.context.applySafeWithoutClone(callback, args);
            this.context.logActivity("api_event", this.name, { args, result });
            return result;
          }
        });
      },
    };

    let { module, event } = this;

    let unregister = null;
    let recordStartupData = false;

    // If this is a persistent event, check for a listener that was already
    // created during startup.  If there is one, use it and don't create a
    // new one.
    if (this.canPersistEvents) {
      // Once a background is started, listenerPromises is set to null. At
      // that point, we stop recording startup data.
      recordStartupData = !!this.context.listenerPromises;

      let key = uneval(args);
      EventManager._initPersistentListeners(extension);
      let keyEntry = extension.persistentListeners
        .get(module)
        .get(event)
        .get(key);

      // Get the first persistent listener which matches the module, event and extra arguments
      // and not added back by the extension yet, the persistent listener found may be either
      // primed or not (in particular API Events that belongs to APIs that should not be blocking
      // startup may have persistent listeners that are not primed during the first execution
      // of the background context happening as part of the applications startup, whereas they
      // will be primed when the background context will be suspended on the idle timeout).
      let listener = keyEntry?.listeners.find(listener => !listener.added);
      if (listener) {
        // During startup only a subset of persisted listeners are primed.  As
        // well, each API determines whether to prime a specific listener.
        let { primed } = listener;
        if (primed) {
          listener.primed = null;

          primed.convert(fire, this.context);
          unregister = primed.unregister;

          for (let evt of primed.pendingEvents) {
            evt.resolve(fire.async(...evt.args));
          }
        }
        listener.added = true;

        recordStartupData = false;

        // Do not clear the persistent listener for a non-persistent backgrond
        // context on removeListener calls got after the background context
        // was fully started. The persistent listener can instead be cleared
        // by not re-registering it on the next background context startup.
        //
        // This check prevents that for listeners that were already persisted
        // and primed (a separate one below prevents it for new listeners).
        //
        // TODO Bug 1899767: do not reprime if the listener has been
        // unregistered.
        if (extension.persistentBackground) {
          this.remove.set(callback, () => {
            EventManager.clearPersistentListener(
              extension,
              module,
              event,
              uneval(args),
              listener.primeId
            );
          });
        }
      }
    }

    if (!unregister) {
      unregister = this.register(fire, ...args);
    }

    this.unregister.set(callback, unregister);
    this.context.callOnClose(this);

    // If this is a new listener for a persistent event, record
    // the details for subsequent startups.
    if (recordStartupData) {
      const [, , , /* _module */ /* _event */ /* _key */ primeId] =
        EventManager.savePersistentListener(extension, module, event, args);

      // Do not clear the persistent listener for a non-persistent backgrond
      // context on removeListener calls got after the background context
      // was fully started. The persistent listener can instead be cleared
      // by not re-registering it on the next background context startup.
      //
      // This check prevents that for new listeners that were not already persisted
      // and primed.
      //
      // TODO Bug 1899767: do not reprime if the listener has been
      // unregistered.
      if (extension.persistentBackground) {
        this.remove.set(callback, () => {
          EventManager.clearPersistentListener(
            extension,
            module,
            event,
            uneval(args),
            primeId
          );
        });
      }
    }
  }

  removeListener(callback, clearPersistentListener = true) {
    if (!this.unregister.has(callback)) {
      return;
    }
    this.context.logActivity("api_call", `${this.name}.removeListener`, {
      args: [],
    });

    let unregister = this.unregister.get(callback);
    this.unregister.delete(callback);
    try {
      unregister();
    } catch (e) {
      Cu.reportError(e);
    }

    if (clearPersistentListener && this.remove.has(callback)) {
      let cleanup = this.remove.get(callback);
      this.remove.delete(callback);
      cleanup();
    }

    if (this.unregister.size == 0) {
      this.context.forgetOnClose(this);
    }
  }

  hasListener(callback) {
    return this.unregister.has(callback);
  }

  revoke() {
    for (let callback of this.unregister.keys()) {
      this.removeListener(callback, false);
    }
  }

  close() {
    this.revoke();
  }

  api() {
    return {
      addListener: (...args) => this.addListener(...args),
      removeListener: (...args) => this.removeListener(...args),
      hasListener: (...args) => this.hasListener(...args),
      setUserInput: this.inputHandling,
      [lazy.Schemas.REVOKE]: () => this.revoke(),
    };
  }
}

// Simple API for event listeners where events never fire.
function ignoreEvent(context, name) {
  return {
    addListener: function () {
      let id = context.extension.id;
      let frame = Components.stack.caller;
      let msg = `In add-on ${id}, attempting to use listener "${name}", which is unimplemented.`;
      let scriptError = Cc["@mozilla.org/scripterror;1"].createInstance(
        Ci.nsIScriptError
      );
      scriptError.init(
        msg,
        frame.filename,
        frame.lineNumber,
        frame.columnNumber,
        Ci.nsIScriptError.warningFlag,
        "content javascript"
      );
      Services.console.logMessage(scriptError);
    },
    removeListener: function () {},
    hasListener: function () {},
  };
}

const stylesheetMap = new DefaultMap(url => {
  let uri = Services.io.newURI(url);
  return lazy.styleSheetService.preloadSheet(
    uri,
    // Note: keep in sync with ext-browser-content.js. This used to be
    // AGENT_SHEET, but changed to AUTHOR_SHEET, see bug 1873024.
    lazy.styleSheetService.AUTHOR_SHEET
  );
});

/**
 * Updates the in-memory representation of extension host permissions, i.e.
 * policy.allowedOrigins.
 *
 * @param {WebExtensionPolicy} policy
 *        A policy. All MatchPattern instances in policy.allowedOrigins are
 *        expected to have been constructed with ignorePath: true.
 * @param {string[]} origins
 *        A list of already-normalized origins, equivalent to using the
 *        MatchPattern constructor with ignorePath: true.
 * @param {boolean} isAdd
 *        Whether to add instead of removing the host permissions.
 */
function updateAllowedOrigins(policy, origins, isAdd) {
  if (!origins.length) {
    // Nothing to modify.
    return;
  }
  let patternMap = new Map();
  for (let pattern of policy.allowedOrigins.patterns) {
    patternMap.set(pattern.pattern, pattern);
  }
  if (!isAdd) {
    for (let origin of origins) {
      patternMap.delete(origin);
    }
  } else {
    // In the parent process, policy.extension.restrictSchemes is available.
    // In the content process, we need to check the mozillaAddons permission,
    // which is only available if approved by the parent.
    const restrictSchemes =
      policy.extension?.restrictSchemes ??
      policy.hasPermission("mozillaAddons");
    for (let origin of origins) {
      if (patternMap.has(origin)) {
        continue;
      }
      patternMap.set(
        origin,
        new MatchPattern(origin, { restrictSchemes, ignorePath: true })
      );
    }
  }
  // patternMap contains only MatchPattern instances, so we don't need to set
  // the options parameter (with restrictSchemes, etc.) since that is only used
  // if the input is a string.
  policy.allowedOrigins = new MatchPatternSet(Array.from(patternMap.values()));
}

export var ExtensionCommon = {
  BaseContext,
  CanOfAPIs,
  EventManager,
  ExtensionAPI,
  ExtensionAPIPersistent,
  EventEmitter,
  LocalAPIImplementation,
  LocaleData,
  NoCloneSpreadArgs,
  SchemaAPIInterface,
  SchemaAPIManager,
  SpreadArgs,
  checkLoadURI,
  checkLoadURL,
  defineLazyGetter,
  redefineGetter,
  getConsole,
  ignoreEvent,
  instanceOf,
  makeWidgetId,
  normalizeTime,
  runSafeSyncWithoutClone,
  stylesheetMap,
  updateAllowedOrigins,
  withHandlingUserInput,

  MultiAPIManager,
  LazyAPIManager,
};
PK
!<��.�g�g� modules/ExtensionContent.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

/** @type {Lazy} */
const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ExtensionProcessScript:
    "resource://gre/modules/ExtensionProcessScript.sys.mjs",
  ExtensionTelemetry: "resource://gre/modules/ExtensionTelemetry.sys.mjs",
  LanguageDetector:
    "resource://gre/modules/translation/LanguageDetector.sys.mjs",
  Schemas: "resource://gre/modules/Schemas.sys.mjs",
  WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "styleSheetService",
  "@mozilla.org/content/style-sheet-service;1",
  "nsIStyleSheetService"
);

const Timer = Components.Constructor(
  "@mozilla.org/timer;1",
  "nsITimer",
  "initWithCallback"
);

const ScriptError = Components.Constructor(
  "@mozilla.org/scripterror;1",
  "nsIScriptError",
  "initWithWindowID"
);

import {
  ChildAPIManager,
  ExtensionChild,
  ExtensionActivityLogChild,
  Messenger,
} from "resource://gre/modules/ExtensionChild.sys.mjs";
import { ExtensionCommon } from "resource://gre/modules/ExtensionCommon.sys.mjs";
import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

const {
  DefaultMap,
  DefaultWeakMap,
  getInnerWindowID,
  promiseDocumentIdle,
  promiseDocumentLoaded,
  promiseDocumentReady,
} = ExtensionUtils;

const {
  BaseContext,
  CanOfAPIs,
  SchemaAPIManager,
  defineLazyGetter,
  redefineGetter,
  runSafeSyncWithoutClone,
} = ExtensionCommon;

ChromeUtils.defineLazyGetter(lazy, "isContentScriptProcess", () => {
  return (
    Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT ||
    !WebExtensionPolicy.useRemoteWebExtensions ||
    // Thunderbird still loads some content in the parent process.
    AppConstants.MOZ_APP_NAME == "thunderbird"
  );
});

var DocumentManager;

const CATEGORY_EXTENSION_SCRIPTS_CONTENT = "webextension-scripts-content";

var apiManager = new (class extends SchemaAPIManager {
  constructor() {
    super("content", lazy.Schemas);
    this.initialized = false;
  }

  lazyInit() {
    if (!this.initialized) {
      this.initialized = true;
      this.initGlobal();
      for (let { value } of Services.catMan.enumerateCategory(
        CATEGORY_EXTENSION_SCRIPTS_CONTENT
      )) {
        this.loadScript(value);
      }
    }
  }
})();

const SCRIPT_EXPIRY_TIMEOUT_MS = 5 * 60 * 1000;
const SCRIPT_CLEAR_TIMEOUT_MS = 5 * 1000;

const CSS_EXPIRY_TIMEOUT_MS = 30 * 60 * 1000;
const CSSCODE_EXPIRY_TIMEOUT_MS = 10 * 60 * 1000;

const scriptCaches = new WeakSet();
const sheetCacheDocuments = new DefaultWeakMap(() => new WeakSet());

class CacheMap extends DefaultMap {
  constructor(timeout, getter, extension) {
    super(getter);

    this.expiryTimeout = timeout;

    scriptCaches.add(this);

    // This ensures that all the cached scripts and stylesheets are deleted
    // from the cache and the xpi is no longer actively used.
    // See Bug 1435100 for rationale.
    extension.once("shutdown", () => {
      this.clear(-1);
    });
  }

  get(url) {
    let promise = super.get(url);

    promise.lastUsed = Date.now();
    if (promise.timer) {
      promise.timer.cancel();
    }
    promise.timer = Timer(
      this.delete.bind(this, url),
      this.expiryTimeout,
      Ci.nsITimer.TYPE_ONE_SHOT
    );

    return promise;
  }

  delete(url) {
    if (this.has(url)) {
      super.get(url).timer.cancel();
    }

    return super.delete(url);
  }

  clear(timeout = SCRIPT_CLEAR_TIMEOUT_MS) {
    let now = Date.now();
    for (let [url, promise] of this.entries()) {
      // Delete the entry if expired or if clear has been called with timeout -1
      // (which is used to force the cache to clear all the entries, e.g. when the
      // extension is shutting down).
      if (timeout === -1 || now - promise.lastUsed >= timeout) {
        this.delete(url);
      }
    }
  }
}

class ScriptCache extends CacheMap {
  constructor(options, extension) {
    super(
      SCRIPT_EXPIRY_TIMEOUT_MS,
      url => {
        /** @type {Promise<PrecompiledScript> & { script?: PrecompiledScript }} */
        let promise = ChromeUtils.compileScript(url, options);
        promise.then(script => {
          promise.script = script;
        });
        return promise;
      },
      extension
    );
  }
}

/**
 * Shared base class for the two specialized CSS caches:
 * CSSCache (for the "url"-based stylesheets) and CSSCodeCache
 * (for the stylesheet defined by plain CSS content as a string).
 */
class BaseCSSCache extends CacheMap {
  constructor(expiryTimeout, defaultConstructor, extension) {
    super(expiryTimeout, defaultConstructor, extension);
  }

  addDocument(key, document) {
    sheetCacheDocuments.get(this.get(key)).add(document);
  }

  deleteDocument(key, document) {
    sheetCacheDocuments.get(this.get(key)).delete(document);
  }

  delete(key) {
    if (this.has(key)) {
      let promise = this.get(key);

      // Never remove a sheet from the cache if it's still being used by a
      // document. Rule processors can be shared between documents with the
      // same preloaded sheet, so we only lose by removing them while they're
      // still in use.
      let docs = ChromeUtils.nondeterministicGetWeakSetKeys(
        sheetCacheDocuments.get(promise)
      );
      if (docs.length) {
        return;
      }
    }

    return super.delete(key);
  }
}

/**
 * Cache of the preloaded stylesheet defined by url.
 */
class CSSCache extends BaseCSSCache {
  constructor(sheetType, extension) {
    super(
      CSS_EXPIRY_TIMEOUT_MS,
      url => {
        let uri = Services.io.newURI(url);
        return lazy.styleSheetService
          .preloadSheetAsync(uri, sheetType)
          .then(sheet => {
            return { url, sheet };
          });
      },
      extension
    );
  }
}

/**
 * Cache of the preloaded stylesheet defined by plain CSS content as a string,
 * the key of the cached stylesheet is the hash of its "CSSCode" string.
 */
class CSSCodeCache extends BaseCSSCache {
  constructor(sheetType, extension) {
    super(
      CSSCODE_EXPIRY_TIMEOUT_MS,
      hash => {
        if (!this.has(hash)) {
          // Do not allow the getter to be used to lazily create the cached stylesheet,
          // the cached CSSCode stylesheet has to be explicitly set.
          throw new Error(
            "Unexistent cached cssCode stylesheet: " + Error().stack
          );
        }

        return super.get(hash);
      },
      extension
    );

    // Store the preferred sheetType (used to preload the expected stylesheet type in
    // the addCSSCode method).
    this.sheetType = sheetType;
  }

  addCSSCode(hash, cssCode) {
    if (this.has(hash)) {
      // This cssCode have been already cached, no need to create it again.
      return;
    }
    // The `webext=style` portion is added metadata to help us distinguish
    // different kinds of data URL loads that are triggered with the
    // SystemPrincipal. It shall be removed with bug 1699425.
    const uri = Services.io.newURI(
      "data:text/css;extension=style;charset=utf-8," +
        encodeURIComponent(cssCode)
    );
    const value = lazy.styleSheetService
      .preloadSheetAsync(uri, this.sheetType)
      .then(sheet => {
        return { sheet, uri };
      });

    super.set(hash, value);
  }
}

defineLazyGetter(ExtensionChild.prototype, "staticScripts", function () {
  return new ScriptCache({ hasReturnValue: false }, this);
});

defineLazyGetter(ExtensionChild.prototype, "dynamicScripts", function () {
  return new ScriptCache({ hasReturnValue: true }, this);
});

defineLazyGetter(ExtensionChild.prototype, "anonStaticScripts", function () {
  // TODO bug 1651557: Use dynamic name to improve debugger experience.
  const filename = "<anonymous code>";
  return new ScriptCache({ filename, hasReturnValue: false }, this);
});

defineLazyGetter(ExtensionChild.prototype, "anonDynamicScripts", function () {
  // TODO bug 1651557: Use dynamic name to improve debugger experience.
  const filename = "<anonymous code>";
  return new ScriptCache({ filename, hasReturnValue: true }, this);
});

defineLazyGetter(ExtensionChild.prototype, "userCSS", function () {
  return new CSSCache(Ci.nsIStyleSheetService.USER_SHEET, this);
});

defineLazyGetter(ExtensionChild.prototype, "authorCSS", function () {
  return new CSSCache(Ci.nsIStyleSheetService.AUTHOR_SHEET, this);
});

// These two caches are similar to the above but specialized to cache the cssCode
// using an hash computed from the cssCode string as the key (instead of the generated data
// URI which can be pretty long for bigger injected cssCode).
defineLazyGetter(ExtensionChild.prototype, "userCSSCode", function () {
  return new CSSCodeCache(Ci.nsIStyleSheetService.USER_SHEET, this);
});

defineLazyGetter(ExtensionChild.prototype, "authorCSSCode", function () {
  return new CSSCodeCache(Ci.nsIStyleSheetService.AUTHOR_SHEET, this);
});

/**
 * This is still an ExtensionChild, but with the properties added above.
 * Unfortunately we can't express that using just JSDocs types locally,
 * so this needs to be used with `& ExtensionChild` explicitly below.
 *
 * @typedef {object} ExtensionChildContent
 * @property {ScriptCache} staticScripts
 * @property {ScriptCache} dynamicScripts
 * @property {ScriptCache} anonStaticScripts
 * @property {ScriptCache} anonDynamicScripts
 * @property {CSSCache} userCSS
 * @property {CSSCache} authorCSS
 * @property {CSSCodeCache} userCSSCode
 * @property {CSSCodeCache} authorCSSCode
 */

// Represents a content script.
class Script {
  /**
   * @param {ExtensionChild & ExtensionChildContent} extension
   * @param {WebExtensionContentScript|object} matcher
   *        An object with a "matchesWindowGlobal" method and content script
   *        execution details. This is usually a plain WebExtensionContentScript
   *        except when the script is run via `tabs.executeScript` or
   *        `scripting.executeScript`. In this case, the object may have some
   *        extra properties: wantReturnValue, removeCSS, cssOrigin
   */
  constructor(extension, matcher) {
    this.scriptType = "content_script";
    this.extension = extension;
    this.matcher = matcher;

    this.runAt = this.matcher.runAt;
    this.world = this.matcher.world;
    this.js = this.matcher.jsPaths;
    this.jsCode = null; // tabs/scripting.executeScript + ISOLATED world.
    this.jsCodeCompiledScript = null; // scripting.executeScript + MAIN world.
    this.css = this.matcher.cssPaths.slice();
    this.cssCodeHash = null;

    this.removeCSS = this.matcher.removeCSS;
    this.cssOrigin = this.matcher.cssOrigin;

    this.cssCache =
      extension[this.cssOrigin === "user" ? "userCSS" : "authorCSS"];
    this.cssCodeCache =
      extension[this.cssOrigin === "user" ? "userCSSCode" : "authorCSSCode"];
    if (this.world === "MAIN") {
      this.scriptCache = matcher.wantReturnValue
        ? extension.anonDynamicScripts
        : extension.anonStaticScripts;
    } else {
      this.scriptCache = matcher.wantReturnValue
        ? extension.dynamicScripts
        : extension.staticScripts;
    }

    /** @type {WeakSet<Document>} A set of documents injected into. */
    this.injectedInto = new WeakSet();

    if (matcher.wantReturnValue) {
      this.compileScripts();
      this.loadCSS();
    }
  }

  get requiresCleanup() {
    return !this.removeCSS && (!!this.css.length || this.cssCodeHash);
  }

  async addCSSCode(cssCode) {
    if (!cssCode) {
      return;
    }

    // Store the hash of the cssCode.
    const buffer = await crypto.subtle.digest(
      "SHA-1",
      new TextEncoder().encode(cssCode)
    );
    this.cssCodeHash = String.fromCharCode(...new Uint16Array(buffer));

    // Cache and preload the cssCode stylesheet.
    this.cssCodeCache.addCSSCode(this.cssCodeHash, cssCode);
  }

  addJSCode(jsCode) {
    if (!jsCode) {
      return;
    }
    if (this.world === "MAIN") {
      // To support the scripting.executeScript API, we would like to execute a
      // string in the context of the web page in #injectIntoMainWorld().
      // To do so without being blocked by the web page's CSP, we convert
      // jsCode to a PrecompiledScript, which is then executed by the logic
      // that is usually used for file-based execution.
      const dataUrl = `data:text/javascript,${encodeURIComponent(jsCode)}`;
      const options = {
        hasReturnValue: this.matcher.wantReturnValue,
        // Redact the file name to hide actual script content from web pages.
        // TODO bug 1651557: Use dynamic name to improve debugger experience.
        filename: "<anonymous code>",
      };
      // Note: this logic is similar to this.scriptCaches.get(...), but we are
      // not using scriptCaches because we don't want the URL to be cached.
      /** @type {Promise<PrecompiledScript> & {script?: PrecompiledScript}} */
      let promised = ChromeUtils.compileScript(dataUrl, options);
      promised.then(script => {
        promised.script = script;
      });
      this.jsCodeCompiledScript = promised;
    } else {
      // this.world === "ISOLATED".
      this.jsCode = jsCode;
    }
  }

  compileScripts() {
    return this.js.map(url => this.scriptCache.get(url));
  }

  loadCSS() {
    return this.css.map(url => this.cssCache.get(url));
  }

  preload() {
    this.loadCSS();
    this.compileScripts();
  }

  cleanup(window) {
    if (this.requiresCleanup) {
      if (window) {
        let { windowUtils } = window;

        let type =
          this.cssOrigin === "user"
            ? windowUtils.USER_SHEET
            : windowUtils.AUTHOR_SHEET;

        for (let url of this.css) {
          this.cssCache.deleteDocument(url, window.document);

          if (!window.closed) {
            runSafeSyncWithoutClone(
              windowUtils.removeSheetUsingURIString,
              url,
              type
            );
          }
        }

        const { cssCodeHash } = this;

        if (cssCodeHash && this.cssCodeCache.has(cssCodeHash)) {
          if (!window.closed) {
            this.cssCodeCache.get(cssCodeHash).then(({ uri }) => {
              runSafeSyncWithoutClone(windowUtils.removeSheet, uri, type);
            });
          }
          this.cssCodeCache.deleteDocument(cssCodeHash, window.document);
        }
      }

      // Clear any sheets that were kept alive past their timeout as
      // a result of living in this document.
      this.cssCodeCache.clear(CSSCODE_EXPIRY_TIMEOUT_MS);
      this.cssCache.clear(CSS_EXPIRY_TIMEOUT_MS);
    }
  }

  matchesWindowGlobal(windowGlobal, ignorePermissions) {
    return this.matcher.matchesWindowGlobal(windowGlobal, ignorePermissions);
  }

  async injectInto(window, reportExceptions = true) {
    if (
      !lazy.isContentScriptProcess ||
      this.injectedInto.has(window.document)
    ) {
      return;
    }
    this.injectedInto.add(window.document);

    let context = this.extension.getContext(window);
    for (let script of this.matcher.jsPaths) {
      context.logActivity(this.scriptType, script, {
        url: window.location.href,
      });
    }

    try {
      // In case of initial about:blank documents, inject immediately without
      // awaiting the runAt logic in the blocks below, to avoid getting stuck
      // due to https://bugzilla.mozilla.org/show_bug.cgi?id=1900222#c7
      // This is only relevant for dynamic code execution because declarative
      // content scripts do not run on initial about:blank - bug 1415539).
      if (!window.document.isInitialDocument) {
        if (this.runAt === "document_end") {
          await promiseDocumentReady(window.document);
        } else if (this.runAt === "document_idle") {
          await Promise.race([
            promiseDocumentIdle(window),
            promiseDocumentLoaded(window.document),
          ]);
        }
      }

      return this.inject(context, reportExceptions);
    } catch (e) {
      return Promise.reject(context.normalizeError(e));
    }
  }

  /**
   * Tries to inject this script into the given window and sandbox, if
   * there are pending operations for the window's current load state.
   *
   * @param {ContentScriptContextChild} context
   *        The content script context into which to inject the scripts.
   * @param {boolean} reportExceptions
   *        Defaults to true and reports any exception directly to the console
   *        and no exception will be thrown out of this function.
   * @returns {Promise<any>}
   *        Resolves to the last value in the evaluated script, when
   *        execution is complete.
   */
  async inject(context, reportExceptions = true) {
    // NOTE: Avoid unnecessary use of "await" in this function, because doing
    // so can delay script execution beyond the scheduled point. In particular,
    // document_start scripts should run "immediately" in most cases.

    DocumentManager.lazyInit();
    if (this.requiresCleanup) {
      context.addScript(this);
    }

    const { cssCodeHash } = this;

    let cssPromise;
    if (this.css.length || cssCodeHash) {
      let window = context.contentWindow;
      let { windowUtils } = window;

      let type =
        this.cssOrigin === "user"
          ? windowUtils.USER_SHEET
          : windowUtils.AUTHOR_SHEET;

      if (this.removeCSS) {
        for (let url of this.css) {
          this.cssCache.deleteDocument(url, window.document);

          runSafeSyncWithoutClone(
            windowUtils.removeSheetUsingURIString,
            url,
            type
          );
        }

        if (cssCodeHash && this.cssCodeCache.has(cssCodeHash)) {
          const { uri } = await this.cssCodeCache.get(cssCodeHash);
          this.cssCodeCache.deleteDocument(cssCodeHash, window.document);

          runSafeSyncWithoutClone(windowUtils.removeSheet, uri, type);
        }
      } else {
        cssPromise = Promise.all(this.loadCSS()).then(sheets => {
          let window = context.contentWindow;
          if (!window) {
            return;
          }

          for (let { url, sheet } of sheets) {
            this.cssCache.addDocument(url, window.document);

            runSafeSyncWithoutClone(windowUtils.addSheet, sheet, type);
          }
        });

        if (cssCodeHash) {
          cssPromise = cssPromise.then(async () => {
            const { sheet } = await this.cssCodeCache.get(cssCodeHash);
            this.cssCodeCache.addDocument(cssCodeHash, window.document);

            runSafeSyncWithoutClone(windowUtils.addSheet, sheet, type);
          });
        }

        // We're loading stylesheets via the stylesheet service, which means
        // that the normal mechanism for blocking layout and onload for pending
        // stylesheets aren't in effect (since there's no document to block). So
        // we need to do something custom here, similar to what we do for
        // scripts. Blocking parsing is overkill, since we really just want to
        // block layout and onload. But we have an API to do the former and not
        // the latter, so we do it that way. This hopefully isn't a performance
        // problem since there are no network loads involved, and since we cache
        // the stylesheets on first load. We should fix this up if it does becomes
        // a problem.
        if (this.css.length) {
          context.contentWindow.document.blockParsing(cssPromise, {
            blockScriptCreated: false,
          });
        }
      }
    }

    let scripts = this.getCompiledScripts(context);
    if (scripts instanceof Promise) {
      // Note: in theory, the following async await could result in script
      // execution being scheduled too late. That would be an issue for
      // document_start scripts. In practice, this is not a problem because the
      // compiled script is cached in the process, and preloading to compile
      // starts as soon as the network request for the document has been
      // received (see ExtensionPolicyService::CheckRequest).
      // getCompiledScripts() uses blockParsing() for document_start scripts to
      // ensure that the DOM remains blocked when scripts are still compiling.
      scripts = await scripts;
    }

    if (cssPromise) {
      // Make sure we've injected any related CSS before we run content scripts.
      await cssPromise;
    }

    const { extension } = context;

    // The evaluations below may throw, in which case the promise will be
    // automatically rejected.
    lazy.ExtensionTelemetry.contentScriptInjection.stopwatchStart(
      extension,
      context
    );
    try {
      if (this.world === "MAIN") {
        return this.#injectIntoMainWorld(context, scripts, reportExceptions);
      }
      return this.#injectIntoIsolatedWorld(context, scripts, reportExceptions);
    } finally {
      lazy.ExtensionTelemetry.contentScriptInjection.stopwatchFinish(
        extension,
        context
      );
    }
  }

  #injectIntoIsolatedWorld(context, scripts, reportExceptions) {
    let result;

    // Note: every script execution can potentially destroy the context, in
    // which case context.cloneScope becomes null (bug 1403505).
    for (let script of scripts) {
      result = script.executeInGlobal(context.cloneScope, { reportExceptions });
    }

    if (this.jsCode) {
      result = Cu.evalInSandbox(
        this.jsCode,
        context.cloneScope,
        "latest",
        // TODO bug 1651557: Use dynamic name to improve debugger experience.
        "sandbox eval code",
        1
      );
    }

    return result;
  }

  #injectIntoMainWorld(context, scripts, reportExceptions) {
    let result;

    // Note: every script execution can potentially destroy the context or
    // navigate the window, in which case context.contentWindow will be null,
    // which would cause an error to be thrown (bug 1403505).
    for (let script of scripts) {
      result = script.executeInGlobal(context.contentWindow, {
        reportExceptions,
      });
    }

    // Note: string-based code execution (=our implementation of func+args in
    // scripting.executeScript) is not handled here, because we compile it in
    // addJSCode() and include it in the scripts array via getCompiledScripts().
    // We cannot use context.contentWindow.eval() here because the web page's
    // CSP may block it.

    return result;
  }

  /**
   *  Get the compiled scripts (if they are already precompiled and cached) or a promise which resolves
   *  to the precompiled scripts (once they have been compiled and cached).
   *
   * @param {ContentScriptContextChild} context
   *        The document to block the parsing on, if the scripts are not yet precompiled and cached.
   *
   * @returns {PrecompiledScript[] | Promise<PrecompiledScript[]>}
   *          Returns an array of preloaded scripts if they are already available, or a promise which
   *          resolves to the array of the preloaded scripts once they are precompiled and cached.
   */
  getCompiledScripts(context) {
    let scriptPromises = this.compileScripts();
    if (this.jsCodeCompiledScript) {
      scriptPromises.push(this.jsCodeCompiledScript);
    }
    let scripts = scriptPromises.map(promise => promise.script);

    // If not all scripts are already available in the cache, block
    // parsing and wait all promises to resolve.
    if (!scripts.every(script => script)) {
      let promise = Promise.all(scriptPromises);

      // If there is any syntax error, the script promises will be rejected.
      //
      // Notify the exception directly to the console so that it can
      // be displayed in the web console by flagging the error with the right
      // innerWindowID.
      for (const p of scriptPromises) {
        p.catch(error => {
          Services.console.logMessage(
            new ScriptError(
              error.toString(),
              error.fileName,
              error.lineNumber,
              error.columnNumber,
              Ci.nsIScriptError.errorFlag,
              "content javascript",
              context.innerWindowID
            )
          );
        });
      }

      // If we're supposed to inject at the start of the document load,
      // and we haven't already missed that point, block further parsing
      // until the scripts have been loaded.
      const { document } = context.contentWindow;
      if (
        this.runAt === "document_start" &&
        document.readyState !== "complete"
      ) {
        document.blockParsing(promise, { blockScriptCreated: false });
      }

      return promise;
    }

    return scripts;
  }
}

// Represents a user script.
class UserScript extends Script {
  /**
   * @param {ExtensionChild & ExtensionChildContent} extension
   * @param {WebExtensionContentScript|object} matcher
   *        An object with a "matchesWindowGlobal" method and content script
   *        execution details.
   */
  constructor(extension, matcher) {
    super(extension, matcher);
    this.scriptType = "user_script";

    // This is an opaque object that the extension provides, it is associated to
    // the particular userScript and it is passed as a parameter to the custom
    // userScripts APIs defined by the extension.
    this.scriptMetadata = matcher.userScriptOptions.scriptMetadata;
    this.apiScriptURL =
      extension.manifest.user_scripts &&
      extension.manifest.user_scripts.api_script;

    // Add the apiScript to the js scripts to compile.
    if (this.apiScriptURL) {
      this.js = [this.apiScriptURL].concat(this.js);
    }

    // WeakMap<ContentScriptContextChild, Sandbox>
    this.sandboxes = new DefaultWeakMap(context => {
      return this.createSandbox(context);
    });
  }

  async inject(context) {
    DocumentManager.lazyInit();

    let scripts = this.getCompiledScripts(context);
    if (scripts instanceof Promise) {
      scripts = await scripts;
    }

    let apiScript, sandboxScripts;

    if (this.apiScriptURL) {
      [apiScript, ...sandboxScripts] = scripts;
    } else {
      sandboxScripts = scripts;
    }

    // Load and execute the API script once per context.
    if (apiScript) {
      context.executeAPIScript(apiScript);
    }

    let userScriptSandbox = this.sandboxes.get(context);

    context.callOnClose({
      close: () => {
        // Destroy the userScript sandbox when the related ContentScriptContextChild instance
        // is being closed.
        this.sandboxes.delete(context);
        Cu.nukeSandbox(userScriptSandbox);
      },
    });

    // Notify listeners subscribed to the userScripts.onBeforeScript API event,
    // to allow extension API script to provide its custom APIs to the userScript.
    if (apiScript) {
      context.userScriptsEvents.emit(
        "on-before-script",
        this.scriptMetadata,
        userScriptSandbox
      );
    }

    for (let script of sandboxScripts) {
      script.executeInGlobal(userScriptSandbox);
    }
  }

  createSandbox(context) {
    const { contentWindow } = context;
    const contentPrincipal = contentWindow.document.nodePrincipal;
    const ssm = Services.scriptSecurityManager;

    let principal;
    if (contentPrincipal.isSystemPrincipal) {
      principal = ssm.createNullPrincipal(contentPrincipal.originAttributes);
    } else {
      principal = [contentPrincipal];
    }

    const sandbox = Cu.Sandbox(principal, {
      sandboxName: `User Script registered by ${this.extension.policy.debugName}`,
      sandboxPrototype: contentWindow,
      sameZoneAs: contentWindow,
      wantXrays: true,
      wantGlobalProperties: ["XMLHttpRequest", "fetch", "WebSocket"],
      originAttributes: contentPrincipal.originAttributes,
      metadata: {
        "inner-window-id": context.innerWindowID,
        addonId: this.extension.policy.id,
      },
    });

    return sandbox;
  }
}

var contentScripts = new DefaultWeakMap(matcher => {
  const extension = lazy.ExtensionProcessScript.extensions.get(
    matcher.extension
  );

  if ("userScriptOptions" in matcher) {
    return new UserScript(extension, matcher);
  }

  return new Script(extension, matcher);
});

/**
 * An execution context for semi-privileged extension content scripts.
 *
 * This is the child side of the ContentScriptContextParent class
 * defined in ExtensionParent.sys.mjs.
 */
class ContentScriptContextChild extends BaseContext {
  constructor(extension, contentWindow) {
    super("content_child", extension);

    this.setContentWindow(contentWindow);

    let frameId = lazy.WebNavigationFrames.getFrameId(contentWindow);
    this.frameId = frameId;

    this.browsingContextId = contentWindow.docShell.browsingContext.id;

    this.scripts = [];

    let contentPrincipal = contentWindow.document.nodePrincipal;
    let ssm = Services.scriptSecurityManager;

    // Copy origin attributes from the content window origin attributes to
    // preserve the user context id.
    let attrs = contentPrincipal.originAttributes;
    let extensionPrincipal = ssm.createContentPrincipal(
      this.extension.baseURI,
      attrs
    );

    this.isExtensionPage = contentPrincipal.equals(extensionPrincipal);

    if (this.isExtensionPage) {
      // This is an iframe with content script API enabled and its principal
      // should be the contentWindow itself. We create a sandbox with the
      // contentWindow as principal and with X-rays disabled because it
      // enables us to create the APIs object in this sandbox object and then
      // copying it into the iframe's window.  See bug 1214658.
      this.sandbox = Cu.Sandbox(contentWindow, {
        sandboxName: `Web-Accessible Extension Page ${extension.policy.debugName}`,
        sandboxPrototype: contentWindow,
        sameZoneAs: contentWindow,
        wantXrays: false,
        isWebExtensionContentScript: true,
      });
    } else {
      let principal;
      if (contentPrincipal.isSystemPrincipal) {
        // Make sure we don't hand out the system principal by accident.
        // Also make sure that the null principal has the right origin attributes.
        principal = ssm.createNullPrincipal(attrs);
      } else {
        principal = [contentPrincipal, extensionPrincipal];
      }
      // This metadata is required by the Developer Tools, in order for
      // the content script to be associated with both the extension and
      // the tab holding the content page.
      let metadata = {
        "inner-window-id": this.innerWindowID,
        addonId: extensionPrincipal.addonId,
      };

      let isMV2 = extension.manifestVersion == 2;
      let wantGlobalProperties;
      if (isMV2) {
        // In MV2, fetch/XHR support cross-origin requests.
        // WebSocket was also included to avoid CSP effects (bug 1676024).
        wantGlobalProperties = ["XMLHttpRequest", "fetch", "WebSocket"];
      } else {
        // In MV3, fetch/XHR have the same capabilities as the web page.
        wantGlobalProperties = [];
      }
      this.sandbox = Cu.Sandbox(principal, {
        metadata,
        sandboxName: `Content Script ${extension.policy.debugName}`,
        sandboxPrototype: contentWindow,
        sameZoneAs: contentWindow,
        wantXrays: true,
        isWebExtensionContentScript: true,
        wantExportHelpers: true,
        wantGlobalProperties,
        originAttributes: attrs,
      });

      // Preserve a copy of the original Error and Promise globals from the sandbox object,
      // which are used in the WebExtensions internals (before any content script code had
      // any chance to redefine them).
      this.cloneScopePromise = this.sandbox.Promise;
      this.cloneScopeError = this.sandbox.Error;

      if (isMV2) {
        // Preserve a copy of the original window's XMLHttpRequest and fetch
        // in a content object (fetch is manually binded to the window
        // to prevent it from raising a TypeError because content object is not
        // a real window).
        Cu.evalInSandbox(
          `
          this.content = {
            XMLHttpRequest: window.XMLHttpRequest,
            fetch: window.fetch.bind(window),
            WebSocket: window.WebSocket,
          };

          window.JSON = JSON;
          window.XMLHttpRequest = XMLHttpRequest;
          window.fetch = fetch;
          window.WebSocket = WebSocket;
        `,
          this.sandbox
        );
      } else {
        // The sandbox's JSON API can deal with values from the sandbox and the
        // contentWindow, but window.JSON cannot (and it could potentially be
        // spoofed by the web page). jQuery.parseJSON relies on window.JSON.
        Cu.evalInSandbox("window.JSON = JSON;", this.sandbox);
      }
    }

    Object.defineProperty(this, "principal", {
      value: Cu.getObjectPrincipal(this.sandbox),
      enumerable: true,
      configurable: true,
    });

    this.url = contentWindow.location.href;

    lazy.Schemas.exportLazyGetter(
      this.sandbox,
      "browser",
      () => this.chromeObj
    );
    lazy.Schemas.exportLazyGetter(this.sandbox, "chrome", () => this.chromeObj);

    // Keep track if the userScript API script has been already executed in this context
    // (e.g. because there are more then one UserScripts that match the related webpage
    // and so the UserScript apiScript has already been executed).
    this.hasUserScriptAPIs = false;

    // A lazy created EventEmitter related to userScripts-specific events.
    defineLazyGetter(this, "userScriptsEvents", () => {
      return new ExtensionCommon.EventEmitter();
    });
  }

  injectAPI() {
    if (!this.isExtensionPage) {
      throw new Error("Cannot inject extension API into non-extension window");
    }

    // This is an iframe with content script API enabled (See Bug 1214658)
    lazy.Schemas.exportLazyGetter(
      this.contentWindow,
      "browser",
      () => this.chromeObj
    );
    lazy.Schemas.exportLazyGetter(
      this.contentWindow,
      "chrome",
      () => this.chromeObj
    );
  }

  async logActivity(type, name, data) {
    ExtensionActivityLogChild.log(this, type, name, data);
  }

  get cloneScope() {
    return this.sandbox;
  }

  async executeAPIScript(apiScript) {
    // Execute the UserScript apiScript only once per context (e.g. more then one UserScripts
    // match the same webpage and the apiScript has already been executed).
    if (apiScript && !this.hasUserScriptAPIs) {
      this.hasUserScriptAPIs = true;
      apiScript.executeInGlobal(this.cloneScope);
    }
  }

  addScript(script) {
    if (script.requiresCleanup) {
      this.scripts.push(script);
    }
  }

  close() {
    super.unload();

    // Cleanup the scripts even if the contentWindow have been destroyed.
    for (let script of this.scripts) {
      script.cleanup(this.contentWindow);
    }

    if (this.contentWindow) {
      // Overwrite the content script APIs with an empty object if the APIs objects are still
      // defined in the content window (See Bug 1214658).
      if (this.isExtensionPage) {
        Cu.createObjectIn(this.contentWindow, { defineAs: "browser" });
        Cu.createObjectIn(this.contentWindow, { defineAs: "chrome" });
      }
    }
    Cu.nukeSandbox(this.sandbox);

    this.sandbox = null;
  }

  get childManager() {
    apiManager.lazyInit();
    let can = new CanOfAPIs(this, apiManager, {});
    let childManager = new ChildAPIManager(this, this.messageManager, can, {
      envType: "content_parent",
      url: this.url,
    });
    this.callOnClose(childManager);
    return redefineGetter(this, "childManager", childManager);
  }

  get chromeObj() {
    let chromeObj = Cu.createObjectIn(this.sandbox);
    this.childManager.inject(chromeObj);
    return redefineGetter(this, "chromeObj", chromeObj);
  }

  get messenger() {
    return redefineGetter(this, "messenger", new Messenger(this));
  }
}

// Responsible for creating ExtensionContexts and injecting content
// scripts into them when new documents are created.
DocumentManager = {
  /** @type {Map<number, Map<ExtensionChild, ContentScriptContextChild>>} */
  contexts: new Map(),

  initialized: false,

  lazyInit() {
    if (this.initialized) {
      return;
    }
    this.initialized = true;

    Services.obs.addObserver(this, "inner-window-destroyed");
    Services.obs.addObserver(this, "memory-pressure");
  },

  uninit() {
    Services.obs.removeObserver(this, "inner-window-destroyed");
    Services.obs.removeObserver(this, "memory-pressure");
  },

  observers: {
    "inner-window-destroyed"(subject) {
      let windowId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;

      // Close any existent content-script context for the destroyed window.
      if (this.contexts.has(windowId)) {
        let extensions = this.contexts.get(windowId);
        for (let context of extensions.values()) {
          context.close();
        }

        this.contexts.delete(windowId);
      }
    },
    "memory-pressure"(subject, topic, data) {
      let timeout = data === "heap-minimize" ? 0 : undefined;

      for (let cache of ChromeUtils.nondeterministicGetWeakSetKeys(
        scriptCaches
      )) {
        cache.clear(timeout);
      }
    },
  },

  /**
   * @param {object} subject
   * @param {keyof typeof DocumentManager.observers} topic
   * @param {any} data
   */
  observe(subject, topic, data) {
    this.observers[topic].call(this, subject, topic, data);
  },

  shutdownExtension(extension) {
    for (let extensions of this.contexts.values()) {
      let context = extensions.get(extension);
      if (context) {
        context.close();
        extensions.delete(extension);
      }
    }
  },

  getContexts(window) {
    let winId = getInnerWindowID(window);

    let extensions = this.contexts.get(winId);
    if (!extensions) {
      extensions = new Map();
      this.contexts.set(winId, extensions);
    }

    return extensions;
  },

  // For test use only.
  getContext(extensionId, window) {
    for (let [extension, context] of this.getContexts(window)) {
      if (extension.id === extensionId) {
        return context;
      }
    }
  },

  getContentScriptGlobals(window) {
    let extensions = this.contexts.get(getInnerWindowID(window));

    if (extensions) {
      return Array.from(extensions.values(), ctx => ctx.sandbox);
    }

    return [];
  },

  initExtensionContext(extension, window) {
    extension.getContext(window).injectAPI();
  },
};

export var ExtensionContent = {
  contentScripts,

  shutdownExtension(extension) {
    DocumentManager.shutdownExtension(extension);
  },

  // This helper is exported to be integrated in the devtools RDP actors,
  // that can use it to retrieve the existent WebExtensions ContentScripts
  // of a target window and be able to show the ContentScripts source in the
  // DevTools Debugger panel.
  getContentScriptGlobals(window) {
    return DocumentManager.getContentScriptGlobals(window);
  },

  initExtensionContext(extension, window) {
    DocumentManager.initExtensionContext(extension, window);
  },

  getContext(extension, window) {
    let extensions = DocumentManager.getContexts(window);

    let context = extensions.get(extension);
    if (!context) {
      context = new ContentScriptContextChild(extension, window);
      extensions.set(extension, context);
    }
    return context;
  },

  // For test use only.
  getContextByExtensionId(extensionId, window) {
    return DocumentManager.getContext(extensionId, window);
  },

  async handleDetectLanguage({ windows }) {
    let wgc = WindowGlobalChild.getByInnerWindowId(windows[0]);
    let doc = wgc.browsingContext.window.document;
    await promiseDocumentReady(doc);

    // The CLD2 library can analyze HTML, but that uses more memory, and
    // emscripten can't shrink its heap, so we use plain text instead.
    let encoder = Cu.createDocumentEncoder("text/plain");
    encoder.init(doc, "text/plain", Ci.nsIDocumentEncoder.SkipInvisibleContent);

    let result = await lazy.LanguageDetector.detectLanguage({
      language:
        doc.documentElement.getAttribute("xml:lang") ||
        doc.documentElement.getAttribute("lang") ||
        doc.contentLanguage ||
        null,
      tld: doc.location.hostname.match(/[a-z]*$/)[0],
      text: encoder.encodeToStringWithMaxLength(60 * 1024),
      encoding: doc.characterSet,
    });
    return result.language === "un" ? "und" : result.language;
  },

  // Activate MV3 content scripts in all same-origin frames for this tab.
  handleActivateScripts({ options, windows }) {
    let policy = WebExtensionPolicy.getByID(options.id);

    // Order content scripts by run_at timing.
    let runAt = { document_start: [], document_end: [], document_idle: [] };
    for (let matcher of policy.contentScripts) {
      runAt[matcher.runAt].push(this.contentScripts.get(matcher));
    }

    // If we got here, checks in TabManagerBase.activateScripts assert:
    // 1) this is a MV3 extension, with Origin Controls,
    // 2) with a host permission (or content script) for the tab's top origin,
    // 3) and that host permission hasn't been granted yet.

    // We treat the action click as implicit user's choice to activate the
    // extension on the current site, so we can safely run (matching) content
    // scripts in all sameOriginWithTop frames while ignoring host permission.

    let { browsingContext } = WindowGlobalChild.getByInnerWindowId(windows[0]);
    for (let bc of browsingContext.getAllBrowsingContextsInSubtree()) {
      let wgc = bc.currentWindowContext.windowGlobalChild;
      if (wgc?.sameOriginWithTop) {
        // This is TOCTOU safe: if a frame navigated after same-origin check,
        // wgc.isClosed would be true and .matchesWindowGlobal() would fail.
        const runScript = cs => {
          if (cs.matchesWindowGlobal(wgc, /* ignorePermissions */ true)) {
            return cs.injectInto(bc.window);
          }
        };

        // Inject all matching content scripts in proper run_at order.
        Promise.all(runAt.document_start.map(runScript))
          .then(() => Promise.all(runAt.document_end.map(runScript)))
          .then(() => Promise.all(runAt.document_idle.map(runScript)));
      }
    }
  },

  // Used to executeScript, insertCSS and removeCSS.
  async handleActorExecute({ options, windows }) {
    let policy = WebExtensionPolicy.getByID(options.extensionId);
    // `WebExtensionContentScript` uses `MozDocumentMatcher::Matches` to ensure
    // that a script can be run in a document. That requires either `frameId`
    // or `allFrames` to be set. When `frameIds` (plural) is used, we force
    // `allFrames` to be `true` in order to match any frame. This is OK because
    // `executeInWin()` below looks up the window for the given `frameIds`
    // immediately before `script.injectInto()`. Due to this, we won't run
    // scripts in windows with non-matching `frameId`, despite `allFrames`
    // being set to `true`.
    if (options.frameIds) {
      options.allFrames = true;
    }
    let matcher = new WebExtensionContentScript(policy, options);

    Object.assign(matcher, {
      wantReturnValue: options.wantReturnValue,
      removeCSS: options.removeCSS,
      cssOrigin: options.cssOrigin,
    });
    let script = contentScripts.get(matcher);

    if (options.jsCode) {
      script.addJSCode(options.jsCode);
      delete options.jsCode;
    }

    // Add the cssCode to the script, so that it can be converted into a cached URL.
    await script.addCSSCode(options.cssCode);
    delete options.cssCode;

    const executeInWin = innerId => {
      let wg = WindowGlobalChild.getByInnerWindowId(innerId);
      if (wg?.isCurrentGlobal && script.matchesWindowGlobal(wg)) {
        let bc = wg.browsingContext;

        return {
          frameId: bc.parent ? bc.id : 0,
          // Disable exception reporting directly to the console
          // in order to pass the exceptions back to the callsite.
          promise: script.injectInto(bc.window, false),
        };
      }
    };

    let promisesWithFrameIds = windows.map(executeInWin).filter(obj => obj);

    let result = await Promise.all(
      promisesWithFrameIds.map(async ({ frameId, promise }) => {
        if (!options.returnResultsWithFrameIds) {
          return promise;
        }

        try {
          const result = await promise;

          return { frameId, result };
        } catch (error) {
          return { frameId, error };
        }
      })
    ).catch(
      // This is useful when we do not return results/errors with frame IDs in
      // the promises above.
      e => Promise.reject({ message: e.message })
    );

    try {
      // Check if the result can be structured-cloned before sending back.
      return Cu.cloneInto(result, this);
    } catch (e) {
      let path = options.jsPaths.slice(-1)[0] ?? "<anonymous code>";
      let message = `Script '${path}' result is non-structured-clonable data`;
      return Promise.reject({ message, fileName: path });
    }
  },
};

/**
 * Child side of the ExtensionContent process actor, handles some tabs.* APIs.
 */
export class ExtensionContentChild extends JSProcessActorChild {
  receiveMessage({ name, data }) {
    if (!lazy.isContentScriptProcess) {
      return;
    }
    switch (name) {
      case "DetectLanguage":
        return ExtensionContent.handleDetectLanguage(data);
      case "Execute":
        return ExtensionContent.handleActorExecute(data);
      case "ActivateScripts":
        return ExtensionContent.handleActivateScripts(data);
    }
  }
}
PK
!<�n�h�d�dmodules/ExtensionDNR.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Each extension that uses DNR has one RuleManager. All registered RuleManagers
// are checked whenever a network request occurs. Individual extensions may
// occasionally modify their rules (e.g. via the updateSessionRules API).
const gRuleManagers = [];

/**
 * Whenever a request occurs, the rules of each RuleManager are matched against
 * the request to determine the final action to take. The RequestEvaluator class
 * is responsible for evaluating rules, and its behavior is described below.
 *
 * Short version:
 * Find the highest-priority rule that matches the given request. If the
 * request is not canceled, all matching allowAllRequests and modifyHeaders
 * actions are returned.
 *
 * Longer version:
 * Unless stated otherwise, the explanation below describes the behavior within
 * an extension.
 * An extension can specify rules, optionally in multiple rulesets. The ability
 * to have multiple ruleset exists to support bulk updates of rules. Rulesets
 * are NOT independent - rules from different rulesets can affect each other.
 *
 * When multiple rules match, the order between rules are defined as follows:
 * - Ruleset precedence: session > dynamic > static (order from manifest.json).
 * - Rules in ruleset precedence: ordered by rule.id, lowest (numeric) ID first.
 * - Across all rules+rulesets: highest rule.priority (default 1) first,
 *                              action precedence if rule priority are the same.
 *
 * The primary documented way for extensions to describe precedence is by
 * specifying rule.priority. Between same-priority rules, their precedence is
 * dependent on the rule action. The ruleset/rule ID precedence is only used to
 * have a defined ordering if multiple rules have the same priority+action.
 *
 * Rule actions have the following order of precedence and meaning:
 * - "allow" can be used to ignore other same-or-lower-priority rules.
 * - "allowAllRequests" (for main_frame / sub_frame resourceTypes only) has the
 *      same effect as allow, but also applies to (future) subresource loads in
 *      the document (including descendant frames) generated from the request.
 * - "block" cancels the matched request.
 * - "upgradeScheme" upgrades the scheme of the request.
 * - "redirect" redirects the request.
 * - "modifyHeaders" rewrites request/response headers.
 *
 * The matched rules are evaluated in two passes:
 * 1. findMatchingRules():
 *    Find the highest-priority rule(s), and choose the action with the highest
 *    precedence (across all rulesets, any action except modifyHeaders).
 *    This also accounts for any allowAllRequests from an ancestor frame.
 *
 * 2. getMatchingModifyHeadersRules():
 *    Find matching rules with the "modifyHeaders" action, minus ignored rules.
 *    Reaching this step implies that the request was not canceled, so either
 *    the first step did not yield a rule, or the rule action is "allow" or
 *    "allowAllRequests" (i.e. ignore same-or-lower-priority rules).
 *
 * If an extension does not have sufficient permissions for the action, the
 * resulting action is ignored.
 *
 * The above describes the evaluation within one extension. When a sequence of
 * (multiple) extensions is given, they may return conflicting actions in the
 * first pass. This is resolved by choosing the action with the following order
 * of precedence, in RequestEvaluator.evaluateRequest():
 *  - block
 *  - redirect / upgradeScheme
 *  - allow / allowAllRequests
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

/** @type {Lazy} */
const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ExtensionDNRLimits: "resource://gre/modules/ExtensionDNRLimits.sys.mjs",
  ExtensionDNRStore: "resource://gre/modules/ExtensionDNRStore.sys.mjs",
  WebRequest: "resource://gre/modules/WebRequest.sys.mjs",
});

import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

const { DefaultWeakMap, ExtensionError } = ExtensionUtils;

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gMatchRequestsFromOtherExtensions",
  "extensions.dnr.match_requests_from_other_extensions",
  false
);

// As documented above:
// Ruleset precedence: session > dynamic > static (order from manifest.json).
const PRECEDENCE_SESSION_RULESET = 1;
const PRECEDENCE_DYNAMIC_RULESET = 2;
const PRECEDENCE_STATIC_RULESETS_BASE = 3;

// The RuleCondition class represents a rule's "condition" type as described in
// schemas/declarative_net_request.json. This class exists to allow the JS
// engine to use one Shape for all Rule instances.
class RuleCondition {
  #compiledUrlFilter;
  #compiledRegexFilter;

  constructor(cond) {
    this.urlFilter = cond.urlFilter;
    this.regexFilter = cond.regexFilter;
    this.isUrlFilterCaseSensitive = cond.isUrlFilterCaseSensitive;
    this.initiatorDomains = cond.initiatorDomains;
    this.excludedInitiatorDomains = cond.excludedInitiatorDomains;
    this.requestDomains = cond.requestDomains;
    this.excludedRequestDomains = cond.excludedRequestDomains;
    this.resourceTypes = cond.resourceTypes;
    this.excludedResourceTypes = cond.excludedResourceTypes;
    this.requestMethods = cond.requestMethods;
    this.excludedRequestMethods = cond.excludedRequestMethods;
    this.domainType = cond.domainType;
    this.tabIds = cond.tabIds;
    this.excludedTabIds = cond.excludedTabIds;
  }

  // See CompiledUrlFilter for documentation.
  urlFilterMatches(requestDataForUrlFilter) {
    if (!this.#compiledUrlFilter) {
      // eslint-disable-next-line no-use-before-define
      this.#compiledUrlFilter = new CompiledUrlFilter(
        this.urlFilter,
        this.isUrlFilterCaseSensitive
      );
    }
    return this.#compiledUrlFilter.matchesRequest(requestDataForUrlFilter);
  }

  // Used for testing regexFilter matches in RuleEvaluator.#matchRuleCondition
  // and to get redirect URL from regexSubstitution in applyRegexSubstitution.
  getCompiledRegexFilter() {
    return this.#compiledRegexFilter;
  }

  // RuleValidator compiles regexFilter before this Rule class is instantiated.
  // To avoid unnecessarily compiling it again, the result is assigned here.
  setCompiledRegexFilter(compiledRegexFilter) {
    this.#compiledRegexFilter = compiledRegexFilter;
  }
}

export class Rule {
  constructor(rule) {
    this.id = rule.id;
    this.priority = rule.priority;
    this.condition = new RuleCondition(rule.condition);
    this.action = rule.action;
  }

  // The precedence of rules within an extension. This method is frequently
  // used during the first pass of the RequestEvaluator.
  actionPrecedence() {
    switch (this.action.type) {
      case "allow":
        return 1; // Highest precedence.
      case "allowAllRequests":
        return 2;
      case "block":
        return 3;
      case "upgradeScheme":
        return 4;
      case "redirect":
        return 5;
      case "modifyHeaders":
        return 6;
      default:
        throw new Error(`Unexpected action type: ${this.action.type}`);
    }
  }

  isAllowOrAllowAllRequestsAction() {
    const type = this.action.type;
    return type === "allow" || type === "allowAllRequests";
  }
}

class Ruleset {
  /**
   * @typedef {number} integer
   *
   * @param {string} rulesetId - extension-defined ruleset ID.
   * @param {integer} rulesetPrecedence
   * @param {Rule[]} rules - extension-defined rules
   * @param {Set<Rule> | null} disabledRuleIds - An optional set of disabled rule ids
   * @param {RuleManager} ruleManager - owner of this ruleset.
   */
  constructor(
    rulesetId,
    rulesetPrecedence,
    rules,
    disabledRuleIds,
    ruleManager
  ) {
    this.id = rulesetId;
    this.rulesetPrecedence = rulesetPrecedence;
    this.rules = rules;
    this.disabledRuleIds = disabledRuleIds;
    // For use by MatchedRule.
    this.ruleManager = ruleManager;
  }
}

/**
 * @param {string} uriQuery - The query of a nsIURI to transform.
 * @param {object} queryTransform - The value of the
 *   Rule.action.redirect.transform.queryTransform property as defined in
 *   declarative_net_request.json.
 * @returns {string} The uriQuery with the queryTransform applied to it.
 */
function applyQueryTransform(uriQuery, queryTransform) {
  // URLSearchParams cannot be applied to the full query string, because that
  // API formats the full query string using form-urlencoding. But the input
  // may be in a different format. So we try to only modify matched params.

  function urlencode(s) {
    // Encode in application/x-www-form-urlencoded format.
    // The only JS API to do that is URLSearchParams. encodeURIComponent is not
    // the same, it differs in how it handles " " ("%20") and "!'()~" (raw).
    // But urlencoded space should be "+" and the latter be "%21%27%28%29%7E".
    return new URLSearchParams({ s }).toString().slice(2);
  }
  if (!uriQuery.length && !queryTransform.addOrReplaceParams) {
    // Nothing to do.
    return "";
  }
  const removeParamsSet = new Set(queryTransform.removeParams?.map(urlencode));
  const addParams = (queryTransform.addOrReplaceParams || []).map(orig => ({
    normalizedKey: urlencode(orig.key),
    orig,
  }));
  const finalParams = [];
  if (uriQuery.length) {
    for (let part of uriQuery.split("&")) {
      let key = part.split("=", 1)[0];
      if (removeParamsSet.has(key)) {
        continue;
      }
      let i = addParams.findIndex(p => p.normalizedKey === key);
      if (i !== -1) {
        // Replace found param with the key-value from addOrReplaceParams.
        finalParams.push(`${key}=${urlencode(addParams[i].orig.value)}`);
        // Omit param so that a future search for the same key can find the next
        // specified key-value pair, if any. And to prevent the already-used
        // key-value pairs from being appended after the loop.
        addParams.splice(i, 1);
      } else {
        finalParams.push(part);
      }
    }
  }
  // Append remaining, unused key-value pairs.
  for (let { normalizedKey, orig } of addParams) {
    if (!orig.replaceOnly) {
      finalParams.push(`${normalizedKey}=${urlencode(orig.value)}`);
    }
  }
  return finalParams.length ? `?${finalParams.join("&")}` : "";
}

/**
 * @param {nsIURI} uri - Usually a http(s) URL.
 * @param {object} transform - The value of the Rule.action.redirect.transform
 *   property as defined in declarative_net_request.json.
 * @returns {nsIURI} uri - The new URL.
 * @throws if the transformation is invalid.
 */
function applyURLTransform(uri, transform) {
  let mut = uri.mutate();
  if (transform.scheme) {
    // Note: declarative_net_request.json only allows http(s)/moz-extension:.
    mut.setScheme(transform.scheme);
    if (uri.port !== -1 || transform.port) {
      // If the URI contains a port or transform.port was specified, the default
      // port is significant. So we must set it in that case.
      if (transform.scheme === "https") {
        mut.QueryInterface(Ci.nsIStandardURLMutator).setDefaultPort(443);
      } else if (transform.scheme === "http") {
        mut.QueryInterface(Ci.nsIStandardURLMutator).setDefaultPort(80);
      }
    }
  }
  if (transform.username != null) {
    mut.setUsername(transform.username);
  }
  if (transform.password != null) {
    mut.setPassword(transform.password);
  }
  if (transform.host != null) {
    mut.setHost(transform.host);
  }
  if (transform.port != null) {
    // The caller ensures that transform.port is a string consisting of digits
    // only. When it is an empty string, it should be cleared (-1).
    mut.setPort(transform.port || -1);
  }
  if (transform.path != null) {
    mut.setFilePath(transform.path);
  }
  if (transform.query != null) {
    mut.setQuery(transform.query);
  } else if (transform.queryTransform) {
    mut.setQuery(applyQueryTransform(uri.query, transform.queryTransform));
  }
  if (transform.fragment != null) {
    mut.setRef(transform.fragment);
  }
  return mut.finalize();
}

/**
 * @param {nsIURI} uri - Usually a http(s) URL.
 * @param {MatchedRule} matchedRule - The matched rule with a regexFilter
 *   condition and regexSubstitution action.
 * @returns {nsIURI} The new URL derived from the regexSubstitution combined
 *   with capturing group from regexFilter applied to the input uri.
 * @throws if the resulting URL is an invalid redirect target.
 */
function applyRegexSubstitution(uri, matchedRule) {
  const rule = matchedRule.rule;
  const extension = matchedRule.ruleManager.extension;
  const regexSubstitution = rule.action.redirect.regexSubstitution;
  const compiledRegexFilter = rule.condition.getCompiledRegexFilter();
  // This method being called implies that regexFilter matched, so |matches| is
  // always non-null, i.e. an array of string/undefined values.
  const matches = compiledRegexFilter.exec(uri.spec);

  let redirectUrl = regexSubstitution.replace(/\\(.)/g, (_, char) => {
    // #checkActionRedirect ensures that every \ is followed by a \ or digit.
    return char === "\\" ? char : matches[char] ?? "";
  });

  // Throws if the URL is invalid:
  let redirectUri;
  try {
    redirectUri = Services.io.newURI(redirectUrl);
  } catch (e) {
    throw new Error(
      `Extension ${extension.id} tried to redirect to an invalid URL: ${redirectUrl}`
    );
  }
  if (!extension.checkLoadURI(redirectUri, { dontReportErrors: true })) {
    throw new Error(
      `Extension ${extension.id} may not redirect to: ${redirectUrl}`
    );
  }
  return redirectUri;
}

/**
 * An urlFilter is a string pattern to match a canonical http(s) URL.
 * urlFilter matches anywhere in the string, unless an anchor is present:
 * - ||... ("Domain name anchor") - domain or subdomain starts with ...
 * - |... ("Left anchor") - URL starts with ...
 * - ...| ("Right anchor") - URL ends with ...
 *
 * Other than the anchors, the following special characters exist:
 * - ^ = end of URL, or any char except: alphanum _ - . % ("Separator")
 * - * = any number of characters ("Wildcard")
 *
 * Ambiguous cases (undocumented but actual Chrome behavior):
 * - Plain "||" is a domain name anchor, not left + empty + right anchor.
 * - "^" repeated at end of pattern: "^" matches end of URL only once.
 * - "^|" at end of pattern: "^" is allowed to match end of URL.
 *
 * Implementation details:
 * - CompiledUrlFilter's constructor (+#initializeUrlFilter) extracts the
 *   actual urlFilter and anchors, for matching against URLs later.
 * - RequestDataForUrlFilter class precomputes the URL / domain anchors to
 *   support matching more efficiently.
 * - CompiledUrlFilter's matchesRequest(request) checks whether the request is
 *   actually matched, using the precomputed information.
 *
 * The class was designed to minimize the number of string allocations during
 * request evaluation, because the matchesRequest method may be called very
 * often for every network request.
 */
class CompiledUrlFilter {
  #isUrlFilterCaseSensitive;
  #urlFilterParts; // = parts of urlFilter, minus anchors, split at "*".
  // isAnchorLeft and isAnchorDomain are mutually exclusive.
  #isAnchorLeft = false;
  #isAnchorDomain = false;
  #isAnchorRight = false;
  #isTrailingSeparator = false; // Whether urlFilter ends with "^".

  /**
   * @param {string} urlFilter - non-empty urlFilter
   * @param {boolean} [isUrlFilterCaseSensitive]
   */
  constructor(urlFilter, isUrlFilterCaseSensitive) {
    this.#isUrlFilterCaseSensitive = isUrlFilterCaseSensitive;
    this.#initializeUrlFilter(urlFilter, isUrlFilterCaseSensitive);
  }

  #initializeUrlFilter(urlFilter, isUrlFilterCaseSensitive) {
    let start = 0;
    let end = urlFilter.length;

    // First, trim the anchors off urlFilter.
    if (urlFilter[0] === "|") {
      if (urlFilter[1] === "|") {
        start = 2;
        this.#isAnchorDomain = true;
        // ^ will not revert to false below, because "||*" is already rejected
        // by RuleValidator's #checkCondUrlFilterAndRegexFilter method.
      } else {
        start = 1;
        this.#isAnchorLeft = true; // may revert to false below.
      }
    }
    if (end > start && urlFilter[end - 1] === "|") {
      --end;
      this.#isAnchorRight = true; // may revert to false below.
    }

    // Skip unnecessary wildcards, and adjust meaningless anchors accordingly:
    // "|*" and "*|" are not effective anchors, they could have been omitted.
    while (start < end && urlFilter[start] === "*") {
      ++start;
      this.#isAnchorLeft = false;
    }
    while (end > start && urlFilter[end - 1] === "*") {
      --end;
      this.#isAnchorRight = false;
    }

    // Special-case the last "^", so that the matching algorithm can rely on
    // the simple assumption that a "^" in the filter matches exactly one char:
    // The "^" at the end of the pattern is specified to match either one char
    // as usual, or as an anchor for the end of the URL (i.e. zero characters).
    this.#isTrailingSeparator = urlFilter[end - 1] === "^";

    let urlFilterWithoutAnchors = urlFilter.slice(start, end);
    if (!isUrlFilterCaseSensitive) {
      urlFilterWithoutAnchors = urlFilterWithoutAnchors.toLowerCase();
    }
    this.#urlFilterParts = urlFilterWithoutAnchors.split("*");
  }

  /**
   * Tests whether |request| matches the urlFilter.
   *
   * @param {RequestDataForUrlFilter} requestDataForUrlFilter
   * @returns {boolean} Whether the condition matches the URL.
   */
  matchesRequest(requestDataForUrlFilter) {
    const url = requestDataForUrlFilter.getUrl(this.#isUrlFilterCaseSensitive);
    const domainAnchors = requestDataForUrlFilter.domainAnchors;

    const urlFilterParts = this.#urlFilterParts;

    const REAL_END_OF_URL = url.length - 1; // minus trailing "^"

    // atUrlIndex is the position after the most recently matched part.
    // If a match is not found, it is -1 and we should return false.
    let atUrlIndex = 0;

    // The head always exists, potentially even an empty string.
    const head = urlFilterParts[0];
    if (this.#isAnchorLeft) {
      if (!this.#startsWithPart(head, url, 0)) {
        return false;
      }
      atUrlIndex = head.length;
    } else if (this.#isAnchorDomain) {
      atUrlIndex = this.#indexAfterDomainPart(head, url, domainAnchors);
    } else {
      atUrlIndex = this.#indexAfterPart(head, url, 0);
    }

    let previouslyAtUrlIndex = 0;
    for (let i = 1; i < urlFilterParts.length && atUrlIndex !== -1; ++i) {
      previouslyAtUrlIndex = atUrlIndex;
      atUrlIndex = this.#indexAfterPart(urlFilterParts[i], url, atUrlIndex);
    }
    if (atUrlIndex === -1) {
      return false;
    }
    if (atUrlIndex === url.length) {
      // We always append a "^" to the URL, so if the match is at the end of the
      // URL (REAL_END_OF_URL), only accept if the pattern ended with a "^".
      return this.#isTrailingSeparator;
    }
    if (!this.#isAnchorRight || atUrlIndex === REAL_END_OF_URL) {
      // Either not interested in the end, or already at the end of the URL.
      return true;
    }

    // #isAnchorRight is true but we are not at the end of the URL.
    // Backtrack once, to retry the last pattern (tail) with the end of the URL.

    const tail = urlFilterParts[urlFilterParts.length - 1];
    // The expected offset where the tail should be located.
    const expectedTailIndex = REAL_END_OF_URL - tail.length;
    // If #isTrailingSeparator is true, then accept the URL's trailing "^".
    const expectedTailIndexPlus1 = expectedTailIndex + 1;
    if (urlFilterParts.length === 1) {
      if (this.#isAnchorLeft) {
        // If matched, we would have returned at the REAL_END_OF_URL checks.
        return false;
      }
      if (this.#isAnchorDomain) {
        // The tail must be exactly at one of the domain anchors.
        return (
          (domainAnchors.includes(expectedTailIndex) &&
            this.#startsWithPart(tail, url, expectedTailIndex)) ||
          (this.#isTrailingSeparator &&
            domainAnchors.includes(expectedTailIndexPlus1) &&
            this.#startsWithPart(tail, url, expectedTailIndexPlus1))
        );
      }
      // head has no left/domain anchor, fall through.
    }
    // The tail is not left/domain anchored, accept it as long as it did not
    // overlap with an already-matched part of the URL.
    return (
      (expectedTailIndex > previouslyAtUrlIndex &&
        this.#startsWithPart(tail, url, expectedTailIndex)) ||
      (this.#isTrailingSeparator &&
        expectedTailIndexPlus1 > previouslyAtUrlIndex &&
        this.#startsWithPart(tail, url, expectedTailIndexPlus1))
    );
  }

  // Whether a character should match "^" in an urlFilter.
  // The "match end of URL" meaning of "^" is covered by #isTrailingSeparator.
  static #regexIsSep = /[^A-Za-z0-9_\-.%]/;

  #matchPartAt(part, url, urlIndex, sepStart) {
    if (sepStart === -1) {
      // Fast path.
      return url.startsWith(part, urlIndex);
    }
    if (urlIndex + part.length > url.length) {
      return false;
    }
    for (let i = 0; i < part.length; ++i) {
      let partChar = part[i];
      let urlChar = url[urlIndex + i];
      if (
        partChar !== urlChar &&
        (partChar !== "^" || !CompiledUrlFilter.#regexIsSep.test(urlChar))
      ) {
        return false;
      }
    }
    return true;
  }

  #startsWithPart(part, url, urlIndex) {
    const sepStart = part.indexOf("^");
    return this.#matchPartAt(part, url, urlIndex, sepStart);
  }

  #indexAfterPart(part, url, urlIndex) {
    let sepStart = part.indexOf("^");
    if (sepStart === -1) {
      // Fast path.
      let i = url.indexOf(part, urlIndex);
      return i === -1 ? i : i + part.length;
    }
    let maxUrlIndex = url.length - part.length;
    for (let i = urlIndex; i <= maxUrlIndex; ++i) {
      if (this.#matchPartAt(part, url, i, sepStart)) {
        return i + part.length;
      }
    }
    return -1;
  }

  #indexAfterDomainPart(part, url, domainAnchors) {
    const sepStart = part.indexOf("^");
    for (let offset of domainAnchors) {
      if (this.#matchPartAt(part, url, offset, sepStart)) {
        return offset + part.length;
      }
    }
    return -1;
  }
}

// See CompiledUrlFilter for documentation of RequestDataForUrlFilter.
class RequestDataForUrlFilter {
  /**
   * @param {string} requestURIspec - The URL to match against.
   */
  constructor(requestURIspec) {
    // "^" is appended, see CompiledUrlFilter's #initializeUrlFilter.
    this.urlAnyCase = requestURIspec + "^";
    this.urlLowerCase = this.urlAnyCase.toLowerCase();
    // For "||..." (Domain name anchor): where (sub)domains start in the URL.
    this.domainAnchors = this.#getDomainAnchors(this.urlAnyCase);
  }

  getUrl(isUrlFilterCaseSensitive) {
    return isUrlFilterCaseSensitive ? this.urlAnyCase : this.urlLowerCase;
  }

  #getDomainAnchors(url) {
    let hostStart = url.indexOf("://") + 3;
    let hostEnd = url.indexOf("/", hostStart);
    let userpassEnd = url.lastIndexOf("@", hostEnd) + 1;
    if (userpassEnd) {
      hostStart = userpassEnd;
    }
    let host = url.slice(hostStart, hostEnd);
    let domainAnchors = [hostStart];
    let offset = 0;
    // Find all offsets after ".". If not found, -1 + 1 = 0, and the loop ends.
    while ((offset = host.indexOf(".", offset) + 1)) {
      domainAnchors.push(hostStart + offset);
    }
    return domainAnchors;
  }
}

function compileRegexFilter(regexFilter, isUrlFilterCaseSensitive) {
  // TODO bug 1821033: Restrict supported regex to avoid perf issues. For
  // discussion on the desired syntax, see
  // https://github.com/w3c/webextensions/issues/344
  return new RegExp(regexFilter, isUrlFilterCaseSensitive ? "" : "i");
}

class ModifyHeadersBase {
  // Map<string,MatchedRule> - The first MatchedRule that modified the header.
  // After modifying a header, it cannot be modified further, with the exception
  // of the "append" operation, provided that they are from the same extension.
  #alreadyModifiedMap = new Map();
  // Set<string> - The list of headers allowed to be modified with "append",
  // despite having been modified. Allowed for "set"/"append", not for "remove".
  #appendStillAllowed = new Set();

  /**
   * @param {ChannelWrapper} channel
   */
  constructor(channel) {
    this.channel = channel;
  }

  /**
   * @param {MatchedRule} _matchedRule
   * @returns {object[]}
   */
  headerActionsFor(_matchedRule) {
    throw new Error("Not implemented.");
  }

  /**
   * @param {MatchedRule} _matchedrule
   * @param {string} _name
   * @param {string} _value
   * @param {boolean} _merge
   */
  setHeaderImpl(_matchedrule, _name, _value, _merge) {
    throw new Error("Not implemented.");
  }

  /** @param {MatchedRule[]} matchedRules */
  applyModifyHeaders(matchedRules) {
    for (const matchedRule of matchedRules) {
      for (const headerAction of this.headerActionsFor(matchedRule)) {
        const { header: name, operation, value } = headerAction;
        if (!this.#isOperationAllowed(name, operation, matchedRule)) {
          continue;
        }
        let ok;
        switch (operation) {
          case "set":
            ok = this.setHeader(matchedRule, name, value, /* merge */ false);
            if (ok) {
              this.#appendStillAllowed.add(name);
            }
            break;
          case "append":
            ok = this.setHeader(matchedRule, name, value, /* merge */ true);
            if (ok) {
              this.#appendStillAllowed.add(name);
            }
            break;
          case "remove":
            ok = this.setHeader(matchedRule, name, "", /* merge */ false);
            // Note: removal is final, so we don't add to #appendStillAllowed.
            break;
        }
        if (ok) {
          this.#alreadyModifiedMap.set(name, matchedRule);
        }
      }
    }
  }

  #isOperationAllowed(name, operation, matchedRule) {
    const modifiedBy = this.#alreadyModifiedMap.get(name);
    if (!modifiedBy) {
      return true;
    }
    if (
      operation === "append" &&
      this.#appendStillAllowed.has(name) &&
      matchedRule.ruleManager === modifiedBy.ruleManager
    ) {
      return true;
    }
    // TODO bug 1803369: dev experience improvement: consider logging when
    // a header modification was rejected.
    return false;
  }

  setHeader(matchedRule, name, value, merge) {
    try {
      this.setHeaderImpl(matchedRule, name, value, merge);
      return true;
    } catch (e) {
      const extension = matchedRule.ruleManager.extension;
      extension.logger.error(
        `Failed to apply modifyHeaders action to header "${name}" (DNR rule id ${matchedRule.rule.id} from ruleset "${matchedRule.ruleset.id}"): ${e}`
      );
    }
    return false;
  }

  // kName should already be in lower case.
  isHeaderNameEqual(name, kName) {
    return name.length === kName.length && name.toLowerCase() === kName;
  }
}

class ModifyRequestHeaders extends ModifyHeadersBase {
  static maybeApplyModifyHeaders(channel, matchedRules) {
    matchedRules = matchedRules.filter(mr => {
      const action = mr.rule.action;
      return action.type === "modifyHeaders" && action.requestHeaders?.length;
    });
    if (matchedRules.length) {
      new ModifyRequestHeaders(channel).applyModifyHeaders(matchedRules);
    }
  }

  /** @param {MatchedRule} matchedRule */
  headerActionsFor(matchedRule) {
    return matchedRule.rule.action.requestHeaders;
  }

  setHeaderImpl(matchedRule, name, value, merge) {
    if (this.isHeaderNameEqual(name, "host")) {
      this.#checkHostHeader(matchedRule, value);
    }
    if (merge && value && this.isHeaderNameEqual(name, "cookie")) {
      // By default, headers are merged with ",". But Cookie should use "; ".
      // HTTP/1.1 allowed only one Cookie header, but HTTP/2.0 allows multiple,
      // but recommends concatenation on one line. Relevant RFCs:
      // - https://www.rfc-editor.org/rfc/rfc6265#section-5.4
      // - https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2.5
      // Consistent with Firefox internals, we ensure that there is at most one
      // Cookie header, by overwriting the previous one, if any.
      let existingCookie = this.channel.getRequestHeader("cookie");
      if (existingCookie) {
        value = existingCookie + "; " + value;
        merge = false;
      }
    }
    this.channel.setRequestHeader(name, value, merge);
  }

  #checkHostHeader(matchedRule, value) {
    let uri = Services.io.newURI(`https://${value}/`);
    let { policy } = matchedRule.ruleManager.extension;

    if (!policy.allowedOrigins.matches(uri)) {
      throw new Error(
        `Unable to set host header, url missing from permissions.`
      );
    }

    if (WebExtensionPolicy.isRestrictedURI(uri)) {
      throw new Error(`Unable to set host header to restricted url.`);
    }
  }
}

class ModifyResponseHeaders extends ModifyHeadersBase {
  static maybeApplyModifyHeaders(channel, matchedRules) {
    matchedRules = matchedRules.filter(mr => {
      const action = mr.rule.action;
      return action.type === "modifyHeaders" && action.responseHeaders?.length;
    });
    if (matchedRules.length) {
      new ModifyResponseHeaders(channel).applyModifyHeaders(matchedRules);
    }
  }

  headerActionsFor(matchedRule) {
    return matchedRule.rule.action.responseHeaders;
  }

  setHeaderImpl(matchedRule, name, value, merge) {
    this.channel.setResponseHeader(name, value, merge);
  }
}

class RuleValidator {
  constructor(alreadyValidatedRules, { isSessionRuleset = false } = {}) {
    this.rulesMap = new Map(alreadyValidatedRules.map(r => [r.id, r]));
    this.failures = [];
    this.isSessionRuleset = isSessionRuleset;
  }

  /**
   * Static method used to deserialize Rule class instances from a plain
   * js object rule as serialized implicitly by aomStartup.encodeBlob
   * when we store the rules into the startup cache file.
   *
   * @param {object} rule
   * @returns {Rule}
   */
  static deserializeRule(rule) {
    const newRule = new Rule(rule);
    if (newRule.condition.regexFilter) {
      newRule.condition.setCompiledRegexFilter(
        compileRegexFilter(
          newRule.condition.regexFilter,
          newRule.condition.isUrlFilterCaseSensitive
        )
      );
    }
    return newRule;
  }

  removeRuleIds(ruleIds) {
    for (const ruleId of ruleIds) {
      this.rulesMap.delete(ruleId);
    }
  }

  /**
   * @param {object[]} rules - A list of objects that adhere to the Rule type
   *    from declarative_net_request.json.
   */
  addRules(rules) {
    for (const rule of rules) {
      if (this.rulesMap.has(rule.id)) {
        this.#collectInvalidRule(rule, `Duplicate rule ID: ${rule.id}`);
        continue;
      }
      // declarative_net_request.json defines basic types, such as the expected
      // object properties and (primitive) type. Trivial constraints such as
      // minimum array lengths are also expressed in the schema.
      // Anything more complex is validated here. In particular, constraints
      // involving multiple properties (e.g. mutual exclusiveness).
      //
      // The following conditions have already been validated by the schema:
      // - isUrlFilterCaseSensitive (boolean)
      // - domainType (enum string)
      // - initiatorDomains & excludedInitiatorDomains & requestDomains &
      //   excludedRequestDomains (array of string in canonicalDomain format)
      if (
        !this.#checkCondResourceTypes(rule) ||
        !this.#checkCondRequestMethods(rule) ||
        !this.#checkCondTabIds(rule) ||
        !this.#checkCondUrlFilterAndRegexFilter(rule) ||
        !this.#checkAction(rule)
      ) {
        continue;
      }

      const newRule = new Rule(rule);
      // #lastCompiledRegexFilter is set if regexFilter is set, and null
      // otherwise by the above call to #checkCondUrlFilterAndRegexFilter().
      if (this.#lastCompiledRegexFilter) {
        newRule.condition.setCompiledRegexFilter(this.#lastCompiledRegexFilter);
      }

      this.rulesMap.set(rule.id, newRule);
    }
  }

  // #checkCondUrlFilterAndRegexFilter() compiles the regexFilter to check its
  // validity. To avoid having to compile it again when the Rule (RuleCondition)
  // is constructed, we temporarily cache the result.
  #lastCompiledRegexFilter;

  // Checks: resourceTypes & excludedResourceTypes
  #checkCondResourceTypes(rule) {
    const { resourceTypes, excludedResourceTypes } = rule.condition;
    if (this.#hasOverlap(resourceTypes, excludedResourceTypes)) {
      this.#collectInvalidRule(
        rule,
        "resourceTypes and excludedResourceTypes should not overlap"
      );
      return false;
    }
    if (rule.action.type === "allowAllRequests") {
      if (!resourceTypes) {
        this.#collectInvalidRule(
          rule,
          "An allowAllRequests rule must have a non-empty resourceTypes array"
        );
        return false;
      }
      if (resourceTypes.some(r => r !== "main_frame" && r !== "sub_frame")) {
        this.#collectInvalidRule(
          rule,
          "An allowAllRequests rule may only include main_frame/sub_frame in resourceTypes"
        );
        return false;
      }
    }
    return true;
  }

  // Checks: requestMethods & excludedRequestMethods
  #checkCondRequestMethods(rule) {
    const { requestMethods, excludedRequestMethods } = rule.condition;
    if (this.#hasOverlap(requestMethods, excludedRequestMethods)) {
      this.#collectInvalidRule(
        rule,
        "requestMethods and excludedRequestMethods should not overlap"
      );
      return false;
    }
    const isInvalidRequestMethod = method => method.toLowerCase() !== method;
    if (
      requestMethods?.some(isInvalidRequestMethod) ||
      excludedRequestMethods?.some(isInvalidRequestMethod)
    ) {
      this.#collectInvalidRule(rule, "request methods must be in lower case");
      return false;
    }
    return true;
  }

  // Checks: tabIds & excludedTabIds
  #checkCondTabIds(rule) {
    const { tabIds, excludedTabIds } = rule.condition;

    if ((tabIds || excludedTabIds) && !this.isSessionRuleset) {
      this.#collectInvalidRule(
        rule,
        "tabIds and excludedTabIds can only be specified in session rules"
      );
      return false;
    }

    if (this.#hasOverlap(tabIds, excludedTabIds)) {
      this.#collectInvalidRule(
        rule,
        "tabIds and excludedTabIds should not overlap"
      );
      return false;
    }
    return true;
  }

  static #regexNonASCII = /[^\x00-\x7F]/; // eslint-disable-line no-control-regex
  static #regexDigitOrBackslash = /^[0-9\\]$/;

  // Checks: urlFilter & regexFilter
  #checkCondUrlFilterAndRegexFilter(rule) {
    const { urlFilter, regexFilter } = rule.condition;

    this.#lastCompiledRegexFilter = null;

    const checkEmptyOrNonASCII = (str, prop) => {
      if (!str) {
        this.#collectInvalidRule(rule, `${prop} should not be an empty string`);
        return false;
      }
      // Non-ASCII in URLs are always encoded in % (or punycode in domains).
      if (RuleValidator.#regexNonASCII.test(str)) {
        this.#collectInvalidRule(
          rule,
          `${prop} should not contain non-ASCII characters`
        );
        return false;
      }
      return true;
    };
    if (urlFilter != null) {
      if (regexFilter != null) {
        this.#collectInvalidRule(
          rule,
          "urlFilter and regexFilter are mutually exclusive"
        );
        return false;
      }
      if (!checkEmptyOrNonASCII(urlFilter, "urlFilter")) {
        // #collectInvalidRule already called by checkEmptyOrNonASCII.
        return false;
      }
      if (urlFilter.startsWith("||*")) {
        // Rejected because Chrome does too. '||*' is equivalent to '*'.
        this.#collectInvalidRule(rule, "urlFilter should not start with '||*'");
        return false;
      }
    } else if (regexFilter != null) {
      if (!checkEmptyOrNonASCII(regexFilter, "regexFilter")) {
        // #collectInvalidRule already called by checkEmptyOrNonASCII.
        return false;
      }
      try {
        this.#lastCompiledRegexFilter = compileRegexFilter(
          regexFilter,
          rule.condition.isUrlFilterCaseSensitive
        );
      } catch (e) {
        this.#collectInvalidRule(
          rule,
          "regexFilter is not a valid regular expression"
        );
        return false;
      }
    }
    return true;
  }

  #checkAction(rule) {
    switch (rule.action.type) {
      case "allow":
      case "allowAllRequests":
      case "block":
      case "upgradeScheme":
        // These actions have no extra properties.
        break;
      case "redirect":
        return this.#checkActionRedirect(rule);
      case "modifyHeaders":
        return this.#checkActionModifyHeaders(rule);
      default:
        // Other values are not possible because declarative_net_request.json
        // only accepts the above action types.
        throw new Error(`Unexpected action type: ${rule.action.type}`);
    }
    return true;
  }

  #checkActionRedirect(rule) {
    const { url, extensionPath, transform, regexSubstitution } =
      rule.action.redirect ?? {};
    const hasExtensionPath = extensionPath != null;
    const hasRegexSubstitution = regexSubstitution != null;
    const redirectKeyCount = // @ts-ignore trivial/noisy
      !!url + !!hasExtensionPath + !!transform + !!hasRegexSubstitution;
    if (redirectKeyCount !== 1) {
      if (redirectKeyCount === 0) {
        this.#collectInvalidRule(
          rule,
          "A redirect rule must have a non-empty action.redirect object"
        );
        return false;
      }
      // Side note: Chrome silently ignores excess keys, and skips validation
      // for ignored keys, in this order:
      // - url > extensionPath > transform > regexSubstitution
      this.#collectInvalidRule(
        rule,
        "redirect.url, redirect.extensionPath, redirect.transform and redirect.regexSubstitution are mutually exclusive"
      );
      return false;
    }

    if (hasExtensionPath && !extensionPath.startsWith("/")) {
      this.#collectInvalidRule(
        rule,
        "redirect.extensionPath should start with a '/'"
      );
      return false;
    }

    // If specified, the "url" property is described as "format": "url" in the
    // JSON schema, which ensures that the URL is a canonical form, and that
    // the extension is allowed to trigger a navigation to the URL.
    // E.g. javascript: and privileged about:-URLs cannot be navigated to, but
    // http(s) URLs can (regardless of extension permissions).
    // data:-URLs are currently blocked due to bug 1622986.

    if (transform) {
      if (transform.query != null && transform.queryTransform) {
        this.#collectInvalidRule(
          rule,
          "redirect.transform.query and redirect.transform.queryTransform are mutually exclusive"
        );
        return false;
      }
      // Most of the validation is done by nsIURIMutator via applyURLTransform.
      // nsIURIMutator is not very strict, so we perform some extra checks here
      // to reject values that are not technically valid URLs.

      if (transform.port && /\D/.test(transform.port)) {
        // nsIURIMutator's setPort takes an int, so any string will implicitly
        // be converted to a number. This part verifies that the input only
        // consists of digits. setPort will ensure that it is at most 65535.
        this.#collectInvalidRule(
          rule,
          "redirect.transform.port should be empty or an integer"
        );
        return false;
      }

      // Note: we don't verify whether transform.query starts with '/', because
      // Chrome does not require it, and nsIURIMutator prepends it if missing.

      if (transform.query && !transform.query.startsWith("?")) {
        this.#collectInvalidRule(
          rule,
          "redirect.transform.query should be empty or start with a '?'"
        );
        return false;
      }
      if (transform.fragment && !transform.fragment.startsWith("#")) {
        this.#collectInvalidRule(
          rule,
          "redirect.transform.fragment should be empty or start with a '#'"
        );
        return false;
      }
      try {
        const dummyURI = Services.io.newURI("http://dummy");
        // applyURLTransform uses nsIURIMutator to transform a URI, and throws
        // if |transform| is invalid, e.g. invalid host, port, etc.
        applyURLTransform(dummyURI, transform);
      } catch (e) {
        this.#collectInvalidRule(
          rule,
          "redirect.transform does not describe a valid URL transformation"
        );
        return false;
      }
    }

    if (hasRegexSubstitution) {
      if (!rule.condition.regexFilter) {
        this.#collectInvalidRule(
          rule,
          "redirect.regexSubstitution requires the regexFilter condition to be specified"
        );
        return false;
      }
      let i = 0;
      // i will be index after \. Loop breaks if not found (-1+1=0 = false).
      while ((i = regexSubstitution.indexOf("\\", i) + 1)) {
        let c = regexSubstitution[i++]; // may be undefined if \ is at end.
        if (c === undefined || !RuleValidator.#regexDigitOrBackslash.test(c)) {
          this.#collectInvalidRule(
            rule,
            "redirect.regexSubstitution only allows digit or \\ after \\."
          );
          return false;
        }
      }
    }

    return true;
  }

  #checkActionModifyHeaders(rule) {
    const { requestHeaders, responseHeaders } = rule.action;
    if (!requestHeaders && !responseHeaders) {
      this.#collectInvalidRule(
        rule,
        "A modifyHeaders rule must have a non-empty requestHeaders or modifyHeaders list"
      );
      return false;
    }

    const isValidModifyHeadersOp = ({ header, operation, value }) => {
      if (!header) {
        this.#collectInvalidRule(rule, "header must be non-empty");
        return false;
      }
      if (!value && (operation === "append" || operation === "set")) {
        this.#collectInvalidRule(
          rule,
          "value is required for operations append/set"
        );
        return false;
      }
      if (value && operation === "remove") {
        this.#collectInvalidRule(
          rule,
          "value must not be provided for operation remove"
        );
        return false;
      }
      return true;
    };
    if (
      (requestHeaders && !requestHeaders.every(isValidModifyHeadersOp)) ||
      (responseHeaders && !responseHeaders.every(isValidModifyHeadersOp))
    ) {
      // #collectInvalidRule already called by isValidModifyHeadersOp.
      return false;
    }
    return true;
  }

  // Conditions with a filter and an exclude-filter should reject overlapping
  // lists, because they can never simultaneously be true.
  #hasOverlap(arrayA, arrayB) {
    return arrayA && arrayB && arrayA.some(v => arrayB.includes(v));
  }

  #collectInvalidRule(rule, message) {
    this.failures.push({ rule, message });
  }

  getValidatedRules() {
    return Array.from(this.rulesMap.values());
  }

  getFailures() {
    return this.failures;
  }
}

export class RuleQuotaCounter {
  constructor(ruleLimitName) {
    this.ruleLimitName = ruleLimitName;
    this.ruleLimitRemaining = lazy.ExtensionDNRLimits[this.ruleLimitName];
    this.regexRemaining = lazy.ExtensionDNRLimits.MAX_NUMBER_OF_REGEX_RULES;
  }

  tryAddRules(rulesetId, rules) {
    if (rules.length > this.ruleLimitRemaining) {
      this.#throwQuotaError(rulesetId, "rules", this.ruleLimitName);
    }
    let regexCount = 0;
    for (let rule of rules) {
      if (rule.condition.regexFilter && ++regexCount > this.regexRemaining) {
        this.#throwQuotaError(
          rulesetId,
          "regexFilter rules",
          "MAX_NUMBER_OF_REGEX_RULES"
        );
      }
    }

    // Update counters only when there are no quota errors.
    this.ruleLimitRemaining -= rules.length;
    this.regexRemaining -= regexCount;
  }

  #throwQuotaError(rulesetId, what, limitName) {
    if (this.ruleLimitName === "GUARANTEED_MINIMUM_STATIC_RULES") {
      throw new ExtensionError(
        `Number of ${what} across all enabled static rulesets exceeds ${limitName} if ruleset "${rulesetId}" were to be enabled.`
      );
    }
    throw new ExtensionError(
      `Number of ${what} in ruleset "${rulesetId}" exceeds ${limitName}.`
    );
  }
}

/**
 * Compares two rules to determine the relative order of precedence.
 * Rules are only comparable if they are from the same extension!
 *
 * @param {Rule} ruleA
 * @param {Rule} ruleB
 * @param {Ruleset} rulesetA - the ruleset ruleA is part of.
 * @param {Ruleset} rulesetB - the ruleset ruleB is part of.
 * @returns {integer}
 *   0 if equal.
 *   <0 if ruleA comes before ruleB.
 *   >0 if ruleA comes after ruleB.
 */
function compareRule(ruleA, ruleB, rulesetA, rulesetB) {
  // Comparators: 0 if equal, >0 if a after b, <0 if a before b.
  function cmpHighestNumber(a, b) {
    return a === b ? 0 : b - a;
  }
  function cmpLowestNumber(a, b) {
    return a === b ? 0 : a - b;
  }
  return (
    // All compared operands are non-negative integers.
    cmpHighestNumber(ruleA.priority, ruleB.priority) ||
    cmpLowestNumber(ruleA.actionPrecedence(), ruleB.actionPrecedence()) ||
    // As noted in the big comment at the top of the file, the following two
    // comparisons only exist in order to have a stable ordering of rules. The
    // specific comparison is somewhat arbitrary and matches Chrome's behavior.
    // For context, see https://github.com/w3c/webextensions/issues/280
    cmpLowestNumber(rulesetA.rulesetPrecedence, rulesetB.rulesetPrecedence) ||
    cmpLowestNumber(ruleA.id, ruleB.id)
  );
}

class MatchedRule {
  /**
   * @param {Rule} rule
   * @param {Ruleset} ruleset
   */
  constructor(rule, ruleset) {
    this.rule = rule;
    this.ruleset = ruleset;
  }

  // The RuleManager that generated this MatchedRule.
  get ruleManager() {
    return this.ruleset.ruleManager;
  }
}

// tabId computation is currently not free, and depends on the initialization of
// ExtensionParent.apiManager.global (see WebRequest.getTabIdForChannelWrapper).
// Fortunately, DNR only supports tabIds in session rules, so by keeping track
// of session rules with tabIds/excludedTabIds conditions, we can find tabId
// exactly and only when necessary.
let gHasAnyTabIdConditions = false;

class RequestDetails {
  /**
   * @param {object} options
   * @param {nsIURI} options.requestURI - URL of the requested resource.
   * @param {nsIURI} [options.initiatorURI] - URL of triggering principal,
   *   provided that it is a content principal. Otherwise null.
   * @param {string} options.type - ResourceType (MozContentPolicyType).
   * @param {string} [options.method] - HTTP method
   * @param {integer} [options.tabId]
   * @param {CanonicalBrowsingContext} [options.browsingContext] - The CBC
   *   associated with the request. Typically the bc for which the subresource
   *   request is initiated, if any. For document requests, this is the parent
   *   (i.e. the parent frame for sub_frame, null for main_frame).
   */
  constructor({
    requestURI,
    initiatorURI,
    type,
    method,
    tabId,
    browsingContext,
  }) {
    this.requestURI = requestURI;
    this.initiatorURI = initiatorURI;
    this.type = type;
    this.method = method;
    this.tabId = tabId;
    this.browsingContext = browsingContext;

    let requestDomain = this.#domainFromURI(requestURI);
    let initiatorDomain = initiatorURI
      ? this.#domainFromURI(initiatorURI)
      : null;
    this.allRequestDomains =
      requestDomain && this.#getAllDomainsWithin(requestDomain);
    this.allInitiatorDomains =
      initiatorDomain && this.#getAllDomainsWithin(initiatorDomain);

    this.domainType = this.#isThirdParty(requestURI, initiatorURI)
      ? "thirdParty"
      : "firstParty";

    this.requestURIspec = requestURI.spec;
    this.requestDataForUrlFilter = new RequestDataForUrlFilter(
      this.requestURIspec
    );
  }

  #isThirdParty(requestURI, initiatorURI) {
    if (!initiatorURI) {
      // E.g. main_frame request or opaque origin.
      return true;
    }

    try {
      return (
        Services.eTLD.getBaseDomain(requestURI) !==
        Services.eTLD.getBaseDomain(initiatorURI)
      );
    } catch (err) {
      // May throw if either domain is an IP address, lacks a public suffix
      // (e.g. http://localhost or moz-extension://UUID)
      // or contains characters disallowed in URIs. Fall back:
      return (
        this.#domainFromURI(requestURI) !== this.#domainFromURI(initiatorURI)
      );
    }
  }

  static fromChannelWrapper(channel) {
    let tabId = -1;
    if (gHasAnyTabIdConditions) {
      tabId = lazy.WebRequest.getTabIdForChannelWrapper(channel);
    }
    return new RequestDetails({
      requestURI: channel.finalURI,
      // Note: originURI may be null, if missing or null principal, as desired.
      initiatorURI: channel.originURI,
      type: channel.type,
      method: channel.method.toLowerCase(),
      tabId,
      browsingContext: channel.loadInfo.browsingContext,
    });
  }

  #ancestorRequestDetails;
  get ancestorRequestDetails() {
    if (this.#ancestorRequestDetails) {
      return this.#ancestorRequestDetails;
    }
    this.#ancestorRequestDetails = [];
    if (!this.browsingContext?.ancestorsAreCurrent) {
      // this.browsingContext is set for real requests (via fromChannelWrapper).
      // It may be void for testMatchOutcome and for the ancestor requests
      // simulated below.
      //
      // ancestorsAreCurrent being false is unexpected, but could theoretically
      // happen if the request is triggered from an unloaded (sub)frame. In that
      // case we don't want to use potentially incorrect ancestor information.
      //
      // In any case, nothing left to do.
      return this.#ancestorRequestDetails;
    }
    // Reconstruct the frame hierarchy of the request's document, in order to
    // retroactively recompute the relevant matches of allowAllRequests rules.
    //
    // The allowAllRequests rule is supposedly applying to all subresource
    // requests. For non-document requests, this is usually the document if any.
    // In case of document requests, there is some ambiguity:
    // - Usually, the initiator is the parent document that created the frame.
    // - Sometimes, the initiator is a different frame or even another window.
    //
    // In RequestDetails.fromChannelWrapper, the actual initiator is used and
    // reflected in initiatorURI, but here we use the document's parent. This
    // is done because the chain of initiators is unstable (e.g. an opener can
    // navigate/unload), whereas frame ancestor chain is constant as long as
    // the leaf BrowsingContext is current. Moreover, allowAllRequests was
    // originally designed to operate on frame hierarchies (crbug.com/1038831).
    //
    // This implementation of "initiator" for "allowAllRequests" is consistent
    // with Chrome and Safari.
    for (let bc = this.browsingContext; bc; bc = bc.parent) {
      // Note: requestURI may differ from the document's initial requestURI,
      // e.g. due to same-document navigations.
      const requestURI = bc.currentURI;
      if (!requestURI.schemeIs("https") && !requestURI.schemeIs("http")) {
        // DNR is currently only hooked up to http(s) requests. Ignore other
        // URLs, e.g. about:, blob:, moz-extension:, data:, etc.
        continue;
      }
      const isTop = !bc.parent;
      const parentPrin = bc.parentWindowContext?.documentPrincipal;
      const requestDetails = new RequestDetails({
        requestURI,
        // Note: initiatorURI differs from RequestDetails.fromChannelWrapper;
        // See the above comment for more info.
        initiatorURI: parentPrin?.isContentPrincipal ? parentPrin.URI : null,
        type: isTop ? "main_frame" : "sub_frame",
        method: bc.activeSessionHistoryEntry?.hasPostData ? "post" : "get",
        tabId: this.tabId,
        // In this loop we are already explicitly accounting for ancestors, so
        // we intentionally omit browsingContext even though we have |bc|. If
        // we were to set `browsingContext: bc`, the output would be the same,
        // but be derived from unnecessarily repeated request evaluations.
        browsingContext: null,
      });
      this.#ancestorRequestDetails.unshift(requestDetails);
    }
    return this.#ancestorRequestDetails;
  }

  canExtensionModify(extension) {
    const policy = extension.policy;
    if (!policy.canAccessURI(this.requestURI)) {
      return false;
    }
    if (
      this.initiatorURI &&
      this.type !== "main_frame" &&
      this.type !== "sub_frame" &&
      !policy.canAccessURI(this.initiatorURI, false, true, true)
    ) {
      // Host permissions for the initiator is required except for navigation
      // requests: https://bugzilla.mozilla.org/show_bug.cgi?id=1825824#c2
      return false;
    }
    return true;
  }

  #domainFromURI(uri) {
    try {
      let hostname = uri.host;
      // nsIURI omits brackets from IPv6 addresses. But the canonical form of an
      // IPv6 address is with brackets, so add them.
      return hostname.includes(":") ? `[${hostname}]` : hostname;
    } catch (e) {
      // uri.host throws for some schemes (e.g. about:). In practice we won't
      // encounter this for network (via NetworkIntegration.startDNREvaluation)
      // because isRestrictedPrincipalURI filters the initiatorURI. Furthermore,
      // because only http(s) requests are observed, requestURI is http(s).
      //
      // declarativeNetRequest.testMatchOutcome can pass arbitrary URIs and thus
      // trigger the error in nsIURI::GetHost.
      Cu.reportError(e);
      return null;
    }
  }

  /**
   * @param {string} domain - The canonical representation of the host of a URL.
   * @returns {string[]} A non-empty list of the domain and all superdomains
   *   within the given domain. This may include items that are not resolvable
   *   domains, such as "com" (from input "example.com").
   */
  #getAllDomainsWithin(domain) {
    const domains = [domain];
    let i = 0;
    // Reminder: domain cannot start with a dot, nor contain consecutive dots.
    while ((i = domain.indexOf(".", i) + 1) !== 0) {
      domain = domain.slice(i);
      // A full domain can end with a dot (FQDN) such as "example.com.", in
      // which case the last domain should be "com." and not "".
      if (domain) {
        domains.push(domain);
      }
    }
    return domains;
  }
}

// Domain lists in rule conditions (requestDomains, excludedRequestDomains,
// initiatorDomains, excludedInitiatorDomains) could be really long, containing
// thousands of entries. We convert them to Set for faster lookup.
const gDomainsListToSet = new DefaultWeakMap(domains => new Set(domains));

/**
 * This RequestEvaluator class's logic is documented at the top of this file.
 */
class RequestEvaluator {
  // private constructor, only used by RequestEvaluator.evaluateRequest.
  constructor(request, ruleManager) {
    this.req = request;
    this.ruleManager = ruleManager;
    this.canModify = request.canExtensionModify(ruleManager.extension);

    // These values are initialized by findMatchingRules():
    this.matchedRule = null;
    this.matchedModifyHeadersRules = [];
    this.didCheckAncestors = false;
    this.findMatchingRules();
  }

  /**
   * Finds the matched rules for the given request and extensions,
   * according to the logic documented at the top of this file.
   *
   * @param {RequestDetails} request
   * @param {RuleManager[]} ruleManagers
   *    The list of RuleManagers, ordered by importance of its extension.
   * @returns {MatchedRule[]}
   */
  static evaluateRequest(request, ruleManagers) {
    // Helper to determine precedence of rules from different extensions.
    function precedence(matchedRule) {
      switch (matchedRule.rule.action.type) {
        case "block":
          return 1;
        case "redirect":
        case "upgradeScheme":
          return 2;
        case "allow":
        case "allowAllRequests":
          return 3;
        // case "modifyHeaders": not comparable after the first pass.
        default:
          throw new Error(`Unexpected action: ${matchedRule.rule.action.type}`);
      }
    }

    let requestEvaluators = [];
    let finalMatch;
    for (let ruleManager of ruleManagers) {
      // Evaluate request with findMatchingRules():
      const requestEvaluator = new RequestEvaluator(request, ruleManager);
      // RequestEvaluator may be used after the loop when the request is
      // accepted, to collect modifyHeaders/allow/allowAllRequests actions.
      requestEvaluators.push(requestEvaluator);
      let matchedRule = requestEvaluator.matchedRule;
      if (
        matchedRule &&
        (!finalMatch || precedence(matchedRule) < precedence(finalMatch))
      ) {
        // Before choosing the matched rule as finalMatch, check whether there
        // is an allowAllRequests rule override among the ancestors.
        requestEvaluator.findAncestorRuleOverride();
        matchedRule = requestEvaluator.matchedRule;
        if (!finalMatch || precedence(matchedRule) < precedence(finalMatch)) {
          finalMatch = matchedRule;
          if (finalMatch.rule.action.type === "block") {
            break;
          }
        }
      }
    }
    if (finalMatch && !finalMatch.rule.isAllowOrAllowAllRequestsAction()) {
      // Found block/redirect/upgradeScheme, request will be replaced.
      return [finalMatch];
    }
    // Request not canceled, collect all modifyHeaders actions:
    let matchedRules = requestEvaluators
      .map(re => re.getMatchingModifyHeadersRules())
      .flat(1);

    // ... and collect the allowAllRequests actions:
    // Note: Only needed for testMatchOutcome, getMatchedRules (bug 1745765) and
    // onRuleMatchedDebug (bug 1745773). Not for regular requests, since regular
    // requests do not distinguish between no rule vs allow vs allowAllRequests.
    let finalAllowAllRequestsMatches = [];
    for (let requestEvaluator of requestEvaluators) {
      // TODO bug 1745765 / bug 1745773: Uncomment findAncestorRuleOverride()
      // when getMatchedRules() or onRuleMatchedDebug are implemented.
      // requestEvaluator.findAncestorRuleOverride();
      let matchedRule = requestEvaluator.matchedRule;
      if (matchedRule && matchedRule.rule.action.type === "allowAllRequests") {
        // Even if a different extension wins the final match, an extension
        // may want to record the "allowAllRequests" action for the future.
        finalAllowAllRequestsMatches.push(matchedRule);
      }
    }
    if (finalAllowAllRequestsMatches.length) {
      matchedRules = finalAllowAllRequestsMatches.concat(matchedRules);
    }

    // ... and collect the "allow" action. At this point, finalMatch could also
    // be a modifyHeaders or allowAllRequests action, but these would already
    // have been added to the matchedRules result before.
    if (finalMatch && finalMatch.rule.action.type === "allow") {
      matchedRules.unshift(finalMatch);
    }
    return matchedRules;
  }

  /**
   * Finds the matching rules, as documented in the comment before the class.
   */
  findMatchingRules() {
    if (!this.canModify && !this.ruleManager.hasBlockPermission) {
      // If the extension cannot apply any action, don't bother.
      return;
    }

    this.#collectMatchInRuleset(this.ruleManager.sessionRules);
    this.#collectMatchInRuleset(this.ruleManager.dynamicRules);
    for (let ruleset of this.ruleManager.enabledStaticRules) {
      this.#collectMatchInRuleset(ruleset);
    }

    if (this.matchedRule && !this.#isRuleActionAllowed(this.matchedRule.rule)) {
      this.matchedRule = null;
      // Note: this.matchedModifyHeadersRules is [] because canModify access is
      // checked before populating the list.
    }
  }

  /**
   * Find an "allowAllRequests" rule among the ancestors that may override the
   * current matchedRule and/or matchedModifyHeadersRules rules.
   */
  findAncestorRuleOverride() {
    if (this.didCheckAncestors) {
      return;
    }
    this.didCheckAncestors = true;

    if (!this.ruleManager.hasRulesWithAllowAllRequests) {
      // Optimization: Skip ancestorRequestDetails lookup and/or request
      // evaluation if there are no allowAllRequests rules.
      return;
    }

    // Now we need to check whether any of the ancestor frames had a matching
    // allowAllRequests rule. matchedRule and/or matchedModifyHeadersRules
    // results may be ignored if their priority is lower or equal to the
    // highest-priority allowAllRequests rule among the frame ancestors.
    //
    // In theory, every ancestor may potentially yield an allowAllRequests rule,
    // and should therefore be checked unconditionally. But logically, if there
    // are no existing matches, then any matching allowAllRequests rules will
    // not have any effect on the request outcome. As an optimization, we
    // therefore skip ancestor checks in this case.
    if (
      (!this.matchedRule ||
        this.matchedRule.rule.isAllowOrAllowAllRequestsAction()) &&
      !this.matchedModifyHeadersRules.length
    ) {
      // Optimization: Do not look up ancestors if no rules were matched.
      //
      // TODO bug 1745773: onRuleMatchedDebug is supposed to report when a rule
      // has been matched. To be pedantic, when there is an onRuleMatchedDebug
      // listener, the parents need to be checked unconditionally, in order to
      // report potential allowAllRequests matches among ancestors.
      // TODO bug 1745765: the above may also apply to getMatchedRules().
      return;
    }

    for (let request of this.req.ancestorRequestDetails) {
      // TODO: Optimize by only evaluating allow/allowAllRequests rules, because
      // the request being seen here implies that the request was not canceled,
      // i.e. that there were no block/redirect/upgradeScheme rules in any of
      // the ancestors (across all extensions!).
      let requestEvaluator = new RequestEvaluator(request, this.ruleManager);
      let ancestorMatchedRule = requestEvaluator.matchedRule;
      if (
        ancestorMatchedRule &&
        ancestorMatchedRule.rule.action.type === "allowAllRequests" &&
        (!this.matchedRule ||
          compareRule(
            this.matchedRule.rule,
            ancestorMatchedRule.rule,
            this.matchedRule.ruleset,
            ancestorMatchedRule.ruleset
          ) > 0)
      ) {
        // Found an allowAllRequests rule that takes precedence over whatever
        // the current rule was.
        this.matchedRule = ancestorMatchedRule;
      }
    }
  }

  /**
   * Retrieves the list of matched modifyHeaders rules that should apply.
   *
   * @returns {MatchedRule[]}
   */
  getMatchingModifyHeadersRules() {
    if (this.matchedModifyHeadersRules.length) {
      // Find parent allowAllRequests rules, if any, to make sure that we can
      // appropriately ignore same-or-lower-priority modifyHeaders rules.
      this.findAncestorRuleOverride();
    }
    // The minimum priority is 1. Defaulting to 0 = include all.
    let priorityThreshold = 0;
    if (this.matchedRule?.rule.isAllowOrAllowAllRequestsAction()) {
      priorityThreshold = this.matchedRule.rule.priority;
    }
    // Note: the result cannot be non-empty if this.matchedRule is a non-allow
    // action, because if that were to be the case, then the request would have
    // been canceled, and therefore there would not be any header to modify.
    // Even if another extension were to override the action, it could only be
    // any other non-allow action, which would still cancel the request.
    let matchedRules = this.matchedModifyHeadersRules.filter(matchedRule => {
      return matchedRule.rule.priority > priorityThreshold;
    });
    // Sort output for a deterministic order.
    // NOTE: Sorting rules at registration (in RuleManagers) would avoid the
    // need to sort here. Since the number of matched modifyHeaders rules are
    // expected to be small, we don't bother optimizing.
    matchedRules.sort((a, b) => {
      return compareRule(a.rule, b.rule, a.ruleset, b.ruleset);
    });
    return matchedRules;
  }

  /** @param {Ruleset} ruleset */
  #collectMatchInRuleset(ruleset) {
    for (let rule of ruleset.rules) {
      if (ruleset.disabledRuleIds?.has(rule.id)) {
        continue;
      }
      if (!this.#matchesRuleCondition(rule.condition)) {
        continue;
      }
      if (rule.action.type === "modifyHeaders") {
        if (this.canModify) {
          this.matchedModifyHeadersRules.push(new MatchedRule(rule, ruleset));
        }
        continue;
      }
      if (
        this.matchedRule &&
        compareRule(
          this.matchedRule.rule,
          rule,
          this.matchedRule.ruleset,
          ruleset
        ) <= 0
      ) {
        continue;
      }
      this.matchedRule = new MatchedRule(rule, ruleset);
    }
  }

  /**
   * @param {RuleCondition} cond
   * @returns {boolean} Whether the condition matched.
   */
  #matchesRuleCondition(cond) {
    if (cond.resourceTypes) {
      if (!cond.resourceTypes.includes(this.req.type)) {
        return false;
      }
    } else if (cond.excludedResourceTypes) {
      if (cond.excludedResourceTypes.includes(this.req.type)) {
        return false;
      }
    } else if (this.req.type === "main_frame") {
      // When resourceTypes/excludedResourceTypes are not specified, the
      // documented behavior is to ignore main_frame requests.
      return false;
    }

    // Check this.req.requestURI:
    if (cond.urlFilter) {
      if (!cond.urlFilterMatches(this.req.requestDataForUrlFilter)) {
        return false;
      }
    } else if (cond.regexFilter) {
      if (!cond.getCompiledRegexFilter().test(this.req.requestURIspec)) {
        return false;
      }
    }
    if (
      cond.excludedRequestDomains &&
      this.#matchesDomains(
        cond.excludedRequestDomains,
        this.req.allRequestDomains
      )
    ) {
      return false;
    }
    if (
      cond.requestDomains &&
      !this.#matchesDomains(cond.requestDomains, this.req.allRequestDomains)
    ) {
      return false;
    }
    if (
      cond.excludedInitiatorDomains &&
      // Note: unable to only match null principals (bug 1798225).
      this.req.allInitiatorDomains &&
      this.#matchesDomains(
        cond.excludedInitiatorDomains,
        this.req.allInitiatorDomains
      )
    ) {
      return false;
    }
    if (
      cond.initiatorDomains &&
      // Note: unable to only match null principals (bug 1798225).
      (!this.req.allInitiatorDomains ||
        !this.#matchesDomains(
          cond.initiatorDomains,
          this.req.allInitiatorDomains
        ))
    ) {
      return false;
    }

    if (cond.domainType && cond.domainType !== this.req.domainType) {
      return false;
    }

    if (cond.requestMethods) {
      if (!cond.requestMethods.includes(this.req.method)) {
        return false;
      }
    } else if (cond.excludedRequestMethods?.includes(this.req.method)) {
      return false;
    }

    if (cond.tabIds) {
      if (!cond.tabIds.includes(this.req.tabId)) {
        return false;
      }
    } else if (cond.excludedTabIds?.includes(this.req.tabId)) {
      return false;
    }

    return true;
  }

  /**
   * @param {string[]} domainsInCondition - A potentially long list of
   *   canonicalized domain patterns that are part of a rule condition.
   *   Canonical means punycode, no ports, and IPv6 without brackets, and not
   *   starting with a dot. May end with a dot if it is a FQDN.
   * @param {string[]} targetDomains - The list of domains and superdomains
   *   within the original URI (see #getAllDomainsWithin).
   * @returns {boolean} Whether the actual host (encoded in targetDomains) is a
   *   (sub)domain of any of the domains in the condition (domainsInCondition).
   */
  #matchesDomains(domainsInCondition, targetDomains) {
    const ruleDomainsSet = gDomainsListToSet.get(domainsInCondition);
    return targetDomains.some(domain => ruleDomainsSet.has(domain));
  }

  /**
   * @param {Rule} rule - The final rule from the first pass.
   * @returns {boolean} Whether the extension is allowed to execute the rule.
   */
  #isRuleActionAllowed(rule) {
    if (this.canModify) {
      return true;
    }
    switch (rule.action.type) {
      case "allow":
      case "allowAllRequests":
      case "block":
      case "upgradeScheme":
        return this.ruleManager.hasBlockPermission;
      case "redirect":
        return false;
      // case "modifyHeaders" is never an action for this.matchedRule.
      default:
        throw new Error(`Unexpected action type: ${rule.action.type}`);
    }
  }
}

/**
 * Checks whether a request from a document with the given URI is allowed to
 * be modified by an unprivileged extension (e.g. an extension without host
 * permissions but the "declarativeNetRequest" permission).
 * The output is comparable to WebExtensionPolicy::CanAccessURI for an extension
 * with the `<all_urls>` permission, for consistency with the webRequest API.
 *
 * @param {nsIURI} [uri] The URI of a request's loadingPrincipal. May be void
 *   if missing (e.g. top-level requests) or not a content principal.
 * @returns {boolean} Whether there is any extension that is allowed to see
 *   requests from a document with the given URI. Callers are expected to:
 *   - check system requests (and treat as true).
 *   - check WebExtensionPolicy.isRestrictedURI (and treat as true).
 */
function isRestrictedPrincipalURI(uri) {
  if (!uri) {
    // No URI, could be:
    // - System principal (caller should have checked and disallowed access).
    // - Expanded principal, typically content script in documents. If an
    //   extension content script managed to run there, that implies that an
    //   extension was able to access it.
    // - Null principal (e.g. sandboxed document, about:blank, data:).
    return false;
  }

  // An unprivileged extension with maximal host permissions has allowedOrigins
  // set to [`<all_urls>`, `moz-extension://extensions-own-uuid-here`].
  // `<all_urls>` matches PermittedSchemes from MatchPattern.cpp:
  // https://searchfox.org/mozilla-central/rev/55d5c4b9dffe5e59eb6b019c1a930ec9ada47e10/toolkit/components/extensions/MatchPattern.cpp#209-211
  // i.e. "http", "https", "ws", "wss", "file", "ftp", "data".
  // - It is not possible to have a loadingPrincipal for: ws, wss, ftp.
  // - data:-URIs always have an opaque origin, i.e. the principal is not a
  //   content principal, thus void here.
  // - The remaining schemes from `<all_urls>` are: http, https, file, data,
  //   and checked below.
  //
  // Privileged addons can also access resource: and about:, but we do not need
  // to support these now.

  // http(s) are common, and allowed, except for some restricted domains. The
  // caller is expected to check WebExtensionPolicy.isRestrictedURI.
  if (uri.schemeIs("http") || uri.schemeIs("https")) {
    return false; // Very common.
  }

  // moz-extension: is not restricted because an extension always has permission
  // to its own moz-extension:-origin. The caller is expected to verify that an
  // extension can only access its own URI.
  if (uri.schemeIs("moz-extension")) {
    return false;
  }

  // Requests from local files are intentionally allowed (bug 1621935).
  if (uri.schemeIs("file")) {
    return false;
  }

  // Anything else (e.g. resource:, about:newtab, etc.) is not allowed.
  return true;
}

const NetworkIntegration = {
  maxEvaluatedRulesCount: 0,

  register() {
    // We register via WebRequest.sys.mjs to ensure predictable ordering of DNR and
    // WebRequest behavior.
    lazy.WebRequest.setDNRHandlingEnabled(true);
  },
  unregister() {
    lazy.WebRequest.setDNRHandlingEnabled(false);
  },
  maybeUpdateTabIdChecker() {
    gHasAnyTabIdConditions = gRuleManagers.some(rm => rm.hasRulesWithTabIds);
  },

  startDNREvaluation(channel) {
    let ruleManagers = gRuleManagers;
    // TODO bug 1827422: Merge isRestrictedPrincipalURI with canModify.
    if (!channel.canModify || isRestrictedPrincipalURI(channel.documentURI)) {
      // Ignore system requests or requests to restricted domains.
      ruleManagers = [];
    }
    if (channel.loadInfo.originAttributes.privateBrowsingId > 0) {
      ruleManagers = ruleManagers.filter(
        rm => rm.extension.privateBrowsingAllowed
      );
    }
    if (ruleManagers.length && !lazy.gMatchRequestsFromOtherExtensions) {
      const policy = channel.loadInfo.loadingPrincipal?.addonPolicy;
      if (policy) {
        ruleManagers = ruleManagers.filter(
          rm => rm.extension.policy === policy
        );
      }
    }
    let matchedRules;
    if (ruleManagers.length) {
      const evaluateRulesTimerId =
        Glean.extensionsApisDnr.evaluateRulesTime.start();
      try {
        const request = RequestDetails.fromChannelWrapper(channel);
        matchedRules = RequestEvaluator.evaluateRequest(request, ruleManagers);
      } finally {
        if (evaluateRulesTimerId !== undefined) {
          Glean.extensionsApisDnr.evaluateRulesTime.stopAndAccumulate(
            evaluateRulesTimerId
          );
        }
      }
      const evaluateRulesCount = ruleManagers.reduce(
        (sum, ruleManager) => sum + ruleManager.getRulesCount(),
        0
      );
      if (evaluateRulesCount > this.maxEvaluatedRulesCount) {
        Glean.extensionsApisDnr.evaluateRulesCountMax.set(evaluateRulesCount);
        this.maxEvaluatedRulesCount = evaluateRulesCount;
      }
    }
    // Cache for later. In case of redirects, _dnrMatchedRules may exist for
    // the pre-redirect HTTP channel, and is overwritten here again.
    channel._dnrMatchedRules = matchedRules;
  },

  /**
   * Applies the actions of the DNR rules.
   *
   * @typedef {ChannelWrapper & { _dnrMatchedRules?: MatchedRule[] }}
   *          ChannelWrapperViaDNR
   *
   * @param {ChannelWrapperViaDNR} channel
   * @returns {boolean} Whether to ignore any responses from the webRequest API.
   */
  onBeforeRequest(channel) {
    let matchedRules = channel._dnrMatchedRules;
    if (!matchedRules?.length) {
      return false;
    }
    // If a matched rule closes the channel, it is the sole match.
    const finalMatch = matchedRules[0];
    switch (finalMatch.rule.action.type) {
      case "block":
        this.applyBlock(channel, finalMatch);
        return true;
      case "redirect":
        this.applyRedirect(channel, finalMatch);
        return true;
      case "upgradeScheme":
        this.applyUpgradeScheme(channel);
        return true;
    }
    // If there are multiple rules, then it may be a combination of allow,
    // allowAllRequests and/or modifyHeaders.

    // "modifyHeaders" is handled by onBeforeSendHeaders/onHeadersReceived.
    // "allow" and "allowAllRequests" require no further action now.
    // "allowAllRequests" is applied to new requests in the future (if any)
    // through RequestEvaluator's findAncestorRuleOverride().

    return false;
  },

  onBeforeSendHeaders(channel) {
    let matchedRules = channel._dnrMatchedRules;
    if (!matchedRules?.length) {
      return;
    }
    ModifyRequestHeaders.maybeApplyModifyHeaders(channel, matchedRules);
  },

  onHeadersReceived(channel) {
    let matchedRules = channel._dnrMatchedRules;
    if (!matchedRules?.length) {
      return;
    }
    ModifyResponseHeaders.maybeApplyModifyHeaders(channel, matchedRules);
  },

  applyBlock(channel, matchedRule) {
    // TODO bug 1802259: Consider a DNR-specific reason.
    channel.cancel(
      Cr.NS_ERROR_ABORT,
      Ci.nsILoadInfo.BLOCKING_REASON_EXTENSION_WEBREQUEST
    );
    const addonId = matchedRule.ruleManager.extension.id;
    let properties = channel.channel.QueryInterface(Ci.nsIWritablePropertyBag);
    properties.setProperty("cancelledByExtension", addonId);
  },

  applyUpgradeScheme(channel) {
    // Request upgrade. No-op if already secure (i.e. https).
    channel.upgradeToSecure();
  },

  applyRedirect(channel, matchedRule) {
    // Ambiguity resolution order of redirect dict keys, consistent with Chrome:
    // - url > extensionPath > transform > regexSubstitution
    const redirect = matchedRule.rule.action.redirect;
    const extension = matchedRule.ruleManager.extension;
    const preRedirectUri = channel.finalURI;
    let redirectUri;
    if (redirect.url) {
      // redirect.url already validated by checkActionRedirect.
      redirectUri = Services.io.newURI(redirect.url);
    } else if (redirect.extensionPath) {
      redirectUri = extension.baseURI
        .mutate()
        .setPathQueryRef(redirect.extensionPath)
        .finalize();
    } else if (redirect.transform) {
      redirectUri = applyURLTransform(preRedirectUri, redirect.transform);
    } else if (redirect.regexSubstitution) {
      // Note: may throw if regexSubstitution results in an invalid redirect.
      // The error propagates up to handleRequest, which will just allow the
      // request to continue.
      redirectUri = applyRegexSubstitution(preRedirectUri, matchedRule);
    } else {
      // #checkActionRedirect ensures that the redirect action is non-empty.
    }

    if (preRedirectUri.equals(redirectUri)) {
      // URL did not change. Sometimes it is a bug in the extension, but there
      // are also cases where the result is unavoidable. E.g. redirect.transform
      // with queryTransform.removeParams that does not remove anything.
      // TODO: consider logging to help with debugging.
      return;
    }

    channel.redirectTo(redirectUri);

    let properties = channel.channel.QueryInterface(Ci.nsIWritablePropertyBag);
    properties.setProperty("redirectedByExtension", extension.id);

    let origin = channel.getRequestHeader("Origin");
    if (origin) {
      channel.setResponseHeader("Access-Control-Allow-Origin", origin);
      channel.setResponseHeader("Access-Control-Allow-Credentials", "true");
      channel.setResponseHeader("Access-Control-Max-Age", "0");
    }
  },
};

class RuleManager {
  constructor(extension) {
    this.extension = extension;
    this.sessionRules = this.makeRuleset(
      "_session",
      PRECEDENCE_SESSION_RULESET
    );
    this.dynamicRules = this.makeRuleset(
      "_dynamic",
      PRECEDENCE_DYNAMIC_RULESET
    );
    this.enabledStaticRules = [];

    this.hasBlockPermission = extension.hasPermission("declarativeNetRequest");
    this.hasRulesWithTabIds = false;
    this.hasRulesWithAllowAllRequests = false;
    this.totalRulesCount = 0;
  }

  get availableStaticRuleCount() {
    return Math.max(
      lazy.ExtensionDNRLimits.GUARANTEED_MINIMUM_STATIC_RULES -
        this.enabledStaticRules.reduce(
          (acc, ruleset) => acc + ruleset.rules.length,
          0
        ),
      0
    );
  }

  get enabledStaticRulesetIds() {
    return this.enabledStaticRules.map(ruleset => ruleset.id);
  }

  makeRuleset(
    rulesetId,
    rulesetPrecedence,
    rules = [],
    disabledRuleIds = null
  ) {
    return new Ruleset(
      rulesetId,
      rulesetPrecedence,
      rules,
      disabledRuleIds,
      this
    );
  }

  setSessionRules(validatedSessionRules) {
    let oldRulesCount = this.sessionRules.rules.length;
    let newRulesCount = validatedSessionRules.length;
    this.sessionRules.rules = validatedSessionRules;
    this.totalRulesCount += newRulesCount - oldRulesCount;
    this.hasRulesWithTabIds = !!this.sessionRules.rules.find(rule => {
      return rule.condition.tabIds || rule.condition.excludedTabIds;
    });
    this.#updateAllowAllRequestRules();
    NetworkIntegration.maybeUpdateTabIdChecker();
  }

  setDynamicRules(validatedDynamicRules) {
    let oldRulesCount = this.dynamicRules.rules.length;
    let newRulesCount = validatedDynamicRules.length;
    this.dynamicRules.rules = validatedDynamicRules;
    this.totalRulesCount += newRulesCount - oldRulesCount;
    this.#updateAllowAllRequestRules();
  }

  /**
   * Set the enabled static rulesets.
   *
   * @param {Array<{ id, rules, disabledRuleIds }>} enabledStaticRulesets
   *        Array of objects including the ruleset id and rules.
   *        The order of the rulesets in the Array is expected to
   *        match the order of the rulesets in the extension manifest.
   */
  setEnabledStaticRulesets(enabledStaticRulesets) {
    const rulesets = [];
    for (const [
      idx,
      { id, rules, disabledRuleIds },
    ] of enabledStaticRulesets.entries()) {
      rulesets.push(
        this.makeRuleset(
          id,
          idx + PRECEDENCE_STATIC_RULESETS_BASE,
          rules,
          disabledRuleIds
        )
      );
    }
    const countRules = rulesets =>
      rulesets.reduce((sum, ruleset) => sum + ruleset.rules.length, 0);
    const oldRulesCount = countRules(this.enabledStaticRules);
    const newRulesCount = countRules(rulesets);
    this.enabledStaticRules = rulesets;
    this.totalRulesCount += newRulesCount - oldRulesCount;
    this.#updateAllowAllRequestRules();
  }

  /**
   * Get the session scoped rules.
   *
   * @param {Array<integer>|null} ruleIds
            Optional array of rule IDs to return. By default, all the session
            scoped rules are returned.
   */
  getSessionRules(ruleIds = null) {
    if (!ruleIds) {
      return this.sessionRules.rules;
    }

    return this.sessionRules.rules.filter(rule => ruleIds.includes(rule.id));
  }

  /**
   * Get the dynamic rules.
   *
   * @param {Array<integer>|null} ruleIds
            Optional array of rule IDs to return. By default, all the dynamic
            rules are returned.
   */
  getDynamicRules(ruleIds = null) {
    if (!ruleIds) {
      return this.dynamicRules.rules;
    }

    return this.dynamicRules.rules.filter(rule => ruleIds.includes(rule.id));
  }

  getRulesCount() {
    return this.totalRulesCount;
  }

  #updateAllowAllRequestRules() {
    const filterAAR = rule => rule.action.type === "allowAllRequests";
    this.hasRulesWithAllowAllRequests =
      this.sessionRules.rules.some(filterAAR) ||
      this.dynamicRules.rules.some(filterAAR) ||
      this.enabledStaticRules.some(ruleset => ruleset.rules.some(filterAAR));
  }
}

function getRuleManager(extension, createIfMissing = true) {
  let ruleManager = gRuleManagers.find(rm => rm.extension === extension);
  if (!ruleManager && createIfMissing) {
    if (extension.hasShutdown) {
      throw new Error(
        `Error on creating new DNR RuleManager after extension shutdown: ${extension.id}`
      );
    }
    ruleManager = new RuleManager(extension);
    // The most recently installed extension gets priority, i.e. appears at the
    // start of the gRuleManagers list. It is not yet possible to determine the
    // installation time of a given Extension, so currently the last to
    // instantiate a RuleManager claims the highest priority.
    // TODO bug 1786059: order extensions by "installation time".
    gRuleManagers.unshift(ruleManager);
    if (gRuleManagers.length === 1) {
      // The first DNR registration.
      NetworkIntegration.register();
    }
  }
  return ruleManager;
}

function clearRuleManager(extension) {
  let i = gRuleManagers.findIndex(rm => rm.extension === extension);
  if (i !== -1) {
    gRuleManagers.splice(i, 1);
    NetworkIntegration.maybeUpdateTabIdChecker();
    if (gRuleManagers.length === 0) {
      // The last DNR registration.
      NetworkIntegration.unregister();
    }
  }
}

/**
 * Finds all matching rules for a request, optionally restricted to one
 * extension. Used by declarativeNetRequest.testMatchOutcome.
 *
 * @param {object|RequestDetails} request
 * @param {Extension} [extension]
 * @returns {MatchedRule[]}
 */
function getMatchedRulesForRequest(request, extension) {
  let requestDetails = new RequestDetails(request);
  const { requestURI, initiatorURI } = requestDetails;
  let ruleManagers = gRuleManagers;
  if (extension) {
    ruleManagers = ruleManagers.filter(rm => rm.extension === extension);
  }
  if (
    // NetworkIntegration.startDNREvaluation does not check requestURI, but we
    // do that here to filter URIs that are obviously disallowed. In practice,
    // anything other than http(s) is bogus and unsupported in DNR.
    isRestrictedPrincipalURI(requestURI) ||
    // Equivalent to NetworkIntegration.startDNREvaluation's channel.canModify
    // check, which excludes system requests and restricted domains.
    WebExtensionPolicy.isRestrictedURI(requestURI) ||
    (initiatorURI && WebExtensionPolicy.isRestrictedURI(initiatorURI)) ||
    isRestrictedPrincipalURI(initiatorURI)
  ) {
    ruleManagers = [];
  }
  // While this simulated request is not really from another extension, apply
  // the same access control checks from NetworkIntegration.startDNREvaluation
  // for consistency.
  if (
    !lazy.gMatchRequestsFromOtherExtensions &&
    initiatorURI?.schemeIs("moz-extension")
  ) {
    const extUuid = initiatorURI.host;
    ruleManagers = ruleManagers.filter(rm => rm.extension.uuid === extUuid);
  }
  return RequestEvaluator.evaluateRequest(requestDetails, ruleManagers);
}

/**
 * Runs before any webRequest event is notified. Headers may be modified, but
 * the request should not be canceled (see handleRequest instead).
 *
 * @param {ChannelWrapper} channel
 * @param {string} kind - The name of the webRequest event.
 */
function beforeWebRequestEvent(channel, kind) {
  try {
    switch (kind) {
      case "onBeforeRequest":
        NetworkIntegration.startDNREvaluation(channel);
        break;
      case "onBeforeSendHeaders":
        NetworkIntegration.onBeforeSendHeaders(channel);
        break;
      case "onHeadersReceived":
        NetworkIntegration.onHeadersReceived(channel);
        break;
    }
  } catch (e) {
    Cu.reportError(e);
  }
}

/**
 * Applies matching DNR rules, some of which may potentially cancel the request.
 *
 * @param {ChannelWrapperViaDNR} channel
 * @param {string} kind - The name of the webRequest event.
 * @returns {boolean} Whether to ignore any responses from the webRequest API.
 */
function handleRequest(channel, kind) {
  try {
    if (kind === "onBeforeRequest") {
      return NetworkIntegration.onBeforeRequest(channel);
    }
  } catch (e) {
    Cu.reportError(e);
  }
  return false;
}

async function initExtension(extension) {
  // These permissions are NOT an OptionalPermission, so their status can be
  // assumed to be constant for the lifetime of the extension.
  if (
    extension.hasPermission("declarativeNetRequest") ||
    extension.hasPermission("declarativeNetRequestWithHostAccess")
  ) {
    if (extension.hasShutdown) {
      throw new Error(
        `Aborted ExtensionDNR.initExtension call, extension "${extension.id}" is not active anymore`
      );
    }
    extension.once("shutdown", () => clearRuleManager(extension));
    await lazy.ExtensionDNRStore.initExtension(extension);
  }
}

function ensureInitialized(extension) {
  return (extension._dnrReady ??= initExtension(extension));
}

function validateManifestEntry(extension) {
  const ruleResourcesArray =
    extension.manifest.declarative_net_request.rule_resources;

  const getWarningMessage = msg =>
    `Warning processing declarative_net_request: ${msg}`;

  const { MAX_NUMBER_OF_STATIC_RULESETS } = lazy.ExtensionDNRLimits;
  if (ruleResourcesArray.length > MAX_NUMBER_OF_STATIC_RULESETS) {
    extension.manifestWarning(
      getWarningMessage(
        `Static rulesets are exceeding the MAX_NUMBER_OF_STATIC_RULESETS limit (${MAX_NUMBER_OF_STATIC_RULESETS}).`
      )
    );
  }

  const seenRulesetIds = new Set();
  const seenRulesetPaths = new Set();
  const duplicatedRulesetIds = [];
  const duplicatedRulesetPaths = [];
  for (const [idx, { id, path }] of ruleResourcesArray.entries()) {
    if (seenRulesetIds.has(id)) {
      duplicatedRulesetIds.push({ idx, id });
    }
    if (seenRulesetPaths.has(path)) {
      duplicatedRulesetPaths.push({ idx, path });
    }
    seenRulesetIds.add(id);
    seenRulesetPaths.add(path);
  }

  if (duplicatedRulesetIds.length) {
    const errorDetails = duplicatedRulesetIds
      .map(({ idx, id }) => `"${id}" at index ${idx}`)
      .join(", ");
    extension.manifestWarning(
      getWarningMessage(
        `Static ruleset ids should be unique, duplicated ruleset ids: ${errorDetails}.`
      )
    );
  }

  if (duplicatedRulesetPaths.length) {
    // NOTE: technically Chrome allows duplicated paths without any manifest
    // validation warnings or errors, but if this happens it not unlikely to be
    // actually a mistake in the manifest that may have been missed.
    //
    // In Firefox we decided to allow the same behavior to avoid introducing a chrome
    // incompatibility, but we still warn about it to avoid extension developers
    // to investigate more easily issue that may be due to duplicated rulesets
    // paths.
    const errorDetails = duplicatedRulesetPaths
      .map(({ idx, path }) => `"${path}" at index ${idx}`)
      .join(", ");
    extension.manifestWarning(
      getWarningMessage(
        `Static rulesets paths are not unique, duplicated ruleset paths: ${errorDetails}.`
      )
    );
  }

  const { MAX_NUMBER_OF_ENABLED_STATIC_RULESETS } = lazy.ExtensionDNRLimits;

  const enabledRulesets = ruleResourcesArray.filter(rs => rs.enabled);
  if (enabledRulesets.length > MAX_NUMBER_OF_ENABLED_STATIC_RULESETS) {
    const exceedingRulesetIds = enabledRulesets
      .slice(MAX_NUMBER_OF_ENABLED_STATIC_RULESETS)
      .map(ruleset => `"${ruleset.id}"`)
      .join(", ");
    extension.manifestWarning(
      getWarningMessage(
        `Enabled static rulesets are exceeding the MAX_NUMBER_OF_ENABLED_STATIC_RULESETS limit (${MAX_NUMBER_OF_ENABLED_STATIC_RULESETS}): ${exceedingRulesetIds}.`
      )
    );
  }
}

async function updateEnabledStaticRulesets(extension, updateRulesetOptions) {
  await ensureInitialized(extension);
  await lazy.ExtensionDNRStore.updateEnabledStaticRulesets(
    extension,
    updateRulesetOptions
  );
}

async function updateDynamicRules(extension, updateRuleOptions) {
  await ensureInitialized(extension);
  await lazy.ExtensionDNRStore.updateDynamicRules(extension, updateRuleOptions);
}

async function updateStaticRules(extension, updateStaticRulesOptions) {
  await ensureInitialized(extension);
  await lazy.ExtensionDNRStore.updateStaticRules(
    extension,
    updateStaticRulesOptions
  );
}

async function getDisabledRuleIds(extension, rulesetId) {
  await ensureInitialized(extension);
  return lazy.ExtensionDNRStore.getDisabledRuleIds(extension, rulesetId);
}

// exports used by the DNR API implementation.
export const ExtensionDNR = {
  RuleValidator,
  RuleQuotaCounter,
  clearRuleManager,
  ensureInitialized,
  getMatchedRulesForRequest,
  getDisabledRuleIds,
  getRuleManager,
  updateDynamicRules,
  updateEnabledStaticRulesets,
  updateStaticRules,
  validateManifestEntry,
  beforeWebRequestEvent,
  handleRequest,
};
PK
!<؀ͩ��"modules/ExtensionDNRLimits.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

// TODO(Bug 1803370): allow extension to exceed the GUARANTEED_MINIMUM_STATIC_RULES limit.
//
// The maximum number of static rules exceeding the per-extension
// GUARANTEED_MINIMUM_STATIC_RULES across every extensions.
//
// const MAX_GLOBAL_NUMBER_OF_STATIC_RULES = 300000;

const lazy = {};

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "GUARANTEED_MINIMUM_STATIC_RULES",
  "extensions.dnr.guaranteed_minimum_static_rules",
  30000
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "MAX_NUMBER_OF_STATIC_RULESETS",
  "extensions.dnr.max_number_of_static_rulesets",
  100
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "MAX_NUMBER_OF_ENABLED_STATIC_RULESETS",
  "extensions.dnr.max_number_of_enabled_static_rulesets",
  20
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "MAX_NUMBER_OF_DISABLED_STATIC_RULES",
  "extensions.dnr.max_number_of_disabled_static_rules",
  5000
);

/**
 * NOTE: this limit may be increased in the future, see
 * https://bugzilla.mozilla.org/show_bug.cgi?id=1894119
 */
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "MAX_NUMBER_OF_DYNAMIC_RULES",
  "extensions.dnr.max_number_of_dynamic_rules",
  5000
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "MAX_NUMBER_OF_SESSION_RULES",
  "extensions.dnr.max_number_of_session_rules",
  5000
);

// TODO bug 1821033: Bump limit after optimizing regexFilter.
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "MAX_NUMBER_OF_REGEX_RULES",
  "extensions.dnr.max_number_of_regex_rules",
  1000
);

export const ExtensionDNRLimits = {
  /**
   * The minimum number of static rules guaranteed to an extension across its
   * enabled static rulesets. Any rules above this limit will count towards the
   * global static rule limit.
   */
  get GUARANTEED_MINIMUM_STATIC_RULES() {
    return lazy.GUARANTEED_MINIMUM_STATIC_RULES;
  },

  /**
   * The maximum number of static Rulesets an extension can specify as part of
   * the "rule_resources" manifest key.
   */
  get MAX_NUMBER_OF_STATIC_RULESETS() {
    return lazy.MAX_NUMBER_OF_STATIC_RULESETS;
  },

  /**
   * The maximum number of static Rulesets an extension can enable at any one
   * time.
   */
  get MAX_NUMBER_OF_ENABLED_STATIC_RULESETS() {
    return lazy.MAX_NUMBER_OF_ENABLED_STATIC_RULESETS;
  },

  /**
   * The maximum number of static rules that can be disabled on each static
   * ruleset.
   */
  get MAX_NUMBER_OF_DISABLED_STATIC_RULES() {
    return lazy.MAX_NUMBER_OF_DISABLED_STATIC_RULES;
  },

  /**
   * The maximum number of dynamic rules an extension can add.
   */
  get MAX_NUMBER_OF_DYNAMIC_RULES() {
    return lazy.MAX_NUMBER_OF_DYNAMIC_RULES;
  },

  /**
   * The maximum number of session rules an extension can add.
   */
  get MAX_NUMBER_OF_SESSION_RULES() {
    return lazy.MAX_NUMBER_OF_SESSION_RULES;
  },

  /**
   * The maximum number of regular expression rules that an extension can add.
   * Session, dynamic and static rules have their own quota.
   */
  get MAX_NUMBER_OF_REGEX_RULES() {
    return lazy.MAX_NUMBER_OF_REGEX_RULES;
  },
};
PK
!<g1�MM!modules/ExtensionDNRStore.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ExtensionParent } from "resource://gre/modules/ExtensionParent.sys.mjs";

import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { ExtensionDNRLimits } from "./ExtensionDNRLimits.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  Extension: "resource://gre/modules/Extension.sys.mjs",
  ExtensionDNR: "resource://gre/modules/ExtensionDNR.sys.mjs",
  ExtensionDNRLimits: "resource://gre/modules/ExtensionDNRLimits.sys.mjs",
  Schemas: "resource://gre/modules/Schemas.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetters(lazy, {
  aomStartup: [
    "@mozilla.org/addons/addon-manager-startup;1",
    "amIAddonManagerStartup",
  ],
});

const LAST_UPDATE_TAG_PREF_PREFIX = "extensions.dnr.lastStoreUpdateTag.";

const { DefaultMap, ExtensionError } = ExtensionUtils;
const { StartupCache } = ExtensionParent;

// DNR Rules store subdirectory/file names and file extensions.
//
// NOTE: each extension's stored rules are stored in a per-extension file
// and stored rules filename is derived from the extension uuid assigned
// at install time.
const RULES_STORE_DIRNAME = "extension-dnr";
const RULES_STORE_FILEEXT = ".json.lz4";
const RULES_CACHE_FILENAME = "extensions-dnr.sc.lz4";

const requireTestOnlyCallers = () => {
  if (!Services.env.exists("XPCSHELL_TEST_PROFILE_DIR")) {
    throw new Error("This should only be called from XPCShell tests");
  }
};

/**
 * Internal representation of the enabled static rulesets (used in StoreData
 * and Store methods type signatures).
 *
 * @typedef {object} EnabledStaticRuleset
 * @inner
 * @property {number} idx
 *           Represent the position of the static ruleset in the manifest
 *           `declarative_net_request.rule_resources` array.
 * @property {Array<Rule>} rules
 *           Represent the array of the DNR rules associated with the static
 *           ruleset.
 */

// Class defining the format of the data stored into the per-extension files
// managed by RulesetsStore.
//
// StoreData instances are saved in the profile extension-dir subdirectory as
// lz4-compressed JSON files, only the ruleset_id is stored on disk for the
// enabled static rulesets (while the actual rules would need to be loaded back
// from the related rules JSON files part of the extension assets).
class StoreData {
  // NOTE: Update schema version upgrade handling code in `StoreData.fromJSON`
  // along with bumps to the schema version here.
  //
  // Changelog:
  // - 1: Initial DNR store schema:
  //      Initial implementation officially release in Firefox 113.
  //      Support for disableStaticRuleIds added in Firefox 128 (Bug 1810762).
  static VERSION = 1;

  static getLastUpdateTagPref(extensionUUID) {
    return `${LAST_UPDATE_TAG_PREF_PREFIX}${extensionUUID}`;
  }

  static getLastUpdateTag(extensionUUID) {
    return Services.prefs.getCharPref(
      this.getLastUpdateTagPref(extensionUUID),
      null
    );
  }

  static storeLastUpdateTag(extensionUUID, lastUpdateTag) {
    Services.prefs.setCharPref(
      this.getLastUpdateTagPref(extensionUUID),
      lastUpdateTag
    );
  }

  static clearLastUpdateTagPref(extensionUUID) {
    Services.prefs.clearUserPref(this.getLastUpdateTagPref(extensionUUID));
  }

  static isStaleCacheEntry(extensionUUID, cacheStoreData) {
    return (
      // Drop the cache entry if the data stored doesn't match the current
      // StoreData schema version (this shouldn't happen unless the file
      // have been manually restored by the user from an older firefox version).
      cacheStoreData.schemaVersion !== this.VERSION ||
      // Drop the cache entry if the lastUpdateTag from the cached data entry
      // doesn't match the lastUpdateTag recorded in the prefs, the tag is applied
      // with a per-extension granularity to reduce the chances of cache misses
      // last update on the cached data for an unrelated extensions did not make it
      // to disk).
      cacheStoreData.lastUpdateTag != this.getLastUpdateTag(extensionUUID)
    );
  }

  #extUUID;
  #initialLastUdateTag;
  #temporarilyInstalled;

  /**
   * @param {Extension} extension
   *        The extension the StoreData is associated to.
   * @param {object} params
   * @param {string} [params.extVersion]
   *        extension version
   * @param {string} [params.lastUpdateTag]
   *        a tag associated to the data. It is only passed when we are loading the data
   *        from the StartupCache file, while a new tag uuid string will be generated
   *        for brand new data (and then new ones generated on each calls to the `updateRulesets`
   *        method).
   * @param {number} [params.schemaVersion=StoreData.VERSION]
   *        file schema version
   * @param {Map<string, EnabledStaticRuleset>} [params.staticRulesets=new Map()]
   *        map of the enabled static rulesets by ruleset_id, as resolved by
   *        `Store.prototype.#getManifestStaticRulesets`.
   *        NOTE: This map is converted in an array of the ruleset_id strings when the StoreData
   *        instance is being stored on disk (see `toJSON` method) and then converted back to a Map
   *        by `Store.prototype.#getManifestStaticRulesets` when the data is loaded back from disk.
   * @param {object} [params.disabledStaticRuleIds={}]
   *        map of the disabled static rule ids by ruleset_id. This map is updated by the extension
   *        calls to the updateStaticRules API method and persisted across browser session,
   *        and browser and extension updates. Disabled rule ids for a disabled ruleset are going
   *        to become effective when the disabled ruleset is enabled (e.g. through updateEnabledRulesets
   *        API calls or through manifest in extension updates).
   * @param {Array<Rule>} [params.dynamicRuleset=[]]
   *        array of dynamic rules stored by the extension.
   */
  constructor(
    extension,
    {
      extVersion,
      lastUpdateTag,
      dynamicRuleset,
      disabledStaticRuleIds,
      staticRulesets,
      schemaVersion,
    } = {}
  ) {
    if (!(extension instanceof lazy.Extension)) {
      throw new Error("Missing mandatory extension parameter");
    }
    this.schemaVersion = schemaVersion || StoreData.VERSION;
    this.extVersion = extVersion ?? extension.version;
    this.#extUUID = extension.uuid;
    // Used to skip storing the data in the startupCache or storing the lastUpdateTag in
    // the about:config prefs.
    this.#temporarilyInstalled = extension.temporarilyInstalled;
    // The lastUpdateTag gets set (and updated) by calls to updateRulesets.
    this.lastUpdateTag = undefined;
    this.#initialLastUdateTag = lastUpdateTag;

    this.#updateRulesets({
      staticRulesets: staticRulesets ?? new Map(),
      disabledStaticRuleIds: disabledStaticRuleIds ?? {},
      dynamicRuleset: dynamicRuleset ?? [],
      lastUpdateTag,
    });
  }

  isFromStartupCache() {
    return this.#initialLastUdateTag == this.lastUpdateTag;
  }

  isFromTemporarilyInstalled() {
    return this.#temporarilyInstalled;
  }

  get isEmpty() {
    return !this.staticRulesets.size && !this.dynamicRuleset.length;
  }

  /**
   * Updates the static and or dynamic rulesets stored for the related
   * extension.
   *
   * NOTE: This method also:
   * - regenerates the lastUpdateTag associated as an unique identifier
   *   of the revision for the stored data (used to detect stale startup
   *   cache data)
   * - stores the lastUpdateTag into an about:config pref associated to
   *   the extension uuid (also used as part of detecting stale startup
   *   cache data), unless the extension is installed temporarily.
   *
   * @param {object} params
   * @param {Map<string, EnabledStaticRuleset>} [params.staticRulesets]
   *        optional new updated Map of static rulesets
   *        (static rulesets are unchanged if not passed).
   * @param {object} [params.disabledStaticRuleIds]
   *        optional new updated Map of static rules ids disabled individually.
   * @param {Array<Rule>} [params.dynamicRuleset=[]]
   *        optional array of updated dynamic rules
   *        (dynamic rules are unchanged if not passed).
   */
  updateRulesets({
    staticRulesets,
    disabledStaticRuleIds,
    dynamicRuleset,
  } = {}) {
    let currentUpdateTag = this.lastUpdateTag;
    let lastUpdateTag = this.#updateRulesets({
      staticRulesets,
      disabledStaticRuleIds,
      dynamicRuleset,
    });

    // Tag each cache data entry with a value synchronously stored in an
    // about:config prefs, if on a browser restart the tag in the startupCache
    // data entry doesn't match the one in the about:config pref then the startup
    // cache entry is dropped as stale (assuming an issue prevented the updated
    // cache data to be written on disk, e.g. browser crash, failure on writing
    // on disk etc.), each entry is tagged separately to decrease the chances
    // of cache misses on unrelated cache data entries if only a few extension
    // got stale data in the startup cache file.
    if (
      !this.isFromTemporarilyInstalled() &&
      currentUpdateTag != lastUpdateTag
    ) {
      StoreData.storeLastUpdateTag(this.#extUUID, lastUpdateTag);
    }
  }

  #updateRulesets({
    staticRulesets = null,
    disabledStaticRuleIds = null,
    dynamicRuleset = null,
    lastUpdateTag = Services.uuid.generateUUID().toString(),
  } = {}) {
    if (staticRulesets) {
      this.staticRulesets = staticRulesets;
    }

    if (disabledStaticRuleIds) {
      this.disabledStaticRuleIds = disabledStaticRuleIds;
    }

    if (dynamicRuleset) {
      this.dynamicRuleset = dynamicRuleset;
    }

    if (staticRulesets || dynamicRuleset) {
      this.lastUpdateTag = lastUpdateTag;
    }

    return this.lastUpdateTag;
  }

  // This method is used to convert the data in the format stored on disk
  // as a JSON file.
  toJSON() {
    const data = {
      schemaVersion: this.schemaVersion,
      extVersion: this.extVersion,
      // Only store the array of the enabled ruleset_id in the set of data
      // persisted in a JSON form.
      staticRulesets: this.staticRulesets
        ? Array.from(this.staticRulesets.entries(), ([id, _ruleset]) => id)
        : undefined,
      disabledStaticRuleIds:
        this.disabledStaticRuleIds &&
        Object.keys(this.disabledStaticRuleIds).length
          ? this.disabledStaticRuleIds
          : undefined,
      dynamicRuleset: this.dynamicRuleset,
    };
    return data;
  }

  // This method is used to convert the data back to a StoreData class from
  // the format stored on disk as a JSON file.
  // NOTE: this method should be kept in sync with toJSON and make sure that
  // we do deserialize the same property we are serializing into the JSON file.
  static fromJSON(paramsFromJSON, extension) {
    // TODO: Add schema versions migrations here if necessary.
    // if (paramsFromJSON.version < StoreData.VERSION) {
    //   paramsFromJSON = this.upgradeStoreDataSchema(paramsFromJSON);
    // }

    let {
      schemaVersion,
      extVersion,
      staticRulesets,
      disabledStaticRuleIds,
      dynamicRuleset,
    } = paramsFromJSON;

    return new StoreData(extension, {
      schemaVersion,
      extVersion,
      staticRulesets,
      disabledStaticRuleIds,
      dynamicRuleset,
    });
  }
}

class Queue {
  #tasks = [];
  #runningTask = null;
  #closed = false;

  get hasPendingTasks() {
    return !!this.#runningTask || !!this.#tasks.length;
  }

  get isClosed() {
    return this.#closed;
  }

  async close() {
    if (this.#closed) {
      const lastTask = this.#tasks[this.#tasks.length - 1];
      return lastTask?.deferred.promise;
    }
    const drainedQueuePromise = this.queueTask(() => {});
    this.#closed = true;
    return drainedQueuePromise;
  }

  queueTask(callback) {
    if (this.#closed) {
      throw new Error("Unexpected queueTask call on closed queue");
    }
    const deferred = Promise.withResolvers();
    this.#tasks.push({ callback, deferred });
    // Run the queued task right away if there isn't one already running.
    if (!this.#runningTask) {
      this.#runNextTask();
    }
    return deferred.promise;
  }

  async #runNextTask() {
    if (!this.#tasks.length) {
      this.#runningTask = null;
      return;
    }

    this.#runningTask = this.#tasks.shift();
    const { callback, deferred } = this.#runningTask;
    try {
      let result = callback();
      if (result instanceof Promise) {
        result = await result;
      }
      deferred.resolve(result);
    } catch (err) {
      deferred.reject(err);
    }

    this.#runNextTask();
  }
}

/**
 * Class managing the rulesets persisted across browser sessions.
 *
 * The data gets stored in two per-extension files:
 *
 * - `ProfD/extension-dnr/EXT_UUID.json.lz4` is a lz4-compressed JSON file that is expected to include
 *   the ruleset ids for the enabled static rulesets and the dynamic rules.
 *
 * All browser data stored is expected to be persisted across browser updates, but the enabled static ruleset
 * ids are expected to be reset and reinitialized from the extension manifest.json properties when the
 * add-on is being updated (either downgraded or upgraded).
 *
 * In case of unexpected data schema downgrades (which may be hit if the user explicit pass --allow-downgrade
 * while using an older browser version than the one used when the data has been stored), the entire stored
 * data is reset and re-initialized from scratch based on the manifest.json file.
 */
class RulesetsStore {
  constructor() {
    // Map<extensionUUID, StoreData>
    this._data = new Map();
    // Map<extensionUUID, Promise<StoreData>>
    this._dataPromises = new Map();
    // Map<extensionUUID, Promise<void>>
    this._savePromises = new Map();
    // Map<extensionUUID, Queue>
    this._dataUpdateQueues = new DefaultMap(() => new Queue());
    // Promise to await on to ensure the store parent directory exist
    // (the parent directory is shared by all extensions and so we only need one).
    this._ensureStoreDirectoryPromise = null;
    // Promise to await on to ensure (there is only one startupCache file for all
    // extensions and so we only need one):
    // - the cache file parent directory exist
    // - the cache file data has been loaded (if any was available and matching
    //   the last DNR data stored on disk)
    // - the cache file data has been saved.
    this._ensureCacheDirectoryPromise = null;
    this._ensureCacheLoaded = null;
    this._saveCacheTask = null;
    // Map of the raw data read from the startupCache.
    // Map<extensionUUID, Object>
    this._startupCacheData = new Map();
  }

  /**
   * Wait for the startup cache data to be stored on disk.
   *
   * NOTE: Only meant to be used in xpcshell tests.
   *
   * @returns {Promise<void>}
   */
  async waitSaveCacheDataForTesting() {
    requireTestOnlyCallers();
    if (this._saveCacheTask) {
      if (this._saveCacheTask.isRunning) {
        await this._saveCacheTask._runningPromise;
      }
      // #saveCacheDataNow() may schedule another save if anything has changed in between
      while (this._saveCacheTask.isArmed) {
        this._saveCacheTask.disarm();
        await this.#saveCacheDataNow();
      }
    }
  }

  /**
   * Remove store file for the given extension UUId from disk (used to remove all
   * data on addon uninstall).
   *
   * @param {string} extensionUUID
   * @returns {Promise<void>}
   */
  async clearOnUninstall(extensionUUID) {
    // TODO(Bug 1825510): call scheduleCacheDataSave to update the startup cache data
    // stored on disk, but skip it if it is late in the application shutdown.
    StoreData.clearLastUpdateTagPref(extensionUUID);
    const storeFile = this.#getStoreFilePath(extensionUUID);

    // TODO(Bug 1803363): consider collect telemetry on DNR store file removal errors.
    // TODO: consider catch and report unexpected errors
    await IOUtils.remove(storeFile, { ignoreAbsent: true });
  }

  /**
   * Load (or initialize) the store file data for the given extension and
   * return an Array of the dynamic rules.
   *
   * @param {Extension} extension
   *
   * @returns {Promise<Array<Rule>>}
   *          Resolve to a reference to the dynamic rules array.
   *          NOTE: the caller should never mutate the content of this array,
   *          updates to the dynamic rules should always go through
   *          the `updateDynamicRules` method.
   */
  async getDynamicRules(extension) {
    let data = await this.#getDataPromise(extension);
    return data.dynamicRuleset;
  }

  /**
   * Load (or initialize) the store file data for the given extension and
   * return a Map of the enabled static rulesets and their related rules.
   *
   * - if the extension manifest doesn't have any static rulesets declared in the
   *   manifest, returns null
   *
   * - if the extension version from the stored data doesn't match the current
   *   extension versions, the static rules are being reloaded from the manifest.
   *
   * @param {Extension} extension
   *
   * @returns {Promise<Map<ruleset_id, EnabledStaticRuleset>>}
   *          Resolves to a reference to the static rulesets map.
   *          NOTE: the caller should never mutate the content of this map,
   *          updates to the enabled static rulesets should always go through
   *          the `updateEnabledStaticRulesets` method.
   */
  async getEnabledStaticRulesets(extension) {
    let data = await this.#getDataPromise(extension);
    return data.staticRulesets;
  }

  /**
   * Returns the number of static rules still available to the given extension.
   *
   * @param {Extension} extension
   *
   * @returns {Promise<number>}
   *          Resolves to the number of static rules available.
   */
  async getAvailableStaticRuleCount(extension) {
    const { GUARANTEED_MINIMUM_STATIC_RULES } = lazy.ExtensionDNRLimits;

    const existingRulesetIds = this.#getExistingStaticRulesetIds(extension);
    if (!existingRulesetIds.length) {
      return GUARANTEED_MINIMUM_STATIC_RULES;
    }

    const enabledRulesets = await this.getEnabledStaticRulesets(extension);
    const enabledRulesCount = Array.from(enabledRulesets.values()).reduce(
      (acc, ruleset) => acc + ruleset.rules.length,
      0
    );

    return GUARANTEED_MINIMUM_STATIC_RULES - enabledRulesCount;
  }

  /**
   * Returns the static rule ids disabled individually for the given extension
   * and static ruleset id.
   *
   * @param {Extension} extension
   * @param {string} rulesetId
   *
   * @returns {Promise<Array<number>>}
   *          Resolves to the array of rule ids disabled.
   */
  async getDisabledRuleIds(extension, rulesetId) {
    const existingRulesetIds = this.#getExistingStaticRulesetIds(extension);
    if (!existingRulesetIds.includes(rulesetId)) {
      throw new ExtensionError(`Invalid ruleset id: "${rulesetId}"`);
    }

    let data = await this.#getDataPromise(extension);
    return data.disabledStaticRuleIds[rulesetId] ?? [];
  }

  /**
   * Initialize the DNR store for the given extension, it does also queue the task to make
   * sure that extension DNR API calls triggered while the initialization may still be
   * in progress will be executed sequentially.
   *
   * @param {Extension}     extension
   *
   * @returns {Promise<void>} A promise resolved when the async initialization has been
   *                          completed.
   */
  async initExtension(extension) {
    const ensureExtensionRunning = () => {
      if (extension.hasShutdown) {
        throw new Error(
          `DNR store initialization abort, extension is already shutting down: ${extension.id}`
        );
      }
    };

    // Make sure we wait for pending save promise to have been
    // completed and old data unloaded (this may be hit if an
    // extension updates or reloads while there are still
    // rules updates being processed and then stored on disk).
    ensureExtensionRunning();
    if (this._savePromises.has(extension.uuid)) {
      Cu.reportError(
        `Unexpected pending save task while reading DNR data after an install/update of extension "${extension.id}"`
      );
      // await pending saving data to be saved and unloaded.
      await this.#unloadData(extension.uuid);
      // Make sure the extension is still running after awaiting on
      // unloadData to be completed.
      ensureExtensionRunning();
    }

    return this._dataUpdateQueues.get(extension.uuid).queueTask(() => {
      return this.#initExtension(extension);
    });
  }

  /**
   * Update the dynamic rules, queue changes to prevent races between calls
   * that may be triggered while an update is still in process.
   *
   * @param {Extension}     extension
   * @param {object}        params
   * @param {Array<number>} [params.removeRuleIds=[]]
   * @param {Array<Rule>} [params.addRules=[]]
   *
   * @returns {Promise<void>} A promise resolved when the dynamic rules async update has
   *                          been completed.
   */
  async updateDynamicRules(extension, { removeRuleIds, addRules }) {
    return this._dataUpdateQueues.get(extension.uuid).queueTask(() => {
      return this.#updateDynamicRules(extension, {
        removeRuleIds,
        addRules,
      });
    });
  }

  /**
   * Update the static rules ids disabled individually on a given static ruleset id,
   * queue changes to prevent races between calls that may be triggered while an
   * update is still in process.
   *
   * @param {Extension}     extension
   * @param {object}        params
   * @param {string}        [params.rulesetId]
   * @param {Array<number>} [params.disableRuleIds]
   * @param {Array<number>} [params.enableRuleIds]
   *
   * @returns {Promise<void>} A promise resolved when the disabled rules async update has
   *                          been completed.
   */
  async updateStaticRules(
    extension,
    { rulesetId, disableRuleIds, enableRuleIds }
  ) {
    return this._dataUpdateQueues.get(extension.uuid).queueTask(() => {
      return this.#updateStaticRules(extension, {
        rulesetId,
        disableRuleIds,
        enableRuleIds,
      });
    });
  }

  /**
   * Update the enabled rulesets, queue changes to prevent races between calls
   * that may be triggered while an update is still in process.
   *
   * @param {Extension}     extension
   * @param {object}        params
   * @param {Array<string>} [params.disableRulesetIds=[]]
   * @param {Array<string>} [params.enableRulesetIds=[]]
   *
   * @returns {Promise<void>} A promise resolved when the enabled static rulesets async
   *                          update has been completed.
   */
  async updateEnabledStaticRulesets(
    extension,
    { disableRulesetIds, enableRulesetIds }
  ) {
    return this._dataUpdateQueues.get(extension.uuid).queueTask(() => {
      return this.#updateEnabledStaticRulesets(extension, {
        disableRulesetIds,
        enableRulesetIds,
      });
    });
  }

  /**
   * Update DNR RulesetManager rules to match the current DNR rules enabled in the DNRStore.
   *
   * @param {Extension} extension
   * @param {object}    [params]
   * @param {boolean}   [params.updateStaticRulesets=true]
   * @param {boolean}   [params.updateDynamicRuleset=true]
   */
  updateRulesetManager(
    extension,
    { updateStaticRulesets = true, updateDynamicRuleset = true } = {}
  ) {
    if (!updateStaticRulesets && !updateDynamicRuleset) {
      return;
    }

    if (
      !this._dataPromises.has(extension.uuid) ||
      !this._data.has(extension.uuid)
    ) {
      throw new Error(
        `Unexpected call to updateRulesetManager before DNR store was fully initialized for extension "${extension.id}"`
      );
    }
    const data = this._data.get(extension.uuid);
    const ruleManager = lazy.ExtensionDNR.getRuleManager(extension);

    if (updateStaticRulesets) {
      let staticRulesetsMap = data.staticRulesets;
      // Convert into array and ensure order match the order of the rulesets in
      // the extension manifest.
      const enabledStaticRules = [];
      // Order the static rulesets by index of rule_resources in manifest.json.
      const orderedRulesets = Array.from(staticRulesetsMap.entries()).sort(
        ([_idA, rsA], [_idB, rsB]) => rsA.idx - rsB.idx
      );
      for (const [rulesetId, ruleset] of orderedRulesets) {
        enabledStaticRules.push({
          id: rulesetId,
          rules: ruleset.rules,
          disabledRuleIds: data.disabledStaticRuleIds[rulesetId]
            ? new Set(data.disabledStaticRuleIds[rulesetId])
            : null,
        });
      }
      ruleManager.setEnabledStaticRulesets(enabledStaticRules);
    }

    if (updateDynamicRuleset) {
      ruleManager.setDynamicRules(data.dynamicRuleset);
    }
  }

  /**
   * Return the store file path for the given the extension's uuid and the cache
   * file with startupCache data for all the extensions.
   *
   * @param {string} extensionUUID
   * @returns {{ storeFile: string | void, cacheFile: string}}
   *          An object including the full paths to both the per-extension store file
   *          for the given extension UUID and the full path to the single startupCache
   *          file (which would include the cached data for all the extensions).
   */
  getFilePaths(extensionUUID) {
    return {
      storeFile: this.#getStoreFilePath(extensionUUID),
      cacheFile: this.#getCacheFilePath(),
    };
  }

  /**
   * Save the data for the given extension on disk.
   *
   * @param {Extension} extension
   */
  async save(extension) {
    const { uuid, id } = extension;
    let savePromise = this._savePromises.get(uuid);

    if (!savePromise) {
      savePromise = this.#saveNow(uuid, id);
      this._savePromises.set(uuid, savePromise);
      IOUtils.profileBeforeChange.addBlocker(
        `Flush WebExtension DNR RulesetsStore: ${id}`,
        savePromise
      );
    }

    return savePromise;
  }

  /**
   * Register an onClose shutdown handler to cleanup the data from memory when
   * the extension is shutting down.
   *
   * @param {Extension} extension
   * @returns {void}
   */
  unloadOnShutdown(extension) {
    if (extension.hasShutdown) {
      throw new Error(
        `DNR store registering an extension shutdown handler too late, the extension is already shutting down: ${extension.id}`
      );
    }

    const extensionUUID = extension.uuid;
    extension.callOnClose({
      close: async () => this.#unloadData(extensionUUID),
    });
  }

  /**
   * Return a branch new StoreData instance given an extension.
   *
   * @param {Extension} extension
   * @returns {StoreData}
   */
  #getDefaults(extension) {
    return new StoreData(extension, { extVersion: extension.version });
  }

  /**
   * Return the cache file path.
   *
   * @returns {string}
   *          The absolute path to the startupCache file.
   */
  #getCacheFilePath() {
    // When the application version changes, this file is removed by
    // RemoveComponentRegistries in nsAppRunner.cpp.
    return PathUtils.join(
      Services.dirsvc.get("ProfLD", Ci.nsIFile).path,
      "startupCache",
      RULES_CACHE_FILENAME
    );
  }

  /**
   * Return the path to the store file given the extension's uuid.
   *
   * @param {string} extensionUUID
   * @returns {string} Full path to the store file for the extension.
   */
  #getStoreFilePath(extensionUUID) {
    return PathUtils.join(
      Services.dirsvc.get("ProfD", Ci.nsIFile).path,
      RULES_STORE_DIRNAME,
      `${extensionUUID}${RULES_STORE_FILEEXT}`
    );
  }

  #ensureCacheDirectory() {
    if (this._ensureCacheDirectoryPromise === null) {
      const file = this.#getCacheFilePath();
      this._ensureCacheDirectoryPromise = IOUtils.makeDirectory(
        PathUtils.parent(file),
        {
          ignoreExisting: true,
          createAncestors: true,
        }
      );
    }
    return this._ensureCacheDirectoryPromise;
  }

  #ensureStoreDirectory(extensionUUID) {
    // Currently all extensions share the same directory, so we can re-use this promise across all
    // `#ensureStoreDirectory` calls.
    if (this._ensureStoreDirectoryPromise === null) {
      const file = this.#getStoreFilePath(extensionUUID);
      this._ensureStoreDirectoryPromise = IOUtils.makeDirectory(
        PathUtils.parent(file),
        {
          ignoreExisting: true,
          createAncestors: true,
        }
      );
    }
    return this._ensureStoreDirectoryPromise;
  }

  #getDataPromise(extension) {
    let dataPromise = this._dataPromises.get(extension.uuid);
    if (!dataPromise) {
      if (extension.hasShutdown) {
        throw new Error(
          `DNR store data loading aborted, the extension is already shutting down: ${extension.id}`
        );
      }

      this.unloadOnShutdown(extension);
      dataPromise = this.#readData(extension);
      this._dataPromises.set(extension.uuid, dataPromise);
    }
    return dataPromise;
  }

  /**
   * Reads the store file for the given extensions and all rules
   * for the enabled static ruleset ids listed in the store file.
   *
   * @typedef {string} ruleset_id
   *
   * @param {Extension} extension
   * @param {object} [options]
   * @param {Array<string>} [options.enabledRulesetIds]
   *        An optional array of enabled ruleset ids to be loaded
   *        (used to load a specific group of static rulesets,
   *        either when the list of static rules needs to be recreated based
   *        on the enabled rulesets, or when the extension is
   *        changing the enabled rulesets using the `updateEnabledRulesets`
   *        API method).
   * @param {boolean} [options.isUpdateEnabledRulesets]
   *        Whether this is a call by updateEnabledRulesets. When true,
   *        `enabledRulesetIds` contains the IDs of disabled rulesets that
   *        should be enabled. Already-enabled rulesets are not included in
   *        `enabledRulesetIds`.
   * @param {import("ExtensionDNR.sys.mjs").RuleQuotaCounter} [options.ruleQuotaCounter]
   *        The counter of already-enabled rules that are not part of
   *        `enabledRulesetIds`. Set when `isUpdateEnabledRulesets` is true.
   *        This method may mutate its internal counters.
   * @returns {Promise<Map<ruleset_id, EnabledStaticRuleset>>}
   *          map of the enabled static rulesets by ruleset_id.
   */
  async #getManifestStaticRulesets(
    extension,
    {
      enabledRulesetIds = null,
      isUpdateEnabledRulesets = false,
      ruleQuotaCounter,
    } = {}
  ) {
    // Map<ruleset_id, EnabledStaticRuleset>}
    const rulesets = new Map();

    const ruleResources =
      extension.manifest.declarative_net_request?.rule_resources;
    if (!Array.isArray(ruleResources)) {
      return rulesets;
    }

    if (!isUpdateEnabledRulesets) {
      ruleQuotaCounter = new lazy.ExtensionDNR.RuleQuotaCounter(
        "GUARANTEED_MINIMUM_STATIC_RULES"
      );
    }

    const {
      MAX_NUMBER_OF_ENABLED_STATIC_RULESETS,
      // Warnings on MAX_NUMBER_OF_STATIC_RULESETS are already
      // reported (see ExtensionDNR.validateManifestEntry, called
      // from the DNR API onManifestEntry callback).
    } = lazy.ExtensionDNRLimits;

    for (let [idx, { id, enabled, path }] of ruleResources.entries()) {
      // If passed enabledRulesetIds is used to determine if the enabled
      // rules in the manifest should be overridden from the list of
      // enabled static rulesets stored on disk.
      if (Array.isArray(enabledRulesetIds)) {
        enabled = enabledRulesetIds.includes(id);
      }

      // Duplicated ruleset ids are validated as part of the JSONSchema validation,
      // here we log a warning to signal that we are ignoring it if when the validation
      // error isn't strict (e.g. for non temporarily installed, which shouldn't normally
      // hit in the long run because we can also validate it before signing the extension).
      if (rulesets.has(id)) {
        Cu.reportError(
          `Disabled static ruleset with duplicated ruleset_id "${id}"`
        );
        continue;
      }

      if (enabled && rulesets.size >= MAX_NUMBER_OF_ENABLED_STATIC_RULESETS) {
        // This is technically reported from the manifest validation, as a warning
        // on extension installed non temporarily, and so checked and logged here
        // in case we are hitting it while loading the enabled rulesets.
        Cu.reportError(
          `Ignoring enabled static ruleset exceeding the MAX_NUMBER_OF_ENABLED_STATIC_RULESETS limit (${MAX_NUMBER_OF_ENABLED_STATIC_RULESETS}): ruleset_id "${id}" (extension: "${extension.id}")`
        );
        continue;
      }

      const readJSONStartTime = Cu.now();
      const rawRules =
        enabled &&
        (await fetch(path)
          .then(res => res.json())
          .catch(err => {
            Cu.reportError(err);
            enabled = false;
            extension.packagingError(
              `Reading declarative_net_request static rules file ${path}: ${err.message}`
            );
          }));
      ChromeUtils.addProfilerMarker(
        "ExtensionDNRStore",
        { startTime: readJSONStartTime },
        `StaticRulesetsReadJSON, addonId: ${extension.id}`
      );

      // Skip rulesets that are not enabled or can't be enabled (e.g. if we got error on loading or
      // parsing the rules JSON file).
      if (!enabled) {
        continue;
      }

      if (!Array.isArray(rawRules)) {
        extension.packagingError(
          `Reading declarative_net_request static rules file ${path}: rules file must contain an Array of rules`
        );
        continue;
      }

      // TODO(Bug 1803369): consider to only report the errors and warnings about invalid static rules for
      // temporarily installed extensions (chrome only shows them for unpacked extensions).
      const logRuleValidationError = err => extension.packagingWarning(err);

      const validatedRules = this.#getValidatedRules(extension, id, rawRules, {
        logRuleValidationError,
      });

      // NOTE: this is currently only accounting for valid rules because
      // only the valid rules will be actually be loaded. Reconsider if
      // we should instead also account for the rules that have been
      // ignored as invalid.
      try {
        ruleQuotaCounter.tryAddRules(id, validatedRules);
      } catch (e) {
        // If this is an API call (updateEnabledRulesets), just propagate the
        // error. Otherwise we are intializing the extension and should just
        // ignore the ruleset while reporting the error.
        if (isUpdateEnabledRulesets) {
          throw e;
        }
        // TODO(Bug 1803363): consider collect telemetry.
        Cu.reportError(
          `Ignoring static ruleset "${id}" in extension "${extension.id}" because: ${e.message}`
        );
        continue;
      }

      rulesets.set(id, { idx, rules: validatedRules });
    }

    return rulesets;
  }

  /**
   * Returns an array of validated and normalized Rule instances given an array
   * of raw rules data (e.g. in form of plain objects read from the static rules
   * JSON files or the dynamicRuleset property from the extension DNR store data).
   *
   * @typedef {import("ExtensionDNR.sys.mjs").Rule} Rule
   *
   * @param   {Extension}     extension
   * @param   {string}        rulesetId
   * @param   {Array<object>} rawRules
   * @param   {object}        options
   * @param   {Function}      [options.logRuleValidationError]
   *                          an optional callback to call for logging the
   *                          validation errors, defaults to use Cu.reportError
   *                          (but getManifestStaticRulesets overrides it to use
   *                          extensions.packagingWarning instead).
   *
   * @returns {Array<Rule>}
   */
  #getValidatedRules(
    extension,
    rulesetId,
    rawRules,
    { logRuleValidationError = err => Cu.reportError(err) } = {}
  ) {
    const startTime = Cu.now();
    const validatedRulesTimerId =
      Glean.extensionsApisDnr.validateRulesTime.start();
    try {
      const ruleValidator = new lazy.ExtensionDNR.RuleValidator([]);
      // Normalize rules read from JSON.
      const validationContext = {
        url: extension.baseURI.spec,
        principal: extension.principal,
        logError: logRuleValidationError,
        preprocessors: {},
        manifestVersion: extension.manifestVersion,
        ignoreUnrecognizedProperties: true,
      };

      // TODO(Bug 1803369): consider to also include the rule id if one was available.
      const getInvalidRuleMessage = (ruleIndex, msg) =>
        `Invalid rule at index ${ruleIndex} from ruleset "${rulesetId}", ${msg}`;

      for (const [rawIndex, rawRule] of rawRules.entries()) {
        try {
          const normalizedRule = lazy.Schemas.normalize(
            rawRule,
            "declarativeNetRequest.Rule",
            validationContext
          );
          if (normalizedRule.value) {
            ruleValidator.addRules([normalizedRule.value]);
          } else {
            logRuleValidationError(
              getInvalidRuleMessage(
                rawIndex,
                normalizedRule.error ?? "Unexpected undefined rule"
              )
            );
          }
        } catch (err) {
          Cu.reportError(err);
          logRuleValidationError(
            getInvalidRuleMessage(rawIndex, "An unexpected error occurred")
          );
        }
      }

      // TODO(Bug 1803369): consider including an index in the invalid rules warnings.
      if (ruleValidator.getFailures().length) {
        logRuleValidationError(
          `Invalid rules found in ruleset "${rulesetId}": ${ruleValidator
            .getFailures()
            .map(f => f.message)
            .join(", ")}`
        );
      }

      return ruleValidator.getValidatedRules();
    } finally {
      ChromeUtils.addProfilerMarker(
        "ExtensionDNRStore",
        { startTime },
        `#getValidatedRules, addonId: ${extension.id}`
      );
      Glean.extensionsApisDnr.validateRulesTime.stopAndAccumulate(
        validatedRulesTimerId
      );
    }
  }

  #getExistingStaticRulesetIds(extension) {
    const ruleResources =
      extension.manifest.declarative_net_request?.rule_resources;
    if (!Array.isArray(ruleResources)) {
      return [];
    }

    return ruleResources.map(rs => rs.id);
  }

  #hasInstallOrUpdateStartupReason(extension) {
    switch (extension.startupReason) {
      case "ADDON_INSTALL":
      case "ADDON_UPGRADE":
      case "ADDON_DOWNGRADE":
        return true;
    }

    return false;
  }

  /**
   * Load and add the DNR stored rules to the RuleManager instance for the given
   * extension.
   *
   * @param {Extension} extension
   * @returns {Promise<void>}
   */
  async #initExtension(extension) {
    // - on new installs the stored rules should be recreated from scratch
    //   (and any stale previously stored data to be ignored)
    // - on upgrades/downgrades:
    //   - the dynamic rules are expected to be preserved
    //   - the static rules are expected to be refreshed from the new
    //     manifest data (also the enabled rulesets are expected to be
    //     reset to the state described in the manifest)
    //
    // TODO(Bug 1803369): consider also setting to true if the extension is installed temporarily.
    if (this.#hasInstallOrUpdateStartupReason(extension)) {
      // Reset the stored static rules on addon updates.
      await StartupCache.delete(extension, ["dnr", "hasEnabledStaticRules"]);
    }

    const hasEnabledStaticRules = await StartupCache.get(
      extension,
      ["dnr", "hasEnabledStaticRules"],
      async () => {
        const staticRulesets = await this.getEnabledStaticRulesets(extension);

        return staticRulesets.size;
      }
    );
    const hasDynamicRules = await StartupCache.get(
      extension,
      ["dnr", "hasDynamicRules"],
      async () => {
        const dynamicRuleset = await this.getDynamicRules(extension);

        return dynamicRuleset.length;
      }
    );

    if (hasEnabledStaticRules || hasDynamicRules) {
      const data = await this.#getDataPromise(extension);
      if (!data.isFromStartupCache() && !data.isFromTemporarilyInstalled()) {
        this.scheduleCacheDataSave();
      }
      if (extension.hasShutdown) {
        return;
      }
      this.updateRulesetManager(extension, {
        updateStaticRulesets: hasEnabledStaticRules,
        updateDynamicRuleset: hasDynamicRules,
      });
    }
  }

  #promiseStartupCacheLoaded() {
    if (!this._ensureCacheLoaded) {
      if (this._data.size) {
        return Promise.reject(
          new Error(
            "Unexpected non-empty DNRStore data. DNR startupCache data load aborted."
          )
        );
      }

      const startTime = Cu.now();
      const timerId = Glean.extensionsApisDnr.startupCacheReadTime.start();
      this._ensureCacheLoaded = (async () => {
        const cacheFilePath = this.#getCacheFilePath();
        const { buffer, byteLength } = await IOUtils.read(cacheFilePath);
        Glean.extensionsApisDnr.startupCacheReadSize.accumulate(byteLength);
        const decodedData = lazy.aomStartup.decodeBlob(buffer);
        const emptyOrCorruptedCache = !(decodedData?.cacheData instanceof Map);
        if (emptyOrCorruptedCache) {
          Cu.reportError(
            `Unexpected corrupted DNRStore startupCache data. DNR startupCache data load dropped.`
          );
          // Remove the cache file right away on corrupted (unexpected empty)
          // or obsolete cache content.
          await IOUtils.remove(cacheFilePath, { ignoreAbsent: true });
          return;
        }
        if (this._data.size) {
          Cu.reportError(
            `Unexpected non-empty DNRStore data. DNR startupCache data load dropped.`
          );
          return;
        }
        for (const [
          extUUID,
          cacheStoreData,
        ] of decodedData.cacheData.entries()) {
          if (StoreData.isStaleCacheEntry(extUUID, cacheStoreData)) {
            StoreData.clearLastUpdateTagPref(extUUID);
            continue;
          }
          // TODO(Bug 1825510): schedule a task long enough after startup to detect and
          // remove unused entries in the _startupCacheData Map sooner.
          this._startupCacheData.set(extUUID, {
            extUUID: extUUID,
            ...cacheStoreData,
          });
        }
      })()
        .catch(err => {
          // TODO: collect telemetry on unexpected cache load failures.
          if (!DOMException.isInstance(err) || err.name !== "NotFoundError") {
            Cu.reportError(err);
          }
        })
        .finally(() => {
          ChromeUtils.addProfilerMarker(
            "ExtensionDNRStore",
            { startTime },
            "_ensureCacheLoaded"
          );
          Glean.extensionsApisDnr.startupCacheReadTime.stopAndAccumulate(
            timerId
          );
        });
    }

    return this._ensureCacheLoaded;
  }

  /**
   * Read the stored data for the given extension, either from:
   * - store file (if available and not detected as a data schema downgrade)
   * - manifest file and packaged ruleset JSON files (if there was no valid stored data found)
   *
   * This private method is only called from #getDataPromise, which caches the return value
   * in memory.
   *
   * @param {Extension} extension
   *
   * @returns {Promise<StoreData>}
   */
  async #readData(extension) {
    const startTime = Cu.now();
    try {
      let result;
      // Try to load data from the startupCache.
      if (extension.startupReason === "APP_STARTUP") {
        result = await this.#readStoreDataFromStartupCache(extension);
      }
      // Fallback to load the data stored in the json file.
      result ??= await this.#readStoreData(extension);

      // Reset the stored data if a data schema version downgrade has been
      // detected (this should only be hit on downgrades if the user have
      // also explicitly passed --allow-downgrade CLI option).
      if (result && result.schemaVersion > StoreData.VERSION) {
        Cu.reportError(
          `Unsupport DNR store schema version downgrade: resetting stored data for ${extension.id}`
        );
        result = null;
      }

      // If the number of disabled rules exceeds the limit when loaded from the store
      // (e.g. if the limit has been customized through prefs, and so not expected to
      // be a common case), then we drop the entire list of disabled rules.
      if (result?.disabledStaticRuleIds) {
        for (const [rulesetId, disabledRuleIds] of Object.entries(
          result.disabledStaticRuleIds
        )) {
          if (
            Array.isArray(disabledRuleIds) &&
            disabledRuleIds.length <=
              ExtensionDNRLimits.MAX_NUMBER_OF_DISABLED_STATIC_RULES
          ) {
            continue;
          }

          Cu.reportError(
            `Discard "${extension.id}" static ruleset "${rulesetId}" disabled rules` +
              ` for exceeding the MAX_NUMBER_OF_DISABLED_STATIC_RULES (${ExtensionDNRLimits.MAX_NUMBER_OF_DISABLED_STATIC_RULES})`
          );
          result.disabledStaticRuleIds[rulesetId] = [];
        }
      }

      // Use defaults and extension manifest if no data stored was found
      // (or it got reset due to an unsupported profile downgrade being detected).
      if (!result) {
        // We don't have any data stored, load the static rules from the manifest.
        result = this.#getDefaults(extension);
        // Initialize the staticRules data from the manifest.
        result.updateRulesets({
          staticRulesets: await this.#getManifestStaticRulesets(extension),
        });
      }

      // The extension has already shutting down and we may already got past
      // the unloadData cleanup (given that there is still a promise in
      // the _dataPromises Map).
      if (extension.hasShutdown && !this._dataPromises.has(extension.uuid)) {
        throw new Error(
          `DNR store data loading aborted, the extension is already shutting down: ${extension.id}`
        );
      }

      this._data.set(extension.uuid, result);

      return result;
    } finally {
      ChromeUtils.addProfilerMarker(
        "ExtensionDNRStore",
        { startTime },
        `readData, addonId: ${extension.id}`
      );
    }
  }

  // Convert extension entries in the startCache map back to StoreData instances
  // (because the StoreData instances get converted into plain objects when
  // serialized into the startupCache structured clone blobs).
  async #readStoreDataFromStartupCache(extension) {
    await this.#promiseStartupCacheLoaded();

    if (!this._startupCacheData.has(extension.uuid)) {
      Glean.extensionsApisDnr.startupCacheEntries.miss.add(1);
      return;
    }

    const extCacheData = this._startupCacheData.get(extension.uuid);
    this._startupCacheData.delete(extension.uuid);

    if (extCacheData.extVersion != extension.version) {
      StoreData.clearLastUpdateTagPref(extension.uuid);
      Glean.extensionsApisDnr.startupCacheEntries.miss.add(1);
      return;
    }

    Glean.extensionsApisDnr.startupCacheEntries.hit.add(1);
    for (const ruleset of extCacheData.staticRulesets.values()) {
      ruleset.rules = ruleset.rules.map(rule =>
        lazy.ExtensionDNR.RuleValidator.deserializeRule(rule)
      );
    }
    extCacheData.dynamicRuleset = extCacheData.dynamicRuleset.map(rule =>
      lazy.ExtensionDNR.RuleValidator.deserializeRule(rule)
    );
    return new StoreData(extension, extCacheData);
  }

  /**
   * Reads the store file for the given extensions and all rules
   * for the enabled static ruleset ids listed in the store file.
   *
   * @param {Extension} extension
   *
   * @returns {Promise<StoreData|null>}
   */
  async #readStoreData(extension) {
    // TODO(Bug 1803363): record into Glean telemetry DNR RulesetsStore store load time.
    let file = this.#getStoreFilePath(extension.uuid);
    let data;
    let isCorrupted = false;
    let storeFileFound = false;
    try {
      data = await IOUtils.readJSON(file, { decompress: true });
      storeFileFound = true;
    } catch (e) {
      if (!(DOMException.isInstance(e) && e.name === "NotFoundError")) {
        Cu.reportError(e);
        isCorrupted = true;
        storeFileFound = true;
      }
      // TODO(Bug 1803363) record store read errors in telemetry scalar.
    }

    // Reset data read from disk if its type isn't the expected one.
    isCorrupted ||=
      !data ||
      !Array.isArray(data.staticRulesets) ||
      // DNR data stored in 109 would not have any dynamicRuleset
      // property and so don't consider the data corrupted if
      // there isn't any dynamicRuleset property at all.
      ("dynamicRuleset" in data && !Array.isArray(data.dynamicRuleset));

    if (isCorrupted && storeFileFound) {
      // Wipe the corrupted data and backup the corrupted file.
      data = null;
      try {
        let uniquePath = await IOUtils.createUniqueFile(
          PathUtils.parent(file),
          PathUtils.filename(file) + ".corrupt",
          0o600
        );
        Cu.reportError(
          `Detected corrupted DNR store data for ${extension.id}, renaming store data file to ${uniquePath}`
        );
        await IOUtils.move(file, uniquePath);
      } catch (err) {
        Cu.reportError(err);
      }
    }

    if (!data) {
      return null;
    }

    const resetStaticRulesets =
      // Reset the static rulesets on install or updating the extension.
      //
      // NOTE: this method is called only once and its return value cached in
      // memory for the entire lifetime of the extension and so we don't need
      // to store any flag to avoid resetting the static rulesets more than
      // once for the same Extension instance.
      this.#hasInstallOrUpdateStartupReason(extension) ||
      // Ignore the stored enabled ruleset ids if the current extension version
      // mismatches the version the store data was generated from.
      data.extVersion !== extension.version;

    if (resetStaticRulesets) {
      data.staticRulesets = undefined;
      data.disabledStaticRuleIds = {};
      data.extVersion = extension.version;
    }

    // If the data is being loaded for a new addon install, make sure to clear
    // any potential stale dynamic rules stored on disk.
    //
    // NOTE: this is expected to only be hit if there was a failure to cleanup
    // state data upon uninstall (e.g. in case the machine shutdowns or
    // Firefox crashes before we got to update the data stored on disk).
    if (extension.startupReason === "ADDON_INSTALL") {
      data.dynamicRuleset = [];
    }

    // In the JSON stored data we only store the enabled rulestore_id and
    // the actual rules have to be loaded.
    data.staticRulesets = await this.#getManifestStaticRulesets(
      extension,
      // Only load the rules from rulesets that are enabled in the stored DNR data,
      // if the array (eventually empty) of the enabled static rules isn't in the
      // stored data, then load all the ones enabled in the manifest.
      { enabledRulesetIds: data.staticRulesets }
    );

    if (data.dynamicRuleset?.length) {
      // Make sure all dynamic rules loaded from disk as validated and normalized
      // (in case they may have been tempered, but also for when we are loading
      // data stored by a different Firefox version from the one that stored the
      // data on disk, e.g. in case validation or normalization logic may have been
      // different in the two Firefox version).
      const validatedDynamicRules = this.#getValidatedRules(
        extension,
        "_dynamic" /* rulesetId */,
        data.dynamicRuleset
      );

      let ruleQuotaCounter = new lazy.ExtensionDNR.RuleQuotaCounter(
        "MAX_NUMBER_OF_DYNAMIC_RULES"
      );
      try {
        ruleQuotaCounter.tryAddRules("_dynamic", validatedDynamicRules);
        data.dynamicRuleset = validatedDynamicRules;
      } catch (e) {
        // This should not happen in practice, because updateDynamicRules
        // rejects quota errors. If we get here, the data on disk may have been
        // tampered with, or the limit was lowered in a browser update.
        Cu.reportError(
          `Ignoring dynamic ruleset in extension "${extension.id}" because: ${e.message}`
        );
        data.dynamicRuleset = [];
      }
    }
    // We use StoreData.fromJSON here to prevent properties that are not expected to
    // be stored in the JSON file from overriding other StoreData constructor properties
    // that are not included in the JSON data returned by StoreData toJSON.
    return StoreData.fromJSON(data, extension);
  }

  async scheduleCacheDataSave() {
    this.#ensureCacheDirectory();
    if (!this._saveCacheTask) {
      this._saveCacheTask = new lazy.DeferredTask(
        () => this.#saveCacheDataNow(),
        5000
      );
      IOUtils.profileBeforeChange.addBlocker(
        "Flush WebExtensions DNR RulesetsStore startupCache",
        async () => {
          await this._saveCacheTask.finalize();
          this._saveCacheTask = null;
        }
      );
    }

    return this._saveCacheTask.arm();
  }

  getStartupCacheData() {
    const filteredData = new Map();
    const seenLastUpdateTags = new Set();
    for (const [extUUID, dataEntry] of this._data) {
      // Only store in the startup cache extensions that are permanently
      // installed (the temporarilyInstalled extension are removed
      // automatically either on shutdown or startup, and so the data
      // stored and then loaded back from the startup cache file
      // would never be used).
      if (dataEntry.isFromTemporarilyInstalled()) {
        continue;
      }
      filteredData.set(extUUID, dataEntry);
      seenLastUpdateTags.add(dataEntry.lastUpdateTag);
    }
    return {
      seenLastUpdateTags,
      filteredData,
    };
  }

  detectStartupCacheDataChanged(seenLastUpdateTags) {
    // Detect if there are changes to the stored data applied while we
    // have been writing the cache data on disk, and reschedule a new
    // cache data save if that is the case.
    // TODO(Bug 1825510): detect also obsoleted entries to make sure
    // they are removed from the startup cache data stored on disk
    // sooner.
    for (const dataEntry of this._data.values()) {
      if (dataEntry.isFromTemporarilyInstalled()) {
        continue;
      }
      if (!seenLastUpdateTags.has(dataEntry.lastUpdateTag)) {
        return true;
      }
    }
    return false;
  }

  async #saveCacheDataNow() {
    const startTime = Cu.now();
    const timerId = Glean.extensionsApisDnr.startupCacheWriteTime.start();
    try {
      const cacheFilePath = this.#getCacheFilePath();
      const { filteredData, seenLastUpdateTags } = this.getStartupCacheData();
      const data = new Uint8Array(
        lazy.aomStartup.encodeBlob({
          cacheData: filteredData,
        })
      );
      await this._ensureCacheDirectoryPromise;
      await IOUtils.write(cacheFilePath, data, {
        tmpPath: `${cacheFilePath}.tmp`,
      });
      Glean.extensionsApisDnr.startupCacheWriteSize.accumulate(data.byteLength);

      if (this.detectStartupCacheDataChanged(seenLastUpdateTags)) {
        this.scheduleCacheDataSave();
      }
    } finally {
      ChromeUtils.addProfilerMarker(
        "ExtensionDNRStore",
        { startTime },
        "#saveCacheDataNow"
      );
      Glean.extensionsApisDnr.startupCacheWriteTime.stopAndAccumulate(timerId);
    }
  }

  /**
   * Save the data for the given extension on disk.
   *
   * @param {string} extensionUUID
   * @param {string} extensionId
   * @returns {Promise<void>}
   */
  async #saveNow(extensionUUID, extensionId) {
    const startTime = Cu.now();
    try {
      if (
        !this._dataPromises.has(extensionUUID) ||
        !this._data.has(extensionUUID)
      ) {
        throw new Error(
          `Unexpected uninitialized DNR store on saving data for extension uuid "${extensionUUID}"`
        );
      }
      const storeFile = this.#getStoreFilePath(extensionUUID);
      const data = this._data.get(extensionUUID);
      await this.#ensureStoreDirectory(extensionUUID);
      await IOUtils.writeJSON(storeFile, data, {
        tmpPath: `${storeFile}.tmp`,
        compress: true,
      });

      this.scheduleCacheDataSave();

      // TODO(Bug 1803363): report jsonData lengths into a telemetry scalar.
      // TODO(Bug 1803363): report jsonData time to write into a telemetry scalar.
    } catch (err) {
      Cu.reportError(err);
      throw err;
    } finally {
      this._savePromises.delete(extensionUUID);
      ChromeUtils.addProfilerMarker(
        "ExtensionDNRStore",
        { startTime },
        `#saveNow, addonId: ${extensionId}`
      );
    }
  }

  /**
   * Unload data for the given extension UUID from memory (e.g. when the extension is disabled or uninstalled),
   * waits for a pending save promise to be settled if any.
   *
   * NOTE: this method clear the data cached in memory and close the update queue
   * and so it should only be called from the extension shutdown handler and
   * by the initExtension method before pushing into the update queue for the
   * for the extension the initExtension task.
   *
   * @param {string} extensionUUID
   * @returns {Promise<void>}
   */
  async #unloadData(extensionUUID) {
    // Wait for the update tasks to have been executed, then
    // wait for the data to have been saved and finally unload
    // the data cached in memory.
    const dataUpdateQueue = this._dataUpdateQueues.has(extensionUUID)
      ? this._dataUpdateQueues.get(extensionUUID)
      : undefined;

    if (dataUpdateQueue) {
      try {
        await dataUpdateQueue.close();
      } catch (err) {
        // Unexpected error on closing the update queue.
        Cu.reportError(err);
      }
      this._dataUpdateQueues.delete(extensionUUID);
    }

    const savePromise = this._savePromises.get(extensionUUID);
    if (savePromise) {
      await savePromise;
      this._savePromises.delete(extensionUUID);
    }

    this._dataPromises.delete(extensionUUID);
    this._data.delete(extensionUUID);
  }

  /**
   * Internal implementation for updating the dynamic ruleset and enforcing
   * dynamic rules count limits.
   *
   * Callers ensure that there is never a concurrent call of #updateDynamicRules
   * for a given extension, so we can safely modify ruleManager.dynamicRules
   * from inside this method, even asynchronously.
   *
   * @param {Extension}     extension
   * @param {object}        params
   * @param {Array<number>} [params.removeRuleIds=[]]
   * @param {Array<Rule>}   [params.addRules=[]]
   */
  async #updateDynamicRules(extension, { removeRuleIds, addRules }) {
    const ruleManager = lazy.ExtensionDNR.getRuleManager(extension);
    const ruleValidator = new lazy.ExtensionDNR.RuleValidator(
      ruleManager.getDynamicRules()
    );
    if (removeRuleIds) {
      ruleValidator.removeRuleIds(removeRuleIds);
    }
    if (addRules) {
      ruleValidator.addRules(addRules);
    }
    let failures = ruleValidator.getFailures();
    if (failures.length) {
      throw new ExtensionError(failures[0].message);
    }

    const validatedRules = ruleValidator.getValidatedRules();
    let ruleQuotaCounter = new lazy.ExtensionDNR.RuleQuotaCounter(
      "MAX_NUMBER_OF_DYNAMIC_RULES"
    );
    ruleQuotaCounter.tryAddRules("_dynamic", validatedRules);

    this._data.get(extension.uuid).updateRulesets({
      dynamicRuleset: validatedRules,
    });
    await this.save(extension);
    // updateRulesetManager calls ruleManager.setDynamicRules using the
    // validated rules assigned above to this._data.
    this.updateRulesetManager(extension, {
      updateDynamicRuleset: true,
      updateStaticRulesets: false,
    });
  }

  async #updateStaticRules(
    extension,
    { rulesetId, disableRuleIds, enableRuleIds }
  ) {
    const existingRulesetIds = this.#getExistingStaticRulesetIds(extension);
    if (!existingRulesetIds.includes(rulesetId)) {
      throw new ExtensionError(`Invalid ruleset id: "${rulesetId}"`);
    }

    const data = this._data.get(extension.uuid);
    const disabledRuleIdsSet = new Set(data.disabledStaticRuleIds[rulesetId]);
    const enableSet = new Set(enableRuleIds);
    const disableSet = new Set(disableRuleIds);

    let changed = false;
    for (const ruleId of disableSet) {
      // Skip rule ids that are disabled and enabled in the same call.
      if (enableSet.delete(ruleId)) {
        continue;
      }
      if (!disabledRuleIdsSet.has(ruleId)) {
        changed = true;
      }
      disabledRuleIdsSet.add(ruleId);
    }
    for (const ruleId of enableSet) {
      if (disabledRuleIdsSet.delete(ruleId)) {
        changed = true;
      }
    }

    if (!changed) {
      return;
    }

    if (
      disabledRuleIdsSet.size >
      ExtensionDNRLimits.MAX_NUMBER_OF_DISABLED_STATIC_RULES
    ) {
      throw new ExtensionError(
        `Number of individually disabled static rules exceeds MAX_NUMBER_OF_DISABLED_STATIC_RULES limit`
      );
    }

    // Chrome doesn't seem to validate if the rule id actually exists in the ruleset,
    // and so set the resulting updated array of disabled rule ids right away.
    //
    // For more details, see the "Invalid rules" and "Error handling in updateStaticRules"
    // section of https://github.com/w3c/webextensions/issues/162#issuecomment-2101003746
    data.disabledStaticRuleIds[rulesetId] = Array.from(disabledRuleIdsSet);

    await this.save(extension);

    // If the ruleset isn't currently enabled, after saving the updated
    // disabledRuleIdsSet we are done.
    if (!data.staticRulesets.has(rulesetId)) {
      return;
    }
    //
    // updateRulesetManager calls ruleManager.setStaticRules to
    // update the list of disabled ruleIds.
    this.updateRulesetManager(extension, {
      updateDynamicRuleset: false,
      updateStaticRulesets: true,
    });
  }

  /**
   * Internal implementation for updating the enabled rulesets and enforcing
   * static rulesets and rules count limits.
   *
   * @param {Extension}     extension
   * @param {object}        params
   * @param {Array<string>} [params.disableRulesetIds=[]]
   * @param {Array<string>} [params.enableRulesetIds=[]]
   */
  async #updateEnabledStaticRulesets(
    extension,
    { disableRulesetIds, enableRulesetIds }
  ) {
    const existingIds = new Set(this.#getExistingStaticRulesetIds(extension));
    if (!existingIds.size) {
      return;
    }

    const enabledRulesets = await this.getEnabledStaticRulesets(extension);
    const updatedEnabledRulesets = new Map();
    let disableIds = new Set(disableRulesetIds);
    let enableIds = new Set(enableRulesetIds);

    // valiate the ruleset ids for existence (which will also reject calls
    // including the reserved _session and _dynamic, because static rulesets
    // id are validated as part of the manifest validation and they are not
    // allowed to start with '_').
    const errorOnInvalidRulesetIds = rsIdSet => {
      for (const rsId of rsIdSet) {
        if (!existingIds.has(rsId)) {
          throw new ExtensionError(`Invalid ruleset id: "${rsId}"`);
        }
      }
    };
    errorOnInvalidRulesetIds(disableIds);
    errorOnInvalidRulesetIds(enableIds);

    // Copy into the updatedEnabledRulesets Map any ruleset that is not
    // requested to be disabled or is enabled back in the same request.
    for (const [rulesetId, ruleset] of enabledRulesets) {
      if (!disableIds.has(rulesetId) || enableIds.has(rulesetId)) {
        updatedEnabledRulesets.set(rulesetId, ruleset);
        enableIds.delete(rulesetId);
      }
    }

    const { MAX_NUMBER_OF_ENABLED_STATIC_RULESETS } = lazy.ExtensionDNRLimits;

    const maxNewRulesetsCount =
      MAX_NUMBER_OF_ENABLED_STATIC_RULESETS - updatedEnabledRulesets.size;

    if (enableIds.size > maxNewRulesetsCount) {
      // Log an error for the developer.
      throw new ExtensionError(
        `updatedEnabledRulesets request is exceeding MAX_NUMBER_OF_ENABLED_STATIC_RULESETS`
      );
    }

    // At this point, every item in |updatedEnabledRulesets| is an enabled
    // ruleset with already-valid rules. In order to not exceed the rule quota
    // when previously-disabled rulesets are enabled, we need to count what we
    // already have.
    let ruleQuotaCounter = new lazy.ExtensionDNR.RuleQuotaCounter(
      "GUARANTEED_MINIMUM_STATIC_RULES"
    );
    for (let [rulesetId, ruleset] of updatedEnabledRulesets) {
      ruleQuotaCounter.tryAddRules(rulesetId, ruleset.rules);
    }

    const newRulesets = await this.#getManifestStaticRulesets(extension, {
      enabledRulesetIds: Array.from(enableIds),
      ruleQuotaCounter,
      isUpdateEnabledRulesets: true,
    });

    for (const [rulesetId, ruleset] of newRulesets.entries()) {
      updatedEnabledRulesets.set(rulesetId, ruleset);
    }

    this._data.get(extension.uuid).updateRulesets({
      staticRulesets: updatedEnabledRulesets,
    });
    await this.save(extension);
    this.updateRulesetManager(extension, {
      updateDynamicRuleset: false,
      updateStaticRulesets: true,
    });
  }
}

let store = new RulesetsStore();

export const ExtensionDNRStore = {
  SCHEMA_VERSION: StoreData.VERSION,
  async clearOnUninstall(extensionUUID) {
    return store.clearOnUninstall(extensionUUID);
  },
  async initExtension(extension) {
    await store.initExtension(extension);
  },
  async updateDynamicRules(extension, updateRuleOptions) {
    await store.updateDynamicRules(extension, updateRuleOptions);
  },
  async updateEnabledStaticRulesets(extension, updateRulesetOptions) {
    await store.updateEnabledStaticRulesets(extension, updateRulesetOptions);
  },
  async updateStaticRules(extension, updateStaticRulesOptions) {
    await store.updateStaticRules(extension, updateStaticRulesOptions);
  },
  getDisabledRuleIds(extension, rulesetId) {
    return store.getDisabledRuleIds(extension, rulesetId);
  },
  // Test-only helpers
  _getLastUpdateTag(extensionUUID) {
    requireTestOnlyCallers();
    return StoreData.getLastUpdateTag(extensionUUID);
  },
  _getStoreForTesting() {
    requireTestOnlyCallers();
    return store;
  },
  _getStoreDataClassForTesting() {
    requireTestOnlyCallers();
    return StoreData;
  },
  _recreateStoreForTesting() {
    requireTestOnlyCallers();
    store = new RulesetsStore();
    return store;
  },
  _storeLastUpdateTag(extensionUUID, lastUpdateTag) {
    requireTestOnlyCallers();
    return StoreData.storeLastUpdateTag(extensionUUID, lastUpdateTag);
  },
};
PK
!<z���L�Lmodules/ExtensionMenus.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
  KeyValueService: "resource://gre/modules/kvstore.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "MENU_STORE_WRITE_DEBOUNCE_TIME",
  "extensions.webextensions.menus.writeDebounceTime",
  // TODO: agree on the default value to set on this pref.
  5000, // Default to 5s
  null,
  // Minimum 0ms, max 1min
  value => Math.min(Math.max(value, 0), 1 * 60 * 1000)
);

const SCHEMA_VERSION = 1;
const KVSTORE_DIRNAME = "extension-store-menus";

/**
 * MenuId represent the type of the ids associated to the extension
 * created menus, which is expected to be of type:
 *
 * - string
 * - or an auto-generated integer (for menus created without a pre-assigned menu id,
 *   only allowed for extensions with a persistent background script or without any background
 *   context at all).
 *
 * Only menus registered by extensions with a non-persistent backgrond context are going
 * to be persisted across sessions, and their id is always a string.
 *
 * @typedef {number} integer
 * @typedef {string|integer} MenuId
 *

/**
 * This class manages the extensions menus stored on disk across
 * all extensions (with kvstore as the underlying backend).
 */
class ExtensionMenusStore {
  #store = null;
  #initPromise = null;

  /**
   * Determine if the menus created by the given extension should
   * be persisted on disk.
   *
   * @param {Extension} extension
   *
   * @returns {boolean} Returns true if the menus should be persisted on disk.
   */
  static shouldPersistMenus(extension) {
    return extension.manifest.background && !extension.persistentBackground;
  }

  get storePath() {
    const { path: storePath } = lazy.FileUtils.getDir("ProfD", [
      KVSTORE_DIRNAME,
    ]);
    return storePath;
  }

  async #asyncInit() {
    const { storePath } = this;
    await IOUtils.makeDirectory(storePath, { ignoreExisting: true });
    this.#store = await lazy.KeyValueService.getOrCreateWithOptions(
      storePath,
      "menus",
      { strategy: lazy.KeyValueService.RecoveryStrategy.RENAME }
    );
  }

  #lazyInit() {
    if (!this.#initPromise) {
      this.#initPromise = this.#asyncInit();
    }

    return this.#initPromise;
  }

  #notifyPersistedMenusUpdatedForTesting(extensionId) {
    Services.obs.notifyObservers(
      null,
      "webext-persisted-menus-updated",
      extensionId
    );
  }

  /**
   * An helper method to check if the store includes data for the given extension (ID).
   *
   * @param {string} extensionId An extension ID
   * @returns {Promise<boolean>} true if the store includes data for the given
   * extension, false otherwise.
   */
  async hasExtensionData(extensionId) {
    await this.#lazyInit();
    return this.#store.has(extensionId);
  }

  /**
   * Returns all the persisted menus for a given extension (ID), or an empty map
   * if there isn't any data for the given extension id and extension version.
   *
   * @param {string} extensionId An extension ID
   * @param {string} currentExtensionVersion The current extension version
   * @returns {Promise<Map>} A map of persisted menu details.
   */
  async getPersistedMenus(extensionId, currentExtensionVersion) {
    await this.#lazyInit();

    let value;
    try {
      value = await this.#store.get(extensionId);
    } catch (err) {
      Cu.reportError(
        `Error on retrieving stored menus for ${extensionId}: ${err}\n`
      );
    }

    if (!value) {
      return new Map();
    }

    const { menuSchemaVersion, extensionVersion, menus } = JSON.parse(value);

    // Drop the stored data if the extension version is not matching the
    // current extension version.
    if (extensionVersion != currentExtensionVersion) {
      return new Map();
    }

    // NOTE: future version may use the following block to convert stored menus
    // data.
    if (menuSchemaVersion !== SCHEMA_VERSION) {
      Cu.reportError(
        `Dropping stored menus for ${extensionId} due to unxpected menuSchemaVersion ${menuSchemaVersion} (expected ${SCHEMA_VERSION})`
      );
      // TODO: should we consider firing onInstalled if we had to drop stored
      // menus due to a schema mismatch? should we do the same in case of
      // corrupted storage?
      return new Map();
    }

    return new Map(menus);
  }

  /**
   * Updates the map of persisted menus for a given extension (ID).
   *
   * We store each menu registered by extensions as an array of
   * key/value pairs (derived from the Map of MenuCreateProperties).
   *
   * The format on disk should look like this one:
   *
   * ```
   * {
   *   "@extension-id1": { menuSchemaVersion: N, menus: [["menuid-1", <MenuCreateProperties>], ...] },
   *   "@extension-id2": { menuSchemaVersion: N, menus: [["menuid-2", <MenuCreateProperties>], ...] },
   * }
   * ```
   *
   * @param {string} extensionId An extension ID
   * @returns {Promise<void>}
   */
  async updatePersistedMenus(extensionId, extensionVersion, menusMap) {
    await this.#lazyInit();
    await this.#store.put(
      extensionId,
      JSON.stringify({
        menuSchemaVersion: SCHEMA_VERSION,
        extensionVersion: extensionVersion,
        menus: Array.from(menusMap.entries()),
      })
    );
    this.#notifyPersistedMenusUpdatedForTesting(extensionId);
  }

  /**
   * Clears all the menus persisted on disk for a given extension (ID)
   * being uninstalled.
   *
   * @param {string} extensionId An extension ID
   */
  async clearPersistedMenusOnUninstall(extensionId) {
    const { storePath } = this;
    const kvstoreDirExists = await IOUtils.exists(storePath);
    if (!kvstoreDirExists) {
      // Avoid to create an unnecessary kvstore directory (through the call
      // to lazyInit). If one doesn't already, then there isn't any data
      // to clear.
      return;
    }

    await this.#lazyInit();
    await this.#store.delete(extensionId);
    this.#notifyPersistedMenusUpdatedForTesting(extensionId);
  }
}

let store = new ExtensionMenusStore();

/**
 * This class manages the extensions menus for a specific extension.
 *
 * For extensions with a persistent background extension context
 * or without any background extension the menus are kept only in memory,
 * whereas for extensions with a non persistent background context
 * the menus are also persisted on disk.
 */
class ExtensionMenusManager {
  #writeToStoreTask = null;
  #shutdownBlockerFn = null;

  constructor(extension) {
    if (extension.hasShutdown) {
      throw new Error(
        `Error on creating new ExtensionMenusManager after extension shutdown: ${extension.id}`
      );
    }
    this.extensionId = extension.id;
    this.extensionVersion = extension.version;
    this.persistMenusData = ExtensionMenusStore.shouldPersistMenus(extension);
    // Map[MenuId -> MenuCreateProperties]
    this.menus = null;
    if (this.persistMenusData) {
      extension.callOnClose(this);
    }
  }

  async _finalizeStoreTaskForTesting() {
    if (this.#writeToStoreTask && !this.#writeToStoreTask.isFinalized) {
      await this.#writeToStoreTask;
    }
  }

  close() {
    if (!this.#shutdownBlockerFn) {
      return;
    }

    const shutdownBlockerFn = this.#shutdownBlockerFn;
    shutdownBlockerFn().then(() => {
      lazy.AsyncShutdown.profileBeforeChange.removeBlocker(shutdownBlockerFn);
    });
  }

  async asyncInit() {
    if (this.menus) {
      // ExtensionMenusManager is expected to be called only once from
      // ExtensionMenus.asyncInitForExtension, which is expected to be
      // called only once per extension (from the ext-menus onStartup
      // lifecycle method).
      Cu.reportError(
        `ExtensionMenusManager for ${this.extensionId} should not be initialized more than once`
      );

      return;
    }

    if (!this.persistMenusData) {
      this.menus = new Map();
    }

    this.menus = await store
      .getPersistedMenus(this.extensionId, this.extensionVersion)
      .catch(err => {
        Cu.reportError(
          `Error loading ${this.extensionId} persisted menus: ${err}`
        );
        return new Map();
      });
  }

  #ensureInitialized() {
    // ExtensionMenusStore instance for each extension using the menus API
    // is expected to be done from the menus API onStartup lifecycle method.
    if (!this.menus) {
      throw new Error(
        `ExtensionMenusStore instance for ${this.extensionId} is not initialized`
      );
    }
  }

  /**
   * Synchronously retrieve the map of the extension menus.
   *
   * For extensions that should persist menus across sessions the map is
   * initialized from the data stored on disk and so this method is expected
   * to only be called after ext-menus onStartup lifecycle method have
   * called asyncInit on the instance of this class.
   *
   * @returns {Map<MenuId, object>} Map of the menus createProperties keyed by
   * menu id.
   */
  getMenus() {
    this.#ensureInitialized();
    return this.menus;
  }

  /**
   * Add or update menu data and optionally reparent menus (used when the update to
   * a menu includes a different parentId). A DeferredTask scheduled by this
   * method will update all menus data stored on disk for extensions that should
   * persist menus across sessions.
   *
   * @param {object}  menuDetails The createProperties for the menu
   * to add or update.
   * @param {boolean} [reparent=false] Set to true if the menu should also
   * be reparented.
   */
  updateMenus(menuDetails, reparent = false) {
    this.#ensureInitialized();

    if (this.persistMenusData && reparent) {
      // Make sure the reparent menu item is appended at the end (and so for sure
      // after the menu item that will become its new parent).
      // This is necessary if menuDetails.parentId is set (because it may point
      // to a menu entry that appears after the current entry in the Map), but we
      // still do it unconditionally (even if parentId is null) to make sure the
      // relocated menu item is always rendered at the bottom.
      this.menus.delete(menuDetails.id);
    }
    this.menus.set(menuDetails.id, menuDetails);

    if (!this.persistMenusData) {
      return;
    }

    if (reparent) {
      // The menu items are ordered, with child menu items always following its parent.
      // The logic below moves menu item registrations as needed to ensure a consistent order.
      let menuIds = new Set();
      menuIds.add(menuDetails.id);
      // Iterate over a copy of the entries because we may modify the menus Map.
      for (let [id, menuCreateDetails] of Array.from(this.menus)) {
        if (menuIds.has(menuCreateDetails.parentId)) {
          // Remember menu ID to detect its children.
          menuIds.add(id);
          // Append menu items to the end, to ensure that child menu items always follow the parent.
          this.menus.delete(id);
          this.menus.set(id, menuCreateDetails);
        }
      }
    }

    this.#scheduleWriteToStoreTask();
  }

  /**
   * Delete the given menu ids. A DeferredTask will update all menus
   * data stored on disk for extensions that should persist menus across sessions.
   *
   * @param {Array<MenuId>}  menuIds Array of menu ids to remove.
   */
  deleteMenus(menuIds) {
    this.#ensureInitialized();
    for (const menuId of menuIds) {
      this.menus.delete(menuId);
    }
    if (!this.persistMenusData) {
      return;
    }
    this.#scheduleWriteToStoreTask();
  }

  /**
   * Delete all menus. A DeferredTask scheduled by this method will update all menus
   * data stored on disk for extensions that should persist menus across sessions.
   */
  deleteAllMenus() {
    this.#ensureInitialized();
    let alreadyEmpty = !this.menus.size;
    this.menus.clear();
    if (!this.persistMenusData || alreadyEmpty) {
      return;
    }
    this.#scheduleWriteToStoreTask();
  }

  #scheduleWriteToStoreTask() {
    this.#ensureInitialized();
    if (!this.#writeToStoreTask) {
      this.#writeToStoreTask = new lazy.DeferredTask(
        () => this.#writeToStoreNow(),
        lazy.MENU_STORE_WRITE_DEBOUNCE_TIME
      );
      this.#shutdownBlockerFn = async () => {
        if (!this.#writeToStoreTask || this.#writeToStoreTask.isFinalized) {
          return;
        }
        await this.#writeToStoreTask.finalize();
        this.#writeToStoreTask = null;
        this.#shutdownBlockerFn = null;
      };
      lazy.AsyncShutdown.profileBeforeChange.addBlocker(
        `Flush "${this.extensionId}" persisted menus to disk`,
        this.#shutdownBlockerFn
      );
    }
    this.#writeToStoreTask.arm();
  }

  async #writeToStoreNow() {
    this.#ensureInitialized();
    await store.updatePersistedMenus(
      this.extensionId,
      this.extensionVersion,
      this.menus
    );
  }
}

/**
 * Singleton providing a collection of methods used by
 * ext-menus.js (and tests) to interact with the underlying classes.
 */
export const ExtensionMenus = {
  KVSTORE_DIRNAME,

  // WeakMap<Extension, { promise: Promise<ExtensionMenusManager>, instance: ExtensionMenusManager}>
  _menusManagers: new WeakMap(),

  /**
   * Determine if the menus created by the given extension should
   * be persisted on disk.
   *
   * @param {Extension} extension
   *
   * @returns {boolean} Returns true if the menus should be persisted on disk.
   */
  shouldPersistMenus(extension) {
    return ExtensionMenusStore.shouldPersistMenus(extension);
  },

  /**
   * Create and initialize ExtensionMenusManager instance
   * for the given extension.  Used by ext-menus.js onStartup
   * lifecycle method.
   *
   * @param {Extension} extension
   *
   * @returns {Promise<void>} A promise resolved when the
   * ExtensionMenusManager instance is fully initialized
   * (and persisted menus data loaded from disk for the
   * extensions with a non-persistent background script).
   */
  async asyncInitForExtension(extension) {
    let { promise } = this._menusManagers.get(extension) ?? {};
    if (promise) {
      return promise;
    }

    const instance = new ExtensionMenusManager(extension);
    extension.callOnClose({
      close: () => {
        this._menusManagers.delete(extension);
      },
    });
    promise = instance.asyncInit().then(() => instance);
    this._menusManagers.set(extension, { promise, instance });
    return promise;
  },

  _getManager(extension) {
    const { instance } = this._menusManagers.get(extension) ?? {};
    if (!instance) {
      throw new Error(
        `No ExtensionMenusManager instance found for ${extension.id}`
      );
    }
    return instance;
  },

  /**
   * Helper function used to normalize and merge menus
   * create and update properties.
   *
   * @param {object} obj The target object
   * @param {object} properties The properties to merge
   * on the target object.
   *
   * @returns {object} The target object updated with
   * the merged properties.
   */
  mergeMenuProperties(obj, properties) {
    // The menu properties are being normalized based on
    // the API JSONSchema definitions, and so we can
    // rely on expecting properties not specified to be
    // set to null, besides "icons" which is expected to
    // be omitted when not explicitly specified (due to
    // the use of `"optional": "omit-key-if-missing"` in
    // its schema definition).
    for (let propName in properties) {
      if (properties[propName] === null) {
        // Omitted optional argument.
        continue;
      }
      obj[propName] = properties[propName];
    }

    if ("icons" in properties && properties.icons === null && obj.icons) {
      obj.icons = null;
    }

    return obj;
  },

  /**
   * Synchronously retrieve the map of the extension menus.
   * Expected to only be called after ext-menus onStartup lifecycle
   * method has already initialized the ExtensionMenusManager through
   * a call to ExtensionMenus.asyncInitForExtension.
   *
   * @returns {Map<MenuId, object>} Map of the menus createProperties keyed by
   * menu id.
   */
  getMenus(extension) {
    return this._getManager(extension).getMenus();
  },

  _getStoredMenusForTesting(extensionId, extensionVersion) {
    return store.getPersistedMenus(extensionId, extensionVersion);
  },

  _hasStoredExtensionData(extensionId) {
    return store.hasExtensionData(extensionId);
  },

  _getStoreForTesting() {
    return store;
  },

  _recreateStoreForTesting() {
    store = new ExtensionMenusStore();
    return store;
  },

  /**
   * Add a new extension menu for the given extension. A DeferredTask
   * will update all menus data stored on disk for extensions that should
   * persist menus across sessions.
   *
   * Used by menus.create API method.
   *
   * @param {Extension} extension
   * @param {object} createProperties The properties for the
   * newly created menu.
   */
  addMenu(extension, createProperties) {
    // Only keep properties that are necessary.
    const menuProperties = this.mergeMenuProperties({}, createProperties);
    return this._getManager(extension).updateMenus(menuProperties);
  },

  /**
   * Update menu data and optionally reparent menus (used when the update to
   * a menu includes a different parentId). A DeferredTask scheduled by this
   * method will update all menus data stored on disk for extensions that should
   * persist menus across sessions.
   *
   * Used by menus.update API method.
   *
   * @param {Extension} extension
   * @param {MenuId}    menuId           The id of the menu to be updated.
   * @param {object}    updateProperties The properties to update on an existing
   * menu.
   * be reparented.
   */
  updateMenu(extension, menuId, updateProperties) {
    let menuProperties = this.getMenus(extension).get(menuId);
    let needsReparenting =
      updateProperties.parentId != null &&
      menuProperties.parentId != updateProperties.parentId;
    // Only keep properties that are necessary.
    menuProperties = this.mergeMenuProperties(
      this.getMenus(extension).get(menuId),
      updateProperties
    );
    return this._getManager(extension).updateMenus(
      menuProperties,
      needsReparenting
    );
  },

  /**
   * Delete the given menu ids. A DeferredTask will update all menus
   * data stored on disk for extensions that should persist menus across sessions.
   *
   * @param {Extension} extension
   * @param {Array<MenuId>} menuIds Array of menu ids to remove.
   */
  deleteMenus(extension, menuIds) {
    this._getManager(extension).deleteMenus(menuIds);
  },

  /**
   * Delete all menus. A DeferredTask scheduled by this method will update all menus
   * data stored on disk for extensions that should persist menus across sessions.
   *
   * @param {Extension} extension
   */
  deleteAllMenus(extension) {
    this._getManager(extension).deleteAllMenus();
  },

  /**
   * Remove the entry for the given extensionId from the data stored on disk (if any).
   *
   * @param {string} extensionId
   *
   * @returns {Promise<void>} A promise resolved when the extension data has been
   * removed from the store.
   */
  clearPersistedMenusOnUninstall(extensionId) {
    return store.clearPersistedMenusOnUninstall(extensionId);
  },
};
PK
!<x��y;y;"modules/ExtensionPageChild.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file handles privileged extension page logic that runs in the
 * child process.
 */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ExtensionChildDevToolsUtils:
    "resource://gre/modules/ExtensionChildDevToolsUtils.sys.mjs",
  Schemas: "resource://gre/modules/Schemas.sys.mjs",
});

const CATEGORY_EXTENSION_SCRIPTS_ADDON = "webextension-scripts-addon";
const CATEGORY_EXTENSION_SCRIPTS_DEVTOOLS = "webextension-scripts-devtools";

import { ExtensionCommon } from "resource://gre/modules/ExtensionCommon.sys.mjs";
import {
  ChildAPIManager,
  ExtensionActivityLogChild,
  Messenger,
} from "resource://gre/modules/ExtensionChild.sys.mjs";
import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

const { getInnerWindowID, promiseEvent } = ExtensionUtils;

const { BaseContext, CanOfAPIs, SchemaAPIManager, redefineGetter } =
  ExtensionCommon;

const initializeBackgroundPage = context => {
  // Override the `alert()` method inside background windows;
  // we alias it to console.log().
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1203394
  let alertDisplayedWarning = false;
  const innerWindowID = getInnerWindowID(context.contentWindow);

  /** @param {{ text, filename, lineNumber?, columnNumber? }} options */
  function logWarningMessage({ text, filename, lineNumber, columnNumber }) {
    let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(
      Ci.nsIScriptError
    );
    consoleMsg.initWithWindowID(
      text,
      filename,
      lineNumber,
      columnNumber,
      Ci.nsIScriptError.warningFlag,
      "webextension",
      innerWindowID
    );
    Services.console.logMessage(consoleMsg);
  }

  function ignoredSuspendListener() {
    logWarningMessage({
      text: "Background event page was not terminated on idle because a DevTools toolbox is attached to the extension.",
      filename: context.contentWindow.location.href,
    });
  }

  if (!context.extension.manifest.background.persistent) {
    context.extension.on(
      "background-script-suspend-ignored",
      ignoredSuspendListener
    );
    context.callOnClose({
      close: () => {
        context.extension.off(
          "background-script-suspend-ignored",
          ignoredSuspendListener
        );
      },
    });
  }

  let alertOverwrite = text => {
    const { filename, columnNumber, lineNumber } = Components.stack.caller;

    if (!alertDisplayedWarning) {
      context.childManager.callParentAsyncFunction(
        "runtime.openBrowserConsole",
        []
      );

      logWarningMessage({
        text: "alert() is not supported in background windows; please use console.log instead.",
        filename,
        lineNumber,
        columnNumber,
      });

      alertDisplayedWarning = true;
    }

    logWarningMessage({ text, filename, lineNumber, columnNumber });
  };
  Cu.exportFunction(alertOverwrite, context.contentWindow, {
    defineAs: "alert",
  });
};

var apiManager = new (class extends SchemaAPIManager {
  constructor() {
    super("addon", lazy.Schemas);
    this.initialized = false;
  }

  lazyInit() {
    if (!this.initialized) {
      this.initialized = true;
      this.initGlobal();
      for (let { value } of Services.catMan.enumerateCategory(
        CATEGORY_EXTENSION_SCRIPTS_ADDON
      )) {
        this.loadScript(value);
      }
    }
  }
})();

var devtoolsAPIManager = new (class extends SchemaAPIManager {
  constructor() {
    super("devtools", lazy.Schemas);
    this.initialized = false;
  }

  lazyInit() {
    if (!this.initialized) {
      this.initialized = true;
      this.initGlobal();
      for (let { value } of Services.catMan.enumerateCategory(
        CATEGORY_EXTENSION_SCRIPTS_DEVTOOLS
      )) {
        this.loadScript(value);
      }
    }
  }
})();

export function getContextChildManagerGetter(
  { envType },
  ChildAPIManagerClass = ChildAPIManager
) {
  return function () {
    let apiManager =
      envType === "devtools_parent"
        ? devtoolsAPIManager
        : this.extension.apiManager;

    apiManager.lazyInit();

    let localApis = {};
    let can = new CanOfAPIs(this, apiManager, localApis);

    let childManager = new ChildAPIManagerClass(
      this,
      this.messageManager,
      can,
      {
        envType,
        viewType: this.viewType,
        url: this.uri.spec,
        incognito: this.incognito,
        // Additional data a BaseContext subclass may optionally send
        // as part of the CreateProxyContext request sent to the main process
        // (e.g. WorkerContexChild implements this method to send the service
        // worker descriptor id along with the details send by default here).
        ...this.getCreateProxyContextData?.(),
      }
    );

    this.callOnClose(childManager);

    return childManager;
  };
}

export class ExtensionBaseContextChild extends BaseContext {
  /**
   * This ExtensionBaseContextChild represents an addon execution environment
   * that is running in an addon or devtools child process.
   *
   * @param {ExtensionChild} extension This context's owner.
   * @param {object} params
   * @param {string} params.envType One of "addon_child" or "devtools_child".
   * @param {nsIDOMWindow} params.contentWindow The window where the addon runs.
   * @param {string} params.viewType One of "background", "popup", "tab",
   *   "sidebar", "devtools_page" or "devtools_panel".
   * @param {number} [params.tabId] This tab's ID, used if viewType is "tab".
   * @param {nsIURI} [params.uri] The URI of the page.
   */
  constructor(extension, params) {
    if (!params.envType) {
      throw new Error("Missing envType");
    }

    super(params.envType, extension);
    let { viewType = "tab", uri, contentWindow, tabId } = params;
    this.viewType = viewType;
    this.uri = uri || extension.baseURI;

    this.setContentWindow(contentWindow);
    this.browsingContextId = contentWindow.docShell.browsingContext.id;

    if (viewType == "tab") {
      Object.defineProperty(this, "tabId", {
        value: tabId,
        enumerable: true,
        configurable: true,
      });
    }

    lazy.Schemas.exportLazyGetter(contentWindow, "browser", () => {
      return this.browserObj;
    });

    lazy.Schemas.exportLazyGetter(contentWindow, "chrome", () => {
      // For MV3 and later, this is just an alias for browser.
      if (extension.manifestVersion > 2) {
        return this.browserObj;
      }
      // Chrome compat is only used with MV2
      let chromeApiWrapper = Object.create(this.childManager);
      chromeApiWrapper.isChromeCompat = true;

      let chromeObj = Cu.createObjectIn(contentWindow);
      chromeApiWrapper.inject(chromeObj);
      return chromeObj;
    });
  }

  get browserObj() {
    const browserObj = Cu.createObjectIn(this.contentWindow);
    this.childManager.inject(browserObj);
    return redefineGetter(this, "browserObj", browserObj);
  }

  logActivity(type, name, data) {
    ExtensionActivityLogChild.log(this, type, name, data);
  }

  get cloneScope() {
    return this.contentWindow;
  }

  get principal() {
    return this.contentWindow.document.nodePrincipal;
  }

  get tabId() {
    // Will be overwritten in the constructor if necessary.
    return -1;
  }

  // Called when the extension shuts down.
  shutdown() {
    if (this.contentWindow) {
      this.contentWindow.close();
    }

    this.unload();
  }

  // This method is called when an extension page navigates away or
  // its tab is closed.
  unload() {
    // Note that without this guard, we end up running unload code
    // multiple times for tab pages closed by the "page-unload" handlers
    // triggered below.
    if (this.unloaded) {
      return;
    }

    super.unload();
  }

  get messenger() {
    return redefineGetter(this, "messenger", new Messenger(this));
  }

  /** @type {ReturnType<ReturnType<getContextChildManagerGetter>>} */
  get childManager() {
    throw new Error("childManager getter must be overridden");
  }
}

class ExtensionPageContextChild extends ExtensionBaseContextChild {
  /**
   * This ExtensionPageContextChild represents a privileged addon
   * execution environment that has full access to the WebExtensions
   * APIs (provided that the correct permissions have been requested).
   *
   * This is the child side of the ExtensionPageContextParent class
   * defined in ExtensionParent.sys.mjs.
   *
   * @param {ExtensionChild} extension This context's owner.
   * @param {object} params
   * @param {nsIDOMWindow} params.contentWindow The window where the addon runs.
   * @param {string} params.viewType One of "background", "popup", "sidebar" or "tab".
   *     "background", "sidebar" and "tab" are used by `browser.extension.getViews`.
   *     "popup" is only used internally to identify page action and browser
   *     action popups and options_ui pages.
   * @param {number} [params.tabId] This tab's ID, used if viewType is "tab".
   * @param {nsIURI} [params.uri] The URI of the page.
   */
  constructor(extension, params) {
    super(extension, Object.assign(params, { envType: "addon_child" }));

    if (this.viewType == "background") {
      initializeBackgroundPage(this);
    }

    this.extension.views.add(this);
  }

  unload() {
    super.unload();
    this.extension.views.delete(this);
  }

  get childManager() {
    const childManager = getContextChildManagerGetter({
      envType: "addon_parent",
    }).call(this);
    return redefineGetter(this, "childManager", childManager);
  }
}

export class DevToolsContextChild extends ExtensionBaseContextChild {
  /**
   * This DevToolsContextChild represents a devtools-related addon execution
   * environment that has access to the devtools API namespace and to the same subset
   * of APIs available in a content script execution environment.
   *
   * @param {ExtensionChild} extension This context's owner.
   * @param {object} params
   * @param {nsIDOMWindow} params.contentWindow The window where the addon runs.
   * @param {string} params.viewType One of "devtools_page" or "devtools_panel".
   * @param {object} [params.devtoolsToolboxInfo] This devtools toolbox's information,
   *   used if viewType is "devtools_page" or "devtools_panel".
   * @param {number} [params.tabId] This tab's ID, used if viewType is "tab".
   * @param {nsIURI} [params.uri] The URI of the page.
   */
  constructor(extension, params) {
    super(extension, Object.assign(params, { envType: "devtools_child" }));

    this.devtoolsToolboxInfo = params.devtoolsToolboxInfo;
    lazy.ExtensionChildDevToolsUtils.initThemeChangeObserver(
      params.devtoolsToolboxInfo.themeName,
      this
    );

    this.extension.devtoolsViews.add(this);
  }

  unload() {
    super.unload();
    this.extension.devtoolsViews.delete(this);
  }

  get childManager() {
    const childManager = getContextChildManagerGetter({
      envType: "devtools_parent",
    }).call(this);
    return redefineGetter(this, "childManager", childManager);
  }
}

export var ExtensionPageChild = {
  initialized: false,

  // Map<innerWindowId, ExtensionPageContextChild>
  extensionContexts: new Map(),

  apiManager,

  _init() {
    if (this.initialized) {
      return;
    }
    this.initialized = true;

    Services.obs.addObserver(this, "inner-window-destroyed"); // eslint-ignore-line mozilla/balanced-listeners
  },

  observe(subject, topic) {
    if (topic === "inner-window-destroyed") {
      let windowId = subject.QueryInterface(Ci.nsISupportsPRUint64).data;

      this.destroyExtensionContext(windowId);
    }
  },

  expectViewLoad(global, viewType) {
    promiseEvent(
      global,
      "DOMContentLoaded",
      true,
      /** @param {{target: Window|any}} event */
      event =>
        event.target.location != "about:blank" &&
        // Ignore DOMContentLoaded bubbled from child frames:
        event.target.defaultView === global.content
    ).then(() => {
      let windowId = getInnerWindowID(global.content);
      let context = this.extensionContexts.get(windowId);
      // This initializes ChildAPIManager (and creation of ProxyContextParent)
      // if they don't exist already at this point.
      let childId = context?.childManager.id;
      if (viewType === "background") {
        global.sendAsyncMessage("Extension:BackgroundViewLoaded", { childId });
      }
    });
  },

  /**
   * Create a privileged context at initial-document-element-inserted.
   *
   * @param {ExtensionChild} extension
   *     The extension for which the context should be created.
   * @param {nsIDOMWindow} contentWindow The global of the page.
   */
  initExtensionContext(extension, contentWindow) {
    this._init();

    if (!WebExtensionPolicy.isExtensionProcess) {
      throw new Error(
        "Cannot create an extension page context in current process"
      );
    }

    let windowId = getInnerWindowID(contentWindow);
    let context = this.extensionContexts.get(windowId);
    if (context) {
      if (context.extension !== extension) {
        throw new Error(
          "A different extension context already exists for this frame"
        );
      }
      throw new Error(
        "An extension context was already initialized for this frame"
      );
    }

    let uri = contentWindow.document.documentURIObject;

    let mm = contentWindow.docShell.messageManager;
    let data = mm.sendSyncMessage("Extension:GetFrameData")[0];
    if (!data) {
      let policy = WebExtensionPolicy.getByHostname(uri.host);
      // TODO bug 1749116: Handle this unexpected result, because data
      // (viewType in particular) should never be void for extension documents.
      Cu.reportError(`FrameData missing for ${policy?.id} page ${uri.spec}`);
    }
    let { viewType, tabId, devtoolsToolboxInfo } = data ?? {};

    if (viewType && contentWindow.top === contentWindow) {
      ExtensionPageChild.expectViewLoad(mm, viewType);
    }

    if (devtoolsToolboxInfo) {
      context = new DevToolsContextChild(extension, {
        viewType,
        contentWindow,
        uri,
        tabId,
        devtoolsToolboxInfo,
      });
    } else {
      context = new ExtensionPageContextChild(extension, {
        viewType,
        contentWindow,
        uri,
        tabId,
      });
    }

    this.extensionContexts.set(windowId, context);
  },

  /**
   * Close the ExtensionPageContextChild belonging to the given window, if any.
   *
   * @param {number} windowId The inner window ID of the destroyed context.
   */
  destroyExtensionContext(windowId) {
    let context = this.extensionContexts.get(windowId);
    if (context) {
      context.unload();
      this.extensionContexts.delete(windowId);
    }
  },

  shutdownExtension(extensionId) {
    for (let [windowId, context] of this.extensionContexts) {
      if (context.extension.id == extensionId) {
        context.shutdown();
        this.extensionContexts.delete(windowId);
      }
    }
  },
};
PK
!<��?���modules/ExtensionParent.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This module contains code for managing APIs that need to run in the
 * parent process, and handles the parent side of operations that need
 * to be proxied from ExtensionChild.sys.mjs.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

/** @type {Lazy} */
const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  BroadcastConduit: "resource://gre/modules/ConduitsParent.sys.mjs",
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  DevToolsShim: "chrome://devtools-startup/content/DevToolsShim.sys.mjs",
  ExtensionActivityLog: "resource://gre/modules/ExtensionActivityLog.sys.mjs",
  ExtensionData: "resource://gre/modules/Extension.sys.mjs",
  GeckoViewConnection: "resource://gre/modules/GeckoViewWebExtension.sys.mjs",
  MessageManagerProxy: "resource://gre/modules/MessageManagerProxy.sys.mjs",
  NativeApp: "resource://gre/modules/NativeMessaging.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
  Schemas: "resource://gre/modules/Schemas.sys.mjs",
  getErrorNameForTelemetry: "resource://gre/modules/ExtensionTelemetry.sys.mjs",
  WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetters(lazy, {
  aomStartup: [
    "@mozilla.org/addons/addon-manager-startup;1",
    "amIAddonManagerStartup",
  ],
});

import { ExtensionCommon } from "resource://gre/modules/ExtensionCommon.sys.mjs";
import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

const DUMMY_PAGE_URI = Services.io.newURI(
  "chrome://extensions/content/dummy.xhtml"
);

var { BaseContext, CanOfAPIs, SchemaAPIManager, SpreadArgs, redefineGetter } =
  ExtensionCommon;

var {
  DefaultMap,
  DefaultWeakMap,
  ExtensionError,
  promiseDocumentLoaded,
  promiseEvent,
  promiseObserved,
} = ExtensionUtils;

const ERROR_NO_RECEIVERS =
  "Could not establish connection. Receiving end does not exist.";

const BASE_SCHEMA = "chrome://extensions/content/schemas/manifest.json";
const CATEGORY_EXTENSION_MODULES = "webextension-modules";
const CATEGORY_EXTENSION_SCHEMAS = "webextension-schemas";
const CATEGORY_EXTENSION_SCRIPTS = "webextension-scripts";

let schemaURLs = new Set();

schemaURLs.add("chrome://extensions/content/schemas/experiments.json");

let GlobalManager;
let ParentAPIManager;

function verifyActorForContext(actor, context) {
  if (JSWindowActorParent.isInstance(actor)) {
    let target = actor.browsingContext.top.embedderElement;
    if (context.parentMessageManager !== target.messageManager) {
      throw new Error("Got message on unexpected message manager");
    }
  } else if (JSProcessActorParent.isInstance(actor)) {
    if (actor.manager.remoteType !== context.extension.remoteType) {
      throw new Error("Got message from unexpected process");
    }
  }
}

// This object loads the ext-*.js scripts that define the extension API.
let apiManager = new (class extends SchemaAPIManager {
  constructor() {
    super("main", lazy.Schemas);
    this.initialized = null;

    /* eslint-disable mozilla/balanced-listeners */
    this.on("startup", (e, extension) => {
      return extension.apiManager.onStartup(extension);
    });

    this.on("update", async (e, { id, resourceURI, isPrivileged }) => {
      let modules = this.eventModules.get("update");
      if (modules.size == 0) {
        return;
      }

      let extension = new lazy.ExtensionData(resourceURI, isPrivileged);
      await extension.loadManifest();

      return Promise.all(
        Array.from(modules).map(async apiName => {
          let module = await this.asyncLoadModule(apiName);
          module.onUpdate(id, extension.manifest);
        })
      );
    });

    this.on("uninstall", (e, { id }) => {
      let modules = this.eventModules.get("uninstall");
      return Promise.all(
        Array.from(modules).map(async apiName => {
          let module = await this.asyncLoadModule(apiName);
          return module.onUninstall(id);
        })
      );
    });
    /* eslint-enable mozilla/balanced-listeners */

    // Handle any changes that happened during startup
    let disabledIds = lazy.AddonManager.getStartupChanges(
      lazy.AddonManager.STARTUP_CHANGE_DISABLED
    );
    if (disabledIds.length) {
      this._callHandlers(disabledIds, "disable", "onDisable");
    }

    let uninstalledIds = lazy.AddonManager.getStartupChanges(
      lazy.AddonManager.STARTUP_CHANGE_UNINSTALLED
    );
    if (uninstalledIds.length) {
      this._callHandlers(uninstalledIds, "uninstall", "onUninstall");
    }
  }

  getModuleJSONURLs() {
    return Array.from(
      Services.catMan.enumerateCategory(CATEGORY_EXTENSION_MODULES),
      ({ value }) => value
    );
  }

  // Loads all the ext-*.js scripts currently registered.
  lazyInit() {
    if (this.initialized) {
      return this.initialized;
    }

    let modulesPromise = StartupCache.other.get(["parentModules"], () =>
      this.loadModuleJSON(this.getModuleJSONURLs())
    );

    let scriptURLs = [];
    for (let { value } of Services.catMan.enumerateCategory(
      CATEGORY_EXTENSION_SCRIPTS
    )) {
      scriptURLs.push(value);
    }

    let promise = (async () => {
      let scripts = await Promise.all(
        scriptURLs.map(url => ChromeUtils.compileScript(url))
      );

      this.initModuleData(await modulesPromise);

      this.initGlobal();
      for (let script of scripts) {
        script.executeInGlobal(this.global);
      }

      // Load order matters here. The base manifest defines types which are
      // extended by other schemas, so needs to be loaded first.
      return lazy.Schemas.load(BASE_SCHEMA).then(() => {
        let promises = [];
        for (let { value } of Services.catMan.enumerateCategory(
          CATEGORY_EXTENSION_SCHEMAS
        )) {
          promises.push(lazy.Schemas.load(value));
        }
        for (let [url, { content }] of this.schemaURLs) {
          promises.push(lazy.Schemas.load(url, content));
        }
        for (let url of schemaURLs) {
          promises.push(lazy.Schemas.load(url));
        }
        return Promise.all(promises).then(() => {
          lazy.Schemas.updateSharedSchemas();
        });
      });
    })();

    Services.mm.addMessageListener("Extension:GetFrameData", this);

    this.initialized = promise;
    return this.initialized;
  }

  receiveMessage({ target }) {
    let data = GlobalManager.frameData.get(target) || {};
    Object.assign(data, this.global.tabTracker.getBrowserData(target));
    return data;
  }

  // Call static handlers for the given event on the given extension ids,
  // and set up a shutdown blocker to ensure they all complete.
  _callHandlers(ids, event, method) {
    let promises = Array.from(this.eventModules.get(event))
      .map(async modName => {
        let module = await this.asyncLoadModule(modName);
        return ids.map(id => module[method](id));
      })
      .flat();
    if (event === "disable") {
      promises.push(...ids.map(id => this.emit("disable", id)));
    }
    if (event === "enabling") {
      promises.push(...ids.map(id => this.emit("enabling", id)));
    }

    lazy.AsyncShutdown.profileBeforeChange.addBlocker(
      `Extension API ${event} handlers for ${ids.join(",")}`,
      Promise.all(promises)
    );
  }
})();

/**
 * @typedef {object} ParentPort
 * @property {boolean} [native]
 * @property {string} [senderChildId]
 * @property {function(StructuredCloneHolder): any} onPortMessage
 * @property {Function} onPortDisconnect
 */

// Receives messages related to the extension messaging API and forwards them
// to relevant child messengers.  Also handles Native messaging and GeckoView.
/** @typedef {typeof ProxyMessenger} NativeMessenger */
const ProxyMessenger = {
  /** @type {Map<number, ParentPort>} */
  ports: new Map(),
  /** @type {Map<number, Promise>} */
  portPromises: new Map(),

  init() {
    this.conduit = new lazy.BroadcastConduit(ProxyMessenger, {
      id: "ProxyMessenger",
      reportOnClosed: "portId",
      recv: ["PortConnect", "PortMessage", "NativeMessage", "RuntimeMessage"],
      cast: ["PortConnect", "PortMessage", "PortDisconnect", "RuntimeMessage"],
    });
  },

  openNative(nativeApp, sender) {
    let context = ParentAPIManager.getContextById(sender.childId);
    if (context.extension.hasPermission("geckoViewAddons")) {
      return new lazy.GeckoViewConnection(
        this.getSender(context.extension, sender),
        sender.actor.browsingContext.top.embedderElement,
        nativeApp,
        context.extension.hasPermission("nativeMessagingFromContent")
      );
    } else if (sender.verified) {
      return new lazy.NativeApp(context, nativeApp);
    }
    sender = this.getSender(context.extension, sender);
    throw new Error(`Native messaging not allowed: ${JSON.stringify(sender)}`);
  },

  recvNativeMessage({ nativeApp, holder }, { sender }) {
    const app = this.openNative(nativeApp, sender);

    // Track in-flight NativeApp sendMessage requests as
    // a NativeApp port destroyed when the request
    // has been handled.
    const promiseSendMessage = app.sendMessage(holder);
    const sendMessagePort = {
      native: true,
      senderChildId: sender.childId,
    };
    this.trackNativeAppPort(sendMessagePort);
    const untrackSendMessage = () => this.untrackNativeAppPort(sendMessagePort);
    promiseSendMessage.then(untrackSendMessage, untrackSendMessage);

    return promiseSendMessage;
  },

  getSender(extension, source) {
    let sender = {
      contextId: source.id,
      id: source.extensionId,
      envType: source.envType,
      url: source.url,
    };

    if (JSWindowActorParent.isInstance(source.actor)) {
      let { currentWindowContext, top } = source.actor.browsingContext;
      let browser = top.embedderElement;
      let data =
        browser && apiManager.global.tabTracker.getBrowserData(browser);
      if (data?.tabId > 0) {
        sender.tab = extension.tabManager.get(data.tabId, null)?.convert();
        // frameId is documented to only be set if sender.tab is set.
        sender.frameId = source.frameId;
      }

      let principal = currentWindowContext.documentPrincipal;
      // We intend the serialization of null principals *and* file scheme to be
      // "null".
      sender.origin = new URL(principal.originNoSuffix).origin;
    } else if (source.verified) {
      sender.origin = `moz-extension://${extension.uuid}`;
    }

    return sender;
  },

  getTopBrowsingContextId(tabId) {
    // If a tab alredy has content scripts, no need to check private browsing.
    let tab = apiManager.global.tabTracker.getTab(tabId, null);
    if (!tab || (tab.browser || tab).getAttribute("pending") === "true") {
      // No receivers in discarded tabs, so bail early to keep the browser lazy.
      throw new ExtensionError(ERROR_NO_RECEIVERS);
    }
    let browser = tab.linkedBrowser || tab.browser;
    return browser.browsingContext.id;
  },

  // TODO: Rework/simplify this and getSender/getTopBC after bug 1580766.
  async normalizeArgs(arg, sender) {
    arg.extensionId = arg.extensionId || sender.extensionId;
    let extension = GlobalManager.extensionMap.get(arg.extensionId);
    if (!extension) {
      return Promise.reject({ message: ERROR_NO_RECEIVERS });
    }
    // TODO bug 1852317: This should not be unconditional.
    await extension.wakeupBackground?.();

    arg.sender = this.getSender(extension, sender);
    arg.topBC = arg.tabId && this.getTopBrowsingContextId(arg.tabId);
    return arg.tabId ? "tab" : "messenger";
  },

  async recvRuntimeMessage(arg, { sender }) {
    arg.firstResponse = true;
    let kind = await this.normalizeArgs(arg, sender);
    let result = await this.conduit.castRuntimeMessage(kind, arg);
    if (!result) {
      // "throw new ExtensionError" cannot be used because then the stack of the
      // sendMessage call would not be added to the error object generated by
      // context.normalizeError. Test coverage by test_ext_error_location.js.
      return Promise.reject({ message: ERROR_NO_RECEIVERS });
    }
    return result.value;
  },

  async recvPortConnect(arg, { sender }) {
    if (arg.native) {
      let port = this.openNative(arg.name, sender).onConnect(arg.portId, this);
      port.senderChildId = sender.childId;
      port.native = true;
      this.ports.set(arg.portId, port);
      this.trackNativeAppPort(port);
      return;
    }

    // PortMessages that follow will need to wait for the port to be opened.
    let { promise, resolve, reject } = Promise.withResolvers();
    this.portPromises.set(arg.portId, promise);

    try {
      let kind = await this.normalizeArgs(arg, sender);
      let all = await this.conduit.castPortConnect(kind, arg);
      resolve();

      // If there are no active onConnect listeners.
      if (!all.some(x => x.value)) {
        throw new ExtensionError(ERROR_NO_RECEIVERS);
      }
    } catch (err) {
      // Throw _and_ reject with error, so everything awaiting this port fails.
      reject(err);
      throw err;
    } finally {
      this.portPromises.delete(arg.portId);
    }
  },

  async recvPortMessage({ holder }, { sender }) {
    if (sender.native) {
      // If the nativeApp port connect fails (e.g. if triggered by a content
      // script), the portId may not be in the map (because it did throw in
      // the openNative method).
      return this.ports.get(sender.portId)?.onPortMessage(holder);
    }
    // NOTE: the following await make sure we await for promised ports
    // (ports that were not yet open when added to the Map,
    // see recvPortConnect).
    await this.portPromises.get(sender.portId);
    this.sendPortMessage(sender.portId, holder, !sender.source);
  },

  recvConduitClosed(sender) {
    let app = this.ports.get(sender.portId);
    if (this.ports.delete(sender.portId) && sender.native) {
      this.untrackNativeAppPort(app);
      return app.onPortDisconnect();
    }
    this.sendPortDisconnect(sender.portId, null, !sender.source);
  },

  sendPortMessage(portId, holder, source = true) {
    this.conduit.castPortMessage("port", { portId, source, holder });
  },

  sendPortDisconnect(portId, error, source = true) {
    let port = this.ports.get(portId);
    this.untrackNativeAppPort(port);
    this.conduit.castPortDisconnect("port", { portId, source, error });
    this.ports.delete(portId);
  },

  trackNativeAppPort(port) {
    if (!port?.native) {
      return;
    }

    try {
      let context = ParentAPIManager.getContextById(port.senderChildId);
      context?.trackNativeAppPort(port);
    } catch {
      // getContextById will throw if the context has been destroyed
      // in the meantime.
    }
  },

  untrackNativeAppPort(port) {
    if (!port?.native) {
      return;
    }

    try {
      let context = ParentAPIManager.getContextById(port.senderChildId);
      context?.untrackNativeAppPort(port);
    } catch {
      // getContextById will throw if the context has been destroyed
      // in the meantime.
    }
  },
};
ProxyMessenger.init();

// Responsible for loading extension APIs into the right globals.
GlobalManager = {
  // Map[extension ID -> Extension]. Determines which extension is
  // responsible for content under a particular extension ID.
  extensionMap: new Map(),
  initialized: false,

  /** @type {WeakMap<XULBrowserElement, object>} Extension Context init data. */
  frameData: new WeakMap(),

  init(extension) {
    if (this.extensionMap.size == 0) {
      apiManager.on("extension-browser-inserted", this._onExtensionBrowser);
      this.initialized = true;
    }
    this.extensionMap.set(extension.id, extension);
  },

  uninit(extension) {
    this.extensionMap.delete(extension.id);

    if (this.extensionMap.size == 0 && this.initialized) {
      apiManager.off("extension-browser-inserted", this._onExtensionBrowser);
      this.initialized = false;
    }
  },

  _onExtensionBrowser(type, browser, data = {}) {
    data.viewType = browser.getAttribute("webextension-view-type");
    if (data.viewType) {
      GlobalManager.frameData.set(browser, data);
    }
  },

  getExtension(extensionId) {
    return this.extensionMap.get(extensionId);
  },
};

/**
 * The proxied parent side of a context in ExtensionChild.sys.mjs, for the
 * parent side of a proxied API.
 */
class ProxyContextParent extends BaseContext {
  constructor(envType, extension, params, browsingContext, principal) {
    super(envType, extension);

    this.childId = params.childId;
    this.uri = Services.io.newURI(params.url);
    this.browsingContext = browsingContext;

    this.incognito = params.incognito;

    this.listenerPromises = new Set();

    // browsingContext is null when subclassed by BackgroundWorkerContextParent.
    const xulBrowser = browsingContext?.top.embedderElement;
    // This message manager is used by ParentAPIManager to send messages and to
    // close the ProxyContext if the underlying message manager closes. This
    // message manager object may change when `xulBrowser` swaps docshells, e.g.
    // when a tab is moved to a different window.
    // TODO: Is xulBrowser correct for ContentScriptContextParent? Messages
    // through the xulBrowser won't reach cross-process iframes.
    this.messageManagerProxy =
      xulBrowser && new lazy.MessageManagerProxy(xulBrowser);

    Object.defineProperty(this, "principal", {
      value: principal,
      enumerable: true,
      configurable: true,
    });

    this.listenerProxies = new Map();

    this.pendingEventBrowser = null;
    this.callContextData = null;

    // Set of active NativeApp ports.
    this.activeNativePorts = new WeakSet();

    // Set of pending queryRunListener promises.
    this.runListenerPromises = new Set();

    apiManager.emit("proxy-context-load", this);
  }

  get isProxyContextParent() {
    return true;
  }

  get frameId() {
    if (!this.browsingContext) {
      return -1;
    }

    return lazy.WebNavigationFrames.getFrameId(this.browsingContext);
  }

  get contextType() {
    switch (this.viewType) {
      case "background_worker": // intentionally fall-through
      case "background":
        return "BACKGROUND";
      case "popup":
        return "POPUP";
      case "sidebar":
        return "SIDE_PANEL";
      case "tab":
        return "TAB";
      default:
        throw new Error(
          `Unexpected missing contextType mapping for viewType "${this.viewType}"`
        );
    }
  }

  toExtensionContext() {
    // NOTE: implemented in subclasses that should be listed in runtime.getContexts results
    // when they match the ContextFilter, whereas instances from subclasses that don't
    // implement it will always be filtered out.
    return undefined;
  }

  trackRunListenerPromise(runListenerPromise) {
    if (
      // The extension was already shutdown.
      !this.extension ||
      // Not a non persistent background script context.
      !this.isBackgroundContext ||
      this.extension.persistentBackground
    ) {
      return;
    }
    const clearFromSet = () =>
      this.runListenerPromises.delete(runListenerPromise);
    runListenerPromise.then(clearFromSet, clearFromSet);
    this.runListenerPromises.add(runListenerPromise);
  }

  clearPendingRunListenerPromises() {
    this.runListenerPromises.clear();
  }

  get pendingRunListenerPromisesCount() {
    return this.runListenerPromises.size;
  }

  trackNativeAppPort(port) {
    if (
      // Not a native port.
      !port?.native ||
      // Not a non persistent background script context.
      !this.isBackgroundContext ||
      this.extension?.persistentBackground ||
      // The extension was already shutdown.
      !this.extension
    ) {
      return;
    }
    this.activeNativePorts.add(port);
  }

  untrackNativeAppPort(port) {
    this.activeNativePorts.delete(port);
  }

  get hasActiveNativeAppPorts() {
    return !!ChromeUtils.nondeterministicGetWeakSetKeys(this.activeNativePorts)
      .length;
  }

  /**
   * Call the `callable` parameter with `context.callContextData` set to the value passed
   * as the first parameter of this method.
   *
   * `context.callContextData` is expected to:
   * - don't be set when context.withCallContextData is being called
   * - be set back to null right after calling the `callable` function, without
   *   awaiting on any async code that the function may be running internally
   *
   * The callable method itself is responsabile of eventually retrieve the value initially set
   * on the `context.callContextData` before any code executed asynchronously (e.g. from a
   * callback or after awaiting internally on a promise if the `callable` function was async).
   *
   * @param {object} callContextData
   * @param {boolean} callContextData.isHandlingUserInput
   * @param {Function} callable
   *
   * @returns {any} Returns the value returned by calling the `callable` method.
   */
  withCallContextData({ isHandlingUserInput }, callable) {
    if (this.callContextData) {
      Cu.reportError(
        `Unexpected pre-existing callContextData on "${this.extension?.policy.debugName}" contextId ${this.contextId}`
      );
    }

    try {
      this.callContextData = {
        isHandlingUserInput,
      };
      return callable();
    } finally {
      this.callContextData = null;
    }
  }

  async withPendingBrowser(browser, callable) {
    let savedBrowser = this.pendingEventBrowser;
    this.pendingEventBrowser = browser;
    try {
      let result = await callable();
      return result;
    } finally {
      this.pendingEventBrowser = savedBrowser;
    }
  }

  logActivity() {
    // The base class will throw so we catch any subclasses that do not implement.
    // We do not want to throw here, but we also do not log here.
  }

  get cloneScope() {
    return this.sandbox;
  }

  applySafe(callback, args) {
    // There's no need to clone when calling listeners for a proxied
    // context.
    return this.applySafeWithoutClone(callback, args);
  }

  get xulBrowser() {
    return this.messageManagerProxy?.eventTarget;
  }

  get parentMessageManager() {
    // TODO bug 1595186: Replace use of parentMessageManager.
    return this.messageManagerProxy?.messageManager;
  }

  shutdown() {
    this.unload();
  }

  unload() {
    if (this.unloaded) {
      return;
    }

    this.messageManagerProxy?.dispose();

    super.unload();
    apiManager.emit("proxy-context-unload", this);
  }

  get apiCan() {
    const apiCan = new CanOfAPIs(this, this.extension.apiManager, {});
    return redefineGetter(this, "apiCan", apiCan);
  }

  get apiObj() {
    return redefineGetter(this, "apiObj", this.apiCan.root);
  }

  get sandbox() {
    // Note: Blob and URL globals are used in ext-contentScripts.js.
    const sandbox = Cu.Sandbox(this.principal, {
      sandboxName: this.uri.spec,
      wantGlobalProperties: ["Blob", "URL"],
    });
    return redefineGetter(this, "sandbox", sandbox);
  }
}

/**
 * The parent side of proxied API context for extension content script
 * running in ExtensionContent.sys.mjs.
 */
class ContentScriptContextParent extends ProxyContextParent {}

/**
 * The parent side of proxied API context for extension page, such as a
 * background script, a tab page, or a popup, running in
 * ExtensionChild.sys.mjs.
 */
class ExtensionPageContextParent extends ProxyContextParent {
  constructor(envType, extension, params, browsingContext) {
    super(envType, extension, params, browsingContext, extension.principal);

    this.viewType = params.viewType;
    this.isTopContext = browsingContext.top === browsingContext;

    this.extension.views.add(this);

    extension.emit("extension-proxy-context-load", this);
  }

  // The window that contains this context. This may change due to moving tabs.
  get appWindow() {
    let win = this.xulBrowser.ownerGlobal;
    return win.browsingContext.topChromeWindow;
  }

  get currentWindow() {
    if (this.viewType !== "background") {
      return this.appWindow;
    }
    return undefined;
  }

  get tabId() {
    let { tabTracker } = apiManager.global;
    let data = tabTracker.getBrowserData(this.xulBrowser);
    if (data.tabId >= 0) {
      return data.tabId;
    }
    return undefined;
  }

  toExtensionContext() {
    const { tabTracker } = apiManager.global;
    const { tabId, windowId } = tabTracker.getBrowserDataForContext(this);
    const windowContext = this.browsingContext?.currentWindowContext;
    return {
      // NOTE: the contextId property in the final set of properties returned to
      // extensions code is filled in on the ext-runtime.js and it is not to be
      // confused with the internal property called contextId.
      contextId: undefined,
      // NOTE: contextType is a getter that maps the viewType property used
      // internally with the value expected for the runtime.ExtensionContext
      // contextType property (which should be one of the values part of the
      // runtime.ContextType enum).
      contextType: this.contextType,
      // TODO(Bug 1891478): add documentId.
      // TODO(Bug 1890739): consider switching this to use webExposedOriginSerialization when available
      // Using nsIPrincipal.originNoSuffix to avoid including the
      // private browsing (or contextual identity ones)
      documentOrigin: windowContext?.documentPrincipal.originNoSuffix,
      documentUrl: windowContext?.documentURI.spec,
      incognito: this.incognito,
      frameId: this.frameId,
      tabId,
      windowId,
      // TODO: File followup to also add a Firefox-only userContextId?
    };
  }

  unload() {
    super.unload();
    this.extension.views.delete(this);
  }

  shutdown() {
    apiManager.emit("page-shutdown", this);
    super.shutdown();
  }
}

/**
 * The parent side of proxied API context for devtools extension page, such as a
 * devtools pages and panels running in ExtensionChild.sys.mjs.
 */
class DevToolsExtensionPageContextParent extends ExtensionPageContextParent {
  constructor(...params) {
    super(...params);

    // Set all attributes that are lazily defined to `null` here.
    //
    // Note that we can't do that for `this._devToolsToolbox` because it will
    // be defined when calling our parent constructor and so would override it back to `null`.
    this._devToolsCommands = null;
    this._onNavigatedListeners = null;

    this._onResourceAvailable = this._onResourceAvailable.bind(this);
  }

  toExtensionContext() {
    // NOTE: devtools extension contexts are currently omitted in getContexts
    // results.
    return undefined;
  }

  set devToolsToolbox(toolbox) {
    if (this._devToolsToolbox) {
      throw new Error("Cannot set the context DevTools toolbox twice");
    }

    this._devToolsToolbox = toolbox;
  }

  get devToolsToolbox() {
    return this._devToolsToolbox;
  }

  async addOnNavigatedListener(listener) {
    if (!this._onNavigatedListeners) {
      this._onNavigatedListeners = new Set();

      await this.devToolsToolbox.resourceCommand.watchResources(
        [this.devToolsToolbox.resourceCommand.TYPES.DOCUMENT_EVENT],
        {
          onAvailable: this._onResourceAvailable,
          ignoreExistingResources: true,
        }
      );
    }

    this._onNavigatedListeners.add(listener);
  }

  removeOnNavigatedListener(listener) {
    if (this._onNavigatedListeners) {
      this._onNavigatedListeners.delete(listener);
    }
  }

  /**
   * The returned "commands" object, exposing modules implemented from devtools/shared/commands.
   * Each attribute being a static interface to communicate with the server backend.
   *
   * @returns {Promise<object>}
   */
  async getDevToolsCommands() {
    // Ensure that we try to instantiate a commands only once,
    // even if createCommandsForTabForWebExtension is async.
    if (this._devToolsCommandsPromise) {
      return this._devToolsCommandsPromise;
    }
    if (this._devToolsCommands) {
      return this._devToolsCommands;
    }

    this._devToolsCommandsPromise = (async () => {
      const commands =
        await lazy.DevToolsShim.createCommandsForTabForWebExtension(
          this.devToolsToolbox.commands.descriptorFront.localTab
        );
      await commands.targetCommand.startListening();
      this._devToolsCommands = commands;
      this._devToolsCommandsPromise = null;
      return commands;
    })();
    return this._devToolsCommandsPromise;
  }

  unload() {
    // Bail if the toolbox reference was already cleared.
    if (!this.devToolsToolbox) {
      return;
    }

    if (this._onNavigatedListeners) {
      this.devToolsToolbox.resourceCommand.unwatchResources(
        [this.devToolsToolbox.resourceCommand.TYPES.DOCUMENT_EVENT],
        { onAvailable: this._onResourceAvailable }
      );
    }

    if (this._devToolsCommands) {
      this._devToolsCommands.destroy();
      this._devToolsCommands = null;
    }

    if (this._onNavigatedListeners) {
      this._onNavigatedListeners.clear();
      this._onNavigatedListeners = null;
    }

    this._devToolsToolbox = null;

    super.unload();
  }

  async _onResourceAvailable(resources) {
    for (const resource of resources) {
      const { targetFront } = resource;
      if (targetFront.isTopLevel && resource.name === "dom-complete") {
        for (const listener of this._onNavigatedListeners) {
          listener(targetFront.url);
        }
      }
    }
  }
}

/**
 * The parent side of proxied API context for extension background service
 * worker script.
 */
class BackgroundWorkerContextParent extends ProxyContextParent {
  constructor(envType, extension, params) {
    // TODO: split out from ProxyContextParent a base class that
    // doesn't expect a browsingContext and one for contexts that are
    // expected to have a browsingContext associated.
    super(envType, extension, params, null, extension.principal);

    this.viewType = params.viewType;
    this.workerDescriptorId = params.workerDescriptorId;

    this.extension.views.add(this);

    extension.emit("extension-proxy-context-load", this);
  }
}

ParentAPIManager = {
  proxyContexts: new Map(),

  init() {
    // TODO: Bug 1595186 - remove/replace all usage of MessageManager below.
    Services.obs.addObserver(this, "message-manager-close");

    this.conduit = new lazy.BroadcastConduit(this, {
      id: "ParentAPIManager",
      reportOnClosed: "childId",
      recv: [
        "CreateProxyContext",
        "ContextLoaded",
        "APICall",
        "AddListener",
        "RemoveListener",
      ],
      send: ["CallResult"],
      query: ["RunListener", "StreamFilterSuspendCancel"],
    });
  },

  attachMessageManager(extension, processMessageManager) {
    extension.parentMessageManager = processMessageManager;
  },

  async observe(subject, topic) {
    if (topic === "message-manager-close") {
      let mm = subject;
      for (let [childId, context] of this.proxyContexts) {
        if (context.parentMessageManager === mm) {
          this.closeProxyContext(childId);
        }
      }

      // Reset extension message managers when their child processes shut down.
      for (let extension of GlobalManager.extensionMap.values()) {
        if (extension.parentMessageManager === mm) {
          extension.parentMessageManager = null;
        }
      }
    }
  },

  shutdownExtension(extensionId, reason) {
    if (["ADDON_DISABLE", "ADDON_UNINSTALL"].includes(reason)) {
      apiManager._callHandlers([extensionId], "disable", "onDisable");
    }

    for (let [childId, context] of this.proxyContexts) {
      if (context.extension.id == extensionId) {
        context.shutdown();
        this.proxyContexts.delete(childId);
      }
    }
  },

  queryStreamFilterSuspendCancel(childId) {
    return this.conduit.queryStreamFilterSuspendCancel(childId);
  },

  recvCreateProxyContext(data, { actor, sender }) {
    let { envType, extensionId, childId, principal } = data;

    if (this.proxyContexts.has(childId)) {
      throw new Error(
        "A WebExtension context with the given ID already exists!"
      );
    }

    let extension = GlobalManager.getExtension(extensionId);
    if (!extension) {
      throw new Error(`No WebExtension found with ID ${extensionId}`);
    }

    let context;
    if (envType == "addon_parent" || envType == "devtools_parent") {
      if (!sender.verified) {
        throw new Error(`Bad sender context envType: ${sender.envType}`);
      }

      if (JSWindowActorParent.isInstance(actor)) {
        const target = actor.browsingContext.top.embedderElement;
        let processMessageManager =
          target.messageManager.processMessageManager ||
          Services.ppmm.getChildAt(0);

        if (!extension.parentMessageManager) {
          if (target.remoteType === extension.remoteType) {
            this.attachMessageManager(extension, processMessageManager);
          }
        }

        if (processMessageManager !== extension.parentMessageManager) {
          throw new Error(
            "Attempt to create privileged extension parent from incorrect child process"
          );
        }

        if (envType == "addon_parent") {
          context = new ExtensionPageContextParent(
            envType,
            extension,
            data,
            actor.browsingContext
          );
        } else if (envType == "devtools_parent") {
          context = new DevToolsExtensionPageContextParent(
            envType,
            extension,
            data,
            actor.browsingContext
          );
        }
      } else if (JSProcessActorParent.isInstance(actor)) {
        if (actor.manager.remoteType !== extension.remoteType) {
          throw new Error(
            "Attempt to create privileged extension parent from incorrect child process"
          );
        }

        if (envType !== "addon_parent") {
          throw new Error(
            `Unexpected envType ${envType} on an extension process actor`
          );
        }
        if (data.viewType !== "background_worker") {
          throw new Error(
            `Unexpected viewType ${data.viewType} on an extension process actor`
          );
        }
        context = new BackgroundWorkerContextParent(envType, extension, data);
      } else {
        // Unreacheable: JSWindowActorParent and JSProcessActorParent are the
        // only actors.
        throw new Error(
          "Attempt to create privileged extension parent via incorrect actor"
        );
      }
    } else if (envType == "content_parent") {
      // Note: actor is always a JSWindowActorParent, with a browsingContext.
      context = new ContentScriptContextParent(
        envType,
        extension,
        data,
        actor.browsingContext,
        principal
      );
    } else {
      throw new Error(`Invalid WebExtension context envType: ${envType}`);
    }
    this.proxyContexts.set(childId, context);
  },

  recvContextLoaded(data, { actor }) {
    let context = this.getContextById(data.childId);
    verifyActorForContext(actor, context);
    const { extension } = context;
    extension.emit("extension-proxy-context-load:completed", context);
  },

  recvConduitClosed(sender) {
    this.closeProxyContext(sender.id);
  },

  closeProxyContext(childId) {
    let context = this.proxyContexts.get(childId);
    if (context) {
      context.unload();
      this.proxyContexts.delete(childId);
    }
  },

  /**
   * Call the given function and also log the call as appropriate
   * (i.e., with activity logging and/or profiler markers)
   *
   * @param {BaseContext} context The context making this call.
   * @param {object} data Additional data about the call.
   * @param {Function} callable The actual implementation to invoke.
   */
  async callAndLog(context, data, callable) {
    let { id } = context.extension;
    // If we were called via callParentAsyncFunction we don't want
    // to log again, check for the flag.
    const { alreadyLogged } = data.options || {};
    if (!alreadyLogged) {
      lazy.ExtensionActivityLog.log(
        id,
        context.viewType,
        "api_call",
        data.path,
        {
          args: data.args,
        }
      );
    }

    let start = Cu.now();
    try {
      return callable();
    } finally {
      ChromeUtils.addProfilerMarker(
        "ExtensionParent",
        { startTime: start },
        `${id}, api_call: ${data.path}`
      );
    }
  },

  async recvAPICall(data, { actor }) {
    let context = this.getContextById(data.childId);
    let target = actor.browsingContext?.top.embedderElement;

    verifyActorForContext(actor, context);

    let reply = result => {
      if (target && !context.parentMessageManager) {
        Services.console.logStringMessage(
          "Cannot send function call result: other side closed connection " +
            `(call data: ${uneval({ path: data.path, args: data.args })})`
        );
        return;
      }

      this.conduit.sendCallResult(data.childId, {
        childId: data.childId,
        callId: data.callId,
        path: data.path,
        ...result,
      });
    };

    try {
      if (
        context.isBackgroundContext &&
        !context.extension.persistentBackground
      ) {
        context.extension.emit("background-script-reset-idle", {
          reason: "parentapicall",
          path: data.path,
        });
      }

      let args = data.args;
      let { isHandlingUserInput = false } = data.options || {};
      let pendingBrowser = context.pendingEventBrowser;
      let fun = await context.apiCan.asyncFindAPIPath(data.path);
      let result = this.callAndLog(context, data, () => {
        return context.withPendingBrowser(pendingBrowser, () =>
          context.withCallContextData({ isHandlingUserInput }, () =>
            fun(...args)
          )
        );
      });

      if (data.callId) {
        result = result || Promise.resolve();

        result.then(
          result => {
            result = result instanceof SpreadArgs ? [...result] : [result];

            let holder = new StructuredCloneHolder(
              `ExtensionParent/${context.extension.id}/recvAPICall/${data.path}`,
              null,
              result
            );

            reply({ result: holder });
          },
          error => {
            error = context.normalizeError(error);
            reply({
              error: { message: error.message, fileName: error.fileName },
            });
          }
        );
      }
    } catch (e) {
      if (data.callId) {
        let error = context.normalizeError(e);
        reply({ error: { message: error.message } });
      } else {
        Cu.reportError(e);
      }
    }
  },

  async recvAddListener(data, { actor }) {
    let context = this.getContextById(data.childId);

    verifyActorForContext(actor, context);

    let { childId, alreadyLogged = false } = data;
    let handlingUserInput = false;

    let listener = async (...listenerArgs) => {
      let startTime = Cu.now();
      // Extract urgentSend flag to avoid deserializing args holder later.
      let urgentSend = false;
      if (listenerArgs[0] && data.path.startsWith("webRequest.")) {
        urgentSend = listenerArgs[0].urgentSend;
        delete listenerArgs[0].urgentSend;
      }
      let runListenerPromise = this.conduit.queryRunListener(childId, {
        childId,
        handlingUserInput,
        listenerId: data.listenerId,
        path: data.path,
        urgentSend,
        get args() {
          return new StructuredCloneHolder(
            `ExtensionParent/${context.extension.id}/recvAddListener/${data.path}`,
            null,
            listenerArgs
          );
        },
      });
      context.trackRunListenerPromise(runListenerPromise);

      const result = await runListenerPromise;
      let rv = result && result.deserialize(globalThis);
      ChromeUtils.addProfilerMarker(
        "ExtensionParent",
        { startTime },
        `${context.extension.id}, api_event: ${data.path}`
      );
      lazy.ExtensionActivityLog.log(
        context.extension.id,
        context.viewType,
        "api_event",
        data.path,
        { args: listenerArgs, result: rv }
      );
      return rv;
    };

    context.listenerProxies.set(data.listenerId, listener);

    let args = data.args;
    let promise = context.apiCan.asyncFindAPIPath(data.path);

    // Store pending listener additions so we can be sure they're all
    // fully initialize before we consider extension startup complete.
    if (context.isBackgroundContext && context.listenerPromises) {
      const { listenerPromises } = context;
      listenerPromises.add(promise);
      let remove = () => {
        listenerPromises.delete(promise);
      };
      promise.then(remove, remove);
    }

    let handler = await promise;
    if (handler.setUserInput) {
      handlingUserInput = true;
    }
    handler.addListener(listener, ...args);
    if (!alreadyLogged) {
      lazy.ExtensionActivityLog.log(
        context.extension.id,
        context.viewType,
        "api_call",
        `${data.path}.addListener`,
        { args }
      );
    }
  },

  async recvRemoveListener(data) {
    let context = this.getContextById(data.childId);
    let listener = context.listenerProxies.get(data.listenerId);

    let handler = await context.apiCan.asyncFindAPIPath(data.path);
    handler.removeListener(listener);

    let { alreadyLogged = false } = data;
    if (!alreadyLogged) {
      lazy.ExtensionActivityLog.log(
        context.extension.id,
        context.viewType,
        "api_call",
        `${data.path}.removeListener`,
        { args: [] }
      );
    }
  },

  getContextById(childId) {
    let context = this.proxyContexts.get(childId);
    if (!context) {
      throw new Error("WebExtension context not found!");
    }
    return context;
  },
};

ParentAPIManager.init();

/**
 * A hidden window which contains the extension pages that are not visible
 * (i.e., background pages and devtools pages), and is also used by
 * ExtensionDebuggingUtils to contain the browser elements used by the
 * addon debugger to connect to the devtools actors running in the same
 * process of the target extension (and be able to stay connected across
 *  the addon reloads).
 */
class HiddenXULWindow {
  constructor() {
    this._windowlessBrowser = null;
    this.unloaded = false;
    this.waitInitialized = this.initWindowlessBrowser();
  }

  shutdown() {
    if (this.unloaded) {
      throw new Error(
        "Unable to shutdown an unloaded HiddenXULWindow instance"
      );
    }

    this.unloaded = true;

    this.waitInitialized = null;

    if (!this._windowlessBrowser) {
      Cu.reportError("HiddenXULWindow was shut down while it was loading.");
      // initWindowlessBrowser will close windowlessBrowser when possible.
      return;
    }

    this._windowlessBrowser.close();
    this._windowlessBrowser = null;
  }

  get chromeDocument() {
    return this._windowlessBrowser.document;
  }

  /**
   * Private helper that create a HTMLDocument in a windowless browser.
   *
   * @returns {Promise<void>}
   *          A promise which resolves when the windowless browser is ready.
   */
  async initWindowlessBrowser() {
    if (this.waitInitialized) {
      throw new Error("HiddenXULWindow already initialized");
    }

    // The invisible page is currently wrapped in a XUL window to fix an issue
    // with using the canvas API from a background page (See Bug 1274775).
    let windowlessBrowser = Services.appShell.createWindowlessBrowser(true);

    // The windowless browser is a thin wrapper around a docShell that keeps
    // its related resources alive. It implements nsIWebNavigation and
    // forwards its methods to the underlying docShell.
    let chromeShell = windowlessBrowser.docShell;
    let webNav = chromeShell.QueryInterface(Ci.nsIWebNavigation);

    if (lazy.PrivateBrowsingUtils.permanentPrivateBrowsing) {
      let attrs = chromeShell.getOriginAttributes();
      attrs.privateBrowsingId = 1;
      chromeShell.setOriginAttributes(attrs);
    }

    windowlessBrowser.browsingContext.useGlobalHistory = false;
    webNav.loadURI(DUMMY_PAGE_URI, {
      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
    });

    await promiseObserved(
      "chrome-document-global-created",
      win => win.document == webNav.document
    );
    await promiseDocumentLoaded(windowlessBrowser.document);
    if (this.unloaded) {
      windowlessBrowser.close();
      return;
    }
    this._windowlessBrowser = windowlessBrowser;
  }

  /**
   * Creates the browser XUL element that will contain the WebExtension Page.
   *
   * @param {object} xulAttributes
   *        An object that contains the xul attributes to set of the newly
   *        created browser XUL element.
   *
   * @returns {Promise<XULBrowserElement>}
   *          A Promise which resolves to the newly created browser XUL element.
   */
  async createBrowserElement(xulAttributes) {
    if (!xulAttributes || Object.keys(xulAttributes).length === 0) {
      throw new Error("missing mandatory xulAttributes parameter");
    }

    await this.waitInitialized;

    const chromeDoc = this.chromeDocument;

    const browser = chromeDoc.createXULElement("browser");
    browser.setAttribute("type", "content");
    browser.setAttribute("disableglobalhistory", "true");
    browser.setAttribute("messagemanagergroup", "webext-browsers");
    browser.setAttribute("manualactiveness", "true");

    for (const [name, value] of Object.entries(xulAttributes)) {
      if (value != null) {
        browser.setAttribute(name, value);
      }
    }

    let awaitFrameLoader;

    if (browser.getAttribute("remote") === "true") {
      awaitFrameLoader = promiseEvent(browser, "XULFrameLoaderCreated");
    }

    chromeDoc.documentElement.appendChild(browser);

    // Forcibly flush layout so that we get a pres shell soon enough, see
    // bug 1274775.
    browser.getBoundingClientRect();
    await awaitFrameLoader;

    // FIXME(emilio): This unconditionally active frame seems rather
    // unfortunate, but matches previous behavior.
    browser.docShellIsActive = true;

    return browser;
  }
}

const SharedWindow = {
  _window: null,
  _count: 0,

  acquire() {
    if (this._window == null) {
      if (this._count != 0) {
        throw new Error(
          `Shared window already exists with count ${this._count}`
        );
      }

      this._window = new HiddenXULWindow();
    }

    this._count++;
    return this._window;
  },

  release() {
    if (this._count < 1) {
      throw new Error(`Releasing shared window with count ${this._count}`);
    }

    this._count--;
    if (this._count == 0) {
      this._window.shutdown();
      this._window = null;
    }
  },
};

/**
 * This is a base class used by the ext-backgroundPage and ext-devtools API implementations
 * to inherits the shared boilerplate code needed to create a parent document for the hidden
 * extension pages (e.g. the background page, the devtools page) in the BackgroundPage and
 * DevToolsPage classes.
 */
class HiddenExtensionPage {
  /**
   * @param {Extension} extension
   *        The Extension which owns the hidden extension page created (used to decide
   *        if the hidden extension page parent doc is going to be a windowlessBrowser or
   *        a visible XUL window).
   * @param {string} viewType
   *        The viewType of the WebExtension page that is going to be loaded
   *        in the created browser element (e.g. "background" or "devtools_page").
   */
  constructor(extension, viewType) {
    if (!extension || !viewType) {
      throw new Error("extension and viewType parameters are mandatory");
    }

    this.extension = extension;
    this.viewType = viewType;
    this.browser = null;
    this.unloaded = false;
  }

  /**
   * Destroy the created parent document.
   */
  shutdown() {
    if (this.unloaded) {
      throw new Error(
        "Unable to shutdown an unloaded HiddenExtensionPage instance"
      );
    }

    this.unloaded = true;

    if (this.browser) {
      this._releaseBrowser();
    }
  }

  _releaseBrowser() {
    this.browser.remove();
    this.browser = null;
    SharedWindow.release();
  }

  /**
   * Creates the browser XUL element that will contain the WebExtension Page.
   *
   * @returns {Promise<XULElement>}
   *          A Promise which resolves to the newly created browser XUL element.
   */
  async createBrowserElement() {
    if (this.browser) {
      throw new Error("createBrowserElement called twice");
    }

    let window = SharedWindow.acquire();
    try {
      this.browser = await window.createBrowserElement({
        "webextension-view-type": this.viewType,
        remote: this.extension.remote ? "true" : null,
        remoteType: this.extension.remoteType,
        initialBrowsingContextGroupId: this.extension.browsingContextGroupId,
      });
    } catch (e) {
      SharedWindow.release();
      throw e;
    }

    if (this.unloaded) {
      this._releaseBrowser();
      throw new Error("Extension shut down before browser element was created");
    }

    return this.browser;
  }
}

/** @typedef {import("resource://devtools/server/actors/descriptors/webextension.js")
              .WebExtensionDescriptorActor} WebExtensionDescriptorActor */

/**
 * This object provides utility functions needed by the devtools actors to
 * be able to connect and debug an extension (which can run in the main or in
 * a child extension process).
 */
const DebugUtils = {
  // A lazily created hidden XUL window, which contains the browser elements
  // which are used to connect the webextension patent actor to the extension process.
  hiddenXULWindow: null,

  /** @type {Map<string, Promise<XULBrowserElement> & { browser: XULBrowserElement }>} */
  debugBrowserPromises: new Map(),
  /** @type {WeakMap<Promise<XULBrowserElement>, Set<WebExtensionDescriptorActor>>} */
  debugActors: new DefaultWeakMap(() => new Set()),

  _extensionUpdatedWatcher: null,
  watchExtensionUpdated() {
    if (!this._extensionUpdatedWatcher) {
      // Watch the updated extension objects.
      this._extensionUpdatedWatcher = async (evt, extension) => {
        const browserPromise = this.debugBrowserPromises.get(extension.id);
        if (browserPromise) {
          const browser = await browserPromise;
          if (
            browser.isRemoteBrowser !== extension.remote &&
            this.debugBrowserPromises.get(extension.id) === browserPromise
          ) {
            // If the cached browser element is not anymore of the same
            // remote type of the extension, remove it.
            this.debugBrowserPromises.delete(extension.id);
            browser.remove();
          }
        }
      };

      apiManager.on("ready", this._extensionUpdatedWatcher);
    }
  },

  unwatchExtensionUpdated() {
    if (this._extensionUpdatedWatcher) {
      apiManager.off("ready", this._extensionUpdatedWatcher);
      delete this._extensionUpdatedWatcher;
    }
  },

  getExtensionManifestWarnings(id) {
    const addon = GlobalManager.extensionMap.get(id);
    if (addon) {
      return addon.warnings;
    }
    return [];
  },

  /**
   * Determine if the extension does have a non-persistent background script
   * (either an event page or a background service worker):
   *
   * Based on this the DevTools client will determine if this extension should provide
   * to the extension developers a button to forcefully terminate the background
   * script.
   *
   * @param {string} addonId
   *   The id of the addon
   *
   * @returns {void|boolean}
   *   - undefined => does not apply (no background script in the manifest)
   *   - true => the background script is persistent.
   *   - false => the background script is an event page or a service worker.
   */
  hasPersistentBackgroundScript(addonId) {
    const policy = WebExtensionPolicy.getByID(addonId);

    // The addon doesn't have any background script or we
    // can't be sure yet.
    if (
      policy?.extension?.type !== "extension" ||
      !policy?.extension?.manifest?.background
    ) {
      return undefined;
    }

    return policy.extension.persistentBackground;
  },

  /**
   * Determine if the extension background page is running.
   *
   * Based on this the DevTools client will show the status of the background
   * script in about:debugging.
   *
   * @param {string} addonId
   *   The id of the addon
   *
   * @returns {void|boolean}
   *   - undefined => does not apply (no background script in the manifest)
   *   - true => the background script is running.
   *   - false => the background script is stopped.
   */
  isBackgroundScriptRunning(addonId) {
    const policy = WebExtensionPolicy.getByID(addonId);

    // The addon doesn't have any background script or we
    // can't be sure yet.
    if (!(this.hasPersistentBackgroundScript(addonId) === false)) {
      return undefined;
    }

    const views = policy?.extension?.views || [];
    for (const view of views) {
      if (
        view.viewType === "background" ||
        (view.viewType === "background_worker" && !view.unloaded)
      ) {
        return true;
      }
    }

    return false;
  },

  async terminateBackgroundScript(addonId) {
    // Terminate the background if the extension does have
    // a non-persistent background script (event page or background
    // service worker).
    if (this.hasPersistentBackgroundScript(addonId) === false) {
      const policy = WebExtensionPolicy.getByID(addonId);
      // When the event page is being terminated through the Devtools
      // action, we should terminate it even if there are DevTools
      // toolboxes attached to the extension.
      return policy.extension.terminateBackground({
        ignoreDevToolsAttached: true,
      });
    }
    throw Error(`Unable to terminate background script for ${addonId}`);
  },

  /**
   * Determine whether a devtools toolbox attached to the extension.
   *
   * This method is called by the background page idle timeout handler,
   * to inhibit terminating the event page when idle while the extension
   * developer is debugging the extension through the Addon Debugging window
   * (similarly to how service workers are kept alive while the devtools are
   * attached).
   *
   * @param {string} id
   *        The id of the extension.
   *
   * @returns {boolean}
   *          true when a devtools toolbox is attached to an extension with
   *          the given id, false otherwise.
   */
  hasDevToolsAttached(id) {
    return this.debugBrowserPromises.has(id);
  },

  /**
   * Retrieve a XUL browser element which has been configured to be able to connect
   * the devtools actor with the process where the extension is running.
   *
   * @param {WebExtensionDescriptorActor} webExtensionParentActor
   *        The devtools actor that is retrieving the browser element.
   *
   * @returns {Promise<XULBrowserElement>}
   *          A promise which resolves to the configured browser XUL element.
   */
  async getExtensionProcessBrowser(webExtensionParentActor) {
    const extensionId = webExtensionParentActor.addonId;
    const extension = GlobalManager.getExtension(extensionId);
    if (!extension) {
      throw new Error(`Extension not found: ${extensionId}`);
    }

    const createBrowser = () => {
      if (!this.hiddenXULWindow) {
        this.hiddenXULWindow = new HiddenXULWindow();
        this.watchExtensionUpdated();
      }

      return this.hiddenXULWindow.createBrowserElement({
        "webextension-addon-debug-target": extensionId,
        remote: extension.remote ? "true" : null,
        remoteType: extension.remoteType,
        initialBrowsingContextGroupId: extension.browsingContextGroupId,
      });
    };

    let browserPromise = this.debugBrowserPromises.get(extensionId);

    // Create a new promise if there is no cached one in the map.
    if (!browserPromise) {
      browserPromise = createBrowser();
      this.debugBrowserPromises.set(extensionId, browserPromise);
      browserPromise.then(browser => {
        browserPromise.browser = browser;
      });
      browserPromise.catch(e => {
        Cu.reportError(e);
        this.debugBrowserPromises.delete(extensionId);
      });
    }

    this.debugActors.get(browserPromise).add(webExtensionParentActor);

    return browserPromise;
  },

  getFrameLoader(extensionId) {
    let promise = this.debugBrowserPromises.get(extensionId);
    return promise && promise.browser && promise.browser.frameLoader;
  },

  /**
   * Given the devtools actor that has retrieved an addon debug browser element,
   * it destroys the XUL browser element, and it also destroy the hidden XUL window
   * if it is not currently needed.
   *
   * @param {WebExtensionDescriptorActor} webExtensionParentActor
   *        The devtools actor that has retrieved an addon debug browser element.
   */
  async releaseExtensionProcessBrowser(webExtensionParentActor) {
    const extensionId = webExtensionParentActor.addonId;
    const browserPromise = this.debugBrowserPromises.get(extensionId);

    if (browserPromise) {
      const actorsSet = this.debugActors.get(browserPromise);
      actorsSet.delete(webExtensionParentActor);
      if (actorsSet.size === 0) {
        this.debugActors.delete(browserPromise);
        this.debugBrowserPromises.delete(extensionId);
        await browserPromise.then(browser => browser.remove());
      }
    }

    if (this.debugBrowserPromises.size === 0 && this.hiddenXULWindow) {
      this.hiddenXULWindow.shutdown();
      this.hiddenXULWindow = null;
      this.unwatchExtensionUpdated();
    }
  },
};

/**
 * Returns a Promise which resolves with the message data when the given message
 * was received by the message manager. The promise is rejected if the message
 * manager was closed before a message was received.
 *
 * @param {MessageListenerManager} messageManager
 *        The message manager on which to listen for messages.
 * @param {string} messageName
 *        The message to listen for.
 * @returns {Promise<*>}
 */
function promiseMessageFromChild(messageManager, messageName) {
  return new Promise((resolve, reject) => {
    let unregister;
    function listener(message) {
      unregister();
      resolve(message.data);
    }
    function observer(subject) {
      if (subject === messageManager) {
        unregister();
        reject(
          new Error(
            `Message manager was disconnected before receiving ${messageName}`
          )
        );
      }
    }
    unregister = () => {
      Services.obs.removeObserver(observer, "message-manager-close");
      messageManager.removeMessageListener(messageName, listener);
    };
    messageManager.addMessageListener(messageName, listener);
    Services.obs.addObserver(observer, "message-manager-close");
  });
}

// This should be called before browser.loadURI is invoked.
async function promiseBackgroundViewLoaded(browser) {
  let { childId } = await promiseMessageFromChild(
    browser.messageManager,
    "Extension:BackgroundViewLoaded"
  );
  if (childId) {
    return ParentAPIManager.getContextById(childId);
  }
}

/**
 * This helper is used to subscribe a listener (e.g. in the ext-devtools API implementation)
 * to be called for every ExtensionProxyContext created for an extension page given
 * its related extension, viewType and browser element (both the top level context and any context
 * created for the extension urls running into its iframe descendants).
 *
 * @param {object} params
 * @param {object} params.extension
 *        The Extension on which we are going to listen for the newly created ExtensionProxyContext.
 * @param {string} params.viewType
 *        The viewType of the WebExtension page that we are watching (e.g. "background" or
 *        "devtools_page").
 * @param {XULElement} params.browser
 *        The browser element of the WebExtension page that we are watching.
 * @param {Function} onExtensionProxyContextLoaded
 *        The callback that is called when a new context has been loaded (as `callback(context)`);
 *
 * @returns {Function}
 *          Unsubscribe the listener.
 */
function watchExtensionProxyContextLoad(
  { extension, viewType, browser },
  onExtensionProxyContextLoaded
) {
  if (typeof onExtensionProxyContextLoaded !== "function") {
    throw new Error("Missing onExtensionProxyContextLoaded handler");
  }

  const listener = (event, context) => {
    if (context.viewType == viewType && context.xulBrowser == browser) {
      onExtensionProxyContextLoaded(context);
    }
  };

  extension.on("extension-proxy-context-load", listener);

  return () => {
    extension.off("extension-proxy-context-load", listener);
  };
}

/**
 * This helper is used to subscribe a listener (e.g. in the ext-backgroundPage)
 * to be called for every ExtensionProxyContext created for an extension
 * background service worker given its related extension.
 *
 * @param {object} params
 * @param {object} params.extension
 *        The Extension on which we are going to listen for the newly created ExtensionProxyContext.
 * @param {Function} onExtensionWorkerContextLoaded
 *        The callback that is called when the worker script has been fully loaded (as `callback(context)`);
 *
 * @returns {Function}
 *          Unsubscribe the listener.
 */
function watchExtensionWorkerContextLoaded(
  { extension },
  onExtensionWorkerContextLoaded
) {
  if (typeof onExtensionWorkerContextLoaded !== "function") {
    throw new Error("Missing onExtensionWorkerContextLoaded handler");
  }

  const listener = (event, context) => {
    if (context.viewType == "background_worker") {
      onExtensionWorkerContextLoaded(context);
    }
  };

  extension.on("extension-proxy-context-load:completed", listener);

  return () => {
    extension.off("extension-proxy-context-load:completed", listener);
  };
}

// Manages icon details for toolbar buttons in the |pageAction| and
// |browserAction| APIs.
let IconDetails = {
  DEFAULT_ICON: "chrome://mozapps/skin/extensions/extensionGeneric.svg",

  // WeakMap<Extension -> Map<url-string -> Map<iconType-string -> object>>>
  iconCache: new DefaultWeakMap(() => {
    return new DefaultMap(() => new DefaultMap(() => new Map()));
  }),

  // Normalizes the various acceptable input formats into an object
  // with icon size as key and icon URL as value.
  //
  // If a context is specified (function is called from an extension):
  // Throws an error if an invalid icon size was provided or the
  // extension is not allowed to load the specified resources.
  //
  // If no context is specified, instead of throwing an error, this
  // function simply logs a warning message.
  normalize(details, extension, context = null) {
    if (!details.imageData && details.path != null) {
      // Pick a cache key for the icon paths. If the path is a string,
      // use it directly. Otherwise, stringify the path object.
      let key = details.path;
      if (typeof key !== "string") {
        key = uneval(key);
      }

      let icons = this.iconCache
        .get(extension)
        .get(context && context.uri.spec)
        .get(details.iconType);

      let icon = icons.get(key);
      if (!icon) {
        icon = this._normalize(details, extension, context);
        icons.set(key, icon);
      }
      return icon;
    }

    return this._normalize(details, extension, context);
  },

  _normalize(details, extension, context = null) {
    let result = {};

    try {
      let { imageData, path, themeIcons } = details;

      if (imageData) {
        if (typeof imageData == "string") {
          imageData = { 19: imageData };
        }

        for (let size of Object.keys(imageData)) {
          result[size] = imageData[size];
        }
      }

      let baseURI = context ? context.uri : extension.baseURI;

      if (path != null) {
        if (typeof path != "object") {
          path = { 19: path };
        }

        for (let size of Object.keys(path)) {
          let url = path[size];
          if (url) {
            url = baseURI.resolve(path[size]);

            // The Chrome documentation specifies these parameters as
            // relative paths. We currently accept absolute URLs as well,
            // which means we need to check that the extension is allowed
            // to load them. This will throw an error if it's not allowed.
            this._checkURL(url, extension);
          }
          result[size] = url || this.DEFAULT_ICON;
        }
      }

      if (themeIcons) {
        themeIcons.forEach(({ size, light, dark }) => {
          let lightURL = baseURI.resolve(light);
          let darkURL = baseURI.resolve(dark);

          this._checkURL(lightURL, extension);
          this._checkURL(darkURL, extension);

          let defaultURL = result[size] || result[19]; // always fallback to default first
          result[size] = {
            default: defaultURL || darkURL, // Fallback to the dark url if no default is specified.
            light: lightURL,
            dark: darkURL,
          };
        });
      }
    } catch (e) {
      // Function is called from extension code, delegate error.
      if (context) {
        throw e;
      }
      // If there's no context, it's because we're handling this
      // as a manifest directive. Log a warning rather than
      // raising an error.
      extension.manifestError(`Invalid icon data: ${e}`);
    }

    return result;
  },

  // Checks if the extension is allowed to load the given URL with the specified principal.
  // This will throw an error if the URL is not allowed.
  _checkURL(url, extension) {
    if (!extension.checkLoadURL(url, { allowInheritsPrincipal: true })) {
      throw new ExtensionError(`Illegal URL ${url}`);
    }
  },

  // Returns the appropriate icon URL for the given icons object and the
  // screen resolution of the given window.
  getPreferredIcon(icons, extension, size = 16) {
    const DEFAULT = "chrome://mozapps/skin/extensions/extensionGeneric.svg";

    let bestSize = null;
    if (icons[size]) {
      bestSize = size;
    } else if (icons[2 * size]) {
      bestSize = 2 * size;
    } else {
      let sizes = Object.keys(icons)
        .map(key => parseInt(key, 10))
        .sort((a, b) => a - b);

      bestSize = sizes.find(candidate => candidate > size) || sizes.pop();
    }

    if (bestSize) {
      return { size: bestSize, icon: icons[bestSize] || DEFAULT };
    }

    return { size, icon: DEFAULT };
  },

  // These URLs should already be properly escaped, but make doubly sure CSS
  // string escape characters are escaped here, since they could lead to a
  // sandbox break.
  escapeUrl(url) {
    return url.replace(/[\\\s"]/g, encodeURIComponent);
  },
};

class CacheStore {
  constructor(storeName) {
    this.storeName = storeName;
  }

  async getStore(path = null) {
    let data = await StartupCache.dataPromise;

    let store = data.get(this.storeName);
    if (!store) {
      store = new Map();
      data.set(this.storeName, store);
    }

    let key = path;
    if (Array.isArray(path)) {
      for (let elem of path.slice(0, -1)) {
        let next = store.get(elem);
        if (!next) {
          next = new Map();
          store.set(elem, next);
        }
        store = next;
      }
      key = path[path.length - 1];
    }

    return [store, key];
  }

  async get(path, createFunc) {
    let [store, key] = await this.getStore(path);

    let result = store.get(key);

    if (result === undefined) {
      result = await createFunc(path);
      store.set(key, result);
      StartupCache.save();
    }

    return result;
  }

  async set(path, value) {
    let [store, key] = await this.getStore(path);

    store.set(key, value);
    StartupCache.save();
  }

  async getAll() {
    let [store] = await this.getStore();

    return new Map(store);
  }

  async delete(path) {
    let [store, key] = await this.getStore(path);

    if (store.delete(key)) {
      StartupCache.save();
    }
  }
}

// A cache to support faster initialization of extensions at browser startup.
// All cached data is removed when the browser is updated.
// Extension-specific data is removed when the add-on is updated.
var StartupCache = {
  _ensureDirectoryPromise: null,
  _saveTask: null,

  _ensureDirectory() {
    if (this._ensureDirectoryPromise === null) {
      this._ensureDirectoryPromise = IOUtils.makeDirectory(
        PathUtils.parent(this.file),
        {
          ignoreExisting: true,
          createAncestors: true,
        }
      );
    }

    return this._ensureDirectoryPromise;
  },

  // When the application version changes, this file is removed by
  // RemoveComponentRegistries in nsAppRunner.cpp.
  file: PathUtils.join(
    Services.dirsvc.get("ProfLD", Ci.nsIFile).path,
    "startupCache",
    "webext.sc.lz4"
  ),

  async _saveNow() {
    let data = new Uint8Array(lazy.aomStartup.encodeBlob(this._data));
    await this._ensureDirectoryPromise;
    await IOUtils.write(this.file, data, { tmpPath: `${this.file}.tmp` });

    Glean.extensions.startupCacheWriteBytelength.set(data.byteLength);
  },

  save() {
    this._ensureDirectory();

    if (!this._saveTask) {
      this._saveTask = new lazy.DeferredTask(() => this._saveNow(), 5000);

      IOUtils.profileBeforeChange.addBlocker(
        "Flush WebExtension StartupCache",
        async () => {
          await this._saveTask.finalize();
          this._saveTask = null;
        }
      );
    }

    return this._saveTask.arm();
  },

  _data: null,
  async _readData() {
    let result = new Map();
    try {
      Glean.extensions.startupCacheLoadTime.start();
      let { buffer } = await IOUtils.read(this.file);

      result = lazy.aomStartup.decodeBlob(buffer);
      Glean.extensions.startupCacheLoadTime.stop();
    } catch (e) {
      Glean.extensions.startupCacheLoadTime.cancel();
      if (!DOMException.isInstance(e) || e.name !== "NotFoundError") {
        Cu.reportError(e);
      }
      let error = lazy.getErrorNameForTelemetry(e);
      Glean.extensions.startupCacheReadErrors[error].add(1);
    }

    this._data = result;
    return result;
  },

  get dataPromise() {
    if (!this._dataPromise) {
      this._dataPromise = this._readData();
    }
    return this._dataPromise;
  },

  clearAddonData(id) {
    return Promise.all([
      this.general.delete(id),
      this.locales.delete(id),
      this.manifests.delete(id),
      this.permissions.delete(id),
    ]).catch(() => {
      // Ignore the error. It happens when we try to flush the add-on
      // data after the AddonManager has flushed the entire startup cache.
    });
  },

  observe(subject, topic) {
    if (topic === "startupcache-invalidate") {
      this._data = new Map();
      this._dataPromise = Promise.resolve(this._data);
    }
  },

  get(extension, path, createFunc) {
    return this.general.get(
      [extension.id, extension.version, ...path],
      createFunc
    );
  },

  delete(extension, path) {
    return this.general.delete([extension.id, extension.version, ...path]);
  },

  general: new CacheStore("general"),
  locales: new CacheStore("locales"),
  manifests: new CacheStore("manifests"),
  other: new CacheStore("other"),
  permissions: new CacheStore("permissions"),
  schemas: new CacheStore("schemas"),
};

Services.obs.addObserver(StartupCache, "startupcache-invalidate");

export var ExtensionParent = {
  GlobalManager,
  HiddenExtensionPage,
  IconDetails,
  ParentAPIManager,
  StartupCache,
  WebExtensionPolicy,
  apiManager,
  promiseBackgroundViewLoaded,
  watchExtensionProxyContextLoad,
  watchExtensionWorkerContextLoaded,
  DebugUtils,
};

// browserPaintedPromise and browserStartupPromise are promises that
// resolve after the first browser window is painted and after browser
// windows have been restored, respectively. Alternatively,
// browserStartupPromise also resolves from the extensions-late-startup
// notification sent by Firefox Reality on desktop platforms, because it
// doesn't support SessionStore.
// _resetStartupPromises should only be called from outside this file in tests.
ExtensionParent._resetStartupPromises = () => {
  ExtensionParent.browserPaintedPromise = promiseObserved(
    "browser-delayed-startup-finished"
  ).then(() => {});
  ExtensionParent.browserStartupPromise = Promise.race([
    promiseObserved("sessionstore-windows-restored"),
    promiseObserved("extensions-late-startup"),
  ]).then(() => {});
};
ExtensionParent._resetStartupPromises();

ChromeUtils.defineLazyGetter(ExtensionParent, "PlatformInfo", () => {
  return Object.freeze({
    os: (function () {
      let os = AppConstants.platform;
      if (os == "macosx") {
        os = "mac";
      }
      return os;
    })(),
    arch: (function () {
      let abi = Services.appinfo.XPCOMABI;
      let [arch] = abi.split("-");
      if (arch == "x86") {
        arch = "x86-32";
      } else if (arch == "x86_64") {
        arch = "x86-64";
      }
      return arch;
    })(),
  });
});
PK
!<s����+modules/ExtensionPermissionMessages.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Localization object holding the fluent definitions of permission descriptions
 * of WebExtension APIs defined in toolkit.
 *
 * This is exported to allow builds (e.g. Thunderbird) to extend or modify the
 * object via its addResourceIds() method.
 */
export const PERMISSION_L10N = new Localization(
  [
    "toolkit/global/extensions.ftl",
    "toolkit/global/extensionPermissions.ftl",
    "branding/brand.ftl",
  ],
  true
);

/**
 * List of permissions that are associated with a permission message.
 *
 * Keep this list in sync with:
 * - The messages in `toolkit/locales/en-US/toolkit/global/extensionPermissions.ftl`
 * - `permissionToTranslation` at https://github.com/mozilla-mobile/firefox-android/blob/d9c08c387917e3e53963386ad53229e69d52da6e/android-components/components/feature/addons/src/main/java/mozilla/components/feature/addons/Addon.kt#L174-L206
 * - https://extensionworkshop.com/documentation/develop/request-the-right-permissions/#advised-permissions
 * - https://support.mozilla.org/en-US/kb/permission-request-messages-firefox-extensions
 *
 * This is exported to allow builds (e.g. Thunderbird) to extend or modify the set.
 */
export const PERMISSIONS_WITH_MESSAGE = new Set([
  "bookmarks",
  "browserSettings",
  "browsingData",
  "clipboardRead",
  "clipboardWrite",
  "declarativeNetRequest",
  "declarativeNetRequestFeedback",
  "devtools",
  "downloads",
  "downloads.open",
  "find",
  "geolocation",
  "history",
  "management",
  "nativeMessaging",
  "notifications",
  "pkcs11",
  "privacy",
  "proxy",
  "sessions",
  "tabs",
  "tabHide",
  "topSites",
  "webNavigation",
]);

/**
 * Overrides for permission description l10n identifiers,
 * which by default use the pattern `webext-perms-description-${permission}`
 * where `permission` is sanitized to be a valid Fluent identifier.
 *
 * This is exported to allow builds (e.g. Thunderbird) to extend or modify the map.
 */
export const PERMISSION_L10N_ID_OVERRIDES = new Map();

/**
 * Maps a permission name to its l10n identifier.
 *
 * Returns `null` for permissions not in `PERMISSIONS_WITH_MESSAGE`.
 *
 * The default `webext-perms-description-${permission}` mapping
 * may be overridden by entries in `PERMISSION_L10N_ID_OVERRIDES`.
 *
 * @param {string} permission
 * @returns {string | null}
 */
export function permissionToL10nId(permission) {
  if (!PERMISSIONS_WITH_MESSAGE.has(permission)) {
    return null;
  }

  if (PERMISSION_L10N_ID_OVERRIDES.has(permission)) {
    return PERMISSION_L10N_ID_OVERRIDES.get(permission);
  }

  // Sanitize input to end up with a valid l10n id.
  // E.g. "<all_urls>" to "all-urls", "downloads.open" to "downloads-open".
  const sanitized = permission
    .replace(/[^a-zA-Z0-9]+/g, "-")
    .replace(/^-|-$/g, "");

  return `webext-perms-description-${sanitized}`;
}
PK
!<��u��~�~$modules/ExtensionPermissions.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

/** @type {Lazy} */
const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs",
  Extension: "resource://gre/modules/Extension.sys.mjs",
  ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs",
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
  JSONFile: "resource://gre/modules/JSONFile.sys.mjs",
  KeyValueService: "resource://gre/modules/kvstore.sys.mjs",
});

ChromeUtils.defineLazyGetter(
  lazy,
  "StartupCache",
  () => lazy.ExtensionParent.StartupCache
);

ChromeUtils.defineLazyGetter(
  lazy,
  "Management",
  () => lazy.ExtensionParent.apiManager
);

function emptyPermissions() {
  return { permissions: [], origins: [] };
}

const DEFAULT_VALUE = JSON.stringify(emptyPermissions());

const KEY_PREFIX = "id-";

// This is the old preference file pre-migration to rkv.
const OLD_JSON_FILENAME = "extension-preferences.json";
// This is the old path to the rkv store dir (which used to be shared with ExtensionScriptingStore).
const OLD_RKV_DIRNAME = "extension-store";
// This is the new path to the rkv store dir.
const RKV_DIRNAME = "extension-store-permissions";

const VERSION_KEY = "_version";

const VERSION_VALUE = 1;

const WEB_SCHEMES = ["http", "https"];

// Bug 1646182: remove once we fully migrate to rkv
let prefs;

// Bug 1646182: remove once we fully migrate to rkv
class LegacyPermissionStore {
  async lazyInit() {
    if (!this._initPromise) {
      this._initPromise = this._init();
    }
    return this._initPromise;
  }

  async _init() {
    let path = PathUtils.join(
      Services.dirsvc.get("ProfD", Ci.nsIFile).path,
      OLD_JSON_FILENAME
    );

    prefs = new lazy.JSONFile({ path });
    prefs.data = {};

    try {
      prefs.data = await IOUtils.readJSON(path);
    } catch (e) {
      if (!(DOMException.isInstance(e) && e.name == "NotFoundError")) {
        Cu.reportError(e);
      }
    }
  }

  async has(extensionId) {
    await this.lazyInit();
    return !!prefs.data[extensionId];
  }

  async get(extensionId) {
    await this.lazyInit();

    let perms = prefs.data[extensionId];
    if (!perms) {
      perms = emptyPermissions();
    }

    return perms;
  }

  async put(extensionId, permissions) {
    await this.lazyInit();
    prefs.data[extensionId] = permissions;
    prefs.saveSoon();
  }

  async delete(extensionId) {
    await this.lazyInit();
    if (prefs.data[extensionId]) {
      delete prefs.data[extensionId];
      prefs.saveSoon();
    }
  }

  async uninitForTest() {
    if (!this._initPromise) {
      return;
    }

    await this._initPromise;
    await prefs.finalize();
    prefs = null;
    this._initPromise = null;
  }

  async resetVersionForTest() {
    throw new Error("Not supported");
  }
}

class PermissionStore {
  _shouldMigrateFromOldKVStorePath = AppConstants.NIGHTLY_BUILD;

  async _init() {
    const storePath = lazy.FileUtils.getDir("ProfD", [RKV_DIRNAME]).path;
    // Make sure the folder exists
    await IOUtils.makeDirectory(storePath, { ignoreExisting: true });
    this._store = await lazy.KeyValueService.getOrCreateWithOptions(
      storePath,
      "permissions",
      { strategy: lazy.KeyValueService.RecoveryStrategy.RENAME }
    );
    if (!(await this._store.has(VERSION_KEY))) {
      // If _shouldMigrateFromOldKVStorePath is true (default only on Nightly channel
      // where the rkv store has been enabled by default for a while), we need to check
      // if we would need to import data from the old kvstore path (ProfD/extensions-store)
      // first, and fallback to try to import from the JSONFile if there was no data in
      // the old kvstore path.
      // NOTE: _shouldMigrateFromOldKVStorePath is also explicitly set to true in unit tests
      // that are meant to explicitly cover this path also when running on on non-Nightly channels.
      if (this._shouldMigrateFromOldKVStorePath) {
        // Try to import data from the old kvstore path (ProfD/extensions-store).
        await this.maybeImportFromOldKVStorePath();
        if (!(await this._store.has(VERSION_KEY))) {
          // There was no data in the old kvstore path, migrate any data
          // available from the LegacyPermissionStore JSONFile if any.
          await this.maybeMigrateDataFromOldJSONFile();
        }
      } else {
        // On non-Nightly channels, where LegacyPermissionStore was still the
        // only backend ever enabled, try to import permissions data from the
        // legacy JSONFile, if any data is available there.
        await this.maybeMigrateDataFromOldJSONFile();
      }
    }
  }

  lazyInit() {
    if (!this._initPromise) {
      this._initPromise = this._init();
    }
    return this._initPromise;
  }

  validateMigratedData(json) {
    let data = {};
    for (let [extensionId, permissions] of Object.entries(json)) {
      // If both arrays are empty there's no need to include the value since
      // it's the default
      if (
        "permissions" in permissions &&
        "origins" in permissions &&
        (permissions.permissions.length || permissions.origins.length)
      ) {
        data[extensionId] = permissions;
      }
    }
    return data;
  }

  async maybeMigrateDataFromOldJSONFile() {
    let migrationWasSuccessful = false;
    let oldStore = PathUtils.join(
      Services.dirsvc.get("ProfD", Ci.nsIFile).path,
      OLD_JSON_FILENAME
    );
    try {
      await this.migrateFrom(oldStore);
      migrationWasSuccessful = true;
    } catch (e) {
      if (!(DOMException.isInstance(e) && e.name == "NotFoundError")) {
        Cu.reportError(e);
      }
    }

    await this._store.put(VERSION_KEY, VERSION_VALUE);

    if (migrationWasSuccessful) {
      IOUtils.remove(oldStore);
    }
  }

  async maybeImportFromOldKVStorePath() {
    try {
      const oldStorePath = lazy.FileUtils.getDir("ProfD", [
        OLD_RKV_DIRNAME,
      ]).path;
      if (!(await IOUtils.exists(oldStorePath))) {
        return;
      }
      const oldStore = await lazy.KeyValueService.getOrCreate(
        oldStorePath,
        "permissions"
      );
      const enumerator = await oldStore.enumerate();
      const kvpairs = [];
      while (enumerator.hasMoreElements()) {
        const { key, value } = enumerator.getNext();
        kvpairs.push([key, value]);
      }

      // NOTE: we don't add a VERSION_KEY entry explicitly here because
      // if the database was not empty the VERSION_KEY is already set to
      // 1 and will be copied into the new file as part of the pairs
      // written below (along with the entries for the actual extensions
      // permissions).
      if (kvpairs.length) {
        await this._store.writeMany(kvpairs);
      }

      // NOTE: the old rkv store path used to be shared with the
      // ExtensionScriptingStore, and so we are not removing the old
      // rkv store dir here (that is going to be left to a separate
      // migration we will be adding to ExtensionScriptingStore).
    } catch (err) {
      Cu.reportError(err);
    }
  }

  async migrateFrom(oldStore) {
    // Some other migration job might have started and not completed, let's
    // start from scratch
    await this._store.clear();

    let json = await IOUtils.readJSON(oldStore);
    let data = this.validateMigratedData(json);

    if (data) {
      let entries = Object.entries(data).map(([extensionId, permissions]) => [
        this.makeKey(extensionId),
        JSON.stringify(permissions),
      ]);
      if (entries.length) {
        await this._store.writeMany(entries);
      }
    }
  }

  makeKey(extensionId) {
    // We do this so that the extensionId field cannot clash with internal
    // fields like `_version`
    return KEY_PREFIX + extensionId;
  }

  async has(extensionId) {
    await this.lazyInit();
    return this._store.has(this.makeKey(extensionId));
  }

  async get(extensionId) {
    await this.lazyInit();
    return this._store
      .get(this.makeKey(extensionId), DEFAULT_VALUE)
      .then(JSON.parse);
  }

  async put(extensionId, permissions) {
    await this.lazyInit();
    return this._store.put(
      this.makeKey(extensionId),
      JSON.stringify(permissions)
    );
  }

  async delete(extensionId) {
    await this.lazyInit();
    return this._store.delete(this.makeKey(extensionId));
  }

  async resetVersionForTest() {
    await this.lazyInit();
    return this._store.delete(VERSION_KEY);
  }

  async uninitForTest() {
    // Nothing special to do to unitialize, let's just
    // make sure we're not in the middle of initialization
    return this._initPromise;
  }
}

// Bug 1646182: turn on rkv on all channels
function createStore(useRkv = AppConstants.NIGHTLY_BUILD) {
  if (useRkv) {
    return new PermissionStore();
  }
  return new LegacyPermissionStore();
}

let store = createStore();

// Map<string, Function[]> @see _synchronizeExtPermAccess.
const extPermAccessQueues = new Map();

export var ExtensionPermissions = {
  /**
   * A per-extension container for origins requested at runtime, not in the
   * manifest. This is only preserved in memory for UI consistency.
   *
   * @type {Map<string, Set>}
   */
  tempOrigins: new ExtensionUtils.DefaultMap(() => new Set()),

  // The public ExtensionPermissions.add, get, remove, removeAll methods may
  // interact with the same underlying data source. These methods are not
  // designed with concurrent modifications in mind, and therefore we
  // explicitly synchronize each operation, by processing them sequentially.
  async _synchronizeExtPermAccess(extensionId, asyncFunctionCallback) {
    return new Promise((resolve, reject) => {
      // Here we add the (wrapped) function in the queue. The first queue item
      // is always the currently executing task. Once it completes, the next
      // (wrapped) function in the queue will be run.
      let queue = extPermAccessQueues.get(extensionId) ?? [];
      queue.push(async () => {
        try {
          resolve(await asyncFunctionCallback());
        } catch (e) {
          reject(e);
        }
        queue.shift();
        if (queue.length) {
          queue[0]();
        } else {
          extPermAccessQueues.delete(extensionId);
        }
      });
      if (queue.length === 1) {
        // First item in the queue. Store queue (so that future calls become
        // aware of the pending call), and call the first function in the queue.
        extPermAccessQueues.set(extensionId, queue);
        queue[0]();
      }
    });
  },

  async _update(extensionId, perms) {
    await store.put(extensionId, perms);
    return lazy.StartupCache.permissions.set(extensionId, perms);
  },

  async _get(extensionId) {
    return store.get(extensionId);
  },

  async _getCached(extensionId) {
    return lazy.StartupCache.permissions.get(extensionId, () =>
      this._get(extensionId)
    );
  },

  /**
   * Retrieves the optional permissions for the given extension.
   * The information may be retrieved from the StartupCache, and otherwise fall
   * back to data from the disk (and cache the result in the StartupCache).
   *
   * @param {string} extensionId The extensionId
   * @returns {Promise<object>} Object with "permissions" and "origins" arrays.
   *   The object may be a direct reference to the storage or cache, so its
   *   value should immediately be used and not be modified by callers.
   */
  get(extensionId) {
    return this._synchronizeExtPermAccess(extensionId, () =>
      this._getCached(extensionId)
    );
  },

  /**
   * Validate and normalize passed in `perms`, including a fixup to
   * include all possible "all sites" permissions when appropriate.
   *
   * @throws if an origin or permission is not part of optional permissions.
   *
   * @typedef {object} Perms
   * @property {string[]} origins
   * @property {string[]} permissions
   *
   * @param {Perms} perms api permissions and origins to be added/removed.
   * @param {Perms} optional permissions and origins from the manifest.
   * @returns {Perms} normalized
   */
  normalizeOptional(perms, optional) {
    let allSites = false;
    let patterns = new MatchPatternSet(optional.origins, { ignorePath: true });
    let normalized = Object.assign({}, perms);

    for (let o of perms.origins) {
      if (!patterns.subsumes(new MatchPattern(o))) {
        throw new Error(`${o} was not declared in the manifest`);
      }
      // If this is one of the "all sites" permissions
      allSites ||= lazy.Extension.isAllSitesPermission(o);
    }

    if (allSites) {
      // Grant/revoke ALL "all sites" optional permissions from the manifest.
      let origins = perms.origins.concat(
        optional.origins.filter(o => lazy.Extension.isAllSitesPermission(o))
      );
      normalized.origins = Array.from(new Set(origins));
    }

    for (let p of perms.permissions) {
      if (!optional.permissions.includes(p)) {
        throw new Error(`${p} was not declared in optional_permissions`);
      }
    }

    return normalized;
  },

  _fixupAllUrlsPerms(perms) {
    // Unfortunately, we treat <all_urls> as an API permission as well.
    // If it is added to either, ensure it is added to both.
    if (perms.origins.includes("<all_urls>")) {
      perms.permissions.push("<all_urls>");
    } else if (perms.permissions.includes("<all_urls>")) {
      perms.origins.push("<all_urls>");
    }
  },

  /**
   * Add new permissions for the given extension.  `permissions` is
   * in the format that is passed to browser.permissions.request().
   *
   * @typedef {import("ExtensionCommon.sys.mjs").EventEmitter} EventEmitter
   *
   * @param {string} extensionId The extension id
   * @param {Perms} perms Object with permissions and origins array.
   * @param {EventEmitter} [emitter] optional object implementing emitter interfaces
   */
  async add(extensionId, perms, emitter) {
    return this._synchronizeExtPermAccess(extensionId, async () => {
      let { permissions, origins } = await this._get(extensionId);

      let added = emptyPermissions();

      this._fixupAllUrlsPerms(perms);

      for (let perm of perms.permissions) {
        if (!permissions.includes(perm)) {
          added.permissions.push(perm);
          permissions.push(perm);
        }
      }

      for (let origin of perms.origins) {
        origin = new MatchPattern(origin, { ignorePath: true }).pattern;
        if (!origins.includes(origin)) {
          added.origins.push(origin);
          origins.push(origin);
        }
      }

      if (added.permissions.length || added.origins.length) {
        await this._update(extensionId, { permissions, origins });
        lazy.Management.emit("change-permissions", { extensionId, added });
        if (emitter) {
          emitter.emit("add-permissions", added);
        }
      }
    });
  },

  /**
   * Revoke permissions from the given extension.  `permissions` is
   * in the format that is passed to browser.permissions.request().
   *
   * @param {string} extensionId The extension id
   * @param {Perms} perms Object with permissions and origins array.
   * @param {EventEmitter} [emitter] optional object implementing emitter interfaces
   */
  async remove(extensionId, perms, emitter) {
    return this._synchronizeExtPermAccess(extensionId, async () => {
      let { permissions, origins } = await this._get(extensionId);

      let removed = emptyPermissions();

      this._fixupAllUrlsPerms(perms);

      for (let perm of perms.permissions) {
        let i = permissions.indexOf(perm);
        if (i >= 0) {
          removed.permissions.push(perm);
          permissions.splice(i, 1);
        }
      }

      for (let origin of perms.origins) {
        origin = new MatchPattern(origin, { ignorePath: true }).pattern;

        let i = origins.indexOf(origin);
        if (i >= 0) {
          removed.origins.push(origin);
          origins.splice(i, 1);
        }
      }

      if (removed.permissions.length || removed.origins.length) {
        await this._update(extensionId, { permissions, origins });
        lazy.Management.emit("change-permissions", { extensionId, removed });
        if (emitter) {
          emitter.emit("remove-permissions", removed);
        }
      }

      let temp = this.tempOrigins.get(extensionId);
      for (let origin of removed.origins) {
        temp.add(origin);
      }
    });
  },

  async removeAll(extensionId) {
    return this._synchronizeExtPermAccess(extensionId, async () => {
      this.tempOrigins.delete(extensionId);
      lazy.StartupCache.permissions.delete(extensionId);

      let removed = store.get(extensionId);
      await store.delete(extensionId);
      lazy.Management.emit("change-permissions", {
        extensionId,
        removed: await removed,
      });
    });
  },

  // This is meant for tests only
  async _has(extensionId) {
    return store.has(extensionId);
  },

  // This is meant for tests only
  async _resetVersion() {
    await store.resetVersionForTest();
  },

  // This is meant for tests only
  _useLegacyStorageBackend: false,

  // This is meant for tests only
  async _uninit({ recreateStore = true } = {}) {
    await store?.uninitForTest();
    store = null;
    if (recreateStore) {
      store = createStore(!this._useLegacyStorageBackend);
    }
  },

  // This is meant for tests only
  _getStore() {
    return store;
  },

  // Convenience listener members for all permission changes.
  addListener(listener) {
    lazy.Management.on("change-permissions", listener);
  },

  removeListener(listener) {
    lazy.Management.off("change-permissions", listener);
  },
};

export var OriginControls = {
  /**
   * @typedef {object} NativeTab
   * @property {XULBrowserElement} linkedBrowser
   */

  /**
   * Determine if the given Manifest V3 extension has a host permissions for
   * the given tab which was one expected to be granted at install time (by
   * being listed in host_permissions or derived from match patterns for
   * content scripts declared in the manifest).
   *
   * NOTE: this helper method is only used for additional checks only hit for
   * MV3 extensions, but the implementation is technically not strictly MV3
   * specific.
   *
   * @param {WebExtensionPolicy} policy
   * @param {NativeTab} nativeTab
   * @returns {boolean} Whether the extension has a non optional host
   * permission for the given tab.
   */
  hasMV3RequestedOrigin(policy, nativeTab) {
    const uri = nativeTab.linkedBrowser?.currentURI;

    if (!uri) {
      return false;
    }

    // Determine if that are host permissions that would have been granted
    // as install time that are matching the tab URI.
    const manifestOrigins =
      policy.extension.getManifestOriginsMatchPatternSet();
    return manifestOrigins.matches(uri);
  },

  /**
   * @typedef {object} OriginControlState
   * @param {boolean} noAccess        no options, can never access host.
   * @param {boolean} whenClicked     option to access host when clicked.
   * @param {boolean} alwaysOn        option to always access this host.
   * @param {boolean} allDomains      option to access to all domains.
   * @param {boolean} hasAccess       extension currently has access to host.
   * @param {boolean} temporaryAccess extension has temporary access to the tab.
   */

  /**
   * Get origin controls state for a given extension on a given tab.
   *
   * @param {WebExtensionPolicy} policy
   * @param {NativeTab} nativeTab
   * @returns {OriginControlState} Extension origin controls for this host include:
   */
  getState(policy, nativeTab) {
    // Note: don't use the nativeTab directly because it's different on mobile.
    let tab = policy?.extension?.tabManager?.getWrapper(nativeTab);
    let tabHasActiveTabPermission = tab?.hasActiveTabPermission;
    let uri = tab?.browser.currentURI;
    return this._getStateInternal(policy, { uri, tabHasActiveTabPermission });
  },

  _getStateInternal(policy, { uri, tabHasActiveTabPermission }) {
    let temporaryAccess = tabHasActiveTabPermission;

    if (!uri) {
      return { noAccess: true };
    }

    // activeTab and the resulting whenClicked state is only applicable for MV2
    // extensions with a browser action and MV3 extensions (with or without).
    let activeTab =
      policy.permissions.includes("activeTab") &&
      (policy.manifestVersion >= 3 || policy.extension?.hasBrowserActionUI);

    let couldRequest = policy.extension.optionalOrigins.matches(uri);
    let hasAccess = policy.canAccessURI(uri);

    // If any of (MV2) content script patterns match the URI.
    let csPatternMatches = false;
    let quarantinedFrom = policy.quarantinedFromURI(uri);

    if (policy.manifestVersion < 3 && !hasAccess) {
      csPatternMatches = policy.contentScripts.some(cs =>
        cs.matches.patterns.some(p => p.matches(uri))
      );
      // MV2 access through content scripts is implicit.
      hasAccess = csPatternMatches && !quarantinedFrom;
    }

    // If extension is quarantined from this host, but could otherwise have
    // access (via activeTab, optional, allowedOrigins or content scripts).
    let quarantined =
      quarantinedFrom &&
      (activeTab ||
        couldRequest ||
        csPatternMatches ||
        policy.allowedOrigins.matches(uri));

    if (
      quarantined ||
      (uri.scheme !== "https" && uri.scheme !== "http") ||
      WebExtensionPolicy.isRestrictedURI(uri) ||
      (!couldRequest && !hasAccess && !activeTab)
    ) {
      return { noAccess: true, quarantined };
    }

    if (!couldRequest && !hasAccess && activeTab) {
      return { whenClicked: true, temporaryAccess };
    }
    if (policy.allowedOrigins.matchesAllWebUrls) {
      return { allDomains: true, hasAccess };
    }

    return {
      whenClicked: true,
      alwaysOn: true,
      temporaryAccess,
      hasAccess,
    };
  },

  /**
   * Whether to show the attention indicator for extension on current tab. We
   * usually show attention when:
   *
   * - some permissions are needed (in MV3+)
   * - the extension is not allowed on the domain (quarantined)
   *
   * @param {WebExtensionPolicy} policy an extension's policy
   * @param {Window} window The window for which we can get the attention state
   * @returns {{attention: boolean, quarantined: boolean}}
   */
  getAttentionState(policy, window) {
    if (policy?.manifestVersion >= 3) {
      const { selectedTab } = window.gBrowser;
      const state = this.getState(policy, selectedTab);
      // Request attention when the extension cannot access the current tab,
      // but has a host permission that could be granted.
      // Quarantined is always false when the feature is disabled.
      const quarantined = !!state.quarantined;
      let attention =
        quarantined ||
        (!!state.alwaysOn &&
          !state.hasAccess &&
          !state.temporaryAccess &&
          this.hasMV3RequestedOrigin(policy, selectedTab));

      return { attention, quarantined };
    }

    // No need to check whether the Quarantined Domains feature is enabled
    // here, it's already done in `getState()`.
    const state = this.getState(policy, window.gBrowser.selectedTab);
    const attention = !!state.quarantined;
    // If it needs attention, it's because of the quarantined domains.
    return { attention, quarantined: attention };
  },

  // Grant extension host permission to always run on this host.
  setAlwaysOn(policy, uri) {
    if (!policy.active) {
      return;
    }

    // Already granted.
    if (policy.allowedOrigins.matches(uri)) {
      return;
    }

    // Only try to compute the per-host host permissions on web scheme urls (http/https).
    if (!WEB_SCHEMES.includes(uri.scheme)) {
      return;
    }

    // Determine which one from the 3 set of granted host permissions
    // (granting access to the given url's host and scheme) are subsumed
    // by the optional host permissions declared by the extension.
    let originPatterns = [];
    const originPatternsChoices = [
      // Single wildcard scheme permission for the current host.
      [`*://${uri.host}/*`],
      // Two separate scheme-specific permission for the current host.
      WEB_SCHEMES.map(scheme => `${scheme}://${uri.host}/*`),
      // One scheme-specific permission for the current host and scheme.
      [`${uri.scheme}://${uri.host}/*`],
    ];
    for (const originPatternsChoice of originPatternsChoices) {
      const choiceMatchPatternSet = new MatchPatternSet(originPatternsChoice);
      const choiceSubsumed = choiceMatchPatternSet.patterns.every(mp =>
        policy.extension.optionalOrigins.subsumes(mp)
      );
      if (choiceSubsumed) {
        originPatterns = originPatternsChoice;
        break;
      }
    }

    // Nothing to grant.
    if (!originPatterns.length) {
      // This shouldn't be ever hit outside of unit tests and so we log an error
      // to prevent it from being silently hit (and make it easier to investigate
      // potential bugs in our OriginControls.getState logic that could leave to
      // this).
      Cu.reportError(
        `Unxpected no host permission patterns to grant found for ${policy.debugName} on ${uri.spec}`
      );
      return;
    }

    let perms = { permissions: [], origins: originPatterns };
    return ExtensionPermissions.add(policy.id, perms, policy.extension);
  },

  // Revoke permission, extension should run only when clicked on this host.
  setWhenClicked(policy, uri) {
    if (!policy.active) {
      return;
    }

    // Return earlier if the extension doesn't really have access to the
    // given url.
    if (!policy.allowedOrigins.matches(uri)) {
      return;
    }

    // Only try to revoke per-host host permissions on web scheme urls (http/https).
    if (!WEB_SCHEMES.includes(uri.scheme)) {
      // TODO: once we have introduce a user-controlled opt-in for file urls
      // we could consider to remove that internal permission to revoke
      // to the extension access to file urls (and the user would be able
      // to grant it back from the addon manager).
      return;
    }

    // NOTE: all urls wouldn't be currently be revoked and so in that case
    // setWhenClicked is going to be a no-op.
    const matchHost = new MatchPattern(`*://${uri.host}/*`);
    const patternsToRevoke = policy.allowedOrigins.patterns
      .filter(mp => mp.overlaps(matchHost))
      .map(mp => mp.pattern)
      .filter(pattern => !lazy.Extension.isAllSitesPermission(pattern));

    // Nothing to revoke.
    if (!patternsToRevoke.length) {
      // This shouldn't be ever hit outside of unit tests and so we log an error
      // to prevent it from being silently hit (and make it easier to investigate
      // potential bugs in our OriginControls.getState logic that could leave to
      // this).
      Cu.reportError(
        `Unxpected no host permission patterns to revoke found for ${policy.debugName} on ${uri.spec}`
      );
      return;
    }

    let perms = {
      permissions: [],
      origins: patternsToRevoke,
    };
    return ExtensionPermissions.remove(policy.id, perms, policy.extension);
  },

  /**
   * @typedef {object} FluentIdInfo
   * @param {string} default      the message ID corresponding to the state
   *                              that should be displayed by default.
   * @param {string | null} onHover an optional message ID to be shown when
   *                              users hover interactive elements (e.g. a
   *                              button).
   */

  /**
   * Get origin controls messages (fluent IDs) to be shown to users for a given
   * extension on a given host. The messages might be different for extensions
   * with a browser action (that might or might not open a popup).
   *
   * @param {object} params
   * @param {WebExtensionPolicy} params.policy an extension's policy
   * @param {NativeTab} params.tab             the current tab
   * @param {boolean} params.isAction          this should be true for
   *                                           extensions with a browser
   *                                           action, false otherwise.
   * @param {boolean} params.hasPopup          this should be true when the
   *                                           browser action opens a popup,
   *                                           false otherwise.
   *
   * @returns {FluentIdInfo?} An object with origin controls message IDs or
   *                        `null` when there is no message for the state.
   */
  getStateMessageIDs({ policy, tab, isAction = false, hasPopup = false }) {
    const state = this.getState(policy, tab);

    const onHoverForAction = hasPopup
      ? "origin-controls-state-runnable-hover-open"
      : "origin-controls-state-runnable-hover-run";

    if (state.noAccess) {
      return {
        default: state.quarantined
          ? "origin-controls-state-quarantined"
          : "origin-controls-state-no-access",
        onHover: isAction ? onHoverForAction : null,
      };
    }

    if (state.allDomains || (state.alwaysOn && state.hasAccess)) {
      return {
        default: "origin-controls-state-always-on",
        onHover: isAction ? onHoverForAction : null,
      };
    }

    if (state.whenClicked) {
      return {
        default: state.temporaryAccess
          ? "origin-controls-state-temporary-access"
          : "origin-controls-state-when-clicked",
        onHover: "origin-controls-state-hover-run-visit-only",
      };
    }

    return null;
  },
};

export var QuarantinedDomains = {
  getUserAllowedAddonIdPrefName(addonId) {
    return `${this.PREF_ADDONS_BRANCH_NAME}${addonId}`;
  },
  isUserAllowedAddonId(addonId) {
    return Services.prefs.getBoolPref(
      this.getUserAllowedAddonIdPrefName(addonId),
      false
    );
  },
  setUserAllowedAddonIdPref(addonId, userAllowed) {
    Services.prefs.setBoolPref(
      this.getUserAllowedAddonIdPrefName(addonId),
      userAllowed
    );
  },
  clearUserPref(addonId) {
    Services.prefs.clearUserPref(this.getUserAllowedAddonIdPrefName(addonId));
  },

  // Implementation internals.

  PREF_ADDONS_BRANCH_NAME: `extensions.quarantineIgnoredByUser.`,
  PREF_DOMAINSLIST_NAME: `extensions.quarantinedDomains.list`,
  _initialized: false,
  _init() {
    if (this._initialized) {
      return;
    }

    const onUserAllowedPrefChanged = this._onUserAllowedPrefChanged.bind(this);
    Services.prefs.addObserver(
      this.PREF_ADDONS_BRANCH_NAME,
      onUserAllowedPrefChanged
    );

    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "currentDomainsList",
      this.PREF_DOMAINSLIST_NAME,
      "",
      null,
      value => this._transformDomainsListPrefValue(value || "")
    );

    this._initialized = true;
  },
  async _onUserAllowedPrefChanged(_subject, _topic, prefName) {
    let addonId = prefName.slice(this.PREF_ADDONS_BRANCH_NAME.length);
    // Sanity check.
    if (!addonId || prefName !== this.getUserAllowedAddonIdPrefName(addonId)) {
      return;
    }

    // Notify listeners, e.g. to update details in TelemetryEnvironment.
    const addon = await lazy.AddonManager.getAddonByID(addonId);
    // Do not call onPropertyChanged listeners if the addon cannot be found
    // anymore (e.g. it has been uninstalled).
    if (addon) {
      lazy.AddonManagerPrivate.callAddonListeners("onPropertyChanged", addon, [
        "quarantineIgnoredByUser",
      ]);
    }
  },
  _transformDomainsListPrefValue(value) {
    try {
      return {
        set: new Set(
          value
            .split(",")
            .map(v => v.trim())
            .filter(v => v.length)
        ),
      };
    } catch (err) {
      return { hash: "unexpected-error", set: new Set() };
    }
  },
};
QuarantinedDomains._init();

// Constants exported for testing purpose.
export {
  OLD_JSON_FILENAME,
  OLD_RKV_DIRNAME,
  RKV_DIRNAME,
  VERSION_KEY,
  VERSION_VALUE,
};
PK
!<���Y�Y+modules/ExtensionPreferencesManager.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * @file
 * This module is used for managing preferences from WebExtension APIs.
 * It takes care of the precedence chain and decides whether a preference
 * needs to be updated when a change is requested by an API.
 *
 * It deals with preferences via settings objects, which are objects with
 * the following properties:
 *
 * prefNames:   An array of strings, each of which is a preference on
 *              which the setting depends.
 * setCallback: A function that returns an object containing properties and
 *              values that correspond to the prefs to be set.
 */

export let ExtensionPreferencesManager;

import { Management } from "resource://gre/modules/Extension.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ExtensionCommon: "resource://gre/modules/ExtensionCommon.sys.mjs",
  ExtensionSettingsStore:
    "resource://gre/modules/ExtensionSettingsStore.sys.mjs",
  Preferences: "resource://gre/modules/Preferences.sys.mjs",
});
import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

const { ExtensionError } = ExtensionUtils;

ChromeUtils.defineLazyGetter(lazy, "defaultPreferences", function () {
  return new lazy.Preferences({ defaultBranch: true });
});

/* eslint-disable mozilla/balanced-listeners */
Management.on("uninstall", async (type, { id }) => {
  // Ensure managed preferences are cleared if they were
  // not cleared at the module level.
  await Management.asyncLoadSettingsModules();
  return ExtensionPreferencesManager.removeAll(id);
});

Management.on("disable", async (type, id) => {
  await Management.asyncLoadSettingsModules();
  return ExtensionPreferencesManager.disableAll(id);
});

Management.on("enabling", async (type, id) => {
  await Management.asyncLoadSettingsModules();
  return ExtensionPreferencesManager.enableAll(id);
});

Management.on("change-permissions", (type, change) => {
  // Called for added or removed, but we only care about removed here.
  if (!change.removed) {
    return;
  }
  ExtensionPreferencesManager.removeSettingsForPermissions(
    change.extensionId,
    change.removed.permissions
  );
});

/* eslint-enable mozilla/balanced-listeners */

const STORE_TYPE = "prefs";

// Definitions of settings, each of which correspond to a different API.
let settingsMap = new Map();

/**
 * This function is passed into the ExtensionSettingsStore to determine the
 * initial value of the setting. It reads an array of preference names from
 * the this scope, which gets bound to a settings object.
 *
 * @returns {object}
 *          An object with one property per preference, which holds the current
 *          value of that preference.
 */
function initialValueCallback() {
  let initialValue = {};
  for (let pref of this.prefNames) {
    // If there is a prior user-set value, get it.
    if (lazy.Preferences.isSet(pref)) {
      initialValue[pref] = lazy.Preferences.get(pref);
    }
  }
  return initialValue;
}

/**
 * Updates the initialValue stored to exclude any values that match
 * default preference values.
 *
 * @param {object} initialValue Initial Value data from settings store.
 * @returns {object}
 *          The initialValue object after updating the values.
 */
function settingsUpdate(initialValue) {
  for (let pref of this.prefNames) {
    try {
      if (
        initialValue[pref] !== undefined &&
        initialValue[pref] === lazy.defaultPreferences.get(pref)
      ) {
        initialValue[pref] = undefined;
      }
    } catch (e) {
      // Exception thrown if a default value doesn't exist.  We
      // presume that this pref had a user-set value initially.
    }
  }
  return initialValue;
}

/**
 * Loops through a set of prefs, either setting or resetting them.
 *
 * @param {string} name
 *        The api name of the setting.
 * @param {object} setting
 *        An object that represents a setting, which will have a setCallback
 *        property. If a onPrefsChanged function is provided it will be called
 *        with item when the preferences change.
 * @param {object} item
 *        An object that represents an item handed back from the setting store
 *        from which the new pref values can be calculated.
 */
function setPrefs(name, setting, item) {
  let prefs = item.initialValue || setting.setCallback(item.value);
  let changed = false;
  for (let pref of setting.prefNames) {
    if (prefs[pref] === undefined) {
      if (lazy.Preferences.isSet(pref)) {
        changed = true;
        lazy.Preferences.reset(pref);
      }
    } else if (lazy.Preferences.get(pref) != prefs[pref]) {
      lazy.Preferences.set(pref, prefs[pref]);
      changed = true;
    }
  }
  if (changed && typeof setting.onPrefsChanged == "function") {
    setting.onPrefsChanged(item);
  }
  Management.emit(`extension-setting-changed:${name}`);
}

/**
 * Commits a change to a setting and conditionally sets preferences.
 *
 * If the change to the setting causes a different extension to gain
 * control of the pref (or removes all extensions with control over the pref)
 * then the prefs should be updated, otherwise they should not be.
 * In addition, if the current value of any of the prefs does not
 * match what we expect the value to be (which could be the result of a
 * user manually changing the pref value), then we do not change any
 * of the prefs.
 *
 * @param {string} id
 *        The id of the extension for which a setting is being modified.  Also
 *        see selectSetting.
 * @param {string} name
 *        The name of the setting being processed.
 * @param {string} action
 *        The action that is being performed. Will be one of disable, enable
 *        or removeSetting.

 * @returns {Promise}
 *          Resolves to true if preferences were set as a result and to false
 *          if preferences were not set.
*/
async function processSetting(id, name, action) {
  await lazy.ExtensionSettingsStore.initialize();
  let expectedItem = lazy.ExtensionSettingsStore.getSetting(STORE_TYPE, name);
  let item = lazy.ExtensionSettingsStore[action](id, STORE_TYPE, name);
  if (item) {
    let setting = settingsMap.get(name);
    let expectedPrefs =
      expectedItem.initialValue || setting.setCallback(expectedItem.value);
    if (
      Object.keys(expectedPrefs).some(
        pref =>
          expectedPrefs[pref] &&
          lazy.Preferences.get(pref) != expectedPrefs[pref]
      )
    ) {
      return false;
    }
    setPrefs(name, setting, item);
    return true;
  }
  return false;
}

ExtensionPreferencesManager = {
  /**
   * Adds a setting to the settingsMap. This is how an API tells the
   * preferences manager what its setting object is. The preferences
   * manager needs to know this when settings need to be removed
   * automatically.
   *
   * @param {string} name The unique id of the setting.
   * @param {object} setting
   *        A setting object that should have properties for
   *        prefNames, getCallback and setCallback.
   */
  addSetting(name, setting) {
    settingsMap.set(name, setting);
  },

  /**
   * Gets the default value for a preference.
   *
   * @param {string} prefName The name of the preference.
   *
   * @returns {string|number|boolean} The default value of the preference.
   */
  getDefaultValue(prefName) {
    return lazy.defaultPreferences.get(prefName);
  },

  /**
   * Returns a map of prefName to setting Name for use in about:config, about:preferences or
   * other areas of Firefox that need to know whether a specific pref is controlled by an
   * extension.
   *
   * Given a prefName, you can get the settingName.  Call EPM.getSetting(settingName) to
   * get the details of the setting, including which id if any is in control of the
   * setting.
   *
   * @returns {Promise}
   *          Resolves to a Map of prefName->settingName
   */
  async getManagedPrefDetails() {
    await Management.asyncLoadSettingsModules();
    let prefs = new Map();
    settingsMap.forEach((setting, name) => {
      for (let prefName of setting.prefNames) {
        prefs.set(prefName, name);
      }
    });
    return prefs;
  },

  /**
   * Indicates that an extension would like to change the value of a previously
   * defined setting.
   *
   * @param {string} id
   *        The id of the extension for which a setting is being set.
   * @param {string} name
   *        The unique id of the setting.
   * @param {any} value
   *        The value to be stored in the settings store for this
   *        group of preferences.
   *
   * @returns {Promise}
   *          Resolves to true if the preferences were changed and to false if
   *          the preferences were not changed.
   */
  async setSetting(id, name, value) {
    let setting = settingsMap.get(name);
    await lazy.ExtensionSettingsStore.initialize();
    let item = await lazy.ExtensionSettingsStore.addSetting(
      id,
      STORE_TYPE,
      name,
      value,
      initialValueCallback.bind(setting),
      name,
      settingsUpdate.bind(setting)
    );
    if (item) {
      setPrefs(name, setting, item);
      return true;
    }
    return false;
  },

  /**
   * Indicates that this extension wants to temporarily cede control over the
   * given setting.
   *
   * @param {string} id
   *        The id of the extension for which a preference setting is being disabled.
   * @param {string} name
   *        The unique id of the setting.
   *
   * @returns {Promise}
   *          Resolves to true if the preferences were changed and to false if
   *          the preferences were not changed.
   */
  disableSetting(id, name) {
    return processSetting(id, name, "disable");
  },

  /**
   * Enable a setting that has been disabled.
   *
   * @param {string} id
   *        The id of the extension for which a setting is being enabled.
   * @param {string} name
   *        The unique id of the setting.
   *
   * @returns {Promise}
   *          Resolves to true if the preferences were changed and to false if
   *          the preferences were not changed.
   */
  enableSetting(id, name) {
    return processSetting(id, name, "enable");
  },

  /**
   * Specifically select an extension, the user, or the precedence order that will
   * be in control of this setting.
   *
   * @param {string | null} id
   *        The id of the extension for which a setting is being selected, or
   *        ExtensionSettingStore.SETTING_USER_SET (null).
   * @param {string} name
   *        The unique id of the setting.
   *
   * @returns {Promise}
   *          Resolves to true if the preferences were changed and to false if
   *          the preferences were not changed.
   */
  selectSetting(id, name) {
    return processSetting(id, name, "select");
  },

  /**
   * Indicates that this extension no longer wants to set the given setting.
   *
   * @param {string} id
   *        The id of the extension for which a preference setting is being removed.
   * @param {string} name
   *        The unique id of the setting.
   *
   * @returns {Promise}
   *          Resolves to true if the preferences were changed and to false if
   *          the preferences were not changed.
   */
  removeSetting(id, name) {
    return processSetting(id, name, "removeSetting");
  },

  /**
   * Disables all previously set settings for an extension. This can be called when
   * an extension is being disabled, for example.
   *
   * @param {string} id
   *        The id of the extension for which all settings are being unset.
   */
  async disableAll(id) {
    await lazy.ExtensionSettingsStore.initialize();
    let settings = lazy.ExtensionSettingsStore.getAllForExtension(
      id,
      STORE_TYPE
    );
    let disablePromises = [];
    for (let name of settings) {
      disablePromises.push(this.disableSetting(id, name));
    }
    await Promise.all(disablePromises);
  },

  /**
   * Enables all disabled settings for an extension. This can be called when
   * an extension has finished updating or is being re-enabled, for example.
   *
   * @param {string} id
   *        The id of the extension for which all settings are being enabled.
   */
  async enableAll(id) {
    await lazy.ExtensionSettingsStore.initialize();
    let settings = lazy.ExtensionSettingsStore.getAllForExtension(
      id,
      STORE_TYPE
    );
    let enablePromises = [];
    for (let name of settings) {
      enablePromises.push(this.enableSetting(id, name));
    }
    await Promise.all(enablePromises);
  },

  /**
   * Removes all previously set settings for an extension. This can be called when
   * an extension is being uninstalled, for example.
   *
   * @param {string} id
   *        The id of the extension for which all settings are being unset.
   */
  async removeAll(id) {
    await lazy.ExtensionSettingsStore.initialize();
    let settings = lazy.ExtensionSettingsStore.getAllForExtension(
      id,
      STORE_TYPE
    );
    let removePromises = [];
    for (let name of settings) {
      removePromises.push(this.removeSetting(id, name));
    }
    await Promise.all(removePromises);
  },

  /**
   * Removes a set of settings that are available under certain addon permissions.
   *
   * @param {string} id
   *        The extension id.
   * @param {Array<string>} permissions
   *        The permission name from the extension manifest.
   * @returns {Promise}
   *        A promise that resolves when all related settings are removed.
   */
  async removeSettingsForPermissions(id, permissions) {
    if (!permissions || !permissions.length) {
      return;
    }
    await Management.asyncLoadSettingsModules();
    let removePromises = [];
    settingsMap.forEach((setting, name) => {
      if (permissions.includes(setting.permission)) {
        removePromises.push(this.removeSetting(id, name));
      }
    });
    return Promise.all(removePromises);
  },

  /**
   * Return the currently active value for a setting.
   *
   * @param {string} name
   *        The unique id of the setting.
   *
   * @returns {Promise<object>} The current setting object.
   */
  async getSetting(name) {
    await lazy.ExtensionSettingsStore.initialize();
    return lazy.ExtensionSettingsStore.getSetting(STORE_TYPE, name);
  },

  /**
   * Return the levelOfControl for a setting / extension combo.
   * This queries the levelOfControl from the ExtensionSettingsStore and also
   * takes into account whether any of the setting's preferences are locked.
   *
   * @param {string} id
   *        The id of the extension for which levelOfControl is being requested.
   * @param {string} name
   *        The unique id of the setting.
   * @param {string} storeType
   *        The name of the store in ExtensionSettingsStore.
   *        Defaults to STORE_TYPE.
   *
   * @returns {Promise}
   *          Resolves to the level of control of the extension over the setting.
   */
  async getLevelOfControl(id, name, storeType = STORE_TYPE) {
    // This could be called for a setting that isn't defined to the PreferencesManager,
    // in which case we simply defer to the SettingsStore.
    if (storeType === STORE_TYPE) {
      let setting = settingsMap.get(name);
      if (!setting) {
        return "not_controllable";
      }
      for (let prefName of setting.prefNames) {
        if (lazy.Preferences.locked(prefName)) {
          return "not_controllable";
        }
      }
    }
    await lazy.ExtensionSettingsStore.initialize();
    return lazy.ExtensionSettingsStore.getLevelOfControl(id, storeType, name);
  },

  /**
   * Returns an API object with get/set/clear used for a setting.
   *
   * @param {string|object} extensionId or params object
   * @param {string} name
   *          The unique id of the setting.
   * @param {Function} callback
   *          The function that retreives the current setting from prefs.
   * @param {string} storeType
   *          The name of the store in ExtensionSettingsStore.
   *          Defaults to STORE_TYPE.
   * @param {boolean} readOnly
   * @param {Function} validate
   *          Utility function for any specific validation, such as checking
   *          for supported platform.  Function should throw an error if necessary.
   *
   * @returns {object} API object with get/set/clear methods
   */
  getSettingsAPI(
    extensionId,
    name,
    callback,
    storeType,
    readOnly = false,
    validate
  ) {
    if (arguments.length > 1) {
      Services.console.logStringMessage(
        `ExtensionPreferencesManager.getSettingsAPI for ${name} should be updated to use a single paramater object.`
      );
    }
    return ExtensionPreferencesManager._getInternalSettingsAPI(
      arguments.length === 1
        ? extensionId
        : {
            extensionId,
            name,
            callback,
            storeType,
            readOnly,
            validate,
          }
    ).api;
  },

  /**
   * getPrimedSettingsListener returns a function used to create
   * a primed event listener.
   *
   * If a module overrides onChange then it must provide it's own
   * persistent listener logic.  See homepage_override in browserSettings
   * for an example.
   *
   * addSetting must be called prior to priming listeners.
   *
   * @param {object} config see getSettingsAPI
   *        {Extension} extension, passed through to validate and used for extensionId
   *        {string} name
   *          The unique id of the settings api in the module, e.g. "settings"
   * @returns {object} prime listener object
   */
  getPrimedSettingsListener(config) {
    let { name, extension } = config;
    if (!name || !extension) {
      throw new Error(
        `name and extension are required for getPrimedSettingListener`
      );
    }
    if (!settingsMap.get(name)) {
      throw new Error(
        `addSetting must be called prior to getPrimedSettingListener`
      );
    }
    return ExtensionPreferencesManager._getInternalSettingsAPI({
      name,
      extension,
    }).registerEvent;
  },

  /**
   * Returns an object with a public API containing get/set/clear used for a setting,
   * and a registerEvent function used for registering the event listener.
   *
   * @param {object} params The params object contains the following:
   *        {BaseContext} context
   *        {Extension} extension, optional, passed through to validate and used for extensionId
   *        {string} extensionId, optional to support old API
   *        {string} module
   *          The name of the api module, e.g. "proxy"
   *        {string} name
   *          The unique id of the settings api in the module, e.g. "settings"
   *          "name" should match the name given in the addSetting call.
   *        {Function} callback
   *          The function that retreives the current setting from prefs.
   *        {string} storeType
   *          The name of the store in ExtensionSettingsStore.
   *          Defaults to STORE_TYPE.
   *        {boolean} readOnly
   *        {Function} validate
   *          Utility function for any specific validation, such as checking
   *          for supported platform.  Function should throw an error if necessary.
   *
   * @returns {object} internal API object with
   *          {object} api
   *            the public api available to extensions
   *          {Function} registerEvent
   *            the registration function used for priming events
   */
  _getInternalSettingsAPI(params) {
    let {
      extensionId,
      context,
      extension,
      module,
      name,
      callback,
      storeType,
      readOnly = false,
      onChange,
      validate,
    } = params;
    if (context) {
      extension = context.extension;
    }
    if (!extensionId && extension) {
      extensionId = extension.id;
    }

    const checkScope = details => {
      let { scope } = details;
      if (scope && scope !== "regular") {
        throw new ExtensionError(
          `Firefox does not support the ${scope} settings scope.`
        );
      }
    };

    // Check the setting for anything we may need.
    let setting = settingsMap.get(name);
    readOnly = readOnly || !!setting?.readOnly;
    validate = validate || setting?.validate || (() => {});
    let getValue = callback || setting?.getCallback;
    if (!getValue || typeof getValue !== "function") {
      throw new Error(`Invalid get callback for setting ${name} in ${module}`);
    }

    let settingsAPI = {
      async get(details) {
        validate(extension);
        let levelOfControl = details.incognito
          ? "not_controllable"
          : await ExtensionPreferencesManager.getLevelOfControl(
              extensionId,
              name,
              storeType
            );
        levelOfControl =
          readOnly && levelOfControl === "controllable_by_this_extension"
            ? "not_controllable"
            : levelOfControl;
        return {
          levelOfControl,
          value: await getValue(),
        };
      },
      set(details) {
        validate(extension);
        checkScope(details);
        if (!readOnly) {
          return ExtensionPreferencesManager.setSetting(
            extensionId,
            name,
            details.value
          );
        }
        return false;
      },
      clear(details) {
        validate(extension);
        checkScope(details);
        if (!readOnly) {
          return ExtensionPreferencesManager.removeSetting(extensionId, name);
        }
        return false;
      },
      onChange,
    };
    let registerEvent = fire => {
      let listener = async () => {
        fire.async(await settingsAPI.get({}));
      };
      Management.on(`extension-setting-changed:${name}`, listener);
      return {
        unregister: () => {
          Management.off(`extension-setting-changed:${name}`, listener);
        },
        convert(_fire) {
          fire = _fire;
        },
      };
    };

    // Any caller using the old call signature will not have passed
    // context to us.  This should only be experimental addons in the
    // wild.
    if (onChange === undefined && context) {
      // Some settings that are read-only may not have called addSetting, in
      // which case we have no way to listen on the pref changes.
      if (setting) {
        settingsAPI.onChange = new lazy.ExtensionCommon.EventManager({
          context,
          module,
          event: name,
          name: `${name}.onChange`,
          register: fire => {
            return registerEvent(fire).unregister;
          },
        }).api();
      } else {
        Services.console.logStringMessage(
          `ExtensionPreferencesManager API ${name} created but addSetting was not called.`
        );
      }
    }
    return { api: settingsAPI, registerEvent };
  },
};
PK
!<!u�7�?�?&modules/ExtensionProcessScript.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This script contains the minimum, skeleton content process code that we need
 * in order to lazily load other extension modules when they are first
 * necessary. Anything which is not likely to be needed immediately, or shortly
 * after startup, in *every* browser process live outside of this file.
 */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

/** @type {Lazy} */
const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ExtensionChild: "resource://gre/modules/ExtensionChild.sys.mjs",
  ExtensionCommon: "resource://gre/modules/ExtensionCommon.sys.mjs",
  ExtensionContent: "resource://gre/modules/ExtensionContent.sys.mjs",
  ExtensionPageChild: "resource://gre/modules/ExtensionPageChild.sys.mjs",
  ExtensionWorkerChild: "resource://gre/modules/ExtensionWorkerChild.sys.mjs",
  Schemas: "resource://gre/modules/Schemas.sys.mjs",
});

import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

const { DefaultWeakMap } = ExtensionUtils;

const { sharedData } = Services.cpmm;

function getData(extension, key = "") {
  return sharedData.get(`extension/${extension.id}/${key}`);
}

// We need to avoid touching Services.appinfo here in order to prevent
// the wrong version from being cached during xpcshell test startup.
// eslint-disable-next-line mozilla/use-services
ChromeUtils.defineLazyGetter(lazy, "isContentProcess", () => {
  return Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;
});

ChromeUtils.defineLazyGetter(lazy, "isContentScriptProcess", () => {
  return (
    lazy.isContentProcess ||
    !WebExtensionPolicy.useRemoteWebExtensions ||
    // Thunderbird still loads some content in the parent process.
    AppConstants.MOZ_APP_NAME == "thunderbird"
  );
});

var extensions = new DefaultWeakMap(policy => {
  return new lazy.ExtensionChild(policy);
});

var pendingExtensions = new Map();

var ExtensionManager;

ExtensionManager = {
  // WeakMap<WebExtensionPolicy, Map<number, WebExtensionContentScript>>
  registeredContentScripts: new DefaultWeakMap(() => new Map()),

  init() {
    Services.cpmm.addMessageListener("Extension:Startup", this);
    Services.cpmm.addMessageListener("Extension:Shutdown", this);
    Services.cpmm.addMessageListener("Extension:FlushJarCache", this);
    Services.cpmm.addMessageListener("Extension:RegisterContentScripts", this);
    Services.cpmm.addMessageListener(
      "Extension:UnregisterContentScripts",
      this
    );
    Services.cpmm.addMessageListener("Extension:UpdateContentScripts", this);
    Services.cpmm.addMessageListener("Extension:UpdatePermissions", this);
    Services.cpmm.addMessageListener("Extension:UpdateIgnoreQuarantine", this);

    this.updateStubExtensions();

    for (let id of sharedData.get("extensions/activeIDs") || []) {
      this.initExtension(getData({ id }));
    }
  },

  initStubPolicy(id, data) {
    let resolveReadyPromise;
    let readyPromise = new Promise(resolve => {
      resolveReadyPromise = resolve;
    });

    let policy = new WebExtensionPolicy({
      id,
      localizeCallback() {},
      readyPromise,
      allowedOrigins: new MatchPatternSet([]),
      ...data,
    });

    try {
      policy.active = true;

      pendingExtensions.set(id, { policy, resolveReadyPromise });
    } catch (e) {
      Cu.reportError(e);
    }
  },

  updateStubExtensions() {
    for (let [id, data] of sharedData.get("extensions/pending") || []) {
      if (!pendingExtensions.has(id)) {
        this.initStubPolicy(id, data);
      }
    }
  },

  initExtensionPolicy(extension) {
    let policy = WebExtensionPolicy.getByID(extension.id);
    if (!policy || pendingExtensions.has(extension.id)) {
      let localizeCallback;
      if (extension.localize) {
        // We have a real Extension object.
        localizeCallback = extension.localize.bind(extension);
      } else {
        // We have serialized extension data;
        localizeCallback = str => extensions.get(policy).localize(str);
      }

      let { backgroundScripts } = extension;
      if (!backgroundScripts && WebExtensionPolicy.isExtensionProcess) {
        ({ backgroundScripts } = getData(extension, "extendedData") || {});
      }

      let { backgroundWorkerScript } = extension;
      if (!backgroundWorkerScript && WebExtensionPolicy.isExtensionProcess) {
        ({ backgroundWorkerScript } = getData(extension, "extendedData") || {});
      }

      let { backgroundTypeModule } = extension;
      if (
        backgroundTypeModule == null &&
        WebExtensionPolicy.isExtensionProcess
      ) {
        ({ backgroundTypeModule } = getData(extension, "extendedData") || {});
      }

      policy = new WebExtensionPolicy({
        id: extension.id,
        mozExtensionHostname: extension.uuid,
        name: extension.name,
        type: extension.type,
        baseURL: extension.resourceURL,

        isPrivileged: extension.isPrivileged,
        ignoreQuarantine: extension.ignoreQuarantine,
        temporarilyInstalled: extension.temporarilyInstalled,
        permissions: extension.permissions,
        allowedOrigins: extension.allowedOrigins,
        webAccessibleResources: extension.webAccessibleResources,

        manifestVersion: extension.manifestVersion,
        extensionPageCSP: extension.extensionPageCSP,

        localizeCallback,

        backgroundScripts,
        backgroundWorkerScript,
        backgroundTypeModule,

        contentScripts: extension.contentScripts,
      });

      policy.debugName = `${JSON.stringify(policy.name)} (ID: ${
        policy.id
      }, ${policy.getURL()})`;

      // Register any existent dynamically registered content script for the extension
      // when a content process is started for the first time (which also cover
      // a content process that crashed and it has been recreated).
      const registeredContentScripts =
        this.registeredContentScripts.get(policy);

      for (let [scriptId, options] of getData(extension, "contentScripts") ||
        []) {
        const script = new WebExtensionContentScript(policy, options);

        // If the script is a userScript, add the additional userScriptOptions
        // property to the WebExtensionContentScript instance.
        if ("userScriptOptions" in options) {
          script.userScriptOptions = options.userScriptOptions;
        }

        policy.registerContentScript(script);
        registeredContentScripts.set(scriptId, script);
      }

      let stub = pendingExtensions.get(extension.id);
      if (stub) {
        pendingExtensions.delete(extension.id);
        stub.policy.active = false;
        stub.resolveReadyPromise(policy);
      }

      policy.active = true;
      policy.instanceId = extension.instanceId;
      policy.optionalPermissions = extension.optionalPermissions;
    }
    return policy;
  },

  initExtension(data) {
    if (typeof data === "string") {
      data = getData({ id: data });
    }
    let policy = this.initExtensionPolicy(data);

    policy.injectContentScripts();
  },

  handleEvent(event) {
    if (
      event.type === "change" &&
      event.changedKeys.includes("extensions/pending")
    ) {
      this.updateStubExtensions();
    }
  },

  receiveMessage({ name, data }) {
    try {
      switch (name) {
        case "Extension:Startup":
          this.initExtension(data);
          break;

        case "Extension:Shutdown": {
          let policy = WebExtensionPolicy.getByID(data.id);
          if (policy) {
            if (extensions.has(policy)) {
              extensions.get(policy).shutdown();
            }

            if (lazy.isContentProcess) {
              policy.active = false;
            }
          }
          break;
        }

        case "Extension:FlushJarCache":
          ExtensionUtils.flushJarCache(data.path);
          break;

        case "Extension:RegisterContentScripts": {
          let policy = WebExtensionPolicy.getByID(data.id);

          if (policy) {
            const registeredContentScripts =
              this.registeredContentScripts.get(policy);

            for (const { scriptId, options } of data.scripts) {
              const type =
                "userScriptOptions" in options ? "userScript" : "contentScript";

              if (registeredContentScripts.has(scriptId)) {
                Cu.reportError(
                  new Error(
                    `Registering ${type} ${scriptId} on ${data.id} more than once`
                  )
                );
              } else {
                const script = new WebExtensionContentScript(policy, options);

                // If the script is a userScript, add the additional
                // userScriptOptions property to the WebExtensionContentScript
                // instance.
                if (type === "userScript") {
                  script.userScriptOptions = options.userScriptOptions;
                }

                policy.registerContentScript(script);
                registeredContentScripts.set(scriptId, script);
              }
            }
          }
          break;
        }

        case "Extension:UnregisterContentScripts": {
          let policy = WebExtensionPolicy.getByID(data.id);

          if (policy) {
            const registeredContentScripts =
              this.registeredContentScripts.get(policy);

            for (const scriptId of data.scriptIds) {
              const script = registeredContentScripts.get(scriptId);
              if (script) {
                policy.unregisterContentScript(script);
                registeredContentScripts.delete(scriptId);
              }
            }
          }
          break;
        }

        case "Extension:UpdateContentScripts": {
          let policy = WebExtensionPolicy.getByID(data.id);

          if (policy) {
            const registeredContentScripts =
              this.registeredContentScripts.get(policy);

            for (const { scriptId, options } of data.scripts) {
              const oldScript = registeredContentScripts.get(scriptId);
              const newScript = new WebExtensionContentScript(policy, options);

              policy.unregisterContentScript(oldScript);
              policy.registerContentScript(newScript);
              registeredContentScripts.set(scriptId, newScript);
            }
          }
          break;
        }

        case "Extension:UpdatePermissions": {
          let policy = WebExtensionPolicy.getByID(data.id);
          if (!policy) {
            break;
          }
          // In the parent process, Extension.sys.mjs updates the policy.
          if (lazy.isContentProcess) {
            lazy.ExtensionCommon.updateAllowedOrigins(
              policy,
              data.origins,
              data.add
            );

            if (data.permissions.length) {
              let perms = new Set(policy.permissions);
              for (let perm of data.permissions) {
                if (data.add) {
                  perms.add(perm);
                } else {
                  perms.delete(perm);
                }
              }
              policy.permissions = Array.from(perms);
            }
          }

          if (data.permissions.length && extensions.has(policy)) {
            // Notify ChildApiManager of permission changes.
            extensions.get(policy).emit("update-permissions");
          }
          break;
        }

        case "Extension:UpdateIgnoreQuarantine": {
          let policy = WebExtensionPolicy.getByID(data.id);
          if (policy?.active) {
            policy.ignoreQuarantine = data.ignoreQuarantine;
          }
          break;
        }
      }
    } catch (e) {
      Cu.reportError(e);
    }
    Services.cpmm.sendAsyncMessage(`${name}Complete`);
  },
};

export var ExtensionProcessScript = {
  extensions,

  initExtension(extension) {
    return ExtensionManager.initExtensionPolicy(extension);
  },

  initExtensionDocument(policy, doc, privileged) {
    let extension = extensions.get(policy);
    if (privileged) {
      lazy.ExtensionPageChild.initExtensionContext(extension, doc.defaultView);
    } else {
      lazy.ExtensionContent.initExtensionContext(extension, doc.defaultView);
    }
  },

  getExtensionChild(id) {
    let policy = WebExtensionPolicy.getByID(id);
    if (policy) {
      return extensions.get(policy);
    }
  },

  preloadContentScript(contentScript) {
    if (lazy.isContentScriptProcess) {
      lazy.ExtensionContent.contentScripts.get(contentScript).preload();
    }
  },

  loadContentScript(contentScript, window) {
    return lazy.ExtensionContent.contentScripts
      .get(contentScript)
      .injectInto(window);
  },
};

export var ExtensionAPIRequestHandler = {
  initExtensionWorker(policy, serviceWorkerInfo) {
    let extension = extensions.get(policy);

    if (!extension) {
      throw new Error(`Extension instance not found for addon ${policy.id}`);
    }

    lazy.ExtensionWorkerChild.initExtensionWorkerContext(
      extension,
      serviceWorkerInfo
    );
  },

  onExtensionWorkerLoaded(policy, serviceWorkerDescriptorId) {
    lazy.ExtensionWorkerChild.notifyExtensionWorkerContextLoaded(
      serviceWorkerDescriptorId,
      policy
    );
  },

  onExtensionWorkerDestroyed(policy, serviceWorkerDescriptorId) {
    lazy.ExtensionWorkerChild.destroyExtensionWorkerContext(
      serviceWorkerDescriptorId
    );
  },

  handleAPIRequest(policy, request) {
    let context;

    try {
      let extension = extensions.get(policy);

      if (!extension) {
        throw new Error(`Extension instance not found for addon ${policy.id}`);
      }

      context = this.getExtensionContextForAPIRequest({
        extension,
        request,
      });

      if (!context) {
        throw new Error(
          `Extension context not found for API request: ${request}`
        );
      }

      // Add a property to the request object for the normalizedArgs.
      request.normalizedArgs = this.validateAndNormalizeRequestArgs({
        context,
        request,
      });

      return context.childManager.handleWebIDLAPIRequest(request);
    } catch (error) {
      // Propagate errors related to parameter validation when the error object
      // belongs to the extension context that initiated the call.
      if (context?.Error && error instanceof context.Error) {
        return {
          type: Ci.mozIExtensionAPIRequestResult.EXTENSION_ERROR,
          value: error,
        };
      }
      // Do not propagate errors that are not meant to be accessible to the
      // extension, report it to the console and just throw the generic
      // "An unexpected error occurred".
      Cu.reportError(error);
      return {
        type: Ci.mozIExtensionAPIRequestResult.EXTENSION_ERROR,
        value: new Error("An unexpected error occurred"),
      };
    }
  },

  getExtensionContextForAPIRequest({ extension, request }) {
    if (request.serviceWorkerInfo) {
      return lazy.ExtensionWorkerChild.getExtensionWorkerContext(
        extension,
        request.serviceWorkerInfo
      );
    }

    return null;
  },

  validateAndNormalizeRequestArgs({ context, request }) {
    if (
      !lazy.Schemas.checkPermissions(request.apiNamespace, context.extension)
    ) {
      throw new context.Error(
        `Not enough privileges to access ${request.apiNamespace}`
      );
    }
    if (request.requestType === "getProperty") {
      return [];
    }

    if (request.apiObjectType) {
      // skip parameter validation on request targeting an api object,
      // even the JS-based implementation of the API objects are not
      // going through the same kind of Schema based validation that
      // the API namespaces methods and events go through.
      //
      // TODO(Bug 1728535): validate and normalize also this request arguments
      // as a low priority follow up.
      return request.args;
    }

    // Validate and normalize parameters, set the normalized args on the
    // mozIExtensionAPIRequest normalizedArgs property.
    return lazy.Schemas.checkWebIDLRequestParameters(
      context.childManager,
      request
    );
  },
};

ExtensionManager.init();
PK
!<�ƬP--'modules/ExtensionScriptingStore.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

import { ExtensionParent } from "resource://gre/modules/ExtensionParent.sys.mjs";

const { StartupCache } = ExtensionParent;

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
  KeyValueService: "resource://gre/modules/kvstore.sys.mjs",
});

class Store {
  async _init() {
    const { path: storePath } = lazy.FileUtils.getDir("ProfD", [
      "extension-store",
    ]);
    // Make sure the folder exists.
    await IOUtils.makeDirectory(storePath, { ignoreExisting: true });
    this._store = await lazy.KeyValueService.getOrCreateWithOptions(
      storePath,
      "scripting-contentScripts",
      { strategy: lazy.KeyValueService.RecoveryStrategy.RENAME }
    );
  }

  lazyInit() {
    if (!this._initPromise) {
      this._initPromise = this._init();
    }

    return this._initPromise;
  }

  _uninitForTesting() {
    this._store = null;
    this._initPromise = null;
  }

  /**
   * Returns all the stored scripts for a given extension (ID).
   *
   * @param {string} extensionId An extension ID
   * @returns {Promise<Array>} An array of scripts
   */
  async getAll(extensionId) {
    await this.lazyInit();
    const pairs = await this.getByExtensionId(extensionId);

    return pairs.map(([_, script]) => script);
  }

  /**
   * Writes all the scripts provided for a given extension (ID) to the internal
   * store (which is eventually stored on disk).
   *
   * We store each script of an extension as a key/value pair where the key is
   * `<extensionId>/<scriptId>` and the value is the corresponding script
   * details as a JSON string.
   *
   * The format on disk should look like this one:
   *
   * ```
   * {
   *   "@extension-id/script-1": {"id: "script-1", <other props>},
   *   "@extension-id/script-2": {"id: "script-2", <other props>}
   * }
   * ```
   *
   * @param {string} extensionId An extension ID
   * @param {Array} scripts An array of scripts to store on disk
   */
  async writeMany(extensionId, scripts) {
    await this.lazyInit();

    return this._store.writeMany(
      scripts.map(script => [
        `${extensionId}/${script.id}`,
        JSON.stringify(script),
      ])
    );
  }

  /**
   * Deletes all the stored scripts for a given extension (ID).
   *
   * @param {string} extensionId An extension ID
   */
  async deleteAll(extensionId) {
    await this.lazyInit();
    const pairs = await this.getByExtensionId(extensionId);

    return Promise.all(pairs.map(([key, _]) => this._store.delete(key)));
  }

  /**
   * Returns an array of key/script pairs from the internal store belonging to
   * the given extension (ID).
   *
   * The data returned by this method should look like this (assuming we have
   * two scripts named `script-1` and `script-2` for the extension with ID
   * `@extension-id`):
   *
   * ```
   * [
   *   ["@extension-id/script-1", {"id: "script-1", <other props>}],
   *   ["@extension-id/script-2", {"id: "script-2", <other props>}]
   * ]
   * ```
   *
   * @param {string} extensionId An extension ID
   * @returns {Promise<Array>} An array of key/script pairs
   */
  async getByExtensionId(extensionId) {
    await this.lazyInit();

    const entries = [];
    // Retrieve all the scripts registered for the given extension ID by
    // enumerating all keys that are stored in a lexical order.
    const enumerator = await this._store.enumerate(
      `${extensionId}/`, // from_key (inclusive)
      `${extensionId}0` // to_key (exclusive)
    );

    while (enumerator.hasMoreElements()) {
      const { key, value } = enumerator.getNext();
      entries.push([key, JSON.parse(value)]);
    }

    return entries;
  }
}

const store = new Store();

/**
 * Given an extension and some content script options, this function returns
 * the content script representation we use internally, which is an object with
 * a `scriptId` and a nested object containing `options`. These (internal)
 * objects are shared with all content processes using IPC/sharedData.
 *
 * This function can optionally prepend the extension's base URL to the CSS and
 * JS paths, which is needed when we load internal scripts from the scripting
 * store (because the UUID in the base URL changes).
 *
 * @param {Extension} extension
 *        The extension that owns the content script.
 * @param {object} options
 *        Content script options.
 * @param {boolean} prependBaseURL
 *        Whether to prepend JS and CSS paths with the extension's base URL.
 *
 * @returns {object}
 */
export const makeInternalContentScript = (
  extension,
  options,
  prependBaseURL = false
) => {
  let cssPaths = options.css || [];
  let jsPaths = options.js || [];

  if (prependBaseURL) {
    cssPaths = cssPaths.map(css => `${extension.baseURL}${css}`);
    jsPaths = jsPaths.map(js => `${extension.baseURL}${js}`);
  }

  return {
    scriptId: ExtensionUtils.getUniqueId(),
    options: {
      // We need to store the user-supplied script ID for persisted scripts.
      id: options.id,
      allFrames: options.allFrames || false,
      // Although this flag defaults to true with MV3, it is not with MV2.
      // Check permissions at runtime since we aren't checking permissions
      // upfront.
      checkPermissions: true,
      cssPaths,
      excludeMatches: options.excludeMatches,
      jsPaths,
      matches: options.matches,
      matchOriginAsFallback: options.matchOriginAsFallback || false,
      originAttributesPatterns: null,
      persistAcrossSessions: options.persistAcrossSessions,
      runAt: options.runAt || "document_idle",
      world: options.world || "ISOLATED",
    },
  };
};

/**
 * Given an internal content script registered with the "scripting" API (and an
 * extension), this function returns a new object that matches the public
 * "scripting" API.
 *
 * This function is primarily in `scripting.getRegisteredContentScripts()`.
 *
 * @param {Extension} extension
 *        The extension that owns the content script.
 * @param {object} internalScript
 *        An internal script (see also: `makeInternalContentScript()`).
 *
 * @returns {object}
 */
export const makePublicContentScript = (extension, internalScript) => {
  let script = {
    id: internalScript.id,
    allFrames: internalScript.allFrames,
    matches: internalScript.matches,
    matchOriginAsFallback: internalScript.matchOriginAsFallback,
    runAt: internalScript.runAt,
    world: internalScript.world,
    persistAcrossSessions: internalScript.persistAcrossSessions,
  };

  if (internalScript.cssPaths.length) {
    script.css = internalScript.cssPaths.map(cssPath =>
      cssPath.replace(extension.baseURL, "")
    );
  }

  if (internalScript.excludeMatches?.length) {
    script.excludeMatches = internalScript.excludeMatches;
  }

  if (internalScript.jsPaths.length) {
    script.js = internalScript.jsPaths.map(jsPath =>
      jsPath.replace(extension.baseURL, "")
    );
  }

  return script;
};

export const ExtensionScriptingStore = {
  async initExtension(extension) {
    let scripts;

    // On downgrades/upgrades (and re-installation on top of an existing one),
    // we do clear any previously stored scripts and return earlier.
    switch (extension.startupReason) {
      case "ADDON_INSTALL":
      case "ADDON_UPGRADE":
      case "ADDON_DOWNGRADE":
        // On extension upgrades/downgrades the StartupCache data for the
        // extension would already be cleared, and so we set the hasPersistedScripts
        // flag here just to avoid having to check that (by loading the rkv store data)
        // on the next startup.
        StartupCache.general.set(
          [extension.id, extension.version, "scripting", "hasPersistedScripts"],
          false
        );
        store.deleteAll(extension.id);
        return;
    }

    const hasPersistedScripts = await StartupCache.get(
      extension,
      ["scripting", "hasPersistedScripts"],
      async () => {
        scripts = await store.getAll(extension.id);
        return !!scripts.length;
      }
    );

    if (!hasPersistedScripts) {
      return;
    }

    // Load the scripts from the storage, then convert them to their internal
    // representation and add them to the extension's registered scripts.
    scripts ??= await store.getAll(extension.id);

    scripts.forEach(script => {
      const { scriptId, options } = makeInternalContentScript(
        extension,
        script,
        true /* prepend the css/js paths with the extension base URL */
      );
      extension.registeredContentScripts.set(scriptId, options);
    });
  },

  getInitialScriptIdsMap(extension) {
    // This returns the current map of public script IDs to internal IDs.
    // `extension.registeredContentScripts` is initialized in `initExtension`,
    // which may be updated later via the scripting API. In practice, the map
    // of script IDs is retrieved before any scripting API method is exposed,
    // so the return value always matches the initial result from
    // `initExtension`.
    return new Map(
      Array.from(extension.registeredContentScripts.entries())
        .filter(
          // Filter out entries without an options.id property, which are the
          // ones registered through the contentScripts API namespace where the
          // id attribute is not allowed, while it is mandatory for the
          // scripting API namespace.
          ([_id, options]) => options.id?.length
        )
        .map(([scriptId, options]) => [options.id, scriptId])
    );
  },

  async persistAll(extension) {
    // We only persist the scripts that should be persisted and we convert each
    // script to their "public" representation before storing them. This is
    // because we don't want to deal with data migrations if we ever want to
    // change the internal representation (the "public" representation is less
    // likely to change because it is bound to the public scripting API).
    const scripts = Array.from(extension.registeredContentScripts.values())
      .filter(options => options.persistAcrossSessions)
      .map(options => makePublicContentScript(extension, options));

    // We want to replace all the scripts for the extension so we should delete
    // the existing ones first, and then write the new ones.
    //
    // TODO: Bug 1783131 - Implement individual updates without requiring all
    // data to be erased and written.
    await store.deleteAll(extension.id);
    await store.writeMany(extension.id, scripts);
    StartupCache.general.set(
      [extension.id, extension.version, "scripting", "hasPersistedScripts"],
      !!scripts.length
    );
  },

  // Delete all the persisted scripts for the given extension (id).
  //
  // NOTE: to be only used on addon uninstall, the extension entry in the StartupCache
  // is expected to also be fully cleared as part of handling the addon uninstall.
  async clearOnUninstall(extensionId) {
    await store.deleteAll(extensionId);
  },

  // As its name implies, don't use this method for anything but an easy access
  // to the internal store for testing purposes.
  _getStoreForTesting() {
    return store;
  },
};
PK
!<���z+z+&modules/ExtensionSearchHandler.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

// Used to keep track of all of the registered keywords, where each keyword is
// mapped to a KeywordInfo instance.
let gKeywordMap = new Map();

// Used to keep track of the active input session.
let gActiveInputSession = null;

// Used to keep track of who has control over the active suggestion callback
// so older callbacks can be ignored. The callback ID should increment whenever
// the input changes or the input session ends.
let gCurrentCallbackID = 0;

// Handles keeping track of information associated to the registered keyword.
class KeywordInfo {
  constructor(extension, description) {
    this._extension = extension;
    this._description = description;
  }

  get description() {
    return this._description;
  }

  set description(desc) {
    this._description = desc;
  }

  get extension() {
    return this._extension;
  }
}

// Responsible for handling communication between the extension and the urlbar.
class InputSession {
  constructor(keyword, extension) {
    this._keyword = keyword;
    this._extension = extension;
    this._suggestionsCallback = null;
    this._searchFinishedCallback = null;
  }

  get keyword() {
    return this._keyword;
  }

  addSuggestions(suggestions) {
    if (this._suggestionsCallback) {
      this._suggestionsCallback(suggestions);
    }
  }

  start(eventName) {
    this._extension.emit(eventName);
  }

  update(eventName, text, suggestionsCallback, searchFinishedCallback) {
    // Check to see if an existing input session needs to be ended first.
    if (this._searchFinishedCallback) {
      this._searchFinishedCallback();
    }
    this._searchFinishedCallback = searchFinishedCallback;
    this._suggestionsCallback = suggestionsCallback;
    this._extension.emit(eventName, text, ++gCurrentCallbackID);
  }

  cancel(eventName) {
    if (this._searchFinishedCallback) {
      this._searchFinishedCallback();
      this._searchFinishedCallback = null;
      this._suggestionsCallback = null;
    }
    this._extension.emit(eventName);
  }

  end(eventName, text, disposition) {
    if (this._searchFinishedCallback) {
      this._searchFinishedCallback();
      this._searchFinishedCallback = null;
      this._suggestionsCallback = null;
    }
    this._extension.emit(eventName, text, disposition);
  }
}

export var ExtensionSearchHandler = Object.freeze({
  MSG_INPUT_STARTED: "webext-omnibox-input-started",
  MSG_INPUT_CHANGED: "webext-omnibox-input-changed",
  MSG_INPUT_ENTERED: "webext-omnibox-input-entered",
  MSG_INPUT_CANCELLED: "webext-omnibox-input-cancelled",
  MSG_INPUT_DELETED: "webext-omnibox-input-deleted",

  /**
   * Registers a keyword.
   *
   * @param {string} keyword The keyword to register.
   * @param {Extension} extension The extension registering the keyword.
   */
  registerKeyword(keyword, extension) {
    if (gKeywordMap.has(keyword)) {
      throw new Error(
        `The keyword provided is already registered: "${keyword}"`
      );
    }
    gKeywordMap.set(keyword, new KeywordInfo(extension, extension.name));
  },

  /**
   * Unregisters a keyword.
   *
   * @param {string} keyword The keyword to unregister.
   */
  unregisterKeyword(keyword) {
    if (!gKeywordMap.has(keyword)) {
      throw new Error(`The keyword provided is not registered: "${keyword}"`);
    }
    gActiveInputSession = null;
    gKeywordMap.delete(keyword);
  },

  /**
   * Checks if a keyword is registered.
   *
   * @param {string} keyword The word to check.
   * @return {boolean} true if the word is a registered keyword.
   */
  isKeywordRegistered(keyword) {
    return gKeywordMap.has(keyword);
  },

  /**
   * @return {boolean} true if there is an active input session.
   */
  hasActiveInputSession() {
    return gActiveInputSession != null;
  },

  /**
   * @param {string} keyword The keyword to look up.
   * @return {string} the description to use for the heuristic result.
   */
  getDescription(keyword) {
    if (!gKeywordMap.has(keyword)) {
      throw new Error(`The keyword provided is not registered: "${keyword}"`);
    }
    return gKeywordMap.get(keyword).description;
  },

  /**
   * Sets the default suggestion for the registered keyword. The suggestion's
   * description will be used for the comment in the heuristic result.
   *
   * @param {string} keyword The keyword.
   * @param {string} description The description to use for the heuristic result.
   */
  setDefaultSuggestion(keyword, { description }) {
    if (!gKeywordMap.has(keyword)) {
      throw new Error(`The keyword provided is not registered: "${keyword}"`);
    }
    gKeywordMap.get(keyword).description = description;
  },

  /**
   * Adds suggestions for the registered keyword. This function will throw if
   * the keyword provided is not registered or active, or if the callback ID
   * provided is no longer equal to the active callback ID.
   *
   * @param {string} keyword The keyword.
   * @param {integer} id The ID of the suggestion callback.
   * @param {Array<Object>} suggestions An array of suggestions to provide to the urlbar.
   */
  addSuggestions(keyword, id, suggestions) {
    if (!gKeywordMap.has(keyword)) {
      throw new Error(`The keyword provided is not registered: "${keyword}"`);
    }

    if (!gActiveInputSession || gActiveInputSession.keyword != keyword) {
      throw new Error(
        `The keyword provided is not apart of an active input session: "${keyword}"`
      );
    }

    if (id != gCurrentCallbackID) {
      throw new Error(
        `The callback is no longer active for the keyword provided: "${keyword}"`
      );
    }

    gActiveInputSession.addSuggestions(suggestions);
  },

  /**
   * Called when the input in the urlbar begins with `<keyword><space>`.
   *
   * If the keyword is inactive, MSG_INPUT_STARTED is emitted and the
   * keyword is marked as active. If the keyword is followed by any text,
   * MSG_INPUT_CHANGED is fired with the current callback ID that can be
   * used to provide suggestions to the urlbar while the callback ID is active.
   * The callback is invalidated when either the input changes or the urlbar blurs.
   *
   * @param {object} data An object that contains
   *                 {string} keyword The keyword to handle.
   *                 {string} text The search text in the urlbar.
   *                 {boolean} inPrivateWindow privateness of window search
   *                           is occuring in.
   * @param {Function} callback The callback used to provide search suggestions.
   * @return {Promise} promise that resolves when the current search is complete.
   */
  handleSearch(data, callback) {
    let { keyword, text } = data;
    let keywordInfo = gKeywordMap.get(keyword);
    if (!keywordInfo) {
      throw new Error(`The keyword provided is not registered: "${keyword}"`);
    }

    let { extension } = keywordInfo;
    if (data.inPrivateWindow && !extension.privateBrowsingAllowed) {
      return Promise.resolve(false);
    }

    if (gActiveInputSession && gActiveInputSession.keyword != keyword) {
      throw new Error("A different input session is already ongoing");
    }

    if (!text || !text.startsWith(`${keyword} `)) {
      throw new Error(`The text provided must start with: "${keyword} "`);
    }

    if (!callback) {
      throw new Error("A callback must be provided");
    }

    // The search text in the urlbar currently starts with <keyword><space>, and
    // we only want the text that follows.
    text = text.substring(keyword.length + 1);

    // We fire MSG_INPUT_STARTED once we have <keyword><space>, and only fire
    // MSG_INPUT_CHANGED when we have text to process. This is different from Chrome's
    // behavior, which always fires MSG_INPUT_STARTED right before MSG_INPUT_CHANGED
    // first fires, but this is a bug in Chrome according to https://crbug.com/258911.
    if (!gActiveInputSession) {
      gActiveInputSession = new InputSession(keyword, extension);
      gActiveInputSession.start(this.MSG_INPUT_STARTED);

      // Resolve early if there is no text to process. There can be text to process when
      // the input starts if the user copy/pastes the text into the urlbar.
      if (!text.length) {
        return Promise.resolve();
      }
    }

    return new Promise(resolve => {
      gActiveInputSession.update(
        this.MSG_INPUT_CHANGED,
        text,
        callback,
        resolve
      );
    });
  },

  /**
   * Called when the user clicks on a suggestion that was added by
   * an extension. MSG_INPUT_ENTERED is emitted to the extension with
   * the keyword, the current search string, and info about how the
   * the search should be handled. This ends the active input session.
   *
   * @param {string} keyword The keyword associated to the suggestion.
   * @param {string} text The search text in the urlbar.
   * @param {string} where How the page should be opened. Accepted values are:
   *    "current": open the page in the same tab.
   *    "tab": open the page in a new foreground tab.
   *    "tabshifted": open the page in a new background tab.
   */
  handleInputEntered(keyword, text, where) {
    if (!gKeywordMap.has(keyword)) {
      throw new Error(`The keyword provided is not registered: "${keyword}"`);
    }

    if (!gActiveInputSession) {
      throw new Error("There is no active input session");
    }

    if (gActiveInputSession && gActiveInputSession.keyword != keyword) {
      throw new Error("A different input session is already ongoing");
    }

    if (!text || !text.startsWith(`${keyword} `)) {
      throw new Error(`The text provided must start with: "${keyword} "`);
    }

    let dispositionMap = {
      current: "currentTab",
      tab: "newForegroundTab",
      tabshifted: "newBackgroundTab",
    };
    let disposition = dispositionMap[where];

    if (!disposition) {
      throw new Error(`Invalid "where" argument: ${where}`);
    }

    // The search text in the urlbar currently starts with <keyword><space>, and
    // we only want to send the text that follows.
    text = text.substring(keyword.length + 1);

    gActiveInputSession.end(this.MSG_INPUT_ENTERED, text, disposition);
    gActiveInputSession = null;
  },

  /**
   * Called when the user deletes a suggestion that was added by
   * an extension. MSG_INPUT_DELETED is emitted to the extension with
   * the description of the suggestion that was deleted.
   *
   * @param {string} text The description of the suggestion.
   */
  handleInputDeleted(text) {
    return gActiveInputSession.update(this.MSG_INPUT_DELETED, text);
  },

  /**
   * If the user has ended the keyword input session without accepting the input,
   * MSG_INPUT_CANCELLED is emitted and the input session is ended.
   */
  handleInputCancelled() {
    if (!gActiveInputSession) {
      throw new Error("There is no active input session");
    }
    gActiveInputSession.cancel(this.MSG_INPUT_CANCELLED);
    gActiveInputSession = null;
  },
});
PK
!<�ų�\V\V&modules/ExtensionSettingsStore.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * @file
 * This module is used for storing changes to settings that are
 * requested by extensions, and for finding out what the current value
 * of a setting should be, based on the precedence chain.
 *
 * When multiple extensions request to make a change to a particular
 * setting, the most recently installed extension will be given
 * precedence.
 *
 * This precedence chain of settings is stored in JSON format,
 * without indentation, using UTF-8 encoding.
 * With indentation applied, the file would look like this:
 *
 * {
 *   type: { // The type of settings being stored in this object, i.e., prefs.
 *     key: { // The unique key for the setting.
 *       initialValue, // The initial value of the setting.
 *       precedenceList: [
 *         {
 *           id, // The id of the extension requesting the setting.
 *           installDate, // The install date of the extension, stored as a number.
 *           value, // The value of the setting requested by the extension.
 *           enabled // Whether the setting is currently enabled.
 *         }
 *       ],
 *     },
 *     key: {
 *       // ...
 *     }
 *   }
 * }
 */

import { ExtensionParent } from "resource://gre/modules/ExtensionParent.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  JSONFile: "resource://gre/modules/JSONFile.sys.mjs",
});

// Defined for readability of precedence and selection code.  keyInfo.selected will be
// one of these defines, or the id of an extension if an extension has been explicitly
// selected.
const SETTING_USER_SET = null;
const SETTING_PRECEDENCE_ORDER = undefined;

const JSON_FILE_NAME = "extension-settings.json";
const JSON_FILE_VERSION = 3;
const STORE_PATH = PathUtils.join(
  Services.dirsvc.get("ProfD", Ci.nsIFile).path,
  JSON_FILE_NAME
);

let _initializePromise;
let _store = {};

// Processes the JSON data when read from disk to convert string dates into numbers.
function dataPostProcessor(json) {
  if (json.version !== JSON_FILE_VERSION) {
    for (let storeType in json) {
      for (let setting in json[storeType]) {
        for (let extData of json[storeType][setting].precedenceList) {
          if (setting == "overrideContentColorScheme" && extData.value > 2) {
            extData.value = 2;
          }
          if (typeof extData.installDate != "number") {
            extData.installDate = new Date(extData.installDate).valueOf();
          }
        }
      }
    }
    json.version = JSON_FILE_VERSION;
  }
  return json;
}

// Loads the data from the JSON file into memory.
function initialize() {
  if (!_initializePromise) {
    _store = new lazy.JSONFile({
      path: STORE_PATH,
      dataPostProcessor,
    });
    _initializePromise = _store.load();
  }
  return _initializePromise;
}

// Test-only method to force reloading of the JSON file.
async function reloadFile(saveChanges) {
  if (!saveChanges) {
    // Disarm the saver so that the current changes are dropped.
    _store._saver.disarm();
  }
  await _store.finalize();
  _initializePromise = null;
  return initialize();
}

// Checks that the store is ready and that the requested type exists.
function ensureType(type) {
  if (!_store.dataReady) {
    throw new Error(
      "The ExtensionSettingsStore was accessed before the initialize promise resolved."
    );
  }

  // Ensure a property exists for the given type.
  if (!_store.data[type]) {
    _store.data[type] = {};
  }
}

/**
 * Return an object with properties for key, value|initialValue, id|null, or
 * null if no setting has been stored for that key.
 *
 * If no id is passed then return the highest priority item for the key.
 *
 * @param {string} type
 *        The type of setting to be retrieved.
 * @param {string} key
 *        A string that uniquely identifies the setting.
 * @param {string} [id]
 *        The id of the extension for which the item is being retrieved.
 *        If no id is passed, then the highest priority item for the key
 *        is returned.
 *
 * @returns {object | null}
 *          Either an object with properties for key and value, or
 *          null if no key is found.
 */
function getItem(type, key, id) {
  ensureType(type);

  let keyInfo = _store.data[type][key];
  if (!keyInfo) {
    return null;
  }

  // If no id was provided, the selected entry will have precedence.
  if (!id && keyInfo.selected) {
    id = keyInfo.selected;
  }
  if (id) {
    // Return the item that corresponds to the extension with id of id.
    let item = keyInfo.precedenceList.find(item => item.id === id);
    return item ? { key, value: item.value, id } : null;
  }

  // Find the highest precedence, enabled setting, if it has not been
  // user set.
  if (keyInfo.selected === SETTING_PRECEDENCE_ORDER) {
    for (let item of keyInfo.precedenceList) {
      if (item.enabled) {
        return { key, value: item.value, id: item.id };
      }
    }
  }

  // Nothing found in the precedenceList or the setting is user-set,
  // return the initialValue.
  return { key, initialValue: keyInfo.initialValue };
}

/**
 * Return an array of objects with properties for key, value, id, and enabled
 * or an empty array if no settings have been stored for that key.
 *
 * @param {string} type
 *        The type of setting to be retrieved.
 * @param {string} key
 *        A string that uniquely identifies the setting.
 *
 * @returns {Array} an array of objects with properties for key, value, id, and enabled
 */
function getAllItems(type, key) {
  ensureType(type);

  let keyInfo = _store.data[type][key];
  if (!keyInfo) {
    return [];
  }

  let items = keyInfo.precedenceList;
  return items
    ? items.map(item => ({
        key,
        value: item.value,
        id: item.id,
        enabled: item.enabled,
      }))
    : [];
}

// Comparator used when sorting the precedence list.
function precedenceComparator(a, b) {
  if (a.enabled && !b.enabled) {
    return -1;
  }
  if (b.enabled && !a.enabled) {
    return 1;
  }
  return b.installDate - a.installDate;
}

/**
 * Helper method that alters a setting, either by changing its enabled status
 * or by removing it.
 *
 * @param {string|null} id
 *        The id of the extension for which a setting is being altered, may also
 *        be SETTING_USER_SET (null).
 * @param {string} type
 *        The type of setting to be altered.
 * @param {string} key
 *        A string that uniquely identifies the setting.
 * @param {string} action
 *        The action to perform on the setting.
 *        Will be one of remove|enable|disable.
 *
 * @returns {object | null}
 *          Either an object with properties for key and value, which
 *          corresponds to the current top precedent setting, or null if
 *          the current top precedent setting has not changed.
 */
function alterSetting(id, type, key, action) {
  let returnItem = null;
  ensureType(type);

  let keyInfo = _store.data[type][key];
  if (!keyInfo) {
    if (action === "remove") {
      return null;
    }
    throw new Error(
      `Cannot alter the setting for ${type}:${key} as it does not exist.`
    );
  }

  let foundIndex = keyInfo.precedenceList.findIndex(item => item.id == id);

  if (foundIndex === -1 && (action !== "select" || id !== SETTING_USER_SET)) {
    if (action === "remove") {
      return null;
    }
    throw new Error(
      `Cannot alter the setting for ${type}:${key} as ${id} does not exist.`
    );
  }

  let selected = keyInfo.selected;
  switch (action) {
    case "select":
      if (foundIndex >= 0 && !keyInfo.precedenceList[foundIndex].enabled) {
        throw new Error(
          `Cannot select the setting for ${type}:${key} as ${id} is disabled.`
        );
      }
      keyInfo.selected = id;
      keyInfo.selectedDate = Date.now();
      break;

    case "remove":
      // Removing a user-set setting reverts to precedence order.
      if (id === keyInfo.selected) {
        keyInfo.selected = SETTING_PRECEDENCE_ORDER;
        delete keyInfo.selectedDate;
      }
      keyInfo.precedenceList.splice(foundIndex, 1);
      break;

    case "enable":
      keyInfo.precedenceList[foundIndex].enabled = true;
      keyInfo.precedenceList.sort(precedenceComparator);
      // Enabling a setting does not change a user-set setting, so we
      // save and bail early.
      if (keyInfo.selected !== SETTING_PRECEDENCE_ORDER) {
        _store.saveSoon();
        return null;
      }
      foundIndex = keyInfo.precedenceList.findIndex(item => item.id == id);
      break;

    case "disable":
      // Disabling a user-set setting reverts to precedence order.
      if (keyInfo.selected === id) {
        keyInfo.selected = SETTING_PRECEDENCE_ORDER;
        delete keyInfo.selectedDate;
      }
      keyInfo.precedenceList[foundIndex].enabled = false;
      keyInfo.precedenceList.sort(precedenceComparator);
      break;

    default:
      throw new Error(`${action} is not a valid action for alterSetting.`);
  }

  if (selected !== keyInfo.selected || foundIndex === 0) {
    returnItem = getItem(type, key);
  }

  if (action === "remove" && keyInfo.precedenceList.length === 0) {
    delete _store.data[type][key];
  }

  _store.saveSoon();
  ExtensionParent.apiManager.emit("extension-setting-changed", {
    action,
    id,
    type,
    key,
    item: returnItem,
  });
  return returnItem;
}

export var ExtensionSettingsStore = {
  SETTING_USER_SET,

  /**
   * Loads the JSON file for the SettingsStore into memory.
   * The promise this returns must be resolved before asking the SettingsStore
   * to perform any other operations.
   *
   * @returns {Promise}
   *          A promise that resolves when the Store is ready to be accessed.
   */
  initialize() {
    return initialize();
  },

  /**
   * Adds a setting to the store, returning the new setting if it changes.
   *
   * @param {string} id
   *        The id of the extension for which a setting is being added.
   * @param {string} type
   *        The type of setting to be stored.
   * @param {string} key
   *        A string that uniquely identifies the setting.
   * @param {string} value
   *        The value to be stored in the setting.
   * @param {Function} initialValueCallback
   *        A function to be called to determine the initial value for the
   *        setting. This will be passed the value in the callbackArgument
   *        argument. If omitted the initial value will be undefined.
   * @param {any} callbackArgument
   *        The value to be passed into the initialValueCallback. It defaults to
   *        the value of the key argument.
   * @param {Function} settingDataUpdate
   *        A function to be called to modify the initial value if necessary.
   *
   * @returns {Promise<object?>} Either an object with properties for key and
   *                          value, which corresponds to the item that was
   *                          just added, or null if the item that was just
   *                          added does not need to be set because it is not
   *                          selected or at the top of the precedence list.
   */
  async addSetting(
    id,
    type,
    key,
    value,
    initialValueCallback = () => undefined,
    callbackArgument = key,
    settingDataUpdate = val => val
  ) {
    if (typeof initialValueCallback != "function") {
      throw new Error("initialValueCallback must be a function.");
    }

    ensureType(type);

    if (!_store.data[type][key]) {
      // The setting for this key does not exist. Set the initial value.
      let initialValue = await initialValueCallback(callbackArgument);
      _store.data[type][key] = {
        initialValue,
        precedenceList: [],
      };
    }
    let keyInfo = _store.data[type][key];

    // Allow settings to upgrade the initial value if necessary.
    keyInfo.initialValue = settingDataUpdate(keyInfo.initialValue);

    // Check for this item in the precedenceList.
    let foundIndex = keyInfo.precedenceList.findIndex(item => item.id == id);
    let newInstall = false;
    if (foundIndex === -1) {
      // No item for this extension, so add a new one.
      let addon = await lazy.AddonManager.getAddonByID(id);
      keyInfo.precedenceList.push({
        id,
        installDate: addon.installDate.valueOf(),
        value,
        enabled: true,
      });
      newInstall = addon.installDate.valueOf() > keyInfo.selectedDate;
    } else {
      // Item already exists or this extension, so update it.
      let item = keyInfo.precedenceList[foundIndex];
      item.value = value;
      // Ensure the item is enabled.
      item.enabled = true;
    }

    // Sort the list.
    keyInfo.precedenceList.sort(precedenceComparator);
    foundIndex = keyInfo.precedenceList.findIndex(item => item.id == id);

    // If our new setting is top of precedence, then reset the selected entry.
    if (foundIndex === 0 && newInstall) {
      keyInfo.selected = SETTING_PRECEDENCE_ORDER;
      delete keyInfo.selectedDate;
    }

    _store.saveSoon();

    // Check whether this is currently selected item if one is
    // selected, otherwise the top item has precedence.
    if (
      keyInfo.selected !== SETTING_USER_SET &&
      (keyInfo.selected === id || foundIndex === 0)
    ) {
      return { id, key, value };
    }
    return null;
  },

  /**
   * Removes a setting from the store, returning the new setting if it changes.
   *
   * @param {string} id
   *        The id of the extension for which a setting is being removed.
   * @param {string} type
   *        The type of setting to be removed.
   * @param {string} key
   *        A string that uniquely identifies the setting.
   *
   * @returns {object | null}
   *          Either an object with properties for key and value if the setting changes, or null.
   */
  removeSetting(id, type, key) {
    return alterSetting(id, type, key, "remove");
  },

  /**
   * Enables a setting in the store, returning the new setting if it changes.
   *
   * @param {string} id
   *        The id of the extension for which a setting is being enabled.
   * @param {string} type
   *        The type of setting to be enabled.
   * @param {string} key
   *        A string that uniquely identifies the setting.
   *
   * @returns {object | null}
   *          Either an object with properties for key and value if the setting changes, or null.
   */
  enable(id, type, key) {
    return alterSetting(id, type, key, "enable");
  },

  /**
   * Disables a setting in the store, returning the new setting if it changes.
   *
   * @param {string} id
   *        The id of the extension for which a setting is being disabled.
   * @param {string} type
   *        The type of setting to be disabled.
   * @param {string} key
   *        A string that uniquely identifies the setting.
   *
   * @returns {object | null}
   *          Either an object with properties for key and value if the setting changes, or null.
   */
  disable(id, type, key) {
    return alterSetting(id, type, key, "disable");
  },

  /**
   * Specifically select an extension, or no extension, that will be in control of
   * this setting.
   *
   * To select a specific extension that controls this setting, pass the extension id.
   *
   * To select as user-set  pass SETTING_USER_SET as the id.  In this case, no extension
   * will have control of the setting.
   *
   * Once a specific selection is made, precedence order will not be used again unless the selected
   * extension is disabled, removed, or a new extension takes control of the setting.
   *
   * @param {string | null} id
   *        The id of the extension being selected or SETTING_USER_SET (null).
   * @param {string} type
   *        The type of setting to be selected.
   * @param {string} key
   *        A string that uniquely identifies the setting.
   *
   * @returns {object | null}
   *          Either an object with properties for key and value if the setting changes, or null.
   */
  select(id, type, key) {
    return alterSetting(id, type, key, "select");
  },

  /**
   * Retrieves all settings from the store for a given extension.
   *
   * @param {string} id
   *        The id of the extension for which a settings are being retrieved.
   * @param {string} type
   *        The type of setting to be returned.
   *
   * @returns {Array}
   *          A list of settings which have been stored for the extension.
   */
  getAllForExtension(id, type) {
    ensureType(type);

    let keysObj = _store.data[type];
    let items = [];
    for (let key in keysObj) {
      if (keysObj[key].precedenceList.find(item => item.id == id)) {
        items.push(key);
      }
    }
    return items;
  },

  /**
   * Retrieves a setting from the store, either for a specific extension,
   * or current top precedent setting for the key.
   *
   * @param {string} type The type of setting to be returned.
   * @param {string} key A string that uniquely identifies the setting.
   * @param {string} id
   *        The id of the extension for which the setting is being retrieved.
   *        Defaults to undefined, in which case the top setting is returned.
   *
   * @returns {object} An object with properties for key, value and id.
   */
  getSetting(type, key, id) {
    return getItem(type, key, id);
  },

  /**
   * Retrieves an array of objects representing extensions attempting to control the specified setting
   * or an empty array if no settings have been stored for that key.
   *
   * @param {string} type
   *        The type of setting to be retrieved.
   * @param {string} key
   *        A string that uniquely identifies the setting.
   *
   * @returns {Array} an array of objects with properties for key, value, id, and enabled
   */
  getAllSettings(type, key) {
    return getAllItems(type, key);
  },

  /**
   * Returns whether an extension currently has a stored setting for a given
   * key.
   *
   * @param {string} id The id of the extension which is being checked.
   * @param {string} type The type of setting to be checked.
   * @param {string} key A string that uniquely identifies the setting.
   *
   * @returns {boolean} Whether the extension currently has a stored setting.
   */
  hasSetting(id, type, key) {
    return this.getAllForExtension(id, type).includes(key);
  },

  /**
   * Return the levelOfControl for a key / extension combo.
   * levelOfControl is required by Google's ChromeSetting prototype which
   * in turn is used by the privacy API among others.
   *
   * It informs a caller of the state of a setting with respect to the current
   * extension, and can be one of the following values:
   *
   * controlled_by_other_extensions: controlled by extensions with higher precedence
   * controllable_by_this_extension: can be controlled by this extension
   * controlled_by_this_extension: controlled by this extension
   *
   * @param {string} id
   *        The id of the extension for which levelOfControl is being requested.
   * @param {string} type
   *        The type of setting to be returned. For example `pref`.
   * @param {string} key
   *        A string that uniquely identifies the setting, for example, a
   *        preference name.
   *
   * @returns {Promise<string>}
   *          The level of control of the extension over the key.
   */
  async getLevelOfControl(id, type, key) {
    ensureType(type);

    let keyInfo = _store.data[type][key];
    if (!keyInfo || !keyInfo.precedenceList.length) {
      return "controllable_by_this_extension";
    }

    if (keyInfo.selected !== SETTING_PRECEDENCE_ORDER) {
      if (id === keyInfo.selected) {
        return "controlled_by_this_extension";
      }
      // When user set, the setting is never "controllable" unless the installDate
      // is later than the user date.
      let addon = await lazy.AddonManager.getAddonByID(id);
      return !addon || keyInfo.selectedDate > addon.installDate.valueOf()
        ? "not_controllable"
        : "controllable_by_this_extension";
    }

    let enabledItems = keyInfo.precedenceList.filter(item => item.enabled);
    if (!enabledItems.length) {
      return "controllable_by_this_extension";
    }

    let topItem = enabledItems[0];
    if (topItem.id == id) {
      return "controlled_by_this_extension";
    }

    let addon = await lazy.AddonManager.getAddonByID(id);
    return !addon || topItem.installDate > addon.installDate.valueOf()
      ? "controlled_by_other_extensions"
      : "controllable_by_this_extension";
  },

  /**
   * Test-only method to force reloading of the JSON file.
   *
   * Note that this method simply clears the local variable that stores the
   * file, so the next time the file is accessed it will be reloaded.
   *
   * @param   {boolean} saveChanges
   *          When false, discard any changes that have been made since the last
   *          time the store was saved.
   * @returns {Promise}
   *          A promise that resolves once the settings store has been cleared.
   */
  _reloadFile(saveChanges = true) {
    return reloadFile(saveChanges);
  },
};

// eslint-disable-next-line mozilla/balanced-listeners
ExtensionParent.apiManager.on("uninstall-complete", async (type, { id }) => {
  // Catch any settings that were not properly removed during "uninstall".
  await ExtensionSettingsStore.initialize();
  for (let type in _store.data) {
    // prefs settings must be handled by ExtensionPreferencesManager.
    if (type === "prefs") {
      continue;
    }
    let items = ExtensionSettingsStore.getAllForExtension(id, type);
    for (let key of items) {
      ExtensionSettingsStore.removeSetting(id, type, key);
      Services.console.logStringMessage(
        `Post-Uninstall removal of addon settings for ${id}, type: ${type} key: ${key}`
      );
    }
  }
});
PK
!<U���gAgA"modules/ExtensionShortcuts.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ExtensionCommon } from "resource://gre/modules/ExtensionCommon.sys.mjs";

import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

/** @type {Lazy} */
const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs",
  ExtensionSettingsStore:
    "resource://gre/modules/ExtensionSettingsStore.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
  ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
});

/**
 * These properties cannot be lazy getters otherwise they
 * get defined on first use, at a time when some modules
 * may not have been loaded.  In that case, the getter would
 * become undefined until next app restart.
 */
Object.defineProperties(lazy, {
  windowTracker: {
    get() {
      return lazy.ExtensionParent.apiManager.global.windowTracker;
    },
  },
  browserActionFor: {
    get() {
      return lazy.ExtensionParent.apiManager.global.browserActionFor;
    },
  },
  pageActionFor: {
    get() {
      return lazy.ExtensionParent.apiManager.global.pageActionFor;
    },
  },
  sidebarActionFor: {
    get() {
      return lazy.ExtensionParent.apiManager.global.sidebarActionFor;
    },
  },
});

const { ExtensionError, DefaultMap } = ExtensionUtils;
const { makeWidgetId } = ExtensionCommon;

const EXECUTE_SIDEBAR_ACTION = "_execute_sidebar_action";

function normalizeShortcut(shortcut) {
  return shortcut ? shortcut.replace(/\s+/g, "") : "";
}

export class ExtensionShortcutKeyMap extends DefaultMap {
  async buildForAddonIds(addonIds) {
    this.clear();
    for (const addonId of addonIds) {
      const policy = WebExtensionPolicy.getByID(addonId);
      if (policy?.extension?.shortcuts) {
        const { shortcuts } = policy.extension;
        for (const command of await shortcuts.allCommands()) {
          this.recordShortcut(command.shortcut, policy.name, command.name);
        }
      }
    }
  }

  recordShortcut(shortcutString, addonName, commandName) {
    if (!shortcutString) {
      return;
    }

    const valueSet = this.get(shortcutString);
    valueSet.add({ addonName, commandName });
  }

  removeShortcut(shortcutString, addonName, commandName) {
    if (!this.has(shortcutString)) {
      return;
    }

    const valueSet = this.get(shortcutString);
    for (const entry of valueSet.values()) {
      if (entry.addonName === addonName && entry.commandName === commandName) {
        valueSet.delete(entry);
      }
    }
    if (valueSet.size === 0) {
      this.delete(shortcutString);
    }
  }

  getFirstAddonName(shortcutString) {
    if (this.has(shortcutString)) {
      return this.get(shortcutString).values().next().value.addonName;
    }
    return null;
  }

  has(shortcutString) {
    const platformShortcut = this.getPlatformShortcutString(shortcutString);
    return super.has(platformShortcut) && super.get(platformShortcut).size > 0;
  }

  // Class internals.

  constructor() {
    super(() => new Set());

    // Overridden in some unit test to make it easier to cover some
    // platform specific behaviors (in particular the platform specific.
    // normalization of the shortcuts using the Ctrl modifier on macOS).
    this._os = lazy.ExtensionParent.PlatformInfo.os;
  }

  getPlatformShortcutString(shortcutString) {
    if (this._os == "mac") {
      // when running on macos, make sure to also track in the shortcutKeyMap
      // (which is used to check for duplicated shortcuts) a shortcut string
      // that replace the `Ctrl` modifiers with the `Command` modified:
      // they are going to be the same accel in the key element generated,
      // by tracking both of them shortcut string value would confuse the about:addons "Manager Shortcuts"
      // view and make it unable to correctly catch conflicts on mac
      // (See bug 1565854).
      shortcutString = shortcutString
        .split("+")
        .map(p => (p === "Ctrl" ? "Command" : p))
        .join("+");
    }

    return shortcutString;
  }

  get(shortcutString) {
    const platformShortcut = this.getPlatformShortcutString(shortcutString);
    return super.get(platformShortcut);
  }

  add(shortcutString, addonCommandValue) {
    const setValue = this.get(shortcutString);
    setValue.add(addonCommandValue);
  }

  delete(shortcutString) {
    const platformShortcut = this.getPlatformShortcutString(shortcutString);
    return super.delete(platformShortcut);
  }
}

/**
 * An instance of this class is assigned to the shortcuts property of each
 * active webextension that has commands defined.
 *
 * It manages loading any updated shortcuts along with the ones defined in
 * the manifest and registering them to a browser window. It also provides
 * the list, update and reset APIs for the browser.commands interface and
 * the about:addons manage shortcuts page.
 */
export class ExtensionShortcuts {
  static async removeCommandsFromStorage(extensionId) {
    // Cleanup the updated commands. In some cases the extension is installed
    // and uninstalled so quickly that `this.commands` hasn't loaded yet. To
    // handle that we need to make sure ExtensionSettingsStore is initialized
    // before we clean it up.
    await lazy.ExtensionSettingsStore.initialize();
    lazy.ExtensionSettingsStore.getAllForExtension(
      extensionId,
      "commands"
    ).forEach(key => {
      lazy.ExtensionSettingsStore.removeSetting(extensionId, "commands", key);
    });
  }

  constructor({ extension, onCommand, onShortcutChanged }) {
    this.keysetsMap = new WeakMap();
    this.windowOpenListener = null;
    this.extension = extension;
    this.onCommand = onCommand;
    this.onShortcutChanged = onShortcutChanged;
    this.id = makeWidgetId(extension.id);
  }

  async allCommands() {
    let commands = await this.commands;
    return Array.from(commands, ([name, command]) => {
      return {
        name,
        description: command.description,
        shortcut: command.shortcut,
      };
    });
  }

  async updateCommand({ name, description, shortcut }) {
    let { extension } = this;
    let commands = await this.commands;
    let command = commands.get(name);

    if (!command) {
      throw new ExtensionError(`Unknown command "${name}"`);
    }

    // Only store the updates so manifest changes can take precedence
    // later.
    let previousUpdates = await lazy.ExtensionSettingsStore.getSetting(
      "commands",
      name,
      extension.id
    );
    let commandUpdates = (previousUpdates && previousUpdates.value) || {};

    if (description && description != command.description) {
      commandUpdates.description = description;
      command.description = description;
    }

    let oldShortcut = command.shortcut;

    if (shortcut != null && shortcut != command.shortcut) {
      shortcut = normalizeShortcut(shortcut);
      commandUpdates.shortcut = shortcut;
      command.shortcut = shortcut;
    }

    await lazy.ExtensionSettingsStore.addSetting(
      extension.id,
      "commands",
      name,
      commandUpdates
    );

    this.registerKeys(commands);

    if (command.shortcut !== oldShortcut) {
      this.onShortcutChanged({
        name,
        newShortcut: command.shortcut,
        oldShortcut,
      });
    }
  }

  async resetCommand(name) {
    let { extension, manifestCommands } = this;
    let commands = await this.commands;
    let command = commands.get(name);

    if (!command) {
      throw new ExtensionError(`Unknown command "${name}"`);
    }

    let storedCommand = lazy.ExtensionSettingsStore.getSetting(
      "commands",
      name,
      extension.id
    );

    if (storedCommand && storedCommand.value) {
      commands.set(name, { ...manifestCommands.get(name) });

      lazy.ExtensionSettingsStore.removeSetting(extension.id, "commands", name);
      if (
        name === "_execute_action" &&
        extension.manifestVersion > 2 &&
        lazy.ExtensionSettingsStore.hasSetting(
          extension.id,
          "commands",
          "_execute_browser_action"
        )
      ) {
        lazy.ExtensionSettingsStore.removeSetting(
          extension.id,
          "commands",
          "_execute_browser_action"
        );
      }

      this.registerKeys(commands);
    }
  }

  loadCommands() {
    let { extension } = this;

    // Map[{String} commandName -> {Object} commandProperties]
    this.manifestCommands = this.loadCommandsFromManifest(extension.manifest);

    this.commands = (async () => {
      // Deep copy the manifest commands to commands so we can keep the original
      // manifest commands and update commands as needed.
      let commands = new Map();
      this.manifestCommands.forEach((command, name) => {
        commands.set(name, { ...command });
      });

      // Update the manifest commands with the persisted updates from
      // browser.commands.update().
      let savedCommands = await this.loadCommandsFromStorage(extension.id);
      savedCommands.forEach((update, name) => {
        let command = commands.get(name);
        if (
          name === "_execute_browser_action" &&
          extension.manifestVersion > 2
        ) {
          // Ignore the old _execute_browser_action if there is data stored for
          // the new _execute_action command. Otherwise use the stored data for
          // `_execute_action` (since we renamed `_execute_browser_action` to
          // `_execute_action` in MV3).
          command = savedCommands.has("_execute_action")
            ? null
            : commands.get("_execute_action");
        }

        if (command) {
          // We will only update commands, not add them.
          Object.assign(command, update);
        }
      });

      return commands;
    })();
  }

  registerKeys(commands) {
    for (let window of lazy.windowTracker.browserWindows()) {
      this.registerKeysToDocument(window, commands);
    }
  }

  /**
   * Registers the commands to all open windows and to any which
   * are later created.
   */
  async register() {
    let commands = await this.commands;
    this.registerKeys(commands);

    this.windowOpenListener = window => {
      if (!this.keysetsMap.has(window)) {
        this.registerKeysToDocument(window, commands);
      }
    };

    lazy.windowTracker.addOpenListener(this.windowOpenListener);
  }

  /**
   * Unregisters the commands from all open windows and stops commands
   * from being registered to windows which are later created.
   */
  unregister() {
    for (let window of lazy.windowTracker.browserWindows()) {
      if (this.keysetsMap.has(window)) {
        this.keysetsMap.get(window).remove();
      }
    }

    lazy.windowTracker.removeOpenListener(this.windowOpenListener);
  }

  /**
   * Creates a Map from commands for each command in the manifest.commands object.
   *
   * @param {object} manifest The manifest JSON object.
   * @returns {Map<string, object>}
   */
  loadCommandsFromManifest(manifest) {
    let commands = new Map();
    // For Windows, chrome.runtime expects 'win' while chrome.commands
    // expects 'windows'.  We can special case this for now.
    let { PlatformInfo } = lazy.ExtensionParent;
    let os = PlatformInfo.os == "win" ? "windows" : PlatformInfo.os;
    for (let [name, command] of Object.entries(manifest.commands)) {
      let suggested_key = command.suggested_key || {};
      let shortcut = normalizeShortcut(
        suggested_key[os] || suggested_key.default
      );
      commands.set(name, {
        description: command.description,
        shortcut,
      });
    }
    return commands;
  }

  async loadCommandsFromStorage(extensionId) {
    await lazy.ExtensionSettingsStore.initialize();
    let names = lazy.ExtensionSettingsStore.getAllForExtension(
      extensionId,
      "commands"
    );
    return names.reduce((map, name) => {
      let command = lazy.ExtensionSettingsStore.getSetting(
        "commands",
        name,
        extensionId
      ).value;
      return map.set(name, command);
    }, new Map());
  }

  /**
   * Registers the commands to a document.
   *
   * @param {ChromeWindow} window The XUL window to insert the Keyset.
   * @param {Map} commands The commands to be set.
   */
  registerKeysToDocument(window, commands) {
    if (
      !this.extension.privateBrowsingAllowed &&
      lazy.PrivateBrowsingUtils.isWindowPrivate(window)
    ) {
      return;
    }

    let doc = window.document;
    let keyset = doc.createXULElement("keyset");
    keyset.id = `ext-keyset-id-${this.id}`;
    if (this.keysetsMap.has(window)) {
      this.keysetsMap.get(window).remove();
    }
    let sidebarKey;
    for (let [name, command] of commands) {
      if (command.shortcut) {
        let parts = command.shortcut.split("+");

        // The key is always the last element.
        let key = parts.pop();

        if (/^[0-9]$/.test(key)) {
          let shortcutWithNumpad = command.shortcut.replace(
            /[0-9]$/,
            "Numpad$&"
          );
          let numpadKeyElement = this.buildKey(doc, name, shortcutWithNumpad);
          keyset.appendChild(numpadKeyElement);
        }

        let keyElement = this.buildKey(doc, name, command.shortcut);
        keyset.appendChild(keyElement);
        if (name == EXECUTE_SIDEBAR_ACTION) {
          sidebarKey = keyElement;
        }
      }
    }
    doc.documentElement.appendChild(keyset);
    if (sidebarKey) {
      window.SidebarController.updateShortcut({ keyId: sidebarKey.id });
    }
    this.keysetsMap.set(window, keyset);
  }

  /**
   * Builds a XUL Key element and attaches an onCommand listener which
   * emits a command event with the provided name when fired.
   *
   * @param {Document} doc The XUL document.
   * @param {string} name The name of the command.
   * @param {string} shortcut The shortcut provided in the manifest.
   * @see https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/key
   *
   * @returns {Element} The newly created Key element.
   */
  buildKey(doc, name, shortcut) {
    let keyElement = this.buildKeyFromShortcut(doc, name, shortcut);

    // We need to have the attribute "oncommand" for the "command" listener to fire,
    // and it is currently ignored when set to the empty string.
    keyElement.setAttribute("oncommand", "//");

    /* eslint-disable mozilla/balanced-listeners */
    // We remove all references to the key elements when the extension is shutdown,
    // therefore the listeners for these elements will be garbage collected.
    keyElement.addEventListener("command", event => {
      let action;
      let _execute_action =
        this.extension.manifestVersion < 3
          ? "_execute_browser_action"
          : "_execute_action";

      let actionFor = {
        [_execute_action]: lazy.browserActionFor,
        _execute_page_action: lazy.pageActionFor,
        _execute_sidebar_action: lazy.sidebarActionFor,
      }[name];

      if (actionFor) {
        action = actionFor(this.extension);
        let win = event.target.ownerGlobal;
        action.triggerAction(win);
      } else {
        this.onCommand(name);
      }
    });
    /* eslint-enable mozilla/balanced-listeners */

    return keyElement;
  }

  /**
   * Builds a XUL Key element from the provided shortcut.
   *
   * @param {Document} doc The XUL document.
   * @param {string} name The name of the shortcut.
   * @param {string} shortcut The shortcut provided in the manifest.
   *
   * @see https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/key
   * @returns {Element} The newly created Key element.
   */
  buildKeyFromShortcut(doc, name, shortcut) {
    let keyElement = doc.createXULElement("key");

    let parts = shortcut.split("+");

    // The key is always the last element.
    let chromeKey = parts.pop();

    // The modifiers are the remaining elements.
    keyElement.setAttribute(
      "modifiers",
      lazy.ShortcutUtils.getModifiersAttribute(parts)
    );

    // A keyElement with key "NumpadX" is created above and isn't from the
    // manifest. The id will be set on the keyElement with key "X" only.
    if (name == EXECUTE_SIDEBAR_ACTION && !chromeKey.startsWith("Numpad")) {
      let id = `ext-key-id-${this.id}-sidebar-action`;
      keyElement.setAttribute("id", id);
    }

    let [attribute, value] = lazy.ShortcutUtils.getKeyAttribute(chromeKey);
    keyElement.setAttribute(attribute, value);
    if (attribute == "keycode") {
      keyElement.setAttribute("event", "keydown");
    }

    return keyElement;
  }
}
PK
!<��)��J�J modules/ExtensionStorage.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const { DefaultWeakMap, ExtensionError } = ExtensionUtils;

/** @type {Lazy} */
const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ExtensionCommon: "resource://gre/modules/ExtensionCommon.sys.mjs",
  JSONFile: "resource://gre/modules/JSONFile.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "enforceSessionQuota",
  "webextensions.storage.session.enforceQuota",
  false
);

function isStructuredCloneHolder(value) {
  return (
    value &&
    typeof value === "object" &&
    Cu.getClassName(value, true) === "StructuredCloneHolder"
  );
}

class SerializeableMap extends Map {
  toJSON() {
    let result = {};
    for (let [key, value] of this) {
      if (isStructuredCloneHolder(value)) {
        value = value.deserialize(globalThis);
        this.set(key, value);
      }

      result[key] = value;
    }
    return result;
  }

  /**
   * Like toJSON, but attempts to serialize every value separately, and
   * elides any which fail to serialize. Should only be used if initial
   * JSON serialization fails.
   *
   * @returns {object}
   */
  toJSONSafe() {
    let result = {};
    for (let [key, value] of this) {
      try {
        void JSON.stringify(value);

        result[key] = value;
      } catch (e) {
        Cu.reportError(
          new Error(`Failed to serialize browser.storage key "${key}": ${e}`)
        );
      }
    }
    return result;
  }
}

/**
 * Serializes an arbitrary value into a StructuredCloneHolder, if
 * appropriate. Existing StructuredCloneHolders are returned unchanged.
 * Non-object values are also returned unchanged. Anything else is
 * serialized, and a new StructuredCloneHolder returned.
 *
 * This allows us to avoid a second structured clone operation after
 * sending a storage value across a message manager, before cloning it
 * into an extension scope.
 *
 * @param {string} name
 *        A debugging name for the value, which will appear in the
 *        StructuredCloneHolder's about:memory path.
 * @param {string?} anonymizedName
 *        An anonymized version of `name`, to be used in anonymized memory
 *        reports. If `null`, then `name` will be used instead.
 * @param {StructuredCloneHolder|*} value
 *        A value to serialize.
 * @returns {*}
 */
function serialize(name, anonymizedName, value) {
  if (value && typeof value === "object" && !isStructuredCloneHolder(value)) {
    return new StructuredCloneHolder(name, anonymizedName, value);
  }
  return value;
}

export var ExtensionStorage = {
  /** @type {Map<string, Promise<typeof lazy.JSONFile>>} */
  jsonFilePromises: new Map(),

  listeners: new Map(),

  /**
   * Asynchronously reads the storage file for the given extension ID
   * and returns a Promise for its initialized JSONFile object.
   *
   * @param {string} extensionId
   *        The ID of the extension for which to return a file.
   * @returns {Promise<InstanceType<Lazy['JSONFile']>>}
   */
  async _readFile(extensionId) {
    await IOUtils.makeDirectory(this.getExtensionDir(extensionId));

    let jsonFile = new lazy.JSONFile({
      path: this.getStorageFile(extensionId),
    });
    await jsonFile.load();

    jsonFile.data = this._serializableMap(jsonFile.data);
    return jsonFile;
  },

  _serializableMap(data) {
    return new SerializeableMap(Object.entries(data));
  },

  /**
   * Returns a Promise for initialized JSONFile instance for the
   * extension's storage file.
   *
   * @param {string} extensionId
   *        The ID of the extension for which to return a file.
   * @returns {Promise<InstanceType<Lazy['JSONFile']>>}
   */
  getFile(extensionId) {
    let promise = this.jsonFilePromises.get(extensionId);
    if (!promise) {
      promise = this._readFile(extensionId);
      this.jsonFilePromises.set(extensionId, promise);
    }
    return promise;
  },

  /**
   * Clear the cached jsonFilePromise for a given extensionId
   * (used by ExtensionStorageIDB to free the jsonFile once the data migration
   * has been completed).
   *
   * @param {string} extensionId
   *        The ID of the extension for which to return a file.
   */
  async clearCachedFile(extensionId) {
    let promise = this.jsonFilePromises.get(extensionId);
    if (promise) {
      this.jsonFilePromises.delete(extensionId);
      await promise.then(jsonFile => jsonFile.finalize());
    }
  },

  /**
   * Sanitizes the given value, and returns a JSON-compatible
   * representation of it, based on the privileges of the given global.
   *
   * @param {any} value
   *        The value to sanitize.
   * @param {BaseContext} context
   *        The extension context in which to sanitize the value
   * @returns {value}
   *        The sanitized value.
   */
  sanitize(value, context) {
    let json = context.jsonStringify(value === undefined ? null : value);
    if (json == undefined) {
      throw new ExtensionError(
        "DataCloneError: The object could not be cloned."
      );
    }
    return JSON.parse(json);
  },

  /**
   * Returns the path to the storage directory within the profile for
   * the given extension ID.
   *
   * @param {string} extensionId
   *        The ID of the extension for which to return a directory path.
   * @returns {string}
   */
  getExtensionDir(extensionId) {
    return PathUtils.join(this.extensionDir, extensionId);
  },

  /**
   * Returns the path to the JSON storage file for the given extension
   * ID.
   *
   * @param {string} extensionId
   *        The ID of the extension for which to return a file path.
   * @returns {string}
   */
  getStorageFile(extensionId) {
    return PathUtils.join(this.extensionDir, extensionId, "storage.js");
  },

  /**
   * Asynchronously sets the values of the given storage items for the
   * given extension.
   *
   * @param {string} extensionId
   *        The ID of the extension for which to set storage values.
   * @param {object} items
   *        The storage items to set. For each property in the object,
   *        the storage value for that property is set to its value in
   *        said object. Any values which are StructuredCloneHolder
   *        instances are deserialized before being stored.
   * @returns {Promise<void>}
   */
  async set(extensionId, items) {
    let jsonFile = await this.getFile(extensionId);

    let changes = {};
    for (let prop in items) {
      let item = items[prop];
      changes[prop] = {
        oldValue: serialize(
          `set/${extensionId}/old/${prop}`,
          `set/${extensionId}/old/<anonymized>`,
          jsonFile.data.get(prop)
        ),
        newValue: serialize(
          `set/${extensionId}/new/${prop}`,
          `set/${extensionId}/new/<anonymized>`,
          item
        ),
      };
      jsonFile.data.set(prop, item);
    }

    this.notifyListeners(extensionId, changes);

    jsonFile.saveSoon();
    return null;
  },

  /**
   * Asynchronously removes the given storage items for the given
   * extension ID.
   *
   * @param {string} extensionId
   *        The ID of the extension for which to remove storage values.
   * @param {Array<string>} items
   *        A list of storage items to remove.
   * @returns {Promise<void>}
   */
  async remove(extensionId, items) {
    let jsonFile = await this.getFile(extensionId);

    let changed = false;
    let changes = {};

    for (let prop of [].concat(items)) {
      if (jsonFile.data.has(prop)) {
        changes[prop] = {
          oldValue: serialize(
            `remove/${extensionId}/${prop}`,
            `remove/${extensionId}/<anonymized>`,
            jsonFile.data.get(prop)
          ),
        };
        jsonFile.data.delete(prop);
        changed = true;
      }
    }

    if (changed) {
      this.notifyListeners(extensionId, changes);
      jsonFile.saveSoon();
    }
    return null;
  },

  /**
   * Asynchronously clears all storage entries for the given extension
   * ID.
   *
   * @param {string} extensionId
   *        The ID of the extension for which to clear storage.
   * @param {object} options
   * @param {boolean} [options.shouldNotifyListeners = true]
   *         Whether or not collect and send the changes to the listeners,
   *         used when the extension data is being cleared on uninstall.
   * @returns {Promise<void>}
   */
  async clear(extensionId, { shouldNotifyListeners = true } = {}) {
    let jsonFile = await this.getFile(extensionId);

    let changed = false;
    let changes = {};

    for (let [prop, oldValue] of jsonFile.data.entries()) {
      if (shouldNotifyListeners) {
        changes[prop] = {
          oldValue: serialize(
            `clear/${extensionId}/${prop}`,
            `clear/${extensionId}/<anonymized>`,
            oldValue
          ),
        };
      }

      jsonFile.data.delete(prop);
      changed = true;
    }

    if (changed) {
      if (shouldNotifyListeners) {
        this.notifyListeners(extensionId, changes);
      }

      jsonFile.saveSoon();
    }
    return null;
  },

  /**
   * Asynchronously retrieves the values for the given storage items for
   * the given extension ID.
   *
   * @param {string} extensionId
   *        The ID of the extension for which to get storage values.
   * @param {Array<string>|object|null} [keys]
   *        The storage items to get. If an array, the value of each key
   *        in the array is returned. If null, the values of all items
   *        are returned. If an object, the value for each key in the
   *        object is returned, or that key's value if the item is not
   *        set.
   * @returns {Promise<object>}
   *        An object which a property for each requested key,
   *        containing that key's storage value. Values are
   *        StructuredCloneHolder objects which can be deserialized to
   *        the original storage value.
   */
  async get(extensionId, keys) {
    let jsonFile = await this.getFile(extensionId);
    return this._filterProperties(extensionId, jsonFile.data, keys);
  },

  async _filterProperties(extensionId, data, keys) {
    let result = {};
    if (keys === null) {
      Object.assign(result, data.toJSON());
    } else if (typeof keys == "object" && !Array.isArray(keys)) {
      for (let prop in keys) {
        if (data.has(prop)) {
          result[prop] = serialize(
            `filterProperties/${extensionId}/${prop}`,
            `filterProperties/${extensionId}/<anonymized>`,
            data.get(prop)
          );
        } else {
          result[prop] = keys[prop];
        }
      }
    } else {
      for (let prop of [].concat(keys)) {
        if (data.has(prop)) {
          result[prop] = serialize(
            `filterProperties/${extensionId}/${prop}`,
            `filterProperties/${extensionId}/<anonymized>`,
            data.get(prop)
          );
        }
      }
    }

    return result;
  },

  addOnChangedListener(extensionId, listener) {
    let listeners = this.listeners.get(extensionId) || new Set();
    listeners.add(listener);
    this.listeners.set(extensionId, listeners);
  },

  removeOnChangedListener(extensionId, listener) {
    let listeners = this.listeners.get(extensionId);
    listeners.delete(listener);
  },

  notifyListeners(extensionId, changes) {
    let listeners = this.listeners.get(extensionId);
    if (listeners) {
      for (let listener of listeners) {
        listener(changes);
      }
    }
  },

  init() {
    if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT) {
      return;
    }
    Services.obs.addObserver(this, "extension-invalidate-storage-cache");
    Services.obs.addObserver(this, "xpcom-shutdown");
  },

  observe(subject, topic) {
    if (topic == "xpcom-shutdown") {
      Services.obs.removeObserver(this, "extension-invalidate-storage-cache");
      Services.obs.removeObserver(this, "xpcom-shutdown");
    } else if (topic == "extension-invalidate-storage-cache") {
      for (let promise of this.jsonFilePromises.values()) {
        promise.then(jsonFile => {
          jsonFile.finalize();
        });
      }
      this.jsonFilePromises.clear();
    }
  },

  // Serializes an arbitrary value into a StructuredCloneHolder, if appropriate.
  serialize,

  /**
   * Serializes the given storage items for transporting between processes.
   *
   * @param {BaseContext} context
   *        The context to use for the created StructuredCloneHolder
   *        objects.
   * @param {Array<string>|object} items
   *        The items to serialize. If an object is provided, its
   *        values are serialized to StructuredCloneHolder objects.
   *        Otherwise, it is returned as-is.
   * @returns {Array<string>|object}
   */
  serializeForContext(context, items) {
    if (items && typeof items === "object" && !Array.isArray(items)) {
      let result = {};
      for (let [key, value] of Object.entries(items)) {
        try {
          result[key] = new StructuredCloneHolder(
            `serializeForContext/${context.extension.id}`,
            null,
            value,
            context.cloneScope
          );
        } catch (e) {
          throw new ExtensionError(String(e));
        }
      }
      return result;
    }
    return items;
  },

  /**
   * Deserializes the given storage items into the given extension context.
   *
   * @param {BaseContext} context
   *        The context to use to deserialize the StructuredCloneHolder objects.
   * @param {object} items
   *        The items to deserialize. Any property of the object which
   *        is a StructuredCloneHolder instance is deserialized into
   *        the extension scope. Any other object is cloned into the
   *        extension scope directly.
   * @returns {object}
   */
  deserializeForContext(context, items) {
    let result = new context.cloneScope.Object();
    for (let [key, value] of Object.entries(items)) {
      if (
        value &&
        typeof value === "object" &&
        Cu.getClassName(value, true) === "StructuredCloneHolder"
      ) {
        value = value.deserialize(context.cloneScope, true);
      } else {
        value = Cu.cloneInto(value, context.cloneScope);
      }
      result[key] = value;
    }
    return result;
  },
};

ChromeUtils.defineLazyGetter(ExtensionStorage, "extensionDir", () =>
  PathUtils.join(PathUtils.profileDir, "browser-extension-data")
);

ExtensionStorage.init();

class QuotaMap extends Map {
  static QUOTA_BYTES = 10485760;

  bytesUsed = 0;

  /**
   * @param {string} key
   * @param {StructuredCloneHolder} holder
   */
  dataSize(key, holder) {
    // Using key.length is not really correct, but is probably less surprising
    // for developers. We don't need an exact count, just ensure it's bounded.
    return key.length + holder.dataSize;
  }

  /**
   * @param {string} key
   * @param {StructuredCloneHolder} holder
   */
  getSizeDelta(key, holder) {
    let before = this.has(key) ? this.dataSize(key, this.get(key)) : 0;
    return this.dataSize(key, holder) - before;
  }

  /** @param {Record<string, StructuredCloneHolder>} items */
  checkQuota(items) {
    let after = this.bytesUsed;
    for (let [key, holder] of Object.entries(items)) {
      after += this.getSizeDelta(key, holder);
    }
    if (lazy.enforceSessionQuota && after > QuotaMap.QUOTA_BYTES) {
      throw new ExtensionError(
        "QuotaExceededError: storage.session API call exceeded its quota limitations."
      );
    }
  }

  set(key, holder) {
    this.checkQuota({ [key]: holder });
    this.bytesUsed += this.getSizeDelta(key, holder);
    return super.set(key, holder);
  }

  delete(key) {
    if (this.has(key)) {
      this.bytesUsed -= this.dataSize(key, this.get(key));
    }
    return super.delete(key);
  }

  clear() {
    this.bytesUsed = 0;
    super.clear();
  }
}

export var extensionStorageSession = {
  /** @type {WeakMap<Extension, QuotaMap>} */
  buckets: new DefaultWeakMap(_extension => new QuotaMap()),

  /** @type {WeakMap<Extension, Set<callback>>} */
  listeners: new DefaultWeakMap(_extension => new Set()),

  get QUOTA_BYTES() {
    // Even if quota is not enforced yet, report the future default of 10MB.
    return QuotaMap.QUOTA_BYTES;
  },

  /**
   * @param {Extension} extension
   * @param {null | undefined | string | string[] | object} items
   * Schema normalization ensures items are normalized to one of above types.
   */
  get(extension, items) {
    let bucket = this.buckets.get(extension);

    let result = {};
    /** @type {Iterable<string>} */
    let keys = [];

    if (!items) {
      keys = bucket.keys();
    } else if (typeof items !== "object" || Array.isArray(items)) {
      keys = [].concat(items);
    } else {
      keys = Object.keys(items);
      result = items;
    }

    for (let prop of keys) {
      if (bucket.has(prop)) {
        result[prop] = bucket.get(prop);
      }
    }
    return result;
  },

  set(extension, items) {
    let bucket = this.buckets.get(extension);

    // set() below also checks the quota for each item, but
    // this check includes all inputs to avoid partial updates.
    bucket.checkQuota(items);

    let changes = {};
    for (let [key, value] of Object.entries(items)) {
      changes[key] = {
        oldValue: bucket.get(key),
        newValue: value,
      };
      bucket.set(key, value);
    }
    this.notifyListeners(extension, changes);
  },

  remove(extension, keys) {
    let bucket = this.buckets.get(extension);
    let changes = {};
    for (let k of [].concat(keys)) {
      if (bucket.has(k)) {
        changes[k] = { oldValue: bucket.get(k) };
        bucket.delete(k);
      }
    }
    this.notifyListeners(extension, changes);
  },

  clear(extension) {
    let bucket = this.buckets.get(extension);
    let changes = {};
    for (let k of bucket.keys()) {
      changes[k] = { oldValue: bucket.get(k) };
    }
    bucket.clear();
    this.notifyListeners(extension, changes);
  },

  /**
   * @param {Extension} extension
   * @param {null | undefined | string | string[] } keys
   */
  getBytesInUse(extension, keys) {
    let bucket = this.buckets.get(extension);
    if (keys == null) {
      return bucket.bytesUsed;
    }
    let result = 0;
    for (let k of [].concat(keys)) {
      if (bucket.has(k)) {
        result += bucket.dataSize(k, bucket.get(k));
      }
    }
    return result;
  },

  registerListener(extension, listener) {
    this.listeners.get(extension).add(listener);
    return () => {
      this.listeners.get(extension).delete(listener);
    };
  },

  notifyListeners(extension, changes) {
    if (!Object.keys(changes).length) {
      return;
    }
    for (let listener of this.listeners.get(extension)) {
      lazy.ExtensionCommon.runSafeSyncWithoutClone(listener, changes);
    }
  },
};
PK
!<m����*modules/ExtensionStorageComponents.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
});

const StorageSyncArea = Components.Constructor(
  "@mozilla.org/extensions/storage/internal/sync-area;1",
  "mozIConfigurableExtensionStorageArea",
  "configure"
);

/**
 * An XPCOM service for the WebExtension `storage.sync` API. The service manages
 * a storage area for storing and syncing extension data.
 *
 * The service configures its storage area with the database path, and hands
 * out references to the configured area via `getInterface`. It also registers
 * a shutdown blocker to automatically tear down the area.
 *
 * ## What's the difference between `storage/internal/storage-sync-area;1` and
 *    `storage/sync;1`?
 *
 * `components.conf` has two classes:
 * `@mozilla.org/extensions/storage/internal/sync-area;1` and
 * `@mozilla.org/extensions/storage/sync;1`.
 *
 * The `storage/internal/sync-area;1` class is implemented in Rust, and can be
 * instantiated using `createInstance` and `Components.Constructor`. It's not
 * a singleton, so creating a new instance will create a new `storage.sync`
 * area, with its own database connection. It's useful for testing, but not
 * meant to be used outside of this module.
 *
 * The `storage/sync;1` class is implemented in this file. It's a singleton,
 * ensuring there's only one `storage.sync` area, with one database connection.
 * The service implements `nsIInterfaceRequestor`, so callers can access the
 * storage interface like this:
 *
 *     let storageSyncArea = Cc["@mozilla.org/extensions/storage/sync;1"]
 *        .getService(Ci.nsIInterfaceRequestor)
 *        .getInterface(Ci.mozIExtensionStorageArea);
 *
 * ...And the Sync interface like this:
 *
 *    let extensionStorageEngine = Cc["@mozilla.org/extensions/storage/sync;1"]
 *       .getService(Ci.nsIInterfaceRequestor)
 *       .getInterface(Ci.mozIBridgedSyncEngine);
 *
 * @class
 */
export function StorageSyncService() {
  if (StorageSyncService._singleton) {
    return StorageSyncService._singleton;
  }

  let file = new lazy.FileUtils.File(
    PathUtils.join(PathUtils.profileDir, "storage-sync-v2.sqlite")
  );
  let kintoFile = new lazy.FileUtils.File(
    PathUtils.join(PathUtils.profileDir, "storage-sync.sqlite")
  );
  this._storageArea = new StorageSyncArea(file, kintoFile);

  // Register a blocker to close the storage connection on shutdown.
  this._shutdownBound = () => this._shutdown();
  lazy.AsyncShutdown.profileChangeTeardown.addBlocker(
    "StorageSyncService: shutdown",
    this._shutdownBound
  );

  StorageSyncService._singleton = this;
}

StorageSyncService._singleton = null;

StorageSyncService.prototype = {
  QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),

  // Returns the storage and syncing interfaces. This just hands out a
  // reference to the underlying storage area, with a quick check to make sure
  // that callers are asking for the right interfaces.
  getInterface(iid) {
    if (
      iid.equals(Ci.mozIExtensionStorageArea) ||
      iid.equals(Ci.mozIBridgedSyncEngine)
    ) {
      return this._storageArea.QueryInterface(iid);
    }
    throw Components.Exception(
      "This interface isn't implemented",
      Cr.NS_ERROR_NO_INTERFACE
    );
  },

  // Tears down the storage area and lifts the blocker so that shutdown can
  // continue.
  async _shutdown() {
    try {
      await new Promise((resolve, reject) => {
        this._storageArea.teardown({
          handleSuccess: resolve,
          handleError(code, message) {
            reject(Components.Exception(message, code));
          },
        });
      });
    } finally {
      lazy.AsyncShutdown.profileChangeTeardown.removeBlocker(
        this._shutdownBound
      );
    }
  },
};
PK
!<���msms#modules/ExtensionStorageIDB.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { IndexedDB } from "resource://gre/modules/IndexedDB.sys.mjs";

/** @type {Lazy} */
const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ExtensionStorage: "resource://gre/modules/ExtensionStorage.sys.mjs",
  ExtensionUtils: "resource://gre/modules/ExtensionUtils.sys.mjs",
  getTrimmedString: "resource://gre/modules/ExtensionTelemetry.sys.mjs",
});

// The userContextID reserved for the extension storage (its purpose is ensuring that the IndexedDB
// storage used by the browser.storage.local API is not directly accessible from the extension code,
// it is defined and reserved as "userContextIdInternal.webextStorageLocal" in ContextualIdentityService.sys.mjs).
const WEBEXT_STORAGE_USER_CONTEXT_ID = -1 >>> 0;

const IDB_NAME = "webExtensions-storage-local";
const IDB_DATA_STORENAME = "storage-local-data";
const IDB_VERSION = 1;
const IDB_MIGRATE_RESULT_HISTOGRAM =
  "WEBEXT_STORAGE_LOCAL_IDB_MIGRATE_RESULT_COUNT";

// Whether or not the installed extensions should be migrated to the storage.local IndexedDB backend.
const BACKEND_ENABLED_PREF =
  "extensions.webextensions.ExtensionStorageIDB.enabled";
const IDB_MIGRATED_PREF_BRANCH =
  "extensions.webextensions.ExtensionStorageIDB.migrated";

class DataMigrationAbortedError extends Error {
  get name() {
    return "DataMigrationAbortedError";
  }
}

var ErrorsTelemetry = {
  initialized: false,

  lazyInit() {
    if (this.initialized) {
      return;
    }
    this.initialized = true;

    // Ensure that these telemetry events category is enabled.
    Services.telemetry.setEventRecordingEnabled("extensions.data", true);

    this.resultHistogram = Services.telemetry.getHistogramById(
      IDB_MIGRATE_RESULT_HISTOGRAM
    );
  },

  /**
   * Get the DOMException error name for a given error object.
   *
   * @param {Error | undefined} error
   *        The Error object to convert into a string, or undefined if there was no error.
   *
   * @returns {string | undefined}
   *          The DOMException error name (sliced to a maximum of 80 chars),
   *          "OtherError" if the error object is not a DOMException instance,
   *          or `undefined` if there wasn't an error.
   */
  getErrorName(error) {
    if (!error) {
      return undefined;
    }

    if (
      DOMException.isInstance(error) ||
      error instanceof DataMigrationAbortedError
    ) {
      if (error.name.length > 80) {
        return lazy.getTrimmedString(error.name);
      }

      return error.name;
    }

    return "OtherError";
  },

  /**
   * Record telemetry related to a data migration result.
   *
   * @param {object} telemetryData
   * @param {string} telemetryData.backend
   *        The backend selected ("JSONFile" or "IndexedDB").
   * @param {boolean} [telemetryData.dataMigrated]
   *        Old extension data has been migrated successfully.
   * @param {string} telemetryData.extensionId
   *        The id of the extension migrated.
   * @param {Error | undefined} telemetryData.error
   *        The error raised during the data migration, if any.
   * @param {boolean} [telemetryData.hasJSONFile]
   *        The extension has an existing JSONFile to migrate.
   * @param {boolean} [telemetryData.hasOldData]
   *        The extension's JSONFile wasn't empty.
   * @param {string} telemetryData.histogramCategory
   *        The histogram category for the result ("success" or "failure").
   */
  recordDataMigrationResult(telemetryData) {
    try {
      const {
        backend,
        dataMigrated,
        extensionId,
        error,
        hasJSONFile,
        hasOldData,
        histogramCategory,
      } = telemetryData;

      this.lazyInit();
      this.resultHistogram.add(histogramCategory);

      const extra = { backend };

      if (dataMigrated != null) {
        extra.data_migrated = dataMigrated ? "y" : "n";
      }

      if (hasJSONFile != null) {
        extra.has_jsonfile = hasJSONFile ? "y" : "n";
      }

      if (hasOldData != null) {
        extra.has_olddata = hasOldData ? "y" : "n";
      }

      if (error) {
        extra.error_name = this.getErrorName(error);
      }

      let addon_id = lazy.getTrimmedString(extensionId);
      Services.telemetry.recordEvent(
        "extensions.data",
        "migrateResult",
        "storageLocal",
        addon_id,
        extra
      );
      Glean.extensionsData.migrateResult.record({
        addon_id,
        backend: extra.backend,
        data_migrated: extra.data_migrated,
        has_jsonfile: extra.has_jsonfile,
        has_olddata: extra.has_olddata,
        error_name: extra.error_name,
      });
    } catch (err) {
      // Report any telemetry error on the browser console, but
      // we treat it as a non-fatal error and we don't re-throw
      // it to the caller.
      Cu.reportError(err);
    }
  },

  /**
   * Record telemetry related to the unexpected errors raised while executing
   * a storage.local API call.
   *
   * @param {object} options
   * @param {string} options.extensionId
   *        The id of the extension migrated.
   * @param {string} options.storageMethod
   *        The storage.local API method being run.
   * @param {Error}  options.error
   *        The unexpected error raised during the API call.
   */
  recordStorageLocalError({ extensionId, storageMethod, error }) {
    this.lazyInit();
    let addon_id = lazy.getTrimmedString(extensionId);
    let error_name = this.getErrorName(error);

    Services.telemetry.recordEvent(
      "extensions.data",
      "storageLocalError",
      storageMethod,
      addon_id,
      { error_name }
    );
    Glean.extensionsData.storageLocalError.record({
      addon_id,
      method: storageMethod,
      error_name,
    });
  },
};

class ExtensionStorageLocalIDB extends IndexedDB {
  onupgradeneeded(event) {
    if (event.oldVersion < 1) {
      this.createObjectStore(IDB_DATA_STORENAME);
    }
  }

  static openForPrincipal(storagePrincipal) {
    // The db is opened using an extension principal isolated in a reserved user context id.
    return super.openForPrincipal(storagePrincipal, IDB_NAME, IDB_VERSION);
  }

  async isEmpty() {
    const cursor = await this.objectStore(
      IDB_DATA_STORENAME,
      "readonly"
    ).openKeyCursor();
    return cursor.done;
  }

  /**
   * Asynchronously sets the values of the given storage items.
   *
   * @param {object} items
   *        The storage items to set. For each property in the object,
   *        the storage value for that property is set to its value in
   *        said object. Any values which are StructuredCloneHolder
   *        instances are deserialized before being stored.
   * @param {object}  options
   * @param {callback} [options.serialize]
   *        Set to a function which will be used to serialize the values into
   *        a StructuredCloneHolder object (if appropriate) and being sent
   *        across the processes (it is also used to detect data cloning errors
   *        and raise an appropriate error to the caller).
   *
   * @returns {Promise<null|object>}
   *        Return a promise which resolves to the computed "changes" object
   *        or null.
   */
  async set(items, { serialize } = {}) {
    const changes = {};
    let changed = false;

    // Explicitly create a transaction, so that we can explicitly abort it
    // as soon as one of the put requests fails.
    const transaction = this.transaction(IDB_DATA_STORENAME, "readwrite");
    const objectStore = transaction.objectStore(IDB_DATA_STORENAME);
    const transactionCompleted = transaction.promiseComplete();

    if (!serialize) {
      serialize = (name, anonymizedName, value) => value;
    }

    for (let key of Object.keys(items)) {
      try {
        let oldValue = await objectStore.get(key);

        await objectStore.put(items[key], key);

        changes[key] = {
          oldValue:
            oldValue && serialize(`old/${key}`, `old/<anonymized>`, oldValue),
          newValue: serialize(`new/${key}`, `new/<anonymized>`, items[key]),
        };
        changed = true;
      } catch (err) {
        transactionCompleted.catch(() => {
          // We ignore this rejection because we are explicitly aborting the transaction,
          // the transaction.error will be null, and we throw the original error below.
        });
        transaction.abort();

        throw err;
      }
    }

    await transactionCompleted;

    return changed ? changes : null;
  }

  /**
   * Asynchronously retrieves the values for the given storage items.
   *
   * @param {Array<string>|object|null} [keysOrItems]
   *        The storage items to get. If an array, the value of each key
   *        in the array is returned. If null, the values of all items
   *        are returned. If an object, the value for each key in the
   *        object is returned, or that key's value if the item is not
   *        set.
   * @returns {Promise<object>}
   *        An object which has a property for each requested key,
   *        containing that key's value as stored in the IndexedDB
   *        storage.
   */
  async get(keysOrItems) {
    let keys;
    let defaultValues;

    if (typeof keysOrItems === "string") {
      keys = [keysOrItems];
    } else if (Array.isArray(keysOrItems)) {
      keys = keysOrItems;
    } else if (keysOrItems && typeof keysOrItems === "object") {
      keys = Object.keys(keysOrItems);
      defaultValues = keysOrItems;
    }

    const result = {};

    // Retrieve all the stored data using a cursor when browser.storage.local.get()
    // has been called with no keys.
    if (keys == null) {
      const cursor = await this.objectStore(
        IDB_DATA_STORENAME,
        "readonly"
      ).openCursor();
      while (!cursor.done) {
        result[cursor.key] = cursor.value;
        await cursor.continue();
      }
    } else {
      const objectStore = this.objectStore(IDB_DATA_STORENAME);
      for (let key of keys) {
        const storedValue = await objectStore.get(key);
        if (storedValue === undefined) {
          if (defaultValues && defaultValues[key] !== undefined) {
            result[key] = defaultValues[key];
          }
        } else {
          result[key] = storedValue;
        }
      }
    }

    return result;
  }

  /**
   * Asynchronously removes the given storage items.
   *
   * @param {string|Array<string>} keys
   *        A string key of a list of storage items keys to remove.
   * @returns {Promise<object>}
   *          Returns an object which contains applied changes.
   */
  async remove(keys) {
    // Ensure that keys is an array of strings.
    keys = [].concat(keys);

    if (keys.length === 0) {
      // Early exit if there is nothing to remove.
      return null;
    }

    const changes = {};
    let changed = false;

    const objectStore = this.objectStore(IDB_DATA_STORENAME, "readwrite");

    let promises = [];

    for (let key of keys) {
      promises.push(
        objectStore.getKey(key).then(async foundKey => {
          if (foundKey === key) {
            changed = true;
            changes[key] = { oldValue: await objectStore.get(key) };
            return objectStore.delete(key);
          }
        })
      );
    }

    await Promise.all(promises);

    return changed ? changes : null;
  }

  /**
   * Asynchronously clears all storage entries.
   *
   * @returns {Promise<object>}
   *          Returns an object which contains applied changes.
   */
  async clear() {
    const changes = {};
    let changed = false;

    const objectStore = this.objectStore(IDB_DATA_STORENAME, "readwrite");

    const cursor = await objectStore.openCursor();
    while (!cursor.done) {
      changes[cursor.key] = { oldValue: cursor.value };
      changed = true;
      await cursor.continue();
    }

    await objectStore.clear();

    return changed ? changes : null;
  }
}

/**
 * Migrate the data stored in the JSONFile backend to the IDB Backend.
 *
 * Returns a promise which is resolved once the data migration has been
 * completed and the new IDB backend can be enabled.
 * Rejects if the data has been read successfully from the JSONFile backend
 * but it failed to be saved in the new IDB backend.
 *
 * This method is called only from the main process (where the file
 * can be opened).
 *
 * @param {Extension} extension
 *        The extension to migrate to the new IDB backend.
 * @param {nsIPrincipal} storagePrincipal
 *        The "internally reserved" extension storagePrincipal to be used to create
 *        the ExtensionStorageLocalIDB instance.
 */
async function migrateJSONFileData(extension, storagePrincipal) {
  let oldStoragePath;
  let oldStorageExists;
  let idbConn;
  let jsonFile;
  let hasEmptyIDB;
  let nonFatalError;
  let dataMigrateCompleted = false;
  let hasOldData = false;

  function abortIfShuttingDown() {
    if (extension.hasShutdown || Services.startup.shuttingDown) {
      throw new DataMigrationAbortedError("extension or app is shutting down");
    }
  }

  if (ExtensionStorageIDB.isMigratedExtension(extension)) {
    return;
  }

  try {
    abortIfShuttingDown();
    idbConn = await ExtensionStorageIDB.open(
      storagePrincipal,
      extension.hasPermission("unlimitedStorage")
    );
    abortIfShuttingDown();

    hasEmptyIDB = await idbConn.isEmpty();

    if (!hasEmptyIDB) {
      // If the IDB backend is enabled and there is data already stored in the IDB backend,
      // there is no "going back": any data that has not been migrated will be still on disk
      // but it is not going to be migrated anymore, it could be eventually used to allow
      // a user to manually retrieve the old data file).
      ExtensionStorageIDB.setMigratedExtensionPref(extension, true);
      return;
    }
  } catch (err) {
    extension.logWarning(
      `storage.local data migration cancelled, unable to open IDB connection: ${err.message}::${err.stack}`
    );

    ErrorsTelemetry.recordDataMigrationResult({
      backend: "JSONFile",
      extensionId: extension.id,
      error: err,
      histogramCategory: "failure",
    });

    throw err;
  }

  try {
    abortIfShuttingDown();

    oldStoragePath = lazy.ExtensionStorage.getStorageFile(extension.id);
    oldStorageExists = await IOUtils.exists(oldStoragePath).catch(fileErr => {
      // If we can't access the oldStoragePath here, then extension is also going to be unable to
      // access it, and so we log the error but we don't stop the extension from switching to
      // the IndexedDB backend.
      extension.logWarning(
        `Unable to access extension storage.local data file: ${fileErr.message}::${fileErr.stack}`
      );
      return false;
    });

    // Migrate any data stored in the JSONFile backend (if any), and remove the old data file
    // if the migration has been completed successfully.
    if (oldStorageExists) {
      // Do not load the old JSON file content if shutting down is already in progress.
      abortIfShuttingDown();

      Services.console.logStringMessage(
        `Migrating storage.local data for ${extension.policy.debugName}...`
      );

      jsonFile = await lazy.ExtensionStorage.getFile(extension.id);

      abortIfShuttingDown();

      const data = {};
      for (let [key, value] of jsonFile.data.entries()) {
        data[key] = value;
        hasOldData = true;
      }

      await idbConn.set(data);
      Services.console.logStringMessage(
        `storage.local data successfully migrated to IDB Backend for ${extension.policy.debugName}.`
      );
    }

    dataMigrateCompleted = true;
  } catch (err) {
    extension.logWarning(
      `Error on migrating storage.local data file: ${err.message}::${err.stack}`
    );

    if (oldStorageExists && !dataMigrateCompleted) {
      ErrorsTelemetry.recordDataMigrationResult({
        backend: "JSONFile",
        dataMigrated: dataMigrateCompleted,
        extensionId: extension.id,
        error: err,
        hasJSONFile: oldStorageExists,
        hasOldData,
        histogramCategory: "failure",
      });

      // If the data failed to be stored into the IndexedDB backend, then we clear the IndexedDB
      // backend to allow the extension to retry the migration on its next startup, and reject
      // the data migration promise explicitly (which would prevent the new backend
      // from being enabled for this session).
      await new Promise(resolve => {
        let req = Services.qms.clearStoragesForPrincipal(storagePrincipal);
        req.callback = resolve;
      });

      throw err;
    }

    // This error is not preventing the extension from switching to the IndexedDB backend,
    // but we may still want to know that it has been triggered and include it into the
    // telemetry data collected for the extension.
    nonFatalError = err;
  } finally {
    // Clear the jsonFilePromise cached by the ExtensionStorage.
    await lazy.ExtensionStorage.clearCachedFile(extension.id).catch(err => {
      extension.logWarning(err.message);
    });
  }

  // If the IDB backend has been enabled, rename the old storage.local data file, but
  // do not prevent the extension from switching to the IndexedDB backend if it fails.
  if (oldStorageExists && dataMigrateCompleted) {
    try {
      // Only migrate the file when it actually exists (e.g. the file name is not going to exist
      // when it is corrupted, because JSONFile internally rename it to `.corrupt`.
      if (await IOUtils.exists(oldStoragePath)) {
        const uniquePath = await IOUtils.createUniqueFile(
          PathUtils.parent(oldStoragePath),
          `${PathUtils.filename(oldStoragePath)}.migrated`
        );
        await IOUtils.move(oldStoragePath, uniquePath);
      }
    } catch (err) {
      nonFatalError = err;
      extension.logWarning(err.message);
    }
  }

  ExtensionStorageIDB.setMigratedExtensionPref(extension, true);

  ErrorsTelemetry.recordDataMigrationResult({
    backend: "IndexedDB",
    dataMigrated: dataMigrateCompleted,
    extensionId: extension.id,
    error: nonFatalError,
    hasJSONFile: oldStorageExists,
    hasOldData,
    histogramCategory: "success",
  });
}

/**
 * This ExtensionStorage class implements a backend for the storage.local API which
 * uses IndexedDB to store the data.
 */
export var ExtensionStorageIDB = {
  BACKEND_ENABLED_PREF,
  IDB_MIGRATED_PREF_BRANCH,
  IDB_MIGRATE_RESULT_HISTOGRAM,

  // Map<extension-id, Set<Function>>
  listeners: new Map(),

  // Keep track if the IDB backend has been selected or not for a running extension
  // (the selected backend should never change while the extension is running, even if the
  // related preference has been changed in the meantime):
  //
  //   WeakMap<extension -> Promise<boolean>
  selectedBackendPromises: new WeakMap(),

  init() {
    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "isBackendEnabled",
      BACKEND_ENABLED_PREF,
      false
    );
  },

  isMigratedExtension(extension) {
    return Services.prefs.getBoolPref(
      `${IDB_MIGRATED_PREF_BRANCH}.${extension.id}`,
      false
    );
  },

  setMigratedExtensionPref(extension, val) {
    Services.prefs.setBoolPref(
      `${IDB_MIGRATED_PREF_BRANCH}.${extension.id}`,
      !!val
    );
  },

  clearMigratedExtensionPref(extensionId) {
    Services.prefs.clearUserPref(`${IDB_MIGRATED_PREF_BRANCH}.${extensionId}`);
  },

  getStoragePrincipal(extension) {
    return extension.createPrincipal(extension.baseURI, {
      userContextId: WEBEXT_STORAGE_USER_CONTEXT_ID,
    });
  },

  /**
   * Select the preferred backend and return a promise which is resolved once the
   * selected backend is ready to be used (e.g. if the extension is switching from
   * the old JSONFile storage to the new IDB backend, any previously stored data will
   * be migrated to the backend before the promise is resolved).
   *
   * This method is called from both the main and child (content or extension) processes:
   * - an extension child context will call this method lazily, when the browser.storage.local
   *   is being used for the first time, and it will result into asking the main process
   *   to call the same method in the main process
   * - on the main process side, it will check if the new IDB backend can be used (and if it can,
   *   it will migrate any existing data into the new backend, which needs to happen in the
   *   main process where the file can directly be accessed)
   *
   * The result will be cached while the extension is still running, and so an extension
   * child context is going to ask the main process only once per child process, and on the
   * main process side the backend selection and data migration will happen only once.
   *
   * @param {import("ExtensionPageChild.sys.mjs").ExtensionBaseContextChild} context
   *        The extension context that is selecting the storage backend.
   *
   * @returns {Promise<object>}
   *          Returns a promise which resolves to an object which provides a
   *          `backendEnabled` boolean property, and if it is true the extension should use
   *          the IDB backend and the object also includes a `storagePrincipal` property
   *          of type nsIPrincipal, otherwise `backendEnabled` will be false when the
   *          extension should use the old JSONFile backend (e.g. because the IDB backend has
   *          not been enabled from the preference).
   */
  selectBackend(context) {
    const { extension } = context;

    if (!this.selectedBackendPromises.has(extension)) {
      let promise;

      if (context.childManager) {
        return context.childManager
          .callParentAsyncFunction("storage.local.IDBBackend.selectBackend", [])
          .then(parentResult => {
            let result;

            if (!parentResult.backendEnabled) {
              result = { backendEnabled: false };
            } else {
              result = {
                ...parentResult,
                // In the child process, we need to deserialize the storagePrincipal
                // from the StructuredCloneHolder used to send it across the processes.
                storagePrincipal: parentResult.storagePrincipal.deserialize(
                  this,
                  true
                ),
              };
            }

            // Cache the result once we know that it has been resolved. The promise returned by
            // context.childManager.callParentAsyncFunction will be dead when context.cloneScope
            // is destroyed. To keep a promise alive in the cache, we wrap the result in an
            // independent promise.
            this.selectedBackendPromises.set(
              extension,
              Promise.resolve(result)
            );

            return result;
          });
      }

      // If migrating to the IDB backend is not enabled by the preference, then we
      // don't need to migrate any data and the new backend is not enabled.
      if (!this.isBackendEnabled) {
        promise = Promise.resolve({ backendEnabled: false });
      } else {
        // In the main process, lazily create a storagePrincipal isolated in a
        // reserved user context id (its purpose is ensuring that the IndexedDB storage used
        // by the browser.storage.local API is not directly accessible from the extension code).
        const storagePrincipal = this.getStoragePrincipal(extension);

        // Serialize the nsIPrincipal object into a StructuredCloneHolder related to the privileged
        // js global, ready to be sent to the child processes.
        const serializedPrincipal = new StructuredCloneHolder(
          "ExtensionStorageIDB/selectBackend/serializedPrincipal",
          null,
          storagePrincipal,
          this
        );

        promise = migrateJSONFileData(extension, storagePrincipal)
          .then(() => {
            extension.setSharedData("storageIDBBackend", true);
            extension.setSharedData("storageIDBPrincipal", storagePrincipal);
            Services.ppmm.sharedData.flush();
            return {
              backendEnabled: true,
              storagePrincipal: serializedPrincipal,
            };
          })
          .catch(err => {
            // If the data migration promise is rejected, the old data has been read
            // successfully from the old JSONFile backend but it failed to be saved
            // into the IndexedDB backend (which is likely unrelated to the kind of
            // data stored and more likely a general issue with the IndexedDB backend)
            // In this case we keep the JSONFile backend enabled for this session
            // and we will retry to migrate to the IDB Backend the next time the
            // extension is being started.
            // TODO Bug 1465129: This should be a very unlikely scenario, some telemetry
            // data about it may be useful.
            extension.logWarning(
              "JSONFile backend is being kept enabled by an unexpected " +
                `IDBBackend failure: ${err.message}::${err.stack}`
            );
            extension.setSharedData("storageIDBBackend", false);
            Services.ppmm.sharedData.flush();

            return { backendEnabled: false };
          });
      }

      this.selectedBackendPromises.set(extension, promise);
    }

    return this.selectedBackendPromises.get(extension);
  },

  persist(storagePrincipal) {
    return new Promise((resolve, reject) => {
      const request = Services.qms.persist(storagePrincipal);
      request.callback = () => {
        if (request.resultCode === Cr.NS_OK) {
          resolve();
        } else {
          reject(
            new Error(
              `Failed to persist storage for principal: ${storagePrincipal.originNoSuffix}`
            )
          );
        }
      };
    });
  },

  /**
   * Open a connection to the IDB storage.local db for a given extension.
   * given extension.
   *
   * @param {nsIPrincipal} storagePrincipal
   *        The "internally reserved" extension storagePrincipal to be used to create
   *        the ExtensionStorageLocalIDB instance.
   * @param {boolean} persisted
   *        A boolean which indicates if the storage should be set into persistent mode.
   *
   * @returns {Promise<ExtensionStorageLocalIDB>}
   *          Return a promise which resolves to the opened IDB connection.
   */
  open(storagePrincipal, persisted) {
    if (!storagePrincipal) {
      return Promise.reject(new Error("Unexpected empty principal"));
    }
    let setPersistentMode = persisted
      ? this.persist(storagePrincipal)
      : Promise.resolve();
    return setPersistentMode.then(() =>
      ExtensionStorageLocalIDB.openForPrincipal(storagePrincipal)
    );
  },

  /**
   * Ensure that an error originated from the ExtensionStorageIDB methods is normalized
   * into an ExtensionError (e.g. DataCloneError and QuotaExceededError instances raised
   * from the internal IndexedDB operations have to be converted into an ExtensionError
   * to be accessible to the extension code).
   *
   * @typedef {import("ExtensionUtils.sys.mjs").ExtensionError} ExtensionError
   *
   * @param {object} params
   * @param {Error|ExtensionError|DOMException} params.error
   *        The error object to normalize.
   * @param {string} params.extensionId
   *        The id of the extension that was executing the storage.local method.
   * @param {string} params.storageMethod
   *        The storage method being executed when the error has been thrown
   *        (used to keep track of the unexpected error incidence in telemetry).
   *
   * @returns {ExtensionError}
   *          Return an ExtensionError error instance.
   */
  normalizeStorageError({ error, extensionId, storageMethod }) {
    const { ExtensionError } = lazy.ExtensionUtils;

    if (error instanceof ExtensionError) {
      // @ts-ignore (will go away after `lazy` is properly typed)
      return error;
    }

    let errorMessage;

    if (DOMException.isInstance(error)) {
      switch (error.name) {
        case "DataCloneError":
          errorMessage = String(error);
          break;
        case "QuotaExceededError":
          errorMessage = `${error.name}: storage.local API call exceeded its quota limitations.`;
          break;
      }
    }

    if (!errorMessage) {
      Cu.reportError(error);

      errorMessage = "An unexpected error occurred";

      ErrorsTelemetry.recordStorageLocalError({
        error,
        extensionId,
        storageMethod,
      });
    }

    return new ExtensionError(errorMessage);
  },

  addOnChangedListener(extensionId, listener) {
    let listeners = this.listeners.get(extensionId) || new Set();
    listeners.add(listener);
    this.listeners.set(extensionId, listeners);
  },

  removeOnChangedListener(extensionId, listener) {
    let listeners = this.listeners.get(extensionId);
    listeners.delete(listener);
  },

  notifyListeners(extensionId, changes) {
    let listeners = this.listeners.get(extensionId);
    if (listeners) {
      for (let listener of listeners) {
        listener(changes);
      }
    }
  },

  hasListeners(extensionId) {
    let listeners = this.listeners.get(extensionId);
    return listeners && listeners.size > 0;
  },
};

ExtensionStorageIDB.init();
PK
!<AFl33$modules/ExtensionStorageSync.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const STORAGE_SYNC_ENABLED_PREF = "webextensions.storage.sync.enabled";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const NS_ERROR_DOM_QUOTA_EXCEEDED_ERR = 0x80530016;

/** @type {Lazy} */
const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ExtensionCommon: "resource://gre/modules/ExtensionCommon.sys.mjs",
  ExtensionUtils: "resource://gre/modules/ExtensionUtils.sys.mjs",
  // We might end up falling back to kinto...
  extensionStorageSyncKinto:
    "resource://gre/modules/ExtensionStorageSyncKinto.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "prefPermitsStorageSync",
  STORAGE_SYNC_ENABLED_PREF,
  true
);

// This xpcom service implements a "bridge" from the JS world to the Rust world.
// It sets up the database and implements a callback-based version of the
// browser.storage API.
ChromeUtils.defineLazyGetter(lazy, "storageSvc", () =>
  Cc["@mozilla.org/extensions/storage/sync;1"]
    .getService(Ci.nsIInterfaceRequestor)
    .getInterface(Ci.mozIExtensionStorageArea)
);

// The interfaces which define the callbacks used by the bridge. There's a
// callback for success, failure, and to record data changes.
function ExtensionStorageApiCallback(resolve, reject, changeCallback) {
  this.resolve = resolve;
  this.reject = reject;
  this.changeCallback = changeCallback;
}

ExtensionStorageApiCallback.prototype = {
  QueryInterface: ChromeUtils.generateQI([
    "mozIExtensionStorageListener",
    "mozIExtensionStorageCallback",
  ]),

  handleSuccess(result) {
    this.resolve(result ? JSON.parse(result) : null);
  },

  handleError(code, message) {
    /** @type {Error & { code?: number }} */
    let e = new Error(message);
    e.code = code;
    Cu.reportError(e);
    this.reject(e);
  },

  onChanged(extId, json) {
    if (this.changeCallback && json) {
      try {
        this.changeCallback(extId, JSON.parse(json));
      } catch (ex) {
        Cu.reportError(ex);
      }
    }
  },
};

// The backing implementation of the browser.storage.sync web extension API.
export class ExtensionStorageSync {
  constructor() {
    this.listeners = new Map();
    // We are optimistic :) If we ever see the special nsresult which indicates
    // migration failure, it will become false. In practice, this will only ever
    // happen on the first operation.
    this.migrationOk = true;
  }

  // The main entry-point to our bridge. It performs some important roles:
  // * Ensures the API is allowed to be used.
  // * Works out what "extension id" to use.
  // * Turns the callback API into a promise API.
  async _promisify(fnName, extension, context, ...args) {
    let extId = extension.id;
    if (lazy.prefPermitsStorageSync !== true) {
      throw new lazy.ExtensionUtils.ExtensionError(
        `Please set ${STORAGE_SYNC_ENABLED_PREF} to true in about:config`
      );
    }

    if (this.migrationOk) {
      // We can call ours.
      try {
        return await new Promise((resolve, reject) => {
          let callback = new ExtensionStorageApiCallback(
            resolve,
            reject,
            (extId, changes) => this.notifyListeners(extId, changes)
          );
          let sargs = args.map(val => JSON.stringify(val));
          lazy.storageSvc[fnName](extId, ...sargs, callback);
        });
      } catch (ex) {
        if (ex.code != Cr.NS_ERROR_CANNOT_CONVERT_DATA) {
          // Some non-migration related error we want to sanitize and propagate.
          // The only "public" exception here is for quota failure - all others
          // are sanitized.
          let sanitized =
            ex.code == NS_ERROR_DOM_QUOTA_EXCEEDED_ERR
              ? // The same message as the local IDB implementation
                `QuotaExceededError: storage.sync API call exceeded its quota limitations.`
              : // The standard, generic extension error.
                "An unexpected error occurred";
          throw new lazy.ExtensionUtils.ExtensionError(sanitized);
        }
        // This means "migrate failed" so we must fall back to kinto.
        Cu.reportError(
          "migration of extension-storage failed - will fall back to kinto"
        );
        this.migrationOk = false;
      }
    }
    // We've detected failure to migrate, so we want to use kinto.
    return lazy.extensionStorageSyncKinto[fnName](extension, ...args, context);
  }

  set(extension, items, context) {
    return this._promisify("set", extension, context, items);
  }

  remove(extension, keys, context) {
    return this._promisify("remove", extension, context, keys);
  }

  clear(extension, context) {
    return this._promisify("clear", extension, context);
  }

  clearOnUninstall(extensionId) {
    if (!this.migrationOk) {
      // If the rust-based backend isn't being used,
      // no need to clear it.
      return;
    }
    // Resolve the returned promise once the request has been either resolved
    // or rejected (and report the error on the browser console in case of
    // unexpected clear failures on addon uninstall).
    return new Promise(resolve => {
      const callback = new ExtensionStorageApiCallback(
        resolve,
        err => {
          Cu.reportError(err);
          resolve();
        },
        // empty changeCallback (no need to notify the extension
        // while clearing the extension on uninstall).
        () => {}
      );
      lazy.storageSvc.clear(extensionId, callback);
    });
  }

  get(extension, spec, context) {
    return this._promisify("get", extension, context, spec);
  }

  getBytesInUse(extension, keys, context) {
    return this._promisify("getBytesInUse", extension, context, keys);
  }

  addOnChangedListener(extension, listener) {
    let listeners = this.listeners.get(extension.id) || new Set();
    listeners.add(listener);
    this.listeners.set(extension.id, listeners);
  }

  removeOnChangedListener(extension, listener) {
    let listeners = this.listeners.get(extension.id);
    listeners.delete(listener);
    if (listeners.size == 0) {
      this.listeners.delete(extension.id);
    }
  }

  notifyListeners(extId, changes) {
    let listeners = this.listeners.get(extId) || new Set();
    if (listeners) {
      for (let listener of listeners) {
        lazy.ExtensionCommon.runSafeSyncWithoutClone(listener, changes);
      }
    }
  }
}

export var extensionStorageSync = new ExtensionStorageSync();
PK
!<7y̮����)modules/ExtensionStorageSyncKinto.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// TODO:
// * find out how the Chrome implementation deals with conflicts

// TODO bug 1637465: Remove the Kinto-based storage implementation.

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const KINTO_PROD_SERVER_URL =
  "https://webextensions.settings.services.mozilla.com/v1";
const KINTO_DEFAULT_SERVER_URL = KINTO_PROD_SERVER_URL;

const STORAGE_SYNC_ENABLED_PREF = "webextensions.storage.sync.enabled";
const STORAGE_SYNC_SERVER_URL_PREF = "webextensions.storage.sync.serverURL";
const STORAGE_SYNC_SCOPE = "sync:addon_storage";
const STORAGE_SYNC_CRYPTO_COLLECTION_NAME = "storage-sync-crypto";
const STORAGE_SYNC_CRYPTO_KEYRING_RECORD_ID = "keys";
const STORAGE_SYNC_CRYPTO_SALT_LENGTH_BYTES = 32;
const FXA_OAUTH_OPTIONS = {
  scope: STORAGE_SYNC_SCOPE,
};
// Default is 5sec, which seems a bit aggressive on the open internet
const KINTO_REQUEST_TIMEOUT = 30000;

import { Log } from "resource://gre/modules/Log.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

/** @type {Lazy} */
const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  BulkKeyBundle: "resource://services-sync/keys.sys.mjs",
  CollectionKeyManager: "resource://services-sync/record.sys.mjs",
  CommonUtils: "resource://services-common/utils.sys.mjs",
  CryptoUtils: "resource://services-crypto/utils.sys.mjs",
  ExtensionCommon: "resource://gre/modules/ExtensionCommon.sys.mjs",
  FirefoxAdapter: "resource://services-common/kinto-storage-adapter.sys.mjs",
  Kinto: "resource://services-common/kinto-offline-client.sys.mjs",
  KintoHttpClient: "resource://services-common/kinto-http-client.sys.mjs",
  Observers: "resource://services-common/observers.sys.mjs",
  Utils: "resource://services-sync/util.sys.mjs",
});

/**
 * @typedef {any} Collection
 * @typedef {any} CollectionKeyManager
 * @typedef {any} FXAccounts
 * @typedef {any} KeyBundle
 * @typedef {any} SyncResultObject
 */

ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => {
  return ChromeUtils.importESModule(
    "resource://gre/modules/FxAccounts.sys.mjs"
  ).getFxAccountsSingleton();
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "prefPermitsStorageSync",
  STORAGE_SYNC_ENABLED_PREF,
  true
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "prefStorageSyncServerURL",
  STORAGE_SYNC_SERVER_URL_PREF,
  KINTO_DEFAULT_SERVER_URL
);
ChromeUtils.defineLazyGetter(lazy, "WeaveCrypto", function () {
  let { WeaveCrypto } = ChromeUtils.importESModule(
    "resource://services-crypto/WeaveCrypto.sys.mjs"
  );
  return new WeaveCrypto();
});

const { DefaultMap } = ExtensionUtils;

// Map of Extensions to Set<Contexts> to track contexts that are still
// "live" and use storage.sync.
const extensionContexts = new DefaultMap(() => new Set());
// Borrow logger from Sync.
const log = Log.repository.getLogger("Sync.Engine.Extension-Storage");

// A global that is fxAccounts, or null if (as on android) fxAccounts
// isn't available.
let _fxaService = null;
if (AppConstants.platform != "android") {
  _fxaService = lazy.fxAccounts;
}

class ServerKeyringDeleted extends Error {
  constructor() {
    super(
      "server keyring appears to have disappeared; we were called to decrypt null"
    );
  }
}

/**
 * Check for FXA and throw an exception if we don't have access.
 *
 * @param {object} fxAccounts  The reference we were hoping to use to
 *     access FxA
 * @param {string} action  The thing we were doing when we decided to
 *     see if we had access to FxA
 */
function throwIfNoFxA(fxAccounts, action) {
  if (!fxAccounts) {
    throw new Error(
      `${action} is impossible because FXAccounts is not available; are you on Android?`
    );
  }
}

/**
 * Utility function to enforce an order of fields when computing an HMAC.
 *
 * @param {KeyBundle} keyBundle  The key bundle to use to compute the HMAC
 * @param {string}    id         The record ID to use when computing the HMAC
 * @param {string}    IV         The IV to use when computing the HMAC
 * @param {string}    ciphertext The ciphertext over which to compute the HMAC
 * @returns {Promise<string>} The computed HMAC
 */
async function ciphertextHMAC(keyBundle, id, IV, ciphertext) {
  const hmacKey = lazy.CommonUtils.byteStringToArrayBuffer(keyBundle.hmacKey);
  const encoder = new TextEncoder();
  const data = encoder.encode(id + IV + ciphertext);
  const hmac = await lazy.CryptoUtils.hmac("SHA-256", hmacKey, data);
  return lazy.CommonUtils.bytesAsHex(
    lazy.CommonUtils.arrayBufferToByteString(hmac)
  );
}

/**
 * Get the current user's hashed kB.
 *
 * @param {FXAccounts} fxaService  The service to use to get the
 *     current user.
 * @returns {Promise<string>} sha256 of the user's kB as a hex string
 */
const getKBHash = async function (fxaService) {
  const key = await fxaService.keys.getKeyForScope(STORAGE_SYNC_SCOPE);
  return fxaService.keys.kidAsHex(key);
};

/**
 * A "remote transformer" that the Kinto library will use to
 * encrypt/decrypt records when syncing.
 *
 * This is an "abstract base class". Subclass this and override
 * getKeys() to use it.
 */
class EncryptionRemoteTransformer {
  async encode(record) {
    const keyBundle = await this.getKeys();
    if (record.ciphertext) {
      throw new Error("Attempt to reencrypt??");
    }
    let id = await this.getEncodedRecordId(record);
    if (!id) {
      throw new Error("Record ID is missing or invalid");
    }

    let IV = lazy.WeaveCrypto.generateRandomIV();
    let ciphertext = await lazy.WeaveCrypto.encrypt(
      JSON.stringify(record),
      keyBundle.encryptionKeyB64,
      IV
    );
    let hmac = await ciphertextHMAC(keyBundle, id, IV, ciphertext);
    const encryptedResult = { ciphertext, IV, hmac, id };

    // Copy over the _status field, so that we handle concurrency
    // headers (If-Match, If-None-Match) correctly.
    // DON'T copy over "deleted" status, because then we'd leak
    // plaintext deletes.
    encryptedResult._status =
      record._status == "deleted" ? "updated" : record._status;
    if (record.hasOwnProperty("last_modified")) {
      encryptedResult.last_modified = record.last_modified;
    }

    return encryptedResult;
  }

  async decode(record) {
    if (!record.ciphertext) {
      // This can happen for tombstones if a record is deleted.
      if (record.deleted) {
        return record;
      }
      throw new Error("No ciphertext: nothing to decrypt?");
    }
    const keyBundle = await this.getKeys();
    // Authenticate the encrypted blob with the expected HMAC
    let computedHMAC = await ciphertextHMAC(
      keyBundle,
      record.id,
      record.IV,
      record.ciphertext
    );

    if (computedHMAC != record.hmac) {
      lazy.Utils.throwHMACMismatch(record.hmac, computedHMAC);
    }

    // Handle invalid data here. Elsewhere we assume that cleartext is an object.
    let cleartext = await lazy.WeaveCrypto.decrypt(
      record.ciphertext,
      keyBundle.encryptionKeyB64,
      record.IV
    );
    let jsonResult = JSON.parse(cleartext);
    if (!jsonResult || typeof jsonResult !== "object") {
      throw new Error(
        "Decryption failed: result is <" + jsonResult + ">, not an object."
      );
    }

    if (record.hasOwnProperty("last_modified")) {
      jsonResult.last_modified = record.last_modified;
    }

    // _status: deleted records were deleted on a client, but
    // uploaded as an encrypted blob so we don't leak deletions.
    // If we get such a record, flag it as deleted.
    if (jsonResult._status == "deleted") {
      jsonResult.deleted = true;
    }

    return jsonResult;
  }

  /**
   * Retrieve keys to use during encryption.
   *
   * @returns {Promise<KeyBundle>}
   */
  getKeys() {
    throw new Error("override getKeys in a subclass");
  }

  /**
   * Compute the record ID to use for the encoded version of the
   * record.
   *
   * The default version just re-uses the record's ID.
   *
   * @param {object} record The record being encoded.
   * @returns {Promise<string>} The ID to use.
   */
  getEncodedRecordId(record) {
    return Promise.resolve(record.id);
  }
}

/**
 * An EncryptionRemoteTransformer that provides a keybundle derived
 * from the user's kB, suitable for encrypting a keyring.
 */
class KeyRingEncryptionRemoteTransformer extends EncryptionRemoteTransformer {
  constructor(fxaService) {
    super();
    this._fxaService = fxaService;
  }

  getKeys() {
    throwIfNoFxA(this._fxaService, "encrypting chrome.storage.sync records");
    const self = this;
    return (async function () {
      let key = await self._fxaService.keys.getKeyForScope(STORAGE_SYNC_SCOPE);
      return lazy.BulkKeyBundle.fromJWK(key);
    })();
  }
  // Pass through the kbHash field from the unencrypted record. If
  // encryption fails, we can use this to try to detect whether we are
  // being compromised or if the record here was encoded with a
  // different kB.
  async encode(record) {
    const encoded = await super.encode(record);
    encoded.kbHash = record.kbHash;
    return encoded;
  }

  async decode(record) {
    try {
      return await super.decode(record);
    } catch (e) {
      if (lazy.Utils.isHMACMismatch(e)) {
        const currentKBHash = await getKBHash(this._fxaService);
        if (record.kbHash != currentKBHash) {
          // Some other client encoded this with a kB that we don't
          // have access to.
          KeyRingEncryptionRemoteTransformer.throwOutdatedKB(
            currentKBHash,
            record.kbHash
          );
        }
      }
      throw e;
    }
  }

  // Generator and discriminator for KB-is-outdated exceptions.
  static throwOutdatedKB(shouldBe, is) {
    throw new Error(
      `kB hash on record is outdated: should be ${shouldBe}, is ${is}`
    );
  }

  static isOutdatedKB(exc) {
    const kbMessage = "kB hash on record is outdated: ";
    return (
      exc &&
      exc.message &&
      exc.message.indexOf &&
      exc.message.indexOf(kbMessage) == 0
    );
  }
}

/**
 * A Promise that centralizes initialization of ExtensionStorageSyncKinto.
 *
 * This centralizes the use of the Sqlite database, to which there is
 * only one connection which is shared by all threads.
 *
 * Fields in the object returned by this Promise:
 *
 * - connection: a Sqlite connection. Meant for internal use only.
 * - kinto: a KintoBase object, suitable for using in Firefox. All
 *   collections in this database will use the same Sqlite connection.
 *
 * @returns {Promise<object>}
 */
async function storageSyncInit() {
  // Memoize the result to share the connection.
  if (storageSyncInit.promise === undefined) {
    const path = "storage-sync.sqlite";
    storageSyncInit.promise = lazy.FirefoxAdapter.openConnection({ path })
      .then(connection => {
        return {
          connection,
          kinto: new lazy.Kinto({
            adapter: lazy.FirefoxAdapter,
            adapterOptions: { sqliteHandle: connection },
            timeout: KINTO_REQUEST_TIMEOUT,
            retry: 0,
          }),
        };
      })
      .catch(e => {
        // Ensure one failure doesn't break us forever.
        Cu.reportError(e);
        storageSyncInit.promise = undefined;
        throw e;
      });
  }
  return storageSyncInit.promise;
}
storageSyncInit.promise = undefined;

// Kinto record IDs have two conditions:
//
// - They must contain only ASCII alphanumerics plus - and _. To fix
// this, we encode all non-letters using _C_, where C is the
// percent-encoded character, so space becomes _20_
// and underscore becomes _5F_.
//
// - They must start with an ASCII letter. To ensure this, we prefix
// all keys with "key-".
function keyToId(key) {
  function escapeChar(match) {
    return "_" + match.codePointAt(0).toString(16).toUpperCase() + "_";
  }
  return "key-" + key.replace(/[^a-zA-Z0-9]/g, escapeChar);
}

// Convert a Kinto ID back into a chrome.storage key.
// Returns null if a key couldn't be parsed.
function idToKey(id) {
  function unescapeNumber(match, group1) {
    return String.fromCodePoint(parseInt(group1, 16));
  }
  // An escaped ID should match this regex.
  // An escaped ID should consist of only letters and numbers, plus
  // code points escaped as _[0-9a-f]+_.
  const ESCAPED_ID_FORMAT = /^(?:[a-zA-Z0-9]|_[0-9A-F]+_)*$/;

  if (!id.startsWith("key-")) {
    return null;
  }
  const unprefixed = id.slice(4);
  // Verify that the ID is the correct format.
  if (!ESCAPED_ID_FORMAT.test(unprefixed)) {
    return null;
  }
  return unprefixed.replace(/_([0-9A-F]+)_/g, unescapeNumber);
}

// An "id schema" used to validate Kinto IDs and generate new ones.
const storageSyncIdSchema = {
  // We should never generate IDs; chrome.storage only acts as a
  // key-value store, so we should always have a key.
  generate() {
    throw new Error("cannot generate IDs");
  },

  // See keyToId and idToKey for more details.
  validate(id) {
    return idToKey(id) !== null;
  },
};

// An "id schema" used for the system collection, which doesn't
// require validation or generation of IDs.
const cryptoCollectionIdSchema = {
  generate() {
    throw new Error("cannot generate IDs for system collection");
  },

  validate() {
    return true;
  },
};

/**
 * Wrapper around the crypto collection providing some handy utilities.
 */
class CryptoCollection {
  constructor(fxaService) {
    this._fxaService = fxaService;
  }

  async getCollection() {
    throwIfNoFxA(this._fxaService, "tried to access cryptoCollection");
    const { kinto } = await storageSyncInit();
    return kinto.collection(STORAGE_SYNC_CRYPTO_COLLECTION_NAME, {
      idSchema: cryptoCollectionIdSchema,
      remoteTransformers: [
        new KeyRingEncryptionRemoteTransformer(this._fxaService),
      ],
    });
  }

  /**
   * Generate a new salt for use in hashing extension and record
   * IDs.
   *
   * @returns {string} A base64-encoded string of the salt
   */
  getNewSalt() {
    return btoa(
      lazy.CryptoUtils.generateRandomBytesLegacy(
        STORAGE_SYNC_CRYPTO_SALT_LENGTH_BYTES
      )
    );
  }

  /**
   * Retrieve the keyring record from the crypto collection.
   *
   * You can use this if you want to check metadata on the keyring
   * record rather than use the keyring itself.
   *
   * The keyring record, if present, should have the structure:
   *
   * - kbHash: a hash of the user's kB. When this changes, we will
   *   try to sync the collection.
   * - uuid: a record identifier. This will only change when we wipe
   *   the collection (due to kB getting reset).
   * - keys: a "WBO" form of a CollectionKeyManager.
   * - salts: a normal JS Object with keys being collection IDs and
   *   values being base64-encoded salts to use when hashing IDs
   *   for that collection.
   *
   * @returns {Promise<object>}
   */
  async getKeyRingRecord() {
    const collection = await this.getCollection();
    const cryptoKeyRecord = await collection.getAny(
      STORAGE_SYNC_CRYPTO_KEYRING_RECORD_ID
    );

    let data = cryptoKeyRecord.data;
    if (!data) {
      // This is a new keyring. Invent an ID for this record. If this
      // changes, it means a client replaced the keyring, so we need to
      // reupload everything.
      const uuid = Services.uuid.generateUUID().toString();
      data = { uuid, id: STORAGE_SYNC_CRYPTO_KEYRING_RECORD_ID };
    }
    return data;
  }

  async getSalts() {
    const cryptoKeyRecord = await this.getKeyRingRecord();
    return cryptoKeyRecord && cryptoKeyRecord.salts;
  }

  /**
   * Used for testing with a known salt.
   *
   * @param {string} extensionId  The extension ID for which to set a
   *     salt.
   * @param {string} salt  The salt to use for this extension, as a
   *     base64-encoded salt.
   */
  async _setSalt(extensionId, salt) {
    const cryptoKeyRecord = await this.getKeyRingRecord();
    cryptoKeyRecord.salts = cryptoKeyRecord.salts || {};
    cryptoKeyRecord.salts[extensionId] = salt;
    await this.upsert(cryptoKeyRecord);
  }

  /**
   * Hash an extension ID for a given user so that an attacker can't
   * identify the extensions a user has installed.
   *
   * The extension ID is assumed to be a string (i.e. series of
   * code points), and its UTF8 encoding is prefixed with the salt
   * for that collection and hashed.
   *
   * The returned hash must conform to the syntax for Kinto
   * identifiers, which (as of this writing) must match
   * [a-zA-Z0-9][a-zA-Z0-9_-]*. We thus encode the hash using
   * "base64-url" without padding (so that we don't get any equals
   * signs (=)). For fear that a hash could start with a hyphen
   * (-) or an underscore (_), prefix it with "ext-".
   *
   * @param {string} extensionId The extension ID to obfuscate.
   * @returns {Promise<bytestring>} A collection ID suitable for use to sync to.
   */
  extensionIdToCollectionId(extensionId) {
    return this.hashWithExtensionSalt(
      lazy.CommonUtils.encodeUTF8(extensionId),
      extensionId
    ).then(hash => `ext-${hash}`);
  }

  /**
   * Hash some value with the salt for the given extension.
   *
   * The value should be a "bytestring", i.e. a string whose
   * "characters" are values, each within [0, 255]. You can produce
   * such a bytestring using e.g. CommonUtils.encodeUTF8.
   *
   * @typedef {string} bytestring
   *
   * The returned value is a base64url-encoded string of the hash.
   *
   * @param {bytestring} value The value to be hashed.
   * @param {string} extensionId The ID of the extension whose salt
   *                             we should use.
   * @returns {Promise<bytestring>} The hashed value.
   */
  async hashWithExtensionSalt(value, extensionId) {
    const salts = await this.getSalts();
    const saltBase64 = salts && salts[extensionId];
    if (!saltBase64) {
      // This should never happen; salts should be populated before
      // we need them by ensureCanSync.
      throw new Error(
        `no salt available for ${extensionId}; how did this happen?`
      );
    }

    const hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
      Ci.nsICryptoHash
    );
    hasher.init(hasher.SHA256);

    const salt = atob(saltBase64);
    const message = `${salt}\x00${value}`;
    const hash = lazy.CryptoUtils.digestBytes(message, hasher);
    return lazy.CommonUtils.encodeBase64URL(hash, false);
  }

  /**
   * Retrieve the actual keyring from the crypto collection.
   *
   * @returns {Promise<CollectionKeyManager>}
   */
  async getKeyRing() {
    const cryptoKeyRecord = await this.getKeyRingRecord();
    const collectionKeys = new lazy.CollectionKeyManager();
    if (cryptoKeyRecord.keys) {
      collectionKeys.setContents(
        cryptoKeyRecord.keys,
        cryptoKeyRecord.last_modified
      );
    } else {
      // We never actually use the default key, so it's OK if we
      // generate one multiple times.
      await collectionKeys.generateDefaultKey();
    }
    // Pass through uuid field so that we can save it if we need to.
    collectionKeys.uuid = cryptoKeyRecord.uuid;
    return collectionKeys;
  }

  async updateKBHash(kbHash) {
    const coll = await this.getCollection();
    await coll.update(
      { id: STORAGE_SYNC_CRYPTO_KEYRING_RECORD_ID, kbHash: kbHash },
      { patch: true }
    );
  }

  async upsert(record) {
    const collection = await this.getCollection();
    await collection.upsert(record);
  }

  async sync(extensionStorageSyncKinto) {
    const collection = await this.getCollection();
    return extensionStorageSyncKinto._syncCollection(collection, {
      strategy: "server_wins",
    });
  }

  /**
   * Reset sync status for ALL collections by directly
   * accessing the FirefoxAdapter.
   */
  async resetSyncStatus() {
    const coll = await this.getCollection();
    await coll.db.resetSyncStatus();
  }

  // Used only for testing.
  async _clear() {
    const collection = await this.getCollection();
    await collection.clear();
  }
}

/**
 * An EncryptionRemoteTransformer for extension records.
 *
 * It uses the special "keys" record to find a key for a given
 * extension, thus its name
 * CollectionKeyEncryptionRemoteTransformer.
 *
 * Also, during encryption, it will replace the ID of the new record
 * with a hashed ID, using the salt for this collection.
 *
 * @param {string} extensionId The extension ID for which to find a key.
 */
let CollectionKeyEncryptionRemoteTransformer = class extends EncryptionRemoteTransformer {
  constructor(cryptoCollection, keyring, extensionId) {
    super();
    this.cryptoCollection = cryptoCollection;
    this.keyring = keyring;
    this.extensionId = extensionId;
  }

  async getKeys() {
    if (!this.keyring.hasKeysFor([this.extensionId])) {
      // This should never happen. Keys should be created (and
      // synced) at the beginning of the sync cycle.
      throw new Error(
        `tried to encrypt records for ${this.extensionId}, but key is not present`
      );
    }
    return this.keyring.keyForCollection(this.extensionId);
  }

  getEncodedRecordId(record) {
    // It isn't really clear whether kinto.js record IDs are
    // bytestrings or strings that happen to only contain ASCII
    // characters, so encode them to be sure.
    const id = lazy.CommonUtils.encodeUTF8(record.id);
    // Like extensionIdToCollectionId, the rules about Kinto record
    // IDs preclude equals signs or strings starting with a
    // non-alphanumeric, so prefix all IDs with a constant "id-".
    return this.cryptoCollection
      .hashWithExtensionSalt(id, this.extensionId)
      .then(hash => `id-${hash}`);
  }
};

/**
 * Clean up now that one context is no longer using this extension's collection.
 *
 * @param {Extension} extension
 *                    The extension whose context just ended.
 * @param {BaseContext} context
 *                  The context that just ended.
 */
function cleanUpForContext(extension, context) {
  const contexts = extensionContexts.get(extension);
  contexts.delete(context);
  if (contexts.size === 0) {
    // Nobody else is using this collection. Clean up.
    extensionContexts.delete(extension);
  }
}

/**
 * Generate a promise that produces the Collection for an extension.
 *
 * @param {Extension} extension
 *                    The extension whose collection needs to
 *                    be opened.
 * @param {object} options
 *                 Options to be passed to the call to `.collection()`.
 * @returns {Promise<Collection>}
 */
const openCollection = async function (extension, options = {}) {
  let collectionId = extension.id;
  const { kinto } = await storageSyncInit();
  const coll = kinto.collection(collectionId, {
    ...options,
    idSchema: storageSyncIdSchema,
  });
  return coll;
};

export class ExtensionStorageSyncKinto {
  /**
   * @param {FXAccounts} fxaService (Optional) If not
   *    present, trying to sync will fail.
   */
  constructor(fxaService) {
    this._fxaService = fxaService;
    this.cryptoCollection = new CryptoCollection(fxaService);
    this.listeners = new WeakMap();
  }

  /**
   * Get a set of extensions to sync (including the ones with an
   * active extension context that used the storage.sync API and
   * the extensions that are enabled and have been synced before).
   *
   * @returns {Promise<Set<Extension>>}
   *   A promise which resolves to the set of the extensions to sync.
   */
  async getExtensions() {
    // Start from the set of the extensions with an active
    // context that used the storage.sync APIs.
    const extensions = new Set(extensionContexts.keys());

    const allEnabledExtensions = await lazy.AddonManager.getAddonsByTypes([
      "extension",
    ]);

    // Get the existing extension collections salts.
    const keysRecord = await this.cryptoCollection.getKeyRingRecord();

    // Add any enabled extensions that have been synced before.
    for (const addon of allEnabledExtensions) {
      if (this.hasSaltsFor(keysRecord, [addon.id])) {
        const policy = WebExtensionPolicy.getByID(addon.id);
        if (policy && policy.extension) {
          extensions.add(policy.extension);
        }
      }
    }

    return extensions;
  }

  async syncAll() {
    const extensions = await this.getExtensions();
    const extIds = Array.from(extensions, extension => extension.id);
    log.debug(`Syncing extension settings for ${JSON.stringify(extIds)}`);
    if (!extIds.length) {
      // No extensions to sync. Get out.
      return;
    }
    await this.ensureCanSync(extIds);
    await this.checkSyncKeyRing();
    const keyring = await this.cryptoCollection.getKeyRing();
    const promises = Array.from(extensions, extension => {
      const remoteTransformers = [
        new CollectionKeyEncryptionRemoteTransformer(
          this.cryptoCollection,
          keyring,
          extension.id
        ),
      ];
      return openCollection(extension, { remoteTransformers }).then(coll => {
        return this.sync(extension, coll);
      });
    });
    await Promise.all(promises);
  }

  async sync(extension, collection) {
    throwIfNoFxA(this._fxaService, "syncing chrome.storage.sync");
    const isSignedIn = !!(await this._fxaService.getSignedInUser());
    if (!isSignedIn) {
      // FIXME: this should support syncing to self-hosted
      log.info("User was not signed into FxA; cannot sync");
      throw new Error("Not signed in to FxA");
    }
    const collectionId = await this.cryptoCollection.extensionIdToCollectionId(
      extension.id
    );
    let syncResults;
    try {
      syncResults = await this._syncCollection(collection, {
        strategy: "client_wins",
        collection: collectionId,
      });
    } catch (err) {
      log.warn("Syncing failed", err);
      throw err;
    }

    let changes = {};
    for (const record of syncResults.created) {
      changes[record.key] = {
        newValue: record.data,
      };
    }
    for (const record of syncResults.updated) {
      // N.B. It's safe to just pick old.key because it's not
      // possible to "rename" a record in the storage.sync API.
      const key = record.old.key;
      changes[key] = {
        oldValue: record.old.data,
        newValue: record.new.data,
      };
    }
    for (const record of syncResults.deleted) {
      changes[record.key] = {
        oldValue: record.data,
      };
    }
    for (const resolution of syncResults.resolved) {
      // FIXME: We can't send a "changed" notification because
      // kinto.js only provides the newly-resolved value. But should
      // we even send a notification? We use CLIENT_WINS so nothing
      // has really "changed" on this end. (The change will come on
      // the other end when it pulls down the update, which is handled
      // by the "updated" case above.) If we are going to send a
      // notification, what best values for "old" and "new"?  This
      // might violate client code's assumptions, since from their
      // perspective, we were in state L, but this diff is from R ->
      // L.
      const accepted = resolution.accepted;
      changes[accepted.key] = {
        newValue: accepted.data,
      };
    }
    if (Object.keys(changes).length) {
      this.notifyListeners(extension, changes);
    }
    log.info(`Successfully synced '${collection.name}'`);
  }

  /**
   * Utility function that handles the common stuff about syncing all
   * Kinto collections (including "meta" collections like the crypto
   * one).
   *
   * @param {Collection} collection
   * @param {object} options
   *                 Additional options to be passed to sync().
   * @returns {Promise<SyncResultObject>}
   */
  _syncCollection(collection, options) {
    // FIXME: this should support syncing to self-hosted
    return this._requestWithToken(
      `Syncing ${collection.name}`,
      function (token) {
        const allOptions = Object.assign(
          {},
          {
            remote: lazy.prefStorageSyncServerURL,
            headers: {
              Authorization: "Bearer " + token,
            },
          },
          options
        );

        return collection.sync(allOptions);
      }
    );
  }

  // Make a Kinto request with a current FxA token.
  // If the response indicates that the token might have expired,
  // retry the request.
  async _requestWithToken(description, f) {
    throwIfNoFxA(
      this._fxaService,
      "making remote requests from chrome.storage.sync"
    );
    const fxaToken = await this._fxaService.getOAuthToken(FXA_OAUTH_OPTIONS);
    try {
      return await f(fxaToken);
    } catch (e) {
      if (e && e.response && e.response.status == 401) {
        // Our token might have expired. Refresh and retry.
        log.info("Token might have expired");
        await this._fxaService.removeCachedOAuthToken({ token: fxaToken });
        const newToken = await this._fxaService.getOAuthToken(
          FXA_OAUTH_OPTIONS
        );

        // If this fails too, let it go.
        return f(newToken);
      }
      // Otherwise, we don't know how to handle this error, so just reraise.
      log.error(`${description}: request failed`, e);
      throw e;
    }
  }

  /**
   * Helper similar to _syncCollection, but for deleting the user's bucket.
   *
   * @returns {Promise<void>}
   */
  _deleteBucket() {
    log.error("Deleting default bucket and everything in it");
    return this._requestWithToken("Clearing server", function (token) {
      const headers = { Authorization: "Bearer " + token };
      const kintoHttp = new lazy.KintoHttpClient(
        lazy.prefStorageSyncServerURL,
        {
          headers: headers,
          timeout: KINTO_REQUEST_TIMEOUT,
        }
      );
      return kintoHttp.deleteBucket("default");
    });
  }

  async ensureSaltsFor(keysRecord, extIds) {
    const newSalts = Object.assign({}, keysRecord.salts);
    for (let collectionId of extIds) {
      if (newSalts[collectionId]) {
        continue;
      }

      newSalts[collectionId] = this.cryptoCollection.getNewSalt();
    }

    return newSalts;
  }

  /**
   * Check whether the keys record (provided) already has salts for
   * all the extensions given in extIds.
   *
   * @param {object} keysRecord A previously-retrieved keys record.
   * @param {Array<string>} extIds The IDs of the extensions which
   *                need salts.
   * @returns {boolean}
   */
  hasSaltsFor(keysRecord, extIds) {
    if (!keysRecord.salts) {
      return false;
    }

    for (let collectionId of extIds) {
      if (!keysRecord.salts[collectionId]) {
        return false;
      }
    }

    return true;
  }

  /**
   * Recursive promise that terminates when our local collectionKeys,
   * as well as that on the server, have keys for all the extensions
   * in extIds.
   *
   * @param {Array<string>} extIds
   *                        The IDs of the extensions which need keys.
   * @returns {Promise<CollectionKeyManager>}
   */
  async ensureCanSync(extIds) {
    const keysRecord = await this.cryptoCollection.getKeyRingRecord();
    const collectionKeys = await this.cryptoCollection.getKeyRing();
    if (
      collectionKeys.hasKeysFor(extIds) &&
      this.hasSaltsFor(keysRecord, extIds)
    ) {
      return collectionKeys;
    }

    log.info(`Need to create keys and/or salts for ${JSON.stringify(extIds)}`);
    const kbHash = await getKBHash(this._fxaService);
    const newKeys = await collectionKeys.ensureKeysFor(extIds);
    const newSalts = await this.ensureSaltsFor(keysRecord, extIds);
    const newRecord = {
      id: STORAGE_SYNC_CRYPTO_KEYRING_RECORD_ID,
      keys: newKeys.asWBO().cleartext,
      salts: newSalts,
      uuid: collectionKeys.uuid,
      // Add a field for the current kB hash.
      kbHash: kbHash,
    };
    await this.cryptoCollection.upsert(newRecord);
    const result = await this._syncKeyRing(newRecord);
    if (result.resolved.length) {
      // We had a conflict which was automatically resolved. We now
      // have a new keyring which might have keys for the
      // collections. Recurse.
      return this.ensureCanSync(extIds);
    }

    // No conflicts. We're good.
    return newKeys;
  }

  /**
   * Update the kB in the crypto record.
   */
  async updateKeyRingKB() {
    throwIfNoFxA(this._fxaService, 'use of chrome.storage.sync "keyring"');
    const isSignedIn = !!(await this._fxaService.getSignedInUser());
    if (!isSignedIn) {
      // Although this function is meant to be called on login,
      // it's not unreasonable to check any time, even if we aren't
      // logged in.
      //
      // If we aren't logged in, we don't have any information about
      // the user's kB, so we can't be sure that the user changed
      // their kB, so just return.
      return;
    }

    const thisKBHash = await getKBHash(this._fxaService);
    await this.cryptoCollection.updateKBHash(thisKBHash);
  }

  /**
   * Make sure the keyring is up to date and synced.
   *
   * This is called on syncs to make sure that we don't sync anything
   * to any collection unless the key for that collection is on the
   * server.
   */
  async checkSyncKeyRing() {
    await this.updateKeyRingKB();

    const cryptoKeyRecord = await this.cryptoCollection.getKeyRingRecord();
    if (cryptoKeyRecord && cryptoKeyRecord._status !== "synced") {
      // We haven't successfully synced the keyring since the last
      // change. This could be because kB changed and we touched the
      // keyring, or it could be because we failed to sync after
      // adding a key. Either way, take this opportunity to sync the
      // keyring.
      await this._syncKeyRing(cryptoKeyRecord);
    }
  }

  async _syncKeyRing(cryptoKeyRecord) {
    throwIfNoFxA(this._fxaService, 'syncing chrome.storage.sync "keyring"');
    try {
      // Try to sync using server_wins.
      //
      // We use server_wins here because whatever is on the server is
      // at least consistent with itself -- the crypto in the keyring
      // matches the crypto on the collection records. This is because
      // we generate and upload keys just before syncing data.
      //
      // It's possible that we can't decode the version on the server.
      // This can happen if a user is locked out of their account, and
      // does a "reset password" to get in on a new device. In this
      // case, we are in a bind -- we can't decrypt the record on the
      // server, so we can't merge keys. If this happens, we try to
      // figure out if we're the one with the correct (new) kB or if
      // we just got locked out because we have the old kB. If we're
      // the one with the correct kB, we wipe the server and reupload
      // everything, including a new keyring.
      //
      // If another device has wiped the server, we need to reupload
      // everything we have on our end too, so we detect this by
      // adding a UUID to the keyring. UUIDs are preserved throughout
      // the lifetime of a keyring, so the only time a keyring UUID
      // changes is when a new keyring is uploaded, which only happens
      // after a server wipe. So when we get a "conflict" (resolved by
      // server_wins), we check whether the server version has a new
      // UUID. If so, reset our sync status, so that we'll reupload
      // everything.
      const result = await this.cryptoCollection.sync(this);
      if (result.resolved.length) {
        // Automatically-resolved conflict. It should
        // be for the keys record.
        const resolutionIds = result.resolved.map(resolution => resolution.id);
        if (resolutionIds > 1) {
          // This should never happen -- there is only ever one record
          // in this collection.
          log.error(
            `Too many resolutions for sync-storage-crypto collection: ${JSON.stringify(
              resolutionIds
            )}`
          );
        }
        const keyResolution = result.resolved[0];
        if (keyResolution.id != STORAGE_SYNC_CRYPTO_KEYRING_RECORD_ID) {
          // This should never happen -- there should only ever be the
          // keyring in this collection.
          log.error(
            `Strange conflict in sync-storage-crypto collection: ${JSON.stringify(
              resolutionIds
            )}`
          );
        }

        // Due to a bug in the server-side code (see
        // https://github.com/Kinto/kinto/issues/1209), lots of users'
        // keyrings were deleted. We discover this by trying to push a
        // new keyring (because the user aded a new extension), and we
        // get a conflict. We have SERVER_WINS, so the client will
        // accept this deleted keyring and delete it locally. Discover
        // this and undo it.
        if (keyResolution.accepted === null) {
          log.error("Conflict spotted -- the server keyring was deleted");
          await this.cryptoCollection.upsert(keyResolution.rejected);
          // It's possible that the keyring on the server that was
          // deleted had keys for other extensions, which had already
          // encrypted data. For this to happen, another client would
          // have had to upload the keyring and then the delete happened
          // before this client did a sync (and got the new extension
          // and tried to sync the keyring again). Just to be safe,
          // let's signal that something went wrong and we should wipe
          // the bucket.
          throw new ServerKeyringDeleted();
        }

        if (keyResolution.accepted.uuid != cryptoKeyRecord.uuid) {
          log.info(
            `Detected a new UUID (${keyResolution.accepted.uuid}, was ${cryptoKeyRecord.uuid}). Resetting sync status for everything.`
          );
          await this.cryptoCollection.resetSyncStatus();

          // Server version is now correct. Return that result.
          return result;
        }
      }
      // No conflicts, or conflict was just someone else adding keys.
      return result;
    } catch (e) {
      if (
        KeyRingEncryptionRemoteTransformer.isOutdatedKB(e) ||
        e instanceof ServerKeyringDeleted ||
        // This is another way that ServerKeyringDeleted can
        // manifest; see bug 1350088 for more details.
        e.message.includes("Server has been flushed.")
      ) {
        // Check if our token is still valid, or if we got locked out
        // between starting the sync and talking to Kinto.
        const isSessionValid = await this._fxaService.checkAccountStatus();
        if (isSessionValid) {
          log.error(
            "Couldn't decipher old keyring; deleting the default bucket and resetting sync status"
          );
          await this._deleteBucket();
          await this.cryptoCollection.resetSyncStatus();

          // Reupload our keyring, which is the only new keyring.
          // We don't want client_wins here because another device
          // could have uploaded another keyring in the meantime.
          return this.cryptoCollection.sync(this);
        }
      }
      throw e;
    }
  }

  registerInUse(extension, context) {
    // Register that the extension and context are in use.
    const contexts = extensionContexts.get(extension);
    if (!contexts.has(context)) {
      // New context. Register it and make sure it cleans itself up
      // when it closes.
      contexts.add(context);
      context.callOnClose({
        close: () => cleanUpForContext(extension, context),
      });
    }
  }

  /**
   * Get the collection for an extension, and register the extension
   * as being "in use".
   *
   * @param {Extension} extension
   *                    The extension for which we are seeking
   *                    a collection.
   * @param {BaseContext} context
   *                  The context of the extension, so that we can
   *                  stop syncing the collection when the extension ends.
   * @returns {Promise<Collection>}
   */
  getCollection(extension, context) {
    if (lazy.prefPermitsStorageSync !== true) {
      return Promise.reject({
        message: `Please set ${STORAGE_SYNC_ENABLED_PREF} to true in about:config`,
      });
    }
    this.registerInUse(extension, context);
    return openCollection(extension);
  }

  async set(extension, items, context) {
    const coll = await this.getCollection(extension, context);
    const keys = Object.keys(items);
    const ids = keys.map(keyToId);
    const changes = await coll.execute(
      txn => {
        let changes = {};
        for (let [i, key] of keys.entries()) {
          const id = ids[i];
          let item = items[key];
          let { oldRecord } = txn.upsert({
            id,
            key,
            data: item,
          });
          changes[key] = {
            newValue: item,
          };
          if (oldRecord) {
            // Extract the "data" field from the old record, which
            // represents the value part of the key-value store
            changes[key].oldValue = oldRecord.data;
          }
        }
        return changes;
      },
      { preloadIds: ids }
    );
    this.notifyListeners(extension, changes);
  }

  async remove(extension, keys, context) {
    const coll = await this.getCollection(extension, context);
    keys = [].concat(keys);
    const ids = keys.map(keyToId);
    let changes = {};
    await coll.execute(
      txn => {
        for (let [i, key] of keys.entries()) {
          const id = ids[i];
          const res = txn.deleteAny(id);
          if (res.deleted) {
            changes[key] = {
              oldValue: res.data.data,
            };
          }
        }
        return changes;
      },
      { preloadIds: ids }
    );
    if (Object.keys(changes).length) {
      this.notifyListeners(extension, changes);
    }
  }

  /* Wipe local data for all collections without causing the changes to be synced */
  async clearAll() {
    const extensions = await this.getExtensions();
    const extIds = Array.from(extensions, extension => extension.id);
    log.debug(`Clearing extension data for ${JSON.stringify(extIds)}`);
    if (extIds.length) {
      const promises = Array.from(extensions, extension => {
        return openCollection(extension).then(coll => {
          return coll.clear();
        });
      });
      await Promise.all(promises);
    }

    // and clear the crypto collection.
    const cc = await this.cryptoCollection.getCollection();
    await cc.clear();
  }

  async clear(extension, context) {
    // We can't call Collection#clear here, because that just clears
    // the local database. We have to explicitly delete everything so
    // that the deletions can be synced as well.
    const coll = await this.getCollection(extension, context);
    const res = await coll.list();
    const records = res.data;
    const keys = records.map(record => record.key);
    await this.remove(extension, keys, context);
  }

  async get(extension, spec, context) {
    const coll = await this.getCollection(extension, context);
    let keys, records;
    if (spec === null) {
      records = {};
      const res = await coll.list();
      for (let record of res.data) {
        records[record.key] = record.data;
      }
      return records;
    }
    if (typeof spec === "string") {
      keys = [spec];
      records = {};
    } else if (Array.isArray(spec)) {
      keys = spec;
      records = {};
    } else {
      keys = Object.keys(spec);
      records = Cu.cloneInto(spec, {});
    }

    for (let key of keys) {
      const res = await coll.getAny(keyToId(key));
      if (res.data && res.data._status != "deleted") {
        records[res.data.key] = res.data.data;
      }
    }

    return records;
  }

  async getBytesInUse(extension, keys, context) {
    // This is defined by the chrome spec as being the length of the key and
    // the length of the json repr of the value.
    let size = 0;
    let data = await this.get(extension, keys, context);
    for (const [key, value] of Object.entries(data)) {
      size += key.length + JSON.stringify(value).length;
    }
    return size;
  }

  addOnChangedListener(extension, listener, context) {
    let listeners = this.listeners.get(extension) || new Set();
    listeners.add(listener);
    this.listeners.set(extension, listeners);

    this.registerInUse(extension, context);
  }

  removeOnChangedListener(extension, listener) {
    let listeners = this.listeners.get(extension);
    listeners.delete(listener);
    if (listeners.size == 0) {
      this.listeners.delete(extension);
    }
  }

  notifyListeners(extension, changes) {
    lazy.Observers.notify("ext.storage.sync-changed");
    let listeners = this.listeners.get(extension) || new Set();
    if (listeners) {
      for (let listener of listeners) {
        lazy.ExtensionCommon.runSafeSyncWithoutClone(listener, changes);
      }
    }
  }
}

/**
 * Global ExtensionStorageSyncKinto instance that extensions and Fx Sync use.
 * On Android, because there's no FXAccounts instance, any syncing
 * operations will fail.
 */
export const extensionStorageSyncKinto = new ExtensionStorageSyncKinto(
  _fxaService
);

// For test use only.
export const KintoStorageTestUtils = {
  CollectionKeyEncryptionRemoteTransformer,
  CryptoCollection,
  EncryptionRemoteTransformer,
  KeyRingEncryptionRemoteTransformer,
  cleanUpForContext,
  idToKey,
  keyToId,
};
PK
!<�j�U
/
/"modules/ExtensionTelemetry.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

const { DefaultWeakMap } = ExtensionUtils;

// Map of the base histogram ids for the metrics recorded for the extensions.
const HISTOGRAMS_IDS = {
  backgroundPageLoad: "WEBEXT_BACKGROUND_PAGE_LOAD_MS",
  browserActionPopupOpen: "WEBEXT_BROWSERACTION_POPUP_OPEN_MS",
  browserActionPreloadResult: "WEBEXT_BROWSERACTION_POPUP_PRELOAD_RESULT_COUNT",
  contentScriptInjection: "WEBEXT_CONTENT_SCRIPT_INJECTION_MS",
  eventPageRunningTime: "WEBEXT_EVENTPAGE_RUNNING_TIME_MS",
  eventPageIdleResult: "WEBEXT_EVENTPAGE_IDLE_RESULT_COUNT",
  extensionStartup: "WEBEXT_EXTENSION_STARTUP_MS",
  pageActionPopupOpen: "WEBEXT_PAGEACTION_POPUP_OPEN_MS",
  storageLocalGetIdb: "WEBEXT_STORAGE_LOCAL_IDB_GET_MS",
  storageLocalSetIdb: "WEBEXT_STORAGE_LOCAL_IDB_SET_MS",
};

const GLEAN_METRICS_TYPES = {
  backgroundPageLoad: "timing_distribution",
  browserActionPopupOpen: "timing_distribution",
  browserActionPreloadResult: "labeled_counter",
  contentScriptInjection: "timing_distribution",
  eventPageRunningTime: "custom_distribution",
  eventPageIdleResult: "labeled_counter",
  extensionStartup: "timing_distribution",
  pageActionPopupOpen: "timing_distribution",
  storageLocalGetIdb: "timing_distribution",
  storageLocalSetIdb: "timing_distribution",
};

/**
 * Get a trimmed version of the given string if it is longer than 80 chars (used in telemetry
 * when a string may be longer than allowed).
 *
 * @param {string} str
 *        The original string content.
 *
 * @returns {string}
 *          The trimmed version of the string when longer than 80 chars, or the given string
 *          unmodified otherwise.
 */
export function getTrimmedString(str) {
  if (str.length <= 80) {
    return str;
  }

  const length = str.length;

  // Trim the string to prevent a flood of warnings messages logged internally by recordEvent,
  // the trimmed version is going to be composed by the first 40 chars and the last 37 and 3 dots
  // that joins the two parts, to visually indicate that the string has been trimmed.
  return `${str.slice(0, 40)}...${str.slice(length - 37, length)}`;
}

/**
 * Get a string representing the error which can be included in telemetry data.
 * If the resulting string is longer than 80 characters it is going to be
 * trimmed using the `getTrimmedString` helper function.
 *
 * @param {Error | DOMException | ReturnType<typeof Components.Exception>} error
 *        The error object to convert into a string representation.
 *
 * @returns {string}
 *          - The `error.name` string on DOMException or Components.Exception
 *            (trimmed to 80 chars).
 *          - "NoError" if error is falsey.
 *          - "UnkownError" as a fallback.
 */
export function getErrorNameForTelemetry(error) {
  let text = "UnknownError";
  if (!error) {
    text = "NoError";
  } else if (
    DOMException.isInstance(error) ||
    error instanceof Components.Exception
  ) {
    text = error.name;
    if (text.length > 80) {
      text = getTrimmedString(text);
    }
  }
  return text;
}

/**
 * This is a internal helper object which contains a collection of helpers used to make it easier
 * to collect extension telemetry (in both the general histogram and in the one keyed by addon id).
 *
 * This helper object is not exported from ExtensionUtils, it is used by the ExtensionTelemetry
 * Proxy which is exported and used by the callers to record telemetry data for one of the
 * supported metrics.
 */
class ExtensionTelemetryMetric {
  constructor(metric) {
    this.metric = metric;
    this.gleanTimerIdsMap = new DefaultWeakMap(() => new WeakMap());
  }

  // Stopwatch methods.
  stopwatchStart(extension, obj = extension) {
    this._wrappedStopwatchMethod("start", this.metric, extension, obj);
    this._wrappedTimingDistributionMethod("start", this.metric, extension, obj);
  }

  stopwatchFinish(extension, obj = extension) {
    this._wrappedStopwatchMethod("finish", this.metric, extension, obj);
    this._wrappedTimingDistributionMethod(
      "stopAndAccumulate",
      this.metric,
      extension,
      obj
    );
  }

  stopwatchCancel(extension, obj = extension) {
    this._wrappedStopwatchMethod("cancel", this.metric, extension, obj);
    this._wrappedTimingDistributionMethod(
      "cancel",
      this.metric,
      extension,
      obj
    );
  }

  // Histogram counters methods.
  histogramAdd(opts) {
    this._histogramAdd(this.metric, opts);
  }

  /**
   * Wraps a call to Glean timing_distribution methods for a given metric and extension.
   *
   * @param {string} method
   *        The Glean timing_distribution method to call ("start", "stopAndAccumulate" or "cancel").
   * @param {string} metric
   *        The Glean timing_distribution metric to record (used to retrieve the Glean metric type from the
   *        GLEAN_METRICS_TYPES map).
   * @param {Extension | ExtensionChild} extension
   *        The extension to record the telemetry for.
   * @param {any | undefined} [obj = extension]
   *        An optional object the timing_distribution method call should be related to
   *        (defaults to the extension parameter when missing).
   */
  _wrappedTimingDistributionMethod(method, metric, extension, obj = extension) {
    if (!extension) {
      Cu.reportError(`Mandatory extension parameter is undefined`);
      return;
    }

    const gleanMetricType = GLEAN_METRICS_TYPES[metric];
    if (!gleanMetricType) {
      Cu.reportError(`Unknown metric ${metric}`);
      return;
    }

    if (gleanMetricType !== "timing_distribution") {
      Cu.reportError(
        `Glean metric ${metric} is of type ${gleanMetricType}, expected timing_distribution`
      );
      return;
    }

    switch (method) {
      case "start": {
        const timerId = Glean.extensionsTiming[metric].start();
        this.gleanTimerIdsMap.get(extension).set(obj, timerId);
        break;
      }
      case "stopAndAccumulate": // Intentional fall-through.
      case "cancel": {
        if (
          !this.gleanTimerIdsMap.has(extension) ||
          !this.gleanTimerIdsMap.get(extension).has(obj)
        ) {
          Cu.reportError(
            `timerId not found for Glean timing_distribution ${metric}`
          );
          return;
        }
        const timerId = this.gleanTimerIdsMap.get(extension).get(obj);
        this.gleanTimerIdsMap.get(extension).delete(obj);
        Glean.extensionsTiming[metric][method](timerId);
        break;
      }
      default:
        Cu.reportError(
          `Unknown method ${method} call for Glean metric ${metric}`
        );
    }
  }

  /**
   * Wraps a call to a TelemetryStopwatch method for a given metric and extension.
   *
   * @param {string} method
   *        The stopwatch method to call ("start", "finish" or "cancel").
   * @param {string} metric
   *        The stopwatch metric to record (used to retrieve the base histogram id from the HISTOGRAMS_IDS object).
   * @param {Extension | ExtensionChild} extension
   *        The extension to record the telemetry for.
   * @param {any | undefined} [obj = extension]
   *        An optional telemetry stopwatch object (which defaults to the extension parameter when missing).
   */
  _wrappedStopwatchMethod(method, metric, extension, obj = extension) {
    if (!extension) {
      Cu.reportError(`Mandatory extension parameter is undefined`);
      return;
    }

    const baseId = HISTOGRAMS_IDS[metric];
    if (!baseId) {
      Cu.reportError(`Unknown metric ${metric}`);
      return;
    }

    // Record metric in the general histogram.
    TelemetryStopwatch[method](baseId, obj);

    // Record metric in the histogram keyed by addon id.
    let extensionId = getTrimmedString(extension.id);
    TelemetryStopwatch[`${method}Keyed`](
      `${baseId}_BY_ADDONID`,
      extensionId,
      obj
    );
  }

  /**
   * Record a telemetry category and/or value for a given metric.
   *
   * @param {string} metric
   *        The metric to record (used to retrieve the base histogram id from the _histogram object).
   * @param {object}                              options
   * @param {Extension | ExtensionChild} options.extension
   *        The extension to record the telemetry for.
   * @param {string | undefined}                  [options.category]
   *        An optional histogram category.
   * @param {number | undefined}                  [options.value]
   *        An optional value to record.
   */
  _histogramAdd(metric, { category, extension, value }) {
    if (!extension) {
      Cu.reportError(`Mandatory extension parameter is undefined`);
      return;
    }

    const baseId = HISTOGRAMS_IDS[metric];
    if (!baseId) {
      Cu.reportError(`Unknown metric ${metric}`);
      return;
    }

    const histogram = Services.telemetry.getHistogramById(baseId);
    if (typeof category === "string") {
      histogram.add(category, value);
    } else {
      histogram.add(value);
    }

    const keyedHistogram = Services.telemetry.getKeyedHistogramById(
      `${baseId}_BY_ADDONID`
    );
    const extensionId = getTrimmedString(extension.id);

    if (typeof category === "string") {
      keyedHistogram.add(extensionId, category, value);
    } else {
      keyedHistogram.add(extensionId, value);
    }

    switch (GLEAN_METRICS_TYPES[metric]) {
      case "custom_distribution": {
        if (typeof category === "string") {
          Cu.reportError(
            `Unexpected unsupported category parameter set on Glean metric ${metric}`
          );
          return;
        }
        // NOTE: extensionsTiming may become a property of the GLEAN_METRICS_TYPES
        // map once we may introduce new histograms that are not part of the
        // extensionsTiming Glean metrics category.
        Glean.extensionsTiming[metric].accumulateSingleSample(value);
        break;
      }
      case "labeled_counter": {
        if (typeof category !== "string") {
          Cu.reportError(
            `Missing mandatory category on adding data to labeled Glean metric ${metric}`
          );
          return;
        }
        Glean.extensionsCounters[metric][category].add(value ?? 1);
        break;
      }
      default:
        Cu.reportError(
          `Unexpected unsupported Glean metric type "${GLEAN_METRICS_TYPES[metric]}" for metric ${metric}`
        );
    }
  }
}

// Cache of the ExtensionTelemetryMetric instances that has been lazily created by the
// Extension Telemetry Proxy.
/** @type {Map<string|symbol, ExtensionTelemetryMetric>} */
const metricsCache = new Map();

/**
 * This proxy object provides the telemetry helpers for the currently supported metrics (the ones listed in
 * HISTOGRAMS_IDS), the telemetry helpers for a particular metric are lazily created
 * when the related property is being accessed on this object for the first time, e.g.:
 *
 *      ExtensionTelemetry.extensionStartup.stopwatchStart(extension);
 *      ExtensionTelemetry.browserActionPreloadResult.histogramAdd({category: "Shown", extension});
 */
/** @type {Record<string, ExtensionTelemetryMetric>} */
// @ts-ignore no easy way in TS to say Proxy is a different type from target.
export var ExtensionTelemetry = new Proxy(metricsCache, {
  get(target, prop) {
    // NOTE: if we would be start adding glean probes that do not have a unified
    // telemetry histogram counterpart, we would need to change this check
    // accordingly.
    if (!(prop in HISTOGRAMS_IDS)) {
      throw new Error(`Unknown metric ${String(prop)}`);
    }

    // Lazily create and cache the metric result object.
    if (!target.has(prop)) {
      target.set(prop, new ExtensionTelemetryMetric(prop));
    }

    return target.get(prop);
  },
});
PK
!<&�i�x&x&modules/ExtensionUtils.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

// xpcshell doesn't handle idle callbacks well.
ChromeUtils.defineLazyGetter(lazy, "idleTimeout", () =>
  Services.appinfo.name === "XPCShell" ? 500 : undefined
);

// It would be nicer to go through `Services.appinfo`, but some tests need to be
// able to replace that field with a custom implementation before it is first
// called.
// eslint-disable-next-line mozilla/use-services
const appinfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);

let nextId = 0;
const uniqueProcessID = appinfo.uniqueProcessID;
// Store the process ID in a 16 bit field left shifted to end of a
// double's mantissa.
// Note: We can't use bitwise ops here, since they truncate to a 32 bit
// integer and we need all 53 mantissa bits.
const processIDMask = (uniqueProcessID & 0xffff) * 2 ** 37;

function getUniqueId() {
  // Note: We can't use bitwise ops here, since they truncate to a 32 bit
  // integer and we need all 53 mantissa bits.
  return processIDMask + nextId++;
}

function promiseTimeout(delay) {
  return new Promise(resolve => lazy.setTimeout(resolve, delay));
}

/**
 * An Error subclass for which complete error messages are always passed
 * to extensions, rather than being interpreted as an unknown error.
 */
export class ExtensionError extends DOMException {
  constructor(message) {
    super(message, "ExtensionError");
  }
  // Custom JS classes can't survive IPC, so need to check error name.
  static [Symbol.hasInstance](e) {
    return DOMException.isInstance(e) && e.name === "ExtensionError";
  }
}

function filterStack(error) {
  return String(error.stack).replace(
    /(^.*(Task\.jsm|Promise-backend\.js).*\n)+/gm,
    "<Promise Chain>\n"
  );
}

/**
 * An Error subclass used to recognize the errors that should
 * to be forwarded to the worker thread and being accessible
 * to the extension worker script (vs. the errors that should be
 * only logged internally and raised to the worker script as
 * the generic unexpected error).
 */
export class WorkerExtensionError extends DOMException {
  constructor(message) {
    super(message, "Error");
  }
}

/**
 * Similar to a WeakMap, but creates a new key with the given
 * constructor if one is not present.
 */
// @ts-ignore (https://github.com/microsoft/TypeScript/issues/56664)
class DefaultWeakMap extends WeakMap {
  constructor(defaultConstructor = undefined, init = undefined) {
    super(init);
    if (defaultConstructor) {
      this.defaultConstructor = defaultConstructor;
    }
  }

  get(key) {
    let value = super.get(key);
    if (value === undefined && !this.has(key)) {
      value = this.defaultConstructor(key);
      this.set(key, value);
    }
    return value;
  }
}

class DefaultMap extends Map {
  constructor(defaultConstructor = undefined, init = undefined) {
    super(init);
    if (defaultConstructor) {
      this.defaultConstructor = defaultConstructor;
    }
  }

  get(key) {
    let value = super.get(key);
    if (value === undefined && !this.has(key)) {
      value = this.defaultConstructor(key);
      this.set(key, value);
    }
    return value;
  }
}

function getInnerWindowID(window) {
  return window.windowGlobalChild?.innerWindowId;
}

/**
 * A set with a limited number of slots, which flushes older entries as
 * newer ones are added.
 *
 * @param {number} limit
 *        The maximum size to trim the set to after it grows too large.
 * @param {number} [slop = limit * .25]
 *        The number of extra entries to allow in the set after it
 *        reaches the size limit, before it is truncated to the limit.
 * @param {Iterable} [iterable]
 *        An iterable of initial entries to add to the set.
 */
class LimitedSet extends Set {
  constructor(limit, slop = Math.round(limit * 0.25), iterable = undefined) {
    super(iterable);
    this.limit = limit;
    this.slop = slop;
  }

  truncate(limit) {
    for (let item of this) {
      // Live set iterators can ge relatively expensive, since they need
      // to be updated after every modification to the set. Since
      // breaking out of the loop early will keep the iterator alive
      // until the next full GC, we're currently better off finishing
      // the entire loop even after we're done truncating.
      if (this.size > limit) {
        this.delete(item);
      }
    }
  }

  add(item) {
    if (this.size >= this.limit + this.slop && !this.has(item)) {
      this.truncate(this.limit - 1);
    }
    return super.add(item);
  }
}

/**
 * Returns a Promise which resolves when the given document's DOM has
 * fully loaded.
 *
 * @param {Document} doc The document to await the load of.
 * @returns {Promise<Document>}
 */
function promiseDocumentReady(doc) {
  if (doc.readyState == "interactive" || doc.readyState == "complete") {
    return Promise.resolve(doc);
  }

  return new Promise(resolve => {
    doc.addEventListener(
      "DOMContentLoaded",
      function onReady(event) {
        if (event.target === event.currentTarget) {
          doc.removeEventListener("DOMContentLoaded", onReady, true);
          resolve(doc);
        }
      },
      true
    );
  });
}

/**
  * Returns a Promise which resolves when the given window's document's DOM has
  * fully loaded, the <head> stylesheets have fully loaded, and we have hit an
  * idle time.
  *
  * @param {Window} window The window whose document we will await
                           the readiness of.
  * @returns {Promise<IdleDeadline>}
  */
function promiseDocumentIdle(window) {
  return window.document.documentReadyForIdle.then(() => {
    return new Promise(resolve =>
      window.requestIdleCallback(resolve, { timeout: lazy.idleTimeout })
    );
  });
}

/**
 * Returns a Promise which resolves when the given document is fully
 * loaded.
 *
 * @param {Document} doc The document to await the load of.
 * @returns {Promise<Document>}
 */
function promiseDocumentLoaded(doc) {
  if (doc.readyState == "complete") {
    return Promise.resolve(doc);
  }

  return new Promise(resolve => {
    doc.defaultView.addEventListener("load", () => resolve(doc), {
      once: true,
    });
  });
}

/**
 * Returns a Promise which resolves when the given event is dispatched to the
 * given element.
 *
 * @param {Element} element
 *        The element on which to listen.
 * @param {string} eventName
 *        The event to listen for.
 * @param {boolean} [useCapture = true]
 *        If true, listen for the even in the capturing rather than
 *        bubbling phase.
 * @param {function(Event): boolean} [test]
 *        An optional test function which, when called with the
 *        observer's subject and data, should return true if this is the
 *        expected event, false otherwise.
 * @returns {Promise<Event>}
 */
function promiseEvent(
  element,
  eventName,
  useCapture = true,
  test = () => true
) {
  return new Promise(resolve => {
    function listener(event) {
      if (test(event)) {
        element.removeEventListener(eventName, listener, useCapture);
        resolve(event);
      }
    }
    element.addEventListener(eventName, listener, useCapture);
  });
}

/**
 * Returns a Promise which resolves the given observer topic has been
 * observed.
 *
 * @param {string} topic
 *        The topic to observe.
 * @param {function(any, string): boolean} [test]
 *        An optional test function which, when called with the
 *        observer's subject and data, should return true if this is the
 *        expected notification, false otherwise.
 * @returns {Promise<object>}
 */
function promiseObserved(topic, test = () => true) {
  return new Promise(resolve => {
    let observer = (subject, topic, data) => {
      if (test(subject, data)) {
        Services.obs.removeObserver(observer, topic);
        resolve({ subject, data });
      }
    };
    Services.obs.addObserver(observer, topic);
  });
}

function getMessageManager(target) {
  if (target.frameLoader) {
    return target.frameLoader.messageManager;
  }
  return target;
}

function flushJarCache(jarPath) {
  Services.obs.notifyObservers(null, "flush-cache-entry", jarPath);
}
function parseMatchPatterns(patterns, options) {
  try {
    return new MatchPatternSet(patterns, options);
  } catch (e) {
    let pattern;
    for (pattern of patterns) {
      try {
        new MatchPattern(pattern, options);
      } catch (e) {
        throw new ExtensionError(`Invalid url pattern: ${pattern}`);
      }
    }
    // Unexpectedly MatchPatternSet threw, but MatchPattern did not.
    throw e;
  }
}

/**
 * Fetch icon content and convert it to a data: URI.
 *
 * @param {string} iconUrl Icon url to fetch.
 * @returns {Promise<string>}
 */
async function makeDataURI(iconUrl) {
  let response;
  try {
    response = await fetch(iconUrl);
  } catch (e) {
    // Failed to fetch, ignore engine's favicon.
    Cu.reportError(e);
    return;
  }
  let buffer = await response.arrayBuffer();
  let contentType = response.headers.get("content-type");
  let bytes = new Uint8Array(buffer);
  let str = String.fromCharCode.apply(null, bytes);
  return `data:${contentType};base64,${btoa(str)}`;
}

export var ExtensionUtils = {
  flushJarCache,
  getInnerWindowID,
  getMessageManager,
  getUniqueId,
  filterStack,
  makeDataURI,
  parseMatchPatterns,
  promiseDocumentIdle,
  promiseDocumentLoaded,
  promiseDocumentReady,
  promiseEvent,
  promiseObserved,
  promiseTimeout,
  DefaultMap,
  DefaultWeakMap,
  ExtensionError,
  LimitedSet,
};
PK
!<�iG�bebe$modules/ExtensionWorkerChild.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file handles extension background service worker logic that runs in the
 * child process.
 */

import {
  ChildAPIManager,
  ChildLocalAPIImplementation,
  ExtensionActivityLogChild,
  MessageEvent,
  Messenger,
  Port,
  ProxyAPIImplementation,
  SimpleEventAPI,
} from "resource://gre/modules/ExtensionChild.sys.mjs";

import { ExtensionCommon } from "resource://gre/modules/ExtensionCommon.sys.mjs";
import {
  ExtensionPageChild,
  getContextChildManagerGetter,
} from "resource://gre/modules/ExtensionPageChild.sys.mjs";
import {
  ExtensionUtils,
  WorkerExtensionError,
} from "resource://gre/modules/ExtensionUtils.sys.mjs";

const { BaseContext, redefineGetter } = ExtensionCommon;

const { DefaultMap, getUniqueId } = ExtensionUtils;

/**
 * SimpleEventAPI subclass specialized for the worker port events
 * used by WorkerMessenger.
 */
class WorkerRuntimePortEvent extends SimpleEventAPI {
  api() {
    return {
      ...super.api(),
      createListenerForAPIRequest: (...args) =>
        this.createListenerForAPIRequest(...args),
    };
  }

  createListenerForAPIRequest(request) {
    const { eventListener } = request;
    return function (port, ...args) {
      return eventListener.callListener(args, {
        apiObjectType: Ci.mozIExtensionListenerCallOptions.RUNTIME_PORT,
        apiObjectDescriptor: { portId: port.portId, name: port.name },
      });
    };
  }
}

/**
 * SimpleEventAPI subclass specialized for the worker runtime messaging events
 * used by WorkerMessenger.
 */
class WorkerMessageEvent extends MessageEvent {
  api() {
    return {
      ...super.api(),
      createListenerForAPIRequest: (...args) =>
        this.createListenerForAPIRequest(...args),
    };
  }

  createListenerForAPIRequest(request) {
    const { eventListener } = request;
    return function (message, sender) {
      return eventListener.callListener([message, sender], {
        eventListenerType:
          Ci.mozIExtensionListenerCallOptions.CALLBACK_SEND_RESPONSE,
      });
    };
  }
}

/**
 * MessageEvent subclass specialized for the worker's port API events
 * used by WorkerPort.
 */
class WorkerPortEvent extends SimpleEventAPI {
  api() {
    return {
      ...super.api(),
      createListenerForAPIRequest: (...args) =>
        this.createListenerForAPIRequest(...args),
    };
  }

  createListenerForAPIRequest(request) {
    const { eventListener } = request;
    switch (this.name) {
      case "Port.onDisconnect":
        return function (port) {
          eventListener.callListener([], {
            apiObjectType: Ci.mozIExtensionListenerCallOptions.RUNTIME_PORT,
            apiObjectDescriptor: {
              portId: port.portId,
              name: port.name,
            },
          });
        };
      case "Port.onMessage":
        return function (message, port) {
          eventListener.callListener([message], {
            apiObjectType: Ci.mozIExtensionListenerCallOptions.RUNTIME_PORT,
            apiObjectDescriptor: {
              portId: port.portId,
              name: port.name,
            },
          });
        };
    }
    return undefined;
  }
}

/**
 * Port subclass specialized for the workers and used by WorkerMessager.
 */
class WorkerPort extends Port {
  constructor(context, portId, name, native, sender) {
    const { viewType, contextId } = context;
    if (viewType !== "background_worker") {
      throw new Error(
        `Unexpected viewType "${viewType}" on context ${contextId}`
      );
    }

    super(context, portId, name, native, sender);
    this.portId = portId;
  }

  initEventManagers() {
    const { context } = this;
    this.onMessage = new WorkerPortEvent(context, "Port.onMessage");
    this.onDisconnect = new WorkerPortEvent(context, "Port.onDisconnect");
  }

  getAPI() {
    const api = super.getAPI();
    // Add the portId to the API object, needed by the WorkerMessenger
    // to retrieve the port given the apiObjectId part of the
    // mozIExtensionAPIRequest sent from the ExtensionPort webidl.
    api.portId = this.portId;
    return api;
  }

  get api() {
    // No need to clone this for the worker, it's on a separate JSRuntime.
    return redefineGetter(this, "api", this.getAPI());
  }
}

/**
 * A Messenger subclass specialized for the background service worker.
 */
class WorkerMessenger extends Messenger {
  constructor(context) {
    const { viewType, contextId } = context;
    if (viewType !== "background_worker") {
      throw new Error(
        `Unexpected viewType "${viewType}" on context ${contextId}`
      );
    }

    super(context);

    // Used by WebIDL API requests to get a port instance given the apiObjectId
    // received in the API request coming from the ExtensionPort instance
    // returned in the thread where the request was originating from.
    this.portsById = new Map();
    this.context.callOnClose(this);
  }

  initEventManagers() {
    const { context } = this;
    this.onConnect = new WorkerRuntimePortEvent(context, "runtime.onConnect");
    this.onConnectEx = new WorkerRuntimePortEvent(
      context,
      "runtime.onConnectExternal"
    );
    this.onMessage = new WorkerMessageEvent(this.context, "runtime.onMessage");
    this.onMessageEx = new WorkerMessageEvent(
      context,
      "runtime.onMessageExternal"
    );
  }

  close() {
    this.portsById.clear();
  }

  getPortById(portId) {
    return this.portsById.get(portId);
  }

  /**
   * @typedef {object} ExtensionPortDescriptor
   * https://phabricator.services.mozilla.com/D196385?id=801874#inline-1093734
   *
   * @returns {ExtensionPortDescriptor}
   */
  connect({ name, native, ...args }) {
    let portId = getUniqueId();
    let port = new WorkerPort(this.context, portId, name, !!native);
    this.conduit
      .queryPortConnect({ portId, name, native, ...args })
      .catch(error => port.recvPortDisconnect({ error }));
    this.portsById.set(`${portId}`, port);
    // Extension worker calls this method through the WebIDL bindings,
    // and the Port instance returned by the runtime.connect/connectNative
    // methods will be an instance of ExtensionPort webidl interface based
    // on the ExtensionPortDescriptor dictionary returned by this method.
    return { portId, name };
  }

  recvPortConnect({ extensionId, portId, name, sender }) {
    let event = sender.id === extensionId ? this.onConnect : this.onConnectEx;
    if (this.context.active && event.fires.size) {
      let port = new WorkerPort(this.context, portId, name, false, sender);
      this.portsById.set(`${port.portId}`, port);
      return event.emit(port).length;
    }
  }
}

/**
 * APIImplementation subclass specialized for handling mozIExtensionAPIRequests
 * originated from webidl bindings.
 *
 * Provides a createListenerForAPIRequest method which is used by
 * WebIDLChildAPIManager to retrieve an API event specific wrapper
 * for the mozIExtensionEventListener for the API events that needs
 * special handling (e.g. runtime.onConnect).
 *
 * createListenerForAPIRequest delegates to the API event the creation
 * of the special event listener wrappers, the EventManager api objects
 * for the events that needs special wrapper are expected to provide
 * a method with the same name.
 */
class ChildLocalWebIDLAPIImplementation extends ChildLocalAPIImplementation {
  constructor(pathObj, namespace, name, childApiManager) {
    super(pathObj, namespace, name, childApiManager);
    this.childApiManager = childApiManager;
  }

  createListenerForAPIRequest(request) {
    return this.pathObj[this.name].createListenerForAPIRequest?.(request);
  }

  setProperty() {
    // mozIExtensionAPIRequest doesn't support this requestType at the moment,
    // setting a pref would just replace the previous value on the wrapper
    // object living in the owner thread.
    // To be implemented if we have an actual use case where that is needed.
    throw new Error("Unexpected call to setProperty");
  }

  hasListener() {
    // hasListener is implemented in C++ by ExtensionEventManager, and so
    // a call to this method is unexpected.
    throw new Error("Unexpected call to hasListener");
  }
}

/**
 * APIImplementation subclass specialized for handling API requests related
 * to an API Object type.
 *
 * Retrieving the apiObject instance is delegated internally to the
 * ExtensionAPI subclass that implements the request apiNamespace,
 * through an optional getAPIObjectForRequest method expected to be
 * available on the ExtensionAPI class.
 */
class ChildWebIDLObjectTypeImplementation extends ChildLocalWebIDLAPIImplementation {
  constructor(request, childApiManager) {
    const { apiNamespace, apiName, apiObjectType, apiObjectId } = request;
    const api = childApiManager.getExtensionAPIInstance(apiNamespace);
    const pathObj = api.getAPIObjectForRequest?.(
      childApiManager.context,
      request
    );
    if (!pathObj) {
      throw new Error(`apiObject instance not found for ${request}`);
    }
    super(pathObj, apiNamespace, apiName, childApiManager);
    this.fullname = `${apiNamespace}.${apiObjectType}(${apiObjectId}).${apiName}`;
  }
}

/**
 * A ChildAPIManager subclass specialized for handling mozIExtensionAPIRequest
 * originated from the WebIDL bindings.
 *
 * Currently used only for the extension contexts related to the background
 * service worker.
 */
class WebIDLChildAPIManager extends ChildAPIManager {
  constructor(...args) {
    super(...args);
    // Map<apiPathToEventString, WeakMap<nsIExtensionEventListener, Function>>
    //
    // apiPathToEventString is a string that represents the full API path
    // related to the event name (e.g. "runtime.onConnect", or "runtime.Port.onMessage")
    this.eventListenerWrappers = new DefaultMap(() => new WeakMap());
  }

  getImplementation(namespace, name) {
    this.apiCan.findAPIPath(`${namespace}.${name}`);
    let obj = this.apiCan.findAPIPath(namespace);

    if (obj && name in obj) {
      return new ChildLocalWebIDLAPIImplementation(obj, namespace, name, this);
    }

    return this.getFallbackImplementation(namespace, name);
  }

  getImplementationForRequest(request) {
    const { apiNamespace, apiName, apiObjectType } = request;
    if (apiObjectType) {
      return new ChildWebIDLObjectTypeImplementation(request, this);
    }
    return this.getImplementation(apiNamespace, apiName);
  }

  /**
   * Handles an ExtensionAPIRequest originated by the Extension APIs WebIDL bindings.
   *
   * @param {mozIExtensionAPIRequest} request
   *        The object that represents the API request received
   *        (including arguments, an event listener wrapper etc)
   *
   * @returns {Partial<mozIExtensionAPIRequestResult>}
   *          Result for the API request, either a value to be returned
   *          (which has to be a value that can be structure cloned
   *          if the request was originated from the worker thread) or
   *          an error to raise to the extension code.
   */
  handleWebIDLAPIRequest(request) {
    try {
      const impl = this.getImplementationForRequest(request);
      let result;
      this.context.withAPIRequest(request, () => {
        if (impl instanceof ProxyAPIImplementation) {
          result = this.handleForProxyAPIImplementation(request, impl);
        } else {
          result = this.callAPIImplementation(request, impl);
        }
      });

      return {
        type: Ci.mozIExtensionAPIRequestResult.RETURN_VALUE,
        value: result,
      };
    } catch (error) {
      return this.handleExtensionError(error);
    }
  }

  /**
   * Convert an error raised while handling an API request,
   * into the expected mozIExtensionAPIRequestResult.
   *
   * @param {Error | WorkerExtensionError} error
   * @returns {Partial<mozIExtensionAPIRequestResult>}
   */
  handleExtensionError(error) {
    // Propagate an extension error to the caller on the worker thread.
    if (error instanceof this.context.Error) {
      return {
        type: Ci.mozIExtensionAPIRequestResult.EXTENSION_ERROR,
        value: error,
      };
    }

    // Otherwise just log it and throw a generic error.
    Cu.reportError(error);
    return {
      type: Ci.mozIExtensionAPIRequestResult.EXTENSION_ERROR,
      value: new this.context.Error("An unexpected error occurred"),
    };
  }

  /**
   * Handle the given mozIExtensionAPIRequest using the given
   * APIImplementation instance.
   *
   * @param {mozIExtensionAPIRequest} request
   * @param {ChildLocalWebIDLAPIImplementation | ProxyAPIImplementation} impl
   * @returns {any}
   * @throws {Error | WorkerExtensionError}
   */
  callAPIImplementation(request, impl) {
    const { requestType, normalizedArgs } = request;

    switch (requestType) {
      // TODO (Bug 1728328): follow up to take callAsyncFunction requireUserInput
      // parameter into account (until then callAsyncFunction, callFunction
      // and callFunctionNoReturn calls do not differ yet).
      case "callAsyncFunction":
      case "callFunction":
      case "callFunctionNoReturn":
      case "getProperty":
        return impl[requestType](normalizedArgs);
      case "addListener": {
        const listener = this.getOrCreateListenerWrapper(request, impl);
        impl.addListener(listener, normalizedArgs);

        return undefined;
      }
      case "removeListener": {
        const listener = this.getListenerWrapper(request);
        if (listener) {
          // Remove the previously added listener and forget the cleanup
          // observer previously passed to context.callOnClose.
          listener._callOnClose.close();
          this.context.forgetOnClose(listener._callOnClose);
          this.forgetListenerWrapper(request);
        }
        return undefined;
      }
      default:
        throw new Error(
          `Unexpected requestType ${requestType} while handling "${request}"`
        );
    }
  }

  /**
   * Handle the given mozIExtensionAPIRequest using the given
   * ProxyAPIImplementation instance.
   *
   * @param {mozIExtensionAPIRequest} request
   * @param {ProxyAPIImplementation} impl
   * @returns {any}
   * @throws {Error | WorkerExtensionError}
   */
  handleForProxyAPIImplementation(request, impl) {
    const { requestType } = request;
    switch (requestType) {
      case "callAsyncFunction":
      case "callFunctionNoReturn":
      case "addListener":
      case "removeListener":
        return this.callAPIImplementation(request, impl);
      default:
        // Any other request types (e.g. getProperty or callFunction) are
        // unexpected and so we raise a more detailed error to be logged
        // on the browser console (while the extension will receive the
        // generic "An unexpected error occurred" one).
        throw new Error(
          `Unexpected requestType ${requestType} while handling "${request}"`
        );
    }
  }

  getAPIPathForWebIDLRequest(request) {
    const { apiNamespace, apiName, apiObjectType } = request;
    if (apiObjectType) {
      return `${apiNamespace}.${apiObjectType}.${apiName}`;
    }

    return `${apiNamespace}.${apiName}`;
  }

  /**
   * Return an ExtensionAPI class instance given its namespace.
   *
   * @param {string} namespace
   * @returns {import("ExtensionCommon.sys.mjs").ExtensionAPI}
   */
  getExtensionAPIInstance(namespace) {
    return this.apiCan.apis.get(namespace);
  }

  getOrCreateListenerWrapper(request, impl) {
    let listener = this.getListenerWrapper(request);
    if (listener) {
      return listener;
    }

    // Look for special wrappers that are needed for some API events
    // (e.g. runtime.onMessage/onConnect/...).
    if (impl instanceof ChildLocalWebIDLAPIImplementation) {
      listener = impl.createListenerForAPIRequest(request);
    }

    const { eventListener } = request;
    listener =
      listener ??
      function (...args) {
        // Default wrapper just forwards all the arguments to the
        // extension callback (all arguments has to be structure cloneable
        // if the extension callback is on the worker thread).
        eventListener.callListener(args);
      };
    listener._callOnClose = {
      close: () => {
        this.eventListenerWrappers.delete(eventListener);
        // Failing to send the request to remove the listener in the parent
        // process shouldn't prevent the extension or context shutdown,
        // otherwise we would leak a WebExtensionPolicy instance.
        try {
          impl.removeListener(listener);
        } catch (err) {
          // Removing a listener when the extension context is being closed can
          // fail if the API is proxied to the parent process and the conduit
          // has been already closed, and so we ignore the error if we are not
          // processing a call proxied to the parent process.
          if (impl instanceof ChildLocalWebIDLAPIImplementation) {
            Cu.reportError(err);
          }
        }
      },
    };
    this.storeListenerWrapper(request, listener);
    this.context.callOnClose(listener._callOnClose);
    return listener;
  }

  getListenerWrapper(request) {
    const { eventListener } = request;
    if (!(eventListener instanceof Ci.mozIExtensionEventListener)) {
      throw new Error(`Unexpected eventListener type for request: ${request}`);
    }
    const apiPath = this.getAPIPathForWebIDLRequest(request);
    if (!this.eventListenerWrappers.has(apiPath)) {
      return undefined;
    }
    return this.eventListenerWrappers.get(apiPath).get(eventListener);
  }

  storeListenerWrapper(request, listener) {
    const { eventListener } = request;
    if (!(eventListener instanceof Ci.mozIExtensionEventListener)) {
      throw new Error(`Missing eventListener for request: ${request}`);
    }
    const apiPath = this.getAPIPathForWebIDLRequest(request);
    this.eventListenerWrappers.get(apiPath).set(eventListener, listener);
  }

  forgetListenerWrapper(request) {
    const { eventListener } = request;
    if (!(eventListener instanceof Ci.mozIExtensionEventListener)) {
      throw new Error(`Missing eventListener for request: ${request}`);
    }
    const apiPath = this.getAPIPathForWebIDLRequest(request);
    if (this.eventListenerWrappers.has(apiPath)) {
      this.eventListenerWrappers.get(apiPath).delete(eventListener);
    }
  }
}

class WorkerContextChild extends BaseContext {
  /**
   * This WorkerContextChild represents an addon execution environment
   * that is running on the worker thread in an extension child process.
   *
   * @param {ExtensionChild} extension This context's owner.
   * @param {object}                         params
   * @param {mozIExtensionServiceWorkerInfo} params.serviceWorkerInfo
   */
  constructor(extension, { serviceWorkerInfo }) {
    if (
      !serviceWorkerInfo?.scriptURL ||
      !serviceWorkerInfo?.clientInfoId ||
      !serviceWorkerInfo?.principal
    ) {
      throw new Error("Missing or invalid serviceWorkerInfo");
    }

    super("addon_child", extension);
    this.viewType = "background_worker";
    this.uri = Services.io.newURI(serviceWorkerInfo.scriptURL);
    this.workerClientInfoId = serviceWorkerInfo.clientInfoId;
    this.workerDescriptorId = serviceWorkerInfo.descriptorId;
    this.workerPrincipal = serviceWorkerInfo.principal;
    this.incognito = serviceWorkerInfo.principal.privateBrowsingId > 0;

    // A mozIExtensionAPIRequest being processed (set by the withAPIRequest
    // method while executing a given callable, can be optionally used by
    // the API implementation methods to access the mozIExtensionAPIRequest
    // being processed and customize their result if necessary to handle
    // requests originated by the webidl bindings).
    this.webidlAPIRequest = null;

    // This context uses a plain object as a cloneScope (anyway the values
    // moved across thread are going to be automatically serialized/deserialized
    // as structure clone data, we may remove this if we are changing the
    // internals to not use the context.cloneScope).
    this.workerCloneScope = {
      Promise,
      // The instances of this Error constructor will be recognized by the
      // ExtensionAPIRequestHandler as errors that should be propagated to
      // the worker thread and received by extension code that originated
      // the API request.
      Error: WorkerExtensionError,
    };
  }

  getCreateProxyContextData() {
    const { workerDescriptorId } = this;
    return { workerDescriptorId };
  }

  /** @type {ConduitGen} */
  openConduit(subject, address) {
    let proc = ChromeUtils.domProcessChild;
    let conduit = proc.getActor("ProcessConduits").openConduit(subject, {
      id: subject.id || getUniqueId(),
      extensionId: this.extension.id,
      envType: this.envType,
      workerScriptURL: this.uri.spec,
      workerDescriptorId: this.workerDescriptorId,
      ...address,
    });
    this.callOnClose(conduit);
    conduit.setCloseCallback(() => {
      this.forgetOnClose(conduit);
    });
    return conduit;
  }

  notifyWorkerLoaded() {
    this.childManager.conduit.sendContextLoaded({
      childId: this.childManager.id,
      extensionId: this.extension.id,
      workerDescriptorId: this.workerDescriptorId,
    });
  }

  withAPIRequest(request, callable) {
    this.webidlAPIRequest = request;
    try {
      return callable();
    } finally {
      this.webidlAPIRequest = null;
    }
  }

  getAPIRequest() {
    return this.webidlAPIRequest;
  }

  /**
   * Captures the most recent stack frame from the WebIDL API request being
   * processed.
   *
   * @returns {nsIStackFrame}
   */
  getCaller() {
    return this.webidlAPIRequest?.callerSavedFrame;
  }

  logActivity(type, name, data) {
    ExtensionActivityLogChild.log(this, type, name, data);
  }

  get cloneScope() {
    return this.workerCloneScope;
  }

  get principal() {
    return this.workerPrincipal;
  }

  get tabId() {
    return -1;
  }

  get useWebIDLBindings() {
    return true;
  }

  shutdown() {
    this.unload();
  }

  unload() {
    if (this.unloaded) {
      return;
    }

    super.unload();
  }

  get childManager() {
    const childManager = getContextChildManagerGetter(
      { envType: "addon_parent" },
      WebIDLChildAPIManager
    ).call(this);
    return redefineGetter(this, "childManager", childManager);
  }

  get messenger() {
    return redefineGetter(this, "messenger", new WorkerMessenger(this));
  }
}

export var ExtensionWorkerChild = {
  /** @type {Map<number, WorkerContextChild>} */
  extensionWorkerContexts: new Map(),

  apiManager: ExtensionPageChild.apiManager,

  /**
   * Create an extension worker context (on a mozExtensionAPIRequest with
   * requestType "initWorkerContext").
   *
   * @param {ExtensionChild} extension
   *     The extension for which the context should be created.
   * @param {mozIExtensionServiceWorkerInfo} serviceWorkerInfo
   */
  initExtensionWorkerContext(extension, serviceWorkerInfo) {
    if (!WebExtensionPolicy.isExtensionProcess) {
      throw new Error(
        "Cannot create an extension worker context in current process"
      );
    }

    const swId = serviceWorkerInfo.descriptorId;
    let context = this.extensionWorkerContexts.get(swId);
    if (context) {
      if (context.extension !== extension) {
        throw new Error(
          "A different extension context already exists for this service worker"
        );
      }
      throw new Error(
        "An extension context was already initialized for this service worker"
      );
    }

    context = new WorkerContextChild(extension, { serviceWorkerInfo });
    this.extensionWorkerContexts.set(swId, context);
  },

  /**
   * Get an existing extension worker context for the given extension and
   * service worker.
   *
   * @param {ExtensionChild} extension
   *     The extension for which the context should be created.
   * @param {mozIExtensionServiceWorkerInfo} serviceWorkerInfo
   *
   * @returns {WorkerContextChild}
   */
  getExtensionWorkerContext(extension, serviceWorkerInfo) {
    if (!serviceWorkerInfo) {
      return null;
    }

    const context = this.extensionWorkerContexts.get(
      serviceWorkerInfo.descriptorId
    );

    if (context?.extension === extension) {
      return context;
    }

    return null;
  },

  /**
   * Notify the main process when an extension worker script has been loaded.
   *
   * @param {number} descriptorId The service worker descriptor ID of the destroyed context.
   * @param {WebExtensionPolicy} policy
   */
  notifyExtensionWorkerContextLoaded(descriptorId, policy) {
    let context = this.extensionWorkerContexts.get(descriptorId);
    if (context) {
      if (context.extension.id !== policy.id) {
        Cu.reportError(
          new Error(
            `ServiceWorker ${descriptorId} does not belong to the expected extension: ${policy.id}`
          )
        );
        return;
      }
      context.notifyWorkerLoaded();
    }
  },

  /**
   * Close the WorkerContextChild belonging to the given service worker, if any.
   *
   * @param {number} descriptorId The service worker descriptor ID of the destroyed context.
   */
  destroyExtensionWorkerContext(descriptorId) {
    let context = this.extensionWorkerContexts.get(descriptorId);
    if (context) {
      context.unload();
      this.extensionWorkerContexts.delete(descriptorId);
    }
  },

  shutdownExtension(extensionId) {
    for (let [workerClientInfoId, context] of this.extensionWorkerContexts) {
      if (context.extension.id == extensionId) {
        context.shutdown();
        this.extensionWorkerContexts.delete(workerClientInfoId);
      }
    }
  },
};
PK
!<^n�O��modules/FileUtils.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export var FileUtils = {
  MODE_RDONLY: 0x01,
  MODE_WRONLY: 0x02,
  MODE_RDWR: 0x04,
  MODE_CREATE: 0x08,
  MODE_APPEND: 0x10,
  MODE_TRUNCATE: 0x20,

  PERMS_FILE: 0o644,
  PERMS_DIRECTORY: 0o755,

  /**
   * Gets a directory at the specified hierarchy under a nsIDirectoryService
   * key.
   * @param   key
   *          The Directory Service Key to start from
   * @param   pathArray
   *          An array of path components to locate beneath the directory
   *          specified by |key|
   * @return  nsIFile object for the location specified.
   */
  getDir: function FileUtils_getDir(key, pathArray) {
    var dir = Services.dirsvc.get(key, Ci.nsIFile);
    for (var i = 0; i < pathArray.length; ++i) {
      dir.append(pathArray[i]);
    }
    return dir;
  },

  /**
   * Opens a file output stream for writing.
   * @param   file
   *          The file to write to.
   * @param   modeFlags
   *          (optional) File open flags. Can be undefined.
   * @returns nsIFileOutputStream to write to.
   * @note The stream is initialized with the DEFER_OPEN behavior flag.
   *       See nsIFileOutputStream.
   */
  openFileOutputStream: function FileUtils_openFileOutputStream(
    file,
    modeFlags
  ) {
    var fos = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
      Ci.nsIFileOutputStream
    );
    return this._initFileOutputStream(fos, file, modeFlags);
  },

  /**
   * Opens an atomic file output stream for writing.
   * @param   file
   *          The file to write to.
   * @param   modeFlags
   *          (optional) File open flags. Can be undefined.
   * @returns nsIFileOutputStream to write to.
   * @note The stream is initialized with the DEFER_OPEN behavior flag.
   *       See nsIFileOutputStream.
   *       OpeanAtomicFileOutputStream is generally better than openSafeFileOutputStream
   *       baecause flushing is not needed in most of the issues.
   */
  openAtomicFileOutputStream: function FileUtils_openAtomicFileOutputStream(
    file,
    modeFlags
  ) {
    var fos = Cc[
      "@mozilla.org/network/atomic-file-output-stream;1"
    ].createInstance(Ci.nsIFileOutputStream);
    return this._initFileOutputStream(fos, file, modeFlags);
  },

  /**
   * Opens a safe file output stream for writing.
   * @param   file
   *          The file to write to.
   * @param   modeFlags
   *          (optional) File open flags. Can be undefined.
   * @returns nsIFileOutputStream to write to.
   * @note The stream is initialized with the DEFER_OPEN behavior flag.
   *       See nsIFileOutputStream.
   */
  openSafeFileOutputStream: function FileUtils_openSafeFileOutputStream(
    file,
    modeFlags
  ) {
    var fos = Cc[
      "@mozilla.org/network/safe-file-output-stream;1"
    ].createInstance(Ci.nsIFileOutputStream);
    return this._initFileOutputStream(fos, file, modeFlags);
  },

  _initFileOutputStream: function FileUtils__initFileOutputStream(
    fos,
    file,
    modeFlags
  ) {
    if (modeFlags === undefined) {
      modeFlags = this.MODE_WRONLY | this.MODE_CREATE | this.MODE_TRUNCATE;
    }
    fos.init(file, modeFlags, this.PERMS_FILE, fos.DEFER_OPEN);
    return fos;
  },

  /**
   * Closes an atomic file output stream.
   * @param   stream
   *          The stream to close.
   */
  closeAtomicFileOutputStream: function FileUtils_closeAtomicFileOutputStream(
    stream
  ) {
    if (stream instanceof Ci.nsISafeOutputStream) {
      try {
        stream.finish();
        return;
      } catch (e) {}
    }
    stream.close();
  },

  /**
   * Closes a safe file output stream.
   * @param   stream
   *          The stream to close.
   */
  closeSafeFileOutputStream: function FileUtils_closeSafeFileOutputStream(
    stream
  ) {
    if (stream instanceof Ci.nsISafeOutputStream) {
      try {
        stream.finish();
        return;
      } catch (e) {}
    }
    stream.close();
  },

  File: Components.Constructor(
    "@mozilla.org/file/local;1",
    Ci.nsIFile,
    "initWithPath"
  ),
};
PK
!<��	��modules/FillHelpers.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// This item shows image, label & secondary.
// Once selected it will send fillMessageName with fillMessageData
// to the parent actor and response will be used to fill into the field.
export class GenericAutocompleteItem {
  comment = "";
  style = "generic";
  value = "";

  constructor(image, label, secondary, fillMessageName, fillMessageData) {
    this.image = image;
    this.label = label;
    this.comment = JSON.stringify({
      secondary,
      fillMessageName,
      fillMessageData,
    });
  }
}

/**
 * Show confirmation tooltip
 *
 * @param {object} browser - An object representing the browser.
 * @param {string} messageId - Message ID from browser/confirmationHints.ftl
 * @param {string} [anchorId="identity-icon-box"] - ID of the element to anchor the hint to.
                   The "password-notification-icon" and "notification-popup-box" are hidden
                   at the point of showing the hint (for *most* cases), so approximate the
                   location with the next closest, visible icon as the anchor.
 */
export function showConfirmation(
  browser,
  messageId,
  anchorId = "identity-icon-box"
) {
  const anchor = browser.ownerDocument.getElementById(anchorId);
  anchor.ownerGlobal.ConfirmationHint.show(anchor, messageId, {});
}
PK
!<��Y�modules/FindBarContent.sys.mjs// vim: set ts=2 sw=2 sts=2 tw=80:
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

/* Please keep in sync with toolkit/content/widgets/findbar.js */
const FIND_NORMAL = 0;
const FIND_TYPEAHEAD = 1;
const FIND_LINKS = 2;

export class FindBarContent {
  constructor(actor) {
    this.actor = actor;

    this.findMode = 0;
    this.inQuickFind = false;

    this.addedEventListener = false;
  }

  start() {
    this.inPassThrough = true;
  }

  startQuickFind(event, autostart = false) {
    if (!this.addedEventListener) {
      this.addedEventListener = true;
      this.actor.document.defaultView.addEventListener("mouseup", this, {
        mozSystemGroup: true,
      });
    }

    let mode = FIND_TYPEAHEAD;
    if (
      event.charCode == "'".charAt(0) ||
      (autostart && FindBarContent.typeAheadLinksOnly)
    ) {
      mode = FIND_LINKS;
    }

    // Set findMode immediately (without waiting for child->parent->child roundtrip)
    // to ensure we pass any further keypresses, too.
    this.findMode = mode;
    this.passKeyToParent(event);
  }

  updateState(data) {
    this.findMode = data.findMode;
    this.inQuickFind = data.hasQuickFindTimeout;
    if (data.isOpenAndFocused) {
      this.inPassThrough = false;
    }
  }

  handleEvent(event) {
    switch (event.type) {
      case "keypress":
        this.onKeypress(event);
        break;
      case "mouseup":
        this.onMouseup(event);
        break;
    }
  }

  onKeypress(event) {
    if (this.inPassThrough) {
      this.passKeyToParent(event);
    } else if (
      this.findMode != FIND_NORMAL &&
      this.inQuickFind &&
      event.charCode
    ) {
      this.passKeyToParent(event);
    }
  }

  passKeyToParent(event) {
    event.preventDefault();
    // These are the properties required to dispatch another 'real' event
    // to the findbar in the parent in _dispatchKeypressEvent in findbar.xml .
    // If you make changes here, verify that that method can still do its job.
    const kRequiredProps = [
      "type",
      "bubbles",
      "cancelable",
      "ctrlKey",
      "altKey",
      "shiftKey",
      "metaKey",
      "keyCode",
      "charCode",
    ];
    let fakeEvent = {};
    for (let prop of kRequiredProps) {
      fakeEvent[prop] = event[prop];
    }
    this.actor.sendAsyncMessage("Findbar:Keypress", fakeEvent);
  }

  onMouseup() {
    if (this.findMode != FIND_NORMAL) {
      this.actor.sendAsyncMessage("Findbar:Mouseup", {});
    }
  }
}

XPCOMUtils.defineLazyPreferenceGetter(
  FindBarContent,
  "typeAheadLinksOnly",
  "accessibility.typeaheadfind.linksonly"
);
PK
!<M��m��modules/FindContent.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Finder: "resource://gre/modules/Finder.sys.mjs",
  FinderHighlighter: "resource://gre/modules/FinderHighlighter.sys.mjs",
  FinderIterator: "resource://gre/modules/FinderIterator.sys.mjs",
});

export class FindContent {
  constructor(docShell) {
    this.finder = new lazy.Finder(docShell);
  }

  get iterator() {
    if (!this._iterator) {
      this._iterator = new lazy.FinderIterator();
    }
    return this._iterator;
  }

  get highlighter() {
    if (!this._highlighter) {
      this._highlighter = new lazy.FinderHighlighter(this.finder, true);
    }
    return this._highlighter;
  }

  /**
   * findRanges
   *
   * Performs a search which will cache found ranges in `iterator._previousRanges`.  Cached
   * data can then be used by `highlightResults`, `_collectRectData` and `_serializeRangeData`.
   *
   * @param {object} params - the params.
   * @param {string} params.queryphrase - the text to search for.
   * @param {boolean} params.caseSensitive - whether to use case sensitive matches.
   * @param {boolean} params.includeRangeData - whether to collect and return range data.
   * @param {boolean} params.matchDiacritics - whether diacritics must match.
   * @param {boolean} params.searchString - whether to collect and return rect data.
   * @param {boolean} params.entireWord - whether to match entire words.
   * @param {boolean} params.includeRectData - collect and return rect data.
   *
   * @returns {object} that includes:
   *   {number} count - number of results found.
   *   {array} rangeData (if opted) - serialized representation of ranges found.
   *   {array} rectData (if opted) - rect data of ranges found.
   */
  findRanges(params) {
    return new Promise(resolve => {
      let {
        queryphrase,
        caseSensitive,
        entireWord,
        includeRangeData,
        includeRectData,
        matchDiacritics,
      } = params;

      this.iterator.reset();

      // Cast `caseSensitive` and `entireWord` to boolean, otherwise _iterator.start will throw.
      let iteratorPromise = this.iterator.start({
        word: queryphrase,
        caseSensitive: !!caseSensitive,
        entireWord: !!entireWord,
        finder: this.finder,
        listener: this.finder,
        matchDiacritics: !!matchDiacritics,
        useSubFrames: false,
      });

      iteratorPromise.then(() => {
        let rangeData;
        let rectData;
        if (includeRangeData) {
          rangeData = this._serializeRangeData();
        }
        if (includeRectData) {
          rectData = this._collectRectData();
        }

        resolve({
          count: this.iterator._previousRanges.length,
          rangeData,
          rectData,
        });
      });
    });
  }

  /**
   * _serializeRangeData
   *
   * Optionally returned by `findRanges`.
   * Collects DOM data from ranges found on the most recent search made by `findRanges`
   * and encodes it into a serializable form.  Useful to extensions for custom UI presentation
   * of search results, eg, getting surrounding context of results.
   *
   * @returns {Array} - serializable range data.
   */
  _serializeRangeData() {
    let ranges = this.iterator._previousRanges;

    let rangeData = [];
    let nodeCountWin = 0;
    let lastDoc;
    let walker;
    let node;

    for (let range of ranges) {
      let startContainer = range.startContainer;
      let doc = startContainer.ownerDocument;

      if (lastDoc !== doc) {
        walker = doc.createTreeWalker(
          doc,
          doc.defaultView.NodeFilter.SHOW_TEXT,
          null,
          false
        );
        // Get first node.
        node = walker.nextNode();
        // Reset node count.
        nodeCountWin = 0;
      }
      lastDoc = doc;

      // The framePos will be set by the parent process later.
      let data = { framePos: 0, text: range.toString() };
      rangeData.push(data);

      if (node != range.startContainer) {
        node = walker.nextNode();
        while (node) {
          nodeCountWin++;
          if (node == range.startContainer) {
            break;
          }
          node = walker.nextNode();
        }
      }
      data.startTextNodePos = nodeCountWin;
      data.startOffset = range.startOffset;

      if (range.startContainer != range.endContainer) {
        node = walker.nextNode();
        while (node) {
          nodeCountWin++;
          if (node == range.endContainer) {
            break;
          }
          node = walker.nextNode();
        }
      }
      data.endTextNodePos = nodeCountWin;
      data.endOffset = range.endOffset;
    }

    return rangeData;
  }

  /**
   * _collectRectData
   *
   * Optionally returned by `findRanges`.
   * Collects rect data of ranges found by most recent search made by `findRanges`.
   * Useful to extensions for custom highlighting of search results.
   *
   * @returns {Array} rectData - serializable rect data.
   */
  _collectRectData() {
    let rectData = [];

    let ranges = this.iterator._previousRanges;
    for (let range of ranges) {
      let rectsAndTexts = this.highlighter._getRangeRectsAndTexts(range);
      rectData.push({ text: range.toString(), rectsAndTexts });
    }

    return rectData;
  }

  /**
   * highlightResults
   *
   * Highlights range(s) found in previous browser.find.find.
   *
   * @param {object} params - may contain any of the following properties:
   *   all of which are optional:
   *   {number} rangeIndex -
   *            Found range to be highlighted held in API's ranges array for the tabId.
   *            Default highlights all ranges.
   *   {number} tabId - Tab to highlight.  Defaults to the active tab.
   *   {boolean} noScroll - Don't scroll to highlighted item.
   *
   * @returns {string} - a string describing the resulting status of the highlighting,
   *   which will be used as criteria for resolving or rejecting the promise.
   *   This can be:
   *   "Success" - Highlighting succeeded.
   *   "OutOfRange" - The index supplied was out of range.
   *   "NoResults" - There were no search results to highlight.
   */
  highlightResults(params) {
    let { rangeIndex, noScroll } = params;

    this.highlighter.highlight(false);
    let ranges = this.iterator._previousRanges;

    let status = "Success";

    if (ranges.length) {
      if (typeof rangeIndex == "number") {
        if (rangeIndex < ranges.length) {
          let foundRange = ranges[rangeIndex];
          this.highlighter.highlightRange(foundRange);

          if (!noScroll) {
            let node = foundRange.startContainer;
            let editableNode = this.highlighter._getEditableNode(node);
            let controller = editableNode
              ? editableNode.editor.selectionController
              : this.finder._getSelectionController(node.ownerGlobal);

            controller.scrollSelectionIntoView(
              controller.SELECTION_FIND,
              controller.SELECTION_ON,
              controller.SCROLL_VERTICAL_CENTER
            );
          }
        } else {
          status = "OutOfRange";
        }
      } else {
        for (let range of ranges) {
          this.highlighter.highlightRange(range);
        }
      }
    } else {
      status = "NoResults";
    }

    return status;
  }
}
PK
!<��7y^y^modules/Finder.sys.mjs// vim: set ts=2 sw=2 sts=2 tw=80:
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { Rect } from "resource://gre/modules/Geometry.sys.mjs";

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  FinderIterator: "resource://gre/modules/FinderIterator.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "ClipboardHelper",
  "@mozilla.org/widget/clipboardhelper;1",
  "nsIClipboardHelper"
);

const kSelectionMaxLen = 150;
const kMatchesCountLimitPref = "accessibility.typeaheadfind.matchesCountLimit";

const activeFinderRoots = new WeakSet();

export function Finder(docShell) {
  this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance(
    Ci.nsITypeAheadFind
  );
  this._fastFind.init(docShell);

  this._currentFoundRange = null;
  this._docShell = docShell;
  this._listeners = [];
  this._previousLink = null;
  this._searchString = null;
  this._highlighter = null;

  docShell
    .QueryInterface(Ci.nsIInterfaceRequestor)
    .getInterface(Ci.nsIWebProgress)
    .addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
  docShell.domWindow.addEventListener(
    "unload",
    this.onLocationChange.bind(this, { isTopLevel: true })
  );
}

Finder.isFindbarVisible = function (docShell) {
  return activeFinderRoots.has(docShell.browsingContext.top);
};

Finder.prototype = {
  get iterator() {
    if (!this._iterator) {
      this._iterator = new lazy.FinderIterator();
    }
    return this._iterator;
  },

  destroy() {
    if (this._iterator) {
      this._iterator.reset();
    }
    let window = this._getWindow();
    if (this._highlighter && window) {
      // if we clear all the references before we hide the highlights (in both
      // highlighting modes), we simply can't use them to find the ranges we
      // need to clear from the selection.
      this._highlighter.hide(window);
      this._highlighter.clear(window);
      this.highlighter.removeScrollMarks();
    }
    this.listeners = [];
    this._docShell
      .QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIWebProgress)
      .removeProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
    this._listeners = [];
    this._currentFoundRange =
      this._fastFind =
      this._docShell =
      this._previousLink =
      this._highlighter =
        null;
  },

  addResultListener(aListener) {
    if (!this._listeners.includes(aListener)) {
      this._listeners.push(aListener);
    }
  },

  removeResultListener(aListener) {
    this._listeners = this._listeners.filter(l => l != aListener);
  },

  _setResults(options) {
    if (typeof options.storeResult != "boolean") {
      options.storeResult = true;
    }

    if (options.storeResult) {
      this._searchString = options.searchString;
      this.clipboardSearchString = options.searchString;
    }

    let foundLink = this._fastFind.foundLink;
    let linkURL = null;
    if (foundLink) {
      linkURL = Services.textToSubURI.unEscapeURIForUI(foundLink.href);
    }

    options.linkURL = linkURL;
    options.rect = this._getResultRect();
    options.searchString = this._searchString;

    this._outlineLink(options.drawOutline);

    for (let l of this._listeners) {
      try {
        l.onFindResult(options);
      } catch (ex) {}
    }
  },

  get searchString() {
    if (!this._searchString && this._fastFind.searchString) {
      this._searchString = this._fastFind.searchString;
    }
    return this._searchString;
  },

  get clipboardSearchString() {
    return GetClipboardSearchString(
      this._getWindow().docShell.QueryInterface(Ci.nsILoadContext)
    );
  },

  set clipboardSearchString(aSearchString) {
    if (!lazy.PrivateBrowsingUtils.isContentWindowPrivate(this._getWindow())) {
      SetClipboardSearchString(aSearchString);
    }
  },

  set caseSensitive(aSensitive) {
    if (this._fastFind.caseSensitive === aSensitive) {
      return;
    }
    this._fastFind.caseSensitive = aSensitive;
    this.iterator.reset();
  },

  set matchDiacritics(aMatchDiacritics) {
    if (this._fastFind.matchDiacritics === aMatchDiacritics) {
      return;
    }
    this._fastFind.matchDiacritics = aMatchDiacritics;
    this.iterator.reset();
  },

  set entireWord(aEntireWord) {
    if (this._fastFind.entireWord === aEntireWord) {
      return;
    }
    this._fastFind.entireWord = aEntireWord;
    this.iterator.reset();
  },

  get highlighter() {
    if (this._highlighter) {
      return this._highlighter;
    }

    const { FinderHighlighter } = ChromeUtils.importESModule(
      "resource://gre/modules/FinderHighlighter.sys.mjs"
    );
    return (this._highlighter = new FinderHighlighter(this));
  },

  get matchesCountLimit() {
    if (typeof this._matchesCountLimit == "number") {
      return this._matchesCountLimit;
    }

    this._matchesCountLimit =
      Services.prefs.getIntPref(kMatchesCountLimitPref) || 0;
    return this._matchesCountLimit;
  },

  _lastFindResult: null,

  /**
   * Used for normal search operations, highlights the first match.
   * This method is used only for compatibility with non-remote browsers.
   *
   * @param aSearchString String to search for.
   * @param aLinksOnly Only consider nodes that are links for the search.
   * @param aDrawOutline Puts an outline around matched links.
   */
  fastFind(aSearchString, aLinksOnly, aDrawOutline) {
    this._lastFindResult = this._fastFind.find(
      aSearchString,
      aLinksOnly,
      Ci.nsITypeAheadFind.FIND_INITIAL,
      false
    );
    let searchString = this._fastFind.searchString;

    let results = {
      searchString,
      result: this._lastFindResult,
      findBackwards: false,
      findAgain: false,
      drawOutline: aDrawOutline,
      linksOnly: aLinksOnly,
      useSubFrames: true,
    };

    this._setResults(results);
    this.updateHighlightAndMatchCount(results);

    return this._lastFindResult;
  },

  /**
   * Repeat the previous search. Should only be called after a previous
   * call to Finder.fastFind.
   * This method is used only for compatibility with non-remote browsers.
   *
   * @param aSearchString String to search for.
   * @param aFindBackwards Controls the search direction:
   *    true: before current match, false: after current match.
   * @param aLinksOnly Only consider nodes that are links for the search.
   * @param aDrawOutline Puts an outline around matched links.
   */
  findAgain(aSearchString, aFindBackwards, aLinksOnly, aDrawOutline) {
    let mode = aFindBackwards
      ? Ci.nsITypeAheadFind.FIND_PREVIOUS
      : Ci.nsITypeAheadFind.FIND_NEXT;
    this._lastFindResult = this._fastFind.find(
      aFindBackwards,
      aLinksOnly,
      mode,
      false
    );
    let searchString = this._fastFind.searchString;

    let results = {
      searchString,
      result: this._lastFindResult,
      findBackwards: aFindBackwards,
      findAgain: true,
      drawOutline: aDrawOutline,
      linksOnly: aLinksOnly,
      useSubFrames: true,
    };
    this._setResults(results);
    this.updateHighlightAndMatchCount(results);

    return this._lastFindResult;
  },

  /**
   * Used for normal search operations, highlights the first or
   * subsequent match depending on the mode.
   *
   * Options are:
   *  searchString String to search for.
   *  findAgain True if this a find again operation.
   *  mode Search mode from nsITypeAheadFind.
   *  linksOnly Only consider nodes that are links for the search.
   *  drawOutline Puts an outline around matched links.
   *  useSubFrames True to iterate over subframes.
   *  caseSensitive True for case sensitive searching.
   *  entireWord True to match entire words.
   *  matchDiacritics True to match diacritics.
   */
  find(options) {
    this.caseSensitive = options.caseSensitive;
    this.entireWord = options.entireWord;
    this.matchDiacritics = options.matchDiacritics;

    this._lastFindResult = this._fastFind.find(
      options.searchString,
      options.linksOnly,
      options.mode,
      !options.useSubFrames
    );
    let searchString = this._fastFind.searchString;
    let results = {
      searchString,
      result: this._lastFindResult,
      findBackwards:
        options.mode == Ci.nsITypeAheadFind.FIND_PREVIOUS ||
        options.mode == Ci.nsITypeAheadFind.FIND_LAST,
      findAgain: options.findAgain,
      drawOutline: options.drawOutline,
      linksOnly: options.linksOnly,
      entireWord: this._fastFind.entireWord,
      useSubFrames: options.useSubFrames,
    };
    this._setResults(results, options.mode);
    return new Promise(resolve => resolve(results));
  },

  /**
   * Forcibly set the search string of the find clipboard to the currently
   * selected text in the window, on supported platforms (i.e. OSX).
   */
  setSearchStringToSelection() {
    let searchInfo = this.getActiveSelectionText();

    // If an empty string is returned or a subframe is focused, don't
    // assign the search string.
    if (searchInfo.selectedText) {
      this.clipboardSearchString = searchInfo.selectedText;
    }

    return searchInfo;
  },

  async highlight(aHighlight, aWord, aLinksOnly, aUseSubFrames = true) {
    return this.highlighter.highlight(
      aHighlight,
      aWord,
      aLinksOnly,
      false,
      aUseSubFrames
    );
  },

  async updateHighlightAndMatchCount(aArgs) {
    this._lastFindResult = aArgs;

    if (
      !this.iterator.continueRunning({
        caseSensitive: this._fastFind.caseSensitive,
        entireWord: this._fastFind.entireWord,
        linksOnly: aArgs.linksOnly,
        matchDiacritics: this._fastFind.matchDiacritics,
        word: aArgs.searchString,
        useSubFrames: aArgs.useSubFrames,
      })
    ) {
      this.iterator.stop();
    }

    let highlightPromise = this.highlighter.update(
      aArgs,
      aArgs.useSubFrames ? false : aArgs.foundInThisFrame
    );
    let matchCountPromise = this.requestMatchesCount(
      aArgs.searchString,
      aArgs.linksOnly,
      aArgs.useSubFrames
    );

    let results = await Promise.all([highlightPromise, matchCountPromise]);

    this.highlighter.updateScrollMarks();

    if (results[1]) {
      return Object.assign(results[1], results[0]);
    } else if (results[0]) {
      return results[0];
    }

    return null;
  },

  getInitialSelection() {
    let initialSelection = this.getActiveSelectionText().selectedText;
    this._getWindow().setTimeout(() => {
      for (let l of this._listeners) {
        try {
          l.onCurrentSelection(initialSelection, true);
        } catch (ex) {}
      }
    }, 0);
  },

  getActiveSelectionText() {
    let focusedWindow = {};
    let focusedElement = Services.focus.getFocusedElementForWindow(
      this._getWindow(),
      true,
      focusedWindow
    );
    focusedWindow = focusedWindow.value;

    let selText;

    // If this is a remote subframe, return an empty string but
    // indiciate which browsing context was focused.
    if (
      focusedElement &&
      "frameLoader" in focusedElement &&
      BrowsingContext.isInstance(focusedElement.browsingContext)
    ) {
      return {
        focusedChildBrowserContextId: focusedElement.browsingContext.id,
        selectedText: "",
      };
    }

    if (focusedElement && focusedElement.editor) {
      // The user may have a selection in an input or textarea.
      selText = focusedElement.editor.selectionController
        .getSelection(Ci.nsISelectionController.SELECTION_NORMAL)
        .toString();
    } else {
      // Look for any selected text on the actual page.
      selText = focusedWindow.getSelection().toString();
    }

    if (!selText) {
      return { selectedText: "" };
    }

    // Process our text to get rid of unwanted characters.
    selText = selText.trim().replace(/\s+/g, " ");
    let truncLength = kSelectionMaxLen;
    if (selText.length > truncLength) {
      let truncChar = selText.charAt(truncLength).charCodeAt(0);
      if (truncChar >= 0xdc00 && truncChar <= 0xdfff) {
        truncLength++;
      }
      selText = selText.substr(0, truncLength);
    }

    return { selectedText: selText };
  },

  enableSelection() {
    this._fastFind.setSelectionModeAndRepaint(
      Ci.nsISelectionController.SELECTION_ON
    );
    this._restoreOriginalOutline();
  },

  removeSelection(keepHighlight) {
    this._fastFind.collapseSelection();
    this.enableSelection();
    let window = this._getWindow();
    if (keepHighlight) {
      this.highlighter.clearCurrentOutline(window);
    } else {
      this.highlighter.clear(window);
      this.highlighter.removeScrollMarks();
    }
  },

  focusContent() {
    // Allow Finder listeners to cancel focusing the content.
    for (let l of this._listeners) {
      try {
        if ("shouldFocusContent" in l && !l.shouldFocusContent()) {
          return;
        }
      } catch (ex) {
        console.error(ex);
      }
    }

    let fastFind = this._fastFind;
    try {
      // Try to find the best possible match that should receive focus and
      // block scrolling on focus since find already scrolls. Further
      // scrolling is due to user action, so don't override this.
      if (fastFind.foundLink) {
        Services.focus.setFocus(
          fastFind.foundLink,
          Services.focus.FLAG_NOSCROLL
        );
      } else if (fastFind.foundEditable) {
        Services.focus.setFocus(
          fastFind.foundEditable,
          Services.focus.FLAG_NOSCROLL
        );
        fastFind.collapseSelection();
      } else {
        this._getWindow().focus();
      }
    } catch (e) {}
  },

  onFindbarClose() {
    this.enableSelection();
    this.highlighter.highlight(false);
    this.highlighter.removeScrollMarks();
    this.iterator.reset();
    activeFinderRoots.delete(this._docShell.browsingContext.top);
  },

  onFindbarOpen() {
    activeFinderRoots.add(this._docShell.browsingContext.top);
  },

  onModalHighlightChange(useModalHighlight) {
    if (this._highlighter) {
      this._highlighter.onModalHighlightChange(useModalHighlight);
    }
  },

  onHighlightAllChange(highlightAll) {
    if (this._highlighter) {
      this._highlighter.onHighlightAllChange(highlightAll);
    }
    if (this._iterator) {
      this._iterator.reset();
    }
  },

  keyPress(aEvent) {
    let controller = this._getSelectionController(this._getWindow());
    let accelKeyPressed =
      AppConstants.platform == "macosx" ? aEvent.metaKey : aEvent.ctrlKey;

    switch (aEvent.keyCode) {
      case aEvent.DOM_VK_RETURN:
        if (this._fastFind.foundLink) {
          let view = this._fastFind.foundLink.ownerGlobal;
          this._fastFind.foundLink.dispatchEvent(
            new view.PointerEvent("click", {
              view,
              cancelable: true,
              bubbles: true,
              ctrlKey: aEvent.ctrlKey,
              altKey: aEvent.altKey,
              shiftKey: aEvent.shiftKey,
              metaKey: aEvent.metaKey,
            })
          );
        }
        break;
      case aEvent.DOM_VK_TAB:
        let direction = Services.focus.MOVEFOCUS_FORWARD;
        if (aEvent.shiftKey) {
          direction = Services.focus.MOVEFOCUS_BACKWARD;
        }
        Services.focus.moveFocus(this._getWindow(), null, direction, 0);
        break;
      case aEvent.DOM_VK_PAGE_UP:
        controller.scrollPage(false);
        break;
      case aEvent.DOM_VK_PAGE_DOWN:
        controller.scrollPage(true);
        break;
      case aEvent.DOM_VK_UP:
        if (accelKeyPressed) {
          controller.completeScroll(false);
        } else {
          controller.scrollLine(false);
        }
        break;
      case aEvent.DOM_VK_DOWN:
        if (accelKeyPressed) {
          controller.completeScroll(true);
        } else {
          controller.scrollLine(true);
        }
        break;
    }
  },

  _notifyMatchesCount(aWord, result = this._currentMatchesCountResult) {
    // The `_currentFound` property is only used for internal bookkeeping.
    delete result._currentFound;
    result.searchString = aWord;
    result.limit = this.matchesCountLimit;
    if (result.total == result.limit) {
      result.total = -1;
    }

    for (let l of this._listeners) {
      try {
        l.onMatchesCountResult(result);
      } catch (ex) {}
    }

    this._currentMatchesCountResult = null;
    return result;
  },

  async requestMatchesCount(aWord, aLinksOnly, aUseSubFrames = true) {
    if (
      this._lastFindResult == Ci.nsITypeAheadFind.FIND_NOTFOUND ||
      this.searchString == "" ||
      !aWord ||
      !this.matchesCountLimit
    ) {
      return this._notifyMatchesCount(aWord, {
        total: 0,
        current: 0,
      });
    }

    this._currentFoundRange = this._fastFind.getFoundRange();

    let params = {
      caseSensitive: this._fastFind.caseSensitive,
      entireWord: this._fastFind.entireWord,
      linksOnly: aLinksOnly,
      matchDiacritics: this._fastFind.matchDiacritics,
      word: aWord,
      useSubFrames: aUseSubFrames,
    };
    if (!this.iterator.continueRunning(params)) {
      this.iterator.stop();
    }

    await this.iterator.start(
      Object.assign(params, {
        finder: this,
        limit: this.matchesCountLimit,
        listener: this,
        useCache: true,
        useSubFrames: aUseSubFrames,
      })
    );

    // Without a valid result, there's nothing to notify about. This happens
    // when the iterator was started before and won the race.
    if (!this._currentMatchesCountResult) {
      return null;
    }

    return this._notifyMatchesCount(aWord);
  },

  // FinderIterator listener implementation

  onIteratorRangeFound(range) {
    let result = this._currentMatchesCountResult;
    if (!result) {
      return;
    }

    ++result.total;
    if (!result._currentFound) {
      ++result.current;
      result._currentFound =
        this._currentFoundRange &&
        range.startContainer == this._currentFoundRange.startContainer &&
        range.startOffset == this._currentFoundRange.startOffset &&
        range.endContainer == this._currentFoundRange.endContainer &&
        range.endOffset == this._currentFoundRange.endOffset;
    }
  },

  onIteratorReset() {},

  onIteratorRestart({ word, linksOnly, useSubFrames }) {
    this.requestMatchesCount(word, linksOnly, useSubFrames);
  },

  onIteratorStart() {
    this._currentMatchesCountResult = {
      total: 0,
      current: 0,
      _currentFound: false,
    };
  },

  _getWindow() {
    if (!this._docShell) {
      return null;
    }
    return this._docShell.domWindow;
  },

  /**
   * Get the bounding selection rect in CSS px relative to the origin of the
   * top-level content document.
   */
  _getResultRect() {
    let topWin = this._getWindow();
    let win = this._fastFind.currentWindow;
    if (!win) {
      return null;
    }

    let selection = win.getSelection();
    if (!selection.rangeCount || selection.isCollapsed) {
      // The selection can be into an input or a textarea element.
      let nodes = win.document.querySelectorAll("input, textarea");
      for (let node of nodes) {
        if (node.editor) {
          try {
            let sc = node.editor.selectionController;
            selection = sc.getSelection(
              Ci.nsISelectionController.SELECTION_NORMAL
            );
            if (selection.rangeCount && !selection.isCollapsed) {
              break;
            }
          } catch (e) {
            // If this textarea is hidden, then its selection controller might
            // not be intialized. Ignore the failure.
          }
        }
      }
    }

    if (!selection.rangeCount || selection.isCollapsed) {
      return null;
    }

    let utils = topWin.windowUtils;

    let scrollX = {},
      scrollY = {};
    utils.getScrollXY(false, scrollX, scrollY);

    for (let frame = win; frame != topWin; frame = frame.parent) {
      let rect = frame.frameElement.getBoundingClientRect();
      let left = frame.getComputedStyle(frame.frameElement).borderLeftWidth;
      let top = frame.getComputedStyle(frame.frameElement).borderTopWidth;
      scrollX.value += rect.left + parseInt(left, 10);
      scrollY.value += rect.top + parseInt(top, 10);
    }
    let rect = Rect.fromRect(selection.getRangeAt(0).getBoundingClientRect());
    return rect.translate(scrollX.value, scrollY.value);
  },

  _outlineLink(aDrawOutline) {
    let foundLink = this._fastFind.foundLink;

    // Optimization: We are drawing outlines and we matched
    // the same link before, so don't duplicate work.
    if (foundLink == this._previousLink && aDrawOutline) {
      return;
    }

    this._restoreOriginalOutline();

    if (foundLink && aDrawOutline) {
      // Backup original outline
      this._tmpOutline = foundLink.style.outline;
      this._tmpOutlineOffset = foundLink.style.outlineOffset;

      // Draw pseudo focus rect
      // XXX Should we change the following style for FAYT pseudo focus?
      // XXX Shouldn't we change default design if outline is visible
      //     already?
      // Don't set the outline-color, we should always use initial value.
      foundLink.style.outline = "1px dotted";
      foundLink.style.outlineOffset = "0";

      this._previousLink = foundLink;
    }
  },

  _restoreOriginalOutline() {
    // Removes the outline around the last found link.
    if (this._previousLink) {
      this._previousLink.style.outline = this._tmpOutline;
      this._previousLink.style.outlineOffset = this._tmpOutlineOffset;
      this._previousLink = null;
    }
  },

  _getSelectionController(aWindow) {
    // display: none iframes don't have a selection controller, see bug 493658
    try {
      if (!aWindow.innerWidth || !aWindow.innerHeight) {
        return null;
      }
    } catch (e) {
      // If getting innerWidth or innerHeight throws, we can't get a selection
      // controller.
      return null;
    }

    // Yuck. See bug 138068.
    let docShell = aWindow.docShell;

    let controller = docShell
      .QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsISelectionDisplay)
      .QueryInterface(Ci.nsISelectionController);
    return controller;
  },

  // Start of nsIWebProgressListener implementation.

  onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
    if (!aWebProgress.isTopLevel) {
      return;
    }
    // Ignore events that don't change the document.
    if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) {
      return;
    }

    // Avoid leaking if we change the page.
    this._lastFindResult = this._previousLink = this._currentFoundRange = null;
    this.highlighter.onLocationChange();
    this.iterator.reset();
  },

  QueryInterface: ChromeUtils.generateQI([
    "nsIWebProgressListener",
    "nsISupportsWeakReference",
  ]),
};

export function GetClipboardSearchString(aLoadContext) {
  let searchString = "";
  if (
    !Services.clipboard.isClipboardTypeSupported(
      Services.clipboard.kFindClipboard
    )
  ) {
    return searchString;
  }

  try {
    let trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
      Ci.nsITransferable
    );
    trans.init(aLoadContext);
    trans.addDataFlavor("text/plain");

    Services.clipboard.getData(trans, Ci.nsIClipboard.kFindClipboard);

    let data = {};
    trans.getTransferData("text/plain", data);
    if (data.value) {
      data = data.value.QueryInterface(Ci.nsISupportsString);
      searchString = data.toString();
    }
  } catch (ex) {}

  return searchString;
}

export function SetClipboardSearchString(aSearchString) {
  if (
    !aSearchString ||
    !Services.clipboard.isClipboardTypeSupported(
      Services.clipboard.kFindClipboard
    )
  ) {
    return;
  }

  lazy.ClipboardHelper.copyStringToClipboard(
    aSearchString,
    Ci.nsIClipboard.kFindClipboard
  );
}
PK
!<��t�MM!modules/FinderHighlighter.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Color: "resource://gre/modules/Color.sys.mjs",
  Rect: "resource://gre/modules/Geometry.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "kDebug", () => {
  const kDebugPref = "findbar.modalHighlight.debug";
  return (
    Services.prefs.getPrefType(kDebugPref) &&
    Services.prefs.getBoolPref(kDebugPref)
  );
});

const kContentChangeThresholdPx = 5;
const kBrightTextSampleSize = 5;
// This limit is arbitrary and doesn't scale for low-powered machines or
// high-powered machines. Netbooks will probably need a much lower limit, for
// example. Though getting something out there is better than nothing.
const kPageIsTooBigPx = 500000;
const kModalHighlightRepaintLoFreqMs = 100;
const kModalHighlightRepaintHiFreqMs = 16;
const kHighlightAllPref = "findbar.highlightAll";
const kModalHighlightPref = "findbar.modalHighlight";
const kFontPropsCSS = [
  "color",
  "font-family",
  "font-kerning",
  "font-size",
  "font-size-adjust",
  "font-stretch",
  "font-variant",
  "font-weight",
  "line-height",
  "letter-spacing",
  "text-emphasis",
  "text-orientation",
  "text-transform",
  "word-spacing",
];
const kFontPropsCamelCase = kFontPropsCSS.map(prop => {
  let parts = prop.split("-");
  return (
    parts.shift() +
    parts.map(part => part.charAt(0).toUpperCase() + part.slice(1)).join("")
  );
});
const kRGBRE = /^rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*/i;
// This uuid is used to prefix HTML element IDs in order to make them unique and
// hard to clash with IDs content authors come up with.
const kModalIdPrefix = "cedee4d0-74c5-4f2d-ab43-4d37c0f9d463";
const kModalOutlineId = kModalIdPrefix + "-findbar-modalHighlight-outline";
const kOutlineBoxColor = "255,197,53";
const kOutlineBoxBorderSize = 1;
const kOutlineBoxBorderRadius = 2;
const kModalStyles = {
  outlineNode: [
    ["background-color", `rgb(${kOutlineBoxColor})`],
    ["background-clip", "padding-box"],
    ["border", `${kOutlineBoxBorderSize}px solid rgba(${kOutlineBoxColor},.7)`],
    ["border-radius", `${kOutlineBoxBorderRadius}px`],
    ["box-shadow", `0 2px 0 0 rgba(0,0,0,.1)`],
    ["color", "#000"],
    ["display", "flex"],
    [
      "margin",
      `-${kOutlineBoxBorderSize}px 0 0 -${kOutlineBoxBorderSize}px !important`,
    ],
    ["overflow", "hidden"],
    ["pointer-events", "none"],
    ["position", "absolute"],
    ["white-space", "nowrap"],
    ["will-change", "transform"],
    ["z-index", 2],
  ],
  outlineNodeDebug: [["z-index", 2147483647]],
  outlineText: [
    ["margin", "0 !important"],
    ["padding", "0 !important"],
    ["vertical-align", "top !important"],
  ],
  maskNode: [
    ["background", "rgba(0,0,0,.25)"],
    ["pointer-events", "none"],
    ["position", "absolute"],
    ["z-index", 1],
  ],
  maskNodeTransition: [["transition", "background .2s ease-in"]],
  maskNodeDebug: [
    ["z-index", 2147483646],
    ["top", 0],
    ["left", 0],
  ],
  maskNodeBrightText: [["background", "rgba(255,255,255,.25)"]],
};
const kModalOutlineAnim = {
  keyframes: [
    { transform: "scaleX(1) scaleY(1)" },
    { transform: "scaleX(1.5) scaleY(1.5)", offset: 0.5, easing: "ease-in" },
    { transform: "scaleX(1) scaleY(1)" },
  ],
  duration: 50,
};
const kNSHTML = "http://www.w3.org/1999/xhtml";
const kRepaintSchedulerStopped = 1;
const kRepaintSchedulerPaused = 2;
const kRepaintSchedulerRunning = 3;

function mockAnonymousContentNode(domNode) {
  return {
    setTextContentForElement(id, text) {
      (domNode.querySelector("#" + id) || domNode).textContent = text;
    },
    getAttributeForElement(id, attrName) {
      let node = domNode.querySelector("#" + id) || domNode;
      if (!node.hasAttribute(attrName)) {
        return undefined;
      }
      return node.getAttribute(attrName);
    },
    setAttributeForElement(id, attrName, attrValue) {
      (domNode.querySelector("#" + id) || domNode).setAttribute(
        attrName,
        attrValue
      );
    },
    removeAttributeForElement(id, attrName) {
      let node = domNode.querySelector("#" + id) || domNode;
      if (!node.hasAttribute(attrName)) {
        return;
      }
      node.removeAttribute(attrName);
    },
    remove() {
      try {
        domNode.remove();
      } catch (ex) {}
    },
    setAnimationForElement(id, keyframes, duration) {
      return (domNode.querySelector("#" + id) || domNode).animate(
        keyframes,
        duration
      );
    },
    setCutoutRectsForElement() {
      // no-op for now.
    },
  };
}

let gWindows = new WeakMap();

/**
 * FinderHighlighter class that is used by Finder.sys.mjs to take care of the
 * 'Highlight All' feature, which can highlight all find occurrences in a page.
 *
 * @param {Finder} finder Finder.sys.mjs instance
 * @param {boolean} useTop check and use top-level windows for rectangle
 *                         computation, if possible.
 */
export function FinderHighlighter(finder, useTop = false) {
  this._highlightAll = Services.prefs.getBoolPref(kHighlightAllPref);
  this._modal = Services.prefs.getBoolPref(kModalHighlightPref);
  this._useSubFrames = false;
  this._useTop = useTop;
  this._marksListener = null;
  this._testing = false;
  this.finder = finder;
}

FinderHighlighter.prototype = {
  get iterator() {
    return this.finder.iterator;
  },

  enableTesting(enable) {
    this._testing = enable;
  },

  // Get the top-most window when allowed. When out-of-process frames are used,
  // this will usually be the same as the passed-in window. The checkUseTop
  // argument can be used to instead check the _useTop flag which can be used
  // to enable rectangle coordinate checks.
  getTopWindow(window, checkUseTop) {
    if (this._useSubFrames || (checkUseTop && this._useTop)) {
      try {
        return window.top;
      } catch (ex) {}
    }

    return window;
  },

  useModal() {
    // Modal highlighting is currently only enabled when there are no
    // out-of-process subframes.
    return this._modal && this._useSubFrames;
  },

  /**
   * Each window is unique, globally, and the relation between an active
   * highlighting session and a window is 1:1.
   * For each window we track a number of properties which _at least_ consist of
   *  - {Boolean} detectedGeometryChange Whether the geometry of the found ranges'
   *                                     rectangles has changed substantially
   *  - {Set}     dynamicRangesSet       Set of ranges that may move around, depending
   *                                     on page layout changes and user input
   *  - {Map}     frames                 Collection of frames that were encountered
   *                                     when inspecting the found ranges
   *  - {Map}     modalHighlightRectsMap Collection of ranges and their corresponding
   *                                     Rects and texts
   *
   * @param  {nsIDOMWindow} window
   * @return {Object}
   */
  getForWindow(window) {
    if (!gWindows.has(window)) {
      gWindows.set(window, {
        detectedGeometryChange: false,
        dynamicRangesSet: new Set(),
        frames: new Map(),
        lastWindowDimensions: { width: 0, height: 0 },
        modalHighlightRectsMap: new Map(),
        previousRangeRectsAndTexts: { rectList: [], textList: [] },
        repaintSchedulerState: kRepaintSchedulerStopped,
      });
    }
    return gWindows.get(window);
  },

  /**
   * Notify all registered listeners that the 'Highlight All' operation finished.
   *
   * @param {Boolean} highlight Whether highlighting was turned on
   */
  notifyFinished(highlight) {
    for (let l of this.finder._listeners) {
      try {
        l.onHighlightFinished(highlight);
      } catch (ex) {}
    }
  },

  /**
   * Toggle highlighting all occurrences of a word in a page. This method will
   * be called recursively for each (i)frame inside a page.
   *
   * @param {Booolean} highlight    Whether highlighting should be turned on
   * @param {String}   word         Needle to search for and highlight when found
   * @param {Boolean}  linksOnly    Only consider nodes that are links for the search
   * @param {Boolean}  drawOutline  Whether found links should be outlined.
   * @param {Boolean}  useSubFrames Whether to iterate over subframes.
   * @yield {Promise}  that resolves once the operation has finished
   */
  async highlight(highlight, word, linksOnly, drawOutline, useSubFrames) {
    let window = this.finder._getWindow();
    let dict = this.getForWindow(window);
    let controller = this.finder._getSelectionController(window);
    let doc = window.document;
    this._found = false;
    this._useSubFrames = useSubFrames;

    let result = { searchString: word, highlight, found: false };

    if (!controller || !doc || !doc.documentElement) {
      // Without the selection controller,
      // we are unable to (un)highlight any matches
      return result;
    }

    if (highlight) {
      let params = {
        allowDistance: 1,
        caseSensitive: this.finder._fastFind.caseSensitive,
        entireWord: this.finder._fastFind.entireWord,
        linksOnly,
        word,
        finder: this.finder,
        listener: this,
        matchDiacritics: this.finder._fastFind.matchDiacritics,
        useCache: true,
        useSubFrames,
        window,
      };
      if (
        this.iterator.isAlreadyRunning(params) ||
        (this.useModal() &&
          this.iterator._areParamsEqual(params, dict.lastIteratorParams))
      ) {
        return result;
      }

      if (!this.useModal()) {
        dict.visible = true;
      }
      await this.iterator.start(params);
      if (this._found) {
        this.finder._outlineLink(drawOutline);
      }
    } else {
      this.hide(window);

      // Removing the highlighting always succeeds, so return true.
      this._found = true;
    }

    result.found = this._found;
    this.notifyFinished(result);
    return result;
  },

  // FinderIterator listener implementation

  onIteratorRangeFound(range) {
    this.highlightRange(range);
    this._found = true;
  },

  onIteratorReset() {},

  onIteratorRestart() {
    this.clear(this.finder._getWindow());
  },

  onIteratorStart(params) {
    let window = this.finder._getWindow();
    let dict = this.getForWindow(window);
    // Save a clean params set for use later in the `update()` method.
    dict.lastIteratorParams = params;
    if (!this.useModal()) {
      this.hide(window, this.finder._fastFind.getFoundRange());
    }
    this.clear(window);
  },

  /**
   * Add a range to the find selection, i.e. highlight it, and if it's inside an
   * editable node, track it.
   *
   * @param {Range} range Range object to be highlighted
   */
  highlightRange(range) {
    let node = range.startContainer;
    let editableNode = this._getEditableNode(node);
    let window = node.ownerGlobal;
    let controller = this.finder._getSelectionController(window);
    if (editableNode) {
      controller = editableNode.editor.selectionController;
    }

    if (this.useModal()) {
      this._modalHighlight(range, controller, window);
    } else {
      let findSelection = controller.getSelection(
        Ci.nsISelectionController.SELECTION_FIND
      );
      findSelection.addRange(range);
      // Check if the range is inside an (i)frame.
      if (window != this.getTopWindow(window)) {
        let dict = this.getForWindow(this.getTopWindow(window));
        // Add this frame to the list, so that we'll be able to find it later
        // when we need to clear its selection(s).
        dict.frames.set(window, {});
      }
    }

    if (editableNode) {
      // Highlighting added, so cache this editor, and hook up listeners
      // to ensure we deal properly with edits within the highlighting
      this._addEditorListeners(editableNode.editor);
    }
  },

  /**
   * If modal highlighting is enabled, show the dimmed background that will overlay
   * the page.
   *
   * @param {nsIDOMWindow} window The dimmed background will overlay this window.
   *                              Optional, defaults to the finder window.
   */
  show(window = null) {
    window = this.getTopWindow(window || this.finder._getWindow());
    let dict = this.getForWindow(window);
    if (!this.useModal() || dict.visible) {
      return;
    }

    dict.visible = true;

    this._maybeCreateModalHighlightNodes(window);
    this._addModalHighlightListeners(window);
  },

  /**
   * Clear all highlighted matches. If modal highlighting is enabled and
   * the outline + dimmed background is currently visible, both will be hidden.
   *
   * @param {nsIDOMWindow} window    The dimmed background will overlay this window.
   *                                 Optional, defaults to the finder window.
   * @param {Range}        skipRange A range that should not be removed from the
   *                                 find selection.
   * @param {Event}        event     When called from an event handler, this will
   *                                 be the triggering event.
   */
  hide(window, skipRange = null, event = null) {
    try {
      window = this.getTopWindow(window);
    } catch (ex) {
      console.error(ex);
      return;
    }
    let dict = this.getForWindow(window);

    let isBusySelecting = dict.busySelecting;
    dict.busySelecting = false;
    // Do not hide on anything but a left-click.
    if (
      event &&
      event.type == "click" &&
      (event.button !== 0 ||
        event.altKey ||
        event.ctrlKey ||
        event.metaKey ||
        event.shiftKey ||
        event.relatedTarget ||
        isBusySelecting ||
        (event.target.localName == "a" && event.target.href))
    ) {
      return;
    }

    this._clearSelection(
      this.finder._getSelectionController(window),
      skipRange
    );
    for (let frame of dict.frames.keys()) {
      this._clearSelection(
        this.finder._getSelectionController(frame),
        skipRange
      );
    }

    // Next, check our editor cache, for editors belonging to this
    // document
    if (this._editors) {
      let doc = window.document;
      for (let x = this._editors.length - 1; x >= 0; --x) {
        if (this._editors[x].document == doc) {
          this._clearSelection(this._editors[x].selectionController, skipRange);
          // We don't need to listen to this editor any more
          this._unhookListenersAtIndex(x);
        }
      }
    }

    if (dict.modalRepaintScheduler) {
      window.clearTimeout(dict.modalRepaintScheduler);
      dict.modalRepaintScheduler = null;
      dict.repaintSchedulerState = kRepaintSchedulerStopped;
    }
    dict.lastWindowDimensions = { width: 0, height: 0 };

    this._removeRangeOutline(window);
    this._removeHighlightAllMask(window);
    this._removeModalHighlightListeners(window);

    dict.visible = false;
  },

  /**
   * Called by the Finder after a find result comes in; update the position and
   * content of the outline to the newly found occurrence.
   * To make sure that the outline covers the found range completely, all the
   * CSS styles that influence the text are copied and applied to the outline.
   *
   * @param {Object} data Dictionary coming from Finder that contains the
   *                      following properties:
   *   {Number}  result        One of the nsITypeAheadFind.FIND_* constants
   *                           indicating the result of a search operation.
   *   {Boolean} findBackwards If TRUE, the search was performed backwards,
   *                           FALSE if forwards.
   *   {Boolean} findAgain     If TRUE, the search was performed using the same
   *                           search string as before.
   *   {String}  linkURL       If a link was hit, this will contain a URL string.
   *   {Rect}    rect          An object with top, left, width and height
   *                           coordinates of the current selection.
   *   {String}  searchString  The string the search was performed with.
   *   {Boolean} storeResult   Indicator if the search string should be stored
   *                           by the consumer of the Finder.
   */
  async update(data, foundInThisFrame) {
    let window = this.finder._getWindow();
    let dict = this.getForWindow(window);
    let foundRange = this.finder._fastFind.getFoundRange();

    if (
      data.result == Ci.nsITypeAheadFind.FIND_NOTFOUND ||
      !data.searchString ||
      (foundInThisFrame && !foundRange)
    ) {
      this.hide(window);
      return;
    }

    this._useSubFrames = data.useSubFrames;
    if (!this.useModal()) {
      if (this._highlightAll) {
        dict.previousFoundRange = dict.currentFoundRange;
        dict.currentFoundRange = foundRange;
        let params = this.iterator.params;
        if (
          dict.visible &&
          this.iterator._areParamsEqual(params, dict.lastIteratorParams)
        ) {
          return;
        }
        if (!dict.visible && !params) {
          params = { word: data.searchString, linksOnly: data.linksOnly };
        }
        if (params) {
          await this.highlight(
            true,
            params.word,
            params.linksOnly,
            params.drawOutline,
            data.useSubFrames
          );
        }
      }
      return;
    }

    dict.animateOutline = true;
    // Immediately finish running animations, if any.
    this._finishOutlineAnimations(dict);

    if (foundRange !== dict.currentFoundRange || data.findAgain) {
      dict.previousFoundRange = dict.currentFoundRange;
      dict.currentFoundRange = foundRange;

      if (!dict.visible) {
        this.show(window);
      } else {
        this._maybeCreateModalHighlightNodes(window);
      }
    }

    if (this._highlightAll) {
      await this.highlight(
        true,
        data.searchString,
        data.linksOnly,
        data.drawOutline,
        data.useSubFrames
      );
    }
  },

  /**
   * Invalidates the list by clearing the map of highlighted ranges that we
   * keep to build the mask for.
   */
  clear(window = null) {
    if (!window || !this.getTopWindow(window)) {
      return;
    }

    let dict = this.getForWindow(this.getTopWindow(window));
    this._finishOutlineAnimations(dict);
    dict.dynamicRangesSet.clear();
    dict.frames.clear();
    dict.modalHighlightRectsMap.clear();
    dict.brightText = null;
  },

  /**
   * Removes the outline from a single window. This is done when
   * switching the current search to a new frame.
   */
  clearCurrentOutline(window = null) {
    let dict = this.getForWindow(this.getTopWindow(window));
    this._finishOutlineAnimations(dict);
    this._removeRangeOutline(window);
  },

  // Update the tick marks that should appear on the page's scrollbar(s).
  updateScrollMarks() {
    // Only show scrollbar marks when normal highlighting is enabled.
    if (this.useModal() || !this._highlightAll) {
      this.removeScrollMarks();
      return;
    }

    let marks = new Set(); // Use a set so duplicate values are removed.
    let window = this.finder._getWindow();
    // Show the marks on the horizontal scrollbar for vertical writing modes.
    let onHorizontalScrollbar = !window
      .getComputedStyle(window.document.body || window.document.documentElement)
      .writingMode.startsWith("horizontal");
    let yStart = window.scrollY - window.scrollMinY;
    let xStart = window.scrollX - window.scrollMinX;

    let hasRanges = false;
    if (window) {
      let controllers = [this.finder._getSelectionController(window)];
      let editors = this.editors;
      if (editors) {
        // Add the selection controllers from any input fields.
        controllers.push(...editors.map(editor => editor.selectionController));
      }

      for (let controller of controllers) {
        let findSelection = controller.getSelection(
          Ci.nsISelectionController.SELECTION_FIND
        );

        let rangeCount = findSelection.rangeCount;
        if (rangeCount > 0) {
          hasRanges = true;
        }

        // No need to calculate the mark positions if there is no visible scrollbar.
        if (window.scrollMaxY > window.scrollMinY && !onHorizontalScrollbar) {
          // Use the body's scrollHeight if available.
          let scrollHeight =
            window.document.body?.scrollHeight ||
            window.document.documentElement.scrollHeight;
          let yAdj = (window.scrollMaxY - window.scrollMinY) / scrollHeight;

          for (let r = 0; r < rangeCount; r++) {
            let rect = findSelection.getRangeAt(r).getBoundingClientRect();
            let yPos = Math.round((yStart + rect.y + rect.height / 2) * yAdj); // use the midpoint
            marks.add(yPos);
          }
        } else if (
          window.scrollMaxX > window.scrollMinX &&
          onHorizontalScrollbar
        ) {
          // Use the body's scrollWidth if available.
          let scrollWidth =
            window.document.body?.scrollWidth ||
            window.document.documentElement.scrollWidth;
          let xAdj = (window.scrollMaxX - window.scrollMinX) / scrollWidth;

          for (let r = 0; r < rangeCount; r++) {
            let rect = findSelection.getRangeAt(r).getBoundingClientRect();
            let xPos = Math.round((xStart + rect.x + rect.width / 2) * xAdj);
            marks.add(xPos);
          }
        }
      }
    }

    if (hasRanges) {
      // Assign the marks to the window and add a listener for the MozScrolledAreaChanged
      // event which fires whenever the scrollable area's size is updated.
      this.setScrollMarks(window, Array.from(marks), onHorizontalScrollbar);

      if (!this._marksListener) {
        this._marksListener = () => {
          this.updateScrollMarks();
        };

        window.addEventListener(
          "MozScrolledAreaChanged",
          this._marksListener,
          true
        );
        window.addEventListener("resize", this._marksListener);
      }
    } else if (this._marksListener) {
      // No results were found so remove any existing ones and the MozScrolledAreaChanged listener.
      this.removeScrollMarks();
    }
  },

  removeScrollMarks() {
    let window;
    try {
      window = this.finder._getWindow();
    } catch (ex) {
      // An exception can happen after changing remoteness but this
      // would have deleted the marks anyway.
      return;
    }

    if (this._marksListener) {
      window.removeEventListener(
        "MozScrolledAreaChanged",
        this._marksListener,
        true
      );
      window.removeEventListener("resize", this._marksListener);
      this._marksListener = null;
    }
    this.setScrollMarks(window, []);
  },

  /**
   * Set the scrollbar marks for a current search. If testing mode is enabled, fire a
   * find-scrollmarks-changed event at the window.
   *
   * @param window window to set the scrollbar marks on
   * @param marks array of integer scrollbar mark positions
   * @param onHorizontalScrollbar whether to display the marks on the horizontal scrollbar
   */
  setScrollMarks(window, marks, onHorizontalScrollbar = false) {
    window.setScrollMarks(marks, onHorizontalScrollbar);

    // Fire an event containing the found mark values if testing mode is enabled.
    if (this._testing) {
      window.dispatchEvent(
        new CustomEvent("find-scrollmarks-changed", {
          detail: {
            marks: Array.from(marks),
            onHorizontalScrollbar,
          },
        })
      );
    }
  },

  /**
   * When the current page is refreshed or navigated away from, the CanvasFrame
   * contents is not valid anymore, i.e. all anonymous content is destroyed.
   * We need to clear the references we keep, which'll make sure we redraw
   * everything when the user starts to find in page again.
   */
  onLocationChange() {
    let window = this.finder._getWindow();
    if (!window || !this.getTopWindow(window)) {
      return;
    }
    this.hide(window);
    this.clear(window);
    this._removeRangeOutline(window);

    gWindows.delete(this.getTopWindow(window));
  },

  /**
   * When `kModalHighlightPref` pref changed during a session, this callback is
   * invoked. When modal highlighting is turned off, we hide the CanvasFrame
   * contents.
   *
   * @param {Boolean} useModalHighlight
   */
  onModalHighlightChange(useModalHighlight) {
    let window = this.finder._getWindow();
    if (window && this.useModal() && !useModalHighlight) {
      this.hide(window);
      this.clear(window);
    }
    this._modal = useModalHighlight;
    this.updateScrollMarks();
  },

  /**
   * When 'Highlight All' is toggled during a session, this callback is invoked
   * and when it's turned off, the found occurrences will be removed from the mask.
   *
   * @param {Boolean} highlightAll
   */
  onHighlightAllChange(highlightAll) {
    this._highlightAll = highlightAll;
    if (!highlightAll) {
      let window = this.finder._getWindow();
      if (!this.useModal()) {
        this.hide(window);
      }
      this.clear(window);
      this._scheduleRepaintOfMask(window);
    }

    this.updateScrollMarks();
  },

  /**
   * Utility; removes all ranges from the find selection that belongs to a
   * controller. Optionally skips a specific range.
   *
   * @param  {nsISelectionController} controller
   * @param  {Range}                  restoreRange
   */
  _clearSelection(controller, restoreRange = null) {
    if (!controller) {
      return;
    }
    let sel = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
    sel.removeAllRanges();
    if (restoreRange) {
      sel = controller.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
      sel.addRange(restoreRange);
      controller.setDisplaySelection(
        Ci.nsISelectionController.SELECTION_ATTENTION
      );
      controller.repaintSelection(Ci.nsISelectionController.SELECTION_NORMAL);
    }
  },

  /**
   * Utility; get the nsIDOMWindowUtils for a window.
   *
   * @param  {nsIDOMWindow} window Optional, defaults to the finder window.
   * @return {nsIDOMWindowUtils}
   */
  _getDWU(window = null) {
    return (window || this.finder._getWindow()).windowUtils;
  },

  /**
   * Utility; returns the bounds of the page relative to the viewport.
   * If the pages is part of a frameset or inside an iframe of any kind, its
   * offset is accounted for.
   * Geometry.sys.mjs takes care of the DOMRect calculations.
   *
   * @param  {nsIDOMWindow} window          Window to read the boundary rect from
   * @param  {Boolean}      [includeScroll] Whether to ignore the scroll offset,
   *                                        which is useful for comparing DOMRects.
   *                                        Optional, defaults to `true`
   * @return {Rect}
   */
  _getRootBounds(window, includeScroll = true) {
    let dwu = this._getDWU(this.getTopWindow(window, true));
    let cssPageRect = lazy.Rect.fromRect(dwu.getRootBounds());
    let scrollX = {};
    let scrollY = {};
    if (includeScroll && window == this.getTopWindow(window, true)) {
      dwu.getScrollXY(false, scrollX, scrollY);
      cssPageRect.translate(scrollX.value, scrollY.value);
    }

    // If we're in a frame, update the position of the rect (top/ left).
    let currWin = window;
    while (currWin != this.getTopWindow(window, true)) {
      let frameOffsets = this._getFrameElementOffsets(currWin);
      cssPageRect.translate(frameOffsets.x, frameOffsets.y);

      // Since the frame is an element inside a parent window, we'd like to
      // learn its position relative to it.
      let el = currWin.browsingContext.embedderElement;
      currWin = currWin.parent;
      dwu = this._getDWU(currWin);
      let parentRect = lazy.Rect.fromRect(dwu.getBoundsWithoutFlushing(el));

      if (includeScroll) {
        dwu.getScrollXY(false, scrollX, scrollY);
        parentRect.translate(scrollX.value, scrollY.value);
        // If the current window is an iframe with scrolling="no" and its parent
        // is also an iframe the scroll offsets from the parents' documentElement
        // (inverse scroll position) needs to be subtracted from the parent
        // window rect.
        if (
          el.getAttribute("scrolling") == "no" &&
          currWin != this.getTopWindow(window, true)
        ) {
          let docEl = currWin.document.documentElement;
          parentRect.translate(-docEl.scrollLeft, -docEl.scrollTop);
        }
      }

      cssPageRect.translate(parentRect.left, parentRect.top);
    }
    let frameOffsets = this._getFrameElementOffsets(currWin);
    cssPageRect.translate(frameOffsets.x, frameOffsets.y);

    return cssPageRect;
  },

  /**
   * (I)Frame elements may have a border and/ or padding set, which is not
   * included in the bounds returned by nsDOMWindowUtils#getRootBounds() for the
   * window it hosts.
   * This method fetches this offset of the frame element to the respective window.
   *
   * @param  {nsIDOMWindow} window          Window to read the boundary rect from
   * @return {Object}       Simple object that contains the following two properties:
   *                        - {Number} x Offset along the horizontal axis.
   *                        - {Number} y Offset along the vertical axis.
   */
  _getFrameElementOffsets(window) {
    let frame = window.frameElement;
    if (!frame) {
      return { x: 0, y: 0 };
    }

    // Getting style info is super expensive, causing reflows, so let's cache
    // frame border widths and padding values aggressively.
    let dict = this.getForWindow(this.getTopWindow(window, true));
    let frameData = dict.frames.get(window);
    if (!frameData) {
      dict.frames.set(window, (frameData = {}));
    }
    if (frameData.offset) {
      return frameData.offset;
    }

    let style = frame.ownerGlobal.getComputedStyle(frame);
    // We only need to left sides, because ranges are offset from point 0,0 in
    // the top-left corner.
    let borderOffset = [
      parseInt(style.borderLeftWidth, 10) || 0,
      parseInt(style.borderTopWidth, 10) || 0,
    ];
    let paddingOffset = [
      parseInt(style.paddingLeft, 10) || 0,
      parseInt(style.paddingTop, 10) || 0,
    ];
    return (frameData.offset = {
      x: borderOffset[0] + paddingOffset[0],
      y: borderOffset[1] + paddingOffset[1],
    });
  },

  /**
   * Utility; fetch the full width and height of the current window, excluding
   * scrollbars.
   *
   * @param  {nsiDOMWindow} window The current finder window.
   * @return {Object} The current full page dimensions with `width` and `height`
   *                  properties
   */
  _getWindowDimensions(window) {
    // First we'll try without flushing layout, because it's way faster.
    let dwu = this._getDWU(window);
    let { width, height } = dwu.getRootBounds();

    if (!width || !height) {
      // We need a flush after all :'(
      width = window.innerWidth + window.scrollMaxX - window.scrollMinX;
      height = window.innerHeight + window.scrollMaxY - window.scrollMinY;

      let scrollbarHeight = {};
      let scrollbarWidth = {};
      dwu.getScrollbarSize(false, scrollbarWidth, scrollbarHeight);
      width -= scrollbarWidth.value;
      height -= scrollbarHeight.value;
    }

    return { width, height };
  },

  /**
   * Utility; get all available font styles as applied to the content of a given
   * range. The CSS properties we look for can be found in `kFontPropsCSS`.
   *
   * @param  {Range} range Range to fetch style info from.
   * @return {Object} Dictionary consisting of the styles that were found.
   */
  _getRangeFontStyle(range) {
    let node = range.startContainer;
    while (node.nodeType != 1) {
      node = node.parentNode;
    }
    let style = node.ownerGlobal.getComputedStyle(node);
    let props = {};
    for (let prop of kFontPropsCamelCase) {
      if (prop in style && style[prop]) {
        props[prop] = style[prop];
      }
    }
    return props;
  },

  /**
   * Utility; transform a dictionary object as returned by `_getRangeFontStyle`
   * above into a HTML style attribute value.
   *
   * @param  {Object} fontStyle
   * @return {String}
   */
  _getHTMLFontStyle(fontStyle) {
    let style = [];
    for (let prop of Object.getOwnPropertyNames(fontStyle)) {
      let idx = kFontPropsCamelCase.indexOf(prop);
      if (idx == -1) {
        continue;
      }
      style.push(`${kFontPropsCSS[idx]}: ${fontStyle[prop]}`);
    }
    return style.join("; ");
  },

  /**
   * Transform a style definition array as defined in `kModalStyles` into a CSS
   * string that can be used to set the 'style' property of a DOM node.
   *
   * @param  {Array}    stylePairs         Two-dimensional array of style pairs
   * @param  {...Array} [additionalStyles] Optional set of style pairs that will
   *                                       augment or override the styles defined
   *                                       by `stylePairs`
   * @return {String}
   */
  _getStyleString(stylePairs, ...additionalStyles) {
    let baseStyle = new Map(stylePairs);
    for (let additionalStyle of additionalStyles) {
      for (let [prop, value] of additionalStyle) {
        baseStyle.set(prop, value);
      }
    }
    return [...baseStyle]
      .map(([cssProp, cssVal]) => `${cssProp}: ${cssVal}`)
      .join("; ");
  },

  /**
   * Checks whether a CSS RGB color value can be classified as being 'bright'.
   *
   * @param  {String} cssColor RGB color value in the default format rgb[a](r,g,b)
   * @return {Boolean}
   */
  _isColorBright(cssColor) {
    cssColor = cssColor.match(kRGBRE);
    if (!cssColor || !cssColor.length) {
      return false;
    }
    cssColor.shift();
    return !new lazy.Color(...cssColor).useBrightText;
  },

  /**
   * Detects if the overall text color in the page can be described as bright.
   * This is done according to the following algorithm:
   *  1. With the entire set of ranges that we have found thusfar;
   *  2. Get an odd-numbered `sampleSize`, with a maximum of `kBrightTextSampleSize`
   *     ranges,
   *  3. Slice the set of ranges into `sampleSize` number of equal parts,
   *  4. Grab the first range for each slice and inspect the brightness of the
   *     color of its text content.
   *  5. When the majority of ranges are counted as contain bright colored text,
   *     the page is considered to contain bright text overall.
   *
   * @param {Object} dict Dictionary of properties belonging to the
   *                      currently active window. The page text color property
   *                      will be recorded in `dict.brightText` as `true` or `false`.
   */
  _detectBrightText(dict) {
    let sampleSize = Math.min(
      dict.modalHighlightRectsMap.size,
      kBrightTextSampleSize
    );
    let ranges = [...dict.modalHighlightRectsMap.keys()];
    let rangesCount = ranges.length;
    // Make sure the sample size is an odd number.
    if (sampleSize % 2 == 0) {
      // Make the previously or currently found range weigh heavier.
      if (dict.previousFoundRange || dict.currentFoundRange) {
        ranges.push(dict.previousFoundRange || dict.currentFoundRange);
        ++sampleSize;
        ++rangesCount;
      } else {
        --sampleSize;
      }
    }
    let brightCount = 0;
    for (let i = 0; i < sampleSize; ++i) {
      let range = ranges[Math.floor((rangesCount / sampleSize) * i)];
      let fontStyle = this._getRangeFontStyle(range);
      if (this._isColorBright(fontStyle.color)) {
        ++brightCount;
      }
    }

    dict.brightText = brightCount >= Math.ceil(sampleSize / 2);
  },

  /**
   * Checks if a range is inside a DOM node that's positioned in a way that it
   * doesn't scroll along when the document is scrolled and/ or zoomed. This
   * is the case for 'fixed' and 'sticky' positioned elements, elements inside
   * (i)frames and elements that have their overflow styles set to 'auto' or
   * 'scroll'.
   *
   * @param  {Range} range Range that be enclosed in a dynamic container
   * @return {Boolean}
   */
  _isInDynamicContainer(range) {
    const kFixed = new Set(["fixed", "sticky", "scroll", "auto"]);
    let node = range.startContainer;
    while (node.nodeType != 1) {
      node = node.parentNode;
    }
    let document = node.ownerDocument;
    let window = document.defaultView;
    let dict = this.getForWindow(this.getTopWindow(window));

    // Check if we're in a frameset (including iframes).
    if (window != this.getTopWindow(window)) {
      if (!dict.frames.has(window)) {
        dict.frames.set(window, {});
      }
      return true;
    }

    do {
      let style = window.getComputedStyle(node);
      if (
        kFixed.has(style.position) ||
        kFixed.has(style.overflow) ||
        kFixed.has(style.overflowX) ||
        kFixed.has(style.overflowY)
      ) {
        return true;
      }
      node = node.parentNode;
    } while (node && node != document.documentElement);

    return false;
  },

  /**
   * Read and store the rectangles that encompass the entire region of a range
   * for use by the drawing function of the highlighter.
   *
   * @param  {Range}  range  Range to fetch the rectangles from
   * @param  {Object} [dict] Dictionary of properties belonging to
   *                         the currently active window
   * @return {Set}    Set of rects that were found for the range
   */
  _getRangeRectsAndTexts(range, dict = null) {
    let window = range.startContainer.ownerGlobal;
    let bounds;
    // If the window is part of a frameset, try to cache the bounds query.
    if (dict && dict.frames.has(window)) {
      let frameData = dict.frames.get(window);
      bounds = frameData.bounds;
      if (!bounds) {
        bounds = frameData.bounds = this._getRootBounds(window);
      }
    } else {
      bounds = this._getRootBounds(window);
    }

    let topBounds = this._getRootBounds(this.getTopWindow(window, true), false);
    let rects = [];
    // A range may consist of multiple rectangles, we can also do these kind of
    // precise cut-outs. range.getBoundingClientRect() returns the fully
    // encompassing rectangle, which is too much for our purpose here.
    let { rectList, textList } = range.getClientRectsAndTexts();
    for (let rect of rectList) {
      rect = lazy.Rect.fromRect(rect);
      rect.x += bounds.x;
      rect.y += bounds.y;
      // If the rect is not even visible from the top document, we can ignore it.
      if (rect.intersects(topBounds)) {
        rects.push(rect);
      }
    }
    return { rectList: rects, textList };
  },

  /**
   * Read and store the rectangles that encompass the entire region of a range
   * for use by the drawing function of the highlighter and store them in the
   * cache.
   *
   * @param  {Range}   range            Range to fetch the rectangles from
   * @param  {Boolean} [checkIfDynamic] Whether we should check if the range
   *                                    is dynamic as per the rules in
   *                                    `_isInDynamicContainer()`. Optional,
   *                                    defaults to `true`
   * @param  {Object}  [dict]           Dictionary of properties belonging to
   *                                    the currently active window
   * @return {Set}     Set of rects that were found for the range
   */
  _updateRangeRects(range, checkIfDynamic = true, dict = null) {
    let window = range.startContainer.ownerGlobal;
    let rectsAndTexts = this._getRangeRectsAndTexts(range, dict);

    // Only fetch the rect at this point, if not passed in as argument.
    dict = dict || this.getForWindow(this.getTopWindow(window));
    let oldRectsAndTexts = dict.modalHighlightRectsMap.get(range);
    dict.modalHighlightRectsMap.set(range, rectsAndTexts);
    // Check here if we suddenly went down to zero rects from more than zero before,
    // which indicates that we should re-iterate the document.
    if (
      oldRectsAndTexts &&
      oldRectsAndTexts.rectList.length &&
      !rectsAndTexts.rectList.length
    ) {
      dict.detectedGeometryChange = true;
    }
    if (checkIfDynamic && this._isInDynamicContainer(range)) {
      dict.dynamicRangesSet.add(range);
    }
    return rectsAndTexts;
  },

  /**
   * Re-read the rectangles of the ranges that we keep track of separately,
   * because they're enclosed by a position: fixed container DOM node or (i)frame.
   *
   * @param {Object} dict Dictionary of properties belonging to the currently
   *                      active window
   */
  _updateDynamicRangesRects(dict) {
    // Reset the frame bounds cache.
    for (let frameData of dict.frames.values()) {
      frameData.bounds = null;
    }
    for (let range of dict.dynamicRangesSet) {
      this._updateRangeRects(range, false, dict);
    }
  },

  /**
   * Update the content, position and style of the yellow current found range
   * outline that floats atop the mask with the dimmed background.
   * Rebuild it, if necessary, This will deactivate the animation between
   * occurrences.
   *
   * @param {Object} dict Dictionary of properties belonging to the currently
   *                      active window
   */
  _updateRangeOutline(dict) {
    let range = dict.currentFoundRange;
    if (!range) {
      return;
    }

    let fontStyle = this._getRangeFontStyle(range);
    // Text color in the outline is determined by kModalStyles.
    delete fontStyle.color;

    let rectsAndTexts = this._updateRangeRects(range, true, dict);
    let outlineAnonNode = dict.modalHighlightOutline;
    let rectCount = rectsAndTexts.rectList.length;
    let previousRectCount = dict.previousRangeRectsAndTexts.rectList.length;
    // (re-)Building the outline is conditional and happens when one of the
    // following conditions is met:
    // 1. No outline nodes were built before, or
    // 2. When the amount of rectangles to draw is different from before, or
    // 3. When there's more than one rectangle to draw, because it's impossible
    //    to animate that consistently with AnonymousContent nodes.
    let rebuildOutline =
      !outlineAnonNode || rectCount !== previousRectCount || rectCount != 1;
    dict.previousRangeRectsAndTexts = rectsAndTexts;

    let window = this.getTopWindow(range.startContainer.ownerGlobal);
    let document = window.document;
    // First see if we need to and can remove the previous outline nodes.
    if (rebuildOutline) {
      this._removeRangeOutline(window);
    }

    // Abort when there's no text to highlight OR when it's the exact same range
    // as the previous call and isn't inside a dynamic container.
    if (
      !rectsAndTexts.textList.length ||
      (!rebuildOutline &&
        dict.previousUpdatedRange == range &&
        !dict.dynamicRangesSet.has(range))
    ) {
      return;
    }

    let outlineBox;
    if (rebuildOutline) {
      // Create the main (yellow) highlight outline box.
      outlineBox = document.createElementNS(kNSHTML, "div");
      outlineBox.setAttribute("id", kModalOutlineId);
    }

    const kModalOutlineTextId = kModalOutlineId + "-text";
    let i = 0;
    for (let rect of rectsAndTexts.rectList) {
      let text = rectsAndTexts.textList[i];

      // Next up is to check of the outline box' borders will not overlap with
      // rects that we drew before or will draw after this one.
      // We're taking the width of the border into account, which is
      // `kOutlineBoxBorderSize` pixels.
      // When left and/ or right sides will overlap with the current, previous
      // or next rect, make sure to make the necessary adjustments to the style.
      // These adjustments will override the styles as defined in `kModalStyles.outlineNode`.
      let intersectingSides = new Set();
      let previous = rectsAndTexts.rectList[i - 1];
      if (previous && rect.left - previous.right <= 2 * kOutlineBoxBorderSize) {
        intersectingSides.add("left");
      }
      let next = rectsAndTexts.rectList[i + 1];
      if (next && next.left - rect.right <= 2 * kOutlineBoxBorderSize) {
        intersectingSides.add("right");
      }
      let borderStyles = [...intersectingSides].map(side => [
        "border-" + side,
        0,
      ]);
      if (intersectingSides.size) {
        borderStyles.push([
          "margin",
          `-${kOutlineBoxBorderSize}px 0 0 ${
            intersectingSides.has("left") ? 0 : -kOutlineBoxBorderSize
          }px !important`,
        ]);
        borderStyles.push([
          "border-radius",
          (intersectingSides.has("left") ? 0 : kOutlineBoxBorderRadius) +
            "px " +
            (intersectingSides.has("right") ? 0 : kOutlineBoxBorderRadius) +
            "px " +
            (intersectingSides.has("right") ? 0 : kOutlineBoxBorderRadius) +
            "px " +
            (intersectingSides.has("left") ? 0 : kOutlineBoxBorderRadius) +
            "px",
        ]);
      }

      let outlineStyle = this._getStyleString(
        kModalStyles.outlineNode,
        [
          ["top", rect.top + "px"],
          ["left", rect.left + "px"],
          ["height", rect.height + "px"],
          ["width", rect.width + "px"],
        ],
        borderStyles,
        lazy.kDebug ? kModalStyles.outlineNodeDebug : []
      );
      fontStyle.lineHeight = rect.height + "px";
      let textStyle =
        this._getStyleString(kModalStyles.outlineText) +
        "; " +
        this._getHTMLFontStyle(fontStyle);

      if (rebuildOutline) {
        let textBoxParent = outlineBox.appendChild(
          document.createElementNS(kNSHTML, "div")
        );
        textBoxParent.setAttribute("id", kModalOutlineId + i);
        textBoxParent.setAttribute("style", outlineStyle);

        let textBox = document.createElementNS(kNSHTML, "span");
        textBox.setAttribute("id", kModalOutlineTextId + i);
        textBox.setAttribute("style", textStyle);
        textBox.textContent = text;
        textBoxParent.appendChild(textBox);
      } else {
        // Set the appropriate properties on the existing nodes, which will also
        // activate the transitions.
        outlineAnonNode.setAttributeForElement(
          kModalOutlineId + i,
          "style",
          outlineStyle
        );
        outlineAnonNode.setAttributeForElement(
          kModalOutlineTextId + i,
          "style",
          textStyle
        );
        outlineAnonNode.setTextContentForElement(kModalOutlineTextId + i, text);
      }

      ++i;
    }

    if (rebuildOutline) {
      dict.modalHighlightOutline = lazy.kDebug
        ? mockAnonymousContentNode(
            (document.body || document.documentElement).appendChild(outlineBox)
          )
        : document.insertAnonymousContent(outlineBox);
    }

    if (dict.animateOutline && !this._isPageTooBig(dict)) {
      let animation;
      dict.animations = new Set();
      for (let i = rectsAndTexts.rectList.length - 1; i >= 0; --i) {
        animation = dict.modalHighlightOutline.setAnimationForElement(
          kModalOutlineId + i,
          Cu.cloneInto(kModalOutlineAnim.keyframes, window),
          kModalOutlineAnim.duration
        );
        animation.onfinish = function () {
          dict.animations.delete(this);
        };
        dict.animations.add(animation);
      }
    }
    dict.animateOutline = false;
    dict.ignoreNextContentChange = true;

    dict.previousUpdatedRange = range;
  },

  /**
   * Finish any currently playing animations on the found range outline node.
   *
   * @param {Object} dict Dictionary of properties belonging to the currently
   *                      active window
   */
  _finishOutlineAnimations(dict) {
    if (!dict.animations) {
      return;
    }
    for (let animation of dict.animations) {
      animation.finish();
    }
  },

  /**
   * Safely remove the outline AnoymousContent node from the CanvasFrame.
   *
   * @param {nsIDOMWindow} window
   */
  _removeRangeOutline(window) {
    let dict = this.getForWindow(window);
    if (!dict.modalHighlightOutline) {
      return;
    }

    if (lazy.kDebug) {
      dict.modalHighlightOutline.remove();
    } else {
      try {
        window.document.removeAnonymousContent(dict.modalHighlightOutline);
      } catch (ex) {}
    }

    dict.modalHighlightOutline = null;
  },

  /**
   * Add a range to the list of ranges to highlight on, or cut out of, the dimmed
   * background.
   *
   * @param {Range}        range  Range object that should be inspected
   * @param {nsIDOMWindow} window Window object, whose DOM tree is being traversed
   */
  _modalHighlight(range, controller, window) {
    this._updateRangeRects(range);

    this.show(window);
    // We don't repaint the mask right away, but pass it off to a render loop of
    // sorts.
    this._scheduleRepaintOfMask(window);
  },

  /**
   * Lazily insert the nodes we need as anonymous content into the CanvasFrame
   * of a window.
   *
   * @param {nsIDOMWindow} window Window to draw in.
   */
  _maybeCreateModalHighlightNodes(window) {
    window = this.getTopWindow(window);
    let dict = this.getForWindow(window);
    if (dict.modalHighlightOutline) {
      if (!dict.modalHighlightAllMask) {
        // Make sure to at least show the dimmed background.
        this._repaintHighlightAllMask(window, false);
        this._scheduleRepaintOfMask(window);
      } else {
        this._scheduleRepaintOfMask(window, { contentChanged: true });
      }
      return;
    }

    let document = window.document;
    // A hidden document doesn't accept insertAnonymousContent calls yet.
    if (document.hidden) {
      let onVisibilityChange = () => {
        document.removeEventListener("visibilitychange", onVisibilityChange);
        this._maybeCreateModalHighlightNodes(window);
      };
      document.addEventListener("visibilitychange", onVisibilityChange);
      return;
    }

    // Make sure to at least show the dimmed background.
    this._repaintHighlightAllMask(window, false);
  },

  /**
   * Build and draw the mask that takes care of the dimmed background that
   * overlays the current page and the mask that cuts out all the rectangles of
   * the ranges that were found.
   *
   * @param {nsIDOMWindow} window Window to draw in.
   * @param {Boolean} [paintContent]
   */
  _repaintHighlightAllMask(window, paintContent = true) {
    window = this.getTopWindow(window);
    let dict = this.getForWindow(window);

    const kMaskId = kModalIdPrefix + "-findbar-modalHighlight-outlineMask";
    if (!dict.modalHighlightAllMask) {
      let document = window.document;
      let maskNode = document.createElementNS(kNSHTML, "div");
      maskNode.setAttribute("id", kMaskId);
      dict.modalHighlightAllMask = lazy.kDebug
        ? mockAnonymousContentNode(
            (document.body || document.documentElement).appendChild(maskNode)
          )
        : document.insertAnonymousContent(maskNode);
    }

    // Make sure the dimmed mask node takes the full width and height that's available.
    let { width, height } = (dict.lastWindowDimensions =
      this._getWindowDimensions(window));
    if (typeof dict.brightText != "boolean" || dict.updateAllRanges) {
      this._detectBrightText(dict);
    }
    let maskStyle = this._getStyleString(
      kModalStyles.maskNode,
      [
        ["width", width + "px"],
        ["height", height + "px"],
      ],
      dict.brightText ? kModalStyles.maskNodeBrightText : [],
      paintContent ? kModalStyles.maskNodeTransition : [],
      lazy.kDebug ? kModalStyles.maskNodeDebug : []
    );
    dict.modalHighlightAllMask.setAttributeForElement(
      kMaskId,
      "style",
      maskStyle
    );

    this._updateRangeOutline(dict);

    let allRects = [];
    // When the user's busy scrolling the document, don't bother cutting out rectangles,
    // because they're not going to keep up with scrolling speed anyway.
    if (!dict.busyScrolling && (paintContent || dict.modalHighlightAllMask)) {
      // No need to update dynamic ranges separately when we already about to
      // update all of them anyway.
      if (!dict.updateAllRanges) {
        this._updateDynamicRangesRects(dict);
      }

      let DOMRect = window.DOMRect;
      for (let [range, rectsAndTexts] of dict.modalHighlightRectsMap) {
        if (!this.finder._fastFind.isRangeVisible(range, false)) {
          continue;
        }

        if (dict.updateAllRanges) {
          rectsAndTexts = this._updateRangeRects(range);
        }

        // If a geometry change was detected, we bail out right away here, because
        // the current set of ranges has been invalidated.
        if (dict.detectedGeometryChange) {
          return;
        }

        for (let rect of rectsAndTexts.rectList) {
          allRects.push(new DOMRect(rect.x, rect.y, rect.width, rect.height));
        }
      }
      dict.updateAllRanges = false;
    }

    // We may also want to cut out zero rects, which effectively clears out the mask.
    dict.modalHighlightAllMask.setCutoutRectsForElement(kMaskId, allRects);

    // The reflow observer may ignore the reflow we cause ourselves here.
    dict.ignoreNextContentChange = true;
  },

  /**
   * Safely remove the mask AnoymousContent node from the CanvasFrame.
   *
   * @param {nsIDOMWindow} window
   */
  _removeHighlightAllMask(window) {
    window = this.getTopWindow(window);
    let dict = this.getForWindow(window);
    if (!dict.modalHighlightAllMask) {
      return;
    }

    // If the current window isn't the one the content was inserted into, this
    // will fail, but that's fine.
    if (lazy.kDebug) {
      dict.modalHighlightAllMask.remove();
    } else {
      try {
        window.document.removeAnonymousContent(dict.modalHighlightAllMask);
      } catch (ex) {}
    }
    dict.modalHighlightAllMask = null;
  },

  /**
   * Check if the width or height of the current document is too big to handle
   * for certain operations. This allows us to degrade gracefully when we expect
   * the performance to be negatively impacted due to drawing-intensive operations.
   *
   * @param  {Object} dict Dictionary of properties belonging to the currently
   *                       active window
   * @return {Boolean}
   */
  _isPageTooBig(dict) {
    let { height, width } = dict.lastWindowDimensions;
    return height >= kPageIsTooBigPx || width >= kPageIsTooBigPx;
  },

  /**
   * Doing a full repaint each time a range is delivered by the highlight iterator
   * is way too costly, thus we pipe the frequency down to every
   * `kModalHighlightRepaintLoFreqMs` milliseconds. If there are dynamic ranges
   * found (see `_isInDynamicContainer()` for the definition), the frequency
   * will be upscaled to `kModalHighlightRepaintHiFreqMs`.
   *
   * @param {nsIDOMWindow} window
   * @param {Object}       options Dictionary of painter hints that contains the
   *                               following properties:
   *   {Boolean} contentChanged  Whether the documents' content changed in the
   *                             meantime. This happens when the DOM is updated
   *                             whilst the page is loaded.
   *   {Boolean} scrollOnly      TRUE when the page has scrolled in the meantime,
   *                             which means that the dynamically positioned
   *                             elements need to be repainted.
   *   {Boolean} updateAllRanges Whether to recalculate the rects of all ranges
   *                             that were found up until now.
   */
  _scheduleRepaintOfMask(
    window,
    { contentChanged = false, scrollOnly = false, updateAllRanges = false } = {}
  ) {
    if (!this.useModal()) {
      return;
    }

    window = this.getTopWindow(window);
    let dict = this.getForWindow(window);
    // Bail out early if the repaint scheduler is paused or when we're supposed
    // to ignore the next paint (i.e. content change).
    if (
      dict.repaintSchedulerState == kRepaintSchedulerPaused ||
      (contentChanged && dict.ignoreNextContentChange)
    ) {
      dict.ignoreNextContentChange = false;
      return;
    }

    let hasDynamicRanges = !!dict.dynamicRangesSet.size;
    let pageIsTooBig = this._isPageTooBig(dict);
    let repaintDynamicRanges =
      (scrollOnly || contentChanged) && hasDynamicRanges && !pageIsTooBig;

    // Determine scroll behavior and keep that state around.
    let startedScrolling = !dict.busyScrolling && scrollOnly;
    // When the user started scrolling the document, hide the other highlights.
    if (startedScrolling) {
      dict.busyScrolling = startedScrolling;
      this._repaintHighlightAllMask(window);
    }
    // Whilst scrolling, suspend the repaint scheduler, but only when the page is
    // too big or the find results contains ranges that are inside dynamic
    // containers.
    if (dict.busyScrolling && (pageIsTooBig || hasDynamicRanges)) {
      dict.ignoreNextContentChange = true;
      this._updateRangeOutline(dict);
      // NB: we're not using `kRepaintSchedulerPaused` on purpose here, otherwise
      // we'd break the `busyScrolling` detection (re-)using the timer.
      if (dict.modalRepaintScheduler) {
        window.clearTimeout(dict.modalRepaintScheduler);
        dict.modalRepaintScheduler = null;
      }
    }

    // When we request to repaint unconditionally, we mean to call
    // `_repaintHighlightAllMask()` right after the timeout.
    if (!dict.unconditionalRepaintRequested) {
      dict.unconditionalRepaintRequested =
        !contentChanged || repaintDynamicRanges;
    }
    // Some events, like a resize, call for recalculation of all the rects of all ranges.
    if (!dict.updateAllRanges) {
      dict.updateAllRanges = updateAllRanges;
    }

    if (dict.modalRepaintScheduler) {
      return;
    }

    let timeoutMs =
      hasDynamicRanges && !dict.busyScrolling
        ? kModalHighlightRepaintHiFreqMs
        : kModalHighlightRepaintLoFreqMs;
    dict.modalRepaintScheduler = window.setTimeout(() => {
      dict.modalRepaintScheduler = null;
      dict.repaintSchedulerState = kRepaintSchedulerStopped;
      dict.busyScrolling = false;

      let pageContentChanged = dict.detectedGeometryChange;
      if (!pageContentChanged && !pageIsTooBig) {
        let { width: previousWidth, height: previousHeight } =
          dict.lastWindowDimensions;
        let { width, height } = (dict.lastWindowDimensions =
          this._getWindowDimensions(window));
        pageContentChanged =
          dict.detectedGeometryChange ||
          Math.abs(previousWidth - width) > kContentChangeThresholdPx ||
          Math.abs(previousHeight - height) > kContentChangeThresholdPx;
      }
      dict.detectedGeometryChange = false;
      // When the page has changed significantly enough in size, we'll restart
      // the iterator with the same parameters as before to find us new ranges.
      if (pageContentChanged && !pageIsTooBig) {
        this.iterator.restart(this.finder);
      }

      if (
        dict.unconditionalRepaintRequested ||
        (dict.modalHighlightRectsMap.size && pageContentChanged)
      ) {
        dict.unconditionalRepaintRequested = false;
        this._repaintHighlightAllMask(window);
      }
    }, timeoutMs);
    dict.repaintSchedulerState = kRepaintSchedulerRunning;
  },

  /**
   * Add event listeners to the content which will cause the modal highlight
   * AnonymousContent to be re-painted or hidden.
   *
   * @param {nsIDOMWindow} window
   */
  _addModalHighlightListeners(window) {
    window = this.getTopWindow(window);
    let dict = this.getForWindow(window);
    if (dict.highlightListeners) {
      return;
    }

    dict.highlightListeners = [
      this._scheduleRepaintOfMask.bind(this, window, { contentChanged: true }),
      this._scheduleRepaintOfMask.bind(this, window, { updateAllRanges: true }),
      this._scheduleRepaintOfMask.bind(this, window, { scrollOnly: true }),
      this.hide.bind(this, window, null),
      () => (dict.busySelecting = true),
      () => {
        if (window.document.hidden) {
          dict.repaintSchedulerState = kRepaintSchedulerPaused;
        } else if (dict.repaintSchedulerState == kRepaintSchedulerPaused) {
          dict.repaintSchedulerState = kRepaintSchedulerRunning;
          this._scheduleRepaintOfMask(window);
        }
      },
    ];
    let target = this.iterator._getDocShell(window).chromeEventHandler;
    target.addEventListener("MozAfterPaint", dict.highlightListeners[0]);
    target.addEventListener("resize", dict.highlightListeners[1]);
    target.addEventListener("scroll", dict.highlightListeners[2], {
      capture: true,
      passive: true,
    });
    target.addEventListener("click", dict.highlightListeners[3]);
    target.addEventListener("selectstart", dict.highlightListeners[4]);
    window.document.addEventListener(
      "visibilitychange",
      dict.highlightListeners[5]
    );
  },

  /**
   * Remove event listeners from content.
   *
   * @param {nsIDOMWindow} window
   */
  _removeModalHighlightListeners(window) {
    window = this.getTopWindow(window);
    let dict = this.getForWindow(window);
    if (!dict.highlightListeners) {
      return;
    }

    let target = this.iterator._getDocShell(window).chromeEventHandler;
    target.removeEventListener("MozAfterPaint", dict.highlightListeners[0]);
    target.removeEventListener("resize", dict.highlightListeners[1]);
    target.removeEventListener("scroll", dict.highlightListeners[2], {
      capture: true,
      passive: true,
    });
    target.removeEventListener("click", dict.highlightListeners[3]);
    target.removeEventListener("selectstart", dict.highlightListeners[4]);
    window.document.removeEventListener(
      "visibilitychange",
      dict.highlightListeners[5]
    );

    dict.highlightListeners = null;
  },

  /**
   * For a given node returns its editable parent or null if there is none.
   * It's enough to check if node is a text node and its parent's parent is
   * an input or textarea.
   *
   * @param node the node we want to check
   * @returns the first node in the parent chain that is editable,
   *          null if there is no such node
   */
  _getEditableNode(node) {
    if (
      node.nodeType === node.TEXT_NODE &&
      node.parentNode &&
      node.parentNode.parentNode &&
      (ChromeUtils.getClassName(node.parentNode.parentNode) ===
        "HTMLInputElement" ||
        ChromeUtils.getClassName(node.parentNode.parentNode) ===
          "HTMLTextAreaElement")
    ) {
      return node.parentNode.parentNode;
    }
    return null;
  },

  /**
   * Add ourselves as an nsIEditActionListener and nsIDocumentStateListener for
   * a given editor
   *
   * @param editor the editor we'd like to listen to
   */
  _addEditorListeners(editor) {
    if (!this._editors) {
      this._editors = [];
      this._stateListeners = [];
    }

    let existingIndex = this._editors.indexOf(editor);
    if (existingIndex == -1) {
      let x = this._editors.length;
      this._editors[x] = editor;
      this._stateListeners[x] = this._createStateListener();
      this._editors[x].addEditActionListener(this);
      this._editors[x].addDocumentStateListener(this._stateListeners[x]);
    }
  },

  /**
   * Helper method to unhook listeners, remove cached editors
   * and keep the relevant arrays in sync
   *
   * @param idx the index into the array of editors/state listeners
   *        we wish to remove
   */
  _unhookListenersAtIndex(idx) {
    this._editors[idx].removeEditActionListener(this);
    this._editors[idx].removeDocumentStateListener(this._stateListeners[idx]);
    this._editors.splice(idx, 1);
    this._stateListeners.splice(idx, 1);
    if (!this._editors.length) {
      delete this._editors;
      delete this._stateListeners;
    }
  },

  /**
   * Remove ourselves as an nsIEditActionListener and
   * nsIDocumentStateListener from a given cached editor
   *
   * @param editor the editor we no longer wish to listen to
   */
  _removeEditorListeners(editor) {
    // editor is an editor that we listen to, so therefore must be
    // cached. Find the index of this editor
    let idx = this._editors.indexOf(editor);
    if (idx == -1) {
      return;
    }
    // Now unhook ourselves, and remove our cached copy
    this._unhookListenersAtIndex(idx);
  },

  /*
   * nsIEditActionListener logic follows
   *
   * We implement this interface to allow us to catch the case where
   * the findbar found a match in a HTML <input> or <textarea>. If the
   * user adjusts the text in some way, it will no longer match, so we
   * want to remove the highlight, rather than have it expand/contract
   * when letters are added or removed.
   */

  /**
   * Helper method used to check whether a selection intersects with
   * some highlighting
   *
   * @param selectionRange the range from the selection to check
   * @param findRange the highlighted range to check against
   * @returns true if they intersect, false otherwise
   */
  _checkOverlap(selectionRange, findRange) {
    if (!selectionRange || !findRange) {
      return false;
    }
    // The ranges overlap if one of the following is true:
    // 1) At least one of the endpoints of the deleted selection
    //    is in the find selection
    // 2) At least one of the endpoints of the find selection
    //    is in the deleted selection
    if (
      findRange.isPointInRange(
        selectionRange.startContainer,
        selectionRange.startOffset
      )
    ) {
      return true;
    }
    if (
      findRange.isPointInRange(
        selectionRange.endContainer,
        selectionRange.endOffset
      )
    ) {
      return true;
    }
    if (
      selectionRange.isPointInRange(
        findRange.startContainer,
        findRange.startOffset
      )
    ) {
      return true;
    }
    if (
      selectionRange.isPointInRange(findRange.endContainer, findRange.endOffset)
    ) {
      return true;
    }

    return false;
  },

  /**
   * Helper method to determine if an edit occurred within a highlight
   *
   * @param selection the selection we wish to check
   * @param node the node we want to check is contained in selection
   * @param offset the offset into node that we want to check
   * @returns the range containing (node, offset) or null if no ranges
   *          in the selection contain it
   */
  _findRange(selection, node, offset) {
    let rangeCount = selection.rangeCount;
    let rangeidx = 0;
    let foundContainingRange = false;
    let range = null;

    // Check to see if this node is inside one of the selection's ranges
    while (!foundContainingRange && rangeidx < rangeCount) {
      range = selection.getRangeAt(rangeidx);
      if (range.isPointInRange(node, offset)) {
        foundContainingRange = true;
        break;
      }
      rangeidx++;
    }

    if (foundContainingRange) {
      return range;
    }

    return null;
  },

  // Start of nsIEditActionListener implementations

  WillDeleteText(textNode, offset) {
    let editor = this._getEditableNode(textNode).editor;
    let controller = editor.selectionController;
    let fSelection = controller.getSelection(
      Ci.nsISelectionController.SELECTION_FIND
    );
    let range = this._findRange(fSelection, textNode, offset);

    if (range) {
      // Don't remove the highlighting if the deleted text is at the
      // end of the range
      if (textNode != range.endContainer || offset != range.endOffset) {
        // Text within the highlight is being removed - the text can
        // no longer be a match, so remove the highlighting
        fSelection.removeRange(range);
        if (fSelection.rangeCount == 0) {
          this._removeEditorListeners(editor);
        }
      }
    }
  },

  DidInsertText(textNode, offset, aString) {
    let editor = this._getEditableNode(textNode).editor;
    let controller = editor.selectionController;
    let fSelection = controller.getSelection(
      Ci.nsISelectionController.SELECTION_FIND
    );
    let range = this._findRange(fSelection, textNode, offset);

    if (range) {
      // If the text was inserted before the highlight
      // adjust the highlight's bounds accordingly
      if (textNode == range.startContainer && offset == range.startOffset) {
        range.setStart(
          range.startContainer,
          range.startOffset + aString.length
        );
      } else if (textNode != range.endContainer || offset != range.endOffset) {
        // The edit occurred within the highlight - any addition of text
        // will result in the text no longer being a match,
        // so remove the highlighting
        fSelection.removeRange(range);
        if (fSelection.rangeCount == 0) {
          this._removeEditorListeners(editor);
        }
      }
    }
  },

  WillDeleteRanges(rangesToDelete) {
    let { editor } = this._getEditableNode(rangesToDelete[0].startContainer);
    let controller = editor.selectionController;
    let fSelection = controller.getSelection(
      Ci.nsISelectionController.SELECTION_FIND
    );

    let shouldDelete = {};
    let numberOfDeletedSelections = 0;
    let numberOfMatches = fSelection.rangeCount;

    // We need to test if any ranges to be deleted
    // are in any of the ranges of the find selection
    // Usually both selections will only contain one range, however
    // either may contain more than one.

    for (let fIndex = 0; fIndex < numberOfMatches; fIndex++) {
      shouldDelete[fIndex] = false;
      let fRange = fSelection.getRangeAt(fIndex);

      for (let selRange of rangesToDelete) {
        if (shouldDelete[fIndex]) {
          continue;
        }

        let doesOverlap = this._checkOverlap(selRange, fRange);
        if (doesOverlap) {
          shouldDelete[fIndex] = true;
          numberOfDeletedSelections++;
        }
      }
    }

    // OK, so now we know what matches (if any) are in the selection
    // that is being deleted. Time to remove them.
    if (!numberOfDeletedSelections) {
      return;
    }

    for (let i = numberOfMatches - 1; i >= 0; i--) {
      if (shouldDelete[i]) {
        fSelection.removeRange(fSelection.getRangeAt(i));
      }
    }

    // Remove listeners if no more highlights left
    if (!fSelection.rangeCount) {
      this._removeEditorListeners(editor);
    }
  },

  /*
   * nsIDocumentStateListener logic follows
   *
   * When attaching nsIEditActionListeners, there are no guarantees
   * as to whether the findbar or the documents in the browser will get
   * destructed first. This leads to the potential to either leak, or to
   * hold on to a reference an editable element's editor for too long,
   * preventing it from being destructed.
   *
   * However, when an editor's owning node is being destroyed, the editor
   * sends out a DocumentWillBeDestroyed notification. We can use this to
   * clean up our references to the object, to allow it to be destroyed in a
   * timely fashion.
   */

  /**
   * Unhook ourselves when one of our state listeners has been called.
   * This can happen in 4 cases:
   *  1) The document the editor belongs to is navigated away from, and
   *     the document is not being cached
   *
   *  2) The document the editor belongs to is expired from the cache
   *
   *  3) The tab containing the owning document is closed
   *
   *  4) The <input> or <textarea> that owns the editor is explicitly
   *     removed from the DOM
   *
   * @param the listener that was invoked
   */
  _onEditorDestruction(aListener) {
    // First find the index of the editor the given listener listens to.
    // The listeners and editors arrays must always be in sync.
    // The listener will be in our array of cached listeners, as this
    // method could not have been called otherwise.
    let idx = 0;
    while (this._stateListeners[idx] != aListener) {
      idx++;
    }

    // Unhook both listeners
    this._unhookListenersAtIndex(idx);
  },

  /**
   * Creates a unique document state listener for an editor.
   *
   * It is not possible to simply have the findbar implement the
   * listener interface itself, as it wouldn't have sufficient information
   * to work out which editor was being destroyed. Therefore, we create new
   * listeners on the fly, and cache them in sync with the editors they
   * listen to.
   */
  _createStateListener() {
    return {
      findbar: this,

      QueryInterface: ChromeUtils.generateQI(["nsIDocumentStateListener"]),

      NotifyDocumentWillBeDestroyed() {
        this.findbar._onEditorDestruction(this);
      },

      // Unimplemented
      notifyDocumentStateChanged() {},
    };
  },
};
PK
!<��SOgOgmodules/FinderIterator.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  NLP: "resource://gre/modules/NLP.sys.mjs",
  Rect: "resource://gre/modules/Geometry.sys.mjs",
});

const kDebug = false;
const kIterationSizeMax = 100;
const kTimeoutPref = "findbar.iteratorTimeout";

/**
 * FinderIterator. See the documentation for the `start()` method to
 * learn more.
 */
export class FinderIterator {
  constructor() {
    this._listeners = new Map();
    this._currentParams = null;
    this._catchingUp = new Set();
    this._previousParams = null;
    this._previousRanges = [];
    this._spawnId = 0;
    this._timer = null;
    this.ranges = [];
    this.running = false;
    this.useSubFrames = false;
  }

  _timeout = Services.prefs.getIntPref(kTimeoutPref);

  // Expose `kIterationSizeMax` to the outside world for unit tests to use.
  get kIterationSizeMax() {
    return kIterationSizeMax;
  }

  get params() {
    if (!this._currentParams && !this._previousParams) {
      return null;
    }
    return Object.assign({}, this._currentParams || this._previousParams);
  }

  /**
   * Start iterating the active Finder docShell, using the options below. When
   * it already started at the request of another consumer, we first yield the
   * results we already collected before continuing onward to yield fresh results.
   * We make sure to pause every `kIterationSizeMax` iterations to make sure we
   * don't block the host process too long. In the case of a break like this, we
   * yield `undefined`, instead of a range.
   * Upon re-entrance after a break, we check if `stop()` was called during the
   * break and if so, we stop iterating.
   * Results are also passed to the `listener.onIteratorRangeFound` callback
   * method, along with a flag that specifies if the result comes from the cache
   * or is fresh. The callback also adheres to the `limit` flag.
   * The returned promise is resolved when 1) the limit is reached, 2) when all
   * the ranges have been found or 3) when `stop()` is called whilst iterating.
   *
   * @param {Number}  [options.allowDistance] Allowed edit distance between the
   *                                          current word and `options.word`
   *                                          when the iterator is already running
   * @param {Boolean} options.caseSensitive   Whether to search in case sensitive
   *                                          mode
   * @param {Boolean} options.entireWord      Whether to search in entire-word mode
   * @param {Finder}  options.finder          Currently active Finder instance
   * @param {Number}  [options.limit]         Limit the amount of results to be
   *                                          passed back. Optional, defaults to no
   *                                          limit.
   * @param {Boolean} [options.linksOnly]     Only yield ranges that are inside a
   *                                          hyperlink (used by QuickFind).
   *                                          Optional, defaults to `false`.
   * @param {Object}  options.listener        Listener object that implements the
   *                                          following callback functions:
   *                                           - onIteratorRangeFound({Range} range);
   *                                           - onIteratorReset();
   *                                           - onIteratorRestart({Object} iterParams);
   *                                           - onIteratorStart({Object} iterParams);
   * @param {Boolean} options.matchDiacritics Whether to search in
   *                                          diacritic-matching mode
   * @param {Boolean} [options.useCache]        Whether to allow results already
   *                                            present in the cache or demand fresh.
   *                                            Optional, defaults to `false`.
   * @param {Boolean} [options.useSubFrames]    Whether to iterate over subframes.
   *                                            Optional, defaults to `false`.
   * @param {String}  options.word              Word to search for
   * @return {Promise}
   */
  start({
    allowDistance,
    caseSensitive,
    entireWord,
    finder,
    limit,
    linksOnly,
    listener,
    matchDiacritics,
    useCache,
    word,
    useSubFrames,
  }) {
    // Take care of default values for non-required options.
    if (typeof allowDistance != "number") {
      allowDistance = 0;
    }
    if (typeof limit != "number") {
      limit = -1;
    }
    if (typeof linksOnly != "boolean") {
      linksOnly = false;
    }
    if (typeof useCache != "boolean") {
      useCache = false;
    }
    if (typeof useSubFrames != "boolean") {
      useSubFrames = false;
    }

    // Validate the options.
    if (typeof caseSensitive != "boolean") {
      throw new Error("Missing required option 'caseSensitive'");
    }
    if (typeof entireWord != "boolean") {
      throw new Error("Missing required option 'entireWord'");
    }
    if (typeof matchDiacritics != "boolean") {
      throw new Error("Missing required option 'matchDiacritics'");
    }
    if (!finder) {
      throw new Error("Missing required option 'finder'");
    }
    if (!word) {
      throw new Error("Missing required option 'word'");
    }
    if (typeof listener != "object" || !listener.onIteratorRangeFound) {
      throw new TypeError("Missing valid, required option 'listener'");
    }

    // If the listener was added before, make sure the promise is resolved before
    // we replace it with another.
    if (this._listeners.has(listener)) {
      let { onEnd } = this._listeners.get(listener);
      if (onEnd) {
        onEnd();
      }
    }

    let window = finder._getWindow();
    let resolver;
    let promise = new Promise(resolve => (resolver = resolve));
    let iterParams = {
      caseSensitive,
      entireWord,
      linksOnly,
      matchDiacritics,
      useCache,
      window,
      word,
      useSubFrames,
    };

    this._listeners.set(listener, { limit, onEnd: resolver });

    // If we're not running anymore and we're requesting the previous result, use it.
    if (!this.running && this._previousResultAvailable(iterParams)) {
      this._yieldPreviousResult(listener, window);
      return promise;
    }

    if (this.running) {
      // Double-check if we're not running the iterator with a different set of
      // parameters, otherwise report an error with the most common reason.
      if (
        !this._areParamsEqual(this._currentParams, iterParams, allowDistance)
      ) {
        if (kDebug) {
          console.error(
            `We're currently iterating over '${this._currentParams.word}', not '${word}'\n` +
              new Error().stack
          );
        }
        this._listeners.delete(listener);
        resolver();
        return promise;
      }

      // if we're still running, yield the set we have built up this far.
      this._yieldIntermediateResult(listener, window);

      return promise;
    }

    // Start!
    this.running = true;
    this._currentParams = iterParams;
    this._findAllRanges(finder, ++this._spawnId);

    return promise;
  }

  /**
   * Stop the currently running iterator as soon as possible and optionally cache
   * the result for later.
   *
   * @param {Boolean} [cachePrevious] Whether to save the result for later.
   *                                  Optional.
   */
  stop(cachePrevious = false) {
    if (!this.running) {
      return;
    }

    if (this._timer) {
      clearTimeout(this._timer);
      this._timer = null;
    }
    if (this._runningFindResolver) {
      this._runningFindResolver();
      this._runningFindResolver = null;
    }

    if (cachePrevious) {
      this._previousRanges = [].concat(this.ranges);
      this._previousParams = Object.assign({}, this._currentParams);
    } else {
      this._previousRanges = [];
      this._previousParams = null;
    }

    this._catchingUp.clear();
    this._currentParams = null;
    this.ranges = [];
    this.running = false;

    for (let [, { onEnd }] of this._listeners) {
      onEnd();
    }
  }

  /**
   * Stops the iteration that currently running, if it is, and start a new one
   * with the exact same params as before.
   *
   * @param {Finder} finder Currently active Finder instance
   */
  restart(finder) {
    // Capture current iterator params before we stop the show.
    let iterParams = this.params;
    if (!iterParams) {
      return;
    }
    this.stop();

    // Restart manually.
    this.running = true;
    this._currentParams = iterParams;

    this._findAllRanges(finder, ++this._spawnId);
    this._notifyListeners("restart", iterParams);
  }

  /**
   * Reset the internal state of the iterator. Typically this would be called
   * when the docShell is not active anymore, which makes the current and cached
   * previous result invalid.
   * If the iterator is running, it will be stopped as soon as possible.
   */
  reset() {
    if (this._timer) {
      clearTimeout(this._timer);
      this._timer = null;
    }
    if (this._runningFindResolver) {
      this._runningFindResolver();
      this._runningFindResolver = null;
    }

    this._catchingUp.clear();
    this._currentParams = this._previousParams = null;
    this._previousRanges = [];
    this.ranges = [];
    this.running = false;

    this._notifyListeners("reset");
    for (let [, { onEnd }] of this._listeners) {
      onEnd();
    }
    this._listeners.clear();
  }

  /**
   * Check if the currently running iterator parameters are the same as the ones
   * passed through the arguments. When `true`, we can keep it running as-is and
   * the consumer should stop the iterator when `false`.
   *
   * @param {Boolean}  options.caseSensitive Whether to search in case sensitive
   *                                         mode
   * @param {Boolean}  options.entireWord    Whether to search in entire-word mode
   * @param  {Boolean} options.linksOnly     Whether to search for the word to be
   *                                         present in links only
   * @param {Boolean}  options.matchDiacritics Whether to search in
   *                                           diacritic-matching mode
   * @param  {String}  options.word          The word being searched for
   * @param  (Boolean) options.useSubFrames  Whether to search subframes
   * @return {Boolean}
   */
  continueRunning({
    caseSensitive,
    entireWord,
    linksOnly,
    matchDiacritics,
    word,
    useSubFrames,
  }) {
    return (
      this.running &&
      this._currentParams.caseSensitive === caseSensitive &&
      this._currentParams.entireWord === entireWord &&
      this._currentParams.linksOnly === linksOnly &&
      this._currentParams.matchDiacritics === matchDiacritics &&
      this._currentParams.word == word &&
      this._currentParams.useSubFrames == useSubFrames
    );
  }

  /**
   * The default mode of operation of the iterator is to not accept duplicate
   * listeners, resolve the promise of the older listeners and replace it with
   * the new listener.
   * Consumers may opt-out of this behavior by using this check and not call
   * start().
   *
   * @param  {Object} paramSet Property bag with the same signature as you would
   *                           pass into `start()`
   * @return {Boolean}
   */
  isAlreadyRunning(paramSet) {
    return (
      this.running &&
      this._areParamsEqual(this._currentParams, paramSet) &&
      this._listeners.has(paramSet.listener)
    );
  }

  /**
   * Safely notify all registered listeners that an event has occurred.
   *
   * @param {String}   callback    Name of the callback to invoke
   * @param {mixed}    [params]    Optional argument that will be passed to the
   *                               callback
   * @param {Iterable} [listeners] Set of listeners to notify. Optional, defaults
   *                               to `this._listeners.keys()`.
   */
  _notifyListeners(callback, params, listeners = this._listeners.keys()) {
    callback =
      "onIterator" + callback.charAt(0).toUpperCase() + callback.substr(1);
    for (let listener of listeners) {
      try {
        listener[callback](params);
      } catch (ex) {
        console.error("FinderIterator Error: ", ex);
      }
    }
  }

  /**
   * Internal; check if an iteration request is available in the previous result
   * that we cached.
   *
   * @param  {Boolean} options.caseSensitive Whether to search in case sensitive
   *                                         mode
   * @param  {Boolean} options.entireWord    Whether to search in entire-word mode
   * @param  {Boolean} options.linksOnly     Whether to search for the word to be
   *                                         present in links only
   * @param  {Boolean} options.matchDiacritics Whether to search in
   *                                           diacritic-matching mode
   * @param  {Boolean} options.useCache      Whether the consumer wants to use the
   *                                         cached previous result at all
   * @param  {String}  options.word          The word being searched for
   * @return {Boolean}
   */
  _previousResultAvailable({
    caseSensitive,
    entireWord,
    linksOnly,
    matchDiacritics,
    useCache,
    word,
  }) {
    return !!(
      useCache &&
      this._areParamsEqual(this._previousParams, {
        caseSensitive,
        entireWord,
        linksOnly,
        matchDiacritics,
        word,
      }) &&
      this._previousRanges.length
    );
  }

  /**
   * Internal; compare if two sets of iterator parameters are equivalent.
   *
   * @param  {Object} paramSet1       First set of params (left hand side)
   * @param  {Object} paramSet2       Second set of params (right hand side)
   * @param  {Number} [allowDistance] Allowed edit distance between the two words.
   *                                  Optional, defaults to '0', which means 'no
   *                                  distance'.
   * @return {Boolean}
   */
  _areParamsEqual(paramSet1, paramSet2, allowDistance = 0) {
    return (
      !!paramSet1 &&
      !!paramSet2 &&
      paramSet1.caseSensitive === paramSet2.caseSensitive &&
      paramSet1.entireWord === paramSet2.entireWord &&
      paramSet1.linksOnly === paramSet2.linksOnly &&
      paramSet1.matchDiacritics === paramSet2.matchDiacritics &&
      paramSet1.window === paramSet2.window &&
      paramSet1.useSubFrames === paramSet2.useSubFrames &&
      lazy.NLP.levenshtein(paramSet1.word, paramSet2.word) <= allowDistance
    );
  }

  /**
   * Internal; iterate over a predefined set of ranges that have been collected
   * before.
   * Also here, we make sure to pause every `kIterationSizeMax` iterations to
   * make sure we don't block the host process too long. In the case of a break
   * like this, we yield `undefined`, instead of a range.
   *
   * @param {Object}       listener    Listener object
   * @param {Array}        rangeSource Set of ranges to iterate over
   * @param {nsIDOMWindow} window      The window object is only really used
   *                                   for access to `setTimeout`
   * @param {Boolean}      [withPause] Whether to pause after each `kIterationSizeMax`
   *                                   number of ranges yielded. Optional, defaults
   *                                   to `true`.
   * @yield {Range}
   */
  async _yieldResult(listener, rangeSource, window, withPause = true) {
    // We keep track of the number of iterations to allow a short pause between
    // every `kIterationSizeMax` number of iterations.
    let iterCount = 0;
    let { limit, onEnd } = this._listeners.get(listener);
    let ranges = rangeSource.slice(0, limit > -1 ? limit : undefined);
    for (let range of ranges) {
      try {
        range.startContainer;
      } catch (ex) {
        // Don't yield dead objects, so use the escape hatch.
        if (ex.message.includes("dead object")) {
          return;
        }
      }

      // Pass a flag that is `true` when we're returning the result from a
      // cached previous iteration.
      listener.onIteratorRangeFound(range, !this.running);
      await range;

      if (withPause && ++iterCount >= kIterationSizeMax) {
        iterCount = 0;
        // Make sure to save the current limit for later.
        this._listeners.set(listener, { limit, onEnd });
        // Sleep for the rest of this cycle.
        await new Promise(resolve => window.setTimeout(resolve, 0));
        // After a sleep, the set of ranges may have updated.
        ranges = rangeSource.slice(0, limit > -1 ? limit : undefined);
      }

      if (limit !== -1 && --limit === 0) {
        // We've reached our limit; no need to do more work.
        this._listeners.delete(listener);
        onEnd();
        return;
      }
    }

    // Save the updated limit globally.
    this._listeners.set(listener, { limit, onEnd });
  }

  /**
   * Internal; iterate over the set of previously found ranges. Meanwhile it'll
   * mark the listener as 'catching up', meaning it will not receive fresh
   * results from a running iterator.
   *
   * @param {Object}       listener Listener object
   * @param {nsIDOMWindow} window   The window object is only really used
   *                                for access to `setTimeout`
   * @yield {Range}
   */
  async _yieldPreviousResult(listener, window) {
    this._notifyListeners("start", this.params, [listener]);
    this._catchingUp.add(listener);
    await this._yieldResult(listener, this._previousRanges, window);
    this._catchingUp.delete(listener);
    let { onEnd } = this._listeners.get(listener);
    if (onEnd) {
      onEnd();
    }
  }

  /**
   * Internal; iterate over the set of already found ranges. Meanwhile it'll
   * mark the listener as 'catching up', meaning it will not receive fresh
   * results from the running iterator.
   *
   * @param {Object}       listener Listener object
   * @param {nsIDOMWindow} window   The window object is only really used
   *                                for access to `setTimeout`
   * @yield {Range}
   */
  async _yieldIntermediateResult(listener, window) {
    this._notifyListeners("start", this.params, [listener]);
    this._catchingUp.add(listener);
    await this._yieldResult(listener, this.ranges, window, false);
    this._catchingUp.delete(listener);
  }

  /**
   * Internal; see the documentation of the start() method above.
   *
   * @param {Finder}       finder  Currently active Finder instance
   * @param {Number}       spawnId Since `stop()` is synchronous and this method
   *                               is not, this identifier is used to learn if
   *                               it's supposed to still continue after a pause.
   * @yield {Range}
   */
  async _findAllRanges(finder, spawnId) {
    if (this._timeout) {
      if (this._timer) {
        clearTimeout(this._timer);
      }
      if (this._runningFindResolver) {
        this._runningFindResolver();
      }

      let timeout = this._timeout;
      let searchTerm = this._currentParams.word;
      // Wait a little longer when the first or second character is typed into
      // the findbar.
      if (searchTerm.length == 1) {
        timeout *= 4;
      } else if (searchTerm.length == 2) {
        timeout *= 2;
      }
      await new Promise(resolve => {
        this._runningFindResolver = resolve;
        this._timer = setTimeout(resolve, timeout);
      });
      this._timer = this._runningFindResolver = null;
      // During the timeout, we could have gotten the signal to stop iterating.
      // Make sure we do here.
      if (!this.running || spawnId !== this._spawnId) {
        return;
      }
    }

    this._notifyListeners("start", this.params);

    let { linksOnly, useSubFrames, window } = this._currentParams;
    // First we collect all frames we need to search through, whilst making sure
    // that the parent window gets dibs.
    let frames = [window];
    if (useSubFrames) {
      frames.push(...this._collectFrames(window, finder));
    }
    let iterCount = 0;
    for (let frame of frames) {
      for (let range of this._iterateDocument(this._currentParams, frame)) {
        // Between iterations, for example after a sleep of one cycle, we could
        // have gotten the signal to stop iterating. Make sure we do here.
        if (!this.running || spawnId !== this._spawnId) {
          return;
        }

        // Deal with links-only mode here.
        if (linksOnly && !this._rangeStartsInLink(range)) {
          continue;
        }

        this.ranges.push(range);

        // Call each listener with the range we just found.
        for (let [listener, { limit, onEnd }] of this._listeners) {
          if (this._catchingUp.has(listener)) {
            continue;
          }

          listener.onIteratorRangeFound(range);

          if (limit !== -1 && --limit === 0) {
            // We've reached our limit; no need to do more work for this listener.
            this._listeners.delete(listener);
            onEnd();
            continue;
          }

          // Save the updated limit globally.
          this._listeners.set(listener, { limit, onEnd });
        }

        await range;

        if (++iterCount >= kIterationSizeMax) {
          iterCount = 0;
          // Sleep for the rest of this cycle.
          await new Promise(resolve => window.setTimeout(resolve, 0));
        }
      }
    }

    // When the iterating has finished, make sure we reset and save the state
    // properly.
    this.stop(true);
  }

  /**
   * Internal; basic wrapper around nsIFind that provides a generator yielding
   * a range each time an occurence of `word` string is found.
   *
   * @param {Boolean}      options.caseSensitive Whether to search in case
   *                                             sensitive mode
   * @param {Boolean}      options.entireWord    Whether to search in entire-word
   *                                             mode
   * @param {Boolean}      options.matchDiacritics Whether to search in
   *                                               diacritic-matching mode
   * @param {String}       options.word          The word to search for
   * @param {nsIDOMWindow} window                The window to search in
   * @yield {Range}
   */
  *_iterateDocument(
    { caseSensitive, entireWord, matchDiacritics, word },
    window
  ) {
    let doc = window.document;
    let body = doc.body || doc.documentElement;

    if (!body) {
      return;
    }

    let searchRange = doc.createRange();
    searchRange.selectNodeContents(body);

    let startPt = searchRange.cloneRange();
    startPt.collapse(true);

    let endPt = searchRange.cloneRange();
    endPt.collapse(false);

    let retRange = null;

    let nsIFind = Cc["@mozilla.org/embedcomp/rangefind;1"]
      .createInstance()
      .QueryInterface(Ci.nsIFind);
    nsIFind.caseSensitive = caseSensitive;
    nsIFind.entireWord = entireWord;
    nsIFind.matchDiacritics = matchDiacritics;

    while ((retRange = nsIFind.Find(word, searchRange, startPt, endPt))) {
      yield retRange;
      startPt = retRange.cloneRange();
      startPt.collapse(false);
    }
  }

  /**
   * Internal; helper method for the iterator that recursively collects all
   * visible (i)frames inside a window.
   *
   * @param  {nsIDOMWindow} window The window to extract the (i)frames from
   * @param  {Finder}       finder The Finder instance
   * @return {Array}        Stack of frames to iterate over
   */
  _collectFrames(window, finder) {
    let frames = [];
    if (!("frames" in window) || !window.frames.length) {
      return frames;
    }

    // Casting `window.frames` to an Iterator doesn't work, so we're stuck with
    // a plain, old for-loop.
    let dwu = window.windowUtils;
    for (let i = 0, l = window.frames.length; i < l; ++i) {
      let frame = window.frames[i];
      // Don't count matches in hidden frames; get the frame element rect and
      // check if it's empty. We shan't flush!
      let frameEl = frame && frame.frameElement;
      if (
        !frameEl ||
        lazy.Rect.fromRect(dwu.getBoundsWithoutFlushing(frameEl)).isEmpty()
      ) {
        continue;
      }
      // All conditions pass, so push the current frame and its children on the
      // stack.
      frames.push(frame, ...this._collectFrames(frame, finder));
    }

    return frames;
  }

  /**
   * Internal; helper method to extract the docShell reference from a Window or
   * Range object.
   *
   * @param  {Range} windowOrRange Window object to query. May also be a
   *                               Range, from which the owner window will
   *                               be queried.
   * @return {nsIDocShell}
   */
  _getDocShell(windowOrRange) {
    let window = windowOrRange;
    // Ranges may also be passed in, so fetch its window.
    if (ChromeUtils.getClassName(windowOrRange) === "Range") {
      window = windowOrRange.startContainer.ownerGlobal;
    }
    return window.docShell;
  }

  /**
   * Internal; determines whether a range is inside a link.
   *
   * @param  {Range} range the range to check
   * @return {Boolean}     True if the range starts in a link
   */
  _rangeStartsInLink(range) {
    let isInsideLink = false;
    let node = range.startContainer;

    if (node.nodeType == node.ELEMENT_NODE) {
      if (node.hasChildNodes) {
        let childNode = node.item(range.startOffset);
        if (childNode) {
          node = childNode;
        }
      }
    }

    const XLink_NS = "http://www.w3.org/1999/xlink";
    const HTMLAnchorElement = (node.ownerDocument || node).defaultView
      .HTMLAnchorElement;
    do {
      if (HTMLAnchorElement.isInstance(node)) {
        isInsideLink = node.hasAttribute("href");
        break;
      } else if (
        typeof node.hasAttributeNS == "function" &&
        node.hasAttributeNS(XLink_NS, "href")
      ) {
        isInsideLink = node.getAttributeNS(XLink_NS, "type") == "simple";
        break;
      }

      node = node.parentNode;
    } while (node);

    return isInsideLink;
  }
}
PK
!<�Բ�F�Fmodules/FinderParent.sys.mjs// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
// vim: set ts=2 sw=2 sts=2 et tw=80: */
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

const kModalHighlightPref = "findbar.modalHighlight";
const kSoundEnabledPref = "accessibility.typeaheadfind.enablesound";
const kNotFoundSoundPref = "accessibility.typeaheadfind.soundURL";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  GetClipboardSearchString: "resource://gre/modules/Finder.sys.mjs",
  RFPHelper: "resource://gre/modules/RFPHelper.sys.mjs",
  Rect: "resource://gre/modules/Geometry.sys.mjs",
});

const kPrefLetterboxing = "privacy.resistFingerprinting.letterboxing";

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "isLetterboxingEnabled",
  kPrefLetterboxing,
  false
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "isSoundEnabled",
  kSoundEnabledPref,
  false
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "notFoundSoundURL",
  kNotFoundSoundPref,
  ""
);

export function FinderParent(browser) {
  this._listeners = new Set();
  this._searchString = "";
  this._foundSearchString = null;
  this._lastFoundBrowsingContext = null;

  // The correct states of these will be updated when the findbar is opened.
  this._caseSensitive = false;
  this._entireWord = false;
  this._matchDiacritics = false;

  this.swapBrowser(browser);
}

let gSound = null;

FinderParent.prototype = {
  get browsingContext() {
    return this._browser.browsingContext;
  },

  get useRemoteSubframes() {
    return this._browser.ownerGlobal.docShell.nsILoadContext.useRemoteSubframes;
  },

  swapBrowser(aBrowser) {
    this._browser = aBrowser;
    // Ideally listeners would have removed themselves but that doesn't happen
    // right now
    this._listeners.clear();
  },

  addResultListener(aListener) {
    this._listeners.add(aListener);
  },

  removeResultListener(aListener) {
    this._listeners.delete(aListener);
  },

  callListeners(aCallback, aArgs) {
    for (let l of this._listeners) {
      // Don't let one callback throwing stop us calling the rest
      try {
        l[aCallback].apply(l, aArgs);
      } catch (e) {
        if (!l[aCallback]) {
          console.error(
            `Missing ${aCallback} callback on RemoteFinderListener`
          );
        } else {
          console.error(e);
        }
      }
    }
  },

  getLastFoundBrowsingContext(aList) {
    // If a search was already performed, returned the last
    // browsing context where the result was found. However,
    // ensure that this browsing context is still valid, and
    // if not, return null.
    if (
      aList.includes(this._lastFoundBrowsingContext) &&
      !this._lastFoundBrowsingContext.isUnderHiddenEmbedderElement
    ) {
      return this._lastFoundBrowsingContext;
    }

    this._lastFoundBrowsingContext = null;
    return null;
  },

  sendMessageToContext(aMessageName, aArgs = {}) {
    // If there is a last found browsing context, use that. Otherwise,
    // use the top-level browsing context.
    let browsingContext = null;
    if (this._lastFoundBrowsingContext) {
      let list = this.gatherBrowsingContexts(this.browsingContext);
      let lastBrowsingContext = this.getLastFoundBrowsingContext(list);
      if (lastBrowsingContext) {
        browsingContext = lastBrowsingContext;
      }
    }

    if (!browsingContext) {
      browsingContext = this.browsingContext;
    }

    let windowGlobal = browsingContext.currentWindowGlobal;
    if (windowGlobal) {
      let actor = windowGlobal.getActor("Finder");
      actor.sendAsyncMessage(aMessageName, aArgs);
    }
  },

  sendQueryToContext(aMessageName, aArgs, aBrowsingContext) {
    let windowGlobal = aBrowsingContext.currentWindowGlobal;
    if (windowGlobal) {
      let actor = windowGlobal.getActor("Finder");
      return actor.sendQuery(aMessageName, aArgs).then(
        result => result,
        () => {}
      );
    }

    return Promise.resolve({});
  },

  sendMessageToAllContexts(aMessageName, aArgs = {}) {
    let list = this.gatherBrowsingContexts(this.browsingContext);
    for (let browsingContext of list) {
      let windowGlobal = browsingContext.currentWindowGlobal;
      if (windowGlobal) {
        let actor = windowGlobal.getActor("Finder");
        actor.sendAsyncMessage(aMessageName, aArgs);
      }
    }
  },

  gatherBrowsingContexts(aBrowsingContext) {
    if (aBrowsingContext.isUnderHiddenEmbedderElement) {
      return [];
    }

    let list = [aBrowsingContext];

    for (let child of aBrowsingContext.children) {
      list.push(...this.gatherBrowsingContexts(child));
    }

    return list;
  },

  // If the modal highlighter is on, and there are no out-of-process child
  // frames, send a message only to the top-level frame and set the useSubFrames
  // flag, so that the finder iterator iterates over subframes. If there is
  // an out-of-process subframe, modal highlighting is disabled.
  needSubFrameSearch(aList) {
    let useSubFrames = false;

    let useModalHighlighter = Services.prefs.getBoolPref(kModalHighlightPref);
    let hasOutOfProcessChild = false;
    if (useModalHighlighter) {
      if (this.useRemoteSubframes) {
        return false;
      }

      for (let browsingContext of aList) {
        if (
          browsingContext != this.browsingContext &&
          browsingContext.currentWindowGlobal.isProcessRoot
        ) {
          hasOutOfProcessChild = true;
        }
      }

      if (!hasOutOfProcessChild) {
        aList.splice(0);
        aList.push(this.browsingContext);
        useSubFrames = true;
      }
    }

    return useSubFrames;
  },

  onResultFound(aResponse) {
    this._foundSearchString = aResponse.searchString;
    // The rect stops being a Geometry.sys.mjs:Rect over IPC.
    if (aResponse.rect) {
      aResponse.rect = lazy.Rect.fromRect(aResponse.rect);
    }

    this.callListeners("onFindResult", [aResponse]);
  },

  get searchString() {
    return this._foundSearchString;
  },

  get clipboardSearchString() {
    return lazy.GetClipboardSearchString(this._browser.loadContext);
  },

  set caseSensitive(aSensitive) {
    this._caseSensitive = aSensitive;
    this.sendMessageToAllContexts("Finder:CaseSensitive", {
      caseSensitive: aSensitive,
    });
  },

  set entireWord(aEntireWord) {
    this._entireWord = aEntireWord;
    this.sendMessageToAllContexts("Finder:EntireWord", {
      entireWord: aEntireWord,
    });
  },

  set matchDiacritics(aMatchDiacritics) {
    this._matchDiacritics = aMatchDiacritics;
    this.sendMessageToAllContexts("Finder:MatchDiacritics", {
      matchDiacritics: aMatchDiacritics,
    });
  },

  async setSearchStringToSelection() {
    return this.setToSelection("Finder:SetSearchStringToSelection", false);
  },

  async getInitialSelection() {
    return this.setToSelection("Finder:GetInitialSelection", true);
  },

  async setToSelection(aMessage, aInitial) {
    let browsingContext = this.browsingContext;

    // Iterate over focused subframe descendants until one is found
    // that has the selection.
    let result;
    do {
      result = await this.sendQueryToContext(aMessage, {}, browsingContext);
      if (!result || !result.focusedChildBrowserContextId) {
        break;
      }

      browsingContext = BrowsingContext.get(
        result.focusedChildBrowserContextId
      );
    } while (browsingContext);

    if (result) {
      this.callListeners("onCurrentSelection", [result.selectedText, aInitial]);
    }

    return result;
  },

  async doFind(aFindNext, aArgs) {
    let rootBC = this.browsingContext;
    let highlightList = this.gatherBrowsingContexts(rootBC);

    let canPlayNotFoundSound =
      aArgs.searchString.length > this._searchString.length;

    this._searchString = aArgs.searchString;

    let initialBC = this.getLastFoundBrowsingContext(highlightList);
    if (!initialBC) {
      initialBC = rootBC;
      aFindNext = false;
    }

    // Make a copy of the list starting from the
    // browsing context that was last searched from. The original
    // list will be used for the highlighter where the search
    // order doesn't matter.
    let searchList = [];
    for (let c = 0; c < highlightList.length; c++) {
      if (highlightList[c] == initialBC) {
        searchList = highlightList.slice(c);
        searchList.push(...highlightList.slice(0, c));
        break;
      }
    }

    let mode = Ci.nsITypeAheadFind.FIND_INITIAL;
    if (aFindNext) {
      mode = aArgs.findBackwards
        ? Ci.nsITypeAheadFind.FIND_PREVIOUS
        : Ci.nsITypeAheadFind.FIND_NEXT;
    }
    aArgs.findAgain = aFindNext;

    aArgs.caseSensitive = this._caseSensitive;
    aArgs.matchDiacritics = this._matchDiacritics;
    aArgs.entireWord = this._entireWord;

    aArgs.useSubFrames = this.needSubFrameSearch(searchList);
    if (aArgs.useSubFrames) {
      // Use the single frame for the highlight list as well.
      highlightList = searchList;
      // The typeaheadfind component will play the sound in this case.
      canPlayNotFoundSound = false;
    }

    if (canPlayNotFoundSound) {
      this.initNotFoundSound();
    }

    // Add the initial browsing context twice to allow looping around.
    searchList = [...searchList, initialBC];

    if (aArgs.findBackwards) {
      searchList.reverse();
    }

    let response = null;
    let wrapped = false;
    let foundBC = null;

    for (let c = 0; c < searchList.length; c++) {
      let currentBC = searchList[c];
      aArgs.mode = mode;

      // A search has started for a different string, so
      // ignore further searches of the old string.
      if (this._searchString != aArgs.searchString) {
        return;
      }

      response = await this.sendQueryToContext("Finder:Find", aArgs, currentBC);

      // This can happen if the tab is closed while the find is in progress.
      if (!response) {
        break;
      }

      // If the search term was found, stop iterating.
      if (response.result != Ci.nsITypeAheadFind.FIND_NOTFOUND) {
        if (
          this._lastFoundBrowsingContext &&
          this._lastFoundBrowsingContext != currentBC
        ) {
          // If the new result is in a different frame than the previous result,
          // clear the result from the old frame. If it is the same frame, the
          // previous result will be cleared by the find component.
          this.removeSelection(true);
        }
        this._lastFoundBrowsingContext = currentBC;

        // Set the wrapped result flag if needed.
        if (wrapped) {
          response.result = Ci.nsITypeAheadFind.FIND_WRAPPED;
        }

        foundBC = currentBC;
        break;
      }

      if (aArgs.findBackwards && currentBC == rootBC) {
        wrapped = true;
      } else if (
        !aArgs.findBackwards &&
        c + 1 < searchList.length &&
        searchList[c + 1] == rootBC
      ) {
        wrapped = true;
      }

      mode = aArgs.findBackwards
        ? Ci.nsITypeAheadFind.FIND_LAST
        : Ci.nsITypeAheadFind.FIND_FIRST;
    }

    if (response) {
      response.useSubFrames = aArgs.useSubFrames;
      // Update the highlight in all browsing contexts. This needs to happen separately
      // once it is clear whether a match was found or not.
      this.updateHighlightAndMatchCount({
        list: highlightList,
        message: "Finder:UpdateHighlightAndMatchCount",
        args: response,
        foundBrowsingContextId: foundBC ? foundBC.id : -1,
        doHighlight: true,
        doMatchCount: true,
      });

      // Use the last result found.
      this.onResultFound(response);

      if (
        canPlayNotFoundSound &&
        response.result == Ci.nsITypeAheadFind.FIND_NOTFOUND &&
        !aFindNext &&
        !response.entireWord
      ) {
        this.playNotFoundSound();
      }
    }
  },

  fastFind(aSearchString, aLinksOnly, aDrawOutline) {
    this.doFind(false, {
      searchString: aSearchString,
      findBackwards: false,
      linksOnly: aLinksOnly,
      drawOutline: aDrawOutline,
    });
  },

  findAgain(aSearchString, aFindBackwards, aLinksOnly, aDrawOutline) {
    this.doFind(true, {
      searchString: aSearchString,
      findBackwards: aFindBackwards,
      linksOnly: aLinksOnly,
      drawOutline: aDrawOutline,
    });
  },

  highlight(aHighlight, aWord, aLinksOnly) {
    let list = this.gatherBrowsingContexts(this.browsingContext);
    let args = {
      highlight: aHighlight,
      linksOnly: aLinksOnly,
      searchString: aWord,
    };

    args.useSubFrames = this.needSubFrameSearch(list);

    let lastBrowsingContext = this.getLastFoundBrowsingContext(list);
    this.updateHighlightAndMatchCount({
      list,
      message: "Finder:Highlight",
      args,
      foundBrowsingContextId: lastBrowsingContext ? lastBrowsingContext.id : -1,
      doHighlight: true,
      doMatchCount: false,
    });
  },

  requestMatchesCount(aSearchString, aLinksOnly) {
    let list = this.gatherBrowsingContexts(this.browsingContext);
    let args = { searchString: aSearchString, linksOnly: aLinksOnly };

    args.useSubFrames = this.needSubFrameSearch(list);

    let lastBrowsingContext = this.getLastFoundBrowsingContext(list);
    this.updateHighlightAndMatchCount({
      list,
      message: "Finder:MatchesCount",
      args,
      foundBrowsingContextId: lastBrowsingContext ? lastBrowsingContext.id : -1,
      doHighlight: false,
      doMatchCount: true,
    });
  },

  updateHighlightAndMatchCount(options) {
    let promises = [];
    let found = options.args.result != Ci.nsITypeAheadFind.FIND_NOTFOUND;
    for (let browsingContext of options.list) {
      options.args.foundInThisFrame =
        options.foundBrowsingContextId != -1 &&
        found &&
        browsingContext.id == options.foundBrowsingContextId;

      // Don't wait for the result
      let promise = this.sendQueryToContext(
        options.message,
        options.args,
        browsingContext
      );
      promises.push(promise);
    }

    Promise.all(promises).then(responses => {
      if (options.doHighlight) {
        let sendNotification = false;
        let highlight = false;
        let found = false;
        for (let response of responses) {
          if (!response) {
            break;
          }

          sendNotification = true;
          if (response.found) {
            found = true;
          }
          highlight = response.highlight;
        }

        if (sendNotification) {
          this.callListeners("onHighlightFinished", [
            { searchString: options.args.searchString, highlight, found },
          ]);
        }
      }

      if (options.doMatchCount) {
        let sendNotification = false;
        let current = 0;
        let total = 0;
        let limit = 0;
        for (let response of responses) {
          // A null response can happen if another search was started
          // and this one became invalid.
          if (!response || !("total" in response)) {
            break;
          }

          sendNotification = true;

          if (
            options.args.useSubFrames ||
            (options.foundBrowsingContextId >= 0 &&
              response.browsingContextId == options.foundBrowsingContextId)
          ) {
            current = total + response.current;
          }
          total += response.total;
          limit = response.limit;
        }

        if (sendNotification) {
          this.callListeners("onMatchesCountResult", [
            { searchString: options.args.searchString, current, total, limit },
          ]);
        }
      }
    });
  },

  enableSelection() {
    this.sendMessageToContext("Finder:EnableSelection");
  },

  removeSelection(aKeepHighlight) {
    this.sendMessageToContext("Finder:RemoveSelection", {
      keepHighlight: aKeepHighlight,
    });
  },

  focusContent() {
    // Allow Finder listeners to cancel focusing the content.
    for (let l of this._listeners) {
      try {
        if ("shouldFocusContent" in l && !l.shouldFocusContent()) {
          return;
        }
      } catch (ex) {
        console.error(ex);
      }
    }

    this._browser.focus();
    this.sendMessageToContext("Finder:FocusContent");
  },

  onFindbarClose() {
    this._lastFoundBrowsingContext = null;
    this.sendMessageToAllContexts("Finder:FindbarClose");

    if (lazy.isLetterboxingEnabled) {
      let window = this._browser.ownerGlobal;
      lazy.RFPHelper.contentSizeUpdated(window);
    }
  },

  onFindbarOpen() {
    this.sendMessageToAllContexts("Finder:FindbarOpen");

    if (lazy.isLetterboxingEnabled) {
      let window = this._browser.ownerGlobal;
      lazy.RFPHelper.contentSizeUpdated(window);
    }
  },

  onModalHighlightChange(aUseModalHighlight) {
    this.sendMessageToAllContexts("Finder:ModalHighlightChange", {
      useModalHighlight: aUseModalHighlight,
    });
  },

  onHighlightAllChange(aHighlightAll) {
    this.sendMessageToAllContexts("Finder:HighlightAllChange", {
      highlightAll: aHighlightAll,
    });
  },

  keyPress(aEvent) {
    this.sendMessageToContext("Finder:KeyPress", {
      keyCode: aEvent.keyCode,
      ctrlKey: aEvent.ctrlKey,
      metaKey: aEvent.metaKey,
      altKey: aEvent.altKey,
      shiftKey: aEvent.shiftKey,
    });
  },

  initNotFoundSound() {
    if (!gSound && lazy.isSoundEnabled && lazy.notFoundSoundURL) {
      try {
        gSound = Cc["@mozilla.org/sound;1"].getService(Ci.nsISound);
        gSound.init();
      } catch (ex) {}
    }
  },

  playNotFoundSound() {
    if (!lazy.isSoundEnabled || !lazy.notFoundSoundURL) {
      return;
    }

    this.initNotFoundSound();
    if (!gSound) {
      return;
    }

    let soundUrl = lazy.notFoundSoundURL;
    if (soundUrl == "beep") {
      gSound.beep();
    } else {
      if (soundUrl == "default") {
        soundUrl = "chrome://global/content/notfound.wav";
      }
      gSound.play(Services.io.newURI(soundUrl));
    }
  },
};
PK
!<DV����.modules/FingerprintingWebCompatService.sys.mjs// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at https://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  JsonSchema: "resource://gre/modules/JsonSchema.sys.mjs",
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
});
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

ChromeUtils.defineLazyGetter(lazy, "logConsole", () => {
  return console.createInstance({
    prefix: "FingerprintingWebCompatService",
    maxLogLevelPref:
      "privacy.fingerprintingProtection.WebCompatService.logLevel",
  });
});

const SCHEMA = `{
  "type": "object",
  "title": "Fingerprinting Overrides",
  "$schema": "http://json-schema.org/draft-07/schema#",
  "required": [
    "firstPartyDomain",
    "overrides"
  ],
  "properties": {
    "overrides": {
      "type": "string",
      "pattern": "^[+-][A-Za-z]+(?:,[+-][A-Za-z]+)*$",
      "description": "The fingerprinting overrides. See https://searchfox.org/mozilla-central/source/toolkit/components/resistfingerprinting/RFPTargets.inc for details."
    },
    "firstPartyDomain": {
      "type": "string",
      "pattern": "^(\\*|(?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}$",
      "description": "The first-party domain associated with the override. Use '*' to match all domains. Only legit domains allowed."
    },
    "thirdPartyDomain": {
      "type": "string",
      "pattern": "^(\\*|(?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}$",
      "description": "The third-party domain associated with the override. Use '*' to match all domains. Only legit domains allowed. Leave this field empty if the override is only for the first-party context."
    }
  }
}`;

const COLLECTION_NAME = "fingerprinting-protection-overrides";
const PREF_GRANULAR_OVERRIDES =
  "privacy.fingerprintingProtection.granularOverrides";
const PREF_REMOTE_OVERRIDES_ENABLED =
  "privacy.fingerprintingProtection.remoteOverrides.enabled";

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "granularOverridesPref",
  PREF_GRANULAR_OVERRIDES
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "remoteOverridesEnabled",
  PREF_REMOTE_OVERRIDES_ENABLED
);

/**
 * The object represents a fingerprinting override.
 */
export class FingerprintingOverride {
  classID = Components.ID("{07f45442-1806-44be-9230-12eb79de9bac}");
  QueryInterface = ChromeUtils.generateQI(["nsIFingerprintingOverride"]);

  constructor(firstPartyDomain, thirdPartyDomain, overrides) {
    this.firstPartyDomain = firstPartyDomain;
    this.thirdPartyDomain = thirdPartyDomain;
    this.overrides = overrides;
  }
}

/**
 * The singleton service that is responsible for the WebCompat of the
 * fingerprinting protection. It gets fingerprinting overrides from remote
 * settings and the local test pref.
 */
export class FingerprintingWebCompatService {
  classId = Components.ID("{e7b1da06-2594-4670-aea4-131070baca4c}");
  QueryInterface = ChromeUtils.generateQI([
    "nsIFingerprintingWebCompatService",
  ]);
  #initialized = false;
  #remoteOverrides;
  #granularOverrides;
  #rs;
  #validator;

  #isParentProcess;

  constructor() {
    this.#isParentProcess =
      Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT;

    this.#remoteOverrides = new Set();
    this.#granularOverrides = new Set();

    this.#rs = lazy.RemoteSettings(COLLECTION_NAME);
    this.#validator = new lazy.JsonSchema.Validator(SCHEMA);
  }

  async init() {
    lazy.logConsole.debug("init");
    // We can only access remote settings from the parent process. So, never
    // init it in the content process.
    if (!this.#isParentProcess) {
      throw new Error(
        "Shouldn't init FingerprintingWebCompatService in content processes."
      );
    }

    // Return if we have initiated.
    if (this.#initialized) {
      return;
    }
    this.#initialized = true;

    // Register listener to import overrides when the overrides pref changes.
    Services.prefs.addObserver(PREF_GRANULAR_OVERRIDES, this);

    // Register the sync event for the remote settings updates.
    this.#rs.on("sync", event => {
      let {
        data: { current },
      } = event;
      this.#onRemoteUpdate(current);

      this.#populateOverrides();
    });

    // Get the remote overrides from the remote settings.
    await this.#importRemoteSettingsOverrides();

    // Get the granular overrides from the pref.
    this.#importPrefOverrides();

    // Populate the overrides to the nsRFPService.
    this.#populateOverrides();

    lazy.logConsole.debug("Init completes");
  }

  // Import fingerprinting overrides from the local granular pref.
  #importPrefOverrides() {
    lazy.logConsole.debug("importLocalGranularOverrides");

    // Clear overrides before we update.
    this.#granularOverrides.clear();

    let overrides;
    try {
      overrides = JSON.parse(lazy.granularOverridesPref || "[]");
    } catch (error) {
      lazy.logConsole.error(
        `Failed to parse granular override JSON string: Not a valid JSON.`,
        error
      );
      return;
    }

    // Ensure we have an array we can iterate over and not an object.
    if (!Array.isArray(overrides)) {
      lazy.logConsole.error(
        "Failed to parse granular overrides JSON String: Not an array."
      );
      return;
    }

    for (let override of overrides) {
      // Validate the override.
      let { valid, errors } = this.#validator.validate(override);

      if (!valid) {
        lazy.logConsole.debug("Override validation error", override, errors);
        continue;
      }

      this.#granularOverrides.add(
        this.#createFingerprintingOverrideFrom(override)
      );
    }
  }

  // Import fingerprinting overrides from the remote settings.
  async #importRemoteSettingsOverrides() {
    lazy.logConsole.debug("importRemoteSettingsOverrides");

    let entries;
    try {
      entries = await this.#rs.get();
    } catch (e) {}

    this.#onRemoteUpdate(entries || []);
  }

  #onRemoteUpdate(entries) {
    lazy.logConsole.debug("onUpdateEntries", { entries });

    if (!lazy.remoteOverridesEnabled) {
      lazy.logConsole.debug("Abort remote overrides");
      return;
    }

    // Clear all overrides before we update the overrides.
    this.#remoteOverrides.clear();

    for (let entry of entries) {
      this.#remoteOverrides.add(this.#createFingerprintingOverrideFrom(entry));
    }
  }

  #createFingerprintingOverrideFrom(entry) {
    return new FingerprintingOverride(
      entry.firstPartyDomain,
      entry.thirdPartyDomain,
      entry.overrides
    );
  }

  #populateOverrides() {
    lazy.logConsole.debug("populateOverrides");

    // Create the array that contains all overrides. We explicitly concat the
    // overrides from testing pref after the ones from remote settings to ensure
    // that the testing pref will take precedence.
    let overrides = Array.from(this.#remoteOverrides).concat(
      Array.from(this.#granularOverrides)
    );

    // Set the remote override to the RFP service.
    Services.rfp.setFingerprintingOverrides(Array.from(overrides));
  }

  observe(subject, topic, prefName) {
    if (prefName != PREF_GRANULAR_OVERRIDES) {
      return;
    }

    this.#importPrefOverrides();
    this.#populateOverrides();
  }

  shutdown() {
    lazy.logConsole.debug("shutdown");

    Services.prefs.removeObserver(PREF_GRANULAR_OVERRIDES, this);
  }
}
PK
!<�A�ЙH�Hmodules/FirefoxRelay.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { FirefoxRelayTelemetry } from "resource://gre/modules/FirefoxRelayTelemetry.mjs";
import {
  LoginHelper,
  OptInFeature,
  ParentAutocompleteOption,
} from "resource://gre/modules/LoginHelper.sys.mjs";
import { TelemetryUtils } from "resource://gre/modules/TelemetryUtils.sys.mjs";
import { showConfirmation } from "resource://gre/modules/FillHelpers.sys.mjs";

const lazy = {};

// Static configuration
const gConfig = (function () {
  const baseUrl = Services.prefs.getStringPref(
    "signon.firefoxRelay.base_url",
    undefined
  );
  return {
    scope: ["profile", "https://identity.mozilla.com/apps/relay"],
    addressesUrl: baseUrl + `relayaddresses/`,
    acceptTermsUrl: baseUrl + `terms-accepted-user/`,
    profilesUrl: baseUrl + `profiles/`,
    learnMoreURL: Services.urlFormatter.formatURLPref(
      "signon.firefoxRelay.learn_more_url"
    ),
    manageURL: Services.urlFormatter.formatURLPref(
      "signon.firefoxRelay.manage_url"
    ),
    relayFeaturePref: "signon.firefoxRelay.feature",
    termsOfServiceUrl: Services.urlFormatter.formatURLPref(
      "signon.firefoxRelay.terms_of_service_url"
    ),
    privacyPolicyUrl: Services.urlFormatter.formatURLPref(
      "signon.firefoxRelay.privacy_policy_url"
    ),
  };
})();

ChromeUtils.defineLazyGetter(lazy, "log", () =>
  LoginHelper.createLogger("FirefoxRelay")
);
ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () =>
  ChromeUtils.importESModule(
    "resource://gre/modules/FxAccounts.sys.mjs"
  ).getFxAccountsSingleton()
);
ChromeUtils.defineLazyGetter(lazy, "strings", function () {
  return new Localization([
    "branding/brand.ftl",
    "browser/firefoxRelay.ftl",
    "toolkit/branding/brandings.ftl",
  ]);
});

if (Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT) {
  throw new Error("FirefoxRelay.sys.mjs should only run in the parent process");
}

// Using 418 to avoid conflict with other standard http error code
const AUTH_TOKEN_ERROR_CODE = 418;

let gFlowId;

async function getRelayTokenAsync() {
  try {
    return await lazy.fxAccounts.getOAuthToken({ scope: gConfig.scope });
  } catch (e) {
    console.error(`There was an error getting the user's token: ${e.message}`);
    return undefined;
  }
}

async function hasFirefoxAccountAsync() {
  if (!lazy.fxAccounts.constructor.config.isProductionConfig()) {
    return false;
  }
  return lazy.fxAccounts.hasLocalSession();
}

async function fetchWithReauth(
  browser,
  createRequest,
  canGetFreshOAuthToken = true
) {
  const relayToken = await getRelayTokenAsync();
  if (!relayToken) {
    if (browser) {
      await showErrorAsync(browser, "firefox-relay-must-login-to-account");
    }
    return undefined;
  }

  const headers = new Headers({
    Authorization: `Bearer ${relayToken}`,
    Accept: "application/json",
    "Accept-Language": Services.locale.requestedLocales,
    "Content-Type": "application/json",
  });

  const request = createRequest(headers);
  const response = await fetch(request);

  if (canGetFreshOAuthToken && response.status == 401) {
    await lazy.fxAccounts.removeCachedOAuthToken({ token: relayToken });
    return fetchWithReauth(browser, createRequest, false);
  }
  return response;
}

async function getReusableMasksAsync(browser, _origin) {
  const response = await fetchWithReauth(
    browser,
    headers =>
      new Request(gConfig.addressesUrl, {
        method: "GET",
        headers,
      })
  );

  if (!response) {
    // fetchWithReauth only returns undefined if login / obtaining a token failed.
    // Otherwise, it will return a response object.
    return [undefined, AUTH_TOKEN_ERROR_CODE];
  }

  if (response.ok) {
    return [await response.json(), response.status];
  }

  lazy.log.error(
    `failed to find reusable Relay masks: ${response.status}:${response.statusText}`
  );
  await showErrorAsync(browser, "firefox-relay-get-reusable-masks-failed", {
    status: response.status,
  });

  return [undefined, response.status];
}

/**
 * Show localized notification.
 *
 * @param {*} browser
 * @param {*} messageId from browser/firefoxRelay.ftl
 * @param {object} messageArgs
 */
async function showErrorAsync(browser, messageId, messageArgs) {
  const { PopupNotifications } = browser.ownerGlobal.wrappedJSObject;
  const [message] = await lazy.strings.formatValues([
    { id: messageId, args: messageArgs },
  ]);
  PopupNotifications.show(
    browser,
    "relay-integration-error",
    message,
    "password-notification-icon",
    null,
    null,
    {
      autofocus: true,
      removeOnDismissal: true,
      popupIconURL: "chrome://browser/content/logos/relay.svg",
      learnMoreURL: gConfig.learnMoreURL,
    }
  );
}

function customizeNotificationHeader(notification) {
  if (!notification) {
    return;
  }
  const document = notification.owner.panel.ownerDocument;
  const description = document.querySelector(
    `description[popupid=${notification.id}]`
  );
  const headerTemplate = document.getElementById("firefox-relay-header");
  description.replaceChildren(headerTemplate.firstChild.cloneNode(true));
}

async function formatMessages(...ids) {
  for (let i in ids) {
    if (typeof ids[i] == "string") {
      ids[i] = { id: ids[i] };
    }
  }

  const messages = await lazy.strings.formatMessages(ids);
  return messages.map(message => {
    if (message.attributes) {
      return message.attributes.reduce(
        (result, { name, value }) => ({ ...result, [name]: value }),
        {}
      );
    }
    return message.value;
  });
}

async function showReusableMasksAsync(browser, origin, error) {
  const [reusableMasks, status] = await getReusableMasksAsync(browser, origin);
  if (!reusableMasks) {
    FirefoxRelayTelemetry.recordRelayReusePanelEvent("shown", gFlowId, status);
    return null;
  }

  let fillUsername;
  const fillUsernamePromise = new Promise(resolve => (fillUsername = resolve));
  const [getUnlimitedMasksStrings] = await formatMessages(
    "firefox-relay-get-unlimited-masks"
  );
  const getUnlimitedMasks = {
    label: getUnlimitedMasksStrings.label,
    accessKey: getUnlimitedMasksStrings.accesskey,
    dismiss: true,
    async callback() {
      FirefoxRelayTelemetry.recordRelayReusePanelEvent(
        "get_unlimited_masks",
        gFlowId
      );
      browser.ownerGlobal.openWebLinkIn(gConfig.manageURL, "tab");
    },
  };

  let notification;

  function getReusableMasksList() {
    return notification?.owner.panel.getElementsByClassName(
      "reusable-relay-masks"
    )[0];
  }

  function notificationShown() {
    if (!notification) {
      return;
    }

    customizeNotificationHeader(notification);

    notification.owner.panel.getElementsByClassName(
      "error-message"
    )[0].textContent = error.detail || "";

    // rebuild "reuse mask" buttons list
    const list = getReusableMasksList();
    list.innerHTML = "";

    const document = list.ownerDocument;
    const fragment = document.createDocumentFragment();
    reusableMasks
      .filter(mask => mask.enabled)
      .forEach(mask => {
        const button = document.createElement("button");

        const maskFullAddress = document.createElement("span");
        maskFullAddress.textContent = mask.full_address;
        button.appendChild(maskFullAddress);

        const maskDescription = document.createElement("span");
        maskDescription.textContent =
          mask.description || mask.generated_for || mask.used_on;
        button.appendChild(maskDescription);

        button.addEventListener(
          "click",
          () => {
            notification.remove();
            lazy.log.info("Reusing Relay mask");
            fillUsername(mask.full_address);
            showConfirmation(
              browser,
              "confirmation-hint-firefox-relay-mask-reused"
            );
            FirefoxRelayTelemetry.recordRelayReusePanelEvent(
              "reuse_mask",
              gFlowId
            );
          },
          { once: true }
        );
        fragment.appendChild(button);
      });
    list.appendChild(fragment);
  }

  function notificationRemoved() {
    const list = getReusableMasksList();
    list.innerHTML = "";
  }

  function onNotificationEvent(event) {
    switch (event) {
      case "removed":
        notificationRemoved();
        break;
      case "shown":
        notificationShown();
        FirefoxRelayTelemetry.recordRelayReusePanelEvent("shown", gFlowId);
        break;
    }
  }

  const { PopupNotifications } = browser.ownerGlobal.wrappedJSObject;
  notification = PopupNotifications.show(
    browser,
    "relay-integration-reuse-masks",
    "", // content is provided after popup shown
    "password-notification-icon",
    getUnlimitedMasks,
    [],
    {
      autofocus: true,
      removeOnDismissal: true,
      eventCallback: onNotificationEvent,
    }
  );

  return fillUsernamePromise;
}

async function generateUsernameAsync(browser, origin) {
  const body = JSON.stringify({
    enabled: true,
    description: origin.substr(0, 64),
    generated_for: origin.substr(0, 255),
    used_on: origin,
  });

  const response = await fetchWithReauth(
    browser,
    headers =>
      new Request(gConfig.addressesUrl, {
        method: "POST",
        headers,
        body,
      })
  );

  if (!response) {
    FirefoxRelayTelemetry.recordRelayUsernameFilledEvent(
      "shown",
      gFlowId,
      AUTH_TOKEN_ERROR_CODE
    );
    return undefined;
  }

  if (response.ok) {
    lazy.log.info(`generated Relay mask`);
    const result = await response.json();
    showConfirmation(browser, "confirmation-hint-firefox-relay-mask-created");
    return result.full_address;
  }

  if (response.status == 403) {
    const error = await response.json();
    if (error?.error_code == "free_tier_limit") {
      FirefoxRelayTelemetry.recordRelayUsernameFilledEvent(
        "shown",
        gFlowId,
        error?.error_code
      );
      return showReusableMasksAsync(browser, origin, error);
    }
  }

  lazy.log.error(
    `failed to generate Relay mask: ${response.status}:${response.statusText}`
  );

  await showErrorAsync(browser, "firefox-relay-mask-generation-failed", {
    status: response.status,
  });

  FirefoxRelayTelemetry.recordRelayReusePanelEvent(
    "shown",
    gFlowId,
    response.status
  );

  return undefined;
}

function isSignup(scenarioName) {
  return scenarioName == "SignUpFormScenario";
}

class RelayOffered {
  async *autocompleteItemsAsync(_origin, scenarioName, hasInput) {
    if (
      !hasInput &&
      isSignup(scenarioName) &&
      (await hasFirefoxAccountAsync()) &&
      !Services.prefs.prefIsLocked("signon.firefoxRelay.feature")
    ) {
      const [title, subtitle] = await formatMessages(
        "firefox-relay-opt-in-title-1",
        "firefox-relay-opt-in-subtitle-1"
      );
      yield new ParentAutocompleteOption(
        "chrome://browser/content/logos/relay.svg",
        title,
        subtitle,
        "PasswordManager:offerRelayIntegration",
        {
          telemetry: {
            flowId: gFlowId,
            scenarioName,
          },
        }
      );
      FirefoxRelayTelemetry.recordRelayOfferedEvent(
        "shown",
        gFlowId,
        scenarioName
      );
    }
  }

  async notifyServerTermsAcceptedAsync(browser) {
    const response = await fetchWithReauth(
      browser,
      headers =>
        new Request(gConfig.acceptTermsUrl, {
          method: "POST",
          headers,
        })
    );

    if (!response?.ok) {
      lazy.log.error(
        `failed to notify server that terms are accepted : ${response?.status}:${response?.statusText}`
      );

      let error;
      try {
        error = await response?.json();
      } catch {}
      await showErrorAsync(browser, "firefox-relay-mask-generation-failed", {
        status: error?.detail || response.status,
      });
      return false;
    }

    return true;
  }

  async offerRelayIntegration(feature, browser, origin) {
    const fxaUser = await lazy.fxAccounts.getSignedInUser();

    if (!fxaUser) {
      return null;
    }
    const { PopupNotifications } = browser.ownerGlobal.wrappedJSObject;
    let fillUsername;
    const fillUsernamePromise = new Promise(
      resolve => (fillUsername = resolve)
    );
    const [enableStrings, disableStrings, postponeStrings] =
      await formatMessages(
        "firefox-relay-opt-in-confirmation-enable-button",
        "firefox-relay-opt-in-confirmation-disable",
        "firefox-relay-opt-in-confirmation-postpone"
      );
    const enableIntegration = {
      label: enableStrings.label,
      accessKey: enableStrings.accesskey,
      dismiss: true,
      callback: async () => {
        lazy.log.info("user opted in to Firefox Relay integration");
        // Capture the flowId here since async operations might take some time to resolve
        // and by then gFlowId might have another value
        const flowId = gFlowId;
        if (await this.notifyServerTermsAcceptedAsync(browser)) {
          feature.markAsEnabled();
          FirefoxRelayTelemetry.recordRelayOptInPanelEvent("enabled", flowId);
          fillUsername(await generateUsernameAsync(browser, origin));
        }
      },
    };
    const postpone = {
      label: postponeStrings.label,
      accessKey: postponeStrings.accesskey,
      dismiss: true,
      callback() {
        lazy.log.info(
          "user decided not to decide about Firefox Relay integration"
        );
        feature.markAsOffered();
        FirefoxRelayTelemetry.recordRelayOptInPanelEvent("postponed", gFlowId);
      },
    };
    const disableIntegration = {
      label: disableStrings.label,
      accessKey: disableStrings.accesskey,
      dismiss: true,
      callback() {
        lazy.log.info("user opted out from Firefox Relay integration");
        feature.markAsDisabled();
        FirefoxRelayTelemetry.recordRelayOptInPanelEvent("disabled", gFlowId);
      },
    };
    let notification;
    feature.markAsOffered();
    notification = PopupNotifications.show(
      browser,
      "relay-integration-offer",
      "", // content is provided after popup shown
      "password-notification-icon",
      enableIntegration,
      [postpone, disableIntegration],
      {
        autofocus: true,
        removeOnDismissal: true,
        learnMoreURL: gConfig.learnMoreURL,
        eventCallback: event => {
          switch (event) {
            case "shown":
              customizeNotificationHeader(notification);
              const document = notification.owner.panel.ownerDocument;
              const tosLink = document.getElementById(
                "firefox-relay-offer-tos-url"
              );
              tosLink.href = gConfig.termsOfServiceUrl;
              const privacyLink = document.getElementById(
                "firefox-relay-offer-privacy-url"
              );
              privacyLink.href = gConfig.privacyPolicyUrl;
              const content = document.querySelector(
                `popupnotification[id=${notification.id}-notification] popupnotificationcontent`
              );
              const line3 = content.querySelector(
                "[id=firefox-relay-offer-what-relay-provides]"
              );
              document.l10n.setAttributes(
                line3,
                "firefox-relay-offer-what-relay-provides",
                {
                  useremail: fxaUser.email,
                }
              );
              FirefoxRelayTelemetry.recordRelayOptInPanelEvent(
                "shown",
                gFlowId
              );
              break;
          }
        },
      }
    );
    getRelayTokenAsync();
    return fillUsernamePromise;
  }
}

class RelayEnabled {
  async *autocompleteItemsAsync(origin, scenarioName, hasInput) {
    if (
      !hasInput &&
      isSignup(scenarioName) &&
      (await hasFirefoxAccountAsync())
    ) {
      const [title] = await formatMessages("firefox-relay-use-mask-title");
      yield new ParentAutocompleteOption(
        "chrome://browser/content/logos/relay.svg",
        title,
        "", // when the user has opted-in, there is no subtitle content
        "PasswordManager:generateRelayUsername",
        {
          telemetry: {
            flowId: gFlowId,
          },
        }
      );
      FirefoxRelayTelemetry.recordRelayUsernameFilledEvent("shown", gFlowId);
    }
  }

  async generateUsername(browser, origin) {
    return generateUsernameAsync(browser, origin);
  }
}

class RelayDisabled {}

class RelayFeature extends OptInFeature {
  constructor() {
    super(RelayOffered, RelayEnabled, RelayDisabled, gConfig.relayFeaturePref);
    Services.telemetry.setEventRecordingEnabled("relay_integration", true);
    // Update the config when the signon.firefoxRelay.base_url pref is changed.
    // This is added mainly for tests.
    Services.prefs.addObserver(
      "signon.firefoxRelay.base_url",
      this.updateConfig
    );
  }

  get learnMoreUrl() {
    return gConfig.learnMoreURL;
  }

  updateConfig() {
    const newBaseUrl = Services.prefs.getStringPref(
      "signon.firefoxRelay.base_url"
    );
    gConfig.addressesUrl = newBaseUrl + `relayaddresses/`;
    gConfig.profilesUrl = newBaseUrl + `profiles/`;
    gConfig.acceptTermsUrl = newBaseUrl + `terms-accepted-user/`;
  }

  async autocompleteItemsAsync({ origin, scenarioName, hasInput }) {
    const result = [];

    // Generate a flowID to unique identify a series of user action. FlowId
    // allows us to link users' interaction on different UI component (Ex. autocomplete, notification)
    // We can use flowID to build the Funnel Diagram
    // This value need to always be regenerated in the entry point of an user
    // action so we overwrite the previous one.
    gFlowId = TelemetryUtils.generateUUID();

    if (this.implementation.autocompleteItemsAsync) {
      for await (const item of this.implementation.autocompleteItemsAsync(
        origin,
        scenarioName,
        hasInput
      )) {
        result.push(item);
      }
    }

    return result;
  }

  async generateUsername(browser, origin) {
    return this.implementation.generateUsername?.(browser, origin);
  }

  async offerRelayIntegration(browser, origin) {
    return this.implementation.offerRelayIntegration?.(this, browser, origin);
  }
}

export const FirefoxRelay = new RelayFeature();
PK
!<��ۜ��!modules/FirefoxRelayTelemetry.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export const FirefoxRelayTelemetry = {
  recordRelayIntegrationTelemetryEvent(
    eventObject,
    eventMethod,
    eventFlowId,
    eventExtras
  ) {
    Services.telemetry.recordEvent(
      "relay_integration",
      eventMethod,
      eventObject,
      eventFlowId ?? "",
      eventExtras ?? {}
    );
  },

  recordRelayPrefEvent(eventMethod, eventFlowId, eventExtras) {
    this.recordRelayIntegrationTelemetryEvent(
      "pref_change",
      eventMethod,
      eventFlowId,
      eventExtras
    );
  },

  recordRelayOfferedEvent(eventMethod, eventFlowId, scenarioName) {
    return this.recordRelayIntegrationTelemetryEvent(
      "offer_relay",
      eventMethod,
      eventFlowId,
      {
        scenario: scenarioName,
      }
    );
  },

  recordRelayUsernameFilledEvent(eventMethod, eventFlowId, errorCode = 0) {
    return this.recordRelayIntegrationTelemetryEvent(
      "fill_username",
      eventMethod,
      eventFlowId,
      {
        error_code: errorCode + "",
      }
    );
  },

  recordRelayReusePanelEvent(eventMethod, eventFlowId, errorCode = 0) {
    return this.recordRelayIntegrationTelemetryEvent(
      "reuse_panel",
      eventMethod,
      eventFlowId,
      {
        error_code: errorCode + "",
      }
    );
  },

  recordRelayOptInPanelEvent(eventMethod, eventFlowId, eventExtras) {
    return this.recordRelayIntegrationTelemetryEvent(
      "opt_in_panel",
      eventMethod,
      eventFlowId,
      eventExtras
    );
  },
};

export default FirefoxRelayTelemetry;
PK
!<������!modules/FirefoxRelayUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { LoginHelper } from "resource://gre/modules/LoginHelper.sys.mjs";

export const FirefoxRelayUtils = {
  isRelayInterestedField(input) {
    return (
      FirefoxRelayUtils.relayIsAvailableOrEnabled &&
      (LoginHelper.isInferredEmailField(input) ||
        LoginHelper.isInferredUsernameField(input))
    );
  },

  relayIsAvailableOrEnabled() {
    const value = Services.prefs.getStringPref(
      "signon.firefoxRelay.feature",
      undefined
    );
    return ["available", "offered", "enabled"].includes(value);
  },
};
PK
!<��!��modules/FirstStartup.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Normandy: "resource://normandy/Normandy.sys.mjs",
  TaskScheduler: "resource://gre/modules/TaskScheduler.sys.mjs",
});

const PREF_TIMEOUT = "first-startup.timeout";

/**
 * Service for blocking application startup, to be used on the first install. The intended
 * use case is for `FirstStartup` to be invoked when the application is called by an installer,
 * such as the Windows Stub Installer, to allow the application to do some first-install tasks
 * such as performance tuning and downloading critical data.
 *
 * In this scenario, the installer does not exit until the first application window appears,
 * which gives the user experience of the application starting up quickly on first install.
 */
export var FirstStartup = {
  NOT_STARTED: 0,
  IN_PROGRESS: 1,
  TIMED_OUT: 2,
  SUCCESS: 3,
  UNSUPPORTED: 4,

  _state: 0, // NOT_STARTED,
  /**
   * Initialize and run first-startup services. This will always run synchronously
   * and spin the event loop until either all required services have
   * completed, or until a timeout is reached.
   *
   * In the latter case, services are expected to run post-UI instead as usual.
   *
   * @param {boolean} newProfile
   *   True if a new profile was just created, false otherwise.
   */
  init(newProfile) {
    if (!newProfile) {
      // In this case, we actually don't want to do any FirstStartup work,
      // since a pre-existing profile was detected (presumably, we entered here
      // because a user re-installed via the stub installer when there existed
      // previous user profiles on the file system). We do, however, want to
      // measure how often this occurs.
      Glean.firstStartup.statusCode.set(this.NOT_STARTED);
      Glean.firstStartup.newProfile.set(false);
      GleanPings.firstStartup.submit();
      return;
    }

    Glean.firstStartup.newProfile.set(true);

    this._state = this.IN_PROGRESS;
    const timeout = Services.prefs.getIntPref(PREF_TIMEOUT, 30000); // default to 30 seconds
    let startingTime = Cu.now();
    let initialized = false;

    let promises = [];

    let normandyInitEndTime = null;
    if (AppConstants.MOZ_NORMANDY) {
      promises.push(
        lazy.Normandy.init({ runAsync: false }).finally(() => {
          normandyInitEndTime = Cu.now();
        })
      );
    }

    let deleteTasksEndTime = null;
    if (AppConstants.MOZ_UPDATE_AGENT) {
      // It's technically possible for a previous installation to leave an old
      // OS-level scheduled task around.  Start fresh.
      promises.push(
        lazy.TaskScheduler.deleteAllTasks()
          .catch(() => {})
          .finally(() => {
            deleteTasksEndTime = Cu.now();
          })
      );
    }

    if (promises.length) {
      Promise.all(promises).then(() => (initialized = true));

      this.elapsed = 0;
      Services.tm.spinEventLoopUntil("FirstStartup.sys.mjs:init", () => {
        this.elapsed = Math.round(Cu.now() - startingTime);
        if (this.elapsed >= timeout) {
          this._state = this.TIMED_OUT;
          return true;
        } else if (initialized) {
          this._state = this.SUCCESS;
          return true;
        }
        return false;
      });
    } else {
      this._state = this.UNSUPPORTED;
    }

    if (AppConstants.MOZ_NORMANDY) {
      Glean.firstStartup.normandyInitTime.set(
        Math.ceil(normandyInitEndTime || Cu.now() - startingTime)
      );
    }

    if (AppConstants.MOZ_UPDATE_AGENT) {
      Glean.firstStartup.deleteTasksTime.set(
        Math.ceil(deleteTasksEndTime || Cu.now() - startingTime)
      );
    }

    Glean.firstStartup.statusCode.set(this._state);
    Glean.firstStartup.elapsed.set(this.elapsed);
    GleanPings.firstStartup.submit();
  },

  get state() {
    return this._state;
  },

  /**
   * For testing only. This puts us back into the initial NOT_STARTED state.
   */
  resetForTesting() {
    this._state = this.NOT_STARTED;
  },
};
PK
!<��G�RRmodules/ForgetAboutSite.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export var ForgetAboutSite = {
  /**
   * Clear data associated with a base domain. This includes partitioned storage
   * associated with the domain. If a base domain can not be computed from
   * aDomainOrHost, data will be cleared by host instead.
   *
   * @param {string} aDomainOrHost - Domain or host to clear data for. Will be
   * converted to base domain if needed.
   * @returns {Promise} - Resolves once all matching data has been cleared.
   * Throws if any of the internal cleaners fail.
   */
  async removeDataFromBaseDomain(aDomainOrHost) {
    if (!aDomainOrHost) {
      throw new Error("aDomainOrHost can not be empty.");
    }

    let baseDomain;
    try {
      baseDomain = Services.eTLD.getBaseDomainFromHost(aDomainOrHost);
    } catch (e) {}

    let errorCount;
    if (baseDomain) {
      errorCount = await new Promise(resolve => {
        Services.clearData.deleteDataFromBaseDomain(
          baseDomain,
          true /* user request */,
          Ci.nsIClearDataService.CLEAR_FORGET_ABOUT_SITE,
          errorCode => resolve(bitCounting(errorCode))
        );
      });
    } else {
      // If we can't get a valid base domain for aDomainOrHost, fall back to
      // delete by host.
      errorCount = await new Promise(resolve => {
        Services.clearData.deleteDataFromHost(
          aDomainOrHost,
          true /* user request */,
          Ci.nsIClearDataService.CLEAR_FORGET_ABOUT_SITE,
          errorCode => resolve(bitCounting(errorCode))
        );
      });
    }

    if (errorCount !== 0) {
      throw new Error(
        `There were a total of ${errorCount} errors during removal`
      );
    }
  },

  /**
   * @deprecated This is a legacy method which clears by host only. Also it does
   * not clear all storage partitioned via dFPI. Use removeDataFromBaseDomain
   * instead.
   */
  async removeDataFromDomain(aDomain) {
    let promises = [
      new Promise(resolve =>
        Services.clearData.deleteDataFromHost(
          aDomain,
          true /* user request */,
          Ci.nsIClearDataService.CLEAR_FORGET_ABOUT_SITE,
          errorCode => resolve(bitCounting(errorCode))
        )
      ),
    ];

    try {
      let baseDomain = Services.eTLD.getBaseDomainFromHost(aDomain);

      let cookies = Services.cookies.cookies;
      let hosts = new Set();
      for (let cookie of cookies) {
        if (Services.eTLD.hasRootDomain(cookie.rawHost, baseDomain)) {
          hosts.add(cookie.rawHost);
        }
      }

      for (let host of hosts) {
        promises.push(
          new Promise(resolve =>
            Services.clearData.deleteDataFromHost(
              host,
              true /* user request */,
              Ci.nsIClearDataService.CLEAR_COOKIES,
              errorCode => resolve(bitCounting(errorCode))
            )
          )
        );
      }
    } catch (e) {
      // - NS_ERROR_HOST_IS_IP_ADDRESS: the host is in ipv4/ipv6.
      // - NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS: not enough domain parts to extract,
      //   i.e. the host is on the PSL.
      // In both these cases we should probably not try to use the host as a base
      // domain to remove more data, but we can still (try to) continue deleting the host.
      if (
        e.result != Cr.NS_ERROR_HOST_IS_IP_ADDRESS &&
        e.result != Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
      ) {
        throw e;
      }
    }

    let errorCount = (await Promise.all(promises)).reduce((a, b) => a + b);

    if (errorCount !== 0) {
      throw new Error(
        `There were a total of ${errorCount} errors during removal`
      );
    }
  },
};

function bitCounting(value) {
  // To know more about how to count bits set to 1 in a numeric value, see this
  // interesting article:
  // https://blogs.msdn.microsoft.com/jeuge/2005/06/08/bit-fiddling-3/
  const count =
    value - ((value >> 1) & 0o33333333333) - ((value >> 2) & 0o11111111111);
  return ((count + (count >> 3)) & 0o30707070707) % 63;
}
PK
!<�lʛ����modules/FormHistory.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * FormHistory
 *
 * Used to store values that have been entered into forms which may later
 * be used to automatically fill in the values when the form is visited again.
 *
 * async search(terms, queryData)
 *   Look up values that have been previously stored.
 *     terms - array of terms to return data for
 *     queryData - object that contains the query terms
 *       The query object contains properties for each search criteria to match, where the value
 *       of the property specifies the value that term must have. For example,
 *       { term1: value1, term2: value2 }
 *   Resolves to an array containing the found results. Each element in
 *   the array is an object containing a property for each search term
 *   specified by 'terms'.
 *   Rejects in case of errors.
 * async count(queryData)
 *   Find the number of stored entries that match the given criteria.
 *     queryData - array of objects that indicate the query. See the search method for details.
 *   Resolves to the number of found entries.
 *   Rejects in case of errors.
 * async update(changes)
 *    Write data to form history storage.
 *      changes - an array of changes to be made. If only one change is to be made, it
 *                may be passed as an object rather than a one-element array.
 *        Each change object is of the form:
 *          { op: operation, term1: value1, term2: value2, ... }
 *        Valid operations are:
 *          add - add a new entry
 *          update - update an existing entry
 *          remove - remove an entry
 *          bump - update the last accessed time on an entry
 *        The terms specified allow matching of one or more specific entries. If no terms
 *        are specified then all entries are matched. This means that { op: "remove" } is
 *        used to remove all entries and clear the form history.
 *   Resolves once the operation is complete.
 *   Rejects in case of errors.
 * async getAutoCompeteResults(searchString, params, callback)
 *   Retrieve an array of form history values suitable for display in an autocomplete list.
 *     searchString - the string to search for, typically the entered value of a textbox
 *     params - zero or more filter arguments:
 *       fieldname - form field name
 *       agedWeight
 *       bucketSize
 *       expiryDate
 *       maxTimeGroundings
 *       timeGroupingSize
 *       prefixWeight
 *       boundaryWeight
 *       source
 *     callback - callback that is invoked for each result, the second argument
 *                is a function that can be used to cancel the operation.
 *                Each result is an object with four properties:
 *                  text, textLowerCase, frecency, totalScore
 *   Resolves with an array of results, once the operation is complete.
 *   Rejects in case of errors.
 *
 * schemaVersion
 *   This property holds the version of the database schema
 *
 * Terms:
 *  guid - entry identifier. For 'add', a guid will be generated.
 *  fieldname - form field name
 *  value - form value
 *  timesUsed - the number of times the entry has been accessed
 *  firstUsed - the time the the entry was first created
 *  lastUsed - the time the entry was last accessed
 *  firstUsedStart - search for entries created after or at this time
 *  firstUsedEnd - search for entries created before or at this time
 *  lastUsedStart - search for entries last accessed after or at this time
 *  lastUsedEnd - search for entries last accessed before or at this time
 *  newGuid - a special case valid only for 'update' and allows the guid for
 *            an existing record to be updated. The 'guid' term is the only
 *            other term which can be used (ie, you can not also specify a
 *            fieldname, value etc) and indicates the guid of the existing
 *            record that should be updated.
 */

export let FormHistory;

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

const DB_SCHEMA_VERSION = 5;
const DAY_IN_MS = 86400000; // 1 day in milliseconds
const MAX_SEARCH_TOKENS = 10;
const DB_FILENAME = "formhistory.sqlite";

var supportsDeletedTable = AppConstants.platform == "android";

const wait = ms => new Promise(res => lazy.setTimeout(res, ms));

var Prefs = {
  _initialized: false,

  get(name) {
    this.ensureInitialized();
    return this[`_${name}`];
  },

  ensureInitialized() {
    if (this._initialized) {
      return;
    }

    this._initialized = true;

    this._prefBranch = Services.prefs.getBranch("browser.formfill.");
    this._prefBranch.addObserver("", this, true);

    this._agedWeight = this._prefBranch.getIntPref("agedWeight");
    this._boundaryWeight = this._prefBranch.getIntPref("boundaryWeight");
    this._bucketSize = this._prefBranch.getIntPref("bucketSize");
    this._debug = this._prefBranch.getBoolPref("debug");
    this._enabled = this._prefBranch.getBoolPref("enable");
    this._expireDays = this._prefBranch.getIntPref("expire_days");
    this._maxTimeGroupings = this._prefBranch.getIntPref("maxTimeGroupings");
    this._prefixWeight = this._prefBranch.getIntPref("prefixWeight");
    this._timeGroupingSize =
      this._prefBranch.getIntPref("timeGroupingSize") * 1000 * 1000;
  },

  observe(_subject, topic, data) {
    if (topic == "nsPref:changed") {
      let prefName = data;
      log(`got change to ${prefName} preference`);

      switch (prefName) {
        case "agedWeight":
          this._agedWeight = this._prefBranch.getIntPref(prefName);
          break;
        case "boundaryWeight":
          this._boundaryWeight = this._prefBranch.getIntPref(prefName);
          break;
        case "bucketSize":
          this._bucketSize = this._prefBranch.getIntPref(prefName);
          break;
        case "debug":
          this._debug = this._prefBranch.getBoolPref(prefName);
          break;
        case "enable":
          this._enabled = this._prefBranch.getBoolPref(prefName);
          break;
        case "expire_days":
          this._expireDays = this._prefBranch.getIntPref("expire_days");
          break;
        case "maxTimeGroupings":
          this._maxTimeGroupings = this._prefBranch.getIntPref(prefName);
          break;
        case "prefixWeight":
          this._prefixWeight = this._prefBranch.getIntPref(prefName);
          break;
        case "timeGroupingSize":
          this._timeGroupingSize =
            this._prefBranch.getIntPref(prefName) * 1000 * 1000;
          break;
        default:
          log(`Oops! Pref ${prefName} not handled, change ignored.`);
          break;
      }
    }
  },

  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
  ]),
};

function log(aMessage) {
  if (Prefs.get("debug")) {
    Services.console.logStringMessage("FormHistory: " + aMessage);
  }
}

function sendNotification(aType, aData) {
  if (typeof aData == "string") {
    const strWrapper = Cc["@mozilla.org/supports-string;1"].createInstance(
      Ci.nsISupportsString
    );
    strWrapper.data = aData;
    aData = strWrapper;
  } else if (typeof aData == "number") {
    const intWrapper = Cc["@mozilla.org/supports-PRInt64;1"].createInstance(
      Ci.nsISupportsPRInt64
    );
    intWrapper.data = aData;
    aData = intWrapper;
  } else if (aData) {
    throw Components.Exception(
      `Invalid type ${typeof aType} passed to sendNotification`,
      Cr.NS_ERROR_ILLEGAL_VALUE
    );
  }

  Services.obs.notifyObservers(aData, "satchel-storage-changed", aType);
}

/**
 * Current database schema
 */

const dbSchema = {
  tables: {
    moz_formhistory: {
      id: "INTEGER PRIMARY KEY",
      fieldname: "TEXT NOT NULL",
      value: "TEXT NOT NULL",
      timesUsed: "INTEGER",
      firstUsed: "INTEGER",
      lastUsed: "INTEGER",
      guid: "TEXT",
    },
    moz_deleted_formhistory: {
      id: "INTEGER PRIMARY KEY",
      timeDeleted: "INTEGER",
      guid: "TEXT",
    },
    moz_sources: {
      id: "INTEGER PRIMARY KEY",
      source: "TEXT NOT NULL",
    },
    moz_history_to_sources: {
      history_id: "INTEGER",
      source_id: "INTEGER",
      SQL: `
        PRIMARY KEY (history_id, source_id),
        FOREIGN KEY (history_id) REFERENCES moz_formhistory(id) ON DELETE CASCADE,
        FOREIGN KEY (source_id) REFERENCES moz_sources(id) ON DELETE CASCADE
      `,
    },
  },
  indices: {
    moz_formhistory_index: {
      table: "moz_formhistory",
      columns: ["fieldname"],
    },
    moz_formhistory_lastused_index: {
      table: "moz_formhistory",
      columns: ["lastUsed"],
    },
    moz_formhistory_guid_index: {
      table: "moz_formhistory",
      columns: ["guid"],
    },
  },
};

/**
 * Validating and processing API querying data
 */

const validFields = [
  "fieldname",
  "firstUsed",
  "guid",
  "lastUsed",
  "source",
  "timesUsed",
  "value",
];

const searchFilters = [
  "firstUsedStart",
  "firstUsedEnd",
  "lastUsedStart",
  "lastUsedEnd",
  "source",
];

function validateOpData(aData, aDataType) {
  let thisValidFields = validFields;
  // A special case to update the GUID - in this case there can be a 'newGuid'
  // field and of the normally valid fields, only 'guid' is accepted.
  if (aDataType == "Update" && "newGuid" in aData) {
    thisValidFields = ["guid", "newGuid"];
  }
  for (const field in aData) {
    if (field != "op" && !thisValidFields.includes(field)) {
      throw Components.Exception(
        `${aDataType} query contains an unrecognized field: ${field}`,
        Cr.NS_ERROR_ILLEGAL_VALUE
      );
    }
  }
  return aData;
}

function validateSearchData(aData, aDataType) {
  for (const field in aData) {
    if (
      field != "op" &&
      !validFields.includes(field) &&
      !searchFilters.includes(field)
    ) {
      throw Components.Exception(
        `${aDataType} query contains an unrecognized field: ${field}`,
        Cr.NS_ERROR_ILLEGAL_VALUE
      );
    }
  }
}

function makeQueryPredicates(aQueryData, delimiter = " AND ") {
  const params = {};
  const queryTerms = Object.keys(aQueryData)
    .filter(field => aQueryData[field] !== undefined)
    .map(field => {
      params[field] = aQueryData[field];
      switch (field) {
        case "firstUsedStart":
          return "firstUsed >= :" + field;
        case "firstUsedEnd":
          return "firstUsed <= :" + field;
        case "lastUsedStart":
          return "lastUsed >= :" + field;
        case "lastUsedEnd":
          return "lastUsed <= :" + field;
        case "source":
          return `EXISTS(
            SELECT 1 FROM moz_history_to_sources
            JOIN moz_sources s ON s.id = source_id
            WHERE source = :${field}
              AND history_id = moz_formhistory.id
          )`;
      }
      return field + " = :" + field;
    })
    .join(delimiter);
  return { queryTerms, params };
}

function generateGUID() {
  // string like: "{f60d9eac-9421-4abc-8491-8e8322b063d4}"
  const uuid = Services.uuid.generateUUID().toString();
  let raw = ""; // A string with the low bytes set to random values
  let bytes = 0;
  for (let i = 1; bytes < 12; i += 2) {
    // Skip dashes
    if (uuid[i] == "-") {
      i++;
    }
    const hexVal = parseInt(uuid[i] + uuid[i + 1], 16);
    raw += String.fromCharCode(hexVal);
    bytes++;
  }
  return btoa(raw);
}

var Migrators = {
  // Bug 506402 - Adds deleted form history table.
  async dbAsyncMigrateToVersion4(conn) {
    const tableName = "moz_deleted_formhistory";
    const tableExists = await conn.tableExists(tableName);
    if (!tableExists) {
      await createTable(conn, tableName);
    }
  },

  // Bug 1654862 - Adds sources and moz_history_to_sources tables.
  async dbAsyncMigrateToVersion5(conn) {
    if (!(await conn.tableExists("moz_sources"))) {
      for (const tableName of ["moz_history_to_sources", "moz_sources"]) {
        await createTable(conn, tableName);
      }
    }
  },
};

/**
 * @typedef {object} InsertQueryData
 * @property {object} updatedChange
 *           A change requested by FormHistory.
 * @property {string} query
 *           The insert query string.
 */

/**
 * Prepares a query and some default parameters when inserting an entry
 * to the database.
 *
 * @param {object} change
 *        The change requested by FormHistory.
 * @param {number} now
 *        The current timestamp in microseconds.
 * @returns {InsertQueryData}
 *          The query information needed to pass along to the database.
 */
function prepareInsertQuery(change, now) {
  const params = {};
  for (const key of new Set([
    ...Object.keys(change),
    // These must always be NOT NULL.
    "firstUsed",
    "lastUsed",
    "timesUsed",
  ])) {
    switch (key) {
      case "fieldname":
      case "guid":
      case "value":
        params[key] = change[key];
        break;
      case "firstUsed":
      case "lastUsed":
        params[key] = change[key] || now;
        break;
      case "timesUsed":
        params[key] = change[key] || 1;
        break;
      default:
      // Skip unnecessary properties.
    }
  }

  return {
    query: `
      INSERT INTO moz_formhistory
        (fieldname, value, timesUsed, firstUsed, lastUsed, guid)
      VALUES (:fieldname, :value, :timesUsed, :firstUsed, :lastUsed, :guid)`,
    params,
  };
}

// There is a fieldname / value uniqueness constraint that's at this time
// only enforced at this level. This Map maps fieldnames => values that
// are in the process of being inserted into the database so that we know
// not to try to insert the same ones on top. Attempts to do so will be
// ignored.
var InProgressInserts = {
  _inProgress: new Map(),

  add(fieldname, value) {
    const fieldnameSet = this._inProgress.get(fieldname);
    if (!fieldnameSet) {
      this._inProgress.set(fieldname, new Set([value]));
      return true;
    }

    if (!fieldnameSet.has(value)) {
      fieldnameSet.add(value);
      return true;
    }

    return false;
  },

  clear(fieldnamesAndValues) {
    for (const [fieldname, value] of fieldnamesAndValues) {
      const fieldnameSet = this._inProgress.get(fieldname);
      if (fieldnameSet?.delete(value) && fieldnameSet.size == 0) {
        this._inProgress.delete(fieldname);
      }
    }
  },
};

function getAddSourceToGuidQueries(source, guid) {
  return [
    {
      query: `INSERT OR IGNORE INTO moz_sources (source) VALUES (:source)`,
      params: { source },
    },
    {
      query: `
        INSERT OR IGNORE INTO moz_history_to_sources (history_id, source_id)
        VALUES(
          (SELECT id FROM moz_formhistory WHERE guid = :guid),
          (SELECT id FROM moz_sources WHERE source = :source)
        )
      `,
      params: { guid, source },
    },
  ];
}

/**
 * Constructs and executes database statements from a pre-processed list of
 * inputted changes.
 *
 * @param {Array.<object>} aChanges changes to form history
 */
// XXX This should be split up and the complexity reduced.
// eslint-disable-next-line complexity
async function updateFormHistoryWrite(aChanges) {
  log("updateFormHistoryWrite  " + aChanges.length);

  // pass 'now' down so that every entry in the batch has the same timestamp
  const now = Date.now() * 1000;
  let queries = [];
  const notifications = [];
  const adds = [];
  const conn = await FormHistory.db;

  for (const change of aChanges) {
    const operation = change.op;
    delete change.op;
    switch (operation) {
      case "remove": {
        log("Remove from form history  " + change);
        const { queryTerms, params } = makeQueryPredicates(change);

        // If source is defined, we only remove the source relation, if the
        // consumer intends to remove the value from everywhere, then they
        // should not pass source. This gives full control to the caller.
        if (change.source) {
          await conn.executeCached(
            `DELETE FROM moz_history_to_sources
              WHERE source_id = (
                SELECT id FROM moz_sources WHERE source = :source
              )
              AND history_id = (
                SELECT id FROM moz_formhistory WHERE ${queryTerms}
              )
            `,
            params
          );
          break;
        }

        // Fetch the GUIDs we are going to delete.
        try {
          let query = "SELECT guid FROM moz_formhistory";
          if (queryTerms) {
            query += " WHERE " + queryTerms;
          }

          await conn.executeCached(query, params, row => {
            notifications.push([
              "formhistory-remove",
              row.getResultByName("guid"),
            ]);
          });
        } catch (e) {
          log("Error getting guids from moz_formhistory: " + e);
        }

        if (supportsDeletedTable) {
          log("Moving to deleted table " + change);
          let query = "INSERT INTO moz_deleted_formhistory (guid, timeDeleted)";

          // TODO: Add these items to the deleted items table once we've sorted
          //       out the issues from bug 756701
          if (change.guid || queryTerms) {
            query += change.guid
              ? " VALUES (:guid, :timeDeleted)"
              : " SELECT guid, :timeDeleted FROM moz_formhistory WHERE " +
                queryTerms;
            queries.push({
              query,
              params: Object.assign({ timeDeleted: now }, params),
            });
          }
        }

        let query = "DELETE FROM moz_formhistory";
        if (queryTerms) {
          log("removeEntries");
          query += " WHERE " + queryTerms;
        } else {
          log("removeAllEntries");
          // Not specifying any fields means we should remove all entries. We
          // won't need to modify the query in this case.
        }

        queries.push({ query, params });
        // Expire orphan sources.
        queries.push({
          query: `
            DELETE FROM moz_sources WHERE id NOT IN (
              SELECT DISTINCT source_id FROM moz_history_to_sources
            )`,
        });
        break;
      }
      case "update": {
        log("Update form history " + change);
        const guid = change.guid;
        delete change.guid;
        // a special case for updating the GUID - the new value can be
        // specified in newGuid.
        if (change.newGuid) {
          change.guid = change.newGuid;
          delete change.newGuid;
        }

        let query = "UPDATE moz_formhistory SET ";
        let { queryTerms, params } = makeQueryPredicates(change, ", ");
        if (!queryTerms) {
          throw Components.Exception(
            "Update query must define fields to modify.",
            Cr.NS_ERROR_ILLEGAL_VALUE
          );
        }
        query += queryTerms + " WHERE guid = :existing_guid";
        queries.push({
          query,
          params: Object.assign({ existing_guid: guid }, params),
        });

        notifications.push(["formhistory-update", guid]);

        // Source is ignored for "update" operations, since it's not really
        // common to change the source of a value, and anyway currently this is
        // mostly used to update guids.
        break;
      }
      case "bump": {
        log("Bump form history " + change);
        if (change.guid) {
          const query =
            "UPDATE moz_formhistory " +
            "SET timesUsed = timesUsed + 1, lastUsed = :lastUsed WHERE guid = :guid";
          const queryParams = {
            lastUsed: now,
            guid: change.guid,
          };

          queries.push({ query, params: queryParams });
          notifications.push(["formhistory-update", change.guid]);
        } else {
          if (!InProgressInserts.add(change.fieldname, change.value)) {
            // This updateFormHistoryWrite call, or a previous one, is already
            // going to add this fieldname / value pair, so we can ignore this.
            continue;
          }
          adds.push([change.fieldname, change.value]);
          change.guid = generateGUID();
          const { query, params } = prepareInsertQuery(change, now);
          queries.push({ query, params });
          notifications.push(["formhistory-add", params.guid]);
        }

        if (change.source) {
          queries = queries.concat(
            getAddSourceToGuidQueries(change.source, change.guid)
          );
        }
        break;
      }
      case "add": {
        if (!InProgressInserts.add(change.fieldname, change.value)) {
          // This updateFormHistoryWrite call, or a previous one, is already
          // going to add this fieldname / value pair, so we can ignore this.
          continue;
        }
        adds.push([change.fieldname, change.value]);

        log("Add to form history " + change);
        if (!change.guid) {
          change.guid = generateGUID();
        }

        const { query, params } = prepareInsertQuery(change, now);
        queries.push({ query, params });

        notifications.push(["formhistory-add", params.guid]);

        if (change.source) {
          queries = queries.concat(
            getAddSourceToGuidQueries(change.source, change.guid)
          );
        }
        break;
      }
      default: {
        // We should've already guaranteed that change.op is one of the above
        throw Components.Exception(
          "Invalid operation " + operation,
          Cr.NS_ERROR_ILLEGAL_VALUE
        );
      }
    }
  }

  try {
    await conn.executeTransaction(async () => {
      for (const { query, params } of queries) {
        await conn.executeCached(query, params);
      }
    });
    for (const [notification, param] of notifications) {
      // We're either sending a GUID or nothing at all.
      sendNotification(notification, param);
    }
  } finally {
    InProgressInserts.clear(adds);
  }
}

/**
 * Functions that expire entries in form history and shrinks database
 * afterwards as necessary initiated by expireOldEntries.
 */

/**
 * Removes entries from database.
 *
 * @param {number} aExpireTime expiration timestamp
 * @param {number} aBeginningCount numer of entries at first
 * @returns {Promise} resolved once the work is complete
 */
async function expireOldEntriesDeletion(aExpireTime, aBeginningCount) {
  log(`expireOldEntriesDeletion(${aExpireTime},${aBeginningCount})`);

  await FormHistory.update([
    {
      op: "remove",
      lastUsedEnd: aExpireTime,
    },
  ]);
  await expireOldEntriesVacuum(aExpireTime, aBeginningCount);
}

/**
 * Counts number of entries removed and shrinks database as necessary.
 *
 * @param {number} aExpireTime expiration timestamp
 * @param {number} aBeginningCount number of entries at first
 */
async function expireOldEntriesVacuum(aExpireTime, aBeginningCount) {
  const count = await FormHistory.count({});
  if (aBeginningCount - count > 500) {
    log("expireOldEntriesVacuum");
    const conn = await FormHistory.db;
    await conn.executeCached("VACUUM");
  }
  sendNotification("formhistory-expireoldentries", aExpireTime);
}

async function createTable(conn, tableName) {
  const table = dbSchema.tables[tableName];
  const columns = Object.keys(table)
    .filter(col => col != "SQL")
    .map(col => [col, table[col]].join(" "))
    .join(", ");
  const no_rowid = Object.keys(table).includes("id") ? "" : "WITHOUT ROWID";
  log(`Creating table ${tableName} with ${columns}`);
  await conn.execute(
    `CREATE TABLE ${tableName} (
      ${columns}
      ${table.SQL ? "," + table.SQL : ""}
    ) ${no_rowid}`
  );
}

/**
 * Database creation and access. Used by FormHistory and some of the
 * utility functions, but is not exposed to the outside world.
 *
 * @class
 */
var DB = {
  // Once we establish a database connection, we have to hold a reference
  // to it so that it won't get GC'd.
  _instance: null,
  // MAX_ATTEMPTS is how many times we'll try to establish a connection
  // or migrate a database before giving up.
  MAX_ATTEMPTS: 4,

  /** String representing where the FormHistory database is on the filesystem */
  get path() {
    return PathUtils.join(PathUtils.profileDir, DB_FILENAME);
  },

  /**
   * Sets up and returns a connection to the FormHistory database. The
   * connection also registers itself with AsyncShutdown so that the
   * connection is closed on when the profile-before-change observer
   * notification is fired.
   *
   * @returns {Promise<OpenedConnection>}
   *        A {@link toolkit/modules/Sqlite.sys.mjs} connection to the database.
   * @throws
   *        If connecting to the database, or migrating the database
   *        failed after MAX_ATTEMPTS attempts, this will reject
   *        with the Sqlite.sys.mjs error.
   */
  get conn() {
    delete this.conn;
    const conn = (async () => {
      try {
        this._instance = await this._establishConn();
      } catch (e) {
        log("Failed to establish database connection: " + e);
        throw e;
      }

      return this._instance;
    })();

    return (this.conn = conn);
  },

  // Private functions

  /**
   * Tries to connect to the Sqlite database at this.path, and then
   * migrates the database as necessary. If any of the steps to do this
   * fail, this function should re-enter itself with an incremented
   * attemptNum so that another attempt can be made after backing up
   * and deleting the old database.
   *
   * @async
   * @param {number} attemptNum
   *        The optional number of the attempt that is being made to connect
   *        to the database. Defaults to 0.
   * @returns {Promise<OpenedConnection>}
   *        A {@link toolkit/modules/Sqlite.sys.mjs} connection to the database.
   * @throws
   *        If connecting to the database, or migrating the database
   *        failed after MAX_ATTEMPTS attempts, this will reject
   *        with the Sqlite.sys.mjs error.
   */
  async _establishConn(attemptNum = 0) {
    log(`Establishing database connection - attempt # ${attemptNum}`);
    let conn;
    try {
      conn = await lazy.Sqlite.openConnection({ path: this.path });
      lazy.Sqlite.shutdown.addBlocker("Closing FormHistory database.", () =>
        conn.close()
      );
    } catch (e) {
      // retrying.
      // If error is a db corruption error, backup the database and create a new one.
      // Else, use an exponential backoff algorithm to restart up to MAX_ATTEMPTS times.
      if (attemptNum < this.MAX_ATTEMPTS) {
        log(`Establishing connection failed due with error ${e.result}`);

        if (e.result === Cr.NS_ERROR_FILE_CORRUPTED) {
          log("Corrupt database, resetting database");
          await this._failover(conn);
        } else {
          if (conn) {
            await conn.close();
          }
          // retrying with an exponential backoff
          await wait(2 ** attemptNum * 10);
        }

        return this._establishConn(++attemptNum);
      }

      if (conn) {
        await conn.close();
      }
      log("Establishing connection failed too many times. Giving up.");
      throw e;
    }

    try {
      // Enable foreign keys support.
      await conn.execute("PRAGMA foreign_keys = ON");

      const dbVersion = parseInt(await conn.getSchemaVersion(), 10);

      // Case 1: Database is up to date and we're ready to go.
      if (dbVersion == DB_SCHEMA_VERSION) {
        return conn;
      }

      // Case 2: Downgrade
      if (dbVersion > DB_SCHEMA_VERSION) {
        log("Downgrading to version " + DB_SCHEMA_VERSION);
        // User's DB is newer. Sanity check that our expected columns are
        // present, and if so mark the lower version and merrily continue
        // on. If the columns are borked, something is wrong so blow away
        // the DB and start from scratch. [Future incompatible upgrades
        // should switch to a different table or file.]
        if (!(await this._expectedColumnsPresent(conn))) {
          throw Components.Exception(
            "DB is missing expected columns",
            Cr.NS_ERROR_FILE_CORRUPTED
          );
        }

        // Change the stored version to the current version. If the user
        // runs the newer code again, it will see the lower version number
        // and re-upgrade (to fixup any entries the old code added).
        await conn.setSchemaVersion(DB_SCHEMA_VERSION);
        return conn;
      }

      // Case 3: Very old database that cannot be migrated.
      //
      // When FormHistory is released, we will no longer support the various
      // schema versions prior to this release that nsIFormHistory2 once did.
      // We'll throw an NS_ERROR_FILE_CORRUPTED, which should cause us to wipe
      // out this DB and create a new one (unless this is our MAX_ATTEMPTS
      // attempt).
      if (dbVersion > 0 && dbVersion < 3) {
        throw Components.Exception(
          "DB version is unsupported.",
          Cr.NS_ERROR_FILE_CORRUPTED
        );
      }

      if (dbVersion == 0) {
        // Case 4: New database
        await conn.executeTransaction(async () => {
          log("Creating DB -- tables");
          for (const name in dbSchema.tables) {
            await createTable(conn, name);
          }

          log("Creating DB -- indices");
          for (const name in dbSchema.indices) {
            const index = dbSchema.indices[name];
            const statement = `CREATE INDEX IF NOT EXISTS ${name} ON ${
              index.table
            }(${index.columns.join(", ")})`;
            await conn.execute(statement);
          }
        });
      } else {
        // Case 5: Old database requiring a migration
        await conn.executeTransaction(async () => {
          for (let v = dbVersion + 1; v <= DB_SCHEMA_VERSION; v++) {
            log(`Upgrading to version ${v}...`);
            await Migrators["dbAsyncMigrateToVersion" + v](conn);
          }
        });
      }

      await conn.setSchemaVersion(DB_SCHEMA_VERSION);

      return conn;
    } catch (e) {
      if (e.result != Cr.NS_ERROR_FILE_CORRUPTED) {
        throw e;
      }

      if (attemptNum < this.MAX_ATTEMPTS) {
        log("Setting up database failed.");
        await this._failover(conn);
        return this._establishConn(++attemptNum);
      }

      if (conn) {
        await conn.close();
      }

      log("Setting up database failed too many times. Giving up.");

      throw e;
    }
  },

  /**
   * Closes a connection to the database, then backs up the database before
   * deleting it.
   *
   * @async
   * @param {SqliteConnection | null} conn
   *        The connection to the database that we failed to establish or
   *        migrate.
   * @throws If any file operations fail.
   */
  async _failover(conn) {
    log("Cleaning up DB file - close & remove & backup.");
    if (conn) {
      await conn.close();
    }
    const backupFile = this.path + ".corrupt";
    const uniquePath = await IOUtils.createUniqueFile(
      PathUtils.parent(backupFile),
      PathUtils.filename(backupFile),
      0o600
    );
    await IOUtils.copy(this.path, uniquePath);
    await IOUtils.remove(this.path);
    log("Completed DB cleanup.");
  },

  /**
   * Tests that a database connection contains the tables that we expect.
   *
   * @async
   * @param {SqliteConnection | null} conn
   *        The connection to the database that we're testing.
   * @returns {Promise<boolean>} true if all expected columns are present.
   */
  async _expectedColumnsPresent(conn) {
    for (const name in dbSchema.tables) {
      const table = dbSchema.tables[name];
      const columns = Object.keys(table).filter(col => col != "SQL");
      const query = `SELECT ${columns.join(", ")} FROM ${name}`;
      try {
        await conn.execute(query, null, (_row, cancel) => {
          // One row is enough to let us know this worked.
          cancel();
        });
      } catch (e) {
        return false;
      }
    }

    log("Verified that expected columns are present in DB.");
    return true;
  },
};

FormHistory = {
  get db() {
    return DB.conn;
  },

  get enabled() {
    return Prefs.get("enabled");
  },

  async search(aSelectTerms, aSearchData, aRowFunc) {
    // if no terms selected, select everything
    if (!aSelectTerms) {
      // Source is not a valid column in moz_formhistory.
      aSelectTerms = validFields.filter(f => f != "source");
    }

    validateSearchData(aSearchData, "Search");

    let query = `SELECT ${aSelectTerms.join(", ")} FROM moz_formhistory`;
    const { queryTerms, params } = makeQueryPredicates(aSearchData);
    if (queryTerms) {
      query += " WHERE " + queryTerms;
    }

    const allResults = [];

    const conn = await this.db;
    await conn.executeCached(query, params, row => {
      const result = {};
      for (const field of aSelectTerms) {
        result[field] = row.getResultByName(field);
      }
      aRowFunc?.(result);
      allResults.push(result);
    });

    return allResults;
  },

  async count(aSearchData) {
    validateSearchData(aSearchData, "Count");

    let query = "SELECT COUNT(*) AS numEntries FROM moz_formhistory";
    const { queryTerms, params } = makeQueryPredicates(aSearchData);
    if (queryTerms) {
      query += " WHERE " + queryTerms;
    }

    const conn = await this.db;
    const rows = await conn.executeCached(query, params);
    return rows[0].getResultByName("numEntries");
  },

  async update(aChanges) {
    function validIdentifier(change) {
      // The identifier is only valid if one of either the guid
      // or the (fieldname/value) are set (so an X-OR)
      return Boolean(change.guid) != Boolean(change.fieldname && change.value);
    }

    if (!("length" in aChanges)) {
      aChanges = [aChanges];
    }

    const isRemoveOperation = aChanges.every(change => change?.op == "remove");
    if (!this.enabled && !isRemoveOperation) {
      throw new Error(
        "Form history is disabled, only remove operations are allowed"
      );
    }

    for (const change of aChanges) {
      switch (change.op) {
        case "remove":
          validateSearchData(change, "Remove");
          continue;
        case "update":
          if (validIdentifier(change)) {
            validateOpData(change, "Update");
            if (change.guid) {
              continue;
            }
          } else {
            throw Components.Exception(
              "update op='update' does not correctly reference a entry.",
              Cr.NS_ERROR_ILLEGAL_VALUE
            );
          }
          break;
        case "bump":
          if (validIdentifier(change)) {
            validateOpData(change, "Bump");
            if (change.guid) {
              continue;
            }
          } else {
            throw Components.Exception(
              "update op='bump' does not correctly reference a entry.",
              Cr.NS_ERROR_ILLEGAL_VALUE
            );
          }
          break;
        case "add":
          if (change.fieldname && change.value) {
            validateOpData(change, "Add");
          } else {
            throw Components.Exception(
              "update op='add' must have a fieldname and a value.",
              Cr.NS_ERROR_ILLEGAL_VALUE
            );
          }
          break;
        default:
          throw Components.Exception(
            "update does not recognize op='" + change.op + "'",
            Cr.NS_ERROR_ILLEGAL_VALUE
          );
      }

      const results = await FormHistory.search(["guid"], {
        fieldname: change.fieldname,
        value: change.value,
      });
      if (results.length > 1) {
        const error =
          "Database contains multiple entries with the same fieldname/value pair.";
        log(error);
        throw new Error(error);
      }
      change.guid = results[0]?.guid;
    }

    await updateFormHistoryWrite(aChanges);
  },

  /**
   * Gets results for the autocomplete widget.
   *
   * @param {string} searchString The string to search for.
   * @param {object} params zero or more filter properties:
   *   - fieldname
   *   - source
   * @param {Function} [isCancelled] optional function that can return true
   *   to cancel result retrieval
   * @returns {Promise<Array>}
   *   An array of results. If the search was canceled it will be an empty array.
   */
  async getAutoCompleteResults(searchString, params, isCancelled) {
    // only do substring matching when the search string contains more than one character
    let searchTokens;
    let where = "";
    let boundaryCalc = "";

    params = {
      agedWeight: Prefs.get("agedWeight"),
      bucketSize: Prefs.get("bucketSize"),
      expiryDate:
        1000 * (Date.now() - Prefs.get("expireDays") * 24 * 60 * 60 * 1000),
      maxTimeGroupings: Prefs.get("maxTimeGroupings"),
      timeGroupingSize: Prefs.get("timeGroupingSize"),
      prefixWeight: Prefs.get("prefixWeight"),
      boundaryWeight: Prefs.get("boundaryWeight"),
      ...params,
    };

    if (searchString.length >= 1) {
      params.valuePrefix = searchString.replaceAll("/", "//") + "%";
    }

    if (searchString.length > 1) {
      searchTokens = searchString.split(/\s+/);

      // build up the word boundary and prefix match bonus calculation
      boundaryCalc =
        "MAX(1, :prefixWeight * (value LIKE :valuePrefix ESCAPE '/') + (";
      // for each word, calculate word boundary weights for the SELECT clause and
      // add word to the WHERE clause of the query
      let tokenCalc = [];
      let searchTokenCount = Math.min(searchTokens.length, MAX_SEARCH_TOKENS);
      for (let i = 0; i < searchTokenCount; i++) {
        let escapedToken = searchTokens[i].replaceAll("/", "//");
        params["tokenBegin" + i] = escapedToken + "%";
        params["tokenBoundary" + i] = "% " + escapedToken + "%";
        params["tokenContains" + i] = "%" + escapedToken + "%";

        tokenCalc.push(
          `(value LIKE :tokenBegin${i} ESCAPE '/') + (value LIKE :tokenBoundary${i} ESCAPE '/')`
        );
        where += `AND (value LIKE :tokenContains${i} ESCAPE '/') `;
      }
      // add more weight if we have a traditional prefix match and
      // multiply boundary bonuses by boundary weight
      boundaryCalc += tokenCalc.join(" + ") + ") * :boundaryWeight)";
    } else if (searchString.length == 1) {
      where = "AND (value LIKE :valuePrefix ESCAPE '/') ";
      boundaryCalc = "1";
      delete params.prefixWeight;
      delete params.boundaryWeight;
    } else {
      where = "";
      boundaryCalc = "1";
      delete params.prefixWeight;
      delete params.boundaryWeight;
    }

    params.now = Date.now() * 1000; // convert from ms to microseconds

    if (params.source) {
      where += `AND EXISTS(
        SELECT 1 FROM moz_history_to_sources
        JOIN moz_sources s ON s.id = source_id
        WHERE source = :source
          AND history_id = moz_formhistory.id
      )`;
    }

    /* Three factors in the frecency calculation for an entry (in order of use in calculation):
     * 1) average number of times used - items used more are ranked higher
     * 2) how recently it was last used - items used recently are ranked higher
     * 3) additional weight for aged entries surviving expiry - these entries are relevant
     *    since they have been used multiple times over a large time span so rank them higher
     * The score is then divided by the bucket size and we round the result so that entries
     * with a very similar frecency are bucketed together with an alphabetical sort. This is
     * to reduce the amount of moving around by entries while typing.
     */

    const query =
      "/* do not warn (bug 496471): can't use an index */ " +
      "SELECT value, guid, " +
      "ROUND( " +
      "timesUsed / MAX(1.0, (lastUsed - firstUsed) / :timeGroupingSize) * " +
      "MAX(1.0, :maxTimeGroupings - (:now - lastUsed) / :timeGroupingSize) * " +
      "MAX(1.0, :agedWeight * (firstUsed < :expiryDate)) / " +
      ":bucketSize " +
      ", 3) AS frecency, " +
      boundaryCalc +
      " AS boundaryBonuses " +
      "FROM moz_formhistory " +
      "WHERE fieldname=:fieldname " +
      where +
      "ORDER BY ROUND(frecency * boundaryBonuses) DESC, UPPER(value) ASC";

    let results = [];
    const conn = await this.db;
    await conn.executeCached(query, params, (row, cancel) => {
      if (isCancelled?.()) {
        cancel();
        results = [];
        return;
      }

      const value = row.getResultByName("value");
      const guid = row.getResultByName("guid");
      const frecency = row.getResultByName("frecency");
      const entry = {
        text: value,
        guid,
        textLowerCase: value.toLowerCase(),
        frecency,
        totalScore: Math.round(
          frecency * row.getResultByName("boundaryBonuses")
        ),
      };
      results.push(entry);
    });
    return results;
  },

  // This is used only so that the test can verify deleted table support.
  get _supportsDeletedTable() {
    return supportsDeletedTable;
  },
  set _supportsDeletedTable(val) {
    supportsDeletedTable = val;
  },

  // The remaining methods are called by FormHistoryStartup.js
  async expireOldEntries() {
    log("expireOldEntries");

    // Determine how many days of history we're supposed to keep.
    // Calculate expireTime in microseconds
    const expireTime =
      (Date.now() - Prefs.get("expireDays") * DAY_IN_MS) * 1000;

    sendNotification("formhistory-beforeexpireoldentries", expireTime);

    const count = await FormHistory.count({});
    await expireOldEntriesDeletion(expireTime, count);
  },
};

// Prevent add-ons from redefining this API
Object.freeze(FormHistory);
PK
!<���8��'modules/FormHistoryAutoComplete.sys.mjs/* vim: set ts=4 sts=4 sw=4 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This autocomplete result combines 3 arrays of entries, fixedEntries and
 * externalEntries.
 * Entries are Form History entries, they can be removed.
 * Fixed entries are "appended" to entries, they are used for datalist items,
 * search suggestions and extra items from integrations.
 * External entries are meant for integrations, like Firefox Relay.
 * Internally entries and fixed entries are kept separated so we can
 * reuse and filter them.
 *
 * @implements {nsIAutoCompleteResult}
 */
export class FormHistoryAutoCompleteResult {
  constructor(input, entries, inputName, searchString) {
    this.input = input;
    this.entries = entries;
    this.inputName = inputName;
    this.searchString = searchString;
  }

  QueryInterface = ChromeUtils.generateQI([
    "nsIAutoCompleteResult",
    "nsISupportsWeakReference",
  ]);

  // private
  input = null;
  entries = null;
  inputName = null;
  #fixedEntries = [];
  externalEntries = [];

  set fixedEntries(value) {
    this.#fixedEntries = value;
    this.removeDuplicateHistoryEntries();
  }

  /**
   * Remove items from history list that are already present in fixed list.
   * We do this rather than the opposite ( i.e. remove items from fixed list)
   * to reflect the order that is specified in the fixed list.
   */
  removeDuplicateHistoryEntries() {
    this.entries = this.entries.filter(entry =>
      this.#fixedEntries.every(
        fixed => entry.text != (fixed.label || fixed.value)
      )
    );
  }

  getAt(index) {
    for (const group of [
      this.entries,
      this.#fixedEntries,
      this.externalEntries,
    ]) {
      if (index < group.length) {
        return group[index];
      }
      index -= group.length;
    }

    throw Components.Exception(
      "Index out of range.",
      Cr.NS_ERROR_ILLEGAL_VALUE
    );
  }

  // Allow autoCompleteSearch to get at the JS object so it can
  // modify some readonly properties for internal use.
  get wrappedJSObject() {
    return this;
  }

  // Interfaces from idl...
  searchString = "";
  errorDescription = "";

  get defaultIndex() {
    return this.matchCount ? 0 : -1;
  }

  get searchResult() {
    return this.matchCount
      ? Ci.nsIAutoCompleteResult.RESULT_SUCCESS
      : Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
  }

  get matchCount() {
    return (
      this.entries.length +
      this.#fixedEntries.length +
      this.externalEntries.length
    );
  }

  getValueAt(index) {
    const item = this.getAt(index);
    return item.text || item.value;
  }

  getLabelAt(index) {
    const item = this.getAt(index);
    return item.text || item.label || item.value;
  }

  getCommentAt(index) {
    return this.getAt(index).comment ?? "";
  }

  getStyleAt(index) {
    const itemStyle = this.getAt(index).style;
    if (itemStyle) {
      return itemStyle;
    }

    if (index >= 0) {
      if (index < this.entries.length) {
        return "fromhistory";
      }

      if (index > 0 && index == this.entries.length) {
        return "datalist-first";
      }
    }
    return "";
  }

  getImageAt(_index) {
    return "";
  }

  getFinalCompleteValueAt(index) {
    return this.getValueAt(index);
  }

  isRemovableAt(index) {
    return this.#isFormHistoryEntry(index) || this.getAt(index).removable;
  }

  removeValueAt(index) {
    if (this.#isFormHistoryEntry(index)) {
      const [removedEntry] = this.entries.splice(index, 1);
      const actor =
        this.input.ownerGlobal.windowGlobalChild.getActor("FormHistory");
      actor.sendAsyncMessage("FormHistory:RemoveEntry", {
        inputName: this.inputName,
        value: removedEntry.text,
        guid: removedEntry.guid,
      });
    }
  }

  #isFormHistoryEntry(index) {
    return index >= 0 && index < this.entries.length;
  }
}
PK
!<���	�	"modules/FormHistoryStartup.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  FormHistory: "resource://gre/modules/FormHistory.sys.mjs",
});

export class FormHistoryStartup {
  classID = Components.ID("{3A0012EB-007F-4BB8-AA81-A07385F77A25}");

  QueryInterface = ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
  ]);

  observe(_subject, topic, _data) {
    switch (topic) {
      case "idle-daily":
      case "formhistory-expire-now":
        lazy.FormHistory.expireOldEntries().catch(console.error);
        break;
      case "profile-after-change":
        this.init();
        break;
    }
  }

  init() {
    if (this.inited) {
      return;
    }
    this.inited = true;

    // triggers needed service cleanup and db shutdown
    Services.obs.addObserver(this, "idle-daily", true);
    Services.obs.addObserver(this, "formhistory-expire-now", true);

    Services.ppmm.addMessageListener(
      "FormHistory:AutoCompleteSearchAsync",
      this
    );
    Services.ppmm.addMessageListener("FormHistory:RemoveEntry", this);
  }

  receiveMessage(message) {
    switch (message.name) {
      case "FormHistory:AutoCompleteSearchAsync":
        this.#onFormHistoryAutoCompleteSearchAsync({
          ...message.data,
          target: message.target,
        });
        break;

      case "FormHistory:RemoveEntry":
        this.#onFormHistoryRemoveEntry(message.data);
        break;
    }
  }

  async #onFormHistoryAutoCompleteSearchAsync({
    id,
    searchString,
    params,
    target,
  }) {
    // This case is only used for the search field. There is a
    // similar algorithm in FormHistoryParent.sys.mjs that uses
    // sendQuery for other form fields.

    const instance = (this._queryInstance = {});
    const formHistoryEntries = await lazy.FormHistory.getAutoCompleteResults(
      searchString,
      params,
      () => this._queryInstance != instance
    );

    if (this._queryInstance == instance) {
      target.sendAsyncMessage("FormHistory:AutoCompleteSearchResults", {
        id,
        results: {
          formHistoryEntries,
          externalEntries: [],
        },
      });
    }
  }

  #onFormHistoryRemoveEntry({ inputName, value, guid }) {
    lazy.FormHistory.update({
      op: "remove",
      fieldname: inputName,
      value,
      guid,
    });
  }
}
PK
!<���5,,modules/FormLikeFactory.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * A factory to generate FormLike objects that represent a set of related fields
 * which aren't necessarily marked up with a <form> element. FormLike's emulate
 * the properties of an HTMLFormElement which are relevant to form tasks.
 */
export let FormLikeFactory = {
  _propsFromForm: ["action", "autocomplete", "ownerDocument"],

  /**
   * Create a FormLike object from a <form>.
   *
   * @param {HTMLFormElement} aForm
   * @return {FormLike}
   * @throws Error if aForm isn't an HTMLFormElement
   */
  createFromForm(aForm) {
    if (!HTMLFormElement.isInstance(aForm)) {
      throw new Error("createFromForm: aForm must be a HTMLFormElement");
    }

    let formLike = {
      elements: [...aForm.elements],
      rootElement: aForm,
    };

    for (let prop of this._propsFromForm) {
      formLike[prop] = aForm[prop];
    }

    this._addToJSONProperty(formLike);

    return formLike;
  },

  /**
   * Create a FormLike object from an element that is the root of the document
   *
   * Currently all <input> not in a <form> are one LoginForm but this
   * shouldn't be relied upon as the heuristics may change to detect multiple
   * "forms" (e.g. registration and login) on one page with a <form>.
   *
   * @param {HTMLElement} aDocumentRoot
   * @return {formLike}
   * @throws Error if aDocumentRoot is null
   */
  createFromDocumentRoot(aDocumentRoot) {
    if (!aDocumentRoot) {
      throw new Error("createFromDocumentRoot: aDocumentRoot is null");
    }

    let formLike = {
      action: aDocumentRoot.baseURI,
      autocomplete: "on",
      ownerDocument: aDocumentRoot.ownerDocument,
      rootElement: aDocumentRoot,
    };

    // FormLikes can be created when fields are inserted into the DOM. When
    // many, many fields are inserted one after the other, we create many
    // FormLikes, and computing the elements list becomes more and more
    // expensive. Making the elements list lazy means that it'll only
    // be computed when it's eventually needed (if ever).
    ChromeUtils.defineLazyGetter(formLike, "elements", function () {
      let elements = [];
      for (let el of aDocumentRoot.querySelectorAll("input, select")) {
        // Exclude elements inside the rootElement that are already in a <form> as
        // they will be handled by their own FormLike.
        if (!el.form) {
          elements.push(el);
        }
      }

      return elements;
    });

    this._addToJSONProperty(formLike);
    return formLike;
  },

  /**
   * Create a FormLike object from an <input>/<select> in a document.
   *
   * If the field is in a <form>, construct the FormLike from the form.
   * Otherwise, create a FormLike with a rootElement (wrapper) according to
   * heuristics. Currently all <input>/<select> not in a <form> are one FormLike
   * but this shouldn't be relied upon as the heuristics may change to detect
   * multiple "forms" (e.g. registration and login) on one page with a <form>.
   *
   * Note that two FormLikes created from the same field won't return the same FormLike object.
   * Use the `rootElement` property on the FormLike as a key instead.
   *
   * @param {HTMLInputElement|HTMLSelectElement} aField - an <input> or <select> field in a document
   * @return {FormLike}
   * @throws Error if aField isn't a password or username field in a document
   */
  createFromField(aField) {
    if (
      (!HTMLInputElement.isInstance(aField) &&
        !HTMLSelectElement.isInstance(aField)) ||
      !aField.ownerDocument
    ) {
      throw new Error("createFromField requires a field in a document");
    }

    const rootElement = this.findRootForField(aField);
    return HTMLFormElement.isInstance(rootElement)
      ? this.createFromForm(rootElement)
      : this.createFromDocumentRoot(rootElement);
  },

  /**
   * Find the closest <form> if any when aField is inside a ShadowRoot.
   *
   * @param {HTMLInputElement} aField - a password or username field in a document
   * @return {HTMLFormElement|null}
   */
  closestFormIgnoringShadowRoots(aField) {
    let form = aField.closest("form");
    let current = aField;
    while (!form) {
      let shadowRoot = current.getRootNode();
      if (!ShadowRoot.isInstance(shadowRoot)) {
        break;
      }
      let host = shadowRoot.host;
      form = host.closest("form");
      current = host;
    }
    return form;
  },

  /**
   * Determine the Element that encapsulates the related fields. For example, if
   * a page contains a login form and a checkout form which are "submitted"
   * separately, and the username field is passed in, ideally this would return
   * an ancestor Element of the username and password fields which doesn't
   * include any of the checkout fields.
   *
   * @param {HTMLInputElement|HTMLSelectElement} aField - a field in a document
   * @return {HTMLElement} - the root element surrounding related fields
   */
  findRootForField(aField) {
    let form = aField.form || this.closestFormIgnoringShadowRoots(aField);
    if (form) {
      return form;
    }

    return aField.ownerDocument.documentElement;
  },

  /**
   * Add a `toJSON` property to a FormLike so logging which ends up going
   * through dump doesn't include usless garbage from DOM objects.
   */
  _addToJSONProperty(aFormLike) {
    function prettyElementOutput(aElement) {
      let idText = aElement.id ? "#" + aElement.id : "";
      let classText = "";
      for (let className of aElement.classList) {
        classText += "." + className;
      }
      return `<${aElement.nodeName + idText + classText}>`;
    }

    Object.defineProperty(aFormLike, "toJSON", {
      value: () => {
        let cleansed = {};
        for (let key of Object.keys(aFormLike)) {
          let value = aFormLike[key];
          let cleansedValue = value;

          switch (key) {
            case "elements": {
              cleansedValue = [];
              for (let element of value) {
                cleansedValue.push(prettyElementOutput(element));
              }
              break;
            }

            case "ownerDocument": {
              cleansedValue = {
                location: {
                  href: value.location.href,
                },
              };
              break;
            }

            case "rootElement": {
              cleansedValue = prettyElementOutput(value);
              break;
            }
          }

          cleansed[key] = cleansedValue;
        }
        return cleansed;
      },
    });
  },
};
PK
!<Ԁ
]oomodules/FormScenarios.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { FormLikeFactory } from "resource://gre/modules/FormLikeFactory.sys.mjs";
import { SignUpFormRuleset } from "resource://gre/modules/SignUpFormRuleset.sys.mjs";
import { FirefoxRelayUtils } from "resource://gre/modules/FirefoxRelayUtils.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

export class FormScenarios {
  /**
   * Caches the scores when running the SignUpFormRuleset against a form
   */
  static #cachedSignUpFormScore = new WeakMap();

  /**
   * Detect usage scenarios of the form.
   *
   * @param {object} options named options
   * @param {HTMLInputElement} [options.input] where current focus is
   * @param {FormLike} [options.form]
   *
   * @returns {Array<string>} detected scenario names
   */
  static detect({ input, form }) {
    const scenarios = {};

    // Running simple heuristics first, because running the SignUpFormRuleset is expensive
    if (
      input &&
      // At the moment Relay integration is the only interested party in "sign up form",
      // so we optimize a bit by checking if it's enabled or not.
      FirefoxRelayUtils.isRelayInterestedField(input)
    ) {
      form ??= FormLikeFactory.findRootForField(input);

      scenarios.signUpForm = FormScenarios.#isProbablyASignUpForm(form);
    }

    return scenarios;
  }

  /**
   * Determine if the form is a sign-up form.
   * This is done by running the rules of the Fathom SignUpFormRuleset against the form and calucating a score between 0 and 1.
   * It's considered a sign-up form, if the score is higher than the confidence threshold (default=0.75)
   *
   * @param {HTMLFormElement} formElement
   * @returns {boolean} returns true if the calculcated score is higher than the confidenceThreshold
   */
  static #isProbablyASignUpForm(formElement) {
    let score = FormScenarios.#cachedSignUpFormScore.get(formElement);
    if (!score) {
      TelemetryStopwatch.start("PWMGR_SIGNUP_FORM_DETECTION_MS");
      try {
        const { rules, type } = SignUpFormRuleset;
        const results = rules.against(formElement);
        score = results.get(formElement).scoreFor(type);
        TelemetryStopwatch.finish("PWMGR_SIGNUP_FORM_DETECTION_MS");
      } finally {
        if (TelemetryStopwatch.running("PWMGR_SIGNUP_FORM_DETECTION_MS")) {
          TelemetryStopwatch.cancel("PWMGR_SIGNUP_FORM_DETECTION_MS");
        }
      }
      FormScenarios.#cachedSignUpFormScore.set(formElement, score);
    }

    const threshold = FormScenarios.signupDetectionConfidenceThreshold;
    return score > threshold;
  }
}
XPCOMUtils.defineLazyPreferenceGetter(
  FormScenarios,
  "signupDetectionConfidenceThreshold",
  "signon.signupDetection.confidenceThreshold",
  "0.75"
);
PK
!<��?�PfPf modules/FxAccountsClient.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { CommonUtils } from "resource://services-common/utils.sys.mjs";

import { HawkClient } from "resource://services-common/hawkclient.sys.mjs";
import { deriveHawkCredentials } from "resource://services-common/hawkrequest.sys.mjs";
import { CryptoUtils } from "resource://services-crypto/utils.sys.mjs";

import {
  ERRNO_ACCOUNT_DOES_NOT_EXIST,
  ERRNO_INCORRECT_EMAIL_CASE,
  ERRNO_INCORRECT_PASSWORD,
  ERRNO_INVALID_AUTH_NONCE,
  ERRNO_INVALID_AUTH_TIMESTAMP,
  ERRNO_INVALID_AUTH_TOKEN,
  log,
} from "resource://gre/modules/FxAccountsCommon.sys.mjs";

import { Credentials } from "resource://gre/modules/Credentials.sys.mjs";

const HOST_PREF = "identity.fxaccounts.auth.uri";

const SIGNIN = "/account/login";
const SIGNUP = "/account/create";
// Devices older than this many days will not appear in the devices list
const DEVICES_FILTER_DAYS = 21;

export var FxAccountsClient = function (
  host = Services.prefs.getStringPref(HOST_PREF)
) {
  this.host = host;

  // The FxA auth server expects requests to certain endpoints to be authorized
  // using Hawk.
  this.hawk = new HawkClient(host);
  this.hawk.observerPrefix = "FxA:hawk";

  // Manage server backoff state. C.f.
  // https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#backoff-protocol
  this.backoffError = null;
};

FxAccountsClient.prototype = {
  /**
   * Return client clock offset, in milliseconds, as determined by hawk client.
   * Provided because callers should not have to know about hawk
   * implementation.
   *
   * The offset is the number of milliseconds that must be added to the client
   * clock to make it equal to the server clock.  For example, if the client is
   * five minutes ahead of the server, the localtimeOffsetMsec will be -300000.
   */
  get localtimeOffsetMsec() {
    return this.hawk.localtimeOffsetMsec;
  },

  /*
   * Return current time in milliseconds
   *
   * Not used by this module, but made available to the FxAccounts.sys.mjs
   * that uses this client.
   */
  now() {
    return this.hawk.now();
  },

  /**
   * Common code from signIn and signUp.
   *
   * @param path
   *        Request URL path. Can be /account/create or /account/login
   * @param email
   *        The email address for the account (utf8)
   * @param password
   *        The user's password
   * @param [getKeys=false]
   *        If set to true the keyFetchToken will be retrieved
   * @param [retryOK=true]
   *        If capitalization of the email is wrong and retryOK is set to true,
   *        we will retry with the suggested capitalization from the server
   * @return Promise
   *        Returns a promise that resolves to an object:
   *        {
   *          authAt: authentication time for the session (seconds since epoch)
   *          email: the primary email for this account
   *          keyFetchToken: a key fetch token (hex)
   *          sessionToken: a session token (hex)
   *          uid: the user's unique ID (hex)
   *          unwrapBKey: used to unwrap kB, derived locally from the
   *                      password (not revealed to the FxA server)
   *          verified (optional): flag indicating verification status of the
   *                               email
   *        }
   */
  _createSession(path, email, password, getKeys = false, retryOK = true) {
    return Credentials.setup(email, password).then(creds => {
      let data = {
        authPW: CommonUtils.bytesAsHex(creds.authPW),
        email,
      };
      let keys = getKeys ? "?keys=true" : "";

      return this._request(path + keys, "POST", null, data).then(
        // Include the canonical capitalization of the email in the response so
        // the caller can set its signed-in user state accordingly.
        result => {
          result.email = data.email;
          result.unwrapBKey = CommonUtils.bytesAsHex(creds.unwrapBKey);

          return result;
        },
        error => {
          log.debug("Session creation failed", error);
          // If the user entered an email with different capitalization from
          // what's stored in the database (e.g., Greta.Garbo@gmail.COM as
          // opposed to greta.garbo@gmail.com), the server will respond with a
          // errno 120 (code 400) and the expected capitalization of the email.
          // We retry with this email exactly once.  If successful, we use the
          // server's version of the email as the signed-in-user's email. This
          // is necessary because the email also serves as salt; so we must be
          // in agreement with the server on capitalization.
          //
          // API reference:
          // https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md
          if (ERRNO_INCORRECT_EMAIL_CASE === error.errno && retryOK) {
            if (!error.email) {
              log.error("Server returned errno 120 but did not provide email");
              throw error;
            }
            return this._createSession(
              path,
              error.email,
              password,
              getKeys,
              false
            );
          }
          throw error;
        }
      );
    });
  },

  /**
   * Create a new Firefox Account and authenticate
   *
   * @param email
   *        The email address for the account (utf8)
   * @param password
   *        The user's password
   * @param [getKeys=false]
   *        If set to true the keyFetchToken will be retrieved
   * @return Promise
   *        Returns a promise that resolves to an object:
   *        {
   *          uid: the user's unique ID (hex)
   *          sessionToken: a session token (hex)
   *          keyFetchToken: a key fetch token (hex),
   *          unwrapBKey: used to unwrap kB, derived locally from the
   *                      password (not revealed to the FxA server)
   *        }
   */
  signUp(email, password, getKeys = false) {
    return this._createSession(
      SIGNUP,
      email,
      password,
      getKeys,
      false /* no retry */
    );
  },

  /**
   * Authenticate and create a new session with the Firefox Account API server
   *
   * @param email
   *        The email address for the account (utf8)
   * @param password
   *        The user's password
   * @param [getKeys=false]
   *        If set to true the keyFetchToken will be retrieved
   * @return Promise
   *        Returns a promise that resolves to an object:
   *        {
   *          authAt: authentication time for the session (seconds since epoch)
   *          email: the primary email for this account
   *          keyFetchToken: a key fetch token (hex)
   *          sessionToken: a session token (hex)
   *          uid: the user's unique ID (hex)
   *          unwrapBKey: used to unwrap kB, derived locally from the
   *                      password (not revealed to the FxA server)
   *          verified: flag indicating verification status of the email
   *        }
   */
  signIn: function signIn(email, password, getKeys = false) {
    return this._createSession(
      SIGNIN,
      email,
      password,
      getKeys,
      true /* retry */
    );
  },

  /**
   * Check the status of a session given a session token
   *
   * @param sessionTokenHex
   *        The session token encoded in hex
   * @return Promise
   *        Resolves with a boolean indicating if the session is still valid
   */
  async sessionStatus(sessionTokenHex) {
    const credentials = await deriveHawkCredentials(
      sessionTokenHex,
      "sessionToken"
    );
    return this._request("/session/status", "GET", credentials).then(
      () => Promise.resolve(true),
      error => {
        if (isInvalidTokenError(error)) {
          return Promise.resolve(false);
        }
        throw error;
      }
    );
  },

  /**
   * List all the clients connected to the authenticated user's account,
   * including devices, OAuth clients, and web sessions.
   *
   * @param sessionTokenHex
   *        The session token encoded in hex
   * @return Promise
   */
  async attachedClients(sessionTokenHex) {
    const credentials = await deriveHawkCredentials(
      sessionTokenHex,
      "sessionToken"
    );
    return this._requestWithHeaders(
      "/account/attached_clients",
      "GET",
      credentials
    );
  },

  /**
   * Retrieves an OAuth authorization code.
   *
   * @param String sessionTokenHex
   *        The session token encoded in hex
   * @param {Object} options
   * @param options.client_id
   * @param options.state
   * @param options.scope
   * @param options.access_type
   * @param options.code_challenge_method
   * @param options.code_challenge
   * @param [options.keys_jwe]
   * @returns {Promise<Object>} Object containing `code` and `state`.
   */
  async oauthAuthorize(sessionTokenHex, options) {
    const credentials = await deriveHawkCredentials(
      sessionTokenHex,
      "sessionToken"
    );
    const body = {
      client_id: options.client_id,
      response_type: "code",
      state: options.state,
      scope: options.scope,
      access_type: options.access_type,
      code_challenge: options.code_challenge,
      code_challenge_method: options.code_challenge_method,
    };
    if (options.keys_jwe) {
      body.keys_jwe = options.keys_jwe;
    }
    return this._request("/oauth/authorization", "POST", credentials, body);
  },
  /**
   * Exchanges an OAuth authorization code with a refresh token, access tokens and an optional JWE representing scoped keys
   *  Takes in the sessionToken to tie the device record associated with the session, with the device record associated with the refreshToken
   *
   * @param string sessionTokenHex: The session token encoded in hex
   * @param String code: OAuth authorization code
   * @param String verifier: OAuth PKCE verifier
   * @param String clientId: OAuth client ID
   *
   * @returns { Object } object containing `refresh_token`, `access_token` and `keys_jwe`
   **/
  async oauthToken(sessionTokenHex, code, verifier, clientId) {
    const credentials = await deriveHawkCredentials(
      sessionTokenHex,
      "sessionToken"
    );
    const body = {
      grant_type: "authorization_code",
      code,
      client_id: clientId,
      code_verifier: verifier,
    };
    return this._request("/oauth/token", "POST", credentials, body);
  },
  /**
   * Destroy an OAuth access token or refresh token.
   *
   * @param String clientId
   * @param String token The token to be revoked.
   */
  async oauthDestroy(clientId, token) {
    const body = {
      client_id: clientId,
      token,
    };
    return this._request("/oauth/destroy", "POST", null, body);
  },

  /**
   * Query for the information required to derive
   * scoped encryption keys requested by the specified OAuth client.
   *
   * @param sessionTokenHex
   *        The session token encoded in hex
   * @param clientId
   * @param scope
   *        Space separated list of scopes
   * @return Promise
   */
  async getScopedKeyData(sessionTokenHex, clientId, scope) {
    if (!clientId) {
      throw new Error("Missing 'clientId' parameter");
    }
    if (!scope) {
      throw new Error("Missing 'scope' parameter");
    }
    const params = {
      client_id: clientId,
      scope,
    };
    const credentials = await deriveHawkCredentials(
      sessionTokenHex,
      "sessionToken"
    );
    return this._request(
      "/account/scoped-key-data",
      "POST",
      credentials,
      params
    );
  },

  /**
   * Destroy the current session with the Firefox Account API server and its
   * associated device.
   *
   * @param sessionTokenHex
   *        The session token encoded in hex
   * @return Promise
   */
  async signOut(sessionTokenHex, options = {}) {
    const credentials = await deriveHawkCredentials(
      sessionTokenHex,
      "sessionToken"
    );
    let path = "/session/destroy";
    if (options.service) {
      path += "?service=" + encodeURIComponent(options.service);
    }
    return this._request(path, "POST", credentials);
  },

  /**
   * Check the verification status of the user's FxA email address
   *
   * @param sessionTokenHex
   *        The current session token encoded in hex
   * @return Promise
   */
  async recoveryEmailStatus(sessionTokenHex, options = {}) {
    const credentials = await deriveHawkCredentials(
      sessionTokenHex,
      "sessionToken"
    );
    let path = "/recovery_email/status";
    if (options.reason) {
      path += "?reason=" + encodeURIComponent(options.reason);
    }

    return this._request(path, "GET", credentials);
  },

  /**
   * Resend the verification email for the user
   *
   * @param sessionTokenHex
   *        The current token encoded in hex
   * @return Promise
   */
  async resendVerificationEmail(sessionTokenHex) {
    const credentials = await deriveHawkCredentials(
      sessionTokenHex,
      "sessionToken"
    );
    return this._request("/recovery_email/resend_code", "POST", credentials);
  },

  /**
   * Retrieve encryption keys
   *
   * @param keyFetchTokenHex
   *        A one-time use key fetch token encoded in hex
   * @return Promise
   *        Returns a promise that resolves to an object:
   *        {
   *          kA: an encryption key for recevorable data (bytes)
   *          wrapKB: an encryption key that requires knowledge of the
   *                  user's password (bytes)
   *        }
   */
  async accountKeys(keyFetchTokenHex) {
    let creds = await deriveHawkCredentials(keyFetchTokenHex, "keyFetchToken");
    let keyRequestKey = creds.extra.slice(0, 32);
    let morecreds = await CryptoUtils.hkdfLegacy(
      keyRequestKey,
      undefined,
      Credentials.keyWord("account/keys"),
      3 * 32
    );
    let respHMACKey = morecreds.slice(0, 32);
    let respXORKey = morecreds.slice(32, 96);

    const resp = await this._request("/account/keys", "GET", creds);
    if (!resp.bundle) {
      throw new Error("failed to retrieve keys");
    }

    let bundle = CommonUtils.hexToBytes(resp.bundle);
    let mac = bundle.slice(-32);
    let key = CommonUtils.byteStringToArrayBuffer(respHMACKey);
    // CryptoUtils.hmac takes ArrayBuffers as inputs for the key and data and
    // returns an ArrayBuffer.
    let bundleMAC = await CryptoUtils.hmac(
      "SHA-256",
      key,
      CommonUtils.byteStringToArrayBuffer(bundle.slice(0, -32))
    );
    if (mac !== CommonUtils.arrayBufferToByteString(bundleMAC)) {
      throw new Error("error unbundling encryption keys");
    }

    let keyAWrapB = CryptoUtils.xor(respXORKey, bundle.slice(0, 64));

    return {
      kA: keyAWrapB.slice(0, 32),
      wrapKB: keyAWrapB.slice(32),
    };
  },

  /**
   * Obtain an OAuth access token by authenticating using a session token.
   *
   * @param {String} sessionTokenHex
   *        The session token encoded in hex
   * @param {String} clientId
   * @param {String} scope
   *        List of space-separated scopes.
   * @param {Number} ttl
   *        Token time to live.
   * @return {Promise<Object>} Object containing an `access_token`.
   */
  async accessTokenWithSessionToken(sessionTokenHex, clientId, scope, ttl) {
    const credentials = await deriveHawkCredentials(
      sessionTokenHex,
      "sessionToken"
    );
    const body = {
      client_id: clientId,
      grant_type: "fxa-credentials",
      scope,
      ttl,
    };
    return this._request("/oauth/token", "POST", credentials, body);
  },

  /**
   * Determine if an account exists
   *
   * @param email
   *        The email address to check
   * @return Promise
   *        The promise resolves to true if the account exists, or false
   *        if it doesn't. The promise is rejected on other errors.
   */
  accountExists(email) {
    return this.signIn(email, "").then(
      () => {
        throw new Error("How did I sign in with an empty password?");
      },
      expectedError => {
        switch (expectedError.errno) {
          case ERRNO_ACCOUNT_DOES_NOT_EXIST:
            return false;
          case ERRNO_INCORRECT_PASSWORD:
            return true;
          default:
            // not so expected, any more ...
            throw expectedError;
        }
      }
    );
  },

  /**
   * Given the uid of an existing account (not an arbitrary email), ask
   * the server if it still exists via /account/status.
   *
   * Used for differentiating between password change and account deletion.
   */
  accountStatus(uid) {
    return this._request("/account/status?uid=" + uid, "GET").then(
      result => {
        return result.exists;
      },
      error => {
        log.error("accountStatus failed", error);
        return Promise.reject(error);
      }
    );
  },

  /**
   * Register a new device
   *
   * @method registerDevice
   * @param  sessionTokenHex
   *         Session token obtained from signIn
   * @param  name
   *         Device name
   * @param  type
   *         Device type (mobile|desktop)
   * @param  [options]
   *         Extra device options
   * @param  [options.availableCommands]
   *         Available commands for this device
   * @param  [options.pushCallback]
   *         `pushCallback` push endpoint callback
   * @param  [options.pushPublicKey]
   *         `pushPublicKey` push public key (URLSafe Base64 string)
   * @param  [options.pushAuthKey]
   *         `pushAuthKey` push auth secret (URLSafe Base64 string)
   * @return Promise
   *         Resolves to an object:
   *         {
   *           id: Device identifier
   *           createdAt: Creation time (milliseconds since epoch)
   *           name: Name of device
   *           type: Type of device (mobile|desktop)
   *         }
   */
  async registerDevice(sessionTokenHex, name, type, options = {}) {
    let path = "/account/device";

    let creds = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
    let body = { name, type };

    if (options.pushCallback) {
      body.pushCallback = options.pushCallback;
    }
    if (options.pushPublicKey && options.pushAuthKey) {
      body.pushPublicKey = options.pushPublicKey;
      body.pushAuthKey = options.pushAuthKey;
    }
    body.availableCommands = options.availableCommands;

    return this._request(path, "POST", creds, body);
  },

  /**
   * Sends a message to other devices. Must conform with the push payload schema:
   * https://github.com/mozilla/fxa-auth-server/blob/master/docs/pushpayloads.schema.json
   *
   * @method notifyDevice
   * @param  sessionTokenHex
   *         Session token obtained from signIn
   * @param  deviceIds
   *         Devices to send the message to. If null, will be sent to all devices.
   * @param  excludedIds
   *         Devices to exclude when sending to all devices (deviceIds must be null).
   * @param  payload
   *         Data to send with the message
   * @return Promise
   *         Resolves to an empty object:
   *         {}
   */
  async notifyDevices(
    sessionTokenHex,
    deviceIds,
    excludedIds,
    payload,
    TTL = 0
  ) {
    const credentials = await deriveHawkCredentials(
      sessionTokenHex,
      "sessionToken"
    );
    if (deviceIds && excludedIds) {
      throw new Error(
        "You cannot specify excluded devices if deviceIds is set."
      );
    }
    const body = {
      to: deviceIds || "all",
      payload,
      TTL,
    };
    if (excludedIds) {
      body.excluded = excludedIds;
    }
    return this._request("/account/devices/notify", "POST", credentials, body);
  },

  /**
   * Retrieves pending commands for our device.
   *
   * @method getCommands
   * @param  sessionTokenHex - Session token obtained from signIn
   * @param  [index] - If specified, only messages received after the one who
   *                   had that index will be retrieved.
   * @param  [limit] - Maximum number of messages to retrieve.
   */
  async getCommands(sessionTokenHex, { index, limit }) {
    const credentials = await deriveHawkCredentials(
      sessionTokenHex,
      "sessionToken"
    );
    const params = new URLSearchParams();
    if (index != undefined) {
      params.set("index", index);
    }
    if (limit != undefined) {
      params.set("limit", limit);
    }
    const path = `/account/device/commands?${params.toString()}`;
    return this._request(path, "GET", credentials);
  },

  /**
   * Invokes a command on another device.
   *
   * @method invokeCommand
   * @param  sessionTokenHex - Session token obtained from signIn
   * @param  command - Name of the command to invoke
   * @param  target - Recipient device ID.
   * @param  payload
   * @return Promise
   *         Resolves to the request's response, (which should be an empty object)
   */
  async invokeCommand(sessionTokenHex, command, target, payload) {
    const credentials = await deriveHawkCredentials(
      sessionTokenHex,
      "sessionToken"
    );
    const body = {
      command,
      target,
      payload,
    };
    return this._request(
      "/account/devices/invoke_command",
      "POST",
      credentials,
      body
    );
  },

  /**
   * Update the session or name for an existing device
   *
   * @method updateDevice
   * @param  sessionTokenHex
   *         Session token obtained from signIn
   * @param  id
   *         Device identifier
   * @param  name
   *         Device name
   * @param  [options]
   *         Extra device options
   * @param  [options.availableCommands]
   *         Available commands for this device
   * @param  [options.pushCallback]
   *         `pushCallback` push endpoint callback
   * @param  [options.pushPublicKey]
   *         `pushPublicKey` push public key (URLSafe Base64 string)
   * @param  [options.pushAuthKey]
   *         `pushAuthKey` push auth secret (URLSafe Base64 string)
   * @return Promise
   *         Resolves to an object:
   *         {
   *           id: Device identifier
   *           name: Device name
   *         }
   */
  async updateDevice(sessionTokenHex, id, name, options = {}) {
    let path = "/account/device";

    let creds = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
    let body = { id, name };
    if (options.pushCallback) {
      body.pushCallback = options.pushCallback;
    }
    if (options.pushPublicKey && options.pushAuthKey) {
      body.pushPublicKey = options.pushPublicKey;
      body.pushAuthKey = options.pushAuthKey;
    }
    body.availableCommands = options.availableCommands;

    return this._request(path, "POST", creds, body);
  },

  /**
   * Get a list of currently registered devices that have been accessed
   * in the last `DEVICES_FILTER_DAYS` days
   *
   * @method getDeviceList
   * @param  sessionTokenHex
   *         Session token obtained from signIn
   * @return Promise
   *         Resolves to an array of objects:
   *         [
   *           {
   *             id: Device id
   *             isCurrentDevice: Boolean indicating whether the item
   *                              represents the current device
   *             name: Device name
   *             type: Device type (mobile|desktop)
   *           },
   *           ...
   *         ]
   */
  async getDeviceList(sessionTokenHex) {
    let timestamp = Date.now() - 1000 * 60 * 60 * 24 * DEVICES_FILTER_DAYS;
    let path = `/account/devices?filterIdleDevicesTimestamp=${timestamp}`;
    let creds = await deriveHawkCredentials(sessionTokenHex, "sessionToken");
    return this._request(path, "GET", creds, {});
  },

  _clearBackoff() {
    this.backoffError = null;
  },

  /**
   * A general method for sending raw API calls to the FxA auth server.
   * All request bodies and responses are JSON.
   *
   * @param path
   *        API endpoint path
   * @param method
   *        The HTTP request method
   * @param credentials
   *        Hawk credentials
   * @param jsonPayload
   *        A JSON payload
   * @return Promise
   *        Returns a promise that resolves to the JSON response of the API call,
   *        or is rejected with an error. Error responses have the following properties:
   *        {
   *          "code": 400, // matches the HTTP status code
   *          "errno": 107, // stable application-level error number
   *          "error": "Bad Request", // string description of the error type
   *          "message": "the value of salt is not allowed to be undefined",
   *          "info": "https://docs.dev.lcip.og/errors/1234" // link to more info on the error
   *        }
   */
  async _requestWithHeaders(path, method, credentials, jsonPayload) {
    // We were asked to back off.
    if (this.backoffError) {
      log.debug("Received new request during backoff, re-rejecting.");
      throw this.backoffError;
    }
    let response;
    try {
      response = await this.hawk.request(
        path,
        method,
        credentials,
        jsonPayload
      );
    } catch (error) {
      log.error(`error ${method}ing ${path}`, error);
      if (error.retryAfter) {
        log.debug("Received backoff response; caching error as flag.");
        this.backoffError = error;
        // Schedule clearing of cached-error-as-flag.
        CommonUtils.namedTimer(
          this._clearBackoff,
          error.retryAfter * 1000,
          this,
          "fxaBackoffTimer"
        );
      }
      throw error;
    }
    try {
      return { body: JSON.parse(response.body), headers: response.headers };
    } catch (error) {
      log.error("json parse error on response: " + response.body);
      // eslint-disable-next-line no-throw-literal
      throw { error };
    }
  },

  async _request(path, method, credentials, jsonPayload) {
    const response = await this._requestWithHeaders(
      path,
      method,
      credentials,
      jsonPayload
    );
    return response.body;
  },
};

function isInvalidTokenError(error) {
  if (error.code != 401) {
    return false;
  }
  switch (error.errno) {
    case ERRNO_INVALID_AUTH_TOKEN:
    case ERRNO_INVALID_AUTH_TIMESTAMP:
    case ERRNO_INVALID_AUTH_NONCE:
      return true;
  }
  return false;
}
PK
!<���%�%�"modules/FxAccountsCommands.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import {
  CLIENT_IS_THUNDERBIRD,
  COMMAND_SENDTAB,
  COMMAND_SENDTAB_TAIL,
  COMMAND_CLOSETAB,
  COMMAND_CLOSETAB_TAIL,
  SCOPE_OLD_SYNC,
  log,
} from "resource://gre/modules/FxAccountsCommon.sys.mjs";

import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { Observers } from "resource://services-common/observers.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  BulkKeyBundle: "resource://services-sync/keys.sys.mjs",
  CryptoWrapper: "resource://services-sync/record.sys.mjs",
  PushCrypto: "resource://gre/modules/PushCrypto.sys.mjs",
  getRemoteCommandStore: "resource://services-sync/TabsStore.sys.mjs",
  RemoteCommand: "resource://services-sync/TabsStore.sys.mjs",
  Utils: "resource://services-sync/util.sys.mjs",
  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "INVALID_SHAREABLE_SCHEMES",
  "services.sync.engine.tabs.filteredSchemes",
  "",
  null,
  val => {
    return new Set(val.split("|"));
  }
);

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "idleService",
  "@mozilla.org/widget/useridleservice;1",
  "nsIUserIdleService"
);

const TOPIC_TABS_CHANGED = "services.sync.tabs.changed";
const COMMAND_MAX_PAYLOAD_SIZE = 16 * 1024;

export class FxAccountsCommands {
  constructor(fxAccountsInternal) {
    this._fxai = fxAccountsInternal;
    this.sendTab = new SendTab(this, fxAccountsInternal);
    this.closeTab = new CloseRemoteTab(this, fxAccountsInternal);
    this.commandQueue = new CommandQueue(this, fxAccountsInternal);
    this._invokeRateLimitExpiry = 0;
  }

  async availableCommands() {
    let commands = {};

    if (!CLIENT_IS_THUNDERBIRD) {
      // Invalid keys usually means the account is not verified yet.
      const encryptedSendTabKeys = await this.sendTab.getEncryptedCommandKeys();

      if (encryptedSendTabKeys) {
        commands[COMMAND_SENDTAB] = encryptedSendTabKeys;
      }

      const encryptedCloseTabKeys =
        await this.closeTab.getEncryptedCommandKeys();
      if (encryptedCloseTabKeys) {
        commands[COMMAND_CLOSETAB] = encryptedCloseTabKeys;
      }
    }

    return commands;
  }

  async invoke(command, device, payload) {
    const { sessionToken } = await this._fxai.getUserAccountData([
      "sessionToken",
    ]);
    const client = this._fxai.fxAccountsClient;
    const now = Date.now();
    if (now < this._invokeRateLimitExpiry) {
      const remaining = (this._invokeRateLimitExpiry - now) / 1000;
      throw new Error(
        `Invoke for ${command} is rate-limited for ${remaining} seconds.`
      );
    }
    try {
      let info = await client.invokeCommand(
        sessionToken,
        command,
        device.id,
        payload
      );
      if (!info.enqueued || !info.notified) {
        // We want an error log here to help diagnose users who report failure.
        log.error("Sending was only partially successful", info);
      } else {
        log.info("Successfully sent", info);
      }
    } catch (err) {
      if (err.code && err.code === 429 && err.retryAfter) {
        this._invokeRateLimitExpiry = Date.now() + err.retryAfter * 1000;
      }
      throw err;
    }
    log.info(`Payload sent to device ${device.id}.`);
  }

  /**
   * Poll and handle device commands for the current device.
   * This method can be called either in response to a Push message,
   * or by itself as a "commands recovery" mechanism.
   *
   * @param {Number} notifiedIndex "Command received" push messages include
   * the index of the command that triggered the message. We use it as a
   * hint when we have no "last command index" stored.
   */
  async pollDeviceCommands(notifiedIndex = 0) {
    // Whether the call to `pollDeviceCommands` was initiated by a Push message from the FxA
    // servers in response to a message being received or simply scheduled in order
    // to fetch missed messages.
    log.info(`Polling device commands.`);
    await this._fxai.withCurrentAccountState(async state => {
      const { device } = await state.getUserAccountData(["device"]);
      if (!device) {
        throw new Error("No device registration.");
      }
      // We increment lastCommandIndex by 1 because the server response includes the current index.
      // If we don't have a `lastCommandIndex` stored, we fall back on the index from the push message we just got.
      const lastCommandIndex = device.lastCommandIndex + 1 || notifiedIndex;
      // We have already received this message before.
      if (notifiedIndex > 0 && notifiedIndex < lastCommandIndex) {
        return;
      }
      const { index, messages } = await this._fetchDeviceCommands(
        lastCommandIndex
      );
      if (messages.length) {
        await state.updateUserAccountData({
          device: { ...device, lastCommandIndex: index },
        });
        log.info(`Handling ${messages.length} messages`);
        await this._handleCommands(messages, notifiedIndex);
      }
    });
    return true;
  }

  async _fetchDeviceCommands(index, limit = null) {
    const userData = await this._fxai.getUserAccountData();
    if (!userData) {
      throw new Error("No user.");
    }
    const { sessionToken } = userData;
    if (!sessionToken) {
      throw new Error("No session token.");
    }
    const client = this._fxai.fxAccountsClient;
    const opts = { index };
    if (limit != null) {
      opts.limit = limit;
    }
    return client.getCommands(sessionToken, opts);
  }

  _getReason(notifiedIndex, messageIndex) {
    // The returned reason value represents an explanation for why the command associated with the
    // message of the given `messageIndex` is being handled. If `notifiedIndex` is zero the command
    // is a part of a fallback polling process initiated by "Sync Now" ["poll"]. If `notifiedIndex` is
    // greater than `messageIndex` this is a push command that was previously missed ["push-missed"],
    // otherwise we assume this is a push command with no missed messages ["push"].
    if (notifiedIndex == 0) {
      return "poll";
    } else if (notifiedIndex > messageIndex) {
      return "push-missed";
    }
    // Note: The returned reason might be "push" in the case where a user sends multiple tabs
    // in quick succession. We are not attempting to distinguish this from other push cases at
    // present.
    return "push";
  }

  async _handleCommands(messages, notifiedIndex) {
    try {
      await this._fxai.device.refreshDeviceList();
    } catch (e) {
      log.warn("Error refreshing device list", e);
    }
    // We debounce multiple incoming tabs so we show a single notification.
    const tabsReceived = [];
    const tabsToClose = [];
    for (const { index, data } of messages) {
      const { command, payload, sender: senderId } = data;
      const reason = this._getReason(notifiedIndex, index);
      const sender =
        senderId && this._fxai.device.recentDeviceList
          ? this._fxai.device.recentDeviceList.find(d => d.id == senderId)
          : null;
      if (!sender) {
        log.warn(
          "Incoming command is from an unknown device (maybe disconnected?)"
        );
      }
      switch (command) {
        case COMMAND_CLOSETAB:
          try {
            const { urls } = await this.closeTab.handleTabClose(
              senderId,
              payload,
              reason
            );
            log.info(
              `Close Tab received with FxA commands: "${urls.length} tabs"
               from ${sender ? sender.name : "Unknown device"}.`
            );
            // URLs are PII, so only logged at trace.
            log.trace(`Close Remote Tabs received URLs: ${urls}`);
            tabsToClose.push({ urls, sender });
          } catch (e) {
            log.error(`Error while handling incoming Close Tab payload.`, e);
          }
          break;
        case COMMAND_SENDTAB:
          try {
            const { title, uri } = await this.sendTab.handle(
              senderId,
              payload,
              reason
            );
            log.info(
              `Tab received with FxA commands: "${
                title || "<no title>"
              }" from ${sender ? sender.name : "Unknown device"}.`
            );
            // URLs are PII, so only logged at trace.
            log.trace(`Tab received URL: ${uri}`);
            // This should eventually be rare to hit as all platforms will be using the same
            // scheme filter list, but we have this here in the case other platforms
            // haven't caught up and/or trying to send invalid uris using older versions
            const scheme = Services.io.newURI(uri).scheme;
            if (lazy.INVALID_SHAREABLE_SCHEMES.has(scheme)) {
              throw new Error("Invalid scheme found for received URI.");
            }
            tabsReceived.push({ title, uri, sender });
          } catch (e) {
            log.error(`Error while handling incoming Send Tab payload.`, e);
          }
          break;
        default:
          log.info(`Unknown command: ${command}.`);
      }
    }
    if (tabsReceived.length) {
      this._notifyFxATabsReceived(tabsReceived);
    }
    if (tabsToClose.length) {
      this._notifyFxATabsClosed(tabsToClose);
    }
  }

  _notifyFxATabsReceived(tabsReceived) {
    Observers.notify("fxaccounts:commands:open-uri", tabsReceived);
  }

  _notifyFxATabsClosed(tabsToClose) {
    Observers.notify("fxaccounts:commands:close-uri", tabsToClose);
  }
}

/**
 * This is built on top of FxA commands.
 *
 * Devices exchange keys wrapped in the oldsync key between themselves (getEncryptedCommandKeys)
 * during the device registration flow. The FxA server can theoretically never
 * retrieve the send tab keys since it doesn't know the oldsync key.
 *
 * Note about the keys:
 * The server has the `pushPublicKey`. The FxA server encrypt the send-tab payload again using the
 * push keys - after the client has encrypted the payload using the send-tab keys.
 * The push keys are different from the send-tab keys. The FxA server uses
 * the push keys to deliver the tabs using same mechanism we use for web-push.
 * However, clients use the send-tab keys for end-to-end encryption.
 *
 * Every command uses the same key management code, although each has its own key.
 */

export class Command {
  constructor(commands, fxAccountsInternal) {
    this._commands = commands;
    this._fxai = fxAccountsInternal;
  }

  // Must be set by the command.
  deviceCapability; // eg, COMMAND_SENDTAB;
  keyFieldName; // eg, "sendTabKeys";
  encryptedKeyFieldName; // eg, "encryptedSendTabKeys"

  // Returns true if the target device is compatible with FxA Commands Send tab.
  isDeviceCompatible(device) {
    return (
      device.availableCommands &&
      device.availableCommands[this.deviceCapability]
    );
  }

  async _encrypt(bytes, device) {
    let bundle = device.availableCommands[this.deviceCapability];
    if (!bundle) {
      throw new Error(`Device ${device.id} does not have send tab keys.`);
    }
    const oldsyncKey = await this._fxai.keys.getKeyForScope(SCOPE_OLD_SYNC);
    // Older clients expect this to be hex, due to pre-JWK sync key ids :-(
    const ourKid = this._fxai.keys.kidAsHex(oldsyncKey);
    const { kid: theirKid } = JSON.parse(
      device.availableCommands[this.deviceCapability]
    );
    if (theirKid != ourKid) {
      throw new Error("Target Send Tab key ID is different from ours");
    }
    const json = JSON.parse(bundle);
    const wrapper = new lazy.CryptoWrapper();
    wrapper.deserialize({ payload: json });
    const syncKeyBundle = lazy.BulkKeyBundle.fromJWK(oldsyncKey);
    let { publicKey, authSecret } = await wrapper.decrypt(syncKeyBundle);
    authSecret = urlsafeBase64Decode(authSecret);
    publicKey = urlsafeBase64Decode(publicKey);

    const { ciphertext: encrypted } = await lazy.PushCrypto.encrypt(
      bytes,
      publicKey,
      authSecret
    );
    return urlsafeBase64Encode(encrypted);
  }

  async _decrypt(ciphertext) {
    let { privateKey, publicKey, authSecret } =
      await this._getPersistedCommandKeys();
    publicKey = urlsafeBase64Decode(publicKey);
    authSecret = urlsafeBase64Decode(authSecret);
    ciphertext = new Uint8Array(urlsafeBase64Decode(ciphertext));
    return lazy.PushCrypto.decrypt(
      privateKey,
      publicKey,
      authSecret,
      // The only Push encoding we support.
      { encoding: "aes128gcm" },
      ciphertext
    );
  }

  async _getPersistedCommandKeys() {
    const { device } = await this._fxai.getUserAccountData(["device"]);
    return device && device[this.keyFieldName];
  }

  async _generateAndPersistCommandKeys() {
    let [publicKey, privateKey] = await lazy.PushCrypto.generateKeys();
    publicKey = urlsafeBase64Encode(publicKey);
    let authSecret = lazy.PushCrypto.generateAuthenticationSecret();
    authSecret = urlsafeBase64Encode(authSecret);
    const sendTabKeys = {
      publicKey,
      privateKey,
      authSecret,
    };
    await this._fxai.withCurrentAccountState(async state => {
      const { device = {} } = await state.getUserAccountData(["device"]);
      device[this.keyFieldName] = sendTabKeys;
      log.trace(
        `writing to ${this.keyFieldName} for command ${this.deviceCapability}`
      );
      await state.updateUserAccountData({
        device,
      });
    });
    return sendTabKeys;
  }

  async _getPersistedEncryptedCommandKey() {
    const data = await this._fxai.getUserAccountData([
      this.encryptedKeyFieldName,
    ]);
    return data[this.encryptedKeyFieldName];
  }

  async _generateAndPersistEncryptedCommandKey() {
    if (!(await this._fxai.keys.canGetKeyForScope(SCOPE_OLD_SYNC))) {
      log.info("Can't fetch keys, so unable to determine command keys");
      return null;
    }
    let sendTabKeys = await this._getPersistedCommandKeys();
    if (!sendTabKeys) {
      log.info("Could not find command keys, generating them");
      sendTabKeys = await this._generateAndPersistCommandKeys();
    }
    // Strip the private key from the bundle to encrypt.
    const keyToEncrypt = {
      publicKey: sendTabKeys.publicKey,
      authSecret: sendTabKeys.authSecret,
    };
    let oldsyncKey;
    try {
      oldsyncKey = await this._fxai.keys.getKeyForScope(SCOPE_OLD_SYNC);
    } catch (ex) {
      log.warn("Failed to fetch keys, so unable to determine command keys", ex);
      return null;
    }
    const wrapper = new lazy.CryptoWrapper();
    wrapper.cleartext = keyToEncrypt;
    const keyBundle = lazy.BulkKeyBundle.fromJWK(oldsyncKey);
    await wrapper.encrypt(keyBundle);
    const encryptedSendTabKeys = JSON.stringify({
      // This is expected in hex, due to pre-JWK sync key ids :-(
      kid: this._fxai.keys.kidAsHex(oldsyncKey),
      IV: wrapper.IV,
      hmac: wrapper.hmac,
      ciphertext: wrapper.ciphertext,
    });
    await this._fxai.withCurrentAccountState(async state => {
      let data = {};
      data[this.encryptedKeyFieldName] = encryptedSendTabKeys;
      await state.updateUserAccountData(data);
    });
    return encryptedSendTabKeys;
  }

  async getEncryptedCommandKeys() {
    log.trace("Getting command keys", this.deviceCapability);
    let encryptedSendTabKeys = await this._getPersistedEncryptedCommandKey();
    const sendTabKeys = await this._getPersistedCommandKeys();
    if (!encryptedSendTabKeys || !sendTabKeys) {
      log.info(
        `Generating and persisting encrypted key (${!!encryptedSendTabKeys}, ${!!sendTabKeys})`
      );
      // Generating the encrypted key requires the sync key so we expect to fail
      // in some cases (primary password is locked, account not verified, etc)
      // However, we will eventually end up generating it when we can, and device registration
      // will handle this late update and update the remote record as necessary, so it gets there in the end.
      // It's okay to persist these keys in plain text; they're encrypted.
      encryptedSendTabKeys =
        await this._generateAndPersistEncryptedCommandKey();
    }
    return encryptedSendTabKeys;
  }
}

/**
 * Send Tab
 */
export class SendTab extends Command {
  deviceCapability = COMMAND_SENDTAB;
  keyFieldName = "sendTabKeys";
  encryptedKeyFieldName = "encryptedSendTabKeys";

  /**
   * @param {Device[]} to - Device objects (typically returned by fxAccounts.getDevicesList()).
   * @param {Object} tab
   * @param {string} tab.url
   * @param {string} tab.title
   * @returns A report object, in the shape of
   *          {succeded: [Device], error: [{device: Device, error: Exception}]}
   */
  async send(to, tab) {
    log.info(`Sending a tab to ${to.length} devices.`);
    const flowID = this._fxai.telemetry.generateFlowID();
    const encoder = new TextEncoder();
    const data = { entries: [{ title: tab.title, url: tab.url }] };
    const report = {
      succeeded: [],
      failed: [],
    };
    for (let device of to) {
      try {
        const streamID = this._fxai.telemetry.generateFlowID();
        const targetData = Object.assign({ flowID, streamID }, data);
        const bytes = encoder.encode(JSON.stringify(targetData));
        const encrypted = await this._encrypt(bytes, device);
        // FxA expects an object as the payload, but we only have a single encrypted string; wrap it.
        // If you add any plaintext items to this payload, please carefully consider the privacy implications
        // of revealing that data to the FxA server.
        const payload = { encrypted };
        await this._commands.invoke(COMMAND_SENDTAB, device, payload);
        this._fxai.telemetry.recordEvent(
          "command-sent",
          COMMAND_SENDTAB_TAIL,
          this._fxai.telemetry.sanitizeDeviceId(device.id),
          { flowID, streamID }
        );
        report.succeeded.push(device);
      } catch (error) {
        log.error("Error while invoking a send tab command.", error);
        report.failed.push({ device, error });
      }
    }
    return report;
  }

  // Handle incoming send tab payload, called by FxAccountsCommands.
  async handle(senderID, { encrypted }, reason) {
    const bytes = await this._decrypt(encrypted);
    const decoder = new TextDecoder("utf8");
    const data = JSON.parse(decoder.decode(bytes));
    const { flowID, streamID, entries } = data;
    const current = data.hasOwnProperty("current")
      ? data.current
      : entries.length - 1;
    const { title, url: uri } = entries[current];
    // `flowID` and `streamID` are in the top-level of the JSON, `entries` is
    // an array of "tabs" with `current` being what index is the one we care
    // about, or the last one if not specified.
    this._fxai.telemetry.recordEvent(
      "command-received",
      COMMAND_SENDTAB_TAIL,
      this._fxai.telemetry.sanitizeDeviceId(senderID),
      { flowID, streamID, reason }
    );

    return {
      title,
      uri,
    };
  }
}

/**
 * Close Tabs
 */
export class CloseRemoteTab extends Command {
  deviceCapability = COMMAND_CLOSETAB;
  keyFieldName = "closeTabKeys";
  encryptedKeyFieldName = "encryptedCloseTabKeys";

  /**
   * @param {Device} target - Device object (typically returned by fxAccounts.getDevicesList()).
   * @param {String[]} urls - array of urls that should be closed on the remote device
   */
  async sendCloseTabsCommand(target, urls, flowID) {
    log.info(`Sending tab closures to ${target.id} device.`);
    const encoder = new TextEncoder();
    try {
      const streamID = this._fxai.telemetry.generateFlowID();
      const targetData = { flowID, streamID, urls };
      const bytes = encoder.encode(JSON.stringify(targetData));
      const encrypted = await this._encrypt(bytes, target);
      // FxA expects an object as the payload, but we only have a single encrypted string; wrap it.
      // If you add any plaintext items to this payload, please carefully consider the privacy implications
      // of revealing that data to the FxA server.
      const payload = { encrypted };
      await this._commands.invoke(COMMAND_CLOSETAB, target, payload);
      this._fxai.telemetry.recordEvent(
        "command-sent",
        COMMAND_CLOSETAB_TAIL,
        this._fxai.telemetry.sanitizeDeviceId(target.id),
        { flowID, streamID }
      );
      return true;
    } catch (error) {
      // We should also show the user there was some kind've error
      log.error("Error while invoking a send tab command.", error);
      return false;
    }
  }

  // Returns true if:
  // - The target device is compatible with closing a tab (device capability) and
  // - The local device has the feature enabled locally
  isDeviceCompatible(device) {
    return (
      lazy.NimbusFeatures.remoteTabManagement.getVariable("closeTabsEnabled") &&
      super.isDeviceCompatible(device)
    );
  }

  // Handle incoming remote tab payload, called by FxAccountsCommands.
  async handleTabClose(senderID, { encrypted }, reason) {
    const bytes = await this._decrypt(encrypted);
    const decoder = new TextDecoder("utf8");
    const data = JSON.parse(decoder.decode(bytes));
    // urls is an array of strings
    const { flowID, streamID, urls } = data;
    this._fxai.telemetry.recordEvent(
      "command-received",
      COMMAND_CLOSETAB_TAIL,
      this._fxai.telemetry.sanitizeDeviceId(senderID),
      { flowID, streamID, reason }
    );

    return {
      urls,
    };
  }
}

export class CommandQueue {
  // The delay between a command being queued and it being actioned. This delay
  // is primarily to support "undo" functionality in the UI.
  // It's likely we will end up needing a different delay per command (including no delay), but this
  // seems fine while we work that out.
  DELAY = 5000;

  // The timer ID if we have one scheduled, otherwise null
  #timer = null;

  // `this.#onShutdown` bound to `this`.
  #onShutdownBound = null;

  // Since we only ever show one notification to the user
  // we keep track of how many tabs have actually been closed
  // and update the count, user dismissing the notification will
  // reset the count
  closeTabNotificationCount = 0;
  hasPendingCloseTabNotification = false;

  // We ensure the queue is flushed soon after startup. After the first tab sync we see, we
  // wait for this many seconds of being idle before checking.
  // Note that this delay has nothing to do with DELAY - that is for "undo" capability, this
  // delay is to ensure we don't put unnecessary load on the browser during startup.
  #idleThresholdSeconds = 3;
  #isObservingTabSyncs = false;
  // This helps ensure we aren't flushing the queue multiple times concurrently.
  #flushQueuePromise = null;

  constructor(commands, fxAccountsInternal) {
    this._commands = commands;
    this._fxai = fxAccountsInternal;
    Services.obs.addObserver(this, "services.sync.tabs.command-queued");
    Services.obs.addObserver(this, "weave:engine:sync:finish");
    this.#isObservingTabSyncs = true;
    log.trace("Command queue observer created");
    this.#onShutdownBound = this.#onShutdown.bind(this);
    lazy.AsyncShutdown.quitApplicationGranted.addBlocker(
      "FxAccountsCommands: flush command queue",
      this.#onShutdownBound
    );
  }

  // Used for tests - when in the browser this object lives forever.
  shutdown() {
    if (this.#timer) {
      clearTimeout(this.#timer);
    }
    Services.obs.removeObserver(this, "services.sync.tabs.command-queued");
    if (this.#isObservingTabSyncs) {
      Services.obs.removeObserver(this, "weave:engine:sync:finish");
      this.#isObservingTabSyncs = false;
    }
    lazy.AsyncShutdown.quitApplicationGranted.removeBlocker(
      this.#onShutdownBound
    );
    this.#onShutdownBound = null;
  }

  observe(subject, topic, data) {
    log.trace(
      `CommandQueue observed topic=${topic}, data=${data}, subject=${subject}`
    );
    switch (topic) {
      case "services.sync.tabs.command-queued":
        this.flushQueue().catch(e => {
          log.error("Failed to flush the outgoing queue", e);
        });
        break;

      case "weave:engine:sync:finish":
        // This is to pick up pending commands we failed to send in the last session.
        if (data != "tabs") {
          return;
        }
        Services.obs.removeObserver(this, "weave:engine:sync:finish");
        this.#isObservingTabSyncs = false;
        this.#checkQueueAfterStartup();
        break;

      default:
        log.error(`unexpected observer topic: ${topic}`);
    }
  }

  // for test mocking.
  _getIdleService() {
    return lazy.idleService;
  }

  async #checkQueueAfterStartup() {
    // do this on idle because we are probably syncing quite close to startup.
    const idleService = this._getIdleService();
    const idleObserver = (/* subject, topic, data */) => {
      idleService.removeIdleObserver(idleObserver, this.#idleThresholdSeconds);
      log.info("checking if the command queue is empty now we are idle");
      this.flushQueue()
        .then(didSend => {
          // TODO: it would be good to get telemetry here, because we expect this to be true rarely.
          log.info(
            `pending command check had ${didSend ? "some" : "no"} commands`
          );
        })
        .catch(err => {
          log.error(
            "Checking for pending tab commands after first tab sync failed",
            err
          );
        });
    };
    idleService.addIdleObserver(idleObserver, this.#idleThresholdSeconds);
  }

  async flushQueue(isForShutdown = false) {
    // We really don't want multiple queue flushes concurrently, which is a real possibility.
    // If we are shutting down and there's already a `flushQueue()` running, it's almost certainly
    // not going to be `isForShutdown()`. We don't really want to wait for that to complete just
    // to start another, so there's a risk we will fail to send commands in that scenario - but
    // we will send them at startup time.
    if (this.#flushQueuePromise == null) {
      this.#flushQueuePromise = this.#flushQueue(isForShutdown);
    }
    try {
      return await this.#flushQueuePromise;
    } finally {
      this.#flushQueuePromise = null;
    }
  }

  async #flushQueue(isForShutdown) {
    // get all the queued items to work out what's ready to send. If a device has queued item less than
    // our pushDelay, then we don't send *any* command for that device yet, but ensure a timer is set
    // for the delay.
    let store = await lazy.getRemoteCommandStore();
    let pending = await store.getUnsentCommands();
    log.trace("flushQueue total queued items", pending.length);
    // any timeRequested less than `sendThreshold` should be sent now (unless we are shutting down,
    // in which case we send everything now)
    let now = this.now();
    // We want to be efficient with batching commands to send to the user
    // so we categorize things into 3 buckets:
    // mustSend - overdue and should be sent as early as we can
    // canSend - is due but not yet "overdue", should be sent if possible
    // early - can still be undone and should not be sent yet
    const mustSendThreshold = isForShutdown ? Infinity : now - this.DELAY;
    const canSendThreshold = isForShutdown ? Infinity : now - this.DELAY * 2;
    // make a map of deviceId -> device
    let recentDevices = this._fxai.device.recentDeviceList;
    if (!recentDevices.length) {
      // If we can't map a device ID to the device with the keys etc, we are screwed!
      log.error("No devices available for queued tab commands");
      return false;
    }
    let deviceMap = new Map(recentDevices.map(d => [d.id, d]));
    // make a map of commands keyed by device ID.
    let byDevice = Map.groupBy(pending, c => c.deviceId);
    let nextTime = Infinity;
    let didSend = false;
    for (let [deviceId, commands] of byDevice) {
      let device = deviceMap.get(deviceId);
      if (!device) {
        // If we can't map *this* device ID to a device with the keys etc, we are screwed!
        // This however *is* possible if the target device was disconnected before we had a chance to send it,
        // so remove this item.
        log.warn("Unknown device for queued tab commands", deviceId);
        await Promise.all(
          commands.map(command => store.removeRemoteCommand(deviceId, command))
        );
        continue;
      }
      let toSend = [];
      // We process in reverse order so it's newest-to-oldest
      // which means if the newest is already a "must send"
      // we can simply send all of the "can sends"
      for (const command of commands.reverse()) {
        if (!(command.command instanceof lazy.RemoteCommand.CloseTab)) {
          log.error(`Ignoring unknown pending command ${command}`);
          continue;
        }
        if (command.timeRequested <= mustSendThreshold) {
          log.trace(
            `command for url ${command.command.url} is overdue, adding to send`
          );
          toSend.push(command);
        } else if (command.timeRequested <= canSendThreshold) {
          if (toSend.length) {
            log.trace(`command for url ${command.command.url} is due,
              since there others to be sent, also adding to send`);
            toSend.push(command);
          } else {
            // Though it's due, since there are no others we can check again
            // and see if we can batch
            nextTime = Math.min(nextTime, command.timeRequested + this.DELAY);
          }
        } else {
          // We set the next timer just a little later to ensure we'll have an overdue
          nextTime = Math.min(
            nextTime,
            command.timeRequested + this.DELAY * 1.1
          );
          // Since the list is sorted newest to oldest,
          // we can assume the rest are not ready
          break;
        }
      }

      if (toSend.length) {
        let urlsToClose = toSend.map(c => c.command.url);
        // Generate a flowID to use for all chunked commands
        const flowID = this._fxai.telemetry.generateFlowID();
        // If we're dealing with large sets of urls, we should split them across
        // multiple payloads to prevent breaking the issues for the user
        let chunks = this.chunkUrls(urlsToClose, COMMAND_MAX_PAYLOAD_SIZE);
        for (let chunk of chunks) {
          if (
            await this._commands.closeTab.sendCloseTabsCommand(
              device,
              chunk,
              flowID
            )
          ) {
            // We build a set from the sent urls for faster comparing
            const urlChunkSet = new Set(chunk);
            // success! Mark them as sent.
            for (let cmd of toSend.filter(c =>
              urlChunkSet.has(c.command.url)
            )) {
              log.trace(
                `Setting pending command for device ${deviceId} as sent`,
                cmd
              );
              await store.setPendingCommandSent(cmd);
              didSend = true;
            }
          } else {
            // We should investigate a better backoff strategy
            // https://bugzilla.mozilla.org/show_bug.cgi?id=1899433
            // For now just say 60s.
            log.warn(
              `Failed to send close tab commands for device ${deviceId}`
            );
            nextTime = Math.min(nextTime, now + 60000);
          }
        }
      } else {
        log.trace(`Skipping send for device ${deviceId}`);
      }
    }

    if (didSend) {
      Services.obs.notifyObservers(null, TOPIC_TABS_CHANGED);
    }

    if (nextTime == Infinity) {
      log.info("No new close-tab timer needed");
    } else if (isForShutdown) {
      // because we never delay sending in this case the logic above should never set `nextTime`
      log.error(
        "logic error in command queue manager: flush for shutdown should never set a timer"
      );
    } else {
      let delay = nextTime - now + 10;
      log.trace(`Setting new close-tab timer for ${delay}ms`);
      this._ensureTimer(delay);
    }
    return didSend;
  }

  // Take a an array of urls and a max size and split them into chunks
  // that are smaller than the passed in max size
  // Note: This method modifies the passed in array
  chunkUrls(urls, maxSize) {
    let chunks = [];

    // For optimal packing, we sort the array of urls from shortest-to-longest
    urls.sort((a, b) => a.length - b.length);

    while (urls.length) {
      let chunk = lazy.Utils.tryFitItems(urls, maxSize);
      if (!chunk.length) {
        // None of the remaining URLs can fit into a single command
        urls.forEach(url => {
          log.warn(`Skipping oversized URL: ${url}`);
        });
        break;
      }
      chunks.push(chunk);
      // Remove the processed URLs from the list
      urls.splice(0, chunk.length);
    }
    return chunks;
  }

  async _ensureTimer(timeout) {
    log.info(
      `Setting a new close-tab timer with delay=${timeout} with existing timer=${!!this
        .#timer}`
    );

    if (this.#timer) {
      clearTimeout(this.#timer);
    }

    // If the browser shuts down while a timer exists we should force the send
    // While we should pick up the command after a restart, we don't know
    // how long that will be.
    // See https://bugzilla.mozilla.org/show_bug.cgi?id=1888299
    this.#timer = setTimeout(async () => {
      // XXX - this might be racey - if a new timer fires before this promise resolves - it
      // might seem unlikely, but network is involved!
      // flushQueue might create another timer, so we must clear our current timer first.
      this.#timer = null;
      await this.flushQueue();
    }, timeout);
  }

  // On shutdown we want to send any pending items - ie, pretend the timer fired *now*.
  // Sadly it's not easy for us to abort any in-flight requests, nor to limit the amount of
  // time any new requests we create take, so we don't do this for now. This means that in
  // the case of a super slow network or super slow FxA, we might crash at shutdown, but we
  // can think of doing this in a followup.
  async #onShutdown() {
    // If there is no timer set, then there's nothing pending to do.
    log.debug(
      `CommandQueue shutdown is flushing the queue with a timer=${!!this
        .#timer}`
    );
    if (this.#timer) {
      // We don't want the current one to fire at the same time!
      clearTimeout(this.#timer);
      this.#timer = null;
      await this.flushQueue(true);
    }
  }

  // hook points for tests.
  now() {
    return Date.now();
  }
}

function urlsafeBase64Encode(buffer) {
  return ChromeUtils.base64URLEncode(new Uint8Array(buffer), { pad: false });
}

function urlsafeBase64Decode(str) {
  return ChromeUtils.base64URLDecode(str, { padding: "reject" });
}
PK
!<�t.{K{K modules/FxAccountsCommon.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { Log } from "resource://gre/modules/Log.sys.mjs";
import { LogManager } from "resource://gre/modules/LogManager.sys.mjs";

// loglevel should be one of "Fatal", "Error", "Warn", "Info", "Config",
// "Debug", "Trace" or "All". If none is specified, "Debug" will be used by
// default.  Note "Debug" is usually appropriate so that when this log is
// included in the Sync file logs we get verbose output.
const PREF_LOG_LEVEL = "identity.fxaccounts.loglevel";

// A pref that can be set so "sensitive" information (eg, personally
// identifiable info, credentials, etc) will be logged.
const PREF_LOG_SENSITIVE_DETAILS = "identity.fxaccounts.log.sensitive";

export let log = Log.repository.getLogger("FirefoxAccounts");
log.manageLevelFromPref(PREF_LOG_LEVEL);

let logs = [
  "Sync",
  "Services.Common",
  "FirefoxAccounts",
  "Hawk",
  "browserwindow.syncui",
  "BookmarkSyncUtils",
  "addons.xpi",
];

// For legacy reasons, the log manager still thinks it's part of sync.
export let logManager = new LogManager({
  prefRoot: "services.sync.",
  logNames: logs,
  logFilePrefix: "sync",
  logFileSubDirectoryEntries: ["weave", "logs"],
  testTopicPrefix: "services-tests:common:log-manager:",
});

// A boolean to indicate if personally identifiable information (or anything
// else sensitive, such as credentials) should be logged.
export let logPII = () =>
  Services.prefs.getBoolPref(PREF_LOG_SENSITIVE_DETAILS, false);

export let FXACCOUNTS_PERMISSION = "firefox-accounts";

export let DATA_FORMAT_VERSION = 1;
export let DEFAULT_STORAGE_FILENAME = "signedInUser.json";

export let OAUTH_TOKEN_FOR_SYNC_LIFETIME_SECONDS = 3600 * 6; // 6 hours

// After we start polling for account verification, we stop polling when this
// many milliseconds have elapsed.
export let POLL_SESSION = 1000 * 60 * 20; // 20 minutes

// Observer notifications.
export let ONLOGIN_NOTIFICATION = "fxaccounts:onlogin";
export let ONVERIFIED_NOTIFICATION = "fxaccounts:onverified";
export let ONLOGOUT_NOTIFICATION = "fxaccounts:onlogout";
export let ON_PRELOGOUT_NOTIFICATION = "fxaccounts:on_pre_logout";
// Internal to services/fxaccounts only
export let ON_DEVICE_CONNECTED_NOTIFICATION = "fxaccounts:device_connected";
export let ON_DEVICE_DISCONNECTED_NOTIFICATION =
  "fxaccounts:device_disconnected";
export let ON_PROFILE_UPDATED_NOTIFICATION = "fxaccounts:profile_updated"; // Push
export let ON_PASSWORD_CHANGED_NOTIFICATION = "fxaccounts:password_changed";
export let ON_PASSWORD_RESET_NOTIFICATION = "fxaccounts:password_reset";
export let ON_ACCOUNT_DESTROYED_NOTIFICATION = "fxaccounts:account_destroyed";
export let ON_COLLECTION_CHANGED_NOTIFICATION = "sync:collection_changed";
export let ON_VERIFY_LOGIN_NOTIFICATION = "fxaccounts:verify_login";
export let ON_COMMAND_RECEIVED_NOTIFICATION = "fxaccounts:command_received";

export let FXA_PUSH_SCOPE_ACCOUNT_UPDATE = "chrome://fxa-device-update";

export let ON_PROFILE_CHANGE_NOTIFICATION = "fxaccounts:profilechange"; // WebChannel
export let ON_ACCOUNT_STATE_CHANGE_NOTIFICATION = "fxaccounts:statechange";
export let ON_NEW_DEVICE_ID = "fxaccounts:new_device_id";
export let ON_DEVICELIST_UPDATED = "fxaccounts:devicelist_updated";

// The common prefix for all commands.
export let COMMAND_PREFIX = "https://identity.mozilla.com/cmd/";

// The commands we support - only the _TAIL values are recorded in telemetry.
export let COMMAND_SENDTAB_TAIL = "open-uri";
export let COMMAND_SENDTAB = COMMAND_PREFIX + COMMAND_SENDTAB_TAIL;
// A command to close a tab on this device
export let COMMAND_CLOSETAB_TAIL = "close-uri/v1";
export let COMMAND_CLOSETAB = COMMAND_PREFIX + COMMAND_CLOSETAB_TAIL;

// OAuth
export let CLIENT_IS_THUNDERBIRD = AppConstants.MOZ_APP_NAME == "thunderbird";
let FX_OAUTH_CLIENT_ID = "5882386c6d801776";
let TB_OAUTH_CLIENT_ID = "8269bacd7bbc7f80";
export let OAUTH_CLIENT_ID = CLIENT_IS_THUNDERBIRD
  ? TB_OAUTH_CLIENT_ID
  : FX_OAUTH_CLIENT_ID;
export let SCOPE_PROFILE = "profile";
export let SCOPE_PROFILE_WRITE = "profile:write";
// Sync scope in Firefox.
export let SCOPE_OLD_SYNC = "https://identity.mozilla.com/apps/oldsync";
// Sync scope in Thunderbird.
let SCOPE_THUNDERBIRD_SYNC = "https://identity.thunderbird.net/apps/sync";
// The scope to use for sync, depending on the current application.
export let SCOPE_APP_SYNC = CLIENT_IS_THUNDERBIRD
  ? SCOPE_THUNDERBIRD_SYNC
  : SCOPE_OLD_SYNC;

// This scope was previously used to calculate a telemetry tracking identifier for
// the account, but that system has since been decommissioned. It's here entirely
// so that we can remove the corresponding key from storage if present. We should
// be safe to remove it after some sensible period of time has elapsed to allow
// most clients to update; ref Bug 1697596.
export let DEPRECATED_SCOPE_ECOSYSTEM_TELEMETRY =
  "https://identity.mozilla.com/ids/ecosystem_telemetry";

// OAuth metadata for other Firefox-related services that we might need to know about
// in order to provide an enhanced user experience.
export let FX_MONITOR_OAUTH_CLIENT_ID = "802d56ef2a9af9fa";
export let FX_RELAY_OAUTH_CLIENT_ID = "9ebfe2c2f9ea3c58";
export let VPN_OAUTH_CLIENT_ID = "e6eb0d1e856335fc";

// UI Requests.
export let UI_REQUEST_SIGN_IN_FLOW = "signInFlow";
export let UI_REQUEST_REFRESH_AUTH = "refreshAuthentication";

// Firefox Accounts WebChannel ID
export let WEBCHANNEL_ID = "account_updates";

// WebChannel commands
export let COMMAND_PAIR_HEARTBEAT = "fxaccounts:pair_heartbeat";
export let COMMAND_PAIR_SUPP_METADATA = "fxaccounts:pair_supplicant_metadata";
export let COMMAND_PAIR_AUTHORIZE = "fxaccounts:pair_authorize";
export let COMMAND_PAIR_DECLINE = "fxaccounts:pair_decline";
export let COMMAND_PAIR_COMPLETE = "fxaccounts:pair_complete";

export let COMMAND_PROFILE_CHANGE = "profile:change";
export let COMMAND_CAN_LINK_ACCOUNT = "fxaccounts:can_link_account";
export let COMMAND_LOGIN = "fxaccounts:login";
export let COMMAND_OAUTH = "fxaccounts:oauth_login";
export let COMMAND_LOGOUT = "fxaccounts:logout";
export let COMMAND_DELETE = "fxaccounts:delete";
export let COMMAND_SYNC_PREFERENCES = "fxaccounts:sync_preferences";
export let COMMAND_CHANGE_PASSWORD = "fxaccounts:change_password";
export let COMMAND_FXA_STATUS = "fxaccounts:fxa_status";
export let COMMAND_PAIR_PREFERENCES = "fxaccounts:pair_preferences";
export let COMMAND_FIREFOX_VIEW = "fxaccounts:firefox_view";

// The pref branch where any prefs which relate to a specific account should
// be stored. This branch will be reset on account signout and signin.
export let PREF_ACCOUNT_ROOT = "identity.fxaccounts.account.";

export let PREF_LAST_FXA_USER = "identity.fxaccounts.lastSignedInUserHash";
export let PREF_REMOTE_PAIRING_URI = "identity.fxaccounts.remote.pairing.uri";

// Server errno.
// From https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-format
export let ERRNO_ACCOUNT_ALREADY_EXISTS = 101;
export let ERRNO_ACCOUNT_DOES_NOT_EXIST = 102;
export let ERRNO_INCORRECT_PASSWORD = 103;
export let ERRNO_UNVERIFIED_ACCOUNT = 104;
export let ERRNO_INVALID_VERIFICATION_CODE = 105;
export let ERRNO_NOT_VALID_JSON_BODY = 106;
export let ERRNO_INVALID_BODY_PARAMETERS = 107;
export let ERRNO_MISSING_BODY_PARAMETERS = 108;
export let ERRNO_INVALID_REQUEST_SIGNATURE = 109;
export let ERRNO_INVALID_AUTH_TOKEN = 110;
export let ERRNO_INVALID_AUTH_TIMESTAMP = 111;
export let ERRNO_MISSING_CONTENT_LENGTH = 112;
export let ERRNO_REQUEST_BODY_TOO_LARGE = 113;
export let ERRNO_TOO_MANY_CLIENT_REQUESTS = 114;
export let ERRNO_INVALID_AUTH_NONCE = 115;
export let ERRNO_ENDPOINT_NO_LONGER_SUPPORTED = 116;
export let ERRNO_INCORRECT_LOGIN_METHOD = 117;
export let ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD = 118;
export let ERRNO_INCORRECT_API_VERSION = 119;
export let ERRNO_INCORRECT_EMAIL_CASE = 120;
export let ERRNO_ACCOUNT_LOCKED = 121;
export let ERRNO_ACCOUNT_UNLOCKED = 122;
export let ERRNO_UNKNOWN_DEVICE = 123;
export let ERRNO_DEVICE_SESSION_CONFLICT = 124;
export let ERRNO_SERVICE_TEMP_UNAVAILABLE = 201;
export let ERRNO_PARSE = 997;
export let ERRNO_NETWORK = 998;
export let ERRNO_UNKNOWN_ERROR = 999;

// Offset oauth server errnos so they don't conflict with auth server errnos
export let OAUTH_SERVER_ERRNO_OFFSET = 1000;

// OAuth Server errno.
export let ERRNO_UNKNOWN_CLIENT_ID = 101 + OAUTH_SERVER_ERRNO_OFFSET;
export let ERRNO_INCORRECT_CLIENT_SECRET = 102 + OAUTH_SERVER_ERRNO_OFFSET;
export let ERRNO_INCORRECT_REDIRECT_URI = 103 + OAUTH_SERVER_ERRNO_OFFSET;
export let ERRNO_INVALID_FXA_ASSERTION = 104 + OAUTH_SERVER_ERRNO_OFFSET;
export let ERRNO_UNKNOWN_CODE = 105 + OAUTH_SERVER_ERRNO_OFFSET;
export let ERRNO_INCORRECT_CODE = 106 + OAUTH_SERVER_ERRNO_OFFSET;
export let ERRNO_EXPIRED_CODE = 107 + OAUTH_SERVER_ERRNO_OFFSET;
export let ERRNO_OAUTH_INVALID_TOKEN = 108 + OAUTH_SERVER_ERRNO_OFFSET;
export let ERRNO_INVALID_REQUEST_PARAM = 109 + OAUTH_SERVER_ERRNO_OFFSET;
export let ERRNO_INVALID_RESPONSE_TYPE = 110 + OAUTH_SERVER_ERRNO_OFFSET;
export let ERRNO_UNAUTHORIZED = 111 + OAUTH_SERVER_ERRNO_OFFSET;
export let ERRNO_FORBIDDEN = 112 + OAUTH_SERVER_ERRNO_OFFSET;
export let ERRNO_INVALID_CONTENT_TYPE = 113 + OAUTH_SERVER_ERRNO_OFFSET;

// Errors.
export let ERROR_ACCOUNT_ALREADY_EXISTS = "ACCOUNT_ALREADY_EXISTS";
export let ERROR_ACCOUNT_DOES_NOT_EXIST = "ACCOUNT_DOES_NOT_EXIST ";
export let ERROR_ACCOUNT_LOCKED = "ACCOUNT_LOCKED";
export let ERROR_ACCOUNT_UNLOCKED = "ACCOUNT_UNLOCKED";
export let ERROR_ALREADY_SIGNED_IN_USER = "ALREADY_SIGNED_IN_USER";
export let ERROR_DEVICE_SESSION_CONFLICT = "DEVICE_SESSION_CONFLICT";
export let ERROR_ENDPOINT_NO_LONGER_SUPPORTED = "ENDPOINT_NO_LONGER_SUPPORTED";
export let ERROR_INCORRECT_API_VERSION = "INCORRECT_API_VERSION";
export let ERROR_INCORRECT_EMAIL_CASE = "INCORRECT_EMAIL_CASE";
export let ERROR_INCORRECT_KEY_RETRIEVAL_METHOD =
  "INCORRECT_KEY_RETRIEVAL_METHOD";
export let ERROR_INCORRECT_LOGIN_METHOD = "INCORRECT_LOGIN_METHOD";
export let ERROR_INVALID_EMAIL = "INVALID_EMAIL";
export let ERROR_INVALID_AUDIENCE = "INVALID_AUDIENCE";
export let ERROR_INVALID_AUTH_TOKEN = "INVALID_AUTH_TOKEN";
export let ERROR_INVALID_AUTH_TIMESTAMP = "INVALID_AUTH_TIMESTAMP";
export let ERROR_INVALID_AUTH_NONCE = "INVALID_AUTH_NONCE";
export let ERROR_INVALID_BODY_PARAMETERS = "INVALID_BODY_PARAMETERS";
export let ERROR_INVALID_PASSWORD = "INVALID_PASSWORD";
export let ERROR_INVALID_VERIFICATION_CODE = "INVALID_VERIFICATION_CODE";
export let ERROR_INVALID_REFRESH_AUTH_VALUE = "INVALID_REFRESH_AUTH_VALUE";
export let ERROR_INVALID_REQUEST_SIGNATURE = "INVALID_REQUEST_SIGNATURE";
export let ERROR_INTERNAL_INVALID_USER = "INTERNAL_ERROR_INVALID_USER";
export let ERROR_MISSING_BODY_PARAMETERS = "MISSING_BODY_PARAMETERS";
export let ERROR_MISSING_CONTENT_LENGTH = "MISSING_CONTENT_LENGTH";
export let ERROR_NO_TOKEN_SESSION = "NO_TOKEN_SESSION";
export let ERROR_NO_SILENT_REFRESH_AUTH = "NO_SILENT_REFRESH_AUTH";
export let ERROR_NOT_VALID_JSON_BODY = "NOT_VALID_JSON_BODY";
export let ERROR_OFFLINE = "OFFLINE";
export let ERROR_PERMISSION_DENIED = "PERMISSION_DENIED";
export let ERROR_REQUEST_BODY_TOO_LARGE = "REQUEST_BODY_TOO_LARGE";
export let ERROR_SERVER_ERROR = "SERVER_ERROR";
export let ERROR_SYNC_DISABLED = "SYNC_DISABLED";
export let ERROR_TOO_MANY_CLIENT_REQUESTS = "TOO_MANY_CLIENT_REQUESTS";
export let ERROR_SERVICE_TEMP_UNAVAILABLE = "SERVICE_TEMPORARY_UNAVAILABLE";
export let ERROR_UI_ERROR = "UI_ERROR";
export let ERROR_UI_REQUEST = "UI_REQUEST";
export let ERROR_PARSE = "PARSE_ERROR";
export let ERROR_NETWORK = "NETWORK_ERROR";
export let ERROR_UNKNOWN = "UNKNOWN_ERROR";
export let ERROR_UNKNOWN_DEVICE = "UNKNOWN_DEVICE";
export let ERROR_UNVERIFIED_ACCOUNT = "UNVERIFIED_ACCOUNT";

// OAuth errors.
export let ERROR_UNKNOWN_CLIENT_ID = "UNKNOWN_CLIENT_ID";
export let ERROR_INCORRECT_CLIENT_SECRET = "INCORRECT_CLIENT_SECRET";
export let ERROR_INCORRECT_REDIRECT_URI = "INCORRECT_REDIRECT_URI";
export let ERROR_INVALID_FXA_ASSERTION = "INVALID_FXA_ASSERTION";
export let ERROR_UNKNOWN_CODE = "UNKNOWN_CODE";
export let ERROR_INCORRECT_CODE = "INCORRECT_CODE";
export let ERROR_EXPIRED_CODE = "EXPIRED_CODE";
export let ERROR_OAUTH_INVALID_TOKEN = "OAUTH_INVALID_TOKEN";
export let ERROR_INVALID_REQUEST_PARAM = "INVALID_REQUEST_PARAM";
export let ERROR_INVALID_RESPONSE_TYPE = "INVALID_RESPONSE_TYPE";
export let ERROR_UNAUTHORIZED = "UNAUTHORIZED";
export let ERROR_FORBIDDEN = "FORBIDDEN";
export let ERROR_INVALID_CONTENT_TYPE = "INVALID_CONTENT_TYPE";

// Additional generic error classes for external consumers
export let ERROR_NO_ACCOUNT = "NO_ACCOUNT";
export let ERROR_AUTH_ERROR = "AUTH_ERROR";
export let ERROR_INVALID_PARAMETER = "INVALID_PARAMETER";

// Status code errors
export let ERROR_CODE_METHOD_NOT_ALLOWED = 405;
export let ERROR_MSG_METHOD_NOT_ALLOWED = "METHOD_NOT_ALLOWED";

// FxAccounts has the ability to "split" the credentials between a plain-text
// JSON file in the profile dir and in the login manager.
// In order to prevent new fields accidentally ending up in the "wrong" place,
// all fields stored are listed here.

// The fields we save in the plaintext JSON.
// See bug 1013064 comments 23-25 for why the sessionToken is "safe"
export let FXA_PWDMGR_PLAINTEXT_FIELDS = new Set([
  "email",
  "verified",
  "authAt",
  "sessionToken",
  "uid",
  "oauthTokens",
  "profile",
  "device",
  "profileCache",
  "encryptedSendTabKeys",
  "encryptedCloseTabKeys",
]);

// Fields we store in secure storage if it exists.
export let FXA_PWDMGR_SECURE_FIELDS = new Set([
  "keyFetchToken",
  "unwrapBKey",
  "scopedKeys",
]);

// An allowlist of fields that remain in storage when the user needs to
// reauthenticate. All other fields will be removed.
export let FXA_PWDMGR_REAUTH_ALLOWLIST = new Set([
  "email",
  "uid",
  "profile",
  "device",
  "verified",
]);

// The pseudo-host we use in the login manager
export let FXA_PWDMGR_HOST = "chrome://FirefoxAccounts";
// The realm we use in the login manager.
export let FXA_PWDMGR_REALM = "Firefox Accounts credentials";

// Error matching.
export let SERVER_ERRNO_TO_ERROR = {
  [ERRNO_ACCOUNT_ALREADY_EXISTS]: ERROR_ACCOUNT_ALREADY_EXISTS,
  [ERRNO_ACCOUNT_DOES_NOT_EXIST]: ERROR_ACCOUNT_DOES_NOT_EXIST,
  [ERRNO_INCORRECT_PASSWORD]: ERROR_INVALID_PASSWORD,
  [ERRNO_UNVERIFIED_ACCOUNT]: ERROR_UNVERIFIED_ACCOUNT,
  [ERRNO_INVALID_VERIFICATION_CODE]: ERROR_INVALID_VERIFICATION_CODE,
  [ERRNO_NOT_VALID_JSON_BODY]: ERROR_NOT_VALID_JSON_BODY,
  [ERRNO_INVALID_BODY_PARAMETERS]: ERROR_INVALID_BODY_PARAMETERS,
  [ERRNO_MISSING_BODY_PARAMETERS]: ERROR_MISSING_BODY_PARAMETERS,
  [ERRNO_INVALID_REQUEST_SIGNATURE]: ERROR_INVALID_REQUEST_SIGNATURE,
  [ERRNO_INVALID_AUTH_TOKEN]: ERROR_INVALID_AUTH_TOKEN,
  [ERRNO_INVALID_AUTH_TIMESTAMP]: ERROR_INVALID_AUTH_TIMESTAMP,
  [ERRNO_MISSING_CONTENT_LENGTH]: ERROR_MISSING_CONTENT_LENGTH,
  [ERRNO_REQUEST_BODY_TOO_LARGE]: ERROR_REQUEST_BODY_TOO_LARGE,
  [ERRNO_TOO_MANY_CLIENT_REQUESTS]: ERROR_TOO_MANY_CLIENT_REQUESTS,
  [ERRNO_INVALID_AUTH_NONCE]: ERROR_INVALID_AUTH_NONCE,
  [ERRNO_ENDPOINT_NO_LONGER_SUPPORTED]: ERROR_ENDPOINT_NO_LONGER_SUPPORTED,
  [ERRNO_INCORRECT_LOGIN_METHOD]: ERROR_INCORRECT_LOGIN_METHOD,
  [ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD]: ERROR_INCORRECT_KEY_RETRIEVAL_METHOD,
  [ERRNO_INCORRECT_API_VERSION]: ERROR_INCORRECT_API_VERSION,
  [ERRNO_INCORRECT_EMAIL_CASE]: ERROR_INCORRECT_EMAIL_CASE,
  [ERRNO_ACCOUNT_LOCKED]: ERROR_ACCOUNT_LOCKED,
  [ERRNO_ACCOUNT_UNLOCKED]: ERROR_ACCOUNT_UNLOCKED,
  [ERRNO_UNKNOWN_DEVICE]: ERROR_UNKNOWN_DEVICE,
  [ERRNO_DEVICE_SESSION_CONFLICT]: ERROR_DEVICE_SESSION_CONFLICT,
  [ERRNO_SERVICE_TEMP_UNAVAILABLE]: ERROR_SERVICE_TEMP_UNAVAILABLE,
  [ERRNO_UNKNOWN_ERROR]: ERROR_UNKNOWN,
  [ERRNO_NETWORK]: ERROR_NETWORK,
  // oauth
  [ERRNO_UNKNOWN_CLIENT_ID]: ERROR_UNKNOWN_CLIENT_ID,
  [ERRNO_INCORRECT_CLIENT_SECRET]: ERROR_INCORRECT_CLIENT_SECRET,
  [ERRNO_INCORRECT_REDIRECT_URI]: ERROR_INCORRECT_REDIRECT_URI,
  [ERRNO_INVALID_FXA_ASSERTION]: ERROR_INVALID_FXA_ASSERTION,
  [ERRNO_UNKNOWN_CODE]: ERROR_UNKNOWN_CODE,
  [ERRNO_INCORRECT_CODE]: ERROR_INCORRECT_CODE,
  [ERRNO_EXPIRED_CODE]: ERROR_EXPIRED_CODE,
  [ERRNO_OAUTH_INVALID_TOKEN]: ERROR_OAUTH_INVALID_TOKEN,
  [ERRNO_INVALID_REQUEST_PARAM]: ERROR_INVALID_REQUEST_PARAM,
  [ERRNO_INVALID_RESPONSE_TYPE]: ERROR_INVALID_RESPONSE_TYPE,
  [ERRNO_UNAUTHORIZED]: ERROR_UNAUTHORIZED,
  [ERRNO_FORBIDDEN]: ERROR_FORBIDDEN,
  [ERRNO_INVALID_CONTENT_TYPE]: ERROR_INVALID_CONTENT_TYPE,
};

// Map internal errors to more generic error classes for consumers
export let ERROR_TO_GENERAL_ERROR_CLASS = {
  [ERROR_ACCOUNT_ALREADY_EXISTS]: ERROR_AUTH_ERROR,
  [ERROR_ACCOUNT_DOES_NOT_EXIST]: ERROR_AUTH_ERROR,
  [ERROR_ACCOUNT_LOCKED]: ERROR_AUTH_ERROR,
  [ERROR_ACCOUNT_UNLOCKED]: ERROR_AUTH_ERROR,
  [ERROR_ALREADY_SIGNED_IN_USER]: ERROR_AUTH_ERROR,
  [ERROR_DEVICE_SESSION_CONFLICT]: ERROR_AUTH_ERROR,
  [ERROR_ENDPOINT_NO_LONGER_SUPPORTED]: ERROR_AUTH_ERROR,
  [ERROR_INCORRECT_API_VERSION]: ERROR_AUTH_ERROR,
  [ERROR_INCORRECT_EMAIL_CASE]: ERROR_AUTH_ERROR,
  [ERROR_INCORRECT_KEY_RETRIEVAL_METHOD]: ERROR_AUTH_ERROR,
  [ERROR_INCORRECT_LOGIN_METHOD]: ERROR_AUTH_ERROR,
  [ERROR_INVALID_EMAIL]: ERROR_AUTH_ERROR,
  [ERROR_INVALID_AUDIENCE]: ERROR_AUTH_ERROR,
  [ERROR_INVALID_AUTH_TOKEN]: ERROR_AUTH_ERROR,
  [ERROR_INVALID_AUTH_TIMESTAMP]: ERROR_AUTH_ERROR,
  [ERROR_INVALID_AUTH_NONCE]: ERROR_AUTH_ERROR,
  [ERROR_INVALID_BODY_PARAMETERS]: ERROR_AUTH_ERROR,
  [ERROR_INVALID_PASSWORD]: ERROR_AUTH_ERROR,
  [ERROR_INVALID_VERIFICATION_CODE]: ERROR_AUTH_ERROR,
  [ERROR_INVALID_REFRESH_AUTH_VALUE]: ERROR_AUTH_ERROR,
  [ERROR_INVALID_REQUEST_SIGNATURE]: ERROR_AUTH_ERROR,
  [ERROR_INTERNAL_INVALID_USER]: ERROR_AUTH_ERROR,
  [ERROR_MISSING_BODY_PARAMETERS]: ERROR_AUTH_ERROR,
  [ERROR_MISSING_CONTENT_LENGTH]: ERROR_AUTH_ERROR,
  [ERROR_NO_TOKEN_SESSION]: ERROR_AUTH_ERROR,
  [ERROR_NO_SILENT_REFRESH_AUTH]: ERROR_AUTH_ERROR,
  [ERROR_NOT_VALID_JSON_BODY]: ERROR_AUTH_ERROR,
  [ERROR_PERMISSION_DENIED]: ERROR_AUTH_ERROR,
  [ERROR_REQUEST_BODY_TOO_LARGE]: ERROR_AUTH_ERROR,
  [ERROR_UNKNOWN_DEVICE]: ERROR_AUTH_ERROR,
  [ERROR_UNVERIFIED_ACCOUNT]: ERROR_AUTH_ERROR,
  [ERROR_UI_ERROR]: ERROR_AUTH_ERROR,
  [ERROR_UI_REQUEST]: ERROR_AUTH_ERROR,
  [ERROR_OFFLINE]: ERROR_NETWORK,
  [ERROR_SERVER_ERROR]: ERROR_NETWORK,
  [ERROR_TOO_MANY_CLIENT_REQUESTS]: ERROR_NETWORK,
  [ERROR_SERVICE_TEMP_UNAVAILABLE]: ERROR_NETWORK,
  [ERROR_PARSE]: ERROR_NETWORK,
  [ERROR_NETWORK]: ERROR_NETWORK,

  // oauth
  [ERROR_INCORRECT_CLIENT_SECRET]: ERROR_AUTH_ERROR,
  [ERROR_INCORRECT_REDIRECT_URI]: ERROR_AUTH_ERROR,
  [ERROR_INVALID_FXA_ASSERTION]: ERROR_AUTH_ERROR,
  [ERROR_UNKNOWN_CODE]: ERROR_AUTH_ERROR,
  [ERROR_INCORRECT_CODE]: ERROR_AUTH_ERROR,
  [ERROR_EXPIRED_CODE]: ERROR_AUTH_ERROR,
  [ERROR_OAUTH_INVALID_TOKEN]: ERROR_AUTH_ERROR,
  [ERROR_INVALID_REQUEST_PARAM]: ERROR_AUTH_ERROR,
  [ERROR_INVALID_RESPONSE_TYPE]: ERROR_AUTH_ERROR,
  [ERROR_UNAUTHORIZED]: ERROR_AUTH_ERROR,
  [ERROR_FORBIDDEN]: ERROR_AUTH_ERROR,
  [ERROR_INVALID_CONTENT_TYPE]: ERROR_AUTH_ERROR,
};
PK
!<u06�** modules/FxAccountsConfig.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { RESTRequest } from "resource://services-common/rest.sys.mjs";

import {
  log,
  SCOPE_APP_SYNC,
  SCOPE_PROFILE,
} from "resource://gre/modules/FxAccountsCommon.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => {
  return ChromeUtils.importESModule(
    "resource://gre/modules/FxAccounts.sys.mjs"
  ).getFxAccountsSingleton();
});

ChromeUtils.defineESModuleGetters(lazy, {
  EnsureFxAccountsWebChannel:
    "resource://gre/modules/FxAccountsWebChannel.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "ROOT_URL",
  "identity.fxaccounts.remote.root"
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "CONTEXT_PARAM",
  "identity.fxaccounts.contextParam"
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "REQUIRES_HTTPS",
  "identity.fxaccounts.allowHttp",
  false,
  null,
  val => !val
);

const CONFIG_PREFS = [
  "identity.fxaccounts.remote.root",
  "identity.fxaccounts.auth.uri",
  "identity.fxaccounts.remote.oauth.uri",
  "identity.fxaccounts.remote.profile.uri",
  "identity.fxaccounts.remote.pairing.uri",
  "identity.sync.tokenserver.uri",
];
const SYNC_PARAM = "sync";

export var FxAccountsConfig = {
  async promiseEmailURI(email, entrypoint, extraParams = {}) {
    const authParams = await this._getAuthParams();
    return this._buildURL("", {
      extraParams: {
        entrypoint,
        email,
        ...authParams,
        ...extraParams,
      },
    });
  },

  async promiseConnectAccountURI(entrypoint, extraParams = {}) {
    const authParams = await this._getAuthParams();
    return this._buildURL("", {
      extraParams: {
        entrypoint,
        action: "email",
        ...authParams,
        ...extraParams,
      },
    });
  },

  async promiseManageURI(entrypoint, extraParams = {}) {
    return this._buildURL("settings", {
      extraParams: { entrypoint, ...extraParams },
      addAccountIdentifiers: true,
    });
  },

  async promiseChangeAvatarURI(entrypoint, extraParams = {}) {
    return this._buildURL("settings/avatar/change", {
      extraParams: { entrypoint, ...extraParams },
      addAccountIdentifiers: true,
    });
  },

  async promiseManageDevicesURI(entrypoint, extraParams = {}) {
    return this._buildURL("settings/clients", {
      extraParams: { entrypoint, ...extraParams },
      addAccountIdentifiers: true,
    });
  },

  async promiseConnectDeviceURI(entrypoint, extraParams = {}) {
    return this._buildURL("connect_another_device", {
      extraParams: { entrypoint, service: SYNC_PARAM, ...extraParams },
      addAccountIdentifiers: true,
    });
  },

  async promisePairingURI(extraParams = {}) {
    return this._buildURL("pair", {
      extraParams,
      includeDefaultParams: false,
    });
  },

  async promiseOAuthURI(extraParams = {}) {
    return this._buildURL("oauth", {
      extraParams,
      includeDefaultParams: false,
    });
  },

  async promiseMetricsFlowURI(entrypoint, extraParams = {}) {
    return this._buildURL("metrics-flow", {
      extraParams: { entrypoint, ...extraParams },
      includeDefaultParams: false,
    });
  },

  get defaultParams() {
    return { context: lazy.CONTEXT_PARAM };
  },

  /**
   * @param path should be parsable by the URL constructor first parameter.
   * @param {bool} [options.includeDefaultParams] If true include the default search params.
   * @param {Object.<string, string>} [options.extraParams] Additionnal search params.
   * @param {bool} [options.addAccountIdentifiers] if true we add the current logged-in user uid and email to the search params.
   */
  async _buildURL(
    path,
    {
      includeDefaultParams = true,
      extraParams = {},
      addAccountIdentifiers = false,
    }
  ) {
    await this.ensureConfigured();
    const url = new URL(path, lazy.ROOT_URL);
    if (lazy.REQUIRES_HTTPS && url.protocol != "https:") {
      throw new Error("Firefox Accounts server must use HTTPS");
    }
    const params = {
      ...(includeDefaultParams ? this.defaultParams : null),
      ...extraParams,
    };
    for (let [k, v] of Object.entries(params)) {
      url.searchParams.append(k, v);
    }
    if (addAccountIdentifiers) {
      const accountData = await this.getSignedInUser();
      if (!accountData) {
        return null;
      }
      url.searchParams.append("uid", accountData.uid);
      url.searchParams.append("email", accountData.email);
    }
    return url.href;
  },

  async _buildURLFromString(href, extraParams = {}) {
    const url = new URL(href);
    for (let [k, v] of Object.entries(extraParams)) {
      url.searchParams.append(k, v);
    }
    return url.href;
  },

  resetConfigURLs() {
    let autoconfigURL = this.getAutoConfigURL();
    if (autoconfigURL) {
      return;
    }
    // They have the autoconfig uri pref set, so we clear all the prefs that we
    // will have initialized, which will leave them pointing at production.
    for (let pref of CONFIG_PREFS) {
      Services.prefs.clearUserPref(pref);
    }
    // Reset the webchannel.
    lazy.EnsureFxAccountsWebChannel();
  },

  getAutoConfigURL() {
    let pref = Services.prefs.getStringPref(
      "identity.fxaccounts.autoconfig.uri",
      ""
    );
    if (!pref) {
      // no pref / empty pref means we don't bother here.
      return "";
    }
    let rootURL = Services.urlFormatter.formatURL(pref);
    if (rootURL.endsWith("/")) {
      rootURL = rootURL.slice(0, -1);
    }
    return rootURL;
  },

  async ensureConfigured() {
    let isSignedIn = !!(await this.getSignedInUser());
    if (!isSignedIn) {
      await this.updateConfigURLs();
    }
  },

  // Returns true if this user is using the FxA "production" systems, false
  // if using any other configuration, including self-hosting or the FxA
  // non-production systems such as "dev" or "staging".
  // It's typically used as a proxy for "is this likely to be a self-hosted
  // user?", but it's named this way to make the implementation less
  // surprising. As a result, it's fairly conservative and would prefer to have
  // a false-negative than a false-position as it determines things which users
  // might consider sensitive (notably, telemetry).
  // Note also that while it's possible to self-host just sync and not FxA, we
  // don't make that distinction - that's a self-hoster from the POV of this
  // function.
  isProductionConfig() {
    // Specifically, if the autoconfig URLs, or *any* of the URLs that
    // we consider configurable are modified, we assume self-hosted.
    if (this.getAutoConfigURL()) {
      return false;
    }
    for (let pref of CONFIG_PREFS) {
      if (Services.prefs.prefHasUserValue(pref)) {
        return false;
      }
    }
    return true;
  },

  // Read expected client configuration from the fxa auth server
  // (from `identity.fxaccounts.autoconfig.uri`/.well-known/fxa-client-configuration)
  // and replace all the relevant our prefs with the information found there.
  // This is only done before sign-in and sign-up, and even then only if the
  // `identity.fxaccounts.autoconfig.uri` preference is set.
  async updateConfigURLs() {
    let rootURL = this.getAutoConfigURL();
    if (!rootURL) {
      return;
    }
    const config = await this.fetchConfigDocument(rootURL);
    try {
      // Update the prefs directly specified by the config.
      let authServerBase = config.auth_server_base_url;
      if (!authServerBase.endsWith("/v1")) {
        authServerBase += "/v1";
      }
      Services.prefs.setStringPref(
        "identity.fxaccounts.auth.uri",
        authServerBase
      );
      Services.prefs.setStringPref(
        "identity.fxaccounts.remote.oauth.uri",
        config.oauth_server_base_url + "/v1"
      );
      // At the time of landing this, our servers didn't yet answer with pairing_server_base_uri.
      // Remove this condition check once Firefox 68 is stable.
      if (config.pairing_server_base_uri) {
        Services.prefs.setStringPref(
          "identity.fxaccounts.remote.pairing.uri",
          config.pairing_server_base_uri
        );
      }
      Services.prefs.setStringPref(
        "identity.fxaccounts.remote.profile.uri",
        config.profile_server_base_url + "/v1"
      );
      Services.prefs.setStringPref(
        "identity.sync.tokenserver.uri",
        config.sync_tokenserver_base_url + "/1.0/sync/1.5"
      );
      Services.prefs.setStringPref("identity.fxaccounts.remote.root", rootURL);

      // Ensure the webchannel is pointed at the correct uri
      lazy.EnsureFxAccountsWebChannel();
    } catch (e) {
      log.error(
        "Failed to initialize configuration preferences from autoconfig object",
        e
      );
      throw e;
    }
  },

  // Read expected client configuration from the fxa auth server
  // (or from the provided rootURL, if present) and return it as an object.
  async fetchConfigDocument(rootURL = null) {
    if (!rootURL) {
      rootURL = lazy.ROOT_URL;
    }
    let configURL = rootURL + "/.well-known/fxa-client-configuration";
    let request = new RESTRequest(configURL);
    request.setHeader("Accept", "application/json");

    // Catch and rethrow the error inline.
    let resp = await request.get().catch(e => {
      log.error(`Failed to get configuration object from "${configURL}"`, e);
      throw e;
    });
    if (!resp.success) {
      // Note: 'resp.body' is included with the error log below as we are not concerned
      // that the body will contain PII, but if that changes it should be excluded.
      log.error(
        `Received HTTP response code ${resp.status} from configuration object request:
        ${resp.body}`
      );
      throw new Error(
        `HTTP status ${resp.status} from configuration object request`
      );
    }
    log.debug("Got successful configuration response", resp.body);
    try {
      return JSON.parse(resp.body);
    } catch (e) {
      log.error(
        `Failed to parse configuration preferences from ${configURL}`,
        e
      );
      throw e;
    }
  },

  // For test purposes, returns a Promise.
  getSignedInUser() {
    return lazy.fxAccounts.getSignedInUser();
  },

  _isOAuthFlow() {
    return Services.prefs.getBoolPref(
      "identity.fxaccounts.oauth.enabled",
      false
    );
  },

  async _getAuthParams() {
    if (this._isOAuthFlow()) {
      const scopes = [SCOPE_APP_SYNC, SCOPE_PROFILE];
      return lazy.fxAccounts._internal.beginOAuthFlow(scopes);
    }
    return { service: SYNC_PARAM };
  },
};
PK
!<���L-X-X modules/FxAccountsDevice.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import {
  log,
  ERRNO_DEVICE_SESSION_CONFLICT,
  ERRNO_UNKNOWN_DEVICE,
  ON_NEW_DEVICE_ID,
  ON_DEVICELIST_UPDATED,
  ON_DEVICE_CONNECTED_NOTIFICATION,
  ON_DEVICE_DISCONNECTED_NOTIFICATION,
  ONVERIFIED_NOTIFICATION,
  PREF_ACCOUNT_ROOT,
} from "resource://gre/modules/FxAccountsCommon.sys.mjs";

import { DEVICE_TYPE_DESKTOP } from "resource://services-sync/constants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  CommonUtils: "resource://services-common/utils.sys.mjs",
});

const PREF_LOCAL_DEVICE_NAME = PREF_ACCOUNT_ROOT + "device.name";
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "pref_localDeviceName",
  PREF_LOCAL_DEVICE_NAME,
  ""
);

const PREF_DEPRECATED_DEVICE_NAME = "services.sync.client.name";

// Sanitizes all characters which the FxA server considers invalid, replacing
// them with the unicode replacement character.
// At time of writing, FxA has a regex DISPLAY_SAFE_UNICODE_WITH_NON_BMP, which
// the regex below is based on.
const INVALID_NAME_CHARS =
  // eslint-disable-next-line no-control-regex
  /[\u0000-\u001F\u007F\u0080-\u009F\u2028-\u2029\uE000-\uF8FF\uFFF9-\uFFFC\uFFFE-\uFFFF]/g;
const MAX_NAME_LEN = 255;
const REPLACEMENT_CHAR = "\uFFFD";

function sanitizeDeviceName(name) {
  return name
    .substr(0, MAX_NAME_LEN)
    .replace(INVALID_NAME_CHARS, REPLACEMENT_CHAR);
}

// Everything to do with FxA devices.
export class FxAccountsDevice {
  constructor(fxai) {
    this._fxai = fxai;
    this._deviceListCache = null;
    this._fetchAndCacheDeviceListPromise = null;

    // The current version of the device registration, we use this to re-register
    // devices after we update what we send on device registration.
    this.DEVICE_REGISTRATION_VERSION = 2;

    // This is to avoid multiple sequential syncs ending up calling
    // this expensive endpoint multiple times in a row.
    this.TIME_BETWEEN_FXA_DEVICES_FETCH_MS = 1 * 60 * 1000; // 1 minute

    // Invalidate our cached device list when a device is connected or disconnected.
    Services.obs.addObserver(this, ON_DEVICE_CONNECTED_NOTIFICATION, true);
    Services.obs.addObserver(this, ON_DEVICE_DISCONNECTED_NOTIFICATION, true);
    // A user becoming verified probably means we need to re-register the device
    // because we are now able to get the sendtab keys.
    Services.obs.addObserver(this, ONVERIFIED_NOTIFICATION, true);
  }

  async getLocalId() {
    return this._withCurrentAccountState(currentState => {
      // It turns out _updateDeviceRegistrationIfNecessary() does exactly what we
      // need.
      return this._updateDeviceRegistrationIfNecessary(currentState);
    });
  }

  // Generate a client name if we don't have a useful one yet
  getDefaultLocalName() {
    let user = Services.env.get("USER") || Services.env.get("USERNAME");
    // Note that we used to fall back to the "services.sync.username" pref here,
    // but that's no longer suitable in a world where sync might not be
    // configured. However, we almost never *actually* fell back to that, and
    // doing so sanely here would mean making this function async, which we don't
    // really want to do yet.

    // A little hack for people using the the moz-build environment on Windows
    // which sets USER to the literal "%USERNAME%" (yes, really)
    if (user == "%USERNAME%" && Services.env.get("USERNAME")) {
      user = Services.env.get("USERNAME");
    }

    // The DNS service may fail to provide a hostname in edge-cases we don't
    // fully understand - bug 1391488.
    let hostname;
    try {
      // hostname of the system, usually assigned by the user or admin
      hostname = Services.dns.myHostName;
    } catch (ex) {
      console.error(ex);
    }
    let system =
      // 'device' is defined on unix systems
      Services.sysinfo.get("device") ||
      hostname ||
      // fall back on ua info string
      Cc["@mozilla.org/network/protocol;1?name=http"].getService(
        Ci.nsIHttpProtocolHandler
      ).oscpu;

    const l10n = new Localization(
      ["services/accounts.ftl", "branding/brand.ftl"],
      true
    );
    return sanitizeDeviceName(
      l10n.formatValueSync("account-client-name", { user, system })
    );
  }

  getLocalName() {
    // We used to store this in services.sync.client.name, but now store it
    // under an fxa-specific location.
    let deprecated_value = Services.prefs.getStringPref(
      PREF_DEPRECATED_DEVICE_NAME,
      ""
    );
    if (deprecated_value) {
      Services.prefs.setStringPref(PREF_LOCAL_DEVICE_NAME, deprecated_value);
      Services.prefs.clearUserPref(PREF_DEPRECATED_DEVICE_NAME);
    }
    let name = lazy.pref_localDeviceName;
    if (!name) {
      name = this.getDefaultLocalName();
      Services.prefs.setStringPref(PREF_LOCAL_DEVICE_NAME, name);
    }
    // We need to sanitize here because some names were generated before we
    // started sanitizing.
    return sanitizeDeviceName(name);
  }

  setLocalName(newName) {
    Services.prefs.clearUserPref(PREF_DEPRECATED_DEVICE_NAME);
    Services.prefs.setStringPref(
      PREF_LOCAL_DEVICE_NAME,
      sanitizeDeviceName(newName)
    );
    // Update the registration in the background.
    this.updateDeviceRegistration().catch(error => {
      log.warn("failed to update fxa device registration", error);
    });
  }

  getLocalType() {
    return DEVICE_TYPE_DESKTOP;
  }

  /**
   * Returns the most recently fetched device list, or `null` if the list
   * hasn't been fetched yet. This is synchronous, so that consumers like
   * Send Tab can render the device list right away, without waiting for
   * it to refresh.
   *
   * @type {?Array}
   */
  get recentDeviceList() {
    return this._deviceListCache ? this._deviceListCache.devices : null;
  }

  /**
   * Refreshes the device list. After this function returns, consumers can
   * access the new list using the `recentDeviceList` getter. Note that
   * multiple concurrent calls to `refreshDeviceList` will only refresh the
   * list once.
   *
   * @param  {Boolean} [options.ignoreCached]
   *         If `true`, forces a refresh, even if the cached device list is
   *         still fresh. Defaults to `false`.
   * @return {Promise<Boolean>}
   *         `true` if the list was refreshed, `false` if the cached list is
   *         fresh. Rejects if an error occurs refreshing the list or device
   *         push registration.
   */
  async refreshDeviceList({ ignoreCached = false } = {}) {
    // If we're already refreshing the list in the background, let that finish.
    if (this._fetchAndCacheDeviceListPromise) {
      log.info("Already fetching device list, return existing promise");
      return this._fetchAndCacheDeviceListPromise;
    }

    // If the cache is fresh enough, don't refresh it again.
    if (!ignoreCached && this._deviceListCache) {
      const ageOfCache = this._fxai.now() - this._deviceListCache.lastFetch;
      if (ageOfCache < this.TIME_BETWEEN_FXA_DEVICES_FETCH_MS) {
        log.info("Device list cache is fresh, re-using it");
        return false;
      }
    }

    log.info("fetching updated device list");
    this._fetchAndCacheDeviceListPromise = (async () => {
      try {
        const devices = await this._withVerifiedAccountState(
          async currentState => {
            const accountData = await currentState.getUserAccountData([
              "sessionToken",
              "device",
            ]);
            const devices = await this._fxai.fxAccountsClient.getDeviceList(
              accountData.sessionToken
            );
            log.info(
              `Got new device list: ${devices.map(d => d.id).join(", ")}`
            );

            await this._refreshRemoteDevice(currentState, accountData, devices);
            return devices;
          }
        );
        log.info("updating the cache");
        // Be careful to only update the cache once the above has resolved, so
        // we know that the current account state didn't change underneath us.
        this._deviceListCache = {
          lastFetch: this._fxai.now(),
          devices,
        };
        Services.obs.notifyObservers(null, ON_DEVICELIST_UPDATED);
        return true;
      } finally {
        this._fetchAndCacheDeviceListPromise = null;
      }
    })();
    return this._fetchAndCacheDeviceListPromise;
  }

  async _refreshRemoteDevice(currentState, accountData, remoteDevices) {
    // Check if our push registration previously succeeded and is still
    // good (although background device registration means it's possible
    // we'll be fetching the device list before we've actually
    // registered ourself!)
    // (For a missing subscription we check for an explicit 'null' -
    // both to help tests and as a safety valve - missing might mean
    // "no push available" for self-hosters or similar?)
    const ourDevice = remoteDevices.find(device => device.isCurrentDevice);
    const subscription = await this._fxai.fxaPushService.getSubscription();
    if (
      ourDevice &&
      (ourDevice.pushCallback === null || // fxa server doesn't know our subscription.
        ourDevice.pushEndpointExpired || // fxa server thinks it has expired.
        !subscription || // we don't have a local subscription.
        subscription.isExpired() || // our local subscription is expired.
        ourDevice.pushCallback != subscription.endpoint) // we don't agree with fxa.
    ) {
      log.warn(`Our push endpoint needs resubscription`);
      await this._fxai.fxaPushService.unsubscribe();
      await this._registerOrUpdateDevice(currentState, accountData);
      // and there's a reasonable chance there are commands waiting.
      await this._fxai.commands.pollDeviceCommands();
    } else if (
      ourDevice &&
      (await this._checkRemoteCommandsUpdateNeeded(ourDevice.availableCommands))
    ) {
      log.warn(`Our commands need to be updated on the server`);
      await this._registerOrUpdateDevice(currentState, accountData);
    } else {
      log.trace(`Our push subscription looks OK`);
    }
  }

  async updateDeviceRegistration() {
    return this._withCurrentAccountState(async currentState => {
      const signedInUser = await currentState.getUserAccountData([
        "sessionToken",
        "device",
      ]);
      if (signedInUser) {
        await this._registerOrUpdateDevice(currentState, signedInUser);
      }
    });
  }

  async updateDeviceRegistrationIfNecessary() {
    return this._withCurrentAccountState(currentState => {
      return this._updateDeviceRegistrationIfNecessary(currentState);
    });
  }

  reset() {
    this._deviceListCache = null;
    this._fetchAndCacheDeviceListPromise = null;
  }

  /**
   * Here begin our internal helper methods.
   *
   * Many of these methods take the current account state as first argument,
   * in order to avoid racing our state updates with e.g. the uer signing
   * out while we're in the middle of an update. If this does happen, the
   * resulting promise will be rejected rather than persisting stale state.
   *
   */

  _withCurrentAccountState(func) {
    return this._fxai.withCurrentAccountState(async currentState => {
      try {
        return await func(currentState);
      } catch (err) {
        // `_handleTokenError` always throws, this syntax keeps the linter happy.
        // TODO: probably `_handleTokenError` could be done by `_fxai.withCurrentAccountState`
        // internally rather than us having to remember to do it here.
        throw await this._fxai._handleTokenError(err);
      }
    });
  }

  _withVerifiedAccountState(func) {
    return this._fxai.withVerifiedAccountState(async currentState => {
      try {
        return await func(currentState);
      } catch (err) {
        // `_handleTokenError` always throws, this syntax keeps the linter happy.
        throw await this._fxai._handleTokenError(err);
      }
    });
  }

  async _checkDeviceUpdateNeeded(device) {
    // There is no device registered or the device registration is outdated.
    // Either way, we should register the device with FxA
    // before returning the id to the caller.
    const availableCommandsKeys = Object.keys(
      await this._fxai.commands.availableCommands()
    ).sort();
    return (
      !device ||
      !device.registrationVersion ||
      device.registrationVersion < this.DEVICE_REGISTRATION_VERSION ||
      !device.registeredCommandsKeys ||
      !lazy.CommonUtils.arrayEqual(
        device.registeredCommandsKeys,
        availableCommandsKeys
      )
    );
  }

  async _checkRemoteCommandsUpdateNeeded(remoteAvailableCommands) {
    if (!remoteAvailableCommands) {
      return true;
    }
    const remoteAvailableCommandsKeys = Object.keys(
      remoteAvailableCommands
    ).sort();
    const localAvailableCommands =
      await this._fxai.commands.availableCommands();
    const localAvailableCommandsKeys = Object.keys(
      localAvailableCommands
    ).sort();

    if (
      !lazy.CommonUtils.arrayEqual(
        localAvailableCommandsKeys,
        remoteAvailableCommandsKeys
      )
    ) {
      return true;
    }

    for (const key of localAvailableCommandsKeys) {
      if (remoteAvailableCommands[key] !== localAvailableCommands[key]) {
        return true;
      }
    }
    return false;
  }

  async _updateDeviceRegistrationIfNecessary(currentState) {
    let data = await currentState.getUserAccountData([
      "sessionToken",
      "device",
    ]);
    if (!data) {
      // Can't register a device without a signed-in user.
      return null;
    }
    const { device } = data;
    if (await this._checkDeviceUpdateNeeded(device)) {
      return this._registerOrUpdateDevice(currentState, data);
    }
    // Return the device ID we already had.
    return device.id;
  }

  // If you change what we send to the FxA servers during device registration,
  // you'll have to bump the DEVICE_REGISTRATION_VERSION number to force older
  // devices to re-register when Firefox updates.
  async _registerOrUpdateDevice(currentState, signedInUser) {
    // This method has the side-effect of setting some account-related prefs
    // (e.g. for caching the device name) so it's important we don't execute it
    // if the signed-in state has changed.
    if (!currentState.isCurrent) {
      throw new Error(
        "_registerOrUpdateDevice called after a different user has signed in"
      );
    }

    const { sessionToken, device: currentDevice } = signedInUser;
    if (!sessionToken) {
      throw new Error("_registerOrUpdateDevice called without a session token");
    }

    try {
      const subscription =
        await this._fxai.fxaPushService.registerPushEndpoint();
      const deviceName = this.getLocalName();
      let deviceOptions = {};

      // if we were able to obtain a subscription
      if (subscription && subscription.endpoint) {
        deviceOptions.pushCallback = subscription.endpoint;
        let publicKey = subscription.getKey("p256dh");
        let authKey = subscription.getKey("auth");
        if (publicKey && authKey) {
          deviceOptions.pushPublicKey = urlsafeBase64Encode(publicKey);
          deviceOptions.pushAuthKey = urlsafeBase64Encode(authKey);
        }
      }
      deviceOptions.availableCommands =
        await this._fxai.commands.availableCommands();
      const availableCommandsKeys = Object.keys(
        deviceOptions.availableCommands
      ).sort();
      log.info("registering with available commands", availableCommandsKeys);

      let device;
      let is_existing = currentDevice && currentDevice.id;
      if (is_existing) {
        log.debug("updating existing device details");
        device = await this._fxai.fxAccountsClient.updateDevice(
          sessionToken,
          currentDevice.id,
          deviceName,
          deviceOptions
        );
      } else {
        log.debug("registering new device details");
        device = await this._fxai.fxAccountsClient.registerDevice(
          sessionToken,
          deviceName,
          this.getLocalType(),
          deviceOptions
        );
      }

      // Get the freshest device props before updating them.
      let { device: deviceProps } = await currentState.getUserAccountData([
        "device",
      ]);
      await currentState.updateUserAccountData({
        device: {
          ...deviceProps, // Copy the other properties (e.g. handledCommands).
          id: device.id,
          registrationVersion: this.DEVICE_REGISTRATION_VERSION,
          registeredCommandsKeys: availableCommandsKeys,
        },
      });
      // Must send the notification after we've written the storage.
      if (!is_existing) {
        Services.obs.notifyObservers(null, ON_NEW_DEVICE_ID);
      }
      return device.id;
    } catch (error) {
      return this._handleDeviceError(currentState, error, sessionToken);
    }
  }

  async _handleDeviceError(currentState, error, sessionToken) {
    try {
      if (error.code === 400) {
        if (error.errno === ERRNO_UNKNOWN_DEVICE) {
          return this._recoverFromUnknownDevice(currentState);
        }

        if (error.errno === ERRNO_DEVICE_SESSION_CONFLICT) {
          return this._recoverFromDeviceSessionConflict(
            currentState,
            error,
            sessionToken
          );
        }
      }

      // `_handleTokenError` always throws, this syntax keeps the linter happy.
      // Note that the re-thrown error is immediately caught, logged and ignored
      // by the containing scope here, which is why we have to `_handleTokenError`
      // ourselves rather than letting it bubble up for handling by the caller.
      throw await this._fxai._handleTokenError(error);
    } catch (error) {
      await this._logErrorAndResetDeviceRegistrationVersion(
        currentState,
        error
      );
      return null;
    }
  }

  async _recoverFromUnknownDevice(currentState) {
    // FxA did not recognise the device id. Handle it by clearing the device
    // id on the account data. At next sync or next sign-in, registration is
    // retried and should succeed.
    log.warn("unknown device id, clearing the local device data");
    try {
      await currentState.updateUserAccountData({
        device: null,
        encryptedSendTabKeys: null,
      });
    } catch (error) {
      await this._logErrorAndResetDeviceRegistrationVersion(
        currentState,
        error
      );
    }
    return null;
  }

  async _recoverFromDeviceSessionConflict(currentState, error, sessionToken) {
    // FxA has already associated this session with a different device id.
    // Perhaps we were beaten in a race to register. Handle the conflict:
    //   1. Fetch the list of devices for the current user from FxA.
    //   2. Look for ourselves in the list.
    //   3. If we find a match, set the correct device id and device registration
    //      version on the account data and return the correct device id. At next
    //      sync or next sign-in, registration is retried and should succeed.
    //   4. If we don't find a match, log the original error.
    log.warn(
      "device session conflict, attempting to ascertain the correct device id"
    );
    try {
      const devices = await this._fxai.fxAccountsClient.getDeviceList(
        sessionToken
      );
      const matchingDevices = devices.filter(device => device.isCurrentDevice);
      const length = matchingDevices.length;
      if (length === 1) {
        const deviceId = matchingDevices[0].id;
        await currentState.updateUserAccountData({
          device: {
            id: deviceId,
            registrationVersion: null,
          },
          encryptedSendTabKeys: null,
        });
        return deviceId;
      }
      if (length > 1) {
        log.error(
          "insane server state, " + length + " devices for this session"
        );
      }
      await this._logErrorAndResetDeviceRegistrationVersion(
        currentState,
        error
      );
    } catch (secondError) {
      log.error("failed to recover from device-session conflict", secondError);
      await this._logErrorAndResetDeviceRegistrationVersion(
        currentState,
        error
      );
    }
    return null;
  }

  async _logErrorAndResetDeviceRegistrationVersion(currentState, error) {
    // Device registration should never cause other operations to fail.
    // If we've reached this point, just log the error and reset the device
    // on the account data. At next sync or next sign-in,
    // registration will be retried.
    log.error("device registration failed", error);
    try {
      await currentState.updateUserAccountData({
        device: null,
        encryptedSendTabKeys: null,
      });
    } catch (secondError) {
      log.error(
        "failed to reset the device registration version, device registration won't be retried",
        secondError
      );
    }
  }

  // Kick off a background refresh when a device is connected or disconnected.
  observe(subject, topic, data) {
    switch (topic) {
      case ON_DEVICE_CONNECTED_NOTIFICATION:
        this.refreshDeviceList({ ignoreCached: true }).catch(error => {
          log.warn(
            "failed to refresh devices after connecting a new device",
            error
          );
        });
        break;
      case ON_DEVICE_DISCONNECTED_NOTIFICATION:
        let json = JSON.parse(data);
        if (!json.isLocalDevice) {
          // If we're the device being disconnected, don't bother fetching a new
          // list, since our session token is now invalid.
          this.refreshDeviceList({ ignoreCached: true }).catch(error => {
            log.warn(
              "failed to refresh devices after disconnecting a device",
              error
            );
          });
        }
        break;
      case ONVERIFIED_NOTIFICATION:
        this.updateDeviceRegistrationIfNecessary().catch(error => {
          log.warn(
            "updateDeviceRegistrationIfNecessary failed after verification",
            error
          );
        });
        break;
    }
  }
}

FxAccountsDevice.prototype.QueryInterface = ChromeUtils.generateQI([
  "nsIObserver",
  "nsISupportsWeakReference",
]);

function urlsafeBase64Encode(buffer) {
  return ChromeUtils.base64URLEncode(new Uint8Array(buffer), { pad: false });
}
PK
!<��]�"�"modules/FxAccountsOAuth.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  jwcrypto: "resource://services-crypto/jwcrypto.sys.mjs",
});

import {
  OAUTH_CLIENT_ID,
  SCOPE_PROFILE,
  SCOPE_PROFILE_WRITE,
  SCOPE_APP_SYNC,
} from "resource://gre/modules/FxAccountsCommon.sys.mjs";

const VALID_SCOPES = [SCOPE_PROFILE, SCOPE_PROFILE_WRITE, SCOPE_APP_SYNC];

export const ERROR_INVALID_SCOPES = "INVALID_SCOPES";
export const ERROR_INVALID_STATE = "INVALID_STATE";
export const ERROR_SYNC_SCOPE_NOT_GRANTED = "ERROR_SYNC_SCOPE_NOT_GRANTED";
export const ERROR_NO_KEYS_JWE = "ERROR_NO_KEYS_JWE";
export const ERROR_OAUTH_FLOW_ABANDONED = "ERROR_OAUTH_FLOW_ABANDONED";
export const ERROR_INVALID_SCOPED_KEYS = "ERROR_INVALID_SCOPED_KEYS";

/**
 * Handles all logic and state related to initializing, and completing OAuth flows
 * with FxA
 * It's possible to start multiple OAuth flow, but only one can be completed, and once one flow is completed
 * all the other in-flight flows will be concluded, and attempting to complete those flows will result in errors.
 */
export class FxAccountsOAuth {
  #flow;
  #fxaClient;
  #fxaKeys;
  /**
   * Creates a new FxAccountsOAuth
   *
   * @param { Object } fxaClient: The fxa client used to send http request to the oauth server
   */
  constructor(fxaClient, fxaKeys) {
    this.#flow = {};
    this.#fxaClient = fxaClient;
    this.#fxaKeys = fxaKeys;
  }

  /**
   * Stores a flow in-memory
   * @param { string } state: A base-64 URL-safe string represnting a random value created at the start of the flow
   * @param { Object } value: The data needed to complete a flow, once the oauth code is available.
   * in practice, `value` is:
   *  - `verifier`: A base=64 URL-safe string representing the PKCE code verifier
   *  - `key`: The private key need to decrypt the JWE we recieve from the auth server
   *  - `requestedScopes`: The scopes the caller requested, meant to be compared against the scopes the server authorized
   */
  addFlow(state, value) {
    this.#flow[state] = value;
  }

  /**
   * Clears all started flows
   */
  clearAllFlows() {
    this.#flow = {};
  }

  /*
   * Gets a stored flow
   * @param { string } state: The base-64 URL-safe state string that was created at the start of the flow
   * @returns { Object }: The values initially stored when startign th eoauth flow
   * in practice, the return value is:
   *  - `verifier`: A base=64 URL-safe string representing the PKCE code verifier
   *  - `key`: The private key need to decrypt the JWE we recieve from the auth server
   *  - ``requestedScopes`: The scopes the caller requested, meant to be compared against the scopes the server authorized
   */
  getFlow(state) {
    return this.#flow[state];
  }

  /* Returns the number of flows, used by tests
   *
   */
  numOfFlows() {
    return Object.keys(this.#flow).length;
  }

  /**
   * Begins an OAuth flow, to be completed with a an OAuth code and state.
   *
   * This function stores needed information to complete the flow. You must call `completeOAuthFlow`
   * on the same instance of `FxAccountsOAuth`, otherwise the completing of the oauth flow will fail.
   *
   * @param { string[] } scopes: The OAuth scopes the client should request from FxA
   *
   * @returns { Object }: Returns an object representing the query parameters that should be
   *     added to the FxA authorization URL to initialize an oAuth flow.
   *     In practice, the query parameters are:
   *       - `client_id`: The OAuth client ID for Firefox Desktop
   *       - `scope`: The scopes given by the caller, space seperated
   *       - `action`: This will always be `email`
   *       - `response_type`: This will always be `code`
   *       - `access_type`: This will always be `offline`
   *       - `state`: A URL-safe base-64 string randomly generated
   *       - `code_challenge`: A URL-safe base-64 string representing the PKCE challenge
   *       - `code_challenge_method`: This will always be `S256`
   *          For more informatio about PKCE, read https://datatracker.ietf.org/doc/html/rfc7636
   *       - `keys_jwk`: A URL-safe base-64 representing a JWK to be used as a public key by the server
   *          to generate a JWE
   */
  async beginOAuthFlow(scopes) {
    if (
      !Array.isArray(scopes) ||
      scopes.some(scope => !VALID_SCOPES.includes(scope))
    ) {
      throw new Error(ERROR_INVALID_SCOPES);
    }
    const queryParams = {
      client_id: OAUTH_CLIENT_ID,
      action: "email",
      response_type: "code",
      access_type: "offline",
      scope: scopes.join(" "),
    };

    // Generate a random, 16 byte value to represent a state that we verify
    // once we complete the oauth flow, to ensure that we only conclude
    // an oauth flow that we started
    const state = new Uint8Array(16);
    crypto.getRandomValues(state);
    const stateB64 = ChromeUtils.base64URLEncode(state, { pad: false });
    queryParams.state = stateB64;

    // Generate a 43 byte code verifier for PKCE, in accordance with
    // https://datatracker.ietf.org/doc/html/rfc7636#section-7.1 which recommends a
    // 43-octet URL safe string
    // The byte array is 32 bytes
    const codeVerifier = new Uint8Array(32);
    crypto.getRandomValues(codeVerifier);
    // When base64 encoded, it is 43 bytes
    const codeVerifierB64 = ChromeUtils.base64URLEncode(codeVerifier, {
      pad: false,
    });
    const challenge = await crypto.subtle.digest(
      "SHA-256",
      new TextEncoder().encode(codeVerifierB64)
    );
    const challengeB64 = ChromeUtils.base64URLEncode(challenge, { pad: false });
    queryParams.code_challenge = challengeB64;
    queryParams.code_challenge_method = "S256";

    // Generate a public, private key pair to be used during the oauth flow
    // to encrypt scoped-keys as they roundtrip through the auth server
    const ECDH_KEY = { name: "ECDH", namedCurve: "P-256" };
    const key = await crypto.subtle.generateKey(ECDH_KEY, false, ["deriveKey"]);
    const publicKey = await crypto.subtle.exportKey("jwk", key.publicKey);
    const privateKey = key.privateKey;

    // We encode the public key as URL-safe base64 to be included in the query parameters
    const encodedPublicKey = ChromeUtils.base64URLEncode(
      new TextEncoder().encode(JSON.stringify(publicKey)),
      { pad: false }
    );
    queryParams.keys_jwk = encodedPublicKey;

    // We store the state in-memory, to verify once the oauth flow is completed
    this.addFlow(stateB64, {
      key: privateKey,
      verifier: codeVerifierB64,
      requestedScopes: scopes.join(" "),
    });
    return queryParams;
  }

  /** Completes an OAuth flow and invalidates any other ongoing flows
   * @param { string } sessionTokenHex: The session token encoded in hexadecimal
   * @param { string } code: OAuth authorization code provided by running an OAuth flow
   * @param { string } state: The state first provided by `beginOAuthFlow`, then roundtripped through the server
   *
   * @returns { Object }: Returns an object representing the result of completing the oauth flow.
   *   The object includes the following:
   *     - 'scopedKeys': The encryption keys provided by the server, already decrypted
   *     - 'refreshToken': The refresh token provided by the server
   *     - 'accessToken': The access token provided by the server
   * */
  async completeOAuthFlow(sessionTokenHex, code, state) {
    const flow = this.getFlow(state);
    if (!flow) {
      throw new Error(ERROR_INVALID_STATE);
    }
    const { key, verifier, requestedScopes } = flow;
    const { keys_jwe, refresh_token, access_token, scope } =
      await this.#fxaClient.oauthToken(
        sessionTokenHex,
        code,
        verifier,
        OAUTH_CLIENT_ID
      );
    if (
      requestedScopes.includes(SCOPE_APP_SYNC) &&
      !scope.includes(SCOPE_APP_SYNC)
    ) {
      throw new Error(ERROR_SYNC_SCOPE_NOT_GRANTED);
    }
    if (scope.includes(SCOPE_APP_SYNC) && !keys_jwe) {
      throw new Error(ERROR_NO_KEYS_JWE);
    }
    let scopedKeys;
    if (keys_jwe) {
      scopedKeys = JSON.parse(
        new TextDecoder().decode(await lazy.jwcrypto.decryptJWE(keys_jwe, key))
      );
      if (!this.#fxaKeys.validScopedKeys(scopedKeys)) {
        throw new Error(ERROR_INVALID_SCOPED_KEYS);
      }
    }

    // We make sure no other flow snuck in, and completed before we did
    if (!this.getFlow(state)) {
      throw new Error(ERROR_OAUTH_FLOW_ABANDONED);
    }

    // Clear all flows, so any in-flight or future flows trigger an error as the browser
    // would have been signed in
    this.clearAllFlows();
    return {
      scopedKeys,
      refreshToken: refresh_token,
      accessToken: access_token,
    };
  }
}
PK
!<Г|�<<!modules/FxAccountsPairing.sys.mjs// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

import {
  log,
  PREF_REMOTE_PAIRING_URI,
  COMMAND_PAIR_SUPP_METADATA,
  COMMAND_PAIR_AUTHORIZE,
  COMMAND_PAIR_DECLINE,
  COMMAND_PAIR_HEARTBEAT,
  COMMAND_PAIR_COMPLETE,
} from "resource://gre/modules/FxAccountsCommon.sys.mjs";

import {
  getFxAccountsSingleton,
  FxAccounts,
} from "resource://gre/modules/FxAccounts.sys.mjs";

const fxAccounts = getFxAccountsSingleton();
import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";

ChromeUtils.importESModule("resource://services-common/utils.sys.mjs");
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  FxAccountsPairingChannel:
    "resource://gre/modules/FxAccountsPairingChannel.sys.mjs",

  Weave: "resource://services-sync/main.sys.mjs",
  jwcrypto: "resource://services-crypto/jwcrypto.sys.mjs",
});

const PAIRING_REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob:pair-auth-webchannel";
// A pairing flow is not tied to a specific browser window, can also finish in
// various ways and subsequently might leak a Web Socket, so just in case we
// time out and free-up the resources after a specified amount of time.
const FLOW_TIMEOUT_MS = 15 * 60 * 1000; // 15 minutes.

class PairingStateMachine {
  constructor(emitter) {
    this._emitter = emitter;
    this._transition(SuppConnectionPending);
  }

  get currentState() {
    return this._currentState;
  }

  _transition(StateCtor, ...args) {
    const state = new StateCtor(this, ...args);
    this._currentState = state;
  }

  assertState(RequiredStates, messagePrefix = null) {
    if (!(RequiredStates instanceof Array)) {
      RequiredStates = [RequiredStates];
    }
    if (
      !RequiredStates.some(
        RequiredState => this._currentState instanceof RequiredState
      )
    ) {
      const msg = `${
        messagePrefix ? `${messagePrefix}. ` : ""
      }Valid expected states: ${RequiredStates.map(({ name }) => name).join(
        ", "
      )}. Current state: ${this._currentState.label}.`;
      throw new Error(msg);
    }
  }
}

/**
 * The pairing flow can be modeled by a finite state machine:
 * We start by connecting to a WebSocket channel (SuppConnectionPending).
 * Then the other party connects and requests some metadata from us (PendingConfirmations).
 * A confirmation happens locally first (PendingRemoteConfirmation)
 * or the oppposite (PendingLocalConfirmation).
 * Any side can decline this confirmation (Aborted).
 * Once both sides have confirmed, the pairing flow is finished (Completed).
 * During this flow errors can happen and should be handled (Errored).
 */
class State {
  constructor(stateMachine, ...args) {
    this._transition = (...args) => stateMachine._transition(...args);
    this._notify = (...args) => stateMachine._emitter.emit(...args);
    this.init(...args);
  }

  init() {
    /* Does nothing by default but can be re-implemented. */
  }

  get label() {
    return this.constructor.name;
  }

  hasErrored(error) {
    this._notify("view:Error", error);
    this._transition(Errored, error);
  }

  hasAborted() {
    this._transition(Aborted);
  }
}
class SuppConnectionPending extends State {
  suppConnected(sender, oauthOptions) {
    this._transition(PendingConfirmations, sender, oauthOptions);
  }
}
class PendingConfirmationsState extends State {
  localConfirmed() {
    throw new Error("Subclasses must implement this method.");
  }
  remoteConfirmed() {
    throw new Error("Subclasses must implement this method.");
  }
}
class PendingConfirmations extends PendingConfirmationsState {
  init(sender, oauthOptions) {
    this.sender = sender;
    this.oauthOptions = oauthOptions;
  }

  localConfirmed() {
    this._transition(PendingRemoteConfirmation);
  }

  remoteConfirmed() {
    this._transition(PendingLocalConfirmation, this.sender, this.oauthOptions);
  }
}
class PendingLocalConfirmation extends PendingConfirmationsState {
  init(sender, oauthOptions) {
    this.sender = sender;
    this.oauthOptions = oauthOptions;
  }

  localConfirmed() {
    this._transition(Completed);
  }

  remoteConfirmed() {
    throw new Error(
      "Insane state! Remote has already been confirmed at this point."
    );
  }
}
class PendingRemoteConfirmation extends PendingConfirmationsState {
  localConfirmed() {
    throw new Error(
      "Insane state! Local has already been confirmed at this point."
    );
  }

  remoteConfirmed() {
    this._transition(Completed);
  }
}
class Completed extends State {}
class Aborted extends State {}
class Errored extends State {
  init(error) {
    this.error = error;
  }
}

const flows = new Map();

export class FxAccountsPairingFlow {
  static get(channelId) {
    return flows.get(channelId);
  }

  static finalizeAll() {
    for (const flow of flows) {
      flow.finalize();
    }
  }

  static async start(options) {
    const { emitter } = options;
    const fxaConfig = options.fxaConfig || FxAccounts.config;
    const fxa = options.fxAccounts || fxAccounts;
    const weave = options.weave || lazy.Weave;
    const flowTimeout = options.flowTimeout || FLOW_TIMEOUT_MS;

    const contentPairingURI = await fxaConfig.promisePairingURI();
    const wsUri = Services.urlFormatter.formatURLPref(PREF_REMOTE_PAIRING_URI);
    const pairingChannel =
      options.pairingChannel ||
      (await lazy.FxAccountsPairingChannel.create(wsUri));
    const { channelId, channelKey } = pairingChannel;
    const channelKeyB64 = ChromeUtils.base64URLEncode(channelKey, {
      pad: false,
    });
    const pairingFlow = new FxAccountsPairingFlow({
      channelId,
      pairingChannel,
      emitter,
      fxa,
      fxaConfig,
      flowTimeout,
      weave,
    });
    flows.set(channelId, pairingFlow);

    return `${contentPairingURI}#channel_id=${channelId}&channel_key=${channelKeyB64}`;
  }

  constructor(options) {
    this._channelId = options.channelId;
    this._pairingChannel = options.pairingChannel;
    this._emitter = options.emitter;
    this._fxa = options.fxa;
    this._fxai = options.fxai || this._fxa._internal;
    this._fxaConfig = options.fxaConfig;
    this._weave = options.weave;
    this._stateMachine = new PairingStateMachine(this._emitter);
    this._setupListeners();
    this._flowTimeoutId = setTimeout(
      () => this._onFlowTimeout(),
      options.flowTimeout
    );
  }

  _onFlowTimeout() {
    log.warn(`The pairing flow ${this._channelId} timed out.`);
    this._onError(new Error("Timeout"));
    this.finalize();
  }

  _closeChannel() {
    if (!this._closed && !this._pairingChannel.closed) {
      this._pairingChannel.close();
      this._closed = true;
    }
  }

  finalize() {
    this._closeChannel();
    clearTimeout(this._flowTimeoutId);
    // Free up resources and let the GC do its thing.
    flows.delete(this._channelId);
  }

  _setupListeners() {
    this._pairingChannel.addEventListener(
      "message",
      ({ detail: { sender, data } }) =>
        this.onPairingChannelMessage(sender, data)
    );
    this._pairingChannel.addEventListener("error", event =>
      this._onPairingChannelError(event.detail.error)
    );
    this._emitter.on("view:Closed", () => this.onPrefViewClosed());
  }

  _onAbort() {
    this._stateMachine.currentState.hasAborted();
    this.finalize();
  }

  _onError(error) {
    this._stateMachine.currentState.hasErrored(error);
    this._closeChannel();
  }

  _onPairingChannelError(error) {
    log.error("Pairing channel error", error);
    this._onError(error);
  }

  // Any non-falsy returned value is sent back through WebChannel.
  async onWebChannelMessage(command) {
    const stateMachine = this._stateMachine;
    const curState = stateMachine.currentState;
    try {
      switch (command) {
        case COMMAND_PAIR_SUPP_METADATA:
          stateMachine.assertState(
            [PendingConfirmations, PendingLocalConfirmation],
            `Wrong state for ${command}`
          );
          const {
            ua,
            city,
            region,
            country,
            remote: ipAddress,
          } = curState.sender;
          return { ua, city, region, country, ipAddress };
        case COMMAND_PAIR_AUTHORIZE:
          stateMachine.assertState(
            [PendingConfirmations, PendingLocalConfirmation],
            `Wrong state for ${command}`
          );
          const {
            client_id,
            state,
            scope,
            code_challenge,
            code_challenge_method,
            keys_jwk,
          } = curState.oauthOptions;
          const authorizeParams = {
            client_id,
            access_type: "offline",
            state,
            scope,
            code_challenge,
            code_challenge_method,
            keys_jwk,
          };
          const codeAndState = await this._authorizeOAuthCode(authorizeParams);
          if (codeAndState.state != state) {
            throw new Error(`OAuth state mismatch`);
          }
          await this._pairingChannel.send({
            message: "pair:auth:authorize",
            data: {
              ...codeAndState,
            },
          });
          curState.localConfirmed();
          break;
        case COMMAND_PAIR_DECLINE:
          this._onAbort();
          break;
        case COMMAND_PAIR_HEARTBEAT:
          if (curState instanceof Errored || this._pairingChannel.closed) {
            return { err: curState.error.message || "Pairing channel closed" };
          }
          const suppAuthorized = !(
            curState instanceof PendingConfirmations ||
            curState instanceof PendingRemoteConfirmation
          );
          return { suppAuthorized };
        case COMMAND_PAIR_COMPLETE:
          this.finalize();
          break;
        default:
          throw new Error(`Received unknown WebChannel command: ${command}`);
      }
    } catch (e) {
      log.error(e);
      curState.hasErrored(e);
    }
    return {};
  }

  async onPairingChannelMessage(sender, payload) {
    const { message } = payload;
    const stateMachine = this._stateMachine;
    const curState = stateMachine.currentState;
    try {
      switch (message) {
        case "pair:supp:request":
          stateMachine.assertState(
            SuppConnectionPending,
            `Wrong state for ${message}`
          );
          const oauthUri = await this._fxaConfig.promiseOAuthURI();
          const { uid, email, avatar, displayName } =
            await this._fxa.getSignedInUser();
          const deviceName = this._weave.Service.clientsEngine.localName;
          await this._pairingChannel.send({
            message: "pair:auth:metadata",
            data: {
              email,
              avatar,
              displayName,
              deviceName,
            },
          });
          const {
            client_id,
            state,
            scope,
            code_challenge,
            code_challenge_method,
            keys_jwk,
          } = payload.data;
          const url = new URL(oauthUri);
          url.searchParams.append("client_id", client_id);
          url.searchParams.append("scope", scope);
          url.searchParams.append("email", email);
          url.searchParams.append("uid", uid);
          url.searchParams.append("channel_id", this._channelId);
          url.searchParams.append("redirect_uri", PAIRING_REDIRECT_URI);
          this._emitter.emit("view:SwitchToWebContent", url.href);
          curState.suppConnected(sender, {
            client_id,
            state,
            scope,
            code_challenge,
            code_challenge_method,
            keys_jwk,
          });
          break;
        case "pair:supp:authorize":
          stateMachine.assertState(
            [PendingConfirmations, PendingRemoteConfirmation],
            `Wrong state for ${message}`
          );
          curState.remoteConfirmed();
          break;
        default:
          throw new Error(
            `Received unknown Pairing Channel message: ${message}`
          );
      }
    } catch (e) {
      log.error(e);
      curState.hasErrored(e);
    }
  }

  onPrefViewClosed() {
    const curState = this._stateMachine.currentState;
    // We don't want to stop the pairing process in the later stages.
    if (
      curState instanceof SuppConnectionPending ||
      curState instanceof Aborted ||
      curState instanceof Errored
    ) {
      this.finalize();
    }
  }

  /**
   * Grant an OAuth authorization code for the connecting client.
   *
   * @param {Object} options
   * @param options.client_id
   * @param options.state
   * @param options.scope
   * @param options.access_type
   * @param options.code_challenge_method
   * @param options.code_challenge
   * @param [options.keys_jwe]
   * @returns {Promise<Object>} Object containing "code" and "state" properties.
   */
  _authorizeOAuthCode(options) {
    return this._fxa._withVerifiedAccountState(async state => {
      const { sessionToken } = await state.getUserAccountData(["sessionToken"]);
      const params = { ...options };
      if (params.keys_jwk) {
        const jwk = JSON.parse(
          new TextDecoder().decode(
            ChromeUtils.base64URLDecode(params.keys_jwk, { padding: "reject" })
          )
        );
        params.keys_jwe = await this._createKeysJWE(
          sessionToken,
          params.client_id,
          params.scope,
          jwk
        );
        delete params.keys_jwk;
      }
      try {
        return await this._fxai.fxAccountsClient.oauthAuthorize(
          sessionToken,
          params
        );
      } catch (err) {
        throw this._fxai._errorToErrorClass(err);
      }
    });
  }

  /**
   * Create a JWE to deliver keys to another client via the OAuth scoped-keys flow.
   *
   * This method is used to transfer key material to another client, by providing
   * an appropriately-encrypted value for the `keys_jwe` OAuth response parameter.
   * Since we're transferring keys from one client to another, two things must be
   * true:
   *
   *   * This client must actually have the key.
   *   * The other client must be allowed to request that key.
   *
   * @param {String} sessionToken the sessionToken to use when fetching key metadata
   * @param {String} clientId the client requesting access to our keys
   * @param {String} scopes Space separated requested scopes being requested
   * @param {Object} jwk Ephemeral JWK provided by the client for secure key transfer
   */
  async _createKeysJWE(sessionToken, clientId, scopes, jwk) {
    // This checks with the FxA server about what scopes the client is allowed.
    // Note that we pass the requesting client_id here, not our own client_id.
    const clientKeyData = await this._fxai.fxAccountsClient.getScopedKeyData(
      sessionToken,
      clientId,
      scopes
    );
    const scopedKeys = {};
    for (const scope of Object.keys(clientKeyData)) {
      const key = await this._fxai.keys.getKeyForScope(scope);
      if (!key) {
        throw new Error(`Key not available for scope "${scope}"`);
      }
      scopedKeys[scope] = key;
    }
    return lazy.jwcrypto.generateJWE(
      jwk,
      new TextEncoder().encode(JSON.stringify(scopedKeys))
    );
  }
}
PK
!<�W�{U�U�(modules/FxAccountsPairingChannel.sys.mjs/*!
 * 
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * 
 * The following bundle is from an external repository at github.com/mozilla/fxa-pairing-channel,
 * it implements a shared library for two javascript environments to create an encrypted and authenticated
 * communication channel by sharing a secret key and by relaying messages through a websocket server.
 * 
 * It is used by the Firefox Accounts pairing flow, with one side of the channel being web
 * content from https://accounts.firefox.com and the other side of the channel being chrome native code.
 * 
 * This uses the event-target-shim node library published under the MIT license:
 * https://github.com/mysticatea/event-target-shim/blob/master/LICENSE
 * 
 * Bundle generated from https://github.com/mozilla/fxa-pairing-channel.git. Hash:c8ec3119920b4ffa833b, Chunkhash:378a5f51445e7aa7630e.
 * 
 */

// This header provides a little bit of plumbing to use `FxAccountsPairingChannel`
// from Firefox browser code, hence the presence of these privileged browser APIs.
// If you're trying to use this from ordinary web content you're in for a bad time.

import { setTimeout } from "resource://gre/modules/Timer.sys.mjs";

// We cannot use WebSocket from chrome code without a window,
// see https://bugzilla.mozilla.org/show_bug.cgi?id=784686
const browser = Services.appShell.createWindowlessBrowser(true);
const {WebSocket} = browser.document.ownerGlobal;

export var FxAccountsPairingChannel =
/******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ 		}
/******/ 	};
/******/
/******/ 	// define __esModule on exports
/******/ 	__webpack_require__.r = function(exports) {
/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 		}
/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
/******/ 	};
/******/
/******/ 	// create a fake namespace object
/******/ 	// mode & 1: value is a module id, require it
/******/ 	// mode & 2: merge all properties of value into the ns
/******/ 	// mode & 4: return value when already ns object
/******/ 	// mode & 8|1: behave like require
/******/ 	__webpack_require__.t = function(value, mode) {
/******/ 		if(mode & 1) value = __webpack_require__(value);
/******/ 		if(mode & 8) return value;
/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ 		var ns = Object.create(null);
/******/ 		__webpack_require__.r(ns);
/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ 		return ns;
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
// ESM COMPAT FLAG
__webpack_require__.r(__webpack_exports__);

// EXPORTS
__webpack_require__.d(__webpack_exports__, "PairingChannel", function() { return /* binding */ src_PairingChannel; });
__webpack_require__.d(__webpack_exports__, "base64urlToBytes", function() { return /* reexport */ base64urlToBytes; });
__webpack_require__.d(__webpack_exports__, "bytesToBase64url", function() { return /* reexport */ bytesToBase64url; });
__webpack_require__.d(__webpack_exports__, "bytesToHex", function() { return /* reexport */ bytesToHex; });
__webpack_require__.d(__webpack_exports__, "bytesToUtf8", function() { return /* reexport */ bytesToUtf8; });
__webpack_require__.d(__webpack_exports__, "hexToBytes", function() { return /* reexport */ hexToBytes; });
__webpack_require__.d(__webpack_exports__, "TLSCloseNotify", function() { return /* reexport */ TLSCloseNotify; });
__webpack_require__.d(__webpack_exports__, "TLSError", function() { return /* reexport */ TLSError; });
__webpack_require__.d(__webpack_exports__, "utf8ToBytes", function() { return /* reexport */ utf8ToBytes; });
__webpack_require__.d(__webpack_exports__, "_internals", function() { return /* binding */ _internals; });

// CONCATENATED MODULE: ./src/alerts.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-disable sorting/sort-object-props */
const ALERT_LEVEL = {
  WARNING: 1,
  FATAL: 2
};

const ALERT_DESCRIPTION = {
  CLOSE_NOTIFY: 0,
  UNEXPECTED_MESSAGE: 10,
  BAD_RECORD_MAC: 20,
  RECORD_OVERFLOW: 22,
  HANDSHAKE_FAILURE: 40,
  ILLEGAL_PARAMETER: 47,
  DECODE_ERROR: 50,
  DECRYPT_ERROR: 51,
  PROTOCOL_VERSION: 70,
  INTERNAL_ERROR: 80,
  MISSING_EXTENSION: 109,
  UNSUPPORTED_EXTENSION: 110,
  UNKNOWN_PSK_IDENTITY: 115,
  NO_APPLICATION_PROTOCOL: 120,
};
/* eslint-enable sorting/sort-object-props */

function alertTypeToName(type) {
  for (const name in ALERT_DESCRIPTION) {
    if (ALERT_DESCRIPTION[name] === type) {
      return `${name} (${type})`;
    }
  }
  return `UNKNOWN (${type})`;
}

class TLSAlert extends Error {
  constructor(description, level) {
    super(`TLS Alert: ${alertTypeToName(description)}`);
    this.description = description;
    this.level = level;
  }

  static fromBytes(bytes) {
    if (bytes.byteLength !== 2) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    switch (bytes[1]) {
      case ALERT_DESCRIPTION.CLOSE_NOTIFY:
        if (bytes[0] !== ALERT_LEVEL.WARNING) {
          // Close notifications should be fatal.
          throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
        }
        return new TLSCloseNotify();
      default:
        return new TLSError(bytes[1]);
    }
  }

  toBytes() {
    return new Uint8Array([this.level, this.description]);
  }
}

class TLSCloseNotify extends TLSAlert {
  constructor() {
    super(ALERT_DESCRIPTION.CLOSE_NOTIFY, ALERT_LEVEL.WARNING);
  }
}

class TLSError extends TLSAlert {
  constructor(description = ALERT_DESCRIPTION.INTERNAL_ERROR) {
    super(description, ALERT_LEVEL.FATAL);
  }
}

// CONCATENATED MODULE: ./src/utils.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */



//
// Various low-level utility functions.
//
// These are mostly conveniences for working with Uint8Arrays as
// the primitive "bytes" type.
//

const UTF8_ENCODER = new TextEncoder();
const UTF8_DECODER = new TextDecoder();

function noop() {}

function assert(cond, msg) {
  if (! cond) {
    throw new Error('assert failed: ' + msg);
  }
}

function assertIsBytes(value, msg = 'value must be a Uint8Array') {
  // Using `value instanceof Uint8Array` seems to fail in Firefox chrome code
  // for inscrutable reasons, so we do a less direct check.
  assert(ArrayBuffer.isView(value), msg);
  assert(value.BYTES_PER_ELEMENT === 1, msg);
  return value;
}

const EMPTY = new Uint8Array(0);

function zeros(n) {
  return new Uint8Array(n);
}

function arrayToBytes(value) {
  return new Uint8Array(value);
}

function bytesToHex(bytes) {
  return Array.prototype.map.call(bytes, byte => {
    let s = byte.toString(16);
    if (s.length === 1) {
      s = '0' + s;
    }
    return s;
  }).join('');
}

function hexToBytes(hexstr) {
  assert(hexstr.length % 2 === 0, 'hexstr.length must be even');
  return new Uint8Array(Array.prototype.map.call(hexstr, (c, n) => {
    if (n % 2 === 1) {
      return hexstr[n - 1] + c;
    } else {
      return '';
    }
  }).filter(s => {
    return !! s;
  }).map(s => {
    return parseInt(s, 16);
  }));
}

function bytesToUtf8(bytes) {
  return UTF8_DECODER.decode(bytes);
}

function utf8ToBytes(str) {
  return UTF8_ENCODER.encode(str);
}

function bytesToBase64url(bytes) {
  // XXX TODO: try to use something constant-time, in case calling code
  // uses it to encode secrets?
  const charCodes = String.fromCharCode.apply(String, bytes);
  return btoa(charCodes).replace(/\+/g, '-').replace(/\//g, '_');
}

function base64urlToBytes(str) {
  // XXX TODO: try to use something constant-time, in case calling code
  // uses it to decode secrets?
  str = atob(str.replace(/-/g, '+').replace(/_/g, '/'));
  const bytes = new Uint8Array(str.length);
  for (let i = 0; i < str.length; i++) {
    bytes[i] = str.charCodeAt(i);
  }
  return bytes;
}

function bytesAreEqual(v1, v2) {
  assertIsBytes(v1);
  assertIsBytes(v2);
  if (v1.length !== v2.length) {
    return false;
  }
  for (let i = 0; i < v1.length; i++) {
    if (v1[i] !== v2[i]) {
      return false;
    }
  }
  return true;
}

// The `BufferReader` and `BufferWriter` classes are helpers for dealing with the
// binary struct format that's used for various TLS message.  Think of them as a
// buffer with a pointer to the "current position" and a bunch of helper methods
// to read/write structured data and advance said pointer.

class utils_BufferWithPointer {
  constructor(buf) {
    this._buffer = buf;
    this._dataview = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
    this._pos = 0;
  }

  length() {
    return this._buffer.byteLength;
  }

  tell() {
    return this._pos;
  }

  seek(pos) {
    if (pos < 0) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    if (pos > this.length()) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    this._pos = pos;
  }

  incr(offset) {
    this.seek(this._pos + offset);
  }
}

// The `BufferReader` class helps you read structured data from a byte array.
// It offers methods for reading both primitive values, and the variable-length
// vector structures defined in https://tools.ietf.org/html/rfc8446#section-3.4.
//
// Such vectors are represented as a length followed by the concatenated
// bytes of each item, and the size of the length field is determined by
// the maximum allowed number of bytes in the vector.  For example
// to read a vector that may contain up to 65535 bytes, use `readVector16`.
//
// To read a variable-length vector of between 1 and 100 uint16 values,
// defined in the RFC like this:
//
//    uint16 items<2..200>;
//
// You would do something like this:
//
//    const items = []
//    buf.readVector8(buf => {
//      items.push(buf.readUint16())
//    })
//
// The various `read` will throw `DECODE_ERROR` if you attempt to read path
// the end of the buffer, or past the end of a variable-length list.
//
class utils_BufferReader extends utils_BufferWithPointer {

  hasMoreBytes() {
    return this.tell() < this.length();
  }

  readBytes(length) {
    // This avoids copies by returning a view onto the existing buffer.
    const start = this._buffer.byteOffset + this.tell();
    this.incr(length);
    return new Uint8Array(this._buffer.buffer, start, length);
  }

  _rangeErrorToAlert(cb) {
    try {
      return cb(this);
    } catch (err) {
      if (err instanceof RangeError) {
        throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
      }
      throw err;
    }
  }

  readUint8() {
    return this._rangeErrorToAlert(() => {
      const n = this._dataview.getUint8(this._pos);
      this.incr(1);
      return n;
    });
  }

  readUint16() {
    return this._rangeErrorToAlert(() => {
      const n = this._dataview.getUint16(this._pos);
      this.incr(2);
      return n;
    });
  }

  readUint24() {
    return this._rangeErrorToAlert(() => {
      let n = this._dataview.getUint16(this._pos);
      n = (n << 8) | this._dataview.getUint8(this._pos + 2);
      this.incr(3);
      return n;
    });
  }

  readUint32() {
    return this._rangeErrorToAlert(() => {
      const n = this._dataview.getUint32(this._pos);
      this.incr(4);
      return n;
    });
  }

  _readVector(length, cb) {
    const contentsBuf = new utils_BufferReader(this.readBytes(length));
    const expectedEnd = this.tell();
    // Keep calling the callback until we've consumed the expected number of bytes.
    let n = 0;
    while (contentsBuf.hasMoreBytes()) {
      const prevPos = contentsBuf.tell();
      cb(contentsBuf, n);
      // Check that the callback made forward progress, otherwise we'll infinite loop.
      if (contentsBuf.tell() <= prevPos) {
        throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
      }
      n += 1;
    }
    // Check that the callback correctly consumed the vector's entire contents.
    if (this.tell() !== expectedEnd) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
  }

  readVector8(cb) {
    const length = this.readUint8();
    return this._readVector(length, cb);
  }

  readVector16(cb) {
    const length = this.readUint16();
    return this._readVector(length, cb);
  }

  readVector24(cb) {
    const length = this.readUint24();
    return this._readVector(length, cb);
  }

  readVectorBytes8() {
    return this.readBytes(this.readUint8());
  }

  readVectorBytes16() {
    return this.readBytes(this.readUint16());
  }

  readVectorBytes24() {
    return this.readBytes(this.readUint24());
  }
}


class utils_BufferWriter extends utils_BufferWithPointer {
  constructor(size = 1024) {
    super(new Uint8Array(size));
  }

  _maybeGrow(n) {
    const curSize = this._buffer.byteLength;
    const newPos = this._pos + n;
    const shortfall = newPos - curSize;
    if (shortfall > 0) {
      // Classic grow-by-doubling, up to 4kB max increment.
      // This formula was not arrived at by any particular science.
      const incr = Math.min(curSize, 4 * 1024);
      const newbuf = new Uint8Array(curSize + Math.ceil(shortfall / incr) * incr);
      newbuf.set(this._buffer, 0);
      this._buffer = newbuf;
      this._dataview = new DataView(newbuf.buffer, newbuf.byteOffset, newbuf.byteLength);
    }
  }

  slice(start = 0, end = this.tell()) {
    if (end < 0) {
      end = this.tell() + end;
    }
    if (start < 0) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    if (end < 0) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    if (end > this.length()) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    return this._buffer.slice(start, end);
  }

  flush() {
    const slice = this.slice();
    this.seek(0);
    return slice;
  }

  writeBytes(data) {
    this._maybeGrow(data.byteLength);
    this._buffer.set(data, this.tell());
    this.incr(data.byteLength);
  }

  writeUint8(n) {
    this._maybeGrow(1);
    this._dataview.setUint8(this._pos, n);
    this.incr(1);
  }

  writeUint16(n) {
    this._maybeGrow(2);
    this._dataview.setUint16(this._pos, n);
    this.incr(2);
  }

  writeUint24(n) {
    this._maybeGrow(3);
    this._dataview.setUint16(this._pos, n >> 8);
    this._dataview.setUint8(this._pos + 2, n & 0xFF);
    this.incr(3);
  }

  writeUint32(n) {
    this._maybeGrow(4);
    this._dataview.setUint32(this._pos, n);
    this.incr(4);
  }

  // These are helpers for writing the variable-length vector structure
  // defined in https://tools.ietf.org/html/rfc8446#section-3.4.
  //
  // Such vectors are represented as a length followed by the concatenated
  // bytes of each item, and the size of the length field is determined by
  // the maximum allowed size of the vector.  For example to write a vector
  // that may contain up to 65535 bytes, use `writeVector16`.
  //
  // To write a variable-length vector of between 1 and 100 uint16 values,
  // defined in the RFC like this:
  //
  //    uint16 items<2..200>;
  //
  // You would do something like this:
  //
  //    buf.writeVector8(buf => {
  //      for (let item of items) {
  //          buf.writeUint16(item)
  //      }
  //    })
  //
  // The helper will automatically take care of writing the appropriate
  // length field once the callback completes.

  _writeVector(maxLength, writeLength, cb) {
    // Initially, write the length field as zero.
    const lengthPos = this.tell();
    writeLength(0);
    // Call the callback to write the vector items.
    const bodyPos = this.tell();
    cb(this);
    const length = this.tell() - bodyPos;
    if (length >= maxLength) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    // Backfill the actual length field.
    this.seek(lengthPos);
    writeLength(length);
    this.incr(length);
    return length;
  }

  writeVector8(cb) {
    return this._writeVector(Math.pow(2, 8), len => this.writeUint8(len), cb);
  }

  writeVector16(cb) {
    return this._writeVector(Math.pow(2, 16), len => this.writeUint16(len), cb);
  }

  writeVector24(cb) {
    return this._writeVector(Math.pow(2, 24), len => this.writeUint24(len), cb);
  }

  writeVectorBytes8(bytes) {
    return this.writeVector8(buf => {
      buf.writeBytes(bytes);
    });
  }

  writeVectorBytes16(bytes) {
    return this.writeVector16(buf => {
      buf.writeBytes(bytes);
    });
  }

  writeVectorBytes24(bytes) {
    return this.writeVector24(buf => {
      buf.writeBytes(bytes);
    });
  }
}

// CONCATENATED MODULE: ./src/crypto.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//
// Low-level crypto primitives.
//
// This file implements the AEAD encrypt/decrypt and hashing routines
// for the TLS_AES_128_GCM_SHA256 ciphersuite. They are (thankfully)
// fairly light-weight wrappers around what's available via the WebCrypto
// API.
//




const AEAD_SIZE_INFLATION = 16;
const KEY_LENGTH = 16;
const IV_LENGTH = 12;
const HASH_LENGTH = 32;

async function prepareKey(key, mode) {
  return crypto.subtle.importKey('raw', key, { name: 'AES-GCM' }, false, [mode]);
}

async function encrypt(key, iv, plaintext, additionalData) {
  const ciphertext = await crypto.subtle.encrypt({
    additionalData,
    iv,
    name: 'AES-GCM',
    tagLength: AEAD_SIZE_INFLATION * 8
  }, key, plaintext);
  return new Uint8Array(ciphertext);
}

async function decrypt(key, iv, ciphertext, additionalData) {
  try {
    const plaintext = await crypto.subtle.decrypt({
      additionalData,
      iv,
      name: 'AES-GCM',
      tagLength: AEAD_SIZE_INFLATION * 8
    }, key, ciphertext);
    return new Uint8Array(plaintext);
  } catch (err) {
    // Yes, we really do throw 'decrypt_error' when failing to verify a HMAC,
    // and a 'bad_record_mac' error when failing to decrypt.
    throw new TLSError(ALERT_DESCRIPTION.BAD_RECORD_MAC);
  }
}

async function hash(message) {
  return new Uint8Array(await crypto.subtle.digest({ name: 'SHA-256' }, message));
}

async function hmac(keyBytes, message) {
  const key = await crypto.subtle.importKey('raw', keyBytes, {
    hash: { name: 'SHA-256' },
    name: 'HMAC',
  }, false, ['sign']);
  const sig = await crypto.subtle.sign({ name: 'HMAC' }, key, message);
  return new Uint8Array(sig);
}

async function verifyHmac(keyBytes, signature, message) {
  const key = await crypto.subtle.importKey('raw', keyBytes, {
    hash: { name: 'SHA-256' },
    name: 'HMAC',
  }, false, ['verify']);
  if (! (await crypto.subtle.verify({ name: 'HMAC' }, key, signature, message))) {
    // Yes, we really do throw 'decrypt_error' when failing to verify a HMAC,
    // and a 'bad_record_mac' error when failing to decrypt.
    throw new TLSError(ALERT_DESCRIPTION.DECRYPT_ERROR);
  }
}

async function hkdfExtract(salt, ikm) {
  // Ref https://tools.ietf.org/html/rfc5869#section-2.2
  return await hmac(salt, ikm);
}

async function hkdfExpand(prk, info, length) {
  // Ref https://tools.ietf.org/html/rfc5869#section-2.3
  const N = Math.ceil(length / HASH_LENGTH);
  if (N <= 0) {
    throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
  }
  if (N >= 255) {
    throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
  }
  const input = new utils_BufferWriter();
  const output = new utils_BufferWriter();
  let T = new Uint8Array(0);
  for (let i = 1; i <= N; i++) {
    input.writeBytes(T);
    input.writeBytes(info);
    input.writeUint8(i);
    T = await hmac(prk, input.flush());
    output.writeBytes(T);
  }
  return output.slice(0, length);
}

async function hkdfExpandLabel(secret, label, context, length) {
  //  struct {
  //    uint16 length = Length;
  //    opaque label < 7..255 > = "tls13 " + Label;
  //    opaque context < 0..255 > = Context;
  //  } HkdfLabel;
  const hkdfLabel = new utils_BufferWriter();
  hkdfLabel.writeUint16(length);
  hkdfLabel.writeVectorBytes8(utf8ToBytes('tls13 ' + label));
  hkdfLabel.writeVectorBytes8(context);
  return hkdfExpand(secret, hkdfLabel.flush(), length);
}

async function getRandomBytes(size) {
  const bytes = new Uint8Array(size);
  crypto.getRandomValues(bytes);
  return bytes;
}

// CONCATENATED MODULE: ./src/extensions.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//
// Extension parsing.
//
// This file contains some helpers for reading/writing the various kinds
// of Extension that might appear in a HandshakeMessage.
//
// "Extensions" are how TLS signals the presence of particular bits of optional
// functionality in the protocol. Lots of parts of TLS1.3 that don't seem like
// they're optional are implemented in terms of an extension, IIUC because that's
// what was needed for a clean deployment in amongst earlier versions of the protocol.
//





/* eslint-disable sorting/sort-object-props */
const EXTENSION_TYPE = {
  PRE_SHARED_KEY: 41,
  SUPPORTED_VERSIONS: 43,
  PSK_KEY_EXCHANGE_MODES: 45,
};
/* eslint-enable sorting/sort-object-props */

// Base class for generic reading/writing of extensions,
// which are all uniformly formatted as:
//
//   struct {
//     ExtensionType extension_type;
//     opaque extension_data<0..2^16-1>;
//   } Extension;
//
// Extensions always appear inside of a handshake message,
// and their internal structure may differ based on the
// type of that message.

class extensions_Extension {

  get TYPE_TAG() {
    throw new Error('not implemented');
  }

  static read(messageType, buf) {
    const type = buf.readUint16();
    let ext = {
      TYPE_TAG: type,
    };
    buf.readVector16(buf => {
      switch (type) {
        case EXTENSION_TYPE.PRE_SHARED_KEY:
          ext = extensions_PreSharedKeyExtension._read(messageType, buf);
          break;
        case EXTENSION_TYPE.SUPPORTED_VERSIONS:
          ext = extensions_SupportedVersionsExtension._read(messageType, buf);
          break;
        case EXTENSION_TYPE.PSK_KEY_EXCHANGE_MODES:
          ext = extensions_PskKeyExchangeModesExtension._read(messageType, buf);
          break;
        default:
          // Skip over unrecognised extensions.
          buf.incr(buf.length());
      }
      if (buf.hasMoreBytes()) {
        throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
      }
    });
    return ext;
  }

  write(messageType, buf) {
    buf.writeUint16(this.TYPE_TAG);
    buf.writeVector16(buf => {
      this._write(messageType, buf);
    });
  }

  static _read(messageType, buf) {
    throw new Error('not implemented');
  }

  static _write(messageType, buf) {
    throw new Error('not implemented');
  }
}

// The PreSharedKey extension:
//
//  struct {
//    opaque identity<1..2^16-1>;
//    uint32 obfuscated_ticket_age;
//  } PskIdentity;
//  opaque PskBinderEntry<32..255>;
//  struct {
//    PskIdentity identities<7..2^16-1>;
//    PskBinderEntry binders<33..2^16-1>;
//  } OfferedPsks;
//  struct {
//    select(Handshake.msg_type) {
//      case client_hello: OfferedPsks;
//      case server_hello: uint16 selected_identity;
//    };
//  } PreSharedKeyExtension;

class extensions_PreSharedKeyExtension extends extensions_Extension {
  constructor(identities, binders, selectedIdentity) {
    super();
    this.identities = identities;
    this.binders = binders;
    this.selectedIdentity = selectedIdentity;
  }

  get TYPE_TAG() {
    return EXTENSION_TYPE.PRE_SHARED_KEY;
  }

  static _read(messageType, buf) {
    let identities = null, binders = null, selectedIdentity = null;
    switch (messageType) {
      case HANDSHAKE_TYPE.CLIENT_HELLO:
        identities = []; binders = [];
        buf.readVector16(buf => {
          const identity = buf.readVectorBytes16();
          buf.readBytes(4); // Skip over the ticket age.
          identities.push(identity);
        });
        buf.readVector16(buf => {
          const binder = buf.readVectorBytes8();
          if (binder.byteLength < HASH_LENGTH) {
            throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
          }
          binders.push(binder);
        });
        if (identities.length !== binders.length) {
          throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
        }
        break;
      case HANDSHAKE_TYPE.SERVER_HELLO:
        selectedIdentity = buf.readUint16();
        break;
      default:
        throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    return new this(identities, binders, selectedIdentity);
  }

  _write(messageType, buf) {
    switch (messageType) {
      case HANDSHAKE_TYPE.CLIENT_HELLO:
        buf.writeVector16(buf => {
          this.identities.forEach(pskId => {
            buf.writeVectorBytes16(pskId);
            buf.writeUint32(0); // Zero for "tag age" field.
          });
        });
        buf.writeVector16(buf => {
          this.binders.forEach(pskBinder => {
            buf.writeVectorBytes8(pskBinder);
          });
        });
        break;
      case HANDSHAKE_TYPE.SERVER_HELLO:
        buf.writeUint16(this.selectedIdentity);
        break;
      default:
        throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
  }
}


// The SupportedVersions extension:
//
//  struct {
//    select(Handshake.msg_type) {
//      case client_hello:
//        ProtocolVersion versions < 2..254 >;
//      case server_hello:
//        ProtocolVersion selected_version;
//    };
//  } SupportedVersions;

class extensions_SupportedVersionsExtension extends extensions_Extension {
  constructor(versions, selectedVersion) {
    super();
    this.versions = versions;
    this.selectedVersion = selectedVersion;
  }

  get TYPE_TAG() {
    return EXTENSION_TYPE.SUPPORTED_VERSIONS;
  }

  static _read(messageType, buf) {
    let versions = null, selectedVersion = null;
    switch (messageType) {
      case HANDSHAKE_TYPE.CLIENT_HELLO:
        versions = [];
        buf.readVector8(buf => {
          versions.push(buf.readUint16());
        });
        break;
      case HANDSHAKE_TYPE.SERVER_HELLO:
        selectedVersion = buf.readUint16();
        break;
      default:
        throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    return new this(versions, selectedVersion);
  }

  _write(messageType, buf) {
    switch (messageType) {
      case HANDSHAKE_TYPE.CLIENT_HELLO:
        buf.writeVector8(buf => {
          this.versions.forEach(version => {
            buf.writeUint16(version);
          });
        });
        break;
      case HANDSHAKE_TYPE.SERVER_HELLO:
        buf.writeUint16(this.selectedVersion);
        break;
      default:
        throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
  }
}


class extensions_PskKeyExchangeModesExtension extends extensions_Extension {
  constructor(modes) {
    super();
    this.modes = modes;
  }

  get TYPE_TAG() {
    return EXTENSION_TYPE.PSK_KEY_EXCHANGE_MODES;
  }

  static _read(messageType, buf) {
    const modes = [];
    switch (messageType) {
      case HANDSHAKE_TYPE.CLIENT_HELLO:
        buf.readVector8(buf => {
          modes.push(buf.readUint8());
        });
        break;
      default:
        throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    return new this(modes);
  }

  _write(messageType, buf) {
    switch (messageType) {
      case HANDSHAKE_TYPE.CLIENT_HELLO:
        buf.writeVector8(buf => {
          this.modes.forEach(mode => {
            buf.writeUint8(mode);
          });
        });
        break;
      default:
        throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
  }
}

// CONCATENATED MODULE: ./src/constants.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const VERSION_TLS_1_0 = 0x0301;
const VERSION_TLS_1_2 = 0x0303;
const VERSION_TLS_1_3 = 0x0304;
const TLS_AES_128_GCM_SHA256 = 0x1301;
const PSK_MODE_KE = 0;

// CONCATENATED MODULE: ./src/messages.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//
// Message parsing.
//
// Herein we have code for reading and writing the various Handshake
// messages involved in the TLS protocol.
//







/* eslint-disable sorting/sort-object-props */
const HANDSHAKE_TYPE = {
  CLIENT_HELLO: 1,
  SERVER_HELLO: 2,
  NEW_SESSION_TICKET: 4,
  ENCRYPTED_EXTENSIONS: 8,
  FINISHED: 20,
};
/* eslint-enable sorting/sort-object-props */

// Base class for generic reading/writing of handshake messages,
// which are all uniformly formatted as:
//
//  struct {
//    HandshakeType msg_type;    /* handshake type */
//    uint24 length;             /* bytes in message */
//    select(Handshake.msg_type) {
//        ... type specific cases here ...
//    };
//  } Handshake;

class messages_HandshakeMessage {

  get TYPE_TAG() {
    throw new Error('not implemented');
  }

  static fromBytes(bytes) {
    // Each handshake message has a type and length prefix, per
    // https://tools.ietf.org/html/rfc8446#appendix-B.3
    const buf = new utils_BufferReader(bytes);
    const msg = this.read(buf);
    if (buf.hasMoreBytes()) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    return msg;
  }

  toBytes() {
    const buf = new utils_BufferWriter();
    this.write(buf);
    return buf.flush();
  }

  static read(buf) {
    const type = buf.readUint8();
    let msg = null;
    buf.readVector24(buf => {
      switch (type) {
        case HANDSHAKE_TYPE.CLIENT_HELLO:
          msg = messages_ClientHello._read(buf);
          break;
        case HANDSHAKE_TYPE.SERVER_HELLO:
          msg = messages_ServerHello._read(buf);
          break;
        case HANDSHAKE_TYPE.NEW_SESSION_TICKET:
          msg = messages_NewSessionTicket._read(buf);
          break;
        case HANDSHAKE_TYPE.ENCRYPTED_EXTENSIONS:
          msg = EncryptedExtensions._read(buf);
          break;
        case HANDSHAKE_TYPE.FINISHED:
          msg = messages_Finished._read(buf);
          break;
      }
      if (buf.hasMoreBytes()) {
        throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
      }
    });
    if (msg === null) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    return msg;
  }

  write(buf) {
    buf.writeUint8(this.TYPE_TAG);
    buf.writeVector24(buf => {
      this._write(buf);
    });
  }

  static _read(buf) {
    throw new Error('not implemented');
  }

  _write(buf) {
    throw new Error('not implemented');
  }

  // Some little helpers for reading a list of extensions,
  // which is uniformly represented as:
  //
  //   Extension extensions<8..2^16-1>;
  //
  // Recognized extensions are returned as a Map from extension type
  // to extension data object, with a special `lastSeenExtension`
  // property to make it easy to check which one came last.

  static _readExtensions(messageType, buf) {
    const extensions = new Map();
    buf.readVector16(buf => {
      const ext = extensions_Extension.read(messageType, buf);
      if (extensions.has(ext.TYPE_TAG)) {
        throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
      }
      extensions.set(ext.TYPE_TAG, ext);
      extensions.lastSeenExtension = ext.TYPE_TAG;
    });
    return extensions;
  }

  _writeExtensions(buf, extensions) {
    buf.writeVector16(buf => {
      extensions.forEach(ext => {
        ext.write(this.TYPE_TAG, buf);
      });
    });
  }
}


// The ClientHello message:
//
// struct {
//   ProtocolVersion legacy_version = 0x0303;
//   Random random;
//   opaque legacy_session_id<0..32>;
//   CipherSuite cipher_suites<2..2^16-2>;
//   opaque legacy_compression_methods<1..2^8-1>;
//   Extension extensions<8..2^16-1>;
// } ClientHello;

class messages_ClientHello extends messages_HandshakeMessage {

  constructor(random, sessionId, extensions) {
    super();
    this.random = random;
    this.sessionId = sessionId;
    this.extensions = extensions;
  }

  get TYPE_TAG() {
    return HANDSHAKE_TYPE.CLIENT_HELLO;
  }

  static _read(buf) {
    // The legacy_version field may indicate an earlier version of TLS
    // for backwards compatibility, but must not predate TLS 1.0!
    if (buf.readUint16() < VERSION_TLS_1_0) {
      throw new TLSError(ALERT_DESCRIPTION.PROTOCOL_VERSION);
    }
    // The random bytes provided by the peer.
    const random = buf.readBytes(32);
    // Read legacy_session_id, so the server can echo it.
    const sessionId = buf.readVectorBytes8();
    // We only support a single ciphersuite, but the peer may offer several.
    // Scan the list to confirm that the one we want is present.
    let found = false;
    buf.readVector16(buf => {
      const cipherSuite = buf.readUint16();
      if (cipherSuite === TLS_AES_128_GCM_SHA256) {
        found = true;
      }
    });
    if (! found) {
      throw new TLSError(ALERT_DESCRIPTION.HANDSHAKE_FAILURE);
    }
    // legacy_compression_methods must be a single zero byte for TLS1.3 ClientHellos.
    // It can be non-zero in previous versions of TLS, but we're not going to
    // make a successful handshake with such versions, so better to just bail out now.
    const legacyCompressionMethods = buf.readVectorBytes8();
    if (legacyCompressionMethods.byteLength !== 1) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    if (legacyCompressionMethods[0] !== 0x00) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    // Read and check the extensions.
    const extensions = this._readExtensions(HANDSHAKE_TYPE.CLIENT_HELLO, buf);
    if (! extensions.has(EXTENSION_TYPE.SUPPORTED_VERSIONS)) {
      throw new TLSError(ALERT_DESCRIPTION.MISSING_EXTENSION);
    }
    if (extensions.get(EXTENSION_TYPE.SUPPORTED_VERSIONS).versions.indexOf(VERSION_TLS_1_3) === -1) {
      throw new TLSError(ALERT_DESCRIPTION.PROTOCOL_VERSION);
    }
    // Was the PreSharedKey extension the last one?
    if (extensions.has(EXTENSION_TYPE.PRE_SHARED_KEY)) {
      if (extensions.lastSeenExtension !== EXTENSION_TYPE.PRE_SHARED_KEY) {
        throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
      }
    }
    return new this(random, sessionId, extensions);
  }

  _write(buf) {
    buf.writeUint16(VERSION_TLS_1_2);
    buf.writeBytes(this.random);
    buf.writeVectorBytes8(this.sessionId);
    // Our single supported ciphersuite
    buf.writeVector16(buf => {
      buf.writeUint16(TLS_AES_128_GCM_SHA256);
    });
    // A single zero byte for legacy_compression_methods
    buf.writeVectorBytes8(new Uint8Array(1));
    this._writeExtensions(buf, this.extensions);
  }
}


// The ServerHello message:
//
//  struct {
//      ProtocolVersion legacy_version = 0x0303;    /* TLS v1.2 */
//      Random random;
//      opaque legacy_session_id_echo<0..32>;
//      CipherSuite cipher_suite;
//      uint8 legacy_compression_method = 0;
//      Extension extensions < 6..2 ^ 16 - 1 >;
//  } ServerHello;

class messages_ServerHello extends messages_HandshakeMessage {

  constructor(random, sessionId, extensions) {
    super();
    this.random = random;
    this.sessionId = sessionId;
    this.extensions = extensions;
  }

  get TYPE_TAG() {
    return HANDSHAKE_TYPE.SERVER_HELLO;
  }

  static _read(buf) {
    // Fixed value for legacy_version.
    if (buf.readUint16() !== VERSION_TLS_1_2) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    // Random bytes from the server.
    const random = buf.readBytes(32);
    // It should have echoed our vector for legacy_session_id.
    const sessionId = buf.readVectorBytes8();
    // It should have selected our single offered ciphersuite.
    if (buf.readUint16() !== TLS_AES_128_GCM_SHA256) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    // legacy_compression_method must be zero.
    if (buf.readUint8() !== 0) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    const extensions = this._readExtensions(HANDSHAKE_TYPE.SERVER_HELLO, buf);
    if (! extensions.has(EXTENSION_TYPE.SUPPORTED_VERSIONS)) {
      throw new TLSError(ALERT_DESCRIPTION.MISSING_EXTENSION);
    }
    if (extensions.get(EXTENSION_TYPE.SUPPORTED_VERSIONS).selectedVersion !== VERSION_TLS_1_3) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    return new this(random, sessionId, extensions);
  }

  _write(buf) {
    buf.writeUint16(VERSION_TLS_1_2);
    buf.writeBytes(this.random);
    buf.writeVectorBytes8(this.sessionId);
    // Our single supported ciphersuite
    buf.writeUint16(TLS_AES_128_GCM_SHA256);
    // A single zero byte for legacy_compression_method
    buf.writeUint8(0);
    this._writeExtensions(buf, this.extensions);
  }
}


// The EncryptedExtensions message:
//
//  struct {
//    Extension extensions < 0..2 ^ 16 - 1 >;
//  } EncryptedExtensions;
//
// We don't actually send any EncryptedExtensions,
// but still have to send an empty message.

class EncryptedExtensions extends messages_HandshakeMessage {
  constructor(extensions) {
    super();
    this.extensions = extensions;
  }

  get TYPE_TAG() {
    return HANDSHAKE_TYPE.ENCRYPTED_EXTENSIONS;
  }

  static _read(buf) {
    const extensions = this._readExtensions(HANDSHAKE_TYPE.ENCRYPTED_EXTENSIONS, buf);
    return new this(extensions);
  }

  _write(buf) {
    this._writeExtensions(buf, this.extensions);
  }
}


// The Finished message:
//
// struct {
//   opaque verify_data[Hash.length];
// } Finished;

class messages_Finished extends messages_HandshakeMessage {

  constructor(verifyData) {
    super();
    this.verifyData = verifyData;
  }

  get TYPE_TAG() {
    return HANDSHAKE_TYPE.FINISHED;
  }

  static _read(buf) {
    const verifyData = buf.readBytes(HASH_LENGTH);
    return new this(verifyData);
  }

  _write(buf) {
    buf.writeBytes(this.verifyData);
  }
}


// The NewSessionTicket message:
//
//   struct {
//    uint32 ticket_lifetime;
//    uint32 ticket_age_add;
//    opaque ticket_nonce < 0..255 >;
//    opaque ticket < 1..2 ^ 16 - 1 >;
//    Extension extensions < 0..2 ^ 16 - 2 >;
//  } NewSessionTicket;
//
// We don't actually make use of these, but we need to be able
// to accept them and do basic validation.

class messages_NewSessionTicket extends messages_HandshakeMessage {
  constructor(ticketLifetime, ticketAgeAdd, ticketNonce, ticket, extensions) {
    super();
    this.ticketLifetime = ticketLifetime;
    this.ticketAgeAdd = ticketAgeAdd;
    this.ticketNonce = ticketNonce;
    this.ticket = ticket;
    this.extensions = extensions;
  }

  get TYPE_TAG() {
    return HANDSHAKE_TYPE.NEW_SESSION_TICKET;
  }

  static _read(buf) {
    const ticketLifetime = buf.readUint32();
    const ticketAgeAdd = buf.readUint32();
    const ticketNonce = buf.readVectorBytes8();
    const ticket = buf.readVectorBytes16();
    if (ticket.byteLength < 1) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    const extensions = this._readExtensions(HANDSHAKE_TYPE.NEW_SESSION_TICKET, buf);
    return new this(ticketLifetime, ticketAgeAdd, ticketNonce, ticket, extensions);
  }

  _write(buf) {
    buf.writeUint32(this.ticketLifetime);
    buf.writeUint32(this.ticketAgeAdd);
    buf.writeVectorBytes8(this.ticketNonce);
    buf.writeVectorBytes16(this.ticket);
    this._writeExtensions(buf, this.extensions);
  }
}

// CONCATENATED MODULE: ./src/states.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */








//
// State-machine for TLS Handshake Management.
//
// Internally, we manage the TLS connection by explicitly modelling the
// client and server state-machines from RFC8446.  You can think of
// these `State` objects as little plugins for the `Connection` class
// that provide different behaviours of `send` and `receive` depending
// on the state of the connection.
//

class states_State {

  constructor(conn) {
    this.conn = conn;
  }

  async initialize() {
    // By default, nothing to do when entering the state.
  }

  async sendApplicationData(bytes) {
    // By default, assume we're not ready to send yet and the caller
    // should be blocking on the connection promise before reaching here.
    throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
  }

  async recvApplicationData(bytes) {
    throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
  }

  async recvHandshakeMessage(msg) {
    throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
  }

  async recvAlertMessage(alert) {
    switch (alert.description) {
      case ALERT_DESCRIPTION.CLOSE_NOTIFY:
        this.conn._closeForRecv(alert);
        throw alert;
      default:
        return await this.handleErrorAndRethrow(alert);
    }
  }

  async recvChangeCipherSpec(bytes) {
    throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
  }

  async handleErrorAndRethrow(err) {
    let alert = err;
    if (! (alert instanceof TLSAlert)) {
      alert = new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    // Try to send error alert to the peer, but we may not
    // be able to if the outgoing connection was already closed.
    try {
      await this.conn._sendAlertMessage(alert);
    } catch (_) { }
    await this.conn._transition(ERROR, err);
    throw err;
  }

  async close() {
    const alert = new TLSCloseNotify();
    await this.conn._sendAlertMessage(alert);
    this.conn._closeForSend(alert);
  }

}

// A special "guard" state to prevent us from using
// an improperly-initialized Connection.

class UNINITIALIZED extends states_State {
  async initialize() {
    throw new Error('uninitialized state');
  }
  async sendApplicationData(bytes) {
    throw new Error('uninitialized state');
  }
  async recvApplicationData(bytes) {
    throw new Error('uninitialized state');
  }
  async recvHandshakeMessage(msg) {
    throw new Error('uninitialized state');
  }
  async recvChangeCipherSpec(bytes) {
    throw new Error('uninitialized state');
  }
  async handleErrorAndRethrow(err) {
    throw err;
  }
  async close() {
    throw new Error('uninitialized state');
  }
}

// A special "error" state for when something goes wrong.
// This state never transitions to another state, effectively
// terminating the connection.

class ERROR extends states_State {
  async initialize(err) {
    this.error = err;
    this.conn._setConnectionFailure(err);
    // Unceremoniously shut down the record layer on error.
    this.conn._recordlayer.setSendError(err);
    this.conn._recordlayer.setRecvError(err);
  }
  async sendApplicationData(bytes) {
    throw this.error;
  }
  async recvApplicationData(bytes) {
    throw this.error;
  }
  async recvHandshakeMessage(msg) {
    throw this.error;
  }
  async recvAlertMessage(err) {
    throw this.error;
  }
  async recvChangeCipherSpec(bytes) {
    throw this.error;
  }
  async handleErrorAndRethrow(err) {
    throw err;
  }
  async close() {
    throw this.error;
  }
}

// The "connected" state, for when the handshake is complete
// and we're ready to send application-level data.
// The logic for this is largely symmetric between client and server.

class states_CONNECTED extends states_State {
  async initialize() {
    this.conn._setConnectionSuccess();
  }
  async sendApplicationData(bytes) {
    await this.conn._sendApplicationData(bytes);
  }
  async recvApplicationData(bytes) {
    return bytes;
  }
  async recvChangeCipherSpec(bytes) {
    throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
  }
}

// A base class for states that occur in the middle of the handshake
// (that is, between ClientHello and Finished).  These states may receive
// CHANGE_CIPHER_SPEC records for b/w compat reasons, which must contain
// exactly a single 0x01 byte and must otherwise be ignored.

class states_MidHandshakeState extends states_State {
  async recvChangeCipherSpec(bytes) {
    if (this.conn._hasSeenChangeCipherSpec) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    if (bytes.byteLength !== 1 || bytes[0] !== 1) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    this.conn._hasSeenChangeCipherSpec = true;
  }
}

// These states implement (part of) the client state-machine from
// https://tools.ietf.org/html/rfc8446#appendix-A.1
//
// Since we're only implementing a small subset of TLS1.3,
// we only need a small subset of the handshake.  It basically goes:
//
//   * send ClientHello
//   * receive ServerHello
//   * receive EncryptedExtensions
//   * receive server Finished
//   * send client Finished
//
// We include some unused states for completeness, so that it's easier
// to check the implementation against the diagrams in the RFC.

class states_CLIENT_START extends states_State {
  async initialize() {
    const keyschedule = this.conn._keyschedule;
    await keyschedule.addPSK(this.conn.psk);
    // Construct a ClientHello message with our single PSK.
    // We can't know the PSK binder value yet, so we initially write zeros.
    const clientHello = new messages_ClientHello(
      // Client random salt.
      await getRandomBytes(32),
      // Random legacy_session_id; we *could* send an empty string here,
      // but sending a random one makes it easier to be compatible with
      // the data emitted by tlslite-ng for test-case generation.
      await getRandomBytes(32),
      [
        new extensions_SupportedVersionsExtension([VERSION_TLS_1_3]),
        new extensions_PskKeyExchangeModesExtension([PSK_MODE_KE]),
        new extensions_PreSharedKeyExtension([this.conn.pskId], [zeros(HASH_LENGTH)]),
      ],
    );
    const buf = new utils_BufferWriter();
    clientHello.write(buf);
    // Now that we know what the ClientHello looks like,
    // go back and calculate the appropriate PSK binder value.
    // We only support a single PSK, so the length of the binders field is the
    // length of the hash plus one for rendering it as a variable-length byte array,
    // plus two for rendering the variable-length list of PSK binders.
    const PSK_BINDERS_SIZE = HASH_LENGTH + 1 + 2;
    const truncatedTranscript = buf.slice(0, buf.tell() - PSK_BINDERS_SIZE);
    const pskBinder = await keyschedule.calculateFinishedMAC(keyschedule.extBinderKey, truncatedTranscript);
    buf.incr(-HASH_LENGTH);
    buf.writeBytes(pskBinder);
    await this.conn._sendHandshakeMessageBytes(buf.flush());
    await this.conn._transition(states_CLIENT_WAIT_SH, clientHello.sessionId);
  }
}

class states_CLIENT_WAIT_SH extends states_State {
  async initialize(sessionId) {
    this._sessionId = sessionId;
  }
  async recvHandshakeMessage(msg) {
    if (! (msg instanceof messages_ServerHello)) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    if (! bytesAreEqual(msg.sessionId, this._sessionId)) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    const pskExt = msg.extensions.get(EXTENSION_TYPE.PRE_SHARED_KEY);
    if (! pskExt) {
      throw new TLSError(ALERT_DESCRIPTION.MISSING_EXTENSION);
    }
    // We expect only the SUPPORTED_VERSIONS and PRE_SHARED_KEY extensions.
    if (msg.extensions.size !== 2) {
      throw new TLSError(ALERT_DESCRIPTION.UNSUPPORTED_EXTENSION);
    }
    if (pskExt.selectedIdentity !== 0) {
      throw new TLSError(ALERT_DESCRIPTION.ILLEGAL_PARAMETER);
    }
    await this.conn._keyschedule.addECDHE(null);
    await this.conn._setSendKey(this.conn._keyschedule.clientHandshakeTrafficSecret);
    await this.conn._setRecvKey(this.conn._keyschedule.serverHandshakeTrafficSecret);
    await this.conn._transition(states_CLIENT_WAIT_EE);
  }
}

class states_CLIENT_WAIT_EE extends states_MidHandshakeState {
  async recvHandshakeMessage(msg) {
    // We don't make use of any encrypted extensions, but we still
    // have to wait for the server to send the (empty) list of them.
    if (! (msg instanceof EncryptedExtensions)) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    // We do not support any EncryptedExtensions.
    if (msg.extensions.size !== 0) {
      throw new TLSError(ALERT_DESCRIPTION.UNSUPPORTED_EXTENSION);
    }
    const keyschedule = this.conn._keyschedule;
    const serverFinishedTranscript = keyschedule.getTranscript();
    await this.conn._transition(states_CLIENT_WAIT_FINISHED, serverFinishedTranscript);
  }
}

class states_CLIENT_WAIT_FINISHED extends states_State {
  async initialize(serverFinishedTranscript) {
    this._serverFinishedTranscript = serverFinishedTranscript;
  }
  async recvHandshakeMessage(msg) {
    if (! (msg instanceof messages_Finished)) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    // Verify server Finished MAC.
    const keyschedule = this.conn._keyschedule;
    await keyschedule.verifyFinishedMAC(keyschedule.serverHandshakeTrafficSecret, msg.verifyData, this._serverFinishedTranscript);
    // Send our own Finished message in return.
    // This must be encrypted with the handshake traffic key,
    // but must not appear in the transcript used to calculate the application keys.
    const clientFinishedMAC = await keyschedule.calculateFinishedMAC(keyschedule.clientHandshakeTrafficSecret);
    await keyschedule.finalize();
    await this.conn._sendHandshakeMessage(new messages_Finished(clientFinishedMAC));
    await this.conn._setSendKey(keyschedule.clientApplicationTrafficSecret);
    await this.conn._setRecvKey(keyschedule.serverApplicationTrafficSecret);
    await this.conn._transition(states_CLIENT_CONNECTED);
  }
}

class states_CLIENT_CONNECTED extends states_CONNECTED {
  async recvHandshakeMessage(msg) {
    // A connected client must be prepared to accept NewSessionTicket
    // messages.  We never use them, but other server implementations
    // might send them.
    if (! (msg instanceof messages_NewSessionTicket)) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
  }
}

// These states implement (part of) the server state-machine from
// https://tools.ietf.org/html/rfc8446#appendix-A.2
//
// Since we're only implementing a small subset of TLS1.3,
// we only need a small subset of the handshake.  It basically goes:
//
//   * receive ClientHello
//   * send ServerHello
//   * send empty EncryptedExtensions
//   * send server Finished
//   * receive client Finished
//
// We include some unused states for completeness, so that it's easier
// to check the implementation against the diagrams in the RFC.

class states_SERVER_START extends states_State {
  async recvHandshakeMessage(msg) {
    if (! (msg instanceof messages_ClientHello)) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    // In the spec, this is where we select connection parameters, and maybe
    // tell the client to try again if we can't find a compatible set.
    // Since we only support a fixed cipherset, the only thing to "negotiate"
    // is whether they provided an acceptable PSK.
    const pskExt = msg.extensions.get(EXTENSION_TYPE.PRE_SHARED_KEY);
    const pskModesExt = msg.extensions.get(EXTENSION_TYPE.PSK_KEY_EXCHANGE_MODES);
    if (! pskExt || ! pskModesExt) {
      throw new TLSError(ALERT_DESCRIPTION.MISSING_EXTENSION);
    }
    if (pskModesExt.modes.indexOf(PSK_MODE_KE) === -1) {
      throw new TLSError(ALERT_DESCRIPTION.HANDSHAKE_FAILURE);
    }
    const pskIndex = pskExt.identities.findIndex(pskId => bytesAreEqual(pskId, this.conn.pskId));
    if (pskIndex === -1) {
      throw new TLSError(ALERT_DESCRIPTION.UNKNOWN_PSK_IDENTITY);
    }
    await this.conn._keyschedule.addPSK(this.conn.psk);
    // Validate the PSK binder.
    const keyschedule = this.conn._keyschedule;
    const transcript = keyschedule.getTranscript();
    // Calculate size occupied by the PSK binders.
    let pskBindersSize = 2; // Vector16 representation overhead.
    for (const binder of pskExt.binders) {
      pskBindersSize += binder.byteLength + 1; // Vector8 representation overhead.
    }
    await keyschedule.verifyFinishedMAC(keyschedule.extBinderKey, pskExt.binders[pskIndex], transcript.slice(0, -pskBindersSize));
    await this.conn._transition(states_SERVER_NEGOTIATED, msg.sessionId, pskIndex);
  }
}

class states_SERVER_NEGOTIATED extends states_MidHandshakeState {
  async initialize(sessionId, pskIndex) {
    await this.conn._sendHandshakeMessage(new messages_ServerHello(
      // Server random
      await getRandomBytes(32),
      sessionId,
      [
        new extensions_SupportedVersionsExtension(null, VERSION_TLS_1_3),
        new extensions_PreSharedKeyExtension(null, null, pskIndex),
      ]
    ));
    // If the client sent a non-empty sessionId, the server *must* send a change-cipher-spec for b/w compat.
    if (sessionId.byteLength > 0) {
      await this.conn._sendChangeCipherSpec();
    }
    // We can now transition to the encrypted part of the handshake.
    const keyschedule = this.conn._keyschedule;
    await keyschedule.addECDHE(null);
    await this.conn._setSendKey(keyschedule.serverHandshakeTrafficSecret);
    await this.conn._setRecvKey(keyschedule.clientHandshakeTrafficSecret);
    // Send an empty EncryptedExtensions message.
    await this.conn._sendHandshakeMessage(new EncryptedExtensions([]));
    // Send the Finished message.
    const serverFinishedMAC = await keyschedule.calculateFinishedMAC(keyschedule.serverHandshakeTrafficSecret);
    await this.conn._sendHandshakeMessage(new messages_Finished(serverFinishedMAC));
    // We can now *send* using the application traffic key,
    // but have to wait to receive the client Finished before receiving under that key.
    // We need to remember the handshake state from before the client Finished
    // in order to successfully verify the client Finished.
    const clientFinishedTranscript = await keyschedule.getTranscript();
    const clientHandshakeTrafficSecret = keyschedule.clientHandshakeTrafficSecret;
    await keyschedule.finalize();
    await this.conn._setSendKey(keyschedule.serverApplicationTrafficSecret);
    await this.conn._transition(states_SERVER_WAIT_FINISHED, clientHandshakeTrafficSecret, clientFinishedTranscript);
  }
}

class states_SERVER_WAIT_FINISHED extends states_MidHandshakeState {
  async initialize(clientHandshakeTrafficSecret, clientFinishedTranscript) {
    this._clientHandshakeTrafficSecret = clientHandshakeTrafficSecret;
    this._clientFinishedTranscript = clientFinishedTranscript;
  }
  async recvHandshakeMessage(msg) {
    if (! (msg instanceof messages_Finished)) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    const keyschedule = this.conn._keyschedule;
    await keyschedule.verifyFinishedMAC(this._clientHandshakeTrafficSecret, msg.verifyData, this._clientFinishedTranscript);
    this._clientHandshakeTrafficSecret = this._clientFinishedTranscript = null;
    await this.conn._setRecvKey(keyschedule.clientApplicationTrafficSecret);
    await this.conn._transition(states_CONNECTED);
  }
}

// CONCATENATED MODULE: ./src/keyschedule.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// TLS1.3 Key Schedule.
//
// In this file we implement the "key schedule" from
// https://tools.ietf.org/html/rfc8446#section-7.1, which
// defines how to calculate various keys as the handshake
// state progresses.







// The `KeySchedule` class progresses through three stages corresponding
// to the three phases of the TLS1.3 key schedule:
//
//   UNINITIALIZED
//       |
//       | addPSK()
//       v
//   EARLY_SECRET
//       |
//       | addECDHE()
//       v
//   HANDSHAKE_SECRET
//       |
//       | finalize()
//       v
//   MASTER_SECRET
//
// It will error out if the calling code attempts to add key material
// in the wrong order.

const STAGE_UNINITIALIZED = 0;
const STAGE_EARLY_SECRET = 1;
const STAGE_HANDSHAKE_SECRET = 2;
const STAGE_MASTER_SECRET = 3;

class keyschedule_KeySchedule {
  constructor() {
    this.stage = STAGE_UNINITIALIZED;
    // WebCrypto doesn't support a rolling hash construct, so we have to
    // keep the entire message transcript in memory.
    this.transcript = new utils_BufferWriter();
    // This tracks the main secret from with other keys are derived at each stage.
    this.secret = null;
    // And these are all the various keys we'll derive as the handshake progresses.
    this.extBinderKey = null;
    this.clientHandshakeTrafficSecret = null;
    this.serverHandshakeTrafficSecret = null;
    this.clientApplicationTrafficSecret = null;
    this.serverApplicationTrafficSecret = null;
  }

  async addPSK(psk) {
    // Use the selected PSK (if any) to calculate the "early secret".
    if (psk === null) {
      psk = zeros(HASH_LENGTH);
    }
    if (this.stage !== STAGE_UNINITIALIZED) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    this.stage = STAGE_EARLY_SECRET;
    this.secret = await hkdfExtract(zeros(HASH_LENGTH), psk);
    this.extBinderKey = await this.deriveSecret('ext binder', EMPTY);
    this.secret = await this.deriveSecret('derived', EMPTY);
  }

  async addECDHE(ecdhe) {
    // Mix in the ECDHE output (if any) to calculate the "handshake secret".
    if (ecdhe === null) {
      ecdhe = zeros(HASH_LENGTH);
    }
    if (this.stage !== STAGE_EARLY_SECRET) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    this.stage = STAGE_HANDSHAKE_SECRET;
    this.extBinderKey = null;
    this.secret = await hkdfExtract(this.secret, ecdhe);
    this.clientHandshakeTrafficSecret = await this.deriveSecret('c hs traffic');
    this.serverHandshakeTrafficSecret = await this.deriveSecret('s hs traffic');
    this.secret = await this.deriveSecret('derived', EMPTY);
  }

  async finalize() {
    if (this.stage !== STAGE_HANDSHAKE_SECRET) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    this.stage = STAGE_MASTER_SECRET;
    this.clientHandshakeTrafficSecret = null;
    this.serverHandshakeTrafficSecret = null;
    this.secret = await hkdfExtract(this.secret, zeros(HASH_LENGTH));
    this.clientApplicationTrafficSecret = await this.deriveSecret('c ap traffic');
    this.serverApplicationTrafficSecret = await this.deriveSecret('s ap traffic');
    this.secret = null;
  }

  addToTranscript(bytes) {
    this.transcript.writeBytes(bytes);
  }

  getTranscript() {
    return this.transcript.slice();
  }

  async deriveSecret(label, transcript = undefined) {
    transcript = transcript || this.getTranscript();
    return await hkdfExpandLabel(this.secret, label, await hash(transcript), HASH_LENGTH);
  }

  async calculateFinishedMAC(baseKey, transcript = undefined) {
    transcript = transcript || this.getTranscript();
    const finishedKey = await hkdfExpandLabel(baseKey, 'finished', EMPTY, HASH_LENGTH);
    return await hmac(finishedKey, await hash(transcript));
  }

  async verifyFinishedMAC(baseKey, mac, transcript = undefined) {
    transcript = transcript || this.getTranscript();
    const finishedKey = await hkdfExpandLabel(baseKey, 'finished', EMPTY, HASH_LENGTH);
    await verifyHmac(finishedKey, mac, await hash(transcript));
  }
}

// CONCATENATED MODULE: ./src/recordlayer.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//
// This file implements the "record layer" for TLS1.3, as defined in
// https://tools.ietf.org/html/rfc8446#section-5.
//
// The record layer is responsible for encrypting/decrypting bytes to be
// sent over the wire, including stateful management of sequence numbers
// for the incoming and outgoing stream.
//
// The main interface is the RecordLayer class, which takes a callback function
// sending data and can be used like so:
//
//    rl = new RecordLayer(async function send_encrypted_data(data) {
//      // application-specific sending logic here.
//    });
//
//    // Records are sent and received in plaintext by default,
//    // until you specify the key to use.
//    await rl.setSendKey(key)
//
//    // Send some data by specifying the record type and the bytes.
//    // Where allowed by the record type, it will be buffered until
//    // explicitly flushed, and then sent by calling the callback.
//    await rl.send(RECORD_TYPE.HANDSHAKE, <bytes for a handshake message>)
//    await rl.send(RECORD_TYPE.HANDSHAKE, <bytes for another handshake message>)
//    await rl.flush()
//
//    // Separate keys are used for sending and receiving.
//    rl.setRecvKey(key);
//
//    // When data is received, push it into the RecordLayer
//    // and pass a callback that will be called with a [type, bytes]
//    // pair for each message parsed from the data.
//    rl.recv(dataReceivedFromPeer, async (type, bytes) => {
//      switch (type) {
//        case RECORD_TYPE.APPLICATION_DATA:
//          // do something with application data
//        case RECORD_TYPE.HANDSHAKE:
//          // do something with a handshake message
//        default:
//          // etc...
//      }
//    });
//







/* eslint-disable sorting/sort-object-props */
const RECORD_TYPE = {
  CHANGE_CIPHER_SPEC: 20,
  ALERT: 21,
  HANDSHAKE: 22,
  APPLICATION_DATA: 23,
};
/* eslint-enable sorting/sort-object-props */

// Encrypting at most 2^24 records will force us to stay
// below data limits on AES-GCM encryption key use, and also
// means we can accurately represent the sequence number as
// a javascript double.
const MAX_SEQUENCE_NUMBER = Math.pow(2, 24);
const MAX_RECORD_SIZE = Math.pow(2, 14);
const MAX_ENCRYPTED_RECORD_SIZE = MAX_RECORD_SIZE + 256;
const RECORD_HEADER_SIZE = 5;

// These are some helper classes to manage the encryption/decryption state
// for a particular key.

class recordlayer_CipherState {
  constructor(key, iv) {
    this.key = key;
    this.iv = iv;
    this.seqnum = 0;
  }

  static async create(baseKey, mode) {
    // Derive key and iv per https://tools.ietf.org/html/rfc8446#section-7.3
    const key = await prepareKey(await hkdfExpandLabel(baseKey, 'key', EMPTY, KEY_LENGTH), mode);
    const iv = await hkdfExpandLabel(baseKey, 'iv', EMPTY, IV_LENGTH);
    return new this(key, iv);
  }

  nonce() {
    // Ref https://tools.ietf.org/html/rfc8446#section-5.3:
    // * left-pad the sequence number with zeros to IV_LENGTH
    // * xor with the provided iv
    // Our sequence numbers are always less than 2^24, so fit in a Uint32
    // in the last 4 bytes of the nonce.
    const nonce = this.iv.slice();
    const dv = new DataView(nonce.buffer, nonce.byteLength - 4, 4);
    dv.setUint32(0, dv.getUint32(0) ^ this.seqnum);
    this.seqnum += 1;
    if (this.seqnum > MAX_SEQUENCE_NUMBER) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    return nonce;
  }
}

class recordlayer_EncryptionState extends recordlayer_CipherState {
  static async create(key) {
    return super.create(key, 'encrypt');
  }

  async encrypt(plaintext, additionalData) {
    return await encrypt(this.key, this.nonce(), plaintext, additionalData);
  }
}

class recordlayer_DecryptionState extends recordlayer_CipherState {
  static async create(key) {
    return super.create(key, 'decrypt');
  }

  async decrypt(ciphertext, additionalData) {
    return await decrypt(this.key, this.nonce(), ciphertext, additionalData);
  }
}

// The main RecordLayer class.

class recordlayer_RecordLayer {
  constructor(sendCallback) {
    this.sendCallback = sendCallback;
    this._sendEncryptState = null;
    this._sendError = null;
    this._recvDecryptState = null;
    this._recvError = null;
    this._pendingRecordType = 0;
    this._pendingRecordBuf = null;
  }

  async setSendKey(key) {
    await this.flush();
    this._sendEncryptState = await recordlayer_EncryptionState.create(key);
  }

  async setRecvKey(key) {
    this._recvDecryptState = await recordlayer_DecryptionState.create(key);
  }

  async setSendError(err) {
    this._sendError = err;
  }

  async setRecvError(err) {
    this._recvError = err;
  }

  async send(type, data) {
    if (this._sendError !== null) {
      throw this._sendError;
    }
    // Forbid sending data that doesn't fit into a single record.
    // We do not support fragmentation over multiple records.
    if (data.byteLength > MAX_RECORD_SIZE) {
      throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
    }
    // Flush if we're switching to a different record type.
    if (this._pendingRecordType && this._pendingRecordType !== type) {
      await this.flush();
    }
    // Flush if we would overflow the max size of a record.
    if (this._pendingRecordBuf !== null) {
      if (this._pendingRecordBuf.tell() + data.byteLength > MAX_RECORD_SIZE) {
        await this.flush();
      }
    }
    // Start a new pending record if necessary.
    // We reserve space at the start of the buffer for the record header,
    // which is conveniently always a fixed size.
    if (this._pendingRecordBuf === null) {
      this._pendingRecordType = type;
      this._pendingRecordBuf = new utils_BufferWriter();
      this._pendingRecordBuf.incr(RECORD_HEADER_SIZE);
    }
    this._pendingRecordBuf.writeBytes(data);
  }

  async flush() {
    // If there's nothing to flush, bail out early.
    // Don't throw `_sendError` if we're not sending anything, because `flush()`
    // can be called when we're trying to transition into an error state.
    const buf = this._pendingRecordBuf;
    let type = this._pendingRecordType;
    if (! type) {
      if (buf !== null) {
        throw new TLSError(ALERT_DESCRIPTION.INTERNAL_ERROR);
      }
      return;
    }
    if (this._sendError !== null) {
      throw this._sendError;
    }
    // If we're encrypting, turn the existing buffer contents into a `TLSInnerPlaintext` by
    // appending the type. We don't do any zero-padding, although the spec allows it.
    let inflation = 0, innerPlaintext = null;
    if (this._sendEncryptState !== null) {
      buf.writeUint8(type);
      innerPlaintext = buf.slice(RECORD_HEADER_SIZE);
      inflation = AEAD_SIZE_INFLATION;
      type = RECORD_TYPE.APPLICATION_DATA;
    }
    // Write the common header for either `TLSPlaintext` or `TLSCiphertext` record.
    const length = buf.tell() - RECORD_HEADER_SIZE + inflation;
    buf.seek(0);
    buf.writeUint8(type);
    buf.writeUint16(VERSION_TLS_1_2);
    buf.writeUint16(length);
    // Followed by different payload depending on encryption status.
    if (this._sendEncryptState !== null) {
      const additionalData = buf.slice(0, RECORD_HEADER_SIZE);
      const ciphertext = await this._sendEncryptState.encrypt(innerPlaintext, additionalData);
      buf.writeBytes(ciphertext);
    } else {
      buf.incr(length);
    }
    this._pendingRecordBuf = null;
    this._pendingRecordType = 0;
    await this.sendCallback(buf.flush());
  }

  async recv(data) {
    if (this._recvError !== null) {
      throw this._recvError;
    }
    // For simplicity, we assume that the given data contains exactly one record.
    // Peers using this library will send one record at a time over the websocket
    // connection, and we can assume that the server-side websocket bridge will split
    // up any traffic into individual records if we ever start interoperating with
    // peers using a different TLS implementation.
    // Similarly, we assume that handshake messages will not be fragmented across
    // multiple records. This should be trivially true for the PSK-only mode used
    // by this library, but we may want to relax it in future for interoperability
    // with e.g. large ClientHello messages that contain lots of different options.
    const buf = new utils_BufferReader(data);
    // The data to read is either a TLSPlaintext or TLSCiphertext struct,
    // depending on whether record protection has been enabled yet:
    //
    //    struct {
    //        ContentType type;
    //        ProtocolVersion legacy_record_version;
    //        uint16 length;
    //        opaque fragment[TLSPlaintext.length];
    //    } TLSPlaintext;
    //
    //    struct {
    //        ContentType opaque_type = application_data; /* 23 */
    //        ProtocolVersion legacy_record_version = 0x0303; /* TLS v1.2 */
    //        uint16 length;
    //        opaque encrypted_record[TLSCiphertext.length];
    //    } TLSCiphertext;
    //
    let type = buf.readUint8();
    // The spec says legacy_record_version "MUST be ignored for all purposes",
    // but we know TLS1.3 implementations will only ever emit two possible values,
    // so it seems useful to bail out early if we receive anything else.
    const version = buf.readUint16();
    if (version !== VERSION_TLS_1_2) {
      // TLS1.0 is only acceptable on initial plaintext records.
      if (this._recvDecryptState !== null || version !== VERSION_TLS_1_0) {
        throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
      }
    }
    const length = buf.readUint16();
    let plaintext;
    if (this._recvDecryptState === null || type === RECORD_TYPE.CHANGE_CIPHER_SPEC) {
      [type, plaintext] = await this._readPlaintextRecord(type, length, buf);
    } else {
      [type, plaintext] = await this._readEncryptedRecord(type, length, buf);
    }
    // Sanity-check that we received exactly one record.
    if (buf.hasMoreBytes()) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    return [type, plaintext];
  }

  // Helper to read an unencrypted `TLSPlaintext` struct

  async _readPlaintextRecord(type, length, buf) {
    if (length > MAX_RECORD_SIZE) {
      throw new TLSError(ALERT_DESCRIPTION.RECORD_OVERFLOW);
    }
    return [type, buf.readBytes(length)];
  }

  // Helper to read an encrypted `TLSCiphertext` struct,
  // decrypting it into plaintext.

  async _readEncryptedRecord(type, length, buf) {
    if (length > MAX_ENCRYPTED_RECORD_SIZE) {
      throw new TLSError(ALERT_DESCRIPTION.RECORD_OVERFLOW);
    }
    // The outer type for encrypted records is always APPLICATION_DATA.
    if (type !== RECORD_TYPE.APPLICATION_DATA) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    // Decrypt and decode the contained `TLSInnerPlaintext` struct:
    //
    //    struct {
    //        opaque content[TLSPlaintext.length];
    //        ContentType type;
    //        uint8 zeros[length_of_padding];
    //    } TLSInnerPlaintext;
    //
    // The additional data for the decryption is the `TLSCiphertext` record
    // header, which is a fixed size and immediately prior to current buffer position.
    buf.incr(-RECORD_HEADER_SIZE);
    const additionalData = buf.readBytes(RECORD_HEADER_SIZE);
    const ciphertext = buf.readBytes(length);
    const paddedPlaintext = await this._recvDecryptState.decrypt(ciphertext, additionalData);
    // We have to scan backwards over the zero padding at the end of the struct
    // in order to find the non-zero `type` byte.
    let i;
    for (i = paddedPlaintext.byteLength - 1; i >= 0; i--) {
      if (paddedPlaintext[i] !== 0) {
        break;
      }
    }
    if (i < 0) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    type = paddedPlaintext[i];
    // `change_cipher_spec` records must always be plaintext.
    if (type === RECORD_TYPE.CHANGE_CIPHER_SPEC) {
      throw new TLSError(ALERT_DESCRIPTION.DECODE_ERROR);
    }
    return [type, paddedPlaintext.slice(0, i)];
  }
}

// CONCATENATED MODULE: ./src/tlsconnection.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// The top-level APIs offered by this module are `ClientConnection` and
// `ServerConnection` classes, which provide authenticated and encrypted
// communication via the "externally-provisioned PSK" mode of TLS1.3.
// They each take a callback to be used for sending data to the remote peer,
// and operate like this:
//
//    conn = await ClientConnection.create(psk, pskId, async function send_data_to_server(data) {
//      // application-specific sending logic here.
//    })
//
//    // Send data to the server by calling `send`,
//    // which will use the callback provided in the constructor.
//    // A single `send()` by the application may result in multiple
//    // invokations of the callback.
//
//    await conn.send('application-level data')
//
//    // When data is received from the server, push it into
//    // the connection and let it return any decrypted app-level data.
//    // There might not be any app-level data if it was a protocol control
//    //  message, and the receipt of the data might trigger additional calls
//    // to the send callback for protocol control purposes.
//
//    serverSocket.on('data', async encrypted_data => {
//      const plaintext = await conn.recv(data)
//      if (plaintext !== null) {
//        do_something_with_app_level_data(plaintext)
//      }
//    })
//
//    // It's good practice to explicitly close the connection
//    // when finished.  This will send a "closed" notification
//    // to the server.
//
//    await conn.close()
//
//    // When the peer sends a "closed" notification it will show up
//    // as a `TLSCloseNotify` exception from recv:
//
//    try {
//      data = await conn.recv(data);
//    } catch (err) {
//      if (! (err instanceof TLSCloseNotify) { throw err }
//      do_something_to_cleanly_close_data_connection();
//    }
//
// The `ServerConnection` API operates similarly; the distinction is mainly
// in which side is expected to send vs receieve during the protocol handshake.










class tlsconnection_Connection {
  constructor(psk, pskId, sendCallback) {
    this.psk = assertIsBytes(psk);
    this.pskId = assertIsBytes(pskId);
    this.connected = new Promise((resolve, reject) => {
      this._onConnectionSuccess = resolve;
      this._onConnectionFailure = reject;
    });
    this._state = new UNINITIALIZED(this);
    this._handshakeRecvBuffer = null;
    this._hasSeenChangeCipherSpec = false;
    this._recordlayer = new recordlayer_RecordLayer(sendCallback);
    this._keyschedule = new keyschedule_KeySchedule();
    this._lastPromise = Promise.resolve();
  }

  // Subclasses will override this with some async initialization logic.
  static async create(psk, pskId, sendCallback) {
    return new this(psk, pskId, sendCallback);
  }

  // These are the three public API methods that consumers can use
  // to send and receive data encrypted with TLS1.3.

  async send(data) {
    assertIsBytes(data);
    await this.connected;
    await this._synchronized(async () => {
      await this._state.sendApplicationData(data);
    });
  }

  async recv(data) {
    assertIsBytes(data);
    return await this._synchronized(async () => {
      // Decrypt the data using the record layer.
      // We expect to receive precisely one record at a time.
      const [type, bytes] = await this._recordlayer.recv(data);
      // Dispatch based on the type of the record.
      switch (type) {
        case RECORD_TYPE.CHANGE_CIPHER_SPEC:
          await this._state.recvChangeCipherSpec(bytes);
          return null;
        case RECORD_TYPE.ALERT:
          await this._state.recvAlertMessage(TLSAlert.fromBytes(bytes));
          return null;
        case RECORD_TYPE.APPLICATION_DATA:
          return await this._state.recvApplicationData(bytes);
        case RECORD_TYPE.HANDSHAKE:
          // Multiple handshake messages may be coalesced into a single record.
          // Store the in-progress record buffer on `this` so that we can guard
          // against handshake messages that span a change in keys.
          this._handshakeRecvBuffer = new utils_BufferReader(bytes);
          if (! this._handshakeRecvBuffer.hasMoreBytes()) {
            throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
          }
          do {
            // Each handshake messages has a type and length prefix, per
            // https://tools.ietf.org/html/rfc8446#appendix-B.3
            this._handshakeRecvBuffer.incr(1);
            const mlength = this._handshakeRecvBuffer.readUint24();
            this._handshakeRecvBuffer.incr(-4);
            const messageBytes = this._handshakeRecvBuffer.readBytes(mlength + 4);
            this._keyschedule.addToTranscript(messageBytes);
            await this._state.recvHandshakeMessage(messages_HandshakeMessage.fromBytes(messageBytes));
          } while (this._handshakeRecvBuffer.hasMoreBytes());
          this._handshakeRecvBuffer = null;
          return null;
        default:
          throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
      }
    });
  }

  async close() {
    await this._synchronized(async () => {
      await this._state.close();
    });
  }

  // Ensure that async functions execute one at a time,
  // by waiting for the previous call to `_synchronized()` to complete
  // before starting a new one.  This helps ensure that we complete
  // one state-machine transition before starting to do the next.
  // It's also a convenient place to catch and alert on errors.

  _synchronized(cb) {
    const nextPromise = this._lastPromise.then(() => {
      return cb();
    }).catch(async err => {
      if (err instanceof TLSCloseNotify) {
        throw err;
      }
      await this._state.handleErrorAndRethrow(err);
    });
    // We don't want to hold on to the return value or error,
    // just synchronize on the fact that it completed.
    this._lastPromise = nextPromise.then(noop, noop);
    return nextPromise;
  }

  // This drives internal transition of the state-machine,
  // ensuring that the new state is properly initialized.

  async _transition(State, ...args) {
    this._state = new State(this);
    await this._state.initialize(...args);
    await this._recordlayer.flush();
  }

  // These are helpers to allow the State to manipulate the recordlayer
  // and send out various types of data.

  async _sendApplicationData(bytes) {
    await this._recordlayer.send(RECORD_TYPE.APPLICATION_DATA, bytes);
    await this._recordlayer.flush();
  }

  async _sendHandshakeMessage(msg) {
    await this._sendHandshakeMessageBytes(msg.toBytes());
  }

  async _sendHandshakeMessageBytes(bytes) {
    this._keyschedule.addToTranscript(bytes);
    await this._recordlayer.send(RECORD_TYPE.HANDSHAKE, bytes);
    // Don't flush after each handshake message, since we can probably
    // coalesce multiple messages into a single record.
  }

  async _sendAlertMessage(err) {
    await this._recordlayer.send(RECORD_TYPE.ALERT, err.toBytes());
    await this._recordlayer.flush();
  }

  async _sendChangeCipherSpec() {
    await this._recordlayer.send(RECORD_TYPE.CHANGE_CIPHER_SPEC, new Uint8Array([0x01]));
    await this._recordlayer.flush();
  }

  async _setSendKey(key) {
    return await this._recordlayer.setSendKey(key);
  }

  async _setRecvKey(key) {
    // Handshake messages that change keys must be on a record boundary.
    if (this._handshakeRecvBuffer && this._handshakeRecvBuffer.hasMoreBytes()) {
      throw new TLSError(ALERT_DESCRIPTION.UNEXPECTED_MESSAGE);
    }
    return await this._recordlayer.setRecvKey(key);
  }

  _setConnectionSuccess() {
    if (this._onConnectionSuccess !== null) {
      this._onConnectionSuccess();
      this._onConnectionSuccess = null;
      this._onConnectionFailure = null;
    }
  }

  _setConnectionFailure(err) {
    if (this._onConnectionFailure !== null) {
      this._onConnectionFailure(err);
      this._onConnectionSuccess = null;
      this._onConnectionFailure = null;
    }
  }

  _closeForSend(alert) {
    this._recordlayer.setSendError(alert);
  }

  _closeForRecv(alert) {
    this._recordlayer.setRecvError(alert);
  }
}

class tlsconnection_ClientConnection extends tlsconnection_Connection {
  static async create(psk, pskId, sendCallback) {
    const instance = await super.create(psk, pskId, sendCallback);
    await instance._transition(states_CLIENT_START);
    return instance;
  }
}

class tlsconnection_ServerConnection extends tlsconnection_Connection {
  static async create(psk, pskId, sendCallback) {
    const instance = await super.create(psk, pskId, sendCallback);
    await instance._transition(states_SERVER_START);
    return instance;
  }
}

// CONCATENATED MODULE: ./node_modules/event-target-shim/dist/event-target-shim.mjs
/**
 * @author Toru Nagashima <https://github.com/mysticatea>
 * @copyright 2015 Toru Nagashima. All rights reserved.
 * See LICENSE file in root directory for full license.
 */
/**
 * @typedef {object} PrivateData
 * @property {EventTarget} eventTarget The event target.
 * @property {{type:string}} event The original event object.
 * @property {number} eventPhase The current event phase.
 * @property {EventTarget|null} currentTarget The current event target.
 * @property {boolean} canceled The flag to prevent default.
 * @property {boolean} stopped The flag to stop propagation.
 * @property {boolean} immediateStopped The flag to stop propagation immediately.
 * @property {Function|null} passiveListener The listener if the current listener is passive. Otherwise this is null.
 * @property {number} timeStamp The unix time.
 * @private
 */

/**
 * Private data for event wrappers.
 * @type {WeakMap<Event, PrivateData>}
 * @private
 */
const privateData = new WeakMap();

/**
 * Cache for wrapper classes.
 * @type {WeakMap<Object, Function>}
 * @private
 */
const wrappers = new WeakMap();

/**
 * Get private data.
 * @param {Event} event The event object to get private data.
 * @returns {PrivateData} The private data of the event.
 * @private
 */
function pd(event) {
    const retv = privateData.get(event);
    console.assert(
        retv != null,
        "'this' is expected an Event object, but got",
        event
    );
    return retv
}

/**
 * https://dom.spec.whatwg.org/#set-the-canceled-flag
 * @param data {PrivateData} private data.
 */
function setCancelFlag(data) {
    if (data.passiveListener != null) {
        if (
            typeof console !== "undefined" &&
            typeof console.error === "function"
        ) {
            console.error(
                "Unable to preventDefault inside passive event listener invocation.",
                data.passiveListener
            );
        }
        return
    }
    if (!data.event.cancelable) {
        return
    }

    data.canceled = true;
    if (typeof data.event.preventDefault === "function") {
        data.event.preventDefault();
    }
}

/**
 * @see https://dom.spec.whatwg.org/#interface-event
 * @private
 */
/**
 * The event wrapper.
 * @constructor
 * @param {EventTarget} eventTarget The event target of this dispatching.
 * @param {Event|{type:string}} event The original event to wrap.
 */
function Event(eventTarget, event) {
    privateData.set(this, {
        eventTarget,
        event,
        eventPhase: 2,
        currentTarget: eventTarget,
        canceled: false,
        stopped: false,
        immediateStopped: false,
        passiveListener: null,
        timeStamp: event.timeStamp || Date.now(),
    });

    // https://heycam.github.io/webidl/#Unforgeable
    Object.defineProperty(this, "isTrusted", { value: false, enumerable: true });

    // Define accessors
    const keys = Object.keys(event);
    for (let i = 0; i < keys.length; ++i) {
        const key = keys[i];
        if (!(key in this)) {
            Object.defineProperty(this, key, defineRedirectDescriptor(key));
        }
    }
}

// Should be enumerable, but class methods are not enumerable.
Event.prototype = {
    /**
     * The type of this event.
     * @type {string}
     */
    get type() {
        return pd(this).event.type
    },

    /**
     * The target of this event.
     * @type {EventTarget}
     */
    get target() {
        return pd(this).eventTarget
    },

    /**
     * The target of this event.
     * @type {EventTarget}
     */
    get currentTarget() {
        return pd(this).currentTarget
    },

    /**
     * @returns {EventTarget[]} The composed path of this event.
     */
    composedPath() {
        const currentTarget = pd(this).currentTarget;
        if (currentTarget == null) {
            return []
        }
        return [currentTarget]
    },

    /**
     * Constant of NONE.
     * @type {number}
     */
    get NONE() {
        return 0
    },

    /**
     * Constant of CAPTURING_PHASE.
     * @type {number}
     */
    get CAPTURING_PHASE() {
        return 1
    },

    /**
     * Constant of AT_TARGET.
     * @type {number}
     */
    get AT_TARGET() {
        return 2
    },

    /**
     * Constant of BUBBLING_PHASE.
     * @type {number}
     */
    get BUBBLING_PHASE() {
        return 3
    },

    /**
     * The target of this event.
     * @type {number}
     */
    get eventPhase() {
        return pd(this).eventPhase
    },

    /**
     * Stop event bubbling.
     * @returns {void}
     */
    stopPropagation() {
        const data = pd(this);

        data.stopped = true;
        if (typeof data.event.stopPropagation === "function") {
            data.event.stopPropagation();
        }
    },

    /**
     * Stop event bubbling.
     * @returns {void}
     */
    stopImmediatePropagation() {
        const data = pd(this);

        data.stopped = true;
        data.immediateStopped = true;
        if (typeof data.event.stopImmediatePropagation === "function") {
            data.event.stopImmediatePropagation();
        }
    },

    /**
     * The flag to be bubbling.
     * @type {boolean}
     */
    get bubbles() {
        return Boolean(pd(this).event.bubbles)
    },

    /**
     * The flag to be cancelable.
     * @type {boolean}
     */
    get cancelable() {
        return Boolean(pd(this).event.cancelable)
    },

    /**
     * Cancel this event.
     * @returns {void}
     */
    preventDefault() {
        setCancelFlag(pd(this));
    },

    /**
     * The flag to indicate cancellation state.
     * @type {boolean}
     */
    get defaultPrevented() {
        return pd(this).canceled
    },

    /**
     * The flag to be composed.
     * @type {boolean}
     */
    get composed() {
        return Boolean(pd(this).event.composed)
    },

    /**
     * The unix time of this event.
     * @type {number}
     */
    get timeStamp() {
        return pd(this).timeStamp
    },

    /**
     * The target of this event.
     * @type {EventTarget}
     * @deprecated
     */
    get srcElement() {
        return pd(this).eventTarget
    },

    /**
     * The flag to stop event bubbling.
     * @type {boolean}
     * @deprecated
     */
    get cancelBubble() {
        return pd(this).stopped
    },
    set cancelBubble(value) {
        if (!value) {
            return
        }
        const data = pd(this);

        data.stopped = true;
        if (typeof data.event.cancelBubble === "boolean") {
            data.event.cancelBubble = true;
        }
    },

    /**
     * The flag to indicate cancellation state.
     * @type {boolean}
     * @deprecated
     */
    get returnValue() {
        return !pd(this).canceled
    },
    set returnValue(value) {
        if (!value) {
            setCancelFlag(pd(this));
        }
    },

    /**
     * Initialize this event object. But do nothing under event dispatching.
     * @param {string} type The event type.
     * @param {boolean} [bubbles=false] The flag to be possible to bubble up.
     * @param {boolean} [cancelable=false] The flag to be possible to cancel.
     * @deprecated
     */
    initEvent() {
        // Do nothing.
    },
};

// `constructor` is not enumerable.
Object.defineProperty(Event.prototype, "constructor", {
    value: Event,
    configurable: true,
    writable: true,
});

// Ensure `event instanceof window.Event` is `true`.
if (typeof window !== "undefined" && typeof window.Event !== "undefined") {
    Object.setPrototypeOf(Event.prototype, window.Event.prototype);

    // Make association for wrappers.
    wrappers.set(window.Event.prototype, Event);
}

/**
 * Get the property descriptor to redirect a given property.
 * @param {string} key Property name to define property descriptor.
 * @returns {PropertyDescriptor} The property descriptor to redirect the property.
 * @private
 */
function defineRedirectDescriptor(key) {
    return {
        get() {
            return pd(this).event[key]
        },
        set(value) {
            pd(this).event[key] = value;
        },
        configurable: true,
        enumerable: true,
    }
}

/**
 * Get the property descriptor to call a given method property.
 * @param {string} key Property name to define property descriptor.
 * @returns {PropertyDescriptor} The property descriptor to call the method property.
 * @private
 */
function defineCallDescriptor(key) {
    return {
        value() {
            const event = pd(this).event;
            return event[key].apply(event, arguments)
        },
        configurable: true,
        enumerable: true,
    }
}

/**
 * Define new wrapper class.
 * @param {Function} BaseEvent The base wrapper class.
 * @param {Object} proto The prototype of the original event.
 * @returns {Function} The defined wrapper class.
 * @private
 */
function defineWrapper(BaseEvent, proto) {
    const keys = Object.keys(proto);
    if (keys.length === 0) {
        return BaseEvent
    }

    /** CustomEvent */
    function CustomEvent(eventTarget, event) {
        BaseEvent.call(this, eventTarget, event);
    }

    CustomEvent.prototype = Object.create(BaseEvent.prototype, {
        constructor: { value: CustomEvent, configurable: true, writable: true },
    });

    // Define accessors.
    for (let i = 0; i < keys.length; ++i) {
        const key = keys[i];
        if (!(key in BaseEvent.prototype)) {
            const descriptor = Object.getOwnPropertyDescriptor(proto, key);
            const isFunc = typeof descriptor.value === "function";
            Object.defineProperty(
                CustomEvent.prototype,
                key,
                isFunc
                    ? defineCallDescriptor(key)
                    : defineRedirectDescriptor(key)
            );
        }
    }

    return CustomEvent
}

/**
 * Get the wrapper class of a given prototype.
 * @param {Object} proto The prototype of the original event to get its wrapper.
 * @returns {Function} The wrapper class.
 * @private
 */
function getWrapper(proto) {
    if (proto == null || proto === Object.prototype) {
        return Event
    }

    let wrapper = wrappers.get(proto);
    if (wrapper == null) {
        wrapper = defineWrapper(getWrapper(Object.getPrototypeOf(proto)), proto);
        wrappers.set(proto, wrapper);
    }
    return wrapper
}

/**
 * Wrap a given event to management a dispatching.
 * @param {EventTarget} eventTarget The event target of this dispatching.
 * @param {Object} event The event to wrap.
 * @returns {Event} The wrapper instance.
 * @private
 */
function wrapEvent(eventTarget, event) {
    const Wrapper = getWrapper(Object.getPrototypeOf(event));
    return new Wrapper(eventTarget, event)
}

/**
 * Get the immediateStopped flag of a given event.
 * @param {Event} event The event to get.
 * @returns {boolean} The flag to stop propagation immediately.
 * @private
 */
function isStopped(event) {
    return pd(event).immediateStopped
}

/**
 * Set the current event phase of a given event.
 * @param {Event} event The event to set current target.
 * @param {number} eventPhase New event phase.
 * @returns {void}
 * @private
 */
function setEventPhase(event, eventPhase) {
    pd(event).eventPhase = eventPhase;
}

/**
 * Set the current target of a given event.
 * @param {Event} event The event to set current target.
 * @param {EventTarget|null} currentTarget New current target.
 * @returns {void}
 * @private
 */
function setCurrentTarget(event, currentTarget) {
    pd(event).currentTarget = currentTarget;
}

/**
 * Set a passive listener of a given event.
 * @param {Event} event The event to set current target.
 * @param {Function|null} passiveListener New passive listener.
 * @returns {void}
 * @private
 */
function setPassiveListener(event, passiveListener) {
    pd(event).passiveListener = passiveListener;
}

/**
 * @typedef {object} ListenerNode
 * @property {Function} listener
 * @property {1|2|3} listenerType
 * @property {boolean} passive
 * @property {boolean} once
 * @property {ListenerNode|null} next
 * @private
 */

/**
 * @type {WeakMap<object, Map<string, ListenerNode>>}
 * @private
 */
const listenersMap = new WeakMap();

// Listener types
const CAPTURE = 1;
const BUBBLE = 2;
const ATTRIBUTE = 3;

/**
 * Check whether a given value is an object or not.
 * @param {any} x The value to check.
 * @returns {boolean} `true` if the value is an object.
 */
function isObject(x) {
    return x !== null && typeof x === "object" //eslint-disable-line no-restricted-syntax
}

/**
 * Get listeners.
 * @param {EventTarget} eventTarget The event target to get.
 * @returns {Map<string, ListenerNode>} The listeners.
 * @private
 */
function getListeners(eventTarget) {
    const listeners = listenersMap.get(eventTarget);
    if (listeners == null) {
        throw new TypeError(
            "'this' is expected an EventTarget object, but got another value."
        )
    }
    return listeners
}

/**
 * Get the property descriptor for the event attribute of a given event.
 * @param {string} eventName The event name to get property descriptor.
 * @returns {PropertyDescriptor} The property descriptor.
 * @private
 */
function defineEventAttributeDescriptor(eventName) {
    return {
        get() {
            const listeners = getListeners(this);
            let node = listeners.get(eventName);
            while (node != null) {
                if (node.listenerType === ATTRIBUTE) {
                    return node.listener
                }
                node = node.next;
            }
            return null
        },

        set(listener) {
            if (typeof listener !== "function" && !isObject(listener)) {
                listener = null; // eslint-disable-line no-param-reassign
            }
            const listeners = getListeners(this);

            // Traverse to the tail while removing old value.
            let prev = null;
            let node = listeners.get(eventName);
            while (node != null) {
                if (node.listenerType === ATTRIBUTE) {
                    // Remove old value.
                    if (prev !== null) {
                        prev.next = node.next;
                    } else if (node.next !== null) {
                        listeners.set(eventName, node.next);
                    } else {
                        listeners.delete(eventName);
                    }
                } else {
                    prev = node;
                }

                node = node.next;
            }

            // Add new value.
            if (listener !== null) {
                const newNode = {
                    listener,
                    listenerType: ATTRIBUTE,
                    passive: false,
                    once: false,
                    next: null,
                };
                if (prev === null) {
                    listeners.set(eventName, newNode);
                } else {
                    prev.next = newNode;
                }
            }
        },
        configurable: true,
        enumerable: true,
    }
}

/**
 * Define an event attribute (e.g. `eventTarget.onclick`).
 * @param {Object} eventTargetPrototype The event target prototype to define an event attrbite.
 * @param {string} eventName The event name to define.
 * @returns {void}
 */
function defineEventAttribute(eventTargetPrototype, eventName) {
    Object.defineProperty(
        eventTargetPrototype,
        `on${eventName}`,
        defineEventAttributeDescriptor(eventName)
    );
}

/**
 * Define a custom EventTarget with event attributes.
 * @param {string[]} eventNames Event names for event attributes.
 * @returns {EventTarget} The custom EventTarget.
 * @private
 */
function defineCustomEventTarget(eventNames) {
    /** CustomEventTarget */
    function CustomEventTarget() {
        EventTarget.call(this);
    }

    CustomEventTarget.prototype = Object.create(EventTarget.prototype, {
        constructor: {
            value: CustomEventTarget,
            configurable: true,
            writable: true,
        },
    });

    for (let i = 0; i < eventNames.length; ++i) {
        defineEventAttribute(CustomEventTarget.prototype, eventNames[i]);
    }

    return CustomEventTarget
}

/**
 * EventTarget.
 *
 * - This is constructor if no arguments.
 * - This is a function which returns a CustomEventTarget constructor if there are arguments.
 *
 * For example:
 *
 *     class A extends EventTarget {}
 *     class B extends EventTarget("message") {}
 *     class C extends EventTarget("message", "error") {}
 *     class D extends EventTarget(["message", "error"]) {}
 */
function EventTarget() {
    /*eslint-disable consistent-return */
    if (this instanceof EventTarget) {
        listenersMap.set(this, new Map());
        return
    }
    if (arguments.length === 1 && Array.isArray(arguments[0])) {
        return defineCustomEventTarget(arguments[0])
    }
    if (arguments.length > 0) {
        const types = new Array(arguments.length);
        for (let i = 0; i < arguments.length; ++i) {
            types[i] = arguments[i];
        }
        return defineCustomEventTarget(types)
    }
    throw new TypeError("Cannot call a class as a function")
    /*eslint-enable consistent-return */
}

// Should be enumerable, but class methods are not enumerable.
EventTarget.prototype = {
    /**
     * Add a given listener to this event target.
     * @param {string} eventName The event name to add.
     * @param {Function} listener The listener to add.
     * @param {boolean|{capture?:boolean,passive?:boolean,once?:boolean}} [options] The options for this listener.
     * @returns {void}
     */
    addEventListener(eventName, listener, options) {
        if (listener == null) {
            return
        }
        if (typeof listener !== "function" && !isObject(listener)) {
            throw new TypeError("'listener' should be a function or an object.")
        }

        const listeners = getListeners(this);
        const optionsIsObj = isObject(options);
        const capture = optionsIsObj
            ? Boolean(options.capture)
            : Boolean(options);
        const listenerType = capture ? CAPTURE : BUBBLE;
        const newNode = {
            listener,
            listenerType,
            passive: optionsIsObj && Boolean(options.passive),
            once: optionsIsObj && Boolean(options.once),
            next: null,
        };

        // Set it as the first node if the first node is null.
        let node = listeners.get(eventName);
        if (node === undefined) {
            listeners.set(eventName, newNode);
            return
        }

        // Traverse to the tail while checking duplication..
        let prev = null;
        while (node != null) {
            if (
                node.listener === listener &&
                node.listenerType === listenerType
            ) {
                // Should ignore duplication.
                return
            }
            prev = node;
            node = node.next;
        }

        // Add it.
        prev.next = newNode;
    },

    /**
     * Remove a given listener from this event target.
     * @param {string} eventName The event name to remove.
     * @param {Function} listener The listener to remove.
     * @param {boolean|{capture?:boolean,passive?:boolean,once?:boolean}} [options] The options for this listener.
     * @returns {void}
     */
    removeEventListener(eventName, listener, options) {
        if (listener == null) {
            return
        }

        const listeners = getListeners(this);
        const capture = isObject(options)
            ? Boolean(options.capture)
            : Boolean(options);
        const listenerType = capture ? CAPTURE : BUBBLE;

        let prev = null;
        let node = listeners.get(eventName);
        while (node != null) {
            if (
                node.listener === listener &&
                node.listenerType === listenerType
            ) {
                if (prev !== null) {
                    prev.next = node.next;
                } else if (node.next !== null) {
                    listeners.set(eventName, node.next);
                } else {
                    listeners.delete(eventName);
                }
                return
            }

            prev = node;
            node = node.next;
        }
    },

    /**
     * Dispatch a given event.
     * @param {Event|{type:string}} event The event to dispatch.
     * @returns {boolean} `false` if canceled.
     */
    dispatchEvent(event) {
        if (event == null || typeof event.type !== "string") {
            throw new TypeError('"event.type" should be a string.')
        }

        // If listeners aren't registered, terminate.
        const listeners = getListeners(this);
        const eventName = event.type;
        let node = listeners.get(eventName);
        if (node == null) {
            return true
        }

        // Since we cannot rewrite several properties, so wrap object.
        const wrappedEvent = wrapEvent(this, event);

        // This doesn't process capturing phase and bubbling phase.
        // This isn't participating in a tree.
        let prev = null;
        while (node != null) {
            // Remove this listener if it's once
            if (node.once) {
                if (prev !== null) {
                    prev.next = node.next;
                } else if (node.next !== null) {
                    listeners.set(eventName, node.next);
                } else {
                    listeners.delete(eventName);
                }
            } else {
                prev = node;
            }

            // Call this listener
            setPassiveListener(
                wrappedEvent,
                node.passive ? node.listener : null
            );
            if (typeof node.listener === "function") {
                try {
                    node.listener.call(this, wrappedEvent);
                } catch (err) {
                    if (
                        typeof console !== "undefined" &&
                        typeof console.error === "function"
                    ) {
                        console.error(err);
                    }
                }
            } else if (
                node.listenerType !== ATTRIBUTE &&
                typeof node.listener.handleEvent === "function"
            ) {
                node.listener.handleEvent(wrappedEvent);
            }

            // Break if `event.stopImmediatePropagation` was called.
            if (isStopped(wrappedEvent)) {
                break
            }

            node = node.next;
        }
        setPassiveListener(wrappedEvent, null);
        setEventPhase(wrappedEvent, 0);
        setCurrentTarget(wrappedEvent, null);

        return !wrappedEvent.defaultPrevented
    },
};

// `constructor` is not enumerable.
Object.defineProperty(EventTarget.prototype, "constructor", {
    value: EventTarget,
    configurable: true,
    writable: true,
});

// Ensure `eventTarget instanceof window.EventTarget` is `true`.
if (
    typeof window !== "undefined" &&
    typeof window.EventTarget !== "undefined"
) {
    Object.setPrototypeOf(EventTarget.prototype, window.EventTarget.prototype);
}

/* harmony default export */ var event_target_shim = (EventTarget);


// CONCATENATED MODULE: ./src/index.js
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// A wrapper that combines a WebSocket to the channelserver
// with some client-side encryption for securing the channel.
//
// This code is responsible for the event handling and the consumer API.
// All the details of encrypting the messages are delegated to`./tlsconnection.js`.







const CLOSE_FLUSH_BUFFER_INTERVAL_MS = 200;
const CLOSE_FLUSH_BUFFER_MAX_TRIES = 5;

class src_PairingChannel extends EventTarget {
  constructor(channelId, channelKey, socket, connection) {
    super();
    this._channelId = channelId;
    this._channelKey = channelKey;
    this._socket = socket;
    this._connection = connection;
    this._selfClosed = false;
    this._peerClosed = false;
    this._setupListeners();
  }

  /**
   * Create a new pairing channel.
   *
   * This will open a channel on the channelserver, and generate a random client-side
   * encryption key. When the promise resolves, `this.channelId` and `this.channelKey`
   * can be transferred to another client to allow it to securely connect to the channel.
   *
   * @returns Promise<PairingChannel>
   */
  static create(channelServerURI) {
    const wsURI = new URL('/v1/ws/', channelServerURI).href;
    const channelKey = crypto.getRandomValues(new Uint8Array(32));
    // The one who creates the channel plays the role of 'server' in the underlying TLS exchange.
    return this._makePairingChannel(wsURI, tlsconnection_ServerConnection, channelKey);
  }

  /**
   * Connect to an existing pairing channel.
   *
   * This will connect to a channel on the channelserver previously established by
   * another client calling `create`. The `channelId` and `channelKey` must have been
   * obtained via some out-of-band mechanism (such as by scanning from a QR code).
   *
   * @returns Promise<PairingChannel>
   */
  static connect(channelServerURI, channelId, channelKey) {
    const wsURI = new URL(`/v1/ws/${channelId}`, channelServerURI).href;
    // The one who connects to an existing channel plays the role of 'client'
    // in the underlying TLS exchange.
    return this._makePairingChannel(wsURI, tlsconnection_ClientConnection, channelKey);
  }

  static _makePairingChannel(wsUri, ConnectionClass, psk) {
    const socket = new WebSocket(wsUri);
    return new Promise((resolve, reject) => {
      // eslint-disable-next-line prefer-const
      let stopListening;
      const onConnectionError = async () => {
        stopListening();
        reject(new Error('Error while creating the pairing channel'));
      };
      const onFirstMessage = async event => {
        stopListening();
        try {
          // The channelserver echos back the channel id, and we use it as an
          // additional input to the TLS handshake via the "psk id" field.
          const {channelid: channelId} = JSON.parse(event.data);
          const pskId = utf8ToBytes(channelId);
          const connection = await ConnectionClass.create(psk, pskId, data => {
            // Send data by forwarding it via the channelserver websocket.
            // The TLS connection gives us `data` as raw bytes, but channelserver
            // expects b64urlsafe strings, because it wraps them in a JSON object envelope.
            socket.send(bytesToBase64url(data));
          });
          const instance = new this(channelId, psk, socket, connection);
          resolve(instance);
        } catch (err) {
          reject(err);
        }
      };
      stopListening = () => {
        socket.removeEventListener('close', onConnectionError);
        socket.removeEventListener('error', onConnectionError);
        socket.removeEventListener('message', onFirstMessage);
      };
      socket.addEventListener('close', onConnectionError);
      socket.addEventListener('error', onConnectionError);
      socket.addEventListener('message', onFirstMessage);
    });
  }

  _setupListeners() {
    this._socket.addEventListener('message', async event => {
      try {
        // When we receive data from the channelserver, pump it through the TLS connection
        // to decrypt it, then echo it back out to consumers as an event.
        const channelServerEnvelope = JSON.parse(event.data);
        const payload = await this._connection.recv(base64urlToBytes(channelServerEnvelope.message));
        if (payload !== null) {
          const data = JSON.parse(bytesToUtf8(payload));
          this.dispatchEvent(new CustomEvent('message', {
            detail: {
              data,
              sender: channelServerEnvelope.sender,
            },
          }));
        }
      } catch (error) {
        let event;
        // The underlying TLS connection will signal a clean shutdown of the channel
        // by throwing a special error, because it doesn't really have a better
        // signally mechanism available.
        if (error instanceof TLSCloseNotify) {
          this._peerClosed = true;
          if (this._selfClosed) {
            this._shutdown();
          }
          event = new CustomEvent('close');
        } else {
          event = new CustomEvent('error', {
            detail: {
              error,
            }
          });
        }
        this.dispatchEvent(event);
      }
    });
    // Relay the WebSocket events.
    this._socket.addEventListener('error', () => {
      this._shutdown();
      // The dispatched event that we receive has no useful information.
      this.dispatchEvent(new CustomEvent('error', {
        detail: {
          error: new Error('WebSocket error.'),
        },
      }));
    });
    // In TLS, the peer has to explicitly send a close notification,
    // which we dispatch above.  Unexpected socket close is an error.
    this._socket.addEventListener('close', () => {
      this._shutdown();
      if (! this._peerClosed) {
        this.dispatchEvent(new CustomEvent('error', {
          detail: {
            error: new Error('WebSocket unexpectedly closed'),
          }
        }));
      }
    });
  }

  /**
   * @param {Object} data
   */
  async send(data) {
    const payload = utf8ToBytes(JSON.stringify(data));
    await this._connection.send(payload);
  }

  async close() {
    this._selfClosed = true;
    await this._connection.close();
    try {
      // Ensure all queued bytes have been sent before closing the connection.
      let tries = 0;
      while (this._socket.bufferedAmount > 0) {
        if (++tries > CLOSE_FLUSH_BUFFER_MAX_TRIES) {
          throw new Error('Could not flush the outgoing buffer in time.');
        }
        await new Promise(res => setTimeout(res, CLOSE_FLUSH_BUFFER_INTERVAL_MS));
      }
    } finally {
      // If the peer hasn't closed, we might still receive some data.
      if (this._peerClosed) {
        this._shutdown();
      }
    }
  }

  _shutdown() {
    if (this._socket) {
      this._socket.close();
      this._socket = null;
      this._connection = null;
    }
  }

  get closed() {
    return (! this._socket) || (this._socket.readyState === 3);
  }

  get channelId() {
    return this._channelId;
  }

  get channelKey() {
    return this._channelKey;
  }
}

// Re-export helpful utilities for calling code to use.


// For running tests using the built bundle,
// expose a bunch of implementation details.







const _internals = {
  arrayToBytes: arrayToBytes,
  BufferReader: utils_BufferReader,
  BufferWriter: utils_BufferWriter,
  bytesAreEqual: bytesAreEqual,
  bytesToHex: bytesToHex,
  bytesToUtf8: bytesToUtf8,
  ClientConnection: tlsconnection_ClientConnection,
  Connection: tlsconnection_Connection,
  DecryptionState: recordlayer_DecryptionState,
  EncryptedExtensions: EncryptedExtensions,
  EncryptionState: recordlayer_EncryptionState,
  Finished: messages_Finished,
  HASH_LENGTH: HASH_LENGTH,
  hexToBytes: hexToBytes,
  hkdfExpand: hkdfExpand,
  KeySchedule: keyschedule_KeySchedule,
  NewSessionTicket: messages_NewSessionTicket,
  RecordLayer: recordlayer_RecordLayer,
  ServerConnection: tlsconnection_ServerConnection,
  utf8ToBytes: utf8ToBytes,
  zeros: zeros,
};


/***/ })
/******/ ])["PairingChannel"];
PK
!<c�!Wpp!modules/FxAccountsProfile.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Firefox Accounts Profile helper.
 *
 * This class abstracts interaction with the profile server for an account.
 * It will handle things like fetching profile data, listening for updates to
 * the user's profile in open browser tabs, and cacheing/invalidating profile data.
 */

import {
  ON_PROFILE_CHANGE_NOTIFICATION,
  log,
} from "resource://gre/modules/FxAccountsCommon.sys.mjs";

import { getFxAccountsSingleton } from "resource://gre/modules/FxAccounts.sys.mjs";

const fxAccounts = getFxAccountsSingleton();

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  FxAccountsProfileClient:
    "resource://gre/modules/FxAccountsProfileClient.sys.mjs",
});

export var FxAccountsProfile = function (options = {}) {
  this._currentFetchPromise = null;
  this._cachedAt = 0; // when we saved the cached version.
  this._isNotifying = false; // are we sending a notification?
  this.fxai = options.fxai || fxAccounts._internal;
  this.client =
    options.profileClient ||
    new lazy.FxAccountsProfileClient({
      fxai: this.fxai,
      serverURL: options.profileServerUrl,
    });

  // An observer to invalidate our _cachedAt optimization. We use a weak-ref
  // just incase this.tearDown isn't called in some cases.
  Services.obs.addObserver(this, ON_PROFILE_CHANGE_NOTIFICATION, true);
  // for testing
  if (options.channel) {
    this.channel = options.channel;
  }
};

FxAccountsProfile.prototype = {
  // If we get subsequent requests for a profile within this period, don't bother
  // making another request to determine if it is fresh or not.
  PROFILE_FRESHNESS_THRESHOLD: 120000, // 2 minutes

  observe(subject, topic) {
    // If we get a profile change notification from our webchannel it means
    // the user has just changed their profile via the web, so we want to
    // ignore our "freshness threshold"
    if (topic == ON_PROFILE_CHANGE_NOTIFICATION && !this._isNotifying) {
      log.debug("FxAccountsProfile observed profile change");
      this._cachedAt = 0;
    }
  },

  tearDown() {
    this.fxai = null;
    this.client = null;
    Services.obs.removeObserver(this, ON_PROFILE_CHANGE_NOTIFICATION);
  },

  _notifyProfileChange(uid) {
    this._isNotifying = true;
    Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION, uid);
    this._isNotifying = false;
  },

  // Cache fetched data and send out a notification so that UI can update.
  _cacheProfile(response) {
    return this.fxai.withCurrentAccountState(async state => {
      const profile = response.body;
      const userData = await state.getUserAccountData();
      if (profile.uid != userData.uid) {
        throw new Error(
          "The fetched profile does not correspond with the current account."
        );
      }
      let profileCache = {
        profile,
        etag: response.etag,
      };
      await state.updateUserAccountData({ profileCache });
      if (profile.email != userData.email) {
        await this.fxai._handleEmailUpdated(profile.email);
      }
      log.debug("notifying profile changed for user ${uid}", userData);
      this._notifyProfileChange(userData.uid);
      return profile;
    });
  },

  async _getProfileCache() {
    let data = await this.fxai.currentAccountState.getUserAccountData([
      "profileCache",
    ]);
    return data ? data.profileCache : null;
  },

  async _fetchAndCacheProfileInternal() {
    try {
      const profileCache = await this._getProfileCache();
      const etag = profileCache ? profileCache.etag : null;
      let response;
      try {
        response = await this.client.fetchProfile(etag);
      } catch (err) {
        await this.fxai._handleTokenError(err);
        // _handleTokenError always re-throws.
        throw new Error("not reached!");
      }

      // response may be null if the profile was not modified (same ETag).
      if (!response) {
        return null;
      }
      return await this._cacheProfile(response);
    } finally {
      this._cachedAt = Date.now();
      this._currentFetchPromise = null;
    }
  },

  _fetchAndCacheProfile() {
    if (!this._currentFetchPromise) {
      this._currentFetchPromise = this._fetchAndCacheProfileInternal();
    }
    return this._currentFetchPromise;
  },

  // Returns cached data right away if available, otherwise returns null - if
  // it returns null, or if the profile is possibly stale, it attempts to
  // fetch the latest profile data in the background. After data is fetched a
  // notification will be sent out if the profile has changed.
  async getProfile() {
    const profileCache = await this._getProfileCache();
    if (!profileCache) {
      // fetch and cache it in the background.
      this._fetchAndCacheProfile().catch(err => {
        log.error("Background refresh of initial profile failed", err);
      });
      return null;
    }
    if (Date.now() > this._cachedAt + this.PROFILE_FRESHNESS_THRESHOLD) {
      // Note that _fetchAndCacheProfile isn't returned, so continues
      // in the background.
      this._fetchAndCacheProfile().catch(err => {
        log.error("Background refresh of profile failed", err);
      });
    } else {
      log.trace("not checking freshness of profile as it remains recent");
    }
    return profileCache.profile;
  },

  // Get the user's profile data, fetching from the network if necessary.
  // Most callers should instead use `getProfile()`; this methods exists to support
  // callers who need to await the underlying network request.
  async ensureProfile({ staleOk = false, forceFresh = false } = {}) {
    if (staleOk && forceFresh) {
      throw new Error("contradictory options specified");
    }
    const profileCache = await this._getProfileCache();
    if (
      forceFresh ||
      !profileCache ||
      (Date.now() > this._cachedAt + this.PROFILE_FRESHNESS_THRESHOLD &&
        !staleOk)
    ) {
      const profile = await this._fetchAndCacheProfile().catch(err => {
        log.error("Background refresh of profile failed", err);
      });
      if (profile) {
        return profile;
      }
    }
    log.trace("not checking freshness of profile as it remains recent");
    return profileCache ? profileCache.profile : null;
  },

  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
  ]),
};
PK
!<���j  'modules/FxAccountsProfileClient.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * A client to fetch profile information for a Firefox Account.
 */

import {
  ERRNO_NETWORK,
  ERRNO_PARSE,
  ERRNO_UNKNOWN_ERROR,
  ERROR_CODE_METHOD_NOT_ALLOWED,
  ERROR_MSG_METHOD_NOT_ALLOWED,
  ERROR_NETWORK,
  ERROR_PARSE,
  ERROR_UNKNOWN,
  log,
  SCOPE_PROFILE,
  SCOPE_PROFILE_WRITE,
} from "resource://gre/modules/FxAccountsCommon.sys.mjs";

import { getFxAccountsSingleton } from "resource://gre/modules/FxAccounts.sys.mjs";

const fxAccounts = getFxAccountsSingleton();
import { RESTRequest } from "resource://services-common/rest.sys.mjs";

/**
 * Create a new FxAccountsProfileClient to be able to fetch Firefox Account profile information.
 *
 * @param {Object} options Options
 *   @param {String} options.serverURL
 *   The URL of the profile server to query.
 *   Example: https://profile.accounts.firefox.com/v1
 *   @param {String} options.token
 *   The bearer token to access the profile server
 * @constructor
 */
export var FxAccountsProfileClient = function (options) {
  if (!options || !options.serverURL) {
    throw new Error("Missing 'serverURL' configuration option");
  }

  this.fxai = options.fxai || fxAccounts._internal;

  try {
    this.serverURL = new URL(options.serverURL);
  } catch (e) {
    throw new Error("Invalid 'serverURL'");
  }
  log.debug("FxAccountsProfileClient: Initialized");
};

FxAccountsProfileClient.prototype = {
  /**
   * {nsIURI}
   * The server to fetch profile information from.
   */
  serverURL: null,

  /**
   * Interface for making remote requests.
   */
  _Request: RESTRequest,

  /**
   * Remote request helper which abstracts authentication away.
   *
   * @param {String} path
   *        Profile server path, i.e "/profile".
   * @param {String} [method]
   *        Type of request, e.g. "GET".
   * @param {String} [etag]
   *        Optional ETag used for caching purposes.
   * @param {Object} [body]
   *        Optional request body, to be sent as application/json.
   * @return Promise
   *         Resolves: {body: Object, etag: Object} Successful response from the Profile server.
   *         Rejects: {FxAccountsProfileClientError} Profile client error.
   * @private
   */
  async _createRequest(path, method = "GET", etag = null, body = null) {
    method = method.toUpperCase();
    let token = await this._getTokenForRequest(method);
    try {
      return await this._rawRequest(path, method, token, etag, body);
    } catch (ex) {
      if (!(ex instanceof FxAccountsProfileClientError) || ex.code != 401) {
        throw ex;
      }
      // it's an auth error - assume our token expired and retry.
      log.info(
        "Fetching the profile returned a 401 - revoking our token and retrying"
      );
      await this.fxai.removeCachedOAuthToken({ token });
      token = await this._getTokenForRequest(method);
      // and try with the new token - if that also fails then we fail after
      // revoking the token.
      try {
        return await this._rawRequest(path, method, token, etag, body);
      } catch (ex) {
        if (!(ex instanceof FxAccountsProfileClientError) || ex.code != 401) {
          throw ex;
        }
        log.info(
          "Retry fetching the profile still returned a 401 - revoking our token and failing"
        );
        await this.fxai.removeCachedOAuthToken({ token });
        throw ex;
      }
    }
  },

  /**
   * Helper to get an OAuth token for a request.
   *
   * OAuth tokens are cached, so it's fine to call this for each request.
   *
   * @param {String} [method]
   *        Type of request, i.e "GET".
   * @return Promise
   *         Resolves: Object containing "scope", "token" and "key" properties
   *         Rejects: {FxAccountsProfileClientError} Profile client error.
   * @private
   */
  async _getTokenForRequest(method) {
    let scope = SCOPE_PROFILE;
    if (method === "POST") {
      scope = SCOPE_PROFILE_WRITE;
    }
    return this.fxai.getOAuthToken({ scope });
  },

  /**
   * Remote "raw" request helper - doesn't handle auth errors and tokens.
   *
   * @param {String} path
   *        Profile server path, i.e "/profile".
   * @param {String} method
   *        Type of request, i.e "GET".
   * @param {String} token
   * @param {String} etag
   * @param {Object} payload
   *        The payload of the request, if any.
   * @return Promise
   *         Resolves: {body: Object, etag: Object} Successful response from the Profile server
                        or null if 304 is hit (same ETag).
   *         Rejects: {FxAccountsProfileClientError} Profile client error.
   * @private
   */
  async _rawRequest(path, method, token, etag = null, payload = null) {
    let profileDataUrl = this.serverURL + path;
    let request = new this._Request(profileDataUrl);

    request.setHeader("Authorization", "Bearer " + token);
    request.setHeader("Accept", "application/json");
    if (etag) {
      request.setHeader("If-None-Match", etag);
    }

    if (method != "GET" && method != "POST") {
      // method not supported
      throw new FxAccountsProfileClientError({
        error: ERROR_NETWORK,
        errno: ERRNO_NETWORK,
        code: ERROR_CODE_METHOD_NOT_ALLOWED,
        message: ERROR_MSG_METHOD_NOT_ALLOWED,
      });
    }
    try {
      await request.dispatch(method, payload);
    } catch (error) {
      throw new FxAccountsProfileClientError({
        error: ERROR_NETWORK,
        errno: ERRNO_NETWORK,
        message: error.toString(),
      });
    }

    let body = null;
    try {
      if (request.response.status == 304) {
        return null;
      }
      body = JSON.parse(request.response.body);
    } catch (e) {
      throw new FxAccountsProfileClientError({
        error: ERROR_PARSE,
        errno: ERRNO_PARSE,
        code: request.response.status,
        message: request.response.body,
      });
    }

    // "response.success" means status code is 200
    if (!request.response.success) {
      throw new FxAccountsProfileClientError({
        error: body.error || ERROR_UNKNOWN,
        errno: body.errno || ERRNO_UNKNOWN_ERROR,
        code: request.response.status,
        message: body.message || body,
      });
    }
    return {
      body,
      etag: request.response.headers.etag,
    };
  },

  /**
   * Retrieve user's profile from the server
   *
   * @param {String} [etag]
   *        Optional ETag used for caching purposes. (may generate a 304 exception)
   * @return Promise
   *         Resolves: {body: Object, etag: Object} Successful response from the '/profile' endpoint.
   *         Rejects: {FxAccountsProfileClientError} profile client error.
   */
  fetchProfile(etag) {
    log.debug("FxAccountsProfileClient: Requested profile");
    return this._createRequest("/profile", "GET", etag);
  },
};

/**
 * Normalized profile client errors
 * @param {Object} [details]
 *        Error details object
 *   @param {number} [details.code]
 *          Error code
 *   @param {number} [details.errno]
 *          Error number
 *   @param {String} [details.error]
 *          Error description
 *   @param {String|null} [details.message]
 *          Error message
 * @constructor
 */
export var FxAccountsProfileClientError = function (details) {
  details = details || {};

  this.name = "FxAccountsProfileClientError";
  this.code = details.code || null;
  this.errno = details.errno || ERRNO_UNKNOWN_ERROR;
  this.error = details.error || ERROR_UNKNOWN;
  this.message = details.message || null;
};

/**
 * Returns error object properties
 *
 * @returns {{name: *, code: *, errno: *, error: *, message: *}}
 * @private
 */
FxAccountsProfileClientError.prototype._toStringFields = function () {
  return {
    name: this.name,
    code: this.code,
    errno: this.errno,
    error: this.error,
    message: this.message,
  };
};

/**
 * String representation of a profile client error
 *
 * @returns {String}
 */
FxAccountsProfileClientError.prototype.toString = function () {
  return this.name + "(" + JSON.stringify(this._toStringFields()) + ")";
};
PK
!<��s''modules/FxAccountsPush.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Async } from "resource://services-common/async.sys.mjs";

import {
  FXA_PUSH_SCOPE_ACCOUNT_UPDATE,
  ONLOGOUT_NOTIFICATION,
  ON_ACCOUNT_DESTROYED_NOTIFICATION,
  ON_COLLECTION_CHANGED_NOTIFICATION,
  ON_COMMAND_RECEIVED_NOTIFICATION,
  ON_DEVICE_CONNECTED_NOTIFICATION,
  ON_DEVICE_DISCONNECTED_NOTIFICATION,
  ON_PASSWORD_CHANGED_NOTIFICATION,
  ON_PASSWORD_RESET_NOTIFICATION,
  ON_PROFILE_CHANGE_NOTIFICATION,
  ON_PROFILE_UPDATED_NOTIFICATION,
  ON_VERIFY_LOGIN_NOTIFICATION,
  log,
} from "resource://gre/modules/FxAccountsCommon.sys.mjs";

/**
 * FxAccountsPushService manages Push notifications for Firefox Accounts in the browser
 *
 * @param [options]
 *        Object, custom options that used for testing
 * @constructor
 */
export function FxAccountsPushService(options = {}) {
  this.log = log;

  if (options.log) {
    // allow custom log for testing purposes
    this.log = options.log;
  }

  this.log.debug("FxAccountsPush loading service");
  this.wrappedJSObject = this;
  this.initialize(options);
}

FxAccountsPushService.prototype = {
  /**
   * Helps only initialize observers once.
   */
  _initialized: false,
  /**
   * Instance of the nsIPushService or a mocked object.
   */
  pushService: null,
  /**
   * Instance of FxAccountsInternal or a mocked object.
   */
  fxai: null,
  /**
   * Component ID of this service, helps register this component.
   */
  classID: Components.ID("{1b7db999-2ecd-4abf-bb95-a726896798ca}"),
  /**
   * Register used interfaces in this service
   */
  QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
  /**
   * Initialize the service and register all the required observers.
   *
   * @param [options]
   */
  initialize(options) {
    if (this._initialized) {
      return false;
    }

    this._initialized = true;

    if (options.pushService) {
      this.pushService = options.pushService;
    } else {
      this.pushService = Cc["@mozilla.org/push/Service;1"].getService(
        Ci.nsIPushService
      );
    }

    if (options.fxai) {
      this.fxai = options.fxai;
    } else {
      const { getFxAccountsSingleton } = ChromeUtils.importESModule(
        "resource://gre/modules/FxAccounts.sys.mjs"
      );
      const fxAccounts = getFxAccountsSingleton();
      this.fxai = fxAccounts._internal;
    }

    this.asyncObserver = Async.asyncObserver(this, this.log);
    // We use an async observer because a device waking up can
    // observe multiple "Send Tab received" push notifications at the same time.
    // The way these notifications are handled is as follows:
    // Read index from storage, make network request, update the index.
    // You can imagine what happens when multiple calls race: we load
    // the same index multiple times and receive the same exact tabs, multiple times.
    // The async observer will ensure we make these network requests serially.
    Services.obs.addObserver(this.asyncObserver, this.pushService.pushTopic);
    Services.obs.addObserver(
      this.asyncObserver,
      this.pushService.subscriptionChangeTopic
    );
    Services.obs.addObserver(this.asyncObserver, ONLOGOUT_NOTIFICATION);

    this.log.debug("FxAccountsPush initialized");
    return true;
  },
  /**
   * Registers a new endpoint with the Push Server
   *
   * @returns {Promise}
   *          Promise always resolves with a subscription or a null if failed to subscribe.
   */
  registerPushEndpoint() {
    this.log.trace("FxAccountsPush registerPushEndpoint");

    return new Promise(resolve => {
      this.pushService.subscribe(
        FXA_PUSH_SCOPE_ACCOUNT_UPDATE,
        Services.scriptSecurityManager.getSystemPrincipal(),
        (result, subscription) => {
          if (Components.isSuccessCode(result)) {
            this.log.debug("FxAccountsPush got subscription");
            resolve(subscription);
          } else {
            this.log.warn("FxAccountsPush failed to subscribe", result);
            resolve(null);
          }
        }
      );
    });
  },
  /**
   * Async observer interface to listen to push messages, changes and logout.
   *
   * @param subject
   * @param topic
   * @param data
   * @returns {Promise}
   */
  async observe(subject, topic, data) {
    try {
      this.log.trace(
        `observed topic=${topic}, data=${data}, subject=${subject}`
      );
      switch (topic) {
        case this.pushService.pushTopic:
          if (data === FXA_PUSH_SCOPE_ACCOUNT_UPDATE) {
            let message = subject.QueryInterface(Ci.nsIPushMessage);
            await this._onPushMessage(message);
          }
          break;
        case this.pushService.subscriptionChangeTopic:
          if (data === FXA_PUSH_SCOPE_ACCOUNT_UPDATE) {
            await this._onPushSubscriptionChange();
          }
          break;
        case ONLOGOUT_NOTIFICATION:
          // user signed out, we need to stop polling the Push Server
          await this.unsubscribe();
          break;
      }
    } catch (err) {
      this.log.error(err);
    }
  },

  /**
   * Fired when the Push server sends a notification.
   *
   * @private
   * @returns {Promise}
   */
  async _onPushMessage(message) {
    this.log.trace("FxAccountsPushService _onPushMessage");
    if (!message.data) {
      // Use the empty signal to check the verification state of the account right away
      this.log.debug("empty push message - checking account status");
      this.fxai.checkVerificationStatus();
      return;
    }
    let payload = message.data.json();
    this.log.debug(`push command: ${payload.command}`);
    switch (payload.command) {
      case ON_COMMAND_RECEIVED_NOTIFICATION:
        await this.fxai.commands.pollDeviceCommands(payload.data.index);
        break;
      case ON_DEVICE_CONNECTED_NOTIFICATION:
        Services.obs.notifyObservers(
          null,
          ON_DEVICE_CONNECTED_NOTIFICATION,
          payload.data.deviceName
        );
        break;
      case ON_DEVICE_DISCONNECTED_NOTIFICATION:
        this.fxai._handleDeviceDisconnection(payload.data.id);
        return;
      case ON_PROFILE_UPDATED_NOTIFICATION:
        // We already have a "profile updated" notification sent via WebChannel,
        // let's just re-use that.
        Services.obs.notifyObservers(null, ON_PROFILE_CHANGE_NOTIFICATION);
        return;
      case ON_PASSWORD_CHANGED_NOTIFICATION:
      case ON_PASSWORD_RESET_NOTIFICATION:
        this._onPasswordChanged();
        return;
      case ON_ACCOUNT_DESTROYED_NOTIFICATION:
        this.fxai._handleAccountDestroyed(payload.data.uid);
        return;
      case ON_COLLECTION_CHANGED_NOTIFICATION:
        Services.obs.notifyObservers(
          null,
          ON_COLLECTION_CHANGED_NOTIFICATION,
          payload.data.collections
        );
        return;
      case ON_VERIFY_LOGIN_NOTIFICATION:
        Services.obs.notifyObservers(
          null,
          ON_VERIFY_LOGIN_NOTIFICATION,
          JSON.stringify(payload.data)
        );
        break;
      default:
        this.log.warn("FxA Push command unrecognized: " + payload.command);
    }
  },
  /**
   * Check the FxA session status after a password change/reset event.
   * If the session is invalid, reset credentials and notify listeners of
   * ON_ACCOUNT_STATE_CHANGE_NOTIFICATION that the account may have changed
   *
   * @returns {Promise}
   * @private
   */
  _onPasswordChanged() {
    return this.fxai.withCurrentAccountState(async state => {
      return this.fxai.checkAccountStatus(state);
    });
  },
  /**
   * Fired when the Push server drops a subscription, or the subscription identifier changes.
   *
   * https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPushService#Receiving_Push_Messages
   *
   * @returns {Promise}
   * @private
   */
  _onPushSubscriptionChange() {
    this.log.trace("FxAccountsPushService _onPushSubscriptionChange");
    return this.fxai.updateDeviceRegistration();
  },
  /**
   * Unsubscribe from the Push server
   *
   * Ref: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPushService#unsubscribe()
   *
   * @returns {Promise} - The promise resolves with a bool to indicate if we successfully unsubscribed.
   *                      The promise never rejects.
   * @private
   */
  unsubscribe() {
    this.log.trace("FxAccountsPushService unsubscribe");
    return new Promise(resolve => {
      this.pushService.unsubscribe(
        FXA_PUSH_SCOPE_ACCOUNT_UPDATE,
        Services.scriptSecurityManager.getSystemPrincipal(),
        (result, ok) => {
          if (Components.isSuccessCode(result)) {
            if (ok === true) {
              this.log.debug("FxAccountsPushService unsubscribed");
            } else {
              this.log.debug(
                "FxAccountsPushService had no subscription to unsubscribe"
              );
            }
          } else {
            this.log.warn(
              "FxAccountsPushService failed to unsubscribe",
              result
            );
          }
          return resolve(ok);
        }
      );
    });
  },

  /**
   * Get our Push server subscription.
   *
   * Ref: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIPushService#getSubscription()
   *
   * @returns {Promise} - resolves with the subscription or null. Never rejects.
   */
  getSubscription() {
    return new Promise(resolve => {
      this.pushService.getSubscription(
        FXA_PUSH_SCOPE_ACCOUNT_UPDATE,
        Services.scriptSecurityManager.getSystemPrincipal(),
        (result, subscription) => {
          if (!subscription) {
            this.log.info("FxAccountsPushService no subscription found");
            return resolve(null);
          }
          return resolve(subscription);
        }
      );
    });
  },
};
PK
!<�Z5��#modules/FxAccountsTelemetry.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

// FxA Telemetry support. For hysterical raisins, the actual implementation
// is inside "sync". We should move the core implementation somewhere that's
// sanely shared (eg, services-common?), but let's wait and see where we end up
// first...

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  CryptoUtils: "resource://services-crypto/utils.sys.mjs",

  // We use this observers module because we leverage its support for richer
  // "subject" data.
  Observers: "resource://services-common/observers.sys.mjs",
});

import {
  PREF_ACCOUNT_ROOT,
  log,
} from "resource://gre/modules/FxAccountsCommon.sys.mjs";

const PREF_SANITIZED_UID = PREF_ACCOUNT_ROOT + "telemetry.sanitized_uid";
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "pref_sanitizedUid",
  PREF_SANITIZED_UID,
  ""
);

export class FxAccountsTelemetry {
  constructor(fxai) {
    this._fxai = fxai;
    Services.telemetry.setEventRecordingEnabled("fxa", true);
  }

  // Records an event *in the Fxa/Sync ping*.
  recordEvent(object, method, value, extra = undefined) {
    // We need to ensure the telemetry module is loaded.
    ChromeUtils.importESModule("resource://services-sync/telemetry.sys.mjs");
    // Now it will be listening for the notifications...
    lazy.Observers.notify("fxa:telemetry:event", {
      object,
      method,
      value,
      extra,
    });
  }

  generateUUID() {
    return Services.uuid.generateUUID().toString().slice(1, -1);
  }

  // A flow ID can be anything that's "probably" unique, so for now use a UUID.
  generateFlowID() {
    return this.generateUUID();
  }

  // FxA- and Sync-related metrics are submitted in a special-purpose "sync ping". This ping
  // identifies the user by a version of their FxA uid that is HMAC-ed with a server-side secret
  // key, in an attempt to provide a bit of anonymity.

  // Secret back-channel by which tokenserver client code can set the hashed UID.
  // This value conceptually belongs to FxA, but we currently get it from tokenserver,
  // so there's some light hackery to put it in the right place.
  _setHashedUID(hashedUID) {
    if (!hashedUID) {
      Services.prefs.clearUserPref(PREF_SANITIZED_UID);
    } else {
      Services.prefs.setStringPref(PREF_SANITIZED_UID, hashedUID);
    }
  }

  getSanitizedUID() {
    // Sadly, we can only currently obtain this value if the user has enabled sync.
    return lazy.pref_sanitizedUid || null;
  }

  // Sanitize the ID of a device into something suitable for including in the
  // ping. Returns null if no transformation is possible.
  sanitizeDeviceId(deviceId) {
    const uid = this.getSanitizedUID();
    if (!uid) {
      // Sadly, we can only currently get this if the user has enabled sync.
      return null;
    }
    // Combine the raw device id with the sanitized uid to create a stable
    // unique identifier that can't be mapped back to the user's FxA
    // identity without knowing the metrics HMAC key.
    // The result is 64 bytes long, which in retrospect is probably excessive,
    // but it's already shipping...
    return lazy.CryptoUtils.sha256(deviceId + uid);
  }

  // Record the connection of FxA or one of its services.
  // Note that you must call this before performing the actual connection
  // or we may record incorrect data - for example, we will not be able to
  // determine whether FxA itself was connected before this call.
  //
  // Currently sends an event in the main telemetry event ping rather than the
  // FxA/Sync ping (although this might change in the future)
  //
  // @param services - An array of service names which should be recorded. FxA
  //  itself is not counted as a "service" - ie, an empty array should be passed
  //  if the account is connected without anything else .
  //
  // @param how - How the connection was done.
  async recordConnection(services, how = null) {
    try {
      let extra = {};
      // Record that fxa was connected if it isn't currently - it will be soon.
      if (!(await this._fxai.getUserAccountData())) {
        extra.fxa = "true";
      }
      // Events.yaml only declares "sync" as a valid service.
      if (services.includes("sync")) {
        extra.sync = "true";
      }
      Services.telemetry.recordEvent("fxa", "connect", "account", how, extra);
    } catch (ex) {
      log.error("Failed to record connection telemetry", ex);
      console.error("Failed to record connection telemetry", ex);
    }
  }

  // Record the disconnection of FxA or one of its services.
  // Note that you must call this before performing the actual disconnection
  // or we may record incomplete data - for example, if this is called after
  // disconnection, we've almost certainly lost the ability to record what
  // services were enabled prior to disconnection.
  //
  // Currently sends an event in the main telemetry event ping rather than the
  // FxA/Sync ping (although this might change in the future)
  //
  // @param service - the service being disconnected. If null, the account
  // itself is being disconnected, so all connected services are too.
  //
  // @param how - how the disconnection was done.
  async recordDisconnection(service = null, how = null) {
    try {
      let extra = {};
      if (!service) {
        extra.fxa = "true";
        // We need a way to enumerate all services - but for now we just hard-code
        // all possibilities here.
        if (Services.prefs.prefHasUserValue("services.sync.username")) {
          extra.sync = "true";
        }
      } else if (service == "sync") {
        extra[service] = "true";
      } else {
        // Events.yaml only declares "sync" as a valid service.
        log.warn(
          `recordDisconnection has invalid value for service: ${service}`
        );
      }
      Services.telemetry.recordEvent(
        "fxa",
        "disconnect",
        "account",
        how,
        extra
      );
    } catch (ex) {
      log.error("Failed to record disconnection telemetry", ex);
      console.error("Failed to record disconnection telemetry", ex);
    }
  }
}
PK
!<Ƅ�Vmodules/GMPExtractor.worker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const FILE_ENTRY = "201: ";

async function readJarDirectory(jarPath, installToDirPath) {
  let extractedPaths = [];
  // Construct a jar URI from the file URI so we can use the JAR URI scheme
  // handling to navigate inside the zip file.
  let jarResponse = await fetch(jarPath);
  let dirListing = await jarResponse.text();
  let lines = dirListing.split("\n");
  let reader = new FileReader();
  for (let line of lines) {
    if (!line.startsWith(FILE_ENTRY)) {
      // Not a file entry, skip.
      continue;
    }
    // code: entry size modified-time type
    let lineSplits = line.split(" ");
    let jarEntry = lineSplits[1];
    let jarType = lineSplits[4];
    // Descend into and flatten any subfolders.
    if (jarType === "DIRECTORY") {
      extractedPaths.push(
        ...(await readJarDirectory(jarPath + jarEntry, installToDirPath))
      );
      continue;
    }
    let fileName = jarEntry.split("/").pop();
    // Only keep the binaries and metadata files.
    if (
      !fileName.endsWith(".info") &&
      !fileName.endsWith(".dll") &&
      !fileName.endsWith(".dylib") &&
      !fileName.endsWith(".sig") &&
      !fileName.endsWith(".so") &&
      !fileName.endsWith(".txt") &&
      fileName !== "LICENSE" &&
      fileName !== "manifest.json"
    ) {
      continue;
    }
    let filePath = jarPath + jarEntry;
    let filePathResponse = await fetch(filePath);
    let fileContents = await filePathResponse.blob();
    let fileData = await new Promise(resolve => {
      reader.onloadend = function () {
        resolve(reader.result);
      };
      reader.readAsArrayBuffer(fileContents);
    });
    await IOUtils.makeDirectory(installToDirPath);
    // Do not extract into directories. Extract all files to the same
    // directory.
    let destPath = PathUtils.join(installToDirPath, fileName);
    await IOUtils.write(destPath, new Uint8Array(fileData), {
      tmpPath: destPath + ".tmp",
    });
    // Ensure files are writable and executable. Otherwise, we may be
    // unable to execute or uninstall them.
    await IOUtils.setPermissions(destPath, 0o700);
    if (IOUtils.delMacXAttr) {
      // If we're on MacOS Firefox will add the quarantine xattr to files it
      // downloads. In this case we want to clear that xattr so we can load
      // the CDM.
      try {
        await IOUtils.delMacXAttr(destPath, "com.apple.quarantine");
      } catch (e) {
        // Failed to remove the attribute. This could be because the profile
        // exists on a file system without xattr support.
        //
        // Don't fail the extraction here, as in this case it's likely we
        // didn't set quarantine on these files in the first place.
      }
    }
    extractedPaths.push(destPath);
  }
  return extractedPaths;
}

onmessage = async function (msg) {
  try {
    // Construct a jar URI from the file URI so we can use the JAR URI scheme
    // handling to navigate inside the zip file.
    let jarPath = "jar:" + msg.data.zipURI + "!/";
    let installToDirPath = PathUtils.join(
      await PathUtils.getProfileDir(),
      ...msg.data.relativeInstallPath
    );
    let extractedPaths = await readJarDirectory(jarPath, installToDirPath);
    postMessage({
      result: "success",
      extractedPaths,
    });
  } catch (e) {
    postMessage({
      result: "fail",
      exception: e.message,
    });
  }
};
PK
!<n����#�#modules/GMPUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
});

// GMP IDs
export const OPEN_H264_ID = "gmp-gmpopenh264";
export const WIDEVINE_L1_ID = "gmp-widevinecdm-l1";
export const WIDEVINE_L3_ID = "gmp-widevinecdm";

export const GMP_PLUGIN_IDS = [OPEN_H264_ID, WIDEVINE_L1_ID, WIDEVINE_L3_ID];

export var GMPUtils = {
  /**
   * Checks whether or not a given plugin is hidden. Hidden plugins are neither
   * downloaded nor displayed in the addons manager.
   * @param   aPlugin
   *          The plugin to check.
   */
  isPluginHidden(aPlugin) {
    if (!this._isPluginSupported(aPlugin) || !this._isPluginVisible(aPlugin)) {
      return true;
    }

    if (!aPlugin.isEME) {
      return false;
    }

    if (!GMPPrefs.getBool(GMPPrefs.KEY_EME_ENABLED, true)) {
      return true;
    }

    return false;
  },

  /**
   * Checks whether or not a given plugin is supported by the current OS.
   * @param   aPlugin
   *          The plugin to check.
   */
  _isPluginSupported(aPlugin) {
    if (this._isPluginForceSupported(aPlugin)) {
      return true;
    }
    if (aPlugin.id == WIDEVINE_L1_ID) {
      // The Widevine L1 plugin is currently only available for Windows x64.
      return (
        AppConstants.MOZ_WMF_CDM &&
        AppConstants.platform == "win" &&
        lazy.UpdateUtils.ABI.match(/x64/)
      );
    }
    if (aPlugin.id == WIDEVINE_L1_ID || aPlugin.id == WIDEVINE_L3_ID) {
      // The Widevine plugin is available for Windows versions Vista and later,
      // Mac OSX, and Linux.
      return (
        AppConstants.platform == "win" ||
        AppConstants.platform == "macosx" ||
        AppConstants.platform == "linux"
      );
    }

    return true;
  },

  /**
   * Checks whether or not a given plugin is visible in the addons manager
   * UI and the "enable DRM" notification box. This can be used to test
   * plugins that aren't yet turned on in the mozconfig.
   * @param   aPlugin
   *          The plugin to check.
   */
  _isPluginVisible(aPlugin) {
    return GMPPrefs.getBool(GMPPrefs.KEY_PLUGIN_VISIBLE, false, aPlugin.id);
  },

  /**
   * Checks whether or not a given plugin is forced-supported. This is used
   * in automated tests to override the checks that prevent GMPs running on an
   * unsupported platform.
   * @param   aPlugin
   *          The plugin to check.
   */
  _isPluginForceSupported(aPlugin) {
    return GMPPrefs.getBool(
      GMPPrefs.KEY_PLUGIN_FORCE_SUPPORTED,
      false,
      aPlugin.id
    );
  },

  _isWindowsOnARM64() {
    return (
      AppConstants.platform == "win" && lazy.UpdateUtils.ABI.match(/aarch64/)
    );
  },

  _expectedABI(aPlugin) {
    let defaultABI = lazy.UpdateUtils.ABI;
    let expectedABIs = [defaultABI];
    if (aPlugin.id == WIDEVINE_L3_ID && this._isWindowsOnARM64()) {
      // On Windows on aarch64, we may use either the x86 or the ARM64 plugin
      // as we are still shipping the former to release.
      expectedABIs.push(defaultABI.replace(/aarch64/g, "x86"));
    }
    return expectedABIs.join(",");
  },
};

/**
 * Manages preferences for GMP addons
 */
export var GMPPrefs = {
  KEY_EME_ENABLED: "media.eme.enabled",
  KEY_PLUGIN_ENABLED: "media.{0}.enabled",
  KEY_PLUGIN_LAST_DOWNLOAD: "media.{0}.lastDownload",
  KEY_PLUGIN_LAST_DOWNLOAD_FAILED: "media.{0}.lastDownloadFailed",
  KEY_PLUGIN_LAST_DOWNLOAD_FAIL_REASON: "media.{0}.lastDownloadFailReason",
  KEY_PLUGIN_LAST_INSTALL_FAILED: "media.{0}.lastInstallFailed",
  KEY_PLUGIN_LAST_INSTALL_FAIL_REASON: "media.{0}.lastInstallFailReason",
  KEY_PLUGIN_LAST_INSTALL_START: "media.{0}.lastInstallStart",
  KEY_PLUGIN_LAST_UPDATE: "media.{0}.lastUpdate",
  KEY_PLUGIN_HASHVALUE: "media.{0}.hashValue",
  KEY_PLUGIN_VERSION: "media.{0}.version",
  KEY_PLUGIN_AUTOUPDATE: "media.{0}.autoupdate",
  KEY_PLUGIN_VISIBLE: "media.{0}.visible",
  KEY_PLUGIN_ABI: "media.{0}.abi",
  KEY_PLUGIN_FORCE_SUPPORTED: "media.{0}.forceSupported",
  KEY_PLUGIN_FORCE_INSTALL: "media.{0}.forceInstall",
  KEY_PLUGIN_ALLOW_X64_ON_ARM64: "media.{0}.allow-x64-plugin-on-arm64",
  KEY_ALLOW_LOCAL_SOURCES: "media.gmp-manager.allowLocalSources",
  KEY_URL: "media.gmp-manager.url",
  KEY_URL_OVERRIDE: "media.gmp-manager.url.override",
  KEY_CERT_CHECKATTRS: "media.gmp-manager.cert.checkAttributes",
  KEY_CERT_REQUIREBUILTIN: "media.gmp-manager.cert.requireBuiltIn",
  KEY_CHECK_CONTENT_SIGNATURE: "media.gmp-manager.checkContentSignature",
  KEY_UPDATE_LAST_CHECK: "media.gmp-manager.lastCheck",
  KEY_UPDATE_LAST_EMPTY_CHECK: "media.gmp-manager.lastEmptyCheck",
  KEY_SECONDS_BETWEEN_CHECKS: "media.gmp-manager.secondsBetweenChecks",
  KEY_UPDATE_ENABLED: "media.gmp-manager.updateEnabled",
  KEY_APP_DISTRIBUTION: "distribution.id",
  KEY_APP_DISTRIBUTION_VERSION: "distribution.version",
  KEY_BUILDID: "media.gmp-manager.buildID",
  KEY_CERTS_BRANCH: "media.gmp-manager.certs.",
  KEY_PROVIDER_ENABLED: "media.gmp-provider.enabled",
  KEY_LOG_BASE: "media.gmp.log.",
  KEY_LOGGING_LEVEL: "media.gmp.log.level",
  KEY_LOGGING_DUMP: "media.gmp.log.dump",

  /**
   * Obtains the specified string preference in relation to the specified plugin.
   * @param aKey The preference key value to use.
   * @param aDefaultValue The default value if no preference exists.
   * @param aPlugin The plugin to scope the preference to.
   * @return The obtained preference value, or the defaultValue if none exists.
   */
  getString(aKey, aDefaultValue, aPlugin) {
    if (
      aKey === this.KEY_APP_DISTRIBUTION ||
      aKey === this.KEY_APP_DISTRIBUTION_VERSION
    ) {
      return Services.prefs.getDefaultBranch(null).getCharPref(aKey, "default");
    }
    return Services.prefs.getStringPref(
      this.getPrefKey(aKey, aPlugin),
      aDefaultValue
    );
  },

  /**
   * Obtains the specified int preference in relation to the specified plugin.
   * @param aKey The preference key value to use.
   * @param aDefaultValue The default value if no preference exists.
   * @param aPlugin The plugin to scope the preference to.
   * @return The obtained preference value, or the defaultValue if none exists.
   */
  getInt(aKey, aDefaultValue, aPlugin) {
    return Services.prefs.getIntPref(
      this.getPrefKey(aKey, aPlugin),
      aDefaultValue
    );
  },

  /**
   * Obtains the specified bool preference in relation to the specified plugin.
   * @param aKey The preference key value to use.
   * @param aDefaultValue The default value if no preference exists.
   * @param aPlugin The plugin to scope the preference to.
   * @return The obtained preference value, or the defaultValue if none exists.
   */
  getBool(aKey, aDefaultValue, aPlugin) {
    return Services.prefs.getBoolPref(
      this.getPrefKey(aKey, aPlugin),
      aDefaultValue
    );
  },

  /**
   * Sets the specified string preference in relation to the specified plugin.
   * @param aKey The preference key value to use.
   * @param aVal The value to set.
   * @param aPlugin The plugin to scope the preference to.
   */
  setString(aKey, aVal, aPlugin) {
    Services.prefs.setStringPref(this.getPrefKey(aKey, aPlugin), aVal);
  },

  /**
   * Sets the specified bool preference in relation to the specified plugin.
   * @param aKey The preference key value to use.
   * @param aVal The value to set.
   * @param aPlugin The plugin to scope the preference to.
   */
  setBool(aKey, aVal, aPlugin) {
    Services.prefs.setBoolPref(this.getPrefKey(aKey, aPlugin), aVal);
  },

  /**
   * Sets the specified int preference in relation to the specified plugin.
   * @param aKey The preference key value to use.
   * @param aVal The value to set.
   * @param aPlugin The plugin to scope the preference to.
   */
  setInt(aKey, aVal, aPlugin) {
    Services.prefs.setIntPref(this.getPrefKey(aKey, aPlugin), aVal);
  },

  /**
   * Checks whether or not the specified preference is set in relation to the
   * specified plugin.
   * @param aKey The preference key value to use.
   * @param aPlugin The plugin to scope the preference to.
   * @return true if the preference is set, false otherwise.
   */
  isSet(aKey, aPlugin) {
    return Services.prefs.prefHasUserValue(this.getPrefKey(aKey, aPlugin));
  },

  /**
   * Resets the specified preference in relation to the specified plugin to its
   * default.
   * @param aKey The preference key value to use.
   * @param aPlugin The plugin to scope the preference to.
   */
  reset(aKey, aPlugin) {
    Services.prefs.clearUserPref(this.getPrefKey(aKey, aPlugin));
  },

  /**
   * Scopes the specified preference key to the specified plugin.
   * @param aKey The preference key value to use.
   * @param aPlugin The plugin to scope the preference to.
   * @return A preference key scoped to the specified plugin.
   */
  getPrefKey(aKey, aPlugin) {
    return aKey.replace("{0}", aPlugin || "");
  },
};
PK
!<9h��#�#modules/Geometry.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Simple Point class.
 *
 * Any method that takes an x and y may also take a point.
 */
export function Point(x, y) {
  this.set(x, y);
}

Point.prototype = {
  clone: function clone() {
    return new Point(this.x, this.y);
  },

  set: function set(x, y) {
    this.x = x;
    this.y = y;
    return this;
  },

  equals: function equals(x, y) {
    return this.x == x && this.y == y;
  },

  toString: function toString() {
    return "(" + this.x + "," + this.y + ")";
  },

  map: function map(f) {
    this.x = f.call(this, this.x);
    this.y = f.call(this, this.y);
    return this;
  },

  add: function add(x, y) {
    this.x += x;
    this.y += y;
    return this;
  },

  subtract: function subtract(x, y) {
    this.x -= x;
    this.y -= y;
    return this;
  },

  scale: function scale(s) {
    this.x *= s;
    this.y *= s;
    return this;
  },

  isZero() {
    return this.x == 0 && this.y == 0;
  },
};

(function () {
  function takePointOrArgs(f) {
    return function (arg1, arg2) {
      if (arg2 === undefined) {
        return f.call(this, arg1.x, arg1.y);
      }
      return f.call(this, arg1, arg2);
    };
  }

  for (let f of ["add", "subtract", "equals", "set"]) {
    Point.prototype[f] = takePointOrArgs(Point.prototype[f]);
  }
})();

/**
 * Rect is a simple data structure for representation of a rectangle supporting
 * many basic geometric operations.
 *
 * NOTE: Since its operations are closed, rectangles may be empty and will report
 * non-positive widths and heights in that case.
 */

export function Rect(x, y, w, h) {
  this.left = x;
  this.top = y;
  this.right = x + w;
  this.bottom = y + h;
}

Rect.fromRect = function fromRect(r) {
  return new Rect(r.left, r.top, r.right - r.left, r.bottom - r.top);
};

Rect.prototype = {
  get x() {
    return this.left;
  },
  get y() {
    return this.top;
  },
  get width() {
    return this.right - this.left;
  },
  get height() {
    return this.bottom - this.top;
  },
  set x(v) {
    let diff = this.left - v;
    this.left = v;
    this.right -= diff;
  },
  set y(v) {
    let diff = this.top - v;
    this.top = v;
    this.bottom -= diff;
  },
  set width(v) {
    this.right = this.left + v;
  },
  set height(v) {
    this.bottom = this.top + v;
  },

  isEmpty: function isEmpty() {
    return this.left >= this.right || this.top >= this.bottom;
  },

  setRect(x, y, w, h) {
    this.left = x;
    this.top = y;
    this.right = x + w;
    this.bottom = y + h;

    return this;
  },

  setBounds(l, t, r, b) {
    this.top = t;
    this.left = l;
    this.bottom = b;
    this.right = r;

    return this;
  },

  equals: function equals(other) {
    return (
      other != null &&
      ((this.isEmpty() && other.isEmpty()) ||
        (this.top == other.top &&
          this.left == other.left &&
          this.bottom == other.bottom &&
          this.right == other.right))
    );
  },

  clone: function clone() {
    return new Rect(
      this.left,
      this.top,
      this.right - this.left,
      this.bottom - this.top
    );
  },

  center: function center() {
    if (this.isEmpty()) {
      throw new Error("Empty rectangles do not have centers");
    }
    return new Point(
      this.left + (this.right - this.left) / 2,
      this.top + (this.bottom - this.top) / 2
    );
  },

  copyFrom(other) {
    this.top = other.top;
    this.left = other.left;
    this.bottom = other.bottom;
    this.right = other.right;

    return this;
  },

  translate(x, y) {
    this.left += x;
    this.right += x;
    this.top += y;
    this.bottom += y;

    return this;
  },

  toString() {
    return (
      "[" + this.x + "," + this.y + "," + this.width + "," + this.height + "]"
    );
  },

  /** return a new rect that is the union of that one and this one */
  union(other) {
    return this.clone().expandToContain(other);
  },

  contains(other) {
    if (other.isEmpty()) {
      return true;
    }
    if (this.isEmpty()) {
      return false;
    }

    return (
      other.left >= this.left &&
      other.right <= this.right &&
      other.top >= this.top &&
      other.bottom <= this.bottom
    );
  },

  intersect(other) {
    return this.clone().restrictTo(other);
  },

  intersects(other) {
    if (this.isEmpty() || other.isEmpty()) {
      return false;
    }

    let x1 = Math.max(this.left, other.left);
    let x2 = Math.min(this.right, other.right);
    let y1 = Math.max(this.top, other.top);
    let y2 = Math.min(this.bottom, other.bottom);
    return x1 < x2 && y1 < y2;
  },

  /** Restrict area of this rectangle to the intersection of both rectangles. */
  restrictTo: function restrictTo(other) {
    if (this.isEmpty() || other.isEmpty()) {
      return this.setRect(0, 0, 0, 0);
    }

    let x1 = Math.max(this.left, other.left);
    let x2 = Math.min(this.right, other.right);
    let y1 = Math.max(this.top, other.top);
    let y2 = Math.min(this.bottom, other.bottom);
    // If width or height is 0, the intersection was empty.
    return this.setRect(x1, y1, Math.max(0, x2 - x1), Math.max(0, y2 - y1));
  },

  /** Expand this rectangle to the union of both rectangles. */
  expandToContain: function expandToContain(other) {
    if (this.isEmpty()) {
      return this.copyFrom(other);
    }
    if (other.isEmpty()) {
      return this;
    }

    let l = Math.min(this.left, other.left);
    let r = Math.max(this.right, other.right);
    let t = Math.min(this.top, other.top);
    let b = Math.max(this.bottom, other.bottom);
    return this.setRect(l, t, r - l, b - t);
  },

  /**
   * Expands to the smallest rectangle that contains original rectangle and is bounded
   * by lines with integer coefficients.
   */
  expandToIntegers: function round() {
    this.left = Math.floor(this.left);
    this.top = Math.floor(this.top);
    this.right = Math.ceil(this.right);
    this.bottom = Math.ceil(this.bottom);
    return this;
  },

  scale: function scale(xscl, yscl) {
    this.left *= xscl;
    this.right *= xscl;
    this.top *= yscl;
    this.bottom *= yscl;
    return this;
  },

  map: function map(f) {
    this.left = f.call(this, this.left);
    this.top = f.call(this, this.top);
    this.right = f.call(this, this.right);
    this.bottom = f.call(this, this.bottom);
    return this;
  },

  /** Ensure this rectangle is inside the other, if possible. Preserves w, h. */
  translateInside: function translateInside(other) {
    let offsetX = 0;
    if (this.left <= other.left) {
      offsetX = other.left - this.left;
    } else if (this.right > other.right) {
      offsetX = other.right - this.right;
    }

    let offsetY = 0;
    if (this.top <= other.top) {
      offsetY = other.top - this.top;
    } else if (this.bottom > other.bottom) {
      offsetY = other.bottom - this.bottom;
    }

    return this.translate(offsetX, offsetY);
  },

  /** Subtract other area from this. Returns array of rects whose union is this-other. */
  subtract: function subtract(other) {
    let r = new Rect(0, 0, 0, 0);
    let result = [];
    other = other.intersect(this);
    if (other.isEmpty()) {
      return [this.clone()];
    }

    // left strip
    r.setBounds(this.left, this.top, other.left, this.bottom);
    if (!r.isEmpty()) {
      result.push(r.clone());
    }
    // inside strip
    r.setBounds(other.left, this.top, other.right, other.top);
    if (!r.isEmpty()) {
      result.push(r.clone());
    }
    r.setBounds(other.left, other.bottom, other.right, this.bottom);
    if (!r.isEmpty()) {
      result.push(r.clone());
    }
    // right strip
    r.setBounds(other.right, this.top, this.right, this.bottom);
    if (!r.isEmpty()) {
      result.push(r.clone());
    }

    return result;
  },

  /**
   * Blends two rectangles together.
   * @param rect Rectangle to blend this one with
   * @param scalar Ratio from 0 (returns a clone of this rect) to 1 (clone of rect).
   * @return New blended rectangle.
   */
  blend: function blend(rect, scalar) {
    return new Rect(
      this.left + (rect.left - this.left) * scalar,
      this.top + (rect.top - this.top) * scalar,
      this.width + (rect.width - this.width) * scalar,
      this.height + (rect.height - this.height) * scalar
    );
  },

  /**
   * Grows or shrinks the rectangle while keeping the center point.
   * Accepts single multipler, or separate for both axes.
   */
  inflate: function inflate(xscl, yscl) {
    let xAdj = (this.width * xscl - this.width) / 2;
    let s = arguments.length > 1 ? yscl : xscl;
    let yAdj = (this.height * s - this.height) / 2;
    this.left -= xAdj;
    this.right += xAdj;
    this.top -= yAdj;
    this.bottom += yAdj;
    return this;
  },

  /**
   * Grows or shrinks the rectangle by fixed amount while keeping the center point.
   * Accepts single fixed amount
   */
  inflateFixed: function inflateFixed(fixed) {
    this.left -= fixed;
    this.right += fixed;
    this.top -= fixed;
    this.bottom += fixed;
    return this;
  },
};
PK
!<+�0�		!modules/HPKEConfigManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

let knownConfigs = new Map();

export class HPKEConfigManager {
  /**
   * Decodes a base64url-encoded key string.
   * @param {string} aBase64Key
   * @returns {Uint8Array}
   */
  static decodeKey(aBase64Key) {
    return new Uint8Array(
      ChromeUtils.base64URLDecode(aBase64Key, { padding: "ignore" })
    );
  }

  static async get(aURL, aOptions = {}) {
    // If we're in a child, forward to the parent.
    let { remoteType } = Services.appinfo;
    if (remoteType) {
      if (remoteType != "privilegedabout") {
        // The remoteTypes definition in the actor definition will enforce
        // that calling getActor fails, this is a more readable error:
        throw new Error(
          "HPKEConfigManager cannot be used outside of the privilegedabout process."
        );
      }
      let actor = ChromeUtils.domProcessChild.getActor("HPKEConfigManager");
      return actor.sendQuery("getconfig", { url: aURL, options: aOptions });
    }
    try {
      let config = await this.#getInternal(aURL, aOptions);
      return new Uint8Array(config);
    } catch (ex) {
      console.error(ex);
      return null;
    }
  }

  static async #getInternal(aURL, aOptions = {}) {
    let { maxAge = -1 } = aOptions;
    let knownConfig = knownConfigs.get(aURL);
    if (
      knownConfig &&
      (maxAge < 0 || Date.now() - knownConfig.fetchDate < maxAge)
    ) {
      return knownConfig.config;
    }
    return this.#fetchAndStore(aURL, aOptions);
  }

  static async #fetchAndStore(aURL, aOptions = {}) {
    let fetchDate = Date.now();
    let resp = await fetch(aURL, { signal: aOptions.abortSignal });
    if (!resp?.ok) {
      throw new Error(
        `Fetching HPKE config from ${aURL} failed with error ${resp.status}`
      );
    }
    let config = await resp.blob().then(b => b.arrayBuffer());
    knownConfigs.set(aURL, { config, fetchDate });
    return config;
  }
}

export class HPKEConfigManagerParent extends JSProcessActorParent {
  receiveMessage(msg) {
    if (msg.name == "getconfig") {
      return HPKEConfigManager.get(msg.data.url, msg.data.options);
    }
    return null;
  }
}
PK
!<?�1l��modules/HelperAppDlg.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { BrowserUtils } from "resource://gre/modules/BrowserUtils.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  EnableDelayHelper: "resource://gre/modules/PromptUtils.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gReputationService",
  "@mozilla.org/reputationservice/application-reputation-service;1",
  Ci.nsIApplicationReputationService
);
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gMIMEService",
  "@mozilla.org/mime;1",
  Ci.nsIMIMEService
);

import { Integration } from "resource://gre/modules/Integration.sys.mjs";

Integration.downloads.defineESModuleGetter(
  lazy,
  "DownloadIntegration",
  "resource://gre/modules/DownloadIntegration.sys.mjs"
);

// /////////////////////////////////////////////////////////////////////////////
// // Helper Functions

/**
 * Determines if a given directory is able to be used to download to.
 *
 * @param aDirectory
 *        The directory to check.
 * @return true if we can use the directory, false otherwise.
 */
function isUsableDirectory(aDirectory) {
  return (
    aDirectory.exists() && aDirectory.isDirectory() && aDirectory.isWritable()
  );
}

// Web progress listener so we can detect errors while mLauncher is
// streaming the data to a temporary file.
function nsUnknownContentTypeDialogProgressListener(aHelperAppDialog) {
  this.helperAppDlg = aHelperAppDialog;
}

nsUnknownContentTypeDialogProgressListener.prototype = {
  // nsIWebProgressListener methods.
  // Look for error notifications and display alert to user.
  onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
    if (aStatus != Cr.NS_OK) {
      // Display error alert (using text supplied by back-end).
      // FIXME this.dialog is undefined?
      Services.prompt.alert(this.dialog, this.helperAppDlg.mTitle, aMessage);
      // Close the dialog.
      this.helperAppDlg.onCancel();
      if (this.helperAppDlg.mDialog) {
        this.helperAppDlg.mDialog.close();
      }
    }
  },

  // Ignore onProgressChange, onProgressChange64, onStateChange, onLocationChange, onSecurityChange, onContentBlockingEvent and onRefreshAttempted notifications.
  onProgressChange() {},

  onProgressChange64() {},

  onStateChange() {},

  onLocationChange() {},

  onSecurityChange() {},

  onContentBlockingEvent() {},

  onRefreshAttempted() {
    return true;
  },
};

// /////////////////////////////////////////////////////////////////////////////
// // nsUnknownContentTypeDialog

/* This file implements the nsIHelperAppLauncherDialog interface.
 *
 * The implementation consists of a JavaScript "class" named nsUnknownContentTypeDialog,
 * comprised of:
 *   - a JS constructor function
 *   - a prototype providing all the interface methods and implementation stuff
 */

const PREF_BD_USEDOWNLOADDIR = "browser.download.useDownloadDir";
const nsITimer = Ci.nsITimer;

import * as downloadModule from "resource://gre/modules/DownloadLastDir.sys.mjs";
import { DownloadPaths } from "resource://gre/modules/DownloadPaths.sys.mjs";

import { DownloadUtils } from "resource://gre/modules/DownloadUtils.sys.mjs";
import { Downloads } from "resource://gre/modules/Downloads.sys.mjs";
import { FileUtils } from "resource://gre/modules/FileUtils.sys.mjs";

/* ctor
 */
export function nsUnknownContentTypeDialog() {
  // Initialize data properties.
  this.mLauncher = null;
  this.mContext = null;
  this.mReason = null;
  this.chosenApp = null;
  this.givenDefaultApp = false;
  this.updateSelf = true;
  this.mTitle = "";
}

nsUnknownContentTypeDialog.prototype = {
  classID: Components.ID("{F68578EB-6EC2-4169-AE19-8C6243F0ABE1}"),

  nsIMIMEInfo: Ci.nsIMIMEInfo,

  QueryInterface: ChromeUtils.generateQI([
    "nsIHelperAppLauncherDialog",
    "nsITimerCallback",
  ]),

  // ---------- nsIHelperAppLauncherDialog methods ----------

  // show: Open XUL dialog using window watcher.  Since the dialog is not
  //       modal, it needs to be a top level window and the way to open
  //       one of those is via that route).
  show(aLauncher, aContext, aReason) {
    this.mLauncher = aLauncher;
    this.mContext = aContext;
    this.mReason = aReason;

    // Cache some information in case this context goes away:
    try {
      let parent = aContext.getInterface(Ci.nsIDOMWindow);
      this._mDownloadDir = new downloadModule.DownloadLastDir(parent);
    } catch (ex) {
      console.error(
        "Missing window information when showing nsIHelperAppLauncherDialog:",
        ex
      );
    }

    const nsITimer = Ci.nsITimer;
    this._showTimer = Cc["@mozilla.org/timer;1"].createInstance(nsITimer);
    this._showTimer.initWithCallback(this, 0, nsITimer.TYPE_ONE_SHOT);
  },

  // When opening from new tab, if tab closes while dialog is opening,
  // (which is a race condition on the XUL file being cached and the timer
  // in nsExternalHelperAppService), the dialog gets a blur and doesn't
  // activate the OK button.  So we wait a bit before doing opening it.
  reallyShow() {
    try {
      let docShell = this.mContext.getInterface(Ci.nsIDocShell);
      let rootWin = docShell.browsingContext.topChromeWindow;
      this.mDialog = Services.ww.openWindow(
        rootWin,
        "chrome://mozapps/content/downloads/unknownContentType.xhtml",
        null,
        "chrome,centerscreen,titlebar,dialog=yes,dependent",
        null
      );
    } catch (ex) {
      // The containing window may have gone away.  Break reference
      // cycles and stop doing the download.
      this.mLauncher.cancel(Cr.NS_BINDING_ABORTED);
      return;
    }

    // Hook this object to the dialog.
    this.mDialog.dialog = this;

    // Hook up utility functions.
    this.getSpecialFolderKey = this.mDialog.getSpecialFolderKey;

    // Watch for error notifications.
    var progressListener = new nsUnknownContentTypeDialogProgressListener(this);
    this.mLauncher.setWebProgressListener(progressListener);
  },

  //
  // displayBadPermissionAlert()
  //
  // Diplay an alert panel about the bad permission of folder/directory.
  //
  displayBadPermissionAlert() {
    let bundle = Services.strings.createBundle(
      "chrome://mozapps/locale/downloads/unknownContentType.properties"
    );

    Services.prompt.alert(
      this.dialog,
      bundle.GetStringFromName("badPermissions.title"),
      bundle.GetStringFromName("badPermissions")
    );
  },

  promptForSaveToFileAsync(
    aLauncher,
    aContext,
    aDefaultFileName,
    aSuggestedFileExtension,
    aForcePrompt
  ) {
    var result = null;

    this.mLauncher = aLauncher;

    let bundle = Services.strings.createBundle(
      "chrome://mozapps/locale/downloads/unknownContentType.properties"
    );

    let parent;
    let gDownloadLastDir;
    try {
      parent = aContext.getInterface(Ci.nsIDOMWindow);
    } catch (ex) {}

    if (parent) {
      gDownloadLastDir = new downloadModule.DownloadLastDir(parent);
    } else {
      // Use the cached download info, but pick an arbitrary parent window
      // because the original one is definitely gone (and nsIFilePicker doesn't like
      // a null parent):
      gDownloadLastDir = this._mDownloadDir;
      for (let someWin of Services.wm.getEnumerator("")) {
        // We need to make sure we don't end up with this dialog, because otherwise
        // that's going to go away when the user clicks "Save", and that breaks the
        // windows file picker that's supposed to show up if we let the user choose
        // where to save files...
        if (someWin != this.mDialog) {
          parent = someWin;
        }
      }
      if (!parent) {
        console.error(
          "No candidate parent windows were found for the save filepicker." +
            "This should never happen."
        );
      }
    }

    (async () => {
      if (!aForcePrompt) {
        // Check to see if the user wishes to auto save to the default download
        // folder without prompting. Note that preference might not be set.
        let autodownload = Services.prefs.getBoolPref(
          PREF_BD_USEDOWNLOADDIR,
          false
        );

        if (autodownload) {
          // Retrieve the user's default download directory
          let preferredDir = await Downloads.getPreferredDownloadsDirectory();
          let defaultFolder = new FileUtils.File(preferredDir);

          try {
            if (aDefaultFileName) {
              result = this.validateLeafName(
                defaultFolder,
                aDefaultFileName,
                aSuggestedFileExtension
              );
            }
          } catch (ex) {
            // When the default download directory is write-protected,
            // prompt the user for a different target file.
          }

          // Check to make sure we have a valid directory, otherwise, prompt
          if (result) {
            // This path is taken when we have a writable default download directory.
            aLauncher.saveDestinationAvailable(result);
            return;
          }
        }
      }

      // Use file picker to show dialog.
      var nsIFilePicker = Ci.nsIFilePicker;
      var picker =
        Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
      var windowTitle = bundle.GetStringFromName("saveDialogTitle");
      picker.init(parent.browsingContext, windowTitle, nsIFilePicker.modeSave);
      if (aDefaultFileName) {
        picker.defaultString = this.getFinalLeafName(aDefaultFileName);
      }

      if (aSuggestedFileExtension) {
        // aSuggestedFileExtension includes the period, so strip it
        picker.defaultExtension = aSuggestedFileExtension.substring(1);
      } else {
        try {
          picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension;
        } catch (ex) {}
      }

      var wildCardExtension = "*";
      if (aSuggestedFileExtension) {
        wildCardExtension += aSuggestedFileExtension;
        picker.appendFilter(
          this.mLauncher.MIMEInfo.description,
          wildCardExtension
        );
      }

      picker.appendFilters(nsIFilePicker.filterAll);

      // Default to lastDir if it is valid, otherwise use the user's default
      // downloads directory.  getPreferredDownloadsDirectory should always
      // return a valid directory path, so we can safely default to it.
      let preferredDir = await Downloads.getPreferredDownloadsDirectory();
      picker.displayDirectory = new FileUtils.File(preferredDir);

      gDownloadLastDir.getFileAsync(aLauncher.source).then(lastDir => {
        if (lastDir && isUsableDirectory(lastDir)) {
          picker.displayDirectory = lastDir;
        }

        picker.open(returnValue => {
          if (returnValue == nsIFilePicker.returnCancel) {
            // null result means user cancelled.
            aLauncher.saveDestinationAvailable(null);
            return;
          }

          // Be sure to save the directory the user chose through the Save As...
          // dialog  as the new browser.download.dir since the old one
          // didn't exist.
          result = picker.file;

          if (result) {
            let allowOverwrite = false;
            try {
              // If we're overwriting, avoid renaming our file, and assume
              // overwriting it does the right thing.
              if (
                result.exists() &&
                this.getFinalLeafName(result.leafName, "", true) ==
                  result.leafName
              ) {
                allowOverwrite = true;
              }
            } catch (ex) {
              // As it turns out, the failure to remove the file, for example due to
              // permission error, will be handled below eventually somehow.
            }

            var newDir = result.parent.QueryInterface(Ci.nsIFile);

            // Do not store the last save directory as a pref inside the private browsing mode
            gDownloadLastDir.setFile(aLauncher.source, newDir);

            try {
              result = this.validateLeafName(
                newDir,
                result.leafName,
                null,
                allowOverwrite,
                true
              );
            } catch (ex) {
              // When the chosen download directory is write-protected,
              // display an informative error message.
              // In all cases, download will be stopped.

              if (ex.result == Cr.NS_ERROR_FILE_ACCESS_DENIED) {
                this.displayBadPermissionAlert();
                aLauncher.saveDestinationAvailable(null);
                return;
              }
            }
          }
          // Don't pop up the downloads panel redundantly.
          aLauncher.saveDestinationAvailable(result, true);
        });
      });
    })().catch(console.error);
  },

  getFinalLeafName(aLeafName, aFileExt, aAfterFilePicker) {
    return (
      DownloadPaths.sanitize(aLeafName, {
        compressWhitespaces: !aAfterFilePicker,
        allowInvalidFilenames: aAfterFilePicker,
      }) || "unnamed" + (aFileExt ? "." + aFileExt : "")
    );
  },

  /**
   * Ensures that a local folder/file combination does not already exist in
   * the file system (or finds such a combination with a reasonably similar
   * leaf name), creates the corresponding file, and returns it.
   *
   * @param   aLocalFolder
   *          the folder where the file resides
   * @param   aLeafName
   *          the string name of the file (may be empty if no name is known,
   *          in which case a name will be chosen)
   * @param   aFileExt
   *          the extension of the file, if one is known; this will be ignored
   *          if aLeafName is non-empty
   * @param   aAllowExisting
   *          if set to true, avoid creating a unique file.
   * @param   aAfterFilePicker
   *          if set to true, this was a file entered by the user from a file picker.
   * @return  nsIFile
   *          the created file
   * @throw   an error such as permission doesn't allow creation of
   *          file, etc.
   */
  validateLeafName(
    aLocalFolder,
    aLeafName,
    aFileExt,
    aAllowExisting = false,
    aAfterFilePicker = false
  ) {
    if (!(aLocalFolder && isUsableDirectory(aLocalFolder))) {
      throw new Components.Exception(
        "Destination directory non-existing or permission error",
        Cr.NS_ERROR_FILE_ACCESS_DENIED
      );
    }

    aLeafName = this.getFinalLeafName(aLeafName, aFileExt, aAfterFilePicker);
    aLocalFolder.append(aLeafName);

    if (!aAllowExisting) {
      // The following assignment can throw an exception, but
      // is now caught properly in the caller of validateLeafName.
      var validatedFile = DownloadPaths.createNiceUniqueFile(aLocalFolder);
    } else {
      validatedFile = aLocalFolder;
    }

    return validatedFile;
  },

  // ---------- implementation methods ----------

  // initDialog:  Fill various dialog fields with initial content.
  initDialog() {
    // Put file name in window title.
    var suggestedFileName = this.mLauncher.suggestedFileName;

    this.mDialog.document.addEventListener("dialogaccept", this);
    this.mDialog.document.addEventListener("dialogcancel", this);

    let url = this.mLauncher.source;

    if (url instanceof Ci.nsINestedURI) {
      url = url.innermostURI;
    }

    let iconPath = "goat";
    let fname = "";
    if (suggestedFileName) {
      fname = iconPath = suggestedFileName;
    } else if (url instanceof Ci.nsIURL) {
      // A url, use file name from it.
      fname = iconPath = url.fileName;
    } else if (["data", "blob"].includes(url.scheme)) {
      // The path is useless for these, so use a reasonable default.
      let { MIMEType } = this.mLauncher.MIMEInfo;
      fname = lazy.gMIMEService.getValidFileName(null, MIMEType, url, 0);
    } else {
      fname = url.pathQueryRef;
    }

    this.mSourcePath = url.prePath;
    // Some URIs do not implement nsIURL, so we can't just QI.
    if (url instanceof Ci.nsIURL) {
      this.mSourcePath += url.directory;
    } else {
      // Don't make the url excessively long (e.g. for data URIs)
      // (this doesn't use a temp var to avoid copying a potentially
      // several mb-long string)
      this.mSourcePath +=
        url.pathQueryRef.length > 500
          ? url.pathQueryRef.substring(0, 500) + "\u2026"
          : url.pathQueryRef;
    }

    var displayName = fname.replace(/ +/g, " ");

    this.mTitle = this.dialogElement("strings").getFormattedString("title", [
      displayName,
    ]);
    this.mDialog.document.title = this.mTitle;

    // Put content type, filename and location into intro.
    this.initIntro(url, displayName);

    var iconString =
      "moz-icon://" +
      iconPath +
      "?size=16&contentType=" +
      this.mLauncher.MIMEInfo.MIMEType;
    this.dialogElement("contentTypeImage").setAttribute("src", iconString);

    let dialog = this.mDialog.document.getElementById("unknownContentType");

    // if always-save and is-executable and no-handler
    // then set up simple ui
    var mimeType = this.mLauncher.MIMEInfo.MIMEType;
    let isPlain = mimeType == "text/plain";

    this.isExemptExecutableExtension =
      Services.policies.isExemptExecutableExtension(
        url.spec,
        fname?.split(".").at(-1)
      );

    var shouldntRememberChoice =
      mimeType == "application/octet-stream" ||
      mimeType == "application/x-msdownload" ||
      (this.mLauncher.targetFileIsExecutable &&
        !this.isExemptExecutableExtension) ||
      // Do not offer to remember text/plain mimetype choices if the file
      // isn't actually a 'plain' text file.
      (isPlain && lazy.gReputationService.isBinary(suggestedFileName));
    if (
      (shouldntRememberChoice && !this.openWithDefaultOK()) ||
      Services.prefs.getBoolPref("browser.download.forbid_open_with")
    ) {
      // hide featured choice
      this.dialogElement("normalBox").collapsed = true;
      // show basic choice
      this.dialogElement("basicBox").collapsed = false;
      // change button labels and icons; use "save" icon for the accept
      // button since it's the only action possible
      let acceptButton = dialog.getButton("accept");
      acceptButton.label = this.dialogElement("strings").getString(
        "unknownAccept.label"
      );
      acceptButton.setAttribute("icon", "save");
      dialog.getButton("cancel").label = this.dialogElement(
        "strings"
      ).getString("unknownCancel.label");
      // hide other handler
      this.dialogElement("openHandler").collapsed = true;
      // set save as the selected option
      this.dialogElement("mode").selectedItem = this.dialogElement("save");
    } else {
      this.initInteractiveControls();

      // Initialize "always ask me" box. This should always be disabled
      // and set to true for the ambiguous type application/octet-stream.
      // We don't also check for application/x-msdownload here since we
      // want users to be able to autodownload .exe files.
      var rememberChoice = this.dialogElement("rememberChoice");

      // Just because we have a content-type of application/octet-stream
      // here doesn't actually mean that the content is of that type. Many
      // servers default to sending text/plain for file types they don't know
      // about. To account for this, the uriloader does some checking to see
      // if a file sent as text/plain contains binary characters, and if so (*)
      // it morphs the content-type into application/octet-stream so that
      // the file can be properly handled. Since this is not generic binary
      // data, rather, a data format that the system probably knows about,
      // we don't want to use the content-type provided by this dialog's
      // opener, as that's the generic application/octet-stream that the
      // uriloader has passed, rather we want to ask the MIME Service.
      // This is so we don't needlessly disable the "autohandle" checkbox.

      if (shouldntRememberChoice) {
        rememberChoice.checked = false;
        rememberChoice.hidden = true;
      } else {
        rememberChoice.checked =
          !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling &&
          this.mLauncher.MIMEInfo.preferredAction !=
            this.nsIMIMEInfo.handleInternally;
      }
      this.toggleRememberChoice(rememberChoice);
    }

    this.mDialog.setTimeout(function () {
      this.dialog.postShowCallback();
    }, 0);

    this.delayHelper = new lazy.EnableDelayHelper({
      disableDialog: () => {
        dialog.getButton("accept").disabled = true;
      },
      enableDialog: () => {
        dialog.getButton("accept").disabled = false;
      },
      focusTarget: this.mDialog,
    });
  },

  notify(aTimer) {
    if (aTimer == this._showTimer) {
      if (!this.mDialog) {
        this.reallyShow();
      }
      // The timer won't release us, so we have to release it.
      this._showTimer = null;
    } else if (aTimer == this._saveToDiskTimer) {
      // Since saveToDisk may open a file picker and therefore block this routine,
      // we should only call it once the dialog is closed.
      this.mLauncher.promptForSaveDestination();
      this._saveToDiskTimer = null;
    }
  },

  postShowCallback() {
    this.mDialog.sizeToContent();

    // Set initial focus
    this.dialogElement("mode").focus();
  },

  initIntro(url, displayName) {
    this.dialogElement("location").value = displayName;
    this.dialogElement("location").setAttribute("tooltiptext", displayName);

    // if mSourcePath is a local file, then let's use the pretty path name
    // instead of an ugly url...
    let pathString;
    if (url instanceof Ci.nsIFileURL) {
      try {
        // Getting .file might throw, or .parent could be null
        pathString = url.file.parent.path;
      } catch (ex) {}
    }

    if (!pathString) {
      pathString = BrowserUtils.formatURIForDisplay(url, {
        showInsecureHTTP: true,
      });
    }

    // Set the location text, which is separate from the intro text so it can be cropped
    var location = this.dialogElement("source");
    location.value = pathString;
    location.setAttribute("tooltiptext", this.mSourcePath);

    // Show the type of file.
    var type = this.dialogElement("type");
    var mimeInfo = this.mLauncher.MIMEInfo;

    // 1. Try to use the pretty description of the type, if one is available.
    var typeString = mimeInfo.description;

    if (typeString == "") {
      // 2. If there is none, use the extension to identify the file, e.g. "ZIP file"
      var primaryExtension = "";
      try {
        primaryExtension = mimeInfo.primaryExtension;
      } catch (ex) {}
      if (primaryExtension != "") {
        typeString = this.dialogElement("strings").getFormattedString(
          "fileType",
          [primaryExtension.toUpperCase()]
        );
      }
      // 3. If we can't even do that, just give up and show the MIME type.
      else {
        typeString = mimeInfo.MIMEType;
      }
    }
    // When the length is unknown, contentLength would be -1
    let value = typeString;
    if (this.mLauncher.contentLength >= 0) {
      let [size, unit] = DownloadUtils.convertByteUnits(
        this.mLauncher.contentLength
      );
      value = this.dialogElement("strings").getFormattedString(
        "orderedFileSizeWithType",
        [typeString, size, unit]
      );
    }
    type.textContent = value;
  },

  // Returns true if opening the default application makes sense.
  openWithDefaultOK() {
    // The checking is different on Windows...
    if (AppConstants.platform == "win") {
      // Windows presents some special cases.
      // We need to prevent use of "system default" when the file is
      // executable (so the user doesn't launch nasty programs downloaded
      // from the web), and, enable use of "system default" if it isn't
      // executable (because we will prompt the user for the default app
      // in that case).

      //  Default is Ok if the file isn't executable (and vice-versa).
      return (
        !this.mLauncher.targetFileIsExecutable ||
        this.isExemptExecutableExtension
      );
    }
    // On other platforms, default is Ok if there is a default app.
    // Note that nsIMIMEInfo providers need to ensure that this holds true
    // on each platform.
    return this.mLauncher.MIMEInfo.hasDefaultHandler;
  },

  // Set "default" application description field.
  initDefaultApp() {
    // Use description, if we can get one.
    var desc = this.mLauncher.MIMEInfo.defaultDescription;
    if (desc) {
      var defaultApp = this.dialogElement("strings").getFormattedString(
        "defaultApp",
        [desc]
      );
      this.dialogElement("defaultHandler").label = defaultApp;
    } else {
      this.dialogElement("modeDeck").setAttribute("selectedIndex", "1");
      // Hide the default handler item too, in case the user picks a
      // custom handler at a later date which triggers the menulist to show.
      this.dialogElement("defaultHandler").hidden = true;
    }
  },

  getPath(aFile) {
    if (AppConstants.platform == "macosx") {
      return aFile.leafName || aFile.path;
    }
    return aFile.path;
  },

  initInteractiveControls() {
    var modeGroup = this.dialogElement("mode");

    // We don't let users open .exe files or random binary data directly
    // from the browser at the moment because of security concerns.
    var openWithDefaultOK = this.openWithDefaultOK();
    var mimeType = this.mLauncher.MIMEInfo.MIMEType;
    var openHandler = this.dialogElement("openHandler");
    if (
      (this.mLauncher.targetFileIsExecutable &&
        !this.isExemptExecutableExtension) ||
      ((mimeType == "application/octet-stream" ||
        mimeType == "application/x-msdos-program" ||
        mimeType == "application/x-msdownload") &&
        !openWithDefaultOK)
    ) {
      this.dialogElement("open").disabled = true;
      openHandler.disabled = true;
      openHandler.selectedItem = null;
      modeGroup.selectedItem = this.dialogElement("save");
      return;
    }

    // Fill in helper app info, if there is any.
    try {
      this.chosenApp =
        this.mLauncher.MIMEInfo.preferredApplicationHandler.QueryInterface(
          Ci.nsILocalHandlerApp
        );
    } catch (e) {
      this.chosenApp = null;
    }
    // Initialize "default application" field.
    this.initDefaultApp();

    var otherHandler = this.dialogElement("otherHandler");

    // Fill application name textbox.
    if (
      this.chosenApp &&
      this.chosenApp.executable &&
      this.chosenApp.executable.path
    ) {
      otherHandler.setAttribute(
        "path",
        this.getPath(this.chosenApp.executable)
      );

      otherHandler.label = this.getFileDisplayName(this.chosenApp.executable);
      otherHandler.hidden = false;
    }

    openHandler.selectedIndex = 0;
    var defaultOpenHandler = this.dialogElement("defaultHandler");

    if (this.shouldShowInternalHandlerOption()) {
      this.dialogElement("handleInternally").hidden = false;
    }

    if (
      this.mLauncher.MIMEInfo.preferredAction ==
      this.nsIMIMEInfo.useSystemDefault
    ) {
      // Open (using system default).
      modeGroup.selectedItem = this.dialogElement("open");
    } else if (
      this.mLauncher.MIMEInfo.preferredAction == this.nsIMIMEInfo.useHelperApp
    ) {
      // Open with given helper app.
      modeGroup.selectedItem = this.dialogElement("open");
      openHandler.selectedItem =
        otherHandler && !otherHandler.hidden
          ? otherHandler
          : defaultOpenHandler;
    } else if (
      !this.dialogElement("handleInternally").hidden &&
      this.mLauncher.MIMEInfo.preferredAction ==
        this.nsIMIMEInfo.handleInternally
    ) {
      // Handle internally
      modeGroup.selectedItem = this.dialogElement("handleInternally");
    } else {
      // Save to disk.
      modeGroup.selectedItem = this.dialogElement("save");
    }

    // If we don't have a "default app" then disable that choice.
    if (!openWithDefaultOK) {
      var isSelected = defaultOpenHandler.selected;

      // Disable that choice.
      defaultOpenHandler.hidden = true;
      // If that's the default, then switch to "save to disk."
      if (isSelected) {
        openHandler.selectedIndex = 1;
        if (this.dialogElement("open").selected) {
          modeGroup.selectedItem = this.dialogElement("save");
        }
      }
    }

    otherHandler.nextSibling.hidden =
      otherHandler.nextSibling.nextSibling.hidden = false;
    this.updateOKButton();
  },

  // Returns the user-selected application
  helperAppChoice() {
    return this.chosenApp;
  },

  get saveToDisk() {
    return this.dialogElement("save").selected;
  },

  get useOtherHandler() {
    return (
      this.dialogElement("open").selected &&
      this.dialogElement("openHandler").selectedIndex == 1
    );
  },

  get useSystemDefault() {
    return (
      this.dialogElement("open").selected &&
      this.dialogElement("openHandler").selectedIndex == 0
    );
  },

  get handleInternally() {
    return this.dialogElement("handleInternally").selected;
  },

  toggleRememberChoice(aCheckbox) {
    this.dialogElement("settingsChange").hidden = !aCheckbox.checked;
    this.mDialog.sizeToContent();
  },

  openHandlerCommand() {
    var openHandler = this.dialogElement("openHandler");
    if (openHandler.selectedItem.id == "choose") {
      this.chooseApp();
    } else {
      openHandler.setAttribute(
        "lastSelectedItemID",
        openHandler.selectedItem.id
      );
    }
  },

  updateOKButton() {
    var ok = false;
    if (this.dialogElement("save").selected) {
      // This is always OK.
      ok = true;
    } else if (this.dialogElement("open").selected) {
      switch (this.dialogElement("openHandler").selectedIndex) {
        case 0:
          // No app need be specified in this case.
          ok = true;
          break;
        case 1:
          // only enable the OK button if we have a default app to use or if
          // the user chose an app....
          ok =
            this.chosenApp ||
            /\S/.test(this.dialogElement("otherHandler").getAttribute("path"));
          break;
      }
    }

    // Enable Ok button if ok to press.
    let dialog = this.mDialog.document.getElementById("unknownContentType");
    dialog.getButton("accept").disabled = !ok;
  },

  // Returns true iff the user-specified helper app has been modified.
  appChanged() {
    return (
      this.helperAppChoice() !=
      this.mLauncher.MIMEInfo.preferredApplicationHandler
    );
  },

  updateMIMEInfo() {
    let { MIMEInfo } = this.mLauncher;

    // Don't erase the preferred choice being internal handler
    // -- this dialog is often the result of the handler fallback
    // (e.g. Content-Disposition was set as attachment) and we don't
    // want to inadvertently cause that to always show the dialog if
    // users don't want that behaviour.

    // Note: this is the same condition as the one in initDialog
    // which avoids ticking the checkbox. The user can still change
    // the action by ticking the checkbox, or by using the prefs to
    // manually select always ask (at which point `areAlwaysOpeningInternally`
    // will be false, which means `discardUpdate` will be false, which means
    // we'll store the last-selected option even if the filetype's pref is
    // set to always ask).
    let areAlwaysOpeningInternally =
      MIMEInfo.preferredAction == Ci.nsIMIMEInfo.handleInternally &&
      !MIMEInfo.alwaysAskBeforeHandling;
    let discardUpdate =
      areAlwaysOpeningInternally &&
      !this.dialogElement("rememberChoice").checked;

    var needUpdate = false;
    // If current selection differs from what's in the mime info object,
    // then we need to update.
    if (this.saveToDisk) {
      needUpdate =
        this.mLauncher.MIMEInfo.preferredAction != this.nsIMIMEInfo.saveToDisk;
      if (needUpdate) {
        this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.saveToDisk;
      }
    } else if (this.useSystemDefault) {
      needUpdate =
        this.mLauncher.MIMEInfo.preferredAction !=
        this.nsIMIMEInfo.useSystemDefault;
      if (needUpdate) {
        this.mLauncher.MIMEInfo.preferredAction =
          this.nsIMIMEInfo.useSystemDefault;
      }
    } else if (this.useOtherHandler) {
      // For "open with", we need to check both preferred action and whether the user chose
      // a new app.
      needUpdate =
        this.mLauncher.MIMEInfo.preferredAction !=
          this.nsIMIMEInfo.useHelperApp || this.appChanged();
      if (needUpdate) {
        this.mLauncher.MIMEInfo.preferredAction = this.nsIMIMEInfo.useHelperApp;
        // App may have changed - Update application
        var app = this.helperAppChoice();
        this.mLauncher.MIMEInfo.preferredApplicationHandler = app;
      }
    } else if (this.handleInternally) {
      needUpdate =
        this.mLauncher.MIMEInfo.preferredAction !=
        this.nsIMIMEInfo.handleInternally;
      if (needUpdate) {
        this.mLauncher.MIMEInfo.preferredAction =
          this.nsIMIMEInfo.handleInternally;
      }
    }
    // We will also need to update if the "always ask" flag has changed.
    needUpdate =
      needUpdate ||
      this.mLauncher.MIMEInfo.alwaysAskBeforeHandling !=
        !this.dialogElement("rememberChoice").checked;

    // One last special case: If the input "always ask" flag was false, then we always
    // update.  In that case we are displaying the helper app dialog for the first
    // time for this mime type and we need to store the user's action in the handler service
    // (whether that action has changed or not; if it didn't change, then we need
    // to store the "always ask" flag so the helper app dialog will or won't display
    // next time, per the user's selection).
    needUpdate = needUpdate || !this.mLauncher.MIMEInfo.alwaysAskBeforeHandling;

    // Make sure mime info has updated setting for the "always ask" flag.
    this.mLauncher.MIMEInfo.alwaysAskBeforeHandling =
      !this.dialogElement("rememberChoice").checked;

    return needUpdate && !discardUpdate;
  },

  // See if the user changed things, and if so, store this mime type in the
  // handler service.
  updateHelperAppPref() {
    var handlerInfo = this.mLauncher.MIMEInfo;
    var hs = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
      Ci.nsIHandlerService
    );
    hs.store(handlerInfo);
  },

  onOK(aEvent) {
    // Verify typed app path, if necessary.
    if (this.useOtherHandler) {
      var helperApp = this.helperAppChoice();
      if (
        !helperApp ||
        !helperApp.executable ||
        !helperApp.executable.exists()
      ) {
        // Show alert and try again.
        var bundle = this.dialogElement("strings");
        var msg = bundle.getFormattedString("badApp", [
          this.dialogElement("otherHandler").getAttribute("path"),
        ]);
        Services.prompt.alert(
          this.mDialog,
          bundle.getString("badApp.title"),
          msg
        );

        // Disable the OK button.
        let dialog = this.mDialog.document.getElementById("unknownContentType");
        dialog.getButton("accept").disabled = true;
        this.dialogElement("mode").focus();

        // Clear chosen application.
        this.chosenApp = null;

        // Leave dialog up.
        aEvent.preventDefault();
      }
    }

    // Remove our web progress listener (a progress dialog will be
    // taking over).
    this.mLauncher.setWebProgressListener(null);

    // saveToDisk and setDownloadToLaunch can return errors in
    // certain circumstances (e.g. The user clicks cancel in the
    // "Save to Disk" dialog. In those cases, we don't want to
    // update the helper application preferences in the RDF file.
    try {
      var needUpdate = this.updateMIMEInfo();

      if (this.dialogElement("save").selected) {
        // see @notify
        // we cannot use opener's setTimeout, see bug 420405
        this._saveToDiskTimer =
          Cc["@mozilla.org/timer;1"].createInstance(nsITimer);
        this._saveToDiskTimer.initWithCallback(this, 0, nsITimer.TYPE_ONE_SHOT);
      } else {
        let uri = this.mLauncher.source;
        // Launch local files immediately without downloading them:
        if (uri instanceof Ci.nsIFileURL) {
          this.mLauncher.launchLocalFile();
        } else {
          this.mLauncher.setDownloadToLaunch(this.handleInternally, null);
        }
      }

      // Update user pref for this mime type (if necessary). We do not
      // store anything in the mime type preferences for the ambiguous
      // type application/octet-stream. We do NOT do this for
      // application/x-msdownload since we want users to be able to
      // autodownload these to disk.
      if (
        needUpdate &&
        this.mLauncher.MIMEInfo.MIMEType != "application/octet-stream"
      ) {
        this.updateHelperAppPref();
      }
    } catch (e) {
      console.error(e);
    }

    this.onUnload();
  },

  onCancel() {
    // Remove our web progress listener.
    this.mLauncher.setWebProgressListener(null);

    // Cancel app launcher.
    try {
      this.mLauncher.cancel(Cr.NS_BINDING_ABORTED);
    } catch (e) {
      console.error(e);
    }

    this.onUnload();
  },

  onUnload() {
    this.mDialog.document.removeEventListener("dialogaccept", this);
    this.mDialog.document.removeEventListener("dialogcancel", this);

    // Unhook dialog from this object.
    this.mDialog.dialog = null;
  },

  handleEvent(aEvent) {
    switch (aEvent.type) {
      case "dialogaccept":
        this.onOK(aEvent);
        break;
      case "dialogcancel":
        this.onCancel();
        break;
    }
  },

  dialogElement(id) {
    return this.mDialog.document.getElementById(id);
  },

  // Retrieve the pretty description from the file
  getFileDisplayName: function getFileDisplayName(file) {
    if (AppConstants.platform == "win") {
      if (file instanceof Ci.nsILocalFileWin) {
        try {
          return file.getVersionInfoField("FileDescription");
        } catch (e) {}
      }
    } else if (AppConstants.platform == "macosx") {
      if (file instanceof Ci.nsILocalFileMac) {
        try {
          return file.bundleDisplayName;
        } catch (e) {}
      }
    }
    return file.leafName;
  },

  finishChooseApp() {
    if (this.chosenApp) {
      // Show the "handler" menulist since we have a (user-specified)
      // application now.
      this.dialogElement("modeDeck").setAttribute("selectedIndex", "0");

      // Update dialog.
      var otherHandler = this.dialogElement("otherHandler");
      otherHandler.removeAttribute("hidden");
      otherHandler.setAttribute(
        "path",
        this.getPath(this.chosenApp.executable)
      );
      if (AppConstants.platform == "win") {
        otherHandler.label = this.getFileDisplayName(this.chosenApp.executable);
      } else {
        otherHandler.label = this.chosenApp.name;
      }
      this.dialogElement("openHandler").selectedIndex = 1;
      this.dialogElement("openHandler").setAttribute(
        "lastSelectedItemID",
        "otherHandler"
      );

      this.dialogElement("mode").selectedItem = this.dialogElement("open");
    } else {
      var openHandler = this.dialogElement("openHandler");
      var lastSelectedID = openHandler.getAttribute("lastSelectedItemID");
      if (!lastSelectedID) {
        lastSelectedID = "defaultHandler";
      }
      openHandler.selectedItem = this.dialogElement(lastSelectedID);
    }
  },
  // chooseApp:  Open file picker and prompt user for application.
  chooseApp() {
    if (AppConstants.platform == "win") {
      // Protect against the lack of an extension
      var fileExtension = "";
      try {
        fileExtension = this.mLauncher.MIMEInfo.primaryExtension;
      } catch (ex) {}

      // Try to use the pretty description of the type, if one is available.
      var typeString = this.mLauncher.MIMEInfo.description;

      if (!typeString) {
        // If there is none, use the extension to
        // identify the file, e.g. "ZIP file"
        if (fileExtension) {
          typeString = this.dialogElement("strings").getFormattedString(
            "fileType",
            [fileExtension.toUpperCase()]
          );
        } else {
          // If we can't even do that, just give up and show the MIME type.
          typeString = this.mLauncher.MIMEInfo.MIMEType;
        }
      }

      var params = {};
      params.title = this.dialogElement("strings").getString(
        "chooseAppFilePickerTitle"
      );
      params.description = typeString;
      params.filename = this.mLauncher.suggestedFileName;
      params.mimeInfo = this.mLauncher.MIMEInfo;
      params.handlerApp = null;

      this.mDialog.openDialog(
        "chrome://global/content/appPicker.xhtml",
        null,
        "chrome,modal,centerscreen,titlebar,dialog=yes",
        params
      );

      if (
        params.handlerApp &&
        params.handlerApp.executable &&
        params.handlerApp.executable.isFile()
      ) {
        // Remember the file they chose to run.
        this.chosenApp = params.handlerApp;
      }
    } else if ("@mozilla.org/applicationchooser;1" in Cc) {
      var nsIApplicationChooser = Ci.nsIApplicationChooser;
      var appChooser = Cc["@mozilla.org/applicationchooser;1"].createInstance(
        nsIApplicationChooser
      );
      appChooser.init(
        this.mDialog,
        this.dialogElement("strings").getString("chooseAppFilePickerTitle")
      );
      var contentTypeDialogObj = this;
      let appChooserCallback = function appChooserCallback_done(aResult) {
        if (aResult) {
          contentTypeDialogObj.chosenApp = aResult.QueryInterface(
            Ci.nsILocalHandlerApp
          );
        }
        contentTypeDialogObj.finishChooseApp();
      };
      appChooser.open(this.mLauncher.MIMEInfo.MIMEType, appChooserCallback);
      // The finishChooseApp is called from appChooserCallback
      return;
    } else {
      var nsIFilePicker = Ci.nsIFilePicker;
      var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
      fp.init(
        this.mDialog.browsingContext,
        this.dialogElement("strings").getString("chooseAppFilePickerTitle"),
        nsIFilePicker.modeOpen
      );

      fp.appendFilters(nsIFilePicker.filterApps);

      fp.open(aResult => {
        if (aResult == nsIFilePicker.returnOK && fp.file) {
          // Remember the file they chose to run.
          var localHandlerApp = Cc[
            "@mozilla.org/uriloader/local-handler-app;1"
          ].createInstance(Ci.nsILocalHandlerApp);
          localHandlerApp.executable = fp.file;
          this.chosenApp = localHandlerApp;
        }
        this.finishChooseApp();
      });
      // The finishChooseApp is called from fp.open() callback
      return;
    }

    this.finishChooseApp();
  },

  shouldShowInternalHandlerOption() {
    let browsingContext = this.mDialog.BrowsingContext.get(
      this.mLauncher.browsingContextId
    );
    let primaryExtension = "";
    try {
      // The primaryExtension getter may throw if there are no
      // known extensions for this mimetype.
      primaryExtension = this.mLauncher.MIMEInfo.primaryExtension;
    } catch (e) {}

    // Only available for PDF files when pdf.js is enabled.
    // Skip if the current window uses the resource scheme, to avoid
    // showing the option when using the Download button in pdf.js.
    if (primaryExtension == "pdf") {
      return (
        !(
          this.mLauncher.source.schemeIs("blob") ||
          this.mLauncher.source.equalsExceptRef(
            browsingContext.currentWindowGlobal.documentURI
          )
        ) &&
        !Services.prefs.getBoolPref("pdfjs.disabled", true) &&
        Services.prefs.getBoolPref(
          "browser.helperApps.showOpenOptionForPdfJS",
          false
        )
      );
    }

    return (
      Services.prefs.getBoolPref(
        "browser.helperApps.showOpenOptionForViewableInternally",
        false
      ) &&
      lazy.DownloadIntegration.shouldViewDownloadInternally(
        this.mLauncher.MIMEInfo.MIMEType,
        primaryExtension
      )
    );
  },

  // Turn this on to get debugging messages.
  debug: false,

  // Dump text (if debug is on).
  dump(text) {
    if (this.debug) {
      dump(text);
    }
  },
};
PK
!<�^6�
�
modules/HiddenFrame.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const XUL_PAGE = Services.io.newURI("chrome://global/content/win.xhtml");

const gAllHiddenFrames = new Set();

let cleanupRegistered = false;
function ensureCleanupRegistered() {
  if (!cleanupRegistered) {
    cleanupRegistered = true;
    Services.obs.addObserver(function () {
      for (let hiddenFrame of gAllHiddenFrames) {
        hiddenFrame.destroy();
      }
    }, "xpcom-shutdown");
  }
}

/**
 * An hidden frame object. It takes care of creating a windowless browser and
 * passing the window containing a blank XUL <window> back.
 */
export function HiddenFrame() {}

HiddenFrame.prototype = {
  _frame: null,
  _browser: null,
  _listener: null,
  _webProgress: null,
  _deferred: null,

  /**
   * Gets the |contentWindow| of the hidden frame. Creates the frame if needed.
   * @returns Promise Returns a promise which is resolved when the hidden frame has finished
   *          loading.
   */
  get() {
    if (!this._deferred) {
      this._deferred = Promise.withResolvers();
      this._create();
    }

    return this._deferred.promise;
  },

  /**
   * Fetch a sync ref to the window inside the frame (needed for the add-on SDK).
   */
  getWindow() {
    this.get();
    return this._browser.document.ownerGlobal;
  },

  destroy() {
    if (this._browser) {
      if (this._listener) {
        this._webProgress.removeProgressListener(this._listener);
        this._listener = null;
        this._webProgress = null;
      }
      this._frame = null;
      this._deferred = null;

      gAllHiddenFrames.delete(this);
      this._browser.close();
      this._browser = null;
    }
  },

  _create() {
    ensureCleanupRegistered();
    let chromeFlags = Ci.nsIWebBrowserChrome.CHROME_REMOTE_WINDOW;
    if (Services.appinfo.fissionAutostart) {
      chromeFlags |= Ci.nsIWebBrowserChrome.CHROME_FISSION_WINDOW;
    }
    this._browser = Services.appShell.createWindowlessBrowser(
      true,
      chromeFlags
    );
    this._browser.QueryInterface(Ci.nsIInterfaceRequestor);
    gAllHiddenFrames.add(this);
    this._webProgress = this._browser.getInterface(Ci.nsIWebProgress);
    this._listener = {
      QueryInterface: ChromeUtils.generateQI([
        "nsIWebProgressListener",
        "nsIWebProgressListener2",
        "nsISupportsWeakReference",
      ]),
    };
    this._listener.onStateChange = (wbp, request, stateFlags) => {
      if (!request) {
        return;
      }
      if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
        this._webProgress.removeProgressListener(this._listener);
        this._listener = null;
        this._webProgress = null;
        // Get the window reference via the document.
        this._frame = this._browser.document.ownerGlobal;
        this._deferred.resolve(this._frame);
      }
    };
    this._webProgress.addProgressListener(
      this._listener,
      Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT
    );
    let docShell = this._browser.docShell;
    let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
    docShell.createAboutBlankDocumentViewer(systemPrincipal, systemPrincipal);
    let browsingContext = this._browser.browsingContext;
    browsingContext.useGlobalHistory = false;
    let loadURIOptions = {
      triggeringPrincipal: systemPrincipal,
    };
    this._browser.loadURI(XUL_PAGE, loadURIOptions);
  },
};
PK
!<8bl�_�_/modules/IdentityCredentialPromptService.sys.mjs/**
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "IDNService",
  "@mozilla.org/network/idn-service;1",
  "nsIIDNService"
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "SELECT_FIRST_IN_UI_LISTS",
  "dom.security.credentialmanagement.identity.select_first_in_ui_lists",
  false
);

ChromeUtils.defineESModuleGetters(lazy, {
  GeckoViewIdentityCredential:
    "resource://gre/modules/GeckoViewIdentityCredential.sys.mjs",
});
const BEST_HEADER_ICON_SIZE = 16;
const BEST_ICON_SIZE = 32;

// Used in plain mochitests to enable automation
function fulfilledPromiseFromFirstListElement(list) {
  if (list.length) {
    return Promise.resolve(0);
  }
  return Promise.reject();
}

// Converts a "blob" to a data URL
function blobToDataUrl(blob) {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.addEventListener("loadend", function () {
      if (reader.error) {
        reject(reader.error);
      }
      resolve(reader.result);
    });
    reader.readAsDataURL(blob);
  });
}

// Converts a URL into a data:// url, suitable for inclusion in Chrome UI
async function fetchToDataUrl(url) {
  let result = await fetch(url);
  if (!result.ok) {
    throw result.status;
  }
  let blob = await result.blob();
  let data = blobToDataUrl(blob);
  return data;
}

/**
 * Class implementing the nsIIdentityCredentialPromptService
 * */
export class IdentityCredentialPromptService {
  classID = Components.ID("{936007db-a957-4f1d-a23d-f7d9403223e6}");
  QueryInterface = ChromeUtils.generateQI([
    "nsIIdentityCredentialPromptService",
  ]);

  async loadIconFromManifest(
    providerManifest,
    bestIconSize = BEST_ICON_SIZE,
    defaultIcon = null
  ) {
    if (providerManifest?.branding?.icons?.length) {
      // Prefer a vector icon, then an exactly sized icon,
      // the the largest icon available.
      let iconsArray = providerManifest.branding.icons;
      let vectorIcon = iconsArray.find(icon => !icon.size);
      if (vectorIcon) {
        return fetchToDataUrl(vectorIcon.url);
      }
      let exactIcon = iconsArray.find(icon => icon.size == bestIconSize);
      if (exactIcon) {
        return fetchToDataUrl(exactIcon.url);
      }
      let biggestIcon = iconsArray.sort(
        (iconA, iconB) => iconB.size - iconA.size
      )[0];
      if (biggestIcon) {
        return fetchToDataUrl(biggestIcon.url);
      }
    }

    return defaultIcon;
  }

  /**
   * Ask the user, using a PopupNotification, to select an Identity Provider from a provided list.
   * @param {BrowsingContext} browsingContext - The BrowsingContext of the document requesting an identity credential via navigator.credentials.get()
   * @param {IdentityProviderConfig[]} identityProviders - The list of identity providers the user selects from
   * @param {IdentityProviderAPIConfig[]} identityManifests - The manifests corresponding 1-to-1 with identityProviders
   * @returns {Promise<number>} The user-selected identity provider
   */
  async showProviderPrompt(
    browsingContext,
    identityProviders,
    identityManifests
  ) {
    // For testing only.
    if (lazy.SELECT_FIRST_IN_UI_LISTS) {
      return fulfilledPromiseFromFirstListElement(identityProviders);
    }
    let browser = browsingContext.top.embedderElement;
    if (!browser) {
      throw new Error("Null browser provided");
    }

    if (identityProviders.length != identityManifests.length) {
      throw new Error("Mismatch argument array length");
    }

    // Map each identity manifest to a promise that would resolve to its icon
    let promises = identityManifests.map(async providerManifest => {
      // we don't need to set default icon because default icon is already set on popup-notifications.inc
      const iconResult = await this.loadIconFromManifest(providerManifest);
      // If we didn't have a manifest with an icon, push a rejection.
      // This will be replaced with the default icon.
      return iconResult ? iconResult : Promise.reject();
    });

    const providerNames = identityManifests.map(
      providerManifest => providerManifest?.branding?.name
    );

    // Sanity check that we made one promise per IDP.
    if (promises.length != identityManifests.length) {
      throw new Error("Mismatch promise array length");
    }

    let iconResults = await Promise.allSettled(promises);
    if (AppConstants.platform === "android") {
      const providers = [];
      for (const [providerIndex, provider] of identityProviders.entries()) {
        let providerURL = new URL(provider.configURL);
        let displayDomain = lazy.IDNService.convertToDisplayIDN(
          providerURL.host
        );

        let iconResult = iconResults[providerIndex];
        const data = {
          id: providerIndex,
          icon: iconResult.value,
          name: providerNames[providerIndex],
          domain: displayDomain,
        };
        providers.push(data);
      }

      return new Promise((resolve, reject) => {
        lazy.GeckoViewIdentityCredential.onShowProviderPrompt(
          browsingContext,
          providers,
          resolve,
          reject
        );
      });
    }

    // Localize all strings to be used
    // Bug 1797154 - Convert localization calls to use the async formatValues.
    let localization = new Localization(
      ["browser/identityCredentialNotification.ftl"],
      true
    );
    let headerMessage = localization.formatValueSync(
      "identity-credential-header-providers"
    );
    let [accept, cancel] = localization.formatMessagesSync([
      { id: "identity-credential-accept-button" },
      { id: "identity-credential-cancel-button" },
    ]);

    let cancelLabel = cancel.attributes.find(x => x.name == "label").value;
    let cancelKey = cancel.attributes.find(x => x.name == "accesskey").value;
    let acceptLabel = accept.attributes.find(x => x.name == "label").value;
    let acceptKey = accept.attributes.find(x => x.name == "accesskey").value;

    // Build the choices into the panel
    let listBox = browser.ownerDocument.getElementById(
      "identity-credential-provider-selector-container"
    );
    while (listBox.firstChild) {
      listBox.removeChild(listBox.lastChild);
    }
    let itemTemplate = browser.ownerDocument.getElementById(
      "template-credential-provider-list-item"
    );
    for (const [providerIndex, provider] of identityProviders.entries()) {
      let providerURL = new URL(provider.configURL);
      let displayDomain = lazy.IDNService.convertToDisplayIDN(providerURL.host);
      let newItem = itemTemplate.content.firstElementChild.cloneNode(true);

      // Create the radio button,
      // including the check callback and the initial state
      let newRadio = newItem.getElementsByClassName(
        "identity-credential-list-item-radio"
      )[0];
      newRadio.value = providerIndex;
      newRadio.addEventListener("change", function (event) {
        for (let item of listBox.children) {
          item.classList.remove("checked");
        }
        if (event.target.checked) {
          event.target.parentElement.classList.add("checked");
        }
      });
      if (providerIndex == 0) {
        newRadio.checked = true;
        newItem.classList.add("checked");
      }

      // Set the icon to the data url if we have one
      let iconResult = iconResults[providerIndex];
      if (iconResult.status == "fulfilled") {
        let newIcon = newItem.getElementsByClassName(
          "identity-credential-list-item-icon"
        )[0];
        newIcon.setAttribute("src", iconResult.value);
      }

      // Set the words that the user sees in the selection
      newItem.getElementsByClassName(
        "identity-credential-list-item-label-primary"
      )[0].textContent = providerNames[providerIndex] || displayDomain;
      newItem.getElementsByClassName(
        "identity-credential-list-item-label-secondary"
      )[0].hidden = true;

      if (providerNames[providerIndex] && displayDomain) {
        newItem.getElementsByClassName(
          "identity-credential-list-item-label-secondary"
        )[0].hidden = false;
        newItem.getElementsByClassName(
          "identity-credential-list-item-label-secondary"
        )[0].textContent = displayDomain;
      }

      // Add the new item to the DOM!
      listBox.append(newItem);
    }

    // Create a new promise to wrap the callbacks of the popup buttons
    return new Promise((resolve, reject) => {
      // Construct the necessary arguments for notification behavior
      let options = {
        hideClose: true,
        eventCallback: (topic, nextRemovalReason, isCancel) => {
          if (topic == "removed" && isCancel) {
            reject();
          }
        },
      };
      let mainAction = {
        label: acceptLabel,
        accessKey: acceptKey,
        callback(_event) {
          let result = listBox.querySelector(
            ".identity-credential-list-item-radio:checked"
          ).value;
          resolve(parseInt(result));
        },
      };
      let secondaryActions = [
        {
          label: cancelLabel,
          accessKey: cancelKey,
          callback(_event) {
            reject();
          },
        },
      ];

      // Show the popup
      browser.ownerDocument.getElementById(
        "identity-credential-provider"
      ).hidden = false;
      browser.ownerDocument.getElementById(
        "identity-credential-policy"
      ).hidden = true;
      browser.ownerDocument.getElementById(
        "identity-credential-account"
      ).hidden = true;
      browser.ownerDocument.getElementById(
        "identity-credential-header"
      ).hidden = true;
      browser.ownerGlobal.PopupNotifications.show(
        browser,
        "identity-credential",
        headerMessage,
        "identity-credential-notification-icon",
        mainAction,
        secondaryActions,
        options
      );
    });
  }

  /**
   * Ask the user, using a PopupNotification, to approve or disapprove of the policies of the Identity Provider.
   * @param {BrowsingContext} browsingContext - The BrowsingContext of the document requesting an identity credential via navigator.credentials.get()
   * @param {IdentityProviderConfig} identityProvider - The Identity Provider that the user has selected to use
   * @param {IdentityProviderAPIConfig} identityManifest - The Identity Provider that the user has selected to use's manifest
   * @param {IdentityCredentialMetadata} identityCredentialMetadata - The metadata displayed to the user
   * @returns {Promise<bool>} A boolean representing the user's acceptance of the metadata.
   */
  async showPolicyPrompt(
    browsingContext,
    identityProvider,
    identityManifest,
    identityCredentialMetadata
  ) {
    // For testing only.
    if (lazy.SELECT_FIRST_IN_UI_LISTS) {
      return Promise.resolve(true);
    }
    if (
      !identityCredentialMetadata ||
      !identityCredentialMetadata.privacy_policy_url ||
      !identityCredentialMetadata.terms_of_service_url
    ) {
      return Promise.resolve(true);
    }

    let iconResult = await this.loadIconFromManifest(
      identityManifest,
      BEST_HEADER_ICON_SIZE,
      "chrome://global/skin/icons/defaultFavicon.svg"
    );

    const providerName = identityManifest?.branding?.name;

    return new Promise(function (resolve, reject) {
      let browser = browsingContext.top.embedderElement;
      if (!browser) {
        reject();
        return;
      }

      let providerURL = new URL(identityProvider.configURL);
      let providerDisplayDomain = lazy.IDNService.convertToDisplayIDN(
        providerURL.host
      );
      let currentBaseDomain =
        browsingContext.currentWindowContext.documentPrincipal.baseDomain;

      if (AppConstants.platform === "android") {
        lazy.GeckoViewIdentityCredential.onShowPolicyPrompt(
          browsingContext,
          identityCredentialMetadata.privacy_policy_url,
          identityCredentialMetadata.terms_of_service_url,
          providerDisplayDomain,
          currentBaseDomain,
          iconResult,
          resolve,
          reject
        );
      } else {
        // Localize the description
        // Bug 1797154 - Convert localization calls to use the async formatValues.
        let localization = new Localization(
          ["browser/identityCredentialNotification.ftl"],
          true
        );
        let [accept, cancel] = localization.formatMessagesSync([
          { id: "identity-credential-accept-button" },
          { id: "identity-credential-cancel-button" },
        ]);

        let cancelLabel = cancel.attributes.find(x => x.name == "label").value;
        let cancelKey = cancel.attributes.find(
          x => x.name == "accesskey"
        ).value;
        let acceptLabel = accept.attributes.find(x => x.name == "label").value;
        let acceptKey = accept.attributes.find(
          x => x.name == "accesskey"
        ).value;

        let title = localization.formatValueSync(
          "identity-credential-policy-title",
          {
            provider: providerName || providerDisplayDomain,
          }
        );

        if (iconResult) {
          let headerIcon = browser.ownerDocument.getElementsByClassName(
            "identity-credential-header-icon"
          )[0];
          headerIcon.setAttribute("src", iconResult);
        }

        const headerText = browser.ownerDocument.getElementById(
          "identity-credential-header-text"
        );
        headerText.textContent = title;

        let privacyPolicyAnchor = browser.ownerDocument.getElementById(
          "identity-credential-privacy-policy"
        );
        privacyPolicyAnchor.href =
          identityCredentialMetadata.privacy_policy_url;
        let termsOfServiceAnchor = browser.ownerDocument.getElementById(
          "identity-credential-terms-of-service"
        );
        termsOfServiceAnchor.href =
          identityCredentialMetadata.terms_of_service_url;

        // Populate the content of the policy panel
        let description = browser.ownerDocument.getElementById(
          "identity-credential-policy-explanation"
        );
        browser.ownerDocument.l10n.setAttributes(
          description,
          "identity-credential-policy-description",
          {
            host: currentBaseDomain,
            provider: providerDisplayDomain,
          }
        );

        // Construct the necessary arguments for notification behavior
        let options = {
          hideClose: true,
          eventCallback: (topic, nextRemovalReason, isCancel) => {
            if (topic == "removed" && isCancel) {
              reject();
            }
          },
        };
        let mainAction = {
          label: acceptLabel,
          accessKey: acceptKey,
          callback(_event) {
            resolve(true);
          },
        };
        let secondaryActions = [
          {
            label: cancelLabel,
            accessKey: cancelKey,
            callback(_event) {
              resolve(false);
            },
          },
        ];

        // Show the popup
        let ownerDocument = browser.ownerDocument;
        ownerDocument.getElementById(
          "identity-credential-provider"
        ).hidden = true;
        ownerDocument.getElementById(
          "identity-credential-policy"
        ).hidden = false;
        ownerDocument.getElementById(
          "identity-credential-account"
        ).hidden = true;
        ownerDocument.getElementById(
          "identity-credential-header"
        ).hidden = false;
        browser.ownerGlobal.PopupNotifications.show(
          browser,
          "identity-credential",
          "",
          "identity-credential-notification-icon",
          mainAction,
          secondaryActions,
          options
        );
      }
    });
  }

  /**
   * Ask the user, using a PopupNotification, to select an account from a provided list.
   * @param {BrowsingContext} browsingContext - The BrowsingContext of the document requesting an identity credential via navigator.credentials.get()
   * @param {IdentityProviderAccountList} accountList - The list of accounts the user selects from
   * @param {IdentityProviderConfig} provider - The selected identity provider
   * @param {IdentityProviderAPIConfig} providerManifest - The manifest of the selected identity provider
   * @returns {Promise<IdentityProviderAccount>} The user-selected account
   */
  async showAccountListPrompt(
    browsingContext,
    accountList,
    provider,
    providerManifest
  ) {
    // For testing only.
    if (lazy.SELECT_FIRST_IN_UI_LISTS) {
      return fulfilledPromiseFromFirstListElement(accountList.accounts);
    }

    let browser = browsingContext.top.embedderElement;
    if (!browser) {
      throw new Error("Null browser provided");
    }

    // Map to an array of promises that resolve to a data URL,
    // encoding the corresponding account's picture
    let promises = accountList.accounts.map(async account => {
      if (!account?.picture) {
        throw new Error("Missing picture");
      }
      return fetchToDataUrl(account.picture);
    });

    // Sanity check that we made one promise per account.
    if (promises.length != accountList.accounts.length) {
      throw new Error("Incorrect number of promises obtained");
    }

    let pictureResults = await Promise.allSettled(promises);

    // Localize all strings to be used
    // Bug 1797154 - Convert localization calls to use the async formatValues.
    let localization = new Localization(
      ["browser/identityCredentialNotification.ftl"],
      true
    );
    const providerName = providerManifest?.branding?.name;
    let providerURL = new URL(provider.configURL);
    let displayDomain = lazy.IDNService.convertToDisplayIDN(providerURL.host);

    let headerIconResult = await this.loadIconFromManifest(
      providerManifest,
      BEST_HEADER_ICON_SIZE,
      "chrome://global/skin/icons/defaultFavicon.svg"
    );

    if (AppConstants.platform === "android") {
      const accounts = [];

      for (const [accountIndex, account] of accountList.accounts.entries()) {
        var picture = "";
        let pictureResult = pictureResults[accountIndex];
        if (pictureResult.status == "fulfilled") {
          picture = pictureResult.value;
        }
        account.name;
        account.email;
        const data = {
          id: accountIndex,
          icon: picture,
          name: account.name,
          email: account.email,
        };
        accounts.push(data);
        console.log(data);
      }

      const provider = {
        name: providerName || displayDomain,
        domain: displayDomain,
        icon: headerIconResult,
      };

      const result = {
        provider,
        accounts,
      };

      return new Promise((resolve, reject) => {
        lazy.GeckoViewIdentityCredential.onShowAccountsPrompt(
          browsingContext,
          result,
          resolve,
          reject
        );
      });
    }

    let headerMessage = localization.formatValueSync(
      "identity-credential-header-accounts",
      {
        provider: providerName || displayDomain,
      }
    );

    let [accept, cancel] = localization.formatMessagesSync([
      { id: "identity-credential-sign-in-button" },
      { id: "identity-credential-cancel-button" },
    ]);

    let cancelLabel = cancel.attributes.find(x => x.name == "label").value;
    let cancelKey = cancel.attributes.find(x => x.name == "accesskey").value;
    let acceptLabel = accept.attributes.find(x => x.name == "label").value;
    let acceptKey = accept.attributes.find(x => x.name == "accesskey").value;

    // Build the choices into the panel
    let listBox = browser.ownerDocument.getElementById(
      "identity-credential-account-selector-container"
    );
    while (listBox.firstChild) {
      listBox.removeChild(listBox.lastChild);
    }
    let itemTemplate = browser.ownerDocument.getElementById(
      "template-credential-account-list-item"
    );
    for (const [accountIndex, account] of accountList.accounts.entries()) {
      let newItem = itemTemplate.content.firstElementChild.cloneNode(true);

      // Add the new radio button, including pre-selection and the callback
      let newRadio = newItem.getElementsByClassName(
        "identity-credential-list-item-radio"
      )[0];
      newRadio.value = accountIndex;
      newRadio.addEventListener("change", function (event) {
        for (let item of listBox.children) {
          item.classList.remove("checked");
        }
        if (event.target.checked) {
          event.target.parentElement.classList.add("checked");
        }
      });
      if (accountIndex == 0) {
        newRadio.checked = true;
        newItem.classList.add("checked");
      }

      // Change the default picture if one exists
      let pictureResult = pictureResults[accountIndex];
      if (pictureResult.status == "fulfilled") {
        let newPicture = newItem.getElementsByClassName(
          "identity-credential-list-item-icon"
        )[0];
        newPicture.setAttribute("src", pictureResult.value);
      }

      // Add information to the label
      newItem.getElementsByClassName(
        "identity-credential-list-item-label-primary"
      )[0].textContent = account.name;
      newItem.getElementsByClassName(
        "identity-credential-list-item-label-secondary"
      )[0].textContent = account.email;

      // Add the item to the DOM!
      listBox.append(newItem);
    }

    // Create a new promise to wrap the callbacks of the popup buttons
    return new Promise(function (resolve, reject) {
      // Construct the necessary arguments for notification behavior
      let options = {
        hideClose: true,
        eventCallback: (topic, nextRemovalReason, isCancel) => {
          if (topic == "removed" && isCancel) {
            reject();
          }
        },
      };
      let mainAction = {
        label: acceptLabel,
        accessKey: acceptKey,
        callback(_event) {
          let result = listBox.querySelector(
            ".identity-credential-list-item-radio:checked"
          ).value;
          resolve(parseInt(result));
        },
      };
      let secondaryActions = [
        {
          label: cancelLabel,
          accessKey: cancelKey,
          callback(_event) {
            reject();
          },
        },
      ];

      if (headerIconResult) {
        let headerIcon = browser.ownerDocument.getElementsByClassName(
          "identity-credential-header-icon"
        )[0];
        headerIcon.setAttribute("src", headerIconResult);
      }

      const headerText = browser.ownerDocument.getElementById(
        "identity-credential-header-text"
      );
      headerText.textContent = headerMessage;

      // Show the popup
      browser.ownerDocument.getElementById(
        "identity-credential-provider"
      ).hidden = true;
      browser.ownerDocument.getElementById(
        "identity-credential-policy"
      ).hidden = true;
      browser.ownerDocument.getElementById(
        "identity-credential-account"
      ).hidden = false;
      browser.ownerDocument.getElementById(
        "identity-credential-header"
      ).hidden = false;
      browser.ownerGlobal.PopupNotifications.show(
        browser,
        "identity-credential",
        "",
        "identity-credential-notification-icon",
        mainAction,
        secondaryActions,
        options
      );
    });
  }

  /**
   * Close all UI from the other methods of this module for the provided window.
   * @param {BrowsingContext} browsingContext - The BrowsingContext of the document requesting an identity credential via navigator.credentials.get()
   * @returns
   */
  close(browsingContext) {
    let browser = browsingContext.top.embedderElement;
    if (!browser || AppConstants.platform === "android") {
      return;
    }
    let notification = browser.ownerGlobal.PopupNotifications.getNotification(
      "identity-credential",
      browser
    );
    if (notification) {
      browser.ownerGlobal.PopupNotifications.remove(notification, true);
    }
  }
}
PK
!<l�#q�
�
modules/IgnoreLists.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
  RemoteSettingsClient:
    "resource://services-settings/RemoteSettingsClient.sys.mjs",
});

const SETTINGS_IGNORELIST_KEY = "hijack-blocklists";

class IgnoreListsManager {
  async init() {
    if (!this._ignoreListSettings) {
      this._ignoreListSettings = lazy.RemoteSettings(SETTINGS_IGNORELIST_KEY);
    }
  }

  async getAndSubscribe(listener) {
    await this.init();

    // Trigger a get of the initial value.
    const settings = await this._getIgnoreList();

    // Listen for future updates after we first get the values.
    this._ignoreListSettings.on("sync", listener);

    return settings;
  }

  unsubscribe(listener) {
    if (!this._ignoreListSettings) {
      return;
    }

    this._ignoreListSettings.off("sync", listener);
  }

  async _getIgnoreList() {
    if (this._getSettingsPromise) {
      return this._getSettingsPromise;
    }

    const settings = await (this._getSettingsPromise =
      this._getIgnoreListSettings());
    delete this._getSettingsPromise;
    return settings;
  }

  /**
   * Obtains the current ignore list from remote settings. This includes
   * verifying the signature of the ignore list within the database.
   *
   * If the signature in the database is invalid, the database will be wiped
   * and the stored dump will be used, until the settings next update.
   *
   * Note that this may cause a network check of the certificate, but that
   * should generally be quick.
   *
   * @param {boolean} [firstTime]
   *   Internal boolean to indicate if this is the first time check or not.
   * @returns {array}
   *   An array of objects in the database, or an empty array if none
   *   could be obtained.
   */
  async _getIgnoreListSettings(firstTime = true) {
    let result = [];
    try {
      result = await this._ignoreListSettings.get({
        verifySignature: true,
      });
    } catch (ex) {
      if (
        ex instanceof lazy.RemoteSettingsClient.InvalidSignatureError &&
        firstTime
      ) {
        // The local database is invalid, try and reset it.
        await this._ignoreListSettings.db.clear();
        // Now call this again.
        return this._getIgnoreListSettings(false);
      }
      // Don't throw an error just log it, just continue with no data, and hopefully
      // a sync will fix things later on.
      console.error(ex);
    }
    return result;
  }
}

export const IgnoreLists = new IgnoreListsManager();
PK
!<	��**$modules/ImageObjectProcessor.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
/*
 * ImageObjectProcessor
 * Implementation of Image Object processing algorithms from:
 * http://www.w3.org/TR/appmanifest/#image-object-and-its-members
 *
 * This is intended to be used in conjunction with ManifestProcessor.sys.mjs
 *
 * Creates an object to process Image Objects as defined by the
 * W3C specification. This is used to process things like the
 * icon member and the splash_screen member.
 *
 * Usage:
 *
 *   .process(aManifest, aBaseURL, aMemberName);
 *
 */

export function ImageObjectProcessor(aErrors, aExtractor, aBundle) {
  this.errors = aErrors;
  this.extractor = aExtractor;
  this.domBundle = aBundle;
}

const iconPurposes = Object.freeze(["any", "maskable", "monochrome"]);

// Static getters
Object.defineProperties(ImageObjectProcessor, {
  decimals: {
    get() {
      return /^\d+$/;
    },
  },
  anyRegEx: {
    get() {
      return new RegExp("any", "i");
    },
  },
});

ImageObjectProcessor.prototype.process = function (
  aManifest,
  aBaseURL,
  aMemberName
) {
  const spec = {
    objectName: "manifest",
    object: aManifest,
    property: aMemberName,
    expectedType: "array",
    trim: false,
  };
  const { domBundle, extractor, errors } = this;
  const images = [];
  const value = extractor.extractValue(spec);
  if (Array.isArray(value)) {
    value
      .map(toImageObject)
      // Filter out images that resulted in "failure", per spec.
      .filter(image => image)
      .forEach(image => images.push(image));
  }
  return images;

  function toImageObject(aImageSpec, index) {
    let img; // if "failure" happens below, we return undefined.
    try {
      // can throw
      const src = processSrcMember(aImageSpec, aBaseURL, index);
      // can throw
      const purpose = processPurposeMember(aImageSpec, index);
      const type = processTypeMember(aImageSpec);
      const sizes = processSizesMember(aImageSpec);
      img = {
        src,
        purpose,
        type,
        sizes,
      };
    } catch (err) {
      /* Errors are collected by each process* function */
    }
    return img;
  }

  function processPurposeMember(aImage, index) {
    const spec = {
      objectName: "image",
      object: aImage,
      property: "purpose",
      expectedType: "string",
      trim: true,
      throwTypeError: true,
    };

    // Type errors are treated at "any"...
    let value;
    try {
      value = extractor.extractValue(spec);
    } catch (err) {
      return ["any"];
    }

    // Was only whitespace...
    if (!value) {
      return ["any"];
    }

    const keywords = value.split(/\s+/);

    // Emtpy is treated as "any"...
    if (keywords.length === 0) {
      return ["any"];
    }

    // We iterate over keywords and classify them into:
    const purposes = new Set();
    const unknownPurposes = new Set();
    const repeatedPurposes = new Set();

    for (const keyword of keywords) {
      const canonicalKeyword = keyword.toLowerCase();

      if (purposes.has(canonicalKeyword)) {
        repeatedPurposes.add(keyword);
        continue;
      }

      iconPurposes.includes(canonicalKeyword)
        ? purposes.add(canonicalKeyword)
        : unknownPurposes.add(keyword);
    }

    // Tell developer about unknown purposes...
    if (unknownPurposes.size) {
      const warn = domBundle.formatStringFromName(
        "ManifestImageUnsupportedPurposes",
        [aMemberName, index, [...unknownPurposes].join(" ")]
      );
      errors.push({ warn });
    }

    // Tell developer about repeated purposes...
    if (repeatedPurposes.size) {
      const warn = domBundle.formatStringFromName(
        "ManifestImageRepeatedPurposes",
        [aMemberName, index, [...repeatedPurposes].join(" ")]
      );
      errors.push({ warn });
    }

    if (purposes.size === 0) {
      const warn = domBundle.formatStringFromName("ManifestImageUnusable", [
        aMemberName,
        index,
      ]);
      errors.push({ warn });
      throw new TypeError(warn);
    }

    return [...purposes];
  }

  function processTypeMember(aImage) {
    const charset = {};
    const hadCharset = {};
    const spec = {
      objectName: "image",
      object: aImage,
      property: "type",
      expectedType: "string",
      trim: true,
    };
    let value = extractor.extractValue(spec);
    if (value) {
      value = Services.io.parseRequestContentType(value, charset, hadCharset);
    }
    return value || undefined;
  }

  function processSrcMember(aImage, aBaseURL, index) {
    const spec = {
      objectName: aMemberName,
      object: aImage,
      property: "src",
      expectedType: "string",
      trim: false,
      throwTypeError: true,
    };
    const value = extractor.extractValue(spec);
    let url;
    if (typeof value === "undefined" || value === "") {
      // We throw here as the value is unusable,
      // but it's not an developer error.
      throw new TypeError();
    }
    if (value && value.length) {
      try {
        url = new URL(value, aBaseURL).href;
      } catch (e) {
        const warn = domBundle.formatStringFromName(
          "ManifestImageURLIsInvalid",
          [aMemberName, index, "src", value]
        );
        errors.push({ warn });
        throw e;
      }
    }
    return url;
  }

  function processSizesMember(aImage) {
    const sizes = new Set();
    const spec = {
      objectName: "image",
      object: aImage,
      property: "sizes",
      expectedType: "string",
      trim: true,
    };
    const value = extractor.extractValue(spec);
    if (value) {
      // Split on whitespace and filter out invalid values.
      value
        .split(/\s+/)
        .filter(isValidSizeValue)
        .reduce((collector, size) => collector.add(size), sizes);
    }
    return sizes.size ? Array.from(sizes) : undefined;
    // Implementation of HTML's link@size attribute checker.
    function isValidSizeValue(aSize) {
      const size = aSize.toLowerCase();
      if (ImageObjectProcessor.anyRegEx.test(aSize)) {
        return true;
      }
      if (!size.includes("x") || size.indexOf("x") !== size.lastIndexOf("x")) {
        return false;
      }
      // Split left of x for width, after x for height.
      const widthAndHeight = size.split("x");
      const w = widthAndHeight.shift();
      const h = widthAndHeight.join("x");
      const validStarts = !w.startsWith("0") && !h.startsWith("0");
      const validDecimals = ImageObjectProcessor.decimals.test(w + h);
      return validStarts && validDecimals;
    }
  }
};
PK
!<��L5050modules/IndexedDB.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * @file
 *
 * This module provides Promise-based wrappers around ordinarily
 * IDBRequest-based IndexedDB methods and classes.
 */

/**
 * Wraps the given request object, and returns a Promise which resolves when
 * the requests succeeds or rejects when it fails.
 *
 * @param {IDBRequest} request
 *        An IndexedDB request object to wrap.
 * @returns {Promise}
 */
function wrapRequest(request) {
  return new Promise((resolve, reject) => {
    request.onsuccess = () => {
      resolve(request.result);
    };
    request.onerror = () => {
      reject(request.error);
    };
  });
}

/**
 * Forwards a set of getter properties from a wrapper class to the wrapped
 * object.
 *
 * @param {function} cls
 *        The class constructor for which to forward the getters.
 * @param {string} target
 *        The name of the property which contains the wrapped object to which
 *        to forward the getters.
 * @param {Array<string>} props
 *        A list of property names to forward.
 */
function forwardGetters(cls, target, props) {
  for (let prop of props) {
    Object.defineProperty(cls.prototype, prop, {
      get() {
        return this[target][prop];
      },
    });
  }
}

/**
 * Forwards a set of getter and setter properties from a wrapper class to the
 * wrapped object.
 *
 * @param {function} cls
 *        The class constructor for which to forward the properties.
 * @param {string} target
 *        The name of the property which contains the wrapped object to which
 *        to forward the properties.
 * @param {Array<string>} props
 *        A list of property names to forward.
 */
function forwardProps(cls, target, props) {
  for (let prop of props) {
    Object.defineProperty(cls.prototype, prop, {
      get() {
        return this[target][prop];
      },
      set(value) {
        this[target][prop] = value;
      },
    });
  }
}

/**
 * Wraps a set of IDBRequest-based methods via {@link wrapRequest} and
 * forwards them to the equivalent methods on the wrapped object.
 *
 * @param {function} cls
 *        The class constructor for which to forward the methods.
 * @param {string} target
 *        The name of the property which contains the wrapped object to which
 *        to forward the methods.
 * @param {Array<string>} methods
 *        A list of method names to forward.
 */
function wrapMethods(cls, target, methods) {
  for (let method of methods) {
    cls.prototype[method] = function (...args) {
      return wrapRequest(this[target][method](...args));
    };
  }
}

/**
 * Forwards a set of methods from a wrapper class to the wrapped object.
 *
 * @param {function} cls
 *        The class constructor for which to forward the getters.
 * @param {string} target
 *        The name of the property which contains the wrapped object to which
 *        to forward the methods.
 * @param {Array<string>} methods
 *        A list of method names to forward.
 */
function forwardMethods(cls, target, methods) {
  for (let method of methods) {
    cls.prototype[method] = function (...args) {
      return this[target][method](...args);
    };
  }
}

class Cursor {
  constructor(cursorRequest, source) {
    this.cursorRequest = cursorRequest;
    this.source = source;
    this.cursor = null;
  }

  get done() {
    return !this.cursor;
  }

  // This method is used internally to wait the cursor's IDBRequest to have been
  // completed and the internal cursor has been updated (used when we initially
  // create the cursor from Cursed.openCursor/openKeyCursor, and in the method
  // of this class defined by defineCursorUpdateMethods).
  async awaitRequest() {
    this.cursor = await wrapRequest(this.cursorRequest);
    return this;
  }
}

/**
 * Define the Cursor class methods that update the cursor (continue, continuePrimaryKey
 * and advance) as async functions that call the related IDBCursor methods and
 * await the cursor's IDBRequest to be completed.
 *
 * @param {function} cls
 *        The class constructor for which to define the cursor update methods.
 * @param {Array<string>} methods
 *        A list of "cursor update" method names to define.
 */
function defineCursorUpdateMethods(cls, methods) {
  for (let method of methods) {
    cls.prototype[method] = async function (...args) {
      const promise = this.awaitRequest();
      this.cursor[method](...args);
      await promise;
    };
  }
}

defineCursorUpdateMethods(Cursor, [
  "advance",
  "continue",
  "continuePrimaryKey",
]);

forwardGetters(Cursor, "cursor", ["direction", "key", "primaryKey"]);
wrapMethods(Cursor, "cursor", ["delete", "update"]);

class CursorWithValue extends Cursor {}

forwardGetters(CursorWithValue, "cursor", ["value"]);

class Cursed {
  constructor(cursed) {
    this.cursed = cursed;
  }

  openCursor(...args) {
    const cursor = new CursorWithValue(this.cursed.openCursor(...args), this);
    return cursor.awaitRequest();
  }

  openKeyCursor(...args) {
    const cursor = new Cursor(this.cursed.openKeyCursor(...args), this);
    return cursor.awaitRequest();
  }
}

wrapMethods(Cursed, "cursed", [
  "count",
  "get",
  "getAll",
  "getAllKeys",
  "getKey",
]);

class Index extends Cursed {
  constructor(index, objectStore) {
    super(index);

    this.objectStore = objectStore;
    this.index = index;
  }
}

forwardGetters(Index, "index", [
  "isAutoLocale",
  "keyPath",
  "locale",
  "multiEntry",
  "name",
  "unique",
]);

class ObjectStore extends Cursed {
  constructor(store) {
    super(store);

    this.store = store;
  }

  createIndex(...args) {
    return new Index(this.store.createIndex(...args), this);
  }

  index(...args) {
    return new Index(this.store.index(...args), this);
  }
}

wrapMethods(ObjectStore, "store", ["add", "clear", "delete", "put"]);

forwardMethods(ObjectStore, "store", ["deleteIndex"]);

class Transaction {
  constructor(transaction) {
    this.transaction = transaction;

    this._completionPromise = new Promise((resolve, reject) => {
      transaction.oncomplete = resolve;
      transaction.onerror = () => {
        reject(transaction.error);
      };
      transaction.onabort = () => {
        const error =
          transaction.error ||
          new DOMException("The operation has been aborted", "AbortError");
        reject(error);
      };
    });
  }

  objectStore(name) {
    return new ObjectStore(this.transaction.objectStore(name));
  }

  /**
   * Returns a Promise which resolves when the transaction completes, or
   * rejects when a transaction error or abort occurs.
   *
   * @returns {Promise}
   */
  promiseComplete() {
    return this._completionPromise;
  }
}

forwardGetters(Transaction, "transaction", [
  "db",
  "mode",
  "error",
  "objectStoreNames",
]);

forwardMethods(Transaction, "transaction", ["abort"]);

export class IndexedDB {
  /**
   * Opens the database with the given name, and returns a Promise which
   * resolves to an IndexedDB instance when the operation completes.
   *
   * @param {string} dbName
   *        The name of the database to open.
   * @param {integer} version
   *        The schema version with which the database needs to be opened. If
   *        the database does not exist, or its current schema version does
   *        not match, the `onupgradeneeded` function will be called.
   * @param {function} [onupgradeneeded]
   *        A function which will be called with an IndexedDB object as its
   *        first parameter when the database needs to be created, or its
   *        schema needs to be upgraded. If this function is not provided, the
   *        {@link #onupgradeneeded} method will be called instead.
   *
   * @returns {Promise<IndexedDB>}
   */
  static open(dbName, version, onupgradeneeded = null) {
    let request = indexedDB.open(dbName, version);
    return this._wrapOpenRequest(request, onupgradeneeded);
  }

  /**
   * Opens the database for a given principal and with the given name, returns
   * a Promise which resolves to an IndexedDB instance when the operation completes.
   *
   * @param {nsIPrincipal} principal
   *        The principal to open the database for.
   * @param {string} dbName
   *        The name of the database to open.
   * @param {object} options
   *        The options with which to open the database.
   * @param {integer} options.version
   *        The schema version with which the database needs to be opened. If
   *        the database does not exist, or its current schema version does
   *        not match, the `onupgradeneeded` function will be called.
   * @param {function} [onupgradeneeded]
   *        A function which will be called with an IndexedDB object as its
   *        first parameter when the database needs to be created, or its
   *        schema needs to be upgraded. If this function is not provided, the
   *        {@link #onupgradeneeded} method will be called instead.
   *
   * @returns {Promise<IndexedDB>}
   */
  static openForPrincipal(principal, dbName, options, onupgradeneeded = null) {
    const request = indexedDB.openForPrincipal(principal, dbName, options);
    return this._wrapOpenRequest(request, onupgradeneeded);
  }

  static _wrapOpenRequest(request, onupgradeneeded = null) {
    request.onupgradeneeded = event => {
      let db = new this(request.result);
      if (onupgradeneeded) {
        onupgradeneeded(db, event);
      } else {
        db.onupgradeneeded(event);
      }
    };

    return wrapRequest(request).then(db => new this(db));
  }

  constructor(db) {
    this.db = db;
  }

  onupgradeneeded() {}

  /**
   * Opens a transaction for the given object stores.
   *
   * @param {Array<string>} storeNames
   *        The names of the object stores for which to open a transaction.
   * @param {string} [mode = "readonly"]
   *        The mode in which to open the transaction.
   * @param {function} [callback]
   *        An optional callback function. If provided, the function will be
   *        called with the Transaction, and a Promise will be returned, which
   *        will resolve to the callback's return value when the transaction
   *        completes.
   * @returns {Transaction|Promise}
   */
  transaction(storeNames, mode, callback = null) {
    let transaction = new Transaction(this.db.transaction(storeNames, mode));

    if (callback) {
      let result = new Promise(resolve => {
        resolve(callback(transaction));
      });
      return transaction.promiseComplete().then(() => result);
    }

    return transaction;
  }

  /**
   * Opens a transaction for a single object store, and returns that object
   * store.
   *
   * @param {string} storeName
   *        The name of the object store to open.
   * @param {string} [mode = "readonly"]
   *        The mode in which to open the transaction.
   * @param {function} [callback]
   *        An optional callback function. If provided, the function will be
   *        called with the ObjectStore, and a Promise will be returned, which
   *        will resolve to the callback's return value when the transaction
   *        completes.
   * @returns {ObjectStore|Promise}
   */
  objectStore(storeName, mode, callback = null) {
    let transaction = this.transaction([storeName], mode);
    let objectStore = transaction.objectStore(storeName);

    if (callback) {
      let result = new Promise(resolve => {
        resolve(callback(objectStore));
      });
      return transaction.promiseComplete().then(() => result);
    }

    return objectStore;
  }

  createObjectStore(...args) {
    return new ObjectStore(this.db.createObjectStore(...args));
  }
}

for (let method of ["cmp", "deleteDatabase"]) {
  IndexedDB[method] = function (...args) {
    return indexedDB[method](...args);
  };
}

forwardMethods(IndexedDB, "db", [
  "addEventListener",
  "close",
  "deleteObjectStore",
  "hasEventListener",
  "removeEventListener",
]);

forwardGetters(IndexedDB, "db", ["name", "objectStoreNames", "version"]);

forwardProps(IndexedDB, "db", [
  "onabort",
  "onclose",
  "onerror",
  "onversionchange",
]);
PK
!<�����I�I"modules/InlineSpellChecker.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const MAX_UNDO_STACK_DEPTH = 1;

export function InlineSpellChecker(aEditor) {
  this.init(aEditor);
  this.mAddedWordStack = []; // We init this here to preserve it between init/uninit calls
}

InlineSpellChecker.prototype = {
  // Call this function to initialize for a given editor
  init(aEditor) {
    this.uninit();
    this.mEditor = aEditor;
    try {
      this.mInlineSpellChecker = this.mEditor.getInlineSpellChecker(true);
      // note: this might have been NULL if there is no chance we can spellcheck
    } catch (e) {
      this.mInlineSpellChecker = null;
    }
  },

  initFromRemote(aSpellInfo, aWindowGlobalParent) {
    if (this.mRemote) {
      // We shouldn't get here, but let's just recover instead of bricking the
      // menu by throwing exceptions:
      console.error(new Error("Unexpected remote spellchecker present!"));
      try {
        this.mRemote.uninit();
      } catch (ex) {
        console.error(ex);
      }
      this.mRemote = null;
    }
    this.uninit();

    if (!aSpellInfo) {
      return;
    }
    this.mInlineSpellChecker = this.mRemote = new RemoteSpellChecker(
      aSpellInfo,
      aWindowGlobalParent
    );
    this.mOverMisspelling = aSpellInfo.overMisspelling;
    this.mMisspelling = aSpellInfo.misspelling;
  },

  // call this to clear state
  uninit() {
    if (this.mRemote) {
      this.mRemote.uninit();
      this.mRemote = null;
    }

    this.mEditor = null;
    this.mInlineSpellChecker = null;
    this.mOverMisspelling = false;
    this.mMisspelling = "";
    this.mMenu = null;
    this.mSuggestionItems = [];
    this.mDictionaryMenu = null;
    this.mDictionaryItems = [];
    this.mWordNode = null;
  },

  // for each UI event, you must call this function, it will compute the
  // word the cursor is over
  initFromEvent(rangeParent, rangeOffset) {
    this.mOverMisspelling = false;

    if (!rangeParent || !this.mInlineSpellChecker) {
      return;
    }

    var selcon = this.mEditor.selectionController;
    var spellsel = selcon.getSelection(selcon.SELECTION_SPELLCHECK);
    if (spellsel.rangeCount == 0) {
      return;
    } // easy case - no misspellings

    var range = this.mInlineSpellChecker.getMisspelledWord(
      rangeParent,
      rangeOffset
    );
    if (!range) {
      return;
    } // not over a misspelled word

    this.mMisspelling = range.toString();
    this.mOverMisspelling = true;
    this.mWordNode = rangeParent;
    this.mWordOffset = rangeOffset;
  },

  // returns false if there should be no spellchecking UI enabled at all, true
  // means that you can at least give the user the ability to turn it on.
  get canSpellCheck() {
    // inline spell checker objects will be created only if there are actual
    // dictionaries available
    if (this.mRemote) {
      return this.mRemote.canSpellCheck;
    }
    return this.mInlineSpellChecker != null;
  },

  get initialSpellCheckPending() {
    if (this.mRemote) {
      return this.mRemote.spellCheckPending;
    }
    return !!(
      this.mInlineSpellChecker &&
      !this.mInlineSpellChecker.spellChecker &&
      this.mInlineSpellChecker.spellCheckPending
    );
  },

  // Whether spellchecking is enabled in the current box
  get enabled() {
    if (this.mRemote) {
      return this.mRemote.enableRealTimeSpell;
    }
    return (
      this.mInlineSpellChecker && this.mInlineSpellChecker.enableRealTimeSpell
    );
  },
  set enabled(isEnabled) {
    if (this.mRemote) {
      this.mRemote.setSpellcheckUserOverride(isEnabled);
    } else if (this.mInlineSpellChecker) {
      this.mEditor.setSpellcheckUserOverride(isEnabled);
    }
  },

  // returns true if the given event is over a misspelled word
  get overMisspelling() {
    return this.mOverMisspelling;
  },

  // this prepends up to "maxNumber" suggestions at the given menu position
  // for the word under the cursor. Returns the number of suggestions inserted.
  addSuggestionsToMenuOnParent(menu, insertBefore, maxNumber) {
    if (this.mRemote) {
      // This is used on parent process only.
      // If you want to add suggestions to context menu, get suggestions then
      // use addSuggestionsToMenu instead.
      return 0;
    }
    if (!this.mInlineSpellChecker || !this.mOverMisspelling) {
      return 0;
    }

    let spellchecker = this.mInlineSpellChecker.spellChecker;
    let spellSuggestions = [];

    try {
      if (!spellchecker.CheckCurrentWord(this.mMisspelling)) {
        return 0;
      }

      for (let i = 0; i < maxNumber; i++) {
        let suggestion = spellchecker.GetSuggestedWord();
        if (!suggestion.length) {
          // no more data
          break;
        }
        spellSuggestions.push(suggestion);
      }
    } catch (e) {
      return 0;
    }
    return this._addSuggestionsToMenu(menu, insertBefore, spellSuggestions);
  },

  addSuggestionsToMenu(menu, insertBefore, spellSuggestions) {
    if (
      !this.mRemote &&
      (!this.mInlineSpellChecker || !this.mOverMisspelling)
    ) {
      return 0;
    } // nothing to do

    if (!spellSuggestions?.length) {
      return 0;
    }

    return this._addSuggestionsToMenu(menu, insertBefore, spellSuggestions);
  },

  _addSuggestionsToMenu(menu, insertBefore, spellSuggestions) {
    this.mMenu = menu;
    this.mSuggestionItems = [];

    for (let suggestion of spellSuggestions) {
      var item = menu.ownerDocument.createXULElement("menuitem");
      this.mSuggestionItems.push(item);
      item.setAttribute("label", suggestion);
      item.setAttribute("value", suggestion);
      item.addEventListener(
        "command",
        this.replaceMisspelling.bind(this, suggestion),
        true
      );
      item.setAttribute("class", "spell-suggestion");
      menu.insertBefore(item, insertBefore);
    }
    return spellSuggestions.length;
  },

  // undoes the work of addSuggestionsToMenu for the same menu
  // (call from popup hiding)
  clearSuggestionsFromMenu() {
    for (var i = 0; i < this.mSuggestionItems.length; i++) {
      this.mMenu.removeChild(this.mSuggestionItems[i]);
    }
    this.mSuggestionItems = [];
  },

  sortDictionaryList(list) {
    var sortedList = [];
    var names = Services.intl.getLocaleDisplayNames(undefined, list);
    for (var i = 0; i < list.length; i++) {
      sortedList.push({ localeCode: list[i], displayName: names[i] });
    }
    let comparer = new Services.intl.Collator().compare;
    sortedList.sort((a, b) => comparer(a.displayName, b.displayName));
    return sortedList;
  },

  async languageMenuListener(evt) {
    let curlangs = new Set();
    if (this.mRemote) {
      curlangs = new Set(this.mRemote.currentDictionaries);
    } else if (this.mInlineSpellChecker) {
      let spellchecker = this.mInlineSpellChecker.spellChecker;
      try {
        curlangs = new Set(spellchecker.getCurrentDictionaries());
      } catch (e) {}
    }

    let localeCodes = new Set(curlangs);
    let localeCode = evt.target.dataset.localeCode;
    if (localeCodes.has(localeCode)) {
      localeCodes.delete(localeCode);
    } else {
      localeCodes.add(localeCode);
    }
    let dictionaries = Array.from(localeCodes);
    await this.selectDictionaries(dictionaries);
    if (this.mRemote) {
      // Store the new set in case the menu doesn't close.
      this.mRemote.currentDictionaries = dictionaries;
    }
    // Notify change of dictionary, especially for Thunderbird,
    // which is otherwise not notified any more.
    let view = this.mDictionaryMenu.ownerGlobal;
    let spellcheckChangeEvent = new view.CustomEvent("spellcheck-changed", {
      detail: { dictionaries },
    });
    this.mDictionaryMenu.ownerDocument.dispatchEvent(spellcheckChangeEvent);
  },

  // returns the number of dictionary languages. If insertBefore is NULL, this
  // does an append to the given menu
  addDictionaryListToMenu(menu, insertBefore) {
    this.mDictionaryMenu = menu;
    this.mDictionaryItems = [];

    if (!this.enabled) {
      return 0;
    }

    let list;
    let curlangs = new Set();
    if (this.mRemote) {
      list = this.mRemote.dictionaryList;
      curlangs = new Set(this.mRemote.currentDictionaries);
    } else if (this.mInlineSpellChecker) {
      let spellchecker = this.mInlineSpellChecker.spellChecker;
      list = spellchecker.GetDictionaryList();
      try {
        curlangs = new Set(spellchecker.getCurrentDictionaries());
      } catch (e) {}
    }

    let sortedList = this.sortDictionaryList(list);
    this.languageMenuListenerBind = this.languageMenuListener.bind(this);
    menu.addEventListener("command", this.languageMenuListenerBind, true);

    for (let i = 0; i < sortedList.length; i++) {
      let item = menu.ownerDocument.createXULElement("menuitem");

      item.setAttribute(
        "id",
        "spell-check-dictionary-" + sortedList[i].localeCode
      );
      // XXX: Once Fluent has dynamic references, we could also lazily
      //      inject regionNames/languageNames FTL and localize using
      //      `l10n-id` here.
      item.setAttribute("label", sortedList[i].displayName);
      item.setAttribute("type", "checkbox");
      item.setAttribute("selection-type", "multiple");
      if (sortedList.length > 1) {
        item.setAttribute("closemenu", "none");
      }
      this.mDictionaryItems.push(item);
      item.dataset.localeCode = sortedList[i].localeCode;
      if (curlangs.has(sortedList[i].localeCode)) {
        item.setAttribute("checked", "true");
      }
      if (insertBefore) {
        menu.insertBefore(item, insertBefore);
      } else {
        menu.appendChild(item);
      }
    }
    return list.length;
  },

  // undoes the work of addDictionaryListToMenu for the menu
  // (call on popup hiding)
  clearDictionaryListFromMenu() {
    this.mDictionaryMenu?.removeEventListener(
      "command",
      this.languageMenuListenerBind,
      true
    );
    for (var i = 0; i < this.mDictionaryItems.length; i++) {
      this.mDictionaryMenu.removeChild(this.mDictionaryItems[i]);
    }
    this.mDictionaryItems = [];
  },

  // callback for selecting a dictionary
  async selectDictionaries(localeCodes) {
    if (this.mRemote) {
      this.mRemote.selectDictionaries(localeCodes);
      return;
    }
    if (!this.mInlineSpellChecker) {
      return;
    }
    var spellchecker = this.mInlineSpellChecker.spellChecker;
    await spellchecker.setCurrentDictionaries(localeCodes);
    this.mInlineSpellChecker.spellCheckRange(null); // causes recheck
  },

  // callback for selecting a suggested replacement
  replaceMisspelling(suggestion) {
    if (this.mRemote) {
      this.mRemote.replaceMisspelling(suggestion);
      return;
    }
    if (!this.mInlineSpellChecker || !this.mOverMisspelling) {
      return;
    }
    this.mInlineSpellChecker.replaceWord(
      this.mWordNode,
      this.mWordOffset,
      suggestion
    );
  },

  // callback for enabling or disabling spellchecking
  toggleEnabled() {
    if (this.mRemote) {
      this.mRemote.toggleEnabled();
    } else {
      this.mEditor.setSpellcheckUserOverride(
        !this.mInlineSpellChecker.enableRealTimeSpell
      );
    }
  },

  // callback for adding the current misspelling to the user-defined dictionary
  addToDictionary() {
    // Prevent the undo stack from growing over the max depth
    if (this.mAddedWordStack.length == MAX_UNDO_STACK_DEPTH) {
      this.mAddedWordStack.shift();
    }

    this.mAddedWordStack.push(this.mMisspelling);
    if (this.mRemote) {
      this.mRemote.addToDictionary();
    } else {
      this.mInlineSpellChecker.addWordToDictionary(this.mMisspelling);
    }
  },
  // callback for removing the last added word to the dictionary LIFO fashion
  undoAddToDictionary() {
    if (this.mAddedWordStack.length) {
      var word = this.mAddedWordStack.pop();
      if (this.mRemote) {
        this.mRemote.undoAddToDictionary(word);
      } else {
        this.mInlineSpellChecker.removeWordFromDictionary(word);
      }
    }
  },
  canUndo() {
    // Return true if we have words on the stack
    return !!this.mAddedWordStack.length;
  },
  ignoreWord() {
    if (this.mRemote) {
      this.mRemote.ignoreWord();
    } else {
      this.mInlineSpellChecker.ignoreWord(this.mMisspelling);
    }
  },
};

export var SpellCheckHelper = {
  // Set when over a non-read-only <textarea> or editable <input>
  // (that allows text entry of some kind, so not e.g. <input type=checkbox>)
  EDITABLE: 0x1,

  // Set when over an <input> element of any type.
  INPUT: 0x2,

  // Set when over any <textarea>.
  TEXTAREA: 0x4,

  // Set when over any text-entry <input>.
  TEXTINPUT: 0x8,

  // Set when over an <input> that can be used as a keyword field.
  KEYWORD: 0x10,

  // Set when over an element that otherwise would not be considered
  // "editable" but is because content editable is enabled for the document.
  CONTENTEDITABLE: 0x20,

  // Set when over an <input type="number"> or other non-text field.
  NUMERIC: 0x40,

  // Set when over an <input type="password"> field.
  PASSWORD: 0x80,

  // Set when spellcheckable. Replaces `EDITABLE`/`CONTENTEDITABLE` combination
  // specifically for spellcheck.
  SPELLCHECKABLE: 0x100,

  isTargetAKeywordField(aNode, window) {
    if (!window.HTMLInputElement.isInstance(aNode)) {
      return false;
    }

    var form = aNode.form;
    if (!form || aNode.type == "password") {
      return false;
    }

    var method = form.method.toUpperCase();

    // These are the following types of forms we can create keywords for:
    //
    // method   encoding type       can create keyword
    // GET      *                                 YES
    //          *                                 YES
    // POST                                       YES
    // POST     application/x-www-form-urlencoded YES
    // POST     text/plain                        NO (a little tricky to do)
    // POST     multipart/form-data               NO
    // POST     everything else                   YES
    return (
      method == "GET" ||
      method == "" ||
      (form.enctype != "text/plain" && form.enctype != "multipart/form-data")
    );
  },

  // Returns the computed style attribute for the given element.
  getComputedStyle(aElem, aProp) {
    return aElem.ownerGlobal.getComputedStyle(aElem).getPropertyValue(aProp);
  },

  isEditable(element, window) {
    var flags = 0;
    if (window.HTMLInputElement.isInstance(element)) {
      flags |= this.INPUT;
      if (element.mozIsTextField(false) || element.type == "number") {
        flags |= this.TEXTINPUT;
        if (!element.readOnly) {
          flags |= this.EDITABLE;
        }

        if (element.type == "number") {
          flags |= this.NUMERIC;
        }

        // Allow spellchecking UI on all text and search inputs.
        if (
          !element.readOnly &&
          (element.type == "text" || element.type == "search")
        ) {
          flags |= this.SPELLCHECKABLE;
        }
        if (this.isTargetAKeywordField(element, window)) {
          flags |= this.KEYWORD;
        }
        if (element.type == "password") {
          flags |= this.PASSWORD;
        }
      }
    } else if (window.HTMLTextAreaElement.isInstance(element)) {
      flags |= this.TEXTINPUT | this.TEXTAREA;
      if (!element.readOnly) {
        flags |= this.SPELLCHECKABLE | this.EDITABLE;
      }
    }

    if (!(flags & this.SPELLCHECKABLE)) {
      var win = element.ownerGlobal;
      if (win) {
        var isSpellcheckable = false;
        try {
          var editingSession = win.docShell.editingSession;
          if (
            editingSession.windowIsEditable(win) &&
            this.getComputedStyle(element, "-moz-user-modify") == "read-write"
          ) {
            isSpellcheckable = true;
          }
        } catch (ex) {
          // If someone built with composer disabled, we can't get an editing session.
        }

        if (isSpellcheckable) {
          flags |= this.CONTENTEDITABLE | this.SPELLCHECKABLE;
        }
      }
    }

    return flags;
  },
};

function RemoteSpellChecker(aSpellInfo, aWindowGlobalParent) {
  this._spellInfo = aSpellInfo;
  this._suggestionGenerator = null;
  this._actor = aWindowGlobalParent.getActor("InlineSpellChecker");
  this._actor.registerDestructionObserver(this);
}

RemoteSpellChecker.prototype = {
  get canSpellCheck() {
    return this._spellInfo.canSpellCheck;
  },
  get spellCheckPending() {
    return this._spellInfo.initialSpellCheckPending;
  },
  get overMisspelling() {
    return this._spellInfo.overMisspelling;
  },
  get enableRealTimeSpell() {
    return this._spellInfo.enableRealTimeSpell;
  },
  get suggestions() {
    return this._spellInfo.spellSuggestions;
  },

  get currentDictionaries() {
    return this._spellInfo.currentDictionaries;
  },
  set currentDictionaries(dicts) {
    this._spellInfo.currentDictionaries = dicts;
  },
  get dictionaryList() {
    return this._spellInfo.dictionaryList.slice();
  },

  selectDictionaries(localeCodes) {
    this._actor.selectDictionaries({ localeCodes });
  },

  replaceMisspelling(suggestion) {
    this._actor.replaceMisspelling({ suggestion });
  },

  toggleEnabled() {
    this._actor.toggleEnabled();
  },
  addToDictionary() {
    // This is really ugly. There is an nsISpellChecker somewhere in the
    // parent that corresponds to our current element's spell checker in the
    // child, but it's hard to access it. However, we know that
    // addToDictionary adds the word to the singleton personal dictionary, so
    // we just do that here.
    // NB: We also rely on the fact that we only ever pass an empty string in
    // as the "lang".

    let dictionary = Cc[
      "@mozilla.org/spellchecker/personaldictionary;1"
    ].getService(Ci.mozIPersonalDictionary);
    dictionary.addWord(this._spellInfo.misspelling);
    this._actor.recheckSpelling();
  },
  undoAddToDictionary(word) {
    let dictionary = Cc[
      "@mozilla.org/spellchecker/personaldictionary;1"
    ].getService(Ci.mozIPersonalDictionary);
    dictionary.removeWord(word);
    this._actor.recheckSpelling();
  },
  ignoreWord() {
    let dictionary = Cc[
      "@mozilla.org/spellchecker/personaldictionary;1"
    ].getService(Ci.mozIPersonalDictionary);
    dictionary.ignoreWord(this._spellInfo.misspelling);
    this._actor.recheckSpelling();
  },
  uninit() {
    if (this._actor) {
      this._actor.uninit();
      this._actor.unregisterDestructionObserver(this);
    }
  },

  actorDestroyed() {
    // The actor lets us know if it gets destroyed, so we don't
    // later try to call `.uninit()` on it.
    this._actor = null;
  },
};
PK
!<�uu���)modules/InlineSpellCheckerContent.sys.mjs/* vim: set ts=2 sw=2 sts=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import {
  InlineSpellChecker,
  SpellCheckHelper,
} from "resource://gre/modules/InlineSpellChecker.sys.mjs";

export var InlineSpellCheckerContent = {
  _spellChecker: null,
  _actor: null,

  async initContextMenu(event, editFlags, actor) {
    this._actor = actor;
    this._actor.registerDestructionObserver(this);

    let spellChecker;
    if (!(editFlags & (SpellCheckHelper.TEXTAREA | SpellCheckHelper.INPUT))) {
      // Get the editor off the window.
      let win = event.target.ownerGlobal;
      let editingSession = win.docShell.editingSession;
      spellChecker = this._spellChecker = new InlineSpellChecker(
        editingSession.getEditorForWindow(win)
      );
    } else {
      // Use the element's editor.
      spellChecker = this._spellChecker = new InlineSpellChecker(
        event.composedTarget.editor
      );
    }

    this._spellChecker.initFromEvent(event.rangeParent, event.rangeOffset);

    if (!spellChecker.canSpellCheck) {
      return {
        canSpellCheck: false,
        initialSpellCheckPending: true,
        enableRealTimeSpell: false,
      };
    }

    if (!spellChecker.mInlineSpellChecker.enableRealTimeSpell) {
      return {
        canSpellCheck: true,
        initialSpellCheckPending: spellChecker.initialSpellCheckPending,
        enableRealTimeSpell: false,
      };
    }

    if (spellChecker.initialSpellCheckPending) {
      return {
        canSpellCheck: true,
        initialSpellCheckPending: true,
        enableRealTimeSpell: true,
      };
    }

    let realSpellChecker = spellChecker.mInlineSpellChecker.spellChecker;
    let dictionaryList = realSpellChecker.GetDictionaryList();
    let spellSuggestions = await this._generateSpellSuggestions();

    return {
      canSpellCheck: spellChecker.canSpellCheck,
      initialSpellCheckPending: spellChecker.initialSpellCheckPending,
      enableRealTimeSpell: spellChecker.enabled,
      overMisspelling: spellChecker.overMisspelling,
      misspelling: spellChecker.mMisspelling,
      spellSuggestions,
      currentDictionaries:
        spellChecker.mInlineSpellChecker.spellChecker.getCurrentDictionaries(),
      dictionaryList,
    };
  },

  uninitContextMenu() {
    if (this._actor) {
      this._actor.unregisterDestructionObserver(this);
    }
    this._actor = null;
    this._spellChecker = null;
  },

  actorDestroyed() {
    this.uninitContextMenu();
  },

  async _generateSpellSuggestions() {
    let spellChecker = this._spellChecker.mInlineSpellChecker.spellChecker;
    let suggestions = null;
    try {
      suggestions = await spellChecker.suggest(
        this._spellChecker.mMisspelling,
        5
      );
    } catch (e) {
      return [];
    }

    return suggestions;
  },

  selectDictionaries(localeCodes) {
    this._spellChecker.selectDictionaries(localeCodes);
  },

  replaceMisspelling(suggestion) {
    this._spellChecker.replaceMisspelling(suggestion);
  },

  toggleEnabled() {
    this._spellChecker.toggleEnabled();
  },

  recheck() {
    this._spellChecker.mInlineSpellChecker.enableRealTimeSpell = true;
  },
};
PK
!<ٜ�,��%modules/InsecurePasswordUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ownerGlobal doesn't exist in content privileged windows. */
/* eslint-disable mozilla/use-ownerGlobal */

const STRINGS_URI = "chrome://global/locale/security/security.properties";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  return lazy.LoginHelper.createLogger("InsecurePasswordUtils");
});

/*
 * A module that provides utility functions for form security.
 *
 */
export const InsecurePasswordUtils = {
  _formRootsWarned: new WeakMap(),

  /**
   * Gets the ID of the inner window of this DOM window.
   *
   * @param nsIDOMWindow window
   * @return integer
   *         Inner ID for the given window.
   */
  _getInnerWindowId(window) {
    return window.windowGlobalChild.innerWindowId;
  },

  _sendWebConsoleMessage(messageTag, domDoc) {
    let windowId = this._getInnerWindowId(domDoc.defaultView);
    let category = "Insecure Password Field";
    // All web console messages are warnings for now.
    let flag = Ci.nsIScriptError.warningFlag;
    let bundle = Services.strings.createBundle(STRINGS_URI);
    let message = bundle.GetStringFromName(messageTag);
    let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(
      Ci.nsIScriptError
    );
    consoleMsg.initWithWindowID(
      message,
      domDoc.location.href,
      0,
      0,
      flag,
      category,
      windowId
    );

    Services.console.logMessage(consoleMsg);
  },

  /**
   * Gets the security state of the passed form.
   *
   * @param {FormLike} aForm A form-like object. @See {FormLikeFactory}
   *
   * @returns {Object} An object with the following boolean values:
   *  isFormSubmitHTTP: if the submit action is an http:// URL
   *  isFormSubmitSecure: if the submit action URL is secure,
   *    either because it is HTTPS or because its origin is considered trustworthy
   */
  _checkFormSecurity(aForm) {
    let isFormSubmitHTTP = false,
      isFormSubmitSecure = false;
    if (HTMLFormElement.isInstance(aForm.rootElement)) {
      let uri = Services.io.newURI(
        aForm.rootElement.action || aForm.rootElement.baseURI
      );
      let principal = Services.scriptSecurityManager.createContentPrincipal(
        uri,
        {}
      );

      if (uri.schemeIs("http")) {
        isFormSubmitHTTP = true;
        if (
          principal.isOriginPotentiallyTrustworthy ||
          // Ignore sites with local IP addresses pointing to local forms.
          (this._isPrincipalForLocalIPAddress(
            aForm.rootElement.nodePrincipal
          ) &&
            this._isPrincipalForLocalIPAddress(principal))
        ) {
          isFormSubmitSecure = true;
        }
      } else {
        isFormSubmitSecure = true;
      }
    }

    return { isFormSubmitHTTP, isFormSubmitSecure };
  },

  _isPrincipalForLocalIPAddress(aPrincipal) {
    let res = aPrincipal.isLocalIpAddress;
    if (res) {
      lazy.log.debug(
        "hasInsecureLoginForms: detected local IP address:",
        aPrincipal.asciispec
      );
    }
    return res;
  },

  /**s
   * Checks if there are insecure password fields present on the form's document
   * i.e. passwords inside forms with http action, inside iframes with http src,
   * or on insecure web pages.
   *
   * @param {FormLike} aForm A form-like object. @See {LoginFormFactory}
   * @return {boolean} whether the form is secure
   */
  isFormSecure(aForm) {
    let isSafePage = aForm.ownerDocument.defaultView.isSecureContext;

    // Ignore insecure documents with URLs that are local IP addresses.
    // This is done because the vast majority of routers and other devices
    // on the network do not use HTTPS, making this warning show up almost
    // constantly on local connections, which annoys users and hurts our cause.
    if (!isSafePage && this._ignoreLocalIPAddress) {
      let isLocalIP = this._isPrincipalForLocalIPAddress(
        aForm.rootElement.nodePrincipal
      );

      let topIsLocalIP =
        aForm.ownerDocument.defaultView.windowGlobalChild.windowContext
          .topWindowContext.isLocalIP;

      // Only consider the page safe if the top window has a local IP address
      // and, if this is an iframe, the iframe also has a local IP address.
      if (isLocalIP && topIsLocalIP) {
        isSafePage = true;
      }
    }

    let { isFormSubmitSecure, isFormSubmitHTTP } =
      this._checkFormSecurity(aForm);

    return isSafePage && (isFormSubmitSecure || !isFormSubmitHTTP);
  },

  /**
   * Report insecure password fields in a form to the web console to warn developers.
   *
   * @param {FormLike} aForm A form-like object. @See {FormLikeFactory}
   */
  reportInsecurePasswords(aForm) {
    if (
      this._formRootsWarned.has(aForm.rootElement) ||
      this._formRootsWarned.get(aForm.rootElement)
    ) {
      return;
    }

    let domDoc = aForm.ownerDocument;
    let isSafePage = domDoc.defaultView.isSecureContext;

    let { isFormSubmitHTTP, isFormSubmitSecure } =
      this._checkFormSecurity(aForm);

    if (!isSafePage) {
      if (domDoc.defaultView == domDoc.defaultView.parent) {
        this._sendWebConsoleMessage("InsecurePasswordsPresentOnPage", domDoc);
      } else {
        this._sendWebConsoleMessage("InsecurePasswordsPresentOnIframe", domDoc);
      }
      this._formRootsWarned.set(aForm.rootElement, true);
    } else if (isFormSubmitHTTP && !isFormSubmitSecure) {
      this._sendWebConsoleMessage("InsecureFormActionPasswordsPresent", domDoc);
      this._formRootsWarned.set(aForm.rootElement, true);
    }

    // The safety of a password field determined by the form action and the page protocol
    let passwordSafety;
    if (isSafePage) {
      if (isFormSubmitSecure) {
        passwordSafety = 0;
      } else if (isFormSubmitHTTP) {
        passwordSafety = 1;
      } else {
        passwordSafety = 2;
      }
    } else if (isFormSubmitSecure) {
      passwordSafety = 3;
    } else if (isFormSubmitHTTP) {
      passwordSafety = 4;
    } else {
      passwordSafety = 5;
    }

    Services.telemetry
      .getHistogramById("PWMGR_LOGIN_PAGE_SAFETY")
      .add(passwordSafety);
  },
};

XPCOMUtils.defineLazyPreferenceGetter(
  InsecurePasswordUtils,
  "_ignoreLocalIPAddress",
  "security.insecure_field_warning.ignore_local_ip_address",
  true
);
PK
!<	|��])])modules/Integration.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Implements low-overhead integration between components of the application.
 * This may have different uses depending on the component, including:
 *
 * - Providing product-specific implementations registered at startup.
 * - Using alternative implementations during unit tests.
 * - Allowing add-ons to change specific behaviors.
 *
 * Components may define one or more integration points, each defined by a
 * root integration object whose properties and methods are the public interface
 * and default implementation of the integration point. For example:
 *
 *   const DownloadIntegration = {
 *     getTemporaryDirectory() {
 *       return "/tmp/";
 *     },
 *
 *     getTemporaryFile(name) {
 *       return this.getTemporaryDirectory() + name;
 *     },
 *   };
 *
 * Other parts of the application may register overrides for some or all of the
 * defined properties and methods. The component defining the integration point
 * does not have to be loaded at this stage, because the name of the integration
 * point is the only information required. For example, if the integration point
 * is called "downloads":
 *
 *   Integration.downloads.register(base => ({
 *     getTemporaryDirectory() {
 *       return base.getTemporaryDirectory.call(this) + "subdir/";
 *     },
 *   }));
 *
 * When the component defining the integration point needs to call a method on
 * the integration object, instead of using it directly the component would use
 * the "getCombined" method to retrieve an object that includes all overrides.
 * For example:
 *
 *   let combined = Integration.downloads.getCombined(DownloadIntegration);
 *   Assert.is(combined.getTemporaryFile("file"), "/tmp/subdir/file");
 *
 * Overrides can be registered at startup or at any later time, so each call to
 * "getCombined" may return a different object. The simplest way to create a
 * reference to the combined object that stays updated to the latest version is
 * to define the root object in a JSM and use the "defineModuleGetter" method.
 *
 * *** Registration ***
 *
 * Since the interface is not declared formally, the registrations can happen
 * at startup without loading the component, so they do not affect performance.
 *
 * Hovever, this module does not provide a startup registry, this means that the
 * code that registers and implements the override must be loaded at startup.
 *
 * If performance for the override code is a concern, you can take advantage of
 * the fact that the function used to create the override is called lazily, and
 * include only a stub loader for the final code in an existing startup module.
 *
 * The registration of overrides should be repeated for each process where the
 * relevant integration methods will be called.
 *
 * *** Accessing base methods and properties ***
 *
 * Overrides are included in the prototype chain of the combined object in the
 * same order they were registered, where the first is closest to the root.
 *
 * When defining overrides, you do not need to manipulate the prototype chain of
 * the objects you create, because their properties and methods are moved to a
 * new object with the correct prototype. If you do, however, you can call base
 * properties and methods using the "super" keyword. For example:
 *
 *   Integration.downloads.register(base => {
 *     let newObject = {
 *       getTemporaryDirectory() {
 *         return super.getTemporaryDirectory() + "subdir/";
 *       },
 *     };
 *     Object.setPrototypeOf(newObject, base);
 *     return newObject;
 *   });
 *
 * *** State handling ***
 *
 * Storing state directly on the combined integration object using the "this"
 * reference is not recommended. When a new integration is registered, own
 * properties stored on the old combined object are copied to the new combined
 * object using a shallow copy, but the "this" reference for new invocations
 * of the methods will be different.
 *
 * If the root object defines a property that always points to the same object,
 * for example a "state" property, you can safely use it across registrations.
 *
 * Integration overrides provided by restartless add-ons should not use the
 * "this" reference to store state, to avoid conflicts with other add-ons.
 *
 * *** Interaction with XPCOM ***
 *
 * Providing the combined object as an argument to any XPCOM method will
 * generate a console error message, and will throw an exception where possible.
 * For example, you cannot register observers directly on the combined object.
 * This helps preventing mistakes due to the fact that the combined object
 * reference changes when new integration overrides are registered.
 */

/**
 * Maps integration point names to IntegrationPoint objects.
 */
const gIntegrationPoints = new Map();

/**
 * This Proxy object creates IntegrationPoint objects using their name as key.
 * The objects will be the same for the duration of the process. For example:
 *
 *   Integration.downloads.register(...);
 *   Integration["addon-provided-integration"].register(...);
 */
export var Integration = new Proxy(
  {},
  {
    get(target, name) {
      let integrationPoint = gIntegrationPoints.get(name);
      if (!integrationPoint) {
        integrationPoint = new IntegrationPoint();
        gIntegrationPoints.set(name, integrationPoint);
      }
      return integrationPoint;
    },
  }
);

/**
 * Individual integration point for which overrides can be registered.
 */
var IntegrationPoint = function () {
  this._overrideFns = new Set();
  this._combined = {
    // eslint-disable-next-line mozilla/use-chromeutils-generateqi
    QueryInterface() {
      let ex = new Components.Exception(
        "Integration objects should not be used with XPCOM because" +
          " they change when new overrides are registered.",
        Cr.NS_ERROR_NO_INTERFACE
      );
      console.error(ex);
      throw ex;
    },
  };
};

IntegrationPoint.prototype = {
  /**
   * Ordered set of registered functions defining integration overrides.
   */
  _overrideFns: null,

  /**
   * Combined integration object. When this reference changes, properties
   * defined directly on this object are copied to the new object.
   *
   * Initially, the only property of this object is a "QueryInterface" method
   * that throws an exception, to prevent misuse as a permanent XPCOM listener.
   */
  _combined: null,

  /**
   * Indicates whether the integration object is current based on the list of
   * registered integration overrides.
   */
  _combinedIsCurrent: false,

  /**
   * Registers new overrides for the integration methods. For example:
   *
   *   Integration.nameOfIntegrationPoint.register(base => ({
   *     asyncMethod: Task.async(function* () {
   *       return yield base.asyncMethod.apply(this, arguments);
   *     }),
   *   }));
   *
   * @param overrideFn
   *        Function returning an object defining the methods that should be
   *        overridden. Its only parameter is an object that contains the base
   *        implementation of all the available methods.
   *
   * @note The override function is called every time the list of registered
   *       override functions changes. Thus, it should not have any side
   *       effects or do any other initialization.
   */
  register(overrideFn) {
    this._overrideFns.add(overrideFn);
    this._combinedIsCurrent = false;
  },

  /**
   * Removes a previously registered integration override.
   *
   * Overrides don't usually need to be unregistered, unless they are added by a
   * restartless add-on, in which case they should be unregistered when the
   * add-on is disabled or uninstalled.
   *
   * @param overrideFn
   *        This must be the same function object passed to "register".
   */
  unregister(overrideFn) {
    this._overrideFns.delete(overrideFn);
    this._combinedIsCurrent = false;
  },

  /**
   * Retrieves the dynamically generated object implementing the integration
   * methods. Platform-specific code and add-ons can override methods of this
   * object using the "register" method.
   */
  getCombined(root) {
    if (this._combinedIsCurrent) {
      return this._combined;
    }

    // In addition to enumerating all the registered integration overrides in
    // order, we want to keep any state that was previously stored in the
    // combined object using the "this" reference in integration methods.
    let overrideFnArray = [...this._overrideFns, () => this._combined];

    let combined = root;
    for (let overrideFn of overrideFnArray) {
      try {
        // Obtain a new set of methods from the next override function in the
        // list, specifying the current combined object as the base argument.
        let override = overrideFn(combined);

        // Retrieve a list of property descriptors from the returned object, and
        // use them to build a new combined object whose prototype points to the
        // previous combined object.
        let descriptors = {};
        for (let name of Object.getOwnPropertyNames(override)) {
          descriptors[name] = Object.getOwnPropertyDescriptor(override, name);
        }
        combined = Object.create(combined, descriptors);
      } catch (ex) {
        // Any error will result in the current override being skipped.
        console.error(ex);
      }
    }

    this._combinedIsCurrent = true;
    return (this._combined = combined);
  },

  /**
   * Defines a getter to retrieve the dynamically generated object implementing
   * the integration methods, loading the root implementation lazily from the
   * specified sys.mjs module. For example:
   *
   *   Integration.test.defineModuleGetter(this, "TestIntegration",
   *                    "resource://testing-common/TestIntegration.sys.mjs");
   *
   * @param targetObject
   *        The object on which the lazy getter will be defined.
   * @param name
   *        The name of the getter to define.
   * @param moduleUrl
   *        The URL used to obtain the module.
   */
  defineESModuleGetter(targetObject, name, moduleUrl) {
    let moduleHolder = {};
    // eslint-disable-next-line mozilla/lazy-getter-object-name
    ChromeUtils.defineESModuleGetters(moduleHolder, {
      [name]: moduleUrl,
    });
    Object.defineProperty(targetObject, name, {
      get: () => this.getCombined(moduleHolder[name]),
      configurable: true,
      enumerable: true,
    });
  },
};
PK
!<�VO�A�Amodules/JSONFile.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Handles serialization of the data and persistence into a file.
 *
 * This modules handles the raw data stored in JavaScript serializable objects,
 * and contains no special validation or query logic, that is handled entirely
 * by "storage.js" instead.
 *
 * The data can be manipulated only after it has been loaded from disk.  The
 * load process can happen asynchronously, through the "load" method, or
 * synchronously, through "ensureDataReady".  After any modification, the
 * "saveSoon" method must be called to flush the data to disk asynchronously.
 *
 * The raw data should be manipulated synchronously, without waiting for the
 * event loop or for promise resolution, so that the saved file is always
 * consistent.  This synchronous approach also simplifies the query and update
 * logic.  For example, it is possible to find an object and modify it
 * immediately without caring whether other code modifies it in the meantime.
 *
 * An asynchronous shutdown observer makes sure that data is always saved before
 * the browser is closed. The data cannot be modified during shutdown.
 *
 * The file is stored in JSON format, without indentation, using UTF-8 encoding.
 */

// Globals

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "gTextDecoder", function () {
  return new TextDecoder();
});

const FileInputStream = Components.Constructor(
  "@mozilla.org/network/file-input-stream;1",
  "nsIFileInputStream",
  "init"
);

/**
 * Delay between a change to the data and the related save operation.
 */
const kSaveDelayMs = 1500;

/**
 * Cleansed basenames of the filenames that telemetry can be recorded for.
 * Keep synchronized with 'objects' from Events.yaml.
 */
const TELEMETRY_BASENAMES = new Set(["logins", "autofillprofiles"]);

// JSONFile

/**
 * Handles serialization of the data and persistence into a file.
 *
 * @param config An object containing following members:
 *        - path: String containing the file path where data should be saved.
 *        - sanitizedBasename: Sanitized string identifier used for logging,
 *                             shutdown debugging, and telemetry.  Defaults to
 *                             basename of given `path`, sanitized.
 *        - dataPostProcessor: Function triggered when data is just loaded. The
 *                             data object will be passed as the first argument
 *                             and should be returned no matter it's modified or
 *                             not. Its failure leads to the failure of load()
 *                             and ensureDataReady().
 *        - saveDelayMs: Number indicating the delay (in milliseconds) between a
 *                       change to the data and the related save operation. The
 *                       default value will be applied if omitted.
 *        - beforeSave: Promise-returning function triggered just before the
 *                      data is written to disk. This can be used to create any
 *                      intermediate directories before saving. The file will
 *                      not be saved if the promise rejects or the function
 *                      throws an exception.
 *        - finalizeAt: An `IOUtils` phase or barrier client that should
 *                      automatically finalize the file when triggered. Defaults
 *                      to `profileBeforeChange`; exposed as an option for
 *                      testing.
 *        - compression: A compression algorithm to use when reading and
 *                       writing the data.
 *        - backupTo: A string value indicating where writeAtomic should create
 *                    a backup before writing to json files. Note that using this
 *                    option currently ensures that we automatically restore backed
 *                    up json files in load() and ensureDataReady() when original
 *                    files are missing or corrupt.
 */
export function JSONFile(config) {
  this.path = config.path;
  this.sanitizedBasename =
    config.sanitizedBasename ??
    PathUtils.filename(this.path)
      .replace(/\.json(.lz4)?$/, "")
      .replaceAll(/[^a-zA-Z0-9_.]/g, "");

  if (typeof config.dataPostProcessor === "function") {
    this._dataPostProcessor = config.dataPostProcessor;
  }
  if (typeof config.beforeSave === "function") {
    this._beforeSave = config.beforeSave;
  }

  if (config.saveDelayMs === undefined) {
    config.saveDelayMs = kSaveDelayMs;
  }
  this._saver = new lazy.DeferredTask(() => this._save(), config.saveDelayMs);

  this._options = {};
  if (config.compression) {
    this._options.decompress = this._options.compress = true;
  }

  if (config.backupTo) {
    this._options.backupFile = this._options.backupTo = config.backupTo;
  }

  this._finalizeAt = config.finalizeAt || IOUtils.profileBeforeChange;
  this._finalizeInternalBound = this._finalizeInternal.bind(this);
  this._finalizeAt.addBlocker(
    `JSON store: writing data for '${this.sanitizedBasename}'`,
    this._finalizeInternalBound,
    () => ({ sanitizedBasename: this.sanitizedBasename })
  );

  Services.telemetry.setEventRecordingEnabled("jsonfile", true);
}

JSONFile.prototype = {
  /**
   * String containing the file path where data should be saved.
   */
  path: "",

  /**
   * Sanitized identifier used for logging, shutdown debugging, and telemetry.
   */
  sanitizedBasename: "",

  /**
   * True when data has been loaded.
   */
  dataReady: false,

  /**
   * DeferredTask that handles the save operation.
   */
  _saver: null,

  /**
   * Internal data object.
   */
  _data: null,

  /**
   * Internal fields used during finalization.
   */
  _finalizeAt: null,
  _finalizePromise: null,
  _finalizeInternalBound: null,

  /**
   * Serializable object containing the data. This is populated directly with
   * the data loaded from the file, and is saved without modifications.
   *
   * The raw data should be manipulated synchronously, without waiting for the
   * event loop or for promise resolution, so that the saved file is always
   * consistent.
   */
  get data() {
    if (!this.dataReady) {
      throw new Error("Data is not ready.");
    }
    return this._data;
  },

  /**
   * Sets the loaded data to a new object. This will overwrite any persisted
   * data on the next save.
   */
  set data(data) {
    this._data = data;
    this.dataReady = true;
  },

  /**
   * Loads persistent data from the file to memory.
   *
   * @return {Promise}
   * @resolves When the operation finished successfully.
   * @rejects JavaScript exception when dataPostProcessor fails. It never fails
   *          if there is no dataPostProcessor.
   */
  async load() {
    if (this.dataReady) {
      return;
    }

    let data = {};

    try {
      data = await IOUtils.readJSON(this.path, this._options);

      // If synchronous loading happened in the meantime, exit now.
      if (this.dataReady) {
        return;
      }
    } catch (ex) {
      // If an exception occurs because the file does not exist or it cannot be read,
      // we do two things.
      // 1. For consumers of JSONFile.sys.mjs that have configured a `backupTo` path option,
      //    we try to look for and use backed up json files first. If the backup
      //    is also not found or if the backup is unreadable, we then start with an empty file.
      // 2. If a consumer does not configure a `backupTo` path option, we just start
      //    with an empty file.

      // In the event that the file exists, but an exception is thrown because it cannot be read,
      // we store it as a .corrupt file for debugging purposes.

      let errorNo = ex.winLastError || ex.unixErrno;
      this._recordTelemetry("load", errorNo ? errorNo.toString() : "");
      if (!(DOMException.isInstance(ex) && ex.name == "NotFoundError")) {
        console.error(ex);

        // Move the original file to a backup location, ignoring errors.
        try {
          let uniquePath = await IOUtils.createUniqueFile(
            PathUtils.parent(this.path),
            PathUtils.filename(this.path) + ".corrupt",
            0o600
          );
          await IOUtils.move(this.path, uniquePath);
          this._recordTelemetry("load", "invalid_json");
        } catch (e2) {
          console.error(e2);
        }
      }

      if (this._options.backupFile) {
        // Restore the original file from the backup here so fresh writes to empty
        // json files don't happen at any time in the future compromising the backup
        // in the process.
        try {
          await IOUtils.copy(this._options.backupFile, this.path);
        } catch (e) {
          if (!(DOMException.isInstance(e) && e.name == "NotFoundError")) {
            console.error(e);
          }
        }

        try {
          // We still read from the backup file here instead of the original file in case
          // access to the original file is blocked, e.g. by anti-virus software on the
          // user's computer.
          data = await IOUtils.readJSON(
            this._options.backupFile,
            this._options
          );
          // If synchronous loading happened in the meantime, exit now.
          if (this.dataReady) {
            return;
          }
          this._recordTelemetry("load", "used_backup");
        } catch (e3) {
          if (!(DOMException.isInstance(e3) && e3.name == "NotFoundError")) {
            console.error(e3);
          }
        }
      }

      // In some rare cases it's possible for data to have been added to
      // our database between the call to IOUtils.read and when we've been
      // notified that there was a problem with it. In that case, leave the
      // synchronously-added data alone.
      if (this.dataReady) {
        return;
      }
    }

    this._processLoadedData(data);
  },

  /**
   * Loads persistent data from the file to memory, synchronously. An exception
   * can be thrown only if dataPostProcessor exists and fails.
   */
  ensureDataReady() {
    if (this.dataReady) {
      return;
    }

    let data = {};

    try {
      // This reads the file and automatically detects the UTF-8 encoding.
      let inputStream = new FileInputStream(
        new lazy.FileUtils.File(this.path),
        lazy.FileUtils.MODE_RDONLY,
        lazy.FileUtils.PERMS_FILE,
        0
      );
      try {
        let bytes = lazy.NetUtil.readInputStream(
          inputStream,
          inputStream.available()
        );
        data = JSON.parse(lazy.gTextDecoder.decode(bytes));
      } finally {
        inputStream.close();
      }
    } catch (ex) {
      // If an exception occurs because the file does not exist or it cannot be read,
      // we do two things.
      // 1. For consumers of JSONFile.sys.mjs that have configured a `backupTo` path option,
      //    we try to look for and use backed up json files first. If the backup
      //    is also not found or if the backup is unreadable, we then start with an empty file.
      // 2. If a consumer does not configure a `backupTo` path option, we just start
      //    with an empty file.

      // In the event that the file exists, but an exception is thrown because it cannot be read,
      // we store it as a .corrupt file for debugging purposes.
      if (
        !(
          ex instanceof Components.Exception &&
          ex.result == Cr.NS_ERROR_FILE_NOT_FOUND
        )
      ) {
        console.error(ex);
        // Move the original file to a backup location, ignoring errors.
        try {
          let originalFile = new lazy.FileUtils.File(this.path);
          let backupFile = originalFile.clone();
          backupFile.leafName += ".corrupt";
          backupFile.createUnique(
            Ci.nsIFile.NORMAL_FILE_TYPE,
            lazy.FileUtils.PERMS_FILE
          );
          backupFile.remove(false);
          originalFile.moveTo(backupFile.parent, backupFile.leafName);
        } catch (e2) {
          console.error(e2);
        }
      }

      if (this._options.backupFile) {
        // Restore the original file from the backup here so fresh writes to empty
        // json files don't happen at any time in the future compromising the backup
        // in the process.
        try {
          let basename = PathUtils.filename(this.path);
          let backupFile = new lazy.FileUtils.File(this._options.backupFile);
          backupFile.copyTo(null, basename);
        } catch (e) {
          if (e.result != Cr.NS_ERROR_FILE_NOT_FOUND) {
            console.error(e);
          }
        }

        try {
          // We still read from the backup file here instead of the original file in case
          // access to the original file is blocked, e.g. by anti-virus software on the
          // user's computer.
          // This reads the file and automatically detects the UTF-8 encoding.
          let inputStream = new FileInputStream(
            new lazy.FileUtils.File(this._options.backupFile),
            lazy.FileUtils.MODE_RDONLY,
            lazy.FileUtils.PERMS_FILE,
            0
          );
          try {
            let bytes = lazy.NetUtil.readInputStream(
              inputStream,
              inputStream.available()
            );
            data = JSON.parse(lazy.gTextDecoder.decode(bytes));
          } finally {
            inputStream.close();
          }
        } catch (e3) {
          if (e3.result != Cr.NS_ERROR_FILE_NOT_FOUND) {
            console.error(e3);
          }
        }
      }
    }

    this._processLoadedData(data);
  },

  /**
   * Called when the data changed, this triggers asynchronous serialization.
   */
  saveSoon() {
    return this._saver.arm();
  },

  /**
   * Saves persistent data from memory to the file.
   *
   * If an error occurs, the previous file is not deleted.
   *
   * @return {Promise}
   * @resolves When the operation finished successfully.
   * @rejects JavaScript exception.
   */
  async _save() {
    // Create or overwrite the file.
    if (this._beforeSave) {
      await Promise.resolve(this._beforeSave());
    }

    try {
      await IOUtils.writeJSON(
        this.path,
        this._data,
        Object.assign({ tmpPath: this.path + ".tmp" }, this._options)
      );
    } catch (ex) {
      if (typeof this._data.toJSONSafe == "function") {
        // If serialization fails, try fallback safe JSON converter.
        await IOUtils.writeUTF8(
          this.path,
          this._data.toJSONSafe(),
          Object.assign({ tmpPath: this.path + ".tmp" }, this._options)
        );
      }
    }
  },

  /**
   * Synchronously work on the data just loaded into memory.
   */
  _processLoadedData(data) {
    if (this._finalizePromise) {
      // It's possible for `load` to race with `finalize`. In that case, don't
      // process or set the loaded data.
      return;
    }
    this.data = this._dataPostProcessor ? this._dataPostProcessor(data) : data;
  },

  _recordTelemetry(method, value) {
    if (!TELEMETRY_BASENAMES.has(this.sanitizedBasename)) {
      // Avoid recording so we don't log an error in the console.
      return;
    }

    Services.telemetry.recordEvent(
      "jsonfile",
      method,
      this.sanitizedBasename,
      value
    );
  },

  /**
   * Finishes persisting data to disk and resets all state for this file.
   *
   * @return {Promise}
   * @resolves When the object is finalized.
   */
  _finalizeInternal() {
    if (this._finalizePromise) {
      // Finalization already in progress; return the pending promise. This is
      // possible if `finalize` is called concurrently with shutdown.
      return this._finalizePromise;
    }
    this._finalizePromise = (async () => {
      await this._saver.finalize();
      this._data = null;
      this.dataReady = false;
    })();
    return this._finalizePromise;
  },

  /**
   * Ensures that all data is persisted to disk, and prevents future calls to
   * `saveSoon`. This is called automatically on shutdown, but can also be
   * called explicitly when the file is no longer needed.
   */
  async finalize() {
    if (this._finalizePromise) {
      throw new Error(`The file ${this.path} has already been finalized`);
    }
    // Wait for finalization before removing the shutdown blocker.
    await this._finalizeInternal();
    this._finalizeAt.removeBlocker(this._finalizeInternalBound);
  },
};
PK
!<c���modules/JsonSchema.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * A facade around @cfworker/json-schema that provides additional formats and
 * convenience methods whil executing inside a sandbox.
 */

const sandbox = new Cu.Sandbox(null, {
  wantComponents: false,
  wantGlobalProperties: ["URL"],
});

Services.scriptloader.loadSubScript(
  "chrome://global/content/third_party/cfworker/json-schema.js",
  sandbox
);

/**
 * A JSON Schema string format for URLs intended to go through Services.urlFormatter.
 */
Cu.exportFunction(
  function validateMozUrlFormat(input) {
    try {
      const formatted = Services.urlFormatter.formatURL(input);
      return Cu.waiveXrays(sandbox.fastFormat).uri(formatted);
    } catch {
      return false;
    }
  },
  sandbox.fastFormat,
  { defineAs: "moz-url-format" }
);

// initialBaseURI defaults to github.com/cfworker, which will be confusing.
Cu.evalInSandbox(
  `this.initialBaseURI = initialBaseURI = new URL("http://mozilla.org");`,
  sandbox
);

/**
 * A JSONSchema validator that performs validation inside a sandbox.
 */
class Validator {
  #inner;
  #draft;

  /**
   * Create a new validator.
   *
   * @param {object} schema The schema to validate with.
   * @param {object} options  Options for the validator.
   * @param {string} options.draft  The draft to validate against. Should be one
   *                                of "4", "6", "7", "2019-09", or "2020-12".
   *
   *                                If the |$schema| key is present in the
   *                                |schema|, it will be used to auto-detect the
   *                                correct version.  Otherwise, 2019-09 will be
   *                                used.
   * @param {boolean} options.shortCircuit  Whether or not the validator should
   *                                        return after a single error occurs.
   */
  constructor(
    schema,
    { draft = detectSchemaDraft(schema), shortCircuit = true } = {}
  ) {
    this.#draft = draft;
    this.#inner = Cu.waiveXrays(
      new sandbox.Validator(Cu.cloneInto(schema, sandbox), draft, shortCircuit)
    );
  }

  /**
   * Validate the instance against the known schemas.
   *
   * @param {object} instance  The instance to validate.
   *
   * @return {object}  An object with |valid| and |errors| keys that indicates
   *                   the success of validation.
   */
  validate(instance) {
    return this.#inner.validate(Cu.cloneInto(instance, sandbox));
  }

  /**
   * Add a schema to the validator.
   *
   * @param {object} schema  A JSON schema object.
   * @param {string} id  An optional ID to identify the schema if it does not
   *                     provide an |$id| field.
   */
  addSchema(schema, id) {
    const draft = detectSchemaDraft(schema, undefined);
    if (draft && this.#draft != draft) {
      console.error(
        `Adding a draft "${draft}" schema to a draft "${
          this.#draft
        }" validator.`
      );
    }
    this.#inner.addSchema(Cu.cloneInto(schema, sandbox), id);
  }
}

/**
 * A wrapper around validate that provides some options as an object
 * instead of positional arguments.
 *
 * @param {object} instance  The instance to validate.
 * @param {object} schema  The JSON schema to validate against.
 * @param {object} options  Options for the validator.
 * @param {string} options.draft  The draft to validate against. Should
 *                                be one of "4", "6", "7", "2019-09", or "2020-12".
 *
 *                               If the |$schema| key is present in the |schema|, it
 *                               will be used to auto-detect the correct version.
 *                               Otherwise, 2019-09 will be used.
 * @param {boolean} options.shortCircuit  Whether or not the validator should
 *                                        return after a single error occurs.
 *
 * @returns {object} An object with |valid| and |errors| keys that indicates the
 *                   success of validation.
 */
function validate(
  instance,
  schema,
  { draft = detectSchemaDraft(schema), shortCircuit = true } = {}
) {
  const clonedSchema = Cu.cloneInto(schema, sandbox);

  return sandbox.validate(
    Cu.cloneInto(instance, sandbox),
    clonedSchema,
    draft,
    sandbox.dereference(clonedSchema),
    shortCircuit
  );
}

function detectSchemaDraft(schema, defaultDraft = "2019-09") {
  const { $schema } = schema;

  if (typeof $schema === "undefined") {
    return defaultDraft;
  }

  switch ($schema) {
    case "http://json-schema.org/draft-04/schema#":
      return "4";

    case "http://json-schema.org/draft-06/schema#":
      return "6";

    case "http://json-schema.org/draft-07/schema#":
      return "7";

    case "https://json-schema.org/draft/2019-09/schema":
      return "2019-09";

    case "https://json-schema.org/draft/2020-12/schema":
      return "2020-12";

    default:
      console.error(
        `Unexpected $schema "${$schema}", defaulting to ${defaultDraft}.`
      );
      return defaultDraft;
  }
}

export const JsonSchema = {
  Validator,
  validate,
  detectSchemaDraft,
};
PK
!<�t�U��modules/KeywordUtils.sys.mjs/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
});

export var KeywordUtils = {
  /**
   * Replaces %s or %S in the provided url or postData with the given parameter,
   * acccording to the best charset for the given url.
   *
   * @return [url, postData]
   * @throws if nor url nor postData accept a param, but a param was provided.
   */
  async parseUrlAndPostData(url, postData, param) {
    let hasGETParam = /%s/i.test(url);
    let decodedPostData = postData ? unescape(postData) : "";
    let hasPOSTParam = /%s/i.test(decodedPostData);

    if (!hasGETParam && !hasPOSTParam) {
      if (param) {
        // If nor the url, nor postData contain parameters, but a parameter was
        // provided, return the original input.
        throw new Error(
          "A param was provided but there's nothing to bind it to"
        );
      }
      return [url, postData];
    }

    let charset = "";
    const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/;
    let matches = url.match(re);
    if (matches) {
      [, url, charset] = matches;
    } else {
      // Try to fetch a charset from History.
      try {
        // Will return an empty string if character-set is not found.
        let pageInfo = await lazy.PlacesUtils.history.fetch(url, {
          includeAnnotations: true,
        });
        if (
          pageInfo &&
          pageInfo.annotations.has(lazy.PlacesUtils.CHARSET_ANNO)
        ) {
          charset = pageInfo.annotations.get(lazy.PlacesUtils.CHARSET_ANNO);
        }
      } catch (ex) {
        // makeURI() throws if url is invalid.
        console.error(ex);
      }
    }

    // encodeURIComponent produces UTF-8, and cannot be used for other charsets.
    // escape() works in those cases, but it doesn't uri-encode +, @, and /.
    // Therefore we need to manually replace these ASCII characters by their
    // encodeURIComponent result, to match the behavior of nsEscape() with
    // url_XPAlphas.
    let encodedParam = "";
    if (charset && charset != "UTF-8") {
      try {
        let converter = Cc[
          "@mozilla.org/intl/scriptableunicodeconverter"
        ].createInstance(Ci.nsIScriptableUnicodeConverter);
        converter.charset = charset;
        encodedParam = converter.ConvertFromUnicode(param) + converter.Finish();
      } catch (ex) {
        encodedParam = param;
      }
      encodedParam = escape(encodedParam).replace(
        /[+@\/]+/g,
        encodeURIComponent
      );
    } else {
      // Default charset is UTF-8
      encodedParam = encodeURIComponent(param);
    }

    url = url.replace(/%s/g, encodedParam).replace(/%S/g, param);
    if (hasPOSTParam) {
      postData = decodedPostData
        .replace(/%s/g, encodedParam)
        .replace(/%S/g, param);
    }
    return [url, postData];
  },

  /**
   * Returns a set of parameters if a keyword is registered and the search
   * string can be bound to it.
   *
   * @param {string} keyword The typed keyword.
   * @param {string} searchString The full search string, including the keyword.
   * @returns { entry, url, postData }
   */
  async getBindableKeyword(keyword, searchString) {
    let entry = await lazy.PlacesUtils.keywords.fetch(keyword);
    if (!entry) {
      return {};
    }

    try {
      let [url, postData] = await this.parseUrlAndPostData(
        entry.url.href,
        entry.postData,
        searchString
      );
      return { entry, url, postData };
    } catch (ex) {
      return {};
    }
  },
};
PK
!<p��%,%,modules/LangPackMatcher.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  AddonRepository: "resource://gre/modules/addons/AddonRepository.sys.mjs",
});

if (Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT) {
  // This check ensures that the `mockable` API calls can be consisently mocked in tests.
  // If this requirement needs to be eased, please ensure the test logic remains valid.
  throw new Error("This code is assumed to run in the parent process.");
}

/**
 * Attempts to find an appropriate langpack for a given language. The async function
 * is infallible, but may not return a langpack.
 *
 * @returns {{
 *   langPack: LangPack | null,
 *   langPackDisplayName: string | null
 * }}
 */
async function negotiateLangPackForLanguageMismatch() {
  const localeInfo = getAppAndSystemLocaleInfo();
  const nullResult = {
    langPack: null,
    langPackDisplayName: null,
  };
  if (!localeInfo.systemLocale) {
    // The system locale info was not valid.
    return nullResult;
  }

  /**
   * Fetch the available langpacks from AMO.
   *
   * @type {Array<LangPack>}
   */
  const availableLangpacks = await mockable.getAvailableLangpacks();
  if (!availableLangpacks) {
    return nullResult;
  }

  /**
   * Figure out a langpack to recommend.
   * @type {LangPack | null}
   */
  const langPack =
    // First look for a langpack that matches the baseName, which may include a script.
    // e.g. system "fr-FR" matches langpack "fr-FR"
    //      system "en-GB" matches langpack "en-GB".
    //      system "zh-Hant-CN" matches langpack "zh-Hant-CN".
    availableLangpacks.find(
      ({ target_locale }) => target_locale === localeInfo.systemLocale.baseName
    ) ||
    // Next try matching language and region while excluding script
    // e.g. system "zh-Hant-TW" matches langpack "zh-TW" but not "zh-CN".
    availableLangpacks.find(
      ({ target_locale }) =>
        target_locale ===
        `${localeInfo.systemLocale.language}-${localeInfo.systemLocale.region}`
    ) ||
    // Next look for langpacks that just match the language.
    // e.g. system "fr-FR" matches langpack "fr".
    //      system "en-AU" matches langpack "en".
    availableLangpacks.find(
      ({ target_locale }) => target_locale === localeInfo.systemLocale.language
    ) ||
    // Next look for a langpack that matches the language, but not the region.
    // e.g. "es-CL" (Chilean Spanish) as a system language matching
    //      "es-ES" (European Spanish)
    availableLangpacks.find(({ target_locale }) =>
      target_locale.startsWith(`${localeInfo.systemLocale.language}-`)
    ) ||
    null;

  if (!langPack) {
    return nullResult;
  }

  return {
    langPack,
    langPackDisplayName: Services.intl.getLocaleDisplayNames(
      undefined,
      [langPack.target_locale],
      { preferNative: true }
    )[0],
  };
}

// If a langpack is being installed, allow blocking on that.
let installingLangpack = new Map();

/**
 * @typedef {LangPack}
 * @type {object}
 * @property {string} target_locale
 * @property {string} url
 * @property {string} hash
 */

/**
 * Ensure that a given lanpack is installed.
 *
 * @param {LangPack} langPack
 * @returns {Promise<boolean>} Success or failure.
 */
function ensureLangPackInstalled(langPack) {
  if (!langPack) {
    throw new Error("Expected a LangPack to install.");
  }
  // Make sure any outstanding calls get resolved before attempting another call.
  // This guards against any quick page refreshes attempting to install the langpack
  // twice.
  const inProgress = installingLangpack.get(langPack.hash);
  if (inProgress) {
    return inProgress;
  }
  const promise = _ensureLangPackInstalledImpl(langPack);
  installingLangpack.set(langPack.hash, promise);
  promise.finally(() => {
    installingLangpack.delete(langPack.hash);
  });
  return promise;
}

/**
 * @param {LangPack} langPack
 * @returns {boolean} Success or failure.
 */
async function _ensureLangPackInstalledImpl(langPack) {
  const availablelocales = await getAvailableLocales();
  if (availablelocales.includes(langPack.target_locale)) {
    // The langpack is already installed.
    return true;
  }

  return mockable.installLangPack(langPack);
}

/**
 * These are all functions with side effects or configuration options that should be
 * mockable for tests.
 */
const mockable = {
  /**
   * @returns {LangPack[] | null}
   */
  async getAvailableLangpacks() {
    try {
      return lazy.AddonRepository.getAvailableLangpacks();
    } catch (error) {
      console.error(
        `Failed to get the list of available language packs: ${error?.message}`
      );
      return null;
    }
  },

  /**
   * Use the AddonManager to install an addon from the URL.
   * @param {LangPack} langPack
   */
  async installLangPack(langPack) {
    let install;
    try {
      install = await lazy.AddonManager.getInstallForURL(langPack.url, {
        hash: langPack.hash,
        telemetryInfo: {
          source: "about:welcome",
        },
      });
    } catch (error) {
      console.error(error);
      return false;
    }

    try {
      await install.install();
    } catch (error) {
      console.error(error);
      return false;
    }
    return true;
  },

  /**
   * Returns the available locales, including the fallback locale, which may not include
   * all of the resources, in cases where the defaultLocale is not "en-US".
   *
   * @returns {string[]}
   */
  getAvailableLocalesIncludingFallback() {
    return Services.locale.availableLocales;
  },

  /**
   * @returns {string}
   */
  getDefaultLocale() {
    return Services.locale.defaultLocale;
  },

  /**
   * @returns {string}
   */
  getLastFallbackLocale() {
    return Services.locale.lastFallbackLocale;
  },

  /**
   * @returns {string}
   */
  getAppLocaleAsBCP47() {
    return Services.locale.appLocaleAsBCP47;
  },

  /**
   * @returns {string}
   */
  getSystemLocale() {
    // Allow the system locale to be overridden for manual testing.
    const systemLocaleOverride = Services.prefs.getCharPref(
      "intl.multilingual.aboutWelcome.systemLocaleOverride",
      null
    );
    if (systemLocaleOverride) {
      try {
        // If the locale can't be parsed, ignore the pref.
        new Services.intl.Locale(systemLocaleOverride);
        return systemLocaleOverride;
      } catch (_error) {}
    }

    const osPrefs = Cc["@mozilla.org/intl/ospreferences;1"].getService(
      Ci.mozIOSPreferences
    );
    return osPrefs.systemLocale;
  },

  /**
   * @param {string[]} locales The BCP 47 locale identifiers.
   */
  setRequestedAppLocales(locales) {
    Services.locale.requestedLocales = locales;
  },
};

/**
 * This function is really only setting `Services.locale.requestedLocales`, but it's
 * using the `mockable` object to allow this behavior to be mocked in tests.
 *
 * @param {string[]} locales The BCP 47 locale identifiers.
 */
function setRequestedAppLocales(locales) {
  mockable.setRequestedAppLocales(locales);
}

/**
 * A serializable Intl.Locale.
 *
 * @typedef StructuredLocale
 * @type {object}
 * @property {string} baseName
 * @property {string} language
 * @property {string} region
 */

/**
 * In telemetry data, some of the system locales show up as blank. Guard against this
 * and any other malformed locale information provided by the system by wrapping the call
 * into a catch/try.
 *
 * @param {string} locale
 * @returns {StructuredLocale | null}
 */
function getStructuredLocaleOrNull(localeString) {
  try {
    const locale = new Services.intl.Locale(localeString);
    return {
      baseName: locale.baseName,
      language: locale.language,
      region: locale.region,
    };
  } catch (_err) {
    return null;
  }
}

/**
 * Determine the system and app locales, and how much the locales match.
 *
 * @returns {{
 *  systemLocale: StructuredLocale,
 *  appLocale: StructuredLocale,
 *  matchType: "unknown" | "language-mismatch" | "region-mismatch" | "match",
 * }}
 */
function getAppAndSystemLocaleInfo() {
  // Convert locale strings into structured locale objects.
  const systemLocaleRaw = mockable.getSystemLocale();
  const appLocaleRaw = mockable.getAppLocaleAsBCP47();

  const systemLocale = getStructuredLocaleOrNull(systemLocaleRaw);
  const appLocale = getStructuredLocaleOrNull(appLocaleRaw);

  let matchType = "unknown";
  if (systemLocale && appLocale) {
    if (systemLocale.language !== appLocale.language) {
      matchType = "language-mismatch";
    } else if (systemLocale.region !== appLocale.region) {
      matchType = "region-mismatch";
    } else {
      matchType = "match";
    }
  }

  // Live reloading with bidi switching may not be supported.
  let canLiveReload = null;
  if (systemLocale && appLocale) {
    const systemDirection = Services.intl.getScriptDirection(
      systemLocale.language
    );
    const appDirection = Services.intl.getScriptDirection(appLocale.language);
    const supportsBidiSwitching = Services.prefs.getBoolPref(
      "intl.multilingual.liveReloadBidirectional",
      false
    );
    canLiveReload = systemDirection === appDirection || supportsBidiSwitching;
  }
  return {
    // Return the Intl.Locale in a serializable form.
    systemLocaleRaw,
    systemLocale,
    appLocaleRaw,
    appLocale,
    matchType,
    canLiveReload,

    // These can be used as Fluent message args.
    displayNames: {
      systemLanguage: systemLocale
        ? Services.intl.getLocaleDisplayNames(
            undefined,
            [systemLocale.baseName],
            { preferNative: true }
          )[0]
        : null,
      appLanguage: appLocale
        ? Services.intl.getLocaleDisplayNames(undefined, [appLocale.baseName], {
            preferNative: true,
          })[0]
        : null,
    },
  };
}

/**
 * Filter the lastFallbackLocale from availableLocales if it doesn't have all
 * of the needed strings.
 *
 * When the lastFallbackLocale isn't the defaultLocale, then by default only
 * fluent strings are included. To fully use that locale you need the langpack
 * to be installed, so if it isn't installed remove it from availableLocales.
 */
async function getAvailableLocales() {
  const availableLocales = mockable.getAvailableLocalesIncludingFallback();
  const defaultLocale = mockable.getDefaultLocale();
  const lastFallbackLocale = mockable.getLastFallbackLocale();
  // If defaultLocale isn't lastFallbackLocale, then we still need the langpack
  // for lastFallbackLocale for it to be useful.
  if (defaultLocale != lastFallbackLocale) {
    let lastFallbackId = `langpack-${lastFallbackLocale}@firefox.mozilla.org`;
    let lastFallbackInstalled = await lazy.AddonManager.getAddonByID(
      lastFallbackId
    );
    if (!lastFallbackInstalled) {
      return availableLocales.filter(locale => locale != lastFallbackLocale);
    }
  }
  return availableLocales;
}

export var LangPackMatcher = {
  negotiateLangPackForLanguageMismatch,
  ensureLangPackInstalled,
  getAppAndSystemLocaleInfo,
  setRequestedAppLocales,
  getAvailableLocales,
  mockable,
};
PK
!<[�m

modules/LayoutUtils.sys.mjs/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export var LayoutUtils = {
  /**
   * For a given DOM element, returns its position in screen coordinates of CSS units
   * (<https://developer.mozilla.org/en-US/docs/Web/CSS/CSSOM_View/Coordinate_systems#screen>).
   */
  getElementBoundingScreenRect(aElement) {
    let rect = aElement.getBoundingClientRect();
    let win = aElement.ownerGlobal;

    const { x, y, width, height } = this._rectToClientRect(win, rect);
    return win.windowUtils.toScreenRectInCSSUnits(x, y, width, height);
  },

  /**
   * Similar to getElementBoundingScreenRect using window and rect,
   * returns screen coordinates in screen units.
   */
  rectToScreenRect(win, rect) {
    const { x, y, width, height } = this._rectToClientRect(win, rect);
    return win.ownerGlobal.windowUtils.toScreenRect(x, y, width, height);
  },

  /**
   * Convert rect into the top level widget coordinates in LayoutDevicePixel
   * units.
   */
  rectToTopLevelWidgetRect(win, rect) {
    const { x, y, width, height } = this._rectToClientRect(win, rect);
    return win.ownerGlobal.windowUtils.toTopLevelWidgetRect(
      x,
      y,
      width,
      height
    );
  },

  _rectToClientRect(win, rect) {
    // We need to compensate the position for ancestor iframes in the same
    // process that might shift things over. Those might have different CSS
    // pixel scales, so we compute the position in device pixels and then go
    // back to css pixels at the end.
    let winDpr = win.devicePixelRatio;
    let x = rect.left * winDpr;
    let y = rect.top * winDpr;

    let parentFrame = win.browsingContext?.embedderElement;
    while (parentFrame) {
      win = parentFrame.ownerGlobal;
      let cstyle = win.getComputedStyle(parentFrame);

      let framerect = parentFrame.getBoundingClientRect();
      let xDelta =
        framerect.left +
        parseFloat(cstyle.borderLeftWidth) +
        parseFloat(cstyle.paddingLeft);
      let yDelta =
        framerect.top +
        parseFloat(cstyle.borderTopWidth) +
        parseFloat(cstyle.paddingTop);

      x += xDelta * win.devicePixelRatio;
      y += yDelta * win.devicePixelRatio;

      parentFrame = win.browsingContext?.embedderElement;
    }

    return {
      x: x / winDpr,
      y: y / winDpr,
      width: rect.width,
      height: rect.height,
    };
  },
};
PK
!<�3�7�Q�Q(modules/LightweightThemeConsumer.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};
// Get the theme variables from the app resource directory.
// This allows per-app variables.
ChromeUtils.defineESModuleGetters(lazy, {
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
  ThemeContentPropertyList: "resource:///modules/ThemeVariableMap.sys.mjs",
  ThemeVariableMap: "resource:///modules/ThemeVariableMap.sys.mjs",
});

// Whether the content and chrome areas should always use the same color
// scheme (unless user-overridden). Thunderbird uses this.
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "BROWSER_THEME_UNIFIED_COLOR_SCHEME",
  "browser.theme.unified-color-scheme",
  false
);

const DEFAULT_THEME_ID = "default-theme@mozilla.org";

const toolkitVariableMap = [
  [
    "--lwt-accent-color",
    {
      lwtProperty: "accentcolor",
      processColor(rgbaChannels) {
        if (!rgbaChannels || rgbaChannels.a == 0) {
          return "white";
        }
        // Remove the alpha channel
        const { r, g, b } = rgbaChannels;
        return `rgb(${r}, ${g}, ${b})`;
      },
    },
  ],
  [
    "--lwt-text-color",
    {
      lwtProperty: "textcolor",
      processColor(rgbaChannels) {
        if (!rgbaChannels) {
          rgbaChannels = { r: 0, g: 0, b: 0 };
        }
        // Remove the alpha channel
        const { r, g, b } = rgbaChannels;
        return `rgba(${r}, ${g}, ${b})`;
      },
    },
  ],
  [
    "--arrowpanel-background",
    {
      lwtProperty: "popup",
    },
  ],
  [
    "--arrowpanel-color",
    {
      lwtProperty: "popup_text",
    },
  ],
  [
    "--arrowpanel-border-color",
    {
      lwtProperty: "popup_border",
    },
  ],
  [
    "--toolbar-field-background-color",
    {
      lwtProperty: "toolbar_field",
      fallbackColor: "rgba(255, 255, 255, 0.8)",
    },
  ],
  [
    "--toolbar-bgcolor",
    {
      lwtProperty: "toolbarColor",
    },
  ],
  [
    "--toolbar-color",
    {
      lwtProperty: "toolbar_text",
    },
  ],
  [
    "--toolbar-field-color",
    {
      lwtProperty: "toolbar_field_text",
      fallbackColor: "black",
    },
  ],
  [
    "--toolbar-field-border-color",
    {
      lwtProperty: "toolbar_field_border",
      fallbackColor: "transparent",
    },
  ],
  [
    "--toolbar-field-focus-background-color",
    {
      lwtProperty: "toolbar_field_focus",
      fallbackProperty: "toolbar_field",
      fallbackColor: "white",
      processColor(rgbaChannels, element, propertyOverrides) {
        if (!rgbaChannels) {
          return null;
        }
        // Ensure minimum opacity as this is used behind address bar results.
        const min_opacity = 0.9;
        let { r, g, b, a } = rgbaChannels;
        if (a < min_opacity) {
          propertyOverrides.set(
            "toolbar_field_text_focus",
            _isColorDark(r, g, b) ? "white" : "black"
          );
          return `rgba(${r}, ${g}, ${b}, ${min_opacity})`;
        }
        return `rgba(${r}, ${g}, ${b}, ${a})`;
      },
    },
  ],
  [
    "--toolbar-field-focus-color",
    {
      lwtProperty: "toolbar_field_text_focus",
      fallbackProperty: "toolbar_field_text",
      fallbackColor: "black",
    },
  ],
  [
    "--toolbar-field-focus-border-color",
    {
      lwtProperty: "toolbar_field_border_focus",
    },
  ],
  [
    "--lwt-toolbar-field-highlight",
    {
      lwtProperty: "toolbar_field_highlight",
      processColor(rgbaChannels) {
        if (!rgbaChannels) {
          return null;
        }
        const { r, g, b, a } = rgbaChannels;
        return `rgba(${r}, ${g}, ${b}, ${a})`;
      },
    },
  ],
  [
    "--lwt-toolbar-field-highlight-text",
    {
      lwtProperty: "toolbar_field_highlight_text",
    },
  ],
  // The following 3 are given to the new tab page by contentTheme.js. They are
  // also exposed here, in the browser chrome, so popups anchored on top of the
  // new tab page can use them to avoid clashing with the new tab page content.
  [
    "--newtab-background-color",
    {
      lwtProperty: "ntp_background",
      processColor(rgbaChannels) {
        if (!rgbaChannels) {
          return null;
        }
        const { r, g, b } = rgbaChannels;
        // Drop alpha channel
        return `rgb(${r}, ${g}, ${b})`;
      },
    },
  ],
  [
    "--newtab-background-color-secondary",
    { lwtProperty: "ntp_card_background" },
  ],
  [
    "--newtab-text-primary-color",
    {
      lwtProperty: "ntp_text",
      processColor(rgbaChannels, element) {
        if (!rgbaChannels) {
          element.removeAttribute("lwt-newtab-brighttext");
          return null;
        }

        const { r, g, b } = rgbaChannels;
        element.toggleAttribute(
          "lwt-newtab-brighttext",
          0.2125 * r + 0.7154 * g + 0.0721 * b > 110
        );

        return _rgbaToString(rgbaChannels);
      },
    },
  ],
];

export function LightweightThemeConsumer(aDocument) {
  this._doc = aDocument;
  this._win = aDocument.defaultView;
  this._winId = this._win.docShell.outerWindowID;

  Services.obs.addObserver(this, "lightweight-theme-styling-update");

  this.darkThemeMediaQuery = this._win.matchMedia("(-moz-system-dark-theme)");
  this.darkThemeMediaQuery.addListener(this);

  const { LightweightThemeManager } = ChromeUtils.importESModule(
    "resource://gre/modules/LightweightThemeManager.sys.mjs"
  );
  this._update(LightweightThemeManager.themeData);

  this._win.addEventListener("unload", this, { once: true });
}

LightweightThemeConsumer.prototype = {
  _lastData: null,

  observe(aSubject, aTopic) {
    if (aTopic != "lightweight-theme-styling-update") {
      return;
    }

    let data = aSubject.wrappedJSObject;
    if (data.window && data.window !== this._winId) {
      return;
    }

    this._update(data);
  },

  handleEvent(aEvent) {
    if (aEvent.target == this.darkThemeMediaQuery) {
      this._update(this._lastData);
      return;
    }

    switch (aEvent.type) {
      case "unload":
        Services.obs.removeObserver(this, "lightweight-theme-styling-update");
        Services.ppmm.sharedData.delete(`theme/${this._winId}`);
        this._win = this._doc = null;
        if (this.darkThemeMediaQuery) {
          this.darkThemeMediaQuery.removeListener(this);
          this.darkThemeMediaQuery = null;
        }
        break;
    }
  },

  _update(themeData) {
    this._lastData = themeData;

    const hasDarkTheme = !!themeData.darkTheme;
    let updateGlobalThemeData = true;
    let useDarkTheme = (() => {
      if (!hasDarkTheme) {
        return false;
      }

      if (this.darkThemeMediaQuery?.matches) {
        return themeData.darkTheme.id != DEFAULT_THEME_ID;
      }

      // If enabled, apply the dark theme variant to private browsing windows.
      if (
        !Services.prefs.getBoolPref("browser.theme.dark-private-windows") ||
        !lazy.PrivateBrowsingUtils.isWindowPrivate(this._win) ||
        lazy.PrivateBrowsingUtils.permanentPrivateBrowsing
      ) {
        return false;
      }
      // When applying the dark theme for a PBM window we need to skip calling
      // _determineToolbarAndContentTheme, because it applies the color scheme
      // globally for all windows. Skipping this method also means we don't
      // switch the content theme to dark.
      //
      // TODO: On Linux we most likely need to apply the dark theme, but on
      // Windows and macOS we should be able to render light and dark windows
      // with the default theme at the same time.
      updateGlobalThemeData = false;
      return true;
    })();

    // If this is a per-window dark theme, set the color scheme override so
    // child BrowsingContexts, such as embedded prompts, get themed
    // appropriately.
    // If not, reset the color scheme override field. This is required to reset
    // the color scheme on theme switch.
    if (this._win.browsingContext == this._win.browsingContext.top) {
      if (useDarkTheme && !updateGlobalThemeData) {
        this._win.browsingContext.prefersColorSchemeOverride = "dark";
      } else {
        this._win.browsingContext.prefersColorSchemeOverride = "none";
      }
    }

    let theme = useDarkTheme ? themeData.darkTheme : themeData.theme;
    if (!theme) {
      theme = { id: DEFAULT_THEME_ID };
    }
    let hasTheme = theme.id != DEFAULT_THEME_ID || useDarkTheme;

    let root = this._doc.documentElement;
    if (hasTheme && theme.headerURL) {
      root.setAttribute("lwtheme-image", "true");
    } else {
      root.removeAttribute("lwtheme-image");
    }

    this._setExperiment(hasTheme, themeData.experiment, theme.experimental);
    _setImage(this._win, root, hasTheme, "--lwt-header-image", theme.headerURL);
    _setImage(
      this._win,
      root,
      hasTheme,
      "--lwt-additional-images",
      theme.additionalBackgrounds
    );
    let _processedColors = _setProperties(root, hasTheme, theme);

    if (hasTheme) {
      if (updateGlobalThemeData) {
        _determineToolbarAndContentTheme(
          this._doc,
          theme,
          _processedColors,
          hasDarkTheme,
          useDarkTheme
        );
      }
      root.setAttribute("lwtheme", "true");
    } else {
      _determineToolbarAndContentTheme(this._doc, null, null);
      root.removeAttribute("lwtheme");
    }

    _setDarkModeAttributes(this._doc, root, _processedColors, hasTheme);

    let contentThemeData = _getContentProperties(this._doc, hasTheme, theme);
    Services.ppmm.sharedData.set(`theme/${this._winId}`, contentThemeData);
    // We flush sharedData because contentThemeData can be responsible for
    // painting large background surfaces. If this data isn't delivered to the
    // content process before about:home is painted, we will paint a default
    // background and then replace it when sharedData syncs, causing flashing.
    Services.ppmm.sharedData.flush();

    this._win.dispatchEvent(new CustomEvent("windowlwthemeupdate"));
  },

  _setExperiment(hasTheme, experiment, properties) {
    const root = this._doc.documentElement;
    if (this._lastExperimentData) {
      const { stylesheet, usedVariables } = this._lastExperimentData;
      if (stylesheet) {
        stylesheet.remove();
      }
      if (usedVariables) {
        for (const [variable] of usedVariables) {
          _setProperty(root, false, variable);
        }
      }
    }

    this._lastExperimentData = {};

    if (!hasTheme || !experiment) {
      return;
    }

    let usedVariables = [];
    if (properties.colors) {
      for (const property in properties.colors) {
        const cssVariable = experiment.colors[property];
        const value = _rgbaToString(
          _cssColorToRGBA(root.ownerDocument, properties.colors[property])
        );
        usedVariables.push([cssVariable, value]);
      }
    }

    if (properties.images) {
      for (const property in properties.images) {
        const cssVariable = experiment.images[property];
        usedVariables.push([
          cssVariable,
          `url(${properties.images[property]})`,
        ]);
      }
    }
    if (properties.properties) {
      for (const property in properties.properties) {
        const cssVariable = experiment.properties[property];
        usedVariables.push([cssVariable, properties.properties[property]]);
      }
    }
    for (const [variable, value] of usedVariables) {
      _setProperty(root, true, variable, value);
    }
    this._lastExperimentData.usedVariables = usedVariables;

    if (experiment.stylesheet) {
      /* Stylesheet URLs are validated using WebExtension schemas */
      let stylesheetAttr = `href="${experiment.stylesheet}" type="text/css"`;
      let stylesheet = this._doc.createProcessingInstruction(
        "xml-stylesheet",
        stylesheetAttr
      );
      this._doc.insertBefore(stylesheet, root);
      this._lastExperimentData.stylesheet = stylesheet;
    }
  },
};

function _getContentProperties(doc, hasTheme, data) {
  let properties = { hasTheme };
  if (!hasTheme) {
    return properties;
  }
  for (let property in data) {
    if (lazy.ThemeContentPropertyList.includes(property)) {
      properties[property] = _cssColorToRGBA(doc, data[property]);
    }
  }
  if (data.experimental) {
    for (const property in data.experimental.colors) {
      if (lazy.ThemeContentPropertyList.includes(property)) {
        properties[property] = _cssColorToRGBA(
          doc,
          data.experimental.colors[property]
        );
      }
    }
    for (const property in data.experimental.images) {
      if (lazy.ThemeContentPropertyList.includes(property)) {
        properties[property] = `url(${data.experimental.images[property]})`;
      }
    }
    for (const property in data.experimental.properties) {
      if (lazy.ThemeContentPropertyList.includes(property)) {
        properties[property] = data.experimental.properties[property];
      }
    }
  }
  return properties;
}

function _setImage(aWin, aRoot, aActive, aVariableName, aURLs) {
  if (aURLs && !Array.isArray(aURLs)) {
    aURLs = [aURLs];
  }
  _setProperty(
    aRoot,
    aActive,
    aVariableName,
    aURLs && aURLs.map(v => `url(${aWin.CSS.escape(v)})`).join(", ")
  );
}

function _setProperty(elem, hasTheme, variableName, value) {
  if (hasTheme && value) {
    elem.style.setProperty(variableName, value);
  } else {
    elem.style.removeProperty(variableName);
  }
}

function _isToolbarDark(aDoc, aColors) {
  // We prefer looking at toolbar background first (if it's opaque) because
  // some text colors can be dark enough for our heuristics, but still
  // contrast well enough with a dark background, see bug 1743010.
  if (aColors.toolbarColor) {
    let color = _cssColorToRGBA(aDoc, aColors.toolbarColor);
    if (color.a == 1) {
      return _isColorDark(color.r, color.g, color.b);
    }
  }
  if (aColors.toolbar_text) {
    let color = _cssColorToRGBA(aDoc, aColors.toolbar_text);
    return !_isColorDark(color.r, color.g, color.b);
  }
  // It'd seem sensible to try looking at the "frame" background (accentcolor),
  // but we don't because some themes that use background images leave it to
  // black, see bug 1741931.
  //
  // Fall back to black as per the textcolor processing above.
  let color = _cssColorToRGBA(aDoc, aColors.textcolor || "black");
  return !_isColorDark(color.r, color.g, color.b);
}

function _determineToolbarAndContentTheme(
  aDoc,
  aTheme,
  colors,
  aHasDarkTheme = false,
  aIsDarkTheme = false
) {
  const kDark = 0;
  const kLight = 1;
  const kSystem = 2;

  function colorSchemeValue(aColorScheme) {
    if (!aColorScheme) {
      return null;
    }
    switch (aColorScheme) {
      case "light":
        return kLight;
      case "dark":
        return kDark;
      case "system":
        return kSystem;
      case "auto":
      default:
        break;
    }
    return null;
  }

  let toolbarTheme = (function () {
    if (!aTheme) {
      return kSystem;
    }
    let themeValue = colorSchemeValue(aTheme.color_scheme);
    if (themeValue !== null) {
      return themeValue;
    }
    if (aHasDarkTheme) {
      return aIsDarkTheme ? kDark : kLight;
    }
    return _isToolbarDark(aDoc, colors) ? kDark : kLight;
  })();

  let contentTheme = (function () {
    if (lazy.BROWSER_THEME_UNIFIED_COLOR_SCHEME) {
      return toolbarTheme;
    }
    if (!aTheme) {
      return kSystem;
    }
    let themeValue = colorSchemeValue(
      aTheme.content_color_scheme || aTheme.color_scheme
    );
    if (themeValue !== null) {
      return themeValue;
    }
    return kSystem;
  })();

  Services.prefs.setIntPref("browser.theme.toolbar-theme", toolbarTheme);
  Services.prefs.setIntPref("browser.theme.content-theme", contentTheme);
}

/**
 * Sets dark mode attributes on root, if required. We must do this here,
 * instead of in each color's processColor function, because multiple colors
 * are considered.
 * @param {Document} doc
 * @param {Element} root
 * @param {object} colors
 *   The `_processedColors` object from the object created for our theme.
 * @param {boolean} hasTheme
 */
function _setDarkModeAttributes(doc, root, colors, hasTheme) {
  {
    let textColor = _cssColorToRGBA(doc, colors.textcolor);
    if (textColor && !_isColorDark(textColor.r, textColor.g, textColor.b)) {
      root.setAttribute("lwtheme-brighttext", "true");
    } else {
      root.removeAttribute("lwtheme-brighttext");
    }
  }

  if (hasTheme) {
    root.setAttribute(
      "lwt-toolbar",
      _isToolbarDark(doc, colors) ? "dark" : "light"
    );
  } else {
    root.removeAttribute("lwt-toolbar");
  }

  const setAttribute = function (
    attribute,
    textPropertyName,
    backgroundPropertyName
  ) {
    let dark = _determineIfColorPairIsDark(
      doc,
      colors,
      textPropertyName,
      backgroundPropertyName
    );
    if (dark === null) {
      root.removeAttribute(attribute);
    } else {
      root.setAttribute(attribute, dark ? "dark" : "light");
    }
  };

  setAttribute("lwt-tab-selected", "tab_text", "tab_selected");
  setAttribute("lwt-toolbar-field", "toolbar_field_text", "toolbar_field");
  setAttribute(
    "lwt-toolbar-field-focus",
    "toolbar_field_text_focus",
    "toolbar_field_focus"
  );
  setAttribute("lwt-popup", "popup_text", "popup");
  setAttribute("lwt-sidebar", "sidebar_text", "sidebar");
}

/**
 * Determines if a themed color pair should be considered to have a dark color
 * scheme. We consider both the background and foreground (i.e. usually text)
 * colors because some text colors can be dark enough for our heuristics, but
 * still contrast well enough with a dark background
 * @param {Document} doc
 * @param {object} colors
 * @param {string} foregroundElementId
 *   The key for the foreground element in `colors`.
 * @param {string} backgroundElementId
 *   The key for the background element in `colors`.
 * @returns {boolean | null} True if the element should be considered dark, false
 *   if light, null for preferred scheme.
 */
function _determineIfColorPairIsDark(
  doc,
  colors,
  textPropertyName,
  backgroundPropertyName
) {
  if (!colors[backgroundPropertyName] && !colors[textPropertyName]) {
    // Handles the system theme.
    return null;
  }

  let color = _cssColorToRGBA(doc, colors[backgroundPropertyName]);
  if (color && color.a == 1) {
    return _isColorDark(color.r, color.g, color.b);
  }

  color = _cssColorToRGBA(doc, colors[textPropertyName]);
  if (!color) {
    // Handles the case where a theme only provides a background color and it is
    // semi-transparent.
    return null;
  }

  return !_isColorDark(color.r, color.g, color.b);
}

function _setProperties(root, hasTheme, themeData) {
  let propertyOverrides = new Map();
  let doc = root.ownerDocument;

  // Copy the theme into _processedColors. We'll replace values with processed
  // colors if necessary. We copy because some colors (such as those used in
  // content) are not processed here, but are referenced in places that check
  // _processedColors. Copying means _processedColors will contain irrelevant
  // properties like `id`. There aren't too many, so that's OK.
  let _processedColors = { ...themeData };
  for (let map of [toolkitVariableMap, lazy.ThemeVariableMap]) {
    for (let [cssVarName, definition] of map) {
      const {
        lwtProperty,
        fallbackProperty,
        fallbackColor,
        processColor,
        isColor = true,
      } = definition;
      let val = propertyOverrides.get(lwtProperty) || themeData[lwtProperty];
      if (isColor) {
        val = _cssColorToRGBA(doc, val);
        if (!val && fallbackProperty) {
          val = _cssColorToRGBA(doc, themeData[fallbackProperty]);
        }
        if (!val && hasTheme && fallbackColor) {
          val = _cssColorToRGBA(doc, fallbackColor);
        }
        if (processColor) {
          val = processColor(val, root, propertyOverrides);
        } else {
          val = _rgbaToString(val);
        }
      }

      // Add processed color to themeData.
      _processedColors[lwtProperty] = val;

      _setProperty(root, hasTheme, cssVarName, val);
    }
  }
  return _processedColors;
}

const kInvalidColor = { r: 0, g: 0, b: 0, a: 1 };

function _cssColorToRGBA(doc, cssColor) {
  if (!cssColor) {
    return null;
  }
  return (
    doc.defaultView.InspectorUtils.colorToRGBA(cssColor, doc) || kInvalidColor
  );
}

function _rgbaToString(parsedColor) {
  if (!parsedColor) {
    return null;
  }
  let { r, g, b, a } = parsedColor;
  if (a == 1) {
    return `rgb(${r}, ${g}, ${b})`;
  }
  return `rgba(${r}, ${g}, ${b}, ${a})`;
}

function _isColorDark(r, g, b) {
  return 0.2125 * r + 0.7154 * g + 0.0721 * b <= 127;
}
PK
!<��>00'modules/LightweightThemeManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Holds optional fallback theme data that will be returned when no data for an
// active theme can be found. This the case for WebExtension Themes, for example.
var _fallbackThemeData = null;

export var LightweightThemeManager = {
  set fallbackThemeData(data) {
    if (data && Object.getOwnPropertyNames(data).length) {
      _fallbackThemeData = Object.assign({}, data);
    } else {
      _fallbackThemeData = null;
    }
  },

  /*
   * Returns the currently active theme, taking the fallback theme into account
   * if we'd be using the default theme otherwise.
   *
   * This will always return the original theme data and not make use of
   * locally persisted resources.
   */
  get currentThemeWithFallback() {
    return _fallbackThemeData && _fallbackThemeData.theme;
  },

  get themeData() {
    return _fallbackThemeData || { theme: null };
  },
};
PK
!<�aIImodules/LocationHelper.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

function isPublic(ap) {
  let mask = "_nomap";
  let result = ap.ssid.indexOf(mask, ap.ssid.length - mask.length);
  return result == -1;
}

function sort(a, b) {
  return b.signal - a.signal;
}

function encode(ap) {
  return { macAddress: ap.mac, signalStrength: ap.signal };
}

/**
 * Shared utility functions for modules dealing with
 * Location Services.
 */

export class LocationHelper {
  static formatWifiAccessPoints(accessPoints) {
    return accessPoints.filter(isPublic).sort(sort).map(encode);
  }

  /**
   * Calculate the distance between 2 points using the Haversine formula.
   * https://en.wikipedia.org/wiki/Haversine_formula
   */
  static distance(p1, p2) {
    let rad = x => (x * Math.PI) / 180;
    // Radius of the earth.
    let R = 6371e3;
    let lat = rad(p2.lat - p1.lat);
    let lng = rad(p2.lng - p1.lng);

    let a =
      Math.sin(lat / 2) * Math.sin(lat / 2) +
      Math.cos(rad(p1.lat)) *
        Math.cos(rad(p2.lat)) *
        Math.sin(lng / 2) *
        Math.sin(lng / 2);

    let c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

    return R * c;
  }
}
PK
!<6�2G�K�Kmodules/Log.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const INTERNAL_FIELDS = new Set(["_level", "_message", "_time", "_namespace"]);

/*
 * Dump a message everywhere we can if we have a failure.
 */
function dumpError(text) {
  dump(text + "\n");
  // TODO: Bug 1801091 - Figure out how to replace this.
  // eslint-disable-next-line mozilla/no-cu-reportError
  Cu.reportError(text);
}

export var Log = {
  Level: {
    Fatal: 70,
    Error: 60,
    Warn: 50,
    Info: 40,
    Config: 30,
    Debug: 20,
    Trace: 10,
    All: -1, // We don't want All to be falsy.
    Desc: {
      70: "FATAL",
      60: "ERROR",
      50: "WARN",
      40: "INFO",
      30: "CONFIG",
      20: "DEBUG",
      10: "TRACE",
      "-1": "ALL",
    },
    Numbers: {
      FATAL: 70,
      ERROR: 60,
      WARN: 50,
      INFO: 40,
      CONFIG: 30,
      DEBUG: 20,
      TRACE: 10,
      ALL: -1,
    },
  },

  get repository() {
    delete Log.repository;
    Log.repository = new LoggerRepository();
    return Log.repository;
  },
  set repository(value) {
    delete Log.repository;
    Log.repository = value;
  },

  _formatError(e) {
    let result = String(e);
    if (e.fileName) {
      let loc = [e.fileName];
      if (e.lineNumber) {
        loc.push(e.lineNumber);
      }
      if (e.columnNumber) {
        loc.push(e.columnNumber);
      }
      result += `(${loc.join(":")})`;
    }
    return `${result} ${Log.stackTrace(e)}`;
  },

  // This is for back compatibility with services/common/utils.js; we duplicate
  // some of the logic in ParameterFormatter
  exceptionStr(e) {
    if (!e) {
      return String(e);
    }
    if (e instanceof Ci.nsIException) {
      return `${e} ${Log.stackTrace(e)}`;
    } else if (isError(e)) {
      return Log._formatError(e);
    }
    // else
    let message = e.message || e;
    return `${message} ${Log.stackTrace(e)}`;
  },

  stackTrace(e) {
    if (!e) {
      return Components.stack.caller.formattedStack.trim();
    }
    // Wrapped nsIException
    if (e.location) {
      let frame = e.location;
      let output = [];
      while (frame) {
        // Works on frames or exceptions, munges file:// URIs to shorten the paths
        // FIXME: filename munging is sort of hackish.
        let str = "<file:unknown>";

        let file = frame.filename || frame.fileName;
        if (file) {
          str = file.replace(/^(?:chrome|file):.*?([^\/\.]+(\.\w+)+)$/, "$1");
        }

        if (frame.lineNumber) {
          str += ":" + frame.lineNumber;
        }

        if (frame.name) {
          str = frame.name + "()@" + str;
        }

        if (str) {
          output.push(str);
        }
        frame = frame.caller;
      }
      return `Stack trace: ${output.join("\n")}`;
    }
    // Standard JS exception
    if (e.stack) {
      let stack = e.stack;
      return (
        "JS Stack trace: " +
        stack.trim().replace(/@[^@]*?([^\/\.]+(\.\w+)+:)/g, "@$1")
      );
    }

    if (e instanceof Ci.nsIStackFrame) {
      return e.formattedStack.trim();
    }
    return "No traceback available";
  },
};

/*
 * LogMessage
 * Encapsulates a single log event's data
 */
class LogMessage {
  constructor(loggerName, level, message, params) {
    this.loggerName = loggerName;
    this.level = level;
    /*
     * Special case to handle "log./level/(object)", for example logging a caught exception
     * without providing text or params like: catch(e) { logger.warn(e) }
     * Treating this as an empty text with the object in the 'params' field causes the
     * object to be formatted properly by BasicFormatter.
     */
    if (
      !params &&
      message &&
      typeof message == "object" &&
      typeof message.valueOf() != "string"
    ) {
      this.message = null;
      this.params = message;
    } else {
      // If the message text is empty, or a string, or a String object, normal handling
      this.message = message;
      this.params = params;
    }

    // The _structured field will correspond to whether this message is to
    // be interpreted as a structured message.
    this._structured = this.params && this.params.action;
    this.time = Date.now();
  }

  get levelDesc() {
    if (this.level in Log.Level.Desc) {
      return Log.Level.Desc[this.level];
    }
    return "UNKNOWN";
  }

  toString() {
    let msg = `${this.time} ${this.level} ${this.message}`;
    if (this.params) {
      msg += ` ${JSON.stringify(this.params)}`;
    }
    return `LogMessage [${msg}]`;
  }
}

/*
 * Logger
 * Hierarchical version.  Logs to all appenders, assigned or inherited
 */

class Logger {
  constructor(name, repository) {
    if (!repository) {
      repository = Log.repository;
    }
    this._name = name;
    this.children = [];
    this.ownAppenders = [];
    this.appenders = [];
    this._repository = repository;

    this._levelPrefName = null;
    this._levelPrefValue = null;
    this._level = null;
    this._parent = null;
  }

  get name() {
    return this._name;
  }

  get level() {
    if (this._levelPrefName) {
      // We've been asked to use a preference to configure the logs. If the
      // pref has a value we use it, otherwise we continue to use the parent.
      const lpv = this._levelPrefValue;
      if (lpv) {
        const levelValue = Log.Level[lpv];
        if (levelValue) {
          // stash it in _level just in case a future value of the pref is
          // invalid, in which case we end up continuing to use this value.
          this._level = levelValue;
          return levelValue;
        }
      } else {
        // in case the pref has transitioned from a value to no value, we reset
        // this._level and fall through to using the parent.
        this._level = null;
      }
    }
    if (this._level != null) {
      return this._level;
    }
    if (this.parent) {
      return this.parent.level;
    }
    dumpError("Log warning: root logger configuration error: no level defined");
    return Log.Level.All;
  }
  set level(level) {
    if (this._levelPrefName) {
      // I guess we could honor this by nuking this._levelPrefValue, but it
      // almost certainly implies confusion, so we'll warn and ignore.
      dumpError(
        `Log warning: The log '${this.name}' is configured to use ` +
          `the preference '${this._levelPrefName}' - you must adjust ` +
          `the level by setting this preference, not by using the ` +
          `level setter`
      );
      return;
    }
    this._level = level;
  }

  get parent() {
    return this._parent;
  }
  set parent(parent) {
    if (this._parent == parent) {
      return;
    }
    // Remove ourselves from parent's children
    if (this._parent) {
      let index = this._parent.children.indexOf(this);
      if (index != -1) {
        this._parent.children.splice(index, 1);
      }
    }
    this._parent = parent;
    parent.children.push(this);
    this.updateAppenders();
  }

  manageLevelFromPref(prefName) {
    if (prefName == this._levelPrefName) {
      // We've already configured this log with an observer for that pref.
      return;
    }
    if (this._levelPrefName) {
      dumpError(
        `The log '${this.name}' is already configured with the ` +
          `preference '${this._levelPrefName}' - ignoring request to ` +
          `also use the preference '${prefName}'`
      );
      return;
    }
    this._levelPrefName = prefName;
    XPCOMUtils.defineLazyPreferenceGetter(this, "_levelPrefValue", prefName);
  }

  updateAppenders() {
    if (this._parent) {
      let notOwnAppenders = this._parent.appenders.filter(function (appender) {
        return !this.ownAppenders.includes(appender);
      }, this);
      this.appenders = notOwnAppenders.concat(this.ownAppenders);
    } else {
      this.appenders = this.ownAppenders.slice();
    }

    // Update children's appenders.
    for (let i = 0; i < this.children.length; i++) {
      this.children[i].updateAppenders();
    }
  }

  addAppender(appender) {
    if (this.ownAppenders.includes(appender)) {
      return;
    }
    this.ownAppenders.push(appender);
    this.updateAppenders();
  }

  removeAppender(appender) {
    let index = this.ownAppenders.indexOf(appender);
    if (index == -1) {
      return;
    }
    this.ownAppenders.splice(index, 1);
    this.updateAppenders();
  }

  _unpackTemplateLiteral(string, params) {
    if (!Array.isArray(params)) {
      // Regular log() call.
      return [string, params];
    }

    if (!Array.isArray(string)) {
      // Not using template literal. However params was packed into an array by
      // the this.[level] call, so we need to unpack it here.
      return [string, params[0]];
    }

    // We're using template literal format (logger.warn `foo ${bar}`). Turn the
    // template strings into one string containing "${0}"..."${n}" tokens, and
    // feed it to the basic formatter. The formatter will treat the numbers as
    // indices into the params array, and convert the tokens to the params.

    if (!params.length) {
      // No params; we need to set params to undefined, so the formatter
      // doesn't try to output the params array.
      return [string[0], undefined];
    }

    let concat = string[0];
    for (let i = 0; i < params.length; i++) {
      concat += `\${${i}}${string[i + 1]}`;
    }
    return [concat, params];
  }

  log(level, string, params) {
    if (this.level > level) {
      return;
    }

    // Hold off on creating the message object until we actually have
    // an appender that's responsible.
    let message;
    let appenders = this.appenders;
    for (let appender of appenders) {
      if (appender.level > level) {
        continue;
      }
      if (!message) {
        [string, params] = this._unpackTemplateLiteral(string, params);
        message = new LogMessage(this._name, level, string, params);
      }
      appender.append(message);
    }
  }

  fatal(string, ...params) {
    this.log(Log.Level.Fatal, string, params);
  }
  error(string, ...params) {
    this.log(Log.Level.Error, string, params);
  }
  warn(string, ...params) {
    this.log(Log.Level.Warn, string, params);
  }
  info(string, ...params) {
    this.log(Log.Level.Info, string, params);
  }
  config(string, ...params) {
    this.log(Log.Level.Config, string, params);
  }
  debug(string, ...params) {
    this.log(Log.Level.Debug, string, params);
  }
  trace(string, ...params) {
    this.log(Log.Level.Trace, string, params);
  }
}

/*
 * LoggerRepository
 * Implements a hierarchy of Loggers
 */

class LoggerRepository {
  constructor() {
    this._loggers = {};
    this._rootLogger = null;
  }

  get rootLogger() {
    if (!this._rootLogger) {
      this._rootLogger = new Logger("root", this);
      this._rootLogger.level = Log.Level.All;
    }
    return this._rootLogger;
  }
  set rootLogger(logger) {
    throw new Error("Cannot change the root logger");
  }

  _updateParents(name) {
    let pieces = name.split(".");
    let cur, parent;

    // find the closest parent
    // don't test for the logger name itself, as there's a chance it's already
    // there in this._loggers
    for (let i = 0; i < pieces.length - 1; i++) {
      if (cur) {
        cur += "." + pieces[i];
      } else {
        cur = pieces[i];
      }
      if (cur in this._loggers) {
        parent = cur;
      }
    }

    // if we didn't assign a parent above, there is no parent
    if (!parent) {
      this._loggers[name].parent = this.rootLogger;
    } else {
      this._loggers[name].parent = this._loggers[parent];
    }

    // trigger updates for any possible descendants of this logger
    for (let logger in this._loggers) {
      if (logger != name && logger.indexOf(name) == 0) {
        this._updateParents(logger);
      }
    }
  }

  /**
   * Obtain a named Logger.
   *
   * The returned Logger instance for a particular name is shared among
   * all callers. In other words, if two consumers call getLogger("foo"),
   * they will both have a reference to the same object.
   *
   * @return Logger
   */
  getLogger(name) {
    if (name in this._loggers) {
      return this._loggers[name];
    }
    this._loggers[name] = new Logger(name, this);
    this._updateParents(name);
    return this._loggers[name];
  }

  /**
   * Obtain a Logger that logs all string messages with a prefix.
   *
   * A common pattern is to have separate Logger instances for each instance
   * of an object. But, you still want to distinguish between each instance.
   * Since Log.repository.getLogger() returns shared Logger objects,
   * monkeypatching one Logger modifies them all.
   *
   * This function returns a new object with a prototype chain that chains
   * up to the original Logger instance. The new prototype has log functions
   * that prefix content to each message.
   *
   * @param name
   *        (string) The Logger to retrieve.
   * @param prefix
   *        (string) The string to prefix each logged message with.
   */
  getLoggerWithMessagePrefix(name, prefix) {
    let log = this.getLogger(name);

    let proxy = Object.create(log);
    proxy.log = (level, string, params) => {
      if (Array.isArray(string) && Array.isArray(params)) {
        // Template literal.
        // We cannot change the original array, so create a new one.
        string = [prefix + string[0]].concat(string.slice(1));
      } else {
        string = prefix + string; // Regular string.
      }
      return log.log(level, string, params);
    };
    return proxy;
  }
}

/*
 * Formatters
 * These massage a LogMessage into whatever output is desired.
 */

// Basic formatter that doesn't do anything fancy.
class BasicFormatter {
  constructor(dateFormat) {
    if (dateFormat) {
      this.dateFormat = dateFormat;
    }
    this.parameterFormatter = new ParameterFormatter();
  }

  /**
   * Format the text of a message with optional parameters.
   * If the text contains ${identifier}, replace that with
   * the value of params[identifier]; if ${}, replace that with
   * the entire params object. If no params have been substituted
   * into the text, format the entire object and append that
   * to the message.
   */
  formatText(message) {
    let params = message.params;
    if (typeof params == "undefined") {
      return message.message || "";
    }
    // Defensive handling of non-object params
    // We could add a special case for NSRESULT values here...
    let pIsObject = typeof params == "object" || typeof params == "function";

    // if we have params, try and find substitutions.
    if (this.parameterFormatter) {
      // have we successfully substituted any parameters into the message?
      // in the log message
      let subDone = false;
      let regex = /\$\{(\S*?)\}/g;
      let textParts = [];
      if (message.message) {
        textParts.push(
          message.message.replace(regex, (_, sub) => {
            // ${foo} means use the params['foo']
            if (sub) {
              if (pIsObject && sub in message.params) {
                subDone = true;
                return this.parameterFormatter.format(message.params[sub]);
              }
              return "${" + sub + "}";
            }
            // ${} means use the entire params object.
            subDone = true;
            return this.parameterFormatter.format(message.params);
          })
        );
      }
      if (!subDone) {
        // There were no substitutions in the text, so format the entire params object
        let rest = this.parameterFormatter.format(message.params);
        if (rest !== null && rest != "{}") {
          textParts.push(rest);
        }
      }
      return textParts.join(": ");
    }
    return undefined;
  }

  format(message) {
    return (
      message.time +
      "\t" +
      message.loggerName +
      "\t" +
      message.levelDesc +
      "\t" +
      this.formatText(message)
    );
  }
}

/**
 * Test an object to see if it is a Mozilla JS Error.
 */
function isError(aObj) {
  return (
    aObj &&
    typeof aObj == "object" &&
    "name" in aObj &&
    "message" in aObj &&
    "fileName" in aObj &&
    "lineNumber" in aObj &&
    "stack" in aObj
  );
}

/*
 * Parameter Formatters
 * These massage an object used as a parameter for a LogMessage into
 * a string representation of the object.
 */

class ParameterFormatter {
  constructor() {
    this._name = "ParameterFormatter";
  }

  format(ob) {
    try {
      if (ob === undefined) {
        return "undefined";
      }
      if (ob === null) {
        return "null";
      }
      // Pass through primitive types and objects that unbox to primitive types.
      if (
        (typeof ob != "object" || typeof ob.valueOf() != "object") &&
        typeof ob != "function"
      ) {
        return ob;
      }
      if (ob instanceof Ci.nsIException) {
        return `${ob} ${Log.stackTrace(ob)}`;
      } else if (isError(ob)) {
        return Log._formatError(ob);
      }
      // Just JSONify it. Filter out our internal fields and those the caller has
      // already handled.
      return JSON.stringify(ob, (key, val) => {
        if (INTERNAL_FIELDS.has(key)) {
          return undefined;
        }
        return val;
      });
    } catch (e) {
      dumpError(
        `Exception trying to format object for log message: ${Log.exceptionStr(
          e
        )}`
      );
    }
    // Fancy formatting failed. Just toSource() it - but even this may fail!
    try {
      return ob.toSource();
    } catch (_) {}
    try {
      return String(ob);
    } catch (_) {
      return "[object]";
    }
  }
}

/*
 * Appenders
 * These can be attached to Loggers to log to different places
 * Simply subclass and override doAppend to implement a new one
 */

class Appender {
  constructor(formatter) {
    this.level = Log.Level.All;
    this._name = "Appender";
    this._formatter = formatter || new BasicFormatter();
  }

  append(message) {
    if (message) {
      this.doAppend(this._formatter.format(message));
    }
  }

  toString() {
    return `${this._name} [level=${this.level}, formatter=${this._formatter}]`;
  }
}

/*
 * DumpAppender
 * Logs to standard out
 */

class DumpAppender extends Appender {
  constructor(formatter) {
    super(formatter);
    this._name = "DumpAppender";
  }

  doAppend(formatted) {
    dump(formatted + "\n");
  }
}

/*
 * ConsoleAppender
 * Logs to the javascript console
 */

class ConsoleAppender extends Appender {
  constructor(formatter) {
    super(formatter);
    this._name = "ConsoleAppender";
  }

  // XXX this should be replaced with calls to the Browser Console
  append(message) {
    if (message) {
      let m = this._formatter.format(message);
      if (message.level > Log.Level.Warn) {
        // TODO: Bug 1801091 - Figure out how to replace this.
        // eslint-disable-next-line mozilla/no-cu-reportError
        Cu.reportError(m);
        return;
      }
      this.doAppend(m);
    }
  }

  doAppend(formatted) {
    Services.console.logStringMessage(formatted);
  }
}

Object.assign(Log, {
  LogMessage,
  Logger,
  LoggerRepository,

  BasicFormatter,

  Appender,
  DumpAppender,
  ConsoleAppender,

  ParameterFormatter,
});
PK
!<,����8�8modules/LogManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This module provides a file-based, persistent logging facility for scenarios where
 * retaining those logs over time and across browser restarts is important.
 * Unless you need this feature specifically, please use console.createInstance.
 */

// See Bug 1889052
// eslint-disable-next-line mozilla/use-console-createInstance
import { Log } from "resource://gre/modules/Log.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
});

const DEFAULT_MAX_ERROR_AGE = 20 * 24 * 60 * 60; // 20 days

// "shared" logs (ie, where the same log name is used by multiple LogManager
// instances) are a fact of life here - eg, FirefoxAccounts logs are used by
// both Sync and Reading List.
// However, different instances have different pref branches, so we need to
// handle when one pref branch says "Debug" and the other says "Error"
// So we (a) keep singleton console and dump appenders and (b) keep track
// of the minimum (ie, most verbose) level and use that.
// This avoids (a) the most recent setter winning (as that is indeterminate)
// and (b) multiple dump/console appenders being added to the same log multiple
// times, which would cause messages to appear twice.

// Singletons used by each instance.
var formatter;
var dumpAppender;
var consoleAppender;

// A set of all preference roots used by all instances.
var allBranches = new Set();

const STREAM_SEGMENT_SIZE = 4096;
const PR_UINT32_MAX = 0xffffffff;

/**
 * Append to an nsIStorageStream
 *
 * This writes logging output to an in-memory stream which can later be read
 * back as an nsIInputStream. It can be used to avoid expensive I/O operations
 * during logging. Instead, one can periodically consume the input stream and
 * e.g. write it to disk asynchronously.
 */
class StorageStreamAppender extends Log.Appender {
  constructor(formatter) {
    super(formatter);
    this._name = "StorageStreamAppender";

    this._converterStream = null; // holds the nsIConverterOutputStream
    this._outputStream = null; // holds the underlying nsIOutputStream

    this._ss = null;
  }

  get outputStream() {
    if (!this._outputStream) {
      // First create a raw stream. We can bail out early if that fails.
      this._outputStream = this.newOutputStream();
      if (!this._outputStream) {
        return null;
      }

      // Wrap the raw stream in an nsIConverterOutputStream. We can reuse
      // the instance if we already have one.
      if (!this._converterStream) {
        this._converterStream = Cc[
          "@mozilla.org/intl/converter-output-stream;1"
        ].createInstance(Ci.nsIConverterOutputStream);
      }
      this._converterStream.init(this._outputStream, "UTF-8");
    }
    return this._converterStream;
  }

  newOutputStream() {
    let ss = (this._ss = Cc["@mozilla.org/storagestream;1"].createInstance(
      Ci.nsIStorageStream
    ));
    ss.init(STREAM_SEGMENT_SIZE, PR_UINT32_MAX, null);
    return ss.getOutputStream(0);
  }

  getInputStream() {
    if (!this._ss) {
      return null;
    }
    return this._ss.newInputStream(0);
  }

  reset() {
    if (!this._outputStream) {
      return;
    }
    this.outputStream.close();
    this._outputStream = null;
    this._ss = null;
  }

  doAppend(formatted) {
    if (!formatted) {
      return;
    }
    try {
      this.outputStream.writeString(formatted + "\n");
    } catch (ex) {
      if (ex.result == Cr.NS_BASE_STREAM_CLOSED) {
        // The underlying output stream is closed, so let's open a new one
        // and try again.
        this._outputStream = null;
      }
      try {
        this.outputStream.writeString(formatted + "\n");
      } catch (ex) {
        // Ah well, we tried, but something seems to be hosed permanently.
      }
    }
  }
}

/**
 * A storage appender that is flushable to a file on disk.
 *
 * Policies for when to flush, to what file, log rotation etc are up to the consumer
 * (although it does maintain a .sawError property to help the consumer decide
 *  based on its policies)
 */
class FlushableStorageAppender extends StorageStreamAppender {
  constructor(formatter) {
    super(formatter);
    this.sawError = false;
  }

  append(message) {
    if (message.level >= Log.Level.Error) {
      this.sawError = true;
    }
    StorageStreamAppender.prototype.append.call(this, message);
  }

  reset() {
    super.reset();
    this.sawError = false;
  }

  /**
   * Flush the current stream to a file.
   *
   * Somewhat counter-intuitively, you must pass a log which will be written to
   * with details of the operation.
   */
  async flushToFile(subdirArray, filename, log) {
    let inStream = this.getInputStream();
    this.reset();
    if (!inStream) {
      log.debug("Failed to flush log to a file - no input stream");
      return;
    }
    log.debug("Flushing file log");
    log.trace("Beginning stream copy to " + filename + ": " + Date.now());
    try {
      await this._copyStreamToFile(inStream, subdirArray, filename, log);
      log.trace("onCopyComplete", Date.now());
    } catch (ex) {
      log.error("Failed to copy log stream to file", ex);
    }
  }

  /**
   * Copy an input stream to the named file, doing everything off the main
   * thread.
   * subDirArray is an array of path components, relative to the profile
   * directory, where the file will be created.
   * outputFileName is the filename to create.
   * Returns a promise that is resolved on completion or rejected with an error.
   */
  async _copyStreamToFile(inputStream, subdirArray, outputFileName, log) {
    let outputDirectory = PathUtils.join(PathUtils.profileDir, ...subdirArray);
    await IOUtils.makeDirectory(outputDirectory);
    let fullOutputFileName = PathUtils.join(outputDirectory, outputFileName);

    let outputStream = Cc[
      "@mozilla.org/network/file-output-stream;1"
    ].createInstance(Ci.nsIFileOutputStream);

    outputStream.init(
      new lazy.FileUtils.File(fullOutputFileName),
      -1,
      -1,
      Ci.nsIFileOutputStream.DEFER_OPEN
    );

    await new Promise(resolve =>
      lazy.NetUtil.asyncCopy(inputStream, outputStream, () => resolve())
    );

    outputStream.close();
    log.trace("finished copy to", fullOutputFileName);
  }
}

/**
 * Each LogManager monitors preferences, resolves log levels and verbosity,
 * and manages the creation, rotation and clean up of log files in a profile subdirectory.
 */
export class LogManager {
  constructor(options = {}) {
    this._prefObservers = [];
    this.#init(options);
  }

  static StorageStreamAppender = StorageStreamAppender;

  _cleaningUpFileLogs = false;

  #init({
    prefRoot,
    logNames,
    logFilePrefix,
    logFileSubDirectoryEntries,
    testTopicPrefix,
  } = {}) {
    this._prefs = Services.prefs.getBranch(prefRoot);
    this._prefsBranch = prefRoot;

    this.logFilePrefix = logFilePrefix;
    this._testTopicPrefix = testTopicPrefix;

    // At this point we don't allow a custom directory for the logs, nor allow
    // it to be outside the profile directory.
    // This returns an array of the the relative directory entries below the
    // profile dir, and is the directory about:sync-log uses.
    this.logFileSubDirectoryEntries = Object.freeze(logFileSubDirectoryEntries);

    if (!formatter) {
      // Create a formatter and various appenders to attach to the logs.
      formatter = new Log.BasicFormatter();
      consoleAppender = new Log.ConsoleAppender(formatter);
      dumpAppender = new Log.DumpAppender(formatter);
    }

    allBranches.add(this._prefsBranch);
    // We create a preference observer for all our prefs so they are magically
    // reflected if the pref changes after creation.
    let setupAppender = (
      appender,
      prefName,
      defaultLevel,
      findSmallest = false
    ) => {
      let observer = newVal => {
        let level = Log.Level[newVal] || defaultLevel;
        if (findSmallest) {
          // As some of our appenders have global impact (ie, there is only one
          // place 'dump' goes to), we need to find the smallest value from all
          // prefs controlling this appender.
          // For example, if consumerA has dump=Debug then consumerB sets
          // dump=Error, we need to keep dump=Debug so consumerA is respected.
          for (let branch of allBranches) {
            let lookPrefBranch = Services.prefs.getBranch(branch);
            let lookVal =
              Log.Level[lookPrefBranch.getStringPref(prefName, null)];
            if (lookVal && lookVal < level) {
              level = lookVal;
            }
          }
        }
        appender.level = level;
      };
      this._prefs.addObserver(prefName, observer);
      this._prefObservers.push([prefName, observer]);
      // and call the observer now with the current pref value.
      observer(this._prefs.getStringPref(prefName, null));
      return observer;
    };

    this._observeConsolePref = setupAppender(
      consoleAppender,
      "log.appender.console",
      Log.Level.Fatal,
      true
    );
    this._observeDumpPref = setupAppender(
      dumpAppender,
      "log.appender.dump",
      Log.Level.Error,
      true
    );

    // The file appender doesn't get the special singleton behaviour.
    let fapp = (this._fileAppender = new FlushableStorageAppender(formatter));
    // the stream gets a default of Debug as the user must go out of their way
    // to see the stuff spewed to it.
    this._observeStreamPref = setupAppender(
      fapp,
      "log.appender.file.level",
      Log.Level.Debug
    );

    // now attach the appenders to all our logs.
    for (let logName of logNames) {
      let log = Log.repository.getLogger(logName);
      for (let appender of [fapp, dumpAppender, consoleAppender]) {
        log.addAppender(appender);
      }
    }
    // and use the first specified log as a "root" for our log.
    this._log = Log.repository.getLogger(logNames[0] + ".LogManager");
  }

  /**
   * Cleanup this instance
   */
  finalize() {
    for (let [prefName, observer] of this._prefObservers) {
      this._prefs.removeObserver(prefName, observer);
    }
    this._prefObservers = [];
    try {
      allBranches.delete(this._prefsBranch);
    } catch (e) {}
    this._prefs = null;
  }

  get sawError() {
    return this._fileAppender.sawError;
  }

  // Result values for resetFileLog.
  SUCCESS_LOG_WRITTEN = "success-log-written";
  ERROR_LOG_WRITTEN = "error-log-written";

  /**
   * Possibly generate a log file for all accumulated log messages and refresh
   * the input & output streams.
   * Whether a "success" or "error" log is written is determined based on
   * whether an "Error" log entry was written to any of the logs.
   * Returns a promise that resolves on completion with either null (for no
   * file written or on error), SUCCESS_LOG_WRITTEN if a "success" log was
   * written, or ERROR_LOG_WRITTEN if an "error" log was written.
   */
  async resetFileLog() {
    try {
      let flushToFile;
      let reasonPrefix;
      let reason;
      if (this._fileAppender.sawError) {
        reason = this.ERROR_LOG_WRITTEN;
        flushToFile = this._prefs.getBoolPref(
          "log.appender.file.logOnError",
          true
        );
        reasonPrefix = "error";
      } else {
        reason = this.SUCCESS_LOG_WRITTEN;
        flushToFile = this._prefs.getBoolPref(
          "log.appender.file.logOnSuccess",
          false
        );
        reasonPrefix = "success";
      }

      // might as well avoid creating an input stream if we aren't going to use it.
      if (!flushToFile) {
        this._fileAppender.reset();
        return null;
      }

      // We have reasonPrefix at the start of the filename so all "error"
      // logs are grouped in about:sync-log.
      let filename =
        reasonPrefix + "-" + this.logFilePrefix + "-" + Date.now() + ".txt";
      await this._fileAppender.flushToFile(
        this.logFileSubDirectoryEntries,
        filename,
        this._log
      );
      if (!this._cleaningUpFileLogs) {
        this._log.trace("Running cleanup.");
        try {
          await this.cleanupLogs();
        } catch (err) {
          this._log.error("Failed to cleanup logs", err);
        }
      }
      return reason;
    } catch (ex) {
      this._log.error("Failed to resetFileLog", ex);
      return null;
    }
  }

  /**
   * Finds all logs older than maxErrorAge and deletes them using async I/O.
   */
  cleanupLogs() {
    let maxAge = this._prefs.getIntPref(
      "log.appender.file.maxErrorAge",
      DEFAULT_MAX_ERROR_AGE
    );
    let threshold = Date.now() - 1000 * maxAge;
    this._log.debug("Log cleanup threshold time: " + threshold);

    let shouldDelete = fileInfo => {
      return fileInfo.lastModified < threshold;
    };
    return this._deleteLogFiles(shouldDelete);
  }

  /**
   * Finds all logs and removes them.
   */
  removeAllLogs() {
    return this._deleteLogFiles(() => true);
  }

  /**
   * Delete some log files. A callback is invoked for each found log file to
   * determine if that file should be removed.
   */
  async _deleteLogFiles(cbShouldDelete) {
    this._cleaningUpFileLogs = true;
    let logDir = lazy.FileUtils.getDir(
      "ProfD",
      this.logFileSubDirectoryEntries
    );
    for (const path of await IOUtils.getChildren(logDir.path)) {
      const name = PathUtils.filename(path);

      if (!name.startsWith("error-") && !name.startsWith("success-")) {
        continue;
      }

      try {
        const info = await IOUtils.stat(path);
        if (!cbShouldDelete(info)) {
          continue;
        }

        this._log.trace(` > Cleanup removing ${name} (${info.lastModified})`);
        await IOUtils.remove(path);
        this._log.trace(`Deleted ${name}`);
      } catch (ex) {
        this._log.debug(
          `Encountered error trying to clean up old log file ${name}`,
          ex
        );
      }
    }
    this._cleaningUpFileLogs = false;
    this._log.debug("Done deleting files.");
    // This notification is used only for tests.
    if (this._testTopicPrefix) {
      Services.obs.notifyObservers(
        null,
        `${this._testTopicPrefix}cleanup-logs`
      );
      ("cleanup-logs");
    }
  }
}
PK
!<�:`�00!modules/LoginAutoComplete.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * nsIAutoCompleteResult implementations for saved logins.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { GenericAutocompleteItem } from "resource://gre/modules/FillHelpers.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
});
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "formFillController",
  "@mozilla.org/satchel/form-fill-controller;1",
  Ci.nsIFormFillController
);
ChromeUtils.defineLazyGetter(lazy, "log", () => {
  return lazy.LoginHelper.createLogger("LoginAutoComplete");
});
ChromeUtils.defineLazyGetter(lazy, "passwordMgrBundle", () => {
  return Services.strings.createBundle(
    "chrome://passwordmgr/locale/passwordmgr.properties"
  );
});
ChromeUtils.defineLazyGetter(lazy, "dateAndTimeFormatter", () => {
  return new Services.intl.DateTimeFormat(undefined, {
    dateStyle: "medium",
  });
});

function loginSort(formHostPort, a, b) {
  let maybeHostPortA = lazy.LoginHelper.maybeGetHostPortForURL(a.origin);
  let maybeHostPortB = lazy.LoginHelper.maybeGetHostPortForURL(b.origin);
  if (formHostPort == maybeHostPortA && formHostPort != maybeHostPortB) {
    return -1;
  }
  if (formHostPort != maybeHostPortA && formHostPort == maybeHostPortB) {
    return 1;
  }

  if (a.httpRealm !== b.httpRealm) {
    // Sort HTTP auth. logins after form logins for the same origin.
    if (b.httpRealm === null) {
      return 1;
    }
    if (a.httpRealm === null) {
      return -1;
    }
  }

  let userA = a.username.toLowerCase();
  let userB = b.username.toLowerCase();

  if (userA < userB) {
    return -1;
  }

  if (userA > userB) {
    return 1;
  }

  return 0;
}

function findDuplicates(loginList) {
  let seen = new Set();
  let duplicates = new Set();
  for (let login of loginList) {
    if (seen.has(login.username)) {
      duplicates.add(login.username);
    }
    seen.add(login.username);
  }
  return duplicates;
}

function getLocalizedString(key, ...formatArgs) {
  if (formatArgs.length) {
    return lazy.passwordMgrBundle.formatStringFromName(key, formatArgs);
  }
  return lazy.passwordMgrBundle.GetStringFromName(key);
}

class AutocompleteItem {
  constructor(style) {
    this.comment = "";
    this.style = style;
    this.value = "";
  }

  removeFromStorage() {
    /* Do nothing by default */
  }
}

class InsecureLoginFormAutocompleteItem extends AutocompleteItem {
  constructor() {
    super("insecureWarning");

    this.label = getLocalizedString(
      "insecureFieldWarningDescription2",
      getLocalizedString("insecureFieldWarningLearnMore")
    );
  }
}

class LoginAutocompleteItem extends AutocompleteItem {
  login;
  #actor;

  constructor(
    login,
    hasBeenTypePassword,
    duplicateUsernames,
    actor,
    isOriginMatched
  ) {
    super("loginWithOrigin");
    this.login = login.QueryInterface(Ci.nsILoginMetaInfo);
    this.#actor = actor;

    const isDuplicateUsername =
      login.username && duplicateUsernames.has(login.username);

    let username = login.username
      ? login.username
      : getLocalizedString("noUsername");

    // If login is empty or duplicated we want to append a modification date to it.
    if (!login.username || isDuplicateUsername) {
      const time = lazy.dateAndTimeFormatter.format(
        new Date(login.timePasswordChanged)
      );
      username = getLocalizedString("loginHostAge", username, time);
    }

    this.label = username;
    this.value = hasBeenTypePassword ? login.password : login.username;
    this.comment = JSON.stringify({
      fillMessageName: "PasswordManager:OnFieldAutoComplete",
      fillMessageData: login,
      guid: login.guid,
      isDuplicateUsername,
      isOriginMatched,
      secondary:
        isOriginMatched && login.httpRealm === null
          ? getLocalizedString("displaySameOrigin")
          : login.displayOrigin,
    });
    this.image = `page-icon:${login.origin}`;
  }

  removeFromStorage() {
    if (this.#actor) {
      let vanilla = lazy.LoginHelper.loginToVanillaObject(this.login);
      this.#actor.sendAsyncMessage("PasswordManager:removeLogin", {
        login: vanilla,
      });
    } else {
      Services.logins.removeLogin(this.login);
    }
  }
}

class GeneratedPasswordAutocompleteItem extends AutocompleteItem {
  constructor(generatedPassword, willAutoSaveGeneratedPassword) {
    super("generatedPassword");

    this.label = getLocalizedString("useASecurelyGeneratedPassword");
    this.value = generatedPassword;
    this.comment = JSON.stringify({
      fillMessageName: "PasswordManager:FillGeneratedPassword",
      generatedPassword,
      willAutoSaveGeneratedPassword,
    });
    this.image = "chrome://browser/skin/login.svg";
  }
}

class ImportableLearnMoreAutocompleteItem extends AutocompleteItem {
  constructor() {
    super("importableLearnMore");
    this.comment = JSON.stringify({
      fillMessageName: "PasswordManager:OpenImportableLearnMore",
    });
  }
}

class ImportableLoginsAutocompleteItem extends AutocompleteItem {
  #actor;

  constructor(browserId, hostname, actor) {
    super("importableLogins");
    this.label = browserId;
    this.comment = JSON.stringify({
      hostname,
      fillMessageName: "PasswordManager:HandleImportable",
      fillMessageData: {
        browserId,
      },
    });
    this.#actor = actor;

    // This is sent for every item (re)shown, but the parent will debounce to
    // reduce the count by 1 total.
    this.#actor.sendAsyncMessage(
      "PasswordManager:decreaseSuggestImportCount",
      1
    );
  }

  removeFromStorage() {
    this.#actor.sendAsyncMessage(
      "PasswordManager:decreaseSuggestImportCount",
      100
    );
  }
}

class LoginsFooterAutocompleteItem extends AutocompleteItem {
  constructor(formHostname, telemetryEventData) {
    super("loginsFooter");

    this.label = getLocalizedString("managePasswords.label");

    // The comment field of `loginsFooter` results have many additional pieces of
    // information for telemetry purposes. After bug 1555209, this information
    // can be passed to the parent process outside of nsIAutoCompleteResult APIs
    // so we won't need this hack.
    this.comment = JSON.stringify({
      telemetryEventData,
      formHostname,
      fillMessageName: "PasswordManager:OpenPreferences",
      fillMessageData: {
        entryPoint: "autocomplete",
      },
    });
  }
}

// nsIAutoCompleteResult implementation
export class LoginAutoCompleteResult {
  #rows = [];

  constructor(
    aSearchString,
    matchingLogins,
    autocompleteItems,
    formOrigin,
    {
      generatedPassword,
      willAutoSaveGeneratedPassword,
      importable,
      isSecure,
      actor,
      hasBeenTypePassword,
      hostname,
      telemetryEventData,
    }
  ) {
    let hidingFooterOnPWFieldAutoOpened = false;
    const importableBrowsers =
      importable?.state === "import" && importable?.browsers;

    function isFooterEnabled() {
      // We need to check LoginHelper.enabled here since the insecure warning should
      // appear even if pwmgr is disabled but the footer should never appear in that case.
      if (
        !lazy.LoginHelper.showAutoCompleteFooter ||
        !lazy.LoginHelper.enabled
      ) {
        return false;
      }

      // Don't show the footer on non-empty password fields as it's not providing
      // value and only adding noise since a password was already filled.
      if (hasBeenTypePassword && aSearchString && !generatedPassword) {
        lazy.log.debug("Hiding footer: non-empty password field");
        return false;
      }

      if (
        !autocompleteItems?.length &&
        !importableBrowsers &&
        !matchingLogins.length &&
        !generatedPassword &&
        hasBeenTypePassword &&
        lazy.formFillController.passwordPopupAutomaticallyOpened
      ) {
        hidingFooterOnPWFieldAutoOpened = true;
        lazy.log.debug(
          "Hiding footer: no logins and the popup was opened upon focus of the pw. field"
        );
        return false;
      }

      return true;
    }

    this.searchString = aSearchString;

    // Insecure field warning comes first.
    if (!isSecure) {
      this.#rows.push(new InsecureLoginFormAutocompleteItem());
    }

    // Saved login items
    let formHostPort = lazy.LoginHelper.maybeGetHostPortForURL(formOrigin);
    let logins = matchingLogins.sort(loginSort.bind(null, formHostPort));
    let duplicateUsernames = findDuplicates(matchingLogins);

    for (let login of logins) {
      let item = new LoginAutocompleteItem(
        login,
        hasBeenTypePassword,
        duplicateUsernames,
        actor,
        lazy.LoginHelper.isOriginMatching(login.origin, formOrigin, {
          schemeUpgrades: lazy.LoginHelper.schemeUpgrades,
        })
      );
      this.#rows.push(item);
    }

    // The footer comes last if it's enabled
    if (isFooterEnabled()) {
      if (autocompleteItems) {
        this.#rows.push(
          ...autocompleteItems.map(
            item =>
              new GenericAutocompleteItem(
                item.image,
                item.label,
                item.secondary,
                item.fillMessageName,
                item.fillMessageData
              )
          )
        );
      }

      if (generatedPassword) {
        this.#rows.push(
          new GeneratedPasswordAutocompleteItem(
            generatedPassword,
            willAutoSaveGeneratedPassword
          )
        );
      }

      // Suggest importing logins if there are none found.
      if (!logins.length && importableBrowsers) {
        this.#rows.push(
          ...importableBrowsers.map(
            browserId =>
              new ImportableLoginsAutocompleteItem(browserId, hostname, actor)
          )
        );
        this.#rows.push(new ImportableLearnMoreAutocompleteItem());
      }

      // If we have anything in autocomplete, then add "Manage Passwords"
      this.#rows.push(
        new LoginsFooterAutocompleteItem(hostname, telemetryEventData)
      );
    }

    // Determine the result code and default index.
    if (this.matchCount > 0) {
      this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
      this.defaultIndex = 0;
    } else if (hidingFooterOnPWFieldAutoOpened) {
      // We use a failure result so that the empty results aren't re-used for when
      // the user tries to manually open the popup (we want the footer in that case).
      this.searchResult = Ci.nsIAutoCompleteResult.RESULT_FAILURE;
      this.defaultIndex = -1;
    }
  }

  QueryInterface = ChromeUtils.generateQI([
    "nsIAutoCompleteResult",
    "nsISupportsWeakReference",
  ]);

  /**
   * Accessed via .wrappedJSObject
   * @private
   */
  get logins() {
    return this.#rows
      .filter(item => item instanceof LoginAutocompleteItem)
      .map(item => item.login);
  }

  // Allow autoCompleteSearch to get at the JS object so it can
  // modify some readonly properties for internal use.
  get wrappedJSObject() {
    return this;
  }

  // Interfaces from idl...
  searchString = null;
  searchResult = Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
  defaultIndex = -1;
  errorDescription = "";

  get matchCount() {
    return this.#rows.length;
  }

  #throwOnBadIndex(index) {
    if (index < 0 || index >= this.matchCount) {
      throw new Error("Index out of range.");
    }
  }

  getValueAt(index) {
    this.#throwOnBadIndex(index);
    return this.#rows[index].value;
  }

  getLabelAt(index) {
    this.#throwOnBadIndex(index);
    return this.#rows[index].label;
  }

  getCommentAt(index) {
    this.#throwOnBadIndex(index);
    return this.#rows[index].comment;
  }

  getStyleAt(index) {
    this.#throwOnBadIndex(index);
    return this.#rows[index].style;
  }

  getImageAt(index) {
    this.#throwOnBadIndex(index);
    return this.#rows[index].image ?? "";
  }

  getFinalCompleteValueAt(index) {
    return this.getValueAt(index);
  }

  isRemovableAt(_index) {
    return false;
  }

  removeValueAt(_index) {
    return false;
  }
}
PK
!<��+ȗ�modules/LoginCSVImport.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Provides a class to import login-related data CSV files.
 */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  CSV: "resource://gre/modules/CSV.sys.mjs",
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
});

/**
 * All the CSV column names will be converted to lower case before lookup
 * so they must be specified here in lower case.
 */
const FIELD_TO_CSV_COLUMNS = {
  origin: ["url", "login_uri"],
  username: ["username", "login_username"],
  password: ["password", "login_password"],
  httpRealm: ["httprealm"],
  formActionOrigin: ["formactionorigin"],
  guid: ["guid"],
  timeCreated: ["timecreated"],
  timeLastUsed: ["timelastused"],
  timePasswordChanged: ["timepasswordchanged"],
};

export const ImportFailedErrorType = Object.freeze({
  CONFLICTING_VALUES_ERROR: "CONFLICTING_VALUES_ERROR",
  FILE_FORMAT_ERROR: "FILE_FORMAT_ERROR",
  FILE_PERMISSIONS_ERROR: "FILE_PERMISSIONS_ERROR",
  UNABLE_TO_READ_ERROR: "UNABLE_TO_READ_ERROR",
});

export class ImportFailedException extends Error {
  constructor(errorType, message) {
    super(message != null ? message : errorType);
    this.errorType = errorType;
  }
}

/**
 * Provides an object that has a method to import login-related data CSV files
 */
export class LoginCSVImport {
  /**
   * Returns a map that has the csv column name as key and the value the field name.
   *
   * @returns {Map} A map that has the csv column name as key and the value the field name.
   */
  static _getCSVColumnToFieldMap() {
    let csvColumnToField = new Map();
    for (let [field, columns] of Object.entries(FIELD_TO_CSV_COLUMNS)) {
      for (let column of columns) {
        csvColumnToField.set(column.toLowerCase(), field);
      }
    }
    return csvColumnToField;
  }

  /**
   * Builds a vanilla JS object containing all the login fields from a row of CSV cells.
   *
   * @param {object} csvObject
   *        An object created from a csv row. The keys are the csv column names, the values are the cells.
   * @param {Map} csvColumnToFieldMap
   *        A map where the keys are the csv properties and the values are the object keys.
   * @returns {object} Representing login object with only properties, not functions.
   */
  static _getVanillaLoginFromCSVObject(csvObject, csvColumnToFieldMap) {
    let vanillaLogin = Object.create(null);
    for (let columnName of Object.keys(csvObject)) {
      let fieldName = csvColumnToFieldMap.get(columnName.toLowerCase());
      if (!fieldName) {
        continue;
      }

      if (
        typeof vanillaLogin[fieldName] != "undefined" &&
        vanillaLogin[fieldName] !== csvObject[columnName]
      ) {
        // Differing column values map to one property.
        // e.g. if two headings map to `origin` we won't know which to use.
        return {};
      }

      vanillaLogin[fieldName] = csvObject[columnName];
    }

    // Since `null` can't be represented in a CSV file and the httpRealm header
    // cannot be an empty string, assume that an empty httpRealm means this is
    // a form login and therefore null-out httpRealm.
    if (vanillaLogin.httpRealm === "") {
      vanillaLogin.httpRealm = null;
    }

    return vanillaLogin;
  }
  static _recordHistogramTelemetry(histogram, report) {
    for (let reportRow of report) {
      let { result } = reportRow;
      if (result.includes("error")) {
        histogram.add("error");
      } else {
        histogram.add(result);
      }
    }
  }
  /**
   * Imports logins from a CSV file (comma-separated values file).
   * Existing logins may be updated in the process.
   *
   * @param {string} filePath
   * @returns {Object[]} An array of rows where each is mapped to a row in the CSV and it's import information.
   */
  static async importFromCSV(filePath) {
    let csvColumnToFieldMap = LoginCSVImport._getCSVColumnToFieldMap();
    let csvFieldToColumnMap = new Map();

    let csvString;
    try {
      csvString = await IOUtils.readUTF8(filePath, { encoding: "utf-8" });
    } catch (ex) {
      console.error(ex);
      throw new ImportFailedException(
        ImportFailedErrorType.FILE_PERMISSIONS_ERROR
      );
    }
    let headerLine;
    let parsedLines;
    try {
      let delimiter = filePath.toUpperCase().endsWith(".CSV") ? "," : "\t";
      [headerLine, parsedLines] = lazy.CSV.parse(csvString, delimiter);
    } catch {
      throw new ImportFailedException(ImportFailedErrorType.FILE_FORMAT_ERROR);
    }
    if (parsedLines && headerLine) {
      for (const columnName of headerLine) {
        const fieldName = csvColumnToFieldMap.get(
          columnName.toLocaleLowerCase()
        );
        if (fieldName) {
          if (!csvFieldToColumnMap.has(fieldName)) {
            csvFieldToColumnMap.set(fieldName, columnName);
          } else {
            throw new ImportFailedException(
              ImportFailedErrorType.CONFLICTING_VALUES_ERROR
            );
          }
        }
      }
    }
    if (csvFieldToColumnMap.size === 0) {
      throw new ImportFailedException(ImportFailedErrorType.FILE_FORMAT_ERROR);
    }
    if (
      parsedLines[0] &&
      (!csvFieldToColumnMap.has("origin") ||
        !csvFieldToColumnMap.has("username") ||
        !csvFieldToColumnMap.has("password"))
    ) {
      // The username *value* can be empty but we require a username column to
      // ensure that we don't import logins without their usernames due to the
      // username column not being recognized.
      throw new ImportFailedException(ImportFailedErrorType.FILE_FORMAT_ERROR);
    }

    let loginsToImport = parsedLines.map(csvObject => {
      return LoginCSVImport._getVanillaLoginFromCSVObject(
        csvObject,
        csvColumnToFieldMap
      );
    });

    let report = await lazy.LoginHelper.maybeImportLogins(loginsToImport);

    for (const reportRow of report) {
      if (reportRow.result === "error_missing_field") {
        reportRow.field_name = csvFieldToColumnMap.get(reportRow.field_name);
      }
    }

    // Record quantity and duration telemetry.
    try {
      let histogram = Services.telemetry.getHistogramById(
        "PWMGR_IMPORT_LOGINS_FROM_FILE_CATEGORICAL"
      );
      this._recordHistogramTelemetry(histogram, report);
    } catch (ex) {
      console.error(ex);
    }
    LoginCSVImport.lastImportReport = report;
    return report;
  }
}
PK
!<o�rNNmodules/LoginExport.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Module to support exporting logins to a .csv file.
 */

export class LoginExport {
  /**
   * Builds an array of strings representing a row in a CSV.
   *
   * @param {nsILoginInfo} login
   *        The object that will be converted into a csv row.
   * @param {string[]} columns
   *        The CSV columns, used to find the properties from the login object.
   * @returns {string[]} Representing a row.
   */
  static _buildCSVRow(login, columns) {
    let row = [];
    for (let columnName of columns) {
      let columnValue = login[columnName];
      if (typeof columnValue == "string") {
        columnValue = columnValue.split('"').join('""');
      }
      if (columnValue !== null && columnValue != undefined) {
        row.push(`"${columnValue}"`);
      } else {
        row.push("");
      }
    }
    return row;
  }

  /**
   * Given a path it saves all the logins as a CSV file.
   *
   * @param {string} path
   *        The file path to save the login to.
   * @param {nsILoginInfo[]} [logins = null]
   *        An optional list of logins.
   */
  static async exportAsCSV(path, logins = null) {
    if (!logins) {
      logins = await Services.logins.getAllLogins();
    }
    let columns = [
      "origin",
      "username",
      "password",
      "httpRealm",
      "formActionOrigin",
      "guid",
      "timeCreated",
      "timeLastUsed",
      "timePasswordChanged",
    ];
    let csvHeader = columns.map(name => {
      if (name == "origin") {
        return '"url"';
      }
      return `"${name}"`;
    });

    let rows = [];
    rows.push(csvHeader);
    for (let login of logins) {
      rows.push(LoginExport._buildCSVRow(login, columns));
    }
    // https://tools.ietf.org/html/rfc7111 suggests always using CRLF.
    const csvAsString = rows.map(e => e.join(",")).join("\r\n");
    await IOUtils.writeUTF8(path, csvAsString, {
      tmpPath: path + ".tmp",
    });
  }
}
PK
!<^�-S�S�modules/LoginHelper.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Contains functions shared by different Login Manager components.
 *
 * This JavaScript module exists in order to share code between the different
 * XPCOM components that constitute the Login Manager, including implementations
 * of nsILoginManager and nsILoginManagerStorage.
 */

import { Logic } from "resource://gre/modules/LoginManager.shared.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  OSKeyStore: "resource://gre/modules/OSKeyStore.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "Crypto",
  "@mozilla.org/login-manager/crypto/SDR;1",
  "nsILoginManagerCrypto"
);

export class ParentAutocompleteOption {
  image;
  label;
  secondary;
  fillMessageName;
  fillMessageData;

  constructor(image, label, secondary, fillMessageName, fillMessageData) {
    this.image = image;
    this.label = label;
    this.secondary = secondary;
    this.fillMessageName = fillMessageName;
    this.fillMessageData = fillMessageData;
  }
}

/**
 * A helper class to deal with CSV import rows.
 */
class ImportRowProcessor {
  uniqueLoginIdentifiers = new Set();
  originToRows = new Map();
  summary = [];
  mandatoryFields = ["origin", "password"];

  /**
   * Validates if the login data contains a GUID that was already found in a previous row in the current import.
   * If this is the case, the summary will be updated with an error.
   * @param {object} loginData
   *        An vanilla object for the login without any methods.
   * @returns {boolean} True if there is an error, false otherwise.
   */
  checkNonUniqueGuidError(loginData) {
    if (loginData.guid) {
      if (this.uniqueLoginIdentifiers.has(loginData.guid)) {
        this.addLoginToSummary({ ...loginData }, "error");
        return true;
      }
      this.uniqueLoginIdentifiers.add(loginData.guid);
    }
    return false;
  }

  /**
   * Validates if the login data contains invalid fields that are mandatory like origin and password.
   * If this is the case, the summary will be updated with an error.
   * @param {object} loginData
   *        An vanilla object for the login without any methods.
   * @returns {boolean} True if there is an error, false otherwise.
   */
  checkMissingMandatoryFieldsError(loginData) {
    loginData.origin = LoginHelper.getLoginOrigin(loginData.origin);
    for (let mandatoryField of this.mandatoryFields) {
      if (!loginData[mandatoryField]) {
        const missingFieldRow = this.addLoginToSummary(
          { ...loginData },
          "error_missing_field"
        );
        missingFieldRow.field_name = mandatoryField;
        return true;
      }
    }
    return false;
  }

  /**
   * Validates if there is already an existing entry with similar values.
   * If there are similar values but not identical, a new "modified" entry will be added to the summary.
   * If there are identical values, a new "no_change" entry will be added to the summary
   * If either of these is the case, it will return true.
   * @param {object} loginData
   *        An vanilla object for the login without any methods.
   * @returns {boolean} True if the entry is similar or identical to another previously processed entry, false otherwise.
   */
  async checkExistingEntry(loginData) {
    if (loginData.guid) {
      // First check for `guid` matches if it's set.
      // `guid` matches will allow every kind of update, including reverting
      // to older passwords which can be useful if the user wants to recover
      // an old password.
      let existingLogins = await Services.logins.searchLoginsAsync({
        guid: loginData.guid,
        origin: loginData.origin, // Ignored outside of GV.
      });

      if (existingLogins.length) {
        lazy.log.debug("maybeImportLogins: Found existing login with GUID.");
        // There should only be one `guid` match.
        let existingLogin = existingLogins[0].QueryInterface(
          Ci.nsILoginMetaInfo
        );

        if (
          loginData.username !== existingLogin.username ||
          loginData.password !== existingLogin.password ||
          loginData.httpRealm !== existingLogin.httpRealm ||
          loginData.formActionOrigin !== existingLogin.formActionOrigin ||
          `${loginData.timeCreated}` !== `${existingLogin.timeCreated}` ||
          `${loginData.timePasswordChanged}` !==
            `${existingLogin.timePasswordChanged}`
        ) {
          // Use a property bag rather than an nsILoginInfo so we don't clobber
          // properties that the import source doesn't provide.
          let propBag = LoginHelper.newPropertyBag(loginData);
          this.addLoginToSummary({ ...existingLogin }, "modified", propBag);
          return true;
        }
        this.addLoginToSummary({ ...existingLogin }, "no_change");
        return true;
      }
    }
    return false;
  }

  /**
   * Validates if there is a conflict with previous rows based on the origin.
   * We need to check the logins that we've already decided to add, to see if this is a duplicate.
   * If this is the case, we mark this one as "no_change" in the summary and return true.
   * @param {object} login
   *        A login object.
   * @returns {boolean} True if the entry is similar or identical to another previously processed entry, false otherwise.
   */
  checkConflictingOriginWithPreviousRows(login) {
    let rowsPerOrigin = this.originToRows.get(login.origin);
    if (rowsPerOrigin) {
      if (
        rowsPerOrigin.some(r =>
          login.matches(r.login, false /* ignorePassword */)
        )
      ) {
        this.addLoginToSummary(login, "no_change");
        return true;
      }
      for (let row of rowsPerOrigin) {
        let newLogin = row.login;
        if (login.username == newLogin.username) {
          this.addLoginToSummary(login, "no_change");
          return true;
        }
      }
    }
    return false;
  }

  /**
   * Validates if there is a conflict with existing logins based on the origin.
   * If this is the case and there are some changes, we mark it as "modified" in the summary.
   * If it matches an existing login without any extra modifications, we mark it as "no_change".
   * For both cases we return true.
   * @param {object} login
   *        A login object.
   * @returns {boolean} True if the entry is similar or identical to another previously processed entry, false otherwise.
   */
  async checkConflictingWithExistingLogins(login) {
    // While here we're passing formActionOrigin and httpRealm, they could be empty/null and get
    // ignored in that case, leading to multiple logins for the same username.
    let existingLogins = await Services.logins.searchLoginsAsync({
      origin: login.origin,
      formActionOrigin: login.formActionOrigin,
      httpRealm: login.httpRealm,
    });

    // Check for an existing login that matches *including* the password.
    // If such a login exists, we do not need to add a new login.
    if (
      existingLogins.some(l => login.matches(l, false /* ignorePassword */))
    ) {
      this.addLoginToSummary(login, "no_change");
      return true;
    }
    // Now check for a login with the same username, where it may be that we have an
    // updated password.
    let foundMatchingLogin = false;
    for (let existingLogin of existingLogins) {
      if (login.username == existingLogin.username) {
        foundMatchingLogin = true;
        existingLogin.QueryInterface(Ci.nsILoginMetaInfo);
        if (
          (login.password != existingLogin.password) &
          (login.timePasswordChanged > existingLogin.timePasswordChanged)
        ) {
          // if a login with the same username and different password already exists and it's older
          // than the current one, update its password and timestamp.
          let propBag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
            Ci.nsIWritablePropertyBag
          );
          propBag.setProperty("password", login.password);
          propBag.setProperty("timePasswordChanged", login.timePasswordChanged);
          this.addLoginToSummary({ ...existingLogin }, "modified", propBag);
          return true;
        }
      }
    }
    // if the new login is an update or is older than an exiting login, don't add it.
    if (foundMatchingLogin) {
      this.addLoginToSummary(login, "no_change");
      return true;
    }
    return false;
  }

  /**
   * Validates if there are any invalid values using LoginHelper.checkLoginValues.
   * If this is the case we mark it as "error" and return true.
   * @param {object} login
   *        A login object.
   * @param {object} loginData
   *        An vanilla object for the login without any methods.
   * @returns {boolean} True if there is a validation error we return true, false otherwise.
   */
  checkLoginValuesError(login, loginData) {
    try {
      // Ensure we only send checked logins through, since the validation is optimized
      // out from the bulk APIs below us.
      LoginHelper.checkLoginValues(login);
    } catch (e) {
      this.addLoginToSummary({ ...loginData }, "error");
      console.error(e);
      return true;
    }
    return false;
  }

  /**
   * Creates a new login from loginData.
   * @param {object} loginData
   *        An vanilla object for the login without any methods.
   * @returns {object} A login object.
   */
  createNewLogin(loginData) {
    let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(
      Ci.nsILoginInfo
    );
    login.init(
      loginData.origin,
      loginData.formActionOrigin,
      loginData.httpRealm,
      loginData.username,
      loginData.password,
      loginData.usernameElement || "",
      loginData.passwordElement || ""
    );

    login.QueryInterface(Ci.nsILoginMetaInfo);
    login.timeCreated = loginData.timeCreated;
    login.timeLastUsed = loginData.timeLastUsed || loginData.timeCreated;
    login.timePasswordChanged =
      loginData.timePasswordChanged || loginData.timeCreated;
    login.timesUsed = loginData.timesUsed || 1;
    login.guid = loginData.guid || null;
    return login;
  }

  /**
   * Cleans the action and realm field of the loginData.
   * @param {object} loginData
   *        An vanilla object for the login without any methods.
   */
  cleanupActionAndRealmFields(loginData) {
    const cleanOrigin = loginData.formActionOrigin
      ? LoginHelper.getLoginOrigin(loginData.formActionOrigin, true)
      : "";
    loginData.formActionOrigin =
      cleanOrigin || (typeof loginData.httpRealm == "string" ? null : "");

    loginData.httpRealm =
      typeof loginData.httpRealm == "string" ? loginData.httpRealm : null;
  }

  /**
   * Adds a login to the summary.
   * @param {object} login
   *        A login object.
   * @param {string} result
   *        The result type. One of "added", "modified", "error", "error_invalid_origin", "error_invalid_password" or "no_change".
   * @param {object} propBag
   *        An optional parameter with the properties bag.
   * @returns {object} The row that was added.
   */
  addLoginToSummary(login, result, propBag) {
    let rows = this.originToRows.get(login.origin) || [];
    if (rows.length === 0) {
      this.originToRows.set(login.origin, rows);
    }
    const newSummaryRow = { result, login, propBag };
    rows.push(newSummaryRow);
    this.summary.push(newSummaryRow);
    return newSummaryRow;
  }

  /**
   * Iterates over all then rows where more than two match the same origin. It mutates the internal state of the processor.
   * It makes sure that if the `timePasswordChanged` field is present it will be used to decide if it's a "no_change" or "added".
   * The entry with the oldest `timePasswordChanged` will be "added", the rest will be "no_change".
   */
  markLastTimePasswordChangedAsModified() {
    const originUserToRowMap = new Map();
    for (let currentRow of this.summary) {
      if (
        currentRow.result === "added" ||
        currentRow.result === "modified" ||
        currentRow.result === "no_change"
      ) {
        const originAndUser =
          currentRow.login.origin + currentRow.login.username;
        let lastTimeChangedRow = originUserToRowMap.get(originAndUser);
        if (lastTimeChangedRow) {
          if (
            (currentRow.login.password != lastTimeChangedRow.login.password) &
            (currentRow.login.timePasswordChanged >
              lastTimeChangedRow.login.timePasswordChanged)
          ) {
            lastTimeChangedRow.result = "no_change";
            currentRow.result = "added";
            originUserToRowMap.set(originAndUser, currentRow);
          }
        } else {
          originUserToRowMap.set(originAndUser, currentRow);
        }
      }
    }
  }

  /**
   * Iterates over all then rows where more than two match the same origin. It mutates the internal state of the processor.
   * It makes sure that if the `timePasswordChanged` field is present it will be used to decide if it's a "no_change" or "added".
   * The entry with the oldest `timePasswordChanged` will be "added", the rest will be "no_change".
   * @returns {Object[]} An entry for each processed row containing how the row was processed and the login data.
   */
  async processLoginsAndBuildSummary() {
    this.markLastTimePasswordChangedAsModified();
    for (let summaryRow of this.summary) {
      try {
        if (summaryRow.result === "added") {
          summaryRow.login = await Services.logins.addLoginAsync(
            summaryRow.login
          );
        } else if (summaryRow.result === "modified") {
          Services.logins.modifyLogin(summaryRow.login, summaryRow.propBag);
        }
      } catch (e) {
        console.error(e);
        summaryRow.result = "error";
      }
    }
    return this.summary;
  }
}
const OS_AUTH_FOR_PASSWORDS_PREF = "signon.management.page.os-auth.optout";
/**
 * Contains functions shared by different Login Manager components.
 */
export const LoginHelper = {
  debug: null,
  enabled: null,
  storageEnabled: null,
  formlessCaptureEnabled: null,
  formRemovalCaptureEnabled: null,
  generationAvailable: null,
  generationConfidenceThreshold: null,
  generationEnabled: null,
  improvedPasswordRulesEnabled: null,
  improvedPasswordRulesCollection: "password-rules",
  includeOtherSubdomainsInLookup: null,
  insecureAutofill: null,
  privateBrowsingCaptureEnabled: null,
  remoteRecipesEnabled: null,
  remoteRecipesCollection: "password-recipes",
  relatedRealmsEnabled: null,
  relatedRealmsCollection: "websites-with-shared-credential-backends",
  schemeUpgrades: null,
  showAutoCompleteFooter: null,
  showAutoCompleteImport: null,
  testOnlyUserHasInteractedWithDocument: null,
  userInputRequiredToCapture: null,
  captureInputChanges: null,
  OS_AUTH_FOR_PASSWORDS_PREF,

  init() {
    // Watch for pref changes to update cached pref values.
    Services.prefs.addObserver("signon.", () => this.updateSignonPrefs());
    this.updateSignonPrefs();
    Services.telemetry.setEventRecordingEnabled("pwmgr", true);
    Services.telemetry.setEventRecordingEnabled("form_autocomplete", true);

    // Watch for FXA Logout to reset signon.firefoxRelay to 'available'
    // Using hard-coded value for FxAccountsCommon.ONLOGOUT_NOTIFICATION because
    // importing FxAccountsCommon here caused hard-to-diagnose crash.
    Services.obs.addObserver(() => {
      Services.prefs.clearUserPref("signon.firefoxRelay.feature");
    }, "fxaccounts:onlogout");
  },

  updateSignonPrefs() {
    this.autofillForms = Services.prefs.getBoolPref("signon.autofillForms");
    this.autofillAutocompleteOff = Services.prefs.getBoolPref(
      "signon.autofillForms.autocompleteOff"
    );
    this.captureInputChanges = Services.prefs.getBoolPref(
      "signon.capture.inputChanges.enabled"
    );
    this.debug = Services.prefs.getBoolPref("signon.debug");
    this.enabled = Services.prefs.getBoolPref("signon.rememberSignons");
    this.storageEnabled = Services.prefs.getBoolPref(
      "signon.storeSignons",
      true
    );
    this.formlessCaptureEnabled = Services.prefs.getBoolPref(
      "signon.formlessCapture.enabled"
    );
    this.formRemovalCaptureEnabled = Services.prefs.getBoolPref(
      "signon.formRemovalCapture.enabled"
    );
    this.generationAvailable = Services.prefs.getBoolPref(
      "signon.generation.available"
    );
    this.generationConfidenceThreshold = parseFloat(
      Services.prefs.getStringPref("signon.generation.confidenceThreshold")
    );
    this.generationEnabled = Services.prefs.getBoolPref(
      "signon.generation.enabled"
    );
    this.improvedPasswordRulesEnabled = Services.prefs.getBoolPref(
      "signon.improvedPasswordRules.enabled"
    );
    this.insecureAutofill = Services.prefs.getBoolPref(
      "signon.autofillForms.http"
    );
    this.includeOtherSubdomainsInLookup = Services.prefs.getBoolPref(
      "signon.includeOtherSubdomainsInLookup"
    );
    this.passwordEditCaptureEnabled = Services.prefs.getBoolPref(
      "signon.passwordEditCapture.enabled"
    );
    this.privateBrowsingCaptureEnabled = Services.prefs.getBoolPref(
      "signon.privateBrowsingCapture.enabled"
    );
    this.schemeUpgrades = Services.prefs.getBoolPref("signon.schemeUpgrades");
    this.showAutoCompleteFooter = Services.prefs.getBoolPref(
      "signon.showAutoCompleteFooter"
    );

    this.showAutoCompleteImport = Services.prefs.getStringPref(
      "signon.showAutoCompleteImport",
      ""
    );

    this.storeWhenAutocompleteOff = Services.prefs.getBoolPref(
      "signon.storeWhenAutocompleteOff"
    );

    this.suggestImportCount = Services.prefs.getIntPref(
      "signon.suggestImportCount",
      0
    );

    if (
      Services.prefs.getBoolPref(
        "signon.testOnlyUserHasInteractedByPrefValue",
        false
      )
    ) {
      this.testOnlyUserHasInteractedWithDocument = Services.prefs.getBoolPref(
        "signon.testOnlyUserHasInteractedWithDocument",
        false
      );
      lazy.log.debug(
        `Using pref value for testOnlyUserHasInteractedWithDocument ${this.testOnlyUserHasInteractedWithDocument}.`
      );
    } else {
      this.testOnlyUserHasInteractedWithDocument = null;
    }

    this.userInputRequiredToCapture = Services.prefs.getBoolPref(
      "signon.userInputRequiredToCapture.enabled"
    );
    this.usernameOnlyFormEnabled = Services.prefs.getBoolPref(
      "signon.usernameOnlyForm.enabled"
    );
    this.usernameOnlyFormLookupThreshold = Services.prefs.getIntPref(
      "signon.usernameOnlyForm.lookupThreshold"
    );
    this.remoteRecipesEnabled = Services.prefs.getBoolPref(
      "signon.recipes.remoteRecipes.enabled"
    );
    this.relatedRealmsEnabled = Services.prefs.getBoolPref(
      "signon.relatedRealms.enabled"
    );
  },

  createLogger(aLogPrefix) {
    let getMaxLogLevel = () => {
      return this.debug ? "Debug" : "Warn";
    };

    // Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
    let consoleOptions = {
      maxLogLevel: getMaxLogLevel(),
      prefix: aLogPrefix,
    };
    let logger = console.createInstance(consoleOptions);

    // Watch for pref changes and update this.debug and the maxLogLevel for created loggers
    Services.prefs.addObserver("signon.debug", () => {
      this.debug = Services.prefs.getBoolPref("signon.debug");
      if (logger) {
        logger.maxLogLevel = getMaxLogLevel();
      }
    });

    return logger;
  },

  /**
   * Due to the way the signons2.txt file is formatted, we need to make
   * sure certain field values or characters do not cause the file to
   * be parsed incorrectly.  Reject origins that we can't store correctly.
   *
   * @throws String with English message in case validation failed.
   */
  checkOriginValue(aOrigin) {
    // Nulls are invalid, as they don't round-trip well.  Newlines are also
    // invalid for any field stored as plaintext, and an origin made of a
    // single dot cannot be stored in the legacy format.
    if (
      aOrigin == "." ||
      aOrigin.includes("\r") ||
      aOrigin.includes("\n") ||
      aOrigin.includes("\0")
    ) {
      throw new Error("Invalid origin");
    }
  },

  /**
   * Due to the way the signons2.txt file was formatted, we needed to make
   * sure certain field values or characters do not cause the file to
   * be parsed incorrectly. These characters can cause problems in other
   * formats/languages too so reject logins that may not be stored correctly.
   *
   * @throws String with English message in case validation failed.
   */
  checkLoginValues(aLogin) {
    function badCharacterPresent(l, c) {
      return (
        (l.formActionOrigin && l.formActionOrigin.includes(c)) ||
        (l.httpRealm && l.httpRealm.includes(c)) ||
        l.origin.includes(c) ||
        l.usernameField.includes(c) ||
        l.passwordField.includes(c)
      );
    }

    // Nulls are invalid, as they don't round-trip well.
    // Mostly not a formatting problem, although ".\0" can be quirky.
    if (badCharacterPresent(aLogin, "\0")) {
      throw new Error("login values can't contain nulls");
    }

    if (!aLogin.password || typeof aLogin.password != "string") {
      throw new Error("passwords must be non-empty strings");
    }

    // In theory these nulls should just be rolled up into the encrypted
    // values, but nsISecretDecoderRing doesn't use nsStrings, so the
    // nulls cause truncation. Check for them here just to avoid
    // unexpected round-trip surprises.
    if (aLogin.username.includes("\0") || aLogin.password.includes("\0")) {
      throw new Error("login values can't contain nulls");
    }

    // Newlines are invalid for any field stored as plaintext.
    if (
      badCharacterPresent(aLogin, "\r") ||
      badCharacterPresent(aLogin, "\n")
    ) {
      throw new Error("login values can't contain newlines");
    }

    // A line with just a "." can have special meaning.
    if (aLogin.usernameField == "." || aLogin.formActionOrigin == ".") {
      throw new Error("login values can't be periods");
    }

    // An origin with "\ \(" won't roundtrip.
    // eg host="foo (", realm="bar" --> "foo ( (bar)"
    // vs host="foo", realm=" (bar" --> "foo ( (bar)"
    if (aLogin.origin.includes(" (")) {
      throw new Error("bad parens in origin");
    }
  },

  /**
   * Returns a new XPCOM property bag with the provided properties.
   *
   * @param {Object} aProperties
   *        Each property of this object is copied to the property bag.  This
   *        parameter can be omitted to return an empty property bag.
   *
   * @return A new property bag, that is an instance of nsIWritablePropertyBag,
   *         nsIWritablePropertyBag2, nsIPropertyBag, and nsIPropertyBag2.
   */
  newPropertyBag(aProperties) {
    let propertyBag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
      Ci.nsIWritablePropertyBag
    );
    if (aProperties) {
      for (let [name, value] of Object.entries(aProperties)) {
        propertyBag.setProperty(name, value);
      }
    }
    return propertyBag
      .QueryInterface(Ci.nsIPropertyBag)
      .QueryInterface(Ci.nsIPropertyBag2)
      .QueryInterface(Ci.nsIWritablePropertyBag2);
  },

  /**
   * Helper to avoid the property bags when calling
   * Services.logins.searchLogins from JS.
   * @deprecated Use Services.logins.searchLoginsAsync instead.
   *
   * @param {Object} aSearchOptions - A regular JS object to copy to a property bag before searching
   * @return {nsILoginInfo[]} - The result of calling searchLogins.
   */
  searchLoginsWithObject(aSearchOptions) {
    return Services.logins.searchLogins(this.newPropertyBag(aSearchOptions));
  },

  /**
   * @param {string} aURL
   * @returns {string} which is the hostPort of aURL if supported by the scheme
   *                   otherwise, returns the original aURL.
   */
  maybeGetHostPortForURL(aURL) {
    try {
      let uri = Services.io.newURI(aURL);
      return uri.hostPort;
    } catch (ex) {
      // No need to warn for javascript:/data:/about:/chrome:/etc.
    }
    return aURL;
  },

  /**
   * Get the parts of the URL we want for identification.
   * Strip out things like the userPass portion and handle javascript:.
   */
  getLoginOrigin(uriString, allowJS = false) {
    return Logic.getLoginOrigin(uriString, allowJS);
  },

  getFormActionOrigin(form) {
    return Logic.getFormActionOrigin(form);
  },

  /**
   * @param {String} aLoginOrigin - An origin value from a stored login's
   *                                origin or formActionOrigin properties.
   * @param {String} aSearchOrigin - The origin that was are looking to match
   *                                 with aLoginOrigin. This would normally come
   *                                 from a form or page that we are considering.
   * @param {nsILoginFindOptions} aOptions - Options to affect whether the origin
   *                                         from the login (aLoginOrigin) is a
   *                                         match for the origin we're looking
   *                                         for (aSearchOrigin).
   */
  isOriginMatching(
    aLoginOrigin,
    aSearchOrigin,
    aOptions = {
      schemeUpgrades: false,
      acceptWildcardMatch: false,
      acceptDifferentSubdomains: false,
      acceptRelatedRealms: false,
      relatedRealms: [],
    }
  ) {
    if (aLoginOrigin == aSearchOrigin) {
      return true;
    }

    if (!aOptions) {
      return false;
    }

    if (aOptions.acceptWildcardMatch && aLoginOrigin == "") {
      return true;
    }

    // We can only match logins now if either of these flags are true, so
    // avoid doing the work of constructing URL objects if neither is true.
    if (!aOptions.acceptDifferentSubdomains && !aOptions.schemeUpgrades) {
      return false;
    }

    try {
      let loginURI = Services.io.newURI(aLoginOrigin);
      let searchURI = Services.io.newURI(aSearchOrigin);
      let schemeMatches =
        loginURI.scheme == "http" && searchURI.scheme == "https";

      if (aOptions.acceptDifferentSubdomains) {
        let loginBaseDomain = Services.eTLD.getBaseDomain(loginURI);
        let searchBaseDomain = Services.eTLD.getBaseDomain(searchURI);
        if (
          loginBaseDomain == searchBaseDomain &&
          (loginURI.scheme == searchURI.scheme ||
            (aOptions.schemeUpgrades && schemeMatches))
        ) {
          return true;
        }
        if (
          aOptions.acceptRelatedRealms &&
          aOptions.relatedRealms.length &&
          (loginURI.scheme == searchURI.scheme ||
            (aOptions.schemeUpgrades && schemeMatches))
        ) {
          for (let relatedOrigin of aOptions.relatedRealms) {
            if (Services.eTLD.hasRootDomain(loginURI.host, relatedOrigin)) {
              return true;
            }
          }
        }
      }

      if (
        aOptions.schemeUpgrades &&
        loginURI.host == searchURI.host &&
        schemeMatches &&
        loginURI.port == searchURI.port
      ) {
        return true;
      }
    } catch (ex) {
      // newURI will throw for some values e.g. chrome://FirefoxAccounts
      // uri.host and uri.port will throw for some values e.g. javascript:
      return false;
    }

    return false;
  },

  doLoginsMatch(
    aLogin1,
    aLogin2,
    { ignorePassword = false, ignoreSchemes = false }
  ) {
    if (
      aLogin1.httpRealm != aLogin2.httpRealm ||
      aLogin1.username != aLogin2.username
    ) {
      return false;
    }

    if (!ignorePassword && aLogin1.password != aLogin2.password) {
      return false;
    }

    if (ignoreSchemes) {
      let login1HostPort = this.maybeGetHostPortForURL(aLogin1.origin);
      let login2HostPort = this.maybeGetHostPortForURL(aLogin2.origin);
      if (login1HostPort != login2HostPort) {
        return false;
      }

      if (
        aLogin1.formActionOrigin != "" &&
        aLogin2.formActionOrigin != "" &&
        this.maybeGetHostPortForURL(aLogin1.formActionOrigin) !=
          this.maybeGetHostPortForURL(aLogin2.formActionOrigin)
      ) {
        return false;
      }
    } else {
      if (aLogin1.origin != aLogin2.origin) {
        return false;
      }

      // If either formActionOrigin is blank (but not null), then match.
      if (
        aLogin1.formActionOrigin != "" &&
        aLogin2.formActionOrigin != "" &&
        aLogin1.formActionOrigin != aLogin2.formActionOrigin
      ) {
        return false;
      }
    }

    // The .usernameField and .passwordField values are ignored.

    return true;
  },

  /**
   * Creates a new login object that results by modifying the given object with
   * the provided data.
   *
   * @param {nsILoginInfo} aOldStoredLogin
   *        Existing login object to modify.
   * @param {nsILoginInfo|nsIProperyBag} aNewLoginData
   *        The new login values, either as an nsILoginInfo or nsIProperyBag.
   *
   * @return {nsILoginInfo} The newly created nsILoginInfo object.
   *
   * @throws {Error} With English message in case validation failed.
   */
  buildModifiedLogin(aOldStoredLogin, aNewLoginData) {
    function bagHasProperty(aPropName) {
      try {
        aNewLoginData.getProperty(aPropName);
        return true;
      } catch (ex) {}
      return false;
    }

    aOldStoredLogin.QueryInterface(Ci.nsILoginMetaInfo);

    let newLogin;
    if (aNewLoginData instanceof Ci.nsILoginInfo) {
      // Clone the existing login to get its nsILoginMetaInfo, then init it
      // with the replacement nsILoginInfo data from the new login.
      newLogin = aOldStoredLogin.clone();
      newLogin.init(
        aNewLoginData.origin,
        aNewLoginData.formActionOrigin,
        aNewLoginData.httpRealm,
        aNewLoginData.username,
        aNewLoginData.password,
        aNewLoginData.usernameField,
        aNewLoginData.passwordField
      );
      newLogin.unknownFields = aNewLoginData.unknownFields;
      newLogin.QueryInterface(Ci.nsILoginMetaInfo);

      // Automatically update metainfo when password is changed.
      if (newLogin.password != aOldStoredLogin.password) {
        newLogin.timePasswordChanged = Date.now();
      }
    } else if (aNewLoginData instanceof Ci.nsIPropertyBag) {
      // Clone the existing login, along with all its properties.
      newLogin = aOldStoredLogin.clone();
      newLogin.QueryInterface(Ci.nsILoginMetaInfo);

      // Automatically update metainfo when password is changed.
      // (Done before the main property updates, lest the caller be
      // explicitly updating both .password and .timePasswordChanged)
      if (bagHasProperty("password")) {
        let newPassword = aNewLoginData.getProperty("password");
        if (newPassword != aOldStoredLogin.password) {
          newLogin.timePasswordChanged = Date.now();
        }
      }

      for (let prop of aNewLoginData.enumerator) {
        switch (prop.name) {
          // nsILoginInfo (fall through)
          case "origin":
          case "httpRealm":
          case "formActionOrigin":
          case "username":
          case "password":
          case "usernameField":
          case "passwordField":
          case "unknownFields":
          // nsILoginMetaInfo (fall through)
          case "guid":
          case "timeCreated":
          case "timeLastUsed":
          case "timePasswordChanged":
          case "timesUsed":
            newLogin[prop.name] = prop.value;
            break;

          // Fake property, allows easy incrementing.
          case "timesUsedIncrement":
            newLogin.timesUsed += prop.value;
            break;

          // Fail if caller requests setting an unknown property.
          default:
            throw new Error("Unexpected propertybag item: " + prop.name);
        }
      }
    } else {
      throw new Error("newLoginData needs an expected interface!");
    }

    // Sanity check the login
    if (newLogin.origin == null || !newLogin.origin.length) {
      throw new Error("Can't add a login with a null or empty origin.");
    }

    // For logins w/o a username, set to "", not null.
    if (newLogin.username == null) {
      throw new Error("Can't add a login with a null username.");
    }

    if (newLogin.password == null || !newLogin.password.length) {
      throw new Error("Can't add a login with a null or empty password.");
    }

    if (newLogin.formActionOrigin || newLogin.formActionOrigin == "") {
      // We have a form submit URL. Can't have a HTTP realm.
      if (newLogin.httpRealm != null) {
        throw new Error(
          "Can't add a login with both a httpRealm and formActionOrigin."
        );
      }
    } else if (newLogin.httpRealm || newLogin.httpRealm == "") {
      // We have a HTTP realm. Can't have a form submit URL.
      if (newLogin.formActionOrigin != null) {
        throw new Error(
          "Can't add a login with both a httpRealm and formActionOrigin."
        );
      }
    } else {
      // Need one or the other!
      throw new Error(
        "Can't add a login without a httpRealm or formActionOrigin."
      );
    }

    // Throws if there are bogus values.
    this.checkLoginValues(newLogin);

    return newLogin;
  },

  /**
   * Remove http: logins when there is an https: login with the same username and hostPort.
   * Sort order is preserved.
   *
   * @param {nsILoginInfo[]} logins
   *        A list of logins we want to process for shadowing.
   * @returns {nsILoginInfo[]} A subset of of the passed logins.
   */
  shadowHTTPLogins(logins) {
    /**
     * Map a (hostPort, username) to a boolean indicating whether `logins`
     * contains an https: login for that combo.
     */
    let hasHTTPSByHostPortUsername = new Map();
    for (let login of logins) {
      let key = this.getUniqueKeyForLogin(login, ["hostPort", "username"]);
      let hasHTTPSlogin = hasHTTPSByHostPortUsername.get(key) || false;
      let loginURI = Services.io.newURI(login.origin);
      hasHTTPSByHostPortUsername.set(
        key,
        loginURI.scheme == "https" || hasHTTPSlogin
      );
    }

    return logins.filter(login => {
      let key = this.getUniqueKeyForLogin(login, ["hostPort", "username"]);
      let loginURI = Services.io.newURI(login.origin);
      if (loginURI.scheme == "http" && hasHTTPSByHostPortUsername.get(key)) {
        // If this is an http: login and we have an https: login for the
        // (hostPort, username) combo then remove it.
        return false;
      }
      return true;
    });
  },

  /**
   * Generate a unique key string from a login.
   * @param {nsILoginInfo} login
   * @param {string[]} uniqueKeys containing nsILoginInfo attribute names or "hostPort"
   * @returns {string} to use as a key in a Map
   */
  getUniqueKeyForLogin(login, uniqueKeys) {
    const KEY_DELIMITER = ":";
    return uniqueKeys.reduce((prev, key) => {
      let val = null;
      if (key == "hostPort") {
        val = Services.io.newURI(login.origin).hostPort;
      } else {
        val = login[key];
      }

      return prev + KEY_DELIMITER + val;
    }, "");
  },

  /**
   * Removes duplicates from a list of logins while preserving the sort order.
   *
   * @param {nsILoginInfo[]} logins
   *        A list of logins we want to deduplicate.
   * @param {string[]} [uniqueKeys = ["username", "password"]]
   *        A list of login attributes to use as unique keys for the deduplication.
   * @param {string[]} [resolveBy = ["timeLastUsed"]]
   *        Ordered array of keyword strings used to decide which of the
   *        duplicates should be used. "scheme" would prefer the login that has
   *        a scheme matching `preferredOrigin`'s if there are two logins with
   *        the same `uniqueKeys`. The default preference to distinguish two
   *        logins is `timeLastUsed`. If there is no preference between two
   *        logins, the first one found wins.
   * @param {string} [preferredOrigin = undefined]
   *        String representing the origin to use for preferring one login over
   *        another when they are dupes. This is used with "scheme" for
   *        `resolveBy` so the scheme from this origin will be preferred.
   * @param {string} [preferredFormActionOrigin = undefined]
   *        String representing the action origin to use for preferring one login over
   *        another when they are dupes. This is used with "actionOrigin" for
   *        `resolveBy` so the scheme from this action origin will be preferred.
   *
   * @returns {nsILoginInfo[]} list of unique logins.
   */
  dedupeLogins(
    logins,
    uniqueKeys = ["username", "password"],
    resolveBy = ["timeLastUsed"],
    preferredOrigin = undefined,
    preferredFormActionOrigin = undefined
  ) {
    if (!preferredOrigin) {
      if (resolveBy.includes("scheme")) {
        throw new Error(
          "dedupeLogins: `preferredOrigin` is required in order to " +
            "prefer schemes which match it."
        );
      }
      if (resolveBy.includes("subdomain")) {
        throw new Error(
          "dedupeLogins: `preferredOrigin` is required in order to " +
            "prefer subdomains which match it."
        );
      }
    }

    let preferredOriginScheme;
    if (preferredOrigin) {
      try {
        preferredOriginScheme = Services.io.newURI(preferredOrigin).scheme;
      } catch (ex) {
        // Handle strings that aren't valid URIs e.g. chrome://FirefoxAccounts
      }
    }

    if (!preferredOriginScheme && resolveBy.includes("scheme")) {
      lazy.log.warn(
        "Deduping with a scheme preference but couldn't get the preferred origin scheme."
      );
    }

    // We use a Map to easily lookup logins by their unique keys.
    let loginsByKeys = new Map();

    /**
     * @return {bool} whether `login` is preferred over its duplicate (considering `uniqueKeys`)
     *                `existingLogin`.
     *
     * `resolveBy` is a sorted array so we can return true the first time `login` is preferred
     * over the existingLogin.
     */
    function isLoginPreferred(existingLogin, login) {
      if (!resolveBy || !resolveBy.length) {
        // If there is no preference, prefer the existing login.
        return false;
      }

      for (let preference of resolveBy) {
        switch (preference) {
          case "actionOrigin": {
            if (!preferredFormActionOrigin) {
              break;
            }
            if (
              LoginHelper.isOriginMatching(
                existingLogin.formActionOrigin,
                preferredFormActionOrigin,
                { schemeUpgrades: LoginHelper.schemeUpgrades }
              ) &&
              !LoginHelper.isOriginMatching(
                login.formActionOrigin,
                preferredFormActionOrigin,
                { schemeUpgrades: LoginHelper.schemeUpgrades }
              )
            ) {
              return false;
            }
            break;
          }
          case "scheme": {
            if (!preferredOriginScheme) {
              break;
            }

            try {
              // Only `origin` is currently considered
              let existingLoginURI = Services.io.newURI(existingLogin.origin);
              let loginURI = Services.io.newURI(login.origin);
              // If the schemes of the two logins are the same or neither match the
              // preferredOriginScheme then we have no preference and look at the next resolveBy.
              if (
                loginURI.scheme == existingLoginURI.scheme ||
                (loginURI.scheme != preferredOriginScheme &&
                  existingLoginURI.scheme != preferredOriginScheme)
              ) {
                break;
              }

              return loginURI.scheme == preferredOriginScheme;
            } catch (e) {
              // Some URLs aren't valid nsIURI (e.g. chrome://FirefoxAccounts)
              lazy.log.debug(
                "dedupeLogins/shouldReplaceExisting: Error comparing schemes:",
                existingLogin.origin,
                login.origin,
                "preferredOrigin:",
                preferredOrigin,
                e.name
              );
            }
            break;
          }
          case "subdomain": {
            // Replace the existing login only if the new login is an exact match on the host.
            let existingLoginURI = Services.io.newURI(existingLogin.origin);
            let newLoginURI = Services.io.newURI(login.origin);
            let preferredOriginURI = Services.io.newURI(preferredOrigin);
            if (
              existingLoginURI.hostPort != preferredOriginURI.hostPort &&
              newLoginURI.hostPort == preferredOriginURI.hostPort
            ) {
              return true;
            }
            if (
              existingLoginURI.host != preferredOriginURI.host &&
              newLoginURI.host == preferredOriginURI.host
            ) {
              return true;
            }
            // if the existing login host *is* a match and the new one isn't
            // we explicitly want to keep the existing one
            if (
              existingLoginURI.host == preferredOriginURI.host &&
              newLoginURI.host != preferredOriginURI.host
            ) {
              return false;
            }
            break;
          }
          case "timeLastUsed":
          case "timePasswordChanged": {
            // If we find a more recent login for the same key, replace the existing one.
            let loginDate = login.QueryInterface(Ci.nsILoginMetaInfo)[
              preference
            ];
            let storedLoginDate = existingLogin.QueryInterface(
              Ci.nsILoginMetaInfo
            )[preference];
            if (loginDate == storedLoginDate) {
              break;
            }

            return loginDate > storedLoginDate;
          }
          default: {
            throw new Error(
              "dedupeLogins: Invalid resolveBy preference: " + preference
            );
          }
        }
      }

      return false;
    }

    for (let login of logins) {
      let key = this.getUniqueKeyForLogin(login, uniqueKeys);

      if (loginsByKeys.has(key)) {
        if (!isLoginPreferred(loginsByKeys.get(key), login)) {
          // If there is no preference for the new login, use the existing one.
          continue;
        }
      }
      loginsByKeys.set(key, login);
    }

    // Return the map values in the form of an array.
    return [...loginsByKeys.values()];
  },

  /**
   * Open the password manager window.
   *
   * @param {Window} window
   *                 the window from where we want to open the dialog
   *
   * @param {object?} args
   *                  params for opening the password manager
   * @param {string} [args.filterString=""]
   *                 the domain (not origin) to pass to the login manager dialog
   *                 to pre-filter the results
   * @param {string} args.entryPoint
   *                 The name of the entry point, used for telemetry
   */
  openPasswordManager(
    window,
    { filterString = "", entryPoint = "", loginGuid = null } = {}
  ) {
    // Get currently active tab's origin
    const openedFrom =
      window.gBrowser?.selectedTab.linkedBrowser.currentURI.spec;
    // If no loginGuid is set, get sanitized origin, this will return null for about:* uris
    const preselectedLogin = loginGuid ?? this.getLoginOrigin(openedFrom);

    const params = new URLSearchParams({
      ...(filterString && { filter: filterString }),
      ...(entryPoint && { entryPoint }),
    });

    const paramsPart = params.toString() ? `?${params}` : "";

    const browser = window.gBrowser ?? window.opener?.gBrowser;

    const tab = browser.addTrustedTab(`about:logins${paramsPart}`, {
      inBackground: false,
    });

    tab.setAttribute("preselect-login", preselectedLogin);
  },

  /**
   * Checks if a field type is password compatible.
   *
   * @param {Element} element
   *                  the field we want to check.
   * @param {Object} options
   * @param {bool} [options.ignoreConnect] - Whether to ignore checking isConnected
   *                                         of the element.
   *
   * @returns {Boolean} true if the field can
   *                    be treated as a password input
   */
  isPasswordFieldType(element, { ignoreConnect = false } = {}) {
    return Logic.isPasswordFieldType(element, { ignoreConnect });
  },

  /**
   * Checks if a field type is username compatible.
   *
   * @param {Element} element
   *                  the field we want to check.
   * @param {Object} options
   * @param {bool} [options.ignoreConnect] - Whether to ignore checking isConnected
   *                                         of the element.
   *
   * @returns {Boolean} true if the field type is one
   *                    of the username types.
   */
  isUsernameFieldType(element, { ignoreConnect = false } = {}) {
    return Logic.isUsernameFieldType(element, { ignoreConnect });
  },

  /**
   * Infer whether a form is a sign-in form by searching keywords
   * in its attributes
   *
   * @param {Element} element
   *                  the form we want to check.
   *
   * @returns {boolean} True if any of the rules matches
   */
  isInferredLoginForm(formElement) {
    // This is copied from 'loginFormAttrRegex' in NewPasswordModel.sys.mjs
    const loginExpr =
      /login|log in|log on|log-on|sign in|sigin|sign\/in|sign-in|sign on|sign-on/i;

    if (Logic.elementAttrsMatchRegex(formElement, loginExpr)) {
      return true;
    }

    return false;
  },

  /**
   * Infer whether an input field is a username field by searching
   * 'username' keyword in its attributes
   *
   * @param {Element} element
   *                  the field we want to check.
   *
   * @returns {boolean} True if any of the rules matches
   */
  isInferredUsernameField(element) {
    const expr = /username/i;

    let ac = element.getAutocompleteInfo()?.fieldName;
    if (ac && ac == "username") {
      return true;
    }

    if (
      Logic.elementAttrsMatchRegex(element, expr) ||
      Logic.hasLabelMatchingRegex(element, expr)
    ) {
      return true;
    }

    return false;
  },

  /**
   * Search for keywords that indicates the input field is not likely a
   * field of a username login form.
   *
   * @param {Element} element
   *                  the input field we want to check.
   *
   * @returns {boolean} True if any of the rules matches
   */
  isInferredNonUsernameField(element) {
    const expr = /search|code|add/i;

    if (
      Logic.elementAttrsMatchRegex(element, expr) ||
      Logic.hasLabelMatchingRegex(element, expr)
    ) {
      return true;
    }

    return false;
  },

  /**
   * Infer whether an input field is an email field by searching
   * 'email' keyword in its attributes.
   *
   * @param {Element} element
   *                  the field we want to check.
   *
   * @returns {boolean} True if any of the rules matches
   */
  isInferredEmailField(element) {
    const expr = /email|邮箱/i;

    if (element.type == "email") {
      return true;
    }

    let ac = element.getAutocompleteInfo()?.fieldName;
    if (ac && ac == "email") {
      return true;
    }

    if (
      Logic.elementAttrsMatchRegex(element, expr) ||
      Logic.hasLabelMatchingRegex(element, expr)
    ) {
      return true;
    }

    return false;
  },

  /**
   * For each login, add the login to the password manager if a similar one
   * doesn't already exist. Merge it otherwise with the similar existing ones.
   *
   * @param {Object[]} loginDatas - For each login, the data that needs to be added.
   * @returns {Object[]} An entry for each processed row containing how the row was processed and the login data.
   */
  async maybeImportLogins(loginDatas) {
    this.importing = true;
    try {
      const processor = new ImportRowProcessor();
      for (let rawLoginData of loginDatas) {
        // Do some sanitization on a clone of the loginData.
        let loginData = ChromeUtils.shallowClone(rawLoginData);
        if (processor.checkNonUniqueGuidError(loginData)) {
          continue;
        }
        if (processor.checkMissingMandatoryFieldsError(loginData)) {
          continue;
        }
        processor.cleanupActionAndRealmFields(loginData);
        if (await processor.checkExistingEntry(loginData)) {
          continue;
        }
        let login = processor.createNewLogin(loginData);
        if (processor.checkLoginValuesError(login, loginData)) {
          continue;
        }
        if (processor.checkConflictingOriginWithPreviousRows(login)) {
          continue;
        }
        if (await processor.checkConflictingWithExistingLogins(login)) {
          continue;
        }
        processor.addLoginToSummary(login, "added");
      }
      return await processor.processLoginsAndBuildSummary();
    } finally {
      this.importing = false;

      Services.obs.notifyObservers(null, "passwordmgr-reload-all");
      this.notifyStorageChanged("importLogins", []);
    }
  },

  /**
   * Convert an array of nsILoginInfo to vanilla JS objects suitable for
   * sending over IPC. Avoid using this in other cases.
   *
   * NB: All members of nsILoginInfo (not nsILoginMetaInfo) are strings.
   */
  loginsToVanillaObjects(logins) {
    return logins.map(this.loginToVanillaObject);
  },

  /**
   * Same as above, but for a single login.
   */
  loginToVanillaObject(login) {
    let obj = {};
    for (let i in login.QueryInterface(Ci.nsILoginMetaInfo)) {
      if (typeof login[i] !== "function") {
        obj[i] = login[i];
      }
    }
    return obj;
  },

  /**
   * Convert an object received from IPC into an nsILoginInfo (with guid).
   */
  vanillaObjectToLogin(login) {
    let formLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(
      Ci.nsILoginInfo
    );
    formLogin.init(
      login.origin,
      login.formActionOrigin,
      login.httpRealm,
      login.username,
      login.password,
      login.usernameField,
      login.passwordField
    );

    formLogin.QueryInterface(Ci.nsILoginMetaInfo);
    for (let prop of [
      "guid",
      "timeCreated",
      "timeLastUsed",
      "timePasswordChanged",
      "timesUsed",
    ]) {
      formLogin[prop] = login[prop];
    }
    return formLogin;
  },

  /**
   * As above, but for an array of objects.
   */
  vanillaObjectsToLogins(vanillaObjects) {
    const logins = [];
    for (const vanillaObject of vanillaObjects) {
      logins.push(this.vanillaObjectToLogin(vanillaObject));
    }
    return logins;
  },

  /**
   * Returns true if the user has a primary password set and false otherwise.
   */
  isPrimaryPasswordSet() {
    let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"].getService(
      Ci.nsIPK11TokenDB
    );
    let token = tokenDB.getInternalKeyToken();
    return token.hasPassword;
  },

  /**
   * Get the decrypted value for a string pref.
   *
   * @param {string} prefName -> The pref whose value is needed.
   * @param {string} safeDefaultValue -> Value to be returned incase the pref is not yet set.
   * @returns {string}
   */
  getSecurePref(prefName, safeDefaultValue) {
    if (Services.prefs.getBoolPref("security.nocertdb", false)) {
      return false;
    }
    try {
      const encryptedValue = Services.prefs.getStringPref(prefName, "");
      return encryptedValue === ""
        ? safeDefaultValue
        : lazy.Crypto.decrypt(encryptedValue);
    } catch {
      return safeDefaultValue;
    }
  },

  /**
   * Set the pref to the encrypted form of the value.
   *
   * @param {string} prefName -> The pref whose value is to be set.
   * @param {string} value -> The value to be set in its encrypted form.
   */
  setSecurePref(prefName, value) {
    if (Services.prefs.getBoolPref("security.nocertdb", false)) {
      return;
    }
    if (value) {
      const encryptedValue = lazy.Crypto.encrypt(value);
      Services.prefs.setStringPref(prefName, encryptedValue);
    } else {
      Services.prefs.clearUserPref(prefName);
    }
  },

  /**
   * Get whether the OSAuth is enabled or not.
   *
   * @param {string} prefName -> The name of the pref (creditcards or addresses)
   * @returns {boolean}
   */
  getOSAuthEnabled(prefName) {
    return (
      lazy.OSKeyStore.canReauth() &&
      this.getSecurePref(prefName, "") !== "opt out"
    );
  },

  /**
   * Set whether the OSAuth is enabled or not.
   *
   * @param {string} prefName -> The pref to encrypt.
   * @param {boolean} enable -> Whether the pref is to be enabled.
   */
  setOSAuthEnabled(prefName, enable) {
    this.setSecurePref(prefName, enable ? null : "opt out");
  },

  async verifyUserOSAuth(
    prefName,
    promptMessage,
    captionDialog = "",
    parentWindow = null,
    generateKeyIfNotAvailable = true
  ) {
    if (!this.getOSAuthEnabled(prefName)) {
      promptMessage = false;
    }
    try {
      return (
        await lazy.OSKeyStore.ensureLoggedIn(
          promptMessage,
          captionDialog,
          parentWindow,
          generateKeyIfNotAvailable
        )
      ).authenticated;
    } catch (ex) {
      // Since Win throws an exception whereas Mac resolves to false upon cancelling.
      if (ex.result !== Cr.NS_ERROR_FAILURE) {
        throw ex;
      }
    }
    return false;
  },

  /**
   * Shows the Primary Password prompt if enabled, or the
   * OS auth dialog otherwise.
   * @param {Element} browser
   *        The <browser> that the prompt should be shown on
   * @param OSReauthEnabled Boolean indicating if OS reauth should be tried
   * @param expirationTime Optional timestamp indicating next required re-authentication
   * @param messageText Formatted and localized string to be displayed when the OS auth dialog is used.
   * @param captionText Formatted and localized string to be displayed when the OS auth dialog is used.
   */
  async requestReauth(
    browser,
    OSReauthEnabled,
    expirationTime,
    messageText,
    captionText
  ) {
    let isAuthorized = false;
    let telemetryEvent;

    // This does no harm if primary password isn't set.
    let tokendb = Cc["@mozilla.org/security/pk11tokendb;1"].createInstance(
      Ci.nsIPK11TokenDB
    );
    let token = tokendb.getInternalKeyToken();

    // Do we have a recent authorization?
    if (expirationTime && Date.now() < expirationTime) {
      isAuthorized = true;
      telemetryEvent = {
        object: token.hasPassword ? "master_password" : "os_auth",
        method: "reauthenticate",
        value: "success_no_prompt",
      };
      return {
        isAuthorized,
        telemetryEvent,
      };
    }

    // Default to true if there is no primary password and OS reauth is not available
    if (!token.hasPassword && !OSReauthEnabled) {
      isAuthorized = true;
      telemetryEvent = {
        object: "os_auth",
        method: "reauthenticate",
        value: "success_disabled",
      };
      return {
        isAuthorized,
        telemetryEvent,
      };
    }
    // Use the OS auth dialog if there is no primary password
    if (!token.hasPassword && OSReauthEnabled) {
      let isAuthorized = await this.verifyUserOSAuth(
        OS_AUTH_FOR_PASSWORDS_PREF,
        messageText,
        captionText,
        browser.ownerGlobal,
        false
      );
      let value = lazy.OSKeyStore.canReauth()
        ? "success"
        : "success_unsupported_platform";

      telemetryEvent = {
        object: "os_auth",
        method: "reauthenticate",
        value: isAuthorized ? value : "fail",
      };
      return {
        isAuthorized,
        telemetryEvent,
      };
    }
    // We'll attempt to re-auth via Primary Password, force a log-out
    token.checkPassword("");

    // If a primary password prompt is already open, just exit early and return false.
    // The user can re-trigger it after responding to the already open dialog.
    if (Services.logins.uiBusy) {
      isAuthorized = false;
      return {
        isAuthorized,
        telemetryEvent,
      };
    }

    // So there's a primary password. But since checkPassword didn't succeed, we're logged out (per nsIPK11Token.idl).
    try {
      // Relogin and ask for the primary password.
      token.login(true); // 'true' means always prompt for token password. User will be prompted until
      // clicking 'Cancel' or entering the correct password.
    } catch (e) {
      // An exception will be thrown if the user cancels the login prompt dialog.
      // User is also logged out of Software Security Device.
    }
    isAuthorized = token.isLoggedIn();
    telemetryEvent = {
      object: "master_password",
      method: "reauthenticate",
      value: isAuthorized ? "success" : "fail",
    };
    return {
      isAuthorized,
      telemetryEvent,
    };
  },

  /**
   * Send a notification when stored data is changed.
   */
  notifyStorageChanged(changeType, data) {
    if (this.importing) {
      return;
    }

    let dataObject = data;
    // Can't pass a raw JS string or array though notifyObservers(). :-(
    if (Array.isArray(data)) {
      dataObject = Cc["@mozilla.org/array;1"].createInstance(
        Ci.nsIMutableArray
      );
      for (let i = 0; i < data.length; i++) {
        dataObject.appendElement(data[i]);
      }
    } else if (typeof data == "string") {
      dataObject = Cc["@mozilla.org/supports-string;1"].createInstance(
        Ci.nsISupportsString
      );
      dataObject.data = data;
    }
    Services.obs.notifyObservers(
      dataObject,
      "passwordmgr-storage-changed",
      changeType
    );
  },

  isUserFacingLogin(login) {
    return login.origin != "chrome://FirefoxAccounts"; // FXA_PWDMGR_HOST
  },

  async getAllUserFacingLogins() {
    try {
      let logins = await Services.logins.getAllLogins();
      return logins.filter(this.isUserFacingLogin);
    } catch (e) {
      if (e.result == Cr.NS_ERROR_ABORT) {
        // If the user cancels the MP prompt then return no logins.
        return [];
      }
      throw e;
    }
  },

  createLoginAlreadyExistsError(guid) {
    // The GUID is stored in an nsISupportsString here because we cannot pass
    // raw JS objects within Components.Exception due to bug 743121.
    let guidSupportsString = Cc[
      "@mozilla.org/supports-string;1"
    ].createInstance(Ci.nsISupportsString);
    guidSupportsString.data = guid;
    return Components.Exception("This login already exists.", {
      data: guidSupportsString,
    });
  },

  /**
   * Determine the <browser> that a prompt should be shown on.
   *
   * Some sites pop up a temporary login window, which disappears
   * upon submission of credentials. We want to put the notification
   * prompt in the opener window if this seems to be happening.
   *
   * @param {Element} browser
   *        The <browser> that a prompt was triggered for
   * @returns {Element} The <browser> that the prompt should be shown on,
   *                    which could be in a different window.
   */
  getBrowserForPrompt(browser) {
    let chromeWindow = browser.ownerGlobal;
    let openerBrowsingContext = browser.browsingContext.opener;
    let openerBrowser = openerBrowsingContext
      ? openerBrowsingContext.top.embedderElement
      : null;
    if (openerBrowser) {
      let chromeDoc = chromeWindow.document.documentElement;

      // Check to see if the current window was opened with chrome
      // disabled, and if so use the opener window. But if the window
      // has been used to visit other pages (ie, has a history),
      // assume it'll stick around and *don't* use the opener.
      if (chromeDoc.getAttribute("chromehidden") && !browser.canGoBack) {
        lazy.log.debug("Using opener window for prompt.");
        return openerBrowser;
      }
    }

    return browser;
  },
};

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  let processName =
    Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT
      ? "Main"
      : "Content";
  return LoginHelper.createLogger(`LoginHelper(${processName})`);
});

LoginHelper.init();

export class OptInFeature {
  implementation;
  #offered;
  #enabled;
  #disabled;
  #pref;

  static PREF_AVAILABLE_VALUE = "available";
  static PREF_OFFERED_VALUE = "offered";
  static PREF_ENABLED_VALUE = "enabled";
  static PREF_DISABLED_VALUE = "disabled";

  constructor(offered, enabled, disabled, pref) {
    this.#pref = pref;
    this.#offered = offered;
    this.#enabled = enabled;
    this.#disabled = disabled;

    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "implementationPref",
      pref,
      undefined,
      (_preference, _prevValue, _newValue) => this.#updateImplementation()
    );

    this.#updateImplementation();
  }

  get #currentPrefValue() {
    // Read pref directly instead of relying on this.implementationPref because
    // there is an implementationPref value update lag that affects tests.
    return Services.prefs.getStringPref(this.#pref, undefined);
  }

  get isAvailable() {
    return [
      OptInFeature.PREF_AVAILABLE_VALUE,
      OptInFeature.PREF_OFFERED_VALUE,
      OptInFeature.PREF_ENABLED_VALUE,
      OptInFeature.PREF_DISABLED_VALUE,
    ].includes(this.#currentPrefValue);
  }

  get isEnabled() {
    return this.#currentPrefValue == OptInFeature.PREF_ENABLED_VALUE;
  }

  get isDisabled() {
    return this.#currentPrefValue == OptInFeature.PREF_DISABLED_VALUE;
  }

  markAsAvailable() {
    this.#markAs(OptInFeature.PREF_AVAILABLE_VALUE);
  }

  markAsOffered() {
    this.#markAs(OptInFeature.PREF_OFFERED_VALUE);
  }

  markAsEnabled() {
    this.#markAs(OptInFeature.PREF_ENABLED_VALUE);
  }

  markAsDisabled() {
    this.#markAs(OptInFeature.PREF_DISABLED_VALUE);
  }

  #markAs(value) {
    Services.prefs.setStringPref(this.#pref, value);
  }

  #updateImplementation() {
    switch (this.implementationPref) {
      case OptInFeature.PREF_ENABLED_VALUE:
        this.implementation = new this.#enabled();
        break;
      case OptInFeature.PREF_AVAILABLE_VALUE:
      case OptInFeature.PREF_OFFERED_VALUE:
        this.implementation = new this.#offered();
        break;
      case OptInFeature.PREF_DISABLED_VALUE:
      default:
        this.implementation = new this.#disabled();
        break;
    }
  }
}
PK
!<D.7�`
`
modules/LoginInfo.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
});

export function nsLoginInfo() {}

nsLoginInfo.prototype = {
  classID: Components.ID("{0f2f347c-1e4f-40cc-8efd-792dea70a85e}"),
  QueryInterface: ChromeUtils.generateQI(["nsILoginInfo", "nsILoginMetaInfo"]),

  //
  // nsILoginInfo interfaces...
  //

  origin: null,
  formActionOrigin: null,
  httpRealm: null,
  username: null,
  password: null,
  usernameField: null,
  passwordField: null,
  unknownFields: null,

  everSynced: false,
  syncCounter: 0,

  get displayOrigin() {
    let displayOrigin = this.origin;
    try {
      let uri = Services.io.newURI(this.origin);
      // Fallback to handle file: URIs
      displayOrigin = uri.displayHostPort || this.origin;
    } catch (ex) {
      // Fallback to this.origin set above in case a URI can't be contructed e.g.
      // file://
    }

    if (this.httpRealm === null) {
      return displayOrigin;
    }

    return `${displayOrigin} (${this.httpRealm})`;
  },

  /**
   * @deprecated Use `origin` instead.
   */
  get hostname() {
    return this.origin;
  },

  /**
   * @deprecated Use `formActionOrigin` instead.
   */
  get formSubmitURL() {
    return this.formActionOrigin;
  },

  init(
    aOrigin,
    aFormActionOrigin,
    aHttpRealm,
    aUsername,
    aPassword,
    aUsernameField = "",
    aPasswordField = ""
  ) {
    this.origin = aOrigin;
    this.formActionOrigin = aFormActionOrigin;
    this.httpRealm = aHttpRealm;
    this.username = aUsername;
    this.password = aPassword;
    this.usernameField = aUsernameField || "";
    this.passwordField = aPasswordField || "";
  },

  matches(aLogin, ignorePassword) {
    return lazy.LoginHelper.doLoginsMatch(this, aLogin, {
      ignorePassword,
    });
  },

  equals(aLogin) {
    if (
      this.origin != aLogin.origin ||
      this.formActionOrigin != aLogin.formActionOrigin ||
      this.httpRealm != aLogin.httpRealm ||
      this.username != aLogin.username ||
      this.password != aLogin.password ||
      this.usernameField != aLogin.usernameField ||
      this.passwordField != aLogin.passwordField
    ) {
      return false;
    }

    return true;
  },

  clone() {
    let clone = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(
      Ci.nsILoginInfo
    );
    clone.init(
      this.origin,
      this.formActionOrigin,
      this.httpRealm,
      this.username,
      this.password,
      this.usernameField,
      this.passwordField
    );

    // Copy nsILoginMetaInfo props
    clone.QueryInterface(Ci.nsILoginMetaInfo);
    clone.guid = this.guid;
    clone.timeCreated = this.timeCreated;
    clone.timeLastUsed = this.timeLastUsed;
    clone.timePasswordChanged = this.timePasswordChanged;
    clone.timesUsed = this.timesUsed;
    clone.syncCounter = this.syncCounter;
    clone.everSynced = this.everSynced;

    // Unknown fields from other clients
    clone.unknownFields = this.unknownFields;

    return clone;
  },

  //
  // nsILoginMetaInfo interfaces...
  //

  guid: null,
  timeCreated: null,
  timeLastUsed: null,
  timePasswordChanged: null,
  timesUsed: null,
}; // end of nsLoginInfo implementation
PK
!<���`modules/LoginManager.shared.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Code that we can share across Firefox Desktop, Firefox Android and Firefox iOS.
 */

class Logic {
  static inputTypeIsCompatibleWithUsername(input) {
    const fieldType = input.getAttribute("type")?.toLowerCase() || input.type;
    return (
      ["text", "email", "url", "tel", "number", "search"].includes(fieldType) ||
      fieldType?.includes("user")
    );
  }

  /**
   * Test whether the element has the keyword in its attributes.
   * The tested attributes include id, name, className, and placeholder.
   */
  static elementAttrsMatchRegex(element, regex) {
    if (
      regex.test(element.id) ||
      regex.test(element.name) ||
      regex.test(element.className)
    ) {
      return true;
    }

    const placeholder = element.getAttribute("placeholder");
    return placeholder && regex.test(placeholder);
  }

  /**
   * Test whether associated labels of the element have the keyword.
   * This is a simplified rule of hasLabelMatchingRegex in NewPasswordModel.sys.mjs
   */
  static hasLabelMatchingRegex(element, regex) {
    return regex.test(element.labels?.[0]?.textContent);
  }

  /**
   * Get the parts of the URL we want for identification.
   * Strip out things like the userPass portion and handle javascript:.
   */
  static getLoginOrigin(uriString, allowJS = false) {
    try {
      const mozProxyRegex = /^moz-proxy:\/\//i;
      if (mozProxyRegex.test(uriString)) {
        // Special handling for moz-proxy URIs
        const uri = new URL(uriString.replace(mozProxyRegex, "https://"));
        return `moz-proxy://${uri.host}`;
      }

      const uri = new URL(uriString);
      if (uri.protocol === "javascript:") {
        return allowJS ? "javascript:" : null;
      }

      // Ensure the URL has a host
      // Execption: file URIs See Bug 1651186
      return uri.host || uri.protocol === "file:"
        ? `${uri.protocol}//${uri.host}`
        : null;
    } catch {
      return null;
    }
  }

  static getFormActionOrigin(form) {
    let uriString = form.action;

    // A blank or missing action submits to where it came from.
    if (uriString == "") {
      // ala bug 297761
      uriString = form.baseURI;
    }

    return this.getLoginOrigin(uriString, true);
  }

  /**
   * Checks if a field type is username compatible.
   *
   * @param {Element} element
   *                  the field we want to check.
   * @param {Object} options
   * @param {bool} [options.ignoreConnect] - Whether to ignore checking isConnected
   *                                         of the element.
   *
   * @returns {Boolean} true if the field type is one
   *                    of the username types.
   */
  static isUsernameFieldType(element, { ignoreConnect = false } = {}) {
    if (!HTMLInputElement.isInstance(element)) {
      return false;
    }

    if (!element.isConnected && !ignoreConnect) {
      // If the element isn't connected then it isn't visible to the user so
      // shouldn't be considered. It must have been connected in the past.
      return false;
    }

    if (element.hasBeenTypePassword) {
      return false;
    }

    if (!Logic.inputTypeIsCompatibleWithUsername(element)) {
      return false;
    }

    let acFieldName = element.getAutocompleteInfo().fieldName;
    if (
      !(
        acFieldName == "username" ||
        acFieldName == "webauthn" ||
        // Bug 1540154: Some sites use tel/email on their username fields.
        acFieldName == "email" ||
        acFieldName == "tel" ||
        acFieldName == "tel-national" ||
        acFieldName == "off" ||
        acFieldName == "on" ||
        acFieldName == ""
      )
    ) {
      return false;
    }
    return true;
  }

  /**
   * Checks if a field type is password compatible.
   *
   * @param {Element} element
   *                  the field we want to check.
   * @param {Object} options
   * @param {bool} [options.ignoreConnect] - Whether to ignore checking isConnected
   *                                         of the element.
   *
   * @returns {Boolean} true if the field can
   *                    be treated as a password input
   */
  static isPasswordFieldType(element, { ignoreConnect = false } = {}) {
    if (!HTMLInputElement.isInstance(element)) {
      return false;
    }

    if (!element.isConnected && !ignoreConnect) {
      // If the element isn't connected then it isn't visible to the user so
      // shouldn't be considered. It must have been connected in the past.
      return false;
    }

    if (!element.hasBeenTypePassword) {
      return false;
    }

    // Ensure the element is of a type that could have autocomplete.
    // These include the types with user-editable values. If not, even if it used to be
    // a type=password, we can't treat it as a password input now
    let acInfo = element.getAutocompleteInfo();
    if (!acInfo) {
      return false;
    }

    return true;
  }
}

export { Logic };
PK
!<���O�O�(modules/LoginManagerAuthPrompter.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { PrivateBrowsingUtils } from "resource://gre/modules/PrivateBrowsingUtils.sys.mjs";
import { PromptUtils } from "resource://gre/modules/PromptUtils.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gPrompterService",
  "@mozilla.org/login-manager/prompter;1",
  Ci.nsILoginManagerPrompter
);

/* eslint-disable block-scoped-var, no-var */

ChromeUtils.defineESModuleGetters(lazy, {
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
});

const LoginInfo = Components.Constructor(
  "@mozilla.org/login-manager/loginInfo;1",
  "nsILoginInfo",
  "init"
);

/**
 * A helper module to prevent modal auth prompt abuse.
 */
const PromptAbuseHelper = {
  getBaseDomainOrFallback(hostname) {
    try {
      return Services.eTLD.getBaseDomainFromHost(hostname);
    } catch (e) {
      return hostname;
    }
  },

  incrementPromptAbuseCounter(baseDomain, browser) {
    if (!browser) {
      return;
    }

    if (!browser.authPromptAbuseCounter) {
      browser.authPromptAbuseCounter = {};
    }

    if (!browser.authPromptAbuseCounter[baseDomain]) {
      browser.authPromptAbuseCounter[baseDomain] = 0;
    }

    browser.authPromptAbuseCounter[baseDomain] += 1;
  },

  resetPromptAbuseCounter(baseDomain, browser) {
    if (!browser || !browser.authPromptAbuseCounter) {
      return;
    }

    browser.authPromptAbuseCounter[baseDomain] = 0;
  },

  hasReachedAbuseLimit(baseDomain, browser) {
    if (!browser || !browser.authPromptAbuseCounter) {
      return false;
    }

    let abuseCounter = browser.authPromptAbuseCounter[baseDomain];
    // Allow for setting -1 to turn the feature off.
    if (this.abuseLimit < 0) {
      return false;
    }
    return !!abuseCounter && abuseCounter >= this.abuseLimit;
  },
};

XPCOMUtils.defineLazyPreferenceGetter(
  PromptAbuseHelper,
  "abuseLimit",
  "prompts.authentication_dialog_abuse_limit"
);

/**
 * Implements nsIPromptFactory
 *
 * Invoked by [toolkit/components/prompts/src/Prompter.sys.mjs]
 */
export function LoginManagerAuthPromptFactory() {
  Services.obs.addObserver(this, "passwordmgr-crypto-login", true);
}

LoginManagerAuthPromptFactory.prototype = {
  classID: Components.ID("{749e62f4-60ae-4569-a8a2-de78b649660e}"),
  QueryInterface: ChromeUtils.generateQI([
    "nsIPromptFactory",
    "nsIObserver",
    "nsISupportsWeakReference",
  ]),

  // Tracks pending auth prompts per top level browser and hash key.
  // browser -> hashkey -> prompt
  // This enables us to consolidate auth prompts with the same browser and
  // hashkey (level, origin, realm).
  _pendingPrompts: new WeakMap(),
  _pendingSavePrompts: new WeakMap(),
  // We use a separate bucket for when we don't have a browser.
  // _noBrowser -> hashkey -> prompt
  _noBrowser: {},
  // Promise used to defer prompts if the password manager isn't ready when
  // they're called.
  _uiBusyPromise: null,
  _uiBusyResolve: null,

  observe(_subject, topic, _data) {
    this.log(`Observed topic: ${topic}.`);
    if (topic == "passwordmgr-crypto-login") {
      // Show the deferred prompters.
      this._uiBusyResolve?.();
    }
  },

  getPrompt(aWindow, aIID) {
    var prompt = new LoginManagerAuthPrompter().QueryInterface(aIID);
    prompt.init(aWindow, this);
    return prompt;
  },

  getPendingPrompt(browser, hashKey) {
    // If there is already a matching auth prompt which has no browser
    // associated we can reuse it. This way we avoid showing tab level prompts
    // when there is already a pending window prompt.
    let pendingNoBrowserPrompt = this._pendingPrompts
      .get(this._noBrowser)
      ?.get(hashKey);
    if (pendingNoBrowserPrompt) {
      return pendingNoBrowserPrompt;
    }
    return this._pendingPrompts.get(browser)?.get(hashKey);
  },

  _dismissPendingSavePrompt(browser) {
    this._pendingSavePrompts.get(browser)?.dismiss();
    this._pendingSavePrompts.delete(browser);
  },

  _setPendingSavePrompt(browser, prompt) {
    this._pendingSavePrompts.set(browser, prompt);
  },

  _setPendingPrompt(prompt, hashKey) {
    let browser = prompt.prompter.browser || this._noBrowser;
    let hashToPrompt = this._pendingPrompts.get(browser);
    if (!hashToPrompt) {
      hashToPrompt = new Map();
      this._pendingPrompts.set(browser, hashToPrompt);
    }
    hashToPrompt.set(hashKey, prompt);
  },

  _removePendingPrompt(prompt, hashKey) {
    let browser = prompt.prompter.browser || this._noBrowser;
    let hashToPrompt = this._pendingPrompts.get(browser);
    if (!hashToPrompt) {
      return;
    }
    hashToPrompt.delete(hashKey);
    if (!hashToPrompt.size) {
      this._pendingPrompts.delete(browser);
    }
  },

  async _waitForLoginsUI(prompt) {
    await this._uiBusyPromise;

    let [origin, httpRealm] = prompt.prompter._getAuthTarget(
      prompt.channel,
      prompt.authInfo
    );

    // No UI to wait for.
    if (!Services.logins.uiBusy) {
      return;
    }

    let hasLogins = Services.logins.countLogins(origin, null, httpRealm) > 0;
    if (
      !hasLogins &&
      lazy.LoginHelper.schemeUpgrades &&
      origin.startsWith("https://")
    ) {
      let httpOrigin = origin.replace(/^https:\/\//, "http://");
      hasLogins = Services.logins.countLogins(httpOrigin, null, httpRealm) > 0;
    }
    // We don't depend on saved logins.
    if (!hasLogins) {
      return;
    }

    this.log("Waiting for primary password UI.");

    this._uiBusyPromise = new Promise(resolve => {
      this._uiBusyResolve = resolve;
    });
    await this._uiBusyPromise;
  },

  async _doAsyncPrompt(prompt, hashKey) {
    this._setPendingPrompt(prompt, hashKey);

    // UI might be busy due to the primary password dialog. Wait for it to close.
    await this._waitForLoginsUI(prompt);

    let ok = false;
    let promptAborted = false;
    try {
      this.log(`Performing the prompt for ${hashKey}.`);
      ok = await prompt.prompter.promptAuthInternal(
        prompt.channel,
        prompt.level,
        prompt.authInfo
      );
    } catch (e) {
      if (
        e instanceof Components.Exception &&
        e.result == Cr.NS_ERROR_NOT_AVAILABLE
      ) {
        this.log("Bypassed, UI is not available in this context.");
        // Prompts throw NS_ERROR_NOT_AVAILABLE if they're aborted.
        promptAborted = true;
      } else {
        console.error("LoginManagerAuthPrompter: _doAsyncPrompt", e);
      }
    }

    this._removePendingPrompt(prompt, hashKey);

    // Handle callbacks
    for (var consumer of prompt.consumers) {
      if (!consumer.callback) {
        // Not having a callback means that consumer didn't provide it
        // or canceled the notification
        continue;
      }

      this.log(`Calling back to callback: ${consumer.callback} ok: ${ok}.`);
      try {
        if (ok) {
          consumer.callback.onAuthAvailable(consumer.context, prompt.authInfo);
        } else {
          consumer.callback.onAuthCancelled(consumer.context, !promptAborted);
        }
      } catch (e) {
        /* Throw away exceptions caused by callback */
      }
    }
  },
}; // end of LoginManagerAuthPromptFactory implementation

ChromeUtils.defineLazyGetter(
  LoginManagerAuthPromptFactory.prototype,
  "log",
  () => {
    let logger = lazy.LoginHelper.createLogger("LoginManagerAuthPromptFactory");
    return logger.log.bind(logger);
  }
);

/* ==================== LoginManagerAuthPrompter ==================== */

/**
 * Implements interfaces for prompting the user to enter/save/change auth info.
 *
 * nsIAuthPrompt: Used by SeaMonkey, Thunderbird, but not Firefox.
 *
 * Note this implementation no longer provides `nsIAuthPrompt.promptPassword()`
 * and `nsIAuthPrompt.promptUsernameAndPassword()`. Use their async
 * counterparts `asyncPromptPassword` and `asyncPromptUsernameAndPassword`
 * instead.
 *
 * nsIAuthPrompt2: Is invoked by a channel for protocol-based authentication
 * (eg HTTP Authenticate, FTP login).
 *
 * nsILoginManagerAuthPrompter: Used by consumers to indicate which tab/window a
 * prompt should appear on.
 */
export function LoginManagerAuthPrompter() {}

LoginManagerAuthPrompter.prototype = {
  classID: Components.ID("{8aa66d77-1bbb-45a6-991e-b8f47751c291}"),
  QueryInterface: ChromeUtils.generateQI([
    "nsIAuthPrompt",
    "nsIAuthPrompt2",
    "nsILoginManagerAuthPrompter",
  ]),

  _factory: null,
  _chromeWindow: null,
  _browser: null,

  __strBundle: null, // String bundle for L10N
  get _strBundle() {
    if (!this.__strBundle) {
      this.__strBundle = Services.strings.createBundle(
        "chrome://passwordmgr/locale/passwordmgr.properties"
      );
      if (!this.__strBundle) {
        throw new Error("String bundle for Login Manager not present!");
      }
    }

    return this.__strBundle;
  },

  __ellipsis: null,
  get _ellipsis() {
    if (!this.__ellipsis) {
      this.__ellipsis = "\u2026";
      try {
        this.__ellipsis = Services.prefs.getComplexValue(
          "intl.ellipsis",
          Ci.nsIPrefLocalizedString
        ).data;
      } catch (e) {}
    }
    return this.__ellipsis;
  },

  // Whether we are in private browsing mode
  get _inPrivateBrowsing() {
    if (this._chromeWindow) {
      return PrivateBrowsingUtils.isWindowPrivate(this._chromeWindow);
    }
    // If we don't that we're in private browsing mode if the caller did
    // not provide a window.  The callers which really care about this
    // will indeed pass down a window to us, and for those who don't,
    // we can just assume that we don't want to save the entered login
    // information.
    this.log("We have no chromeWindow so assume we're in a private context.");
    return true;
  },

  get _allowRememberLogin() {
    if (!this._inPrivateBrowsing) {
      return true;
    }
    return lazy.LoginHelper.privateBrowsingCaptureEnabled;
  },

  /* ---------- nsIAuthPrompt prompts ---------- */

  /**
   * Wrapper around the prompt service prompt. Saving random fields here
   * doesn't really make sense and therefore isn't implemented.
   */
  prompt(
    aDialogTitle,
    aText,
    aPasswordRealm,
    aSavePassword,
    aDefaultText,
    aResult
  ) {
    if (aSavePassword != Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER) {
      throw new Components.Exception(
        "prompt only supports SAVE_PASSWORD_NEVER",
        Cr.NS_ERROR_NOT_IMPLEMENTED
      );
    }

    if (aDefaultText) {
      aResult.value = aDefaultText;
    }

    return Services.prompt.prompt(
      this._chromeWindow,
      aDialogTitle,
      aText,
      aResult,
      null,
      {}
    );
  },

  /**
   * Looks up a username and password in the database. Will prompt the user
   * with a dialog, even if a username and password are found.
   */
  async asyncPromptUsernameAndPassword(
    aDialogTitle,
    aText,
    aPasswordRealm,
    aSavePassword,
    aUsername,
    aPassword
  ) {
    if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION) {
      throw new Components.Exception(
        "asyncPromptUsernameAndPassword doesn't support SAVE_PASSWORD_FOR_SESSION",
        Cr.NS_ERROR_NOT_IMPLEMENTED
      );
    }

    let foundLogins = null;
    let canRememberLogin = false;
    var selectedLogin = null;
    var [origin, realm] = this._getRealmInfo(aPasswordRealm);

    // If origin is null, we can't save this login.
    if (origin) {
      if (this._allowRememberLogin) {
        canRememberLogin =
          aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY &&
          Services.logins.getLoginSavingEnabled(origin);
      }

      // Look for existing logins.
      // We don't use searchLoginsAsync here and in asyncPromptPassword
      // because of bug 1848682
      let matchData = lazy.LoginHelper.newPropertyBag({
        origin,
        httpRealm: realm,
      });
      foundLogins = Services.logins.searchLogins(matchData);

      // XXX Like the original code, we can't deal with multiple
      // account selection. (bug 227632)
      if (foundLogins.length) {
        selectedLogin = foundLogins[0];

        // If the caller provided a username, try to use it. If they
        // provided only a password, this will try to find a password-only
        // login (or return null if none exists).
        if (aUsername.value) {
          selectedLogin = this._repickSelectedLogin(
            foundLogins,
            aUsername.value
          );
        }

        if (selectedLogin) {
          aUsername.value = selectedLogin.username;
          // If the caller provided a password, prefer it.
          if (!aPassword.value) {
            aPassword.value = selectedLogin.password;
          }
        }
      }
    }

    let autofilled = !!aPassword.value;
    var ok = Services.prompt.promptUsernameAndPassword(
      this._chromeWindow,
      aDialogTitle,
      aText,
      aUsername,
      aPassword
    );

    if (!ok || !canRememberLogin) {
      return ok;
    }

    if (!aPassword.value) {
      this.log("No password entered, so won't offer to save.");
      return ok;
    }

    // XXX We can't prompt with multiple logins yet (bug 227632), so
    // the entered login might correspond to an existing login
    // other than the one we originally selected.
    selectedLogin = this._repickSelectedLogin(foundLogins, aUsername.value);

    // If we didn't find an existing login, or if the username
    // changed, save as a new login.
    let newLogin = new LoginInfo(
      origin,
      null,
      realm,
      aUsername.value,
      aPassword.value
    );
    if (!selectedLogin) {
      // add as new
      this.log(`New login seen for: ${realm}.`);
      await Services.logins.addLoginAsync(newLogin);
    } else if (aPassword.value != selectedLogin.password) {
      // update password
      this.log(`Updating password for ${realm}.`);
      this._updateLogin(selectedLogin, newLogin);
    } else {
      this.log("Login unchanged, no further action needed.");
      Services.logins.recordPasswordUse(
        selectedLogin,
        this._inPrivateBrowsing,
        "prompt_login",
        autofilled
      );
    }

    return ok;
  },

  /**
   * If a password is found in the database for the password realm, it is
   * returned straight away without displaying a dialog.
   *
   * If a password is not found in the database, the user will be prompted
   * with a dialog with a text field and ok/cancel buttons. If the user
   * allows it, then the password will be saved in the database.
   */
  async asyncPromptPassword(
    aDialogTitle,
    aText,
    aPasswordRealm,
    aSavePassword,
    aPassword
  ) {
    if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION) {
      throw new Components.Exception(
        "promptPassword doesn't support SAVE_PASSWORD_FOR_SESSION",
        Cr.NS_ERROR_NOT_IMPLEMENTED
      );
    }

    var [origin, realm, username] = this._getRealmInfo(aPasswordRealm);

    username = decodeURIComponent(username);

    let canRememberLogin = false;
    // If origin is null, we can't save this login.
    if (origin && !this._inPrivateBrowsing) {
      canRememberLogin =
        aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY &&
        Services.logins.getLoginSavingEnabled(origin);
      if (!aPassword.value) {
        // Look for existing logins.
        let matchData = lazy.LoginHelper.newPropertyBag({
          origin,
          httpRealm: realm,
        });
        let foundLogins = Services.logins.searchLogins(matchData);

        // XXX Like the original code, we can't deal with multiple
        // account selection (bug 227632). We can deal with finding the
        // account based on the supplied username - but in this case we'll
        // just return the first match.
        for (var i = 0; i < foundLogins.length; ++i) {
          if (foundLogins[i].username == username) {
            aPassword.value = foundLogins[i].password;
            // wallet returned straight away, so this mimics that code
            return true;
          }
        }
      }
    }

    var ok = Services.prompt.promptPassword(
      this._chromeWindow,
      aDialogTitle,
      aText,
      aPassword
    );

    if (ok && canRememberLogin && aPassword.value) {
      let newLogin = new LoginInfo(
        origin,
        null,
        realm,
        username,
        aPassword.value
      );

      this.log(`New login seen for ${realm}.`);

      await Services.logins.addLoginAsync(newLogin);
    }

    return ok;
  },

  /* ---------- nsIAuthPrompt helpers ---------- */

  /**
   * Given aRealmString, such as "http://user@example.com/foo", returns an
   * array of:
   *   - the formatted origin
   *   - the realm (origin + path)
   *   - the username, if present
   *
   * If aRealmString is in the format produced by NS_GetAuthKey for HTTP[S]
   * channels, e.g. "example.com:80 (httprealm)", null is returned for all
   * arguments to let callers know the login can't be saved because we don't
   * know whether it's http or https.
   */
  _getRealmInfo(aRealmString) {
    var httpRealm = /^.+ \(.+\)$/;
    if (httpRealm.test(aRealmString)) {
      return [null, null, null];
    }

    var uri = Services.io.newURI(aRealmString);
    var pathname = "";

    if (uri.pathQueryRef != "/") {
      pathname = uri.pathQueryRef;
    }

    var formattedOrigin = this._getFormattedOrigin(uri);

    return [formattedOrigin, formattedOrigin + pathname, uri.username];
  },

  async promptAuthInternal(aChannel, aLevel, aAuthInfo) {
    var selectedLogin = null;
    var epicfail = false;
    var canAutologin = false;
    var foundLogins;
    let autofilled = false;

    try {
      // If the user submits a login but it fails, we need to remove the
      // notification prompt that was displayed. Conveniently, the user will
      // be prompted for authentication again, which brings us here.
      this._factory._dismissPendingSavePrompt(this._browser);

      var [origin, httpRealm] = this._getAuthTarget(aChannel, aAuthInfo);

      // Looks for existing logins to prefill the prompt with.
      foundLogins = await Services.logins.searchLoginsAsync({
        origin,
        httpRealm,
        schemeUpgrades: lazy.LoginHelper.schemeUpgrades,
      });
      this.log(`Found ${foundLogins.length} matching logins.`);
      let resolveBy = ["scheme", "timePasswordChanged"];
      foundLogins = lazy.LoginHelper.dedupeLogins(
        foundLogins,
        ["username"],
        resolveBy,
        origin
      );
      this.log(`${foundLogins.length} matching logins remain after deduping.`);

      // XXX Can't select from multiple accounts yet. (bug 227632)
      if (foundLogins.length) {
        selectedLogin = foundLogins[0];
        this._SetAuthInfo(
          aAuthInfo,
          selectedLogin.username,
          selectedLogin.password
        );
        autofilled = true;

        // Allow automatic proxy login
        if (
          aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY &&
          !(aAuthInfo.flags & Ci.nsIAuthInformation.PREVIOUS_FAILED) &&
          Services.prefs.getBoolPref("signon.autologin.proxy") &&
          !PrivateBrowsingUtils.permanentPrivateBrowsing
        ) {
          this.log("Autologin enabled, skipping auth prompt.");
          canAutologin = true;
        }
      }

      var canRememberLogin = Services.logins.getLoginSavingEnabled(origin);
      if (!this._allowRememberLogin) {
        canRememberLogin = false;
      }
    } catch (e) {
      // Ignore any errors and display the prompt anyway.
      epicfail = true;
      console.error("LoginManagerAuthPrompter: Epic fail in promptAuth:", e);
    }

    var ok = canAutologin;
    let browser = this._browser;
    let baseDomain;

    // We might not have a browser or browser.currentURI.host could fail
    // (e.g. on about:blank). Fall back to the subresource hostname in that case.
    try {
      let topLevelHost = browser.currentURI.host;
      baseDomain = PromptAbuseHelper.getBaseDomainOrFallback(topLevelHost);
    } catch (e) {
      baseDomain = PromptAbuseHelper.getBaseDomainOrFallback(origin);
    }

    if (!ok) {
      if (PromptAbuseHelper.hasReachedAbuseLimit(baseDomain, browser)) {
        this.log("Blocking auth dialog, due to exceeding dialog bloat limit.");
        return false;
      }

      // Set up a counter for ensuring that the basic auth prompt can not
      // be abused for DOS-style attacks. With this counter, each eTLD+1
      // per browser will get a limited number of times a user can
      // cancel the prompt until we stop showing it.
      PromptAbuseHelper.incrementPromptAbuseCounter(baseDomain, browser);

      if (this._chromeWindow) {
        PromptUtils.fireDialogEvent(
          this._chromeWindow,
          "DOMWillOpenModalDialog",
          this._browser
        );
      }

      ok = await Services.prompt.asyncPromptAuth(
        this._browser?.browsingContext,
        Ci.nsIPrompt.MODAL_TYPE_TAB,
        aChannel,
        aLevel,
        aAuthInfo
      );
    }

    let [username, password] = this._GetAuthInfo(aAuthInfo);

    // Reset the counter state if the user replied to a prompt and actually
    // tried to login (vs. simply clicking any button to get out).
    if (ok && (username || password)) {
      PromptAbuseHelper.resetPromptAbuseCounter(baseDomain, browser);
    }

    if (!ok || !canRememberLogin || epicfail) {
      return ok;
    }

    try {
      if (!password) {
        this.log("No password entered, so won't offer to save.");
        return ok;
      }

      // XXX We can't prompt with multiple logins yet (bug 227632), so
      // the entered login might correspond to an existing login
      // other than the one we originally selected.
      selectedLogin = this._repickSelectedLogin(foundLogins, username);

      // If we didn't find an existing login, or if the username
      // changed, save as a new login.
      let newLogin = new LoginInfo(origin, null, httpRealm, username, password);
      if (!selectedLogin) {
        this.log(`New login seen for origin: ${origin}.`);

        let promptBrowser = lazy.LoginHelper.getBrowserForPrompt(browser);
        let savePrompt = lazy.gPrompterService.promptToSavePassword(
          promptBrowser,
          newLogin
        );
        this._factory._setPendingSavePrompt(promptBrowser, savePrompt);
      } else if (password != selectedLogin.password) {
        this.log(`Updating password for origin: ${origin}.`);

        let promptBrowser = lazy.LoginHelper.getBrowserForPrompt(browser);
        let savePrompt = lazy.gPrompterService.promptToChangePassword(
          promptBrowser,
          selectedLogin,
          newLogin
        );
        this._factory._setPendingSavePrompt(promptBrowser, savePrompt);
      } else {
        this.log("Login unchanged, no further action needed.");
        Services.logins.recordPasswordUse(
          selectedLogin,
          this._inPrivateBrowsing,
          "auth_login",
          autofilled
        );
      }
    } catch (e) {
      console.error("LoginManagerAuthPrompter: Fail2 in promptAuth:", e);
    }

    return ok;
  },

  /* ---------- nsIAuthPrompt2 prompts ---------- */

  /**
   * Implementation of nsIAuthPrompt2.
   *
   * @param {nsIChannel} aChannel
   * @param {int}        aLevel
   * @param {nsIAuthInformation} aAuthInfo
   */
  promptAuth(aChannel, aLevel, aAuthInfo) {
    let closed = false;
    let result = false;
    this.promptAuthInternal(aChannel, aLevel, aAuthInfo)
      .then(ok => (result = ok))
      .finally(() => (closed = true));
    Services.tm.spinEventLoopUntilOrQuit(
      "LoginManagerAuthPrompter.sys.mjs:promptAuth",
      () => closed
    );
    return result;
  },

  asyncPromptAuth(aChannel, aCallback, aContext, aLevel, aAuthInfo) {
    var cancelable = null;

    try {
      // If the user submits a login but it fails, we need to remove the
      // notification prompt that was displayed. Conveniently, the user will
      // be prompted for authentication again, which brings us here.
      this._factory._dismissPendingSavePrompt(this._browser);

      cancelable = this._newAsyncPromptConsumer(aCallback, aContext);

      let [origin, httpRealm] = this._getAuthTarget(aChannel, aAuthInfo);

      let hashKey = aLevel + "|" + origin + "|" + httpRealm;
      let pendingPrompt = this._factory.getPendingPrompt(
        this._browser,
        hashKey
      );
      if (pendingPrompt) {
        this.log(
          `Prompt bound to an existing one in the queue, callback: ${aCallback}.`
        );
        pendingPrompt.consumers.push(cancelable);
        return cancelable;
      }

      this.log(`Adding new async prompt, callback: ${aCallback}.`);
      let asyncPrompt = {
        consumers: [cancelable],
        channel: aChannel,
        authInfo: aAuthInfo,
        level: aLevel,
        prompter: this,
      };

      this._factory._doAsyncPrompt(asyncPrompt, hashKey);
    } catch (e) {
      console.error("LoginManagerAuthPrompter: asyncPromptAuth:", e);
      console.error("Falling back to promptAuth");
      // Fail the prompt operation to let the consumer fall back
      // to synchronous promptAuth method
      throw e;
    }

    return cancelable;
  },

  /* ---------- nsILoginManagerAuthPrompter prompts ---------- */

  init(aWindow = null, aFactory = null) {
    if (!aWindow) {
      // There may be no applicable window e.g. in a Sandbox or JSM.
      this._chromeWindow = null;
      this._browser = null;
    } else if (aWindow.isChromeWindow) {
      this._chromeWindow = aWindow;
      // needs to be set explicitly using setBrowser
      this._browser = null;
    } else {
      let { win, browser } = this._getChromeWindow(aWindow);
      this._chromeWindow = win;
      this._browser = browser;
    }
    this._factory = aFactory || null;
  },

  set browser(aBrowser) {
    this._browser = aBrowser;
  },

  get browser() {
    return this._browser;
  },

  /* ---------- Internal Methods ---------- */

  _updateLogin(login, aNewLogin) {
    var now = Date.now();
    var propBag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
      Ci.nsIWritablePropertyBag
    );
    propBag.setProperty("formActionOrigin", aNewLogin.formActionOrigin);
    propBag.setProperty("origin", aNewLogin.origin);
    propBag.setProperty("password", aNewLogin.password);
    propBag.setProperty("username", aNewLogin.username);
    // Explicitly set the password change time here (even though it would
    // be changed automatically), to ensure that it's exactly the same
    // value as timeLastUsed.
    propBag.setProperty("timePasswordChanged", now);
    propBag.setProperty("timeLastUsed", now);
    propBag.setProperty("timesUsedIncrement", 1);
    // Note that we don't call `recordPasswordUse` so we won't potentially record
    // both a use and a save/update. See bug 1640096.
    Services.logins.modifyLogin(login, propBag);
  },

  /**
   * Given a content DOM window, returns the chrome window and browser it's in.
   */
  _getChromeWindow(aWindow) {
    let browser = aWindow.docShell.chromeEventHandler;
    if (!browser) {
      return null;
    }

    let chromeWin = browser.ownerGlobal;
    if (!chromeWin) {
      return null;
    }

    return { win: chromeWin, browser };
  },

  /**
   * The user might enter a login that isn't the one we prefilled, but
   * is the same as some other existing login. So, pick a login with a
   * matching username, or return null.
   */
  _repickSelectedLogin(foundLogins, username) {
    for (var i = 0; i < foundLogins.length; i++) {
      if (foundLogins[i].username == username) {
        return foundLogins[i];
      }
    }
    return null;
  },

  /**
   * Can be called as:
   *   _getLocalizedString("key1");
   *   _getLocalizedString("key2", ["arg1"]);
   *   _getLocalizedString("key3", ["arg1", "arg2"]);
   *   (etc)
   *
   * Returns the localized string for the specified key,
   * formatted if required.
   *
   */
  _getLocalizedString(key, formatArgs) {
    if (formatArgs) {
      return this._strBundle.formatStringFromName(key, formatArgs);
    }
    return this._strBundle.GetStringFromName(key);
  },

  /**
   * Sanitizes the specified username, by stripping quotes and truncating if
   * it's too long. This helps prevent an evil site from messing with the
   * "save password?" prompt too much.
   */
  _sanitizeUsername(username) {
    if (username.length > 30) {
      username = username.substring(0, 30);
      username += this._ellipsis;
    }
    return username.replace(/['"]/g, "");
  },

  /**
   * The aURI parameter may either be a string uri, or an nsIURI instance.
   *
   * Returns the origin to use in a nsILoginInfo object (for example,
   * "http://example.com").
   */
  _getFormattedOrigin(aURI) {
    let uri;
    if (aURI instanceof Ci.nsIURI) {
      uri = aURI;
    } else {
      uri = Services.io.newURI(aURI);
    }

    return uri.scheme + "://" + uri.displayHostPort;
  },

  /**
   * Converts a login's origin field (a URL) to a short string for
   * prompting purposes. Eg, "http://foo.com" --> "foo.com", or
   * "ftp://www.site.co.uk" --> "site.co.uk".
   */
  _getShortDisplayHost(aURIString) {
    var displayHost;

    var idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
      Ci.nsIIDNService
    );
    try {
      var uri = Services.io.newURI(aURIString);
      var baseDomain = Services.eTLD.getBaseDomain(uri);
      displayHost = idnService.convertToDisplayIDN(baseDomain);
    } catch (e) {
      this.log(`Couldn't process supplied URIString ${aURIString}.`);
    }

    if (!displayHost) {
      displayHost = aURIString;
    }

    return displayHost;
  },

  /**
   * Returns the origin and realm for which authentication is being
   * requested, in the format expected to be used with nsILoginInfo.
   */
  _getAuthTarget(aChannel, aAuthInfo) {
    var origin, realm;

    // If our proxy is demanding authentication, don't use the
    // channel's actual destination.
    if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
      this.log("getAuthTarget is for proxy auth.");
      if (!(aChannel instanceof Ci.nsIProxiedChannel)) {
        throw new Error("proxy auth needs nsIProxiedChannel");
      }

      var info = aChannel.proxyInfo;
      if (!info) {
        throw new Error("proxy auth needs nsIProxyInfo");
      }

      // Proxies don't have a scheme, but we'll use "moz-proxy://"
      // so that it's more obvious what the login is for.
      var idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
        Ci.nsIIDNService
      );
      origin =
        "moz-proxy://" +
        idnService.convertUTF8toACE(info.host) +
        ":" +
        info.port;
      realm = aAuthInfo.realm;
      if (!realm) {
        realm = origin;
      }

      return [origin, realm];
    }

    origin = this._getFormattedOrigin(aChannel.URI);

    // If a HTTP WWW-Authenticate header specified a realm, that value
    // will be available here. If it wasn't set or wasn't HTTP, we'll use
    // the formatted origin instead.
    realm = aAuthInfo.realm;
    if (!realm) {
      realm = origin;
    }

    return [origin, realm];
  },

  /**
   * Returns [username, password] as extracted from aAuthInfo (which
   * holds this info after having prompted the user).
   *
   * If the authentication was for a Windows domain, we'll prepend the
   * return username with the domain. (eg, "domain\user")
   */
  _GetAuthInfo(aAuthInfo) {
    var username, password;

    var flags = aAuthInfo.flags;
    if (flags & Ci.nsIAuthInformation.NEED_DOMAIN && aAuthInfo.domain) {
      username = aAuthInfo.domain + "\\" + aAuthInfo.username;
    } else {
      username = aAuthInfo.username;
    }

    password = aAuthInfo.password;

    return [username, password];
  },

  /**
   * Given a username (possibly in DOMAIN\user form) and password, parses the
   * domain out of the username if necessary and sets domain, username and
   * password on the auth information object.
   */
  _SetAuthInfo(aAuthInfo, username, password) {
    var flags = aAuthInfo.flags;
    if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
      // Domain is separated from username by a backslash
      var idx = username.indexOf("\\");
      if (idx == -1) {
        aAuthInfo.username = username;
      } else {
        aAuthInfo.domain = username.substring(0, idx);
        aAuthInfo.username = username.substring(idx + 1);
      }
    } else {
      aAuthInfo.username = username;
    }
    aAuthInfo.password = password;
  },

  _newAsyncPromptConsumer(aCallback, aContext) {
    return {
      QueryInterface: ChromeUtils.generateQI(["nsICancelable"]),
      callback: aCallback,
      context: aContext,
      cancel() {
        this.callback.onAuthCancelled(this.context, false);
        this.callback = null;
        this.context = null;
      },
    };
  },
}; // end of LoginManagerAuthPrompter implementation

ChromeUtils.defineLazyGetter(LoginManagerAuthPrompter.prototype, "log", () => {
  let logger = lazy.LoginHelper.createLogger("LoginManagerAuthPrompter");
  return logger.log.bind(logger);
});
PK
!<�e�����!modules/LoginManagerChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Module doing most of the content process work for the password manager.
 */

// Disable use-ownerGlobal since LoginForm doesn't have it.
/* eslint-disable mozilla/use-ownerGlobal */

const PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS = 1;
// The amount of time a context menu event supresses showing a
// popup from a focus event in ms. This matches the threshold in
// toolkit/components/satchel/nsFormFillController.cpp
const AUTOCOMPLETE_AFTER_RIGHT_CLICK_THRESHOLD_MS = 400;
const AUTOFILL_STATE = "autofill";

const LOG_MESSAGE_FORM_SUBMISSION = "form submission";
const LOG_MESSAGE_FIELD_EDIT = "field edit";

export const AUTOFILL_RESULT = {
  FILLED: "filled",
  NO_PASSWORD_FIELD: "no_password_field",
  PASSWORD_DISABLED_READONLY: "password_disabled_readonly",
  NO_LOGINS_FIT: "no_logins_fit",
  NO_SAVED_LOGINS: "no_saved_logins",
  EXISTING_PASSWORD: "existing_password",
  EXISTING_USERNAME: "existing_username",
  MULTIPLE_LOGINS: "multiple_logins",
  NO_AUTOFILL_FORMS: "no_autofill_forms",
  AUTOCOMPLETE_OFF: "autocomplete_off",
  INSECURE: "insecure",
  PASSWORD_AUTOCOMPLETE_NEW_PASSWORD: "password_autocomplete_new_password",
  TYPE_NO_LONGER_PASSWORD: "type_no_longer_password",
  FORM_IN_CROSSORIGIN_SUBFRAME: "form_in_crossorigin_subframe",
  FILLED_USERNAME_ONLY_FORM: "filled_username_only_form",
};

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { PrivateBrowsingUtils } from "resource://gre/modules/PrivateBrowsingUtils.sys.mjs";
import { CreditCard } from "resource://gre/modules/CreditCard.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ContentDOMReference: "resource://gre/modules/ContentDOMReference.sys.mjs",
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  FormLikeFactory: "resource://gre/modules/FormLikeFactory.sys.mjs",
  FormScenarios: "resource://gre/modules/FormScenarios.sys.mjs",
  FORM_SUBMISSION_REASON: "resource://gre/actors/FormHandlerChild.sys.mjs",
  InsecurePasswordUtils: "resource://gre/modules/InsecurePasswordUtils.sys.mjs",
  LoginAutoCompleteResult: "resource://gre/modules/LoginAutoComplete.sys.mjs",
  LoginFormFactory: "resource://gre/modules/shared/LoginFormFactory.sys.mjs",
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
  LoginRecipesContent: "resource://gre/modules/LoginRecipes.sys.mjs",
  LoginManagerTelemetry: "resource://gre/modules/LoginManagerTelemetry.sys.mjs",
  NewPasswordModel: "resource://gre/modules/shared/NewPasswordModel.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gFormFillService",
  "@mozilla.org/satchel/form-fill-controller;1",
  "nsIFormFillController"
);

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  let logger = lazy.LoginHelper.createLogger("LoginManagerChild");
  return logger.log.bind(logger);
});

Services.cpmm.addMessageListener("clearRecipeCache", () => {
  lazy.LoginRecipesContent._clearRecipeCache();
});

let gLastRightClickTimeStamp = Number.NEGATIVE_INFINITY;

// Events on pages with Shadow DOM could return the shadow host element
// (aEvent.target) rather than the actual username or password field
// (aEvent.composedTarget).
// Only allow input elements (can be extended later) to avoid false negatives.
class WeakFieldSet extends WeakSet {
  add(value) {
    if (!HTMLInputElement.isInstance(value)) {
      throw new Error("Non-field type added to a WeakFieldSet");
    }
    super.add(value);
  }
}

const observer = {
  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsIWebProgressListener",
    "nsISupportsWeakReference",
  ]),

  onStateChange(aWebProgress, aRequest, aState, _aStatus) {
    if (
      aState & Ci.nsIWebProgressListener.STATE_RESTORING &&
      aState & Ci.nsIWebProgressListener.STATE_STOP
    ) {
      // Re-fill a document restored from bfcache since password field values
      // aren't persisted there.
      const window = aWebProgress.DOMWindow;
      const loginManagerChild = LoginManagerChild.forWindow(window);
      loginManagerChild._onDocumentRestored(window.document);
    }
  },

  // nsIDOMEventListener
  handleEvent(aEvent) {
    if (!aEvent.isTrusted) {
      return;
    }

    if (!lazy.LoginHelper.enabled) {
      return;
    }

    const ownerDocument = aEvent.target.ownerDocument;
    const window = ownerDocument.defaultView;
    const loginManagerChild = LoginManagerChild.forWindow(window);
    const docState = loginManagerChild.stateForDocument(ownerDocument);
    const field = aEvent.composedTarget;

    switch (aEvent.type) {
      // Used to mask fields with filled generated passwords when blurred.
      case "blur":
        this.handleBlur(docState, field);
        break;

      // Used to watch for changes to username and password fields.
      case "change":
        this.handleChange(docState, field, loginManagerChild);
        break;

      case "input":
        this.handleInput(
          aEvent,
          docState,
          field,
          loginManagerChild,
          ownerDocument
        );
        break;

      case "keydown":
        this.handleKeydown(aEvent, field, loginManagerChild, ownerDocument);
        break;

      case "focus":
        this.handleFocus(field, docState, aEvent.target);
        break;

      case "mousedown":
        this.handleMousedown(aEvent.button);
        break;

      default: {
        throw new Error("Unexpected event");
      }
    }
  },

  handleBlur(docState, field) {
    if (docState.generatedPasswordFields.has(field)) {
      docState._togglePasswordFieldMasking(field, false);
    }
  },

  handleChange(docState, field, loginManagerChild) {
    let formLikeRoot = lazy.FormLikeFactory.findRootForField(field);
    if (!docState.fieldModificationsByRootElement.get(formLikeRoot)) {
      lazy.log("Ignoring change event on form that hasn't been user-modified.");
      if (field.hasBeenTypePassword) {
        // Send notification that the password field has not been changed.
        // This is used only for testing.
        loginManagerChild._ignorePasswordEdit();
      }
      return;
    }

    docState.storeUserInput(field);
    let detail = {
      possibleValues: {
        usernames: docState.possibleUsernames,
        passwords: docState.possiblePasswords,
      },
    };
    loginManagerChild.sendAsyncMessage(
      "PasswordManager:updateDoorhangerSuggestions",
      detail
    );

    if (field.hasBeenTypePassword) {
      let triggeredByFillingGenerated =
        docState.generatedPasswordFields.has(field);
      // Autosave generated password initial fills and subsequent edits
      if (triggeredByFillingGenerated) {
        loginManagerChild._passwordEditedOrGenerated(field, {
          triggeredByFillingGenerated,
        });
      } else {
        // Send a notification that we are not saving the edit to the password field.
        // This is used only for testing.
        loginManagerChild._ignorePasswordEdit();
      }
    }
  },

  handleInput(aEvent, docState, field, loginManagerChild, ownerDocument) {
    let isPasswordType = lazy.LoginHelper.isPasswordFieldType(field);
    // React to input into fields filled with generated passwords.
    if (
      docState.generatedPasswordFields.has(field) &&
      // Depending on the edit, we may no longer want to consider
      // the field a generated password field to avoid autosaving.
      loginManagerChild._doesEventClearPrevFieldValue(aEvent)
    ) {
      docState._stopTreatingAsGeneratedPasswordField(field);
    }

    if (!isPasswordType && !lazy.LoginHelper.isUsernameFieldType(field)) {
      return;
    }

    // React to input into potential username or password fields
    let formLikeRoot = lazy.FormLikeFactory.findRootForField(field);

    if (formLikeRoot !== aEvent.currentTarget) {
      return;
    }
    // flag this form as user-modified for the closest form/root ancestor
    let alreadyModified =
      docState.fieldModificationsByRootElement.get(formLikeRoot);
    let { login: filledLogin, userTriggered: fillWasUserTriggered } =
      docState.fillsByRootElement.get(formLikeRoot) || {};

    // don't flag as user-modified if the form was autofilled and doesn't appear to have changed
    let isAutofillInput = filledLogin && !fillWasUserTriggered;
    if (!alreadyModified && isAutofillInput) {
      if (isPasswordType && filledLogin.password == field.value) {
        lazy.log(
          "Ignoring password input event that doesn't change autofilled values."
        );
        return;
      }
      if (
        !isPasswordType &&
        filledLogin.usernameField &&
        filledLogin.username == field.value
      ) {
        lazy.log(
          "Ignoring username input event that doesn't change autofilled values."
        );
        return;
      }
    }
    docState.fieldModificationsByRootElement.set(formLikeRoot, true);
    // Keep track of the modified formless password field to trigger form submission
    // when it is removed from DOM.
    let alreadyModifiedFormLessField = true;
    if (!HTMLFormElement.isInstance(formLikeRoot)) {
      alreadyModifiedFormLessField =
        docState.formlessModifiedPasswordFields.has(field);
      if (!alreadyModifiedFormLessField) {
        docState.formlessModifiedPasswordFields.add(field);
      }
    }

    // Infer form submission only when there has been an user interaction on the form
    // or the formless password field.
    if (!alreadyModified || !alreadyModifiedFormLessField) {
      const formHandlerChild =
        ownerDocument.defaultView.windowGlobalChild?.getActor("FormHandler");
      formHandlerChild.registerFormSubmissionInterest(loginManagerChild, {
        includesFormRemoval: lazy.LoginHelper.formRemovalCaptureEnabled,
        includesPageNavigation: lazy.LoginHelper.formlessCaptureEnabled,
      });
    }

    if (
      // When the password field value is cleared or entirely replaced we don't treat it as
      // an autofilled form any more. We don't do the same for username edits to avoid snooping
      // on the autofilled password in the resulting doorhanger
      isPasswordType &&
      loginManagerChild._doesEventClearPrevFieldValue(aEvent) &&
      // Don't clear last recorded autofill if THIS is an autofilled value. This will be true
      // when filling from the context menu.
      filledLogin &&
      filledLogin.password !== field.value
    ) {
      docState.fillsByRootElement.delete(formLikeRoot);
    }

    if (!lazy.LoginHelper.passwordEditCaptureEnabled) {
      return;
    }
    if (field.hasBeenTypePassword) {
      // When a field is filled with a generated password, we also fill a confirm password field
      // if found. To do this, _fillConfirmFieldWithGeneratedPassword calls setUserInput, which fires
      // an "input" event on the confirm password field. compareAndUpdatePreviouslySentValues will
      // allow that message through due to triggeredByFillingGenerated, so early return here.
      let form = lazy.LoginFormFactory.createFromField(field);
      if (
        docState.generatedPasswordFields.has(field) &&
        docState._getFormFields(form).confirmPasswordField === field
      ) {
        return;
      }
      // Don't check for triggeredByFillingGenerated, as we do not want to autosave
      // a field marked as a generated password field on every "input" event
      loginManagerChild._passwordEditedOrGenerated(field);
    } else {
      let [usernameField, passwordField] =
        docState.getUserNameAndPasswordFields(field);
      if (field == usernameField && passwordField?.value) {
        loginManagerChild._passwordEditedOrGenerated(passwordField, {
          triggeredByFillingGenerated:
            docState.generatedPasswordFields.has(passwordField),
        });
      }
    }
  },

  handleKeydown(aEvent, field, loginManagerChild, ownerDocument) {
    if (
      field.value &&
      (aEvent.keyCode == aEvent.DOM_VK_TAB ||
        aEvent.keyCode == aEvent.DOM_VK_RETURN)
    ) {
      const autofillForm =
        lazy.LoginHelper.autofillForms &&
        !PrivateBrowsingUtils.isContentWindowPrivate(ownerDocument.defaultView);

      if (autofillForm) {
        loginManagerChild.onUsernameAutocompleted(field);
      }
    }
  },

  handleFocus(field, docState, target) {
    //@sg see if we can drop focusedField (aEvent.target) and use field (aEvent.composedTarget)
    docState.onFocus(field, target);
  },

  handleMousedown(button) {
    if (button == 2) {
      // Date.now() is used instead of event.timeStamp since
      // dom.event.highrestimestamp.enabled isn't true on all channels yet.
      gLastRightClickTimeStamp = Date.now();
    }
  },
};

/**
 * Form scenario defines what can be done with form.
 */
class FormScenario {}

/**
 * Sign up scenario defines typical account registration flow.
 */
class SignUpFormScenario extends FormScenario {
  usernameField;
  passwordField;
}

/**
 * Logic of Capture and Filling.
 *
 * This class will be shared with Firefox iOS and should have no references to
 * Gecko internals. See Bug 1774208.
 */
export class LoginFormState {
  /**
   * Keeps track of filled fields and values.
   */
  fillsByRootElement = new WeakMap();
  /**
   * Keeps track of fields we've filled with generated passwords
   */
  generatedPasswordFields = new WeakFieldSet();
  /**
   * Keeps track of logins that were last submitted.
   */
  lastSubmittedValuesByRootElement = new WeakMap();
  fieldModificationsByRootElement = new WeakMap();
  /**
   * Anything entered into an <input> that we think might be a username
   */
  possibleUsernames = new Set();
  /**
   * Anything entered into an <input> that we think might be a password
   */
  possiblePasswords = new Set();

  /**
   * Keeps track of the formLike of nodes (form or formless password field)
   * that we are watching when they are removed from DOM.
   */
  formLikeByObservedNode = new WeakMap();

  /**
   * Keeps track of all formless password fields that have been
   * updated by the user.
   */
  formlessModifiedPasswordFields = new WeakFieldSet();

  /**
   * Caches the results of the username heuristics
   */
  #cachedIsInferredUsernameField = new WeakMap();
  #cachedIsInferredEmailField = new WeakMap();
  #cachedIsInferredLoginForm = new WeakMap();

  /**
   * Records the mock username field when its associated form is submitted.
   */
  mockUsernameOnlyField = null;

  /**
   * Records the number of possible username event received for this document.
   */
  numFormHasPossibleUsernameEvent = 0;

  captureLoginTimeStamp = 0;

  // Scenarios detected on this page
  #scenariosByRoot = new WeakMap();

  getScenario(inputElement) {
    const formLikeRoot = lazy.FormLikeFactory.findRootForField(inputElement);
    return this.#scenariosByRoot.get(formLikeRoot);
  }

  setScenario(formLikeRoot, scenario) {
    this.#scenariosByRoot.set(formLikeRoot, scenario);
  }

  storeUserInput(field) {
    if (field.value && lazy.LoginHelper.captureInputChanges) {
      if (lazy.LoginHelper.isPasswordFieldType(field)) {
        this.possiblePasswords.add(field.value);
      } else if (lazy.LoginHelper.isUsernameFieldType(field)) {
        this.possibleUsernames.add(field.value);
      }
    }
  }

  /**
   * Returns true if the input field is considered an email field by
   * 'LoginHelper.isInferredEmailField'.
   *
   * @param {Element} element the field to check.
   * @returns {boolean} True if the element is likely an email field
   */
  isProbablyAnEmailField(inputElement) {
    if (!inputElement) {
      return false;
    }

    let result = this.#cachedIsInferredEmailField.get(inputElement);
    if (result === undefined) {
      result = lazy.LoginHelper.isInferredEmailField(inputElement);
      this.#cachedIsInferredEmailField.set(inputElement, result);
    }

    return result;
  }

  /**
   * Returns true if the input field is considered a username field by
   * 'LoginHelper.isInferredUsernameField'. The main purpose of this method
   * is to cache the result because _getFormFields has many call sites and we
   * want to avoid applying the heuristic every time.
   *
   * @param {Element} element the field to check.
   * @returns {boolean} True if the element is likely a username field
   */
  isProbablyAUsernameField(inputElement) {
    let result = this.#cachedIsInferredUsernameField.get(inputElement);
    if (result === undefined) {
      result = lazy.LoginHelper.isInferredUsernameField(inputElement);
      this.#cachedIsInferredUsernameField.set(inputElement, result);
    }

    return result;
  }

  /**
   * Returns true if the form is considered a username login form if
   * 1. The input element looks like a username field or the form looks
   *    like a login form
   * 2. The input field doesn't match keywords that indicate the username
   *    is not used for login (ex, search) or the login form is not use
   *    a username to sign-in (ex, authentication code)
   *
   * @param {Element} element the form to check.
   * @returns {boolean} True if the element is likely a login form
   */
  #isProbablyAUsernameLoginForm(formElement, inputElement) {
    let result = this.#cachedIsInferredLoginForm.get(formElement);
    if (result === undefined) {
      // We should revisit these rules after we collect more positive or negative
      // cases for username-only forms. Right now, if-else-based rules are good
      // enough to cover the sites we know, but if we find out defining "weight" for each
      // rule is necessary to improve the heuristic, we should consider switching
      // this with Fathom.

      result = false;
      // Check whether the input field looks like a username field or the
      // form looks like a sign-in or sign-up form.
      if (
        this.isProbablyAUsernameField(inputElement) ||
        lazy.LoginHelper.isInferredLoginForm(formElement)
      ) {
        // This is where we collect hints that indicate this is not a username
        // login form.
        if (!lazy.LoginHelper.isInferredNonUsernameField(inputElement)) {
          result = true;
        }
      }
      this.#cachedIsInferredLoginForm.set(formElement, result);
    }

    return result;
  }

  /**
   * Given a field, determine whether that field was last filled as a username
   * field AND whether the username is still filled in with the username AND
   * whether the associated password field has the matching password.
   *
   * @note This could possibly be unified with getFieldContext but they have
   * slightly different use cases. getFieldContext looks up recipes whereas this
   * method doesn't need to since it's only returning a boolean based upon the
   * recipes used for the last fill (in _fillForm).
   *
   * @param {HTMLInputElement} aUsernameField element contained in a LoginForm
   *                                          cached in LoginFormFactory.
   * @returns {Boolean} whether the username and password fields still have the
   *                    last-filled values, if previously filled.
   */
  #isLoginAlreadyFilled(aUsernameField) {
    let formLikeRoot = lazy.FormLikeFactory.findRootForField(aUsernameField);
    // Look for the existing LoginForm.
    let existingLoginForm =
      lazy.LoginFormFactory.getForRootElement(formLikeRoot);
    if (!existingLoginForm) {
      throw new Error(
        "#isLoginAlreadyFilled called with a username field with " +
          "no rootElement LoginForm"
      );
    }

    let { login: filledLogin } =
      this.fillsByRootElement.get(formLikeRoot) || {};
    if (!filledLogin) {
      return false;
    }

    // Unpack the weak references.
    let autoFilledUsernameField = filledLogin.usernameField?.get();
    let autoFilledPasswordField = filledLogin.passwordField?.get();

    // Check username and password values match what was filled.
    if (
      !autoFilledUsernameField ||
      autoFilledUsernameField != aUsernameField ||
      autoFilledUsernameField.value != filledLogin.username ||
      (autoFilledPasswordField &&
        autoFilledPasswordField.value != filledLogin.password)
    ) {
      return false;
    }

    return true;
  }

  _togglePasswordFieldMasking(passwordField, unmask) {
    let { editor } = passwordField;

    if (passwordField.type != "password") {
      // The type may have been changed by the website.
      lazy.log("Field isn't type=password.");
      return;
    }

    if (!unmask && !editor) {
      // It hasn't been created yet but the default is to be masked anyways.
      return;
    }

    if (unmask) {
      editor.unmask(0);
      return;
    }

    if (editor.autoMaskingEnabled) {
      return;
    }
    editor.mask();
  }

  /**
   * Track a form field as has having been filled with a generated password. This adds explicit
   * focus & blur handling to unmask & mask the value, and enables special handling of edits to
   * generated password values (see the observer's input event handler.)
   *
   * @param {HTMLInputElement} passwordField
   */
  _treatAsGeneratedPasswordField(passwordField) {
    this.generatedPasswordFields.add(passwordField);

    // blur/focus: listen for focus changes to we can mask/unmask generated passwords
    for (let eventType of ["blur", "focus"]) {
      passwordField.addEventListener(eventType, observer, {
        capture: true,
        mozSystemGroup: true,
      });
    }
    if (passwordField.ownerDocument.activeElement == passwordField) {
      // Unmask the password field
      this._togglePasswordFieldMasking(passwordField, true);
    }
  }

  _formHasModifiedFields(form) {
    const doc = form.rootElement.ownerDocument;
    let userHasInteracted;
    const testOnlyUserHasInteracted =
      lazy.LoginHelper.testOnlyUserHasInteractedWithDocument;
    if (Cu.isInAutomation && testOnlyUserHasInteracted !== null) {
      userHasInteracted = testOnlyUserHasInteracted;
    } else {
      userHasInteracted =
        !lazy.LoginHelper.userInputRequiredToCapture ||
        this.captureLoginTimeStamp != doc.lastUserGestureTimeStamp;
    }

    lazy.log(
      `_formHasModifiedFields: userHasInteracted: ${userHasInteracted}.`
    );

    // Skip if user didn't interact with the page since last call or ever
    if (!userHasInteracted) {
      return false;
    }

    // check for user inputs to the form fields
    let fieldsModified = this.fieldModificationsByRootElement.get(
      form.rootElement
    );
    // also consider a form modified if there's a difference between fields' .value and .defaultValue
    if (!fieldsModified) {
      fieldsModified = Array.from(form.elements).some(
        field =>
          field.defaultValue !== undefined && field.value !== field.defaultValue
      );
    }
    return fieldsModified;
  }

  _stopTreatingAsGeneratedPasswordField(passwordField) {
    this.generatedPasswordFields.delete(passwordField);

    // Remove all the event listeners added in _passwordEditedOrGenerated
    for (let eventType of ["blur", "focus"]) {
      passwordField.removeEventListener(eventType, observer, {
        capture: true,
        mozSystemGroup: true,
      });
    }

    // Mask the password field
    this._togglePasswordFieldMasking(passwordField, false);
  }

  onFocus(field, focusedField) {
    if (field.hasBeenTypePassword && this.generatedPasswordFields.has(field)) {
      // Used to unmask fields with filled generated passwords when focused.
      this._togglePasswordFieldMasking(field, true);
      return;
    }

    // Only used for username fields.
    this.#onUsernameFocus(focusedField);
  }

  /**
   * Focus event handler for username fields to decide whether to show autocomplete.
   * @param {HTMLInputElement} focusedField
   */
  #onUsernameFocus(focusedField) {
    if (
      !focusedField.mozIsTextField(true) ||
      focusedField.hasBeenTypePassword ||
      focusedField.readOnly
    ) {
      return;
    }

    if (this.#isLoginAlreadyFilled(focusedField)) {
      lazy.log("Login already filled.");
      return;
    }

    /*
     * A `mousedown` event is fired before the `focus` event if the user right clicks into an
     * unfocused field. In that case we don't want to show both autocomplete and a context menu
     * overlapping so we check against the timestamp that was set by the `mousedown` event if the
     * button code indicated a right click.
     * We use a timestamp instead of a bool to avoid complexity when dealing with multiple input
     * forms and the fact that a mousedown into an already focused field does not trigger another focus.
     * Date.now() is used instead of event.timeStamp since dom.event.highrestimestamp.enabled isn't
     * true on all channels yet.
     */
    let timeDiff = Date.now() - gLastRightClickTimeStamp;
    if (timeDiff < AUTOCOMPLETE_AFTER_RIGHT_CLICK_THRESHOLD_MS) {
      lazy.log(
        `Not opening autocomplete after focus since a context menu was opened within ${timeDiff}ms.`
      );
      return;
    }

    // The login manager is responsible for fields with the "webauthn" credential type.
    let acCredentialType = focusedField.getAutocompleteInfo()?.credentialType;
    if (acCredentialType == "webauthn") {
      const actor =
        focusedField.ownerGlobal.windowGlobalChild.getActor("LoginManager");
      actor.markAsAutoCompletableField(focusedField);
    }

    lazy.log("Opening the autocomplete popup.");
    lazy.gFormFillService.showPopup();
  }

  /** Remove login field highlight when its value is cleared or overwritten.
   */
  static #removeFillFieldHighlight(event) {
    event.target.autofillState = "";
  }

  /**
   * Highlight login fields on autocomplete or autofill on page load.
   * @param {Node} element that needs highlighting.
   */
  static _highlightFilledField(element) {
    element.autofillState = AUTOFILL_STATE;
    // Remove highlighting when the field is changed.
    element.addEventListener(
      "input",
      LoginFormState.#removeFillFieldHighlight,
      {
        mozSystemGroup: true,
        once: true,
      }
    );
  }

  /**
   * Returns the username field of the passed form if the form is a
   * username-only form.
   * A form is considered a username-only form only if it meets all the
   * following conditions:
   * 1. Does not have any password field,
   * 2. Only contains one input field whose type is username compatible.
   * 3. The username compatible input field looks like a username field
   *    or the form itself looks like a sign-in or sign-up form.
   * Additionally, if an input is formless and its autocomplete attribute is
   * set to 'username' (this check is done in the DOM to avoid firing excessive events),
   * we construct a FormLike object using this input and perform the same logic
   * described above to determine if the new FormLike object is username-only.
   *
   * @param {FormLike} form
   *                  the form to check.
   * @param {Object}  recipe=null
   *                  A relevant field override recipe to use.
   * @returns {Element} The username field or null (if the form is not a
   *                    username-only form).
   */
  getUsernameFieldFromUsernameOnlyForm(form, recipe = null) {
    let candidate = null;
    for (let element of form.elements) {
      // We are looking for a username-only form, so if there is a password
      // field in the form, this is NOT a username-only form.
      if (element.hasBeenTypePassword) {
        return null;
      }

      // Ignore input fields whose type are not username compatiable, ex, hidden.
      if (!lazy.LoginHelper.isUsernameFieldType(element)) {
        continue;
      }

      if (
        recipe?.notUsernameSelector &&
        element.matches(recipe.notUsernameSelector)
      ) {
        continue;
      }

      // If there are more than two input fields whose type is username
      // compatiable, this is NOT a username-only form.
      if (candidate) {
        return null;
      }
      candidate = element;
    }
    if (
      candidate &&
      this.#isProbablyAUsernameLoginForm(form.rootElement, candidate)
    ) {
      return candidate;
    }

    return null;
  }

  /**
   * @param {LoginForm} form - the LoginForm to look for password fields in.
   * @param {Object} options
   * @param {bool} [options.skipEmptyFields=false] - Whether to ignore password fields with no value.
   *                                                 Used at capture time since saving empty values isn't
   *                                                 useful.
   * @param {Object} [options.fieldOverrideRecipe=null] - A relevant field override recipe to use.
   * @return {Array|null} Array of password field elements for the specified form.
   *                      If no pw fields are found, or if more than 5 are found, then null
   *                      is returned.
   */
  static _getPasswordFields(
    form,
    {
      fieldOverrideRecipe = null,
      minPasswordLength = 0,
      ignoreConnect = false,
    } = {}
  ) {
    // Locate the password fields in the form.
    let pwFields = [];
    for (let i = 0; i < form.elements.length; i++) {
      let element = form.elements[i];
      if (
        !HTMLInputElement.isInstance(element) ||
        !element.hasBeenTypePassword ||
        (!element.isConnected && !ignoreConnect)
      ) {
        continue;
      }

      // Exclude ones matching a `notPasswordSelector`, if specified.
      if (
        fieldOverrideRecipe?.notPasswordSelector &&
        element.matches(fieldOverrideRecipe.notPasswordSelector)
      ) {
        lazy.log(
          `Skipping password field with id: ${element.id}, name: ${element.name} due to recipe ${fieldOverrideRecipe}.`
        );
        continue;
      }

      // XXX: Bug 780449 tracks our handling of emoji and multi-code-point characters in
      // password fields. To avoid surprises, we should be consistent with the visual
      // representation of the masked password
      if (
        minPasswordLength &&
        element.value.trim().length < minPasswordLength
      ) {
        lazy.log(
          `Skipping password field with id: ${element.id}, name: ${element.name} as value is too short.`
        );
        continue; // Ignore empty or too-short passwords fields
      }

      pwFields[pwFields.length] = {
        index: i,
        element,
      };
    }

    // If too few or too many fields, bail out.
    if (!pwFields.length) {
      lazy.log("Form ignored, no password fields.");
      return null;
    }

    if (pwFields.length > 5) {
      lazy.log(`Form ignored, too many password fields:  ${pwFields.length}.`);
      return null;
    }

    return pwFields;
  }

  /**
   * Stores passed arguments, and returns whether or not they match the args given the last time
   * this method was called with the same [formLikeRoot]. This is used to avoid sending duplicate
   * messages to the parent.
   *
   * @param {Element} formLikeRoot
   * @param {string} usernameValue
   * @param {string} passwordValue
   * @param {boolean?} [dismissed=false]
   * @param {boolean?} [triggeredByFillingGenerated=false] whether or not this call was triggered by a generated
   *        password being filled into a form-like element.
   *
   * @returns {boolean} true if args match the most recently passed values
   */
  compareAndUpdatePreviouslySentValues(
    formLikeRoot,
    usernameValue,
    passwordValue,
    dismissed = false,
    triggeredByFillingGenerated = false
  ) {
    const lastSentValues =
      this.lastSubmittedValuesByRootElement.get(formLikeRoot);
    if (lastSentValues) {
      if (dismissed && !lastSentValues.dismissed) {
        // preserve previous dismissed value if it was false (i.e. shown/open)
        dismissed = false;
      }
      if (
        lastSentValues.username == usernameValue &&
        lastSentValues.password == passwordValue &&
        lastSentValues.dismissed == dismissed &&
        lastSentValues.triggeredByFillingGenerated ==
          triggeredByFillingGenerated
      ) {
        lazy.log(
          "compareAndUpdatePreviouslySentValues: values are equivalent, returning true."
        );
        return true;
      }
    }

    // Save the last submitted values so we don't prompt twice for the same values using
    // different capture methods e.g. a form submit event and upon navigation.
    this.lastSubmittedValuesByRootElement.set(formLikeRoot, {
      username: usernameValue,
      password: passwordValue,
      dismissed,
      triggeredByFillingGenerated,
    });
    lazy.log(
      "compareAndUpdatePreviouslySentValues: values not equivalent, returning false."
    );
    return false;
  }

  fillConfirmFieldWithGeneratedPassword(passwordField) {
    // Fill a nearby password input if it looks like a confirm-password field
    let form = lazy.LoginFormFactory.createFromField(passwordField);
    let confirmPasswordInput = null;
    // The confirm-password field shouldn't be more than 3 form elements away from the password field we filled
    let MAX_CONFIRM_PASSWORD_DISTANCE = 3;

    let startIndex = form.elements.indexOf(passwordField);
    if (startIndex == -1) {
      throw new Error(
        "Password field is not in the form's elements collection"
      );
    }

    // If we've already filled another field with a generated password,
    // this might be the confirm-password field, so don't try and find another
    let previousGeneratedPasswordField = form.elements.some(
      inp => inp !== passwordField && this.generatedPasswordFields.has(inp)
    );
    if (previousGeneratedPasswordField) {
      lazy.log("Previously-filled generated password input found.");
      return;
    }

    // Get a list of input fields to search in.
    // Pre-filter type=hidden fields; they don't count against the distance threshold
    let afterFields = form.elements
      .slice(startIndex + 1)
      .filter(elem => elem.type !== "hidden");

    let acFieldName = passwordField.getAutocompleteInfo()?.fieldName;

    // Match same autocomplete values first
    if (acFieldName == "new-password") {
      let matchIndex = afterFields.findIndex(
        elem =>
          lazy.LoginHelper.isPasswordFieldType(elem) &&
          elem.getAutocompleteInfo().fieldName == acFieldName &&
          !elem.disabled &&
          !elem.readOnly
      );
      if (matchIndex >= 0 && matchIndex < MAX_CONFIRM_PASSWORD_DISTANCE) {
        confirmPasswordInput = afterFields[matchIndex];
      }
    }
    if (!confirmPasswordInput) {
      for (
        let idx = 0;
        idx < Math.min(MAX_CONFIRM_PASSWORD_DISTANCE, afterFields.length);
        idx++
      ) {
        if (
          lazy.LoginHelper.isPasswordFieldType(afterFields[idx]) &&
          !afterFields[idx].disabled &&
          !afterFields[idx].readOnly
        ) {
          confirmPasswordInput = afterFields[idx];
          break;
        }
      }
    }
    if (confirmPasswordInput && !confirmPasswordInput.value) {
      this._treatAsGeneratedPasswordField(confirmPasswordInput);
      confirmPasswordInput.setUserInput(passwordField.value);
      LoginFormState._highlightFilledField(confirmPasswordInput);
    }
  }

  /**
   * Returns the username and password fields found in the form.
   * Can handle complex forms by trying to figure out what the
   * relevant fields are.
   *
   * @param {LoginForm} form
   * @param {bool} isSubmission
   * @param {Set} recipes
   * @param {Object} options
   * @param {bool} [options.ignoreConnect] - Whether to ignore checking isConnected
   *                                         of the element.
   * @return {Object} {usernameField, newPasswordField, oldPasswordField, confirmPasswordField}
   *
   * usernameField may be null.
   * newPasswordField may be null. If null, this is a username-only form.
   * oldPasswordField may be null. If null, newPasswordField is just
   * "theLoginField". If not null, the form is apparently a
   * change-password field, with oldPasswordField containing the password
   * that is being changed.
   *
   * Note that even though we can create a LoginForm from a text field,
   * this method will only return a non-null usernameField if the
   * LoginForm has a password field.
   */
  _getFormFields(form, isSubmission, recipes, { ignoreConnect = false } = {}) {
    let usernameField = null;
    let newPasswordField = null;
    let oldPasswordField = null;
    let confirmPasswordField = null;
    let emptyResult = {
      usernameField: null,
      newPasswordField: null,
      oldPasswordField: null,
      confirmPasswordField: null,
    };

    let pwFields = null;
    let fieldOverrideRecipe = lazy.LoginRecipesContent.getFieldOverrides(
      recipes,
      form
    );
    if (fieldOverrideRecipe) {
      lazy.log("fieldOverrideRecipe found ", fieldOverrideRecipe);
      let pwOverrideField = lazy.LoginRecipesContent.queryLoginField(
        form,
        fieldOverrideRecipe.passwordSelector
      );
      if (pwOverrideField) {
        lazy.log("pwOverrideField found ", pwOverrideField);
        // The field from the password override may be in a different LoginForm.
        let formLike = lazy.LoginFormFactory.createFromField(pwOverrideField);
        pwFields = [
          {
            index: [...formLike.elements].indexOf(pwOverrideField),
            element: pwOverrideField,
          },
        ];
      }

      let usernameOverrideField = lazy.LoginRecipesContent.queryLoginField(
        form,
        fieldOverrideRecipe.usernameSelector
      );
      if (usernameOverrideField) {
        usernameField = usernameOverrideField;
      }
    }

    if (!pwFields) {
      // Locate the password field(s) in the form. Up to 5 supported.
      // If there's no password field, there's nothing for us to do.
      const minSubmitPasswordLength = 2;
      pwFields = LoginFormState._getPasswordFields(form, {
        fieldOverrideRecipe,
        minPasswordLength: isSubmission ? minSubmitPasswordLength : 0,
        ignoreConnect,
      });
    }

    // Check whether this is a username-only form when the form doesn't have
    // a password field. Note that recipes are not supported in username-only
    // forms currently (Bug 1708455).
    if (!pwFields) {
      if (!lazy.LoginHelper.usernameOnlyFormEnabled) {
        return emptyResult;
      }

      usernameField = this.getUsernameFieldFromUsernameOnlyForm(
        form,
        fieldOverrideRecipe
      );

      if (usernameField) {
        lazy.log(`Found username field with name: ${usernameField.name}.`);
      }

      return {
        ...emptyResult,
        usernameField,
      };
    }

    if (!usernameField) {
      // Searching backwards from the first password field until we find a field
      // that looks like a "username" field. If no "username" field is found,
      // consider an email-like field a username field, if any.
      // If neither a username-like or an email-like field exists, assume the
      // first text field before the password field is the username.
      // We might not find a username field if the user is already logged in to the site.
      //
      // Note: We only search fields precede the first password field because we
      // don't see sites putting a username field after a password field. We can
      // extend searching to all fields in the form if this turns out not to be the case.

      for (let i = pwFields[0].index - 1; i >= 0; i--) {
        let element = form.elements[i];
        if (!lazy.LoginHelper.isUsernameFieldType(element, { ignoreConnect })) {
          continue;
        }

        if (
          fieldOverrideRecipe?.notUsernameSelector &&
          element.matches(fieldOverrideRecipe.notUsernameSelector)
        ) {
          continue;
        }

        // Assume the first text field is the username by default.
        // It will be replaced if we find a likely username field afterward.
        if (!usernameField) {
          usernameField = element;
        }

        if (this.isProbablyAUsernameField(element)) {
          // An username field is found, we are done.
          usernameField = element;
          break;
        } else if (this.isProbablyAnEmailField(element)) {
          // An email field is found, consider it a username field but continue
          // to search for an "username" field.
          // In current implementation, if another email field is found during
          // the process, we will use the new one.
          usernameField = element;
        }
      }
    }

    if (!usernameField) {
      lazy.log("No username field found.");
    } else {
      lazy.log(`Found username field with name: ${usernameField.name}.`);
    }

    let pwGeneratedFields = pwFields.filter(pwField =>
      this.generatedPasswordFields.has(pwField.element)
    );
    if (pwGeneratedFields.length) {
      // we have at least the newPasswordField
      [newPasswordField, confirmPasswordField] = pwGeneratedFields.map(
        pwField => pwField.element
      );
      // if the user filled a field with a generated password,
      // a field immediately previous to that is most likely the old password field
      let idx = pwFields.findIndex(
        pwField => pwField.element === newPasswordField
      );
      if (idx > 0) {
        oldPasswordField = pwFields[idx - 1].element;
      }
      return {
        ...emptyResult,
        usernameField,
        newPasswordField,
        oldPasswordField: oldPasswordField || null,
        confirmPasswordField: confirmPasswordField || null,
      };
    }

    // If we're not submitting a form (it's a page load), there are no
    // password field values for us to use for identifying fields. So,
    // just assume the first password field is the one to be filled in.
    if (!isSubmission || pwFields.length == 1) {
      let passwordField = pwFields[0].element;
      lazy.log(`Found Password field with name: ${passwordField.name}.`);
      return {
        ...emptyResult,
        usernameField,
        newPasswordField: passwordField,
        oldPasswordField: null,
      };
    }

    // We're looking for both new and old password field
    // Try to figure out what is in the form based on the password values.
    let pw1 = pwFields[0].element.value;
    let pw2 = pwFields[1] ? pwFields[1].element.value : null;
    let pw3 = pwFields[2] ? pwFields[2].element.value : null;

    if (pwFields.length == 3) {
      // Look for two identical passwords, that's the new password

      if (pw1 == pw2 && pw2 == pw3) {
        // All 3 passwords the same? Weird! Treat as if 1 pw field.
        newPasswordField = pwFields[0].element;
        oldPasswordField = null;
      } else if (pw1 == pw2) {
        newPasswordField = pwFields[0].element;
        oldPasswordField = pwFields[2].element;
      } else if (pw2 == pw3) {
        oldPasswordField = pwFields[0].element;
        newPasswordField = pwFields[2].element;
      } else if (pw1 == pw3) {
        // A bit odd, but could make sense with the right page layout.
        newPasswordField = pwFields[0].element;
        oldPasswordField = pwFields[1].element;
      } else {
        // We can't tell which of the 3 passwords should be saved.
        lazy.log(`Form ignored -- all 3 pw fields differ.`);
        return emptyResult;
      }
    } else if (pw1 == pw2) {
      // pwFields.length == 2
      // Treat as if 1 pw field
      newPasswordField = pwFields[0].element;
      oldPasswordField = null;
    } else {
      // Just assume that the 2nd password is the new password
      oldPasswordField = pwFields[0].element;
      newPasswordField = pwFields[1].element;
    }

    lazy.log(
      `New Password field id: ${newPasswordField.id}, name: ${newPasswordField.name}.`
    );

    lazy.log(
      oldPasswordField
        ? `Old Password field id: ${oldPasswordField.id}, name: ${oldPasswordField.name}.`
        : "No Old password field."
    );
    return {
      ...emptyResult,
      usernameField,
      newPasswordField,
      oldPasswordField,
    };
  }

  /**
   * Returns the username and password fields found in the form by input
   * element into form.
   *
   * @param {HTMLInputElement} aField
   *                           A form field
   * @return {Array} [usernameField, newPasswordField, oldPasswordField]
   *
   * Details of these values are the same as _getFormFields.
   */
  getUserNameAndPasswordFields(aField) {
    const noResult = [null, null, null];
    if (!HTMLInputElement.isInstance(aField)) {
      throw new Error("getUserNameAndPasswordFields: input element required");
    }

    if (aField.nodePrincipal.isNullPrincipal || !aField.isConnected) {
      return noResult;
    }

    // If the element is not a login form field, return all null.
    if (
      !aField.hasBeenTypePassword &&
      !lazy.LoginHelper.isUsernameFieldType(aField)
    ) {
      return noResult;
    }

    const form = lazy.LoginFormFactory.createFromField(aField);
    const doc = aField.ownerDocument;
    const formOrigin = lazy.LoginHelper.getLoginOrigin(doc.documentURI);
    const recipes = lazy.LoginRecipesContent.getRecipes(
      formOrigin,
      doc.defaultView
    );
    const { usernameField, newPasswordField, oldPasswordField } =
      this._getFormFields(form, false, recipes);

    return [usernameField, newPasswordField, oldPasswordField];
  }

  /**
   * Verify if a field is a valid login form field and
   * returns some information about it's LoginForm.
   *
   * @param {Element} aField
   *                  A form field we want to verify.
   *
   * @returns {Object} an object with information about the
   *                   LoginForm username and password field
   *                   or null if the passed field is invalid.
   */
  getFieldContext(aField) {
    // If the element is not a proper form field, return null.
    if (
      !HTMLInputElement.isInstance(aField) ||
      (!aField.hasBeenTypePassword &&
        !lazy.LoginHelper.isUsernameFieldType(aField)) ||
      aField.nodePrincipal.isNullPrincipal ||
      aField.nodePrincipal.schemeIs("about") ||
      !aField.ownerDocument
    ) {
      return null;
    }
    let { hasBeenTypePassword } = aField;

    // This array provides labels that correspond to the return values from
    // `getUserNameAndPasswordFields` so we can know which one aField is.
    const LOGIN_FIELD_ORDER = ["username", "new-password", "current-password"];
    let usernameAndPasswordFields = this.getUserNameAndPasswordFields(aField);
    let fieldNameHint;
    let indexOfFieldInUsernameAndPasswordFields =
      usernameAndPasswordFields.indexOf(aField);
    if (indexOfFieldInUsernameAndPasswordFields == -1) {
      // For fields in the form that are neither username nor password,
      // set fieldNameHint to "other". Right now, in contextmenu, we treat both
      // "username" and "other" field as username fields.
      fieldNameHint = hasBeenTypePassword ? "current-password" : "other";
    } else {
      fieldNameHint =
        LOGIN_FIELD_ORDER[indexOfFieldInUsernameAndPasswordFields];
    }
    let [, newPasswordField] = usernameAndPasswordFields;

    return {
      activeField: {
        disabled: aField.disabled || aField.readOnly,
        fieldNameHint,
      },
      // `passwordField` may be the same as `activeField`.
      passwordField: {
        found: !!newPasswordField,
        disabled:
          newPasswordField &&
          (newPasswordField.disabled || newPasswordField.readOnly),
      },
    };
  }
}

/**
 * Integration with browser and IPC with LoginManagerParent.
 *
 * NOTE: there are still bits of code here that needs to be moved to
 * LoginFormState.
 */
export class LoginManagerChild extends JSWindowActorChild {
  /**
   * WeakMap of the root element of a LoginForm to the DeferredTask to fill its fields.
   *
   * This is used to be able to throttle fills for a LoginForm since onDOMInputPasswordAdded gets
   * dispatched for each password field added to a document but we only want to fill once per
   * LoginForm when multiple fields are added at once.
   *
   * @type {WeakMap}
   */
  #deferredPasswordAddedTasksByRootElement = new WeakMap();

  /**
   * WeakMap of a document to the array of callbacks to execute when it becomes visible
   *
   * This is used to defer handling DOMFormHasPassword and onDOMInputPasswordAdded events when the
   * containing document is hidden.
   * When the document first becomes visible, any queued events will be handled as normal.
   *
   * @type {WeakMap}
   */
  #visibleTasksByDocument = new WeakMap();

  /**
   * Maps all DOM content documents in this content process, including those in
   * frames, to the current state used by the Login Manager.
   */
  #loginFormStateByDocument = new WeakMap();

  /**
   * Set of fields where the user specifically requested password generation
   * (from the context menu) even if we wouldn't offer it on this field by default.
   */
  #fieldsWithPasswordGenerationForcedOn = new WeakSet();

  /**
   * Tracks whether the web progress listener that listens for the
   * restoring of documents from the bfcache is already added
   */
  #isListeningForDocumentRestoring = false;

  static forWindow(window) {
    return window.windowGlobalChild?.getActor("LoginManager");
  }

  receiveMessage(msg) {
    switch (msg.name) {
      case "PasswordManager:fillForm": {
        this.fillForm({
          loginFormOrigin: msg.data.loginFormOrigin,
          loginsFound: lazy.LoginHelper.vanillaObjectsToLogins(msg.data.logins),
          recipes: msg.data.recipes,
          inputElementIdentifier: msg.data.inputElementIdentifier,
          originMatches: msg.data.originMatches,
          style: msg.data.style,
        });
        break;
      }
      case "PasswordManager:useGeneratedPassword": {
        this.#onUseGeneratedPassword(msg.data.inputElementIdentifier);
        break;
      }
      case "PasswordManager:repopulateAutocompletePopup": {
        this.repopulateAutocompletePopup();
        break;
      }
      case "PasswordManager:formIsPending": {
        return this.#visibleTasksByDocument.has(this.document);
      }
      case "PasswordManager:formProcessed": {
        this.notifyObserversOfFormProcessed(msg.data.formid);
        break;
      }
      case "PasswordManager:OnFieldAutoComplete": {
        const { focusedInput } = lazy.gFormFillService;
        const login = lazy.LoginHelper.vanillaObjectToLogin(msg.data);
        this.onFieldAutoComplete(focusedInput, login);
        break;
      }
      case "PasswordManager:FillGeneratedPassword": {
        const { focusedInput } = lazy.gFormFillService;
        this.filledWithGeneratedPassword(focusedInput);
        break;
      }
      case "PasswordManager:FillRelayUsername": {
        const { focusedInput } = lazy.gFormFillService;
        this.fillRelayUsername(focusedInput, msg.data);
        break;
      }
    }

    return undefined;
  }

  #onUseGeneratedPassword(inputElementIdentifier) {
    let inputElement = lazy.ContentDOMReference.resolve(inputElementIdentifier);
    if (!inputElement) {
      lazy.log("Could not resolve inputElementIdentifier to a living element.");
      return;
    }

    if (inputElement != lazy.gFormFillService.focusedInput) {
      lazy.log("Could not open popup on input that's no longer focused.");
      return;
    }

    this.#fieldsWithPasswordGenerationForcedOn.add(inputElement);
    this.repopulateAutocompletePopup();
  }

  repopulateAutocompletePopup() {
    // Clear the cache of previous autocomplete results to show new options.
    lazy.gFormFillService.QueryInterface(Ci.nsIAutoCompleteInput);
    lazy.gFormFillService.controller.resetInternalState();
    lazy.gFormFillService.showPopup();
  }

  shouldIgnoreLoginManagerEvent(event) {
    let nodePrincipal = event.target.nodePrincipal;
    // If we have a system or null principal then prevent any more password manager code from running and
    // incorrectly using the document `location`. Also skip password manager for about: pages.
    return (
      nodePrincipal.isSystemPrincipal ||
      nodePrincipal.isNullPrincipal ||
      nodePrincipal.schemeIs("about")
    );
  }

  handleEvent(event) {
    if (
      AppConstants.platform == "android" &&
      Services.prefs.getBoolPref("reftest.remote", false)
    ) {
      // XXX known incompatibility between reftest harness and form-fill. Is this still needed?
      return;
    }

    if (this.shouldIgnoreLoginManagerEvent(event)) {
      return;
    }

    switch (event.type) {
      case "DOMFormHasPassword": {
        this.#onDOMFormHasPassword(event, this.document.defaultView);
        let formLike = lazy.LoginFormFactory.createFromForm(
          event.originalTarget
        );
        lazy.InsecurePasswordUtils.reportInsecurePasswords(formLike);
        break;
      }
      case "DOMPossibleUsernameInputAdded": {
        this.#onDOMPossibleUsernameInputAdded(event);
        break;
      }
      case "DOMInputPasswordAdded": {
        this.#onDOMInputPasswordAdded(event);
        let formLike = lazy.LoginFormFactory.createFromField(
          event.originalTarget
        );
        lazy.InsecurePasswordUtils.reportInsecurePasswords(formLike);
        break;
      }
      case "form-submission-detected": {
        const form = event.detail.form;
        const reason = event.detail.reason;
        this.#onFormSubmission(form, reason);
        break;
      }
      case "before-form-submission": {
        this.#onPrepareFormSubmission();
        break;
      }
    }
  }

  notifyObserversOfFormProcessed(formid) {
    Services.obs.notifyObservers(this, "passwordmgr-processed-form", formid);
  }

  /**
   * Get relevant logins and recipes from the parent
   *
   * @param {HTMLFormElement} form - form to get login data for
   * @param {Object} options
   * @param {boolean} options.guid - guid of a login to retrieve
   * @param {boolean} options.showPrimaryPassword - whether to show a primary password prompt
   */
  _getLoginDataFromParent(form, options) {
    let actionOrigin = lazy.LoginHelper.getFormActionOrigin(form);
    let messageData = { actionOrigin, options };
    let resultPromise = this.sendQuery(
      "PasswordManager:findLogins",
      messageData
    );
    return resultPromise.then(result => {
      return {
        form,
        importable: result.importable,
        loginsFound: lazy.LoginHelper.vanillaObjectsToLogins(result.logins),
        recipes: result.recipes,
      };
    });
  }

  /**
   * Set up web progress listener that listens for the restoring of a document
   * from the  bfcache in order to refill the previously autofilled login fields
   */
  #ensureDocumentRestoredListenerRegistered() {
    if (this.#isListeningForDocumentRestoring) {
      // The web progress listener is already set up
      return;
    }
    // Get the docshell of the process root and attach the progress listener to that.
    let currentBrowsingContext = this.browsingContext;
    while (currentBrowsingContext.parent) {
      currentBrowsingContext = currentBrowsingContext.parent;
    }
    const docShell = currentBrowsingContext.docShell;

    try {
      let webProgress = docShell
        .QueryInterface(Ci.nsIInterfaceRequestor)
        .getInterface(Ci.nsIWebProgress);
      webProgress.addProgressListener(
        observer,
        Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT |
          Ci.nsIWebProgress.NOTIFY_LOCATION
      );
      this.#isListeningForDocumentRestoring = true;
    } catch (ex) {
      // Ignore NS_ERROR_FAILURE if the progress listener was already added
    }
  }

  /**
   * We received a before-form-submission event and are expecting a
   * form-submission-detected event to follow. So we cache any modified
   * form or formless login fields in case they are removed from DOM
   * before we are able to capture them.
   */
  #onPrepareFormSubmission() {
    let docState = this.stateForDocument(this.document);
    let weakModificationsRootElements =
      ChromeUtils.nondeterministicGetWeakMapKeys(
        docState.fieldModificationsByRootElement
      );

    lazy.log(
      `modificationsByRootElement approx size: ${weakModificationsRootElements.length}.`
    );

    for (let rootElement of weakModificationsRootElements) {
      if (HTMLFormElement.isInstance(rootElement)) {
        // If we create formLike when it is removed, we might not have the
        // right elements at that point, so create formLike object now.
        let formLike = lazy.LoginFormFactory.createFromForm(rootElement);
        docState.formLikeByObservedNode.set(rootElement, formLike);
      }
    }

    let weakFormlessModifiedPasswordFields =
      ChromeUtils.nondeterministicGetWeakSetKeys(
        docState.formlessModifiedPasswordFields
      );

    lazy.log(
      `formlessModifiedPasswordFields approx size: ${weakFormlessModifiedPasswordFields.length}.`
    );
    for (let passwordField of weakFormlessModifiedPasswordFields) {
      let formLike = lazy.LoginFormFactory.createFromField(passwordField);
      // force elements lazy getter being called.
      if (formLike.elements.length) {
        docState.formLikeByObservedNode.set(passwordField, formLike);
      }
    }
  }

  /**
   * We received a form-submission-detected event because
   * a form or password input field was removed from the DOM
   * after a successful xhr/fetch request
   *
   * @param {HTMLElement} element form or password input field that was removed from the DOM
   */
  #onDOMElementRemoved(element) {
    if (!lazy.LoginHelper.formRemovalCaptureEnabled) {
      return;
    }
    let docState = this.stateForDocument(this.document);
    let formLike = docState.formLikeByObservedNode.get(element);
    if (!formLike) {
      return;
    }

    this._onFormSubmit(
      formLike,
      lazy.FORM_SUBMISSION_REASON.FORM_REMOVAL_AFTER_FETCH
    );

    docState.formLikeByObservedNode.delete(element);
    let weakObserveredNodes = ChromeUtils.nondeterministicGetWeakMapKeys(
      docState.formLikeByObservedNode
    );

    if (!weakObserveredNodes.length) {
      this.manager.getActor("FormHandler").unregisterFormRemovalInterest(this);
    }
  }

  /**
   * Handle form-submission-detected event (dispatched by FormHandlerChild)
   *
   * Depending on the heuristic that detected the form submission, the
   * submitted form or the formless login fields are retrieved differently
   *
   * @param {HTMLFormElement | HTMLInputElement | null} form form or formless login fields that is being submitted
   * @param {string} reason heuristic that detected the form submission
   *                        (see FormHandlerChild.FORM_SUBMISSION_REASON)
   */
  #onFormSubmission(form, reason) {
    switch (reason) {
      case lazy.FORM_SUBMISSION_REASON.PAGE_NAVIGATION:
        this._onPageNavigation();
        break;
      case lazy.FORM_SUBMISSION_REASON.FORM_SUBMIT_EVENT: {
        // We're invoked before the content's |submit| event handlers, so we
        // can grab form data before it might be modified (see bug 257781).
        let formLike = lazy.LoginFormFactory.createFromForm(form);
        this._onFormSubmit(formLike, reason);
        break;
      }
      case lazy.FORM_SUBMISSION_REASON.FORM_REMOVAL_AFTER_FETCH:
      case lazy.FORM_SUBMISSION_REASON.PASSWORD_REMOVAL_AFTER_FETCH:
        this.#onDOMElementRemoved(form);
        break;
    }
  }

  onDocumentVisibilityChange(event) {
    if (!event.isTrusted) {
      return;
    }
    let document = event.target;
    let onVisibleTasks = this.#visibleTasksByDocument.get(document);
    if (!onVisibleTasks) {
      return;
    }
    for (let task of onVisibleTasks) {
      lazy.log("onDocumentVisibilityChange: executing queued task.");
      task();
    }
    this.#visibleTasksByDocument.delete(document);
  }

  _deferHandlingEventUntilDocumentVisible(event, document, fn) {
    lazy.log(
      `Defer handling event, document.visibilityState: ${document.visibilityState}, defer handling ${event.type}.`
    );
    let onVisibleTasks = this.#visibleTasksByDocument.get(document);
    if (!onVisibleTasks) {
      lazy.log(
        "Defer handling first queued event and register the visibilitychange handler."
      );
      onVisibleTasks = [];
      this.#visibleTasksByDocument.set(document, onVisibleTasks);
      document.addEventListener(
        "visibilitychange",
        event => {
          this.onDocumentVisibilityChange(event);
        },
        { once: true }
      );
    }
    onVisibleTasks.push(fn);
  }

  #getIsPrimaryPasswordSet() {
    return Services.cpmm.sharedData.get("isPrimaryPasswordSet");
  }

  #onDOMFormHasPassword(event) {
    if (!event.isTrusted) {
      return;
    }

    if (lazy.LoginHelper.formlessCaptureEnabled) {
      this.manager
        .getActor("FormHandler")
        .registerFormSubmissionInterest(this, {
          includesFormRemoval: lazy.LoginHelper.formRemovalCaptureEnabled,
          includesPageNavigation: lazy.LoginHelper.formlessCaptureEnabled,
        });
    }
    this.#ensureDocumentRestoredListenerRegistered();

    const isPrimaryPasswordSet = this.#getIsPrimaryPasswordSet();
    let document = event.target.ownerDocument;

    // don't attempt to defer handling when a primary password is set
    // Showing the MP modal as soon as possible minimizes its interference with tab interactions
    // See bug 1539091 and bug 1538460.
    lazy.log(
      `#onDOMFormHasPassword: visibilityState: ${document.visibilityState}, isPrimaryPasswordSet: ${isPrimaryPasswordSet}.`
    );

    if (document.visibilityState == "visible" || isPrimaryPasswordSet) {
      this._processDOMFormHasPasswordEvent(event);
    } else {
      // wait until the document becomes visible before handling this event
      this._deferHandlingEventUntilDocumentVisible(event, document, () => {
        this._processDOMFormHasPasswordEvent(event);
      });
    }
  }

  _processDOMFormHasPasswordEvent(event) {
    let form = event.target;
    let formLike = lazy.LoginFormFactory.createFromForm(form);
    this._fetchLoginsFromParentAndFillForm(formLike);
  }

  #onDOMPossibleUsernameInputAdded(event) {
    if (!event.isTrusted) {
      return;
    }
    const isPrimaryPasswordSet = this.#getIsPrimaryPasswordSet();

    let document;
    if (HTMLFormElement.isInstance(event.target)) {
      document = event.target.ownerDocument;
    } else {
      document = event.target;
    }

    lazy.log(
      `#onDomPossibleUsernameInputAdded: visibilityState: ${document.visibilityState}, isPrimaryPasswordSet: ${isPrimaryPasswordSet}.`
    );

    // For simplicity, the result of the telemetry is stacked. This means if a
    // document receives two `DOMPossibleUsernameInputAdded`, we add one counter to both
    // bucket 1 & 2.
    let docState = this.stateForDocument(document);

    // Infer whether a form is a username-only form is expensive, so we restrict the
    // number of form looked up per document.
    if (
      ++docState.numFormHasPossibleUsernameEvent >
      lazy.LoginHelper.usernameOnlyFormLookupThreshold
    ) {
      return;
    }

    if (document.visibilityState == "visible" || isPrimaryPasswordSet) {
      this._processDOMPossibleUsernameInputAddedEvent(event);
    } else {
      // wait until the document becomes visible before handling this event
      this._deferHandlingEventUntilDocumentVisible(event, document, () => {
        this._processDOMPossibleUsernameInputAddedEvent(event);
      });
    }
  }

  _processDOMPossibleUsernameInputAddedEvent(event) {
    let formLike;
    if (HTMLFormElement.isInstance(event.target)) {
      formLike = lazy.LoginFormFactory.createFromForm(event.target);
    } else {
      formLike = lazy.LoginFormFactory.createFromDocumentRoot(
        event.target.documentElement
      );
    }
    // If the form contains a passoword field, `getUsernameFieldFromUsernameOnlyForm` returns
    // null, so we don't trigger autofill for those forms here. In this function,
    // we only care about username-only forms. For forms contain a password, they'll be handled
    // in onDOMFormHasPassword.

    // We specifically set the recipe to empty here to avoid loading site recipes during page loads.
    // This is okay because if we end up finding a username-only form that should be ignore by
    // the site recipe, the form will be skipped while autofilling later.
    let docState = this.stateForDocument(formLike.ownerDocument);
    let usernameField = docState.getUsernameFieldFromUsernameOnlyForm(
      formLike,
      {}
    );

    if (usernameField) {
      // Autofill the username-only form.
      lazy.log("A username-only form is found.");
      this._fetchLoginsFromParentAndFillForm(formLike);
    }

    Services.telemetry
      .getHistogramById("PWMGR_IS_USERNAME_ONLY_FORM")
      .add(!!usernameField);
  }

  #onDOMInputPasswordAdded(event) {
    if (!event.isTrusted) {
      return;
    }

    if (lazy.LoginHelper.formlessCaptureEnabled) {
      this.manager
        .getActor("FormHandler")
        .registerFormSubmissionInterest(this, {
          includesFormRemoval: lazy.LoginHelper.formRemovalCaptureEnabled,
          includesPageNavigation: lazy.LoginHelper.formlessCaptureEnabled,
        });
    }
    this.#ensureDocumentRestoredListenerRegistered();

    let pwField = event.originalTarget;
    if (pwField.form) {
      // Fill is handled by onDOMFormHasPassword which is already throttled.
      return;
    }

    let document = pwField.ownerDocument;
    const isPrimaryPasswordSet = this.#getIsPrimaryPasswordSet();
    lazy.log(
      `#onDOMInputPasswordAdded, visibilityState: ${document.visibilityState}, isPrimaryPasswordSet: ${isPrimaryPasswordSet}.`
    );

    // don't attempt to defer handling when a primary password is set
    // Showing the MP modal as soon as possible minimizes its interference with tab interactions
    // See bug 1539091 and bug 1538460.
    if (document.visibilityState == "visible" || isPrimaryPasswordSet) {
      this._processDOMInputPasswordAddedEvent(event);
    } else {
      // wait until the document becomes visible before handling this event
      this._deferHandlingEventUntilDocumentVisible(event, document, () => {
        this._processDOMInputPasswordAddedEvent(event);
      });
    }
  }

  _processDOMInputPasswordAddedEvent(event) {
    let pwField = event.originalTarget;
    let formLike = lazy.LoginFormFactory.createFromField(pwField);

    let deferredTask = this.#deferredPasswordAddedTasksByRootElement.get(
      formLike.rootElement
    );
    if (!deferredTask) {
      lazy.log(
        "Creating a DeferredTask to call _fetchLoginsFromParentAndFillForm soon."
      );
      lazy.LoginFormFactory.setForRootElement(formLike.rootElement, formLike);

      deferredTask = new lazy.DeferredTask(
        () => {
          // Get the updated LoginForm instead of the one at the time of creating the DeferredTask via
          // a closure since it could be stale since LoginForm.elements isn't live.
          let formLike2 = lazy.LoginFormFactory.getForRootElement(
            formLike.rootElement
          );
          lazy.log("Running deferred processing of onDOMInputPasswordAdded.");
          this.#deferredPasswordAddedTasksByRootElement.delete(
            formLike2.rootElement
          );
          this._fetchLoginsFromParentAndFillForm(formLike2);
        },
        PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS,
        0
      );

      this.#deferredPasswordAddedTasksByRootElement.set(
        formLike.rootElement,
        deferredTask
      );
    }

    let window = pwField.ownerGlobal;
    if (deferredTask.isArmed) {
      lazy.log("DeferredTask is already armed so just updating the LoginForm.");
      // We update the LoginForm so it (most important .elements) is fresh when the task eventually
      // runs since changes to the elements could affect our field heuristics.
      lazy.LoginFormFactory.setForRootElement(formLike.rootElement, formLike);
    } else if (
      ["interactive", "complete"].includes(window.document.readyState)
    ) {
      lazy.log(
        "Arming the DeferredTask we just created since document.readyState == 'interactive' or 'complete'."
      );
      deferredTask.arm();
    } else {
      window.addEventListener(
        "DOMContentLoaded",
        function () {
          lazy.log(
            "Arming the onDOMInputPasswordAdded DeferredTask due to DOMContentLoaded."
          );
          deferredTask.arm();
        },
        { once: true }
      );
    }
  }

  /**
   * Fetch logins from the parent for a given form and then attempt to fill it.
   *
   * @param {LoginForm} form to fetch the logins for then try autofill.
   */
  _fetchLoginsFromParentAndFillForm(form) {
    if (!lazy.LoginHelper.enabled) {
      return;
    }

    if (lazy.LoginHelper.formlessCaptureEnabled) {
      this.manager
        .getActor("FormHandler")
        .registerFormSubmissionInterest(this, {
          includesFormRemoval: lazy.LoginHelper.formRemovalCaptureEnabled,
          includesPageNavigation: lazy.LoginHelper.formlessCaptureEnabled,
        });
    }

    // set up input event listeners so we know if the user has interacted with these fields
    // * input: Listen for the field getting blanked (without blurring) or a paste
    // * change: Listen for changes to the field filled with the generated password so we can preserve edits.
    form.rootElement.addEventListener("input", observer, {
      capture: true,
      mozSystemGroup: true,
    });
    form.rootElement.addEventListener("change", observer, {
      capture: true,
      mozSystemGroup: true,
    });

    this._getLoginDataFromParent(form, { showPrimaryPassword: true })
      .then(this.loginsFound.bind(this))
      .catch(console.error);
  }

  isPasswordGenerationForcedOn(passwordField) {
    return this.#fieldsWithPasswordGenerationForcedOn.has(passwordField);
  }

  /**
   * Retrieves a reference to the state object associated with the given
   * document. This is initialized to an object with default values.
   */
  stateForDocument(document) {
    let loginFormState = this.#loginFormStateByDocument.get(document);
    if (!loginFormState) {
      loginFormState = new LoginFormState();
      this.#loginFormStateByDocument.set(document, loginFormState);
    }
    return loginFormState;
  }

  /**
   * Perform a password fill upon user request coming from the parent process.
   * The fill will be in the form previously identified during page navigation.
   *
   * @param An object with the following properties:
   *        {
   *          loginFormOrigin:
   *            String with the origin for which the login UI was displayed.
   *            This must match the origin of the form used for the fill.
   *          loginsFound:
   *            Array containing the login to fill. While other messages may
   *            have more logins, for this use case this is expected to have
   *            exactly one element. The origin of the login may be different
   *            from the origin of the form used for the fill.
   *          recipes:
   *            Fill recipes transmitted together with the original message.
   *          inputElementIdentifier:
   *            An identifier generated for the input element via ContentDOMReference.
   *          originMatches:
   *            True if the origin of the form matches the page URI.
   *        }
   */
  fillForm({
    loginFormOrigin,
    loginsFound,
    recipes,
    inputElementIdentifier,
    originMatches,
    style,
  }) {
    if (!inputElementIdentifier) {
      lazy.log("No input element specified.");
      return;
    }

    let inputElement = lazy.ContentDOMReference.resolve(inputElementIdentifier);
    if (!inputElement) {
      lazy.log("Could not resolve inputElementIdentifier to a living element.");
      return;
    }

    if (!originMatches) {
      if (
        lazy.LoginHelper.getLoginOrigin(
          inputElement.ownerDocument.documentURI
        ) != loginFormOrigin
      ) {
        lazy.log(
          "The requested origin doesn't match the one from the",
          "document. This may mean we navigated to a document from a different",
          "site before we had a chance to indicate this change in the user",
          "interface."
        );
        return;
      }
    }

    let clobberUsername = true;
    let form = lazy.LoginFormFactory.createFromField(inputElement);
    if (inputElement.hasBeenTypePassword) {
      clobberUsername = false;
    }

    this._fillForm(form, loginsFound, recipes, {
      inputElement,
      autofillForm: true,
      clobberUsername,
      clobberPassword: true,
      userTriggered: true,
      style,
    });
  }

  loginsFound({ form, importable, loginsFound, recipes }) {
    let doc = form.ownerDocument;
    let autofillForm =
      lazy.LoginHelper.autofillForms &&
      !PrivateBrowsingUtils.isContentWindowPrivate(doc.defaultView);

    let formOrigin = lazy.LoginHelper.getLoginOrigin(doc.documentURI);
    lazy.LoginRecipesContent.cacheRecipes(formOrigin, doc.defaultView, recipes);

    this._fillForm(form, loginsFound, recipes, { autofillForm, importable });
  }

  /**
   * A username or password was autocompleted into a field.
   */
  onFieldAutoComplete(acInputField, login) {
    if (!lazy.LoginHelper.enabled) {
      return;
    }

    // This is probably a bit over-conservatative.
    if (!Document.isInstance(acInputField.ownerDocument)) {
      return;
    }

    if (!lazy.LoginFormFactory.createFromField(acInputField)) {
      return;
    }

    if (lazy.LoginHelper.isUsernameFieldType(acInputField)) {
      this.onUsernameAutocompleted(acInputField, [login]);
    } else if (acInputField.hasBeenTypePassword) {
      // Ensure the field gets re-masked and edits don't overwrite the generated
      // password in case a generated password was filled into it previously.
      const docState = this.stateForDocument(acInputField.ownerDocument);
      docState._stopTreatingAsGeneratedPasswordField(acInputField);
      LoginFormState._highlightFilledField(acInputField);
    }
  }

  /**
   * A username field was filled or tabbed away from so try fill in the
   * associated password in the password field.
   */
  async onUsernameAutocompleted(acInputField, loginsFound = null) {
    lazy.log(`Autocompleting input field with name: ${acInputField.name}`);

    let acForm = lazy.LoginFormFactory.createFromField(acInputField);
    let doc = acForm.ownerDocument;
    let formOrigin = lazy.LoginHelper.getLoginOrigin(doc.documentURI);
    let recipes = lazy.LoginRecipesContent.getRecipes(
      formOrigin,
      doc.defaultView
    );

    // Make sure the username field fillForm will use is the
    // same field as the autocomplete was activated on.
    const docState = this.stateForDocument(acInputField.ownerDocument);
    let { usernameField, newPasswordField: passwordField } =
      docState._getFormFields(acForm, false, recipes);
    // Ignore the event, it's for some input we don't care about.
    if (usernameField != acInputField) {
      return;
    }

    if (!passwordField) {
      // Use `loginsFound !== null` to distinguish whether this is called when the
      // field is filled or tabbed away from. For the latter, don't highlight the field.
      if (loginsFound !== null) {
        LoginFormState._highlightFilledField(usernameField);
      }
      return;
    }

    // Fill the form when a password field is present.
    if (!loginsFound) {
      const loginData = await this._getLoginDataFromParent(acForm, {
        showPrimaryPassword: false,
      }).catch(console.error);

      if (!loginData?.loginsFound.length) {
        return;
      }

      // not an explicit autocomplete menu selection, filter for exact matches only
      loginsFound = this._filterForExactFormOriginLogins(
        loginData.loginsFound,
        acForm
      );
      // filter the list for exact matches with the username
      // NOTE: this could be an empty string which is a valid username
      const searchString = usernameField.value.toLowerCase();
      loginsFound = loginsFound.filter(
        l => l.username.toLowerCase() == searchString
      );
      recipes = loginData.recipes;
    }

    this._fillForm(acForm, loginsFound, recipes, {
      autofillForm: true,
      clobberPassword: true,
      userTriggered: true,
    });
  }

  /**
   * @return true if the page requests autocomplete be disabled for the
   *              specified element.
   */
  _isAutocompleteDisabled(element) {
    return element?.autocomplete == "off";
  }

  /**
   * Fill a page that was restored from bfcache since we wouldn't receive
   * DOMInputPasswordAdded or DOMFormHasPassword events for it.
   */
  _onDocumentRestored() {
    let rootElsWeakSet =
      lazy.LoginFormFactory.getRootElementsWeakSetForDocument(this.document);
    let weakLoginFormRootElements =
      ChromeUtils.nondeterministicGetWeakSetKeys(rootElsWeakSet);

    lazy.log(
      `loginFormRootElements approx size: ${weakLoginFormRootElements.length}.`
    );

    for (let formRoot of weakLoginFormRootElements) {
      if (!formRoot.isConnected) {
        continue;
      }

      let formLike = lazy.LoginFormFactory.getForRootElement(formRoot);
      this._fetchLoginsFromParentAndFillForm(formLike);
    }
  }

  /**
   * Trigger capture on any relevant FormLikes due to a navigation alone (not
   * necessarily due to an actual form submission). This method is used to
   * capture logins for cases where form submit events are not used.
   */
  _onPageNavigation() {
    if (!lazy.LoginHelper.formlessCaptureEnabled) {
      return;
    }
    let rootElsWeakSet =
      lazy.LoginFormFactory.getRootElementsWeakSetForDocument(this.document);
    let weakLoginFormRootElements =
      ChromeUtils.nondeterministicGetWeakSetKeys(rootElsWeakSet);

    lazy.log(`root elements approx size: ${weakLoginFormRootElements.length}`);

    for (let formRoot of weakLoginFormRootElements) {
      if (!formRoot.isConnected) {
        continue;
      }

      let formLike = lazy.LoginFormFactory.getForRootElement(formRoot);
      this._onFormSubmit(formLike, lazy.FORM_SUBMISSION_REASON.PAGE_NAVIGATION);
    }
  }

  /**
   * Called after detecting a form submission.
   * Looks for a password change in the submitted form, so we can update
   * our stored password.
   *
   * @param {LoginForm} form form to be submitted
   * @param {string} reason form submission reason
   */
  _onFormSubmit(form, reason) {
    lazy.log(`Handling form submission - infered by ${reason}`);

    this._maybeSendFormInteractionMessage(
      form,
      "PasswordManager:ShowDoorhanger",
      {
        targetField: null,
        isSubmission: true,
        // When this is trigger by inferring from form removal, the form is not
        // connected anymore, skip checking isConnected in this case.
        ignoreConnect:
          reason == lazy.FORM_SUBMISSION_REASON.FORM_REMOVAL_AFTER_FETCH,
      }
    );
  }

  /**
   * Extracts and validates information from a form-like element on the page.  If validation is
   * successful, sends a message to the parent process requesting that it show a dialog.
   *
   * The validation works are divided into two parts:
   * 1. Whether this is a valid form with a password (validate in this function)
   * 2. Whether the password manager decides to send interaction message for this form
   *    (validate in _maybeSendFormInteractionMessageContinue)
   *
   * When the function is triggered by a form submission event, and the form is valid (pass #1),
   * We still send the message to the parent even the validation of #2 fails. This is because
   * there might be someone who is interested in form submission events regardless of whether
   * the password manager decides to show the doorhanger or not.
   *
   * @param {LoginForm} form
   * @param {string} messageName used to categorize the type of message sent to the parent process.
   * @param {Element?} options.targetField
   * @param {boolean} options.isSubmission if true, this function call was prompted by a form submission.
   * @param {boolean?} options.triggeredByFillingGenerated whether or not this call was triggered by a
   *        generated password being filled into a form-like element.
   * @param {boolean?} options.ignoreConnect Whether to ignore isConnected attribute of a element.
   *
   * @returns {Boolean} whether the message is sent to the parent process.
   */
  _maybeSendFormInteractionMessage(
    form,
    messageName,
    { targetField, isSubmission, triggeredByFillingGenerated, ignoreConnect }
  ) {
    let logMessagePrefix = isSubmission
      ? LOG_MESSAGE_FORM_SUBMISSION
      : LOG_MESSAGE_FIELD_EDIT;
    let doc = form.ownerDocument;
    let win = doc.defaultView;
    let passwordField = null;
    if (targetField?.hasBeenTypePassword) {
      passwordField = targetField;
    }

    let origin = lazy.LoginHelper.getLoginOrigin(doc.documentURI);
    if (!origin) {
      lazy.log(`${logMessagePrefix} ignored -- invalid origin.`);
      return;
    }

    // Get the appropriate fields from the form.
    let recipes = lazy.LoginRecipesContent.getRecipes(origin, win);
    const docState = this.stateForDocument(form.ownerDocument);
    let fields = {
      targetField,
      ...docState._getFormFields(form, true, recipes, { ignoreConnect }),
    };

    // It's possible the field triggering this message isn't one of those found by _getFormFields' heuristics
    if (
      passwordField &&
      passwordField != fields.newPasswordField &&
      passwordField != fields.oldPasswordField &&
      passwordField != fields.confirmPasswordField
    ) {
      fields.newPasswordField = passwordField;
    }

    // Need at least 1 valid password field to do anything.
    if (fields.newPasswordField == null) {
      if (isSubmission && fields.usernameField) {
        lazy.log(
          "_onFormSubmit: username-only form. Record the username field but not sending prompt."
        );
        docState.mockUsernameOnlyField = {
          name: fields.usernameField.name,
          value: fields.usernameField.value,
        };
      }
      return;
    }

    this._maybeSendFormInteractionMessageContinue(form, messageName, {
      ...fields,
      isSubmission,
      triggeredByFillingGenerated,
    });

    if (isSubmission) {
      // Notify `PasswordManager:onFormSubmit` as long as we detect submission event on a
      // valid form with a password field.
      this.sendAsyncMessage("PasswordManager:onFormSubmit", {});
    }
  }

  /**
   * Continues the works that are not done in _maybeSendFormInteractionMessage.
   * See comments in _maybeSendFormInteractionMessage for more details.
   */
  _maybeSendFormInteractionMessageContinue(
    form,
    messageName,
    {
      targetField,
      usernameField,
      newPasswordField,
      oldPasswordField,
      isSubmission,
      triggeredByFillingGenerated,
    }
  ) {
    let logMessagePrefix = isSubmission
      ? LOG_MESSAGE_FORM_SUBMISSION
      : LOG_MESSAGE_FIELD_EDIT;
    let doc = form.ownerDocument;
    let win = doc.defaultView;
    let detail = { messageSent: false };
    try {
      // when filling a generated password, we do still want to message the parent
      if (
        !triggeredByFillingGenerated &&
        PrivateBrowsingUtils.isContentWindowPrivate(win) &&
        !lazy.LoginHelper.privateBrowsingCaptureEnabled
      ) {
        // We won't do anything in private browsing mode anyway,
        // so there's no need to perform further checks.
        lazy.log(`${logMessagePrefix} ignored in private browsing mode.`);
        return;
      }

      // If password saving is disabled globally, bail out now.
      if (!lazy.LoginHelper.enabled) {
        return;
      }

      if (usernameField) {
        this.markAsAutoCompletableField(usernameField);
      }

      let fullyMungedPattern = /^\*+$|^•+$|^\.+$/;
      // Check `isSubmission` to allow munged passwords in dismissed by default doorhangers (since
      // they are initiated by the user) in case this matches their actual password.
      if (isSubmission && newPasswordField?.value.match(fullyMungedPattern)) {
        lazy.log("New password looks munged. Not sending prompt.");
        return;
      }

      // When the username field is empty, check whether we have found it previously from
      // a username-only form, if yes, fill in its value.
      // XXX This is not ideal, we only use the previous saved username field when the current
      // form doesn't have one. This means if there is a username field found in the current
      // form, we don't compare it to the saved one, which might be a better choice in some cases.
      // The reason we are not doing it now is because we haven't found a real world example.
      let docState = this.stateForDocument(doc);
      if (!usernameField) {
        if (docState.mockUsernameOnlyField) {
          usernameField = docState.mockUsernameOnlyField;
        }
      }
      if (usernameField?.value.match(/\.{3,}|\*{3,}|•{3,}/)) {
        lazy.log(
          `usernameField with name ${usernameField.name} looks munged, setting to null.`
        );
        usernameField = null;
      }

      // Check for autocomplete=off attribute. We don't use it to prevent
      // autofilling (for existing logins), but won't save logins when it's
      // present and the storeWhenAutocompleteOff pref is false.
      // XXX spin out a bug that we don't update timeLastUsed in this case?
      if (
        (this._isAutocompleteDisabled(form) ||
          this._isAutocompleteDisabled(usernameField) ||
          this._isAutocompleteDisabled(newPasswordField) ||
          this._isAutocompleteDisabled(oldPasswordField)) &&
        !lazy.LoginHelper.storeWhenAutocompleteOff
      ) {
        lazy.log(`${logMessagePrefix} ignored -- autocomplete=off found.`);
        return;
      }

      // Don't try to send DOM nodes over IPC.
      let mockUsername = usernameField
        ? { name: usernameField.name, value: usernameField.value }
        : null;
      let mockPassword = {
        name: newPasswordField.name,
        value: newPasswordField.value,
      };
      let mockOldPassword = oldPasswordField
        ? { name: oldPasswordField.name, value: oldPasswordField.value }
        : null;

      let usernameValue = usernameField?.value;
      // Dismiss prompt if the username field is a credit card number AND
      // if the password field is a three digit number. Also dismiss prompt if
      // the password is a credit card number and the password field has attribute
      // autocomplete="cc-number".
      let dismissedPrompt = !isSubmission;
      let newPasswordFieldValue = newPasswordField.value;
      if (
        (!dismissedPrompt &&
          CreditCard.isValidNumber(usernameValue) &&
          newPasswordFieldValue.trim().match(/^[0-9]{3}$/)) ||
        (CreditCard.isValidNumber(newPasswordFieldValue) &&
          newPasswordField.getAutocompleteInfo().fieldName == "cc-number")
      ) {
        dismissedPrompt = true;
      }

      const fieldsModified = docState._formHasModifiedFields(form);
      if (!fieldsModified && lazy.LoginHelper.userInputRequiredToCapture) {
        if (targetField) {
          throw new Error("No user input on targetField");
        }
        // we know no fields in this form had user modifications, so don't prompt
        lazy.log(
          `${logMessagePrefix} ignored -- submitting values that are not changed by the user.`
        );
        return;
      }

      if (
        docState.compareAndUpdatePreviouslySentValues(
          form.rootElement,
          usernameValue,
          newPasswordField.value,
          dismissedPrompt,
          triggeredByFillingGenerated
        )
      ) {
        lazy.log(
          `${logMessagePrefix} ignored -- already submitted with the same username and password.`
        );
        return;
      }

      let { login: autoFilledLogin } =
        docState.fillsByRootElement.get(form.rootElement) || {};
      let browsingContextId = win.windowGlobalChild.browsingContext.id;
      let formActionOrigin = lazy.LoginHelper.getFormActionOrigin(form);

      detail = {
        browsingContextId,
        formActionOrigin,
        autoFilledLoginGuid: autoFilledLogin && autoFilledLogin.guid,
        usernameField: mockUsername,
        newPasswordField: mockPassword,
        oldPasswordField: mockOldPassword,
        dismissedPrompt,
        triggeredByFillingGenerated,
        possibleValues: {
          usernames: docState.possibleUsernames,
          passwords: docState.possiblePasswords,
        },
        messageSent: true,
      };

      if (messageName == "PasswordManager:ShowDoorhanger") {
        docState.captureLoginTimeStamp = doc.lastUserGestureTimeStamp;
      }
      this.sendAsyncMessage(messageName, detail);
    } catch (ex) {
      console.error(ex);
      throw ex;
    } finally {
      detail.form = form;
      const evt = new CustomEvent(messageName, { detail });
      win.windowRoot.dispatchEvent(evt);
    }
  }

  /**
   * Heuristic for whether or not we should consider [field]s value to be 'new' (as opposed to
   * 'changed') after applying [event].
   *
   * @param {HTMLInputElement} event.target input element being changed.
   * @param {string?} event.data new value being input into the field.
   *
   * @returns {boolean}
   */
  _doesEventClearPrevFieldValue({ target, data, inputType }) {
    return (
      !target.value ||
      // We check inputType here as a proxy for the previous field value.
      // If the previous field value was empty, e.g. automatically filling
      // a confirm password field when a new password field is filled with
      // a generated password, there's nothing to replace.
      // We may be able to use the "beforeinput" event instead when that
      // ships (Bug 1609291).
      (data && data == target.value && inputType !== "insertReplacementText")
    );
  }

  /**
   * The password field has been filled with a generated password, ensure the
   * field is handled accordingly.
   * @param {HTMLInputElement} passwordField
   */
  filledWithGeneratedPassword(passwordField) {
    LoginFormState._highlightFilledField(passwordField);
    this._passwordEditedOrGenerated(passwordField, {
      triggeredByFillingGenerated: true,
    });
    let docState = this.stateForDocument(passwordField.ownerDocument);
    docState.fillConfirmFieldWithGeneratedPassword(passwordField);
  }

  /**
   * Fill the relay generated username to a username field.
   */
  fillRelayUsername(usernameField, value) {
    usernameField.setUserInput(value);
  }

  /**
   * Notify the parent that we are ignoring the password edit
   * so that tests can listen for this as opposed to waiting for
   * nothing to happen.
   */
  _ignorePasswordEdit() {
    if (Cu.isInAutomation) {
      this.sendAsyncMessage("PasswordManager:onIgnorePasswordEdit", {});
    }
  }
  /**
   * Notify the parent that a generated password was filled into a field or
   * edited so that it can potentially be saved.
   * @param {HTMLInputElement} passwordField
   */
  _passwordEditedOrGenerated(
    passwordField,
    { triggeredByFillingGenerated = false } = {}
  ) {
    lazy.log(
      `Password field with name ${passwordField.name} was filled or edited.`
    );

    if (!lazy.LoginHelper.enabled && triggeredByFillingGenerated) {
      throw new Error(
        "A generated password was filled while the password manager was disabled."
      );
    }

    let loginForm = lazy.LoginFormFactory.createFromField(passwordField);

    if (triggeredByFillingGenerated) {
      LoginFormState._highlightFilledField(passwordField);
      let docState = this.stateForDocument(passwordField.ownerDocument);
      docState._treatAsGeneratedPasswordField(passwordField);

      // Once the generated password was filled we no longer want to autocomplete
      // saved logins into a non-empty password field (see LoginAutoComplete.startSearch)
      // because it is confusing.
      this.#fieldsWithPasswordGenerationForcedOn.delete(passwordField);
    }

    this._maybeSendFormInteractionMessage(
      loginForm,
      "PasswordManager:onPasswordEditedOrGenerated",
      {
        targetField: passwordField,
        isSubmission: false,
        triggeredByFillingGenerated,
      }
    );
  }

  /**
   * Filter logins for exact origin/formActionOrigin and dedupe on usernamematche
   * @param {nsILoginInfo[]} logins an array of nsILoginInfo that could be
   *        used for the form, including ones with a different form action origin
   *        which are only used when the fill is userTriggered
   * @param {LoginForm} form
   */
  _filterForExactFormOriginLogins(logins, form) {
    let loginOrigin = lazy.LoginHelper.getLoginOrigin(
      form.ownerDocument.documentURI
    );
    let formActionOrigin = lazy.LoginHelper.getFormActionOrigin(form);
    logins = logins.filter(l => {
      let formActionMatches = lazy.LoginHelper.isOriginMatching(
        l.formActionOrigin,
        formActionOrigin,
        {
          schemeUpgrades: lazy.LoginHelper.schemeUpgrades,
          acceptWildcardMatch: true,
          acceptDifferentSubdomains: true,
        }
      );
      let formOriginMatches = lazy.LoginHelper.isOriginMatching(
        l.origin,
        loginOrigin,
        {
          schemeUpgrades: lazy.LoginHelper.schemeUpgrades,
          acceptWildcardMatch: true,
          acceptDifferentSubdomains: false,
        }
      );
      return formActionMatches && formOriginMatches;
    });

    // Since the logins are already filtered now to only match the origin and formAction,
    // dedupe to just the username since remaining logins may have different schemes.
    logins = lazy.LoginHelper.dedupeLogins(
      logins,
      ["username"],
      ["scheme", "timePasswordChanged"],
      loginOrigin,
      formActionOrigin
    );
    return logins;
  }

  /**
   * Attempt to find the username and password fields in a form, and fill them
   * in using the provided logins and recipes.
   *
   * @param {LoginForm} form
   * @param {nsILoginInfo[]} foundLogins an array of nsILoginInfo that could be
   *        used for the form, including ones with a different form action origin
   *        which are only used when the fill is userTriggered
   * @param {Set} recipes a set of recipes that could be used to affect how the
   *        form is filled
   * @param {Object} [options = {}] a list of options for this method
   * @param {HTMLInputElement} [options.inputElement = null] an optional target
   *        input element we want to fill
   * @param {bool} [options.autofillForm = false] denotes if we should fill the
   *        form in automatically
   * @param {bool} [options.clobberUsername = false] controls if an existing
   *        username can be overwritten. If this is false and an inputElement
   *        of type password is also passed, the username field will be ignored.
   *        If this is false and no inputElement is passed, if the username
   *        field value is not found in foundLogins, it will not fill the
   *        password.
   * @param {bool} [options.clobberPassword = false] controls if an existing
   *        password value can be overwritten
   * @param {bool} [options.userTriggered = false] an indication of whether
   *        this filling was triggered by the user
   */
  // eslint-disable-next-line complexity
  _fillForm(
    form,
    foundLogins,
    recipes,
    {
      inputElement = null,
      autofillForm = false,
      importable = null,
      clobberUsername = false,
      clobberPassword = false,
      userTriggered = false,
      style = null,
    } = {}
  ) {
    if (HTMLFormElement.isInstance(form)) {
      throw new Error("_fillForm should only be called with LoginForm objects");
    }

    lazy.log(`Found ${form.elements.length} form elements.`);
    // Will be set to one of AUTOFILL_RESULT in the `try` block.
    let autofillResult;
    const docState = this.stateForDocument(form.ownerDocument);

    // Heuristically determine what the user/pass fields are
    // We do this before checking to see if logins are stored,
    // so that the user isn't prompted for a primary password
    // without need.
    let { usernameField, newPasswordField: passwordField } =
      docState._getFormFields(form, false, recipes);

    const passwordACFieldName = passwordField?.getAutocompleteInfo().fieldName;

    let scenario;

    if (usernameField) {
      const isSignUpForm =
        lazy.FormScenarios.detect({
          input: usernameField,
          formRoot: form.rootElement,
        }).signUpForm ?? passwordACFieldName == "new-password";

      if (isSignUpForm) {
        scenario = new SignUpFormScenario(usernameField, passwordField);
      }

      if (scenario) {
        docState.setScenario(form.rootElement, scenario);
        this.markAsAutoCompletableField(usernameField);
      }
    }

    try {
      // Nothing to do if we have no matching (excluding form action
      // checks) logins available, and there isn't a need to show
      // the insecure form warning.
      if (
        !foundLogins.length &&
        !(importable?.state === "import" && importable?.browsers) &&
        lazy.InsecurePasswordUtils.isFormSecure(form)
      ) {
        // We don't log() here since this is a very common case.
        autofillResult = AUTOFILL_RESULT.NO_SAVED_LOGINS;
        return;
      }

      // If we have a password inputElement parameter and it's not
      // the same as the one heuristically found, use the parameter
      // one instead.
      if (inputElement) {
        if (inputElement.hasBeenTypePassword) {
          passwordField = inputElement;
          if (!clobberUsername) {
            usernameField = null;
          }
        } else if (lazy.LoginHelper.isUsernameFieldType(inputElement)) {
          usernameField = inputElement;
        } else {
          throw new Error("Unexpected input element type.");
        }
      }

      // Need a valid password or username field to do anything.
      if (passwordField == null && usernameField == null) {
        lazy.log("Not filling form, no password and username field found.");
        autofillResult = AUTOFILL_RESULT.NO_PASSWORD_FIELD;
        return;
      }

      // Attach autocomplete stuff to the username field, if we have
      // one. This is normally used to select from multiple accounts,
      // but even with one account we should refill if the user edits.
      // We would also need this attached to show the insecure login
      // warning, regardless of saved login.
      if (usernameField) {
        this.markAsAutoCompletableField(usernameField);
        usernameField.addEventListener("keydown", observer);
      }

      // If the password field is disabled or read-only, there's nothing to do.
      if (passwordField?.disabled || passwordField?.readOnly) {
        lazy.log("Not filling form, password field disabled or read-only.");
        autofillResult = AUTOFILL_RESULT.PASSWORD_DISABLED_READONLY;
        return;
      }

      if (
        !userTriggered &&
        !form.rootElement.ownerGlobal.windowGlobalChild.sameOriginWithTop
      ) {
        lazy.log("Not filling form; it is in a cross-origin subframe.");
        autofillResult = AUTOFILL_RESULT.FORM_IN_CROSSORIGIN_SUBFRAME;
        return;
      }

      if (!userTriggered) {
        // Only autofill logins that match the form's action and origin. In the above code
        // we have attached autocomplete for logins that don't match the form action.
        foundLogins = this._filterForExactFormOriginLogins(foundLogins, form);
      }

      // Nothing to do if we have no matching logins available.
      // Only insecure pages reach this block and logs the same
      // telemetry flag.
      if (!foundLogins.length) {
        // We don't log() here since this is a very common case.
        autofillResult = AUTOFILL_RESULT.NO_SAVED_LOGINS;
        return;
      }

      // Prevent autofilling insecure forms.
      if (
        !userTriggered &&
        !lazy.LoginHelper.insecureAutofill &&
        !lazy.InsecurePasswordUtils.isFormSecure(form)
      ) {
        lazy.log("Not filling form since it's insecure.");
        autofillResult = AUTOFILL_RESULT.INSECURE;
        return;
      }

      // Discard logins which have username/password values that don't
      // fit into the fields (as specified by the maxlength attribute).
      // The user couldn't enter these values anyway, and it helps
      // with sites that have an extra PIN to be entered (bug 391514)
      let maxUsernameLen = Number.MAX_VALUE;
      let maxPasswordLen = Number.MAX_VALUE;

      // If attribute wasn't set, default is -1.
      if (usernameField?.maxLength >= 0) {
        maxUsernameLen = usernameField.maxLength;
      }
      if (passwordField?.maxLength >= 0) {
        maxPasswordLen = passwordField.maxLength;
      }

      let logins = foundLogins.filter(function (l) {
        let fit =
          l.username.length <= maxUsernameLen &&
          l.password.length <= maxPasswordLen;
        if (!fit) {
          lazy.log(`Ignored login: won't fit ${l.username.length}.`);
        }

        return fit;
      }, this);

      if (!logins.length) {
        lazy.log("Form not filled, none of the logins fit in the field.");
        autofillResult = AUTOFILL_RESULT.NO_LOGINS_FIT;
        return;
      }

      if (passwordField) {
        if (!userTriggered && passwordField.type != "password") {
          // We don't want to autofill (without user interaction) into a field
          // that's unmasked.
          lazy.log(
            "Not autofilling, password field isn't currently type=password."
          );
          autofillResult = AUTOFILL_RESULT.TYPE_NO_LONGER_PASSWORD;
          return;
        }

        // If the password field has the autocomplete value of "new-password"
        // and we're autofilling without user interaction, there's nothing to do.
        if (!userTriggered && passwordACFieldName == "new-password") {
          lazy.log(
            "Not filling form, password field has the autocomplete new-password value."
          );
          autofillResult = AUTOFILL_RESULT.PASSWORD_AUTOCOMPLETE_NEW_PASSWORD;
          return;
        }

        // Don't clobber an existing password.
        if (passwordField.value && !clobberPassword) {
          lazy.log("Form not filled, the password field was already filled.");
          autofillResult = AUTOFILL_RESULT.EXISTING_PASSWORD;
          return;
        }
      }

      // Select a login to use for filling in the form.
      let selectedLogin;
      if (
        !clobberUsername &&
        usernameField &&
        (usernameField.value ||
          usernameField.disabled ||
          usernameField.readOnly)
      ) {
        // If username was specified in the field, it's disabled or it's readOnly, only fill in the
        // password if we find a matching login.
        let username = usernameField.value.toLowerCase();

        let matchingLogins = logins.filter(
          l => l.username.toLowerCase() == username
        );
        if (!matchingLogins.length) {
          lazy.log(
            "Password not filled. None of the stored logins match the username already present."
          );
          autofillResult = AUTOFILL_RESULT.EXISTING_USERNAME;
          return;
        }

        // If there are multiple, and one matches case, use it
        for (let l of matchingLogins) {
          if (l.username == usernameField.value) {
            selectedLogin = l;
          }
        }
        // Otherwise just use the first
        if (!selectedLogin) {
          selectedLogin = matchingLogins[0];
        }
      } else if (logins.length == 1) {
        selectedLogin = logins[0];
      } else {
        // We have multiple logins. Handle a special case here, for sites
        // which have a normal user+pass login *and* a password-only login
        // (eg, a PIN). Prefer the login that matches the type of the form
        // (user+pass or pass-only) when there's exactly one that matches.
        let matchingLogins;
        if (usernameField) {
          matchingLogins = logins.filter(l => l.username);
        } else {
          matchingLogins = logins.filter(l => !l.username);
        }

        if (matchingLogins.length != 1) {
          lazy.log("Multiple logins for form, so not filling any.");
          autofillResult = AUTOFILL_RESULT.MULTIPLE_LOGINS;
          return;
        }

        selectedLogin = matchingLogins[0];
      }

      // We will always have a selectedLogin at this point.

      if (!autofillForm) {
        lazy.log("autofillForms=false but form can be filled.");
        autofillResult = AUTOFILL_RESULT.NO_AUTOFILL_FORMS;
        return;
      }

      if (
        !userTriggered &&
        passwordACFieldName == "off" &&
        !lazy.LoginHelper.autofillAutocompleteOff
      ) {
        lazy.log(
          "Not autofilling the login because we're respecting autocomplete=off."
        );
        autofillResult = AUTOFILL_RESULT.AUTOCOMPLETE_OFF;
        return;
      }

      // Fill the form

      let willAutofill =
        usernameField || passwordField.value != selectedLogin.password;
      if (willAutofill) {
        let autoFilledLogin = {
          guid: selectedLogin.QueryInterface(Ci.nsILoginMetaInfo).guid,
          username: selectedLogin.username,
          usernameField: usernameField
            ? Cu.getWeakReference(usernameField)
            : null,
          password: selectedLogin.password,
          passwordField: passwordField
            ? Cu.getWeakReference(passwordField)
            : null,
        };
        // Ensure the state is updated before setUserInput is called.
        lazy.log(
          "Saving autoFilledLogin",
          autoFilledLogin.guid,
          "for",
          form.rootElement
        );
        docState.fillsByRootElement.set(form.rootElement, {
          login: autoFilledLogin,
          userTriggered,
        });
      }
      if (usernameField) {
        // Don't modify the username field because the user wouldn't be able to change it either.
        let disabledOrReadOnly =
          usernameField.disabled || usernameField.readOnly;

        if (selectedLogin.username && !disabledOrReadOnly) {
          let userNameDiffers = selectedLogin.username != usernameField.value;
          // Don't replace the username if it differs only in case, and the user triggered
          // this autocomplete. We assume that if it was user-triggered the entered text
          // is desired.
          let userEnteredDifferentCase =
            userTriggered &&
            userNameDiffers &&
            usernameField.value.toLowerCase() ==
              selectedLogin.username.toLowerCase();

          if (!userEnteredDifferentCase && userNameDiffers) {
            usernameField.setUserInput(selectedLogin.username);
          }
          LoginFormState._highlightFilledField(usernameField);
        }
      }

      if (passwordField) {
        if (passwordField.value != selectedLogin.password) {
          // Ensure the field gets re-masked in case a generated password was
          // filled into it previously.
          docState._stopTreatingAsGeneratedPasswordField(passwordField);

          passwordField.setUserInput(selectedLogin.password);
        }

        LoginFormState._highlightFilledField(passwordField);
      }

      if (style === "generatedPassword") {
        this.filledWithGeneratedPassword(passwordField);
      }

      lazy.log("_fillForm succeeded");
      if (passwordField) {
        autofillResult = AUTOFILL_RESULT.FILLED;
      } else if (usernameField) {
        autofillResult = AUTOFILL_RESULT.FILLED_USERNAME_ONLY_FORM;
      }
    } catch (ex) {
      console.error(ex);
      throw ex;
    } finally {
      if (!autofillResult) {
        // eslint-disable-next-line no-unsafe-finally
        throw new Error("_fillForm: autofillResult must be specified");
      }

      if (!userTriggered) {
        // Ignore fills as a result of user action for this probe.

        lazy.LoginManagerTelemetry.recordAutofillResult(autofillResult);

        if (usernameField) {
          let focusedElement = lazy.gFormFillService.focusedInput;
          if (
            usernameField == focusedElement &&
            ![
              AUTOFILL_RESULT.FILLED,
              AUTOFILL_RESULT.FILLED_USERNAME_ONLY_FORM,
            ].includes(autofillResult)
          ) {
            lazy.log(
              "Opening username autocomplete popup since the form wasn't autofilled."
            );
            lazy.gFormFillService.showPopup();
          }
        }
      }

      if (usernameField) {
        lazy.log("Attaching event listeners to usernameField.");
        usernameField.addEventListener("focus", observer);
        usernameField.addEventListener("mousedown", observer);
      }

      this.sendAsyncMessage("PasswordManager:formProcessed", {
        formid: form.rootElement.id,
        autofillResult,
      });
    }
  }

  getScenario(inputElement) {
    const docState = this.stateForDocument(inputElement.ownerDocument);
    return docState.getScenario(inputElement);
  }

  #interestedInputs = [];

  markAsAutoCompletableField(input) {
    this.#interestedInputs.push(input);
    this.manager
      .getActor("AutoComplete")
      ?.markAsAutoCompletableField(input, this);
  }

  get actorName() {
    return "LoginManager";
  }

  /**
   * Get the search options when searching for autocomplete entries in the parent
   *
   * @param {HTMLInputElement} input - The input element to search for autocomplete entries
   * @returns {object} the search options for the input
   */
  getAutoCompleteSearchOption(input, searchString) {
    const form = lazy.LoginFormFactory.createFromField(input);
    const formOrigin = lazy.LoginHelper.getLoginOrigin(
      input.ownerDocument.documentURI
    );
    const actionOrigin = lazy.LoginHelper.getFormActionOrigin(form);
    const autocompleteInfo = input.getAutocompleteInfo();
    const hasBeenTypePassword = input.hasBeenTypePassword;

    let forcePasswordGeneration = false;
    let isProbablyANewPasswordField = false;
    if (hasBeenTypePassword) {
      forcePasswordGeneration = this.isPasswordGenerationForcedOn(input);
      // Run the Fathom model only if the password field does not have the
      // autocomplete="new-password" attribute.
      isProbablyANewPasswordField =
        autocompleteInfo.fieldName == "new-password" ||
        this.isProbablyANewPasswordField(input);
    }

    const scenarioName = lazy.FormScenarios.detect({ input }).signUpForm
      ? "SignUpFormScenario"
      : "";

    const r = {
      formOrigin,
      actionOrigin,
      searchString,
      forcePasswordGeneration,
      hasBeenTypePassword,
      isProbablyANewPasswordField,
      scenarioName,
      inputMaxLength: input.maxLength,
      isWebAuthn: this.#isWebAuthnCredentials(autocompleteInfo),
    };
    return r;
  }

  #searchStartTimeMS = null;

  /**
   * Ask the provider whether it might have autocomplete entry to show
   * for the given input.
   *
   * @param {HTMLInputElement} input - The input element to search for autocomplete entries
   * @returns {boolean} true if we shold search for autocomplete entries
   */
  shouldSearchForAutoComplete(input, searchString) {
    this.#searchStartTimeMS = Services.telemetry.msSystemNow();

    // Don't search login storage when the field has a null principal as we don't want to fill
    // logins for the `location` in this case.
    if (input.nodePrincipal.isNullPrincipal) {
      return false;
    }

    // Return empty result on password fields with password already filled,
    // unless password generation was forced.
    if (
      input.hasBeenTypePassword &&
      searchString &&
      !this.isPasswordGenerationForcedOn(input)
    ) {
      return false;
    }

    if (!lazy.LoginHelper.enabled) {
      return false;
    }

    return true;
  }

  /**
   * Convert the search result to autocomplete results
   *
   * @param {string} searchString - The string to search for
   * @param {HTMLInputElement} input - The input element to search for autocomplete entries
   * @param {Array<object>} records - autocomplete records
   * @returns {AutocompleteResult}
   */
  searchResultToAutoCompleteResult(searchString, input, records) {
    if (
      input.nodePrincipal.schemeIs("about") ||
      input.nodePrincipal.isSystemPrincipal
    ) {
      // Don't show autocomplete results for about: pages.
      return null;
    }

    let {
      generatedPassword,
      autocompleteItems,
      importable,
      logins,
      willAutoSaveGeneratedPassword,
    } = records ?? {};
    logins ||= [];

    const formOrigin = lazy.LoginHelper.getLoginOrigin(
      input.ownerDocument.documentURI
    );

    const isNullPrincipal = input.nodePrincipal.isNullPrincipal;
    const form = lazy.LoginFormFactory.createFromField(input);
    const isSecure =
      !isNullPrincipal && lazy.InsecurePasswordUtils.isFormSecure(form);

    const telemetryEventData = {
      acFieldName: input.getAutocompleteInfo().fieldName,
      //hadPrevious: !!aPreviousResult,
      hadPrevious: false,
      typeWasPassword: input.hasBeenTypePassword,
      fieldType: input.type,
      searchStartTimeMS: this.#searchStartTimeMS,
      stringLength: searchString.length,
    };

    const acResult = new lazy.LoginAutoCompleteResult(
      searchString,
      lazy.LoginHelper.vanillaObjectsToLogins(logins),
      autocompleteItems,
      formOrigin,
      {
        generatedPassword,
        willAutoSaveGeneratedPassword,
        importable,
        actor: this,
        isSecure,
        hasBeenTypePassword: input.hasBeenTypePassword,
        hostname: input.ownerDocument.documentURIObject.host,
        telemetryEventData,
      }
    );
    return acResult;
  }

  isLoginManagerField(input) {
    return input.hasBeenTypePassword || this.#interestedInputs.includes(input);
  }

  #cachedNewPasswordScore = new WeakMap();

  isProbablyANewPasswordField(inputElement) {
    const threshold = lazy.LoginHelper.generationConfidenceThreshold;
    if (threshold == -1) {
      // Fathom is disabled
      return false;
    }

    let score = this.#cachedNewPasswordScore.get(inputElement);
    if (score) {
      return score >= threshold;
    }

    const { rules, type } = lazy.NewPasswordModel;
    const results = rules.against(inputElement);
    score = results.get(inputElement).scoreFor(type);
    this.#cachedNewPasswordScore.set(inputElement, score);
    return score >= threshold;
  }

  /**
   * @param {string} autocompleteInfo
   * @returns whether the non-autofill credential type (https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#non-autofill-credential-type)
   * of the input field is "webauthn"
   */
  #isWebAuthnCredentials(autocompleteInfo) {
    return autocompleteInfo.credentialType == "webauthn";
  }
}
PK
!<�^^'modules/LoginManagerContextMenu.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
});

/**
 * Password manager object for the browser contextual menu.
 */
export const LoginManagerContextMenu = {
  /**
   * Look for login items and add them to the contextual menu.
   *
   * @param {Object} inputElementIdentifier
   *        An identifier generated for the input element via ContentDOMReference.
   * @param {xul:browser} browser
   *        The browser for the document the context menu was open on.
   * @param {string} formOrigin
   *        The origin of the document that the context menu was activated from.
   *        This isn't the same as the browser's top-level document origin
   *        when subframes are involved.
   * @returns {DocumentFragment} a document fragment with all the login items.
   */
  addLoginsToMenu(inputElementIdentifier, browser, formOrigin) {
    let foundLogins = this._findLogins(formOrigin);

    if (!foundLogins.length) {
      return null;
    }

    let fragment = browser.ownerDocument.createDocumentFragment();
    let duplicateUsernames = this._findDuplicates(foundLogins);
    for (let login of foundLogins) {
      let item = fragment.ownerDocument.createXULElement("menuitem");

      let username = login.username;
      // If login is empty or duplicated we want to append a modification date to it.
      if (!username || duplicateUsernames.has(username)) {
        if (!username) {
          username = this._getLocalizedString("noUsername");
        }
        let meta = login.QueryInterface(Ci.nsILoginMetaInfo);
        let time = this.dateAndTimeFormatter.format(
          new Date(meta.timePasswordChanged)
        );
        username = this._getLocalizedString("loginHostAge", [username, time]);
      }
      item.setAttribute("label", username);
      item.setAttribute("class", "context-login-item");

      // login is bound so we can keep the reference to each object.
      item.addEventListener(
        "command",
        function (login, _event) {
          this._fillTargetField(
            login,
            inputElementIdentifier,
            browser,
            formOrigin
          );
        }.bind(this, login)
      );

      fragment.appendChild(item);
    }

    return fragment;
  },

  /**
   * Undoes the work of addLoginsToMenu for the same menu.
   *
   * @param {Document}
   *        The context menu owner document.
   */
  clearLoginsFromMenu(document) {
    let loginItems = document.getElementsByClassName("context-login-item");
    while (loginItems.item(0)) {
      loginItems.item(0).remove();
    }
  },

  /**
   * Show the password autocomplete UI with the generation option forced to appear.
   */
  async useGeneratedPassword(inputElementIdentifier) {
    let browsingContextId = inputElementIdentifier.browsingContextId;
    let browsingContext = BrowsingContext.get(browsingContextId);
    let actor = browsingContext.currentWindowGlobal.getActor("LoginManager");

    actor.sendAsyncMessage("PasswordManager:useGeneratedPassword", {
      inputElementIdentifier,
    });
  },

  /**
   * Find logins for the specified origin..
   *
   * @param {string} formOrigin
   *        Origin of the logins we want to find that has be sanitized by `getLoginOrigin`.
   *        This isn't the same as the browser's top-level document URI
   *        when subframes are involved.
   *
   * @returns {nsILoginInfo[]} a login list
   */
  _findLogins(formOrigin) {
    let searchParams = {
      origin: formOrigin,
      schemeUpgrades: lazy.LoginHelper.schemeUpgrades,
    };
    let logins = lazy.LoginHelper.searchLoginsWithObject(searchParams);
    let resolveBy = ["scheme", "timePasswordChanged"];
    logins = lazy.LoginHelper.dedupeLogins(
      logins,
      ["username", "password"],
      resolveBy,
      formOrigin
    );

    // Sort logins in alphabetical order and by date.
    logins.sort((loginA, loginB) => {
      // Sort alphabetically
      let result = loginA.username.localeCompare(loginB.username);
      if (result) {
        // Forces empty logins to be at the end
        if (!loginA.username) {
          return 1;
        }
        if (!loginB.username) {
          return -1;
        }
        return result;
      }

      // Same username logins are sorted by last change date
      let metaA = loginA.QueryInterface(Ci.nsILoginMetaInfo);
      let metaB = loginB.QueryInterface(Ci.nsILoginMetaInfo);
      return metaB.timePasswordChanged - metaA.timePasswordChanged;
    });

    return logins;
  },

  /**
   * Find duplicate usernames in a login list.
   *
   * @param {nsILoginInfo[]} loginList
   *        A list of logins we want to look for duplicate usernames.
   *
   * @returns {Set} a set with the duplicate usernames.
   */
  _findDuplicates(loginList) {
    let seen = new Set();
    let duplicates = new Set();
    for (let login of loginList) {
      if (seen.has(login.username)) {
        duplicates.add(login.username);
      }
      seen.add(login.username);
    }
    return duplicates;
  },

  /**
   * @param {nsILoginInfo} login
   *        The login we want to fill the form with.
   * @param {Object} inputElementIdentifier
   *        An identifier generated for the input element via ContentDOMReference.
   * @param {xul:browser} browser
   *        The target tab browser.
   * @param {string} formOrigin
   *        Origin of the document we're filling after sanitization via
   *        `getLoginOrigin`.
   *        This isn't the same as the browser's top-level
   *        origin when subframes are involved.
   */
  _fillTargetField(login, inputElementIdentifier, browser, formOrigin) {
    let browsingContextId = inputElementIdentifier.browsingContextId;
    let browsingContext = BrowsingContext.get(browsingContextId);
    if (!browsingContext) {
      return;
    }

    let actor = browsingContext.currentWindowGlobal.getActor("LoginManager");
    if (!actor) {
      return;
    }

    actor
      .fillForm({
        browser,
        inputElementIdentifier,
        loginFormOrigin: formOrigin,
        login,
      })
      .catch(console.error);
  },

  /**
   * @param {string} key
   *        The localized string key
   * @param {string[]} formatArgs
   *        An array of formatting argument string
   *
   * @returns {string} the localized string for the specified key,
   *          formatted with arguments if required.
   */
  _getLocalizedString(key, formatArgs) {
    if (formatArgs) {
      return this._stringBundle.formatStringFromName(key, formatArgs);
    }
    return this._stringBundle.GetStringFromName(key);
  },
};

ChromeUtils.defineLazyGetter(
  LoginManagerContextMenu,
  "_stringBundle",
  function () {
    return Services.strings.createBundle(
      "chrome://passwordmgr/locale/passwordmgr.properties"
    );
  }
);

ChromeUtils.defineLazyGetter(
  LoginManagerContextMenu,
  "dateAndTimeFormatter",
  function () {
    return new Services.intl.DateTimeFormat(undefined, {
      dateStyle: "medium",
    });
  }
);
PK
!<�[U
�
�"modules/LoginManagerParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { FirefoxRelayTelemetry } from "resource://gre/modules/FirefoxRelayTelemetry.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const LoginInfo = new Components.Constructor(
  "@mozilla.org/login-manager/loginInfo;1",
  Ci.nsILoginInfo,
  "init"
);

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "LoginRelatedRealmsParent", () => {
  const { LoginRelatedRealmsParent } = ChromeUtils.importESModule(
    "resource://gre/modules/LoginRelatedRealms.sys.mjs"
  );
  return new LoginRelatedRealmsParent();
});

ChromeUtils.defineLazyGetter(lazy, "PasswordRulesManager", () => {
  const { PasswordRulesManagerParent } = ChromeUtils.importESModule(
    "resource://gre/modules/PasswordRulesManager.sys.mjs"
  );
  return new PasswordRulesManagerParent();
});

ChromeUtils.defineESModuleGetters(lazy, {
  ChromeMigrationUtils: "resource:///modules/ChromeMigrationUtils.sys.mjs",
  FirefoxRelay: "resource://gre/modules/FirefoxRelay.sys.mjs",
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
  MigrationUtils: "resource:///modules/MigrationUtils.sys.mjs",
  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
  WebAuthnFeature: "resource://gre/modules/WebAuthnFeature.sys.mjs",
  PasswordGenerator: "resource://gre/modules/shared/PasswordGenerator.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "prompterSvc",
  "@mozilla.org/login-manager/prompter;1",
  Ci.nsILoginManagerPrompter
);

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  let logger = lazy.LoginHelper.createLogger("LoginManagerParent");
  return logger.log.bind(logger);
});
ChromeUtils.defineLazyGetter(lazy, "debug", () => {
  let logger = lazy.LoginHelper.createLogger("LoginManagerParent");
  return logger.debug.bind(logger);
});

/**
 * A listener for notifications to tests.
 */
let gListenerForTests = null;

/**
 * A map of a principal's origin (including suffixes) to a generated password string and filled flag
 * so that we can offer the same password later (e.g. in a confirmation field).
 *
 * We don't currently evict from this cache so entries should last until the end of the browser
 * session. That may change later but for now a typical session would max out at a few entries.
 */
let gGeneratedPasswordsByPrincipalOrigin = new Map();

/**
 * Reference to the default LoginRecipesParent (instead of the initialization promise) for
 * synchronous access. This is a temporary hack and new consumers should yield on
 * recipeParentPromise instead.
 *
 * @type LoginRecipesParent
 * @deprecated
 */
let gRecipeManager = null;

/**
 * Tracks the last time the user cancelled the primary password prompt,
 *  to avoid spamming primary password prompts on autocomplete searches.
 */
let gLastMPLoginCancelled = Number.NEGATIVE_INFINITY;

let gGeneratedPasswordObserver = {
  addedObserver: false,

  observe(subject, topic, data) {
    if (topic == "last-pb-context-exited") {
      // The last private browsing context closed so clear all cached generated
      // passwords for private window origins.
      for (let principalOrigin of gGeneratedPasswordsByPrincipalOrigin.keys()) {
        let principal =
          Services.scriptSecurityManager.createContentPrincipalFromOrigin(
            principalOrigin
          );
        if (!principal.privateBrowsingId) {
          // The origin isn't for a private context so leave it alone.
          continue;
        }
        gGeneratedPasswordsByPrincipalOrigin.delete(principalOrigin);
      }
      return;
    }

    // We cache generated passwords in gGeneratedPasswordsByPrincipalOrigin.
    // When generated password used on the page,
    // we store a login with generated password and without username.
    // When user updates that autosaved login with username,
    // we must clear cached generated password.
    // This will generate a new password next time user needs it.
    if (topic == "passwordmgr-storage-changed" && data == "modifyLogin") {
      const originalLogin = subject.GetElementAt(0);
      const updatedLogin = subject.GetElementAt(1);

      if (originalLogin && !originalLogin.username && updatedLogin?.username) {
        const generatedPassword = gGeneratedPasswordsByPrincipalOrigin.get(
          originalLogin.origin
        );

        if (
          originalLogin.password == generatedPassword.value &&
          updatedLogin.password == generatedPassword.value
        ) {
          gGeneratedPasswordsByPrincipalOrigin.delete(originalLogin.origin);
        }
      }
    }

    if (
      topic == "passwordmgr-autosaved-login-merged" ||
      (topic == "passwordmgr-storage-changed" && data == "removeLogin")
    ) {
      let { origin, guid } = subject;
      let generatedPW = gGeneratedPasswordsByPrincipalOrigin.get(origin);

      // in the case where an autosaved login removed or merged into an existing login,
      // clear the guid associated with the generated-password cache entry
      if (
        generatedPW &&
        (guid == generatedPW.storageGUID ||
          topic == "passwordmgr-autosaved-login-merged")
      ) {
        lazy.log(
          `Removing storageGUID for generated-password cache entry on origin: ${origin}.`
        );
        generatedPW.storageGUID = null;
      }
    }
  },
};

Services.ppmm.addMessageListener("PasswordManager:findRecipes", message => {
  let formHost = new URL(message.data.formOrigin).host;
  return gRecipeManager?.getRecipesForHost(formHost) ?? [];
});

/**
 * Lazily create a Map of origins to array of browsers with importable logins.
 *
 * @param {origin} formOrigin
 * @returns {Object?} containing array of migration browsers and experiment state.
 */
async function getImportableLogins(formOrigin) {
  // Include the experiment state for data and UI decisions; otherwise skip
  // importing if not supported or disabled.
  const state =
    lazy.LoginHelper.suggestImportCount > 0 &&
    lazy.LoginHelper.showAutoCompleteImport;
  return state
    ? {
        browsers: await lazy.ChromeMigrationUtils.getImportableLogins(
          formOrigin
        ),
        state,
      }
    : null;
}

export class LoginManagerParent extends JSWindowActorParent {
  possibleValues = {
    // This is stored at the parent (i.e., frame) scope because the LoginManagerPrompter
    // is shared across all frames.
    //
    // It is mutated to update values without forcing us to set a new doorhanger.
    usernames: new Set(),
    passwords: new Set(),
  };

  // This is used by tests to listen to form submission.
  static setListenerForTests(listener) {
    gListenerForTests = listener;
  }

  // Used by tests to clean up recipes only when they were actually used.
  static get _recipeManager() {
    return gRecipeManager;
  }

  // Some unit tests need to access this.
  static getGeneratedPasswordsByPrincipalOrigin() {
    return gGeneratedPasswordsByPrincipalOrigin;
  }

  getRootBrowser() {
    let browsingContext = null;
    if (this._overrideBrowsingContextId) {
      browsingContext = BrowsingContext.get(this._overrideBrowsingContextId);
    } else {
      browsingContext = this.browsingContext.top;
    }
    return browsingContext.embedderElement;
  }

  #origin = null;
  get origin() {
    if (!this.#origin) {
      this.#origin = lazy.LoginHelper.getLoginOrigin(
        this.manager.documentPrincipal?.originNoSuffix
      );
    }
    return this.#origin;
  }

  /**
   * @param {origin} formOrigin
   * @param {object} options
   * @param {origin?} options.formActionOrigin To match on. Omit this argument to match all action origins.
   * @param {origin?} options.httpRealm To match on. Omit this argument to match all realms.
   * @param {boolean} options.acceptDifferentSubdomains Include results for eTLD+1 matches
   * @param {boolean} options.ignoreActionAndRealm Include all form and HTTP auth logins for the site
   * @param {string[]} options.relatedRealms Related realms to match against when searching
   */
  static async searchAndDedupeLogins(
    formOrigin,
    {
      acceptDifferentSubdomains,
      formActionOrigin,
      httpRealm,
      ignoreActionAndRealm,
      relatedRealms,
    } = {}
  ) {
    let logins;
    let matchData = {
      origin: formOrigin,
      schemeUpgrades: lazy.LoginHelper.schemeUpgrades,
      acceptDifferentSubdomains,
    };
    if (!ignoreActionAndRealm) {
      if (typeof formActionOrigin != "undefined") {
        matchData.formActionOrigin = formActionOrigin;
      } else if (typeof httpRealm != "undefined") {
        matchData.httpRealm = httpRealm;
      }
    }
    if (lazy.LoginHelper.relatedRealmsEnabled) {
      matchData.acceptRelatedRealms = lazy.LoginHelper.relatedRealmsEnabled;
      matchData.relatedRealms = relatedRealms;
    }
    try {
      logins = await Services.logins.searchLoginsAsync(matchData);
    } catch (e) {
      // Record the last time the user cancelled the MP prompt
      // to avoid spamming them with MP prompts for autocomplete.
      if (e.result == Cr.NS_ERROR_ABORT) {
        lazy.log("User cancelled primary password prompt.");
        gLastMPLoginCancelled = Date.now();
        return [];
      }
      throw e;
    }

    logins = lazy.LoginHelper.shadowHTTPLogins(logins);

    let resolveBy = [
      "subdomain",
      "actionOrigin",
      "scheme",
      "timePasswordChanged",
    ];
    return lazy.LoginHelper.dedupeLogins(
      logins,
      ["username", "password"],
      resolveBy,
      formOrigin,
      formActionOrigin
    );
  }

  async receiveMessage(msg) {
    let data = msg.data;
    if (data.origin || data.formOrigin) {
      throw new Error(
        "The child process should not send an origin to the parent process. See bug 1513003"
      );
    }

    switch (msg.name) {
      // Called by the preference
      case "PasswordManager:OpenPreferences": {
        this.#onOpenPreferences(data.hostname, data.entryPoint);
        break;
      }

      case "PasswordManager:updateDoorhangerSuggestions": {
        this.#onUpdateDoorhangerSuggestions(data.possibleValues);
        break;
      }

      case "PasswordManager:decreaseSuggestImportCount": {
        this.decreaseSuggestImportCount(data);
        break;
      }

      case "PasswordManager:findLogins": {
        return this.sendLoginDataToChild(
          this.origin,
          data.actionOrigin,
          data.options
        );
      }

      case "PasswordManager:onFormSubmit": {
        this.#onFormSubmit();
        break;
      }

      case "PasswordManager:onPasswordEditedOrGenerated": {
        this.#onPasswordEditedOrGenerated(data);
        break;
      }

      case "PasswordManager:onIgnorePasswordEdit": {
        this.#onIgnorePasswordEdit();
        break;
      }

      case "PasswordManager:ShowDoorhanger": {
        this.#onShowDoorhanger(data);
        break;
      }

      case "PasswordManager:autoCompleteLogins": {
        return this.doAutocompleteSearch(this.origin, data);
      }

      case "PasswordManager:removeLogin": {
        this.#onRemoveLogin(data.login);
        break;
      }

      // Used by tests to detect that a form-fill has occurred. This redirects
      // to the top-level browsing context.
      case "PasswordManager:formProcessed": {
        this.#onFormProcessed(data.formid, data.autofillResult);
        break;
      }
    }

    return undefined;
  }

  #onUpdateDoorhangerSuggestions(possibleValues) {
    this.possibleValues.usernames = possibleValues.usernames;
    this.possibleValues.passwords = possibleValues.passwords;
  }

  #onFormSubmit() {
    Services.obs.notifyObservers(
      null,
      "passwordmgr-form-submission-detected",
      this.origin
    );
  }

  #onPasswordEditedOrGenerated(data) {
    lazy.log("#onPasswordEditedOrGenerated: Received PasswordManager.");
    if (gListenerForTests) {
      lazy.log("#onPasswordEditedOrGenerated: Calling gListenerForTests.");
      gListenerForTests("PasswordEditedOrGenerated", {});
    }
    let browser = this.getRootBrowser();
    this._onPasswordEditedOrGenerated(browser, this.origin, data);
  }

  #onIgnorePasswordEdit() {
    lazy.log("#onIgnorePasswordEdit: Received PasswordManager.");
    if (gListenerForTests) {
      lazy.log("#onIgnorePasswordEdit: Calling gListenerForTests.");
      gListenerForTests("PasswordIgnoreEdit", {});
    }
  }

  #onShowDoorhanger(data) {
    const browser = this.getRootBrowser();
    const submitPromise = this.showDoorhanger(browser, this.origin, data);
    if (gListenerForTests) {
      submitPromise.then(() => {
        gListenerForTests("ShowDoorhanger", {
          origin: this.origin,
          data,
        });
      });
    }
  }

  #onRemoveLogin(login) {
    login = lazy.LoginHelper.vanillaObjectToLogin(login);
    Services.logins.removeLogin(login);
  }

  #onOpenImportableLearnMore() {
    const window = this.getRootBrowser().ownerGlobal;
    window.openTrustedLinkIn(
      Services.urlFormatter.formatURLPref("app.support.baseURL") +
        "password-import",
      "tab",
      { relatedToCurrent: true }
    );
  }

  async #onHandleImportable(browserId) {
    // Directly migrate passwords for a single profile.
    const migrator = await lazy.MigrationUtils.getMigrator(browserId);
    const profiles = await migrator.getSourceProfiles();
    if (
      profiles.length == 1 &&
      lazy.NimbusFeatures["password-autocomplete"].getVariable(
        "directMigrateSingleProfile"
      )
    ) {
      const loginAdded = new Promise(resolve => {
        const obs = (_subject, _topic, data) => {
          if (data == "addLogin") {
            Services.obs.removeObserver(obs, "passwordmgr-storage-changed");
            resolve();
          }
        };
        Services.obs.addObserver(obs, "passwordmgr-storage-changed");
      });

      await migrator.migrate(
        lazy.MigrationUtils.resourceTypes.PASSWORDS,
        null,
        profiles[0]
      );
      await loginAdded;

      // Reshow the popup with the imported password.
      this.sendAsyncMessage("PasswordManager:repopulateAutocompletePopup");
    } else {
      // Open the migration wizard pre-selecting the appropriate browser.
      lazy.MigrationUtils.showMigrationWizard(
        this.getRootBrowser().ownerGlobal,
        {
          entrypoint: lazy.MigrationUtils.MIGRATION_ENTRYPOINTS.PASSWORDS,
          migratorKey: browserId,
        }
      );
    }
  }

  #onOpenPreferences(hostname, entryPoint) {
    const window = this.getRootBrowser().ownerGlobal;
    lazy.LoginHelper.openPasswordManager(window, {
      filterString: hostname,
      entryPoint,
    });
  }

  #onFormProcessed(formid, autofillResult) {
    const topActor =
      this.browsingContext.currentWindowGlobal.getActor("LoginManager");
    topActor.sendAsyncMessage("PasswordManager:formProcessed", { formid });
    if (gListenerForTests) {
      gListenerForTests("FormProcessed", {
        browsingContext: this.browsingContext,
        data: {
          formId: formid,
          autofillResult,
        },
      });
    }
  }

  async #offerRelayIntegration(origin) {
    const browser = lazy.LoginHelper.getBrowserForPrompt(this.getRootBrowser());
    return lazy.FirefoxRelay.offerRelayIntegration(browser, origin);
  }

  async #generateRelayUsername(origin) {
    const browser = lazy.LoginHelper.getBrowserForPrompt(this.getRootBrowser());
    return lazy.FirefoxRelay.generateUsername(browser, origin);
  }

  async #promptForAuthenticator(selection) {
    const browser = lazy.LoginHelper.getBrowserForPrompt(this.getRootBrowser());
    return lazy.WebAuthnFeature.promptForAuthenticator(browser, selection);
  }

  /**
   * Update the remaining number of import suggestion impressions with debounce
   * to allow multiple popups showing the "same" items to count as one.
   */
  decreaseSuggestImportCount(count) {
    // Delay an existing timer with a potentially larger count.
    if (this._suggestImportTimer) {
      this._suggestImportTimer.delay =
        LoginManagerParent.SUGGEST_IMPORT_DEBOUNCE_MS;
      this._suggestImportCount = Math.max(count, this._suggestImportCount);
      return;
    }

    this._suggestImportTimer = Cc["@mozilla.org/timer;1"].createInstance(
      Ci.nsITimer
    );
    this._suggestImportTimer.init(
      () => {
        this._suggestImportTimer = null;
        Services.prefs.setIntPref(
          "signon.suggestImportCount",
          lazy.LoginHelper.suggestImportCount - this._suggestImportCount
        );
      },
      LoginManagerParent.SUGGEST_IMPORT_DEBOUNCE_MS,
      Ci.nsITimer.TYPE_ONE_SHOT
    );
    this._suggestImportCount = count;
  }

  async #getRecipesForHost(origin) {
    let recipes;
    if (origin) {
      try {
        const formHost = new URL(origin).host;
        let recipeManager = await LoginManagerParent.recipeParentPromise;
        recipes = recipeManager.getRecipesForHost(formHost);
      } catch (ex) {
        // Some schemes e.g. chrome aren't supported by URL
      }
    }

    return recipes ?? [];
  }

  /**
   * Trigger a login form fill and send relevant data (e.g. logins and recipes)
   * to the child process (LoginManagerChild).
   */
  async fillForm({
    browser,
    loginFormOrigin,
    login,
    inputElementIdentifier,
    style,
  }) {
    const recipes = await this.#getRecipesForHost(loginFormOrigin);

    // Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
    // doesn't support structured cloning.
    const jsLogins = [lazy.LoginHelper.loginToVanillaObject(login)];

    const browserURI = browser.currentURI.spec;
    const originMatches =
      lazy.LoginHelper.getLoginOrigin(browserURI) == loginFormOrigin;

    this.sendAsyncMessage("PasswordManager:fillForm", {
      inputElementIdentifier,
      loginFormOrigin,
      originMatches,
      logins: jsLogins,
      recipes,
      style,
    });
  }

  /**
   * Send relevant data (e.g. logins and recipes) to the child process (LoginManagerChild).
   */
  async sendLoginDataToChild(
    formOrigin,
    actionOrigin,
    { guid, showPrimaryPassword }
  ) {
    const recipes = await this.#getRecipesForHost(formOrigin);

    if (!showPrimaryPassword && !Services.logins.isLoggedIn) {
      return { logins: [], recipes };
    }

    // If we're currently displaying a primary password prompt, defer
    // processing this form until the user handles the prompt.
    if (Services.logins.uiBusy) {
      lazy.log(
        "UI is busy. Deferring sendLoginDataToChild for form: ",
        formOrigin
      );

      let uiBusyPromiseResolve;
      const uiBusyPromise = new Promise(resolve => {
        uiBusyPromiseResolve = resolve;
      });

      const self = this;
      const observer = {
        QueryInterface: ChromeUtils.generateQI([
          "nsIObserver",
          "nsISupportsWeakReference",
        ]),

        observe(_subject, topic, _data) {
          lazy.log("Got deferred sendLoginDataToChild notification:", topic);
          // Only run observer once.
          Services.obs.removeObserver(this, "passwordmgr-crypto-login");
          Services.obs.removeObserver(this, "passwordmgr-crypto-loginCanceled");
          if (topic == "passwordmgr-crypto-loginCanceled") {
            uiBusyPromiseResolve({ logins: [], recipes });
            return;
          }

          const result = self.sendLoginDataToChild(formOrigin, actionOrigin, {
            showPrimaryPassword,
          });
          uiBusyPromiseResolve(result);
        },
      };

      // Possible leak: it's possible that neither of these notifications
      // will fire, and if that happens, we'll leak the observer (and
      // never return). We should guarantee that at least one of these
      // will fire.
      // See bug XXX.
      Services.obs.addObserver(observer, "passwordmgr-crypto-login");
      Services.obs.addObserver(observer, "passwordmgr-crypto-loginCanceled");

      return uiBusyPromise;
    }

    // Autocomplete results do not need to match actionOrigin or exact origin.
    let logins = null;
    if (guid) {
      logins = await Services.logins.searchLoginsAsync({
        guid,
        origin: formOrigin,
      });
    } else {
      let relatedRealmsOrigins = [];
      if (lazy.LoginHelper.relatedRealmsEnabled) {
        relatedRealmsOrigins =
          await lazy.LoginRelatedRealmsParent.findRelatedRealms(formOrigin);
      }
      logins = await LoginManagerParent.searchAndDedupeLogins(formOrigin, {
        formActionOrigin: actionOrigin,
        ignoreActionAndRealm: true,
        acceptDifferentSubdomains:
          lazy.LoginHelper.includeOtherSubdomainsInLookup,
        relatedRealms: relatedRealmsOrigins,
      });

      if (lazy.LoginHelper.relatedRealmsEnabled) {
        lazy.debug(
          "Adding related logins on page load",
          logins.map(l => l.origin)
        );
      }
    }
    lazy.log(`Deduped ${logins.length} logins.`);
    // Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
    // doesn't support structured cloning.
    let jsLogins = lazy.LoginHelper.loginsToVanillaObjects(logins);
    return {
      importable: await getImportableLogins(formOrigin),
      logins: jsLogins,
      recipes,
    };
  }

  async doAutocompleteSearch(
    formOrigin,
    {
      actionOrigin,
      searchString,
      previousResult,
      forcePasswordGeneration,
      hasBeenTypePassword,
      isProbablyANewPasswordField,
      scenarioName,
      inputMaxLength,
      isWebAuthn,
    }
  ) {
    // Note: previousResult is a regular object, not an
    // nsIAutoCompleteResult.

    // Cancel if the primary password prompt is already showing or we unsuccessfully prompted for it too recently.
    if (!Services.logins.isLoggedIn) {
      if (Services.logins.uiBusy) {
        lazy.log(
          "Not searching logins for autocomplete since the primary password prompt is already showing."
        );
        // Return an empty array to make LoginManagerChild clear the
        // outstanding request it has temporarily saved.
        return { logins: [] };
      }

      const timeDiff = Date.now() - gLastMPLoginCancelled;
      if (timeDiff < LoginManagerParent._repromptTimeout) {
        lazy.log(
          `Not searching logins for autocomplete since the primary password prompt was last cancelled ${Math.round(
            timeDiff / 1000
          )} seconds ago.`
        );
        // Return an empty array to make LoginManagerChild clear the
        // outstanding request it has temporarily saved.
        return { logins: [] };
      }
    }

    const searchStringLower = searchString.toLowerCase();
    let logins;
    if (
      previousResult &&
      searchStringLower.startsWith(previousResult.searchString.toLowerCase())
    ) {
      lazy.log("Using previous autocomplete result.");

      // We have a list of results for a shorter search string, so just
      // filter them further based on the new search string.
      logins = lazy.LoginHelper.vanillaObjectsToLogins(previousResult.logins);
    } else {
      lazy.log("Creating new autocomplete search result.");
      let relatedRealmsOrigins = [];
      if (lazy.LoginHelper.relatedRealmsEnabled) {
        relatedRealmsOrigins =
          await lazy.LoginRelatedRealmsParent.findRelatedRealms(formOrigin);
      }
      // Autocomplete results do not need to match actionOrigin or exact origin.
      logins = await LoginManagerParent.searchAndDedupeLogins(formOrigin, {
        formActionOrigin: actionOrigin,
        ignoreActionAndRealm: true,
        acceptDifferentSubdomains:
          lazy.LoginHelper.includeOtherSubdomainsInLookup,
        relatedRealms: relatedRealmsOrigins,
      });
    }

    const matchingLogins = logins.filter(fullMatch => {
      // Remove results that are too short, or have different prefix.
      // Also don't offer empty usernames as possible results except
      // for on password fields.
      if (hasBeenTypePassword) {
        return true;
      }

      const match = fullMatch.username;

      return match && match.toLowerCase().startsWith(searchStringLower);
    });

    let generatedPassword = null;
    let willAutoSaveGeneratedPassword = false;
    if (
      // If MP was cancelled above, don't try to offer pwgen or access storage again (causing a new MP prompt).
      Services.logins.isLoggedIn &&
      (forcePasswordGeneration ||
        (isProbablyANewPasswordField &&
          Services.logins.getLoginSavingEnabled(formOrigin)))
    ) {
      // We either generate a new password here, or grab the previously generated password
      // if we're still on the same domain when we generated the password
      generatedPassword = await this.getGeneratedPassword({ inputMaxLength });
      const potentialConflictingLogins =
        await Services.logins.searchLoginsAsync({
          origin: formOrigin,
          formActionOrigin: actionOrigin,
          httpRealm: null,
        });
      willAutoSaveGeneratedPassword = !potentialConflictingLogins.find(
        login => login.username == ""
      );
    }

    // Convert the array of nsILoginInfo to vanilla JS objects since nsILoginInfo
    // doesn't support structured cloning.
    let jsLogins = lazy.LoginHelper.loginsToVanillaObjects(matchingLogins);

    let autocompleteItems = [];

    if (!hasBeenTypePassword) {
      autocompleteItems.push(
        ...(await lazy.FirefoxRelay.autocompleteItemsAsync({
          formOrigin,
          scenarioName,
          hasInput: !!searchStringLower.length,
        }))
      );
    }
    // This check is only used to init webauthn in tests, which causes
    // intermittent like Bug 1890419.
    if (LoginManagerParent._webAuthnAutoComplete) {
      autocompleteItems.push(
        ...(await lazy.WebAuthnFeature.autocompleteItemsAsync(
          this._overrideBrowsingContextId ??
            this.getRootBrowser().browsingContext.id,
          formOrigin,
          scenarioName,
          isWebAuthn
        ))
      );
    }

    return {
      generatedPassword,
      importable: await getImportableLogins(formOrigin),
      autocompleteItems,
      logins: jsLogins,
      willAutoSaveGeneratedPassword,
    };
  }

  /**
   * Expose `BrowsingContext` so we can stub it in tests.
   */
  static get _browsingContextGlobal() {
    return BrowsingContext;
  }

  // Set an override context within a test.
  useBrowsingContext(browsingContextId = 0) {
    this._overrideBrowsingContextId = browsingContextId;
  }

  getBrowsingContextToUse() {
    if (this._overrideBrowsingContextId) {
      return BrowsingContext.get(this._overrideBrowsingContextId);
    }

    return this.browsingContext;
  }

  async getGeneratedPassword({ inputMaxLength } = {}) {
    if (
      !lazy.LoginHelper.enabled ||
      !lazy.LoginHelper.generationAvailable ||
      !lazy.LoginHelper.generationEnabled
    ) {
      return null;
    }

    let browsingContext = this.getBrowsingContextToUse();
    if (!browsingContext) {
      return null;
    }
    let framePrincipalOrigin =
      browsingContext.currentWindowGlobal.documentPrincipal.origin;
    // Use the same password if we already generated one for this origin so that it doesn't change
    // with each search/keystroke and the user can easily re-enter a password in a confirmation field.
    let generatedPW =
      gGeneratedPasswordsByPrincipalOrigin.get(framePrincipalOrigin);
    if (generatedPW) {
      return generatedPW.value;
    }

    generatedPW = {
      autocompleteShown: false,
      edited: false,
      filled: false,
      /**
       * GUID of a login that was already saved for this generated password that
       * will be automatically updated with password changes. This shouldn't be
       * an existing saved login for the site unless the user chose to
       * merge/overwrite via a doorhanger.
       */
      storageGUID: null,
    };
    if (lazy.LoginHelper.improvedPasswordRulesEnabled) {
      generatedPW.value = await lazy.PasswordRulesManager.generatePassword(
        browsingContext.currentWindowGlobal.documentURI,
        { inputMaxLength }
      );
    } else {
      generatedPW.value = lazy.PasswordGenerator.generatePassword({
        inputMaxLength,
      });
    }

    // Add these observers when a password is assigned.
    if (!gGeneratedPasswordObserver.addedObserver) {
      Services.obs.addObserver(
        gGeneratedPasswordObserver,
        "passwordmgr-autosaved-login-merged"
      );
      Services.obs.addObserver(
        gGeneratedPasswordObserver,
        "passwordmgr-storage-changed"
      );
      Services.obs.addObserver(
        gGeneratedPasswordObserver,
        "last-pb-context-exited"
      );
      gGeneratedPasswordObserver.addedObserver = true;
    }

    gGeneratedPasswordsByPrincipalOrigin.set(framePrincipalOrigin, generatedPW);
    return generatedPW.value;
  }

  maybeRecordPasswordGenerationShownTelemetryEvent(autocompleteResults) {
    if (!autocompleteResults.some(r => r.style == "generatedPassword")) {
      return;
    }

    let browsingContext = this.getBrowsingContextToUse();

    let framePrincipalOrigin =
      browsingContext.currentWindowGlobal.documentPrincipal.origin;
    let generatedPW =
      gGeneratedPasswordsByPrincipalOrigin.get(framePrincipalOrigin);

    // We only want to record the first time it was shown for an origin
    if (generatedPW.autocompleteShown) {
      return;
    }

    generatedPW.autocompleteShown = true;

    Services.telemetry.recordEvent(
      "pwmgr",
      "autocomplete_shown",
      "generatedpassword"
    );
  }

  /**
   * Used for stubbing by tests.
   */
  _getPrompter() {
    return lazy.prompterSvc;
  }

  // Look for an existing login that matches the form login.
  #findSameLogin(logins, formLogin) {
    return logins.find(login => {
      let same;

      // If one login has a username but the other doesn't, ignore
      // the username when comparing and only match if they have the
      // same password. Otherwise, compare the logins and match even
      // if the passwords differ.
      if (!login.username && formLogin.username) {
        let restoreMe = formLogin.username;
        formLogin.username = "";
        same = lazy.LoginHelper.doLoginsMatch(formLogin, login, {
          ignorePassword: false,
          ignoreSchemes: lazy.LoginHelper.schemeUpgrades,
        });
        formLogin.username = restoreMe;
      } else if (!formLogin.username && login.username) {
        formLogin.username = login.username;
        same = lazy.LoginHelper.doLoginsMatch(formLogin, login, {
          ignorePassword: false,
          ignoreSchemes: lazy.LoginHelper.schemeUpgrades,
        });
        formLogin.username = ""; // we know it's always blank.
      } else {
        same = lazy.LoginHelper.doLoginsMatch(formLogin, login, {
          ignorePassword: true,
          ignoreSchemes: lazy.LoginHelper.schemeUpgrades,
        });
      }

      return same;
    });
  }

  async showDoorhanger(
    browser,
    formOrigin,
    {
      browsingContextId,
      formActionOrigin,
      autoFilledLoginGuid,
      usernameField,
      newPasswordField,
      oldPasswordField,
      dismissedPrompt,
    }
  ) {
    function recordLoginUse(login) {
      Services.logins.recordPasswordUse(
        login,
        browser && lazy.PrivateBrowsingUtils.isBrowserPrivate(browser),
        login.username ? "form_login" : "form_password",
        !!autoFilledLoginGuid
      );
    }

    // If password storage is disabled, bail out.
    if (!lazy.LoginHelper.storageEnabled) {
      return;
    }

    if (!Services.logins.getLoginSavingEnabled(formOrigin)) {
      lazy.log(
        `Form submission ignored because saving is disabled for origin: ${formOrigin}.`
      );
      return;
    }

    let browsingContext = BrowsingContext.get(browsingContextId);
    let framePrincipalOrigin =
      browsingContext.currentWindowGlobal.documentPrincipal.origin;

    let formLogin = new LoginInfo(
      formOrigin,
      formActionOrigin,
      null,
      usernameField?.value ?? "",
      newPasswordField.value,
      usernameField?.name ?? "",
      newPasswordField.name
    );
    // we don't auto-save logins on form submit
    let notifySaved = false;

    if (autoFilledLoginGuid) {
      let loginsForGuid = await Services.logins.searchLoginsAsync({
        guid: autoFilledLoginGuid,
        origin: formOrigin, // Ignored outside of GV.
      });
      if (
        loginsForGuid.length == 1 &&
        loginsForGuid[0].password == formLogin.password &&
        (!formLogin.username || // Also cover cases where only the password is requested.
          loginsForGuid[0].username == formLogin.username)
      ) {
        lazy.log(
          "The filled login matches the form submission. Nothing to change."
        );
        recordLoginUse(loginsForGuid[0]);
        return;
      }
    }

    let existingLogin = null;
    let canMatchExistingLogin = true;
    // Below here we have one login per hostPort + action + username with the
    // matching scheme being preferred.
    const logins = await LoginManagerParent.searchAndDedupeLogins(formOrigin, {
      formActionOrigin,
    });

    const generatedPW =
      gGeneratedPasswordsByPrincipalOrigin.get(framePrincipalOrigin);
    const autoSavedStorageGUID = generatedPW?.storageGUID ?? "";

    // If we didn't find a username field, but seem to be changing a
    // password, allow the user to select from a list of applicable
    // logins to update the password for.
    if (!usernameField && oldPasswordField && logins.length) {
      if (logins.length == 1) {
        existingLogin = logins[0];

        if (existingLogin.password == formLogin.password) {
          recordLoginUse(existingLogin);
          lazy.log(
            "Not prompting to save/change since we have no username and the only saved password matches the new password."
          );
          return;
        }

        formLogin.username = existingLogin.username;
        formLogin.usernameField = existingLogin.usernameField;
      } else if (!generatedPW || generatedPW.value != newPasswordField.value) {
        // Note: It's possible that that we already have the correct u+p saved
        // but since we don't have the username, we don't know if the user is
        // changing a second account to the new password so we ask anyways.
        canMatchExistingLogin = false;
      }
    }

    if (canMatchExistingLogin && !existingLogin) {
      existingLogin = this.#findSameLogin(logins, formLogin);
    }

    const promptBrowser = lazy.LoginHelper.getBrowserForPrompt(browser);
    const prompter = this._getPrompter(browser);

    if (!canMatchExistingLogin) {
      prompter.promptToChangePasswordWithUsernames(
        promptBrowser,
        logins,
        formLogin
      );
      return;
    }

    if (existingLogin) {
      lazy.log("Found an existing login matching this form submission.");

      // Change password if needed.
      if (existingLogin.password != formLogin.password) {
        lazy.log("Passwords differ, prompting to change.");
        prompter.promptToChangePassword(
          promptBrowser,
          existingLogin,
          formLogin,
          dismissedPrompt,
          notifySaved,
          autoSavedStorageGUID,
          autoFilledLoginGuid,
          this.possibleValues
        );
      } else if (!existingLogin.username && formLogin.username) {
        lazy.log("Empty username update, prompting to change.");
        prompter.promptToChangePassword(
          promptBrowser,
          existingLogin,
          formLogin,
          dismissedPrompt,
          notifySaved,
          autoSavedStorageGUID,
          autoFilledLoginGuid,
          this.possibleValues
        );
      } else {
        recordLoginUse(existingLogin);
      }

      return;
    }

    // Prompt user to save login (via dialog or notification bar)
    prompter.promptToSavePassword(
      promptBrowser,
      formLogin,
      dismissedPrompt,
      notifySaved,
      autoFilledLoginGuid,
      this.possibleValues
    );
  }

  /**
   * Performs validation of inputs against already-saved logins in order to determine whether and
   * how these inputs can be stored. Depending on validation, will either no-op or show a 'save'
   * or 'update' dialog to the user.
   *
   * This is called after any of the following:
   *   - The user edits a password
   *   - A generated password is filled
   *   - The user edits a username (when a matching password field has already been filled)
   *
   * @param {Element} browser
   * @param {string} formOrigin
   * @param {string} options.formActionOrigin
   * @param {string?} options.autoFilledLoginGuid
   * @param {Object} options.newPasswordField
   * @param {Object?} options.usernameField
   * @param {Element?} options.oldPasswordField
   * @param {boolean} [options.triggeredByFillingGenerated = false]
   */
  /* eslint-disable-next-line complexity */
  async _onPasswordEditedOrGenerated(
    browser,
    formOrigin,
    {
      formActionOrigin,
      autoFilledLoginGuid,
      newPasswordField,
      usernameField = null,
      oldPasswordField,
      triggeredByFillingGenerated = false,
    }
  ) {
    lazy.log(
      `_onPasswordEditedOrGenerated: triggeredByFillingGenerated: ${triggeredByFillingGenerated}.`
    );

    // If password storage is disabled, bail out.
    if (!lazy.LoginHelper.storageEnabled) {
      return;
    }

    if (!Services.logins.getLoginSavingEnabled(formOrigin)) {
      // No UI should be shown to offer generation in this case but a user may
      // disable saving for the site after already filling one and they may then
      // edit it.
      lazy.log(`Saving is disabled for origin: ${formOrigin}.`);
      return;
    }

    if (!newPasswordField.value) {
      lazy.log("The password field is empty.");
      return;
    }

    if (!browser) {
      lazy.log("The browser is gone.");
      return;
    }

    let browsingContext = this.getBrowsingContextToUse();
    if (!browsingContext) {
      return;
    }

    if (!triggeredByFillingGenerated && !Services.logins.isLoggedIn) {
      // Don't show the dismissed doorhanger on "input" or "change" events
      // when the Primary Password is locked
      lazy.log(
        "Edited field is not a generated password field, and Primary Password is locked."
      );
      return;
    }

    let framePrincipalOrigin =
      browsingContext.currentWindowGlobal.documentPrincipal.origin;

    lazy.log("Got framePrincipalOrigin: ", framePrincipalOrigin);

    let formLogin = new LoginInfo(
      formOrigin,
      formActionOrigin,
      null,
      usernameField?.value ?? "",
      newPasswordField.value,
      usernameField?.name ?? "",
      newPasswordField.name
    );
    let existingLogin = null;
    let canMatchExistingLogin = true;
    let shouldAutoSaveLogin = triggeredByFillingGenerated;
    let autoSavedLogin = null;
    let notifySaved = false;

    if (autoFilledLoginGuid) {
      let [matchedLogin] = await Services.logins.searchLoginsAsync({
        guid: autoFilledLoginGuid,
        origin: formOrigin, // Ignored outside of GV.
      });
      if (
        matchedLogin &&
        matchedLogin.password == formLogin.password &&
        (!formLogin.username || // Also cover cases where only the password is requested.
          matchedLogin.username == formLogin.username)
      ) {
        lazy.log(
          "The filled login matches the changed fields. Nothing to change."
        );
        // We may want to update an existing doorhanger
        existingLogin = matchedLogin;
      }
    }

    let generatedPW =
      gGeneratedPasswordsByPrincipalOrigin.get(framePrincipalOrigin);

    // Below here we have one login per hostPort + action + username with the
    // matching scheme being preferred.
    let logins = await LoginManagerParent.searchAndDedupeLogins(formOrigin, {
      formActionOrigin,
    });
    // only used in the generated pw case where we auto-save
    let formLoginWithoutUsername;

    if (triggeredByFillingGenerated && generatedPW) {
      lazy.log("Got cached generatedPW.");
      formLoginWithoutUsername = new LoginInfo(
        formOrigin,
        formActionOrigin,
        null,
        "",
        newPasswordField.value
      );

      if (newPasswordField.value != generatedPW.value) {
        // The user edited the field after generation to a non-empty value.
        lazy.log("The field containing the generated password has changed.");

        // Record telemetry for the first edit
        if (!generatedPW.edited) {
          Services.telemetry.recordEvent(
            "pwmgr",
            "filled_field_edited",
            "generatedpassword"
          );
          lazy.log("filled_field_edited telemetry event recorded.");
          generatedPW.edited = true;
        }
      }

      // This will throw if we can't look up the entry in the password/origin map
      if (!generatedPW.filled) {
        if (generatedPW.storageGUID) {
          throw new Error(
            "Generated password was saved in storage without being filled first"
          );
        }
        // record first use of this generated password
        Services.telemetry.recordEvent(
          "pwmgr",
          "autocomplete_field",
          "generatedpassword"
        );
        lazy.log("autocomplete_field telemetry event recorded.");
        generatedPW.filled = true;
      }

      // We may have already autosaved this login
      // Note that it could have been saved in a totally different tab in the session.
      if (generatedPW.storageGUID) {
        [autoSavedLogin] = await Services.logins.searchLoginsAsync({
          guid: generatedPW.storageGUID,
          origin: formOrigin, // Ignored outside of GV.
        });

        if (autoSavedLogin) {
          lazy.log("login to change is the auto-saved login.");
          existingLogin = autoSavedLogin;
        }
        // The generated password login may have been deleted in the meantime.
        // Proceed to maybe save a new login below.
      }
      generatedPW.value = newPasswordField.value;

      if (!existingLogin) {
        lazy.log("Did not match generated-password login.");

        // Check if we already have a login saved for this site since we don't want to overwrite it in
        // case the user still needs their old password to successfully complete a password change.
        let matchedLogin = logins.find(login =>
          formLoginWithoutUsername.matches(login, true)
        );
        if (matchedLogin) {
          shouldAutoSaveLogin = false;
          if (matchedLogin.password == formLoginWithoutUsername.password) {
            // This login is already saved so show no new UI.
            // We may want to update an existing doorhanger though...
            lazy.log("Matching login already saved.");
            existingLogin = matchedLogin;
          }
          lazy.log(
            "_onPasswordEditedOrGenerated: Login with empty username already saved for this site."
          );
        }
      }
    }

    // If we didn't find a username field, but seem to be changing a
    // password, use the first match if there is only one
    // If there's more than one we'll prompt to save with the initial formLogin
    // and let the doorhanger code resolve this
    if (
      !triggeredByFillingGenerated &&
      !existingLogin &&
      !usernameField &&
      oldPasswordField &&
      logins.length
    ) {
      if (logins.length == 1) {
        existingLogin = logins[0];

        if (existingLogin.password == formLogin.password) {
          lazy.log(
            "Not prompting to save/change since we have no username and the " +
              "only saved password matches the new password."
          );
          return;
        }

        formLogin.username = existingLogin.username;
        formLogin.usernameField = existingLogin.usernameField;
      } else if (!generatedPW || generatedPW.value != newPasswordField.value) {
        // Note: It's possible that that we already have the correct u+p saved
        // but since we don't have the username, we don't know if the user is
        // changing a second account to the new password so we ask anyways.
        canMatchExistingLogin = false;
      }
    }

    if (canMatchExistingLogin && !existingLogin) {
      existingLogin = this.#findSameLogin(logins, formLogin);
      if (existingLogin) {
        lazy.log("Matched saved login.");
      }
    }

    if (shouldAutoSaveLogin) {
      if (
        existingLogin &&
        existingLogin == autoSavedLogin &&
        existingLogin.password !== formLogin.password
      ) {
        lazy.log("Updating auto-saved login.");

        Services.logins.modifyLogin(
          existingLogin,
          lazy.LoginHelper.newPropertyBag({
            password: formLogin.password,
          })
        );
        notifySaved = true;
        // Update `existingLogin` with the new password if modifyLogin didn't
        // throw so that the prompts later uses the new password.
        existingLogin.password = formLogin.password;
      } else if (!autoSavedLogin) {
        lazy.log("Auto-saving new login with empty username.");
        existingLogin = await Services.logins.addLoginAsync(
          formLoginWithoutUsername
        );
        // Remember the GUID where we saved the generated password so we can update
        // the login if the user later edits the generated password.
        generatedPW.storageGUID = existingLogin.guid;
        notifySaved = true;
      }
    } else {
      lazy.log("Not auto-saving this login.");
    }

    const prompter = this._getPrompter(browser);
    const promptBrowser = lazy.LoginHelper.getBrowserForPrompt(browser);

    if (existingLogin) {
      // Show a change doorhanger to allow modifying an already-saved login
      // e.g. to add a username or update the password.
      let autoSavedStorageGUID = "";
      if (
        generatedPW &&
        generatedPW.value == existingLogin.password &&
        generatedPW.storageGUID == existingLogin.guid
      ) {
        autoSavedStorageGUID = generatedPW.storageGUID;
      }

      // Change password if needed.
      if (
        (shouldAutoSaveLogin && !formLogin.username) ||
        existingLogin.password != formLogin.password
      ) {
        lazy.log(
          `promptToChangePassword with autoSavedStorageGUID: ${autoSavedStorageGUID}`
        );
        prompter.promptToChangePassword(
          promptBrowser,
          existingLogin,
          formLogin,
          true, // dismissed prompt
          notifySaved,
          autoSavedStorageGUID, // autoSavedLoginGuid
          autoFilledLoginGuid,
          this.possibleValues
        );
      } else if (!existingLogin.username && formLogin.username) {
        lazy.log("Empty username update, prompting to change.");
        prompter.promptToChangePassword(
          promptBrowser,
          existingLogin,
          formLogin,
          true, // dismissed prompt
          notifySaved,
          autoSavedStorageGUID, // autoSavedLoginGuid
          autoFilledLoginGuid,
          this.possibleValues
        );
      } else {
        lazy.log("No change to existing login.");
        // is there a doorhanger we should update?
        let popupNotifications = promptBrowser.ownerGlobal.PopupNotifications;
        let notif = popupNotifications.getNotification("password", browser);
        lazy.log(
          `_onPasswordEditedOrGenerated: Has doorhanger? ${
            notif && notif.dismissed
          }`
        );
        if (notif && notif.dismissed) {
          prompter.promptToChangePassword(
            promptBrowser,
            existingLogin,
            formLogin,
            true, // dismissed prompt
            notifySaved,
            autoSavedStorageGUID, // autoSavedLoginGuid
            autoFilledLoginGuid,
            this.possibleValues
          );
        }
      }
      return;
    }
    lazy.log("No matching login to save/update.");
    prompter.promptToSavePassword(
      promptBrowser,
      formLogin,
      true, // dismissed prompt
      notifySaved,
      autoFilledLoginGuid,
      this.possibleValues
    );
  }

  static get recipeParentPromise() {
    if (!gRecipeManager) {
      const { LoginRecipesParent } = ChromeUtils.importESModule(
        "resource://gre/modules/LoginRecipes.sys.mjs"
      );
      gRecipeManager = new LoginRecipesParent({
        defaults: Services.prefs.getStringPref("signon.recipes.path"),
      });
    }

    return gRecipeManager.initializationPromise;
  }

  async searchAutoCompleteEntries(searchString, data) {
    return this.doAutocompleteSearch(data.formOrigin, data);
  }

  onAutoCompleteEntryHovered(_message, _data) {
    // Logins do not show previews
  }

  async onAutoCompleteEntrySelected(message, data) {
    switch (message) {
      // Called when clicking the open preference entry in the autocomplete
      case "PasswordManager:OpenPreferences": {
        this.#onOpenPreferences(data.hostname, data.entryPoint);
        break;
      }

      case "PasswordManager:OpenImportableLearnMore": {
        this.#onOpenImportableLearnMore();
        break;
      }

      case "PasswordManager:HandleImportable": {
        this.#onHandleImportable(data.browserId);
        break;
      }

      case "PasswordManager:offerRelayIntegration": {
        FirefoxRelayTelemetry.recordRelayOfferedEvent(
          "clicked",
          data.telemetry.flowId,
          data.telemetry.scenarioName
        );
        const username = await this.#offerRelayIntegration(this.origin);
        if (username) {
          this.sendAsyncMessage("PasswordManager:FillRelayUsername", username);
        }
        break;
      }

      case "PasswordManager:generateRelayUsername": {
        FirefoxRelayTelemetry.recordRelayUsernameFilledEvent(
          "clicked",
          data.telemetry.flowId
        );
        const username = await this.#generateRelayUsername(this.origin);
        if (username) {
          this.sendAsyncMessage("PasswordManager:FillRelayUsername", username);
        }
        break;
      }

      case "PasswordManager:promptForAuthenticator": {
        return this.#promptForAuthenticator(data.selection);
      }

      case "PasswordManager:FillGeneratedPassword":
      case "PasswordManager:OnFieldAutoComplete": {
        this.sendAsyncMessage(message, data);
        break;
      }

      default: {
        lazy.log.debug("Unsupported autocomplete message:", message);
        break;
      }
    }

    return undefined;
  }
}

LoginManagerParent.SUGGEST_IMPORT_DEBOUNCE_MS = 10000;

XPCOMUtils.defineLazyPreferenceGetter(
  LoginManagerParent,
  "_repromptTimeout",
  "signon.masterPasswordReprompt.timeout_ms",
  900000
); // 15 Minutes

XPCOMUtils.defineLazyPreferenceGetter(
  LoginManagerParent,
  "_webAuthnAutoComplete",
  "signon.webauthn.autocomplete",
  true
);
PK
!<���xʗʗ$modules/LoginManagerPrompter.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { PrivateBrowsingUtils } from "resource://gre/modules/PrivateBrowsingUtils.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { showConfirmation } from "resource://gre/modules/FillHelpers.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "usernameAutocompleteSearch",
  "@mozilla.org/autocomplete/search;1?name=login-doorhanger-username",
  "nsIAutoCompleteSimpleSearch"
);

ChromeUtils.defineLazyGetter(lazy, "l10n", () => {
  return new Localization(["toolkit/passwordmgr/passwordmgr.ftl"], true);
});

const LoginInfo = Components.Constructor(
  "@mozilla.org/login-manager/loginInfo;1",
  "nsILoginInfo",
  "init"
);

/**
 * The maximum age of the password in ms (using `timePasswordChanged`) whereby
 * a user can toggle the password visibility in a doorhanger to add a username to
 * a saved login.
 */
const VISIBILITY_TOGGLE_MAX_PW_AGE_MS = 2 * 60 * 1000; // 2 minutes

/**
 * Constants for password prompt telemetry.
 */
const PROMPT_DISPLAYED = 0;
const PROMPT_ADD_OR_UPDATE = 1;
const PROMPT_NOTNOW_OR_DONTUPDATE = 2;
const PROMPT_NEVER = 3;
const PROMPT_DELETE = 3;

/**
 * The minimum age of a doorhanger in ms before it will get removed after a locationchange
 */
const NOTIFICATION_TIMEOUT_MS = 10 * 1000; // 10 seconds

/**
 * The minimum age of an attention-requiring dismissed doorhanger in ms
 * before it will get removed after a locationchange
 */
const ATTENTION_NOTIFICATION_TIMEOUT_MS = 60 * 1000; // 1 minute

function autocompleteSelected(popup) {
  const doc = popup.ownerDocument;
  const nameField = doc.getElementById("password-notification-username");
  const passwordField = doc.getElementById("password-notification-password");

  const activeElement = nameField.ownerDocument.activeElement;
  if (activeElement == nameField) {
    popup.onUsernameSelect();
  } else if (activeElement == passwordField) {
    popup.onPasswordSelect();
  }
}

const observer = {
  QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),

  // nsIObserver
  observe(subject, topic, _data) {
    switch (topic) {
      case "autocomplete-did-enter-text": {
        const input = subject.QueryInterface(Ci.nsIAutoCompleteInput);
        autocompleteSelected(input.popupElement);
        break;
      }
    }
  },
};

/**
 * Implements interfaces for prompting the user to enter/save/change login info
 * found in HTML forms.
 */
export class LoginManagerPrompter {
  get classID() {
    return Components.ID("{c47ff942-9678-44a5-bc9b-05e0d676c79c}");
  }

  get QueryInterface() {
    return ChromeUtils.generateQI(["nsILoginManagerPrompter"]);
  }

  /**
   * Called when we detect a password or username that is not yet saved as
   * an existing login.
   *
   * @param {Element} aBrowser
   *                  The browser element that the request came from.
   * @param {nsILoginInfo} aLogin
   *                       The new login from the page form.
   * @param {boolean} [dismissed = false]
   *                  If the prompt should be automatically dismissed on being shown.
   * @param {boolean} [notifySaved = false]
   *                  Whether the notification should indicate that a login has been saved
   * @param {string} [autoSavedLoginGuid = ""]
   *                 A guid value for the old login to be removed if the changes match it
   *                 to a different login
   * @param {object?} possibleValues
   *                 Contains values from anything that we think, but are not sure, might be
   *                 a username or password.  Has two properties, 'usernames' and 'passwords'.
   * @param {Set<String>} possibleValues.usernames
   * @param {Set<String>} possibleValues.passwords
   */
  promptToSavePassword(
    aBrowser,
    aLogin,
    dismissed = false,
    notifySaved = false,
    autoFilledLoginGuid = "",
    possibleValues = undefined
  ) {
    lazy.log.debug("Prompting user to save login.");
    const inPrivateBrowsing = PrivateBrowsingUtils.isBrowserPrivate(aBrowser);
    const notification = LoginManagerPrompter._showLoginCaptureDoorhanger(
      aBrowser,
      aLogin,
      "password-save",
      {
        dismissed: inPrivateBrowsing || dismissed,
        extraAttr: notifySaved ? "attention" : "",
      },
      possibleValues,
      {
        notifySaved,
        autoFilledLoginGuid,
      }
    );
    Services.obs.notifyObservers(aLogin, "passwordmgr-prompt-save");

    return {
      dismiss() {
        const { PopupNotifications } = aBrowser.ownerGlobal.wrappedJSObject;
        PopupNotifications.remove(notification);
      },
    };
  }

  /**
   * Displays the PopupNotifications.sys.mjs doorhanger for password save or change.
   *
   * @param {Element} browser
   *        The browser to show the doorhanger on.
   * @param {nsILoginInfo} login
   *        Login to save or change. For changes, this login should contain the
   *        new password and/or username
   * @param {string} type
   *        This is "password-save" or "password-change" depending on the
   *        original notification type. This is used for telemetry and tests.
   * @param {object} showOptions
   *        Options to pass along to PopupNotifications.show().
   * @param {bool} [options.notifySaved = false]
   *        Whether to indicate to the user that the login was already saved.
   * @param {string} [options.messageStringID = undefined]
   *        An optional string ID to override the default message.
   * @param {string} [options.autoSavedLoginGuid = ""]
   *        A string guid value for the auto-saved login to be removed if the changes
   *        match it to a different login
   * @param {string} [options.autoFilledLoginGuid = ""]
   *        A string guid value for the autofilled login
   * @param {object?} possibleValues
   *                 Contains values from anything that we think, but are not sure, might be
   *                 a username or password.  Has two properties, 'usernames' and 'passwords'.
   * @param {Set<String>} possibleValues.usernames
   * @param {Set<String>} possibleValues.passwords
   */
  static _showLoginCaptureDoorhanger(
    browser,
    login,
    type,
    showOptions = {},
    possibleValues = undefined,
    {
      notifySaved = false,
      messageStringID,
      autoSavedLoginGuid = "",
      autoFilledLoginGuid = "",
    } = {}
  ) {
    lazy.log.debug(
      `Got autoSavedLoginGuid: ${autoSavedLoginGuid} and autoFilledLoginGuid ${autoFilledLoginGuid}.`
    );

    const saveMessageIds = {
      prompt: "password-manager-save-password-message",
      mainButton: "password-manager-save-password-button-allow",
      secondaryButton: "password-manager-save-password-button-deny",
    };

    const changeMessageIds = {
      prompt: messageStringID ?? "password-manager-update-password-message",
      mainButton: "password-manager-password-password-button-allow",
      secondaryButton: "password-manager-update-password-button-deny",
    };

    const initialMessageIds =
      type == "password-save" ? saveMessageIds : changeMessageIds;

    const promptId = initialMessageIds.prompt;
    const host = this._getShortDisplayHost(login.origin);
    const promptMessage = lazy.l10n.formatValueSync(promptId, { host });

    const histogramName =
      type == "password-save"
        ? "PWMGR_PROMPT_REMEMBER_ACTION"
        : "PWMGR_PROMPT_UPDATE_ACTION";
    const histogram = Services.telemetry.getHistogramById(histogramName);

    const chromeDoc = browser.ownerDocument;
    let currentNotification;

    const wasModifiedEvent = {
      // Values are mutated
      did_edit_un: "false",
      did_select_un: "false",
      did_edit_pw: "false",
      did_select_pw: "false",
    };

    const updateButtonStatus = element => {
      const mainActionButton = element.button;
      // Disable the main button inside the menu-button if the password field is empty.
      if (!login.password.length) {
        mainActionButton.setAttribute("disabled", true);
        chromeDoc
          .getElementById("password-notification-password")
          .classList.add("popup-notification-invalid-input");
      } else {
        mainActionButton.removeAttribute("disabled");
        chromeDoc
          .getElementById("password-notification-password")
          .classList.remove("popup-notification-invalid-input");
      }
    };

    const updateButtonLabel = () => {
      if (!currentNotification) {
        console.error("updateButtonLabel, no currentNotification");
      }
      const foundLogins = lazy.LoginHelper.searchLoginsWithObject({
        formActionOrigin: login.formActionOrigin,
        origin: login.origin,
        httpRealm: login.httpRealm,
        schemeUpgrades: lazy.LoginHelper.schemeUpgrades,
      });

      const logins = this._filterUpdatableLogins(
        login,
        foundLogins,
        autoSavedLoginGuid
      );
      const messageIds = !logins.length ? saveMessageIds : changeMessageIds;

      // Update the label based on whether this will be a new login or not.

      const mainButton = this.getLabelAndAccessKey(messageIds.mainButton);

      // Update the labels for the next time the panel is opened.
      currentNotification.mainAction.label = mainButton.label;
      currentNotification.mainAction.accessKey = mainButton.accessKey;

      // Update the labels in real time if the notification is displayed.
      const element = [...currentNotification.owner.panel.childNodes].find(
        n => n.notification == currentNotification
      );
      if (element) {
        element.setAttribute("buttonlabel", mainButton.label);
        element.setAttribute("buttonaccesskey", mainButton.accessKey);
        updateButtonStatus(element);
      }
    };

    const writeDataToUI = () => {
      const nameField = chromeDoc.getElementById(
        "password-notification-username"
      );

      nameField.placeholder = usernamePlaceholder;
      nameField.value = login.username;

      const toggleCheckbox = chromeDoc.getElementById(
        "password-notification-visibilityToggle"
      );
      toggleCheckbox.removeAttribute("checked");
      const passwordField = chromeDoc.getElementById(
        "password-notification-password"
      );
      // Ensure the type is reset so the field is masked.
      passwordField.type = "password";
      passwordField.value = login.password;

      updateButtonLabel();
    };

    const readDataFromUI = () => {
      login.username = chromeDoc.getElementById(
        "password-notification-username"
      ).value;
      login.password = chromeDoc.getElementById(
        "password-notification-password"
      ).value;
    };

    const onInput = () => {
      readDataFromUI();
      updateButtonLabel();
    };

    const onUsernameInput = () => {
      wasModifiedEvent.did_edit_un = "true";
      wasModifiedEvent.did_select_un = "false";
      onInput();
    };

    const onUsernameSelect = () => {
      wasModifiedEvent.did_edit_un = "false";
      wasModifiedEvent.did_select_un = "true";
    };

    const onPasswordInput = () => {
      wasModifiedEvent.did_edit_pw = "true";
      wasModifiedEvent.did_select_pw = "false";
      onInput();
    };

    const onPasswordSelect = () => {
      wasModifiedEvent.did_edit_pw = "false";
      wasModifiedEvent.did_select_pw = "true";
    };

    const onKeyUp = e => {
      if (e.key == "Enter") {
        e.target.closest("popupnotification").button.doCommand();
      }
    };

    const onVisibilityToggle = commandEvent => {
      const passwordField = chromeDoc.getElementById(
        "password-notification-password"
      );
      // Gets the caret position before changing the type of the textbox
      const selectionStart = passwordField.selectionStart;
      const selectionEnd = passwordField.selectionEnd;
      passwordField.setAttribute(
        "type",
        commandEvent.target.checked ? "" : "password"
      );
      if (!passwordField.hasAttribute("focused")) {
        return;
      }
      passwordField.selectionStart = selectionStart;
      passwordField.selectionEnd = selectionEnd;
    };

    const togglePopup = event => {
      event.target.parentElement
        .getElementsByClassName("ac-has-end-icon")[0]
        .toggleHistoryPopup();
    };

    const persistData = async () => {
      const foundLogins = lazy.LoginHelper.searchLoginsWithObject({
        formActionOrigin: login.formActionOrigin,
        origin: login.origin,
        httpRealm: login.httpRealm,
        schemeUpgrades: lazy.LoginHelper.schemeUpgrades,
      });

      let logins = this._filterUpdatableLogins(
        login,
        foundLogins,
        autoSavedLoginGuid
      );
      const resolveBy = ["scheme", "timePasswordChanged"];
      logins = lazy.LoginHelper.dedupeLogins(
        logins,
        ["username"],
        resolveBy,
        login.origin
      );
      // sort exact username matches to the top
      logins.sort(l => (l.username == login.username ? -1 : 1));

      lazy.log.debug(`Matched ${logins.length} logins.`);

      let loginToRemove;
      const loginToUpdate = logins.shift();

      if (logins.length && logins[0].guid == autoSavedLoginGuid) {
        loginToRemove = logins.shift();
      }
      if (logins.length) {
        lazy.log.warn(
          "persistData:",
          logins.length,
          "other updatable logins!",
          logins.map(l => l.guid),
          "loginToUpdate:",
          loginToUpdate && loginToUpdate.guid,
          "loginToRemove:",
          loginToRemove && loginToRemove.guid
        );
        // Proceed with updating the login with the best username match rather
        // than returning and losing the edit.
      }

      if (!loginToUpdate) {
        // Create a new login, don't update an original.
        // The original login we have been provided with might have its own
        // metadata, but we don't want it propagated to the newly created one.
        await Services.logins.addLoginAsync(
          new LoginInfo(
            login.origin,
            login.formActionOrigin,
            login.httpRealm,
            login.username,
            login.password,
            login.usernameField,
            login.passwordField
          )
        );
      } else if (
        loginToUpdate.password == login.password &&
        loginToUpdate.username == login.username
      ) {
        // We only want to touch the login's use count and last used time.
        lazy.log.debug(`Touch matched login: ${loginToUpdate.guid}.`);
        Services.logins.recordPasswordUse(
          loginToUpdate,
          PrivateBrowsingUtils.isBrowserPrivate(browser),
          loginToUpdate.username ? "form_password" : "form_login",
          !!autoFilledLoginGuid
        );
      } else {
        lazy.log.debug(`Update matched login: ${loginToUpdate.guid}.`);
        this._updateLogin(loginToUpdate, login);
        // notify that this auto-saved login has been merged
        if (loginToRemove && loginToRemove.guid == autoSavedLoginGuid) {
          Services.obs.notifyObservers(
            loginToRemove,
            "passwordmgr-autosaved-login-merged"
          );
        }
      }

      if (loginToRemove) {
        lazy.log.debug(`Removing login ${loginToRemove.guid}.`);
        Services.logins.removeLogin(loginToRemove);
      }
    };

    const supportedHistogramNames = {
      PWMGR_PROMPT_REMEMBER_ACTION: true,
      PWMGR_PROMPT_UPDATE_ACTION: true,
    };

    const mainButton = this.getLabelAndAccessKey(initialMessageIds.mainButton);

    // The main action is the "Save" or "Update" button.
    const mainAction = {
      label: mainButton.label,
      accessKey: mainButton.accessKey,
      callback: async () => {
        const eventTypeMapping = {
          "password-save": {
            eventObject: "save",
            confirmationHintFtlId: "confirmation-hint-password-created",
          },
          "password-change": {
            eventObject: "update",
            confirmationHintFtlId: "confirmation-hint-password-updated",
          },
        };

        if (!eventTypeMapping[type]) {
          throw new Error(`Unexpected doorhanger type: '${type}'`);
        }

        readDataFromUI();
        if (
          type == "password-save" &&
          !Services.policies.isAllowed("removeMasterPassword")
        ) {
          if (!lazy.LoginHelper.isPrimaryPasswordSet()) {
            browser.ownerGlobal.openDialog(
              "chrome://mozapps/content/preferences/changemp.xhtml",
              "",
              "centerscreen,chrome,modal,titlebar"
            );
            if (!lazy.LoginHelper.isPrimaryPasswordSet()) {
              return;
            }
          }
        }
        histogram.add(PROMPT_ADD_OR_UPDATE);
        if (!supportedHistogramNames[histogramName]) {
          throw new Error("Unknown histogram");
        }

        showConfirmation(browser, eventTypeMapping[type].confirmationHintFtlId);
        // The popup does not wait until this promise is resolved, but is
        // closed immediately when the function is returned. Therefore, we set
        // the focus before awaiting the asynchronous operation.
        browser.focus();
        await persistData();

        Services.telemetry.recordEvent(
          "pwmgr",
          "doorhanger_submitted",
          eventTypeMapping[type].eventObject,
          null,
          wasModifiedEvent
        );

        if (histogramName == "PWMGR_PROMPT_REMEMBER_ACTION") {
          Services.obs.notifyObservers(browser, "LoginStats:NewSavedPassword");
        } else if (histogramName == "PWMGR_PROMPT_UPDATE_ACTION") {
          Services.obs.notifyObservers(browser, "LoginStats:LoginUpdateSaved");
        }

        Services.obs.notifyObservers(
          null,
          "weave:telemetry:histogram",
          histogramName
        );
      },
    };

    const secondaryButton = this.getLabelAndAccessKey(
      initialMessageIds.secondaryButton
    );

    const secondaryActions = [
      {
        label: secondaryButton.label,
        accessKey: secondaryButton.accessKey,
        callback: () => {
          histogram.add(PROMPT_NOTNOW_OR_DONTUPDATE);
          Services.obs.notifyObservers(
            null,
            "weave:telemetry:histogram",
            histogramName
          );
          browser.focus();
        },
      },
    ];
    // Include a "Never for this site" button when saving a new password.
    if (type == "password-save") {
      const neverSaveButton = this.getLabelAndAccessKey(
        "password-manager-save-password-button-never"
      );
      secondaryActions.push({
        label: neverSaveButton.label,
        accessKey: neverSaveButton.accessKey,
        callback: () => {
          histogram.add(PROMPT_NEVER);
          Services.obs.notifyObservers(
            null,
            "weave:telemetry:histogram",
            histogramName
          );
          Services.logins.setLoginSavingEnabled(login.origin, false);
          browser.focus();
        },
      });
    }

    const updatePasswordButtonDelete = this.getLabelAndAccessKey(
      "password-manager-update-password-button-delete"
    );

    // Include a "Delete this login" button when updating an existing password
    if (type == "password-change") {
      secondaryActions.push({
        label: updatePasswordButtonDelete.label,
        accessKey: updatePasswordButtonDelete.accessKey,
        callback: async () => {
          histogram.add(PROMPT_DELETE);
          Services.obs.notifyObservers(
            null,
            "weave:telemetry:histogram",
            histogramName
          );
          const matchingLogins = await Services.logins.searchLoginsAsync({
            guid: login.guid,
            origin: login.origin,
          });
          Services.logins.removeLogin(matchingLogins[0]);
          browser.focus();
          lazy.log.debug("Showing the ConfirmationHint");
          showConfirmation(browser, "confirmation-hint-password-removed");
        },
      });
    }

    const usernamePlaceholder = lazy.l10n.formatValueSync(
      "password-manager-no-username-placeholder"
    );
    const togglePassword = this.getLabelAndAccessKey(
      "password-manager-toggle-password"
    );

    // .wrappedJSObject needed here -- see bug 422974 comment 5.
    const { PopupNotifications } = browser.ownerGlobal.wrappedJSObject;

    const notificationID = "password";
    // keep attention notifications around for longer after a locationchange
    const timeoutMs =
      showOptions.dismissed && showOptions.extraAttr == "attention"
        ? ATTENTION_NOTIFICATION_TIMEOUT_MS
        : NOTIFICATION_TIMEOUT_MS;

    const options = Object.assign(
      {
        timeout: Date.now() + timeoutMs,
        persistWhileVisible: true,
        passwordNotificationType: type,
        hideClose: true,
        eventCallback(topic) {
          switch (topic) {
            case "showing":
              {
                lazy.log.debug("showing");
                currentNotification = this;

                // Record the first time this instance of the doorhanger is shown.
                if (!this.timeShown) {
                  histogram.add(PROMPT_DISPLAYED);
                  Services.obs.notifyObservers(
                    null,
                    "weave:telemetry:histogram",
                    histogramName
                  );
                }

                chromeDoc
                  .getElementById("password-notification-password")
                  .removeAttribute("focused");
                chromeDoc
                  .getElementById("password-notification-username")
                  .removeAttribute("focused");
                chromeDoc
                  .getElementById("password-notification-username")
                  .addEventListener("input", onUsernameInput);
                chromeDoc
                  .getElementById("password-notification-username")
                  .addEventListener("keyup", onKeyUp);
                chromeDoc
                  .getElementById("password-notification-password")
                  .addEventListener("keyup", onKeyUp);
                chromeDoc
                  .getElementById("password-notification-password")
                  .addEventListener("input", onPasswordInput);
                chromeDoc
                  .getElementById("password-notification-username-dropmarker")
                  .addEventListener("click", togglePopup);

                LoginManagerPrompter._getUsernameSuggestions(
                  login,
                  possibleValues?.usernames
                ).then(usernameSuggestions => {
                  const dropmarker = chromeDoc?.getElementById(
                    "password-notification-username-dropmarker"
                  );
                  if (dropmarker) {
                    dropmarker.hidden = !usernameSuggestions.length;
                  }

                  const usernameField = chromeDoc?.getElementById(
                    "password-notification-username"
                  );
                  if (usernameField) {
                    usernameField.classList.toggle(
                      "ac-has-end-icon",
                      !!usernameSuggestions.length
                    );
                  }
                });

                const toggleBtn = chromeDoc.getElementById(
                  "password-notification-visibilityToggle"
                );

                if (
                  Services.prefs.getBoolPref(
                    "signon.rememberSignons.visibilityToggle"
                  )
                ) {
                  toggleBtn.addEventListener("command", onVisibilityToggle);

                  toggleBtn.setAttribute("label", togglePassword.label);
                  toggleBtn.setAttribute("accesskey", togglePassword.accessKey);

                  const hideToggle =
                    lazy.LoginHelper.isPrimaryPasswordSet() ||
                    // Don't show the toggle when the login was autofilled
                    !!autoFilledLoginGuid ||
                    // Dismissed-by-default prompts should still show the toggle.
                    (this.timeShown && this.wasDismissed) ||
                    // If we are only adding a username then the password is
                    // one that is already saved and we don't want to reveal
                    // it as the submitter of this form may not be the account
                    // owner, they may just be using the saved password.
                    (messageStringID ==
                      "password-manager-update-login-add-username" &&
                      login.timePasswordChanged <
                        Date.now() - VISIBILITY_TOGGLE_MAX_PW_AGE_MS);
                  toggleBtn.hidden = hideToggle;
                }

                let popup = chromeDoc.getElementById("PopupAutoComplete");
                popup.onUsernameSelect = onUsernameSelect;
                popup.onPasswordSelect = onPasswordSelect;

                LoginManagerPrompter._setUsernameAutocomplete(
                  login,
                  possibleValues?.usernames
                );
              }
              break;
            case "shown": {
              lazy.log.debug("shown");
              writeDataToUI();
              const anchorIcon = this.anchorElement;
              if (anchorIcon && this.options.extraAttr == "attention") {
                anchorIcon.removeAttribute("extraAttr");
                delete this.options.extraAttr;
              }
              break;
            }
            case "dismissed":
              // Note that this can run after `showing` but before `shown` upon tab switch.
              this.wasDismissed = true;
            // Fall through.
            case "removed": {
              // Note that this can run after `showing` and `shown` for the
              // notification it's replacing.
              lazy.log.debug(topic);
              currentNotification = null;

              const usernameField = chromeDoc.getElementById(
                "password-notification-username"
              );
              usernameField.removeEventListener("input", onUsernameInput);
              usernameField.removeEventListener("keyup", onKeyUp);
              const passwordField = chromeDoc.getElementById(
                "password-notification-password"
              );
              passwordField.removeEventListener("input", onPasswordInput);
              passwordField.removeEventListener("keyup", onKeyUp);
              passwordField.removeEventListener("command", onVisibilityToggle);
              chromeDoc
                .getElementById("password-notification-username-dropmarker")
                .removeEventListener("click", togglePopup);
              break;
            }
          }
          return false;
        },
      },
      showOptions
    );

    const notification = PopupNotifications.show(
      browser,
      notificationID,
      promptMessage,
      "password-notification-icon",
      mainAction,
      secondaryActions,
      options
    );

    if (notifySaved) {
      showConfirmation(
        browser,
        "confirmation-hint-password-created",
        "password-notification-icon"
      );
    }

    return notification;
  }

  /**
   * Called when we think we detect a password or username change for
   * an existing login, when the form being submitted contains multiple
   * password fields.
   *
   * @param {Element} aBrowser
   *                  The browser element that the request came from.
   * @param {nsILoginInfo} aOldLogin
   *                       The old login we may want to update.
   * @param {nsILoginInfo} aNewLogin
   *                       The new login from the page form.
   * @param {boolean} [dismissed = false]
   *                  If the prompt should be automatically dismissed on being shown.
   * @param {boolean} [notifySaved = false]
   *                  Whether the notification should indicate that a login has been saved
   * @param {string} [autoSavedLoginGuid = ""]
   *                 A guid value for the old login to be removed if the changes match it
   *                 to a different login
   * @param {object?} possibleValues
   *                 Contains values from anything that we think, but are not sure, might be
   *                 a username or password.  Has two properties, 'usernames' and 'passwords'.
   * @param {Set<String>} possibleValues.usernames
   * @param {Set<String>} possibleValues.passwords
   */
  promptToChangePassword(
    aBrowser,
    aOldLogin,
    aNewLogin,
    dismissed = false,
    notifySaved = false,
    autoSavedLoginGuid = "",
    autoFilledLoginGuid = "",
    possibleValues = undefined
  ) {
    const login = aOldLogin.clone();
    login.origin = aNewLogin.origin;
    login.formActionOrigin = aNewLogin.formActionOrigin;
    login.password = aNewLogin.password;
    login.username = aNewLogin.username;

    let messageStringID;
    if (
      aOldLogin.username === "" &&
      login.username !== "" &&
      login.password == aOldLogin.password
    ) {
      // If the saved password matches the password we're prompting with then we
      // are only prompting to let the user add a username since there was one in
      // the form. Change the message so the purpose of the prompt is clearer.
      messageStringID = "password-manager-update-login-add-username";
    }

    const notification = LoginManagerPrompter._showLoginCaptureDoorhanger(
      aBrowser,
      login,
      "password-change",
      {
        dismissed,
        extraAttr: notifySaved ? "attention" : "",
      },
      possibleValues,
      {
        notifySaved,
        messageStringID,
        autoSavedLoginGuid,
        autoFilledLoginGuid,
      }
    );

    const oldGUID = aOldLogin.QueryInterface(Ci.nsILoginMetaInfo).guid;
    Services.obs.notifyObservers(
      aNewLogin,
      "passwordmgr-prompt-change",
      oldGUID
    );

    return {
      dismiss() {
        const { PopupNotifications } = aBrowser.ownerGlobal.wrappedJSObject;
        PopupNotifications.remove(notification);
      },
    };
  }

  /**
   * Called when we detect a password change in a form submission, but we
   * don't know which existing login (username) it's for. Asks the user
   * to select a username and confirm the password change.
   *
   * Note: The caller doesn't know the username for aNewLogin, so this
   *       function fills in .username and .usernameField with the values
   *       from the login selected by the user.
   */
  promptToChangePasswordWithUsernames(browser, logins, aNewLogin) {
    lazy.log.debug(
      `Prompting user to change passowrd for username with count: ${logins.length}.`
    );

    const noUsernamePlaceholder = lazy.l10n.formatValueSync(
      "password-manager-no-username-placeholder"
    );
    const usernames = logins.map(l => l.username || noUsernamePlaceholder);
    const dialogText = lazy.l10n.formatValueSync(
      "password-manager-select-username"
    );
    const dialogTitle = lazy.l10n.formatValueSync(
      "password-manager-confirm-password-change"
    );
    const selectedIndex = { value: null };

    // If user selects ok, outparam.value is set to the index
    // of the selected username.
    const ok = Services.prompt.select(
      browser.ownerGlobal,
      dialogTitle,
      dialogText,
      usernames,
      selectedIndex
    );
    if (ok) {
      // Now that we know which login to use, modify its password.
      const selectedLogin = logins[selectedIndex.value];
      lazy.log.debug(`Updating password for origin: ${aNewLogin.origin}.`);
      const newLoginWithUsername = Cc[
        "@mozilla.org/login-manager/loginInfo;1"
      ].createInstance(Ci.nsILoginInfo);
      newLoginWithUsername.init(
        aNewLogin.origin,
        aNewLogin.formActionOrigin,
        aNewLogin.httpRealm,
        selectedLogin.username,
        aNewLogin.password,
        selectedLogin.usernameField,
        aNewLogin.passwordField
      );
      LoginManagerPrompter._updateLogin(selectedLogin, newLoginWithUsername);
    }
  }

  /* ---------- Internal Methods ---------- */

  /**
   * Helper method to update and persist an existing nsILoginInfo object with new property values.
   */
  static _updateLogin(login, aNewLogin) {
    const now = Date.now();
    const propBag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
      Ci.nsIWritablePropertyBag
    );
    propBag.setProperty("formActionOrigin", aNewLogin.formActionOrigin);
    propBag.setProperty("origin", aNewLogin.origin);
    propBag.setProperty("password", aNewLogin.password);
    propBag.setProperty("username", aNewLogin.username);
    // Explicitly set the password change time here (even though it would
    // be changed automatically), to ensure that it's exactly the same
    // value as timeLastUsed.
    propBag.setProperty("timePasswordChanged", now);
    propBag.setProperty("timeLastUsed", now);
    propBag.setProperty("timesUsedIncrement", 1);
    // Note that we don't call `recordPasswordUse` so telemetry won't record a
    // use in this case though that is normally correct since we would instead
    // record the save/update in a separate probe and recording it in both would
    // be wrong.
    Services.logins.modifyLogin(login, propBag);
  }

  /**
   * Retrieves the message of the given id from fluent
   * and extracts the label and accesskey
   *
   * @param {String} id message id
   * @returns label and accesskey
   */
  static getLabelAndAccessKey(id) {
    const msg = lazy.l10n.formatMessagesSync([id])[0];
    return {
      label: msg.attributes.find(x => x.name == "label").value,
      accessKey: msg.attributes.find(x => x.name == "accesskey").value,
    };
  }

  /**
   * Converts a login's origin field to a short string for
   * prompting purposes. Eg, "http://foo.com" --> "foo.com", or
   * "ftp://www.site.co.uk" --> "site.co.uk".
   */
  static _getShortDisplayHost(aURIString) {
    let displayHost;

    const idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
      Ci.nsIIDNService
    );
    try {
      const uri = Services.io.newURI(aURIString);
      const baseDomain = Services.eTLD.getBaseDomain(uri);
      displayHost = idnService.convertToDisplayIDN(baseDomain);
    } catch (e) {
      lazy.log.warn(`Couldn't process supplied URIString: ${aURIString}`);
    }

    if (!displayHost) {
      displayHost = aURIString;
    }

    return displayHost;
  }

  /**
   * This function looks for existing logins that can be updated
   * to match a submitted login, instead of creating a new one.
   *
   * Given a login and a loginList, it filters the login list
   * to find every login with either:
   * - the same username as aLogin
   * - the same password as aLogin and an empty username
   *   so the user can add a username.
   * - the same guid as the given login when it has an empty username
   *
   * @param {nsILoginInfo} aLogin
   *                       login to use as filter.
   * @param {nsILoginInfo[]} aLoginList
   *                         Array of logins to filter.
   * @param {String} includeGUID
   *                 guid value for login that not be filtered out
   * @returns {nsILoginInfo[]} the filtered array of logins.
   */
  static _filterUpdatableLogins(aLogin, aLoginList, includeGUID) {
    return aLoginList.filter(
      l =>
        l.username == aLogin.username ||
        (l.password == aLogin.password && !l.username) ||
        (includeGUID && includeGUID == l.guid)
    );
  }

  /**
   * Set the values that will be used the next time the username autocomplete popup is opened.
   *
   * @param {nsILoginInfo} login - used only for its information about the current domain.
   * @param {Set<String>?} possibleUsernames - values that we believe may be new/changed login usernames.
   */
  static async _setUsernameAutocomplete(login, possibleUsernames = new Set()) {
    const result = Cc[
      "@mozilla.org/autocomplete/simple-result;1"
    ].createInstance(Ci.nsIAutoCompleteSimpleResult);
    result.setDefaultIndex(0);

    const usernames = await this._getUsernameSuggestions(
      login,
      possibleUsernames
    );
    for (const { text, style } of usernames) {
      const value = text;
      const comment = "";
      const image = "";
      const _style = style;
      result.appendMatch(value, comment, image, _style);
    }

    result.setSearchResult(
      usernames.length
        ? Ci.nsIAutoCompleteResult.RESULT_SUCCESS
        : Ci.nsIAutoCompleteResult.RESULT_NOMATCH
    );

    lazy.usernameAutocompleteSearch.overrideNextResult(result);
  }

  /**
   * @param {nsILoginInfo} login - used only for its information about the current domain.
   * @param {Set<String>?} possibleUsernames - values that we believe may be new/changed login usernames.
   *
   * @returns {object[]} an ordered list of usernames to be used the next time the username autocomplete popup is opened.
   */
  static async _getUsernameSuggestions(login, possibleUsernames = new Set()) {
    if (!Services.prefs.getBoolPref("signon.capture.inputChanges.enabled")) {
      return [];
    }

    // Don't reprompt for Primary Password, as we already prompted at least once
    // to show the doorhanger if it is locked
    if (!Services.logins.isLoggedIn) {
      return [];
    }

    const baseDomainLogins = await Services.logins.searchLoginsAsync({
      origin: login.origin,
      schemeUpgrades: lazy.LoginHelper.schemeUpgrades,
      acceptDifferentSubdomains: true,
    });

    const saved = baseDomainLogins.map(login => {
      return { text: login.username, style: "login" };
    });
    const possible = [...possibleUsernames].map(username => {
      return { text: username, style: "possible-username" };
    });

    return possible
      .concat(saved)
      .reduce((acc, next) => {
        const alreadyInAcc =
          acc.findIndex(entry => entry.text == next.text) != -1;
        if (!alreadyInAcc) {
          acc.push(next);
        } else if (next.style == "possible-username") {
          const existingIndex = acc.findIndex(entry => entry.text == next.text);
          acc[existingIndex] = next;
        }
        return acc;
      }, [])
      .filter(suggestion => !!suggestion.text);
  }
}

// Add this observer once for the process.
Services.obs.addObserver(observer, "autocomplete-did-enter-text");

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  return lazy.LoginHelper.createLogger("LoginManagerPrompter");
});
PK
!<�c����%modules/LoginManagerTelemetry.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Provides the logic for recording all password manager related telemetry data.
 */
export class LoginManagerTelemetry {
  static recordAutofillResult(result) {
    Glean.pwmgr.formAutofillResult[result].add(1);
    LoginManagerLegacyTelemetry.recordAutofillResult(result);
  }
}

/**
 * Until the password manager related measurements are fully migrated to Glean,
 * we need to collect the data in both systems (Legacy Telemetry and Glean) for now.
 * Not all new Glean metric can be mirrored automatically (using the property telemetry_mirror in metrics.yaml).
 * Therefore, we need to manually call the Legacy Telemetry API calls in this class.
 * Once we have collected enough data for all probes in the Glean system, we can remove this class and its references.
 */
class LoginManagerLegacyTelemetry {
  static HISTOGRAM_AUTOFILL_RESULT = "PWMGR_FORM_AUTOFILL_RESULT";
  static AUTOFILL_RESULT = {
    filled: 0,
    no_password_field: 1,
    password_disabled_readonly: 2,
    no_logins_fit: 3,
    no_saved_logins: 4,
    existing_password: 5,
    existing_username: 6,
    multiple_logins: 7,
    no_autofill_forms: 8,
    autocomplete_off: 9,
    insecure: 10,
    password_autocomplete_new_password: 11,
    type_no_longer_password: 12,
    form_in_crossorigin_subframe: 13,
    filled_username_only_form: 14,
  };

  static convertToAutofillResultNumber(result) {
    return LoginManagerLegacyTelemetry.AUTOFILL_RESULT[result];
  }

  static recordAutofillResult(result) {
    const autofillResultNumber =
      LoginManagerLegacyTelemetry.convertToAutofillResultNumber(result);
    Services.telemetry
      .getHistogramById(LoginManagerLegacyTelemetry.HISTOGRAM_AUTOFILL_RESULT)
      .add(autofillResultNumber);
  }
}
export default LoginManagerTelemetry;
PK
!<����b-b-modules/LoginRecipes.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const REQUIRED_KEYS = ["hosts"];
const OPTIONAL_KEYS = [
  "description",
  "notPasswordSelector",
  "notUsernameSelector",
  "passwordSelector",
  "pathRegex",
  "usernameSelector",
  "schema",
  "id",
  "last_modified",
];
const SUPPORTED_KEYS = REQUIRED_KEYS.concat(OPTIONAL_KEYS);

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "log", () =>
  lazy.LoginHelper.createLogger("LoginRecipes")
);

/**
 * Create an instance of the object to manage recipes in the parent process.
 * Consumers should wait until {@link initializationPromise} resolves before
 * calling methods on the object.
 *
 * @constructor
 * @param {String} [aOptions.defaults=null] the URI to load the recipes from.
 *                                          If it's null, nothing is loaded.
 *
 */
export function LoginRecipesParent(aOptions = { defaults: null }) {
  if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
    throw new Error(
      "LoginRecipesParent should only be used from the main process"
    );
  }
  this._defaults = aOptions.defaults;
  this.reset();
}

LoginRecipesParent.prototype = {
  /**
   * Promise resolved with an instance of itself when the module is ready.
   *
   * @type {Promise}
   */
  initializationPromise: null,

  /**
   * @type {bool} Whether default recipes were loaded at construction time.
   */
  _defaults: null,

  /**
   * @type {Map} Map of hosts (including non-default port numbers) to Sets of recipes.
   *             e.g. "example.com:8080" => Set({...})
   */
  _recipesByHost: null,

  /**
   * @type {Object} Instance of Remote Settings client that has access to the
   *                "password-recipes" collection
   */
  _rsClient: null,

  /**
   * @param {Object} aRecipes an object containing recipes to load for use. The object
   *                          should be compatible with JSON (e.g. no RegExp).
   * @return {Promise} resolving when the recipes are loaded
   */
  load(aRecipes) {
    let recipeErrors = 0;
    for (let rawRecipe of aRecipes.siteRecipes) {
      try {
        rawRecipe.pathRegex = rawRecipe.pathRegex
          ? new RegExp(rawRecipe.pathRegex)
          : undefined;
        this.add(rawRecipe);
      } catch (e) {
        recipeErrors++;
        lazy.log.error("Error loading recipe.", rawRecipe, e);
      }
    }
    if (recipeErrors) {
      return Promise.reject(`There were ${recipeErrors} recipe error(s)`);
    }
    return Promise.resolve();
  },

  /**
   * Reset the set of recipes to the ones from the time of construction.
   */
  reset() {
    lazy.log.debug("Resetting recipes with defaults:", this._defaults);
    this._recipesByHost = new Map();
    if (this._defaults) {
      let initPromise;
      /**
       * Both branches rely on a JSON dump of the Remote Settings collection, packaged both in Desktop and Android.
       * The «legacy» mode will read the dump directly from the packaged resources.
       * With Remote Settings, the dump is used to initialize the local database without network,
       * and the list of password recipes can be refreshed without restarting and without software update.
       */
      if (lazy.LoginHelper.remoteRecipesEnabled) {
        if (!this._rsClient) {
          this._rsClient = lazy.RemoteSettings(
            lazy.LoginHelper.remoteRecipesCollection
          );
          // Set up sync observer to update local recipes from Remote Settings recipes
          this._rsClient.on("sync", event => this.onRemoteSettingsSync(event));
        }
        initPromise = this._rsClient.get();
      } else if (this._defaults.startsWith("resource://")) {
        initPromise = fetch(this._defaults)
          .then(resp => resp.json())
          .then(({ data }) => data);
      } else {
        lazy.log.error(
          "Invalid recipe path found, setting empty recipes list!"
        );
        initPromise = new Promise(() => []);
      }
      this.initializationPromise = initPromise.then(async siteRecipes => {
        Services.ppmm.broadcastAsyncMessage("clearRecipeCache");
        await this.load({ siteRecipes });
        return this;
      });
    } else {
      this.initializationPromise = Promise.resolve(this);
    }
  },

  /**
   * Validate the recipe is sane and then add it to the set of recipes.
   *
   * @param {Object} recipe
   */
  add(recipe) {
    let recipeKeys = Object.keys(recipe);
    let unknownKeys = recipeKeys.filter(key => !SUPPORTED_KEYS.includes(key));
    if (unknownKeys.length) {
      throw new Error(
        "The following recipe keys aren't supported: " + unknownKeys.join(", ")
      );
    }

    let missingRequiredKeys = REQUIRED_KEYS.filter(
      key => !recipeKeys.includes(key)
    );
    if (missingRequiredKeys.length) {
      throw new Error(
        "The following required recipe keys are missing: " +
          missingRequiredKeys.join(", ")
      );
    }

    if (!Array.isArray(recipe.hosts)) {
      throw new Error("'hosts' must be a array");
    }

    if (!recipe.hosts.length) {
      throw new Error("'hosts' must be a non-empty array");
    }

    if (recipe.pathRegex && recipe.pathRegex.constructor.name != "RegExp") {
      throw new Error("'pathRegex' must be a regular expression");
    }

    const OPTIONAL_STRING_PROPS = [
      "description",
      "passwordSelector",
      "usernameSelector",
    ];
    for (let prop of OPTIONAL_STRING_PROPS) {
      if (recipe[prop] && typeof recipe[prop] != "string") {
        throw new Error(`'${prop}' must be a string`);
      }
    }

    // Add the recipe to the map for each host
    for (let host of recipe.hosts) {
      if (!this._recipesByHost.has(host)) {
        this._recipesByHost.set(host, new Set());
      }
      this._recipesByHost.get(host).add(recipe);
    }
  },

  /**
   * Currently only exact host matches are returned but this will eventually handle parent domains.
   *
   * @param {String} aHost (e.g. example.com:8080 [non-default port] or sub.example.com)
   * @return {Set} of recipes that apply to the host ordered by host priority
   */
  getRecipesForHost(aHost) {
    let hostRecipes = this._recipesByHost.get(aHost);
    if (!hostRecipes) {
      return new Set();
    }

    return hostRecipes;
  },

  /**
   * Handles the Remote Settings sync event for the "password-recipes" collection.
   *
   * @param {Object} aEvent
   * @param {Array} event.current Records in the "password-recipes" collection after the sync event
   * @param {Array} event.created Records that were created with this particular sync
   * @param {Array} event.updated Records that were updated with this particular sync
   * @param {Array} event.deleted Records that were deleted with this particular sync
   */
  onRemoteSettingsSync(aEvent) {
    this._recipesByHost = new Map();
    let {
      data: { current },
    } = aEvent;
    let recipes = {
      siteRecipes: current,
    };
    Services.ppmm.broadcastAsyncMessage("clearRecipeCache");
    this.load(recipes);
  },
};

export const LoginRecipesContent = {
  _recipeCache: new WeakMap(),

  _clearRecipeCache() {
    lazy.log.debug("Clearing recipe cache.");
    this._recipeCache = new WeakMap();
  },

  /**
   * Locally caches recipes for a given host.
   *
   * @param {String} aHost (e.g. example.com:8080 [non-default port] or sub.example.com)
   * @param {Object} win - the window of the host
   * @param {Set} recipes - recipes that apply to the host
   */
  cacheRecipes(aHost, win, recipes) {
    let recipeMap = this._recipeCache.get(win);

    if (!recipeMap) {
      recipeMap = new Map();
      this._recipeCache.set(win, recipeMap);
    }

    recipeMap.set(aHost, recipes);
  },

  /**
   * Tries to fetch recipes for a given host, using a local cache if possible.
   * Otherwise, the recipes are cached for later use.
   *
   * @param {String} aHost (e.g. example.com:8080 [non-default port] or sub.example.com)
   * @param {Object} win - the window of the host
   * @return {Set} of recipes that apply to the host
   */
  getRecipes(aHost, win) {
    let recipes;
    const recipeMap = this._recipeCache.get(win);

    if (recipeMap) {
      recipes = recipeMap.get(aHost);

      if (recipes) {
        return recipes;
      }
    }

    if (!Cu.isInAutomation) {
      // this is a blocking call we expect in tests and rarely expect in
      // production, for example when Remote Settings are updated.
      lazy.log.warn(`Falling back to a synchronous message for: ${aHost}.`);
    }
    recipes = Services.cpmm.sendSyncMessage("PasswordManager:findRecipes", {
      formOrigin: aHost,
    })[0];
    this.cacheRecipes(aHost, win, recipes);

    return recipes;
  },

  /**
   * @param {Set} aRecipes - Possible recipes that could apply to the form
   * @param {FormLike} aForm - We use a form instead of just a URL so we can later apply
   * tests to the page contents.
   * @return {Set} a subset of recipes that apply to the form with the order preserved
   */
  _filterRecipesForForm(aRecipes, aForm) {
    let formDocURL = aForm.ownerDocument.location;
    let hostRecipes = aRecipes;
    let recipes = new Set();
    if (!hostRecipes) {
      return recipes;
    }

    for (let hostRecipe of hostRecipes) {
      if (
        hostRecipe.pathRegex &&
        !hostRecipe.pathRegex.test(formDocURL.pathname)
      ) {
        continue;
      }
      recipes.add(hostRecipe);
    }

    return recipes;
  },

  /**
   * Given a set of recipes that apply to the host, choose the one most applicable for
   * overriding login fields in the form.
   *
   * @param {Set} aRecipes The set of recipes to consider for the form
   * @param {FormLike} aForm The form where login fields exist.
   * @return {Object} The recipe that is most applicable for the form.
   */
  getFieldOverrides(aRecipes, aForm) {
    let recipes = this._filterRecipesForForm(aRecipes, aForm);
    lazy.log.debug(`Filtered recipes size: ${recipes.size}.`);
    if (!recipes.size) {
      return null;
    }

    let chosenRecipe = null;
    // Find the first (most-specific recipe that involves field overrides).
    for (let recipe of recipes) {
      if (
        !recipe.usernameSelector &&
        !recipe.passwordSelector &&
        !recipe.notUsernameSelector &&
        !recipe.notPasswordSelector
      ) {
        continue;
      }

      chosenRecipe = recipe;
      break;
    }

    return chosenRecipe;
  },

  /**
   * @param {HTMLElement} aParent the element to query for the selector from.
   * @param {CSSSelector} aSelector the CSS selector to query for the login field.
   * @return {HTMLElement|null}
   */
  queryLoginField(aParent, aSelector) {
    if (!aSelector) {
      return null;
    }
    let field = aParent.ownerDocument.querySelector(aSelector);
    if (!field) {
      lazy.log.debug(`Login field selector wasn't matched: ${aSelector}.`);
      return null;
    }
    // ownerGlobal doesn't exist in content privileged windows.
    if (
      // eslint-disable-next-line mozilla/use-ownerGlobal
      !aParent.ownerDocument.defaultView.HTMLInputElement.isInstance(field)
    ) {
      lazy.log.warn(
        `Login field with selector ${aSelector} isn't an <input> so ignoring it.`
      );
      return null;
    }
    return field;
  },
};
PK
!<0� 5�
�
"modules/LoginRelatedRealms.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  let logger = lazy.LoginHelper.createLogger("LoginRelatedRealms");
  return logger;
});

export class LoginRelatedRealmsParent extends JSWindowActorParent {
  /**
   * @type RemoteSettingsClient
   *
   * @memberof LoginRelatedRealmsParent
   */
  _sharedCredentialsClient = null;
  /**
   * @type string[][]
   *
   * @memberof LoginRelatedRealmsParent
   */
  _relatedDomainsList = [[]];

  /**
   * Handles the Remote Settings sync event
   *
   * @param {Object} aEvent
   * @param {Array} aEvent.current Records that are currently in the collection after the sync event
   * @param {Array} aEvent.created Records that were created
   * @param {Array} aEvent.updated Records that were updated
   * @param {Array} aEvent.deleted Records that were deleted
   * @memberof LoginRelatedRealmsParent
   */
  onRemoteSettingsSync(aEvent) {
    let {
      data: { current },
    } = aEvent;
    this._relatedDomainsList = current;
  }

  async getSharedCredentialsCollection() {
    if (!this._sharedCredentialsClient) {
      this._sharedCredentialsClient = lazy.RemoteSettings(
        lazy.LoginHelper.relatedRealmsCollection
      );
      this._sharedCredentialsClient.on("sync", event =>
        this.onRemoteSettingsSync(event)
      );
      this._relatedDomainsList = await this._sharedCredentialsClient.get();
    }
    return this._relatedDomainsList;
  }

  /**
   * Determine if there are any related realms of this `formOrigin` using the related realms collection
   * @param {string} formOrigin A form origin
   * @return {string[]} filteredRealms An array of domains related to the `formOrigin`
   * @async
   * @memberof LoginRelatedRealmsParent
   */
  async findRelatedRealms(formOrigin) {
    try {
      let formOriginURI = Services.io.newURI(formOrigin);
      let originDomain = formOriginURI.host;
      let [{ relatedRealms } = {}] =
        await this.getSharedCredentialsCollection();
      if (!relatedRealms) {
        return [];
      }
      let filterOriginIndex;
      let shouldInclude = false;
      let filteredRealms = relatedRealms.filter(_realms => {
        for (let relatedOrigin of _realms) {
          // We can't have an origin that matches multiple entries in our related realms collection
          // so we exit the loop early
          if (shouldInclude) {
            return false;
          }
          if (Services.eTLD.hasRootDomain(originDomain, relatedOrigin)) {
            shouldInclude = true;
            break;
          }
        }
        return shouldInclude;
      });
      // * Filtered realms is a nested array due to its structure in Remote Settings
      filteredRealms = filteredRealms.flat();

      filterOriginIndex = filteredRealms.indexOf(originDomain);
      // Removing the current formOrigin match if it exists in the related realms
      // so that we don't return duplicates when we search for logins
      if (filterOriginIndex !== -1) {
        filteredRealms.splice(filterOriginIndex, 1);
      }
      return filteredRealms;
    } catch (e) {
      lazy.log.error(e);
      return [];
    }
  }
}
PK
!<PH>tII$modules/MainProcessSingleton.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export function MainProcessSingleton() {}
MainProcessSingleton.prototype = {
  classID: Components.ID("{0636a680-45cb-11e4-916c-0800200c9a66}"),
  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
  ]),

  observe(subject, topic) {
    switch (topic) {
      case "app-startup": {
        // Imported for side-effects.
        ChromeUtils.importESModule(
          "resource://gre/modules/CustomElementsListener.sys.mjs"
        );

        Services.ppmm.loadProcessScript(
          "chrome://global/content/process-content.js",
          true
        );
        break;
      }
    }
  },
};
PK
!<C�JX33modules/Manifest.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Manifest.sys.mjs is the top level api for managing installed web applications
 * https://www.w3.org/TR/appmanifest/
 *
 * It is used to trigger the installation of a web application via .install()
 * and to access the manifest data (including icons).
 *
 * TODO:
 *  - Trigger appropriate app installed events
 */

import { ManifestObtainer } from "resource://gre/modules/ManifestObtainer.sys.mjs";

import { ManifestIcons } from "resource://gre/modules/ManifestIcons.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  JSONFile: "resource://gre/modules/JSONFile.sys.mjs",
});

/**
 * Generates an hash for the given string.
 *
 * @note The generated hash is returned in base64 form.  Mind the fact base64
 * is case-sensitive if you are going to reuse this code.
 */
function generateHash(aString, hashAlg) {
  const cryptoHash = Cc["@mozilla.org/security/hash;1"].createInstance(
    Ci.nsICryptoHash
  );
  cryptoHash.init(hashAlg);
  const stringStream = Cc[
    "@mozilla.org/io/string-input-stream;1"
  ].createInstance(Ci.nsIStringInputStream);
  stringStream.data = aString;
  cryptoHash.updateFromStream(stringStream, -1);
  // base64 allows the '/' char, but we can't use it for filenames.
  return cryptoHash.finish(true).replace(/\//g, "-");
}

/**
 * Trims the query parameters from a url
 */
function stripQuery(url) {
  return url.split("?")[0];
}

// Folder in which we store the manifest files
const MANIFESTS_DIR = PathUtils.join(PathUtils.profileDir, "manifests");

// We maintain a list of scopes for installed webmanifests so we can determine
// whether a given url is within the scope of a previously installed manifest
const MANIFESTS_FILE = "manifest-scopes.json";

/**
 * Manifest object
 */

class Manifest {
  constructor(browser, manifestUrl) {
    this._manifestUrl = manifestUrl;
    // The key for this is the manifests URL that is required to be unique.
    // However arbitrary urls are not safe file paths so lets hash it.
    const filename =
      generateHash(manifestUrl, Ci.nsICryptoHash.SHA256) + ".json";
    this._path = PathUtils.join(MANIFESTS_DIR, filename);
    this.browser = browser;
  }

  /**
   * See Bug 1871109
   * This function is called at the beginning of initialize() to check if a given
   * manifest has MD5 based filename, if so we remove it and migrate the content to
   * a new file with SHA256 based name.
   * This is done due to security concern, as MD5 is an outdated hashing algorithm and
   * shouldn't be used anymore
   */
  async removeMD5BasedFilename() {
    const filenameMD5 =
      generateHash(this._manifestUrl, Ci.nsICryptoHash.MD5) + ".json";
    const MD5Path = PathUtils.join(MANIFESTS_DIR, filenameMD5);
    try {
      await IOUtils.copy(MD5Path, this._path, { noOverwrite: true });
    } catch (error) {
      // we are ignoring the failures returned from copy as it should not stop us from
      // installing a new manifest
    }

    // Remove the old MD5 based file unconditionally to ensure it's no longer used
    try {
      await IOUtils.remove(MD5Path);
    } catch {
      // ignore the error in case MD5 based file does not exist
    }
  }

  get browser() {
    return this._browser;
  }

  set browser(aBrowser) {
    this._browser = aBrowser;
  }

  async initialize() {
    await this.removeMD5BasedFilename();
    this._store = new lazy.JSONFile({ path: this._path, saveDelayMs: 100 });
    await this._store.load();
  }

  async prefetch(browser) {
    const manifestData = await ManifestObtainer.browserObtainManifest(browser);
    const icon = await ManifestIcons.browserFetchIcon(
      browser,
      manifestData,
      192
    );
    const data = {
      installed: false,
      manifest: manifestData,
      cached_icon: icon,
    };
    return data;
  }

  async install() {
    const manifestData = await ManifestObtainer.browserObtainManifest(
      this._browser
    );
    this._store.data = {
      installed: true,
      manifest: manifestData,
    };
    Manifests.manifestInstalled(this);
    this._store.saveSoon();
  }

  async icon(expectedSize) {
    if ("cached_icon" in this._store.data) {
      return this._store.data.cached_icon;
    }
    const icon = await ManifestIcons.browserFetchIcon(
      this._browser,
      this._store.data.manifest,
      expectedSize
    );
    // Cache the icon so future requests do not go over the network
    this._store.data.cached_icon = icon;
    this._store.saveSoon();
    return icon;
  }

  get scope() {
    const scope =
      this._store.data.manifest.scope || this._store.data.manifest.start_url;
    return stripQuery(scope);
  }

  get name() {
    return (
      this._store.data.manifest.short_name ||
      this._store.data.manifest.name ||
      this._store.data.manifest.short_url
    );
  }

  get url() {
    return this._manifestUrl;
  }

  get installed() {
    return (this._store.data && this._store.data.installed) || false;
  }

  get start_url() {
    return this._store.data.manifest.start_url;
  }

  get path() {
    return this._path;
  }
}

/*
 * Manifests maintains the list of installed manifests
 */
export var Manifests = {
  async _initialize() {
    if (this._readyPromise) {
      return this._readyPromise;
    }

    // Prevent multiple initializations
    this._readyPromise = (async () => {
      // Make sure the manifests have the folder needed to save into
      await IOUtils.makeDirectory(MANIFESTS_DIR, { ignoreExisting: true });

      // Ensure any existing scope data we have about manifests is loaded
      this._path = PathUtils.join(PathUtils.profileDir, MANIFESTS_FILE);
      this._store = new lazy.JSONFile({ path: this._path });
      await this._store.load();

      // If we don't have any existing data, initialize empty
      if (!this._store.data.hasOwnProperty("scopes")) {
        this._store.data.scopes = new Map();
      }
    })();

    // Cache the Manifest objects creates as they are references to files
    // and we do not want multiple file handles
    this.manifestObjs = new Map();
    return this._readyPromise;
  },

  // When a manifest is installed, we save its scope so we can determine if
  // future visits fall within this manifests scope
  manifestInstalled(manifest) {
    this._store.data.scopes[manifest.scope] = manifest.url;
    this._store.saveSoon();
  },

  // Given a url, find if it is within an installed manifests scope and if so
  // return that manifests url
  findManifestUrl(url) {
    for (let scope in this._store.data.scopes) {
      if (url.startsWith(scope)) {
        return this._store.data.scopes[scope];
      }
    }
    return null;
  },

  // Get the manifest given a url, or if not look for a manifest that is
  // tied to the current page
  async getManifest(browser, manifestUrl) {
    // Ensure we have all started up
    if (!this._readyPromise) {
      await this._initialize();
    }

    // If the client does not already know its manifestUrl, we take the
    // url of the client and see if it matches the scope of any installed
    // manifests
    if (!manifestUrl) {
      const url = stripQuery(browser.currentURI.spec);
      manifestUrl = this.findManifestUrl(url);
    }

    // No matches so no manifest
    if (manifestUrl === null) {
      return null;
    }

    // If we have already created this manifest return cached
    if (this.manifestObjs.has(manifestUrl)) {
      const manifest = this.manifestObjs.get(manifestUrl);
      if (manifest.browser !== browser) {
        manifest.browser = browser;
      }
      return manifest;
    }

    // Otherwise create a new manifest object
    const manifest = new Manifest(browser, manifestUrl);
    this.manifestObjs.set(manifestUrl, manifest);
    await manifest.initialize();
    return manifest;
  },
};
PK
!<��modules/ManifestFinder.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

export var ManifestFinder = {
  /**
   * Check from content process if DOM Window has a conforming
   * manifest link relationship.
   * @param aContent DOM Window to check.
   * @return {Promise<Boolean>}
   */
  contentHasManifestLink(aContent) {
    if (!aContent || isXULBrowser(aContent)) {
      throw new TypeError("Invalid input.");
    }
    return checkForManifest(aContent);
  },

  /**
   * Check from a XUL browser (parent process) if it's content document has a
   * manifest link relationship.
   * @param aBrowser The XUL browser to check.
   * @return {Promise}
   */
  async browserHasManifestLink(aBrowser) {
    if (!isXULBrowser(aBrowser)) {
      throw new TypeError("Invalid input.");
    }

    const actor =
      aBrowser.browsingContext.currentWindowGlobal.getActor("ManifestMessages");
    const reply = await actor.sendQuery("DOM:WebManifest:hasManifestLink");
    return reply.result;
  },
};

function isXULBrowser(aBrowser) {
  if (!aBrowser || !aBrowser.namespaceURI || !aBrowser.localName) {
    return false;
  }
  const XUL_NS =
    "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  return aBrowser.namespaceURI === XUL_NS && aBrowser.localName === "browser";
}

function checkForManifest(aWindow) {
  // Only top-level browsing contexts are valid.
  if (!aWindow || aWindow.top !== aWindow) {
    return false;
  }
  const elem = aWindow.document.querySelector("link[rel~='manifest']");
  // Only if we have an element and a non-empty href attribute.
  if (!elem || !elem.getAttribute("href")) {
    return false;
  }
  return true;
}
PK
!<�\�%�
�
modules/ManifestIcons.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export var ManifestIcons = {
  async browserFetchIcon(aBrowser, manifest, iconSize) {
    const msgKey = "DOM:WebManifest:fetchIcon";

    const actor =
      aBrowser.browsingContext.currentWindowGlobal.getActor("ManifestMessages");
    const reply = await actor.sendQuery(msgKey, { manifest, iconSize });
    if (!reply.success) {
      throw reply.result;
    }
    return reply.result;
  },

  async contentFetchIcon(aWindow, manifest, iconSize) {
    return getIcon(aWindow, toIconArray(manifest.icons), iconSize);
  },
};

function parseIconSize(size) {
  if (size === "any" || size === "") {
    // We want icons without size specified to sorted
    // as the largest available icons
    return Number.MAX_SAFE_INTEGER;
  }
  // 100x100 will parse as 100
  return parseInt(size, 10);
}

// Create an array of icons sorted by their size
function toIconArray(icons) {
  const iconBySize = [];
  icons.forEach(icon => {
    const sizes = "sizes" in icon ? icon.sizes : "";
    sizes.forEach(size => {
      iconBySize.push({ src: icon.src, size: parseIconSize(size) });
    });
  });
  return iconBySize.sort((a, b) => a.size - b.size);
}

async function getIcon(aWindow, icons, expectedSize) {
  if (!icons.length) {
    throw new Error("Could not find valid icon");
  }
  // We start trying the smallest icon that is larger than the requested
  // size and go up to the largest icon if they fail, if all those fail
  // go back down to the smallest
  let index = icons.findIndex(icon => icon.size >= expectedSize);
  if (index === -1) {
    index = icons.length - 1;
  }

  return fetchIcon(aWindow, icons[index].src).catch(() => {
    // Remove all icons with the failed source, the same source
    // may have been used for multiple sizes
    icons = icons.filter(x => x.src !== icons[index].src);
    return getIcon(aWindow, icons, expectedSize);
  });
}

async function fetchIcon(aWindow, src) {
  const iconURL = new aWindow.URL(src, aWindow.location);
  // If this is already a data URL then no need to load it again.
  if (iconURL.protocol === "data:") {
    return iconURL.href;
  }

  const request = new aWindow.Request(iconURL, { mode: "cors" });
  request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_IMAGE);
  const response = await aWindow.fetch(request);
  const blob = await response.blob();
  return new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onloadend = () => resolve(reader.result);
    reader.onerror = reject;
    reader.readAsDataURL(blob);
  });
}
PK
!<܇-���%modules/ManifestMessagesChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.*/
/*
 * Manifest obtainer frame script implementation of:
 * http://www.w3.org/TR/appmanifest/#obtaining
 *
 * It searches a top-level browsing context for
 * a <link rel=manifest> element. Then fetches
 * and processes the linked manifest.
 *
 * BUG: https://bugzilla.mozilla.org/show_bug.cgi?id=1083410
 */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ManifestFinder: "resource://gre/modules/ManifestFinder.sys.mjs",
  ManifestIcons: "resource://gre/modules/ManifestIcons.sys.mjs",
  ManifestObtainer: "resource://gre/modules/ManifestObtainer.sys.mjs",
});

export class ManifestMessagesChild extends JSWindowActorChild {
  receiveMessage(message) {
    switch (message.name) {
      case "DOM:WebManifest:hasManifestLink":
        return this.hasManifestLink();
      case "DOM:ManifestObtainer:Obtain":
        return this.obtainManifest(message.data);
      case "DOM:WebManifest:fetchIcon":
        return this.fetchIcon(message);
    }
    return undefined;
  }

  /**
   * Check if the document includes a link to a web manifest.
   */
  hasManifestLink() {
    const response = makeMsgResponse();
    response.result = lazy.ManifestFinder.contentHasManifestLink(
      this.contentWindow
    );
    response.success = true;
    return response;
  }

  /**
   * Asynchronously obtains a web manifest from this window by using the
   * ManifestObtainer and returns the result.
   * @param {Object} checkConformance True if spec conformance messages should be collected.
   */
  async obtainManifest(options) {
    const { checkConformance } = options;
    const response = makeMsgResponse();
    try {
      response.result = await lazy.ManifestObtainer.contentObtainManifest(
        this.contentWindow,
        { checkConformance }
      );
      response.success = true;
    } catch (err) {
      response.result = serializeError(err);
    }
    return response;
  }

  /**
   * Given a manifest and an expected icon size, ask ManifestIcons
   * to fetch the appropriate icon and send along result
   */
  async fetchIcon({ data: { manifest, iconSize } }) {
    const response = makeMsgResponse();
    try {
      response.result = await lazy.ManifestIcons.contentFetchIcon(
        this.contentWindow,
        manifest,
        iconSize
      );
      response.success = true;
    } catch (err) {
      response.result = serializeError(err);
    }
    return response;
  }
}

/**
 * Utility function to Serializes an JS Error, so it can be transferred over
 * the message channel.
 * FIX ME: https://bugzilla.mozilla.org/show_bug.cgi?id=1172586
 * @param  {Error} aError The error to serialize.
 * @return {Object} The serialized object.
 */
function serializeError(aError) {
  const clone = {
    fileName: aError.fileName,
    lineNumber: aError.lineNumber,
    columnNumber: aError.columnNumber,
    stack: aError.stack,
    message: aError.message,
    name: aError.name,
  };
  return clone;
}

function makeMsgResponse() {
  return {
    success: false,
    result: undefined,
  };
}
PK
!<�X�> modules/ManifestObtainer.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
/*
 * ManifestObtainer is an implementation of:
 * http://w3c.github.io/manifest/#obtaining
 *
 * Exposes 2 public method:
 *
 *  .contentObtainManifest(aContent) - used in content process
 *  .browserObtainManifest(aBrowser) - used in browser/parent process
 *
 * both return a promise. If successful, you get back a manifest object.
 *
 * Import it with URL:
 *   'chrome://global/content/manifestMessages.js'
 *
 * e10s IPC message from this components are handled by:
 *   dom/ipc/manifestMessages.js
 *
 * Which is injected into every browser instance via browser.js.
 */

import { ManifestProcessor } from "resource://gre/modules/ManifestProcessor.sys.mjs";

export var ManifestObtainer = {
  /**
   * Public interface for obtaining a web manifest from a XUL browser, to use
   * on the parent process.
   * @param  {XULBrowser} The browser to check for the manifest.
   * @param {Object} aOptions
   * @param {Boolean} aOptions.checkConformance If spec conformance messages should be collected.
   *                                            Adds proprietary moz_* members to manifest.
   * @return {Promise<Object>} The processed manifest.
   */
  async browserObtainManifest(
    aBrowser,
    aOptions = { checkConformance: false }
  ) {
    if (!isXULBrowser(aBrowser)) {
      throw new TypeError("Invalid input. Expected XUL browser.");
    }

    const actor =
      aBrowser.browsingContext.currentWindowGlobal.getActor("ManifestMessages");

    const reply = await actor.sendQuery(
      "DOM:ManifestObtainer:Obtain",
      aOptions
    );
    if (!reply.success) {
      const error = toError(reply.result);
      throw error;
    }
    return reply.result;
  },
  /**
   * Public interface for obtaining a web manifest from a XUL browser.
   * @param {Window} aContent A content Window from which to extract the manifest.
   * @param {Object} aOptions
   * @param {Boolean} aOptions.checkConformance If spec conformance messages should be collected.
   *                                            Adds proprietary moz_* members to manifest.
   * @return {Promise<Object>} The processed manifest.
   */
  async contentObtainManifest(
    aContent,
    aOptions = { checkConformance: false }
  ) {
    if (!Services.prefs.getBoolPref("dom.manifest.enabled")) {
      throw new Error(
        "Obtaining manifest is disabled by pref: dom.manifest.enabled"
      );
    }
    if (!aContent || isXULBrowser(aContent)) {
      const err = new TypeError("Invalid input. Expected a DOM Window.");
      return Promise.reject(err);
    }
    const response = await fetchManifest(aContent);
    const result = await processResponse(response, aContent, aOptions);
    const clone = Cu.cloneInto(result, aContent);
    return clone;
  },
};

function toError(aErrorClone) {
  let error;
  switch (aErrorClone.name) {
    case "TypeError":
      error = new TypeError();
      break;
    default:
      error = new Error();
  }
  Object.getOwnPropertyNames(aErrorClone).forEach(
    name => (error[name] = aErrorClone[name])
  );
  return error;
}

function isXULBrowser(aBrowser) {
  if (!aBrowser || !aBrowser.namespaceURI || !aBrowser.localName) {
    return false;
  }
  const XUL_NS =
    "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  return aBrowser.namespaceURI === XUL_NS && aBrowser.localName === "browser";
}

/**
 * Asynchronously processes the result of response after having fetched
 * a manifest.
 * @param {Response} aResp Response from fetch().
 * @param {Window} aContentWindow The content window.
 * @return {Promise<Object>} The processed manifest.
 */
async function processResponse(aResp, aContentWindow, aOptions) {
  const badStatus = aResp.status < 200 || aResp.status >= 300;
  if (aResp.type === "error" || badStatus) {
    const msg = `Fetch error: ${aResp.status} - ${aResp.statusText} at ${aResp.url}`;
    throw new Error(msg);
  }
  const text = await aResp.text();
  const args = {
    jsonText: text,
    manifestURL: aResp.url,
    docURL: aContentWindow.location.href,
  };
  const processingOptions = Object.assign({}, args, aOptions);
  const manifest = ManifestProcessor.process(processingOptions);
  return manifest;
}

/**
 * Asynchronously fetches a web manifest.
 * @param {Window} a The content Window from where to extract the manifest.
 * @return {Promise<Object>}
 */
async function fetchManifest(aWindow) {
  if (!aWindow || aWindow.top !== aWindow) {
    const msg = "Window must be a top-level browsing context.";
    throw new Error(msg);
  }
  const elem = aWindow.document.querySelector("link[rel~='manifest']");
  if (!elem || !elem.getAttribute("href")) {
    // There is no actual manifest to fetch, we just return null.
    return new aWindow.Response("null");
  }
  // Throws on malformed URLs
  const manifestURL = new aWindow.URL(elem.href, elem.baseURI);
  const reqInit = {
    credentials: "omit",
    mode: "cors",
  };
  if (elem.crossOrigin === "use-credentials") {
    reqInit.credentials = "include";
  }
  const request = new aWindow.Request(manifestURL, reqInit);
  request.overrideContentPolicyType(Ci.nsIContentPolicy.TYPE_WEB_MANIFEST);
  // Can reject...
  return aWindow.fetch(request);
}
PK
!<n��}K'K'!modules/ManifestProcessor.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
 * ManifestProcessor
 * Implementation of processing algorithms from:
 * http://www.w3.org/2008/webapps/manifest/
 *
 * Creates manifest processor that lets you process a JSON file
 * or individual parts of a manifest object. A manifest is just a
 * standard JS object that has been cleaned up.
 *
 *   .process({jsonText,manifestURL,docURL});
 *
 * Depends on ImageObjectProcessor to process things like
 * icons and splash_screens.
 *
 * TODO: The constructor should accept the UA's supported orientations.
 * TODO: The constructor should accept the UA's supported display modes.
 */

const displayModes = new Set([
  "fullscreen",
  "standalone",
  "minimal-ui",
  "browser",
]);
const orientationTypes = new Set([
  "any",
  "natural",
  "landscape",
  "portrait",
  "portrait-primary",
  "portrait-secondary",
  "landscape-primary",
  "landscape-secondary",
]);
const textDirections = new Set(["ltr", "rtl", "auto"]);

// ValueExtractor is used by the various processors to get values
// from the manifest and to report errors.
import { ValueExtractor } from "resource://gre/modules/ValueExtractor.sys.mjs";

// ImageObjectProcessor is used to process things like icons and images
import { ImageObjectProcessor } from "resource://gre/modules/ImageObjectProcessor.sys.mjs";

const domBundle = Services.strings.createBundle(
  "chrome://global/locale/dom/dom.properties"
);

export var ManifestProcessor = {
  get defaultDisplayMode() {
    return "browser";
  },
  get displayModes() {
    return displayModes;
  },
  get orientationTypes() {
    return orientationTypes;
  },
  get textDirections() {
    return textDirections;
  },
  // process() method processes JSON text into a clean manifest
  // that conforms with the W3C specification. Takes an object
  // expecting the following dictionary items:
  //  * jsonText: the JSON string to be processed.
  //  * manifestURL: the URL of the manifest, to resolve URLs.
  //  * docURL: the URL of the owner doc, for security checks
  //  * checkConformance: boolean. If true, collects any conformance
  //    errors into a "moz_validation" property on the returned manifest.
  process(aOptions) {
    const {
      jsonText,
      manifestURL: aManifestURL,
      docURL: aDocURL,
      checkConformance,
    } = aOptions;

    // The errors get populated by the different process* functions.
    const errors = [];

    let rawManifest = {};
    try {
      rawManifest = JSON.parse(jsonText);
    } catch (e) {
      errors.push({ type: "json", error: e.message });
    }
    if (rawManifest === null) {
      return null;
    }
    if (typeof rawManifest !== "object") {
      const warn = domBundle.GetStringFromName("ManifestShouldBeObject");
      errors.push({ warn });
      rawManifest = {};
    }
    const manifestURL = new URL(aManifestURL);
    const docURL = new URL(aDocURL);
    const extractor = new ValueExtractor(errors, domBundle);
    const imgObjProcessor = new ImageObjectProcessor(
      errors,
      extractor,
      domBundle
    );
    const processedManifest = {
      dir: processDirMember.call(this),
      lang: processLangMember(),
      start_url: processStartURLMember(),
      display: processDisplayMember.call(this),
      orientation: processOrientationMember.call(this),
      name: processNameMember(),
      icons: imgObjProcessor.process(rawManifest, manifestURL, "icons"),
      short_name: processShortNameMember(),
      theme_color: processThemeColorMember(),
      background_color: processBackgroundColorMember(),
    };
    processedManifest.scope = processScopeMember();
    processedManifest.id = processIdMember();
    if (checkConformance) {
      processedManifest.moz_validation = errors;
      processedManifest.moz_manifest_url = manifestURL.href;
    }
    return processedManifest;

    function processDirMember() {
      const spec = {
        objectName: "manifest",
        object: rawManifest,
        property: "dir",
        expectedType: "string",
        trim: true,
      };
      const value = extractor.extractValue(spec);
      if (this.textDirections.has(value)) {
        return value;
      }
      return "auto";
    }

    function processNameMember() {
      const spec = {
        objectName: "manifest",
        object: rawManifest,
        property: "name",
        expectedType: "string",
        trim: true,
      };
      return extractor.extractValue(spec);
    }

    function processShortNameMember() {
      const spec = {
        objectName: "manifest",
        object: rawManifest,
        property: "short_name",
        expectedType: "string",
        trim: true,
      };
      return extractor.extractValue(spec);
    }

    function processOrientationMember() {
      const spec = {
        objectName: "manifest",
        object: rawManifest,
        property: "orientation",
        expectedType: "string",
        trim: true,
      };
      const value = extractor.extractValue(spec);
      if (
        value &&
        typeof value === "string" &&
        this.orientationTypes.has(value.toLowerCase())
      ) {
        return value.toLowerCase();
      }
      return undefined;
    }

    function processDisplayMember() {
      const spec = {
        objectName: "manifest",
        object: rawManifest,
        property: "display",
        expectedType: "string",
        trim: true,
      };
      const value = extractor.extractValue(spec);
      if (
        value &&
        typeof value === "string" &&
        displayModes.has(value.toLowerCase())
      ) {
        return value.toLowerCase();
      }
      return this.defaultDisplayMode;
    }

    function processScopeMember() {
      const spec = {
        objectName: "manifest",
        object: rawManifest,
        property: "scope",
        expectedType: "string",
        trim: false,
      };
      let scopeURL;
      const startURL = new URL(processedManifest.start_url);
      const defaultScope = new URL(".", startURL).href;
      const value = extractor.extractValue(spec);
      if (value === undefined || value === "") {
        return defaultScope;
      }
      try {
        scopeURL = new URL(value, manifestURL);
      } catch (e) {
        const warn = domBundle.GetStringFromName("ManifestScopeURLInvalid");
        errors.push({ warn });
        return defaultScope;
      }
      if (scopeURL.origin !== docURL.origin) {
        const warn = domBundle.GetStringFromName("ManifestScopeNotSameOrigin");
        errors.push({ warn });
        return defaultScope;
      }
      // If start URL is not within scope of scope URL:
      if (
        startURL.origin !== scopeURL.origin ||
        startURL.pathname.startsWith(scopeURL.pathname) === false
      ) {
        const warn = domBundle.GetStringFromName(
          "ManifestStartURLOutsideScope"
        );
        errors.push({ warn });
        return defaultScope;
      }
      // Drop search params and fragment
      // https://github.com/w3c/manifest/pull/961
      scopeURL.hash = "";
      scopeURL.search = "";
      return scopeURL.href;
    }

    function processStartURLMember() {
      const spec = {
        objectName: "manifest",
        object: rawManifest,
        property: "start_url",
        expectedType: "string",
        trim: false,
      };
      const defaultStartURL = new URL(docURL).href;
      const value = extractor.extractValue(spec);
      if (value === undefined || value === "") {
        return defaultStartURL;
      }
      let potentialResult;
      try {
        potentialResult = new URL(value, manifestURL);
      } catch (e) {
        const warn = domBundle.GetStringFromName("ManifestStartURLInvalid");
        errors.push({ warn });
        return defaultStartURL;
      }
      if (potentialResult.origin !== docURL.origin) {
        const warn = domBundle.GetStringFromName(
          "ManifestStartURLShouldBeSameOrigin"
        );
        errors.push({ warn });
        return defaultStartURL;
      }
      return potentialResult.href;
    }

    function processThemeColorMember() {
      const spec = {
        objectName: "manifest",
        object: rawManifest,
        property: "theme_color",
        expectedType: "string",
        trim: true,
      };
      return extractor.extractColorValue(spec);
    }

    function processBackgroundColorMember() {
      const spec = {
        objectName: "manifest",
        object: rawManifest,
        property: "background_color",
        expectedType: "string",
        trim: true,
      };
      return extractor.extractColorValue(spec);
    }

    function processLangMember() {
      const spec = {
        objectName: "manifest",
        object: rawManifest,
        property: "lang",
        expectedType: "string",
        trim: true,
      };
      return extractor.extractLanguageValue(spec);
    }

    function processIdMember() {
      // the start_url serves as the fallback, in case the id is not specified
      // or in error. A start_url is assured.
      const startURL = new URL(processedManifest.start_url);

      const spec = {
        objectName: "manifest",
        object: rawManifest,
        property: "id",
        expectedType: "string",
        trim: false,
      };
      const extractedValue = extractor.extractValue(spec);

      if (typeof extractedValue !== "string" || extractedValue === "") {
        return startURL.href;
      }

      let appId;
      try {
        appId = new URL(extractedValue, startURL.origin);
      } catch {
        const warn = domBundle.GetStringFromName("ManifestIdIsInvalid");
        errors.push({ warn });
        return startURL.href;
      }

      if (appId.origin !== startURL.origin) {
        const warn = domBundle.GetStringFromName("ManifestIdNotSameOrigin");
        errors.push({ warn });
        return startURL.href;
      }

      return appId.href;
    }
  },
};
PK
!<�x�WWmodules/MatchURLFilters.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Match WebNavigation URL Filters.
export class MatchURLFilters {
  constructor(filters) {
    if (!Array.isArray(filters)) {
      throw new TypeError("filters should be an array");
    }

    if (!filters.length) {
      throw new Error("filters array should not be empty");
    }

    this.filters = filters;
  }

  matches(url) {
    let uri = Services.io.newURI(url);
    // Set uriURL to an empty object (needed because some schemes, e.g. about doesn't support nsIURL).
    let uriURL = {};
    if (uri instanceof Ci.nsIURL) {
      uriURL = uri;
    }

    // Set host to a empty string by default (needed so that schemes without an host,
    // e.g. about, can pass an empty string for host based event filtering as expected).
    let host = "";
    try {
      host = uri.host;
    } catch (e) {
      // 'uri.host' throws an exception with some uri schemes (e.g. about).
    }

    let port;
    try {
      port = uri.port;
    } catch (e) {
      // 'uri.port' throws an exception with some uri schemes (e.g. about),
      // in which case it will be |undefined|.
    }

    let data = {
      // NOTE: This properties are named after the name of their related
      // filters (e.g. `pathContains/pathEquals/...` will be tested against the
      // `data.path` property, and the same is done for the `host`, `query` and `url`
      // components as well).
      path: uriURL.filePath,
      query: uriURL.query,
      host,
      port,
      url,
    };

    // If any of the filters matches, matches returns true.
    return this.filters.some(filter =>
      this.matchURLFilter({ filter, data, uri, uriURL })
    );
  }

  matchURLFilter({ filter, data, uri, uriURL }) {
    // Test for scheme based filtering.
    if (filter.schemes) {
      // Return false if none of the schemes matches.
      if (!filter.schemes.some(scheme => uri.schemeIs(scheme))) {
        return false;
      }
    }

    // Test for exact port matching or included in a range of ports.
    if (filter.ports) {
      let port = data.port;
      if (port === -1) {
        // NOTE: currently defaultPort for "resource" and "chrome" schemes defaults to -1,
        // for "about", "data" and "javascript" schemes defaults to undefined.
        if (["resource", "chrome"].includes(uri.scheme)) {
          port = undefined;
        } else {
          port = Services.io.getDefaultPort(uri.scheme);
        }
      }

      // Return false if none of the ports (or port ranges) is verified
      const portMatch = filter.ports.some(filterPort => {
        if (Array.isArray(filterPort)) {
          let [lower, upper] = filterPort;
          return port >= lower && port <= upper;
        }

        return port === filterPort;
      });

      if (!portMatch) {
        return false;
      }
    }

    // Filters on host, url, path, query:
    // hostContains, hostEquals, hostSuffix, hostPrefix,
    // urlContains, urlEquals, ...
    for (let urlComponent of ["host", "path", "query", "url"]) {
      if (!this.testMatchOnURLComponent({ urlComponent, data, filter })) {
        return false;
      }
    }

    // urlMatches is a regular expression string and it is tested for matches
    // on the "url without the ref".
    if (filter.urlMatches) {
      let urlWithoutRef = uri.specIgnoringRef;
      if (!urlWithoutRef.match(filter.urlMatches)) {
        return false;
      }
    }

    // originAndPathMatches is a regular expression string and it is tested for matches
    // on the "url without the query and the ref".
    if (filter.originAndPathMatches) {
      let urlWithoutQueryAndRef = uri.resolve(uriURL.filePath);
      // The above 'uri.resolve(...)' will be null for some URI schemes
      // (e.g. about).
      // TODO: handle schemes which will not be able to resolve the filePath
      // (e.g. for "about:blank", 'urlWithoutQueryAndRef' should be "about:blank" instead
      // of null)
      if (
        !urlWithoutQueryAndRef ||
        !urlWithoutQueryAndRef.match(filter.originAndPathMatches)
      ) {
        return false;
      }
    }

    return true;
  }

  testMatchOnURLComponent({ urlComponent: key, data, filter }) {
    // Test for equals.
    // NOTE: an empty string should not be considered a filter to skip.
    if (filter[`${key}Equals`] != null) {
      if (data[key] !== filter[`${key}Equals`]) {
        return false;
      }
    }

    // Test for contains.
    if (filter[`${key}Contains`]) {
      let value = (key == "host" ? "." : "") + data[key];
      if (!data[key] || !value.includes(filter[`${key}Contains`])) {
        return false;
      }
    }

    // Test for prefix.
    if (filter[`${key}Prefix`]) {
      if (!data[key] || !data[key].startsWith(filter[`${key}Prefix`])) {
        return false;
      }
    }

    // Test for suffix.
    if (filter[`${key}Suffix`]) {
      if (!data[key] || !data[key].endsWith(filter[`${key}Suffix`])) {
        return false;
      }
    }

    return true;
  }

  serialize() {
    return this.filters;
  }
}
PK
!<]�[g$modules/MemoryNotificationDB.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { NotificationDB } from "./NotificationDB.sys.mjs";

class MemoryNotificationDB extends NotificationDB {
  storageQualifier() {
    return "MemoryNotification";
  }

  async load() {
    this.loaded = true;
  }

  async createStore() {}
  async createFile() {}
  async save() {}
}

new MemoryNotificationDB();
PK
!<��l���#modules/MessageManagerProxy.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// @ts-nocheck TODO: Many references to old types which don't exist anymore.

import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

const { DefaultMap } = ExtensionUtils;

/**
 * Acts as a proxy for a message manager or message manager owner, and
 * tracks docShell swaps so that messages are always sent to the same
 * receiver, even if it is moved to a different <browser>.
 *
 * @param {nsIMessageSender|Element} target
 *        The target message manager on which to send messages, or the
 *        <browser> element which owns it.
 */
export class MessageManagerProxy {
  constructor(target) {
    this.listeners = new DefaultMap(() => new Map());
    this.closed = false;

    if (target instanceof Ci.nsIMessageSender) {
      this.messageManager = target;
    } else {
      this.addListeners(target);
    }

    Services.obs.addObserver(this, "message-manager-close");
  }

  /**
   * Disposes of the proxy object, removes event listeners, and drops
   * all references to the underlying message manager.
   *
   * Must be called before the last reference to the proxy is dropped,
   * unless the underlying message manager or <browser> is also being
   * destroyed.
   */
  dispose() {
    if (this.eventTarget) {
      this.removeListeners(this.eventTarget);
      this.eventTarget = null;
    }
    this.messageManager = null;

    Services.obs.removeObserver(this, "message-manager-close");
  }

  observe(subject, topic) {
    if (topic === "message-manager-close") {
      if (subject === this.messageManager) {
        this.closed = true;
      }
    }
  }

  /**
   * Returns true if the given target is the same as, or owns, the given
   * message manager.
   *
   * @param {nsIMessageSender|MessageManagerProxy|Element} target
   *        The message manager, MessageManagerProxy, or <browser>
   *        element against which to match.
   * @param {nsIMessageSender} messageManager
   *        The message manager against which to match `target`.
   *
   * @returns {boolean}
   *        True if `messageManager` is the same object as `target`, or
   *        `target` is a MessageManagerProxy or <browser> element that
   *        is tied to it.
   */
  static matches(target, messageManager) {
    return (
      target === messageManager || target.messageManager === messageManager
    );
  }

  /**
   * @property {nsIMessageSender|null} messageManager
   *        The message manager that is currently being proxied. This
   *        may change during the life of the proxy object, so should
   *        not be stored elsewhere.
   */

  /**
   * Sends a message on the proxied message manager.
   *
   * @param {Array} args
   *        Arguments to be passed verbatim to the underlying
   *        sendAsyncMessage method.
   * @returns {undefined}
   */
  sendAsyncMessage(...args) {
    if (this.messageManager) {
      return this.messageManager.sendAsyncMessage(...args);
    }

    Cu.reportError(
      `Cannot send message: Other side disconnected: ${uneval(args)}`
    );
  }

  get isDisconnected() {
    return this.closed || !this.messageManager;
  }

  /**
   * Adds a message listener to the current message manager, and
   * transfers it to the new message manager after a docShell swap.
   *
   * @param {string} message
   *        The name of the message to listen for.
   * @param {nsIMessageListener} listener
   *        The listener to add.
   * @param {boolean} [listenWhenClosed = false]
   *        If true, the listener will receive messages which were sent
   *        after the remote side of the listener began closing.
   */
  addMessageListener(message, listener, listenWhenClosed = false) {
    this.messageManager.addMessageListener(message, listener, listenWhenClosed);
    this.listeners.get(message).set(listener, listenWhenClosed);
  }

  /**
   * Adds a message listener from the current message manager.
   *
   * @param {string} message
   *        The name of the message to stop listening for.
   * @param {nsIMessageListener} listener
   *        The listener to remove.
   */
  removeMessageListener(message, listener) {
    this.messageManager.removeMessageListener(message, listener);

    let listeners = this.listeners.get(message);
    listeners.delete(listener);
    if (!listeners.size) {
      this.listeners.delete(message);
    }
  }

  /**
   * Iterates over all of the currently registered message listeners.
   *
   * @private
   */
  *iterListeners() {
    for (let [message, listeners] of this.listeners) {
      for (let [listener, listenWhenClosed] of listeners) {
        yield { message, listener, listenWhenClosed };
      }
    }
  }

  /**
   * Adds docShell swap listeners to the message manager owner.
   *
   * @param {Browser} target
   *        The target element.
   * @private
   */
  addListeners(target) {
    target.addEventListener("SwapDocShells", this);

    this.eventTarget = target;
    this.messageManager = target.messageManager;

    for (let { message, listener, listenWhenClosed } of this.iterListeners()) {
      this.messageManager.addMessageListener(
        message,
        listener,
        listenWhenClosed
      );
    }
  }

  /**
   * Removes docShell swap listeners to the message manager owner.
   *
   * @param {Element} target
   *        The target element.
   * @private
   */
  removeListeners(target) {
    target.removeEventListener("SwapDocShells", this);

    for (let { message, listener } of this.iterListeners()) {
      this.messageManager.removeMessageListener(message, listener);
    }
  }

  handleEvent(event) {
    if (event.type == "SwapDocShells") {
      this.removeListeners(this.eventTarget);
      // The SwapDocShells event is dispatched for both browsers that are being
      // swapped. To avoid double-swapping, register the event handler after
      // both SwapDocShells events have fired.
      this.eventTarget.addEventListener(
        "EndSwapDocShells",
        () => {
          this.addListeners(event.detail);
        },
        { once: true }
      );
    }
  }
}
PK
!<����
�
modules/ModulesPing.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { Log } from "resource://gre/modules/Log.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  TelemetryController: "resource://gre/modules/TelemetryController.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gUpdateTimerManager",
  "@mozilla.org/updates/timer-manager;1",
  "nsIUpdateTimerManager"
);

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetryModules::";

// The default is 1 week.
const MODULES_PING_INTERVAL_SECONDS = 7 * 24 * 60 * 60;
const MODULES_PING_INTERVAL_PREFERENCE =
  "toolkit.telemetry.modulesPing.interval";

const MAX_MODULES_NUM = 512;
const MAX_NAME_LENGTH = 64;
const TRUNCATION_DELIMITER = "\u2026";

export var TelemetryModules = Object.freeze({
  _log: Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX),

  start() {
    // The list of loaded modules is obtainable only when the profiler is enabled.
    // If it isn't, we don't want to send the ping at all.
    if (!AppConstants.MOZ_GECKO_PROFILER) {
      return;
    }

    // Use nsIUpdateTimerManager for a long-duration timer that survives across sessions.
    let interval = Services.prefs.getIntPref(
      MODULES_PING_INTERVAL_PREFERENCE,
      MODULES_PING_INTERVAL_SECONDS
    );
    lazy.gUpdateTimerManager.registerTimer(
      "telemetry_modules_ping",
      this,
      interval,
      interval != 0 // only skip the first interval if the interval is non-0
    );
  },

  /**
   * Called when the 'telemetry_modules_ping' timer fires.
   */
  notify() {
    try {
      Services.telemetry.getLoadedModules().then(
        modules => {
          modules = modules.filter(module => !!module.name.length);

          // Cut the list of modules to MAX_MODULES_NUM entries.
          if (modules.length > MAX_MODULES_NUM) {
            modules = modules.slice(0, MAX_MODULES_NUM);
          }

          // Cut the file names of the modules to MAX_NAME_LENGTH characters.
          for (let module of modules) {
            if (module.name.length > MAX_NAME_LENGTH) {
              module.name =
                module.name.substr(0, MAX_NAME_LENGTH - 1) +
                TRUNCATION_DELIMITER;
            }

            if (
              module.debugName !== null &&
              module.debugName.length > MAX_NAME_LENGTH
            ) {
              module.debugName =
                module.debugName.substr(0, MAX_NAME_LENGTH - 1) +
                TRUNCATION_DELIMITER;
            }

            if (
              module.certSubject !== undefined &&
              module.certSubject.length > MAX_NAME_LENGTH
            ) {
              module.certSubject =
                module.certSubject.substr(0, MAX_NAME_LENGTH - 1) +
                TRUNCATION_DELIMITER;
            }
          }

          lazy.TelemetryController.submitExternalPing(
            "modules",
            {
              version: 1,
              modules,
            },
            {
              addClientId: true,
              addEnvironment: true,
            }
          );
        },
        err => this._log.error("notify - promise failed", err)
      );
    } catch (ex) {
      this._log.error("notify - caught exception", ex);
    }
  },
});
PK
!<��JJ"modules/MozProtocolHandler.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { NetUtil } from "resource://gre/modules/NetUtil.sys.mjs";

export function MozProtocolHandler() {
  XPCOMUtils.defineLazyPreferenceGetter(
    this,
    "urlToLoad",
    "toolkit.mozprotocol.url",
    "https://www.mozilla.org/about/manifesto/"
  );
}

MozProtocolHandler.prototype = {
  scheme: "moz",
  defaultPort: -1,
  protocolFlags: Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD,

  newChannel(uri, loadInfo) {
    const kCanada = "https://www.mozilla.org/contact/communities/canada/";
    let realURL = NetUtil.newURI(
      uri && uri.spec == "moz://eh" ? kCanada : this.urlToLoad
    );
    let channel = Services.io.newChannelFromURIWithLoadInfo(realURL, loadInfo);
    loadInfo.resultPrincipalURI = realURL;
    return channel;
  },

  QueryInterface: ChromeUtils.generateQI(["nsIProtocolHandler"]),
};
PK
!<�v>��modules/NLP.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * NLP, which stands for Natural Language Processing, is a module that provides
 * an entry point to various methods to interface with human language.
 *
 * At least, that's the goal. Eventually. Right now, the find toolbar only really
 * needs the Levenshtein distance algorithm.
 */
export var NLP = {
  /**
   * Calculate the Levenshtein distance between two words.
   * The implementation of this method was heavily inspired by
   * http://locutus.io/php/strings/levenshtein/index.html
   * License: MIT.
   *
   * @param  {String} word1   Word to compare against
   * @param  {String} word2   Word that may be different
   * @param  {Number} costIns The cost to insert a character
   * @param  {Number} costRep The cost to replace a character
   * @param  {Number} costDel The cost to delete a character
   * @return {Number}
   */
  levenshtein(word1 = "", word2 = "", costIns = 1, costRep = 1, costDel = 1) {
    if (word1 === word2) {
      return 0;
    }

    let l1 = word1.length;
    let l2 = word2.length;
    if (!l1) {
      return l2 * costIns;
    }
    if (!l2) {
      return l1 * costDel;
    }

    let p1 = new Array(l2 + 1);
    let p2 = new Array(l2 + 1);

    let i1, i2, c0, c1, c2, tmp;

    for (i2 = 0; i2 <= l2; i2++) {
      p1[i2] = i2 * costIns;
    }

    for (i1 = 0; i1 < l1; i1++) {
      p2[0] = p1[0] + costDel;

      for (i2 = 0; i2 < l2; i2++) {
        c0 = p1[i2] + (word1[i1] === word2[i2] ? 0 : costRep);
        c1 = p1[i2 + 1] + costDel;

        if (c1 < c0) {
          c0 = c1;
        }

        c2 = p2[i2] + costIns;

        if (c2 < c0) {
          c0 = c2;
        }

        p2[i2 + 1] = c0;
      }

      tmp = p1;
      p1 = p2;
      p2 = tmp;
    }

    c0 = p1[l2];

    return c0;
  },
};
PK
!<+����modules/NativeManifests.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Schemas: "resource://gre/modules/Schemas.sys.mjs",
  WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs",
});

const DASHED = AppConstants.platform === "linux";

// Supported native manifest types, with platform-specific slugs.
const TYPES = {
  stdio: DASHED ? "native-messaging-hosts" : "NativeMessagingHosts",
  storage: DASHED ? "managed-storage" : "ManagedStorage",
  pkcs11: DASHED ? "pkcs11-modules" : "PKCS11Modules",
};

const NATIVE_MANIFEST_SCHEMA =
  "chrome://extensions/content/schemas/native_manifest.json";

const REGPATH = "Software\\Mozilla";

export var NativeManifests = {
  _initializePromise: null,
  _lookup: null,

  init() {
    if (!this._initializePromise) {
      let platform = AppConstants.platform;
      if (platform == "win") {
        this._lookup = this._winLookup;
      } else if (platform == "macosx" || platform == "linux") {
        let dirs = [
          Services.dirsvc.get("XREUserNativeManifests", Ci.nsIFile).path,
          Services.dirsvc.get("XRESysNativeManifests", Ci.nsIFile).path,
        ];
        this._lookup = (type, name, context) =>
          this._tryPaths(type, name, dirs, context);
      } else {
        throw new Error(
          `Native manifests are not supported on ${AppConstants.platform}`
        );
      }
      this._initializePromise = lazy.Schemas.load(NATIVE_MANIFEST_SCHEMA);
    }
    return this._initializePromise;
  },

  async _winLookup(type, name, context) {
    const REGISTRY = Ci.nsIWindowsRegKey;
    let regPath = `${REGPATH}\\${TYPES[type]}\\${name}`;
    let path = lazy.WindowsRegistry.readRegKey(
      REGISTRY.ROOT_KEY_CURRENT_USER,
      regPath,
      "",
      REGISTRY.WOW64_64
    );
    if (!path) {
      path = lazy.WindowsRegistry.readRegKey(
        REGISTRY.ROOT_KEY_LOCAL_MACHINE,
        regPath,
        "",
        REGISTRY.WOW64_32
      );
    }
    if (!path) {
      path = lazy.WindowsRegistry.readRegKey(
        REGISTRY.ROOT_KEY_LOCAL_MACHINE,
        regPath,
        "",
        REGISTRY.WOW64_64
      );
    }
    if (!path) {
      return null;
    }

    // Normalize in case the extension used / instead of \.
    path = path.replaceAll("/", "\\");

    let manifest = await this._tryPath(type, path, name, context, true);
    return manifest ? { path, manifest } : null;
  },

  async _tryPath(type, path, name, context, logIfNotFound) {
    let manifest;
    try {
      manifest = await IOUtils.readJSON(path);
    } catch (ex) {
      if (ex instanceof SyntaxError && ex.message.startsWith("JSON.parse:")) {
        Cu.reportError(`Error parsing native manifest ${path}: ${ex.message}`);
        return null;
      }
      if (DOMException.isInstance(ex) && ex.name == "NotFoundError") {
        if (logIfNotFound) {
          Cu.reportError(
            `Error reading native manifest file ${path}: file is referenced in the registry but does not exist`
          );
        }
        return null;
      }
      Cu.reportError(ex);
      return null;
    }
    let normalized = lazy.Schemas.normalize(
      manifest,
      "manifest.NativeManifest",
      context
    );
    if (normalized.error) {
      Cu.reportError(normalized.error);
      return null;
    }
    manifest = normalized.value;

    if (manifest.type !== type) {
      Cu.reportError(
        `Native manifest ${path} has type property ${manifest.type} (expected ${type})`
      );
      return null;
    }
    if (manifest.name !== name) {
      Cu.reportError(
        `Native manifest ${path} has name property ${manifest.name} (expected ${name})`
      );
      return null;
    }
    if (
      type === "stdio" &&
      AppConstants.platform != "win" &&
      !PathUtils.isAbsolute(manifest.path)
    ) {
      // manifest.path is defined for type "stdio" and "pkcs11".
      // stdio requires an absolute path on Linux and macOS,
      // pkcs11 also accepts relative paths.
      Cu.reportError(
        `Native manifest ${path} has relative path value ${manifest.path} (expected absolute path)`
      );
      return null;
    }
    if (
      manifest.allowed_extensions &&
      !manifest.allowed_extensions.includes(context.extension.id)
    ) {
      Cu.reportError(
        `This extension does not have permission to use native manifest ${path}`
      );
      return null;
    }

    return manifest;
  },

  async _tryPaths(type, name, dirs, context) {
    for (let dir of dirs) {
      let path = PathUtils.join(dir, TYPES[type], `${name}.json`);
      let manifest = await this._tryPath(type, path, name, context, false);
      if (manifest) {
        return { path, manifest };
      }
    }
    return null;
  },

  /**
   * Search for a valid native manifest of the given type and name.
   * The directories searched and rules for manifest validation are all
   * detailed in the Native Manifests documentation.
   *
   * @param {string} type The type, one of: "pkcs11", "stdio" or "storage".
   * @param {string} name The name of the manifest to search for.
   * @param {object} context A context object as expected by Schemas.normalize.
   * @returns {object} The contents of the validated manifest, or null if
   *                   no valid manifest can be found for this type and name.
   */
  lookupManifest(type, name, context) {
    return this.init().then(() => this._lookup(type, name, context));
  },
};
PK
!<��J()8)8modules/NativeMessaging.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs";
import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  NativeManifests: "resource://gre/modules/NativeManifests.sys.mjs",
  Subprocess: "resource://gre/modules/Subprocess.sys.mjs",
});

const { ExtensionError, promiseTimeout } = ExtensionUtils;

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "portal",
  "@mozilla.org/extensions/native-messaging-portal;1",
  "nsINativeMessagingPortal"
);

// For a graceful shutdown (i.e., when the extension is unloaded or when it
// explicitly calls disconnect() on a native port), how long we give the native
// application to exit before we start trying to kill it.  (in milliseconds)
const GRACEFUL_SHUTDOWN_TIME = 3000;

// Hard limits on maximum message size that can be read/written
// These are defined in the native messaging documentation, note that
// the write limit is imposed by the "wire protocol" in which message
// boundaries are defined by preceding each message with its length as
// 4-byte unsigned integer so this is the largest value that can be
// represented.  Good luck generating a serialized message that large,
// the practical write limit is likely to be dictated by available memory.
const MAX_READ = 1024 * 1024;
const MAX_WRITE = 0xffffffff;

// Preferences that can lower the message size limits above,
// used for testing the limits.
const PREF_MAX_READ = "webextensions.native-messaging.max-input-message-bytes";
const PREF_MAX_WRITE =
  "webextensions.native-messaging.max-output-message-bytes";

XPCOMUtils.defineLazyPreferenceGetter(lazy, "maxRead", PREF_MAX_READ, MAX_READ);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "maxWrite",
  PREF_MAX_WRITE,
  MAX_WRITE
);

export class NativeApp extends EventEmitter {
  /**
   * @param {BaseContext} context The context that initiated the native app.
   * @param {string} application The identifier of the native app.
   */
  constructor(context, application) {
    super();

    this.context = context;
    this.name = application;

    // We want a close() notification when the window is destroyed.
    this.context.callOnClose(this);

    this.proc = null;
    this.readPromise = null;
    this.sendQueue = [];
    this.writePromise = null;
    this.cleanupStarted = false;
    this.portalSessionHandle = null;

    if ("@mozilla.org/extensions/native-messaging-portal;1" in Cc) {
      if (lazy.portal.shouldUse()) {
        this._initPortal();
        return;
      }
    }

    this.startupPromise = lazy.NativeManifests.lookupManifest(
      "stdio",
      application,
      context
    )
      .then(hostInfo => {
        // Report a generic error to not leak information about whether a native
        // application is installed to addons that do not have the right permission.
        if (!hostInfo) {
          throw new ExtensionError(`No such native application ${application}`);
        }

        let command = hostInfo.manifest.path;
        if (AppConstants.platform == "win") {
          // Normalize in case the extension used / instead of \.
          command = command.replaceAll("/", "\\");

          // Relative paths are only supported on Windows. On Linux and macOS,
          // _tryPath in NativeManifests.sys.mjs enforces that the command path
          // is absolute.
          if (!PathUtils.isAbsolute(command)) {
            // Note: hostInfo.path is an absolute path to the manifest.
            const parentPath = PathUtils.parent(
              hostInfo.path.replaceAll("/", "\\")
            );
            // PathUtils.joinRelative cannot be used because it throws for "..".
            // but command is allowed to contain ".." to traverse the directory.
            command = `${parentPath}\\${command}`;
          }
        }

        let subprocessOpts = {
          command: command,
          arguments: [hostInfo.path, context.extension.id],
          workdir: PathUtils.parent(command),
          stderr: "pipe",
          disclaim: true,
        };

        return lazy.Subprocess.call(subprocessOpts);
      })
      .then(proc => {
        this.startupPromise = null;
        this.proc = proc;
        this._startRead();
        this._startWrite();
        this._startStderrRead();
      })
      .catch(err => {
        this.startupPromise = null;
        Cu.reportError(err instanceof Error ? err : err.message);
        this._cleanup(err);
      });
  }

  _initPortal() {
    this.startupPromise = this._doInitPortal();
  }

  async _doInitPortal() {
    let available = await lazy.portal.available;

    if (!available) {
      this.startupPromise = null;
      let err = new ExtensionError("Native messaging portal is not available");
      Cu.reportError(err);
      this._cleanup(err);
      return;
    }

    try {
      let handle = await lazy.portal.createSession(this.name);
      this.portalSessionHandle = handle;
      let pipes;
      try {
        pipes = await lazy.portal.start(
          handle,
          this.name,
          this.context.extension.id
        );
      } catch (err) {
        if (err.name == "NotFoundError") {
          throw new ExtensionError(`No such native application ${this.name}`);
        } else {
          throw err;
        }
      }
      this.proc = await lazy.Subprocess.connectRunning([
        pipes.stdin,
        pipes.stdout,
        pipes.stderr,
      ]);
      this.startupPromise = null;
      this._startRead();
      this._startWrite();
      this._startStderrRead();
    } catch (err) {
      this.startupPromise = null;
      Cu.reportError(err instanceof Error ? err : err.message);
      this._cleanup(err);
    }
  }

  /**
   * Open a connection to a native messaging host.
   *
   * @param {number} portId A unique internal ID that identifies the port.
   * @param {import("ExtensionParent.sys.mjs").NativeMessenger} port Parent NativeMessenger used to send messages.
   * @returns {import("ExtensionParent.sys.mjs").ParentPort}
   */
  onConnect(portId, port) {
    // eslint-disable-next-line
    this.on("message", (_, message) => {
      port.sendPortMessage(
        portId,
        new StructuredCloneHolder(
          `NativeMessaging/onConnect/${this.name}`,
          null,
          message
        )
      );
    });
    this.once("disconnect", (_, error) => {
      port.sendPortDisconnect(portId, error && new ClonedErrorHolder(error));
    });
    return {
      onPortMessage: holder => this.send(holder),
      onPortDisconnect: () => this.close(),
    };
  }

  /**
   * @param {BaseContext} context The scope from where `message` originates.
   * @param {*} message A message from the extension, meant for a native app.
   * @returns {ArrayBuffer} An ArrayBuffer that can be sent to the native app.
   */
  static encodeMessage(context, message) {
    message = context.jsonStringify(message);
    let buffer = new TextEncoder().encode(message).buffer;
    if (buffer.byteLength > lazy.maxWrite) {
      throw new context.Error("Write too big");
    }
    return buffer;
  }

  // A port is definitely "alive" if this.proc is non-null.  But we have
  // to provide a live port object immediately when connecting so we also
  // need to consider a port alive if proc is null but the startupPromise
  // is still pending.
  get _isDisconnected() {
    return !this.proc && !this.startupPromise;
  }

  _startRead() {
    if (this.readPromise) {
      throw new Error("Entered _startRead() while readPromise is non-null");
    }
    this.readPromise = this.proc.stdout
      .readUint32()
      .then(len => {
        if (len > lazy.maxRead) {
          throw new ExtensionError(
            `Native application tried to send a message of ${len} bytes, which exceeds the limit of ${lazy.maxRead} bytes.`
          );
        }
        return this.proc.stdout.readJSON(len);
      })
      .then(msg => {
        this.emit("message", msg);
        this.readPromise = null;
        this._startRead();
      })
      .catch(err => {
        if (err.errorCode != lazy.Subprocess.ERROR_END_OF_FILE) {
          Cu.reportError(err instanceof Error ? err : err.message);
        }
        this._cleanup(err);
      });
  }

  _startWrite() {
    if (!this.sendQueue.length) {
      return;
    }

    if (this.writePromise) {
      throw new Error("Entered _startWrite() while writePromise is non-null");
    }

    let buffer = this.sendQueue.shift();
    let uintArray = Uint32Array.of(buffer.byteLength);

    this.writePromise = Promise.all([
      this.proc.stdin.write(uintArray.buffer),
      this.proc.stdin.write(buffer),
    ])
      .then(() => {
        this.writePromise = null;
        this._startWrite();
      })
      .catch(err => {
        Cu.reportError(err.message);
        this._cleanup(err);
      });
  }

  _startStderrRead() {
    let proc = this.proc;
    let app = this.name;
    (async function () {
      let partial = "";
      while (true) {
        let data = await proc.stderr.readString();
        if (!data.length) {
          // We have hit EOF, just stop reading
          if (partial) {
            Services.console.logStringMessage(
              `stderr output from native app ${app}: ${partial}`
            );
          }
          break;
        }

        let lines = data.split(/\r?\n/);
        lines[0] = partial + lines[0];
        partial = lines.pop();

        for (let line of lines) {
          Services.console.logStringMessage(
            `stderr output from native app ${app}: ${line}`
          );
        }
      }
    })();
  }

  send(holder) {
    if (this._isDisconnected) {
      throw new ExtensionError("Attempt to postMessage on disconnected port");
    }
    let msg = holder.deserialize(globalThis);
    if (Cu.getClassName(msg, true) != "ArrayBuffer") {
      // This error cannot be triggered by extensions; it indicates an error in
      // our implementation.
      throw new Error(
        "The message to the native messaging host is not an ArrayBuffer"
      );
    }

    let buffer = msg;

    if (buffer.byteLength > lazy.maxWrite) {
      throw new ExtensionError("Write too big");
    }

    this.sendQueue.push(buffer);
    if (!this.startupPromise && !this.writePromise) {
      this._startWrite();
    }
  }

  // Shut down the native application and (by default) signal to the extension
  // that the connect has been disconnected.
  async _cleanup(err, fromExtension = false) {
    if (this.cleanupStarted) {
      return;
    }
    this.cleanupStarted = true;
    this.context.forgetOnClose(this);

    if (!fromExtension) {
      if (err && err.errorCode == lazy.Subprocess.ERROR_END_OF_FILE) {
        err = null;
      }
      this.emit("disconnect", err);
    }

    await this.startupPromise;

    if (this.portalSessionHandle) {
      await this.writePromise;
      await lazy.portal.closeSession(this.portalSessionHandle).then(_ => {
        this.portalSessionHandle = null;
        this.proc = null;
      });
      return;
    }

    if (!this.proc) {
      // Failed to initialize proc in the constructor.
      return;
    }

    // To prevent an uncooperative process from blocking shutdown, we take the
    // following actions, and wait for GRACEFUL_SHUTDOWN_TIME in between.
    //
    // 1. Allow exit by closing the stdin pipe.
    // 2. Allow exit by a kill signal.
    // 3. Allow exit by forced kill signal.
    // 4. Give up and unblock shutdown despite the process still being alive.

    // Close the stdin stream and allow the process to exit on its own.
    // proc.wait() below will resolve once the process has exited gracefully.
    this.proc.stdin.close().catch(err => {
      if (err.errorCode != lazy.Subprocess.ERROR_END_OF_FILE) {
        Cu.reportError(err);
      }
    });
    let exitPromise = Promise.race([
      // 1. Allow the process to exit on its own after closing stdin.
      this.proc.wait().then(() => {
        this.proc = null;
      }),
      promiseTimeout(GRACEFUL_SHUTDOWN_TIME).then(() => {
        if (this.proc) {
          // 2. Kill the process gracefully. 3. Force kill after a timeout.
          this.proc.kill(GRACEFUL_SHUTDOWN_TIME);

          // 4. If the process is still alive after a kill + timeout followed
          // by a forced kill + timeout, give up and just resolve exitPromise.
          //
          // Note that waiting for just one interval is not enough, because the
          // `proc.kill()` is asynchronous, so we need to wait a bit after the
          // kill signal has been sent.
          return promiseTimeout(2 * GRACEFUL_SHUTDOWN_TIME);
        }
      }),
    ]);

    lazy.AsyncShutdown.profileBeforeChange.addBlocker(
      `Native Messaging: Wait for application ${this.name} to exit`,
      exitPromise
    );
  }

  // Called when the Context or Port is closed.
  close() {
    this._cleanup(null, true);
  }

  sendMessage(holder) {
    let responsePromise = new Promise((resolve, reject) => {
      this.once("message", (what, msg) => {
        resolve(msg);
      });
      this.once("disconnect", (what, err) => {
        reject(err);
      });
    });

    let result = this.startupPromise.then(() => {
      // Skip .send() if _cleanup() has been called already;
      // otherwise the error passed to _cleanup/"disconnect" would be hidden by the
      // "Attempt to postMessage on disconnected port" error from this.send().
      if (!this.cleanupStarted) {
        this.send(holder);
      }
      return responsePromise;
    });

    result.then(
      () => {
        this._cleanup();
      },
      () => {
        // Prevent the response promise from being reported as an
        // unchecked rejection if the startup promise fails.
        responsePromise.catch(() => {});

        this._cleanup();
      }
    );

    return result;
  }
}
PK
!<O�<:<:modules/NetUtil.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*-
 * vim: sw=4 ts=4 sts=4 et filetype=javascript
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Necko utilities
 */

// //////////////////////////////////////////////////////////////////////////////
// // Constants

const PR_UINT32_MAX = 0xffffffff;

const BinaryInputStream = Components.Constructor(
  "@mozilla.org/binaryinputstream;1",
  "nsIBinaryInputStream",
  "setInputStream"
);

// //////////////////////////////////////////////////////////////////////////////
// // NetUtil Object

export var NetUtil = {
  /**
   * Function to perform simple async copying from aSource (an input stream)
   * to aSink (an output stream).  The copy will happen on some background
   * thread.  Both streams will be closed when the copy completes.
   *
   * @param aSource
   *        The input stream to read from
   * @param aSink
   *        The output stream to write to
   * @param aCallback [optional]
   *        A function that will be called at copy completion with a single
   *        argument: the nsresult status code for the copy operation.
   *
   * @return An nsIRequest representing the copy operation (for example, this
   *         can be used to cancel the copying).  The consumer can ignore the
   *         return value if desired.
   */
  asyncCopy: function NetUtil_asyncCopy(aSource, aSink, aCallback = null) {
    if (!aSource || !aSink) {
      let exception = new Components.Exception(
        "Must have a source and a sink",
        Cr.NS_ERROR_INVALID_ARG,
        Components.stack.caller
      );
      throw exception;
    }

    // make a stream copier
    var copier = Cc[
      "@mozilla.org/network/async-stream-copier;1"
    ].createInstance(Ci.nsIAsyncStreamCopier2);
    copier.init(
      aSource,
      aSink,
      null /* Default event target */,
      0 /* Default length */,
      true,
      true /* Auto-close */
    );

    var observer;
    if (aCallback) {
      observer = {
        onStartRequest() {},
        onStopRequest(aRequest, aStatusCode) {
          aCallback(aStatusCode);
        },
      };
    } else {
      observer = null;
    }

    // start the copying
    copier.QueryInterface(Ci.nsIAsyncStreamCopier).asyncCopy(observer, null);
    return copier;
  },

  /**
   * Asynchronously opens a source and fetches the response.  While the fetch
   * is asynchronous, I/O may happen on the main thread.  When reading from
   * a local file, prefer using IOUtils methods instead.
   *
   * @param aSource
   *        This argument can be one of the following:
   *         - An options object that will be passed to NetUtil.newChannel.
   *         - An existing nsIChannel.
   *         - An existing nsIInputStream.
   *        Using an nsIURI, nsIFile, or string spec directly is deprecated.
   * @param aCallback
   *        The callback function that will be notified upon completion.  It
   *        will get these arguments:
   *        1) An nsIInputStream containing the data from aSource, if any.
   *        2) The status code from opening the source.
   *        3) Reference to the nsIRequest.
   */
  asyncFetch: function NetUtil_asyncFetch(aSource, aCallback) {
    if (!aSource || !aCallback) {
      let exception = new Components.Exception(
        "Must have a source and a callback",
        Cr.NS_ERROR_INVALID_ARG,
        Components.stack.caller
      );
      throw exception;
    }

    // Create a pipe that will create our output stream that we can use once
    // we have gotten all the data.
    let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
    pipe.init(true, true, 0, PR_UINT32_MAX, null);

    // Create a listener that will give data to the pipe's output stream.
    let listener = Cc[
      "@mozilla.org/network/simple-stream-listener;1"
    ].createInstance(Ci.nsISimpleStreamListener);
    listener.init(pipe.outputStream, {
      onStartRequest() {},
      onStopRequest(aRequest, aStatusCode) {
        pipe.outputStream.close();
        aCallback(pipe.inputStream, aStatusCode, aRequest);
      },
    });

    // Input streams are handled slightly differently from everything else.
    if (aSource instanceof Ci.nsIInputStream) {
      let pump = Cc["@mozilla.org/network/input-stream-pump;1"].createInstance(
        Ci.nsIInputStreamPump
      );
      pump.init(aSource, 0, 0, true);
      pump.asyncRead(listener, null);
      return;
    }

    let channel = aSource;
    if (!(channel instanceof Ci.nsIChannel)) {
      channel = this.newChannel(aSource);
    }

    try {
      channel.asyncOpen(listener);
    } catch (e) {
      let exception = new Components.Exception(
        "Failed to open input source '" + channel.originalURI.spec + "'",
        e.result,
        Components.stack.caller,
        aSource,
        e
      );
      throw exception;
    }
  },

  /**
   * Constructs a new URI for the given spec, character set, and base URI, or
   * an nsIFile.
   *
   * @param aTarget
   *        The string spec for the desired URI or an nsIFile.
   * @param aOriginCharset [optional]
   *        The character set for the URI.  Only used if aTarget is not an
   *        nsIFile.
   * @param aBaseURI [optional]
   *        The base URI for the spec.  Only used if aTarget is not an
   *        nsIFile.
   *
   * @return an nsIURI object.
   */
  newURI: function NetUtil_newURI(aTarget, aOriginCharset, aBaseURI) {
    if (!aTarget) {
      let exception = new Components.Exception(
        "Must have a non-null string spec or nsIFile object",
        Cr.NS_ERROR_INVALID_ARG,
        Components.stack.caller
      );
      throw exception;
    }

    if (aTarget instanceof Ci.nsIFile) {
      return Services.io.newFileURI(aTarget);
    }

    return Services.io.newURI(aTarget, aOriginCharset, aBaseURI);
  },

  /**
   * Constructs a new channel for the given source.
   *
   * Keep in mind that URIs coming from a webpage should *never* use the
   * systemPrincipal as the loadingPrincipal.
   *
   * @param aWhatToLoad
   *        This argument used to be a string spec for the desired URI, an
   *        nsIURI, or an nsIFile.  Now it should be an options object with
   *        the following properties:
   *        {
   *          uri:
   *            The full URI spec string, nsIURI or nsIFile to create the
   *            channel for.
   *            Note that this cannot be an nsIFile if you have to specify a
   *            non-default charset or base URI.  Call NetUtil.newURI first if
   *            you need to construct an URI using those options.
   *          loadingNode:
   *          loadingPrincipal:
   *          triggeringPrincipal:
   *          securityFlags:
   *          contentPolicyType:
   *            These will be used as values for the nsILoadInfo object on the
   *            created channel. For details, see nsILoadInfo in nsILoadInfo.idl
   *          loadUsingSystemPrincipal:
   *            Set this to true to use the system principal as
   *            loadingPrincipal.  This must be omitted if loadingPrincipal or
   *            loadingNode are present.
   *            This should be used with care as it skips security checks.
   *        }
   * @return an nsIChannel object.
   */
  newChannel: function NetUtil_newChannel(aWhatToLoad) {
    // Make sure the API is called using only the options object.
    if (typeof aWhatToLoad != "object" || arguments.length != 1) {
      throw new Components.Exception(
        "newChannel requires a single object argument",
        Cr.NS_ERROR_INVALID_ARG,
        Components.stack.caller
      );
    }

    let {
      uri,
      loadingNode,
      loadingPrincipal,
      loadUsingSystemPrincipal,
      triggeringPrincipal,
      securityFlags,
      contentPolicyType,
    } = aWhatToLoad;

    if (!uri) {
      throw new Components.Exception(
        "newChannel requires the 'uri' property on the options object.",
        Cr.NS_ERROR_INVALID_ARG,
        Components.stack.caller
      );
    }

    if (typeof uri == "string" || uri instanceof Ci.nsIFile) {
      uri = this.newURI(uri);
    }

    if (!loadingNode && !loadingPrincipal && !loadUsingSystemPrincipal) {
      throw new Components.Exception(
        "newChannel requires at least one of the 'loadingNode'," +
          " 'loadingPrincipal', or 'loadUsingSystemPrincipal'" +
          " properties on the options object.",
        Cr.NS_ERROR_INVALID_ARG,
        Components.stack.caller
      );
    }

    if (loadUsingSystemPrincipal === true) {
      if (loadingNode || loadingPrincipal) {
        throw new Components.Exception(
          "newChannel does not accept 'loadUsingSystemPrincipal'" +
            " if the 'loadingNode' or 'loadingPrincipal' properties" +
            " are present on the options object.",
          Cr.NS_ERROR_INVALID_ARG,
          Components.stack.caller
        );
      }
      loadingPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
    } else if (loadUsingSystemPrincipal !== undefined) {
      throw new Components.Exception(
        "newChannel requires the 'loadUsingSystemPrincipal'" +
          " property on the options object to be 'true' or 'undefined'.",
        Cr.NS_ERROR_INVALID_ARG,
        Components.stack.caller
      );
    }

    if (securityFlags === undefined) {
      if (!loadUsingSystemPrincipal) {
        throw new Components.Exception(
          "newChannel requires the 'securityFlags' property on" +
            " the options object unless loading from system principal.",
          Cr.NS_ERROR_INVALID_ARG,
          Components.stack.caller
        );
      }
      securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
    }

    if (contentPolicyType === undefined) {
      if (!loadUsingSystemPrincipal) {
        throw new Components.Exception(
          "newChannel requires the 'contentPolicyType' property on" +
            " the options object unless loading from system principal.",
          Cr.NS_ERROR_INVALID_ARG,
          Components.stack.caller
        );
      }
      contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER;
    }

    let channel = Services.io.newChannelFromURI(
      uri,
      loadingNode || null,
      loadingPrincipal || null,
      triggeringPrincipal || null,
      securityFlags,
      contentPolicyType
    );
    if (loadUsingSystemPrincipal) {
      channel.loadInfo.allowDeprecatedSystemRequests = true;
    }
    return channel;
  },

  newWebTransport: function NetUtil_newWebTransport() {
    return Services.io.newWebTransport();
  },

  /**
   * Reads aCount bytes from aInputStream into a string.
   *
   * @param aInputStream
   *        The input stream to read from.
   * @param aCount
   *        The number of bytes to read from the stream.
   * @param aOptions [optional]
   *        charset
   *          The character encoding of stream data.
   *        replacement
   *          The character to replace unknown byte sequences.
   *          If unset, it causes an exceptions to be thrown.
   *
   * @return the bytes from the input stream in string form.
   *
   * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream.
   * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would
   *         block the calling thread (non-blocking mode only).
   * @throws NS_ERROR_FAILURE if there are not enough bytes available to read
   *         aCount amount of data.
   * @throws NS_ERROR_ILLEGAL_INPUT if aInputStream has invalid sequences
   */
  readInputStreamToString: function NetUtil_readInputStreamToString(
    aInputStream,
    aCount,
    aOptions
  ) {
    if (!(aInputStream instanceof Ci.nsIInputStream)) {
      let exception = new Components.Exception(
        "First argument should be an nsIInputStream",
        Cr.NS_ERROR_INVALID_ARG,
        Components.stack.caller
      );
      throw exception;
    }

    if (!aCount) {
      let exception = new Components.Exception(
        "Non-zero amount of bytes must be specified",
        Cr.NS_ERROR_INVALID_ARG,
        Components.stack.caller
      );
      throw exception;
    }

    if (aOptions && "charset" in aOptions) {
      let cis = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(
        Ci.nsIConverterInputStream
      );
      try {
        // When replacement is set, the character that is unknown sequence
        // replaces with aOptions.replacement character.
        if (!("replacement" in aOptions)) {
          // aOptions.replacement isn't set.
          // If input stream has unknown sequences for aOptions.charset,
          // throw NS_ERROR_ILLEGAL_INPUT.
          aOptions.replacement = 0;
        }

        cis.init(aInputStream, aOptions.charset, aCount, aOptions.replacement);
        let str = {};
        cis.readString(-1, str);
        cis.close();
        return str.value;
      } catch (e) {
        // Adjust the stack so it throws at the caller's location.
        throw new Components.Exception(
          e.message,
          e.result,
          Components.stack.caller,
          e.data
        );
      }
    }

    let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
      Ci.nsIScriptableInputStream
    );
    sis.init(aInputStream);
    try {
      return sis.readBytes(aCount);
    } catch (e) {
      // Adjust the stack so it throws at the caller's location.
      throw new Components.Exception(
        e.message,
        e.result,
        Components.stack.caller,
        e.data
      );
    }
  },

  /**
   * Reads aCount bytes from aInputStream into a string.
   *
   * @param {nsIInputStream} aInputStream
   *        The input stream to read from.
   * @param {integer} [aCount = aInputStream.available()]
   *        The number of bytes to read from the stream.
   *
   * @return the bytes from the input stream in string form.
   *
   * @throws NS_ERROR_INVALID_ARG if aInputStream is not an nsIInputStream.
   * @throws NS_BASE_STREAM_WOULD_BLOCK if reading from aInputStream would
   *         block the calling thread (non-blocking mode only).
   * @throws NS_ERROR_FAILURE if there are not enough bytes available to read
   *         aCount amount of data.
   */
  readInputStream(aInputStream, aCount) {
    if (!(aInputStream instanceof Ci.nsIInputStream)) {
      let exception = new Components.Exception(
        "First argument should be an nsIInputStream",
        Cr.NS_ERROR_INVALID_ARG,
        Components.stack.caller
      );
      throw exception;
    }

    if (!aCount) {
      aCount = aInputStream.available();
    }

    let stream = new BinaryInputStream(aInputStream);
    let result = new ArrayBuffer(aCount);
    stream.readArrayBuffer(result.byteLength, result);
    return result;
  },
};
PK
!<߂��;;#modules/NetworkErrorLogging.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

function policyExpired(policy) {
  let currentDate = new Date();
  return (currentDate - policy.creation) / 1_000 > policy.nel.max_age;
}

function errorType(aChannel) {
  // TODO: we have to map a lot more error codes
  switch (aChannel.status) {
    case Cr.NS_ERROR_UNKNOWN_HOST:
      // TODO: if there is no connectivity, return "dns.unreachable"
      return "dns.name_not_resolved";
    case Cr.NS_ERROR_REDIRECT_LOOP:
      return "http.response.redirect_loop";
    case Cr.NS_BINDING_REDIRECTED:
      return "ok";
    case Cr.NS_ERROR_NET_TIMEOUT:
      return "tcp.timed_out";
    case Cr.NS_ERROR_NET_RESET:
      return "tcp.reset";
    case Cr.NS_ERROR_CONNECTION_REFUSED:
      return "tcp.refused";
    default:
      break;
  }

  if (
    aChannel.status == Cr.NS_OK &&
    (aChannel.responseStatus / 100 == 2 || aChannel.responseStatus == 304)
  ) {
    return "ok";
  }

  if (
    aChannel.status == Cr.NS_OK &&
    aChannel.responseStatus >= 400 &&
    aChannel.responseStatus <= 599
  ) {
    return "http.error";
  }
  return "unknown" + aChannel.status;
}

function channelPhase(aChannel) {
  const NS_NET_STATUS_RESOLVING_HOST = 0x4b0003;
  const NS_NET_STATUS_RESOLVED_HOST = 0x4b000b;
  const NS_NET_STATUS_CONNECTING_TO = 0x4b0007;
  const NS_NET_STATUS_CONNECTED_TO = 0x4b0004;
  const NS_NET_STATUS_TLS_HANDSHAKE_STARTING = 0x4b000c;
  const NS_NET_STATUS_TLS_HANDSHAKE_ENDED = 0x4b000d;
  const NS_NET_STATUS_SENDING_TO = 0x4b0005;
  const NS_NET_STATUS_WAITING_FOR = 0x4b000a;
  const NS_NET_STATUS_RECEIVING_FROM = 0x4b0006;
  const NS_NET_STATUS_READING = 0x4b0008;
  const NS_NET_STATUS_WRITING = 0x4b0009;

  let lastStatus = aChannel.QueryInterface(
    Ci.nsIHttpChannelInternal
  ).lastTransportStatus;

  switch (lastStatus) {
    case NS_NET_STATUS_RESOLVING_HOST:
    case NS_NET_STATUS_RESOLVED_HOST:
      return "dns";
    case NS_NET_STATUS_CONNECTING_TO:
    case NS_NET_STATUS_CONNECTED_TO: // TODO: is this right?
      return "connection";
    case NS_NET_STATUS_TLS_HANDSHAKE_STARTING:
    case NS_NET_STATUS_TLS_HANDSHAKE_ENDED:
      return "connection";
    case NS_NET_STATUS_SENDING_TO:
    case NS_NET_STATUS_WAITING_FOR:
    case NS_NET_STATUS_RECEIVING_FROM:
    case NS_NET_STATUS_READING:
    case NS_NET_STATUS_WRITING:
      return "application";
    default:
      // XXX(valentin): we default to DNS, but we should never get here.
      return "dns";
  }
}

export class NetworkErrorLogging {
  constructor() {}

  // Policy cache
  // https://www.w3.org/TR/2023/WD-network-error-logging-20231005/#policy-cache
  policyCache = {};
  // TODO: maybe persist policies to disk?

  // https://www.w3.org/TR/2023/WD-network-error-logging-20231005/#process-policy-headers
  registerPolicy(aChannel) {
    // 1. Abort these steps if any of the following conditions are true:
    // 1.1 The result of executing the "Is origin potentially trustworthy?" algorithm on request's origin is not Potentially Trustworthy.
    if (
      !Services.scriptSecurityManager.getChannelResultPrincipal(aChannel)
        .isOriginPotentiallyTrustworthy
    ) {
      return;
    }

    // 4. Let header be the value of the response header whose name is NEL.
    // 5. Let list be the result of executing the algorithm defined in Section 4 of [HTTP-JFV] on header. If that algorithm results in an error, or if list is empty, abort these steps.
    let list = [];
    aChannel.getOriginalResponseHeader("NEL", {
      QueryInterface: ChromeUtils.generateQI(["nsIHttpHeaderVisitor"]),
      visitHeader: (aHeader, aValue) => {
        list.push(aValue);
        // We only care about the first one so we could exit early
        // We could throw early, but that makes the errors show up in stderr.
        // The performance impact of not throwing is minimal.
        // throw new Error(Cr.NS_ERROR_ABORT);
      },
    });

    // 1.2 response does not contain a response header whose name is NEL.
    if (!list.length) {
      return;
    }

    // 2. Let origin be request's origin.
    let origin =
      Services.scriptSecurityManager.getChannelResultPrincipal(aChannel).origin;

    // 3. Let key be the result of calling determine the network partition key, given request.
    let key = Services.io.originAttributesForNetworkState(aChannel);

    // 6. Let item be the first element of list.
    let item = JSON.parse(list[0]);

    // 7. If item has no member named max_age, or that member's value is not a number, abort these steps.
    if (!item.max_age || !Number.isInteger(item.max_age)) {
      return;
    }

    // 8. If the value of item's max_age member is 0, then remove any NEL policy from the policy cache whose origin is origin, and skip the remaining steps.
    if (!item.max_age) {
      delete this.policyCache[String([key, origin])];
      return;
    }

    // 9. If item has no member named report_to, or that member's value is not a string, abort these steps.
    if (!item.report_to || typeof item.report_to != "string") {
      return;
    }

    // 10. If item has a member named success_fraction, whose value is not a number in the range 0.0 to 1.0, inclusive, abort these steps.
    if (
      item.success_fraction &&
      (typeof item.success_fraction != "number" ||
        item.success_fraction < 0 ||
        item.success_fraction > 1)
    ) {
      return;
    }

    // 11. If item has a member named failure_fraction, whose value is not a number in the range 0.0 to 1.0, inclusive, abort these steps.
    if (
      item.failure_fraction &&
      (typeof item.failure_fraction != "number" ||
        item.failure_fraction < 0 ||
        item.success_fraction > 1)
    ) {
      return;
    }

    // 12. If item has a member named request_headers, whose value is not a list, or if any element of that list is not a string, abort these steps.
    if (
      item.request_headers &&
      !Array.isArray(
        item.request_headers ||
          !item.request_headers.every(e => typeof e == "string")
      )
    ) {
      return;
    }

    // 13. If item has a member named response_headers, whose value is not a list, or if any element of that list is not a string, abort these steps.
    if (
      item.response_headers &&
      !Array.isArray(
        item.response_headers ||
          !item.response_headers.every(e => typeof e == "string")
      )
    ) {
      return;
    }

    // 14. Let policy be a new NEL policy whose properties are set as follows:
    let policy = {};

    // received IP address
    // XXX: What should we do when using a proxy?
    try {
      policy.ip_address = aChannel.QueryInterface(
        Ci.nsIHttpChannelInternal
      ).remoteAddress;
    } catch (e) {
      return;
    }

    // origin
    policy.origin = origin;

    if (item.include_subdomains) {
      policy.subdomains = true;
    }

    policy.request_headers = item.request_headers;
    policy.response_headers = item.response_headers;
    policy.ttl = item.max_age;
    policy.creation = new Date();
    policy.successful_sampling_rate = item.success_fraction || 0.0;
    policy.failure_sampling_rate = item.failure_fraction || 1.0;

    // TODO: Remove these when no longer needed
    policy.nel = item;
    let reportTo = JSON.parse(
      aChannel.QueryInterface(Ci.nsIHttpChannel).getResponseHeader("Report-To")
    );
    policy.reportTo = reportTo;

    // 15. If there is already an entry in the policy cache for (key, origin), replace it with policy; otherwise, insert policy into the policy cache for (key, origin).
    this.policyCache[String([key, origin])] = policy;
  }

  // https://www.w3.org/TR/2023/WD-network-error-logging-20231005/#choose-a-policy-for-a-request
  choosePolicyForRequest(aChannel) {
    // 1. Let origin be request's origin.
    let principal =
      Services.scriptSecurityManager.getChannelResultPrincipal(aChannel);
    let origin = principal.origin;
    // 2. Let key be the result of calling determine the network partition key, given request.
    let key = Services.io.originAttributesForNetworkState(aChannel);

    // 3. If there is an entry in the policy cache for (key, origin):
    let policy = this.policyCache[String([key, origin])];
    //   3.1. Let policy be that entry.
    if (policy) {
      // 3.2. If policy is not expired, return it.
      if (!policyExpired(policy)) {
        return { policy, key, origin };
      }
    }

    // 4. For each parent origin that is a superdomain match of origin:
    // 4.1. If there is an entry in the policy cache for (key, parent origin):
    //    4.1.1. Let policy be that entry.
    //    4.1.2. If policy is not expired, and its subdomains flag is include, return it.
    while (principal.nextSubDomainPrincipal) {
      principal = principal.nextSubDomainPrincipal;
      origin = principal.origin;
      policy = this.policyCache[String([key, origin])];
      if (policy && !policyExpired(policy)) {
        return { policy, key, origin };
      }
    }

    // 5. Return no policy.
    return {};
  }

  // https://www.w3.org/TR/2023/WD-network-error-logging-20231005/#generate-a-network-error-report
  generateNELReport(aChannel) {
    // 1. If the result of executing the "Is origin potentially trustworthy?" algorithm on request's origin is not Potentially Trustworthy, return null.
    if (
      !Services.scriptSecurityManager.getChannelResultPrincipal(aChannel)
        .isOriginPotentiallyTrustworthy
    ) {
      return;
    }
    // 2. Let origin be request's origin.
    let origin =
      Services.scriptSecurityManager.getChannelResultPrincipal(aChannel).origin;

    // 3. Let policy be the result of executing 5.1 Choose a policy for a request on request. If policy is no policy, return null.
    let {
      policy,
      key,
      origin: policyOrigin,
    } = this.choosePolicyForRequest(aChannel);
    if (!policy) {
      return;
    }

    // 4. Determine the active sampling rate for this request:
    let samplingRate = 0.0;
    if (
      aChannel.status == Cr.NS_OK &&
      aChannel.responseStatus >= 200 &&
      aChannel.responseStatus <= 299
    ) {
      // If request succeeded, let sampling rate be policy's successful sampling rate.
      samplingRate = policy.successful_sampling_rate || 0.0;
    } else {
      // If request failed, let sampling rate be policy's failure sampling rate.
      samplingRate = policy.successful_sampling_rate || 1.0;
    }

    // 5. Decide whether or not to report on this request. Let roll be a random number between 0.0 and 1.0, inclusive. If roll ≥ sampling rate, return null.
    if (Math.random() >= samplingRate) {
      return;
    }

    // 6. Let report body be a new ECMAScript object with the following properties:

    let phase = channelPhase(aChannel);
    let report_body = {
      sampling_fraction: samplingRate,
      elapsed_time: 1, // TODO
      phase,
      type: errorType(aChannel), // TODO
    };

    // 7. If report body's phase property is not dns, append the following properties to report body:
    if (phase != "dns") {
      // XXX: should we actually report server_ip?
      // It could be used to detect the presence of a PiHole.
      report_body.server_ip = aChannel.QueryInterface(
        Ci.nsIHttpChannelInternal
      ).remoteAddress;
      report_body.protocol = aChannel.protocolVersion;
    }

    // 8. If report body's phase property is not dns or connection, append the following properties to report body:
    // referrer?
    // method
    // request_headers?
    // response_headers?
    // status_code
    if (phase != "dns" && phase != "connection") {
      report_body.method = aChannel.requestMethod;
      report_body.status_code = aChannel.responseStatus;
    }

    // 9. If origin is not equal to policy's origin, policy's subdomains flag is include, and report body's phase property is not dns, return null.
    if (
      origin != policyOrigin &&
      policy.subdomains &&
      report_body.phase != "dns"
    ) {
      return;
    }

    // 10. If report body's phase property is not dns, and report body's server_ip property is non-empty and not equal to policy's received IP address:
    if (phase != "dns" && report_body.server_ip != policy.ip_address) {
      // 10.1 Set report body's phase to dns.
      report_body.phase = "dns";
      // 10.2 Set report body's type to dns.address_changed.
      report_body.type = "dns.address_changed";
      // 10.3 Clear report body's request_headers, response_headers, status_code, and elapsed_time properties.
      delete report_body.request_headers;
      delete report_body.response_headers;
      delete report_body.status_code;
      delete report_body.elapsed_time;
    }
    if (phase == "dns") {
      //TODO this is just to pass the test sends-report-on-subdomain-dns-failure.https.html
      report_body.method = aChannel.requestMethod;
      report_body.status_code = 0;
      // TODO
    }

    // 11. If policy is stale, then delete policy from the policy cache.
    let currentDate = new Date();
    if ((currentDate - policy.creation) / 1_000 > 172800) {
      // Delete the policy.
      delete this.policyCache[String([key, policyOrigin])];

      // XXX: should we exit here, or continue submit the report?
    }

    // 12. Return report body and policy.

    // https://www.w3.org/TR/2023/WD-network-error-logging-20231005/#deliver-a-network-report
    // 1. Let url be request's URL.
    // 2. Clear url's fragment.
    let uriMutator = aChannel.URI.mutate().setRef("");
    // 3. If report body's phase property is dns or connection:
    //    Clear url's path and query.
    if (report_body.phase == "dns" || report_body.phase == "connection") {
      uriMutator.setPathQueryRef("");
    }

    // 4. Generate a network report given these parameters:
    let report = {
      type: "network-error",
      url: aChannel.URI.specIgnoringRef, // uriMutator.finalize().spec, // XXX: sends-report-on-subdomain-dns-failure.https.html expects full URL
      user_agent: Cc["@mozilla.org/network/protocol;1?name=http"].getService(
        Ci.nsIHttpProtocolHandler
      ).userAgent,
      body: report_body,
    };
    // XXX: this would benefit from using the actual reporting API,
    //      but it's not clear how easy it is to:
    //        - use it in the parent process
    //        - have it use the Report-To header
    // https://w3c.github.io/reporting/#queue-report
    if (policy && policy.reportTo.group === policy.nel.report_to) {
      // TODO: defer to later.
      fetch(policy.reportTo.endpoints[0].url, {
        method: "POST",
        mode: "cors",
        credentials: "omit",
        headers: {
          "Content-Type": "application/reports+json",
        },
        body: JSON.stringify([report]),
        triggeringPrincipal:
          Services.scriptSecurityManager.getChannelResultPrincipal(aChannel),
      });
    }
  }

  QueryInterface = ChromeUtils.generateQI(["nsINetworkErrorLogging"]);
}
PK
!<�zLq�5�5*modules/NetworkGeolocationProvider.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  LocationHelper: "resource://gre/modules/LocationHelper.sys.mjs",
  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

// GeolocationPositionError has no interface object, so we can't use that here.
const POSITION_UNAVAILABLE = 2;

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gLoggingEnabled",
  "geo.provider.network.logging.enabled",
  false
);

function LOG(aMsg) {
  if (lazy.gLoggingEnabled) {
    dump("*** WIFI GEO: " + aMsg + "\n");
  }
}

function CachedRequest(loc, cellInfo, wifiList) {
  this.location = loc;

  let wifis = new Set();
  if (wifiList) {
    for (let i = 0; i < wifiList.length; i++) {
      wifis.add(wifiList[i].macAddress);
    }
  }

  // Use only these values for equality
  // (the JSON will contain additional values in future)
  function makeCellKey(cell) {
    return (
      "" +
      cell.radio +
      ":" +
      cell.mobileCountryCode +
      ":" +
      cell.mobileNetworkCode +
      ":" +
      cell.locationAreaCode +
      ":" +
      cell.cellId
    );
  }

  let cells = new Set();
  if (cellInfo) {
    for (let i = 0; i < cellInfo.length; i++) {
      cells.add(makeCellKey(cellInfo[i]));
    }
  }

  this.hasCells = () => cells.size > 0;

  this.hasWifis = () => wifis.size > 0;

  // if fields match
  this.isCellEqual = function (cellInfo) {
    if (!this.hasCells()) {
      return false;
    }

    let len1 = cells.size;
    let len2 = cellInfo.length;

    if (len1 != len2) {
      LOG("cells not equal len");
      return false;
    }

    for (let i = 0; i < len2; i++) {
      if (!cells.has(makeCellKey(cellInfo[i]))) {
        return false;
      }
    }
    return true;
  };

  // if 50% of the SSIDS match
  this.isWifiApproxEqual = function (wifiList) {
    if (!this.hasWifis()) {
      return false;
    }

    // if either list is a 50% subset of the other, they are equal
    let common = 0;
    for (let i = 0; i < wifiList.length; i++) {
      if (wifis.has(wifiList[i].macAddress)) {
        common++;
      }
    }
    let kPercentMatch = 0.5;
    return common >= Math.max(wifis.size, wifiList.length) * kPercentMatch;
  };

  this.isGeoip = function () {
    return !this.hasCells() && !this.hasWifis();
  };

  this.isCellAndWifi = function () {
    return this.hasCells() && this.hasWifis();
  };

  this.isCellOnly = function () {
    return this.hasCells() && !this.hasWifis();
  };

  this.isWifiOnly = function () {
    return this.hasWifis() && !this.hasCells();
  };
}

var gCachedRequest = null;
var gDebugCacheReasoning = ""; // for logging the caching logic

// This function serves two purposes:
// 1) do we have a cached request
// 2) is the cached request better than what newCell and newWifiList will obtain
// If the cached request exists, and we know it to have greater accuracy
// by the nature of its origin (wifi/cell/geoip), use its cached location.
//
// If there is more source info than the cached request had, return false
// In other cases, MLS is known to produce better/worse accuracy based on the
// inputs, so base the decision on that.
function isCachedRequestMoreAccurateThanServerRequest(newCell, newWifiList) {
  gDebugCacheReasoning = "";
  let isNetworkRequestCacheEnabled = Services.prefs.getBoolPref(
    "geo.provider.network.debug.requestCache.enabled",
    true
  );
  // Mochitest needs this pref to simulate request failure
  if (!isNetworkRequestCacheEnabled) {
    gCachedRequest = null;
  }

  if (!gCachedRequest || !isNetworkRequestCacheEnabled) {
    gDebugCacheReasoning = "No cached data";
    return false;
  }

  if (!newCell && !newWifiList) {
    gDebugCacheReasoning = "New req. is GeoIP.";
    return true;
  }

  if (
    newCell &&
    newWifiList &&
    (gCachedRequest.isCellOnly() || gCachedRequest.isWifiOnly())
  ) {
    gDebugCacheReasoning = "New req. is cell+wifi, cache only cell or wifi.";
    return false;
  }

  if (newCell && gCachedRequest.isWifiOnly()) {
    // In order to know if a cell-only request should trump a wifi-only request
    // need to know if wifi is low accuracy. >5km would be VERY low accuracy,
    // it is worth trying the cell
    var isHighAccuracyWifi = gCachedRequest.location.coords.accuracy < 5000;
    gDebugCacheReasoning =
      "Req. is cell, cache is wifi, isHigh:" + isHighAccuracyWifi;
    return isHighAccuracyWifi;
  }

  let hasEqualCells = false;
  if (newCell) {
    hasEqualCells = gCachedRequest.isCellEqual(newCell);
  }

  let hasEqualWifis = false;
  if (newWifiList) {
    hasEqualWifis = gCachedRequest.isWifiApproxEqual(newWifiList);
  }

  gDebugCacheReasoning =
    "EqualCells:" + hasEqualCells + " EqualWifis:" + hasEqualWifis;

  if (gCachedRequest.isCellOnly()) {
    gDebugCacheReasoning += ", Cell only.";
    if (hasEqualCells) {
      return true;
    }
  } else if (gCachedRequest.isWifiOnly() && hasEqualWifis) {
    gDebugCacheReasoning += ", Wifi only.";
    return true;
  } else if (gCachedRequest.isCellAndWifi()) {
    gDebugCacheReasoning += ", Cache has Cell+Wifi.";
    if (
      (hasEqualCells && hasEqualWifis) ||
      (!newWifiList && hasEqualCells) ||
      (!newCell && hasEqualWifis)
    ) {
      return true;
    }
  }

  return false;
}

function NetworkGeoCoordsObject(lat, lon, acc) {
  this.latitude = lat;
  this.longitude = lon;
  this.accuracy = acc;

  // Neither GLS nor MLS return the following properties, so set them to NaN
  // here. nsGeoPositionCoords will convert NaNs to null for optional properties
  // of the JavaScript Coordinates object.
  this.altitude = NaN;
  this.altitudeAccuracy = NaN;
  this.heading = NaN;
  this.speed = NaN;
}

NetworkGeoCoordsObject.prototype = {
  QueryInterface: ChromeUtils.generateQI(["nsIDOMGeoPositionCoords"]),
};

function NetworkGeoPositionObject(lat, lng, acc) {
  this.coords = new NetworkGeoCoordsObject(lat, lng, acc);
  this.address = null;
  this.timestamp = Date.now();
}

NetworkGeoPositionObject.prototype = {
  QueryInterface: ChromeUtils.generateQI(["nsIDOMGeoPosition"]),
};

export function NetworkGeolocationProvider() {
  /*
    The _wifiMonitorTimeout controls how long we wait on receiving an update
    from the Wifi subsystem.  If this timer fires, we believe the Wifi scan has
    had a problem and we no longer can use Wifi to position the user this time
    around (we will continue to be hopeful that Wifi will recover).

    This timeout value is also used when Wifi scanning is disabled (see
    isWifiScanningEnabled).  In this case, we use this timer to collect cell/ip
    data and xhr it to the location server.
  */
  XPCOMUtils.defineLazyPreferenceGetter(
    this,
    "_wifiMonitorTimeout",
    "geo.provider.network.timeToWaitBeforeSending",
    5000
  );

  XPCOMUtils.defineLazyPreferenceGetter(
    this,
    "_wifiScanningEnabled",
    "geo.provider.network.scan",
    true
  );

  this.wifiService = null;
  this.timer = null;
  this.started = false;
}

NetworkGeolocationProvider.prototype = {
  classID: Components.ID("{77DA64D3-7458-4920-9491-86CC9914F904}"),
  name: "NetworkGeolocationProvider",
  QueryInterface: ChromeUtils.generateQI([
    "nsIGeolocationProvider",
    "nsIWifiListener",
    "nsITimerCallback",
    "nsIObserver",
    "nsINamed",
  ]),
  listener: null,

  get isWifiScanningEnabled() {
    return Cc["@mozilla.org/wifi/monitor;1"] && this._wifiScanningEnabled;
  },

  resetTimer() {
    if (this.timer) {
      this.timer.cancel();
      this.timer = null;
    }
    // Wifi thread triggers NetworkGeolocationProvider to proceed. With no wifi,
    // do manual timeout.
    this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    this.timer.initWithCallback(
      this,
      this._wifiMonitorTimeout,
      this.timer.TYPE_REPEATING_SLACK
    );
  },

  startup() {
    if (this.started) {
      return;
    }

    this.started = true;

    if (this.isWifiScanningEnabled) {
      if (this.wifiService) {
        this.wifiService.stopWatching(this);
      }
      this.wifiService = Cc["@mozilla.org/wifi/monitor;1"].getService(
        Ci.nsIWifiMonitor
      );
      this.wifiService.startWatching(this, false);
    }

    this.resetTimer();
    LOG("startup called.");
  },

  watch(c) {
    this.listener = c;
  },

  shutdown() {
    LOG("shutdown called");
    if (!this.started) {
      return;
    }

    // Without clearing this, we could end up using the cache almost indefinitely
    // TODO: add logic for cache lifespan, for now just be safe and clear it
    gCachedRequest = null;

    if (this.timer) {
      this.timer.cancel();
      this.timer = null;
    }

    if (this.wifiService) {
      this.wifiService.stopWatching(this);
      this.wifiService = null;
    }

    this.listener = null;
    this.started = false;
  },

  setHighAccuracy(enable) {
    // Mochitest wants to check this value
    if (Services.prefs.getBoolPref("geo.provider.testing", false)) {
      Services.obs.notifyObservers(
        null,
        "testing-geolocation-high-accuracy",
        enable
      );
    }
  },

  onChange(accessPoints) {
    // we got some wifi data, rearm the timer.
    this.resetTimer();

    let wifiData = null;
    if (accessPoints) {
      wifiData = lazy.LocationHelper.formatWifiAccessPoints(accessPoints);
    }
    this.sendLocationRequest(wifiData);
  },

  onError(code) {
    LOG("wifi error: " + code);
    this.sendLocationRequest(null);
  },

  onStatus(err, statusMessage) {
    if (!this.listener) {
      return;
    }
    LOG("onStatus called." + statusMessage);

    if (statusMessage && this.listener.notifyStatus) {
      this.listener.notifyStatus(statusMessage);
    }

    if (err && this.listener.notifyError) {
      this.listener.notifyError(POSITION_UNAVAILABLE, statusMessage);
    }
  },

  notify() {
    this.onStatus(false, "wifi-timeout");
    this.sendLocationRequest(null);
  },

  /**
   * After wifi (and possible cell tower) data has been gathered, this method is
   * invoked to perform the request to network geolocation provider.
   * The result of each request is sent to all registered listener (@see watch)
   * by invoking its respective `update`, `notifyError` or `notifyStatus`
   * callbacks.
   * `update` is called upon a successful request with its response data; this will be a `NetworkGeoPositionObject` instance.
   * `notifyError` is called whenever the request gets an error from the local
   * network subsystem, the server or simply times out.
   * `notifyStatus` is called for each status change of the request that may be
   * of interest to the consumer of this class. Currently the following status
   * changes are reported: 'xhr-start', 'xhr-timeout', 'xhr-error' and
   * 'xhr-empty'.
   *
   * @param  {Array} wifiData Optional set of publicly available wifi networks
   *                          in the following structure:
   *                          <code>
   *                          [
   *                            { macAddress: <mac1>, signalStrength: <signal1> },
   *                            { macAddress: <mac2>, signalStrength: <signal2> }
   *                          ]
   *                          </code>
   */
  async sendLocationRequest(wifiData) {
    let data = { cellTowers: undefined, wifiAccessPoints: undefined };
    if (wifiData && wifiData.length >= 2) {
      data.wifiAccessPoints = wifiData;
    }

    let useCached = isCachedRequestMoreAccurateThanServerRequest(
      data.cellTowers,
      data.wifiAccessPoints
    );

    LOG("Use request cache:" + useCached + " reason:" + gDebugCacheReasoning);

    if (useCached) {
      gCachedRequest.location.timestamp = Date.now();
      if (this.listener) {
        this.listener.update(gCachedRequest.location);
      }
      return;
    }

    // From here on, do a network geolocation request //
    let url = Services.urlFormatter.formatURLPref("geo.provider.network.url");
    LOG("Sending request");

    let result;
    try {
      result = await this.makeRequest(url, wifiData);
      LOG(
        `geo provider reported: ${result.location.lng}:${result.location.lat}`
      );
      let newLocation = new NetworkGeoPositionObject(
        result.location.lat,
        result.location.lng,
        result.accuracy
      );

      if (this.listener) {
        this.listener.update(newLocation);
      }

      gCachedRequest = new CachedRequest(
        newLocation,
        data.cellTowers,
        data.wifiAccessPoints
      );
    } catch (err) {
      LOG("Location request hit error: " + err.name);
      console.error(err);
      if (err.name == "AbortError") {
        this.onStatus(true, "xhr-timeout");
      } else {
        this.onStatus(true, "xhr-error");
      }
    }
  },

  async makeRequest(url, wifiData) {
    this.onStatus(false, "xhr-start");

    let fetchController = new AbortController();
    let fetchOpts = {
      method: "POST",
      headers: { "Content-Type": "application/json; charset=UTF-8" },
      credentials: "omit",
      signal: fetchController.signal,
    };

    if (wifiData) {
      fetchOpts.body = JSON.stringify({ wifiAccessPoints: wifiData });
    }

    let timeoutId = lazy.setTimeout(
      () => fetchController.abort(),
      Services.prefs.getIntPref("geo.provider.network.timeout")
    );

    let req = await fetch(url, fetchOpts);
    lazy.clearTimeout(timeoutId);
    let result = req.json();
    return result;
  },
};
PK
!<IB�55modules/NewTabUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

// Android tests don't import these properly, so guard against that
let shortURL = {};
let searchShortcuts = {};
let didSuccessfulImport = false;
try {
  shortURL = ChromeUtils.importESModule(
    "resource://activity-stream/lib/ShortURL.sys.mjs"
  );
  searchShortcuts = ChromeUtils.importESModule(
    "resource://activity-stream/lib/SearchShortcuts.sys.mjs"
  );
  didSuccessfulImport = true;
} catch (e) {
  // The test failed to import these files
}

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  BinarySearch: "resource://gre/modules/BinarySearch.sys.mjs",
  PageThumbs: "resource://gre/modules/PageThumbs.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
  Pocket: "chrome://pocket/content/Pocket.sys.mjs",
  pktApi: "chrome://pocket/content/pktApi.sys.mjs",
});

let BrowserWindowTracker;
try {
  BrowserWindowTracker = ChromeUtils.importESModule(
    "resource:///modules/BrowserWindowTracker.sys.mjs"
  ).BrowserWindowTracker;
} catch (e) {
  // BrowserWindowTracker is used to determine devicePixelRatio in
  // _addFavicons. We fallback to the value 2 if we can't find a window,
  // so it's safe to do nothing with this here.
}

ChromeUtils.defineLazyGetter(lazy, "gCryptoHash", function () {
  return Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash);
});

// Boolean preferences that control newtab content
const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled";

// The maximum number of results PlacesProvider retrieves from history.
const HISTORY_RESULTS_LIMIT = 100;

// The maximum number of links Links.getLinks will return.
const LINKS_GET_LINKS_LIMIT = 100;

// The gather telemetry topic.
const TOPIC_GATHER_TELEMETRY = "gather-telemetry";

// Some default frecency threshold for Activity Stream requests
const ACTIVITY_STREAM_DEFAULT_FRECENCY = 150;

// Some default query limit for Activity Stream requests
const ACTIVITY_STREAM_DEFAULT_LIMIT = 12;

// Some default seconds ago for Activity Stream recent requests
const ACTIVITY_STREAM_DEFAULT_RECENT = 5 * 24 * 60 * 60;

// The fallback value for the width of smallFavicon in pixels.
// This value will be multiplied by the current window's devicePixelRatio.
// If devicePixelRatio cannot be found, it will be multiplied by 2.
const DEFAULT_SMALL_FAVICON_WIDTH = 16;

const POCKET_UPDATE_TIME = 24 * 60 * 60 * 1000; // 1 day
const POCKET_INACTIVE_TIME = 7 * 24 * 60 * 60 * 1000; // 1 week
const PREF_POCKET_LATEST_SINCE = "extensions.pocket.settings.latestSince";

/**
 * Calculate the MD5 hash for a string.
 * @param aValue
 *        The string to convert.
 * @return The base64 representation of the MD5 hash.
 */
function toHash(aValue) {
  let value = new TextEncoder().encode(aValue);
  lazy.gCryptoHash.init(lazy.gCryptoHash.MD5);
  lazy.gCryptoHash.update(value, value.length);
  return lazy.gCryptoHash.finish(true);
}

/**
 * Singleton that provides storage functionality.
 */
ChromeUtils.defineLazyGetter(lazy, "Storage", function () {
  return new LinksStorage();
});

function LinksStorage() {
  // Handle migration of data across versions.
  try {
    if (this._storedVersion < this._version) {
      // This is either an upgrade, or version information is missing.
      if (this._storedVersion < 1) {
        // Version 1 moved data from DOM Storage to prefs.  Since migrating from
        // version 0 is no more supported, we just reportError a dataloss later.
        throw new Error("Unsupported newTab storage version");
      }
      // Add further migration steps here.
    } else {
      // This is a downgrade.  Since we cannot predict future, upgrades should
      // be backwards compatible.  We will set the version to the old value
      // regardless, so, on next upgrade, the migration steps will run again.
      // For this reason, they should also be able to run multiple times, even
      // on top of an already up-to-date storage.
    }
  } catch (ex) {
    // Something went wrong in the update process, we can't recover from here,
    // so just clear the storage and start from scratch (dataloss!).
    console.error(
      "Unable to migrate the newTab storage to the current version. " +
        "Restarting from scratch.\n",
      ex
    );
    this.clear();
  }

  // Set the version to the current one.
  this._storedVersion = this._version;
}

LinksStorage.prototype = {
  get _version() {
    return 1;
  },

  get _prefs() {
    return Object.freeze({
      pinnedLinks: "browser.newtabpage.pinned",
      blockedLinks: "browser.newtabpage.blocked",
    });
  },

  get _storedVersion() {
    if (this.__storedVersion === undefined) {
      // When the pref is not set, the storage version is unknown, so either:
      // - it's a new profile
      // - it's a profile where versioning information got lost
      // In this case we still run through all of the valid migrations,
      // starting from 1, as if it was a downgrade.  As previously stated the
      // migrations should already support running on an updated store.
      this.__storedVersion = Services.prefs.getIntPref(
        "browser.newtabpage.storageVersion",
        1
      );
    }
    return this.__storedVersion;
  },
  set _storedVersion(aValue) {
    Services.prefs.setIntPref("browser.newtabpage.storageVersion", aValue);
    this.__storedVersion = aValue;
  },

  /**
   * Gets the value for a given key from the storage.
   * @param aKey The storage key (a string).
   * @param aDefault A default value if the key doesn't exist.
   * @return The value for the given key.
   */
  get: function Storage_get(aKey, aDefault) {
    let value;
    try {
      let prefValue = Services.prefs.getStringPref(this._prefs[aKey]);
      value = JSON.parse(prefValue);
    } catch (e) {}
    return value || aDefault;
  },

  /**
   * Sets the storage value for a given key.
   * @param aKey The storage key (a string).
   * @param aValue The value to set.
   */
  set: function Storage_set(aKey, aValue) {
    // Page titles may contain unicode, thus use complex values.
    Services.prefs.setStringPref(this._prefs[aKey], JSON.stringify(aValue));
  },

  /**
   * Removes the storage value for a given key.
   * @param aKey The storage key (a string).
   */
  remove: function Storage_remove(aKey) {
    Services.prefs.clearUserPref(this._prefs[aKey]);
  },

  /**
   * Clears the storage and removes all values.
   */
  clear: function Storage_clear() {
    for (let key in this._prefs) {
      this.remove(key);
    }
  },
};

/**
 * Singleton that serves as a registry for all open 'New Tab Page's.
 */
var AllPages = {
  /**
   * The array containing all active pages.
   */
  _pages: [],

  /**
   * Cached value that tells whether the New Tab Page feature is enabled.
   */
  _enabled: null,

  /**
   * Adds a page to the internal list of pages.
   * @param aPage The page to register.
   */
  register: function AllPages_register(aPage) {
    this._pages.push(aPage);
    this._addObserver();
  },

  /**
   * Removes a page from the internal list of pages.
   * @param aPage The page to unregister.
   */
  unregister: function AllPages_unregister(aPage) {
    let index = this._pages.indexOf(aPage);
    if (index > -1) {
      this._pages.splice(index, 1);
    }
  },

  /**
   * Returns whether the 'New Tab Page' is enabled.
   */
  get enabled() {
    if (this._enabled === null) {
      this._enabled = Services.prefs.getBoolPref(PREF_NEWTAB_ENABLED);
    }

    return this._enabled;
  },

  /**
   * Enables or disables the 'New Tab Page' feature.
   */
  set enabled(aEnabled) {
    if (this.enabled != aEnabled) {
      Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, !!aEnabled);
    }
  },

  /**
   * Returns the number of registered New Tab Pages (i.e. the number of open
   * about:newtab instances).
   */
  get length() {
    return this._pages.length;
  },

  /**
   * Updates all currently active pages but the given one.
   * @param aExceptPage The page to exclude from updating.
   * @param aReason The reason for updating all pages.
   */
  update(aExceptPage, aReason = "") {
    for (let page of this._pages.slice()) {
      if (aExceptPage != page) {
        page.update(aReason);
      }
    }
  },

  /**
   * Implements the nsIObserver interface to get notified when the preference
   * value changes or when a new copy of a page thumbnail is available.
   */
  observe: function AllPages_observe(aSubject, aTopic, aData) {
    if (aTopic == "nsPref:changed") {
      // Clear the cached value.
      switch (aData) {
        case PREF_NEWTAB_ENABLED:
          this._enabled = null;
          break;
      }
    }
    // and all notifications get forwarded to each page.
    this._pages.forEach(function (aPage) {
      aPage.observe(aSubject, aTopic, aData);
    }, this);
  },

  /**
   * Adds a preference and new thumbnail observer and turns itself into a
   * no-op after the first invokation.
   */
  _addObserver: function AllPages_addObserver() {
    Services.prefs.addObserver(PREF_NEWTAB_ENABLED, this, true);
    Services.obs.addObserver(this, "page-thumbnail:create", true);
    this._addObserver = function () {};
  },

  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
  ]),
};

/**
 * Singleton that keeps track of all pinned links and their positions in the
 * grid.
 */
var PinnedLinks = {
  /**
   * The cached list of pinned links.
   */
  _links: null,

  /**
   * The array of pinned links.
   */
  get links() {
    if (!this._links) {
      this._links = lazy.Storage.get("pinnedLinks", []);
    }

    return this._links;
  },

  /**
   * Pins a link at the given position.
   * @param aLink The link to pin.
   * @param aIndex The grid index to pin the cell at.
   * @return true if link changes, false otherwise
   */
  pin: function PinnedLinks_pin(aLink, aIndex) {
    // Clear the link's old position, if any.
    this.unpin(aLink);

    // change pinned link into a history link
    let changed = this._makeHistoryLink(aLink);
    this.links[aIndex] = aLink;
    this.save();
    return changed;
  },

  /**
   * Unpins a given link.
   * @param aLink The link to unpin.
   */
  unpin: function PinnedLinks_unpin(aLink) {
    let index = this._indexOfLink(aLink);
    if (index == -1) {
      return;
    }
    let links = this.links;
    links[index] = null;
    // trim trailing nulls
    let i = links.length - 1;
    while (i >= 0 && links[i] == null) {
      i--;
    }
    links.splice(i + 1);
    this.save();
  },

  /**
   * Saves the current list of pinned links.
   */
  save: function PinnedLinks_save() {
    lazy.Storage.set("pinnedLinks", this.links);
  },

  /**
   * Checks whether a given link is pinned.
   * @params aLink The link to check.
   * @return whether The link is pinned.
   */
  isPinned: function PinnedLinks_isPinned(aLink) {
    return this._indexOfLink(aLink) != -1;
  },

  /**
   * Resets the links cache.
   */
  resetCache: function PinnedLinks_resetCache() {
    this._links = null;
  },

  /**
   * Finds the index of a given link in the list of pinned links.
   * @param aLink The link to find an index for.
   * @return The link's index.
   */
  _indexOfLink: function PinnedLinks_indexOfLink(aLink) {
    for (let i = 0; i < this.links.length; i++) {
      let link = this.links[i];
      if (link && link.url == aLink.url) {
        return i;
      }
    }

    // The given link is unpinned.
    return -1;
  },

  /**
   * Transforms link into a "history" link
   * @param aLink The link to change
   * @return true if link changes, false otherwise
   */
  _makeHistoryLink: function PinnedLinks_makeHistoryLink(aLink) {
    if (!aLink.type || aLink.type == "history") {
      return false;
    }
    aLink.type = "history";
    return true;
  },

  /**
   * Replaces existing link with another link.
   * @param aUrl The url of existing link
   * @param aLink The replacement link
   */
  replace: function PinnedLinks_replace(aUrl, aLink) {
    let index = this._indexOfLink({ url: aUrl });
    if (index == -1) {
      return;
    }
    this.links[index] = aLink;
    this.save();
  },
};

/**
 * Singleton that keeps track of all blocked links in the grid.
 */
var BlockedLinks = {
  /**
   * A list of objects that are observing blocked link changes.
   */
  _observers: [],

  /**
   * The cached list of blocked links.
   */
  _links: null,

  /**
   * Registers an object that will be notified when the blocked links change.
   */
  addObserver(aObserver) {
    this._observers.push(aObserver);
  },

  /**
   * Remove the observers.
   */
  removeObservers() {
    this._observers = [];
  },

  /**
   * The list of blocked links.
   */
  get links() {
    if (!this._links) {
      this._links = lazy.Storage.get("blockedLinks", {});
    }

    return this._links;
  },

  /**
   * Blocks a given link. Adjusts siteMap accordingly, and notifies listeners.
   * @param aLink The link to block.
   */
  block: function BlockedLinks_block(aLink) {
    this._callObservers("onLinkBlocked", aLink);
    this.links[toHash(aLink.url)] = 1;
    this.save();

    // Make sure we unpin blocked links.
    PinnedLinks.unpin(aLink);
  },

  /**
   * Unblocks a given link. Adjusts siteMap accordingly, and notifies listeners.
   * @param aLink The link to unblock.
   */
  unblock: function BlockedLinks_unblock(aLink) {
    if (this.isBlocked(aLink)) {
      delete this.links[toHash(aLink.url)];
      this.save();
      this._callObservers("onLinkUnblocked", aLink);
    }
  },

  /**
   * Saves the current list of blocked links.
   */
  save: function BlockedLinks_save() {
    lazy.Storage.set("blockedLinks", this.links);
  },

  /**
   * Returns whether a given link is blocked.
   * @param aLink The link to check.
   */
  isBlocked: function BlockedLinks_isBlocked(aLink) {
    return toHash(aLink.url) in this.links;
  },

  /**
   * Checks whether the list of blocked links is empty.
   * @return Whether the list is empty.
   */
  isEmpty: function BlockedLinks_isEmpty() {
    return !Object.keys(this.links).length;
  },

  /**
   * Resets the links cache.
   */
  resetCache: function BlockedLinks_resetCache() {
    this._links = null;
  },

  _callObservers(methodName, ...args) {
    for (let obs of this._observers) {
      if (typeof obs[methodName] == "function") {
        try {
          obs[methodName](...args);
        } catch (err) {
          console.error(err);
        }
      }
    }
  },
};

/**
 * Singleton that serves as the default link provider for the grid. It queries
 * the history to retrieve the most frequently visited sites.
 */
var PlacesProvider = {
  /**
   * Set this to change the maximum number of links the provider will provide.
   */
  maxNumLinks: HISTORY_RESULTS_LIMIT,

  /**
   * Must be called before the provider is used.
   */
  init: function PlacesProvider_init() {
    this._placesObserver = new PlacesWeakCallbackWrapper(
      this.handlePlacesEvents.bind(this)
    );
    PlacesObservers.addListener(
      ["page-visited", "page-title-changed", "pages-rank-changed"],
      this._placesObserver
    );
  },

  /**
   * Gets the current set of links delivered by this provider.
   * @param aCallback The function that the array of links is passed to.
   */
  getLinks: function PlacesProvider_getLinks(aCallback) {
    let options = lazy.PlacesUtils.history.getNewQueryOptions();
    options.maxResults = this.maxNumLinks;

    // Sort by frecency, descending.
    options.sortingMode =
      Ci.nsINavHistoryQueryOptions.SORT_BY_FRECENCY_DESCENDING;

    let links = [];

    let callback = {
      handleResult(aResultSet) {
        let row;

        while ((row = aResultSet.getNextRow())) {
          let url = row.getResultByIndex(1);
          if (LinkChecker.checkLoadURI(url)) {
            let title = row.getResultByIndex(2);
            let frecency = row.getResultByIndex(12);
            let lastVisitDate = row.getResultByIndex(5);
            links.push({
              url,
              title,
              frecency,
              lastVisitDate,
              type: "history",
            });
          }
        }
      },

      handleError() {
        // Should we somehow handle this error?
        aCallback([]);
      },

      handleCompletion() {
        // The Places query breaks ties in frecency by place ID descending, but
        // that's different from how Links.compareLinks breaks ties, because
        // compareLinks doesn't have access to place IDs.  It's very important
        // that the initial list of links is sorted in the same order imposed by
        // compareLinks, because Links uses compareLinks to perform binary
        // searches on the list.  So, ensure the list is so ordered.
        let i = 1;
        let outOfOrder = [];
        while (i < links.length) {
          if (Links.compareLinks(links[i - 1], links[i]) > 0) {
            outOfOrder.push(links.splice(i, 1)[0]);
          } else {
            i++;
          }
        }
        for (let link of outOfOrder) {
          i = lazy.BinarySearch.insertionIndexOf(
            Links.compareLinks,
            links,
            link
          );
          links.splice(i, 0, link);
        }

        aCallback(links);
      },
    };

    // Execute the query.
    let query = lazy.PlacesUtils.history.getNewQuery();
    lazy.PlacesUtils.history.asyncExecuteLegacyQuery(query, options, callback);
  },

  /**
   * Registers an object that will be notified when the provider's links change.
   * @param aObserver An object with the following optional properties:
   *        * onLinkChanged: A function that's called when a single link
   *          changes.  It's passed the provider and the link object.  Only the
   *          link's `url` property is guaranteed to be present.  If its `title`
   *          property is present, then its title has changed, and the
   *          property's value is the new title.  If any sort properties are
   *          present, then its position within the provider's list of links may
   *          have changed, and the properties' values are the new sort-related
   *          values.  Note that this link may not necessarily have been present
   *          in the lists returned from any previous calls to getLinks.
   *        * onManyLinksChanged: A function that's called when many links
   *          change at once.  It's passed the provider.  You should call
   *          getLinks to get the provider's new list of links.
   */
  addObserver: function PlacesProvider_addObserver(aObserver) {
    this._observers.push(aObserver);
  },

  _observers: [],

  handlePlacesEvents(aEvents) {
    for (let event of aEvents) {
      switch (event.type) {
        case "page-visited": {
          if (event.visitCount == 1 && event.lastKnownTitle) {
            this._callObservers("onLinkChanged", {
              url: event.url,
              title: event.lastKnownTitle,
            });
          }
          break;
        }
        case "page-title-changed": {
          this._callObservers("onLinkChanged", {
            url: event.url,
            title: event.title,
          });
          break;
        }
        case "pages-rank-changed": {
          this._callObservers("onManyLinksChanged");
          break;
        }
      }
    }
  },

  _callObservers: function PlacesProvider__callObservers(aMethodName, aArg) {
    for (let obs of this._observers) {
      if (obs[aMethodName]) {
        try {
          obs[aMethodName](this, aArg);
        } catch (err) {
          console.error(err);
        }
      }
    }
  },
};

/**
 * Queries history to retrieve the most frecent sites. Emits events when the
 * history changes.
 */
var ActivityStreamProvider = {
  THUMB_FAVICON_SIZE: 96,

  /**
   * Shared adjustment for selecting potentially blocked links.
   */
  _adjustLimitForBlocked({ ignoreBlocked, numItems }) {
    // Just use the usual number if blocked links won't be filtered out
    if (ignoreBlocked) {
      return numItems;
    }
    // Additionally select the number of blocked links in case they're removed
    return Object.keys(BlockedLinks.links).length + numItems;
  },

  /**
   * Shared sub-SELECT to get the guid of a bookmark of the current url while
   * avoiding LEFT JOINs on moz_bookmarks. This avoids gettings tags. The guid
   * could be one of multiple possible guids. Assumes `moz_places h` is in FROM.
   */
  _commonBookmarkGuidSelect: `(
    SELECT guid
    FROM moz_bookmarks b
    WHERE fk = h.id
      AND type = :bookmarkType
      AND (
        SELECT id
        FROM moz_bookmarks p
        WHERE p.id = b.parent
          AND p.parent <> :tagsFolderId
      ) NOTNULL
    ) AS bookmarkGuid`,

  /**
   * Shared WHERE expression filtering out undesired pages, e.g., hidden,
   * unvisited, and non-http/s urls. Assumes moz_places is in FROM / JOIN.
   *
   * NB: SUBSTR(url) is used even without an index instead of url_hash because
   * most desired pages will match http/s, so it will only run on the ~10s of
   * rows matched. If url_hash were to be used, it should probably *not* be used
   * by the query optimizer as we primarily want it optimized for the other
   * conditions, e.g., most frecent first.
   */
  _commonPlacesWhere: `
    AND hidden = 0
    AND last_visit_date > 0
    AND (SUBSTR(url, 1, 6) == "https:"
      OR SUBSTR(url, 1, 5) == "http:")
  `,

  /**
   * Shared parameters for getting correct bookmarks and LIMITed queries.
   */
  _getCommonParams(aOptions, aParams = {}) {
    return Object.assign(
      {
        bookmarkType: lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK,
        limit: this._adjustLimitForBlocked(aOptions),
        tagsFolderId: lazy.PlacesUtils.tagsFolderId,
      },
      aParams
    );
  },

  /**
   * Shared columns for Highlights related queries.
   */
  _highlightsColumns: [
    "bookmarkGuid",
    "description",
    "guid",
    "preview_image_url",
    "title",
    "url",
  ],

  /**
   * Shared post-processing of Highlights links.
   */
  _processHighlights(aLinks, aOptions, aType) {
    // Filter out blocked if necessary
    if (!aOptions.ignoreBlocked) {
      aLinks = aLinks.filter(
        link =>
          !BlockedLinks.isBlocked(
            link.pocket_id ? { url: link.open_url } : link
          )
      );
    }

    // Limit the results to the requested number and set a type corresponding to
    // which query selected it
    return aLinks.slice(0, aOptions.numItems).map(item =>
      Object.assign(item, {
        type: aType,
      })
    );
  },

  /**
   * From an Array of links, if favicons are present, convert to data URIs
   *
   * @param {Array} aLinks
   *          an array containing objects with favicon data and mimeTypes
   *
   * @returns {Array} an array of links with favicons as data uri
   */
  _faviconBytesToDataURI(aLinks) {
    return aLinks.map(link => {
      if (link.favicon) {
        let encodedData = btoa(String.fromCharCode.apply(null, link.favicon));
        link.favicon = `data:${link.mimeType};base64,${encodedData}`;
        delete link.mimeType;
      }

      if (link.smallFavicon) {
        let encodedData = btoa(
          String.fromCharCode.apply(null, link.smallFavicon)
        );
        link.smallFavicon = `data:${link.smallFaviconMimeType};base64,${encodedData}`;
        delete link.smallFaviconMimeType;
      }

      return link;
    });
  },

  /**
   * Get favicon data (and metadata) for a uri. Fetches both the largest favicon
   * available, for Activity Stream; and a normal-sized favicon, for the Urlbar.
   *
   * @param {nsIURI} aUri Page to check for favicon data
   * @param {number} preferredFaviconWidth
   *   The preferred width of the of the normal-sized favicon in pixels.
   * @returns A promise of an object (possibly empty) containing the data.
   */
  async _loadIcons(aUri, preferredFaviconWidth) {
    let iconData = {};
    // Fetch the largest icon available.
    let faviconData;
    try {
      faviconData = await lazy.PlacesUtils.promiseFaviconData(
        aUri,
        this.THUMB_FAVICON_SIZE
      );
      Object.assign(iconData, {
        favicon: faviconData.data,
        faviconLength: faviconData.dataLen,
        faviconRef: faviconData.uri.ref,
        faviconSize: faviconData.size,
        mimeType: faviconData.mimeType,
      });
    } catch (e) {
      // Return early because fetching the largest favicon is the primary
      // purpose of NewTabUtils.
      return null;
    }

    // Also fetch a smaller icon.
    try {
      faviconData = await lazy.PlacesUtils.promiseFaviconData(
        aUri,
        preferredFaviconWidth
      );
      Object.assign(iconData, {
        smallFavicon: faviconData.data,
        smallFaviconLength: faviconData.dataLen,
        smallFaviconRef: faviconData.uri.ref,
        smallFaviconSize: faviconData.size,
        smallFaviconMimeType: faviconData.mimeType,
      });
    } catch (e) {
      // Do nothing with the error since we still have the large favicon fields.
    }

    return iconData;
  },

  /**
   * Computes favicon data for each url in a set of links
   *
   * @param {Array} links
   *          an array containing objects without favicon data or mimeTypes yet
   *
   * @returns {Promise} Returns a promise with the array of links with the largest
   *                    favicon available (as a byte array), mimeType, byte array
   *                    length, and favicon size (width)
   */
  _addFavicons(aLinks) {
    let win;
    if (BrowserWindowTracker) {
      win = BrowserWindowTracker.getTopWindow();
    }
    // We fetch two copies of a page's favicon: the largest available, for
    // Activity Stream; and a smaller size appropriate for the Urlbar.
    const preferredFaviconWidth =
      DEFAULT_SMALL_FAVICON_WIDTH * (win ? win.devicePixelRatio : 2);
    // Each link in the array needs a favicon for it's page - so we fire off a
    // promise for each link to compute the favicon data and attach it back to
    // the original link object. We must wait until all favicons for the array
    // of links are computed before returning
    return Promise.all(
      aLinks.map(
        link =>
          // eslint-disable-next-line no-async-promise-executor
          new Promise(async resolve => {
            // Never add favicon data for pocket items
            if (link.type === "pocket") {
              resolve(link);
              return;
            }
            let iconData;
            try {
              let linkUri = Services.io.newURI(link.url);
              iconData = await this._loadIcons(linkUri, preferredFaviconWidth);

              // Switch the scheme to try again with the other
              if (!iconData) {
                linkUri = linkUri
                  .mutate()
                  .setScheme(linkUri.scheme === "https" ? "http" : "https")
                  .finalize();
                iconData = await this._loadIcons(
                  linkUri,
                  preferredFaviconWidth
                );
              }
            } catch (e) {
              // We just won't put icon data on the link
            }

            // Add the icon data to the link if we have any
            resolve(Object.assign(link, iconData));
          })
      )
    );
  },

  /**
   * Helper function which makes the call to the Pocket API to fetch the user's
   * saved Pocket items.
   */
  fetchSavedPocketItems(requestData) {
    const latestSince =
      Services.prefs.getStringPref(PREF_POCKET_LATEST_SINCE, 0) * 1000;

    // Do not fetch Pocket items for users that have been inactive for too long, or are not logged in
    if (
      !lazy.pktApi.isUserLoggedIn() ||
      Date.now() - latestSince > POCKET_INACTIVE_TIME
    ) {
      return Promise.resolve(null);
    }

    return new Promise((resolve, reject) => {
      lazy.pktApi.retrieve(requestData, {
        success(data) {
          resolve(data);
        },
        error(error) {
          reject(error);
        },
      });
    });
  },

  /**
   * Get the most recently Pocket-ed items from a user's Pocket list. See:
   * https://getpocket.com/developer/docs/v3/retrieve for details
   *
   * @param {Object} aOptions
   *   {int} numItems: The max number of pocket items to fetch
   */
  async getRecentlyPocketed(aOptions) {
    const pocketSecondsAgo =
      Math.floor(Date.now() / 1000) - ACTIVITY_STREAM_DEFAULT_RECENT;
    const requestData = {
      detailType: "complete",
      count: aOptions.numItems,
      since: pocketSecondsAgo,
    };
    let data;
    try {
      data = await this.fetchSavedPocketItems(requestData);
      if (!data) {
        return [];
      }
    } catch (e) {
      console.error(e);
      return [];
    }
    /* Extract relevant parts needed to show this card as a highlight:
     * url, preview image, title, description, and the unique item_id
     * necessary for Pocket to identify the item
     */
    let items = Object.values(data.list)
      // status "0" means not archived or deleted
      .filter(item => item.status === "0")
      .map(item => ({
        date_added: item.time_added * 1000,
        description: item.excerpt,
        preview_image_url: item.image && item.image.src,
        title: item.resolved_title,
        url: item.resolved_url,
        pocket_id: item.item_id,
        open_url: item.open_url,
      }));

    // Append the query param to let Pocket know this item came from highlights
    for (let item of items) {
      let url = new URL(item.open_url);
      url.searchParams.append("src", "fx_new_tab");
      item.open_url = url.href;
    }

    return this._processHighlights(items, aOptions, "pocket");
  },

  /**
   * Get most-recently-created visited bookmarks for Activity Stream.
   *
   * @param {Object} aOptions
   *   {num}  bookmarkSecondsAgo: Maximum age of added bookmark.
   *   {bool} ignoreBlocked: Do not filter out blocked links.
   *   {int}  numItems: Maximum number of items to return.
   */
  async getRecentBookmarks(aOptions) {
    const options = Object.assign(
      {
        bookmarkSecondsAgo: ACTIVITY_STREAM_DEFAULT_RECENT,
        ignoreBlocked: false,
        numItems: ACTIVITY_STREAM_DEFAULT_LIMIT,
      },
      aOptions || {}
    );

    const sqlQuery = `
      SELECT
        b.guid AS bookmarkGuid,
        description,
        h.guid,
        preview_image_url,
        b.title,
        b.dateAdded / 1000 AS date_added,
        url
      FROM moz_bookmarks b
      JOIN moz_bookmarks p
        ON p.id = b.parent
      JOIN moz_places h
        ON h.id = b.fk
      WHERE b.dateAdded >= :dateAddedThreshold
        AND b.title NOTNULL
        AND b.type = :bookmarkType
        AND p.parent <> :tagsFolderId
        ${this._commonPlacesWhere}
      ORDER BY b.dateAdded DESC
      LIMIT :limit
    `;

    return this._processHighlights(
      await this.executePlacesQuery(sqlQuery, {
        columns: [...this._highlightsColumns, "date_added"],
        params: this._getCommonParams(options, {
          dateAddedThreshold:
            (Date.now() - options.bookmarkSecondsAgo * 1000) * 1000,
        }),
      }),
      options,
      "bookmark"
    );
  },

  /**
   * Get total count of all bookmarks.
   * Note: this includes default bookmarks
   *
   * @return {int} The number bookmarks in the places DB.
   */
  async getTotalBookmarksCount() {
    let sqlQuery = `
      SELECT count(*) FROM moz_bookmarks b
      JOIN moz_bookmarks t ON t.id = b.parent
      AND t.parent <> :tags_folder
     WHERE b.type = :type_bookmark
    `;

    const result = await this.executePlacesQuery(sqlQuery, {
      params: {
        tags_folder: lazy.PlacesUtils.tagsFolderId,
        type_bookmark: lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK,
      },
    });

    return result[0][0];
  },

  /**
   * Get most-recently-visited history with metadata for Activity Stream.
   *
   * @param {Object} aOptions
   *   {bool} ignoreBlocked: Do not filter out blocked links.
   *   {int}  numItems: Maximum number of items to return.
   */
  async getRecentHistory(aOptions) {
    const options = Object.assign(
      {
        ignoreBlocked: false,
        numItems: ACTIVITY_STREAM_DEFAULT_LIMIT,
      },
      aOptions || {}
    );

    const sqlQuery = `
      SELECT
        ${this._commonBookmarkGuidSelect},
        description,
        guid,
        preview_image_url,
        title,
        url
      FROM moz_places h
      WHERE description NOTNULL
        AND preview_image_url NOTNULL
        ${this._commonPlacesWhere}
      ORDER BY last_visit_date DESC
      LIMIT :limit
    `;

    return this._processHighlights(
      await this.executePlacesQuery(sqlQuery, {
        columns: this._highlightsColumns,
        params: this._getCommonParams(options),
      }),
      options,
      "history"
    );
  },

  /*
   * Gets the top frecent sites for Activity Stream.
   *
   * @param {Object} aOptions
   *   {bool} ignoreBlocked: Do not filter out blocked links.
   *   {int}  numItems: Maximum number of items to return.
   *   {int}  topsiteFrecency: Minimum amount of frecency for a site.
   *   {bool} onePerDomain: Dedupe the resulting list.
   *   {bool} includeFavicon: Include favicons if available.
   *   {string} hideWithSearchParam: URLs that contain this search param will be
   *     excluded from the returned links. This value should be either undefined
   *     or a string with one of the following forms:
   *     - undefined: Fall back to the value of pref
   *       `browser.newtabpage.activity-stream.hideTopSitesWithSearchParam`
   *     - "" (empty) - Disable this feature
   *     - "key" - Search param named "key" with any or no value
   *     - "key=" - Search param named "key" with no value
   *     - "key=value" - Search param named "key" with value "value"
   *
   * @returns {Promise} Returns a promise with the array of links as payload.
   */
  async getTopFrecentSites(aOptions) {
    const options = Object.assign(
      {
        ignoreBlocked: false,
        numItems: ACTIVITY_STREAM_DEFAULT_LIMIT,
        topsiteFrecency: ACTIVITY_STREAM_DEFAULT_FRECENCY,
        onePerDomain: true,
        includeFavicon: true,
        hideWithSearchParam: Services.prefs.getCharPref(
          "browser.newtabpage.activity-stream.hideTopSitesWithSearchParam",
          ""
        ),
      },
      aOptions || {}
    );

    // Double the item count in case the host is deduped between with www or
    // not-www (i.e., 2 hosts) and an extra buffer for multiple pages per host.
    const origNumItems = options.numItems;
    if (options.onePerDomain) {
      options.numItems *= 2 * 10;
    }

    // Keep this query fast with frecency-indexed lookups (even with excess
    // rows) and shift the more complex logic to post-processing afterwards
    const sqlQuery = `
      SELECT
        ${this._commonBookmarkGuidSelect},
        frecency,
        guid,
        last_visit_date / 1000 AS lastVisitDate,
        rev_host,
        title,
        url,
        "history" as type
      FROM moz_places h
      WHERE frecency >= :frecencyThreshold
        ${this._commonPlacesWhere}
      ORDER BY frecency DESC
      LIMIT :limit
    `;

    let links = await this.executePlacesQuery(sqlQuery, {
      columns: [
        "bookmarkGuid",
        "frecency",
        "guid",
        "lastVisitDate",
        "title",
        "url",
        "type",
      ],
      params: this._getCommonParams(options, {
        frecencyThreshold: options.topsiteFrecency,
      }),
    });

    // Determine if the other link is "better" (larger frecency, more recent,
    // lexicographically earlier url)
    function isOtherBetter(link, other) {
      if (other.frecency === link.frecency) {
        if (other.lastVisitDate === link.lastVisitDate) {
          return other.url < link.url;
        }
        return other.lastVisitDate > link.lastVisitDate;
      }
      return other.frecency > link.frecency;
    }

    // Update a host Map with the better link
    function setBetterLink(map, link, hostMatcher, combiner = () => {}) {
      const host = hostMatcher(link.url)[1];
      if (map.has(host)) {
        const other = map.get(host);
        if (isOtherBetter(link, other)) {
          link = other;
        }
        combiner(link, other);
      }
      map.set(host, link);
    }

    // Convert all links that are supposed to be a seach shortcut to its canonical URL
    if (
      didSuccessfulImport &&
      Services.prefs.getBoolPref(
        `browser.newtabpage.activity-stream.${searchShortcuts.SEARCH_SHORTCUTS_EXPERIMENT}`
      )
    ) {
      links.forEach(link => {
        let searchProvider = searchShortcuts.getSearchProvider(
          shortURL.shortURL(link)
        );
        if (searchProvider) {
          link.url = searchProvider.url;
        }
      });
    }

    // Remove links that contain the hide-with search param.
    if (options.hideWithSearchParam) {
      let [key, value] = options.hideWithSearchParam.split("=");
      links = links.filter(link => {
        try {
          let { searchParams } = new URL(link.url);
          return value === undefined
            ? !searchParams.has(key)
            : !searchParams.getAll(key).includes(value);
        } catch (error) {}
        return true;
      });
    }

    // Remove any blocked links.
    if (!options.ignoreBlocked) {
      links = links.filter(link => !BlockedLinks.isBlocked(link));
    }

    if (options.onePerDomain) {
      // De-dup the links.
      const exactHosts = new Map();
      for (const link of links) {
        // First we want to find the best link for an exact host
        setBetterLink(exactHosts, link, url => url.match(/:\/\/([^\/]+)/));
      }

      // Clean up exact hosts to dedupe as non-www hosts
      const hosts = new Map();
      for (const link of exactHosts.values()) {
        setBetterLink(
          hosts,
          link,
          url => url.match(/:\/\/(?:www\.)?([^\/]+)/),
          // Combine frecencies when deduping these links
          (targetLink, otherLink) => {
            targetLink.frecency = link.frecency + otherLink.frecency;
          }
        );
      }

      links = [...hosts.values()];
    }
    // Pick out the top links using the same comparer as before
    links = links.sort(isOtherBetter).slice(0, origNumItems);

    if (!options.includeFavicon) {
      return links;
    }
    // Get the favicons as data URI for now (until we use the favicon protocol)
    return this._faviconBytesToDataURI(await this._addFavicons(links));
  },

  /**
   * Gets a specific bookmark given some info about it
   *
   * @param {Obj} aInfo
   *          An object with one and only one of the following properties:
   *            - url
   *            - guid
   *            - parentGuid and index
   */
  async getBookmark(aInfo) {
    let bookmark = await lazy.PlacesUtils.bookmarks.fetch(aInfo);
    if (!bookmark) {
      return null;
    }
    let result = {};
    result.bookmarkGuid = bookmark.guid;
    result.bookmarkTitle = bookmark.title;
    result.lastModified = bookmark.lastModified.getTime();
    result.url = bookmark.url.href;
    return result;
  },

  /**
   * Count the number of visited urls grouped by day
   */
  getUserMonthlyActivity() {
    let sqlQuery = `
      SELECT count(*),
        strftime('%Y-%m-%d', visit_date/1000000.0, 'unixepoch', 'localtime') as date_format
      FROM moz_historyvisits
      WHERE visit_date > 0
      AND visit_date > strftime('%s','now','localtime','start of day','-27 days','utc') * 1000000
      GROUP BY date_format
      ORDER BY date_format ASC
    `;

    return this.executePlacesQuery(sqlQuery);
  },

  /**
   * Executes arbitrary query against places database
   *
   * @param {String} aQuery
   *        SQL query to execute
   * @param {Object} [optional] aOptions
   *          aOptions.columns - an array of column names. if supplied the return
   *          items will consists of objects keyed on column names. Otherwise
   *          array of raw values is returned in the select order
   *          aOptions.param - an object of SQL binding parameters
   *
   * @returns {Promise} Returns a promise with the array of retrieved items
   */
  async executePlacesQuery(aQuery, aOptions = {}) {
    let { columns, params } = aOptions;
    let items = [];
    let queryError = null;
    let conn = await lazy.PlacesUtils.promiseDBConnection();
    await conn.executeCached(aQuery, params, (aRow, aCancel) => {
      try {
        let item = null;
        // if columns array is given construct an object
        if (columns && Array.isArray(columns)) {
          item = {};
          columns.forEach(column => {
            item[column] = aRow.getResultByName(column);
          });
        } else {
          // if no columns - make an array of raw values
          item = [];
          for (let i = 0; i < aRow.numEntries; i++) {
            item.push(aRow.getResultByIndex(i));
          }
        }
        items.push(item);
      } catch (e) {
        queryError = e;
        aCancel();
      }
    });
    if (queryError) {
      throw new Error(queryError);
    }
    return items;
  },
};

/**
 * A set of actions which influence what sites shown on the Activity Stream page
 */
var ActivityStreamLinks = {
  _savedPocketStories: null,
  _pocketLastUpdated: 0,
  _pocketLastLatest: 0,

  /**
   * Block a url
   *
   * @param {Object} aLink
   *          The link which contains a URL to add to the block list
   */
  blockURL(aLink) {
    BlockedLinks.block(aLink);
    // If we're blocking a pocket item, invalidate the cache too
    if (aLink.pocket_id) {
      this._savedPocketStories = null;
    }
  },

  onLinkBlocked(aLink) {
    Services.obs.notifyObservers(null, "newtab-linkBlocked", aLink.url);
  },

  /**
   * Adds a bookmark and opens up the Bookmark Dialog to show feedback that
   * the bookmarking action has been successful
   *
   * @param {Object} aData
   *          aData.url The url to bookmark
   *          aData.title The title of the page to bookmark
   * @param {Window} aBrowserWindow
   *          The current browser chrome window
   *
   * @returns {Promise} Returns a promise set to an object representing the bookmark
   */
  addBookmark(aData, aBrowserWindow) {
    const { url, title } = aData;
    return aBrowserWindow.PlacesCommandHook.bookmarkLink(url, title);
  },

  /**
   * Removes a bookmark
   *
   * @param {String} aBookmarkGuid
   *          The bookmark guid associated with the bookmark to remove
   *
   * @returns {Promise} Returns a promise at completion.
   */
  deleteBookmark(aBookmarkGuid) {
    return lazy.PlacesUtils.bookmarks.remove(aBookmarkGuid);
  },

  /**
   * Removes a history link and unpins the URL if previously pinned
   *
   * @param {String} aUrl
   *           The url to be removed from history
   *
   * @returns {Promise} Returns a promise set to true if link was removed
   */
  deleteHistoryEntry(aUrl) {
    const url = aUrl;
    PinnedLinks.unpin({ url });
    return lazy.PlacesUtils.history.remove(url);
  },

  /**
   * Helper function which makes the call to the Pocket API to delete an item from
   * a user's saved to Pocket feed. Also, invalidate the Pocket stories cache
   *
   * @param {Integer} aItemID
   *           The unique pocket ID used to find the item to be deleted
   *
   *@returns {Promise} Returns a promise at completion
   */
  deletePocketEntry(aItemID) {
    this._savedPocketStories = null;
    return new Promise((success, error) =>
      lazy.pktApi.deleteItem(aItemID, { success, error })
    );
  },

  /**
   * Helper function which makes the call to the Pocket API to archive an item from
   * a user's saved to Pocket feed. Also, invalidate the Pocket stories cache
   *
   * @param {Integer} aItemID
   *           The unique pocket ID used to find the item to be archived
   *
   *@returns {Promise} Returns a promise at completion
   */
  archivePocketEntry(aItemID) {
    this._savedPocketStories = null;
    return new Promise((success, error) =>
      lazy.pktApi.archiveItem(aItemID, { success, error })
    );
  },

  /**
   * Helper function which makes the call to the Pocket API to save an item to
   * a user's saved to Pocket feed if they are logged in. Also, invalidate the
   * Pocket stories cache
   *
   * @param {String} aUrl
   *           The URL belonging to the story being saved
   * @param {String} aTitle
   *           The title belonging to the story being saved
   * @param {Browser} aBrowser
   *           The target browser to show the doorhanger in
   *
   *@returns {Promise} Returns a promise at completion
   */
  addPocketEntry(aUrl, aTitle, aBrowser) {
    // If the user is not logged in, show the panel to prompt them to log in
    if (!lazy.pktApi.isUserLoggedIn()) {
      lazy.Pocket.savePage(aBrowser, aUrl, aTitle);
      return Promise.resolve(null);
    }

    // If the user is logged in, just save the link to Pocket and Activity Stream
    // will update the page
    this._savedPocketStories = null;
    return new Promise((success, error) => {
      lazy.pktApi.addLink(aUrl, {
        title: aTitle,
        success,
        error,
      });
    });
  },

  /**
   * Get the Highlights links to show on Activity Stream
   *
   * @param {Object} aOptions
   *   {bool} excludeBookmarks: Don't add bookmark items.
   *   {bool} excludeHistory: Don't add history items.
   *   {bool} excludePocket: Don't add Pocket items.
   *   {bool} withFavicons: Add favicon data: URIs, when possible.
   *   {int}  numItems: Maximum number of (bookmark or history) items to return.
   *
   * @return {Promise} Returns a promise with the array of links as the payload
   */
  async getHighlights(aOptions = {}) {
    aOptions.numItems = aOptions.numItems || ACTIVITY_STREAM_DEFAULT_LIMIT;
    const results = [];

    // First get bookmarks if we want them
    if (!aOptions.excludeBookmarks) {
      results.push(
        ...(await ActivityStreamProvider.getRecentBookmarks(aOptions))
      );
    }

    // Add the Pocket items if we need more and want them
    if (aOptions.numItems - results.length > 0 && !aOptions.excludePocket) {
      const latestSince = ~~Services.prefs.getStringPref(
        PREF_POCKET_LATEST_SINCE,
        0
      );
      // Invalidate the cache, get new stories, and update timestamps if:
      //  1. we do not have saved to Pocket stories already cached OR
      //  2. it has been too long since we last got Pocket stories OR
      //  3. there has been a paged saved to pocket since we last got new stories
      if (
        !this._savedPocketStories ||
        Date.now() - this._pocketLastUpdated > POCKET_UPDATE_TIME ||
        this._pocketLastLatest < latestSince
      ) {
        this._savedPocketStories =
          await ActivityStreamProvider.getRecentlyPocketed(aOptions);
        this._pocketLastUpdated = Date.now();
        this._pocketLastLatest = latestSince;
      }
      results.push(...this._savedPocketStories);
    }

    // Add in history if we need more and want them
    if (aOptions.numItems - results.length > 0 && !aOptions.excludeHistory) {
      // Use the same numItems as bookmarks above in case we remove duplicates
      const history = await ActivityStreamProvider.getRecentHistory(aOptions);

      // Only include a url once in the result preferring the bookmark
      const bookmarkUrls = new Set(results.map(({ url }) => url));
      for (const page of history) {
        if (!bookmarkUrls.has(page.url)) {
          results.push(page);

          // Stop adding pages once we reach the desired maximum
          if (results.length === aOptions.numItems) {
            break;
          }
        }
      }
    }

    if (aOptions.withFavicons) {
      return ActivityStreamProvider._faviconBytesToDataURI(
        await ActivityStreamProvider._addFavicons(results)
      );
    }

    return results;
  },

  /**
   * Get the top sites to show on Activity Stream
   *
   * @return {Promise} Returns a promise with the array of links as the payload
   */
  async getTopSites(aOptions = {}) {
    return ActivityStreamProvider.getTopFrecentSites(aOptions);
  },
};

/**
 * Singleton that provides access to all links contained in the grid (including
 * the ones that don't fit on the grid). A link is a plain object that looks
 * like this:
 *
 * {
 *   url: "http://www.mozilla.org/",
 *   title: "Mozilla",
 *   frecency: 1337,
 *   lastVisitDate: 1394678824766431,
 * }
 */
var Links = {
  /**
   * The maximum number of links returned by getLinks.
   */
  maxNumLinks: LINKS_GET_LINKS_LIMIT,

  /**
   * A mapping from each provider to an object { sortedLinks, siteMap, linkMap }.
   * sortedLinks is the cached, sorted array of links for the provider.
   * siteMap is a mapping from base domains to URL count associated with the domain.
   *         The count does not include blocked URLs. siteMap is used to look up a
   *         user's top sites that can be targeted with a suggested tile.
   * linkMap is a Map from link URLs to link objects.
   */
  _providers: new Map(),

  /**
   * The properties of link objects used to sort them.
   */
  _sortProperties: ["frecency", "lastVisitDate", "url"],

  /**
   * List of callbacks waiting for the cache to be populated.
   */
  _populateCallbacks: [],

  /**
   * A list of objects that are observing links updates.
   */
  _observers: [],

  /**
   * Registers an object that will be notified when links updates.
   */
  addObserver(aObserver) {
    this._observers.push(aObserver);
  },

  /**
   * Adds a link provider.
   * @param aProvider The link provider.
   */
  addProvider: function Links_addProvider(aProvider) {
    this._providers.set(aProvider, null);
    aProvider.addObserver(this);
  },

  /**
   * Removes a link provider.
   * @param aProvider The link provider.
   */
  removeProvider: function Links_removeProvider(aProvider) {
    if (!this._providers.delete(aProvider)) {
      throw new Error("Unknown provider");
    }
  },

  /**
   * Populates the cache with fresh links from the providers.
   * @param aCallback The callback to call when finished (optional).
   * @param aForce When true, populates the cache even when it's already filled.
   */
  populateCache: function Links_populateCache(aCallback, aForce) {
    let callbacks = this._populateCallbacks;

    // Enqueue the current callback.
    callbacks.push(aCallback);

    // There was a callback waiting already, thus the cache has not yet been
    // populated.
    if (callbacks.length > 1) {
      return;
    }

    function executeCallbacks() {
      while (callbacks.length) {
        let callback = callbacks.shift();
        if (callback) {
          try {
            callback();
          } catch (e) {
            // We want to proceed even if a callback fails.
          }
        }
      }
    }

    let numProvidersRemaining = this._providers.size;
    for (let [provider /* , links */] of this._providers) {
      this._populateProviderCache(
        provider,
        () => {
          if (--numProvidersRemaining == 0) {
            executeCallbacks();
          }
        },
        aForce
      );
    }

    this._addObserver();
  },

  /**
   * Gets the current set of links contained in the grid.
   * @return The links in the grid.
   */
  getLinks: function Links_getLinks() {
    let pinnedLinks = Array.from(PinnedLinks.links);
    let links = this._getMergedProviderLinks();

    let sites = new Set();
    for (let link of pinnedLinks) {
      if (link) {
        sites.add(NewTabUtils.extractSite(link.url));
      }
    }

    // Filter blocked and pinned links and duplicate base domains.
    links = links.filter(function (link) {
      let site = NewTabUtils.extractSite(link.url);
      if (site == null || sites.has(site)) {
        return false;
      }
      sites.add(site);

      return !BlockedLinks.isBlocked(link) && !PinnedLinks.isPinned(link);
    });

    // Try to fill the gaps between pinned links.
    for (let i = 0; i < pinnedLinks.length && links.length; i++) {
      if (!pinnedLinks[i]) {
        pinnedLinks[i] = links.shift();
      }
    }

    // Append the remaining links if any.
    if (links.length) {
      pinnedLinks = pinnedLinks.concat(links);
    }

    for (let link of pinnedLinks) {
      if (link) {
        link.baseDomain = NewTabUtils.extractSite(link.url);
      }
    }
    return pinnedLinks;
  },

  /**
   * Resets the links cache.
   */
  resetCache: function Links_resetCache() {
    for (let provider of this._providers.keys()) {
      this._providers.set(provider, null);
    }
  },

  /**
   * Compares two links.
   * @param aLink1 The first link.
   * @param aLink2 The second link.
   * @return A negative number if aLink1 is ordered before aLink2, zero if
   *         aLink1 and aLink2 have the same ordering, or a positive number if
   *         aLink1 is ordered after aLink2.
   *
   * @note compareLinks's this object is bound to Links below.
   */
  compareLinks: function Links_compareLinks(aLink1, aLink2) {
    for (let prop of this._sortProperties) {
      if (!(prop in aLink1) || !(prop in aLink2)) {
        throw new Error("Comparable link missing required property: " + prop);
      }
    }
    return (
      aLink2.frecency - aLink1.frecency ||
      aLink2.lastVisitDate - aLink1.lastVisitDate ||
      aLink1.url.localeCompare(aLink2.url)
    );
  },

  _incrementSiteMap(map, link) {
    if (NewTabUtils.blockedLinks.isBlocked(link)) {
      // Don't count blocked URLs.
      return;
    }
    let site = NewTabUtils.extractSite(link.url);
    map.set(site, (map.get(site) || 0) + 1);
  },

  _decrementSiteMap(map, link) {
    if (NewTabUtils.blockedLinks.isBlocked(link)) {
      // Blocked URLs are not included in map.
      return;
    }
    let site = NewTabUtils.extractSite(link.url);
    let previousURLCount = map.get(site);
    if (previousURLCount === 1) {
      map.delete(site);
    } else {
      map.set(site, previousURLCount - 1);
    }
  },

  /**
   * Update the siteMap cache based on the link given and whether we need
   * to increment or decrement it. We do this by iterating over all stored providers
   * to find which provider this link already exists in. For providers that
   * have this link, we will adjust siteMap for them accordingly.
   *
   * @param aLink The link that will affect siteMap
   * @param increment A boolean for whether to increment or decrement siteMap
   */
  _adjustSiteMapAndNotify(aLink, increment = true) {
    for (let [, /* provider */ cache] of this._providers) {
      // We only update siteMap if aLink is already stored in linkMap.
      if (cache.linkMap.get(aLink.url)) {
        if (increment) {
          this._incrementSiteMap(cache.siteMap, aLink);
          continue;
        }
        this._decrementSiteMap(cache.siteMap, aLink);
      }
    }
    this._callObservers("onLinkChanged", aLink);
  },

  onLinkBlocked(aLink) {
    this._adjustSiteMapAndNotify(aLink, false);
  },

  onLinkUnblocked(aLink) {
    this._adjustSiteMapAndNotify(aLink);
  },

  populateProviderCache(provider, callback) {
    if (!this._providers.has(provider)) {
      throw new Error(
        "Can only populate provider cache for existing provider."
      );
    }

    return this._populateProviderCache(provider, callback, false);
  },

  /**
   * Calls getLinks on the given provider and populates our cache for it.
   * @param aProvider The provider whose cache will be populated.
   * @param aCallback The callback to call when finished.
   * @param aForce When true, populates the provider's cache even when it's
   *               already filled.
   */
  _populateProviderCache(aProvider, aCallback, aForce) {
    let cache = this._providers.get(aProvider);
    let createCache = !cache;
    if (createCache) {
      cache = {
        // Start with a resolved promise.
        populatePromise: new Promise(resolve => resolve()),
      };
      this._providers.set(aProvider, cache);
    }
    // Chain the populatePromise so that calls are effectively queued.
    cache.populatePromise = cache.populatePromise.then(() => {
      return new Promise(resolve => {
        if (!createCache && !aForce) {
          aCallback();
          resolve();
          return;
        }
        aProvider.getLinks(links => {
          // Filter out null and undefined links so we don't have to deal with
          // them in getLinks when merging links from providers.
          links = links.filter(link => !!link);
          cache.sortedLinks = links;
          cache.siteMap = links.reduce((map, link) => {
            this._incrementSiteMap(map, link);
            return map;
          }, new Map());
          cache.linkMap = links.reduce((map, link) => {
            map.set(link.url, link);
            return map;
          }, new Map());
          aCallback();
          resolve();
        });
      });
    });
  },

  /**
   * Merges the cached lists of links from all providers whose lists are cached.
   * @return The merged list.
   */
  _getMergedProviderLinks: function Links__getMergedProviderLinks() {
    // Build a list containing a copy of each provider's sortedLinks list.
    let linkLists = [];
    for (let provider of this._providers.keys()) {
      let links = this._providers.get(provider);
      if (links && links.sortedLinks) {
        linkLists.push(links.sortedLinks.slice());
      }
    }

    return this.mergeLinkLists(linkLists);
  },

  mergeLinkLists: function Links_mergeLinkLists(linkLists) {
    if (linkLists.length == 1) {
      return linkLists[0];
    }

    function getNextLink() {
      let minLinks = null;
      for (let links of linkLists) {
        if (
          links.length &&
          (!minLinks || Links.compareLinks(links[0], minLinks[0]) < 0)
        ) {
          minLinks = links;
        }
      }
      return minLinks ? minLinks.shift() : null;
    }

    let finalLinks = [];
    for (
      let nextLink = getNextLink();
      nextLink && finalLinks.length < this.maxNumLinks;
      nextLink = getNextLink()
    ) {
      finalLinks.push(nextLink);
    }

    return finalLinks;
  },

  /**
   * Called by a provider to notify us when a single link changes.
   * @param aProvider The provider whose link changed.
   * @param aLink The link that changed.  If the link is new, it must have all
   *              of the _sortProperties.  Otherwise, it may have as few or as
   *              many as is convenient.
   * @param aIndex The current index of the changed link in the sortedLinks
                   cache in _providers. Defaults to -1 if the provider doesn't know the index
   * @param aDeleted Boolean indicating if the provider has deleted the link.
   */
  onLinkChanged: function Links_onLinkChanged(
    aProvider,
    aLink,
    aIndex = -1,
    aDeleted = false
  ) {
    if (!("url" in aLink)) {
      throw new Error("Changed links must have a url property");
    }

    let links = this._providers.get(aProvider);
    if (!links) {
      // This is not an error, it just means that between the time the provider
      // was added and the future time we call getLinks on it, it notified us of
      // a change.
      return;
    }

    let { sortedLinks, siteMap, linkMap } = links;
    let existingLink = linkMap.get(aLink.url);
    let insertionLink = null;
    let updatePages = false;

    if (existingLink) {
      // Update our copy's position in O(lg n) by first removing it from its
      // list.  It's important to do this before modifying its properties.
      if (this._sortProperties.some(prop => prop in aLink)) {
        let idx = aIndex;
        if (idx < 0) {
          idx = this._indexOf(sortedLinks, existingLink);
        } else if (this.compareLinks(aLink, sortedLinks[idx]) != 0) {
          throw new Error("aLink should be the same as sortedLinks[idx]");
        }

        if (idx < 0) {
          throw new Error("Link should be in _sortedLinks if in _linkMap");
        }
        sortedLinks.splice(idx, 1);

        if (aDeleted) {
          updatePages = true;
          linkMap.delete(existingLink.url);
          this._decrementSiteMap(siteMap, existingLink);
        } else {
          // Update our copy's properties.
          Object.assign(existingLink, aLink);

          // Finally, reinsert our copy below.
          insertionLink = existingLink;
        }
      }
      // Update our copy's title in O(1).
      if ("title" in aLink && aLink.title != existingLink.title) {
        existingLink.title = aLink.title;
        updatePages = true;
      }
    } else if (this._sortProperties.every(prop => prop in aLink)) {
      // Before doing the O(lg n) insertion below, do an O(1) check for the
      // common case where the new link is too low-ranked to be in the list.
      if (sortedLinks.length && sortedLinks.length == aProvider.maxNumLinks) {
        let lastLink = sortedLinks[sortedLinks.length - 1];
        if (this.compareLinks(lastLink, aLink) < 0) {
          return;
        }
      }
      // Copy the link object so that changes later made to it by the caller
      // don't affect our copy.
      insertionLink = {};
      for (let prop in aLink) {
        insertionLink[prop] = aLink[prop];
      }
      linkMap.set(aLink.url, insertionLink);
      this._incrementSiteMap(siteMap, aLink);
    }

    if (insertionLink) {
      let idx = this._insertionIndexOf(sortedLinks, insertionLink);
      sortedLinks.splice(idx, 0, insertionLink);
      if (sortedLinks.length > aProvider.maxNumLinks) {
        let lastLink = sortedLinks.pop();
        linkMap.delete(lastLink.url);
        this._decrementSiteMap(siteMap, lastLink);
      }
      updatePages = true;
    }

    if (updatePages) {
      AllPages.update(null, "links-changed");
    }
  },

  /**
   * Called by a provider to notify us when many links change.
   */
  onManyLinksChanged: function Links_onManyLinksChanged(aProvider) {
    this._populateProviderCache(
      aProvider,
      () => {
        AllPages.update(null, "links-changed");
      },
      true
    );
  },

  _indexOf: function Links__indexOf(aArray, aLink) {
    return this._binsearch(aArray, aLink, "indexOf");
  },

  _insertionIndexOf: function Links__insertionIndexOf(aArray, aLink) {
    return this._binsearch(aArray, aLink, "insertionIndexOf");
  },

  _binsearch: function Links__binsearch(aArray, aLink, aMethod) {
    return lazy.BinarySearch[aMethod](this.compareLinks, aArray, aLink);
  },

  /**
   * Implements the nsIObserver interface to get notified about browser history
   * sanitization.
   */
  observe: function Links_observe() {
    // Make sure to update open about:newtab instances. If there are no opened
    // pages we can just wait for the next new tab to populate the cache again.
    if (AllPages.length && AllPages.enabled) {
      this.populateCache(function () {
        AllPages.update();
      }, true);
    } else {
      this.resetCache();
    }
  },

  _callObservers(methodName, ...args) {
    for (let obs of this._observers) {
      if (typeof obs[methodName] == "function") {
        try {
          obs[methodName](this, ...args);
        } catch (err) {
          console.error(err);
        }
      }
    }
  },

  /**
   * Adds a sanitization observer and turns itself into a no-op after the first
   * invokation.
   */
  _addObserver: function Links_addObserver() {
    Services.obs.addObserver(this, "browser:purge-session-history", true);
    this._addObserver = function () {};
  },

  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
  ]),
};

Links.compareLinks = Links.compareLinks.bind(Links);

/**
 * Singleton used to collect telemetry data.
 *
 */
var Telemetry = {
  /**
   * Initializes object.
   */
  init: function Telemetry_init() {
    Services.obs.addObserver(this, TOPIC_GATHER_TELEMETRY);
  },

  uninit: function Telemetry_uninit() {
    Services.obs.removeObserver(this, TOPIC_GATHER_TELEMETRY);
  },

  /**
   * Collects data.
   */
  _collect: function Telemetry_collect() {
    let probes = [
      { histogram: "NEWTAB_PAGE_ENABLED", value: AllPages.enabled },
      {
        histogram: "NEWTAB_PAGE_PINNED_SITES_COUNT",
        value: PinnedLinks.links.length,
      },
      {
        histogram: "NEWTAB_PAGE_BLOCKED_SITES_COUNT",
        value: Object.keys(BlockedLinks.links).length,
      },
    ];

    probes.forEach(function Telemetry_collect_forEach(aProbe) {
      Services.telemetry.getHistogramById(aProbe.histogram).add(aProbe.value);
    });
  },

  /**
   * Listens for gather telemetry topic.
   */
  observe: function Telemetry_observe() {
    this._collect();
  },
};

/**
 * Singleton that checks if a given link should be displayed on about:newtab
 * or if we should rather not do it for security reasons. URIs that inherit
 * their caller's principal will be filtered.
 */
var LinkChecker = {
  _cache: {},

  get flags() {
    return (
      Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL |
      Ci.nsIScriptSecurityManager.DONT_REPORT_ERRORS
    );
  },

  checkLoadURI: function LinkChecker_checkLoadURI(aURI) {
    if (!(aURI in this._cache)) {
      this._cache[aURI] = this._doCheckLoadURI(aURI);
    }

    return this._cache[aURI];
  },

  _doCheckLoadURI: function Links_doCheckLoadURI(aURI) {
    try {
      // about:newtab is currently privileged. In any case, it should be
      // possible for tiles to point to pretty much everything - but not
      // to stuff that inherits the system principal, so we check:
      let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
      Services.scriptSecurityManager.checkLoadURIStrWithPrincipal(
        systemPrincipal,
        aURI,
        this.flags
      );
      return true;
    } catch (e) {
      // We got a weird URI or one that would inherit the caller's principal.
      return false;
    }
  },
};

var ExpirationFilter = {
  init: function ExpirationFilter_init() {
    lazy.PageThumbs.addExpirationFilter(this);
  },

  filterForThumbnailExpiration:
    function ExpirationFilter_filterForThumbnailExpiration(aCallback) {
      if (!AllPages.enabled) {
        aCallback([]);
        return;
      }

      Links.populateCache(function () {
        let urls = [];

        // Add all URLs to the list that we want to keep thumbnails for.
        for (let link of Links.getLinks().slice(0, 25)) {
          if (link && link.url) {
            urls.push(link.url);
          }
        }

        aCallback(urls);
      });
    },
};

/**
 * Singleton that provides the public API of this JSM.
 */
export var NewTabUtils = {
  _initialized: false,

  /**
   * Extract a "site" from a url in a way that multiple urls of a "site" returns
   * the same "site."
   * @param aUrl Url spec string
   * @return The "site" string or null
   */
  extractSite: function Links_extractSite(url) {
    let host;
    try {
      // Note that nsIURI.asciiHost throws NS_ERROR_FAILURE for some types of
      // URIs, including jar and moz-icon URIs.
      host = Services.io.newURI(url).asciiHost;
    } catch (ex) {
      return null;
    }

    // Strip off common subdomains of the same site (e.g., www, load balancer)
    return host.replace(/^(m|mobile|www\d*)\./, "");
  },

  init: function NewTabUtils_init() {
    if (this.initWithoutProviders()) {
      PlacesProvider.init();
      Links.addProvider(PlacesProvider);
      BlockedLinks.addObserver(Links);
      BlockedLinks.addObserver(ActivityStreamLinks);
    }
  },

  initWithoutProviders: function NewTabUtils_initWithoutProviders() {
    if (!this._initialized) {
      this._initialized = true;
      ExpirationFilter.init();
      Telemetry.init();
      return true;
    }
    return false;
  },

  uninit: function NewTabUtils_uninit() {
    if (this.initialized) {
      Telemetry.uninit();
      BlockedLinks.removeObservers();
    }
  },

  getProviderLinks(aProvider) {
    let cache = Links._providers.get(aProvider);
    if (cache && cache.sortedLinks) {
      return cache.sortedLinks;
    }
    return [];
  },

  isTopSiteGivenProvider(aSite, aProvider) {
    let cache = Links._providers.get(aProvider);
    if (cache && cache.siteMap) {
      return cache.siteMap.has(aSite);
    }
    return false;
  },

  isTopPlacesSite(aSite) {
    return this.isTopSiteGivenProvider(aSite, PlacesProvider);
  },

  /**
   * Restores all sites that have been removed from the grid.
   */
  restore: function NewTabUtils_restore() {
    lazy.Storage.clear();
    Links.resetCache();
    PinnedLinks.resetCache();
    BlockedLinks.resetCache();

    Links.populateCache(function () {
      AllPages.update();
    }, true);
  },

  /**
   * Undoes all sites that have been removed from the grid and keep the pinned
   * tabs.
   * @param aCallback the callback method.
   */
  undoAll: function NewTabUtils_undoAll(aCallback) {
    lazy.Storage.remove("blockedLinks");
    Links.resetCache();
    BlockedLinks.resetCache();
    Links.populateCache(aCallback, true);
  },

  links: Links,
  allPages: AllPages,
  pinnedLinks: PinnedLinks,
  blockedLinks: BlockedLinks,
  activityStreamLinks: ActivityStreamLinks,
  activityStreamProvider: ActivityStreamProvider,
};
PK
!<;�|M`.`.modules/NotificationDB.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  return console.createInstance({
    prefix: "NotificationDB",
    maxLogLevelPref: "dom.webnotifications.loglevel",
  });
});

const NOTIFICATION_STORE_DIR = PathUtils.profileDir;
const NOTIFICATION_STORE_PATH = PathUtils.join(
  NOTIFICATION_STORE_DIR,
  "notificationstore.json"
);

export class NotificationDB {
  // Ensure we won't call init() while xpcom-shutdown is performed
  #shutdownInProgress = false;

  // A promise that resolves once the ongoing task queue has been drained.
  // The value will be reset when the queue starts again.
  #queueDrainedPromise = null;
  #queueDrainedPromiseResolve = null;

  #byTag = Object.create(null);
  #notifications = Object.create(null);
  #loaded = false;
  #tasks = [];
  #runningTask = null;

  storageQualifier() {
    return "Notification";
  }

  prefixStorageQualifier(message) {
    return `${this.storageQualifier()}:${message}`;
  }

  formatMessageType(message) {
    return this.prefixStorageQualifier(message);
  }

  supportedMessageTypes() {
    return [
      this.formatMessageType("Save"),
      this.formatMessageType("Delete"),
      this.formatMessageType("GetAll"),
    ];
  }

  constructor() {
    if (this.#shutdownInProgress) {
      return;
    }

    this.#notifications = Object.create(null);
    this.#byTag = Object.create(null);
    this.#loaded = false;

    this.#tasks = []; // read/write operation queue
    this.#runningTask = null;

    Services.obs.addObserver(this, "xpcom-shutdown");
    this.registerListeners();

    // This assumes that nothing will queue a new task at profile-change-teardown phase,
    // potentially replacing the #queueDrainedPromise if there was no existing task run.
    lazy.AsyncShutdown.profileChangeTeardown.addBlocker(
      "NotificationDB: Need to make sure that all notification messages are processed",
      () => this.#queueDrainedPromise
    );
  }

  registerListeners() {
    for (let message of this.supportedMessageTypes()) {
      Services.ppmm.addMessageListener(message, this);
    }
  }

  unregisterListeners() {
    for (let message of this.supportedMessageTypes()) {
      Services.ppmm.removeMessageListener(message, this);
    }
  }

  observe(aSubject, aTopic) {
    lazy.console.debug(`Topic: ${aTopic}`);
    if (aTopic == "xpcom-shutdown") {
      this.#shutdownInProgress = true;
      Services.obs.removeObserver(this, "xpcom-shutdown");
      this.unregisterListeners();
    }
  }

  filterNonAppNotifications(notifications) {
    let result = Object.create(null);
    for (let origin in notifications) {
      result[origin] = Object.create(null);
      let persistentNotificationCount = 0;
      for (let id in notifications[origin]) {
        if (notifications[origin][id].serviceWorkerRegistrationScope) {
          persistentNotificationCount++;
          result[origin][id] = notifications[origin][id];
        }
      }
      if (persistentNotificationCount == 0) {
        lazy.console.debug(
          `Origin ${origin} is not linked to an app manifest, deleting.`
        );
        delete result[origin];
      }
    }

    return result;
  }

  // Attempt to read notification file, if it's not there we will create it.
  load() {
    var promise = IOUtils.readUTF8(NOTIFICATION_STORE_PATH);
    return promise.then(
      data => {
        if (data.length) {
          // Preprocessing phase intends to cleanly separate any migration-related
          // tasks.
          this.#notifications = this.filterNonAppNotifications(
            JSON.parse(data)
          );
        }

        // populate the list of notifications by tag
        if (this.#notifications) {
          for (var origin in this.#notifications) {
            this.#byTag[origin] = Object.create(null);
            for (var id in this.#notifications[origin]) {
              var curNotification = this.#notifications[origin][id];
              if (curNotification.tag) {
                this.#byTag[origin][curNotification.tag] = curNotification;
              }
            }
          }
        }

        this.#loaded = true;
      },

      // If read failed, we assume we have no notifications to load.
      () => {
        this.#loaded = true;
        return this.createStore();
      }
    );
  }

  // Creates the notification directory.
  createStore() {
    var promise = IOUtils.makeDirectory(NOTIFICATION_STORE_DIR, {
      ignoreExisting: true,
    });
    return promise.then(this.createFile.bind(this));
  }

  // Creates the notification file once the directory is created.
  createFile() {
    return IOUtils.writeUTF8(NOTIFICATION_STORE_PATH, "", {
      tmpPath: NOTIFICATION_STORE_PATH + ".tmp",
    });
  }

  // Save current notifications to the file.
  save() {
    var data = JSON.stringify(this.#notifications);
    return IOUtils.writeUTF8(NOTIFICATION_STORE_PATH, data, {
      tmpPath: NOTIFICATION_STORE_PATH + ".tmp",
    });
  }

  // Helper function: promise will be resolved once file exists and/or is loaded.
  #ensureLoaded() {
    if (!this.#loaded) {
      return this.load();
    }
    return Promise.resolve();
  }

  receiveMessage(message) {
    lazy.console.debug(`Received message: ${message.name}`);

    // sendAsyncMessage can fail if the child process exits during a
    // notification storage operation, so always wrap it in a try/catch.
    function returnMessage(name, data) {
      try {
        message.target.sendAsyncMessage(name, data);
      } catch (e) {
        lazy.console.debug(`Return message failed, ${name}`);
      }
    }

    switch (message.name) {
      case this.formatMessageType("GetAll"):
        this.queueTask("getall", message.data)
          .then(notifications => {
            returnMessage(this.formatMessageType("GetAll:Return:OK"), {
              requestID: message.data.requestID,
              origin: message.data.origin,
              notifications,
            });
          })
          .catch(error => {
            returnMessage(this.formatMessageType("GetAll:Return:KO"), {
              requestID: message.data.requestID,
              origin: message.data.origin,
              errorMsg: error,
            });
          });
        break;

      case this.formatMessageType("Save"):
        this.queueTask("save", message.data)
          .then(() => {
            returnMessage(this.formatMessageType("Save:Return:OK"), {
              requestID: message.data.requestID,
            });
          })
          .catch(error => {
            returnMessage(this.formatMessageType("Save:Return:KO"), {
              requestID: message.data.requestID,
              errorMsg: error,
            });
          });
        break;

      case this.formatMessageType("Delete"):
        this.queueTask("delete", message.data)
          .then(() => {
            returnMessage(this.formatMessageType("Delete:Return:OK"), {
              requestID: message.data.requestID,
            });
          })
          .catch(error => {
            returnMessage(this.formatMessageType("Delete:Return:KO"), {
              requestID: message.data.requestID,
              errorMsg: error,
            });
          });
        break;

      default:
        lazy.console.debug(`Invalid message name ${message.name}`);
    }
  }

  // We need to make sure any read/write operations are atomic,
  // so use a queue to run each operation sequentially.
  queueTask(operation, data) {
    lazy.console.debug(`Queueing task: ${operation}`);

    var defer = {};

    this.#tasks.push({
      operation,
      data,
      defer,
    });

    var promise = new Promise((resolve, reject) => {
      defer.resolve = resolve;
      defer.reject = reject;
    });

    // Only run immediately if we aren't currently running another task.
    if (!this.#runningTask) {
      lazy.console.debug("Task queue was not running, starting now...");
      this.runNextTask();
      this.#queueDrainedPromise = new Promise(resolve => {
        this.#queueDrainedPromiseResolve = resolve;
      });
    }

    return promise;
  }

  runNextTask() {
    if (this.#tasks.length === 0) {
      lazy.console.debug("No more tasks to run, queue depleted");
      this.#runningTask = null;
      if (this.#queueDrainedPromiseResolve) {
        this.#queueDrainedPromiseResolve();
      } else {
        lazy.console.debug(
          "#queueDrainedPromiseResolve was null somehow, no promise to resolve"
        );
      }
      return;
    }
    this.#runningTask = this.#tasks.shift();

    // Always make sure we are loaded before performing any read/write tasks.
    this.#ensureLoaded()
      .then(() => {
        var task = this.#runningTask;

        switch (task.operation) {
          case "getall":
            return this.taskGetAll(task.data);

          case "save":
            return this.taskSave(task.data);

          case "delete":
            return this.taskDelete(task.data);

          default:
            return Promise.reject(
              new Error(`Found a task with unknown operation ${task.operation}`)
            );
        }
      })
      .then(payload => {
        lazy.console.debug(`Finishing task: ${this.#runningTask.operation}`);
        this.#runningTask.defer.resolve(payload);
      })
      .catch(err => {
        lazy.console.debug(
          `Error while running ${this.#runningTask.operation}: ${err}`
        );
        this.#runningTask.defer.reject(err);
      })
      .then(() => {
        this.runNextTask();
      });
  }

  taskGetAll(data) {
    lazy.console.debug("Task, getting all");
    var origin = data.origin;
    var notifications = [];
    // Grab only the notifications for specified origin.
    if (this.#notifications[origin]) {
      if (data.tag) {
        let n;
        if ((n = this.#byTag[origin][data.tag])) {
          notifications.push(n);
        }
      } else {
        for (var i in this.#notifications[origin]) {
          notifications.push(this.#notifications[origin][i]);
        }
      }
    }
    return Promise.resolve(notifications);
  }

  taskSave(data) {
    lazy.console.debug("Task, saving");
    var origin = data.origin;
    var notification = data.notification;
    if (!this.#notifications[origin]) {
      this.#notifications[origin] = Object.create(null);
      this.#byTag[origin] = Object.create(null);
    }

    // We might have existing notification with this tag,
    // if so we need to remove it before saving the new one.
    if (notification.tag) {
      var oldNotification = this.#byTag[origin][notification.tag];
      if (oldNotification) {
        delete this.#notifications[origin][oldNotification.id];
      }
      this.#byTag[origin][notification.tag] = notification;
    }

    this.#notifications[origin][notification.id] = notification;
    return this.save();
  }

  taskDelete(data) {
    lazy.console.debug("Task, deleting");
    var origin = data.origin;
    var id = data.id;
    if (!this.#notifications[origin]) {
      lazy.console.debug(`No notifications found for origin: ${origin}`);
      return Promise.resolve();
    }

    // Make sure we can find the notification to delete.
    var oldNotification = this.#notifications[origin][id];
    if (!oldNotification) {
      lazy.console.debug(`No notification found with id: ${id}`);
      return Promise.resolve();
    }

    if (oldNotification.tag) {
      delete this.#byTag[origin][oldNotification.tag];
    }
    delete this.#notifications[origin][id];
    return this.save();
  }
}

new NotificationDB();
PK
!<䶜�#modules/NotificationStorage.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  return console.createInstance({
    prefix: "NotificationStorage",
    maxLogLevelPref: "dom.webnotifications.loglevel",
  });
});

const kMessageGetAllOk = "GetAll:Return:OK";
const kMessageGetAllKo = "GetAll:Return:KO";
const kMessageSaveKo = "Save:Return:KO";
const kMessageDeleteKo = "Delete:Return:KO";

export class NotificationStorage {
  #requests = {};
  #requestCount = 0;

  constructor() {
    Services.obs.addObserver(this, "xpcom-shutdown");

    // Register for message listeners.
    this.registerListeners();
  }

  storageQualifier() {
    return "Notification";
  }

  prefixStorageQualifier(message) {
    return `${this.storageQualifier()}:${message}`;
  }

  formatMessageType(message) {
    return this.prefixStorageQualifier(message);
  }

  supportedMessages() {
    return [
      this.formatMessageType(kMessageGetAllOk),
      this.formatMessageType(kMessageGetAllKo),
      this.formatMessageType(kMessageSaveKo),
      this.formatMessageType(kMessageDeleteKo),
    ];
  }

  registerListeners() {
    for (let message of this.supportedMessages()) {
      Services.cpmm.addMessageListener(message, this);
    }
  }

  unregisterListeners() {
    for (let message of this.supportedMessages()) {
      Services.cpmm.removeMessageListener(message, this);
    }
  }

  observe(aSubject, aTopic) {
    lazy.console.debug(`Topic: ${aTopic}`);
    if (aTopic === "xpcom-shutdown") {
      Services.obs.removeObserver(this, "xpcom-shutdown");
      this.unregisterListeners();
    }
  }

  put(
    origin,
    id,
    title,
    dir,
    lang,
    body,
    tag,
    icon,
    alertName,
    data,
    behavior,
    serviceWorkerRegistrationScope
  ) {
    lazy.console.debug(`PUT: ${origin} ${id}: ${title}`);
    var notification = {
      id,
      title,
      dir,
      lang,
      body,
      tag,
      icon,
      alertName,
      timestamp: new Date().getTime(),
      origin,
      data,
      mozbehavior: behavior,
      serviceWorkerRegistrationScope,
    };

    Services.cpmm.sendAsyncMessage(this.formatMessageType("Save"), {
      origin,
      notification,
    });
  }

  get(origin, tag, callback) {
    lazy.console.debug(`GET: ${origin} ${tag}`);
    this.#fetchFromDB(origin, tag, callback);
  }

  delete(origin, id) {
    lazy.console.debug(`DELETE: ${id}`);
    Services.cpmm.sendAsyncMessage(this.formatMessageType("Delete"), {
      origin,
      id,
    });
  }

  receiveMessage(message) {
    var request = this.#requests[message.data.requestID];

    switch (message.name) {
      case this.formatMessageType(kMessageGetAllOk):
        delete this.#requests[message.data.requestID];
        this.#returnNotifications(message.data.notifications, request.callback);
        break;

      case this.formatMessageType(kMessageGetAllKo):
        delete this.#requests[message.data.requestID];
        try {
          request.callback.done();
        } catch (e) {
          lazy.console.debug(`Error calling callback done: ${e}`);
        }
        break;
      case this.formatMessageType(kMessageSaveKo):
      case this.formatMessageType(kMessageDeleteKo):
        lazy.console.debug(
          `Error received when treating: '${message.name}': ${message.data.errorMsg}`
        );
        break;

      default:
        lazy.console.debug(`Unrecognized message: ${message.name}`);
        break;
    }
  }

  #getUniqueRequestID() {
    // This assumes the count will never go above MAX_SAFE_INTEGER, as
    // notifications are not supposed to happen that frequently.
    this.#requestCount += 1;
    return this.#requestCount;
  }

  #fetchFromDB(origin, tag, callback) {
    var request = {
      origin,
      tag,
      callback,
    };
    var requestID = this.#getUniqueRequestID();
    this.#requests[requestID] = request;
    Services.cpmm.sendAsyncMessage(this.formatMessageType("GetAll"), {
      origin,
      tag,
      requestID,
    });
  }

  #returnNotifications(notifications, callback) {
    // Pass each notification back separately.
    // The callback is called asynchronously to match the behaviour when
    // fetching from the database.
    notifications.forEach(function (notification) {
      try {
        Services.tm.dispatchToMainThread(
          callback.handle.bind(
            callback,
            notification.id,
            notification.title,
            notification.dir,
            notification.lang,
            notification.body,
            notification.tag,
            notification.icon,
            notification.data,
            notification.mozbehavior,
            notification.serviceWorkerRegistrationScope
          )
        );
      } catch (e) {
        lazy.console.debug(`Error calling callback handle: ${e}`);
      }
    });
    try {
      Services.tm.dispatchToMainThread(callback.done);
    } catch (e) {
      lazy.console.debug(`Error calling callback done: ${e}`);
    }
  }

  QueryInterface = ChromeUtils.generateQI(["nsINotificationStorage"]);
}

export class MemoryNotificationStorage extends NotificationStorage {
  storageQualifier() {
    return "MemoryNotification";
  }
}
PK
!<�d�u�7�7modules/OSKeyStore.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Helpers for using OS Key Store.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
});
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "nativeOSKeyStore",
  "@mozilla.org/security/oskeystore;1",
  Ci.nsIOSKeyStore
);
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "osReauthenticator",
  "@mozilla.org/security/osreauthenticator;1",
  Ci.nsIOSReauthenticator
);

// Skip reauth during tests, only works in non-official builds.
const TEST_ONLY_REAUTH = "toolkit.osKeyStore.unofficialBuildOnlyLogin";

export var OSKeyStore = {
  /**
   * On macOS this becomes part of the name label visible on Keychain Acesss as
   * "Firefox Encrypted Storage" (where "Firefox" is the MOZ_APP_BASENAME).
   * Unfortunately, since this is the index into the keystore, we can't
   * localize it without some really unfortunate side effects, like users
   * losing access to stored information when they change their locale.
   * This is a limitation of the interface exposed by macOS. Notably, both
   * Chrome and Safari suffer the same shortcoming.
   */
  STORE_LABEL: AppConstants.MOZ_APP_BASENAME + " Encrypted Storage",

  /**
   * Consider the module is initialized as locked. OS might unlock without a
   * prompt.
   * @type {Boolean}
   */
  _isLocked: true,

  _pendingUnlockPromise: null,

  /**
   * @returns {boolean} True if logged in (i.e. decrypt(reauth = false) will
   *                    not retrigger a dialog) and false if not.
   *                    User might log out elsewhere in the OS, so even if this
   *                    is true a prompt might still pop up.
   */
  get isLoggedIn() {
    return !this._isLocked;
  },

  /**
   * @returns {boolean} True if there is another login dialog existing and false
   *                    otherwise.
   */
  get isUIBusy() {
    return !!this._pendingUnlockPromise;
  },

  canReauth() {
    // We have no support on linux (bug 1527745)
    if (AppConstants.platform == "win" || AppConstants.platform == "macosx") {
      lazy.log.debug(
        "canReauth, returning true, this._testReauth:",
        this._testReauth
      );
      return true;
    }
    lazy.log.debug("canReauth, returning false");
    return false;
  },

  /**
   * If the test pref exists, this method will dispatch a observer message and
   * resolves to simulate successful reauth, or rejects to simulate failed reauth.
   *
   * @returns {Promise<undefined>} Resolves when sucessful login, rejects when
   *                               login fails.
   */
  async _reauthInTests() {
    // Skip this reauth because there is no way to mock the
    // native dialog in the testing environment, for now.
    lazy.log.debug("_reauthInTests: _testReauth: ", this._testReauth);
    switch (this._testReauth) {
      case "pass":
        Services.obs.notifyObservers(
          null,
          "oskeystore-testonly-reauth",
          "pass"
        );
        return { authenticated: true, auth_details: "success" };
      case "cancel":
        Services.obs.notifyObservers(
          null,
          "oskeystore-testonly-reauth",
          "cancel"
        );
        throw new Components.Exception(
          "Simulating user cancelling login dialog",
          Cr.NS_ERROR_FAILURE
        );
      default:
        throw new Components.Exception(
          "Unknown test pref value",
          Cr.NS_ERROR_FAILURE
        );
    }
  },

  /**
   * Ensure the store in use is logged in. It will display the OS
   * login prompt or do nothing if it's logged in already. If an existing login
   * prompt is already prompted, the result from it will be used instead.
   *
   * Note: This method must set _pendingUnlockPromise before returning the
   * promise (i.e. the first |await|), otherwise we'll risk re-entry.
   * This is why there aren't an |await| in the method. The method is marked as
   * |async| to communicate that it's async.
   *
   * @param   {boolean|string} reauth If set to a string, prompt the reauth login dialog,
   *                                  showing the string on the native OS login dialog.
   *                                  Otherwise `false` will prevent showing the prompt.
   * @param   {string} dialogCaption  The string will be shown on the native OS
   *                                  login dialog as the dialog caption (usually Product Name).
   * @param   {Window?} parentWindow  The window of the caller, used to center the
   *                                  OS prompt in the middle of the application window.
   * @param   {boolean} generateKeyIfNotAvailable Makes key generation optional
   *                                  because it will currently cause more
   *                                  problems for us down the road on macOS since the application
   *                                  that creates the Keychain item is the only one that gets
   *                                  access to the key in the future and right now that key isn't
   *                                  specific to the channel or profile. This means if a user uses
   *                                  both DevEdition and Release on the same OS account (not
   *                                  unreasonable for a webdev.) then when you want to simply
   *                                  re-auth the user for viewing passwords you may also get a
   *                                  KeyChain prompt to allow the app to access the stored key even
   *                                  though that's not at all relevant for the re-auth. We skip the
   *                                  code here so that we can postpone deciding on how we want to
   *                                  handle this problem (multiple channels) until we actually use
   *                                  the key storage. If we start creating keys on macOS by running
   *                                  this code we'll potentially have to do extra work to cleanup
   *                                  the mess later.
   * @returns {Promise<Object>}       Object with the following properties:
   *                                    authenticated: {boolean} Set to true if the user successfully authenticated.
   *                                    auth_details: {String?} Details of the authentication result.
   */
  async ensureLoggedIn(
    reauth = false,
    dialogCaption = "",
    parentWindow = null,
    generateKeyIfNotAvailable = true
  ) {
    if (
      (typeof reauth != "boolean" && typeof reauth != "string") ||
      reauth === true ||
      reauth === ""
    ) {
      throw new Error(
        "reauth is required to either be `false` or a non-empty string"
      );
    }

    if (this._pendingUnlockPromise) {
      lazy.log.debug("ensureLoggedIn: Has a pending unlock operation");
      return this._pendingUnlockPromise;
    }
    lazy.log.debug(
      "ensureLoggedIn: Creating new pending unlock promise. reauth: ",
      reauth
    );

    let unlockPromise;
    if (typeof reauth == "string") {
      // Only allow for local builds
      if (
        lazy.UpdateUtils.getUpdateChannel(false) == "default" &&
        this._testReauth
      ) {
        unlockPromise = this._reauthInTests();
      } else if (this.canReauth()) {
        // On Windows, this promise rejects when the user cancels login dialog, see bug 1502121.
        // On macOS this resolves to false, so we would need to check it.
        unlockPromise = lazy.osReauthenticator
          .asyncReauthenticateUser(reauth, dialogCaption, parentWindow)
          .then(reauthResult => {
            let auth_details_extra = {};
            if (reauthResult.length > 3) {
              auth_details_extra.auto_admin = "" + !!reauthResult[2];
              auth_details_extra.require_signon = "" + !!reauthResult[3];
            }
            if (!reauthResult[0]) {
              throw new Components.Exception(
                "User canceled OS reauth entry",
                Cr.NS_ERROR_FAILURE,
                null,
                auth_details_extra
              );
            }
            let result = {
              authenticated: true,
              auth_details: "success",
              auth_details_extra,
            };
            if (reauthResult.length > 1 && reauthResult[1]) {
              result.auth_details += "_no_password";
            }
            return result;
          });
      } else {
        lazy.log.debug(
          "ensureLoggedIn: Skipping reauth on unsupported platforms"
        );
        unlockPromise = Promise.resolve({
          authenticated: true,
          auth_details: "success_unsupported_platform",
        });
      }
    } else {
      unlockPromise = Promise.resolve({ authenticated: true });
    }

    if (generateKeyIfNotAvailable) {
      unlockPromise = unlockPromise.then(async reauthResult => {
        if (
          !(await lazy.nativeOSKeyStore.asyncSecretAvailable(this.STORE_LABEL))
        ) {
          lazy.log.debug(
            "ensureLoggedIn: Secret unavailable, attempt to generate new secret."
          );
          let recoveryPhrase = await lazy.nativeOSKeyStore.asyncGenerateSecret(
            this.STORE_LABEL
          );
          // TODO We should somehow have a dialog to ask the user to write this down,
          // and another dialog somewhere for the user to restore the secret with it.
          // (Intentionally not printing it out in the console)
          lazy.log.debug(
            "ensureLoggedIn: Secret generated. Recovery phrase length: " +
              recoveryPhrase.length
          );
        }
        return reauthResult;
      });
    }

    unlockPromise = unlockPromise.then(
      reauthResult => {
        lazy.log.debug("ensureLoggedIn: Logged in");
        this._pendingUnlockPromise = null;
        this._isLocked = false;

        return reauthResult;
      },
      err => {
        lazy.log.debug("ensureLoggedIn: Not logged in", err);
        this._pendingUnlockPromise = null;
        this._isLocked = true;

        return {
          authenticated: false,
          auth_details: "fail",
          auth_details_extra: err.data?.QueryInterface(Ci.nsISupports)
            .wrappedJSObject,
        };
      }
    );

    this._pendingUnlockPromise = unlockPromise;

    return this._pendingUnlockPromise;
  },

  /**
   * Decrypts cipherText.
   *
   * Note: In the event of an rejection, check the result property of the Exception
   *       object. Handles NS_ERROR_ABORT as user has cancelled the action (e.g.,
   *       don't show that dialog), apart from other errors (e.g., gracefully
   *       recover from that and still shows the dialog.)
   *
   * @param   {string}         cipherText Encrypted string including the algorithm details.
   * @param   {boolean|string} reauth     If set to a string, prompt the reauth login dialog.
   *                                      The string may be shown on the native OS
   *                                      login dialog. Empty strings and `true` are disallowed.
   * @returns {Promise<string>}           resolves to the decrypted string, or rejects otherwise.
   */
  async decrypt(cipherText, reauth = false) {
    if (!(await this.ensureLoggedIn(reauth)).authenticated) {
      throw Components.Exception(
        "User canceled OS unlock entry",
        Cr.NS_ERROR_ABORT
      );
    }
    let bytes = await lazy.nativeOSKeyStore.asyncDecryptBytes(
      this.STORE_LABEL,
      cipherText
    );
    return String.fromCharCode.apply(String, bytes);
  },

  /**
   * Encrypts a string and returns cipher text containing algorithm information used for decryption.
   *
   * @param   {string} plainText Original string without encryption.
   * @returns {Promise<string>} resolves to the encrypted string (with algorithm), otherwise rejects.
   */
  async encrypt(plainText) {
    if (!(await this.ensureLoggedIn()).authenticated) {
      throw Components.Exception(
        "User canceled OS unlock entry",
        Cr.NS_ERROR_ABORT
      );
    }

    // Convert plain text into a UTF-8 binary string
    plainText = unescape(encodeURIComponent(plainText));

    // Convert it to an array
    let textArr = [];
    for (let char of plainText) {
      textArr.push(char.charCodeAt(0));
    }

    let rawEncryptedText = await lazy.nativeOSKeyStore.asyncEncryptBytes(
      this.STORE_LABEL,
      textArr
    );

    // Mark the output with a version number.
    return rawEncryptedText;
  },

  /**
   * Exports the recovery phrase within the native OSKeyStore if authenticated
   * as a byte string.
   *
   * @returns {Promise<string>}
   */
  async exportRecoveryPhrase() {
    if (!(await this.ensureLoggedIn()).authenticated) {
      throw Components.Exception(
        "User canceled OS unlock entry",
        Cr.NS_ERROR_ABORT
      );
    }

    return await lazy.nativeOSKeyStore.asyncGetRecoveryPhrase(this.STORE_LABEL);
  },

  /**
   * Resolve when the login dialogs are closed, immediately if none are open.
   *
   * An existing MP dialog will be focused and will request attention.
   *
   * @returns {Promise<boolean>}
   *          Resolves with whether the user is logged in to MP.
   */
  async waitForExistingDialog() {
    if (this.isUIBusy) {
      return this._pendingUnlockPromise;
    }
    return this.isLoggedIn;
  },

  /**
   * Remove the store. For tests.
   */
  async cleanup() {
    return lazy.nativeOSKeyStore.asyncDeleteSecret(this.STORE_LABEL);
  },
};

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  let { ConsoleAPI } = ChromeUtils.importESModule(
    "resource://gre/modules/Console.sys.mjs"
  );
  return new ConsoleAPI({
    maxLogLevelPref: "toolkit.osKeyStore.loglevel",
    prefix: "OSKeyStore",
  });
});

XPCOMUtils.defineLazyPreferenceGetter(
  OSKeyStore,
  "_testReauth",
  TEST_ONLY_REAUTH,
  ""
);
PK
!<��ԕ�modules/ObjectUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Portions of this file are originally from narwhal.js (http://narwhaljs.org)
// Copyright (c) 2009 Thomas Robinson <280north.com>
// MIT license: http://opensource.org/licenses/MIT

// Used only to cause test failures.

var pSlice = Array.prototype.slice;

export var ObjectUtils = {
  /**
   * This tests objects & values for deep equality.
   *
   * We check using the most exact approximation of equality between two objects
   * to keep the chance of false positives to a minimum.
   * `JSON.stringify` is not designed to be used for this purpose; objects may
   * have ambiguous `toJSON()` implementations that would influence the test.
   *
   * @param a (mixed) Object or value to be compared.
   * @param b (mixed) Object or value to be compared.
   * @return Boolean Whether the objects are deep equal.
   */
  deepEqual(a, b) {
    return _deepEqual(a, b);
  },

  /**
   * A thin wrapper on an object, designed to prevent client code from
   * accessing non-existent properties because of typos.
   *
   * // Without `strict`
   * let foo = { myProperty: 1 };
   * foo.MyProperty; // undefined
   *
   * // With `strict`
   * let strictFoo = ObjectUtils.strict(foo);
   * strictFoo.myProperty; // 1
   * strictFoo.MyProperty; // TypeError: No such property "MyProperty"
   *
   * Note that `strict` has no effect in non-DEBUG mode.
   */
  strict(obj) {
    return _strict(obj);
  },

  /**
   * Returns `true` if `obj` is an array without elements, an object without
   * enumerable properties, or a falsy primitive; `false` otherwise.
   */
  isEmpty(obj) {
    if (!obj) {
      return true;
    }
    if (typeof obj != "object") {
      return false;
    }
    if (Array.isArray(obj)) {
      return !obj.length;
    }
    for (let key in obj) {
      return false;
    }
    return true;
  },
};

// ... Start of previously MIT-licensed code.
// This deepEqual implementation is originally from narwhal.js (http://narwhaljs.org)
// Copyright (c) 2009 Thomas Robinson <280north.com>
// MIT license: http://opensource.org/licenses/MIT

function _deepEqual(a, b) {
  // The numbering below refers to sections in the CommonJS spec.

  // 7.1 All identical values are equivalent, as determined by ===.
  if (a === b) {
    return true;
    // 7.2 If the b value is a Date object, the a value is
    // equivalent if it is also a Date object that refers to the same time.
  }
  let aIsDate = instanceOf(a, "Date");
  let bIsDate = instanceOf(b, "Date");
  if (aIsDate || bIsDate) {
    if (!aIsDate || !bIsDate) {
      return false;
    }
    if (isNaN(a.getTime()) && isNaN(b.getTime())) {
      return true;
    }
    return a.getTime() === b.getTime();
    // 7.3 If the b value is a RegExp object, the a value is
    // equivalent if it is also a RegExp object with the same source and
    // properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
  }
  let aIsRegExp = instanceOf(a, "RegExp");
  let bIsRegExp = instanceOf(b, "RegExp");
  if (aIsRegExp || bIsRegExp) {
    return (
      aIsRegExp &&
      bIsRegExp &&
      a.source === b.source &&
      a.global === b.global &&
      a.multiline === b.multiline &&
      a.lastIndex === b.lastIndex &&
      a.ignoreCase === b.ignoreCase
    );
    // 7.4 Other pairs that do not both pass typeof value == "object",
    // equivalence is determined by ==.
  }
  if (typeof a != "object" || typeof b != "object") {
    return a == b;
  }
  // 7.5 For all other Object pairs, including Array objects, equivalence is
  // determined by having the same number of owned properties (as verified
  // with Object.prototype.hasOwnProperty.call), the same set of keys
  // (although not necessarily the same order), equivalent values for every
  // corresponding key, and an identical 'prototype' property. Note: this
  // accounts for both named and indexed properties on Arrays.
  return objEquiv(a, b);
}

function instanceOf(object, type) {
  return Object.prototype.toString.call(object) == "[object " + type + "]";
}

function isUndefinedOrNull(value) {
  return value === null || value === undefined;
}

function isArguments(object) {
  return instanceOf(object, "Arguments");
}

function objEquiv(a, b) {
  if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) {
    return false;
  }
  // An identical 'prototype' property.
  if ((a.prototype || undefined) != (b.prototype || undefined)) {
    return false;
  }

  // Check for ArrayBuffer equality
  if (instanceOf(a, "ArrayBuffer") && instanceOf(b, "ArrayBuffer")) {
    if (a.byteLength !== b.byteLength) {
      return false;
    }
    const viewA = new Uint8Array(a);
    const viewB = new Uint8Array(b);
    for (let i = 0; i < viewA.length; i++) {
      if (viewA[i] !== viewB[i]) {
        return false;
      }
    }
    return true;
  }

  // Object.keys may be broken through screwy arguments passing. Converting to
  // an array solves the problem.
  if (isArguments(a)) {
    if (!isArguments(b)) {
      return false;
    }
    a = pSlice.call(a);
    b = pSlice.call(b);
    return _deepEqual(a, b);
  }
  let ka, kb;
  try {
    ka = Object.keys(a);
    kb = Object.keys(b);
  } catch (e) {
    // Happens when one is a string literal and the other isn't
    return false;
  }
  // Having the same number of owned properties (keys incorporates
  // hasOwnProperty)
  if (ka.length != kb.length) {
    return false;
  }
  // The same set of keys (although not necessarily the same order),
  ka.sort();
  kb.sort();
  // Equivalent values for every corresponding key, and possibly expensive deep
  // test
  for (let key of ka) {
    if (!_deepEqual(a[key], b[key])) {
      return false;
    }
  }
  return true;
}

// ... End of previously MIT-licensed code.

function _strict(obj) {
  if (typeof obj != "object") {
    throw new TypeError("Expected an object");
  }

  return new Proxy(obj, {
    get(target, name) {
      if (name in obj) {
        return obj[name];
      }

      let error = new TypeError(`No such property: "${name}"`);
      Promise.reject(error); // Cause an xpcshell/mochitest failure.
      throw error;
    },
  });
}
PK
!<Q.�mCCmodules/ObliviousHTTP.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  HPKEConfigManager: "resource://gre/modules/HPKEConfigManager.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "decoder", () => new TextDecoder());
XPCOMUtils.defineLazyServiceGetters(lazy, {
  ohttpService: [
    "@mozilla.org/network/oblivious-http-service;1",
    Ci.nsIObliviousHttpService,
  ],
});

const BinaryInputStream = Components.Constructor(
  "@mozilla.org/binaryinputstream;1",
  "nsIBinaryInputStream",
  "setInputStream"
);

const StringInputStream = Components.Constructor(
  "@mozilla.org/io/string-input-stream;1",
  "nsIStringInputStream",
  "setData"
);

const ArrayBufferInputStream = Components.Constructor(
  "@mozilla.org/io/arraybuffer-input-stream;1",
  "nsIArrayBufferInputStream",
  "setData"
);

function readFromStream(stream, count) {
  let binaryStream = new BinaryInputStream(stream);
  let arrayBuffer = new ArrayBuffer(count);
  while (count > 0) {
    let actuallyRead = binaryStream.readArrayBuffer(count, arrayBuffer);
    if (!actuallyRead) {
      throw new Error("Nothing read from input stream!");
    }
    count -= actuallyRead;
  }
  return arrayBuffer;
}

export class ObliviousHTTP {
  /**
   * Get a cached, or fetch a copy of, an OHTTP config from a given URL.
   *
   * @param {string} gatewayConfigURL
   *   The URL for the config that needs to be fetched.
   *   The URL should be complete (i.e. include the full path to the config).
   * @returns {Uint8Array}
   *   The config bytes.
   */
  static async getOHTTPConfig(gatewayConfigURL) {
    return lazy.HPKEConfigManager.get(gatewayConfigURL);
  }

  /**
   * Make a request over OHTTP.
   *
   * @param {string} obliviousHTTPRelay
   *   The URL of the OHTTP relay to use.
   * @param {Uint8Array} config
   *   A byte array representing the OHTTP config.
   * @param {string} requestURL
   *   The URL of the request we want to make over the relay.
   * @param {object} options
   * @param {string} options.method
   *   The HTTP method to use for the inner request. Only GET, POST, and PUT are
   *   supported right now.
   * @param {string|ArrayBuffer} options.body
   *   The body content to send over the request.
   * @param {object} options.headers
   *   The request headers to set. Each property of the object represents
   *   a header, with the key the header name and the value the header value.
   * @param {AbortSignal} options.signal
   *   If the consumer passes an AbortSignal object, aborting the signal
   *   will abort the request.
   * @param {Function} options.abortCallback
   *   Called if the abort signal is triggered before the request completes
   *   fully.
   *
   * @returns {object}
   *   Returns an object with properties mimicking that of a normal fetch():
   *   .ok = boolean indicating whether the request was successful.
   *   .status = integer representation of the HTTP status code
   *   .headers = object representing the response headers.
   *   .json() = method that returns the parsed JSON response body.
   */
  static async ohttpRequest(
    obliviousHTTPRelay,
    config,
    requestURL,
    { method = "GET", body, headers, signal, abortCallback } = {}
  ) {
    let relayURI = Services.io.newURI(obliviousHTTPRelay);
    let requestURI = Services.io.newURI(requestURL);
    let obliviousHttpChannel = lazy.ohttpService
      .newChannel(relayURI, requestURI, config)
      .QueryInterface(Ci.nsIHttpChannel);

    if (method == "POST" || method == "PUT") {
      let uploadChannel = obliviousHttpChannel.QueryInterface(
        Ci.nsIUploadChannel2
      );
      let bodyStream;
      if (typeof body === "string") {
        bodyStream = new StringInputStream(body, body.length);
      } else if (body instanceof ArrayBuffer) {
        bodyStream = new ArrayBufferInputStream(body, 0, body.byteLength);
      } else {
        throw new Error("ohttpRequest got unexpected body payload type.");
      }
      uploadChannel.explicitSetUploadStream(
        bodyStream,
        null,
        -1,
        method,
        false
      );
    } else if (method != "GET") {
      throw new Error(`Unsupported HTTP verb ${method}`);
    }

    for (let headerName of Object.keys(headers)) {
      obliviousHttpChannel.setRequestHeader(
        headerName,
        headers[headerName],
        false
      );
    }
    let abortHandler = () => {
      abortCallback?.();
      obliviousHttpChannel.cancel(Cr.NS_BINDING_ABORTED);
    };
    signal.addEventListener("abort", abortHandler);
    return new Promise((resolve, reject) => {
      let listener = {
        _buffer: [],
        _headers: null,
        QueryInterface: ChromeUtils.generateQI([
          "nsIStreamListener",
          "nsIRequestObserver",
        ]),
        onStartRequest(request) {
          this._headers = new Headers();
          try {
            request
              .QueryInterface(Ci.nsIHttpChannel)
              .visitResponseHeaders((header, value) => {
                this._headers.append(header, value);
              });
          } catch (error) {
            this._headers = null;
          }
        },
        onDataAvailable(request, stream, offset, count) {
          this._buffer.push(readFromStream(stream, count));
        },
        onStopRequest(request, requestStatus) {
          signal.removeEventListener("abort", abortHandler);
          let result = this._buffer;
          try {
            let ohttpStatus = request.QueryInterface(Ci.nsIObliviousHttpChannel)
              .relayChannel.responseStatus;
            if (ohttpStatus == 200) {
              let httpStatus = request.QueryInterface(
                Ci.nsIHttpChannel
              ).responseStatus;
              resolve({
                ok: requestStatus == Cr.NS_OK && httpStatus == 200,
                status: httpStatus,
                headers: this._headers,
                json() {
                  let decodedBuffer = result.reduce((accumulator, currVal) => {
                    return accumulator + lazy.decoder.decode(currVal);
                  }, "");
                  return JSON.parse(decodedBuffer);
                },
                blob() {
                  return new Blob(result, { type: "image/jpeg" });
                },
              });
            } else {
              resolve({
                ok: false,
                status: ohttpStatus,
                json() {
                  return null;
                },
                blob() {
                  return null;
                },
              });
            }
          } catch (error) {
            reject(error);
          }
        },
      };
      obliviousHttpChannel.asyncOpen(listener);
    });
  }
}
PK
!<H��	T.T. modules/OpenSearchLoader.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * OpenSearchLoader is used for loading OpenSearch definitions from content.
 */

/* eslint no-shadow: error, mozilla/no-aArgs: error */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logConsole", () => {
  return console.createInstance({
    prefix: "OpenSearchLoader",
    maxLogLevel: lazy.SearchUtils.loggingEnabled ? "Debug" : "Warn",
  });
});

// The namespaces from the specification at
// https://github.com/dewitt/opensearch/blob/master/opensearch-1-1-draft-6.md#namespace
const OPENSEARCH_NS_10 = "http://a9.com/-/spec/opensearch/1.0/";
const OPENSEARCH_NS_11 = "http://a9.com/-/spec/opensearch/1.1/";

// Although the specification at gives the namespace names defined above, many
// existing OpenSearch engines are using the following versions. We therefore
// allow any one of these.
const OPENSEARCH_NAMESPACES = [
  OPENSEARCH_NS_11,
  OPENSEARCH_NS_10,
  "http://a9.com/-/spec/opensearchdescription/1.1/",
  "http://a9.com/-/spec/opensearchdescription/1.0/",
];

// The name of the element defining the OpenSearch definition.
const OPENSEARCH_LOCALNAME = "OpenSearchDescription";

// These were OpenSearch definitions for engines used internally by Mozilla.
// It may be possible to deprecate/remove these in future.
const MOZSEARCH_NS_10 = "http://www.mozilla.org/2006/browser/search/";
const MOZSEARCH_LOCALNAME = "SearchPlugin";

/**
 * @typedef {object} OpenSearchProperties
 * @property {string} name
 *   The display name of the engine.
 * @property {nsIURI} installURL
 *   The URL that the engine was initially loaded from.
 * @property {string} [description]
 *   The description of the engine.
 * @property {string} [queryCharset]
 *   The character set to use for encoding query values.
 * @property {string} [UpdateUrl]
 *   Non-standard. The update URL for the engine.
 * @property {number} [UpdateInterval]
 *   Non-standard. The update interval for the engine.
 * @property {string} [IconUpdateUrl]
 *   Non-standard. The update URL for the icon.
 * @property {OpenSearchURL[]} urls
 *   An array of URLs associated with the engine.
 * @property {OpenSearchImage[]} images
 *   An array of images assocaiated with the engine.
 */

/**
 * @typedef {object} OpenSearchURL
 * @property {string} type
 *   The OpenSearch based type of the URL see SearchUtils.URL_TYPE.
 * @property {string} method
 *   The method of submission for the URL: GET or POST.
 * @property {string} template
 *   The template for the URL.
 * @property {object[]} params
 *   An array of additional properties of name/value pairs. These are not part
 *   of the OpenSearch specification, but were used in Firefox prior to Firefox 78.
 * @property {string[]} rels
 *   An array of strings that define the relationship of this URL.
 *
 * @see SearchUtils.URL_TYPE
 * @see https://github.com/dewitt/opensearch/blob/master/opensearch-1-1-draft-6.md#url-rel-values
 */

/**
 * @typedef {object} OpenSearchImage
 * @property {string} url
 *   The source URL of the image.
 * @property {boolean} isPrefered
 *   If this image is of the preferred 16x16 size.
 * @property {width} width
 *   The reported width of the image.
 * @property {height} height
 *   The reported height of the image.
 */

/**
 * Retrieves the engine data from a URI and returns it.
 *
 * @param {nsIURI} sourceURI
 *   The uri from which to load the OpenSearch engine data.
 * @param {string} [lastModified]
 *   The UTC date when the engine was last updated, if any.
 * @returns {OpenSearchProperties}
 *   The properties of the loaded OpenSearch engine.
 */
export async function loadAndParseOpenSearchEngine(sourceURI, lastModified) {
  if (!sourceURI) {
    throw Components.Exception(
      sourceURI,
      "Must have URI when calling _install!",
      Cr.NS_ERROR_UNEXPECTED
    );
  }
  if (!/^https?$/i.test(sourceURI.scheme)) {
    throw Components.Exception(
      "Invalid URI passed to SearchEngine constructor",
      Cr.NS_ERROR_INVALID_ARG
    );
  }

  lazy.logConsole.debug("Downloading OpenSearch engine from:", sourceURI.spec);

  let xmlData = await loadEngineXML(sourceURI, lastModified);
  let xmlDocument = await parseXML(xmlData);

  lazy.logConsole.debug("Loading search plugin");

  let engineData;
  try {
    engineData = processXMLDocument(xmlDocument);
  } catch (ex) {
    lazy.logConsole.error("parseData: Failed to init engine!", ex);

    if (ex.result == Cr.NS_ERROR_FILE_CORRUPTED) {
      throw Components.Exception(
        "",
        Ci.nsISearchService.ERROR_ENGINE_CORRUPTED
      );
    }
    throw Components.Exception("", Ci.nsISearchService.ERROR_DOWNLOAD_FAILURE);
  }

  engineData.installURL = sourceURI;
  return engineData;
}

/**
 * Loads the engine XML from the given URI.
 *
 * @param {nsIURI} sourceURI
 *   The uri from which to load the OpenSearch engine data.
 * @param {string} [lastModified]
 *   The UTC date when the engine was last updated, if any.
 * @returns {Promise}
 *   A promise that is resolved with the data if the engine is successfully loaded
 *   and rejected otherwise.
 */
function loadEngineXML(sourceURI, lastModified) {
  var chan = lazy.SearchUtils.makeChannel(
    sourceURI,
    // OpenSearchEngine is loading a definition file for a search engine,
    // TYPE_DOCUMENT captures that load best.
    Ci.nsIContentPolicy.TYPE_DOCUMENT
  );

  // we collect https telemetry for all top-level (document) loads.
  chan.loadInfo.httpsUpgradeTelemetry = sourceURI.schemeIs("https")
    ? Ci.nsILoadInfo.ALREADY_HTTPS
    : Ci.nsILoadInfo.NO_UPGRADE;

  if (lastModified && chan instanceof Ci.nsIHttpChannel) {
    chan.setRequestHeader("If-Modified-Since", lastModified, false);
  }
  let loadPromise = Promise.withResolvers();

  let loadHandler = data => {
    if (!data) {
      loadPromise.reject(
        Components.Exception("", Ci.nsISearchService.ERROR_DOWNLOAD_FAILURE)
      );
      return;
    }
    loadPromise.resolve(data);
  };

  var listener = new lazy.SearchUtils.LoadListener(
    chan,
    /(^text\/|xml$)/,
    loadHandler
  );
  chan.notificationCallbacks = listener;
  chan.asyncOpen(listener);

  return loadPromise.promise;
}

/**
 * Parses an engines XML data into a document element.
 *
 * @param {number[]} xmlData
 *   The loaded search engine data.
 * @returns {Element}
 *   A document element containing the parsed data.
 */
function parseXML(xmlData) {
  var parser = new DOMParser();
  var doc = parser.parseFromBuffer(xmlData, "text/xml");

  if (!doc?.documentElement) {
    throw Components.Exception(
      "Could not parse file",
      Ci.nsISearchService.ERROR_ENGINE_CORRUPTED
    );
  }

  if (!hasExpectedNamspeace(doc.documentElement)) {
    throw Components.Exception(
      "Not a valid OpenSearch xml file",
      Ci.nsISearchService.ERROR_ENGINE_CORRUPTED
    );
  }
  return doc.documentElement;
}

/**
 * Extract search engine information from the given document into a form that
 * can be passed to an OpenSearchEngine.
 *
 * @param {Element} xmlDocument
 *   The document to examine.
 * @returns {OpenSearchProperties}
 *   The properties of the OpenSearch engine.
 */
function processXMLDocument(xmlDocument) {
  let result = { urls: [], images: [] };

  for (let i = 0; i < xmlDocument.children.length; ++i) {
    var child = xmlDocument.children[i];
    switch (child.localName) {
      case "ShortName":
        result.name = child.textContent;
        break;
      case "Description":
        result.description = child.textContent;
        break;
      case "Url":
        try {
          result.urls.push(parseURL(child));
        } catch (ex) {
          // Parsing of the element failed, just skip it.
          lazy.logConsole.error("Failed to parse URL child:", ex);
        }
        break;
      case "Image": {
        let imageData = parseImage(child);
        if (imageData) {
          result.images.push(imageData);
        }
        break;
      }
      case "InputEncoding":
        // If this is not specified we fallback to the SearchEngine constructor
        // which currently uses SearchUtils.DEFAULT_QUERY_CHARSET which is
        // UTF-8 - the same as for OpenSearch.
        result.queryCharset = child.textContent;
        break;

      // Non-OpenSearch elements
      case "UpdateUrl":
        result.updateURL = child.textContent;
        break;
      case "UpdateInterval":
        result.updateInterval = parseInt(child.textContent);
        break;
      case "IconUpdateUrl":
        result.iconUpdateURL = child.textContent;
        break;
    }
  }
  if (!result.name || !result.urls.length) {
    throw Components.Exception(
      "_parse: No name, or missing URL!",
      Cr.NS_ERROR_FAILURE
    );
  }
  if (!result.urls.find(url => url.type == lazy.SearchUtils.URL_TYPE.SEARCH)) {
    throw Components.Exception(
      "_parse: No text/html result type!",
      Cr.NS_ERROR_FAILURE
    );
  }
  return result;
}

/**
 * Extracts data from an OpenSearch URL element and creates an object which can
 * be used to create an OpenSearchEngine's URL.
 *
 * @param {Element} element
 *   The OpenSearch URL element.
 * @returns {OpenSearchURL}
 *   The extracted URL data.
 * @throws NS_ERROR_FAILURE if a URL object could not be created.
 *
 * @see https://github.com/dewitt/opensearch/blob/master/opensearch-1-1-draft-6.md#the-url-element
 */
function parseURL(element) {
  var type = element.getAttribute("type");
  // According to the spec, method is optional, defaulting to "GET" if not
  // specified.
  var method = element.getAttribute("method") || "GET";
  var template = element.getAttribute("template");

  let rels = [];
  if (element.hasAttribute("rel")) {
    rels = element.getAttribute("rel").toLowerCase().split(/\s+/);
  }

  // Support an alternate suggestion type, see bug 1425827 for details.
  if (type == "application/json" && rels.includes("suggestions")) {
    type = lazy.SearchUtils.URL_TYPE.SUGGEST_JSON;
  }

  let url = {
    type,
    method,
    template,
    params: [],
    rels,
  };

  // Non-standard. Used to be for Mozilla search engine files.
  for (var i = 0; i < element.children.length; ++i) {
    var param = element.children[i];
    if (param.localName == "Param") {
      url.params.push({
        name: param.getAttribute("name"),
        value: param.getAttribute("value"),
      });
    }
  }

  return url;
}

/**
 * Extracts an icon from an OpenSearch Image element.
 *
 * @param {Element} element
 *   The OpenSearch URL element.
 * @returns {OpenSearchImage}
 *   The properties of the image.
 * @see https://github.com/dewitt/opensearch/blob/master/opensearch-1-1-draft-6.md#the-image-element
 */
function parseImage(element) {
  let width = parseInt(element.getAttribute("width"), 10);
  let height = parseInt(element.getAttribute("height"), 10);
  let isPrefered = width == 16 && height == 16;

  if (isNaN(width) || isNaN(height) || width <= 0 || height <= 0) {
    lazy.logConsole.warn(
      "OpenSearch image element must have positive width and height."
    );
    return null;
  }

  return {
    url: element.textContent,
    isPrefered,
    width,
    height,
  };
}

/**
 * Confirms if the document has the expected namespace.
 *
 * @param {DOMElement} element
 *   The document to check.
 * @returns {boolean}
 *   True if the document matches the namespace.
 */
function hasExpectedNamspeace(element) {
  return (
    (element.localName == MOZSEARCH_LOCALNAME &&
      element.namespaceURI == MOZSEARCH_NS_10) ||
    (element.localName == OPENSEARCH_LOCALNAME &&
      OPENSEARCH_NAMESPACES.includes(element.namespaceURI))
  );
}
PK
!<�f{�1:1:modules/PageThumbUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, you can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Common thumbnailing routines used by various consumers, including
 * PageThumbs and BackgroundPageThumbs.
 */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
});

export var PageThumbUtils = {
  // The default thumbnail size for images
  THUMBNAIL_DEFAULT_SIZE: 448,
  // The default background color for page thumbnails.
  THUMBNAIL_BG_COLOR: "#fff",
  // The namespace for thumbnail canvas elements.
  HTML_NAMESPACE: "http://www.w3.org/1999/xhtml",

  /**
   * Creates a new canvas element in the context of aWindow.
   *
   * @param aWindow The document of this window will be used to
   *  create the canvas.
   * @param aWidth (optional) width of the canvas to create
   * @param aHeight (optional) height of the canvas to create
   * @return The newly created canvas.
   */
  createCanvas(aWindow, aWidth = 0, aHeight = 0) {
    let doc = aWindow.document;
    let canvas = doc.createElementNS(this.HTML_NAMESPACE, "canvas");
    canvas.mozOpaque = true;
    canvas.imageSmoothingEnabled = true;
    let [thumbnailWidth, thumbnailHeight] = this.getThumbnailSize(aWindow);
    canvas.width = aWidth ? aWidth : thumbnailWidth;
    canvas.height = aHeight ? aHeight : thumbnailHeight;
    return canvas;
  },

  /**
   * Calculates a preferred initial thumbnail size based based on newtab.css
   * sizes or a preference for other applications. The sizes should be the same
   * as set for the tile sizes in newtab.
   *
   * @param aWindow (optional) aWindow that is used to calculate the scaling size.
   * @return The calculated thumbnail size or a default if unable to calculate.
   */
  getThumbnailSize(aWindow = null) {
    if (!this._thumbnailWidth || !this._thumbnailHeight) {
      let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"].getService(
        Ci.nsIScreenManager
      );
      let left = {},
        top = {},
        screenWidth = {},
        screenHeight = {};
      screenManager.primaryScreen.GetRectDisplayPix(
        left,
        top,
        screenWidth,
        screenHeight
      );

      /**
       * The primary monitor default scale might be different than
       * what is reported by the window on mixed-DPI systems.
       * To get the best image quality, query both and take the highest one.
       */
      let primaryScale = screenManager.primaryScreen.defaultCSSScaleFactor;
      let windowScale = aWindow ? aWindow.devicePixelRatio : primaryScale;
      let scale = Math.max(primaryScale, windowScale);

      /** *
       * THESE VALUES ARE DEFINED IN newtab.css and hard coded.
       * If you change these values from the prefs,
       * ALSO CHANGE THEM IN newtab.css
       */
      let prefWidth = Services.prefs.getIntPref("toolkit.pageThumbs.minWidth");
      let prefHeight = Services.prefs.getIntPref(
        "toolkit.pageThumbs.minHeight"
      );
      let divisor = Services.prefs.getIntPref(
        "toolkit.pageThumbs.screenSizeDivisor"
      );

      prefWidth *= scale;
      prefHeight *= scale;

      this._thumbnailWidth = Math.max(
        Math.round(screenWidth.value / divisor),
        prefWidth
      );
      this._thumbnailHeight = Math.max(
        Math.round(screenHeight.value / divisor),
        prefHeight
      );
    }

    return [this._thumbnailWidth, this._thumbnailHeight];
  },

  /** *
   * Given a browser window, return the size of the content
   * minus the scroll bars.
   */
  getContentSize(aWindow) {
    let utils = aWindow.windowUtils;
    let sbWidth = {};
    let sbHeight = {};

    try {
      utils.getScrollbarSize(false, sbWidth, sbHeight);
    } catch (e) {
      // This might fail if the window does not have a presShell.
      console.error("Unable to get scrollbar size in determineCropSize.");
      sbWidth.value = sbHeight.value = 0;
    }

    // Even in RTL mode, scrollbars are always on the right.
    // So there's no need to determine a left offset.
    let width = aWindow.innerWidth - sbWidth.value;
    let height = aWindow.innerHeight - sbHeight.value;

    return [width, height];
  },

  /**
   * Renders an image onto a new canvas of a given width and proportional
   * height. Uses an image that exists in the window and is loaded, or falls
   * back to loading the url into a new image element.
   */
  async createImageThumbnailCanvas(
    window,
    url,
    targetWidth = 448,
    backgroundColor = this.THUMBNAIL_BG_COLOR
  ) {
    // 224px is the width of cards in ActivityStream; capture thumbnails at 2x
    const doc = window.document;

    let image = doc.querySelector("img");
    if (!image) {
      image = doc.createElementNS(this.HTML_NAMESPACE, "img");
      await new Promise((resolve, reject) => {
        image.onload = () => resolve();
        image.onerror = () => reject(new Error("LOAD_FAILED"));
        image.src = url;
      });
    }

    // <img src="*.svg"> has width/height but not naturalWidth/naturalHeight
    const imageWidth = image.naturalWidth || image.width;
    const imageHeight = image.naturalHeight || image.height;
    if (imageWidth === 0 || imageHeight === 0) {
      throw new Error("IMAGE_ZERO_DIMENSION");
    }
    const width = Math.min(targetWidth, imageWidth);
    const height = (imageHeight * width) / imageWidth;

    // As we're setting the width and maintaining the aspect ratio, if an image
    // is very tall we might get a very large thumbnail. Restricting the canvas
    // size to {width}x{width} solves this problem. Here we choose to clip the
    // image at the bottom rather than centre it vertically, based on an
    // estimate that the focus of a tall image is most likely to be near the top
    // (e.g., the face of a person).
    const canvasHeight = Math.min(height, width);
    const canvas = this.createCanvas(window, width, canvasHeight);
    const context = canvas.getContext("2d");
    context.fillStyle = backgroundColor;
    context.fillRect(0, 0, width, canvasHeight);
    context.drawImage(image, 0, 0, width, height);

    return {
      width,
      height: canvasHeight,
      imageData: canvas.toDataURL(),
    };
  },

  /**
   * Given a browser, this creates a snapshot of the content
   * and returns a canvas with the resulting snapshot of the content
   * at the thumbnail size. It has to do this through a two step process:
   *
   * 1) Render the content at the window size to a canvas that is 2x the thumbnail size
   * 2) Downscale the canvas from (1) down to the thumbnail size
   *
   * This is because the thumbnail size is too small to render at directly,
   * causing pages to believe the browser is a small resolution. Also,
   * at that resolution, graphical artifacts / text become very jagged.
   * It's actually better to the eye to have small blurry text than sharp
   * jagged pixels to represent text.
   *
   * @params aBrowser - the browser to create a snapshot of.
   * @params aDestCanvas destination canvas to draw the final
   *   snapshot to. Can be null.
   * @param aArgs (optional) Additional named parameters:
   *   fullScale - request that a non-downscaled image be returned.
   * @return Canvas with a scaled thumbnail of the window.
   */
  async createSnapshotThumbnail(aBrowser, aDestCanvas, aArgs) {
    const aWindow = aBrowser.contentWindow;
    let backgroundColor = aArgs
      ? aArgs.backgroundColor
      : PageThumbUtils.THUMBNAIL_BG_COLOR;
    let fullScale = aArgs ? aArgs.fullScale : false;
    let [contentWidth, contentHeight] = this.getContentSize(aWindow);
    let [thumbnailWidth, thumbnailHeight] = aDestCanvas
      ? [aDestCanvas.width, aDestCanvas.height]
      : this.getThumbnailSize(aWindow);

    // If the caller wants a fullscale image, set the desired thumbnail dims
    // to the dims of content and (if provided) size the incoming canvas to
    // support our results.
    if (fullScale) {
      thumbnailWidth = contentWidth;
      thumbnailHeight = contentHeight;
      if (aDestCanvas) {
        aDestCanvas.width = contentWidth;
        aDestCanvas.height = contentHeight;
      }
    } else if (contentHeight && aArgs.preserveAspectRatio) {
      // Calculate the thumbnail height based on thumbnail width
      // and content aspect ratio
      if (aArgs.targetWidth) {
        thumbnailWidth = aArgs.targetWidth;
      }
      thumbnailHeight = thumbnailWidth / (contentWidth / contentHeight);
      if (aDestCanvas) {
        aDestCanvas.width = thumbnailWidth;
        aDestCanvas.height = thumbnailHeight;
      }
    }

    let intermediateWidth = thumbnailWidth * 2;
    let intermediateHeight = thumbnailHeight * 2;
    let skipDownscale = false;

    // If the intermediate thumbnail is larger than content dims (hiDPI
    // devices can experience this) or a full preview is requested render
    // at the final thumbnail size.
    if (
      intermediateWidth >= contentWidth ||
      intermediateHeight >= contentHeight ||
      fullScale
    ) {
      intermediateWidth = thumbnailWidth;
      intermediateHeight = thumbnailHeight;
      skipDownscale = true;
    }

    // Create an intermediate surface
    let snapshotCanvas = this.createCanvas(
      aWindow,
      intermediateWidth,
      intermediateHeight
    );

    // Step 1: capture the image at the intermediate dims. For thumbnails
    // this is twice the thumbnail size, for fullScale images this is at
    // content dims.
    // Also by default, canvas does not draw the scrollbars, so no need to
    // remove the scrollbar sizes.
    let targetScale;
    if (aArgs.preserveAspectRatio) {
      // always scale based on width, as we resize height to accommodate
      targetScale = intermediateWidth / contentWidth;
    } else {
      targetScale = Math.max(
        intermediateWidth / contentWidth,
        intermediateHeight / contentHeight
      );
    }
    let scale = Math.min(targetScale, 1);

    let snapshotCtx = snapshotCanvas.getContext("2d");
    snapshotCtx.save();
    snapshotCtx.scale(scale, scale);
    const image = await aBrowser.drawSnapshot(
      0,
      0,
      contentWidth,
      contentHeight,
      scale,
      backgroundColor
    );
    snapshotCtx.drawImage(image, 0, 0, contentWidth, contentHeight);
    snapshotCtx.restore();

    // Part 2: Downscale from our intermediate dims to the final thumbnail
    // dims and copy the result to aDestCanvas. If the caller didn't
    // provide a target canvas, create a new canvas and return it.
    let finalCanvas =
      aDestCanvas ||
      this.createCanvas(aWindow, thumbnailWidth, thumbnailHeight);

    let finalCtx = finalCanvas.getContext("2d");
    finalCtx.save();
    if (!skipDownscale) {
      finalCtx.scale(0.5, 0.5);
    }
    finalCtx.drawImage(snapshotCanvas, 0, 0);
    finalCtx.restore();

    return finalCanvas;
  },

  /**
   * Determine a good thumbnail crop size and scale for a given content
   * window.
   *
   * @param aWindow The content window.
   * @param aCanvas The target canvas.
   * @return An array containing width, height and scale.
   */
  determineCropSize(aWindow, aCanvas) {
    let utils = aWindow.windowUtils;
    let sbWidth = {};
    let sbHeight = {};

    try {
      utils.getScrollbarSize(false, sbWidth, sbHeight);
    } catch (e) {
      // This might fail if the window does not have a presShell.
      console.error("Unable to get scrollbar size in determineCropSize.");
      sbWidth.value = sbHeight.value = 0;
    }

    // Even in RTL mode, scrollbars are always on the right.
    // So there's no need to determine a left offset.
    let width = aWindow.innerWidth - sbWidth.value;
    let height = aWindow.innerHeight - sbHeight.value;

    let { width: thumbnailWidth, height: thumbnailHeight } = aCanvas;
    let scale = Math.min(
      Math.max(thumbnailWidth / width, thumbnailHeight / height),
      1
    );
    let scaledWidth = width * scale;
    let scaledHeight = height * scale;

    if (scaledHeight > thumbnailHeight) {
      height -= Math.floor(Math.abs(scaledHeight - thumbnailHeight) * scale);
    }

    if (scaledWidth > thumbnailWidth) {
      width -= Math.floor(Math.abs(scaledWidth - thumbnailWidth) * scale);
    }

    return [width, height, scale];
  },

  shouldStoreContentThumbnail(aDocument, aDocShell) {
    if (lazy.BrowserUtils.isFindbarVisible(aDocShell)) {
      return false;
    }

    // FIXME Bug 720575 - Don't capture thumbnails for SVG or XML documents as
    //       that currently regresses Talos SVG tests.
    if (ChromeUtils.getClassName(aDocument) === "XMLDocument") {
      return false;
    }

    let webNav = aDocShell.QueryInterface(Ci.nsIWebNavigation);

    // Don't take screenshots of about: pages.
    if (webNav.currentURI.schemeIs("about")) {
      return false;
    }

    // There's no point in taking screenshot of loading pages.
    if (aDocShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE) {
      return false;
    }

    let channel = aDocShell.currentDocumentChannel;

    // No valid document channel. We shouldn't take a screenshot.
    if (!channel) {
      return false;
    }

    // Don't take screenshots of internally redirecting about: pages.
    // This includes error pages.
    let uri = channel.originalURI;
    if (uri.schemeIs("about")) {
      return false;
    }

    let httpChannel;
    try {
      httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
    } catch (e) {
      /* Not an HTTP channel. */
    }

    if (httpChannel) {
      // Continue only if we have a 2xx status code.
      try {
        if (Math.floor(httpChannel.responseStatus / 100) != 2) {
          return false;
        }
      } catch (e) {
        // Can't get response information from the httpChannel
        // because mResponseHead is not available.
        return false;
      }

      // Cache-Control: no-store.
      if (httpChannel.isNoStoreResponse()) {
        return false;
      }

      // Don't capture HTTPS pages unless the user explicitly enabled it.
      if (
        uri.schemeIs("https") &&
        !Services.prefs.getBoolPref("browser.cache.disk_cache_ssl")
      ) {
        return false;
      }
    } // httpChannel
    return true;
  },

  /**
   * Given a channel, returns true if it should be considered an "error
   * response", false otherwise.
   */
  isChannelErrorResponse(channel) {
    // No valid document channel sounds like an error to me!
    if (!channel) {
      return true;
    }
    if (!(channel instanceof Ci.nsIHttpChannel)) {
      // it might be FTP etc, so assume it's ok.
      return false;
    }
    try {
      return !channel.requestSucceeded;
    } catch (_) {
      // not being able to determine success is surely failure!
      return true;
    }
  },
};
PK
!<C�Y:z:zmodules/PageThumbs.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const PREF_STORAGE_VERSION = "browser.pagethumbnails.storage_version";
const LATEST_STORAGE_VERSION = 3;

const EXPIRATION_MIN_CHUNK_SIZE = 50;
const EXPIRATION_INTERVAL_SECS = 3600;

// If a request for a thumbnail comes in and we find one that is "stale"
// (or don't find one at all) we automatically queue a request to generate a
// new one.
const MAX_THUMBNAIL_AGE_SECS = 172800; // 2 days == 60*60*24*2 == 172800 secs.

/**
 * Name of the directory in the profile that contains the thumbnails.
 */
const THUMBNAIL_DIRECTORY = "thumbnails";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { BasePromiseWorker } from "resource://gre/modules/PromiseWorker.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PageThumbUtils: "resource://gre/modules/PageThumbUtils.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gUpdateTimerManager",
  "@mozilla.org/updates/timer-manager;1",
  "nsIUpdateTimerManager"
);

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "PageThumbsStorageService",
  "@mozilla.org/thumbnails/pagethumbs-service;1",
  "nsIPageThumbsStorageService"
);

/**
 * Utilities for dealing with promises.
 */
const TaskUtils = {
  /**
   * Read the bytes from a blob, asynchronously.
   *
   * @return {Promise}
   * @resolve {ArrayBuffer} In case of success, the bytes contained in the blob.
   * @reject {DOMException} In case of error, the underlying DOMException.
   */
  readBlob: function readBlob(blob) {
    return new Promise((resolve, reject) => {
      let reader = new FileReader();
      reader.onloadend = function onloadend() {
        if (reader.readyState != FileReader.DONE) {
          reject(reader.error);
        } else {
          resolve(reader.result);
        }
      };
      reader.readAsArrayBuffer(blob);
    });
  },
};

/**
 * Singleton providing functionality for capturing web page thumbnails and for
 * accessing them if already cached.
 */
export var PageThumbs = {
  _initialized: false,

  /**
   * The calculated width and height of the thumbnails.
   */
  _thumbnailWidth: 0,
  _thumbnailHeight: 0,

  /**
   * The scheme to use for thumbnail urls.
   */
  get scheme() {
    return "moz-page-thumb";
  },

  /**
   * The static host to use for thumbnail urls.
   */
  get staticHost() {
    return "thumbnails";
  },

  /**
   * The thumbnails' image type.
   */
  get contentType() {
    return "image/png";
  },

  init: function PageThumbs_init() {
    if (!this._initialized) {
      this._initialized = true;

      this._placesObserver = new PlacesWeakCallbackWrapper(
        this.handlePlacesEvents.bind(this)
      );
      PlacesObservers.addListener(
        ["history-cleared", "page-removed"],
        this._placesObserver
      );

      // Migrate the underlying storage, if needed.
      PageThumbsStorageMigrator.migrate();
      PageThumbsExpiration.init();
    }
  },

  handlePlacesEvents(events) {
    for (const event of events) {
      switch (event.type) {
        case "history-cleared": {
          PageThumbsStorage.wipe();
          break;
        }
        case "page-removed": {
          if (event.isRemovedFromStore) {
            PageThumbsStorage.remove(event.url);
          }
          break;
        }
      }
    }
  },

  uninit: function PageThumbs_uninit() {
    if (this._initialized) {
      this._initialized = false;
    }
  },

  /**
   * Gets the thumbnail image's url for a given web page's url.
   * @param aUrl The web page's url that is depicted in the thumbnail.
   * @return The thumbnail image's url.
   */
  getThumbnailURL: function PageThumbs_getThumbnailURL(aUrl) {
    return (
      this.scheme +
      "://" +
      this.staticHost +
      "/?url=" +
      encodeURIComponent(aUrl) +
      "&revision=" +
      PageThumbsStorage.getRevision(aUrl)
    );
  },

  /**
   * Gets the path of the thumbnail file for a given web page's
   * url. This file may or may not exist depending on whether the
   * thumbnail has been captured or not.
   *
   * @param aUrl The web page's url.
   * @return The path of the thumbnail file.
   */
  getThumbnailPath: function PageThumbs_getThumbnailPath(aUrl) {
    return lazy.PageThumbsStorageService.getFilePathForURL(aUrl);
  },

  /**
   * Asynchronously returns a thumbnail as a blob for the given
   * window.
   *
   * @param aBrowser The <browser> to capture a thumbnail from.
   * @param aArgs See captureToCanvas for accepted arguments.
   * @return {Promise}
   * @resolve {Blob} The thumbnail, as a Blob.
   */
  captureToBlob: function PageThumbs_captureToBlob(aBrowser, aArgs) {
    if (!this._prefEnabled()) {
      return null;
    }

    return new Promise(resolve => {
      let canvas = this.createCanvas(aBrowser.ownerGlobal);
      this.captureToCanvas(aBrowser, canvas, aArgs)
        .then(() => {
          canvas.toBlob(blob => {
            resolve(blob, this.contentType);
          });
        })
        .catch(e => console.error(e));
    });
  },

  /**
   * Captures a thumbnail from a given window and draws it to the given canvas.
   * Note, when dealing with remote content, this api draws into the passed
   * canvas asynchronously. Pass aCallback to receive an async callback after
   * canvas painting has completed.
   * @param aBrowser The browser to capture a thumbnail from.
   * @param aCanvas The canvas to draw to. The thumbnail will be scaled to match
   *   the dimensions of this canvas. If callers pass a 0x0 canvas, the canvas
   *   will be resized to default thumbnail dimensions just prior to painting.
   * @param aArgs (optional) Additional named parameters:
   *   fullScale - request that a non-downscaled image be returned.
   *   isImage - indicate that this should be treated as an image url.
   *   backgroundColor - background color to draw behind images.
   *   targetWidth - desired width for images.
   *   preserveAspectRatio - resize image height based on targetWidth
   *   isBackgroundThumb - true if request is from the background thumb service.
   *   fullViewport - request that a screenshot for the viewport be
   *     captured. This makes it possible to get a screenshot that reflects
   *     the current scroll position of aBrowser.
   * @param aSkipTelemetry skip recording telemetry
   */
  async captureToCanvas(aBrowser, aCanvas, aArgs, aSkipTelemetry = false) {
    let telemetryCaptureTime = new Date();
    let args = {
      fullScale: aArgs ? aArgs.fullScale : false,
      isImage: aArgs ? aArgs.isImage : false,
      backgroundColor:
        aArgs?.backgroundColor ?? lazy.PageThumbUtils.THUMBNAIL_BG_COLOR,
      targetWidth:
        aArgs?.targetWidth ?? lazy.PageThumbUtils.THUMBNAIL_DEFAULT_SIZE,
      preserveAspectRatio: aArgs?.preserveAspectRatio ?? false,
      isBackgroundThumb: aArgs ? aArgs.isBackgroundThumb : false,
      fullViewport: aArgs?.fullViewport ?? false,
    };

    return this._captureToCanvas(aBrowser, aCanvas, args).then(() => {
      if (!aSkipTelemetry) {
        Services.telemetry
          .getHistogramById("FX_THUMBNAILS_CAPTURE_TIME_MS")
          .add(new Date() - telemetryCaptureTime);
      }
      return aCanvas;
    });
  },

  /**
   * Asynchronously check the state of aBrowser to see if it passes a set of
   * predefined security checks. Consumers should refrain from storing
   * thumbnails if these checks fail. Note the final result of this call is
   * transitory as it is based on current navigation state and the type of
   * content being displayed.
   *
   * @param aBrowser The target browser
   */
  async shouldStoreThumbnail(aBrowser) {
    // Don't capture in private browsing mode.
    if (lazy.PrivateBrowsingUtils.isBrowserPrivate(aBrowser)) {
      return false;
    }
    if (aBrowser.isRemoteBrowser) {
      if (aBrowser.browsingContext.currentWindowGlobal) {
        let thumbnailsActor =
          aBrowser.browsingContext.currentWindowGlobal.getActor("Thumbnails");
        return thumbnailsActor
          .sendQuery("Browser:Thumbnail:CheckState")
          .catch(() => {
            return false;
          });
      }
      return false;
    }
    return lazy.PageThumbUtils.shouldStoreContentThumbnail(
      aBrowser.contentDocument,
      aBrowser.docShell
    );
  },

  // The background thumbnail service captures to canvas but doesn't want to
  // participate in this service's telemetry, which is why this method exists.
  async _captureToCanvas(aBrowser, aCanvas, aArgs) {
    if (aBrowser.isRemoteBrowser) {
      let thumbnail = await this._captureRemoteThumbnail(
        aBrowser,
        aCanvas.width,
        aCanvas.height,
        aArgs
      );

      // 'thumbnail' can be null if the browser has navigated away after starting
      // the thumbnail request, so we check it here.
      if (thumbnail) {
        let ctx = thumbnail.getContext("2d");
        let imgData = ctx.getImageData(0, 0, thumbnail.width, thumbnail.height);
        aCanvas.width = thumbnail.width;
        aCanvas.height = thumbnail.height;
        aCanvas.getContext("2d").putImageData(imgData, 0, 0);
      }

      return aCanvas;
    }
    // The content is a local page, grab a thumbnail sync.
    await lazy.PageThumbUtils.createSnapshotThumbnail(aBrowser, aCanvas, aArgs);
    return aCanvas;
  },

  /**
   * Asynchrnously render an appropriately scaled thumbnail to canvas.
   *
   * @param aBrowser The browser to capture a thumbnail from.
   * @param aWidth The desired canvas width.
   * @param aHeight The desired canvas height.
   * @param aArgs (optional) Additional named parameters:
   *   fullScale - request that a non-downscaled image be returned.
   *   isImage - indicate that this should be treated as an image url.
   *   backgroundColor - background color to draw behind images.
   *   targetWidth - desired width for images.
   *   preserveAspectRatio - resize image height based on targetWidth
   *   isBackgroundThumb - true if request is from the background thumb service.
   *   fullViewport - request that a screenshot for the viewport be
   *     captured. This makes it possible to get a screenshot that reflects
   *     the current scroll position of aBrowser.
   * @return a promise
   */
  async _captureRemoteThumbnail(aBrowser, aWidth, aHeight, aArgs) {
    if (!aBrowser.browsingContext || !aBrowser.isConnected) {
      return null;
    }

    let thumbnailsActor = aBrowser.browsingContext.currentWindowGlobal.getActor(
      aArgs.isBackgroundThumb ? "BackgroundThumbnails" : "Thumbnails"
    );
    let contentInfo = await thumbnailsActor.sendQuery(
      "Browser:Thumbnail:ContentInfo",
      {
        isImage: aArgs.isImage,
        targetWidth: aArgs.targetWidth,
        backgroundColor: aArgs.backgroundColor,
      }
    );

    let contentWidth = contentInfo.width;
    let contentHeight = contentInfo.height;
    if (contentWidth == 0 || contentHeight == 0) {
      throw new Error("IMAGE_ZERO_DIMENSION");
    }
    let aspectRatio = contentWidth / contentHeight;

    if (!aBrowser.isConnected) {
      return null;
    }
    let doc = aBrowser.ownerDocument;
    let thumbnail = doc.createElementNS(
      lazy.PageThumbUtils.HTML_NAMESPACE,
      "canvas"
    );

    let image;
    if (contentInfo.imageData) {
      thumbnail.width = contentWidth;
      thumbnail.height = contentHeight;

      image = new aBrowser.ownerGlobal.Image();
      await new Promise(resolve => {
        image.onload = resolve;
        image.src = contentInfo.imageData;
      });
    } else {
      let fullScale = aArgs ? aArgs.fullScale : false;
      let targetWidth = aArgs.targetWidth ? aArgs.targetWidth : aWidth;
      let preserveAspectRatio = aArgs ? aArgs.preserveAspectRatio : false;
      let scale = 1;
      if (!fullScale) {
        let targetScale;
        if (preserveAspectRatio) {
          targetScale = targetWidth / contentWidth;
        } else {
          targetScale = Math.max(
            aWidth / contentWidth,
            aHeight / contentHeight
          );
        }
        scale = Math.min(targetScale, 1);
      }

      image = await aBrowser.drawSnapshot(
        0,
        0,
        contentWidth,
        contentHeight,
        scale,
        aArgs.backgroundColor,
        aArgs.fullViewport
      );
      if (!image) {
        return null;
      }

      if (preserveAspectRatio) {
        thumbnail.width = targetWidth;
        thumbnail.height = targetWidth / aspectRatio;
      } else {
        thumbnail.width = fullScale ? contentWidth : aWidth;
        thumbnail.height = fullScale ? contentHeight : aHeight;
      }
    }

    thumbnail.getContext("2d").drawImage(image, 0, 0);

    return thumbnail;
  },

  /**
   * Captures a thumbnail for the given browser and stores it to the cache.
   * @param aBrowser The browser to capture a thumbnail for.
   */
  captureAndStore: async function PageThumbs_captureAndStore(aBrowser) {
    if (!this._prefEnabled()) {
      return;
    }

    let url = aBrowser.currentURI.spec;
    let originalURL;
    let channelError = false;

    if (!aBrowser.isRemoteBrowser) {
      let channel = aBrowser.docShell.currentDocumentChannel;
      originalURL = channel.originalURI.spec;
      // see if this was an error response.
      channelError = lazy.PageThumbUtils.isChannelErrorResponse(channel);
    } else {
      let thumbnailsActor =
        aBrowser.browsingContext.currentWindowGlobal.getActor("Thumbnails");
      let resp = await thumbnailsActor.sendQuery(
        "Browser:Thumbnail:GetOriginalURL"
      );

      originalURL = resp.originalURL || url;
      channelError = resp.channelError;
    }

    try {
      let blob = await this.captureToBlob(aBrowser);
      let buffer = await TaskUtils.readBlob(blob);
      await this._store(originalURL, url, buffer, channelError);
    } catch (ex) {
      console.error("Exception thrown during thumbnail capture:", ex);
    }
  },

  /**
   * Checks if an existing thumbnail for the specified URL is either missing
   * or stale, and if so, captures and stores it.  Once the thumbnail is stored,
   * an observer service notification will be sent, so consumers should observe
   * such notifications if they want to be notified of an updated thumbnail.
   *
   * @param aBrowser The content window of this browser will be captured.
   */
  captureAndStoreIfStale: async function PageThumbs_captureAndStoreIfStale(
    aBrowser
  ) {
    if (!aBrowser.currentURI) {
      return false;
    }
    let url = aBrowser.currentURI.spec;
    let recent;
    try {
      recent = await PageThumbsStorage.isFileRecentForURL(url);
    } catch {
      return false;
    }
    if (
      !recent &&
      // Careful, the call to PageThumbsStorage is async, so the browser may
      // have navigated away from the URL or even closed.
      aBrowser.currentURI &&
      aBrowser.currentURI.spec == url
    ) {
      await this.captureAndStore(aBrowser);
    }
    return true;
  },

  /**
   * Capture a thumbnail for tab previews and draw it to canvas
   *
   * @param aBrowser the content window of this browser will be captured.
   * @param aCanvas the thumbnail will be rendered to this canvas.
   */
  async captureTabPreviewThumbnail(aBrowser, aCanvas) {
    let desiredAspectRatio = aCanvas.width / aCanvas.height;

    let thumbnailsActor =
      aBrowser.browsingContext.currentWindowGlobal.getActor("Thumbnails");
    let contentInfo = await thumbnailsActor.sendQuery(
      "Browser:Thumbnail:ContentInfo"
    );

    // capture vars
    let captureX = 0;
    let captureY = contentInfo.scrollY;
    let captureWidth = contentInfo.width;
    let captureHeight = captureWidth / desiredAspectRatio;
    let captureScale = aCanvas.width / captureWidth;

    // render vars
    let renderX = 0;
    let renderY = 0;
    let renderWidth = aCanvas.width;
    let renderHeight = aCanvas.height;

    // We're scaling based on width, so when the window is really wide,
    // the screenshot will be really small.
    // Some pages might not have enough content to vertically fill our canvas.
    // In this case, we scale the capture based on height instead and crop
    // the horizontal edges when rendering.
    if (contentInfo.documentHeight < captureHeight) {
      captureY = 0;
      captureHeight = contentInfo.documentHeight;
      captureScale = aCanvas.height / captureHeight;

      renderWidth = captureWidth * captureScale;
      renderHeight = aCanvas.height;

      //canvasY = (aCanvas.height - eventualHeight) / 2;

      renderX = (aCanvas.width - renderWidth) / 2;
      renderY = (aCanvas.height - renderHeight) / 2;
    }
    // Avoid showing a blank space at the bottom of the thumbnail
    // when the scroll position is  near the bottom of the document.
    else if (contentInfo.documentHeight - captureY < captureHeight) {
      captureY = contentInfo.documentHeight - captureHeight;
    }
    let snapshotResult = await aBrowser.drawSnapshot(
      captureX,
      captureY,
      captureWidth,
      captureHeight,
      captureScale * 2,
      "transparent",
      false
    );
    aCanvas
      .getContext("2d")
      .drawImage(snapshotResult, renderX, renderY, renderWidth, renderHeight);
  },

  /**
   * Stores data to disk for the given URLs.
   *
   * NB: The background thumbnail service calls this, too.
   *
   * @param aOriginalURL The URL with which the capture was initiated.
   * @param aFinalURL The URL to which aOriginalURL ultimately resolved.
   * @param aData An ArrayBuffer containing the image data.
   * @param aNoOverwrite If true and files for the URLs already exist, the files
   *                     will not be overwritten.
   */
  _store: async function PageThumbs__store(
    aOriginalURL,
    aFinalURL,
    aData,
    aNoOverwrite
  ) {
    let telemetryStoreTime = new Date();
    await PageThumbsStorage.writeData(aFinalURL, aData, aNoOverwrite);
    Services.telemetry
      .getHistogramById("FX_THUMBNAILS_STORE_TIME_MS")
      .add(new Date() - telemetryStoreTime);

    Services.obs.notifyObservers(null, "page-thumbnail:create", aFinalURL);
    // We've been redirected. Create a copy of the current thumbnail for
    // the redirect source. We need to do this because:
    //
    // 1) Users can drag any kind of links onto the newtab page. If those
    //    links redirect to a different URL then we want to be able to
    //    provide thumbnails for both of them.
    //
    // 2) The newtab page should actually display redirect targets, only.
    //    Because of bug 559175 this information can get lost when using
    //    Sync and therefore also redirect sources appear on the newtab
    //    page. We also want thumbnails for those.
    if (aFinalURL != aOriginalURL) {
      await PageThumbsStorage.copy(aFinalURL, aOriginalURL, aNoOverwrite);
      Services.obs.notifyObservers(null, "page-thumbnail:create", aOriginalURL);
    }
  },

  /**
   * Register an expiration filter.
   *
   * When thumbnails are going to expire, each registered filter is asked for a
   * list of thumbnails to keep.
   *
   * The filter (if it is a callable) or its filterForThumbnailExpiration method
   * (if the filter is an object) is called with a single argument.  The
   * argument is a callback function.  The filter must call the callback
   * function and pass it an array of zero or more URLs.  (It may do so
   * asynchronously.)  Thumbnails for those URLs will be except from expiration.
   *
   * @param aFilter callable, or object with filterForThumbnailExpiration method
   */
  addExpirationFilter: function PageThumbs_addExpirationFilter(aFilter) {
    PageThumbsExpiration.addFilter(aFilter);
  },

  /**
   * Unregister an expiration filter.
   * @param aFilter A filter that was previously passed to addExpirationFilter.
   */
  removeExpirationFilter: function PageThumbs_removeExpirationFilter(aFilter) {
    PageThumbsExpiration.removeFilter(aFilter);
  },

  /**
   * Creates a new hidden canvas element.
   * @param aWindow The document of this window will be used to create the
   *                canvas.  If not given, the hidden window will be used.
   * @return The newly created canvas.
   */
  createCanvas: function PageThumbs_createCanvas(aWindow) {
    return lazy.PageThumbUtils.createCanvas(aWindow);
  },

  _prefEnabled: function PageThumbs_prefEnabled() {
    try {
      return !Services.prefs.getBoolPref(
        "browser.pagethumbnails.capturing_disabled"
      );
    } catch (e) {
      return true;
    }
  },
};

export var PageThumbsStorage = {
  ensurePath: function Storage_ensurePath() {
    // Create the directory (ignore any error if the directory
    // already exists). As all writes are done from the PageThumbsWorker
    // thread, which serializes its operations, this ensures that
    // future operations can proceed without having to check whether
    // the directory exists.
    return PageThumbsWorker.post("makeDir", [
      lazy.PageThumbsStorageService.path,
      { ignoreExisting: true },
    ]).catch(function onError(aReason) {
      console.error("Could not create thumbnails directory", aReason);
    });
  },

  _revisionTable: {},

  // Generate an arbitrary revision tag, i.e. one that can't be used to
  // infer URL frecency.
  updateRevision(aURL) {
    // Initialize with a random value and increment on each update. Wrap around
    // modulo _revisionRange, so that even small values carry no meaning.
    let rev = this._revisionTable[aURL];
    if (rev == null) {
      rev = Math.floor(Math.random() * this._revisionRange);
    }
    this._revisionTable[aURL] = (rev + 1) % this._revisionRange;
  },

  // If two thumbnails with the same URL and revision are in cache at the
  // same time, the image loader may pick the stale thumbnail in some cases.
  // Therefore _revisionRange must be large enough to prevent this, e.g.
  // in the pathological case image.cache.size (5MB by default) could fill
  // with (abnormally small) 10KB thumbnail images if the browser session
  // runs long enough (though this is unlikely as thumbnails are usually
  // only updated every MAX_THUMBNAIL_AGE_SECS).
  _revisionRange: 8192,

  /**
   * Return a revision tag for the thumbnail stored for a given URL.
   *
   * @param aURL The URL spec string
   * @return A revision tag for the corresponding thumbnail. Returns a changed
   * value whenever the stored thumbnail changes.
   */
  getRevision(aURL) {
    let rev = this._revisionTable[aURL];
    if (rev == null) {
      this.updateRevision(aURL);
      rev = this._revisionTable[aURL];
    }
    return rev;
  },

  /**
   * Write the contents of a thumbnail, off the main thread.
   *
   * @param {string} aURL The url for which to store a thumbnail.
   * @param {ArrayBuffer} aData The data to store in the thumbnail, as
   * an ArrayBuffer. This array buffer will be detached and cannot be
   * reused after the copy.
   * @param {boolean} aNoOverwrite If true and the thumbnail's file already
   * exists, the file will not be overwritten.
   *
   * @return {Promise}
   */
  writeData: function Storage_writeData(aURL, aData, aNoOverwrite) {
    let path = lazy.PageThumbsStorageService.getFilePathForURL(aURL);
    this.ensurePath();
    aData = new Uint8Array(aData);
    let msg = [
      path,
      aData,
      {
        tmpPath: path + ".tmp",
        mode: aNoOverwrite ? "create" : "overwrite",
      },
    ];
    return PageThumbsWorker.post(
      "writeAtomic",
      msg,
      msg /* we don't want that message garbage-collected,
           as OS.Shared.Type.void_t.in_ptr.toMsg uses C-level
           memory tricks to enforce zero-copy*/
    ).then(
      () => this.updateRevision(aURL),
      this._eatNoOverwriteError(aNoOverwrite)
    );
  },

  /**
   * Copy a thumbnail, off the main thread.
   *
   * @param {string} aSourceURL The url of the thumbnail to copy.
   * @param {string} aTargetURL The url of the target thumbnail.
   * @param {boolean} aNoOverwrite If true and the target file already exists,
   * the file will not be overwritten.
   *
   * @return {Promise}
   */
  copy: function Storage_copy(aSourceURL, aTargetURL, aNoOverwrite) {
    this.ensurePath();
    let sourceFile =
      lazy.PageThumbsStorageService.getFilePathForURL(aSourceURL);
    let targetFile =
      lazy.PageThumbsStorageService.getFilePathForURL(aTargetURL);
    let options = { noOverwrite: aNoOverwrite };
    return PageThumbsWorker.post("copy", [
      sourceFile,
      targetFile,
      options,
    ]).then(
      () => this.updateRevision(aTargetURL),
      this._eatNoOverwriteError(aNoOverwrite)
    );
  },

  /**
   * Remove a single thumbnail, off the main thread.
   *
   * @return {Promise}
   */
  remove: function Storage_remove(aURL) {
    return PageThumbsWorker.post("remove", [
      lazy.PageThumbsStorageService.getFilePathForURL(aURL),
    ]);
  },

  /**
   * Remove all thumbnails, off the main thread.
   *
   * @return {Promise}
   */
  wipe: async function Storage_wipe() {
    //
    // This operation may be launched during shutdown, so we need to
    // take a few precautions to ensure that:
    //
    // 1. it is not interrupted by shutdown, in which case we
    //    could be leaving privacy-sensitive files on disk;
    // 2. it is not launched too late during shutdown, in which
    //    case this could cause shutdown freezes (see bug 1005487,
    //    which will eventually be fixed by bug 965309)
    //

    let blocker = () => undefined;

    // The following operation will rise an error if we have already
    // reached profileBeforeChange, in which case it is too late
    // to clear the thumbnail wipe.
    IOUtils.profileBeforeChange.addBlocker(
      "PageThumbs: removing all thumbnails",
      blocker
    );

    // Start the work only now that `profileBeforeChange` has had
    // a chance to throw an error.

    let promise = PageThumbsWorker.post("wipe", [
      lazy.PageThumbsStorageService.path,
    ]);
    try {
      await promise;
    } finally {
      // Generally, we will be done much before profileBeforeChange,
      // so let's not hoard blockers.
      IOUtils.profileBeforeChange.removeBlocker(blocker);
    }
  },

  fileExistsForURL: function Storage_fileExistsForURL(aURL) {
    return PageThumbsWorker.post("exists", [
      lazy.PageThumbsStorageService.getFilePathForURL(aURL),
    ]);
  },

  isFileRecentForURL: function Storage_isFileRecentForURL(aURL) {
    return PageThumbsWorker.post("isFileRecent", [
      lazy.PageThumbsStorageService.getFilePathForURL(aURL),
      MAX_THUMBNAIL_AGE_SECS,
    ]);
  },

  /**
   * For functions that take a noOverwrite option, IOUtils throws an error if
   * the target file exists and noOverwrite is true.  We don't consider that an
   * error, and we don't want such errors propagated.
   *
   * @param {aNoOverwrite} The noOverwrite option used in the IOUtils operation.
   *
   * @return {function} A function that should be passed as the second argument
   * to then() (the `onError` argument).
   */
  _eatNoOverwriteError: function Storage__eatNoOverwriteError(aNoOverwrite) {
    return function onError(err) {
      if (
        !aNoOverwrite ||
        !DOMException.isInstance(err) ||
        err.name !== "TypeMismatchError"
      ) {
        throw err;
      }
    };
  },
};

var PageThumbsStorageMigrator = {
  get currentVersion() {
    try {
      return Services.prefs.getIntPref(PREF_STORAGE_VERSION);
    } catch (e) {
      // The pref doesn't exist, yet. Return version 0.
      return 0;
    }
  },

  set currentVersion(aVersion) {
    Services.prefs.setIntPref(PREF_STORAGE_VERSION, aVersion);
  },

  migrate: function Migrator_migrate() {
    let version = this.currentVersion;

    // Storage version 1 never made it to beta.
    // At the time of writing only Windows had (ProfD != ProfLD) and we
    // needed to move thumbnails from the roaming profile to the locale
    // one so that they're not needlessly included in backups and/or
    // written via SMB.

    // Storage version 2 also never made it to beta.
    // The thumbnail folder structure has been changed and old thumbnails
    // were not migrated. Instead, we just renamed the current folder to
    // "<name>-old" and will remove it later.

    if (version < 3) {
      this.migrateToVersion3();
    }

    this.currentVersion = LATEST_STORAGE_VERSION;
  },

  /**
   * Bug 239254 added support for having the disk cache and thumbnail
   * directories on a local path (i.e. ~/.cache/) under Linux. We'll first
   * try to move the old thumbnails to their new location. If that's not
   * possible (because ProfD might be on a different file system than
   * ProfLD) we'll just discard them.
   *
   * @param {string*} local The path to the local profile directory.
   * Used for testing. Default argument is good for all non-testing uses.
   * @param {string*} roaming The path to the roaming profile directory.
   * Used for testing. Default argument is good for all non-testing uses.
   */
  migrateToVersion3: function Migrator_migrateToVersion3(
    local = Services.dirsvc.get("ProfLD", Ci.nsIFile).path,
    roaming = Services.dirsvc.get("ProfD", Ci.nsIFile).path
  ) {
    PageThumbsWorker.post("moveOrDeleteAllThumbnails", [
      PathUtils.join(roaming, THUMBNAIL_DIRECTORY),
      PathUtils.join(local, THUMBNAIL_DIRECTORY),
    ]);
  },
};

// Export required for testing
export var PageThumbsExpiration = {
  _filters: [],

  init: function Expiration_init() {
    lazy.gUpdateTimerManager.registerTimer(
      "browser-cleanup-thumbnails",
      this,
      EXPIRATION_INTERVAL_SECS
    );
  },

  addFilter: function Expiration_addFilter(aFilter) {
    this._filters.push(aFilter);
  },

  removeFilter: function Expiration_removeFilter(aFilter) {
    let index = this._filters.indexOf(aFilter);
    if (index > -1) {
      this._filters.splice(index, 1);
    }
  },

  notify: function Expiration_notify() {
    let urls = [];
    let filtersToWaitFor = this._filters.length;

    let expire = () => {
      this.expireThumbnails(urls);
    };

    // No registered filters.
    if (!filtersToWaitFor) {
      expire();
      return;
    }

    function filterCallback(aURLs) {
      urls = urls.concat(aURLs);
      if (--filtersToWaitFor == 0) {
        expire();
      }
    }

    for (let filter of this._filters) {
      if (typeof filter == "function") {
        filter(filterCallback);
      } else {
        filter.filterForThumbnailExpiration(filterCallback);
      }
    }
  },

  expireThumbnails: function Expiration_expireThumbnails(aURLsToKeep) {
    let keep = aURLsToKeep.map(url =>
      lazy.PageThumbsStorageService.getLeafNameForURL(url)
    );
    let msg = [
      lazy.PageThumbsStorageService.path,
      keep,
      EXPIRATION_MIN_CHUNK_SIZE,
    ];

    return PageThumbsWorker.post("expireFilesInDirectory", msg);
  },
};

/**
 * Interface to a dedicated thread handling I/O
 */
var PageThumbsWorker = new BasePromiseWorker(
  "resource://gre/modules/PageThumbs.worker.js"
);
PK
!<�Ύi��0modules/PartitioningExceptionListService.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
});

const COLLECTION_NAME = "partitioning-exempt-urls";
const PREF_NAME = "privacy.restrict3rdpartystorage.skip_list";

class Feature {
  constructor() {
    this.prefName = PREF_NAME;
    this.observers = new Set();
    this.prefValue = [];
    this.remoteEntries = [];

    if (this.prefName) {
      let prefValue = Services.prefs.getStringPref(this.prefName, null);
      this.prefValue = prefValue ? prefValue.split(";") : [];
      Services.prefs.addObserver(this.prefName, this);
    }
  }

  async addAndRunObserver(observer) {
    this.observers.add(observer);
    this.notifyObservers(observer);
  }

  removeObserver(observer) {
    this.observers.delete(observer);
  }

  observe(subject, topic, data) {
    if (topic != "nsPref:changed" || data != this.prefName) {
      console.error(`Unexpected event ${topic} with ${data}`);
      return;
    }

    let prefValue = Services.prefs.getStringPref(this.prefName, null);
    this.prefValue = prefValue ? prefValue.split(";") : [];
    this.notifyObservers();
  }

  onRemoteSettingsUpdate(entries) {
    this.remoteEntries = [];

    for (let entry of entries) {
      this.remoteEntries.push(
        `${entry.firstPartyOrigin},${entry.thirdPartyOrigin}`
      );
    }
  }

  notifyObservers(observer = null) {
    let entries = this.prefValue.concat(this.remoteEntries);
    let entriesAsString = entries.join(";").toLowerCase();
    if (observer) {
      observer.onExceptionListUpdate(entriesAsString);
    } else {
      for (let obs of this.observers) {
        obs.onExceptionListUpdate(entriesAsString);
      }
    }
  }
}

export function PartitioningExceptionListService() {}

PartitioningExceptionListService.prototype = {
  classID: Components.ID("{ab94809d-33f0-4f28-af38-01efbd3baf22}"),
  QueryInterface: ChromeUtils.generateQI([
    "nsIPartitioningExceptionListService",
  ]),

  _initialized: false,

  async lazyInit() {
    if (this._initialized) {
      return;
    }

    this.feature = new Feature();

    let rs = lazy.RemoteSettings(COLLECTION_NAME);
    rs.on("sync", event => {
      let {
        data: { current },
      } = event;
      this.onUpdateEntries(current);
    });

    this._initialized = true;

    let entries;
    // If the remote settings list hasn't been populated yet we have to make sure
    // to do it before firing the first notification.
    // This has to be run after _initialized is set because we'll be
    // blocked while getting entries from RemoteSetting, and we don't want
    // LazyInit is executed again.
    try {
      // The data will be initially available from the local DB (via a
      // resource:// URI).
      entries = await rs.get();
    } catch (e) {}

    // RemoteSettings.get() could return null, ensure passing a list to
    // onUpdateEntries.
    this.onUpdateEntries(entries || []);
  },

  onUpdateEntries(entries) {
    if (!this.feature) {
      return;
    }
    this.feature.onRemoteSettingsUpdate(entries);
    this.feature.notifyObservers();
  },

  registerAndRunExceptionListObserver(observer) {
    // We don't await this; the caller is C++ and won't await this function,
    // and because we prevent re-entering into this method, once it's been
    // called once any subsequent calls will early-return anyway - so
    // awaiting that would be meaningless. Instead, `Feature` implementations
    // make sure not to call into observers until they have data, and we
    // make sure to let feature instances know whether we have data
    // immediately.
    this.lazyInit();

    this.feature.addAndRunObserver(observer);
  },

  unregisterExceptionListObserver(observer) {
    if (!this.feature) {
      return;
    }
    this.feature.removeObserver(observer);
  },
};
PK
!<O�%��$modules/PasswordRulesManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
  PasswordGenerator: "resource://gre/modules/shared/PasswordGenerator.sys.mjs",
  PasswordRulesParser:
    "resource://gre/modules/shared/PasswordRulesParser.sys.mjs",
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  let logger = lazy.LoginHelper.createLogger("PasswordRulesManager");
  return logger.log.bind(logger);
});

const IMPROVED_PASSWORD_GENERATION_HISTOGRAM =
  "PWMGR_NUM_IMPROVED_GENERATED_PASSWORDS";

/**
 * Handles interactions between PasswordRulesParser and the "password-rules" Remote Settings collection
 *
 * @class PasswordRulesManagerParent
 * @extends {JSWindowActorParent}
 */
export class PasswordRulesManagerParent extends JSWindowActorParent {
  /**
   * @type RemoteSettingsClient
   *
   * @memberof PasswordRulesManagerParent
   */
  _passwordRulesClient = null;

  async initPasswordRulesCollection() {
    if (!this._passwordRulesClient) {
      this._passwordRulesClient = lazy.RemoteSettings(
        lazy.LoginHelper.improvedPasswordRulesCollection
      );
    }
  }
  /**
   * Transforms the parsed rules returned from PasswordRulesParser into a Map for easier access.
   * The returned Map could have the following keys: "allowed", "required", "maxlength", "minlength", and "max-consecutive"
   * @example
   * // Returns a Map with a key-value pair of "allowed": "ascii-printable"
   * _transformRulesToMap([{ _name: "allowed", value: [{ _name: "ascii-printable" }] }])
   * @param {Object[]} rules rules from PasswordRulesParser.parsePasswordRules
   * @return {Map} mapped rules
   * @memberof PasswordRulesManagerParent
   */
  _transformRulesToMap(rules) {
    let map = new Map();
    for (let rule of rules) {
      let { _name, value } = rule;
      if (
        _name === "minlength" ||
        _name === "maxlength" ||
        _name === "max-consecutive"
      ) {
        map.set(_name, value);
      } else {
        let _value = [];
        if (map.get(_name)) {
          _value = map.get(_name);
        }
        for (let _class of value) {
          let { _name: _className } = _class;
          if (_className) {
            _value.push(_className);
          } else {
            let { _characters } = _class;
            _value.push(_characters);
          }
        }
        map.set(_name, _value);
      }
    }
    return map;
  }

  /**
   * Generates a password based on rules from the origin parameters.
   * @param {nsIURI} uri
   * @return {string} password
   * @memberof PasswordRulesManagerParent
   */
  async generatePassword(uri, { inputMaxLength } = {}) {
    await this.initPasswordRulesCollection();
    let originDisplayHost = uri.displayHost;
    let records = await this._passwordRulesClient.get();
    let currentRecord;
    for (let record of records) {
      if (Services.eTLD.hasRootDomain(originDisplayHost, record.Domain)) {
        currentRecord = record;
        break;
      }
    }
    let isCustomRule = false;
    // If we found a matching result, use that to generate a stronger password.
    // Otherwise, generate a password using the default rules set.
    if (currentRecord?.Domain) {
      isCustomRule = true;
      lazy.log(
        `Password rules for ${currentRecord.Domain}:  ${currentRecord["password-rules"]}.`
      );
      let currentRules = lazy.PasswordRulesParser.parsePasswordRules(
        currentRecord["password-rules"]
      );
      let mapOfRules = this._transformRulesToMap(currentRules);
      Services.telemetry
        .getHistogramById(IMPROVED_PASSWORD_GENERATION_HISTOGRAM)
        .add(isCustomRule);
      return lazy.PasswordGenerator.generatePassword({
        rules: mapOfRules,
        inputMaxLength,
      });
    }
    lazy.log(
      `No password rules for specified origin, generating standard password.`
    );
    Services.telemetry
      .getHistogramById(IMPROVED_PASSWORD_GENERATION_HISTOGRAM)
      .add(isCustomRule);
    return lazy.PasswordGenerator.generatePassword({ inputMaxLength });
  }
}
PK
!<���y
y
 modules/PermissionsUtils.sys.mjs// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

var gImportedPrefBranches = new Set();

function importPrefBranch(aPrefBranch, aPermission, aAction) {
  let list = Services.prefs.getChildList(aPrefBranch);

  for (let pref of list) {
    let origins = Services.prefs.getCharPref(pref, "");

    if (!origins) {
      continue;
    }

    origins = origins.split(",");

    for (let origin of origins) {
      let principals = [];
      try {
        principals = [
          Services.scriptSecurityManager.createContentPrincipalFromOrigin(
            origin
          ),
        ];
      } catch (e) {
        // This preference used to contain a list of hosts. For back-compat
        // reasons, we convert these hosts into http:// and https:// permissions
        // on default ports.
        try {
          let httpURI = Services.io.newURI("http://" + origin);
          let httpsURI = Services.io.newURI("https://" + origin);

          principals = [
            Services.scriptSecurityManager.createContentPrincipal(httpURI, {}),
            Services.scriptSecurityManager.createContentPrincipal(httpsURI, {}),
          ];
        } catch (e2) {}
      }

      for (let principal of principals) {
        try {
          Services.perms.addFromPrincipal(principal, aPermission, aAction);
        } catch (e) {}
      }
    }

    Services.prefs.setCharPref(pref, "");
  }
}

export var PermissionsUtils = {
  /**
   * Import permissions from perferences to the Permissions Manager. After being
   * imported, all processed permissions will be set to an empty string.
   * Perferences are only processed once during the application's
   * lifetime - it's safe to call this multiple times without worrying about
   * doing unnecessary work, as the preferences branch will only be processed
   * the first time.
   *
   * @param aPrefBranch  Preferences branch to import from. The preferences
   *                     under this branch can specify whitelist (ALLOW_ACTION)
   *                     or blacklist (DENY_ACTION) additions using perference
   *                     names of the form:
   *                     * <BRANCH>.whitelist.add.<ID>
   *                     * <BRANCH>.blacklist.add.<ID>
   *                     Where <ID> can be any valid preference name.
   *                     The value is expected to be a comma separated list of
   *                     host named. eg:
   *                     * something.example.com
   *                     * foo.exmaple.com,bar.example.com
   *
   * @param aPermission Permission name to be passsed to the Permissions
   *                    Manager.
   */
  importFromPrefs(aPrefBranch, aPermission) {
    if (!aPrefBranch.endsWith(".")) {
      aPrefBranch += ".";
    }

    // Ensure we only import this pref branch once.
    if (gImportedPrefBranches.has(aPrefBranch)) {
      return;
    }

    importPrefBranch(
      aPrefBranch + "whitelist.add",
      aPermission,
      Services.perms.ALLOW_ACTION
    );
    importPrefBranch(
      aPrefBranch + "blacklist.add",
      aPermission,
      Services.perms.DENY_ACTION
    );

    gImportedPrefBranches.add(aPrefBranch);
  },
};

// For test use only.
export const PermissionsTestUtils = {
  clearImportedPrefBranches() {
    gImportedPrefBranches.clear();
  },
};
PK
!<�%>���� modules/PictureInPicture.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { ShortcutUtils } from "resource://gre/modules/ShortcutUtils.sys.mjs";

const lazy = {};
XPCOMUtils.defineLazyServiceGetters(lazy, {
  WindowsUIUtils: ["@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils"],
});

ChromeUtils.defineESModuleGetters(lazy, {
  ASRouter:
    // eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
    "resource:///modules/asrouter/ASRouter.sys.mjs",
  PageActions: "resource:///modules/PageActions.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

import { Rect, Point } from "resource://gre/modules/Geometry.sys.mjs";

const PLAYER_URI = "chrome://global/content/pictureinpicture/player.xhtml";
// Currently, we need titlebar="yes" on macOS in order for the player window
// to be resizable. See bug 1824171.
const TITLEBAR = AppConstants.platform == "macosx" ? "yes" : "no";
const PLAYER_FEATURES = `chrome,alwaysontop,lockaspectratio,resizable,dialog,titlebar=${TITLEBAR}`;

const WINDOW_TYPE = "Toolkit:PictureInPicture";
const TOGGLE_ENABLED_PREF =
  "media.videocontrols.picture-in-picture.video-toggle.enabled";
const TOGGLE_FIRST_SEEN_PREF =
  "media.videocontrols.picture-in-picture.video-toggle.first-seen-secs";
const TOGGLE_HAS_USED_PREF =
  "media.videocontrols.picture-in-picture.video-toggle.has-used";
const TOGGLE_POSITION_PREF =
  "media.videocontrols.picture-in-picture.video-toggle.position";
const TOGGLE_POSITION_RIGHT = "right";
const TOGGLE_POSITION_LEFT = "left";
const RESIZE_MARGIN_PX = 16;
const BACKGROUND_DURATION_HISTOGRAM_ID =
  "FX_PICTURE_IN_PICTURE_BACKGROUND_TAB_PLAYING_DURATION";
const FOREGROUND_DURATION_HISTOGRAM_ID =
  "FX_PICTURE_IN_PICTURE_FOREGROUND_TAB_PLAYING_DURATION";

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "PIP_ENABLED",
  "media.videocontrols.picture-in-picture.enabled",
  false
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "PIP_URLBAR_BUTTON",
  "media.videocontrols.picture-in-picture.urlbar-button.enabled",
  false
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "RESPECT_PIP_DISABLED",
  "media.videocontrols.picture-in-picture.respect-disablePictureInPicture",
  true
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "PIP_WHEN_SWITCHING_TABS",
  "media.videocontrols.picture-in-picture.enable-when-switching-tabs.enabled",
  true
);

/**
 * Tracks the number of currently open player windows for Telemetry tracking
 */
let gCurrentPlayerCount = 0;

/**
 * To differentiate windows in the Telemetry Event Log, each Picture-in-Picture
 * player window is given a unique ID.
 */
let gNextWindowID = 0;

export class PictureInPictureLauncherParent extends JSWindowActorParent {
  receiveMessage(aMessage) {
    switch (aMessage.name) {
      case "PictureInPicture:Request": {
        let videoData = aMessage.data;
        PictureInPicture.handlePictureInPictureRequest(this.manager, videoData);
        break;
      }
    }
  }
}

export class PictureInPictureToggleParent extends JSWindowActorParent {
  receiveMessage(aMessage) {
    let browsingContext = aMessage.target.browsingContext;
    let browser = browsingContext.top.embedderElement;
    switch (aMessage.name) {
      case "PictureInPicture:OpenToggleContextMenu": {
        let win = browser.ownerGlobal;
        PictureInPicture.openToggleContextMenu(win, aMessage.data);
        break;
      }
      case "PictureInPicture:UpdateEligiblePipVideoCount": {
        let { pipCount, pipDisabledCount } = aMessage.data;
        PictureInPicture.updateEligiblePipVideoCount(browsingContext, {
          pipCount,
          pipDisabledCount,
        });
        PictureInPicture.updateUrlbarToggle(browser);
        break;
      }
      case "PictureInPicture:SetFirstSeen": {
        let { dateSeconds } = aMessage.data;
        PictureInPicture.setFirstSeen(dateSeconds);
        break;
      }
      case "PictureInPicture:SetHasUsed": {
        let { hasUsed } = aMessage.data;
        PictureInPicture.setHasUsed(hasUsed);
        break;
      }
      case "PictureInPicture:VideoTabHidden": {
        if (!lazy.PIP_ENABLED || !lazy.PIP_WHEN_SWITCHING_TABS) {
          break;
        }
        // If the tab is still selected, then we can ignore this event
        if (browser.ownerGlobal.gBrowser.selectedBrowser == browser) {
          break;
        }
        let actor = browsingContext.currentWindowGlobal.getActor(
          "PictureInPictureLauncher"
        );
        actor.sendAsyncMessage("PictureInPicture:AutoToggle");
        break;
      }
      case "PictureInPicture:VideoTabShown": {
        if (!lazy.PIP_ENABLED || !lazy.PIP_WHEN_SWITCHING_TABS) {
          break;
        }
        if (browser.ownerGlobal.gBrowser.selectedBrowser != browser) {
          break;
        }
        for (let win of Services.wm.getEnumerator(WINDOW_TYPE)) {
          let originatingBrowser = PictureInPicture.weakWinToBrowser.get(win);
          if (browser == originatingBrowser) {
            win.closeFromForeground();
            break;
          }
        }
        break;
      }
    }
  }
}

/**
 * This module is responsible for creating a Picture in Picture window to host
 * a clone of a video element running in web content.
 */
export class PictureInPictureParent extends JSWindowActorParent {
  receiveMessage(aMessage) {
    switch (aMessage.name) {
      case "PictureInPicture:Resize": {
        let videoData = aMessage.data;
        PictureInPicture.resizePictureInPictureWindow(videoData, this);
        break;
      }
      case "PictureInPicture:Close": {
        /**
         * Content has requested that its Picture in Picture window go away.
         */
        let reason = aMessage.data.reason;
        PictureInPicture.closeSinglePipWindow({ reason, actorRef: this });
        break;
      }
      case "PictureInPicture:Playing": {
        let player = PictureInPicture.getWeakPipPlayer(this);
        if (player) {
          player.setIsPlayingState(true);
        }
        break;
      }
      case "PictureInPicture:Paused": {
        let player = PictureInPicture.getWeakPipPlayer(this);
        if (player) {
          player.setIsPlayingState(false);
        }
        break;
      }
      case "PictureInPicture:Muting": {
        let player = PictureInPicture.getWeakPipPlayer(this);
        if (player) {
          player.setIsMutedState(true);
        }
        break;
      }
      case "PictureInPicture:Unmuting": {
        let player = PictureInPicture.getWeakPipPlayer(this);
        if (player) {
          player.setIsMutedState(false);
        }
        break;
      }
      case "PictureInPicture:EnableSubtitlesButton": {
        let player = PictureInPicture.getWeakPipPlayer(this);
        if (player) {
          player.enableSubtitlesButton();
        }
        break;
      }
      case "PictureInPicture:DisableSubtitlesButton": {
        let player = PictureInPicture.getWeakPipPlayer(this);
        if (player) {
          player.disableSubtitlesButton();
        }
        break;
      }
      case "PictureInPicture:SetTimestampAndScrubberPosition": {
        let { timestamp, scrubberPosition } = aMessage.data;
        let player = PictureInPicture.getWeakPipPlayer(this);
        player.setTimestamp(timestamp);
        player.setScrubberPosition(scrubberPosition);
        break;
      }
      case "PictureInPicture:VolumeChange": {
        let { volume } = aMessage.data;
        let player = PictureInPicture.getWeakPipPlayer(this);
        player.setVolume(volume);
        break;
      }
    }
  }
}

/**
 * This module is responsible for creating a Picture in Picture window to host
 * a clone of a video element running in web content.
 */
export var PictureInPicture = {
  // Maps PictureInPictureParent actors to their corresponding PiP player windows
  weakPipToWin: new WeakMap(),

  // Maps PiP player windows to their originating content's browser
  weakWinToBrowser: new WeakMap(),

  // Maps a browser to the number of PiP windows it has
  browserWeakMap: new WeakMap(),

  // Maps an AppWindow to the number of PiP windows it has
  originatingWinWeakMap: new WeakMap(),

  // Maps a WindowGlobal to count of eligible PiP videos
  weakGlobalToEligiblePipCount: new WeakMap(),

  /**
   * Returns the player window if one exists and if it hasn't yet been closed.
   *
   * @param {PictureInPictureParent} pipActorRef
   *   Reference to the calling PictureInPictureParent actor
   *
   * @returns {Window} the player window if it exists and is not in the
   * process of being closed. Returns null otherwise.
   */
  getWeakPipPlayer(pipActorRef) {
    let playerWin = this.weakPipToWin.get(pipActorRef);
    if (!playerWin || playerWin.closed) {
      return null;
    }
    return playerWin;
  },

  /**
   * Get the PiP panel for a browser. Create the panel if needed.
   * @param {Browser} browser The current browser
   * @returns panel The panel element
   */
  getPanelForBrowser(browser) {
    let panel = browser.ownerDocument.querySelector("#PictureInPicturePanel");

    if (!panel) {
      let template = browser.ownerDocument.querySelector(
        "#PictureInPicturePanelTemplate"
      );
      let clone = template.content.cloneNode(true);
      template.replaceWith(clone);

      panel = this.getPanelForBrowser(browser);
    }
    return panel;
  },

  handleEvent(event) {
    switch (event.type) {
      case "TabSwapPictureInPicture": {
        this.onPipSwappedBrowsers(event);
        break;
      }
      case "TabSelect": {
        this.updatePlayingDurationHistograms();
        break;
      }
    }
  },

  /**
   * Increase the count of PiP windows for a given browser
   * @param browser The browser to increase PiP count in browserWeakMap
   */
  addPiPBrowserToWeakMap(browser) {
    let count = this.browserWeakMap.has(browser)
      ? this.browserWeakMap.get(browser)
      : 0;
    this.browserWeakMap.set(browser, count + 1);

    // If a browser is being added to the browserWeakMap, that means its
    // probably a good time to make sure the playing duration histograms
    // are up-to-date, as it means that we've either opened a new PiP
    // player window, or moved the originating tab to another window.
    this.updatePlayingDurationHistograms();
  },

  /**
   * Increase the count of PiP windows for a given AppWindow.
   *
   * @param {Browser} browser
   *   The content browser that the originating video lives in and from which
   *   we'll read its parent window to increase PiP window count in originatingWinWeakMap.
   */
  addOriginatingWinToWeakMap(browser) {
    let parentWin = browser.ownerGlobal;
    let count = this.originatingWinWeakMap.get(parentWin);
    if (!count || count == 0) {
      this.setOriginatingWindowActive(parentWin.browsingContext, true);
      this.originatingWinWeakMap.set(parentWin, 1);

      let gBrowser = browser.getTabBrowser();
      if (gBrowser) {
        gBrowser.tabContainer.addEventListener("TabSelect", this);
      }
    } else {
      this.originatingWinWeakMap.set(parentWin, count + 1);
    }
  },

  /**
   * Decrease the count of PiP windows for a given browser.
   * If the count becomes 0, we will remove the browser from the WeakMap
   * @param browser The browser to decrease PiP count in browserWeakMap
   */
  removePiPBrowserFromWeakMap(browser) {
    let count = this.browserWeakMap.get(browser);
    if (count <= 1) {
      this.browserWeakMap.delete(browser);
      let tabbrowser = browser.getTabBrowser();
      if (tabbrowser && !tabbrowser.shouldActivateDocShell(browser)) {
        browser.docShellIsActive = false;
      }
    } else {
      this.browserWeakMap.set(browser, count - 1);
    }
  },

  /**
   * Decrease the count of PiP windows for a given AppWindow.
   * If the count becomes 0, we will remove the AppWindow from the WeakMap.
   *
   * @param {Browser} browser
   *   The content browser that the originating video lives in and from which
   *   we'll read its parent window to decrease PiP window count in originatingWinWeakMap.
   */
  removeOriginatingWinFromWeakMap(browser) {
    let parentWin = browser?.ownerGlobal;

    if (!parentWin) {
      return;
    }

    let count = this.originatingWinWeakMap.get(parentWin);
    if (!count || count <= 1) {
      this.originatingWinWeakMap.delete(parentWin, 0);
      this.setOriginatingWindowActive(parentWin.browsingContext, false);

      let gBrowser = browser.getTabBrowser();
      if (gBrowser) {
        gBrowser.tabContainer.removeEventListener("TabSelect", this);
      }
    } else {
      this.originatingWinWeakMap.set(parentWin, count - 1);
    }
  },

  onPipSwappedBrowsers(event) {
    let otherTab = event.detail;
    if (otherTab) {
      for (let win of Services.wm.getEnumerator(WINDOW_TYPE)) {
        if (this.weakWinToBrowser.get(win) === event.target.linkedBrowser) {
          this.weakWinToBrowser.set(win, otherTab.linkedBrowser);
          this.removePiPBrowserFromWeakMap(event.target.linkedBrowser);
          this.removeOriginatingWinFromWeakMap(event.target.linkedBrowser);
          this.addPiPBrowserToWeakMap(otherTab.linkedBrowser);
          this.addOriginatingWinToWeakMap(otherTab.linkedBrowser);
        }
      }
      otherTab.addEventListener("TabSwapPictureInPicture", this);
    }
  },

  updatePlayingDurationHistograms() {
    // A tab switch occurred in a browser window with one more tabs that have
    // PiP player windows associated with them.
    for (let win of Services.wm.getEnumerator(WINDOW_TYPE)) {
      let browser = this.weakWinToBrowser.get(win);
      let gBrowser = browser.getTabBrowser();
      if (gBrowser?.selectedBrowser == browser) {
        // If there are any background stopwatches running for this window, finish
        // them and switch to foreground.
        if (TelemetryStopwatch.running(BACKGROUND_DURATION_HISTOGRAM_ID, win)) {
          TelemetryStopwatch.finish(BACKGROUND_DURATION_HISTOGRAM_ID, win);
        }
        if (
          !TelemetryStopwatch.running(FOREGROUND_DURATION_HISTOGRAM_ID, win)
        ) {
          TelemetryStopwatch.start(FOREGROUND_DURATION_HISTOGRAM_ID, win, {
            inSeconds: true,
          });
        }
      } else {
        // If there are any foreground stopwatches running for this window, finish
        // them and switch to background.
        if (TelemetryStopwatch.running(FOREGROUND_DURATION_HISTOGRAM_ID, win)) {
          TelemetryStopwatch.finish(FOREGROUND_DURATION_HISTOGRAM_ID, win);
        }

        if (
          !TelemetryStopwatch.running(BACKGROUND_DURATION_HISTOGRAM_ID, win)
        ) {
          TelemetryStopwatch.start(BACKGROUND_DURATION_HISTOGRAM_ID, win, {
            inSeconds: true,
          });
        }
      }
    }
  },

  /**
   * Called when the browser UI handles the View:PictureInPicture command via
   * the keyboard.
   *
   * @param {Event} event
   */
  onCommand(event) {
    if (!lazy.PIP_ENABLED) {
      return;
    }

    let win = event.target.ownerGlobal;
    let bc = Services.focus.focusedContentBrowsingContext;
    if (bc.top == win.gBrowser.selectedBrowser.browsingContext) {
      let actor = bc.currentWindowGlobal.getActor("PictureInPictureLauncher");
      actor.sendAsyncMessage("PictureInPicture:KeyToggle");
    }
  },

  async focusTabAndClosePip(window, pipActor) {
    let browser = this.weakWinToBrowser.get(window);
    if (!browser) {
      return;
    }

    let gBrowser = browser.getTabBrowser();
    let tab = gBrowser.getTabForBrowser(browser);

    // focus the tab's window
    tab.ownerGlobal.focus();

    gBrowser.selectedTab = tab;
    await this.closeSinglePipWindow({ reason: "unpip", actorRef: pipActor });
  },

  /**
   * Update the respect PiPDisabled pref value when the toggle is clicked.
   * @param {Event} event The event from toggling the respect
   *   PiPDisabled in the PiP panel
   */
  toggleRespectDisablePip(event) {
    let toggle = event.target;
    let respectPipDisabled = !toggle.pressed;

    Services.prefs.setBoolPref(
      "media.videocontrols.picture-in-picture.respect-disablePictureInPicture",
      respectPipDisabled
    );

    Services.telemetry.recordEvent(
      "pictureinpicture",
      "disrespect_disable",
      "urlBar"
    );
  },

  /**
   * Updates the PiP count and PiPDisabled count of eligible PiP videos for a
   * respective WindowGlobal.
   * @param {BrowsingContext} browsingContext The BrowsingContext with eligible videos
   * @param {Object} object
   *    pipCount: The number of eligible videos for the respective WindowGlobal
   *    pipDisabledCount: The number of disablePiP videos for the respective WindowGlobal
   */
  updateEligiblePipVideoCount(browsingContext, object) {
    let windowGlobal = browsingContext.currentWindowGlobal;

    if (windowGlobal) {
      this.weakGlobalToEligiblePipCount.set(windowGlobal, object);
    }
  },

  /**
   * A generator function that yeilds a WindowGlobal, it's respective PiP
   * count, and if any of the videos have PiPDisabled set.
   * @param {Browser} browser The selected browser
   */
  *windowGlobalPipCountGenerator(browser) {
    let contextsToVisit = [browser.browsingContext];
    while (contextsToVisit.length) {
      let currentBC = contextsToVisit.pop();
      let windowGlobal = currentBC.currentWindowGlobal;

      if (!windowGlobal) {
        continue;
      }

      let { pipCount, pipDisabledCount } =
        this.weakGlobalToEligiblePipCount.get(windowGlobal) || {
          pipCount: 0,
          pipDisabledCount: 0,
        };

      contextsToVisit.push(...currentBC.children);

      yield { windowGlobal, pipCount, pipDisabledCount };
    }
  },

  /**
   * Gets the total eligible video count and total PiPDisabled count for a
   * given browser.
   * @param {Browser} browser The selected browser
   * @returns Total count of eligible PiP videos for the selected broser
   */
  getEligiblePipVideoCount(browser) {
    let totalPipCount = 0;
    let totalPipDisabled = 0;

    for (let {
      pipCount,
      pipDisabledCount,
    } of this.windowGlobalPipCountGenerator(browser)) {
      totalPipCount += pipCount;
      totalPipDisabled += pipDisabledCount;
    }

    return { totalPipCount, totalPipDisabled };
  },

  /**
   * This function updates the hover text on the urlbar PiP button when we enter or exit PiP
   * @param {Document} document The window document
   * @param {Element} pipToggle The urlbar PiP button
   * @param {String} dataL10nId The data l10n id of the string we want to show
   */
  updateUrlbarHoverText(document, pipToggle, dataL10nId) {
    let shortcut = document.getElementById("key_togglePictureInPicture");

    document.l10n.setAttributes(pipToggle, dataL10nId, {
      shortcut: ShortcutUtils.prettifyShortcut(shortcut),
    });
  },

  /**
   * Toggles the visibility of the PiP urlbar button. If the total video count
   * is 1, then we will show the button. If any eligible video has PiPDisabled,
   * then the button will show. Otherwise the button is hidden.
   * @param {Browser} browser The selected browser
   */
  updateUrlbarToggle(browser) {
    if (!lazy.PIP_ENABLED || !lazy.PIP_URLBAR_BUTTON) {
      return;
    }

    let win = browser.ownerGlobal;
    if (win.closed || win.gBrowser?.selectedBrowser !== browser) {
      return;
    }

    let { totalPipCount, totalPipDisabled } =
      this.getEligiblePipVideoCount(browser);

    let pipToggle = win.document.getElementById("picture-in-picture-button");
    if (
      totalPipCount === 1 ||
      (totalPipDisabled > 0 && lazy.RESPECT_PIP_DISABLED)
    ) {
      pipToggle.hidden = false;
      lazy.PageActions.sendPlacedInUrlbarTrigger(pipToggle);
    } else {
      pipToggle.hidden = true;
    }

    let browserHasPip = !!this.browserWeakMap.get(browser);
    if (browserHasPip) {
      this.setUrlbarPipIconActive(browser.ownerGlobal);
    } else {
      this.setUrlbarPipIconInactive(browser.ownerGlobal);
    }
  },

  /**
   * Open the PiP panel if any video has PiPDisabled, otherwise finds the
   * correct WindowGlobal to open the eligible PiP video.
   * @param {Event} event Event from clicking the PiP urlbar button
   */
  toggleUrlbar(event) {
    if (event.button !== 0) {
      return;
    }

    let win = event.target.ownerGlobal;
    let browser = win.gBrowser.selectedBrowser;

    let pipPanel = this.getPanelForBrowser(browser);

    for (let {
      windowGlobal,
      pipCount,
      pipDisabledCount,
    } of this.windowGlobalPipCountGenerator(browser)) {
      if (
        (pipDisabledCount > 0 && lazy.RESPECT_PIP_DISABLED) ||
        (pipPanel && pipPanel.state !== "closed")
      ) {
        this.togglePipPanel(browser);
        return;
      } else if (pipCount === 1) {
        let eventExtraKeys = {};
        if (
          !Services.prefs.getBoolPref(TOGGLE_HAS_USED_PREF) &&
          lazy.ASRouter.initialized
        ) {
          let { messages, messageImpressions } = lazy.ASRouter.state;
          let pipCallouts = messages.filter(
            message =>
              message.template === "feature_callout" &&
              message.content.screens.some(screen =>
                screen.anchors.some(anchor =>
                  anchor.selector.includes("picture-in-picture-button")
                )
              )
          );
          if (pipCallouts.length) {
            // Has one of the callouts been seen in the last 48 hours?
            let now = Date.now();
            let callout = pipCallouts.some(message =>
              messageImpressions[message.id]?.some(
                impression => now - impression < 48 * 60 * 60 * 1000
              )
            );
            if (callout) {
              eventExtraKeys.callout = "true";
            }
          }
        }
        let actor = windowGlobal.getActor("PictureInPictureToggle");
        actor.sendAsyncMessage("PictureInPicture:UrlbarToggle", eventExtraKeys);
        return;
      }
    }
  },

  /**
   * Set the toggle for PiPDisabled when the panel is shown.
   * If the pref is set from about:config, we need to update
   * the toggle switch in the panel to match the pref.
   * @param {Event} event The panel shown event
   */
  onPipPanelShown(event) {
    let toggle = event.target.querySelector("#respect-pipDisabled-switch");
    toggle.pressed = !lazy.RESPECT_PIP_DISABLED;
  },

  /**
   * Update the visibility of the urlbar PiP button when the panel is hidden.
   * The button will show when there is more than 1 video and at least 1 video
   * has PiPDisabled. If we no longer want to respect PiPDisabled then we
   * need to check if the urlbar button should still be visible.
   * @param {Event} event The panel hidden event
   */
  onPipPanelHidden(event) {
    this.updateUrlbarToggle(event.view.gBrowser.selectedBrowser);
  },

  /**
   * Create the PiP panel if needed and toggle the display of the panel
   * @param {Browser} browser The current browser
   */
  togglePipPanel(browser) {
    let pipPanel = this.getPanelForBrowser(browser);

    if (pipPanel.state === "closed") {
      let anchor = browser.ownerDocument.querySelector(
        "#picture-in-picture-button"
      );

      pipPanel.openPopup(anchor, "bottomright topright");
      Services.telemetry.recordEvent(
        "pictureinpicture",
        "opened_method",
        "urlBar",
        null,
        { disableDialog: "true" }
      );
    } else {
      pipPanel.hidePopup();
    }
  },

  /**
   * Sets the PiP urlbar to an active state. This changes the icon in the
   * urlbar button to the unpip icon.
   * @param {Window} win The current Window
   */
  setUrlbarPipIconActive(win) {
    let pipToggle = win.document.getElementById("picture-in-picture-button");
    pipToggle.toggleAttribute("pipactive", true);

    this.updateUrlbarHoverText(
      win.document,
      pipToggle,
      "picture-in-picture-urlbar-button-close"
    );
  },

  /**
   * Sets the PiP urlbar to an inactive state. This changes the icon in the
   * urlbar button to the open pip icon.
   * @param {Window} win The current window
   */
  setUrlbarPipIconInactive(win) {
    if (!win) {
      return;
    }
    let pipToggle = win.document.getElementById("picture-in-picture-button");
    pipToggle.toggleAttribute("pipactive", false);

    this.updateUrlbarHoverText(
      win.document,
      pipToggle,
      "picture-in-picture-urlbar-button-open"
    );
  },

  /**
   * Remove attribute which enables pip icon in tab
   *
   * @param {Window} window
   *   A PictureInPicture player's window, used to resolve the player's
   *   associated originating content browser
   */
  clearPipTabIcon(window) {
    const browser = this.weakWinToBrowser.get(window);
    if (!browser) {
      return;
    }

    // see if no other pip windows are open for this content browser
    for (let win of Services.wm.getEnumerator(WINDOW_TYPE)) {
      if (
        win !== window &&
        this.weakWinToBrowser.has(win) &&
        this.weakWinToBrowser.get(win) === browser
      ) {
        return;
      }
    }

    let gBrowser = browser.getTabBrowser();
    let tab = gBrowser?.getTabForBrowser(browser);
    if (tab) {
      tab.removeAttribute("pictureinpicture");
    }
  },

  /**
   * Closes and waits for passed PiP player window to finish closing.
   *
   * @param {Window} pipWin
   *   Player window to close
   */
  async closePipWindow(pipWin) {
    if (pipWin.closed) {
      return;
    }
    let closedPromise = new Promise(resolve => {
      pipWin.addEventListener("unload", resolve, { once: true });
    });
    pipWin.close();
    await closedPromise;
  },

  /**
   * Closes a single PiP window. Used exclusively in conjunction with support
   * for multiple PiP windows
   *
   * @param {Object} closeData
   *   Additional data required to complete a close operation on a PiP window
   * @param {PictureInPictureParent} closeData.actorRef
   *   The PictureInPictureParent actor associated with the PiP window being closed
   * @param {string} closeData.reason
   *   The reason for closing this PiP window
   */
  async closeSinglePipWindow(closeData) {
    const { reason, actorRef } = closeData;
    const win = this.getWeakPipPlayer(actorRef);
    if (!win) {
      return;
    }
    this.removePiPBrowserFromWeakMap(this.weakWinToBrowser.get(win));

    Services.telemetry.recordEvent(
      "pictureinpicture",
      "closed_method",
      reason,
      null
    );
    await this.closePipWindow(win);
  },

  /**
   * A request has come up from content to open a Picture in Picture
   * window.
   *
   * @param {WindowGlobalParent} wgps
   *   The WindowGlobalParent that is requesting the Picture in Picture
   *   window.
   *
   * @param {object} videoData
   *   An object containing the following properties:
   *
   *   videoHeight (int):
   *     The preferred height of the video.
   *
   *   videoWidth (int):
   *     The preferred width of the video.
   *
   * @returns {Promise}
   *   Resolves once the Picture in Picture window has been created, and
   *   the player component inside it has finished loading.
   */
  async handlePictureInPictureRequest(wgp, videoData) {
    gCurrentPlayerCount += 1;

    Services.telemetry.scalarSetMaximum(
      "pictureinpicture.most_concurrent_players",
      gCurrentPlayerCount
    );

    let browser = wgp.browsingContext.top.embedderElement;
    let parentWin = browser.ownerGlobal;

    let win = await this.openPipWindow(parentWin, videoData);
    win.setIsPlayingState(videoData.playing);
    win.setIsMutedState(videoData.isMuted);

    // set attribute which shows pip icon in tab
    let tab = parentWin.gBrowser.getTabForBrowser(browser);
    tab.setAttribute("pictureinpicture", true);

    this.setUrlbarPipIconActive(parentWin);

    tab.addEventListener("TabSwapPictureInPicture", this);

    let pipId = gNextWindowID.toString();
    win.setupPlayer(pipId, wgp, videoData.videoRef, videoData.autoFocus);
    gNextWindowID++;

    this.weakWinToBrowser.set(win, browser);
    this.addPiPBrowserToWeakMap(browser);
    this.addOriginatingWinToWeakMap(browser);

    win.setScrubberPosition(videoData.scrubberPosition);
    win.setTimestamp(videoData.timestamp);
    win.setVolume(videoData.volume);

    Services.prefs.setBoolPref(TOGGLE_HAS_USED_PREF, true);

    let args = {
      width: win.innerWidth.toString(),
      height: win.innerHeight.toString(),
      screenX: win.screenX.toString(),
      screenY: win.screenY.toString(),
      ccEnabled: videoData.ccEnabled.toString(),
      webVTTSubtitles: videoData.webVTTSubtitles.toString(),
    };

    Services.telemetry.recordEvent(
      "pictureinpicture",
      "create",
      "player",
      pipId,
      args
    );
  },

  /**
   * Calls the browsingContext's `forceAppWindowActive` flag to determine if the
   * the top level chrome browsingContext should be forcefully set as active or not.
   * When the originating window's browsing context is set to active, captions on the
   * PiP window are properly updated. Forcing active while a PiP window is open ensures
   * that captions are still updated when the originating window is occluded.
   *
   * @param {BrowsingContext} browsingContext
   *   The browsing context of the originating window
   * @param {boolean} isActive
   *   True to force originating window as active, or false to not enforce it
   * @see CanonicalBrowsingContext
   */
  setOriginatingWindowActive(browsingContext, isActive) {
    browsingContext.forceAppWindowActive = isActive;
  },

  /**
   * unload event has been called in player.js, cleanup our preserved
   * browser object.
   *
   * @param {Window} window
   */
  unload(window) {
    TelemetryStopwatch.finish(
      "FX_PICTURE_IN_PICTURE_WINDOW_OPEN_DURATION",
      window
    );

    if (TelemetryStopwatch.running(BACKGROUND_DURATION_HISTOGRAM_ID, window)) {
      TelemetryStopwatch.finish(BACKGROUND_DURATION_HISTOGRAM_ID, window);
    } else if (
      TelemetryStopwatch.running(FOREGROUND_DURATION_HISTOGRAM_ID, window)
    ) {
      TelemetryStopwatch.finish(FOREGROUND_DURATION_HISTOGRAM_ID, window);
    }

    let browser = this.weakWinToBrowser.get(window);
    this.removeOriginatingWinFromWeakMap(browser);

    gCurrentPlayerCount -= 1;
    // Saves the location of the Picture in Picture window
    this.savePosition(window);
    this.clearPipTabIcon(window);
    this.setUrlbarPipIconInactive(browser?.ownerGlobal);
  },

  /**
   * Open a Picture in Picture window on the same screen as parentWin,
   * sized based on the information in videoData.
   *
   * @param {ChromeWindow} parentWin
   *   The window hosting the browser that requested the Picture in
   *   Picture window.
   *
   * @param {object} videoData
   *   An object containing the following properties:
   *
   *   videoHeight (int):
   *     The preferred height of the video.
   *
   *   videoWidth (int):
   *     The preferred width of the video.
   *
   * @param {PictureInPictureParent} actorReference
   *   Reference to the calling PictureInPictureParent
   *
   * @returns {Promise}
   *   Resolves once the window has opened and loaded the player component.
   */
  async openPipWindow(parentWin, videoData) {
    let { top, left, width, height } = this.fitToScreen(parentWin, videoData);

    let { left: resolvedLeft, top: resolvedTop } = this.resolveOverlapConflicts(
      left,
      top,
      width,
      height
    );

    top = Math.round(resolvedTop);
    left = Math.round(resolvedLeft);
    width = Math.round(width);
    height = Math.round(height);

    let features =
      `${PLAYER_FEATURES},top=${top},left=${left},outerWidth=${width},` +
      `outerHeight=${height}`;
    let isPrivate = lazy.PrivateBrowsingUtils.isWindowPrivate(parentWin);

    if (isPrivate) {
      features += ",private";
    }

    let pipWindow = Services.ww.openWindow(
      parentWin,
      PLAYER_URI,
      null,
      features,
      null
    );

    TelemetryStopwatch.start(
      "FX_PICTURE_IN_PICTURE_WINDOW_OPEN_DURATION",
      pipWindow,
      {
        inSeconds: true,
      }
    );

    pipWindow.windowUtils.setResizeMargin(RESIZE_MARGIN_PX);

    // If the window is Private the icon will have already been set when
    // it was opened.
    if (Services.appinfo.OS == "WINNT" && !isPrivate) {
      lazy.WindowsUIUtils.setWindowIconNoData(pipWindow);
    }

    return new Promise(resolve => {
      pipWindow.addEventListener(
        "load",
        () => {
          resolve(pipWindow);
        },
        { once: true }
      );
    });
  },

  /**
   * This function tries to restore the last known Picture-in-Picture location
   * and size. If those values are unknown or offscreen, then a default
   * location and size is used.
   *
   * @param {ChromeWindow|PlayerWindow} requestingWin
   *   The window hosting the browser that requested the Picture in
   *   Picture window. If this is an existing player window then the returned
   *   player size and position will be determined based on the existing
   *   player window's size and position.
   *
   * @param {object} videoData
   *   An object containing the following properties:
   *
   *   videoHeight (int):
   *     The preferred height of the video.
   *
   *   videoWidth (int):
   *     The preferred width of the video.
   *
   * @returns {object}
   *   The size and position for the player window, in CSS pixels relative to
   *   requestingWin.
   *
   *   top (int):
   *     The top position for the player window.
   *
   *   left (int):
   *     The left position for the player window.
   *
   *   width (int):
   *     The width of the player window.
   *
   *   height (int):
   *     The height of the player window.
   */
  fitToScreen(requestingWin, videoData) {
    let { videoHeight, videoWidth } = videoData;

    const isPlayer = requestingWin.document.location.href == PLAYER_URI;

    let requestingCssToDesktopScale =
      requestingWin.devicePixelRatio / requestingWin.desktopToDeviceScale;

    let top, left, width, height;
    if (!isPlayer) {
      // requestingWin is a content window, load last PiP's dimensions
      ({ top, left, width, height } = this.loadPosition());
    } else if (requestingWin.windowState === requestingWin.STATE_FULLSCREEN) {
      // `requestingWin` is a PiP window and in fullscreen. We stored the size
      // and position before entering fullscreen and we will use that to
      // calculate the new position
      ({ top, left, width, height } = requestingWin.getDeferredResize());
      left *= requestingCssToDesktopScale;
      top *= requestingCssToDesktopScale;
    } else {
      // requestingWin is a PiP player, conserve its dimensions in this case
      left = requestingWin.screenX * requestingCssToDesktopScale;
      top = requestingWin.screenY * requestingCssToDesktopScale;
      width = requestingWin.outerWidth;
      height = requestingWin.outerHeight;
    }

    // Check that previous location and size were loaded.
    // Note that at this point left and top are in desktop pixels, while width
    // and height are in CSS pixels.
    if (!isNaN(top) && !isNaN(left) && !isNaN(width) && !isNaN(height)) {
      // Get the screen of the last PiP window. PiP screen will be the default
      // screen if the point was not on a screen.
      let PiPScreen = this.getWorkingScreen(left, top);

      // Center position of PiP window.
      let PipScreenCssToDesktopScale =
        PiPScreen.defaultCSSScaleFactor / PiPScreen.contentsScaleFactor;
      let centerX = left + (width * PipScreenCssToDesktopScale) / 2;
      let centerY = top + (height * PipScreenCssToDesktopScale) / 2;

      // We have the screen, now we will get the dimensions of the screen
      let [PiPScreenLeft, PiPScreenTop, PiPScreenWidth, PiPScreenHeight] =
        this.getAvailScreenSize(PiPScreen);

      // Check that the center of the last PiP location is within the screen limits
      // If it's not, then we will use the default size and position
      if (
        PiPScreenLeft <= centerX &&
        centerX <= PiPScreenLeft + PiPScreenWidth &&
        PiPScreenTop <= centerY &&
        centerY <= PiPScreenTop + PiPScreenHeight
      ) {
        let oldWidthDesktopPix = width * PipScreenCssToDesktopScale;

        // The new PiP window will keep the height of the old
        // PiP window and adjust the width to the correct ratio
        width = Math.round((height * videoWidth) / videoHeight);

        // Minimum window size on Windows is 136
        if (AppConstants.platform == "win") {
          width = 136 > width ? 136 : width;
        }

        let widthDesktopPix = width * PipScreenCssToDesktopScale;
        let heightDesktopPix = height * PipScreenCssToDesktopScale;

        // WIGGLE_ROOM allows the PiP window to be within 5 pixels of the right
        // side of the screen to stay snapped to the right side
        const WIGGLE_ROOM = 5;
        // If the PiP window was right next to the right side of the screen
        // then move the PiP window to the right the same distance that
        // the width changes from previous width to current width
        let rightScreen = PiPScreenLeft + PiPScreenWidth;
        let distFromRight = rightScreen - (left + widthDesktopPix);
        if (
          0 < distFromRight &&
          distFromRight <= WIGGLE_ROOM + (oldWidthDesktopPix - widthDesktopPix)
        ) {
          left += distFromRight;
        }

        // Checks if some of the PiP window is off screen and
        // if so it will adjust to move everything on screen
        if (left < PiPScreenLeft) {
          // off the left of the screen
          // slide right
          left = PiPScreenLeft;
        }
        if (top < PiPScreenTop) {
          // off the top of the screen
          // slide down
          top = PiPScreenTop;
        }
        if (left + widthDesktopPix > PiPScreenLeft + PiPScreenWidth) {
          // off the right of the screen
          // slide left
          left = PiPScreenLeft + PiPScreenWidth - widthDesktopPix;
        }
        if (top + heightDesktopPix > PiPScreenTop + PiPScreenHeight) {
          // off the bottom of the screen
          // slide up
          top = PiPScreenTop + PiPScreenHeight - heightDesktopPix;
        }
        // Convert top / left from desktop to requestingWin-relative CSS pixels.
        top /= requestingCssToDesktopScale;
        left /= requestingCssToDesktopScale;
        return { top, left, width, height };
      }
    }

    // We don't have the size or position of the last PiP window, so fall
    // back to calculating the default location.
    let screen = this.getWorkingScreen(
      requestingWin.screenX * requestingCssToDesktopScale,
      requestingWin.screenY * requestingCssToDesktopScale,
      requestingWin.outerWidth * requestingCssToDesktopScale,
      requestingWin.outerHeight * requestingCssToDesktopScale
    );
    let [screenLeft, screenTop, screenWidth, screenHeight] =
      this.getAvailScreenSize(screen);

    let screenCssToDesktopScale =
      screen.defaultCSSScaleFactor / screen.contentsScaleFactor;

    // The Picture in Picture window will be a maximum of a quarter of
    // the screen height, and a third of the screen width.
    const MAX_HEIGHT = screenHeight / 4;
    const MAX_WIDTH = screenWidth / 3;

    width = videoWidth * screenCssToDesktopScale;
    height = videoHeight * screenCssToDesktopScale;
    let aspectRatio = videoWidth / videoHeight;

    if (videoHeight > MAX_HEIGHT || videoWidth > MAX_WIDTH) {
      // We're bigger than the max.
      // Take the largest dimension and clamp it to the associated max.
      // Recalculate the other dimension to maintain aspect ratio.
      if (videoWidth >= videoHeight) {
        // We're clamping the width, so the height must be adjusted to match
        // the original aspect ratio. Since aspect ratio is width over height,
        // that means we need to _divide_ the MAX_WIDTH by the aspect ratio to
        // calculate the appropriate height.
        width = MAX_WIDTH;
        height = Math.round(MAX_WIDTH / aspectRatio);
      } else {
        // We're clamping the height, so the width must be adjusted to match
        // the original aspect ratio. Since aspect ratio is width over height,
        // this means we need to _multiply_ the MAX_HEIGHT by the aspect ratio
        // to calculate the appropriate width.
        height = MAX_HEIGHT;
        width = Math.round(MAX_HEIGHT * aspectRatio);
      }
    }

    // Now that we have the dimensions of the video, we need to figure out how
    // to position it in the bottom right corner. Since we know the width of the
    // available rect, we need to subtract the dimensions of the window we're
    // opening to get the top left coordinates that openWindow expects.
    //
    // In event that the user has multiple displays connected, we have to
    // calculate the top-left coordinate of the new window in absolute
    // coordinates that span the entire display space, since this is what the
    // openWindow expects for its top and left feature values.
    //
    // The screenWidth and screenHeight values only tell us the available
    // dimensions on the screen that the parent window is on. We add these to
    // the screenLeft and screenTop values, which tell us where this screen is
    // located relative to the "origin" in absolute coordinates.
    let isRTL = Services.locale.isAppLocaleRTL;
    left = isRTL ? screenLeft : screenLeft + screenWidth - width;
    top = screenTop + screenHeight - height;

    // Convert top/left from desktop pixels to requestingWin-relative CSS
    // pixels, and width / height to the target screen's CSS pixels, which is
    // what we've made the size calculation against.
    top /= requestingCssToDesktopScale;
    left /= requestingCssToDesktopScale;
    width /= screenCssToDesktopScale;
    height /= screenCssToDesktopScale;

    return { top, left, width, height };
  },

  /**
   * This function will take the size and potential location of a new
   * Picture-in-Picture player window, and try to return the location
   * coordinates that will best ensure that the player window will not overlap
   * with other pre-existing player windows.
   *
   * @param {int} left
   *  x position of left edge for Picture-in-Picture window that is being
   *  opened
   * @param {int} top
   *  y position of top edge for Picture-in-Picture window that is being
   *  opened
   * @param {int} width
   *  Width of Picture-in-Picture window that is being opened
   * @param {int} height
   *  Height of Picture-in-Picture window that is being opened
   *
   * @returns {object}
   *  An object with the following properties:
   *
   *   top (int):
   *     The recommended top position for the player window.
   *
   *   left (int):
   *     The recommended left position for the player window.
   */
  resolveOverlapConflicts(left, top, width, height) {
    // This algorithm works by first identifying the possible candidate
    // locations that the new player window could be placed without overlapping
    // other player windows (assuming that a confict is discovered at all of
    // course). The optimal candidate is then selected by its distance to the
    // original conflict, shorter distances are better.
    //
    // Candidates are discovered by iterating over each of the sides of every
    // pre-existing player window. One candidate is collected for each side.
    // This is done to ensure that the new player window will be opened to
    // tightly fit along the edge of another player window.
    //
    // These candidates are then pruned for candidates that will introduce
    // further conflicts. Finally the ideal candidate is selected from this
    // pool of remaining candidates, optimized for minimizing distance to
    // the original conflict.
    let playerRects = [];

    for (let playerWin of Services.wm.getEnumerator(WINDOW_TYPE)) {
      playerRects.push(
        new Rect(
          playerWin.screenX,
          playerWin.screenY,
          playerWin.outerWidth,
          playerWin.outerHeight
        )
      );
    }

    const newPlayerRect = new Rect(left, top, width, height);
    let conflictingPipRect = playerRects.find(rect =>
      rect.intersects(newPlayerRect)
    );

    if (!conflictingPipRect) {
      // no conflicts found
      return { left, top };
    }

    const conflictLoc = conflictingPipRect.center();

    // Will try to resolve a better placement only on the screen where
    // the conflict occurred
    const conflictScreen = this.getWorkingScreen(conflictLoc.x, conflictLoc.y);

    const [screenTop, screenLeft, screenWidth, screenHeight] =
      this.getAvailScreenSize(conflictScreen);

    const screenRect = new Rect(
      screenTop,
      screenLeft,
      screenWidth,
      screenHeight
    );

    const getEdgeCandidates = rect => {
      return [
        // left edge's candidate
        new Point(rect.left - newPlayerRect.width, rect.top),
        // top edge's candidate
        new Point(rect.left, rect.top - newPlayerRect.height),
        // right edge's candidate
        new Point(rect.right + newPlayerRect.width, rect.top),
        // bottom edge's candidate
        new Point(rect.left, rect.bottom),
      ];
    };

    let candidateLocations = [];
    for (const playerRect of playerRects) {
      for (let candidateLoc of getEdgeCandidates(playerRect)) {
        const candidateRect = new Rect(
          candidateLoc.x,
          candidateLoc.y,
          width,
          height
        );

        if (!screenRect.contains(candidateRect)) {
          continue;
        }

        // test that no PiPs conflict with this candidate box
        if (playerRects.some(rect => rect.intersects(candidateRect))) {
          continue;
        }

        const candidateCenter = candidateRect.center();
        const candidateDistanceToConflict =
          Math.abs(conflictLoc.x - candidateCenter.x) +
          Math.abs(conflictLoc.y - candidateCenter.y);

        candidateLocations.push({
          distanceToConflict: candidateDistanceToConflict,
          location: candidateLoc,
        });
      }
    }

    if (!candidateLocations.length) {
      // if no suitable candidates can be found, return the original location
      return { left, top };
    }

    // sort candidates by distance to the conflict, select the closest
    const closestCandidate = candidateLocations.sort(
      (firstCand, secondCand) =>
        firstCand.distanceToConflict - secondCand.distanceToConflict
    )[0];

    if (!closestCandidate) {
      // can occur if there were no valid candidates, return original location
      return { left, top };
    }

    const resolvedX = closestCandidate.location.x;
    const resolvedY = closestCandidate.location.y;

    return { left: resolvedX, top: resolvedY };
  },

  /**
   * Resizes the the PictureInPicture player window.
   *
   * @param {object} videoData
   *    The source video's data.
   * @param {PictureInPictureParent} actorRef
   *    Reference to the PictureInPicture parent actor.
   */
  resizePictureInPictureWindow(videoData, actorRef) {
    let win = this.getWeakPipPlayer(actorRef);

    if (!win) {
      return;
    }

    win.resizeToVideo(this.fitToScreen(win, videoData));
  },

  /**
   * Opens the context menu for toggling PictureInPicture.
   *
   * @param {Window} window
   * @param {object} data
   *  Message data from the PictureInPictureToggleParent
   */
  openToggleContextMenu(window, data) {
    let document = window.document;
    let popup = document.getElementById("pictureInPictureToggleContextMenu");
    let contextMoveToggle = document.getElementById(
      "context_MovePictureInPictureToggle"
    );

    // Set directional string for toggle position
    let position = Services.prefs.getStringPref(
      TOGGLE_POSITION_PREF,
      TOGGLE_POSITION_RIGHT
    );
    switch (position) {
      case TOGGLE_POSITION_RIGHT:
        document.l10n.setAttributes(
          contextMoveToggle,
          "picture-in-picture-move-toggle-left"
        );
        break;
      case TOGGLE_POSITION_LEFT:
        document.l10n.setAttributes(
          contextMoveToggle,
          "picture-in-picture-move-toggle-right"
        );
        break;
    }

    // We synthesize a new MouseEvent to propagate the inputSource to the
    // subsequently triggered popupshowing event.
    let newEvent = new PointerEvent("contextmenu", {
      bubbles: true,
      cancelable: true,
      screenX: data.screenXDevPx / window.devicePixelRatio,
      screenY: data.screenYDevPx / window.devicePixelRatio,
      pointerType: (() => {
        switch (data.inputSource) {
          case MouseEvent.MOZ_SOURCE_MOUSE:
            return "mouse";
          case MouseEvent.MOZ_SOURCE_PEN:
            return "pen";
          case MouseEvent.MOZ_SOURCE_ERASER:
            return "eraser";
          case MouseEvent.MOZ_SOURCE_CURSOR:
            return "cursor";
          case MouseEvent.MOZ_SOURCE_TOUCH:
            return "touch";
          case MouseEvent.MOZ_SOURCE_KEYBOARD:
            return "keyboard";
          default:
            return "";
        }
      })(),
    });
    popup.openPopupAtScreen(newEvent.screenX, newEvent.screenY, true, newEvent);
  },

  hideToggle() {
    Services.prefs.setBoolPref(TOGGLE_ENABLED_PREF, false);
    Services.telemetry.recordEvent(
      "pictureinpicture.settings",
      "disable",
      "player"
    );
  },

  /**
   * This is used in AsyncTabSwitcher.sys.mjs and tabbrowser.js to check if the browser
   * currently has a PiP window.
   * If the browser has a PiP window we want to keep the browser in an active state because
   * the browser is still partially visible.
   * @param browser The browser to check if it has a PiP window
   * @returns true if browser has PiP window else false
   */
  isOriginatingBrowser(browser) {
    return this.browserWeakMap.has(browser);
  },

  moveToggle() {
    // Get the current position
    let position = Services.prefs.getStringPref(
      TOGGLE_POSITION_PREF,
      TOGGLE_POSITION_RIGHT
    );
    let newPosition = "";
    // Determine what the opposite position would be for that preference
    switch (position) {
      case TOGGLE_POSITION_RIGHT:
        newPosition = TOGGLE_POSITION_LEFT;
        break;
      case TOGGLE_POSITION_LEFT:
        newPosition = TOGGLE_POSITION_RIGHT;
        break;
    }
    if (newPosition) {
      Services.prefs.setStringPref(TOGGLE_POSITION_PREF, newPosition);
    }
  },

  /**
   * This function takes a screen and will return the left, top, width and
   * height of the screen
   * @param {Screen} screen
   * The screen we need to get the size and coordinates of
   *
   * @returns {array}
   * Size and location of screen in desktop pixels.
   *
   *   screenLeft.value (int):
   *     The left position for the screen.
   *
   *   screenTop.value (int):
   *     The top position for the screen.
   *
   *   screenWidth.value (int):
   *     The width of the screen.
   *
   *   screenHeight.value (int):
   *     The height of the screen.
   */
  getAvailScreenSize(screen) {
    let screenLeft = {},
      screenTop = {},
      screenWidth = {},
      screenHeight = {};
    screen.GetAvailRectDisplayPix(
      screenLeft,
      screenTop,
      screenWidth,
      screenHeight
    );
    return [
      screenLeft.value,
      screenTop.value,
      screenWidth.value,
      screenHeight.value,
    ];
  },

  /**
   * This function takes in a rect in desktop pixels, and returns the screen it
   * is located on.
   *
   * If the left and top are not on any screen, it will return the default
   * screen.
   *
   * @param {int} left
   *  left or x coordinate
   *
   * @param {int} top
   *  top or y coordinate
   *
   * @param {int} width
   *  top or y coordinate
   *
   * @param {int} height
   *  top or y coordinate
   *
   * @returns {Screen} screen
   *  the screen the left and top are on otherwise, default screen
   */
  getWorkingScreen(left, top, width = 1, height = 1) {
    // Get the screen manager
    let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"].getService(
      Ci.nsIScreenManager
    );
    // use screenForRect to get screen
    // this returns the default screen if left and top are not
    // on any screen
    return screenManager.screenForRect(left, top, width, height);
  },

  /**
   * Saves position and size of Picture-in-Picture window
   * @param {Window} win The Picture-in-Picture window
   */
  savePosition(win) {
    let xulStore = Services.xulStore;

    // We store left / top position in desktop pixels, like SessionStore does,
    // so that we can restore them properly (as CSS pixels need to be relative
    // to a screen, and we won't have a target screen to restore).
    let cssToDesktopScale = win.devicePixelRatio / win.desktopToDeviceScale;

    let left = win.screenX * cssToDesktopScale;
    let top = win.screenY * cssToDesktopScale;
    let width = win.outerWidth;
    let height = win.outerHeight;

    xulStore.setValue(PLAYER_URI, "picture-in-picture", "left", left);
    xulStore.setValue(PLAYER_URI, "picture-in-picture", "top", top);
    xulStore.setValue(PLAYER_URI, "picture-in-picture", "width", width);
    xulStore.setValue(PLAYER_URI, "picture-in-picture", "height", height);
  },

  /**
   * Load last Picture in Picture location and size
   * @returns {object}
   *   The size and position of the last Picture in Picture window.
   *
   *   top (int):
   *     The top position for the last player window.
   *     Otherwise NaN
   *
   *   left (int):
   *     The left position for the last player window.
   *     Otherwise NaN
   *
   *   width (int):
   *     The width of the player last window.
   *     Otherwise NaN
   *
   *   height (int):
   *     The height of the player last window.
   *     Otherwise NaN
   */
  loadPosition() {
    let xulStore = Services.xulStore;

    let left = parseInt(
      xulStore.getValue(PLAYER_URI, "picture-in-picture", "left")
    );
    let top = parseInt(
      xulStore.getValue(PLAYER_URI, "picture-in-picture", "top")
    );
    let width = parseInt(
      xulStore.getValue(PLAYER_URI, "picture-in-picture", "width")
    );
    let height = parseInt(
      xulStore.getValue(PLAYER_URI, "picture-in-picture", "height")
    );

    return { top, left, width, height };
  },

  setFirstSeen(dateSeconds) {
    if (!dateSeconds) {
      return;
    }

    Services.prefs.setIntPref(TOGGLE_FIRST_SEEN_PREF, dateSeconds);
  },

  setHasUsed(hasUsed) {
    Services.prefs.setBoolPref(TOGGLE_HAS_USED_PREF, !!hasUsed);
  },
};
PK
!<��|c��(modules/PictureInPictureControls.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// These denote which keyboard controls to disable for a qualified video element.
export const KEYBOARD_CONTROLS = {
  ALL: 0,
  PLAY_PAUSE: 1 << 0,
  MUTE_UNMUTE: 1 << 1,
  VOLUME: 1 << 2,
  SEEK: 1 << 3,
  CLOSE: 1 << 4,
  LIVE_SEEK: 1 << 5,
};

// These are the possible toggle positions along the right side of
// a qualified video element.
export const TOGGLE_POLICIES = {
  DEFAULT: 1,
  HIDDEN: 2,
  TOP: 3,
  ONE_QUARTER: 4,
  MIDDLE: 5,
  THREE_QUARTERS: 6,
  BOTTOM: 7,
};

// These strings are used in the videocontrols.css stylesheet as
// toggle policy attribute values for setting rules on the position
// of the toggle.
export const TOGGLE_POLICY_STRINGS = {
  [TOGGLE_POLICIES.DEFAULT]: "default",
  [TOGGLE_POLICIES.HIDDEN]: "hidden",
  [TOGGLE_POLICIES.TOP]: "top",
  [TOGGLE_POLICIES.ONE_QUARTER]: "one-quarter",
  [TOGGLE_POLICIES.MIDDLE]: "middle",
  [TOGGLE_POLICIES.THREE_QUARTERS]: "three-quarters",
  [TOGGLE_POLICIES.BOTTOM]: "bottom",
};
PK
!<9,LxAAmodules/PlacesBackups.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * vim: sw=2 ts=2 sts=2 expandtab filetype=javascript
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
  BookmarkJSONUtils: "resource://gre/modules/BookmarkJSONUtils.sys.mjs",
});

ChromeUtils.defineLazyGetter(
  lazy,
  "filenamesRegex",
  () =>
    /^bookmarks-([0-9-]+)(?:_([0-9]+)){0,1}(?:_([a-z0-9=_+-]{24,})){0,1}\.(json(lz4)?)$/i
);

async function limitBackups(aMaxBackups, backupFiles) {
  if (
    typeof aMaxBackups == "number" &&
    aMaxBackups > -1 &&
    backupFiles.length >= aMaxBackups
  ) {
    let numberOfBackupsToDelete = backupFiles.length - aMaxBackups;
    while (numberOfBackupsToDelete--) {
      let oldestBackup = backupFiles.pop();
      await IOUtils.remove(oldestBackup);
    }
  }
}

/**
 * Appends meta-data information to a given filename.
 */
function appendMetaDataToFilename(aFilename, aMetaData) {
  let matches = aFilename.match(lazy.filenamesRegex);
  return (
    "bookmarks-" +
    matches[1] +
    "_" +
    aMetaData.count +
    "_" +
    aMetaData.hash +
    "." +
    matches[4]
  );
}

/**
 * Gets the hash from a backup filename.
 *
 * @return the extracted hash or null.
 */
function getHashFromFilename(aFilename) {
  let matches = aFilename.match(lazy.filenamesRegex);
  if (matches && matches[3]) {
    return matches[3];
  }
  return null;
}

/**
 * Given two filenames, checks if they contain the same date.
 */
function isFilenameWithSameDate(aSourceName, aTargetName) {
  let sourceMatches = aSourceName.match(lazy.filenamesRegex);
  let targetMatches = aTargetName.match(lazy.filenamesRegex);

  return sourceMatches && targetMatches && sourceMatches[1] == targetMatches[1];
}

/**
 * Given a filename, searches for another backup with the same date.
 *
 * @return path string or null.
 */
function getBackupFileForSameDate(aFilename) {
  return (async function () {
    let backupFiles = await PlacesBackups.getBackupFiles();
    for (let backupFile of backupFiles) {
      if (isFilenameWithSameDate(PathUtils.filename(backupFile), aFilename)) {
        return backupFile;
      }
    }
    return null;
  })();
}

export var PlacesBackups = {
  /**
   * Matches the backup filename:
   *  0: file name
   *  1: date in form Y-m-d
   *  2: bookmarks count
   *  3: contents hash
   *  4: file extension
   */
  get filenamesRegex() {
    return lazy.filenamesRegex;
  },

  /**
   * Gets backup folder asynchronously.
   * @return {Promise}
   * @resolve the folder (the folder string path).
   */
  getBackupFolder: function PB_getBackupFolder() {
    return (async () => {
      if (this._backupFolder) {
        return this._backupFolder;
      }
      let backupsDirPath = PathUtils.join(
        PathUtils.profileDir,
        this.profileRelativeFolderPath
      );
      await IOUtils.makeDirectory(backupsDirPath);
      return (this._backupFolder = backupsDirPath);
    })();
  },

  get profileRelativeFolderPath() {
    return "bookmarkbackups";
  },

  /**
   * Cache current backups in a sorted (by date DESC) array.
   * @return {Promise}
   * @resolve a sorted array of string paths.
   */
  getBackupFiles: function PB_getBackupFiles() {
    return (async () => {
      if (this._backupFiles) {
        return this._backupFiles;
      }

      this._backupFiles = [];

      let backupFolderPath = await this.getBackupFolder();
      let children = await IOUtils.getChildren(backupFolderPath);
      let list = [];
      for (const entry of children) {
        // Since IOUtils I/O is serialized, we can safely remove .tmp files
        // without risking to remove ongoing backups.
        let filename = PathUtils.filename(entry);
        if (filename.endsWith(".tmp")) {
          list.push(IOUtils.remove(entry));
          continue;
        }

        if (lazy.filenamesRegex.test(filename)) {
          // Remove bogus backups in future dates.
          if (this.getDateForFile(entry) > new Date()) {
            list.push(IOUtils.remove(entry));
            continue;
          }
          this._backupFiles.push(entry);
        }
      }
      await Promise.all(list);

      this._backupFiles.sort((a, b) => {
        let aDate = this.getDateForFile(a);
        let bDate = this.getDateForFile(b);
        return bDate - aDate;
      });

      return this._backupFiles;
    })();
  },

  /**
   * Invalidates the internal cache for testing purposes.
   */
  invalidateCache() {
    this._backupFiles = null;
  },

  /**
   * Generates a ISO date string (YYYY-MM-DD) from a Date object.
   *
   * @param dateObj
   *        The date object to parse.
   * @return an ISO date string.
   */
  toISODateString: function toISODateString(dateObj) {
    if (!dateObj || dateObj.constructor.name != "Date" || !dateObj.getTime()) {
      throw new Error("invalid date object");
    }
    let padDate = val => ("0" + val).substr(-2, 2);
    return [
      dateObj.getFullYear(),
      padDate(dateObj.getMonth() + 1),
      padDate(dateObj.getDate()),
    ].join("-");
  },

  /**
   * Creates a filename for bookmarks backup files.
   *
   * @param [optional] aDateObj
   *                   Date object used to build the filename.
   *                   Will use current date if empty.
   * @param [optional] bool - aCompress
   *                   Determines if file extension is json or jsonlz4
                       Default is json
   * @return A bookmarks backup filename.
   */
  getFilenameForDate: function PB_getFilenameForDate(aDateObj, aCompress) {
    let dateObj = aDateObj || new Date();
    // Use YYYY-MM-DD (ISO 8601) as it doesn't contain illegal characters
    // and makes the alphabetical order of multiple backup files more useful.
    return (
      "bookmarks-" +
      PlacesBackups.toISODateString(dateObj) +
      ".json" +
      (aCompress ? "lz4" : "")
    );
  },

  /**
   * Creates a Date object from a backup file.  The date is the backup
   * creation date.
   *
   * @param {Sring} aBackupFile The path of the backup.
   * @return {Date} A Date object for the backup's creation time.
   */
  getDateForFile: function PB_getDateForFile(aBackupFile) {
    let filename = PathUtils.filename(aBackupFile);
    let matches = filename.match(lazy.filenamesRegex);
    if (!matches) {
      throw new Error(`Invalid backup file name: ${filename}`);
    }
    return new Date(matches[1].replace(/-/g, "/"));
  },

  /**
   * Get the most recent backup file.
   *
   * @return {Promise}
   * @result the path to the file.
   */
  getMostRecentBackup: function PB_getMostRecentBackup() {
    return (async () => {
      let entries = await this.getBackupFiles();
      for (let entry of entries) {
        let rx = /\.json(lz4)?$/;
        if (PathUtils.filename(entry).match(rx)) {
          return entry;
        }
      }
      return null;
    })();
  },

  /**
   * Returns whether a recent enough backup exists, using these heuristic: if
   * a backup exists, it should be newer than the last browser session date,
   * otherwise it should not be older than maxDays.
   * If the backup is older than the last session, the calculated time is
   * reported to telemetry.
   *
   * @param [maxDays] The maximum number of days a backup can be old.
   */
  async hasRecentBackup({ maxDays = 3 } = {}) {
    let lastBackupFile = await PlacesBackups.getMostRecentBackup();
    if (!lastBackupFile) {
      return false;
    }
    let lastBackupTime = PlacesBackups.getDateForFile(lastBackupFile);
    let profileLastUse = Services.appinfo.replacedLockTime || Date.now();
    if (lastBackupTime > profileLastUse) {
      return true;
    }
    let backupAge = Math.round((profileLastUse - lastBackupTime) / 86400000);
    // Telemetry the age of the last available backup.
    try {
      Services.telemetry
        .getHistogramById("PLACES_BACKUPS_DAYSFROMLAST")
        .add(backupAge);
    } catch (ex) {
      console.error(new Error("Unable to report telemetry."));
    }
    return backupAge <= maxDays;
  },

  /**
   * Serializes bookmarks using JSON, and writes to the supplied file.
   *
   * @param aFilePath
   *        path for the "bookmarks.json" file to be created.
   * @return {Promise}
   * @resolves the number of serialized uri nodes.
   */
  async saveBookmarksToJSONFile(aFilePath) {
    let { count: nodeCount, hash: hash } =
      await lazy.BookmarkJSONUtils.exportToFile(aFilePath);

    let backupFolderPath = await this.getBackupFolder();
    if (PathUtils.profileDir == backupFolderPath) {
      // We are creating a backup in the default backups folder,
      // so just update the internal cache.
      if (!this._backupFiles) {
        await this.getBackupFiles();
      }
      this._backupFiles.unshift(aFilePath);
    } else {
      let aMaxBackup = Services.prefs.getIntPref(
        "browser.bookmarks.max_backups"
      );
      if (aMaxBackup === 0) {
        if (!this._backupFiles) {
          await this.getBackupFiles();
        }
        limitBackups(aMaxBackup, this._backupFiles);
        return nodeCount;
      }
      // If we are saving to a folder different than our backups folder, then
      // we also want to create a new compressed version in it.
      // This way we ensure the latest valid backup is the same saved by the
      // user.  See bug 424389.
      let mostRecentBackupFile = await this.getMostRecentBackup();
      if (
        !mostRecentBackupFile ||
        hash != getHashFromFilename(PathUtils.filename(mostRecentBackupFile))
      ) {
        let name = this.getFilenameForDate(undefined, true);
        let newFilename = appendMetaDataToFilename(name, {
          count: nodeCount,
          hash,
        });
        let newFilePath = PathUtils.join(backupFolderPath, newFilename);
        let backupFile = await getBackupFileForSameDate(name);
        if (backupFile) {
          // There is already a backup for today, replace it.
          await IOUtils.remove(backupFile);
          if (!this._backupFiles) {
            await this.getBackupFiles();
          } else {
            this._backupFiles.shift();
          }
          this._backupFiles.unshift(newFilePath);
        } else {
          // There is no backup for today, add the new one.
          if (!this._backupFiles) {
            await this.getBackupFiles();
          }
          this._backupFiles.unshift(newFilePath);
        }
        let jsonString = await IOUtils.read(aFilePath);
        await IOUtils.write(newFilePath, jsonString, {
          compress: true,
        });
        await limitBackups(aMaxBackup, this._backupFiles);
      }
    }
    return nodeCount;
  },

  /**
   * Creates a dated backup in <profile>/bookmarkbackups.
   * Stores the bookmarks using a lz4 compressed JSON file.
   *
   * @param [optional] int aMaxBackups
   *                       The maximum number of backups to keep.  If set to 0
   *                       all existing backups are removed and aForceBackup is
   *                       ignored, so a new one won't be created.
   * @param [optional] bool aForceBackup
   *                        Forces creating a backup even if one was already
   *                        created that day (overwrites).
   * @return {Promise}
   */
  create: function PB_create(aMaxBackups, aForceBackup) {
    return (async () => {
      if (aMaxBackups === 0) {
        // Backups are disabled, delete any existing one and bail out.
        if (!this._backupFiles) {
          await this.getBackupFiles();
        }
        await limitBackups(0, this._backupFiles);
        return;
      }

      // Ensure to initialize _backupFiles
      if (!this._backupFiles) {
        await this.getBackupFiles();
      }
      let newBackupFilename = this.getFilenameForDate(undefined, true);
      // If we already have a backup for today we should do nothing, unless we
      // were required to enforce a new backup.
      let backupFile = await getBackupFileForSameDate(newBackupFilename);
      if (backupFile && !aForceBackup) {
        return;
      }

      if (backupFile) {
        // In case there is a backup for today we should recreate it.
        this._backupFiles.shift();
        await IOUtils.remove(backupFile);
      }

      // Now check the hash of the most recent backup, and try to create a new
      // backup, if that fails due to hash conflict, just rename the old backup.
      let mostRecentBackupFile = await this.getMostRecentBackup();
      let mostRecentHash =
        mostRecentBackupFile &&
        getHashFromFilename(PathUtils.filename(mostRecentBackupFile));

      // Save bookmarks to a backup file.
      let backupFolder = await this.getBackupFolder();
      let newBackupFile = PathUtils.join(backupFolder, newBackupFilename);
      let newFilenameWithMetaData;
      try {
        let { count: nodeCount, hash: hash } =
          await lazy.BookmarkJSONUtils.exportToFile(newBackupFile, {
            compress: true,
            failIfHashIs: mostRecentHash,
          });
        newFilenameWithMetaData = appendMetaDataToFilename(newBackupFilename, {
          count: nodeCount,
          hash,
        });
      } catch (ex) {
        if (!ex.becauseSameHash) {
          throw ex;
        }
        // The last backup already contained up-to-date information, just
        // rename it as if it was today's backup.
        this._backupFiles.shift();
        newBackupFile = mostRecentBackupFile;
        // Ensure we retain the proper extension when renaming
        // the most recent backup file.
        if (/\.json$/.test(PathUtils.filename(mostRecentBackupFile))) {
          newBackupFilename = this.getFilenameForDate();
        }
        newFilenameWithMetaData = appendMetaDataToFilename(newBackupFilename, {
          count: this.getBookmarkCountForFile(mostRecentBackupFile),
          hash: mostRecentHash,
        });
      }

      // Append metadata to the backup filename.
      let newBackupFileWithMetadata = PathUtils.join(
        backupFolder,
        newFilenameWithMetaData
      );
      await IOUtils.move(newBackupFile, newBackupFileWithMetadata);
      this._backupFiles.unshift(newBackupFileWithMetadata);

      // Limit the number of backups.
      await limitBackups(aMaxBackups, this._backupFiles);
    })();
  },

  /**
   * Gets the bookmark count for backup file.
   *
   * @param aFilePath
   *        File path The backup file.
   *
   * @return the bookmark count or null.
   */
  getBookmarkCountForFile: function PB_getBookmarkCountForFile(aFilePath) {
    let count = null;
    let filename = PathUtils.filename(aFilePath);
    let matches = filename.match(lazy.filenamesRegex);
    if (matches && matches[2]) {
      count = matches[2];
    }
    return count;
  },

  /**
   * Gets a bookmarks tree representation usable to create backups in different
   * file formats.  The root or the tree is PlacesUtils.bookmarks.rootGuid.
   *
   * @return an object representing a tree with the places root as its root.
   *         Each bookmark is represented by an object having these properties:
   *         * id: the item id (make this not enumerable after bug 824502)
   *         * title: the title
   *         * guid: unique id
   *         * parent: item id of the parent folder, not enumerable
   *         * index: the position in the parent
   *         * dateAdded: microseconds from the epoch
   *         * lastModified: microseconds from the epoch
   *         * type: type of the originating node as defined in PlacesUtils
   *         The following properties exist only for a subset of bookmarks:
   *         * annos: array of annotations
   *         * uri: url
   *         * iconUri: favicon's url
   *         * keyword: associated keyword
   *         * charset: last known charset
   *         * tags: csv string of tags
   *         * root: string describing whether this represents a root
   *         * children: array of child items in a folder
   */
  async getBookmarksTree() {
    let startTime = Date.now();
    let root = await lazy.PlacesUtils.promiseBookmarksTree(
      lazy.PlacesUtils.bookmarks.rootGuid,
      {
        includeItemIds: true,
      }
    );

    try {
      Services.telemetry
        .getHistogramById("PLACES_BACKUPS_BOOKMARKSTREE_MS")
        .add(Date.now() - startTime);
    } catch (ex) {
      console.error("Unable to report telemetry.");
    }
    return [root, root.itemsCount];
  },
};
PK
!<M�8
����modules/PlacesDBUtils.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * vim: sw=2 ts=2 sts=2 expandtab filetype=javascript
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const BYTES_PER_MEBIBYTE = 1048576;
const MS_PER_DAY = 86400000;
// Threshold value for removeOldCorruptDBs.
// Corrupt DBs older than this value are removed.
const CORRUPT_DB_RETAIN_DAYS = 14;

// Seconds between maintenance runs.
const MAINTENANCE_INTERVAL_SECONDS = 7 * 86400;

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PlacesPreviews: "resource://gre/modules/PlacesPreviews.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
  Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
});

export var PlacesDBUtils = {
  _isShuttingDown: false,

  _clearTaskQueue: false,
  clearPendingTasks() {
    PlacesDBUtils._clearTaskQueue = true;
  },

  /**
   * Executes integrity check and common maintenance tasks.
   *
   * @return a Map[taskName(String) -> Object]. The Object has the following properties:
   *         - succeeded: boolean
   *         - logs: an array of strings containing the messages logged by the task.
   */
  async maintenanceOnIdle() {
    let tasks = [
      this.checkIntegrity,
      this.checkCoherence,
      this._refreshUI,
      this.incrementalVacuum,
      this.removeOldCorruptDBs,
      this.deleteOrphanPreviews,
    ];
    let telemetryStartTime = Date.now();
    let taskStatusMap = await PlacesDBUtils.runTasks(tasks);

    Services.prefs.setIntPref(
      "places.database.lastMaintenance",
      parseInt(Date.now() / 1000)
    );
    Services.telemetry
      .getHistogramById("PLACES_IDLE_MAINTENANCE_TIME_MS")
      .add(Date.now() - telemetryStartTime);
    return taskStatusMap;
  },

  /**
   * Executes integrity check, common and advanced maintenance tasks (like
   * expiration and vacuum).  Will also collect statistics on the database.
   *
   * Note: although this function isn't actually async, we keep it async to
   * allow us to maintain a simple, consistent API for the tasks within this object.
   *
   * @return {Promise}
   *        A promise that resolves with a Map[taskName(String) -> Object].
   *        The Object has the following properties:
   *         - succeeded: boolean
   *         - logs: an array of strings containing the messages logged by the task.
   */
  async checkAndFixDatabase() {
    let tasks = [
      this.checkIntegrity,
      this.checkCoherence,
      this.expire,
      this.vacuum,
      this.stats,
      this._refreshUI,
    ];
    return PlacesDBUtils.runTasks(tasks);
  },

  /**
   * Forces a full refresh of Places views.
   *
   * Note: although this function isn't actually async, we keep it async to
   * allow us to maintain a simple, consistent API for the tasks within this object.
   *
   * @returns {Array} An empty array.
   */
  async _refreshUI() {
    PlacesObservers.notifyListeners([new PlacesPurgeCaches()]);
    return [];
  },

  /**
   * Checks integrity and tries to fix the database through a reindex.
   *
   * @return {Promise} resolves if database is sane or is made sane.
   * @resolves to an array of logs for this task.
   * @rejects if we're unable to fix corruption or unable to check status.
   */
  async checkIntegrity() {
    let logs = [];

    async function check(dbName) {
      try {
        await integrity(dbName);
        logs.push(`The ${dbName} database is sane`);
      } catch (ex) {
        PlacesDBUtils.clearPendingTasks();
        if (ex.result == Cr.NS_ERROR_FILE_CORRUPTED) {
          logs.push(`The ${dbName} database is corrupt`);
          Services.prefs.setCharPref(
            "places.database.replaceDatabaseOnStartup",
            dbName
          );
          throw new Error(
            `Unable to fix corruption, ${dbName} will be replaced on next startup`
          );
        }
        throw new Error(`Unable to check ${dbName} integrity: ${ex}`);
      }
    }

    await check("places.sqlite");
    await check("favicons.sqlite");

    return logs;
  },

  /**
   * Checks data coherence and tries to fix most common errors.
   *
   * @return {Promise} resolves when coherence is checked.
   * @resolves to an array of logs for this task.
   * @rejects if database is not coherent.
   */
  async checkCoherence() {
    let logs = [];
    let stmts = await PlacesDBUtils._getCoherenceStatements();
    let coherenceCheck = true;
    await lazy.PlacesUtils.withConnectionWrapper(
      "PlacesDBUtils: coherence check:",
      db =>
        db.executeTransaction(async () => {
          for (let { query, params } of stmts) {
            try {
              await db.execute(query, params || null);
            } catch (ex) {
              console.error(ex);
              coherenceCheck = false;
            }
          }
        })
    );

    if (coherenceCheck) {
      logs.push("The database is coherent");
    } else {
      PlacesDBUtils.clearPendingTasks();
      throw new Error("Unable to complete the coherence check");
    }
    return logs;
  },

  /**
   * Runs incremental vacuum on databases supporting it.
   *
   * @return {Promise} resolves when done.
   * @resolves to an array of logs for this task.
   * @rejects if we were unable to vacuum.
   */
  async incrementalVacuum() {
    let logs = [];
    return lazy.PlacesUtils.withConnectionWrapper(
      "PlacesDBUtils: incrementalVacuum",
      async db => {
        let count = (
          await db.execute("PRAGMA favicons.freelist_count")
        )[0].getResultByIndex(0);
        if (count < 10) {
          logs.push(
            `The favicons database has only ${count} free pages, not vacuuming.`
          );
        } else {
          logs.push(
            `The favicons database has ${count} free pages, vacuuming.`
          );
          await db.execute("PRAGMA favicons.incremental_vacuum");
          count = (
            await db.execute("PRAGMA favicons.freelist_count")
          )[0].getResultByIndex(0);
          logs.push(
            `The database has been vacuumed and has now ${count} free pages.`
          );
        }
        return logs;
      }
    ).catch(ex => {
      PlacesDBUtils.clearPendingTasks();
      throw new Error(
        "Unable to incrementally vacuum the favicons database " + ex
      );
    });
  },

  /**
   * Expire orphan previews that don't have a Places entry anymore.
   *
   * @return {Promise} resolves when done.
   * @resolves to an array of logs for this task.
   */
  async deleteOrphanPreviews() {
    let logs = [];
    try {
      let deleted = await lazy.PlacesPreviews.deleteOrphans();
      if (deleted) {
        logs.push(`Orphan previews deleted.`);
      }
    } catch (ex) {
      throw new Error("Unable to delete orphan previews " + ex);
    }
    return logs;
  },

  async _getCoherenceStatements() {
    let cleanupStatements = [
      // MOZ_PLACES
      // L.1 remove duplicate URLs.
      // This task uses a temp table of potential dupes, and a trigger to remove
      // them. It runs first because it relies on subsequent tasks to clean up
      // orphaned foreign key references. The task works like this: first, we
      // insert all rows with the same hash into the temp table. This lets
      // SQLite use the `url_hash` index for scanning `moz_places`. Hashes
      // aren't unique, so two different URLs might have the same hash. To find
      // the actual dupes, we use a unique constraint on the URL in the temp
      // table. If that fails, we bump the dupe count. Then, we delete all dupes
      // from the table. This fires the cleanup trigger, which updates all
      // foreign key references to point to one of the duplicate Places, then
      // deletes the others.
      {
        query: `CREATE TEMP TABLE IF NOT EXISTS moz_places_dupes_temp(
          id INTEGER PRIMARY KEY
        , hash INTEGER NOT NULL
        , url TEXT UNIQUE NOT NULL
        , count INTEGER NOT NULL DEFAULT 0
        )`,
      },
      {
        query: `CREATE TEMP TRIGGER IF NOT EXISTS moz_places_remove_dupes_temp_trigger
        AFTER DELETE ON moz_places_dupes_temp
        FOR EACH ROW
        BEGIN
          /* Reassign history visits. */
          UPDATE moz_historyvisits SET
            place_id = OLD.id
          WHERE place_id IN (SELECT id FROM moz_places
                             WHERE id <> OLD.id AND
                                   url_hash = OLD.hash AND
                                   url = OLD.url);

          /* Merge autocomplete history entries. */
          INSERT INTO moz_inputhistory(place_id, input, use_count)
          SELECT OLD.id, a.input, a.use_count
          FROM moz_inputhistory a
          JOIN moz_places h ON h.id = a.place_id
          WHERE h.id <> OLD.id AND
                h.url_hash = OLD.hash AND
                h.url = OLD.url
          ON CONFLICT(place_id, input) DO UPDATE SET
            place_id = excluded.place_id,
            use_count = use_count + excluded.use_count;

          /* Merge page annos, ignoring annos with the same name that are
             already set on the destination. */
          INSERT OR IGNORE INTO moz_annos(id, place_id, anno_attribute_id,
                                          content, flags, expiration, type,
                                          dateAdded, lastModified)
          SELECT (SELECT k.id FROM moz_annos k
                  WHERE k.place_id = OLD.id AND
                        k.anno_attribute_id = a.anno_attribute_id), OLD.id,
                 a.anno_attribute_id, a.content, a.flags, a.expiration, a.type,
                 a.dateAdded, a.lastModified
          FROM moz_annos a
          JOIN moz_places h ON h.id = a.place_id
          WHERE h.id <> OLD.id AND
                url_hash = OLD.hash AND
                url = OLD.url;

          /* Reassign bookmarks, and bump the Sync change counter just in case
             we have new keywords. */
          UPDATE moz_bookmarks SET
            fk = OLD.id,
            syncChangeCounter = syncChangeCounter + 1
          WHERE fk IN (SELECT id FROM moz_places
                       WHERE url_hash = OLD.hash AND
                             url = OLD.url);

          /* Reassign keywords. */
          UPDATE moz_keywords SET
            place_id = OLD.id
          WHERE place_id IN (SELECT id FROM moz_places
                             WHERE id <> OLD.id AND
                                   url_hash = OLD.hash AND
                                   url = OLD.url);

          /* Now that we've updated foreign key references, drop the
             conflicting source. */
          DELETE FROM moz_places
          WHERE id <> OLD.id AND
                url_hash = OLD.hash AND
                url = OLD.url;

          /* Recalculate frecency for the destination. */
          UPDATE moz_places SET recalc_frecency = 1, recalc_alt_frecency = 1
          WHERE id = OLD.id;
        END`,
      },
      {
        query: `INSERT INTO moz_places_dupes_temp(id, hash, url, count)
        SELECT h.id, h.url_hash, h.url, 1
        FROM moz_places h
        JOIN (SELECT url_hash FROM moz_places
              GROUP BY url_hash
              HAVING count(*) > 1) d ON d.url_hash = h.url_hash
        ON CONFLICT(url) DO UPDATE SET
          count = count + 1`,
      },
      { query: `DELETE FROM moz_places_dupes_temp WHERE count > 1` },
      { query: `DROP TABLE moz_places_dupes_temp` },

      // MOZ_ANNO_ATTRIBUTES
      // A.1 remove obsolete annotations from moz_annos.
      // The 'weave0' idiom exploits character ordering (0 follows /) to
      // efficiently select all annos with a 'weave/' prefix.
      {
        query: `DELETE FROM moz_annos
        WHERE type = 4 OR anno_attribute_id IN (
          SELECT id FROM moz_anno_attributes
          WHERE name = 'downloads/destinationFileName' OR
                name BETWEEN 'weave/' AND 'weave0'
        )`,
      },

      // A.3 remove unused attributes.
      {
        query: `DELETE FROM moz_anno_attributes WHERE id IN (
          SELECT id FROM moz_anno_attributes n
          WHERE NOT EXISTS
              (SELECT id FROM moz_annos WHERE anno_attribute_id = n.id LIMIT 1)
        )`,
      },

      // MOZ_ANNOS
      // B.1 remove annos with an invalid attribute
      {
        query: `DELETE FROM moz_annos WHERE id IN (
          SELECT id FROM moz_annos a
          WHERE NOT EXISTS
            (SELECT id FROM moz_anno_attributes
              WHERE id = a.anno_attribute_id LIMIT 1)
        )`,
      },

      // B.2 remove orphan annos
      {
        query: `DELETE FROM moz_annos WHERE id IN (
          SELECT id FROM moz_annos a
          WHERE NOT EXISTS
            (SELECT id FROM moz_places WHERE id = a.place_id LIMIT 1)
        )`,
      },

      // D.1 remove items that are not uri bookmarks from tag containers
      {
        query: `DELETE FROM moz_bookmarks WHERE guid NOT IN (
          :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid  /* skip roots */
        ) AND id IN (
          SELECT b.id FROM moz_bookmarks b
          WHERE b.parent IN
            (SELECT id FROM moz_bookmarks WHERE parent = :tags_folder)
            AND b.type <> :bookmark_type
        )`,
        params: {
          tags_folder: lazy.PlacesUtils.tagsFolderId,
          bookmark_type: lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK,
          rootGuid: lazy.PlacesUtils.bookmarks.rootGuid,
          menuGuid: lazy.PlacesUtils.bookmarks.menuGuid,
          toolbarGuid: lazy.PlacesUtils.bookmarks.toolbarGuid,
          unfiledGuid: lazy.PlacesUtils.bookmarks.unfiledGuid,
          tagsGuid: lazy.PlacesUtils.bookmarks.tagsGuid,
        },
      },

      // D.2 remove empty tags
      {
        query: `DELETE FROM moz_bookmarks WHERE guid NOT IN (
          :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid  /* skip roots */
        ) AND id IN (
          SELECT b.id FROM moz_bookmarks b
          WHERE b.id IN
            (SELECT id FROM moz_bookmarks WHERE parent = :tags_folder)
            AND NOT EXISTS
              (SELECT id from moz_bookmarks WHERE parent = b.id LIMIT 1)
        )`,
        params: {
          tags_folder: lazy.PlacesUtils.tagsFolderId,
          rootGuid: lazy.PlacesUtils.bookmarks.rootGuid,
          menuGuid: lazy.PlacesUtils.bookmarks.menuGuid,
          toolbarGuid: lazy.PlacesUtils.bookmarks.toolbarGuid,
          unfiledGuid: lazy.PlacesUtils.bookmarks.unfiledGuid,
          tagsGuid: lazy.PlacesUtils.bookmarks.tagsGuid,
        },
      },

      // D.3 move orphan items to unsorted folder
      {
        query: `UPDATE moz_bookmarks SET
          parent = (SELECT id FROM moz_bookmarks WHERE guid = :unfiledGuid)
        WHERE guid NOT IN (
          :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid  /* skip roots */
        ) AND id IN (
          SELECT b.id FROM moz_bookmarks b
          WHERE NOT EXISTS
            (SELECT id FROM moz_bookmarks WHERE id = b.parent LIMIT 1)
        )`,
        params: {
          rootGuid: lazy.PlacesUtils.bookmarks.rootGuid,
          menuGuid: lazy.PlacesUtils.bookmarks.menuGuid,
          toolbarGuid: lazy.PlacesUtils.bookmarks.toolbarGuid,
          unfiledGuid: lazy.PlacesUtils.bookmarks.unfiledGuid,
          tagsGuid: lazy.PlacesUtils.bookmarks.tagsGuid,
        },
      },

      // D.4 Insert tombstones for any synced items with the wrong type.
      // Sync doesn't support changing the type of an existing item while
      // keeping its GUID. To avoid confusing other clients, we insert
      // tombstones for all synced items with the wrong type, so that we
      // can reupload them with the correct type and a new GUID.
      {
        query: `INSERT OR IGNORE INTO moz_bookmarks_deleted(guid, dateRemoved)
                SELECT guid, :dateRemoved
                FROM moz_bookmarks
                WHERE syncStatus <> :syncStatus AND
                      ((type IN (:folder_type, :separator_type) AND
                        fk NOTNULL) OR
                       (type = :bookmark_type AND
                        fk IS NULL) OR
                       type IS NULL)`,
        params: {
          dateRemoved: lazy.PlacesUtils.toPRTime(new Date()),
          syncStatus: lazy.PlacesUtils.bookmarks.SYNC_STATUS.NEW,
          bookmark_type: lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK,
          folder_type: lazy.PlacesUtils.bookmarks.TYPE_FOLDER,
          separator_type: lazy.PlacesUtils.bookmarks.TYPE_SEPARATOR,
        },
      },

      // D.5 fix wrong item types
      // Folders and separators should not have an fk.
      // If they have a valid fk, convert them to bookmarks, and give them new
      // GUIDs. If the item has children, we'll move them to the unfiled root
      // in D.8. If the `fk` doesn't exist in `moz_places`, we'll remove the
      // item in D.9.
      {
        query: `UPDATE moz_bookmarks
        SET guid = GENERATE_GUID(),
            type = :bookmark_type
        WHERE guid NOT IN (
          :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid  /* skip roots */
        ) AND id IN (
          SELECT id FROM moz_bookmarks b
          WHERE type IN (:folder_type, :separator_type)
            AND fk NOTNULL
        )`,
        params: {
          bookmark_type: lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK,
          folder_type: lazy.PlacesUtils.bookmarks.TYPE_FOLDER,
          separator_type: lazy.PlacesUtils.bookmarks.TYPE_SEPARATOR,
          rootGuid: lazy.PlacesUtils.bookmarks.rootGuid,
          menuGuid: lazy.PlacesUtils.bookmarks.menuGuid,
          toolbarGuid: lazy.PlacesUtils.bookmarks.toolbarGuid,
          unfiledGuid: lazy.PlacesUtils.bookmarks.unfiledGuid,
          tagsGuid: lazy.PlacesUtils.bookmarks.tagsGuid,
        },
      },

      // D.6 fix wrong item types
      // Bookmarks should have an fk, if they don't have any, convert them to
      // folders.
      {
        query: `UPDATE moz_bookmarks
        SET guid = GENERATE_GUID(),
            type = :folder_type
        WHERE guid NOT IN (
          :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid  /* skip roots */
        ) AND id IN (
          SELECT id FROM moz_bookmarks b
          WHERE type = :bookmark_type
            AND fk IS NULL
        )`,
        params: {
          bookmark_type: lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK,
          folder_type: lazy.PlacesUtils.bookmarks.TYPE_FOLDER,
          rootGuid: lazy.PlacesUtils.bookmarks.rootGuid,
          menuGuid: lazy.PlacesUtils.bookmarks.menuGuid,
          toolbarGuid: lazy.PlacesUtils.bookmarks.toolbarGuid,
          unfiledGuid: lazy.PlacesUtils.bookmarks.unfiledGuid,
          tagsGuid: lazy.PlacesUtils.bookmarks.tagsGuid,
        },
      },

      // D.7 fix wrong item types
      // `moz_bookmarks.type` doesn't have a NOT NULL constraint, so it's
      // possible for an item to not have a type (bug 1586427).
      {
        query: `UPDATE moz_bookmarks
        SET guid = GENERATE_GUID(),
            type = CASE WHEN fk NOT NULL THEN :bookmark_type ELSE :folder_type END
        WHERE guid NOT IN (
         :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid  /* skip roots */
        ) AND type IS NULL`,
        params: {
          bookmark_type: lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK,
          folder_type: lazy.PlacesUtils.bookmarks.TYPE_FOLDER,
          rootGuid: lazy.PlacesUtils.bookmarks.rootGuid,
          menuGuid: lazy.PlacesUtils.bookmarks.menuGuid,
          toolbarGuid: lazy.PlacesUtils.bookmarks.toolbarGuid,
          unfiledGuid: lazy.PlacesUtils.bookmarks.unfiledGuid,
          tagsGuid: lazy.PlacesUtils.bookmarks.tagsGuid,
        },
      },

      // D.8 fix wrong parents
      // Items cannot have separators or other bookmarks
      // as parent, if they have bad parent move them to unsorted bookmarks.
      {
        query: `UPDATE moz_bookmarks SET
          parent = (SELECT id FROM moz_bookmarks WHERE guid = :unfiledGuid)
        WHERE guid NOT IN (
          :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid  /* skip roots */
        ) AND id IN (
          SELECT id FROM moz_bookmarks b
          WHERE EXISTS
            (SELECT id FROM moz_bookmarks WHERE id = b.parent
              AND type IN (:bookmark_type, :separator_type)
              LIMIT 1)
        )`,
        params: {
          bookmark_type: lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK,
          separator_type: lazy.PlacesUtils.bookmarks.TYPE_SEPARATOR,
          rootGuid: lazy.PlacesUtils.bookmarks.rootGuid,
          menuGuid: lazy.PlacesUtils.bookmarks.menuGuid,
          toolbarGuid: lazy.PlacesUtils.bookmarks.toolbarGuid,
          unfiledGuid: lazy.PlacesUtils.bookmarks.unfiledGuid,
          tagsGuid: lazy.PlacesUtils.bookmarks.tagsGuid,
        },
      },

      // D.9 remove items without a valid place
      // We've already converted folders with an `fk` to bookmarks in D.5,
      // and bookmarks without an `fk` to folders in D.6. However, the `fk`
      // might not reference an existing `moz_places.id`, even if it's
      // NOT NULL. This statement takes care of those.
      {
        query: `DELETE FROM moz_bookmarks AS b
        WHERE b.guid NOT IN (
          :rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid  /* skip roots */
        ) AND b.fk NOT NULL
          AND b.type = :bookmark_type
          AND NOT EXISTS (SELECT 1 FROM moz_places h WHERE h.id = b.fk)`,
        params: {
          bookmark_type: lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK,
          rootGuid: lazy.PlacesUtils.bookmarks.rootGuid,
          menuGuid: lazy.PlacesUtils.bookmarks.menuGuid,
          toolbarGuid: lazy.PlacesUtils.bookmarks.toolbarGuid,
          unfiledGuid: lazy.PlacesUtils.bookmarks.unfiledGuid,
          tagsGuid: lazy.PlacesUtils.bookmarks.tagsGuid,
        },
      },

      // D.10 fix non-consecutive positions.
      {
        query: `
          WITH positions(item_id, pos, seq) AS (
            SELECT id, position AS pos,
                   (row_number() OVER (PARTITION BY parent ORDER BY position)) - 1 AS seq
            FROM moz_bookmarks
          )
          UPDATE moz_bookmarks
          SET position = seq
          FROM positions
          WHERE item_id = moz_bookmarks.id AND seq <> pos`,
      },

      // D.12 Fix empty-named tags.
      // Tags were allowed to have empty names due to a UI bug.  Fix them by
      // replacing their title with "(notitle)", and bumping the change counter
      // for all bookmarks with the fixed tags.
      {
        query: `UPDATE moz_bookmarks SET syncChangeCounter = syncChangeCounter + 1
         WHERE fk IN (SELECT b.fk FROM moz_bookmarks b
                      JOIN moz_bookmarks p ON p.id = b.parent
                      WHERE length(p.title) = 0 AND p.type = :folder_type AND
                            p.parent = :tags_folder)`,
        params: {
          folder_type: lazy.PlacesUtils.bookmarks.TYPE_FOLDER,
          tags_folder: lazy.PlacesUtils.tagsFolderId,
        },
      },
      {
        query: `UPDATE moz_bookmarks SET title = :empty_title
        WHERE length(title) = 0 AND type = :folder_type
          AND parent = :tags_folder`,
        params: {
          empty_title: "(notitle)",
          folder_type: lazy.PlacesUtils.bookmarks.TYPE_FOLDER,
          tags_folder: lazy.PlacesUtils.tagsFolderId,
        },
      },

      // MOZ_ICONS
      // E.1 remove orphan icon entries.
      {
        query: `DELETE FROM moz_pages_w_icons WHERE page_url_hash NOT IN (
          SELECT url_hash FROM moz_places
        )`,
      },

      // Remove icons whose origin is not in moz_origins, unless referenced.
      {
        query: `DELETE FROM moz_icons WHERE id IN (
          SELECT id FROM moz_icons WHERE root = 0
          UNION ALL
          SELECT id FROM moz_icons
          WHERE root = 1
            AND get_host_and_port(icon_url) NOT IN (SELECT host FROM moz_origins)
            AND fixup_url(get_host_and_port(icon_url)) NOT IN (SELECT host FROM moz_origins)
          EXCEPT
          SELECT icon_id FROM moz_icons_to_pages
        )`,
      },

      // MOZ_HISTORYVISITS
      // F.1 remove orphan visits
      {
        query: `DELETE FROM moz_historyvisits WHERE id IN (
          SELECT id FROM moz_historyvisits v
          WHERE NOT EXISTS
            (SELECT id FROM moz_places WHERE id = v.place_id LIMIT 1)
        )`,
      },

      // MOZ_INPUTHISTORY
      // G.1 remove orphan input history
      {
        query: `DELETE FROM moz_inputhistory WHERE place_id IN (
          SELECT place_id FROM moz_inputhistory i
          WHERE NOT EXISTS
            (SELECT id FROM moz_places WHERE id = i.place_id LIMIT 1)
        )`,
      },

      // MOZ_KEYWORDS
      // I.1 remove unused keywords
      {
        query: `DELETE FROM moz_keywords WHERE id IN (
          SELECT id FROM moz_keywords k
          WHERE NOT EXISTS
            (SELECT 1 FROM moz_places h WHERE k.place_id = h.id)
        )`,
      },

      // MOZ_PLACES
      // L.2 recalculate visit_count and last_visit_date
      {
        query: `UPDATE moz_places
        SET visit_count = (SELECT count(*) FROM moz_historyvisits
                            WHERE place_id = moz_places.id AND visit_type NOT IN (0,4,7,8,9)),
            last_visit_date = (SELECT MAX(visit_date) FROM moz_historyvisits
                                WHERE place_id = moz_places.id)
        WHERE id IN (
          SELECT h.id FROM moz_places h
          WHERE visit_count <> (SELECT count(*) FROM moz_historyvisits v
                                WHERE v.place_id = h.id AND visit_type NOT IN (0,4,7,8,9))
              OR last_visit_date IS NOT
                (SELECT MAX(visit_date) FROM moz_historyvisits v WHERE v.place_id = h.id)
        )`,
      },

      // L.3 recalculate hidden for redirects.
      {
        query: `UPDATE moz_places
        SET hidden = 1
        WHERE id IN (
          SELECT h.id FROM moz_places h
          JOIN moz_historyvisits src ON src.place_id = h.id
          JOIN moz_historyvisits dst ON dst.from_visit = src.id AND dst.visit_type IN (5,6)
          LEFT JOIN moz_bookmarks on fk = h.id AND fk ISNULL
          GROUP BY src.place_id HAVING count(*) = visit_count
        )`,
      },

      // L.4 recalculate foreign_count.
      {
        query: `UPDATE moz_places SET foreign_count =
          (SELECT count(*) FROM moz_bookmarks WHERE fk = moz_places.id ) +
          (SELECT count(*) FROM moz_keywords WHERE place_id = moz_places.id )`,
      },

      // L.5 recalculate missing hashes.
      {
        query: `UPDATE moz_places SET url_hash = hash(url) WHERE url_hash = 0`,
      },

      // L.6 fix invalid Place GUIDs.
      {
        query: `UPDATE moz_places
        SET guid = GENERATE_GUID()
        WHERE guid IS NULL OR
              NOT IS_VALID_GUID(guid)`,
      },

      // MOZ_BOOKMARKS
      // S.1 fix invalid GUIDs for synced bookmarks.
      // This requires multiple related statements.
      // First, we insert tombstones for all synced bookmarks with invalid
      // GUIDs, so that we can delete them on the server. Second, we add a
      // temporary trigger to bump the change counter for the parents of any
      // items we update, since Sync stores the list of child GUIDs on the
      // parent. Finally, we assign new GUIDs for all items with missing and
      // invalid GUIDs, bump their change counters, and reset their sync
      // statuses to NEW so that they're considered for deduping.
      {
        query: `INSERT OR IGNORE INTO moz_bookmarks_deleted(guid, dateRemoved)
        SELECT guid, :dateRemoved
        FROM moz_bookmarks
        WHERE syncStatus <> :syncStatus AND
              guid NOT NULL AND
              NOT IS_VALID_GUID(guid)`,
        params: {
          dateRemoved: lazy.PlacesUtils.toPRTime(new Date()),
          syncStatus: lazy.PlacesUtils.bookmarks.SYNC_STATUS.NEW,
        },
      },
      {
        query: `UPDATE moz_bookmarks
        SET guid = GENERATE_GUID(),
            syncStatus = :syncStatus
        WHERE guid IS NULL OR
              NOT IS_VALID_GUID(guid)`,
        params: {
          syncStatus: lazy.PlacesUtils.bookmarks.SYNC_STATUS.NEW,
        },
      },

      // S.2 drop tombstones for bookmarks that aren't deleted.
      {
        query: `DELETE FROM moz_bookmarks_deleted
        WHERE guid IN (SELECT guid FROM moz_bookmarks)`,
      },

      // S.3 set missing added and last modified dates.
      {
        query: `UPDATE moz_bookmarks
        SET dateAdded = COALESCE(NULLIF(dateAdded, 0), NULLIF(lastModified, 0), NULLIF((
              SELECT MIN(visit_date) FROM moz_historyvisits
              WHERE place_id = fk
            ), 0), STRFTIME('%s', 'now', 'localtime', 'utc') * 1000000),
            lastModified = COALESCE(NULLIF(lastModified, 0), NULLIF(dateAdded, 0), NULLIF((
              SELECT MAX(visit_date) FROM moz_historyvisits
              WHERE place_id = fk
            ), 0), STRFTIME('%s', 'now', 'localtime', 'utc') * 1000000)
        WHERE NULLIF(dateAdded, 0) IS NULL OR
              NULLIF(lastModified, 0) IS NULL`,
      },

      // S.4 reset added dates that are ahead of last modified dates.
      {
        query: `UPDATE moz_bookmarks
         SET dateAdded = lastModified
         WHERE dateAdded > lastModified`,
      },
    ];

    // Create triggers for updating Sync metadata. The "sync change" trigger
    // bumps the parent's change counter when we update a GUID or move an item
    // to a different folder, since Sync stores the list of child GUIDs on the
    // parent. The "sync tombstone" trigger inserts tombstones for deleted
    // synced bookmarks.
    cleanupStatements.unshift({
      query: `CREATE TEMP TRIGGER IF NOT EXISTS moz_bm_sync_change_temp_trigger
      AFTER UPDATE OF guid, parent, position ON moz_bookmarks
      FOR EACH ROW
      BEGIN
        UPDATE moz_bookmarks
        SET syncChangeCounter = syncChangeCounter + 1
        WHERE id IN (OLD.parent, NEW.parent, NEW.id);
      END`,
    });
    cleanupStatements.unshift({
      query: `CREATE TEMP TRIGGER IF NOT EXISTS moz_bm_sync_tombstone_temp_trigger
      AFTER DELETE ON moz_bookmarks
      FOR EACH ROW WHEN OLD.guid NOT NULL AND
                        OLD.syncStatus <> 1
      BEGIN
        UPDATE moz_bookmarks
        SET syncChangeCounter = syncChangeCounter + 1
        WHERE id = OLD.parent;

        INSERT INTO moz_bookmarks_deleted(guid, dateRemoved)
        VALUES(OLD.guid, STRFTIME('%s', 'now', 'localtime', 'utc') * 1000000);
      END`,
    });
    cleanupStatements.push({
      query: `DROP TRIGGER moz_bm_sync_change_temp_trigger`,
    });
    cleanupStatements.push({
      query: `DROP TRIGGER moz_bm_sync_tombstone_temp_trigger`,
    });

    return cleanupStatements;
  },

  /**
   * Tries to vacuum the database.
   *
   * Note: although this function isn't actually async, we keep it async to
   * allow us to maintain a simple, consistent API for the tasks within this object.
   *
   * @return {Promise} resolves when database is vacuumed.
   * @resolves to an array of logs for this task.
   * @rejects if we are unable to vacuum database.
   */
  async vacuum() {
    let logs = [];
    let placesDbPath = PathUtils.join(PathUtils.profileDir, "places.sqlite");
    let info = await IOUtils.stat(placesDbPath);
    logs.push(`Initial database size is ${parseInt(info.size / 1024)}KiB`);
    return lazy.PlacesUtils.withConnectionWrapper(
      "PlacesDBUtils: vacuum",
      async db => {
        await db.execute("VACUUM");
        logs.push("The database has been vacuumed");
        info = await IOUtils.stat(placesDbPath);
        logs.push(`Final database size is ${parseInt(info.size / 1024)}KiB`);
        return logs;
      }
    ).catch(() => {
      PlacesDBUtils.clearPendingTasks();
      throw new Error("Unable to vacuum database");
    });
  },

  /**
   * Forces a full expiration on the database.
   *
   * Note: although this function isn't actually async, we keep it async to
   * allow us to maintain a simple, consistent API for the tasks within this object.
   *
   * @return {Promise} resolves when the database in cleaned up.
   * @resolves to an array of logs for this task.
   */
  async expire() {
    let logs = [];

    let expiration = Cc["@mozilla.org/places/expiration;1"].getService(
      Ci.nsIObserver
    );

    let returnPromise = new Promise(res => {
      let observer = (subject, topic) => {
        Services.obs.removeObserver(observer, topic);
        logs.push("Database cleaned up");
        res(logs);
      };
      Services.obs.addObserver(
        observer,
        lazy.PlacesUtils.TOPIC_EXPIRATION_FINISHED
      );
    });

    // Force an orphans expiration step.
    expiration.observe(null, "places-debug-start-expiration", 0);
    return returnPromise;
  },

  /**
   * Collects statistical data on the database.
   *
   * @return {Promise} resolves when statistics are collected.
   * @resolves to an array of logs for this task.
   * @rejects if we are unable to collect stats for some reason.
   */
  async stats() {
    let logs = [];
    let placesDbPath = PathUtils.join(PathUtils.profileDir, "places.sqlite");
    let info = await IOUtils.stat(placesDbPath);
    logs.push(`Places.sqlite size is ${parseInt(info.size / 1024)}KiB`);
    let faviconsDbPath = PathUtils.join(
      PathUtils.profileDir,
      "favicons.sqlite"
    );
    info = await IOUtils.stat(faviconsDbPath);
    logs.push(`Favicons.sqlite size is ${parseInt(info.size / 1024)}KiB`);

    // Execute each step async.
    let pragmas = [
      "user_version",
      "page_size",
      "cache_size",
      "journal_mode",
      "synchronous",
    ].map(p => `pragma_${p}`);
    let pragmaQuery = `SELECT * FROM ${pragmas.join(", ")}`;
    await lazy.PlacesUtils.withConnectionWrapper(
      "PlacesDBUtils: pragma for stats",
      async db => {
        let row = (await db.execute(pragmaQuery))[0];
        for (let i = 0; i != pragmas.length; i++) {
          logs.push(`${pragmas[i]} is ${row.getResultByIndex(i)}`);
        }
      }
    ).catch(() => {
      logs.push("Could not set pragma for stat collection");
    });

    // Get maximum number of unique URIs.
    try {
      let limitURIs = await Cc["@mozilla.org/places/expiration;1"]
        .getService(Ci.nsISupports)
        .wrappedJSObject.getPagesLimit();
      logs.push(
        "History can store a maximum of " + limitURIs + " unique pages"
      );
    } catch (ex) {}

    let query = "SELECT name FROM sqlite_master WHERE type = :type";
    let params = {};
    let _getTableCount = async tableName => {
      let db = await lazy.PlacesUtils.promiseDBConnection();
      let rows = await db.execute(`SELECT count(*) FROM ${tableName}`);
      logs.push(
        `Table ${tableName} has ${rows[0].getResultByIndex(0)} records`
      );
    };

    try {
      params.type = "table";
      let db = await lazy.PlacesUtils.promiseDBConnection();
      await db.execute(query, params, r =>
        _getTableCount(r.getResultByIndex(0))
      );
    } catch (ex) {
      throw new Error("Unable to collect stats.");
    }

    let details = await PlacesDBUtils.getEntitiesStats();
    logs.push(
      `Pages sequentiality: ${details.get("moz_places").sequentialityPerc}`
    );
    let entities = Array.from(details.keys()).sort((a, b) => {
      return details.get(a).sizePerc - details.get(b).sizePerc;
    });
    for (let key of entities) {
      let info = details.get(key);
      logs.push(
        `${key}: ${info.sizeBytes / 1024}KiB (${info.sizePerc}%), ${
          info.efficiencyPerc
        }% eff.`
      );
    }

    return logs;
  },

  /**
   * Collects telemetry data and reports it to Telemetry.
   *
   * Note: although this function isn't actually async, we keep it async to
   * allow us to maintain a simple, consistent API for the tasks within this object.
   *
   */
  async telemetry() {
    // This will be populated with one integer property for each probe result,
    // using the histogram name as key.
    let probeValues = {};

    // The following array contains an ordered list of entries that are
    // processed to collect telemetry data.  Each entry has these properties:
    //
    //  histogram: Name of the telemetry histogram to update.
    //  query:     This is optional.  If present, contains a database command
    //             that will be executed asynchronously, and whose result will
    //             be added to the telemetry histogram.
    //  callback:  This is optional.  If present, contains a function that must
    //             return the value that will be added to the telemetry
    //             histogram. If a query is also present, its result is passed
    //             as the first argument of the function.  If the function
    //             raises an exception, no data is added to the histogram.
    //
    // Since all queries are executed in order by the database backend, the
    // callbacks can also use the result of previous queries stored in the
    // probeValues object.
    let probes = [
      {
        histogram: "PLACES_PAGES_COUNT",
        query: "SELECT count(*) FROM moz_places",
      },

      {
        histogram: "PLACES_BOOKMARKS_COUNT",
        query: `SELECT count(*) FROM moz_bookmarks b
                    JOIN moz_bookmarks t ON t.id = b.parent
                    AND t.parent <> :tags_folder
                    WHERE b.type = :type_bookmark`,
        params: {
          tags_folder: lazy.PlacesUtils.tagsFolderId,
          type_bookmark: lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK,
        },
      },

      {
        histogram: "PLACES_TAGS_COUNT",
        query: `SELECT count(*) FROM moz_bookmarks
                    WHERE parent = :tags_folder`,
        params: {
          tags_folder: lazy.PlacesUtils.tagsFolderId,
        },
      },

      {
        histogram: "PLACES_KEYWORDS_COUNT",
        query: "SELECT count(*) FROM moz_keywords",
      },

      {
        histogram: "PLACES_SORTED_BOOKMARKS_PERC",
        query: `SELECT IFNULL(ROUND((
                      SELECT count(*) FROM moz_bookmarks b
                      JOIN moz_bookmarks p ON p.id = b.parent
                      JOIN moz_bookmarks g ON g.id = p.parent
                      WHERE g.guid <> :root_guid
                        AND g.guid <> :tags_guid
                        AND b.type  = :type_bookmark
                      ) * 100 / (
                      SELECT count(*) FROM moz_bookmarks b
                      JOIN moz_bookmarks p ON p.id = b.parent
                      JOIN moz_bookmarks g ON g.id = p.parent
                      AND g.guid <> :tags_guid
                      WHERE b.type = :type_bookmark
                    )), 0)`,
        params: {
          root_guid: lazy.PlacesUtils.bookmarks.rootGuid,
          tags_guid: lazy.PlacesUtils.bookmarks.tagsGuid,
          type_bookmark: lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK,
        },
      },

      {
        histogram: "PLACES_TAGGED_BOOKMARKS_PERC",
        query: `SELECT IFNULL(ROUND((
                      SELECT count(*) FROM moz_bookmarks b
                      JOIN moz_bookmarks t ON t.id = b.parent
                      AND t.parent = :tags_folder
                      ) * 100 / (
                      SELECT count(*) FROM moz_bookmarks b
                      JOIN moz_bookmarks t ON t.id = b.parent
                      AND t.parent <> :tags_folder
                      WHERE b.type = :type_bookmark
                    )), 0)`,
        params: {
          tags_folder: lazy.PlacesUtils.tagsFolderId,
          type_bookmark: lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK,
        },
      },

      {
        histogram: "PLACES_DATABASE_FILESIZE_MB",
        async callback() {
          let placesDbPath = PathUtils.join(
            PathUtils.profileDir,
            "places.sqlite"
          );
          let info = await IOUtils.stat(placesDbPath);
          return parseInt(info.size / BYTES_PER_MEBIBYTE);
        },
      },

      {
        histogram: "PLACES_DATABASE_FAVICONS_FILESIZE_MB",
        async callback() {
          let faviconsDbPath = PathUtils.join(
            PathUtils.profileDir,
            "favicons.sqlite"
          );
          let info = await IOUtils.stat(faviconsDbPath);
          return parseInt(info.size / BYTES_PER_MEBIBYTE);
        },
      },

      {
        histogram: "PLACES_ANNOS_PAGES_COUNT",
        query: "SELECT count(*) FROM moz_annos",
      },

      {
        histogram: "PLACES_MAINTENANCE_DAYSFROMLAST",
        callback() {
          try {
            let lastMaintenance = Services.prefs.getIntPref(
              "places.database.lastMaintenance"
            );
            let nowSeconds = parseInt(Date.now() / 1000);
            return parseInt((nowSeconds - lastMaintenance) / 86400);
          } catch (ex) {
            return 60;
          }
        },
      },
      {
        scalar: "places.pages_need_frecency_recalculation",
        query: "SELECT count(*) FROM moz_places WHERE recalc_frecency = 1",
      },
      {
        scalar: "places.previousday_visits",
        query: `SELECT COUNT(*) from moz_places
                      WHERE hidden=0 AND last_visit_date < (strftime('%s', 'now', 'start of day') * 1000000)
                      AND last_visit_date > (strftime('%s', 'now', 'start of day', '-1 day') * 1000000)
                      AND last_visit_date IS NOT NULL;`,
      },
    ];

    for (let probe of probes) {
      let val;
      if ("query" in probe) {
        let db = await lazy.PlacesUtils.promiseDBConnection();
        val = (
          await db.execute(probe.query, probe.params || {})
        )[0].getResultByIndex(0);
      }
      // Report the result of the probe through Telemetry.
      // The resulting promise cannot reject.
      if ("callback" in probe) {
        val = await probe.callback(val);
      }
      probeValues[probe.histogram || probe.scalar] = val;
      if (probe.histogram) {
        Services.telemetry.getHistogramById(probe.histogram).add(val);
      } else if (probe.scalar) {
        Services.telemetry.scalarSet(probe.scalar, val);
      } else {
        throw new Error("Unknwon telemetry probe type");
      }
    }
  },

  /**
   * Remove old and useless places.sqlite.corrupt files.
   *
   * @resolves to an array of logs for this task.
   *
   */
  async removeOldCorruptDBs() {
    let logs = [];
    logs.push(
      "> Cleanup profile from places.sqlite.corrupt files older than " +
        CORRUPT_DB_RETAIN_DAYS +
        " days."
    );
    let re = /places\.sqlite(-\d)?\.corrupt$/;
    let currentTime = Date.now();
    let children = await IOUtils.getChildren(PathUtils.profileDir);
    try {
      for (let entry of children) {
        let fileInfo = await IOUtils.stat(entry);
        let lastModificationDate;
        if (fileInfo.type == "regular" && re.test(entry)) {
          lastModificationDate = fileInfo.lastModified;
          try {
            // Convert milliseconds to days.
            let days = Math.ceil(
              (currentTime - lastModificationDate) / MS_PER_DAY
            );
            if (days >= CORRUPT_DB_RETAIN_DAYS || days < 0) {
              await IOUtils.remove(entry);
            }
          } catch (error) {
            logs.push("Could not remove file: " + entry, error);
          }
        }
      }
    } catch (error) {
      logs.push("removeOldCorruptDBs failed", error);
    }
    return logs;
  },

  /**
   * Gets detailed statistics about database entities like tables and indices.
   * @returns {Map} a Map by table name, containing an object with the following
   *          properties:
   *            - efficiencyPerc: percentage filling of pages, an high
   *              efficiency means most pages are filled up almost completely.
   *              This value is not particularly useful with a low number of
   *              pages.
   *            - sizeBytes: size of the entity in bytes
   *            - pages: number of pages of the entity
   *            - sizePerc: percentage of the total database size
   *            - sequentialityPerc: percentage of sequential pages, this is
   *              a global value of the database, thus it's the same for every
   *              entity, and it can be used to evaluate fragmentation and the
   *              need for vacuum.
   */
  async getEntitiesStats() {
    let db = await lazy.PlacesUtils.promiseDBConnection();
    let rows = await db.execute(`
      /* do not warn (bug no): no need for index */
      SELECT name,
      round((pgsize - unused) * 100.0 / pgsize, 1) as efficiency_perc,
      pgsize as size_bytes, pageno as pages,
      round(pgsize * 100.0 / (SELECT sum(pgsize) FROM dbstat WHERE aggregate = TRUE), 1) as size_perc,
      round((
        WITH s(row, pageno) AS (
          SELECT row_number() OVER (ORDER BY path), pageno FROM dbstat ORDER BY path
        )
        SELECT sum(s1.pageno+1==s2.pageno)*100.0/count(*)
        FROM s AS s1, s AS s2
        WHERE s1.row+1=s2.row
      ), 1) AS sequentiality_perc
      FROM dbstat
      WHERE aggregate = TRUE
    `);
    let entitiesByName = new Map();
    for (let row of rows) {
      let details = {
        efficiencyPerc: row.getResultByName("efficiency_perc"),
        pages: row.getResultByName("pages"),
        sizeBytes: row.getResultByName("size_bytes"),
        sizePerc: row.getResultByName("size_perc"),
        sequentialityPerc: row.getResultByName("sequentiality_perc"),
      };
      entitiesByName.set(row.getResultByName("name"), details);
    }
    return entitiesByName;
  },

  /**
   * Gets detailed statistics about database entities and their respective row
   * counts.
   * @returns {Array} An array that augments each object returned by
   *          {@link getEntitiesStats} with the following extra properties:
   *            - entity: name of the entity
   *            - count: row count of the entity
   */
  async getEntitiesStatsAndCounts() {
    let stats = await PlacesDBUtils.getEntitiesStats();
    let data = [];
    let db = await lazy.PlacesUtils.promiseDBConnection();
    for (let [entity, value] of stats) {
      let count = "-";
      try {
        if (
          entity.startsWith("moz_") &&
          !entity.endsWith("index") &&
          entity != "moz_places_visitcount" /* bug in index name */
        ) {
          count = (
            await db.execute(`SELECT count(*) FROM ${entity}`)
          )[0].getResultByIndex(0);
        }
      } catch (ex) {
        console.error(ex);
      }
      data.push(Object.assign(value, { entity, count }));
    }
    return data;
  },

  /**
   * Runs a list of tasks, returning a Map when done.
   *
   * @param tasks
   *        Array of tasks to be executed, in form of pointers to methods in
   *        this module.
   * @return {Promise}
   *        A promise that resolves with a Map[taskName(String) -> Object].
   *        The Object has the following properties:
   *         - succeeded: boolean
   *         - logs: an array of strings containing the messages logged by the task
   */
  async runTasks(tasks) {
    if (!this._registeredShutdownObserver) {
      this._registeredShutdownObserver = true;
      lazy.PlacesUtils.registerShutdownFunction(() => {
        this._isShuttingDown = true;
      });
    }
    PlacesDBUtils._clearTaskQueue = false;
    let tasksMap = new Map();
    for (let task of tasks) {
      if (PlacesDBUtils._isShuttingDown) {
        tasksMap.set(task.name, {
          succeeded: false,
          logs: ["Shutting down, will not schedule the task."],
        });
        continue;
      }

      if (PlacesDBUtils._clearTaskQueue) {
        tasksMap.set(task.name, {
          succeeded: false,
          logs: ["The task queue was cleared by an error in another task."],
        });
        continue;
      }

      let result = await task()
        .then((logs = [`${task.name} complete`]) => ({ succeeded: true, logs }))
        .catch(err => ({ succeeded: false, logs: [err.message] }));
      tasksMap.set(task.name, result);
    }
    return tasksMap;
  },
};

async function integrity(dbName) {
  async function check(db) {
    let row;
    await db.execute("PRAGMA integrity_check", null, (r, cancel) => {
      row = r;
      cancel();
    });
    return row.getResultByIndex(0) === "ok";
  }

  // Create a new connection for this check, so we can operate independently
  // from a broken Places service.
  // openConnection returns an exception with .result == Cr.NS_ERROR_FILE_CORRUPTED,
  // we should do the same everywhere we want maintenance to try replacing the
  // database on next startup.
  let path = PathUtils.join(PathUtils.profileDir, dbName);
  let db = await lazy.Sqlite.openConnection({ path });
  try {
    if (await check(db)) {
      return;
    }

    // We stopped due to an integrity corruption, try to fix it if possible.
    // First, try to reindex, this often fixes simple indices problems.
    try {
      await db.execute("REINDEX");
    } catch (ex) {
      throw new Components.Exception(
        "Impossible to reindex database",
        Cr.NS_ERROR_FILE_CORRUPTED
      );
    }

    // Check again.
    if (!(await check(db))) {
      throw new Components.Exception(
        "The database is still corrupt",
        Cr.NS_ERROR_FILE_CORRUPTED
      );
    }
  } finally {
    await db.close();
  }
}

export function PlacesDBUtilsIdleMaintenance() {}

PlacesDBUtilsIdleMaintenance.prototype = {
  observe(subject, topic) {
    switch (topic) {
      case "idle-daily": {
        // Once a week run places.sqlite maintenance tasks.
        let lastMaintenance = Services.prefs.getIntPref(
          "places.database.lastMaintenance",
          0
        );
        let nowSeconds = parseInt(Date.now() / 1000);
        if (lastMaintenance < nowSeconds - MAINTENANCE_INTERVAL_SECONDS) {
          PlacesDBUtils.maintenanceOnIdle();
        }
        break;
      }
      default:
        throw new Error("Trying to handle an unknown category.");
    }
  },
  classID: Components.ID("d38926e0-29c1-11eb-8588-0800200c9a66"),
  QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
};
PK
!<O^9U�� modules/PlacesExpiration.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * vim: sw=2 ts=2 sts=2 expandtab
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This component handles history and orphans expiration.
 * Expiration runs:
 * - At idle, but just once, we stop any other kind of expiration during idle
 *   to preserve batteries in portable devices.
 * - At shutdown, only if the database is dirty, we should still avoid to
 *   expire too heavily on shutdown.
 * - On a repeating timer we expire in small chunks.
 *
 * Expiration algorithm will adapt itself based on:
 * - Memory size of the device.
 * - Status of the database (clean or dirty).
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
});

// Last expiration step should run before the final sync.
const TOPIC_DEBUG_START_EXPIRATION = "places-debug-start-expiration";
const TOPIC_IDLE_BEGIN = "idle";
const TOPIC_IDLE_END = "active";
const TOPIC_IDLE_DAILY = "idle-daily";
const TOPIC_TESTING_MODE = "testing-mode";
const TOPIC_TEST_INTERVAL_CHANGED = "test-interval-changed";

// This value determines which systems we consider to have limited memory.
// This is used to protect against large database sizes on those systems.
const DATABASE_MEMORY_CONSTRAINED_THRESHOLD = 2147483648; // 2 GiB

// This value determines which systems we consider to have limited disk space.
// This is used to protect against large database sizes on those systems.
const DATABASE_DISK_CONSTRAINED_THRESHOLD = 5368709120; // 5 GiB

// Maximum size of the optimal database.  High-end hardware has plenty of
// memory and disk space, but performances don't grow linearly.
const DATABASE_MAX_SIZE = 78643200; // 75 MiB
// If the physical memory size is bogus, fallback to this.
const MEMSIZE_FALLBACK_BYTES = 268435456; // 256 MiB
// If the disk available space is bogus, fallback to this.
const DISKSIZE_FALLBACK_BYTES = 268435456; // 256 MiB

// Max number of entries to expire at each expiration step.
// This value is globally used for different kind of data we expire, can be
// tweaked based on data type.  See below in getQuery.
const EXPIRE_LIMIT_PER_STEP = 6;
// When we run a large expiration step, the above limit is multiplied by this.
const EXPIRE_LIMIT_PER_LARGE_STEP_MULTIPLIER = 10;

// When history is clean or dirty enough we will adapt the expiration algorithm
// to be more lazy or more aggressive.
// This is done acting on the interval between expiration steps and the number
// of expirable items.
// 1. Clean history:
//   We expire at (default interval * EXPIRE_AGGRESSIVITY_MULTIPLIER) the
//   default number of entries.
// 2. Dirty history:
//   We expire at the default interval, but a greater number of entries
//   (default number of entries * EXPIRE_AGGRESSIVITY_MULTIPLIER).
const EXPIRE_AGGRESSIVITY_MULTIPLIER = 3;

// This is the average size in bytes of an URI entry in the database.
// Magic numbers are determined through analysis of the distribution of a ratio
// between number of unique URIs and database size among our users.
// Used as a fall back value when it's not possible to calculate the real value.
const URIENTRY_AVG_SIZE = 700;

// Seconds of idle time before starting a larger expiration step.
// Notice during idle we stop the expiration timer since we don't want to hurt
// stand-by or mobile devices batteries.
const IDLE_TIMEOUT_SECONDS = 5 * 60;

// If the number of pages over history limit is greater than this threshold,
// expiration will be more aggressive, to bring back history to a saner size.
const OVERLIMIT_PAGES_THRESHOLD = 1000;

// Milliseconds in a day.
const MSECS_PER_DAY = 86400000;

// When we expire we can use these limits:
// - SMALL for usual partial expirations, will expire a small chunk.
// - LARGE for idle or shutdown expirations, will expire a large chunk.
// - UNLIMITED will expire all the orphans.
// - DEBUG will use a known limit, passed along with the debug notification.
const LIMIT = {
  SMALL: 0,
  LARGE: 1,
  UNLIMITED: 2,
  DEBUG: 3,
};

// Represents the status of history database.
const STATUS = {
  CLEAN: 0,
  DIRTY: 1,
  UNKNOWN: 2,
};

// Represents actions on which a query will run.
const ACTION = {
  TIMED: 1 << 0, // happens every this.intervalSeconds
  TIMED_OVERLIMIT: 1 << 1, // like TIMED but only when history is over limits
  SHUTDOWN_DIRTY: 1 << 2, // happens at shutdown for DIRTY state
  IDLE_DIRTY: 1 << 3, // happens on idle for DIRTY state
  IDLE_DAILY: 1 << 4, // happens once a day on idle
  DEBUG: 1 << 5, // happens on TOPIC_DEBUG_START_EXPIRATION
};

// The queries we use to expire.
const EXPIRATION_QUERIES = {
  // Some visits can be expired more often than others, cause they are less
  // useful to the user and can pollute awesomebar results:
  // 1. urls over 255 chars having only one visit
  // 2. downloads
  // 3. non-typed hidden single-visit urls
  // We never expire redirect targets, because they are currently necessary to
  // recognize redirect sources (see Bug 468710 for better options).
  QUERY_FIND_EXOTIC_VISITS_TO_EXPIRE: {
    sql: `INSERT INTO expiration_notify (v_id, url, guid, visit_date, reason)
      WITH visits AS (
        SELECT v.id, url, guid, visit_type, visit_date, visit_count, hidden, typed
        FROM moz_historyvisits v
        JOIN moz_places h ON h.id = v.place_id
        WHERE visit_date < strftime('%s','now','localtime','start of day','-90 days','utc') * 1000000
        AND visit_type NOT IN (5,6)
      )
      SELECT id, url, guid, visit_date, "exotic"
      FROM visits
      WHERE (hidden = 1 AND typed = 0 AND visit_count <= 1) OR visit_type = 7
      UNION ALL
      SELECT id, url, guid, visit_date, "exotic"
      FROM visits
      WHERE visit_count = 1 AND LENGTH(url) > 255
      ORDER BY visit_date ASC
      LIMIT :limit_visits`,
    actions:
      ACTION.TIMED_OVERLIMIT |
      ACTION.IDLE_DIRTY |
      ACTION.IDLE_DAILY |
      ACTION.DEBUG,
  },

  // Finds visits to be expired when history is over the unique pages limit,
  // otherwise will return nothing.
  // This explicitly excludes any visits added in the last 7 days, to protect
  // users with thousands of bookmarks from constantly losing history.
  QUERY_FIND_VISITS_TO_EXPIRE: {
    sql: `INSERT INTO expiration_notify
            (v_id, url, guid, visit_date, expected_results)
          SELECT v.id, h.url, h.guid, v.visit_date, :limit_visits
          FROM moz_historyvisits v
          JOIN moz_places h ON h.id = v.place_id
          WHERE (SELECT COUNT(*) FROM moz_places) > :max_uris
          AND visit_date < strftime('%s','now','localtime','start of day','-7 days','utc') * 1000000
          ORDER BY v.visit_date ASC
          LIMIT :limit_visits`,
    actions:
      ACTION.TIMED_OVERLIMIT |
      ACTION.IDLE_DIRTY |
      ACTION.IDLE_DAILY |
      ACTION.DEBUG,
  },

  // Removes the previously found visits.
  QUERY_EXPIRE_VISITS: {
    sql: `DELETE FROM moz_historyvisits WHERE id IN (
            SELECT v_id FROM expiration_notify WHERE v_id NOTNULL
          )`,
    actions:
      ACTION.TIMED_OVERLIMIT |
      ACTION.IDLE_DIRTY |
      ACTION.IDLE_DAILY |
      ACTION.DEBUG,
  },

  // Finds orphan URIs in the database.
  // Notice we won't notify single removed URIs on History.clear(), so we don't
  // run this query in such a case, but just delete URIs.
  // This could run in the middle of adding a visit or bookmark to a new page.
  // In such a case since it is async, could end up expiring the orphan page
  // before it actually gets the new visit or bookmark.
  // Thus, since new pages get frecency -1, we filter on that.
  QUERY_FIND_URIS_TO_EXPIRE: {
    sql: `INSERT INTO expiration_notify (p_id, url, guid, visit_date)
          SELECT h.id, h.url, h.guid, h.last_visit_date
          FROM moz_places h
          LEFT JOIN moz_historyvisits v ON h.id = v.place_id
          WHERE h.last_visit_date IS NULL
            AND h.foreign_count = 0
            AND v.id IS NULL
            AND frecency <> -1
          LIMIT :limit_uris`,
    actions:
      ACTION.TIMED |
      ACTION.TIMED_OVERLIMIT |
      ACTION.SHUTDOWN_DIRTY |
      ACTION.IDLE_DIRTY |
      ACTION.IDLE_DAILY |
      ACTION.DEBUG,
  },

  // Expire found URIs from the database.
  QUERY_EXPIRE_URIS: {
    sql: `DELETE FROM moz_places WHERE id IN (
            SELECT p_id FROM expiration_notify WHERE p_id NOTNULL
          ) AND foreign_count = 0 AND last_visit_date ISNULL`,
    actions:
      ACTION.TIMED |
      ACTION.TIMED_OVERLIMIT |
      ACTION.SHUTDOWN_DIRTY |
      ACTION.IDLE_DIRTY |
      ACTION.IDLE_DAILY |
      ACTION.DEBUG,
  },

  // Hosts accumulated during the places delete are updated through a trigger
  // (see nsPlacesTriggers.h).
  QUERY_UPDATE_HOSTS: {
    sql: `DELETE FROM moz_updateoriginsdelete_temp`,
    actions:
      ACTION.TIMED |
      ACTION.TIMED_OVERLIMIT |
      ACTION.SHUTDOWN_DIRTY |
      ACTION.IDLE_DIRTY |
      ACTION.IDLE_DAILY |
      ACTION.DEBUG,
  },

  // Expire from favicons any page that has only relations older than 180 days,
  // if the page is not bookmarked, and we have a root icon that can be used
  // as a placeholder until the page is visited again.
  // The moz_pages_to_icons entries are removed by the table's FOREIGN KEY,
  // while orphan icons are removed by the following queries.
  QUERY_EXPIRE_OLD_FAVICONS: {
    sql: `
    DELETE FROM moz_pages_w_icons WHERE id IN (
      WITH pages_with_old_relations (page_id, page_url_hash) AS (
        SELECT page_id, page_url_hash
        FROM moz_icons_to_pages ip
        JOIN moz_pages_w_icons p ON p.id = page_id
        GROUP BY page_id
        HAVING max(expire_ms) < strftime('%s','now','localtime','start of day','-180 days','utc') * 1000
      )
      SELECT page_id
      FROM pages_with_old_relations
      JOIN moz_places h ON h.url_hash = page_url_hash
      JOIN moz_origins o ON h.origin_id = o.id
      WHERE foreign_count = 0
      AND EXISTS (
        SELECT 1 FROM moz_icons
        WHERE root = 1
          AND fixed_icon_url_hash = hash(fixup_url(o.host) || '/favicon.ico')
      )
      LIMIT 100
    )`,
    actions: ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG,
  },

  // Expire orphan pages from the icons database.
  QUERY_EXPIRE_FAVICONS_PAGES: {
    sql: `DELETE FROM moz_pages_w_icons
          WHERE page_url_hash NOT IN (
            SELECT url_hash FROM moz_places
          ) OR NOT EXISTS (
            SELECT 1 FROM moz_icons_to_pages WHERE page_id = moz_pages_w_icons.id
          )`,
    actions:
      ACTION.TIMED_OVERLIMIT |
      ACTION.SHUTDOWN_DIRTY |
      ACTION.IDLE_DIRTY |
      ACTION.IDLE_DAILY |
      ACTION.DEBUG,
  },

  // Expire orphan icons from the database.
  QUERY_EXPIRE_FAVICONS: {
    sql: `DELETE FROM moz_icons WHERE id IN (
            SELECT id FROM moz_icons WHERE root = 0
            EXCEPT
            SELECT icon_id FROM moz_icons_to_pages
          )`,
    actions:
      ACTION.TIMED_OVERLIMIT |
      ACTION.SHUTDOWN_DIRTY |
      ACTION.IDLE_DIRTY |
      ACTION.IDLE_DAILY |
      ACTION.DEBUG,
  },

  // Expire orphan page annotations from the database.
  QUERY_EXPIRE_ANNOS: {
    sql: `DELETE FROM moz_annos WHERE id in (
            SELECT a.id FROM moz_annos a
            LEFT JOIN moz_places h ON a.place_id = h.id
            WHERE h.id IS NULL
            LIMIT :limit_annos
          )`,
    actions:
      ACTION.TIMED |
      ACTION.TIMED_OVERLIMIT |
      ACTION.SHUTDOWN_DIRTY |
      ACTION.IDLE_DIRTY |
      ACTION.IDLE_DAILY |
      ACTION.DEBUG,
  },

  // Expire orphan inputhistory.
  QUERY_EXPIRE_INPUTHISTORY: {
    sql: `DELETE FROM moz_inputhistory
          WHERE place_id IN (SELECT p_id FROM expiration_notify)
          AND place_id IN (
            SELECT i.place_id FROM moz_inputhistory i
            LEFT JOIN moz_places h ON h.id = i.place_id
            WHERE h.id IS NULL
            LIMIT :limit_inputhistory
          )`,
    actions:
      ACTION.TIMED |
      ACTION.TIMED_OVERLIMIT |
      ACTION.SHUTDOWN_DIRTY |
      ACTION.IDLE_DIRTY |
      ACTION.IDLE_DAILY |
      ACTION.DEBUG,
  },

  // Select entries for notifications.
  // If p_id is set whole_entry = 1, then we have expired the full page.
  // Either p_id or v_id are always set.
  QUERY_SELECT_NOTIFICATIONS: {
    sql: `/* do not warn (bug no): temp table has no index */
          SELECT url, guid, MAX(visit_date) AS visit_date,
                 MAX(IFNULL(MIN(p_id, 1), MIN(v_id, 0))) AS whole_entry,
                 MAX(expected_results) AS expected_results,
                 (SELECT MAX(visit_date) FROM expiration_notify
                  WHERE reason = "expired" AND url = n.url AND p_id ISNULL
                 ) AS most_recent_expired_visit
          FROM expiration_notify n
          GROUP BY url`,
    actions:
      ACTION.TIMED |
      ACTION.TIMED_OVERLIMIT |
      ACTION.SHUTDOWN_DIRTY |
      ACTION.IDLE_DIRTY |
      ACTION.IDLE_DAILY |
      ACTION.DEBUG,
  },

  // Empty the notifications table.
  QUERY_DELETE_NOTIFICATIONS: {
    sql: "DELETE FROM expiration_notify",
    actions:
      ACTION.TIMED |
      ACTION.TIMED_OVERLIMIT |
      ACTION.SHUTDOWN_DIRTY |
      ACTION.IDLE_DIRTY |
      ACTION.IDLE_DAILY |
      ACTION.DEBUG,
  },

  // Expire interactions older than N days.
  QUERY_EXPIRE_INTERACTIONS: {
    sql: `DELETE FROM moz_places_metadata
          WHERE id IN (
            SELECT id FROM moz_places_metadata
            WHERE updated_at < strftime('%s','now','localtime','-' || :days_interactions || ' day','start of day','utc') * 1000
            ORDER BY updated_at ASC
            LIMIT :limit_interactions
          )`,
    get disabled() {
      return !Services.prefs.getBoolPref(
        "browser.places.interactions.enabled",
        false
      );
    },
    actions:
      ACTION.TIMED_OVERLIMIT |
      ACTION.SHUTDOWN_DIRTY |
      ACTION.IDLE_DIRTY |
      ACTION.IDLE_DAILY |
      ACTION.DEBUG,
  },
};

export function nsPlacesExpiration() {
  // Allows other components to easily access getPagesLimit.
  this.wrappedJSObject = this;

  XPCOMUtils.defineLazyServiceGetter(
    this,
    "_idle",
    "@mozilla.org/widget/useridleservice;1",
    "nsIUserIdleService"
  );

  // Max number of unique URIs to retain in history.
  // Notice this is a lazy limit.  This means we will start to expire if we will
  // go over it, but we won't ensure that we will stop exactly when we reach it,
  // instead we will stop after the next expiration step that will bring us
  // below it.
  // If this preference does not exist or has a negative value, we will
  // calculate a limit based on current hardware.
  XPCOMUtils.defineLazyPreferenceGetter(
    this,
    "maxPages",
    "places.history.expiration.max_pages",
    -1,
    () => {
      // Clear the cache.
      dump("max_pages changing\n");
      this._pagesLimit = null;
    }
  );

  // Seconds between each expiration step.
  XPCOMUtils.defineLazyPreferenceGetter(
    this,
    "intervalSeconds",
    "places.history.expiration.interval_seconds",
    3 * 60, // 3 minutes
    () => {
      // Renew the timer with the new interval value.
      this._newTimer();
    },
    v => (v > 0 ? v : 3 * 60) // Accept only positive values.
  );

  this._dbInitializedPromise = lazy.PlacesUtils.withConnectionWrapper(
    "PlacesExpiration.sys.mjs: setup",
    async db => {
      await db.execute(
        `CREATE TEMP TABLE expiration_notify (
          id INTEGER PRIMARY KEY,
          v_id INTEGER,
          p_id INTEGER,
          url TEXT NOT NULL,
          guid TEXT NOT NULL,
          visit_date INTEGER,
          expected_results INTEGER NOT NULL DEFAULT 0,
          reason TEXT NOT NULL DEFAULT "expired"
        )`
      );
    }
  )
    .then(() => {
      // Start the expiration timer.
      this._newTimer();
      // Expire daily on idle.
      Services.obs.addObserver(this, TOPIC_IDLE_DAILY, true);
    })
    .catch(console.error);

  // Block shutdown.
  let shutdownClient =
    lazy.PlacesUtils.history.connectionShutdownClient.jsclient;
  shutdownClient.addBlocker("Places Expiration: shutdown", () => {
    if (this._shuttingDown) {
      return;
    }
    this._shuttingDown = true;
    this.expireOnIdle = false;
    if (this._timer) {
      this._timer.cancel();
      this._timer = null;
    }
    // If the database is dirty, we want to expire some entries, to speed up
    // the expiration process.
    if (this.status == STATUS.DIRTY) {
      this._expire(ACTION.SHUTDOWN_DIRTY, LIMIT.LARGE).catch(console.error);
    }
  });
}

nsPlacesExpiration.prototype = {
  observe(aSubject, aTopic, aData) {
    if (this._shuttingDown) {
      return;
    }

    if (aTopic == TOPIC_DEBUG_START_EXPIRATION) {
      // The passed-in limit is the maximum number of visits to expire when
      // history is over capacity.  Mind to correctly handle the NaN value.
      let limit = parseInt(aData);
      if (limit == -1) {
        // Everything should be expired without any limit.  If history is over
        // capacity then all existing visits will be expired.
        // Should only be used in tests, since may cause dataloss.
        this._expire(ACTION.DEBUG, LIMIT.UNLIMITED).catch(console.error);
      } else if (limit > 0) {
        // The number of expired visits is limited by this amount.  It may be
        // used for testing purposes, like checking that limited queries work.
        this._debugLimit = limit;
        this._expire(ACTION.DEBUG, LIMIT.DEBUG).catch(console.error);
      } else {
        // Any other value is intended as a 0 limit, that means no visits
        // will be expired.  Even if this doesn't touch visits, it will remove
        // any orphan pages, icons, annotations and similar from the database,
        // so it may be used for cleanup purposes.
        this._debugLimit = -1;
        this._expire(ACTION.DEBUG, LIMIT.DEBUG).catch(console.error);
      }
    } else if (aTopic == TOPIC_IDLE_BEGIN) {
      // Stop the expiration timer.  We don't want to keep up expiring on idle
      // to preserve batteries on mobile devices and avoid killing stand-by.
      if (this._timer) {
        this._timer.cancel();
        this._timer = null;
      }
      if (this.expireOnIdle) {
        this._expire(ACTION.IDLE_DIRTY, LIMIT.LARGE).catch(console.error);
      }
    } else if (aTopic == TOPIC_IDLE_END) {
      // Restart the expiration timer.
      if (!this._timer) {
        this._newTimer();
      }
    } else if (aTopic == TOPIC_IDLE_DAILY) {
      this._expire(ACTION.IDLE_DAILY, LIMIT.LARGE).catch(console.error);
    } else if (aTopic == TOPIC_TESTING_MODE) {
      this._testingMode = true;
    } else if (aTopic == lazy.PlacesUtils.TOPIC_INIT_COMPLETE) {
      const placesObserver = new PlacesWeakCallbackWrapper(
        // History status is clean after a clear history.
        () => {
          this.status = STATUS.CLEAN;
        }
      );
      PlacesObservers.addListener(["history-cleared"], placesObserver);
    }
  },

  // nsINamed

  name: "nsPlacesExpiration",

  // nsITimerCallback

  notify() {
    // Run at the first idle, or after 5 minutes, whatever comes first.
    Services.tm.idleDispatchToMainThread(async () => {
      let db = await lazy.PlacesUtils.promiseDBConnection();
      let pagesCount = (
        await db.executeCached("SELECT count(*) AS count FROM moz_places")
      )[0].getResultByName("count");
      let pagesLimit = await this.getPagesLimit();
      // Check if we are over history capacity, if so visits must be expired.
      let overLimitPages = pagesCount - pagesLimit;
      let action = overLimitPages > 0 ? ACTION.TIMED_OVERLIMIT : ACTION.TIMED;
      // Adapt expiration aggressivity to the number of pages over the limit.
      let limit =
        overLimitPages > OVERLIMIT_PAGES_THRESHOLD ? LIMIT.LARGE : LIMIT.SMALL;
      this._expire(action, limit).catch(console.error);
    }, 300000);
  },

  _handleQueryResultAndAddNotification(row, notifications) {
    // We don't want to notify after shutdown.
    if (this._shuttingDown) {
      return;
    }

    // expected_results is set to the number of expected visits by
    // QUERY_FIND_VISITS_TO_EXPIRE.  We decrease that counter for each found
    // visit and if it reaches zero we mark the database as dirty, since all
    // the expected visits were expired, so it's likely the next run will
    // find more.
    let expectedResults = row.getResultByName("expected_results");
    if (expectedResults > 0) {
      if (!("_expectedResultsCount" in this)) {
        this._expectedResultsCount = expectedResults;
      }
      if (this._expectedResultsCount > 0) {
        this._expectedResultsCount--;
      }
    }

    let uri = Services.io.newURI(row.getResultByName("url"));
    let guid = row.getResultByName("guid");
    let visitDate = row.getResultByName("visit_date");
    let wholeEntry = row.getResultByName("whole_entry");
    let mostRecentExpiredVisit = row.getResultByName(
      "most_recent_expired_visit"
    );

    if (mostRecentExpiredVisit) {
      let days = parseInt(
        (Date.now() - mostRecentExpiredVisit / 1000) / MSECS_PER_DAY
      );
      if (!this._mostRecentExpiredVisitDays) {
        this._mostRecentExpiredVisitDays = days;
      } else if (days < this._mostRecentExpiredVisitDays) {
        this._mostRecentExpiredVisitDays = days;
      }
    }

    // Dispatch expiration notifications to history.
    const isRemovedFromStore = !!wholeEntry;
    notifications.push(
      new PlacesVisitRemoved({
        url: uri.spec,
        pageGuid: guid,
        reason: PlacesVisitRemoved.REASON_EXPIRED,
        isRemovedFromStore,
        isPartialVisistsRemoval: !isRemovedFromStore && visitDate > 0,
      })
    );
  },

  _shuttingDown: false,

  _status: STATUS.UNKNOWN,
  set status(aNewStatus) {
    if (aNewStatus != this._status) {
      // If status changes we should restart the timer.
      this._status = aNewStatus;
      this._newTimer();
      // If needed add/remove the cleanup step on idle.  We want to expire on
      // idle only if history is dirty, to preserve mobile devices batteries.
      this.expireOnIdle = aNewStatus == STATUS.DIRTY;
    }
  },
  get status() {
    return this._status;
  },

  async getPagesLimit() {
    if (this._pagesLimit != null) {
      return this._pagesLimit;
    }
    if (this.maxPages >= 0) {
      return (this._pagesLimit = this.maxPages);
    }

    // The user didn't specify a custom limit, so we calculate the number of
    // unique places that may fit an optimal database size on this hardware.
    // Oldest pages over this threshold will be expired.
    let memSizeBytes = MEMSIZE_FALLBACK_BYTES;
    try {
      // Limit the size on systems with small memory.
      memSizeBytes = Services.sysinfo.getProperty("memsize");
    } catch (ex) {}
    if (memSizeBytes <= 0) {
      memSizeBytes = MEMSIZE_FALLBACK_BYTES;
    }

    let diskAvailableBytes = DISKSIZE_FALLBACK_BYTES;
    try {
      // Protect against a full disk or tiny quota.
      diskAvailableBytes =
        lazy.PlacesUtils.history.DBConnection.databaseFile.QueryInterface(
          Ci.nsIFile
        ).diskSpaceAvailable;
    } catch (ex) {}
    if (diskAvailableBytes <= 0) {
      diskAvailableBytes = DISKSIZE_FALLBACK_BYTES;
    }

    const isMemoryConstrained =
      memSizeBytes < DATABASE_MEMORY_CONSTRAINED_THRESHOLD;
    const isDiskConstrained =
      diskAvailableBytes < DATABASE_DISK_CONSTRAINED_THRESHOLD;

    let optimalDatabaseSize = DATABASE_MAX_SIZE;
    if (isMemoryConstrained || isDiskConstrained) {
      // This size is used to protect against a large database size
      // on disks with limited space or on systems with small memory
      optimalDatabaseSize /= 2;
    }

    // Calculate avg size of a URI in the database.
    let db;
    try {
      db = await lazy.PlacesUtils.promiseDBConnection();
      if (db) {
        let row = (
          await db.execute(`SELECT * FROM pragma_page_size(),
                                              pragma_page_count(),
                                              pragma_freelist_count(),
                                              (SELECT count(*) FROM moz_places)`)
        )[0];
        let pageSize = row.getResultByIndex(0);
        let pageCount = row.getResultByIndex(1);
        let freelistCount = row.getResultByIndex(2);
        let uriCount = row.getResultByIndex(3);
        let dbSize = (pageCount - freelistCount) * pageSize;
        let avgURISize = Math.ceil(dbSize / uriCount);
        // For new profiles this value may be too large, due to the Sqlite header,
        // or Infinity when there are no pages.  Thus we must limit it.
        if (avgURISize > URIENTRY_AVG_SIZE * 3) {
          avgURISize = URIENTRY_AVG_SIZE;
        }
        return (this._pagesLimit = Math.ceil(optimalDatabaseSize / avgURISize));
      }
    } catch (ex) {
      // We may have been initialized late in the shutdown process, maybe
      // by a call to clear history on shutdown.
      // If we're unable to get a connection clone, we'll just proceed with a
      // large default value, it should not be critical at this point in the
      // application life-cycle.
    }
    return (this._pagesLimit = 100000);
  },

  _isIdleObserver: false,
  _expireOnIdle: false,
  set expireOnIdle(aExpireOnIdle) {
    // Observe idle regardless aExpireOnIdle, since we always want to stop
    // timed expiration on idle, to preserve mobile battery life.
    if (!this._isIdleObserver && !this._shuttingDown) {
      this._idle.addIdleObserver(this, IDLE_TIMEOUT_SECONDS);
      this._isIdleObserver = true;
    } else if (this._isIdleObserver && this._shuttingDown) {
      this._idle.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS);
      this._isIdleObserver = false;
    }

    // If running a debug expiration we need full control of what happens
    // but idle cleanup could activate in the middle, since tinderboxes are
    // permanently idle.  That would cause unexpected oranges, so disable it.
    if (this._debugLimit !== undefined) {
      this._expireOnIdle = false;
    } else {
      this._expireOnIdle = aExpireOnIdle;
    }
  },
  get expireOnIdle() {
    return this._expireOnIdle;
  },

  // Number of expiration steps needed to reach a CLEAN status.
  _telemetrySteps: 1,

  /**
   * Expires visits and orphans.
   *
   * @param aAction
   *        The ACTION we are expiring for.  See the ACTION const for values.
   * @param aLimit
   *        Whether to use small, large or no limits when expiring.  See the
   *        LIMIT const for values.
   */
  async _expire(aAction, aLimit) {
    // Don't try to further expire after shutdown.
    if (this._shuttingDown && aAction != ACTION.SHUTDOWN_DIRTY) {
      return;
    }
    await this._dbInitializedPromise;

    try {
      let notifications = [];
      await lazy.PlacesUtils.withConnectionWrapper(
        "PlacesExpiration.sys.mjs: expire",
        async db => {
          await db.executeTransaction(async () => {
            for (let queryType in EXPIRATION_QUERIES) {
              let query = EXPIRATION_QUERIES[queryType];
              if (query.actions & aAction && !query.disabled) {
                let params = await this._getQueryParams(
                  queryType,
                  aLimit,
                  aAction
                );
                await db.executeCached(query.sql, params, row => {
                  this._handleQueryResultAndAddNotification(row, notifications);
                });
              }
            }
          });
        }
      );
      if (notifications.length) {
        PlacesObservers.notifyListeners(notifications);
      }
    } catch (ex) {
      console.error(ex);
      return;
    }

    if (this._mostRecentExpiredVisitDays) {
      try {
        Services.telemetry
          .getHistogramById("PLACES_MOST_RECENT_EXPIRED_VISIT_DAYS")
          .add(this._mostRecentExpiredVisitDays);
      } catch (ex) {
        console.error("Unable to report telemetry.");
      } finally {
        delete this._mostRecentExpiredVisitDays;
      }
    }

    if ("_expectedResultsCount" in this) {
      // Adapt the aggressivity of steps based on the status of history.
      // A dirty history will return all the entries we are expecting bringing
      // our countdown to zero, while a clean one will not.
      let oldStatus = this.status;
      this.status =
        this._expectedResultsCount == 0 ? STATUS.DIRTY : STATUS.CLEAN;

      // Collect or send telemetry data.
      if (this.status == STATUS.DIRTY) {
        this._telemetrySteps++;
      } else {
        // Avoid reporting the common cases where the database is clean, or
        // a single step is needed.
        if (oldStatus == STATUS.DIRTY) {
          try {
            Services.telemetry
              .getHistogramById("PLACES_EXPIRATION_STEPS_TO_CLEAN2")
              .add(this._telemetrySteps);
          } catch (ex) {
            console.error("Unable to report telemetry.");
          }
        }
        this._telemetrySteps = 1;
      }

      delete this._expectedResultsCount;
    }

    // Dispatch a notification that expiration has finished.
    Services.obs.notifyObservers(
      null,
      lazy.PlacesUtils.TOPIC_EXPIRATION_FINISHED
    );
  },

  /**
   * Generate a query used for expiration.
   *
   * @param aQueryType
   *        Type of the query.
   * @param aLimit
   *        Whether to use small, large or no limits when expiring.  See the
   *        LIMIT const for values.
   * @param aAction
   *        Current action causing the expiration.  See the ACTION const.
   */
  async _getQueryParams(aQueryType, aLimit, aAction) {
    let baseLimit;
    switch (aLimit) {
      case LIMIT.UNLIMITED:
        baseLimit = -1;
        break;
      case LIMIT.SMALL:
        baseLimit = EXPIRE_LIMIT_PER_STEP;
        break;
      case LIMIT.LARGE:
        baseLimit =
          EXPIRE_LIMIT_PER_STEP * EXPIRE_LIMIT_PER_LARGE_STEP_MULTIPLIER;
        break;
      case LIMIT.DEBUG:
        baseLimit = this._debugLimit;
        break;
    }
    if (
      this.status == STATUS.DIRTY &&
      aAction != ACTION.DEBUG &&
      baseLimit > 0
    ) {
      baseLimit *= EXPIRE_AGGRESSIVITY_MULTIPLIER;
    }

    switch (aQueryType) {
      case "QUERY_FIND_EXOTIC_VISITS_TO_EXPIRE":
        return {
          // Avoid expiring all visits in case of an unlimited debug expiration,
          // just remove orphans instead.
          limit_visits:
            aLimit == LIMIT.DEBUG && baseLimit == -1 ? 0 : baseLimit,
        };
      case "QUERY_FIND_VISITS_TO_EXPIRE":
        return {
          max_uris: await this.getPagesLimit(),
          // Avoid expiring all visits in case of an unlimited debug expiration,
          // just remove orphans instead.
          limit_visits:
            aLimit == LIMIT.DEBUG && baseLimit == -1 ? 0 : baseLimit,
        };
      case "QUERY_FIND_URIS_TO_EXPIRE":
        return {
          limit_uris: baseLimit,
        };
      case "QUERY_EXPIRE_ANNOS":
        return {
          // Each page may have multiple annos.
          limit_annos: baseLimit * EXPIRE_AGGRESSIVITY_MULTIPLIER,
        };
      case "QUERY_EXPIRE_INPUTHISTORY":
        return {
          limit_inputhistory: baseLimit,
        };
      case "QUERY_EXPIRE_INTERACTIONS":
        return {
          days_interactions: Services.prefs.getIntPref(
            "browser.places.interactions.expireDays",
            60
          ),
          limit_interactions:
            aLimit == LIMIT.DEBUG && baseLimit == -1 ? 0 : baseLimit,
        };
    }
    return undefined;
  },

  /**
   * Creates a new timer based on this.intervalSeconds.
   *
   * @return a REPEATING_SLACK nsITimer that runs every this.intervalSeconds.
   */
  _newTimer() {
    if (this._timer) {
      this._timer.cancel();
    }
    if (this._shuttingDown) {
      return undefined;
    }

    if (!this._isIdleObserver) {
      this._idle.addIdleObserver(this, IDLE_TIMEOUT_SECONDS);
      this._isIdleObserver = true;
    }

    let interval =
      this.status != STATUS.DIRTY
        ? this.intervalSeconds * EXPIRE_AGGRESSIVITY_MULTIPLIER
        : this.intervalSeconds;

    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    timer.initWithCallback(
      this,
      interval * 1000,
      Ci.nsITimer.TYPE_REPEATING_SLACK_LOW_PRIORITY
    );
    if (this._testingMode) {
      Services.obs.notifyObservers(null, TOPIC_TEST_INTERVAL_CHANGED, interval);
    }
    return (this._timer = timer);
  },

  classID: Components.ID("705a423f-2f69-42f3-b9fe-1517e0dee56f"),

  QueryInterface: ChromeUtils.generateQI([
    "nsINamed",
    "nsIObserver",
    "nsISupportsWeakReference",
    "nsITimerCallback",
  ]),
};
PK
!<z��1\1\*modules/PlacesFrecencyRecalculator.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * vim: sw=2 ts=2 sts=2 expandtab
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This component handles frecency recalculations and decay on idle.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", function () {
  return lazy.PlacesUtils.getLogger({ prefix: "FrecencyRecalculator" });
});

// Decay rate applied daily to frecency scores.
// A scaling factor of .975 results in an half-life of 28 days.
const FRECENCY_DECAYRATE = "0.975";
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "frecencyDecayRate",
  "places.frecency.decayRate",
  FRECENCY_DECAYRATE,
  null,
  val => {
    if (typeof val == "string") {
      val = parseFloat(val);
    }
    if (val > 1.0) {
      lazy.logger.error("Invalid frecency decay rate value: " + val);
      val = parseFloat(FRECENCY_DECAYRATE);
    }
    return val;
  }
);

// An adaptive history entry is removed if unused for these many days.
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "adaptiveHistoryExpireDays",
  "places.adaptiveHistory.expireDays",
  90
);

// For origins frecency calculation only sample pages visited recently.
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "originsFrecencyCutOffDays",
  "places.frecency.originsCutOffDays",
  90
);

// This pref stores whether recalculation should be faster.
// It is set when we detect that a lot of changes happened recently, and it
// will survive restarts. Once there's nothing left to recalculate, we unset
// the pref and return to the normal recalculation rate.
// Note this getter transforms the boolean pref value into an integer
// acceleration rate.
const PREF_ACCELERATE_RECALCULATION = "places.frecency.accelerateRecalculation";
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "accelerationRate",
  PREF_ACCELERATE_RECALCULATION,
  false,
  null,
  accelerate => (accelerate ? 2 : 1)
);

// Time between deferred task executions.
const DEFERRED_TASK_INTERVAL_MS = 2 * 60000;
// Maximum time to wait for an idle before the task is executed anyway.
const DEFERRED_TASK_MAX_IDLE_WAIT_MS = 5 * 60000;
// Number of entries to update at once.
const DEFAULT_CHUNK_SIZE = 50;
// Threshold used to evaluate whether the number of Places events from the last
// recalculation is high enough to deserve a recalculation rate increase.
const ACCELERATION_EVENTS_THRESHOLD = 250;

export class PlacesFrecencyRecalculator {
  classID = Components.ID("1141fd31-4c1a-48eb-8f1a-2f05fad94085");

  /**
   * A DeferredTask that runs our tasks.
   */
  #task = null;

  /**
   * Handler for alternative frecency.
   * This allows to manager alternative ranking algorithms to experiment with.
   */
  #alternativeFrecencyHelper = null;

  /**
   * Tracks whether the recalculator was finalized, usually due to shutdown.
   * We use this explicit boolean rather than checking for a null `#task`
   * because, due to async behavior, `#task` could be resurrected by
   * `#createOrUpdateTask`.
   */
  #finalized = false;

  /**
   * This is useful for testing.
   */
  get alternativeFrecencyInfo() {
    return this.#alternativeFrecencyHelper?.sets;
  }

  constructor() {
    lazy.logger.trace("Initializing Frecency Recalculator");

    this.QueryInterface = ChromeUtils.generateQI([
      "nsIObserver",
      "nsISupportsWeakReference",
    ]);

    // Do not initialize during shutdown.
    if (
      Services.startup.isInOrBeyondShutdownPhase(
        Ci.nsIAppStartup.SHUTDOWN_PHASE_APPSHUTDOWNCONFIRMED
      )
    ) {
      this.#finalized = true;
      return;
    }

    this.#createOrUpdateTask();

    lazy.AsyncShutdown.quitApplicationGranted.addBlocker(
      "PlacesFrecencyRecalculator: shutdown",
      () => this.#finalize()
    );

    // The public methods and properties are intended to be used by tests, and
    // are exposed through the raw js object. Since this is expected to work
    // based on signals or notification, it should not be necessary to expose
    // any API for the product, though if that would become necessary in the
    // future, we could add an interface for the service.
    this.wrappedJSObject = this;
    // This can be used by tests to await for the decay process.
    this.pendingFrecencyDecayPromise = Promise.resolve();

    Services.obs.addObserver(this, "idle-daily", true);
    Services.obs.addObserver(this, "frecency-recalculation-needed", true);

    this.#alternativeFrecencyHelper = new AlternativeFrecencyHelper(this);

    // Run once on startup, so we pick up any leftover work.
    lazy.PlacesUtils.history.shouldStartFrecencyRecalculation = true;
    this.maybeStartFrecencyRecalculation();
  }

  #createOrUpdateTask() {
    if (this.#finalized) {
      lazy.logger.trace(`Not resurrecting #task because finalized`);
      return;
    }
    let wasArmed = this.#task?.isArmed;
    if (this.#task) {
      this.#task.disarm();
      this.#task.finalize().catch(console.error);
    }
    this.#task = new lazy.DeferredTask(
      this.#taskFn.bind(this),
      DEFERRED_TASK_INTERVAL_MS / lazy.accelerationRate,
      DEFERRED_TASK_MAX_IDLE_WAIT_MS / lazy.accelerationRate
    );
    if (wasArmed) {
      this.#task.arm();
    }
  }

  async #taskFn() {
    if (this.#task.isFinalized) {
      return;
    }
    const refObj = {};
    const histogram = "PLACES_FRECENCY_RECALC_CHUNK_TIME_MS";
    TelemetryStopwatch.start(histogram, refObj);
    try {
      if (await this.recalculateSomeFrecencies()) {
        TelemetryStopwatch.finish(histogram, refObj);
      } else {
        TelemetryStopwatch.cancel(histogram, refObj);
      }
    } catch (ex) {
      TelemetryStopwatch.cancel(histogram, refObj);
      console.error(ex);
      lazy.logger.error(ex);
    }
  }

  #finalize() {
    lazy.logger.trace("Finalizing frecency recalculator");
    // We don't mind about tasks completiion, since we can execute them in the
    // next session.
    this.#task.disarm();
    this.#task.finalize().catch(console.error);
    this.#finalized = true;
  }

  #lastEventsCount = 0;

  /**
   * Evaluates whether recalculation speed should be increased, and eventually
   * accelerates.
   * @returns {boolean} whether the recalculation rate is increased.
   */
  maybeUpdateRecalculationSpeed() {
    if (lazy.accelerationRate > 1) {
      return true;
    }
    // We mostly care about additions to cover the common case of importing
    // bookmarks or history. We may care about removals, but in most cases they
    // reduce the number of entries to recalculate.
    let eventsCount =
      PlacesObservers.counts.get("page-visited") +
      PlacesObservers.counts.get("bookmark-added");
    let accelerate =
      eventsCount - this.#lastEventsCount > ACCELERATION_EVENTS_THRESHOLD;
    if (accelerate) {
      Services.prefs.setBoolPref(PREF_ACCELERATE_RECALCULATION, true);
      this.#createOrUpdateTask();
    }
    this.#lastEventsCount = eventsCount;
    return accelerate;
  }

  #resetRecalculationSpeed() {
    if (lazy.accelerationRate > 1) {
      Services.prefs.clearUserPref(PREF_ACCELERATE_RECALCULATION);
      this.#createOrUpdateTask();
    }
  }

  /**
   * Updates a chunk of outdated frecency values. If there's more frecency
   * values to update at the end of the process, it may rearm the task.
   * @param {Number} chunkSize maximum number of entries to update at a time,
   *   set to -1 to update any entry.
   * @resolves {boolean} Whether any entry was recalculated.
   */
  async recalculateSomeFrecencies({ chunkSize = DEFAULT_CHUNK_SIZE } = {}) {
    // In case of acceleration we don't bump up the chunkSize to avoid issues
    // with slow disk systems.
    lazy.logger.trace(
      `Recalculate ${chunkSize >= 0 ? chunkSize : "infinite"} frecency values`
    );
    let affectedCount = 0;
    let hasRecalculatedAnything = false;
    let db = await lazy.PlacesUtils.promiseUnsafeWritableDBConnection();
    await db.executeTransaction(async function () {
      let affected = await db.executeCached(
        `UPDATE moz_places
        SET frecency = CALCULATE_FRECENCY(id)
        WHERE id IN (
          SELECT id FROM moz_places
          WHERE recalc_frecency = 1
          ORDER BY frecency DESC, visit_count DESC
          LIMIT ${chunkSize}
        )
        RETURNING id`
      );
      affectedCount += affected.length;
    });
    let shouldRestartRecalculation = affectedCount >= chunkSize;
    hasRecalculatedAnything = affectedCount > 0;
    if (hasRecalculatedAnything) {
      PlacesObservers.notifyListeners([new PlacesRanking()]);
    }

    // Also recalculate some origins frecency.
    affectedCount = await this.#recalculateSomeOriginsFrecencies({
      chunkSize,
    });
    shouldRestartRecalculation ||= affectedCount >= chunkSize;
    hasRecalculatedAnything ||= affectedCount > 0;

    // If alternative frecency is enabled, also recalculate a chunk of it.
    affectedCount =
      await this.#alternativeFrecencyHelper.recalculateSomeAlternativeFrecencies(
        { chunkSize }
      );
    shouldRestartRecalculation ||= affectedCount >= chunkSize;
    hasRecalculatedAnything ||= affectedCount > 0;

    if (chunkSize > 0 && shouldRestartRecalculation) {
      // There's more entries to recalculate, rearm the task.
      this.maybeUpdateRecalculationSpeed();
      this.#task.arm();
    } else {
      this.#resetRecalculationSpeed();
      // There's nothing left to recalculate, wait for the next change.
      lazy.PlacesUtils.history.shouldStartFrecencyRecalculation = false;
      this.#task.disarm();
    }
    return hasRecalculatedAnything;
  }

  async #recalculateSomeOriginsFrecencies({ chunkSize }) {
    lazy.logger.trace(`Recalculate ${chunkSize} origins frecency values`);
    let affectedCount = 0;
    let db = await lazy.PlacesUtils.promiseUnsafeWritableDBConnection();
    await db.executeTransaction(async () => {
      // NULL frecencies are normalized to 1.0 (to avoid confusion with pages
      // 0 frecency special meaning), as the table doesn't support NULL values.
      let affected = await db.executeCached(
        `
        UPDATE moz_origins
        SET frecency = IFNULL((
          SELECT sum(frecency)
          FROM moz_places h
          WHERE origin_id = moz_origins.id
          AND last_visit_date >
            strftime('%s','now','localtime','start of day',
                     '-${lazy.originsFrecencyCutOffDays} day','utc') * 1000000
        ), 1.0), recalc_frecency = 0
        WHERE id IN (
          SELECT id FROM moz_origins
          WHERE recalc_frecency = 1
          ORDER BY frecency DESC
          LIMIT ${chunkSize}
        )
        RETURNING id`
      );
      affectedCount += affected.length;

      // Calculate and store the frecency threshold. Origins whose frecency is
      // above this value will be considered meaningful and autofilled.
      // While it may be tempting to do this only when some frecency was
      // updated, that won't catch the edge case of the moz_origins table being
      // emptied.
      // In case of NULL, the default threshold is 2, that is higher than the
      // default frecency set above.
      let threshold = (
        await db.executeCached(`SELECT avg(frecency) FROM moz_origins`)
      )[0].getResultByIndex(0);
      await lazy.PlacesUtils.metadata.set(
        "origin_frecency_threshold",
        threshold ?? 2
      );
    });

    return affectedCount;
  }

  /**
   * Forces full recalculation of any outdated frecency values.
   * This exists for testing purposes; in tests we don't want to wait for
   * the deferred task to run, this can enforce a recalculation.
   */
  async recalculateAnyOutdatedFrecencies() {
    this.#task.disarm();
    return this.recalculateSomeFrecencies({ chunkSize: -1 });
  }

  /**
   * Whether a recalculation task is pending.
   */
  get isRecalculationPending() {
    return this.#task.isArmed;
  }

  /**
   * Invoked periodically to eventually start a recalculation task.
   */
  maybeStartFrecencyRecalculation() {
    if (
      lazy.PlacesUtils.history.shouldStartFrecencyRecalculation &&
      !this.#task.isFinalized
    ) {
      lazy.logger.trace("Arm frecency recalculation");
      this.#task.arm();
    }
  }

  /**
   * Decays frecency and adaptive history.
   * @resolves once the process is complete. Never rejects.
   */
  async decay() {
    lazy.logger.trace("Decay frecency");
    let refObj = {};
    TelemetryStopwatch.start("PLACES_IDLE_FRECENCY_DECAY_TIME_MS", refObj);
    // Ensure moz_places_afterupdate_frecency_trigger ignores decaying
    // frecency changes.
    lazy.PlacesUtils.history.isFrecencyDecaying = true;
    try {
      let db = await lazy.PlacesUtils.promiseUnsafeWritableDBConnection();
      await db.executeTransaction(async function () {
        // Decay all frecency rankings to reduce value of pages that haven't
        // been visited in a while.
        await db.executeCached(
          `UPDATE moz_places SET frecency = ROUND(frecency * :decay_rate)
            WHERE frecency > 0 AND recalc_frecency = 0`,
          { decay_rate: lazy.frecencyDecayRate }
        );
        // Decay potentially unused adaptive entries (e.g. those that are at 1)
        // to allow better chances for new entries that will start at 1.
        await db.executeCached(
          `UPDATE moz_inputhistory SET use_count = use_count * :decay_rate`,
          { decay_rate: lazy.frecencyDecayRate }
        );
        // Delete any adaptive entries that won't help in ordering anymore.
        await db.executeCached(
          `DELETE FROM moz_inputhistory WHERE use_count < :use_count`,
          {
            use_count: Math.pow(
              lazy.frecencyDecayRate,
              lazy.adaptiveHistoryExpireDays
            ),
          }
        );

        TelemetryStopwatch.finish("PLACES_IDLE_FRECENCY_DECAY_TIME_MS", refObj);
        PlacesObservers.notifyListeners([new PlacesRanking()]);
      });
    } catch (ex) {
      TelemetryStopwatch.cancel("PLACES_IDLE_FRECENCY_DECAY_TIME_MS", refObj);
      console.error(ex);
      lazy.logger.error(ex);
    } finally {
      lazy.PlacesUtils.history.isFrecencyDecaying = false;
    }
  }

  observe(subject, topic) {
    lazy.logger.trace(`Got ${topic} topic`);
    if (this.#finalized) {
      lazy.logger.trace(`Ignoring topic because finalized`);
      return;
    }
    switch (topic) {
      case "idle-daily":
        this.pendingFrecencyDecayPromise = this.decay();
        // Also recalculate frecencies.
        lazy.logger.trace("Frecency recalculation on idle");
        lazy.PlacesUtils.history.shouldStartFrecencyRecalculation = true;
        this.maybeStartFrecencyRecalculation();
        return;
      case "frecency-recalculation-needed":
        lazy.logger.trace("Frecency recalculation requested");
        this.maybeUpdateRecalculationSpeed();
        this.maybeStartFrecencyRecalculation();
        return;
      case "test-execute-taskFn":
        subject.promise = this.#taskFn();
        return;
      case "test-alternative-frecency-init":
        this.#alternativeFrecencyHelper = new AlternativeFrecencyHelper(this);
        subject.promise =
          this.#alternativeFrecencyHelper.initializedDeferred.promise;
    }
  }
}

class AlternativeFrecencyHelper {
  initializedDeferred = Promise.withResolvers();
  #recalculator = null;

  sets = {
    pages: {
      // This pref is only read once and used to kick-off recalculations.
      enabled: Services.prefs.getBoolPref(
        "places.frecency.pages.alternative.featureGate",
        false
      ),
      // Key used to store variables in the moz_meta table.
      metadataKey: "page_alternative_frecency",
      // The table containing frecency.
      table: "moz_places",
      // Object containing variables influencing the calculation.
      // Any change to this object will cause a full recalculation on restart.
      variables: {
        // Current version of origins alternative frecency.
        //  ! IMPORTANT: Always bump up when making changes to the algorithm.
        version: 2,
        highWeight: Services.prefs.getIntPref(
          "places.frecency.pages.alternative.highWeight",
          100
        ),
        mediumWeight: Services.prefs.getIntPref(
          "places.frecency.pages.alternative.mediumWeight",
          50
        ),
        lowWeight: Services.prefs.getIntPref(
          "places.frecency.pages.alternative.lowWeight",
          20
        ),
        halfLifeDays: Services.prefs.getIntPref(
          "places.frecency.pages.alternative.halfLifeDays",
          30
        ),
        numSampledVisits: Services.prefs.getIntPref(
          "places.frecency.pages.alternative.numSampledVisits",
          10
        ),
      },
      method: this.#recalculateSomePagesAlternativeFrecencies,
    },

    origins: {
      // This pref is only read once and used to kick-off recalculations.
      enabled: Services.prefs.getBoolPref(
        "places.frecency.origins.alternative.featureGate",
        false
      ),
      // Key used to store variables in the moz_meta table.
      metadataKey: "origin_alternative_frecency",
      // The table containing frecency.
      table: "moz_origins",
      // Object containing variables influencing the calculation.
      // Any change to this object will cause a full recalculation on restart.
      variables: {
        // Current version of origins alternative frecency.
        //  ! IMPORTANT: Always bump up when making changes to the algorithm.
        version: 2,
        // Frecencies of pages are ignored after these many days.
        daysCutOff: Services.prefs.getIntPref(
          "places.frecency.origins.alternative.daysCutOff",
          90
        ),
      },
      method: this.#recalculateSomeOriginsAlternativeFrecencies,
    },
  };

  constructor(recalculator) {
    this.#recalculator = recalculator;
    this.#kickOffAlternativeFrecencies()
      .catch(console.error)
      .finally(() => this.initializedDeferred.resolve());
  }

  async #kickOffAlternativeFrecencies() {
    let recalculateFirstChunk = false;
    for (let [type, set] of Object.entries(this.sets)) {
      // Now check the variables cached in the moz_meta table. If not found we
      // assume alternative frecency was disabled in the previous session.
      let storedVariables = await lazy.PlacesUtils.metadata.get(
        set.metadataKey,
        null
      );

      // Check whether this is the first-run, that happens when the alternative
      // ranking is enabled and it was not at the previous session, or variables
      // were changed. We should recalculate all the alternative frecency values.
      if (
        set.enabled &&
        !lazy.ObjectUtils.deepEqual(set.variables, storedVariables)
      ) {
        lazy.logger.trace(
          `Alternative frecency of ${type} must be recalculated`
        );
        await lazy.PlacesUtils.withConnectionWrapper(
          `PlacesFrecencyRecalculator :: ${type} alternative frecency set recalc`,
          async db => {
            await db.execute(`UPDATE ${set.table} SET recalc_alt_frecency = 1`);
          }
        );
        await lazy.PlacesUtils.metadata.set(set.metadataKey, set.variables);
        recalculateFirstChunk = true;
        continue;
      }

      if (!set.enabled && storedVariables) {
        lazy.logger.trace(`Clean up alternative frecency of ${type}`);
        // Clear alternative frecency to save on space.
        await lazy.PlacesUtils.withConnectionWrapper(
          `PlacesFrecencyRecalculator :: ${type} alternative frecency set NULL`,
          async db => {
            await db.execute(`UPDATE ${set.table} SET alt_frecency = NULL`);
          }
        );
        await lazy.PlacesUtils.metadata.delete(set.metadataKey);
      }
    }

    if (recalculateFirstChunk) {
      // Do a first recalculation immediately, so we don't leave the user
      // with unranked entries for too long.
      await this.recalculateSomeAlternativeFrecencies();

      // Ensure the recalculation task is armed for a second run.
      lazy.PlacesUtils.history.shouldStartFrecencyRecalculation = true;
      this.#recalculator.maybeStartFrecencyRecalculation();
    }
  }

  /**
   * Updates a chunk of outdated frecency values.
   * @param {Number} chunkSize maximum number of entries to update at a time,
   *   set to -1 to update any entry.
   * @resolves {Number} Number of affected pages.
   */
  async recalculateSomeAlternativeFrecencies({
    chunkSize = DEFAULT_CHUNK_SIZE,
  } = {}) {
    let affected = 0;
    for (let set of Object.values(this.sets)) {
      if (!set.enabled) {
        continue;
      }
      try {
        affected += await set.method({ chunkSize, variables: set.variables });
      } catch (ex) {
        console.error(ex);
      }
    }
    return affected;
  }

  async #recalculateSomePagesAlternativeFrecencies({ chunkSize }) {
    lazy.logger.trace(
      `Recalculate ${chunkSize} alternative pages frecency values`
    );
    // Since it takes a long period of time to recalculate frecency of all the
    // pages, due to the high number of them, we artificially increase the
    // chunk size here.
    let db = await lazy.PlacesUtils.promiseUnsafeWritableDBConnection();
    let affected = await db.executeCached(
      `UPDATE moz_places
       SET alt_frecency = CALCULATE_ALT_FRECENCY(moz_places.id),
           recalc_alt_frecency = 0
       WHERE id IN (
        SELECT id FROM moz_places
          WHERE recalc_alt_frecency = 1
          ORDER BY frecency DESC
          LIMIT ${chunkSize * 2}
      )
      RETURNING id`
    );
    return affected;
  }

  async #recalculateSomeOriginsAlternativeFrecencies({ chunkSize, variables }) {
    lazy.logger.trace(
      `Recalculate ${chunkSize} alternative origins frecency values`
    );
    let affectedCount = 0;
    let db = await lazy.PlacesUtils.promiseUnsafeWritableDBConnection();
    await db.executeTransaction(async () => {
      let affected = await db.executeCached(
        `
        UPDATE moz_origins
        SET alt_frecency = (
          SELECT sum(frecency)
          FROM moz_places h
          WHERE origin_id = moz_origins.id
          AND last_visit_date >
            strftime('%s','now','localtime','start of day',
                     '-${variables.daysCutOff} day','utc') * 1000000
        ), recalc_alt_frecency = 0
        WHERE id IN (
          SELECT id FROM moz_origins
          WHERE recalc_alt_frecency = 1
          ORDER BY frecency DESC
          LIMIT ${chunkSize}
        )
        RETURNING id`
      );
      affectedCount += affected.length;

      // Calculate and store the alternative frecency threshold. Origins above
      // this threshold will be considered meaningful and autofilled.
      if (affected.length) {
        let threshold = (
          await db.executeCached(`SELECT avg(alt_frecency) FROM moz_origins`)
        )[0].getResultByIndex(0);
        await lazy.PlacesUtils.metadata.set(
          "origin_alt_frecency_threshold",
          threshold
        );
      }
    });

    return affectedCount;
  }
}
PK
!<qq�	E5E5modules/PlacesPreviews.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  BackgroundPageThumbs: "resource://gre/modules/BackgroundPageThumbs.sys.mjs",
  PageThumbsStorage: "resource://gre/modules/PageThumbs.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logger", function () {
  return lazy.PlacesUtils.getLogger({ prefix: "Previews" });
});

// Toggling Places previews requires a restart, because a database trigger
// filling up tombstones is enabled on the database only when the pref is set
// on startup.
ChromeUtils.defineLazyGetter(lazy, "previewsEnabled", function () {
  return Services.prefs.getBoolPref("places.previews.enabled", false);
});

// Preview deletions are done in chunks of this size.
const DELETE_CHUNK_SIZE = 50;
// This is the time between deletion chunks.
const DELETE_TIMEOUT_MS = 60000;

// The folder inside the profile folder where to store previews.
const PREVIEWS_DIRECTORY = "places-previews";

// How old a preview file should be before we replace it.
const DAYS_BEFORE_REPLACEMENT = 30;

/**
 * This extends Set to only keep the latest 100 entries.
 */
class LimitedSet extends Set {
  #limit = 100;
  add(key) {
    super.add(key);
    let oversize = this.size - this.#limit;
    if (oversize > 0) {
      for (let entry of this) {
        if (oversize-- <= 0) {
          break;
        }
        this.delete(entry);
      }
    }
  }
}

/**
 * This class handles previews deletion from tombstones in the database.
 * Deletion happens in chunks, each chunk runs after DELETE_TIMEOUT_MS, and
 * the process is interrupted once there's nothing more to delete.
 * Any page removal operations on the Places database will restart the timer.
 */
class DeletionHandler {
  #timeoutId = null;
  #shutdownProgress = {};

  /**
   * This can be set by tests to speed up the deletion process, otherwise the
   * product should just use the default value.
   */
  #timeout = DELETE_TIMEOUT_MS;
  get timeout() {
    return this.#timeout;
  }
  set timeout(val) {
    if (this.#timeoutId) {
      lazy.clearTimeout(this.#timeoutId);
      this.#timeoutId = null;
    }
    this.#timeout = val;
    this.ensureRunning();
  }

  constructor() {
    // Clear any pending timeouts on shutdown.
    lazy.PlacesUtils.history.shutdownClient.jsclient.addBlocker(
      "PlacesPreviews.sys.mjs::DeletionHandler",
      async () => {
        this.#shutdownProgress.shuttingDown = true;
        lazy.clearTimeout(this.#timeoutId);
        this.#timeoutId = null;
      },
      { fetchState: () => this.#shutdownProgress }
    );
  }

  /**
   * This should be invoked everytime we expect there are tombstones to
   * handle. If deletion is already pending, this is a no-op.
   */
  ensureRunning() {
    if (this.#timeoutId || this.#shutdownProgress.shuttingDown) {
      return;
    }
    this.#timeoutId = lazy.setTimeout(() => {
      this.#timeoutId = null;
      ChromeUtils.idleDispatch(() => {
        this.#deleteChunk().catch(ex =>
          lazy.logger.error("Error during previews deletion:" + ex)
        );
      });
    }, this.timeout);
  }

  /**
   * Deletes a chunk of previews.
   */
  async #deleteChunk() {
    if (this.#shutdownProgress.shuttingDown) {
      return;
    }
    // Select tombstones, delete images, then delete tombstones. This order
    // ensures that in case of problems we'll try again in the future.
    let db = await lazy.PlacesUtils.promiseDBConnection();
    let count;
    let hashes = (
      await db.executeCached(
        `SELECT hash, (SELECT count(*) FROM moz_previews_tombstones) AS count
         FROM moz_previews_tombstones LIMIT ${DELETE_CHUNK_SIZE}`
      )
    ).map(r => {
      if (count === undefined) {
        count = r.getResultByName("count");
      }
      return r.getResultByName("hash");
    });
    if (!count || this.#shutdownProgress.shuttingDown) {
      // There's nothing to delete, or it's too late.
      return;
    }

    let deleted = [];
    for (let hash of hashes) {
      let filePath = PlacesPreviews.getPathForHash(hash);
      try {
        await IOUtils.remove(filePath);
        PlacesPreviews.onDelete(filePath);
        deleted.push(hash);
      } catch (ex) {
        if (DOMException.isInstance(ex) && ex.name == "NotFoundError") {
          deleted.push(hash);
        } else {
          lazy.logger.error("Unable to delete file: " + filePath);
        }
      }
      if (this.#shutdownProgress.shuttingDown) {
        return;
      }
    }
    // Delete hashes from tombstones.
    let params = deleted.reduce((p, c, i) => {
      p["hash" + i] = c;
      return p;
    }, {});
    await lazy.PlacesUtils.withConnectionWrapper(
      "PlacesPreviews.sys.mjs::ExpirePreviews",
      async db => {
        await db.execute(
          `DELETE FROM moz_previews_tombstones WHERE hash in
            (${Object.keys(params)
              .map(p => `:${p}`)
              .join(",")})`,
          params
        );
      }
    );

    if (count > DELETE_CHUNK_SIZE) {
      this.ensureRunning();
    }
  }
}

/**
 * Handles previews for Places urls.
 * Previews are stored in WebP format, using SHA256 hash of the page url in hex
 * format. All the previews are saved into a "places-previews" folder under
 * the roaming profile folder.
 */
export const PlacesPreviews = new (class extends EventEmitter {
  #placesObserver = null;
  #deletionHandler = null;
  // This is used as a cache to avoid fetching the same preview multiple
  // times in a short timeframe.
  #recentlyUpdatedPreviews = new LimitedSet();

  fileExtension = ".webp";
  fileContentType = "image/webp";

  constructor() {
    super();
    // Observe page removals and delete previews when necessary.
    this.#placesObserver = new PlacesWeakCallbackWrapper(
      this.handlePlacesEvents.bind(this)
    );
    PlacesObservers.addListener(
      ["history-cleared", "page-removed"],
      this.#placesObserver
    );

    // Start deletion in case it was interruped during the previous session,
    // it will end once there's nothing more to delete.
    this.#deletionHandler = new DeletionHandler();
    this.#deletionHandler.ensureRunning();
  }

  handlePlacesEvents(events) {
    for (const event of events) {
      if (
        event.type == "history-cleared" ||
        (event.type == "page-removed" && event.isRemovedFromStore)
      ) {
        this.#deletionHandler.ensureRunning();
        return;
      }
    }
  }

  /**
   * Whether the feature is enabled. Use this instead of directly checking
   * the pref, since it requires a restart.
   */
  get enabled() {
    return lazy.previewsEnabled;
  }

  /**
   * Returns the path to the previews folder.
   * @returns {string} The path to the previews folder.
   */
  getPath() {
    return PathUtils.join(
      Services.dirsvc.get("ProfD", Ci.nsIFile).path,
      PREVIEWS_DIRECTORY
    );
  }

  /**
   * Returns the file path of the preview for the given url.
   * This doesn't guarantee the file exists.
   * @param {string} url Address of the page.
   * @returns {string} File path of the preview for the given url.
   */
  getPathForUrl(url) {
    return PathUtils.join(
      this.getPath(),
      lazy.PlacesUtils.sha256(url, { format: "hex" }) + this.fileExtension
    );
  }

  /**
   * Returns the file path of the preview having the given hash.
   * @param {string} hash SHA256 hash in hex format.
   * @returns {string } File path of the preview having the given hash.
   */
  getPathForHash(hash) {
    return PathUtils.join(this.getPath(), hash + this.fileExtension);
  }

  /**
   * Returns the moz-page-thumb: url to show the preview for the given url.
   * @param {string} url Address of the page.
   * @returns {string} Preview url for the given page url.
   */
  getPageThumbURL(url) {
    return (
      "moz-page-thumb://" +
      "places-previews" +
      "/?url=" +
      encodeURIComponent(url) +
      "&revision=" +
      lazy.PageThumbsStorage.getRevision(url)
    );
  }

  /**
   * Updates the preview for the given page url. The update happens in
   * background, using a windowless browser with very conservative privacy
   * settings. Due to this, it may not look exactly like the page that the user
   * is normally facing when logged in. See BackgroundPageThumbs.sys.mjs for
   * additional details.
   * Unless `forceUpdate` is set, the preview is not updated if:
   *  - It was already fetched recently
   *  - The stored preview is younger than DAYS_BEFORE_REPLACEMENT
   * The previem image is encoded using WebP.
   * @param {string} url The address of the page.
   * @param {boolean} [forceUpdate] Whether to update the preview regardless.
   * @returns {boolean} Whether a preview is available and ready.
   */
  async update(url, { forceUpdate = false } = {}) {
    if (!this.enabled) {
      return false;
    }
    let filePath = this.getPathForUrl(url);
    if (!forceUpdate) {
      if (this.#recentlyUpdatedPreviews.has(filePath)) {
        lazy.logger.debug("Skipping update because recently updated");
        return true;
      }
      try {
        let fileInfo = await IOUtils.stat(filePath);
        if (
          fileInfo.lastModified >
          Date.now() - DAYS_BEFORE_REPLACEMENT * 86400000
        ) {
          // File is recent enough.
          this.#recentlyUpdatedPreviews.add(filePath);
          lazy.logger.debug("Skipping update because file is recent");
          return true;
        }
      } catch (ex) {
        // If the file doesn't exist, we always update it.
        if (!DOMException.isInstance(ex) || ex.name != "NotFoundError") {
          lazy.logger.error("Error while trying to stat() preview" + ex);
          return false;
        }
      }
    }

    let buffer = await new Promise(resolve => {
      let observer = (subject, topic, errorUrl) => {
        if (errorUrl == url) {
          resolve(null);
        }
      };
      Services.obs.addObserver(observer, "page-thumbnail:error");
      lazy.BackgroundPageThumbs.capture(url, {
        dontStore: true,
        contentType: this.fileContentType,
        onDone: (url, reason, handle) => {
          Services.obs.removeObserver(observer, "page-thumbnail:error");
          resolve(handle?.data);
        },
      });
    });
    if (!buffer) {
      lazy.logger.error("Unable to fetch preview: " + url);
      return false;
    }
    try {
      await IOUtils.makeDirectory(this.getPath(), { ignoreExisting: true });
      await IOUtils.write(filePath, new Uint8Array(buffer), {
        tmpPath: filePath + ".tmp",
      });
    } catch (ex) {
      lazy.logger.error("Unable to create preview: " + ex);
      return false;
    }
    this.#recentlyUpdatedPreviews.add(filePath);
    return true;
  }

  /**
   * Removes orphan previews that are not tracked by Places.
   * Orphaning should normally not happen, but unexpected manipulation (e.g. the
   * user touching the profile folder, or third party applications) could cause
   * it.
   * This method is slow, because it has to go through all the Places stored
   * pages, thus it's suggested to only run it as periodic maintenance.
   * @returns {boolean} Whether orphans deletion ran.
   */
  async deleteOrphans() {
    if (!this.enabled) {
      return false;
    }

    // From the previews directory, get all the files whose name matches our
    // format.  Avoid any other filenames, also for safety reasons, since we are
    // injecting them into SQL.
    let files = await IOUtils.getChildren(this.getPath());
    let hashes = files
      .map(f => PathUtils.filename(f))
      .filter(() => /^[a-f0-9]{32}\.webp$/)
      .map(n => n.substring(0, n.lastIndexOf(".")));

    await lazy.PlacesUtils.withConnectionWrapper(
      "PlacesPreviews.sys.mjs::deleteOrphans",
      async db => {
        await db.execute(
          `
          WITH files(hash) AS (
            VALUES ${hashes.map(h => `('${h}')`).join(", ")}
          )
          INSERT OR IGNORE INTO moz_previews_tombstones
            SELECT hash FROM files
            EXCEPT
            SELECT sha256hex(url) FROM moz_places
          `
        );
      }
    );
    this.#deletionHandler.ensureRunning();
    return true;
  }

  /**
   * This is invoked by #deletionHandler every time a preview file is removed.
   * @param {string} filePath The path of the deleted file.
   */
  onDelete(filePath) {
    this.#recentlyUpdatedPreviews.delete(filePath);
    this.emit("places-preview-deleted", filePath);
  }

  /**
   * Used by tests to change the deletion timeout between chunks.
   * @param {integer} timeout New timeout in milliseconds.
   */
  testSetDeletionTimeout(timeout) {
    if (timeout === null) {
      this.#deletionHandler.timeout = DELETE_TIMEOUT_MS;
    } else {
      this.#deletionHandler.timeout = timeout;
    }
  }
})();

/**
 * Used to exposes nsIPlacesPreviewsHelperService to the moz-page-thumb protocol
 * cpp implementation.
 */
export function PlacesPreviewsHelperService() {}

PlacesPreviewsHelperService.prototype = {
  classID: Components.ID("{bd0a4d3b-ff26-4d4d-9a62-a513e1c1bf92}"),
  QueryInterface: ChromeUtils.generateQI(["nsIPlacesPreviewsHelperService"]),

  getFilePathForURL(url) {
    return PlacesPreviews.getPathForUrl(url);
  },
};
PK
!<;�%'r7r7modules/PlacesQuery.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  BinarySearch: "resource://gre/modules/BinarySearch.sys.mjs",
  BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
});

const BULK_PLACES_EVENTS_THRESHOLD = 50;
const OBSERVER_DEBOUNCE_RATE_MS = 500;
const OBSERVER_DEBOUNCE_TIMEOUT_MS = 5000;

/**
 * An object that contains details of a page visit.
 *
 * @typedef {object} HistoryVisit
 *
 * @property {Date} date
 *   When this page was visited.
 * @property {string} title
 *   The page's title.
 * @property {string} url
 *   The page's URL.
 */

/**
 * Cache key type depends on how visits are currently being grouped.
 *
 * By date: number - The start of day timestamp of the visit.
 * By site: string - The domain name of the visit.
 *
 * @typedef {number | string} CacheKey
 */

/**
 * Queries the places database using an async read only connection. Maintains
 * an internal cache of query results which is live-updated by adding listeners
 * to `PlacesObservers`. When the results are no longer needed, call `close` to
 * remove the listeners.
 */
export class PlacesQuery {
  /** @type {Map<CacheKey, HistoryVisit[]>} */
  cachedHistory = null;
  /** @type {object} */
  cachedHistoryOptions = null;
  /** @type {Map<string, Set<HistoryVisit>>} */
  #cachedHistoryPerUrl = null;
  /** @type {function(PlacesEvent[])} */
  #historyListener = null;
  /** @type {function(HistoryVisit[])} */
  #historyListenerCallback = null;
  /** @type {DeferredTask} */
  #historyObserverTask = null;

  /**
   * Indicates whether this query is closed. When closed, caches should not be
   * populated, and observers should not be instantiated. It can be reopened by
   * calling `initializeCache()`.
   *
   * @type {boolean}
   */
  #isClosed = false;

  #searchInProgress = false;

  /**
   * Get a snapshot of history visits at this moment.
   *
   * @param {object} [options]
   *   Options to apply to the database query.
   * @param {number} [options.daysOld]
   *   The maximum number of days to go back in history.
   * @param {number} [options.limit]
   *   The maximum number of visits to return.
   * @param {string} [options.sortBy]
   *   The sorting order of history visits:
   *   - "date": Group visits based on the date they occur.
   *   - "site": Group visits based on host, excluding any "www." prefix.
   * @returns {Map<any, HistoryVisit[]>}
   *   History visits obtained from the database query.
   */
  async getHistory({ daysOld = 60, limit, sortBy = "date" } = {}) {
    const options = { daysOld, limit, sortBy };
    const cacheInvalid =
      this.cachedHistory == null ||
      !lazy.ObjectUtils.deepEqual(options, this.cachedHistoryOptions);
    if (cacheInvalid) {
      this.initializeCache(options);
      await this.fetchHistory();
    }
    if (!this.#historyListener && !this.#isClosed) {
      this.#initHistoryListener();
    }
    return this.cachedHistory;
  }

  /**
   * Clear existing cache and store options for the new query.
   *
   * @param {object} options
   *   The database query options.
   */
  initializeCache(options = this.cachedHistoryOptions) {
    this.cachedHistory = new Map();
    this.cachedHistoryOptions = options;
    this.#cachedHistoryPerUrl = new Map();
    this.#isClosed = false;
  }

  /**
   * Run the database query and populate the history cache.
   */
  async fetchHistory() {
    const { daysOld, limit, sortBy } = this.cachedHistoryOptions;
    const db = await lazy.PlacesUtils.promiseDBConnection();
    let groupBy;
    switch (sortBy) {
      case "date":
        groupBy = "url, date(visit_date / 1000000, 'unixepoch', 'localtime')";
        break;
      case "site":
        groupBy = "url";
        break;
    }
    const whereClause =
      daysOld == Infinity
        ? ""
        : `WHERE visit_date >= (strftime('%s','now','localtime','start of day','-${Number(
            daysOld
          )} days','utc') * 1000000)`;
    const sql = `SELECT MAX(visit_date) as visit_date, title, url
      FROM moz_historyvisits v
      JOIN moz_places h
      ON v.place_id = h.id
      AND hidden = 0
      ${whereClause}
      GROUP BY ${groupBy}
      ORDER BY visit_date DESC
      LIMIT ${limit > 0 ? limit : -1}`;
    const rows = await db.executeCached(sql);
    if (this.#isClosed) {
      // Do not cache visits if this instance is closed already.
      return;
    }
    for (const row of rows) {
      const visit = this.formatRowAsVisit(row);
      this.#appendToCache(visit);
    }
  }

  /**
   * Search the database for visits matching a search query. This does not
   * affect internal caches, and observers will not be notified of search
   * results obtained from this query.
   *
   * @param {string} query
   *   The search query.
   * @param {number} [limit]
   *   The maximum number of visits to return.
   * @returns {HistoryVisit[]}
   *   The matching visits.
   */
  async searchHistory(query, limit) {
    const { sortBy } = this.cachedHistoryOptions;
    const db = await lazy.PlacesUtils.promiseLargeCacheDBConnection();
    let orderBy;
    switch (sortBy) {
      case "date":
        orderBy = "visit_date DESC";
        break;
      case "site":
        orderBy = "url";
        break;
    }
    const sql = `SELECT MAX(visit_date) as visit_date, title, url
      FROM moz_historyvisits v
      JOIN moz_places h
      ON v.place_id = h.id
      WHERE AUTOCOMPLETE_MATCH(:query, url, title, NULL, 1, 1, 1, 1, :matchBehavior, :searchBehavior, NULL)
      AND hidden = 0
      GROUP BY url
      ORDER BY ${orderBy}
      LIMIT ${limit > 0 ? limit : -1}`;
    if (this.#searchInProgress) {
      db.interrupt();
    }
    try {
      this.#searchInProgress = true;
      const rows = await db.executeCached(sql, {
        query,
        matchBehavior: Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE_UNMODIFIED,
        searchBehavior: Ci.mozIPlacesAutoComplete.BEHAVIOR_HISTORY,
      });
      return rows.map(row => this.formatRowAsVisit(row));
    } finally {
      this.#searchInProgress = false;
    }
  }

  /**
   * Append a visit into the container it belongs to.
   *
   * @param {HistoryVisit} visit
   *   The visit to append.
   */
  #appendToCache(visit) {
    this.#getContainerForVisit(visit).push(visit);
    this.#insertIntoCachedHistoryPerUrl(visit);
  }

  /**
   * Insert a visit into the container it belongs to, ensuring to maintain
   * sorted order. Used for handling `page-visited` events after the initial
   * fetch of history data.
   *
   * @param {HistoryVisit} visit
   *   The visit to insert.
   */
  #insertSortedIntoCache(visit) {
    const container = this.#getContainerForVisit(visit);
    const existingVisitsForUrl = this.#cachedHistoryPerUrl.get(visit.url) ?? [];
    for (const existingVisit of existingVisitsForUrl) {
      if (this.#getContainerForVisit(existingVisit) === container) {
        if (existingVisit.date.getTime() >= visit.date.getTime()) {
          // Existing visit is more recent. Don't insert this one.
          return;
        }
        // Remove the existing visit, then insert the new one.
        container.splice(container.indexOf(existingVisit), 1);
        existingVisitsForUrl.delete(existingVisit);
        break;
      }
    }
    let insertionPoint = 0;
    if (visit.date.getTime() < container[0]?.date.getTime()) {
      insertionPoint = lazy.BinarySearch.insertionIndexOf(
        (a, b) => b.date.getTime() - a.date.getTime(),
        container,
        visit
      );
    }
    container.splice(insertionPoint, 0, visit);
    this.#insertIntoCachedHistoryPerUrl(visit);
  }

  /**
   * Insert a visit into the url-keyed history cache.
   *
   * @param {HistoryVisit} visit
   *   The visit to insert.
   */
  #insertIntoCachedHistoryPerUrl(visit) {
    const container = this.#cachedHistoryPerUrl.get(visit.url);
    if (container) {
      container.add(visit);
    } else {
      this.#cachedHistoryPerUrl.set(visit.url, new Set().add(visit));
    }
  }

  /**
   * Retrieve the corresponding container for this visit.
   *
   * @param {HistoryVisit} visit
   *   The visit to check.
   * @returns {HistoryVisit[]}
   *   The container it belongs to.
   */
  #getContainerForVisit(visit) {
    const mapKey = this.#getMapKeyForVisit(visit);
    let container = this.cachedHistory?.get(mapKey);
    if (!container) {
      container = [];
      this.cachedHistory?.set(mapKey, container);
    }
    return container;
  }

  #getMapKeyForVisit(visit) {
    switch (this.cachedHistoryOptions.sortBy) {
      case "date":
        return this.getStartOfDayTimestamp(visit.date);
      case "site": {
        const { protocol } = new URL(visit.url);
        return protocol === "http:" || protocol === "https:"
          ? lazy.BrowserUtils.formatURIStringForDisplay(visit.url)
          : "";
      }
    }
    return null;
  }

  /**
   * Observe changes to the visits table. When changes are made, the callback
   * is given the new list of visits. Only one callback can be active at a time
   * (per instance). If one already exists, it will be replaced.
   *
   * @param {function(HistoryVisit[])} callback
   *   The function to call when changes are made.
   */
  observeHistory(callback) {
    this.#historyListenerCallback = callback;
  }

  /**
   * Close this query. Caches are cleared and listeners are removed.
   */
  close() {
    this.#isClosed = true;
    this.cachedHistory = null;
    this.cachedHistoryOptions = null;
    this.#cachedHistoryPerUrl = null;
    if (this.#historyListener) {
      PlacesObservers.removeListener(
        [
          "page-removed",
          "page-visited",
          "history-cleared",
          "page-title-changed",
        ],
        this.#historyListener
      );
    }
    this.#historyListener = null;
    this.#historyListenerCallback = null;
    if (this.#historyObserverTask && !this.#historyObserverTask.isFinalized) {
      this.#historyObserverTask.disarm();
      this.#historyObserverTask.finalize();
    }
  }

  /**
   * Listen for changes to the visits table and update caches accordingly.
   */
  #initHistoryListener() {
    this.#historyObserverTask = new lazy.DeferredTask(
      async () => {
        if (typeof this.#historyListenerCallback === "function") {
          const history = await this.getHistory(this.cachedHistoryOptions);
          this.#historyListenerCallback(history);
        }
      },
      OBSERVER_DEBOUNCE_RATE_MS,
      OBSERVER_DEBOUNCE_TIMEOUT_MS
    );
    this.#historyListener = async events => {
      if (
        events.length >= BULK_PLACES_EVENTS_THRESHOLD ||
        events.some(({ type }) => type === "page-removed")
      ) {
        // Accounting for cascading deletes, or handling places events in bulk,
        // can be expensive. In this case, we invalidate the cache once rather
        // than handling each event individually.
        this.cachedHistory = null;
      } else if (this.cachedHistory != null) {
        for (const event of events) {
          switch (event.type) {
            case "page-visited":
              this.handlePageVisited(event);
              break;
            case "history-cleared":
              this.initializeCache();
              break;
            case "page-title-changed":
              this.handlePageTitleChanged(event);
              break;
          }
        }
      }
      this.#historyObserverTask.arm();
    };
    PlacesObservers.addListener(
      ["page-removed", "page-visited", "history-cleared", "page-title-changed"],
      this.#historyListener
    );
  }

  /**
   * Handle a page visited event.
   *
   * @param {PlacesEvent} event
   *   The event.
   * @return {HistoryVisit}
   *   The visit that was inserted, or `null` if no visit was inserted.
   */
  handlePageVisited(event) {
    if (event.hidden) {
      return null;
    }
    const visit = this.formatEventAsVisit(event);
    this.#insertSortedIntoCache(visit);
    return visit;
  }

  /**
   * Handle a page title changed event.
   *
   * @param {PlacesEvent} event
   *   The event.
   */
  handlePageTitleChanged(event) {
    const visits = this.#cachedHistoryPerUrl.get(event.url);
    if (visits == null) {
      return;
    }
    for (const visit of visits) {
      visit.title = event.title;
    }
  }

  /**
   * Get timestamp from a date by only considering its year, month, and date
   * (so that it can be used as a date-based key).
   *
   * @param {Date} date
   *   The date to truncate.
   * @returns {number}
   *   The corresponding timestamp.
   */
  getStartOfDayTimestamp(date) {
    return new Date(
      date.getFullYear(),
      date.getMonth(),
      date.getDate()
    ).getTime();
  }

  /**
   * Get timestamp from a date by only considering its year and month (so that
   * it can be used as a month-based key).
   *
   * @param {Date} date
   *   The date to truncate.
   * @returns {number}
   *   The corresponding timestamp.
   */
  getStartOfMonthTimestamp(date) {
    return new Date(date.getFullYear(), date.getMonth()).getTime();
  }

  /**
   * Format a database row as a history visit.
   *
   * @param {mozIStorageRow} row
   *   The row to format.
   * @returns {HistoryVisit}
   *   The resulting history visit.
   */
  formatRowAsVisit(row) {
    return {
      date: lazy.PlacesUtils.toDate(row.getResultByName("visit_date")),
      title: row.getResultByName("title"),
      url: row.getResultByName("url"),
    };
  }

  /**
   * Format a page visited event as a history visit.
   *
   * @param {PlacesEvent} event
   *   The event to format.
   * @returns {HistoryVisit}
   *   The resulting history visit.
   */
  formatEventAsVisit(event) {
    return {
      date: new Date(event.visitTime),
      title: event.lastKnownTitle,
      url: event.url,
    };
  }
}
PK
!<��E88modules/PlacesSyncUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "resource://gre/modules/Log.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
});

/**
 * This module exports functions for Sync to use when applying remote
 * records. The calls are similar to those in `Bookmarks.sys.mjs` and
 * `nsINavBookmarksService`, with special handling for
 * tags, keywords, synced annotations, and missing parents.
 */
export var PlacesSyncUtils = {};

const { SOURCE_SYNC } = Ci.nsINavBookmarksService;

const MICROSECONDS_PER_SECOND = 1000000;

const MOBILE_BOOKMARKS_PREF = "browser.bookmarks.showMobileBookmarks";

// These are defined as lazy getters to defer initializing the bookmarks
// service until it's needed.
ChromeUtils.defineLazyGetter(lazy, "ROOT_RECORD_ID_TO_GUID", () => ({
  menu: lazy.PlacesUtils.bookmarks.menuGuid,
  places: lazy.PlacesUtils.bookmarks.rootGuid,
  tags: lazy.PlacesUtils.bookmarks.tagsGuid,
  toolbar: lazy.PlacesUtils.bookmarks.toolbarGuid,
  unfiled: lazy.PlacesUtils.bookmarks.unfiledGuid,
  mobile: lazy.PlacesUtils.bookmarks.mobileGuid,
}));

ChromeUtils.defineLazyGetter(lazy, "ROOT_GUID_TO_RECORD_ID", () => ({
  [lazy.PlacesUtils.bookmarks.menuGuid]: "menu",
  [lazy.PlacesUtils.bookmarks.rootGuid]: "places",
  [lazy.PlacesUtils.bookmarks.tagsGuid]: "tags",
  [lazy.PlacesUtils.bookmarks.toolbarGuid]: "toolbar",
  [lazy.PlacesUtils.bookmarks.unfiledGuid]: "unfiled",
  [lazy.PlacesUtils.bookmarks.mobileGuid]: "mobile",
}));

ChromeUtils.defineLazyGetter(lazy, "ROOTS", () =>
  Object.keys(lazy.ROOT_RECORD_ID_TO_GUID)
);

// Gets the history transition values we ignore and do not sync, as a
// string, which is a comma-separated set of values - ie, something which can
// be used with sqlite's IN operator. Does *not* includes the parens.
ChromeUtils.defineLazyGetter(lazy, "IGNORED_TRANSITIONS_AS_SQL_LIST", () =>
  // * We don't sync `TRANSITION_FRAMED_LINK` visits - these are excluded when
  //   rendering the history menu, so we use the same constraints for Sync.
  // * We don't sync `TRANSITION_DOWNLOAD` because it makes no sense to see
  //   these on other devices - the downloaded file can not exist.
  // * We don't want to sync TRANSITION_EMBED visits, but these aren't
  //   stored in the DB, so no need to specify them.
  // * 0 is invalid, and hopefully don't exist, but let's exclude it anyway.
  // Array.toString() semantics are well defined and exactly what we need, so..
  [
    0,
    lazy.PlacesUtils.history.TRANSITION_FRAMED_LINK,
    lazy.PlacesUtils.history.TRANSITION_DOWNLOAD,
  ].toString()
);

const HistorySyncUtils = (PlacesSyncUtils.history = Object.freeze({
  SYNC_ID_META_KEY: "sync/history/syncId",
  LAST_SYNC_META_KEY: "sync/history/lastSync",

  /**
   * Returns the current history sync ID, or `""` if one isn't set.
   */
  getSyncId() {
    return lazy.PlacesUtils.metadata.get(HistorySyncUtils.SYNC_ID_META_KEY, "");
  },

  /**
   * Assigns a new sync ID. This is called when we sync for the first time with
   * a new account, and when we're the first to sync after a node reassignment.
   *
   * @return {Promise} resolved once the ID has been updated.
   * @resolves to the new sync ID.
   */
  resetSyncId() {
    return lazy.PlacesUtils.withConnectionWrapper(
      "HistorySyncUtils: resetSyncId",
      function (db) {
        let newSyncId = lazy.PlacesUtils.history.makeGuid();
        return db.executeTransaction(async function () {
          await setHistorySyncId(db, newSyncId);
          return newSyncId;
        });
      }
    );
  },

  /**
   * Ensures that the existing local sync ID, if any, is up-to-date with the
   * server. This is called when we sync with an existing account.
   *
   * @param newSyncId
   *        The server's sync ID.
   * @return {Promise} resolved once the ID has been updated.
   */
  async ensureCurrentSyncId(newSyncId) {
    if (!newSyncId || typeof newSyncId != "string") {
      throw new TypeError("Invalid new history sync ID");
    }
    await lazy.PlacesUtils.withConnectionWrapper(
      "HistorySyncUtils: ensureCurrentSyncId",
      async function (db) {
        let existingSyncId = await lazy.PlacesUtils.metadata.getWithConnection(
          db,
          HistorySyncUtils.SYNC_ID_META_KEY,
          ""
        );

        if (existingSyncId == newSyncId) {
          lazy.HistorySyncLog.trace("History sync ID up-to-date", {
            existingSyncId,
          });
          return;
        }

        lazy.HistorySyncLog.info(
          "History sync ID changed; resetting metadata",
          {
            existingSyncId,
            newSyncId,
          }
        );
        await db.executeTransaction(function () {
          return setHistorySyncId(db, newSyncId);
        });
      }
    );
  },

  /**
   * Returns the last sync time, in seconds, for the history collection, or 0
   * if history has never synced before.
   */
  async getLastSync() {
    let lastSync = await lazy.PlacesUtils.metadata.get(
      HistorySyncUtils.LAST_SYNC_META_KEY,
      0
    );
    return lastSync / 1000;
  },

  /**
   * Updates the history collection last sync time.
   *
   * @param lastSyncSeconds
   *        The collection last sync time, in seconds, as a number or string.
   */
  async setLastSync(lastSyncSeconds) {
    let lastSync = Math.floor(lastSyncSeconds * 1000);
    if (!Number.isInteger(lastSync)) {
      throw new TypeError("Invalid history last sync timestamp");
    }
    await lazy.PlacesUtils.metadata.set(
      HistorySyncUtils.LAST_SYNC_META_KEY,
      lastSync
    );
  },

  /**
   * Removes all history visits and pages from the database. Sync calls this
   * method when it receives a command from a remote client to wipe all stored
   * data.
   *
   * @return {Promise} resolved once all pages and visits have been removed.
   */
  async wipe() {
    await lazy.PlacesUtils.history.clear();
    await HistorySyncUtils.reset();
  },

  /**
   * Removes the sync ID and last sync time for the history collection. Unlike
   * `wipe`, this keeps all existing history pages and visits.
   *
   * @return {Promise} resolved once the metadata have been removed.
   */
  reset() {
    return lazy.PlacesUtils.metadata.delete(
      HistorySyncUtils.SYNC_ID_META_KEY,
      HistorySyncUtils.LAST_SYNC_META_KEY
    );
  },

  /**
   * Clamps a history visit date between the current date and the earliest
   * sensible date.
   *
   * @param {Date} visitDate
   *        The visit date.
   * @return {Date} The clamped visit date.
   */
  clampVisitDate(visitDate) {
    let currentDate = new Date();
    if (visitDate > currentDate) {
      return currentDate;
    }
    if (visitDate < BookmarkSyncUtils.EARLIEST_BOOKMARK_TIMESTAMP) {
      return new Date(BookmarkSyncUtils.EARLIEST_BOOKMARK_TIMESTAMP);
    }
    return visitDate;
  },

  /**
   * Fetches the frecency for the URL provided
   *
   * @param url
   * @returns {Number} The frecency of the given url
   */
  async fetchURLFrecency(url) {
    let canonicalURL = lazy.PlacesUtils.SYNC_BOOKMARK_VALIDATORS.url(url);

    let db = await lazy.PlacesUtils.promiseDBConnection();
    let rows = await db.executeCached(
      `
      SELECT frecency
      FROM moz_places
      WHERE url_hash = hash(:url) AND url = :url
      LIMIT 1`,
      { url: canonicalURL.href }
    );

    return rows.length ? rows[0].getResultByName("frecency") : -1;
  },

  /**
   * Filters syncable places from a collection of places guids.
   *
   * @param guids
   *
   * @returns {Array} new Array with the guids that aren't syncable
   */
  async determineNonSyncableGuids(guids) {
    // Filter out hidden pages and transitions that we don't sync.
    let db = await lazy.PlacesUtils.promiseDBConnection();
    let nonSyncableGuids = [];
    for (let chunk of lazy.PlacesUtils.chunkArray(guids, db.variableLimit)) {
      let rows = await db.execute(
        `
        SELECT DISTINCT p.guid FROM moz_places p
        JOIN moz_historyvisits v ON p.id = v.place_id
        WHERE p.guid IN (${new Array(chunk.length).fill("?").join(",")}) AND
            (p.hidden = 1 OR v.visit_type IN (${
              lazy.IGNORED_TRANSITIONS_AS_SQL_LIST
            }))
      `,
        chunk
      );
      nonSyncableGuids = nonSyncableGuids.concat(
        rows.map(row => row.getResultByName("guid"))
      );
    }
    return nonSyncableGuids;
  },

  /**
   * Change the guid of the given uri
   *
   * @param uri
   * @param guid
   */
  changeGuid(uri, guid) {
    let canonicalURL = lazy.PlacesUtils.SYNC_BOOKMARK_VALIDATORS.url(uri);
    let validatedGuid = lazy.PlacesUtils.BOOKMARK_VALIDATORS.guid(guid);
    return lazy.PlacesUtils.withConnectionWrapper(
      "PlacesSyncUtils.history: changeGuid",
      async function (db) {
        await db.executeCached(
          `
            UPDATE moz_places
            SET guid = :guid
            WHERE url_hash = hash(:page_url) AND url = :page_url`,
          { guid: validatedGuid, page_url: canonicalURL.href }
        );
      }
    );
  },

  /**
   * Fetch the last 20 visits (date and type of it) corresponding to a given url
   *
   * @param url
   * @returns {Array} Each element of the Array is an object with members: date and type
   */
  async fetchVisitsForURL(url) {
    let canonicalURL = lazy.PlacesUtils.SYNC_BOOKMARK_VALIDATORS.url(url);
    let db = await lazy.PlacesUtils.promiseDBConnection();
    let rows = await db.executeCached(
      `
      SELECT visit_type type, visit_date date,
      json_extract(e.sync_json, '$.unknown_sync_fields') as unknownSyncFields
      FROM moz_historyvisits v
      JOIN moz_places h ON h.id = v.place_id
      LEFT OUTER JOIN moz_historyvisits_extra e ON e.visit_id = v.id
      WHERE url_hash = hash(:url) AND url = :url
      ORDER BY date DESC LIMIT 20`,
      { url: canonicalURL.href }
    );
    return rows.map(row => {
      let visitDate = row.getResultByName("date");
      let visitType = row.getResultByName("type");
      let visit = { date: visitDate, type: visitType };

      // We should grab unknown fields to roundtrip them
      // back to the server
      let unknownFields = row.getResultByName("unknownSyncFields");
      if (unknownFields) {
        let unknownFieldsObj = JSON.parse(unknownFields);
        for (const key in unknownFieldsObj) {
          // We have to manually add it to the cleartext since that's
          // what gets processed during upload
          visit[key] = unknownFieldsObj[key];
        }
      }
      return visit;
    });
  },

  /**
   * Fetches the guid of a uri
   *
   * @param uri
   * @returns {String} The guid of the given uri
   */
  async fetchGuidForURL(url) {
    let canonicalURL = lazy.PlacesUtils.SYNC_BOOKMARK_VALIDATORS.url(url);
    let db = await lazy.PlacesUtils.promiseDBConnection();
    let rows = await db.executeCached(
      `
        SELECT guid
        FROM moz_places
        WHERE url_hash = hash(:page_url) AND url = :page_url`,
      { page_url: canonicalURL.href }
    );
    if (!rows.length) {
      return null;
    }
    return rows[0].getResultByName("guid");
  },

  /**
   * Fetch information about a guid (url, title and frecency)
   *
   * @param guid
   * @returns {Object} Object with three members: url, title and frecency of the given guid
   */
  async fetchURLInfoForGuid(guid) {
    let db = await lazy.PlacesUtils.promiseDBConnection();
    let rows = await db.executeCached(
      `
      SELECT url, IFNULL(title, '') AS title, frecency,
      json_extract(e.sync_json, '$.unknown_sync_fields') as unknownSyncFields
      FROM moz_places h
      LEFT OUTER JOIN moz_places_extra e ON e.place_id = h.id
      WHERE guid = :guid`,
      { guid }
    );
    if (rows.length === 0) {
      return null;
    }

    let info = {
      url: rows[0].getResultByName("url"),
      title: rows[0].getResultByName("title"),
      frecency: rows[0].getResultByName("frecency"),
    };
    let unknownFields = rows[0].getResultByName("unknownSyncFields");
    if (unknownFields) {
      // This will be unfurled at the caller since the
      // cleartext process will drop this
      info.unknownFields = unknownFields;
    }
    return info;
  },

  /**
   * Get all URLs filtered by the limit and since members of the options object.
   *
   * @param options
   *        Options object with two members, since and limit. Both of them must be provided
   * @returns {Array} - Up to limit number of URLs starting from the date provided by since
   *
   * Note that some visit types are explicitly excluded - downloads and framed
   * links.
   */
  async getAllURLs(options) {
    // Check that the limit property is finite number.
    if (!Number.isFinite(options.limit)) {
      throw new Error("The number provided in options.limit is not finite.");
    }
    // Check that the since property is of type Date.
    if (
      !options.since ||
      Object.prototype.toString.call(options.since) != "[object Date]"
    ) {
      throw new Error(
        "The property since of the options object must be of type Date."
      );
    }
    let db = await lazy.PlacesUtils.promiseDBConnection();
    let sinceInMicroseconds = lazy.PlacesUtils.toPRTime(options.since);
    let rows = await db.executeCached(
      `
      SELECT DISTINCT p.url
      FROM moz_places p
      JOIN moz_historyvisits v ON p.id = v.place_id
      WHERE p.last_visit_date > :cutoff_date AND
            p.hidden = 0 AND
            v.visit_type NOT IN (${lazy.IGNORED_TRANSITIONS_AS_SQL_LIST})
      ORDER BY frecency DESC
      LIMIT :max_results`,
      { cutoff_date: sinceInMicroseconds, max_results: options.limit }
    );
    return rows.map(row => row.getResultByName("url"));
  },
  /**
   * Insert or update the unknownFields that this client doesn't understand (yet)
   * but stores & roundtrips them to prevent other clients from losing that data
   *
   * @param updates array of objects
   *  an update object needs to have either a:
   *  placeId: if we're putting unknownFields for a moz_places item
   *  visitId: if we're putting unknownFields for a moz_historyvisits item
   *  Note: Supplying none or both will result in that record being ignored
   *  unknownFields: the stringified json to insert
   */
  async updateUnknownFieldsBatch(updates) {
    return lazy.PlacesUtils.withConnectionWrapper(
      "HistorySyncUtils: updateUnknownFieldsBatch",
      async function (db) {
        await db.executeTransaction(async () => {
          for await (const update of updates) {
            // Validate we only have one of these props
            if (
              (update.placeId && update.visitId) ||
              (!update.placeId && !update.visitId)
            ) {
              continue;
            }
            let tableName = update.placeId
              ? "moz_places_extra"
              : "moz_historyvisits_extra";
            let keyName = update.placeId ? "place_id" : "visit_id";
            await db.executeCached(
              `
            INSERT INTO ${tableName} (${keyName}, sync_json)
            VALUES (
              :keyValue,
              json_object('unknown_sync_fields', :unknownFields)
            )
            ON CONFLICT(${keyName}) DO UPDATE SET
            sync_json=json_patch(${tableName}.sync_json, json_object('unknown_sync_fields',:unknownFields))
            `,
              {
                keyValue: update.placeId ?? update.visitId,
                unknownFields: update.unknownFields,
              }
            );
          }
        });
      }
    );
  },
  // End of history freeze
}));

const BookmarkSyncUtils = (PlacesSyncUtils.bookmarks = Object.freeze({
  SYNC_ID_META_KEY: "sync/bookmarks/syncId",
  LAST_SYNC_META_KEY: "sync/bookmarks/lastSync",
  WIPE_REMOTE_META_KEY: "sync/bookmarks/wipeRemote",

  // Jan 23, 1993 in milliseconds since 1970. Corresponds roughly to the release
  // of the original NCSA Mosiac. We can safely assume that any dates before
  // this time are invalid.
  EARLIEST_BOOKMARK_TIMESTAMP: Date.UTC(1993, 0, 23),

  KINDS: {
    BOOKMARK: "bookmark",
    QUERY: "query",
    FOLDER: "folder",
    LIVEMARK: "livemark",
    SEPARATOR: "separator",
  },

  get ROOTS() {
    return lazy.ROOTS;
  },

  /**
   * Returns the current bookmarks sync ID, or `""` if one isn't set.
   */
  getSyncId() {
    return lazy.PlacesUtils.metadata.get(
      BookmarkSyncUtils.SYNC_ID_META_KEY,
      ""
    );
  },

  /**
   * Indicates if the bookmarks engine should erase all bookmarks on the server
   * and all other clients, because the user manually restored their bookmarks
   * from a backup on this client.
   */
  async shouldWipeRemote() {
    let shouldWipeRemote = await lazy.PlacesUtils.metadata.get(
      BookmarkSyncUtils.WIPE_REMOTE_META_KEY,
      false
    );
    return !!shouldWipeRemote;
  },

  /**
   * Assigns a new sync ID, bumps the change counter, and flags all items as
   * "NEW" for upload. This is called when we sync for the first time with a
   * new account, when we're the first to sync after a node reassignment, and
   * on the first sync after a manual restore.
   *
   * @return {Promise} resolved once the ID and all items have been updated.
   * @resolves to the new sync ID.
   */
  resetSyncId() {
    return lazy.PlacesUtils.withConnectionWrapper(
      "BookmarkSyncUtils: resetSyncId",
      function (db) {
        let newSyncId = lazy.PlacesUtils.history.makeGuid();
        return db.executeTransaction(async function () {
          await setBookmarksSyncId(db, newSyncId);
          await resetAllSyncStatuses(
            db,
            lazy.PlacesUtils.bookmarks.SYNC_STATUS.NEW
          );
          return newSyncId;
        });
      }
    );
  },

  /**
   * Ensures that the existing local sync ID, if any, is up-to-date with the
   * server. This is called when we sync with an existing account.
   *
   * We always take the server's sync ID. If we don't have an existing ID,
   * we're either syncing for the first time with an existing account, or Places
   * has automatically restored from a backup. If the sync IDs don't match,
   * we're likely syncing after a node reassignment, where another client
   * uploaded their bookmarks first.
   *
   * @param newSyncId
   *        The server's sync ID.
   * @return {Promise} resolved once the ID and all items have been updated.
   */
  async ensureCurrentSyncId(newSyncId) {
    if (!newSyncId || typeof newSyncId != "string") {
      throw new TypeError("Invalid new bookmarks sync ID");
    }
    await lazy.PlacesUtils.withConnectionWrapper(
      "BookmarkSyncUtils: ensureCurrentSyncId",
      async function (db) {
        let existingSyncId = await lazy.PlacesUtils.metadata.getWithConnection(
          db,
          BookmarkSyncUtils.SYNC_ID_META_KEY,
          ""
        );

        // If we don't have a sync ID, take the server's without resetting
        // sync statuses.
        if (!existingSyncId) {
          lazy.BookmarkSyncLog.info("Taking new bookmarks sync ID", {
            newSyncId,
          });
          await db.executeTransaction(() => setBookmarksSyncId(db, newSyncId));
          return;
        }

        // If the existing sync ID matches the server, great!
        if (existingSyncId == newSyncId) {
          lazy.BookmarkSyncLog.trace("Bookmarks sync ID up-to-date", {
            existingSyncId,
          });
          return;
        }

        // Otherwise, we have a sync ID, but it doesn't match, so we were likely
        // node reassigned. Take the server's sync ID and reset all items to
        // "UNKNOWN" so that we can merge.
        lazy.BookmarkSyncLog.info(
          "Bookmarks sync ID changed; resetting sync statuses",
          { existingSyncId, newSyncId }
        );
        await db.executeTransaction(async function () {
          await setBookmarksSyncId(db, newSyncId);
          await resetAllSyncStatuses(
            db,
            lazy.PlacesUtils.bookmarks.SYNC_STATUS.UNKNOWN
          );
        });
      }
    );
  },

  /**
   * Returns the last sync time, in seconds, for the bookmarks collection, or 0
   * if bookmarks have never synced before.
   */
  async getLastSync() {
    let lastSync = await lazy.PlacesUtils.metadata.get(
      BookmarkSyncUtils.LAST_SYNC_META_KEY,
      0
    );
    return lastSync / 1000;
  },

  /**
   * Updates the bookmarks collection last sync time.
   *
   * @param lastSyncSeconds
   *        The collection last sync time, in seconds, as a number or string.
   */
  async setLastSync(lastSyncSeconds) {
    let lastSync = Math.floor(lastSyncSeconds * 1000);
    if (!Number.isInteger(lastSync)) {
      throw new TypeError("Invalid bookmarks last sync timestamp");
    }
    await lazy.PlacesUtils.metadata.set(
      BookmarkSyncUtils.LAST_SYNC_META_KEY,
      lastSync
    );
  },

  /**
   * Resets Sync metadata for bookmarks in Places. This function behaves
   * differently depending on the change source, and may be called from
   * `PlacesSyncUtils.bookmarks.reset` or
   * `PlacesUtils.bookmarks.eraseEverything`.
   *
   * - RESTORE: The user is restoring from a backup. Drop the sync ID, last
   *   sync time, and tombstones; reset sync statuses for remaining items to
   *   "NEW"; then set a flag to wipe the server and all other clients. On the
   *   next sync, we'll replace their bookmarks with ours.
   *
   * - RESTORE_ON_STARTUP: Places is automatically restoring from a backup to
   *   recover from a corrupt database. The sync ID, last sync time, and
   *   tombstones don't exist, since we don't back them up; reset sync statuses
   *   for the roots to "UNKNOWN"; but don't wipe the server. On the next sync,
   *   we'll merge the restored bookmarks with the ones on the server.
   *
   * - SYNC: Either another client told us to erase our bookmarks
   *   (`PlacesSyncUtils.bookmarks.wipe`), or the user disconnected Sync
   *   (`PlacesSyncUtils.bookmarks.reset`). In both cases, drop the existing
   *   sync ID, last sync time, and tombstones; reset sync statuses for
   *   remaining items to "NEW"; and don't wipe the server.
   *
   * @param db
   *        the Sqlite.sys.mjs connection handle.
   * @param source
   *        the change source constant.
   */
  async resetSyncMetadata(db, source) {
    if (
      ![
        lazy.PlacesUtils.bookmarks.SOURCES.RESTORE,
        lazy.PlacesUtils.bookmarks.SOURCES.RESTORE_ON_STARTUP,
        lazy.PlacesUtils.bookmarks.SOURCES.SYNC,
      ].includes(source)
    ) {
      return;
    }

    // Remove the sync ID and last sync time in all cases.
    await lazy.PlacesUtils.metadata.deleteWithConnection(
      db,
      BookmarkSyncUtils.SYNC_ID_META_KEY,
      BookmarkSyncUtils.LAST_SYNC_META_KEY
    );

    // If we're manually restoring from a backup, wipe the server and other
    // clients, so that we replace their bookmarks with the restored tree. If
    // we're automatically restoring to recover from a corrupt database, don't
    // wipe; we want to merge the restored tree with the one on the server.
    await lazy.PlacesUtils.metadata.setWithConnection(
      db,
      new Map([
        [
          BookmarkSyncUtils.WIPE_REMOTE_META_KEY,
          source == lazy.PlacesUtils.bookmarks.SOURCES.RESTORE,
        ],
      ])
    );

    // Reset change counters and sync statuses for roots and remaining
    // items, and drop tombstones.
    let syncStatus =
      source == lazy.PlacesUtils.bookmarks.SOURCES.RESTORE_ON_STARTUP
        ? lazy.PlacesUtils.bookmarks.SYNC_STATUS.UNKNOWN
        : lazy.PlacesUtils.bookmarks.SYNC_STATUS.NEW;
    await resetAllSyncStatuses(db, syncStatus);
  },

  /**
   * Converts a Places GUID to a Sync record ID. Record IDs are identical to
   * Places GUIDs for all items except roots.
   */
  guidToRecordId(guid) {
    return lazy.ROOT_GUID_TO_RECORD_ID[guid] || guid;
  },

  /**
   * Converts a Sync record ID to a Places GUID.
   */
  recordIdToGuid(recordId) {
    return lazy.ROOT_RECORD_ID_TO_GUID[recordId] || recordId;
  },

  /**
   * Fetches the record IDs for a folder's children, ordered by their position
   * within the folder.
   * Used only be tests - but that includes tps, so it lives here.
   */
  fetchChildRecordIds(parentRecordId) {
    lazy.PlacesUtils.SYNC_BOOKMARK_VALIDATORS.recordId(parentRecordId);
    let parentGuid = BookmarkSyncUtils.recordIdToGuid(parentRecordId);

    return lazy.PlacesUtils.withConnectionWrapper(
      "BookmarkSyncUtils: fetchChildRecordIds",
      async function (db) {
        let childGuids = await fetchChildGuids(db, parentGuid);
        return childGuids.map(guid => BookmarkSyncUtils.guidToRecordId(guid));
      }
    );
  },

  /**
   * Migrates an array of `{ recordId, modified }` tuples from the old JSON-based
   * tracker to the new sync change counter. `modified` is when the change was
   * added to the old tracker, in milliseconds.
   *
   * Sync calls this method before the first bookmark sync after the Places
   * schema migration.
   */
  migrateOldTrackerEntries(entries) {
    return lazy.PlacesUtils.withConnectionWrapper(
      "BookmarkSyncUtils: migrateOldTrackerEntries",
      function (db) {
        return db.executeTransaction(async function () {
          // Mark all existing bookmarks as synced, and clear their change
          // counters to avoid a full upload on the next sync. Note that
          // this means we'll miss changes made between startup and the first
          // post-migration sync, as well as changes made on a new release
          // channel that weren't synced before the user downgraded. This is
          // unfortunate, but no worse than the behavior of the old tracker.
          //
          // We also likely have bookmarks that don't exist on the server,
          // because the old tracker missed them. We'll eventually fix the
          // server once we decide on a repair strategy.
          await db.executeCached(
            `
            WITH RECURSIVE
            syncedItems(id) AS (
              SELECT b.id FROM moz_bookmarks b
              WHERE b.guid IN ('menu________', 'toolbar_____', 'unfiled_____',
                               'mobile______')
              UNION ALL
              SELECT b.id FROM moz_bookmarks b
              JOIN syncedItems s ON b.parent = s.id
            )
            UPDATE moz_bookmarks SET
              syncStatus = :syncStatus,
              syncChangeCounter = 0
            WHERE id IN syncedItems`,
            { syncStatus: lazy.PlacesUtils.bookmarks.SYNC_STATUS.NORMAL }
          );

          await db.executeCached(`DELETE FROM moz_bookmarks_deleted`);

          await db.executeCached(`CREATE TEMP TABLE moz_bookmarks_tracked (
            guid TEXT PRIMARY KEY,
            time INTEGER
          )`);

          try {
            for (let { recordId, modified } of entries) {
              let guid = BookmarkSyncUtils.recordIdToGuid(recordId);
              if (!lazy.PlacesUtils.isValidGuid(guid)) {
                lazy.BookmarkSyncLog.warn(
                  `migrateOldTrackerEntries: Ignoring ` +
                    `change for invalid item ${guid}`
                );
                continue;
              }
              let time = lazy.PlacesUtils.toPRTime(
                Number.isFinite(modified) ? modified : Date.now()
              );
              await db.executeCached(
                `
                INSERT OR IGNORE INTO moz_bookmarks_tracked (guid, time)
                VALUES (:guid, :time)`,
                { guid, time }
              );
            }

            // Bump the change counter for existing tracked items.
            await db.executeCached(`
              INSERT OR REPLACE INTO moz_bookmarks (id, fk, type, parent,
                                                    position, title,
                                                    dateAdded, lastModified,
                                                    guid, syncChangeCounter,
                                                    syncStatus)
              SELECT b.id, b.fk, b.type, b.parent, b.position, b.title,
                     b.dateAdded, MAX(b.lastModified, t.time), b.guid,
                     b.syncChangeCounter + 1, b.syncStatus
              FROM moz_bookmarks b
              JOIN moz_bookmarks_tracked t ON b.guid = t.guid`);

            // Insert tombstones for nonexistent tracked items, using the most
            // recent deletion date for more accurate reconciliation. We assume
            // the tracked item belongs to a synced root.
            await db.executeCached(`
              INSERT OR REPLACE INTO moz_bookmarks_deleted (guid, dateRemoved)
              SELECT t.guid, MAX(IFNULL((SELECT dateRemoved FROM moz_bookmarks_deleted
                                         WHERE guid = t.guid), 0), t.time)
              FROM moz_bookmarks_tracked t
              LEFT JOIN moz_bookmarks b ON t.guid = b.guid
              WHERE b.guid IS NULL`);
          } finally {
            await db.executeCached(`DROP TABLE moz_bookmarks_tracked`);
          }
        });
      }
    );
  },

  /**
   * Reorders a folder's children, based on their order in the array of sync
   * IDs.
   *
   * Sync uses this method to reorder all synced children after applying all
   * incoming records.
   *
   * @return {Promise} resolved when reordering is complete.
   * @rejects if an error happens while reordering.
   * @throws if the arguments are invalid.
   */
  order(parentRecordId, childRecordIds) {
    lazy.PlacesUtils.SYNC_BOOKMARK_VALIDATORS.recordId(parentRecordId);
    if (!childRecordIds.length) {
      return undefined;
    }
    let parentGuid = BookmarkSyncUtils.recordIdToGuid(parentRecordId);
    if (parentGuid == lazy.PlacesUtils.bookmarks.rootGuid) {
      // Reordering roots doesn't make sense, but Sync will do this on the
      // first sync.
      return undefined;
    }
    let orderedChildrenGuids = childRecordIds.map(
      BookmarkSyncUtils.recordIdToGuid
    );
    return lazy.PlacesUtils.bookmarks.reorder(
      parentGuid,
      orderedChildrenGuids,
      {
        source: SOURCE_SYNC,
      }
    );
  },

  /**
   * Resolves to true if there are known sync changes.
   */
  havePendingChanges() {
    return lazy.PlacesUtils.withConnectionWrapper(
      "BookmarkSyncUtils: havePendingChanges",
      async function (db) {
        let rows = await db.executeCached(`
          WITH RECURSIVE
          syncedItems(id, guid, syncChangeCounter) AS (
            SELECT b.id, b.guid, b.syncChangeCounter
             FROM moz_bookmarks b
             WHERE b.guid IN ('menu________', 'toolbar_____', 'unfiled_____',
                              'mobile______')
            UNION ALL
            SELECT b.id, b.guid, b.syncChangeCounter
            FROM moz_bookmarks b
            JOIN syncedItems s ON b.parent = s.id
          ),
          changedItems(guid) AS (
            SELECT guid FROM syncedItems
            WHERE syncChangeCounter >= 1
            UNION ALL
            SELECT guid FROM moz_bookmarks_deleted
          )
          SELECT EXISTS(SELECT guid FROM changedItems) AS haveChanges`);
        return !!rows[0].getResultByName("haveChanges");
      }
    );
  },

  /**
   * Returns a changeset containing local bookmark changes since the last sync.
   *
   * @return {Promise} resolved once all items have been fetched.
   * @resolves to an object containing records for changed bookmarks, keyed by
   *           the record ID.
   * @see pullSyncChanges for the implementation, and markChangesAsSyncing for
   *      an explanation of why we update the sync status.
   */
  pullChanges() {
    return lazy.PlacesUtils.withConnectionWrapper(
      "BookmarkSyncUtils: pullChanges",
      pullSyncChanges
    );
  },

  /**
   * Updates the sync status of all "NEW" bookmarks to "NORMAL", so that Sync
   * can recover correctly after an interrupted sync.
   *
   * @param changeRecords
   *        A changeset containing sync change records, as returned by
   *        `pullChanges`.
   * @return {Promise} resolved once all records have been updated.
   */
  markChangesAsSyncing(changeRecords) {
    return lazy.PlacesUtils.withConnectionWrapper(
      "BookmarkSyncUtils: markChangesAsSyncing",
      db => markChangesAsSyncing(db, changeRecords)
    );
  },

  /**
   * Decrements the sync change counter, updates the sync status, and cleans up
   * tombstones for successfully synced items. Sync calls this method at the
   * end of each bookmark sync.
   *
   * @param changeRecords
   *        A changeset containing sync change records, as returned by
   *        `pullChanges`.
   * @return {Promise} resolved once all records have been updated.
   */
  pushChanges(changeRecords) {
    return lazy.PlacesUtils.withConnectionWrapper(
      "BookmarkSyncUtils: pushChanges",
      async function (db) {
        let skippedCount = 0;
        let weakCount = 0;
        let updateParams = [];
        let tombstoneGuidsToRemove = [];

        for (let recordId in changeRecords) {
          // Validate change records to catch coding errors.
          let changeRecord = validateChangeRecord(
            "BookmarkSyncUtils: pushChanges",
            changeRecords[recordId],
            {
              tombstone: { required: true },
              counter: { required: true },
              synced: { required: true },
            }
          );

          // Skip weakly uploaded records.
          if (!changeRecord.counter) {
            weakCount++;
            continue;
          }

          // Sync sets the `synced` flag for reconciled or successfully
          // uploaded items. If upload failed, ignore the change; we'll
          // try again on the next sync.
          if (!changeRecord.synced) {
            skippedCount++;
            continue;
          }

          let guid = BookmarkSyncUtils.recordIdToGuid(recordId);
          if (changeRecord.tombstone) {
            tombstoneGuidsToRemove.push(guid);
          } else {
            updateParams.push({
              guid,
              syncChangeDelta: changeRecord.counter,
              syncStatus: lazy.PlacesUtils.bookmarks.SYNC_STATUS.NORMAL,
            });
          }
        }

        // Reduce the change counter and update the sync status for
        // reconciled and uploaded items. If the bookmark was updated
        // during the sync, its change counter will still be > 0 for the
        // next sync.
        if (updateParams.length || tombstoneGuidsToRemove.length) {
          await db.executeTransaction(async function () {
            if (updateParams.length) {
              await db.executeCached(
                `
                UPDATE moz_bookmarks
                SET syncChangeCounter = MAX(syncChangeCounter - :syncChangeDelta, 0),
                    syncStatus = :syncStatus
                WHERE guid = :guid`,
                updateParams
              );
              // and if there are *both* bookmarks and tombstones for these
              // items, we nuke the tombstones.
              // This should be unlikely, but bad if it happens.
              let dupedGuids = updateParams.map(({ guid }) => guid);
              await removeUndeletedTombstones(db, dupedGuids);
            }
            await removeTombstones(db, tombstoneGuidsToRemove);
          });
        }

        lazy.BookmarkSyncLog.debug(`pushChanges: Processed change records`, {
          weak: weakCount,
          skipped: skippedCount,
          updated: updateParams.length,
        });
      }
    );
  },

  /**
   * Removes items from the database. Sync buffers incoming tombstones, and
   * calls this method to apply them at the end of each sync. Deletion
   * happens in three steps:
   *
   *  1. Remove all non-folder items. Deleting a folder on a remote client
   *     uploads tombstones for the folder and its children at the time of
   *     deletion. This preserves any new children we've added locally since
   *     the last sync.
   *  2. Reparent remaining children to the tombstoned folder's parent. This
   *     bumps the change counter for the children and their new parent.
   *  3. Remove the tombstoned folder. Because we don't do this in a
   *     transaction, the user might move new items into the folder before we
   *     can remove it. In that case, we keep the folder and upload the new
   *     subtree to the server.
   *
   * See the comment above `BookmarksStore::deletePending` for the details on
   * why delete works the way it does.
   */
  remove(recordIds) {
    if (!recordIds.length) {
      return null;
    }

    return lazy.PlacesUtils.withConnectionWrapper(
      "BookmarkSyncUtils: remove",
      async function (db) {
        let folderGuids = [];
        for (let recordId of recordIds) {
          if (recordId in lazy.ROOT_RECORD_ID_TO_GUID) {
            lazy.BookmarkSyncLog.warn(
              `remove: Refusing to remove root ${recordId}`
            );
            continue;
          }
          let guid = BookmarkSyncUtils.recordIdToGuid(recordId);
          let bookmarkItem = await lazy.PlacesUtils.bookmarks.fetch(guid);
          if (!bookmarkItem) {
            lazy.BookmarkSyncLog.trace(`remove: Item ${guid} already removed`);
            continue;
          }
          let kind = await getKindForItem(db, bookmarkItem);
          if (kind == BookmarkSyncUtils.KINDS.FOLDER) {
            folderGuids.push(bookmarkItem.guid);
            continue;
          }
          let wasRemoved = await deleteSyncedAtom(bookmarkItem);
          if (wasRemoved) {
            lazy.BookmarkSyncLog.trace(
              `remove: Removed item ${guid} with kind ${kind}`
            );
          }
        }

        for (let guid of folderGuids) {
          let bookmarkItem = await lazy.PlacesUtils.bookmarks.fetch(guid);
          if (!bookmarkItem) {
            lazy.BookmarkSyncLog.trace(
              `remove: Folder ${guid} already removed`
            );
            continue;
          }
          let wasRemoved = await deleteSyncedFolder(db, bookmarkItem);
          if (wasRemoved) {
            lazy.BookmarkSyncLog.trace(
              `remove: Removed folder ${bookmarkItem.guid}`
            );
          }
        }

        // TODO (Bug 1313890): Refactor the bookmarks engine to pull change records
        // before uploading, instead of returning records to merge into the engine's
        // initial changeset.
        return pullSyncChanges(db);
      }
    );
  },

  /**
   * Removes all bookmarks and tombstones from the database. Sync calls this
   * method when it receives a command from a remote client to wipe all stored
   * data.
   *
   * @return {Promise} resolved once all items have been removed.
   */
  wipe() {
    return lazy.PlacesUtils.bookmarks.eraseEverything({
      source: SOURCE_SYNC,
    });
  },

  /**
   * Marks all bookmarks as "NEW" and removes all tombstones. Unlike `wipe`,
   * this keeps all existing bookmarks, and only clears their sync change
   * tracking info.
   *
   * @return {Promise} resolved once all items have been updated.
   */
  reset() {
    return lazy.PlacesUtils.withConnectionWrapper(
      "BookmarkSyncUtils: reset",
      function (db) {
        return db.executeTransaction(async function () {
          await BookmarkSyncUtils.resetSyncMetadata(db, SOURCE_SYNC);
        });
      }
    );
  },

  /**
   * Fetches a Sync bookmark object for an item in the tree.
   *
   * Should only be used by SYNC TESTS.
   * We should remove this in bug XXXXXX, updating the tests to use
   * PlacesUtils.bookmarks.fetch.
   *
   * The object contains
   * the following properties, depending on the item's kind:
   *
   *  - kind (all): A string representing the item's kind.
   *  - recordId (all): The item's record ID.
   *  - parentRecordId (all): The record ID of the item's parent.
   *  - parentTitle (all): The title of the item's parent, used for de-duping.
   *    Omitted for the Places root and parents with empty titles.
   *  - dateAdded (all): Timestamp in milliseconds, when the bookmark was added
   *    or created on a remote device if known.
   *  - title ("bookmark", "folder", "query"): The item's title.
   *    Omitted if empty.
   *  - url ("bookmark", "query"): The item's URL.
   *  - tags ("bookmark", "query"): An array containing the item's tags.
   *  - keyword ("bookmark"): The bookmark's keyword, if one exists.
   *  - childRecordIds ("folder"): An array containing the record IDs of the item's
   *    children, used to determine child order.
   *  - folder ("query"): The tag folder name, if this is a tag query.
   *  - index ("separator"): The separator's position within its parent.
   */
  async fetch(recordId) {
    let guid = BookmarkSyncUtils.recordIdToGuid(recordId);
    let bookmarkItem = await lazy.PlacesUtils.bookmarks.fetch(guid);
    if (!bookmarkItem) {
      return null;
    }
    return lazy.PlacesUtils.withConnectionWrapper(
      "BookmarkSyncUtils: fetch",
      async function (db) {
        // Convert the Places bookmark object to a Sync bookmark and add
        // kind-specific properties. Titles are required for bookmarks,
        // and folders; optional for queries, and omitted for separators.
        let kind = await getKindForItem(db, bookmarkItem);
        let item;
        switch (kind) {
          case BookmarkSyncUtils.KINDS.BOOKMARK:
            item = await fetchBookmarkItem(db, bookmarkItem);
            break;

          case BookmarkSyncUtils.KINDS.QUERY:
            item = await fetchQueryItem(db, bookmarkItem);
            break;

          case BookmarkSyncUtils.KINDS.FOLDER:
            item = await fetchFolderItem(db, bookmarkItem);
            break;

          case BookmarkSyncUtils.KINDS.SEPARATOR:
            item = await placesBookmarkToSyncBookmark(db, bookmarkItem);
            item.index = bookmarkItem.index;
            break;

          default:
            throw new Error(`Unknown bookmark kind: ${kind}`);
        }

        // Sync uses the parent title for de-duping. All Sync bookmark objects
        // except the Places root should have this property.
        if (bookmarkItem.parentGuid) {
          let parent = await lazy.PlacesUtils.bookmarks.fetch(
            bookmarkItem.parentGuid
          );
          item.parentTitle = parent.title || "";
        }

        return item;
      }
    );
  },

  /**
   * Returns the sync change counter increment for a change source constant.
   */
  determineSyncChangeDelta(source) {
    // Don't bump the change counter when applying changes made by Sync, to
    // avoid sync loops.
    return source == lazy.PlacesUtils.bookmarks.SOURCES.SYNC ? 0 : 1;
  },

  /**
   * Returns the sync status for a new item inserted by a change source.
   */
  determineInitialSyncStatus(source) {
    if (source == lazy.PlacesUtils.bookmarks.SOURCES.SYNC) {
      // Incoming bookmarks are "NORMAL", since they already exist on the server.
      return lazy.PlacesUtils.bookmarks.SYNC_STATUS.NORMAL;
    }
    if (source == lazy.PlacesUtils.bookmarks.SOURCES.RESTORE_ON_STARTUP) {
      // If the user restores from a backup, or Places automatically recovers
      // from a corrupt database, all prior sync tracking is lost. Setting the
      // status to "UNKNOWN" allows Sync to reconcile restored bookmarks with
      // those on the server.
      return lazy.PlacesUtils.bookmarks.SYNC_STATUS.UNKNOWN;
    }
    // For all other sources, mark items as "NEW". We'll update their statuses
    // to "NORMAL" after the first sync.
    return lazy.PlacesUtils.bookmarks.SYNC_STATUS.NEW;
  },

  /**
   * An internal helper that bumps the change counter for all bookmarks with
   * a given URL. This is used to update bookmarks when adding or changing a
   * tag or keyword entry.
   *
   * @param db
   *        the Sqlite.sys.mjs connection handle.
   * @param url
   *        the bookmark URL object.
   * @param syncChangeDelta
   *        the sync change counter increment.
   * @return {Promise} resolved when the counters have been updated.
   */
  addSyncChangesForBookmarksWithURL(db, url, syncChangeDelta) {
    if (!url || !syncChangeDelta) {
      return Promise.resolve();
    }
    return db.executeCached(
      `
      UPDATE moz_bookmarks
        SET syncChangeCounter = syncChangeCounter + :syncChangeDelta
      WHERE type = :type AND
            fk = (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND
                  url = :url)`,
      {
        syncChangeDelta,
        type: lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK,
        url: url.href,
      }
    );
  },

  /**
   * Returns `0` if no sensible timestamp could be found.
   * Otherwise, returns the earliest sensible timestamp between `existingMillis`
   * and `serverMillis`.
   */
  ratchetTimestampBackwards(
    existingMillis,
    serverMillis,
    lowerBound = BookmarkSyncUtils.EARLIEST_BOOKMARK_TIMESTAMP
  ) {
    const possible = [+existingMillis, +serverMillis].filter(
      n => !isNaN(n) && n > lowerBound
    );
    if (!possible.length) {
      return 0;
    }
    return Math.min(...possible);
  },

  /**
   * Rebuilds the left pane query for the mobile root under "All Bookmarks" if
   * necessary. Sync calls this method at the end of each bookmark sync. This
   * code should eventually move to `PlacesUIUtils#maybeRebuildLeftPane`; see
   * bug 647605.
   *
   * - If there are no mobile bookmarks, the query will not be created, or
   *   will be removed if it already exists.
   * - If there are mobile bookmarks, the query will be created if it doesn't
   *   exist, or will be updated with the correct title and URL otherwise.
   */
  async ensureMobileQuery() {
    let db = await lazy.PlacesUtils.promiseDBConnection();

    let mobileChildGuids = await fetchChildGuids(
      db,
      lazy.PlacesUtils.bookmarks.mobileGuid
    );
    let hasMobileBookmarks = !!mobileChildGuids.length;

    Services.prefs.setBoolPref(MOBILE_BOOKMARKS_PREF, hasMobileBookmarks);
  },
}));

PlacesSyncUtils.test = {};
PlacesSyncUtils.test.bookmarks = Object.freeze({
  /**
   * Inserts a synced bookmark into the tree. Only SYNC TESTS should call this
   * method; other callers should use `PlacesUtils.bookmarks.insert`.
   *
   * It is in this file rather than a test-only file because it makes use of
   * other internal functions here, so moving is not trivial - see bug 1662602.
   *
   * The following properties are supported:
   *  - kind: Required.
   *  - guid: Required.
   *  - parentGuid: Required.
   *  - url: Required for bookmarks.
   *  - tags: An optional array of tag strings.
   *  - keyword: An optional keyword string.
   *
   * Sync doesn't set the index, since it appends and reorders children
   * after applying all incoming items.
   *
   * @param info
   *        object representing a synced bookmark.
   *
   * @return {Promise} resolved when the creation is complete.
   * @resolves to an object representing the created bookmark.
   * @rejects if it's not possible to create the requested bookmark.
   * @throws if the arguments are invalid.
   */
  insert(info) {
    let insertInfo = validateNewBookmark("BookmarkTestUtils: insert", info);

    return lazy.PlacesUtils.withConnectionWrapper(
      "BookmarkTestUtils: insert",
      async db => {
        // If we're inserting a tag query, make sure the tag exists and fix the
        // folder ID to refer to the local tag folder.
        insertInfo = await updateTagQueryFolder(db, insertInfo);

        let bookmarkInfo = syncBookmarkToPlacesBookmark(insertInfo);
        let bookmarkItem = await lazy.PlacesUtils.bookmarks.insert(
          bookmarkInfo
        );
        let newItem = await insertBookmarkMetadata(
          db,
          bookmarkItem,
          insertInfo
        );

        return newItem;
      }
    );
  },
});

ChromeUtils.defineLazyGetter(lazy, "HistorySyncLog", () => {
  return lazy.Log.repository.getLogger("Sync.Engine.History.HistorySyncUtils");
});

ChromeUtils.defineLazyGetter(lazy, "BookmarkSyncLog", () => {
  // Use a sub-log of the bookmarks engine, so setting the level for that
  // engine also adjust the level of this log.
  return lazy.Log.repository.getLogger(
    "Sync.Engine.Bookmarks.BookmarkSyncUtils"
  );
});

function validateSyncBookmarkObject(name, input, behavior) {
  return lazy.PlacesUtils.validateItemProperties(
    name,
    lazy.PlacesUtils.SYNC_BOOKMARK_VALIDATORS,
    input,
    behavior
  );
}

// Validates a sync change record as returned by `pullChanges` and passed to
// `pushChanges`.
function validateChangeRecord(name, changeRecord, behavior) {
  return lazy.PlacesUtils.validateItemProperties(
    name,
    lazy.PlacesUtils.SYNC_CHANGE_RECORD_VALIDATORS,
    changeRecord,
    behavior
  );
}

// Similar to the private `fetchBookmarksByParent` implementation in
// `Bookmarks.sys.mjs`.
var fetchChildGuids = async function (db, parentGuid) {
  let rows = await db.executeCached(
    `
    SELECT guid
    FROM moz_bookmarks
    WHERE parent = (
      SELECT id FROM moz_bookmarks WHERE guid = :parentGuid
    )
    ORDER BY position`,
    { parentGuid }
  );
  return rows.map(row => row.getResultByName("guid"));
};

// Legacy tag queries may use a `place:` URL that refers to the tag folder ID.
// When we apply a synced tag query from a remote client, we need to update the
// URL to point to the local tag.
function updateTagQueryFolder(db, info) {
  if (
    info.kind != BookmarkSyncUtils.KINDS.QUERY ||
    !info.folder ||
    !info.url ||
    info.url.protocol != "place:"
  ) {
    return info;
  }

  let params = new URLSearchParams(info.url.pathname);
  let type = +params.get("type");
  if (type != Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS) {
    return info;
  }

  lazy.BookmarkSyncLog.debug(
    `updateTagQueryFolder: Tag query folder: ${info.folder}`
  );

  // Rewrite the query to directly reference the tag.
  params.delete("queryType");
  params.delete("type");
  params.delete("folder");
  params.set("tag", info.folder);
  info.url = new URL(info.url.protocol + params);
  return info;
}

// Keywords are a 1 to 1 mapping between strings and pairs of (URL, postData).
// (the postData is not synced, so we ignore it). Sync associates keywords with
// bookmarks, which is not really accurate. -- We might already have a keyword
// with that name, or we might already have another bookmark with that URL with
// a different keyword, etc.
//
// If we don't handle those cases by removing the conflicting keywords first,
// the insertion  will fail, and the keywords will either be wrong, or missing.
// This function handles those cases.
function removeConflictingKeywords(bookmarkURL, newKeyword) {
  return lazy.PlacesUtils.withConnectionWrapper(
    "BookmarkSyncUtils: removeConflictingKeywords",
    async function (db) {
      let entryForURL = await lazy.PlacesUtils.keywords.fetch({
        url: bookmarkURL.href,
      });
      if (entryForURL && entryForURL.keyword !== newKeyword) {
        await lazy.PlacesUtils.keywords.remove({
          keyword: entryForURL.keyword,
          source: SOURCE_SYNC,
        });
        // This will cause us to reupload this record for this sync, but
        // without it, we will risk data corruption.
        await BookmarkSyncUtils.addSyncChangesForBookmarksWithURL(
          db,
          entryForURL.url,
          1
        );
      }
      if (!newKeyword) {
        return;
      }
      let entryForNewKeyword = await lazy.PlacesUtils.keywords.fetch({
        keyword: newKeyword,
      });
      if (entryForNewKeyword) {
        await lazy.PlacesUtils.keywords.remove({
          keyword: entryForNewKeyword.keyword,
          source: SOURCE_SYNC,
        });
        await BookmarkSyncUtils.addSyncChangesForBookmarksWithURL(
          db,
          entryForNewKeyword.url,
          1
        );
      }
    }
  );
}

// Sets annotations, keywords, and tags on a new bookmark. Returns a Sync
// bookmark object.
async function insertBookmarkMetadata(db, bookmarkItem, insertInfo) {
  let newItem = await placesBookmarkToSyncBookmark(db, bookmarkItem);

  try {
    newItem.tags = tagItem(bookmarkItem, insertInfo.tags);
  } catch (ex) {
    lazy.BookmarkSyncLog.warn(
      `insertBookmarkMetadata: Error tagging item ${insertInfo.recordId}`,
      ex
    );
  }

  if (insertInfo.keyword) {
    await removeConflictingKeywords(bookmarkItem.url, insertInfo.keyword);
    await lazy.PlacesUtils.keywords.insert({
      keyword: insertInfo.keyword,
      url: bookmarkItem.url.href,
      source: SOURCE_SYNC,
    });
    newItem.keyword = insertInfo.keyword;
  }

  return newItem;
}

// Determines the Sync record kind for an existing bookmark.
async function getKindForItem(db, item) {
  switch (item.type) {
    case lazy.PlacesUtils.bookmarks.TYPE_FOLDER: {
      return BookmarkSyncUtils.KINDS.FOLDER;
    }
    case lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK:
      return item.url.protocol == "place:"
        ? BookmarkSyncUtils.KINDS.QUERY
        : BookmarkSyncUtils.KINDS.BOOKMARK;

    case lazy.PlacesUtils.bookmarks.TYPE_SEPARATOR:
      return BookmarkSyncUtils.KINDS.SEPARATOR;
  }
  return null;
}

// Returns the `nsINavBookmarksService` bookmark type constant for a Sync
// record kind.
function getTypeForKind(kind) {
  switch (kind) {
    case BookmarkSyncUtils.KINDS.BOOKMARK:
    case BookmarkSyncUtils.KINDS.QUERY:
      return lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK;

    case BookmarkSyncUtils.KINDS.FOLDER:
      return lazy.PlacesUtils.bookmarks.TYPE_FOLDER;

    case BookmarkSyncUtils.KINDS.SEPARATOR:
      return lazy.PlacesUtils.bookmarks.TYPE_SEPARATOR;
  }
  throw new Error(`Unknown bookmark kind: ${kind}`);
}

function validateNewBookmark(name, info) {
  let insertInfo = validateSyncBookmarkObject(name, info, {
    kind: { required: true },
    recordId: { required: true },
    url: {
      requiredIf: b =>
        [
          BookmarkSyncUtils.KINDS.BOOKMARK,
          BookmarkSyncUtils.KINDS.QUERY,
        ].includes(b.kind),
      validIf: b =>
        [
          BookmarkSyncUtils.KINDS.BOOKMARK,
          BookmarkSyncUtils.KINDS.QUERY,
        ].includes(b.kind),
    },
    parentRecordId: { required: true },
    title: {
      validIf: b =>
        [
          BookmarkSyncUtils.KINDS.BOOKMARK,
          BookmarkSyncUtils.KINDS.QUERY,
          BookmarkSyncUtils.KINDS.FOLDER,
        ].includes(b.kind) || b.title === "",
    },
    query: { validIf: b => b.kind == BookmarkSyncUtils.KINDS.QUERY },
    folder: { validIf: b => b.kind == BookmarkSyncUtils.KINDS.QUERY },
    tags: {
      validIf: b =>
        [
          BookmarkSyncUtils.KINDS.BOOKMARK,
          BookmarkSyncUtils.KINDS.QUERY,
        ].includes(b.kind),
    },
    keyword: {
      validIf: b =>
        [
          BookmarkSyncUtils.KINDS.BOOKMARK,
          BookmarkSyncUtils.KINDS.QUERY,
        ].includes(b.kind),
    },
    dateAdded: { required: false },
  });

  return insertInfo;
}

function tagItem(item, tags) {
  if (!item.url) {
    return [];
  }

  // Remove leading and trailing whitespace, then filter out empty tags.
  let newTags = tags ? tags.map(tag => tag.trim()).filter(Boolean) : [];

  // Removing the last tagged item will also remove the tag. To preserve
  // tag IDs, we temporarily tag a dummy URI, ensuring the tags exist.
  let dummyURI = lazy.PlacesUtils.toURI("about:weave#BStore_tagURI");
  let bookmarkURI = lazy.PlacesUtils.toURI(item.url);
  if (newTags && newTags.length) {
    lazy.PlacesUtils.tagging.tagURI(dummyURI, newTags, SOURCE_SYNC);
  }
  lazy.PlacesUtils.tagging.untagURI(bookmarkURI, null, SOURCE_SYNC);
  if (newTags && newTags.length) {
    lazy.PlacesUtils.tagging.tagURI(bookmarkURI, newTags, SOURCE_SYNC);
  }
  lazy.PlacesUtils.tagging.untagURI(dummyURI, null, SOURCE_SYNC);

  return newTags;
}

// Converts a Places bookmark to a Sync bookmark. This function maps Places
// GUIDs to record IDs and filters out extra Places properties like date added,
// last modified, and index.
async function placesBookmarkToSyncBookmark(db, bookmarkItem) {
  let item = {};

  for (let prop in bookmarkItem) {
    switch (prop) {
      // Record IDs are identical to Places GUIDs for all items except roots.
      case "guid":
        item.recordId = BookmarkSyncUtils.guidToRecordId(bookmarkItem.guid);
        break;

      case "parentGuid":
        item.parentRecordId = BookmarkSyncUtils.guidToRecordId(
          bookmarkItem.parentGuid
        );
        break;

      // Sync uses kinds instead of types, which distinguish between folders,
      // livemarks, bookmarks, and queries.
      case "type":
        item.kind = await getKindForItem(db, bookmarkItem);
        break;

      case "title":
      case "url":
        item[prop] = bookmarkItem[prop];
        break;

      case "dateAdded":
        item[prop] = new Date(bookmarkItem[prop]).getTime();
        break;
    }
  }

  return item;
}

// Converts a Sync bookmark object to a Places bookmark or livemark object.
// This function maps record IDs to Places GUIDs, and filters out extra Sync
// properties like keywords, tags. Returns an object that can be passed to
// `PlacesUtils.bookmarks.{insert, update}`.
function syncBookmarkToPlacesBookmark(info) {
  let bookmarkInfo = {
    source: SOURCE_SYNC,
  };

  for (let prop in info) {
    switch (prop) {
      case "kind":
        bookmarkInfo.type = getTypeForKind(info.kind);
        break;

      // Convert record IDs to Places GUIDs for roots.
      case "recordId":
        bookmarkInfo.guid = BookmarkSyncUtils.recordIdToGuid(info.recordId);
        break;

      case "dateAdded":
        bookmarkInfo.dateAdded = new Date(info.dateAdded);
        break;

      case "parentRecordId":
        bookmarkInfo.parentGuid = BookmarkSyncUtils.recordIdToGuid(
          info.parentRecordId
        );
        // Instead of providing an index, Sync reorders children at the end of
        // the sync using `BookmarkSyncUtils.order`. We explicitly specify the
        // default index here to prevent `PlacesUtils.bookmarks.update` from
        // throwing.
        bookmarkInfo.index = lazy.PlacesUtils.bookmarks.DEFAULT_INDEX;
        break;

      case "title":
      case "url":
        bookmarkInfo[prop] = info[prop];
        break;
    }
  }

  return bookmarkInfo;
}

// Creates and returns a Sync bookmark object containing the bookmark's
// tags, keyword.
var fetchBookmarkItem = async function (db, bookmarkItem) {
  let item = await placesBookmarkToSyncBookmark(db, bookmarkItem);

  if (!item.title) {
    item.title = "";
  }

  item.tags = lazy.PlacesUtils.tagging.getTagsForURI(
    lazy.PlacesUtils.toURI(bookmarkItem.url)
  );

  let keywordEntry = await lazy.PlacesUtils.keywords.fetch({
    url: bookmarkItem.url,
  });
  if (keywordEntry) {
    item.keyword = keywordEntry.keyword;
  }

  return item;
};

// Creates and returns a Sync bookmark object containing the folder's children.
async function fetchFolderItem(db, bookmarkItem) {
  let item = await placesBookmarkToSyncBookmark(db, bookmarkItem);

  if (!item.title) {
    item.title = "";
  }

  let childGuids = await fetchChildGuids(db, bookmarkItem.guid);
  item.childRecordIds = childGuids.map(guid =>
    BookmarkSyncUtils.guidToRecordId(guid)
  );

  return item;
}

// Creates and returns a Sync bookmark object containing the query's tag
// folder name.
async function fetchQueryItem(db, bookmarkItem) {
  let item = await placesBookmarkToSyncBookmark(db, bookmarkItem);

  let params = new URLSearchParams(bookmarkItem.url.pathname);
  let tags = params.getAll("tag");
  if (tags.length == 1) {
    item.folder = tags[0];
  }

  return item;
}

function addRowToChangeRecords(row, changeRecords) {
  let guid = row.getResultByName("guid");
  if (!guid) {
    throw new Error(`Changed item missing GUID`);
  }
  let isTombstone = !!row.getResultByName("tombstone");
  let recordId = BookmarkSyncUtils.guidToRecordId(guid);
  if (recordId in changeRecords) {
    let existingRecord = changeRecords[recordId];
    if (existingRecord.tombstone == isTombstone) {
      // Should never happen: `moz_bookmarks.guid` has a unique index, and
      // `moz_bookmarks_deleted.guid` is the primary key.
      throw new Error(`Duplicate item or tombstone ${recordId} in changeset`);
    }
    if (!existingRecord.tombstone && isTombstone) {
      // Don't replace undeleted items with tombstones...
      lazy.BookmarkSyncLog.warn(
        "addRowToChangeRecords: Ignoring tombstone for undeleted item",
        recordId
      );
      return;
    }
    // ...But replace undeleted tombstones with items.
    lazy.BookmarkSyncLog.warn(
      "addRowToChangeRecords: Replacing tombstone for undeleted item",
      recordId
    );
  }
  let modifiedAsPRTime = row.getResultByName("modified");
  let modified = modifiedAsPRTime / MICROSECONDS_PER_SECOND;
  if (Number.isNaN(modified) || modified <= 0) {
    lazy.BookmarkSyncLog.error(
      "addRowToChangeRecords: Invalid modified date for " + recordId,
      modifiedAsPRTime
    );
    modified = 0;
  }
  changeRecords[recordId] = {
    modified,
    counter: row.getResultByName("syncChangeCounter"),
    status: row.getResultByName("syncStatus"),
    tombstone: isTombstone,
    synced: false,
  };
}

/**
 * Queries the database for synced bookmarks and tombstones, and returns a
 * changeset for the Sync bookmarks engine.
 *
 * @param db
 *        The Sqlite.sys.mjs connection handle.
 * @param forGuids
 *        Fetch Sync tracking information for only the requested GUIDs.
 * @return {Promise} resolved once all items have been fetched.
 * @resolves to an object containing records for changed bookmarks, keyed by
 *           the record ID.
 */
var pullSyncChanges = async function (db, forGuids = []) {
  let changeRecords = {};

  let itemConditions = ["syncChangeCounter >= 1"];
  let tombstoneConditions = ["1 = 1"];
  if (forGuids.length) {
    let restrictToGuids = `guid IN (${forGuids
      .map(guid => JSON.stringify(guid))
      .join(",")})`;
    itemConditions.push(restrictToGuids);
    tombstoneConditions.push(restrictToGuids);
  }

  let rows = await db.executeCached(
    `
    WITH RECURSIVE
    syncedItems(id, guid, modified, syncChangeCounter, syncStatus) AS (
      SELECT b.id, b.guid, b.lastModified, b.syncChangeCounter, b.syncStatus
       FROM moz_bookmarks b
       WHERE b.guid IN ('menu________', 'toolbar_____', 'unfiled_____',
                        'mobile______')
      UNION ALL
      SELECT b.id, b.guid, b.lastModified, b.syncChangeCounter, b.syncStatus
      FROM moz_bookmarks b
      JOIN syncedItems s ON b.parent = s.id
    )
    SELECT guid, modified, syncChangeCounter, syncStatus, 0 AS tombstone
    FROM syncedItems
    WHERE ${itemConditions.join(" AND ")}
    UNION ALL
    SELECT guid, dateRemoved AS modified, 1 AS syncChangeCounter,
           :deletedSyncStatus, 1 AS tombstone
    FROM moz_bookmarks_deleted
    WHERE ${tombstoneConditions.join(" AND ")}`,
    { deletedSyncStatus: lazy.PlacesUtils.bookmarks.SYNC_STATUS.NORMAL }
  );
  for (let row of rows) {
    addRowToChangeRecords(row, changeRecords);
  }

  return changeRecords;
};

// Moves a synced folder's remaining children to its parent, and deletes the
// folder if it's empty.
async function deleteSyncedFolder(db, bookmarkItem) {
  // At this point, any member in the folder that remains is either a folder
  // pending deletion (which we'll get to in this function), or an item that
  // should not be deleted. To avoid deleting these items, we first move them
  // to the parent of the folder we're about to delete.
  let childGuids = await fetchChildGuids(db, bookmarkItem.guid);
  if (!childGuids.length) {
    // No children -- just delete the folder.
    return deleteSyncedAtom(bookmarkItem);
  }

  if (lazy.BookmarkSyncLog.level <= lazy.Log.Level.Trace) {
    lazy.BookmarkSyncLog.trace(
      `deleteSyncedFolder: Moving ${JSON.stringify(childGuids)} children of ` +
        `"${bookmarkItem.guid}" to grandparent
      "${BookmarkSyncUtils.guidToRecordId(bookmarkItem.parentGuid)}" before ` +
        `deletion`
    );
  }

  // Move children out of the parent and into the grandparent
  for (let guid of childGuids) {
    await lazy.PlacesUtils.bookmarks.update({
      guid,
      parentGuid: bookmarkItem.parentGuid,
      index: lazy.PlacesUtils.bookmarks.DEFAULT_INDEX,
      // `SYNC_REPARENT_REMOVED_FOLDER_CHILDREN` bumps the change counter for
      // the child and its new parent, without incrementing the bookmark
      // tracker's score.
      //
      // We intentionally don't check if the child is one we'll remove later,
      // so it's possible we'll bump the change counter of the closest living
      // ancestor when it's not needed. This avoids inconsistency if removal
      // is interrupted, since we don't run this operation in a transaction.
      source:
        lazy.PlacesUtils.bookmarks.SOURCES
          .SYNC_REPARENT_REMOVED_FOLDER_CHILDREN,
    });
  }

  // Delete the (now empty) parent
  try {
    await lazy.PlacesUtils.bookmarks.remove(bookmarkItem.guid, {
      preventRemovalOfNonEmptyFolders: true,
      // We don't want to bump the change counter for this deletion, because
      // a tombstone for the folder is already on the server.
      source: SOURCE_SYNC,
    });
  } catch (e) {
    // We failed, probably because someone added something to this folder
    // between when we got the children and now (or the database is corrupt,
    // or something else happened...) This is unlikely, but possible. To
    // avoid corruption in this case, we need to reupload the record to the
    // server.
    //
    // (Ideally this whole operation would be done in a transaction, and this
    // wouldn't be possible).
    lazy.BookmarkSyncLog.trace(
      `deleteSyncedFolder: Error removing parent ` +
        `${bookmarkItem.guid} after reparenting children`,
      e
    );
    return false;
  }

  return true;
}

// Removes a synced bookmark or empty folder from the database.
var deleteSyncedAtom = async function (bookmarkItem) {
  try {
    await lazy.PlacesUtils.bookmarks.remove(bookmarkItem.guid, {
      preventRemovalOfNonEmptyFolders: true,
      source: SOURCE_SYNC,
    });
  } catch (ex) {
    // Likely already removed.
    lazy.BookmarkSyncLog.trace(
      `deleteSyncedAtom: Error removing ` + bookmarkItem.guid,
      ex
    );
    return false;
  }

  return true;
};

/**
 * Updates the sync status on all "NEW" and "UNKNOWN" bookmarks to "NORMAL".
 *
 * We do this when pulling changes instead of in `pushChanges` to make sure
 * we write tombstones if a new item is deleted after an interrupted sync. (For
 * example, if a "NEW" record is uploaded or reconciled, then the app is closed
 * before Sync calls `pushChanges`).
 */
function markChangesAsSyncing(db, changeRecords) {
  let unsyncedGuids = [];
  for (let recordId in changeRecords) {
    if (changeRecords[recordId].tombstone) {
      continue;
    }
    if (
      changeRecords[recordId].status ==
      lazy.PlacesUtils.bookmarks.SYNC_STATUS.NORMAL
    ) {
      continue;
    }
    let guid = BookmarkSyncUtils.recordIdToGuid(recordId);
    unsyncedGuids.push(JSON.stringify(guid));
  }
  if (!unsyncedGuids.length) {
    return Promise.resolve();
  }
  return db.execute(
    `
    UPDATE moz_bookmarks
    SET syncStatus = :syncStatus
    WHERE guid IN (${unsyncedGuids.join(",")})`,
    { syncStatus: lazy.PlacesUtils.bookmarks.SYNC_STATUS.NORMAL }
  );
}

/**
 * Removes tombstones for successfully synced items.
 *
 * @return {Promise}
 */
var removeTombstones = function (db, guids) {
  if (!guids.length) {
    return Promise.resolve();
  }
  return db.execute(`
    DELETE FROM moz_bookmarks_deleted
    WHERE guid IN (${guids.map(guid => JSON.stringify(guid)).join(",")})`);
};

/**
 * Removes tombstones for successfully synced items where the specified GUID
 * exists in *both* the bookmarks and tombstones tables.
 *
 * @return {Promise}
 */
var removeUndeletedTombstones = function (db, guids) {
  if (!guids.length) {
    return Promise.resolve();
  }
  // sqlite can't join in a DELETE, so we use a subquery.
  return db.execute(`
    DELETE FROM moz_bookmarks_deleted
    WHERE guid IN (${guids.map(guid => JSON.stringify(guid)).join(",")})
    AND guid IN (SELECT guid from moz_bookmarks)`);
};

// Sets the history sync ID and clears the last sync time.
async function setHistorySyncId(db, newSyncId) {
  await lazy.PlacesUtils.metadata.setWithConnection(
    db,
    new Map([[HistorySyncUtils.SYNC_ID_META_KEY, newSyncId]])
  );

  await lazy.PlacesUtils.metadata.deleteWithConnection(
    db,
    HistorySyncUtils.LAST_SYNC_META_KEY
  );
}

// Sets the bookmarks sync ID and clears the last sync time.
async function setBookmarksSyncId(db, newSyncId) {
  await lazy.PlacesUtils.metadata.setWithConnection(
    db,
    new Map([[BookmarkSyncUtils.SYNC_ID_META_KEY, newSyncId]])
  );

  await lazy.PlacesUtils.metadata.deleteWithConnection(
    db,
    BookmarkSyncUtils.LAST_SYNC_META_KEY,
    BookmarkSyncUtils.WIPE_REMOTE_META_KEY
  );
}

// Bumps the change counter and sets the given sync status for all bookmarks,
// and drops stale tombstones.
async function resetAllSyncStatuses(db, syncStatus) {
  await db.execute(
    `
    UPDATE moz_bookmarks
    SET syncChangeCounter = 1,
        syncStatus = :syncStatus`,
    { syncStatus }
  );

  // Drop stale tombstones.
  await db.execute("DELETE FROM moz_bookmarks_deleted");
}

/**
 * Other clients might have new fields we don't quite understand yet,
 * so we add it to a "unknownFields" field to roundtrip back to the server
 * so other clients don't experience data loss
 * @param record: an object, usually from the server, and will iterate through the
 *  the keys and extract any fields that are unknown to this client
 * @param validFields: an array of keys we know are valid and should ignore
 * @returns {String} json object containing unknownfields, null if none found
 */
PlacesSyncUtils.extractUnknownFields = (record, validFields) => {
  let { unknownFields, hasUnknownFields } = Object.keys(record).reduce(
    ({ unknownFields, hasUnknownFields }, key) => {
      if (validFields.includes(key)) {
        return { unknownFields, hasUnknownFields };
      }
      unknownFields[key] = record[key];
      return { unknownFields, hasUnknownFields: true };
    },
    { unknownFields: {}, hasUnknownFields: false }
  );
  if (hasUnknownFields) {
    // For simplicity, we store the unknown fields as a string
    // since we never operate on it and just need it for roundtripping
    return JSON.stringify(unknownFields);
  }
  return null;
};
PK
!<p�s����"modules/PlacesTransactions.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Overview
 * --------
 * This modules serves as the transactions manager for Places (hereinafter PTM).
 * It implements all the elementary transactions for its UI commands: creating
 * items, editing their various properties, and so forth.
 *
 * Note that since the effect of invoking a Places command is not limited to the
 * window in which it was performed (e.g. a folder created in the Library may be
 * the parent of a bookmark created in some browser window), PTM is a singleton.
 * It's therefore unnecessary to initialize PTM in any way apart importing this
 * module.
 *
 * PTM shares most of its semantics with common command pattern implementations.
 * However, the asynchronous design of contemporary and future APIs, combined
 * with the commitment to serialize all UI operations, does make things a little
 * bit different.  For example, when |undo| is called in order to undo the top
 * undo entry, the caller cannot tell for sure what entry would it be, because
 * the execution of some transactions is either in process, or enqueued to be.
 *
 * Also note that unlike the nsITransactionManager, for example, this API is by
 * no means generic.  That is, it cannot be used to execute anything but the
 * elementary transactions implemented here (Please file a bug if you find
 * anything uncovered).  More-complex transactions (e.g. creating a folder and
 * moving a bookmark into it) may be implemented as a batch (see below).
 *
 * A note about GUIDs and item-ids
 * -------------------------------
 * There's an ongoing effort (see bug 1071511) to deprecate item-ids in Places
 * in favor of GUIDs.  Both because new APIs (e.g. Bookmarks.sys.mjs) expose them
 * to the minimum necessary, and because GUIDs play much better with
 * implementing |redo|, this API doesn't support item-ids at all, and only
 * accepts bookmark GUIDs, both for input (e.g. for setting the parent folder
 * for a new bookmark) and for output (when the GUID for such a bookmark is
 * propagated).
 *
 * Constructing transactions
 * -------------------------
 * At the bottom of this module you will find transactions for all Places UI
 * commands.  They are exposed as constructors set on the PlacesTransactions
 * object (e.g. PlacesTransactions.NewFolder).  The input for this constructors
 * is taken in the form of a single argument, a plain object consisting of the
 * properties for the transaction.  Input properties may be either required or
 * optional (for example, |keyword| is required for the EditKeyword transaction,
 * but optional for the NewBookmark transaction).
 *
 * To make things simple, a given input property has the same basic meaning and
 * valid values across all transactions which accept it in the input object.
 * Here is a list of all supported input properties along with their expected
 * values:
 *  - url: a URL object, an nsIURI object, or a href.
 *  - urls: an array of urls, as above.
 *  - tag - a string.
 *  - tags: an array of strings.
 *  - guid, parentGuid, newParentGuid: a valid Places GUID string.
 *  - guids: an array of valid Places GUID strings.
 *  - title: a string
 *  - index, newIndex: the position of an item in its containing folder,
 *    starting from 0.
 *    integer and PlacesUtils.bookmarks.DEFAULT_INDEX
 *
 * If a required property is missing in the input object (e.g. not specifying
 * parentGuid for NewBookmark), or if the value for any of the input properties
 * is invalid "on the surface" (e.g. a numeric value for GUID, or a string that
 * isn't 12-characters long), the transaction constructor throws right way.
 * More complex errors (e.g. passing a non-existent GUID for parentGuid) only
 * reveal once the transaction is executed.
 *
 * Executing Transactions (the |transact| method of transactions)
 * --------------------------------------------------------------
 * Once a transaction is created, you must call its |transact| method for it to
 * be executed and take effect.  |transact| is an asynchronous method that takes
 * no arguments, and returns a promise that resolves once the transaction is
 * executed.  Executing one of the transactions for creating items (NewBookmark,
 * NewFolder, NewSeparator) resolve to the new item's GUID.
 * There's no resolution value for other transactions.
 * If a transaction fails to execute, |transact| rejects and the transactions
 * history is not affected.
 *
 * |transact| throws if it's called more than once (successfully or not) on the
 * same transaction object.
 *
 * Batches
 * -------
 * Sometimes it is useful to "batch" or "merge" transactions.  For example,
 * something like "Bookmark All Tabs" may be implemented as one NewFolder
 * transaction followed by numerous NewBookmark transactions - all to be undone
 * or redone in a single undo or redo command.  Use `PlacesTransactions.batch()`
 * in such cases.
 * It takes an array of transactions which will be executed in the given order
 * and later be treated as a single entry in the transactions history.
 * If a transaction depends on the results from a previous one, it can be
 * replaced by a function that will be invoked with an array of results
 * accumulated from the previous transactions, indexed in the same positions.
 * The function should return the transaction to execute. For example:
 *
 *  let transactions = [
 *    // Returns the GUID of the new bookmark.
 *    PlacesTransactions.NewBookmark({
 *      parentGuid: "someGUID",
 *      title: "someTitle",
 *      url: "https://www.mozilla.org/""
 *    }),
 *    previousResults => PlacesTransactions.EditKeyword({
 *      // Get the GUID from the result of transactions[0].
 *      guid: previousResults[0],
 *      keyword: "someKeyword",
 *    },
 *  ];
 *
 * `PlacesTransactions.batch()` returns a promise resolved when the batch ends.
 * The resolution value is an array with all the transaction return values
 * indexed like the original transactions. So, for example, if a transaction
 * returns an array of GUIDs, to get a list of all the created GUIDs for all the
 * transactions one could use .flat() to flatten the array.
 *
 * If any transactions fails to execute, the batch continues (exceptions are
 * logged) and the result of that transactions will be set to undefined.
 * Only transactions that were executed successfully are added to the
 * transactions history as part of the batch.
 *
 * Serialization
 * -------------
 * All |PlacesTransaction| operations are serialized.  That is, even though the
 * implementation is asynchronous, the order in which PlacesTransactions methods
 * is called does guarantee the order in which they are to be invoked.
 *
 * The only exception to this rule is |transact| calls done during a batch (see
 * above).  |transact| calls are serialized with each other (and with undo, redo
 * and clearTransactionsHistory), but they  are, of course, not serialized with
 * batches.
 *
 * The transactions-history structure
 * ----------------------------------
 * The transactions-history is a two-dimensional stack of transactions: the
 * transactions are ordered in reverse to the order they were committed.
 * It's two-dimensional because PTM allows batching transactions together for
 * the purpose of undo or redo (see Batches above).
 *
 * The undoPosition property is set to the index of the top entry. If there is
 * no entry at that index, there is nothing to undo.
 * Entries prior to undoPosition, if any, are redo entries, the first one being
 * the top redo entry.
 *
 * [ [2nd redo txn, 1st redo txn],  <= 2nd redo entry
 *   [2nd redo txn, 1st redo txn],  <= 1st redo entry
 *   [1st undo txn, 2nd undo txn],  <= 1st undo entry
 *   [1st undo txn, 2nd undo txn]   <= 2nd undo entry ]
 * undoPostion: 2.
 *
 * Note that when a new entry is created, all redo entries are removed.
 */

const TRANSACTIONS_QUEUE_TIMEOUT_MS = 240000; // 4 Mins.

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

// Use a single queue bookmarks transaction manager. This pref exists as an
// emergency switch-off, it will go away in the future.
const prefs = {};
XPCOMUtils.defineLazyPreferenceGetter(
  prefs,
  "USE_SINGLE_QUEUE",
  "places.bookmarks.useSingleQueueTransactionManager",
  true
);

import { PlacesUtils } from "resource://gre/modules/PlacesUtils.sys.mjs";

function setTimeout(callback, ms) {
  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  timer.initWithCallback(callback, ms, timer.TYPE_ONE_SHOT);
}

const lazy = {};
ChromeUtils.defineLazyGetter(lazy, "logger", function () {
  return PlacesUtils.getLogger({ prefix: "Transactions" });
});

class TransactionsHistoryArray extends Array {
  constructor() {
    super();

    // The index of the first undo entry (if any) - See the documentation
    // at the top of this file.
    this._undoPosition = 0;
    // Outside of this module, the API of transactions is inaccessible, and so
    // are any internal properties.  To achieve that, transactions are proxified
    // in their constructors.  This maps the proxies to their respective raw
    // objects.
    this.proxifiedToRaw = new WeakMap();
  }

  get undoPosition() {
    return this._undoPosition;
  }

  // Handy shortcuts
  get topUndoEntry() {
    return this.undoPosition < this.length ? this[this.undoPosition] : null;
  }
  get topRedoEntry() {
    return this.undoPosition > 0 ? this[this.undoPosition - 1] : null;
  }

  /**
   * Proxify a transaction object for consumers.
   * @param rawTransaction
   *        the raw transaction object.
   * @return the proxified transaction object.
   * @see getRawTransaction for retrieving the raw transaction.
   */
  proxifyTransaction(rawTransaction) {
    let proxy = Object.freeze({
      transact(inBatch, batchIndex) {
        return TransactionsManager.transact(this, inBatch, batchIndex);
      },
      toString() {
        return rawTransaction.toString();
      },
    });
    this.proxifiedToRaw.set(proxy, rawTransaction);
    return proxy;
  }

  /**
   * Check if the given object is a the proxy object for some transaction.
   * @param aValue
   *        any JS value.
   * @return true if aValue is the proxy object for some transaction, false
   * otherwise.
   */
  isProxifiedTransactionObject(value) {
    return this.proxifiedToRaw.has(value);
  }

  /**
   * Get the raw transaction for the given proxy.
   * @param aProxy
   *        the proxy object
   * @return the transaction proxified by aProxy; |undefined| is returned if
   * aProxy is not a proxified transaction.
   */
  getRawTransaction(proxy) {
    return this.proxifiedToRaw.get(proxy);
  }

  /**
   * Add a transaction either as a new entry, if forced or if there are no undo
   * entries, or to the top undo entry.
   *
   * @param aProxifiedTransaction
   *        the proxified transaction object to be added to the transaction
   *        history.
   * @param [optional] aForceNewEntry
   *        Force a new entry for the transaction. Default: false.
   *        If false, an entry will we created only if there's no undo entry
   *        to extend.
   */
  add(proxifiedTransaction, forceNewEntry = false) {
    if (!this.isProxifiedTransactionObject(proxifiedTransaction)) {
      throw new Error("aProxifiedTransaction is not a proxified transaction");
    }

    if (!this.length || forceNewEntry) {
      this.clearRedoEntries();
      lazy.logger.debug(`Adding transaction: ${proxifiedTransaction}`);
      this.unshift([proxifiedTransaction]);
    } else {
      lazy.logger.debug(`Adding transaction: ${proxifiedTransaction}`);
      this[this.undoPosition].unshift(proxifiedTransaction);
    }
  }

  /**
   * Clear all undo entries.
   */
  clearUndoEntries() {
    lazy.logger.debug("Clearing undo entries");
    if (this.undoPosition < this.length) {
      this.splice(this.undoPosition);
    }
  }

  /**
   * Clear all redo entries.
   */
  clearRedoEntries() {
    lazy.logger.debug("Clearing redo entries");
    if (this.undoPosition > 0) {
      this.splice(0, this.undoPosition);
      this._undoPosition = 0;
    }
  }

  /**
   * Clear all entries.
   */
  clearAllEntries() {
    lazy.logger.debug("Clearing all entries");
    if (this.length) {
      this.splice(0);
      this._undoPosition = 0;
    }
  }
}

ChromeUtils.defineLazyGetter(
  lazy,
  "TransactionsHistory",
  () => new TransactionsHistoryArray()
);

export var PlacesTransactions = {
  /**
   * @see Batches in the module documentation.
   */
  batch(transactionsToBatch, batchName) {
    if (!Array.isArray(transactionsToBatch) || !transactionsToBatch.length) {
      throw new Error("Must pass a non-empty array");
    }
    if (
      transactionsToBatch.some(
        o =>
          !lazy.TransactionsHistory.isProxifiedTransactionObject(o) &&
          typeof o != "function"
      )
    ) {
      throw new Error("Must pass only transactions or functions");
    }
    lazy.logger.debug(
      `Batch ${batchName}: ${transactionsToBatch.length} transactions`
    );
    return TransactionsManager.batch(async function () {
      lazy.logger.debug(`Batch ${batchName}: executing transactions`);
      let accumulatedResults = [];
      for (let txn of transactionsToBatch) {
        try {
          if (typeof txn == "function") {
            txn = txn(accumulatedResults);
          }
          accumulatedResults.push(
            await txn.transact(true, accumulatedResults.length)
          );
        } catch (ex) {
          // TODO Bug 1865631: handle these errors better, currently we just
          // continue, that works for non-dependent transactions, but will
          // skip most of the work for functions depending on previous results.
          // Moreover in both cases we should notify the user about the problem.
          accumulatedResults.push(undefined);
          // Using console.error() here sometimes fails, due to unknown XPC
          // wrappers reasons, so just use our logger.
          lazy.logger.error(`Failed to execute batched transaction: ${ex}`);
        }
      }
      return accumulatedResults;
    });
  },

  /**
   * Asynchronously undo the transaction immediately after the current undo
   * position in the transactions history in the reverse order, if any, and
   * adjusts the undo position.
   *
   * @return {Promises).  The promise always resolves.
   * @note All undo manager operations are queued. This means that transactions
   * history may change by the time your request is fulfilled.
   */
  undo() {
    lazy.logger.debug("undo() was invoked");
    return TransactionsManager.undo();
  },

  /**
   * Asynchronously redo the transaction immediately before the current undo
   * position in the transactions history, if any, and adjusts the undo
   * position.
   *
   * @return {Promises).  The promise always resolves.
   * @note All undo manager operations are queued. This means that transactions
   * history may change by the time your request is fulfilled.
   */
  redo() {
    lazy.logger.debug("redo() was invoked");
    return TransactionsManager.redo();
  },

  /**
   * Asynchronously clear the undo, redo, or all entries from the transactions
   * history.
   *
   * @param [optional] undoEntries
   *        Whether or not to clear undo entries.  Default: true.
   * @param [optional] redoEntries
   *        Whether or not to clear undo entries.  Default: true.
   *
   * @return {Promises).  The promise always resolves.
   * @throws if both aUndoEntries and aRedoEntries are false.
   * @note All undo manager operations are queued. This means that transactions
   * history may change by the time your request is fulfilled.
   */
  clearTransactionsHistory(undoEntries = true, redoEntries = true) {
    lazy.logger.debug("clearTransactionsHistory() was invoked");
    return TransactionsManager.clearTransactionsHistory(
      undoEntries,
      redoEntries
    );
  },

  /**
   * The numbers of entries in the transactions history.
   */
  get length() {
    return lazy.TransactionsHistory.length;
  },

  /**
   * Get the transaction history entry at a given index.  Each entry consists
   * of one or more transaction objects.
   *
   * @param index
   *        the index of the entry to retrieve.
   * @return an array of transaction objects in their undo order (that is,
   * reversely to the order they were executed).
   * @throw if aIndex is invalid (< 0 or >= length).
   * @note the returned array is a clone of the history entry and is not
   * kept in sync with the original entry if it changes.
   */
  entry(index) {
    if (!Number.isInteger(index) || index < 0 || index >= this.length) {
      throw new Error("Invalid index");
    }

    return lazy.TransactionsHistory[index];
  },

  /**
   * The index of the top undo entry in the transactions history.
   * If there are no undo entries, it equals to |length|.
   * Entries past this point
   * Entries at and past this point are redo entries.
   */
  get undoPosition() {
    return lazy.TransactionsHistory.undoPosition;
  },

  /**
   * Shortcut for accessing the top undo entry in the transaction history.
   */
  get topUndoEntry() {
    return lazy.TransactionsHistory.topUndoEntry;
  },

  /**
   * Shortcut for accessing the top redo entry in the transaction history.
   */
  get topRedoEntry() {
    return lazy.TransactionsHistory.topRedoEntry;
  },
};

/**
 * Helper for serializing the calls to TransactionsManager methods. It allows
 * us to guarantee that the order in which TransactionsManager asynchronous
 * methods are called also enforces the order in which they're executed, and
 * that they are never executed in parallel.
 *
 * In other words: Enqueuer.enqueue(aFunc1); Enqueuer.enqueue(aFunc2) is roughly
 * the same as Task.spawn(aFunc1).then(Task.spawn(aFunc2)).
 */
function Enqueuer(name) {
  this._promise = Promise.resolve();
  this._name = name;
}
Enqueuer.prototype = {
  /**
   * Spawn a functions once all previous functions enqueued are done running,
   * and all promises passed to alsoWaitFor are no longer pending.
   *
   * @param   func
   *          a function returning a promise.
   * @return  a promise that resolves once aFunc is done running. The promise
   *          "mirrors" the promise returned by aFunc.
   */
  enqueue(func) {
    lazy.logger.debug(`${this._name} enqueing`);
    // If a transaction awaits on a never resolved promise, or is mistakenly
    // nested, it could hang the transactions queue forever.  Thus we timeout
    // the execution after a meaningful amount of time, to ensure in any case
    // we'll proceed after a while.
    let timeoutPromise = new Promise((resolve, reject) => {
      setTimeout(
        () =>
          reject(
            new Error(
              "PlacesTransaction timeout, most likely caused by unresolved pending work."
            )
          ),
        TRANSACTIONS_QUEUE_TIMEOUT_MS
      );
    });
    let promise = this._promise.then(() =>
      Promise.race([func(), timeoutPromise])
    );

    // Propagate exceptions to the caller, but dismiss them internally.
    this._promise = promise.catch(lazy.logger.error);
    return promise;
  },

  /**
   * Same as above, but for a promise returned by a function that already run.
   * This is useful, for example, for serializing transact calls with undo calls,
   * even though transact has its own Enqueuer.
   *
   * @param {Promise} otherPromise
   *        any promise.
   * @param {string} source
   *        source for logging purposes
   */
  alsoWaitFor(otherPromise, source) {
    lazy.logger.debug(`${this._name} alsoWaitFor: ${source}`);
    // We don't care if aPromise resolves or rejects, but just that is not
    // pending anymore.
    // If a transaction awaits on a never resolved promise, or is mistakenly
    // nested, it could hang the transactions queue forever.  Thus we timeout
    // the execution after a meaningful amount of time, to ensure in any case
    // we'll proceed after a while.
    let timeoutPromise = new Promise((resolve, reject) => {
      setTimeout(
        () =>
          reject(
            new Error(
              "PlacesTransaction timeout, most likely caused by unresolved pending work."
            )
          ),
        TRANSACTIONS_QUEUE_TIMEOUT_MS
      );
    });
    let promise = Promise.race([otherPromise, timeoutPromise]).catch(
      console.error
    );
    this._promise = Promise.all([this._promise, promise]);
  },

  /**
   * The promise for this queue.
   */
  get promise() {
    return this._promise;
  },
};

var TransactionsManager = {
  // See the documentation at the top of this file. |transact| calls are not
  // serialized with |batch| calls.
  _mainEnqueuer: new Enqueuer("MainEnqueuer"),
  _transactEnqueuer: new Enqueuer("TransactEnqueuer"),

  // Transactions object should never be recycled (that is, |execute| should
  // only be called once (or not at all) after they're constructed.
  // This keeps track of all transactions which were executed.
  _executedTransactions: new WeakSet(),

  /**
   * Execute a proxified transaction.
   *
   * @param {object} txnProxy The proxified transaction to execute.
   * @param {boolean} [inBatch] Whether the transaction is part of a batch.
   * @param {integer} [batchIndex] The index of the transaction in the batch array.
   * @returns {Promise} resolved to the transaction return value once complete.
   */
  transact(txnProxy, inBatch = false, batchIndex = undefined) {
    let rawTxn = lazy.TransactionsHistory.getRawTransaction(txnProxy);
    if (!rawTxn) {
      throw new Error("|transact| was called with an unexpected object");
    }

    if (this._executedTransactions.has(rawTxn)) {
      throw new Error("Transactions objects may not be recycled.");
    }

    lazy.logger.debug(`transact() enqueue: ${txnProxy}`);

    // Add it in advance so one doesn't accidentally do
    // sameTxn.transact(); sameTxn.transact();
    this._executedTransactions.add(rawTxn);

    let task = async () => {
      lazy.logger.debug(`transact execute(): ${txnProxy}`);
      // Don't try to catch exceptions. If execute fails, we better not add the
      // transaction to the undo stack.
      let retval = await rawTxn.execute();

      let forceNewEntry = !inBatch || batchIndex === 0;
      lazy.TransactionsHistory.add(txnProxy, forceNewEntry);

      this._updateCommandsOnActiveWindow();
      return retval;
    };

    if (prefs.USE_SINGLE_QUEUE) {
      return task();
    }

    let promise = this._transactEnqueuer.enqueue(task);
    this._mainEnqueuer.alsoWaitFor(promise, "transact");
    return promise;
  },

  batch(task) {
    return this._mainEnqueuer.enqueue(task);
  },

  /**
   * Undo the top undo entry, if any, and update the undo position accordingly.
   */
  undo() {
    let promise = this._mainEnqueuer.enqueue(async () => {
      lazy.logger.debug("Undo execute");
      let entry = lazy.TransactionsHistory.topUndoEntry;
      if (!entry) {
        return;
      }

      for (let txnProxy of entry) {
        try {
          await lazy.TransactionsHistory.getRawTransaction(txnProxy).undo();
        } catch (ex) {
          // If one transaction is broken, it's not safe to work with any other
          // undo entry.  Report the error and clear the undo history.
          console.error(ex, "Can't undo a transaction, clearing undo entries.");
          lazy.TransactionsHistory.clearUndoEntries();
          return;
        }
      }
      lazy.TransactionsHistory._undoPosition++;
      this._updateCommandsOnActiveWindow();
    });
    if (!prefs.USE_SINGLE_QUEUE) {
      this._transactEnqueuer.alsoWaitFor(promise, "undo");
    }
    return promise;
  },

  /**
   * Redo the top redo entry, if any, and update the undo position accordingly.
   */
  redo() {
    let promise = this._mainEnqueuer.enqueue(async () => {
      lazy.logger.debug("Redo execute");
      let entry = lazy.TransactionsHistory.topRedoEntry;
      if (!entry) {
        return;
      }

      for (let i = entry.length - 1; i >= 0; i--) {
        let transaction = lazy.TransactionsHistory.getRawTransaction(entry[i]);
        try {
          if (transaction.redo) {
            await transaction.redo();
          } else {
            await transaction.execute();
          }
        } catch (ex) {
          // If one transaction is broken, it's not safe to work with any other
          // redo entry. Report the error and clear the undo history.
          console.error(ex, "Can't redo a transaction, clearing redo entries.");
          lazy.TransactionsHistory.clearRedoEntries();
          return;
        }
      }
      lazy.TransactionsHistory._undoPosition--;
      this._updateCommandsOnActiveWindow();
    });
    if (!prefs.USE_SINGLE_QUEUE) {
      this._transactEnqueuer.alsoWaitFor(promise, "redo");
    }
    return promise;
  },

  clearTransactionsHistory(undoEntries, redoEntries) {
    let promise = this._mainEnqueuer.enqueue(function () {
      lazy.logger.debug(`ClearTransactionsHistory execute`);
      if (undoEntries && redoEntries) {
        lazy.TransactionsHistory.clearAllEntries();
      } else if (undoEntries) {
        lazy.TransactionsHistory.clearUndoEntries();
      } else if (redoEntries) {
        lazy.TransactionsHistory.clearRedoEntries();
      } else {
        throw new Error("either aUndoEntries or aRedoEntries should be true");
      }
    });

    if (!prefs.USE_SINGLE_QUEUE) {
      this._transactEnqueuer.alsoWaitFor(promise, "clearTransactionsHistory");
    }
    return promise;
  },

  // Updates commands in the undo group of the active window commands.
  // Inactive windows commands will be updated on focus.
  _updateCommandsOnActiveWindow() {
    // Updating "undo" will cause a group update including "redo".
    try {
      let win = Services.focus.activeWindow;
      if (win) {
        win.updateCommands("undo");
      }
    } catch (ex) {
      console.error(ex, "Couldn't update undo commands.");
    }
  },
};

/**
 * Internal helper for defining the standard transactions and their input.
 * It takes the required and optional properties, and generates the public
 * constructor (which takes the input in the form of a plain object) which,
 * when called, creates the argument-less "public" |execute| method by binding
 * the input properties to the function arguments (required properties first,
 * then the optional properties).
 *
 * If this seems confusing, look at the consumers.
 *
 * This magic serves two purposes:
 * (1) It completely hides the transactions' internals from the module
 *     consumers.
 * (2) It keeps each transaction implementation to what is about, bypassing
 *     all this bureaucracy while still validating input appropriately.
 */
function DefineTransaction(requiredProps = [], optionalProps = []) {
  for (let prop of [...requiredProps, ...optionalProps]) {
    if (!DefineTransaction.inputProps.has(prop)) {
      throw new Error("Property '" + prop + "' is not defined");
    }
  }

  let ctor = function (input) {
    // We want to support both syntaxes:
    // let t = new PlacesTransactions.NewBookmark(),
    // let t = PlacesTransactions.NewBookmark()
    if (this == PlacesTransactions) {
      return new ctor(input);
    }

    if (requiredProps.length || optionalProps.length) {
      // Bind the input properties to the arguments of execute.
      input = DefineTransaction.verifyInput(
        input,
        requiredProps,
        optionalProps
      );
      this.execute = this.execute.bind(this, input);
    }
    return lazy.TransactionsHistory.proxifyTransaction(this);
  };
  return ctor;
}

function simpleValidateFunc(checkFn) {
  return v => {
    if (!checkFn(v)) {
      throw new Error("Invalid value");
    }
    return v;
  };
}

DefineTransaction.strValidate = simpleValidateFunc(v => typeof v == "string");
DefineTransaction.strOrNullValidate = simpleValidateFunc(
  v => typeof v == "string" || v === null
);
DefineTransaction.indexValidate = simpleValidateFunc(
  v => Number.isInteger(v) && v >= PlacesUtils.bookmarks.DEFAULT_INDEX
);
DefineTransaction.guidValidate = simpleValidateFunc(v =>
  /^[a-zA-Z0-9\-_]{12}$/.test(v)
);

function isPrimitive(v) {
  return v === null || (typeof v != "object" && typeof v != "function");
}

function checkProperty(obj, prop, required, checkFn) {
  if (prop in obj) {
    return checkFn(obj[prop]);
  }

  return !required;
}

DefineTransaction.childObjectValidate = function (obj) {
  if (
    obj &&
    checkProperty(obj, "title", false, v => typeof v == "string") &&
    !("type" in obj && obj.type != PlacesUtils.bookmarks.TYPE_BOOKMARK)
  ) {
    obj.url = DefineTransaction.urlValidate(obj.url);
    let validKeys = ["title", "url"];
    if (Object.keys(obj).every(k => validKeys.includes(k))) {
      return obj;
    }
  }
  throw new Error("Invalid child object");
};

DefineTransaction.urlValidate = function (url) {
  if (url instanceof Ci.nsIURI) {
    return URL.fromURI(url);
  }
  return new URL(url);
};

DefineTransaction.inputProps = new Map();
DefineTransaction.defineInputProps = function (
  names,
  validateFn,
  defaultValue
) {
  for (let name of names) {
    this.inputProps.set(name, {
      validateValue(value) {
        if (value === undefined) {
          return defaultValue;
        }
        try {
          return validateFn(value);
        } catch (ex) {
          throw new Error(`Invalid value for input property ${name}: ${ex}`);
        }
      },

      validateInput(input, required) {
        if (required && !(name in input)) {
          throw new Error(`Required input property is missing: ${name}`);
        }
        return this.validateValue(input[name]);
      },

      isArrayProperty: false,
    });
  }
};

DefineTransaction.defineArrayInputProp = function (name, basePropertyName) {
  let baseProp = this.inputProps.get(basePropertyName);
  if (!baseProp) {
    throw new Error(`Unknown input property: ${basePropertyName}`);
  }

  this.inputProps.set(name, {
    validateValue(aValue) {
      if (aValue == undefined) {
        return [];
      }

      if (!Array.isArray(aValue)) {
        throw new Error(`${name} input property value must be an array`);
      }

      // We must create a new array in the local scope to avoid a memory leak due
      // to the array global object. We can't use Cu.cloneInto as that doesn't
      // handle the URIs. Slice & map also aren't good enough, so we start off
      // with a clean array and insert what we need into it.
      let newArray = [];
      for (let item of aValue) {
        newArray.push(baseProp.validateValue(item));
      }
      return newArray;
    },

    // We allow setting either the array property itself (e.g. urls), or a
    // single element of it (url, in that example), that is then transformed
    // into a single-element array.
    validateInput(input, required) {
      if (name in input) {
        // It's not allowed to set both though.
        if (basePropertyName in input) {
          throw new Error(`It is not allowed to set both ${name} and
                          ${basePropertyName} as  input properties`);
        }
        let array = this.validateValue(input[name]);
        if (required && !array.length) {
          throw new Error(`Empty array passed for required input property:
                           ${name}`);
        }
        return array;
      }
      // If the property is required and it's not set as is, check if the base
      // property is set.
      if (required && !(basePropertyName in input)) {
        throw new Error(`Required input property is missing: ${name}`);
      }

      if (basePropertyName in input) {
        return [baseProp.validateValue(input[basePropertyName])];
      }

      return [];
    },

    isArrayProperty: true,
  });
};

DefineTransaction.validatePropertyValue = function (prop, input, required) {
  return this.inputProps.get(prop).validateInput(input, required);
};

DefineTransaction.getInputObjectForSingleValue = function (
  input,
  requiredProps,
  optionalProps
) {
  // The following input forms may be deduced from a single value:
  // * a single required property with or without optional properties (the given
  //   value is set to the required property).
  // * a single optional property with no required properties.
  if (
    requiredProps.length > 1 ||
    (!requiredProps.length && optionalProps.length > 1)
  ) {
    throw new Error("Transaction input isn't an object");
  }

  let propName =
    requiredProps.length == 1 ? requiredProps[0] : optionalProps[0];
  let propValue =
    this.inputProps.get(propName).isArrayProperty && !Array.isArray(input)
      ? [input]
      : input;
  return { [propName]: propValue };
};

DefineTransaction.verifyInput = function (
  input,
  requiredProps = [],
  optionalProps = []
) {
  if (!requiredProps.length && !optionalProps.length) {
    return {};
  }

  // If there's just a single required/optional property, we allow passing it
  // as is, so, for example, one could do PlacesTransactions.Remove(myGuid)
  // rather than PlacesTransactions.Remove({ guid: myGuid}).
  // This shortcut isn't supported for "complex" properties, like objects (note
  // there is no use case for this at the moment anyway).
  let isSinglePropertyInput =
    isPrimitive(input) ||
    Array.isArray(input) ||
    input instanceof Ci.nsISupports;
  if (isSinglePropertyInput) {
    input = this.getInputObjectForSingleValue(
      input,
      requiredProps,
      optionalProps
    );
  }

  let fixedInput = {};
  for (let prop of requiredProps) {
    fixedInput[prop] = this.validatePropertyValue(prop, input, true);
  }
  for (let prop of optionalProps) {
    fixedInput[prop] = this.validatePropertyValue(prop, input, false);
  }

  return fixedInput;
};

// Update the documentation at the top of this module if you add or
// remove properties.
DefineTransaction.defineInputProps(
  ["url"],
  DefineTransaction.urlValidate,
  null
);
DefineTransaction.defineInputProps(
  ["guid", "parentGuid", "newParentGuid"],
  DefineTransaction.guidValidate
);
DefineTransaction.defineInputProps(
  ["title", "postData"],
  DefineTransaction.strOrNullValidate,
  null
);
DefineTransaction.defineInputProps(
  ["keyword", "oldKeyword", "oldTag", "tag"],
  DefineTransaction.strValidate,
  ""
);
DefineTransaction.defineInputProps(
  ["index", "newIndex"],
  DefineTransaction.indexValidate,
  PlacesUtils.bookmarks.DEFAULT_INDEX
);
DefineTransaction.defineInputProps(
  ["child"],
  DefineTransaction.childObjectValidate
);
DefineTransaction.defineArrayInputProp("guids", "guid");
DefineTransaction.defineArrayInputProp("urls", "url");
DefineTransaction.defineArrayInputProp("tags", "tag");
DefineTransaction.defineArrayInputProp("children", "child");

/**
 * Creates items (all types) from a bookmarks tree representation, as defined
 * in PlacesUtils.promiseBookmarksTree.
 *
 * @param tree
 *        the bookmarks tree object.  You may pass either a bookmarks tree
 *        returned by promiseBookmarksTree, or a manually defined one.
 * @param [optional] restoring (default: false)
 *        Whether or not the items are restored.  Only in restore mode, are
 *        the guid, dateAdded and lastModified properties honored.
 * @note the id, root and charset properties of items in aBookmarksTree are
 *       always ignored.  The index property is ignored for all items but the
 *       root one.
 * @return {Promise}
 * @resolves to the guid of the new item.
 */
// TODO: Replace most of this with insertTree.
function createItemsFromBookmarksTree(tree, restoring = false) {
  async function createItem(
    item,
    parentGuid,
    index = PlacesUtils.bookmarks.DEFAULT_INDEX
  ) {
    let guid;
    let info = { parentGuid, index };
    if (restoring) {
      info.guid = item.guid;
      info.dateAdded = PlacesUtils.toDate(item.dateAdded);
      info.lastModified = PlacesUtils.toDate(item.lastModified);
    }
    let shouldResetLastModified = false;
    switch (item.type) {
      case PlacesUtils.TYPE_X_MOZ_PLACE: {
        info.url = item.uri;
        if (typeof item.title == "string") {
          info.title = item.title;
        }

        guid = (await PlacesUtils.bookmarks.insert(info)).guid;

        if ("keyword" in item) {
          let { uri: url, keyword, postData } = item;
          await PlacesUtils.keywords.insert({ url, keyword, postData });
        }
        if ("tags" in item) {
          PlacesUtils.tagging.tagURI(
            Services.io.newURI(item.uri),
            item.tags.split(",")
          );
        }
        break;
      }
      case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER: {
        info.type = PlacesUtils.bookmarks.TYPE_FOLDER;
        if (typeof item.title == "string") {
          info.title = item.title;
        }
        guid = (await PlacesUtils.bookmarks.insert(info)).guid;
        if ("children" in item) {
          for (let child of item.children) {
            await createItem(child, guid);
          }
        }
        if (restoring) {
          shouldResetLastModified = true;
        }
        break;
      }
      case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR: {
        info.type = PlacesUtils.bookmarks.TYPE_SEPARATOR;
        guid = (await PlacesUtils.bookmarks.insert(info)).guid;
        break;
      }
    }

    if (shouldResetLastModified) {
      let lastModified = PlacesUtils.toDate(item.lastModified);
      await PlacesUtils.bookmarks.update({ guid, lastModified });
    }

    return guid;
  }
  return createItem(tree, tree.parentGuid, tree.index);
}

/** ***************************************************************************
 * The Standard Places Transactions.
 *
 * See the documentation at the top of this file. The valid values for input
 * are also documented there.
 *****************************************************************************/

var PT = PlacesTransactions;

/**
 * Transaction for creating a bookmark.
 *
 * Required Input Properties: url, parentGuid.
 * Optional Input Properties: index, title, keyword, tags.
 *
 * When this transaction is executed, it's resolved to the new bookmark's GUID.
 */
PT.NewBookmark = DefineTransaction(
  ["parentGuid", "url"],
  ["index", "title", "tags"]
);
PT.NewBookmark.prototype = Object.seal({
  async execute({ parentGuid, url, index, title, tags }) {
    let info = { parentGuid, index, url, title };
    // Filter tags to exclude already existing ones.
    if (tags.length) {
      let currentTags = PlacesUtils.tagging.getTagsForURI(url.URI);
      tags = tags.filter(t => !currentTags.includes(t));
    }

    async function createItem() {
      info = await PlacesUtils.bookmarks.insert(info);
      if (tags.length) {
        PlacesUtils.tagging.tagURI(url.URI, tags);
      }
    }

    await createItem();

    this.undo = async function () {
      // Pick up the removed info so we have the accurate last-modified value.
      await PlacesUtils.bookmarks.remove(info);
      if (tags.length) {
        PlacesUtils.tagging.untagURI(url.URI, tags);
      }
    };
    this.redo = async function () {
      await createItem();
    };
    return info.guid;
  },
  toString() {
    return "NewBookmark";
  },
});

/**
 * Transaction for creating a folder.
 *
 * Required Input Properties: title, parentGuid.
 * Optional Input Properties: index, children
 *
 * When this transaction is executed, it's resolved to the new folder's GUID.
 */
PT.NewFolder = DefineTransaction(
  ["parentGuid", "title"],
  ["index", "children"]
);
PT.NewFolder.prototype = Object.seal({
  async execute({ parentGuid, title, index, children }) {
    let folderGuid;
    let info = {
      children: [
        {
          // Ensure to specify a guid to be restored on redo.
          guid: PlacesUtils.history.makeGuid(),
          title,
          type: PlacesUtils.bookmarks.TYPE_FOLDER,
        },
      ],
      // insertTree uses guid as the parent for where it is being inserted
      // into.
      guid: parentGuid,
    };

    if (children && children.length) {
      // Ensure to specify a guid for each child to be restored on redo.
      info.children[0].children = children.map(c => {
        c.guid = PlacesUtils.history.makeGuid();
        return c;
      });
    }

    async function createItem() {
      // Note, insertTree returns an array, rather than the folder/child structure.
      // For simplicity, we only get the new folder id here. This means that
      // an undo then redo won't retain exactly the same information for all
      // the child bookmarks, but we believe that isn't important at the moment.
      let bmInfo = await PlacesUtils.bookmarks.insertTree(info);
      // insertTree returns an array, but we only need to deal with the folder guid.
      folderGuid = bmInfo[0].guid;

      // Bug 1388097: insertTree doesn't handle inserting at a specific index for the folder,
      // therefore we update the bookmark manually afterwards.
      if (index != PlacesUtils.bookmarks.DEFAULT_INDEX) {
        bmInfo[0].index = index;
        bmInfo = await PlacesUtils.bookmarks.update(bmInfo[0]);
      }
    }
    await createItem();

    this.undo = async function () {
      await PlacesUtils.bookmarks.remove(folderGuid);
    };
    this.redo = async function () {
      await createItem();
    };
    return folderGuid;
  },
  toString() {
    return "NewFolder";
  },
});

/**
 * Transaction for creating a separator.
 *
 * Required Input Properties: parentGuid.
 * Optional Input Properties: index.
 *
 * When this transaction is executed, it's resolved to the new separator's
 * GUID.
 */
PT.NewSeparator = DefineTransaction(["parentGuid"], ["index"]);
PT.NewSeparator.prototype = Object.seal({
  async execute(info) {
    info.type = PlacesUtils.bookmarks.TYPE_SEPARATOR;
    info = await PlacesUtils.bookmarks.insert(info);
    this.undo = PlacesUtils.bookmarks.remove.bind(PlacesUtils.bookmarks, info);
    this.redo = PlacesUtils.bookmarks.insert.bind(PlacesUtils.bookmarks, info);
    return info.guid;
  },
  toString() {
    return "NewSeparator";
  },
});

/**
 * Transaction for moving an item.
 *
 * Required Input Properties: guid, newParentGuid.
 * Optional Input Properties  newIndex.
 */
PT.Move = DefineTransaction(["guids", "newParentGuid"], ["newIndex"]);
PT.Move.prototype = Object.seal({
  async execute({ guids, newParentGuid, newIndex }) {
    let originalInfos = [];
    let index = newIndex;

    for (let guid of guids) {
      // We need to save the original data for undo.
      let originalInfo = await PlacesUtils.bookmarks.fetch(guid);
      if (!originalInfo) {
        throw new Error("Cannot move a non-existent item");
      }

      originalInfos.push(originalInfo);
    }

    await PlacesUtils.bookmarks.moveToFolder(guids, newParentGuid, index);

    this.undo = async function () {
      // Undo has the potential for moving multiple bookmarks to multiple different
      // folders and positions, which is very complicated to manage. Therefore we do
      // individual moves one at a time and hopefully everything is put back approximately
      // where it should be.
      for (let info of originalInfos) {
        await PlacesUtils.bookmarks.update(info);
      }
    };
    this.redo = PlacesUtils.bookmarks.moveToFolder.bind(
      PlacesUtils.bookmarks,
      guids,
      newParentGuid,
      index
    );
    return guids;
  },
  toString() {
    return "Move";
  },
});

/**
 * Transaction for setting the title for an item.
 *
 * Required Input Properties: guid, title.
 */
PT.EditTitle = DefineTransaction(["guid", "title"]);
PT.EditTitle.prototype = Object.seal({
  async execute({ guid, title }) {
    let originalInfo = await PlacesUtils.bookmarks.fetch(guid);
    if (!originalInfo) {
      throw new Error("cannot update a non-existent item");
    }

    let updateInfo = { guid, title };
    updateInfo = await PlacesUtils.bookmarks.update(updateInfo);

    this.undo = PlacesUtils.bookmarks.update.bind(
      PlacesUtils.bookmarks,
      originalInfo
    );
    this.redo = PlacesUtils.bookmarks.update.bind(
      PlacesUtils.bookmarks,
      updateInfo
    );
  },
  toString() {
    return "EditTitle";
  },
});

/**
 * Transaction for setting the URI for an item.
 *
 * Required Input Properties: guid, url.
 */
PT.EditUrl = DefineTransaction(["guid", "url"]);
PT.EditUrl.prototype = Object.seal({
  async execute({ guid, url }) {
    let originalInfo = await PlacesUtils.bookmarks.fetch(guid);
    if (!originalInfo) {
      throw new Error("cannot update a non-existent item");
    }
    if (originalInfo.type != PlacesUtils.bookmarks.TYPE_BOOKMARK) {
      throw new Error("Cannot edit url for non-bookmark items");
    }

    let uri = url.URI;
    let originalURI = originalInfo.url.URI;
    let originalTags = PlacesUtils.tagging.getTagsForURI(originalURI);
    let updatedInfo = { guid, url };
    let newURIAdditionalTags = null;

    async function updateItem() {
      updatedInfo = await PlacesUtils.bookmarks.update(updatedInfo);
      // Move tags from the original URI to the new URI.
      if (originalTags.length) {
        // Untag the original URI only if this was the only bookmark.
        if (!(await PlacesUtils.bookmarks.fetch({ url: originalInfo.url }))) {
          PlacesUtils.tagging.untagURI(originalURI, originalTags);
        }
        let currentNewURITags = PlacesUtils.tagging.getTagsForURI(uri);
        newURIAdditionalTags = originalTags.filter(
          t => !currentNewURITags.includes(t)
        );
        if (newURIAdditionalTags && newURIAdditionalTags.length) {
          PlacesUtils.tagging.tagURI(uri, newURIAdditionalTags);
        }
      }
    }
    await updateItem();

    this.undo = async function () {
      await PlacesUtils.bookmarks.update(originalInfo);
      // Move tags from new URI to original URI.
      if (originalTags.length) {
        // Only untag the new URI if this is the only bookmark.
        if (
          newURIAdditionalTags &&
          !!newURIAdditionalTags.length &&
          !(await PlacesUtils.bookmarks.fetch({ url }))
        ) {
          PlacesUtils.tagging.untagURI(uri, newURIAdditionalTags);
        }
        PlacesUtils.tagging.tagURI(originalURI, originalTags);
      }
    };

    this.redo = async function () {
      updatedInfo = await updateItem();
    };
  },
  toString() {
    return "EditUrl";
  },
});

/**
 * Transaction for setting the keyword for a bookmark.
 *
 * Required Input Properties: guid, keyword.
 * Optional Input Properties: postData, oldKeyword.
 */
PT.EditKeyword = DefineTransaction(
  ["guid", "keyword"],
  ["postData", "oldKeyword"]
);
PT.EditKeyword.prototype = Object.seal({
  async execute({ guid, keyword, postData, oldKeyword }) {
    let url;
    let oldKeywordEntry;
    if (oldKeyword) {
      oldKeywordEntry = await PlacesUtils.keywords.fetch(oldKeyword);
      url = oldKeywordEntry.url;
      await PlacesUtils.keywords.remove(oldKeyword);
    }

    if (keyword) {
      if (!url) {
        url = (await PlacesUtils.bookmarks.fetch(guid)).url;
      }
      await PlacesUtils.keywords.insert({
        url,
        keyword,
        postData: postData || (oldKeywordEntry ? oldKeywordEntry.postData : ""),
      });
    }

    this.undo = async function () {
      if (keyword) {
        await PlacesUtils.keywords.remove(keyword);
      }
      if (oldKeywordEntry) {
        await PlacesUtils.keywords.insert(oldKeywordEntry);
      }
    };
  },
  toString() {
    return "EditKeyword";
  },
});

/**
 * Transaction for sorting a folder by name.
 *
 * Required Input Properties: guid.
 */
PT.SortByName = DefineTransaction(["guid"]);
PT.SortByName.prototype = {
  async execute({ guid }) {
    let sortingMethod = (node_a, node_b) => {
      if (
        PlacesUtils.nodeIsContainer(node_a) &&
        !PlacesUtils.nodeIsContainer(node_b)
      ) {
        return -1;
      }
      if (
        !PlacesUtils.nodeIsContainer(node_a) &&
        PlacesUtils.nodeIsContainer(node_b)
      ) {
        return 1;
      }
      return node_a.title.localeCompare(node_b.title);
    };
    let oldOrderGuids = [];
    let newOrderGuids = [];
    let preSepNodes = [];

    // This is not great, since it does main-thread IO.
    // PromiseBookmarksTree can't be used, since it' won't stop at the first level'.
    let root = PlacesUtils.getFolderContents(guid, false, false).root;
    for (let i = 0, count = root.childCount; i < count; ++i) {
      let node = root.getChild(i);
      oldOrderGuids.push(node.bookmarkGuid);
      if (PlacesUtils.nodeIsSeparator(node)) {
        if (preSepNodes.length) {
          preSepNodes.sort(sortingMethod);
          newOrderGuids.push(...preSepNodes.map(n => n.bookmarkGuid));
          preSepNodes = [];
        }
        newOrderGuids.push(node.bookmarkGuid);
      } else {
        preSepNodes.push(node);
      }
    }
    root.containerOpen = false;
    if (preSepNodes.length) {
      preSepNodes.sort(sortingMethod);
      newOrderGuids.push(...preSepNodes.map(n => n.bookmarkGuid));
    }
    await PlacesUtils.bookmarks.reorder(guid, newOrderGuids);

    this.undo = async function () {
      await PlacesUtils.bookmarks.reorder(guid, oldOrderGuids);
    };
    this.redo = async function () {
      await PlacesUtils.bookmarks.reorder(guid, newOrderGuids);
    };
  },
  toString() {
    return "SortByName";
  },
};

/**
 * Transaction for removing an item (any type).
 *
 * Required Input Properties: guids.
 */
PT.Remove = DefineTransaction(["guids"]);
PT.Remove.prototype = {
  async execute({ guids }) {
    let removedItems = [];

    for (let guid of guids) {
      try {
        // Although we don't strictly need to get this information for the remove,
        // we do need it for the possibility of undo().
        removedItems.push(await PlacesUtils.promiseBookmarksTree(guid));
      } catch (ex) {
        if (!ex.becauseInvalidURL) {
          throw new Error(`Failed to get info for the guid: ${guid}: ${ex}`);
        }
        removedItems.push({ guid });
      }
    }

    let removeThem = async function () {
      if (removedItems.length) {
        // We have to pass just the guids as although remove() accepts full
        // info items, promiseBookmarksTree returns dateAdded and lastModified
        // as PRTime rather than date types.
        await PlacesUtils.bookmarks.remove(
          removedItems.map(info => ({ guid: info.guid }))
        );
      }
    };
    await removeThem();

    this.undo = async function () {
      for (let info of removedItems) {
        try {
          await createItemsFromBookmarksTree(info, true);
        } catch (ex) {
          console.error(`Unable to undo removal of ${info.guid}`);
        }
      }
    };
    this.redo = removeThem;
  },
  toString() {
    return "Remove";
  },
};

/**
 * Transaction for tagging urls.
 *
 * Required Input Properties: urls, tags.
 */
PT.Tag = DefineTransaction(["urls", "tags"]);
PT.Tag.prototype = {
  async execute({ urls, tags }) {
    let onUndo = [],
      onRedo = [];
    for (let url of urls) {
      if (!(await PlacesUtils.bookmarks.fetch({ url }))) {
        // Tagging is only allowed for bookmarked URIs (but see 424160).
        let createTxn = lazy.TransactionsHistory.getRawTransaction(
          PT.NewBookmark({
            url,
            tags,
            parentGuid: PlacesUtils.bookmarks.unfiledGuid,
          })
        );
        await createTxn.execute();
        onUndo.unshift(createTxn.undo.bind(createTxn));
        onRedo.push(createTxn.redo.bind(createTxn));
      } else {
        let uri = url.URI;
        let currentTags = PlacesUtils.tagging.getTagsForURI(uri);
        let newTags = tags.filter(t => !currentTags.includes(t));
        if (newTags.length) {
          PlacesUtils.tagging.tagURI(uri, newTags);
          onUndo.unshift(() => {
            PlacesUtils.tagging.untagURI(uri, newTags);
          });
          onRedo.push(() => {
            PlacesUtils.tagging.tagURI(uri, newTags);
          });
        }
      }
    }
    this.undo = async function () {
      for (let f of onUndo) {
        await f();
      }
    };
    this.redo = async function () {
      for (let f of onRedo) {
        await f();
      }
    };
  },
  toString() {
    return "Tag";
  },
};

/**
 * Transaction for removing tags from a URI.
 *
 * Required Input Properties: urls.
 * Optional Input Properties: tags.
 *
 * If |tags| is not set, all tags set for |url| are removed.
 */
PT.Untag = DefineTransaction(["urls"], ["tags"]);
PT.Untag.prototype = {
  execute({ urls, tags }) {
    let onUndo = [],
      onRedo = [];
    for (let url of urls) {
      let uri = url.URI;
      let tagsToRemove;
      let tagsSet = PlacesUtils.tagging.getTagsForURI(uri);
      if (tags.length) {
        tagsToRemove = tags.filter(t => tagsSet.includes(t));
      } else {
        tagsToRemove = tagsSet;
      }
      if (tagsToRemove.length) {
        PlacesUtils.tagging.untagURI(uri, tagsToRemove);
      }
      onUndo.unshift(() => {
        if (tagsToRemove.length) {
          PlacesUtils.tagging.tagURI(uri, tagsToRemove);
        }
      });
      onRedo.push(() => {
        if (tagsToRemove.length) {
          PlacesUtils.tagging.untagURI(uri, tagsToRemove);
        }
      });
    }
    this.undo = async function () {
      for (let f of onUndo) {
        await f();
      }
    };
    this.redo = async function () {
      for (let f of onRedo) {
        await f();
      }
    };
  },
  toString() {
    return "Untag";
  },
};

/**
 * Transaction for renaming a tag.
 *
 * Required Input Properties: oldTag, tag.
 */
PT.RenameTag = DefineTransaction(["oldTag", "tag"]);
PT.RenameTag.prototype = {
  async execute({ oldTag, tag }) {
    // For now this is implemented by untagging and tagging all the bookmarks.
    // We should create a specialized bookmarking API to just rename the tag.
    let onUndo = [],
      onRedo = [];
    let urls = new Set();
    await PlacesUtils.bookmarks.fetch({ tags: [oldTag] }, b => urls.add(b.url));
    if (urls.size > 0) {
      urls = Array.from(urls);
      let tagTxn = lazy.TransactionsHistory.getRawTransaction(
        PT.Tag({ urls, tags: [tag] })
      );
      await tagTxn.execute();
      onUndo.unshift(tagTxn.undo.bind(tagTxn));
      onRedo.push(tagTxn.redo.bind(tagTxn));
      let untagTxn = lazy.TransactionsHistory.getRawTransaction(
        PT.Untag({ urls, tags: [oldTag] })
      );
      await untagTxn.execute();
      onUndo.unshift(untagTxn.undo.bind(untagTxn));
      onRedo.push(untagTxn.redo.bind(untagTxn));

      // Update all the place: queries that refer to this tag.
      let db = await PlacesUtils.promiseDBConnection();
      let rows = await db.executeCached(
        `
        SELECT h.url, b.guid, b.title
        FROM moz_places h
        JOIN moz_bookmarks b ON b.fk = h.id
        WHERE url_hash BETWEEN hash("place", "prefix_lo")
                           AND hash("place", "prefix_hi")
          AND url LIKE :tagQuery
      `,
        { tagQuery: "%tag=%" }
      );
      for (let row of rows) {
        let url = row.getResultByName("url");
        try {
          url = new URL(url);
          let urlParams = new URLSearchParams(url.pathname);
          let tags = urlParams.getAll("tag");
          if (!tags.includes(oldTag)) {
            continue;
          }
          if (tags.length > 1) {
            // URLSearchParams cannot set more than 1 same-named param.
            urlParams.delete("tag");
            urlParams.set("tag", tag);
            url = new URL(
              url.protocol +
                urlParams +
                "&tag=" +
                tags.filter(t => t != oldTag).join("&tag=")
            );
          } else {
            urlParams.set("tag", tag);
            url = new URL(url.protocol + urlParams);
          }
        } catch (ex) {
          console.error(
            "Invalid bookmark url: " + row.getResultByName("url") + ": " + ex
          );
          continue;
        }
        let guid = row.getResultByName("guid");
        let title = row.getResultByName("title");

        let editUrlTxn = lazy.TransactionsHistory.getRawTransaction(
          PT.EditUrl({ guid, url })
        );
        await editUrlTxn.execute();
        onUndo.unshift(editUrlTxn.undo.bind(editUrlTxn));
        onRedo.push(editUrlTxn.redo.bind(editUrlTxn));
        if (title == oldTag) {
          let editTitleTxn = lazy.TransactionsHistory.getRawTransaction(
            PT.EditTitle({ guid, title: tag })
          );
          await editTitleTxn.execute();
          onUndo.unshift(editTitleTxn.undo.bind(editTitleTxn));
          onRedo.push(editTitleTxn.redo.bind(editTitleTxn));
        }
      }
    }
    this.undo = async function () {
      for (let f of onUndo) {
        await f();
      }
    };
    this.redo = async function () {
      for (let f of onRedo) {
        await f();
      }
    };
  },
  toString() {
    return "RenameTag";
  },
};

/**
 * Transaction for copying an item.
 *
 * Required Input Properties: guid, newParentGuid
 * Optional Input Properties: newIndex.
 */
PT.Copy = DefineTransaction(["guid", "newParentGuid"], ["newIndex"]);
PT.Copy.prototype = {
  async execute({ guid, newParentGuid, newIndex }) {
    let creationInfo = null;
    try {
      creationInfo = await PlacesUtils.promiseBookmarksTree(guid);
    } catch (ex) {
      throw new Error(
        "Failed to get info for the specified item (guid: " +
          guid +
          "). Ex: " +
          ex
      );
    }
    creationInfo.parentGuid = newParentGuid;
    creationInfo.index = newIndex;

    let newItemGuid = await createItemsFromBookmarksTree(creationInfo, false);
    let newItemInfo = null;
    this.undo = async function () {
      if (!newItemInfo) {
        newItemInfo = await PlacesUtils.promiseBookmarksTree(newItemGuid);
      }
      await PlacesUtils.bookmarks.remove(newItemGuid);
    };
    this.redo = async function () {
      await createItemsFromBookmarksTree(newItemInfo, true);
    };

    return newItemGuid;
  },
  toString() {
    return "Copy";
  },
};
PK
!<r��x�xmodules/PlacesUtils.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Bookmarks: "resource://gre/modules/Bookmarks.sys.mjs",
  History: "resource://gre/modules/History.sys.mjs",
  PlacesSyncUtils: "resource://gre/modules/PlacesSyncUtils.sys.mjs",
  Sqlite: "resource://gre/modules/Sqlite.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "MOZ_ACTION_REGEX", () => {
  return /^moz-action:([^,]+),(.*)$/;
});

ChromeUtils.defineLazyGetter(lazy, "CryptoHash", () => {
  return Components.Constructor(
    "@mozilla.org/security/hash;1",
    "nsICryptoHash",
    "initWithString"
  );
});

// On Mac OSX, the transferable system converts "\r\n" to "\n\n", where
// we really just want "\n". On other platforms, the transferable system
// converts "\r\n" to "\n".
const NEWLINE = AppConstants.platform == "macosx" ? "\n" : "\r\n";

// Timers resolution is not always good, it can have a 16ms precision on Win.
const TIMERS_RESOLUTION_SKEW_MS = 16;

function QI_node(aNode, aIID) {
  try {
    return aNode.QueryInterface(aIID);
  } catch (ex) {}
  return null;
}
function asContainer(aNode) {
  return QI_node(aNode, Ci.nsINavHistoryContainerResultNode);
}
function asQuery(aNode) {
  return QI_node(aNode, Ci.nsINavHistoryQueryResultNode);
}

/**
 * Sends a keyword change notification.
 *
 * @param url
 *        the url to notify about.
 * @param keyword
 *        The keyword to notify, or empty string if a keyword was removed.
 */
async function notifyKeywordChange(url, keyword, source) {
  // Notify bookmarks about the removal.
  let bookmarks = [];
  await PlacesUtils.bookmarks.fetch({ url }, b => bookmarks.push(b), {
    includeItemIds: true,
  });

  const notifications = bookmarks.map(
    bookmark =>
      new PlacesBookmarkKeyword({
        id: bookmark.itemId,
        itemType: bookmark.type,
        url,
        guid: bookmark.guid,
        parentGuid: bookmark.parentGuid,
        keyword,
        lastModified: bookmark.lastModified,
        source,
        isTagging: false,
      })
  );
  if (notifications.length) {
    PlacesObservers.notifyListeners(notifications);
  }
}

/**
 * Serializes the given node in JSON format.
 *
 * @param aNode
 *        An nsINavHistoryResultNode
 */
function serializeNode(aNode) {
  let data = {};

  data.title = aNode.title;
  // The id is no longer used for copying within the same instance/session of
  // Firefox as of at least 61. However, we keep the id for now to maintain
  // backwards compat of drag and drop with older Firefox versions.
  data.id = aNode.itemId;
  data.itemGuid = aNode.bookmarkGuid;
  // Add an instanceId so we can tell which instance of an FF session the data
  // is coming from.
  data.instanceId = PlacesUtils.instanceId;

  let guid = aNode.bookmarkGuid;

  // Some nodes, e.g. the unfiled/menu/toolbar ones can have a virtual guid, so
  // we ignore any that are a folder shortcut. These will be handled below.
  if (
    guid &&
    !PlacesUtils.bookmarks.isVirtualRootItem(guid) &&
    !PlacesUtils.isVirtualLeftPaneItem(guid)
  ) {
    if (aNode.parent) {
      data.parent = aNode.parent.itemId;
      data.parentGuid = aNode.parent.bookmarkGuid;
    }

    data.dateAdded = aNode.dateAdded;
    data.lastModified = aNode.lastModified;
  }

  if (PlacesUtils.nodeIsURI(aNode)) {
    // Check for url validity.
    new URL(aNode.uri);
    data.type = PlacesUtils.TYPE_X_MOZ_PLACE;
    data.uri = aNode.uri;
    if (aNode.tags) {
      data.tags = aNode.tags;
    }
  } else if (PlacesUtils.nodeIsFolderOrShortcut(aNode)) {
    if (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) {
      data.type = PlacesUtils.TYPE_X_MOZ_PLACE;
      data.uri = aNode.uri;
      data.concreteGuid = PlacesUtils.getConcreteItemGuid(aNode);
    } else {
      data.type = PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER;
    }
  } else if (PlacesUtils.nodeIsQuery(aNode)) {
    data.type = PlacesUtils.TYPE_X_MOZ_PLACE;
    data.uri = aNode.uri;
  } else if (PlacesUtils.nodeIsSeparator(aNode)) {
    data.type = PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR;
  }

  return JSON.stringify(data);
}

// Imposed to limit database size.
const DB_URL_LENGTH_MAX = 65536;
const DB_TITLE_LENGTH_MAX = 4096;
const DB_DESCRIPTION_LENGTH_MAX = 256;
const DB_SITENAME_LENGTH_MAX = 50;

/**
 * Executes a boolean validate function, throwing if it returns false.
 *
 * @param boolValidateFn
 *        A boolean validate function.
 * @return the input value.
 * @throws if input doesn't pass the validate function.
 */
function simpleValidateFunc(boolValidateFn) {
  return (v, input) => {
    if (!boolValidateFn(v, input)) {
      throw new Error("Invalid value");
    }
    return v;
  };
}

/**
 * List of bookmark object validators, one per each known property.
 * Validators must throw if the property value is invalid and return a fixed up
 * version of the value, if needed.
 */
const BOOKMARK_VALIDATORS = Object.freeze({
  guid: simpleValidateFunc(v => PlacesUtils.isValidGuid(v)),
  parentGuid: simpleValidateFunc(v => PlacesUtils.isValidGuid(v)),
  guidPrefix: simpleValidateFunc(v => PlacesUtils.isValidGuidPrefix(v)),
  index: simpleValidateFunc(
    v => Number.isInteger(v) && v >= PlacesUtils.bookmarks.DEFAULT_INDEX
  ),
  dateAdded: simpleValidateFunc(v => v.constructor.name == "Date" && !isNaN(v)),
  lastModified: simpleValidateFunc(
    v => v.constructor.name == "Date" && !isNaN(v)
  ),
  type: simpleValidateFunc(
    v =>
      Number.isInteger(v) &&
      [
        PlacesUtils.bookmarks.TYPE_BOOKMARK,
        PlacesUtils.bookmarks.TYPE_FOLDER,
        PlacesUtils.bookmarks.TYPE_SEPARATOR,
      ].includes(v)
  ),
  title: v => {
    if (v === null) {
      return "";
    }
    if (typeof v == "string") {
      return v.slice(0, DB_TITLE_LENGTH_MAX);
    }
    throw new Error("Invalid title");
  },
  url: v => {
    simpleValidateFunc(
      val =>
        (typeof val == "string" && val.length <= DB_URL_LENGTH_MAX) ||
        (val instanceof Ci.nsIURI && val.spec.length <= DB_URL_LENGTH_MAX) ||
        (URL.isInstance(val) && val.href.length <= DB_URL_LENGTH_MAX)
    )(v);
    if (typeof v === "string") {
      return new URL(v);
    }
    if (v instanceof Ci.nsIURI) {
      return URL.fromURI(v);
    }
    return v;
  },
  source: simpleValidateFunc(
    v =>
      Number.isInteger(v) &&
      Object.values(PlacesUtils.bookmarks.SOURCES).includes(v)
  ),
  keyword: simpleValidateFunc(v => typeof v == "string" && v.length),
  charset: simpleValidateFunc(v => typeof v == "string" && v.length),
  postData: simpleValidateFunc(v => typeof v == "string" && v.length),
  tags: simpleValidateFunc(
    v =>
      Array.isArray(v) &&
      v.length &&
      v.every(item => item && typeof item == "string")
  ),
});

// Sync bookmark records can contain additional properties.
const SYNC_BOOKMARK_VALIDATORS = Object.freeze({
  // Sync uses Places GUIDs for all records except roots.
  recordId: simpleValidateFunc(
    v =>
      typeof v == "string" &&
      (lazy.PlacesSyncUtils.bookmarks.ROOTS.includes(v) ||
        PlacesUtils.isValidGuid(v))
  ),
  parentRecordId: v => SYNC_BOOKMARK_VALIDATORS.recordId(v),
  // Sync uses kinds instead of types.
  kind: simpleValidateFunc(
    v =>
      typeof v == "string" &&
      Object.values(lazy.PlacesSyncUtils.bookmarks.KINDS).includes(v)
  ),
  query: simpleValidateFunc(v => v === null || (typeof v == "string" && v)),
  folder: simpleValidateFunc(
    v =>
      typeof v == "string" &&
      v &&
      v.length <= PlacesUtils.bookmarks.MAX_TAG_LENGTH
  ),
  tags: v => {
    if (v === null) {
      return [];
    }
    if (!Array.isArray(v)) {
      throw new Error("Invalid tag array");
    }
    for (let tag of v) {
      if (
        typeof tag != "string" ||
        !tag ||
        tag.length > PlacesUtils.bookmarks.MAX_TAG_LENGTH
      ) {
        throw new Error(`Invalid tag: ${tag}`);
      }
    }
    return v;
  },
  keyword: simpleValidateFunc(v => v === null || typeof v == "string"),
  dateAdded: simpleValidateFunc(
    v =>
      typeof v === "number" &&
      v > lazy.PlacesSyncUtils.bookmarks.EARLIEST_BOOKMARK_TIMESTAMP
  ),
  feed: v => (v === null ? v : BOOKMARK_VALIDATORS.url(v)),
  site: v => (v === null ? v : BOOKMARK_VALIDATORS.url(v)),
  title: BOOKMARK_VALIDATORS.title,
  url: BOOKMARK_VALIDATORS.url,
});

// Sync change records are passed between `PlacesSyncUtils` and the Sync
// bookmarks engine, and are used to update an item's sync status and change
// counter at the end of a sync.
const SYNC_CHANGE_RECORD_VALIDATORS = Object.freeze({
  modified: simpleValidateFunc(v => typeof v == "number" && v >= 0),
  counter: simpleValidateFunc(v => typeof v == "number" && v >= 0),
  status: simpleValidateFunc(
    v =>
      typeof v == "number" &&
      Object.values(PlacesUtils.bookmarks.SYNC_STATUS).includes(v)
  ),
  tombstone: simpleValidateFunc(v => v === true || v === false),
  synced: simpleValidateFunc(v => v === true || v === false),
});
/**
 * List PageInfo bookmark object validators.
 */
const PAGEINFO_VALIDATORS = Object.freeze({
  guid: BOOKMARK_VALIDATORS.guid,
  url: BOOKMARK_VALIDATORS.url,
  title: v => {
    if (v == null || v == undefined) {
      return undefined;
    } else if (typeof v === "string") {
      return v;
    }
    throw new TypeError(
      `title property of PageInfo object: ${v} must be a string if provided`
    );
  },
  previewImageURL: v => {
    if (!v) {
      return null;
    }
    return BOOKMARK_VALIDATORS.url(v);
  },
  description: v => {
    if (typeof v === "string" || v === null) {
      return v ? v.slice(0, DB_DESCRIPTION_LENGTH_MAX) : null;
    }
    throw new TypeError(
      `description property of pageInfo object: ${v} must be either a string or null if provided`
    );
  },
  siteName: v => {
    if (typeof v === "string" || v === null) {
      return v ? v.slice(0, DB_SITENAME_LENGTH_MAX) : null;
    }
    throw new TypeError(
      `siteName property of pageInfo object: ${v} must be either a string or null if provided`
    );
  },
  annotations: v => {
    if (typeof v != "object" || v.constructor.name != "Map") {
      throw new TypeError("annotations must be a Map");
    }

    if (v.size == 0) {
      throw new TypeError("there must be at least one annotation");
    }

    for (let [key, value] of v.entries()) {
      if (typeof key != "string") {
        throw new TypeError("all annotation keys must be strings");
      }
      if (
        typeof value != "string" &&
        typeof value != "number" &&
        typeof value != "boolean" &&
        value !== null &&
        value !== undefined
      ) {
        throw new TypeError(
          "all annotation values must be Boolean, Numbers or Strings"
        );
      }
    }
    return v;
  },
  visits: v => {
    if (!Array.isArray(v) || !v.length) {
      throw new TypeError("PageInfo object must have an array of visits");
    }
    let visits = [];
    for (let inVisit of v) {
      let visit = {
        date: new Date(),
        transition: inVisit.transition || lazy.History.TRANSITIONS.LINK,
      };

      if (!PlacesUtils.history.isValidTransition(visit.transition)) {
        throw new TypeError(
          `transition: ${visit.transition} is not a valid transition type`
        );
      }

      if (inVisit.date) {
        PlacesUtils.history.ensureDate(inVisit.date);
        if (inVisit.date > Date.now() + TIMERS_RESOLUTION_SKEW_MS) {
          throw new TypeError(`date: ${inVisit.date} cannot be a future date`);
        }
        visit.date = inVisit.date;
      }

      if (inVisit.referrer) {
        visit.referrer = PlacesUtils.normalizeToURLOrGUID(inVisit.referrer);
      }
      visits.push(visit);
    }
    return visits;
  },
});

export var PlacesUtils = {
  // Place entries that are containers, e.g. bookmark folders or queries.
  TYPE_X_MOZ_PLACE_CONTAINER: "text/x-moz-place-container",
  // Place entries that are bookmark separators.
  TYPE_X_MOZ_PLACE_SEPARATOR: "text/x-moz-place-separator",
  // Place entries that are not containers or separators
  TYPE_X_MOZ_PLACE: "text/x-moz-place",
  // Place entries in shortcut url format (url\ntitle)
  TYPE_X_MOZ_URL: "text/x-moz-url",
  // Place entries formatted as HTML anchors
  TYPE_HTML: "text/html",
  // Place entries as raw URL text
  TYPE_PLAINTEXT: "text/plain",
  // Used to track the action that populated the clipboard.
  TYPE_X_MOZ_PLACE_ACTION: "text/x-moz-place-action",

  // Deprecated: Remaining only for supporting migration of old livemarks.
  LMANNO_FEEDURI: "livemark/feedURI",
  LMANNO_SITEURI: "livemark/siteURI",
  CHARSET_ANNO: "URIProperties/characterSet",
  // Deprecated: This is only used for supporting import from older datasets.
  MOBILE_ROOT_ANNO: "mobile/bookmarksRoot",

  TOPIC_SHUTDOWN: "places-shutdown",
  TOPIC_INIT_COMPLETE: "places-init-complete",
  TOPIC_DATABASE_LOCKED: "places-database-locked",
  TOPIC_EXPIRATION_FINISHED: "places-expiration-finished",
  TOPIC_FAVICONS_EXPIRED: "places-favicons-expired",
  TOPIC_VACUUM_STARTING: "places-vacuum-starting",
  TOPIC_BOOKMARKS_RESTORE_BEGIN: "bookmarks-restore-begin",
  TOPIC_BOOKMARKS_RESTORE_SUCCESS: "bookmarks-restore-success",
  TOPIC_BOOKMARKS_RESTORE_FAILED: "bookmarks-restore-failed",

  observers: PlacesObservers,

  /**
   * GUIDs associated with virtual queries that are used for displaying the
   * top-level folders in the left pane.
   */
  virtualAllBookmarksGuid: "allbms_____v",
  virtualHistoryGuid: "history____v",
  virtualDownloadsGuid: "downloads__v",
  virtualTagsGuid: "tags_______v",

  /**
   * Checks if a guid is a virtual left-pane root.
   *
   * @param {String} guid The guid of the item to look for.
   * @returns {Boolean} true if guid is a virtual root, false otherwise.
   */
  isVirtualLeftPaneItem(guid) {
    return (
      guid == PlacesUtils.virtualAllBookmarksGuid ||
      guid == PlacesUtils.virtualHistoryGuid ||
      guid == PlacesUtils.virtualDownloadsGuid ||
      guid == PlacesUtils.virtualTagsGuid
    );
  },

  asContainer: aNode => asContainer(aNode),
  asQuery: aNode => asQuery(aNode),

  endl: NEWLINE,

  /**
   * Is a string a valid GUID?
   *
   * @param guid: (String)
   * @return (Boolean)
   */
  isValidGuid(guid) {
    return typeof guid == "string" && guid && /^[a-zA-Z0-9\-_]{12}$/.test(guid);
  },

  /**
   * Is a string a valid GUID prefix?
   *
   * @param guidPrefix: (String)
   * @return (Boolean)
   */
  isValidGuidPrefix(guidPrefix) {
    return (
      typeof guidPrefix == "string" &&
      guidPrefix &&
      /^[a-zA-Z0-9\-_]{1,11}$/.test(guidPrefix)
    );
  },

  /**
   * Generates a random GUID and replace its beginning with the given
   * prefix. We do this instead of just prepending the prefix to keep
   * the correct character length.
   *
   * @param prefix: (String)
   * @return (String)
   */
  generateGuidWithPrefix(prefix) {
    return prefix + this.history.makeGuid().substring(prefix.length);
  },

  /**
   * Converts a string or n URL object to an nsIURI.
   *
   * @param url (URL) or (String)
   *        the URL to convert.
   * @return nsIURI for the given URL.
   */
  toURI(url) {
    if (url instanceof Ci.nsIURI) {
      return url;
    }
    if (URL.isInstance(url)) {
      return url.URI;
    }
    return Services.io.newURI(url);
  },

  /**
   * Convert a Date object to a PRTime (microseconds).
   *
   * @param date
   *        the Date object to convert.
   * @return microseconds from the epoch.
   */
  toPRTime(date) {
    if (
      (typeof date != "number" && date.constructor.name != "Date") ||
      isNaN(date)
    ) {
      throw new Error("Invalid value passed to toPRTime");
    }
    return date * 1000;
  },

  /**
   * Convert a PRTime to a Date object.
   *
   * @param time
   *        microseconds from the epoch.
   * @return a Date object.
   */
  toDate(time) {
    if (typeof time != "number" || isNaN(time)) {
      throw new Error("Invalid value passed to toDate");
    }
    return new Date(parseInt(time / 1000));
  },

  /**
   * Wraps a string in a nsISupportsString wrapper.
   * @param   aString
   *          The string to wrap.
   * @returns A nsISupportsString object containing a string.
   */
  toISupportsString: function PU_toISupportsString(aString) {
    let s = Cc["@mozilla.org/supports-string;1"].createInstance(
      Ci.nsISupportsString
    );
    s.data = aString;
    return s;
  },

  getFormattedString: function PU_getFormattedString(key, params) {
    return lazy.bundle.formatStringFromName(key, params);
  },

  getString: function PU_getString(key) {
    return lazy.bundle.GetStringFromName(key);
  },

  /**
   * Parses a moz-action URL and returns its parts.
   *
   * @param url A moz-action URI.
   * @note URL is in the format moz-action:ACTION,JSON_ENCODED_PARAMS
   */
  parseActionUrl(url) {
    if (url instanceof Ci.nsIURI) {
      url = url.spec;
    } else if (URL.isInstance(url)) {
      url = url.href;
    }
    // Faster bailout.
    if (!url.startsWith("moz-action:")) {
      return null;
    }

    try {
      let [, type, params] = url.match(lazy.MOZ_ACTION_REGEX);
      let action = {
        type,
        params: JSON.parse(params),
      };
      for (let key in action.params) {
        action.params[key] = decodeURIComponent(action.params[key]);
      }
      return action;
    } catch (ex) {
      console.error(`Invalid action url "${url}"`);
      return null;
    }
  },

  /**
   * Determines if a bookmark folder or folder shortcut is generated by a query.
   *
   * @param {nsINavHistoryResultNode} node A result node.
   * @returns {boolean} whether `node` is a bookmark folder or folder shortcut
   * generated as result of a query.
   */
  nodeIsQueryGeneratedFolder(node) {
    return (
      node.parent &&
      this.nodeIsFolderOrShortcut(node) &&
      this.nodeIsQuery(node.parent)
    );
  },

  /**
   * Determines whether or not a ResultNode is a bookmark folder or folder
   * shortcut.
   *
   * @param {nsINavHistoryResultNode} node A result node.
   * @returns {boolean} whether `node` is a bookmark folder or folder shortcut.
   */
  nodeIsFolderOrShortcut(node) {
    return [
      Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER,
      Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT,
    ].includes(node.type);
  },

  /**
   * Determines whether or not a ResultNode is a bookmark folder.
   * For most UI purposes it's better to use nodeIsFolderOrShortcut,
   * as folder shortcuts behave like the target folder.
   *
   * @param {nsINavHistoryResultNode} node A result node.
   * @returns {boolean} whether the node is a bookmark folder.
   */
  nodeIsConcreteFolder(node) {
    return node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER;
  },

  /**
   * Determines whether or not a ResultNode represents a bookmarked URI.
   *
   * @param {nsINavHistoryResultNode} node A result node.
   * @returns {boolean} whether the node is a bookmarked URI.
   */
  nodeIsBookmark(node) {
    return (
      node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI &&
      (node.itemId != -1 || node.bookmarkGuid)
    );
  },

  /**
   * Determines whether or not a ResultNode is a bookmark separator.
   *
   * @param {nsINavHistoryResultNode} node A result node.
   * @returns {boolean} whether the node is a bookmark separator.
   */
  nodeIsSeparator(node) {
    return node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR;
  },

  /**
   * Determines whether or not a ResultNode represents a URL.
   *
   * @param {nsINavHistoryResultNode} node A result node.
   * @returns {boolean} whether the node represents a URL.
   */
  nodeIsURI(node) {
    return node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI;
  },

  /**
   * Determines whether or not a ResultNode is a Places query.
   *
   * @param {nsINavHistoryResultNode} node A result node.
   * @returns {boolean} whether the node is a Places Query.
   */
  nodeIsQuery(node) {
    return node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY;
  },

  /**
   * Generator for a node's ancestors.
   * @param aNode
   *        A result node
   */
  nodeAncestors: function* PU_nodeAncestors(aNode) {
    let node = aNode.parent;
    while (node) {
      yield node;
      node = node.parent;
    }
  },

  /**
   * Checks validity of an object, filling up default values for optional
   * properties.
   *
   * @param {string} name
   *        The operation name. This is included in the error message if
   *        validation fails.
   * @param validators (object)
   *        An object containing input validators. Keys should be field names;
   *        values should be validation functions.
   * @param props (object)
   *        The object to validate.
   * @param behavior (object) [optional]
   *        Object defining special behavior for some of the properties.
   *        The following behaviors may be optionally set:
   *         - required: this property is required.
   *         - replaceWith: this property will be overwritten with the value
   *                        provided
   *         - requiredIf: if the provided condition is satisfied, then this
   *                       property is required.
   *         - validIf: if the provided condition is not satisfied, then this
   *                    property is invalid.
   *         - defaultValue: an undefined property should default to this value.
   *         - fixup: a function invoked when validation fails, takes the input
   *                  object as argument and must fix the property.
   *
   * @return a validated and normalized item.
   * @throws if the object contains invalid data.
   * @note any unknown properties are pass-through.
   */
  validateItemProperties(name, validators, props, behavior = {}) {
    if (typeof props != "object" || !props) {
      throw new Error(`${name}: Input should be a valid object`);
    }
    // Make a shallow copy of `props` to avoid mutating the original object
    // when filling in defaults.
    let input = Object.assign({}, props);
    let normalizedInput = {};
    let required = new Set();
    for (let prop in behavior) {
      if (
        behavior[prop].hasOwnProperty("required") &&
        behavior[prop].required
      ) {
        required.add(prop);
      }
      if (
        behavior[prop].hasOwnProperty("requiredIf") &&
        behavior[prop].requiredIf(input)
      ) {
        required.add(prop);
      }
      if (
        behavior[prop].hasOwnProperty("validIf") &&
        input[prop] !== undefined &&
        !behavior[prop].validIf(input)
      ) {
        if (behavior[prop].hasOwnProperty("fixup")) {
          behavior[prop].fixup(input);
        } else {
          throw new Error(
            `${name}: Invalid value for property '${prop}': ${JSON.stringify(
              input[prop]
            )}`
          );
        }
      }
      if (
        behavior[prop].hasOwnProperty("defaultValue") &&
        input[prop] === undefined
      ) {
        input[prop] = behavior[prop].defaultValue;
      }
      if (behavior[prop].hasOwnProperty("replaceWith")) {
        input[prop] = behavior[prop].replaceWith;
      }
    }

    for (let prop in input) {
      if (required.has(prop)) {
        required.delete(prop);
      } else if (input[prop] === undefined) {
        // Skip undefined properties that are not required.
        continue;
      }
      if (validators.hasOwnProperty(prop)) {
        try {
          normalizedInput[prop] = validators[prop](input[prop], input);
        } catch (ex) {
          if (
            behavior.hasOwnProperty(prop) &&
            behavior[prop].hasOwnProperty("fixup")
          ) {
            behavior[prop].fixup(input);
            normalizedInput[prop] = input[prop];
          } else {
            throw new Error(
              `${name}: Invalid value for property '${prop}': ${JSON.stringify(
                input[prop]
              )}`
            );
          }
        }
      }
    }
    if (required.size > 0) {
      throw new Error(
        `${name}: The following properties were expected: ${[...required].join(
          ", "
        )}`
      );
    }
    return normalizedInput;
  },

  BOOKMARK_VALIDATORS,
  PAGEINFO_VALIDATORS,
  SYNC_BOOKMARK_VALIDATORS,
  SYNC_CHANGE_RECORD_VALIDATORS,

  QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),

  _shutdownFunctions: [],
  registerShutdownFunction: function PU_registerShutdownFunction(aFunc) {
    // If this is the first registered function, add the shutdown observer.
    if (!this._shutdownFunctions.length) {
      Services.obs.addObserver(this, this.TOPIC_SHUTDOWN);
    }
    this._shutdownFunctions.push(aFunc);
  },

  // nsIObserver
  observe: function PU_observe(aSubject, aTopic) {
    switch (aTopic) {
      case this.TOPIC_SHUTDOWN:
        Services.obs.removeObserver(this, this.TOPIC_SHUTDOWN);
        while (this._shutdownFunctions.length) {
          this._shutdownFunctions.shift().apply(this);
        }
        break;
    }
  },

  /**
   * Determines whether or not a ResultNode is a host container.
   * @param   aNode
   *          A result node
   * @returns true if the node is a host container, false otherwise
   */
  nodeIsHost: function PU_nodeIsHost(aNode) {
    return (
      aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY &&
      aNode.parent &&
      asQuery(aNode.parent).queryOptions.resultType ==
        Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY
    );
  },

  /**
   * Determines whether or not a ResultNode is a day container.
   * @param   node
   *          A NavHistoryResultNode
   * @returns true if the node is a day container, false otherwise
   */
  nodeIsDay: function PU_nodeIsDay(aNode) {
    var resultType;
    return (
      aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY &&
      aNode.parent &&
      ((resultType = asQuery(aNode.parent).queryOptions.resultType) ==
        Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY ||
        resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY)
    );
  },

  /**
   * Determines whether or not a result-node is a tag container.
   * @param   aNode
   *          A result-node
   * @returns true if the node is a tag container, false otherwise
   */
  nodeIsTagQuery: function PU_nodeIsTagQuery(aNode) {
    if (aNode.type != Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) {
      return false;
    }
    // Direct child of RESULTS_AS_TAGS_ROOT.
    let parent = aNode.parent;
    if (
      parent &&
      PlacesUtils.asQuery(parent).queryOptions.resultType ==
        Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAGS_ROOT
    ) {
      return true;
    }
    // We must also support the right pane of the Library, when the tag query
    // is the root node. Unfortunately this is also valid for any tag query
    // selected in the left pane that is not a direct child of RESULTS_AS_TAGS_ROOT.
    if (
      !parent &&
      aNode == aNode.parentResult.root &&
      PlacesUtils.asQuery(aNode).query.tags.length == 1
    ) {
      return true;
    }
    return false;
  },

  /**
   * Determines whether or not a ResultNode is a container.
   * @param   aNode
   *          A result node
   * @returns true if the node is a container item, false otherwise
   */
  containerTypes: [
    Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER,
    Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT,
    Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY,
  ],
  nodeIsContainer: function PU_nodeIsContainer(aNode) {
    return this.containerTypes.includes(aNode.type);
  },

  /**
   * Determines whether or not a ResultNode is an history related container.
   * @param   node
   *          A result node
   * @returns true if the node is an history related container, false otherwise
   */
  nodeIsHistoryContainer: function PU_nodeIsHistoryContainer(aNode) {
    var resultType;
    return (
      this.nodeIsQuery(aNode) &&
      ((resultType = asQuery(aNode).queryOptions.resultType) ==
        Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY ||
        resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY ||
        resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY ||
        this.nodeIsDay(aNode) ||
        this.nodeIsHost(aNode))
    );
  },

  /**
   * Gets the concrete item-guid for the given node. For everything but folder
   * shortcuts, this is just node.bookmarkGuid.  For folder shortcuts, this is
   * node.targetFolderGuid (see nsINavHistoryService.idl for the semantics).
   *
   * @param aNode
   *        a result node.
   * @return the concrete item-guid for aNode.
   */
  getConcreteItemGuid(aNode) {
    if (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) {
      return asQuery(aNode).targetFolderGuid;
    }
    return aNode.bookmarkGuid;
  },

  /**
   * Reverse a host based on the moz_places algorithm, that is reverse the host
   * string and add a trailing period.  For example "google.com" becomes
   * "moc.elgoog.".
   *
   * @param url
   *        the URL to generate a rev host for.
   * @return the reversed host string.
   */
  getReversedHost(url) {
    return url.host.split("").reverse().join("") + ".";
  },

  /**
   * String-wraps a result node according to the rules of the specified
   * content type for copy or move operations.
   *
   * @param   aNode
   *          The Result node to wrap (serialize)
   * @param   aType
   *          The content type to serialize as
   * @return  A string serialization of the node
   */
  wrapNode(aNode, aType) {
    // When wrapping a node, we want all the items, even if the original
    // query options are excluding them. This can happen when copying from the
    // left pane of the Library.
    // Since this method is only used for text/plain and text/html, we can
    // use the concrete GUID without the risk of dragging root folders.
    function gatherTextDataFromNode(node, gatherDataFunc) {
      if (
        PlacesUtils.nodeIsFolderOrShortcut(node) &&
        asQuery(node).queryOptions.excludeItems
      ) {
        let folderRoot = PlacesUtils.getFolderContents(
          PlacesUtils.getConcreteItemGuid(node),
          false,
          true
        ).root;
        try {
          return gatherDataFunc(folderRoot);
        } finally {
          folderRoot.containerOpen = false;
        }
      }
      return gatherDataFunc(node);
    }

    function gatherDataHtml(node, recursiveOpen = true) {
      let htmlEscape = s =>
        s
          .replace(/&/g, "&amp;")
          .replace(/>/g, "&gt;")
          .replace(/</g, "&lt;")
          .replace(/"/g, "&quot;")
          .replace(/'/g, "&apos;");

      // escape out potential HTML in the title
      let escapedTitle = node.title ? htmlEscape(node.title) : "";

      if (PlacesUtils.nodeIsContainer(node)) {
        asContainer(node);

        let childString = "<DL><DT>" + escapedTitle + "</DT>" + NEWLINE;

        let wasOpen = node.containerOpen;
        if (!wasOpen && recursiveOpen) {
          node.containerOpen = true;
        }
        if (node.containerOpen) {
          for (let i = 0, count = node.childCount; i < count; ++i) {
            let child = node.getChild(i);
            // We only allow recursively opening concrete folders, not queries
            // nor shortcuts, to avoid the possibility of walking infinite loops.
            childString +=
              "<DD>" +
              NEWLINE +
              gatherDataHtml(child, PlacesUtils.nodeIsConcreteFolder(child)) +
              "</DD>" +
              NEWLINE;
          }
          node.containerOpen = wasOpen;
        }
        return childString + "</DL>" + NEWLINE;
      }
      if (PlacesUtils.nodeIsURI(node)) {
        return `<A HREF="${node.uri}">${escapedTitle}</A>${NEWLINE}`;
      }
      if (PlacesUtils.nodeIsSeparator(node)) {
        return "<HR>" + NEWLINE;
      }
      return "";
    }

    function gatherDataText(node, recursiveOpen = true) {
      if (PlacesUtils.nodeIsContainer(node)) {
        asContainer(node);

        let childString = node.title + NEWLINE;

        let wasOpen = node.containerOpen;
        if (!wasOpen && recursiveOpen) {
          node.containerOpen = true;
        }
        if (node.containerOpen) {
          for (let i = 0, count = node.childCount; i < count; ++i) {
            let child = node.getChild(i);
            let suffix = i < count - 1 ? NEWLINE : "";
            childString +=
              gatherDataText(child, PlacesUtils.nodeIsConcreteFolder(child)) +
              suffix;
          }
          node.containerOpen = wasOpen;
        }
        return childString;
      }
      if (PlacesUtils.nodeIsURI(node)) {
        return node.uri;
      }
      if (PlacesUtils.nodeIsSeparator(node)) {
        return "--------------------";
      }
      return "";
    }

    switch (aType) {
      case this.TYPE_X_MOZ_PLACE:
      case this.TYPE_X_MOZ_PLACE_SEPARATOR:
      case this.TYPE_X_MOZ_PLACE_CONTAINER: {
        // Serialize the node to JSON.
        return serializeNode(aNode);
      }
      case this.TYPE_X_MOZ_URL: {
        if (PlacesUtils.nodeIsURI(aNode)) {
          return aNode.uri + NEWLINE + aNode.title;
        }
        if (PlacesUtils.nodeIsContainer(aNode)) {
          return PlacesUtils.getURLsForContainerNode(aNode)
            .map(item => item.uri + "\n" + item.title)
            .join("\n");
        }
        return "";
      }
      case this.TYPE_HTML: {
        return gatherTextDataFromNode(aNode, gatherDataHtml);
      }
    }

    // Otherwise, we wrap as TYPE_PLAINTEXT.
    return gatherTextDataFromNode(aNode, gatherDataText);
  },

  /**
   * Unwraps data from the Clipboard or the current Drag Session.
   * @param   blob
   *          A blob (string) of data, in some format we potentially know how
   *          to parse.
   * @param   type
   *          The content type of the blob.
   * @returns An array of objects representing each item contained by the source.
   * @throws if the blob contains invalid data.
   */
  unwrapNodes: function PU_unwrapNodes(blob, type) {
    // We split on "\n"  because the transferable system converts "\r\n" to "\n"
    var nodes = [];
    switch (type) {
      case this.TYPE_X_MOZ_PLACE:
      case this.TYPE_X_MOZ_PLACE_SEPARATOR:
      case this.TYPE_X_MOZ_PLACE_CONTAINER:
        nodes = JSON.parse("[" + blob + "]");
        break;
      case this.TYPE_X_MOZ_URL: {
        let parts = blob.split("\n");
        // data in this type has 2 parts per entry, so if there are fewer
        // than 2 parts left, the blob is malformed and we should stop
        // but drag and drop of files from the shell has parts.length = 1
        if (parts.length != 1 && parts.length % 2) {
          break;
        }
        for (let i = 0; i < parts.length; i = i + 2) {
          let uriString = parts[i];
          let titleString = "";
          if (parts.length > i + 1) {
            titleString = parts[i + 1];
          } else {
            // for drag and drop of files, try to use the leafName as title
            try {
              titleString = Services.io
                .newURI(uriString)
                .QueryInterface(Ci.nsIURL).fileName;
            } catch (ex) {}
          }
          // note:  Services.io.newURI() will throw if uriString is not a valid URI
          let uri = Services.io.newURI(uriString);
          if (Services.io.newURI(uriString) && uri.scheme != "place") {
            nodes.push({
              uri: uriString,
              title: titleString ? titleString : uriString,
              type: this.TYPE_X_MOZ_URL,
            });
          }
        }
        break;
      }
      case this.TYPE_PLAINTEXT: {
        let parts = blob.split("\n");
        for (let i = 0; i < parts.length; i++) {
          let uriString = parts[i];
          // text/uri-list is converted to TYPE_PLAINTEXT but it could contain
          // comments line prepended by #, we should skip them, as well as
          // empty uris.
          if (uriString.substr(0, 1) == "\x23" || uriString == "") {
            continue;
          }
          // note: Services.io.newURI) will throw if uriString is not a valid URI
          let uri = Services.io.newURI(uriString);
          if (uri.scheme != "place") {
            nodes.push({
              uri: uriString,
              title: uriString,
              type: this.TYPE_X_MOZ_URL,
            });
          }
        }
        break;
      }
      default:
        throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
    }
    return nodes;
  },

  /**
   * Validate an input PageInfo object, returning a valid PageInfo object.
   *
   * @param pageInfo: (PageInfo)
   * @return (PageInfo)
   */
  validatePageInfo(pageInfo, validateVisits = true) {
    return this.validateItemProperties(
      "PageInfo",
      PAGEINFO_VALIDATORS,
      pageInfo,
      {
        url: { requiredIf: b => !b.guid },
        guid: { requiredIf: b => !b.url },
        visits: { requiredIf: () => validateVisits },
      }
    );
  },
  /**
   * Normalize a key to either a string (if it is a valid GUID) or an
   * instance of `URL` (if it is a `URL`, `nsIURI`, or a string
   * representing a valid url).
   *
   * @throws (TypeError)
   *         If the key is neither a valid guid nor a valid url.
   */
  normalizeToURLOrGUID(key) {
    if (typeof key === "string") {
      // A string may be a URL or a guid
      if (this.isValidGuid(key)) {
        return key;
      }
      return new URL(key);
    }
    if (URL.isInstance(key)) {
      return key;
    }
    if (key instanceof Ci.nsIURI) {
      return URL.fromURI(key);
    }
    throw new TypeError("Invalid url or guid: " + key);
  },

  /**
   * Generates a nsINavHistoryResult for the contents of a folder.
   * @param   aFolderGuid
   *          The folder to open
   * @param   [optional] excludeItems
   *          True to hide all items (individual bookmarks). This is used on
   *          the left places pane so you just get a folder hierarchy.
   * @param   [optional] expandQueries
   *          True to make query items expand as new containers. For managing,
   *          you want this to be false, for menus and such, you want this to
   *          be true.
   * @returns A nsINavHistoryResult containing the contents of the
   *          folder. The result.root is guaranteed to be open.
   */
  getFolderContents(aFolderGuid, aExcludeItems, aExpandQueries) {
    if (!this.isValidGuid(aFolderGuid)) {
      throw new Error("aFolderGuid should be a valid GUID.");
    }
    var query = this.history.getNewQuery();
    query.setParents([aFolderGuid]);
    var options = this.history.getNewQueryOptions();
    options.excludeItems = aExcludeItems;
    options.expandQueries = aExpandQueries;

    var result = this.history.executeQuery(query, options);
    result.root.containerOpen = true;
    return result;
  },

  // Identifier getters for special folders.
  // You should use these everywhere PlacesUtils is available to avoid XPCOM
  // traversal just to get roots' ids.
  get tagsFolderId() {
    delete this.tagsFolderId;
    return (this.tagsFolderId = this.bookmarks.tagsFolder);
  },

  /**
   * Checks if item is a root.
   *
   * @param {String} guid The guid of the item to look for.
   * @returns {Boolean} true if guid is a root, false otherwise.
   */
  isRootItem(guid) {
    return (
      guid == PlacesUtils.bookmarks.menuGuid ||
      guid == PlacesUtils.bookmarks.toolbarGuid ||
      guid == PlacesUtils.bookmarks.unfiledGuid ||
      guid == PlacesUtils.bookmarks.tagsGuid ||
      guid == PlacesUtils.bookmarks.rootGuid ||
      guid == PlacesUtils.bookmarks.mobileGuid
    );
  },

  /**
   * Returns a nsNavHistoryContainerResultNode with forced excludeItems and
   * expandQueries.
   * @param   aNode
   *          The node to convert
   * @param   [optional] excludeItems
   *          True to hide all items (individual bookmarks). This is used on
   *          the left places pane so you just get a folder hierarchy.
   * @param   [optional] expandQueries
   *          True to make query items expand as new containers. For managing,
   *          you want this to be false, for menus and such, you want this to
   *          be true.
   * @returns A nsINavHistoryContainerResultNode containing the unfiltered
   *          contents of the container.
   * @note    The returned container node could be open or closed, we don't
   *          guarantee its status.
   */
  getContainerNodeWithOptions: function PU_getContainerNodeWithOptions(
    aNode,
    aExcludeItems,
    aExpandQueries
  ) {
    if (!this.nodeIsContainer(aNode)) {
      throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
    }

    // excludeItems is inherited by child containers in an excludeItems view.
    var excludeItems =
      asQuery(aNode).queryOptions.excludeItems ||
      asQuery(aNode.parentResult.root).queryOptions.excludeItems;
    // expandQueries is inherited by child containers in an expandQueries view.
    var expandQueries =
      asQuery(aNode).queryOptions.expandQueries &&
      asQuery(aNode.parentResult.root).queryOptions.expandQueries;

    // If our options are exactly what we expect, directly return the node.
    if (excludeItems == aExcludeItems && expandQueries == aExpandQueries) {
      return aNode;
    }

    // Otherwise, get contents manually.
    var query = {},
      options = {};
    this.history.queryStringToQuery(aNode.uri, query, options);
    options.value.excludeItems = aExcludeItems;
    options.value.expandQueries = aExpandQueries;
    return this.history.executeQuery(query.value, options.value).root;
  },

  /**
   * Returns true if a container has uri nodes in its first level.
   * Has better performance than (getURLsForContainerNode(node).length > 0).
   * @param aNode
   *        The container node to search through.
   * @returns true if the node contains uri nodes, false otherwise.
   */
  hasChildURIs: function PU_hasChildURIs(aNode) {
    if (!this.nodeIsContainer(aNode)) {
      return false;
    }

    let root = this.getContainerNodeWithOptions(aNode, false, true);
    let result = root.parentResult;
    let didSuppressNotifications = false;
    let wasOpen = root.containerOpen;
    if (!wasOpen) {
      didSuppressNotifications = result.suppressNotifications;
      if (!didSuppressNotifications) {
        result.suppressNotifications = true;
      }

      root.containerOpen = true;
    }

    let found = false;
    for (let i = 0, count = root.childCount; i < count && !found; i++) {
      let child = root.getChild(i);
      if (this.nodeIsURI(child)) {
        found = true;
      }
    }

    if (!wasOpen) {
      root.containerOpen = false;
      if (!didSuppressNotifications) {
        result.suppressNotifications = false;
      }
    }
    return found;
  },

  /**
   * Returns an array containing all the uris in the first level of the
   * passed in container.
   * If you only need to know if the node contains uris, use hasChildURIs.
   * @param aNode
   *        The container node to search through
   * @returns array of uris in the first level of the container.
   */
  getURLsForContainerNode: function PU_getURLsForContainerNode(aNode) {
    let urls = [];
    if (!this.nodeIsContainer(aNode)) {
      return urls;
    }

    let root = this.getContainerNodeWithOptions(aNode, false, true);
    let result = root.parentResult;
    let wasOpen = root.containerOpen;
    let didSuppressNotifications = false;
    if (!wasOpen) {
      didSuppressNotifications = result.suppressNotifications;
      if (!didSuppressNotifications) {
        result.suppressNotifications = true;
      }

      root.containerOpen = true;
    }

    for (let i = 0, count = root.childCount; i < count; ++i) {
      let child = root.getChild(i);
      if (this.nodeIsURI(child)) {
        urls.push({
          uri: child.uri,
          isBookmark: this.nodeIsBookmark(child),
          title: child.title,
        });
      }
    }

    if (!wasOpen) {
      root.containerOpen = false;
      if (!didSuppressNotifications) {
        result.suppressNotifications = false;
      }
    }
    return urls;
  },

  /**
   * Gets a shared Sqlite.sys.mjs readonly connection to the Places database,
   * usable only for SELECT queries.
   *
   * This is intended to be used mostly internally, components outside of
   * Places should, when possible, use API calls and file bugs to get proper
   * APIs, where they are missing.
   * Keep in mind the Places DB schema is by no means frozen or even stable.
   * Your custom queries can - and will - break overtime.
   *
   * Example:
   * let db = await PlacesUtils.promiseDBConnection();
   * let rows = await db.executeCached(sql, params);
   */
  promiseDBConnection: () => lazy.gAsyncDBConnPromised,

  /**
   * This is pretty much the same as promiseDBConnection, but with a larger
   * page cache, useful for consumers doing large table scans, like the urlbar.
   * @see promiseDBConnection
   */
  promiseLargeCacheDBConnection: () => lazy.gAsyncDBLargeCacheConnPromised,
  get largeCacheDBConnDeferred() {
    return gAsyncDBLargeCacheConnDeferred;
  },

  /**
   * Returns a Sqlite.sys.mjs wrapper for the main Places connection. Most callers
   * should prefer `withConnectionWrapper`, which ensures that all database
   * operations finish before the connection is closed.
   */
  promiseUnsafeWritableDBConnection: () => lazy.gAsyncDBWrapperPromised,

  /**
   * Performs a read/write operation on the Places database through a Sqlite.sys.mjs
   * wrapped connection to the Places database.
   *
   * This is intended to be used only by Places itself, always use APIs if you
   * need to modify the Places database. Use promiseDBConnection if you need to
   * SELECT from the database and there's no covering API.
   * Keep in mind the Places DB schema is by no means frozen or even stable.
   * Your custom queries can - and will - break overtime.
   *
   * As all operations on the Places database are asynchronous, if shutdown
   * is initiated while an operation is pending, this could cause dataloss.
   * Using `withConnectionWrapper` ensures that shutdown waits until all
   * operations are complete before proceeding.
   *
   * Example:
   * await withConnectionWrapper("Bookmarks: Remove a bookmark", Task.async(function*(db) {
   *    // Proceed with the db, asynchronously.
   *    // Shutdown will not interrupt operations that take place here.
   * }));
   *
   * @param {string} name The name of the operation. Used for debugging, logging
   *   and crash reporting.
   * @param {function(db)} task A function that takes as argument a Sqlite.sys.mjs
   *   connection and returns a Promise. Shutdown is guaranteed to not interrupt
   *   execution of `task`.
   */
  async withConnectionWrapper(name, task) {
    if (!name) {
      throw new TypeError("Expecting a user-readable name");
    }
    let db = await lazy.gAsyncDBWrapperPromised;
    return db.executeBeforeShutdown(name, task);
  },

  /**
   * Gets favicon data for a given page url.
   *
   * @param {string | URL | nsIURI} aPageUrl
   *   url of the page to look favicon for.
   * @param {number} preferredWidth
   *   The preferred width of the favicon in pixels. The default value of 0
   *   returns the largest icon available.
   * @resolves to an object representing a favicon entry, having the following
   *           properties: { uri, dataLen, data, mimeType }
   * @rejects JavaScript exception if the given url has no associated favicon.
   */
  promiseFaviconData(aPageUrl, preferredWidth = 0) {
    return new Promise((resolve, reject) => {
      if (!(aPageUrl instanceof Ci.nsIURI)) {
        aPageUrl = PlacesUtils.toURI(aPageUrl);
      }
      PlacesUtils.favicons.getFaviconDataForPage(
        aPageUrl,
        function (uri, dataLen, data, mimeType, size) {
          if (uri) {
            resolve({ uri, dataLen, data, mimeType, size });
          } else {
            reject();
          }
        },
        preferredWidth
      );
    });
  },

  /**
   * Returns the passed URL with a #size ref for the specified size and
   * devicePixelRatio.
   *
   * @param window
   *        The window where the icon will appear.
   * @param href
   *        The string href we should add the ref to.
   * @param size
   *        The target image size
   * @return The URL with the fragment at the end, in the same formar as input.
   */
  urlWithSizeRef(window, href, size) {
    return (
      href +
      (href.includes("#") ? "&" : "#") +
      "size=" +
      Math.round(size) * window.devicePixelRatio
    );
  },

  /**
   * Asynchronously retrieve a JS-object representation of a places bookmarks
   * item (a bookmark, a folder, or a separator) along with all of its
   * descendants.
   *
   * @param [optional] aItemGuid
   *        the (topmost) item to be queried.  If it's not passed, the places
   *        root is queried: that is, you get a representation of the entire
   *        bookmarks hierarchy.
   * @param [optional] aOptions
   *        Options for customizing the query behavior, in the form of a JS
   *        object with any of the following properties:
   *         - excludeItemsCallback: a function for excluding items, along with
   *           their descendants.  Given an item object (that has everything set
   *           apart its potential children data), it should return true if the
   *           item should be excluded.  Once an item is excluded, the function
   *           isn't called for any of its descendants.  This isn't called for
   *           the root item.
   *           WARNING: since the function may be called for each item, using
   *           this option can slow down the process significantly if the
   *           callback does anything that's not relatively trivial.  It is
   *           highly recommended to avoid any synchronous I/O or DB queries.
   *        - includeItemIds: opt-in to include the deprecated id property.
   *          Use it if you must. It'll be removed once the switch to GUIDs is
   *          complete.
   *
   * @return {Promise}
   * @resolves to a JS object that represents either a single item or a
   * bookmarks tree.  Each node in the tree has the following properties set:
   *  - guid (string): the item's GUID (same as aItemGuid for the top item).
   *  - [deprecated] id (number): the item's id. This is only if
   *    aOptions.includeItemIds is set.
   *  - type (string):  the item's type.  @see PlacesUtils.TYPE_X_*
   *  - typeCode (number):  the item's type in numeric format.
   *    @see PlacesUtils.bookmarks.TYPE_*
   *  - title (string): the item's title. If it has no title, this property
   *    isn't set.
   *  - dateAdded (number, microseconds from the epoch): the date-added value of
   *    the item.
   *  - lastModified (number, microseconds from the epoch): the last-modified
   *    value of the item.
   *  - index: the item's index under it's parent.
   *
   * The root object (i.e. the one for aItemGuid) also has the following
   * properties set:
   *  - parentGuid (string): the GUID of the root's parent.  This isn't set if
   *    the root item is the places root.
   *  - itemsCount (number, not enumerable): the number of items, including the
   *    root item itself, which are represented in the resolved object.
   *
   * Bookmark items also have the following properties:
   *  - uri (string): the item's url.
   *  - tags (string): csv string of the bookmark's tags.
   *  - charset (string): the last known charset of the bookmark.
   *  - keyword (string): the bookmark's keyword (unset if none).
   *  - postData (string): the bookmark's keyword postData (unset if none).
   *  - iconUri (string): the bookmark's favicon url.
   * The last four properties are not set at all if they're irrelevant (e.g.
   * |charset| is not set if no charset was previously set for the bookmark
   * url).
   *
   * Folders may also have the following properties:
   *  - children (array): the folder's children information, each of them
   *    having the same set of properties as above.
   *
   * @rejects if the query failed for any reason.
   * @note if aItemGuid points to a non-existent item, the returned promise is
   * resolved to null.
   */
  async promiseBookmarksTree(aItemGuid = "", aOptions = {}) {
    let createItemInfoObject = async function (aRow, aIncludeParentGuid) {
      let item = {};
      let copyProps = (...props) => {
        for (let prop of props) {
          let val = aRow.getResultByName(prop);
          if (val !== null) {
            item[prop] = val;
          }
        }
      };
      copyProps("guid", "title", "index", "dateAdded", "lastModified");
      if (aIncludeParentGuid) {
        copyProps("parentGuid");
      }

      let itemId = aRow.getResultByName("id");
      if (aOptions.includeItemIds) {
        item.id = itemId;
      }

      let type = aRow.getResultByName("type");
      item.typeCode = type;
      if (type == Ci.nsINavBookmarksService.TYPE_BOOKMARK) {
        copyProps("charset", "tags", "iconUri");
      }

      switch (type) {
        case PlacesUtils.bookmarks.TYPE_BOOKMARK: {
          item.type = PlacesUtils.TYPE_X_MOZ_PLACE;
          // If this throws due to an invalid url, the item will be skipped.
          try {
            item.uri = new URL(aRow.getResultByName("url")).href;
          } catch (ex) {
            let error = new Error("Invalid bookmark URL");
            error.becauseInvalidURL = true;
            throw error;
          }
          // Keywords are cached, so this should be decently fast.
          let entry = await PlacesUtils.keywords.fetch({ url: item.uri });
          if (entry) {
            item.keyword = entry.keyword;
            item.postData = entry.postData;
          }
          break;
        }
        case PlacesUtils.bookmarks.TYPE_FOLDER:
          item.type = PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER;
          // Mark root folders.
          if (item.guid == PlacesUtils.bookmarks.rootGuid) {
            item.root = "placesRoot";
          } else if (item.guid == PlacesUtils.bookmarks.menuGuid) {
            item.root = "bookmarksMenuFolder";
          } else if (item.guid == PlacesUtils.bookmarks.unfiledGuid) {
            item.root = "unfiledBookmarksFolder";
          } else if (item.guid == PlacesUtils.bookmarks.toolbarGuid) {
            item.root = "toolbarFolder";
          } else if (item.guid == PlacesUtils.bookmarks.mobileGuid) {
            item.root = "mobileFolder";
          }
          break;
        case PlacesUtils.bookmarks.TYPE_SEPARATOR:
          item.type = PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR;
          break;
        default:
          console.error(`Unexpected bookmark type ${type}`);
          break;
      }
      return item;
    };

    const QUERY_STR = `/* do not warn (bug no): cannot use an index */
       WITH RECURSIVE
       descendants(fk, level, type, id, guid, parent, parentGuid, position,
                   title, dateAdded, lastModified) AS (
         SELECT b1.fk, 0, b1.type, b1.id, b1.guid, b1.parent,
                (SELECT guid FROM moz_bookmarks WHERE id = b1.parent),
                b1.position, b1.title, b1.dateAdded, b1.lastModified
         FROM moz_bookmarks b1 WHERE b1.guid=:item_guid
         UNION ALL
         SELECT b2.fk, level + 1, b2.type, b2.id, b2.guid, b2.parent,
                descendants.guid, b2.position, b2.title, b2.dateAdded,
                b2.lastModified
         FROM moz_bookmarks b2
         JOIN descendants ON b2.parent = descendants.id AND b2.id <> :tags_folder),
       tagged(place_id, tags) AS (
         SELECT b.fk, group_concat(p.title ORDER BY p.title)
         FROM moz_bookmarks b
         JOIN moz_bookmarks p ON p.id = b.parent
         JOIN moz_bookmarks g ON g.id = p.parent
         WHERE g.guid = '${PlacesUtils.bookmarks.tagsGuid}'
         GROUP BY b.fk
       )
       SELECT d.level, d.id, d.guid, d.parent, d.parentGuid, d.type,
              d.position AS [index], IFNULL(d.title, '') AS title, d.dateAdded,
              d.lastModified, h.url, (SELECT icon_url FROM moz_icons i
                      JOIN moz_icons_to_pages ON icon_id = i.id
                      JOIN moz_pages_w_icons pi ON page_id = pi.id
                      WHERE pi.page_url_hash = hash(h.url) AND pi.page_url = h.url
                      ORDER BY width DESC LIMIT 1) AS iconUri,
              (SELECT tags FROM tagged WHERE place_id = h.id) AS tags,
              (SELECT a.content FROM moz_annos a
               JOIN moz_anno_attributes n ON a.anno_attribute_id = n.id
               WHERE place_id = h.id AND n.name = :charset_anno
              ) AS charset
       FROM descendants d
       LEFT JOIN moz_bookmarks b3 ON b3.id = d.parent
       LEFT JOIN moz_places h ON h.id = d.fk
       ORDER BY d.level, d.parent, d.position`;

    if (!aItemGuid) {
      aItemGuid = this.bookmarks.rootGuid;
    }

    let hasExcludeItemsCallback = aOptions.hasOwnProperty(
      "excludeItemsCallback"
    );
    let excludedParents = new Set();
    let shouldExcludeItem = (aItem, aParentGuid) => {
      let exclude =
        excludedParents.has(aParentGuid) ||
        aOptions.excludeItemsCallback(aItem);
      if (exclude) {
        if (aItem.type == this.TYPE_X_MOZ_PLACE_CONTAINER) {
          excludedParents.add(aItem.guid);
        }
      }
      return exclude;
    };

    let rootItem = null;
    let parentsMap = new Map();
    let conn = await this.promiseDBConnection();
    let rows = await conn.executeCached(QUERY_STR, {
      tags_folder: PlacesUtils.tagsFolderId,
      charset_anno: PlacesUtils.CHARSET_ANNO,
      item_guid: aItemGuid,
    });
    let yieldCounter = 0;
    for (let row of rows) {
      let item;
      if (!rootItem) {
        try {
          // This is the first row.
          rootItem = item = await createItemInfoObject(row, true);
          Object.defineProperty(rootItem, "itemsCount", {
            value: 1,
            writable: true,
            enumerable: false,
            configurable: false,
          });
        } catch (ex) {
          console.error("Failed to fetch the data for the root item");
          throw ex;
        }
      } else {
        try {
          // Our query guarantees that we always visit parents ahead of their
          // children.
          item = await createItemInfoObject(row, false);
          let parentGuid = row.getResultByName("parentGuid");
          if (hasExcludeItemsCallback && shouldExcludeItem(item, parentGuid)) {
            continue;
          }

          let parentItem = parentsMap.get(parentGuid);
          if ("children" in parentItem) {
            parentItem.children.push(item);
          } else {
            parentItem.children = [item];
          }

          rootItem.itemsCount++;
        } catch (ex) {
          // This is a bogus child, report and skip it.
          console.error("Failed to fetch the data for an item ", ex);
          continue;
        }
      }

      if (item.type == this.TYPE_X_MOZ_PLACE_CONTAINER) {
        parentsMap.set(item.guid, item);
      }

      // With many bookmarks we end up stealing the CPU - even with yielding!
      // So we let everyone else have a go every few items (bug 1186714).
      if (++yieldCounter % 50 == 0) {
        await new Promise(resolve => {
          Services.tm.dispatchToMainThread(resolve);
        });
      }
    }

    return rootItem;
  },

  /**
   * Returns a generator that iterates over `array` and yields slices of no
   * more than `chunkLength` elements at a time.
   *
   * @param  {Array} array An array containing zero or more elements.
   * @param  {number} chunkLength The maximum number of elements in each chunk.
   * @yields {Array} A chunk of the array.
   * @throws if `chunkLength` is negative or not an integer.
   */
  *chunkArray(array, chunkLength) {
    if (chunkLength <= 0 || !Number.isInteger(chunkLength)) {
      throw new TypeError("Chunk length must be a positive integer");
    }
    if (!array.length) {
      return;
    }
    if (array.length <= chunkLength) {
      yield array;
      return;
    }
    let startIndex = 0;
    while (startIndex < array.length) {
      yield array.slice(startIndex, (startIndex += chunkLength));
    }
  },

  /**
   * Returns SQL placeholders to bind multiple values into an IN clause.
   * @param {Array|number} info
   *   Array or number of entries to create.
   * @param {string} [prefix]
   *   String prefix to add before the SQL param.
   * @param {string} [suffix]
   *   String suffix to add after the SQL param.
   */
  sqlBindPlaceholders(info, prefix = "", suffix = "") {
    let length = Array.isArray(info) ? info.length : info;
    return new Array(length).fill(prefix + "?" + suffix).join(",");
  },

  /**
   * Run some text through md5 and return the hash.
   * @param {string} data The string to hash.
   * @param {string} [format] Which format of the hash to return:
   *   - "base64" for ascii format.
   *   - "hex" for hex format.
   * @returns {string} hash of the input data in the required format.
   * @deprecated use sha256 instead.
   */
  md5(data, { format = "base64" } = {}) {
    let hasher = new lazy.CryptoHash("md5");
    // Convert the data to a byte array for hashing.
    data = new TextEncoder().encode(data);
    hasher.update(data, data.length);
    switch (format) {
      case "hex": {
        let hash = hasher.finish(false);
        return Array.from(hash, (c, i) =>
          hash.charCodeAt(i).toString(16).padStart(2, "0")
        ).join("");
      }
      case "base64":
      default:
        return hasher.finish(true);
    }
  },

  /**
   * Run some text through SHA256 and return the hash.
   * @param {string|nsIStringInputStream} data The data to hash.
   * @param {string} [format] Which format of the hash to return:
   *   - "base64" (default) for ascii format, not safe for URIs or file names.
   *   - "hex" for hex format.
   *   - "base64url" for ascii format safe to be used in file names (RFC 4648).
   *       You should normally use the "hex" format for file names, but if the
   *       user may manipulate the file, it would be annoying to have very long
   *       and unreadable file names, thus this provides a shorter alternative.
   *       Note padding "=" are untouched and may have to be encoded in URIs.
   * @returns {string} hash of the input data in the required format.
   */
  sha256(data, { format = "base64" } = {}) {
    let hasher = new lazy.CryptoHash("sha256");
    if (data instanceof Ci.nsIStringInputStream) {
      hasher.updateFromStream(data, -1);
    } else {
      // Convert the data string to a byte array for hashing.
      data = new TextEncoder().encode(data);
      hasher.update(data, data.length);
    }
    switch (format) {
      case "hex": {
        let hash = hasher.finish(false);
        return Array.from(hash, (c, i) =>
          hash.charCodeAt(i).toString(16).padStart(2, "0")
        ).join("");
      }
      case "base64url":
        return hasher.finish(true).replaceAll("+", "-").replaceAll("/", "_");
      case "base64":
      default:
        return hasher.finish(true);
    }
  },

  /**
   * Inserts a new place if one doesn't currently exist.
   *
   * This should only be used from an API that is connecting this new entry to
   * some additional foreign table. Otherwise this will just create an orphan
   * entry that could be expired at any time.
   *
   * @param db
   *        The database connection to use.
   * @param url
   *        A valid URL object.
   * @return {Promise} resolved when the operation is complete.
   */
  async maybeInsertPlace(db, url) {
    // The IGNORE conflict can trigger on `guid`.
    await db.executeCached(
      `INSERT OR IGNORE INTO moz_places (url, url_hash, rev_host, hidden, frecency, guid)
      VALUES (:url, hash(:url), :rev_host,
              (CASE WHEN :url BETWEEN 'place:' AND 'place:' || X'FFFF' THEN 1 ELSE 0 END),
              :frecency,
              IFNULL((SELECT guid FROM moz_places WHERE url_hash = hash(:url) AND url = :url),
                      GENERATE_GUID()))
      `,
      {
        url: url.href,
        rev_host: this.getReversedHost(url),
        frecency: url.protocol == "place:" ? 0 : -1,
      }
    );
  },

  /**
   * Tries to insert a set of new places if they don't exist yet.
   *
   * This should only be used from an API that is connecting this new entry to
   * some additional foreign table. Otherwise this will just create an orphan
   * entry that could be expired at any time.
   *
   * @param db
   *        The database to use
   * @param urls
   *        An array with all the url objects to insert.
   * @return {Promise} resolved when the operation is complete.
   */
  async maybeInsertManyPlaces(db, urls) {
    await db.executeCached(
      `INSERT OR IGNORE INTO moz_places (url, url_hash, rev_host, hidden, frecency, guid) VALUES
     (:url, hash(:url), :rev_host,
     (CASE WHEN :url BETWEEN 'place:' AND 'place:' || X'FFFF' THEN 1 ELSE 0 END),
     :frecency,
     IFNULL((SELECT guid FROM moz_places WHERE url_hash = hash(:url) AND url = :url), :maybeguid))`,
      urls.map(url => ({
        url: url.href,
        rev_host: this.getReversedHost(url),
        frecency: url.protocol == "place:" ? 0 : -1,
        maybeguid: this.history.makeGuid(),
      }))
    );
  },

  /**
   * Can be used to detect being in automation to allow specific code paths
   * that are normally disallowed.
   */
  get isInAutomation() {
    return (
      Cu.isInAutomation || Services.env.exists("XPCSHELL_TEST_PROFILE_DIR")
    );
  },

  /**
   * Creates a console logger.
   * Logging level can be controlled through the `places.loglevel` preference.
   *
   * @param {object} options
   * @param {string} [options.prefix] Prefix to use for the logged messages.
   * @returns {ConsoleInstance} The console logger.
   */
  getLogger({ prefix = "" } = {}) {
    if (!this._loggers) {
      this._loggers = new Map();
    }
    let logger = this._loggers.get(prefix);
    if (!logger) {
      logger = console.createInstance({
        prefix: `Places${prefix ? " - " + prefix : ""}`,
        maxLogLevelPref: "places.loglevel",
      });
      this._loggers.set(prefix, logger);
    }
    return logger;
  },
};

ChromeUtils.defineLazyGetter(PlacesUtils, "history", function () {
  let hs = Cc["@mozilla.org/browser/nav-history-service;1"].getService(
    Ci.nsINavHistoryService
  );
  return Object.freeze(
    new Proxy(hs, {
      get(target, name) {
        let property, object;
        if (name in target) {
          property = target[name];
          object = target;
        } else {
          property = lazy.History[name];
          object = lazy.History;
        }
        if (typeof property == "function") {
          return property.bind(object);
        }
        return property;
      },
      set(target, name, val) {
        // Forward to the XPCOM object, otherwise don't allow to set properties.
        if (name in target) {
          target[name] = val;
          return true;
        }
        // This will throw in strict mode.
        return false;
      },
    })
  );
});

XPCOMUtils.defineLazyServiceGetter(
  PlacesUtils,
  "favicons",
  "@mozilla.org/browser/favicon-service;1",
  "nsIFaviconService"
);

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "bmsvc",
  "@mozilla.org/browser/nav-bookmarks-service;1",
  "nsINavBookmarksService"
);
ChromeUtils.defineLazyGetter(PlacesUtils, "bookmarks", () => {
  return Object.freeze(
    new Proxy(lazy.Bookmarks, {
      get: (target, name) =>
        lazy.Bookmarks.hasOwnProperty(name)
          ? lazy.Bookmarks[name]
          : lazy.bmsvc[name],
    })
  );
});

XPCOMUtils.defineLazyServiceGetter(
  PlacesUtils,
  "tagging",
  "@mozilla.org/browser/tagging-service;1",
  "nsITaggingService"
);

ChromeUtils.defineLazyGetter(lazy, "bundle", function () {
  const PLACES_STRING_BUNDLE_URI = "chrome://places/locale/places.properties";
  return Services.strings.createBundle(PLACES_STRING_BUNDLE_URI);
});

// This is just used as a reasonably-random value for copy & paste / drag operations.
ChromeUtils.defineLazyGetter(PlacesUtils, "instanceId", () => {
  return PlacesUtils.history.makeGuid();
});

/**
 * Setup internal databases for closing properly during shutdown.
 *
 * 1. Places initiates shutdown.
 * 2. Before places can move to the step where it closes the low-level connection,
 *   we need to make sure that we have closed `conn`.
 * 3. Before we can close `conn`, we need to make sure that all external clients
 *   have stopped using `conn`.
 * 4. Before we can close Sqlite, we need to close `conn`.
 */
function setupDbForShutdown(conn, name) {
  try {
    let state = "0. Not started.";
    let promiseClosed = new Promise((resolve, reject) => {
      // The service initiates shutdown.
      // Before it can safely close its connection, we need to make sure
      // that we have closed the high-level connection.
      try {
        PlacesUtils.history.connectionShutdownClient.jsclient.addBlocker(
          `${name} closing as part of Places shutdown`,
          async function () {
            state = "1. Service has initiated shutdown";

            // At this stage, all external clients have finished using the
            // database. We just need to close the high-level connection.
            try {
              await conn.close();
              state = "2. Closed Sqlite.sys.mjs connection.";
              resolve();
            } catch (ex) {
              state = "2. Failed to closed Sqlite.sys.mjs connection: " + ex;
              reject(ex);
            }
          },
          () => state
        );
      } catch (ex) {
        // It's too late to block shutdown, just close the connection.
        conn.close();
        reject(ex);
      }
    }).catch(console.error);

    // Make sure that Sqlite.sys.mjs doesn't close until we are done
    // with the high-level connection.
    lazy.Sqlite.shutdown.addBlocker(
      `${name} must be closed before Sqlite.sys.mjs`,
      () => promiseClosed,
      () => state
    );
  } catch (ex) {
    // It's too late to block shutdown, just close the connection.
    conn.close();
    throw ex;
  }
}

ChromeUtils.defineLazyGetter(lazy, "gAsyncDBConnPromised", () =>
  lazy.Sqlite.cloneStorageConnection({
    connection: PlacesUtils.history.DBConnection,
    readOnly: true,
  })
    .then(conn => {
      setupDbForShutdown(conn, "PlacesUtils read-only connection");
      return conn;
    })
    .catch(console.error)
);

ChromeUtils.defineLazyGetter(lazy, "gAsyncDBWrapperPromised", () =>
  lazy.Sqlite.wrapStorageConnection({
    connection: PlacesUtils.history.DBConnection,
  })
    .then(conn => {
      setupDbForShutdown(conn, "PlacesUtils wrapped connection");
      return conn;
    })
    .catch(console.error)
);

var gAsyncDBLargeCacheConnDeferred = Promise.withResolvers();
ChromeUtils.defineLazyGetter(lazy, "gAsyncDBLargeCacheConnPromised", () =>
  lazy.Sqlite.cloneStorageConnection({
    connection: PlacesUtils.history.DBConnection,
    readOnly: true,
  })
    .then(async conn => {
      setupDbForShutdown(conn, "PlacesUtils large cache read-only connection");
      // Components like the urlbar often fallback to a table scan due to lack
      // of full text indices.  A larger cache helps reducing IO and improves
      // performance. This value is expected to be larger than the default
      // mozStorage value defined as MAX_CACHE_SIZE_BYTES in
      // storage/mozStorageConnection.cpp.
      await conn.execute("PRAGMA cache_size = -6144"); // 6MiB
      // These should be kept in sync with nsPlacesTables.h.
      await conn.execute(`
        CREATE TEMP TABLE IF NOT EXISTS moz_openpages_temp (
          url TEXT,
          userContextId INTEGER,
          open_count INTEGER,
          PRIMARY KEY (url, userContextId)
        )`);
      await conn.execute(`
        CREATE TEMP TRIGGER IF NOT EXISTS moz_openpages_temp_afterupdate_trigger
        AFTER UPDATE OF open_count ON moz_openpages_temp FOR EACH ROW
        WHEN NEW.open_count = 0
        BEGIN
          DELETE FROM moz_openpages_temp
          WHERE url = NEW.url
            AND userContextId = NEW.userContextId;
        END`);
      gAsyncDBLargeCacheConnDeferred.resolve(conn);
      return conn;
    })
    .catch(console.error)
);

/**
 * The metadata API allows consumers to store simple key-value metadata in
 * Places. Keys are strings, values can be any type that SQLite supports:
 * numbers (integers and doubles), Booleans, strings, and blobs. Values are
 * cached in memory for faster lookups.
 *
 * Since some consumers set metadata as part of an existing operation or active
 * transaction, the API also exposes a `*withConnection` variant for each
 * method that takes an open database connection.
 */
PlacesUtils.metadata = {
  cache: new Map(),
  jsonPrefix: "data:application/json;base64,",

  /**
   * Returns the value associated with a metadata key.
   *
   * @param  {String} key
   *         The metadata key to look up.
   * @param  {String|Object|Array} defaultValue
   *         Optional. The default value to return if the value is not present,
   *         or cannot be parsed.
   * @resolves {*}
   *         The value associated with the key, or the defaultValue if there is one.
   * @rejects
   *         Rejected if the value is not found or it cannot be parsed
   *         and there is no defaultValue.
   */
  get(key, defaultValue) {
    return PlacesUtils.withConnectionWrapper("PlacesUtils.metadata.get", db =>
      this.getWithConnection(db, key, defaultValue)
    );
  },

  /**
   * Sets the value for a metadata key.
   *
   * @param {String} key
   *        The metadata key to update.
   * @param {*}
   *        The value to associate with the key.
   */
  set(key, value) {
    return PlacesUtils.withConnectionWrapper("PlacesUtils.metadata.set", db =>
      this.setWithConnection(db, new Map([[key, value]]))
    );
  },

  /**
   * Sets the value for multiple keys.
   *
   * @param {Map} pairs
   *        The metadata keys to update, with their value.
   */
  setMany(pairs) {
    return PlacesUtils.withConnectionWrapper("PlacesUtils.metadata.set", db =>
      this.setWithConnection(db, pairs)
    );
  },

  /**
   * Removes the values for the given metadata keys.
   *
   * @param {String...}
   *        One or more metadata keys to remove.
   */
  delete(...keys) {
    return PlacesUtils.withConnectionWrapper(
      "PlacesUtils.metadata.delete",
      db => this.deleteWithConnection(db, ...keys)
    );
  },

  async getWithConnection(db, key, defaultValue) {
    key = this.canonicalizeKey(key);
    if (this.cache.has(key)) {
      return this.cache.get(key);
    }
    let rows = await db.executeCached(
      `
      SELECT value FROM moz_meta WHERE key = :key`,
      { key }
    );
    let value = null;
    if (rows.length) {
      let row = rows[0];
      let rawValue = row.getResultByName("value");
      // Convert blobs back to `Uint8Array`s.
      if (row.getTypeOfIndex(0) == row.VALUE_TYPE_BLOB) {
        value = new Uint8Array(rawValue);
      } else if (
        typeof rawValue == "string" &&
        rawValue.startsWith(this.jsonPrefix)
      ) {
        try {
          value = JSON.parse(
            this._base64Decode(rawValue.substr(this.jsonPrefix.length))
          );
        } catch (ex) {
          if (defaultValue !== undefined) {
            // We must create a new array in the local scope to avoid a memory
            // leak due to the array global object.
            value = Cu.cloneInto(defaultValue, {});
          } else {
            throw ex;
          }
        }
      } else {
        value = rawValue;
      }
    } else if (defaultValue !== undefined) {
      // We must create a new array in the local scope to avoid a memory leak due
      // to the array global object.
      value = Cu.cloneInto(defaultValue, {});
    } else {
      throw new Error(`No data stored for key ${key}`);
    }
    this.cache.set(key, value);
    return value;
  },

  async setWithConnection(db, pairs) {
    let entriesToSet = [];
    let keysToDelete = Array.from(pairs.entries())
      .filter(([key, value]) => {
        if (value !== null) {
          entriesToSet.push({ key: this.canonicalizeKey(key), value });
          return false;
        }
        return true;
      })
      .map(([key]) => key);
    if (keysToDelete.length) {
      await this.deleteWithConnection(db, ...keysToDelete);
      if (keysToDelete.length == pairs.size) {
        return;
      }
    }

    // Generate key{i}, value{i} pairs for the SQL bindings.
    let params = entriesToSet.reduce((accumulator, { key, value }, i) => {
      accumulator[`key${i}`] = key;
      // Convert Objects to base64 JSON urls.
      accumulator[`value${i}`] =
        typeof value == "object" &&
        ChromeUtils.getClassName(value) != "Uint8Array"
          ? this.jsonPrefix + this._base64Encode(JSON.stringify(value))
          : value;
      return accumulator;
    }, {});
    await db.executeCached(
      "REPLACE INTO moz_meta (key, value) VALUES " +
        entriesToSet.map((e, i) => `(:key${i}, :value${i})`).join(),
      params
    );

    // Update the cache.
    entriesToSet.forEach(({ key, value }) => {
      this.cache.set(key, value);
    });
  },

  async deleteWithConnection(db, ...keys) {
    keys = keys.map(this.canonicalizeKey);
    if (!keys.length) {
      return;
    }
    await db.execute(
      `
      DELETE FROM moz_meta
      WHERE key IN (${new Array(keys.length).fill("?").join(",")})`,
      keys
    );
    for (let key of keys) {
      this.cache.delete(key);
    }
  },

  canonicalizeKey(key) {
    if (typeof key != "string" || !/^[a-zA-Z0-9\/_]+$/.test(key)) {
      throw new TypeError("Invalid metadata key: " + key);
    }
    return key.toLowerCase();
  },

  _base64Encode(str) {
    return ChromeUtils.base64URLEncode(new TextEncoder().encode(str), {
      pad: true,
    });
  },

  _base64Decode(str) {
    return new TextDecoder("utf-8").decode(
      ChromeUtils.base64URLDecode(str, { padding: "require" })
    );
  },
};

/**
 * Keywords management API.
 * Sooner or later these keywords will merge with search aliases, this is an
 * interim API that should then be replaced by a unified one.
 * Keywords are associated with URLs and can have POST data.
 * The relations between URLs and keywords are the following:
 *  - 1 keyword can only point to 1 URL
 *  - 1 URL can have multiple keywords, iff they differ by POST data (included the empty one).
 */
PlacesUtils.keywords = {
  /**
   * Fetches a keyword entry based on keyword or URL.
   *
   * @param keywordOrEntry
   *        Either the keyword to fetch or an entry providing keyword
   *        or url property to find keywords for.  If both properties are set,
   *        this returns their intersection.
   * @param onResult [optional]
   *        Callback invoked for each found entry.
   * @return {Promise}
   * @resolves to an object in the form: { keyword, url, postData },
   *           or null if a keyword entry was not found.
   */
  fetch(keywordOrEntry, onResult = null) {
    if (typeof keywordOrEntry == "string") {
      keywordOrEntry = { keyword: keywordOrEntry };
    }

    if (
      keywordOrEntry === null ||
      typeof keywordOrEntry != "object" ||
      ("keyword" in keywordOrEntry && typeof keywordOrEntry.keyword != "string")
    ) {
      throw new Error("Invalid keyword");
    }

    let hasKeyword = "keyword" in keywordOrEntry;
    let hasUrl = "url" in keywordOrEntry;

    if (!hasKeyword && !hasUrl) {
      throw new Error("At least keyword or url must be provided");
    }
    if (onResult && typeof onResult != "function") {
      throw new Error("onResult callback must be a valid function");
    }

    if (hasUrl) {
      try {
        keywordOrEntry.url = BOOKMARK_VALIDATORS.url(keywordOrEntry.url);
      } catch (ex) {
        throw new Error(keywordOrEntry.url + " is not a valid URL");
      }
    }
    if (hasKeyword) {
      keywordOrEntry.keyword = keywordOrEntry.keyword.trim().toLowerCase();
    }

    let safeOnResult = entry => {
      if (onResult) {
        try {
          onResult(entry);
        } catch (ex) {
          console.error(ex);
        }
      }
    };

    return promiseKeywordsCache().then(cache => {
      let entries = [];
      if (hasKeyword) {
        let entry = cache.get(keywordOrEntry.keyword);
        if (entry) {
          entries.push(entry);
        }
      }
      if (hasUrl) {
        for (let entry of cache.values()) {
          if (entry.url.href == keywordOrEntry.url.href) {
            entries.push(entry);
          }
        }
      }

      entries = entries.filter(e => {
        return (
          (!hasUrl || e.url.href == keywordOrEntry.url.href) &&
          (!hasKeyword || e.keyword == keywordOrEntry.keyword)
        );
      });

      entries.forEach(safeOnResult);
      return entries.length ? entries[0] : null;
    });
  },

  /**
   * Adds a new keyword and postData for the given URL.
   *
   * @param keywordEntry
   *        An object describing the keyword to insert, in the form:
   *        {
   *          keyword: non-empty string,
   *          url: URL or href to associate to the keyword,
   *          postData: optional POST data to associate to the keyword
   *          source: The change source, forwarded to all bookmark observers.
   *            Defaults to nsINavBookmarksService::SOURCE_DEFAULT.
   *        }
   * @note Do not define a postData property if there isn't any POST data.
   *       Defining an empty string for POST data is equivalent to not having it.
   * @resolves when the addition is complete.
   */
  insert(keywordEntry) {
    if (!keywordEntry || typeof keywordEntry != "object") {
      throw new Error("Input should be a valid object");
    }

    if (
      !("keyword" in keywordEntry) ||
      !keywordEntry.keyword ||
      typeof keywordEntry.keyword != "string"
    ) {
      throw new Error("Invalid keyword");
    }
    if (
      "postData" in keywordEntry &&
      keywordEntry.postData &&
      typeof keywordEntry.postData != "string"
    ) {
      throw new Error("Invalid POST data");
    }
    if (!("url" in keywordEntry)) {
      throw new Error("undefined is not a valid URL");
    }

    if (!("source" in keywordEntry)) {
      keywordEntry.source = PlacesUtils.bookmarks.SOURCES.DEFAULT;
    }
    let { keyword, url, source } = keywordEntry;
    keyword = keyword.trim().toLowerCase();
    let postData = keywordEntry.postData || "";
    // This also checks href for validity
    try {
      url = BOOKMARK_VALIDATORS.url(url);
    } catch (ex) {
      throw new Error(url + " is not a valid URL");
    }

    return PlacesUtils.withConnectionWrapper(
      "PlacesUtils.keywords.insert",
      async db => {
        let cache = await promiseKeywordsCache();

        // Trying to set the same keyword is a no-op.
        let oldEntry = cache.get(keyword);
        if (
          oldEntry &&
          oldEntry.url.href == url.href &&
          (oldEntry.postData || "") == postData
        ) {
          return;
        }

        // A keyword can only be associated to a single page.
        // If another page is using the new keyword, we must update the keyword
        // entry.
        // Note we cannot use INSERT OR REPLACE cause it wouldn't invoke the delete
        // trigger.
        if (oldEntry) {
          await db.executeCached(
            `UPDATE moz_keywords
             SET place_id = (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url),
                 post_data = :post_data
             WHERE keyword = :keyword
            `,
            { url: url.href, keyword, post_data: postData }
          );
          await notifyKeywordChange(oldEntry.url.href, "", source);
        } else {
          // An entry for the given page could be missing, in such a case we need to
          // create it.  The IGNORE conflict can trigger on `guid`.
          await db.executeTransaction(async () => {
            await PlacesUtils.maybeInsertPlace(db, url);

            // A new keyword could be assigned to an url that already has one,
            // then we must replace the old keyword with the new one.
            let oldKeywords = [];
            for (let entry of cache.values()) {
              if (
                entry.url.href == url.href &&
                (entry.postData || "") == postData
              ) {
                oldKeywords.push(entry.keyword);
              }
            }
            if (oldKeywords.length) {
              for (let oldKeyword of oldKeywords) {
                await db.executeCached(
                  `DELETE FROM moz_keywords WHERE keyword = :oldKeyword`,
                  { oldKeyword }
                );
                cache.delete(oldKeyword);
              }
            }

            await db.executeCached(
              `INSERT INTO moz_keywords (keyword, place_id, post_data)
               VALUES (:keyword, (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url), :post_data)
              `,
              { url: url.href, keyword, post_data: postData }
            );

            await lazy.PlacesSyncUtils.bookmarks.addSyncChangesForBookmarksWithURL(
              db,
              url,
              lazy.PlacesSyncUtils.bookmarks.determineSyncChangeDelta(source)
            );
          });
        }

        cache.set(keyword, { keyword, url, postData: postData || null });

        // In any case, notify about the new keyword.
        await notifyKeywordChange(url.href, keyword, source);
      }
    );
  },

  /**
   * Removes a keyword.
   *
   * @param keyword
   *        The keyword to remove.
   * @return {Promise}
   * @resolves when the removal is complete.
   */
  remove(keywordOrEntry) {
    if (typeof keywordOrEntry == "string") {
      keywordOrEntry = {
        keyword: keywordOrEntry,
        source: Ci.nsINavBookmarksService.SOURCE_DEFAULT,
      };
    }

    if (
      keywordOrEntry === null ||
      typeof keywordOrEntry != "object" ||
      !keywordOrEntry.keyword ||
      typeof keywordOrEntry.keyword != "string"
    ) {
      throw new Error("Invalid keyword");
    }

    let { keyword, source = Ci.nsINavBookmarksService.SOURCE_DEFAULT } =
      keywordOrEntry;
    keyword = keywordOrEntry.keyword.trim().toLowerCase();
    return PlacesUtils.withConnectionWrapper(
      "PlacesUtils.keywords.remove",
      async db => {
        let cache = await promiseKeywordsCache();
        if (!cache.has(keyword)) {
          return;
        }
        let { url } = cache.get(keyword);
        cache.delete(keyword);

        await db.executeTransaction(async function () {
          await db.execute(
            `DELETE FROM moz_keywords WHERE keyword = :keyword`,
            { keyword }
          );

          await lazy.PlacesSyncUtils.bookmarks.addSyncChangesForBookmarksWithURL(
            db,
            url,
            lazy.PlacesSyncUtils.bookmarks.determineSyncChangeDelta(source)
          );
        });

        // Notify bookmarks about the removal.
        await notifyKeywordChange(url.href, "", source);
      }
    );
  },

  /**
   * Moves all (keyword, POST data) pairs from one URL to another, and fires
   * observer notifications for all affected bookmarks. If the destination URL
   * already has keywords, they will be removed and replaced with the source
   * URL's keywords.
   *
   * @param oldURL
   *        The source URL.
   * @param newURL
   *        The destination URL.
   * @param source
   *        The change source, forwarded to all bookmark observers.
   * @return {Promise}
   * @resolves when all keywords have been moved to the destination URL.
   */
  reassign(oldURL, newURL, source = PlacesUtils.bookmarks.SOURCES.DEFAULT) {
    try {
      oldURL = BOOKMARK_VALIDATORS.url(oldURL);
    } catch (ex) {
      throw new Error(oldURL + " is not a valid source URL");
    }
    try {
      newURL = BOOKMARK_VALIDATORS.url(newURL);
    } catch (ex) {
      throw new Error(oldURL + " is not a valid destination URL");
    }
    return PlacesUtils.withConnectionWrapper(
      "PlacesUtils.keywords.reassign",
      async function (db) {
        let keywordsToReassign = [];
        let keywordsToRemove = [];
        let cache = await promiseKeywordsCache();
        for (let [keyword, entry] of cache) {
          if (entry.url.href == oldURL.href) {
            keywordsToReassign.push(keyword);
          }
          if (entry.url.href == newURL.href) {
            keywordsToRemove.push(keyword);
          }
        }
        if (!keywordsToReassign.length) {
          return;
        }

        await db.executeTransaction(async function () {
          // Remove existing keywords from the new URL.
          await db.executeCached(
            `DELETE FROM moz_keywords WHERE keyword = :keyword`,
            keywordsToRemove.map(keyword => ({ keyword }))
          );

          // Move keywords from the old URL to the new URL.
          await db.executeCached(
            `
          UPDATE moz_keywords SET
            place_id = (SELECT id FROM moz_places
                        WHERE url_hash = hash(:newURL) AND
                              url = :newURL)
          WHERE place_id = (SELECT id FROM moz_places
                            WHERE url_hash = hash(:oldURL) AND
                                  url = :oldURL)`,
            { newURL: newURL.href, oldURL: oldURL.href }
          );
        });
        for (let keyword of keywordsToReassign) {
          let entry = cache.get(keyword);
          entry.url = newURL;
        }
        for (let keyword of keywordsToRemove) {
          cache.delete(keyword);
        }

        if (keywordsToReassign.length) {
          // If we moved any keywords, notify that we removed all keywords from
          // the old and new URLs, then notify for each moved keyword.
          await notifyKeywordChange(oldURL, "", source);
          await notifyKeywordChange(newURL, "", source);
          for (let keyword of keywordsToReassign) {
            await notifyKeywordChange(newURL, keyword, source);
          }
        } else if (keywordsToRemove.length) {
          // If the old URL didn't have any keywords, but the new URL did, just
          // notify that we removed all keywords from the new URL.
          await notifyKeywordChange(oldURL, "", source);
        }
      }
    );
  },

  /**
   * Removes all orphaned keywords from the given URLs. Orphaned keywords are
   * associated with URLs that are no longer bookmarked. If a given URL is still
   * bookmarked, its keywords will not be removed.
   *
   * @param urls
   *        A list of URLs to check for orphaned keywords.
   * @return {Promise}
   * @resolves when all keywords have been removed from URLs that are no longer
   *           bookmarked.
   */
  removeFromURLsIfNotBookmarked(urls) {
    let hrefs = new Set();
    for (let url of urls) {
      try {
        url = BOOKMARK_VALIDATORS.url(url);
      } catch (ex) {
        throw new Error(url + " is not a valid URL");
      }
      hrefs.add(url.href);
    }
    return PlacesUtils.withConnectionWrapper(
      "PlacesUtils.keywords.removeFromURLsIfNotBookmarked",
      async function (db) {
        let keywordsByHref = new Map();
        let cache = await promiseKeywordsCache();
        for (let [keyword, entry] of cache) {
          let href = entry.url.href;
          if (!hrefs.has(href)) {
            continue;
          }
          if (!keywordsByHref.has(href)) {
            keywordsByHref.set(href, [keyword]);
            continue;
          }
          let existingKeywords = keywordsByHref.get(href);
          existingKeywords.push(keyword);
        }
        if (!keywordsByHref.size) {
          return;
        }

        let placeInfosToRemove = [];
        let rows = await db.execute(
          `
          SELECT h.id, h.url
          FROM moz_places h
          JOIN moz_keywords k ON k.place_id = h.id
          GROUP BY h.id
          HAVING h.foreign_count = count(*) +
            (SELECT count(*)
             FROM moz_bookmarks b
             JOIN moz_bookmarks p ON b.parent = p.id
             WHERE p.parent = :tags_root AND b.fk = h.id)
          `,
          { tags_root: PlacesUtils.tagsFolderId }
        );
        for (let row of rows) {
          placeInfosToRemove.push({
            placeId: row.getResultByName("id"),
            href: row.getResultByName("url"),
          });
        }
        if (!placeInfosToRemove.length) {
          return;
        }

        await db.execute(
          `DELETE FROM moz_keywords WHERE place_id IN (${Array.from(
            placeInfosToRemove.map(info => info.placeId)
          ).join()})`
        );
        for (let { href } of placeInfosToRemove) {
          let keywords = keywordsByHref.get(href);
          for (let keyword of keywords) {
            cache.delete(keyword);
          }
        }
      }
    );
  },

  /**
   * Removes all keywords from all URLs.
   *
   * @return {Promise}
   * @resolves when all keywords have been removed.
   */
  eraseEverything() {
    return PlacesUtils.withConnectionWrapper(
      "PlacesUtils.keywords.eraseEverything",
      async function (db) {
        let cache = await promiseKeywordsCache();
        if (!cache.size) {
          return;
        }
        await db.executeCached(`DELETE FROM moz_keywords`);
        cache.clear();
      }
    );
  },

  /**
   * Invalidates the keywords cache, leaving all existing keywords in place.
   * The cache will be repopulated on the next `PlacesUtils.keywords.*` call.
   *
   * @return {Promise}
   * @resolves when the cache has been cleared.
   */
  invalidateCachedKeywords() {
    gKeywordsCachePromise = gKeywordsCachePromise.then(_ => null);
    this.ensureCacheInitialized();
    return gKeywordsCachePromise;
  },

  /**
   * Ensures the keywords cache is initialized.
   */
  async ensureCacheInitialized() {
    this._cache = await promiseKeywordsCache();
  },

  /**
   * Checks from the cache if a given word is a bookmark keyword.
   * We must make sure the cache is populated, and await ensureCacheInitialized()
   * before calling this function.
   *
   * @return {Boolean} Whether the given word is a keyword.
   */
  isKeywordFromCache(keyword) {
    return this._cache?.has(keyword);
  },
};

var gKeywordsCachePromise = Promise.resolve();

function promiseKeywordsCache() {
  let promise = gKeywordsCachePromise.then(function (cache) {
    if (cache) {
      return cache;
    }
    return PlacesUtils.withConnectionWrapper(
      "PlacesUtils: promiseKeywordsCache",
      async db => {
        let cache = new Map();
        let rows = await db.execute(
          `SELECT keyword, url, post_data
           FROM moz_keywords k
           JOIN moz_places h ON h.id = k.place_id
          `
        );
        let brokenKeywords = [];
        for (let row of rows) {
          let keyword = row.getResultByName("keyword");
          try {
            let entry = {
              keyword,
              url: new URL(row.getResultByName("url")),
              postData: row.getResultByName("post_data") || null,
            };
            cache.set(keyword, entry);
          } catch (ex) {
            // The url is invalid, don't load the keyword and remove it, or it
            // would break the whole keywords API.
            brokenKeywords.push(keyword);
          }
        }
        if (brokenKeywords.length) {
          await db.execute(
            `DELETE FROM moz_keywords
             WHERE keyword IN (${brokenKeywords.map(JSON.stringify).join(",")})
            `
          );
        }
        return cache;
      }
    );
  });
  gKeywordsCachePromise = promise.catch(_ => {});
  return promise;
}
PK
!<G�d���"modules/PolicySearchEngine.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint no-shadow: error, mozilla/no-aArgs: error */

import { SearchEngine } from "resource://gre/modules/SearchEngine.sys.mjs";

/**
 * PolicySearchEngine represents a search engine defined by an enterprise
 * policy.
 */
export class PolicySearchEngine extends SearchEngine {
  /**
   * Creates a PolicySearchEngine.
   *
   * @param {object} options
   *   The options for this search engine.
   * @param {object} options.details
   *   An object that matches the `SearchEngines` policy schema.
   * @param {object} [options.settings]
   *   The saved settings for the user.
   *
   * @see browser/components/enterprisepolicies/schemas/policies-schema.json
   */
  constructor(options = {}) {
    let id = "policy-" + options.details.Name;

    super({
      loadPath: "[policy]",
      id,
    });

    // Map the data to look like a WebExtension manifest, as that is what
    // _initWithDetails requires.
    let details = {
      description: options.details.Description,
      iconURL: options.details.IconURL ? options.details.IconURL.href : null,
      name: options.details.Name,
      // If the encoding is not specified or is falsy, we will fall back to
      // the default encoding.
      encoding: options.details.Encoding,
      search_url: encodeURI(options.details.URLTemplate),
      keyword: options.details.Alias,
      search_url_post_params:
        options.details.Method == "POST" ? options.details.PostData : undefined,
      suggest_url: options.details.SuggestURLTemplate,
    };
    this._initWithDetails(details);

    this._loadSettings(options.settings);
  }

  /**
   * Whether or not this engine is an in-memory only search engine.
   * These engines are typically application provided or policy engines,
   * where they are loaded every time on SearchService initialization
   * using the policy JSON or the extension manifest. Minimal details of the
   * in-memory engines are saved to disk, but they are never loaded
   * from the user's saved settings file.
   *
   * @returns {boolean}
   *   All policy engines are in-memory, so this always returns true.
   */
  get inMemory() {
    return true;
  }

  /**
   * Returns the appropriate identifier to use for telemetry.
   *
   * @returns {string}
   */
  get telemetryId() {
    return `other-${this.name}`;
  }

  /**
   * Creates a JavaScript object that represents this engine.
   *
   * @returns {object}
   *   An object suitable for serialization as JSON.
   */
  toJSON() {
    // For policy engines, we load them at every startup and we don't want to
    // store all their data in the settings file so just return the relevant
    // metadata.
    let json = super.toJSON();

    // We only want to return a sub-set of fields, as the details for this engine
    // are loaded on each startup from the enterprise policies.
    return {
      id: json.id,
      _name: json._name,
      // Load path is included so that we know this is an enterprise engine.
      _loadPath: json._loadPath,
      _metaData: json._metaData,
    };
  }
}
PK
!<Υu��"modules/PopupNotifications.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { PrivateBrowsingUtils } from "resource://gre/modules/PrivateBrowsingUtils.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const NOTIFICATION_EVENT_DISMISSED = "dismissed";
const NOTIFICATION_EVENT_REMOVED = "removed";
const NOTIFICATION_EVENT_SHOWING = "showing";
const NOTIFICATION_EVENT_SHOWN = "shown";
const NOTIFICATION_EVENT_SWAPPING = "swapping";

const ICON_SELECTOR = ".notification-anchor-icon";
const ICON_ATTRIBUTE_SHOWING = "showing";
const ICON_ANCHOR_ATTRIBUTE = "popupnotificationanchor";

const PREF_SECURITY_DELAY = "security.notification_enable_delay";
const FULLSCREEN_TRANSITION_TIME_SHOWN_OFFSET_MS = 2000;

// Enumerated values for the POPUP_NOTIFICATION_STATS telemetry histogram.
const TELEMETRY_STAT_OFFERED = 0;
const TELEMETRY_STAT_ACTION_1 = 1;
const TELEMETRY_STAT_ACTION_2 = 2;
// const TELEMETRY_STAT_ACTION_3 = 3;
const TELEMETRY_STAT_ACTION_LAST = 4;
// const TELEMETRY_STAT_DISMISSAL_CLICK_ELSEWHERE = 5;
const TELEMETRY_STAT_REMOVAL_LEAVE_PAGE = 6;
// const TELEMETRY_STAT_DISMISSAL_CLOSE_BUTTON = 7;
const TELEMETRY_STAT_OPEN_SUBMENU = 10;
const TELEMETRY_STAT_LEARN_MORE = 11;

const TELEMETRY_STAT_REOPENED_OFFSET = 20;
const lazy = {};

XPCOMUtils.defineLazyPreferenceGetter(lazy, "buttonDelay", PREF_SECURITY_DELAY);

var popupNotificationsMap = new WeakMap();
var gNotificationParents = new WeakMap();

function getAnchorFromBrowser(aBrowser, aAnchorID) {
  let attrPrefix = aAnchorID ? aAnchorID.replace("notification-icon", "") : "";
  let anchor =
    aBrowser.getAttribute(attrPrefix + ICON_ANCHOR_ATTRIBUTE) ||
    aBrowser[attrPrefix + ICON_ANCHOR_ATTRIBUTE] ||
    aBrowser.getAttribute(ICON_ANCHOR_ATTRIBUTE) ||
    aBrowser[ICON_ANCHOR_ATTRIBUTE];
  if (anchor) {
    if (ChromeUtils.getClassName(anchor) == "XULElement") {
      return anchor;
    }
    return aBrowser.ownerDocument.getElementById(anchor);
  }
  return null;
}

/**
 * Given a DOM node inside a <popupnotification>, return the parent <popupnotification>.
 */
function getNotificationFromElement(aElement) {
  return aElement.closest("popupnotification");
}

/**
 * Notification object describes a single popup notification.
 *
 * @see PopupNotifications.show()
 */
function Notification(
  id,
  message,
  anchorID,
  mainAction,
  secondaryActions,
  browser,
  owner,
  options
) {
  this.id = id;
  this.message = message;
  this.anchorID = anchorID;
  this.mainAction = mainAction;
  this.secondaryActions = secondaryActions || [];
  this.browser = browser;
  this.owner = owner;
  this.options = options || {};

  this._dismissed = false;
  // Will become a boolean when manually toggled by the user.
  this._checkboxChecked = null;
  this.wasDismissed = false;
  this.recordedTelemetryStats = new Set();
  this.isPrivate = PrivateBrowsingUtils.isWindowPrivate(
    this.browser.ownerGlobal
  );
  this.timeCreated = Cu.now();
}

Notification.prototype = {
  id: null,
  message: null,
  anchorID: null,
  mainAction: null,
  secondaryActions: null,
  browser: null,
  owner: null,
  options: null,
  timeShown: null,

  /**
   * Indicates whether the notification is currently dismissed.
   */
  set dismissed(value) {
    this._dismissed = value;
    if (value) {
      // Keep the dismissal into account when recording telemetry.
      this.wasDismissed = true;
    }
  },
  get dismissed() {
    return this._dismissed;
  },

  /**
   * Removes the notification and updates the popup accordingly if needed.
   */
  remove: function Notification_remove() {
    this.owner.remove(this);
  },

  get anchorElement() {
    let iconBox = this.owner.iconBox;

    let anchorElement = getAnchorFromBrowser(this.browser, this.anchorID);
    if (!iconBox) {
      return anchorElement;
    }

    if (!anchorElement && this.anchorID) {
      anchorElement = iconBox.querySelector("#" + this.anchorID);
    }

    // Use a default anchor icon if it's available
    if (!anchorElement) {
      anchorElement =
        iconBox.querySelector("#default-notification-icon") || iconBox;
    }

    return anchorElement;
  },

  reshow() {
    this.owner._reshowNotifications(this.anchorElement, this.browser);
  },

  /**
   * Adds a value to the specified histogram, that must be keyed by ID.
   */
  _recordTelemetry(histogramId, value) {
    if (this.isPrivate && !this.options.recordTelemetryInPrivateBrowsing) {
      // The reason why we don't record telemetry in private windows is because
      // the available actions can be different from regular mode. The main
      // difference is that all of the persistent permission options like
      // "Always remember" aren't there, so they really need to be handled
      // separately to avoid skewing results. For notifications with the same
      // choices, there would be no reason not to record in private windows as
      // well, but it's just simpler to use the same check for everything.
      return;
    }
    let histogram = Services.telemetry.getKeyedHistogramById(histogramId);
    histogram.add("(all)", value);
    histogram.add(this.id, value);
  },

  /**
   * Adds an enumerated value to the POPUP_NOTIFICATION_STATS histogram,
   * ensuring that it is recorded at most once for each distinct Notification.
   *
   * Statistics for reopened notifications are recorded in separate buckets.
   *
   * @param value
   *        One of the TELEMETRY_STAT_ constants.
   */
  _recordTelemetryStat(value) {
    if (this.wasDismissed) {
      value += TELEMETRY_STAT_REOPENED_OFFSET;
    }
    if (!this.recordedTelemetryStats.has(value)) {
      this.recordedTelemetryStats.add(value);
      this._recordTelemetry("POPUP_NOTIFICATION_STATS", value);
    }
  },
};

/**
 * The PopupNotifications object manages popup notifications for a given browser
 * window.
 * @param tabbrowser
 *        window's TabBrowser. Used to observe tab switching events and
 *        for determining the active browser element.
 * @param panel
 *        The <xul:panel/> element to use for notifications. The panel is
 *        populated with <popupnotification> children and displayed it as
 *        needed.
 * @param iconBox
 *        Reference to a container element that should be hidden or
 *        unhidden when notifications are hidden or shown. It should be the
 *        parent of anchor elements whose IDs are passed to show().
 *        It is used as a fallback popup anchor if notifications specify
 *        invalid or non-existent anchor IDs.
 * @param options
 *        An optional object with the following optional properties:
 *        {
 *          shouldSuppress:
 *            If this function returns true, then all notifications are
 *            suppressed for this window. This state is checked on construction
 *            and when the "anchorVisibilityChange" method is called.
 *          getVisibleAnchorElement(anchorElement):
 *            A function which takes an anchor element as input and should return
 *            either the anchor if it's visible, a fallback anchor element, or if
 *            no fallback exists, a null element.
 *        }
 */
export function PopupNotifications(tabbrowser, panel, iconBox, options = {}) {
  if (!tabbrowser) {
    throw new Error("Invalid tabbrowser");
  }
  if (iconBox && ChromeUtils.getClassName(iconBox) != "XULElement") {
    throw new Error("Invalid iconBox");
  }
  if (ChromeUtils.getClassName(panel) != "XULPopupElement") {
    throw new Error("Invalid panel");
  }

  this._shouldSuppress = options.shouldSuppress || (() => false);
  this._suppress = this._shouldSuppress();

  this._getVisibleAnchorElement = options.getVisibleAnchorElement;

  this.window = tabbrowser.ownerGlobal;
  this.panel = panel;
  this.tabbrowser = tabbrowser;
  this.iconBox = iconBox;

  // panel itself has a listener in the bubble phase and this listener
  // needs to be called after that, so use bubble phase here.
  this.panel.addEventListener("popuphidden", this);
  this.panel.addEventListener("popuppositioned", this);
  this.panel.classList.add("popup-notification-panel", "panel-no-padding");

  // This listener will be attached to the chrome window whenever a notification
  // is showing, to allow the user to dismiss notifications using the escape key.
  this._handleWindowKeyPress = aEvent => {
    if (aEvent.keyCode != aEvent.DOM_VK_ESCAPE) {
      return;
    }

    // Esc key cancels the topmost notification, if there is one.
    let notification = this.panel.firstElementChild;
    if (!notification) {
      return;
    }

    let doc = this.window.document;
    let focusedElement = Services.focus.focusedElement;

    // If the chrome window has a focused element, let it handle the ESC key instead.
    if (
      !focusedElement ||
      focusedElement == doc.body ||
      focusedElement == this.tabbrowser.selectedBrowser ||
      // Ignore focused elements inside the notification.
      notification.contains(focusedElement)
    ) {
      let escAction = notification.notification.options.escAction;
      this._onButtonEvent(aEvent, escAction, "esc-press", notification);
      // Without this preventDefault call, the event will be sent to the content page
      // and our event listener might be called again after receiving a reply from
      // the content process, which could accidentally dismiss another notification.
      aEvent.preventDefault();
    }
  };

  let documentElement = this.window.document.documentElement;
  let locationBarHidden = documentElement
    .getAttribute("chromehidden")
    .includes("location");
  let isFullscreen = !!this.window.document.fullscreenElement;

  this.panel.setAttribute("followanchor", !locationBarHidden && !isFullscreen);

  // There are no anchor icons in DOM fullscreen mode, but we would
  // still like to show the popup notification. To avoid an infinite
  // loop of showing and hiding, we have to disable followanchor
  // (which hides the element without an anchor) in fullscreen.
  this.window.addEventListener(
    "MozDOMFullscreen:Entered",
    () => {
      this.panel.setAttribute("followanchor", "false");
    },
    true
  );
  this.window.addEventListener(
    "MozDOMFullscreen:Exited",
    () => {
      this.panel.setAttribute("followanchor", !locationBarHidden);
    },
    true
  );

  Services.obs.addObserver(this, "fullscreen-transition-start");
  Services.obs.addObserver(this, "pointer-lock-entered");

  this.window.addEventListener("unload", () => {
    Services.obs.removeObserver(this, "fullscreen-transition-start");
    Services.obs.removeObserver(this, "pointer-lock-entered");
  });

  this.window.addEventListener("activate", this, true);
  if (this.tabbrowser.tabContainer) {
    this.tabbrowser.tabContainer.addEventListener("TabSelect", this, true);

    this.tabbrowser.tabContainer.addEventListener("TabClose", aEvent => {
      // If the tab was just closed and we have notifications associated with it,
      // then the notifications were closed because of the tab removal. We need to
      // record this event in telemetry and fire the removal callback.
      this.nextRemovalReason = TELEMETRY_STAT_REMOVAL_LEAVE_PAGE;
      let notifications = this._getNotificationsForBrowser(
        aEvent.target.linkedBrowser
      );
      for (let notification of notifications) {
        this._fireCallback(
          notification,
          NOTIFICATION_EVENT_REMOVED,
          this.nextRemovalReason
        );
        notification._recordTelemetryStat(this.nextRemovalReason);
      }
    });
  }
}

PopupNotifications.prototype = {
  window: null,
  panel: null,
  tabbrowser: null,

  _iconBox: null,
  set iconBox(iconBox) {
    // Remove the listeners on the old iconBox, if needed
    if (this._iconBox) {
      this._iconBox.removeEventListener("click", this);
      this._iconBox.removeEventListener("keypress", this);
    }
    this._iconBox = iconBox;
    if (iconBox) {
      iconBox.addEventListener("click", this);
      iconBox.addEventListener("keypress", this);
    }
  },
  get iconBox() {
    return this._iconBox;
  },

  observe(subject, topic) {
    // These observers apply to all windows.
    if (
      topic == "fullscreen-transition-start" ||
      topic == "pointer-lock-entered"
    ) {
      // Extend security delay if the panel is open.
      if (this.isPanelOpen) {
        let notification = this.panel.firstChild?.notification;
        if (notification) {
          this._extendSecurityDelay([notification]);
        }
      }
    }
  },

  /**
   * Retrieve one or many Notification object/s associated with the browser/ID pair.
   * @param {string|string[]} id
   *        The Notification ID or an array of IDs to search for.
   * @param [browser]
   *        The browser whose notifications should be searched. If null, the
   *        currently selected browser's notifications will be searched.
   *
   * @returns {Notification|Notification[]|null} If passed a single id, returns the corresponding Notification object, or null if no such
   *          notification exists.
   *          If passed an id array, returns an array of Notification objects which match the ids.
   */
  getNotification: function PopupNotifications_getNotification(id, browser) {
    let notifications = this._getNotificationsForBrowser(
      browser || this.tabbrowser.selectedBrowser
    );
    if (Array.isArray(id)) {
      return notifications.filter(x => id.includes(x.id));
    }
    return notifications.find(x => x.id == id) || null;
  },

  /**
   * Adds a new popup notification.
   * @param browser
   *        The <xul:browser> element associated with the notification. Must not
   *        be null.
   * @param id
   *        A unique ID that identifies the type of notification (e.g.
   *        "geolocation"). Only one notification with a given ID can be visible
   *        at a time. If a notification already exists with the given ID, it
   *        will be replaced.
   * @param message
   *        A string containing the text to be displayed as the notification
   *        header.  The string may optionally contain one or two "<>" as a
   *        placeholder which is later replaced by a host name or an addon name
   *        that is formatted to look bold, in which case the options.name
   *        property (as well as options.secondName if passing a "<>" and a "{}"
   *        placeholder) needs to be specified. "<>" will be considered as the
   *        first and "{}" as the second placeholder.
   * @param anchorID
   *        The ID of the element that should be used as this notification
   *        popup's anchor. May be null, in which case the notification will be
   *        anchored to the iconBox.
   * @param mainAction
   *        A JavaScript object literal describing the notification button's
   *        action. If present, it must have the following properties:
   *          - label (string): the button's label.
   *          - accessKey (string): the button's accessKey.
   *          - callback (function): a callback to be invoked when the button is
   *            pressed, is passed an object that contains the following fields:
   *              - checkboxChecked: (boolean) If the optional checkbox is checked.
   *              - source: (string): the source of the action that initiated the
   *                callback, either:
   *                - "button" if popup buttons were directly activated, or
   *                - "esc-press" if the user pressed the escape key, or
   *                - "menucommand" if a menu was activated.
   *          - [optional] dismiss (boolean): If this is true, the notification
   *            will be dismissed instead of removed after running the callback.
   *          - [optional] disabled (boolean): If this is true, the button
   *            will be disabled.
   *        If null, the notification will have a default "OK" action button
   *        that can be used to dismiss the popup and secondaryActions will be ignored.
   * @param secondaryActions
   *        An optional JavaScript array describing the notification's alternate
   *        actions. The array should contain objects with the same properties
   *        as mainAction. These are used to populate the notification button's
   *        dropdown menu.
   * @param options
   *        An options JavaScript object holding additional properties for the
   *        notification. The following properties are currently supported:
   *        persistence: An integer. The notification will not automatically
   *                     dismiss for this many page loads.
   *        timeout:     A time in milliseconds. The notification will not
   *                     automatically dismiss before this time.
   *        persistWhileVisible:
   *                     A boolean. If true, a visible notification will always
   *                     persist across location changes.
   *        persistent:  A boolean. If true, the notification will always
   *                     persist even across tab and app changes (but not across
   *                     location changes), until the user accepts or rejects
   *                     the request. The notification will never be implicitly
   *                     dismissed.
   *        dismissed:   Whether the notification should be added as a dismissed
   *                     notification. Dismissed notifications can be activated
   *                     by clicking on their anchorElement.
   *        autofocus:   Whether the notification should be autofocused on
   *                     showing, stealing focus from any other focused element.
   *        eventCallback:
   *                     Callback to be invoked when the notification changes
   *                     state. The callback's first argument is a string
   *                     identifying the state change:
   *                     "dismissed": notification has been dismissed by the
   *                                  user (e.g. by clicking away or switching
   *                                  tabs)
   *                     "removed": notification has been removed (due to
   *                                location change or user action)
   *                     "showing": notification is about to be shown
   *                                (this can be fired multiple times as
   *                                 notifications are dismissed and re-shown)
   *                                If the callback returns true, the notification
   *                                will be dismissed.
   *                     "shown": notification has been shown (this can be fired
   *                              multiple times as notifications are dismissed
   *                              and re-shown)
   *                     "swapping": the docshell of the browser that created
   *                                 the notification is about to be swapped to
   *                                 another browser. A second parameter contains
   *                                 the browser that is receiving the docshell,
   *                                 so that the event callback can transfer stuff
   *                                 specific to this notification.
   *                                 If the callback returns true, the notification
   *                                 will be moved to the new browser.
   *                                 If the callback isn't implemented, returns false,
   *                                 or doesn't return any value, the notification
   *                                 will be removed.
   *        neverShow:   Indicate that no popup should be shown for this
   *                     notification. Useful for just showing the anchor icon.
   *        removeOnDismissal:
   *                     Notifications with this parameter set to true will be
   *                     removed when they would have otherwise been dismissed
   *                     (i.e. any time the popup is closed due to user
   *                     interaction).
   *        hideClose:   Indicate that the little close button in the corner of
   *                     the panel should be hidden.
   *        checkbox:    An object that allows you to add a checkbox and
   *                     control its behavior with these fields:
   *                       label:
   *                         (required) Label to be shown next to the checkbox.
   *                       checked:
   *                         (optional) Whether the checkbox should be checked
   *                         by default. Defaults to false.
   *                       checkedState:
   *                         (optional) An object that allows you to customize
   *                         the notification state when the checkbox is checked.
   *                           disableMainAction:
   *                             (optional) Whether the mainAction is disabled.
   *                             Defaults to false.
   *                           warningLabel:
   *                             (optional) A (warning) text that is shown below the
   *                             checkbox. Pass null to hide.
   *                       uncheckedState:
   *                         (optional) An object that allows you to customize
   *                         the notification state when the checkbox is not checked.
   *                         Has the same attributes as checkedState.
   *        popupIconClass:
   *                     A string. A class (or space separated list of classes)
   *                     that will be applied to the icon in the popup so that
   *                     several notifications using the same panel can use
   *                     different icons.
   *        popupIconURL:
   *                     A string. URL of the image to be displayed in the popup.
   *        learnMoreURL:
   *                     A string URL. Setting this property will make the
   *                     prompt display a "Learn More" link that, when clicked,
   *                     opens the URL in a new tab.
   *        displayURI:
   *                     The nsIURI of the page the notification came
   *                     from. If present, this will be displayed above the message.
   *                     If the nsIURI represents a file, the path will be displayed,
   *                     otherwise the hostPort will be displayed.
   *        name:
   *                     An optional string formatted to look bold and used in the
   *                     notifiation description header text. Usually a host name or
   *                     addon name.
   *        secondName:
   *                     An optional string formatted to look bold and used in the
   *                     notification description header text. Usually a host name or
   *                     addon name. This is similar to name, and only used in case
   *                     where message contains a "<>" and a "{}" placeholder. "<>"
   *                     is considered the first and "{}" is considered the second
   *                     placeholder.
   *        escAction:
   *                     An optional string indicating the action to take when the
   *                     Esc key is pressed. This should be set to the name of the
   *                     command to run. If not provided, "secondarybuttoncommand"
   *                     will be used.
   *        extraAttr:
   *                     An optional string value which will be given to the
   *                     extraAttr attribute on the notification's anchorElement
   *        popupOptions:
   *                     An optional object containing popup options passed to
   *                     `openPopup()` when defined.
   *        recordTelemetryInPrivateBrowsing:
   *                     An optional boolean indicating whether popup telemetry
   *                     should be recorded in private browsing windows. By default,
   *                     telemetry is NOT recorded in PBM, because the available
   *                     options for persistent permission notifications are
   *                     different between normal and PBM windows, potentially
   *                     skewing the data. But for notifications that do not differ
   *                     in PBM, this option can be used to ensure that popups in
   *                     both PBM and normal windows record the same interactions.
   * @returns the Notification object corresponding to the added notification.
   */
  show: function PopupNotifications_show(
    browser,
    id,
    message,
    anchorID,
    mainAction,
    secondaryActions,
    options
  ) {
    function isInvalidAction(a) {
      return (
        !a || !(typeof a.callback == "function") || !a.label || !a.accessKey
      );
    }

    if (!browser) {
      throw new Error("PopupNotifications_show: invalid browser");
    }
    if (!id) {
      throw new Error("PopupNotifications_show: invalid ID");
    }
    if (mainAction && isInvalidAction(mainAction)) {
      throw new Error("PopupNotifications_show: invalid mainAction");
    }
    if (secondaryActions && secondaryActions.some(isInvalidAction)) {
      throw new Error("PopupNotifications_show: invalid secondaryActions");
    }

    let notification = new Notification(
      id,
      message,
      anchorID,
      mainAction,
      secondaryActions,
      browser,
      this,
      options
    );

    if (options) {
      let escAction = options.escAction;
      if (
        escAction != "buttoncommand" &&
        escAction != "secondarybuttoncommand"
      ) {
        escAction = "secondarybuttoncommand";
      }
      notification.options.escAction = escAction;
    }

    if (options && options.dismissed) {
      notification.dismissed = true;
    }

    let existingNotification = this.getNotification(id, browser);
    if (existingNotification) {
      this._remove(existingNotification);
    }

    let notifications = this._getNotificationsForBrowser(browser);
    notifications.push(notification);

    let isActiveBrowser = this._isActiveBrowser(browser);
    let isActiveWindow = Services.focus.activeWindow == this.window;

    if (isActiveBrowser) {
      if (isActiveWindow) {
        // Autofocus if the notification requests focus.
        if (options && !options.dismissed && options.autofocus) {
          this.panel.removeAttribute("noautofocus");
        } else {
          this.panel.setAttribute("noautofocus", "true");
        }

        // show panel now
        this._update(
          notifications,
          new Set([notification.anchorElement]),
          true
        );
      } else {
        // indicate attention and update the icon if necessary
        if (!notification.dismissed) {
          this.window.getAttention();
        }
        this._updateAnchorIcons(
          notifications,
          this._getAnchorsForNotifications(
            notifications,
            notification.anchorElement
          )
        );
        this._notify("backgroundShow");
      }
    } else {
      // Notify observers that we're not showing the popup (useful for testing)
      this._notify("backgroundShow");
    }

    return notification;
  },

  /**
   * Returns true if the notification popup is currently being displayed.
   */
  get isPanelOpen() {
    let panelState = this.panel.state;

    return panelState == "showing" || panelState == "open";
  },

  /**
   * Called by the consumer to indicate that the open panel should
   * temporarily be hidden while the given panel is showing.
   */
  suppressWhileOpen(panel) {
    this._hidePanel().catch(console.error);
    panel.addEventListener("popuphidden", () => {
      this._update();
    });
  },

  /**
   * Called by the consumer to indicate that a browser's location has changed,
   * so that we can update the active notifications accordingly.
   */
  locationChange: function PopupNotifications_locationChange(aBrowser) {
    if (!aBrowser) {
      throw new Error("PopupNotifications_locationChange: invalid browser");
    }

    let notifications = this._getNotificationsForBrowser(aBrowser);

    this.nextRemovalReason = TELEMETRY_STAT_REMOVAL_LEAVE_PAGE;

    notifications = notifications.filter(function (notification) {
      // The persistWhileVisible option allows an open notification to persist
      // across location changes
      if (notification.options.persistWhileVisible && this.isPanelOpen) {
        if (
          "persistence" in notification.options &&
          notification.options.persistence
        ) {
          notification.options.persistence--;
        }
        return true;
      }

      // The persistence option allows a notification to persist across multiple
      // page loads
      if (
        "persistence" in notification.options &&
        notification.options.persistence
      ) {
        notification.options.persistence--;
        return true;
      }

      // The timeout option allows a notification to persist until a certain time
      if (
        "timeout" in notification.options &&
        Date.now() <= notification.options.timeout
      ) {
        return true;
      }

      notification._recordTelemetryStat(this.nextRemovalReason);
      this._fireCallback(
        notification,
        NOTIFICATION_EVENT_REMOVED,
        this.nextRemovalReason
      );
      return false;
    }, this);

    this._setNotificationsForBrowser(aBrowser, notifications);

    if (this._isActiveBrowser(aBrowser)) {
      this.anchorVisibilityChange();
    }
  },

  /**
   * Called by the consumer to indicate that the visibility of the notification
   * anchors may have changed, but the location has not changed. This also
   * checks whether all notifications are suppressed for this window.
   *
   * Calling this method may result in the "showing" and "shown" events for
   * visible notifications to be invoked even if the anchor has not changed.
   */
  anchorVisibilityChange() {
    let suppress = this._shouldSuppress();
    if (!suppress) {
      // If notifications are not suppressed, always update the visibility.
      this._suppress = false;
      let notifications = this._getNotificationsForBrowser(
        this.tabbrowser.selectedBrowser
      );
      this._update(
        notifications,
        this._getAnchorsForNotifications(
          notifications,
          getAnchorFromBrowser(this.tabbrowser.selectedBrowser)
        )
      );
      return;
    }

    // Notifications are suppressed, ensure that the panel is hidden.
    if (!this._suppress) {
      this._suppress = true;
      this._hidePanel().catch(console.error);
    }
  },

  /**
   * Removes one or many Notifications.
   * @param {Notification|Notification[]} notification - The Notification object/s to remove.
   * @param {Boolean} [isCancel] - Whether to signal,  in the notification event, that removal
   *  should be treated as cancel. This is currently used to cancel permission requests
   *  when their Notifications are removed.
   */
  remove: function PopupNotifications_remove(notification, isCancel = false) {
    let notificationArray = Array.isArray(notification)
      ? notification
      : [notification];
    let activeBrowser;

    notificationArray.forEach(n => {
      this._remove(n, isCancel);
      if (!activeBrowser && this._isActiveBrowser(n.browser)) {
        activeBrowser = n.browser;
      }
    });

    if (activeBrowser) {
      let browserNotifications =
        this._getNotificationsForBrowser(activeBrowser);
      this._update(browserNotifications);
    }
  },

  handleEvent(aEvent) {
    switch (aEvent.type) {
      case "popuphidden":
        this._onPopupHidden(aEvent);
        break;
      case "activate":
      case "popuppositioned":
        if (this.isPanelOpen) {
          for (let elt of this.panel.children) {
            let now = Cu.now();
            elt.notification.timeShown = Math.max(
              now,
              elt.notification.timeShown ?? 0
            );
          }
          break;
        }
      // fall through
      case "TabSelect":
        // setTimeout(..., 0) needed, otherwise openPopup from "activate" event
        // handler results in the popup being hidden again for some reason...
        this.window.setTimeout(() => {
          this._suppress = this._shouldSuppress();
          this._update();
        }, 0);
        break;
      case "click":
      case "keypress":
        this._onIconBoxCommand(aEvent);
        break;
    }
  },

  // Utility methods

  _ignoreDismissal: null,
  _currentAnchorElement: null,

  /**
   * Gets notifications for the currently selected browser.
   */
  get _currentNotifications() {
    return this.tabbrowser.selectedBrowser
      ? this._getNotificationsForBrowser(this.tabbrowser.selectedBrowser)
      : [];
  },

  _remove: function PopupNotifications_removeHelper(
    notification,
    isCancel = false
  ) {
    // This notification may already be removed, in which case let's just fail
    // silently.
    let notifications = this._getNotificationsForBrowser(notification.browser);
    if (!notifications) {
      return;
    }

    var index = notifications.indexOf(notification);
    if (index == -1) {
      return;
    }

    if (this._isActiveBrowser(notification.browser)) {
      notification.anchorElement.removeAttribute(ICON_ATTRIBUTE_SHOWING);
    }

    // remove the notification
    notifications.splice(index, 1);
    this._fireCallback(
      notification,
      NOTIFICATION_EVENT_REMOVED,
      this.nextRemovalReason,
      isCancel
    );
  },

  /**
   * Dismisses the notification without removing it.
   *
   * @param {Event} the event associated with the user interaction that
   *                caused the dismissal
   * @param {boolean} whether to disable persistent status. Normally,
   *                  persistent prompts can not be dismissed. You can
   *                  use this argument to force dismissal.
   */
  _dismiss: function PopupNotifications_dismiss(
    event,
    disablePersistent = false
  ) {
    if (disablePersistent) {
      let notificationEl = getNotificationFromElement(event.target);
      if (notificationEl) {
        notificationEl.notification.options.persistent = false;
      }
    }

    let browser =
      this.panel.firstElementChild &&
      this.panel.firstElementChild.notification.browser;
    this.panel.hidePopup();
    if (browser) {
      browser.focus();
    }
  },

  /**
   * Hides the notification popup.
   */
  _hidePanel: function PopupNotifications_hide() {
    if (this.panel.state == "closed") {
      return Promise.resolve();
    }
    if (this._ignoreDismissal) {
      return this._ignoreDismissal.promise;
    }
    let deferred = Promise.withResolvers();
    this._ignoreDismissal = deferred;
    this.panel.hidePopup();
    return deferred.promise;
  },

  /**
   * Removes all notifications from the notification popup.
   */
  _clearPanel() {
    let popupnotification;
    while ((popupnotification = this.panel.lastElementChild)) {
      this.panel.removeChild(popupnotification);

      // If this notification was provided by the chrome document rather than
      // created ad hoc, move it back to where we got it from.
      let originalParent = gNotificationParents.get(popupnotification);
      if (originalParent) {
        popupnotification.notification = null;

        // Re-hide the notification such that it isn't rendered in the chrome
        // document. _refreshPanel will unhide it again when needed.
        popupnotification.hidden = true;

        originalParent.appendChild(popupnotification);
      }
    }
  },

  /**
   * Formats the notification description message before we display it
   * and splits it into three parts if the message contains "<>" as
   * placeholder.
   *
   * param notification
   *       The Notification object which contains the message to format.
   *
   * @returns a Javascript object that has the following properties:
   * start: A start label string containing the first part of the message.
   *        It may contain the whole string if the description message
   *        does not have "<>" as a placeholder. For example, local
   *        file URIs with description messages that don't display hostnames.
   * name:  A string that is formatted to look bold. It replaces the
   *        placeholder with the options.name property from the notification
   *        object which is usually an addon name or a host name.
   * end:   The last part of the description message.
   */
  _formatDescriptionMessage(n) {
    let text = {};
    let array = n.message.split(/<>|{}/);
    text.start = array[0] || "";
    text.name = n.options.name || "";
    text.end = array[1] || "";
    if (array.length == 3) {
      text.secondName = n.options.secondName || "";
      text.secondEnd = array[2] || "";

      // name and secondName should be in logical positions.  Swap them in case
      // the second placeholder came before the first one in the original string.
      if (n.message.indexOf("{}") < n.message.indexOf("<>")) {
        let tmp = text.name;
        text.name = text.secondName;
        text.secondName = tmp;
      }
    } else if (array.length > 3) {
      console.error(
        "Unexpected array length encountered in " +
          "_formatDescriptionMessage: ",
        array.length
      );
    }
    return text;
  },

  _refreshPanel: function PopupNotifications_refreshPanel(notificationsToShow) {
    this._clearPanel();

    notificationsToShow.forEach(function (n) {
      let doc = this.window.document;

      // Append "-notification" to the ID to try to avoid ID conflicts with other stuff
      // in the document.
      let popupnotificationID = n.id + "-notification";

      // If the chrome document provides a popupnotification with this id, use
      // that. Otherwise create it ad-hoc.
      let popupnotification = doc.getElementById(popupnotificationID);
      if (popupnotification) {
        gNotificationParents.set(
          popupnotification,
          popupnotification.parentNode
        );
      } else {
        popupnotification = doc.createXULElement("popupnotification");
      }

      // Create the notification description element.
      let desc = this._formatDescriptionMessage(n);
      popupnotification.setAttribute("label", desc.start);
      popupnotification.setAttribute("name", desc.name);
      popupnotification.setAttribute("endlabel", desc.end);
      if ("secondName" in desc && "secondEnd" in desc) {
        popupnotification.setAttribute("secondname", desc.secondName);
        popupnotification.setAttribute("secondendlabel", desc.secondEnd);
      } else {
        popupnotification.removeAttribute("secondname");
        popupnotification.removeAttribute("secondendlabel");
      }

      if (n.options.hintText) {
        popupnotification.setAttribute("hinttext", n.options.hintText);
      } else {
        popupnotification.removeAttribute("hinttext");
      }

      popupnotification.setAttribute("id", popupnotificationID);
      popupnotification.setAttribute("popupid", n.id);
      popupnotification.setAttribute(
        "oncommand",
        "PopupNotifications._onCommand(event);"
      );
      popupnotification.setAttribute(
        "closebuttoncommand",
        `PopupNotifications._dismiss(event, true);`
      );

      popupnotification.toggleAttribute(
        "hasicon",
        !!(n.options.popupIconURL || n.options.popupIconClass)
      );

      if (n.mainAction) {
        popupnotification.setAttribute("buttonlabel", n.mainAction.label);
        popupnotification.setAttribute(
          "buttonaccesskey",
          n.mainAction.accessKey
        );
        popupnotification.setAttribute(
          "buttoncommand",
          "PopupNotifications._onButtonEvent(event, 'buttoncommand');"
        );
        popupnotification.setAttribute(
          "dropmarkerpopupshown",
          "PopupNotifications._onButtonEvent(event, 'dropmarkerpopupshown');"
        );
        popupnotification.setAttribute(
          "learnmoreclick",
          "PopupNotifications._onButtonEvent(event, 'learnmoreclick');"
        );
        popupnotification.setAttribute(
          "menucommand",
          "PopupNotifications._onMenuCommand(event);"
        );
      } else {
        // Enable the default button to let the user close the popup if the close button is hidden
        popupnotification.setAttribute(
          "buttoncommand",
          "PopupNotifications._onButtonEvent(event, 'buttoncommand');"
        );
        popupnotification.toggleAttribute("buttonhighlight", true);
        popupnotification.removeAttribute("buttonlabel");
        popupnotification.removeAttribute("buttonaccesskey");
        popupnotification.removeAttribute("dropmarkerpopupshown");
        popupnotification.removeAttribute("learnmoreclick");
        popupnotification.removeAttribute("menucommand");
      }

      let classes = "popup-notification-icon";
      if (n.options.popupIconClass) {
        classes += " " + n.options.popupIconClass;
      }
      popupnotification.setAttribute("iconclass", classes);

      if (n.options.popupIconURL) {
        popupnotification.setAttribute("icon", n.options.popupIconURL);
      } else {
        popupnotification.removeAttribute("icon");
      }

      if (n.options.learnMoreURL) {
        popupnotification.setAttribute("learnmoreurl", n.options.learnMoreURL);
      } else {
        popupnotification.removeAttribute("learnmoreurl");
      }

      if (n.options.displayURI) {
        let uri;
        try {
          if (n.options.displayURI instanceof Ci.nsIFileURL) {
            uri = n.options.displayURI.pathQueryRef;
          } else {
            try {
              uri = n.options.displayURI.hostPort;
            } catch (e) {
              uri = n.options.displayURI.spec;
            }
          }
          popupnotification.setAttribute("origin", uri);
        } catch (e) {
          console.error(e);
          popupnotification.removeAttribute("origin");
        }
      } else {
        popupnotification.removeAttribute("origin");
      }

      if (n.options.hideClose) {
        popupnotification.setAttribute("closebuttonhidden", "true");
      }

      popupnotification.notification = n;
      let menuitems = [];

      if (n.mainAction && n.secondaryActions && n.secondaryActions.length) {
        let telemetryStatId = TELEMETRY_STAT_ACTION_2;

        let secondaryAction = n.secondaryActions[0];
        popupnotification.setAttribute(
          "secondarybuttonlabel",
          secondaryAction.label
        );
        popupnotification.setAttribute(
          "secondarybuttonaccesskey",
          secondaryAction.accessKey
        );
        popupnotification.setAttribute(
          "secondarybuttoncommand",
          "PopupNotifications._onButtonEvent(event, 'secondarybuttoncommand');"
        );

        for (let i = 1; i < n.secondaryActions.length; i++) {
          let action = n.secondaryActions[i];
          let item = doc.createXULElement("menuitem");
          item.setAttribute("label", action.label);
          item.setAttribute("accesskey", action.accessKey);
          item.notification = n;
          item.action = action;

          menuitems.push(item);

          // We can only record a limited number of actions in telemetry. If
          // there are more, the latest are all recorded in the last bucket.
          item.action.telemetryStatId = telemetryStatId;
          if (telemetryStatId < TELEMETRY_STAT_ACTION_LAST) {
            telemetryStatId++;
          }
        }
        popupnotification.setAttribute("secondarybuttonhidden", "false");
      } else {
        popupnotification.setAttribute("secondarybuttonhidden", "true");
      }
      popupnotification.setAttribute(
        "dropmarkerhidden",
        n.secondaryActions.length < 2 ? "true" : "false"
      );

      let checkbox = n.options.checkbox;
      if (checkbox && checkbox.label) {
        let checked =
          n._checkboxChecked != null ? n._checkboxChecked : !!checkbox.checked;
        popupnotification.checkboxState = {
          checked,
          label: checkbox.label,
        };

        if (checked) {
          this._setNotificationUIState(
            popupnotification,
            checkbox.checkedState
          );
        } else {
          this._setNotificationUIState(
            popupnotification,
            checkbox.uncheckedState
          );
        }
      } else {
        popupnotification.checkboxState = null;
        // Reset the UI state to avoid previous state bleeding into this prompt.
        this._setNotificationUIState(popupnotification);
      }

      this.panel.appendChild(popupnotification);

      // The popupnotification may be hidden if we got it from the chrome
      // document rather than creating it ad hoc.
      popupnotification.show();

      popupnotification.menupopup.textContent = "";
      popupnotification.menupopup.append(...menuitems);
    }, this);
  },

  _setNotificationUIState(notification, state = {}) {
    let mainAction = notification.notification.mainAction;
    if (
      (mainAction && mainAction.disabled) ||
      state.disableMainAction ||
      notification.hasAttribute("invalidselection")
    ) {
      notification.setAttribute("mainactiondisabled", "true");
    } else {
      notification.removeAttribute("mainactiondisabled");
    }
    if (state.warningLabel) {
      notification.setAttribute("warninglabel", state.warningLabel);
      notification.removeAttribute("warninghidden");
    } else {
      notification.setAttribute("warninghidden", "true");
    }
  },

  _extendSecurityDelay(notifications) {
    let now = Cu.now();
    notifications.forEach(n => {
      n.timeShown = now + FULLSCREEN_TRANSITION_TIME_SHOWN_OFFSET_MS;
    });
  },

  _showPanel: function PopupNotifications_showPanel(
    notificationsToShow,
    anchorElement
  ) {
    this.panel.hidden = false;

    notificationsToShow = notificationsToShow.filter(n => {
      if (anchorElement != n.anchorElement) {
        return false;
      }

      let dismiss = this._fireCallback(n, NOTIFICATION_EVENT_SHOWING);
      if (dismiss) {
        n.dismissed = true;
      }
      return !dismiss;
    });
    if (!notificationsToShow.length) {
      return;
    }

    let notificationIds = notificationsToShow.map(n => n.id);

    this._refreshPanel(notificationsToShow);

    // The element the PopupNotification should anchor to might not be visible.
    // Check its visibility using a callback that returns the same anchor
    // element if its visible, or a fallback option that is visible.
    // If no fallbacks are visible, it should return null.
    if (this._getVisibleAnchorElement) {
      anchorElement = this._getVisibleAnchorElement(anchorElement);
    }
    // In case _getVisibleAnchorElement provided a non-visible element.
    if (!anchorElement?.checkVisibility()) {
      // We only ever show notifications for the current browser,
      // so we can just use the current tab.
      anchorElement = this.tabbrowser.selectedTab;
      if (!anchorElement?.checkVisibility()) {
        // If we're in an entirely chromeless environment, set the anchorElement
        // to null and let openPopup show the notification at (0,0) later.
        anchorElement = null;
      }
    }

    if (this.isPanelOpen && this._currentAnchorElement == anchorElement) {
      notificationsToShow.forEach(function (n) {
        // If the panel is already open remember the time the notification was
        // shown for the security delay.
        n.timeShown = Math.max(Cu.now(), n.timeShown ?? 0);
        this._fireCallback(n, NOTIFICATION_EVENT_SHOWN);
      }, this);

      // Make sure we update the noautohide attribute on the panel, in case it changed.
      if (notificationsToShow.some(n => n.options.persistent)) {
        this.panel.setAttribute("noautohide", "true");
      } else {
        this.panel.removeAttribute("noautohide");
      }

      // Let tests know that the panel was updated and what notifications it was
      // updated with so that tests can wait for the correct notifications to be
      // added.
      let event = new this.window.CustomEvent("PanelUpdated", {
        detail: notificationIds,
      });
      this.panel.dispatchEvent(event);
      return;
    }

    // If the panel is already open but we're changing anchors, we need to hide
    // it first.  Otherwise it can appear in the wrong spot.  (_hidePanel is
    // safe to call even if the panel is already hidden.)
    this._hidePanel().then(() => {
      this._currentAnchorElement = anchorElement;

      if (notificationsToShow.some(n => n.options.persistent)) {
        this.panel.setAttribute("noautohide", "true");
      } else {
        this.panel.removeAttribute("noautohide");
      }

      notificationsToShow.forEach(function (n) {
        // Record that the notification was actually displayed on screen.
        // Notifications that were opened a second time or that were originally
        // shown with "options.dismissed" will be recorded in a separate bucket.
        n._recordTelemetryStat(TELEMETRY_STAT_OFFERED);
      }, this);

      // We're about to open the panel while in a full screen transition or
      // during pointer lock. Extend the security delay to avoid clickjacking.
      if (
        this.window.isInFullScreenTransition ||
        this.window.PointerLock?.isActive
      ) {
        this._extendSecurityDelay(notificationsToShow);
      }

      let target = this.panel;
      if (target.parentNode) {
        // NOTIFICATION_EVENT_SHOWN should be fired for the panel before
        // anyone listening for popupshown on the panel gets run. Otherwise,
        // the panel will not be initialized when the popupshown event
        // listeners run.
        // By targeting the panel's parent and using a capturing listener, we
        // can have our listener called before others waiting for the panel to
        // be shown (which probably expect the panel to be fully initialized)
        target = target.parentNode;
      }
      if (this._popupshownListener) {
        target.removeEventListener(
          "popupshown",
          this._popupshownListener,
          true
        );
      }
      this._popupshownListener = function () {
        target.removeEventListener(
          "popupshown",
          this._popupshownListener,
          true
        );
        this._popupshownListener = null;

        notificationsToShow.forEach(function (n) {
          // The panel has been opened, remember the time the notification was
          // shown for the security delay.
          n.timeShown = Math.max(Cu.now(), n.timeShown ?? 0);
          this._fireCallback(n, NOTIFICATION_EVENT_SHOWN);
        }, this);
        // These notifications are used by tests to know when all the processing
        // required to display the panel has happened.
        this.panel.dispatchEvent(new this.window.CustomEvent("Shown"));
        let event = new this.window.CustomEvent("PanelUpdated", {
          detail: notificationIds,
        });
        this.panel.dispatchEvent(event);
      };
      this._popupshownListener = this._popupshownListener.bind(this);
      target.addEventListener("popupshown", this._popupshownListener, true);

      let popupOptions = notificationsToShow.findLast(
        n => n.options?.popupOptions
      )?.options?.popupOptions;
      if (popupOptions) {
        this.panel.openPopup(anchorElement, popupOptions);
      } else {
        this.panel.openPopup(anchorElement, "bottomleft topleft", 0, 0);
      }
    });
  },

  /**
   * Updates the notification state in response to window activation or tab
   * selection changes.
   *
   * @param notifications an array of Notification instances. if null,
   *                      notifications will be retrieved off the current
   *                      browser tab
   * @param anchors       is a XUL element or a Set of XUL elements that the
   *                      notifications panel(s) will be anchored to.
   * @param dismissShowing if true, dismiss any currently visible notifications
   *                       if there are no notifications to show. Otherwise,
   *                       currently displayed notifications will be left alone.
   */
  _update: function PopupNotifications_update(
    notifications,
    anchors = new Set(),
    dismissShowing = false
  ) {
    if (ChromeUtils.getClassName(anchors) == "XULElement") {
      anchors = new Set([anchors]);
    }

    if (!notifications) {
      notifications = this._currentNotifications;
    }

    let haveNotifications = !!notifications.length;
    if (!anchors.size && haveNotifications) {
      anchors = this._getAnchorsForNotifications(notifications);
    }

    let useIconBox = !!this.iconBox;
    if (useIconBox && anchors.size) {
      for (let anchor of anchors) {
        if (anchor.parentNode == this.iconBox) {
          continue;
        }
        useIconBox = false;
        break;
      }
    }

    // Filter out notifications that have been dismissed, unless they are
    // persistent. Also check if we should not show any notification.
    let notificationsToShow = [];
    if (!this._suppress) {
      notificationsToShow = notifications.filter(
        n => (!n.dismissed || n.options.persistent) && !n.options.neverShow
      );
    }

    if (useIconBox) {
      // Hide icons of the previous tab.
      this._hideIcons();
    }

    if (haveNotifications) {
      // Also filter out notifications that are for a different anchor.
      notificationsToShow = notificationsToShow.filter(function (n) {
        return anchors.has(n.anchorElement);
      });

      if (useIconBox) {
        this._showIcons(notifications);
        this.iconBox.hidden = false;
        // Make sure that panels can only be attached to anchors of shown
        // notifications inside an iconBox.
        anchors = this._getAnchorsForNotifications(notificationsToShow);
      } else if (anchors.size) {
        this._updateAnchorIcons(notifications, anchors);
      }
    }

    if (notificationsToShow.length) {
      let anchorElement = anchors.values().next().value;
      if (anchorElement) {
        this._showPanel(notificationsToShow, anchorElement);
      }

      // Setup a capturing event listener on the whole window to catch the
      // escape key while persistent notifications are visible.
      this.window.addEventListener(
        "keypress",
        this._handleWindowKeyPress,
        true
      );
    } else {
      // Notify observers that we're not showing the popup (useful for testing)
      this._notify("updateNotShowing");

      // Close the panel if there are no notifications to show.
      // When called from PopupNotifications.show() we should never close the
      // panel, however. It may just be adding a dismissed notification, in
      // which case we want to continue showing any existing notifications.
      if (!dismissShowing) {
        this._dismiss();
      }

      // Only hide the iconBox if we actually have no notifications (as opposed
      // to not having any showable notifications)
      if (!haveNotifications) {
        if (useIconBox) {
          this.iconBox.hidden = true;
        } else if (anchors.size) {
          for (let anchorElement of anchors) {
            anchorElement.removeAttribute(ICON_ATTRIBUTE_SHOWING);
          }
        }
      }

      // Stop listening to keyboard events for notifications.
      this.window.removeEventListener(
        "keypress",
        this._handleWindowKeyPress,
        true
      );
    }
  },

  _updateAnchorIcons: function PopupNotifications_updateAnchorIcons(
    notifications,
    anchorElements
  ) {
    for (let anchorElement of anchorElements) {
      anchorElement.setAttribute(ICON_ATTRIBUTE_SHOWING, "true");
    }
  },

  _showIcons: function PopupNotifications_showIcons(aCurrentNotifications) {
    for (let notification of aCurrentNotifications) {
      let anchorElm = notification.anchorElement;
      if (anchorElm) {
        anchorElm.setAttribute(ICON_ATTRIBUTE_SHOWING, "true");

        if (notification.options.extraAttr) {
          anchorElm.setAttribute("extraAttr", notification.options.extraAttr);
        } else {
          anchorElm.removeAttribute("extraAttr");
        }
      }
    }
  },

  _hideIcons: function PopupNotifications_hideIcons() {
    let icons = this.iconBox.querySelectorAll(ICON_SELECTOR);
    for (let icon of icons) {
      icon.removeAttribute(ICON_ATTRIBUTE_SHOWING);
    }
  },

  /**
   * Gets and sets notifications for the browser.
   */
  _getNotificationsForBrowser: function PopupNotifications_getNotifications(
    browser
  ) {
    let notifications = popupNotificationsMap.get(browser);
    if (!notifications) {
      // Initialize the WeakMap for the browser so callers can reference/manipulate the array.
      notifications = [];
      popupNotificationsMap.set(browser, notifications);
    }
    return notifications;
  },
  _setNotificationsForBrowser: function PopupNotifications_setNotifications(
    browser,
    notifications
  ) {
    popupNotificationsMap.set(browser, notifications);
    return notifications;
  },

  _getAnchorsForNotifications:
    function PopupNotifications_getAnchorsForNotifications(
      notifications,
      defaultAnchor
    ) {
      let anchors = new Set();
      for (let notification of notifications) {
        if (notification.anchorElement) {
          anchors.add(notification.anchorElement);
        }
      }
      if (defaultAnchor && !anchors.size) {
        anchors.add(defaultAnchor);
      }
      return anchors;
    },

  _isActiveBrowser(browser) {
    // We compare on frameLoader instead of just comparing the
    // selectedBrowser and browser directly because browser tabs in
    // Responsive Design Mode put the actual web content into a
    // mozbrowser iframe and proxy property read/write and method
    // calls from the tab to that iframe. This is so that attempts
    // to reload the tab end up reloading the content in
    // Responsive Design Mode, and not the Responsive Design Mode
    // viewer itself.
    //
    // This means that PopupNotifications can come up from a browser
    // in Responsive Design Mode, but the selectedBrowser will not match
    // the browser being passed into this function, despite the browser
    // actually being within the selected tab. We workaround this by
    // comparing frameLoader instead, which is proxied from the outer
    // <xul:browser> to the inner mozbrowser <iframe>.
    return this.tabbrowser.selectedBrowser.frameLoader == browser.frameLoader;
  },

  _onIconBoxCommand: function PopupNotifications_onIconBoxCommand(event) {
    // Left click, space or enter only
    let type = event.type;
    if (type == "click" && event.button != 0) {
      return;
    }

    if (
      type == "keypress" &&
      !(
        event.charCode == event.DOM_VK_SPACE ||
        event.keyCode == event.DOM_VK_RETURN
      )
    ) {
      return;
    }

    if (!this._currentNotifications.length) {
      return;
    }

    event.stopPropagation();

    // Get the anchor that is the immediate child of the icon box
    let anchor = event.target;
    while (anchor && anchor.parentNode != this.iconBox) {
      anchor = anchor.parentNode;
    }

    if (!anchor) {
      return;
    }

    // If the panel is not closed, and the anchor is different, immediately mark all
    // active notifications for the previous anchor as dismissed
    if (this.panel.state != "closed" && anchor != this._currentAnchorElement) {
      this._dismissOrRemoveCurrentNotifications();
    }

    // Avoid reshowing notifications that are already shown and have not been dismissed.
    if (this.panel.state == "closed" || anchor != this._currentAnchorElement) {
      // As soon as the panel is shown, focus the first element in the selected notification.
      this.panel.addEventListener(
        "popupshown",
        () =>
          this.window.document.commandDispatcher.advanceFocusIntoSubtree(
            this.panel
          ),
        { once: true }
      );

      this._reshowNotifications(anchor);
    } else {
      // Focus the first element in the selected notification.
      this.window.document.commandDispatcher.advanceFocusIntoSubtree(
        this.panel
      );
    }
  },

  _reshowNotifications: function PopupNotifications_reshowNotifications(
    anchor,
    browser
  ) {
    // Mark notifications anchored to this anchor as un-dismissed
    browser = browser || this.tabbrowser.selectedBrowser;
    let notifications = this._getNotificationsForBrowser(browser);
    notifications.forEach(function (n) {
      if (n.anchorElement == anchor) {
        n.dismissed = false;
      }
    });

    if (this._isActiveBrowser(browser)) {
      // ...and then show them.
      this._update(notifications, anchor);
    }
  },

  _swapBrowserNotifications:
    function PopupNotifications_swapBrowserNoficications(
      ourBrowser,
      otherBrowser
    ) {
      // When swaping browser docshells (e.g. dragging tab to new window) we need
      // to update our notification map.

      let ourNotifications = this._getNotificationsForBrowser(ourBrowser);
      let other = otherBrowser.ownerGlobal.PopupNotifications;
      if (!other) {
        if (ourNotifications.length) {
          console.error(
            "unable to swap notifications: otherBrowser doesn't support notifications"
          );
        }
        return;
      }
      let otherNotifications = other._getNotificationsForBrowser(otherBrowser);
      if (ourNotifications.length < 1 && otherNotifications.length < 1) {
        // No notification to swap.
        return;
      }

      otherNotifications = otherNotifications.filter(n => {
        if (this._fireCallback(n, NOTIFICATION_EVENT_SWAPPING, ourBrowser)) {
          n.browser = ourBrowser;
          n.owner = this;
          return true;
        }
        other._fireCallback(
          n,
          NOTIFICATION_EVENT_REMOVED,
          this.nextRemovalReason
        );
        return false;
      });

      ourNotifications = ourNotifications.filter(n => {
        if (this._fireCallback(n, NOTIFICATION_EVENT_SWAPPING, otherBrowser)) {
          n.browser = otherBrowser;
          n.owner = other;
          return true;
        }
        this._fireCallback(
          n,
          NOTIFICATION_EVENT_REMOVED,
          this.nextRemovalReason
        );
        return false;
      });

      this._setNotificationsForBrowser(otherBrowser, ourNotifications);
      other._setNotificationsForBrowser(ourBrowser, otherNotifications);

      if (otherNotifications.length) {
        this._update(otherNotifications);
      }
      if (ourNotifications.length) {
        other._update(ourNotifications);
      }
    },

  _fireCallback: function PopupNotifications_fireCallback(n, event, ...args) {
    try {
      if (n.options.eventCallback) {
        return n.options.eventCallback.call(n, event, ...args);
      }
    } catch (error) {
      console.error(error);
    }
    return undefined;
  },

  _onPopupHidden: function PopupNotifications_onPopupHidden(event) {
    if (event.target != this.panel) {
      return;
    }

    // It's possible that a popupnotification set `aria-describedby` on the
    // panel element in its eventCallback function. If so, we'll clear that out
    // before showing the next notification.
    this.panel.removeAttribute("aria-describedby");

    // We may have removed the "noautofocus" attribute before showing the panel
    // if the notification specified it wants to autofocus on first show.
    // When the panel is closed, we have to restore the attribute to its default
    // value, so we don't autofocus it if it's subsequently opened from a different code path.
    this.panel.setAttribute("noautofocus", "true");

    // Handle the case where the panel was closed programmatically.
    if (this._ignoreDismissal) {
      this._ignoreDismissal.resolve();
      this._ignoreDismissal = null;
      return;
    }

    this._dismissOrRemoveCurrentNotifications();

    this._clearPanel();

    this._update();
  },

  _dismissOrRemoveCurrentNotifications() {
    let browser =
      this.panel.firstElementChild &&
      this.panel.firstElementChild.notification.browser;
    if (!browser) {
      return;
    }

    let notifications = this._getNotificationsForBrowser(browser);
    // Mark notifications as dismissed and call dismissal callbacks
    for (let nEl of this.panel.children) {
      let notificationObj = nEl.notification;
      // Never call a dismissal handler on a notification that's been removed.
      if (!notifications.includes(notificationObj)) {
        return;
      }

      // Record the time of the first notification dismissal if the main action
      // was not triggered in the meantime.
      let timeSinceShown = Cu.now() - notificationObj.timeShown;
      if (
        !notificationObj.wasDismissed &&
        !notificationObj.recordedTelemetryMainAction
      ) {
        notificationObj._recordTelemetry(
          "POPUP_NOTIFICATION_DISMISSAL_MS",
          timeSinceShown
        );
      }

      // Do not mark the notification as dismissed or fire NOTIFICATION_EVENT_DISMISSED
      // if the notification is removed.
      if (notificationObj.options.removeOnDismissal) {
        notificationObj._recordTelemetryStat(this.nextRemovalReason);
        this._remove(notificationObj);
      } else {
        notificationObj.dismissed = true;
        this._fireCallback(notificationObj, NOTIFICATION_EVENT_DISMISSED);
      }
    }
  },

  _onCheckboxCommand(event) {
    let notificationEl = getNotificationFromElement(event.originalTarget);
    let checked = notificationEl.checkbox.checked;
    let notification = notificationEl.notification;

    // Save checkbox state to be able to persist it when re-opening the doorhanger.
    notification._checkboxChecked = checked;

    if (checked) {
      this._setNotificationUIState(
        notificationEl,
        notification.options.checkbox.checkedState
      );
    } else {
      this._setNotificationUIState(
        notificationEl,
        notification.options.checkbox.uncheckedState
      );
    }
    event.stopPropagation();
  },

  _onCommand(event) {
    // Ignore events from buttons as they are submitting and so don't need checks
    if (event.originalTarget.localName == "button") {
      return;
    }
    let notificationEl = getNotificationFromElement(event.target);

    let notification = notificationEl.notification;
    if (!notification.options.checkbox) {
      this._setNotificationUIState(notificationEl);
      return;
    }

    if (notificationEl.checkbox.checked) {
      this._setNotificationUIState(
        notificationEl,
        notification.options.checkbox.checkedState
      );
    } else {
      this._setNotificationUIState(
        notificationEl,
        notification.options.checkbox.uncheckedState
      );
    }
  },

  _onButtonEvent(event, type, source = "button", notificationEl = null) {
    if (!notificationEl) {
      notificationEl = getNotificationFromElement(event.originalTarget);
    }

    if (!notificationEl) {
      throw new Error(
        "PopupNotifications._onButtonEvent: couldn't find notification element"
      );
    }

    if (!notificationEl.notification) {
      throw new Error(
        "PopupNotifications._onButtonEvent: couldn't find notification"
      );
    }

    let notification = notificationEl.notification;

    // Receiving a button event means the notification should have been shown.
    // Make sure that timeShown is always set to ensure we don't break the
    // security delay calculation below.
    if (!notification.timeShown) {
      console.warn(
        "_onButtonEvent: notification.timeShown is unset. Setting to now.",
        notification
      );
      notification.timeShown = Cu.now();
    }

    if (type == "dropmarkerpopupshown") {
      notification._recordTelemetryStat(TELEMETRY_STAT_OPEN_SUBMENU);
      return;
    }

    if (type == "learnmoreclick") {
      notification._recordTelemetryStat(TELEMETRY_STAT_LEARN_MORE);
      return;
    }

    if (type == "buttoncommand") {
      // Record the total timing of the main action since the notification was
      // created, even if the notification was dismissed in the meantime.
      let timeSinceCreated = Cu.now() - notification.timeCreated;
      if (!notification.recordedTelemetryMainAction) {
        notification.recordedTelemetryMainAction = true;
        notification._recordTelemetry(
          "POPUP_NOTIFICATION_MAIN_ACTION_MS",
          timeSinceCreated
        );
      }
    }

    if (type == "buttoncommand" || type == "secondarybuttoncommand") {
      // TODO: Bug 1892756.
      if (
        Services.focus.activeWindow != this.window ||
        notificationEl.matches(":-moz-window-inactive")
      ) {
        Services.console.logStringMessage(
          "PopupNotifications._onButtonEvent: " +
            "Button click happened before the window was focused / active"
        );
        this.window.focus();
        return;
      }

      let now = Cu.now();
      let timeSinceShown = now - notification.timeShown;
      if (timeSinceShown < lazy.buttonDelay) {
        Services.console.logStringMessage(
          "PopupNotifications._onButtonEvent: " +
            "Button click happened before the security delay: " +
            timeSinceShown +
            "ms"
        );
        notification.timeShown = Math.max(now, notification.timeShown);
        return;
      }
    }

    let action = notification.mainAction;
    let telemetryStatId = TELEMETRY_STAT_ACTION_1;

    if (type == "secondarybuttoncommand") {
      action = notification.secondaryActions[0];
      telemetryStatId = TELEMETRY_STAT_ACTION_2;
    }

    notification._recordTelemetryStat(telemetryStatId);

    if (action) {
      try {
        action.callback.call(undefined, {
          checkboxChecked: notificationEl.checkbox.checked,
          source,
          event,
        });
      } catch (error) {
        console.error(error);
      }

      if (action.dismiss) {
        this._dismiss();
        return;
      }
    }

    this._remove(notification);
    this._update();
  },

  _onMenuCommand: function PopupNotifications_onMenuCommand(event) {
    let target = event.originalTarget;
    if (!target.action || !target.notification) {
      throw new Error(
        "menucommand target has no associated action/notification"
      );
    }

    let notificationEl = getNotificationFromElement(target);
    event.stopPropagation();

    target.notification._recordTelemetryStat(target.action.telemetryStatId);

    try {
      target.action.callback.call(undefined, {
        checkboxChecked: notificationEl.checkbox.checked,
        source: "menucommand",
      });
    } catch (error) {
      console.error(error);
    }

    if (target.action.dismiss) {
      this._dismiss();
      return;
    }

    this._remove(target.notification);
    this._update();
  },

  _notify: function PopupNotifications_notify(topic) {
    Services.obs.notifyObservers(null, "PopupNotifications-" + topic);
  },
};
PK
!<����7�7modules/Preferences.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// The minimum and maximum integers that can be set as preferences.
// The range of valid values is narrower than the range of valid JS values
// because the native preferences code treats integers as NSPR PRInt32s,
// which are 32-bit signed integers on all platforms.
const MAX_INT = 0x7fffffff; // Math.pow(2, 31) - 1
const MIN_INT = -0x80000000;

export function Preferences(args) {
  this._cachedPrefBranch = null;
  if (isObject(args)) {
    if (args.branch) {
      this._branchStr = args.branch;
    }
    if (args.defaultBranch) {
      this._defaultBranch = args.defaultBranch;
    }
    if (args.privacyContext) {
      this._privacyContext = args.privacyContext;
    }
  } else if (args) {
    this._branchStr = args;
  }
}

/**
 * Get the value of a pref, if any; otherwise return the default value.
 *
 * @param   prefName  {String|Array}
 *          the pref to get, or an array of prefs to get
 *
 * @param   defaultValue
 *          the default value, if any, for prefs that don't have one
 *
 * @param   valueType
 *          the XPCOM interface of the pref's complex value type, if any
 *
 * @returns the value of the pref, if any; otherwise the default value
 */
Preferences.get = function (prefName, defaultValue, valueType = null) {
  if (Array.isArray(prefName)) {
    return prefName.map(v => this.get(v, defaultValue));
  }

  return this._get(prefName, defaultValue, valueType);
};

Preferences._get = function (prefName, defaultValue, valueType) {
  switch (this._prefBranch.getPrefType(prefName)) {
    case Ci.nsIPrefBranch.PREF_STRING:
      if (valueType) {
        let ifaces = ["nsIFile", "nsIPrefLocalizedString"];
        if (ifaces.includes(valueType.name)) {
          return this._prefBranch.getComplexValue(prefName, valueType).data;
        }
      }
      return this._prefBranch.getStringPref(prefName);

    case Ci.nsIPrefBranch.PREF_INT:
      return this._prefBranch.getIntPref(prefName);

    case Ci.nsIPrefBranch.PREF_BOOL:
      return this._prefBranch.getBoolPref(prefName);

    case Ci.nsIPrefBranch.PREF_INVALID:
      return defaultValue;

    default:
      // This should never happen.
      throw new Error(
        `Error getting pref ${prefName}; its value's type is ` +
          `${this._prefBranch.getPrefType(prefName)}, which I don't ` +
          `know how to handle.`
      );
  }
};

/**
 * Set a preference to a value.
 *
 * You can set multiple prefs by passing an object as the only parameter.
 * In that case, this method will treat the properties of the object
 * as preferences to set, where each property name is the name of a pref
 * and its corresponding property value is the value of the pref.
 *
 * @param   prefName  {String|Object}
 *          the name of the pref to set; or an object containing a set
 *          of prefs to set
 *
 * @param   prefValue {String|Number|Boolean}
 *          the value to which to set the pref
 *
 * Note: Preferences cannot store non-integer numbers or numbers outside
 * the signed 32-bit range -(2^31-1) to 2^31-1, If you have such a number,
 * store it as a string by calling toString() on the number before passing
 * it to this method, i.e.:
 *   Preferences.set("pi", 3.14159.toString())
 *   Preferences.set("big", Math.pow(2, 31).toString()).
 */
Preferences.set = function (prefName, prefValue) {
  if (isObject(prefName)) {
    for (let [name, value] of Object.entries(prefName)) {
      this.set(name, value);
    }
    return;
  }

  this._set(prefName, prefValue);
};

Preferences._set = function (prefName, prefValue) {
  let prefType;
  if (typeof prefValue != "undefined" && prefValue != null) {
    prefType = prefValue.constructor.name;
  }

  switch (prefType) {
    case "String":
      this._prefBranch.setStringPref(prefName, prefValue);
      break;

    case "Number":
      // We throw if the number is outside the range, since the result
      // will never be what the consumer wanted to store, but we only warn
      // if the number is non-integer, since the consumer might not mind
      // the loss of precision.
      if (prefValue > MAX_INT || prefValue < MIN_INT) {
        throw new Error(
          `you cannot set the ${prefName} pref to the number ` +
            `${prefValue}, as number pref values must be in the signed ` +
            `32-bit integer range -(2^31-1) to 2^31-1.  To store numbers ` +
            `outside that range, store them as strings.`
        );
      }
      this._prefBranch.setIntPref(prefName, prefValue);
      if (prefValue % 1 != 0) {
        console.error(
          "Warning: setting the ",
          prefName,
          " pref to the non-integer number ",
          prefValue,
          " converted it " +
            "to the integer number " +
            this.get(prefName) +
            "; to retain fractional precision, store non-integer " +
            "numbers as strings."
        );
      }
      break;

    case "Boolean":
      this._prefBranch.setBoolPref(prefName, prefValue);
      break;

    default:
      throw new Error(
        `can't set pref ${prefName} to value '${prefValue}'; ` +
          `it isn't a String, Number, or Boolean`
      );
  }
};

/**
 * Whether or not the given pref has a value.  This is different from isSet
 * because it returns true whether the value of the pref is a default value
 * or a user-set value, while isSet only returns true if the value
 * is a user-set value.
 *
 * @param   prefName  {String|Array}
 *          the pref to check, or an array of prefs to check
 *
 * @returns {Boolean|Array}
 *          whether or not the pref has a value; or, if the caller provided
 *          an array of pref names, an array of booleans indicating whether
 *          or not the prefs have values
 */
Preferences.has = function (prefName) {
  if (Array.isArray(prefName)) {
    return prefName.map(this.has, this);
  }

  return (
    this._prefBranch.getPrefType(prefName) != Ci.nsIPrefBranch.PREF_INVALID
  );
};

/**
 * Whether or not the given pref has a user-set value.  This is different
 * from |has| because it returns true only if the value of the pref is a user-
 * set value, while |has| returns true if the value of the pref is a default
 * value or a user-set value.
 *
 * @param   prefName  {String|Array}
 *          the pref to check, or an array of prefs to check
 *
 * @returns {Boolean|Array}
 *          whether or not the pref has a user-set value; or, if the caller
 *          provided an array of pref names, an array of booleans indicating
 *          whether or not the prefs have user-set values
 */
Preferences.isSet = function (prefName) {
  if (Array.isArray(prefName)) {
    return prefName.map(this.isSet, this);
  }

  return this.has(prefName) && this._prefBranch.prefHasUserValue(prefName);
};

/**
 * Whether or not the given pref has a user-set value. Use isSet instead,
 * which is equivalent.
 * @deprecated
 */
Preferences.modified = function (prefName) {
  return this.isSet(prefName);
};

Preferences.reset = function (prefName) {
  if (Array.isArray(prefName)) {
    prefName.map(v => this.reset(v));
    return;
  }

  this._prefBranch.clearUserPref(prefName);
};

/**
 * Lock a pref so it can't be changed.
 *
 * @param   prefName  {String|Array}
 *          the pref to lock, or an array of prefs to lock
 */
Preferences.lock = function (prefName) {
  if (Array.isArray(prefName)) {
    prefName.map(this.lock, this);
  }

  this._prefBranch.lockPref(prefName);
};

/**
 * Unlock a pref so it can be changed.
 *
 * @param   prefName  {String|Array}
 *          the pref to lock, or an array of prefs to lock
 */
Preferences.unlock = function (prefName) {
  if (Array.isArray(prefName)) {
    prefName.map(this.unlock, this);
  }

  this._prefBranch.unlockPref(prefName);
};

/**
 * Whether or not the given pref is locked against changes.
 *
 * @param   prefName  {String|Array}
 *          the pref to check, or an array of prefs to check
 *
 * @returns {Boolean|Array}
 *          whether or not the pref has a user-set value; or, if the caller
 *          provided an array of pref names, an array of booleans indicating
 *          whether or not the prefs have user-set values
 */
Preferences.locked = function (prefName) {
  if (Array.isArray(prefName)) {
    return prefName.map(this.locked, this);
  }

  return this._prefBranch.prefIsLocked(prefName);
};

/**
 * Start observing a pref.
 *
 * The callback can be a function or any object that implements nsIObserver.
 * When the callback is a function and thisObject is provided, it gets called
 * as a method of thisObject.
 *
 * @param   prefName    {String}
 *          the name of the pref to observe
 *
 * @param   callback    {Function|Object}
 *          the code to notify when the pref changes;
 *
 * @param   thisObject  {Object}  [optional]
 *          the object to use as |this| when calling a Function callback;
 *
 * @returns the wrapped observer
 */
Preferences.observe = function (prefName, callback, thisObject) {
  let fullPrefName = this._branchStr + (prefName || "");

  let observer = new PrefObserver(fullPrefName, callback, thisObject);
  Preferences._prefBranch.addObserver(fullPrefName, observer, true);
  observers.push(observer);

  return observer;
};

/**
 * Stop observing a pref.
 *
 * You must call this method with the same prefName, callback, and thisObject
 * with which you originally registered the observer.  However, you don't have
 * to call this method on the same exact instance of Preferences; you can call
 * it on any instance.  For example, the following code first starts and then
 * stops observing the "foo.bar.baz" preference:
 *
 *   let observer = function() {...};
 *   Preferences.observe("foo.bar.baz", observer);
 *   new Preferences("foo.bar.").ignore("baz", observer);
 *
 * @param   prefName    {String}
 *          the name of the pref being observed
 *
 * @param   callback    {Function|Object}
 *          the code being notified when the pref changes
 *
 * @param   thisObject  {Object}  [optional]
 *          the object being used as |this| when calling a Function callback
 */
Preferences.ignore = function (prefName, callback, thisObject) {
  let fullPrefName = this._branchStr + (prefName || "");

  // This seems fairly inefficient, but I'm not sure how much better we can
  // make it.  We could index by fullBranch, but we can't index by callback
  // or thisObject, as far as I know, since the keys to JavaScript hashes
  // (a.k.a. objects) can apparently only be primitive values.
  let [observer] = observers.filter(
    v =>
      v.prefName == fullPrefName &&
      v.callback == callback &&
      v.thisObject == thisObject
  );

  if (observer) {
    Preferences._prefBranch.removeObserver(fullPrefName, observer);
    observers.splice(observers.indexOf(observer), 1);
  } else {
    console.error(
      `Attempt to stop observing a preference "${prefName}" that's not being observed`
    );
  }
};

Preferences.resetBranch = function (prefBranch = "") {
  this.reset(this._prefBranch.getChildList(prefBranch));
};

/**
 * A string identifying the branch of the preferences tree to which this
 * instance provides access.
 * @private
 */
Preferences._branchStr = "";

/**
 * The cached preferences branch object this instance encapsulates, or null.
 * Do not use!  Use _prefBranch below instead.
 * @private
 */
Preferences._cachedPrefBranch = null;

/**
 * The preferences branch object for this instance.
 * @private
 */
Object.defineProperty(Preferences, "_prefBranch", {
  get: function _prefBranch() {
    if (!this._cachedPrefBranch) {
      let prefSvc = Services.prefs;
      this._cachedPrefBranch = this._defaultBranch
        ? prefSvc.getDefaultBranch(this._branchStr)
        : prefSvc.getBranch(this._branchStr);
    }
    return this._cachedPrefBranch;
  },
  enumerable: true,
  configurable: true,
});

// Constructor-based access (Preferences.get(...) and set) is preferred over
// instance-based access (new Preferences().get(...) and set) and when using the
// root preferences branch, as it's desirable not to allocate the extra object.
// But both forms are acceptable.
Preferences.prototype = Preferences;

/**
 * A cache of pref observers.
 *
 * We use this to remove observers when a caller calls Preferences::ignore.
 *
 * All Preferences instances share this object, because we want callers to be
 * able to remove an observer using a different Preferences object than the one
 * with which they added it.  That means we have to identify the observers
 * in this object by their complete pref name, not just their name relative to
 * the root branch of the Preferences object with which they were created.
 */
var observers = [];

function PrefObserver(prefName, callback, thisObject) {
  this.prefName = prefName;
  this.callback = callback;
  this.thisObject = thisObject;
}

PrefObserver.prototype = {
  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
  ]),

  observe(subject, topic, data) {
    // The pref service only observes whole branches, but we only observe
    // individual preferences, so we check here that the pref that changed
    // is the exact one we're observing (and not some sub-pref on the branch).
    if (data != this.prefName) {
      return;
    }

    if (typeof this.callback == "function") {
      let prefValue = Preferences.get(this.prefName);

      if (this.thisObject) {
        this.callback.call(this.thisObject, prefValue);
      } else {
        this.callback(prefValue);
      }
    } else {
      // typeof this.callback == "object" (nsIObserver)
      this.callback.observe(subject, topic, data);
    }
  },
};

function isObject(val) {
  // We can't check for |val.constructor == Object| here, since the value
  // might be from a different context whose Object constructor is not the same
  // as ours, so instead we match based on the name of the constructor.
  return (
    typeof val != "undefined" &&
    val != null &&
    typeof val == "object" &&
    val.constructor.name == "Object"
  );
}
PK
!<CV�F>>#modules/PrincipalsCollector.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "serviceWorkerManager",
  "@mozilla.org/serviceworkers/manager;1",
  "nsIServiceWorkerManager"
);

let logConsole;
function log(msg) {
  if (!logConsole) {
    logConsole = console.createInstance({
      prefix: "PrincipalsCollector",
      maxLogLevelPref: "browser.sanitizer.loglevel",
    });
  }

  logConsole.log(msg);
}

/**
 * A helper module to collect all principals that have any of the following:
 *  * cookies
 *  * quota storage (indexedDB, localStorage)
 *  * service workers
 *
 *  Note that in case of cookies, because these are not strictly associated with a
 *  full origin (including scheme), the https:// scheme will be used as a convention,
 *  so when the cookie hostname is .example.com the principal will have the origin
 *  https://example.com. Origin Attributes from cookies are copied to the principal.
 *
 *  This class is not a singleton and needs to be instantiated using the constructor
 *  before usage. The class instance will cache the last list of principals.
 *
 *  There is currently no `refresh` method, though you are free to add one.
 */
export class PrincipalsCollector {
  // Indicating that we are in the process of collecting principals,
  // that might take some time
  #pendingCollection = null;
  /**
   * Creates a new PrincipalsCollector.
   */
  constructor() {
    this.principals = null;
  }

  /**
   * Checks whether the passed in principal has a scheme that is considered by the
   * PrincipalsCollector. This is used to avoid including principals for non-web
   * entities such as moz-extension.
   *
   * @param {nsIPrincipal} the principal to check
   * @returns {boolean}
   */
  static isSupportedPrincipal(principal) {
    return ["http", "https", "file"].some(scheme => principal.schemeIs(scheme));
  }

  /**
   * Fetches and collects all principals with cookies and/or site data (see module
   * description). Originally for usage in Sanitizer.sys.mjs to compute principals
   * to be cleared on shutdown based on user settings.
   *
   * This operation might take a while to complete on big profiles.
   * DO NOT call or await this in a way that makes it block user interaction, or you
   * risk several painful seconds or possibly even minutes of lag.
   *
   * This function will cache its result and return the same list on second call,
   * even if the actual number of principals with cookies and site data changed.
   *
   * @param {Object} [optional] progress A Sanitizer.sys.mjs progress object that
   *   will be updated to reflect the current step of fetching principals.
   * @returns {Array<nsIPrincipal>} the list of principals
   */
  async getAllPrincipals(progress = {}) {
    // Here is the list of principals with site data.
    if (this.principals) {
      return this.principals;
    }
    // Gathering all principals might take a while,
    // to ensure this process is only done once, we queue
    // the incomming calls here in case we are not yet done gathering principals
    if (!this.#pendingCollection) {
      this.#pendingCollection = this._getAllPrincipalsInternal(progress);
      this.principals = await this.#pendingCollection;
      this.#pendingCollection = null;
      return this.principals;
    }
    await this.#pendingCollection;
    return this.principals;
  }

  async _getAllPrincipalsInternal(progress = {}) {
    progress.step = "principals-quota-manager";
    let principals = await new Promise(resolve => {
      Services.qms.listOrigins().callback = request => {
        progress.step = "principals-quota-manager-listOrigins";
        if (request.resultCode != Cr.NS_OK) {
          // We are probably shutting down. We don't want to propagate the
          // error, rejecting the promise.
          resolve([]);
          return;
        }

        let principalsMap = new Map();
        for (const origin of request.result) {
          let principal =
            Services.scriptSecurityManager.createContentPrincipalFromOrigin(
              origin
            );
          if (PrincipalsCollector.isSupportedPrincipal(principal)) {
            principalsMap.set(principal.origin, principal);
          }
        }

        progress.step = "principals-quota-manager-completed";
        resolve(principalsMap);
      };
    }).catch(ex => {
      console.error("QuotaManagerService promise failed: ", ex);
      return [];
    });

    progress.step = "principals-service-workers";
    let serviceWorkers = lazy.serviceWorkerManager.getAllRegistrations();
    for (let i = 0; i < serviceWorkers.length; i++) {
      let sw = serviceWorkers.queryElementAt(
        i,
        Ci.nsIServiceWorkerRegistrationInfo
      );
      // We don't need to check the scheme. SW are just exposed to http/https URLs.
      principals.set(sw.principal.origin, sw.principal);
    }

    // Let's take the list of unique hosts+OA from cookies.
    progress.step = "principals-cookies";
    let cookies = Services.cookies.cookies;
    let hosts = new Set();
    for (let cookie of cookies) {
      hosts.add(
        cookie.rawHost +
          ChromeUtils.originAttributesToSuffix(cookie.originAttributes)
      );
    }

    progress.step = "principals-host-cookie";
    hosts.forEach(host => {
      // Cookies and permissions are handled by origin/host. Doesn't matter if we
      // use http: or https: schema here.
      let principal;
      try {
        principal =
          Services.scriptSecurityManager.createContentPrincipalFromOrigin(
            "https://" + host
          );
      } catch (e) {
        log(
          `ERROR: Could not create content principal for host '${host}' ${e.message}`
        );
      }
      if (principal) {
        principals.set(principal.origin, principal);
      }
    });

    principals = Array.from(principals.values());
    progress.step = "total-principals:" + principals.length;
    return principals;
  }
}
PK
!<��խ� � )modules/PrivateAttributionService.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

ChromeUtils.defineESModuleGetters(lazy, {
  IndexedDB: "resource://gre/modules/IndexedDB.sys.mjs",
  DAPTelemetrySender: "resource://gre/modules/DAPTelemetrySender.sys.mjs",
  HPKEConfigManager: "resource://gre/modules/HPKEConfigManager.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gIsTelemetrySendingEnabled",
  "datareporting.healthreport.uploadEnabled",
  true
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gIsPPAEnabled",
  "dom.private-attribution.submission.enabled",
  true
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gOhttpRelayUrl",
  "toolkit.shopping.ohttpRelayURL"
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gOhttpGatewayKeyUrl",
  "toolkit.shopping.ohttpConfigURL"
);

const MAX_CONVERSIONS = 2;
const DAY_IN_MILLI = 1000 * 60 * 60 * 24;
const CONVERSION_RESET_MILLI = 7 * DAY_IN_MILLI;
const DAP_TIMEOUT_MILLI = 30000;

/**
 *
 */
export class PrivateAttributionService {
  constructor({
    dapTelemetrySender,
    dateProvider,
    testForceEnabled,
    testDapOptions,
  } = {}) {
    this._dapTelemetrySender = dapTelemetrySender;
    this._dateProvider = dateProvider ?? Date;
    this._testForceEnabled = testForceEnabled;
    this._testDapOptions = testDapOptions;

    this.dbName = "PrivateAttribution";
    this.impressionStoreName = "impressions";
    this.budgetStoreName = "budgets";
    this.storeNames = [this.impressionStoreName, this.budgetStoreName];
    this.dbVersion = 1;
    this.models = {
      default: "lastImpression",
      view: "lastView",
      click: "lastClick",
    };
  }

  get dapTelemetrySender() {
    return this._dapTelemetrySender || lazy.DAPTelemetrySender;
  }

  now() {
    return this._dateProvider.now();
  }

  async onAttributionEvent(sourceHost, type, index, ad, targetHost) {
    if (!this.isEnabled()) {
      return;
    }

    const now = this.now();

    try {
      const impressionStore = await this.getImpressionStore();

      const impression = await this.getImpression(impressionStore, ad, {
        index,
        target: targetHost,
        source: sourceHost,
      });

      const prop = this.getModelProp(type);
      impression.index = index;
      impression.lastImpression = now;
      impression[prop] = now;

      await this.updateImpression(impressionStore, ad, impression);
    } catch (e) {
      console.error(e);
    }
  }

  async onAttributionConversion(
    targetHost,
    task,
    histogramSize,
    lookbackDays,
    impressionType,
    ads,
    sourceHosts
  ) {
    if (!this.isEnabled()) {
      return;
    }

    const now = this.now();

    try {
      const budget = await this.getBudget(targetHost, now);
      const impression = await this.findImpression(
        ads,
        targetHost,
        sourceHosts,
        impressionType,
        lookbackDays,
        histogramSize,
        now
      );

      let index = 0;
      let value = 0;
      if (budget.conversions < MAX_CONVERSIONS && impression) {
        index = impression.index;
        value = 1;
      }

      await this.updateBudget(budget, value, targetHost);
      await this.sendDapReport(task, index, histogramSize, value);
    } catch (e) {
      console.error(e);
    }
  }

  async findImpression(ads, target, sources, model, days, histogramSize, now) {
    let impressions = [];

    const impressionStore = await this.getImpressionStore();

    // Get matching ad impressions
    if (ads && ads.length) {
      for (var i = 0; i < ads.length; i++) {
        impressions = impressions.concat(
          (await impressionStore.get(ads[i])) ?? []
        );
      }
    } else {
      impressions = (await impressionStore.getAll()).flat(1);
    }

    // Set attribution model properties
    const prop = this.getModelProp(model);

    // Find the most relevant impression
    const lookbackWindow = now - days * DAY_IN_MILLI;
    return (
      impressions
        // Filter by target, sources, and lookback days
        .filter(
          impression =>
            impression.target === target &&
            (!sources || sources.includes(impression.source)) &&
            impression[prop] >= lookbackWindow &&
            impression.index < histogramSize
        )
        // Get the impression with the most recent interaction
        .reduce(
          (cur, impression) =>
            !cur || impression[prop] > cur[prop] ? impression : cur,
          null
        )
    );
  }

  async getImpression(impressionStore, ad, defaultImpression) {
    const impressions = (await impressionStore.get(ad)) ?? [];
    const impression = impressions.find(r =>
      this.compareImpression(r, defaultImpression)
    );

    return impression ?? defaultImpression;
  }

  async updateImpression(impressionStore, key, impression) {
    let impressions = (await impressionStore.get(key)) ?? [];

    const i = impressions.findIndex(r => this.compareImpression(r, impression));
    if (i < 0) {
      impressions.push(impression);
    } else {
      impressions[i] = impression;
    }

    await impressionStore.put(impressions, key);
  }

  compareImpression(cur, impression) {
    return cur.source === impression.source && cur.target === impression.target;
  }

  async getBudget(target, now) {
    const budgetStore = await this.getBudgetStore();
    const budget = await budgetStore.get(target);

    if (!budget || now > budget.nextReset) {
      return {
        conversions: 0,
        nextReset: now + CONVERSION_RESET_MILLI,
      };
    }

    return budget;
  }

  async updateBudget(budget, value, target) {
    const budgetStore = await this.getBudgetStore();
    budget.conversions += value;
    await budgetStore.put(budget, target);
  }

  async getImpressionStore() {
    return await this.getStore(this.impressionStoreName);
  }

  async getBudgetStore() {
    return await this.getStore(this.budgetStoreName);
  }

  async getStore(storeName) {
    return (await this.db).objectStore(storeName, "readwrite");
  }

  get db() {
    return this._db || (this._db = this.createOrOpenDb());
  }

  async createOrOpenDb() {
    try {
      return await this.openDatabase();
    } catch {
      await lazy.IndexedDB.deleteDatabase(this.dbName);
      return this.openDatabase();
    }
  }

  async openDatabase() {
    return await lazy.IndexedDB.open(this.dbName, this.dbVersion, db => {
      this.storeNames.forEach(store => {
        if (!db.objectStoreNames.contains(store)) {
          db.createObjectStore(store);
        }
      });
    });
  }

  async sendDapReport(id, index, size, value) {
    const task = {
      id,
      time_precision: 60,
      measurement_type: "vecu8",
    };

    const measurement = new Array(size).fill(0);
    measurement[index] = value;

    let options = {
      timeout: DAP_TIMEOUT_MILLI,
      ohttp_relay: lazy.gOhttpRelayUrl,
      ...this._testDapOptions,
    };

    if (options.ohttp_relay) {
      // Fetch the OHTTP-Gateway-HPKE key if not provided yet.
      if (!options.ohttp_hpke) {
        const controller = new AbortController();
        lazy.setTimeout(() => controller.abort(), DAP_TIMEOUT_MILLI);

        options.ohttp_hpke = await lazy.HPKEConfigManager.get(
          lazy.gOhttpGatewayKeyUrl,
          {
            maxAge: DAY_IN_MILLI,
            abortSignal: controller.signal,
          }
        );
      }
    } else if (!this._testForceEnabled) {
      // Except for testing, do no allow PPA to bypass OHTTP.
      throw new Error("PPA requires an OHTTP relay for submission");
    }

    await this.dapTelemetrySender.sendDAPMeasurement(
      task,
      measurement,
      options
    );
  }

  getModelProp(type) {
    return this.models[type ? type : "default"];
  }

  isEnabled() {
    return (
      this._testForceEnabled ||
      (lazy.gIsTelemetrySendingEnabled &&
        AppConstants.MOZ_TELEMETRY_REPORTING &&
        lazy.gIsPPAEnabled)
    );
  }

  QueryInterface = ChromeUtils.generateQI([Ci.nsIPrivateAttributionService]);
}
PK
!<֨Ռ�	�	$modules/PrivateBrowsingUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const kAutoStartPref = "browser.privatebrowsing.autostart";

// This will be set to true when the PB mode is autostarted from the command
// line for the current session.
var gTemporaryAutoStartMode = false;

export var PrivateBrowsingUtils = {
  get enabled() {
    return Services.policies.isAllowed("privatebrowsing");
  },

  // Rather than passing content windows to this function, please use
  // isBrowserPrivate since it works with e10s.
  isWindowPrivate: function pbu_isWindowPrivate(aWindow) {
    if (!aWindow.isChromeWindow) {
      dump(
        "WARNING: content window passed to PrivateBrowsingUtils.isWindowPrivate. " +
          "Use isContentWindowPrivate instead (but only for frame scripts).\n" +
          new Error().stack
      );
    }

    return this.privacyContextFromWindow(aWindow).usePrivateBrowsing;
  },

  // This should be used only in frame scripts.
  isContentWindowPrivate: function pbu_isWindowPrivate(aWindow) {
    return this.privacyContextFromWindow(aWindow).usePrivateBrowsing;
  },

  isBrowserPrivate(aBrowser) {
    let chromeWin = aBrowser.ownerGlobal;
    if (chromeWin.gMultiProcessBrowser || !aBrowser.contentWindow) {
      // In e10s we have to look at the chrome window's private
      // browsing status since the only alternative is to check the
      // content window, which is in another process.  If the browser
      // is lazy or is running in windowless configuration then the
      // content window doesn't exist.
      return this.isWindowPrivate(chromeWin);
    }
    return this.privacyContextFromWindow(aBrowser.contentWindow)
      .usePrivateBrowsing;
  },

  privacyContextFromWindow: function pbu_privacyContextFromWindow(aWindow) {
    return aWindow.docShell.QueryInterface(Ci.nsILoadContext);
  },

  get permanentPrivateBrowsing() {
    try {
      return (
        gTemporaryAutoStartMode || Services.prefs.getBoolPref(kAutoStartPref)
      );
    } catch (e) {
      // The pref does not exist
      return false;
    }
  },

  // These should only be used from internal code
  enterTemporaryAutoStartMode: function pbu_enterTemporaryAutoStartMode() {
    gTemporaryAutoStartMode = true;
  },
  get isInTemporaryAutoStartMode() {
    return gTemporaryAutoStartMode;
  },
};
PK
!<�J��!!modules/ProcessType.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

export const ProcessType = Object.freeze({
  /**
   * Converts a key string to a fluent ID defined in processTypes.ftl.
   */
  kProcessTypeMap: {
    // Keys defined in xpcom/build/GeckoProcessTypes.h
    default: "process-type-default",
    gpu: "process-type-gpu",
    tab: "process-type-tab",
    rdd: "process-type-rdd",
    socket: "process-type-socket",
    utility: "process-type-utility",
    forkServer: "process-type-forkserver",

    // Utility with actor names
    utility_audioDecoder_Generic:
      "process-type-utility-actor-audio-decoder-generic",
    utility_audioDecoder_AppleMedia:
      "process-type-utility-actor-audio-decoder-applemedia",
    utility_audioDecoder_WMF: "process-type-utility-actor-audio-decoder-wmf",
    utility_mfMediaEngineCDM: "process-type-utility-actor-mf-media-engine",
    utility_jSOracle: "process-type-utility-actor-js-oracle",
    utility_windowsUtils: "process-type-utility-actor-windows-utils",
    utility_windowsFileDialog: "process-type-utility-actor-windows-file-dialog",

    // Keys defined in dom/ipc/RemoteType.h
    extension: "process-type-extension",
    file: "process-type-file",
    inference: "process-type-inference",
    prealloc: "process-type-prealloc",
    privilegedabout: "process-type-privilegedabout",
    privilegedmozilla: "process-type-privilegedmozilla",
    web: "process-type-web",
    webIsolated: "process-type-webisolated",
    webServiceWorker: "process-type-webserviceworker",
  },

  kFallback: "process-type-unknown",

  fluentNameFromProcessTypeString(type) {
    return this.kProcessTypeMap[type] || this.kFallback;
  },
});
PK
!<{0�[��modules/ProfileAge.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Log } from "resource://gre/modules/Log.sys.mjs";

const FILE_TIMES = "times.json";

/**
 * Traverse the contents of the profile directory, finding the oldest file
 * and returning its creation timestamp.
 */
async function getOldestProfileTimestamp(profilePath, log) {
  let start = Date.now();
  let oldest = start + 1000;
  log.debug("Iterating over profile " + profilePath);

  try {
    for (const childPath of await IOUtils.getChildren(profilePath)) {
      try {
        let info = await IOUtils.stat(childPath);
        let timestamp;
        if (info.creationTime !== undefined) {
          timestamp = info.creationTime;
        } else {
          // We only support file creation times on Mac and Windows. We have to
          // settle for mtime on Linux.
          log.debug("No birth date. Using mtime.");
          timestamp = info.lastModified;
        }

        log.debug(`Using date: ${childPath} = ${timestamp}`);
        if (timestamp < oldest) {
          oldest = timestamp;
        }
      } catch (e) {
        // Never mind.
        log.debug("Stat failure", e);
      }
    }
  } catch (reason) {
    throw new Error("Unable to fetch oldest profile entry: " + reason);
  }

  return oldest;
}

/**
 * Profile access to times.json (eg, creation/reset time).
 * This is separate from the provider to simplify testing and enable extraction
 * to a shared location in the future.
 */
class ProfileAgeImpl {
  constructor(profile, times) {
    this._profilePath = profile;
    this._times = times;
    this._log = Log.repository.getLogger("Toolkit.ProfileAge");

    if ("firstUse" in this._times && this._times.firstUse === null) {
      // Indicates that this is a new profile that needs a first use timestamp.
      this._times.firstUse = Date.now();
      this.writeTimes();
    }
  }

  get profilePath() {
    if (!this._profilePath) {
      this._profilePath = Services.dirsvc.get("ProfD", Ci.nsIFile).path;
    }

    return this._profilePath;
  }

  /**
   * There are two ways we can get our creation time:
   *
   * 1. From the on-disk JSON file.
   * 2. By calculating it from the filesystem.
   *
   * If we have to calculate, we write out the file; if we have
   * to touch the file, we persist in-memory.
   *
   * @return a promise that resolves to the profile's creation time.
   */
  get created() {
    // This can be an expensive operation so make sure we only do it once.
    if (this._created) {
      return this._created;
    }

    if (!this._times.created) {
      this._created = this.computeAndPersistCreated();
    } else {
      this._created = Promise.resolve(this._times.created);
    }

    return this._created;
  }

  /**
   * Returns a promise to the time of first use of the profile. This may be
   * undefined if the first use time is unknown.
   */
  get firstUse() {
    if ("firstUse" in this._times) {
      return Promise.resolve(this._times.firstUse);
    }
    return Promise.resolve(undefined);
  }

  /**
   * Return a promise representing the writing the current times to the profile.
   */
  async writeTimes() {
    try {
      await IOUtils.writeJSON(
        PathUtils.join(this.profilePath, FILE_TIMES),
        this._times
      );
    } catch (e) {
      if (
        !DOMException.isInstance(e) ||
        e.name !== "AbortError" ||
        e.message !== "IOUtils: Shutting down and refusing additional I/O tasks"
      ) {
        throw e;
      }
    }
  }

  /**
   * Calculates the created time by scanning the profile directory, sets it in
   * the current set of times and persists it to the profile. Returns a promise
   * that resolves when all of that is complete.
   */
  async computeAndPersistCreated() {
    let oldest = await getOldestProfileTimestamp(this.profilePath, this._log);
    this._times.created = oldest;
    await this.writeTimes();
    return oldest;
  }

  /**
   * Record (and persist) when a profile reset happened.  We just store a
   * single value - the timestamp of the most recent reset - but there is scope
   * to keep a list of reset times should our health-reporter successor
   * be able to make use of that.
   * Returns a promise that is resolved once the file has been written.
   */
  recordProfileReset(time = Date.now()) {
    this._times.reset = time;
    return this.writeTimes();
  }

  /* Returns a promise that resolves to the time the profile was reset,
   * or undefined if not recorded.
   */
  get reset() {
    if ("reset" in this._times) {
      return Promise.resolve(this._times.reset);
    }
    return Promise.resolve(undefined);
  }

  /**
   * Record (and persist) when a backup recovery happened.  We just store a
   * single value - the timestamp at the time of recovery.
   *
   * Returns a promise that is resolved once the file has been written.
   */
  recordRecoveredFromBackup(time = Date.now()) {
    this._times.recoveredFromBackup = time;
    return this.writeTimes();
  }

  /* Returns a promise that resolves to the time the profile was recovered from
   * a backup or undefined if not recorded.
   */
  get recoveredFromBackup() {
    if ("recoveredFromBackup" in this._times) {
      return Promise.resolve(this._times.recoveredFromBackup);
    }
    return Promise.resolve(undefined);
  }
}

// A Map from profile directory to a promise that resolves to the ProfileAgeImpl.
const PROFILES = new Map();

async function initProfileAge(profile) {
  let timesPath = PathUtils.join(profile, FILE_TIMES);

  try {
    let times = await IOUtils.readJSON(timesPath);
    return new ProfileAgeImpl(profile, times || {});
  } catch (e) {
    // Indicates that the file was missing or broken. In this case we want to
    // record the first use time as now. The constructor will set this and write
    // times.json
    return new ProfileAgeImpl(profile, { firstUse: null });
  }
}

/**
 * Returns a promise that resolves to an instance of ProfileAgeImpl. Will always
 * return the same instance for every call for the same profile.
 *
 * @param {string} profile The path to the profile directory.
 * @return {Promise<ProfileAgeImpl>} Resolves to the ProfileAgeImpl.
 */
export function ProfileAge(profile) {
  if (!profile) {
    profile = PathUtils.profileDir;
  }

  if (PROFILES.has(profile)) {
    return PROFILES.get(profile);
  }

  let promise = initProfileAge(profile);
  PROFILES.set(profile, promise);
  return promise;
}
PK
!<+�A!:!:modules/PromiseWorker.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * A wrapper around ChromeWorker with extended capabilities designed
 * to simplify main thread-to-worker thread asynchronous function calls.
 *
 * This wrapper:
 * - groups requests and responses as a method `post` that returns a `Promise`;
 * - ensures that exceptions thrown on the worker thread are correctly deserialized;
 * - provides some utilities for benchmarking various operations.
 *
 * Generally, you should use PromiseWorker.sys.mjs along with its worker-side
 * counterpart PromiseWorker.js.
 */

/**
 * Constructors for decoding standard exceptions received from the
 * worker.
 */
const EXCEPTION_CONSTRUCTORS = {
  EvalError(error) {
    let result = new EvalError(error.message, error.fileName, error.lineNumber);
    result.stack = error.stack;
    return result;
  },
  InternalError(error) {
    let result = new InternalError(
      error.message,
      error.fileName,
      error.lineNumber
    );
    result.stack = error.stack;
    return result;
  },
  RangeError(error) {
    let result = new RangeError(
      error.message,
      error.fileName,
      error.lineNumber
    );
    result.stack = error.stack;
    return result;
  },
  ReferenceError(error) {
    let result = new ReferenceError(
      error.message,
      error.fileName,
      error.lineNumber
    );
    result.stack = error.stack;
    return result;
  },
  SyntaxError(error) {
    let result = new SyntaxError(
      error.message,
      error.fileName,
      error.lineNumber
    );
    result.stack = error.stack;
    return result;
  },
  TypeError(error) {
    let result = new TypeError(error.message, error.fileName, error.lineNumber);
    result.stack = error.stack;
    return result;
  },
  URIError(error) {
    let result = new URIError(error.message, error.fileName, error.lineNumber);
    result.stack = error.stack;
    return result;
  },
  DOMException(error) {
    let result = new DOMException(error.message, error.name);
    return result;
  },
};

/**
 * An object responsible for dispatching messages to a chrome worker
 * and routing the responses.
 *
 * Instances of this constructor who need logging may provide a method
 * `log: function(...args) { ... }` in charge of printing out (or
 * discarding) logs.
 *
 * Instances of this constructor may add exception handlers to
 * `this.ExceptionHandlers`, if they need to handle custom exceptions.
 *
 * @param {string} url The url containing the source code for this worker,
 * as in constructor ChromeWorker.
 *
 * @param {WorkerOptions} options The option parameter for ChromeWorker.
 *
 * @param {Record<String, function>} functions Functions that the worker can call.
 *
 * Functions can be synchronous functions or promises and return a value
 * that is sent back to the worker. The function can also send back a
 * `BasePromiseWorker.Meta` object containing the data and an array of transferrable
 * objects to transfer data to the worker with zero memory copy via `postMessage`.
 *
 * Example of sunch a function:
 *
 *   async function threadFunction(message) {
 *     return new BasePromiseWorker.Meta(
 *         ["data1", "data2", someBuffer],
 *         {transfers: [someBuffer]}
 *     );
 *   }
 *
 * @constructor
 */
export var BasePromiseWorker = function (url, options = {}, functions = {}) {
  if (typeof url != "string") {
    throw new TypeError("Expecting a string");
  }
  this._url = url;
  this._options = options;
  this._functions = functions;

  /**
   * A set of methods, with the following
   *
   * ConstructorName: function({message, fileName, lineNumber}) {
   *   // Construct a new instance of ConstructorName based on
   *   // `message`, `fileName`, `lineNumber`
   * }
   *
   * By default, this covers EvalError, InternalError, RangeError,
   * ReferenceError, SyntaxError, TypeError, URIError.
   */
  this.ExceptionHandlers = Object.create(EXCEPTION_CONSTRUCTORS);

  /**
   * The map of deferred, waiting for the completion of their
   * respective job by the worker.
   *
   * Each item in the map may contain an additional field |closure|,
   * used to store strong references to value that must not be
   * garbage-collected before the reply has been received (e.g.
   * arrays).
   *
   * @type {Map<string, {deferred, closure, id}>}
   */
  this._deferredJobs = new Map();

  /**
   * The number of the current message.
   *
   * Used for debugging purposes.
   */
  this._deferredJobId = 0;

  /**
   * The instant at which the worker was launched.
   */
  this.launchTimeStamp = null;

  /**
   * Timestamps provided by the worker for statistics purposes.
   */
  this.workerTimeStamps = null;
};

BasePromiseWorker.prototype = {
  log() {
    // By Default, ignore all logs.
  },

  _generateDeferredJobId() {
    this._deferredJobId += 1;
    return "ThreadToWorker-" + this._deferredJobId;
  },

  /**
   * Instantiate the worker lazily.
   */
  get _worker() {
    if (this.__worker) {
      return this.__worker;
    }

    let worker = (this.__worker = new ChromeWorker(this._url, this._options));

    // We assume that we call to _worker for the purpose of calling
    // postMessage().
    this.launchTimeStamp = Date.now();

    /**
     * Receive errors that have been serialized by the built-in mechanism
     * of DOM/Chrome Workers.
     *
     * PromiseWorker.js  knows how  to  serialize a  number of  errors
     * without    losing   information.    These   are    treated   by
     * |worker.onmessage|.   However, for  other  errors,  we rely  on
     * DOM's mechanism  for serializing errors, which  transmits these
     * errors through |worker.onerror|.
     *
     * @param {Error} error Some JS error.
     */
    worker.onerror = error => {
      this.log(
        "Received uncaught error from worker",
        error.message,
        error.filename,
        error.lineno
      );

      error.preventDefault();

      if (this._deferredJobs.size > 0) {
        this._deferredJobs.forEach(job => {
          job.deferred.reject(error);
        });
        this._deferredJobs.clear();
      }
    };

    /**
     * Receive messages from the worker, propagate them to the listeners.
     *
     * Messages must have one of the following shapes:
     * - {ok: some_value} in case of success
     * - {fail: some_error} in case of error, where
     *    some_error is an instance of |PromiseWorker.WorkerError|
     *
     * Messages also contains the following fields:
     * - |id| an integer matching the deferred function to resolve (mandatory)
     * - |fun| a string matching a function to call (optional)
     * - |durationMs| holding the duration of the function call in milliseconds. (optional)
     *
     * @param {*} msg The message received from the worker.
     */
    worker.onmessage = msg => {
      let data = msg.data;
      let messageId = data.id;

      this.log(`Received message ${messageId} from worker`);

      if ("timeStamps" in data) {
        this.workerTimeStamps = data.timeStamps;
      }

      // If fun is provided by the worker, we look into the functions
      if ("fun" in data) {
        if (data.fun in this._functions) {
          Promise.resolve(this._functions[data.fun](...data.args)).then(
            ok => {
              if (ok instanceof BasePromiseWorker.Meta) {
                if ("transfers" in ok.meta) {
                  worker.postMessage(
                    { ok: ok.data, id: messageId },
                    ok.meta.transfers
                  );
                } else {
                  worker.postMessage({ ok: ok.data, id: messageId });
                }
              } else {
                worker.postMessage({ id: messageId, ok });
              }
            },
            fail => {
              worker.postMessage({ id: messageId, fail });
            }
          );
        } else {
          worker.postMessage({
            id: messageId,
            fail: `function ${data.fun} not found`,
          });
        }
        return;
      }

      // If the message id matches one of the promise that waits, we resolve/reject with the data
      if (this._deferredJobs.has(messageId)) {
        let handler = this._deferredJobs.get(messageId);
        let deferred = handler.deferred;

        if ("ok" in data) {
          // Pass the data to the listeners.
          deferred.resolve(data);
        } else if ("fail" in data) {
          // We have received an error that was serialized by the
          // worker.
          deferred.reject(new WorkerError(data.fail));
        }
        return;
      }

      // in any other case, this is an unexpected message from the worker.
      throw new Error(
        `Unexpected message id ${messageId}, data: ${JSON.stringify(data)} `
      );
    };
    return worker;
  },

  /**
   * Post a message to a worker.
   *
   * @param {string} fun The name of the function to call.
   * @param {Array} args The arguments to pass to `fun`. If any
   * of the arguments is a Promise, it is resolved before posting the
   * message. If any of the arguments needs to be transfered instead
   * of copied, this may be specified by making the argument an instance
   * of `BasePromiseWorker.Meta` or by using the `transfers` argument.
   * By convention, the last argument may be an object `options`
   * with some of the following fields:
   * - {number|null} outExecutionDuration A parameter to be filled with the
   *   duration of the off main thread execution for this call.
   * @param {*=} closure An object holding references that should not be
   * garbage-collected before the message treatment is complete.
   * @param {Array=} transfers An array of objects that should be transfered
   * to the worker instead of being copied. If any of the objects is a Promise,
   * it is resolved before posting the message.
   *
   * @return {promise}
   */
  post(fun, args, closure, transfers) {
    return async function postMessage() {
      // Normalize in case any of the arguments is a promise
      if (args) {
        args = await Promise.resolve(Promise.all(args));
      }
      if (transfers) {
        transfers = await Promise.resolve(Promise.all(transfers));
      } else {
        transfers = [];
      }

      if (args) {
        // Extract `Meta` data
        args = args.map(arg => {
          if (arg instanceof BasePromiseWorker.Meta) {
            if (arg.meta && "transfers" in arg.meta) {
              transfers.push(...arg.meta.transfers);
            }
            return arg.data;
          }
          return arg;
        });
      }

      let id = this._generateDeferredJobId();
      let message = { fun, args, id };
      this.log("Posting message", JSON.stringify(message));
      try {
        this._worker.postMessage(message, ...[transfers]);
      } catch (ex) {
        if (typeof ex == "number") {
          this.log("Could not post message", message, "due to xpcom error", ex);
          // handle raw xpcom errors (see eg bug 961317)
          throw new Components.Exception("Error in postMessage", ex);
        }

        this.log("Could not post message", message, "due to error", ex);
        throw ex;
      }

      let deferred = Promise.withResolvers();
      this._deferredJobs.set(id, { deferred, closure, id });

      let reply;
      try {
        this.log("Expecting reply");
        reply = await deferred.promise;
      } catch (error) {
        this.log("Got error", JSON.stringify(error));
        reply = error;

        if (error instanceof WorkerError) {
          // We know how to deserialize most well-known errors
          throw this.ExceptionHandlers[error.data.exn](error.data);
        }

        if (ErrorEvent.isInstance(error)) {
          // Other errors get propagated as instances of ErrorEvent
          this.log(
            "Error serialized by DOM",
            error.message,
            error.filename,
            error.lineno
          );
          throw new Error(error.message, error.filename, error.lineno);
        }

        // We don't know about this kind of error
        throw error;
      }

      // By convention, the last argument may be an object `options`.
      let options = null;
      if (args) {
        options = args[args.length - 1];
      }

      // Check for duration and return result.
      if (
        !options ||
        typeof options !== "object" ||
        !("outExecutionDuration" in options)
      ) {
        return reply.ok;
      }
      // If reply.durationMs is not present, just return the result,
      // without updating durations (there was an error in the method
      // dispatch).
      if (!("durationMs" in reply)) {
        return reply.ok;
      }
      // Bug 874425 demonstrates that two successive calls to Date.now()
      // can actually produce an interval with negative duration.
      // We assume that this is due to an operation that is so short
      // that Date.now() is not monotonic, so we round this up to 0.
      let durationMs = Math.max(0, reply.durationMs);
      // Accumulate (or initialize) outExecutionDuration
      if (typeof options.outExecutionDuration == "number") {
        options.outExecutionDuration += durationMs;
      } else {
        options.outExecutionDuration = durationMs;
      }
      return reply.ok;
    }.bind(this)();
  },

  /**
   * Terminate the worker, if it has been created at all, and set things up to
   * be instantiated lazily again on the next `post()`.
   * If there are pending Promises in the jobs, we'll reject them and clear it.
   */
  terminate() {
    if (!this.__worker) {
      return;
    }

    try {
      this.__worker.terminate();
      delete this.__worker;
    } catch (ex) {
      // Ignore exceptions, only log them.
      this.log("Error whilst terminating ChromeWorker: " + ex.message);
    }

    if (this._deferredJobs.size) {
      let error = new Error("Internal error: worker terminated");
      this._deferredJobs.forEach(job => {
        job.deferred.reject(error);
      });
      this._deferredJobs.clear();
    }
  },
};

/**
 * An error that has been serialized by the worker.
 *
 * @constructor
 */
function WorkerError(data) {
  this.data = data;
}

/**
 * A constructor used to send data to the worker thread while
 * with special treatment (e.g. transmitting data instead of
 * copying it).
 *
 * @param {object=} data The data to send to the caller thread.
 * @param {object=} meta Additional instructions, as an object
 * that may contain the following fields:
 * - {Array} transfers An array of objects that should be transferred
 *   instead of being copied.
 *
 * @constructor
 */
BasePromiseWorker.Meta = function (data, meta) {
  this.data = data;
  this.meta = meta;
};
PK
!<��w��modules/PromptUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

export var PromptUtils = {
  // Fire a dialog open/close event. Used by tabbrowser to focus the
  // tab which is triggering a prompt.
  // For remote dialogs, we pass in a different DOM window and a separate
  // target. If the caller doesn't pass in the target, then we'll simply use
  // the passed-in DOM window.
  // The detail may contain information about the principal on which the
  // prompt is triggered, as well as whether or not this is a tabprompt
  // (ie tabmodal alert/prompt/confirm and friends)
  fireDialogEvent(domWin, eventName, maybeTarget, detail) {
    let target = maybeTarget || domWin;
    let eventOptions = { cancelable: true, bubbles: true };
    if (detail) {
      eventOptions.detail = detail;
    }
    let event = new domWin.CustomEvent(eventName, eventOptions);
    let winUtils = domWin.windowUtils;
    winUtils.dispatchEventToChromeOnly(target, event);
  },

  objectToPropBag(obj) {
    let bag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
      Ci.nsIWritablePropertyBag2
    );
    bag.QueryInterface(Ci.nsIWritablePropertyBag);

    for (let propName in obj) {
      bag.setProperty(propName, obj[propName]);
    }

    return bag;
  },

  propBagToObject(propBag, obj) {
    // Here we iterate over the object's original properties, not the bag
    // (ie, the prompt can't return more/different properties than were
    // passed in). This just helps ensure that the caller provides default
    // values, lest the prompt forget to set them.
    for (let propName in obj) {
      obj[propName] = propBag.getProperty(propName);
    }
  },
};

/**
 * This helper handles the enabling/disabling of dialogs that might
 * be subject to fast-clicking attacks. It handles the initial delayed
 * enabling of the dialog, as well as disabling it on blur and reapplying
 * the delay when the dialog regains focus.
 *
 * @param enableDialog   A custom function to be called when the dialog
 *                       is to be enabled.
 * @param diableDialog   A custom function to be called when the dialog
 *                       is to be disabled.
 * @param focusTarget    The window used to watch focus/blur events.
 */
export var EnableDelayHelper = function ({
  enableDialog,
  disableDialog,
  focusTarget,
}) {
  this.enableDialog = makeSafe(enableDialog);
  this.disableDialog = makeSafe(disableDialog);
  this.focusTarget = focusTarget;

  this.disableDialog();

  this.focusTarget.addEventListener("blur", this);
  this.focusTarget.addEventListener("focus", this);
  // While the user key-repeats, we want to renew the timer until keyup:
  this.focusTarget.addEventListener("keyup", this, true);
  this.focusTarget.addEventListener("keydown", this, true);
  this.focusTarget.document.addEventListener("unload", this);

  // If we're not part of the active window, don't even start the timer yet.
  let topWin = focusTarget.browsingContext.top.window;
  if (topWin != Services.focus.activeWindow) {
    return;
  }

  this.startOnFocusDelay();
};

EnableDelayHelper.prototype = {
  get delayTime() {
    return Services.prefs.getIntPref("security.dialog_enable_delay");
  },

  handleEvent(event) {
    if (
      !event.type.startsWith("key") &&
      event.target != this.focusTarget &&
      event.target != this.focusTarget.document
    ) {
      return;
    }

    switch (event.type) {
      case "keyup":
        // As soon as any key goes up, we can stop treating keypresses
        // as indicative of key-repeating that should prolong the timer.
        this.focusTarget.removeEventListener("keyup", this, true);
        this.focusTarget.removeEventListener("keydown", this, true);
        break;

      case "keydown":
        // Renew timer for repeating keydowns:
        if (this._focusTimer) {
          this._focusTimer.cancel();
          this._focusTimer = null;
          this.startOnFocusDelay();
          event.preventDefault();
        }
        break;

      case "blur":
        this.onBlur();
        break;

      case "focus":
        this.onFocus();
        break;

      case "unload":
        this.onUnload();
        break;
    }
  },

  onBlur() {
    this.disableDialog();
    // If we blur while waiting to enable the buttons, just cancel the
    // timer to ensure the delay doesn't fire while not focused.
    if (this._focusTimer) {
      this._focusTimer.cancel();
      this._focusTimer = null;
    }
  },

  onFocus() {
    this.startOnFocusDelay();
  },

  onUnload() {
    this.focusTarget.removeEventListener("blur", this);
    this.focusTarget.removeEventListener("focus", this);
    this.focusTarget.removeEventListener("keyup", this, true);
    this.focusTarget.removeEventListener("keydown", this, true);
    this.focusTarget.document.removeEventListener("unload", this);

    if (this._focusTimer) {
      this._focusTimer.cancel();
      this._focusTimer = null;
    }

    this.focusTarget = this.enableDialog = this.disableDialog = null;
  },

  startOnFocusDelay() {
    if (this._focusTimer) {
      return;
    }

    this._focusTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    this._focusTimer.initWithCallback(
      () => {
        this.onFocusTimeout();
      },
      this.delayTime,
      Ci.nsITimer.TYPE_ONE_SHOT
    );
  },

  onFocusTimeout() {
    this._focusTimer = null;
    this.enableDialog();
  },
};

function makeSafe(fn) {
  return function () {
    // The dialog could be gone by now (if the user closed it),
    // which makes it likely that the given fn might throw.
    try {
      fn();
    } catch (e) {}
  };
}
PK
!<�m�9��modules/Prompter.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

// This is redefined below, for strange and unfortunate reasons.
import { PromptUtils } from "resource://gre/modules/PromptUtils.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  ClipboardContextMenu: "resource://gre/modules/ClipboardContextMenu.sys.mjs",
});

const {
  MODAL_TYPE_TAB,
  MODAL_TYPE_CONTENT,
  MODAL_TYPE_WINDOW,
  MODAL_TYPE_INTERNAL_WINDOW,
} = Ci.nsIPrompt;

const COMMON_DIALOG = "chrome://global/content/commonDialog.xhtml";
const SELECT_DIALOG = "chrome://global/content/selectDialog.xhtml";

export function Prompter() {
  // Note that EmbedPrompter clones this implementation.
}

/**
 * Implements nsIPromptService and nsIPromptFactory
 * @class Prompter
 */
Prompter.prototype = {
  classID: Components.ID("{1c978d25-b37f-43a8-a2d6-0c7a239ead87}"),
  QueryInterface: ChromeUtils.generateQI([
    "nsIPromptFactory",
    "nsIPromptService",
  ]),

  /* ----------  private members  ---------- */

  pickPrompter(options) {
    return new ModalPrompter(options);
  },

  /* ----------  nsIPromptFactory  ---------- */

  getPrompt(domWin, iid) {
    // This is still kind of dumb; the C++ code delegated to login manager
    // here, which in turn calls back into us via nsIPromptService.
    if (iid.equals(Ci.nsIAuthPrompt2) || iid.equals(Ci.nsIAuthPrompt)) {
      try {
        let pwmgr = Cc[
          "@mozilla.org/passwordmanager/authpromptfactory;1"
        ].getService(Ci.nsIPromptFactory);
        return pwmgr.getPrompt(domWin, iid);
      } catch (e) {
        console.error("nsPrompter: Delegation to password manager failed: ", e);
      }
    }

    let p = new ModalPrompter({ domWin });
    p.QueryInterface(iid);
    return p;
  },

  /* ----------  nsIPromptService  ---------- */

  /**
   * Puts up an alert dialog with an OK button.
   * @param {mozIDOMWindowProxy} domWin - The parent window or null.
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   */
  alert(domWin, title, text) {
    let p = this.pickPrompter({ domWin });
    p.alert(title, text);
  },

  /**
   * Puts up an alert dialog with an OK button.
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   */
  alertBC(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType });
    p.alert(...promptArgs);
  },

  /**
   * Puts up an alert dialog with an OK button.
   *
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @returns {Promise} A promise which resolves when the prompt is dismissed.
   */
  asyncAlert(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType, async: true });
    return p.alert(...promptArgs);
  },

  /**
   * Puts up an alert dialog with an OK button and a labeled checkbox.
   * @param {mozIDOMWindowProxy} domWin - The parent window or null.
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {String} checkLabel - Text to appear with the checkbox.
   * @param {Object} checkValue - Contains the initial checked state of the
   *        checkbox when this method is called and the final checked state
   *        after this method returns.
   */
  alertCheck(domWin, title, text, checkLabel, checkValue) {
    let p = this.pickPrompter({ domWin });
    p.alertCheck(title, text, checkLabel, checkValue);
  },

  /**
   * Puts up an alert dialog with an OK button and a labeled checkbox.
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {String} checkLabel - Text to appear with the checkbox.
   * @param {Object} checkValue - Contains the initial checked state of the
   *        checkbox when this method is called and the final checked state
   *        after this method returns.
   */
  alertCheckBC(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType });
    p.alertCheck(...promptArgs);
  },

  /**
   * Puts up an alert dialog with an OK button and a labeled checkbox.
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {String} checkLabel - Text to appear with the checkbox.
   * @param {Boolean} checkValue - The initial checked state of the checkbox.
   * @returns {Promise<nsIPropertyBag<{ checked: Boolean }>>}
   *          A promise which resolves when the prompt is dismissed.
   */
  asyncAlertCheck(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType, async: true });
    return p.alertCheck(...promptArgs);
  },

  /**
   * Puts up a dialog with OK and Cancel buttons.
   * @param {mozIDOMWindowProxy} domWin - The parent window or null.
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @returns {Boolean} true for OK, false for Cancel.
   */
  confirm(domWin, title, text) {
    let p = this.pickPrompter({ domWin });
    return p.confirm(title, text);
  },

  /**
   * Puts up a dialog with OK and Cancel buttons.
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @returns {Boolean} true for OK, false for Cancel.
   */
  confirmBC(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType });
    return p.confirm(...promptArgs);
  },

  /**
   * Puts up a dialog with OK and Cancel buttons.
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @returns {Promise<nsIPropertyBag<{ ok: Boolean }>>}
   *          A promise which resolves when the prompt is dismissed.
   */
  asyncConfirm(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType, async: true });
    return p.confirm(...promptArgs);
  },

  /**
   * Puts up a dialog with OK and Cancel buttons and a labeled checkbox.
   * @param {mozIDOMWindowProxy} domWin - The parent window or null.
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {String} checkLabel - Text to appear with the checkbox.
   * @param {Object} checkValue - Contains the initial checked state of the
   *        checkbox when this method is called and the final checked state
   *        after this method returns.
   */
  confirmCheck(domWin, title, text, checkLabel, checkValue) {
    let p = this.pickPrompter({ domWin });
    return p.confirmCheck(title, text, checkLabel, checkValue);
  },

  /**
   * Puts up a dialog with OK and Cancel buttons and a labeled checkbox.
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {String} checkLabel - Text to appear with the checkbox.
   * @param {Object} checkValue - Contains the initial checked state of the
   *        checkbox when this method is called and the final checked state
   *        after this method returns.
   * @returns {Boolean} true for OK, false for Cancel
   */
  confirmCheckBC(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType });
    return p.confirmCheck(...promptArgs);
  },

  /**
   * Puts up a dialog with OK and Cancel buttons and a labeled checkbox.
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {String} checkLabel - Text to appear with the checkbox.
   * @param {Boolean} checkValue - The initial checked state of the checkbox.
   * @returns {Promise<nsIPropertyBag<{ ok: Boolean, checked: Boolean }>>}
   *          A promise which resolves when the prompt is dismissed.
   */
  asyncConfirmCheck(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType, async: true });
    return p.confirmCheck(...promptArgs);
  },

  /**
   * Puts up a dialog with up to 3 buttons and an optional, labeled checkbox.
   *
   * Buttons are numbered 0 - 2. Button 0 is the default button unless one of
   * the Button Default Flags is specified.
   *
   * A button may use a predefined title, specified by one of the Button Title
   * Flags values.  Each title value can be multiplied by a position value to
   * assign the title to a particular button.  If BUTTON_TITLE_IS_STRING is
   * used for a button, the string parameter for that button will be used.  If
   * the value for a button position is zero, the button will not be shown.
   *
   * In general, flags is constructed per the following example:
   *
   * flags = (BUTTON_POS_0) * (BUTTON_TITLE_AAA) +
   *         (BUTTON_POS_1) * (BUTTON_TITLE_BBB) +
   *         BUTTON_POS_1_DEFAULT;
   *
   * where "AAA" and "BBB" correspond to one of the button titles.
   *
   * @param {mozIDOMWindowProxy} domWin - The parent window or null.
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {Number} flags - A combination of Button Flags.
   * @param {String} button0 - Used when button 0 uses TITLE_IS_STRING.
   * @param {String} button1 - Used when button 1 uses TITLE_IS_STRING.
   * @param {String} button2 - Used when button 2 uses TITLE_IS_STRING.
   * @param {String} checkLabel - Text to appear with the checkbox.
   *        Null if no checkbox.
   * @param {Object} checkValue - Contains the initial checked state of the
   *        checkbox when this method
   *        is called and the final checked state after this method returns.
   * @returns {Number} The index of the button pressed.
   */
  confirmEx(
    domWin,
    title,
    text,
    flags,
    button0,
    button1,
    button2,
    checkLabel,
    checkValue
  ) {
    let p = this.pickPrompter({ domWin });
    return p.confirmEx(
      title,
      text,
      flags,
      button0,
      button1,
      button2,
      checkLabel,
      checkValue
    );
  },

  /**
   * Puts up a dialog with up to 3 buttons and an optional, labeled checkbox.
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {Number} flags - A combination of Button Flags.
   * @param {String} button0 - Used when button 0 uses TITLE_IS_STRING.
   * @param {String} button1 - Used when button 1 uses TITLE_IS_STRING.
   * @param {String} button2 - Used when button 2 uses TITLE_IS_STRING.
   * @param {String} checkLabel - Text to appear with the checkbox.
   *        Null if no checkbox.
   * @param {Object} checkValue - Contains the initial checked state of the
   *        checkbox when this method is called and the final checked state
   *        after this method returns.
   * @returns {Number} The index of the button pressed.
   */
  confirmExBC(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType });
    return p.confirmEx(...promptArgs);
  },

  /**
   * Puts up a dialog with up to 3 buttons and an optional, labeled checkbox.
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {Number} flags - A combination of Button Flags.
   * @param {String} button0 - Used when button 0 uses TITLE_IS_STRING.
   * @param {String} button1 - Used when button 1 uses TITLE_IS_STRING.
   * @param {String} button2 - Used when button 2 uses TITLE_IS_STRING.
   * @param {String} checkLabel - Text to appear with the checkbox.
   *        Null if no checkbox.
   * @param {Boolean} checkValue - The initial checked state of the checkbox.
   * @param {Object} [extraArgs] - Extra arguments for the prompt metadata.
   * @returns {Promise<nsIPropertyBag<{ buttonNumClicked: Number, checked: Boolean }>>}
   */
  asyncConfirmEx(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType, async: true });
    return p.confirmEx(...promptArgs);
  },

  /**
   * Puts up a dialog with an edit field and an optional, labeled checkbox.
   * @param {mozIDOMWindowProxy} domWin - The parent window or null.
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {Object} value - Contains the default value for the dialog field
   *        when this method is called (null value is ok).  Upon return, if
   *        the user pressed OK, then this parameter contains a newly
   *        allocated string value.
   *        Otherwise, the parameter's value is unmodified.
   * @param {String} checkLabel - Text to appear with the checkbox.
   *        If null, check box will not be shown.
   * @param {Object} checkValue - Contains the initial checked state of the
   *        checkbox when this method is called and the final checked state
   *        after this method returns.
   * @returns {Boolean} true for OK, false for Cancel.
   */
  prompt(domWin, title, text, value, checkLabel, checkValue) {
    let p = this.pickPrompter({ domWin });
    return p.nsIPrompt_prompt(title, text, value, checkLabel, checkValue);
  },

  /**
   * Puts up a dialog with an edit field and an optional, labeled checkbox.
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {Object} value - Contains the default value for the dialog field
   *        when this method is called (null value is ok).  Upon return, if
   *        the user pressed OK, then this parameter contains a newly
   *        allocated string value.
   *        Otherwise, the parameter's value is unmodified.
   * @param {String} checkLabel - Text to appear with the checkbox.
   *        If null, check box will not be shown.
   * @param {Object} checkValue - Contains the initial checked state of the
   *        checkbox when this method is called and the final checked state
   *        after this method returns.
   * @returns {Boolean} true for OK, false for Cancel.
   */
  promptBC(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType });
    return p.nsIPrompt_prompt(...promptArgs);
  },

  /**
   * Puts up a dialog with an edit field and an optional, labeled checkbox.
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {String} value - The default value for the dialog text field.
   * @param {String} checkLabel - Text to appear with the checkbox.
   *        If null, check box will not be shown.
   * @param {Boolean} checkValue - The initial checked state of the checkbox.
   * @returns {Promise<nsIPropertyBag<{ ok: Boolean, checked: Boolean, value: String }>>}
   *          A promise which resolves when the prompt is dismissed.
   */
  asyncPrompt(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType, async: true });
    return p.nsIPrompt_prompt(...promptArgs);
  },

  /**
   * Puts up a dialog with an edit field and a password field.
   * @param {mozIDOMWindowProxy} domWin - The parent window or null.
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {Object} user - Contains the default value for the username
   *        field when this method is called (null value is ok).
   *        Upon return, if the user pressed OK, then this parameter contains
   *        a newly allocated string value. Otherwise, the parameter's value
   *        is unmodified.
   * @param {Object} pass - Contains the default value for the password field
   *        when this method is called (null value is ok). Upon return, if the
   *        user pressed OK, this parameter contains a newly allocated string
   *        value. Otherwise, the parameter's value is unmodified.
   * @returns {Boolean} true for OK, false for Cancel.
   */
  promptUsernameAndPassword(domWin, title, text, user, pass) {
    let p = this.pickPrompter({ domWin });
    return p.nsIPrompt_promptUsernameAndPassword(null, title, text, user, pass);
  },

  /**
   * Puts up a dialog with an edit field and a password field.
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {Object} user - Contains the default value for the username
   *        field when this method is called (null value is ok).
   *        Upon return, if the user pressed OK, then this parameter contains
   *        a newly allocated string value. Otherwise, the parameter's value
   *        is unmodified.
   * @param {Object} pass - Contains the default value for the password field
   *        when this method is called (null value is ok). Upon return, if the
   *        user pressed OK, this parameter contains a newly allocated string
   *        value. Otherwise, the parameter's value is unmodified.
   * @returns {Boolean} true for OK, false for Cancel.
   */
  promptUsernameAndPasswordBC(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType });
    return p.nsIPrompt_promptUsernameAndPassword(null, ...promptArgs);
  },

  /**
   * Puts up a dialog with an edit field and a password field.
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {String} user - Default value for the username field.
   * @param {String} pass - Contains the default value for the password field.
   * @returns {Promise<nsIPropertyBag<{ ok: Boolean, user: String, pass: String }>>}
   *          A promise which resolves when the prompt is dismissed.
   */
  asyncPromptUsernameAndPassword(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType, async: true });
    return p.nsIPrompt_promptUsernameAndPassword(null, ...promptArgs);
  },

  /**
   * Puts up a dialog with a password field.
   * @param {mozIDOMWindowProxy} domWin - The parent window or null.
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {Object} pass - Contains the default value for the password field
   *        when this method is called (null value is ok). Upon return, if the
   *        user pressed OK, this parameter contains a newly allocated string
   *        value. Otherwise, the parameter's value is unmodified.
   * @returns {Boolean} true for OK, false for Cancel.
   */
  promptPassword(domWin, title, text, pass) {
    let p = this.pickPrompter({ domWin });
    return p.nsIPrompt_promptPassword(
      null, // no channel.
      title,
      text,
      pass
    );
  },

  /**
   * Puts up a dialog with a password field.
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {Object} pass - Contains the default value for the password field
   *        when this method is called (null value is ok). Upon return, if the
   *        user pressed OK, this parameter contains a newly allocated string
   *        value. Otherwise, the parameter's value is unmodified.
   * @returns {Boolean} true for OK, false for Cancel.
   */
  promptPasswordBC(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType });
    return p.nsIPrompt_promptPassword(null, ...promptArgs);
  },

  /**
   * Puts up a dialog with a password field.
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {String} pass - Contains the default value for the password field.
   * @returns {Promise<nsIPropertyBag<{ ok: Boolean, pass: String }>>}
   *          A promise which resolves when the prompt is dismissed.
   */
  asyncPromptPassword(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType, async: true });
    return p.nsIPrompt_promptPassword(null, ...promptArgs);
  },

  /**
   * Puts up a dialog box which has a list box of strings from which the user
   * may make a single selection.
   * @param {mozIDOMWindowProxy} domWin - The parent window or null.
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {String[]} list - The list of strings to display.
   * @param {Object} selected - Contains the index of the selected item in the
   *        list when this method returns true.
   * @returns {Boolean} true for OK, false for Cancel.
   */
  select(domWin, title, text, list, selected) {
    let p = this.pickPrompter({ domWin });
    return p.select(title, text, list, selected);
  },

  /**
   * Puts up a dialog box which has a list box of strings from which the user
   * may make a single selection.
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {String[]} list - The list of strings to display.
   * @param {Object} selected - Contains the index of the selected item in the
   *        list when this method returns true.
   * @returns {Boolean} true for OK, false for Cancel.
   */
  selectBC(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType });
    return p.select(...promptArgs);
  },

  /**
   * Puts up a dialog box which has a list box of strings from which the user
   * may make a single selection.
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {String} title - Text to appear in the title of the dialog.
   * @param {String} text - Text to appear in the body of the dialog.
   * @param {String[]} list - The list of strings to display.
   * @returns {Promise<nsIPropertyBag<{ selected: Number, ok: Boolean  }>>}
   *          A promise which resolves when the prompt is dismissed.
   */
  asyncSelect(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType, async: true });
    return p.select(...promptArgs);
  },

  /**
   * Requests a username and a password. Shows a dialog with username and
   * password field, depending on flags also a domain field.
   * @param {mozIDOMWindowProxy} domWin - The parent window or null.
   * @param {nsIChannel} channel - The channel that requires authentication.
   * @param {Number} level - Security level of the credential transmission.
   *        Any of nsIAuthPrompt2.<LEVEL_NONE|LEVEL_PW_ENCRYPTED|LEVEL_SECURE>
   * @param {nsIAuthInformation} authInfo - Authentication information object.
   * @returns {Boolean}
   *          true: Authentication can proceed using the values
   *          in the authInfo object.
   *          false: Authentication should be cancelled, usually because the
   *          user did not provide username/password.
   */
  promptAuth(domWin, channel, level, authInfo) {
    let p = this.pickPrompter({ domWin });
    return p.promptAuth(channel, level, authInfo);
  },

  /**
   * Requests a username and a password. Shows a dialog with username and
   * password field, depending on flags also a domain field.
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {nsIChannel} channel - The channel that requires authentication.
   * @param {Number} level - Security level of the credential transmission.
   *        Any of nsIAuthPrompt2.<LEVEL_NONE|LEVEL_PW_ENCRYPTED|LEVEL_SECURE>
   * @param {nsIAuthInformation} authInfo - Authentication information object.
   * @returns {Boolean}
   *          true: Authentication can proceed using the values
   *          in the authInfo object.
   *          false: Authentication should be cancelled, usually because the
   *          user did not provide username/password.
   */
  promptAuthBC(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType });
    return p.promptAuth(...promptArgs);
  },

  /**
   * Requests a username and a password. Shows a dialog with username and
   * password field, depending on flags also a domain field.
   * @param {BrowsingContext} browsingContext - The browsing context the
   *        prompt should be opened for.
   * @param {Number} modalType - The modal type of the prompt.
   *        nsIPromptService.<MODAL_TYPE_WINDOW|MODAL_TYPE_TAB|MODAL_TYPE_CONTENT>
   * @param {nsIChannel} channel - The channel that requires authentication.
   * @param {Number} level - Security level of the credential transmission.
   *        Any of nsIAuthPrompt2.<LEVEL_NONE|LEVEL_PW_ENCRYPTED|LEVEL_SECURE>
   * @param {nsIAuthInformation} authInfo - Authentication information object.
   * @returns {Promise<nsIPropertyBag<{ ok: Boolean }>>}
   *          A promise which resolves when the prompt is dismissed.
   */
  asyncPromptAuth(browsingContext, modalType, ...promptArgs) {
    let p = this.pickPrompter({ browsingContext, modalType, async: true });
    return p.promptAuth(...promptArgs);
  },

  /**
   * Displays a contextmenu to get user confirmation for clipboard read. Only
   * one context menu can be opened at a time.
   *
   * @param {WindowContext} windowContext - The window context that initiates
   *        the clipboard operation.
   * @returns {Promise<nsIPropertyBag<{ ok: Boolean }>>}
   *          A promise which resolves when the contextmenu is dismissed.
   */
  confirmUserPaste() {
    return lazy.ClipboardContextMenu.confirmUserPaste(...arguments);
  },
};

// Common utils not specific to a particular prompter style.
var InternalPromptUtils = {
  getLocalizedString(key, formatArgs) {
    if (formatArgs) {
      return this.strBundle.formatStringFromName(key, formatArgs);
    }
    return this.strBundle.GetStringFromName(key);
  },

  confirmExHelper(flags, button0, button1, button2) {
    const BUTTON_DEFAULT_MASK = 0x03000000;
    let defaultButtonNum = (flags & BUTTON_DEFAULT_MASK) >> 24;
    let isDelayEnabled = flags & Ci.nsIPrompt.BUTTON_DELAY_ENABLE;

    // Flags can be used to select a specific pre-defined button label or
    // a caller-supplied string (button0/button1/button2). If no flags are
    // set for a button, then the button won't be shown.
    let argText = [button0, button1, button2];
    let buttonLabels = [null, null, null];
    for (let i = 0; i < 3; i++) {
      let buttonLabel;
      switch (flags & 0xff) {
        case Ci.nsIPrompt.BUTTON_TITLE_OK:
          buttonLabel = this.getLocalizedString("OK");
          break;
        case Ci.nsIPrompt.BUTTON_TITLE_CANCEL:
          buttonLabel = this.getLocalizedString("Cancel");
          break;
        case Ci.nsIPrompt.BUTTON_TITLE_YES:
          buttonLabel = this.getLocalizedString("Yes");
          break;
        case Ci.nsIPrompt.BUTTON_TITLE_NO:
          buttonLabel = this.getLocalizedString("No");
          break;
        case Ci.nsIPrompt.BUTTON_TITLE_SAVE:
          buttonLabel = this.getLocalizedString("Save");
          break;
        case Ci.nsIPrompt.BUTTON_TITLE_DONT_SAVE:
          buttonLabel = this.getLocalizedString("DontSave");
          break;
        case Ci.nsIPrompt.BUTTON_TITLE_REVERT:
          buttonLabel = this.getLocalizedString("Revert");
          break;
        case Ci.nsIPrompt.BUTTON_TITLE_IS_STRING:
          buttonLabel = argText[i];
          break;
      }
      if (buttonLabel) {
        buttonLabels[i] = buttonLabel;
      }
      flags >>= 8;
    }

    return [
      buttonLabels[0],
      buttonLabels[1],
      buttonLabels[2],
      defaultButtonNum,
      isDelayEnabled,
    ];
  },

  getAuthInfo(authInfo) {
    let username, password;

    let flags = authInfo.flags;
    if (flags & Ci.nsIAuthInformation.NEED_DOMAIN && authInfo.domain) {
      username = authInfo.domain + "\\" + authInfo.username;
    } else {
      username = authInfo.username;
    }

    password = authInfo.password;

    return [username, password];
  },

  setAuthInfo(authInfo, username, password) {
    let flags = authInfo.flags;
    if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
      // Domain is separated from username by a backslash
      let idx = username.indexOf("\\");
      if (idx == -1) {
        authInfo.username = username;
      } else {
        authInfo.domain = username.substring(0, idx);
        authInfo.username = username.substring(idx + 1);
      }
    } else {
      authInfo.username = username;
    }
    authInfo.password = password;
  },

  /**
   * Strip out things like userPass and path for display.
   */
  getFormattedHostname(uri) {
    return uri.scheme + "://" + uri.hostPort;
  },

  // Note: there's a similar implementation in the login manager.
  getAuthTarget(aChannel, aAuthInfo) {
    let displayHost, realm;

    // If our proxy is demanding authentication, don't use the
    // channel's actual destination.
    if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
      if (!(aChannel instanceof Ci.nsIProxiedChannel)) {
        throw new Error("proxy auth needs nsIProxiedChannel");
      }

      let info = aChannel.proxyInfo;
      if (!info) {
        throw new Error("proxy auth needs nsIProxyInfo");
      }

      // Proxies don't have a scheme, but we'll use "moz-proxy://"
      // so that it's more obvious what the login is for.
      let idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
        Ci.nsIIDNService
      );
      displayHost =
        "moz-proxy://" +
        idnService.convertUTF8toACE(info.host) +
        ":" +
        info.port;
      realm = aAuthInfo.realm;
      if (!realm) {
        realm = displayHost;
      }

      return { realm, displayHost };
    }

    displayHost = this.getFormattedHostname(aChannel.URI);
    let displayHostOnly = aChannel.URI.hostPort;

    // If a HTTP WWW-Authenticate header specified a realm, that value
    // will be available here. If it wasn't set or wasn't HTTP, we'll use
    // the formatted hostname instead.
    realm = aAuthInfo.realm;
    if (!realm) {
      realm = displayHost;
    }

    return { realm, displayHostOnly, displayHost };
  },

  makeAuthMessage(prompt, channel, authInfo) {
    if (prompt.modalType != MODAL_TYPE_TAB) {
      return this._legacyMakeAuthMessage(channel, authInfo);
    }

    let isProxy = authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY;
    let isPassOnly = authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD;
    let isCrossOrig =
      authInfo.flags & Ci.nsIAuthInformation.CROSS_ORIGIN_SUB_RESOURCE;
    let username = authInfo.username;

    // We use the realm and displayHost only for proxy auth,
    // and the displayHostOnly (hostPort) only for x-origin auth prompts.
    // Otherwise we rely on the title of the dialog displaying the correct
    // title.
    let { displayHost, realm, displayHostOnly } = this.getAuthTarget(
      channel,
      authInfo
    );

    if (isProxy) {
      // The realm is server-controlled. Trim it if it's very long, to
      // avoid the dialog becoming unusable.
      // For background, see https://bugzilla.mozilla.org/show_bug.cgi?id=244273
      if (realm.length > 150) {
        realm = realm.substring(0, 150);
        // Append "..." (or localized equivalent).
        realm += this.ellipsis;
      }

      return this.getLocalizedString("EnterLoginForProxy3", [
        realm,
        displayHost,
      ]);
    }
    if (isPassOnly) {
      return this.getLocalizedString("EnterPasswordOnlyFor", [username]);
    }
    if (isCrossOrig) {
      return this.getLocalizedString("EnterCredentialsCrossOrigin", [
        displayHostOnly,
      ]);
    }
    return this.getLocalizedString("EnterCredentials");
  },

  _legacyMakeAuthMessage(channel, authInfo) {
    let isProxy = authInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY;
    let isPassOnly = authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD;
    let isCrossOrig =
      authInfo.flags & Ci.nsIAuthInformation.CROSS_ORIGIN_SUB_RESOURCE;

    let username = authInfo.username;
    let { displayHost, realm } = this.getAuthTarget(channel, authInfo);

    // Suppress "the site says: $realm" when we synthesized a missing realm.
    if (!authInfo.realm && !isProxy) {
      realm = "";
    }

    // The realm is server-controlled. Trim it if it's very long, to
    // avoid the dialog becoming unusable.
    // For background, see https://bugzilla.mozilla.org/show_bug.cgi?id=244273
    if (realm.length > 150) {
      realm = realm.substring(0, 150);
      // Append "..." (or localized equivalent).
      realm += this.ellipsis;
    }

    let text;
    if (isProxy) {
      text = this.getLocalizedString("EnterLoginForProxy3", [
        realm,
        displayHost,
      ]);
    } else if (isPassOnly) {
      text = this.getLocalizedString("EnterPasswordFor", [
        username,
        displayHost,
      ]);
    } else if (isCrossOrig) {
      text = this.getLocalizedString("EnterUserPasswordForCrossOrigin2", [
        displayHost,
      ]);
    } else if (!realm) {
      text = this.getLocalizedString("EnterUserPasswordFor2", [displayHost]);
    } else {
      text = this.getLocalizedString("EnterLoginForRealm3", [
        realm,
        displayHost,
      ]);
    }

    return text;
  },

  getBrandFullName() {
    return this.brandBundle.GetStringFromName("brandFullName");
  },
};

ChromeUtils.defineLazyGetter(InternalPromptUtils, "strBundle", function () {
  let bundle = Services.strings.createBundle(
    "chrome://global/locale/commonDialogs.properties"
  );
  if (!bundle) {
    throw new Error("String bundle for Prompter not present!");
  }
  return bundle;
});

ChromeUtils.defineLazyGetter(InternalPromptUtils, "brandBundle", function () {
  let bundle = Services.strings.createBundle(
    "chrome://branding/locale/brand.properties"
  );
  if (!bundle) {
    throw new Error("String bundle for branding not present!");
  }
  return bundle;
});

ChromeUtils.defineLazyGetter(InternalPromptUtils, "ellipsis", function () {
  let ellipsis = "\u2026";
  try {
    ellipsis = Services.prefs.getComplexValue(
      "intl.ellipsis",
      Ci.nsIPrefLocalizedString
    ).data;
  } catch (e) {}
  return ellipsis;
});

class ModalPrompter {
  constructor({
    browsingContext = null,
    domWin = null,
    modalType = null,
    async = false,
  }) {
    if (browsingContext && domWin) {
      throw new Error("Pass either browsingContext or domWin");
    }

    if (domWin) {
      // We have a domWin, get the associated browsing context
      this.browsingContext = BrowsingContext.getFromWindow(domWin);
    } else {
      this.browsingContext = browsingContext;
    }

    if (
      domWin &&
      (!modalType || modalType == MODAL_TYPE_WINDOW) &&
      !this.browsingContext?.isContent &&
      this.browsingContext?.associatedWindow?.gDialogBox
    ) {
      modalType = MODAL_TYPE_INTERNAL_WINDOW;
    }

    // Use given modal type or fallback to default
    this.modalType = modalType || ModalPrompter.defaultModalType;

    this.async = async;

    this.QueryInterface = ChromeUtils.generateQI([
      "nsIPrompt",
      "nsIAuthPrompt",
      "nsIAuthPrompt2",
      "nsIWritablePropertyBag2",
    ]);
  }

  set modalType(modalType) {
    // Setting modal type window is always allowed
    if (modalType == MODAL_TYPE_WINDOW) {
      this._modalType = modalType;
      return;
    }

    // For content prompts for non-content windows, use window prompts:
    if (modalType == MODAL_TYPE_CONTENT && !this.browsingContext?.isContent) {
      this._modalType = MODAL_TYPE_WINDOW;
      return;
    }

    // We can't use content / tab prompts if we don't have a suitable parent.
    if (
      !this.browsingContext?.isContent &&
      modalType != MODAL_TYPE_INTERNAL_WINDOW
    ) {
      // Only show this error if we're not about to fall back again and show a different one.
      if (this.browsingContext?.associatedWindow?.gDialogBox) {
        console.error(
          "Prompter: Browser not available. Falling back to internal window prompt."
        );
      }
      modalType = MODAL_TYPE_INTERNAL_WINDOW;
    }

    if (
      modalType == MODAL_TYPE_INTERNAL_WINDOW &&
      (this.browsingContext?.isContent ||
        !this.browsingContext?.associatedWindow?.gDialogBox)
    ) {
      console.error(
        "Prompter: internal dialogs not available in this context. Falling back to window prompt."
      );
      modalType = MODAL_TYPE_WINDOW;
    }

    this._modalType = modalType;
  }

  get modalType() {
    return this._modalType;
  }

  /* ---------- internal methods ---------- */

  /**
   * Synchronous wrapper around {@link ModalPrompter#openPrompt}
   * @param {Object} args Prompt arguments. When prompt has been closed, they are updated to reflect the result state.
   */
  openPromptSync(args) {
    let closed = false;
    this.openPrompt(args)
      .then(returnedArgs => {
        if (returnedArgs) {
          for (let key in returnedArgs) {
            args[key] = returnedArgs[key];
          }
        }
      })
      .finally(() => {
        closed = true;
      });
    Services.tm.spinEventLoopUntilOrQuit(
      "prompts/Prompter.sys.mjs:openPromptSync",
      () => closed
    );
  }

  async openPrompt(args) {
    if (!this.browsingContext) {
      // We don't have a browsing context, fallback to a window prompt.
      args.modalType = MODAL_TYPE_WINDOW;
      this.openWindowPrompt(null, args);
      return args;
    }

    if (this._modalType == MODAL_TYPE_INTERNAL_WINDOW) {
      await this.openInternalWindowPrompt(
        this.browsingContext.associatedWindow,
        args
      );
      return args;
    }

    // Select prompts are not part of CommonDialog
    // and thus not supported as tab or content prompts yet. See Bug 1622817.
    // Once they are integrated this override should be removed.
    if (args.promptType == "select" && this.modalType !== MODAL_TYPE_WINDOW) {
      console.error(
        "Prompter: 'select' prompts do not support tab/content prompting. Falling back to window prompt."
      );
      args.modalType = MODAL_TYPE_WINDOW;
    } else {
      args.modalType = this.modalType;
    }

    const IS_CONTENT =
      Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT;

    let actor;
    try {
      if (IS_CONTENT) {
        // When in the content, get the PromptChild actor.
        actor =
          this.browsingContext.window.windowGlobalChild.getActor("Prompt");
      } else {
        // When in the parent, get the PromptParent actor.
        actor = this.browsingContext.currentWindowGlobal.getActor("Prompt");
      }
    } catch (_) {
      // We can't get the prompt actor, fallback to window prompt.
      let parentWin;
      // If given a chrome BC we can try to get its window
      if (!this.browsingContext.isContent && this.browsingContext.window) {
        parentWin = this.browsingContext.window;
      } else {
        // Try to get the window which is the browsers parent
        parentWin = this.browsingContext.top?.embedderElement?.ownerGlobal;
      }
      this.openWindowPrompt(parentWin, args);
      return args;
    }

    /* For prompts with a channel, we want to show the origin requesting
     * authentication. This is different from the prompt principal,
     * which is based on the document loaded in the browsing context over
     * which the prompt appears. So if page foo.com loads bar.com, and the
     * latter asks for auth, we want that bar.com's origin, not foo.com.
     * To avoid confusion, we use different properties
     * (authOrigin / promptPrincipal) to track this information.
     */
    if (args.channel) {
      try {
        args.authOrigin = args.channel.URI.hostPort;
      } catch (ex) {
        args.authOrigin = args.channel.URI.prePath;
      }
      args.isInsecureAuth =
        args.channel.URI.schemeIs("http") &&
        !args.channel.loadInfo.isTopLevelLoad;
      // whether we are going to prompt the user for their credentials for a different base domain.
      // When true, auth prompt spoofing protection mechanisms will be triggered (see bug 791594).
      args.isTopLevelCrossDomainAuth = false;
      // We don't support auth prompt spoofing protections for sub resources and window prompts
      if (
        args.modalType == MODAL_TYPE_TAB &&
        args.channel.loadInfo.isTopLevelLoad
      ) {
        // check if this is a request from a third party
        try {
          args.isTopLevelCrossDomainAuth =
            this.browsingContext.currentWindowGlobal?.documentPrincipal?.isThirdPartyURI(
              args.channel.URI
            );
        } catch (e) {
          // isThirdPartyURI failes for about:/blob/data URIs
          console.warn("nsPrompter: isThirdPartyURI failed: " + e);
        }
      }
    } else {
      args.promptPrincipal =
        this.browsingContext.window?.document.nodePrincipal;
    }
    if (IS_CONTENT) {
      let docShell = this.browsingContext.docShell;
      let inPermitUnload = docShell?.docViewer?.inPermitUnload;
      args.inPermitUnload = inPermitUnload;
      let eventDetail = Cu.cloneInto(
        {
          tabPrompt: this.modalType != MODAL_TYPE_WINDOW,
          inPermitUnload,
        },
        this.browsingContext.window
      );
      PromptUtils.fireDialogEvent(
        this.browsingContext.window,
        "DOMWillOpenModalDialog",
        null,
        eventDetail
      );

      // Put content window in the modal state while the prompt is open.
      let windowUtils = this.browsingContext.window?.windowUtils;
      if (windowUtils) {
        windowUtils.enterModalState();
      }
    } else if (args.inPermitUnload) {
      args.promptPrincipal =
        this.browsingContext.currentWindowGlobal.documentPrincipal;
    }

    // It is technically possible for multiple prompts to be sent from a single
    // BrowsingContext. See bug 1266353. We use a randomly generated UUID to
    // differentiate between the different prompts.
    let id = "id" + Services.uuid.generateUUID().toString();

    args._remoteId = id;

    let returnedArgs;
    try {
      if (IS_CONTENT) {
        // If we're in the content process, send a message to the PromptParent
        // window actor.
        returnedArgs = await actor.sendQuery("Prompt:Open", args);
      } else {
        // If we're in the parent process we already have the parent actor.
        // We can call its message handler directly.
        returnedArgs = await actor.receiveMessage({
          name: "Prompt:Open",
          data: args,
        });
      }

      if (returnedArgs?.promptAborted) {
        throw Components.Exception(
          "prompt aborted by user",
          Cr.NS_ERROR_NOT_AVAILABLE
        );
      }
    } finally {
      if (IS_CONTENT) {
        let windowUtils = this.browsingContext.window?.windowUtils;
        if (windowUtils) {
          windowUtils.leaveModalState();
        }
        PromptUtils.fireDialogEvent(
          this.browsingContext.window,
          "DOMModalDialogClosed"
        );
      }
    }
    return returnedArgs;
  }

  /**
   * Open a window modal prompt
   *
   * There's an implied contract that says modal prompts should still work when
   * no "parent" window is passed for the dialog (eg, the "Master Password"
   * dialog does this).  These prompts must be shown even if there are *no*
   * visible windows at all.
   * We try and find a window to use as the parent, but don't consider if that
   * is visible before showing the prompt. parentWindow may still be null if
   * there are _no_ windows open.
   * @param {Window} [parentWindow] - The parent window for the prompt, may be
   *        null.
   * @param {Object} args - Prompt options and return values.
   */
  openWindowPrompt(parentWindow = null, args) {
    let uri = args.promptType == "select" ? SELECT_DIALOG : COMMON_DIALOG;
    let propBag = PromptUtils.objectToPropBag(args);
    Services.ww.openWindow(
      parentWindow || Services.ww.activeWindow,
      uri,
      "_blank",
      "centerscreen,chrome,modal,titlebar",
      propBag
    );
    PromptUtils.propBagToObject(propBag, args);
  }

  async openInternalWindowPrompt(parentWindow, args) {
    if (!parentWindow?.gDialogBox) {
      this.openWindowPrompt(parentWindow, args);
      return;
    }
    let propBag = PromptUtils.objectToPropBag(args);
    propBag.setProperty("async", this.async);
    let uri = args.promptType == "select" ? SELECT_DIALOG : COMMON_DIALOG;
    await parentWindow.gDialogBox.open(uri, propBag);
    propBag.deleteProperty("async");
    PromptUtils.propBagToObject(propBag, args);
  }

  /**
   * Calls async prompt method and optionally runs promise chained task on
   * result data. Converts result data to nsIPropertyBag.
   * @param {Object} args - Prompt arguments.
   * @param {Function} [task] - Function which is called with the modified
   *  prompt args object once the prompt has been closed. Must return a
   *  result object for the prompt caller.
   * @returns {Promise<nsIPropertyBag>} - Resolves with a property bag holding the
   * prompt result properties. Resolves once prompt has been closed.
   */
  async openPromptAsync(args, task) {
    let result = await this.openPrompt(args);
    // If task is not defined, the prompt method does not return
    // anything. In this case we can resolve without value.
    if (!task) {
      return undefined;
    }
    // Convert task result to nsIPropertyBag and resolve
    let taskResult = task(result);
    if (!(taskResult instanceof Object)) {
      throw new Error("task must return object");
    }
    return PromptUtils.objectToPropBag(taskResult);
  }

  /*
   * ---------- interface disambiguation ----------
   *
   * nsIPrompt and nsIAuthPrompt share 3 method names with slightly
   * different arguments. All but prompt() have the same number of
   * arguments, so look at the arg types to figure out how we're being
   * called. :-(
   */
  prompt() {
    // also, the nsIPrompt flavor has 5 args instead of 6.
    if (typeof arguments[2] == "object") {
      return this.nsIPrompt_prompt.apply(this, arguments);
    }
    return this.nsIAuthPrompt_prompt.apply(this, arguments);
  }

  promptUsernameAndPassword() {
    // Both have 6 args, so use types.
    if (typeof arguments[2] == "object") {
      // Add the null channel:
      let args = Array.from(arguments);
      args.unshift(null);
      return this.nsIPrompt_promptUsernameAndPassword.apply(this, args);
    }
    return this.nsIAuthPrompt_promptUsernameAndPassword.apply(this, arguments);
  }

  promptPassword() {
    // Both have 5 args, so use types.
    if (typeof arguments[2] == "object") {
      // Add the null channel:
      let args = Array.from(arguments);
      args.unshift(null);
      return this.nsIPrompt_promptPassword.apply(this, args);
    }
    return this.nsIAuthPrompt_promptPassword.apply(this, arguments);
  }

  /* ----------  nsIPrompt  ---------- */

  alert(title, text) {
    if (!title) {
      title = InternalPromptUtils.getLocalizedString("Alert");
    }

    let args = {
      promptType: "alert",
      title,
      text,
    };

    if (this.async) {
      return this.openPromptAsync(args);
    }

    return this.openPromptSync(args);
  }

  alertCheck(title, text, checkLabel, checkValue) {
    if (!title) {
      title = InternalPromptUtils.getLocalizedString("Alert");
    }

    // For sync calls checkValue is an XPCOM inout. XPCOM wraps primitves in
    // objects for call by reference.
    // The async version of this method uses call by value.
    let checked = this.async ? checkValue : checkValue.value;

    let args = {
      promptType: "alertCheck",
      title,
      text,
      checkLabel,
      checked,
    };

    if (this.async) {
      return this.openPromptAsync(args, result => ({
        checked: result.checked,
      }));
    }

    this.openPromptSync(args);
    checkValue.value = args.checked;
    return undefined;
  }

  confirm(title, text) {
    if (!title) {
      title = InternalPromptUtils.getLocalizedString("Confirm");
    }

    let args = {
      promptType: "confirm",
      title,
      text,
      ok: false,
    };

    if (this.async) {
      return this.openPromptAsync(args, result => ({ ok: result.ok }));
    }

    this.openPromptSync(args);
    return args.ok;
  }

  confirmCheck(title, text, checkLabel, checkValue) {
    if (!title) {
      title = InternalPromptUtils.getLocalizedString("ConfirmCheck");
    }

    let checked = this.async ? checkValue : checkValue.value;

    let args = {
      promptType: "confirmCheck",
      title,
      text,
      checkLabel,
      checked,
      ok: false,
    };

    if (this.async) {
      return this.openPromptAsync(args, result => ({
        // Checkbox state always returned, even if cancel clicked.
        checked: result.checked,
        // Did user click Ok or Cancel?
        ok: result.ok,
      }));
    }

    this.openPromptSync(args);
    checkValue.value = args.checked;
    return args.ok;
  }

  confirmEx(
    title,
    text,
    flags,
    button0,
    button1,
    button2,
    checkLabel,
    checkValue,
    extraArgs = {}
  ) {
    if (!title) {
      title = InternalPromptUtils.getLocalizedString("Confirm");
    }

    let args = {
      promptType: "confirmEx",
      title,
      text,
      checkLabel,
      checked: this.async ? checkValue : checkValue.value,
      ok: false,
      buttonNumClicked: 1,
      ...extraArgs,
    };

    let [label0, label1, label2, defaultButtonNum, isDelayEnabled] =
      InternalPromptUtils.confirmExHelper(flags, button0, button1, button2);

    args.defaultButtonNum = defaultButtonNum;
    args.enableDelay = isDelayEnabled;

    if (label0) {
      args.button0Label = label0;
      if (label1) {
        args.button1Label = label1;
        if (label2) {
          args.button2Label = label2;
        }
      }
    }

    if (flags & Ci.nsIPrompt.SHOW_SPINNER) {
      args.headerIconCSSValue = "url('chrome://global/skin/icons/loading.svg')";
    }

    if (this.async) {
      return this.openPromptAsync(args, result => ({
        checked: !!result.checked,
        buttonNumClicked: result.buttonNumClicked,
      }));
    }

    this.openPromptSync(args);
    checkValue.value = args.checked;
    return args.buttonNumClicked;
  }

  nsIPrompt_prompt(title, text, value, checkLabel, checkValue) {
    if (!title) {
      title = InternalPromptUtils.getLocalizedString("Prompt");
    }

    let args = {
      promptType: "prompt",
      title,
      text,
      value: this.async ? value : value.value,
      checkLabel,
      checked: this.async ? checkValue : checkValue.value,
      ok: false,
    };

    if (this.async) {
      return this.openPromptAsync(args, result => ({
        checked: !!result.checked,
        value: result.value,
        ok: result.ok,
      }));
    }

    this.openPromptSync(args);

    // Did user click Ok or Cancel?
    let ok = args.ok;
    if (ok) {
      checkValue.value = args.checked;
      value.value = args.value;
    }

    return ok;
  }

  nsIPrompt_promptUsernameAndPassword(channel, title, text, user, pass) {
    if (!title) {
      title = InternalPromptUtils.getLocalizedString(
        "PromptUsernameAndPassword3",
        [InternalPromptUtils.getBrandFullName()]
      );
    }

    let args = {
      channel,
      promptType: "promptUserAndPass",
      title,
      text,
      user: this.async ? user : user.value,
      pass: this.async ? pass : pass.value,
      button0Label: InternalPromptUtils.getLocalizedString("SignIn"),
      ok: false,
    };

    if (this.async) {
      return this.openPromptAsync(args, result => ({
        user: result.user,
        pass: result.pass,
        ok: result.ok,
      }));
    }

    this.openPromptSync(args);

    // Did user click Ok or Cancel?
    let ok = args.ok;
    if (ok) {
      user.value = args.user;
      pass.value = args.pass;
    }

    return ok;
  }

  nsIPrompt_promptPassword(channel, title, text, pass) {
    if (!title) {
      title = InternalPromptUtils.getLocalizedString("PromptPassword3", [
        InternalPromptUtils.getBrandFullName(),
      ]);
    }

    let args = {
      channel,
      promptType: "promptPassword",
      title,
      text,
      pass: this.async ? pass : pass.value,
      button0Label: InternalPromptUtils.getLocalizedString("SignIn"),
      ok: false,
    };

    if (this.async) {
      return this.openPromptAsync(args, result => ({
        pass: result.pass,
        ok: result.ok,
      }));
    }

    this.openPromptSync(args);

    // Did user click Ok or Cancel?
    let ok = args.ok;
    if (ok) {
      pass.value = args.pass;
    }

    return ok;
  }

  select(title, text, list, selected) {
    if (!title) {
      title = InternalPromptUtils.getLocalizedString("Select");
    }

    let args = {
      promptType: "select",
      title,
      text,
      list,
      selected: -1,
      ok: false,
    };

    if (this.async) {
      return this.openPromptAsync(args, result => ({
        selected: result.selected,
        ok: result.ok,
      }));
    }

    this.openPromptSync(args);

    // Did user click Ok or Cancel?
    let ok = args.ok;
    if (ok) {
      selected.value = args.selected;
    }

    return ok;
  }

  /* ----------  nsIAuthPrompt  ---------- */

  nsIAuthPrompt_prompt(
    title,
    text,
    passwordRealm,
    savePassword,
    defaultText,
    result
  ) {
    // The passwordRealm and savePassword args were ignored by nsPrompt.cpp
    if (defaultText) {
      result.value = defaultText;
    }
    return this.nsIPrompt_prompt(title, text, result, null, {});
  }

  nsIAuthPrompt_promptUsernameAndPassword(
    title,
    text,
    passwordRealm,
    savePassword,
    user,
    pass
  ) {
    // The passwordRealm and savePassword args were ignored by nsPrompt.cpp
    return this.nsIPrompt_promptUsernameAndPassword(
      null,
      title,
      text,
      user,
      pass
    );
  }

  nsIAuthPrompt_promptPassword(title, text, passwordRealm, savePassword, pass) {
    // The passwordRealm and savePassword args were ignored by nsPrompt.cpp,
    // and we don't have a channel here.
    return this.nsIPrompt_promptPassword(null, title, text, pass);
  }

  /* ----------  nsIAuthPrompt2  ---------- */

  promptAuth(channel, level, authInfo) {
    let message = InternalPromptUtils.makeAuthMessage(this, channel, authInfo);

    let [username, password] = InternalPromptUtils.getAuthInfo(authInfo);

    let userParam = this.async ? username : { value: username };
    let passParam = this.async ? password : { value: password };

    let result;
    if (authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD) {
      result = this.nsIPrompt_promptPassword(channel, null, message, passParam);
    } else {
      result = this.nsIPrompt_promptUsernameAndPassword(
        channel,
        null,
        message,
        userParam,
        passParam
      );
    }

    // For the async case result is an nsIPropertyBag with prompt results.
    if (this.async) {
      return result.then(bag => {
        let ok = bag.getProperty("ok");
        if (ok) {
          let username = bag.getProperty("user");
          let password = bag.getProperty("pass");
          InternalPromptUtils.setAuthInfo(authInfo, username, password);
        }
        return ok;
      });
    }

    // For the sync case result is the "ok" boolean which indicates whether
    // the user has confirmed the dialog.
    if (result) {
      InternalPromptUtils.setAuthInfo(
        authInfo,
        userParam.value,
        passParam.value
      );
    }
    return result;
  }

  asyncPromptAuth() {
    // Nothing calls this directly; netwerk ends up going through
    // nsIPromptService::GetPrompt, which delegates to login manager.
    // Login manger handles the async bits itself, and only calls out
    // promptAuth, never asyncPromptAuth.
    //
    // Bug 565582 will change this.
    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
  }

  /* ----------  nsIWritablePropertyBag2 ---------- */
  // Legacy way to set modal type when prompting via nsIPrompt.
  // Please prompt via nsIPromptService. This will be removed in the future.
  setPropertyAsUint32(name, value) {
    if (name == "modalType") {
      this.modalType = value;
    } else {
      throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE);
    }
  }
}

XPCOMUtils.defineLazyPreferenceGetter(
  ModalPrompter,
  "defaultModalType",
  "prompts.defaultModalType",
  MODAL_TYPE_WINDOW
);

export function AuthPromptAdapterFactory() {}
AuthPromptAdapterFactory.prototype = {
  classID: Components.ID("{6e134924-6c3a-4d86-81ac-69432dd971dc}"),
  QueryInterface: ChromeUtils.generateQI(["nsIAuthPromptAdapterFactory"]),

  /* ----------  nsIAuthPromptAdapterFactory ---------- */

  createAdapter(oldPrompter) {
    return new AuthPromptAdapter(oldPrompter);
  },
};

// Takes an nsIAuthPrompt implementation, wraps it with a nsIAuthPrompt2 shell.
function AuthPromptAdapter(oldPrompter) {
  this.oldPrompter = oldPrompter;
}
AuthPromptAdapter.prototype = {
  QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt2"]),
  oldPrompter: null,

  /* ----------  nsIAuthPrompt2 ---------- */

  promptAuth(channel, level, authInfo) {
    let message = InternalPromptUtils.makeAuthMessage(
      this.oldPrompter,
      channel,
      authInfo
    );

    let [username, password] = InternalPromptUtils.getAuthInfo(authInfo);
    let userParam = { value: username };
    let passParam = { value: password };

    let { displayHost, realm } = InternalPromptUtils.getAuthTarget(
      channel,
      authInfo
    );
    let authTarget = displayHost + " (" + realm + ")";

    let ok;
    if (authInfo.flags & Ci.nsIAuthInformation.ONLY_PASSWORD) {
      ok = this.oldPrompter.promptPassword(
        null,
        message,
        authTarget,
        Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY,
        passParam
      );
    } else {
      ok = this.oldPrompter.promptUsernameAndPassword(
        null,
        message,
        authTarget,
        Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY,
        userParam,
        passParam
      );
    }

    if (ok) {
      InternalPromptUtils.setAuthInfo(
        authInfo,
        userParam.value,
        passParam.value
      );
    }
    return ok;
  },

  asyncPromptAuth() {
    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
  },
};
PK
!<6��:�0�0"modules/ProxyChannelFilter.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs",
});
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "ProxyService",
  "@mozilla.org/network/protocol-proxy-service;1",
  "nsIProtocolProxyService"
);

ChromeUtils.defineLazyGetter(lazy, "tabTracker", () => {
  return lazy.ExtensionParent.apiManager.global.tabTracker;
});
ChromeUtils.defineLazyGetter(
  lazy,
  "getCookieStoreIdForOriginAttributes",
  () => {
    return lazy.ExtensionParent.apiManager.global
      .getCookieStoreIdForOriginAttributes;
  }
);

// DNS is resolved on the SOCKS proxy server.
const { TRANSPARENT_PROXY_RESOLVES_HOST } = Ci.nsIProxyInfo;

// The length of time (seconds) to wait for a proxy to resolve before ignoring it.
const PROXY_TIMEOUT_SEC = 10;

const { ExtensionError } = ExtensionUtils;

const PROXY_TYPES = Object.freeze({
  DIRECT: "direct",
  HTTPS: "https",
  PROXY: "http", // Synonym for PROXY_TYPES.HTTP
  HTTP: "http",
  SOCKS: "socks", // SOCKS5
  SOCKS4: "socks4",
});

const ProxyInfoData = {
  validate(proxyData) {
    if (proxyData.type && proxyData.type.toLowerCase() === "direct") {
      return { type: proxyData.type };
    }
    for (let prop of [
      "type",
      "host",
      "port",
      "username",
      "password",
      "proxyDNS",
      "failoverTimeout",
      "proxyAuthorizationHeader",
      "connectionIsolationKey",
    ]) {
      this[prop](proxyData);
    }
    return proxyData;
  },

  type(proxyData) {
    let { type } = proxyData;
    if (
      typeof type !== "string" ||
      !PROXY_TYPES.hasOwnProperty(type.toUpperCase())
    ) {
      throw new ExtensionError(
        `ProxyInfoData: Invalid proxy server type: "${type}"`
      );
    }
    proxyData.type = PROXY_TYPES[type.toUpperCase()];
  },

  host(proxyData) {
    let { host } = proxyData;
    if (typeof host !== "string" || host.includes(" ")) {
      throw new ExtensionError(
        `ProxyInfoData: Invalid proxy server host: "${host}"`
      );
    }
    if (!host.length) {
      throw new ExtensionError(
        "ProxyInfoData: Proxy server host cannot be empty"
      );
    }
    proxyData.host = host;
  },

  port(proxyData) {
    let port = Number.parseInt(proxyData.port, 10);
    if (!Number.isInteger(port)) {
      throw new ExtensionError(
        `ProxyInfoData: Invalid proxy server port: "${port}"`
      );
    }

    if (port < 1 || port > 0xffff) {
      throw new ExtensionError(
        `ProxyInfoData: Proxy server port ${port} outside range 1 to 65535`
      );
    }
    proxyData.port = port;
  },

  username(proxyData) {
    let { username } = proxyData;
    if (username !== undefined && typeof username !== "string") {
      throw new ExtensionError(
        `ProxyInfoData: Invalid proxy server username: "${username}"`
      );
    }
  },

  password(proxyData) {
    let { password } = proxyData;
    if (password !== undefined && typeof password !== "string") {
      throw new ExtensionError(
        `ProxyInfoData: Invalid proxy server password: "${password}"`
      );
    }
  },

  proxyDNS(proxyData) {
    let { proxyDNS, type } = proxyData;
    if (proxyDNS !== undefined) {
      if (typeof proxyDNS !== "boolean") {
        throw new ExtensionError(
          `ProxyInfoData: Invalid proxyDNS value: "${proxyDNS}"`
        );
      }
      if (
        proxyDNS &&
        type !== PROXY_TYPES.SOCKS &&
        type !== PROXY_TYPES.SOCKS4
      ) {
        throw new ExtensionError(
          `ProxyInfoData: proxyDNS can only be true for SOCKS proxy servers`
        );
      }
    }
  },

  failoverTimeout(proxyData) {
    let { failoverTimeout } = proxyData;
    if (
      failoverTimeout !== undefined &&
      (!Number.isInteger(failoverTimeout) || failoverTimeout < 1)
    ) {
      throw new ExtensionError(
        `ProxyInfoData: Invalid failover timeout: "${failoverTimeout}"`
      );
    }
  },

  proxyAuthorizationHeader(proxyData) {
    let { proxyAuthorizationHeader, type } = proxyData;
    if (proxyAuthorizationHeader === undefined) {
      return;
    }
    if (typeof proxyAuthorizationHeader !== "string") {
      throw new ExtensionError(
        `ProxyInfoData: Invalid proxy server authorization header: "${proxyAuthorizationHeader}"`
      );
    }
    if (type !== "https" && type !== "http") {
      throw new ExtensionError(
        `ProxyInfoData: ProxyAuthorizationHeader requires type "https" or "http"`
      );
    }
  },

  connectionIsolationKey(proxyData) {
    let { connectionIsolationKey } = proxyData;
    if (
      connectionIsolationKey !== undefined &&
      typeof connectionIsolationKey !== "string"
    ) {
      throw new ExtensionError(
        `ProxyInfoData: Invalid proxy connection isolation key: "${connectionIsolationKey}"`
      );
    }
  },

  createProxyInfoFromData(
    policy,
    proxyDataList,
    defaultProxyInfo,
    proxyDataListIndex = 0
  ) {
    if (proxyDataListIndex >= proxyDataList.length) {
      return defaultProxyInfo;
    }
    let proxyData = proxyDataList[proxyDataListIndex];
    if (proxyData == null) {
      return null;
    }
    let {
      type,
      host,
      port,
      username,
      password,
      proxyDNS,
      failoverTimeout,
      proxyAuthorizationHeader,
      connectionIsolationKey,
    } = ProxyInfoData.validate(proxyData);
    if (type === PROXY_TYPES.DIRECT && defaultProxyInfo) {
      return defaultProxyInfo;
    }
    let failoverProxy = this.createProxyInfoFromData(
      policy,
      proxyDataList,
      defaultProxyInfo,
      proxyDataListIndex + 1
    );

    let proxyInfo;
    if (type === PROXY_TYPES.SOCKS || type === PROXY_TYPES.SOCKS4) {
      proxyInfo = lazy.ProxyService.newProxyInfoWithAuth(
        type,
        host,
        port,
        username,
        password,
        proxyAuthorizationHeader,
        connectionIsolationKey,
        proxyDNS ? TRANSPARENT_PROXY_RESOLVES_HOST : 0,
        failoverTimeout ? failoverTimeout : PROXY_TIMEOUT_SEC,
        failoverProxy
      );
    } else {
      proxyInfo = lazy.ProxyService.newProxyInfo(
        type,
        host,
        port,
        proxyAuthorizationHeader,
        connectionIsolationKey,
        proxyDNS ? TRANSPARENT_PROXY_RESOLVES_HOST : 0,
        failoverTimeout ? failoverTimeout : PROXY_TIMEOUT_SEC,
        failoverProxy
      );
    }
    proxyInfo.sourceId = policy.id;
    return proxyInfo;
  },
};

function normalizeFilter(filter) {
  if (!filter) {
    filter = {};
  }

  return {
    urls: filter.urls || null,
    types: filter.types || null,
    tabId: filter.tabId ?? null,
    windowId: filter.windowId ?? null,
    incognito: filter.incognito ?? null,
  };
}

export class ProxyChannelFilter {
  constructor(context, extension, listener, filter, extraInfoSpec) {
    this.context = context;
    this.extension = extension;
    this.filter = normalizeFilter(filter);
    this.listener = listener;
    this.extraInfoSpec = extraInfoSpec || [];

    lazy.ProxyService.registerChannelFilter(
      this /* nsIProtocolProxyChannelFilter aFilter */,
      0 /* unsigned long aPosition */
    );
  }

  // Originally duplicated from WebRequest.sys.mjs with small changes.  Keep this
  // in sync with WebRequest.sys.mjs as well as parent/ext-webRequest.js when
  // apropiate.
  getRequestData(channel, extraData) {
    let originAttributes = channel.loadInfo?.originAttributes;
    let data = {
      requestId: String(channel.id),
      url: channel.finalURL,
      method: channel.method,
      type: channel.type,
      fromCache: !!channel.fromCache,
      incognito: originAttributes?.privateBrowsingId > 0,
      thirdParty: channel.thirdParty,

      originUrl: channel.originURL || undefined,
      documentUrl: channel.documentURL || undefined,

      frameId: channel.frameId,
      parentFrameId: channel.parentFrameId,

      frameAncestors: channel.frameAncestors || undefined,

      timeStamp: Date.now(),

      ...extraData,
    };
    if (originAttributes) {
      data.cookieStoreId =
        lazy.getCookieStoreIdForOriginAttributes(originAttributes);
    }
    if (this.extraInfoSpec.includes("requestHeaders")) {
      data.requestHeaders = channel.getRequestHeaders();
    }
    if (channel.urlClassification) {
      data.urlClassification = {
        firstParty: channel.urlClassification.firstParty.filter(
          c => !c.startsWith("socialtracking")
        ),
        thirdParty: channel.urlClassification.thirdParty.filter(
          c => !c.startsWith("socialtracking")
        ),
      };
    }
    return data;
  }

  /**
   * This method (which is required by the nsIProtocolProxyService interface)
   * is called to apply proxy filter rules for the given URI and proxy object
   * (or list of proxy objects).
   *
   * @param {nsIChannel} channel The channel for which these proxy settings apply.
   * @param {nsIProxyInfo} defaultProxyInfo The proxy (or list of proxies) that
   *     would be used by default for the given URI. This may be null.
   * @param {nsIProxyProtocolFilterResult} proxyFilter
   */
  async applyFilter(channel, defaultProxyInfo, proxyFilter) {
    let proxyInfo;
    try {
      let wrapper = ChannelWrapper.get(channel);

      let browserData = { tabId: -1, windowId: -1 };
      if (wrapper.browserElement) {
        browserData = lazy.tabTracker.getBrowserData(wrapper.browserElement);
      }

      let { filter, extension } = this;
      if (filter.tabId != null && browserData.tabId !== filter.tabId) {
        return;
      }
      if (filter.windowId != null && browserData.windowId !== filter.windowId) {
        return;
      }
      if (
        extension.userContextIsolation &&
        !extension.canAccessContainer(
          channel.loadInfo?.originAttributes.userContextId
        )
      ) {
        return;
      }

      let { policy } = this.extension;
      if (wrapper.matches(filter, policy, { isProxy: true })) {
        let data = this.getRequestData(wrapper, { tabId: browserData.tabId });

        let ret = await this.listener(data);
        if (ret == null) {
          // If ret undefined or null, fall through to the `finally` block to apply the proxy result.
          proxyInfo = ret;
          return;
        }
        // We only accept proxyInfo objects, not the PAC strings. ProxyInfoData will
        // accept either, so we want to enforce the limit here.
        if (typeof ret !== "object") {
          throw new ExtensionError(
            "ProxyInfoData: proxyData must be an object or array of objects"
          );
        }
        // We allow the call to return either a single proxyInfo or an array of proxyInfo.
        if (!Array.isArray(ret)) {
          ret = [ret];
        }
        proxyInfo = ProxyInfoData.createProxyInfoFromData(
          policy,
          ret,
          defaultProxyInfo
        );
      }
    } catch (e) {
      // We need to normalize errors to dispatch them to the extension handler.  If
      // we have not started up yet, we'll just log those to the console.
      if (!this.context) {
        this.extension.logError(`proxy-error before extension startup: ${e}`);
        return;
      }
      let error = this.context.normalizeError(e);
      this.extension.emit("proxy-error", {
        message: error.message,
        fileName: error.fileName,
        lineNumber: error.lineNumber,
        stack: error.stack,
      });
    } finally {
      // We must call onProxyFilterResult.  proxyInfo may be null or nsIProxyInfo.
      // defaultProxyInfo will be null unless a prior proxy handler has set something.
      // If proxyInfo is null, that removes any prior proxy config.  This allows a
      // proxy extension to override higher level (e.g. prefs) config under certain
      // circumstances.
      proxyFilter.onProxyFilterResult(
        proxyInfo !== undefined ? proxyInfo : defaultProxyInfo
      );
    }
  }

  destroy() {
    lazy.ProxyService.unregisterFilter(this);
  }
}
PK
!<�i-�B�B#modules/PurgeTrackerService.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const THREE_DAYS_MS = 3 * 24 * 60 * 1000;

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gClassifier",
  "@mozilla.org/url-classifier/dbservice;1",
  "nsIURIClassifier"
);
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gStorageActivityService",
  "@mozilla.org/storage/activity-service;1",
  "nsIStorageActivityService"
);

ChromeUtils.defineLazyGetter(lazy, "gClassifierFeature", () => {
  return lazy.gClassifier.getFeatureByName("tracking-annotation");
});

ChromeUtils.defineLazyGetter(lazy, "logger", () => {
  return console.createInstance({
    prefix: "*** PurgeTrackerService:",
    maxLogLevelPref: "privacy.purge_trackers.logging.level",
  });
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gConsiderEntityList",
  "privacy.purge_trackers.consider_entity_list"
);

export function PurgeTrackerService() {}

PurgeTrackerService.prototype = {
  classID: Components.ID("{90d1fd17-2018-4e16-b73c-a04a26fa6dd4}"),
  QueryInterface: ChromeUtils.generateQI(["nsIPurgeTrackerService"]),

  // Purging is batched for cookies to avoid clearing too much data
  // at once. This flag tells us whether this is the first daily iteration.
  _firstIteration: true,

  // We can only know asynchronously if a host is matched by the tracking
  // protection list, so we cache the result for faster future lookups.
  _trackingState: new Map(),

  observe(aSubject, aTopic) {
    switch (aTopic) {
      case "idle-daily":
        // only allow one idle-daily listener to trigger until the list has been fully parsed.
        Services.obs.removeObserver(this, "idle-daily");
        this.purgeTrackingCookieJars();
        break;
      case "profile-after-change":
        Services.obs.addObserver(this, "idle-daily");
        break;
    }
  },

  async isTracker(principal) {
    if (principal.isNullPrincipal || principal.isSystemPrincipal) {
      return false;
    }
    let host;
    try {
      host = principal.asciiHost;
    } catch (error) {
      return false;
    }

    if (!this._trackingState.has(host)) {
      // Temporarily set to false to avoid doing several lookups if a site has
      // several subframes on the same domain.
      this._trackingState.set(host, false);

      await new Promise(resolve => {
        try {
          lazy.gClassifier.asyncClassifyLocalWithFeatures(
            principal.URI,
            [lazy.gClassifierFeature],
            Ci.nsIUrlClassifierFeature.blocklist,
            list => {
              if (list.length) {
                this._trackingState.set(host, true);
              }
              resolve();
            }
          );
        } catch {
          // Error in asyncClassifyLocalWithFeatures, it is not a tracker.
          this._trackingState.set(host, false);
          resolve();
        }
      });
    }

    return this._trackingState.get(host);
  },

  isAllowedThirdParty(firstPartyOriginNoSuffix, thirdPartyHost) {
    let uri = Services.io.newURI(
      `${firstPartyOriginNoSuffix}/?resource=${thirdPartyHost}`
    );
    lazy.logger.debug(`Checking entity list state for`, uri.spec);
    return new Promise(resolve => {
      try {
        lazy.gClassifier.asyncClassifyLocalWithFeatures(
          uri,
          [lazy.gClassifierFeature],
          Ci.nsIUrlClassifierFeature.entitylist,
          list => {
            let sameList = !!list.length;
            lazy.logger.debug(`Is ${uri.spec} on the entity list?`, sameList);
            resolve(sameList);
          }
        );
      } catch {
        resolve(false);
      }
    });
  },

  async maybePurgePrincipal(principal) {
    let origin = principal.origin;
    lazy.logger.debug(`Maybe purging ${origin}.`);

    // First, check if any site with that base domain had received
    // user interaction in the last N days.
    let hasInteraction = this._baseDomainsWithInteraction.has(
      principal.baseDomain
    );
    // Exit early unless we want to see if we're dealing with a tracker,
    // for telemetry.
    if (hasInteraction && !Services.telemetry.canRecordPrereleaseData) {
      lazy.logger.debug(`${origin} has user interaction, exiting.`);
      return;
    }

    // Second, confirm that we're looking at a tracker.
    let isTracker = await this.isTracker(principal);
    if (!isTracker) {
      lazy.logger.debug(`${origin} is not a tracker, exiting.`);
      return;
    }

    if (hasInteraction) {
      let expireTimeMs = this._baseDomainsWithInteraction.get(
        principal.baseDomain
      );

      // Collect how much longer the user interaction will be valid for, in hours.
      let timeRemaining = Math.floor(
        (expireTimeMs - Date.now()) / 1000 / 60 / 60 / 24
      );
      let permissionAgeHistogram = Services.telemetry.getHistogramById(
        "COOKIE_PURGING_TRACKERS_USER_INTERACTION_REMAINING_DAYS"
      );
      permissionAgeHistogram.add(timeRemaining);

      this._telemetryData.notPurged.add(principal.baseDomain);

      lazy.logger.debug(`${origin} is a tracker with interaction, exiting.`);
      return;
    }

    let isAllowedThirdParty = false;
    if (
      lazy.gConsiderEntityList ||
      Services.telemetry.canRecordPrereleaseData
    ) {
      for (let firstPartyPrincipal of this._principalsWithInteraction) {
        if (
          await this.isAllowedThirdParty(
            firstPartyPrincipal.originNoSuffix,
            principal.asciiHost
          )
        ) {
          isAllowedThirdParty = true;
          break;
        }
      }
    }

    if (isAllowedThirdParty && lazy.gConsiderEntityList) {
      lazy.logger.debug(
        `${origin} has interaction on the entity list, exiting.`
      );
      return;
    }

    lazy.logger.log("Deleting data from:", origin);

    await new Promise(resolve => {
      Services.clearData.deleteDataFromPrincipal(
        principal,
        false,
        Ci.nsIClearDataService.CLEAR_ALL_CACHES |
          Ci.nsIClearDataService.CLEAR_COOKIES |
          Ci.nsIClearDataService.CLEAR_DOM_STORAGES |
          Ci.nsIClearDataService.CLEAR_CLIENT_AUTH_REMEMBER_SERVICE |
          Ci.nsIClearDataService.CLEAR_EME |
          Ci.nsIClearDataService.CLEAR_MEDIA_DEVICES |
          Ci.nsIClearDataService.CLEAR_STORAGE_ACCESS |
          Ci.nsIClearDataService.CLEAR_AUTH_TOKENS |
          Ci.nsIClearDataService.CLEAR_AUTH_CACHE |
          Ci.nsIClearDataService.CLEAR_COOKIE_BANNER_EXECUTED_RECORD,
        resolve
      );
    });
    lazy.logger.log(`Data deleted from:`, origin);

    this._telemetryData.purged.add(principal.baseDomain);
  },

  resetPurgeList() {
    // We've reached the end of the cookies.
    // Restore the idle-daily listener so it will purge again tomorrow.
    Services.obs.addObserver(this, "idle-daily");
    // Set the date to 0 so we will start at the beginning of the list next time.
    Services.prefs.setStringPref(
      "privacy.purge_trackers.date_in_cookie_database",
      "0"
    );
  },

  submitTelemetry() {
    let { purged, notPurged, durationIntervals } = this._telemetryData;
    let now = Date.now();
    let lastPurge = Number(
      Services.prefs.getStringPref("privacy.purge_trackers.last_purge", now)
    );

    let intervalHistogram = Services.telemetry.getHistogramById(
      "COOKIE_PURGING_INTERVAL_HOURS"
    );
    let hoursBetween = Math.floor((now - lastPurge) / 1000 / 60 / 60);
    intervalHistogram.add(hoursBetween);

    Services.prefs.setStringPref(
      "privacy.purge_trackers.last_purge",
      now.toString()
    );

    let purgedHistogram = Services.telemetry.getHistogramById(
      "COOKIE_PURGING_ORIGINS_PURGED"
    );
    purgedHistogram.add(purged.size);

    let notPurgedHistogram = Services.telemetry.getHistogramById(
      "COOKIE_PURGING_TRACKERS_WITH_USER_INTERACTION"
    );
    notPurgedHistogram.add(notPurged.size);

    let duration = durationIntervals
      .map(([start, end]) => end - start)
      .reduce((acc, cur) => acc + cur, 0);

    let durationHistogram = Services.telemetry.getHistogramById(
      "COOKIE_PURGING_DURATION_MS"
    );
    durationHistogram.add(duration);
  },

  /*
   * Checks Cookie Permission a given 2 principals
   * if either prinicpial cookie permissions are to prevent purging
   * the function would return true
   */
  checkCookiePermissions(httpsPrincipal, httpPrincipal) {
    let httpsCookiePermission;
    let httpCookiePermission;

    if (httpPrincipal) {
      httpCookiePermission = Services.perms.testPermissionFromPrincipal(
        httpPrincipal,
        "cookie"
      );
    }

    if (httpsPrincipal) {
      httpsCookiePermission = Services.perms.testPermissionFromPrincipal(
        httpsPrincipal,
        "cookie"
      );
    }

    if (
      httpCookiePermission == Ci.nsICookiePermission.ACCESS_ALLOW ||
      httpsCookiePermission == Ci.nsICookiePermission.ACCESS_ALLOW
    ) {
      return true;
    }

    return false;
  },
  /**
   * This loops through all cookies saved in the database and checks if they are a tracking cookie, if it is it checks
   * that they have an interaction permission which is still valid. If the Permission is not valid we delete all data
   * associated with the site that owns that cookie.
   */
  async purgeTrackingCookieJars() {
    let purgeEnabled = Services.prefs.getBoolPref(
      "privacy.purge_trackers.enabled",
      false
    );

    let sanitizeOnShutdownEnabled = Services.prefs.getBoolPref(
      "privacy.sanitize.sanitizeOnShutdown",
      false
    );

    let clearHistoryOnShutdown = Services.prefs.getBoolPref(
      "privacy.clearOnShutdown.history",
      false
    );

    let clearSiteSettingsOnShutdown = Services.prefs.getBoolPref(
      "privacy.clearOnShutdown.siteSettings",
      false
    );

    // This is a hotfix for bug 1672394. It avoids purging if the user has enabled mechanisms
    // that regularly clear the storageAccessAPI permission, such as clearing history or
    // "site settings" (permissions) on shutdown.
    if (
      sanitizeOnShutdownEnabled &&
      (clearHistoryOnShutdown || clearSiteSettingsOnShutdown)
    ) {
      lazy.logger.log(
        `
        Purging canceled because interaction permissions are cleared on shutdown.
        sanitizeOnShutdownEnabled: ${sanitizeOnShutdownEnabled},
        clearHistoryOnShutdown: ${clearHistoryOnShutdown},
        clearSiteSettingsOnShutdown: ${clearSiteSettingsOnShutdown},
        `
      );
      this.resetPurgeList();
      return;
    }

    // Purge cookie jars for following cookie behaviors.
    //   * BEHAVIOR_REJECT_FOREIGN
    //   * BEHAVIOR_LIMIT_FOREIGN
    //   * BEHAVIOR_REJECT_TRACKER (ETP)
    //   * BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN (dFPI)
    let cookieBehavior = Services.cookies.getCookieBehavior(false);

    let activeWithCookieBehavior =
      cookieBehavior == Ci.nsICookieService.BEHAVIOR_REJECT_FOREIGN ||
      cookieBehavior == Ci.nsICookieService.BEHAVIOR_LIMIT_FOREIGN ||
      cookieBehavior == Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER ||
      cookieBehavior ==
        Ci.nsICookieService.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN;

    if (!activeWithCookieBehavior || !purgeEnabled) {
      lazy.logger.log(
        `returning early, activeWithCookieBehavior: ${activeWithCookieBehavior}, purgeEnabled: ${purgeEnabled}`
      );
      this.resetPurgeList();
      return;
    }
    lazy.logger.log("Purging trackers enabled, beginning batch.");
    // How many cookies to loop through in each batch before we quit
    const MAX_PURGE_COUNT = Services.prefs.getIntPref(
      "privacy.purge_trackers.max_purge_count",
      100
    );

    if (this._firstIteration) {
      this._telemetryData = {
        durationIntervals: [],
        purged: new Set(),
        notPurged: new Set(),
      };

      this._baseDomainsWithInteraction = new Map();
      this._principalsWithInteraction = [];
      for (let perm of Services.perms.getAllWithTypePrefix(
        "storageAccessAPI"
      )) {
        this._baseDomainsWithInteraction.set(
          perm.principal.baseDomain,
          perm.expireTime
        );
        this._principalsWithInteraction.push(perm.principal);
      }
    }

    // Record how long this iteration took for telemetry.
    // This is a tuple of start and end time, the second
    // part will be added at the end of this function.
    let duration = [Cu.now()];

    /**
     * We record the creationTime of the last cookie we looked at and
     * start from there next time. This way even if new cookies are added or old ones are deleted we
     * have a reliable way of finding our spot.
     **/
    let saved_date = Services.prefs.getStringPref(
      "privacy.purge_trackers.date_in_cookie_database",
      "0"
    );

    let maybeClearPrincipals = new Map();

    // TODO We only need the host name and creationTime, this gives too much info. See bug 1610373.
    let cookies = Services.cookies.getCookiesSince(saved_date);
    cookies = cookies.slice(0, MAX_PURGE_COUNT);

    for (let cookie of cookies) {
      let httpPrincipal;
      let httpsPrincipal;

      let origin =
        "http://" +
        cookie.rawHost +
        ChromeUtils.originAttributesToSuffix(cookie.originAttributes);
      try {
        httpPrincipal =
          Services.scriptSecurityManager.createContentPrincipalFromOrigin(
            origin
          );
      } catch (e) {
        lazy.logger.error(
          `Creating principal from origin ${origin} led to error ${e}.`
        );
      }

      origin =
        "https://" +
        cookie.rawHost +
        ChromeUtils.originAttributesToSuffix(cookie.originAttributes);
      try {
        httpsPrincipal =
          Services.scriptSecurityManager.createContentPrincipalFromOrigin(
            origin
          );
      } catch (e) {
        lazy.logger.error(
          `Creating principal from origin ${origin} led to error ${e}.`
        );
      }

      // Checking to see if the Cookie Permissions is set to prevent Cookie from
      // purging for either the HTTPS or HTTP conncetions
      let purgeCheck = this.checkCookiePermissions(
        httpsPrincipal,
        httpPrincipal
      );

      if (httpPrincipal && !purgeCheck) {
        maybeClearPrincipals.set(httpPrincipal.origin, httpPrincipal);
      }
      if (httpsPrincipal && !purgeCheck) {
        maybeClearPrincipals.set(httpsPrincipal.origin, httpsPrincipal);
      }

      saved_date = cookie.creationTime;
    }

    // We only consider recently active storage and don't batch it,
    // so only do this in the first iteration.
    if (this._firstIteration) {
      let startDate = Date.now() - THREE_DAYS_MS;
      let storagePrincipals = lazy.gStorageActivityService.getActiveOrigins(
        startDate * 1000,
        Date.now() * 1000
      );

      for (let principal of storagePrincipals.enumerate()) {
        // Check Principal Domains Cookie Permissions for both Schemes
        // To ensure it does not bypass the cookie permissions set by the user
        if (principal.schemeIs("https") || principal.schemeIs("http")) {
          let otherURI;
          let otherPrincipal;

          if (principal.schemeIs("https")) {
            otherURI = principal.URI.mutate().setScheme("http").finalize();
          } else if (principal.schemeIs("http")) {
            otherURI = principal.URI.mutate().setScheme("https").finalize();
          }

          try {
            otherPrincipal =
              Services.scriptSecurityManager.createContentPrincipal(
                otherURI,
                {}
              );
          } catch (e) {
            lazy.logger.error(
              `Creating principal from URI ${otherURI} led to error ${e}.`
            );
          }

          if (!this.checkCookiePermissions(principal, otherPrincipal)) {
            maybeClearPrincipals.set(principal.origin, principal);
          }
        } else {
          maybeClearPrincipals.set(principal.origin, principal);
        }
      }
    }

    for (let principal of maybeClearPrincipals.values()) {
      await this.maybePurgePrincipal(principal);
    }

    Services.prefs.setStringPref(
      "privacy.purge_trackers.date_in_cookie_database",
      saved_date
    );

    duration.push(Cu.now());
    this._telemetryData.durationIntervals.push(duration);

    // We've reached the end, no need to repeat again until next idle-daily.
    if (!cookies.length || cookies.length < 100) {
      lazy.logger.log(
        "All cookie purging finished, resetting list until tomorrow."
      );
      this.resetPurgeList();
      this.submitTelemetry();
      this._firstIteration = true;
      return;
    }

    lazy.logger.log("Batch finished, queueing next batch.");
    this._firstIteration = false;
    Services.tm.idleDispatchToMainThread(() => {
      this.purgeTrackingCookieJars();
    });
  },
};
PK
!<,�#�%�%modules/Push.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  let { ConsoleAPI } = ChromeUtils.importESModule(
    "resource://gre/modules/Console.sys.mjs"
  );
  return new ConsoleAPI({
    maxLogLevelPref: "dom.push.loglevel",
    prefix: "Push",
  });
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "PushService",
  "@mozilla.org/push/Service;1",
  "nsIPushService"
);

/**
 * The Push component runs in the child process and exposes the Push API
 * to the web application. The PushService running in the parent process is the
 * one actually performing all operations.
 */
export class Push {
  constructor() {
    lazy.console.debug("Push()");
  }

  get contractID() {
    return "@mozilla.org/push/PushManager;1";
  }

  get classID() {
    return Components.ID("{cde1d019-fad8-4044-b141-65fb4fb7a245}");
  }

  get QueryInterface() {
    return ChromeUtils.generateQI([
      "nsIDOMGlobalPropertyInitializer",
      "nsISupportsWeakReference",
      "nsIObserver",
    ]);
  }

  init(win) {
    lazy.console.debug("init()");

    this._window = win;

    // Get the client principal from the window. This won't be null because the
    // service worker should be available when accessing the push manager.
    this._principal = win.clientPrincipal;

    if (!this._principal) {
      throw new Error(" The client principal of the window is not available");
    }

    try {
      this._topLevelPrincipal = win.top.document.nodePrincipal;
    } catch (error) {
      // Accessing the top-level document might fails if cross-origin
      this._topLevelPrincipal = undefined;
    }
  }

  __init(scope) {
    this._scope = scope;
  }

  askPermission() {
    lazy.console.debug("askPermission()");

    let hasValidTransientUserGestureActivation =
      this._window.document.hasValidTransientUserGestureActivation;

    return new this._window.Promise((resolve, reject) => {
      // Test permission before requesting to support GeckoView:
      // * GeckoViewPermissionChild wants to return early when requested without user activation
      //   before doing actual permission check:
      //   https://searchfox.org/mozilla-central/rev/0ba4632ee85679a1ccaf652df79c971fa7e9b9f7/mobile/android/actors/GeckoViewPermissionChild.sys.mjs#46-56
      //   which is partly because:
      // * GeckoView test runner has no real permission check but just returns VALUE_ALLOW.
      //   https://searchfox.org/mozilla-central/rev/6e5b9a5a1edab13a1b2e2e90944b6e06b4d8149c/mobile/android/test_runner/src/main/java/org/mozilla/geckoview/test_runner/TestRunnerActivity.java#108-123
      if (this.#testPermission() === Ci.nsIPermissionManager.ALLOW_ACTION) {
        resolve();
        return;
      }

      let permissionDenied = () => {
        reject(
          new this._window.DOMException(
            "User denied permission to use the Push API.",
            "NotAllowedError"
          )
        );
      };

      if (
        Services.prefs.getBoolPref("dom.push.testing.ignorePermission", false)
      ) {
        resolve();
        return;
      }

      this.#requestPermission(
        hasValidTransientUserGestureActivation,
        resolve,
        permissionDenied
      );
    });
  }

  subscribe(options) {
    lazy.console.debug("subscribe()", this._scope);

    return this.askPermission().then(
      () =>
        new this._window.Promise((resolve, reject) => {
          let callback = new PushSubscriptionCallback(this, resolve, reject);

          if (!options || options.applicationServerKey === null) {
            lazy.PushService.subscribe(this._scope, this._principal, callback);
            return;
          }

          let keyView = this.#normalizeAppServerKey(
            options.applicationServerKey
          );
          if (keyView.byteLength === 0) {
            callback.rejectWithError(Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR);
            return;
          }
          lazy.PushService.subscribeWithKey(
            this._scope,
            this._principal,
            keyView,
            callback
          );
        })
    );
  }

  #normalizeAppServerKey(appServerKey) {
    let key;
    if (typeof appServerKey == "string") {
      try {
        key = Cu.cloneInto(
          ChromeUtils.base64URLDecode(appServerKey, {
            padding: "reject",
          }),
          this._window
        );
      } catch (e) {
        throw new this._window.DOMException(
          "String contains an invalid character",
          "InvalidCharacterError"
        );
      }
    } else if (this._window.ArrayBuffer.isView(appServerKey)) {
      key = appServerKey.buffer;
    } else {
      // `appServerKey` is an array buffer.
      key = appServerKey;
    }
    return new this._window.Uint8Array(key);
  }

  getSubscription() {
    lazy.console.debug("getSubscription()", this._scope);

    return new this._window.Promise((resolve, reject) => {
      let callback = new PushSubscriptionCallback(this, resolve, reject);
      lazy.PushService.getSubscription(this._scope, this._principal, callback);
    });
  }

  permissionState() {
    lazy.console.debug("permissionState()", this._scope);

    return new this._window.Promise((resolve, reject) => {
      let permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;

      try {
        permission = this.#testPermission();
      } catch (e) {
        reject();
        return;
      }

      let pushPermissionStatus = "prompt";
      if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
        pushPermissionStatus = "granted";
      } else if (permission == Ci.nsIPermissionManager.DENY_ACTION) {
        pushPermissionStatus = "denied";
      }
      resolve(pushPermissionStatus);
    });
  }

  #testPermission() {
    let permission = Services.perms.testExactPermissionFromPrincipal(
      this._principal,
      "desktop-notification"
    );
    if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {
      return permission;
    }
    try {
      if (Services.prefs.getBoolPref("dom.push.testing.ignorePermission")) {
        permission = Ci.nsIPermissionManager.ALLOW_ACTION;
      }
    } catch (e) {}
    return permission;
  }

  #requestPermission(
    hasValidTransientUserGestureActivation,
    allowCallback,
    cancelCallback
  ) {
    // Create an array with a single nsIContentPermissionType element.
    let type = {
      type: "desktop-notification",
      options: [],
      QueryInterface: ChromeUtils.generateQI(["nsIContentPermissionType"]),
    };
    let typeArray = Cc["@mozilla.org/array;1"].createInstance(
      Ci.nsIMutableArray
    );
    typeArray.appendElement(type);

    // create a nsIContentPermissionRequest
    let request = {
      QueryInterface: ChromeUtils.generateQI(["nsIContentPermissionRequest"]),
      types: typeArray,
      principal: this._principal,
      hasValidTransientUserGestureActivation,
      topLevelPrincipal: this._topLevelPrincipal,
      allow: allowCallback,
      cancel: cancelCallback,
      window: this._window,
    };

    // Using askPermission from nsIDOMWindowUtils that takes care of the
    // remoting if needed.
    let windowUtils = this._window.windowUtils;
    windowUtils.askPermission(request);
  }
}

class PushSubscriptionCallback {
  constructor(pushManager, resolve, reject) {
    this.pushManager = pushManager;
    this.resolve = resolve;
    this.reject = reject;
  }

  get QueryInterface() {
    return ChromeUtils.generateQI(["nsIPushSubscriptionCallback"]);
  }

  onPushSubscription(ok, subscription) {
    let { pushManager } = this;
    if (!Components.isSuccessCode(ok)) {
      this.rejectWithError(ok);
      return;
    }

    if (!subscription) {
      this.resolve(null);
      return;
    }

    let p256dhKey = this.#getKey(subscription, "p256dh");
    let authSecret = this.#getKey(subscription, "auth");
    let options = {
      endpoint: subscription.endpoint,
      scope: pushManager._scope,
      p256dhKey,
      authSecret,
    };
    let appServerKey = this.#getKey(subscription, "appServer");
    if (appServerKey) {
      // Avoid passing null keys to work around bug 1256449.
      options.appServerKey = appServerKey;
    }
    let sub = new pushManager._window.PushSubscription(options);
    this.resolve(sub);
  }

  #getKey(subscription, name) {
    let rawKey = Cu.cloneInto(
      subscription.getKey(name),
      this.pushManager._window
    );
    if (!rawKey.length) {
      return null;
    }

    let key = new this.pushManager._window.ArrayBuffer(rawKey.length);
    let keyView = new this.pushManager._window.Uint8Array(key);
    keyView.set(rawKey);
    return key;
  }

  rejectWithError(result) {
    let error;
    switch (result) {
      case Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR:
        error = new this.pushManager._window.DOMException(
          "Invalid raw ECDSA P-256 public key.",
          "InvalidAccessError"
        );
        break;

      case Cr.NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR:
        error = new this.pushManager._window.DOMException(
          "A subscription with a different application server key already exists.",
          "InvalidStateError"
        );
        break;

      default:
        error = new this.pushManager._window.DOMException(
          "Error retrieving push subscription.",
          "AbortError"
        );
    }
    this.reject(error);
  }
}
PK
!<?e�=C=Cmodules/ReaderMode.sys.mjs// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

// Constants for telemetry.
const DOWNLOAD_SUCCESS = 0;
const DOWNLOAD_ERROR_XHR = 1;
const DOWNLOAD_ERROR_NO_DOC = 2;

const PARSE_SUCCESS = 0;
const PARSE_ERROR_TOO_MANY_ELEMENTS = 1;
const PARSE_ERROR_WORKER = 2;
const PARSE_ERROR_NO_ARTICLE = 3;

// Class names to preserve in the readerized output. We preserve these class
// names so that rules in aboutReader.css can match them.
const CLASSES_TO_PRESERVE = [
  "caption",
  "emoji",
  "hidden",
  "invisible",
  "sr-only",
  "visually-hidden",
  "visuallyhidden",
  "wp-caption",
  "wp-caption-text",
  "wp-smiley",
];

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  LanguageDetector:
    "resource://gre/modules/translation/LanguageDetector.sys.mjs",
  ReaderWorker: "resource://gre/modules/reader/ReaderWorker.sys.mjs",
  Readerable: "resource://gre/modules/Readerable.sys.mjs",
});

const gIsFirefoxDesktop =
  Services.appinfo.ID == "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";

Services.telemetry.setEventRecordingEnabled("readermode", true);

export var ReaderMode = {
  DEBUG: 0,

  // For time spent telemetry
  enterTime: undefined,
  leaveTime: undefined,

  /**
   * Enter the reader mode by going forward one step in history if applicable,
   * if not, append the about:reader page in the history instead.
   */
  enterReaderMode(docShell, win) {
    this.enterTime = Date.now();

    Services.telemetry.recordEvent("readermode", "view", "on", null, {
      subcategory: "feature",
    });

    let url = win.document.location.href;
    let readerURL = "about:reader?url=" + encodeURIComponent(url);

    if (!Services.appinfo.sessionHistoryInParent) {
      let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
      let sh = webNav.sessionHistory;
      if (webNav.canGoForward) {
        let forwardEntry = sh.legacySHistory.getEntryAtIndex(sh.index + 1);
        let forwardURL = forwardEntry.URI.spec;
        if (forwardURL && (forwardURL == readerURL || !readerURL)) {
          webNav.goForward();
          return;
        }
      }
    }

    // This could possibly move to the parent. See bug 1664982.
    win.document.location = readerURL;
  },

  /**
   * Exit the reader mode by going back one step in history if applicable,
   * if not, append the original page in the history instead.
   */
  leaveReaderMode(docShell, win) {
    this.leaveTime = Date.now();

    // Measured in seconds (whole number)
    let timeSpentInReaderMode = Math.floor(
      (this.leaveTime - this.enterTime) / 1000
    );

    // Measured as percentage (whole number)
    let scrollPosition = Math.floor(
      ((win.scrollY + win.innerHeight) / win.document.body.clientHeight) * 100
    );

    Services.telemetry.recordEvent("readermode", "view", "off", null, {
      subcategory: "feature",
      reader_time: `${timeSpentInReaderMode}`,
      scroll_position: `${scrollPosition}`,
    });

    let url = win.document.location.href;
    let originalURL = this.getOriginalUrl(url);
    let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);

    if (!Services.appinfo.sessionHistoryInParent) {
      let sh = webNav.sessionHistory;
      if (webNav.canGoBack) {
        let prevEntry = sh.legacySHistory.getEntryAtIndex(sh.index - 1);
        let prevURL = prevEntry.URI.spec;
        if (prevURL && (prevURL == originalURL || !originalURL)) {
          webNav.goBack();
          return;
        }
      }
    }

    let referrerURI, principal;
    try {
      referrerURI = Services.io.newURI(url);
      principal = Services.scriptSecurityManager.createContentPrincipal(
        referrerURI,
        win.document.nodePrincipal.originAttributes
      );
    } catch (e) {
      console.error(e);
      return;
    }
    let loadFlags = webNav.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
    let ReferrerInfo = Components.Constructor(
      "@mozilla.org/referrer-info;1",
      "nsIReferrerInfo",
      "init"
    );
    let loadURIOptions = {
      triggeringPrincipal: principal,
      loadFlags,
      referrerInfo: new ReferrerInfo(
        Ci.nsIReferrerInfo.EMPTY,
        true,
        referrerURI
      ),
    };
    // This could possibly move to the parent. See bug 1664982.
    webNav.fixupAndLoadURIString(originalURL, loadURIOptions);
  },

  /**
   * Returns original URL from an about:reader URL.
   *
   * @param url An about:reader URL.
   * @return The original URL for the article, or null if we did not find
   *         a properly formatted about:reader URL.
   */
  getOriginalUrl(url) {
    if (!url.startsWith("about:reader?")) {
      return null;
    }

    let outerHash = "";
    try {
      let uriObj = Services.io.newURI(url);
      url = uriObj.specIgnoringRef;
      outerHash = uriObj.ref;
    } catch (ex) {
      /* ignore, use the raw string */
    }

    let searchParams = new URLSearchParams(
      url.substring("about:reader?".length)
    );
    if (!searchParams.has("url")) {
      return null;
    }
    let originalUrl = searchParams.get("url");
    if (outerHash) {
      try {
        let uriObj = Services.io.newURI(originalUrl);
        uriObj = Services.io.newURI("#" + outerHash, null, uriObj);
        originalUrl = uriObj.spec;
      } catch (ex) {}
    }
    return originalUrl;
  },

  getOriginalUrlObjectForDisplay(url) {
    let originalUrl = this.getOriginalUrl(url);
    if (originalUrl) {
      let uriObj;
      try {
        uriObj = Services.uriFixup.getFixupURIInfo(originalUrl).preferredURI;
      } catch (ex) {
        return null;
      }
      try {
        return Services.io.createExposableURI(uriObj);
      } catch (ex) {
        return null;
      }
    }
    return null;
  },

  /**
   * Gets an article from a loaded browser's document. This method will not attempt
   * to parse certain URIs (e.g. about: URIs).
   *
   * @param doc A document to parse.
   * @return {Promise}
   * @resolves JS object representing the article, or null if no article is found.
   */
  parseDocument(doc) {
    if (
      !lazy.Readerable.shouldCheckUri(doc.documentURIObject) ||
      !lazy.Readerable.shouldCheckUri(doc.baseURIObject, true)
    ) {
      this.log("Reader mode disabled for URI");
      return null;
    }

    return this._readerParse(doc);
  },

  /**
   * Downloads and parses a document from a URL.
   *
   * @param url URL to download and parse.
   * @param attrs OriginAttributes to use for the request.
   * @return {Promise}
   * @resolves JS object representing the article, or null if no article is found.
   */
  async downloadAndParseDocument(url, attrs = {}, docContentType = "document") {
    let result = await this._downloadDocument(url, attrs, docContentType);
    if (!result?.doc) {
      return null;
    }
    let { doc, newURL } = result;
    if (
      !lazy.Readerable.shouldCheckUri(doc.documentURIObject) ||
      !lazy.Readerable.shouldCheckUri(doc.baseURIObject, true)
    ) {
      this.log("Reader mode disabled for URI");
      return null;
    }

    let article = await this._readerParse(doc);
    // If we have to redirect, reject to the caller with the parsed article,
    // so we can update the URL before displaying it.
    if (newURL) {
      return Promise.reject({ newURL, article });
    }
    // Otherwise, we can just continue with the article.
    return article;
  },

  _downloadDocument(url, attrs = {}, docContentType = "document") {
    let uri;
    try {
      uri = Services.io.newURI(url);
      if (!lazy.Readerable.shouldCheckUri(uri)) {
        return null;
      }
    } catch (ex) {
      console.error(
        new Error(`Couldn't create URI from ${url} to download: ${ex}`)
      );
      return null;
    }
    let histogram = Services.telemetry.getHistogramById(
      "READER_MODE_DOWNLOAD_RESULT"
    );
    try {
      attrs.firstPartyDomain = Services.eTLD.getSchemelessSite(uri);
    } catch (e) {
      console.error("Failed to get first party domain for about:reader", e);
    }
    return new Promise((resolve, reject) => {
      let xhr = new XMLHttpRequest({ mozAnon: false });
      xhr.open("GET", url, true);
      xhr.setOriginAttributes(attrs);
      xhr.onerror = evt => reject(evt.error);
      xhr.responseType = docContentType === "text/plain" ? "text" : "document";
      xhr.onload = () => {
        if (xhr.status !== 200) {
          reject("Reader mode XHR failed with status: " + xhr.status);
          histogram.add(DOWNLOAD_ERROR_XHR);
          return;
        }

        let doc =
          xhr.responseType === "text" ? xhr.responseText : xhr.responseXML;
        if (!doc) {
          reject("Reader mode XHR didn't return a document");
          histogram.add(DOWNLOAD_ERROR_NO_DOC);
          return;
        }

        let responseURL = xhr.responseURL;
        let givenURL = url;
        // Convert these to real URIs to make sure the escaping (or lack
        // thereof) is identical:
        try {
          responseURL = Services.io.newURI(responseURL).specIgnoringRef;
        } catch (ex) {
          /* Ignore errors - we'll use what we had before */
        }
        try {
          givenURL = Services.io.newURI(givenURL).specIgnoringRef;
        } catch (ex) {
          /* Ignore errors - we'll use what we had before */
        }

        if (xhr.responseType != "document") {
          let initialText = doc;
          let parser = new DOMParser();
          doc = parser.parseFromString(`<pre></pre>`, "text/html");
          doc.querySelector("pre").textContent = initialText;
        }

        // We treat redirects as download successes here:
        histogram.add(DOWNLOAD_SUCCESS);

        let result = { doc };
        if (responseURL != givenURL) {
          result.newURL = xhr.responseURL;
        }

        resolve(result);
      };
      xhr.send();
    });
  },

  log(msg) {
    if (this.DEBUG) {
      dump("Reader: " + msg);
    }
  },

  /**
   * Attempts to parse a document into an article. Heavy lifting happens
   * in Reader.worker.js.
   *
   * @param doc The document to parse.
   * @return {Promise}
   * @resolves JS object representing the article, or null if no article is found.
   */
  async _readerParse(doc) {
    let histogram = Services.telemetry.getHistogramById(
      "READER_MODE_PARSE_RESULT"
    );
    if (this.parseNodeLimit) {
      let numTags = doc.getElementsByTagName("*").length;
      if (numTags > this.parseNodeLimit) {
        this.log(
          "Aborting parse for " +
            doc.baseURIObject.spec +
            "; " +
            numTags +
            " elements found"
        );
        histogram.add(PARSE_ERROR_TOO_MANY_ELEMENTS);
        return null;
      }
    }

    // Fetch this here before we send `doc` off to the worker thread, as later on the
    // document might be nuked but we will still want the URI.
    let { documentURI } = doc;

    let uriParam;
    uriParam = {
      spec: doc.baseURIObject.spec,
      prePath: doc.baseURIObject.prePath,
      scheme: doc.baseURIObject.scheme,

      // Fallback
      host: documentURI,
      pathBase: documentURI,
    };

    // nsIURI.host throws an exception if a host doesn't exist.
    try {
      uriParam.host = doc.baseURIObject.host;
      uriParam.pathBase = Services.io.newURI(".", null, doc.baseURIObject).spec;
    } catch (ex) {
      // Fall back to the initial values we assigned.
      console.warn("Error accessing host name: ", ex);
    }

    // convert text/plain document, if any, to XHTML format
    if (this._isDocumentPlainText(doc)) {
      doc = this._convertPlainTextDocument(doc);
    }

    let serializer = new XMLSerializer();
    let serializedDoc = serializer.serializeToString(doc);
    // Explicitly null out doc to make it clear it might not be available from this
    // point on.
    doc = null;

    let options = {
      classesToPreserve: CLASSES_TO_PRESERVE,
    };

    let article = null;
    try {
      article = await lazy.ReaderWorker.post("parseDocument", [
        uriParam,
        serializedDoc,
        options,
      ]);
    } catch (e) {
      console.error("Error in ReaderWorker: ", e);
      histogram.add(PARSE_ERROR_WORKER);
    }

    if (!article) {
      this.log("Worker did not return an article");
      histogram.add(PARSE_ERROR_NO_ARTICLE);
      return null;
    }

    // Readability returns a URI object based on the baseURI, but we only care
    // about the original document's URL from now on. This also avoids spoofing
    // attempts where the baseURI doesn't match the domain of the documentURI
    article.url = documentURI;
    delete article.uri;

    let flags =
      Ci.nsIDocumentEncoder.OutputSelectionOnly |
      Ci.nsIDocumentEncoder.OutputAbsoluteLinks;
    article.title = Cc["@mozilla.org/parserutils;1"]
      .getService(Ci.nsIParserUtils)
      .convertToPlainText(article.title, flags, 0);
    if (gIsFirefoxDesktop) {
      await this._assignLanguage(article);
      this._maybeAssignTextDirection(article);
    }

    this._assignReadTime(article);

    histogram.add(PARSE_SUCCESS);
    return article;
  },

  /**
   * Sets a global language string value if the result is confident
   *
   * @return Promise
   * @resolves when the language is detected
   */
  _assignLanguage(article) {
    return lazy.LanguageDetector.detectLanguage(article.textContent).then(
      result => {
        article.language = result.confident ? result.language : null;
      }
    );
  },

  _maybeAssignTextDirection(article) {
    // TODO: Remove the hardcoded language codes below once bug 1320265 is resolved.
    if (
      !article.dir &&
      ["ar", "fa", "he", "ug", "ur"].includes(article.language)
    ) {
      article.dir = "rtl";
    }
  },

  /**
   * Assigns the estimated reading time range of the article to the article object.
   *
   * @param article the article object to assign the reading time estimate to.
   */
  _assignReadTime(article) {
    let lang = article.language || "en";
    const readingSpeed = this._getReadingSpeedForLanguage(lang);
    const charactersPerMinuteLow = readingSpeed.cpm - readingSpeed.variance;
    const charactersPerMinuteHigh = readingSpeed.cpm + readingSpeed.variance;
    const length = article.length;

    article.readingTimeMinsSlow = Math.ceil(length / charactersPerMinuteLow);
    article.readingTimeMinsFast = Math.ceil(length / charactersPerMinuteHigh);
  },

  /**
   * Returns the reading speed of a selection of languages with likely variance.
   *
   * Reading speed estimated from a study done on reading speeds in various languages.
   * study can be found here: http://iovs.arvojournals.org/article.aspx?articleid=2166061
   *
   * @return object with characters per minute and variance. Defaults to English
   *         if no suitable language is found in the collection.
   */
  _getReadingSpeedForLanguage(lang) {
    const readingSpeed = new Map([
      ["en", { cpm: 987, variance: 118 }],
      ["ar", { cpm: 612, variance: 88 }],
      ["de", { cpm: 920, variance: 86 }],
      ["es", { cpm: 1025, variance: 127 }],
      ["fi", { cpm: 1078, variance: 121 }],
      ["fr", { cpm: 998, variance: 126 }],
      ["he", { cpm: 833, variance: 130 }],
      ["it", { cpm: 950, variance: 140 }],
      ["jw", { cpm: 357, variance: 56 }],
      ["nl", { cpm: 978, variance: 143 }],
      ["pl", { cpm: 916, variance: 126 }],
      ["pt", { cpm: 913, variance: 145 }],
      ["ru", { cpm: 986, variance: 175 }],
      ["sk", { cpm: 885, variance: 145 }],
      ["sv", { cpm: 917, variance: 156 }],
      ["tr", { cpm: 1054, variance: 156 }],
      ["zh", { cpm: 255, variance: 29 }],
    ]);

    return readingSpeed.get(lang) || readingSpeed.get("en");
  },
  /**
   *
   * Check if the document to be parsed is text document.
   * @param doc the doc object to be parsed.
   * @return boolean
   *
   */
  _isDocumentPlainText(doc) {
    return doc.contentType == "text/plain";
  },
  /**
   *
   * The document to be parsed is text document and is converted to HTML format.
   * @param doc the doc object to be parsed.
   * @return doc
   *
   */
  _convertPlainTextDocument(doc) {
    let preTag = doc.querySelector("pre");
    let docFrag = doc.createDocumentFragment();
    let content = preTag.textContent;
    let paragraphs = content.split(/\r?\n\r?\n/);
    for (let para of paragraphs) {
      let pElem = doc.createElement("p");
      let lines = para.split(/\n/);
      for (let line of lines) {
        pElem.append(line);
        let brElem = doc.createElement("br");
        pElem.append(brElem);
      }
      docFrag.append(pElem);
    }
    // Clone the document to avoid the original document being affected
    // (which shows up when exiting reader mode again).
    let clone = doc.documentElement.cloneNode(true);
    clone.querySelector("pre").replaceWith(docFrag);
    return clone;
  },
};

XPCOMUtils.defineLazyPreferenceGetter(
  ReaderMode,
  "maxElemsToParse",
  "reader.parse-node-limit",
  0
);
PK
!<l�o>>modules/Readerable.sys.mjs// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

//@line 1 "$SRCDIR/toolkit/components/reader/readability/Readability-readerable.js"
/*
 * Copyright (c) 2010 Arc90 Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * This code is heavily based on Arc90's readability.js (1.7.1) script
 * available at: http://code.google.com/p/arc90labs-readability
 */

var REGEXPS = {
  // NOTE: These two regular expressions are duplicated in
  // Readability.js. Please keep both copies in sync.
  unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i,
  okMaybeItsACandidate: /and|article|body|column|content|main|shadow/i,
};

function isNodeVisible(node) {
  // Have to null-check node.style and node.className.indexOf to deal with SVG and MathML nodes.
  return (!node.style || node.style.display != "none")
    && !node.hasAttribute("hidden")
    //check for "fallback-image" so that wikimedia math images are displayed
    && (!node.hasAttribute("aria-hidden") || node.getAttribute("aria-hidden") != "true" || (node.className && node.className.indexOf && node.className.indexOf("fallback-image") !== -1));
}

/**
 * Decides whether or not the document is reader-able without parsing the whole thing.
 * @param {Object} options Configuration object.
 * @param {number} [options.minContentLength=140] The minimum node content length used to decide if the document is readerable.
 * @param {number} [options.minScore=20] The minumum cumulated 'score' used to determine if the document is readerable.
 * @param {Function} [options.visibilityChecker=isNodeVisible] The function used to determine if a node is visible.
 * @return {boolean} Whether or not we suspect Readability.parse() will suceeed at returning an article object.
 */
function isProbablyReaderable(doc, options = {}) {
  // For backward compatibility reasons 'options' can either be a configuration object or the function used
  // to determine if a node is visible.
  if (typeof options == "function") {
    options = { visibilityChecker: options };
  }

  var defaultOptions = { minScore: 20, minContentLength: 140, visibilityChecker: isNodeVisible };
  options = Object.assign(defaultOptions, options);

  var nodes = doc.querySelectorAll("p, pre, article");

  // Get <div> nodes which have <br> node(s) and append them into the `nodes` variable.
  // Some articles' DOM structures might look like
  // <div>
  //   Sentences<br>
  //   <br>
  //   Sentences<br>
  // </div>
  var brNodes = doc.querySelectorAll("div > br");
  if (brNodes.length) {
    var set = new Set(nodes);
    [].forEach.call(brNodes, function (node) {
      set.add(node.parentNode);
    });
    nodes = Array.from(set);
  }

  var score = 0;
  // This is a little cheeky, we use the accumulator 'score' to decide what to return from
  // this callback:
  return [].some.call(nodes, function (node) {
    if (!options.visibilityChecker(node)) {
      return false;
    }

    var matchString = node.className + " " + node.id;
    if (REGEXPS.unlikelyCandidates.test(matchString) &&
        !REGEXPS.okMaybeItsACandidate.test(matchString)) {
      return false;
    }

    if (node.matches("li p")) {
      return false;
    }

    var textContentLength = node.textContent.trim().length;
    if (textContentLength < options.minContentLength) {
      return false;
    }

    score += Math.sqrt(textContentLength - options.minContentLength);

    if (score > options.minScore) {
      return true;
    }
    return false;
  });
}

if (typeof module === "object") {
  /* global module */
  module.exports = isProbablyReaderable;
}
//@line 1 "$SRCDIR/toolkit/components/reader/Readerable.js"
// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

// This file and Readability-readerable.js are merged together into
// Readerable.sys.mjs.

/* exported Readerable */
/* import-globals-from readability/Readability-readerable.js */

const { XPCOMUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/XPCOMUtils.sys.mjs"
);

var Readerable = {
  get isEnabledForParseOnLoad() {
    return this.isEnabled;
  },

  /**
   * Decides whether or not a document is reader-able without parsing the whole thing.
   *
   * @param doc A document to parse.
   * @return boolean Whether or not we should show the reader mode button.
   */
  isProbablyReaderable(doc) {
    // Only care about 'real' HTML documents:
    if (
      doc.mozSyntheticDocument ||
      !doc.defaultView.HTMLDocument.isInstance(doc)
    ) {
      return false;
    }

    let uri = Services.io.newURI(doc.location.href);
    if (!this.shouldCheckUri(uri)) {
      return false;
    }

    return isProbablyReaderable(doc, this._isNodeVisible);
  },

  _isNodeVisible(node) {
    return node.clientHeight > 0 && node.clientWidth > 0;
  },

  _blockedHosts: [
    "amazon.com",
    "github.com",
    "mail.google.com",
    "pinterest.com",
    "reddit.com",
    "twitter.com",
    "youtube.com",
    "app.slack.com",
  ],

  shouldCheckUri(uri, isBaseUri = false) {
    if (!["http", "https", "file", "moz-nullprincipal"].includes(uri.scheme)) {
      return false;
    }

    if (!isBaseUri && uri.scheme.startsWith("http")) {
      // Sadly, some high-profile pages have false positives, so bail early for those:
      let { host } = uri;
      if (this._blockedHosts.some(blockedHost => host.endsWith(blockedHost))) {
        // Allow github on non-project pages
        if (
          host == "github.com" &&
          !uri.filePath.includes("/projects") &&
          !uri.filePath.includes("/issues")
        ) {
          return true;
        }
        return false;
      }

      if (uri.filePath == "/") {
        return false;
      }
    }

    return true;
  },
};

XPCOMUtils.defineLazyPreferenceGetter(
  Readerable,
  "isEnabled",
  "reader.parse-on-load.enabled",
  true
);

export { Readerable };
PK
!<��ݞ�h�hmodules/Region.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

import { RemoteSettings } from "resource://services-settings/remote-settings.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  LocationHelper: "resource://gre/modules/LocationHelper.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "wifiScanningEnabled",
  "browser.region.network.scan",
  true
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "networkTimeout",
  "browser.region.timeout",
  5000
);

// Retry the region lookup every hour on failure, a failure
// is likely to be a service failure so this gives the
// service some time to restore. Setting to 0 disabled retries.
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "retryTimeout",
  "browser.region.retry-timeout",
  60 * 60 * 1000
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "loggingEnabled",
  "browser.region.log",
  false
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "cacheBustEnabled",
  "browser.region.update.enabled",
  false
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "updateDebounce",
  "browser.region.update.debounce",
  60 * 60 * 24
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "lastUpdated",
  "browser.region.update.updated",
  0
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "localGeocodingEnabled",
  "browser.region.local-geocoding",
  false
);

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "timerManager",
  "@mozilla.org/updates/timer-manager;1",
  "nsIUpdateTimerManager"
);

const log = console.createInstance({
  prefix: "Region.sys.mjs",
  maxLogLevel: lazy.loggingEnabled ? "All" : "Warn",
});

const REGION_PREF = "browser.search.region";
const COLLECTION_ID = "regions";
const GEOLOCATION_TOPIC = "geolocation-position-events";

// Prefix for all the region updating related preferences.
const UPDATE_PREFIX = "browser.region.update";

// The amount of time (in seconds) we need to be in a new
// location before we update the home region.
// Currently set to 2 weeks.
const UPDATE_INTERVAL = 60 * 60 * 24 * 14;

const MAX_RETRIES = 3;

// If the user never uses geolocation, schedule a periodic
// update to check the current location (in seconds).
const UPDATE_CHECK_NAME = "region-update-timer";
const UPDATE_CHECK_INTERVAL = 60 * 60 * 24 * 7;

// Let child processes read the current home value
// but dont trigger redundant updates in them.
let inChildProcess =
  Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;

/**
 * This module keeps track of the users current region (country).
 * so the SearchService and other consumers can apply region
 * specific customisations.
 */
class RegionDetector {
  // The users home location.
  _home = null;
  // The most recent location the user was detected.
  _current = null;
  // The RemoteSettings client used to sync region files.
  _rsClient = null;
  // Keep track of the wifi data across listener events.
  _wifiDataPromise = null;
  // Keep track of how many times we have tried to fetch
  // the users region during failure.
  _retryCount = 0;
  // Let tests wait for init to complete.
  _initPromise = null;
  // Topic for Observer events fired by Region.sys.mjs.
  REGION_TOPIC = "browser-region-updated";
  // Values for telemetry.
  TELEMETRY = {
    SUCCESS: 0,
    NO_RESULT: 1,
    TIMEOUT: 2,
    ERROR: 3,
  };

  /**
   * Read currently stored region data and if needed trigger background
   * region detection.
   */
  async init() {
    if (this._initPromise) {
      return this._initPromise;
    }
    if (lazy.cacheBustEnabled && !inChildProcess) {
      Services.tm.idleDispatchToMainThread(() => {
        lazy.timerManager.registerTimer(
          UPDATE_CHECK_NAME,
          () => this._updateTimer(),
          UPDATE_CHECK_INTERVAL
        );
      });
    }
    let promises = [];
    this._home = Services.prefs.getCharPref(REGION_PREF, null);
    if (!this._home && !inChildProcess) {
      promises.push(this._idleDispatch(() => this._fetchRegion()));
    }
    if (lazy.localGeocodingEnabled && !inChildProcess) {
      promises.push(this._idleDispatch(() => this._setupRemoteSettings()));
    }
    return (this._initPromise = Promise.all(promises));
  }

  /**
   * Get the region we currently consider the users home.
   *
   * @returns {string}
   *   The users current home region.
   */
  get home() {
    return this._home;
  }

  /**
   * Get the last region we detected the user to be in.
   *
   * @returns {string}
   *   The users current region.
   */
  get current() {
    return this._current;
  }

  /**
   * Fetch the users current region.
   *
   * @returns {string}
   *   The country_code defining users current region.
   */
  async _fetchRegion() {
    if (this._retryCount >= MAX_RETRIES) {
      return null;
    }
    let startTime = Date.now();
    let telemetryResult = this.TELEMETRY.SUCCESS;
    let result = null;

    try {
      result = await this._getRegion();
    } catch (err) {
      telemetryResult = this.TELEMETRY[err.message] || this.TELEMETRY.ERROR;
      log.error("Failed to fetch region", err);
      if (lazy.retryTimeout) {
        this._retryCount++;
        lazy.setTimeout(() => {
          Services.tm.idleDispatchToMainThread(this._fetchRegion.bind(this));
        }, lazy.retryTimeout);
      }
    }

    let took = Date.now() - startTime;
    if (result) {
      await this._storeRegion(result);
    }
    Services.telemetry
      .getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_TIME_MS")
      .add(took);

    Services.telemetry
      .getHistogramById("SEARCH_SERVICE_COUNTRY_FETCH_RESULT")
      .add(telemetryResult);

    return result;
  }

  /**
   * Validate then store the region and report telemetry.
   *
   * @param region
   *   The region to store.
   */
  async _storeRegion(region) {
    let prefix = "SEARCH_SERVICE";
    let isTimezoneUS = isUSTimezone();
    // If it's a US region, but not a US timezone, we don't store
    // the value. This works because no region defaults to
    // ZZ (unknown) in nsURLFormatter
    if (region != "US" || isTimezoneUS) {
      this._setCurrentRegion(region, true);
    }

    // and telemetry...
    if (region == "US" && !isTimezoneUS) {
      log.info("storeRegion mismatch - US Region, non-US timezone");
      Services.telemetry
        .getHistogramById(`${prefix}_US_COUNTRY_MISMATCHED_TIMEZONE`)
        .add(1);
    }
    if (region != "US" && isTimezoneUS) {
      log.info("storeRegion mismatch - non-US Region, US timezone");
      Services.telemetry
        .getHistogramById(`${prefix}_US_TIMEZONE_MISMATCHED_COUNTRY`)
        .add(1);
    }
    // telemetry to compare our geoip response with
    // platform-specific country data.
    // On Mac and Windows, we can get a country code via sysinfo
    let platformCC = await Services.sysinfo.countryCode;
    if (platformCC) {
      let probeUSMismatched, probeNonUSMismatched;
      switch (AppConstants.platform) {
        case "macosx":
          probeUSMismatched = `${prefix}_US_COUNTRY_MISMATCHED_PLATFORM_OSX`;
          probeNonUSMismatched = `${prefix}_NONUS_COUNTRY_MISMATCHED_PLATFORM_OSX`;
          break;
        case "win":
          probeUSMismatched = `${prefix}_US_COUNTRY_MISMATCHED_PLATFORM_WIN`;
          probeNonUSMismatched = `${prefix}_NONUS_COUNTRY_MISMATCHED_PLATFORM_WIN`;
          break;
        default:
          log.error(
            "Platform " +
              Services.appinfo.OS +
              " has system country code but no search service telemetry probes"
          );
          break;
      }
      if (probeUSMismatched && probeNonUSMismatched) {
        if (region == "US" || platformCC == "US") {
          // one of the 2 said US, so record if they are the same.
          Services.telemetry
            .getHistogramById(probeUSMismatched)
            .add(region != platformCC);
        } else {
          // non-US - record if they are the same
          Services.telemetry
            .getHistogramById(probeNonUSMismatched)
            .add(region != platformCC);
        }
      }
    }
  }

  /**
   * Save the update current region and check if the home region
   * also needs an update.
   *
   * @param {string} region
   *   The region to store.
   */
  _setCurrentRegion(region = "") {
    log.info("Setting current region:", region);
    this._current = region;

    let now = Math.round(Date.now() / 1000);
    let prefs = Services.prefs;
    prefs.setIntPref(`${UPDATE_PREFIX}.updated`, now);

    // Interval is in seconds.
    let interval = prefs.getIntPref(
      `${UPDATE_PREFIX}.interval`,
      UPDATE_INTERVAL
    );
    let seenRegion = prefs.getCharPref(`${UPDATE_PREFIX}.region`, null);
    let firstSeen = prefs.getIntPref(`${UPDATE_PREFIX}.first-seen`, 0);

    // If we don't have a value for .home we can set it immediately.
    if (!this._home) {
      this._setHomeRegion(region);
    } else if (region != this._home && region != seenRegion) {
      // If we are in a different region than what is currently
      // considered home, then keep track of when we first
      // seen the new location.
      prefs.setCharPref(`${UPDATE_PREFIX}.region`, region);
      prefs.setIntPref(`${UPDATE_PREFIX}.first-seen`, now);
    } else if (region != this._home && region == seenRegion) {
      // If we have been in the new region for longer than
      // a specified time period, then set that as the new home.
      if (now >= firstSeen + interval) {
        this._setHomeRegion(region);
      }
    } else {
      // If we are at home again, stop tracking the seen region.
      prefs.clearUserPref(`${UPDATE_PREFIX}.region`);
      prefs.clearUserPref(`${UPDATE_PREFIX}.first-seen`);
    }
  }

  // Wrap a string as a nsISupports.
  _createSupportsString(data) {
    let string = Cc["@mozilla.org/supports-string;1"].createInstance(
      Ci.nsISupportsString
    );
    string.data = data;
    return string;
  }

  /**
   * Save the updated home region and notify observers.
   *
   * @param {string} region
   *   The region to store.
   * @param {boolean} [notify]
   *   Tests can disable the notification for convenience as it
   *   may trigger an engines reload.
   */
  _setHomeRegion(region, notify = true) {
    if (region == this._home) {
      return;
    }
    log.info("Updating home region:", region);
    this._home = region;
    Services.prefs.setCharPref("browser.search.region", region);
    if (notify) {
      Services.obs.notifyObservers(
        this._createSupportsString(region),
        this.REGION_TOPIC
      );
    }
  }

  /**
   * Make the request to fetch the region from the configured service.
   */
  async _getRegion() {
    log.info("_getRegion called");
    let fetchOpts = {
      headers: { "Content-Type": "application/json" },
      credentials: "omit",
    };
    if (lazy.wifiScanningEnabled) {
      let wifiData = await this._fetchWifiData();
      if (wifiData) {
        let postData = JSON.stringify({ wifiAccessPoints: wifiData });
        log.info("Sending wifi details: ", wifiData);
        fetchOpts.method = "POST";
        fetchOpts.body = postData;
      }
    }
    let url = Services.urlFormatter.formatURLPref("browser.region.network.url");
    log.info("_getRegion url is: ", url);

    if (!url) {
      return null;
    }

    try {
      let req = await this._fetchTimeout(url, fetchOpts, lazy.networkTimeout);
      let res = await req.json();
      log.info("_getRegion returning ", res.country_code);
      return res.country_code;
    } catch (err) {
      log.error("Error fetching region", err);
      let errCode = err.message in this.TELEMETRY ? err.message : "NO_RESULT";
      throw new Error(errCode);
    }
  }

  /**
   * Setup the RemoteSetting client + sync listener and ensure
   * the map files are downloaded.
   */
  async _setupRemoteSettings() {
    log.info("_setupRemoteSettings");
    this._rsClient = RemoteSettings(COLLECTION_ID);
    this._rsClient.on("sync", this._onRegionFilesSync.bind(this));
    await this._ensureRegionFilesDownloaded();
    // Start listening to geolocation events only after
    // we know the maps are downloded.
    Services.obs.addObserver(this, GEOLOCATION_TOPIC);
  }

  /**
   * Called when RemoteSettings syncs new data, clean up any
   * stale attachments and download any new ones.
   *
   * @param {Object} syncData
   *   Object describing the data that has just been synced.
   */
  async _onRegionFilesSync({ data: { deleted } }) {
    log.info("_onRegionFilesSync");
    const toDelete = deleted.filter(d => d.attachment);
    // Remove local files of deleted records
    await Promise.all(
      toDelete.map(entry => this._rsClient.attachments.deleteDownloaded(entry))
    );
    await this._ensureRegionFilesDownloaded();
  }

  /**
   * Download the RemoteSetting record attachments, when they are
   * successfully downloaded set a flag so we can start using them
   * for geocoding.
   */
  async _ensureRegionFilesDownloaded() {
    log.info("_ensureRegionFilesDownloaded");
    let records = (await this._rsClient.get()).filter(d => d.attachment);
    log.info("_ensureRegionFilesDownloaded", records);
    if (!records.length) {
      log.info("_ensureRegionFilesDownloaded: Nothing to download");
      return;
    }
    await Promise.all(records.map(r => this._rsClient.attachments.download(r)));
    log.info("_ensureRegionFilesDownloaded complete");
    this._regionFilesReady = true;
  }

  /**
   * Fetch an attachment from RemoteSettings.
   *
   * @param {String} id
   *   The id of the record to fetch the attachment from.
   */
  async _fetchAttachment(id) {
    let record = (await this._rsClient.get({ filters: { id } })).pop();
    let { buffer } = await this._rsClient.attachments.download(record);
    let text = new TextDecoder("utf-8").decode(buffer);
    return JSON.parse(text);
  }

  /**
   * Get a map of the world with region definitions.
   */
  async _getPlainMap() {
    return this._fetchAttachment("world");
  }

  /**
   * Get a map with the regions expanded by a few km to help
   * fallback lookups when a location is not within a region.
   */
  async _getBufferedMap() {
    return this._fetchAttachment("world-buffered");
  }

  /**
   * Gets the users current location using the same reverse IP
   * request that is used for GeoLocation requests.
   *
   * @returns {Object} location
   *   Object representing the user location, with a location key
   *   that contains the lat / lng coordinates.
   */
  async _getLocation() {
    log.info("_getLocation called");
    let fetchOpts = { headers: { "Content-Type": "application/json" } };
    let url = Services.urlFormatter.formatURLPref("geo.provider.network.url");
    let req = await this._fetchTimeout(url, fetchOpts, lazy.networkTimeout);
    let result = await req.json();
    log.info("_getLocation returning", result);
    return result;
  }

  /**
   * Return the users current region using
   * request that is used for GeoLocation requests.
   *
   * @returns {String}
   *   A 2 character string representing a region.
   */
  async _getRegionLocally() {
    let { location } = await this._getLocation();
    return this._geoCode(location);
  }

  /**
   * Take a location and return the region code for that location
   * by looking up the coordinates in geojson map files.
   * Inspired by https://github.com/mozilla/ichnaea/blob/874e8284f0dfa1868e79aae64e14707eed660efe/ichnaea/geocode.py#L114
   *
   * @param {Object} location
   *   A location object containing lat + lng coordinates.
   *
   * @returns {String}
   *   A 2 character string representing a region.
   */
  async _geoCode(location) {
    let plainMap = await this._getPlainMap();
    let polygons = this._getPolygonsContainingPoint(location, plainMap);
    if (polygons.length == 1) {
      log.info("Found in single exact region");
      return polygons[0].properties.alpha2;
    }
    if (polygons.length) {
      log.info("Found in ", polygons.length, "overlapping exact regions");
      return this._findFurthest(location, polygons);
    }

    // We haven't found a match in the exact map, use the buffered map
    // to see if the point is close to a region.
    let bufferedMap = await this._getBufferedMap();
    polygons = this._getPolygonsContainingPoint(location, bufferedMap);

    if (polygons.length === 1) {
      log.info("Found in single buffered region");
      return polygons[0].properties.alpha2;
    }

    // Matched more than one region, which one of those regions
    // is it closest to without the buffer.
    if (polygons.length) {
      log.info("Found in ", polygons.length, "overlapping buffered regions");
      let regions = polygons.map(polygon => polygon.properties.alpha2);
      let unBufferedRegions = plainMap.features.filter(feature =>
        regions.includes(feature.properties.alpha2)
      );
      return this._findClosest(location, unBufferedRegions);
    }
    return null;
  }

  /**
   * Find all the polygons that contain a single point, return
   * an array of those polygons along with the region that
   * they define
   *
   * @param {Object} point
   *   A lat + lng coordinate.
   * @param {Object} map
   *   Geojson object that defined seperate regions with a list
   *   of polygons.
   *
   * @returns {Array}
   *   An array of polygons that contain the point, along with the
   *   region they define.
   */
  _getPolygonsContainingPoint(point, map) {
    let polygons = [];
    for (const feature of map.features) {
      let coords = feature.geometry.coordinates;
      if (feature.geometry.type === "Polygon") {
        if (this._polygonInPoint(point, coords[0])) {
          polygons.push(feature);
        }
      } else if (feature.geometry.type === "MultiPolygon") {
        for (const innerCoords of coords) {
          if (this._polygonInPoint(point, innerCoords[0])) {
            polygons.push(feature);
          }
        }
      }
    }
    return polygons;
  }

  /**
   * Find the largest distance between a point and any of the points that
   * make up an array of regions.
   *
   * @param {Object} location
   *   A lat + lng coordinate.
   * @param {Array} regions
   *   An array of GeoJSON region definitions.
   *
   * @returns {String}
   *   A 2 character string representing a region.
   */
  _findFurthest(location, regions) {
    let max = { distance: 0, region: null };
    this._traverse(regions, ({ lat, lng, region }) => {
      let distance = this._distanceBetween(location, { lng, lat });
      if (distance > max.distance) {
        max = { distance, region };
      }
    });
    return max.region;
  }

  /**
   * Find the smallest distance between a point and any of the points that
   * make up an array of regions.
   *
   * @param {Object} location
   *   A lat + lng coordinate.
   * @param {Array} regions
   *   An array of GeoJSON region definitions.
   *
   * @returns {String}
   *   A 2 character string representing a region.
   */
  _findClosest(location, regions) {
    let min = { distance: Infinity, region: null };
    this._traverse(regions, ({ lat, lng, region }) => {
      let distance = this._distanceBetween(location, { lng, lat });
      if (distance < min.distance) {
        min = { distance, region };
      }
    });
    return min.region;
  }

  /**
   * Utility function to loop over all the coordinate points in an
   * array of polygons and call a function on them.
   *
   * @param {Array} regions
   *   An array of GeoJSON region definitions.
   * @param {Function} fun
   *   Function to call on individual coordinates.
   */
  _traverse(regions, fun) {
    for (const region of regions) {
      if (region.geometry.type === "Polygon") {
        for (const [lng, lat] of region.geometry.coordinates[0]) {
          fun({ lat, lng, region: region.properties.alpha2 });
        }
      } else if (region.geometry.type === "MultiPolygon") {
        for (const innerCoords of region.geometry.coordinates) {
          for (const [lng, lat] of innerCoords[0]) {
            fun({ lat, lng, region: region.properties.alpha2 });
          }
        }
      }
    }
  }

  /**
   * Check whether a point is contained within a polygon using the
   * point in polygon algorithm:
   * https://en.wikipedia.org/wiki/Point_in_polygon
   * This casts a ray from the point and counts how many times
   * that ray intersects with the polygons borders, if it is
   * an odd number of times the point is inside the polygon.
   *
   * @param {Object} location
   *   A lat + lng coordinate.
   * @param {Object} polygon
   *   Array of coordinates that define the boundaries of a polygon.
   *
   * @returns {boolean}
   *   Whether the point is within the polygon.
   */
  _polygonInPoint({ lng, lat }, poly) {
    let inside = false;
    // For each edge of the polygon.
    for (let i = 0, j = poly.length - 1; i < poly.length; j = i++) {
      let xi = poly[i][0];
      let yi = poly[i][1];
      let xj = poly[j][0];
      let yj = poly[j][1];
      // Does a ray cast from the point intersect with this polygon edge.
      let intersect =
        yi > lat != yj > lat && lng < ((xj - xi) * (lat - yi)) / (yj - yi) + xi;
      // If so toggle result, an odd number of intersections
      // means the point is inside.
      if (intersect) {
        inside = !inside;
      }
    }
    return inside;
  }

  /**
   * Find the distance between 2 points.
   *
   * @param {Object} p1
   *   A lat + lng coordinate.
   * @param {Object} p2
   *   A lat + lng coordinate.
   *
   * @returns {int}
   *   The distance between the 2 points.
   */
  _distanceBetween(p1, p2) {
    return Math.hypot(p2.lng - p1.lng, p2.lat - p1.lat);
  }

  /**
   * A wrapper around fetch that implements a timeout, will throw
   * a TIMEOUT error if the request is not completed in time.
   *
   * @param {String} url
   *   The time url to fetch.
   * @param {Object} opts
   *   The options object passed to the call to fetch.
   * @param {int} timeout
   *   The time in ms to wait for the request to complete.
   */
  async _fetchTimeout(url, opts, timeout) {
    let controller = new AbortController();
    opts.signal = controller.signal;
    return Promise.race([fetch(url, opts), this._timeout(timeout, controller)]);
  }

  /**
   * Implement the timeout for network requests. This will be run for
   * all network requests, but the error will only be returned if it
   * completes first.
   *
   * @param {int} timeout
   *   The time in ms to wait for the request to complete.
   * @param {Object} controller
   *   The AbortController passed to the fetch request that
   *   allows us to abort the request.
   */
  async _timeout(timeout, controller) {
    await new Promise(resolve => lazy.setTimeout(resolve, timeout));
    if (controller) {
      // Yield so it is the TIMEOUT that is returned and not
      // the result of the abort().
      lazy.setTimeout(() => controller.abort(), 0);
    }
    throw new Error("TIMEOUT");
  }

  async _fetchWifiData() {
    log.info("fetchWifiData called");
    this.wifiService = Cc["@mozilla.org/wifi/monitor;1"].getService(
      Ci.nsIWifiMonitor
    );
    this.wifiService.startWatching(this, false);

    return new Promise(resolve => {
      this._wifiDataPromise = resolve;
    });
  }

  /**
   * If the user is using geolocation then we will see frequent updates
   * debounce those so we aren't processing them constantly.
   *
   * @returns {bool}
   *   Whether we should continue the update check.
   */
  _needsUpdateCheck() {
    let sinceUpdate = Math.round(Date.now() / 1000) - lazy.lastUpdated;
    let needsUpdate = sinceUpdate >= lazy.updateDebounce;
    if (!needsUpdate) {
      log.info(`Ignoring update check, last seen ${sinceUpdate} seconds ago`);
    }
    return needsUpdate;
  }

  /**
   * Dispatch a promise returning function to the main thread and
   * resolve when it is completed.
   */
  _idleDispatch(fun) {
    return new Promise(resolve => {
      Services.tm.idleDispatchToMainThread(fun().then(resolve));
    });
  }

  /**
   * timerManager will call this periodically to update the region
   * in case the user never users geolocation.
   */
  async _updateTimer() {
    if (this._needsUpdateCheck()) {
      await this._fetchRegion();
    }
  }

  /**
   * Called when we see geolocation updates.
   * in case the user never users geolocation.
   *
   * @param {Object} location
   *   A location object containing lat + lng coordinates.
   *
   */
  async _seenLocation(location) {
    log.info(`Got location update: ${location.lat}:${location.lng}`);
    if (this._needsUpdateCheck()) {
      let region = await this._geoCode(location);
      if (region) {
        this._setCurrentRegion(region);
      }
    }
  }

  onChange(accessPoints) {
    log.info("onChange called");
    if (!accessPoints || !this._wifiDataPromise) {
      return;
    }

    if (this.wifiService) {
      this.wifiService.stopWatching(this);
      this.wifiService = null;
    }

    if (this._wifiDataPromise) {
      let data = lazy.LocationHelper.formatWifiAccessPoints(accessPoints);
      this._wifiDataPromise(data);
      this._wifiDataPromise = null;
    }
  }

  observe(aSubject, aTopic) {
    log.info(`Observed ${aTopic}`);
    switch (aTopic) {
      case GEOLOCATION_TOPIC:
        // aSubject from GeoLocation.cpp will be a GeoPosition
        // DOM Object, but from tests we will receive a
        // wrappedJSObject so handle both here.
        let coords = aSubject.coords || aSubject.wrappedJSObject.coords;
        this._seenLocation({
          lat: coords.latitude,
          lng: coords.longitude,
        });
        break;
    }
  }

  // For tests to create blank new instances.
  newInstance() {
    return new RegionDetector();
  }
}

export let Region = new RegionDetector();
Region.init();

// A method that tries to determine if this user is in a US geography.
function isUSTimezone() {
  // Timezone assumptions! We assume that if the system clock's timezone is
  // between Newfoundland and Hawaii, that the user is in North America.

  // This includes all of South America as well, but we have relatively few
  // en-US users there, so that's OK.

  // 150 minutes = 2.5 hours (UTC-2.5), which is
  // Newfoundland Daylight Time (http://www.timeanddate.com/time/zones/ndt)

  // 600 minutes = 10 hours (UTC-10), which is
  // Hawaii-Aleutian Standard Time (http://www.timeanddate.com/time/zones/hast)

  let UTCOffset = new Date().getTimezoneOffset();
  return UTCOffset >= 150 && UTCOffset <= 600;
}
PK
!<���J66'modules/RemotePageAccessManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * RemotePageAccessManager determines which RPM functions a given
 * about page is allowed to access. It does this based on a map from about
 * page URLs to allowed functions for that page/URL.
 *
 * An RPM function will be exported into the page only if it appears
 * in the access managers's accessMap for that page's uri.
 *
 * This module may be used from both the child and parent process.
 *
 * Please note that prefs that one wants to update need to be
 * explicitly allowed within AsyncPrefs.sys.mjs.
 */
export let RemotePageAccessManager = {
  /* The accessMap lists the permissions that are allowed per page.
   * The structure should be of the following form:
   *   <URL> : {
   *     <function name>: [<keys>],
   *     ...
   *   }
   * For the page with given URL, permission is allowed for each
   * listed function with a matching key. The first argument to the
   * function must match one of the keys. If keys is an array with a
   * single asterisk element ["*"], then all values are permitted.
   */
  accessMap: {
    "about:certerror": {
      RPMSendAsyncMessage: [
        "Browser:EnableOnlineMode",
        "Browser:ResetSSLPreferences",
        "GetChangedCertPrefs",
        "Browser:OpenCaptivePortalPage",
        "Browser:SSLErrorGoBack",
        "Browser:PrimeMitm",
        "Browser:ResetEnterpriseRootsPref",
        "DisplayOfflineSupportPage",
      ],
      RPMRecordTelemetryEvent: ["*"],
      RPMAddMessageListener: ["*"],
      RPMRemoveMessageListener: ["*"],
      RPMGetFormatURLPref: ["app.support.baseURL"],
      RPMGetBoolPref: [
        "security.certerrors.mitm.priming.enabled",
        "security.certerrors.permanentOverride",
        "security.enterprise_roots.auto-enabled",
        "security.certerror.hideAddException",
        "network.trr.display_fallback_warning",
      ],
      RPMGetIntPref: [
        "security.dialog_enable_delay",
        "services.settings.clock_skew_seconds",
        "services.settings.last_update_seconds",
      ],
      RPMGetAppBuildID: ["*"],
      RPMGetInnerMostURI: ["*"],
      RPMIsWindowPrivate: ["*"],
      RPMAddToHistogram: ["*"],
    },
    "about:home": {
      RPMSendAsyncMessage: ["ActivityStream:ContentToMain"],
      RPMAddMessageListener: ["ActivityStream:MainToContent"],
    },
    "about:httpsonlyerror": {
      RPMGetFormatURLPref: ["app.support.baseURL"],
      RPMGetIntPref: ["security.dialog_enable_delay"],
      RPMSendAsyncMessage: ["goBack", "openInsecure"],
      RPMAddMessageListener: ["WWWReachable"],
      RPMTryPingSecureWWWLink: ["*"],
      RPMOpenSecureWWWLink: ["*"],
    },
    "about:certificate": {
      RPMSendQuery: ["getCertificates"],
    },
    "about:neterror": {
      RPMSendAsyncMessage: [
        "Browser:EnableOnlineMode",
        "Browser:ResetSSLPreferences",
        "GetChangedCertPrefs",
        "Browser:OpenCaptivePortalPage",
        "Browser:SSLErrorGoBack",
        "Browser:PrimeMitm",
        "Browser:ResetEnterpriseRootsPref",
        "ReportBlockingError",
        "DisplayOfflineSupportPage",
        "OpenTRRPreferences",
      ],
      RPMCheckAlternateHostAvailable: ["*"],
      RPMRecordTelemetryEvent: [
        "security.doh.neterror",
        "security.ui.tlserror",
      ],
      RPMAddMessageListener: ["*"],
      RPMRemoveMessageListener: ["*"],
      RPMGetFormatURLPref: [
        "app.support.baseURL",
        "network.trr_ui.skip_reason_learn_more_url",
      ],
      RPMGetBoolPref: [
        "security.certerror.hideAddException",
        "security.xfocsp.errorReporting.automatic",
        "security.xfocsp.errorReporting.enabled",
        "security.xfocsp.hideOpenInNewWindow",
        "network.trr.display_fallback_warning",
      ],
      RPMSetPref: [
        "security.xfocsp.errorReporting.automatic",
        "network.trr.display_fallback_warning",
      ],
      RPMAddToHistogram: ["*"],
      RPMGetInnerMostURI: ["*"],
      RPMGetHttpResponseHeader: ["*"],
      RPMIsTRROnlyFailure: ["*"],
      RPMIsFirefox: ["*"],
      RPMIsNativeFallbackFailure: ["*"],
      RPMGetTRRSkipReason: ["*"],
      RPMGetTRRDomain: ["*"],
      RPMIsSiteSpecificTRRError: ["*"],
      RPMSetTRRDisabledLoadFlags: ["*"],
      RPMSendQuery: ["Browser:AddTRRExcludedDomain"],
      RPMGetIntPref: ["network.trr.mode"],
    },
    "about:newtab": {
      RPMSendAsyncMessage: ["ActivityStream:ContentToMain"],
      RPMAddMessageListener: ["ActivityStream:MainToContent"],
    },
    "about:pocket-saved": {
      RPMSendAsyncMessage: ["*"],
      RPMAddMessageListener: ["*"],
      RPMRemoveMessageListener: ["*"],
      RPMGetStringPref: ["extensions.pocket.site"],
    },
    "about:pocket-signup": {
      RPMSendAsyncMessage: ["*"],
      RPMAddMessageListener: ["*"],
      RPMRemoveMessageListener: ["*"],
      RPMGetStringPref: ["extensions.pocket.site"],
    },
    "about:pocket-home": {
      RPMSendAsyncMessage: ["*"],
      RPMAddMessageListener: ["*"],
      RPMRemoveMessageListener: ["*"],
      RPMGetStringPref: ["extensions.pocket.site"],
    },
    "about:pocket-style-guide": {
      RPMSendAsyncMessage: ["*"],
      RPMAddMessageListener: ["*"],
      RPMRemoveMessageListener: ["*"],
    },
    "about:privatebrowsing": {
      RPMSendAsyncMessage: [
        "OpenPrivateWindow",
        "SearchBannerDismissed",
        "OpenSearchPreferences",
        "SearchHandoff",
      ],
      RPMSendQuery: [
        "IsPromoBlocked",
        "ShouldShowSearchBanner",
        "ShouldShowPromo",
        "SpecialMessageActionDispatch",
      ],
      RPMAddMessageListener: ["*"],
      RPMRemoveMessageListener: ["*"],
      RPMGetFormatURLPref: [
        "app.support.baseURL",
        "browser.privatebrowsing.vpnpromourl",
      ],
      RPMIsWindowPrivate: ["*"],
      RPMGetBoolPref: ["browser.privatebrowsing.felt-privacy-v1"],
    },
    "about:protections": {
      RPMSendAsyncMessage: [
        "OpenContentBlockingPreferences",
        "OpenAboutLogins",
        "OpenSyncPreferences",
        "ClearMonitorCache",
        "RecordEntryPoint",
      ],
      RPMSendQuery: [
        "FetchUserLoginsData",
        "FetchMonitorData",
        "FetchContentBlockingEvents",
        "FetchMobileDeviceConnected",
        "GetShowProxyCard",
        "FetchEntryPoint",
        "FetchVPNSubStatus",
        "FetchShowVPNCard",
      ],
      RPMAddMessageListener: ["*"],
      RPMRemoveMessageListener: ["*"],
      RPMSetPref: [
        "browser.contentblocking.report.show_mobile_app",
        "browser.contentblocking.report.hide_vpn_banner",
      ],
      RPMGetBoolPref: [
        "browser.contentblocking.report.lockwise.enabled",
        "browser.contentblocking.report.monitor.enabled",
        "privacy.fingerprintingProtection",
        "privacy.socialtracking.block_cookies.enabled",
        "browser.contentblocking.report.proxy.enabled",
        "privacy.trackingprotection.cryptomining.enabled",
        "privacy.trackingprotection.fingerprinting.enabled",
        "privacy.trackingprotection.enabled",
        "privacy.trackingprotection.socialtracking.enabled",
        "browser.contentblocking.report.show_mobile_app",
        "browser.contentblocking.report.hide_vpn_banner",
        "browser.vpn_promo.enabled",
      ],
      RPMGetStringPref: [
        "browser.contentblocking.category",
        "browser.contentblocking.report.monitor.url",
        "browser.contentblocking.report.monitor.sign_in_url",
        "browser.contentblocking.report.manage_devices.url",
        "browser.contentblocking.report.proxy_extension.url",
        "browser.contentblocking.report.lockwise.mobile-android.url",
        "browser.contentblocking.report.lockwise.mobile-ios.url",
        "browser.contentblocking.report.mobile-ios.url",
        "browser.contentblocking.report.mobile-android.url",
        "browser.contentblocking.report.vpn.url",
        "browser.contentblocking.report.vpn-promo.url",
        "browser.contentblocking.report.vpn-android.url",
        "browser.contentblocking.report.vpn-ios.url",
      ],
      RPMGetIntPref: ["network.cookie.cookieBehavior"],
      RPMGetFormatURLPref: [
        "browser.contentblocking.report.monitor.how_it_works.url",
        "browser.contentblocking.report.lockwise.how_it_works.url",
        "browser.contentblocking.report.monitor.preferences_url",
        "browser.contentblocking.report.monitor.home_page_url",
        "browser.contentblocking.report.social.url",
        "browser.contentblocking.report.cookie.url",
        "browser.contentblocking.report.tracker.url",
        "browser.contentblocking.report.fingerprinter.url",
        "browser.contentblocking.report.cryptominer.url",
      ],
      RPMRecordTelemetryEvent: ["*"],
    },
    "about:shoppingsidebar": {
      RPMSetPref: [
        "browser.shopping.experience2023.optedIn",
        "browser.shopping.experience2023.active",
        "browser.shopping.experience2023.ads.userEnabled",
        "browser.shopping.experience2023.sidebarClosedCount",
        "browser.shopping.experience2023.showKeepSidebarClosedMessage",
        "browser.shopping.experience2023.autoOpen.userEnabled",
      ],
      RPMGetFormatURLPref: ["app.support.baseURL"],
      RPMGetIntPref: ["browser.shopping.experience2023.sidebarClosedCount"],
      RPMGetBoolPref: [
        "browser.shopping.experience2023.showKeepSidebarClosedMessage",
      ],
    },
    "about:tabcrashed": {
      RPMSendAsyncMessage: ["Load", "closeTab", "restoreTab", "restoreAll"],
      RPMAddMessageListener: ["*"],
      RPMRemoveMessageListener: ["*"],
    },
    "about:welcome": {
      RPMSendAsyncMessage: ["ActivityStream:ContentToMain"],
      RPMAddMessageListener: ["ActivityStream:MainToContent"],
    },
  },

  /**
   * Check if access is allowed to the given feature for a given document.
   * This should be called from within the child process.
   *
   * The feature within the accessMap must list the given aValue, for access to
   * be granted.
   *
   * @param aDocument child process document to call from
   * @param aFeature to feature to check access to
   * @param aValue value that must be included with that feature's allow list
   * @returns true if access is allowed or false otherwise
   */
  checkAllowAccess(aDocument, aFeature, aValue) {
    let principal = aDocument.nodePrincipal;
    // if there is no content principal; deny access
    if (!principal) {
      return false;
    }

    return this.checkAllowAccessWithPrincipal(
      principal,
      aFeature,
      aValue,
      aDocument
    );
  },

  /**
   * Check if access is allowed to the given feature for a given principal.
   * This may be called from within the child or parent process.
   *
   * The feature within the accessMap must list the given aValue, for access to
   * be granted.
   *
   * In the parent process, the passed-in document is expected to be null.
   *
   * @param aPrincipal principal being called from
   * @param aFeature to feature to check access to
   * @param aValue value that must be included with that feature's allow list
   * @param aDocument optional child process document to call from
   * @returns true if access is allowed or false otherwise
   */
  checkAllowAccessWithPrincipal(aPrincipal, aFeature, aValue, aDocument) {
    let accessMapForFeature = this.checkAllowAccessToFeature(
      aPrincipal,
      aFeature,
      aDocument
    );
    if (!accessMapForFeature) {
      console.error(
        "RemotePageAccessManager does not allow access to Feature: ",
        aFeature,
        " for: ",
        aDocument.location
      );

      return false;
    }

    // If the actual value is in the allow list for that feature;
    // allow access
    if (accessMapForFeature.includes(aValue) || accessMapForFeature[0] == "*") {
      return true;
    }

    return false;
  },

  /**
   * Check if a particular feature can be accessed without checking for a
   * specific feature value.
   *
   * @param aPrincipal principal being called from
   * @param aFeature to feature to check access to
   * @param aDocument optional child process document to call from
   * @returns non-null allow list if access is allowed or null otherwise
   */
  checkAllowAccessToFeature(aPrincipal, aFeature, aDocument) {
    let spec;
    if (!aPrincipal.isContentPrincipal) {
      // For the sake of remote pages, when the principal has no uri,
      // we want to access the "real" document URI directly, e.g. if the
      // about: page is sandboxed.
      if (!aDocument) {
        return null;
      }
      if (!aDocument.documentURIObject.schemeIs("about")) {
        return null;
      }
      spec =
        aDocument.documentURIObject.prePath +
        aDocument.documentURIObject.filePath;
    } else {
      if (!aPrincipal.schemeIs("about")) {
        return null;
      }
      spec = aPrincipal.prePath + aPrincipal.filePath;
    }

    // Check if there is an entry for that requestying URI in the accessMap;
    // if not, deny access.
    let accessMapForURI = this.accessMap[spec];
    if (!accessMapForURI) {
      return null;
    }

    // Check if the feature is allowed to be accessed for that URI;
    // if not, deny access.
    return accessMapForURI[aFeature];
  },

  /**
   * This function adds a new page to the access map, but can only
   * be used in a test environment.
   */
  addPage(aUrl, aFunctionMap) {
    if (!Cu.isInAutomation) {
      throw new Error("Cannot only modify privileges during testing");
    }

    if (aUrl in this.accessMap) {
      throw new Error("Cannot modify privileges of existing page");
    }

    this.accessMap[aUrl] = aFunctionMap;
  },
};
PK
!<3����#modules/RemoteWebNavigation.sys.mjs// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

// This object implements the JS parts of nsIWebNavigation.
export class RemoteWebNavigation {
  constructor(aBrowser) {
    this._browser = aBrowser;
    this._cancelContentJSEpoch = 1;
    this._currentURI = null;
    this._canGoBack = false;
    this._canGoForward = false;
    this.referringURI = null;
  }

  swapBrowser(aBrowser) {
    this._browser = aBrowser;
  }

  maybeCancelContentJSExecution(aNavigationType, aOptions = {}) {
    const epoch = this._cancelContentJSEpoch++;
    this._browser.frameLoader.remoteTab.maybeCancelContentJSExecution(
      aNavigationType,
      { ...aOptions, epoch }
    );
    return epoch;
  }

  get canGoBack() {
    if (Services.appinfo.sessionHistoryInParent) {
      return this._browser.browsingContext.sessionHistory?.index > 0;
    }
    return this._canGoBack;
  }

  get canGoForward() {
    if (Services.appinfo.sessionHistoryInParent) {
      let sessionHistory = this._browser.browsingContext.sessionHistory;
      return sessionHistory?.index < sessionHistory?.count - 1;
    }
    return this._canGoForward;
  }

  goBack(requireUserInteraction = false) {
    let cancelContentJSEpoch = this.maybeCancelContentJSExecution(
      Ci.nsIRemoteTab.NAVIGATE_BACK
    );
    this._browser.browsingContext.goBack(
      cancelContentJSEpoch,
      requireUserInteraction,
      true
    );
  }
  goForward(requireUserInteraction = false) {
    let cancelContentJSEpoch = this.maybeCancelContentJSExecution(
      Ci.nsIRemoteTab.NAVIGATE_FORWARD
    );
    this._browser.browsingContext.goForward(
      cancelContentJSEpoch,
      requireUserInteraction,
      true
    );
  }
  gotoIndex(aIndex) {
    let cancelContentJSEpoch = this.maybeCancelContentJSExecution(
      Ci.nsIRemoteTab.NAVIGATE_INDEX,
      { index: aIndex }
    );
    this._browser.browsingContext.goToIndex(aIndex, cancelContentJSEpoch, true);
  }

  _speculativeConnect(uri, loadURIOptions) {
    try {
      // Let's start a network connection before the content process asks.
      // Note that we might have already set up the speculative connection in
      // some cases, especially when the url is from location bar or its popup
      // menu.
      if (uri.schemeIs("http") || uri.schemeIs("https")) {
        let isBrowserPrivate = lazy.PrivateBrowsingUtils.isBrowserPrivate(
          this._browser
        );
        let principal = loadURIOptions.triggeringPrincipal;
        // We usually have a triggeringPrincipal assigned, but in case we
        // don't have one or if it's a SystemPrincipal, let's create it with OA
        // inferred from the current context.
        if (!principal || principal.isSystemPrincipal) {
          let attrs = {
            userContextId: this._browser.getAttribute("usercontextid") || 0,
            privateBrowsingId: isBrowserPrivate ? 1 : 0,
          };
          principal = Services.scriptSecurityManager.createContentPrincipal(
            uri,
            attrs
          );
        }
        Services.io.speculativeConnect(uri, principal, null, false);
      }
    } catch (ex) {
      // Can't setup speculative connection for this uri for some
      // reason, just ignore it.
    }
  }

  loadURI(uri, loadURIOptions) {
    this._speculativeConnect(uri, loadURIOptions);
    let cancelContentJSEpoch = this.maybeCancelContentJSExecution(
      Ci.nsIRemoteTab.NAVIGATE_URL,
      { uri }
    );
    this._browser.browsingContext.loadURI(uri, {
      ...loadURIOptions,
      cancelContentJSEpoch,
    });
  }

  fixupAndLoadURIString(uriString, loadURIOptions) {
    let uri;
    try {
      let fixupFlags = Services.uriFixup.webNavigationFlagsToFixupFlags(
        uriString,
        loadURIOptions.loadFlags
      );
      let isBrowserPrivate = lazy.PrivateBrowsingUtils.isBrowserPrivate(
        this._browser
      );
      if (isBrowserPrivate) {
        fixupFlags |= Services.uriFixup.FIXUP_FLAG_PRIVATE_CONTEXT;
      }

      uri = Services.uriFixup.getFixupURIInfo(
        uriString,
        fixupFlags
      ).preferredURI;
    } catch (ex) {
      // In rare cases `uriFixup` can throw. We ignore this here, but it's
      // likely that the fixupAndLoadURIString call below will still throw,
      // hopefully with more details.
    }
    if (uri) {
      this._speculativeConnect(uri, loadURIOptions);
    }

    let cancelContentJSEpoch = this.maybeCancelContentJSExecution(
      Ci.nsIRemoteTab.NAVIGATE_URL,
      { uri }
    );
    // For now, continue to use fixup here, but note that ideally we should be
    // doing fixup only once and reusing the URI we created above. Addressing
    // this is bug 1815509.
    this._browser.browsingContext.fixupAndLoadURIString(uriString, {
      ...loadURIOptions,
      cancelContentJSEpoch,
    });
  }

  reload(aReloadFlags) {
    this._browser.browsingContext.reload(aReloadFlags);
  }
  stop(aStopFlags) {
    this._browser.browsingContext.stop(aStopFlags);
  }

  get document() {
    return this._browser.contentDocument;
  }

  get currentURI() {
    if (!this._currentURI) {
      this._currentURI = Services.io.newURI("about:blank");
    }
    return this._currentURI;
  }
  set currentURI(aURI) {
    // Bug 1498600 verify usages of systemPrincipal here
    let loadURIOptions = {
      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
    };
    this.loadURI(aURI.spec, loadURIOptions);
  }

  // Bug 1233803 - accessing the sessionHistory of remote browsers should be
  // done in content scripts.
  get sessionHistory() {
    throw new Components.Exception(
      "Not implemented",
      Cr.NS_ERROR_NOT_IMPLEMENTED
    );
  }
  set sessionHistory(aValue) {
    throw new Components.Exception(
      "Not implemented",
      Cr.NS_ERROR_NOT_IMPLEMENTED
    );
  }

  _sendMessage(aMessage, aData) {
    try {
      this._browser.sendMessageToActor(aMessage, aData, "WebNavigation");
    } catch (e) {
      console.error(e);
    }
  }
}
PK
!<�q�,��modules/ResetProfile.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "MigrationUtils", () => {
  // MigrationUtils is currently only available in browser builds.
  if (AppConstants.MOZ_BUILD_APP != "browser") {
    return undefined;
  }

  try {
    let { MigrationUtils } = ChromeUtils.importESModule(
      "resource:///modules/MigrationUtils.sys.mjs"
    );
    return MigrationUtils;
  } catch (e) {
    console.error(`Unable to load MigrationUtils.sys.mjs: ${e}`);
  }
  return undefined;
});

const MOZ_APP_NAME = AppConstants.MOZ_APP_NAME;

export var ResetProfile = {
  /**
   * Check if reset is supported for the currently running profile.
   *
   * @return boolean whether reset is supported.
   */
  resetSupported() {
    if (Services.policies && !Services.policies.isAllowed("profileRefresh")) {
      return false;
    }

    // Reset is only supported if the self-migrator used for reset exists.
    if (
      !lazy.MigrationUtils ||
      !lazy.MigrationUtils.migratorExists(MOZ_APP_NAME)
    ) {
      return false;
    }

    // We also need to be using a profile the profile manager knows about.
    let profileService = Cc[
      "@mozilla.org/toolkit/profile-service;1"
    ].getService(Ci.nsIToolkitProfileService);
    let currentProfileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
    for (let profile of profileService.profiles) {
      if (profile.rootDir && profile.rootDir.equals(currentProfileDir)) {
        return true;
      }
    }
    return false;
  },

  /**
   * Ask the user if they wish to restart the application to reset the profile.
   */
  async openConfirmationDialog(window) {
    let win = window;
    // If we are, for instance, on an about page, get the chrome window to
    // access its gDialogBox.
    if (win.docShell.chromeEventHandler) {
      win = win.browsingContext?.topChromeWindow;
    }

    let params = {
      learnMore: false,
      reset: false,
    };

    if (win.gDialogBox) {
      await win.gDialogBox.open(
        "chrome://global/content/resetProfile.xhtml",
        params
      );
    } else {
      win.openDialog(
        "chrome://global/content/resetProfile.xhtml",
        null,
        "modal,centerscreen,titlebar",
        params
      );
    }

    if (params.learnMore) {
      win.openTrustedLinkIn(
        "https://support.mozilla.org/kb/refresh-firefox-reset-add-ons-and-settings",
        "tab"
      );
      return;
    }

    if (!params.reset) {
      return;
    }

    // Set the reset profile environment variable.
    Services.env.set("MOZ_RESET_PROFILE_RESTART", "1");

    Services.startup.quit(
      Ci.nsIAppStartup.eForceQuit | Ci.nsIAppStartup.eRestart
    );
  },
};
PK
!<38�%modules/ResponsivenessMonitor.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export function ResponsivenessMonitor(intervalMS = 100) {
  this._intervalMS = intervalMS;
  this._prevTimestamp = Date.now();
  this._accumulatedDelay = 0;
  this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  this._timer.initWithCallback(
    this,
    this._intervalMS,
    Ci.nsITimer.TYPE_REPEATING_SLACK
  );
}

ResponsivenessMonitor.prototype = {
  QueryInterface: ChromeUtils.generateQI(["nsINamed", "nsITimerCallback"]),

  name: "ResponsivenessMonitor",

  notify() {
    let now = Date.now();
    this._accumulatedDelay += Math.max(
      0,
      now - this._prevTimestamp - this._intervalMS
    );
    this._prevTimestamp = now;
  },

  abort() {
    if (this._timer) {
      this._timer.cancel();
      this._timer = null;
    }
  },

  finish() {
    this.abort();
    return this._accumulatedDelay;
  },
};
PK
!<�	&�� � modules/RustSync15.sys.mjs// This file was autogenerated by the `uniffi-bindgen-gecko-js` crate.
// Trust me, you don't want to mess with it!

import { UniFFITypeError } from "resource://gre/modules/UniFFI.sys.mjs";



// Objects intended to be used in the unit tests
export var UnitTestObjs = {};

// Write/Read data to/from an ArrayBuffer
class ArrayBufferDataStream {
    constructor(arrayBuffer) {
        this.dataView = new DataView(arrayBuffer);
        this.pos = 0;
    }

    readUint8() {
        let rv = this.dataView.getUint8(this.pos);
        this.pos += 1;
        return rv;
    }

    writeUint8(value) {
        this.dataView.setUint8(this.pos, value);
        this.pos += 1;
    }

    readUint16() {
        let rv = this.dataView.getUint16(this.pos);
        this.pos += 2;
        return rv;
    }

    writeUint16(value) {
        this.dataView.setUint16(this.pos, value);
        this.pos += 2;
    }

    readUint32() {
        let rv = this.dataView.getUint32(this.pos);
        this.pos += 4;
        return rv;
    }

    writeUint32(value) {
        this.dataView.setUint32(this.pos, value);
        this.pos += 4;
    }

    readUint64() {
        let rv = this.dataView.getBigUint64(this.pos);
        this.pos += 8;
        return Number(rv);
    }

    writeUint64(value) {
        this.dataView.setBigUint64(this.pos, BigInt(value));
        this.pos += 8;
    }


    readInt8() {
        let rv = this.dataView.getInt8(this.pos);
        this.pos += 1;
        return rv;
    }

    writeInt8(value) {
        this.dataView.setInt8(this.pos, value);
        this.pos += 1;
    }

    readInt16() {
        let rv = this.dataView.getInt16(this.pos);
        this.pos += 2;
        return rv;
    }

    writeInt16(value) {
        this.dataView.setInt16(this.pos, value);
        this.pos += 2;
    }

    readInt32() {
        let rv = this.dataView.getInt32(this.pos);
        this.pos += 4;
        return rv;
    }

    writeInt32(value) {
        this.dataView.setInt32(this.pos, value);
        this.pos += 4;
    }

    readInt64() {
        let rv = this.dataView.getBigInt64(this.pos);
        this.pos += 8;
        return Number(rv);
    }

    writeInt64(value) {
        this.dataView.setBigInt64(this.pos, BigInt(value));
        this.pos += 8;
    }

    readFloat32() {
        let rv = this.dataView.getFloat32(this.pos);
        this.pos += 4;
        return rv;
    }

    writeFloat32(value) {
        this.dataView.setFloat32(this.pos, value);
        this.pos += 4;
    }

    readFloat64() {
        let rv = this.dataView.getFloat64(this.pos);
        this.pos += 8;
        return rv;
    }

    writeFloat64(value) {
        this.dataView.setFloat64(this.pos, value);
        this.pos += 8;
    }


    writeString(value) {
      const encoder = new TextEncoder();
      // Note: in order to efficiently write this data, we first write the
      // string data, reserving 4 bytes for the size.
      const dest = new Uint8Array(this.dataView.buffer, this.pos + 4);
      const encodeResult = encoder.encodeInto(value, dest);
      if (encodeResult.read != value.length) {
        throw new UniFFIError(
            "writeString: out of space when writing to ArrayBuffer.  Did the computeSize() method returned the wrong result?"
        );
      }
      const size = encodeResult.written;
      // Next, go back and write the size before the string data
      this.dataView.setUint32(this.pos, size);
      // Finally, advance our position past both the size and string data
      this.pos += size + 4;
    }

    readString() {
      const decoder = new TextDecoder();
      const size = this.readUint32();
      const source = new Uint8Array(this.dataView.buffer, this.pos, size)
      const value = decoder.decode(source);
      this.pos += size;
      return value;
    }
}

function handleRustResult(result, liftCallback, liftErrCallback) {
    switch (result.code) {
        case "success":
            return liftCallback(result.data);

        case "error":
            throw liftErrCallback(result.data);

        case "internal-error":
            if (result.data) {
                throw new UniFFIInternalError(FfiConverterString.lift(result.data));
            } else {
                throw new UniFFIInternalError("Unknown error");
            }

        default:
            throw new UniFFIError(`Unexpected status code: ${result.code}`);
    }
}

class UniFFIError {
    constructor(message) {
        this.message = message;
    }

    toString() {
        return `UniFFIError: ${this.message}`
    }
}

class UniFFIInternalError extends UniFFIError {}

// Base class for FFI converters
class FfiConverter {
    // throw `UniFFITypeError` if a value to be converted has an invalid type
    static checkType(value) {
        if (value === undefined ) {
            throw new UniFFITypeError(`undefined`);
        }
        if (value === null ) {
            throw new UniFFITypeError(`null`);
        }
    }
}

// Base class for FFI converters that lift/lower by reading/writing to an ArrayBuffer
class FfiConverterArrayBuffer extends FfiConverter {
    static lift(buf) {
        return this.read(new ArrayBufferDataStream(buf));
    }

    static lower(value) {
        const buf = new ArrayBuffer(this.computeSize(value));
        const dataStream = new ArrayBufferDataStream(buf);
        this.write(dataStream, value);
        return buf;
    }
}

// Symbols that are used to ensure that Object constructors
// can only be used with a proper UniFFI pointer
const uniffiObjectPtr = Symbol("uniffiObjectPtr");
const constructUniffiObject = Symbol("constructUniffiObject");
UnitTestObjs.uniffiObjectPtr = uniffiObjectPtr;

// Export the FFIConverter object to make external types work.
export class FfiConverterString extends FfiConverter {
    static checkType(value) {
        super.checkType(value);
        if (typeof value !== "string") {
            throw new UniFFITypeError(`${value} is not a string`);
        }
    }

    static lift(buf) {
        const decoder = new TextDecoder();
        const utf8Arr = new Uint8Array(buf);
        return decoder.decode(utf8Arr);
    }
    static lower(value) {
        const encoder = new TextEncoder();
        return encoder.encode(value).buffer;
    }

    static write(dataStream, value) {
        dataStream.writeString(value);
    }

    static read(dataStream) {
        return dataStream.readString();
    }

    static computeSize(value) {
        const encoder = new TextEncoder();
        return 4 + encoder.encode(value).length
    }
}


export const DeviceType = {
    DESKTOP: 1,
    MOBILE: 2,
    TABLET: 3,
    VR: 4,
    TV: 5,
    UNKNOWN: 6,
};

Object.freeze(DeviceType);
// Export the FFIConverter object to make external types work.
export class FfiConverterTypeDeviceType extends FfiConverterArrayBuffer {
    static read(dataStream) {
        switch (dataStream.readInt32()) {
            case 1:
                return DeviceType.DESKTOP
            case 2:
                return DeviceType.MOBILE
            case 3:
                return DeviceType.TABLET
            case 4:
                return DeviceType.VR
            case 5:
                return DeviceType.TV
            case 6:
                return DeviceType.UNKNOWN
            default:
                throw new UniFFITypeError("Unknown DeviceType variant");
        }
    }

    static write(dataStream, value) {
        if (value === DeviceType.DESKTOP) {
            dataStream.writeInt32(1);
            return;
        }
        if (value === DeviceType.MOBILE) {
            dataStream.writeInt32(2);
            return;
        }
        if (value === DeviceType.TABLET) {
            dataStream.writeInt32(3);
            return;
        }
        if (value === DeviceType.VR) {
            dataStream.writeInt32(4);
            return;
        }
        if (value === DeviceType.TV) {
            dataStream.writeInt32(5);
            return;
        }
        if (value === DeviceType.UNKNOWN) {
            dataStream.writeInt32(6);
            return;
        }
        throw new UniFFITypeError("Unknown DeviceType variant");
    }

    static computeSize(value) {
        return 4;
    }

    static checkType(value) {
      if (!Number.isInteger(value) || value < 1 || value > 6) {
          throw new UniFFITypeError(`${value} is not a valid value for DeviceType`);
      }
    }
}





PK
!<`;�%�%�modules/RustTabs.sys.mjs// This file was autogenerated by the `uniffi-bindgen-gecko-js` crate.
// Trust me, you don't want to mess with it!

import { UniFFITypeError } from "resource://gre/modules/UniFFI.sys.mjs";



// Objects intended to be used in the unit tests
export var UnitTestObjs = {};

// Write/Read data to/from an ArrayBuffer
class ArrayBufferDataStream {
    constructor(arrayBuffer) {
        this.dataView = new DataView(arrayBuffer);
        this.pos = 0;
    }

    readUint8() {
        let rv = this.dataView.getUint8(this.pos);
        this.pos += 1;
        return rv;
    }

    writeUint8(value) {
        this.dataView.setUint8(this.pos, value);
        this.pos += 1;
    }

    readUint16() {
        let rv = this.dataView.getUint16(this.pos);
        this.pos += 2;
        return rv;
    }

    writeUint16(value) {
        this.dataView.setUint16(this.pos, value);
        this.pos += 2;
    }

    readUint32() {
        let rv = this.dataView.getUint32(this.pos);
        this.pos += 4;
        return rv;
    }

    writeUint32(value) {
        this.dataView.setUint32(this.pos, value);
        this.pos += 4;
    }

    readUint64() {
        let rv = this.dataView.getBigUint64(this.pos);
        this.pos += 8;
        return Number(rv);
    }

    writeUint64(value) {
        this.dataView.setBigUint64(this.pos, BigInt(value));
        this.pos += 8;
    }


    readInt8() {
        let rv = this.dataView.getInt8(this.pos);
        this.pos += 1;
        return rv;
    }

    writeInt8(value) {
        this.dataView.setInt8(this.pos, value);
        this.pos += 1;
    }

    readInt16() {
        let rv = this.dataView.getInt16(this.pos);
        this.pos += 2;
        return rv;
    }

    writeInt16(value) {
        this.dataView.setInt16(this.pos, value);
        this.pos += 2;
    }

    readInt32() {
        let rv = this.dataView.getInt32(this.pos);
        this.pos += 4;
        return rv;
    }

    writeInt32(value) {
        this.dataView.setInt32(this.pos, value);
        this.pos += 4;
    }

    readInt64() {
        let rv = this.dataView.getBigInt64(this.pos);
        this.pos += 8;
        return Number(rv);
    }

    writeInt64(value) {
        this.dataView.setBigInt64(this.pos, BigInt(value));
        this.pos += 8;
    }

    readFloat32() {
        let rv = this.dataView.getFloat32(this.pos);
        this.pos += 4;
        return rv;
    }

    writeFloat32(value) {
        this.dataView.setFloat32(this.pos, value);
        this.pos += 4;
    }

    readFloat64() {
        let rv = this.dataView.getFloat64(this.pos);
        this.pos += 8;
        return rv;
    }

    writeFloat64(value) {
        this.dataView.setFloat64(this.pos, value);
        this.pos += 8;
    }


    writeString(value) {
      const encoder = new TextEncoder();
      // Note: in order to efficiently write this data, we first write the
      // string data, reserving 4 bytes for the size.
      const dest = new Uint8Array(this.dataView.buffer, this.pos + 4);
      const encodeResult = encoder.encodeInto(value, dest);
      if (encodeResult.read != value.length) {
        throw new UniFFIError(
            "writeString: out of space when writing to ArrayBuffer.  Did the computeSize() method returned the wrong result?"
        );
      }
      const size = encodeResult.written;
      // Next, go back and write the size before the string data
      this.dataView.setUint32(this.pos, size);
      // Finally, advance our position past both the size and string data
      this.pos += size + 4;
    }

    readString() {
      const decoder = new TextDecoder();
      const size = this.readUint32();
      const source = new Uint8Array(this.dataView.buffer, this.pos, size)
      const value = decoder.decode(source);
      this.pos += size;
      return value;
    }

    // Reads a RemoteCommandStore pointer from the data stream
    // UniFFI Pointers are **always** 8 bytes long. That is enforced
    // by the C++ and Rust Scaffolding code.
    readPointerRemoteCommandStore() {
        const pointerId = 4; // tabs:RemoteCommandStore
        const res = UniFFIScaffolding.readPointer(pointerId, this.dataView.buffer, this.pos);
        this.pos += 8;
        return res;
    }

    // Writes a RemoteCommandStore pointer into the data stream
    // UniFFI Pointers are **always** 8 bytes long. That is enforced
    // by the C++ and Rust Scaffolding code.
    writePointerRemoteCommandStore(value) {
        const pointerId = 4; // tabs:RemoteCommandStore
        UniFFIScaffolding.writePointer(pointerId, value, this.dataView.buffer, this.pos);
        this.pos += 8;
    }
    

    // Reads a TabsBridgedEngine pointer from the data stream
    // UniFFI Pointers are **always** 8 bytes long. That is enforced
    // by the C++ and Rust Scaffolding code.
    readPointerTabsBridgedEngine() {
        const pointerId = 5; // tabs:TabsBridgedEngine
        const res = UniFFIScaffolding.readPointer(pointerId, this.dataView.buffer, this.pos);
        this.pos += 8;
        return res;
    }

    // Writes a TabsBridgedEngine pointer into the data stream
    // UniFFI Pointers are **always** 8 bytes long. That is enforced
    // by the C++ and Rust Scaffolding code.
    writePointerTabsBridgedEngine(value) {
        const pointerId = 5; // tabs:TabsBridgedEngine
        UniFFIScaffolding.writePointer(pointerId, value, this.dataView.buffer, this.pos);
        this.pos += 8;
    }
    

    // Reads a TabsStore pointer from the data stream
    // UniFFI Pointers are **always** 8 bytes long. That is enforced
    // by the C++ and Rust Scaffolding code.
    readPointerTabsStore() {
        const pointerId = 6; // tabs:TabsStore
        const res = UniFFIScaffolding.readPointer(pointerId, this.dataView.buffer, this.pos);
        this.pos += 8;
        return res;
    }

    // Writes a TabsStore pointer into the data stream
    // UniFFI Pointers are **always** 8 bytes long. That is enforced
    // by the C++ and Rust Scaffolding code.
    writePointerTabsStore(value) {
        const pointerId = 6; // tabs:TabsStore
        UniFFIScaffolding.writePointer(pointerId, value, this.dataView.buffer, this.pos);
        this.pos += 8;
    }
    
}

function handleRustResult(result, liftCallback, liftErrCallback) {
    switch (result.code) {
        case "success":
            return liftCallback(result.data);

        case "error":
            throw liftErrCallback(result.data);

        case "internal-error":
            if (result.data) {
                throw new UniFFIInternalError(FfiConverterString.lift(result.data));
            } else {
                throw new UniFFIInternalError("Unknown error");
            }

        default:
            throw new UniFFIError(`Unexpected status code: ${result.code}`);
    }
}

class UniFFIError {
    constructor(message) {
        this.message = message;
    }

    toString() {
        return `UniFFIError: ${this.message}`
    }
}

class UniFFIInternalError extends UniFFIError {}

// Base class for FFI converters
class FfiConverter {
    // throw `UniFFITypeError` if a value to be converted has an invalid type
    static checkType(value) {
        if (value === undefined ) {
            throw new UniFFITypeError(`undefined`);
        }
        if (value === null ) {
            throw new UniFFITypeError(`null`);
        }
    }
}

// Base class for FFI converters that lift/lower by reading/writing to an ArrayBuffer
class FfiConverterArrayBuffer extends FfiConverter {
    static lift(buf) {
        return this.read(new ArrayBufferDataStream(buf));
    }

    static lower(value) {
        const buf = new ArrayBuffer(this.computeSize(value));
        const dataStream = new ArrayBufferDataStream(buf);
        this.write(dataStream, value);
        return buf;
    }
}

// Symbols that are used to ensure that Object constructors
// can only be used with a proper UniFFI pointer
const uniffiObjectPtr = Symbol("uniffiObjectPtr");
const constructUniffiObject = Symbol("constructUniffiObject");
UnitTestObjs.uniffiObjectPtr = uniffiObjectPtr;

// Export the FFIConverter object to make external types work.
export class FfiConverterI64 extends FfiConverter {
    static checkType(value) {
        super.checkType(value);
        if (!Number.isSafeInteger(value)) {
            throw new UniFFITypeError(`${value} exceeds the safe integer bounds`);
        }
    }
    static computeSize() {
        return 8;
    }
    static lift(value) {
        return value;
    }
    static lower(value) {
        return value;
    }
    static write(dataStream, value) {
        dataStream.writeInt64(value)
    }
    static read(dataStream) {
        return dataStream.readInt64()
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterBool extends FfiConverter {
    static computeSize() {
        return 1;
    }
    static lift(value) {
        return value == 1;
    }
    static lower(value) {
        if (value) {
            return 1;
        } else {
            return 0;
        }
    }
    static write(dataStream, value) {
        dataStream.writeUint8(this.lower(value))
    }
    static read(dataStream) {
        return this.lift(dataStream.readUint8())
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterString extends FfiConverter {
    static checkType(value) {
        super.checkType(value);
        if (typeof value !== "string") {
            throw new UniFFITypeError(`${value} is not a string`);
        }
    }

    static lift(buf) {
        const decoder = new TextDecoder();
        const utf8Arr = new Uint8Array(buf);
        return decoder.decode(utf8Arr);
    }
    static lower(value) {
        const encoder = new TextEncoder();
        return encoder.encode(value).buffer;
    }

    static write(dataStream, value) {
        dataStream.writeString(value);
    }

    static read(dataStream) {
        return dataStream.readString();
    }

    static computeSize(value) {
        const encoder = new TextEncoder();
        return 4 + encoder.encode(value).length
    }
}

export class RemoteCommandStore {
    // Use `init` to instantiate this class.
    // DO NOT USE THIS CONSTRUCTOR DIRECTLY
    constructor(opts) {
        if (!Object.prototype.hasOwnProperty.call(opts, constructUniffiObject)) {
            throw new UniFFIError("Attempting to construct an object using the JavaScript constructor directly" +
            "Please use a UDL defined constructor, or the init function for the primary constructor")
        }
        if (!opts[constructUniffiObject] instanceof UniFFIPointer) {
            throw new UniFFIError("Attempting to create a UniFFI object with a pointer that is not an instance of UniFFIPointer")
        }
        this[uniffiObjectPtr] = opts[constructUniffiObject];
    }

    addRemoteCommand(deviceId,command) {
        const liftResult = (result) => FfiConverterBool.lift(result);
        const liftError = (data) => FfiConverterTypeTabsApiError.lift(data);
        const functionCall = () => {
            try {
                FfiConverterString.checkType(deviceId)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("deviceId");
                }
                throw e;
            }
            try {
                FfiConverterTypeRemoteCommand.checkType(command)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("command");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                33, // tabs:uniffi_tabs_fn_method_remotecommandstore_add_remote_command
                FfiConverterTypeRemoteCommandStore.lower(this),
                FfiConverterString.lower(deviceId),
                FfiConverterTypeRemoteCommand.lower(command),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    addRemoteCommandAt(deviceId,command,when) {
        const liftResult = (result) => FfiConverterBool.lift(result);
        const liftError = (data) => FfiConverterTypeTabsApiError.lift(data);
        const functionCall = () => {
            try {
                FfiConverterString.checkType(deviceId)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("deviceId");
                }
                throw e;
            }
            try {
                FfiConverterTypeRemoteCommand.checkType(command)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("command");
                }
                throw e;
            }
            try {
                FfiConverterTypeTimestamp.checkType(when)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("when");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                34, // tabs:uniffi_tabs_fn_method_remotecommandstore_add_remote_command_at
                FfiConverterTypeRemoteCommandStore.lower(this),
                FfiConverterString.lower(deviceId),
                FfiConverterTypeRemoteCommand.lower(command),
                FfiConverterTypeTimestamp.lower(when),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    getUnsentCommands() {
        const liftResult = (result) => FfiConverterSequenceTypePendingCommand.lift(result);
        const liftError = (data) => FfiConverterTypeTabsApiError.lift(data);
        const functionCall = () => {
            return UniFFIScaffolding.callAsync(
                35, // tabs:uniffi_tabs_fn_method_remotecommandstore_get_unsent_commands
                FfiConverterTypeRemoteCommandStore.lower(this),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    removeRemoteCommand(deviceId,command) {
        const liftResult = (result) => FfiConverterBool.lift(result);
        const liftError = (data) => FfiConverterTypeTabsApiError.lift(data);
        const functionCall = () => {
            try {
                FfiConverterString.checkType(deviceId)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("deviceId");
                }
                throw e;
            }
            try {
                FfiConverterTypeRemoteCommand.checkType(command)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("command");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                36, // tabs:uniffi_tabs_fn_method_remotecommandstore_remove_remote_command
                FfiConverterTypeRemoteCommandStore.lower(this),
                FfiConverterString.lower(deviceId),
                FfiConverterTypeRemoteCommand.lower(command),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    setPendingCommandSent(command) {
        const liftResult = (result) => FfiConverterBool.lift(result);
        const liftError = (data) => FfiConverterTypeTabsApiError.lift(data);
        const functionCall = () => {
            try {
                FfiConverterTypePendingCommand.checkType(command)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("command");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                37, // tabs:uniffi_tabs_fn_method_remotecommandstore_set_pending_command_sent
                FfiConverterTypeRemoteCommandStore.lower(this),
                FfiConverterTypePendingCommand.lower(command),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeRemoteCommandStore extends FfiConverter {
    static lift(value) {
        const opts = {};
        opts[constructUniffiObject] = value;
        return new RemoteCommandStore(opts);
    }

    static lower(value) {
        const ptr = value[uniffiObjectPtr];
        if (!(ptr instanceof UniFFIPointer)) {
            throw new UniFFITypeError("Object is not a 'RemoteCommandStore' instance");
        }
        return ptr;
    }

    static read(dataStream) {
        return this.lift(dataStream.readPointerRemoteCommandStore());
    }

    static write(dataStream, value) {
        dataStream.writePointerRemoteCommandStore(value[uniffiObjectPtr]);
    }

    static computeSize(value) {
        return 8;
    }
}

export class TabsBridgedEngine {
    // Use `init` to instantiate this class.
    // DO NOT USE THIS CONSTRUCTOR DIRECTLY
    constructor(opts) {
        if (!Object.prototype.hasOwnProperty.call(opts, constructUniffiObject)) {
            throw new UniFFIError("Attempting to construct an object using the JavaScript constructor directly" +
            "Please use a UDL defined constructor, or the init function for the primary constructor")
        }
        if (!opts[constructUniffiObject] instanceof UniFFIPointer) {
            throw new UniFFIError("Attempting to create a UniFFI object with a pointer that is not an instance of UniFFIPointer")
        }
        this[uniffiObjectPtr] = opts[constructUniffiObject];
    }

    apply() {
        const liftResult = (result) => FfiConverterSequencestring.lift(result);
        const liftError = (data) => FfiConverterTypeTabsApiError.lift(data);
        const functionCall = () => {
            return UniFFIScaffolding.callAsync(
                39, // tabs:uniffi_tabs_fn_method_tabsbridgedengine_apply
                FfiConverterTypeTabsBridgedEngine.lower(this),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    ensureCurrentSyncId(newSyncId) {
        const liftResult = (result) => FfiConverterString.lift(result);
        const liftError = (data) => FfiConverterTypeTabsApiError.lift(data);
        const functionCall = () => {
            try {
                FfiConverterString.checkType(newSyncId)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("newSyncId");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                40, // tabs:uniffi_tabs_fn_method_tabsbridgedengine_ensure_current_sync_id
                FfiConverterTypeTabsBridgedEngine.lower(this),
                FfiConverterString.lower(newSyncId),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    lastSync() {
        const liftResult = (result) => FfiConverterI64.lift(result);
        const liftError = (data) => FfiConverterTypeTabsApiError.lift(data);
        const functionCall = () => {
            return UniFFIScaffolding.callAsync(
                41, // tabs:uniffi_tabs_fn_method_tabsbridgedengine_last_sync
                FfiConverterTypeTabsBridgedEngine.lower(this),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    prepareForSync(clientData) {
        const liftResult = (result) => undefined;
        const liftError = (data) => FfiConverterTypeTabsApiError.lift(data);
        const functionCall = () => {
            try {
                FfiConverterString.checkType(clientData)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("clientData");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                42, // tabs:uniffi_tabs_fn_method_tabsbridgedengine_prepare_for_sync
                FfiConverterTypeTabsBridgedEngine.lower(this),
                FfiConverterString.lower(clientData),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    reset() {
        const liftResult = (result) => undefined;
        const liftError = (data) => FfiConverterTypeTabsApiError.lift(data);
        const functionCall = () => {
            return UniFFIScaffolding.callAsync(
                43, // tabs:uniffi_tabs_fn_method_tabsbridgedengine_reset
                FfiConverterTypeTabsBridgedEngine.lower(this),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    resetSyncId() {
        const liftResult = (result) => FfiConverterString.lift(result);
        const liftError = (data) => FfiConverterTypeTabsApiError.lift(data);
        const functionCall = () => {
            return UniFFIScaffolding.callAsync(
                44, // tabs:uniffi_tabs_fn_method_tabsbridgedengine_reset_sync_id
                FfiConverterTypeTabsBridgedEngine.lower(this),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    setLastSync(lastSync) {
        const liftResult = (result) => undefined;
        const liftError = (data) => FfiConverterTypeTabsApiError.lift(data);
        const functionCall = () => {
            try {
                FfiConverterI64.checkType(lastSync)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("lastSync");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                45, // tabs:uniffi_tabs_fn_method_tabsbridgedengine_set_last_sync
                FfiConverterTypeTabsBridgedEngine.lower(this),
                FfiConverterI64.lower(lastSync),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    setUploaded(newTimestamp,uploadedIds) {
        const liftResult = (result) => undefined;
        const liftError = (data) => FfiConverterTypeTabsApiError.lift(data);
        const functionCall = () => {
            try {
                FfiConverterI64.checkType(newTimestamp)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("newTimestamp");
                }
                throw e;
            }
            try {
                FfiConverterSequenceTypeTabsGuid.checkType(uploadedIds)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("uploadedIds");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                46, // tabs:uniffi_tabs_fn_method_tabsbridgedengine_set_uploaded
                FfiConverterTypeTabsBridgedEngine.lower(this),
                FfiConverterI64.lower(newTimestamp),
                FfiConverterSequenceTypeTabsGuid.lower(uploadedIds),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    storeIncoming(incomingEnvelopesAsJson) {
        const liftResult = (result) => undefined;
        const liftError = (data) => FfiConverterTypeTabsApiError.lift(data);
        const functionCall = () => {
            try {
                FfiConverterSequencestring.checkType(incomingEnvelopesAsJson)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("incomingEnvelopesAsJson");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                47, // tabs:uniffi_tabs_fn_method_tabsbridgedengine_store_incoming
                FfiConverterTypeTabsBridgedEngine.lower(this),
                FfiConverterSequencestring.lower(incomingEnvelopesAsJson),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    syncFinished() {
        const liftResult = (result) => undefined;
        const liftError = (data) => FfiConverterTypeTabsApiError.lift(data);
        const functionCall = () => {
            return UniFFIScaffolding.callAsync(
                48, // tabs:uniffi_tabs_fn_method_tabsbridgedengine_sync_finished
                FfiConverterTypeTabsBridgedEngine.lower(this),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    syncId() {
        const liftResult = (result) => FfiConverterOptionalstring.lift(result);
        const liftError = (data) => FfiConverterTypeTabsApiError.lift(data);
        const functionCall = () => {
            return UniFFIScaffolding.callAsync(
                49, // tabs:uniffi_tabs_fn_method_tabsbridgedengine_sync_id
                FfiConverterTypeTabsBridgedEngine.lower(this),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    syncStarted() {
        const liftResult = (result) => undefined;
        const liftError = (data) => FfiConverterTypeTabsApiError.lift(data);
        const functionCall = () => {
            return UniFFIScaffolding.callAsync(
                50, // tabs:uniffi_tabs_fn_method_tabsbridgedengine_sync_started
                FfiConverterTypeTabsBridgedEngine.lower(this),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    wipe() {
        const liftResult = (result) => undefined;
        const liftError = (data) => FfiConverterTypeTabsApiError.lift(data);
        const functionCall = () => {
            return UniFFIScaffolding.callAsync(
                51, // tabs:uniffi_tabs_fn_method_tabsbridgedengine_wipe
                FfiConverterTypeTabsBridgedEngine.lower(this),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeTabsBridgedEngine extends FfiConverter {
    static lift(value) {
        const opts = {};
        opts[constructUniffiObject] = value;
        return new TabsBridgedEngine(opts);
    }

    static lower(value) {
        const ptr = value[uniffiObjectPtr];
        if (!(ptr instanceof UniFFIPointer)) {
            throw new UniFFITypeError("Object is not a 'TabsBridgedEngine' instance");
        }
        return ptr;
    }

    static read(dataStream) {
        return this.lift(dataStream.readPointerTabsBridgedEngine());
    }

    static write(dataStream, value) {
        dataStream.writePointerTabsBridgedEngine(value[uniffiObjectPtr]);
    }

    static computeSize(value) {
        return 8;
    }
}

export class TabsStore {
    // Use `init` to instantiate this class.
    // DO NOT USE THIS CONSTRUCTOR DIRECTLY
    constructor(opts) {
        if (!Object.prototype.hasOwnProperty.call(opts, constructUniffiObject)) {
            throw new UniFFIError("Attempting to construct an object using the JavaScript constructor directly" +
            "Please use a UDL defined constructor, or the init function for the primary constructor")
        }
        if (!opts[constructUniffiObject] instanceof UniFFIPointer) {
            throw new UniFFIError("Attempting to create a UniFFI object with a pointer that is not an instance of UniFFIPointer")
        }
        this[uniffiObjectPtr] = opts[constructUniffiObject];
    }
    /**
     * An async constructor for TabsStore.
     * 
     * @returns {Promise<TabsStore>}: A promise that resolves
     *      to a newly constructed TabsStore
     */
    static init(path) {
        const liftResult = (result) => FfiConverterTypeTabsStore.lift(result);
        const liftError = null;
        const functionCall = () => {
            try {
                FfiConverterString.checkType(path)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("path");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                53, // tabs:uniffi_tabs_fn_constructor_tabsstore_new
                FfiConverterString.lower(path),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }}

    bridgedEngine() {
        const liftResult = (result) => FfiConverterTypeTabsBridgedEngine.lift(result);
        const liftError = null;
        const functionCall = () => {
            return UniFFIScaffolding.callAsync(
                54, // tabs:uniffi_tabs_fn_method_tabsstore_bridged_engine
                FfiConverterTypeTabsStore.lower(this),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    getAll() {
        const liftResult = (result) => FfiConverterSequenceTypeClientRemoteTabs.lift(result);
        const liftError = null;
        const functionCall = () => {
            return UniFFIScaffolding.callAsync(
                55, // tabs:uniffi_tabs_fn_method_tabsstore_get_all
                FfiConverterTypeTabsStore.lower(this),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    newRemoteCommandStore() {
        const liftResult = (result) => FfiConverterTypeRemoteCommandStore.lift(result);
        const liftError = null;
        const functionCall = () => {
            return UniFFIScaffolding.callAsync(
                56, // tabs:uniffi_tabs_fn_method_tabsstore_new_remote_command_store
                FfiConverterTypeTabsStore.lower(this),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    registerWithSyncManager() {
        const liftResult = (result) => undefined;
        const liftError = null;
        const functionCall = () => {
            return UniFFIScaffolding.callAsync(
                57, // tabs:uniffi_tabs_fn_method_tabsstore_register_with_sync_manager
                FfiConverterTypeTabsStore.lower(this),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

    setLocalTabs(remoteTabs) {
        const liftResult = (result) => undefined;
        const liftError = null;
        const functionCall = () => {
            try {
                FfiConverterSequenceTypeRemoteTabRecord.checkType(remoteTabs)
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart("remoteTabs");
                }
                throw e;
            }
            return UniFFIScaffolding.callAsync(
                58, // tabs:uniffi_tabs_fn_method_tabsstore_set_local_tabs
                FfiConverterTypeTabsStore.lower(this),
                FfiConverterSequenceTypeRemoteTabRecord.lower(remoteTabs),
            )
        }
        try {
            return functionCall().then((result) => handleRustResult(result, liftResult, liftError));
        }  catch (error) {
            return Promise.reject(error)
        }
    }

}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeTabsStore extends FfiConverter {
    static lift(value) {
        const opts = {};
        opts[constructUniffiObject] = value;
        return new TabsStore(opts);
    }

    static lower(value) {
        const ptr = value[uniffiObjectPtr];
        if (!(ptr instanceof UniFFIPointer)) {
            throw new UniFFITypeError("Object is not a 'TabsStore' instance");
        }
        return ptr;
    }

    static read(dataStream) {
        return this.lift(dataStream.readPointerTabsStore());
    }

    static write(dataStream, value) {
        dataStream.writePointerTabsStore(value[uniffiObjectPtr]);
    }

    static computeSize(value) {
        return 8;
    }
}

export class ClientRemoteTabs {
    constructor({ clientId, clientName, deviceType, lastModified, remoteTabs } = {}) {
        try {
            FfiConverterString.checkType(clientId)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("clientId");
            }
            throw e;
        }
        try {
            FfiConverterString.checkType(clientName)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("clientName");
            }
            throw e;
        }
        try {
            FfiConverterTypeDeviceType.checkType(deviceType)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("deviceType");
            }
            throw e;
        }
        try {
            FfiConverterI64.checkType(lastModified)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("lastModified");
            }
            throw e;
        }
        try {
            FfiConverterSequenceTypeRemoteTabRecord.checkType(remoteTabs)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("remoteTabs");
            }
            throw e;
        }
        this.clientId = clientId;
        this.clientName = clientName;
        this.deviceType = deviceType;
        this.lastModified = lastModified;
        this.remoteTabs = remoteTabs;
    }
    equals(other) {
        return (
            this.clientId == other.clientId &&
            this.clientName == other.clientName &&
            this.deviceType == other.deviceType &&
            this.lastModified == other.lastModified &&
            this.remoteTabs == other.remoteTabs
        )
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeClientRemoteTabs extends FfiConverterArrayBuffer {
    static read(dataStream) {
        return new ClientRemoteTabs({
            clientId: FfiConverterString.read(dataStream),
            clientName: FfiConverterString.read(dataStream),
            deviceType: FfiConverterTypeDeviceType.read(dataStream),
            lastModified: FfiConverterI64.read(dataStream),
            remoteTabs: FfiConverterSequenceTypeRemoteTabRecord.read(dataStream),
        });
    }
    static write(dataStream, value) {
        FfiConverterString.write(dataStream, value.clientId);
        FfiConverterString.write(dataStream, value.clientName);
        FfiConverterTypeDeviceType.write(dataStream, value.deviceType);
        FfiConverterI64.write(dataStream, value.lastModified);
        FfiConverterSequenceTypeRemoteTabRecord.write(dataStream, value.remoteTabs);
    }

    static computeSize(value) {
        let totalSize = 0;
        totalSize += FfiConverterString.computeSize(value.clientId);
        totalSize += FfiConverterString.computeSize(value.clientName);
        totalSize += FfiConverterTypeDeviceType.computeSize(value.deviceType);
        totalSize += FfiConverterI64.computeSize(value.lastModified);
        totalSize += FfiConverterSequenceTypeRemoteTabRecord.computeSize(value.remoteTabs);
        return totalSize
    }

    static checkType(value) {
        super.checkType(value);
        if (!(value instanceof ClientRemoteTabs)) {
            throw new UniFFITypeError(`Expected 'ClientRemoteTabs', found '${typeof value}'`);
        }
        try {
            FfiConverterString.checkType(value.clientId);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".clientId");
            }
            throw e;
        }
        try {
            FfiConverterString.checkType(value.clientName);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".clientName");
            }
            throw e;
        }
        try {
            FfiConverterTypeDeviceType.checkType(value.deviceType);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".deviceType");
            }
            throw e;
        }
        try {
            FfiConverterI64.checkType(value.lastModified);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".lastModified");
            }
            throw e;
        }
        try {
            FfiConverterSequenceTypeRemoteTabRecord.checkType(value.remoteTabs);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".remoteTabs");
            }
            throw e;
        }
    }
}

export class PendingCommand {
    constructor({ deviceId, command, timeRequested, timeSent } = {}) {
        try {
            FfiConverterString.checkType(deviceId)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("deviceId");
            }
            throw e;
        }
        try {
            FfiConverterTypeRemoteCommand.checkType(command)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("command");
            }
            throw e;
        }
        try {
            FfiConverterTypeTimestamp.checkType(timeRequested)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("timeRequested");
            }
            throw e;
        }
        try {
            FfiConverterOptionalTypeTimestamp.checkType(timeSent)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("timeSent");
            }
            throw e;
        }
        this.deviceId = deviceId;
        this.command = command;
        this.timeRequested = timeRequested;
        this.timeSent = timeSent;
    }
    equals(other) {
        return (
            this.deviceId == other.deviceId &&
            this.command == other.command &&
            this.timeRequested == other.timeRequested &&
            this.timeSent == other.timeSent
        )
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypePendingCommand extends FfiConverterArrayBuffer {
    static read(dataStream) {
        return new PendingCommand({
            deviceId: FfiConverterString.read(dataStream),
            command: FfiConverterTypeRemoteCommand.read(dataStream),
            timeRequested: FfiConverterTypeTimestamp.read(dataStream),
            timeSent: FfiConverterOptionalTypeTimestamp.read(dataStream),
        });
    }
    static write(dataStream, value) {
        FfiConverterString.write(dataStream, value.deviceId);
        FfiConverterTypeRemoteCommand.write(dataStream, value.command);
        FfiConverterTypeTimestamp.write(dataStream, value.timeRequested);
        FfiConverterOptionalTypeTimestamp.write(dataStream, value.timeSent);
    }

    static computeSize(value) {
        let totalSize = 0;
        totalSize += FfiConverterString.computeSize(value.deviceId);
        totalSize += FfiConverterTypeRemoteCommand.computeSize(value.command);
        totalSize += FfiConverterTypeTimestamp.computeSize(value.timeRequested);
        totalSize += FfiConverterOptionalTypeTimestamp.computeSize(value.timeSent);
        return totalSize
    }

    static checkType(value) {
        super.checkType(value);
        if (!(value instanceof PendingCommand)) {
            throw new UniFFITypeError(`Expected 'PendingCommand', found '${typeof value}'`);
        }
        try {
            FfiConverterString.checkType(value.deviceId);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".deviceId");
            }
            throw e;
        }
        try {
            FfiConverterTypeRemoteCommand.checkType(value.command);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".command");
            }
            throw e;
        }
        try {
            FfiConverterTypeTimestamp.checkType(value.timeRequested);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".timeRequested");
            }
            throw e;
        }
        try {
            FfiConverterOptionalTypeTimestamp.checkType(value.timeSent);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".timeSent");
            }
            throw e;
        }
    }
}

export class RemoteTabRecord {
    constructor({ title, urlHistory, icon, lastUsed, inactive = false } = {}) {
        try {
            FfiConverterString.checkType(title)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("title");
            }
            throw e;
        }
        try {
            FfiConverterSequencestring.checkType(urlHistory)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("urlHistory");
            }
            throw e;
        }
        try {
            FfiConverterOptionalstring.checkType(icon)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("icon");
            }
            throw e;
        }
        try {
            FfiConverterI64.checkType(lastUsed)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("lastUsed");
            }
            throw e;
        }
        try {
            FfiConverterBool.checkType(inactive)
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart("inactive");
            }
            throw e;
        }
        this.title = title;
        this.urlHistory = urlHistory;
        this.icon = icon;
        this.lastUsed = lastUsed;
        this.inactive = inactive;
    }
    equals(other) {
        return (
            this.title == other.title &&
            this.urlHistory == other.urlHistory &&
            this.icon == other.icon &&
            this.lastUsed == other.lastUsed &&
            this.inactive == other.inactive
        )
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeRemoteTabRecord extends FfiConverterArrayBuffer {
    static read(dataStream) {
        return new RemoteTabRecord({
            title: FfiConverterString.read(dataStream),
            urlHistory: FfiConverterSequencestring.read(dataStream),
            icon: FfiConverterOptionalstring.read(dataStream),
            lastUsed: FfiConverterI64.read(dataStream),
            inactive: FfiConverterBool.read(dataStream),
        });
    }
    static write(dataStream, value) {
        FfiConverterString.write(dataStream, value.title);
        FfiConverterSequencestring.write(dataStream, value.urlHistory);
        FfiConverterOptionalstring.write(dataStream, value.icon);
        FfiConverterI64.write(dataStream, value.lastUsed);
        FfiConverterBool.write(dataStream, value.inactive);
    }

    static computeSize(value) {
        let totalSize = 0;
        totalSize += FfiConverterString.computeSize(value.title);
        totalSize += FfiConverterSequencestring.computeSize(value.urlHistory);
        totalSize += FfiConverterOptionalstring.computeSize(value.icon);
        totalSize += FfiConverterI64.computeSize(value.lastUsed);
        totalSize += FfiConverterBool.computeSize(value.inactive);
        return totalSize
    }

    static checkType(value) {
        super.checkType(value);
        if (!(value instanceof RemoteTabRecord)) {
            throw new UniFFITypeError(`Expected 'RemoteTabRecord', found '${typeof value}'`);
        }
        try {
            FfiConverterString.checkType(value.title);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".title");
            }
            throw e;
        }
        try {
            FfiConverterSequencestring.checkType(value.urlHistory);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".urlHistory");
            }
            throw e;
        }
        try {
            FfiConverterOptionalstring.checkType(value.icon);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".icon");
            }
            throw e;
        }
        try {
            FfiConverterI64.checkType(value.lastUsed);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".lastUsed");
            }
            throw e;
        }
        try {
            FfiConverterBool.checkType(value.inactive);
        } catch (e) {
            if (e instanceof UniFFITypeError) {
                e.addItemDescriptionPart(".inactive");
            }
            throw e;
        }
    }
}


export class RemoteCommand {}
RemoteCommand.CloseTab = class extends RemoteCommand{
    constructor(
        url
        ) {
            super();
            this.url = url;
        }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeRemoteCommand extends FfiConverterArrayBuffer {
    static read(dataStream) {
        switch (dataStream.readInt32()) {
            case 1:
                return new RemoteCommand.CloseTab(
                    FfiConverterString.read(dataStream)
                    );
            default:
                throw new UniFFITypeError("Unknown RemoteCommand variant");
        }
    }

    static write(dataStream, value) {
        if (value instanceof RemoteCommand.CloseTab) {
            dataStream.writeInt32(1);
            FfiConverterString.write(dataStream, value.url);
            return;
        }
        throw new UniFFITypeError("Unknown RemoteCommand variant");
    }

    static computeSize(value) {
        // Size of the Int indicating the variant
        let totalSize = 4;
        if (value instanceof RemoteCommand.CloseTab) {
            totalSize += FfiConverterString.computeSize(value.url);
            return totalSize;
        }
        throw new UniFFITypeError("Unknown RemoteCommand variant");
    }

    static checkType(value) {
      if (!(value instanceof RemoteCommand)) {
        throw new UniFFITypeError(`${value} is not a subclass instance of RemoteCommand`);
      }
    }
}





export class TabsApiError extends Error {}


export class SyncError extends TabsApiError {

    constructor(
        reason,
        ...params
    ) {
        const message = `reason: ${ reason }`;
        super(message, ...params);
        this.reason = reason;
    }
    toString() {
        return `SyncError: ${super.toString()}`
    }
}

export class SqlError extends TabsApiError {

    constructor(
        reason,
        ...params
    ) {
        const message = `reason: ${ reason }`;
        super(message, ...params);
        this.reason = reason;
    }
    toString() {
        return `SqlError: ${super.toString()}`
    }
}

export class UnexpectedTabsError extends TabsApiError {

    constructor(
        reason,
        ...params
    ) {
        const message = `reason: ${ reason }`;
        super(message, ...params);
        this.reason = reason;
    }
    toString() {
        return `UnexpectedTabsError: ${super.toString()}`
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeTabsApiError extends FfiConverterArrayBuffer {
    static read(dataStream) {
        switch (dataStream.readInt32()) {
            case 1:
                return new SyncError(
                    FfiConverterString.read(dataStream)
                    );
            case 2:
                return new SqlError(
                    FfiConverterString.read(dataStream)
                    );
            case 3:
                return new UnexpectedTabsError(
                    FfiConverterString.read(dataStream)
                    );
            default:
                throw new UniFFITypeError("Unknown TabsApiError variant");
        }
    }
    static computeSize(value) {
        // Size of the Int indicating the variant
        let totalSize = 4;
        if (value instanceof SyncError) {
            totalSize += FfiConverterString.computeSize(value.reason);
            return totalSize;
        }
        if (value instanceof SqlError) {
            totalSize += FfiConverterString.computeSize(value.reason);
            return totalSize;
        }
        if (value instanceof UnexpectedTabsError) {
            totalSize += FfiConverterString.computeSize(value.reason);
            return totalSize;
        }
        throw new UniFFITypeError("Unknown TabsApiError variant");
    }
    static write(dataStream, value) {
        if (value instanceof SyncError) {
            dataStream.writeInt32(1);
            FfiConverterString.write(dataStream, value.reason);
            return;
        }
        if (value instanceof SqlError) {
            dataStream.writeInt32(2);
            FfiConverterString.write(dataStream, value.reason);
            return;
        }
        if (value instanceof UnexpectedTabsError) {
            dataStream.writeInt32(3);
            FfiConverterString.write(dataStream, value.reason);
            return;
        }
        throw new UniFFITypeError("Unknown TabsApiError variant");
    }

    static errorClass = TabsApiError;
}

// Export the FFIConverter object to make external types work.
export class FfiConverterOptionalstring extends FfiConverterArrayBuffer {
    static checkType(value) {
        if (value !== undefined && value !== null) {
            FfiConverterString.checkType(value)
        }
    }

    static read(dataStream) {
        const code = dataStream.readUint8(0);
        switch (code) {
            case 0:
                return null
            case 1:
                return FfiConverterString.read(dataStream)
            default:
                throw UniFFIError(`Unexpected code: ${code}`);
        }
    }

    static write(dataStream, value) {
        if (value === null || value === undefined) {
            dataStream.writeUint8(0);
            return;
        }
        dataStream.writeUint8(1);
        FfiConverterString.write(dataStream, value)
    }

    static computeSize(value) {
        if (value === null || value === undefined) {
            return 1;
        }
        return 1 + FfiConverterString.computeSize(value)
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterOptionalTypeTimestamp extends FfiConverterArrayBuffer {
    static checkType(value) {
        if (value !== undefined && value !== null) {
            FfiConverterTypeTimestamp.checkType(value)
        }
    }

    static read(dataStream) {
        const code = dataStream.readUint8(0);
        switch (code) {
            case 0:
                return null
            case 1:
                return FfiConverterTypeTimestamp.read(dataStream)
            default:
                throw UniFFIError(`Unexpected code: ${code}`);
        }
    }

    static write(dataStream, value) {
        if (value === null || value === undefined) {
            dataStream.writeUint8(0);
            return;
        }
        dataStream.writeUint8(1);
        FfiConverterTypeTimestamp.write(dataStream, value)
    }

    static computeSize(value) {
        if (value === null || value === undefined) {
            return 1;
        }
        return 1 + FfiConverterTypeTimestamp.computeSize(value)
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterSequencestring extends FfiConverterArrayBuffer {
    static read(dataStream) {
        const len = dataStream.readInt32();
        const arr = [];
        for (let i = 0; i < len; i++) {
            arr.push(FfiConverterString.read(dataStream));
        }
        return arr;
    }

    static write(dataStream, value) {
        dataStream.writeInt32(value.length);
        value.forEach((innerValue) => {
            FfiConverterString.write(dataStream, innerValue);
        })
    }

    static computeSize(value) {
        // The size of the length
        let size = 4;
        for (const innerValue of value) {
            size += FfiConverterString.computeSize(innerValue);
        }
        return size;
    }

    static checkType(value) {
        if (!Array.isArray(value)) {
            throw new UniFFITypeError(`${value} is not an array`);
        }
        value.forEach((innerValue, idx) => {
            try {
                FfiConverterString.checkType(innerValue);
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart(`[${idx}]`);
                }
                throw e;
            }
        })
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterSequenceTypeClientRemoteTabs extends FfiConverterArrayBuffer {
    static read(dataStream) {
        const len = dataStream.readInt32();
        const arr = [];
        for (let i = 0; i < len; i++) {
            arr.push(FfiConverterTypeClientRemoteTabs.read(dataStream));
        }
        return arr;
    }

    static write(dataStream, value) {
        dataStream.writeInt32(value.length);
        value.forEach((innerValue) => {
            FfiConverterTypeClientRemoteTabs.write(dataStream, innerValue);
        })
    }

    static computeSize(value) {
        // The size of the length
        let size = 4;
        for (const innerValue of value) {
            size += FfiConverterTypeClientRemoteTabs.computeSize(innerValue);
        }
        return size;
    }

    static checkType(value) {
        if (!Array.isArray(value)) {
            throw new UniFFITypeError(`${value} is not an array`);
        }
        value.forEach((innerValue, idx) => {
            try {
                FfiConverterTypeClientRemoteTabs.checkType(innerValue);
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart(`[${idx}]`);
                }
                throw e;
            }
        })
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterSequenceTypePendingCommand extends FfiConverterArrayBuffer {
    static read(dataStream) {
        const len = dataStream.readInt32();
        const arr = [];
        for (let i = 0; i < len; i++) {
            arr.push(FfiConverterTypePendingCommand.read(dataStream));
        }
        return arr;
    }

    static write(dataStream, value) {
        dataStream.writeInt32(value.length);
        value.forEach((innerValue) => {
            FfiConverterTypePendingCommand.write(dataStream, innerValue);
        })
    }

    static computeSize(value) {
        // The size of the length
        let size = 4;
        for (const innerValue of value) {
            size += FfiConverterTypePendingCommand.computeSize(innerValue);
        }
        return size;
    }

    static checkType(value) {
        if (!Array.isArray(value)) {
            throw new UniFFITypeError(`${value} is not an array`);
        }
        value.forEach((innerValue, idx) => {
            try {
                FfiConverterTypePendingCommand.checkType(innerValue);
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart(`[${idx}]`);
                }
                throw e;
            }
        })
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterSequenceTypeRemoteTabRecord extends FfiConverterArrayBuffer {
    static read(dataStream) {
        const len = dataStream.readInt32();
        const arr = [];
        for (let i = 0; i < len; i++) {
            arr.push(FfiConverterTypeRemoteTabRecord.read(dataStream));
        }
        return arr;
    }

    static write(dataStream, value) {
        dataStream.writeInt32(value.length);
        value.forEach((innerValue) => {
            FfiConverterTypeRemoteTabRecord.write(dataStream, innerValue);
        })
    }

    static computeSize(value) {
        // The size of the length
        let size = 4;
        for (const innerValue of value) {
            size += FfiConverterTypeRemoteTabRecord.computeSize(innerValue);
        }
        return size;
    }

    static checkType(value) {
        if (!Array.isArray(value)) {
            throw new UniFFITypeError(`${value} is not an array`);
        }
        value.forEach((innerValue, idx) => {
            try {
                FfiConverterTypeRemoteTabRecord.checkType(innerValue);
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart(`[${idx}]`);
                }
                throw e;
            }
        })
    }
}

// Export the FFIConverter object to make external types work.
export class FfiConverterSequenceTypeTabsGuid extends FfiConverterArrayBuffer {
    static read(dataStream) {
        const len = dataStream.readInt32();
        const arr = [];
        for (let i = 0; i < len; i++) {
            arr.push(FfiConverterTypeTabsGuid.read(dataStream));
        }
        return arr;
    }

    static write(dataStream, value) {
        dataStream.writeInt32(value.length);
        value.forEach((innerValue) => {
            FfiConverterTypeTabsGuid.write(dataStream, innerValue);
        })
    }

    static computeSize(value) {
        // The size of the length
        let size = 4;
        for (const innerValue of value) {
            size += FfiConverterTypeTabsGuid.computeSize(innerValue);
        }
        return size;
    }

    static checkType(value) {
        if (!Array.isArray(value)) {
            throw new UniFFITypeError(`${value} is not an array`);
        }
        value.forEach((innerValue, idx) => {
            try {
                FfiConverterTypeTabsGuid.checkType(innerValue);
            } catch (e) {
                if (e instanceof UniFFITypeError) {
                    e.addItemDescriptionPart(`[${idx}]`);
                }
                throw e;
            }
        })
    }
}

import {
  FfiConverterTypeDeviceType,
  DeviceType,
} from "resource://gre/modules/RustSync15.sys.mjs";

// Export the FFIConverter object to make external types work.
export { FfiConverterTypeDeviceType, DeviceType };

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeTabsGuid extends FfiConverter {
    static lift(buf) {
        return FfiConverterString.lift(buf);    
    }
    
    static lower(buf) {
        return FfiConverterString.lower(buf);
    }
    
    static write(dataStream, value) {
        FfiConverterString.write(dataStream, value);
    } 
    
    static read(buf) {
        return FfiConverterString.read(buf);
    }
    
    static computeSize(value) {
        return FfiConverterString.computeSize(value);
    }
}
// TODO: We should also allow JS to customize the type eventually.

// Export the FFIConverter object to make external types work.
export class FfiConverterTypeTimestamp extends FfiConverter {
    static lift(buf) {
        return FfiConverterI64.lift(buf);    
    }
    
    static lower(buf) {
        return FfiConverterI64.lower(buf);
    }
    
    static write(dataStream, value) {
        FfiConverterI64.write(dataStream, value);
    } 
    
    static read(buf) {
        return FfiConverterI64.read(buf);
    }
    
    static computeSize(value) {
        return FfiConverterI64.computeSize(value);
    }
}
// TODO: We should also allow JS to customize the type eventually.




PK
!<u��p	p	modules/SandboxUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

export var SandboxUtils = {
  /**
   * Show a notification bar if user is running without unprivileged namespace
   *
   * @param {NotificationBox} aNotificationBox
   *        The target notification box where notification will be added
   */
  maybeWarnAboutMissingUserNamespaces:
    function SU_maybeWarnAboutMissingUserNamespaces(aNotificationBox) {
      if (AppConstants.platform !== "linux") {
        return;
      }

      // This would cover Flatpak, Snap or any "Packaged App" (e.g., Debian package)
      // Showing the notification on Flatpak would not be correct because of
      // existing Flatpak isolation (see Bug 1882881). And for Snap and
      // Debian packages it would be irrelevant as well.
      const isPackagedApp = Services.sysinfo.getPropertyAsBool("isPackagedApp");
      if (isPackagedApp) {
        return;
      }

      const kSandboxUserNamespacesPref =
        "security.sandbox.warn_unprivileged_namespaces";
      const kSandboxUserNamespacesPrefValue = Services.prefs.getBoolPref(
        kSandboxUserNamespacesPref
      );
      if (!kSandboxUserNamespacesPrefValue) {
        return;
      }

      const userNamespaces =
        Services.sysinfo.getPropertyAsBool("hasUserNamespaces");
      if (userNamespaces) {
        return;
      }

      const mozXulElement = aNotificationBox.stack.ownerGlobal.MozXULElement;
      mozXulElement.insertFTLIfNeeded("toolkit/updates/elevation.ftl");

      let buttons = [
        {
          supportPage: "install-firefox-linux",
          "l10n-id": "sandbox-unprivileged-namespaces-howtofix",
        },
        {
          "l10n-id": "sandbox-unprivileged-namespaces-dismiss-button",
          callback: () => {
            Services.prefs.setBoolPref(kSandboxUserNamespacesPref, false);
          },
        },
      ];

      // Now actually create the notification
      aNotificationBox.appendNotification(
        "sandbox-unprivileged-namespaces",
        {
          label: { "l10n-id": "sandbox-missing-unprivileged-namespaces" },
          priority: aNotificationBox.PRIORITY_WARNING_HIGH,
        },
        buttons
      );
    },
};
PK
!<ߕ�V5�5�modules/Schemas.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

var { DefaultMap, DefaultWeakMap } = ExtensionUtils;

/** @type {Lazy} */
const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs",
  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
  ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "contentPolicyService",
  "@mozilla.org/addons/content-policy;1",
  "nsIAddonContentPolicy"
);

ChromeUtils.defineLazyGetter(
  lazy,
  "StartupCache",
  () => lazy.ExtensionParent.StartupCache
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "treatWarningsAsErrors",
  "extensions.webextensions.warnings-as-errors",
  false
);

const KEY_CONTENT_SCHEMAS = "extensions-framework/schemas/content";
const KEY_PRIVILEGED_SCHEMAS = "extensions-framework/schemas/privileged";

const MIN_MANIFEST_VERSION = 2;
const MAX_MANIFEST_VERSION = 3;

const { DEBUG } = AppConstants;

const isParentProcess =
  Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT;

function readJSON(url) {
  return new Promise((resolve, reject) => {
    lazy.NetUtil.asyncFetch(
      { uri: url, loadUsingSystemPrincipal: true },
      (inputStream, status) => {
        if (!Components.isSuccessCode(status)) {
          // Convert status code to a string
          let e = Components.Exception("", status);
          reject(new Error(`Error while loading '${url}' (${e.name})`));
          return;
        }
        try {
          let text = lazy.NetUtil.readInputStreamToString(
            inputStream,
            inputStream.available()
          );

          // Chrome JSON files include a license comment that we need to
          // strip off for this to be valid JSON. As a hack, we just
          // look for the first '[' character, which signals the start
          // of the JSON content.
          let index = text.indexOf("[");
          text = text.slice(index);

          resolve(JSON.parse(text));
        } catch (e) {
          reject(e);
        }
      }
    );
  });
}

function stripDescriptions(json, stripThis = true) {
  if (Array.isArray(json)) {
    for (let i = 0; i < json.length; i++) {
      if (typeof json[i] === "object" && json[i] !== null) {
        json[i] = stripDescriptions(json[i]);
      }
    }
    return json;
  }

  let result = {};

  // Objects are handled much more efficiently, both in terms of memory and
  // CPU, if they have the same shape as other objects that serve the same
  // purpose. So, normalize the order of properties to increase the chances
  // that the majority of schema objects wind up in large shape groups.
  for (let key of Object.keys(json).sort()) {
    if (stripThis && key === "description" && typeof json[key] === "string") {
      continue;
    }

    if (typeof json[key] === "object" && json[key] !== null) {
      result[key] = stripDescriptions(json[key], key !== "properties");
    } else {
      result[key] = json[key];
    }
  }

  return result;
}

function blobbify(json) {
  // We don't actually use descriptions at runtime, and they make up about a
  // third of the size of our structured clone data, so strip them before
  // blobbifying.
  json = stripDescriptions(json);

  return new StructuredCloneHolder("Schemas/blobbify", null, json);
}

async function readJSONAndBlobbify(url) {
  let json = await readJSON(url);

  return blobbify(json);
}

/**
 * Defines a lazy getter for the given property on the given object. Any
 * security wrappers are waived on the object before the property is
 * defined, and the getter and setter methods are wrapped for the target
 * scope.
 *
 * The given getter function is guaranteed to be called only once, even
 * if the target scope retrieves the wrapped getter from the property
 * descriptor and calls it directly.
 *
 * @param {object} object
 *        The object on which to define the getter.
 * @param {string | symbol} prop
 *        The property name for which to define the getter.
 * @param {Function} getter
 *        The function to call in order to generate the final property
 *        value.
 */
function exportLazyGetter(object, prop, getter) {
  object = ChromeUtils.waiveXrays(object);

  let redefine = value => {
    if (value === undefined) {
      delete object[prop];
    } else {
      Object.defineProperty(object, prop, {
        enumerable: true,
        configurable: true,
        writable: true,
        value,
      });
    }

    getter = null;

    return value;
  };

  Object.defineProperty(object, prop, {
    enumerable: true,
    configurable: true,

    get: Cu.exportFunction(function () {
      return redefine(getter.call(this));
    }, object),

    set: Cu.exportFunction(value => {
      redefine(value);
    }, object),
  });
}

/**
 * Defines a lazily-instantiated property descriptor on the given
 * object. Any security wrappers are waived on the object before the
 * property is defined.
 *
 * The given getter function is guaranteed to be called only once, even
 * if the target scope retrieves the wrapped getter from the property
 * descriptor and calls it directly.
 *
 * @param {object} object
 *        The object on which to define the getter.
 * @param {string | symbol} prop
 *        The property name for which to define the getter.
 * @param {Function} getter
 *        The function to call in order to generate the final property
 *        descriptor object. This will be called, and the property
 *        descriptor installed on the object, the first time the
 *        property is written or read. The function may return
 *        undefined, which will cause the property to be deleted.
 */
function exportLazyProperty(object, prop, getter) {
  object = ChromeUtils.waiveXrays(object);

  let redefine = obj => {
    let desc = getter.call(obj);
    getter = null;

    delete object[prop];
    if (desc) {
      let defaults = {
        configurable: true,
        enumerable: true,
      };

      if (!desc.set && !desc.get) {
        defaults.writable = true;
      }

      Object.defineProperty(object, prop, Object.assign(defaults, desc));
    }
  };

  Object.defineProperty(object, prop, {
    enumerable: true,
    configurable: true,

    get: Cu.exportFunction(function () {
      redefine(this);
      return object[prop];
    }, object),

    set: Cu.exportFunction(function (value) {
      redefine(this);
      object[prop] = value;
    }, object),
  });
}

const POSTPROCESSORS = {
  convertImageDataToURL(imageData, context) {
    let document = context.cloneScope.document;
    let canvas = document.createElementNS(
      "http://www.w3.org/1999/xhtml",
      "canvas"
    );
    canvas.width = imageData.width;
    canvas.height = imageData.height;
    canvas.getContext("2d").putImageData(imageData, 0, 0);

    return canvas.toDataURL("image/png");
  },
  mutuallyExclusiveBlockingOrAsyncBlocking(value, context) {
    if (!Array.isArray(value)) {
      return value;
    }
    if (value.includes("blocking") && value.includes("asyncBlocking")) {
      throw new context.cloneScope.Error(
        "'blocking' and 'asyncBlocking' are mutually exclusive"
      );
    }
    return value;
  },
  webRequestBlockingPermissionRequired(string, context) {
    if (string === "blocking" && !context.hasPermission("webRequestBlocking")) {
      throw new context.cloneScope.Error(
        "Using webRequest.addListener with the " +
          "blocking option requires the 'webRequestBlocking' permission."
      );
    }

    return string;
  },
  webRequestBlockingOrAuthProviderPermissionRequired(string, context) {
    if (
      string === "blocking" &&
      !(
        context.hasPermission("webRequestBlocking") ||
        context.hasPermission("webRequestAuthProvider")
      )
    ) {
      throw new context.cloneScope.Error(
        "Using webRequest.onAuthRequired.addListener with the " +
          "blocking option requires either the 'webRequestBlocking' " +
          "or 'webRequestAuthProvider' permission."
      );
    }

    return string;
  },
  requireBackgroundServiceWorkerEnabled(value, context) {
    if (WebExtensionPolicy.backgroundServiceWorkerEnabled) {
      return value;
    }

    // Add an error to the manifest validations and throw the
    // same error.
    const msg = "background.service_worker is currently disabled";
    context.logError(context.makeError(msg));
    throw new Error(msg);
  },

  manifestVersionCheck(value, context) {
    if (
      value == 2 ||
      (value == 3 &&
        Services.prefs.getBoolPref("extensions.manifestV3.enabled", false))
    ) {
      return value;
    }
    const msg = `Unsupported manifest version: ${value}`;
    context.logError(context.makeError(msg));
    throw new Error(msg);
  },

  webAccessibleMatching(value, context) {
    // Ensure each object has at least one of matches or extension_ids array.
    for (let obj of value) {
      if (!obj.matches && !obj.extension_ids) {
        const msg = `web_accessible_resources requires one of "matches" or "extension_ids"`;
        context.logError(context.makeError(msg));
        throw new Error(msg);
      }
    }
    return value;
  },

  incognitoSplitUnsupportedAndFallback(value, context) {
    if (value === "split") {
      // incognito:split has not been implemented (bug 1380812). There are two
      // alternatives: "spanning" and "not_allowed".
      //
      // "incognito":"split" is required by Chrome when extensions want to load
      // any extension page in a tab in Chrome. In Firefox that is not required,
      // so extensions could replace "split" with "spanning".
      // Another (poorly documented) effect of "incognito":"split" is separation
      // of some state between some extension APIs. Because this can in theory
      // result in unwanted mixing of state between private and non-private
      // browsing, we fall back to "not_allowed", which prevents the user from
      // enabling the extension in private browsing windows.
      value = "not_allowed";
      context.logWarning(
        `incognito "split" is unsupported. Falling back to incognito "${value}".`
      );
    }
    return value;
  },
};

// Parses a regular expression, with support for the Python extended
// syntax that allows setting flags by including the string (?im)
function parsePattern(pattern) {
  let flags = "";
  let match = /^\(\?([im]*)\)(.*)/.exec(pattern);
  if (match) {
    [, flags, pattern] = match;
  }
  return new RegExp(pattern, flags);
}

function getValueBaseType(value) {
  let type = typeof value;
  switch (type) {
    case "object":
      if (value === null) {
        return "null";
      }
      if (Array.isArray(value)) {
        return "array";
      }
      break;

    case "number":
      if (value % 1 === 0) {
        return "integer";
      }
  }
  return type;
}

// Methods of Context that are used by Schemas.normalize. These methods can be
// overridden at the construction of Context.
const CONTEXT_FOR_VALIDATION = ["checkLoadURL", "hasPermission", "logError"];

// Methods of Context that are used by Schemas.inject.
// Callers of Schemas.inject should implement all of these methods.
const CONTEXT_FOR_INJECTION = [
  ...CONTEXT_FOR_VALIDATION,
  "getImplementation",
  "isPermissionRevokable",
  "shouldInject",
];

// If the message is a function, call it and return the result.
// Otherwise, assume it's a string.
function forceString(msg) {
  if (typeof msg === "function") {
    return msg();
  }
  return msg;
}

/**
 * A context for schema validation and error reporting. This class is only used
 * internally within Schemas.
 */
class Context {
  /**
   * @param {object} params Provides the implementation of this class.
   * @param {Array<string>} overridableMethods
   */
  constructor(params, overridableMethods = CONTEXT_FOR_VALIDATION) {
    this.params = params;

    if (typeof params.manifestVersion !== "number") {
      throw new Error(
        `Unexpected params.manifestVersion value: ${params.manifestVersion}`
      );
    }

    this.path = [];
    this.preprocessors = {
      localize(value) {
        return value;
      },
      ...params.preprocessors,
    };

    this.postprocessors = POSTPROCESSORS;
    this.isChromeCompat = params.isChromeCompat ?? false;
    this.manifestVersion = params.manifestVersion;

    this.currentChoices = new Set();
    this.choicePathIndex = 0;

    for (let method of overridableMethods) {
      if (method in params) {
        this[method] = params[method].bind(params);
      }
    }
  }

  get choicePath() {
    let path = this.path.slice(this.choicePathIndex);
    return path.join(".");
  }

  get cloneScope() {
    return this.params.cloneScope || undefined;
  }

  get url() {
    return this.params.url;
  }

  get ignoreUnrecognizedProperties() {
    return !!this.params.ignoreUnrecognizedProperties;
  }

  get principal() {
    return (
      this.params.principal ||
      Services.scriptSecurityManager.createNullPrincipal({})
    );
  }

  /**
   * Checks whether `url` may be loaded by the extension in this context.
   *
   * @param {string} url The URL that the extension wished to load.
   * @returns {boolean} Whether the context may load `url`.
   */
  checkLoadURL(url) {
    let ssm = Services.scriptSecurityManager;
    try {
      ssm.checkLoadURIWithPrincipal(
        this.principal,
        Services.io.newURI(url),
        ssm.DISALLOW_INHERIT_PRINCIPAL
      );
    } catch (e) {
      return false;
    }
    return true;
  }

  /**
   * Checks whether this context has the given permission.
   *
   * @param {string} _permission
   *        The name of the permission to check.
   *
   * @returns {boolean} True if the context has the given permission.
   */
  hasPermission(_permission) {
    return false;
  }

  /**
   * Checks whether the given permission can be dynamically revoked or
   * granted.
   *
   * @param {string} _permission
   *        The name of the permission to check.
   *
   * @returns {boolean} True if the given permission is revokable.
   */
  isPermissionRevokable(_permission) {
    return false;
  }

  /**
   * Returns an error result object with the given message, for return
   * by Type normalization functions.
   *
   * If the context has a `currentTarget` value, this is prepended to
   * the message to indicate the location of the error.
   *
   * @param {string | Function} errorMessage
   *        The error message which will be displayed when this is the
   *        only possible matching schema. If a function is passed, it
   *        will be evaluated when the error string is first needed, and
   *        must return a string.
   * @param {string | Function} choicesMessage
   *        The message describing the valid what constitutes a valid
   *        value for this schema, which will be displayed when multiple
   *        schema choices are available and none match.
   *
   *        A caller may pass `null` to prevent a choice from being
   *        added, but this should *only* be done from code processing a
   *        choices type.
   * @param {boolean} [warning = false]
   *        If true, make message prefixed `Warning`. If false, make message
   *        prefixed `Error`
   * @returns {object}
   */
  error(errorMessage, choicesMessage = undefined, warning = false) {
    if (choicesMessage !== null) {
      let { choicePath } = this;
      if (choicePath) {
        choicesMessage = `.${choicePath} must ${choicesMessage}`;
      }

      this.currentChoices.add(choicesMessage);
    }

    if (this.currentTarget) {
      let { currentTarget } = this;
      return {
        error: () =>
          `${
            warning ? "Warning" : "Error"
          } processing ${currentTarget}: ${forceString(errorMessage)}`,
      };
    }
    return { error: errorMessage };
  }

  /**
   * Creates an `Error` object belonging to the current unprivileged
   * scope. If there is no unprivileged scope associated with this
   * context, the message is returned as a string.
   *
   * If the context has a `currentTarget` value, this is prepended to
   * the message, in the same way as for the `error` method.
   *
   * @param {string} message
   * @param {object} [options]
   * @param {boolean} [options.warning = false]
   * @returns {Error}
   */
  makeError(message, { warning = false } = {}) {
    let error = forceString(this.error(message, null, warning).error);
    if (this.cloneScope) {
      return new this.cloneScope.Error(error);
    }
    return error;
  }

  /**
   * Logs the given error to the console. May be overridden to enable
   * custom logging.
   *
   * @param {Error|string} error
   */
  logError(error) {
    if (this.cloneScope) {
      Cu.reportError(
        // Error objects logged using Cu.reportError are not associated
        // to the related innerWindowID. This results in a leaked docshell
        // since consoleService cannot release the error object when the
        // extension global is destroyed.
        typeof error == "string" ? error : String(error),
        // Report the error with the appropriate stack trace when the
        // is related to an actual extension global (instead of being
        // related to a manifest validation).
        this.principal && ChromeUtils.getCallerLocation(this.principal)
      );
    } else {
      Cu.reportError(error);
    }
  }

  /**
   * Logs a warning. An error might be thrown when we treat warnings as errors.
   *
   * @param {string} warningMessage
   */
  logWarning(warningMessage) {
    let error = this.makeError(warningMessage, { warning: true });
    this.logError(error);

    if (lazy.treatWarningsAsErrors) {
      // This pref is false by default, and true by default in tests to
      // discourage the use of deprecated APIs in our unit tests.
      // If a warning is an expected part of a test, temporarily set the pref
      // to false, e.g. with the ExtensionTestUtils.failOnSchemaWarnings helper.
      Services.console.logStringMessage(
        "Treating warning as error because the preference " +
          "extensions.webextensions.warnings-as-errors is set to true"
      );
      if (typeof error === "string") {
        error = new Error(error);
      }
      throw error;
    }
  }

  /**
   * Returns the name of the value currently being normalized. For a
   * nested object, this is usually approximately equivalent to the
   * JavaScript property accessor for that property. Given:
   *
   *   { foo: { bar: [{ baz: x }] } }
   *
   * When processing the value for `x`, the currentTarget is
   * 'foo.bar.0.baz'
   */
  get currentTarget() {
    return this.path.join(".");
  }

  /**
   * Executes the given callback, and returns an array of choice strings
   * passed to {@see #error} during its execution.
   *
   * @param {Function} callback
   * @returns {object}
   *          An object with a `result` property containing the return
   *          value of the callback, and a `choice` property containing
   *          an array of choices.
   */
  withChoices(callback) {
    let { currentChoices, choicePathIndex } = this;

    let choices = new Set();
    this.currentChoices = choices;
    this.choicePathIndex = this.path.length;

    try {
      let result = callback();

      return { result, choices };
    } finally {
      this.currentChoices = currentChoices;
      this.choicePathIndex = choicePathIndex;

      if (choices.size == 1) {
        for (let choice of choices) {
          currentChoices.add(choice);
        }
      } else if (choices.size) {
        this.error(null, () => {
          let array = Array.from(choices, forceString);
          let n = array.length - 1;
          array[n] = `or ${array[n]}`;

          return `must either [${array.join(", ")}]`;
        });
      }
    }
  }

  /**
   * Appends the given component to the `currentTarget` path to indicate
   * that it is being processed, calls the given callback function, and
   * then restores the original path.
   *
   * This is used to identify the path of the property being processed
   * when reporting type errors.
   *
   * @param {string} component
   * @param {Function} callback
   * @returns {*}
   */
  withPath(component, callback) {
    this.path.push(component);
    try {
      return callback();
    } finally {
      this.path.pop();
    }
  }

  matchManifestVersion(entry) {
    let { manifestVersion } = this;
    return (
      manifestVersion >= entry.min_manifest_version &&
      manifestVersion <= entry.max_manifest_version
    );
  }
}

/**
 * Represents a schema entry to be injected into an object. Handles the
 * injection, revocation, and permissions of said entry.
 *
 * @param {InjectionContext} context
 *        The injection context for the entry.
 * @param {Entry} entry
 *        The entry to inject.
 * @param {object} parentObject
 *        The object into which to inject this entry.
 * @param {string} name
 *        The property name at which to inject this entry.
 * @param {Array<string>} path
 *        The full path from the root entry to this entry.
 * @param {Entry} parentEntry
 *        The parent entry for the injected entry.
 */
class InjectionEntry {
  constructor(context, entry, parentObj, name, path, parentEntry) {
    this.context = context;
    this.entry = entry;
    this.parentObj = parentObj;
    this.name = name;
    this.path = path;
    this.parentEntry = parentEntry;

    this.injected = null;
    this.lazyInjected = null;
  }

  /**
   * @property {Array<string>} allowedContexts
   *        The list of allowed contexts into which the entry may be
   *        injected.
   */
  get allowedContexts() {
    let { allowedContexts } = this.entry;
    if (allowedContexts.length) {
      return allowedContexts;
    }
    return this.parentEntry.defaultContexts;
  }

  /**
   * @property {boolean} isRevokable
   *        Returns true if this entry may be dynamically injected or
   *        revoked based on its permissions.
   */
  get isRevokable() {
    return (
      this.entry.permissions &&
      this.entry.permissions.some(perm =>
        this.context.isPermissionRevokable(perm)
      )
    );
  }

  /**
   * @property {boolean} hasPermission
   *        Returns true if the injection context currently has the
   *        appropriate permissions to access this entry.
   */
  get hasPermission() {
    return (
      !this.entry.permissions ||
      this.entry.permissions.some(perm => this.context.hasPermission(perm))
    );
  }

  /**
   * @property {boolean} shouldInject
   *        Returns true if this entry should be injected in the given
   *        context, without respect to permissions.
   */
  get shouldInject() {
    return (
      this.context.matchManifestVersion(this.entry) &&
      this.context.shouldInject(
        this.path.join("."),
        this.name,
        this.allowedContexts
      )
    );
  }

  /**
   * Revokes this entry, removing its property from its parent object,
   * and invalidating its wrappers.
   */
  revoke() {
    if (this.lazyInjected) {
      this.lazyInjected = false;
    } else if (this.injected) {
      if (this.injected.revoke) {
        this.injected.revoke();
      }

      try {
        let unwrapped = ChromeUtils.waiveXrays(this.parentObj);
        delete unwrapped[this.name];
      } catch (e) {
        Cu.reportError(e);
      }

      let { value } = this.injected.descriptor;
      if (value) {
        this.context.revokeChildren(value);
      }

      this.injected = null;
    }
  }

  /**
   * Returns a property descriptor object for this entry, if it should
   * be injected, or undefined if it should not.
   *
   * @returns {object?}
   *        A property descriptor object, or undefined if the property
   *        should be removed.
   */
  getDescriptor() {
    this.lazyInjected = false;

    if (this.injected) {
      let path = [...this.path, this.name];
      throw new Error(
        `Attempting to re-inject already injected entry: ${path.join(".")}`
      );
    }

    if (!this.shouldInject) {
      return;
    }

    if (this.isRevokable) {
      this.context.pendingEntries.add(this);
    }

    if (!this.hasPermission) {
      return;
    }

    this.injected = this.entry.getDescriptor(this.path, this.context);
    if (!this.injected) {
      return undefined;
    }

    return this.injected.descriptor;
  }

  /**
   * Injects a lazy property descriptor into the parent object which
   * checks permissions and eligibility for injection the first time it
   * is accessed.
   */
  lazyInject() {
    if (this.lazyInjected || this.injected) {
      let path = [...this.path, this.name];
      throw new Error(
        `Attempting to re-lazy-inject already injected entry: ${path.join(".")}`
      );
    }

    this.lazyInjected = true;
    exportLazyProperty(this.parentObj, this.name, () => {
      if (this.lazyInjected) {
        return this.getDescriptor();
      }
    });
  }

  /**
   * Injects or revokes this entry if its current state does not match
   * the context's current permissions.
   */
  permissionsChanged() {
    if (this.injected) {
      this.maybeRevoke();
    } else {
      this.maybeInject();
    }
  }

  maybeInject() {
    if (!this.injected && !this.lazyInjected) {
      this.lazyInject();
    }
  }

  maybeRevoke() {
    if (this.injected && !this.hasPermission) {
      this.revoke();
    }
  }
}

/**
 * Holds methods that run the actual implementation of the extension APIs. These
 * methods are only called if the extension API invocation matches the signature
 * as defined in the schema. Otherwise an error is reported to the context.
 */
class InjectionContext extends Context {
  constructor(params, schemaRoot) {
    super(params, CONTEXT_FOR_INJECTION);

    this.schemaRoot = schemaRoot;

    this.pendingEntries = new Set();
    this.children = new DefaultWeakMap(() => new Map());

    this.injectedRoots = new Set();

    if (params.setPermissionsChangedCallback) {
      params.setPermissionsChangedCallback(this.permissionsChanged.bind(this));
    }
  }

  /**
   * Check whether the API should be injected.
   *
   * @abstract
   * @param {string} _namespace The namespace of the API. This may contain dots,
   *     e.g. in the case of "devtools.inspectedWindow".
   * @param {string?} _name The name of the property in the namespace.
   *     `null` if we are checking whether the namespace should be injected.
   * @param {Array<string>} _allowedContexts A list of additional contexts in
   *      which this API should be available. May include any of:
   *         "main" - The main chrome browser process.
   *         "addon" - An addon process.
   *         "content" - A content process.
   * @returns {boolean} Whether the API should be injected.
   */
  shouldInject(_namespace, _name, _allowedContexts) {
    throw new Error("Not implemented");
  }

  /**
   * Generate the implementation for `namespace`.`name`.
   *
   * @abstract
   * @param {string} _namespace The full path to the namespace of the API, minus
   *     the name of the method or property. E.g. "storage.local".
   * @param {string} _name The name of the method, property or event.
   * @returns {import("ExtensionCommon.sys.mjs").SchemaAPIInterface}
   *          The implementation of the API.
   */
  getImplementation(_namespace, _name) {
    throw new Error("Not implemented");
  }

  /**
   * Updates all injection entries which may need to be updated after a
   * permission change, revoking or re-injecting them as necessary.
   */
  permissionsChanged() {
    for (let entry of this.pendingEntries) {
      try {
        entry.permissionsChanged();
      } catch (e) {
        Cu.reportError(e);
      }
    }
  }

  /**
   * Recursively revokes all child injection entries of the given
   * object.
   *
   * @param {object} object
   *        The object for which to invoke children.
   */
  revokeChildren(object) {
    if (!this.children.has(object)) {
      return;
    }

    let children = this.children.get(object);
    for (let [name, entry] of children.entries()) {
      try {
        entry.revoke();
      } catch (e) {
        Cu.reportError(e);
      }
      children.delete(name);

      // When we revoke children for an object, we consider that object
      // dead. If the entry is ever reified again, a new object is
      // created, with new child entries.
      this.pendingEntries.delete(entry);
    }
    this.children.delete(object);
  }

  _getInjectionEntry(entry, dest, name, path, parentEntry) {
    let injection = new InjectionEntry(
      this,
      entry,
      dest,
      name,
      path,
      parentEntry
    );

    this.children.get(dest).set(name, injection);

    return injection;
  }

  /**
   * Returns the property descriptor for the given entry.
   *
   * @param {Entry|Namespace} entry
   *        The entry instance to return a descriptor for.
   * @param {object} dest
   *        The object into which this entry is being injected.
   * @param {string} name
   *        The property name on the destination object where the entry
   *        will be injected.
   * @param {Array<string>} path
   *        The full path from the root injection object to this entry.
   * @param {Partial<Entry>} parentEntry
   *        The parent entry for this entry.
   *
   * @returns {object?}
   *        A property descriptor object, or null if the entry should
   *        not be injected.
   */
  getDescriptor(entry, dest, name, path, parentEntry) {
    let injection = this._getInjectionEntry(
      entry,
      dest,
      name,
      path,
      parentEntry
    );

    return injection.getDescriptor();
  }

  /**
   * Lazily injects the given entry into the given object.
   *
   * @param {Entry} entry
   *        The entry instance to lazily inject.
   * @param {object} dest
   *        The object into which to inject this entry.
   * @param {string} name
   *        The property name at which to inject the entry.
   * @param {Array<string>} path
   *        The full path from the root injection object to this entry.
   * @param {Entry} parentEntry
   *        The parent entry for this entry.
   */
  injectInto(entry, dest, name, path, parentEntry) {
    let injection = this._getInjectionEntry(
      entry,
      dest,
      name,
      path,
      parentEntry
    );

    injection.lazyInject();
  }
}

/**
 * The methods in this singleton represent the "format" specifier for
 * JSON Schema string types.
 *
 * Each method either returns a normalized version of the original
 * value, or throws an error if the value is not valid for the given
 * format.
 */
const FORMATS = {
  hostname(string) {
    // TODO bug 1797376: Despite the name, this format is NOT a "hostname",
    // but hostname + port and may fail with IPv6. Use canonicalDomain instead.
    let valid = true;

    try {
      valid = new URL(`http://${string}`).host === string;
    } catch (e) {
      valid = false;
    }

    if (!valid) {
      throw new Error(`Invalid hostname ${string}`);
    }

    return string;
  },

  canonicalDomain(string) {
    let valid;

    try {
      valid = new URL(`http://${string}`).hostname === string;
    } catch (e) {
      valid = false;
    }

    if (!valid) {
      // Require the input to be a canonical domain.
      // Rejects obvious non-domains such as URLs,
      // but also catches non-IDN (punycode) domains.
      throw new Error(`Invalid domain ${string}`);
    }

    return string;
  },

  url(string, context) {
    let url = new URL(string).href;

    if (!context.checkLoadURL(url)) {
      throw new Error(`Access denied for URL ${url}`);
    }
    return url;
  },

  origin(string, context) {
    let url;
    try {
      url = new URL(string);
    } catch (e) {
      throw new Error(`Invalid origin: ${string}`);
    }
    if (!/^https?:/.test(url.protocol)) {
      throw new Error(`Invalid origin must be http or https for URL ${string}`);
    }
    // url.origin is punycode so a direct check against string wont work.
    // url.href appends a slash even if not in the original string, we we
    // additionally check that string does not end in slash.
    if (string.endsWith("/") || url.href != new URL(url.origin).href) {
      throw new Error(
        `Invalid origin for URL ${string}, replace with origin ${url.origin}`
      );
    }
    if (!context.checkLoadURL(url.origin)) {
      throw new Error(`Access denied for URL ${url}`);
    }
    return url.origin;
  },

  relativeUrl(string, context) {
    if (!context.url) {
      // If there's no context URL, return relative URLs unresolved, and
      // skip security checks for them.
      try {
        new URL(string);
      } catch (e) {
        return string;
      }
    }

    let url = new URL(string, context.url).href;

    if (!context.checkLoadURL(url)) {
      throw new Error(`Access denied for URL ${url}`);
    }
    return url;
  },

  strictRelativeUrl(string, context) {
    void FORMATS.unresolvedRelativeUrl(string);
    return FORMATS.relativeUrl(string, context);
  },

  unresolvedRelativeUrl(string) {
    if (!string.startsWith("//")) {
      try {
        new URL(string);
      } catch (e) {
        return string;
      }
    }

    throw new SyntaxError(
      `String ${JSON.stringify(string)} must be a relative URL`
    );
  },

  homepageUrl(string, context) {
    // Pipes are used for separating homepages, but we only allow extensions to
    // set a single homepage. Encoding any pipes makes it one URL.
    return FORMATS.relativeUrl(
      string.replace(new RegExp("\\|", "g"), "%7C"),
      context
    );
  },

  imageDataOrStrictRelativeUrl(string, context) {
    // Do not accept a string which resolves as an absolute URL, or any
    // protocol-relative URL, except PNG or JPG data URLs
    if (
      !string.startsWith("data:image/png;base64,") &&
      !string.startsWith("data:image/jpeg;base64,")
    ) {
      try {
        return FORMATS.strictRelativeUrl(string, context);
      } catch (e) {
        throw new SyntaxError(
          `String ${JSON.stringify(
            string
          )} must be a relative or PNG or JPG data:image URL`
        );
      }
    }
    return string;
  },

  contentSecurityPolicy(string, context) {
    // Manifest V3 extension_pages allows WASM.  When sandbox is
    // implemented, or any other V3 or later directive, the flags
    // logic will need to be updated.
    let flags =
      context.manifestVersion < 3
        ? Ci.nsIAddonContentPolicy.CSP_ALLOW_ANY
        : Ci.nsIAddonContentPolicy.CSP_ALLOW_WASM;
    let error = lazy.contentPolicyService.validateAddonCSP(string, flags);
    if (error != null) {
      // The CSP validation error is not reported as part of the "choices" error message,
      // we log the CSP validation error explicitly here to make it easier for the addon developers
      // to see and fix the extension CSP.
      context.logError(`Error processing ${context.currentTarget}: ${error}`);
      return null;
    }
    return string;
  },

  date(string) {
    // A valid ISO 8601 timestamp.
    const PATTERN =
      /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{3})?(Z|([-+]\d{2}:?\d{2})))?$/;
    if (!PATTERN.test(string)) {
      throw new Error(`Invalid date string ${string}`);
    }
    // Our pattern just checks the format, we could still have invalid
    // values (e.g., month=99 or month=02 and day=31).  Let the Date
    // constructor do the dirty work of validating.
    if (isNaN(Date.parse(string))) {
      throw new Error(`Invalid date string ${string}`);
    }
    return string;
  },

  manifestShortcutKey(string) {
    if (lazy.ShortcutUtils.validate(string) == lazy.ShortcutUtils.IS_VALID) {
      return string;
    }
    let errorMessage =
      `Value "${string}" must consist of ` +
      `either a combination of one or two modifiers, including ` +
      `a mandatory primary modifier and a key, separated by '+', ` +
      `or a media key. For details see: ` +
      `https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/commands#Key_combinations`;
    throw new Error(errorMessage);
  },

  manifestShortcutKeyOrEmpty(string) {
    return string === "" ? "" : FORMATS.manifestShortcutKey(string);
  },

  versionString(string, context) {
    const parts = string.split(".");

    if (
      // We accept up to 4 numbers.
      parts.length > 4 ||
      // Non-zero values cannot start with 0 and we allow numbers up to 9 digits.
      parts.some(part => !/^(0|[1-9][0-9]{0,8})$/.test(part))
    ) {
      context.logWarning(
        `version must be a version string consisting of at most 4 integers ` +
          `of at most 9 digits without leading zeros, and separated with dots`
      );
    }

    // The idea is to only emit a warning when the version string does not
    // match the simple format we want to encourage developers to use. Given
    // the version is required, we always accept the value as is.
    return string;
  },
};

// Schema files contain namespaces, and each namespace contains types,
// properties, functions, and events. An Entry is a base class for
// types, properties, functions, and events.
class Entry {
  /** @type {Entry} */
  fallbackEntry;

  constructor(schema = {}) {
    /**
     * If set to any value which evaluates as true, this entry is
     * deprecated, and any access to it will result in a deprecation
     * warning being logged to the browser console.
     *
     * If the value is a string, it will be appended to the deprecation
     * message. If it contains the substring "${value}", it will be
     * replaced with a string representation of the value being
     * processed.
     *
     * If the value is any other truthy value, a generic deprecation
     * message will be emitted.
     */
    this.deprecated = false;
    if ("deprecated" in schema) {
      this.deprecated = schema.deprecated;
    }

    /**
     * @property {string} [preprocessor]
     * If set to a string value, and a preprocessor of the same is
     * defined in the validation context, it will be applied to this
     * value prior to any normalization.
     */
    this.preprocessor = schema.preprocess || null;

    /**
     * @property {string} [postprocessor]
     * If set to a string value, and a postprocessor of the same is
     * defined in the validation context, it will be applied to this
     * value after any normalization.
     */
    this.postprocessor = schema.postprocess || null;

    /**
     * @property {Array<string>} allowedContexts A list of allowed contexts
     * to consider before generating the API.
     * These are not parsed by the schema, but passed to `shouldInject`.
     */
    this.allowedContexts = schema.allowedContexts || [];

    this.min_manifest_version =
      schema.min_manifest_version ?? MIN_MANIFEST_VERSION;
    this.max_manifest_version =
      schema.max_manifest_version ?? MAX_MANIFEST_VERSION;
  }

  /**
   * Preprocess the given value with the preprocessor declared in
   * `preprocessor`.
   *
   * @param {*} value
   * @param {Context} context
   * @returns {*}
   */
  preprocess(value, context) {
    if (this.preprocessor) {
      return context.preprocessors[this.preprocessor](value, context);
    }
    return value;
  }

  /**
   * Postprocess the given result with the postprocessor declared in
   * `postprocessor`.
   *
   * @param {object} result
   * @param {Context} context
   * @returns {object}
   */
  postprocess(result, context) {
    if (result.error || !this.postprocessor) {
      return result;
    }

    let value = context.postprocessors[this.postprocessor](
      result.value,
      context
    );
    return { value };
  }

  /**
   * Logs a deprecation warning for this entry, based on the value of
   * its `deprecated` property.
   *
   * @param {Context} context
   * @param {any} [value]
   */
  logDeprecation(context, value = null) {
    let message = "This property is deprecated";
    if (typeof this.deprecated == "string") {
      message = this.deprecated;
      if (message.includes("${value}")) {
        try {
          value = JSON.stringify(value);
        } catch (e) {
          value = String(value);
        }
        message = message.replace(/\$\{value\}/g, () => value);
      }
    }

    context.logWarning(message);
  }

  /**
   * Checks whether the entry is deprecated and, if so, logs a
   * deprecation message.
   *
   * @param {Context} context
   * @param {any} [value]
   */
  checkDeprecated(context, value = null) {
    if (this.deprecated) {
      this.logDeprecation(context, value);
    }
  }

  /**
   * Returns an object containing property descriptor for use when
   * injecting this entry into an API object.
   *
   * @param {Array<string>} _path The API path, e.g. `["storage", "local"]`.
   * @param {InjectionContext} _context
   *
   * @returns {object?}
   *        An object containing a `descriptor` property, specifying the
   *        entry's property descriptor, and an optional `revoke`
   *        method, to be called when the entry is being revoked.
   */
  getDescriptor(_path, _context) {
    return undefined;
  }
}

// Corresponds either to a type declared in the "types" section of the
// schema or else to any type object used throughout the schema.
class Type extends Entry {
  /**
   * @property {Array<string>} EXTRA_PROPERTIES
   *        An array of extra properties which may be present for
   *        schemas of this type.
   */
  static get EXTRA_PROPERTIES() {
    return [
      "description",
      "deprecated",
      "preprocess",
      "postprocess",
      "privileged",
      "allowedContexts",
      "min_manifest_version",
      "max_manifest_version",
    ];
  }

  /**
   * Parses the given schema object and returns an instance of this
   * class which corresponds to its properties.
   *
   * @param {SchemaRoot} root
   *        The root schema for this type.
   * @param {object} schema
   *        A JSON schema object which corresponds to a definition of
   *        this type.
   * @param {Array<string>} path
   *        The path to this schema object from the root schema,
   *        corresponding to the property names and array indices
   *        traversed during parsing in order to arrive at this schema
   *        object.
   * @param {Array<string>} [extraProperties]
   *        An array of extra property names which are valid for this
   *        schema in the current context.
   * @returns {Type}
   *        An instance of this type which corresponds to the given
   *        schema object.
   * @static
   */
  static parseSchema(root, schema, path, extraProperties = []) {
    this.checkSchemaProperties(schema, path, extraProperties);

    return new this(schema);
  }

  /**
   * Checks that all of the properties present in the given schema
   * object are valid properties for this type, and throws if invalid.
   *
   * @param {object} schema
   *        A JSON schema object.
   * @param {Array<string>} path
   *        The path to this schema object from the root schema,
   *        corresponding to the property names and array indices
   *        traversed during parsing in order to arrive at this schema
   *        object.
   * @param {Iterable<string>} [extra]
   *        An array of extra property names which are valid for this
   *        schema in the current context.
   * @throws {Error}
   *        An error describing the first invalid property found in the
   *        schema object.
   */
  static checkSchemaProperties(schema, path, extra = []) {
    if (DEBUG) {
      let allowedSet = new Set([...this.EXTRA_PROPERTIES, ...extra]);

      for (let prop of Object.keys(schema)) {
        if (!allowedSet.has(prop)) {
          throw new Error(
            `Internal error: Namespace ${path.join(".")} has ` +
              `invalid type property "${prop}" ` +
              `in type "${schema.id || JSON.stringify(schema)}"`
          );
        }
      }
    }
  }

  /**
   * Takes a value, checks that it has the correct type, and returns a
   * "normalized" version of the value. The normalized version will
   * include "nulls" in place of omitted optional properties. The
   * result of this function is either {error: "Some type error"} or
   * {value: <normalized-value>}.
   */
  normalize(value, context) {
    return context.error("invalid type");
  }

  /**
   * Unlike normalize, this function does a shallow check to see if
   * |baseType| (one of the possible getValueBaseType results) is
   * valid for this type. It returns true or false. It's used to fill
   * in optional arguments to functions before actually type checking
   *
   * @param {string} _baseType
   */
  checkBaseType(_baseType) {
    return false;
  }

  /**
   * Helper method that simply relies on checkBaseType to implement
   * normalize. Subclasses can choose to use it or not.
   */
  normalizeBase(type, value, context) {
    if (this.checkBaseType(getValueBaseType(value))) {
      this.checkDeprecated(context, value);
      return { value: this.preprocess(value, context) };
    }

    let choice;
    if ("aeiou".includes(type[0])) {
      choice = `be an ${type} value`;
    } else {
      choice = `be a ${type} value`;
    }

    return context.error(
      () => `Expected ${type} instead of ${JSON.stringify(value)}`,
      choice
    );
  }
}

// Type that allows any value.
class AnyType extends Type {
  normalize(value, context) {
    this.checkDeprecated(context, value);
    return this.postprocess({ value }, context);
  }

  checkBaseType() {
    return true;
  }
}

// An untagged union type.
class ChoiceType extends Type {
  static get EXTRA_PROPERTIES() {
    return ["choices", ...super.EXTRA_PROPERTIES];
  }

  /** @type {(root, schema, path, extraProperties?: Iterable) => ChoiceType} */
  static parseSchema(root, schema, path, extraProperties = []) {
    this.checkSchemaProperties(schema, path, extraProperties);

    let choices = schema.choices.map(t => root.parseSchema(t, path));
    return new this(schema, choices);
  }

  constructor(schema, choices) {
    super(schema);
    this.choices = choices;
  }

  extend(type) {
    this.choices.push(...type.choices);

    return this;
  }

  normalize(value, context) {
    this.checkDeprecated(context, value);

    let error;
    let { choices, result } = context.withChoices(() => {
      for (let choice of this.choices) {
        // Ignore a possible choice if it is not supported by
        // the manifest version we are normalizing.
        if (!context.matchManifestVersion(choice)) {
          continue;
        }

        let r = choice.normalize(value, context);
        if (!r.error) {
          return r;
        }

        error = r;
      }
    });

    if (result) {
      return result;
    }
    if (choices.size <= 1) {
      return error;
    }

    choices = Array.from(choices, forceString);
    let n = choices.length - 1;
    choices[n] = `or ${choices[n]}`;

    let message;
    if (typeof value === "object") {
      message = () => `Value must either: ${choices.join(", ")}`;
    } else {
      message = () =>
        `Value ${JSON.stringify(value)} must either: ${choices.join(", ")}`;
    }

    return context.error(message, null);
  }

  checkBaseType(baseType) {
    return this.choices.some(t => t.checkBaseType(baseType));
  }

  getDescriptor(path, context) {
    // In StringType.getDescriptor, unlike any other Type, a descriptor is returned if
    // it is an enumeration.  Since we need versioned choices in some cases, here we
    // build a list of valid enumerations that will work for a given manifest version.
    if (
      !this.choices.length ||
      !this.choices.every(t => t.checkBaseType("string") && t.enumeration)
    ) {
      return;
    }

    let obj = Cu.createObjectIn(context.cloneScope);
    let descriptor = { value: obj };
    for (let choice of this.choices) {
      // Ignore a possible choice if it is not supported by
      // the manifest version we are normalizing.
      if (!context.matchManifestVersion(choice)) {
        continue;
      }
      let d = choice.getDescriptor(path, context);
      if (d) {
        Object.assign(obj, d.descriptor.value);
      }
    }

    return { descriptor };
  }
}

// This is a reference to another type--essentially a typedef.
class RefType extends Type {
  static get EXTRA_PROPERTIES() {
    return ["$ref", ...super.EXTRA_PROPERTIES];
  }

  /** @type {(root, schema, path, extraProperties?: Iterable) => RefType} */
  static parseSchema(root, schema, path, extraProperties = []) {
    this.checkSchemaProperties(schema, path, extraProperties);

    let ref = schema.$ref;
    let ns = path.join(".");
    if (ref.includes(".")) {
      [, ns, ref] = /^(.*)\.(.*?)$/.exec(ref);
    }
    return new this(root, schema, ns, ref);
  }

  // For a reference to a type named T declared in namespace NS,
  // namespaceName will be NS and reference will be T.
  constructor(root, schema, namespaceName, reference) {
    super(schema);
    this.root = root;
    this.namespaceName = namespaceName;
    this.reference = reference;
  }

  get targetType() {
    let ns = this.root.getNamespace(this.namespaceName);
    let type = ns.get(this.reference);
    if (!type) {
      throw new Error(`Internal error: Type ${this.reference} not found`);
    }
    return type;
  }

  normalize(value, context) {
    this.checkDeprecated(context, value);
    return this.targetType.normalize(value, context);
  }

  checkBaseType(baseType) {
    return this.targetType.checkBaseType(baseType);
  }
}

class StringType extends Type {
  static get EXTRA_PROPERTIES() {
    return [
      "enum",
      "minLength",
      "maxLength",
      "pattern",
      "format",
      ...super.EXTRA_PROPERTIES,
    ];
  }

  static parseSchema(root, schema, path, extraProperties = []) {
    this.checkSchemaProperties(schema, path, extraProperties);

    let enumeration = schema.enum || null;
    if (enumeration) {
      // The "enum" property is either a list of strings that are
      // valid values or else a list of {name, description} objects,
      // where the .name values are the valid values.
      enumeration = enumeration.map(e => {
        if (typeof e == "object") {
          return e.name;
        }
        return e;
      });
    }

    let pattern = null;
    if (schema.pattern) {
      try {
        pattern = parsePattern(schema.pattern);
      } catch (e) {
        throw new Error(
          `Internal error: Invalid pattern ${JSON.stringify(schema.pattern)}`
        );
      }
    }

    let format = null;
    if (schema.format) {
      if (!(schema.format in FORMATS)) {
        throw new Error(
          `Internal error: Invalid string format ${schema.format}`
        );
      }
      format = FORMATS[schema.format];
    }
    return new this(
      schema,
      schema.id || undefined,
      enumeration,
      schema.minLength || 0,
      schema.maxLength || Infinity,
      pattern,
      format
    );
  }

  constructor(
    schema,
    name,
    enumeration,
    minLength,
    maxLength,
    pattern,
    format
  ) {
    super(schema);
    this.name = name;
    this.enumeration = enumeration;
    this.minLength = minLength;
    this.maxLength = maxLength;
    this.pattern = pattern;
    this.format = format;
  }

  normalize(value, context) {
    let r = this.normalizeBase("string", value, context);
    if (r.error) {
      return r;
    }
    value = r.value;

    if (this.enumeration) {
      if (this.enumeration.includes(value)) {
        return this.postprocess({ value }, context);
      }

      let choices = this.enumeration.map(JSON.stringify).join(", ");

      return context.error(
        () => `Invalid enumeration value ${JSON.stringify(value)}`,
        `be one of [${choices}]`
      );
    }

    if (value.length < this.minLength) {
      return context.error(
        () =>
          `String ${JSON.stringify(value)} is too short (must be ${
            this.minLength
          })`,
        `be longer than ${this.minLength}`
      );
    }
    if (value.length > this.maxLength) {
      return context.error(
        () =>
          `String ${JSON.stringify(value)} is too long (must be ${
            this.maxLength
          })`,
        `be shorter than ${this.maxLength}`
      );
    }

    if (this.pattern && !this.pattern.test(value)) {
      return context.error(
        () => `String ${JSON.stringify(value)} must match ${this.pattern}`,
        `match the pattern ${this.pattern.toSource()}`
      );
    }

    if (this.format) {
      try {
        r.value = this.format(r.value, context);
      } catch (e) {
        return context.error(
          String(e),
          `match the format "${this.format.name}"`
        );
      }
    }

    return r;
  }

  checkBaseType(baseType) {
    return baseType == "string";
  }

  getDescriptor(path, context) {
    if (this.enumeration) {
      let obj = Cu.createObjectIn(context.cloneScope);

      for (let e of this.enumeration) {
        obj[e.toUpperCase()] = e;
      }

      return {
        descriptor: { value: obj },
      };
    }
  }
}

class NullType extends Type {
  normalize(value, context) {
    return this.normalizeBase("null", value, context);
  }

  checkBaseType(baseType) {
    return baseType == "null";
  }
}

let FunctionEntry;
let Event;
let SubModuleType;

class ObjectType extends Type {
  static get EXTRA_PROPERTIES() {
    return [
      "properties",
      "patternProperties",
      "$import",
      ...super.EXTRA_PROPERTIES,
    ];
  }

  static parseSchema(root, schema, path, extraProperties = []) {
    if ("functions" in schema) {
      return SubModuleType.parseSchema(root, schema, path, extraProperties);
    }

    if (DEBUG && !("$extend" in schema)) {
      // Only allow extending "properties" and "patternProperties".
      extraProperties = [
        "additionalProperties",
        "isInstanceOf",
        ...extraProperties,
      ];
    }
    this.checkSchemaProperties(schema, path, extraProperties);

    let imported = null;
    if ("$import" in schema) {
      let importPath = schema.$import;
      let idx = importPath.indexOf(".");
      if (idx === -1) {
        imported = [path[0], importPath];
      } else {
        imported = [importPath.slice(0, idx), importPath.slice(idx + 1)];
      }
    }

    let parseProperty = (schema, extraProps = []) => {
      return {
        type: root.parseSchema(
          schema,
          path,
          DEBUG && [
            "unsupported",
            "onError",
            "permissions",
            "default",
            ...extraProps,
          ]
        ),
        optional: schema.optional || false,
        unsupported: schema.unsupported || false,
        onError: schema.onError || null,
        default: schema.default === undefined ? null : schema.default,
      };
    };

    // Parse explicit "properties" object.
    let properties = Object.create(null);
    for (let propName of Object.keys(schema.properties || {})) {
      properties[propName] = parseProperty(schema.properties[propName], [
        "optional",
      ]);
    }

    // Parse regexp properties from "patternProperties" object.
    let patternProperties = [];
    for (let propName of Object.keys(schema.patternProperties || {})) {
      let pattern;
      try {
        pattern = parsePattern(propName);
      } catch (e) {
        throw new Error(
          `Internal error: Invalid property pattern ${JSON.stringify(propName)}`
        );
      }

      patternProperties.push({
        pattern,
        type: parseProperty(schema.patternProperties[propName]),
      });
    }

    // Parse "additionalProperties" schema.
    let additionalProperties = null;
    if (schema.additionalProperties) {
      let type = schema.additionalProperties;
      if (type === true) {
        type = { type: "any" };
      }

      additionalProperties = root.parseSchema(type, path);
    }

    return new this(
      schema,
      properties,
      additionalProperties,
      patternProperties,
      schema.isInstanceOf || null,
      imported
    );
  }

  constructor(
    schema,
    properties,
    additionalProperties,
    patternProperties,
    isInstanceOf,
    imported
  ) {
    super(schema);
    this.properties = properties;
    this.additionalProperties = additionalProperties;
    this.patternProperties = patternProperties;
    this.isInstanceOf = isInstanceOf;

    if (imported) {
      let [ns, path] = imported;
      ns = Schemas.getNamespace(ns);
      let importedType = ns.get(path);
      if (!importedType) {
        throw new Error(`Internal error: imported type ${path} not found`);
      }

      if (DEBUG && !(importedType instanceof ObjectType)) {
        throw new Error(
          `Internal error: cannot import non-object type ${path}`
        );
      }

      this.properties = Object.assign(
        {},
        importedType.properties,
        this.properties
      );
      this.patternProperties = [
        ...importedType.patternProperties,
        ...this.patternProperties,
      ];
      this.additionalProperties =
        importedType.additionalProperties || this.additionalProperties;
    }
  }

  extend(type) {
    for (let key of Object.keys(type.properties)) {
      if (key in this.properties) {
        throw new Error(
          `InternalError: Attempt to extend an object with conflicting property "${key}"`
        );
      }
      this.properties[key] = type.properties[key];
    }

    this.patternProperties.push(...type.patternProperties);

    return this;
  }

  checkBaseType(baseType) {
    return baseType == "object";
  }

  /**
   * Extracts the enumerable properties of the given object, including
   * function properties which would normally be omitted by X-ray
   * wrappers.
   *
   * @param {object} value
   * @param {Context} context
   *        The current parse context.
   * @returns {object}
   *        An object with an `error` or `value` property.
   */
  extractProperties(value, context) {
    // |value| should be a JS Xray wrapping an object in the
    // extension compartment. This works well except when we need to
    // access callable properties on |value| since JS Xrays don't
    // support those. To work around the problem, we verify that
    // |value| is a plain JS object (i.e., not anything scary like a
    // Proxy). Then we copy the properties out of it into a normal
    // object using a waiver wrapper.

    let klass = ChromeUtils.getClassName(value, true);
    if (klass != "Object") {
      throw context.error(
        `Expected a plain JavaScript object, got a ${klass}`,
        `be a plain JavaScript object`
      );
    }

    return ChromeUtils.shallowClone(value);
  }

  checkProperty(context, prop, propType, result, properties, remainingProps) {
    let { type, optional, unsupported, onError } = propType;
    let error = null;

    if (!context.matchManifestVersion(type)) {
      if (prop in properties) {
        error = context.error(
          `Property "${prop}" is unsupported in Manifest Version ${context.manifestVersion}`,
          `not contain an unsupported "${prop}" property`
        );

        context.logWarning(forceString(error.error));
        if (this.additionalProperties) {
          // When `additionalProperties` is set to UnrecognizedProperty, the
          // caller (i.e. ObjectType's normalize method) assigns the original
          // value to `result[prop]`. Erase the property now to prevent
          // `result[prop]` from becoming anything other than `undefined.
          //
          // A warning was already logged above, so we do not need to also log
          // "An unexpected property was found in the WebExtension manifest."
          remainingProps.delete(prop);
        }
        // When `additionalProperties` is not set, ObjectType's normalize method
        // will return an error because prop is still in remainingProps.
        return;
      }
    } else if (unsupported) {
      if (prop in properties) {
        error = context.error(
          `Property "${prop}" is unsupported by Firefox`,
          `not contain an unsupported "${prop}" property`
        );
      }
    } else if (prop in properties) {
      if (
        optional &&
        (properties[prop] === null || properties[prop] === undefined)
      ) {
        result[prop] = propType.default;
      } else {
        let r = context.withPath(prop, () =>
          type.normalize(properties[prop], context)
        );
        if (r.error) {
          error = r;
        } else {
          result[prop] = r.value;
          properties[prop] = r.value;
        }
      }
      remainingProps.delete(prop);
    } else if (!optional) {
      error = context.error(
        `Property "${prop}" is required`,
        `contain the required "${prop}" property`
      );
    } else if (optional !== "omit-key-if-missing") {
      result[prop] = propType.default;
    }

    if (error) {
      if (onError == "warn") {
        context.logWarning(forceString(error.error));
      } else if (onError != "ignore") {
        throw error;
      }

      result[prop] = propType.default;
    }
  }

  normalize(value, context) {
    try {
      let v = this.normalizeBase("object", value, context);
      if (v.error) {
        return v;
      }
      value = v.value;

      if (this.isInstanceOf) {
        if (DEBUG) {
          if (
            Object.keys(this.properties).length ||
            this.patternProperties.length ||
            !(this.additionalProperties instanceof AnyType)
          ) {
            throw new Error(
              "InternalError: isInstanceOf can only be used " +
                "with objects that are otherwise unrestricted"
            );
          }
        }

        if (
          ChromeUtils.getClassName(value) !== this.isInstanceOf &&
          (this.isInstanceOf !== "Element" || value.nodeType !== 1)
        ) {
          return context.error(
            `Object must be an instance of ${this.isInstanceOf}`,
            `be an instance of ${this.isInstanceOf}`
          );
        }

        // This is kind of a hack, but we can't normalize things that
        // aren't JSON, so we just return them.
        return this.postprocess({ value }, context);
      }

      let properties = this.extractProperties(value, context);
      let remainingProps = new Set(Object.keys(properties));

      let result = {};
      for (let prop of Object.keys(this.properties)) {
        this.checkProperty(
          context,
          prop,
          this.properties[prop],
          result,
          properties,
          remainingProps
        );
      }

      for (let prop of Object.keys(properties)) {
        for (let { pattern, type } of this.patternProperties) {
          if (pattern.test(prop)) {
            this.checkProperty(
              context,
              prop,
              type,
              result,
              properties,
              remainingProps
            );
          }
        }
      }

      if (this.additionalProperties) {
        for (let prop of remainingProps) {
          let r = context.withPath(prop, () =>
            this.additionalProperties.normalize(properties[prop], context)
          );
          if (r.error) {
            return r;
          }
          result[prop] = r.value;
        }
      } else if (remainingProps.size && !context.ignoreUnrecognizedProperties) {
        if (remainingProps.size == 1) {
          return context.error(
            `Unexpected property "${[...remainingProps]}"`,
            `not contain an unexpected "${[...remainingProps]}" property`
          );
        } else if (remainingProps.size) {
          let props = [...remainingProps].sort().join(", ");
          return context.error(
            `Unexpected properties: ${props}`,
            `not contain the unexpected properties [${props}]`
          );
        }
      }

      return this.postprocess({ value: result }, context);
    } catch (e) {
      if (e.error) {
        return e;
      }
      throw e;
    }
  }
}

// This type is just a placeholder to be referred to by
// SubModuleProperty. No value is ever expected to have this type.
SubModuleType = class SubModuleType extends Type {
  static get EXTRA_PROPERTIES() {
    return ["functions", "events", "properties", ...super.EXTRA_PROPERTIES];
  }

  static parseSchema(root, schema, path, extraProperties = []) {
    this.checkSchemaProperties(schema, path, extraProperties);

    // The path we pass in here is only used for error messages.
    path = [...path, schema.id];
    let functions = schema.functions
      .filter(fun => !fun.unsupported)
      .map(fun => FunctionEntry.parseSchema(root, fun, path));

    let events = [];

    if (schema.events) {
      events = schema.events
        .filter(event => !event.unsupported)
        .map(event => Event.parseSchema(root, event, path));
    }

    return new this(schema, functions, events);
  }

  constructor(schema, functions, events) {
    // schema contains properties such as min/max_manifest_version needed
    // in the base class so that the Context class can version compare
    // any entries against the manifest version.
    super(schema);
    this.functions = functions;
    this.events = events;
  }
};

class NumberType extends Type {
  normalize(value, context) {
    let r = this.normalizeBase("number", value, context);
    if (r.error) {
      return r;
    }

    if (isNaN(r.value) || !Number.isFinite(r.value)) {
      return context.error(
        "NaN and infinity are not valid",
        "be a finite number"
      );
    }

    return r;
  }

  checkBaseType(baseType) {
    return baseType == "number" || baseType == "integer";
  }
}

class IntegerType extends Type {
  static get EXTRA_PROPERTIES() {
    return ["minimum", "maximum", ...super.EXTRA_PROPERTIES];
  }

  static parseSchema(root, schema, path, extraProperties = []) {
    this.checkSchemaProperties(schema, path, extraProperties);

    let { minimum = -Infinity, maximum = Infinity } = schema;
    return new this(schema, minimum, maximum);
  }

  constructor(schema, minimum, maximum) {
    super(schema);
    this.minimum = minimum;
    this.maximum = maximum;
  }

  normalize(value, context) {
    let r = this.normalizeBase("integer", value, context);
    if (r.error) {
      return r;
    }
    value = r.value;

    // Ensure it's between -2**31 and 2**31-1
    if (!Number.isSafeInteger(value)) {
      return context.error(
        "Integer is out of range",
        "be a valid 32 bit signed integer"
      );
    }

    if (value < this.minimum) {
      return context.error(
        `Integer ${value} is too small (must be at least ${this.minimum})`,
        `be at least ${this.minimum}`
      );
    }
    if (value > this.maximum) {
      return context.error(
        `Integer ${value} is too big (must be at most ${this.maximum})`,
        `be no greater than ${this.maximum}`
      );
    }

    return this.postprocess(r, context);
  }

  checkBaseType(baseType) {
    return baseType == "integer";
  }
}

class BooleanType extends Type {
  static get EXTRA_PROPERTIES() {
    return ["enum", ...super.EXTRA_PROPERTIES];
  }

  static parseSchema(root, schema, path, extraProperties = []) {
    this.checkSchemaProperties(schema, path, extraProperties);
    let enumeration = schema.enum || null;
    return new this(schema, enumeration);
  }

  constructor(schema, enumeration) {
    super(schema);
    this.enumeration = enumeration;
  }

  normalize(value, context) {
    if (!this.checkBaseType(getValueBaseType(value))) {
      return context.error(
        () => `Expected boolean instead of ${JSON.stringify(value)}`,
        `be a boolean`
      );
    }
    value = this.preprocess(value, context);
    if (this.enumeration && !this.enumeration.includes(value)) {
      return context.error(
        () => `Invalid value ${JSON.stringify(value)}`,
        `be ${this.enumeration}`
      );
    }
    this.checkDeprecated(context, value);
    return { value };
  }

  checkBaseType(baseType) {
    return baseType == "boolean";
  }
}

class ArrayType extends Type {
  static get EXTRA_PROPERTIES() {
    return ["items", "minItems", "maxItems", ...super.EXTRA_PROPERTIES];
  }

  static parseSchema(root, schema, path, extraProperties = []) {
    this.checkSchemaProperties(schema, path, extraProperties);

    let items = root.parseSchema(schema.items, path, ["onError"]);

    return new this(
      schema,
      items,
      schema.minItems || 0,
      schema.maxItems || Infinity
    );
  }

  constructor(schema, itemType, minItems, maxItems) {
    super(schema);
    this.itemType = itemType;
    this.minItems = minItems;
    this.maxItems = maxItems;
    this.onError = schema.items.onError || null;
  }

  normalize(value, context) {
    let v = this.normalizeBase("array", value, context);
    if (v.error) {
      return v;
    }
    value = v.value;

    let result = [];
    for (let [i, element] of value.entries()) {
      element = context.withPath(String(i), () =>
        this.itemType.normalize(element, context)
      );
      if (element.error) {
        if (this.onError == "warn") {
          context.logWarning(forceString(element.error));
        } else if (this.onError != "ignore") {
          return element;
        }
        continue;
      }
      result.push(element.value);
    }

    if (result.length < this.minItems) {
      return context.error(
        `Array requires at least ${this.minItems} items; you have ${result.length}`,
        `have at least ${this.minItems} items`
      );
    }

    if (result.length > this.maxItems) {
      return context.error(
        `Array requires at most ${this.maxItems} items; you have ${result.length}`,
        `have at most ${this.maxItems} items`
      );
    }

    return this.postprocess({ value: result }, context);
  }

  checkBaseType(baseType) {
    return baseType == "array";
  }
}

class FunctionType extends Type {
  static get EXTRA_PROPERTIES() {
    return [
      "parameters",
      "async",
      "returns",
      "requireUserInput",
      ...super.EXTRA_PROPERTIES,
    ];
  }

  static parseSchema(root, schema, path, extraProperties = []) {
    this.checkSchemaProperties(schema, path, extraProperties);

    let isAsync = !!schema.async;
    let isExpectingCallback = typeof schema.async === "string";
    let parameters = null;
    if ("parameters" in schema) {
      parameters = [];
      for (let param of schema.parameters) {
        // Callbacks default to optional for now, because of promise
        // handling.
        let isCallback = isAsync && param.name == schema.async;
        if (isCallback) {
          isExpectingCallback = false;
        }

        parameters.push({
          type: root.parseSchema(param, path, ["name", "optional", "default"]),
          name: param.name,
          optional: param.optional == null ? isCallback : param.optional,
          default: param.default == undefined ? null : param.default,
        });
      }
    }
    let hasAsyncCallback = false;
    if (isAsync) {
      hasAsyncCallback =
        parameters &&
        parameters.length &&
        parameters[parameters.length - 1].name == schema.async;
    }

    if (DEBUG) {
      if (isExpectingCallback) {
        throw new Error(
          `Internal error: Expected a callback parameter ` +
            `with name ${schema.async}`
        );
      }

      if (isAsync && schema.returns) {
        throw new Error(
          "Internal error: Async functions must not have return values."
        );
      }
      if (
        isAsync &&
        schema.allowAmbiguousOptionalArguments &&
        !hasAsyncCallback
      ) {
        throw new Error(
          "Internal error: Async functions with ambiguous " +
            "arguments must declare the callback as the last parameter"
        );
      }
    }

    return new this(
      schema,
      parameters,
      isAsync,
      hasAsyncCallback,
      !!schema.requireUserInput
    );
  }

  constructor(schema, parameters, isAsync, hasAsyncCallback, requireUserInput) {
    super(schema);
    this.parameters = parameters;
    this.isAsync = isAsync;
    this.hasAsyncCallback = hasAsyncCallback;
    this.requireUserInput = requireUserInput;
  }

  normalize(value, context) {
    return this.normalizeBase("function", value, context);
  }

  checkBaseType(baseType) {
    return baseType == "function";
  }
}

// Represents a "property" defined in a schema namespace with a
// particular value. Essentially this is a constant.
class ValueProperty extends Entry {
  constructor(schema, name, value) {
    super(schema);
    this.name = name;
    this.value = value;
  }

  getDescriptor(path, context) {
    // Prevent injection if not a supported version.
    if (!context.matchManifestVersion(this)) {
      return;
    }

    return {
      descriptor: { value: this.value },
    };
  }
}

// Represents a "property" defined in a schema namespace that is not a
// constant.
class TypeProperty extends Entry {
  unsupported = false;

  constructor(schema, path, name, type, writable, permissions) {
    super(schema);
    this.path = path;
    this.name = name;
    this.type = type;
    this.writable = writable;
    this.permissions = permissions;
  }

  throwError(context, msg) {
    throw context.makeError(`${msg} for ${this.path.join(".")}.${this.name}.`);
  }

  getDescriptor(path, context) {
    if (this.unsupported || !context.matchManifestVersion(this)) {
      return;
    }

    let apiImpl = context.getImplementation(path.join("."), this.name);

    let getStub = () => {
      this.checkDeprecated(context);
      return apiImpl.getProperty();
    };

    let descriptor = {
      get: Cu.exportFunction(getStub, context.cloneScope),
    };

    if (this.writable) {
      let setStub = value => {
        let normalized = this.type.normalize(value, context);
        if (normalized.error) {
          this.throwError(context, forceString(normalized.error));
        }

        apiImpl.setProperty(normalized.value);
      };

      descriptor.set = Cu.exportFunction(setStub, context.cloneScope);
    }

    return {
      descriptor,
      revoke() {
        apiImpl.revoke();
        apiImpl = null;
      },
    };
  }
}

class SubModuleProperty extends Entry {
  // A SubModuleProperty represents a tree of objects and properties
  // to expose to an extension. Currently we support only a limited
  // form of sub-module properties, where "$ref" points to a
  // SubModuleType containing a list of functions and "properties" is
  // a list of additional simple properties.
  //
  // name: Name of the property stuff is being added to.
  // namespaceName: Namespace in which the property lives.
  // reference: Name of the type defining the functions to add to the property.
  // properties: Additional properties to add to the module (unsupported).
  constructor(root, schema, path, name, reference, properties, permissions) {
    super(schema);
    this.root = root;
    this.name = name;
    this.path = path;
    this.namespaceName = path.join(".");
    this.reference = reference;
    this.properties = properties;
    this.permissions = permissions;
  }

  get targetType() {
    let ns = this.root.getNamespace(this.namespaceName);
    let type = ns.get(this.reference);
    if (!type && this.reference.includes(".")) {
      let [namespaceName, ref] = this.reference.split(".");
      ns = this.root.getNamespace(namespaceName);
      type = ns.get(ref);
    }
    return type;
  }

  getDescriptor(path, context) {
    let obj = Cu.createObjectIn(context.cloneScope);

    let ns = this.root.getNamespace(this.namespaceName);
    let type = this.targetType;

    // Prevent injection if not a supported version.
    if (!context.matchManifestVersion(type)) {
      return;
    }

    if (DEBUG) {
      if (!type || !(type instanceof SubModuleType)) {
        throw new Error(
          `Internal error: ${this.namespaceName}.${this.reference} ` +
            `is not a sub-module`
        );
      }
    }
    let subpath = [...path, this.name];

    let functions = type.functions;
    for (let fun of functions) {
      context.injectInto(fun, obj, fun.name, subpath, ns);
    }

    let events = type.events;
    for (let event of events) {
      context.injectInto(event, obj, event.name, subpath, ns);
    }

    // TODO: Inject this.properties.

    return {
      descriptor: { value: obj },
      revoke() {
        let unwrapped = ChromeUtils.waiveXrays(obj);
        for (let fun of functions) {
          try {
            delete unwrapped[fun.name];
          } catch (e) {
            Cu.reportError(e);
          }
        }
      },
    };
  }
}

// This class is a base class for FunctionEntrys and Events. It takes
// care of validating parameter lists (i.e., handling of optional
// parameters and parameter type checking).
class CallEntry extends Entry {
  hasAsyncCallback = false;

  constructor(schema, path, name, parameters, allowAmbiguousOptionalArguments) {
    super(schema);
    this.path = path;
    this.name = name;
    this.parameters = parameters;
    this.allowAmbiguousOptionalArguments = allowAmbiguousOptionalArguments;
  }

  throwError(context, msg) {
    throw context.makeError(`${msg} for ${this.path.join(".")}.${this.name}.`);
  }

  checkParameters(args, context) {
    let fixedArgs = [];

    // First we create a new array, fixedArgs, that is the same as
    // |args| but with default values in place of omitted optional parameters.
    let check = (parameterIndex, argIndex) => {
      if (parameterIndex == this.parameters.length) {
        if (argIndex == args.length) {
          return true;
        }
        return false;
      }

      let parameter = this.parameters[parameterIndex];
      if (parameter.optional) {
        // Try skipping it.
        fixedArgs[parameterIndex] = parameter.default;
        if (check(parameterIndex + 1, argIndex)) {
          return true;
        }
      }

      if (argIndex == args.length) {
        return false;
      }

      let arg = args[argIndex];
      if (!parameter.type.checkBaseType(getValueBaseType(arg))) {
        // For Chrome compatibility, use the default value if null or undefined
        // is explicitly passed but is not a valid argument in this position.
        if (parameter.optional && (arg === null || arg === undefined)) {
          fixedArgs[parameterIndex] = Cu.cloneInto(parameter.default, {});
        } else {
          return false;
        }
      } else {
        fixedArgs[parameterIndex] = arg;
      }

      return check(parameterIndex + 1, argIndex + 1);
    };

    if (this.allowAmbiguousOptionalArguments) {
      // When this option is set, it's up to the implementation to
      // parse arguments.
      // The last argument for asynchronous methods is either a function or null.
      // This is specifically done for runtime.sendMessage.
      if (this.hasAsyncCallback && typeof args[args.length - 1] != "function") {
        args.push(null);
      }
      return args;
    }
    let success = check(0, 0);
    if (!success) {
      this.throwError(context, "Incorrect argument types");
    }

    // Now we normalize (and fully type check) all non-omitted arguments.
    fixedArgs = fixedArgs.map((arg, parameterIndex) => {
      if (arg === null) {
        return null;
      }
      let parameter = this.parameters[parameterIndex];
      let r = parameter.type.normalize(arg, context);
      if (r.error) {
        this.throwError(
          context,
          `Type error for parameter ${parameter.name} (${forceString(r.error)})`
        );
      }
      return r.value;
    });

    return fixedArgs;
  }
}

// Represents a "function" defined in a schema namespace.
FunctionEntry = class FunctionEntry extends CallEntry {
  static parseSchema(root, schema, path) {
    // When not in DEBUG mode, we just need to know *if* this returns.
    /** @type {boolean|object} */
    let returns = !!schema.returns;
    if (DEBUG && "returns" in schema) {
      returns = {
        type: root.parseSchema(schema.returns, path, ["optional", "name"]),
        optional: schema.returns.optional || false,
        name: "result",
      };
    }

    return new this(
      schema,
      path,
      schema.name,
      root.parseSchema(schema, path, [
        "name",
        "unsupported",
        "returns",
        "permissions",
        "allowAmbiguousOptionalArguments",
        "allowCrossOriginArguments",
      ]),
      schema.unsupported || false,
      schema.allowAmbiguousOptionalArguments || false,
      schema.allowCrossOriginArguments || false,
      returns,
      schema.permissions || null
    );
  }

  constructor(
    schema,
    path,
    name,
    type,
    unsupported,
    allowAmbiguousOptionalArguments,
    allowCrossOriginArguments,
    returns,
    permissions
  ) {
    super(schema, path, name, type.parameters, allowAmbiguousOptionalArguments);
    this.unsupported = unsupported;
    this.returns = returns;
    this.permissions = permissions;
    this.allowCrossOriginArguments = allowCrossOriginArguments;

    this.isAsync = type.isAsync;
    this.hasAsyncCallback = type.hasAsyncCallback;
    this.requireUserInput = type.requireUserInput;
  }

  checkValue({ type, optional, name }, value, context) {
    if (optional && value == null) {
      return;
    }
    if (
      type.reference === "ExtensionPanel" ||
      type.reference === "ExtensionSidebarPane" ||
      type.reference === "Port"
    ) {
      // TODO: We currently treat objects with functions as SubModuleType,
      // which is just wrong, and a bigger yak.  Skipping for now.
      return;
    }
    const { error } = type.normalize(value, context);
    if (error) {
      this.throwError(
        context,
        `Type error for ${name} value (${forceString(error)})`
      );
    }
  }

  checkCallback(args, context) {
    const callback = this.parameters[this.parameters.length - 1];
    for (const [i, param] of callback.type.parameters.entries()) {
      this.checkValue(param, args[i], context);
    }
  }

  getDescriptor(path, context) {
    let apiImpl = context.getImplementation(path.join("."), this.name);

    let stub;
    if (this.isAsync) {
      stub = (...args) => {
        this.checkDeprecated(context);
        let actuals = this.checkParameters(args, context);
        let callback = null;
        if (this.hasAsyncCallback) {
          callback = actuals.pop();
        }
        if (callback === null && context.isChromeCompat) {
          // We pass an empty stub function as a default callback for
          // the `chrome` API, so promise objects are not returned,
          // and lastError values are reported immediately.
          callback = () => {};
        }
        if (DEBUG && this.hasAsyncCallback && callback) {
          let original = callback;
          callback = (...args) => {
            this.checkCallback(args, context);
            original(...args);
          };
        }
        let result = apiImpl.callAsyncFunction(
          actuals,
          callback,
          this.requireUserInput
        );
        if (DEBUG && this.hasAsyncCallback && !callback) {
          return result.then(result => {
            this.checkCallback([result], context);
            return result;
          });
        }
        return result;
      };
    } else if (!this.returns) {
      stub = (...args) => {
        this.checkDeprecated(context);
        let actuals = this.checkParameters(args, context);
        return apiImpl.callFunctionNoReturn(actuals);
      };
    } else {
      stub = (...args) => {
        this.checkDeprecated(context);
        let actuals = this.checkParameters(args, context);
        let result = apiImpl.callFunction(actuals);
        if (DEBUG && this.returns) {
          this.checkValue(this.returns, result, context);
        }
        return result;
      };
    }

    return {
      descriptor: {
        value: Cu.exportFunction(stub, context.cloneScope, {
          allowCrossOriginArguments: this.allowCrossOriginArguments,
        }),
      },
      revoke() {
        apiImpl.revoke();
        apiImpl = null;
      },
    };
  }
};

// Represents an "event" defined in a schema namespace.
//
// TODO Bug 1369722: we should be able to remove the eslint-disable-line that follows
// once Bug 1369722 has been fixed.
// eslint-disable-next-line no-global-assign
Event = class Event extends CallEntry {
  static parseSchema(root, event, path) {
    let extraParameters = Array.from(event.extraParameters || [], param => ({
      type: root.parseSchema(param, path, ["name", "optional", "default"]),
      name: param.name,
      optional: param.optional || false,
      default: param.default == undefined ? null : param.default,
    }));

    let extraProperties = [
      "name",
      "unsupported",
      "permissions",
      "extraParameters",
      // We ignore these properties for now.
      "returns",
      "filters",
    ];

    return new this(
      event,
      path,
      event.name,
      root.parseSchema(event, path, extraProperties),
      extraParameters,
      event.unsupported || false,
      event.permissions || null
    );
  }

  constructor(
    schema,
    path,
    name,
    type,
    extraParameters,
    unsupported,
    permissions
  ) {
    super(schema, path, name, extraParameters);
    this.type = type;
    this.unsupported = unsupported;
    this.permissions = permissions;
  }

  checkListener(listener, context) {
    let r = this.type.normalize(listener, context);
    if (r.error) {
      this.throwError(context, "Invalid listener");
    }
    return r.value;
  }

  getDescriptor(path, context) {
    let apiImpl = context.getImplementation(path.join("."), this.name);

    let addStub = (listener, ...args) => {
      listener = this.checkListener(listener, context);
      let actuals = this.checkParameters(args, context);
      apiImpl.addListener(listener, actuals);
    };

    let removeStub = listener => {
      listener = this.checkListener(listener, context);
      apiImpl.removeListener(listener);
    };

    let hasStub = listener => {
      listener = this.checkListener(listener, context);
      return apiImpl.hasListener(listener);
    };

    let obj = Cu.createObjectIn(context.cloneScope);

    Cu.exportFunction(addStub, obj, { defineAs: "addListener" });
    Cu.exportFunction(removeStub, obj, { defineAs: "removeListener" });
    Cu.exportFunction(hasStub, obj, { defineAs: "hasListener" });

    return {
      descriptor: { value: obj },
      revoke() {
        apiImpl.revoke();
        apiImpl = null;

        let unwrapped = ChromeUtils.waiveXrays(obj);
        delete unwrapped.addListener;
        delete unwrapped.removeListener;
        delete unwrapped.hasListener;
      },
    };
  }
};

const TYPES = Object.freeze(
  Object.assign(Object.create(null), {
    any: AnyType,
    array: ArrayType,
    boolean: BooleanType,
    function: FunctionType,
    integer: IntegerType,
    null: NullType,
    number: NumberType,
    object: ObjectType,
    string: StringType,
  })
);

const LOADERS = {
  events: "loadEvent",
  functions: "loadFunction",
  properties: "loadProperty",
  types: "loadType",
};

class Namespace extends Map {
  /** @type {Entry} */
  fallbackEntry;

  constructor(root, name, path) {
    super();

    this.root = root;

    this._lazySchemas = [];
    this.initialized = false;

    this.name = name;
    this.path = name ? [...path, name] : [...path];

    this.superNamespace = null;

    this.min_manifest_version = MIN_MANIFEST_VERSION;
    this.max_manifest_version = MAX_MANIFEST_VERSION;

    this.permissions = null;
    this.allowedContexts = [];
    this.defaultContexts = [];
  }

  /**
   * Adds a JSON Schema object to the set of schemas that represent this
   * namespace.
   *
   * @param {object} schema
   *        A JSON schema object which partially describes this
   *        namespace.
   */
  addSchema(schema) {
    this._lazySchemas.push(schema);

    for (let prop of [
      "permissions",
      "allowedContexts",
      "defaultContexts",
      "min_manifest_version",
      "max_manifest_version",
    ]) {
      if (schema[prop]) {
        this[prop] = schema[prop];
      }
    }

    if (schema.$import) {
      this.superNamespace = this.root.getNamespace(schema.$import);
    }
  }

  /**
   * Initializes the keys of this namespace based on the schema objects
   * added via previous `addSchema` calls.
   */
  init() {
    if (this.initialized) {
      return;
    }

    if (this.superNamespace) {
      this._lazySchemas.unshift(...this.superNamespace._lazySchemas);
    }

    // Keep in sync with LOADERS above.
    this.types = new DefaultMap(() => []);
    this.properties = new DefaultMap(() => []);
    this.functions = new DefaultMap(() => []);
    this.events = new DefaultMap(() => []);

    for (let schema of this._lazySchemas) {
      for (let type of schema.types || []) {
        if (!type.unsupported) {
          this.types.get(type.$extend || type.id).push(type);
        }
      }

      for (let [name, prop] of Object.entries(schema.properties || {})) {
        if (!prop.unsupported) {
          this.properties.get(name).push(prop);
        }
      }

      for (let fun of schema.functions || []) {
        if (!fun.unsupported) {
          this.functions.get(fun.name).push(fun);
        }
      }

      for (let event of schema.events || []) {
        if (!event.unsupported) {
          this.events.get(event.name).push(event);
        }
      }
    }

    // For each type of top-level property in the schema object, iterate
    // over all properties of that type, and create a temporary key for
    // each property pointing to its type. Those temporary properties
    // are later used to instantiate an Entry object based on the actual
    // schema object.
    for (let type of Object.keys(LOADERS)) {
      for (let key of this[type].keys()) {
        this.set(key, type);
      }
    }

    this.initialized = true;

    if (DEBUG) {
      for (let key of this.keys()) {
        // Force initialization of all lazy keys to catch unexpected errors.
        this.get(key);
      }
      this.#verifyFallbackEntries();
    }
  }

  /**
   * Verify that multiple definitions via fallback entries (currently only
   * supported for functions and events) are defined for mutually exclusive
   * manifest versions.
   */
  #verifyFallbackEntries() {
    for (
      let manifestVersion = MIN_MANIFEST_VERSION;
      manifestVersion <= MAX_MANIFEST_VERSION;
      manifestVersion++
    ) {
      for (let key of this.keys()) {
        let hasMatch = false;
        let entry = this.get(key);
        do {
          let isMatch =
            manifestVersion >= entry.min_manifest_version &&
            manifestVersion <= entry.max_manifest_version;
          if (isMatch && hasMatch) {
            throw new Error(
              `Namespace ${this.path.join(".")} has ` +
                `multiple definitions for ${key} ` +
                `for manifest version ${manifestVersion}`
            );
          }
          hasMatch ||= isMatch;
          entry = entry.fallbackEntry;
        } while (entry);
      }
    }
  }

  /**
   * Returns the definition of the provided Entry or Namespace which is valid for
   * the manifest version of the provided context, or none.
   *
   * @param {Entry|Namespace} entryOrNs
   * @param {Context} context
   *
   * @returns {Entry|Namespace?}
   */
  #getMatchingDefinitionForContext(entryOrNs, context) {
    do {
      if (context.matchManifestVersion(entryOrNs)) {
        // Common case at first iteration.
        return entryOrNs;
      }
      entryOrNs = entryOrNs.fallbackEntry;
    } while (entryOrNs);
  }

  /**
   * Initializes the value of a given key, by parsing the schema object
   * associated with it and replacing its temporary value with an `Entry`
   * instance.
   *
   * @param {string} key
   *        The name of the property to initialize.
   * @param {string} type
   *        The type of property the key represents. Must have a
   *        corresponding entry in the `LOADERS` object, pointing to the
   *        initialization method for that type.
   *
   * @returns {Entry}
   */
  initKey(key, type) {
    let loader = LOADERS[type];

    let entry;
    for (let schema of this[type].get(key)) {
      // Note: The 3rd parameter is currently only supported by loadEvent() and
      // loadFunction(). It stores the entry from the last iteration as a
      // fallbackEntry (different definitions for different manifest versions).
      entry = this[loader](key, schema, entry);
      // entry is always an Entry past the first iteration.
      this.set(key, entry);
    }

    return this.get(key);
  }

  loadType(name, type) {
    if ("$extend" in type) {
      return this.extendType(type);
    }
    return this.root.parseSchema(type, this.path, ["id"]);
  }

  extendType(type) {
    let targetType = this.get(type.$extend);

    // Only allow extending object and choices types for now.
    if (targetType instanceof ObjectType) {
      type.type = "object";
    } else if (DEBUG) {
      if (!targetType) {
        throw new Error(
          `Internal error: Attempt to extend a nonexistent type ${type.$extend}`
        );
      } else if (!(targetType instanceof ChoiceType)) {
        throw new Error(
          `Internal error: Attempt to extend a non-extensible type ${type.$extend}`
        );
      }
    }

    let parsed = this.root.parseSchema(type, this.path, ["$extend"]);

    if (DEBUG && parsed.constructor !== targetType.constructor) {
      throw new Error(`Internal error: Bad attempt to extend ${type.$extend}`);
    }

    targetType.extend(parsed);

    return targetType;
  }

  loadProperty(name, prop) {
    if ("$ref" in prop) {
      if (!prop.unsupported) {
        return new SubModuleProperty(
          this.root,
          prop,
          this.path,
          name,
          prop.$ref,
          prop.properties || {},
          prop.permissions || null
        );
      }
    } else if ("value" in prop) {
      return new ValueProperty(prop, name, prop.value);
    } else {
      // We ignore the "optional" attribute on properties since we
      // don't inject anything here anyway.
      let type = this.root.parseSchema(
        prop,
        [this.name],
        ["optional", "permissions", "writable"]
      );
      return new TypeProperty(
        prop,
        this.path,
        name,
        type,
        prop.writable || false,
        prop.permissions || null
      );
    }
  }

  loadFunction(name, fun, fallbackEntry) {
    const parsed = FunctionEntry.parseSchema(this.root, fun, this.path);
    // If there is already a valid entry, use it as a fallback for the current
    // one. Used for multiple definitions for different manifest versions.
    if (fallbackEntry) {
      parsed.fallbackEntry = fallbackEntry;
    }
    return parsed;
  }

  loadEvent(name, event, fallbackEntry) {
    const parsed = Event.parseSchema(this.root, event, this.path);
    // If there is already a valid entry, use it as a fallback for the current
    // one. Used for multiple definitions for different manifest versions.
    if (fallbackEntry) {
      parsed.fallbackEntry = fallbackEntry;
    }
    return parsed;
  }

  /**
   * Injects the properties of this namespace into the given object.
   *
   * @param {object} dest
   *        The object into which to inject the namespace properties.
   * @param {InjectionContext} context
   *        The injection context with which to inject the properties.
   */
  injectInto(dest, context) {
    for (let name of this.keys()) {
      // TODO bug 1896081: we should not call this.get() unconditionally, but
      //                   only for entries that have min_manifest_version or
      //                   max_manifest_version set.
      let entry = this.#getMatchingDefinitionForContext(
        this.get(name),
        context
      );
      // If no definition matches the manifest version, do not inject the property.
      // This prevents the item from being enumerable in the namespace object.
      // We cannot accomplish this inside exportLazyProperty, it specifically
      // injects an enumerable object.
      if (!entry) {
        continue;
      }

      exportLazyProperty(dest, name, () => {
        // See Bug 1896081.
        // entry ??= this.get(name);
        return context.getDescriptor(entry, dest, name, this.path, this);
      });
    }
  }

  getDescriptor(path, context) {
    let obj = Cu.createObjectIn(context.cloneScope);

    let ns = context.schemaRoot.getNamespace(this.path.join("."));
    ns.injectInto(obj, context);

    // Only inject the namespace object if it isn't empty.
    if (Object.keys(obj).length) {
      return {
        descriptor: { value: obj },
      };
    }
  }

  keys() {
    this.init();
    return super.keys();
  }

  /** @returns {Generator<[string, Entry]>} */
  *entries() {
    for (let key of this.keys()) {
      yield [key, this.get(key)];
    }
  }

  get(key) {
    this.init();
    let value = super.get(key);

    // The initial values of lazily-initialized schema properties are
    // strings, pointing to the type of property, corresponding to one
    // of the entries in the `LOADERS` object.
    if (typeof value === "string") {
      value = this.initKey(key, value);
    }

    return value;
  }

  /**
   * Returns a Namespace object for the given namespace name. If a
   * namespace object with this name does not already exist, it is
   * created. If the name contains any '.' characters, namespaces are
   * recursively created, for each dot-separated component.
   *
   * @param {string} name
   *        The name of the sub-namespace to retrieve.
   * @param {boolean} [create = true]
   *        If true, create any intermediate namespaces which don't
   *        exist.
   *
   * @returns {Namespace}
   */
  getNamespace(name, create = true) {
    let subName;

    let idx = name.indexOf(".");
    if (idx > 0) {
      subName = name.slice(idx + 1);
      name = name.slice(0, idx);
    }

    let ns = super.get(name);
    if (!ns) {
      if (!create) {
        return null;
      }
      ns = new Namespace(this.root, name, this.path);
      this.set(name, ns);
    }

    if (subName) {
      return ns.getNamespace(subName);
    }
    return ns;
  }

  getOwnNamespace(name) {
    return this.getNamespace(name);
  }

  has(key) {
    this.init();
    return super.has(key);
  }
}

/**
 * A namespace which combines the children of an arbitrary number of
 * sub-namespaces.
 */
class Namespaces extends Namespace {
  constructor(root, name, path, namespaces) {
    super(root, name, path);

    this.namespaces = namespaces;
  }

  injectInto(obj, context) {
    for (let ns of this.namespaces) {
      ns.injectInto(obj, context);
    }
  }
}

/**
 * A root schema which combines the contents of an arbitrary number of base
 * schema roots.
 */
class SchemaRoots extends Namespaces {
  constructor(root, bases) {
    bases = bases.map(base => base.rootSchema || base);

    super(null, "", [], bases);

    this.root = root;
    this.bases = bases;
    this._namespaces = new Map();
  }

  _getNamespace(name, create) {
    let results = [];
    for (let root of this.bases) {
      let ns = root.getNamespace(name, create);
      if (ns) {
        results.push(ns);
      }
    }

    if (results.length == 1) {
      return results[0];
    }

    if (results.length) {
      return new Namespaces(this.root, name, name.split("."), results);
    }
    return null;
  }

  getNamespace(name, create) {
    let ns = this._namespaces.get(name);
    if (!ns) {
      ns = this._getNamespace(name, create);
      if (ns) {
        this._namespaces.set(name, ns);
      }
    }
    return ns;
  }

  *getNamespaces(name) {
    for (let root of this.bases) {
      yield* root.getNamespaces(name);
    }
  }
}

/**
 * A root schema namespace containing schema data which is isolated from data in
 * other schema roots. May extend a base namespace, in which case schemas in
 * this root may refer to types in a base, but not vice versa.
 *
 * @implements {SchemaInject}
 */
export class SchemaRoot extends Namespace {
  /**
   * @param {SchemaRoot|SchemaRoot[]} base
   *        A base schema root (or roots) from which to derive, or null.
   * @param {Map<string, Array|StructuredCloneHolder>} schemaJSON
   *        A map of schema URLs and corresponding JSON blobs from which to
   *        populate this root namespace.
   */
  constructor(base, schemaJSON) {
    super(null, "", []);

    if (Array.isArray(base)) {
      this.base = new SchemaRoots(this, base);
    } else {
      this.base = base;
    }

    this.root = this;
    this.schemaJSON = schemaJSON;
  }

  *getNamespaces(path) {
    let name = path.join(".");

    let ns = this.getNamespace(name, false);
    if (ns) {
      yield ns;
    }

    if (this.base) {
      yield* this.base.getNamespaces(name);
    }
  }

  /**
   * Returns the sub-namespace with the given name. If the given namespace
   * doesn't already exist, attempts to find it in the base SchemaRoot before
   * creating a new empty namespace.
   *
   * @param {string} name
   *        The namespace to retrieve.
   * @param {boolean} [create = true]
   *        If true, an empty namespace should be created if one does not
   *        already exist.
   * @returns {Namespace|null}
   */
  getNamespace(name, create = true) {
    let ns = super.getNamespace(name, false);
    if (ns) {
      return ns;
    }

    ns = this.base && this.base.getNamespace(name, false);
    if (ns) {
      return ns;
    }
    return create && super.getNamespace(name, create);
  }

  /**
   * Like getNamespace, but does not take the base SchemaRoot into account.
   *
   * @param {string} name
   *        The namespace to retrieve.
   * @returns {Namespace}
   */
  getOwnNamespace(name) {
    return super.getNamespace(name);
  }

  parseSchema(schema, path, extraProperties = []) {
    let allowedProperties = DEBUG && new Set(extraProperties);

    if ("choices" in schema) {
      return ChoiceType.parseSchema(this, schema, path, allowedProperties);
    } else if ("$ref" in schema) {
      return RefType.parseSchema(this, schema, path, allowedProperties);
    }

    let type = TYPES[schema.type];

    if (DEBUG) {
      allowedProperties.add("type");

      if (!("type" in schema)) {
        throw new Error(`Unexpected value for type: ${JSON.stringify(schema)}`);
      }

      if (!type) {
        throw new Error(`Unexpected type ${schema.type}`);
      }
    }

    return type.parseSchema(this, schema, path, allowedProperties);
  }

  parseSchemas() {
    for (let [key, schema] of this.schemaJSON.entries()) {
      try {
        if (StructuredCloneHolder.isInstance(schema)) {
          schema = schema.deserialize(globalThis, isParentProcess);

          // If we're in the parent process, we need to keep the
          // StructuredCloneHolder blob around in order to send to future child
          // processes. If we're in a child, we have no further use for it, so
          // just store the deserialized schema data in its place.
          if (!isParentProcess) {
            this.schemaJSON.set(key, schema);
          }
        }

        this.loadSchema(schema);
      } catch (e) {
        Cu.reportError(e);
      }
    }
  }

  loadSchema(json) {
    for (let namespace of json) {
      this.getOwnNamespace(namespace.namespace).addSchema(namespace);
    }
  }

  /**
   * Checks whether a given object has the necessary permissions to
   * expose the given namespace.
   *
   * @param {string} namespace
   *        The top-level namespace to check permissions for.
   * @param {object} wrapperFuncs
   *        Wrapper functions for the given context.
   * @param {Function} wrapperFuncs.hasPermission
   *        A function which, when given a string argument, returns true
   *        if the context has the given permission.
   * @returns {boolean}
   *        True if the context has permission for the given namespace.
   */
  checkPermissions(namespace, wrapperFuncs) {
    let ns = this.getNamespace(namespace);
    if (ns && ns.permissions) {
      return ns.permissions.some(perm => wrapperFuncs.hasPermission(perm));
    }
    return true;
  }

  /**
   * Inject registered extension APIs into `dest`.
   *
   * @param {object} dest The root namespace for the APIs.
   *     This object is usually exposed to extensions as "chrome" or "browser".
   * @param {InjectionContext} wrapperFuncs An implementation of the InjectionContext
   *     interface, which runs the actual functionality of the generated API.
   */
  inject(dest, wrapperFuncs) {
    let context = new InjectionContext(wrapperFuncs, this);

    this.injectInto(dest, context);
  }

  injectInto(dest, context) {
    // For schema graphs where multiple schema roots have the same base, don't
    // inject it more than once.

    if (!context.injectedRoots.has(this)) {
      context.injectedRoots.add(this);
      if (this.base) {
        this.base.injectInto(dest, context);
      }
      super.injectInto(dest, context);
    }
  }

  /**
   * Normalize `obj` according to the loaded schema for `typeName`.
   *
   * @param {object} obj The object to normalize against the schema.
   * @param {string} typeName The name in the format namespace.propertyname
   * @param {object} context An implementation of Context. Any validation errors
   *     are reported to the given context.
   * @returns {object} The normalized object.
   */
  normalize(obj, typeName, context) {
    let [namespaceName, prop] = typeName.split(".");
    let ns = this.getNamespace(namespaceName);
    let type = ns.get(prop);

    let result = type.normalize(obj, new Context(context));
    if (result.error) {
      return { error: forceString(result.error) };
    }
    return result;
  }
}

/**
 * @typedef {{ inject: typeof Schemas.inject }} SchemaInject
 *          Interface SchemaInject as used by SchemaApiManager,
 *          with the one method shared across Schemas and SchemaRoot.
 */
export var Schemas = {
  initialized: false,

  REVOKE: Symbol("@@revoke"),

  // Maps a schema URL to the JSON contained in that schema file. This
  // is useful for sending the JSON across processes.
  schemaJSON: new Map(),

  // A map of schema JSON which should be available in all content processes.
  contentSchemaJSON: new Map(),

  // A map of schema JSON which should only be available to extension processes.
  privilegedSchemaJSON: new Map(),

  _rootSchema: null,

  // A weakmap for the validation Context class instances given an extension
  // context (keyed by the extensin context instance).
  // This is used instead of the InjectionContext for webIDL API validation
  // and normalization (see Schemas.checkParameters).
  paramsValidationContexts: new DefaultWeakMap(
    extContext => new Context(extContext)
  ),

  /** @returns {SchemaRoot} */
  get rootSchema() {
    if (!this.initialized) {
      this.init();
    }
    if (!this._rootSchema) {
      this._rootSchema = new SchemaRoot(null, this.schemaJSON);
      this._rootSchema.parseSchemas();
    }
    return this._rootSchema;
  },

  getNamespace(name) {
    return this.rootSchema.getNamespace(name);
  },

  init() {
    if (this.initialized) {
      return;
    }
    this.initialized = true;

    if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
      let addSchemas = schemas => {
        for (let [key, value] of schemas.entries()) {
          this.schemaJSON.set(key, value);
        }
      };

      if (WebExtensionPolicy.isExtensionProcess || DEBUG) {
        addSchemas(Services.cpmm.sharedData.get(KEY_PRIVILEGED_SCHEMAS));
      }

      let schemas = Services.cpmm.sharedData.get(KEY_CONTENT_SCHEMAS);
      if (schemas) {
        addSchemas(schemas);
      }
    }
  },

  _loadCachedSchemasPromise: null,
  loadCachedSchemas() {
    if (!this._loadCachedSchemasPromise) {
      this._loadCachedSchemasPromise = lazy.StartupCache.schemas
        .getAll()
        .then(results => {
          return results;
        });
    }

    return this._loadCachedSchemasPromise;
  },

  addSchema(url, schema, content = false) {
    this.schemaJSON.set(url, schema);

    if (content) {
      this.contentSchemaJSON.set(url, schema);
    } else {
      this.privilegedSchemaJSON.set(url, schema);
    }

    if (this._rootSchema) {
      throw new Error("Schema loaded after root schema populated");
    }
  },

  updateSharedSchemas() {
    let { sharedData } = Services.ppmm;

    sharedData.set(KEY_CONTENT_SCHEMAS, this.contentSchemaJSON);
    sharedData.set(KEY_PRIVILEGED_SCHEMAS, this.privilegedSchemaJSON);
  },

  fetch(url) {
    return readJSONAndBlobbify(url);
  },

  processSchema(json) {
    return blobbify(json);
  },

  async load(url, content = false) {
    if (!isParentProcess) {
      return;
    }

    const startTime = Cu.now();
    let schemaCache = await this.loadCachedSchemas();
    const fromCache = schemaCache.has(url);

    let blob =
      schemaCache.get(url) ||
      (await lazy.StartupCache.schemas.get(url, readJSONAndBlobbify));

    if (!this.schemaJSON.has(url)) {
      this.addSchema(url, blob, content);
    }

    ChromeUtils.addProfilerMarker(
      "ExtensionSchemas",
      { startTime },
      `load ${url}, from cache: ${fromCache}`
    );
  },

  /**
   * Checks whether a given object has the necessary permissions to
   * expose the given namespace.
   *
   * @param {string} namespace
   *        The top-level namespace to check permissions for.
   * @param {object} wrapperFuncs
   *        Wrapper functions for the given context.
   * @param {Function} wrapperFuncs.hasPermission
   *        A function which, when given a string argument, returns true
   *        if the context has the given permission.
   * @returns {boolean}
   *        True if the context has permission for the given namespace.
   */
  checkPermissions(namespace, wrapperFuncs) {
    return this.rootSchema.checkPermissions(namespace, wrapperFuncs);
  },

  /**
   * Returns a sorted array of permission names for the given permission types.
   *
   * @param {Array} types An array of permission types, defaults to all permissions.
   * @returns {Array} sorted array of permission names
   */
  getPermissionNames(
    types = [
      "Permission",
      "OptionalPermission",
      "PermissionNoPrompt",
      "OptionalPermissionNoPrompt",
      "PermissionPrivileged",
    ]
  ) {
    const ns = this.getNamespace("manifest");
    let names = [];
    for (let typeName of types) {
      for (let choice of ns
        .get(typeName)
        .choices.filter(choice => choice.enumeration)) {
        names = names.concat(choice.enumeration);
      }
    }
    return names.sort();
  },

  exportLazyGetter,

  /**
   * Inject registered extension APIs into `dest`.
   *
   * @param {object} dest The root namespace for the APIs.
   *     This object is usually exposed to extensions as "chrome" or "browser".
   * @param {InjectionContext} wrapperFuncs An implementation of the InjectionContext
   *     interface, which runs the actual functionality of the generated API.
   */
  inject(dest, wrapperFuncs) {
    this.rootSchema.inject(dest, wrapperFuncs);
  },

  /**
   * Normalize `obj` according to the loaded schema for `typeName`.
   *
   * @param {object} obj The object to normalize against the schema.
   * @param {string} typeName The name in the format namespace.propertyname
   * @param {object} context An implementation of Context. Any validation errors
   *     are reported to the given context.
   * @returns {object} The normalized object.
   */
  normalize(obj, typeName, context) {
    return this.rootSchema.normalize(obj, typeName, context);
  },

  /**
   * Validate and normalize the arguments for an API request originated
   * from the webIDL API bindings.
   *
   * This provides for calls originating through WebIDL the parameters
   * validation and normalization guarantees that the ext-APINAMESPACE.js
   * scripts expects (what InjectionContext does for the regular bindings).
   *
   * @param {object}                   extContext
   * @param {mozIExtensionAPIRequest } apiRequest
   *
   * @returns {Array<any>} Normalized arguments array.
   */
  checkWebIDLRequestParameters(extContext, apiRequest) {
    const getSchemaForProperty = (schemaObj, propName, schemaPath) => {
      if (schemaObj instanceof Namespace) {
        return schemaObj?.get(propName);
      } else if (schemaObj instanceof SubModuleProperty) {
        for (const fun of schemaObj.targetType.functions) {
          if (fun.name === propName) {
            return fun;
          }
        }

        for (const fun of schemaObj.targetType.events) {
          if (fun.name === propName) {
            return fun;
          }
        }
      } else if (schemaObj instanceof Event) {
        return schemaObj;
      }

      const schemaPathType = schemaObj?.constructor.name;
      throw new Error(
        `API Schema for "${propName}" not found in ${schemaPath} (${schemaPath} type is ${schemaPathType})`
      );
    };
    const { requestType, apiNamespace, apiName } = apiRequest;

    let [ns, ...rest] = (
      ["addListener", "removeListener"].includes(requestType)
        ? `${apiNamespace}.${apiName}.${requestType}`
        : `${apiNamespace}.${apiName}`
    ).split(".");
    /** @type {Namespace|CallEntry} */
    let apiSchema = this.getNamespace(ns);

    // Keep track of the current schema path, populated while navigating the nested API schema
    // data and then used to include the full path to the API schema that is hitting unexpected
    // errors due to schema data not found or an unexpected schema type.
    let schemaPath = [ns];

    while (rest.length) {
      // Nested property as namespace (e.g. used for proxy.settings requests).
      if (!apiSchema) {
        throw new Error(`API Schema not found for ${schemaPath.join(".")}`);
      }

      let [propName, ...newRest] = rest;
      rest = newRest;

      apiSchema = getSchemaForProperty(
        apiSchema,
        propName,
        schemaPath.join(".")
      );
      schemaPath.push(propName);
    }

    if (!apiSchema) {
      throw new Error(`API Schema not found for ${schemaPath.join(".")}`);
    }

    if (!(apiSchema instanceof CallEntry)) {
      throw new Error(
        `Unexpected API Schema type for ${schemaPath.join(
          "."
        )} (${schemaPath.join(".")} type is ${apiSchema.constructor.name})`
      );
    }

    return apiSchema.checkParameters(
      apiRequest.args,
      this.paramsValidationContexts.get(extContext)
    );
  },
};
PK
!<�T+XX$modules/SearchEngineSelector.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
  SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logConsole", () => {
  return console.createInstance({
    prefix: "SearchEngineSelector",
    maxLogLevel: lazy.SearchUtils.loggingEnabled ? "Debug" : "Warn",
  });
});

/**
 * SearchEngineSelector parses the JSON configuration for
 * search engines and returns the applicable engines depending
 * on their region + locale.
 */
export class SearchEngineSelector {
  /**
   * @param {Function} listener
   *   A listener for configuration update changes.
   */
  constructor(listener) {
    this._remoteConfig = lazy.RemoteSettings(lazy.SearchUtils.SETTINGS_KEY);
    this._remoteConfigOverrides = lazy.RemoteSettings(
      lazy.SearchUtils.SETTINGS_OVERRIDES_KEY
    );
    this._listenerAdded = false;
    this._onConfigurationUpdated = this._onConfigurationUpdated.bind(this);
    this._onConfigurationOverridesUpdated =
      this._onConfigurationOverridesUpdated.bind(this);
    this._changeListener = listener;
  }

  /**
   * Resets the remote settings listeners.
   */
  reset() {
    if (this._listenerAdded) {
      this._remoteConfig.off("sync", this._onConfigurationUpdated);
      this._remoteConfigOverrides.off(
        "sync",
        this._onConfigurationOverridesUpdated
      );
      this._listenerAdded = false;
    }
  }

  /**
   * Handles getting the configuration from remote settings.
   *
   * @returns {object}
   *   The configuration data.
   */
  async getEngineConfiguration() {
    if (this._getConfigurationPromise) {
      return this._getConfigurationPromise;
    }

    this._getConfigurationPromise = Promise.all([
      this._getConfiguration(),
      this._getConfigurationOverrides(),
    ]);
    let remoteSettingsData = await this._getConfigurationPromise;
    this._configuration = remoteSettingsData[0];
    this._configurationOverrides = remoteSettingsData[1];
    delete this._getConfigurationPromise;

    if (!this._configuration?.length) {
      throw Components.Exception(
        "Failed to get engine data from Remote Settings",
        Cr.NS_ERROR_UNEXPECTED
      );
    }

    if (!this._listenerAdded) {
      this._remoteConfig.on("sync", this._onConfigurationUpdated);
      this._remoteConfigOverrides.on(
        "sync",
        this._onConfigurationOverridesUpdated
      );
      this._listenerAdded = true;
    }

    return this._configuration;
  }

  /**
   * Used by tests to get the configuration overrides.
   *
   * @returns {object}
   *   The engine overrides data.
   */
  async getEngineConfigurationOverrides() {
    await this.getEngineConfiguration();
    return this._configurationOverrides;
  }

  /**
   * Obtains the configuration from remote settings. This includes
   * verifying the signature of the record within the database.
   *
   * If the signature in the database is invalid, the database will be wiped
   * and the stored dump will be used, until the settings next update.
   *
   * Note that this may cause a network check of the certificate, but that
   * should generally be quick.
   *
   * @param {boolean} [firstTime]
   *   Internal boolean to indicate if this is the first time check or not.
   * @returns {Array}
   *   An array of objects in the database, or an empty array if none
   *   could be obtained.
   */
  async _getConfiguration(firstTime = true) {
    let result = [];
    let failed = false;
    try {
      result = await this._remoteConfig.get({
        order: "id",
      });
    } catch (ex) {
      lazy.logConsole.error(ex);
      failed = true;
    }
    if (!result.length) {
      lazy.logConsole.error("Received empty search configuration!");
      failed = true;
    }
    // If we failed, or the result is empty, try loading from the local dump.
    if (firstTime && failed) {
      await this._remoteConfig.db.clear();
      // Now call this again.
      return this._getConfiguration(false);
    }
    return result;
  }

  /**
   * Handles updating of the configuration. Note that the search service is
   * only updated after a period where the user is observed to be idle.
   *
   * @param {object} options
   *   The options object
   * @param {object} options.data
   *   The data to update
   * @param {Array} options.data.current
   *   The new configuration object
   */
  _onConfigurationUpdated({ data: { current } }) {
    this._configuration = current;
    lazy.logConsole.debug("Search configuration updated remotely");
    if (this._changeListener) {
      this._changeListener();
    }
  }

  /**
   * Handles updating of the configuration. Note that the search service is
   * only updated after a period where the user is observed to be idle.
   *
   * @param {object} options
   *   The options object
   * @param {object} options.data
   *   The data to update
   * @param {Array} options.data.current
   *   The new configuration object
   */
  _onConfigurationOverridesUpdated({ data: { current } }) {
    this._configurationOverrides = current;
    lazy.logConsole.debug("Search configuration overrides updated remotely");
    if (this._changeListener) {
      this._changeListener();
    }
  }

  /**
   * Obtains the configuration overrides from remote settings.
   *
   * @returns {Array}
   *   An array of objects in the database, or an empty array if none
   *   could be obtained.
   */
  async _getConfigurationOverrides() {
    let result = [];
    try {
      result = await this._remoteConfigOverrides.get();
    } catch (ex) {
      // This data is remote only, so we just return an empty array if it fails.
    }
    return result;
  }

  /**
   * @param {object} options
   *   The options object
   * @param {string} options.locale
   *   Users locale.
   * @param {string} options.region
   *   Users region.
   * @param {string} [options.channel]
   *   The update channel the application is running on.
   * @param {string} [options.distroID]
   *   The distribution ID of the application.
   * @param {string} [options.experiment]
   *   Any associated experiment id.
   * @param {string} [options.appName]
   *   The name of the application.
   * @param {string} [options.version]
   *   The version of the application.
   * @returns {object}
   *   An object with "engines" field, a sorted list of engines and
   *   optionally "privateDefault" which is an object containing the engine
   *   details for the engine which should be the default in Private Browsing mode.
   */
  async fetchEngineConfiguration({
    locale,
    region,
    channel = "default",
    distroID,
    experiment,
    appName = Services.appinfo.name ?? "",
    version = Services.appinfo.version ?? "",
  }) {
    if (!this._configuration) {
      await this.getEngineConfiguration();
    }

    lazy.logConsole.debug(
      `fetchEngineConfiguration ${locale}:${region}:${channel}:${distroID}:${experiment}:${appName}:${version}`
    );

    appName = appName.toLowerCase();
    version = version.toLowerCase();
    locale = locale.toLowerCase();
    region = region.toLowerCase();

    let engines = [];
    let defaultsConfig;
    let engineOrders;
    let userEnv = {
      appName,
      version,
      locale,
      region,
      channel,
      distroID,
      experiment,
    };

    for (let config of this._configuration) {
      if (config.recordType == "defaultEngines") {
        defaultsConfig = config;
      }

      if (config.recordType == "engineOrders") {
        engineOrders = config;
      }

      if (config.recordType !== "engine") {
        continue;
      }

      let variant = config.variants?.findLast(variant =>
        this.#matchesUserEnvironment(variant, userEnv)
      );

      if (!variant) {
        continue;
      }

      let subVariant = variant.subVariants?.findLast(subVariant =>
        this.#matchesUserEnvironment(subVariant, userEnv)
      );

      let engine = structuredClone(config.base);
      engine.identifier = config.identifier;
      engine = this.#deepCopyObject(engine, variant);

      if (subVariant) {
        engine = this.#deepCopyObject(engine, subVariant);
      }

      for (let override of this._configurationOverrides) {
        if (override.identifier == engine.identifier) {
          engine = this.#deepCopyObject(engine, override);
        }
      }

      engines.push(engine);
    }

    let { defaultEngine, privateDefault } = this.#defaultEngines(
      engines,
      defaultsConfig,
      userEnv
    );

    for (const orderData of engineOrders.orders) {
      let environment = orderData.environment;

      if (this.#matchesUserEnvironment({ environment }, userEnv)) {
        this.#setEngineOrders(engines, orderData.order);
      }
    }

    engines.sort(this._sort.bind(this, defaultEngine, privateDefault));

    let result = { engines };

    if (privateDefault) {
      result.privateDefault = privateDefault;
    }

    if (lazy.SearchUtils.loggingEnabled) {
      lazy.logConsole.debug(
        "fetchEngineConfiguration: " + result.engines.map(e => e.identifier)
      );
    }
    return result;
  }

  _sort(defaultEngine, defaultPrivateEngine, a, b) {
    return (
      this._sortIndex(b, defaultEngine, defaultPrivateEngine) -
      this._sortIndex(a, defaultEngine, defaultPrivateEngine)
    );
  }

  /**
   * Create an index order to ensure default (and backup default)
   * engines are ordered correctly.
   *
   * @param {object} obj
   *   Object representing the engine configuration.
   * @param {object} defaultEngine
   *   The default engine, for comparison to obj.
   * @param {object} defaultPrivateEngine
   *   The default private engine, for comparison to obj.
   * @returns {integer}
   *  Number indicating how this engine should be sorted.
   */
  _sortIndex(obj, defaultEngine, defaultPrivateEngine) {
    if (obj == defaultEngine) {
      return Number.MAX_SAFE_INTEGER;
    }
    if (obj == defaultPrivateEngine) {
      return Number.MAX_SAFE_INTEGER - 1;
    }
    return obj.orderHint || 0;
  }

  /**
   * Deep copies an object to the target object and ignores some keys.
   *
   * @param {object} target - Object to copy to.
   * @param {object} source - Object to copy from.
   * @returns {object} - The source object.
   */
  #deepCopyObject(target, source) {
    for (let key in source) {
      if (["environment"].includes(key)) {
        continue;
      }

      if (["subVariants"].includes(key)) {
        continue;
      }

      if (typeof source[key] == "object" && !Array.isArray(source[key])) {
        if (key in target) {
          this.#deepCopyObject(target[key], source[key]);
        } else {
          target[key] = structuredClone(source[key]);
        }
      } else {
        target[key] = structuredClone(source[key]);
      }
    }

    return target;
  }

  /**
   * Matches the user's environment against the engine config's environment.
   *
   * @param {object} config
   *   The config for the given base or variant engine.
   * @param {object} user
   *   The user's environment we use to match with the engine's environment.
   * @param {string} user.appName
   *   The name of the application.
   * @param {string} user.version
   *   The version of the application.
   * @param {string} user.locale
   *   The locale of the user.
   * @param {string} user.region
   *   The region of the user.
   * @param {string} user.channel
   *   The channel the application is running on.
   * @param {string} user.distroID
   *   The distribution ID of the application.
   * @param {string} user.experiment
   *   Any associated experiment id.
   * @returns {boolean}
   *   True if the engine config's environment matches the user's environment.
   */
  #matchesUserEnvironment(config, user = {}) {
    if ("experiment" in config.environment) {
      if (user.experiment != config.environment.experiment) {
        return false;
      }
    }

    if ("excludedDistributions" in config.environment) {
      if (config.environment.excludedDistributions.includes(user.distroID)) {
        return false;
      }
    }

    // Skip the optional flag for Desktop, it's a feature only on Android.
    if (config.optional) {
      return false;
    }

    return (
      this.#matchesRegionAndLocale(
        user.region,
        user.locale,
        config.environment
      ) &&
      this.#matchesDistribution(
        user.distroID,
        config.environment.distributions
      ) &&
      this.#matchesVersions(
        config.environment.minVersion,
        config.environment.maxVersion,
        user.version
      ) &&
      this.#matchesChannel(config.environment.channels, user.channel) &&
      this.#matchesApplication(config.environment.applications, user.appName) &&
      !this.#hasDeviceType(config.environment)
    );
  }

  /**
   * @param {string} userDistro
   *  The distribution from the user's environment.
   * @param {string[]} configDistro
   *  An array of distributions for the particular environment in the config.
   * @returns {boolean}
   *  True if the user's distribution is included in the config distribution
   *  list.
   */
  #matchesDistribution(userDistro, configDistro) {
    // If there's no distribution for this engineConfig, ignore the check.
    if (!configDistro) {
      return true;
    }

    return configDistro?.includes(userDistro);
  }

  /**
   * @param {string} min
   *  The minimum version supported.
   * @param {string} max
   *  The maximum version supported.
   * @param {string} userVersion
   *  The user's version.
   * @returns {boolean}
   *  True if the user's version is within the range of the min and max versions
   *  supported.
   */
  #matchesVersions(min, max, userVersion) {
    // If there's no versions for this engineConfig, ignore the check.
    if (!min && !max) {
      return true;
    }

    if (!userVersion) {
      return false;
    }

    if (min && !max) {
      return this.#isAboveOrEqualMin(userVersion, min);
    }

    if (!min && max) {
      return this.#isBelowOrEqualMax(userVersion, max);
    }

    return (
      this.#isAboveOrEqualMin(userVersion, min) &&
      this.#isBelowOrEqualMax(userVersion, max)
    );
  }

  #isAboveOrEqualMin(userVersion, min) {
    return Services.vc.compare(userVersion, min) >= 0;
  }

  #isBelowOrEqualMax(userVersion, max) {
    return Services.vc.compare(userVersion, max) <= 0;
  }

  /**
   * @param {string[]} configChannels
   *  Release channels such as nightly, beta, release, esr.
   * @param {string} userChannel
   *  The user's channel.
   * @returns {boolean}
   *  True if the user's channel is included in the config channels.
   */
  #matchesChannel(configChannels, userChannel) {
    // If there's no channels for this engineConfig, ignore the check.
    if (!configChannels) {
      return true;
    }

    return configChannels.includes(userChannel);
  }

  /**
   * @param {string[]} configApps
   *  The applications such as firefox, firefox-android, firefox-ios,
   *  focus-android, and focus-ios.
   * @param {string} userApp
   *  The user's application.
   * @returns {boolean}
   *  True if the user's application is included in the config applications.
   */
  #matchesApplication(configApps, userApp) {
    // If there's no config Applications for this engineConfig, ignore the check.
    if (!configApps) {
      return true;
    }

    return configApps.includes(userApp);
  }

  /**
   * Generally the device type option should only be used when the application
   * is selected to be on an android or iOS based product. However, we support
   * rejecting if this is non-empty in case of future requirements that we haven't
   * predicted.
   *
   * @param {object} environment
   *   An environment section from the engine configuration.
   * @returns {boolean}
   *   Returns true if there is a device type section and it is not empty.
   */
  #hasDeviceType(environment) {
    return !!environment.deviceType?.length;
  }

  /**
   * Determines whether the region and locale constraints in the config
   * environment  applies to a user given what region and locale they are using.
   *
   * @param {string} region
   *   The region the user is in.
   * @param {string} locale
   *   The language the user has configured.
   * @param {object} configEnv
   *   The environment of the engine configuration.
   * @returns {boolean}
   *   True if the user's region and locale matches the config's region and
   *   locale contraints. Otherwise false.
   */
  #matchesRegionAndLocale(region, locale, configEnv) {
    if (
      this.#doesConfigInclude(configEnv.excludedLocales, locale) ||
      this.#doesConfigInclude(configEnv.excludedRegions, region)
    ) {
      return false;
    }

    if (configEnv.allRegionsAndLocales) {
      return true;
    }

    // When none of the regions and locales are set. This implies its available
    // everywhere.
    if (
      !Object.hasOwn(configEnv, "allRegionsAndLocales") &&
      !Object.hasOwn(configEnv, "regions") &&
      !Object.hasOwn(configEnv, "locales")
    ) {
      return true;
    }

    if (
      this.#doesConfigInclude(configEnv?.locales, locale) &&
      this.#doesConfigInclude(configEnv?.regions, region)
    ) {
      return true;
    }

    if (
      this.#doesConfigInclude(configEnv?.locales, locale) &&
      !Object.hasOwn(configEnv, "regions")
    ) {
      return true;
    }

    if (
      this.#doesConfigInclude(configEnv?.regions, region) &&
      !Object.hasOwn(configEnv, "locales")
    ) {
      return true;
    }

    return false;
  }

  /**
   * This function converts the characters in the config to lowercase and
   * checks if the user's locale or region is included in config the
   * environment.
   *
   * @param {Array} configArray
   *   An Array of locales or regions from the config environment.
   * @param {string} compareItem
   *   The user's locale or region.
   * @returns {boolean}
   *   True if user's region or locale is found in the config environment.
   *   Otherwise false.
   */
  #doesConfigInclude(configArray, compareItem) {
    if (!configArray) {
      return false;
    }

    return configArray.find(
      configItem => configItem.toLowerCase() === compareItem
    );
  }

  /**
   * Gets the default engine and default private engine based on the user's
   * environment.
   *
   * @param {Array} engines
   *   An array that contains the engines for the user environment.
   * @param {object} defaultsConfig
   *   The defaultEngines record type from the search config.
   * @param {object} userEnv
   *   The user's environment.
   * @returns {object}
   *   An object with default engine and default private engine.
   */
  #defaultEngines(engines, defaultsConfig, userEnv) {
    let defaultEngine, privateDefault;

    for (let data of defaultsConfig.specificDefaults) {
      let environment = data.environment;

      if (this.#matchesUserEnvironment({ environment }, userEnv)) {
        defaultEngine = this.#findDefault(engines, data) ?? defaultEngine;
        privateDefault =
          this.#findDefault(engines, data, "private") ?? privateDefault;
      }
    }

    defaultEngine ??= this.#findGlobalDefault(engines, defaultsConfig);
    privateDefault ??= this.#findGlobalDefault(
      engines,
      defaultsConfig,
      "private"
    );

    return { defaultEngine, privateDefault };
  }

  /**
   * Finds the global default engine or global default private engine.
   *
   * @param {Array} engines
   *   The engines for the user environment.
   * @param {string} config
   *   The defaultEngines record from the config.
   * @param {string} [engineType]
   *   A string to identify default or default private.
   * @returns {object}
   *   The global default engine or global default private engine.
   */
  #findGlobalDefault(engines, config, engineType = "default") {
    let engine;
    if (config.globalDefault && engineType == "default") {
      engine = engines.find(e => e.identifier == config.globalDefault);
    }

    if (config.globalDefaultPrivate && engineType == "private") {
      engine = engines.find(e => e.identifier == config.globalDefaultPrivate);
    }

    return engine;
  }

  /**
   * Finds the default engine or default private engine from the list of
   * engines that match the user's environment.
   *
   * @param {Array} engines
   *   The engines for the user environment.
   * @param {string} config
   *   The specific defaults record that contains the default engine or default
   *   private engine identifer for the environment.
   * @param {string} [engineType]
   *   A string to identify default engine or default private engine.
   * @returns {object|undefined}
   *   The default engine or default private engine. Undefined if none can be
   *   found.
   */
  #findDefault(engines, config, engineType = "default") {
    let defaultMatch =
      engineType == "default" ? config.default : config.defaultPrivate;

    if (!defaultMatch) {
      return undefined;
    }

    return this.#findEngineWithMatch(engines, defaultMatch);
  }

  /**
   * Sets the orderHint number for the engines.
   *
   * @param {Array} engines
   *  The engines for the user environment.
   * @param {Array} orderedEngines
   *  The ordering of engines. Engines in the beginning of the list get a higher
   *  orderHint number.
   */
  #setEngineOrders(engines, orderedEngines) {
    let orderNumber = orderedEngines.length;

    for (const engine of orderedEngines) {
      let foundEngine = this.#findEngineWithMatch(engines, engine);
      if (foundEngine) {
        foundEngine.orderHint = orderNumber;
        orderNumber -= 1;
      }
    }
  }

  /**
   * Finds an engine with the given match.
   *
   * @param {object[]} engines
   *   An array of search engine configurations.
   * @param {string} match
   *   A string to match against the engine identifier. This will be an exact
   *   match, unless the string ends with `*`, in which case it will use a
   *   startsWith match.
   * @returns {object|undefined}
   */
  #findEngineWithMatch(engines, match) {
    if (match.endsWith("*")) {
      let matchNoStar = match.slice(0, -1);
      return engines.find(e => e.identifier.startsWith(matchNoStar));
    }
    return engines.find(e => e.identifier == match);
  }
}
PK
!<�%�-;�;�modules/SearchService.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint no-shadow: error, mozilla/no-aArgs: error */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  AppProvidedSearchEngine:
    "resource://gre/modules/AppProvidedSearchEngine.sys.mjs",
  AddonSearchEngine: "resource://gre/modules/AddonSearchEngine.sys.mjs",
  IgnoreLists: "resource://gre/modules/IgnoreLists.sys.mjs",
  loadAndParseOpenSearchEngine:
    "resource://gre/modules/OpenSearchLoader.sys.mjs",
  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
  OpenSearchEngine: "resource://gre/modules/OpenSearchEngine.sys.mjs",
  PolicySearchEngine: "resource://gre/modules/PolicySearchEngine.sys.mjs",
  Region: "resource://gre/modules/Region.sys.mjs",
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
  SearchEngine: "resource://gre/modules/SearchEngine.sys.mjs",
  SearchEngineSelector: "resource://gre/modules/SearchEngineSelector.sys.mjs",
  SearchSettings: "resource://gre/modules/SearchSettings.sys.mjs",
  SearchStaticData: "resource://gre/modules/SearchStaticData.sys.mjs",
  SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",
  UserSearchEngine: "resource://gre/modules/UserSearchEngine.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logConsole", () => {
  return console.createInstance({
    prefix: "SearchService",
    maxLogLevel: lazy.SearchUtils.loggingEnabled ? "Debug" : "Warn",
  });
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "timerManager",
  "@mozilla.org/updates/timer-manager;1",
  "nsIUpdateTimerManager"
);

/**
 * A reference to the handler for the default override allowlist.
 *
 * @type {SearchDefaultOverrideAllowlistHandler}
 */
ChromeUtils.defineLazyGetter(lazy, "defaultOverrideAllowlist", () => {
  return new SearchDefaultOverrideAllowlistHandler();
});

const TOPIC_LOCALES_CHANGE = "intl:app-locales-changed";
const QUIT_APPLICATION_TOPIC = "quit-application";

// The update timer for OpenSearch engines checks in once a day.
const OPENSEARCH_UPDATE_TIMER_TOPIC = "search-engine-update-timer";
const OPENSEARCH_UPDATE_TIMER_INTERVAL = 60 * 60 * 24;

// This is the amount of time we'll be idle for before applying any configuration
// changes.
const RECONFIG_IDLE_TIME_SEC = 5 * 60;

/**
 * A reason that is used in the change of default search engine event telemetry.
 * These are mutally exclusive.
 */
const REASON_CHANGE_MAP = new Map([
  // The cause of the change is unknown.
  [Ci.nsISearchService.CHANGE_REASON_UNKNOWN, "unknown"],
  // The user changed the default search engine via the options in the
  // preferences UI.
  [Ci.nsISearchService.CHANGE_REASON_USER, "user"],
  // The change resulted from the user toggling the "Use this search engine in
  // Private Windows" option in the preferences UI.
  [Ci.nsISearchService.CHANGE_REASON_USER_PRIVATE_SPLIT, "user_private_split"],
  // The user changed the default via keys (cmd/ctrl-up/down) in the separate
  // search bar.
  [Ci.nsISearchService.CHANGE_REASON_USER_SEARCHBAR, "user_searchbar"],
  // The user changed the default via context menu on the one-off buttons in the
  // separate search bar.
  [
    Ci.nsISearchService.CHANGE_REASON_USER_SEARCHBAR_CONTEXT,
    "user_searchbar_context",
  ],
  // An add-on requested the change of default on install, which was either
  // accepted automatically or by the user.
  [Ci.nsISearchService.CHANGE_REASON_ADDON_INSTALL, "addon-install"],
  // An add-on was uninstalled, which caused the engine to be uninstalled.
  [Ci.nsISearchService.CHANGE_REASON_ADDON_UNINSTALL, "addon-uninstall"],
  // A configuration update caused a change of default.
  [Ci.nsISearchService.CHANGE_REASON_CONFIG, "config"],
  // A locale update caused a change of default.
  [Ci.nsISearchService.CHANGE_REASON_LOCALE, "locale"],
  // A region update caused a change of default.
  [Ci.nsISearchService.CHANGE_REASON_REGION, "region"],
  // Turning on/off an experiment caused a change of default.
  [Ci.nsISearchService.CHANGE_REASON_EXPERIMENT, "experiment"],
  // An enterprise policy caused a change of default.
  [Ci.nsISearchService.CHANGE_REASON_ENTERPRISE, "enterprise"],
  // The UI Tour caused a change of default.
  [Ci.nsISearchService.CHANGE_REASON_UITOUR, "uitour"],
  // The engine updated.
  [Ci.nsISearchService.CHANGE_REASON_ENGINE_UPDATE, "engine-update"],
]);

/**
 * The ParseSubmissionResult contains getter methods that return attributes
 * about the parsed submission url.
 *
 * @implements {nsIParseSubmissionResult}
 */
class ParseSubmissionResult {
  constructor(engine, terms, termsParameterName) {
    this.#engine = engine;
    this.#terms = terms;
    this.#termsParameterName = termsParameterName;
  }

  get engine() {
    return this.#engine;
  }

  get terms() {
    return this.#terms;
  }

  get termsParameterName() {
    return this.#termsParameterName;
  }

  /**
   * The search engine associated with the URL passed in to
   * nsISearchEngine::parseSubmissionURL, or null if the URL does not represent
   * a search submission.
   *
   * @type {nsISearchEngine|null}
   */
  #engine;

  /**
   * String containing the sought terms. This can be an empty string in case no
   * terms were specified or the URL does not represent a search submission.
   *
   * @type {string}
   */
  #terms;

  /**
   * The name of the query parameter used by `engine` for queries. E.g. "q".
   *
   * @type {string}
   */
  #termsParameterName;

  QueryInterface = ChromeUtils.generateQI(["nsISearchParseSubmissionResult"]);
}

const gEmptyParseSubmissionResult = Object.freeze(
  new ParseSubmissionResult(null, "", "")
);

/**
 * The search service handles loading and maintaining of search engines. It will
 * also work out the default lists for each locale/region.
 *
 * @implements {nsISearchService}
 */
export class SearchService {
  constructor() {
    // this._engines is prefixed with _ rather than # because it is called from
    // a test.
    this._engines = new Map();
    this._settings = new lazy.SearchSettings(this);

    this.#defineLazyPreferenceGetters();
  }

  classID = Components.ID("{7319788a-fe93-4db3-9f39-818cf08f4256}");

  get defaultEngine() {
    this.#ensureInitialized();
    return this._getEngineDefault(false);
  }

  set defaultEngine(newEngine) {
    this.#ensureInitialized();
    this.#setEngineDefault(false, newEngine);
  }

  get defaultPrivateEngine() {
    this.#ensureInitialized();
    return this._getEngineDefault(this.#separatePrivateDefault);
  }

  set defaultPrivateEngine(newEngine) {
    this.#ensureInitialized();
    if (!this._separatePrivateDefaultPrefValue) {
      Services.prefs.setBoolPref(
        lazy.SearchUtils.BROWSER_SEARCH_PREF + "separatePrivateDefault",
        true
      );
    }
    this.#setEngineDefault(this.#separatePrivateDefault, newEngine);
  }

  async getDefault() {
    await this.init();
    return this.defaultEngine;
  }

  async setDefault(engine, changeSource) {
    await this.init();
    this.#setEngineDefault(false, engine, changeSource);
  }

  async getDefaultPrivate() {
    await this.init();
    return this.defaultPrivateEngine;
  }

  async setDefaultPrivate(engine, changeSource) {
    await this.init();
    if (!this._separatePrivateDefaultPrefValue) {
      Services.prefs.setBoolPref(
        lazy.SearchUtils.BROWSER_SEARCH_PREF + "separatePrivateDefault",
        true
      );
    }
    this.#setEngineDefault(this.#separatePrivateDefault, engine, changeSource);
  }

  /**
   * @returns {SearchEngine}
   *   The engine that is the default for this locale/region, ignoring any
   *   user changes to the default engine.
   */
  get appDefaultEngine() {
    return this.#appDefaultEngine();
  }

  /**
   * @returns {SearchEngine}
   *   The engine that is the default for this locale/region in private browsing
   *   mode, ignoring any user changes to the default engine.
   *   Note: if there is no default for this locale/region, then the non-private
   *   browsing engine will be returned.
   */
  get appPrivateDefaultEngine() {
    return this.#appDefaultEngine(this.#separatePrivateDefault);
  }

  /**
   * Determine whether initialization has been completed.
   *
   * Clients of the service can use this attribute to quickly determine whether
   * initialization is complete, and decide to trigger some immediate treatment,
   * to launch asynchronous initialization or to bailout.
   *
   * Note that this attribute does not indicate that initialization has
   * succeeded, use hasSuccessfullyInitialized() for that.
   *
   * @returns {boolean}
   *  |true | if the search service has finished its attempt to initialize and
   *          we have an outcome. It could have failed or succeeded during this
   *          process.
   *  |false| if initialization has not been triggered yet or initialization is
   *          still ongoing.
   */
  get isInitialized() {
    return (
      this.#initializationStatus == "success" ||
      this.#initializationStatus == "failed"
    );
  }

  /**
   * Determine whether initialization has been successfully completed.
   *
   * @returns {boolean}
   *  |true | if the search service has succesfully initialized.
   *  |false| if initialization has not been started yet, initialization is
   *          still ongoing or initializaiton has failed.
   */
  get hasSuccessfullyInitialized() {
    return this.#initializationStatus == "success";
  }

  /**
   * A promise that is resolved when initialization has finished. This does not
   * trigger initialization to begin.
   *
   * @returns {Promise}
   *   Resolved when initalization has successfully finished, and rejected if it
   *   has failed.
   */
  get promiseInitialized() {
    return this.#initDeferredPromise.promise;
  }

  getDefaultEngineInfo() {
    let [telemetryId, defaultSearchEngineData] = this.#getEngineInfo(
      this.defaultEngine
    );
    const result = {
      defaultSearchEngine: telemetryId,
      defaultSearchEngineData,
    };

    if (this.#separatePrivateDefault) {
      let [privateTelemetryId, defaultPrivateSearchEngineData] =
        this.#getEngineInfo(this.defaultPrivateEngine);
      result.defaultPrivateSearchEngine = privateTelemetryId;
      result.defaultPrivateSearchEngineData = defaultPrivateSearchEngineData;
    }

    return result;
  }

  /**
   * If possible, please call getEngineById() rather than getEngineByName()
   * because engines are stored as { id: object } in this._engine Map.
   *
   * Returns the engine associated with the name.
   *
   * @param {string} engineName
   *   The name of the engine.
   * @returns {SearchEngine}
   *   The associated engine if found, null otherwise.
   */
  getEngineByName(engineName) {
    this.#ensureInitialized();
    return this.#getEngineByName(engineName);
  }

  /**
   * Returns the engine associated with the name without initialization checks.
   *
   * @param {string} engineName
   *   The name of the engine.
   * @returns {SearchEngine}
   *   The associated engine if found, null otherwise.
   */
  #getEngineByName(engineName) {
    for (let engine of this._engines.values()) {
      if (engine.name == engineName) {
        return engine;
      }
    }

    return null;
  }

  /**
   * Returns the engine associated with the id.
   *
   * @param {string} engineId
   *   The id of the engine.
   * @returns {SearchEngine}
   *   The associated engine if found, null otherwise.
   */
  getEngineById(engineId) {
    this.#ensureInitialized();
    return this._engines.get(engineId) || null;
  }

  async getEngineByAlias(alias) {
    await this.init();
    for (var engine of this._engines.values()) {
      if (engine && engine.aliases.includes(alias)) {
        return engine;
      }
    }
    return null;
  }

  async getEngines() {
    await this.init();
    lazy.logConsole.debug("getEngines: getting all engines");
    return this.#sortedEngines;
  }

  async getVisibleEngines() {
    await this.init(true);
    lazy.logConsole.debug("getVisibleEngines: getting all visible engines");
    return this.#sortedVisibleEngines;
  }

  async getAppProvidedEngines() {
    await this.init();

    return lazy.SearchUtils.sortEnginesByDefaults({
      engines: this.#sortedEngines.filter(e => e.isAppProvided),
      appDefaultEngine: this.appDefaultEngine,
      appPrivateDefaultEngine: this.appPrivateDefaultEngine,
    });
  }

  async getEnginesByExtensionID(extensionID) {
    await this.init();
    return this.#getEnginesByExtensionID(extensionID);
  }

  /**
   * This function calls #init to start initialization when it has not been
   * started yet. Otherwise, it returns the pending promise.
   *
   * @returns {Promise}
   *   Returns the pending Promise when #init has started but not yet finished.
   *   | Resolved | when initialization has successfully finished.
   *   | Rejected | when initialization has failed.
   */
  async init() {
    if (["started", "success", "failed"].includes(this.#initializationStatus)) {
      return this.promiseInitialized;
    }
    this.#initializationStatus = "started";
    return this.#init();
  }

  /**
   * Runs background checks for the search service. This is called from
   * BrowserGlue and may be run once per session if the user is idle for
   * long enough.
   */
  async runBackgroundChecks() {
    await this.init();
    await this.#migrateLegacyEngines();
    await this.#checkWebExtensionEngines();
    await this.#addOpenSearchTelemetry();
  }

  /**
   * Test only - reset SearchService data. Ideally this should be replaced
   */
  reset() {
    this.#initializationStatus = "not initialized";
    this.#initDeferredPromise = Promise.withResolvers();
    this.#startupExtensions = new Set();
    this._engines.clear();
    this._cachedSortedEngines = null;
    this.#currentEngine = null;
    this.#currentPrivateEngine = null;
    this._searchDefault = null;
    this.#searchPrivateDefault = null;
    this.#maybeReloadDebounce = false;
    this._settings._batchTask?.disarm();
    if (this.#engineSelector) {
      this.#engineSelector.reset();
      this.#engineSelector = null;
    }
  }

  // Test-only function to set SearchService initialization status
  forceInitializationStatusForTests(status) {
    this.#initializationStatus = status;
  }

  /**
   * Test only variable to indicate an error should occur during
   * search service initialization.
   *
   * @type {string}
   */
  errorToThrowInTest = null;

  // Test-only function to reset just the engine selector so that it can
  // load a different configuration.
  resetEngineSelector() {
    this.#engineSelector = new lazy.SearchEngineSelector(
      this.#handleConfigurationUpdated.bind(this)
    );
  }

  resetToAppDefaultEngine() {
    let appDefaultEngine = this.appDefaultEngine;
    appDefaultEngine.hidden = false;
    this.defaultEngine = appDefaultEngine;
  }

  async maybeSetAndOverrideDefault(extension) {
    let searchProvider =
      extension.manifest.chrome_settings_overrides.search_provider;
    let engine = this.getEngineByName(searchProvider.name);
    if (!engine || !engine.isAppProvided || engine.hidden) {
      // If the engine is not application provided, then we shouldn't simply
      // set default to it.
      // If the engine is application provided, but hidden, then we don't
      // switch to it, nor do we try to install it.
      return {
        canChangeToAppProvided: false,
        canInstallEngine: !engine?.hidden,
      };
    }

    if (
      extension.startupReason === "ADDON_INSTALL" ||
      extension.startupReason === "ADDON_ENABLE"
    ) {
      // Don't allow an extension to set the default if it is already the default.
      if (this.defaultEngine.name == searchProvider.name) {
        return {
          canChangeToAppProvided: false,
          canInstallEngine: false,
        };
      }
      if (
        !(await lazy.defaultOverrideAllowlist.canOverride(extension, engine.id))
      ) {
        lazy.logConsole.debug(
          "Allowing default engine to be set to app-provided.",
          extension.id
        );
        // We don't allow overriding the engine in this case, but we can allow
        // the extension to change the default engine.
        return {
          canChangeToAppProvided: true,
          canInstallEngine: false,
        };
      }
      // We're ok to override.
      engine.overrideWithEngine({ extension });
      lazy.logConsole.debug(
        "Allowing default engine to be set to app-provided and overridden.",
        extension.id
      );
      return {
        canChangeToAppProvided: true,
        canInstallEngine: false,
      };
    }

    if (
      engine.getAttr("overriddenBy") == extension.id &&
      (await lazy.defaultOverrideAllowlist.canOverride(extension, engine.id))
    ) {
      engine.overrideWithEngine({ extension });
      lazy.logConsole.debug(
        "Re-enabling overriding of core extension by",
        extension.id
      );
      return {
        canChangeToAppProvided: true,
        canInstallEngine: false,
      };
    }

    return {
      canChangeToAppProvided: false,
      canInstallEngine: false,
    };
  }

  /**
   * Adds a search engine that is specified from enterprise policies.
   *
   * @param {object} details
   *   An object that matches the `SearchEngines` policy schema.
   * @param {object} [settings]
   *   The saved settings for the user.
   * @see browser/components/enterprisepolicies/schemas/policies-schema.json
   */
  async #addPolicyEngine(details, settings) {
    let newEngine = new lazy.PolicySearchEngine({ details, settings });
    lazy.logConsole.debug("Adding Policy Engine:", newEngine.name);
    this.#addEngineToStore(newEngine);
  }

  /**
   * Adds a search engine that is specified by the user.
   *
   * @param {string} name
   *   The name of the search engine
   * @param {string} url
   *   The url that the search engine uses for searches
   * @param {string} alias
   *   An alias for the search engine
   */
  async addUserEngine(name, url, alias) {
    await this.init();

    let newEngine = new lazy.UserSearchEngine({
      details: { name, url, alias },
    });
    lazy.logConsole.debug(`Adding ${newEngine.name}`);
    this.#addEngineToStore(newEngine);
  }

  /**
   * Called from the AddonManager when it either installs a new
   * extension containing a search engine definition or an upgrade
   * to an existing one.
   *
   * @param {object} extension
   *   An Extension object containing data about the extension.
   */
  async addEnginesFromExtension(extension) {
    // Treat add-on upgrade and downgrades the same - either way, the search
    // engine gets updated, not added. Generally, we don't expect a downgrade,
    // but just in case...
    if (
      extension.startupReason == "ADDON_UPGRADE" ||
      extension.startupReason == "ADDON_DOWNGRADE"
    ) {
      // Bug 1679861 An a upgrade or downgrade could be adding a search engine
      // that was not in a prior version, or the addon may have been blocklisted.
      // In either case, there will not be an existing engine.
      let existing = await this.#upgradeExtensionEngine(extension);
      if (existing?.length) {
        return;
      }
    }

    if (extension.isAppProvided) {
      // TODO: Bug 1885953 - We should store the WebExtension references and
      // remove them on idle.
      lazy.logConsole.debug(
        "addEnginesFromExtension: Ignoring old app provided WebExtension",
        extension.id
      );
      return;
    }
    lazy.logConsole.debug("addEnginesFromExtension:", extension.id);

    // If we haven't started the SearchService yet, store this extension
    // to install in SearchService.init().
    if (!this.isInitialized) {
      this.#startupExtensions.add(extension);
      return;
    }

    await this.#createAndAddAddonEngine({
      extension,
      locale: lazy.SearchUtils.DEFAULT_TAG,
    });
  }

  async addOpenSearchEngine(engineURL, iconURL) {
    lazy.logConsole.debug("addOpenSearchEngine: Adding", engineURL);
    await this.init();
    let engine;
    try {
      let engineData = await lazy.loadAndParseOpenSearchEngine(
        Services.io.newURI(engineURL)
      );
      engine = new lazy.OpenSearchEngine({ engineData });
      engine._setIcon(iconURL, false);
    } catch (ex) {
      throw Components.Exception(
        "addEngine: Error adding engine:\n" + ex,
        ex.result || Cr.NS_ERROR_FAILURE
      );
    }
    this.#addEngineToStore(engine);
    this.#maybeStartOpenSearchUpdateTimer();
    return engine;
  }

  async removeWebExtensionEngine(id) {
    if (!this.isInitialized) {
      lazy.logConsole.debug(
        "Delaying removing extension engine on startup:",
        id
      );
      this.#startupRemovedExtensions.add(id);
      return;
    }

    lazy.logConsole.debug("removeWebExtensionEngine:", id);
    for (let engine of this.#getEnginesByExtensionID(id)) {
      await this.removeEngine(engine);
    }
  }

  async removeEngine(engine) {
    await this.init();
    if (!engine) {
      throw Components.Exception(
        "no engine passed to removeEngine!",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    var engineToRemove = null;
    for (var e of this._engines.values()) {
      if (engine.wrappedJSObject == e) {
        engineToRemove = e;
      }
    }

    if (!engineToRemove) {
      throw Components.Exception(
        "removeEngine: Can't find engine to remove!",
        Cr.NS_ERROR_FILE_NOT_FOUND
      );
    }

    engineToRemove.pendingRemoval = true;

    if (engineToRemove == this.defaultEngine) {
      this.#findAndSetNewDefaultEngine({
        privateMode: false,
      });
    }

    // Bug 1575649 - We can't just check the default private engine here when
    // we're not using separate, as that re-checks the normal default, and
    // triggers update of the default search engine, which messes up various
    // tests. Really, removeEngine should always commit to updating any
    // changed defaults.
    if (
      this.#separatePrivateDefault &&
      engineToRemove == this.defaultPrivateEngine
    ) {
      this.#findAndSetNewDefaultEngine({
        privateMode: true,
      });
    }

    if (engineToRemove.inMemory) {
      // Just hide it (the "hidden" setter will notify) and remove its alias to
      // avoid future conflicts with other engines.
      engineToRemove.hidden = true;
      engineToRemove.alias = null;
      engineToRemove.pendingRemoval = false;
    } else {
      // Remove the engine file from disk if we had a legacy file in the profile.
      if (engineToRemove._filePath) {
        let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
        file.persistentDescriptor = engineToRemove._filePath;
        if (file.exists()) {
          file.remove(false);
        }
        engineToRemove._filePath = null;
      }
      this.#internalRemoveEngine(engineToRemove);

      // Since we removed an engine, we may need to update the preferences.
      if (!this.#dontSetUseSavedOrder) {
        this.#saveSortedEngineList();
      }
    }
    lazy.SearchUtils.notifyAction(
      engineToRemove,
      lazy.SearchUtils.MODIFIED_TYPE.REMOVED
    );
  }

  async moveEngine(engine, newIndex) {
    await this.init();
    if (newIndex > this.#sortedEngines.length || newIndex < 0) {
      throw Components.Exception(
        "moveEngine: Index out of bounds!",
        Cr.NS_ERROR_INVALID_ARG
      );
    }
    if (
      !(engine instanceof Ci.nsISearchEngine) &&
      !(engine instanceof lazy.SearchEngine)
    ) {
      throw Components.Exception(
        "moveEngine: Invalid engine passed to moveEngine!",
        Cr.NS_ERROR_INVALID_ARG
      );
    }
    if (engine.hidden) {
      throw Components.Exception(
        "moveEngine: Can't move a hidden engine!",
        Cr.NS_ERROR_FAILURE
      );
    }

    engine = engine.wrappedJSObject;

    var currentIndex = this.#sortedEngines.indexOf(engine);
    if (currentIndex == -1) {
      throw Components.Exception(
        "moveEngine: Can't find engine to move!",
        Cr.NS_ERROR_UNEXPECTED
      );
    }

    // Our callers only take into account non-hidden engines when calculating
    // newIndex, but we need to move it in the array of all engines, so we
    // need to adjust newIndex accordingly. To do this, we count the number
    // of hidden engines in the list before the engine that we're taking the
    // place of. We do this by first finding newIndexEngine (the engine that
    // we were supposed to replace) and then iterating through the complete
    // engine list until we reach it, increasing newIndex for each hidden
    // engine we find on our way there.
    //
    // This could be further simplified by having our caller pass in
    // newIndexEngine directly instead of newIndex.
    var newIndexEngine = this.#sortedVisibleEngines[newIndex];
    if (!newIndexEngine) {
      throw Components.Exception(
        "moveEngine: Can't find engine to replace!",
        Cr.NS_ERROR_UNEXPECTED
      );
    }

    for (var i = 0; i < this.#sortedEngines.length; ++i) {
      if (newIndexEngine == this.#sortedEngines[i]) {
        break;
      }
      if (this.#sortedEngines[i].hidden) {
        newIndex++;
      }
    }

    if (currentIndex == newIndex) {
      return;
    } // nothing to do!

    // Move the engine
    var movedEngine = this._cachedSortedEngines.splice(currentIndex, 1)[0];
    this._cachedSortedEngines.splice(newIndex, 0, movedEngine);

    lazy.SearchUtils.notifyAction(
      engine,
      lazy.SearchUtils.MODIFIED_TYPE.CHANGED
    );

    // Since we moved an engine, we need to update the preferences.
    this.#saveSortedEngineList();
  }

  restoreDefaultEngines() {
    this.#ensureInitialized();
    for (let e of this._engines.values()) {
      // Unhide all default engines
      if (e.hidden && e.isAppProvided) {
        e.hidden = false;
      }
    }
  }

  parseSubmissionURL(url) {
    if (!this.hasSuccessfullyInitialized) {
      // If search is not initialized or failed initializing, do nothing.
      // This allows us to use this function early in telemetry.
      // The only other consumer of this (places) uses it much later.
      return gEmptyParseSubmissionResult;
    }

    if (!this.#parseSubmissionMap) {
      this.#buildParseSubmissionMap();
    }

    // Extract the elements of the provided URL first.
    let soughtKey, soughtQuery;
    try {
      let soughtUrl = Services.io.newURI(url);

      // Exclude any URL that is not HTTP or HTTPS from the beginning.
      if (soughtUrl.schemeIs("http") && soughtUrl.schemeIs("https")) {
        return gEmptyParseSubmissionResult;
      }

      // Reading these URL properties may fail and raise an exception.
      soughtKey = soughtUrl.host + soughtUrl.filePath.toLowerCase();
      soughtQuery = soughtUrl.query;
    } catch (ex) {
      // Errors while parsing the URL or accessing the properties are not fatal.
      return gEmptyParseSubmissionResult;
    }

    // Look up the domain and path in the map to identify the search engine.
    let mapEntry = this.#parseSubmissionMap.get(soughtKey);
    if (!mapEntry) {
      return gEmptyParseSubmissionResult;
    }

    // Extract the search terms from the parameter, for example "caff%C3%A8"
    // from the URL "https://www.google.com/search?q=caff%C3%A8&client=firefox".
    // We cannot use `URLSearchParams` here as the terms might not be
    // encoded in UTF-8.
    let encodedTerms = null;
    for (let param of soughtQuery.split("&")) {
      let equalPos = param.indexOf("=");
      if (
        equalPos != -1 &&
        param.substr(0, equalPos) == mapEntry.termsParameterName
      ) {
        // This is the parameter we are looking for.
        encodedTerms = param.substr(equalPos + 1);
        break;
      }
    }
    if (encodedTerms === null) {
      return gEmptyParseSubmissionResult;
    }

    // Decode the terms using the charset defined in the search engine.
    let terms;
    try {
      terms = Services.textToSubURI.UnEscapeAndConvert(
        mapEntry.engine.queryCharset,
        encodedTerms.replace(/\+/g, " ")
      );
    } catch (ex) {
      // Decoding errors will cause this match to be ignored.
      return gEmptyParseSubmissionResult;
    }

    return new ParseSubmissionResult(
      mapEntry.engine,
      terms,
      mapEntry.termsParameterName
    );
  }

  getAlternateDomains(domain) {
    return lazy.SearchStaticData.getAlternateDomains(domain);
  }

  /**
   * This is a nsITimerCallback for the timerManager notification that is
   * registered for handling updates to search engines. Only OpenSearch engines
   * have these updates and hence, only those are handled here.
   */
  async notify() {
    lazy.logConsole.debug("notify: checking for updates");

    // Walk the engine list, looking for engines whose update time has expired.
    for (let engine of this._engines.values()) {
      if (!(engine instanceof lazy.OpenSearchEngine)) {
        continue;
      }
      await engine.maybeUpdate();
    }
  }

  #currentEngine;
  #currentPrivateEngine;
  #queuedIdle;

  /**
   * A deferred promise that is resolved when initialization has finished.
   *
   * Resolved when initalization has successfully finished, and rejected if it
   * has failed.
   *
   * @type {Promise}
   */
  #initDeferredPromise = Promise.withResolvers();

  /**
   * Indicates if initialization has started, failed, succeeded or has not
   * started yet.
   *
   * These are the statuses:
   *   "not initialized" - The SearchService has not started initialization.
   *   "started" - The SearchService has started initializaiton.
   *   "success" - The SearchService successfully completed initialization.
   *   "failed" - The SearchService failed during initialization.
   *
   * @type {string}
   */
  #initializationStatus = "not initialized";

  /**
   * Indicates if we're already waiting for maybeReloadEngines to be called.
   *
   * @type {boolean}
   */
  #maybeReloadDebounce = false;

  /**
   * Indicates if we're currently in maybeReloadEngines.
   *
   * This is prefixed with _ rather than # because it is
   * called in a test.
   *
   * @type {boolean}
   */
  _reloadingEngines = false;

  /**
   * The engine selector singleton that is managing the engine configuration.
   *
   * @type {SearchEngineSelector|null}
   */
  #engineSelector = null;

  /**
   * Various search engines may be ignored if their submission urls contain a
   * string that is in the list. The list is controlled via remote settings.
   *
   * @type {Array}
   */
  #submissionURLIgnoreList = [];

  /**
   * Various search engines may be ignored if their load path is contained
   * in this list. The list is controlled via remote settings.
   *
   * @type {Array}
   */
  #loadPathIgnoreList = [];

  /**
   * A map of engine display names to `SearchEngine`.
   *
   * @type {Map<string, object>|null}
   */
  _engines = null;

  /**
   * An array of engine short names sorted into display order.
   *
   * @type {Array}
   */
  _cachedSortedEngines = null;

  /**
   * A flag to prevent setting of useSavedOrder when there's non-user
   * activity happening.
   *
   * @type {boolean}
   */
  #dontSetUseSavedOrder = false;

  /**
   * An object containing the id of the AppProvidedSearchEngine for the default
   * engine, as suggested by the configuration.
   * For the legacy configuration, this is the user visible name.
   *
   * This is prefixed with _ rather than # because it is
   * called in a test.
   *
   * @type {object}
   */
  _searchDefault = null;

  /**
   * An object containing the id of the AppProvidedSearchEngine for the default
   * engine for private browsing mode, as suggested by the configuration.
   * For the legacy configuration, this is the user visible name.
   *
   * @type {object}
   */
  #searchPrivateDefault = null;

  /**
   * A Set of installed search extensions reported by AddonManager
   * startup before SearchSevice has started. Will be installed
   * during init(). Does not contain application provided engines.
   *
   * @type {Set<object>}
   */
  #startupExtensions = new Set();

  /**
   * A Set of removed search extensions reported by AddonManager
   * startup before SearchSevice has started. Will be removed
   * during init().
   *
   * @type {Set<object>}
   */
  #startupRemovedExtensions = new Set();

  /**
   * Used in #parseSubmissionMap
   *
   * @typedef {object} submissionMapEntry
   * @property {nsISearchEngine} engine
   *   The search engine.
   * @property {string} termsParameterName
   *   The search term parameter name.
   */

  /**
   * This map is built lazily after the available search engines change.  It
   * allows quick parsing of an URL representing a search submission into the
   * search engine name and original terms.
   *
   * The keys are strings containing the domain name and lowercase path of the
   * engine submission, for example "www.google.com/search".
   *
   * @type {Map<string, submissionMapEntry>|null}
   */
  #parseSubmissionMap = null;

  /**
   * Keep track of observers have been added.
   *
   * @type {boolean}
   */
  #observersAdded = false;

  /**
   * Keeps track to see if the OpenSearch update timer has been started or not.
   *
   * @type {boolean}
   */
  #openSearchUpdateTimerStarted = false;

  get #sortedEngines() {
    if (!this._cachedSortedEngines) {
      return this.#buildSortedEngineList();
    }
    return this._cachedSortedEngines;
  }
  /**
   * This reflects the combined values of the prefs for enabling the separate
   * private default UI, and for the user choosing a separate private engine.
   * If either one is disabled, then we don't enable the separate private default.
   *
   * @returns {boolean}
   */
  get #separatePrivateDefault() {
    return (
      this._separatePrivateDefaultPrefValue &&
      this._separatePrivateDefaultEnabledPrefValue
    );
  }

  #getEnginesByExtensionID(extensionID) {
    lazy.logConsole.debug("getEngines: getting all engines for", extensionID);
    var engines = this.#sortedEngines.filter(function (engine) {
      return engine._extensionID == extensionID;
    });
    return engines;
  }

  /**
   * Returns the engine associated with the WebExtension details.
   *
   * @param {object} details
   *   Details of the WebExtension.
   * @param {string} details.id
   *   The WebExtension ID
   * @param {string} details.locale
   *   The WebExtension locale
   * @returns {nsISearchEngine|null}
   *   The found engine, or null if no engine matched.
   */
  #getEngineByWebExtensionDetails(details) {
    for (const engine of this._engines.values()) {
      if (
        engine._extensionID == details.id &&
        engine._locale == details.locale
      ) {
        return engine;
      }
    }
    return null;
  }

  /**
   * Helper function to get the current default engine.
   *
   * This is prefixed with _ rather than # because it is
   * called in test_remove_engine_notification_box.js
   *
   * @param {boolean} privateMode
   *   If true, returns the default engine for private browsing mode, otherwise
   *   the default engine for the normal mode. Note, this function does not
   *   check the "separatePrivateDefault" preference - that is up to the caller.
   * @returns {nsISearchEngine|null}
   *   The appropriate search engine, or null if one could not be determined.
   */
  _getEngineDefault(privateMode) {
    let currentEngine = privateMode
      ? this.#currentPrivateEngine
      : this.#currentEngine;

    if (currentEngine && !currentEngine.hidden) {
      return currentEngine;
    }

    // No default loaded, so find it from settings.
    const attributeName = privateMode
      ? "privateDefaultEngineId"
      : "defaultEngineId";

    let engineId = this._settings.getMetaDataAttribute(attributeName);
    let engine = this._engines.get(engineId) || null;
    if (
      engine &&
      this._settings.getVerifiedMetaDataAttribute(
        attributeName,
        engine.isAppProvided
      )
    ) {
      if (privateMode) {
        this.#currentPrivateEngine = engine;
      } else {
        this.#currentEngine = engine;
      }
    }
    if (!engineId) {
      if (privateMode) {
        this.#currentPrivateEngine = this.appPrivateDefaultEngine;
      } else {
        this.#currentEngine = this.appDefaultEngine;
      }
    }

    currentEngine = privateMode
      ? this.#currentPrivateEngine
      : this.#currentEngine;
    if (currentEngine && !currentEngine.hidden) {
      return currentEngine;
    }
    // No default in settings or it is hidden, so find the new default.
    return this.#findAndSetNewDefaultEngine({ privateMode });
  }

  /**
   * If initialization has not been completed yet, perform synchronous
   * initialization.
   * Throws in case of initialization error.
   */
  #ensureInitialized() {
    if (this.#initializationStatus === "success") {
      return;
    }

    if (this.#initializationStatus === "failed") {
      throw new Error("SearchService failed while it was initializing.");
    }

    let err = new Error(
      "Something tried to use the search service before it finished " +
        "initializing. Please examine the stack trace to figure out what and " +
        "where to fix it:\n"
    );
    err.message += err.stack;
    throw err;
  }

  /**
   * Define lazy preference getters for separate private default engine in
   * private browsing mode.
   */
  #defineLazyPreferenceGetters() {
    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "_separatePrivateDefaultPrefValue",
      lazy.SearchUtils.BROWSER_SEARCH_PREF + "separatePrivateDefault",
      false,
      this.#onSeparateDefaultPrefChanged.bind(this)
    );

    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "_separatePrivateDefaultEnabledPrefValue",
      lazy.SearchUtils.BROWSER_SEARCH_PREF +
        "separatePrivateDefault.ui.enabled",
      false,
      this.#onSeparateDefaultPrefChanged.bind(this)
    );

    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "separatePrivateDefaultUrlbarResultEnabled",
      lazy.SearchUtils.BROWSER_SEARCH_PREF +
        "separatePrivateDefault.urlbarResult.enabled",
      false
    );
  }

  /**
   * This function adds observers, retrieves the search engine ignore list, and
   * initializes the Search Engine Selector prior to doing the core tasks of
   * search service initialization.
   *
   */
  #doPreInitWork() {
    // We need to catch the region being updated during initialization so we
    // start listening straight away.
    Services.obs.addObserver(this, lazy.Region.REGION_TOPIC);

    this.#getIgnoreListAndSubscribe().catch(ex =>
      console.error(ex, "Search Service could not get the ignore list.")
    );

    this.#engineSelector = new lazy.SearchEngineSelector(
      this.#handleConfigurationUpdated.bind(this)
    );
  }

  /**
   * This function fetches information to load search engines and ensures the
   * search service is in the correct state for external callers to interact
   * with it.
   *
   * This function sets #initDeferredPromise to resolve or reject.
   *   | Resolved | when initalization has successfully finished.
   *   | Rejected | when initialization has failed.
   */
  async #init() {
    lazy.logConsole.debug("init");

    const timerId = Glean.searchService.startupTime.start();

    this.#doPreInitWork();

    let initSection;
    try {
      initSection = "Settings";
      this.#maybeThrowErrorInTest(initSection);
      const settings = await this._settings.get();

      initSection = "FetchEngines";
      this.#maybeThrowErrorInTest(initSection);
      const { engines, privateDefault } =
        await this._fetchEngineSelectorEngines();

      initSection = "LoadEngines";
      this.#maybeThrowErrorInTest(initSection);
      await this.#loadEngines(settings, engines, privateDefault);
    } catch (ex) {
      Glean.searchService.initializationStatus[`failed${initSection}`].add();
      Glean.searchService.startupTime.cancel(timerId);

      lazy.logConsole.error("#init: failure initializing search:", ex);
      this.#initializationStatus = "failed";
      this.#initDeferredPromise.reject(ex);

      throw ex;
    }

    // If we've got this far, but the application is now shutting down,
    // then we need to abandon any further work, especially not writing
    // the settings. We do this, because the add-on manager has also
    // started shutting down and as a result, we might have an incomplete
    // picture of the installed search engines. Writing the settings at
    // this stage would potentially mean the user would loose their engine
    // data.
    // We will however, rebuild the settings on next start up if we detect
    // it is necessary.
    if (Services.startup.shuttingDown) {
      Glean.searchService.startupTime.cancel(timerId);

      let ex = Components.Exception(
        "#init: abandoning init due to shutting down",
        Cr.NS_ERROR_ABORT
      );

      this.#initializationStatus = "failed";
      this.#initDeferredPromise.reject(ex);
      throw ex;
    }

    this.#initializationStatus = "success";
    Glean.searchService.initializationStatus.success.add();
    this.#initDeferredPromise.resolve();
    this.#addObservers();

    Glean.searchService.startupTime.stopAndAccumulate(timerId);

    this.#recordTelemetryData();

    Services.obs.notifyObservers(
      null,
      lazy.SearchUtils.TOPIC_SEARCH_SERVICE,
      "init-complete"
    );

    lazy.logConsole.debug("Completed #init");
    this.#doPostInitWork();
  }

  /**
   * This function records telemetry, checks experiment updates, sets up a timer
   * for opensearch, removes any necessary Add-on engines immediately after the
   * search service has successfully initialized.
   *
   */
  #doPostInitWork() {
    // It is possible that Nimbus could have called onUpdate before
    // we started listening, so do a check on startup.
    Services.tm.dispatchToMainThread(async () => {
      await lazy.NimbusFeatures.searchConfiguration.ready();
      this.#checkNimbusPrefs(true);
    });

    this.#maybeStartOpenSearchUpdateTimer();

    if (this.#startupRemovedExtensions.size) {
      Services.tm.dispatchToMainThread(async () => {
        // Now that init() has successfully finished, we remove any engines
        // that have had their add-ons removed by the add-on manager.
        // We do this after init() has complete, as that allows us to use
        // removeEngine to look after any default engine changes as well.
        // This could cause a slight flicker on startup, but it should be
        // a rare action.
        lazy.logConsole.debug("Removing delayed extension engines");
        for (let id of this.#startupRemovedExtensions) {
          for (let engine of this.#getEnginesByExtensionID(id)) {
            // Only do this for non-application provided engines. We shouldn't
            // ever get application provided engines removed here, but just in case.
            if (!engine.isAppProvided) {
              await this.removeEngine(engine);
            }
          }
        }
        this.#startupRemovedExtensions.clear();
      });
    }
  }

  /**
   * Obtains the ignore list from remote settings. This should only be
   * called from init(). Any subsequent updates to the remote settings are
   * handled via a sync listener.
   *
   */
  async #getIgnoreListAndSubscribe() {
    let listener = this.#handleIgnoreListUpdated.bind(this);
    const current = await lazy.IgnoreLists.getAndSubscribe(listener);

    // Only save the listener after the subscribe, otherwise for tests it might
    // not be fully set up by the time we remove it again.
    this.ignoreListListener = listener;

    await this.#handleIgnoreListUpdated({ data: { current } });
    Services.obs.notifyObservers(
      null,
      lazy.SearchUtils.TOPIC_SEARCH_SERVICE,
      "settings-update-complete"
    );
  }

  /**
   * This handles updating of the ignore list settings, and removing any ignored
   * engines.
   *
   * @param {object} eventData
   *   The event in the format received from RemoteSettings.
   */
  async #handleIgnoreListUpdated(eventData) {
    lazy.logConsole.debug("#handleIgnoreListUpdated");
    const {
      data: { current },
    } = eventData;

    for (const entry of current) {
      if (entry.id == "load-paths") {
        this.#loadPathIgnoreList = [...entry.matches];
      } else if (entry.id == "submission-urls") {
        this.#submissionURLIgnoreList = [...entry.matches];
      }
    }

    try {
      await this.promiseInitialized;
    } catch (ex) {
      // If there's a problem with initialization return early to allow
      // search service to continue in a limited mode without engines.
      return;
    }

    // We try to remove engines manually, as this should be more efficient and
    // we don't really want to cause a re-init as this upsets unit tests.
    let engineRemoved = false;
    for (let engine of this._engines.values()) {
      if (this.#engineMatchesIgnoreLists(engine)) {
        await this.removeEngine(engine);
        engineRemoved = true;
      }
    }
    // If we've removed an engine, and we don't have any left, we need to
    // reload the engines - it is possible the settings just had one engine in it,
    // and that is now empty, so we need to load from our main list.
    if (engineRemoved && !this._engines.size) {
      this._maybeReloadEngines().catch(console.error);
    }
  }

  /**
   * Determines if a given engine matches the ignorelists or not.
   *
   * @param {Engine} engine
   *   The engine to check against the ignorelists.
   * @returns {boolean}
   *   Returns true if the engine matches a ignorelists entry.
   */
  #engineMatchesIgnoreLists(engine) {
    if (this.#loadPathIgnoreList.includes(engine._loadPath)) {
      return true;
    }
    let url = engine.searchURLWithNoTerms.spec.toLowerCase();
    if (
      this.#submissionURLIgnoreList.some(code =>
        url.includes(code.toLowerCase())
      )
    ) {
      return true;
    }
    return false;
  }

  /**
   * Handles the search configuration being - adds a wait on the user
   * being idle, before the search engine update gets handled.
   */
  #handleConfigurationUpdated() {
    if (this.#queuedIdle) {
      return;
    }

    this.#queuedIdle = true;

    this.idleService.addIdleObserver(this, RECONFIG_IDLE_TIME_SEC);
  }

  /**
   * Returns the engine that is the default for this locale/region, ignoring any
   * user changes to the default engine.
   *
   * @param {boolean} privateMode
   *   Set to true to return the default engine in private mode,
   *   false for normal mode.
   * @returns {SearchEngine}
   *   The engine that is default.
   */
  #appDefaultEngine(privateMode = false) {
    let defaultEngine = this._engines.get(
      privateMode && this.#searchPrivateDefault
        ? this.#searchPrivateDefault
        : this._searchDefault
    );

    if (Services.policies?.status == Ci.nsIEnterprisePolicies.ACTIVE) {
      let activePolicies = Services.policies.getActivePolicies();
      if (activePolicies.SearchEngines) {
        if (activePolicies.SearchEngines.Default) {
          return this.#getEngineByName(activePolicies.SearchEngines.Default);
        }
        if (activePolicies.SearchEngines.Remove?.includes(defaultEngine.name)) {
          defaultEngine = null;
        }
      }
    }

    if (defaultEngine) {
      return defaultEngine;
    }

    if (privateMode) {
      // If for some reason we can't find the private mode engine, fall back
      // to the non-private one.
      return this.#appDefaultEngine(false);
    }

    // Something unexpected has happened. In order to recover the app default
    // engine, use the first visible engine that is also a general purpose engine.
    // Worst case, we just use the first visible engine.
    defaultEngine = this.#sortedVisibleEngines.find(
      e => e.isGeneralPurposeEngine
    );
    return defaultEngine ? defaultEngine : this.#sortedVisibleEngines[0];
  }

  /**
   * Loads engines asynchronously.
   *
   * @param {object} settings
   *   An object representing the search engine settings.
   * @param {Array} engines
   *   An array containing the engines objects from remote settings.
   * @param {object} privateDefault
   *   An object representing the private default search engine.
   */
  async #loadEngines(settings, engines, privateDefault) {
    // Get user's current settings and search engine before we load engines from
    // config. These values will be compared after engines are loaded.
    let prevMetaData = { ...settings?.metaData };
    let prevCurrentEngineId = prevMetaData.defaultEngineId;
    let prevAppDefaultEngineId = prevMetaData?.appDefaultEngineId;

    lazy.logConsole.debug("#loadEngines: start");
    this.#setDefaultAndOrdersFromSelector(engines, privateDefault);

    this.#loadEnginesFromConfig(engines, settings);

    await this.#loadStartupEngines(settings);

    this.#loadEnginesFromPolicies(settings);

    // `loadEnginesFromSettings` loads the engines and their settings together.
    // If loading the settings caused the default engine to change because of an
    // override, then we don't want to show the notification box.
    let skipDefaultChangedNotification = await this.#loadEnginesFromSettings(
      settings
    );

    // If #loadEnginesFromSettings changed the default engine, then we don't
    // need to call #checkOpenSearchOverrides as we know that the overrides have
    // only just been applied.
    skipDefaultChangedNotification ||= await this.#checkOpenSearchOverrides(
      settings
    );

    // Settings file version 6 and below will need a migration to store the
    // engine ids rather than engine names.
    this._settings.migrateEngineIds(settings);

    lazy.logConsole.debug("#loadEngines: done");

    let newCurrentEngine = this._getEngineDefault(false);
    let newCurrentEngineId = newCurrentEngine?.id;

    this._settings.setMetaDataAttribute(
      "appDefaultEngineId",
      this.appDefaultEngine?.id
    );

    if (
      !skipDefaultChangedNotification &&
      this.#shouldDisplayRemovalOfEngineNotificationBox(
        settings,
        prevMetaData,
        newCurrentEngineId,
        prevCurrentEngineId,
        prevAppDefaultEngineId
      )
    ) {
      let newCurrentEngineName = newCurrentEngine?.name;

      let [prevCurrentEngineName, prevAppDefaultEngineName] = [
        settings.engines.find(e => e.id == prevCurrentEngineId)?._name,
        settings.engines.find(e => e.id == prevAppDefaultEngineId)?._name,
      ];

      this._showRemovalOfSearchEngineNotificationBox(
        prevCurrentEngineName || prevAppDefaultEngineName,
        newCurrentEngineName
      );
    }
  }

  /**
   * Helper function to determine if the removal of search engine notification
   * box should be displayed.
   *
   * @param { object } settings
   *   The user's search engine settings.
   * @param { object } prevMetaData
   *   The user's previous search settings metadata.
   * @param { object } newCurrentEngineId
   *   The user's new current default engine.
   * @param { object } prevCurrentEngineId
   *   The user's previous default engine.
   * @param { object } prevAppDefaultEngineId
   *   The user's previous app default engine.
   * @returns { boolean }
   *   Return true if the previous default engine has been removed and
   *   notification box should be displayed.
   */
  #shouldDisplayRemovalOfEngineNotificationBox(
    settings,
    prevMetaData,
    newCurrentEngineId,
    prevCurrentEngineId,
    prevAppDefaultEngineId
  ) {
    if (
      !Services.prefs.getBoolPref("browser.search.removeEngineInfobar.enabled")
    ) {
      return false;
    }

    // If for some reason we were unable to install any engines and hence no
    // default engine, do not display the notification box
    if (!newCurrentEngineId) {
      return false;
    }

    // If the previous engine is still available, don't show the notification
    // box.
    if (prevCurrentEngineId && this._engines.has(prevCurrentEngineId)) {
      return false;
    }
    if (!prevCurrentEngineId && this._engines.has(prevAppDefaultEngineId)) {
      return false;
    }

    // Don't show the notification if the previous engine was an enterprise engine -
    // the text doesn't quite make sense.
    // let checkPolicyEngineId = prevCurrentEngineId ? prevCurrentEngineId : prevAppDefaultEngineId;
    let checkPolicyEngineId = prevCurrentEngineId || prevAppDefaultEngineId;
    if (checkPolicyEngineId) {
      let engineSettings = settings.engines.find(
        e => e.id == checkPolicyEngineId
      );
      if (engineSettings?._loadPath?.startsWith("[policy]")) {
        return false;
      }
    }

    // If the user's previous engine id is different than the new current
    // engine id, or if the user was using the app default engine and the
    // app default engine id is different than the new current engine id,
    // we check if the user's settings metadata has been upddated.
    if (
      (prevCurrentEngineId && prevCurrentEngineId !== newCurrentEngineId) ||
      (!prevCurrentEngineId &&
        prevAppDefaultEngineId &&
        prevAppDefaultEngineId !== newCurrentEngineId)
    ) {
      // Check settings metadata to detect an update to locale. Sometimes when
      // the user changes their locale it causes a change in engines.
      // If there is no update to settings metadata then the engine change was
      // caused by an update to config rather than a user changing their locale.
      if (!this.#didSettingsMetaDataUpdate(prevMetaData)) {
        return true;
      }
    }

    return false;
  }

  /**
   * Loads engines as specified by the configuration. We only expect
   * configured engines here, user engines should not be listed.
   *
   * @param {Array} engineConfigs
   *   An array of engines configurations based on the schema.
   * @param {object} [settings]
   *   The saved settings for the user.
   */
  #loadEnginesFromConfig(engineConfigs, settings) {
    lazy.logConsole.debug("#loadEnginesFromConfig");
    for (let config of engineConfigs) {
      try {
        let engine = new lazy.AppProvidedSearchEngine({ config, settings });
        this.#addEngineToStore(engine);
      } catch (ex) {
        console.error(
          "Could not load app provided search engine id:",
          config.identifier,
          ex
        );
      }
    }
  }

  /**
   * Loads any engines that have been received from the AddonManager during
   * startup and before we have finished initialising.
   *
   * @param {object} [settings]
   *   The saved settings for the user.
   */
  async #loadStartupEngines(settings) {
    if (this.#startupExtensions.size) {
      await lazy.AddonManager.readyPromise;
    }

    lazy.logConsole.debug(
      "#loadStartupEngines: loading",
      this.#startupExtensions.size,
      "engines reported by AddonManager startup"
    );
    for (let extension of this.#startupExtensions) {
      try {
        await this.#createAndAddAddonEngine({
          extension,
          locale: lazy.SearchUtils.DEFAULT_TAG,
          settings,
        });
      } catch (ex) {
        lazy.logConsole.error(
          "#loadStartupEngines failed for",
          extension.id,
          ex
        );
      }
    }
    this.#startupExtensions.clear();
  }

  /**
   * When starting up, check if any of the saved application provided engines
   * are no longer required, previously were default and were overridden by
   * an OpenSearch engine.
   *
   * Also check if any OpenSearch overrides need to be re-applied.
   *
   * Add-on search engines are handled separately.
   *
   * @param {object} settings
   *   The loaded settings for the user.
   * @returns {boolean}
   *   Returns true if the default engine was changed.
   */
  async #checkOpenSearchOverrides(settings) {
    let defaultEngineChanged = false;
    let savedDefaultEngineId =
      settings.metaData.defaultEngineId || settings.metaData.appDefaultEngineId;
    if (!savedDefaultEngineId) {
      return false;
    }
    // First handle the case where the application provided engine was removed,
    // and we need to restore the OpenSearch engine.
    for (let engineSettings of settings.engines) {
      if (
        !this._engines.get(engineSettings.id) &&
        engineSettings._isAppProvided &&
        engineSettings.id == savedDefaultEngineId &&
        engineSettings._metaData.overriddenByOpenSearch
      ) {
        let restoringEngine = new lazy.OpenSearchEngine({
          json: engineSettings._metaData.overriddenByOpenSearch,
        });
        restoringEngine.copyUserSettingsFrom(engineSettings);
        this.#addEngineToStore(restoringEngine, true);

        // We assume that the app provided engine was removed due to a
        // configuration change, and therefore we have re-added the OpenSearch
        // search engine. It is possible that it was actually due to a
        // locale/region change, but that is harder to detect here.
        this.#setEngineDefault(
          false,
          restoringEngine,
          Ci.nsISearchService.CHANGE_REASON_CONFIG
        );
        delete engineSettings._metaData.overriddenByOpenSearch;
      }
    }
    // Now handle the case where the an application provided engine has been
    // overridden by an OpenSearch engine, and we need to re-apply the override.
    for (let engine of this._engines.values()) {
      if (
        engine.isAppProvided &&
        engine.getAttr("overriddenByOpenSearch") &&
        engine.id == savedDefaultEngineId
      ) {
        let restoringEngine = new lazy.OpenSearchEngine({
          json: engine.getAttr("overriddenByOpenSearch"),
        });
        if (
          await lazy.defaultOverrideAllowlist.canEngineOverride(
            restoringEngine,
            engine.id
          )
        ) {
          engine.overrideWithEngine({ engine: restoringEngine });
        }
      }
    }

    return defaultEngineChanged;
  }

  /**
   * Reloads engines asynchronously, but only when
   * the service has already been initialized.
   *
   * This is prefixed with _ rather than # because it is
   * called in test_reload_engines.js
   *
   * @param {integer} changeReason
   *   The reason reload engines is being called, one of
   *   Ci.nsISearchService.CHANGE_REASON*
   */
  async _maybeReloadEngines(changeReason) {
    if (this.#maybeReloadDebounce) {
      lazy.logConsole.debug("We're already waiting to reload engines.");
      return;
    }

    if (!this.isInitialized || this._reloadingEngines) {
      this.#maybeReloadDebounce = true;
      // Schedule a reload to happen at most 10 seconds after the current run.
      Services.tm.idleDispatchToMainThread(() => {
        if (!this.#maybeReloadDebounce) {
          return;
        }
        this.#maybeReloadDebounce = false;
        this._maybeReloadEngines(changeReason).catch(console.error);
      }, 10000);
      lazy.logConsole.debug(
        "Post-poning maybeReloadEngines() as we're currently initializing."
      );
      return;
    }

    // Before entering `_reloadingEngines` get the settings which we'll need.
    // This also ensures that any pending settings have finished being written,
    // which could otherwise cause data loss.
    let settings = await this._settings.get();

    lazy.logConsole.debug("Running maybeReloadEngines");
    this._reloadingEngines = true;

    try {
      await this._reloadEngines(settings, changeReason);
    } catch (ex) {
      lazy.logConsole.error("maybeReloadEngines failed", ex);
    }
    this._reloadingEngines = false;
    lazy.logConsole.debug("maybeReloadEngines complete");
  }

  /**
   * Manages reloading of the search engines when something in the user's
   * environment or the configuration has changed.
   *
   * The order of work here is designed to avoid potential issues when updating
   * the default engines, so that we're not removing active defaults or trying
   * to set a default to something that hasn't been added yet. The order is:
   *
   * 1) Update exising engines that are in both the old and new configuration.
   * 2) Add any new engines from the new configuration.
   * 3) Check for changes needed to the default engines due to environment changes
   *    and potentially overriding engines as per the override allowlist.
   * 4) Update the default engines.
   * 5) Remove any old engines.
   *
   * This is prefixed with _ rather than # because it is called in
   * test_remove_engine_notification_box.js
   *
   * @param {object} settings
   *   The user's current saved settings.
   * @param {integer} changeReason
   *   The reason reload engines is being called, one of
   *   Ci.nsISearchService.CHANGE_REASON*
   */
  async _reloadEngines(settings, changeReason) {
    // Capture the current engine state, in case we need to notify below.
    let prevCurrentEngine = this.#currentEngine;
    let prevPrivateEngine = this.#currentPrivateEngine;
    let prevMetaData = { ...settings?.metaData };

    // Ensure that we don't set the useSavedOrder flag whilst we're doing this.
    // This isn't a user action, so we shouldn't be switching it.
    this.#dontSetUseSavedOrder = true;

    let { engines: appDefaultConfigEngines, privateDefault } =
      await this._fetchEngineSelectorEngines();

    let configEngines = [...appDefaultConfigEngines];
    let oldEngineList = [...this._engines.values()];

    for (let engine of oldEngineList) {
      if (!engine.isAppProvided) {
        if (engine instanceof lazy.AddonSearchEngine) {
          // If this is an add-on search engine, check to see if it needs
          // an update.
          await engine.update();
        }
        continue;
      }

      let index = configEngines.findIndex(e => e.identifier == engine.id);

      if (index == -1) {
        engine.pendingRemoval = true;
        continue;
      } else {
        // This is an existing engine that we should update (we don't know if
        // the configuration for this engine has changed or not).
        await engine.update({
          configuration: configEngines[index],
        });
      }

      configEngines.splice(index, 1);
    }

    let existingDuplicateEngines = [];

    // Any remaining configuration engines are ones that we need to add.
    for (let engine of configEngines) {
      try {
        let newAppEngine = new lazy.AppProvidedSearchEngine({
          config: engine,
          settings,
        });

        // If this is a duplicate name, keep track of the old engine as we need
        // to handle it later.
        let duplicateEngine = this.#getEngineByName(newAppEngine.name);
        if (duplicateEngine) {
          existingDuplicateEngines.push({
            duplicateEngine,
            newAppEngine,
          });
        }
        // We add our new engine to the store anyway, as we know it is an
        // application provided engine which will take priority over the
        // duplicate.
        this.#addEngineToStore(newAppEngine, true);
      } catch (ex) {
        lazy.logConsole.warn(
          "Could not load app provided search engine id:",
          engine.identifier,
          ex
        );
      }
    }

    // Now set the sort out the default engines and notify as appropriate.

    // Clear the current values, so that we'll completely reset.
    this.#currentEngine = null;
    this.#currentPrivateEngine = null;

    // If the user's default is one of the private engines that is being removed,
    // reset the stored setting, so that we correctly detect the change in
    // in default.
    if (prevCurrentEngine?.pendingRemoval) {
      this._settings.setMetaDataAttribute("defaultEngineId", "");
    }
    if (prevPrivateEngine?.pendingRemoval) {
      this._settings.setMetaDataAttribute("privateDefaultEngineId", "");
    }

    this.#setDefaultAndOrdersFromSelector(
      appDefaultConfigEngines,
      privateDefault
    );

    let skipDefaultChangedNotification = false;

    for (let { duplicateEngine, newAppEngine } of existingDuplicateEngines) {
      if (prevCurrentEngine && prevCurrentEngine == duplicateEngine) {
        if (
          await lazy.defaultOverrideAllowlist.canEngineOverride(
            duplicateEngine,
            newAppEngine?.id
          )
        ) {
          lazy.logConsole.log(
            "Applying override from",
            duplicateEngine.id,
            "to application engine",
            newAppEngine.id,
            "and setting app engine default"
          );
          // This engine was default, and is allowed to override our application
          // provided engines, so update the application engine and set it as
          // default.
          newAppEngine.overrideWithEngine({
            engine: duplicateEngine,
          });

          this.defaultEngine = newAppEngine;
          // We're removing the old engine and we've changed the default, but this
          // is intentional and effectively everything is the same for the user, so
          // don't notify.
          skipDefaultChangedNotification = true;
        }
      }
      duplicateEngine.pendingRemoval = true;
    }

    if (prevCurrentEngine && prevCurrentEngine.pendingRemoval) {
      skipDefaultChangedNotification ||=
        await this.#maybeRestoreEngineFromOverride(prevCurrentEngine);
    }

    // If the defaultEngine has changed between the previous load and this one,
    // dispatch the appropriate notifications.
    if (prevCurrentEngine && this.defaultEngine !== prevCurrentEngine) {
      this.#recordDefaultChangedEvent(
        false,
        prevCurrentEngine,
        this.defaultEngine,
        changeReason
      );
      lazy.SearchUtils.notifyAction(
        this.#currentEngine,
        lazy.SearchUtils.MODIFIED_TYPE.DEFAULT
      );
      // If we've not got a separate private active, notify update of the
      // private so that the UI updates correctly.
      if (!this.#separatePrivateDefault) {
        lazy.SearchUtils.notifyAction(
          this.#currentEngine,
          lazy.SearchUtils.MODIFIED_TYPE.DEFAULT_PRIVATE
        );
      }

      if (
        !skipDefaultChangedNotification &&
        prevMetaData &&
        settings.metaData &&
        !this.#didSettingsMetaDataUpdate(prevMetaData) &&
        prevCurrentEngine?.pendingRemoval &&
        Services.prefs.getBoolPref("browser.search.removeEngineInfobar.enabled")
      ) {
        this._showRemovalOfSearchEngineNotificationBox(
          prevCurrentEngine.name,
          this.defaultEngine.name
        );
      }
    }

    if (
      this.#separatePrivateDefault &&
      prevPrivateEngine &&
      this.defaultPrivateEngine !== prevPrivateEngine
    ) {
      this.#recordDefaultChangedEvent(
        true,
        prevPrivateEngine,
        this.defaultPrivateEngine,
        changeReason
      );
      lazy.SearchUtils.notifyAction(
        this.#currentPrivateEngine,
        lazy.SearchUtils.MODIFIED_TYPE.DEFAULT_PRIVATE
      );
    }

    // Finally, remove any engines that need removing. We do this after sorting
    // out the new default, as otherwise this could cause multiple notifications
    // and the wrong engine to be selected as default.
    await this.#maybeRemoveEnginesAfterReload(this._engines);

    // Save app default engine to the user's settings metaData incase it has
    // been updated
    this._settings.setMetaDataAttribute(
      "appDefaultEngineId",
      this.appDefaultEngine?.id
    );

    // If we are leaving an experiment, and the default is the same as the
    // application default, we reset the user's setting to blank, so that
    // future changes of the application default engine may take effect.
    if (
      prevMetaData.experiment &&
      !this._settings.getMetaDataAttribute("experiment")
    ) {
      if (this.defaultEngine == this.appDefaultEngine) {
        this._settings.setVerifiedMetaDataAttribute("defaultEngineId", "");
      }
      if (
        this.#separatePrivateDefault &&
        this.defaultPrivateEngine == this.appPrivateDefaultEngine
      ) {
        this._settings.setVerifiedMetaDataAttribute(
          "privateDefaultEngineId",
          ""
        );
      }
    }

    this.#dontSetUseSavedOrder = false;
    // Clear out the sorted engines settings, so that we re-sort it if necessary.
    this._cachedSortedEngines = null;
    Services.obs.notifyObservers(
      null,
      lazy.SearchUtils.TOPIC_SEARCH_SERVICE,
      "engines-reloaded"
    );
  }

  /**
   * Potentially restores an engine if it was previously overriding the app
   * provided engine.
   *
   * @param {SearchEngine} prevCurrentEngine
   *   The previous current engine to check for override.
   * @returns {boolean}
   *   True if an engine was restored.
   */
  async #maybeRestoreEngineFromOverride(prevCurrentEngine) {
    let overriddenBy = prevCurrentEngine.getAttr("overriddenBy");
    if (!overriddenBy) {
      return false;
    }
    let overriddenByOpenSearch = prevCurrentEngine.getAttr(
      "overriddenByOpenSearch"
    );
    let engine;
    if (overriddenByOpenSearch) {
      engine = new lazy.OpenSearchEngine({
        json: overriddenByOpenSearch,
      });
    } else {
      // The previous application default engine is being removed, and it was
      // overridden by another engine. We want to put the previous engine back,
      // so that the user retains that engine as default.
      engine = new lazy.AddonSearchEngine({
        isAppProvided: false,
        details: {
          extensionID: overriddenBy,
          locale: lazy.SearchUtils.DEFAULT_TAG,
        },
      });
      try {
        await engine.init({ locale: lazy.SearchUtils.DEFAULT_TAG });
      } catch (ex) {
        // If there is an error, the add-on may no longer be available, or
        // there was some other issue with the settings.
        lazy.logConsole.error(
          "Error restoring overridden engine",
          overriddenBy,
          ex
        );
        return false;
      }
    }
    engine.copyUserSettingsFrom(prevCurrentEngine);
    this.#addEngineToStore(engine, true);

    // Now set it back to default.
    this.defaultEngine = engine;
    return true;
  }

  /**
   * Remove any engines that have been flagged for removal during reloadEngines.
   *
   * @param {SearchEngine[]} engines
   *   The list of engines to check.
   */
  async #maybeRemoveEnginesAfterReload(engines) {
    for (let engine of engines.values()) {
      if (!engine.pendingRemoval) {
        continue;
      }

      // Use the internal remove - _reloadEngines already deals with default
      // engines etc, and we want to avoid adjusting the sort order unnecessarily.
      this.#internalRemoveEngine(engine);

      if (engine instanceof lazy.AppProvidedSearchEngine) {
        await engine.cleanup();
      }

      lazy.SearchUtils.notifyAction(
        engine,
        lazy.SearchUtils.MODIFIED_TYPE.REMOVED
      );
    }
  }

  #addEngineToStore(engine, skipDuplicateCheck = false) {
    if (this.#engineMatchesIgnoreLists(engine)) {
      lazy.logConsole.debug("#addEngineToStore: Ignoring engine");
      return;
    }

    lazy.logConsole.debug("#addEngineToStore: Adding engine:", engine.name);

    // See if there is an existing engine with the same name.
    if (!skipDuplicateCheck && this.#getEngineByName(engine.name)) {
      throw Components.Exception(
        `#addEngineToStore: An engine called ${engine.name} already exists!`,
        Cr.NS_ERROR_FILE_ALREADY_EXISTS
      );
    }

    // Not an update, just add the new engine.
    this._engines.set(engine.id, engine);
    // Only add the engine to the list of sorted engines if the initial list
    // has already been built (i.e. if this._cachedSortedEngines is non-null). If
    // it hasn't, we're loading engines from disk and the sorted engine list
    // will be built once we need it.
    if (this._cachedSortedEngines && !this.#dontSetUseSavedOrder) {
      this._cachedSortedEngines.push(engine);
      this.#saveSortedEngineList();
    }
    lazy.SearchUtils.notifyAction(engine, lazy.SearchUtils.MODIFIED_TYPE.ADDED);

    // Let the engine know it can start notifying new updates.
    engine._engineAddedToStore = true;
  }

  /**
   * Loads any search engines specified by enterprise policies.
   *
   * @param {object} [settings]
   *   The saved settings for the user.
   */
  #loadEnginesFromPolicies(settings) {
    if (Services.policies?.status != Ci.nsIEnterprisePolicies.ACTIVE) {
      return;
    }

    let activePolicies = Services.policies.getActivePolicies();
    if (!activePolicies.SearchEngines) {
      return;
    }
    for (let engineDetails of activePolicies.SearchEngines.Add ?? []) {
      this.#addPolicyEngine(engineDetails, settings);
    }
  }

  /**
   * Loads remaining user search engines from settings.
   *
   * @param {object} [settings]
   *   The saved settings for the user.
   * @returns {boolean}
   *   Returns true if the default engine was changed.
   */
  async #loadEnginesFromSettings(settings) {
    if (!settings.engines) {
      return false;
    }

    lazy.logConsole.debug(
      "#loadEnginesFromSettings: Loading",
      settings.engines.length,
      "engines from settings"
    );

    let defaultEngineChanged = false;
    let skippedEngines = 0;
    for (let engineJSON of settings.engines) {
      // We renamed isBuiltin to isAppProvided in bug 1631898,
      // keep checking isBuiltin for older settings.
      if (engineJSON._isAppProvided || engineJSON._isBuiltin) {
        ++skippedEngines;
        continue;
      }

      // Some OpenSearch type engines are now obsolete and no longer supported.
      // These were application provided engines that used to use the OpenSearch
      // format before gecko transitioned to WebExtensions.
      // These will sometimes have been missed in migration due to various
      // reasons, and due to how the settings saves everything. We therefore
      // explicitly ignore them here to drop them, and let the rest of the code
      // fallback to the application/distribution default if necessary.
      let loadPath = engineJSON._loadPath?.toLowerCase();
      if (
        loadPath &&
        // Replaced by application provided in Firefox 79.
        (loadPath.startsWith("[distribution]") ||
          // Langpack engines moved in-app in Firefox 62.
          // Note: these may be prefixed by jar:,
          loadPath.includes("[app]/extensions/langpack") ||
          loadPath.includes("[other]/langpack") ||
          loadPath.includes("[profile]/extensions/langpack") ||
          // Old omni.ja engines also moved to in-app in Firefox 62.
          loadPath.startsWith("jar:[app]/omni.ja"))
      ) {
        continue;
      }

      try {
        let engine;
        if (loadPath?.startsWith("[policy]")) {
          skippedEngines++;
          continue;
        } else if (loadPath?.startsWith("[user]")) {
          engine = new lazy.UserSearchEngine({ json: engineJSON });
        } else if (engineJSON.extensionID ?? engineJSON._extensionID) {
          let existingEngine = this.#getEngineByName(engineJSON._name);
          let extensionId = engineJSON.extensionID ?? engineJSON._extensionID;

          if (existingEngine && existingEngine._extensionID == extensionId) {
            // We assume that this WebExtension was already loaded as part of
            // #loadStartupEngines, and therefore do not try to add it again.
            lazy.logConsole.log(
              "Ignoring already added WebExtension",
              extensionId
            );
            continue;
          }

          engine = new lazy.AddonSearchEngine({
            isAppProvided: false,
            json: engineJSON,
          });
        } else {
          engine = new lazy.OpenSearchEngine({
            json: engineJSON,
          });
        }
        // Only check the override for Add-on or OpenSearch engines, and only
        // if they are the default engine.
        if (
          (engine instanceof lazy.OpenSearchEngine ||
            engine instanceof lazy.AddonSearchEngine) &&
          settings.metaData?.defaultEngineId == engine.id
        ) {
          defaultEngineChanged = await this.#maybeApplyOverride(engine);
          if (defaultEngineChanged) {
            continue;
          }
        }
        this.#addEngineToStore(engine);
      } catch (ex) {
        lazy.logConsole.error(
          "Failed to load",
          engineJSON._name,
          "from settings:",
          ex,
          engineJSON
        );
      }
    }

    if (skippedEngines) {
      lazy.logConsole.debug(
        "#loadEnginesFromSettings: skipped",
        skippedEngines,
        "built-in/policy engines."
      );
    }
    return defaultEngineChanged;
  }

  /**
   * Looks to see if an override may be applied to an application engine
   * if the supplied engine is a duplicate of it. This should only be called
   * in the case where the engine would become the default engine.
   *
   * @param {SearchEngine} engine
   *   The search engine to check to see if it should override an existing engine.
   * @returns {boolean}
   *  True if the default engine was changed.
   */
  async #maybeApplyOverride(engine) {
    // If an engine with the same name already exists, we're not going to
    // be allowed to add it - however, if it is default, and it
    // matches an existing engine, then we might be allowed to
    // override the application provided engine.
    let existingEngine = this.#getEngineByName(engine.name);
    if (
      existingEngine?.isAppProvided &&
      (await lazy.defaultOverrideAllowlist.canEngineOverride(
        engine,
        existingEngine?.id
      ))
    ) {
      existingEngine.overrideWithEngine({
        engine,
      });
      this.#setEngineDefault(
        false,
        existingEngine,
        // We assume that the application provided engine was added due
        // to a configuration change. It is possible that it was actually
        // due to a locale/region change, but that is harder to detect
        // here.
        Ci.nsISearchService.CHANGE_REASON_CONFIG
      );
      return true;
    }
    return false;
  }

  // This is prefixed with _ rather than # because it is
  // called in test_remove_engine_notification_box.js
  async _fetchEngineSelectorEngines() {
    let searchEngineSelectorProperties = {
      locale: Services.locale.appLocaleAsBCP47,
      region: lazy.Region.home || "unknown",
      channel: lazy.SearchUtils.MODIFIED_APP_CHANNEL,
      experiment:
        lazy.NimbusFeatures.searchConfiguration.getVariable("experiment") ?? "",
      distroID: lazy.SearchUtils.distroID ?? "",
    };

    for (let [key, value] of Object.entries(searchEngineSelectorProperties)) {
      this._settings.setMetaDataAttribute(key, value);
    }

    return this.#engineSelector.fetchEngineConfiguration(
      searchEngineSelectorProperties
    );
  }

  #setDefaultAndOrdersFromSelector(engines, privateDefault) {
    this._searchDefault = engines[0].identifier;
    if (privateDefault) {
      this.#searchPrivateDefault = privateDefault.identifier;
    }
  }

  #saveSortedEngineList() {
    lazy.logConsole.debug("#saveSortedEngineList");

    // Set the useSavedOrder attribute to indicate that from now on we should
    // use the user's order information stored in settings.
    this._settings.setMetaDataAttribute("useSavedOrder", true);

    var engines = this.#sortedEngines;

    for (var i = 0; i < engines.length; ++i) {
      engines[i].setAttr("order", i + 1);
    }
  }

  #buildSortedEngineList() {
    // We must initialise _cachedSortedEngines here to avoid infinite recursion
    // in the case of tests which don't define a default search engine.
    // If there's no default defined, then we revert to the first item in the
    // sorted list, but we can't do that if we don't have a list.
    this._cachedSortedEngines = [];

    // If the user has specified a custom engine order, read the order
    // information from the metadata instead of the default prefs.
    if (this._settings.getMetaDataAttribute("useSavedOrder")) {
      lazy.logConsole.debug("#buildSortedEngineList: using saved order");
      let addedEngines = {};

      // Flag to keep track of whether or not we need to call #saveSortedEngineList.
      let needToSaveEngineList = false;

      for (let engine of this._engines.values()) {
        var orderNumber = engine.getAttr("order");

        // Since the DB isn't regularly cleared, and engine files may disappear
        // without us knowing, we may already have an engine in this slot. If
        // that happens, we just skip it - it will be added later on as an
        // unsorted engine.
        if (orderNumber && !this._cachedSortedEngines[orderNumber - 1]) {
          this._cachedSortedEngines[orderNumber - 1] = engine;
          addedEngines[engine.name] = engine;
        } else {
          // We need to call #saveSortedEngineList so this gets sorted out.
          needToSaveEngineList = true;
        }
      }

      // Filter out any nulls for engines that may have been removed
      var filteredEngines = this._cachedSortedEngines.filter(function (a) {
        return !!a;
      });
      if (this._cachedSortedEngines.length != filteredEngines.length) {
        needToSaveEngineList = true;
      }
      this._cachedSortedEngines = filteredEngines;

      if (needToSaveEngineList) {
        this.#saveSortedEngineList();
      }

      // Array for the remaining engines, alphabetically sorted.
      let alphaEngines = [];

      for (let engine of this._engines.values()) {
        if (!(engine.name in addedEngines)) {
          alphaEngines.push(engine);
        }
      }

      const collator = new Intl.Collator();
      alphaEngines.sort((a, b) => {
        return collator.compare(a.name, b.name);
      });
      return (this._cachedSortedEngines =
        this._cachedSortedEngines.concat(alphaEngines));
    }
    lazy.logConsole.debug("#buildSortedEngineList: using default orders");

    return (this._cachedSortedEngines = lazy.SearchUtils.sortEnginesByDefaults({
      engines: Array.from(this._engines.values()),
      appDefaultEngine: this.appDefaultEngine,
      appPrivateDefaultEngine: this.appPrivateDefaultEngine,
    }));
  }

  /**
   * Get a sorted array of the visible engines.
   *
   * @returns {Array<SearchEngine>}
   */
  get #sortedVisibleEngines() {
    return this.#sortedEngines.filter(engine => !engine.hidden);
  }

  /**
   * Migrates legacy add-ons which used the OpenSearch definitions to
   * WebExtensions, if an equivalent WebExtension is installed.
   *
   * Run during the background checks.
   */
  async #migrateLegacyEngines() {
    lazy.logConsole.debug("Running migrate legacy engines");

    const matchRegExp = /extensions\/(.*?)\.xpi!/i;
    for (let engine of this._engines.values()) {
      if (
        !engine.isAppProvided &&
        !engine._extensionID &&
        engine._loadPath.includes("[profile]/extensions/")
      ) {
        let match = engine._loadPath.match(matchRegExp);
        if (match?.[1]) {
          // There's a chance here that the WebExtension might not be
          // installed any longer, even though the engine is. We'll deal
          // with that in `checkWebExtensionEngines`.
          let engines = await this.getEnginesByExtensionID(match[1]);
          if (engines.length) {
            lazy.logConsole.debug(
              `Migrating ${engine.name} to WebExtension install`
            );

            if (this.defaultEngine == engine) {
              this.defaultEngine = engines[0];
            }
            await this.removeEngine(engine);
          }
        }
      }
    }

    lazy.logConsole.debug("Migrate legacy engines complete");
  }

  /**
   * Checks if Search Engines associated with WebExtensions are valid and
   * up-to-date, and reports them via telemetry if not.
   *
   * Run during the background checks.
   */
  async #checkWebExtensionEngines() {
    lazy.logConsole.debug("Running check on WebExtension engines");

    for (let engine of this._engines.values()) {
      if (engine instanceof lazy.AddonSearchEngine) {
        await engine.checkAndReportIfSettingsValid();
      }
    }
    lazy.logConsole.debug("WebExtension engine check complete");
  }

  /**
   * Counts the number of secure, insecure, securely updated and insecurely
   * updated OpenSearch engines the user has installed and reports those
   * counts via telemetry.
   *
   * Run during the background checks.
   */
  async #addOpenSearchTelemetry() {
    let totalSecure = 0;
    let totalInsecure = 0;
    let totalWithSecureUpdates = 0;
    let totalWithInsecureUpdates = 0;

    let engine;
    let searchURI;
    let updateURI;
    for (let elem of this._engines) {
      engine = elem[1];
      if (engine instanceof lazy.OpenSearchEngine) {
        searchURI = engine.searchURLWithNoTerms;
        updateURI = engine.updateURI;

        if (lazy.SearchUtils.isSecureURIForOpenSearch(searchURI)) {
          totalSecure++;
        } else {
          totalInsecure++;
        }

        if (updateURI && lazy.SearchUtils.isSecureURIForOpenSearch(updateURI)) {
          totalWithSecureUpdates++;
        } else if (updateURI) {
          totalWithInsecureUpdates++;
        }
      }
    }

    Services.telemetry.scalarSet(
      "browser.searchinit.secure_opensearch_engine_count",
      totalSecure
    );
    Services.telemetry.scalarSet(
      "browser.searchinit.insecure_opensearch_engine_count",
      totalInsecure
    );
    Services.telemetry.scalarSet(
      "browser.searchinit.secure_opensearch_update_count",
      totalWithSecureUpdates
    );
    Services.telemetry.scalarSet(
      "browser.searchinit.insecure_opensearch_update_count",
      totalWithInsecureUpdates
    );
  }

  /**
   * Creates and adds a WebExtension based engine. It is expected that this
   * function is only called after initialisation has completed, or at a stage
   * where we are ready to load the engines we've been told about during startup.
   *
   * @param {object} options
   *   Options for the engine.
   * @param {Extension} options.extension
   *   An Extension object containing data about the extension.
   * @param {string} [options.locale]
   *   The locale to use within the WebExtension. Defaults to the WebExtension's
   *   default locale.
   * @param {object} [options.settings]
   *   The saved settings for the user.
   */
  async #createAndAddAddonEngine({
    extension,
    locale = lazy.SearchUtils.DEFAULT_TAG,
    settings,
  }) {
    // If we're in the startup cycle, and we've already loaded this engine,
    // then we use the existing one rather than trying to start from scratch.
    // This also avoids console errors.
    if (extension.startupReason == "APP_STARTUP") {
      let engine = this.#getEngineByWebExtensionDetails({
        id: extension.id,
        locale,
      });
      if (engine) {
        lazy.logConsole.debug(
          "Engine already loaded via settings, skipping due to APP_STARTUP:",
          extension.id
        );
        return;
      }
    }

    lazy.logConsole.debug(
      "#createAndAddAddonEngine: installing:",
      extension.id,
      locale
    );

    let shouldSetAsDefault = false;
    let changeReason = Ci.nsISearchService.CHANGE_REASON_UNKNOWN;

    for (let engine of this._engines.values()) {
      if (
        !engine.extensionID &&
        engine._loadPath.startsWith(`jar:[profile]/extensions/${extension.id}`)
      ) {
        // This is a legacy extension engine that needs to be migrated to WebExtensions.
        lazy.logConsole.debug("Migrating existing engine");
        shouldSetAsDefault = shouldSetAsDefault || this.defaultEngine == engine;
        await this.removeEngine(engine);
      }
    }

    let newEngine = new lazy.AddonSearchEngine({
      isAppProvided: false,
      details: {
        extensionID: extension.id,
        locale,
      },
    });
    await newEngine.init({
      settings,
      extension,
      locale,
    });

    // If this extension is starting up, check to see if it previously overrode
    // an application provided engine that has now been removed from the user's
    // set-up. If the application provided engine has been removed and was
    // default, then we should set this engine back to default and copy
    // the settings across.
    if (extension.startupReason == "APP_STARTUP") {
      if (!settings) {
        settings = await this._settings.get();
      }
      // We check the saved settings for the overridden flag, because if the engine
      // has been removed, we won't have that in _engines.
      let previouslyOverridden = settings.engines?.find(
        e => !!e._metaData.overriddenBy
      );
      if (previouslyOverridden) {
        // Only allow override if we were previously overriding and the
        // engine is no longer installed, and the new engine still matches the
        // override allow list.
        if (
          previouslyOverridden._metaData.overriddenBy == extension.id &&
          !this._engines.get(previouslyOverridden.id) &&
          (await lazy.defaultOverrideAllowlist.canEngineOverride(
            newEngine,
            previouslyOverridden.id
          ))
        ) {
          shouldSetAsDefault = true;
          // We assume that the app provided engine was removed due to a
          // configuration change, and therefore we have re-added the add-on
          // search engine. It is possible that it was actually due to a
          // locale/region change, but that is harder to detect here.
          changeReason = Ci.nsISearchService.CHANGE_REASON_CONFIG;
          newEngine.copyUserSettingsFrom(previouslyOverridden);
        }
      }
    }

    this.#addEngineToStore(newEngine);
    if (shouldSetAsDefault) {
      this.#setEngineDefault(false, newEngine, changeReason);
    }
  }

  /**
   * Called when we see an upgrade to an existing search extension.
   *
   * @param {object} extension
   *   An Extension object containing data about the extension.
   */
  async #upgradeExtensionEngine(extension) {
    let extensionEngines = await this.getEnginesByExtensionID(extension.id);

    for (let engine of extensionEngines) {
      let isDefault = engine == this.defaultEngine;
      let isDefaultPrivate = engine == this.defaultPrivateEngine;

      let originalName = engine.name;
      let locale = engine._locale || lazy.SearchUtils.DEFAULT_TAG;

      await engine.update({
        extension,
        locale,
      });

      if (engine.name != originalName) {
        if (isDefault) {
          this._settings.setVerifiedMetaDataAttribute(
            "defaultEngineId",
            engine.id
          );
        }
        if (isDefaultPrivate) {
          this._settings.setVerifiedMetaDataAttribute(
            "privateDefaultEngineId",
            engine.id
          );
        }
        this._cachedSortedEngines = null;
      }
    }
    return extensionEngines;
  }

  #internalRemoveEngine(engine) {
    // Remove the engine from _sortedEngines
    if (this._cachedSortedEngines) {
      var index = this._cachedSortedEngines.indexOf(engine);
      if (index == -1) {
        throw Components.Exception(
          "Can't find engine to remove in _sortedEngines!",
          Cr.NS_ERROR_FAILURE
        );
      }
      this._cachedSortedEngines.splice(index, 1);
    }

    // Remove the engine from the internal store
    this._engines.delete(engine.id);
  }

  /**
   * Helper function to find a new default engine and set it. This could
   * be used if there is not default set yet, or if the current default is
   * being removed.
   *
   * This function will not consider engines that have a `pendingRemoval`
   * property set to true.
   *
   * The new default will be chosen from (in order):
   *
   * - Existing default from configuration, if it is not hidden.
   * - The first non-hidden engine that is a general search engine.
   * - If all other engines are hidden, unhide the default from the configuration.
   * - If the default from the configuration is the one being removed, unhide
   *   the first general search engine, or first visible engine.
   *
   * @param {boolean} privateMode
   *   If true, returns the default engine for private browsing mode, otherwise
   *   the default engine for the normal mode. Note, this function does not
   *   check the "separatePrivateDefault" preference - that is up to the caller.
   * @returns {nsISearchEngine|null}
   *   The appropriate search engine, or null if one could not be determined.
   */
  #findAndSetNewDefaultEngine({ privateMode }) {
    // First to the app default engine...
    let newDefault = privateMode
      ? this.appPrivateDefaultEngine
      : this.appDefaultEngine;

    if (!newDefault || newDefault.hidden || newDefault.pendingRemoval) {
      let sortedEngines = this.#sortedVisibleEngines;
      let generalSearchEngines = sortedEngines.filter(
        e => e.isGeneralPurposeEngine
      );

      // then to the first visible general search engine that isn't excluded...
      let firstVisible = generalSearchEngines.find(e => !e.pendingRemoval);
      if (firstVisible) {
        newDefault = firstVisible;
      } else if (newDefault) {
        // then to the app default if it is not the one that is excluded...
        if (!newDefault.pendingRemoval) {
          newDefault.hidden = false;
        } else {
          newDefault = null;
        }
      }

      // and finally as a last resort we unhide the first engine
      // even if the name is the same as the excluded one (should never happen).
      if (!newDefault) {
        if (!firstVisible) {
          sortedEngines = this.#sortedEngines;
          firstVisible = sortedEngines.find(e => e.isGeneralPurposeEngine);
          if (!firstVisible) {
            firstVisible = sortedEngines[0];
          }
        }
        if (firstVisible) {
          firstVisible.hidden = false;
          newDefault = firstVisible;
        }
      }
    }
    // We tried out best but something went very wrong.
    if (!newDefault) {
      lazy.logConsole.error("Could not find a replacement default engine.");
      return null;
    }

    // If the current engine wasn't set or was hidden, we used a fallback
    // to pick a new current engine. As soon as we return it, this new
    // current engine will become user-visible, so we should persist it.
    // by calling the setter.
    this.#setEngineDefault(privateMode, newDefault);

    return privateMode ? this.#currentPrivateEngine : this.#currentEngine;
  }

  /**
   * Helper function to set the current default engine.
   *
   * @param {boolean} privateMode
   *   If true, sets the default engine for private browsing mode, otherwise
   *   sets the default engine for the normal mode. Note, this function does not
   *   check the "separatePrivateDefault" preference - that is up to the caller.
   * @param {nsISearchEngine} newEngine
   *   The search engine to select
   * @param {SearchUtils.REASON_CHANGE_MAP} changeSource
   *   The source of the change of engine.
   */
  #setEngineDefault(privateMode, newEngine, changeSource) {
    // Sometimes we get wrapped nsISearchEngine objects (external XPCOM callers),
    // and sometimes we get raw Engine JS objects (callers in this file), so
    // handle both.
    if (
      !(newEngine instanceof Ci.nsISearchEngine) &&
      !(newEngine instanceof lazy.SearchEngine)
    ) {
      throw Components.Exception(
        "Invalid argument passed to defaultEngine setter",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    const newCurrentEngine = this._engines.get(newEngine.id);
    if (!newCurrentEngine) {
      throw Components.Exception(
        "Can't find engine in store!",
        Cr.NS_ERROR_UNEXPECTED
      );
    }

    if (!newCurrentEngine.isAppProvided) {
      // If a non default engine is being set as the current engine, ensure
      // its loadPath has a verification hash.
      if (!newCurrentEngine._loadPath) {
        newCurrentEngine._loadPath = "[other]unknown";
      }
      let loadPathHash = lazy.SearchUtils.getVerificationHash(
        newCurrentEngine._loadPath
      );
      let currentHash = newCurrentEngine.getAttr("loadPathHash");
      if (!currentHash || currentHash != loadPathHash) {
        newCurrentEngine.setAttr("loadPathHash", loadPathHash);
        lazy.SearchUtils.notifyAction(
          newCurrentEngine,
          lazy.SearchUtils.MODIFIED_TYPE.CHANGED
        );
      }
    }

    let currentEngine = privateMode
      ? this.#currentPrivateEngine
      : this.#currentEngine;

    if (newCurrentEngine == currentEngine) {
      return;
    }

    // Ensure that we reset an engine override if it was previously overridden.
    currentEngine?.removeExtensionOverride();

    if (privateMode) {
      this.#currentPrivateEngine = newCurrentEngine;
    } else {
      this.#currentEngine = newCurrentEngine;
    }

    // If we change the default engine in the future, that change should impact
    // users who have switched away from and then back to the build's
    // "app default" engine. So clear the user pref when the currentEngine is
    // set to the build's app default engine, so that the currentEngine getter
    // falls back to whatever the default is.
    // However, we do not do this whilst we are running an experiment - an
    // experiment must preseve the user's choice of default engine during it's
    // runtime and when it ends. Once the experiment ends, we will reset the
    // attribute elsewhere.
    let newId = newCurrentEngine.id;
    const appDefaultEngine = privateMode
      ? this.appPrivateDefaultEngine
      : this.appDefaultEngine;
    if (
      newCurrentEngine == appDefaultEngine &&
      !lazy.NimbusFeatures.searchConfiguration.getVariable("experiment")
    ) {
      newId = "";
    }

    this._settings.setVerifiedMetaDataAttribute(
      privateMode ? "privateDefaultEngineId" : "defaultEngineId",
      newId
    );

    // Only do this if we're initialized though - this function can get called
    // during initalization.
    if (this.isInitialized) {
      this.#recordDefaultChangedEvent(
        privateMode,
        currentEngine,
        newCurrentEngine,
        changeSource
      );
      this.#recordTelemetryData();
    }

    lazy.SearchUtils.notifyAction(
      newCurrentEngine,
      lazy.SearchUtils.MODIFIED_TYPE[
        privateMode ? "DEFAULT_PRIVATE" : "DEFAULT"
      ]
    );
    // If we've not got a separate private active, notify update of the
    // private so that the UI updates correctly.
    if (!privateMode && !this.#separatePrivateDefault) {
      lazy.SearchUtils.notifyAction(
        newCurrentEngine,
        lazy.SearchUtils.MODIFIED_TYPE.DEFAULT_PRIVATE
      );
    }
  }

  #onSeparateDefaultPrefChanged(prefName, previousValue, currentValue) {
    // Clear out the sorted engines settings, so that we re-sort it if necessary.
    this._cachedSortedEngines = null;
    // We should notify if the normal default, and the currently saved private
    // default are different. Otherwise, save the energy.
    if (this.defaultEngine != this._getEngineDefault(true)) {
      lazy.SearchUtils.notifyAction(
        // Always notify with the new private engine, the function checks
        // the preference value for us.
        this.defaultPrivateEngine,
        lazy.SearchUtils.MODIFIED_TYPE.DEFAULT_PRIVATE
      );
    }
    // Always notify about the change of status of private default if the user
    // toggled the UI.
    if (
      prefName ==
      lazy.SearchUtils.BROWSER_SEARCH_PREF + "separatePrivateDefault"
    ) {
      if (!previousValue && currentValue) {
        this.#recordDefaultChangedEvent(
          true,
          null,
          this._getEngineDefault(true),
          Ci.nsISearchService.CHANGE_REASON_USER_PRIVATE_SPLIT
        );
      } else {
        this.#recordDefaultChangedEvent(
          true,
          this._getEngineDefault(true),
          null,
          Ci.nsISearchService.CHANGE_REASON_USER_PRIVATE_SPLIT
        );
      }
    }
    // Update the telemetry data.
    this.#recordTelemetryData();
  }

  #getEngineInfo(engine) {
    if (!engine) {
      // The defaultEngine getter will throw if there's no engine at all,
      // which shouldn't happen unless an add-on or a test deleted all of them.
      // Our preferences UI doesn't let users do that.
      console.error("getDefaultEngineInfo: No default engine");
      return ["NONE", { name: "NONE" }];
    }

    const engineData = {
      loadPath: engine._loadPath,
      name: engine.name ? engine.name : "",
    };

    if (engine.isAppProvided) {
      engineData.origin = "default";
    } else {
      let currentHash = engine.getAttr("loadPathHash");
      if (!currentHash) {
        engineData.origin = "unverified";
      } else {
        let loadPathHash = lazy.SearchUtils.getVerificationHash(
          engine._loadPath
        );
        engineData.origin =
          currentHash == loadPathHash ? "verified" : "invalid";
      }
    }

    // For privacy, we only collect the submission URL for default engines...
    let sendSubmissionURL = engine.isAppProvided;

    if (!sendSubmissionURL) {
      // ... or engines that are the same domain as a default engine.
      let engineHost = engine.searchUrlDomain;
      for (let innerEngine of this._engines.values()) {
        if (!innerEngine.isAppProvided) {
          continue;
        }

        if (innerEngine.searchUrlDomain == engineHost) {
          sendSubmissionURL = true;
          break;
        }
      }

      if (!sendSubmissionURL) {
        // ... or well known search domains.
        //
        // Starts with: www.google., search.aol., yandex.
        // or
        // Ends with: search.yahoo.com, .ask.com, .bing.com, .startpage.com, baidu.com, duckduckgo.com
        const urlTest =
          /^(?:www\.google\.|search\.aol\.|yandex\.)|(?:search\.yahoo|\.ask|\.bing|\.startpage|\.baidu|duckduckgo)\.com$/;
        sendSubmissionURL = urlTest.test(engineHost);
      }
    }

    if (sendSubmissionURL) {
      let uri = engine.searchURLWithNoTerms;
      uri = uri
        .mutate()
        .setUserPass("") // Avoid reporting a username or password.
        .finalize();
      engineData.submissionURL = uri.spec;
    }

    return [engine.telemetryId, engineData];
  }

  /**
   * Records an event for where the default engine is changed. This is
   * recorded to both Glean and Telemetry.
   *
   * The Glean GIFFT functionality is not used here because we use longer
   * names in the extra arguments to the event.
   *
   * @param {boolean} isPrivate
   *   True if this is a event about a private engine.
   * @param {SearchEngine} [previousEngine]
   *   The previously default search engine.
   * @param {SearchEngine} [newEngine]
   *   The new default search engine.
   * @param {string} changeSource
   *   The source of the change of default.
   */
  #recordDefaultChangedEvent(
    isPrivate,
    previousEngine,
    newEngine,
    changeSource = Ci.nsISearchService.CHANGE_REASON_UNKNOWN
  ) {
    changeSource = REASON_CHANGE_MAP.get(changeSource) ?? "unknown";
    Services.telemetry.setEventRecordingEnabled("search", true);
    let telemetryId;
    let engineInfo;
    // If we are toggling the separate private browsing settings, we might not
    // have an engine to record.
    if (newEngine) {
      [telemetryId, engineInfo] = this.#getEngineInfo(newEngine);
    } else {
      telemetryId = "";
      engineInfo = {
        name: "",
        loadPath: "",
        submissionURL: "",
      };
    }

    let submissionURL = engineInfo.submissionURL ?? "";
    Services.telemetry.recordEvent(
      "search",
      "engine",
      isPrivate ? "change_private" : "change_default",
      changeSource,
      {
        // In docshell tests, the previous engine does not exist, so we allow
        // for the previousEngine to be undefined.
        prev_id: previousEngine?.telemetryId ?? "",
        new_id: telemetryId,
        new_name: engineInfo.name,
        new_load_path: engineInfo.loadPath,
        // Telemetry has a limit of 80 characters.
        new_sub_url: submissionURL.slice(0, 80),
      }
    );

    let extraArgs = {
      // In docshell tests, the previous engine does not exist, so we allow
      // for the previousEngine to be undefined.
      previous_engine_id: previousEngine?.telemetryId ?? "",
      new_engine_id: telemetryId,
      new_display_name: engineInfo.name,
      new_load_path: engineInfo.loadPath,
      // Glean has a limit of 100 characters.
      new_submission_url: submissionURL.slice(0, 100),
      change_source: changeSource,
    };
    if (isPrivate) {
      Glean.searchEnginePrivate.changed.record(extraArgs);
    } else {
      Glean.searchEngineDefault.changed.record(extraArgs);
    }
  }

  /**
   * Records the user's current default engine (normal and private) data to
   * telemetry.
   */
  #recordTelemetryData() {
    let info = this.getDefaultEngineInfo();

    Glean.searchEngineDefault.engineId.set(info.defaultSearchEngine);
    Glean.searchEngineDefault.displayName.set(
      info.defaultSearchEngineData.name
    );
    Glean.searchEngineDefault.loadPath.set(
      info.defaultSearchEngineData.loadPath
    );
    Glean.searchEngineDefault.submissionUrl.set(
      info.defaultSearchEngineData.submissionURL ?? "blank:"
    );
    Glean.searchEngineDefault.verified.set(info.defaultSearchEngineData.origin);

    Glean.searchEnginePrivate.engineId.set(
      info.defaultPrivateSearchEngine ?? ""
    );

    if (info.defaultPrivateSearchEngineData) {
      Glean.searchEnginePrivate.displayName.set(
        info.defaultPrivateSearchEngineData.name
      );
      Glean.searchEnginePrivate.loadPath.set(
        info.defaultPrivateSearchEngineData.loadPath
      );
      Glean.searchEnginePrivate.submissionUrl.set(
        info.defaultPrivateSearchEngineData.submissionURL ?? "blank:"
      );
      Glean.searchEnginePrivate.verified.set(
        info.defaultPrivateSearchEngineData.origin
      );
    } else {
      Glean.searchEnginePrivate.displayName.set("");
      Glean.searchEnginePrivate.loadPath.set("");
      Glean.searchEnginePrivate.submissionUrl.set("blank:");
      Glean.searchEnginePrivate.verified.set("");
    }
  }

  /**
   * This function is called at the beginning of search service init.
   * If the error type set in a test environment matches errorType
   * passed to this function, we throw an error.
   *
   * @param {string} errorType
   *   The error that can occur during search service init.
   */
  #maybeThrowErrorInTest(errorType) {
    if (
      Services.env.exists("XPCSHELL_TEST_PROFILE_DIR") &&
      this.errorToThrowInTest === errorType
    ) {
      throw new Error(
        `Fake ${errorType} error during search service initialization.`
      );
    }
  }

  #buildParseSubmissionMap() {
    this.#parseSubmissionMap = new Map();

    // Used only while building the map, indicates which entries do not refer to
    // the main domain of the engine but to an alternate domain, for example
    // "www.google.fr" for the "www.google.com" search engine.
    let keysOfAlternates = new Set();

    for (let engine of this.#sortedEngines) {
      if (engine.hidden) {
        continue;
      }

      let urlParsingInfo = engine.getURLParsingInfo();
      if (!urlParsingInfo) {
        continue;
      }

      // Store the same object on each matching map key, as an optimization.
      let mapValueForEngine = {
        engine,
        termsParameterName: urlParsingInfo.termsParameterName,
      };

      let processDomain = (domain, isAlternate) => {
        let key = domain + urlParsingInfo.path;

        // Apply the logic for which main domains take priority over alternate
        // domains, even if they are found later in the ordered engine list.
        let existingEntry = this.#parseSubmissionMap.get(key);
        if (!existingEntry) {
          if (isAlternate) {
            keysOfAlternates.add(key);
          }
        } else if (!isAlternate && keysOfAlternates.has(key)) {
          keysOfAlternates.delete(key);
        } else {
          return;
        }

        this.#parseSubmissionMap.set(key, mapValueForEngine);
      };

      processDomain(urlParsingInfo.mainDomain, false);
      lazy.SearchStaticData.getAlternateDomains(
        urlParsingInfo.mainDomain
      ).forEach(d => processDomain(d, true));
    }
  }

  #nimbusSearchUpdatedFun = null;

  async #nimbusSearchUpdated() {
    this.#checkNimbusPrefs();
    Services.search.wrappedJSObject._maybeReloadEngines(
      Ci.nsISearchService.CHANGE_REASON_EXPERIMENT
    );
  }

  /**
   * Check the prefs are correctly updated for users enrolled in a Nimbus experiment.
   *
   * @param {boolean} isStartup
   *   Whether this function was called as part of the startup flow.
   */
  #checkNimbusPrefs(isStartup = false) {
    // If we are in an experiment we may need to check the status on startup, otherwise
    // ignore the call to check on startup so we do not reset users prefs when they are
    // not an experiment.
    if (
      isStartup &&
      !lazy.NimbusFeatures.searchConfiguration.getVariable("experiment")
    ) {
      return;
    }
    let nimbusPrivateDefaultUIEnabled =
      lazy.NimbusFeatures.searchConfiguration.getVariable(
        "seperatePrivateDefaultUIEnabled"
      );
    let nimbusPrivateDefaultUrlbarResultEnabled =
      lazy.NimbusFeatures.searchConfiguration.getVariable(
        "seperatePrivateDefaultUrlbarResultEnabled"
      );

    let previousPrivateDefault = this.defaultPrivateEngine;
    let uiWasEnabled = this._separatePrivateDefaultEnabledPrefValue;
    if (
      this._separatePrivateDefaultEnabledPrefValue !=
      nimbusPrivateDefaultUIEnabled
    ) {
      Services.prefs.setBoolPref(
        `${lazy.SearchUtils.BROWSER_SEARCH_PREF}separatePrivateDefault.ui.enabled`,
        nimbusPrivateDefaultUIEnabled
      );
      let newPrivateDefault = this.defaultPrivateEngine;
      if (previousPrivateDefault != newPrivateDefault) {
        if (!uiWasEnabled) {
          this.#recordDefaultChangedEvent(
            true,
            null,
            newPrivateDefault,
            Ci.nsISearchService.CHANGE_REASON_EXPERIMENT
          );
        } else {
          this.#recordDefaultChangedEvent(
            true,
            previousPrivateDefault,
            null,
            Ci.nsISearchService.CHANGE_REASON_EXPERIMENT
          );
        }
      }
    }
    if (
      this.separatePrivateDefaultUrlbarResultEnabled !=
      nimbusPrivateDefaultUrlbarResultEnabled
    ) {
      Services.prefs.setBoolPref(
        `${lazy.SearchUtils.BROWSER_SEARCH_PREF}separatePrivateDefault.urlbarResult.enabled`,
        nimbusPrivateDefaultUrlbarResultEnabled
      );
    }
  }

  #addObservers() {
    if (this.#observersAdded) {
      // There might be a race between synchronous and asynchronous
      // initialization for which we try to register the observers twice.
      return;
    }
    this.#observersAdded = true;

    this.#nimbusSearchUpdatedFun = this.#nimbusSearchUpdated.bind(this);
    lazy.NimbusFeatures.searchConfiguration.onUpdate(
      this.#nimbusSearchUpdatedFun
    );

    Services.obs.addObserver(this, lazy.SearchUtils.TOPIC_ENGINE_MODIFIED);
    Services.obs.addObserver(this, QUIT_APPLICATION_TOPIC);
    Services.obs.addObserver(this, TOPIC_LOCALES_CHANGE);

    this._settings.addObservers();

    // The current stage of shutdown. Used to help analyze crash
    // signatures in case of shutdown timeout.
    let shutdownState = {
      step: "Not started",
      latestError: {
        message: undefined,
        stack: undefined,
      },
    };
    IOUtils.profileBeforeChange.addBlocker(
      "Search service: shutting down",
      () =>
        (async () => {
          // If we are in initialization, then don't attempt to save the settings.
          // It is likely that shutdown will have caused the add-on manager to
          // stop, which can cause initialization to fail.
          // Hence at that stage, we could have broken settings which we don't
          // want to write.
          // The good news is, that if we don't write the settings here, we'll
          // detect the out-of-date settings on next state, and automatically
          // rebuild it.
          if (!this.isInitialized) {
            lazy.logConsole.warn(
              "not saving settings on shutdown due to initializing."
            );
            return;
          }

          try {
            await this._settings.shutdown(shutdownState);
          } catch (ex) {
            // Ensure that error is reported and that it causes tests
            // to fail, otherwise ignore it.
            Promise.reject(ex);
          }
        })(),

      () => shutdownState
    );
  }

  // This is prefixed with _ rather than # because it is
  // called in a test.
  _removeObservers() {
    if (this.ignoreListListener) {
      lazy.IgnoreLists.unsubscribe(this.ignoreListListener);
      delete this.ignoreListListener;
    }
    if (this.#queuedIdle) {
      this.idleService.removeIdleObserver(this, RECONFIG_IDLE_TIME_SEC);
      this.#queuedIdle = false;
    }

    this._settings.removeObservers();

    lazy.NimbusFeatures.searchConfiguration.offUpdate(
      this.#nimbusSearchUpdatedFun
    );

    Services.obs.removeObserver(this, lazy.SearchUtils.TOPIC_ENGINE_MODIFIED);
    Services.obs.removeObserver(this, QUIT_APPLICATION_TOPIC);
    Services.obs.removeObserver(this, TOPIC_LOCALES_CHANGE);
    Services.obs.removeObserver(this, lazy.Region.REGION_TOPIC);
  }

  QueryInterface = ChromeUtils.generateQI([
    "nsISearchService",
    "nsIObserver",
    "nsITimerCallback",
  ]);

  // nsIObserver
  observe(engine, topic, verb) {
    switch (topic) {
      case lazy.SearchUtils.TOPIC_ENGINE_MODIFIED:
        switch (verb) {
          case lazy.SearchUtils.MODIFIED_TYPE.ADDED:
            this.#parseSubmissionMap = null;
            break;
          case lazy.SearchUtils.MODIFIED_TYPE.CHANGED:
            engine = engine.wrappedJSObject;
            if (
              engine == this.defaultEngine ||
              engine == this.defaultPrivateEngine
            ) {
              this.#recordDefaultChangedEvent(
                engine != this.defaultEngine,
                engine,
                engine,
                Ci.nsISearchService.CHANGE_REASON_ENGINE_UPDATE
              );
            }
            this.#parseSubmissionMap = null;
            break;
          case lazy.SearchUtils.MODIFIED_TYPE.REMOVED:
            // Invalidate the map used to parse URLs to search engines.
            this.#parseSubmissionMap = null;
            break;
        }
        break;

      case "idle": {
        this.idleService.removeIdleObserver(this, RECONFIG_IDLE_TIME_SEC);
        this.#queuedIdle = false;
        lazy.logConsole.debug(
          "Reloading engines after idle due to configuration change"
        );
        this._maybeReloadEngines(
          Ci.nsISearchService.CHANGE_REASON_CONFIG
        ).catch(console.error);
        break;
      }

      case QUIT_APPLICATION_TOPIC:
        this._removeObservers();
        break;

      case TOPIC_LOCALES_CHANGE:
        // Locale changed. Re-init. We rely on observers, because we can't
        // return this promise to anyone.

        // At the time of writing, when the user does a "Apply and Restart" for
        // a new language the preferences code triggers the locales change and
        // restart straight after, so we delay the check, which means we should
        // be able to avoid the reload on shutdown, and we'll sort it out
        // on next startup.
        // This also helps to avoid issues with the add-on manager shutting
        // down at the same time (see _reInit for more info).
        Services.tm.dispatchToMainThread(() => {
          if (!Services.startup.shuttingDown) {
            this._maybeReloadEngines(
              Ci.nsISearchService.CHANGE_REASON_LOCALE
            ).catch(console.error);
          }
        });
        break;
      case lazy.Region.REGION_TOPIC:
        lazy.logConsole.debug("Region updated:", lazy.Region.home);
        this._maybeReloadEngines(
          Ci.nsISearchService.CHANGE_REASON_REGION
        ).catch(console.error);
        break;
    }
  }

  /**
   * @param {object} metaData
   *    The metadata object that defines the details of the engine.
   * @returns {boolean}
   *    Returns true if metaData has different property values than
   *    the cached _metaData.
   */
  #didSettingsMetaDataUpdate(metaData) {
    let metaDataProperties = [
      "locale",
      "region",
      "channel",
      "experiment",
      "distroID",
    ];

    return metaDataProperties.some(p => {
      return metaData?.[p] !== this._settings.getMetaDataAttribute(p);
    });
  }

  /**
   * Shows an infobar to notify the user their default search engine has been
   * removed and replaced by a new default search engine.
   *
   * This method is prefixed with _ rather than # because it is
   * called in a test.
   *
   * @param {string} prevCurrentEngineName
   *   The name of the previous default engine that will be replaced.
   * @param {string} newCurrentEngineName
   *   The name of the engine that will be the new default engine.
   */
  _showRemovalOfSearchEngineNotificationBox(
    prevCurrentEngineName,
    newCurrentEngineName
  ) {
    let win = Services.wm.getMostRecentBrowserWindow();
    win.BrowserSearch.removalOfSearchEngineNotificationBox(
      prevCurrentEngineName,
      newCurrentEngineName
    );
  }

  /**
   * Maybe starts the timer for OpenSearch engine updates. This will be set
   * only if updates are enabled and there are OpenSearch engines installed
   * which have updates.
   */
  #maybeStartOpenSearchUpdateTimer() {
    if (
      this.#openSearchUpdateTimerStarted ||
      !Services.prefs.getBoolPref(
        lazy.SearchUtils.BROWSER_SEARCH_PREF + "update",
        true
      )
    ) {
      return;
    }

    let engineWithUpdates = [...this._engines.values()].some(
      engine => engine instanceof lazy.OpenSearchEngine && engine.hasUpdates
    );

    if (engineWithUpdates) {
      lazy.logConsole.debug("Engine with updates found, setting update timer");
      lazy.timerManager.registerTimer(
        OPENSEARCH_UPDATE_TIMER_TOPIC,
        this,
        OPENSEARCH_UPDATE_TIMER_INTERVAL,
        true
      );
      this.#openSearchUpdateTimerStarted = true;
    }
  }
} // end SearchService class

XPCOMUtils.defineLazyServiceGetter(
  SearchService.prototype,
  "idleService",
  "@mozilla.org/widget/useridleservice;1",
  "nsIUserIdleService"
);

/**
 * Handles getting and checking extensions against the allow list.
 */
class SearchDefaultOverrideAllowlistHandler {
  constructor() {
    this._remoteConfig = lazy.RemoteSettings(
      lazy.SearchUtils.SETTINGS_ALLOWLIST_KEY
    );
  }

  /**
   * Determines if a search engine extension can override a default one
   * according to the allow list.
   *
   * @param {object} extension
   *   The extension object (from add-on manager) that will override the
   *   app provided search engine.
   * @param {string} appProvidedEngineId
   *   The id of the search engine that will be overriden.
   * @returns {boolean}
   *   Returns true if the search engine extension may override the app provided
   *   instance.
   */
  async canOverride(extension, appProvidedEngineId) {
    const overrideTable = await this._getAllowlist();

    let entry = overrideTable.find(e => e.thirdPartyId == extension.id);
    if (!entry) {
      return false;
    }

    if (appProvidedEngineId != entry.overridesAppIdv2) {
      return false;
    }

    let searchProvider =
      extension.manifest.chrome_settings_overrides.search_provider;

    return entry.urls.some(
      e =>
        searchProvider.search_url == e.search_url &&
        searchProvider.search_url_get_params == e.search_url_get_params &&
        searchProvider.search_url_post_params == e.search_url_post_params
    );
  }

  /**
   * Determines if an existing search engine is allowed to override a default one
   * according to the allow list.
   *
   * @param {SearchEngine} engine
   *   The existing search engine.
   * @param {string} appProvidedEngineId
   *   The id of the search engine that will be overriden.
   * @returns {boolean}
   *   Returns true if the existing search engine is allowed to override the
   *   app provided instance.
   */
  async canEngineOverride(engine, appProvidedEngineId) {
    const overrideEntries = await this._getAllowlist();

    let entry;

    if (engine instanceof lazy.AddonSearchEngine) {
      entry = overrideEntries.find(e => e.thirdPartyId == engine._extensionID);
    } else if (engine instanceof lazy.OpenSearchEngine) {
      entry = overrideEntries.find(
        e =>
          e.thirdPartyId == "opensearch@search.mozilla.org" &&
          e.engineName == engine.name
      );
    }
    if (!entry) {
      return false;
    }

    if (appProvidedEngineId != entry.overridesAppIdv2) {
      return false;
    }

    return entry.urls.some(urlSet =>
      engine.checkSearchUrlMatchesManifest(urlSet)
    );
  }

  /**
   * Obtains the configuration from remote settings. This includes
   * verifying the signature of the record within the database.
   *
   * If the signature in the database is invalid, the database will be wiped
   * and the stored dump will be used, until the settings next update.
   *
   * Note that this may cause a network check of the certificate, but that
   * should generally be quick.
   *
   * @returns {Array}
   *   An array of objects in the database, or an empty array if none
   *   could be obtained.
   */
  async _getAllowlist() {
    let result = [];
    try {
      result = await this._remoteConfig.get();
    } catch (ex) {
      // Don't throw an error just log it, just continue with no data, and hopefully
      // a sync will fix things later on.
      console.error(ex);
    }
    lazy.logConsole.debug("Allow list is:", result);
    return result;
  }
}
PK
!<���Y�Ymodules/SearchSettings.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AppProvidedSearchEngine:
    "resource://gre/modules/AppProvidedSearchEngine.sys.mjs",
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
  SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "logConsole", () => {
  return console.createInstance({
    prefix: "SearchSettings",
    maxLogLevel: lazy.SearchUtils.loggingEnabled ? "Debug" : "Warn",
  });
});

const SETTINGS_FILENAME = "search.json.mozlz4";

/**
 * A map of engine ids to their previous names. These are required for
 * ensuring that user's settings are correctly migrated for users upgrading
 * from a settings file prior to settings version 7 (Firefox 108).
 *
 * @type {Map<string, string>}
 */
const ENGINE_ID_TO_OLD_NAME_MAP = new Map([
  ["wikipedia-hy", "Wikipedia (hy)"],
  ["wikipedia-kn", "Wikipedia (kn)"],
  ["wikipedia-lv", "Vikipēdija"],
  ["wikipedia-NO", "Wikipedia (no)"],
  ["wikipedia-el", "Wikipedia (el)"],
  ["wikipedia-lt", "Wikipedia (lt)"],
  ["wikipedia-my", "Wikipedia (my)"],
  ["wikipedia-pa", "Wikipedia (pa)"],
  ["wikipedia-pt", "Wikipedia (pt)"],
  ["wikipedia-si", "Wikipedia (si)"],
  ["wikipedia-tr", "Wikipedia (tr)"],
]);

/**
 * This class manages the saves search settings.
 *
 * Global settings can be saved and obtained from this class via the
 * `*Attribute` methods.
 */
export class SearchSettings {
  constructor(searchService) {
    this.#searchService = searchService;

    // Once the search service has initialized, schedule a write to ensure
    // that any settings that may have changed or need updating are handled.
    searchService.promiseInitialized.then(() => {
      this._delayedWrite();
    });
  }

  QueryInterface = ChromeUtils.generateQI([Ci.nsIObserver]);

  // Delay for batching invalidation of the JSON settings (ms)
  static SETTINGS_INVALIDATION_DELAY = 1000;

  /**
   * A reference to the pending DeferredTask, if there is one.
   */
  _batchTask = null;

  /**
   * A reference to the search service so that we can save the engines list.
   */
  #searchService = null;

  /*
   * The user's settings file read from disk so we can persist metadata for
   * engines that are default or hidden, the user's locale and region, hashes
   * for the loadPath, and hashes for default and private default engines.
   * This is the JSON we read from disk and save to disk when there's an update
   * to the settings.
   *
   * Structure of settings:
   * Object { version: <number>,
   *          engines: [...],
   *          metaData: {...},
   *        }
   *
   * Settings metaData is the active metadata for setting and getting attributes.
   * When a new metadata attribute is set, we save it to #settings.metaData and
   * write #settings to disk.
   *
   * #settings.metaData attributes:
   * @property {string} current
   *    The current user-set default engine. The associated hash is called
   *    'hash'.
   * @property {string} private
   *    The current user-set private engine. The associated hash is called
   *    'privateHash'.
   *    The current and prviate objects have associated hash fields to validate
   *    the value is set by the application.
   * @property {string} appDefaultEngine
   * @property {string} channel
   *    Configuration is restricted to the specified channel. ESR is an example
   *    of a channel.
   * @property {string} distroID
   *    Specifies which distribution the default engine is included in.
   * @property {string} experiment
   *    Specifies if the application is running on an experiment.
   * @property {string} locale
   * @property {string} region
   * @property {boolean} useSavedOrder
   *    True if the user's order information stored in settings is used.
   *
   */
  #settings = null;

  /**
   * #cachedSettings is updated when we read the settings from disk and when
   * we write settings to disk. #cachedSettings is compared with #settings
   * before we do a write to disk. If there's no change to the settings
   * attributes, then we don't write the settings to disk.
   *
   * This is a deep copy of #settings.
   *
   * @type {object}
   */
  #cachedSettings = {};

  addObservers() {
    Services.obs.addObserver(this, lazy.SearchUtils.TOPIC_ENGINE_MODIFIED);
    Services.obs.addObserver(this, lazy.SearchUtils.TOPIC_SEARCH_SERVICE);
  }

  /**
   * Cleans up, removing observers.
   */
  removeObservers() {
    Services.obs.removeObserver(this, lazy.SearchUtils.TOPIC_ENGINE_MODIFIED);
    Services.obs.removeObserver(this, lazy.SearchUtils.TOPIC_SEARCH_SERVICE);
  }

  /**
   * Reads the settings file.
   *
   * @param {string} origin
   *   If this parameter is "test", then the settings will not be written. As
   *   some tests manipulate the settings directly, we allow turning off writing to
   *   avoid writing stale settings data.
   * @returns {object}
   *   Returns the settings file data.
   */
  async get(origin = "") {
    let json;
    await this._ensurePendingWritesCompleted(origin);
    try {
      let settingsFilePath = PathUtils.join(
        PathUtils.profileDir,
        SETTINGS_FILENAME
      );
      json = await IOUtils.readJSON(settingsFilePath, { decompress: true });
      if (!json.engines || !json.engines.length) {
        throw new Error("no engine in the file");
      }
    } catch (ex) {
      lazy.logConsole.debug("get: No settings file exists, new profile?", ex);
      json = {};
    }

    this.#settings = json;
    this.#cachedSettings = structuredClone(json);

    if (!this.#settings.metaData) {
      this.#settings.metaData = {};
    }

    // Versions of gecko older than 82 stored the order flag as a preference.
    // This was changed in version 6 of the settings file.
    if (
      this.#settings.version < 6 ||
      !("useSavedOrder" in this.#settings.metaData)
    ) {
      const prefName = lazy.SearchUtils.BROWSER_SEARCH_PREF + "useDBForOrder";
      let useSavedOrder = Services.prefs.getBoolPref(prefName, false);

      this.setMetaDataAttribute("useSavedOrder", useSavedOrder);

      // Clear the old pref so it isn't lying around.
      Services.prefs.clearUserPref(prefName);
    }

    // Added in Firefox 110.
    if (this.#settings.version < 8 && Array.isArray(this.#settings.engines)) {
      this.#migrateTelemetryLoadPaths();
    }

    // Migration for hiddenOneOffs
    if (this.#settings.version < 9 && this.#settings.engines) {
      const hiddenOneOffsPrefs = Services.prefs.getStringPref(
        "browser.search.hiddenOneOffs",
        ""
      );
      for (const engine of this.#settings.engines) {
        engine._metaData.hideOneOffButton = hiddenOneOffsPrefs.includes(
          engine._name
        );
      }
      Services.prefs.clearUserPref("browser.search.hiddenOneOffs");
    }

    // Migration for new AppProvidedSearchEngine ID format
    if (
      this.#settings.version > 6 &&
      this.#settings.version < 10 &&
      this.#settings.engines
    ) {
      let changedEngines = new Map();
      for (let engine of this.#settings.engines) {
        if (engine._isAppProvided && engine.id) {
          let oldId = engine.id;
          engine.id = engine.id
            .replace("@search.mozilla.orgdefault", "")
            .replace("@search.mozilla.org", "-");
          changedEngines.set(oldId, engine.id);
        }
      }

      const PROPERTIES_CONTAINING_IDS = [
        "privateDefaultEngineId",
        "appDefaultEngineId",
        "defaultEngineId",
      ];

      for (let prop of PROPERTIES_CONTAINING_IDS) {
        if (changedEngines.has(this.#settings.metaData[prop])) {
          this.#settings.metaData[prop] = changedEngines.get(
            this.#settings.metaData[prop]
          );
        }
      }
    }

    return structuredClone(json);
  }

  /**
   * Queues writing the settings until after SETTINGS_INVALIDATION_DELAY. If there
   * is a currently queued task then it will be restarted.
   */
  _delayedWrite() {
    if (this._batchTask) {
      this._batchTask.disarm();
    } else {
      let task = async () => {
        if (
          !this.#searchService.isInitialized ||
          this.#searchService._reloadingEngines
        ) {
          // Re-arm the task as we don't want to save potentially incomplete
          // information during the middle of (re-)initializing.
          this._batchTask.arm();
          return;
        }
        lazy.logConsole.debug("batchTask: Invalidating engine settings");
        await this._write();
      };
      this._batchTask = new lazy.DeferredTask(
        task,
        SearchSettings.SETTINGS_INVALIDATION_DELAY
      );
    }
    this._batchTask.arm();
  }

  /**
   * Ensures any pending writes of the settings are completed.
   *
   * @param {string} origin
   *   If this parameter is "test", then the settings will not be written. As
   *   some tests manipulate the settings directly, we allow turning off writing to
   *   avoid writing stale settings data.
   */
  async _ensurePendingWritesCompleted(origin = "") {
    // Before we read the settings file, first make sure all pending tasks are clear.
    if (!this._batchTask) {
      return;
    }
    lazy.logConsole.debug("finalizing batch task");
    let task = this._batchTask;
    this._batchTask = null;
    // Tests manipulate the settings directly, so let's not double-write with
    // stale settings data here.
    if (origin == "test") {
      task.disarm();
    } else {
      await task.finalize();
    }
  }

  /**
   * Writes the settings to disk (no delay).
   */
  async _write() {
    if (this._batchTask) {
      this._batchTask.disarm();
    }

    let settings = {};

    // Allows us to force a settings refresh should the settings format change.
    settings.version = lazy.SearchUtils.SETTINGS_VERSION;
    settings.engines = [...this.#searchService._engines.values()].map(engine =>
      JSON.parse(JSON.stringify(engine))
    );
    settings.metaData = this.#settings.metaData;

    // Persist metadata for AppProvided engines even if they aren't currently
    // active, this means if they become active again their settings
    // will be restored.
    if (this.#settings?.engines) {
      for (let engine of this.#settings.engines) {
        let included = settings.engines.some(e => e._name == engine._name);
        if (engine._isAppProvided && !included) {
          settings.engines.push(engine);
        }
      }
    }

    // Update the local copy.
    this.#settings = settings;

    try {
      if (!settings.engines.length) {
        throw new Error("cannot write without any engine.");
      }

      if (this.isCurrentAndCachedSettingsEqual()) {
        lazy.logConsole.debug(
          "_write: Settings unchanged. Did not write to disk."
        );
        Services.obs.notifyObservers(
          null,
          lazy.SearchUtils.TOPIC_SEARCH_SERVICE,
          "write-prevented-when-settings-unchanged"
        );
        Services.obs.notifyObservers(
          null,
          lazy.SearchUtils.TOPIC_SEARCH_SERVICE,
          "write-settings-to-disk-complete"
        );

        return;
      }

      // At this point, the settings and cached settings are different. We
      // write settings to disk and update #cachedSettings.
      this.#cachedSettings = structuredClone(this.#settings);

      lazy.logConsole.debug("_write: Writing to settings file.");
      let path = PathUtils.join(PathUtils.profileDir, SETTINGS_FILENAME);
      await IOUtils.writeJSON(path, settings, {
        compress: true,
        tmpPath: path + ".tmp",
      });
      lazy.logConsole.debug("_write: settings file written to disk.");
      Services.obs.notifyObservers(
        null,
        lazy.SearchUtils.TOPIC_SEARCH_SERVICE,
        "write-settings-to-disk-complete"
      );
    } catch (ex) {
      lazy.logConsole.error("_write: Could not write to settings file:", ex);
    }
  }

  /**
   * Sets an attribute without verification.
   *
   * @param {string} name
   *   The name of the attribute to set.
   * @param {*} val
   *   The value to set.
   */
  setMetaDataAttribute(name, val) {
    this.#settings.metaData[name] = val;
    this._delayedWrite();
  }

  /**
   * Sets a verified attribute. This will save an additional hash
   * value, that can be verified when reading back.
   *
   * @param {string} name
   *   The name of the attribute to set.
   * @param {*} val
   *   The value to set.
   */
  setVerifiedMetaDataAttribute(name, val) {
    this.#settings.metaData[name] = val;
    this.#settings.metaData[this.getHashName(name)] =
      lazy.SearchUtils.getVerificationHash(val);
    this._delayedWrite();
  }

  /**
   * Gets an attribute without verification.
   *
   * @param {string} name
   *   The name of the attribute to get.
   * @returns {*}
   *   The value of the attribute, or undefined if not known.
   */
  getMetaDataAttribute(name) {
    return this.#settings.metaData[name] ?? undefined;
  }

  /**
   * Gets a copy of the settings metadata.
   *
   * @returns {*}
   *   A copy of the settings metadata object.
   */
  getSettingsMetaData() {
    return { ...this.#settings.metaData };
  }

  /**
   * Gets a verified attribute.
   *
   * @param {string} name
   *   The name of the attribute to get.
   * @param {boolean} isAppProvided
   *   |true| if the engine associated with the attribute is an application
   *          provided engine.
   * @returns {*}
   *   The value of the attribute.
   *   We return undefined if the value of the attribute is not known or does
   *   not match the verification hash.
   */
  getVerifiedMetaDataAttribute(name, isAppProvided) {
    let attribute = this.getMetaDataAttribute(name);

    // If the selected engine is an application provided one, we can relax the
    // verification hash check to reduce the annoyance for users who
    // backup/sync their profile in custom ways.
    if (isAppProvided) {
      return attribute;
    }

    if (
      attribute &&
      this.getMetaDataAttribute(this.getHashName(name)) !=
        lazy.SearchUtils.getVerificationHash(attribute)
    ) {
      lazy.logConsole.warn(
        "getVerifiedMetaDataAttribute, invalid hash for",
        name
      );
      return undefined;
    }
    return attribute;
  }

  /**
   * Sets an attribute in #settings.engines._metaData
   *
   * @param {string} engineName
   *   The name of the engine.
   * @param {string} property
   *   The name of the attribute to set.
   * @param {*} value
   *   The value to set.
   */
  setEngineMetaDataAttribute(engineName, property, value) {
    let engines = [...this.#searchService._engines.values()];
    let engine = engines.find(engine => engine._name == engineName);
    if (engine) {
      engine._metaData[property] = value;
      this._delayedWrite();
    }
  }

  /**
   * Gets an attribute from #settings.engines._metaData
   *
   * @param {string} engineName
   *   The name of the engine.
   * @param {string} property
   *   The name of the attribute to get.
   * @returns {*}
   *   The value of the attribute, or undefined if not known.
   */
  getEngineMetaDataAttribute(engineName, property) {
    let engine = this.#settings.engines.find(
      engine => engine._name == engineName
    );
    return engine._metaData[property] ?? undefined;
  }

  /**
   * Returns the name for the hash for a particular attribute. This is
   * necessary because the default engine ID property is named `current`
   * with its hash as `hash`. All other hashes are in the `<name>Hash` format.
   *
   * @param {string} name
   *   The name of the attribute to get the hash name for.
   * @returns {string}
   *   The hash name to use.
   */
  getHashName(name) {
    // The "current" check remains here because we need to retrieve the
    // "current" hash name for the migration of engine ids. After the migration,
    // the "current" property is no longer used because we now store
    // "defaultEngineId" instead.
    if (name == "current") {
      return "hash";
    }
    return name + "Hash";
  }

  /**
   * Handles shutdown; writing the settings if necessary.
   *
   * @param {object} state
   *   The shutdownState object that is used to help analyzing the shutdown
   *   state in case of a crash or shutdown timeout.
   */
  async shutdown(state) {
    if (!this._batchTask) {
      return;
    }
    state.step = "Finalizing batched task";
    try {
      await this._batchTask.finalize();
      state.step = "Batched task finalized";
    } catch (ex) {
      state.step = "Batched task failed to finalize";

      state.latestError.message = "" + ex;
      if (ex && typeof ex == "object") {
        state.latestError.stack = ex.stack || undefined;
      }
    }
  }

  // nsIObserver
  observe(engine, topic, verb) {
    switch (topic) {
      case lazy.SearchUtils.TOPIC_ENGINE_MODIFIED:
        switch (verb) {
          case lazy.SearchUtils.MODIFIED_TYPE.ADDED:
          case lazy.SearchUtils.MODIFIED_TYPE.CHANGED:
          case lazy.SearchUtils.MODIFIED_TYPE.REMOVED:
            this._delayedWrite();
            break;
          case lazy.SearchUtils.MODIFIED_TYPE.ICON_CHANGED:
            // Application Provided Search Engines have their icons stored in
            // Remote Settings, so we don't need to update the saved settings.
            if (
              !(engine?.wrappedJSObject instanceof lazy.AppProvidedSearchEngine)
            ) {
              this._delayedWrite();
            }
            break;
        }
        break;
      case lazy.SearchUtils.TOPIC_SEARCH_SERVICE:
        switch (verb) {
          case "engines-reloaded":
            this._delayedWrite();
            break;
        }
        break;
    }
  }

  /**
   * Compares the #settings and #cachedSettings objects.
   *
   * @returns {boolean}
   *   True if the objects have the same property and values.
   */
  isCurrentAndCachedSettingsEqual() {
    return lazy.ObjectUtils.deepEqual(this.#settings, this.#cachedSettings);
  }

  /**
   * This function writes to settings versions 6 and below. It does two
   * updates:
   *   1) Store engine ids.
   *   2) Store "defaultEngineId" and "privateDefaultEngineId" to replace
   *      "current" and "private" because we are no longer referencing the
   *      "current" and "private" attributes with engine names as their values.
   *
   * @param {object} clonedSettings
   *   The SearchService holds a deep copy of the settings file object. This
   *   clonedSettings is passed in as an argument from SearchService.
   */
  migrateEngineIds(clonedSettings) {
    if (clonedSettings.version <= 6) {
      lazy.logConsole.debug("migrateEngineIds: start");

      for (let engineSettings of clonedSettings.engines) {
        let engine = this.#getEngineByName(engineSettings._name);

        if (engine) {
          // Store the engine id
          engineSettings.id = engine.id;
        }
      }

      let currentDefaultEngine = this.#getEngineByName(
        clonedSettings.metaData.current
      );
      let privateDefaultEngine = this.#getEngineByName(
        clonedSettings.metaData.private
      );

      // As per SearchService._getEngineDefault, we relax the verification hash
      // check for application provided engines to reduce the annoyance for
      // users who backup/sync their profile in custom ways.
      if (
        currentDefaultEngine &&
        (currentDefaultEngine.isAppProvided ||
          lazy.SearchUtils.getVerificationHash(
            clonedSettings.metaData.current
          ) == clonedSettings.metaData[this.getHashName("current")])
      ) {
        // Store the defaultEngineId
        this.setVerifiedMetaDataAttribute(
          "defaultEngineId",
          currentDefaultEngine.id
        );
      } else {
        this.setVerifiedMetaDataAttribute("defaultEngineId", "");
      }

      if (
        privateDefaultEngine &&
        (privateDefaultEngine.isAppProvided ||
          lazy.SearchUtils.getVerificationHash(
            clonedSettings.metaData.private
          ) == clonedSettings.metaData[this.getHashName("private")])
      ) {
        // Store the privateDefaultEngineId
        this.setVerifiedMetaDataAttribute(
          "privateDefaultEngineId",
          privateDefaultEngine.id
        );
      } else {
        this.setVerifiedMetaDataAttribute("privateDefaultEngineId", "");
      }

      lazy.logConsole.debug("migrateEngineIds: done");
    }
  }

  /**
   * Migrates telemetry load paths for versions of settings prior to v8.
   */
  #migrateTelemetryLoadPaths() {
    for (let engine of this.#settings.engines) {
      if (!engine._loadPath) {
        continue;
      }
      if (engine._loadPath.includes("set-via-policy")) {
        engine._loadPath = "[policy]";
      } else if (engine._loadPath.includes("set-via-user")) {
        engine._loadPath = "[user]";
      } else if (engine._loadPath.startsWith("[other]addEngineWithDetails:")) {
        engine._loadPath = engine._loadPath.replace(
          "[other]addEngineWithDetails:",
          "[addon]"
        );
      }
    }
  }

  /**
   * Finds the settings for the engine, based on the version of the settings
   * passed in. Older versions of settings used the engine name as the key,
   * whereas newer versions now use the engine id.
   *
   * @param {object} settings
   *   The saved settings object.
   * @param {string} engineId
   *   The id of the engine.
   * @param {string} engineName
   *   The name of the engine.
   * @returns {object|undefined}
   *   The engine settings if found, undefined otherwise.
   */
  static findSettingsForEngine(settings, engineId, engineName) {
    if (settings.version <= 6) {
      let engineSettings = settings.engines?.find(e => e._name == engineName);
      if (!engineSettings) {
        // If we can't find the engine settings with the current name,
        // see if there was an older name.
        let oldEngineName = ENGINE_ID_TO_OLD_NAME_MAP.get(engineId);
        if (oldEngineName) {
          engineSettings = settings.engines?.find(
            e => e._name == oldEngineName
          );
        }
      }
      return engineSettings;
    }
    return settings.engines?.find(e => e.id == engineId);
  }

  /**
   * Returns the engine associated with the name without SearchService
   * initialization checks.
   *
   * @param {string} engineName
   *   The name of the engine.
   * @returns {SearchEngine}
   *   The associated engine if found, null otherwise.
   */
  #getEngineByName(engineName) {
    for (let engine of this.#searchService._engines.values()) {
      if (engine.name == engineName) {
        return engine;
      }
    }

    return null;
  }
}
PK
!<��>��`�`*modules/SearchSuggestionController.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  FormHistory: "resource://gre/modules/FormHistory.sys.mjs",
  SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",
});

const DEFAULT_FORM_HISTORY_PARAM = "searchbar-history";
const HTTP_OK = 200;
const BROWSER_SUGGEST_PREF = "browser.search.suggest.enabled";
const BROWSER_SUGGEST_PRIVATE_PREF = "browser.search.suggest.enabled.private";
const BROWSER_RICH_SUGGEST_PREF = "browser.urlbar.richSuggestions.featureGate";
const REMOTE_TIMEOUT_PREF = "browser.search.suggest.timeout";
const REMOTE_TIMEOUT_DEFAULT = 500; // maximum time (ms) to wait before giving up on a remote suggestions

const SEARCH_DATA_TRANSFERRED_SCALAR = "browser.search.data_transferred";
const SEARCH_TELEMETRY_KEY_PREFIX = "sggt";
const SEARCH_TELEMETRY_PRIVATE_BROWSING_KEY_SUFFIX = "pb";

const SEARCH_TELEMETRY_LATENCY = "SEARCH_SUGGESTIONS_LATENCY_MS";

/**
 * Generates an UUID.
 *
 * @returns {string}
 *   An UUID string, without leading or trailing braces.
 */
function uuid() {
  let uuid = Services.uuid.generateUUID().toString();
  return uuid.slice(1, uuid.length - 1);
}

/**
 * Represents a search suggestion.
 * TODO: Support other Google tail fields: `a`, `dc`, `i`, `q`, `ansa`,
 * `ansb`, `ansc`, `du`. See bug 1626897 comment 2.
 */
class SearchSuggestionEntry {
  /**
   * Creates an entry.
   *
   * @param {string} value
   *   The suggestion as a full-text string. Suitable for display directly to
   *   the user.
   * @param {object} options
   *   An object with the following properties:
   * @param {string} [options.matchPrefix]
   *   Represents the part of a tail suggestion that is already typed. For
   *   example, Google returns "…" as the match prefix to replace
   *   "what time is it in" in a tail suggestion for the query
   *   "what time is it in t".
   * @param {string} [options.tail]
   *   Represents the suggested part of a tail suggestion. For example, Google
   *   might return "toronto" as the tail for the query "what time is it in t".
   * @param {string} [options.icon]
   *   An icon representing the result in a data uri format.
   * @param {string} [options.description]
   *   A description of the result.
   * @param {boolean} [options.trending]
   *   Whether this is a trending suggestion.
   */
  constructor(value, { matchPrefix, tail, icon, description, trending } = {}) {
    this.#value = value;
    this.#matchPrefix = matchPrefix;
    this.#tail = tail;
    this.#trending = trending;
    this.#icon = icon;
    this.#description = description;
  }

  get value() {
    return this.#value;
  }

  get matchPrefix() {
    return this.#matchPrefix;
  }

  get tail() {
    return this.#tail;
  }

  get trending() {
    return this.#trending;
  }

  get icon() {
    return this.#icon;
  }

  get description() {
    return this.#description;
  }

  get tailOffsetIndex() {
    if (!this.#tail) {
      return -1;
    }

    let offsetIndex = this.#value.lastIndexOf(this.#tail);
    if (offsetIndex + this.#tail.length < this.#value.length) {
      // We might have a tail suggestion that starts with a word contained in
      // the full-text suggestion. e.g. "london sights in l" ... "london".
      let lastWordIndex = this.#value.lastIndexOf(" ");
      if (this.#tail.startsWith(this.#value.substring(lastWordIndex))) {
        offsetIndex = lastWordIndex;
      } else {
        // Something's gone wrong. Consumers should not show this result.
        offsetIndex = -1;
      }
    }

    return offsetIndex;
  }

  /**
   * Returns true if `otherEntry` is equivalent to this instance of
   * SearchSuggestionEntry.
   *
   * @param {SearchSuggestionEntry} otherEntry The entry to compare to.
   * @returns {boolean}
   */
  equals(otherEntry) {
    return otherEntry.value == this.value;
  }

  #value;
  #matchPrefix;
  #tail;
  #trending;
  #icon;
  #description;
}

// Maps each engine name to a unique firstPartyDomain, so that requests to
// different engines are isolated from each other and from normal browsing.
// This is the same for all the controllers.
var gFirstPartyDomains = new Map();

/**
 *
 * The SearchSuggestionController class fetches search suggestions from two
 * sources: a remote search engine and the user's previous searches stored
 * locally in their profile (also called "form history").
 *
 * The number of each suggestion type is configurable, and the controller will
 * fetch and return both types at the same time. Instances of the class are
 * reusable, but one instance should be used per input. The fetch() method is
 * the main entry point. After creating an instance of the class, fetch() can
 * be called many times to fetch suggestions.
 *
 */
export class SearchSuggestionController {
  /**
   * Constructor
   *
   * @param {string} [formHistoryParam]
   *   The form history type to use with this controller.
   */
  constructor(formHistoryParam = DEFAULT_FORM_HISTORY_PARAM) {
    this.formHistoryParam = formHistoryParam;
  }

  /**
   * The maximum length of a value to be stored in search history.
   *
   *  @type {number}
   */
  static SEARCH_HISTORY_MAX_VALUE_LENGTH = 255;

  /**
   * Maximum time (ms) to wait before giving up on remote suggestions
   *
   *  @type {number}
   */
  static REMOTE_TIMEOUT_DEFAULT = REMOTE_TIMEOUT_DEFAULT;

  /**
   * Determines whether the given engine offers search suggestions.
   *
   * @param {nsISearchEngine} engine - The search engine
   * @param {boolean} fetchTrending - Whether we should fetch trending suggestions.
   * @returns {boolean} True if the engine offers suggestions and false otherwise.
   */
  static engineOffersSuggestions(engine, fetchTrending) {
    return engine.supportsResponseType(
      fetchTrending
        ? lazy.SearchUtils.URL_TYPE.TRENDING_JSON
        : lazy.SearchUtils.URL_TYPE.SUGGEST_JSON
    );
  }

  /**
   * The maximum number of local form history results to return. This limit is
   * only enforced if remote results are also returned.
   *
   * @type {number}
   */
  maxLocalResults = 5;

  /**
   * The maximum number of remote search engine results to return.
   * We'll actually only display at most
   * maxRemoteResults - <displayed local results count> remote results.
   *
   * @type {number}
   */
  maxRemoteResults = 10;

  /**
   * The additional parameter used when searching form history.
   *
   * @type {string}
   */
  formHistoryParam = DEFAULT_FORM_HISTORY_PARAM;

  /**
   * The last form history result used to improve the performance of
   * subsequent searches. This shouldn't be used for any other purpose as it
   * is never cleared and therefore could be stale.
   *
   * @type {object|null}
   */
  formHistoryResult = null;

  /**
   * Gets the firstPartyDomains Map, useful for tests.
   *
   * @returns {Map} firstPartyDomains mapped by engine names.
   */
  get firstPartyDomains() {
    return gFirstPartyDomains;
  }

  /**
   * @typedef {object} FetchResult
   * @property {Array<SearchSuggestionEntry>} local
   *   Contains local search suggestions.
   * @property {Array<SearchSuggestionEntry>} remote
   *   Contains remote search suggestions.
   */

  /**
   * Fetch search suggestions from all of the providers. Fetches in progress
   * will be stopped and results from them will not be provided.
   *
   * @param {string} searchTerm - the term to provide suggestions for
   * @param {boolean} privateMode - whether the request is being made in the
   *                                context of private browsing.
   * @param {nsISearchEngine} engine - search engine for the suggestions.
   * @param {int} userContextId - the userContextId of the selected tab.
   * @param {boolean} restrictToEngine - whether to restrict local historical
   *   suggestions to the ones registered under the given engine.
   * @param {boolean} dedupeRemoteAndLocal - whether to remove remote
   *   suggestions that dupe local suggestions
   * @param {boolean} fetchTrending - Whether we should fetch trending suggestions.
   *
   * @returns {Promise<FetchResult>}
   */
  fetch(
    searchTerm,
    privateMode,
    engine,
    userContextId = 0,
    restrictToEngine = false,
    dedupeRemoteAndLocal = true,
    fetchTrending = false
  ) {
    // There is no smart filtering from previous results here (as there is when
    // looking through history/form data) because the result set returned by the
    // server is different for every typed value - e.g. "ocean breathes" does
    // not return a subset of the results returned for "ocean".

    this.stop();

    if (!Services.search.isInitialized) {
      throw new Error("Search not initialized yet (how did you get here?)");
    }
    if (typeof privateMode === "undefined") {
      throw new Error(
        "The privateMode argument is required to avoid unintentional privacy leaks"
      );
    }
    if (!engine.getSubmission) {
      throw new Error("Invalid search engine");
    }
    if (!this.maxLocalResults && !this.maxRemoteResults) {
      throw new Error("Zero results expected, what are you trying to do?");
    }
    if (this.maxLocalResults < 0 || this.maxRemoteResults < 0) {
      throw new Error("Number of requested results must be positive");
    }

    // Array of promises to resolve before returning results.
    let promises = [];
    let context = (this.#context = {
      awaitingLocalResults: false,
      dedupeRemoteAndLocal,
      engine,
      engineId: engine?.identifier || "other",
      fetchTrending,
      privateMode,
      request: null,
      restrictToEngine,
      searchString: searchTerm,
      telemetryHandled: false,
      timer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
      userContextId,
    });

    // Fetch local results from Form History, if requested.
    if (this.maxLocalResults && !fetchTrending) {
      context.awaitingLocalResults = true;
      promises.push(this.#fetchFormHistory(context));
    }
    // Fetch remote results from Search Service, if requested.
    if (
      (searchTerm || fetchTrending) &&
      this.suggestionsEnabled &&
      (!privateMode || this.suggestionsInPrivateBrowsingEnabled) &&
      this.maxRemoteResults &&
      SearchSuggestionController.engineOffersSuggestions(engine, fetchTrending)
    ) {
      promises.push(this.#fetchRemote(context));
    }

    function handleRejection(reason) {
      if (reason == "HTTP request aborted") {
        // Do nothing since this is normal.
        return null;
      }
      console.error("SearchSuggestionController rejection:", reason);
      return null;
    }
    return Promise.all(promises).then(
      results => this.#dedupeAndReturnResults(context, results),
      handleRejection
    );
  }

  /**
   * Stop pending fetches so no results are returned from them.
   *
   * Note: If there was no remote results fetched, the fetching cannot be
   * stopped and local results will still be returned because stopping relies
   * on aborting the XMLHTTPRequest to reject the promise for Promise.all.
   */
  stop() {
    if (this.#context) {
      this.#context.abort = true;
      this.#context.request?.abort();
    }
    this.#context = null;
  }

  #context;

  async #fetchFormHistory(context) {
    // We don't cache these results as we assume that the in-memory SQL cache is
    // good enough in performance.
    let params = {
      fieldname: this.formHistoryParam,
    };

    if (context.restrictToEngine) {
      params.source = context.engine.name;
    }

    let results = await lazy.FormHistory.getAutoCompleteResults(
      context.searchString,
      params
    );

    context.awaitingLocalResults = false;

    return { localResults: results };
  }

  /**
   * Records per-engine telemetry after a search has finished.
   *
   * @param {object} context
   *   The search context.
   */
  #reportTelemetryForEngine(context) {
    this.#reportBandwidthForEngine(context);

    // Stop the latency stopwatch.
    if (!context.telemetryHandled) {
      if (context.abort) {
        TelemetryStopwatch.cancelKeyed(
          SEARCH_TELEMETRY_LATENCY,
          context.engineId,
          context
        );
      } else {
        TelemetryStopwatch.finishKeyed(
          SEARCH_TELEMETRY_LATENCY,
          context.engineId,
          context
        );
      }
      context.telemetryHandled = true;
    }
  }

  /**
   * Report bandwidth used by search activities. It only reports when it matches
   * search provider information.
   *
   * @param {object} context
   *   The search context.
   * @param {boolean} context.abort
   *   If the request should be aborted.
   * @param {string} context.engineId
   *   The search engine identifier.
   * @param {object} context.request
   *   Request information
   * @param {boolean} context.privateMode
   *   Set to true if this is coming from a private browsing mode request.
   */
  #reportBandwidthForEngine(context) {
    if (context.abort || !context.request.channel) {
      return;
    }

    let channel = ChannelWrapper.get(context.request.channel);
    let bytesTransferred = channel.requestSize + channel.responseSize;
    if (bytesTransferred == 0) {
      return;
    }

    let telemetryKey = `${SEARCH_TELEMETRY_KEY_PREFIX}-${context.engineId}`;
    if (context.privateMode) {
      telemetryKey += `-${SEARCH_TELEMETRY_PRIVATE_BROWSING_KEY_SUFFIX}`;
    }

    Services.telemetry.keyedScalarAdd(
      SEARCH_DATA_TRANSFERRED_SCALAR,
      telemetryKey,
      bytesTransferred
    );
  }

  /**
   * Fetch suggestions from the search engine over the network.
   *
   * @param {object} context
   *   The search context.
   * @returns {Promise}
   *   Returns a promise that is resolved when the response is received, or
   *   rejected if there is an error.
   */
  #fetchRemote(context) {
    let deferredResponse = Promise.withResolvers();
    let request = (context.request = new XMLHttpRequest());
    // Expect the response type to be JSON, so that the network layer will
    // decode it for us. This will also ignore incorrect Mime Types, as we are
    // dictating how we process it.
    request.responseType = "json";

    let submission = context.engine.getSubmission(
      context.searchString,
      context.searchString
        ? lazy.SearchUtils.URL_TYPE.SUGGEST_JSON
        : lazy.SearchUtils.URL_TYPE.TRENDING_JSON
    );
    let method = submission.postData ? "POST" : "GET";
    request.open(method, submission.uri.spec, true);
    // Don't set or store cookies or on-disk cache.
    request.channel.loadFlags =
      Ci.nsIChannel.LOAD_ANONYMOUS | Ci.nsIChannel.INHIBIT_PERSISTENT_CACHING;
    // Use a unique first-party domain for each engine, to isolate the
    // suggestions requests.
    if (!gFirstPartyDomains.has(context.engine.name)) {
      // Use the engine identifier, or an uuid when not available, because the
      // domain cannot contain invalid chars and the engine name may not be
      // suitable. When using an uuid the firstPartyDomain of the same engine
      // will differ across restarts, but that's acceptable for now.
      // TODO (Bug 1511339): use a persistent unique identifier per engine.
      gFirstPartyDomains.set(
        context.engine.name,
        `${context.engine.identifier || uuid()}.search.suggestions.mozilla`
      );
    }
    let firstPartyDomain = gFirstPartyDomains.get(context.engine.name);

    request.setOriginAttributes({
      userContextId: context.userContextId,
      privateBrowsingId: context.privateMode ? 1 : 0,
      firstPartyDomain,
    });

    request.mozBackgroundRequest = true; // suppress dialogs and fail silently

    context.timer.initWithCallback(
      () => {
        // Abort if we already got local results.
        if (
          request.readyState != 4 /* not complete */ &&
          !context.awaitingLocalResults
        ) {
          deferredResponse.resolve("HTTP request timeout");
        }
      },
      this.remoteTimeout,
      Ci.nsITimer.TYPE_ONE_SHOT
    );

    request.addEventListener("load", () => {
      context.timer.cancel();
      this.#reportTelemetryForEngine(context);
      if (!this.#context || context != this.#context || context.abort) {
        deferredResponse.resolve(
          "Got HTTP response after the request was cancelled"
        );
        return;
      }
      this.#onRemoteLoaded(context, deferredResponse);
    });

    request.addEventListener("error", () => {
      this.#reportTelemetryForEngine(context);
      deferredResponse.resolve("HTTP error");
    });

    // Reject for an abort assuming it's always from .stop() in which case we
    // shouldn't return local or remote results for existing searches.
    request.addEventListener("abort", () => {
      context.timer.cancel();
      this.#reportTelemetryForEngine(context);
      deferredResponse.reject("HTTP request aborted");
    });

    if (submission.postData) {
      request.sendInputStream(submission.postData);
    } else {
      request.send();
    }

    TelemetryStopwatch.startKeyed(
      SEARCH_TELEMETRY_LATENCY,
      context.engineId,
      context
    );

    return deferredResponse.promise;
  }

  /**
   * Called when the request completed successfully (thought the HTTP status
   * could be anything) so we can handle the response data.
   *
   * @param {object} context
   *   The search context.
   * @param {Promise} deferredResponse
   *   The promise to resolve when a response is received.
   * @private
   */
  #onRemoteLoaded(context, deferredResponse) {
    let status;
    try {
      status = context.request.status;
    } catch (e) {
      // The XMLHttpRequest can throw NS_ERROR_NOT_AVAILABLE.
      deferredResponse.resolve("Unknown HTTP status: " + e);
      return;
    }

    if (status != HTTP_OK) {
      deferredResponse.resolve(
        "Non-200 status or empty HTTP response: " + status
      );
      return;
    }

    let serverResults = context.request.response;

    try {
      if (
        !Array.isArray(serverResults) ||
        serverResults[0] == undefined ||
        (context.searchString.localeCompare(serverResults[0], undefined, {
          sensitivity: "base",
        }) &&
          // Some engines (e.g. Amazon) return a search string containing
          // escaped Unicode sequences. Try decoding the remote search string
          // and compare that with our typed search string.
          context.searchString.localeCompare(
            decodeURIComponent(
              JSON.parse('"' + serverResults[0].replace(/\"/g, '\\"') + '"')
            ),
            undefined,
            {
              sensitivity: "base",
            }
          ))
      ) {
        // something is wrong here so drop remote results
        deferredResponse.resolve(
          "Unexpected response, searchString does not match remote response"
        );
        return;
      }
    } catch (ex) {
      deferredResponse.resolve(
        `Failed to parse the remote response string: ${ex}`
      );
      return;
    }

    // Remove the search string from the server results since it is no longer
    // needed.
    let results = serverResults.slice(1) || [];
    deferredResponse.resolve({ result: results });
  }

  /**
   * @param {object} context
   *   The search context.
   * @param {Array} suggestResults - an array of result objects from different
   *   sources (local or remote).
   * @returns {object}
   */
  #dedupeAndReturnResults(context, suggestResults) {
    if (context.abort) {
      return null;
    }

    let results = {
      term: context.searchString,
      remote: [],
      local: [],
    };

    for (let resultData of suggestResults) {
      if (typeof resultData === "string") {
        // Failure message
        console.error(
          "SearchSuggestionController found an unexpected string value:",
          resultData
        );
      } else if (resultData.localResults) {
        results.formHistoryResults = resultData.localResults;
        results.local = resultData.localResults.map(
          s => new SearchSuggestionEntry(s.text)
        );
      } else if (resultData.result) {
        // Remote result
        let richSuggestionData = this.#getRichSuggestionData(resultData.result);
        let fullTextSuggestions = resultData.result[0];
        for (let i = 0; i < fullTextSuggestions.length; ++i) {
          results.remote.push(
            this.#newSearchSuggestionEntry(
              fullTextSuggestions[i],
              richSuggestionData?.[i],
              context.fetchTrending
            )
          );
        }
      }
    }

    // If we have remote results, cap the number of local results
    if (results.remote.length) {
      results.local = results.local.slice(0, this.maxLocalResults);
    }

    // We don't want things to appear in both history and suggestions so remove
    // entries from remote results that are already in local.
    if (
      results.remote.length &&
      results.local.length &&
      context.dedupeRemoteAndLocal
    ) {
      for (let i = 0; i < results.local.length; ++i) {
        let dupIndex = results.remote.findIndex(e =>
          e.equals(results.local[i])
        );
        if (dupIndex != -1) {
          results.remote.splice(dupIndex, 1);
        }
      }
    }

    // Trim the number of results to the maximum requested (now that we've pruned dupes).
    let maxRemoteCount = this.maxRemoteResults;
    if (context.dedupeRemoteAndLocal) {
      maxRemoteCount -= results.local.length;
    }
    results.remote = results.remote.slice(0, maxRemoteCount);

    return results;
  }

  /**
   * Returns rich suggestion data from a remote fetch, if available.
   *
   * @param {Array} remoteResultData
   *  The results.remote array returned by SearchSuggestionsController.fetch.
   * @returns {Array}
   *  An array of additional rich suggestion data. Each element should
   *  correspond to the array of text suggestions.
   */
  #getRichSuggestionData(remoteResultData) {
    if (!remoteResultData || !Array.isArray(remoteResultData)) {
      return undefined;
    }

    for (let entry of remoteResultData) {
      if (
        typeof entry == "object" &&
        entry.hasOwnProperty("google:suggestdetail")
      ) {
        let richData = entry["google:suggestdetail"];
        if (
          Array.isArray(richData) &&
          richData.length == remoteResultData[0].length
        ) {
          return richData;
        }
      }
    }
    return undefined;
  }

  /**
   * Given a text suggestion and rich suggestion data, returns a
   * SearchSuggestionEntry.
   *
   * @param {string} suggestion
   *   A suggestion string.
   * @param {object} richSuggestionData
   *   Rich suggestion data returned by the engine. In Google's case, this is
   *   the corresponding entry at "google:suggestdetail".
   * @param {boolean} trending
   *   Whether the suggestion is a trending suggestion.
   * @returns {SearchSuggestionEntry}
   */
  #newSearchSuggestionEntry(suggestion, richSuggestionData, trending) {
    if (richSuggestionData && (!trending || this.richSuggestionsEnabled)) {
      // We have valid rich suggestions.
      let args = { trending };

      // RichSuggestions come with icon and tail data, we only want one or the other
      if (!richSuggestionData?.i) {
        args.matchPrefix = richSuggestionData?.mp;
        args.tail = richSuggestionData?.t;
      } else if (this.richSuggestionsEnabled) {
        args.icon = richSuggestionData?.i;
        args.description = richSuggestionData?.a;
      }

      return new SearchSuggestionEntry(suggestion, args);
    }
    // Return a regular suggestion.
    return new SearchSuggestionEntry(suggestion, { trending });
  }
}

/**
 * The maximum time (ms) to wait before giving up on a remote suggestions.
 */
XPCOMUtils.defineLazyPreferenceGetter(
  SearchSuggestionController.prototype,
  "remoteTimeout",
  REMOTE_TIMEOUT_PREF,
  REMOTE_TIMEOUT_DEFAULT
);

/**
 * Whether or not remote suggestions are turned on.
 */
XPCOMUtils.defineLazyPreferenceGetter(
  SearchSuggestionController.prototype,
  "suggestionsEnabled",
  BROWSER_SUGGEST_PREF,
  true
);

/**
 * Whether or not remote suggestions are turned on in private browsing mode.
 */
XPCOMUtils.defineLazyPreferenceGetter(
  SearchSuggestionController.prototype,
  "suggestionsInPrivateBrowsingEnabled",
  BROWSER_SUGGEST_PRIVATE_PREF,
  false
);

/**
 * Whether or not rich suggestions are turned on.
 */
XPCOMUtils.defineLazyPreferenceGetter(
  SearchSuggestionController.prototype,
  "richSuggestionsEnabled",
  BROWSER_RICH_SUGGEST_PREF,
  false
);
PK
!<��ob
3
3!modules/SearchSuggestions.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  FormHistory: "resource://gre/modules/FormHistory.sys.mjs",
  SearchSuggestionController:
    "resource://gre/modules/SearchSuggestionController.sys.mjs",
});

/**
 * A search history autocomplete result that implements nsIAutoCompleteResult.
 * Based on FormHistoryAutoCompleteResult.
 */
class SearchHistoryResult {
  /**
   * The name of the associated field in form history.
   *
   * @type {string}
   */
  #formFieldName = null;

  /**
   * An array of entries from form history.
   *
   * @type {object[]|null}
   */
  #formHistoryEntries = null;

  //
  // An array of entries that have come from a remote source and cannot
  // be deleted. These are listed after the form history entries.
  //
  // @type {object[]}
  // (using proper JSDoc comment here causes sphinx-js failures:
  //  https://github.com/mozilla/sphinx-js/issues/242).
  //
  #remoteEntries = [];

  QueryInterface = ChromeUtils.generateQI([
    "nsIAutoCompleteResult",
    "nsISupportsWeakReference",
  ]);

  /**
   * Constructor
   *
   * @param {string} formFieldName
   *   The name of the associated field in form history.
   * @param {string} searchString
   *   The search string used for the search.
   * @param {object[]} formHistoryEntries
   *   The entries received from form history.
   */
  constructor(formFieldName, searchString, formHistoryEntries) {
    this.#formFieldName = formFieldName;
    this.searchString = searchString;
    this.#formHistoryEntries = formHistoryEntries;
  }

  /**
   * Sets the remote entries and de-dupes them against the form history entries.
   *
   * @param {object[]} remoteEntries
   *   The fixed entries to save.
   */
  set remoteEntries(remoteEntries) {
    this.#remoteEntries = remoteEntries;
    this.#removeDuplicateHistoryEntries();
  }

  /**
   * The search string associated with this result.
   *
   * @type {string}
   */
  searchString = "";

  /**
   * An error description, always blank for these results.
   *
   * @type {string}
   */
  errorDescription = "";

  /**
   * Index of the default item that should be entered if none is selected.
   *
   * @returns {number}
   */
  get defaultIndex() {
    return this.matchCount ? 0 : -1;
  }

  /**
   * The result of the search.
   *
   * @returns {number}
   */
  get searchResult() {
    return this.matchCount
      ? Ci.nsIAutoCompleteResult.RESULT_SUCCESS
      : Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
  }

  /**
   * The number of matches.
   *
   * @returns {number}
   */
  get matchCount() {
    return this.#formHistoryEntries.length + this.#remoteEntries.length;
  }

  /**
   * Gets the value of the result at the given index. This is the value that
   * will be filled into the text field.
   *
   * @param {number} index
   *   The index of the result.
   * @returns {string}
   */
  getValueAt(index) {
    const item = this.#getAt(index);
    return item.text || item.value;
  }

  /**
   * Gets the label at the given index. This is the string that is displayed
   * in the autocomplete dropdown row. If there is additional text to be
   * displayed, it should be stored within a field in the comment.
   *
   * @param {number} index
   *   The index of the result.
   * @returns {string}
   */
  getLabelAt(index) {
    const item = this.#getAt(index);
    return item.text || item.label || item.value;
  }

  /**
   * Get the comment of the result at the given index. This is a serialized
   * JSON object containing additional properties related to the index.
   *
   * @param {number} index
   *   The index of the result.
   * @returns {string}
   */
  getCommentAt(index) {
    return this.#getAt(index).comment ?? "";
  }

  /**
   * Gets the style hint for the result at the given index.
   *
   * @param {number} index
   *   The index of the result.
   * @returns {string}
   */
  getStyleAt(index) {
    const itemStyle = this.#getAt(index).style;
    if (itemStyle) {
      return itemStyle;
    }

    if (index >= 0) {
      if (index < this.#formHistoryEntries.length) {
        return "fromhistory";
      }

      if (index > 0 && index == this.#formHistoryEntries.length) {
        return "datalist-first";
      }
    }
    return "";
  }

  /**
   * Gets the image of the result at the given index.
   *
   * @param {number} _index
   *   The index of the result.
   * @returns {string}
   */
  getImageAt(_index) {
    return "";
  }

  /**
   * Gets the final value that should be completed when the user confirms
   * the match at the given index.
   *
   * @param {number} index
   *   The index of the result.
   * @returns {string}
   */
  getFinalCompleteValueAt(index) {
    return this.getValueAt(index);
  }

  /**
   * True if the value at the given index is removable.
   *
   * @param {number} index
   *   The index of the result.
   * @returns {boolean}
   */
  isRemovableAt(index) {
    return this.#isFormHistoryEntry(index);
  }

  /**
   * Remove the value at the given index from the autocomplete results.
   *
   * @param {number} index
   *   The index of the result.
   */
  removeValueAt(index) {
    if (this.isRemovableAt(index)) {
      const [removedEntry] = this.#formHistoryEntries.splice(index, 1);
      lazy.FormHistory.update({
        op: "remove",
        fieldname: this.#formFieldName,
        value: removedEntry.text,
        guid: removedEntry.guid,
      });
    }
  }

  /**
   * Returns the entry at the given index taking into account both the
   * form history entries and the remote entries.
   *
   * @param {number} index
   *   The index of the entry to find.
   * @returns {object}
   *   The object at the given index.
   * @throws {Components.Exception}
   *   Throws if the index is out of range.
   */
  #getAt(index) {
    for (const group of [this.#formHistoryEntries, this.#remoteEntries]) {
      if (index < group.length) {
        return group[index];
      }
      index -= group.length;
    }

    throw Components.Exception(
      "Index out of range.",
      Cr.NS_ERROR_ILLEGAL_VALUE
    );
  }

  /**
   * Returns true if the value at the given index is one of the form history
   * entries.
   *
   * @param {number} index
   *   The index of the result.
   * @returns {boolean}
   */

  #isFormHistoryEntry(index) {
    return index >= 0 && index < this.#formHistoryEntries.length;
  }

  /**
   * Remove items from history list that are already present in fixed list.
   * We do this rather than the opposite ( i.e. remove items from fixed list)
   * to reflect the order that is specified in the fixed list.
   */
  #removeDuplicateHistoryEntries() {
    this.#formHistoryEntries = this.#formHistoryEntries.filter(entry =>
      this.#remoteEntries.every(
        fixed => entry.text != (fixed.label || fixed.value)
      )
    );
  }
}

/**
 * SuggestAutoComplete is a base class that implements nsIAutoCompleteSearch
 * and can collect results for a given search by using this.#suggestionController.
 * We do it this way since the AutoCompleteController in Mozilla requires a
 * unique XPCOM Service for every search provider, even if the logic for two
 * providers is identical.
 *
 * @class
 */
class SuggestAutoComplete {
  constructor() {
    this.#suggestionController = new lazy.SearchSuggestionController();
    this.#suggestionController.maxLocalResults = this.#historyLimit;
  }

  /**
   * Notifies the front end of new results.
   *
   * @param {FormHistoryAutoCompleteResult} result
   *   Any previous form history result.
   * @private
   */
  onResultsReady(result) {
    if (this.#listener) {
      this.#listener.onSearchResult(this, result);

      // Null out listener to make sure we don't notify it twice
      this.#listener = null;
    }
  }

  /**
   * Initiates the search result gathering process. Part of
   * nsIAutoCompleteSearch implementation.
   *
   * @param {string} searchString
   *   The user's query string.
   * @param {string} searchParam
   *   unused, "an extra parameter"; even though this parameter and the
   *   next are unused, pass them through in case the form history
   *   service wants them
   * @param {object} previousResult
   *   unused, a client-cached store of the previous generated resultset
   *   for faster searching.
   * @param {object} listener
   *   object implementing nsIAutoCompleteObserver which we notify when
   *   results are ready.
   */
  startSearch(searchString, searchParam, previousResult, listener) {
    var formHistorySearchParam = searchParam.split("|")[0];

    // Receive the information about the privacy mode of the window to which
    // this search box belongs.  The front-end's search.xml bindings passes this
    // information in the searchParam parameter.  The alternative would have
    // been to modify nsIAutoCompleteSearch to add an argument to startSearch
    // and patch all of autocomplete to be aware of this, but the searchParam
    // argument is already an opaque argument, so this solution is hopefully
    // less hackish (although still gross.)
    var privacyMode = searchParam.split("|")[1] == "private";

    // Start search immediately if possible, otherwise once the search
    // service is initialized
    if (Services.search.isInitialized) {
      this.#triggerSearch(
        searchString,
        formHistorySearchParam,
        listener,
        privacyMode
      ).catch(console.error);
      return;
    }

    Services.search
      .init()
      .then(() => {
        this.#triggerSearch(
          searchString,
          formHistorySearchParam,
          listener,
          privacyMode
        ).catch(console.error);
      })
      .catch(result =>
        console.error(
          "Could not initialize search service, bailing out:",
          result
        )
      );
  }

  /**
   * Ends the search result gathering process. Part of nsIAutoCompleteSearch
   * implementation.
   */
  stopSearch() {
    // Prevent reporting results of stopped search
    this.#listener = null;
    this.#suggestionController.stop();
  }

  #suggestionController;

  /**
   * Maximum number of history items displayed. This is capped at 7
   * because the primary consumer (Firefox search bar) displays 10 rows
   * by default, and so we want to leave some space for suggestions
   * to be visible.
   *
   * @type {number}
   */
  #historyLimit = 7;

  /**
   * The object implementing nsIAutoCompleteObserver that we notify when
   * we have found results.
   *
   *  @type {object|null}
   */
  #listener = null;

  /**
   * Actual implementation of search.
   *
   * @param {string} searchString
   *   The user's query string.
   * @param {string} searchParam
   *   unused
   * @param {object} listener
   *   object implementing nsIAutoCompleteObserver which we notify when
   *   results are ready.
   * @param {boolean} privacyMode
   *   True if the search was made from a private browsing mode context.
   */
  async #triggerSearch(searchString, searchParam, listener, privacyMode) {
    this.#listener = listener;
    let results = await this.#suggestionController.fetch(
      searchString,
      privacyMode,
      Services.search.defaultEngine
    );

    let formHistoryEntries = (results?.formHistoryResults ?? []).map(
      historyEntry => ({
        // We supply the comments field so that autocomplete does not kick
        // in the unescaping of the results for display which it uses for
        // urls.
        comment: historyEntry.text,
        ...historyEntry,
      })
    );
    let autoCompleteResult = new SearchHistoryResult(
      this.#suggestionController.formHistoryParam,
      searchString,
      formHistoryEntries
    );

    if (results?.remote?.length) {
      // We shouldn't show tail suggestions in their full-text form.
      // Suggestions are shown after form history results.
      autoCompleteResult.remoteEntries = results.remote.reduce(
        (results, item) => {
          if (!item.matchPrefix && !item.tail) {
            results.push({
              value: item.value,
              label: item.value,
              // We supply the comments field so that autocomplete does not kick
              // in the unescaping of the results for display which it uses for
              // urls.
              comment: item.value,
            });
          }

          return results;
        },
        []
      );
    }

    // Notify the FE of our new results
    this.onResultsReady(autoCompleteResult);
  }

  QueryInterface = ChromeUtils.generateQI([
    "nsIAutoCompleteSearch",
    "nsIAutoCompleteObserver",
  ]);
}

/**
 * SearchSuggestAutoComplete is a service implementation that handles suggest
 * results specific to web searches.
 *
 * @class
 */
export class SearchSuggestAutoComplete extends SuggestAutoComplete {
  classID = Components.ID("{aa892eb4-ffbf-477d-9f9a-06c995ae9f27}");
  serviceURL = "";
}
PK
!<�qG2�6�6modules/SearchUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint no-shadow: error, mozilla/no-aArgs: error */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "logConsole", () => {
  return console.createInstance({
    prefix: "SearchUtils",
    maxLogLevel: SearchUtils.loggingEnabled ? "Debug" : "Warn",
  });
});

const BROWSER_SEARCH_PREF = "browser.search.";

/**
 * Load listener
 */
class LoadListener {
  _bytes = [];
  _callback = null;
  _channel = null;
  _countRead = 0;
  _expectedContentType = null;
  _stream = null;
  QueryInterface = ChromeUtils.generateQI([
    Ci.nsIRequestObserver,
    Ci.nsIStreamListener,
    Ci.nsIChannelEventSink,
    Ci.nsIInterfaceRequestor,
    Ci.nsIProgressEventSink,
  ]);

  /**
   * Constructor
   *
   * @param {nsIChannel} channel
   *   The initial channel to load from.
   * @param {RegExp} expectedContentType
   *   A regular expression to match the expected content type to.
   * @param {Function} callback
   *   A callback to receive the loaded data. The callback is passed the bytes
   *   (array) and the content type received. The bytes argument may be null if
   *   no data could be loaded.
   */
  constructor(channel, expectedContentType, callback) {
    this._channel = channel;
    this._callback = callback;
    this._expectedContentType = expectedContentType;
  }

  // nsIRequestObserver
  onStartRequest(request) {
    lazy.logConsole.debug("loadListener: Starting request:", request.name);
    this._stream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
      Ci.nsIBinaryInputStream
    );
  }

  onStopRequest(request, statusCode) {
    lazy.logConsole.debug("loadListener: Stopping request:", request.name);

    var requestFailed = !Components.isSuccessCode(statusCode);
    if (!requestFailed && request instanceof Ci.nsIHttpChannel) {
      requestFailed = !request.requestSucceeded;
    }

    if (requestFailed || this._countRead == 0) {
      lazy.logConsole.warn("loadListener: request failed!");
      // send null so the callback can deal with the failure
      this._bytes = null;
    } else if (!this._expectedContentType.test(this._channel.contentType)) {
      lazy.logConsole.warn(
        "loadListener: Content type does not match expected",
        this._channel.contentType
      );
      this._bytes = null;
    }
    this._callback(this._bytes, this._bytes ? this._channel.contentType : "");
    this._channel = null;
  }

  // nsIStreamListener
  onDataAvailable(request, inputStream, offset, count) {
    this._stream.setInputStream(inputStream);

    // Get a byte array of the data
    this._bytes = this._bytes.concat(this._stream.readByteArray(count));
    this._countRead += count;
  }

  // nsIChannelEventSink
  asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
    this._channel = newChannel;
    callback.onRedirectVerifyCallback(Cr.NS_OK);
  }

  // nsIInterfaceRequestor
  getInterface(iid) {
    return this.QueryInterface(iid);
  }

  // nsIProgressEventSink
  onProgress() {}
  onStatus() {}
}

export var SearchUtils = {
  BROWSER_SEARCH_PREF,

  /**
   * This is the Remote Settings key that we use to get the ignore lists for
   * engines.
   */
  SETTINGS_IGNORELIST_KEY: "hijack-blocklists",

  /**
   * This is the Remote Settings key that we use to get the allow lists for
   * overriding the default engines.
   */
  SETTINGS_ALLOWLIST_KEY: "search-default-override-allowlist",

  /**
   * This is the Remote Settings key that we use to get the search engine
   * configurations.
   */
  SETTINGS_KEY: "search-config-v2",

  /**
   * This is the Remote Settings key that we use to get the search engine
   * configuration overrides.
   */
  SETTINGS_OVERRIDES_KEY: "search-config-overrides-v2",

  /**
   * Topic used for events involving the service itself.
   */
  TOPIC_SEARCH_SERVICE: "browser-search-service",

  // See documentation in nsISearchService.idl.
  TOPIC_ENGINE_MODIFIED: "browser-search-engine-modified",
  MODIFIED_TYPE: {
    CHANGED: "engine-changed",
    ICON_CHANGED: "engine-icon-changed",
    REMOVED: "engine-removed",
    ADDED: "engine-added",
    DEFAULT: "engine-default",
    DEFAULT_PRIVATE: "engine-default-private",
  },

  URL_TYPE: {
    SUGGEST_JSON: "application/x-suggestions+json",
    SEARCH: "text/html",
    OPENSEARCH: "application/opensearchdescription+xml",
    TRENDING_JSON: "application/x-trending+json",
  },

  ENGINES_URLS: {
    "prod-main":
      "https://firefox.settings.services.mozilla.com/v1/buckets/main/collections/search-config/records",
    "prod-preview":
      "https://firefox.settings.services.mozilla.com/v1/buckets/main-preview/collections/search-config/records",
    "stage-main":
      "https://firefox.settings.services.allizom.org/v1/buckets/main/collections/search-config/records",
    "stage-preview":
      "https://firefox.settings.services.allizom.org/v1/buckets/main-preview/collections/search-config/records",
  },

  // The following constants are left undocumented in nsISearchService.idl
  // For the moment, they are meant for testing/debugging purposes only.

  // Set an arbitrary cap on the maximum icon size. Without this, large icons can
  // cause big delays when loading them at startup.
  MAX_ICON_SIZE: 20000,

  DEFAULT_QUERY_CHARSET: "UTF-8",

  // A tag to denote when we are using the "default_locale" of an engine.
  DEFAULT_TAG: "default",

  // Query parameters can have the property "purpose", whose value
  // indicates the context that initiated a search. This list contains
  // defined search contexts.
  PARAM_PURPOSES: {
    CONTEXTMENU: "contextmenu",
    HOMEPAGE: "homepage",
    KEYWORD: "keyword",
    NEWTAB: "newtab",
    SEARCHBAR: "searchbar",
  },

  LoadListener,

  /**
   * Notifies watchers of SEARCH_ENGINE_TOPIC about changes to an engine or to
   * the state of the search service.
   *
   * @param {nsISearchEngine} engine
   *   The engine to which the change applies.
   * @param {string} verb
   *   A verb describing the change.
   *
   * @see nsISearchService.idl
   */
  notifyAction(engine, verb) {
    if (Services.search.isInitialized) {
      lazy.logConsole.debug("NOTIFY: Engine:", engine.name, "Verb:", verb);
      Services.obs.notifyObservers(engine, this.TOPIC_ENGINE_MODIFIED, verb);
    }
  },

  /**
   * Wrapper function for nsIIOService::newURI.
   *
   * @param {string} urlSpec
   *        The URL string from which to create an nsIURI.
   * @returns {nsIURI} an nsIURI object, or null if the creation of the URI failed.
   */
  makeURI(urlSpec) {
    try {
      return Services.io.newURI(urlSpec);
    } catch (ex) {}

    return null;
  },

  /**
   * Wrapper function for nsIIOService::newChannel.
   *
   * @param {string|nsIURI} url
   *   The URL string from which to create an nsIChannel.
   * @param {nsIContentPolicy} contentPolicyType
   *   The type of document being loaded.
   * @returns {nsIChannel}
   *   an nsIChannel object, or null if the url is invalid.
   */
  makeChannel(url, contentPolicyType) {
    if (!contentPolicyType) {
      throw new Error("makeChannel called with invalid content policy type");
    }
    try {
      let uri = typeof url == "string" ? Services.io.newURI(url) : url;
      return Services.io.newChannelFromURI(
        uri,
        null /* loadingNode */,
        Services.scriptSecurityManager.createNullPrincipal({}),
        null /* triggeringPrincipal */,
        Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
        contentPolicyType
      );
    } catch (ex) {}

    return null;
  },

  /**
   * Tests whether this a partner distribution.
   *
   * @returns {boolean}
   *   Whether this is a partner distribution.
   */
  isPartnerBuild() {
    return SearchUtils.distroID && !SearchUtils.distroID.startsWith("mozilla");
  },

  /**
   * Current settings version. This should be incremented if the format of the
   * settings file is modified.
   *
   * @returns {number}
   *   The current settings version.
   */
  get SETTINGS_VERSION() {
    return 10;
  },

  /**
   * Indicates the channel that the build is on, with added hardening for ESR
   * since some ESR builds may be self-built or not on the same channel.
   *
   * @returns {string}
   *   Returns the modified channel, with a focus on ESR if the application
   *   version is indicating ESR.
   */
  get MODIFIED_APP_CHANNEL() {
    return AppConstants.IS_ESR ? "esr" : AppConstants.MOZ_UPDATE_CHANNEL;
  },

  /**
   * Sanitizes a name so that it can be used as an engine name. If it cannot be
   * sanitized (e.g. no valid characters), then it returns a random name.
   *
   * @param {string} name
   *  The name to be sanitized.
   * @returns {string}
   *  The sanitized name.
   */
  sanitizeName(name) {
    const maxLength = 60;
    const minLength = 1;
    var result = name.toLowerCase();
    result = result.replace(/\s+/g, "-");
    result = result.replace(/[^-a-z0-9]/g, "");

    // Use a random name if our input had no valid characters.
    if (result.length < minLength) {
      result = Math.random().toString(36).replace(/^.*\./, "");
    }

    // Force max length.
    return result.substring(0, maxLength);
  },

  getVerificationHash(name, profileDir = PathUtils.profileDir) {
    let disclaimer =
      "By modifying this file, I agree that I am doing so " +
      "only within $appName itself, using official, user-driven search " +
      "engine selection processes, and in a way which does not circumvent " +
      "user consent. I acknowledge that any attempt to change this file " +
      "from outside of $appName is a malicious act, and will be responded " +
      "to accordingly.";

    let salt =
      PathUtils.filename(profileDir) +
      name +
      disclaimer.replace(/\$appName/g, Services.appinfo.name);

    let data = new TextEncoder().encode(salt);
    let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
      Ci.nsICryptoHash
    );
    hasher.init(hasher.SHA256);
    hasher.update(data, data.length);

    return hasher.finish(true);
  },

  /**
   * Tests whether the given URI is a secure OpenSearch submission URI or a
   * secure OpenSearch update URI.
   *
   * Note: We don't want to count something served via localhost as insecure.
   * We also don't want to count sites with .onion as their top-level domain
   * as insecure because .onion URLs actually can't use https and are secured
   * in other ways.
   *
   * @param {nsIURI} uri
   *  The URI to be tested.
   * @returns {boolean}
   *  Whether the URI is secure for OpenSearch purposes.
   */
  isSecureURIForOpenSearch(uri) {
    const loopbackAddresses = ["127.0.0.1", "[::1]", "localhost"];

    return (
      uri.schemeIs("https") ||
      loopbackAddresses.includes(uri.host) ||
      uri.host.toLowerCase().endsWith(".onion")
    );
  },

  /**
   * Sorts engines by the default settings. The sort order is:
   *
   * Application Default Engine
   * Application Private Default Engine (if specified)
   * Engines sorted by orderHint (if specified)
   * Remaining engines in alphabetical order by locale.
   *
   * This is implemented here as it is used in searchengine-devtools as well as
   * the search service.
   *
   * @param {object} options
   *   The options for this function.
   * @param {object[]} options.engines
   *   An array of engine objects to sort. These should have the `name` and
   *   `orderHint` fields as top-level properties.
   * @param {object} options.appDefaultEngine
   *   The application default engine.
   * @param {object} [options.appPrivateDefaultEngine]
   *   The application private default engine, if any.
   * @param {string} [options.locale]
   *   The current application locale, or the locale to use for the sorting.
   * @returns {object[]}
   *   The sorted array of engine objects.
   */
  sortEnginesByDefaults({
    engines,
    appDefaultEngine,
    appPrivateDefaultEngine,
    locale = Services.locale.appLocaleAsBCP47,
  }) {
    const sortedEngines = [];
    const addedEngines = new Set();

    function maybeAddEngineToSort(engine) {
      if (!engine || addedEngines.has(engine.name)) {
        return;
      }

      sortedEngines.push(engine);
      addedEngines.add(engine.name);
    }

    // The app default engine should always be first in the list (except
    // for distros, that we should respect).
    const appDefault = appDefaultEngine;
    maybeAddEngineToSort(appDefault);

    // If there's a private default, and it is different to the normal
    // default, then it should be second in the list.
    const appPrivateDefault = appPrivateDefaultEngine;
    if (appPrivateDefault && appPrivateDefault != appDefault) {
      maybeAddEngineToSort(appPrivateDefault);
    }

    let remainingEngines;
    const collator = new Intl.Collator(locale);

    remainingEngines = engines.filter(e => !addedEngines.has(e.name));

    // We sort by highest orderHint first, then alphabetically by name.
    remainingEngines.sort((a, b) => {
      if (a._orderHint && b.orderHint) {
        if (a._orderHint == b.orderHint) {
          return collator.compare(a.name, b.name);
        }
        return b.orderHint - a.orderHint;
      }
      if (a.orderHint) {
        return -1;
      }
      if (b.orderHint) {
        return 1;
      }
      return collator.compare(a.name, b.name);
    });

    return [...sortedEngines, ...remainingEngines];
  },
};

XPCOMUtils.defineLazyPreferenceGetter(
  SearchUtils,
  "loggingEnabled",
  BROWSER_SEARCH_PREF + "log",
  false
);

// Can't use defineLazyPreferenceGetter because we want the value
// from the default branch
ChromeUtils.defineLazyGetter(SearchUtils, "distroID", () => {
  return Services.prefs.getDefaultBranch("distribution.").getCharPref("id", "");
});
PK
!</\Q-00modules/SecurityInfo.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const wpl = Ci.nsIWebProgressListener;
const lazy = {};
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "NSSErrorsService",
  "@mozilla.org/nss_errors_service;1",
  "nsINSSErrorsService"
);

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "pkps",
  "@mozilla.org/security/publickeypinningservice;1",
  "nsIPublicKeyPinningService"
);

// NOTE: SecurityInfo is largely reworked from the devtools NetworkHelper with changes
// to better support the WebRequest api.  The objects returned are formatted specifically
// to pass through as part of a response to webRequest listeners.

export const SecurityInfo = {
  /**
   * Extracts security information from nsIChannel.securityInfo.
   *
   * @param {nsIChannel} channel
   *        If null channel is assumed to be insecure.
   * @param {object} options
   *
   * @returns {object}
   *         Returns an object containing following members:
   *          - state: The security of the connection used to fetch this
   *                   request. Has one of following string values:
   *                    - "insecure": the connection was not secure (only http)
   *                    - "weak": the connection has minor security issues
   *                    - "broken": secure connection failed (e.g. expired cert)
   *                    - "secure": the connection was properly secured.
   *          If state == broken:
   *            - errorMessage: full error message from
   *                            nsITransportSecurityInfo.
   *          If state == secure:
   *            - protocolVersion: one of TLSv1, TLSv1.1, TLSv1.2, TLSv1.3.
   *            - cipherSuite: the cipher suite used in this connection.
   *            - cert: information about certificate used in this connection.
   *                    See parseCertificateInfo for the contents.
   *            - hsts: true if host uses Strict Transport Security,
   *                    false otherwise
   *            - hpkp: true if host uses Public Key Pinning, false otherwise
   *          If state == weak: Same as state == secure and
   *            - weaknessReasons: list of reasons that cause the request to be
   *                               considered weak. See getReasonsForWeakness.
   */
  getSecurityInfo(channel, options = {}) {
    const info = {
      state: "insecure",
    };

    /**
     * Different scenarios to consider here and how they are handled:
     * - request is HTTP, the connection is not secure
     *   => securityInfo is null
     *      => state === "insecure"
     *
     * - request is HTTPS, the connection is secure
     *   => .securityState has STATE_IS_SECURE flag
     *      => state === "secure"
     *
     * - request is HTTPS, the connection has security issues
     *   => .securityState has STATE_IS_INSECURE flag
     *   => .errorCode is an NSS error code.
     *      => state === "broken"
     *
     * - request is HTTPS, the connection was terminated before the security
     *   could be validated
     *   => .securityState has STATE_IS_INSECURE flag
     *   => .errorCode is NOT an NSS error code.
     *   => .errorMessage is not available.
     *      => state === "insecure"
     *
     * - request is HTTPS but it uses a weak cipher or old protocol, see
     *   https://hg.mozilla.org/mozilla-central/annotate/def6ed9d1c1a/
     *   security/manager/ssl/nsNSSCallbacks.cpp#l1233
     * - request is mixed content (which makes no sense whatsoever)
     *   => .securityState has STATE_IS_BROKEN flag
     *   => .errorCode is NOT an NSS error code
     *   => .errorMessage is not available
     *      => state === "weak"
     */

    let securityInfo = channel.securityInfo;

    if (!securityInfo) {
      return info;
    }

    if (lazy.NSSErrorsService.isNSSErrorCode(securityInfo.errorCode)) {
      // The connection failed.
      info.state = "broken";
      info.errorMessage = securityInfo.errorMessage;
      if (options.certificateChain && securityInfo.failedCertChain) {
        info.certificates = this.getCertificateChain(
          securityInfo.failedCertChain,
          false,
          options
        );
      }
      return info;
    }

    const state = securityInfo.securityState;

    let uri = channel.URI;
    if (uri && !uri.schemeIs("https") && !uri.schemeIs("wss")) {
      // it is not enough to look at the transport security info -
      // schemes other than https and wss are subject to
      // downgrade/etc at the scheme level and should always be
      // considered insecure.
      // Leave info.state = "insecure";
    } else if (state & wpl.STATE_IS_SECURE) {
      // The connection is secure if the scheme is sufficient
      info.state = "secure";
    } else if (state & wpl.STATE_IS_BROKEN) {
      // The connection is not secure, there was no error but there's some
      // minor security issues.
      info.state = "weak";
      info.weaknessReasons = this.getReasonsForWeakness(state);
    } else if (state & wpl.STATE_IS_INSECURE) {
      // This was most likely an https request that was aborted before
      // validation. Return info as info.state = insecure.
      return info;
    } else {
      // No known STATE_IS_* flags.
      return info;
    }

    // Cipher suite.
    info.cipherSuite = securityInfo.cipherName;

    // Length (in bits) of the secret key
    info.secretKeyLength = securityInfo.secretKeyLength;

    // Key exchange group name.
    if (securityInfo.keaGroupName !== "none") {
      info.keaGroupName = securityInfo.keaGroupName;
    }

    // Certificate signature scheme.
    if (securityInfo.signatureSchemeName !== "none") {
      info.signatureSchemeName = securityInfo.signatureSchemeName;
    }

    if (
      securityInfo.overridableErrorCategory ==
      Ci.nsITransportSecurityInfo.ERROR_TRUST
    ) {
      info.overridableErrorCategory = "trust_error";
      info.isUntrusted = true;
    } else if (
      securityInfo.overridableErrorCategory ==
      Ci.nsITransportSecurityInfo.ERROR_DOMAIN
    ) {
      info.overridableErrorCategory = "domain_mismatch";
      info.isDomainMismatch = true;
    } else if (
      securityInfo.overridableErrorCategory ==
      Ci.nsITransportSecurityInfo.ERROR_TIME
    ) {
      info.overridableErrorCategory = "expired_or_not_yet_valid";
      info.isNotValidAtThisTime = true;
    }
    info.isExtendedValidation = securityInfo.isExtendedValidation;

    info.certificateTransparencyStatus = this.getTransparencyStatus(
      securityInfo.certificateTransparencyStatus
    );

    // Protocol version.
    info.protocolVersion = this.formatSecurityProtocol(
      securityInfo.protocolVersion
    );

    if (options.certificateChain && securityInfo.succeededCertChain) {
      info.certificates = this.getCertificateChain(
        securityInfo.succeededCertChain,
        securityInfo.isBuiltCertChainRootBuiltInRoot,
        options
      );
    } else {
      info.certificates = [
        this.parseCertificateInfo(securityInfo.serverCert, false, options),
      ];
    }

    // HSTS and static pinning if available.
    if (uri && uri.host) {
      info.hsts = channel.loadInfo.hstsStatus;
      info.hpkp = lazy.pkps.hostHasPins(uri);
    } else {
      info.hsts = false;
      info.hpkp = false;
    }

    // These values can be unset in rare cases, e.g. when stashed connection
    // data is deseralized from an older version of Firefox.
    try {
      info.usedEch = securityInfo.isAcceptedEch;
    } catch {
      info.usedEch = false;
    }
    try {
      info.usedDelegatedCredentials = securityInfo.isDelegatedCredential;
    } catch {
      info.usedDelegatedCredentials = false;
    }
    info.usedOcsp = securityInfo.madeOCSPRequests;
    info.usedPrivateDns = securityInfo.usedPrivateDNS;

    return info;
  },

  getCertificateChain(certChain, isRootBuiltInRoot, options = {}) {
    let certificates = [];
    // The end-entity and intermediates aren't built-in roots.
    for (let cert of certChain.slice(0, -1)) {
      certificates.push(this.parseCertificateInfo(cert, false, options));
    }
    // The last in the chain may be the root (for successful chains), which may
    // be a built-in root.
    let rootCert = certChain.at(-1);
    if (rootCert) {
      certificates.push(
        this.parseCertificateInfo(rootCert, isRootBuiltInRoot, options)
      );
    }
    return certificates;
  },

  /**
   * Takes an nsIX509Cert and returns an object with certificate information.
   *
   * @param {nsIX509Cert} cert
   *        The certificate to extract the information from.
   * @param {boolean} isBuiltInRoot
   *        Whether or not this certificate is a built-in root.
   * @param {object} options
   * @returns {object}
   *         An object with following format:
   *           {
   *             subject: subjectName,
   *             issuer: issuerName,
   *             validity: { start, end },
   *             fingerprint: { sha1, sha256 }
   *           }
   */
  parseCertificateInfo(cert, isBuiltInRoot, options = {}) {
    if (!cert) {
      return {};
    }

    let certData = {
      subject: cert.subjectName,
      issuer: cert.issuerName,
      validity: {
        start: cert.validity.notBefore
          ? Math.trunc(cert.validity.notBefore / 1000)
          : 0,
        end: cert.validity.notAfter
          ? Math.trunc(cert.validity.notAfter / 1000)
          : 0,
      },
      fingerprint: {
        sha1: cert.sha1Fingerprint,
        sha256: cert.sha256Fingerprint,
      },
      serialNumber: cert.serialNumber,
      isBuiltInRoot,
      subjectPublicKeyInfoDigest: {
        sha256: cert.sha256SubjectPublicKeyInfoDigest,
      },
    };
    if (options.rawDER) {
      certData.rawDER = cert.getRawDER();
    }
    return certData;
  },

  // Bug 1355903 Transparency is currently disabled using security.pki.certificate_transparency.mode
  getTransparencyStatus(status) {
    switch (status) {
      case Ci.nsITransportSecurityInfo.CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE:
        return "not_applicable";
      case Ci.nsITransportSecurityInfo
        .CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT:
        return "policy_compliant";
      case Ci.nsITransportSecurityInfo
        .CERTIFICATE_TRANSPARENCY_POLICY_NOT_ENOUGH_SCTS:
        return "policy_not_enough_scts";
      case Ci.nsITransportSecurityInfo
        .CERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS:
        return "policy_not_diverse_scts";
    }
    return "unknown";
  },

  /**
   * Takes protocolVersion of TransportSecurityInfo object and returns human readable
   * description.
   *
   * @param {number} version
   *        One of nsITransportSecurityInfo version constants.
   * @returns {string}
   *         One of TLSv1, TLSv1.1, TLSv1.2, TLSv1.3 if version
   *         is valid, Unknown otherwise.
   */
  formatSecurityProtocol(version) {
    switch (version) {
      case Ci.nsITransportSecurityInfo.TLS_VERSION_1:
        return "TLSv1";
      case Ci.nsITransportSecurityInfo.TLS_VERSION_1_1:
        return "TLSv1.1";
      case Ci.nsITransportSecurityInfo.TLS_VERSION_1_2:
        return "TLSv1.2";
      case Ci.nsITransportSecurityInfo.TLS_VERSION_1_3:
        return "TLSv1.3";
    }
    return "unknown";
  },

  /**
   * Takes the securityState bitfield and returns reasons for weak connection
   * as an array of strings.
   *
   * @param {number} state
   *        nsITransportSecurityInfo.securityState.
   *
   * @returns {Array<string>}
   *         List of weakness reasons. A subset of { cipher } where
   *         cipher: The cipher suite is consireded to be weak (RC4).
   */
  getReasonsForWeakness(state) {
    // If there's non-fatal security issues the request has STATE_IS_BROKEN
    // flag set. See https://hg.mozilla.org/mozilla-central/file/44344099d119
    // /security/manager/ssl/nsNSSCallbacks.cpp#l1233
    let reasons = [];

    if (state & wpl.STATE_IS_BROKEN) {
      if (state & wpl.STATE_USES_WEAK_CRYPTO) {
        reasons.push("cipher");
      }
    }

    return reasons;
  },
};
PK
!<�:�
��modules/SelectionUtils.sys.mjs/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export var SelectionUtils = {
  /**
   * Trim the selection text to a reasonable size and sanitize it to make it
   * safe for search query input.
   *
   * @param aSelection
   *        The selection text to trim.
   * @param aMaxLen
   *        The maximum string length, defaults to a reasonable size if undefined.
   * @return The trimmed selection text.
   */
  trimSelection(aSelection, aMaxLen) {
    // Selections of more than 150 characters aren't useful.
    const maxLen = Math.min(aMaxLen || 150, aSelection.length);

    if (aSelection.length > maxLen) {
      // only use the first maxLen important chars. see bug 221361
      let pattern = new RegExp("^(?:\\s*.){0," + maxLen + "}");
      pattern.test(aSelection);
      aSelection = RegExp.lastMatch;
    }

    aSelection = aSelection.trim().replace(/\s+/g, " ");

    if (aSelection.length > maxLen) {
      aSelection = aSelection.substr(0, maxLen);
    }

    return aSelection;
  },

  /**
   * Retrieve the text selection details for the given window.
   *
   * @param  aTopWindow
   *         The top window of the element containing the selection.
   * @param  aCharLen
   *         The maximum string length for the selection text.
   * @return The selection details containing the full and trimmed selection text
   *         and link details for link selections.
   */
  getSelectionDetails(aTopWindow, aCharLen) {
    let focusedWindow = {};
    let focusedElement = Services.focus.getFocusedElementForWindow(
      aTopWindow,
      true,
      focusedWindow
    );
    focusedWindow = focusedWindow.value;

    let selection = focusedWindow.getSelection();
    let selectionStr = selection.toString();
    let fullText;

    let url;
    let linkText;

    let isDocumentLevelSelection = true;
    // try getting a selected text in text input.
    if (!selectionStr && focusedElement) {
      // Don't get the selection for password fields. See bug 565717.
      if (
        ChromeUtils.getClassName(focusedElement) === "HTMLTextAreaElement" ||
        (ChromeUtils.getClassName(focusedElement) === "HTMLInputElement" &&
          focusedElement.mozIsTextField(true))
      ) {
        selection = focusedElement.editor.selection;
        selectionStr = selection.toString();
        isDocumentLevelSelection = false;
      }
    }

    let collapsed = selection.areNormalAndCrossShadowBoundaryRangesCollapsed;

    if (selectionStr) {
      // Have some text, let's figure out if it looks like a URL that isn't
      // actually a link.
      linkText = selectionStr.trim();
      if (/^(?:https?|ftp):/i.test(linkText)) {
        try {
          url = Services.io.newURI(linkText);
        } catch (ex) {}
      } else if (/^(?:[a-z\d-]+\.)+[a-z]+$/i.test(linkText)) {
        // Check if this could be a valid url, just missing the protocol.
        // Now let's see if this is an intentional link selection. Our guess is
        // based on whether the selection begins/ends with whitespace or is
        // preceded/followed by a non-word character.

        // selection.toString() trims trailing whitespace, so we look for
        // that explicitly in the first and last ranges.
        let beginRange = selection.getRangeAt(0);
        let delimitedAtStart = /^\s/.test(beginRange);
        if (!delimitedAtStart) {
          let container = beginRange.startContainer;
          let offset = beginRange.startOffset;
          if (container.nodeType == container.TEXT_NODE && offset > 0) {
            delimitedAtStart = /\W/.test(container.textContent[offset - 1]);
          } else {
            delimitedAtStart = true;
          }
        }

        let delimitedAtEnd = false;
        if (delimitedAtStart) {
          let endRange = selection.getRangeAt(selection.rangeCount - 1);
          delimitedAtEnd = /\s$/.test(endRange);
          if (!delimitedAtEnd) {
            let container = endRange.endContainer;
            let offset = endRange.endOffset;
            if (
              container.nodeType == container.TEXT_NODE &&
              offset < container.textContent.length
            ) {
              delimitedAtEnd = /\W/.test(container.textContent[offset]);
            } else {
              delimitedAtEnd = true;
            }
          }
        }

        if (delimitedAtStart && delimitedAtEnd) {
          try {
            url = Services.uriFixup.getFixupURIInfo(linkText).preferredURI;
          } catch (ex) {}
        }
      }
    }

    if (selectionStr) {
      // Pass up to 16K through unmolested.  If an add-on needs more, they will
      // have to use a content script.
      fullText = selectionStr.substr(0, 16384);
      selectionStr = this.trimSelection(selectionStr, aCharLen);
    }

    if (url && !url.host) {
      url = null;
    }

    return {
      text: selectionStr,
      docSelectionIsCollapsed: collapsed,
      isDocumentLevelSelection,
      fullText,
      linkURL: url ? url.spec : null,
      linkText: url ? linkText : "",
    };
  },
};
PK
!<dD��modules/ServiceRequest.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This module consolidates various code and data update requests, so flags
 * can be set, Telemetry collected, etc. in a central place.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "ProxyService",
  "@mozilla.org/network/protocol-proxy-service;1",
  "nsIProtocolProxyService"
);

ChromeUtils.defineESModuleGetters(lazy, {
  ExtensionPreferencesManager:
    "resource://gre/modules/ExtensionPreferencesManager.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "CaptivePortalService",
  "@mozilla.org/network/captive-portal-service;1",
  "nsICaptivePortalService"
);

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gNetworkLinkService",
  "@mozilla.org/network/network-link-service;1",
  "nsINetworkLinkService"
);

const PROXY_CONFIG_TYPES = [
  "direct",
  "manual",
  "pac",
  "unused", // nsIProtocolProxyService.idl skips index 3.
  "wpad",
  "system",
];

function recordEvent(service, source = {}) {
  try {
    Services.telemetry.setEventRecordingEnabled("service_request", true);
    Services.telemetry.recordEvent(
      "service_request",
      "bypass",
      "proxy_info",
      service,
      source
    );
  } catch (err) {
    // If the telemetry throws just log the error so it doesn't break any
    // functionality.
    console.error(err);
  }
}

// If proxy.settings is used to change the proxy, an extension will
// be "in control".  This returns the id of that extension.
async function getControllingExtension() {
  if (
    !WebExtensionPolicy.getActiveExtensions().some(p =>
      p.permissions.includes("proxy")
    )
  ) {
    return undefined;
  }
  // Is this proxied by an extension that set proxy prefs?
  let setting = await lazy.ExtensionPreferencesManager.getSetting(
    "proxy.settings"
  );
  return setting?.id;
}

async function getProxySource(proxyInfo) {
  // sourceId is set when using proxy.onRequest
  if (proxyInfo.sourceId) {
    return {
      source: proxyInfo.sourceId,
      type: "api",
    };
  }
  let type = PROXY_CONFIG_TYPES[lazy.ProxyService.proxyConfigType] || "unknown";

  // If we have a policy it will have set the prefs.
  if (
    Services.policies &&
    Services.policies.status === Services.policies.ACTIVE
  ) {
    let policies = Services.policies.getActivePolicies()?.filter(p => p.Proxy);
    if (policies?.length) {
      return {
        source: "policy",
        type,
      };
    }
  }

  let source = await getControllingExtension();
  return {
    source: source || "prefs",
    type,
  };
}

/**
 * ServiceRequest is intended to be a drop-in replacement for current users
 * of XMLHttpRequest.
 *
 * @param {Object} options - Options for underlying XHR, e.g. { mozAnon: bool }
 */
export class ServiceRequest extends XMLHttpRequest {
  constructor(options) {
    super(options);
  }
  /**
   * Opens an XMLHttpRequest, and sets the NSS "beConservative" flag.
   * Requests are always async.
   *
   * @param {String} method - HTTP method to use, e.g. "GET".
   * @param {String} url - URL to open.
   * @param {Object} options - Additional options { bypassProxy: bool }.
   */
  open(method, url, options) {
    super.open(method, url, true);

    if (super.channel instanceof Ci.nsIHttpChannelInternal) {
      let internal = super.channel.QueryInterface(Ci.nsIHttpChannelInternal);
      // Disable cutting edge features, like TLS 1.3, where middleboxes might brick us
      internal.beConservative = true;
      // Disable use of proxy for this request if necessary.
      if (options?.bypassProxy && this.bypassProxyEnabled) {
        internal.bypassProxy = true;
      }
    }
  }

  get bypassProxy() {
    let { channel } = this;
    return channel.QueryInterface(Ci.nsIHttpChannelInternal).bypassProxy;
  }

  get isProxied() {
    let { channel } = this;
    return !!(channel instanceof Ci.nsIProxiedChannel && channel.proxyInfo);
  }

  get bypassProxyEnabled() {
    return Services.prefs.getBoolPref("network.proxy.allow_bypass", true);
  }

  static async logProxySource(channel, service) {
    if (channel.proxyInfo) {
      let source = await getProxySource(channel.proxyInfo);
      recordEvent(service, source);
    }
  }

  static get isOffline() {
    try {
      return (
        Services.io.offline ||
        lazy.CaptivePortalService.state ==
          lazy.CaptivePortalService.LOCKED_PORTAL ||
        !lazy.gNetworkLinkService.isLinkUp
      );
    } catch (ex) {
      // we cannot get state, assume the best.
    }
    return false;
  }
}
PK
!<�	"ZZ$modules/ShieldContentProcess.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Registers about: pages provided by Shield, and listens for a shutdown event
 * from the add-on before un-registering them.
 *
 * This file is loaded as a process script. It is executed once for each
 * process, including the parent one.
 */

import { AboutPages } from "resource://normandy-content/AboutPages.sys.mjs";

export function AboutStudies() {
  return AboutPages.aboutStudies;
}
PK
!<N'��0�0modules/ShortcutUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "PlatformKeys", function () {
  return Services.strings.createBundle(
    "chrome://global-platform/locale/platformKeys.properties"
  );
});

ChromeUtils.defineLazyGetter(lazy, "Keys", function () {
  return Services.strings.createBundle(
    "chrome://global/locale/keys.properties"
  );
});

export var ShortcutUtils = {
  IS_VALID: "valid",
  INVALID_KEY: "invalid_key",
  INVALID_MODIFIER: "invalid_modifier",
  INVALID_COMBINATION: "invalid_combination",
  DUPLICATE_MODIFIER: "duplicate_modifier",
  MODIFIER_REQUIRED: "modifier_required",

  CLOSE_TAB: "CLOSE_TAB",
  CYCLE_TABS: "CYCLE_TABS",
  TOGGLE_CARET_BROWSING: "TOGGLE_CARET_BROWSING",
  MOVE_TAB_BACKWARD: "MOVE_TAB_BACKWARD",
  MOVE_TAB_FORWARD: "MOVE_TAB_FORWARD",
  NEXT_TAB: "NEXT_TAB",
  PREVIOUS_TAB: "PREVIOUS_TAB",

  /**
   * Prettifies the modifier keys for an element.
   *
   * @param Node aElemKey
   *        The key element to get the modifiers from.
   * @return string
   *         A prettified and properly separated modifier keys string.
   */
  prettifyShortcut(aElemKey) {
    let elemString = this.getModifierString(aElemKey.getAttribute("modifiers"));
    let key = this.getKeyString(
      aElemKey.getAttribute("keycode"),
      aElemKey.getAttribute("key")
    );
    return elemString + key;
  },

  metaKeyIsCommandKey() {
    return AppConstants.platform == "macosx";
  },

  getModifierString(elemMod) {
    if (!elemMod) {
      return "";
    }

    let elemString = "";
    let haveCloverLeaf = false;
    if (elemMod.match("accel")) {
      if (Services.appinfo.OS == "Darwin") {
        haveCloverLeaf = true;
      } else {
        elemString +=
          lazy.PlatformKeys.GetStringFromName("VK_CONTROL") +
          lazy.PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
      }
    }
    if (elemMod.match("access")) {
      if (Services.appinfo.OS == "Darwin") {
        elemString +=
          lazy.PlatformKeys.GetStringFromName("VK_CONTROL") +
          lazy.PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
      } else {
        elemString +=
          lazy.PlatformKeys.GetStringFromName("VK_ALT") +
          lazy.PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
      }
    }
    if (elemMod.match("meta") && !this.metaKeyIsCommandKey()) {
      elemString +=
        lazy.PlatformKeys.GetStringFromName("VK_COMMAND_OR_WIN") +
        lazy.PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
    }
    if (elemMod.match("shift")) {
      elemString +=
        lazy.PlatformKeys.GetStringFromName("VK_SHIFT") +
        lazy.PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
    }
    if (elemMod.match("alt")) {
      elemString +=
        lazy.PlatformKeys.GetStringFromName("VK_ALT") +
        lazy.PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
    }
    if (elemMod.match("ctrl") || elemMod.match("control")) {
      elemString +=
        lazy.PlatformKeys.GetStringFromName("VK_CONTROL") +
        lazy.PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
    }
    if (elemMod.match("meta") && this.metaKeyIsCommandKey()) {
      elemString +=
        lazy.PlatformKeys.GetStringFromName("VK_COMMAND_OR_WIN") +
        lazy.PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
    }

    if (haveCloverLeaf) {
      elemString +=
        lazy.PlatformKeys.GetStringFromName("VK_COMMAND_OR_WIN") +
        lazy.PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
    }

    return elemString;
  },

  getKeyString(keyCode, keyAttribute) {
    let key;
    if (keyCode) {
      keyCode = keyCode.toUpperCase();
      if (AppConstants.platform == "macosx") {
        // Return fancy Unicode symbols for some keys.
        switch (keyCode) {
          case "VK_LEFT":
            return "\u2190"; // U+2190 LEFTWARDS ARROW
          case "VK_RIGHT":
            return "\u2192"; // U+2192 RIGHTWARDS ARROW
        }
      }
      try {
        let bundle = keyCode == "VK_RETURN" ? lazy.PlatformKeys : lazy.Keys;
        // Some keys might not exist in the locale file, which will throw.
        key = bundle.GetStringFromName(keyCode);
      } catch (ex) {
        console.error("Error finding ", keyCode, ": ", ex);
        key = keyCode.replace(/^VK_/, "");
      }
    } else {
      key = keyAttribute.toUpperCase();
    }

    return key;
  },

  getKeyAttribute(chromeKey) {
    if (/^[A-Z]$/.test(chromeKey)) {
      // We use the key attribute for single characters.
      return ["key", chromeKey];
    }
    return ["keycode", this.getKeycodeAttribute(chromeKey)];
  },

  /**
   * Determines the corresponding XUL keycode from the given chrome key.
   *
   * For example:
   *
   *    input     |  output
   *    ---------------------------------------
   *    "PageUp"  |  "VK_PAGE_UP"
   *    "Delete"  |  "VK_DELETE"
   *
   * @param {string} chromeKey The chrome key (e.g. "PageUp", "Space", ...)
   * @returns {string} The constructed value for the Key's 'keycode' attribute.
   */
  getKeycodeAttribute(chromeKey) {
    if (/^[0-9]/.test(chromeKey)) {
      return `VK_${chromeKey}`;
    }
    return `VK${chromeKey.replace(/([A-Z])/g, "_$&").toUpperCase()}`;
  },

  findShortcut(aElemCommand) {
    let document = aElemCommand.ownerDocument;
    return document.querySelector(
      'key[command="' + aElemCommand.getAttribute("id") + '"]'
    );
  },

  chromeModifierKeyMap: {
    Alt: "alt",
    Command: "accel",
    Ctrl: "accel",
    MacCtrl: "control",
    Shift: "shift",
  },

  /**
   * Determines the corresponding XUL modifiers from the chrome modifiers.
   *
   * For example:
   *
   *    input             |   output
   *    ---------------------------------------
   *    ["Ctrl", "Shift"] |   "accel,shift"
   *    ["MacCtrl"]       |   "control"
   *
   * @param {Array} chromeModifiers The array of chrome modifiers.
   * @returns {string} The constructed value for the Key's 'modifiers' attribute.
   */
  getModifiersAttribute(chromeModifiers) {
    return Array.from(chromeModifiers, modifier => {
      return ShortcutUtils.chromeModifierKeyMap[modifier];
    })
      .sort()
      .join(",");
  },

  /**
   * Validate if a shortcut string is valid and return an error code if it
   * isn't valid.
   *
   * For example:
   *
   *    input            |   output
   *    ---------------------------------------
   *    "Ctrl+Shift+A"   |   IS_VALID
   *    "Shift+F"        |   MODIFIER_REQUIRED
   *    "Command+>"      |   INVALID_KEY
   *
   * @param {string} string The shortcut string.
   * @returns {string} The code for the validation result.
   */
  validate(string) {
    // A valid shortcut key for a webextension manifest
    const MEDIA_KEYS =
      /^(MediaNextTrack|MediaPlayPause|MediaPrevTrack|MediaStop)$/;
    const BASIC_KEYS =
      /^([A-Z0-9]|Comma|Period|Home|End|PageUp|PageDown|Space|Insert|Delete|Up|Down|Left|Right)$/;
    const FUNCTION_KEYS = /^(F[1-9]|F1[0-2])$/;

    if (MEDIA_KEYS.test(string.trim())) {
      return this.IS_VALID;
    }

    let modifiers = string.split("+").map(s => s.trim());
    let key = modifiers.pop();

    let chromeModifiers = modifiers.map(
      m => ShortcutUtils.chromeModifierKeyMap[m]
    );
    // If the modifier wasn't found it will be undefined.
    if (chromeModifiers.some(modifier => !modifier)) {
      return this.INVALID_MODIFIER;
    }

    switch (modifiers.length) {
      case 0:
        // A lack of modifiers is only allowed with function keys.
        if (!FUNCTION_KEYS.test(key)) {
          return this.MODIFIER_REQUIRED;
        }
        break;
      case 1:
        // Shift is only allowed on its own with function keys.
        if (chromeModifiers[0] == "shift" && !FUNCTION_KEYS.test(key)) {
          return this.MODIFIER_REQUIRED;
        }
        break;
      case 2:
        if (chromeModifiers[0] == chromeModifiers[1]) {
          return this.DUPLICATE_MODIFIER;
        }
        break;
      default:
        return this.INVALID_COMBINATION;
    }

    if (!BASIC_KEYS.test(key) && !FUNCTION_KEYS.test(key)) {
      return this.INVALID_KEY;
    }

    return this.IS_VALID;
  },

  /**
   * Attempt to find a key for a given shortcut string, such as
   * "Ctrl+Shift+A" and determine if it is a system shortcut.
   *
   * @param {Object} win The window to look for key elements in.
   * @param {string} value The shortcut string.
   * @returns {boolean} Whether a system shortcut was found or not.
   */
  isSystem(win, value) {
    let modifiers = value.split("+");
    let chromeKey = modifiers.pop();
    let modifiersString = this.getModifiersAttribute(modifiers);
    let keycode = this.getKeycodeAttribute(chromeKey);

    let baseSelector = "key";
    if (modifiers.length) {
      baseSelector += `[modifiers="${modifiersString}"]`;
    }

    let keyEl = win.document.querySelector(
      [
        `${baseSelector}[key="${chromeKey}"]`,
        `${baseSelector}[key="${chromeKey.toLowerCase()}"]`,
        `${baseSelector}[keycode="${keycode}"]`,
      ].join(",")
    );
    return keyEl && !keyEl.closest("keyset").id.startsWith("ext-keyset-id");
  },

  /**
   * Determine what action a KeyboardEvent should perform, if any.
   *
   * @param {KeyboardEvent} event The event to check for a related system action.
   * @returns {string} A string identifying the action, or null if no action is found.
   */
  // eslint-disable-next-line complexity
  getSystemActionForEvent(event, { rtl } = {}) {
    // On Windows, Win key state is not strictly checked so that we can ignore
    // Win key state to check the other modifier state.
    const meaningfulMetaKey = event.metaKey && AppConstants.platform != "win";
    // This is set to true only when the Meta key is accel key on the platform.
    const accelMetaKey = event.metaKey && this.metaKeyIsCommandKey();
    switch (event.keyCode) {
      case event.DOM_VK_TAB:
        if (event.ctrlKey && !event.altKey && !meaningfulMetaKey) {
          return ShortcutUtils.CYCLE_TABS;
        }
        break;
      case event.DOM_VK_F7:
        // shift + F7 is the default DevTools shortcut for the Style Editor.
        if (!event.shiftKey) {
          return ShortcutUtils.TOGGLE_CARET_BROWSING;
        }
        break;
      case event.DOM_VK_PAGE_UP:
        if (
          event.ctrlKey &&
          !event.shiftKey &&
          !event.altKey &&
          !meaningfulMetaKey
        ) {
          return ShortcutUtils.PREVIOUS_TAB;
        }
        if (
          event.ctrlKey &&
          event.shiftKey &&
          !event.altKey &&
          !meaningfulMetaKey
        ) {
          return ShortcutUtils.MOVE_TAB_BACKWARD;
        }
        break;
      case event.DOM_VK_PAGE_DOWN:
        if (
          event.ctrlKey &&
          !event.shiftKey &&
          !event.altKey &&
          !meaningfulMetaKey
        ) {
          return ShortcutUtils.NEXT_TAB;
        }
        if (
          event.ctrlKey &&
          event.shiftKey &&
          !event.altKey &&
          !meaningfulMetaKey
        ) {
          return ShortcutUtils.MOVE_TAB_FORWARD;
        }
        break;
      case event.DOM_VK_LEFT:
        if (accelMetaKey && event.altKey && !event.shiftKey && !event.ctrlKey) {
          return ShortcutUtils.PREVIOUS_TAB;
        }
        break;
      case event.DOM_VK_RIGHT:
        if (accelMetaKey && event.altKey && !event.shiftKey && !event.ctrlKey) {
          return ShortcutUtils.NEXT_TAB;
        }
        break;
    }

    if (AppConstants.platform == "macosx") {
      if (!event.altKey && event.metaKey) {
        switch (event.charCode) {
          case "}".charCodeAt(0):
            if (rtl) {
              return ShortcutUtils.PREVIOUS_TAB;
            }
            return ShortcutUtils.NEXT_TAB;
          case "{".charCodeAt(0):
            if (rtl) {
              return ShortcutUtils.NEXT_TAB;
            }
            return ShortcutUtils.PREVIOUS_TAB;
        }
      }
    }
    // Not on Mac from now on.
    if (AppConstants.platform != "macosx") {
      if (
        event.ctrlKey &&
        !event.shiftKey &&
        event.keyCode == KeyEvent.DOM_VK_F4
      ) {
        return ShortcutUtils.CLOSE_TAB;
      }
    }

    return null;
  },
};

Object.freeze(ShortcutUtils);
PK
!<K\ӧLQLQ!modules/SignUpFormRuleset.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Fathom ML model for identifying sign up <forms>
 *
 * This is developed out-of-tree at https://github.com/mozilla-services/fathom-login-forms,
 * where there is also over a GB of training, validation, and
 * testing data. To make changes, do your edits there (whether adding new
 * training pages, adding new rules, or both), retrain and evaluate as
 * documented at https://mozilla.github.io/fathom/training.html, paste the
 * coefficients emitted by the trainer into the ruleset, and finally copy the
 * ruleset's "CODE TO COPY INTO PRODUCTION" section to this file's "CODE FROM
 * TRAINING REPOSITORY" section.
 */

import {
  dom,
  out,
  rule,
  ruleset,
  score,
  type,
  element,
  utils,
} from "resource://gre/modules/third_party/fathom/fathom.mjs";

let { isVisible, attributesMatch, setDefault } = utils;

const DEVELOPMENT = false;

/**
 * --- START OF CODE FROM TRAINING REPOSITORY ---
 */
const coefficients = {
  form: new Map([
    ["formAttributesMatchRegisterRegex", 0.4614015519618988],
    ["formAttributesMatchLoginRegex", -2.608457326889038],
    ["formAttributesMatchSubscriptionRegex", -3.253319501876831],
    ["formAttributesMatchLoginAndRegisterRegex", 3.6423728466033936],
    ["formHasAcNewPassword", 2.214113473892212],
    ["formHasAcCurrentPassword", -0.43707895278930664],
    ["formHasEmailField", 1.760241150856018],
    ["formHasUsernameField", 1.1527059078216553],
    ["formHasPasswordField", 1.6670876741409302],
    ["formHasFirstOrLastNameField", 0.9517516493797302],
    ["formHasRegisterButton", 1.574048638343811],
    ["formHasLoginButton", -1.1688978672027588],
    ["formHasSubscribeButton", -0.26299405097961426],
    ["formHasContinueButton", 2.3797709941864014],
    ["formHasTermsAndConditionsHyperlink", 1.764896035194397],
    ["formHasPasswordForgottenHyperlink", -0.32138824462890625],
    ["formHasAlreadySignedUpHyperlink", 3.160510301589966],
    ["closestElementIsEmailLabelLike", 1.0336143970489502],
    ["formHasRememberMeCheckbox", -1.2176686525344849],
    ["formHasSubcriptionCheckbox", 0.6100747585296631],
    ["docTitleMatchesRegisterRegex", 0.680654764175415],
    ["docTitleMatchesEditProfileRegex", -4.104133605957031],
    ["closestHeaderMatchesRegisterRegex", 1.3462989330291748],
    ["closestHeaderMatchesLoginRegex", -0.1804502159357071],
    ["closestHeaderMatchesSubscriptionRegex", -1.3057124614715576],
  ]),
};

const biases = [["form", -4.402400970458984]];

const loginRegex =
  /login|log-in|log_in|log in|signon|sign-on|sign_on|sign on|signin|sign-in|sign_in|sign in|einloggen|anmelden|logon|log-on|log_on|log on|Войти|ورود|登录|Přihlásit se|Přihlaste|Авторизоваться|Авторизация|entrar|ログイン|로그인|inloggen|Συνδέσου|accedi|ログオン|Giriş Yap|登入|connecter|connectez-vous|Connexion|Вход|inicia/i;
const registerRegex =
  /regist|sign up|signup|sign-up|sign_up|join|new|登録|neu|erstellen|設定|신규|Créer|Nouveau|baru|nouă|nieuw|create[a-zA-Z\s]+account|create[a-zA-Z\s]+profile|activate[a-zA-Z\s]+account|Zugang anlegen|Angaben prüfen|Konto erstellen|ثبت نام|登録|注册|cadastr|Зарегистрироваться|Регистрация|Bellige alynmak|تسجيل|ΕΓΓΡΑΦΗΣ|Εγγραφή|Créer mon compte|Créer un compte|Mendaftar|가입하기|inschrijving|Zarejestruj się|Deschideți un cont|Создать аккаунт|ร่วม|Üye Ol|ساخت حساب کاربری|Schrijf je|S'inscrire/i;
const emailRegex = /mail/i;
const usernameRegex = /user|member/i;
const nameRegex = /first|last|middle/i;
const subscriptionRegex =
  /subscri|trial|offer|information|angebote|probe|ニュースレター|abonn|promotion|news/i;
const termsAndConditionsRegex =
  /terms|condition|rules|policy|privacy|nutzungsbedingungen|AGB|richtlinien|datenschutz|términos|condiciones/i;
const pwForgottenRegex =
  /forgot|reset|set password|vergessen|vergeten|oublié|dimenticata|Esqueceu|esqueci|Забыли|忘记|找回|Zapomenuté|lost|忘れた|忘れられた|忘れの方|재설정|찾기|help|فراموشی| را فراموش کرده اید|Восстановить|Unuttu|perdus|重新設定|recover|remind|request|restore|trouble|olvidada/i;
const continueRegex =
  /continue|go on|weiter|fortfahren|ga verder|next|continuar/i;
const rememberMeRegex =
  /remember|stay|speichern|merken|bleiben|auto_login|auto-login|auto login|ricordami|manter|mantenha|savelogin|keep me logged in|keep me signed in|save email address|save id|stay signed in|次回からログオンIDの入力を省略する|メールアドレスを保存する|を保存|아이디저장|아이디 저장|로그인 상태 유지|lembrar|mantenha-me conectado|Запомни меня|запомнить меня|Запомните меня|Не спрашивать в следующий раз|下次自动登录|记住我|recordar|angemeldet bleiben/i;
const alreadySignedUpRegex = /already|bereits|schon|ya tienes cuenta/i;
const editProfile = /edit/i;

function createRuleset(coeffs, biases) {
  let descendantsCache;
  let surroundingNodesCache;

  /**
   * Check document characteristics
   */
  function docTitleMatchesRegisterRegex(fnode) {
    const docTitle = fnode.element.ownerDocument.title;
    return checkValueAgainstRegex(docTitle, registerRegex);
  }
  function docTitleMatchesEditProfileRegex(fnode) {
    const docTitle = fnode.element.ownerDocument.title;
    return checkValueAgainstRegex(docTitle, editProfile);
  }

  /**
   * Check header
   */
  function closestHeaderMatchesLoginRegex(fnode) {
    return closestHeaderMatchesPredicate(fnode.element, header =>
      checkValueAgainstRegex(header.innerText, loginRegex)
    );
  }
  function closestHeaderMatchesRegisterRegex(fnode) {
    return closestHeaderMatchesPredicate(fnode.element, header =>
      checkValueAgainstRegex(header.innerText, registerRegex)
    );
  }
  function closestHeaderMatchesSubscriptionRegex(fnode) {
    return closestHeaderMatchesPredicate(fnode.element, header =>
      checkValueAgainstRegex(header.innerText, subscriptionRegex)
    );
  }

  /**
   * Check checkboxes
   */
  function formHasRememberMeCheckbox(fnode) {
    return elementHasRegexMatchingCheckbox(fnode.element, rememberMeRegex);
  }
  function formHasSubcriptionCheckbox(fnode) {
    return elementHasRegexMatchingCheckbox(fnode.element, subscriptionRegex);
  }

  /**
   * Check input fields
   */
  function formHasFirstOrLastNameField(fnode) {
    const acValues = ["name", "given-name", "family-name"];
    return elementHasPredicateMatchingInput(
      fnode.element,
      elem =>
        atLeastOne(acValues.filter(ac => elem.autocomplete == ac)) ||
        inputFieldMatchesPredicate(elem, attr =>
          checkValueAgainstRegex(attr, nameRegex)
        )
    );
  }
  function formHasEmailField(fnode) {
    return elementHasPredicateMatchingInput(
      fnode.element,
      elem =>
        elem.autocomplete == "email" ||
        elem.type == "email" ||
        inputFieldMatchesPredicate(elem, attr =>
          checkValueAgainstRegex(attr, emailRegex)
        )
    );
  }
  function formHasUsernameField(fnode) {
    return elementHasPredicateMatchingInput(
      fnode.element,
      elem =>
        elem.autocomplete == "username" ||
        inputFieldMatchesPredicate(elem, attr =>
          checkValueAgainstRegex(attr, usernameRegex)
        )
    );
  }
  function formHasPasswordField(fnode) {
    const acValues = ["current-password", "new-password"];
    return elementHasPredicateMatchingInput(
      fnode.element,
      elem =>
        atLeastOne(acValues.filter(ac => elem.autocomplete == ac)) ||
        elem.type == "password"
    );
  }

  /**
   * Check autocomplete values
   */
  function formHasAcCurrentPassword(fnode) {
    return inputFieldMatchesSelector(
      fnode.element,
      "autocomplete=current-password"
    );
  }
  function formHasAcNewPassword(fnode) {
    return inputFieldMatchesSelector(
      fnode.element,
      "autocomplete=new-password"
    );
  }

  /**
   * Check hyperlinks within form
   */
  function formHasTermsAndConditionsHyperlink(fnode) {
    return elementHasPredicateMatchingHyperlink(
      fnode.element,
      termsAndConditionsRegex
    );
  }
  function formHasPasswordForgottenHyperlink(fnode) {
    return elementHasPredicateMatchingHyperlink(
      fnode.element,
      pwForgottenRegex
    );
  }
  function formHasAlreadySignedUpHyperlink(fnode) {
    return elementHasPredicateMatchingHyperlink(
      fnode.element,
      alreadySignedUpRegex
    );
  }

  /**
   * Check labels
   */
  function closestElementIsEmailLabelLike(fnode) {
    return elementHasPredicateMatchingInput(fnode.element, elem =>
      previousSiblingLabelMatchesRegex(elem, emailRegex)
    );
  }

  /**
   * Check buttons
   */
  function formHasRegisterButton(fnode) {
    return elementHasPredicateMatchingButton(
      fnode.element,
      button =>
        checkValueAgainstRegex(button.innerText, registerRegex) ||
        buttonMatchesPredicate(button, attr =>
          checkValueAgainstRegex(attr, registerRegex)
        )
    );
  }
  function formHasLoginButton(fnode) {
    return elementHasPredicateMatchingButton(
      fnode.element,
      button =>
        checkValueAgainstRegex(button.innerText, loginRegex) ||
        buttonMatchesPredicate(button, attr =>
          checkValueAgainstRegex(attr, loginRegex)
        )
    );
  }
  function formHasContinueButton(fnode) {
    return elementHasPredicateMatchingButton(
      fnode.element,
      button =>
        checkValueAgainstRegex(button.innerText, continueRegex) ||
        buttonMatchesPredicate(button, attr =>
          checkValueAgainstRegex(attr, continueRegex)
        )
    );
  }
  function formHasSubscribeButton(fnode) {
    return elementHasPredicateMatchingButton(
      fnode.element,
      button =>
        checkValueAgainstRegex(button.innerText, subscriptionRegex) ||
        buttonMatchesPredicate(button, attr =>
          checkValueAgainstRegex(attr, subscriptionRegex)
        )
    );
  }

  /**
   * Check form attributes
   */
  function formAttributesMatchRegisterRegex(fnode) {
    return formMatchesPredicate(fnode.element, attr =>
      checkValueAgainstRegex(attr, registerRegex)
    );
  }
  function formAttributesMatchLoginRegex(fnode) {
    return formMatchesPredicate(fnode.element, attr =>
      checkValueAgainstRegex(attr, loginRegex)
    );
  }
  function formAttributesMatchSubscriptionRegex(fnode) {
    return formMatchesPredicate(fnode.element, attr =>
      checkValueAgainstRegex(attr, subscriptionRegex)
    );
  }
  function formAttributesMatchLoginAndRegisterRegex(fnode) {
    return formMatchesPredicate(fnode.element, attr =>
      checkValueAgainstAllRegex(attr, [registerRegex, loginRegex])
    );
  }

  /**
   * HELPER FUNCTIONS
   */
  function elementMatchesPredicate(element, predicate, additional = []) {
    return attributesMatch(
      element,
      predicate,
      ["id", "name", "className"].concat(additional)
    );
  }
  function formMatchesPredicate(element, predicate) {
    return elementMatchesPredicate(element, predicate, ["action"]);
  }
  function inputFieldMatchesPredicate(element, predicate) {
    return elementMatchesPredicate(element, predicate, ["placeholder"]);
  }
  function inputFieldMatchesSelector(element, selector) {
    return atLeastOne(getElementDescendants(element, `input[${selector}]`));
  }
  function buttonMatchesPredicate(element, predicate) {
    return elementMatchesPredicate(element, predicate, [
      "value",
      "id",
      "title",
    ]);
  }
  function elementHasPredicateMatchingDescendant(element, selector, predicate) {
    const matchingElements = getElementDescendants(element, selector);
    return matchingElements.some(predicate);
  }
  function elementHasPredicateMatchingHeader(element, predicate) {
    return (
      elementHasPredicateMatchingDescendant(
        element,
        "h1,h2,h3,h4,h5,h6",
        predicate
      ) ||
      elementHasPredicateMatchingDescendant(
        element,
        "div[class*=heading],div[class*=header],div[class*=title],header",
        predicate
      )
    );
  }
  function elementHasPredicateMatchingButton(element, predicate) {
    return elementHasPredicateMatchingDescendant(
      element,
      "button,input[type=submit],input[type=button]",
      predicate
    );
  }
  function elementHasPredicateMatchingInput(element, predicate) {
    return elementHasPredicateMatchingDescendant(element, "input", predicate);
  }
  function elementHasPredicateMatchingHyperlink(element, regexExp) {
    return elementHasPredicateMatchingDescendant(
      element,
      "a",
      link =>
        previousSiblingLabelMatchesRegex(link, regexExp) ||
        checkValueAgainstRegex(link.innerText, regexExp) ||
        elementMatchesPredicate(
          link,
          attr => checkValueAgainstRegex(attr, regexExp),
          ["href"]
        ) ||
        nextSiblingLabelMatchesRegex(link, regexExp)
    );
  }
  function elementHasRegexMatchingCheckbox(element, regexExp) {
    return elementHasPredicateMatchingDescendant(
      element,
      "input[type=checkbox], div[class*=checkbox]",
      box =>
        elementMatchesPredicate(box, attr =>
          checkValueAgainstRegex(attr, regexExp)
        ) || nextSiblingLabelMatchesRegex(box, regexExp)
    );
  }

  function nextSiblingLabelMatchesRegex(element, regexExp) {
    let nextElem = element.nextElementSibling;
    if (nextElem && nextElem.tagName == "LABEL") {
      return checkValueAgainstRegex(nextElem.innerText, regexExp);
    }
    let closestElem = closestElementFollowing(element, "label");
    return closestElem
      ? checkValueAgainstRegex(closestElem.innerText, regexExp)
      : false;
  }

  function previousSiblingLabelMatchesRegex(element, regexExp) {
    let previousElem = element.previousElementSibling;
    if (previousElem && previousElem.tagName == "LABEL") {
      return checkValueAgainstRegex(previousElem.innerText, regexExp);
    }
    let closestElem = closestElementPreceding(element, "label");
    return closestElem
      ? checkValueAgainstRegex(closestElem.innerText, regexExp)
      : false;
  }
  function getElementDescendants(element, selector) {
    const selectorToDescendants = setDefault(
      descendantsCache,
      element,
      () => new Map()
    );

    return setDefault(selectorToDescendants, selector, () =>
      Array.from(element.querySelectorAll(selector))
    );
  }

  function clearCache() {
    descendantsCache = new WeakMap();
    surroundingNodesCache = new WeakMap();
  }
  function closestHeaderMatchesPredicate(element, predicate) {
    return (
      elementHasPredicateMatchingHeader(element, predicate) ||
      closestHeaderAboveMatchesPredicate(element, predicate)
    );
  }
  function closestHeaderAboveMatchesPredicate(element, predicate) {
    let closestHeader = closestElementPreceding(element, "h1,h2,h3,h4,h5,h6");

    if (closestHeader !== null) {
      if (predicate(closestHeader)) {
        return true;
      }
    }
    closestHeader = closestElementPreceding(
      element,
      "div[class*=heading],div[class*=header],div[class*=title],header"
    );
    return closestHeader ? predicate(closestHeader) : false;
  }
  function closestElementPreceding(element, selector) {
    return getSurroundingNodes(element, selector).precedingNode;
  }
  function closestElementFollowing(element, selector) {
    return getSurroundingNodes(element, selector).followingNode;
  }
  function getSurroundingNodes(element, selector) {
    const selectorToSurroundingNodes = setDefault(
      surroundingNodesCache,
      element,
      () => new Map()
    );

    return setDefault(selectorToSurroundingNodes, selector, () => {
      let elements = getElementDescendants(element.ownerDocument, selector);
      let followingIndex = closestFollowingNodeIndex(elements, element);
      let precedingIndex = followingIndex - 1;
      let preceding = precedingIndex < 0 ? null : elements[precedingIndex];
      let following =
        followingIndex == elements.length ? null : elements[followingIndex];
      return { precedingNode: preceding, followingNode: following };
    });
  }
  function closestFollowingNodeIndex(elements, element) {
    let low = 0;
    let high = elements.length;
    while (low < high) {
      let i = (low + high) >>> 1;
      if (
        element.compareDocumentPosition(elements[i]) &
        Node.DOCUMENT_POSITION_PRECEDING
      ) {
        low = i + 1;
      } else {
        high = i;
      }
    }
    return low;
  }

  function checkValueAgainstAllRegex(value, regexExp = []) {
    return regexExp.every(reg => checkValueAgainstRegex(value, reg));
  }

  function checkValueAgainstRegex(value, regexExp) {
    return value ? regexExp.test(value) : false;
  }
  function atLeastOne(iter) {
    return iter.length >= 1;
  }

  /**
   * CREATION OF RULESET
   */
  const rules = ruleset(
    [
      rule(
        DEVELOPMENT ? dom("form").when(isVisible) : element("form"),
        type("form").note(clearCache)
      ),
      // Check form attributes
      rule(type("form"), score(formAttributesMatchRegisterRegex), {
        name: "formAttributesMatchRegisterRegex",
      }),
      rule(type("form"), score(formAttributesMatchLoginRegex), {
        name: "formAttributesMatchLoginRegex",
      }),
      rule(type("form"), score(formAttributesMatchSubscriptionRegex), {
        name: "formAttributesMatchSubscriptionRegex",
      }),
      rule(type("form"), score(formAttributesMatchLoginAndRegisterRegex), {
        name: "formAttributesMatchLoginAndRegisterRegex",
      }),
      // Check autocomplete attributes
      rule(type("form"), score(formHasAcCurrentPassword), {
        name: "formHasAcCurrentPassword",
      }),
      rule(type("form"), score(formHasAcNewPassword), {
        name: "formHasAcNewPassword",
      }),
      // Check input fields
      rule(type("form"), score(formHasEmailField), {
        name: "formHasEmailField",
      }),
      rule(type("form"), score(formHasUsernameField), {
        name: "formHasUsernameField",
      }),
      rule(type("form"), score(formHasPasswordField), {
        name: "formHasPasswordField",
      }),
      rule(type("form"), score(formHasFirstOrLastNameField), {
        name: "formHasFirstOrLastNameField",
      }),
      // Check buttons
      rule(type("form"), score(formHasRegisterButton), {
        name: "formHasRegisterButton",
      }),
      rule(type("form"), score(formHasLoginButton), {
        name: "formHasLoginButton",
      }),
      rule(type("form"), score(formHasContinueButton), {
        name: "formHasContinueButton",
      }),
      rule(type("form"), score(formHasSubscribeButton), {
        name: "formHasSubscribeButton",
      }),
      // Check hyperlinks
      rule(type("form"), score(formHasTermsAndConditionsHyperlink), {
        name: "formHasTermsAndConditionsHyperlink",
      }),
      rule(type("form"), score(formHasPasswordForgottenHyperlink), {
        name: "formHasPasswordForgottenHyperlink",
      }),
      rule(type("form"), score(formHasAlreadySignedUpHyperlink), {
        name: "formHasAlreadySignedUpHyperlink",
      }),
      // Check labels
      rule(type("form"), score(closestElementIsEmailLabelLike), {
        name: "closestElementIsEmailLabelLike",
      }),
      // Check checkboxes
      rule(type("form"), score(formHasRememberMeCheckbox), {
        name: "formHasRememberMeCheckbox",
      }),
      rule(type("form"), score(formHasSubcriptionCheckbox), {
        name: "formHasSubcriptionCheckbox",
      }),
      // Check header
      rule(type("form"), score(closestHeaderMatchesRegisterRegex), {
        name: "closestHeaderMatchesRegisterRegex",
      }),
      rule(type("form"), score(closestHeaderMatchesLoginRegex), {
        name: "closestHeaderMatchesLoginRegex",
      }),
      rule(type("form"), score(closestHeaderMatchesSubscriptionRegex), {
        name: "closestHeaderMatchesSubscriptionRegex",
      }),
      // Check doc title
      rule(type("form"), score(docTitleMatchesRegisterRegex), {
        name: "docTitleMatchesRegisterRegex",
      }),
      rule(type("form"), score(docTitleMatchesEditProfileRegex), {
        name: "docTitleMatchesEditProfileRegex",
      }),
      rule(type("form"), out("form")),
    ],
    coeffs,
    biases
  );
  return rules;
}

/**
 * --- END OF CODE FROM TRAINING REPOSITORY ---
 */

export const SignUpFormRuleset = {
  type: "form",
  rules: createRuleset([...coefficients.form], biases),
};
PK
!<f����modules/SimpleServices.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Dumping ground for simple services for which the isolation of a full global
 * is overkill. Be careful about namespace pollution, and be mindful about
 * importing lots of JSMs in global scope, since this file will almost certainly
 * be loaded from enough callsites that any such imports will always end up getting
 * eagerly loaded at startup.
 */

/* globals WebExtensionPolicy */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "streamConv",
  "@mozilla.org/streamConverters;1",
  "nsIStreamConverterService"
);
const ArrayBufferInputStream = Components.Constructor(
  "@mozilla.org/io/arraybuffer-input-stream;1",
  "nsIArrayBufferInputStream",
  "setData"
);

/*
 * This class provides a stream filter for locale messages in CSS files served
 * by the moz-extension: protocol handler.
 *
 * See SubstituteChannel in netwerk/protocol/res/ExtensionProtocolHandler.cpp
 * for usage.
 */
export function AddonLocalizationConverter() {}

AddonLocalizationConverter.prototype = {
  QueryInterface: ChromeUtils.generateQI(["nsIStreamConverter"]),

  FROM_TYPE: "application/vnd.mozilla.webext.unlocalized",
  TO_TYPE: "text/css",

  checkTypes(aFromType, aToType) {
    if (aFromType != this.FROM_TYPE) {
      throw Components.Exception(
        "Invalid aFromType value",
        Cr.NS_ERROR_INVALID_ARG,
        Components.stack.caller.caller
      );
    }
    if (aToType != this.TO_TYPE) {
      throw Components.Exception(
        "Invalid aToType value",
        Cr.NS_ERROR_INVALID_ARG,
        Components.stack.caller.caller
      );
    }
  },

  // aContext must be a nsIURI object for a valid moz-extension: URL.
  getAddon(aContext) {
    // In this case, we want the add-on ID even if the URL is web accessible,
    // so check the root rather than the exact path.
    let uri = Services.io.newURI("/", null, aContext);

    let addon = WebExtensionPolicy.getByURI(uri);
    if (!addon) {
      throw new Components.Exception(
        "Invalid context",
        Cr.NS_ERROR_INVALID_ARG
      );
    }
    return addon;
  },

  convertToStream(aAddon, aString) {
    aString = aAddon.localize(aString);
    let bytes = new TextEncoder().encode(aString).buffer;
    return new ArrayBufferInputStream(bytes, 0, bytes.byteLength);
  },

  convert(aStream, aFromType, aToType, aContext) {
    this.checkTypes(aFromType, aToType);
    let addon = this.getAddon(aContext);

    let count = aStream.available();
    let string = count
      ? new TextDecoder().decode(lazy.NetUtil.readInputStream(aStream, count))
      : "";
    return this.convertToStream(addon, string);
  },

  asyncConvertData(aFromType, aToType, aListener, aContext) {
    this.checkTypes(aFromType, aToType);
    this.addon = this.getAddon(aContext);
    this.listener = aListener;
  },

  onStartRequest() {
    this.parts = [];
    this.decoder = new TextDecoder();
  },

  onDataAvailable(aRequest, aInputStream, aOffset, aCount) {
    let bytes = lazy.NetUtil.readInputStream(aInputStream, aCount);
    this.parts.push(this.decoder.decode(bytes, { stream: true }));
  },

  onStopRequest(aRequest, aStatusCode) {
    try {
      this.listener.onStartRequest(aRequest, null);
      if (Components.isSuccessCode(aStatusCode)) {
        this.parts.push(this.decoder.decode());
        let string = this.parts.join("");
        let stream = this.convertToStream(this.addon, string);

        this.listener.onDataAvailable(aRequest, stream, 0, stream.available());
      }
    } catch (e) {
      aStatusCode = e.result || Cr.NS_ERROR_FAILURE;
    }
    this.listener.onStopRequest(aRequest, aStatusCode);
  },
};

export function HttpIndexViewer() {}

HttpIndexViewer.prototype = {
  QueryInterface: ChromeUtils.generateQI(["nsIDocumentLoaderFactory"]),

  createInstance(
    aCommand,
    aChannel,
    aLoadGroup,
    aContentType,
    aContainer,
    aExtraInfo,
    aDocListenerResult
  ) {
    // Bug 1824325: application/http-index-format is deprecated for almost all
    // sites, we only allow it for urls with a inner scheme of "file" or
    // "moz-gio" (specified in network.http_index_format.allowed_schemes).
    // This also includes jar: and resource:// uris, as jar: uris has a inner
    // scheme of "file", and resource:// uris have been turned into either a
    // jar: or file:// uri by the point where we are checking them here.

    let uri = aChannel.URI;
    if (uri instanceof Ci.nsINestedURI) {
      uri = uri.QueryInterface(Ci.nsINestedURI).innermostURI;
    }

    const allowedSchemes = Services.prefs.getStringPref(
      "network.http_index_format.allowed_schemes",
      ""
    );
    let isFile =
      allowedSchemes === "*" || allowedSchemes.split(",").some(uri.schemeIs);
    let contentType = isFile ? "text/html" : "text/plain";

    aChannel.contentType = contentType;

    // NOTE: This assumes that both text/html and text/plain will continue to be
    // handled by nsContentDLF. If this ever changes this logic will need to be
    // updated.
    let factory = Cc[
      "@mozilla.org/content/document-loader-factory;1"
    ].getService(Ci.nsIDocumentLoaderFactory);

    let listener = {};
    let res = factory.createInstance(
      "view",
      aChannel,
      aLoadGroup,
      contentType,
      aContainer,
      aExtraInfo,
      listener
    );

    if (isFile) {
      aDocListenerResult.value = lazy.streamConv.asyncConvertData(
        "application/http-index-format",
        "text/html",
        listener.value,
        null
      );
    } else {
      aDocListenerResult.value = listener.value;
      aChannel.loadInfo.browsingContext.window.console.warn(
        "application/http-index-format is deprecated, content will display as plain text"
      );
    }

    return res;
  },
};
PK
!<s�|T��modules/SlowScriptDebug.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export function SlowScriptDebug() {}

SlowScriptDebug.prototype = {
  classDescription: "Slow script debug handler",
  QueryInterface: ChromeUtils.generateQI(["nsISlowScriptDebug"]),

  get activationHandler() {
    return this._activationHandler;
  },
  set activationHandler(cb) {
    this._activationHandler = cb;
  },

  get remoteActivationHandler() {
    return this._remoteActivationHandler;
  },
  set remoteActivationHandler(cb) {
    this._remoteActivationHandler = cb;
  },
};
PK
!<z-Χ�
�
modules/Sqlite.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * PRIVACY WARNING
 * ===============
 *
 * Database file names can be exposed through telemetry and in crash reports on
 * the https://crash-stats.mozilla.org site, to allow recognizing the affected
 * database.
 * if your database name may contain privacy sensitive information, e.g. an
 * URL origin, you should use openDatabaseWithFileURL and pass an explicit
 * TelemetryFilename to it. That name will be used both for telemetry and for
 * thread names in crash reports.
 * If you have different needs (e.g. using the javascript module or an async
 * connection from the main thread) please coordinate with the mozStorage peers.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { setTimeout } from "resource://gre/modules/Timer.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(
  lazy,
  {
    AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
    FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
  },
  { global: "contextual" }
);

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "FinalizationWitnessService",
  "@mozilla.org/toolkit/finalizationwitness;1",
  "nsIFinalizationWitnessService"
);

// Regular expression used by isInvalidBoundLikeQuery
var likeSqlRegex = /\bLIKE\b\s(?![@:?])/i;

// Counts the number of created connections per database basename(). This is
// used for logging to distinguish connection instances.
var connectionCounters = new Map();

// Tracks identifiers of wrapped connections, that are Storage connections
// opened through mozStorage and then wrapped by Sqlite.sys.mjs to use its syntactic
// sugar API.  Since these connections have an unknown origin, we use this set
// to differentiate their behavior.
var wrappedConnections = new Set();

/**
 * Once `true`, reject any attempt to open or close a database.
 */
function isClosed() {
  // If Barriers have not been initialized yet, just trust AppStartup.
  if (
    typeof Object.getOwnPropertyDescriptor(lazy, "Barriers").get == "function"
  ) {
    // It's still possible to open new connections at profile-before-change, so
    // use the next phase here, as a fallback.
    return Services.startup.isInOrBeyondShutdownPhase(
      Ci.nsIAppStartup.SHUTDOWN_PHASE_XPCOMWILLSHUTDOWN
    );
  }
  return lazy.Barriers.shutdown.client.isClosed;
}

var Debugging = {
  // Tests should fail if a connection auto closes.  The exception is
  // when finalization itself is tested, in which case this flag
  // should be set to false.
  failTestsOnAutoClose: true,
};

/**
 * Helper function to check whether LIKE is implemented using proper bindings.
 *
 * @param sql
 *        (string) The SQL query to be verified.
 * @return boolean value telling us whether query was correct or not
 */
function isInvalidBoundLikeQuery(sql) {
  return likeSqlRegex.test(sql);
}

// Displays a script error message
function logScriptError(message) {
  let consoleMessage = Cc["@mozilla.org/scripterror;1"].createInstance(
    Ci.nsIScriptError
  );
  let stack = new Error();
  consoleMessage.init(
    message,
    stack.fileName,
    stack.lineNumber,
    0,
    Ci.nsIScriptError.errorFlag,
    "component javascript"
  );
  Services.console.logMessage(consoleMessage);

  // This `Promise.reject` will cause tests to fail.  The debugging
  // flag can be used to suppress this for tests that explicitly
  // test auto closes.
  if (Debugging.failTestsOnAutoClose) {
    Promise.reject(new Error(message));
  }
}

/**
 * Gets connection identifier from its database file name.
 *
 * @param fileName
 *        A database file string name.
 * @return the connection identifier.
 */
function getIdentifierByFileName(fileName) {
  let number = connectionCounters.get(fileName) || 0;
  connectionCounters.set(fileName, number + 1);
  return fileName + "#" + number;
}

/**
 * Convert mozIStorageError to common NS_ERROR_*
 * The conversion is mostly based on the one in
 * mozStoragePrivateHelpers::ConvertResultCode, plus a few additions.
 *
 * @param {integer} result a mozIStorageError result code.
 * @returns {integer} an NS_ERROR_* result code.
 */
function convertStorageErrorResult(result) {
  switch (result) {
    case Ci.mozIStorageError.PERM:
    case Ci.mozIStorageError.AUTH:
    case Ci.mozIStorageError.CANTOPEN:
      return Cr.NS_ERROR_FILE_ACCESS_DENIED;
    case Ci.mozIStorageError.LOCKED:
      return Cr.NS_ERROR_FILE_IS_LOCKED;
    case Ci.mozIStorageError.READONLY:
      return Cr.NS_ERROR_FILE_READ_ONLY;
    case Ci.mozIStorageError.ABORT:
    case Ci.mozIStorageError.INTERRUPT:
      return Cr.NS_ERROR_ABORT;
    case Ci.mozIStorageError.TOOBIG:
    case Ci.mozIStorageError.FULL:
      return Cr.NS_ERROR_FILE_NO_DEVICE_SPACE;
    case Ci.mozIStorageError.NOMEM:
      return Cr.NS_ERROR_OUT_OF_MEMORY;
    case Ci.mozIStorageError.BUSY:
      return Cr.NS_ERROR_STORAGE_BUSY;
    case Ci.mozIStorageError.CONSTRAINT:
      return Cr.NS_ERROR_STORAGE_CONSTRAINT;
    case Ci.mozIStorageError.NOLFS:
    case Ci.mozIStorageError.IOERR:
      return Cr.NS_ERROR_STORAGE_IOERR;
    case Ci.mozIStorageError.SCHEMA:
    case Ci.mozIStorageError.MISMATCH:
    case Ci.mozIStorageError.MISUSE:
    case Ci.mozIStorageError.RANGE:
      return Ci.NS_ERROR_UNEXPECTED;
    case Ci.mozIStorageError.CORRUPT:
    case Ci.mozIStorageError.EMPTY:
    case Ci.mozIStorageError.FORMAT:
    case Ci.mozIStorageError.NOTADB:
      return Cr.NS_ERROR_FILE_CORRUPTED;
    default:
      return Cr.NS_ERROR_FAILURE;
  }
}
/**
 * Barriers used to ensure that Sqlite.sys.mjs is shutdown after all
 * its clients.
 */
ChromeUtils.defineLazyGetter(lazy, "Barriers", () => {
  let Barriers = {
    /**
     * Public barrier that clients may use to add blockers to the
     * shutdown of Sqlite.sys.mjs. Triggered by profile-before-change.
     * Once all blockers of this barrier are lifted, we close the
     * ability to open new connections.
     */
    shutdown: new lazy.AsyncShutdown.Barrier(
      "Sqlite.sys.mjs: wait until all clients have completed their task"
    ),

    /**
     * Private barrier blocked by connections that are still open.
     * Triggered after Barriers.shutdown is lifted and `isClosed()` returns
     * `true`.
     */
    connections: new lazy.AsyncShutdown.Barrier(
      "Sqlite.sys.mjs: wait until all connections are closed"
    ),
  };

  /**
   * Observer for the event which is broadcasted when the finalization
   * witness `_witness` of `OpenedConnection` is garbage collected.
   *
   * The observer is passed the connection identifier of the database
   * connection that is being finalized.
   */
  let finalizationObserver = function (subject, topic, identifier) {
    let connectionData = ConnectionData.byId.get(identifier);

    if (connectionData === undefined) {
      logScriptError(
        "Error: Attempt to finalize unknown Sqlite connection: " +
          identifier +
          "\n"
      );
      return;
    }

    ConnectionData.byId.delete(identifier);
    logScriptError(
      "Warning: Sqlite connection '" +
        identifier +
        "' was not properly closed. Auto-close triggered by garbage collection.\n"
    );
    connectionData.close();
  };
  Services.obs.addObserver(finalizationObserver, "sqlite-finalization-witness");

  /**
   * Ensure that Sqlite.sys.mjs:
   * - informs its clients before shutting down;
   * - lets clients open connections during shutdown, if necessary;
   * - waits for all connections to be closed before shutdown.
   */
  lazy.AsyncShutdown.profileBeforeChange.addBlocker(
    "Sqlite.sys.mjs shutdown blocker",
    async function () {
      await Barriers.shutdown.wait();
      // At this stage, all clients have had a chance to open (and close)
      // their databases. Some previous close operations may still be pending,
      // so we need to wait until they are complete before proceeding.
      await Barriers.connections.wait();

      // Everything closed, no finalization events to catch
      Services.obs.removeObserver(
        finalizationObserver,
        "sqlite-finalization-witness"
      );
    },

    function status() {
      if (isClosed()) {
        // We are waiting for the connections to close. The interesting
        // status is therefore the list of connections still pending.
        return {
          description: "Waiting for connections to close",
          state: Barriers.connections.state,
        };
      }

      // We are still in the first stage: waiting for the barrier
      // to be lifted. The interesting status is therefore that of
      // the barrier.
      return {
        description: "Waiting for the barrier to be lifted",
        state: Barriers.shutdown.state,
      };
    }
  );

  return Barriers;
});

const VACUUM_CATEGORY = "vacuum-participant";
const VACUUM_CONTRACTID = "@sqlite.module.js/vacuum-participant;";
var registeredVacuumParticipants = new Map();

function registerVacuumParticipant(connectionData) {
  let contractId = VACUUM_CONTRACTID + connectionData._identifier;
  let factory = {
    createInstance(iid) {
      return connectionData.QueryInterface(iid);
    },
    QueryInterface: ChromeUtils.generateQI(["nsIFactory"]),
  };
  let cid = Services.uuid.generateUUID();
  Components.manager
    .QueryInterface(Ci.nsIComponentRegistrar)
    .registerFactory(cid, contractId, contractId, factory);
  Services.catMan.addCategoryEntry(
    VACUUM_CATEGORY,
    contractId,
    contractId,
    false,
    false
  );
  registeredVacuumParticipants.set(contractId, { cid, factory });
}

function unregisterVacuumParticipant(connectionData) {
  let contractId = VACUUM_CONTRACTID + connectionData._identifier;
  let component = registeredVacuumParticipants.get(contractId);
  if (component) {
    Components.manager
      .QueryInterface(Ci.nsIComponentRegistrar)
      .unregisterFactory(component.cid, component.factory);
    Services.catMan.deleteCategoryEntry(VACUUM_CATEGORY, contractId, false);
  }
}

/**
 * Create a ConsoleInstance logger with a given prefix.
 * @param {string} prefix The prefix to use when logging.
 * @returns {ConsoleInstance} a console logger.
 */
function createLoggerWithPrefix(prefix) {
  return console.createInstance({
    prefix: `SQLite JSM (${prefix})`,
    maxLogLevelPref: "toolkit.sqlitejsm.loglevel",
  });
}

/**
 * Connection data with methods necessary for closing the connection.
 *
 * To support auto-closing in the event of garbage collection, this
 * data structure contains all the connection data of an opened
 * connection and all of the methods needed for sucessfully closing
 * it.
 *
 * By putting this information in its own separate object, it is
 * possible to store an additional reference to it without preventing
 * a garbage collection of a finalization witness in
 * OpenedConnection. When the witness detects a garbage collection,
 * this object can be used to close the connection.
 *
 * This object contains more methods than just `close`.  When
 * OpenedConnection needs to use the methods in this object, it will
 * dispatch its method calls here.
 */
function ConnectionData(connection, identifier, options = {}) {
  this._logger = createLoggerWithPrefix(`Connection ${identifier}`);
  this._logger.debug("Opened");

  this._dbConn = connection;

  // This is a unique identifier for the connection, generated through
  // getIdentifierByFileName.  It may be used for logging or as a key in Maps.
  this._identifier = identifier;

  this._open = true;

  this._cachedStatements = new Map();
  this._anonymousStatements = new Map();
  this._anonymousCounter = 0;

  // A map from statement index to mozIStoragePendingStatement, to allow for
  // canceling prior to finalizing the mozIStorageStatements.
  this._pendingStatements = new Map();

  // Increments for each executed statement for the life of the connection.
  this._statementCounter = 0;

  // Increments whenever we request a unique operation id.
  this._operationsCounter = 0;

  if ("defaultTransactionType" in options) {
    this.defaultTransactionType = options.defaultTransactionType;
  } else {
    this.defaultTransactionType = convertStorageTransactionType(
      this._dbConn.defaultTransactionType
    );
  }
  // Tracks whether this instance initiated a transaction.
  this._initiatedTransaction = false;
  // Manages a chain of transactions promises, so that new transactions
  // always happen in queue to the previous ones.  It never rejects.
  this._transactionQueue = Promise.resolve();

  this._idleShrinkMS = options.shrinkMemoryOnConnectionIdleMS;
  if (this._idleShrinkMS) {
    this._idleShrinkTimer = Cc["@mozilla.org/timer;1"].createInstance(
      Ci.nsITimer
    );
    // We wait for the first statement execute to start the timer because
    // shrinking now would not do anything.
  }

  // Deferred whose promise is resolved when the connection closing procedure
  // is complete.
  this._deferredClose = Promise.withResolvers();
  this._closeRequested = false;

  // An AsyncShutdown barrier used to make sure that we wait until clients
  // are done before shutting down the connection.
  this._barrier = new lazy.AsyncShutdown.Barrier(
    `${this._identifier}: waiting for clients`
  );

  lazy.Barriers.connections.client.addBlocker(
    this._identifier + ": waiting for shutdown",
    this._deferredClose.promise,
    () => ({
      identifier: this._identifier,
      isCloseRequested: this._closeRequested,
      hasDbConn: !!this._dbConn,
      initiatedTransaction: this._initiatedTransaction,
      pendingStatements: this._pendingStatements.size,
      statementCounter: this._statementCounter,
    })
  );

  // We avoid creating a timer for every transaction, because in most cases they
  // are not canceled and they are only used as a timeout.
  // Instead the timer is reused when it's sufficiently close to the previous
  // creation time (see `_getTimeoutPromise` for more info).
  this._timeoutPromise = null;
  // The last timestamp when we should consider using `this._timeoutPromise`.
  this._timeoutPromiseExpires = 0;

  this._useIncrementalVacuum = !!options.incrementalVacuum;
  if (this._useIncrementalVacuum) {
    this._logger.debug("Set auto_vacuum INCREMENTAL");
    this.execute("PRAGMA auto_vacuum = 2").catch(ex => {
      this._logger.error("Setting auto_vacuum to INCREMENTAL failed.");
      console.error(ex);
    });
  }

  this._expectedPageSize = options.pageSize ?? 0;
  if (this._expectedPageSize) {
    this._logger.debug("Set page_size to " + this._expectedPageSize);
    this.execute("PRAGMA page_size = " + this._expectedPageSize).catch(ex => {
      this._logger.error(
        `Setting page_size to ${this._expectedPageSize} failed.`
      );
      console.error(ex);
    });
  }

  this._vacuumOnIdle = options.vacuumOnIdle;
  if (this._vacuumOnIdle) {
    this._logger.debug("Register as vacuum participant");
    this.QueryInterface = ChromeUtils.generateQI([
      Ci.mozIStorageVacuumParticipant,
    ]);
    registerVacuumParticipant(this);
  }
}

/**
 * Map of connection identifiers to ConnectionData objects
 *
 * The connection identifier is a human-readable name of the
 * database. Used by finalization witnesses to be able to close opened
 * connections on garbage collection.
 *
 * Key: _identifier of ConnectionData
 * Value: ConnectionData object
 */
ConnectionData.byId = new Map();

ConnectionData.prototype = Object.freeze({
  get expectedDatabasePageSize() {
    return this._expectedPageSize;
  },

  get useIncrementalVacuum() {
    return this._useIncrementalVacuum;
  },

  /**
   * This should only be used by the VacuumManager component.
   * @see unsafeRawConnection for an official (but still unsafe) API.
   */
  get databaseConnection() {
    if (this._vacuumOnIdle) {
      return this._dbConn;
    }
    return null;
  },

  onBeginVacuum() {
    let granted = !this.transactionInProgress;
    this._logger.debug("Begin Vacuum - " + granted ? "granted" : "denied");
    return granted;
  },

  onEndVacuum(succeeded) {
    this._logger.debug("End Vacuum - " + succeeded ? "success" : "failure");
  },

  /**
   * Run a task, ensuring that its execution will not be interrupted by shutdown.
   *
   * As the operations of this module are asynchronous, a sequence of operations,
   * or even an individual operation, can still be pending when the process shuts
   * down. If any of this operations is a write, this can cause data loss, simply
   * because the write has not been completed (or even started) by shutdown.
   *
   * To avoid this risk, clients are encouraged to use `executeBeforeShutdown` for
   * any write operation, as follows:
   *
   * myConnection.executeBeforeShutdown("Bookmarks: Removing a bookmark",
   *   async function(db) {
   *     // The connection will not be closed and shutdown will not proceed
   *     // until this task has completed.
   *
   *     // `db` exposes the same API as `myConnection` but provides additional
   *     // logging support to help debug hard-to-catch shutdown timeouts.
   *
   *     await db.execute(...);
   * }));
   *
   * @param {string} name A human-readable name for the ongoing operation, used
   *  for logging and debugging purposes.
   * @param {function(db)} task A function that takes as argument a Sqlite.sys.mjs
   *  db and returns a Promise.
   */
  executeBeforeShutdown(parent, name, task) {
    if (!name) {
      throw new TypeError("Expected a human-readable name as first argument");
    }
    if (typeof task != "function") {
      throw new TypeError("Expected a function as second argument");
    }
    if (this._closeRequested) {
      throw new Error(
        `${this._identifier}: cannot execute operation ${name}, the connection is already closing`
      );
    }

    // Status, used for AsyncShutdown crash reports.
    let status = {
      // The latest command started by `task`, either as a
      // sql string, or as one of "<not started>" or "<closing>".
      command: "<not started>",

      // `true` if `command` was started but not completed yet.
      isPending: false,
    };

    // An object with the same API as `this` but with
    // additional logging. To keep logging simple, we
    // assume that `task` is not running several queries
    // concurrently.
    let loggedDb = Object.create(parent, {
      execute: {
        value: async (sql, ...rest) => {
          status.isPending = true;
          status.command = sql;
          try {
            return await this.execute(sql, ...rest);
          } finally {
            status.isPending = false;
          }
        },
      },
      close: {
        value: async () => {
          status.isPending = true;
          status.command = "<close>";
          try {
            return await this.close();
          } finally {
            status.isPending = false;
          }
        },
      },
      executeCached: {
        value: async (sql, ...rest) => {
          status.isPending = true;
          status.command = "cached: " + sql;
          try {
            return await this.executeCached(sql, ...rest);
          } finally {
            status.isPending = false;
          }
        },
      },
    });

    let promiseResult = task(loggedDb);
    if (
      !promiseResult ||
      typeof promiseResult != "object" ||
      !("then" in promiseResult)
    ) {
      throw new TypeError("Expected a Promise");
    }
    let key = `${this._identifier}: ${name} (${this._getOperationId()})`;
    let promiseComplete = promiseResult.catch(() => {});
    this._barrier.client.addBlocker(key, promiseComplete, {
      fetchState: () => status,
    });

    return (async () => {
      try {
        return await promiseResult;
      } finally {
        this._barrier.client.removeBlocker(key, promiseComplete);
      }
    })();
  },
  close() {
    this._closeRequested = true;

    if (!this._dbConn) {
      return this._deferredClose.promise;
    }

    this._logger.debug("Request to close connection.");
    this._clearIdleShrinkTimer();

    if (this._vacuumOnIdle) {
      this._logger.debug("Unregister as vacuum participant");
      unregisterVacuumParticipant(this);
    }

    return this._barrier.wait().then(() => {
      if (!this._dbConn) {
        return undefined;
      }
      return this._finalize();
    });
  },

  clone(readOnly = false) {
    this.ensureOpen();

    this._logger.debug("Request to clone connection.");

    let options = {
      connection: this._dbConn,
      readOnly,
    };
    if (this._idleShrinkMS) {
      options.shrinkMemoryOnConnectionIdleMS = this._idleShrinkMS;
    }

    return cloneStorageConnection(options);
  },
  _getOperationId() {
    return this._operationsCounter++;
  },
  _finalize() {
    this._logger.debug("Finalizing connection.");
    // Cancel any pending statements.
    for (let [, /* k */ statement] of this._pendingStatements) {
      statement.cancel();
    }
    this._pendingStatements.clear();

    // We no longer need to track these.
    this._statementCounter = 0;

    // Next we finalize all active statements.
    for (let [, /* k */ statement] of this._anonymousStatements) {
      statement.finalize();
    }
    this._anonymousStatements.clear();

    for (let [, /* k */ statement] of this._cachedStatements) {
      statement.finalize();
    }
    this._cachedStatements.clear();

    // This guards against operations performed between the call to this
    // function and asyncClose() finishing. See also bug 726990.
    this._open = false;

    // We must always close the connection at the Sqlite.sys.mjs-level, not
    // necessarily at the mozStorage-level.
    let markAsClosed = () => {
      this._logger.debug("Closed");
      // Now that the connection is closed, no need to keep
      // a blocker for Barriers.connections.
      lazy.Barriers.connections.client.removeBlocker(
        this._deferredClose.promise
      );
      this._deferredClose.resolve();
    };
    if (wrappedConnections.has(this._identifier)) {
      wrappedConnections.delete(this._identifier);
      this._dbConn = null;
      markAsClosed();
    } else {
      this._logger.debug("Calling asyncClose().");
      try {
        this._dbConn.asyncClose(markAsClosed);
      } catch (ex) {
        // If for any reason asyncClose fails, we must still remove the
        // shutdown blockers and resolve _deferredClose.
        markAsClosed();
      } finally {
        this._dbConn = null;
      }
    }
    return this._deferredClose.promise;
  },

  executeCached(sql, params = null, onRow = null) {
    this.ensureOpen();

    if (!sql) {
      throw new Error("sql argument is empty.");
    }

    let statement = this._cachedStatements.get(sql);
    if (!statement) {
      statement = this._dbConn.createAsyncStatement(sql);
      this._cachedStatements.set(sql, statement);
    }

    this._clearIdleShrinkTimer();

    return new Promise((resolve, reject) => {
      try {
        this._executeStatement(sql, statement, params, onRow).then(
          result => {
            this._startIdleShrinkTimer();
            resolve(result);
          },
          error => {
            this._startIdleShrinkTimer();
            reject(error);
          }
        );
      } catch (ex) {
        this._startIdleShrinkTimer();
        throw ex;
      }
    });
  },

  execute(sql, params = null, onRow = null) {
    if (typeof sql != "string") {
      throw new Error("Must define SQL to execute as a string: " + sql);
    }

    this.ensureOpen();

    let statement = this._dbConn.createAsyncStatement(sql);
    let index = this._anonymousCounter++;

    this._anonymousStatements.set(index, statement);
    this._clearIdleShrinkTimer();

    let onFinished = () => {
      this._anonymousStatements.delete(index);
      statement.finalize();
      this._startIdleShrinkTimer();
    };

    return new Promise((resolve, reject) => {
      try {
        this._executeStatement(sql, statement, params, onRow).then(
          rows => {
            onFinished();
            resolve(rows);
          },
          error => {
            onFinished();
            reject(error);
          }
        );
      } catch (ex) {
        onFinished();
        throw ex;
      }
    });
  },

  get transactionInProgress() {
    return this._open && this._dbConn.transactionInProgress;
  },

  executeTransaction(func, type) {
    // Identify the caller for debugging purposes.
    let caller = new Error().stack
      .split("\n", 3)
      .pop()
      .match(/^([^@]*@).*\/([^\/:]+)[:0-9]*$/);
    caller = caller[1] + caller[2];
    this._logger.debug(`Transaction (type ${type}) requested by: ${caller}`);

    if (type == OpenedConnection.prototype.TRANSACTION_DEFAULT) {
      type = this.defaultTransactionType;
    } else if (!OpenedConnection.TRANSACTION_TYPES.includes(type)) {
      throw new Error("Unknown transaction type: " + type);
    }
    this.ensureOpen();

    // If a transaction yields on a never resolved promise, or is mistakenly
    // nested, it could hang the transactions queue forever.  Thus we timeout
    // the execution after a meaningful amount of time, to ensure in any case
    // we'll proceed after a while.
    let timeoutPromise = this._getTimeoutPromise();

    let promise = this._transactionQueue.then(() => {
      if (this._closeRequested) {
        throw new Error("Transaction canceled due to a closed connection.");
      }

      let transactionPromise = (async () => {
        // At this point we should never have an in progress transaction, since
        // they are enqueued.
        if (this._initiatedTransaction) {
          this._logger.error(
            "Unexpected transaction in progress when trying to start a new one."
          );
        }
        try {
          // We catch errors in statement execution to detect nested transactions.
          try {
            await this.execute("BEGIN " + type + " TRANSACTION");
            this._logger.debug(`Begin transaction`);
            this._initiatedTransaction = true;
          } catch (ex) {
            // Unfortunately, if we are wrapping an existing connection, a
            // transaction could have been started by a client of the same
            // connection that doesn't use Sqlite.sys.mjs (e.g. C++ consumer).
            // The best we can do is proceed without a transaction and hope
            // things won't break.
            if (wrappedConnections.has(this._identifier)) {
              this._logger.warn(
                "A new transaction could not be started cause the wrapped connection had one in progress",
                ex
              );
            } else {
              this._logger.warn(
                "A transaction was already in progress, likely a nested transaction",
                ex
              );
              throw ex;
            }
          }

          let result;
          try {
            result = await Promise.race([func(), timeoutPromise]);
          } catch (ex) {
            // It's possible that the exception has been caused by trying to
            // close the connection in the middle of a transaction.
            if (this._closeRequested) {
              this._logger.warn(
                "Connection closed while performing a transaction",
                ex
              );
            } else {
              // Otherwise the function didn't resolve before the timeout, or
              // generated an unexpected error. Then we rollback.
              if (ex.becauseTimedOut) {
                let caller_module = caller.split(":", 1)[0];
                Services.telemetry.keyedScalarAdd(
                  "mozstorage.sqlitejsm_transaction_timeout",
                  caller_module,
                  1
                );
                this._logger.error(
                  `The transaction requested by ${caller} timed out. Rolling back`,
                  ex
                );
              } else {
                this._logger.error(
                  `Error during transaction requested by ${caller}. Rolling back`,
                  ex
                );
              }
              // If we began a transaction, we must rollback it.
              if (this._initiatedTransaction) {
                try {
                  await this.execute("ROLLBACK TRANSACTION");
                  this._initiatedTransaction = false;
                  this._logger.debug(`Roll back transaction`);
                } catch (inner) {
                  this._logger.error("Could not roll back transaction", inner);
                }
              }
            }
            // Rethrow the exception.
            throw ex;
          }

          // See comment above about connection being closed during transaction.
          if (this._closeRequested) {
            this._logger.warn(
              "Connection closed before committing the transaction."
            );
            throw new Error(
              "Connection closed before committing the transaction."
            );
          }

          // If we began a transaction, we must commit it.
          if (this._initiatedTransaction) {
            try {
              await this.execute("COMMIT TRANSACTION");
              this._logger.debug(`Commit transaction`);
            } catch (ex) {
              this._logger.warn("Error committing transaction", ex);
              throw ex;
            }
          }

          return result;
        } finally {
          this._initiatedTransaction = false;
        }
      })();

      return Promise.race([transactionPromise, timeoutPromise]);
    });
    // Atomically update the queue before anyone else has a chance to enqueue
    // further transactions.
    this._transactionQueue = promise.catch(ex => {
      this._logger.error(ex);
    });

    // Make sure that we do not shutdown the connection during a transaction.
    this._barrier.client.addBlocker(
      `Transaction (${this._getOperationId()})`,
      this._transactionQueue
    );
    return promise;
  },

  shrinkMemory() {
    this._logger.debug("Shrinking memory usage.");
    return this.execute("PRAGMA shrink_memory").finally(() => {
      this._clearIdleShrinkTimer();
    });
  },

  discardCachedStatements() {
    let count = 0;
    for (let [, /* k */ statement] of this._cachedStatements) {
      ++count;
      statement.finalize();
    }
    this._cachedStatements.clear();
    this._logger.debug("Discarded " + count + " cached statements.");
    return count;
  },

  interrupt() {
    this._logger.debug("Trying to interrupt.");
    this.ensureOpen();
    this._dbConn.interrupt();
  },

  /**
   * Helper method to bind parameters of various kinds through
   * reflection.
   */
  _bindParameters(statement, params) {
    if (!params) {
      return;
    }

    function bindParam(obj, key, val) {
      let isBlob =
        val && typeof val == "object" && val.constructor.name == "Uint8Array";
      let args = [key, val];
      if (isBlob) {
        args.push(val.length);
      }
      let methodName = `bind${isBlob ? "Blob" : ""}By${
        typeof key == "number" ? "Index" : "Name"
      }`;
      obj[methodName](...args);
    }

    if (Array.isArray(params)) {
      // It's an array of separate params.
      if (params.length && typeof params[0] == "object" && params[0] !== null) {
        let paramsArray = statement.newBindingParamsArray();
        for (let p of params) {
          let bindings = paramsArray.newBindingParams();
          for (let [key, value] of Object.entries(p)) {
            bindParam(bindings, key, value);
          }
          paramsArray.addParams(bindings);
        }

        statement.bindParameters(paramsArray);
        return;
      }

      // Indexed params.
      for (let i = 0; i < params.length; i++) {
        bindParam(statement, i, params[i]);
      }
      return;
    }

    // Named params.
    if (params && typeof params == "object") {
      for (let k in params) {
        bindParam(statement, k, params[k]);
      }
      return;
    }

    throw new Error(
      "Invalid type for bound parameters. Expected Array or " +
        "object. Got: " +
        params
    );
  },

  _executeStatement(sql, statement, params, onRow) {
    if (statement.state != statement.MOZ_STORAGE_STATEMENT_READY) {
      throw new Error("Statement is not ready for execution.");
    }

    if (onRow && typeof onRow != "function") {
      throw new Error("onRow must be a function. Got: " + onRow);
    }

    this._bindParameters(statement, params);

    let index = this._statementCounter++;

    let deferred = Promise.withResolvers();
    let userCancelled = false;
    let errors = [];
    let rows = [];
    let handledRow = false;

    // Don't incur overhead for serializing params unless the messages go
    // somewhere.
    if (this._logger.shouldLog("Trace")) {
      let msg = "Stmt #" + index + " " + sql;

      if (params) {
        msg += " - " + JSON.stringify(params);
      }
      this._logger.trace(msg);
    } else {
      this._logger.debug("Stmt #" + index + " starting");
    }

    let self = this;
    let pending = statement.executeAsync({
      handleResult(resultSet) {
        // .cancel() may not be immediate and handleResult() could be called
        // after a .cancel().
        for (
          let row = resultSet.getNextRow();
          row && !userCancelled;
          row = resultSet.getNextRow()
        ) {
          if (!onRow) {
            rows.push(row);
            continue;
          }

          handledRow = true;

          try {
            onRow(row, () => {
              userCancelled = true;
              pending.cancel();
            });
          } catch (e) {
            self._logger.warn("Exception when calling onRow callback", e);
          }
        }
      },

      handleError(error) {
        self._logger.warn(
          "Error when executing SQL (" + error.result + "): " + error.message
        );
        errors.push(error);
      },

      handleCompletion(reason) {
        self._logger.debug("Stmt #" + index + " finished.");
        self._pendingStatements.delete(index);

        switch (reason) {
          case Ci.mozIStorageStatementCallback.REASON_FINISHED:
          case Ci.mozIStorageStatementCallback.REASON_CANCELED:
            // If there is an onRow handler, we always instead resolve to a
            // boolean indicating whether the onRow handler was called or not.
            let result = onRow ? handledRow : rows;
            deferred.resolve(result);
            break;

          case Ci.mozIStorageStatementCallback.REASON_ERROR:
            let error = new Error(
              "Error(s) encountered during statement execution: " +
                errors.map(e => e.message).join(", ")
            );
            error.errors = errors;

            // Forward the error result.
            // Corruption is the most critical one so it's handled apart.
            if (errors.some(e => e.result == Ci.mozIStorageError.CORRUPT)) {
              error.result = Cr.NS_ERROR_FILE_CORRUPTED;
            } else {
              // Just use the first error result in the other cases.
              error.result = convertStorageErrorResult(errors[0]?.result);
            }

            deferred.reject(error);
            break;

          default:
            deferred.reject(
              new Error("Unknown completion reason code: " + reason)
            );
            break;
        }
      },
    });

    this._pendingStatements.set(index, pending);
    return deferred.promise;
  },

  ensureOpen() {
    if (!this._open) {
      throw new Error("Connection is not open.");
    }
  },

  _clearIdleShrinkTimer() {
    if (!this._idleShrinkTimer) {
      return;
    }

    this._idleShrinkTimer.cancel();
  },

  _startIdleShrinkTimer() {
    if (!this._idleShrinkTimer) {
      return;
    }

    this._idleShrinkTimer.initWithCallback(
      this.shrinkMemory.bind(this),
      this._idleShrinkMS,
      this._idleShrinkTimer.TYPE_ONE_SHOT
    );
  },

  /**
   * Returns a promise that will resolve after a time comprised between 80% of
   * `TRANSACTIONS_TIMEOUT_MS` and `TRANSACTIONS_TIMEOUT_MS`. Use
   * this method instead of creating several individual timers that may survive
   * longer than necessary.
   */
  _getTimeoutPromise() {
    if (this._timeoutPromise && Cu.now() <= this._timeoutPromiseExpires) {
      return this._timeoutPromise;
    }
    let timeoutPromise = new Promise((resolve, reject) => {
      setTimeout(() => {
        // Clear out this._timeoutPromise if it hasn't changed since we set it.
        if (this._timeoutPromise == timeoutPromise) {
          this._timeoutPromise = null;
        }
        let e = new Error(
          "Transaction timeout, most likely caused by unresolved pending work."
        );
        e.becauseTimedOut = true;
        reject(e);
      }, Sqlite.TRANSACTIONS_TIMEOUT_MS);
    });
    this._timeoutPromise = timeoutPromise;
    this._timeoutPromiseExpires =
      Cu.now() + Sqlite.TRANSACTIONS_TIMEOUT_MS * 0.2;
    return this._timeoutPromise;
  },

  /**
   * Asynchronously makes a copy of the SQLite database while there may still be
   * open connections on it.
   *
   * @param {string} destFilePath
   *   The path on the local filesystem to write the database copy. Any existing
   *   file at this path will be overwritten.
   * @param {number} [pagesPerStep=0]
   *   The number of pages to copy per step. If not supplied or is 0, falls back
   *   to the platform default which is currently 5.
   * @param {number} [stepDelayMs=0]
   *   The number of milliseconds to wait between copying step. If not supplied
   *   or is 0, falls back to the platform default which is currently 250.
   * @return Promise<undefined, nsresult>
   */
  async backupToFile(destFilePath, pagesPerStep = 0, stepDelayMs = 0) {
    if (!this._dbConn) {
      return Promise.reject(
        new Error("No opened database connection to create a backup from.")
      );
    }
    let destFile = await IOUtils.getFile(destFilePath);
    return new Promise((resolve, reject) => {
      this._dbConn.backupToFileAsync(
        destFile,
        result => {
          if (Components.isSuccessCode(result)) {
            resolve();
          } else {
            reject(result);
          }
        },
        pagesPerStep,
        stepDelayMs
      );
    });
  },
});

/**
 * Opens a connection to a SQLite database.
 *
 * The following parameters can control the connection:
 *
 *   path -- (string) The filesystem path of the database file to open. If the
 *       file does not exist, a new database will be created.
 *
 *   sharedMemoryCache -- (bool) Whether multiple connections to the database
 *       share the same memory cache. Sharing the memory cache likely results
 *       in less memory utilization. However, sharing also requires connections
 *       to obtain a lock, possibly making database access slower. Defaults to
 *       true.
 *
 *   shrinkMemoryOnConnectionIdleMS -- (integer) If defined, the connection
 *       will attempt to minimize its memory usage after this many
 *       milliseconds of connection idle. The connection is idle when no
 *       statements are executing. There is no default value which means no
 *       automatic memory minimization will occur. Please note that this is
 *       *not* a timer on the idle service and this could fire while the
 *       application is active.
 *
 *   readOnly -- (bool) Whether to open the database with SQLITE_OPEN_READONLY
 *       set. If used, writing to the database will fail. Defaults to false.
 *
 *   ignoreLockingMode -- (bool) Whether to ignore locks on the database held
 *       by other connections. If used, implies readOnly. Defaults to false.
 *       USE WITH EXTREME CAUTION. This mode WILL produce incorrect results or
 *       return "false positive" corruption errors if other connections write
 *       to the DB at the same time.
 *
 *   openNotExclusive -- (bool) Whether to open the database without an exclusive
 *       lock so the database can be accessed from multiple processes.
 *
 *   vacuumOnIdle -- (bool) Whether to register this connection to be vacuumed
 *       on idle by the VacuumManager component.
 *       If you're vacuum-ing an incremental vacuum database, ensure to also
 *       set incrementalVacuum to true, otherwise this will try to change it
 *       to full vacuum mode.
 *
 *   incrementalVacuum -- (bool) if set to true auto_vacuum = INCREMENTAL will
 *       be enabled for the database.
 *       Changing auto vacuum of an already populated database requires a full
 *       VACUUM. You can evaluate to enable vacuumOnIdle for that.
 *
 *   pageSize -- (integer) This allows to set a custom page size for the
 *       database. It is usually not necessary to set it, since the default
 *       value should be good for most consumers.
 *       Changing the page size of an already populated database requires a full
 *       VACUUM. You can evaluate to enable vacuumOnIdle for that.
 *
 *   testDelayedOpenPromise -- (promise) Used by tests to delay the open
 *       callback handling and execute code between asyncOpen and its callback.
 *
 * FUTURE options to control:
 *
 *   special named databases
 *   pragma TEMP STORE = MEMORY
 *   TRUNCATE JOURNAL
 *   SYNCHRONOUS = full
 *
 * @param options
 *        (Object) Parameters to control connection and open options.
 *
 * @return Promise<OpenedConnection>
 */
function openConnection(options) {
  let logger = createLoggerWithPrefix("ConnectionOpener");

  if (!options.path) {
    throw new Error("path not specified in connection options.");
  }

  if (isClosed()) {
    throw new Error(
      "Sqlite.sys.mjs has been shutdown. Cannot open connection to: " +
        options.path
    );
  }

  // Retains absolute paths and normalizes relative as relative to profile.
  let path = options.path;
  let file;
  try {
    file = lazy.FileUtils.File(path);
  } catch (ex) {
    // For relative paths, we will get an exception from trying to initialize
    // the file. We must then join this path to the profile directory.
    if (ex.result == Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH) {
      path = PathUtils.joinRelative(
        Services.dirsvc.get("ProfD", Ci.nsIFile).path,
        options.path
      );
      file = lazy.FileUtils.File(path);
    } else {
      throw ex;
    }
  }

  let sharedMemoryCache =
    "sharedMemoryCache" in options ? options.sharedMemoryCache : true;

  let openedOptions = {};

  if ("shrinkMemoryOnConnectionIdleMS" in options) {
    if (!Number.isInteger(options.shrinkMemoryOnConnectionIdleMS)) {
      throw new Error(
        "shrinkMemoryOnConnectionIdleMS must be an integer. " +
          "Got: " +
          options.shrinkMemoryOnConnectionIdleMS
      );
    }

    openedOptions.shrinkMemoryOnConnectionIdleMS =
      options.shrinkMemoryOnConnectionIdleMS;
  }

  if ("defaultTransactionType" in options) {
    let defaultTransactionType = options.defaultTransactionType;
    if (!OpenedConnection.TRANSACTION_TYPES.includes(defaultTransactionType)) {
      throw new Error(
        "Unknown default transaction type: " + defaultTransactionType
      );
    }

    openedOptions.defaultTransactionType = defaultTransactionType;
  }

  if ("vacuumOnIdle" in options) {
    if (typeof options.vacuumOnIdle != "boolean") {
      throw new Error("Invalid vacuumOnIdle: " + options.vacuumOnIdle);
    }
    openedOptions.vacuumOnIdle = options.vacuumOnIdle;
  }

  if ("incrementalVacuum" in options) {
    if (typeof options.incrementalVacuum != "boolean") {
      throw new Error(
        "Invalid incrementalVacuum: " + options.incrementalVacuum
      );
    }
    openedOptions.incrementalVacuum = options.incrementalVacuum;
  }

  if ("pageSize" in options) {
    if (
      ![512, 1024, 2048, 4096, 8192, 16384, 32768, 65536].includes(
        options.pageSize
      )
    ) {
      throw new Error("Invalid pageSize: " + options.pageSize);
    }
    openedOptions.pageSize = options.pageSize;
  }

  let identifier = getIdentifierByFileName(PathUtils.filename(path));

  logger.debug("Opening database: " + path + " (" + identifier + ")");

  return new Promise((resolve, reject) => {
    let dbOpenOptions = Ci.mozIStorageService.OPEN_DEFAULT;
    if (sharedMemoryCache) {
      dbOpenOptions |= Ci.mozIStorageService.OPEN_SHARED;
    }
    if (options.readOnly) {
      dbOpenOptions |= Ci.mozIStorageService.OPEN_READONLY;
    }
    if (options.ignoreLockingMode) {
      dbOpenOptions |= Ci.mozIStorageService.OPEN_IGNORE_LOCKING_MODE;
      dbOpenOptions |= Ci.mozIStorageService.OPEN_READONLY;
    }
    if (options.openNotExclusive) {
      dbOpenOptions |= Ci.mozIStorageService.OPEN_NOT_EXCLUSIVE;
    }

    let dbConnectionOptions = Ci.mozIStorageService.CONNECTION_DEFAULT;

    Services.storage.openAsyncDatabase(
      file,
      dbOpenOptions,
      dbConnectionOptions,
      async (status, connection) => {
        if (!connection) {
          logger.error(`Could not open connection to ${path}: ${status}`);
          let error = new Components.Exception(
            `Could not open connection to ${path}: ${status}`,
            status
          );
          reject(error);
          return;
        }
        logger.debug("Connection opened");

        if (options.testDelayedOpenPromise) {
          await options.testDelayedOpenPromise;
        }

        if (isClosed()) {
          connection.QueryInterface(Ci.mozIStorageAsyncConnection).asyncClose();
          reject(
            new Error(
              "Sqlite.sys.mjs has been shutdown. Cannot open connection to: " +
                options.path
            )
          );
          return;
        }

        try {
          resolve(
            new OpenedConnection(
              connection.QueryInterface(Ci.mozIStorageAsyncConnection),
              identifier,
              openedOptions
            )
          );
        } catch (ex) {
          logger.error("Could not open database", ex);
          connection.asyncClose();
          reject(ex);
        }
      }
    );
  });
}

/**
 * Creates a clone of an existing and open Storage connection.  The clone has
 * the same underlying characteristics of the original connection and is
 * returned in form of an OpenedConnection handle.
 *
 * The following parameters can control the cloned connection:
 *
 *   connection -- (mozIStorageAsyncConnection) The original Storage connection
 *       to clone.  It's not possible to clone connections to memory databases.
 *
 *   readOnly -- (boolean) - If true the clone will be read-only.  If the
 *       original connection is already read-only, the clone will be, regardless
 *       of this option.  If the original connection is using the shared cache,
 *       this parameter will be ignored and the clone will be as privileged as
 *       the original connection.
 *   shrinkMemoryOnConnectionIdleMS -- (integer) If defined, the connection
 *       will attempt to minimize its memory usage after this many
 *       milliseconds of connection idle. The connection is idle when no
 *       statements are executing. There is no default value which means no
 *       automatic memory minimization will occur. Please note that this is
 *       *not* a timer on the idle service and this could fire while the
 *       application is active.
 *
 *
 * @param options
 *        (Object) Parameters to control connection and clone options.
 *
 * @return Promise<OpenedConnection>
 */
function cloneStorageConnection(options) {
  let logger = createLoggerWithPrefix("ConnectionCloner");

  let source = options && options.connection;
  if (!source) {
    throw new TypeError("connection not specified in clone options.");
  }
  if (!(source instanceof Ci.mozIStorageAsyncConnection)) {
    throw new TypeError("Connection must be a valid Storage connection.");
  }

  if (isClosed()) {
    throw new Error(
      "Sqlite.sys.mjs has been shutdown. Cannot clone connection to: " +
        source.databaseFile.path
    );
  }

  let openedOptions = {};

  if ("shrinkMemoryOnConnectionIdleMS" in options) {
    if (!Number.isInteger(options.shrinkMemoryOnConnectionIdleMS)) {
      throw new TypeError(
        "shrinkMemoryOnConnectionIdleMS must be an integer. " +
          "Got: " +
          options.shrinkMemoryOnConnectionIdleMS
      );
    }
    openedOptions.shrinkMemoryOnConnectionIdleMS =
      options.shrinkMemoryOnConnectionIdleMS;
  }

  let path = source.databaseFile.path;
  let identifier = getIdentifierByFileName(PathUtils.filename(path));

  logger.debug("Cloning database: " + path + " (" + identifier + ")");

  return new Promise((resolve, reject) => {
    source.asyncClone(!!options.readOnly, (status, connection) => {
      if (!connection) {
        logger.error("Could not clone connection: " + status);
        reject(new Error("Could not clone connection: " + status));
        return;
      }
      logger.debug("Connection cloned");

      if (isClosed()) {
        connection.QueryInterface(Ci.mozIStorageAsyncConnection).asyncClose();
        reject(
          new Error(
            "Sqlite.sys.mjs has been shutdown. Cannot open connection to: " +
              options.path
          )
        );
        return;
      }

      try {
        let conn = connection.QueryInterface(Ci.mozIStorageAsyncConnection);
        resolve(new OpenedConnection(conn, identifier, openedOptions));
      } catch (ex) {
        logger.error("Could not clone database", ex);
        connection.asyncClose();
        reject(ex);
      }
    });
  });
}

/**
 * Wraps an existing and open Storage connection with Sqlite.sys.mjs API.  The
 * wrapped connection clone has the same underlying characteristics of the
 * original connection and is returned in form of an OpenedConnection handle.
 *
 * Clients are responsible for closing both the Sqlite.sys.mjs wrapper and the
 * underlying mozStorage connection.
 *
 * The following parameters can control the wrapped connection:
 *
 *   connection -- (mozIStorageAsyncConnection) The original Storage connection
 *       to wrap.
 *
 * @param options
 *        (Object) Parameters to control connection and wrap options.
 *
 * @return Promise<OpenedConnection>
 */
function wrapStorageConnection(options) {
  let logger = createLoggerWithPrefix("ConnectionWrapper");

  let connection = options && options.connection;
  if (!connection || !(connection instanceof Ci.mozIStorageAsyncConnection)) {
    throw new TypeError("connection not specified or invalid.");
  }

  if (isClosed()) {
    throw new Error(
      "Sqlite.sys.mjs has been shutdown. Cannot wrap connection to: " +
        connection.databaseFile.path
    );
  }

  let identifier = getIdentifierByFileName(connection.databaseFile.leafName);

  logger.debug("Wrapping database: " + identifier);
  return new Promise(resolve => {
    try {
      let conn = connection.QueryInterface(Ci.mozIStorageAsyncConnection);
      let wrapper = new OpenedConnection(conn, identifier);
      // We must not handle shutdown of a wrapped connection, since that is
      // already handled by the opener.
      wrappedConnections.add(identifier);
      resolve(wrapper);
    } catch (ex) {
      logger.error("Could not wrap database", ex);
      throw ex;
    }
  });
}

/**
 * Handle on an opened SQLite database.
 *
 * This is essentially a glorified wrapper around mozIStorageConnection.
 * However, it offers some compelling advantages.
 *
 * The main functions on this type are `execute` and `executeCached`. These are
 * ultimately how all SQL statements are executed. It's worth explaining their
 * differences.
 *
 * `execute` is used to execute one-shot SQL statements. These are SQL
 * statements that are executed one time and then thrown away. They are useful
 * for dynamically generated SQL statements and clients who don't care about
 * performance (either their own or wasting resources in the overall
 * application). Because of the performance considerations, it is recommended
 * to avoid `execute` unless the statement you are executing will only be
 * executed once or seldomly.
 *
 * `executeCached` is used to execute a statement that will presumably be
 * executed multiple times. The statement is parsed once and stuffed away
 * inside the connection instance. Subsequent calls to `executeCached` will not
 * incur the overhead of creating a new statement object. This should be used
 * in preference to `execute` when a specific SQL statement will be executed
 * multiple times.
 *
 * Instances of this type are not meant to be created outside of this file.
 * Instead, first open an instance of `UnopenedSqliteConnection` and obtain
 * an instance of this type by calling `open`.
 *
 * FUTURE IMPROVEMENTS
 *
 *   Ability to enqueue operations. Currently there can be race conditions,
 *   especially as far as transactions are concerned. It would be nice to have
 *   an enqueueOperation(func) API that serially executes passed functions.
 *
 *   Support for SAVEPOINT (named/nested transactions) might be useful.
 *
 * @param connection
 *        (mozIStorageConnection) Underlying SQLite connection.
 * @param identifier
 *        (string) The unique identifier of this database. It may be used for
 *        logging or as a key in Maps.
 * @param options [optional]
 *        (object) Options to control behavior of connection. See
 *        `openConnection`.
 */
function OpenedConnection(connection, identifier, options = {}) {
  // Store all connection data in a field distinct from the
  // witness. This enables us to store an additional reference to this
  // field without preventing garbage collection of
  // OpenedConnection. On garbage collection, we will still be able to
  // close the database using this extra reference.
  this._connectionData = new ConnectionData(connection, identifier, options);

  // Store the extra reference in a map with connection identifier as
  // key.
  ConnectionData.byId.set(
    this._connectionData._identifier,
    this._connectionData
  );

  // Make a finalization witness. If this object is garbage collected
  // before its `forget` method has been called, an event with topic
  // "sqlite-finalization-witness" is broadcasted along with the
  // connection identifier string of the database.
  this._witness = lazy.FinalizationWitnessService.make(
    "sqlite-finalization-witness",
    this._connectionData._identifier
  );
}

OpenedConnection.TRANSACTION_TYPES = ["DEFERRED", "IMMEDIATE", "EXCLUSIVE"];

// Converts a `mozIStorageAsyncConnection::TRANSACTION_*` constant into the
// corresponding `OpenedConnection.TRANSACTION_TYPES` constant.
function convertStorageTransactionType(type) {
  if (!(type in OpenedConnection.TRANSACTION_TYPES)) {
    throw new Error("Unknown storage transaction type: " + type);
  }
  return OpenedConnection.TRANSACTION_TYPES[type];
}

OpenedConnection.prototype = Object.freeze({
  TRANSACTION_DEFAULT: "DEFAULT",
  TRANSACTION_DEFERRED: "DEFERRED",
  TRANSACTION_IMMEDIATE: "IMMEDIATE",
  TRANSACTION_EXCLUSIVE: "EXCLUSIVE",

  /**
   * Returns a handle to the underlying `mozIStorageAsyncConnection`. This is
   * ⚠️ **extremely unsafe** ⚠️ because `Sqlite.sys.mjs` continues to manage the
   * connection's lifecycle, including transactions and shutdown blockers.
   * Misusing the raw connection can easily lead to data loss, memory leaks,
   * and errors.
   *
   * Consumers of the raw connection **must not** close or re-wrap it,
   * and should not run statements concurrently with `Sqlite.sys.mjs`.
   *
   * It's _much_ safer to open a `mozIStorage{Async}Connection` yourself,
   * and access it from JavaScript via `Sqlite.wrapStorageConnection`.
   * `unsafeRawConnection` is an escape hatch for cases where you can't
   * do that.
   *
   * Please do _not_ add new uses of `unsafeRawConnection` without review
   * from a storage peer.
   */
  get unsafeRawConnection() {
    return this._connectionData._dbConn;
  },

  /**
   * Returns the maximum number of bound parameters for statements executed
   * on this connection.
   *
   * @returns {number} The bound parameters limit.
   */
  get variableLimit() {
    return this.unsafeRawConnection.variableLimit;
  },

  /**
   * Set the the maximum number of bound parameters for statements executed
   * on this connection. If the passed-in value is higher than the maximum
   * default value, it will be silently truncated.
   *
   * @param {number} newLimit The bound parameters limit.
   */
  set variableLimit(newLimit) {
    this.unsafeRawConnection.variableLimit = newLimit;
  },

  /**
   * The integer schema version of the database.
   *
   * This is 0 if not schema version has been set.
   *
   * @return Promise<int>
   */
  getSchemaVersion(schemaName = "main") {
    return this.execute(`PRAGMA ${schemaName}.user_version`).then(result =>
      result[0].getInt32(0)
    );
  },

  setSchemaVersion(value, schemaName = "main") {
    if (!Number.isInteger(value)) {
      // Guarding against accidental SQLi
      throw new TypeError("Schema version must be an integer. Got " + value);
    }
    this._connectionData.ensureOpen();
    return this.execute(`PRAGMA ${schemaName}.user_version = ${value}`);
  },

  /**
   * Close the database connection.
   *
   * This must be performed when you are finished with the database.
   *
   * Closing the database connection has the side effect of forcefully
   * cancelling all active statements. Therefore, callers should ensure that
   * all active statements have completed before closing the connection, if
   * possible.
   *
   * The returned promise will be resolved once the connection is closed.
   * Successive calls to close() return the same promise.
   *
   * IMPROVEMENT: Resolve the promise to a closed connection which can be
   * reopened.
   *
   * @return Promise<>
   */
  close() {
    // Unless cleanup has already been done by a previous call to
    // `close`, delete the database entry from map and tell the
    // finalization witness to forget.
    if (ConnectionData.byId.has(this._connectionData._identifier)) {
      ConnectionData.byId.delete(this._connectionData._identifier);
      this._witness.forget();
    }
    return this._connectionData.close();
  },

  /**
   * Clones this connection to a new Sqlite one.
   *
   * The following parameters can control the cloned connection:
   *
   * @param readOnly
   *        (boolean) - If true the clone will be read-only.  If the original
   *        connection is already read-only, the clone will be, regardless of
   *        this option.  If the original connection is using the shared cache,
   *        this parameter will be ignored and the clone will be as privileged as
   *        the original connection.
   *
   * @return Promise<OpenedConnection>
   */
  clone(readOnly = false) {
    return this._connectionData.clone(readOnly);
  },

  executeBeforeShutdown(name, task) {
    return this._connectionData.executeBeforeShutdown(this, name, task);
  },

  /**
   * Execute a SQL statement and cache the underlying statement object.
   *
   * This function executes a SQL statement and also caches the underlying
   * derived statement object so subsequent executions are faster and use
   * less resources.
   *
   * This function optionally binds parameters to the statement as well as
   * optionally invokes a callback for every row retrieved.
   *
   * By default, no parameters are bound and no callback will be invoked for
   * every row.
   *
   * Bound parameters can be defined as an Array of positional arguments or
   * an object mapping named parameters to their values. If there are no bound
   * parameters, the caller can pass nothing or null for this argument.
   *
   * Callers are encouraged to pass objects rather than Arrays for bound
   * parameters because they prevent foot guns. With positional arguments, it
   * is simple to modify the parameter count or positions without fixing all
   * users of the statement. Objects/named parameters are a little safer
   * because changes in order alone won't result in bad things happening.
   *
   * When `onRow` is not specified, all returned rows are buffered before the
   * returned promise is resolved. For INSERT or UPDATE statements, this has
   * no effect because no rows are returned from these. However, it has
   * implications for SELECT statements.
   *
   * If your SELECT statement could return many rows or rows with large amounts
   * of data, for performance reasons it is recommended to pass an `onRow`
   * handler. Otherwise, the buffering may consume unacceptable amounts of
   * resources.
   *
   * If the second parameter of an `onRow` handler is called during execution
   * of the `onRow` handler, the execution of the statement is immediately
   * cancelled. Subsequent rows will not be processed and no more `onRow`
   * invocations will be made. The promise is resolved immediately.
   *
   * If an exception is thrown by the `onRow` handler, the exception is logged
   * and processing of subsequent rows occurs as if nothing happened. The
   * promise is still resolved (not rejected).
   *
   * The return value is a promise that will be resolved when the statement
   * has completed fully.
   *
   * The promise will be rejected with an `Error` instance if the statement
   * did not finish execution fully. The `Error` may have an `errors` property.
   * If defined, it will be an Array of objects describing individual errors.
   * Each object has the properties `result` and `message`. `result` is a
   * numeric error code and `message` is a string description of the problem.
   *
   * @param name
   *        (string) The name of the registered statement to execute.
   * @param params optional
   *        (Array or object) Parameters to bind.
   * @param onRow optional
   *        (function) Callback to receive each row from result.
   */
  executeCached(sql, params = null, onRow = null) {
    if (isInvalidBoundLikeQuery(sql)) {
      throw new Error("Please enter a LIKE clause with bindings");
    }
    return this._connectionData.executeCached(sql, params, onRow);
  },

  /**
   * Execute a one-shot SQL statement.
   *
   * If you find yourself feeding the same SQL string in this function, you
   * should *not* use this function and instead use `executeCached`.
   *
   * See `executeCached` for the meaning of the arguments and extended usage info.
   *
   * @param sql
   *        (string) SQL to execute.
   * @param params optional
   *        (Array or Object) Parameters to bind to the statement.
   * @param onRow optional
   *        (function) Callback to receive result of a single row.
   */
  execute(sql, params = null, onRow = null) {
    if (isInvalidBoundLikeQuery(sql)) {
      throw new Error("Please enter a LIKE clause with bindings");
    }
    return this._connectionData.execute(sql, params, onRow);
  },

  /**
   * The default behavior for transactions run on this connection.
   */
  get defaultTransactionType() {
    return this._connectionData.defaultTransactionType;
  },

  /**
   * Whether a transaction is currently in progress.
   *
   * Note that this is true if a transaction is active on the connection,
   * regardless of whether it was started by `Sqlite.sys.mjs` or another consumer.
   * See the explanation above `mozIStorageConnection.transactionInProgress` for
   * why this distinction matters.
   */
  get transactionInProgress() {
    return this._connectionData.transactionInProgress;
  },

  /**
   * Perform a transaction.
   *
   * *****************************************************************************
   * YOU SHOULD _NEVER_ NEST executeTransaction CALLS FOR ANY REASON, NOR
   * DIRECTLY, NOR THROUGH OTHER PROMISES.
   * FOR EXAMPLE, NEVER DO SOMETHING LIKE:
   *   await executeTransaction(async function () {
   *     ...some_code...
   *     await executeTransaction(async function () { // WRONG!
   *       ...some_code...
   *     })
   *     await someCodeThatExecuteTransaction(); // WRONG!
   *     await neverResolvedPromise; // WRONG!
   *   });
   * NESTING CALLS WILL BLOCK ANY FUTURE TRANSACTION UNTIL A TIMEOUT KICKS IN.
   * *****************************************************************************
   *
   * A transaction is specified by a user-supplied function that is an
   * async function. The function receives this connection instance as its argument.
   *
   * The supplied function is expected to return promises. These are often
   * promises created by calling `execute` and `executeCached`. If the
   * generator is exhausted without any errors being thrown, the
   * transaction is committed. If an error occurs, the transaction is
   * rolled back.
   *
   * The returned value from this function is a promise that will be resolved
   * once the transaction has been committed or rolled back. The promise will
   * be resolved to whatever value the supplied function resolves to. If
   * the transaction is rolled back, the promise is rejected.
   *
   * @param func
   *        (function) What to perform as part of the transaction.
   * @param type optional
   *        One of the TRANSACTION_* constants attached to this type.
   */
  executeTransaction(func, type = this.TRANSACTION_DEFAULT) {
    return this._connectionData.executeTransaction(() => func(this), type);
  },

  /**
   * Whether a table exists in the database (both persistent and temporary tables).
   *
   * @param name
   *        (string) Name of the table.
   *
   * @return Promise<bool>
   */
  tableExists(name) {
    return this.execute(
      "SELECT name FROM (SELECT * FROM sqlite_master UNION ALL " +
        "SELECT * FROM sqlite_temp_master) " +
        "WHERE type = 'table' AND name=?",
      [name]
    ).then(function onResult(rows) {
      return Promise.resolve(!!rows.length);
    });
  },

  /**
   * Whether a named index exists (both persistent and temporary tables).
   *
   * @param name
   *        (string) Name of the index.
   *
   * @return Promise<bool>
   */
  indexExists(name) {
    return this.execute(
      "SELECT name FROM (SELECT * FROM sqlite_master UNION ALL " +
        "SELECT * FROM sqlite_temp_master) " +
        "WHERE type = 'index' AND name=?",
      [name]
    ).then(function onResult(rows) {
      return Promise.resolve(!!rows.length);
    });
  },

  /**
   * Free up as much memory from the underlying database connection as possible.
   *
   * @return Promise<>
   */
  shrinkMemory() {
    return this._connectionData.shrinkMemory();
  },

  /**
   * Discard all cached statements.
   *
   * Note that this relies on us being non-interruptible between
   * the insertion or retrieval of a statement in the cache and its
   * execution: we finalize all statements, which is only safe if
   * they will not be executed again.
   *
   * @return (integer) the number of statements discarded.
   */
  discardCachedStatements() {
    return this._connectionData.discardCachedStatements();
  },

  /**
   * Interrupts pending database operations returning at the first opportunity.
   * Statement execution will throw an NS_ERROR_ABORT failure.
   * Can only be used on read-only connections.
   */
  interrupt() {
    this._connectionData.interrupt();
  },

  /**
   * Asynchronously makes a copy of the SQLite database while there may still be
   * open connections on it.
   *
   * @param {string} destFilePath
   *   The path on the local filesystem to write the database copy. Any existing
   *   file at this path will be overwritten.
   * @param {number} [pagesPerStep=0]
   *   The number of pages to copy per step. If not supplied or is 0, falls back
   *   to the platform default which is currently 5.
   * @param {number} [stepDelayMs=0]
   *   The number of milliseconds to wait between copying step. If not supplied
   *   or is 0, falls back to the platform default which is currently 250.
   * @return Promise<undefined, nsresult>
   */
  backup(destFilePath, pagesPerStep = 0, stepDelayMs = 0) {
    return this._connectionData.backupToFile(
      destFilePath,
      pagesPerStep,
      stepDelayMs
    );
  },
});

export var Sqlite = {
  // The maximum time to wait before considering a transaction stuck and
  // issuing a ROLLBACK, see `executeTransaction`. Could be modified by tests.
  TRANSACTIONS_TIMEOUT_MS: 300000, // 5 minutes

  openConnection,
  cloneStorageConnection,
  wrapStorageConnection,
  /**
   * Shutdown barrier client. May be used by clients to perform last-minute
   * cleanup prior to the shutdown of this module.
   *
   * See the documentation of AsyncShutdown.Barrier.prototype.client.
   */
  get shutdown() {
    return lazy.Barriers.shutdown.client;
  },
  failTestsOnAutoClose(enabled) {
    Debugging.failTestsOnAutoClose = enabled;
  },
};
PK
!<�Ux���modules/SubDialog.sys.mjs/* - This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this file,
   - You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

let lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "dragService",
  "@mozilla.org/widget/dragservice;1",
  "nsIDragService"
);

const HTML_NS = "http://www.w3.org/1999/xhtml";

/**
 * The SubDialog resize callback.
 * @callback SubDialog~resizeCallback
 * @param {DOMNode} title - The title element of the dialog.
 * @param {xul:browser} frame - The browser frame of the dialog.
 */

/**
 * SubDialog constructor creates a new subdialog from a template and appends
 * it to the parentElement.
 * @param {DOMNode} template - The template is copied to create a new dialog.
 * @param {DOMNode} parentElement - New dialog is appended onto parentElement.
 * @param {String}  id - A unique identifier for the dialog.
 * @param {Object}  dialogOptions - Dialog options object.
 * @param {String[]} [dialogOptions.styleSheets] - An array of URLs to additional
 * stylesheets to inject into the frame.
 * @param {Boolean} [consumeOutsideClicks] - Whether to close the dialog when
 * its background overlay is clicked.
 * @param {SubDialog~resizeCallback} [resizeCallback] - Function to be called on
 * dialog resize.
 */
export function SubDialog({
  template,
  parentElement,
  id,
  dialogOptions: {
    styleSheets = [],
    consumeOutsideClicks = true,
    resizeCallback,
  } = {},
}) {
  this._id = id;

  this._injectedStyleSheets = this._injectedStyleSheets.concat(styleSheets);
  this._consumeOutsideClicks = consumeOutsideClicks;
  this._resizeCallback = resizeCallback;
  this._overlay = template.cloneNode(true);
  this._box = this._overlay.querySelector(".dialogBox");
  this._titleBar = this._overlay.querySelector(".dialogTitleBar");
  this._titleElement = this._overlay.querySelector(".dialogTitle");
  this._closeButton = this._overlay.querySelector(".dialogClose");
  this._frame = this._overlay.querySelector(".dialogFrame");

  this._overlay.classList.add(`dialogOverlay-${id}`);
  this._frame.setAttribute("name", `dialogFrame-${id}`);
  this._frameCreated = new Promise(resolve => {
    this._frame.addEventListener(
      "load",
      () => {
        // We intentionally avoid handling or passing the event to the
        // resolve method to avoid shutdown window leaks. See bug 1686743.
        resolve();
      },
      {
        once: true,
        capture: true,
      }
    );
  });

  parentElement.appendChild(this._overlay);
  this._overlay.hidden = false;
}

SubDialog.prototype = {
  _closingCallback: null,
  _closingEvent: null,
  _isClosing: false,
  _frame: null,
  _frameCreated: null,
  _overlay: null,
  _box: null,
  _openedURL: null,
  _injectedStyleSheets: ["chrome://global/skin/in-content/common.css"],
  _resizeObserver: null,
  _template: null,
  _id: null,
  _titleElement: null,
  _closeButton: null,

  get frameContentWindow() {
    return this._frame?.contentWindow;
  },

  get _window() {
    return this._overlay?.ownerGlobal;
  },

  updateTitle(aEvent) {
    if (aEvent.target != this._frame.contentDocument) {
      return;
    }
    this._titleElement.textContent = this._frame.contentDocument.title;
  },

  injectStylesheet(aStylesheetURL) {
    const doc = this._frame.contentDocument;
    if ([...doc.styleSheets].find(s => s.href === aStylesheetURL)) {
      return;
    }

    // Attempt to insert the stylesheet as a link element into the same place in
    // the document as other link elements. It is almost certain that any
    // document will already have a localization or other stylesheet link
    // present.
    let links = doc.getElementsByTagNameNS(HTML_NS, "link");
    if (links.length) {
      let stylesheetLink = doc.createElementNS(HTML_NS, "link");
      stylesheetLink.setAttribute("rel", "stylesheet");
      stylesheetLink.setAttribute("href", aStylesheetURL);

      // Insert after the last found link element.
      links[links.length - 1].after(stylesheetLink);

      return;
    }

    // In the odd case just insert at the top as a processing instruction.
    let contentStylesheet = doc.createProcessingInstruction(
      "xml-stylesheet",
      'href="' + aStylesheetURL + '" type="text/css"'
    );
    doc.insertBefore(contentStylesheet, doc.documentElement);
  },

  async open(
    aURL,
    { features, closingCallback, closedCallback, sizeTo } = {},
    ...aParams
  ) {
    if (["available", "limitheight"].includes(sizeTo)) {
      this._box.setAttribute("sizeto", sizeTo);
    }

    // Create a promise so consumers can tell when we're done setting up.
    this._dialogReady = new Promise(resolve => {
      this._resolveDialogReady = resolve;
    });
    this._frame._dialogReady = this._dialogReady;

    // Assign close callbacks sync to ensure we can always callback even if the
    // SubDialog is closed directly after opening.
    let dialog = null;

    if (closingCallback) {
      this._closingCallback = (...args) => {
        closingCallback.apply(dialog, args);
      };
    }
    if (closedCallback) {
      this._closedCallback = (...args) => {
        closedCallback.apply(dialog, args);
      };
    }

    // Wait until frame is ready to prevent browser crash in tests
    await this._frameCreated;

    // If we're closing now that we've waited for the dialog to load, abort.
    if (this._isClosing) {
      return;
    }
    this._addDialogEventListeners();

    // Ensure we end any pending drag sessions:
    try {
      // The drag service getService call fails in puppeteer tests on Linux,
      // so this is in a try...catch as it shouldn't stop us from opening the
      // dialog. Bug 1806870 tracks fixing this.
      let session = lazy.dragService.getCurrentSession(this._window);
      if (session) {
        session.endDragSession(true);
      }
    } catch (ex) {
      console.error(ex);
    }

    // If the parent is chrome we also need open the dialog as chrome, otherwise
    // the openDialog call will fail.
    let dialogFeatures = `resizable,dialog=no,centerscreen,chrome=${
      this._window?.isChromeWindow ? "yes" : "no"
    }`;
    if (features) {
      dialogFeatures = `${features},${dialogFeatures}`;
    }

    dialog = this._window.openDialog(
      aURL,
      `dialogFrame-${this._id}`,
      dialogFeatures,
      ...aParams
    );

    this._closingEvent = null;
    this._isClosing = false;
    this._openedURL = aURL;

    dialogFeatures = dialogFeatures.replace(/,/g, "&");
    let featureParams = new URLSearchParams(dialogFeatures.toLowerCase());
    this._box.setAttribute(
      "resizable",
      featureParams.has("resizable") &&
        featureParams.get("resizable") != "no" &&
        featureParams.get("resizable") != "0"
    );
  },

  /**
   * Close the dialog and mark it as aborted.
   */
  abort() {
    this._closingEvent = new CustomEvent("dialogclosing", {
      bubbles: true,
      detail: { dialog: this, abort: true },
    });
    this._frame.contentWindow?.close();
    // It's possible that we're aborting this dialog before we've had a
    // chance to set up the contentWindow.close function override in
    // _onContentLoaded. If so, call this.close() directly to clean things
    // up. That'll be a no-op if the contentWindow.close override had been
    // set up, since this.close is idempotent.
    this.close(this._closingEvent);
  },

  close(aEvent = null) {
    if (this._isClosing) {
      return;
    }
    this._isClosing = true;
    this._closingPromise = new Promise(resolve => {
      this._resolveClosePromise = resolve;
    });

    if (this._closingCallback) {
      try {
        this._closingCallback.call(null, aEvent);
      } catch (ex) {
        console.error(ex);
      }
      this._closingCallback = null;
    }

    this._removeDialogEventListeners();

    this._overlay.style.visibility = "";
    // Clear the sizing inline styles.
    this._frame.removeAttribute("style");
    // Clear the sizing attributes
    this._box.removeAttribute("width");
    this._box.removeAttribute("height");
    this._box.style.removeProperty("--box-max-height-requested");
    this._box.style.removeProperty("--box-max-width-requested");
    this._box.style.removeProperty("min-height");
    this._box.style.removeProperty("min-width");
    this._overlay.style.removeProperty("--subdialog-inner-height");

    let onClosed = () => {
      this._openedURL = null;

      this._resolveClosePromise();

      if (this._closedCallback) {
        try {
          this._closedCallback.call(null, aEvent);
        } catch (ex) {
          console.error(ex);
        }
        this._closedCallback = null;
      }
    };

    // Wait for the frame to unload before running the closed callback.
    if (this._frame.contentWindow) {
      this._frame.contentWindow.addEventListener("unload", onClosed, {
        once: true,
      });
    } else {
      onClosed();
    }

    this._overlay.dispatchEvent(
      new CustomEvent("dialogclose", {
        bubbles: true,
        detail: { dialog: this },
      })
    );

    // Defer removing the overlay so the frame content window can unload.
    Services.tm.dispatchToMainThread(() => {
      this._overlay.remove();
    });
  },

  handleEvent(aEvent) {
    switch (aEvent.type) {
      case "click":
        // Close the dialog if the user clicked the overlay background, just
        // like when the user presses the ESC key (case "command" below).
        if (aEvent.target !== this._overlay) {
          break;
        }
        if (this._consumeOutsideClicks) {
          this._frame.contentWindow.close();
          break;
        }
        this._frame.focus();
        break;
      case "command":
        this._frame.contentWindow.close();
        break;
      case "dialogclosing":
        this._onDialogClosing(aEvent);
        break;
      case "DOMTitleChanged":
        this.updateTitle(aEvent);
        break;
      case "DOMFrameContentLoaded":
        this._onContentLoaded(aEvent);
        break;
      case "load":
        this._onLoad(aEvent);
        break;
      case "unload":
        this._onUnload(aEvent);
        break;
      case "keydown":
        this._onKeyDown(aEvent);
        break;
      case "focus":
        this._onParentWinFocus(aEvent);
        break;
    }
  },

  /* Private methods */

  _onUnload(aEvent) {
    if (
      aEvent.target !== this._frame?.contentDocument ||
      aEvent.target.location.href !== this._openedURL
    ) {
      return;
    }
    this.abort();
  },

  _onContentLoaded(aEvent) {
    if (
      aEvent.target != this._frame ||
      aEvent.target.contentWindow.location == "about:blank"
    ) {
      return;
    }

    for (let styleSheetURL of this._injectedStyleSheets) {
      this.injectStylesheet(styleSheetURL);
    }

    let { contentDocument } = this._frame;
    // Provide the ability for the dialog to know that it is loaded in a frame
    // rather than as a top-level window.
    for (let dialog of contentDocument.querySelectorAll("dialog")) {
      dialog.setAttribute("subdialog", "true");
    }
    // Sub-dialogs loaded in a chrome window should use the system font size so
    // that the user has a way to increase or decrease it via system settings.
    // Sub-dialogs loaded in the content area, on the other hand, can be zoomed
    // like web content.
    if (this._window.isChromeWindow) {
      contentDocument.documentElement.classList.add("system-font-size");
    }
    // Used by CSS to give the appropriate background colour in dark mode.
    contentDocument.documentElement.setAttribute("dialogroot", "true");

    this._frame.contentWindow.addEventListener("dialogclosing", this);

    let oldResizeBy = this._frame.contentWindow.resizeBy;
    this._frame.contentWindow.resizeBy = (resizeByWidth, resizeByHeight) => {
      // Only handle resizeByHeight currently.
      let frameHeight = this._overlay.style.getPropertyValue(
        "--subdialog-inner-height"
      );
      if (frameHeight) {
        frameHeight = parseFloat(frameHeight);
      } else {
        frameHeight = this._frame.clientHeight;
      }
      let boxMinHeight = parseFloat(
        this._window.getComputedStyle(this._box).minHeight
      );

      this._box.style.minHeight = boxMinHeight + resizeByHeight + "px";

      this._overlay.style.setProperty(
        "--subdialog-inner-height",
        frameHeight + resizeByHeight + "px"
      );

      oldResizeBy.call(
        this._frame.contentWindow,
        resizeByWidth,
        resizeByHeight
      );
    };

    // Defining resizeDialog on the contentWindow object to resize dialogs when prompted
    this._frame.contentWindow.resizeDialog = () => {
      return this.resizeDialog();
    };

    // Make window.close calls work like dialog closing.
    let oldClose = this._frame.contentWindow.close;
    this._frame.contentWindow.close = () => {
      var closingEvent = this._closingEvent;
      // If this._closingEvent is set, the dialog is closed externally
      // (dialog.js) and "dialogclosing" has already been dispatched.
      if (!closingEvent) {
        // If called without closing event, we need to create and dispatch it.
        // This is the case for any external close calls not going through
        // dialog.js.
        closingEvent = new CustomEvent("dialogclosing", {
          bubbles: true,
          detail: { button: null },
        });

        this._frame.contentWindow.dispatchEvent(closingEvent);
      } else if (this._closingEvent.detail?.abort) {
        // If the dialog is aborted (SubDialog#abort) we need to dispatch the
        // "dialogclosing" event ourselves.
        this._frame.contentWindow.dispatchEvent(closingEvent);
      }

      this.close(closingEvent);
      oldClose.call(this._frame.contentWindow);
    };

    // XXX: Hack to make focus during the dialog's load functions work. Make the element visible
    // sooner in DOMContentLoaded but mostly invisible instead of changing visibility just before
    // the dialog's load event.
    // Note that this needs to inherit so that hideDialog() works as expected.
    this._overlay.style.visibility = "inherit";
    this._overlay.style.opacity = "0.01";

    // Ensure the document gets an a11y role of dialog.
    const a11yDoc = contentDocument.body || contentDocument.documentElement;
    a11yDoc.setAttribute("role", "dialog");

    Services.obs.notifyObservers(this._frame.contentWindow, "subdialog-loaded");
  },

  async _onLoad(aEvent) {
    let target = aEvent.currentTarget;
    if (target.contentWindow.location == "about:blank") {
      return;
    }

    // In order to properly calculate the sizing of the subdialog, we need to
    // ensure that all of the l10n is done.
    if (target.contentDocument.l10n) {
      await target.contentDocument.l10n.ready;
    }

    // Some subdialogs may want to perform additional, asynchronous steps during initializations.
    //
    // In that case, we expect them to define a Promise which will delay measuring
    // until the promise is fulfilled.
    if (target.contentDocument.mozSubdialogReady) {
      await target.contentDocument.mozSubdialogReady;
    }

    await this.resizeDialog();
    this._resolveDialogReady();
  },

  async resizeDialog() {
    this.resizeHorizontally();
    this.resizeVertically();

    this._overlay.dispatchEvent(
      new CustomEvent("dialogopen", {
        bubbles: true,
        detail: { dialog: this },
      })
    );
    this._overlay.style.visibility = "inherit";
    this._overlay.style.opacity = ""; // XXX: focus hack continued from _onContentLoaded

    if (this._box.getAttribute("resizable") == "true") {
      this._onResize = this._onResize.bind(this);
      this._resizeObserver = new this._window.MutationObserver(this._onResize);
      this._resizeObserver.observe(this._box, { attributes: true });
    }

    this._trapFocus();

    this._resizeCallback?.({
      title: this._titleElement,
      frame: this._frame,
    });
  },

  resizeHorizontally() {
    // Do this on load to wait for the CSS to load and apply before calculating the size.
    let docEl = this._frame.contentDocument.documentElement;

    // These are deduced from styles which we don't change, so it's safe to get them now:
    let boxHorizontalBorder =
      2 * parseFloat(this._window.getComputedStyle(this._box).borderLeftWidth);
    let frameHorizontalMargin =
      2 * parseFloat(this._window.getComputedStyle(this._frame).marginLeft);

    // Then determine and set a bunch of width stuff:
    let { scrollWidth } = docEl.ownerDocument.body || docEl;
    // We need to convert em to px because an em value from the dialog window could
    // translate to something else in the host window, as font sizes may vary.
    let frameMinWidth =
      this._emToPx(docEl.style.minWidth) ||
      this._emToPx(docEl.style.width) ||
      scrollWidth + "px";
    let frameWidth = docEl.getAttribute("width")
      ? docEl.getAttribute("width") + "px"
      : scrollWidth + "px";
    if (
      this._box.getAttribute("sizeto") == "available" &&
      docEl.style.maxWidth
    ) {
      this._box.style.setProperty(
        "--box-max-width-requested",
        this._emToPx(docEl.style.maxWidth)
      );
    }

    if (this._box.getAttribute("sizeto") != "available") {
      this._frame.style.width = frameWidth;
      this._frame.style.minWidth = frameMinWidth;
    }

    let boxMinWidth = `calc(${
      boxHorizontalBorder + frameHorizontalMargin
    }px + ${frameMinWidth})`;

    // Temporary fix to allow parent chrome to collapse properly to min width.
    // See Bug 1658722.
    if (this._window.isChromeWindow) {
      boxMinWidth = `min(80vw, ${boxMinWidth})`;
    }
    this._box.style.minWidth = boxMinWidth;
  },

  resizeVertically() {
    let docEl = this._frame.contentDocument.documentElement;
    let getDocHeight = () => {
      let { scrollHeight } = docEl.ownerDocument.body || docEl;
      // We need to convert em to px because an em value from the dialog window could
      // translate to something else in the host window, as font sizes may vary.
      return this._emToPx(docEl.style.height) || scrollHeight + "px";
    };

    // If the title bar is disabled (not in the template),
    // set its height to 0 for the calculation.
    let titleBarHeight = 0;
    if (this._titleBar) {
      titleBarHeight =
        this._titleBar.clientHeight +
        parseFloat(
          this._window.getComputedStyle(this._titleBar).borderBottomWidth
        );
    }

    let boxVerticalBorder =
      2 * parseFloat(this._window.getComputedStyle(this._box).borderTopWidth);
    let frameVerticalMargin =
      2 * parseFloat(this._window.getComputedStyle(this._frame).marginTop);

    // The difference between the frame and box shouldn't change, either:
    let boxRect = this._box.getBoundingClientRect();
    let frameRect = this._frame.getBoundingClientRect();
    let frameSizeDifference =
      frameRect.top - boxRect.top + (boxRect.bottom - frameRect.bottom);

    let contentPane =
      this._frame.contentDocument.querySelector(".contentPane") ||
      this._frame.contentDocument.querySelector("dialog");

    let sizeTo = this._box.getAttribute("sizeto");
    if (["available", "limitheight"].includes(sizeTo)) {
      if (sizeTo == "limitheight") {
        this._overlay.style.setProperty("--doc-height-px", getDocHeight());
        contentPane?.classList.add("sizeDetermined");
      } else {
        if (docEl.style.maxHeight) {
          this._box.style.setProperty(
            "--box-max-height-requested",
            this._emToPx(docEl.style.maxHeight)
          );
        }
        // Inform the CSS of the toolbar height so the bottom padding can be
        // correctly calculated.
        this._box.style.setProperty("--box-top-px", `${boxRect.top}px`);
      }
      return;
    }

    // Now do the same but for the height. We need to do this afterwards because otherwise
    // XUL assumes we'll optimize for height and gives us "wrong" values which then are no
    // longer correct after we set the width:
    let frameMinHeight = getDocHeight();
    let frameHeight = docEl.getAttribute("height")
      ? docEl.getAttribute("height") + "px"
      : frameMinHeight;

    // Now check if the frame height we calculated is possible at this window size,
    // accounting for titlebar, padding/border and some spacing.
    let frameOverhead = frameSizeDifference + titleBarHeight;
    let maxHeight = this._window.innerHeight - frameOverhead;
    // Do this with a frame height in pixels...
    if (!frameHeight.endsWith("px")) {
      console.error(
        "This dialog (",
        this._frame.contentWindow.location.href,
        ") set a height in non-px-non-em units ('",
        frameHeight,
        "'), " +
          "which is likely to lead to bad sizing in in-content preferences. " +
          "Please consider changing this."
      );
    }

    if (
      parseFloat(frameMinHeight) > maxHeight ||
      parseFloat(frameHeight) > maxHeight
    ) {
      // If the height is bigger than that of the window, we should let the
      // contents scroll. The class is set on the "dialog" element, unless a
      // content pane exists, which is usually the case when the "window"
      // element is used to implement the subdialog instead.
      frameMinHeight = maxHeight + "px";
      // There also instances where the subdialog is neither implemented using
      // a content pane, nor a <dialog> (such as manageAddresses.xhtml)
      // so make sure to check that we actually got a contentPane before we
      // use it.
      contentPane?.classList.add("doScroll");
    }

    this._overlay.style.setProperty("--subdialog-inner-height", frameHeight);
    this._frame.style.height = `min(
      calc(100vh - ${frameOverhead}px),
      var(--subdialog-inner-height, ${frameHeight})
    )`;
    this._box.style.minHeight = `calc(
      ${boxVerticalBorder + titleBarHeight + frameVerticalMargin}px +
      ${frameMinHeight}
    )`;
  },

  /**
   * Helper for converting em to px because an em value from the dialog window could
   * translate to something else in the host window, as font sizes may vary.
   *
   * @param {String} val
   *                 A CSS length value.
   * @return {String} The converted CSS length value, or the original value if
   *                  no conversion took place.
   */
  _emToPx(val) {
    if (val && val.endsWith("em")) {
      let { fontSize } = this.frameContentWindow.getComputedStyle(
        this._frame.contentDocument.documentElement
      );
      return parseFloat(val) * parseFloat(fontSize) + "px";
    }
    return val;
  },

  _onResize(mutations) {
    let frame = this._frame;
    // The width and height styles are needed for the initial
    // layout of the frame, but afterward they need to be removed
    // or their presence will restrict the contents of the <browser>
    // from resizing to a smaller size.
    frame.style.removeProperty("width");
    frame.style.removeProperty("height");

    let docEl = frame.contentDocument.documentElement;
    let persistedAttributes = docEl.getAttribute("persist");
    if (
      !persistedAttributes ||
      (!persistedAttributes.includes("width") &&
        !persistedAttributes.includes("height"))
    ) {
      return;
    }

    for (let mutation of mutations) {
      if (mutation.attributeName == "width") {
        docEl.setAttribute("width", docEl.scrollWidth);
      } else if (mutation.attributeName == "height") {
        docEl.setAttribute("height", docEl.scrollHeight);
      }
    }
  },

  _onDialogClosing(aEvent) {
    this._frame.contentWindow.removeEventListener("dialogclosing", this);
    this._closingEvent = aEvent;
  },

  _onKeyDown(aEvent) {
    // Close on ESC key if target is SubDialog
    // If we're in the parent window, we need to check if the SubDialogs
    // frame is targeted, so we don't close the wrong dialog.
    if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE && !aEvent.defaultPrevented) {
      if (
        (this._window.isChromeWindow && aEvent.currentTarget == this._box) ||
        (!this._window.isChromeWindow && aEvent.currentTarget == this._window)
      ) {
        // Prevent ESC on SubDialog from cancelling page load (Bug 1665339).
        aEvent.preventDefault();
        this._frame.contentWindow.close();
        return;
      }
    }

    if (
      this._window.isChromeWindow ||
      aEvent.keyCode != aEvent.DOM_VK_TAB ||
      aEvent.ctrlKey ||
      aEvent.altKey ||
      aEvent.metaKey
    ) {
      return;
    }

    let fm = Services.focus;

    let isLastFocusableElement = el => {
      // XXXgijs unfortunately there is no way to get the last focusable element without asking
      // the focus manager to move focus to it.
      let rv =
        el ==
        fm.moveFocus(this._frame.contentWindow, null, fm.MOVEFOCUS_LAST, 0);
      fm.setFocus(el, 0);
      return rv;
    };

    let forward = !aEvent.shiftKey;
    // check if focus is leaving the frame (incl. the close button):
    if (
      (aEvent.target == this._closeButton && !forward) ||
      (isLastFocusableElement(aEvent.originalTarget) && forward)
    ) {
      aEvent.preventDefault();
      aEvent.stopImmediatePropagation();

      let parentWin = this._window.docShell.chromeEventHandler.ownerGlobal;
      if (forward) {
        fm.moveFocus(parentWin, null, fm.MOVEFOCUS_FIRST, fm.FLAG_BYKEY);
      } else {
        // Somehow, moving back 'past' the opening doc is not trivial. Cheat by doing it in 2 steps:
        fm.moveFocus(this._window, null, fm.MOVEFOCUS_ROOT, fm.FLAG_BYKEY);
        fm.moveFocus(parentWin, null, fm.MOVEFOCUS_BACKWARD, fm.FLAG_BYKEY);
      }
    }
  },

  _onParentWinFocus(aEvent) {
    // Explicitly check for the focus target of |window| to avoid triggering this when the window
    // is refocused
    if (
      this._closeButton &&
      aEvent.target != this._closeButton &&
      aEvent.target != this._window
    ) {
      this._closeButton.focus();
    }
  },

  /**
   * Setup dialog event listeners.
   * @param {Boolean} [includeLoad] - Whether to register load/unload listeners.
   */
  _addDialogEventListeners(includeLoad = true) {
    if (this._window.isChromeWindow) {
      // Only register an event listener if we have a title to show.
      if (this._titleBar) {
        this._frame.addEventListener("DOMTitleChanged", this, true);
      }

      if (includeLoad) {
        this._window.addEventListener("unload", this, true);
      }
    } else {
      let chromeBrowser = this._window.docShell.chromeEventHandler;

      if (includeLoad) {
        // For content windows we listen for unload of the browser
        chromeBrowser.addEventListener("unload", this, true);
      }

      if (this._titleBar) {
        chromeBrowser.addEventListener("DOMTitleChanged", this, true);
      }
    }

    // Make the close button work.
    this._closeButton?.addEventListener("command", this);

    if (includeLoad) {
      // DOMFrameContentLoaded only fires on the top window
      this._window.addEventListener("DOMFrameContentLoaded", this, true);

      // Wait for the stylesheets injected during DOMContentLoaded to load before showing the dialog
      // otherwise there is a flicker of the stylesheet applying.
      this._frame.addEventListener("load", this, true);
    }

    // Ensure we get <esc> keypresses even if nothing in the subdialog is focusable
    // (happens on OS X when only text inputs and lists are focusable, and
    //  the subdialog only has checkboxes/radiobuttons/buttons)
    if (!this._window.isChromeWindow) {
      this._window.addEventListener("keydown", this, true);
    }

    this._overlay.addEventListener("click", this, true);
  },

  /**
   * Remove dialog event listeners.
   * @param {Boolean} [includeLoad] - Whether to remove load/unload listeners.
   */
  _removeDialogEventListeners(includeLoad = true) {
    if (this._window.isChromeWindow) {
      this._frame.removeEventListener("DOMTitleChanged", this, true);

      if (includeLoad) {
        this._window.removeEventListener("unload", this, true);
      }
    } else {
      let chromeBrowser = this._window.docShell.chromeEventHandler;
      if (includeLoad) {
        chromeBrowser.removeEventListener("unload", this, true);
      }

      chromeBrowser.removeEventListener("DOMTitleChanged", this, true);
    }

    this._closeButton?.removeEventListener("command", this);

    if (includeLoad) {
      this._window.removeEventListener("DOMFrameContentLoaded", this, true);
      this._frame.removeEventListener("load", this, true);
      this._frame.contentWindow.removeEventListener("dialogclosing", this);
    }

    this._window.removeEventListener("keydown", this, true);

    this._overlay.removeEventListener("click", this, true);

    if (this._resizeObserver) {
      this._resizeObserver.disconnect();
      this._resizeObserver = null;
    }

    this._untrapFocus();
  },

  /**
   * Focus the dialog content.
   * If the embedded document defines a custom focus handler it will be called.
   * Otherwise we will focus the first focusable element in the content window.
   * @param {boolean} [isInitialFocus] - Whether the dialog is focused for the
   * first time after opening.
   */
  focus(isInitialFocus = false) {
    // If the content window has its own focus logic, hand off the focus call.
    let focusHandler = this._frame?.contentDocument?.subDialogSetDefaultFocus;
    if (focusHandler) {
      focusHandler(isInitialFocus);
      return;
    }
    // Handle focus ourselves. Try to move the focus to the first element in
    // the content window.
    let fm = Services.focus;

    // We're intentionally hiding the focus ring here for now per bug 1704882,
    // but we aim to have a better fix that retains the focus ring for users
    // that had brought up the dialog by keyboard in bug 1708261.
    let focusedElement = fm.moveFocus(
      this._frame.contentWindow,
      null,
      fm.MOVEFOCUS_FIRST,
      fm.FLAG_NOSHOWRING
    );
    if (!focusedElement) {
      // Ensure the focus is pulled out of the content document even if there's
      // nothing focusable in the dialog.
      this._frame.focus();
    }
  },

  _trapFocus() {
    // Attach a system event listener so the dialog can cancel keydown events.
    // See Bug 1669990.
    this._box.addEventListener("keydown", this, { mozSystemGroup: true });
    this._closeButton?.addEventListener("keydown", this);

    if (!this._window.isChromeWindow) {
      this._window.addEventListener("focus", this, true);
    }
  },

  _untrapFocus() {
    this._box.removeEventListener("keydown", this, { mozSystemGroup: true });
    this._closeButton?.removeEventListener("keydown", this);
    this._window.removeEventListener("focus", this, true);
  },
};

/**
 * Manages multiple SubDialogs in a dialog stack element.
 */
export class SubDialogManager {
  /**
   * @param {Object} options - Dialog manager options.
   * @param {DOMNode} options.dialogStack - Container element for all dialogs
   * this instance manages.
   * @param {DOMNode} options.dialogTemplate - Element to use as template for
   * constructing new dialogs.
   * @param {Number} [options.orderType] - Whether dialogs should be ordered as
   * a stack or a queue.
   * @param {Boolean} [options.allowDuplicateDialogs] - Whether to allow opening
   * duplicate dialogs (same URI) at the same time. If disabled, opening a
   * dialog with the same URI as an existing dialog will be a no-op.
   * @param {Object} options.dialogOptions - Options passed to every
   * SubDialog instance.
   * @see {@link SubDialog} for a list of dialog options.
   */
  constructor({
    dialogStack,
    dialogTemplate,
    orderType = SubDialogManager.ORDER_STACK,
    allowDuplicateDialogs = false,
    dialogOptions,
  }) {
    /**
     * New dialogs are pushed to the end of the _dialogs array.
     * Depending on the orderType either the last element (stack) or the first
     * element (queue) in the array will be the top and visible.
     * @type {SubDialog[]}
     */
    this._dialogs = [];
    this._dialogStack = dialogStack;
    this._dialogTemplate = dialogTemplate;
    this._topLevelPrevActiveElement = null;
    this._orderType = orderType;
    this._allowDuplicateDialogs = allowDuplicateDialogs;
    this._dialogOptions = dialogOptions;

    this._preloadDialog = new SubDialog({
      template: this._dialogTemplate,
      parentElement: this._dialogStack,
      id: SubDialogManager._nextDialogID++,
      dialogOptions: this._dialogOptions,
    });
  }

  /**
   * Get the dialog which is currently on top. This depends on whether the
   * dialogs are in a stack or a queue.
   */
  get _topDialog() {
    if (!this._dialogs.length) {
      return undefined;
    }
    if (this._orderType === SubDialogManager.ORDER_STACK) {
      return this._dialogs[this._dialogs.length - 1];
    }
    return this._dialogs[0];
  }

  open(
    aURL,
    {
      features,
      closingCallback,
      closedCallback,
      allowDuplicateDialogs,
      sizeTo,
      hideContent,
    } = {},
    ...aParams
  ) {
    let allowDuplicates =
      allowDuplicateDialogs != null
        ? allowDuplicateDialogs
        : this._allowDuplicateDialogs;
    // If we're already open/opening on this URL, do nothing.
    if (
      !allowDuplicates &&
      this._dialogs.some(dialog => dialog._openedURL == aURL)
    ) {
      return undefined;
    }

    let doc = this._dialogStack.ownerDocument;

    // For dialog stacks, remember the last active element before opening the
    // next dialog. This allows us to restore focus on dialog close.
    if (
      this._orderType === SubDialogManager.ORDER_STACK &&
      this._dialogs.length
    ) {
      this._topDialog._prevActiveElement = doc.activeElement;
    }

    if (!this._dialogs.length) {
      // When opening the first dialog, show the dialog stack.
      this._dialogStack.hidden = false;
      this._dialogStack.classList.remove("temporarilyHidden");
      this._topLevelPrevActiveElement = doc.activeElement;
    }

    // Consumers may pass this flag to make the dialog overlay background opaque,
    // effectively hiding the content behind it. For example,
    // this is used by the prompt code to prevent certain http authentication spoofing scenarios.
    if (hideContent) {
      this._preloadDialog._overlay.setAttribute("hideContent", true);
    }
    this._dialogs.push(this._preloadDialog);
    this._preloadDialog.open(
      aURL,
      {
        features,
        closingCallback,
        closedCallback,
        sizeTo,
      },
      ...aParams
    );

    let openedDialog = this._preloadDialog;

    this._preloadDialog = new SubDialog({
      template: this._dialogTemplate,
      parentElement: this._dialogStack,
      id: SubDialogManager._nextDialogID++,
      dialogOptions: this._dialogOptions,
    });

    if (this._dialogs.length == 1) {
      this._ensureStackEventListeners();
    }

    return openedDialog;
  }

  close() {
    this._topDialog.close();
  }

  /**
   * Hides the dialog stack for a specific browser, without actually destroying
   * frames for stuff within it.
   *
   * @param aBrowser - The browser associated with the tab dialog.
   */
  hideDialog(aBrowser) {
    aBrowser.removeAttribute("tabDialogShowing");
    this._dialogStack.classList.add("temporarilyHidden");
  }

  /**
   * Abort open dialogs.
   * @param {function} [filterFn] - Function which should return true for
   * dialogs that should be aborted and false for dialogs that should remain
   * open. Defaults to aborting all dialogs.
   */
  abortDialogs(filterFn = () => true) {
    this._dialogs.filter(filterFn).forEach(dialog => dialog.abort());
  }

  get hasDialogs() {
    if (!this._dialogs.length) {
      return false;
    }
    return this._dialogs.some(dialog => !dialog._isClosing);
  }

  get dialogs() {
    return [...this._dialogs];
  }

  focusTopDialog() {
    this._topDialog?.focus();
  }

  handleEvent(aEvent) {
    switch (aEvent.type) {
      case "dialogopen": {
        this._onDialogOpen(aEvent.detail.dialog);
        break;
      }
      case "dialogclose": {
        this._onDialogClose(aEvent.detail.dialog);
        break;
      }
    }
  }

  _onDialogOpen(dialog) {
    let lowerDialogs = [];
    if (dialog == this._topDialog) {
      dialog.focus(true);
    } else {
      // Opening dialog is not on top, hide it
      lowerDialogs.push(dialog);
    }

    // For stack order, hide the previous top
    if (
      this._dialogs.length &&
      this._orderType === SubDialogManager.ORDER_STACK
    ) {
      let index = this._dialogs.indexOf(dialog);
      if (index > 0) {
        lowerDialogs.push(this._dialogs[index - 1]);
      }
    }

    lowerDialogs.forEach(d => {
      if (d._overlay.hasAttribute("topmost")) {
        d._overlay.removeAttribute("topmost");
        d._removeDialogEventListeners(false);
      }
    });
  }

  _onDialogClose(dialog) {
    this._dialogs.splice(this._dialogs.indexOf(dialog), 1);

    if (this._topDialog) {
      // The prevActiveElement is only set for stacked dialogs
      if (this._topDialog._prevActiveElement) {
        this._topDialog._prevActiveElement.focus();
      } else {
        this._topDialog.focus(true);
      }
      this._topDialog._overlay.setAttribute("topmost", true);
      this._topDialog._addDialogEventListeners(false);
      this._dialogStack.hidden = false;
      this._dialogStack.classList.remove("temporarilyHidden");
    } else {
      // We have closed the last dialog, do cleanup.
      this._topLevelPrevActiveElement.focus();
      this._dialogStack.hidden = true;
      this._removeStackEventListeners();
    }
  }

  _ensureStackEventListeners() {
    this._dialogStack.addEventListener("dialogopen", this);
    this._dialogStack.addEventListener("dialogclose", this);
  }

  _removeStackEventListeners() {
    this._dialogStack.removeEventListener("dialogopen", this);
    this._dialogStack.removeEventListener("dialogclose", this);
  }
}

// Used for the SubDialogManager orderType option.
SubDialogManager.ORDER_STACK = 0;
SubDialogManager.ORDER_QUEUE = 1;

SubDialogManager._nextDialogID = 0;
PK
!<V1�ggmodules/Subprocess.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * These modules are loosely based on the subprocess.jsm module created
 * by Jan Gerber and Patrick Brunschwig, though the implementation
 * differs drastically.
 */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { SubprocessConstants } from "resource://gre/modules/subprocess/subprocess_common.sys.mjs";

const lazy = {};

if (AppConstants.platform == "win") {
  ChromeUtils.defineESModuleGetters(lazy, {
    SubprocessImpl: "resource://gre/modules/subprocess/subprocess_win.sys.mjs",
  });
} else {
  ChromeUtils.defineESModuleGetters(lazy, {
    SubprocessImpl: "resource://gre/modules/subprocess/subprocess_unix.sys.mjs",
  });
}

function encodeEnvVar(name, value) {
  if (typeof name === "string" && typeof value === "string") {
    return `${name}=${value}`;
  }

  let encoder = new TextEncoder();
  function encode(val) {
    return typeof val === "string" ? encoder.encode(val) : val;
  }

  return Uint8Array.of(...encode(name), ...encode("="), ...encode(value), 0);
}

function platformSupportsDisclaimedSpawn() {
  return AppConstants.isPlatformAndVersionAtLeast("macosx", 18);
}

/**
 * Allows for creation of and communication with OS-level sub-processes.
 *
 * @namespace
 */
export var Subprocess = {
  /**
   * Launches a process, and returns a handle to it.
   *
   * @param {object} options
   * An object describing the process to launch.
   *
   * @param {string} options.command
   * The full path of the executable to launch. Relative paths are not
   * accepted, and `$PATH` is not searched.
   *
   * If a path search is necessary, the {@link Subprocess.pathSearch} method may
   * be used to map a bare executable name to a full path.
   *
   * @param {string[]} [options.arguments]
   * A list of strings to pass as arguments to the process.
   *
   * @param {object} [options.environment] An object containing a key
   * and value for each environment variable to pass to the
   * process. Values that are `=== null` are ignored. Only the
   * object's own, enumerable properties are added to the environment.
   *
   * @param {boolean} [options.environmentAppend] If true, append the
   * environment variables passed in `environment` to the existing set
   * of environment variables. Values that are `=== null` are removed
   * from the environment. Otherwise, the values in 'environment'
   * constitute the entire set of environment variables passed to the
   * new process.
   *
   * @param {string} [options.stderr]
   * Defines how the process's stderr output is handled. One of:
   *
   * - `"ignore"`: (default) The process's standard error is not redirected.
   * - `"stdout"`: The process's stderr is merged with its stdout.
   * - `"pipe"`: The process's stderr is redirected to a pipe, which can be read
   *   from via its `stderr` property.
   *
   * @param {string} [options.workdir]
   *        The working directory in which to launch the new process.
   *
   * @param {boolean} [options.disclaim]
   * macOS-specific option for 10.14+ OS versions. If true, enables a
   * macOS-specific process launch option allowing the parent process to
   * disclaim responsibility for the child process with respect to privacy/
   * security permission prompts and decisions. This option is ignored on
   * platforms that do not support it.
   *
   * @returns {Promise<Process>}
   *
   * @throws {Error}
   * May be rejected with an Error object if the process can not be
   * launched. The object will include an `errorCode` property with
   * one of the following values if it was rejected for the
   * corresponding reason:
   *
   * - Subprocess.ERROR_BAD_EXECUTABLE: The given command could not
   *   be found, or the file that it references is not executable.
   *
   * Note that if the process is successfully launched, but exits with
   * a non-zero exit code, the promise will still resolve successfully.
   */
  call(options) {
    options = Object.assign({}, options);

    options.stderr = options.stderr || "ignore";
    options.workdir = options.workdir || null;
    options.disclaim = options.disclaim || false;

    let environment = {};
    if (!options.environment || options.environmentAppend) {
      environment = this.getEnvironment();
    }

    if (options.environment) {
      Object.assign(environment, options.environment);
    }

    options.environment = Object.entries(environment)
      .map(([key, val]) => (val !== null ? encodeEnvVar(key, val) : null))
      .filter(s => s);

    options.arguments = Array.from(options.arguments || []);

    if (options.disclaim && !platformSupportsDisclaimedSpawn()) {
      options.disclaim = false;
    }

    return Promise.resolve(
      lazy.SubprocessImpl.isExecutableFile(options.command)
    ).then(isExecutable => {
      if (!isExecutable) {
        let error = new Error(
          `File at path "${options.command}" does not exist, or is not executable`
        );
        error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
        throw error;
      }

      options.arguments.unshift(options.command);

      return lazy.SubprocessImpl.call(options);
    });
  },

  /**
   * Returns an object with a key-value pair for every variable in the process's
   * current environment.
   *
   * @returns {object}
   */
  getEnvironment() {
    let environment = Object.create(null);
    for (let [k, v] of lazy.SubprocessImpl.getEnvironment()) {
      environment[k] = v;
    }
    return environment;
  },

  /**
   * Searches for the given executable file in the system executable
   * file paths as specified by the PATH environment variable.
   *
   * On Windows, if the unadorned filename cannot be found, the
   * extensions in the semicolon-separated list in the PATHSEP
   * environment variable are successively appended to the original
   * name and searched for in turn.
   *
   * @param {string} command
   *        The name of the executable to find.
   * @param {object} [environment]
   *        An object containing a key for each environment variable to be used
   *        in the search. If not provided, full the current process environment
   *        is used.
   * @returns {Promise<string>}
   */
  pathSearch(command, environment = this.getEnvironment()) {
    // Promise.resolve lets us get around returning one of the Promise.jsm
    // pseudo-promises returned by Task.jsm.
    let path = lazy.SubprocessImpl.pathSearch(command, environment);
    return Promise.resolve(path);
  },

  /**
   * Connect to an already-running subprocess
   * given the file descriptors for its stdin, stdout and stderr.
   *
   * @param {int[]} [fds]
   *        A list of three file descriptors [stdin, stdout, stderr].
   *
   * @returns {Process}
   */
  connectRunning(fds) {
    return lazy.SubprocessImpl.connectRunning(fds);
  },
};

Object.assign(Subprocess, SubprocessConstants);
Object.freeze(Subprocess);

export function getSubprocessImplForTest() {
  return lazy.SubprocessImpl;
}
PK
!<���b�b%modules/SyncedBookmarksMirror.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file implements a mirror and two-way merger for synced bookmarks. The
 * mirror matches the complete tree stored on the Sync server, and stages new
 * bookmarks changed on the server since the last sync. The merger walks the
 * local tree in Places and the mirrored remote tree, produces a new merged
 * tree, then updates the local tree to reflect the merged tree.
 *
 * Let's start with an overview of the different classes, and how they fit
 * together.
 *
 * - `SyncedBookmarksMirror` sets up the database, validates and upserts new
 *   incoming records, attaches to Places, and applies the changed records.
 *   During application, we fetch the local and remote bookmark trees, merge
 *   them, and update Places to match. Merging and application happen in a
 *   single transaction, so applying the merged tree won't collide with local
 *   changes. A failure at this point aborts the merge and leaves Places
 *   unchanged.
 *
 * - A `BookmarkTree` is a fully rooted tree that also notes deletions. A
 *   `BookmarkNode` represents a local item in Places, or a remote item in the
 *   mirror.
 *
 * - A `MergedBookmarkNode` holds a local node, a remote node, and a
 *   `MergeState` that indicates which node to prefer when updating Places and
 *   the server to match the merged tree.
 *
 * - `BookmarkObserverRecorder` records all changes made to Places during the
 *   merge, then dispatches `PlacesObservers` notifications. Places uses
 *   these notifications to update the UI and internal caches. We can't dispatch
 *   during the merge because observers won't see the changes until the merge
 *   transaction commits and the database is consistent again.
 *
 * - After application, we flag all applied incoming items as merged, create
 *   Sync records for the locally new and updated items in Places, and upload
 *   the records to the server. At this point, all outgoing items are flagged as
 *   changed in Places, so the next sync can resume cleanly if the upload is
 *   interrupted or fails.
 *
 * - Once upload succeeds, we update the mirror with the uploaded records, so
 *   that the mirror matches the server again. An interruption or error here
 *   will leave the uploaded items flagged as changed in Places, so we'll merge
 *   them again on the next sync. This is redundant work, but shouldn't cause
 *   issues.
 */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Async: "resource://services-common/async.sys.mjs",
  Log: "resource://gre/modules/Log.sys.mjs",
  PlacesSyncUtils: "resource://gre/modules/PlacesSyncUtils.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "MirrorLog", () =>
  lazy.Log.repository.getLogger("Sync.Engine.Bookmarks.Mirror")
);

const SyncedBookmarksMerger = Components.Constructor(
  "@mozilla.org/browser/synced-bookmarks-merger;1",
  "mozISyncedBookmarksMerger"
);

// These can be removed once they're exposed in a central location (bug
// 1375896).
const DB_URL_LENGTH_MAX = 65536;
const DB_TITLE_LENGTH_MAX = 4096;

// The current mirror database schema version. Bump for migrations, then add
// migration code to `migrateMirrorSchema`.
const MIRROR_SCHEMA_VERSION = 9;

// Use a shared jankYielder in these functions
ChromeUtils.defineLazyGetter(lazy, "yieldState", () => lazy.Async.yieldState());

/** Adapts a `Log.sys.mjs` logger to a `mozIServicesLogSink`. */
class LogAdapter {
  constructor(log) {
    this.log = log;
  }

  get maxLevel() {
    let level = this.log.level;
    if (level <= lazy.Log.Level.All) {
      return Ci.mozIServicesLogSink.LEVEL_TRACE;
    }
    if (level <= lazy.Log.Level.Info) {
      return Ci.mozIServicesLogSink.LEVEL_DEBUG;
    }
    if (level <= lazy.Log.Level.Warn) {
      return Ci.mozIServicesLogSink.LEVEL_WARN;
    }
    if (level <= lazy.Log.Level.Error) {
      return Ci.mozIServicesLogSink.LEVEL_ERROR;
    }
    return Ci.mozIServicesLogSink.LEVEL_OFF;
  }

  trace(message) {
    this.log.trace(message);
  }

  debug(message) {
    this.log.debug(message);
  }

  warn(message) {
    this.log.warn(message);
  }

  error(message) {
    this.log.error(message);
  }
}

/**
 * A helper to track the progress of a merge for telemetry and shutdown hang
 * reporting.
 */
class ProgressTracker {
  constructor(recordStepTelemetry) {
    this.recordStepTelemetry = recordStepTelemetry;
    this.steps = [];
  }

  /**
   * Records a merge step, updating the shutdown blocker state.
   *
   * @param {String} name A step name from `ProgressTracker.STEPS`. This is
   *        included in shutdown hang crash reports, along with the timestamp
   *        the step was recorded.
   * @param {Number} [took] The time taken, in milliseconds.
   * @param {Array} [counts] An array of additional counts to report in the
   *        shutdown blocker state.
   */
  step(name, took = -1, counts = null) {
    let info = { step: name, at: Date.now() };
    if (took > -1) {
      info.took = took;
    }
    if (counts) {
      info.counts = counts;
    }
    this.steps.push(info);
  }

  /**
   * Records a merge step with timings and counts for telemetry.
   *
   * @param {String} name The step name.
   * @param {Number} took The time taken, in milliseconds.
   * @param {Array} [counts] An array of additional `{ name, count }` tuples to
   *        record in telemetry for this step.
   */
  stepWithTelemetry(name, took, counts = null) {
    this.step(name, took, counts);
    this.recordStepTelemetry(name, took, counts);
  }

  /**
   * Records a merge step with the time taken and item count.
   *
   * @param {String} name The step name.
   * @param {Number} took The time taken, in milliseconds.
   * @param {Number} count The number of items handled in this step.
   */
  stepWithItemCount(name, took, count) {
    this.stepWithTelemetry(name, took, [{ name: "items", count }]);
  }

  /**
   * Clears all recorded merge steps.
   */
  reset() {
    this.steps = [];
  }

  /**
   * Returns the shutdown blocker state. This is included in shutdown hang
   * crash reports, in the `AsyncShutdownTimeout` annotation.
   *
   * @see    `fetchState` in `AsyncShutdown` for more details.
   * @return {Object} A stringifiable object with the recorded steps.
   */
  fetchState() {
    return { steps: this.steps };
  }
}

/** Merge steps for which we record progress. */
ProgressTracker.STEPS = {
  FETCH_LOCAL_TREE: "fetchLocalTree",
  FETCH_REMOTE_TREE: "fetchRemoteTree",
  MERGE: "merge",
  APPLY: "apply",
  NOTIFY_OBSERVERS: "notifyObservers",
  FETCH_LOCAL_CHANGE_RECORDS: "fetchLocalChangeRecords",
  FINALIZE: "finalize",
};

/**
 * A mirror maintains a copy of the complete tree as stored on the Sync server.
 * It is persistent.
 *
 * The mirror schema is a hybrid of how Sync and Places represent bookmarks.
 * The `items` table contains item attributes (title, kind, URL, etc.), while
 * the `structure` table stores parent-child relationships and position.
 * This is similar to how iOS encodes "value" and "structure" state,
 * though we handle these differently when merging. See `BookmarkMerger` for
 * details.
 *
 * There's no guarantee that the remote state is consistent. We might be missing
 * parents or children, or a bookmark and its parent might disagree about where
 * it belongs. This means we need a strategy to handle missing parents and
 * children.
 *
 * We treat the `children` of the last parent we see as canonical, and ignore
 * the child's `parentid` entirely. We also ignore missing children, and
 * temporarily reparent bookmarks with missing parents to "unfiled". When we
 * eventually see the missing items, either during a later sync or as part of
 * repair, we'll fill in the mirror's gaps and fix up the local tree.
 *
 * During merging, we won't intentionally try to fix inconsistencies on the
 * server, and opt to build as complete a tree as we can from the remote state,
 * even if we diverge from what's in the mirror. See bug 1433512 for context.
 *
 * If a sync is interrupted, we resume downloading from the server collection
 * last modified time, or the server last modified time of the most recent
 * record if newer. New incoming records always replace existing records in the
 * mirror.
 *
 * We delete the mirror database on client reset, including when the sync ID
 * changes on the server, and when the user is node reassigned, disables the
 * bookmarks engine, or signs out.
 */
export class SyncedBookmarksMirror {
  constructor(
    db,
    wasCorrupt = false,
    {
      recordStepTelemetry,
      recordValidationTelemetry,
      finalizeAt = lazy.PlacesUtils.history.shutdownClient.jsclient,
    } = {}
  ) {
    this.db = db;
    this.wasCorrupt = wasCorrupt;
    this.recordValidationTelemetry = recordValidationTelemetry;

    this.merger = new SyncedBookmarksMerger();
    this.merger.db = db.unsafeRawConnection.QueryInterface(
      Ci.mozIStorageConnection
    );
    this.merger.logger = new LogAdapter(lazy.MirrorLog);

    // Automatically close the database connection on shutdown. `progress`
    // tracks state for shutdown hang reporting.
    this.progress = new ProgressTracker(recordStepTelemetry);
    this.finalizeController = new AbortController();
    this.finalizeAt = finalizeAt;
    this.finalizeBound = () => this.finalize({ alsoCleanup: false });
    this.finalizeAt.addBlocker(
      "SyncedBookmarksMirror: finalize",
      this.finalizeBound,
      { fetchState: () => this.progress }
    );
  }

  /**
   * Sets up the mirror database connection and upgrades the mirror to the
   * newest schema version. Automatically recreates the mirror if it's corrupt;
   * throws on failure.
   *
   * @param  {String} options.path
   *         The path to the mirror database file, either absolute or relative
   *         to the profile path.
   * @param  {Function} options.recordStepTelemetry
   *         A function with the signature `(name: String, took: Number,
   *         counts: Array?)`, where `name` is the name of the merge step,
   *         `took` is the time taken in milliseconds, and `counts` is an
   *         array of named counts (`{ name, count }` tuples) with additional
   *         counts for the step to record in the telemetry ping.
   * @param  {Function} options.recordValidationTelemetry
   *         A function with the signature `(took: Number, checked: Number,
   *         problems: Array)`, where `took` is the time taken to run
   *         validation in milliseconds, `checked` is the number of items
   *         checked, and `problems` is an array of named problem counts.
   * @param  {AsyncShutdown.Barrier} [options.finalizeAt]
   *         A shutdown phase, barrier, or barrier client that should
   *         automatically finalize the mirror when triggered. Exposed for
   *         testing.
   * @return {SyncedBookmarksMirror}
   *         A mirror ready for use.
   */
  static async open(options) {
    let db = await lazy.PlacesUtils.promiseUnsafeWritableDBConnection();
    if (!db) {
      throw new TypeError("Can't open mirror without Places connection");
    }
    let path;
    if (PathUtils.isAbsolute(options.path)) {
      path = options.path;
    } else {
      path = PathUtils.join(PathUtils.profileDir, options.path);
    }
    let wasCorrupt = false;
    try {
      await attachAndInitMirrorDatabase(db, path);
    } catch (ex) {
      if (isDatabaseCorrupt(ex)) {
        lazy.MirrorLog.warn(
          "Error attaching mirror to Places; removing and " +
            "recreating mirror",
          ex
        );
        wasCorrupt = true;
        await IOUtils.remove(path);
        await attachAndInitMirrorDatabase(db, path);
      } else {
        lazy.MirrorLog.error(
          "Unrecoverable error attaching mirror to Places",
          ex
        );
        throw ex;
      }
    }
    return new SyncedBookmarksMirror(db, wasCorrupt, options);
  }

  /**
   * Returns the newer of the bookmarks collection last modified time, or the
   * server modified time of the newest record. The bookmarks engine uses this
   * timestamp as the "high water mark" for all downloaded records. Each sync
   * downloads and stores records that are strictly newer than this time.
   *
   * @return {Number}
   *         The high water mark time, in seconds.
   */
  async getCollectionHighWaterMark() {
    // The first case, where we have records with server modified times newer
    // than the collection last modified time, occurs when a sync is interrupted
    // before we call `setCollectionLastModified`. We subtract one second, the
    // maximum time precision guaranteed by the server, so that we don't miss
    // other records with the same time as the newest one we downloaded.
    let rows = await this.db.executeCached(
      `
      SELECT MAX(
        IFNULL((SELECT MAX(serverModified) - 1000 FROM items), 0),
        IFNULL((SELECT CAST(value AS INTEGER) FROM meta
                WHERE key = :modifiedKey), 0)
      ) AS highWaterMark`,
      { modifiedKey: SyncedBookmarksMirror.META_KEY.LAST_MODIFIED }
    );
    let highWaterMark = rows[0].getResultByName("highWaterMark");
    return highWaterMark / 1000;
  }

  /**
   * Updates the bookmarks collection last modified time. Note that this may
   * be newer than the modified time of the most recent record.
   *
   * @param {Number|String} lastModifiedSeconds
   *        The collection last modified time, in seconds.
   */
  async setCollectionLastModified(lastModifiedSeconds) {
    let lastModified = Math.floor(lastModifiedSeconds * 1000);
    if (!Number.isInteger(lastModified)) {
      throw new TypeError("Invalid collection last modified time");
    }
    await this.db.executeBeforeShutdown(
      "SyncedBookmarksMirror: setCollectionLastModified",
      db =>
        db.executeCached(
          `
        REPLACE INTO meta(key, value)
        VALUES(:modifiedKey, :lastModified)`,
          {
            modifiedKey: SyncedBookmarksMirror.META_KEY.LAST_MODIFIED,
            lastModified,
          }
        )
    );
  }

  /**
   * Returns the bookmarks collection sync ID. This corresponds to
   * `PlacesSyncUtils.bookmarks.getSyncId`.
   *
   * @return {String}
   *         The sync ID, or `""` if one isn't set.
   */
  async getSyncId() {
    let rows = await this.db.executeCached(
      `
      SELECT value FROM meta WHERE key = :syncIdKey`,
      { syncIdKey: SyncedBookmarksMirror.META_KEY.SYNC_ID }
    );
    return rows.length ? rows[0].getResultByName("value") : "";
  }

  /**
   * Ensures that the sync ID in the mirror is up-to-date with the server and
   * Places, and discards the mirror on mismatch.
   *
   * The bookmarks engine store the same sync ID in Places and the mirror to
   * "tie" the two together. This allows Sync to do the right thing if the
   * database files are copied between profiles connected to different accounts.
   *
   * See `PlacesSyncUtils.bookmarks.ensureCurrentSyncId` for an explanation of
   * how Places handles sync ID mismatches.
   *
   * @param {String} newSyncId
   *        The server's sync ID.
   */
  async ensureCurrentSyncId(newSyncId) {
    if (!newSyncId || typeof newSyncId != "string") {
      throw new TypeError("Invalid new bookmarks sync ID");
    }
    let existingSyncId = await this.getSyncId();
    if (existingSyncId == newSyncId) {
      lazy.MirrorLog.trace("Sync ID up-to-date in mirror", { existingSyncId });
      return;
    }
    lazy.MirrorLog.info(
      "Sync ID changed from ${existingSyncId} to " +
        "${newSyncId}; resetting mirror",
      { existingSyncId, newSyncId }
    );
    await this.db.executeBeforeShutdown(
      "SyncedBookmarksMirror: ensureCurrentSyncId",
      db =>
        db.executeTransaction(async function () {
          await resetMirror(db);
          await db.execute(
            `
          REPLACE INTO meta(key, value)
          VALUES(:syncIdKey, :newSyncId)`,
            { syncIdKey: SyncedBookmarksMirror.META_KEY.SYNC_ID, newSyncId }
          );
        })
    );
  }

  /**
   * Stores incoming or uploaded Sync records in the mirror. Rejects if any
   * records are invalid.
   *
   * @param {PlacesItem[]} records
   *        Sync records to store in the mirror.
   * @param {Boolean} [options.needsMerge]
   *        Indicates if the records were changed remotely since the last sync,
   *        and should be merged into the local tree. This option is set to
   *        `true` for incoming records, and `false` for successfully uploaded
   *        records. Tests can also pass `false` to set up an existing mirror.
   * @param {AbortSignal} [options.signal]
   *        An abort signal that can be used to interrupt the operation. If
   *        omitted, storing incoming items can still be interrupted when the
   *        mirror is finalized.
   */
  async store(records, { needsMerge = true, signal = null } = {}) {
    let options = {
      needsMerge,
      signal: anyAborted(this.finalizeController.signal, signal),
    };
    await this.db.executeBeforeShutdown("SyncedBookmarksMirror: store", db =>
      db.executeTransaction(async () => {
        for (let record of records) {
          if (options.signal.aborted) {
            throw new SyncedBookmarksMirror.InterruptedError(
              "Interrupted while storing incoming items"
            );
          }
          let guid = lazy.PlacesSyncUtils.bookmarks.recordIdToGuid(record.id);
          if (guid == lazy.PlacesUtils.bookmarks.rootGuid) {
            // The engine should hard DELETE Places roots from the server.
            throw new TypeError("Can't store Places root");
          }
          if (lazy.MirrorLog.level <= lazy.Log.Level.Trace) {
            lazy.MirrorLog.trace(
              `Storing in mirror: ${record.cleartextToString()}`
            );
          }
          switch (record.type) {
            case "bookmark":
              await this.storeRemoteBookmark(record, options);
              continue;

            case "query":
              await this.storeRemoteQuery(record, options);
              continue;

            case "folder":
              await this.storeRemoteFolder(record, options);
              continue;

            case "livemark":
              await this.storeRemoteLivemark(record, options);
              continue;

            case "separator":
              await this.storeRemoteSeparator(record, options);
              continue;

            default:
              if (record.deleted) {
                await this.storeRemoteTombstone(record, options);
                continue;
              }
          }
          lazy.MirrorLog.warn("Ignoring record with unknown type", record.type);
        }
      })
    );
  }

  /**
   * Builds a complete merged tree from the local and remote trees, resolves
   * value and structure conflicts, dedupes local items, applies the merged
   * tree back to Places, and notifies observers about the changes.
   *
   * Merging and application happen in a transaction, meaning code that uses the
   * main Places connection, including the UI, will fail to write to the
   * database until the transaction commits. Asynchronous consumers will retry
   * on `SQLITE_BUSY`; synchronous consumers will fail after waiting for 100ms.
   * See bug 1305563, comment 122 for details.
   *
   * @param  {Number} [options.localTimeSeconds]
   *         The current local time, in seconds.
   * @param  {Number} [options.remoteTimeSeconds]
   *         The current server time, in seconds.
   * @param  {Boolean} [options.notifyInStableOrder]
   *         If `true`, fire observer notifications for items in the same folder
   *         in a stable order. This is disabled by default, to avoid the cost
   *         of sorting the notifications, but enabled in some tests to simplify
   *         their checks.
   * @param  {AbortSignal} [options.signal]
   *         An abort signal that can be used to interrupt a merge when its
   *         associated `AbortController` is aborted. If omitted, the merge can
   *         still be interrupted when the mirror is finalized.
   * @return {Object.<String, BookmarkChangeRecord>}
   *         A changeset containing locally changed and reconciled records to
   *         upload to the server, and to store in the mirror once upload
   *         succeeds.
   */
  async apply({
    localTimeSeconds,
    remoteTimeSeconds,
    notifyInStableOrder,
    signal = null,
  } = {}) {
    // We intentionally don't use `executeBeforeShutdown` in this function,
    // since merging can take a while for large trees, and we don't want to
    // block shutdown. Since all new items are in the mirror, we'll just try
    // to merge again on the next sync.

    let finalizeOrInterruptSignal = anyAborted(
      this.finalizeController.signal,
      signal
    );

    let changeRecords;
    try {
      changeRecords = await this.tryApply(
        finalizeOrInterruptSignal,
        localTimeSeconds,
        remoteTimeSeconds,
        notifyInStableOrder
      );
    } finally {
      this.progress.reset();
    }

    return changeRecords;
  }

  async tryApply(
    signal,
    localTimeSeconds,
    remoteTimeSeconds,
    notifyInStableOrder = false
  ) {
    let wasMerged = await withTiming("Merging bookmarks in Rust", () =>
      this.merge(signal, localTimeSeconds, remoteTimeSeconds)
    );

    if (!wasMerged) {
      lazy.MirrorLog.debug("No changes detected in both mirror and Places");
      return {};
    }

    // At this point, the database is consistent, so we can notify observers and
    // inflate records for outgoing items.

    let observersToNotify = new BookmarkObserverRecorder(this.db, {
      signal,
      notifyInStableOrder,
    });

    await withTiming(
      "Notifying Places observers",
      async () => {
        try {
          // Note that we don't use a transaction when fetching info for
          // observers, so it's possible we might notify with stale info if the
          // main connection changes Places between the time we finish merging,
          // and the time we notify observers.
          await observersToNotify.notifyAll();
        } catch (ex) {
          // Places relies on observer notifications to update internal caches.
          // If notifying observers failed, these caches may be inconsistent,
          // so we invalidate them just in case.
          await lazy.PlacesUtils.keywords.invalidateCachedKeywords();
          lazy.MirrorLog.warn("Error notifying Places observers", ex);
        } finally {
          await this.db.executeTransaction(async () => {
            await this.db.execute(`DELETE FROM itemsAdded`);
            await this.db.execute(`DELETE FROM guidsChanged`);
            await this.db.execute(`DELETE FROM itemsChanged`);
            await this.db.execute(`DELETE FROM itemsRemoved`);
            await this.db.execute(`DELETE FROM itemsMoved`);
          });
        }
      },
      time =>
        this.progress.stepWithTelemetry(
          ProgressTracker.STEPS.NOTIFY_OBSERVERS,
          time
        )
    );

    let { changeRecords } = await withTiming(
      "Fetching records for local items to upload",
      async () => {
        try {
          let result = await this.fetchLocalChangeRecords(signal);
          return result;
        } finally {
          await this.db.execute(`DELETE FROM itemsToUpload`);
        }
      },
      (time, result) =>
        this.progress.stepWithItemCount(
          ProgressTracker.STEPS.FETCH_LOCAL_CHANGE_RECORDS,
          time,
          result.count
        )
    );

    return changeRecords;
  }

  merge(signal, localTimeSeconds = Date.now() / 1000, remoteTimeSeconds = 0) {
    return new Promise((resolve, reject) => {
      let op = null;
      function onAbort() {
        signal.removeEventListener("abort", onAbort);
        op.cancel();
      }
      let callback = {
        QueryInterface: ChromeUtils.generateQI([
          "mozISyncedBookmarksMirrorProgressListener",
          "mozISyncedBookmarksMirrorCallback",
        ]),
        // `mozISyncedBookmarksMirrorProgressListener` methods.
        onFetchLocalTree: (took, itemCount, deleteCount) => {
          let counts = [
            {
              name: "items",
              count: itemCount,
            },
            {
              name: "deletions",
              count: deleteCount,
            },
          ];
          this.progress.stepWithTelemetry(
            ProgressTracker.STEPS.FETCH_LOCAL_TREE,
            took,
            counts
          );
          // We don't record local tree problems in validation telemetry.
        },
        onFetchRemoteTree: (took, itemCount, deleteCount, problemsBag) => {
          let counts = [
            {
              name: "items",
              count: itemCount,
            },
            {
              name: "deletions",
              count: deleteCount,
            },
          ];
          this.progress.stepWithTelemetry(
            ProgressTracker.STEPS.FETCH_REMOTE_TREE,
            took,
            counts
          );
          // Record validation telemetry for problems in the remote tree.
          let problems = bagToNamedCounts(problemsBag, [
            "orphans",
            "misparentedRoots",
            "multipleParents",
            "nonFolderParents",
            "parentChildDisagreements",
            "missingChildren",
          ]);
          let checked = itemCount + deleteCount;
          this.recordValidationTelemetry(took, checked, problems);
        },
        onMerge: (took, countsBag) => {
          let counts = bagToNamedCounts(countsBag, [
            "items",
            "dupes",
            "remoteRevives",
            "localDeletes",
            "localRevives",
            "remoteDeletes",
          ]);
          this.progress.stepWithTelemetry(
            ProgressTracker.STEPS.MERGE,
            took,
            counts
          );
        },
        onApply: took => {
          this.progress.stepWithTelemetry(ProgressTracker.STEPS.APPLY, took);
        },
        // `mozISyncedBookmarksMirrorCallback` methods.
        handleSuccess(result) {
          signal.removeEventListener("abort", onAbort);
          resolve(result);
        },
        handleError(code, message) {
          signal.removeEventListener("abort", onAbort);
          switch (code) {
            case Cr.NS_ERROR_STORAGE_BUSY:
              reject(new SyncedBookmarksMirror.MergeConflictError(message));
              break;

            case Cr.NS_ERROR_ABORT:
              reject(new SyncedBookmarksMirror.InterruptedError(message));
              break;

            default:
              reject(new SyncedBookmarksMirror.MergeError(message));
          }
        },
      };
      op = this.merger.merge(localTimeSeconds, remoteTimeSeconds, callback);
      if (signal.aborted) {
        op.cancel();
      } else {
        signal.addEventListener("abort", onAbort);
      }
    });
  }

  /**
   * Discards the mirror contents. This is called when the user is node
   * reassigned, disables the bookmarks engine, or signs out.
   */
  async reset() {
    await this.db.executeBeforeShutdown("SyncedBookmarksMirror: reset", db =>
      db.executeTransaction(() => resetMirror(db))
    );
  }

  /**
   * Fetches the GUIDs of all items in the remote tree that need to be merged
   * into the local tree.
   *
   * @return {String[]}
   *         Remotely changed GUIDs that need to be merged into Places.
   */
  async fetchUnmergedGuids() {
    let rows = await this.db.execute(`
      SELECT guid FROM items
      WHERE needsMerge
      ORDER BY guid`);
    return rows.map(row => row.getResultByName("guid"));
  }

  async storeRemoteBookmark(record, { needsMerge, signal }) {
    let guid = lazy.PlacesSyncUtils.bookmarks.recordIdToGuid(record.id);

    let url = validateURL(record.bmkUri);
    if (url) {
      await this.maybeStoreRemoteURL(url);
    }

    let parentGuid = lazy.PlacesSyncUtils.bookmarks.recordIdToGuid(
      record.parentid
    );
    let serverModified = determineServerModified(record);
    let dateAdded = determineDateAdded(record);
    let title = validateTitle(record.title);
    let keyword = validateKeyword(record.keyword);
    let validity = url
      ? Ci.mozISyncedBookmarksMerger.VALIDITY_VALID
      : Ci.mozISyncedBookmarksMerger.VALIDITY_REPLACE;

    let unknownFields = lazy.PlacesSyncUtils.extractUnknownFields(
      record.cleartext,
      [
        "bmkUri",
        "description",
        "keyword",
        "tags",
        "title",
        ...COMMON_UNKNOWN_FIELDS,
      ]
    );
    await this.db.executeCached(
      `
      REPLACE INTO items(guid, parentGuid, serverModified, needsMerge, kind,
                         dateAdded, title, keyword, validity, unknownFields,
                         urlId)
      VALUES(:guid, :parentGuid, :serverModified, :needsMerge, :kind,
             :dateAdded, NULLIF(:title, ''), :keyword, :validity, :unknownFields,
             (SELECT id FROM urls
              WHERE hash = hash(:url) AND
                    url = :url))`,
      {
        guid,
        parentGuid,
        serverModified,
        needsMerge,
        kind: Ci.mozISyncedBookmarksMerger.KIND_BOOKMARK,
        dateAdded,
        title,
        keyword,
        url: url ? url.href : null,
        validity,
        unknownFields,
      }
    );

    let tags = record.tags;
    if (tags && Array.isArray(tags)) {
      for (let rawTag of tags) {
        if (signal.aborted) {
          throw new SyncedBookmarksMirror.InterruptedError(
            "Interrupted while storing tags for incoming bookmark"
          );
        }
        let tag = validateTag(rawTag);
        if (!tag) {
          continue;
        }
        await this.db.executeCached(
          `
          INSERT INTO tags(itemId, tag)
          SELECT id, :tag FROM items
          WHERE guid = :guid`,
          { tag, guid }
        );
      }
    }
  }

  async storeRemoteQuery(record, { needsMerge }) {
    let guid = lazy.PlacesSyncUtils.bookmarks.recordIdToGuid(record.id);

    let validity = Ci.mozISyncedBookmarksMerger.VALIDITY_VALID;

    let url = validateURL(record.bmkUri);
    if (url) {
      // The query has a valid URL. Determine if we need to rewrite and reupload
      // it.
      let params = new URLSearchParams(url.href.slice(url.protocol.length));
      let type = +params.get("type");
      if (type == Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS) {
        // Legacy tag queries with this type use a `place:` URL with a `folder`
        // param that points to the tag folder ID. Rewrite the query to directly
        // reference the tag stored in its `folderName`, then flag the rewritten
        // query for reupload.
        let tagFolderName = validateTag(record.folderName);
        if (tagFolderName) {
          try {
            url.href = `place:tag=${tagFolderName}`;
            validity = Ci.mozISyncedBookmarksMerger.VALIDITY_REUPLOAD;
          } catch (ex) {
            // The tag folder name isn't URL-encoded (bug 1449939), so we might
            // produce an invalid URL. However, invalid URLs are already likely
            // to cause other issues, and it's better to replace or delete the
            // query than break syncing or the Firefox UI.
            url = null;
          }
        } else {
          // The tag folder name is invalid, so replace or delete the remote
          // copy.
          url = null;
        }
      } else {
        let folder = params.get("folder");
        if (folder && !params.has("excludeItems")) {
          // We don't sync enough information to rewrite other queries with a
          // `folder` param (bug 1377175). Referencing a nonexistent folder ID
          // causes the query to return all items in the database, so we add
          // `excludeItems=1` to stop it from doing so. We also flag the
          // rewritten query for reupload.
          try {
            url.href = `${url.href}&excludeItems=1`;
            validity = Ci.mozISyncedBookmarksMerger.VALIDITY_REUPLOAD;
          } catch (ex) {
            url = null;
          }
        }
      }

      // Other queries are implicitly valid, and don't need to be reuploaded
      // or replaced.
    }

    if (url) {
      await this.maybeStoreRemoteURL(url);
    } else {
      // If the query doesn't have a valid URL, we must replace the remote copy
      // with either a valid local copy, or a tombstone if the query doesn't
      // exist locally.
      validity = Ci.mozISyncedBookmarksMerger.VALIDITY_REPLACE;
    }

    let parentGuid = lazy.PlacesSyncUtils.bookmarks.recordIdToGuid(
      record.parentid
    );
    let serverModified = determineServerModified(record);
    let dateAdded = determineDateAdded(record);
    let title = validateTitle(record.title);

    let unknownFields = lazy.PlacesSyncUtils.extractUnknownFields(
      record.cleartext,
      [
        "bmkUri",
        "description",
        "folderName",
        "keyword",
        "queryId",
        "tags",
        "title",
        ...COMMON_UNKNOWN_FIELDS,
      ]
    );

    await this.db.executeCached(
      `
      REPLACE INTO items(guid, parentGuid, serverModified, needsMerge, kind,
                         dateAdded, title,
                         urlId,
                         validity, unknownFields)
      VALUES(:guid, :parentGuid, :serverModified, :needsMerge, :kind,
             :dateAdded, NULLIF(:title, ''),
             (SELECT id FROM urls
              WHERE hash = hash(:url) AND
                    url = :url),
             :validity, :unknownFields)`,
      {
        guid,
        parentGuid,
        serverModified,
        needsMerge,
        kind: Ci.mozISyncedBookmarksMerger.KIND_QUERY,
        dateAdded,
        title,
        url: url ? url.href : null,
        validity,
        unknownFields,
      }
    );
  }

  async storeRemoteFolder(record, { needsMerge, signal }) {
    let guid = lazy.PlacesSyncUtils.bookmarks.recordIdToGuid(record.id);
    let parentGuid = lazy.PlacesSyncUtils.bookmarks.recordIdToGuid(
      record.parentid
    );
    let serverModified = determineServerModified(record);
    let dateAdded = determineDateAdded(record);
    let title = validateTitle(record.title);
    let unknownFields = lazy.PlacesSyncUtils.extractUnknownFields(
      record.cleartext,
      ["children", "description", "title", ...COMMON_UNKNOWN_FIELDS]
    );
    await this.db.executeCached(
      `
      REPLACE INTO items(guid, parentGuid, serverModified, needsMerge, kind,
                         dateAdded, title, unknownFields)
      VALUES(:guid, :parentGuid, :serverModified, :needsMerge, :kind,
             :dateAdded, NULLIF(:title, ''), :unknownFields)`,
      {
        guid,
        parentGuid,
        serverModified,
        needsMerge,
        kind: Ci.mozISyncedBookmarksMerger.KIND_FOLDER,
        dateAdded,
        title,
        unknownFields,
      }
    );

    let children = record.children;
    if (children && Array.isArray(children)) {
      let offset = 0;
      for (let chunk of lazy.PlacesUtils.chunkArray(
        children,
        this.db.variableLimit - 1
      )) {
        if (signal.aborted) {
          throw new SyncedBookmarksMirror.InterruptedError(
            "Interrupted while storing children for incoming folder"
          );
        }
        // Builds a fragment like `(?2, ?1, 0), (?3, ?1, 1), ...`, where ?1 is
        // the folder's GUID, [?2, ?3] are the first and second child GUIDs
        // (SQLite binding parameters index from 1), and [0, 1] are the
        // positions. This lets us store the folder's children using as few
        // statements as possible.
        let valuesFragment = Array.from(
          { length: chunk.length },
          (_, index) => `(?${index + 2}, ?1, ${offset + index})`
        ).join(",");
        await this.db.execute(
          `
          INSERT INTO structure(guid, parentGuid, position)
          VALUES ${valuesFragment}`,
          [guid, ...chunk.map(lazy.PlacesSyncUtils.bookmarks.recordIdToGuid)]
        );
        offset += chunk.length;
      }
    }
  }

  async storeRemoteLivemark(record, { needsMerge }) {
    let guid = lazy.PlacesSyncUtils.bookmarks.recordIdToGuid(record.id);
    let parentGuid = lazy.PlacesSyncUtils.bookmarks.recordIdToGuid(
      record.parentid
    );
    let serverModified = determineServerModified(record);
    let feedURL = validateURL(record.feedUri);
    let dateAdded = determineDateAdded(record);
    let title = validateTitle(record.title);
    let siteURL = validateURL(record.siteUri);

    let validity = feedURL
      ? Ci.mozISyncedBookmarksMerger.VALIDITY_VALID
      : Ci.mozISyncedBookmarksMerger.VALIDITY_REPLACE;

    let unknownFields = lazy.PlacesSyncUtils.extractUnknownFields(
      record.cleartext,
      [
        "children",
        "description",
        "feedUri",
        "siteUri",
        "title",
        ...COMMON_UNKNOWN_FIELDS,
      ]
    );

    await this.db.executeCached(
      `
      REPLACE INTO items(guid, parentGuid, serverModified, needsMerge, kind,
                         dateAdded, title, feedURL, siteURL, validity, unknownFields)
      VALUES(:guid, :parentGuid, :serverModified, :needsMerge, :kind,
             :dateAdded, NULLIF(:title, ''), :feedURL, :siteURL, :validity, :unknownFields)`,
      {
        guid,
        parentGuid,
        serverModified,
        needsMerge,
        kind: Ci.mozISyncedBookmarksMerger.KIND_LIVEMARK,
        dateAdded,
        title,
        feedURL: feedURL ? feedURL.href : null,
        siteURL: siteURL ? siteURL.href : null,
        validity,
        unknownFields,
      }
    );
  }

  async storeRemoteSeparator(record, { needsMerge }) {
    let guid = lazy.PlacesSyncUtils.bookmarks.recordIdToGuid(record.id);
    let parentGuid = lazy.PlacesSyncUtils.bookmarks.recordIdToGuid(
      record.parentid
    );
    let serverModified = determineServerModified(record);
    let dateAdded = determineDateAdded(record);
    let unknownFields = lazy.PlacesSyncUtils.extractUnknownFields(
      record.cleartext,
      ["pos", ...COMMON_UNKNOWN_FIELDS]
    );

    await this.db.executeCached(
      `
      REPLACE INTO items(guid, parentGuid, serverModified, needsMerge, kind,
                         dateAdded, unknownFields)
      VALUES(:guid, :parentGuid, :serverModified, :needsMerge, :kind,
             :dateAdded, :unknownFields)`,
      {
        guid,
        parentGuid,
        serverModified,
        needsMerge,
        kind: Ci.mozISyncedBookmarksMerger.KIND_SEPARATOR,
        dateAdded,
        unknownFields,
      }
    );
  }

  async storeRemoteTombstone(record, { needsMerge }) {
    let guid = lazy.PlacesSyncUtils.bookmarks.recordIdToGuid(record.id);
    let serverModified = determineServerModified(record);

    await this.db.executeCached(
      `
      REPLACE INTO items(guid, serverModified, needsMerge, isDeleted)
      VALUES(:guid, :serverModified, :needsMerge, 1)`,
      { guid, serverModified, needsMerge }
    );
  }

  async maybeStoreRemoteURL(url) {
    await this.db.executeCached(
      `
      INSERT OR IGNORE INTO urls(guid, url, hash, revHost)
      VALUES(IFNULL((SELECT guid FROM urls
                     WHERE hash = hash(:url) AND
                                  url = :url),
                    GENERATE_GUID()), :url, hash(:url), :revHost)`,
      { url: url.href, revHost: lazy.PlacesUtils.getReversedHost(url) }
    );
  }

  /**
   * Inflates Sync records for all staged outgoing items.
   *
   * @param  {AbortSignal} signal
   *         Stops fetching records when the associated `AbortController`
   *         is aborted.
   * @return {Object}
   *         A `{ changeRecords, count }` tuple, where `changeRecords` is a
   *         changeset containing Sync record cleartexts for outgoing items and
   *         tombstones, keyed by their Sync record IDs, and `count` is the
   *         number of records.
   */
  async fetchLocalChangeRecords(signal) {
    let changeRecords = {};
    let childRecordIdsByLocalParentId = new Map();
    let tagsByLocalId = new Map();

    let childGuidRows = [];
    await this.db.execute(
      `SELECT parentId, guid FROM structureToUpload
       ORDER BY parentId, position`,
      null,
      (row, cancel) => {
        if (signal.aborted) {
          cancel();
        } else {
          // `Sqlite.sys.mjs` callbacks swallow exceptions (bug 1387775), so we
          // accumulate all rows in an array, and process them after.
          childGuidRows.push(row);
        }
      }
    );

    await lazy.Async.yieldingForEach(
      childGuidRows,
      row => {
        if (signal.aborted) {
          throw new SyncedBookmarksMirror.InterruptedError(
            "Interrupted while fetching structure to upload"
          );
        }
        let localParentId = row.getResultByName("parentId");
        let childRecordId = lazy.PlacesSyncUtils.bookmarks.guidToRecordId(
          row.getResultByName("guid")
        );
        let childRecordIds = childRecordIdsByLocalParentId.get(localParentId);
        if (childRecordIds) {
          childRecordIds.push(childRecordId);
        } else {
          childRecordIdsByLocalParentId.set(localParentId, [childRecordId]);
        }
      },
      lazy.yieldState
    );

    let tagRows = [];
    await this.db.execute(
      `SELECT id, tag FROM tagsToUpload`,
      null,
      (row, cancel) => {
        if (signal.aborted) {
          cancel();
        } else {
          tagRows.push(row);
        }
      }
    );

    await lazy.Async.yieldingForEach(
      tagRows,
      row => {
        if (signal.aborted) {
          throw new SyncedBookmarksMirror.InterruptedError(
            "Interrupted while fetching tags to upload"
          );
        }
        let localId = row.getResultByName("id");
        let tag = row.getResultByName("tag");
        let tags = tagsByLocalId.get(localId);
        if (tags) {
          tags.push(tag);
        } else {
          tagsByLocalId.set(localId, [tag]);
        }
      },
      lazy.yieldState
    );

    let itemRows = [];
    await this.db.execute(
      `SELECT id, syncChangeCounter, guid, isDeleted, type, isQuery,
              tagFolderName, keyword, url, IFNULL(title, '') AS title,
              position, parentGuid, unknownFields,
              IFNULL(parentTitle, '') AS parentTitle, dateAdded
       FROM itemsToUpload`,
      null,
      (row, cancel) => {
        if (signal.interrupted) {
          cancel();
        } else {
          itemRows.push(row);
        }
      }
    );

    await lazy.Async.yieldingForEach(
      itemRows,
      row => {
        if (signal.aborted) {
          throw new SyncedBookmarksMirror.InterruptedError(
            "Interrupted while fetching items to upload"
          );
        }
        let syncChangeCounter = row.getResultByName("syncChangeCounter");

        let guid = row.getResultByName("guid");
        let recordId = lazy.PlacesSyncUtils.bookmarks.guidToRecordId(guid);

        // Tombstones don't carry additional properties.
        let isDeleted = row.getResultByName("isDeleted");
        if (isDeleted) {
          changeRecords[recordId] = new BookmarkChangeRecord(
            syncChangeCounter,
            {
              id: recordId,
              deleted: true,
            }
          );
          return;
        }

        let parentGuid = row.getResultByName("parentGuid");
        let parentRecordId =
          lazy.PlacesSyncUtils.bookmarks.guidToRecordId(parentGuid);

        let unknownFieldsRow = row.getResultByName("unknownFields");
        let unknownFields = unknownFieldsRow
          ? JSON.parse(unknownFieldsRow)
          : null;
        let type = row.getResultByName("type");
        switch (type) {
          case lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK: {
            let isQuery = row.getResultByName("isQuery");
            if (isQuery) {
              let queryCleartext = {
                id: recordId,
                type: "query",
                // We ignore `parentid` and use the parent's `children`, but older
                // Desktops and Android use `parentid` as the canonical parent.
                // iOS is stricter and requires both `children` and `parentid` to
                // match.
                parentid: parentRecordId,
                // Older Desktops use `hasDupe` (along with `parentName` for
                // deduping), if hasDupe is true, then they won't attempt deduping
                // (since they believe that a duplicate for this record should
                // exist). We set it to true to prevent them from applying their
                // deduping logic.
                hasDupe: true,
                parentName: row.getResultByName("parentTitle"),
                // Omit `dateAdded` from the record if it's not set locally.
                dateAdded: row.getResultByName("dateAdded") || undefined,
                bmkUri: row.getResultByName("url"),
                title: row.getResultByName("title"),
                // folderName should never be an empty string or null
                folderName: row.getResultByName("tagFolderName") || undefined,
                ...unknownFields,
              };
              changeRecords[recordId] = new BookmarkChangeRecord(
                syncChangeCounter,
                queryCleartext
              );
              return;
            }

            let bookmarkCleartext = {
              id: recordId,
              type: "bookmark",
              parentid: parentRecordId,
              hasDupe: true,
              parentName: row.getResultByName("parentTitle"),
              dateAdded: row.getResultByName("dateAdded") || undefined,
              bmkUri: row.getResultByName("url"),
              title: row.getResultByName("title"),
              ...unknownFields,
            };
            let keyword = row.getResultByName("keyword");
            if (keyword) {
              bookmarkCleartext.keyword = keyword;
            }
            let localId = row.getResultByName("id");
            let tags = tagsByLocalId.get(localId);
            if (tags) {
              bookmarkCleartext.tags = tags;
            }
            changeRecords[recordId] = new BookmarkChangeRecord(
              syncChangeCounter,
              bookmarkCleartext
            );
            return;
          }

          case lazy.PlacesUtils.bookmarks.TYPE_FOLDER: {
            let folderCleartext = {
              id: recordId,
              type: "folder",
              parentid: parentRecordId,
              hasDupe: true,
              parentName: row.getResultByName("parentTitle"),
              dateAdded: row.getResultByName("dateAdded") || undefined,
              title: row.getResultByName("title"),
              ...unknownFields,
            };
            let localId = row.getResultByName("id");
            let childRecordIds = childRecordIdsByLocalParentId.get(localId);
            folderCleartext.children = childRecordIds || [];
            changeRecords[recordId] = new BookmarkChangeRecord(
              syncChangeCounter,
              folderCleartext
            );
            return;
          }

          case lazy.PlacesUtils.bookmarks.TYPE_SEPARATOR: {
            let separatorCleartext = {
              id: recordId,
              type: "separator",
              parentid: parentRecordId,
              hasDupe: true,
              parentName: row.getResultByName("parentTitle"),
              dateAdded: row.getResultByName("dateAdded") || undefined,
              // Older Desktops use `pos` for deduping.
              pos: row.getResultByName("position"),
              ...unknownFields,
            };
            changeRecords[recordId] = new BookmarkChangeRecord(
              syncChangeCounter,
              separatorCleartext
            );
            return;
          }

          default:
            throw new TypeError("Can't create record for unknown Places item");
        }
      },
      lazy.yieldState
    );

    return { changeRecords, count: itemRows.length };
  }

  /**
   * Closes the mirror database connection. This is called automatically on
   * shutdown, but may also be called explicitly when the mirror is no longer
   * needed.
   *
   * @param {Boolean} [options.alsoCleanup]
   *                  If specified, drop all temp tables, views, and triggers,
   *                  and detach from the mirror database before closing the
   *                  connection. Defaults to `true`.
   */
  finalize({ alsoCleanup = true } = {}) {
    if (!this.finalizePromise) {
      this.finalizePromise = (async () => {
        this.progress.step(ProgressTracker.STEPS.FINALIZE);
        this.finalizeController.abort();
        this.merger.reset();
        if (alsoCleanup) {
          // If the mirror is finalized explicitly, clean up temp entities and
          // detach from the mirror database. We can skip this for automatic
          // finalization, since the Places connection is already shutting
          // down.
          await cleanupMirrorDatabase(this.db);
        }
        await this.db.execute(`PRAGMA mirror.optimize(0x02)`);
        await this.db.execute(`DETACH mirror`);
        this.finalizeAt.removeBlocker(this.finalizeBound);
      })();
    }
    return this.finalizePromise;
  }
}

/** Key names for the key-value `meta` table. */
SyncedBookmarksMirror.META_KEY = {
  LAST_MODIFIED: "collection/lastModified",
  SYNC_ID: "collection/syncId",
};

/**
 * An error thrown when the merge was interrupted.
 */
class InterruptedError extends Error {
  constructor(message) {
    super(message);
    this.name = "InterruptedError";
  }
}
SyncedBookmarksMirror.InterruptedError = InterruptedError;

/**
 * An error thrown when the merge failed for an unexpected reason.
 */
class MergeError extends Error {
  constructor(message) {
    super(message);
    this.name = "MergeError";
  }
}
SyncedBookmarksMirror.MergeError = MergeError;

/**
 * An error thrown when the merge can't proceed because the local tree
 * changed during the merge.
 */
class MergeConflictError extends Error {
  constructor(message) {
    super(message);
    this.name = "MergeConflictError";
  }
}
SyncedBookmarksMirror.MergeConflictError = MergeConflictError;

/**
 * An error thrown when the mirror database is corrupt, or can't be migrated to
 * the latest schema version, and must be replaced.
 */
class DatabaseCorruptError extends Error {
  constructor(message) {
    super(message);
    this.name = "DatabaseCorruptError";
  }
}

// Indicates if the mirror should be replaced because the database file is
// corrupt.
function isDatabaseCorrupt(error) {
  if (error instanceof DatabaseCorruptError) {
    return true;
  }
  if (error.errors) {
    return error.errors.some(
      error =>
        error instanceof Ci.mozIStorageError &&
        (error.result == Ci.mozIStorageError.CORRUPT ||
          error.result == Ci.mozIStorageError.NOTADB)
    );
  }
  return false;
}

/**
 * Attaches a cloned Places database connection to the mirror database,
 * migrates the mirror schema to the latest version, and creates temporary
 * tables, views, and triggers.
 *
 * @param {Sqlite.OpenedConnection} db
 *        The Places database connection.
 * @param {String} path
 *        The full path to the mirror database file.
 */
async function attachAndInitMirrorDatabase(db, path) {
  await db.execute(`ATTACH :path AS mirror`, { path });
  try {
    await db.executeTransaction(async function () {
      let currentSchemaVersion = await db.getSchemaVersion("mirror");
      if (currentSchemaVersion > 0) {
        if (currentSchemaVersion < MIRROR_SCHEMA_VERSION) {
          await migrateMirrorSchema(db, currentSchemaVersion);
        }
      } else {
        await initializeMirrorDatabase(db);
      }
      // Downgrading from a newer profile to an older profile rolls back the
      // schema version, but leaves all new columns in place. We'll run the
      // migration logic again on the next upgrade.
      await db.setSchemaVersion(MIRROR_SCHEMA_VERSION, "mirror");
      await initializeTempMirrorEntities(db);
    });
  } catch (ex) {
    await db.execute(`DETACH mirror`);
    throw ex;
  }
}

/**
 * Migrates the mirror database schema to the latest version.
 *
 * @param {Sqlite.OpenedConnection} db
 *        The mirror database connection.
 * @param {Number} currentSchemaVersion
 *        The current mirror database schema version.
 */
async function migrateMirrorSchema(db, currentSchemaVersion) {
  if (currentSchemaVersion < 5) {
    // The mirror was pref'd off by default for schema versions 1-4.
    throw new DatabaseCorruptError(
      `Can't migrate from schema version ${currentSchemaVersion}; too old`
    );
  }
  if (currentSchemaVersion < 6) {
    await db.execute(`CREATE INDEX IF NOT EXISTS mirror.itemURLs ON
                      items(urlId)`);
    await db.execute(`CREATE INDEX IF NOT EXISTS mirror.itemKeywords ON
                      items(keyword) WHERE keyword NOT NULL`);
  }
  if (currentSchemaVersion < 7) {
    await db.execute(`CREATE INDEX IF NOT EXISTS mirror.structurePositions ON
                      structure(parentGuid, position)`);
  }
  if (currentSchemaVersion < 8) {
    // Not really a "schema" update, but addresses the defect from bug 1635859.
    // In short, every bookmark with a corresponding entry in the mirror should
    // have syncStatus = NORMAL.
    await db.execute(`UPDATE moz_bookmarks AS b
                      SET syncStatus = ${lazy.PlacesUtils.bookmarks.SYNC_STATUS.NORMAL}
                      WHERE EXISTS (SELECT 1 FROM mirror.items
                                    WHERE guid = b.guid)`);
  }
  if (currentSchemaVersion < 9) {
    // Adding unknownFields to the mirror table, which allows us to
    // keep fields we may not yet understand from other clients and roundtrip
    // them during the sync process
    let columns = await db.execute(`PRAGMA table_info(items)`);
    // migration needs to be idempotent, so we check if the column exists first
    let exists = columns.find(
      row => row.getResultByName("name") === "unknownFields"
    );
    if (!exists) {
      await db.execute(`ALTER TABLE items ADD COLUMN unknownFields TEXT`);
    }
  }
}

/**
 * Initializes a new mirror database, creating persistent tables, indexes, and
 * roots.
 *
 * @param {Sqlite.OpenedConnection} db
 *        The mirror database connection.
 */
async function initializeMirrorDatabase(db) {
  // Key-value metadata table. Stores the server collection last modified time
  // and sync ID.
  await db.execute(`CREATE TABLE mirror.meta(
    key TEXT PRIMARY KEY,
    value NOT NULL
  ) WITHOUT ROWID`);

  // Note: description and loadInSidebar are not used as of Firefox 63, but
  // remain to avoid rebuilding the database if the user happens to downgrade.
  await db.execute(`CREATE TABLE mirror.items(
    id INTEGER PRIMARY KEY,
    guid TEXT UNIQUE NOT NULL,
    /* The "parentid" from the record. */
    parentGuid TEXT,
    /* The server modified time, in milliseconds. */
    serverModified INTEGER NOT NULL DEFAULT 0,
    needsMerge BOOLEAN NOT NULL DEFAULT 0,
    validity INTEGER NOT NULL DEFAULT ${Ci.mozISyncedBookmarksMerger.VALIDITY_VALID},
    isDeleted BOOLEAN NOT NULL DEFAULT 0,
    kind INTEGER NOT NULL DEFAULT -1,
    /* The creation date, in milliseconds. */
    dateAdded INTEGER NOT NULL DEFAULT 0,
    title TEXT,
    urlId INTEGER REFERENCES urls(id)
                  ON DELETE SET NULL,
    keyword TEXT,
    description TEXT,
    loadInSidebar BOOLEAN,
    smartBookmarkName TEXT,
    feedURL TEXT,
    siteURL TEXT,
    unknownFields TEXT
  )`);

  await db.execute(`CREATE TABLE mirror.structure(
    guid TEXT,
    parentGuid TEXT REFERENCES items(guid)
                    ON DELETE CASCADE,
    position INTEGER NOT NULL,
    PRIMARY KEY(parentGuid, guid)
  ) WITHOUT ROWID`);

  await db.execute(`CREATE TABLE mirror.urls(
    id INTEGER PRIMARY KEY,
    guid TEXT NOT NULL,
    url TEXT NOT NULL,
    hash INTEGER NOT NULL,
    revHost TEXT NOT NULL
  )`);

  await db.execute(`CREATE TABLE mirror.tags(
    itemId INTEGER NOT NULL REFERENCES items(id)
                            ON DELETE CASCADE,
    tag TEXT NOT NULL
  )`);

  await db.execute(
    `CREATE INDEX mirror.structurePositions ON structure(parentGuid, position)`
  );

  await db.execute(`CREATE INDEX mirror.urlHashes ON urls(hash)`);

  await db.execute(`CREATE INDEX mirror.itemURLs ON items(urlId)`);

  await db.execute(`CREATE INDEX mirror.itemKeywords ON items(keyword)
                    WHERE keyword NOT NULL`);

  await createMirrorRoots(db);
}

/**
 * Drops all temp tables, views, and triggers used for merging, and detaches
 * from the mirror database.
 *
 * @param {Sqlite.OpenedConnection} db
 *        The mirror database connection.
 */
async function cleanupMirrorDatabase(db) {
  await db.executeTransaction(async function () {
    await db.execute(`DROP TABLE changeGuidOps`);
    await db.execute(`DROP TABLE itemsToApply`);
    await db.execute(`DROP TABLE applyNewLocalStructureOps`);
    await db.execute(`DROP VIEW localTags`);
    await db.execute(`DROP TABLE itemsAdded`);
    await db.execute(`DROP TABLE guidsChanged`);
    await db.execute(`DROP TABLE itemsChanged`);
    await db.execute(`DROP TABLE itemsMoved`);
    await db.execute(`DROP TABLE itemsRemoved`);
    await db.execute(`DROP TABLE itemsToUpload`);
    await db.execute(`DROP TABLE structureToUpload`);
    await db.execute(`DROP TABLE tagsToUpload`);
  });
}

/**
 * Sets up the syncable roots. All items in the mirror we apply will descend
 * from these roots - however, malformed records from the server which create
 * a different root *will* be created in the mirror - just not applied.
 *
 *
 * @param {Sqlite.OpenedConnection} db
 *        The mirror database connection.
 */
async function createMirrorRoots(db) {
  const syncableRoots = [
    {
      guid: lazy.PlacesUtils.bookmarks.rootGuid,
      // The Places root is its own parent, to satisfy the foreign key and
      // `NOT NULL` constraints on `structure`.
      parentGuid: lazy.PlacesUtils.bookmarks.rootGuid,
      position: -1,
      needsMerge: false,
    },
    ...lazy.PlacesUtils.bookmarks.userContentRoots.map((guid, position) => {
      return {
        guid,
        parentGuid: lazy.PlacesUtils.bookmarks.rootGuid,
        position,
        needsMerge: true,
      };
    }),
  ];

  for (let { guid, parentGuid, position, needsMerge } of syncableRoots) {
    await db.executeCached(
      `
      INSERT INTO items(guid, parentGuid, kind, needsMerge)
      VALUES(:guid, :parentGuid, :kind, :needsMerge)`,
      {
        guid,
        parentGuid,
        kind: Ci.mozISyncedBookmarksMerger.KIND_FOLDER,
        needsMerge,
      }
    );

    await db.executeCached(
      `
      INSERT INTO structure(guid, parentGuid, position)
      VALUES(:guid, :parentGuid, :position)`,
      { guid, parentGuid, position }
    );
  }
}

/**
 * Creates temporary tables, views, and triggers to apply the mirror to Places.
 *
 * @param {Sqlite.OpenedConnection} db
 *        The mirror database connection.
 */
async function initializeTempMirrorEntities(db) {
  await db.execute(`CREATE TEMP TABLE changeGuidOps(
    localGuid TEXT PRIMARY KEY,
    mergedGuid TEXT UNIQUE NOT NULL,
    syncStatus INTEGER,
    level INTEGER NOT NULL,
    lastModifiedMicroseconds INTEGER NOT NULL
  ) WITHOUT ROWID`);

  await db.execute(`
    CREATE TEMP TRIGGER changeGuids
    AFTER DELETE ON changeGuidOps
    BEGIN
      /* Record item changed notifications for the updated GUIDs. */
      INSERT INTO guidsChanged(itemId, oldGuid, level)
      SELECT b.id, OLD.localGuid, OLD.level
      FROM moz_bookmarks b
      WHERE b.guid = OLD.localGuid;

      UPDATE moz_bookmarks SET
        guid = OLD.mergedGuid,
        lastModified = OLD.lastModifiedMicroseconds,
        syncStatus = IFNULL(OLD.syncStatus, syncStatus)
      WHERE guid = OLD.localGuid;
    END`);

  await db.execute(`CREATE TEMP TABLE itemsToApply(
    mergedGuid TEXT PRIMARY KEY,
    localId INTEGER UNIQUE,
    remoteId INTEGER UNIQUE NOT NULL,
    remoteGuid TEXT UNIQUE NOT NULL,
    newLevel INTEGER NOT NULL,
    newType INTEGER NOT NULL,
    localDateAddedMicroseconds INTEGER,
    remoteDateAddedMicroseconds INTEGER NOT NULL,
    lastModifiedMicroseconds INTEGER NOT NULL,
    oldTitle TEXT,
    newTitle TEXT,
    oldPlaceId INTEGER,
    newPlaceId INTEGER,
    newKeyword TEXT
  )`);

  await db.execute(`CREATE INDEX existingItems ON itemsToApply(localId)
                    WHERE localId NOT NULL`);

  await db.execute(`CREATE INDEX oldPlaceIds ON itemsToApply(oldPlaceId)
                    WHERE oldPlaceId NOT NULL`);

  await db.execute(`CREATE INDEX newPlaceIds ON itemsToApply(newPlaceId)
                    WHERE newPlaceId NOT NULL`);

  await db.execute(`CREATE INDEX newKeywords ON itemsToApply(newKeyword)
                    WHERE newKeyword NOT NULL`);

  await db.execute(`CREATE TEMP TABLE applyNewLocalStructureOps(
    mergedGuid TEXT PRIMARY KEY,
    mergedParentGuid TEXT NOT NULL,
    position INTEGER NOT NULL,
    level INTEGER NOT NULL,
    lastModifiedMicroseconds INTEGER NOT NULL
  ) WITHOUT ROWID`);

  await db.execute(`
    CREATE TEMP TRIGGER applyNewLocalStructure
    AFTER DELETE ON applyNewLocalStructureOps
    BEGIN
      INSERT INTO itemsMoved(itemId, oldParentId, oldParentGuid, oldPosition,
                             level)
      SELECT b.id, p.id, p.guid, b.position, OLD.level
      FROM moz_bookmarks b
      JOIN moz_bookmarks p ON p.id = b.parent
      WHERE b.guid = OLD.mergedGuid;

      UPDATE moz_bookmarks SET
        parent = (SELECT id FROM moz_bookmarks
                  WHERE guid = OLD.mergedParentGuid),
        position = OLD.position,
        lastModified = OLD.lastModifiedMicroseconds
      WHERE guid = OLD.mergedGuid;
    END`);

  // A view of local bookmark tags. Tags, like keywords, are associated with
  // URLs, so two bookmarks with the same URL should have the same tags. Unlike
  // keywords, one tag may be associated with many different URLs. Tags are also
  // different because they're implemented as bookmarks under the hood. Each tag
  // is stored as a folder under the tags root, and tagged URLs are stored as
  // untitled bookmarks under these folders. This complexity can be removed once
  // bug 424160 lands.
  await db.execute(`
    CREATE TEMP VIEW localTags(tagEntryId, tagEntryGuid, tagFolderId,
                               tagFolderGuid, tagEntryPosition, tagEntryType,
                               tag, placeId, lastModifiedMicroseconds) AS
    SELECT b.id, b.guid, p.id, p.guid, b.position, b.type,
           p.title, b.fk, b.lastModified
    FROM moz_bookmarks b
    JOIN moz_bookmarks p ON p.id = b.parent
    WHERE b.type = ${lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK} AND
          p.parent = (SELECT id FROM moz_bookmarks
                      WHERE guid = '${lazy.PlacesUtils.bookmarks.tagsGuid}')`);

  // Untags a URL by removing its tag entry.
  await db.execute(`
    CREATE TEMP TRIGGER untagLocalPlace
    INSTEAD OF DELETE ON localTags
    BEGIN
      /* Record an item removed notification for the tag entry. */
      INSERT INTO itemsRemoved(itemId, parentId, position, type, placeId, guid,
                               parentGuid, title, isUntagging)
      VALUES(OLD.tagEntryId, OLD.tagFolderId, OLD.tagEntryPosition,
             OLD.tagEntryType, OLD.placeId, OLD.tagEntryGuid,
             OLD.tagFolderGuid, OLD.tag, 1);

      DELETE FROM moz_bookmarks WHERE id = OLD.tagEntryId;

      /* Fix the positions of the sibling tag entries. */
      UPDATE moz_bookmarks SET
        position = position - 1
      WHERE parent = OLD.tagFolderId AND
            position > OLD.tagEntryPosition;
    END`);

  // Tags a URL by creating a tag folder if it doesn't exist, then inserting a
  // tag entry for the URL into the tag folder. `NEW.placeId` can be NULL, in
  // which case we'll just create the tag folder.
  await db.execute(`
    CREATE TEMP TRIGGER tagLocalPlace
    INSTEAD OF INSERT ON localTags
    BEGIN
      /* Ensure the tag folder exists. */
      INSERT OR IGNORE INTO moz_bookmarks(guid, parent, position, type, title,
                                          dateAdded, lastModified)
      VALUES(IFNULL((SELECT b.guid FROM moz_bookmarks b
                     JOIN moz_bookmarks p ON p.id = b.parent
                     WHERE b.title = NEW.tag AND
                           p.guid = '${lazy.PlacesUtils.bookmarks.tagsGuid}'),
                    GENERATE_GUID()),
             (SELECT id FROM moz_bookmarks
              WHERE guid = '${lazy.PlacesUtils.bookmarks.tagsGuid}'),
             (SELECT COUNT(*) FROM moz_bookmarks b
              JOIN moz_bookmarks p ON p.id = b.parent
              WHERE p.guid = '${lazy.PlacesUtils.bookmarks.tagsGuid}'),
             ${lazy.PlacesUtils.bookmarks.TYPE_FOLDER}, NEW.tag,
             NEW.lastModifiedMicroseconds,
             NEW.lastModifiedMicroseconds);

      /* Record an item added notification if we created a tag folder.
         "CHANGES()" returns the number of rows affected by the INSERT above:
         1 if we created the folder, or 0 if the folder already existed. */
      INSERT INTO itemsAdded(guid, isTagging)
      SELECT b.guid, 1
      FROM moz_bookmarks b
      JOIN moz_bookmarks p ON p.id = b.parent
      WHERE CHANGES() > 0 AND
            b.title = NEW.tag AND
            p.guid = '${lazy.PlacesUtils.bookmarks.tagsGuid}';

      /* Add a tag entry for the URL under the tag folder. Omitting the place
         ID creates a tag folder without tagging the URL. */
      INSERT OR IGNORE INTO moz_bookmarks(guid, parent, position, type, fk,
                                          dateAdded, lastModified)
      SELECT IFNULL((SELECT b.guid FROM moz_bookmarks b
                     JOIN moz_bookmarks p ON p.id = b.parent
                     WHERE b.fk = NEW.placeId AND
                           p.title = NEW.tag AND
                           p.parent = (SELECT id FROM moz_bookmarks
                                       WHERE guid = '${lazy.PlacesUtils.bookmarks.tagsGuid}')),
                    GENERATE_GUID()),
             (SELECT b.id FROM moz_bookmarks b
              JOIN moz_bookmarks p ON p.id = b.parent
              WHERE p.guid = '${lazy.PlacesUtils.bookmarks.tagsGuid}' AND
                    b.title = NEW.tag),
             (SELECT COUNT(*) FROM moz_bookmarks b
              JOIN moz_bookmarks p ON p.id = b.parent
              WHERE p.title = NEW.tag AND
                    p.parent = (SELECT id FROM moz_bookmarks
                                WHERE guid = '${lazy.PlacesUtils.bookmarks.tagsGuid}')),
             ${lazy.PlacesUtils.bookmarks.TYPE_BOOKMARK}, NEW.placeId,
             NEW.lastModifiedMicroseconds,
             NEW.lastModifiedMicroseconds
      WHERE NEW.placeId NOT NULL;

      /* Record an item added notification for the tag entry. */
      INSERT INTO itemsAdded(guid, isTagging)
      SELECT b.guid, 1
      FROM moz_bookmarks b
      JOIN moz_bookmarks p ON p.id = b.parent
      WHERE CHANGES() > 0 AND
            b.fk = NEW.placeId AND
            p.title = NEW.tag AND
            p.parent = (SELECT id FROM moz_bookmarks
                        WHERE guid = '${lazy.PlacesUtils.bookmarks.tagsGuid}');
    END`);

  // Stores properties to pass to `onItem{Added, Changed, Moved, Removed}`
  // bookmark observers for new, updated, moved, and deleted items.
  await db.execute(`CREATE TEMP TABLE itemsAdded(
    guid TEXT PRIMARY KEY,
    isTagging BOOLEAN NOT NULL DEFAULT 0,
    keywordChanged BOOLEAN NOT NULL DEFAULT 0,
    level INTEGER NOT NULL DEFAULT -1
  ) WITHOUT ROWID`);

  await db.execute(`CREATE INDEX addedItemLevels ON itemsAdded(level)`);

  await db.execute(`CREATE TEMP TABLE guidsChanged(
    itemId INTEGER PRIMARY KEY,
    oldGuid TEXT NOT NULL,
    level INTEGER NOT NULL DEFAULT -1
  )`);

  await db.execute(`CREATE INDEX changedGuidLevels ON guidsChanged(level)`);

  await db.execute(`CREATE TEMP TABLE itemsChanged(
    itemId INTEGER PRIMARY KEY,
    oldTitle TEXT,
    oldPlaceId INTEGER,
    keywordChanged BOOLEAN NOT NULL DEFAULT 0,
    level INTEGER NOT NULL DEFAULT -1
  )`);

  await db.execute(`CREATE INDEX changedItemLevels ON itemsChanged(level)`);

  await db.execute(`CREATE TEMP TABLE itemsMoved(
    itemId INTEGER PRIMARY KEY,
    oldParentId INTEGER NOT NULL,
    oldParentGuid TEXT NOT NULL,
    oldPosition INTEGER NOT NULL,
    level INTEGER NOT NULL DEFAULT -1
  )`);

  await db.execute(`CREATE INDEX movedItemLevels ON itemsMoved(level)`);

  await db.execute(`CREATE TEMP TABLE itemsRemoved(
    itemId INTEGER PRIMARY KEY,
    guid TEXT NOT NULL,
    parentId INTEGER NOT NULL,
    position INTEGER NOT NULL,
    type INTEGER NOT NULL,
    title TEXT NOT NULL,
    placeId INTEGER,
    parentGuid TEXT NOT NULL,
    /* We record the original level of the removed item in the tree so that we
       can notify children before parents. */
    level INTEGER NOT NULL DEFAULT -1,
    isUntagging BOOLEAN NOT NULL DEFAULT 0,
    keywordRemoved BOOLEAN NOT NULL DEFAULT 0
  )`);

  await db.execute(
    `CREATE INDEX removedItemLevels ON itemsRemoved(level DESC)`
  );

  // Stores locally changed items staged for upload.
  await db.execute(`CREATE TEMP TABLE itemsToUpload(
    id INTEGER PRIMARY KEY,
    guid TEXT UNIQUE NOT NULL,
    syncChangeCounter INTEGER NOT NULL,
    isDeleted BOOLEAN NOT NULL DEFAULT 0,
    parentGuid TEXT,
    parentTitle TEXT,
    dateAdded INTEGER, /* In milliseconds. */
    type INTEGER,
    title TEXT,
    placeId INTEGER,
    isQuery BOOLEAN NOT NULL DEFAULT 0,
    url TEXT,
    tagFolderName TEXT,
    keyword TEXT,
    position INTEGER,
    unknownFields TEXT
  )`);

  await db.execute(`CREATE TEMP TABLE structureToUpload(
    guid TEXT PRIMARY KEY,
    parentId INTEGER NOT NULL REFERENCES itemsToUpload(id)
                              ON DELETE CASCADE,
    position INTEGER NOT NULL
  ) WITHOUT ROWID`);

  await db.execute(
    `CREATE INDEX parentsToUpload ON structureToUpload(parentId, position)`
  );

  await db.execute(`CREATE TEMP TABLE tagsToUpload(
    id INTEGER REFERENCES itemsToUpload(id)
               ON DELETE CASCADE,
    tag TEXT,
    PRIMARY KEY(id, tag)
  ) WITHOUT ROWID`);
}

async function resetMirror(db) {
  await db.execute(`DELETE FROM meta`);
  await db.execute(`DELETE FROM structure`);
  await db.execute(`DELETE FROM items`);
  await db.execute(`DELETE FROM urls`);

  // Since we need to reset the modified times and merge flags for the syncable
  // roots, we simply delete and recreate them.
  await createMirrorRoots(db);
}

// Converts a Sync record's last modified time to milliseconds.
function determineServerModified(record) {
  return Math.max(record.modified * 1000, 0) || 0;
}

// Determines a Sync record's creation date.
function determineDateAdded(record) {
  let serverModified = determineServerModified(record);
  return lazy.PlacesSyncUtils.bookmarks.ratchetTimestampBackwards(
    record.dateAdded,
    serverModified
  );
}

function validateTitle(rawTitle) {
  if (typeof rawTitle != "string" || !rawTitle) {
    return null;
  }
  return rawTitle.slice(0, DB_TITLE_LENGTH_MAX);
}

function validateURL(rawURL) {
  if (typeof rawURL != "string" || rawURL.length > DB_URL_LENGTH_MAX) {
    return null;
  }
  return URL.parse(rawURL);
}

function validateKeyword(rawKeyword) {
  if (typeof rawKeyword != "string") {
    return null;
  }
  let keyword = rawKeyword.trim();
  // Drop empty keywords.
  return keyword ? keyword.toLowerCase() : null;
}

function validateTag(rawTag) {
  if (typeof rawTag != "string") {
    return null;
  }
  let tag = rawTag.trim();
  if (!tag || tag.length > lazy.PlacesUtils.bookmarks.MAX_TAG_LENGTH) {
    // Drop empty and oversized tags.
    return null;
  }
  return tag;
}

/**
 * Measures and logs the time taken to execute a function, using a monotonic
 * clock.
 *
 * @param  {String} name
 *         The name of the operation, used for logging.
 * @param  {Function} func
 *         The function to time.
 * @param  {Function} [recordTiming]
 *         An optional function with the signature `(time: Number)`, where
 *         `time` is the measured time.
 * @return The return value of the timed function.
 */
async function withTiming(name, func, recordTiming) {
  lazy.MirrorLog.debug(name);

  let startTime = Cu.now();
  let result = await func();
  let elapsedTime = Cu.now() - startTime;

  lazy.MirrorLog.debug(`${name} took ${elapsedTime.toFixed(3)}ms`);
  if (typeof recordTiming == "function") {
    recordTiming(elapsedTime, result);
  }

  return result;
}

/**
 * Fires bookmark and keyword observer notifications for all changes made during
 * the merge.
 */
class BookmarkObserverRecorder {
  constructor(db, { notifyInStableOrder, signal }) {
    this.db = db;
    this.notifyInStableOrder = notifyInStableOrder;
    this.signal = signal;
    this.placesEvents = [];
    this.shouldInvalidateKeywords = false;
  }

  /**
   * Fires observer notifications for all changed items, invalidates the
   * livemark cache if necessary, and recalculates frecencies for changed
   * URLs. This is called outside the merge transaction.
   */
  async notifyAll() {
    await this.noteAllChanges();
    if (this.shouldInvalidateKeywords) {
      await lazy.PlacesUtils.keywords.invalidateCachedKeywords();
    }
    this.notifyBookmarkObservers();
    if (this.signal.aborted) {
      throw new SyncedBookmarksMirror.InterruptedError(
        "Interrupted before recalculating frecencies for new URLs"
      );
    }
  }

  orderBy(level, parent, position) {
    return `ORDER BY ${
      this.notifyInStableOrder ? `${level}, ${parent}, ${position}` : level
    }`;
  }

  /**
   * Records Places observer notifications for removed, added, moved, and
   * changed items.
   */
  async noteAllChanges() {
    lazy.MirrorLog.trace("Recording observer notifications for removed items");
    // `ORDER BY v.level DESC` sorts deleted children before parents, to ensure
    // that we update caches in the correct order (bug 1297941).
    await this.db.execute(
      `SELECT v.itemId AS id, v.parentId, v.parentGuid, v.position, v.type,
              (SELECT h.url FROM moz_places h WHERE h.id = v.placeId) AS url,
              v.title, v.guid, v.isUntagging, v.keywordRemoved
       FROM itemsRemoved v
       ${this.orderBy("v.level", "v.parentId", "v.position")}`,
      null,
      (row, cancel) => {
        if (this.signal.aborted) {
          cancel();
          return;
        }
        let info = {
          id: row.getResultByName("id"),
          parentId: row.getResultByName("parentId"),
          position: row.getResultByName("position"),
          type: row.getResultByName("type"),
          urlHref: row.getResultByName("url"),
          title: row.getResultByName("title"),
          guid: row.getResultByName("guid"),
          parentGuid: row.getResultByName("parentGuid"),
          isUntagging: row.getResultByName("isUntagging"),
        };
        this.noteItemRemoved(info);
        if (row.getResultByName("keywordRemoved")) {
          this.shouldInvalidateKeywords = true;
        }
      }
    );
    if (this.signal.aborted) {
      throw new SyncedBookmarksMirror.InterruptedError(
        "Interrupted while recording observer notifications for removed items"
      );
    }

    lazy.MirrorLog.trace("Recording observer notifications for changed GUIDs");
    await this.db.execute(
      `SELECT b.id, b.lastModified, b.type, b.guid AS newGuid,
              p.guid AS parentGuid, gp.guid AS grandParentGuid
       FROM guidsChanged c
       JOIN moz_bookmarks b ON b.id = c.itemId
       JOIN moz_bookmarks p ON p.id = b.parent
       LEFT JOIN moz_bookmarks gp ON gp.id = p.parent
       ${this.orderBy("c.level", "b.parent", "b.position")}`,
      null,
      (row, cancel) => {
        if (this.signal.aborted) {
          cancel();
          return;
        }
        let info = {
          id: row.getResultByName("id"),
          lastModified: row.getResultByName("lastModified"),
          type: row.getResultByName("type"),
          newGuid: row.getResultByName("newGuid"),
          parentGuid: row.getResultByName("parentGuid"),
          grandParentGuid: row.getResultByName("grandParentGuid"),
        };
        this.noteGuidChanged(info);
      }
    );
    if (this.signal.aborted) {
      throw new SyncedBookmarksMirror.InterruptedError(
        "Interrupted while recording observer notifications for changed GUIDs"
      );
    }

    lazy.MirrorLog.trace("Recording observer notifications for new items");
    await this.db.execute(
      `SELECT b.id, p.id AS parentId, b.position, b.type,
              IFNULL(b.title, '') AS title, b.dateAdded, b.guid,
              p.guid AS parentGuid, n.isTagging, n.keywordChanged,
              h.url AS url, IFNULL(h.frecency, 0) AS frecency,
              IFNULL(h.hidden, 0) AS hidden,
              IFNULL(h.visit_count, 0) AS visit_count,
              h.last_visit_date,
              (SELECT group_concat(pp.title ORDER BY pp.title)
               FROM moz_bookmarks bb
               JOIN moz_bookmarks pp ON pp.id = bb.parent
               JOIN moz_bookmarks gg ON gg.id = pp.parent
               WHERE bb.fk = h.id
               AND gg.guid = '${lazy.PlacesUtils.bookmarks.tagsGuid}'
              ) AS tags,
              t.guid AS tGuid, t.id AS tId, t.title AS tTitle
       FROM itemsAdded n
       JOIN moz_bookmarks b ON b.guid = n.guid
       JOIN moz_bookmarks p ON p.id = b.parent
       LEFT JOIN moz_places h ON h.id = b.fk
       LEFT JOIN moz_bookmarks t ON t.guid = target_folder_guid(url)
       ${this.orderBy("n.level", "b.parent", "b.position")}`,
      null,
      (row, cancel) => {
        if (this.signal.aborted) {
          cancel();
          return;
        }

        let lastVisitDate = row.getResultByName("last_visit_date");

        let info = {
          id: row.getResultByName("id"),
          parentId: row.getResultByName("parentId"),
          position: row.getResultByName("position"),
          type: row.getResultByName("type"),
          urlHref: row.getResultByName("url"),
          title: row.getResultByName("title"),
          dateAdded: row.getResultByName("dateAdded"),
          guid: row.getResultByName("guid"),
          parentGuid: row.getResultByName("parentGuid"),
          isTagging: row.getResultByName("isTagging"),
          frecency: row.getResultByName("frecency"),
          hidden: row.getResultByName("hidden"),
          visitCount: row.getResultByName("visit_count"),
          lastVisitDate: lastVisitDate
            ? lazy.PlacesUtils.toDate(lastVisitDate).getTime()
            : null,
          tags: row.getResultByName("tags"),
          targetFolderGuid: row.getResultByName("tGuid"),
          targetFolderItemId: row.getResultByName("tId"),
          targetFolderTitle: row.getResultByName("tTitle"),
        };

        this.noteItemAdded(info);
        if (row.getResultByName("keywordChanged")) {
          this.shouldInvalidateKeywords = true;
        }
      }
    );
    if (this.signal.aborted) {
      throw new SyncedBookmarksMirror.InterruptedError(
        "Interrupted while recording observer notifications for new items"
      );
    }

    lazy.MirrorLog.trace("Recording observer notifications for moved items");
    await this.db.execute(
      `SELECT b.id, b.guid, b.type, p.guid AS newParentGuid, c.oldParentGuid,
              b.position AS newPosition, c.oldPosition,
              gp.guid AS grandParentGuid,
              h.url AS url, IFNULL(b.title, '') AS title,
              IFNULL(h.frecency, 0) AS frecency, IFNULL(h.hidden, 0) AS hidden,
              IFNULL(h.visit_count, 0) AS visit_count,
              b.dateAdded, h.last_visit_date,
              (SELECT group_concat(pp.title ORDER BY pp.title)
               FROM moz_bookmarks bb
               JOIN moz_bookmarks pp ON pp.id = bb.parent
               JOIN moz_bookmarks gg ON gg.id = pp.parent
               WHERE bb.fk = h.id
               AND gg.guid = '${lazy.PlacesUtils.bookmarks.tagsGuid}'
              ) AS tags
       FROM itemsMoved c
       JOIN moz_bookmarks b ON b.id = c.itemId
       JOIN moz_bookmarks p ON p.id = b.parent
       LEFT JOIN moz_bookmarks gp ON gp.id = p.parent
       LEFT JOIN moz_places h ON h.id = b.fk
       ${this.orderBy("c.level", "b.parent", "b.position")}`,
      null,
      (row, cancel) => {
        if (this.signal.aborted) {
          cancel();
          return;
        }
        let lastVisitDate = row.getResultByName("last_visit_date");
        let info = {
          id: row.getResultByName("id"),
          guid: row.getResultByName("guid"),
          type: row.getResultByName("type"),
          newParentGuid: row.getResultByName("newParentGuid"),
          oldParentGuid: row.getResultByName("oldParentGuid"),
          newPosition: row.getResultByName("newPosition"),
          oldPosition: row.getResultByName("oldPosition"),
          urlHref: row.getResultByName("url"),
          grandParentGuid: row.getResultByName("grandParentGuid"),
          title: row.getResultByName("title"),
          frecency: row.getResultByName("frecency"),
          hidden: row.getResultByName("hidden"),
          visitCount: row.getResultByName("visit_count"),
          dateAdded: lazy.PlacesUtils.toDate(
            row.getResultByName("dateAdded")
          ).getTime(),
          lastVisitDate: lastVisitDate
            ? lazy.PlacesUtils.toDate(lastVisitDate).getTime()
            : null,
          tags: row.getResultByName("tags"),
        };
        this.noteItemMoved(info);
      }
    );
    if (this.signal.aborted) {
      throw new SyncedBookmarksMirror.InterruptedError(
        "Interrupted while recording observer notifications for moved items"
      );
    }

    lazy.MirrorLog.trace("Recording observer notifications for changed items");
    await this.db.execute(
      `SELECT b.id, b.guid, b.lastModified, b.type,
              IFNULL(b.title, '') AS newTitle,
              IFNULL(c.oldTitle, '') AS oldTitle,
              (SELECT h.url FROM moz_places h
               WHERE h.id = b.fk) AS newURL,
              (SELECT h.url FROM moz_places h
               WHERE h.id = c.oldPlaceId) AS oldURL,
              p.id AS parentId, p.guid AS parentGuid,
              c.keywordChanged,
              gp.guid AS grandParentGuid,
              (SELECT h.url FROM moz_places h WHERE h.id = b.fk) AS url
       FROM itemsChanged c
       JOIN moz_bookmarks b ON b.id = c.itemId
       JOIN moz_bookmarks p ON p.id = b.parent
       LEFT JOIN moz_bookmarks gp ON gp.id = p.parent
       ${this.orderBy("c.level", "b.parent", "b.position")}`,
      null,
      (row, cancel) => {
        if (this.signal.aborted) {
          cancel();
          return;
        }
        let info = {
          id: row.getResultByName("id"),
          guid: row.getResultByName("guid"),
          lastModified: row.getResultByName("lastModified"),
          type: row.getResultByName("type"),
          newTitle: row.getResultByName("newTitle"),
          oldTitle: row.getResultByName("oldTitle"),
          newURLHref: row.getResultByName("newURL"),
          oldURLHref: row.getResultByName("oldURL"),
          parentId: row.getResultByName("parentId"),
          parentGuid: row.getResultByName("parentGuid"),
          grandParentGuid: row.getResultByName("grandParentGuid"),
        };
        this.noteItemChanged(info);
        if (row.getResultByName("keywordChanged")) {
          this.shouldInvalidateKeywords = true;
        }
      }
    );
    if (this.signal.aborted) {
      throw new SyncedBookmarksMirror.InterruptedError(
        "Interrupted while recording observer notifications for changed items"
      );
    }
  }

  noteItemAdded(info) {
    this.placesEvents.push(
      new PlacesBookmarkAddition({
        id: info.id,
        parentId: info.parentId,
        index: info.position,
        url: info.urlHref || "",
        title: info.title,
        // Note that both the database and the legacy `onItem{Moved, Removed,
        // Changed}` notifications use microsecond timestamps, but
        // `PlacesBookmarkAddition` uses milliseconds.
        dateAdded: info.dateAdded / 1000,
        guid: info.guid,
        parentGuid: info.parentGuid,
        source: lazy.PlacesUtils.bookmarks.SOURCES.SYNC,
        itemType: info.type,
        isTagging: info.isTagging,
        tags: info.tags,
        frecency: info.frecency,
        hidden: info.hidden,
        visitCount: info.visitCount,
        lastVisitDate: info.lastVisitDate,
        targetFolderGuid: info.targetFolderGuid,
        targetFolderItemId: info.targetFolderItemId,
        targetFolderTitle: info.targetFolderTitle,
      })
    );
  }

  noteGuidChanged(info) {
    this.placesEvents.push(
      new PlacesBookmarkGuid({
        id: info.id,
        itemType: info.type,
        url: info.urlHref,
        guid: info.newGuid,
        parentGuid: info.parentGuid,
        lastModified: info.lastModified,
        source: lazy.PlacesUtils.bookmarks.SOURCES.SYNC,
        isTagging:
          info.parentGuid === lazy.PlacesUtils.bookmarks.tagsGuid ||
          info.grandParentGuid === lazy.PlacesUtils.bookmarks.tagsGuid,
      })
    );
  }

  noteItemMoved(info) {
    this.placesEvents.push(
      new PlacesBookmarkMoved({
        id: info.id,
        itemType: info.type,
        url: info.urlHref,
        title: info.title,
        guid: info.guid,
        parentGuid: info.newParentGuid,
        source: lazy.PlacesUtils.bookmarks.SOURCES.SYNC,
        index: info.newPosition,
        oldParentGuid: info.oldParentGuid,
        oldIndex: info.oldPosition,
        isTagging:
          info.newParentGuid === lazy.PlacesUtils.bookmarks.tagsGuid ||
          info.grandParentGuid === lazy.PlacesUtils.bookmarks.tagsGuid,
        tags: info.tags,
        frecency: info.frecency,
        hidden: info.hidden,
        visitCount: info.visitCount,
        dateAdded: info.dateAdded,
        lastVisitDate: info.lastVisitDate,
      })
    );
  }

  noteItemChanged(info) {
    if (info.oldTitle != info.newTitle) {
      this.placesEvents.push(
        new PlacesBookmarkTitle({
          id: info.id,
          itemType: info.type,
          url: info.urlHref,
          guid: info.guid,
          parentGuid: info.parentGuid,
          title: info.newTitle,
          lastModified: info.lastModified,
          source: lazy.PlacesUtils.bookmarks.SOURCES.SYNC,
          isTagging:
            info.parentGuid === lazy.PlacesUtils.bookmarks.tagsGuid ||
            info.grandParentGuid === lazy.PlacesUtils.bookmarks.tagsGuid,
        })
      );
    }
    if (info.oldURLHref != info.newURLHref) {
      this.placesEvents.push(
        new PlacesBookmarkUrl({
          id: info.id,
          itemType: info.type,
          url: info.newURLHref,
          guid: info.guid,
          parentGuid: info.parentGuid,
          lastModified: info.lastModified,
          source: lazy.PlacesUtils.bookmarks.SOURCES.SYNC,
          isTagging:
            info.parentGuid === lazy.PlacesUtils.bookmarks.tagsGuid ||
            info.grandParentGuid === lazy.PlacesUtils.bookmarks.tagsGuid,
        })
      );
    }
  }

  noteItemRemoved(info) {
    this.placesEvents.push(
      new PlacesBookmarkRemoved({
        id: info.id,
        parentId: info.parentId,
        index: info.position,
        url: info.urlHref || "",
        title: info.title,
        guid: info.guid,
        parentGuid: info.parentGuid,
        source: lazy.PlacesUtils.bookmarks.SOURCES.SYNC,
        itemType: info.type,
        isTagging: info.isUntagging,
        isDescendantRemoval: false,
      })
    );
  }

  notifyBookmarkObservers() {
    lazy.MirrorLog.trace("Notifying bookmark observers");

    if (this.placesEvents.length) {
      PlacesObservers.notifyListeners(this.placesEvents);
    }

    lazy.MirrorLog.trace("Notified bookmark observers");
  }
}

/**
 * Holds Sync metadata and the cleartext for a locally changed record. The
 * bookmarks engine inflates a Sync record from the cleartext, and updates the
 * `synced` property for successfully uploaded items.
 *
 * At the end of the sync, the engine writes the uploaded cleartext back to the
 * mirror, and passes the updated change record as part of the changeset to
 * `PlacesSyncUtils.bookmarks.pushChanges`.
 */
class BookmarkChangeRecord {
  constructor(syncChangeCounter, cleartext) {
    this.tombstone = cleartext.deleted === true;
    this.counter = syncChangeCounter;
    this.cleartext = cleartext;
    this.synced = false;
  }
}

function bagToNamedCounts(bag, names) {
  let counts = [];
  for (let name of names) {
    let count = bag.getProperty(name);
    if (count > 0) {
      counts.push({ name, count });
    }
  }
  return counts;
}

/**
 * Returns an `AbortSignal` that aborts if either `finalizeSignal` or
 * `interruptSignal` aborts. This is like `Promise.race`, but for
 * cancellations.
 *
 * @param  {AbortSignal} finalizeSignal
 * @param  {AbortSignal?} signal
 * @return {AbortSignal}
 */
function anyAborted(finalizeSignal, interruptSignal = null) {
  if (finalizeSignal.aborted || !interruptSignal) {
    // If the mirror was already finalized, or we don't have an interrupt
    // signal for this merge, just use the finalize signal.
    return finalizeSignal;
  }
  if (interruptSignal.aborted) {
    // If the merge was interrupted, return its already-aborted signal.
    return interruptSignal;
  }
  // Otherwise, we return a new signal that aborts if either the mirror is
  // finalized, or the merge is interrupted, whichever happens first.
  let controller = new AbortController();
  function onAbort() {
    finalizeSignal.removeEventListener("abort", onAbort);
    interruptSignal.removeEventListener("abort", onAbort);
    controller.abort();
  }
  finalizeSignal.addEventListener("abort", onAbort);
  interruptSignal.addEventListener("abort", onAbort);
  return controller.signal;
}

// Common unknown fields for places items
const COMMON_UNKNOWN_FIELDS = [
  "dateAdded",
  "hasDupe",
  "id",
  "modified",
  "parentid",
  "parentName",
  "type",
];

// In conclusion, this is why bookmark syncing is hard.
PK
!<�?�UDDmodules/TaggingService.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { PlacesUtils } from "resource://gre/modules/PlacesUtils.sys.mjs";

const TOPIC_SHUTDOWN = "places-shutdown";

/**
 * The Places Tagging Service
 */
export function TaggingService() {
  this.handlePlacesEvents = this.handlePlacesEvents.bind(this);

  // Observe bookmarks changes.
  PlacesUtils.observers.addListener(
    [
      "bookmark-added",
      "bookmark-removed",
      "bookmark-moved",
      "bookmark-title-changed",
    ],
    this.handlePlacesEvents
  );

  // Cleanup on shutdown.
  Services.obs.addObserver(this, TOPIC_SHUTDOWN);
}

TaggingService.prototype = {
  /**
   * Creates a tag container under the tags-root with the given name.
   *
   * @param aTagName
   *        the name for the new tag.
   * @param aSource
   *        a change source constant from nsINavBookmarksService::SOURCE_*.
   * @returns the id of the new tag container.
   */
  _createTag: function TS__createTag(aTagName, aSource) {
    var newFolderId = PlacesUtils.bookmarks.createFolder(
      PlacesUtils.tagsFolderId,
      aTagName,
      PlacesUtils.bookmarks.DEFAULT_INDEX,
      /* aGuid */ null,
      aSource
    );
    // Add the folder to our local cache, so we can avoid doing this in the
    // observer that would have to check itemType.
    this._tagFolders[newFolderId] = aTagName;

    return newFolderId;
  },

  /**
   * Checks whether the given uri is tagged with the given tag.
   *
   * @param [in] aURI
   *        url to check for
   * @param [in] aTagName
   *        the tag to check for
   * @returns the item id if the URI is tagged with the given tag, -1
   *          otherwise.
   */
  _getItemIdForTaggedURI: function TS__getItemIdForTaggedURI(aURI, aTagName) {
    var tagId = this._getItemIdForTag(aTagName);
    if (tagId == -1) {
      return -1;
    }
    // Using bookmarks service API for this would be a pain.
    // Until tags implementation becomes sane, go the query way.
    let db = PlacesUtils.history.DBConnection;
    let stmt = db.createStatement(
      `SELECT id FROM moz_bookmarks
       WHERE parent = :tag_id
       AND fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url)`
    );
    stmt.params.tag_id = tagId;
    stmt.params.page_url = aURI.spec;
    try {
      if (stmt.executeStep()) {
        return stmt.row.id;
      }
    } finally {
      stmt.finalize();
    }
    return -1;
  },

  /**
   * Returns the folder id for a tag, or -1 if not found.
   * @param [in] aTag
   *        string tag to search for
   * @returns integer id for the bookmark folder for the tag
   */
  _getItemIdForTag: function TS_getItemIdForTag(aTagName) {
    for (var i in this._tagFolders) {
      if (aTagName.toLowerCase() == this._tagFolders[i].toLowerCase()) {
        return parseInt(i);
      }
    }
    return -1;
  },
  /**
   * Makes a proper array of tag objects like  { id: number, name: string }.
   *
   * @param aTags
   *        Array of tags.  Entries can be tag names or concrete item id.
   * @param trim [optional]
   *        Whether to trim passed-in named tags. Defaults to false.
   * @return Array of tag objects like { id: number, name: string }.
   *
   * @throws Cr.NS_ERROR_INVALID_ARG if any element of the input array is not
   *         a valid tag.
   */
  _convertInputMixedTagsArray(aTags, trim = false) {
    // Handle sparse array with a .filter.
    return aTags
      .filter(tag => tag !== undefined)
      .map(idOrName => {
        let tag = {};
        if (typeof idOrName == "number" && this._tagFolders[idOrName]) {
          // This is a tag folder id.
          tag.id = idOrName;
          // We can't know the name at this point, since a previous tag could
          // want to change it.
          tag.__defineGetter__("name", () => this._tagFolders[tag.id]);
        } else if (
          typeof idOrName == "string" &&
          !!idOrName.length &&
          idOrName.length <= PlacesUtils.bookmarks.MAX_TAG_LENGTH
        ) {
          // This is a tag name.
          tag.name = trim ? idOrName.trim() : idOrName;
          // We can't know the id at this point, since a previous tag could
          // have created it.
          tag.__defineGetter__("id", () => this._getItemIdForTag(tag.name));
        } else {
          throw Components.Exception(
            "Invalid tag value",
            Cr.NS_ERROR_INVALID_ARG
          );
        }
        return tag;
      });
  },

  // nsITaggingService
  tagURI: function TS_tagURI(aURI, aTags, aSource) {
    if (!aURI || !aTags || !Array.isArray(aTags) || !aTags.length) {
      throw Components.Exception(
        "Invalid value for tags",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    // This also does some input validation.
    let tags = this._convertInputMixedTagsArray(aTags, true);

    for (let tag of tags) {
      if (tag.id == -1) {
        // Tag does not exist yet, create it.
        this._createTag(tag.name, aSource);
      }

      let itemId = this._getItemIdForTaggedURI(aURI, tag.name);
      if (itemId == -1) {
        // The provided URI is not yet tagged, add a tag for it.
        // Note that bookmarks under tag containers must have null titles.
        PlacesUtils.bookmarks.insertBookmark(
          tag.id,
          aURI,
          PlacesUtils.bookmarks.DEFAULT_INDEX,
          /* aTitle */ null,
          /* aGuid */ null,
          aSource
        );
      } else {
        // Otherwise, bump the tag's timestamp, so that we can increment the
        // sync change counter for all bookmarks with the URI.
        PlacesUtils.bookmarks.setItemLastModified(
          itemId,
          PlacesUtils.toPRTime(Date.now()),
          aSource
        );
      }

      // Try to preserve user's tag name casing.
      // Rename the tag container so the Places view matches the most-recent
      // user-typed value.
      if (PlacesUtils.bookmarks.getItemTitle(tag.id) != tag.name) {
        // this._tagFolders is updated by the bookmarks observer.
        PlacesUtils.bookmarks.setItemTitle(tag.id, tag.name, aSource);
      }
    }
  },

  /**
   * Removes the tag container from the tags root if the given tag is empty.
   *
   * @param aTagId
   *        the itemId of the tag element under the tags root
   * @param aSource
   *        a change source constant from nsINavBookmarksService::SOURCE_*
   */
  _removeTagIfEmpty: function TS__removeTagIfEmpty(aTagId, aSource) {
    let count = 0;
    let db = PlacesUtils.history.DBConnection;
    let stmt = db.createStatement(
      `SELECT count(*) AS count FROM moz_bookmarks
       WHERE parent = :tag_id`
    );
    stmt.params.tag_id = aTagId;
    try {
      if (stmt.executeStep()) {
        count = stmt.row.count;
      }
    } finally {
      stmt.finalize();
    }

    if (count == 0) {
      PlacesUtils.bookmarks.removeItem(aTagId, aSource);
    }
  },

  // nsITaggingService
  untagURI: function TS_untagURI(aURI, aTags, aSource) {
    if (!aURI || (aTags && (!Array.isArray(aTags) || !aTags.length))) {
      throw Components.Exception(
        "Invalid value for tags",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    if (!aTags) {
      // Passing null should clear all tags for aURI, see the IDL.
      // XXXmano: write a perf-sensitive version of this code path...
      aTags = this.getTagsForURI(aURI);
    }

    // This also does some input validation.
    let tags = this._convertInputMixedTagsArray(aTags);

    let isAnyTagNotTrimmed = tags.some(tag => /^\s|\s$/.test(tag.name));
    if (isAnyTagNotTrimmed) {
      throw Components.Exception(
        "At least one tag passed to untagURI was not trimmed",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    for (let tag of tags) {
      if (tag.id != -1) {
        // A tag could exist.
        let itemId = this._getItemIdForTaggedURI(aURI, tag.name);
        if (itemId != -1) {
          // There is a tagged item.
          PlacesUtils.bookmarks.removeItem(itemId, aSource);
        }
      }
    }
  },

  // nsITaggingService
  getTagsForURI: function TS_getTagsForURI(aURI) {
    if (!aURI) {
      throw Components.Exception("Invalid uri", Cr.NS_ERROR_INVALID_ARG);
    }

    let tags = [];
    let db = PlacesUtils.history.DBConnection;
    let stmt = db.createStatement(
      `SELECT t.id AS folderId
       FROM moz_bookmarks b
       JOIN moz_bookmarks t on t.id = b.parent
       WHERE b.fk = (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url) AND
       t.parent = :tags_root
       ORDER BY b.lastModified DESC, b.id DESC`
    );
    stmt.params.url = aURI.spec;
    stmt.params.tags_root = PlacesUtils.tagsFolderId;
    try {
      while (stmt.executeStep()) {
        try {
          tags.push(this._tagFolders[stmt.row.folderId]);
        } catch (ex) {}
      }
    } finally {
      stmt.finalize();
    }

    // sort the tag list
    tags.sort(function (a, b) {
      return a.toLowerCase().localeCompare(b.toLowerCase());
    });
    return tags;
  },

  __tagFolders: null,
  get _tagFolders() {
    if (!this.__tagFolders) {
      this.__tagFolders = [];

      let db = PlacesUtils.history.DBConnection;
      let stmt = db.createStatement(
        "SELECT id, title FROM moz_bookmarks WHERE parent = :tags_root "
      );
      stmt.params.tags_root = PlacesUtils.tagsFolderId;
      try {
        while (stmt.executeStep()) {
          this.__tagFolders[stmt.row.id] = stmt.row.title;
        }
      } finally {
        stmt.finalize();
      }
    }

    return this.__tagFolders;
  },

  // nsIObserver
  observe: function TS_observe(aSubject, aTopic) {
    if (aTopic == TOPIC_SHUTDOWN) {
      PlacesUtils.observers.removeListener(
        [
          "bookmark-added",
          "bookmark-removed",
          "bookmark-moved",
          "bookmark-title-changed",
        ],
        this.handlePlacesEvents
      );
      Services.obs.removeObserver(this, TOPIC_SHUTDOWN);
    }
  },

  /**
   * If the only bookmark items associated with aURI are contained in tag
   * folders, returns the IDs of those items.  This can be the case if
   * the URI was bookmarked and tagged at some point, but the bookmark was
   * removed, leaving only the bookmark items in tag folders.  If the URI is
   * either properly bookmarked or not tagged just returns and empty array.
   *
   * @param   aURI
   *          A URI (string) that may or may not be bookmarked
   * @returns an array of item ids
   */
  _getTaggedItemIdsIfUnbookmarkedURI:
    function TS__getTaggedItemIdsIfUnbookmarkedURI(url) {
      var itemIds = [];
      var isBookmarked = false;

      // Using bookmarks service API for this would be a pain.
      // Until tags implementation becomes sane, go the query way.
      let db = PlacesUtils.history.DBConnection;
      let stmt = db.createStatement(
        `SELECT id, parent
       FROM moz_bookmarks
       WHERE fk = (SELECT id FROM moz_places WHERE url_hash = hash(:page_url) AND url = :page_url)`
      );
      stmt.params.page_url = url;
      try {
        while (stmt.executeStep() && !isBookmarked) {
          if (this._tagFolders[stmt.row.parent]) {
            // This is a tag entry.
            itemIds.push(stmt.row.id);
          } else {
            // This is a real bookmark, so the bookmarked URI is not an orphan.
            isBookmarked = true;
          }
        }
      } finally {
        stmt.finalize();
      }

      return isBookmarked ? [] : itemIds;
    },

  handlePlacesEvents(events) {
    for (let event of events) {
      switch (event.type) {
        case "bookmark-added":
          if (
            !event.isTagging ||
            event.itemType != PlacesUtils.bookmarks.TYPE_FOLDER
          ) {
            continue;
          }

          this._tagFolders[event.id] = event.title;
          break;
        case "bookmark-removed":
          // Item is a tag folder.
          if (
            event.parentId == PlacesUtils.tagsFolderId &&
            this._tagFolders[event.id]
          ) {
            delete this._tagFolders[event.id];
            break;
          }

          Services.tm.dispatchToMainThread(() => {
            if (event.url && !this._tagFolders[event.parentId]) {
              // Item is a bookmark that was removed from a non-tag folder.
              // If the only bookmark items now associated with the bookmark's URI are
              // contained in tag folders, the URI is no longer properly bookmarked, so
              // untag it.
              let itemIds = this._getTaggedItemIdsIfUnbookmarkedURI(event.url);
              for (let i = 0; i < itemIds.length; i++) {
                try {
                  PlacesUtils.bookmarks.removeItem(itemIds[i], event.source);
                } catch (ex) {}
              }
            } else if (event.url && this._tagFolders[event.parentId]) {
              // Item is a tag entry.  If this was the last entry for this tag, remove it.
              this._removeTagIfEmpty(event.parentId, event.source);
            }
          });
          break;
        case "bookmark-moved":
          if (
            this._tagFolders[event.id] &&
            PlacesUtils.bookmarks.tagsGuid === event.oldParentGuid &&
            PlacesUtils.bookmarks.tagsGuid !== event.parentGuid
          ) {
            delete this._tagFolders[event.id];
          }
          break;
        case "bookmark-title-changed":
          if (this._tagFolders[event.id]) {
            this._tagFolders[event.id] = event.title;
          }
          break;
      }
    }
  },

  // nsISupports

  classID: Components.ID("{bbc23860-2553-479d-8b78-94d9038334f7}"),

  QueryInterface: ChromeUtils.generateQI(["nsITaggingService", "nsIObserver"]),
};

/**
 * Class tracking a single tag autocomplete search.
 */
class TagSearch {
  constructor(searchString, autocompleteSearch, listener) {
    // We need a result regardless of having matches.
    this._result = Cc[
      "@mozilla.org/autocomplete/simple-result;1"
    ].createInstance(Ci.nsIAutoCompleteSimpleResult);
    this._result.setDefaultIndex(0);
    this._result.setSearchString(searchString);

    this._autocompleteSearch = autocompleteSearch;
    this._listener = listener;
  }

  async start() {
    if (this._canceled) {
      throw new Error("Can't restart a canceled search");
    }

    let searchString = this._result.searchString;
    // Only search on characters for the last tag.
    let index = Math.max(
      searchString.lastIndexOf(","),
      searchString.lastIndexOf(";")
    );
    let before = "";
    if (index != -1) {
      before = searchString.slice(0, index + 1);
      searchString = searchString.slice(index + 1);
      // skip past whitespace
      var m = searchString.match(/\s+/);
      if (m) {
        before += m[0];
        searchString = searchString.slice(m[0].length);
      }
    }

    if (searchString.length) {
      let tags = await PlacesUtils.bookmarks.fetchTags();
      if (this._canceled) {
        return;
      }

      let lcSearchString = searchString.toLowerCase();
      let matchingTags = tags
        .filter(t => t.name.toLowerCase().startsWith(lcSearchString))
        .map(t => t.name);

      for (let i = 0; i < matchingTags.length; ++i) {
        let tag = matchingTags[i];
        // For each match, prepend what the user has typed so far.
        this._result.appendMatch(before + tag, null, null, null, null, tag);
        // In case of many tags, notify once every 10.
        if (i % 10 == 0) {
          this._notifyResult(true);
          // yield to avoid monopolizing the main-thread
          await new Promise(resolve =>
            Services.tm.dispatchToMainThread(resolve)
          );
          if (this._canceled) {
            return;
          }
        }
      }
    }

    // Search is done.
    this._notifyResult(false);
  }

  cancel() {
    this._canceled = true;
  }

  _notifyResult(searchOngoing) {
    let resultCode = this._result.matchCount
      ? "RESULT_SUCCESS"
      : "RESULT_NOMATCH";
    if (searchOngoing) {
      resultCode += "_ONGOING";
    }
    this._result.setSearchResult(Ci.nsIAutoCompleteResult[resultCode]);
    this._listener.onSearchResult(this._autocompleteSearch, this._result);
  }
}

// Implements nsIAutoCompleteSearch
export function TagAutoCompleteSearch() {}

TagAutoCompleteSearch.prototype = {
  /*
   * Search for a given string and notify a listener of the result.
   *
   * @param searchString - The string to search for
   * @param searchParam - An extra parameter
   * @param previousResult - A previous result to use for faster searching
   * @param listener - A listener to notify when the search is complete
   */
  startSearch(searchString, searchParam, previousResult, listener) {
    if (this._search) {
      this._search.cancel();
    }
    this._search = new TagSearch(searchString, this, listener);
    this._search.start().catch(console.error);
  },

  /**
   * Stop an asynchronous search that is in progress
   */
  stopSearch() {
    this._search.cancel();
    this._search = null;
  },

  classID: Components.ID("{1dcc23b0-d4cb-11dc-9ad6-479d56d89593}"),
  QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteSearch"]),
};
PK
!<u�c�vv#modules/TelemetryController.sys.mjs/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This module chooses the correct telemetry controller module to load
 * based on the process:
 *
 * - TelemetryControllerParent is loaded only in the parent process, and
 *   contains code specific to the parent.
 * - TelemetryControllerContent is loaded only in content processes, and
 *   contains code specific to them.
 *
 * Both the parent and the content modules load TelemetryControllerBase,
 * which contains code which is common to all processes.
 *
 * This division is important for content process memory usage and
 * startup time. The parent-specific code occupies tens of KB of memory
 * which, multiplied by the number of content processes we have, adds up
 * fast.
 */

// We can't use Services.appinfo here because tests stub out the appinfo
// service, and if we touch Services.appinfo now, the built-in version
// will be cached in place of the stub.
const isParentProcess =
  // eslint-disable-next-line mozilla/use-services
  Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).processType ===
  Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;

export var TelemetryController;
if (isParentProcess) {
  ({ TelemetryController } = ChromeUtils.importESModule(
    "resource://gre/modules/TelemetryControllerParent.sys.mjs"
  ));
} else {
  ({ TelemetryController } = ChromeUtils.importESModule(
    "resource://gre/modules/TelemetryControllerContent.sys.mjs"
  ));
}
PK
!<�^�hh'modules/TelemetryControllerBase.sys.mjs/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { Log } from "resource://gre/modules/Log.sys.mjs";

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetryController::";

const PREF_BRANCH_LOG = "toolkit.telemetry.log.";
const PREF_LOG_LEVEL = "toolkit.telemetry.log.level";
const PREF_LOG_DUMP = "toolkit.telemetry.log.dump";

const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";

const Preferences = Object.freeze({
  OverridePreRelease: "toolkit.telemetry.testing.overridePreRelease",
  Unified: "toolkit.telemetry.unified",
});

/**
 * Setup Telemetry logging. This function also gets called when loggin related
 * preferences change.
 */
var gLogger = null;
var gPrefixLogger = null;
var gLogAppenderDump = null;

export var TelemetryControllerBase = Object.freeze({
  // Whether the FHR/Telemetry unification features are enabled.
  // Changing this pref requires a restart.
  IS_UNIFIED_TELEMETRY: Services.prefs.getBoolPref(Preferences.Unified, false),

  Preferences,

  /**
   * Returns the state of the Telemetry enabled preference, making sure
   * it correctly evaluates to a boolean type.
   */
  get isTelemetryEnabled() {
    return Services.prefs.getBoolPref(PREF_TELEMETRY_ENABLED, false) === true;
  },

  get log() {
    if (!gPrefixLogger) {
      gPrefixLogger = Log.repository.getLoggerWithMessagePrefix(
        LOGGER_NAME,
        LOGGER_PREFIX
      );
    }
    return gPrefixLogger;
  },

  configureLogging() {
    if (!gLogger) {
      gLogger = Log.repository.getLogger(LOGGER_NAME);

      // Log messages need to go to the browser console.
      let consoleAppender = new Log.ConsoleAppender(new Log.BasicFormatter());
      gLogger.addAppender(consoleAppender);

      Services.prefs.addObserver(PREF_BRANCH_LOG, this.configureLogging);
    }

    // Make sure the logger keeps up with the logging level preference.
    gLogger.level =
      Log.Level[Services.prefs.getStringPref(PREF_LOG_LEVEL, "Warn")];

    // If enabled in the preferences, add a dump appender.
    let logDumping = Services.prefs.getBoolPref(PREF_LOG_DUMP, false);
    if (logDumping != !!gLogAppenderDump) {
      if (logDumping) {
        gLogAppenderDump = new Log.DumpAppender(new Log.BasicFormatter());
        gLogger.addAppender(gLogAppenderDump);
      } else {
        gLogger.removeAppender(gLogAppenderDump);
        gLogAppenderDump = null;
      }
    }
  },

  /**
   * Set the Telemetry core recording flag for Unified Telemetry.
   */
  setTelemetryRecordingFlags() {
    // Enable extended Telemetry on pre-release channels and disable it
    // on Release/ESR.
    let prereleaseChannels = [
      "nightly",
      "nightly-autoland",
      "nightly-try",
      "aurora",
      "beta",
    ];
    if (!AppConstants.MOZILLA_OFFICIAL) {
      // Turn extended telemetry for local developer builds.
      prereleaseChannels.push("default");
    }
    const isPrereleaseChannel = prereleaseChannels.includes(
      AppConstants.MOZ_UPDATE_CHANNEL
    );
    const isReleaseCandidateOnBeta =
      AppConstants.MOZ_UPDATE_CHANNEL === "release" &&
      Services.prefs.getCharPref("app.update.channel", null) === "beta";
    Services.telemetry.canRecordBase = true;
    Services.telemetry.canRecordExtended =
      isPrereleaseChannel ||
      isReleaseCandidateOnBeta ||
      Services.prefs.getBoolPref(this.Preferences.OverridePreRelease, false);
  },

  /**
   * Perform telemetry initialization for either chrome or content process.
   * @return {Boolean} True if Telemetry is allowed to record at least base (FHR) data,
   *                   false otherwise.
   */
  enableTelemetryRecording: function enableTelemetryRecording() {
    // Configure base Telemetry recording.
    // Unified Telemetry makes it opt-out. If extended Telemetry is enabled, base recording
    // is always on as well.
    if (this.IS_UNIFIED_TELEMETRY) {
      this.setTelemetryRecordingFlags();
    } else {
      // We're not on unified Telemetry, stick to the old behaviour for
      // supporting Fennec.
      Services.telemetry.canRecordBase = Services.telemetry.canRecordExtended =
        this.isTelemetryEnabled;
    }

    this.log.config(
      "enableTelemetryRecording - canRecordBase:" +
        Services.telemetry.canRecordBase +
        ", canRecordExtended: " +
        Services.telemetry.canRecordExtended
    );

    return Services.telemetry.canRecordBase;
  },
});
PK
!<ևQ��	�	*modules/TelemetryControllerContent.sys.mjs/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { TelemetryControllerBase } from "resource://gre/modules/TelemetryControllerBase.sys.mjs";

export var TelemetryController = Object.freeze({
  /**
   * Used only for testing purposes.
   */
  testInitLogging() {
    TelemetryControllerBase.configureLogging();
  },

  /**
   * Used only for testing purposes.
   */
  testSetupContent() {
    return Impl.setupContentTelemetry(true);
  },

  /**
   * Send a notification.
   */
  observe(aSubject, aTopic, aData) {
    return Impl.observe(aSubject, aTopic, aData);
  },

  QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
});

var Impl = {
  // This is true when running in the test infrastructure.
  _testMode: false,

  get _log() {
    return TelemetryControllerBase.log;
  },

  /**
   * This triggers basic telemetry initialization for content processes.
   * @param {Boolean} [testing=false] True if we are in test mode, false otherwise.
   */
  setupContentTelemetry(testing = false) {
    this._testMode = testing;

    // The thumbnail service also runs in a content process, even with e10s off.
    // We need to check if e10s is on so we don't submit child payloads for it.
    // We still need xpcshell child tests to work, so we skip this if test mode is enabled.
    if (testing || Services.appinfo.browserTabsRemoteAutostart) {
      // We call |enableTelemetryRecording| here to make sure that Telemetry.canRecord* flags
      // are in sync between chrome and content processes.
      if (!TelemetryControllerBase.enableTelemetryRecording()) {
        this._log.trace(
          "setupContentTelemetry - Content process recording disabled."
        );
        return;
      }
    }
    Services.telemetry.earlyInit();

    let options = testing ? { timeout: 0 } : {};
    ChromeUtils.idleDispatch(() => Services.telemetry.delayedInit(), options);
  },

  /**
   * This observer drives telemetry.
   */
  observe(aSubject, aTopic) {
    if (aTopic == "content-process-ready-for-script") {
      TelemetryControllerBase.configureLogging();

      this._log.trace(`observe - ${aTopic} notified.`);

      this.setupContentTelemetry();
    }
  },
};

// Used by service registration, which requires a callable function.
export function getTelemetryController() {
  return TelemetryController;
}
PK
!<�R��n�n�)modules/TelemetryControllerParent.sys.mjs/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { AsyncShutdown } from "resource://gre/modules/AsyncShutdown.sys.mjs";
import { DeferredTask } from "resource://gre/modules/DeferredTask.sys.mjs";

import { TelemetryUtils } from "resource://gre/modules/TelemetryUtils.sys.mjs";
import { TelemetryControllerBase } from "resource://gre/modules/TelemetryControllerBase.sys.mjs";

const Utils = TelemetryUtils;

const PING_FORMAT_VERSION = 4;

// Delay before intializing telemetry (ms)
const TELEMETRY_DELAY =
  Services.prefs.getIntPref("toolkit.telemetry.initDelay", 60) * 1000;
// Delay before initializing telemetry if we're testing (ms)
const TELEMETRY_TEST_DELAY = 1;

// How long to wait (ms) before sending the new profile ping on the first
// run of a new profile.
const NEWPROFILE_PING_DEFAULT_DELAY = 30 * 60 * 1000;

// Ping types.
const PING_TYPE_MAIN = "main";
const PING_TYPE_DELETION_REQUEST = "deletion-request";
const PING_TYPE_UNINSTALL = "uninstall";

// Session ping reasons.
const REASON_GATHER_PAYLOAD = "gather-payload";
const REASON_GATHER_SUBSESSION_PAYLOAD = "gather-subsession-payload";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ClientID: "resource://gre/modules/ClientID.sys.mjs",
  CoveragePing: "resource://gre/modules/CoveragePing.sys.mjs",
  TelemetryArchive: "resource://gre/modules/TelemetryArchive.sys.mjs",
  TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
  TelemetryEventPing: "resource://gre/modules/EventPing.sys.mjs",
  TelemetryHealthPing: "resource://gre/modules/HealthPing.sys.mjs",
  TelemetryModules: "resource://gre/modules/ModulesPing.sys.mjs",
  TelemetryReportingPolicy:
    "resource://gre/modules/TelemetryReportingPolicy.sys.mjs",
  TelemetrySend: "resource://gre/modules/TelemetrySend.sys.mjs",
  TelemetrySession: "resource://gre/modules/TelemetrySession.sys.mjs",
  TelemetryStorage: "resource://gre/modules/TelemetryStorage.sys.mjs",
  TelemetryUntrustedModulesPing:
    "resource://gre/modules/UntrustedModulesPing.sys.mjs",
  UninstallPing: "resource://gre/modules/UninstallPing.sys.mjs",
  UpdatePing: "resource://gre/modules/UpdatePing.sys.mjs",
  jwcrypto: "resource://services-crypto/jwcrypto.sys.mjs",
});

if (
  AppConstants.platform === "win" &&
  AppConstants.MOZ_APP_NAME !== "thunderbird"
) {
  ChromeUtils.defineESModuleGetters(lazy, {
    // eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
    BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.sys.mjs",
  });
}

/**
 * This is a policy object used to override behavior for testing.
 */
export var Policy = {
  now: () => new Date(),
  generatePingId: () => Utils.generateUUID(),
  getCachedClientID: () => lazy.ClientID.getCachedClientID(),
};

export var TelemetryController = Object.freeze({
  /**
   * Used only for testing purposes.
   */
  testInitLogging() {
    TelemetryControllerBase.configureLogging();
  },

  /**
   * Used only for testing purposes.
   */
  testReset() {
    return Impl.reset();
  },

  /**
   * Used only for testing purposes.
   */
  testSetup() {
    return Impl.setupTelemetry(true);
  },

  /**
   * Used only for testing purposes.
   */
  testShutdown() {
    return Impl.shutdown();
  },

  /**
   * Used only for testing purposes.
   */
  testPromiseJsProbeRegistration() {
    return Promise.resolve(Impl._probeRegistrationPromise);
  },

  /**
   * Register 'dynamic builtin' probes from the JSON definition files.
   * This is needed to support adding new probes in developer builds
   * without rebuilding the whole codebase.
   *
   * This is not meant to be used outside of local developer builds.
   */
  testRegisterJsProbes() {
    return Impl.registerJsProbes();
  },

  /**
   * Used only for testing purposes.
   */
  testPromiseDeletionRequestPingSubmitted() {
    return Promise.resolve(Impl._deletionRequestPingSubmittedPromise);
  },

  /**
   * Send a notification.
   */
  observe(aSubject, aTopic, aData) {
    return Impl.observe(aSubject, aTopic, aData);
  },

  /**
   * Submit ping payloads to Telemetry. This will assemble a complete ping, adding
   * environment data, client id and some general info.
   * Depending on configuration, the ping will be sent to the server (immediately or later)
   * and archived locally.
   *
   * To identify the different pings and to be able to query them pings have a type.
   * A type is a string identifier that should be unique to the type ping that is being submitted,
   * it should only contain alphanumeric characters and '-' for separation, i.e. satisfy:
   * /^[a-z0-9][a-z0-9-]+[a-z0-9]$/i
   *
   * @param {String} aType The type of the ping.
   * @param {Object} aPayload The actual data payload for the ping.
   * @param {Object} [aOptions] Options object.
   * @param {Boolean} [aOptions.addClientId=false] true if the ping should contain the client
   *                  id, false otherwise.
   * @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the
   *                  environment data.
   * @param {Object}  [aOptions.overrideEnvironment=null] set to override the environment data.
   * @param {Boolean} [aOptions.usePingSender=false] if true, send the ping using the PingSender.
   * @param {String} [aOptions.overrideClientId=undefined] if set, override the
   *                 client id to the provided value. Implies aOptions.addClientId=true.
   * @param {String} [aOptions.overrideProfileGroupId=undefined] if set, override the
   *                 profile group id to the provided value. Implies aOptions.addClientId=true.
   * @returns {Promise} Test-only - a promise that resolves with the ping id once the ping is stored or sent.
   */
  submitExternalPing(aType, aPayload, aOptions = {}) {
    aOptions.addClientId = aOptions.addClientId || false;
    aOptions.addEnvironment = aOptions.addEnvironment || false;
    aOptions.usePingSender = aOptions.usePingSender || false;

    return Impl.submitExternalPing(aType, aPayload, aOptions);
  },

  /**
   * Get the current session ping data as it would be sent out or stored.
   *
   * @param {bool} aSubsession Whether to get subsession data. Optional, defaults to false.
   * @return {object} The current ping data if Telemetry is enabled, null otherwise.
   */
  getCurrentPingData(aSubsession = false) {
    return Impl.getCurrentPingData(aSubsession);
  },

  /**
   * Save a ping to disk.
   *
   * @param {String} aType The type of the ping.
   * @param {Object} aPayload The actual data payload for the ping.
   * @param {Object} [aOptions] Options object.
   * @param {Boolean} [aOptions.addClientId=false] true if the ping should contain the client
   *                  id, false otherwise.
   * @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the
   *                  environment data.
   * @param {Boolean} [aOptions.overwrite=false] true overwrites a ping with the same name,
   *                  if found.
   * @param {Object}  [aOptions.overrideEnvironment=null] set to override the environment data.
   * @param {String} [aOptions.overrideClientId=undefined] if set, override the
   *                 client id to the provided value. Implies aOptions.addClientId=true.
   * @param {String} [aOptions.overrideProfileGroupId=undefined] if set, override the
   *                 profile group id to the provided value. Implies aOptions.addClientId=true.
   *
   * @returns {Promise} A promise that resolves with the ping id when the ping is saved to
   *                    disk.
   */
  addPendingPing(aType, aPayload, aOptions = {}) {
    let options = aOptions;
    options.addClientId = aOptions.addClientId || false;
    options.addEnvironment = aOptions.addEnvironment || false;
    options.overwrite = aOptions.overwrite || false;

    return Impl.addPendingPing(aType, aPayload, options);
  },

  /**
   * Check if we have an aborted-session ping from a previous session.
   * If so, submit and then remove it.
   *
   * @return {Promise} Promise that is resolved when the ping is saved.
   */
  checkAbortedSessionPing() {
    return Impl.checkAbortedSessionPing();
  },

  /**
   * Save an aborted-session ping to disk without adding it to the pending pings.
   *
   * @param {Object} aPayload The ping payload data.
   * @return {Promise} Promise that is resolved when the ping is saved.
   */
  saveAbortedSessionPing(aPayload) {
    return Impl.saveAbortedSessionPing(aPayload);
  },

  /**
   * Remove the aborted-session ping if any exists.
   *
   * @return {Promise} Promise that is resolved when the ping was removed.
   */
  removeAbortedSessionPing() {
    return Impl.removeAbortedSessionPing();
  },

  /**
   * Create an uninstall ping and write it to disk, replacing any already present.
   * This is stored independently from other pings, and only read by
   * the Windows uninstaller.
   *
   * WINDOWS ONLY, does nothing and resolves immediately on other platforms.
   *
   * @return {Promise} Resolved when the ping has been saved.
   */
  saveUninstallPing() {
    return Impl.saveUninstallPing();
  },

  /**
   * Allows the sync ping to tell the controller that it is initializing, so
   * should be included in the orderly shutdown process.
   *
   * @param {Function} aFnShutdown The function to call as telemetry shuts down.

   */
  registerSyncPingShutdown(afnShutdown) {
    Impl.registerSyncPingShutdown(afnShutdown);
  },

  /**
   * Allows waiting for TelemetryControllers delayed initialization to complete.
   * The returned promise is guaranteed to resolve before TelemetryController is shutting down.
   * @return {Promise} Resolved when delayed TelemetryController initialization completed.
   */
  promiseInitialized() {
    return Impl.promiseInitialized();
  },

  /**
   * Allows to trigger TelemetryControllers delayed initialization now and waiting for its completion.
   * The returned promise is guaranteed to resolve before TelemetryController is shutting down.
   * @return {Promise} Resolved when delayed TelemetryController initialization completed.
   */
  ensureInitialized() {
    return Impl.ensureInitialized();
  },
});

var Impl = {
  _initialized: false,
  _initStarted: false, // Whether we started setting up TelemetryController.
  _shuttingDown: false, // Whether the browser is shutting down.
  _shutDown: false, // Whether the browser has shut down.
  _logger: null,
  _prevValues: {},
  // The previous build ID, if this is the first run with a new build.
  // Undefined if this is not the first run, or the previous build ID is unknown.
  _previousBuildID: undefined,
  _clientID: null,
  _profileGroupID: null,
  // A task performing delayed initialization
  _delayedInitTask: null,
  // The deferred promise resolved when the initialization task completes.
  _delayedInitTaskDeferred: null,

  // This is a public barrier Telemetry clients can use to add blockers to the shutdown
  // of TelemetryController.
  // After this barrier, clients can not submit Telemetry pings anymore.
  _shutdownBarrier: new AsyncShutdown.Barrier(
    "TelemetryController: Waiting for clients."
  ),
  // This state is included in the async shutdown annotation for crash pings and reports.
  _shutdownState: "Shutdown not started.",
  // This is a private barrier blocked by pending async ping activity (sending & saving).
  _connectionsBarrier: new AsyncShutdown.Barrier(
    "TelemetryController: Waiting for pending ping activity"
  ),
  // This is true when running in the test infrastructure.
  _testMode: false,
  // The task performing the delayed sending of the "new-profile" ping.
  _delayedNewPingTask: null,
  // The promise used to wait for the JS probe registration (dynamic builtin).
  _probeRegistrationPromise: null,
  // The promise of any outstanding task sending the "deletion-request" ping.
  _deletionRequestPingSubmittedPromise: null,
  // A function to shutdown the sync/fxa ping, or null if that ping has not
  // self-initialized.
  _fnSyncPingShutdown: null,

  get _log() {
    return TelemetryControllerBase.log;
  },

  /**
   * Get the data for the "application" section of the ping.
   */
  _getApplicationSection() {
    // Querying architecture and update channel can throw. Make sure to recover and null
    // those fields.
    let arch = null;
    try {
      arch = Services.sysinfo.get("arch");
    } catch (e) {
      this._log.trace(
        "_getApplicationSection - Unable to get system architecture.",
        e
      );
    }

    let updateChannel = null;
    try {
      updateChannel = Utils.getUpdateChannel();
    } catch (e) {
      this._log.trace(
        "_getApplicationSection - Unable to get update channel.",
        e
      );
    }

    return {
      architecture: arch,
      buildId: Services.appinfo.appBuildID,
      name: Services.appinfo.name,
      version: Services.appinfo.version,
      displayVersion: AppConstants.MOZ_APP_VERSION_DISPLAY,
      vendor: Services.appinfo.vendor,
      platformVersion: Services.appinfo.platformVersion,
      xpcomAbi: Services.appinfo.XPCOMABI,
      channel: updateChannel,
    };
  },

  /**
   * Assemble a complete ping following the common ping format specification.
   *
   * @param {String} aType The type of the ping.
   * @param {Object} aPayload The actual data payload for the ping.
   * @param {Object} aOptions Options object.
   * @param {Boolean} aOptions.addClientId true if the ping should contain the client
   *                  id, false otherwise.
   * @param {Boolean} aOptions.addEnvironment true if the ping should contain the
   *                  environment data.
   * @param {Object}  [aOptions.overrideEnvironment=null] set to override the environment data.
   * @param {String} [aOptions.overrideClientId=undefined] if set, override the
   *                 client id to the provided value. Implies aOptions.addClientId=true.
   * @param {String} [aOptions.overrideProfileGroupId=undefined] if set, override the
   *                 profile group id to the provided value. Implies aOptions.addClientId=true.
   * @param {Boolean} [aOptions.useEncryption=false] if true, encrypt data client-side before sending.
   * @param {Object}  [aOptions.publicKey=null] the public key to use if encryption is enabled (JSON Web Key).
   * @param {String}  [aOptions.encryptionKeyId=null] the public key ID to use if encryption is enabled.
   * @param {String}  [aOptions.studyName=null] the study name to use.
   * @param {String}  [aOptions.schemaName=null] the schema name to use if encryption is enabled.
   * @param {String}  [aOptions.schemaNamespace=null] the schema namespace to use if encryption is enabled.
   * @param {String}  [aOptions.schemaVersion=null] the schema version to use if encryption is enabled.
   * @param {Boolean} [aOptions.addPioneerId=false] true if the ping should contain the Pioneer id, false otherwise.
   * @param {Boolean} [aOptions.overridePioneerId=undefined] if set, override the
   *                  pioneer id to the provided value. Only works if aOptions.addPioneerId=true.
   * @returns {Object} An object that contains the assembled ping data.
   */
  assemblePing: function assemblePing(aType, aPayload, aOptions = {}) {
    this._log.trace(
      "assemblePing - Type " + aType + ", aOptions " + JSON.stringify(aOptions)
    );

    // Clone the payload data so we don't race against unexpected changes in subobjects that are
    // still referenced by other code.
    // We can't trust all callers to do this properly on their own.
    let payload = Cu.cloneInto(aPayload, {});

    // Fill the common ping fields.
    let pingData = {
      type: aType,
      id: Policy.generatePingId(),
      creationDate: Policy.now().toISOString(),
      version: PING_FORMAT_VERSION,
      application: this._getApplicationSection(),
      payload,
    };

    if (
      aOptions.addClientId ||
      aOptions.overrideClientId ||
      aOptions.overrideProfileGroupId
    ) {
      pingData.clientId = aOptions.overrideClientId ?? this._clientID;
      pingData.profileGroupId =
        aOptions.overrideProfileGroupId ?? this._profileGroupID;
    }

    if (aOptions.addEnvironment) {
      pingData.environment =
        aOptions.overrideEnvironment ||
        lazy.TelemetryEnvironment.currentEnvironment;
    }

    return pingData;
  },

  /**
   * Track any pending ping send and save tasks through the promise passed here.
   * This is needed to block shutdown on any outstanding ping activity.
   */
  _trackPendingPingTask(aPromise) {
    this._connectionsBarrier.client.addBlocker(
      "Waiting for ping task",
      aPromise
    );
  },

  /**
   * Internal function to assemble a complete ping, adding environment data, client id
   * and some general info. This waits on the client id to be loaded/generated if it's
   * not yet available. Note that this function is synchronous unless we need to load
   * the client id.
   * Depending on configuration, the ping will be sent to the server (immediately or later)
   * and archived locally.
   *
   * @param {String} aType The type of the ping.
   * @param {Object} aPayload The actual data payload for the ping.
   * @param {Object} [aOptions] Options object.
   * @param {Boolean} [aOptions.addClientId=false] true if the ping should contain the client
   *                  id, false otherwise.
   * @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the
   *                  environment data.
   * @param {Object}  [aOptions.overrideEnvironment=null] set to override the environment data.
   * @param {Boolean} [aOptions.usePingSender=false] if true, send the ping using the PingSender.
   * @param {Boolean} [aOptions.useEncryption=false] if true, encrypt data client-side before sending.
   * @param {Object}  [aOptions.publicKey=null] the public key to use if encryption is enabled (JSON Web Key).
   * @param {String}  [aOptions.encryptionKeyId=null] the public key ID to use if encryption is enabled.
   * @param {String}  [aOptions.studyName=null] the study name to use.
   * @param {String}  [aOptions.schemaName=null] the schema name to use if encryption is enabled.
   * @param {String}  [aOptions.schemaNamespace=null] the schema namespace to use if encryption is enabled.
   * @param {String}  [aOptions.schemaVersion=null] the schema version to use if encryption is enabled.
   * @param {Boolean} [aOptions.addPioneerId=false] true if the ping should contain the Pioneer id, false otherwise.
   * @param {Boolean} [aOptions.overridePioneerId=undefined] if set, override the
   *                  pioneer id to the provided value. Only works if aOptions.addPioneerId=true.
   * @param {String} [aOptions.overrideClientId=undefined] if set, override the
   *                 client id to the provided value. Implies aOptions.addClientId=true.
   * @param {String} [aOptions.overrideProfileGroupId=undefined] if set, override the
   *                 profile group id to the provided value. Implies aOptions.addClientId=true.
   * @returns {Promise} Test-only - a promise that is resolved with the ping id once the ping is stored or sent.
   */
  async _submitPingLogic(aType, aPayload, aOptions) {
    // Make sure to have a clientId if we need one. This cover the case of submitting
    // a ping early during startup, before Telemetry is initialized, if no client id was
    // cached.
    let needsIdentifiers =
      aOptions.addClientId ||
      aOptions.overrideClientId ||
      aOptions.overrideProfileGroupId;
    let hasClientId = aOptions.overrideClientId ?? this._clientID;
    let hasProfileGroupId =
      aOptions.overrideProfileGroupId ?? this._profileGroupID;

    if (needsIdentifiers && !(hasClientId && hasProfileGroupId)) {
      this._log.trace(
        "_submitPingLogic - Waiting on client id or profile group id"
      );
      Services.telemetry
        .getHistogramById("TELEMETRY_PING_SUBMISSION_WAITING_CLIENTID")
        .add();
      // We can safely call |getClientID| here and during initialization: we would still
      // spawn and return one single loading task.
      this._clientID = await lazy.ClientID.getClientID();
      this._profileGroupID = await lazy.ClientID.getProfileGroupID();
    }

    let pingData = this.assemblePing(aType, aPayload, aOptions);
    this._log.trace("submitExternalPing - ping assembled, id: " + pingData.id);

    if (aOptions.useEncryption === true) {
      try {
        if (!aOptions.publicKey) {
          throw new Error("Public key is required when using encryption.");
        }

        if (
          !(
            aOptions.schemaName &&
            aOptions.schemaNamespace &&
            aOptions.schemaVersion
          )
        ) {
          throw new Error(
            "Schema name, namespace, and version are required when using encryption."
          );
        }

        const payload = {};
        payload.encryptedData = await lazy.jwcrypto.generateJWE(
          aOptions.publicKey,
          new TextEncoder().encode(JSON.stringify(aPayload))
        );

        payload.schemaVersion = aOptions.schemaVersion;
        payload.schemaName = aOptions.schemaName;
        payload.schemaNamespace = aOptions.schemaNamespace;

        payload.encryptionKeyId = aOptions.encryptionKeyId;

        if (aOptions.addPioneerId === true) {
          if (aOptions.overridePioneerId) {
            // The caller provided a substitute id, let's use that
            // instead of querying the pref.
            payload.pioneerId = aOptions.overridePioneerId;
          } else {
            // This will throw if there is no pioneer ID set.
            payload.pioneerId = Services.prefs.getStringPref(
              "toolkit.telemetry.pioneerId"
            );
          }
          payload.studyName = aOptions.studyName;
        }

        pingData.payload = payload;
      } catch (e) {
        this._log.error("_submitPingLogic - Unable to encrypt ping", e);
        // Do not attempt to continue
        throw e;
      }
    }

    // Always persist the pings if we are allowed to. We should not yield on any of the
    // following operations to keep this function synchronous for the majority of the calls.
    let archivePromise = lazy.TelemetryArchive.promiseArchivePing(
      pingData
    ).catch(e =>
      this._log.error(
        "submitExternalPing - Failed to archive ping " + pingData.id,
        e
      )
    );
    let p = [archivePromise];

    p.push(
      lazy.TelemetrySend.submitPing(pingData, {
        usePingSender: aOptions.usePingSender,
      })
    );

    return Promise.all(p).then(() => pingData.id);
  },

  /**
   * Submit ping payloads to Telemetry.
   *
   * @param {String} aType The type of the ping.
   * @param {Object} aPayload The actual data payload for the ping.
   * @param {Object} [aOptions] Options object.
   * @param {Boolean} [aOptions.addClientId=false] true if the ping should contain the client
   *                  id, false otherwise.
   * @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the
   *                  environment data.
   * @param {Object}  [aOptions.overrideEnvironment=null] set to override the environment data.
   * @param {Boolean} [aOptions.usePingSender=false] if true, send the ping using the PingSender.
   * @param {Boolean} [aOptions.useEncryption=false] if true, encrypt data client-side before sending.
   * @param {Object}  [aOptions.publicKey=null] the public key to use if encryption is enabled (JSON Web Key).
   * @param {String}  [aOptions.encryptionKeyId=null] the public key ID to use if encryption is enabled.
   * @param {String}  [aOptions.studyName=null] the study name to use.
   * @param {String}  [aOptions.schemaName=null] the schema name to use if encryption is enabled.
   * @param {String}  [aOptions.schemaNamespace=null] the schema namespace to use if encryption is enabled.
   * @param {String}  [aOptions.schemaVersion=null] the schema version to use if encryption is enabled.
   * @param {Boolean} [aOptions.addPioneerId=false] true if the ping should contain the Pioneer id, false otherwise.
   * @param {Boolean} [aOptions.overridePioneerId=undefined] if set, override the
   *                  pioneer id to the provided value. Only works if aOptions.addPioneerId=true.
   * @param {String} [aOptions.overrideClientId=undefined] if set, override the
   *                 client id to the provided value. Implies aOptions.addClientId=true.
   * @param {String} [aOptions.overrideProfileGroupId=undefined] if set, override the
   *                 profile group id to the provided value. Implies aOptions.addClientId=true.
   * @returns {Promise} Test-only - a promise that is resolved with the ping id once the ping is stored or sent.
   */
  submitExternalPing: function send(aType, aPayload, aOptions) {
    this._log.trace(
      "submitExternalPing - type: " +
        aType +
        ", aOptions: " +
        JSON.stringify(aOptions)
    );

    // Reject pings sent after shutdown.
    if (this._shutDown) {
      const errorMessage =
        "submitExternalPing - Submission is not allowed after shutdown, discarding ping of type: " +
        aType;
      this._log.error(errorMessage);
      return Promise.reject(new Error(errorMessage));
    }

    // Enforce the type string to only contain sane characters.
    const typeUuid = /^[a-z0-9][a-z0-9-]+[a-z0-9]$/i;
    if (!typeUuid.test(aType)) {
      this._log.error("submitExternalPing - invalid ping type: " + aType);
      let histogram = Services.telemetry.getKeyedHistogramById(
        "TELEMETRY_INVALID_PING_TYPE_SUBMITTED"
      );
      histogram.add(aType, 1);
      return Promise.reject(new Error("Invalid type string submitted."));
    }
    // Enforce that the payload is an object.
    if (
      aPayload === null ||
      typeof aPayload !== "object" ||
      Array.isArray(aPayload)
    ) {
      this._log.error(
        "submitExternalPing - invalid payload type: " + typeof aPayload
      );
      let histogram = Services.telemetry.getHistogramById(
        "TELEMETRY_INVALID_PAYLOAD_SUBMITTED"
      );
      histogram.add(1);
      return Promise.reject(new Error("Invalid payload type submitted."));
    }

    let promise = this._submitPingLogic(aType, aPayload, aOptions);
    this._trackPendingPingTask(promise);
    return promise;
  },

  /**
   * Save a ping to disk.
   *
   * @param {String} aType The type of the ping.
   * @param {Object} aPayload The actual data payload for the ping.
   * @param {Object} aOptions Options object.
   * @param {Boolean} aOptions.addClientId true if the ping should contain the client id,
   *                  false otherwise.
   * @param {Boolean} aOptions.addEnvironment true if the ping should contain the
   *                  environment data.
   * @param {Boolean} aOptions.overwrite true overwrites a ping with the same name, if found.
   * @param {Object}  [aOptions.overrideEnvironment=null] set to override the environment data.
   * @param {String} [aOptions.overrideClientId=undefined] if set, override the
   *                 client id to the provided value. Implies aOptions.addClientId=true.
   * @param {String} [aOptions.overrideProfileGroupId=undefined] if set, override the
   *                 profile group id to the provided value. Implies aOptions.addClientId=true.
   *
   * @returns {Promise} A promise that resolves with the ping id when the ping is saved to
   *                    disk.
   */
  addPendingPing: function addPendingPing(aType, aPayload, aOptions) {
    this._log.trace(
      "addPendingPing - Type " +
        aType +
        ", aOptions " +
        JSON.stringify(aOptions)
    );

    let pingData = this.assemblePing(aType, aPayload, aOptions);

    let savePromise = lazy.TelemetryStorage.savePendingPing(pingData);
    let archivePromise = lazy.TelemetryArchive.promiseArchivePing(
      pingData
    ).catch(e => {
      this._log.error(
        "addPendingPing - Failed to archive ping " + pingData.id,
        e
      );
    });

    // Wait for both the archiving and ping persistence to complete.
    let promises = [savePromise, archivePromise];
    return Promise.all(promises).then(() => pingData.id);
  },

  /**
   * Check whether we have an aborted-session ping. If so add it to the pending pings and archive it.
   *
   * @return {Promise} Promise that is resolved when the ping is submitted and archived.
   */
  async checkAbortedSessionPing() {
    let ping = await lazy.TelemetryStorage.loadAbortedSessionPing();
    this._log.trace(
      "checkAbortedSessionPing - found aborted-session ping: " + !!ping
    );
    if (!ping) {
      return;
    }

    try {
      // Previous aborted-session might have been with a canary client ID.
      // Don't send it.
      if (ping.clientId != Utils.knownClientID) {
        await lazy.TelemetryStorage.savePendingPing(ping);
        await lazy.TelemetryArchive.promiseArchivePing(ping);
      }
    } catch (e) {
      this._log.error(
        "checkAbortedSessionPing - Unable to add the pending ping",
        e
      );
    } finally {
      await lazy.TelemetryStorage.removeAbortedSessionPing();
    }
  },

  /**
   * Save an aborted-session ping to disk without adding it to the pending pings.
   *
   * @param {Object} aPayload The ping payload data.
   * @return {Promise} Promise that is resolved when the ping is saved.
   */
  saveAbortedSessionPing(aPayload) {
    this._log.trace("saveAbortedSessionPing");
    const options = { addClientId: true, addEnvironment: true };
    const pingData = this.assemblePing(PING_TYPE_MAIN, aPayload, options);
    return lazy.TelemetryStorage.saveAbortedSessionPing(pingData);
  },

  removeAbortedSessionPing() {
    return lazy.TelemetryStorage.removeAbortedSessionPing();
  },

  async saveUninstallPing() {
    if (AppConstants.platform != "win") {
      return undefined;
    }

    this._log.trace("saveUninstallPing");

    let payload = {};
    try {
      payload.otherInstalls = lazy.UninstallPing.getOtherInstallsCount();
      this._log.info(
        "saveUninstallPing - otherInstalls",
        payload.otherInstalls
      );
    } catch (e) {
      this._log.warn("saveUninstallPing - getOtherInstallCount failed", e);
    }
    const options = { addClientId: true, addEnvironment: true };
    const pingData = this.assemblePing(PING_TYPE_UNINSTALL, payload, options);

    return lazy.TelemetryStorage.saveUninstallPing(pingData);
  },

  /**
   * This triggers basic telemetry initialization and schedules a full initialized for later
   * for performance reasons.
   *
   * This delayed initialization means TelemetryController init can be in the following states:
   * 1) setupTelemetry was never called
   * or it was called and
   *   2) _delayedInitTask was scheduled, but didn't run yet.
   *   3) _delayedInitTask is currently running.
   *   4) _delayedInitTask finished running and is nulled out.
   *
   * @return {Promise} Resolved when TelemetryController and TelemetrySession are fully
   *                   initialized. This is only used in tests.
   */
  setupTelemetry: function setupTelemetry(testing) {
    this._initStarted = true;
    this._shuttingDown = false;
    this._shutDown = false;
    this._testMode = testing;

    this._log.trace("setupTelemetry");

    if (this._delayedInitTask) {
      this._log.error("setupTelemetry - init task already running");
      return this._delayedInitTaskDeferred.promise;
    }

    if (this._initialized && !this._testMode) {
      this._log.error("setupTelemetry - already initialized");
      return Promise.resolve();
    }

    // Enable adding scalars in artifact builds and build faster modes.
    // The function is async: we intentionally don't wait for it to complete
    // as we don't want to delay startup.
    this._probeRegistrationPromise = this.registerJsProbes();

    // This will trigger displaying the datachoices infobar.
    lazy.TelemetryReportingPolicy.setup();

    if (!TelemetryControllerBase.enableTelemetryRecording()) {
      this._log.config(
        "setupChromeProcess - Telemetry recording is disabled, skipping Chrome process setup."
      );
      return Promise.resolve();
    }

    this._attachObservers();

    // Perform a lightweight, early initialization for the component, just registering
    // a few observers and initializing the session.
    lazy.TelemetrySession.earlyInit(this._testMode);
    Services.telemetry.earlyInit();

    // Annotate crash reports so that we get pings for startup crashes
    lazy.TelemetrySend.earlyInit();

    // For very short session durations, we may never load the client
    // id from disk.
    // We try to cache it in prefs to avoid this, even though this may
    // lead to some stale client ids.
    this._clientID = lazy.ClientID.getCachedClientID();
    this._profileGroupID = lazy.ClientID.getCachedProfileGroupID();

    // Init the update ping telemetry as early as possible. This won't have
    // an impact on startup.
    lazy.UpdatePing.earlyInit();

    // Delay full telemetry initialization to give the browser time to
    // run various late initializers. Otherwise our gathered memory
    // footprint and other numbers would be too optimistic.
    this._delayedInitTaskDeferred = Promise.withResolvers();
    this._delayedInitTask = new DeferredTask(
      async () => {
        try {
          // TODO: This should probably happen after all the delayed init here.
          this._initialized = true;
          await lazy.TelemetryEnvironment.delayedInit();

          // Load the ClientID.
          this._clientID = await lazy.ClientID.getClientID();
          this._profileGroupID = await lazy.ClientID.getProfileGroupID();

          // Fix-up a canary client ID if detected.
          const uploadEnabled = Services.prefs.getBoolPref(
            TelemetryUtils.Preferences.FhrUploadEnabled,
            false
          );
          if (
            uploadEnabled &&
            (this._clientID == Utils.knownClientID ||
              this._profileGroupID == Utils.knownProfileGroupID)
          ) {
            this._log.trace(
              "Upload enabled, but got canary identifiers. Resetting."
            );
            await lazy.ClientID.resetIdentifiers();
            this._clientID = await lazy.ClientID.getClientID();
            this._profileGroupID = await lazy.ClientID.getProfileGroupID();
          } else if (
            !uploadEnabled &&
            (this._clientID != Utils.knownClientID ||
              this._profileGroupID != Utils.knownProfileGroupID)
          ) {
            this._log.trace(
              "Upload disabled, but got a valid client ID. Setting canary client ID."
            );
            await lazy.ClientID.setCanaryIdentifiers();
            this._clientID = await lazy.ClientID.getClientID();
            this._profileGroupID = await lazy.ClientID.getProfileGroupID();
          }

          await lazy.TelemetrySend.setup(this._testMode);

          // Perform TelemetrySession delayed init.
          await lazy.TelemetrySession.delayedInit();
          await Services.telemetry.delayedInit();

          if (
            Services.prefs.getBoolPref(
              TelemetryUtils.Preferences.NewProfilePingEnabled,
              false
            ) &&
            !lazy.TelemetrySession.newProfilePingSent
          ) {
            // Kick off the scheduling of the new-profile ping.
            this.scheduleNewProfilePing();
          }

          // Purge the pings archive by removing outdated pings. We don't wait for
          // this task to complete, but TelemetryStorage blocks on it during
          // shutdown.
          lazy.TelemetryStorage.runCleanPingArchiveTask();

          // Now that FHR/healthreporter is gone, make sure to remove FHR's DB from
          // the profile directory. This is a temporary measure that we should drop
          // in the future.
          lazy.TelemetryStorage.removeFHRDatabase();

          // The init sequence is forced to run on shutdown for short sessions and
          // we don't want to start TelemetryModules as the timer registration will fail.
          if (!this._shuttingDown) {
            // Report the modules loaded in the Firefox process.
            lazy.TelemetryModules.start();

            // Send coverage ping.
            await lazy.CoveragePing.startup();

            // Start the untrusted modules ping, which reports events where
            // untrusted modules were loaded into the Firefox process.
            if (AppConstants.platform == "win") {
              lazy.TelemetryUntrustedModulesPing.start();
            }
          }

          lazy.TelemetryEventPing.startup();

          if (uploadEnabled) {
            await this.saveUninstallPing().catch(e =>
              this._log.warn("_delayedInitTask - saveUninstallPing failed", e)
            );
          } else {
            await lazy.TelemetryStorage.removeUninstallPings().catch(e =>
              this._log.warn("_delayedInitTask - saveUninstallPing", e)
            );
          }

          this._delayedInitTaskDeferred.resolve();
        } catch (e) {
          this._delayedInitTaskDeferred.reject(e);
        } finally {
          this._delayedInitTask = null;
        }
      },
      this._testMode ? TELEMETRY_TEST_DELAY : TELEMETRY_DELAY,
      this._testMode ? 0 : undefined
    );

    IOUtils.sendTelemetry.addBlocker(
      "TelemetryController: shutting down",
      () => this.shutdown(),
      () => this._getState()
    );

    this._delayedInitTask.arm();
    return this._delayedInitTaskDeferred.promise;
  },

  // Do proper shutdown waiting and cleanup.
  async _cleanupOnShutdown() {
    if (!this._initialized) {
      return;
    }

    this._detachObservers();

    // Now do an orderly shutdown.
    try {
      if (this._delayedNewPingTask) {
        await this._delayedNewPingTask.finalize();
      }

      lazy.UpdatePing.shutdown();

      lazy.TelemetryEventPing.shutdown();

      // Shutdown the sync ping if it is initialized - this is likely, but not
      // guaranteed, to submit a "shutdown" sync ping.
      if (this._fnSyncPingShutdown) {
        this._fnSyncPingShutdown();
      }

      // Stop the datachoices infobar display.
      lazy.TelemetryReportingPolicy.shutdown();
      lazy.TelemetryEnvironment.shutdown();

      // Stop any ping sending.
      await lazy.TelemetrySend.shutdown();

      // Send latest data.
      await lazy.TelemetryHealthPing.shutdown();

      await lazy.TelemetrySession.shutdown();
      await Services.telemetry.shutdown();

      // First wait for clients processing shutdown.
      await this._shutdownBarrier.wait();

      // ... and wait for any outstanding async ping activity.
      await this._connectionsBarrier.wait();

      if (AppConstants.platform !== "android") {
        // No PingSender on Android.
        lazy.TelemetrySend.flushPingSenderBatch();
      }

      // Perform final shutdown operations.
      await lazy.TelemetryStorage.shutdown();
    } finally {
      // Reset state.
      this._initialized = false;
      this._initStarted = false;
      this._shutDown = true;
    }
  },

  shutdown() {
    this._log.trace("shutdown");

    this._shuttingDown = true;

    // We can be in one the following states here:
    // 1) setupTelemetry was never called
    // or it was called and
    //   2) _delayedInitTask was scheduled, but didn't run yet.
    //   3) _delayedInitTask is running now.
    //   4) _delayedInitTask finished running already.

    // This handles 1).
    if (!this._initStarted) {
      this._shutDown = true;
      return Promise.resolve();
    }

    // This handles 4).
    if (!this._delayedInitTask) {
      // We already ran the delayed initialization.
      return this._cleanupOnShutdown();
    }

    // This handles 2) and 3).
    return this._delayedInitTask
      .finalize()
      .then(() => this._cleanupOnShutdown());
  },

  /**
   * This observer drives telemetry.
   */
  observe(aSubject, aTopic, aData) {
    // The logger might still be not available at this point.
    if (aTopic == "profile-after-change") {
      // If we don't have a logger, we need to make sure |Log.repository.getLogger()| is
      // called before |getLoggerWithMessagePrefix|. Otherwise logging won't work.
      TelemetryControllerBase.configureLogging();
    }

    this._log.trace(`observe - ${aTopic} notified.`);

    switch (aTopic) {
      case "profile-after-change":
        // profile-after-change is only registered for chrome processes.
        return this.setupTelemetry();
      case "nsPref:changed":
        if (aData == TelemetryUtils.Preferences.FhrUploadEnabled) {
          return this._onUploadPrefChange();
        }
    }
    return undefined;
  },

  /**
   * Register the sync ping's shutdown handler.
   */
  registerSyncPingShutdown(fnShutdown) {
    if (this._fnSyncPingShutdown) {
      throw new Error("The sync ping shutdown handler is already registered.");
    }
    this._fnSyncPingShutdown = fnShutdown;
  },

  /**
   * Get an object describing the current state of this module for AsyncShutdown diagnostics.
   */
  _getState() {
    return {
      initialized: this._initialized,
      initStarted: this._initStarted,
      haveDelayedInitTask: !!this._delayedInitTask,
      shutdownBarrier: this._shutdownBarrier.state,
      connectionsBarrier: this._connectionsBarrier.state,
      sendModule: lazy.TelemetrySend.getShutdownState(),
      haveDelayedNewProfileTask: !!this._delayedNewPingTask,
    };
  },

  /**
   * Called whenever the FHR Upload preference changes (e.g. when user disables FHR from
   * the preferences panel), this triggers sending the "deletion-request" ping.
   */
  _onUploadPrefChange() {
    const uploadEnabled = Services.prefs.getBoolPref(
      TelemetryUtils.Preferences.FhrUploadEnabled,
      false
    );
    if (uploadEnabled) {
      this._log.trace(
        "_onUploadPrefChange - upload was enabled again. Resetting identifiers"
      );

      // Delete cached identifiers immediately, so other usage is forced to refetch it.
      this._clientID = null;
      this._profileGroupID = null;

      // Generate a new client ID and make sure this module uses the new version
      let p = (async () => {
        await lazy.ClientID.resetIdentifiers();
        this._clientID = await lazy.ClientID.getClientID();
        this._profileGroupID = await lazy.ClientID.getProfileGroupID();
        Services.telemetry.scalarSet("telemetry.data_upload_optin", true);

        await this.saveUninstallPing().catch(e =>
          this._log.warn("_onUploadPrefChange - saveUninstallPing failed", e)
        );
      })();

      this._shutdownBarrier.client.addBlocker(
        "TelemetryController: resetting client ID after data upload was enabled",
        p
      );

      return;
    }

    let p = (async () => {
      try {
        // 1. Cancel the current pings.
        // 2. Clear unpersisted pings
        await lazy.TelemetrySend.clearCurrentPings();

        // 3. Remove all pending pings
        await lazy.TelemetryStorage.removeAppDataPings();
        await lazy.TelemetryStorage.runRemovePendingPingsTask();
        await lazy.TelemetryStorage.removeUninstallPings();
      } catch (e) {
        this._log.error(
          "_onUploadPrefChange - error clearing pending pings",
          e
        );
      } finally {
        // 4. Reset session and subsession counter
        lazy.TelemetrySession.resetSubsessionCounter();

        // 5. Collect any additional identifiers we want to send in the
        // deletion request.
        const scalars = Services.telemetry.getSnapshotForScalars(
          "deletion-request",
          /* clear */ true
        );

        // 6. Set identifiers to a known values
        let oldClientId = await lazy.ClientID.getClientID();
        let oldProfileGroupId = await lazy.ClientID.getProfileGroupID();
        await lazy.ClientID.setCanaryIdentifiers();
        this._clientID = await lazy.ClientID.getClientID();
        this._profileGroupID = await lazy.ClientID.getProfileGroupID();

        // 7. Send the deletion-request ping.
        this._log.trace("_onUploadPrefChange - Sending deletion-request ping.");
        this.submitExternalPing(
          PING_TYPE_DELETION_REQUEST,
          { scalars },
          {
            overrideClientId: oldClientId,
            overrideProfileGroupId: oldProfileGroupId,
          }
        );
        this._deletionRequestPingSubmittedPromise = null;
      }
    })();

    this._deletionRequestPingSubmittedPromise = p;
    this._shutdownBarrier.client.addBlocker(
      "TelemetryController: removing pending pings after data upload was disabled",
      p
    );

    Services.obs.notifyObservers(
      null,
      TelemetryUtils.TELEMETRY_UPLOAD_DISABLED_TOPIC
    );
  },

  QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]),

  _attachObservers() {
    if (TelemetryControllerBase.IS_UNIFIED_TELEMETRY) {
      // Watch the FHR upload setting to trigger "deletion-request" pings.
      Services.prefs.addObserver(
        TelemetryUtils.Preferences.FhrUploadEnabled,
        this,
        true
      );
    }
  },

  /**
   * Remove the preference observer to avoid leaks.
   */
  _detachObservers() {
    if (TelemetryControllerBase.IS_UNIFIED_TELEMETRY) {
      Services.prefs.removeObserver(
        TelemetryUtils.Preferences.FhrUploadEnabled,
        this
      );
    }
  },

  /**
   * Allows waiting for TelemetryControllers delayed initialization to complete.
   * This will complete before TelemetryController is shutting down.
   * @return {Promise} Resolved when delayed TelemetryController initialization completed.
   */
  promiseInitialized() {
    return this._delayedInitTaskDeferred.promise;
  },

  /**
   * Allows to trigger TelemetryControllers delayed initialization now and waiting for its completion.
   * This will complete before TelemetryController is shutting down.
   * @return {Promise} Resolved when delayed TelemetryController initialization completed.
   */
  ensureInitialized() {
    if (this._delayedInitTask) {
      return this._delayedInitTask.finalize();
    }
    return Promise.resolve();
  },

  getCurrentPingData(aSubsession) {
    this._log.trace("getCurrentPingData - subsession: " + aSubsession);

    // Telemetry is disabled, don't gather any data.
    if (!Services.telemetry.canRecordBase) {
      return null;
    }

    const reason = aSubsession
      ? REASON_GATHER_SUBSESSION_PAYLOAD
      : REASON_GATHER_PAYLOAD;
    const type = PING_TYPE_MAIN;
    const payload = lazy.TelemetrySession.getPayload(reason);
    const options = { addClientId: true, addEnvironment: true };
    const ping = this.assemblePing(type, payload, options);

    return ping;
  },

  async reset() {
    this._clientID = null;
    this._profileGroupID = null;
    this._fnSyncPingShutdown = null;
    this._detachObservers();

    let sessionReset = lazy.TelemetrySession.testReset();

    this._connectionsBarrier = new AsyncShutdown.Barrier(
      "TelemetryController: Waiting for pending ping activity"
    );
    this._shutdownBarrier = new AsyncShutdown.Barrier(
      "TelemetryController: Waiting for clients."
    );

    // We need to kick of the controller setup first for tests that check the
    // cached client id.
    let controllerSetup = this.setupTelemetry(true);

    await sessionReset;
    await lazy.TelemetrySend.reset();
    await lazy.TelemetryStorage.reset();
    await lazy.TelemetryEnvironment.testReset();

    await controllerSetup;
  },

  /**
   * Schedule sending the "new-profile" ping.
   */
  scheduleNewProfilePing() {
    this._log.trace("scheduleNewProfilePing");

    const sendDelay = Services.prefs.getIntPref(
      TelemetryUtils.Preferences.NewProfilePingDelay,
      NEWPROFILE_PING_DEFAULT_DELAY
    );

    if (
      AppConstants.platform == "win" &&
      AppConstants.MOZ_APP_NAME !== "thunderbird"
    ) {
      try {
        // This is asynchronous, but we aren't going to await on it now. Just
        // kick it off.
        lazy.BrowserUsageTelemetry.reportInstallationTelemetry();
      } catch (ex) {
        this._log.warn(
          "scheduleNewProfilePing - reportInstallationTelemetry failed",
          ex
        );
      }
    }

    this._delayedNewPingTask = new DeferredTask(async () => {
      try {
        await this.sendNewProfilePing();
      } finally {
        this._delayedNewPingTask = null;
      }
    }, sendDelay);

    this._delayedNewPingTask.arm();
  },

  /**
   * Generate and send the new-profile ping
   */
  async sendNewProfilePing() {
    this._log.trace(
      "sendNewProfilePing - shutting down: " + this._shuttingDown
    );

    if (
      AppConstants.platform == "win" &&
      AppConstants.MOZ_APP_NAME !== "thunderbird"
    ) {
      let failureReason = "UnknownError";
      try {
        await lazy.BrowserUsageTelemetry.reportInstallationTelemetry();
        failureReason = "NoError";
      } catch (ex) {
        this._log.warn(
          "sendNewProfilePing - reportInstallationTelemetry failed",
          ex
        );
        // Overwrite with a more specific error if possible.
        failureReason = ex.name;
      } finally {
        // No dataPathOverride here so we can check the default location
        // for installation_telemetry.json
        let dataPath = Services.dirsvc.get("GreD", Ci.nsIFile);
        dataPath.append("installation_telemetry.json");
        let fileExists = await IOUtils.exists(dataPath.path);
        if (!fileExists) {
          failureReason = "NotFoundError";
        }
        Glean.installationFirstSeen.failureReason.set(failureReason);
      }
    }

    const scalars = Services.telemetry.getSnapshotForScalars(
      "new-profile",
      /* clear */ true
    );

    // Generate the payload.
    const payload = {
      reason: this._shuttingDown ? "shutdown" : "startup",
      processes: {
        parent: {
          scalars: scalars.parent,
        },
      },
    };

    // Generate and send the "new-profile" ping. This uses the
    // pingsender if we're shutting down.
    let options = {
      addClientId: true,
      addEnvironment: true,
      usePingSender: this._shuttingDown,
    };
    // TODO: we need to be smarter about when to send the ping (and save the
    // state to file). |requestIdleCallback| is currently only accessible
    // through DOM. See bug 1361996.
    await TelemetryController.submitExternalPing(
      "new-profile",
      payload,
      options
    ).then(
      () => lazy.TelemetrySession.markNewProfilePingSent(),
      e =>
        this._log.error(
          "sendNewProfilePing - failed to submit new-profile ping",
          e
        )
    );
  },

  /**
   * Register 'dynamic builtin' probes from the JSON definition files.
   * This is needed to support adding new probes in developer builds
   * without rebuilding the whole codebase.
   *
   * This is not meant to be used outside of local developer builds.
   */
  async registerJsProbes() {
    // We don't support this outside of developer builds.
    if (AppConstants.MOZILLA_OFFICIAL && !this._testMode) {
      return;
    }

    this._log.trace("registerJsProbes - registering builtin JS probes");

    await this.registerScalarProbes();
    await this.registerEventProbes();
  },

  _loadProbeDefinitions(filename) {
    let probeFile = Services.dirsvc.get("GreD", Ci.nsIFile);
    probeFile.append(filename);
    if (!probeFile.exists()) {
      this._log.trace(
        `loadProbeDefinitions - no builtin JS probe file ${filename}`
      );
      return null;
    }

    return IOUtils.readUTF8(probeFile.path);
  },

  async registerScalarProbes() {
    this._log.trace(
      "registerScalarProbes - registering scalar builtin JS probes"
    );

    // Load the scalar probes JSON file.
    const scalarProbeFilename = "ScalarArtifactDefinitions.json";
    let scalarJSProbes = {};
    try {
      let fileContent = await this._loadProbeDefinitions(scalarProbeFilename);
      scalarJSProbes = JSON.parse(fileContent, (property, value) => {
        // Fixup the "kind" property: it's a string, and we need the constant
        // coming from nsITelemetry.
        if (property !== "kind" || typeof value != "string") {
          return value;
        }

        let newValue;
        switch (value) {
          case "nsITelemetry::SCALAR_TYPE_COUNT":
            newValue = Services.telemetry.SCALAR_TYPE_COUNT;
            break;
          case "nsITelemetry::SCALAR_TYPE_BOOLEAN":
            newValue = Services.telemetry.SCALAR_TYPE_BOOLEAN;
            break;
          case "nsITelemetry::SCALAR_TYPE_STRING":
            newValue = Services.telemetry.SCALAR_TYPE_STRING;
            break;
        }
        return newValue;
      });
    } catch (ex) {
      this._log.error(
        `registerScalarProbes - there was an error loading ${scalarProbeFilename}`,
        ex
      );
    }

    // Register the builtin probes.
    for (let category in scalarJSProbes) {
      // Expire the expired scalars
      for (let name in scalarJSProbes[category]) {
        let def = scalarJSProbes[category][name];
        if (
          !def ||
          !def.expires ||
          def.expires == "never" ||
          def.expires == "default"
        ) {
          continue;
        }
        if (
          Services.vc.compare(AppConstants.MOZ_APP_VERSION, def.expires) >= 0
        ) {
          def.expired = true;
        }
      }
      Services.telemetry.registerBuiltinScalars(
        category,
        scalarJSProbes[category]
      );
    }
  },

  async registerEventProbes() {
    this._log.trace(
      "registerEventProbes - registering builtin JS Event probes"
    );

    // Load the event probes JSON file.
    const eventProbeFilename = "EventArtifactDefinitions.json";
    let eventJSProbes = {};
    try {
      let fileContent = await this._loadProbeDefinitions(eventProbeFilename);
      eventJSProbes = JSON.parse(fileContent);
    } catch (ex) {
      this._log.error(
        `registerEventProbes - there was an error loading ${eventProbeFilename}`,
        ex
      );
    }

    // Register the builtin probes.
    for (let category in eventJSProbes) {
      for (let name in eventJSProbes[category]) {
        let def = eventJSProbes[category][name];
        if (
          !def ||
          !def.expires ||
          def.expires == "never" ||
          def.expires == "default"
        ) {
          continue;
        }
        if (
          Services.vc.compare(AppConstants.MOZ_APP_VERSION, def.expires) >= 0
        ) {
          def.expired = true;
        }
      }
      Services.telemetry.registerBuiltinEvents(
        category,
        eventJSProbes[category]
      );
    }
  },
};
PK
!<��D�mm$modules/TelemetryEnvironment.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Log } from "resource://gre/modules/Log.sys.mjs";

import { TelemetryUtils } from "resource://gre/modules/TelemetryUtils.sys.mjs";

import { ObjectUtils } from "resource://gre/modules/ObjectUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { UpdateUtils } from "resource://gre/modules/UpdateUtils.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const Utils = TelemetryUtils;
const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";

import {
  AddonManager,
  AddonManagerPrivate,
} from "resource://gre/modules/AddonManager.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AttributionCode: "resource:///modules/AttributionCode.sys.mjs",
  ProfileAge: "resource://gre/modules/ProfileAge.sys.mjs",
  WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs",
  WindowsVersionInfo:
    "resource://gre/modules/components-utils/WindowsVersionInfo.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => {
  return ChromeUtils.importESModule(
    "resource://gre/modules/FxAccounts.sys.mjs"
  ).getFxAccountsSingleton();
});

if (AppConstants.MOZ_UPDATER) {
  XPCOMUtils.defineLazyServiceGetter(
    lazy,
    "UpdateServiceStub",
    "@mozilla.org/updates/update-service-stub;1",
    "nsIApplicationUpdateServiceStub"
  );
}

// The maximum length of a string (e.g. description) in the addons section.
const MAX_ADDON_STRING_LENGTH = 100;
// The maximum length of a string value in the settings.attribution object.
const MAX_ATTRIBUTION_STRING_LENGTH = 100;
// The maximum lengths for the experiment id and branch in the experiments section.
const MAX_EXPERIMENT_ID_LENGTH = 100;
const MAX_EXPERIMENT_BRANCH_LENGTH = 100;
const MAX_EXPERIMENT_TYPE_LENGTH = 20;
const MAX_EXPERIMENT_ENROLLMENT_ID_LENGTH = 40;

/**
 * This is a policy object used to override behavior for testing.
 */
// eslint-disable-next-line no-unused-vars
export var Policy = {
  now: () => new Date(),
  _intlLoaded: false,
  _browserDelayedStartup() {
    if (Policy._intlLoaded) {
      return Promise.resolve();
    }
    return new Promise(resolve => {
      let startupTopic = "browser-delayed-startup-finished";
      Services.obs.addObserver(function observer(subject, topic) {
        if (topic == startupTopic) {
          Services.obs.removeObserver(observer, startupTopic);
          resolve();
        }
      }, startupTopic);
    });
  },
};

// This is used to buffer calls to setExperimentActive and friends, so that we
// don't prematurely initialize our environment if it is called early during
// startup.
var gActiveExperimentStartupBuffer = new Map();

var gGlobalEnvironment;
function getGlobal() {
  if (!gGlobalEnvironment) {
    gGlobalEnvironment = new EnvironmentCache();
  }
  return gGlobalEnvironment;
}

export var TelemetryEnvironment = {
  get currentEnvironment() {
    return getGlobal().currentEnvironment;
  },

  onInitialized() {
    return getGlobal().onInitialized();
  },

  delayedInit() {
    return getGlobal().delayedInit();
  },

  registerChangeListener(name, listener) {
    return getGlobal().registerChangeListener(name, listener);
  },

  unregisterChangeListener(name) {
    return getGlobal().unregisterChangeListener(name);
  },

  /**
   * Add an experiment annotation to the environment.
   * If an annotation with the same id already exists, it will be overwritten.
   * This triggers a new subsession, subject to throttling.
   *
   * @param {String} id The id of the active experiment.
   * @param {String} branch The experiment branch.
   * @param {Object} [options] Optional object with options.
   * @param {String} [options.type=false] The specific experiment type.
   * @param {String} [options.enrollmentId=undefined] The id of the enrollment.
   */
  setExperimentActive(id, branch, options = {}) {
    if (gGlobalEnvironment) {
      gGlobalEnvironment.setExperimentActive(id, branch, options);
    } else {
      gActiveExperimentStartupBuffer.set(id, { branch, options });
    }
  },

  /**
   * Remove an experiment annotation from the environment.
   * If the annotation exists, a new subsession will triggered.
   *
   * @param {String} id The id of the active experiment.
   */
  setExperimentInactive(id) {
    if (gGlobalEnvironment) {
      gGlobalEnvironment.setExperimentInactive(id);
    } else {
      gActiveExperimentStartupBuffer.delete(id);
    }
  },

  /**
   * Returns an object containing the data for the active experiments.
   *
   * The returned object is of the format:
   *
   * {
   *   "<experiment id>": { branch: "<branch>" },
   *   // …
   * }
   */
  getActiveExperiments() {
    if (gGlobalEnvironment) {
      return gGlobalEnvironment.getActiveExperiments();
    }

    const result = {};
    for (const [id, { branch }] of gActiveExperimentStartupBuffer.entries()) {
      result[id] = branch;
    }
    return result;
  },

  shutdown() {
    return getGlobal().shutdown();
  },

  // Policy to use when saving preferences. Exported for using them in tests.
  // Reports "<user-set>" if there is a value set on the user branch
  RECORD_PREF_STATE: 1,

  // Reports the value set on the user branch, if one is set
  RECORD_PREF_VALUE: 2,

  // Reports the active value (set on either the user or default branch)
  // for this pref, if one is set
  RECORD_DEFAULTPREF_VALUE: 3,

  // Reports "<set>" if a value for this pref is defined on either the user
  // or default branch
  RECORD_DEFAULTPREF_STATE: 4,

  // Testing method
  async testWatchPreferences(prefMap) {
    return getGlobal()._watchPreferences(prefMap);
  },

  /**
   * Intended for use in tests only.
   *
   * In multiple tests we need a way to shut and re-start telemetry together
   * with TelemetryEnvironment. This is problematic due to the fact that
   * TelemetryEnvironment is a singleton. We, therefore, need this helper
   * method to be able to re-set TelemetryEnvironment.
   */
  testReset() {
    return getGlobal().reset();
  },

  /**
   * Intended for use in tests only.
   */
  testCleanRestart() {
    getGlobal().shutdown();
    gGlobalEnvironment = null;
    gActiveExperimentStartupBuffer = new Map();
    return getGlobal();
  },
};

const RECORD_PREF_STATE = TelemetryEnvironment.RECORD_PREF_STATE;
const RECORD_PREF_VALUE = TelemetryEnvironment.RECORD_PREF_VALUE;
const RECORD_DEFAULTPREF_VALUE = TelemetryEnvironment.RECORD_DEFAULTPREF_VALUE;
const RECORD_DEFAULTPREF_STATE = TelemetryEnvironment.RECORD_DEFAULTPREF_STATE;
const DEFAULT_ENVIRONMENT_PREFS = new Map([
  ["app.feedback.baseURL", { what: RECORD_PREF_VALUE }],
  ["app.support.baseURL", { what: RECORD_PREF_VALUE }],
  ["accessibility.browsewithcaret", { what: RECORD_PREF_VALUE }],
  ["accessibility.force_disabled", { what: RECORD_PREF_VALUE }],
  ["app.normandy.test-prefs.bool", { what: RECORD_PREF_VALUE }],
  ["app.normandy.test-prefs.integer", { what: RECORD_PREF_VALUE }],
  ["app.normandy.test-prefs.string", { what: RECORD_PREF_VALUE }],
  ["app.shield.optoutstudies.enabled", { what: RECORD_PREF_VALUE }],
  ["app.update.interval", { what: RECORD_PREF_VALUE }],
  ["app.update.service.enabled", { what: RECORD_PREF_VALUE }],
  ["app.update.silent", { what: RECORD_PREF_VALUE }],
  ["browser.cache.disk.enable", { what: RECORD_PREF_VALUE }],
  ["browser.cache.disk.capacity", { what: RECORD_PREF_VALUE }],
  ["browser.cache.memory.enable", { what: RECORD_PREF_VALUE }],
  ["browser.formfill.enable", { what: RECORD_PREF_VALUE }],
  ["browser.migrate.interactions.bookmarks", { what: RECORD_PREF_VALUE }],
  ["browser.migrate.interactions.csvpasswords", { what: RECORD_PREF_VALUE }],
  ["browser.migrate.interactions.history", { what: RECORD_PREF_VALUE }],
  ["browser.migrate.interactions.passwords", { what: RECORD_PREF_VALUE }],
  ["browser.newtabpage.enabled", { what: RECORD_PREF_VALUE }],
  ["browser.privatebrowsing.autostart", { what: RECORD_PREF_VALUE }],
  ["browser.shell.checkDefaultBrowser", { what: RECORD_PREF_VALUE }],
  ["browser.search.region", { what: RECORD_PREF_VALUE }],
  ["browser.search.suggest.enabled", { what: RECORD_PREF_VALUE }],
  ["browser.startup.homepage", { what: RECORD_PREF_STATE }],
  ["browser.startup.page", { what: RECORD_PREF_VALUE }],
  ["browser.urlbar.autoFill", { what: RECORD_DEFAULTPREF_VALUE }],
  [
    "browser.urlbar.autoFill.adaptiveHistory.enabled",
    { what: RECORD_DEFAULTPREF_VALUE },
  ],
  [
    "browser.urlbar.dnsResolveSingleWordsAfterSearch",
    { what: RECORD_DEFAULTPREF_VALUE },
  ],
  [
    "browser.urlbar.quicksuggest.onboardingDialogChoice",
    { what: RECORD_DEFAULTPREF_VALUE },
  ],
  [
    "browser.urlbar.quicksuggest.dataCollection.enabled",
    { what: RECORD_DEFAULTPREF_VALUE },
  ],
  ["browser.urlbar.showSearchSuggestionsFirst", { what: RECORD_PREF_VALUE }],
  ["browser.urlbar.showSearchTerms.enabled", { what: RECORD_PREF_VALUE }],
  [
    "browser.urlbar.suggest.quicksuggest.nonsponsored",
    { what: RECORD_DEFAULTPREF_VALUE },
  ],
  [
    "browser.urlbar.suggest.quicksuggest.sponsored",
    { what: RECORD_DEFAULTPREF_VALUE },
  ],
  ["browser.urlbar.suggest.searches", { what: RECORD_PREF_VALUE }],
  ["devtools.chrome.enabled", { what: RECORD_PREF_VALUE }],
  ["devtools.debugger.enabled", { what: RECORD_PREF_VALUE }],
  ["devtools.debugger.remote-enabled", { what: RECORD_PREF_VALUE }],
  ["doh-rollout.doorhanger-decision", { what: RECORD_PREF_VALUE }],
  ["dom.ipc.processCount", { what: RECORD_PREF_VALUE }],
  ["dom.max_script_run_time", { what: RECORD_PREF_VALUE }],
  ["dom.popup_allowed_events", { what: RECORD_PREF_VALUE }],
  ["editor.truncate_user_pastes", { what: RECORD_PREF_VALUE }],
  ["extensions.InstallTrigger.enabled", { what: RECORD_PREF_VALUE }],
  ["extensions.InstallTriggerImpl.enabled", { what: RECORD_PREF_VALUE }],
  ["extensions.autoDisableScopes", { what: RECORD_PREF_VALUE }],
  ["extensions.blocklist.enabled", { what: RECORD_PREF_VALUE }],
  ["extensions.enabledScopes", { what: RECORD_PREF_VALUE }],
  ["extensions.eventPages.enabled", { what: RECORD_PREF_VALUE }],
  ["extensions.formautofill.addresses.enabled", { what: RECORD_PREF_VALUE }],
  [
    "extensions.formautofill.addresses.capture.enabled",
    { what: RECORD_PREF_VALUE },
  ],
  ["extensions.formautofill.creditCards.enabled", { what: RECORD_PREF_VALUE }],
  ["extensions.manifestV3.enabled", { what: RECORD_PREF_VALUE }],
  ["extensions.quarantinedDomains.enabled", { what: RECORD_PREF_VALUE }],
  ["extensions.strictCompatibility", { what: RECORD_PREF_VALUE }],
  ["extensions.update.enabled", { what: RECORD_PREF_VALUE }],
  ["extensions.update.url", { what: RECORD_PREF_VALUE }],
  ["extensions.update.background.url", { what: RECORD_PREF_VALUE }],
  ["extensions.screenshots.disabled", { what: RECORD_PREF_VALUE }],
  ["general.config.filename", { what: RECORD_DEFAULTPREF_STATE }],
  ["general.smoothScroll", { what: RECORD_PREF_VALUE }],
  ["gfx.direct2d.disabled", { what: RECORD_PREF_VALUE }],
  ["gfx.direct2d.force-enabled", { what: RECORD_PREF_VALUE }],
  ["gfx.webrender.all", { what: RECORD_PREF_VALUE }],
  ["layers.acceleration.disabled", { what: RECORD_PREF_VALUE }],
  ["layers.acceleration.force-enabled", { what: RECORD_PREF_VALUE }],
  ["layers.async-pan-zoom.enabled", { what: RECORD_PREF_VALUE }],
  ["layers.async-video-oop.enabled", { what: RECORD_PREF_VALUE }],
  ["layers.d3d11.disable-warp", { what: RECORD_PREF_VALUE }],
  ["layers.d3d11.force-warp", { what: RECORD_PREF_VALUE }],
  [
    "layers.offmainthreadcomposition.force-disabled",
    { what: RECORD_PREF_VALUE },
  ],
  ["layers.prefer-d3d9", { what: RECORD_PREF_VALUE }],
  ["layers.prefer-opengl", { what: RECORD_PREF_VALUE }],
  ["layout.css.devPixelsPerPx", { what: RECORD_PREF_VALUE }],
  ["media.gmp-gmpopenh264.enabled", { what: RECORD_PREF_VALUE }],
  ["media.gmp-gmpopenh264.lastInstallFailed", { what: RECORD_PREF_VALUE }],
  ["media.gmp-gmpopenh264.lastInstallFailReason", { what: RECORD_PREF_VALUE }],
  ["media.gmp-gmpopenh264.lastInstallStart", { what: RECORD_PREF_VALUE }],
  ["media.gmp-gmpopenh264.lastDownload", { what: RECORD_PREF_VALUE }],
  ["media.gmp-gmpopenh264.lastDownloadFailed", { what: RECORD_PREF_VALUE }],
  ["media.gmp-gmpopenh264.lastDownloadFailReason", { what: RECORD_PREF_VALUE }],
  ["media.gmp-gmpopenh264.lastUpdate", { what: RECORD_PREF_VALUE }],
  ["media.gmp-gmpopenh264.visible", { what: RECORD_PREF_VALUE }],
  ["media.gmp-widevinecdm.enabled", { what: RECORD_PREF_VALUE }],
  ["media.gmp-widevinecdm.lastInstallFailed", { what: RECORD_PREF_VALUE }],
  ["media.gmp-widevinecdm.lastInstallFailReason", { what: RECORD_PREF_VALUE }],
  ["media.gmp-widevinecdm.lastInstallStart", { what: RECORD_PREF_VALUE }],
  ["media.gmp-widevinecdm.lastDownload", { what: RECORD_PREF_VALUE }],
  ["media.gmp-widevinecdm.lastDownloadFailed", { what: RECORD_PREF_VALUE }],
  ["media.gmp-widevinecdm.lastDownloadFailReason", { what: RECORD_PREF_VALUE }],
  ["media.gmp-widevinecdm.lastUpdate", { what: RECORD_PREF_VALUE }],
  ["media.gmp-widevinecdm.visible", { what: RECORD_PREF_VALUE }],
  ["media.gmp-manager.lastCheck", { what: RECORD_PREF_VALUE }],
  ["media.gmp-manager.lastEmptyCheck", { what: RECORD_PREF_VALUE }],
  ["network.http.windows-sso.enabled", { what: RECORD_PREF_VALUE }],
  ["network.proxy.autoconfig_url", { what: RECORD_PREF_STATE }],
  ["network.proxy.http", { what: RECORD_PREF_STATE }],
  ["network.proxy.ssl", { what: RECORD_PREF_STATE }],
  ["network.trr.mode", { what: RECORD_PREF_VALUE }],
  ["network.trr.strict_native_fallback", { what: RECORD_DEFAULTPREF_VALUE }],
  ["pdfjs.disabled", { what: RECORD_PREF_VALUE }],
  ["places.history.enabled", { what: RECORD_PREF_VALUE }],
  ["privacy.firstparty.isolate", { what: RECORD_PREF_VALUE }],
  ["privacy.resistFingerprinting", { what: RECORD_PREF_VALUE }],
  ["privacy.fingerprintingProtection", { what: RECORD_PREF_VALUE }],
  ["privacy.fingerprintingProtection.pbmode", { what: RECORD_PREF_VALUE }],
  ["privacy.trackingprotection.enabled", { what: RECORD_PREF_VALUE }],
  ["privacy.donottrackheader.enabled", { what: RECORD_PREF_VALUE }],
  ["security.enterprise_roots.auto-enabled", { what: RECORD_PREF_VALUE }],
  ["security.enterprise_roots.enabled", { what: RECORD_PREF_VALUE }],
  ["security.pki.mitm_detected", { what: RECORD_PREF_VALUE }],
  ["security.mixed_content.block_active_content", { what: RECORD_PREF_VALUE }],
  ["security.mixed_content.block_display_content", { what: RECORD_PREF_VALUE }],
  ["security.tls.version.enable-deprecated", { what: RECORD_PREF_VALUE }],
  ["signon.management.page.breach-alerts.enabled", { what: RECORD_PREF_VALUE }],
  ["signon.autofillForms", { what: RECORD_PREF_VALUE }],
  ["signon.generation.enabled", { what: RECORD_PREF_VALUE }],
  ["signon.rememberSignons", { what: RECORD_PREF_VALUE }],
  ["signon.firefoxRelay.feature", { what: RECORD_PREF_VALUE }],
  ["toolkit.telemetry.pioneerId", { what: RECORD_PREF_STATE }],
  [
    "widget.content.gtk-high-contrast.enabled",
    { what: RECORD_DEFAULTPREF_VALUE },
  ],
  ["xpinstall.signatures.required", { what: RECORD_PREF_VALUE }],
  [
    "xpinstall.signatures.weakSignaturesTemporarilyAllowed",
    { what: RECORD_PREF_VALUE },
  ],
  ["nimbus.debug", { what: RECORD_PREF_VALUE }],
]);

if (AppConstants.platform == "linux" || AppConstants.platform == "macosx") {
  DEFAULT_ENVIRONMENT_PREFS.set(
    "intl.ime.use_composition_events_for_insert_text",
    { what: RECORD_PREF_VALUE }
  );
}

const LOGGER_NAME = "Toolkit.Telemetry";

const PREF_BLOCKLIST_ENABLED = "extensions.blocklist.enabled";
const PREF_DISTRIBUTION_ID = "distribution.id";
const PREF_DISTRIBUTION_VERSION = "distribution.version";
const PREF_DISTRIBUTOR = "app.distributor";
const PREF_DISTRIBUTOR_CHANNEL = "app.distributor.channel";
const PREF_APP_PARTNER_BRANCH = "app.partner.";
const PREF_PARTNER_ID = "mozilla.partner.id";

const COMPOSITOR_CREATED_TOPIC = "compositor:created";
const COMPOSITOR_PROCESS_ABORTED_TOPIC = "compositor:process-aborted";
const DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC =
  "distribution-customization-complete";
const GFX_FEATURES_READY_TOPIC = "gfx-features-ready";
const SEARCH_ENGINE_MODIFIED_TOPIC = "browser-search-engine-modified";
const SEARCH_SERVICE_TOPIC = "browser-search-service";
const SESSIONSTORE_WINDOWS_RESTORED_TOPIC = "sessionstore-windows-restored";
const PREF_CHANGED_TOPIC = "nsPref:changed";
const GMP_PROVIDER_REGISTERED_TOPIC = "gmp-provider-registered";
const AUTO_UPDATE_PREF_CHANGE_TOPIC =
  UpdateUtils.PER_INSTALLATION_PREFS["app.update.auto"].observerTopic;
const BACKGROUND_UPDATE_PREF_CHANGE_TOPIC =
  UpdateUtils.PER_INSTALLATION_PREFS["app.update.background.enabled"]
    .observerTopic;
const SERVICES_INFO_CHANGE_TOPIC = "sync-ui-state:update";

/**
 * Enforces the parameter to a boolean value.
 * @param aValue The input value.
 * @return {Boolean|Object} If aValue is a boolean or a number, returns its truthfulness
 *         value. Otherwise, return null.
 */
function enforceBoolean(aValue) {
  if (typeof aValue !== "number" && typeof aValue !== "boolean") {
    return null;
  }
  return Boolean(aValue);
}

/**
 * Get the current browser locale.
 * @return a string with the locale or null on failure.
 */
function getBrowserLocale() {
  try {
    return Services.locale.appLocaleAsBCP47;
  } catch (e) {
    return null;
  }
}

/**
 * Get the current OS locale.
 * @return a string with the OS locale or null on failure.
 */
function getSystemLocale() {
  try {
    return Cc["@mozilla.org/intl/ospreferences;1"].getService(
      Ci.mozIOSPreferences
    ).systemLocale;
  } catch (e) {
    return null;
  }
}

/**
 * Get the current OS locales.
 * @return an array of strings with the OS locales or null on failure.
 */
function getSystemLocales() {
  try {
    return Cc["@mozilla.org/intl/ospreferences;1"].getService(
      Ci.mozIOSPreferences
    ).systemLocales;
  } catch (e) {
    return null;
  }
}

/**
 * Get the current OS regional preference locales.
 * @return an array of strings with the OS regional preference locales or null on failure.
 */
function getRegionalPrefsLocales() {
  try {
    return Cc["@mozilla.org/intl/ospreferences;1"].getService(
      Ci.mozIOSPreferences
    ).regionalPrefsLocales;
  } catch (e) {
    return null;
  }
}

function getIntlSettings() {
  return {
    requestedLocales: Services.locale.requestedLocales,
    availableLocales: Services.locale.availableLocales,
    appLocales: Services.locale.appLocalesAsBCP47,
    systemLocales: getSystemLocales(),
    regionalPrefsLocales: getRegionalPrefsLocales(),
    acceptLanguages: Services.prefs
      .getComplexValue("intl.accept_languages", Ci.nsIPrefLocalizedString)
      .data.split(",")
      .map(str => str.trim()),
  };
}

/**
 * Safely get a sysinfo property and return its value. If the property is not
 * available, return aDefault.
 *
 * @param aPropertyName the property name to get.
 * @param aDefault the value to return if aPropertyName is not available.
 * @return The property value, if available, or aDefault.
 */
function getSysinfoProperty(aPropertyName, aDefault) {
  try {
    // |getProperty| may throw if |aPropertyName| does not exist.
    return Services.sysinfo.getProperty(aPropertyName);
  } catch (e) {}

  return aDefault;
}

/**
 * Safely get a gfxInfo field and return its value. If the field is not available, return
 * aDefault.
 *
 * @param aPropertyName the property name to get.
 * @param aDefault the value to return if aPropertyName is not available.
 * @return The property value, if available, or aDefault.
 */
function getGfxField(aPropertyName, aDefault) {
  let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);

  try {
    // Accessing the field may throw if |aPropertyName| does not exist.
    let gfxProp = gfxInfo[aPropertyName];
    if (gfxProp !== undefined && gfxProp !== "") {
      return gfxProp;
    }
  } catch (e) {}

  return aDefault;
}

/**
 * Returns a substring of the input string.
 *
 * @param {String} aString The input string.
 * @param {Integer} aMaxLength The maximum length of the returned substring. If this is
 *        greater than the length of the input string, we return the whole input string.
 * @return {String} The substring or null if the input string is null.
 */
function limitStringToLength(aString, aMaxLength) {
  if (typeof aString !== "string") {
    return null;
  }
  return aString.substring(0, aMaxLength);
}

/**
 * Force a value to be a string.
 * Only if the value is null, null is returned instead.
 */
function forceToStringOrNull(aValue) {
  if (aValue === null) {
    return null;
  }

  return String(aValue);
}

/**
 * Get the information about a graphic adapter.
 *
 * @param aSuffix A suffix to add to the properties names.
 * @return An object containing the adapter properties.
 */
function getGfxAdapter(aSuffix = "") {
  // Note that gfxInfo, and so getGfxField, might return "Unknown" for the RAM on failures,
  // not null.
  let memoryMB = parseInt(getGfxField("adapterRAM" + aSuffix, null), 10);
  if (Number.isNaN(memoryMB)) {
    memoryMB = null;
  }

  return {
    description: getGfxField("adapterDescription" + aSuffix, null),
    vendorID: getGfxField("adapterVendorID" + aSuffix, null),
    deviceID: getGfxField("adapterDeviceID" + aSuffix, null),
    subsysID: getGfxField("adapterSubsysID" + aSuffix, null),
    RAM: memoryMB,
    driver: getGfxField("adapterDriver" + aSuffix, null),
    driverVendor: getGfxField("adapterDriverVendor" + aSuffix, null),
    driverVersion: getGfxField("adapterDriverVersion" + aSuffix, null),
    driverDate: getGfxField("adapterDriverDate" + aSuffix, null),
  };
}

/**
 * Encapsulates the asynchronous magic interfacing with the addon manager. The builder
 * is owned by a parent environment object and is an addon listener.
 */
function EnvironmentAddonBuilder(environment) {
  this._environment = environment;

  // The pending task blocks addon manager shutdown. It can either be the initial load
  // or a change load.
  this._pendingTask = null;

  // Have we added an observer to listen for blocklist changes that still needs to be
  // removed:
  this._gmpProviderObserverAdded = false;

  // Set to true once initial load is complete and we're watching for changes.
  this._loaded = false;

  // The state reported by the shutdown blocker if we hang shutdown.
  this._shutdownState = "Initial";
}
EnvironmentAddonBuilder.prototype = {
  /**
   * Get the initial set of addons.
   * @returns Promise<void> when the initial load is complete.
   */
  async init() {
    AddonManager.beforeShutdown.addBlocker(
      "EnvironmentAddonBuilder",
      () => this._shutdownBlocker(),
      { fetchState: () => this._shutdownState }
    );

    this._pendingTask = (async () => {
      try {
        this._shutdownState = "Awaiting _updateAddons";
        // Gather initial addons details
        await this._updateAddons();

        if (!this._environment._addonsAreFull) {
          // The addon database has not been loaded, wait for it to
          // initialize and gather full data as soon as it does.
          this._shutdownState = "Awaiting AddonManagerPrivate.databaseReady";
          await AddonManagerPrivate.databaseReady;

          // Now gather complete addons details.
          this._shutdownState = "Awaiting second _updateAddons";
          await this._updateAddons();
        }
      } catch (err) {
        this._environment._log.error("init - Exception in _updateAddons", err);
      } finally {
        this._pendingTask = null;
        this._shutdownState = "_pendingTask init complete. No longer blocking.";
      }
    })();

    return this._pendingTask;
  },

  /**
   * Register an addon listener and watch for changes.
   */
  watchForChanges() {
    this._loaded = true;
    AddonManager.addAddonListener(this);
  },

  // AddonListener
  onEnabled(addon) {
    this._onAddonChange(addon);
  },
  onDisabled(addon) {
    this._onAddonChange(addon);
  },
  onInstalled(addon) {
    this._onAddonChange(addon);
  },
  onUninstalling(addon) {
    this._onAddonChange(addon);
  },
  onUninstalled(addon) {
    this._onAddonChange(addon);
  },
  onPropertyChanged(addon, propertiesChanged) {
    // Avoid to update the telemetry environment for onPropertyChanged
    // calls that we are not actually interested in (and quarantineIgnoredByApp
    // is not expected to change at runtime, unless the entire active addons
    // entry is also replaced, e.g. on the extension being uninstalled and
    // installed again).
    if (!propertiesChanged.includes("quarantineIgnoredByUser")) {
      return;
    }
    this._onAddonChange(addon);
  },

  _onAddonChange(addon) {
    if (addon && addon.isBuiltin && !addon.isSystem) {
      return;
    }
    this._environment._log.trace("_onAddonChange");
    this._checkForChanges("addons-changed");
  },

  // nsIObserver
  observe(aSubject, aTopic) {
    this._environment._log.trace("observe - Topic " + aTopic);
    if (aTopic == GMP_PROVIDER_REGISTERED_TOPIC) {
      Services.obs.removeObserver(this, GMP_PROVIDER_REGISTERED_TOPIC);
      this._gmpProviderObserverAdded = false;
      let gmpPluginsPromise = this._getActiveGMPlugins();
      gmpPluginsPromise.then(
        gmpPlugins => {
          let { addons } = this._environment._currentEnvironment;
          addons.activeGMPlugins = gmpPlugins;
        },
        err => {
          this._environment._log.error(
            "blocklist observe: Error collecting plugins",
            err
          );
        }
      );
    }
  },

  _checkForChanges(changeReason) {
    if (this._pendingTask) {
      this._environment._log.trace(
        "_checkForChanges - task already pending, dropping change with reason " +
          changeReason
      );
      return;
    }

    this._shutdownState = "_checkForChanges awaiting _updateAddons";
    this._pendingTask = this._updateAddons().then(
      result => {
        this._pendingTask = null;
        this._shutdownState = "No longer blocking, _updateAddons resolved";
        if (result.changed) {
          this._environment._onEnvironmentChange(
            changeReason,
            result.oldEnvironment
          );
        }
      },
      err => {
        this._pendingTask = null;
        this._shutdownState = "No longer blocking, _updateAddons rejected";
        this._environment._log.error(
          "_checkForChanges: Error collecting addons",
          err
        );
      }
    );
  },

  _shutdownBlocker() {
    if (this._loaded) {
      AddonManager.removeAddonListener(this);
      if (this._gmpProviderObserverAdded) {
        Services.obs.removeObserver(this, GMP_PROVIDER_REGISTERED_TOPIC);
      }
    }

    // At startup, _pendingTask is set to a Promise that does not resolve
    // until the addons database has been read so complete details about
    // addons are available.  Returning it here will cause it to block
    // profileBeforeChange, guranteeing that full information will be
    // available by the time profileBeforeChangeTelemetry is fired.
    return this._pendingTask;
  },

  /**
   * Collect the addon data for the environment.
   *
   * This should only be called from _pendingTask; otherwise we risk
   * running this during addon manager shutdown.
   *
   * @returns Promise<Object> This returns a Promise resolved with a status object with the following members:
   *   changed - Whether the environment changed.
   *   oldEnvironment - Only set if a change occured, contains the environment data before the change.
   */
  async _updateAddons() {
    this._environment._log.trace("_updateAddons");

    let addons = {
      activeAddons: await this._getActiveAddons(),
      theme: await this._getActiveTheme(),
      activeGMPlugins: await this._getActiveGMPlugins(),
    };

    let result = {
      changed:
        !this._environment._currentEnvironment.addons ||
        !ObjectUtils.deepEqual(
          addons.activeAddons,
          this._environment._currentEnvironment.addons.activeAddons
        ),
    };

    if (result.changed) {
      this._environment._log.trace("_updateAddons: addons differ");
      result.oldEnvironment = Cu.cloneInto(
        this._environment._currentEnvironment,
        {}
      );
    }
    this._environment._currentEnvironment.addons = addons;

    return result;
  },

  /**
   * Get the addon data in object form.
   * @return Promise<object> containing the addon data.
   */
  async _getActiveAddons() {
    // Request addons, asynchronously.
    // "theme" is excluded because it is already handled by _getActiveTheme.
    let { addons: allAddons, fullData } = await AddonManager.getActiveAddons(
      AddonManagerPrivate.getAddonTypesByProvider("XPIProvider").filter(
        addonType => addonType != "theme"
      )
    );

    this._environment._addonsAreFull = fullData;
    let activeAddons = {};
    for (let addon of allAddons) {
      // Don't collect any information about the new built-in search webextensions
      if (addon.isBuiltin && !addon.isSystem) {
        continue;
      }
      // Weird addon data in the wild can lead to exceptions while collecting
      // the data.
      try {
        // Make sure to have valid dates.
        let updateDate = new Date(Math.max(0, addon.updateDate));

        activeAddons[addon.id] = {
          version: limitStringToLength(addon.version, MAX_ADDON_STRING_LENGTH),
          scope: addon.scope,
          type: addon.type,
          updateDay: Utils.millisecondsToDays(updateDate.getTime()),
          isSystem: addon.isSystem,
          isWebExtension: addon.isWebExtension,
          multiprocessCompatible: true,
        };

        // getActiveAddons() gives limited data during startup and full
        // data after the addons database is loaded.
        if (fullData) {
          let installDate = new Date(Math.max(0, addon.installDate));
          Object.assign(activeAddons[addon.id], {
            blocklisted:
              addon.blocklistState !== Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
            description: limitStringToLength(
              addon.description,
              MAX_ADDON_STRING_LENGTH
            ),
            name: limitStringToLength(addon.name, MAX_ADDON_STRING_LENGTH),
            userDisabled: enforceBoolean(addon.userDisabled),
            appDisabled: addon.appDisabled,
            foreignInstall: enforceBoolean(addon.foreignInstall),
            hasBinaryComponents: false,
            installDay: Utils.millisecondsToDays(installDate.getTime()),
            signedState: addon.signedState,
            signedTypes: JSON.stringify(addon.signedTypes),
            quarantineIgnoredByApp: enforceBoolean(
              addon.quarantineIgnoredByApp
            ),
            quarantineIgnoredByUser: enforceBoolean(
              addon.quarantineIgnoredByUser
            ),
          });
        }
      } catch (ex) {
        this._environment._log.error(
          "_getActiveAddons - An addon was discarded due to an error",
          ex
        );
        continue;
      }
    }

    return activeAddons;
  },

  /**
   * Get the currently active theme data in object form.
   * @return Promise<object> containing the active theme data.
   */
  async _getActiveTheme() {
    // Request themes, asynchronously.
    let { addons: themes } = await AddonManager.getActiveAddons(["theme"]);

    let activeTheme = {};
    // We only store information about the active theme.
    let theme = themes.find(theme => theme.isActive);
    if (theme) {
      // Make sure to have valid dates.
      let installDate = new Date(Math.max(0, theme.installDate));
      let updateDate = new Date(Math.max(0, theme.updateDate));

      activeTheme = {
        id: theme.id,
        blocklisted:
          theme.blocklistState !== Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
        description: limitStringToLength(
          theme.description,
          MAX_ADDON_STRING_LENGTH
        ),
        name: limitStringToLength(theme.name, MAX_ADDON_STRING_LENGTH),
        userDisabled: enforceBoolean(theme.userDisabled),
        appDisabled: theme.appDisabled,
        version: limitStringToLength(theme.version, MAX_ADDON_STRING_LENGTH),
        scope: theme.scope,
        foreignInstall: enforceBoolean(theme.foreignInstall),
        hasBinaryComponents: false,
        installDay: Utils.millisecondsToDays(installDate.getTime()),
        updateDay: Utils.millisecondsToDays(updateDate.getTime()),
        signedState: theme.signedState,
        signedTypes: JSON.stringify(theme.signedTypes),
      };
    }

    return activeTheme;
  },

  /**
   * Get the GMPlugins data in object form.
   *
   * @return Object containing the GMPlugins data.
   *
   * This should only be called from _pendingTask; otherwise we risk
   * running this during addon manager shutdown.
   */
  async _getActiveGMPlugins() {
    // If we haven't yet loaded the blocklist, pass back dummy data for now,
    // and add an observer to update this data as soon as we get it.
    if (!AddonManager.hasProvider("GMPProvider")) {
      if (!this._gmpProviderObserverAdded) {
        Services.obs.addObserver(this, GMP_PROVIDER_REGISTERED_TOPIC);
        this._gmpProviderObserverAdded = true;
      }
      return {
        "dummy-gmp": {
          version: "0.1",
          userDisabled: false,
          applyBackgroundUpdates: 1,
        },
      };
    }
    // Request plugins, asynchronously.
    let allPlugins = await AddonManager.getAddonsByTypes(["plugin"]);

    let activeGMPlugins = {};
    for (let plugin of allPlugins) {
      // Only get info for active GMplugins.
      if (!plugin.isGMPlugin || !plugin.isActive) {
        continue;
      }

      try {
        activeGMPlugins[plugin.id] = {
          version: plugin.version,
          userDisabled: enforceBoolean(plugin.userDisabled),
          applyBackgroundUpdates: plugin.applyBackgroundUpdates,
        };
      } catch (ex) {
        this._environment._log.error(
          "_getActiveGMPlugins - A GMPlugin was discarded due to an error",
          ex
        );
        continue;
      }
    }

    return activeGMPlugins;
  },
};

function EnvironmentCache() {
  this._log = Log.repository.getLoggerWithMessagePrefix(
    LOGGER_NAME,
    "TelemetryEnvironment::"
  );
  this._log.trace("constructor");

  this._shutdown = false;
  // Don't allow querying the search service too early to prevent
  // impacting the startup performance.
  this._canQuerySearch = false;
  // To guard against slowing down startup, defer gathering heavy environment
  // entries until the session is restored.
  this._sessionWasRestored = false;

  // A map of listeners that will be called on environment changes.
  this._changeListeners = new Map();

  // A map of watched preferences which trigger an Environment change when
  // modified. Every entry contains a recording policy (RECORD_PREF_*).
  this._watchedPrefs = DEFAULT_ENVIRONMENT_PREFS;

  this._currentEnvironment = {
    build: this._getBuild(),
    partner: this._getPartner(),
    system: this._getSystem(),
  };

  this._addObservers();

  // Build the remaining asynchronous parts of the environment. Don't register change listeners
  // until the initial environment has been built.

  let p = [this._updateSettings()];
  this._addonBuilder = new EnvironmentAddonBuilder(this);
  p.push(this._addonBuilder.init());

  this._currentEnvironment.profile = {};
  p.push(this._updateProfile());
  if (AppConstants.MOZ_BUILD_APP == "browser") {
    p.push(this._loadAttributionAsync());
  }
  p.push(this._loadAsyncUpdateSettings());
  p.push(this._loadIntlData());

  for (const [
    id,
    { branch, options },
  ] of gActiveExperimentStartupBuffer.entries()) {
    this.setExperimentActive(id, branch, options);
  }
  gActiveExperimentStartupBuffer = null;

  let setup = () => {
    this._initTask = null;
    this._startWatchingPrefs();
    this._addonBuilder.watchForChanges();
    this._updateGraphicsFeatures();
    return this.currentEnvironment;
  };

  this._initTask = Promise.all(p).then(
    () => setup(),
    err => {
      // log errors but eat them for consumers
      this._log.error("EnvironmentCache - error while initializing", err);
      return setup();
    }
  );

  // Addons may contain partial or full data depending on whether the Addons DB
  // has had a chance to load. Do we have full data yet?
  this._addonsAreFull = false;
}
EnvironmentCache.prototype = {
  /**
   * The current environment data. The returned data is cloned to avoid
   * unexpected sharing or mutation.
   * @returns object
   */
  get currentEnvironment() {
    return Cu.cloneInto(this._currentEnvironment, {});
  },

  /**
   * Wait for the current enviroment to be fully initialized.
   * @returns Promise<object>
   */
  onInitialized() {
    if (this._initTask) {
      return this._initTask;
    }
    return Promise.resolve(this.currentEnvironment);
  },

  /**
   * This gets called when the delayed init completes.
   */
  async delayedInit() {
    this._processData = await Services.sysinfo.processInfo;
    let processData = await Services.sysinfo.processInfo;
    // Remove isWow64 and isWowARM64 from processData
    // to strip it down to just CPU info
    delete processData.isWow64;
    delete processData.isWowARM64;

    let oldEnv = null;
    if (!this._initTask) {
      oldEnv = this.currentEnvironment;
    }

    this._cpuData = this._getCPUData();
    // Augment the return value from the promises with cached values
    this._cpuData = { ...processData, ...this._cpuData };

    this._currentEnvironment.system.cpu = this._getCPUData();

    if (AppConstants.platform == "win") {
      this._hddData = await Services.sysinfo.diskInfo;
      let osData = await Services.sysinfo.osInfo;

      if (!this._initTask) {
        // We've finished creating the initial env, so notify for the update
        // This is all a bit awkward because `currentEnvironment` clones
        // the object, which we need to pass to the notification, but we
        // should only notify once we've updated the current environment...
        // Ideally, _onEnvironmentChange should somehow deal with all this
        // instead of all the consumers.
        oldEnv = this.currentEnvironment;
      }

      this._osData = this._getOSData();

      // Augment the return values from the promises with cached values
      this._osData = Object.assign(osData, this._osData);

      this._currentEnvironment.system.os = this._getOSData();
      this._currentEnvironment.system.hdd = this._getHDDData();

      // Windows only values stored in processData
      this._currentEnvironment.system.isWow64 = this._getProcessData().isWow64;
      this._currentEnvironment.system.isWowARM64 =
        this._getProcessData().isWowARM64;
    }

    if (!this._initTask) {
      this._onEnvironmentChange("system-info", oldEnv);
    }
  },

  /**
   * Register a listener for environment changes.
   * @param name The name of the listener. If a new listener is registered
   *             with the same name, the old listener will be replaced.
   * @param listener function(reason, oldEnvironment) - Will receive a reason for
                     the change and the environment data before the change.
   */
  registerChangeListener(name, listener) {
    this._log.trace("registerChangeListener for " + name);
    if (this._shutdown) {
      this._log.warn("registerChangeListener - already shutdown");
      return;
    }
    this._changeListeners.set(name, listener);
  },

  /**
   * Unregister from listening to environment changes.
   * It's fine to call this on an unitialized TelemetryEnvironment.
   * @param name The name of the listener to remove.
   */
  unregisterChangeListener(name) {
    this._log.trace("unregisterChangeListener for " + name);
    if (this._shutdown) {
      this._log.warn("registerChangeListener - already shutdown");
      return;
    }
    this._changeListeners.delete(name);
  },

  setExperimentActive(id, branch, options) {
    this._log.trace(`setExperimentActive - id: ${id}, branch: ${branch}`);
    // Make sure both the id and the branch have sane lengths.
    const saneId = limitStringToLength(id, MAX_EXPERIMENT_ID_LENGTH);
    const saneBranch = limitStringToLength(
      branch,
      MAX_EXPERIMENT_BRANCH_LENGTH
    );
    if (!saneId || !saneBranch) {
      this._log.error(
        "setExperimentActive - the provided arguments are not strings."
      );
      return;
    }

    // Warn the user about any content truncation.
    if (saneId.length != id.length || saneBranch.length != branch.length) {
      this._log.warn(
        "setExperimentActive - the experiment id or branch were truncated."
      );
    }

    // Truncate the experiment type if present.
    if (options.hasOwnProperty("type")) {
      let type = limitStringToLength(options.type, MAX_EXPERIMENT_TYPE_LENGTH);
      if (type.length != options.type.length) {
        options.type = type;
        this._log.warn(
          "setExperimentActive - the experiment type was truncated."
        );
      }
    }

    // Truncate the enrollment id if present.
    if (options.hasOwnProperty("enrollmentId")) {
      let enrollmentId = limitStringToLength(
        options.enrollmentId,
        MAX_EXPERIMENT_ENROLLMENT_ID_LENGTH
      );
      if (enrollmentId.length != options.enrollmentId.length) {
        options.enrollmentId = enrollmentId;
        this._log.warn(
          "setExperimentActive - the enrollment id was truncated."
        );
      }
    }

    let oldEnvironment = Cu.cloneInto(this._currentEnvironment, {});
    // Add the experiment annotation.
    let experiments = this._currentEnvironment.experiments || {};
    experiments[saneId] = { branch: saneBranch };
    if (options.hasOwnProperty("type")) {
      experiments[saneId].type = options.type;
    }
    if (options.hasOwnProperty("enrollmentId")) {
      experiments[saneId].enrollmentId = options.enrollmentId;
    }
    this._currentEnvironment.experiments = experiments;
    // Notify of the change.
    this._onEnvironmentChange("experiment-annotation-changed", oldEnvironment);
  },

  setExperimentInactive(id) {
    this._log.trace("setExperimentInactive");
    let experiments = this._currentEnvironment.experiments || {};
    if (id in experiments) {
      // Only attempt to notify if a previous annotation was found and removed.
      let oldEnvironment = Cu.cloneInto(this._currentEnvironment, {});
      // Remove the experiment annotation.
      delete this._currentEnvironment.experiments[id];
      // Notify of the change.
      this._onEnvironmentChange(
        "experiment-annotation-changed",
        oldEnvironment
      );
    }
  },

  getActiveExperiments() {
    return Cu.cloneInto(this._currentEnvironment.experiments || {}, {});
  },

  shutdown() {
    this._log.trace("shutdown");
    this._shutdown = true;
  },

  /**
   * Only used in tests, set the preferences to watch.
   * @param aPreferences A map of preferences names and their recording policy.
   */
  _watchPreferences(aPreferences) {
    this._stopWatchingPrefs();
    this._watchedPrefs = aPreferences;
    this._updateSettings();
    this._startWatchingPrefs();
  },

  /**
   * Get an object containing the values for the watched preferences. Depending on the
   * policy, the value for a preference or whether it was changed by user is reported.
   *
   * @return An object containing the preferences values.
   */
  _getPrefData() {
    let prefData = {};
    for (let [pref, policy] of this._watchedPrefs.entries()) {
      let prefValue = this._getPrefValue(pref, policy.what);

      if (prefValue === undefined) {
        continue;
      }

      prefData[pref] = prefValue;
    }
    return prefData;
  },

  /**
   * Get the value of a preference given the preference name and the policy.
   * @param pref Name of the preference.
   * @param what Policy of the preference.
   *
   * @returns The value we need to store for this preference. It can be undefined
   *          or null if the preference is invalid or has a value set by the user.
   */
  _getPrefValue(pref, what) {
    // Check the policy for the preference and decide if we need to store its value
    // or whether it changed from the default value.
    let prefType = Services.prefs.getPrefType(pref);

    if (
      what == TelemetryEnvironment.RECORD_DEFAULTPREF_VALUE ||
      what == TelemetryEnvironment.RECORD_DEFAULTPREF_STATE
    ) {
      // For default prefs, make sure they exist
      if (prefType == Ci.nsIPrefBranch.PREF_INVALID) {
        return undefined;
      }
    } else if (!Services.prefs.prefHasUserValue(pref)) {
      // For user prefs, make sure they are set
      return undefined;
    }

    if (what == TelemetryEnvironment.RECORD_DEFAULTPREF_STATE) {
      return "<set>";
    } else if (what == TelemetryEnvironment.RECORD_PREF_STATE) {
      return "<user-set>";
    } else if (prefType == Ci.nsIPrefBranch.PREF_STRING) {
      return Services.prefs.getStringPref(pref);
    } else if (prefType == Ci.nsIPrefBranch.PREF_BOOL) {
      return Services.prefs.getBoolPref(pref);
    } else if (prefType == Ci.nsIPrefBranch.PREF_INT) {
      return Services.prefs.getIntPref(pref);
    } else if (prefType == Ci.nsIPrefBranch.PREF_INVALID) {
      return null;
    }
    throw new Error(
      `Unexpected preference type ("${prefType}") for "${pref}".`
    );
  },

  QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]),

  /**
   * Start watching the preferences.
   */
  _startWatchingPrefs() {
    this._log.trace("_startWatchingPrefs - " + this._watchedPrefs);

    Services.prefs.addObserver("", this, true);
  },

  _onPrefChanged(aData) {
    this._log.trace("_onPrefChanged");
    let oldEnvironment = Cu.cloneInto(this._currentEnvironment, {});
    this._currentEnvironment.settings.userPrefs[aData] = this._getPrefValue(
      aData,
      this._watchedPrefs.get(aData).what
    );
    this._onEnvironmentChange("pref-changed", oldEnvironment);
  },

  /**
   * Do not receive any more change notifications for the preferences.
   */
  _stopWatchingPrefs() {
    this._log.trace("_stopWatchingPrefs");

    Services.prefs.removeObserver("", this);
  },

  _addObservers() {
    // Watch the search engine change and service topics.
    Services.obs.addObserver(this, SESSIONSTORE_WINDOWS_RESTORED_TOPIC);
    Services.obs.addObserver(this, COMPOSITOR_CREATED_TOPIC);
    Services.obs.addObserver(this, COMPOSITOR_PROCESS_ABORTED_TOPIC);
    Services.obs.addObserver(this, DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC);
    Services.obs.addObserver(this, GFX_FEATURES_READY_TOPIC);
    Services.obs.addObserver(this, SEARCH_ENGINE_MODIFIED_TOPIC);
    Services.obs.addObserver(this, SEARCH_SERVICE_TOPIC);
    Services.obs.addObserver(this, AUTO_UPDATE_PREF_CHANGE_TOPIC);
    Services.obs.addObserver(this, BACKGROUND_UPDATE_PREF_CHANGE_TOPIC);
    Services.obs.addObserver(this, SERVICES_INFO_CHANGE_TOPIC);
  },

  _removeObservers() {
    Services.obs.removeObserver(this, SESSIONSTORE_WINDOWS_RESTORED_TOPIC);
    Services.obs.removeObserver(this, COMPOSITOR_CREATED_TOPIC);
    Services.obs.removeObserver(this, COMPOSITOR_PROCESS_ABORTED_TOPIC);
    try {
      Services.obs.removeObserver(
        this,
        DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC
      );
    } catch (ex) {}
    Services.obs.removeObserver(this, GFX_FEATURES_READY_TOPIC);
    Services.obs.removeObserver(this, SEARCH_ENGINE_MODIFIED_TOPIC);
    Services.obs.removeObserver(this, SEARCH_SERVICE_TOPIC);
    Services.obs.removeObserver(this, AUTO_UPDATE_PREF_CHANGE_TOPIC);
    Services.obs.removeObserver(this, BACKGROUND_UPDATE_PREF_CHANGE_TOPIC);
    Services.obs.removeObserver(this, SERVICES_INFO_CHANGE_TOPIC);
  },

  observe(aSubject, aTopic, aData) {
    this._log.trace("observe - aTopic: " + aTopic + ", aData: " + aData);
    switch (aTopic) {
      case SEARCH_ENGINE_MODIFIED_TOPIC:
        if (
          aData != "engine-default" &&
          aData != "engine-default-private" &&
          aData != "engine-changed"
        ) {
          return;
        }
        if (
          aData == "engine-changed" &&
          aSubject.QueryInterface(Ci.nsISearchEngine) &&
          Services.search.defaultEngine != aSubject
        ) {
          return;
        }
        // Record the new default search choice and send the change notification.
        this._onSearchEngineChange();
        break;
      case SEARCH_SERVICE_TOPIC:
        if (aData != "init-complete") {
          return;
        }
        // Now that the search engine init is complete, record the default search choice.
        this._canQuerySearch = true;
        this._updateSearchEngine();
        break;
      case GFX_FEATURES_READY_TOPIC:
      case COMPOSITOR_CREATED_TOPIC:
        // Full graphics information is not available until we have created at
        // least one off-main-thread-composited window. Thus we wait for the
        // first compositor to be created and then query nsIGfxInfo again.
        this._updateGraphicsFeatures();
        break;
      case COMPOSITOR_PROCESS_ABORTED_TOPIC:
        // Our compositor process has been killed for whatever reason, so refresh
        // our reported graphics features and trigger an environment change.
        this._onCompositorProcessAborted();
        break;
      case DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC:
        // Distribution customizations are applied after final-ui-startup. query
        // partner prefs again when they are ready.
        this._updatePartner();
        Services.obs.removeObserver(this, aTopic);
        break;
      case SESSIONSTORE_WINDOWS_RESTORED_TOPIC:
        this._sessionWasRestored = true;
        // Make sure to initialize the search service once we've done restoring
        // the windows, so that we don't risk loosing search data.
        Services.search.init();
        // The default browser check could take some time, so just call it after
        // the session was restored.
        this._updateDefaultBrowser();
        break;
      case PREF_CHANGED_TOPIC:
        let options = this._watchedPrefs.get(aData);
        if (options && !options.requiresRestart) {
          this._onPrefChanged(aData);
        }
        break;
      case AUTO_UPDATE_PREF_CHANGE_TOPIC:
        this._currentEnvironment.settings.update.autoDownload = aData == "true";
        break;
      case BACKGROUND_UPDATE_PREF_CHANGE_TOPIC:
        this._currentEnvironment.settings.update.background = aData == "true";
        break;
      case SERVICES_INFO_CHANGE_TOPIC:
        this._updateServicesInfo();
        break;
    }
  },

  /**
   * Update the default search engine value.
   */
  _updateSearchEngine() {
    if (!this._canQuerySearch) {
      this._log.trace("_updateSearchEngine - ignoring early call");
      return;
    }

    this._log.trace(
      "_updateSearchEngine - isInitialized: " + Services.search.isInitialized
    );
    if (!Services.search.isInitialized) {
      return;
    }

    // Make sure we have a settings section.
    this._currentEnvironment.settings = this._currentEnvironment.settings || {};

    // Update the search engine entry in the current environment.
    const defaultEngineInfo = Services.search.getDefaultEngineInfo();
    this._currentEnvironment.settings.defaultSearchEngine =
      defaultEngineInfo.defaultSearchEngine;
    this._currentEnvironment.settings.defaultSearchEngineData = {
      ...defaultEngineInfo.defaultSearchEngineData,
    };
    if ("defaultPrivateSearchEngine" in defaultEngineInfo) {
      this._currentEnvironment.settings.defaultPrivateSearchEngine =
        defaultEngineInfo.defaultPrivateSearchEngine;
    }
    if ("defaultPrivateSearchEngineData" in defaultEngineInfo) {
      this._currentEnvironment.settings.defaultPrivateSearchEngineData = {
        ...defaultEngineInfo.defaultPrivateSearchEngineData,
      };
    }
  },

  /**
   * Update the default search engine value and trigger the environment change.
   */
  _onSearchEngineChange() {
    this._log.trace("_onSearchEngineChange");

    // Finally trigger the environment change notification.
    let oldEnvironment = Cu.cloneInto(this._currentEnvironment, {});
    this._updateSearchEngine();
    this._onEnvironmentChange("search-engine-changed", oldEnvironment);
  },

  /**
   * Refresh the Telemetry environment and trigger an environment change due to
   * a change in compositor process (normally this will mean we've fallen back
   * from out-of-process to in-process compositing).
   */
  _onCompositorProcessAborted() {
    this._log.trace("_onCompositorProcessAborted");

    // Trigger the environment change notification.
    let oldEnvironment = Cu.cloneInto(this._currentEnvironment, {});
    this._updateGraphicsFeatures();
    this._onEnvironmentChange("gfx-features-changed", oldEnvironment);
  },

  /**
   * Update the graphics features object.
   */
  _updateGraphicsFeatures() {
    let gfxData = this._currentEnvironment.system.gfx;
    try {
      let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
      gfxData.features = gfxInfo.getFeatures();
    } catch (e) {
      this._log.error("nsIGfxInfo.getFeatures() caught error", e);
    }
  },

  /**
   * Update the partner prefs.
   */
  _updatePartner() {
    this._currentEnvironment.partner = this._getPartner();
  },

  /**
   * Get the build data in object form.
   * @return Object containing the build data.
   */
  _getBuild() {
    let buildData = {
      applicationId: Services.appinfo.ID || null,
      applicationName: Services.appinfo.name || null,
      architecture: Services.sysinfo.get("arch"),
      buildId: Services.appinfo.appBuildID || null,
      version: Services.appinfo.version || null,
      vendor: Services.appinfo.vendor || null,
      displayVersion: AppConstants.MOZ_APP_VERSION_DISPLAY || null,
      platformVersion: Services.appinfo.platformVersion || null,
      xpcomAbi: Services.appinfo.XPCOMABI,
      updaterAvailable: AppConstants.MOZ_UPDATER,
    };

    return buildData;
  },

  /**
   * Determine if we're the default browser.
   * @returns null on error, true if we are the default browser, or false otherwise.
   */
  _isDefaultBrowser() {
    let isDefault = (service, ...args) => {
      try {
        return !!service.isDefaultBrowser(...args);
      } catch (ex) {
        this._log.error(
          "_isDefaultBrowser - Could not determine if default browser",
          ex
        );
        return null;
      }
    };

    if (!("@mozilla.org/browser/shell-service;1" in Cc)) {
      this._log.info(
        "_isDefaultBrowser - Could not obtain browser shell service"
      );
      return null;
    }

    try {
      let { ShellService } = ChromeUtils.importESModule(
        "resource:///modules/ShellService.sys.mjs"
      );
      // This uses the same set of flags used by the pref pane.
      return isDefault(ShellService, false, true);
    } catch (ex) {
      this._log.error("_isDefaultBrowser - Could not obtain shell service JSM");
    }

    try {
      let shellService = Cc["@mozilla.org/browser/shell-service;1"].getService(
        Ci.nsIShellService
      );
      // This uses the same set of flags used by the pref pane.
      return isDefault(shellService, true);
    } catch (ex) {
      this._log.error("_isDefaultBrowser - Could not obtain shell service", ex);
      return null;
    }
  },

  _updateDefaultBrowser() {
    if (AppConstants.platform === "android") {
      return;
    }
    // Make sure to have a settings section.
    this._currentEnvironment.settings = this._currentEnvironment.settings || {};
    this._currentEnvironment.settings.isDefaultBrowser = this
      ._sessionWasRestored
      ? this._isDefaultBrowser()
      : null;
  },

  /**
   * Update the cached settings data.
   */
  _updateSettings() {
    let updateChannel = null;
    try {
      updateChannel = Utils.getUpdateChannel();
    } catch (e) {}

    this._currentEnvironment.settings = {
      blocklistEnabled: Services.prefs.getBoolPref(
        PREF_BLOCKLIST_ENABLED,
        true
      ),
      e10sEnabled: Services.appinfo.browserTabsRemoteAutostart,
      e10sMultiProcesses: Services.appinfo.maxWebProcessCount,
      fissionEnabled: Services.appinfo.fissionAutostart,
      telemetryEnabled:
        Services.prefs.getBoolPref(PREF_TELEMETRY_ENABLED, false) === true,
      locale: getBrowserLocale(),
      // We need to wait for browser-delayed-startup-finished to ensure that the locales
      // have settled, once that's happened we can get the intl data directly.
      intl: Policy._intlLoaded ? getIntlSettings() : {},
      update: {
        channel: updateChannel,
        enabled:
          AppConstants.MOZ_UPDATER && !lazy.UpdateServiceStub.updateDisabled,
      },
      userPrefs: this._getPrefData(),
      sandbox: this._getSandboxData(),
    };

    // Services.appinfo.launcherProcessState is not available in all build
    // configurations, in which case an exception may be thrown.
    try {
      this._currentEnvironment.settings.launcherProcessState =
        Services.appinfo.launcherProcessState;
    } catch (e) {}

    this._currentEnvironment.settings.addonCompatibilityCheckEnabled =
      AddonManager.checkCompatibility;

    if (AppConstants.MOZ_BUILD_APP == "browser") {
      this._updateAttribution();
    }
    this._updateDefaultBrowser();
    this._updateSearchEngine();
    this._loadAsyncUpdateSettingsFromCache();
  },

  _getSandboxData() {
    let effectiveContentProcessLevel = null;
    let contentWin32kLockdownState = null;
    try {
      let sandboxSettings = Cc[
        "@mozilla.org/sandbox/sandbox-settings;1"
      ].getService(Ci.mozISandboxSettings);
      effectiveContentProcessLevel =
        sandboxSettings.effectiveContentSandboxLevel;

      // The possible values for this are defined in the ContentWin32kLockdownState
      // enum in security/sandbox/common/SandboxSettings.h
      contentWin32kLockdownState = sandboxSettings.contentWin32kLockdownState;
    } catch (e) {}
    return {
      effectiveContentProcessLevel,
      contentWin32kLockdownState,
    };
  },

  /**
   * Update the cached profile data.
   * @returns Promise<> resolved when the I/O is complete.
   */
  async _updateProfile() {
    let profileAccessor = await lazy.ProfileAge();

    let creationDate = await profileAccessor.created;
    let resetDate = await profileAccessor.reset;
    let firstUseDate = await profileAccessor.firstUse;
    let recoveredFromBackup = await profileAccessor.recoveredFromBackup;

    this._currentEnvironment.profile.creationDate =
      Utils.millisecondsToDays(creationDate);
    if (resetDate) {
      this._currentEnvironment.profile.resetDate =
        Utils.millisecondsToDays(resetDate);
    }
    if (firstUseDate) {
      this._currentEnvironment.profile.firstUseDate =
        Utils.millisecondsToDays(firstUseDate);
    }
    if (recoveredFromBackup) {
      this._currentEnvironment.profile.recoveredFromBackup =
        Utils.millisecondsToDays(recoveredFromBackup);
    }
  },

  /**
   * Load the attribution data object and updates the environment.
   * @returns Promise<> resolved when the I/O is complete.
   */
  async _loadAttributionAsync() {
    try {
      await lazy.AttributionCode.getAttrDataAsync();
    } catch (e) {
      // The AttributionCode.sys.mjs module might not be always available
      // (e.g. tests). Gracefully handle this.
      return;
    }
    this._updateAttribution();
  },

  /**
   * Update the environment with the cached attribution data.
   */
  _updateAttribution() {
    let data = null;
    try {
      data = lazy.AttributionCode.getCachedAttributionData();
    } catch (e) {
      // The AttributionCode.sys.mjs module might not be always available
      // (e.g. tests). Gracefully handle this.
    }

    if (!data || !Object.keys(data).length) {
      return;
    }

    let attributionData = {};
    for (let key in data) {
      attributionData[key] =
        // At least one of these may be boolean, and limitStringToLength
        // returns null for non-string inputs.
        typeof data[key] === "string"
          ? limitStringToLength(data[key], MAX_ATTRIBUTION_STRING_LENGTH)
          : data[key];
    }
    this._currentEnvironment.settings.attribution = attributionData;
  },

  /**
   * Load the per-installation update settings, cache them, and add them to the
   * environment.
   */
  async _loadAsyncUpdateSettings() {
    if (AppConstants.MOZ_UPDATER) {
      this._updateAutoDownloadCache =
        await UpdateUtils.getAppUpdateAutoEnabled();
      this._updateBackgroundCache = await UpdateUtils.readUpdateConfigSetting(
        "app.update.background.enabled"
      );
    } else {
      this._updateAutoDownloadCache = false;
      this._updateBackgroundCache = false;
    }
    this._loadAsyncUpdateSettingsFromCache();
  },

  /**
   * Update the environment with the cached values for per-installation update
   * settings.
   */
  _loadAsyncUpdateSettingsFromCache() {
    if (this._updateAutoDownloadCache !== undefined) {
      this._currentEnvironment.settings.update.autoDownload =
        this._updateAutoDownloadCache;
    }
    if (this._updateBackgroundCache !== undefined) {
      this._currentEnvironment.settings.update.background =
        this._updateBackgroundCache;
    }
  },

  /**
   * Get i18n data about the system.
   * @return A promise of completion.
   */
  async _loadIntlData() {
    // Wait for the startup topic.
    await Policy._browserDelayedStartup();
    this._currentEnvironment.settings.intl = getIntlSettings();
    Policy._intlLoaded = true;
  },
  // This exists as a separate function for testing.
  async _getFxaSignedInUser() {
    return lazy.fxAccounts.getSignedInUser();
  },

  async _updateServicesInfo() {
    let syncEnabled = false;
    let accountEnabled = false;
    let weaveService =
      Cc["@mozilla.org/weave/service;1"].getService().wrappedJSObject;
    syncEnabled = weaveService && weaveService.enabled;
    if (syncEnabled) {
      // All sync users are account users, definitely.
      accountEnabled = true;
    } else {
      // Not all account users are sync users. See if they're signed into FxA.
      try {
        let user = await this._getFxaSignedInUser();
        if (user) {
          accountEnabled = true;
        }
      } catch (e) {
        // We don't know. This might be a transient issue which will clear
        // itself up later, but the information in telemetry is quite possibly stale
        // (this is called from a change listener), so clear it out to avoid
        // reporting data which might be wrong until we can figure it out.
        delete this._currentEnvironment.services;
        this._log.error("_updateServicesInfo() caught error", e);
        return;
      }
    }
    this._currentEnvironment.services = {
      accountEnabled,
      syncEnabled,
    };
  },

  /**
   * Get the partner data in object form.
   * @return Object containing the partner data.
   */
  _getPartner() {
    let defaults = Services.prefs.getDefaultBranch(null);
    let partnerData = {
      distributionId: defaults.getStringPref(PREF_DISTRIBUTION_ID, null),
      distributionVersion: defaults.getCharPref(
        PREF_DISTRIBUTION_VERSION,
        null
      ),
      partnerId: defaults.getCharPref(PREF_PARTNER_ID, null),
      distributor: defaults.getCharPref(PREF_DISTRIBUTOR, null),
      distributorChannel: defaults.getCharPref(PREF_DISTRIBUTOR_CHANNEL, null),
    };

    // Get the PREF_APP_PARTNER_BRANCH branch and append its children to partner data.
    let partnerBranch = Services.prefs.getDefaultBranch(
      PREF_APP_PARTNER_BRANCH
    );
    partnerData.partnerNames = partnerBranch.getChildList("");

    return partnerData;
  },

  _cpuData: null,
  /**
   * Get the CPU information.
   * @return Object containing the CPU information data.
   */
  _getCPUData() {
    if (this._cpuData) {
      return this._cpuData;
    }

    this._cpuData = {};

    const CPU_EXTENSIONS = [
      "hasMMX",
      "hasSSE",
      "hasSSE2",
      "hasSSE3",
      "hasSSSE3",
      "hasSSE4A",
      "hasSSE4_1",
      "hasSSE4_2",
      "hasAVX",
      "hasAVX2",
      "hasAES",
      "hasEDSP",
      "hasARMv6",
      "hasARMv7",
      "hasNEON",
      "hasUserCET",
    ];

    // Enumerate the available CPU extensions.
    let availableExts = [];
    for (let ext of CPU_EXTENSIONS) {
      if (getSysinfoProperty(ext, false)) {
        availableExts.push(ext);
      }
    }

    this._cpuData.extensions = availableExts;

    return this._cpuData;
  },

  _processData: null,
  /**
   * Get the process information.
   * @return Object containing the process information data.
   */
  _getProcessData() {
    if (this._processData) {
      return this._processData;
    }
    return {};
  },

  /**
   * Get the device information, if we are on a portable device.
   * @return Object containing the device information data, or null if
   * not a portable device.
   */
  _getDeviceData() {
    if (AppConstants.platform !== "android") {
      return null;
    }

    return {
      model: getSysinfoProperty("device", null),
      manufacturer: getSysinfoProperty("manufacturer", null),
      hardware: getSysinfoProperty("hardware", null),
      isTablet: getSysinfoProperty("tablet", null),
    };
  },

  _osData: null,
  /**
   * Get the OS information.
   * @return Object containing the OS data.
   */
  _getOSData() {
    if (this._osData) {
      return this._osData;
    }
    this._osData = {
      name: forceToStringOrNull(getSysinfoProperty("name", null)),
      version: forceToStringOrNull(getSysinfoProperty("version", null)),
      locale: forceToStringOrNull(getSystemLocale()),
    };

    if (AppConstants.platform == "android") {
      this._osData.kernelVersion = forceToStringOrNull(
        getSysinfoProperty("kernel_version", null)
      );
    } else if (AppConstants.platform == "linux") {
      this._osData.distro = forceToStringOrNull(
        getSysinfoProperty("distro", null)
      );
      this._osData.distroVersion = forceToStringOrNull(
        getSysinfoProperty("distroVersion", null)
      );
    } else if (AppConstants.platform === "win") {
      // The path to the "UBR" key, queried to get additional version details on Windows.
      const WINDOWS_UBR_KEY_PATH =
        "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";

      let versionInfo = lazy.WindowsVersionInfo.get({ throwOnError: false });
      this._osData.servicePackMajor = versionInfo.servicePackMajor;
      this._osData.servicePackMinor = versionInfo.servicePackMinor;
      this._osData.windowsBuildNumber = versionInfo.buildNumber;
      // We only need the UBR if we're at or above Windows 10.
      if (
        typeof this._osData.version === "string" &&
        Services.vc.compare(this._osData.version, "10") >= 0
      ) {
        // Query the UBR key and only add it to the environment if it's available.
        // |readRegKey| doesn't throw, but rather returns 'undefined' on error.
        let ubr = lazy.WindowsRegistry.readRegKey(
          Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
          WINDOWS_UBR_KEY_PATH,
          "UBR",
          Ci.nsIWindowsRegKey.WOW64_64
        );
        this._osData.windowsUBR = ubr !== undefined ? ubr : null;
      }
    }

    return this._osData;
  },

  _hddData: null,
  /**
   * Get the HDD information.
   * @return Object containing the HDD data.
   */
  _getHDDData() {
    if (this._hddData) {
      return this._hddData;
    }
    let nullData = { model: null, revision: null, type: null };
    return { profile: nullData, binary: nullData, system: nullData };
  },

  /**
   * Get registered security product information.
   * @return Object containing the security product data
   */
  _getSecurityAppData() {
    const maxStringLength = 256;

    const keys = [
      ["registeredAntiVirus", "antivirus"],
      ["registeredAntiSpyware", "antispyware"],
      ["registeredFirewall", "firewall"],
    ];

    let result = {};

    for (let [inKey, outKey] of keys) {
      let prop = getSysinfoProperty(inKey, null);
      if (prop) {
        prop = limitStringToLength(prop, maxStringLength).split(";");
      }

      result[outKey] = prop;
    }

    return result;
  },

  /**
   * Get the GFX information.
   * @return Object containing the GFX data.
   */
  _getGFXData() {
    let gfxData = {
      D2DEnabled: getGfxField("D2DEnabled", null),
      DWriteEnabled: getGfxField("DWriteEnabled", null),
      ContentBackend: getGfxField("ContentBackend", null),
      Headless: getGfxField("isHeadless", null),
      EmbeddedInFirefoxReality: getGfxField("EmbeddedInFirefoxReality", null),
      TargetFrameRate: getGfxField("TargetFrameRate", null),
      textScaleFactor: getGfxField("textScaleFactor", null),

      // The following line is disabled due to main thread jank and will be enabled
      // again as part of bug 1154500.
      // DWriteVersion: getGfxField("DWriteVersion", null),
      adapters: [],
      monitors: [],
      features: {},
    };

    if (AppConstants.platform !== "android") {
      let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
      try {
        gfxData.monitors = gfxInfo.getMonitors();
      } catch (e) {
        this._log.error("nsIGfxInfo.getMonitors() caught error", e);
      }
    }

    try {
      let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
      gfxData.features = gfxInfo.getFeatures();
    } catch (e) {
      this._log.error("nsIGfxInfo.getFeatures() caught error", e);
    }

    // GfxInfo does not yet expose a way to iterate through all the adapters.
    gfxData.adapters.push(getGfxAdapter(""));
    gfxData.adapters[0].GPUActive = true;

    // If we have a second adapter add it to the gfxData.adapters section.
    let hasGPU2 = getGfxField("adapterDeviceID2", null) !== null;
    if (!hasGPU2) {
      this._log.trace("_getGFXData - Only one display adapter detected.");
      return gfxData;
    }

    this._log.trace("_getGFXData - Two display adapters detected.");

    gfxData.adapters.push(getGfxAdapter("2"));
    gfxData.adapters[1].GPUActive = getGfxField("isGPU2Active", null);

    return gfxData;
  },

  /**
   * Get the system data in object form.
   * @return Object containing the system data.
   */
  _getSystem() {
    let memoryMB = getSysinfoProperty("memsize", null);
    if (memoryMB) {
      // Send RAM size in megabytes. Rounding because sysinfo doesn't
      // always provide RAM in multiples of 1024.
      memoryMB = Math.round(memoryMB / 1024 / 1024);
    }

    let virtualMB = getSysinfoProperty("virtualmemsize", null);
    if (virtualMB) {
      // Send the total virtual memory size in megabytes. Rounding because
      // sysinfo doesn't always provide RAM in multiples of 1024.
      virtualMB = Math.round(virtualMB / 1024 / 1024);
    }

    let data = {
      memoryMB,
      virtualMaxMB: virtualMB,
      cpu: this._getCPUData(),
      os: this._getOSData(),
      hdd: this._getHDDData(),
      gfx: this._getGFXData(),
      appleModelId: getSysinfoProperty("appleModelId", null),
      hasWinPackageId: getSysinfoProperty("hasWinPackageId", null),
    };

    if (AppConstants.platform === "win") {
      // This is only sent for Mozilla produced MSIX packages
      let winPackageFamilyName = getSysinfoProperty("winPackageFamilyName", "");
      if (
        winPackageFamilyName.startsWith("Mozilla.") ||
        winPackageFamilyName.startsWith("MozillaCorporation.")
      ) {
        data = { winPackageFamilyName, ...data };
      }
      data = { ...this._getProcessData(), ...data };
      data.sec = this._getSecurityAppData();
    } else if (AppConstants.platform == "android") {
      data.device = this._getDeviceData();
    }

    return data;
  },

  _onEnvironmentChange(what, oldEnvironment) {
    this._log.trace("_onEnvironmentChange for " + what);

    // We are already skipping change events in _checkChanges if there is a pending change task running.
    if (this._shutdown) {
      this._log.trace("_onEnvironmentChange - Already shut down.");
      return;
    }

    if (ObjectUtils.deepEqual(this._currentEnvironment, oldEnvironment)) {
      this._log.trace("_onEnvironmentChange - Environment didn't change");
      return;
    }

    for (let [name, listener] of this._changeListeners) {
      try {
        this._log.debug("_onEnvironmentChange - calling " + name);
        listener(what, oldEnvironment);
      } catch (e) {
        this._log.error(
          "_onEnvironmentChange - listener " + name + " caught error",
          e
        );
      }
    }
  },

  reset() {
    this._shutdown = false;
  },
};
PK
!<oV��'A'A(modules/TelemetryReportingPolicy.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Log } from "resource://gre/modules/Log.sys.mjs";
import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs";

import { Observers } from "resource://services-common/observers.sys.mjs";
import { TelemetryUtils } from "resource://gre/modules/TelemetryUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  TelemetrySend: "resource://gre/modules/TelemetrySend.sys.mjs",
});

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetryReportingPolicy::";

// Oldest year to allow in date preferences. The FHR infobar was implemented in
// 2012 and no dates older than that should be encountered.
const OLDEST_ALLOWED_ACCEPTANCE_YEAR = 2012;

const PREF_BRANCH = "datareporting.policy.";

// The following preferences are deprecated and will be purged during the preferences
// migration process.
const DEPRECATED_FHR_PREFS = [
  PREF_BRANCH + "dataSubmissionPolicyAccepted",
  PREF_BRANCH + "dataSubmissionPolicyBypassAcceptance",
  PREF_BRANCH + "dataSubmissionPolicyResponseType",
  PREF_BRANCH + "dataSubmissionPolicyResponseTime",
];

// How much time until we display the data choices notification bar, on the first run.
const NOTIFICATION_DELAY_FIRST_RUN_MSEC = 60 * 1000; // 60s
// Same as above, for the next runs.
const NOTIFICATION_DELAY_NEXT_RUNS_MSEC = 10 * 1000; // 10s

/**
 * This is a policy object used to override behavior within this module.
 * Tests override properties on this object to allow for control of behavior
 * that would otherwise be very hard to cover.
 */
export var Policy = {
  now: () => new Date(),
  setShowInfobarTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
  clearShowInfobarTimeout: id => clearTimeout(id),
  fakeSessionRestoreNotification: () => {
    TelemetryReportingPolicyImpl.observe(
      null,
      "sessionstore-windows-restored",
      null
    );
  },
};

/**
 * Represents a request to display data policy.
 *
 * Receivers of these instances are expected to call one or more of the on*
 * functions when events occur.
 *
 * When one of these requests is received, the first thing a callee should do
 * is present notification to the user of the data policy. When the notice
 * is displayed to the user, the callee should call `onUserNotifyComplete`.
 *
 * If for whatever reason the callee could not display a notice,
 * it should call `onUserNotifyFailed`.
 *
 * @param {Object} aLog The log object used to log the error in case of failures.
 */
function NotifyPolicyRequest(aLog) {
  this._log = aLog;
}

NotifyPolicyRequest.prototype = Object.freeze({
  /**
   * Called when the user is notified of the policy.
   */
  onUserNotifyComplete() {
    return TelemetryReportingPolicyImpl._userNotified();
  },

  /**
   * Called when there was an error notifying the user about the policy.
   *
   * @param error
   *        (Error) Explains what went wrong.
   */
  onUserNotifyFailed(error) {
    this._log.error("onUserNotifyFailed - " + error);
  },
});

export var TelemetryReportingPolicy = {
  // The current policy version number. If the version number stored in the prefs
  // is smaller than this, data upload will be disabled until the user is re-notified
  // about the policy changes.
  DEFAULT_DATAREPORTING_POLICY_VERSION: 1,

  /**
   * Setup the policy.
   */
  setup() {
    return TelemetryReportingPolicyImpl.setup();
  },

  /**
   * Shutdown and clear the policy.
   */
  shutdown() {
    return TelemetryReportingPolicyImpl.shutdown();
  },

  /**
   * Check if we are allowed to upload data. In order to submit data both these conditions
   * should be true:
   * - The data submission preference should be true.
   * - The datachoices infobar should have been displayed.
   *
   * @return {Boolean} True if we are allowed to upload data, false otherwise.
   */
  canUpload() {
    return TelemetryReportingPolicyImpl.canUpload();
  },

  /**
   * Check if this is the first time the browser ran.
   */
  isFirstRun() {
    return TelemetryReportingPolicyImpl.isFirstRun();
  },

  /**
   * Test only method, restarts the policy.
   */
  reset() {
    return TelemetryReportingPolicyImpl.reset();
  },

  /**
   * Test only method, used to check if user is notified of the policy in tests.
   */
  testIsUserNotified() {
    return TelemetryReportingPolicyImpl.isUserNotifiedOfCurrentPolicy;
  },

  /**
   * Test only method, used to simulate the infobar being shown in xpcshell tests.
   */
  testInfobarShown() {
    return TelemetryReportingPolicyImpl._userNotified();
  },

  /**
   * Test only method, used to trigger an update of the "first run" state.
   */
  testUpdateFirstRun() {
    TelemetryReportingPolicyImpl._isFirstRun = undefined;
    TelemetryReportingPolicyImpl.isFirstRun();
  },
};

var TelemetryReportingPolicyImpl = {
  _logger: null,
  // Keep track of the notification status if user wasn't notified already.
  _notificationInProgress: false,
  // The timer used to show the datachoices notification at startup.
  _startupNotificationTimerId: null,
  // Keep track of the first session state, as the related preference
  // is flipped right after the browser starts.
  _isFirstRun: undefined,

  get _log() {
    if (!this._logger) {
      this._logger = Log.repository.getLoggerWithMessagePrefix(
        LOGGER_NAME,
        LOGGER_PREFIX
      );
    }

    return this._logger;
  },

  /**
   * Get the date the policy was notified.
   * @return {Object} A date object or null on errors.
   */
  get dataSubmissionPolicyNotifiedDate() {
    let prefString = Services.prefs.getStringPref(
      TelemetryUtils.Preferences.AcceptedPolicyDate,
      "0"
    );
    let valueInteger = parseInt(prefString, 10);

    // Bail out if we didn't store any value yet.
    if (valueInteger == 0) {
      this._log.info(
        "get dataSubmissionPolicyNotifiedDate - No date stored yet."
      );
      return null;
    }

    // If an invalid value is saved in the prefs, bail out too.
    if (Number.isNaN(valueInteger)) {
      this._log.error(
        "get dataSubmissionPolicyNotifiedDate - Invalid date stored."
      );
      return null;
    }

    // Make sure the notification date is newer then the oldest allowed date.
    let date = new Date(valueInteger);
    if (date.getFullYear() < OLDEST_ALLOWED_ACCEPTANCE_YEAR) {
      this._log.error(
        "get dataSubmissionPolicyNotifiedDate - The stored date is too old."
      );
      return null;
    }

    return date;
  },

  /**
   * Set the date the policy was notified.
   * @param {Object} aDate A valid date object.
   */
  set dataSubmissionPolicyNotifiedDate(aDate) {
    this._log.trace("set dataSubmissionPolicyNotifiedDate - aDate: " + aDate);

    if (!aDate || aDate.getFullYear() < OLDEST_ALLOWED_ACCEPTANCE_YEAR) {
      this._log.error(
        "set dataSubmissionPolicyNotifiedDate - Invalid notification date."
      );
      return;
    }

    Services.prefs.setStringPref(
      TelemetryUtils.Preferences.AcceptedPolicyDate,
      aDate.getTime().toString()
    );
  },

  /**
   * Whether submission of data is allowed.
   *
   * This is the master switch for remote server communication. If it is
   * false, we never request upload or deletion.
   */
  get dataSubmissionEnabled() {
    // Default is true because we are opt-out.
    return Services.prefs.getBoolPref(
      TelemetryUtils.Preferences.DataSubmissionEnabled,
      true
    );
  },

  get currentPolicyVersion() {
    return Services.prefs.getIntPref(
      TelemetryUtils.Preferences.CurrentPolicyVersion,
      TelemetryReportingPolicy.DEFAULT_DATAREPORTING_POLICY_VERSION
    );
  },

  /**
   * The minimum policy version which for dataSubmissionPolicyAccepted to
   * to be valid.
   */
  get minimumPolicyVersion() {
    const minPolicyVersion = Services.prefs.getIntPref(
      TelemetryUtils.Preferences.MinimumPolicyVersion,
      1
    );

    // First check if the current channel has a specific minimum policy version. If not,
    // use the general minimum policy version.
    let channel = "";
    try {
      channel = TelemetryUtils.getUpdateChannel();
    } catch (e) {
      this._log.error(
        "minimumPolicyVersion - Unable to retrieve the current channel."
      );
      return minPolicyVersion;
    }
    const channelPref =
      TelemetryUtils.Preferences.MinimumPolicyVersion + ".channel-" + channel;
    return Services.prefs.getIntPref(channelPref, minPolicyVersion);
  },

  get dataSubmissionPolicyAcceptedVersion() {
    return Services.prefs.getIntPref(
      TelemetryUtils.Preferences.AcceptedPolicyVersion,
      0
    );
  },

  set dataSubmissionPolicyAcceptedVersion(value) {
    Services.prefs.setIntPref(
      TelemetryUtils.Preferences.AcceptedPolicyVersion,
      value
    );
  },

  /**
   * Checks to see if the user has been notified about data submission
   * @return {Bool} True if user has been notified and the notification is still valid,
   *         false otherwise.
   */
  get isUserNotifiedOfCurrentPolicy() {
    // If we don't have a sane notification date, the user was not notified yet.
    if (
      !this.dataSubmissionPolicyNotifiedDate ||
      this.dataSubmissionPolicyNotifiedDate.getTime() <= 0
    ) {
      return false;
    }

    // The accepted policy version should not be less than the minimum policy version.
    if (this.dataSubmissionPolicyAcceptedVersion < this.minimumPolicyVersion) {
      return false;
    }

    // Otherwise the user was already notified.
    return true;
  },

  /**
   * Test only method, restarts the policy.
   */
  reset() {
    this.shutdown();
    this._isFirstRun = undefined;
    return this.setup();
  },

  /**
   * Setup the policy.
   */
  setup() {
    this._log.trace("setup");

    // Migrate the data choices infobar, if needed.
    this._migratePreferences();

    // Add the event observers.
    Services.obs.addObserver(this, "sessionstore-windows-restored");
  },

  /**
   * Clean up the reporting policy.
   */
  shutdown() {
    this._log.trace("shutdown");

    this._detachObservers();

    Policy.clearShowInfobarTimeout(this._startupNotificationTimerId);
  },

  /**
   * Detach the observers that were attached during setup.
   */
  _detachObservers() {
    Services.obs.removeObserver(this, "sessionstore-windows-restored");
  },

  /**
   * Check if we are allowed to upload data. In order to submit data both these conditions
   * should be true:
   * - The data submission preference should be true.
   * - The datachoices infobar should have been displayed.
   *
   * @return {Boolean} True if we are allowed to upload data, false otherwise.
   */
  canUpload() {
    // If data submission is disabled, there's no point in showing the infobar. Just
    // forbid to upload.
    if (!this.dataSubmissionEnabled) {
      return false;
    }

    // Submission is enabled. We enable upload if user is notified or we need to bypass
    // the policy.
    const bypassNotification = Services.prefs.getBoolPref(
      TelemetryUtils.Preferences.BypassNotification,
      false
    );
    return this.isUserNotifiedOfCurrentPolicy || bypassNotification;
  },

  isFirstRun() {
    if (this._isFirstRun === undefined) {
      this._isFirstRun = Services.prefs.getBoolPref(
        TelemetryUtils.Preferences.FirstRun,
        true
      );
    }
    return this._isFirstRun;
  },

  /**
   * Migrate the data policy preferences, if needed.
   */
  _migratePreferences() {
    // Current prefs are mostly the same than the old ones, except for some deprecated ones.
    for (let pref of DEPRECATED_FHR_PREFS) {
      Services.prefs.clearUserPref(pref);
    }
  },

  /**
   * Determine whether the user should be notified.
   */
  _shouldNotify() {
    if (!this.dataSubmissionEnabled) {
      this._log.trace(
        "_shouldNotify - Data submission disabled by the policy."
      );
      return false;
    }

    const bypassNotification = Services.prefs.getBoolPref(
      TelemetryUtils.Preferences.BypassNotification,
      false
    );
    if (this.isUserNotifiedOfCurrentPolicy || bypassNotification) {
      this._log.trace(
        "_shouldNotify - User already notified or bypassing the policy."
      );
      return false;
    }

    if (this._notificationInProgress) {
      this._log.trace(
        "_shouldNotify - User not notified, notification already in progress."
      );
      return false;
    }

    return true;
  },

  /**
   * Show the data choices infobar if needed.
   */
  _showInfobar() {
    if (!this._shouldNotify()) {
      return;
    }

    this._log.trace("_showInfobar - User not notified, notifying now.");
    this._notificationInProgress = true;
    let request = new NotifyPolicyRequest(this._log);
    Observers.notify("datareporting:notify-data-policy:request", request);
  },

  /**
   * Called when the user is notified with the infobar or otherwise.
   */
  _userNotified() {
    this._log.trace("_userNotified");
    this._recordNotificationData();
    lazy.TelemetrySend.notifyCanUpload();
  },

  /**
   * Record date and the version of the accepted policy.
   */
  _recordNotificationData() {
    this._log.trace("_recordNotificationData");
    this.dataSubmissionPolicyNotifiedDate = Policy.now();
    this.dataSubmissionPolicyAcceptedVersion = this.currentPolicyVersion;
    // The user was notified and the notification data saved: the notification
    // is no longer in progress.
    this._notificationInProgress = false;
  },

  /**
   * Try to open the privacy policy in a background tab instead of showing the infobar.
   */
  _openFirstRunPage() {
    if (!this._shouldNotify()) {
      return false;
    }

    let firstRunPolicyURL = Services.prefs.getStringPref(
      TelemetryUtils.Preferences.FirstRunURL,
      ""
    );
    if (!firstRunPolicyURL) {
      return false;
    }
    firstRunPolicyURL = Services.urlFormatter.formatURL(firstRunPolicyURL);

    const { BrowserWindowTracker } = ChromeUtils.importESModule(
      "resource:///modules/BrowserWindowTracker.sys.mjs"
    );
    let win = BrowserWindowTracker.getTopWindow();

    if (!win) {
      this._log.info(
        "Couldn't find browser window to open first-run page. Falling back to infobar."
      );
      return false;
    }

    // We'll consider the user notified once the privacy policy has been loaded
    // in a background tab even if that tab hasn't been selected.
    let tab;
    let progressListener = {};
    progressListener.onStateChange = (
      aBrowser,
      aWebProgress,
      aRequest,
      aStateFlags
    ) => {
      if (
        aWebProgress.isTopLevel &&
        tab &&
        tab.linkedBrowser == aBrowser &&
        aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
        aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK
      ) {
        let uri = aBrowser.documentURI;
        if (
          uri &&
          !/^about:(blank|neterror|certerror|blocked)/.test(uri.spec)
        ) {
          this._userNotified();
        } else {
          this._log.info(
            "Failed to load first-run page. Falling back to infobar."
          );
          this._showInfobar();
        }
        removeListeners();
      }
    };

    let removeListeners = () => {
      win.removeEventListener("unload", removeListeners);
      win.gBrowser.removeTabsProgressListener(progressListener);
    };

    win.addEventListener("unload", removeListeners);
    win.gBrowser.addTabsProgressListener(progressListener);

    tab = win.gBrowser.addTab(firstRunPolicyURL, {
      inBackground: true,
      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
    });
    return true;
  },

  observe(aSubject, aTopic) {
    if (aTopic != "sessionstore-windows-restored") {
      return;
    }

    if (this.isFirstRun()) {
      // We're performing the first run, flip firstRun preference for subsequent runs.
      Services.prefs.setBoolPref(TelemetryUtils.Preferences.FirstRun, false);

      try {
        if (this._openFirstRunPage()) {
          return;
        }
      } catch (e) {
        this._log.error("Failed to open privacy policy tab: " + e);
      }
    }

    // Show the info bar.
    const delay = this.isFirstRun()
      ? NOTIFICATION_DELAY_FIRST_RUN_MSEC
      : NOTIFICATION_DELAY_NEXT_RUNS_MSEC;

    this._startupNotificationTimerId = Policy.setShowInfobarTimeout(
      // Calling |canUpload| eventually shows the infobar, if needed.
      () => this._showInfobar(),
      delay
    );
  },
};
PK
!<G�[����modules/TelemetrySend.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This module is responsible for uploading pings to the server and persisting
 * pings that can't be send now.
 * Those pending pings are persisted on disk and sent at the next opportunity,
 * newest first.
 */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

import { ClientID } from "resource://gre/modules/ClientID.sys.mjs";
import { Log } from "resource://gre/modules/Log.sys.mjs";
import { ServiceRequest } from "resource://gre/modules/ServiceRequest.sys.mjs";

import { TelemetryUtils } from "resource://gre/modules/TelemetryUtils.sys.mjs";
import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
  TelemetryHealthPing: "resource://gre/modules/HealthPing.sys.mjs",
  TelemetryReportingPolicy:
    "resource://gre/modules/TelemetryReportingPolicy.sys.mjs",
  TelemetryStorage: "resource://gre/modules/TelemetryStorage.sys.mjs",
});

const Utils = TelemetryUtils;
const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetrySend::";

const TOPIC_IDLE_DAILY = "idle-daily";
// The following topics are notified when Firefox is closing
// because the OS is shutting down.
const TOPIC_QUIT_APPLICATION_GRANTED = "quit-application-granted";
const TOPIC_QUIT_APPLICATION_FORCED = "quit-application-forced";
const PREF_CHANGED_TOPIC = "nsPref:changed";
const TOPIC_PROFILE_CHANGE_NET_TEARDOWN = "profile-change-net-teardown";

// Whether the FHR/Telemetry unification features are enabled.
// Changing this pref requires a restart.
const IS_UNIFIED_TELEMETRY = Services.prefs.getBoolPref(
  TelemetryUtils.Preferences.Unified,
  false
);

const MS_IN_A_MINUTE = 60 * 1000;

const PING_TYPE_DELETION_REQUEST = "deletion-request";

// We try to spread "midnight" pings out over this interval.
const MIDNIGHT_FUZZING_INTERVAL_MS = 60 * MS_IN_A_MINUTE;
// We delay sending "midnight" pings on this client by this interval.
const MIDNIGHT_FUZZING_DELAY_MS = Math.random() * MIDNIGHT_FUZZING_INTERVAL_MS;

// Timeout after which we consider a ping submission failed.
export const PING_SUBMIT_TIMEOUT_MS = 1.5 * MS_IN_A_MINUTE;

// To keep resource usage in check, we limit ping sending to a maximum number
// of pings per minute.
const MAX_PING_SENDS_PER_MINUTE = 10;

// If we have more pending pings then we can send right now, we schedule the next
// send for after SEND_TICK_DELAY.
const SEND_TICK_DELAY = 1 * MS_IN_A_MINUTE;
// If we had any ping send failures since the last ping, we use a backoff timeout
// for the next ping sends. We increase the delay exponentially up to a limit of
// SEND_MAXIMUM_BACKOFF_DELAY_MS.
// This exponential backoff will be reset by external ping submissions & idle-daily.
const SEND_MAXIMUM_BACKOFF_DELAY_MS = 120 * MS_IN_A_MINUTE;

// Strings to map from XHR.errorCode to TELEMETRY_SEND_FAILURE_TYPE.
// Echoes XMLHttpRequestMainThread's ErrorType enum.
// Make sure that any additions done to XHR_ERROR_TYPE enum are also mirrored in
// TELEMETRY_SEND_FAILURE_TYPE and TELEMETRY_SEND_FAILURE_TYPE_PER_PING's labels.
const XHR_ERROR_TYPE = [
  "eOK",
  "eRequest",
  "eUnreachable",
  "eChannelOpen",
  "eRedirect",
  "eTerminated",
];

/**
 * This is a policy object used to override behavior within this module.
 * Tests override properties on this object to allow for control of behavior
 * that would otherwise be very hard to cover.
 */
export var Policy = {
  now: () => new Date(),
  midnightPingFuzzingDelay: () => MIDNIGHT_FUZZING_DELAY_MS,
  pingSubmissionTimeout: () => PING_SUBMIT_TIMEOUT_MS,
  setSchedulerTickTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
  clearSchedulerTickTimeout: id => clearTimeout(id),
  gzipCompressString: data => gzipCompressString(data),
};

/**
 * Determine if the ping has the new v4 ping format or the legacy v2 one or earlier.
 */
function isV4PingFormat(aPing) {
  return (
    "id" in aPing &&
    "application" in aPing &&
    "version" in aPing &&
    aPing.version >= 2
  );
}

/**
 * Check if the provided ping is a deletion-request ping.
 * @param {Object} aPing The ping to check.
 * @return {Boolean} True if the ping is a deletion-request ping, false otherwise.
 */
function isDeletionRequestPing(aPing) {
  return isV4PingFormat(aPing) && aPing.type == PING_TYPE_DELETION_REQUEST;
}

/**
 * Save the provided ping as a pending ping.
 * @param {Object} aPing The ping to save.
 * @return {Promise} A promise resolved when the ping is saved.
 */
function savePing(aPing) {
  return lazy.TelemetryStorage.savePendingPing(aPing);
}

function arrayToString(array) {
  let buffer = "";
  // String.fromCharCode can only deal with 500,000 characters at
  // a time, so chunk the result into parts of that size.
  const chunkSize = 500000;
  for (let offset = 0; offset < array.length; offset += chunkSize) {
    buffer += String.fromCharCode.apply(
      String,
      array.slice(offset, offset + chunkSize)
    );
  }
  return buffer;
}

/**
 * @return {String} This returns a string with the gzip compressed data.
 */
export function gzipCompressString(string) {
  let observer = {
    buffer: null,
    onStreamComplete(loader, context, status, length, result) {
      this.buffer = arrayToString(result);
    },
  };

  let scs = Cc["@mozilla.org/streamConverters;1"].getService(
    Ci.nsIStreamConverterService
  );
  let listener = Cc["@mozilla.org/network/stream-loader;1"].createInstance(
    Ci.nsIStreamLoader
  );
  listener.init(observer);
  let converter = scs.asyncConvertData("uncompressed", "gzip", listener, null);
  let stringStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
    Ci.nsIStringInputStream
  );
  stringStream.data = string;
  converter.onStartRequest(null, null);
  converter.onDataAvailable(null, stringStream, 0, string.length);
  converter.onStopRequest(null, null, null);
  return observer.buffer;
}

const STANDALONE_PING_TIMEOUT = 30 * 1000; // 30 seconds

export function sendStandalonePing(endpoint, payload, extraHeaders = {}) {
  return new Promise((resolve, reject) => {
    let request = new ServiceRequest({ mozAnon: true });
    request.mozBackgroundRequest = true;
    request.timeout = STANDALONE_PING_TIMEOUT;

    request.open("POST", endpoint, true);
    request.overrideMimeType("text/plain");
    request.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
    request.setRequestHeader("Content-Encoding", "gzip");
    request.setRequestHeader("Date", new Date().toUTCString());
    for (let header in extraHeaders) {
      request.setRequestHeader(header, extraHeaders[header]);
    }

    request.onload = event => {
      if (request.status !== 200) {
        reject(event);
      } else {
        resolve(event);
      }
    };
    request.onerror = reject;
    request.onabort = reject;
    request.ontimeout = reject;

    let payloadStream = Cc[
      "@mozilla.org/io/string-input-stream;1"
    ].createInstance(Ci.nsIStringInputStream);

    const utf8Payload = new TextEncoder().encode(payload);

    payloadStream.data = gzipCompressString(arrayToString(utf8Payload));
    request.sendInputStream(payloadStream);
  });
}

export var TelemetrySend = {
  get pendingPingCount() {
    return TelemetrySendImpl.pendingPingCount;
  },

  /**
   * Partial setup that runs immediately at startup. This currently triggers
   * the crash report annotations.
   */
  earlyInit() {
    TelemetrySendImpl.earlyInit();
  },

  /**
   * Initializes this module.
   *
   * @param {Boolean} testing Whether this is run in a test. This changes some behavior
   * to enable proper testing.
   * @return {Promise} Resolved when setup is finished.
   */
  setup(testing = false) {
    return TelemetrySendImpl.setup(testing);
  },

  /**
   * Shutdown this module - this will cancel any pending ping tasks and wait for
   * outstanding async activity like network and disk I/O.
   *
   * @return {Promise} Promise that is resolved when shutdown is finished.
   */
  shutdown() {
    return TelemetrySendImpl.shutdown();
  },

  /**
   * Flushes all pings to pingsender that were both
   *   1. submitted after profile-change-net-teardown, and
   *   2. wanting to be sent using pingsender.
   */
  flushPingSenderBatch() {
    TelemetrySendImpl.flushPingSenderBatch();
  },

  /**
   * Submit a ping for sending. This will:
   * - send the ping right away if possible or
   * - save the ping to disk and send it at the next opportunity
   *
   * @param {Object} ping The ping data to send, must be serializable to JSON.
   * @param {Object} [aOptions] Options object.
   * @param {Boolean} [options.usePingSender=false] if true, send the ping using the PingSender.
   * @return {Promise} Test-only - a promise that is resolved when the ping is sent or saved.
   */
  submitPing(ping, options = {}) {
    options.usePingSender = options.usePingSender || false;
    return TelemetrySendImpl.submitPing(ping, options);
  },

  /**
   * Check if sending is disabled. If Telemetry is not allowed to upload,
   * pings are not sent to the server.
   * If trying to send a deletion-request ping, don't block it.
   *
   * @param {Object} [ping=null] A ping to be checked.
   * @return {Boolean} True if pings can be send to the servers, false otherwise.
   */
  sendingEnabled(ping = null) {
    return TelemetrySendImpl.sendingEnabled(ping);
  },

  /**
   * Notify that we can start submitting data to the servers.
   */
  notifyCanUpload() {
    return TelemetrySendImpl.notifyCanUpload();
  },

  /**
   * Only used in tests. Used to reset the module data to emulate a restart.
   */
  reset() {
    return TelemetrySendImpl.reset();
  },

  /**
   * Only used in tests.
   */
  setServer(server) {
    return TelemetrySendImpl.setServer(server);
  },

  /**
   * Clear out unpersisted, yet to be sent, pings and cancel outgoing ping requests.
   */
  clearCurrentPings() {
    return TelemetrySendImpl.clearCurrentPings();
  },

  /**
   * Only used in tests to wait on outgoing pending pings.
   */
  testWaitOnOutgoingPings() {
    return TelemetrySendImpl.promisePendingPingActivity();
  },

  /**
   * Only used in tests to set whether it is too late in shutdown to send pings.
   */
  testTooLateToSend(tooLate) {
    TelemetrySendImpl._tooLateToSend = tooLate;
  },

  /**
   * Test-only - this allows overriding behavior to enable ping sending in debug builds.
   */
  setTestModeEnabled(testing) {
    TelemetrySendImpl.setTestModeEnabled(testing);
  },

  /**
   * This returns state info for this module for AsyncShutdown timeout diagnostics.
   */
  getShutdownState() {
    return TelemetrySendImpl.getShutdownState();
  },

  /**
   * Send a ping using the ping sender.
   * This method will not wait for the ping to be sent, instead it will return
   * as soon as the pingsender program has been launched.
   *
   * This method is currently exposed here only for testing purposes as it's
   * only used internally.
   *
   * @param {Array}<Object> pings An array of objects holding url / path pairs
   *        for each ping to be sent. The URL represent the telemetry server the
   *        ping will be sent to and the path points to the ping data. The ping
   *        data files will be deleted if the pings have been submitted
   *        successfully.
   * @param {callback} observer A function called with parameters
   *        (subject, topic, data) and a topic of "process-finished" or
   *        "process-failed" after pingsender completion.
   *
   * @throws NS_ERROR_FAILURE if we couldn't find or run the pingsender
   *         executable.
   * @throws NS_ERROR_NOT_IMPLEMENTED on Android as the pingsender is not
   *         available.
   */
  testRunPingSender(pings, observer) {
    return TelemetrySendImpl.runPingSender(pings, observer);
  },
};

var CancellableTimeout = {
  _deferred: null,
  _timer: null,

  /**
   * This waits until either the given timeout passed or the timeout was cancelled.
   *
   * @param {Number} timeoutMs The timeout in ms.
   * @return {Promise<bool>} Promise that is resolved with false if the timeout was cancelled,
   *                         false otherwise.
   */
  promiseWaitOnTimeout(timeoutMs) {
    if (!this._deferred) {
      this._deferred = Promise.withResolvers();
      this._timer = Policy.setSchedulerTickTimeout(
        () => this._onTimeout(),
        timeoutMs
      );
    }

    return this._deferred.promise;
  },

  _onTimeout() {
    if (this._deferred) {
      this._deferred.resolve(false);
      this._timer = null;
      this._deferred = null;
    }
  },

  cancelTimeout() {
    if (this._deferred) {
      Policy.clearSchedulerTickTimeout(this._timer);
      this._deferred.resolve(true);
      this._timer = null;
      this._deferred = null;
    }
  },
};

/**
 * SendScheduler implements the timer & scheduling behavior for ping sends.
 */
export var SendScheduler = {
  // Whether any ping sends failed since the last tick. If yes, we start with our exponential
  // backoff timeout.
  _sendsFailed: false,
  // The current retry delay after ping send failures. We use this for the exponential backoff,
  // increasing this value everytime we had send failures since the last tick.
  _backoffDelay: SEND_TICK_DELAY,
  _shutdown: false,
  _sendTask: null,
  // A string that tracks the last seen send task state, null if it never ran.
  _sendTaskState: null,

  _logger: null,

  get _log() {
    if (!this._logger) {
      this._logger = Log.repository.getLoggerWithMessagePrefix(
        LOGGER_NAME,
        LOGGER_PREFIX + "Scheduler::"
      );
    }

    return this._logger;
  },

  shutdown() {
    this._log.trace("shutdown");
    this._shutdown = true;
    CancellableTimeout.cancelTimeout();
    return Promise.resolve(this._sendTask);
  },

  start() {
    this._log.trace("start");
    this._sendsFailed = false;
    this._backoffDelay = SEND_TICK_DELAY;
    this._shutdown = false;
  },

  /**
   * Only used for testing, resets the state to emulate a restart.
   */
  reset() {
    this._log.trace("reset");
    return this.shutdown().then(() => this.start());
  },

  /**
   * Notify the scheduler of a failure in sending out pings that warrants retrying.
   * This will trigger the exponential backoff timer behavior on the next tick.
   */
  notifySendsFailed() {
    this._log.trace("notifySendsFailed");
    if (this._sendsFailed) {
      return;
    }

    this._sendsFailed = true;
    this._log.trace("notifySendsFailed - had send failures");
  },

  /**
   * Returns whether ping submissions are currently throttled.
   */
  isThrottled() {
    const now = Policy.now();
    const nextPingSendTime = this._getNextPingSendTime(now);
    return nextPingSendTime > now.getTime();
  },

  waitOnSendTask() {
    return Promise.resolve(this._sendTask);
  },

  triggerSendingPings(immediately) {
    this._log.trace(
      "triggerSendingPings - active send task: " +
        !!this._sendTask +
        ", immediately: " +
        immediately
    );

    if (!this._sendTask) {
      this._sendTask = this._doSendTask();
      let clear = () => (this._sendTask = null);
      this._sendTask.then(clear, clear);
    } else if (immediately) {
      CancellableTimeout.cancelTimeout();
    }

    return this._sendTask;
  },

  async _doSendTask() {
    this._sendTaskState = "send task started";
    this._backoffDelay = SEND_TICK_DELAY;
    this._sendsFailed = false;

    const resetBackoffTimer = () => {
      this._backoffDelay = SEND_TICK_DELAY;
    };

    for (;;) {
      this._log.trace("_doSendTask iteration");
      this._sendTaskState = "start iteration";

      if (this._shutdown) {
        this._log.trace("_doSendTask - shutting down, bailing out");
        this._sendTaskState = "bail out - shutdown check";
        return;
      }

      // Get a list of pending pings, sorted by last modified, descending.
      // Filter out all the pings we can't send now. This addresses scenarios like "deletion-request" pings
      // which can be sent even when upload is disabled.
      let pending = lazy.TelemetryStorage.getPendingPingList();
      let current = TelemetrySendImpl.getUnpersistedPings();
      this._log.trace(
        "_doSendTask - pending: " +
          pending.length +
          ", current: " +
          current.length
      );
      // Note that the two lists contain different kind of data. |pending| only holds ping
      // info, while |current| holds actual ping data.
      if (!TelemetrySendImpl.sendingEnabled()) {
        // If sending is disabled, only handle deletion-request pings
        pending = [];
        current = current.filter(p => isDeletionRequestPing(p));
      }
      this._log.trace(
        "_doSendTask - can send - pending: " +
          pending.length +
          ", current: " +
          current.length
      );

      // Bail out if there is nothing to send.
      if (!pending.length && !current.length) {
        this._log.trace("_doSendTask - no pending pings, bailing out");
        this._sendTaskState = "bail out - no pings to send";
        return;
      }

      // If we are currently throttled (e.g. fuzzing to avoid midnight spikes), wait for the next send window.
      const now = Policy.now();
      if (this.isThrottled()) {
        const nextPingSendTime = this._getNextPingSendTime(now);
        this._log.trace(
          "_doSendTask - throttled, delaying ping send to " +
            new Date(nextPingSendTime)
        );
        this._sendTaskState = "wait for throttling to pass";

        const delay = nextPingSendTime - now.getTime();
        const cancelled = await CancellableTimeout.promiseWaitOnTimeout(delay);
        if (cancelled) {
          this._log.trace(
            "_doSendTask - throttling wait was cancelled, resetting backoff timer"
          );
          resetBackoffTimer();
        }

        continue;
      }

      let sending = pending.slice(0, MAX_PING_SENDS_PER_MINUTE);
      pending = pending.slice(MAX_PING_SENDS_PER_MINUTE);
      this._log.trace(
        "_doSendTask - triggering sending of " +
          sending.length +
          " pings now" +
          ", " +
          pending.length +
          " pings waiting"
      );

      this._sendsFailed = false;
      const sendStartTime = Policy.now();
      this._sendTaskState = "wait on ping sends";
      await TelemetrySendImpl.sendPings(
        current,
        sending.map(p => p.id)
      );
      if (this._shutdown || TelemetrySend.pendingPingCount == 0) {
        this._log.trace(
          "_doSendTask - bailing out after sending, shutdown: " +
            this._shutdown +
            ", pendingPingCount: " +
            TelemetrySend.pendingPingCount
        );
        this._sendTaskState = "bail out - shutdown & pending check after send";
        return;
      }

      // Calculate the delay before sending the next batch of pings.
      // We start with a delay that makes us send max. 1 batch per minute.
      // If we had send failures in the last batch, we will override this with
      // a backoff delay.
      const timeSinceLastSend = Policy.now() - sendStartTime;
      let nextSendDelay = Math.max(0, SEND_TICK_DELAY - timeSinceLastSend);

      if (!this._sendsFailed) {
        this._log.trace(
          "_doSendTask - had no send failures, resetting backoff timer"
        );
        resetBackoffTimer();
      } else {
        const newDelay = Math.min(
          SEND_MAXIMUM_BACKOFF_DELAY_MS,
          this._backoffDelay * 2
        );
        this._log.trace(
          "_doSendTask - had send failures, backing off -" +
            " old timeout: " +
            this._backoffDelay +
            ", new timeout: " +
            newDelay
        );
        this._backoffDelay = newDelay;
        nextSendDelay = this._backoffDelay;
      }

      this._log.trace(
        "_doSendTask - waiting for next send opportunity, timeout is " +
          nextSendDelay
      );
      this._sendTaskState = "wait on next send opportunity";
      const cancelled = await CancellableTimeout.promiseWaitOnTimeout(
        nextSendDelay
      );
      if (cancelled) {
        this._log.trace(
          "_doSendTask - batch send wait was cancelled, resetting backoff timer"
        );
        resetBackoffTimer();
      }
    }
  },

  /**
   * This helper calculates the next time that we can send pings at.
   * Currently this mostly redistributes ping sends from midnight until one hour after
   * to avoid submission spikes around local midnight for daily pings.
   *
   * @param now Date The current time.
   * @return Number The next time (ms from UNIX epoch) when we can send pings.
   */
  _getNextPingSendTime(now) {
    // 1. First we check if the pref is set to skip any delay and send immediately.
    // 2. Next we check if the time is between 0am and 1am. If it's not, we send
    // immediately.
    // 3. If we confirmed the time is indeed between 0am and 1am in step 1, we disallow
    // sending before (midnight + fuzzing delay), which is a random time between 0am-1am
    // (decided at startup).

    let disableFuzzingDelay = Services.prefs.getBoolPref(
      TelemetryUtils.Preferences.DisableFuzzingDelay,
      false
    );
    if (disableFuzzingDelay) {
      return now.getTime();
    }

    const midnight = Utils.truncateToDays(now);
    // Don't delay pings if we are not within the fuzzing interval.
    if (now.getTime() - midnight.getTime() > MIDNIGHT_FUZZING_INTERVAL_MS) {
      return now.getTime();
    }

    // Delay ping send if we are within the midnight fuzzing range.
    // We spread those ping sends out between |midnight| and |midnight + midnightPingFuzzingDelay|.
    return midnight.getTime() + Policy.midnightPingFuzzingDelay();
  },

  getShutdownState() {
    return {
      shutdown: this._shutdown,
      hasSendTask: !!this._sendTask,
      sendsFailed: this._sendsFailed,
      sendTaskState: this._sendTaskState,
      backoffDelay: this._backoffDelay,
    };
  },
};

export var TelemetrySendImpl = {
  _sendingEnabled: false,
  // Tracks the shutdown state.
  _shutdown: false,
  _logger: null,
  // This tracks all pending ping requests to the server.
  _pendingPingRequests: new Map(),
  // This tracks all the pending async ping activity.
  _pendingPingActivity: new Set(),
  // This is true when running in the test infrastructure.
  _testMode: false,
  // This holds pings that we currently try and haven't persisted yet.
  _currentPings: new Map(),
  // Used to skip spawning the pingsender if OS is shutting down.
  _isOSShutdown: false,
  // Has the network shut down, making it too late to send pings?
  _tooLateToSend: false,
  // Array of {url, path} awaiting flushPingSenderBatch().
  _pingSenderBatch: [],

  OBSERVER_TOPICS: [
    TOPIC_IDLE_DAILY,
    TOPIC_QUIT_APPLICATION_GRANTED,
    TOPIC_QUIT_APPLICATION_FORCED,
    TOPIC_PROFILE_CHANGE_NET_TEARDOWN,
  ],

  OBSERVED_PREFERENCES: [
    TelemetryUtils.Preferences.TelemetryEnabled,
    TelemetryUtils.Preferences.FhrUploadEnabled,
  ],

  // Whether sending pings has been overridden.
  get _overrideOfficialCheck() {
    return Services.prefs.getBoolPref(
      TelemetryUtils.Preferences.OverrideOfficialCheck,
      false
    );
  },

  get _log() {
    if (!this._logger) {
      this._logger = Log.repository.getLoggerWithMessagePrefix(
        LOGGER_NAME,
        LOGGER_PREFIX
      );
    }

    return this._logger;
  },

  get pendingPingRequests() {
    return this._pendingPingRequests;
  },

  get pendingPingCount() {
    return (
      lazy.TelemetryStorage.getPendingPingList().length +
      this._currentPings.size
    );
  },

  setTestModeEnabled(testing) {
    this._testMode = testing;
  },

  earlyInit() {
    this._annotateCrashReport();

    // Install the observer to detect OS shutdown early enough, so
    // that we catch this before the delayed setup happens.
    Services.obs.addObserver(this, TOPIC_QUIT_APPLICATION_FORCED);
    Services.obs.addObserver(this, TOPIC_QUIT_APPLICATION_GRANTED);
  },

  QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]),

  async setup(testing) {
    this._log.trace("setup");

    this._testMode = testing;

    Services.obs.addObserver(this, TOPIC_IDLE_DAILY);
    Services.obs.addObserver(this, TOPIC_PROFILE_CHANGE_NET_TEARDOWN);

    this._server = Services.prefs.getStringPref(
      TelemetryUtils.Preferences.Server,
      undefined
    );
    this._sendingEnabled = true;

    // Annotate crash reports so that crash pings are sent correctly and listen
    // to pref changes to adjust the annotations accordingly.
    for (let pref of this.OBSERVED_PREFERENCES) {
      Services.prefs.addObserver(pref, this, true);
    }
    this._annotateCrashReport();

    // Check the pending pings on disk now.
    try {
      await this._checkPendingPings();
    } catch (ex) {
      this._log.error("setup - _checkPendingPings rejected", ex);
    }

    // Enforce the pending pings storage quota. It could take a while so don't
    // block on it.
    lazy.TelemetryStorage.runEnforcePendingPingsQuotaTask();

    // Start sending pings, but don't block on this.
    SendScheduler.triggerSendingPings(true);
  },

  /**
   * Triggers the crash report annotations depending on the current
   * configuration. This communicates to the crash reporter if it can send a
   * crash ping or not. This method can be called safely before setup() has
   * been called.
   */
  _annotateCrashReport() {
    try {
      const cr = Cc["@mozilla.org/toolkit/crash-reporter;1"];
      if (cr) {
        // This needs to use nsICrashReporter because test_TelemetrySend.js
        // replaces the crash reporter service, which we can't access here
        // as Services caches it.
        // eslint-disable-next-line mozilla/use-services
        const crs = cr.getService(Ci.nsICrashReporter);

        let clientId = ClientID.getCachedClientID();
        let profileGroupId = ClientID.getCachedProfileGroupID();
        let server =
          this._server ||
          Services.prefs.getStringPref(
            TelemetryUtils.Preferences.Server,
            undefined
          );

        if (
          !this.sendingEnabled() ||
          !lazy.TelemetryReportingPolicy.canUpload()
        ) {
          // If we cannot send pings then clear the crash annotations
          crs.removeCrashReportAnnotation("TelemetryClientId");
          crs.removeCrashReportAnnotation("TelemetryProfileGroupId");
          crs.removeCrashReportAnnotation("TelemetryServerURL");
        } else {
          crs.annotateCrashReport("TelemetryClientId", clientId);
          crs.annotateCrashReport("TelemetryProfileGroupId", profileGroupId);
          crs.annotateCrashReport("TelemetryServerURL", server);
        }
      }
    } catch (e) {
      // Ignore errors when crash reporting is disabled
    }
  },

  /**
   * Discard old pings from the pending pings and detect overdue ones.
   * @return {Boolean} True if we have overdue pings, false otherwise.
   */
  async _checkPendingPings() {
    // Scan the pending pings - that gives us a list sorted by last modified, descending.
    let infos = await lazy.TelemetryStorage.loadPendingPingList();
    this._log.info("_checkPendingPings - pending ping count: " + infos.length);
    if (!infos.length) {
      this._log.trace("_checkPendingPings - no pending pings");
      return;
    }

    const now = Policy.now();

    // Submit the age of the pending pings.
    for (let pingInfo of infos) {
      const ageInDays = Utils.millisecondsToDays(
        Math.abs(now.getTime() - pingInfo.lastModificationDate)
      );
      Services.telemetry
        .getHistogramById("TELEMETRY_PENDING_PINGS_AGE")
        .add(ageInDays);
    }
  },

  async shutdown() {
    this._shutdown = true;

    for (let pref of this.OBSERVED_PREFERENCES) {
      // FIXME: When running tests this causes errors to be printed out if
      // TelemetrySend.shutdown() is called twice in a row without calling
      // TelemetrySend.setup() in-between.
      Services.prefs.removeObserver(pref, this);
    }

    for (let topic of this.OBSERVER_TOPICS) {
      try {
        Services.obs.removeObserver(this, topic);
      } catch (ex) {
        this._log.error(
          "shutdown - failed to remove observer for " + topic,
          ex
        );
      }
    }

    // We can't send anymore now.
    this._sendingEnabled = false;

    // Cancel any outgoing requests.
    await this._cancelOutgoingRequests();

    // Stop any active send tasks.
    await SendScheduler.shutdown();

    // Wait for any outstanding async ping activity.
    await this.promisePendingPingActivity();

    // Save any outstanding pending pings to disk.
    await this._persistCurrentPings();
  },

  flushPingSenderBatch() {
    if (this._pingSenderBatch.length === 0) {
      return;
    }
    this._log.trace(
      `flushPingSenderBatch - Sending ${this._pingSenderBatch.length} pings.`
    );
    this.runPingSender(this._pingSenderBatch);
  },

  reset() {
    this._log.trace("reset");

    this._shutdown = false;
    this._currentPings = new Map();
    this._tooLateToSend = false;
    this._isOSShutdown = false;
    this._sendingEnabled = true;

    const histograms = [
      "TELEMETRY_SUCCESS",
      "TELEMETRY_SEND_SUCCESS",
      "TELEMETRY_SEND_FAILURE",
      "TELEMETRY_SEND_FAILURE_TYPE",
    ];

    histograms.forEach(h => Services.telemetry.getHistogramById(h).clear());

    const keyedHistograms = ["TELEMETRY_SEND_FAILURE_TYPE_PER_PING"];

    keyedHistograms.forEach(h =>
      Services.telemetry.getKeyedHistogramById(h).clear()
    );

    return SendScheduler.reset();
  },

  /**
   * Notify that we can start submitting data to the servers.
   */
  notifyCanUpload() {
    if (!this._sendingEnabled) {
      this._log.trace(
        "notifyCanUpload - notifying before sending is enabled. Ignoring."
      );
      return Promise.resolve();
    }
    // Let the scheduler trigger sending pings if possible, also inform the
    // crash reporter that it can send crash pings if appropriate.
    SendScheduler.triggerSendingPings(true);
    this._annotateCrashReport();

    return this.promisePendingPingActivity();
  },

  observe(subject, topic, data) {
    let setOSShutdown = () => {
      this._log.trace("setOSShutdown - in OS shutdown");
      this._isOSShutdown = true;
    };

    switch (topic) {
      case TOPIC_IDLE_DAILY:
        SendScheduler.triggerSendingPings(true);
        break;
      case TOPIC_QUIT_APPLICATION_FORCED:
        setOSShutdown();
        break;
      case TOPIC_QUIT_APPLICATION_GRANTED:
        if (data == "syncShutdown") {
          setOSShutdown();
        }
        break;
      case PREF_CHANGED_TOPIC:
        if (this.OBSERVED_PREFERENCES.includes(data)) {
          this._annotateCrashReport();
        }
        break;
      case TOPIC_PROFILE_CHANGE_NET_TEARDOWN:
        this._tooLateToSend = true;
        break;
    }
  },

  /**
   * Spawn the PingSender process that sends a ping. This function does
   * not return an error or throw, it only logs an error.
   *
   * Even if the function doesn't fail, it doesn't mean that the ping was
   * successfully sent, as we have no control over the spawned process. If it,
   * succeeds, the ping is eventually removed from the disk to prevent duplicated
   * submissions.
   *
   * @param {String} pingId The id of the ping to send.
   * @param {String} submissionURL The complete Telemetry-compliant URL for the ping.
   */
  _sendWithPingSender(pingId, submissionURL) {
    this._log.trace(
      "_sendWithPingSender - sending " + pingId + " to " + submissionURL
    );
    try {
      const pingPath = PathUtils.join(
        lazy.TelemetryStorage.pingDirectoryPath,
        pingId
      );
      if (this._tooLateToSend) {
        // We're in shutdown. Batch pings destined for pingsender.
        this._log.trace("_sendWithPingSender - too late to send. Batching.");
        this._pingSenderBatch.push({ url: submissionURL, path: pingPath });
        return;
      }
      this.runPingSender([{ url: submissionURL, path: pingPath }]);
    } catch (e) {
      this._log.error("_sendWithPingSender - failed to submit ping", e);
    }
  },

  submitPing(ping, options) {
    this._log.trace(
      "submitPing - ping id: " +
        ping.id +
        ", options: " +
        JSON.stringify(options)
    );

    if (!this.sendingEnabled(ping)) {
      this._log.trace("submitPing - Telemetry is not allowed to send pings.");
      return Promise.resolve();
    }

    // Send the ping using the PingSender, if requested and the user was
    // notified of our policy. We don't support the pingsender on Android,
    // so ignore this option on that platform (see bug 1335917).
    // Moreover, if the OS is shutting down, we don't want to spawn the
    // pingsender as it could unnecessarily slow down OS shutdown.
    // Additionally, it could be be killed before it can complete its tasks,
    // for example after successfully sending the ping but before removing
    // the copy from the disk, resulting in receiving duplicate pings when
    // Firefox restarts.
    if (
      options.usePingSender &&
      !this._isOSShutdown &&
      lazy.TelemetryReportingPolicy.canUpload() &&
      AppConstants.platform != "android"
    ) {
      const url = this._buildSubmissionURL(ping);
      // Serialize the ping to the disk and then spawn the PingSender.
      return savePing(ping).then(() => this._sendWithPingSender(ping.id, url));
    }

    if (!this.canSendNow) {
      // Sending is disabled or throttled, add this to the persisted pending pings.
      this._log.trace(
        "submitPing - can't send ping now, persisting to disk - " +
          "canSendNow: " +
          this.canSendNow
      );
      return savePing(ping);
    }

    // Let the scheduler trigger sending pings if possible.
    // As a safety mechanism, this resets any currently active throttling.
    this._log.trace("submitPing - can send pings, trying to send now");
    this._currentPings.set(ping.id, ping);
    SendScheduler.triggerSendingPings(true);
    return Promise.resolve();
  },

  /**
   * Only used in tests.
   */
  setServer(server) {
    this._log.trace("setServer", server);
    this._server = server;
  },

  /**
   * Clear out unpersisted, yet to be sent, pings and cancel outgoing ping requests.
   */
  async clearCurrentPings() {
    if (this._shutdown) {
      this._log.trace("clearCurrentPings - in shutdown, bailing out");
      return;
    }

    // Temporarily disable the scheduler. It must not try to reschedule ping sending
    // while we're deleting them.
    await SendScheduler.shutdown();

    // Now that the ping activity has settled, abort outstanding ping requests.
    this._cancelOutgoingRequests();

    // Also, purge current pings.
    this._currentPings.clear();

    // We might have been interrupted and shutdown could have been started.
    // We need to bail out in that case to avoid triggering send activity etc.
    // at unexpected times.
    if (this._shutdown) {
      this._log.trace(
        "clearCurrentPings - in shutdown, not spinning SendScheduler up again"
      );
      return;
    }

    // Enable the scheduler again and spin the send task.
    SendScheduler.start();
    SendScheduler.triggerSendingPings(true);
  },

  _cancelOutgoingRequests() {
    // Abort any pending ping XHRs.
    for (let [id, request] of this._pendingPingRequests) {
      this._log.trace(
        "_cancelOutgoingRequests - aborting ping request for id " + id
      );
      try {
        request.abort();
      } catch (e) {
        this._log.error(
          "_cancelOutgoingRequests - failed to abort request for id " + id,
          e
        );
      }
    }
    this._pendingPingRequests.clear();
  },

  sendPings(currentPings, persistedPingIds) {
    let pingSends = [];

    // Prioritize health pings to enable low-latency monitoring.
    currentPings = [
      ...currentPings.filter(ping => ping.type === "health"),
      ...currentPings.filter(ping => ping.type !== "health"),
    ];

    for (let current of currentPings) {
      let ping = current;
      let p = (async () => {
        try {
          await this._doPing(ping, ping.id, false);
        } catch (ex) {
          this._log.info(
            "sendPings - ping " + ping.id + " not sent, saving to disk",
            ex
          );
          await savePing(ping);
        } finally {
          this._currentPings.delete(ping.id);
        }
      })();

      this._trackPendingPingTask(p);
      pingSends.push(p);
    }

    if (persistedPingIds.length) {
      pingSends.push(
        this._sendPersistedPings(persistedPingIds).catch(ex => {
          this._log.info("sendPings - persisted pings not sent", ex);
        })
      );
    }

    return Promise.all(pingSends);
  },

  /**
   * Send the persisted pings to the server.
   *
   * @param {Array<string>} List of ping ids that should be sent.
   *
   * @return Promise A promise that is resolved when all pings finished sending or failed.
   */
  async _sendPersistedPings(pingIds) {
    this._log.trace("sendPersistedPings");

    if (this.pendingPingCount < 1) {
      this._log.trace("_sendPersistedPings - no pings to send");
      return;
    }

    if (pingIds.length < 1) {
      this._log.trace("sendPersistedPings - no pings to send");
      return;
    }

    // We can send now.
    // If there are any send failures, _doPing() sets up handlers that e.g. trigger backoff timer behavior.
    this._log.trace(
      "sendPersistedPings - sending " + pingIds.length + " pings"
    );
    let pingSendPromises = [];
    for (let pingId of pingIds) {
      const id = pingId;
      pingSendPromises.push(
        lazy.TelemetryStorage.loadPendingPing(id)
          .then(data => this._doPing(data, id, true))
          .catch(e =>
            this._log.error("sendPersistedPings - failed to send ping " + id, e)
          )
      );
    }

    let promise = Promise.all(pingSendPromises);
    this._trackPendingPingTask(promise);
    await promise;
  },

  _onPingRequestFinished(success, startTime, id, isPersisted) {
    this._log.trace(
      "_onPingRequestFinished - success: " +
        success +
        ", persisted: " +
        isPersisted
    );

    let sendId = success ? "TELEMETRY_SEND_SUCCESS" : "TELEMETRY_SEND_FAILURE";
    let hsend = Services.telemetry.getHistogramById(sendId);
    let hsuccess = Services.telemetry.getHistogramById("TELEMETRY_SUCCESS");

    hsend.add(Utils.monotonicNow() - startTime);
    hsuccess.add(success);

    if (!success) {
      // Let the scheduler know about send failures for triggering backoff timeouts.
      SendScheduler.notifySendsFailed();
    }

    if (success && isPersisted) {
      return lazy.TelemetryStorage.removePendingPing(id);
    }
    return Promise.resolve();
  },

  _buildSubmissionURL(ping) {
    const version = isV4PingFormat(ping)
      ? AppConstants.TELEMETRY_PING_FORMAT_VERSION
      : 1;
    return this._server + this._getSubmissionPath(ping) + "?v=" + version;
  },

  _getSubmissionPath(ping) {
    // The new ping format contains an "application" section, the old one doesn't.
    let pathComponents;
    if (isV4PingFormat(ping)) {
      // We insert the Ping id in the URL to simplify server handling of duplicated
      // pings.
      let app = ping.application;
      pathComponents = [
        ping.id,
        ping.type,
        app.name,
        app.version,
        app.channel,
        app.buildId,
      ];
    } else {
      // This is a ping in the old format.
      if (!("slug" in ping)) {
        // That's odd, we don't have a slug. Generate one so that TelemetryStorage.sys.mjs works.
        ping.slug = Utils.generateUUID();
      }

      // Do we have enough info to build a submission URL?
      let payload = "payload" in ping ? ping.payload : null;
      if (payload && "info" in payload) {
        let info = ping.payload.info;
        pathComponents = [
          ping.slug,
          info.reason,
          info.appName,
          info.appVersion,
          info.appUpdateChannel,
          info.appBuildID,
        ];
      } else {
        // Only use the UUID as the slug.
        pathComponents = [ping.slug];
      }
    }

    let slug = pathComponents.join("/");
    return "/submit/telemetry/" + slug;
  },

  _doPingRequest(ping, id, url, options, errorHandler, onloadHandler) {
    // Don't send cookies with these requests.
    let request = new ServiceRequest({ mozAnon: true });
    request.mozBackgroundRequest = true;
    request.timeout = Policy.pingSubmissionTimeout();

    request.open("POST", url, options);
    request.overrideMimeType("text/plain");
    request.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
    request.setRequestHeader("Date", Policy.now().toUTCString());
    request.setRequestHeader("Content-Encoding", "gzip");
    request.onerror = errorHandler;
    request.ontimeout = errorHandler;
    request.onabort = errorHandler;
    request.onload = onloadHandler;
    this._pendingPingRequests.set(id, request);

    let startTime = Utils.monotonicNow();

    // If that's a legacy ping format, just send its payload.
    let networkPayload = isV4PingFormat(ping) ? ping : ping.payload;

    const utf8Payload = new TextEncoder().encode(
      JSON.stringify(networkPayload)
    );

    Services.telemetry
      .getHistogramById("TELEMETRY_STRINGIFY")
      .add(Utils.monotonicNow() - startTime);

    let payloadStream = Cc[
      "@mozilla.org/io/string-input-stream;1"
    ].createInstance(Ci.nsIStringInputStream);
    startTime = Utils.monotonicNow();
    payloadStream.data = Policy.gzipCompressString(arrayToString(utf8Payload));

    // Check the size and drop pings which are too big.
    const compressedPingSizeBytes = payloadStream.data.length;
    if (compressedPingSizeBytes > lazy.TelemetryStorage.MAXIMUM_PING_SIZE) {
      this._log.error(
        "_doPing - submitted ping exceeds the size limit, size: " +
          compressedPingSizeBytes
      );
      Services.telemetry
        .getHistogramById("TELEMETRY_PING_SIZE_EXCEEDED_SEND")
        .add();
      Services.telemetry
        .getHistogramById("TELEMETRY_DISCARDED_SEND_PINGS_SIZE_MB")
        .add(Math.floor(compressedPingSizeBytes / 1024 / 1024));
      // We don't need to call |request.abort()| as it was not sent yet.
      this._pendingPingRequests.delete(id);

      lazy.TelemetryHealthPing.recordDiscardedPing(ping.type);
      return { promise: lazy.TelemetryStorage.removePendingPing(id) };
    }

    Services.telemetry
      .getHistogramById("TELEMETRY_COMPRESS")
      .add(Utils.monotonicNow() - startTime);
    request.sendInputStream(payloadStream);

    return { payloadStream };
  },

  _doPing(ping, id, isPersisted) {
    if (!this.sendingEnabled(ping)) {
      // We can't send the pings to the server, so don't try to.
      this._log.trace("_doPing - Can't send ping " + ping.id);
      return Promise.resolve();
    }

    if (this._tooLateToSend) {
      // Too late to send now. Reject so we pend the ping to send it next time.
      this._log.trace("_doPing - Too late to send ping " + ping.id);
      Services.telemetry
        .getHistogramById("TELEMETRY_SEND_FAILURE_TYPE")
        .add("eTooLate");
      Services.telemetry
        .getKeyedHistogramById("TELEMETRY_SEND_FAILURE_TYPE_PER_PING")
        .add(ping.type, "eTooLate");
      return Promise.reject();
    }

    this._log.trace(
      "_doPing - server: " +
        this._server +
        ", persisted: " +
        isPersisted +
        ", id: " +
        id
    );

    const url = this._buildSubmissionURL(ping);

    const monotonicStartTime = Utils.monotonicNow();
    let deferred = Promise.withResolvers();

    let onRequestFinished = (success, event) => {
      let onCompletion = () => {
        if (success) {
          deferred.resolve();
        } else {
          deferred.reject(event);
        }
      };

      this._pendingPingRequests.delete(id);
      this._onPingRequestFinished(
        success,
        monotonicStartTime,
        id,
        isPersisted
      ).then(
        () => onCompletion(),
        error => {
          this._log.error(
            "_doPing - request success: " + success + ", error: " + error
          );
          onCompletion();
        }
      );
    };

    let retryRequest = request => {
      if (
        this._shutdown ||
        ServiceRequest.isOffline ||
        Services.startup.shuttingDown ||
        !request.bypassProxyEnabled ||
        this._tooLateToSend ||
        request.bypassProxy ||
        !request.isProxied
      ) {
        return false;
      }
      ServiceRequest.logProxySource(request.channel, "telemetry.send");
      // If the request failed, and it's using a proxy, automatically
      // attempt without proxy.
      let { payloadStream } = this._doPingRequest(
        ping,
        id,
        url,
        { bypassProxy: true },
        errorHandler,
        onloadHandler
      );
      this.payloadStream = payloadStream;
      return true;
    };

    let errorHandler = event => {
      let request = event.target;
      if (retryRequest(request)) {
        return;
      }

      let failure = event.type;
      if (failure === "error") {
        failure = XHR_ERROR_TYPE[request.errorCode];
      }

      lazy.TelemetryHealthPing.recordSendFailure(failure);

      Services.telemetry
        .getHistogramById("TELEMETRY_SEND_FAILURE_TYPE")
        .add(failure);
      Services.telemetry
        .getKeyedHistogramById("TELEMETRY_SEND_FAILURE_TYPE_PER_PING")
        .add(ping.type, failure);

      this._log.error(
        "_doPing - error making request to " + url + ": " + failure
      );
      onRequestFinished(false, event);
    };

    let onloadHandler = event => {
      let request = event.target;
      let status = request.status;
      let statusClass = status - (status % 100);
      let success = false;

      if (statusClass === 200) {
        // We can treat all 2XX as success.
        this._log.info("_doPing - successfully loaded, status: " + status);
        success = true;
      } else if (statusClass === 400) {
        // 4XX means that something with the request was broken.
        this._log.error(
          "_doPing - error submitting to " +
            url +
            ", status: " +
            status +
            " - ping request broken?"
        );
        Services.telemetry
          .getHistogramById("TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS")
          .add();
        // TODO: we should handle this better, but for now we should avoid resubmitting
        // broken requests by pretending success.
        success = true;
      } else if (statusClass === 500) {
        // 5XX means there was a server-side error and we should try again later.
        this._log.error(
          "_doPing - error submitting to " +
            url +
            ", status: " +
            status +
            " - server error, should retry later"
        );
      } else {
        // We received an unexpected status code.
        this._log.error(
          "_doPing - error submitting to " +
            url +
            ", status: " +
            status +
            ", type: " +
            event.type
        );
      }
      if (!success && retryRequest(request)) {
        return;
      }

      onRequestFinished(success, event);
    };

    let { payloadStream, promise } = this._doPingRequest(
      ping,
      id,
      url,
      {},
      errorHandler,
      onloadHandler
    );
    if (promise) {
      return promise;
    }
    this.payloadStream = payloadStream;

    return deferred.promise;
  },

  /**
   * Check if sending is temporarily disabled.
   * @return {Boolean} True if we can send pings to the server right now, false if
   *         sending is temporarily disabled.
   */
  get canSendNow() {
    // If the reporting policy was not accepted yet, don't send pings.
    if (!lazy.TelemetryReportingPolicy.canUpload()) {
      return false;
    }

    return this._sendingEnabled;
  },

  /**
   * Check if sending is disabled. If Telemetry is not allowed to upload,
   * pings are not sent to the server.
   * If trying to send a "deletion-request" ping, don't block it.
   * If unified telemetry is off, don't send pings if Telemetry is disabled.
   *
   * @param {Object} [ping=null] A ping to be checked.
   * @return {Boolean} True if pings can be send to the servers, false otherwise.
   */
  sendingEnabled(ping = null) {
    // We only send pings from official builds, but allow overriding this for tests.
    if (
      !Services.telemetry.isOfficialTelemetry &&
      !this._testMode &&
      !this._overrideOfficialCheck
    ) {
      return false;
    }

    // With unified Telemetry, the FHR upload setting controls whether we can send pings.
    // The Telemetry pref enables sending extended data sets instead.
    if (IS_UNIFIED_TELEMETRY) {
      // "deletion-request" pings are sent once even if the upload is disabled.
      if (ping && isDeletionRequestPing(ping)) {
        return true;
      }
      return Services.prefs.getBoolPref(
        TelemetryUtils.Preferences.FhrUploadEnabled,
        false
      );
    }

    // Without unified Telemetry, the Telemetry enabled pref controls ping sending.
    return Services.prefs.getBoolPref(PREF_TELEMETRY_ENABLED, false) === true;
  },

  /**
   * Track any pending ping send and save tasks through the promise passed here.
   * This is needed to block shutdown on any outstanding ping activity.
   */
  _trackPendingPingTask(promise) {
    let clear = () => this._pendingPingActivity.delete(promise);
    promise.then(clear, clear);
    this._pendingPingActivity.add(promise);
  },

  /**
   * Return a promise that allows to wait on pending pings.
   * @return {Object<Promise>} A promise resolved when all the pending pings promises
   *         are resolved.
   */
  promisePendingPingActivity() {
    this._log.trace("promisePendingPingActivity - Waiting for ping task");
    let p = Array.from(this._pendingPingActivity, p =>
      p.catch(ex => {
        this._log.error(
          "promisePendingPingActivity - ping activity had an error",
          ex
        );
      })
    );
    p.push(SendScheduler.waitOnSendTask());
    return Promise.all(p);
  },

  async _persistCurrentPings() {
    for (let [id, ping] of this._currentPings) {
      try {
        await savePing(ping);
        this._log.trace("_persistCurrentPings - saved ping " + id);
      } catch (ex) {
        this._log.error("_persistCurrentPings - failed to save ping " + id, ex);
      } finally {
        this._currentPings.delete(id);
      }
    }
  },

  /**
   * Returns the current pending, not yet persisted, pings, newest first.
   */
  getUnpersistedPings() {
    let current = [...this._currentPings.values()];
    current.reverse();
    return current;
  },

  getShutdownState() {
    return {
      sendingEnabled: this._sendingEnabled,
      pendingPingRequestCount: this._pendingPingRequests.size,
      pendingPingActivityCount: this._pendingPingActivity.size,
      unpersistedPingCount: this._currentPings.size,
      persistedPingCount: lazy.TelemetryStorage.getPendingPingList().length,
      schedulerState: SendScheduler.getShutdownState(),
    };
  },

  runPingSender(pings, observer) {
    if (AppConstants.platform === "android") {
      throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
    }

    let suppressPingsender = Services.prefs.getBoolPref(
      "toolkit.telemetry.testing.suppressPingsender",
      false
    );
    if (suppressPingsender) {
      this._log.trace("Silently skipping pingsender call in automation");
      return;
    }

    // By default, invoke `pingsender[.exe] URL path ...`.
    let exeName =
      AppConstants.platform === "win" ? "pingsender.exe" : "pingsender";
    let params = [];

    if (lazy.NimbusFeatures.pingsender.getVariable("backgroundTaskEnabled")) {
      // If using pingsender background task, invoke `firefox[.exe] --backgroundtask pingsender URL path ...`.
      exeName =
        AppConstants.MOZ_APP_NAME +
        (AppConstants.platform === "win" ? ".exe" : "");
      params = ["--backgroundtask", "pingsender"];
    }

    this._log.info(
      `Invoking '${exeName}${params.length ? " " + params.join(" ") : ""} ...'`
    );

    let exe = Services.dirsvc.get("GreBinD", Ci.nsIFile);
    exe.append(exeName);

    params.push(...pings.flatMap(ping => [ping.url, ping.path]));
    let process = Cc["@mozilla.org/process/util;1"].createInstance(
      Ci.nsIProcess
    );
    process.init(exe);
    process.startHidden = true;
    process.noShell = true;
    process.runAsync(params, params.length, observer);
  },
};
PK
!<1k��� modules/TelemetrySession.sys.mjs/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Log } from "resource://gre/modules/Log.sys.mjs";
import { TelemetryUtils } from "resource://gre/modules/TelemetryUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs",
  TelemetryController: "resource://gre/modules/TelemetryController.sys.mjs",
  TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
  TelemetryReportingPolicy:
    "resource://gre/modules/TelemetryReportingPolicy.sys.mjs",
  TelemetryScheduler: "resource://gre/modules/TelemetryScheduler.sys.mjs",
  TelemetryStorage: "resource://gre/modules/TelemetryStorage.sys.mjs",
});

const Utils = TelemetryUtils;

// When modifying the payload in incompatible ways, please bump this version number
const PAYLOAD_VERSION = 4;
const PING_TYPE_MAIN = "main";
const PING_TYPE_SAVED_SESSION = "saved-session";

const REASON_ABORTED_SESSION = "aborted-session";
const REASON_DAILY = "daily";
const REASON_SAVED_SESSION = "saved-session";
const REASON_GATHER_PAYLOAD = "gather-payload";
const REASON_TEST_PING = "test-ping";
const REASON_ENVIRONMENT_CHANGE = "environment-change";
const REASON_SHUTDOWN = "shutdown";

const ENVIRONMENT_CHANGE_LISTENER = "TelemetrySession::onEnvironmentChange";

const MIN_SUBSESSION_LENGTH_MS =
  Services.prefs.getIntPref("toolkit.telemetry.minSubsessionLength", 5 * 60) *
  1000;

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX =
  "TelemetrySession" + (Utils.isContentProcess ? "#content::" : "::");

// Whether the FHR/Telemetry unification features are enabled.
// Changing this pref requires a restart.
const IS_UNIFIED_TELEMETRY = Services.prefs.getBoolPref(
  TelemetryUtils.Preferences.Unified,
  false
);

var gWasDebuggerAttached = false;

function generateUUID() {
  let str = Services.uuid.generateUUID().toString();
  // strip {}
  return str.substring(1, str.length - 1);
}

/**
 * This is a policy object used to override behavior for testing.
 */
export var Policy = {
  now: () => new Date(),
  monotonicNow: Utils.monotonicNow,
  generateSessionUUID: () => generateUUID(),
  generateSubsessionUUID: () => generateUUID(),
};

/**
 * Get the ping type based on the payload.
 * @param {Object} aPayload The ping payload.
 * @return {String} A string representing the ping type.
 */
function getPingType(aPayload) {
  // To remain consistent with server-side ping handling, set "saved-session" as the ping
  // type for "saved-session" payload reasons.
  if (aPayload.info.reason == REASON_SAVED_SESSION) {
    return PING_TYPE_SAVED_SESSION;
  }

  return PING_TYPE_MAIN;
}

/**
 * Annotate the current session ID with the crash reporter to map potential
 * crash pings with the related main ping.
 */
function annotateCrashReport(sessionId) {
  try {
    Services.appinfo.annotateCrashReport("TelemetrySessionId", sessionId);
  } catch (e) {
    // Ignore errors when crash reporting is disabled
  }
}

/**
 * Read current process I/O counters.
 */
var processInfo = {
  _initialized: false,
  _IO_COUNTERS: null,
  _kernel32: null,
  _GetProcessIoCounters: null,
  _GetCurrentProcess: null,
  getCounters() {
    let isWindows = "@mozilla.org/windows-registry-key;1" in Cc;
    if (isWindows) {
      return this.getCounters_Windows();
    }
    return null;
  },
  getCounters_Windows() {
    if (!this._initialized) {
      var { ctypes } = ChromeUtils.importESModule(
        "resource://gre/modules/ctypes.sys.mjs"
      );
      this._IO_COUNTERS = new ctypes.StructType("IO_COUNTERS", [
        { readOps: ctypes.unsigned_long_long },
        { writeOps: ctypes.unsigned_long_long },
        { otherOps: ctypes.unsigned_long_long },
        { readBytes: ctypes.unsigned_long_long },
        { writeBytes: ctypes.unsigned_long_long },
        { otherBytes: ctypes.unsigned_long_long },
      ]);
      try {
        this._kernel32 = ctypes.open("Kernel32.dll");
        this._GetProcessIoCounters = this._kernel32.declare(
          "GetProcessIoCounters",
          ctypes.winapi_abi,
          ctypes.bool, // return
          ctypes.voidptr_t, // hProcess
          this._IO_COUNTERS.ptr
        ); // lpIoCounters
        this._GetCurrentProcess = this._kernel32.declare(
          "GetCurrentProcess",
          ctypes.winapi_abi,
          ctypes.voidptr_t
        ); // return
        this._initialized = true;
      } catch (err) {
        return null;
      }
    }
    let io = new this._IO_COUNTERS();
    if (!this._GetProcessIoCounters(this._GetCurrentProcess(), io.address())) {
      return null;
    }
    return [parseInt(io.readBytes), parseInt(io.writeBytes)];
  },
};

export var TelemetrySession = Object.freeze({
  /**
   * Send a ping to a test server. Used only for testing.
   */
  testPing() {
    return Impl.testPing();
  },
  /**
   * Returns the current telemetry payload.
   * @param reason Optional, the reason to trigger the payload.
   * @param clearSubsession Optional, whether to clear subsession specific data.
   * @returns Object
   */
  getPayload(reason, clearSubsession = false) {
    return Impl.getPayload(reason, clearSubsession);
  },
  /**
   * Save the session state to a pending file.
   * Used only for testing purposes.
   */
  testSavePendingPing() {
    return Impl.testSavePendingPing();
  },
  /**
   * Collect and store information about startup.
   */
  gatherStartup() {
    return Impl.gatherStartup();
  },
  /**
   * Inform the ping which AddOns are installed.
   *
   * @param aAddOns - The AddOns.
   */
  setAddOns(aAddOns) {
    return Impl.setAddOns(aAddOns);
  },
  /**
   * Descriptive metadata
   *
   * @param  reason
   *         The reason for the telemetry ping, this will be included in the
   *         returned metadata,
   * @return The metadata as a JS object
   */
  getMetadata(reason) {
    return Impl.getMetadata(reason);
  },

  /**
   * Reset the subsession and profile subsession counter.
   * This should only be called when the profile should be considered completely new,
   * e.g. after opting out of sending Telemetry
   */
  resetSubsessionCounter() {
    Impl._subsessionCounter = 0;
    Impl._profileSubsessionCounter = 0;
  },

  /**
   * Used only for testing purposes.
   */
  testReset() {
    Impl._newProfilePingSent = false;
    Impl._sessionId = null;
    Impl._subsessionId = null;
    Impl._previousSessionId = null;
    Impl._previousSubsessionId = null;
    Impl._subsessionCounter = 0;
    Impl._profileSubsessionCounter = 0;
    Impl._subsessionStartActiveTicks = 0;
    Impl._sessionActiveTicks = 0;
    Impl._isUserActive = true;
    Impl._subsessionStartTimeMonotonic = 0;
    Impl._lastEnvironmentChangeDate = Policy.monotonicNow();
    this.testUninstall();
  },
  /**
   * Triggers shutdown of the module.
   */
  shutdown() {
    return Impl.shutdownChromeProcess();
  },
  /**
   * Used only for testing purposes.
   */
  testUninstall() {
    try {
      Impl.uninstall();
    } catch (ex) {
      // Ignore errors
    }
  },
  /**
   * Lightweight init function, called as soon as Firefox starts.
   */
  earlyInit(aTesting = false) {
    return Impl.earlyInit(aTesting);
  },
  /**
   * Does the "heavy" Telemetry initialization later on, so we
   * don't impact startup performance.
   * @return {Promise} Resolved when the initialization completes.
   */
  delayedInit() {
    return Impl.delayedInit();
  },
  /**
   * Send a notification.
   */
  observe(aSubject, aTopic, aData) {
    return Impl.observe(aSubject, aTopic, aData);
  },
  /**
   * Marks the "new-profile" ping as sent in the telemetry state file.
   * @return {Promise} A promise resolved when the new telemetry state is saved to disk.
   */
  markNewProfilePingSent() {
    return Impl.markNewProfilePingSent();
  },
  /**
   * Returns if the "new-profile" ping has ever been sent for this profile.
   * Please note that the returned value is trustworthy only after the delayed setup.
   *
   * @return {Boolean} True if the new profile ping was sent on this profile,
   *         false otherwise.
   */
  get newProfilePingSent() {
    return Impl._newProfilePingSent;
  },

  saveAbortedSessionPing(aProvidedPayload) {
    return Impl._saveAbortedSessionPing(aProvidedPayload);
  },

  sendDailyPing() {
    return Impl._sendDailyPing();
  },

  testOnEnvironmentChange(...args) {
    return Impl._onEnvironmentChange(...args);
  },
});

var Impl = {
  _initialized: false,
  _logger: null,
  _slowSQLStartup: {},
  // The activity state for the user. If false, don't count the next
  // active tick. Otherwise, increment the active ticks as usual.
  _isUserActive: true,
  _startupIO: {},
  // The previous build ID, if this is the first run with a new build.
  // Null if this is the first run, or the previous build ID is unknown.
  _previousBuildId: null,
  // Unique id that identifies this session so the server can cope with duplicate
  // submissions, orphaning and other oddities. The id is shared across subsessions.
  _sessionId: null,
  // Random subsession id.
  _subsessionId: null,
  // Session id of the previous session, null on first run.
  _previousSessionId: null,
  // Subsession id of the previous subsession (even if it was in a different session),
  // null on first run.
  _previousSubsessionId: null,
  // The running no. of subsessions since the start of the browser session
  _subsessionCounter: 0,
  // The running no. of all subsessions for the whole profile life time
  _profileSubsessionCounter: 0,
  // Date of the last session split
  _subsessionStartDate: null,
  // Start time of the current subsession using a monotonic clock for the subsession
  // length measurements.
  _subsessionStartTimeMonotonic: 0,
  // The active ticks counted when the subsession starts
  _subsessionStartActiveTicks: 0,
  // Active ticks in the whole session.
  _sessionActiveTicks: 0,
  // A task performing delayed initialization of the chrome process
  _delayedInitTask: null,
  _testing: false,
  // An accumulator of total memory across all processes. Only valid once the final child reports.
  _lastEnvironmentChangeDate: 0,
  // We save whether the "new-profile" ping was sent yet, to
  // survive profile refresh and migrations.
  _newProfilePingSent: false,
  // Keep track of the active observers
  _observedTopics: new Set(),

  addObserver(aTopic) {
    Services.obs.addObserver(this, aTopic);
    this._observedTopics.add(aTopic);
  },

  removeObserver(aTopic) {
    Services.obs.removeObserver(this, aTopic);
    this._observedTopics.delete(aTopic);
  },

  get _log() {
    if (!this._logger) {
      this._logger = Log.repository.getLoggerWithMessagePrefix(
        LOGGER_NAME,
        LOGGER_PREFIX
      );
    }
    return this._logger;
  },

  /**
   * Gets a series of simple measurements (counters). At the moment, this
   * only returns startup data from nsIAppStartup.getStartupInfo().
   * @param {Boolean} isSubsession True if this is a subsession, false otherwise.
   * @param {Boolean} clearSubsession True if a new subsession is being started, false otherwise.
   *
   * @return simple measurements as a dictionary.
   */
  getSimpleMeasurements: function getSimpleMeasurements(
    forSavedSession,
    isSubsession,
    clearSubsession
  ) {
    let si = Services.startup.getStartupInfo();

    // Measurements common to chrome and content processes.
    let elapsedTime = Date.now() - si.process;
    var ret = {
      totalTime: Math.round(elapsedTime / 1000), // totalTime, in seconds
    };

    // Look for app-specific timestamps
    var appTimestamps = {};
    try {
      let { TelemetryTimestamps } = ChromeUtils.importESModule(
        "resource://gre/modules/TelemetryTimestamps.sys.mjs"
      );
      appTimestamps = TelemetryTimestamps.get();
    } catch (ex) {}

    // Only submit this if the extended set is enabled.
    if (!Utils.isContentProcess && Services.telemetry.canRecordExtended) {
      try {
        ret.addonManager = lazy.AddonManagerPrivate.getSimpleMeasures();
      } catch (ex) {}
    }

    if (si.process) {
      for (let field of Object.keys(si)) {
        if (field == "process") {
          continue;
        }
        ret[field] = si[field] - si.process;
      }

      for (let p in appTimestamps) {
        if (!(p in ret) && appTimestamps[p]) {
          ret[p] = appTimestamps[p] - si.process;
        }
      }
    }

    ret.startupInterrupted = Number(Services.startup.interrupted);

    if (Utils.isContentProcess) {
      return ret;
    }

    // Measurements specific to chrome process

    // Update debuggerAttached flag
    let debugService = Cc["@mozilla.org/xpcom/debug;1"].getService(
      Ci.nsIDebug2
    );
    let isDebuggerAttached = debugService.isDebuggerAttached;
    gWasDebuggerAttached = gWasDebuggerAttached || isDebuggerAttached;
    ret.debuggerAttached = Number(gWasDebuggerAttached);

    let shutdownDuration = Services.telemetry.lastShutdownDuration;
    if (shutdownDuration) {
      ret.shutdownDuration = shutdownDuration;
    }

    let failedProfileLockCount = Services.telemetry.failedProfileLockCount;
    if (failedProfileLockCount) {
      ret.failedProfileLockCount = failedProfileLockCount;
    }

    for (let ioCounter in this._startupIO) {
      ret[ioCounter] = this._startupIO[ioCounter];
    }

    let activeTicks = this._sessionActiveTicks;
    if (isSubsession) {
      activeTicks = this._sessionActiveTicks - this._subsessionStartActiveTicks;
    }

    if (clearSubsession) {
      this._subsessionStartActiveTicks = this._sessionActiveTicks;
    }

    ret.activeTicks = activeTicks;

    return ret;
  },

  getHistograms: function getHistograms(clearSubsession) {
    return Services.telemetry.getSnapshotForHistograms(
      "main",
      clearSubsession,
      !this._testing
    );
  },

  getKeyedHistograms(clearSubsession) {
    return Services.telemetry.getSnapshotForKeyedHistograms(
      "main",
      clearSubsession,
      !this._testing
    );
  },

  /**
   * Get a snapshot of the scalars and clear them.
   * @param {subsession} If true, then we collect the data for a subsession.
   * @param {clearSubsession} If true, we  need to clear the subsession.
   * @param {keyed} Take a snapshot of keyed or non keyed scalars.
   * @return {Object} The scalar data as a Javascript object, including the
   *         data from child processes, in the following format:
   *            {'content': { 'scalarName': ... }, 'gpu': { ... } }
   */
  getScalars(subsession, clearSubsession, keyed) {
    if (!subsession) {
      // We only support scalars for subsessions.
      this._log.trace("getScalars - We only support scalars in subsessions.");
      return {};
    }

    let scalarsSnapshot = keyed
      ? Services.telemetry.getSnapshotForKeyedScalars(
          "main",
          clearSubsession,
          !this._testing
        )
      : Services.telemetry.getSnapshotForScalars(
          "main",
          clearSubsession,
          !this._testing
        );

    return scalarsSnapshot;
  },

  /**
   * Descriptive metadata
   *
   * @param  reason
   *         The reason for the telemetry ping, this will be included in the
   *         returned metadata,
   * @return The metadata as a JS object
   */
  getMetadata: function getMetadata(reason) {
    const sessionStartDate = Utils.toLocalTimeISOString(
      Utils.truncateToHours(this._sessionStartDate)
    );
    const subsessionStartDate = Utils.toLocalTimeISOString(
      Utils.truncateToHours(this._subsessionStartDate)
    );
    const monotonicNow = Policy.monotonicNow();

    let ret = {
      reason,
      revision: AppConstants.SOURCE_REVISION_URL,

      // Date.getTimezoneOffset() unintuitively returns negative values if we are ahead of
      // UTC and vice versa (e.g. -60 for UTC+1). We invert the sign here.
      timezoneOffset: -this._subsessionStartDate.getTimezoneOffset(),
      previousBuildId: this._previousBuildId,

      sessionId: this._sessionId,
      subsessionId: this._subsessionId,
      previousSessionId: this._previousSessionId,
      previousSubsessionId: this._previousSubsessionId,

      subsessionCounter: this._subsessionCounter,
      profileSubsessionCounter: this._profileSubsessionCounter,

      sessionStartDate,
      subsessionStartDate,

      // Compute the session and subsession length in seconds.
      // We use monotonic clocks as Date() is affected by jumping clocks (leading
      // to negative lengths and other issues).
      sessionLength: Math.floor(monotonicNow / 1000),
      subsessionLength: Math.floor(
        (monotonicNow - this._subsessionStartTimeMonotonic) / 1000
      ),
    };

    // TODO: Remove this when bug 1201837 lands.
    if (this._addons) {
      ret.addons = this._addons;
    }

    return ret;
  },

  /**
   * Get the current session's payload using the provided
   * simpleMeasurements and info, which are typically obtained by a call
   * to |this.getSimpleMeasurements| and |this.getMetadata|,
   * respectively.
   */
  assemblePayloadWithMeasurements(
    simpleMeasurements,
    info,
    reason,
    clearSubsession
  ) {
    const isSubsession = IS_UNIFIED_TELEMETRY && !this._isClassicReason(reason);
    clearSubsession = IS_UNIFIED_TELEMETRY && clearSubsession;
    this._log.trace(
      "assemblePayloadWithMeasurements - reason: " +
        reason +
        ", submitting subsession data: " +
        isSubsession
    );

    // This allows wrapping data retrieval calls in a try-catch block so that
    // failures don't break the rest of the ping assembly.
    const protect = (fn, defaultReturn = null) => {
      try {
        return fn();
      } catch (ex) {
        this._log.error(
          "assemblePayloadWithMeasurements - caught exception",
          ex
        );
        return defaultReturn;
      }
    };

    // Payload common to chrome and content processes.
    let payloadObj = {
      ver: PAYLOAD_VERSION,
      simpleMeasurements,
    };

    // Add extended set measurements common to chrome & content processes
    if (Services.telemetry.canRecordExtended) {
      payloadObj.log = [];
    }

    if (Utils.isContentProcess) {
      return payloadObj;
    }

    // Additional payload for chrome process.
    let measurements = {
      histograms: protect(() => this.getHistograms(clearSubsession), {}),
      keyedHistograms: protect(
        () => this.getKeyedHistograms(clearSubsession),
        {}
      ),
      scalars: protect(
        () => this.getScalars(isSubsession, clearSubsession),
        {}
      ),
      keyedScalars: protect(
        () => this.getScalars(isSubsession, clearSubsession, true),
        {}
      ),
    };

    let measurementsContainGPU = Object.keys(measurements).some(
      key => "gpu" in measurements[key]
    );

    let measurementsContainSocket = Object.keys(measurements).some(
      key => "socket" in measurements[key]
    );

    let measurementsContainUtility = Object.keys(measurements).some(
      key => "utility" in measurements[key]
    );

    payloadObj.processes = {};
    let processTypes = ["parent", "content", "extension", "dynamic"];
    // Only include the GPU process if we've accumulated data for it.
    if (measurementsContainGPU) {
      processTypes.push("gpu");
    }
    if (measurementsContainSocket) {
      processTypes.push("socket");
    }
    if (measurementsContainUtility) {
      processTypes.push("utility");
    }

    // Collect per-process measurements.
    for (const processType of processTypes) {
      let processPayload = {};

      for (const key in measurements) {
        let payloadLoc = processPayload;
        // Parent histograms are added to the top-level payload object instead of the process payload.
        if (
          processType == "parent" &&
          (key == "histograms" || key == "keyedHistograms")
        ) {
          payloadLoc = payloadObj;
        }
        // The Dynamic process only collects scalars and keyed scalars.
        if (
          processType == "dynamic" &&
          key !== "scalars" &&
          key !== "keyedScalars"
        ) {
          continue;
        }

        // Process measurements can be empty, set a default value.
        payloadLoc[key] = measurements[key][processType] || {};
      }

      // Add process measurements to payload.
      payloadObj.processes[processType] = processPayload;
    }

    payloadObj.info = info;

    // Add extended set measurements for chrome process.
    if (Services.telemetry.canRecordExtended) {
      payloadObj.slowSQL = protect(() => Services.telemetry.slowSQL);
      payloadObj.fileIOReports = protect(
        () => Services.telemetry.fileIOReports
      );
      payloadObj.lateWrites = protect(() => Services.telemetry.lateWrites);

      payloadObj.addonDetails = protect(() =>
        lazy.AddonManagerPrivate.getTelemetryDetails()
      );

      if (
        this._slowSQLStartup &&
        !!Object.keys(this._slowSQLStartup).length &&
        (Object.keys(this._slowSQLStartup.mainThread).length ||
          Object.keys(this._slowSQLStartup.otherThreads).length)
      ) {
        payloadObj.slowSQLStartup = this._slowSQLStartup;
      }
    }

    return payloadObj;
  },

  /**
   * Start a new subsession.
   */
  startNewSubsession() {
    this._subsessionStartDate = Policy.now();
    this._subsessionStartTimeMonotonic = Policy.monotonicNow();
    this._previousSubsessionId = this._subsessionId;
    this._subsessionId = Policy.generateSubsessionUUID();
    this._subsessionCounter++;
    this._profileSubsessionCounter++;
  },

  getSessionPayload: function getSessionPayload(reason, clearSubsession) {
    this._log.trace(
      "getSessionPayload - reason: " +
        reason +
        ", clearSubsession: " +
        clearSubsession
    );

    let payload;
    try {
      const isMobile = AppConstants.platform == "android";
      const isSubsession = isMobile ? false : !this._isClassicReason(reason);

      // The order of the next two msSinceProcessStart* calls is somewhat
      // important. In theory, `session_time_including_suspend` is supposed to
      // ALWAYS be lower or equal than `session_time_excluding_suspend` (because
      // the former is a temporal superset of the latter). When a device has not
      // been suspended since boot, we want the previous property to hold,
      // regardless of the delay during or between the two
      // `msSinceProcessStart*` calls.
      Services.telemetry.scalarSet(
        "browser.engagement.session_time_excluding_suspend",
        Services.telemetry.msSinceProcessStartExcludingSuspend()
      );
      Services.telemetry.scalarSet(
        "browser.engagement.session_time_including_suspend",
        Services.telemetry.msSinceProcessStartIncludingSuspend()
      );

      if (isMobile) {
        clearSubsession = false;
      }

      let measurements = this.getSimpleMeasurements(
        reason == REASON_SAVED_SESSION,
        isSubsession,
        clearSubsession
      );
      let info = !Utils.isContentProcess ? this.getMetadata(reason) : null;
      payload = this.assemblePayloadWithMeasurements(
        measurements,
        info,
        reason,
        clearSubsession
      );
    } finally {
      if (!Utils.isContentProcess && clearSubsession) {
        this.startNewSubsession();
        // Persist session data to disk (don't wait until it completes).
        let sessionData = this._getSessionDataObject();
        lazy.TelemetryStorage.saveSessionData(sessionData);

        // Notify that there was a subsession split in the parent process. This is an
        // internal topic and is only meant for internal Telemetry usage.
        Services.obs.notifyObservers(
          null,
          "internal-telemetry-after-subsession-split"
        );
      }
    }

    return payload;
  },

  /**
   * Send data to the server. Record success/send-time in histograms
   */
  send: async function send(reason) {
    this._log.trace("send - Reason " + reason);
    // populate histograms one last time
    await Services.telemetry.gatherMemory();

    const isSubsession = !this._isClassicReason(reason);
    let payload = this.getSessionPayload(reason, isSubsession);
    let options = {
      addClientId: true,
      addEnvironment: true,
    };
    return lazy.TelemetryController.submitExternalPing(
      getPingType(payload),
      payload,
      options
    );
  },

  /**
   * Attaches the needed observers during Telemetry early init, in the
   * chrome process.
   */
  attachEarlyObservers() {
    this.addObserver("sessionstore-windows-restored");
    if (AppConstants.platform === "android") {
      this.addObserver("application-background");
    }
    this.addObserver("xul-window-visible");

    // Attach the active-ticks related observers.
    this.addObserver("user-interaction-active");
    this.addObserver("user-interaction-inactive");
  },

  /**
   * Lightweight init function, called as soon as Firefox starts.
   */
  earlyInit(testing) {
    this._log.trace("earlyInit");

    this._initStarted = true;
    this._testing = testing;

    if (this._initialized && !testing) {
      this._log.error("earlyInit - already initialized");
      return;
    }

    if (!Services.telemetry.canRecordBase && !testing) {
      this._log.config(
        "earlyInit - Telemetry recording is disabled, skipping Chrome process setup."
      );
      return;
    }

    // Generate a unique id once per session so the server can cope with duplicate
    // submissions, orphaning and other oddities. The id is shared across subsessions.
    this._sessionId = Policy.generateSessionUUID();
    this.startNewSubsession();
    // startNewSubsession sets |_subsessionStartDate| to the current date/time. Use
    // the very same value for |_sessionStartDate|.
    this._sessionStartDate = this._subsessionStartDate;

    annotateCrashReport(this._sessionId);

    // Record old value and update build ID preference if this is the first
    // run with a new build ID.
    let previousBuildId = Services.prefs.getStringPref(
      TelemetryUtils.Preferences.PreviousBuildID,
      null
    );
    let thisBuildID = Services.appinfo.appBuildID;
    // If there is no previousBuildId preference, we send null to the server.
    if (previousBuildId != thisBuildID) {
      this._previousBuildId = previousBuildId;
      Services.prefs.setStringPref(
        TelemetryUtils.Preferences.PreviousBuildID,
        thisBuildID
      );
    }

    this.attachEarlyObservers();
  },

  /**
   * Does the "heavy" Telemetry initialization later on, so we
   * don't impact startup performance.
   * @return {Promise} Resolved when the initialization completes.
   */
  delayedInit() {
    this._log.trace("delayedInit");

    this._delayedInitTask = (async () => {
      try {
        this._initialized = true;

        await this._loadSessionData();
        // Update the session data to keep track of new subsessions created before
        // the initialization.
        await lazy.TelemetryStorage.saveSessionData(
          this._getSessionDataObject()
        );

        this.addObserver("idle-daily");
        await Services.telemetry.gatherMemory();

        Services.telemetry.asyncFetchTelemetryData(function () {});

        if (IS_UNIFIED_TELEMETRY) {
          // Check for a previously written aborted session ping.
          await lazy.TelemetryController.checkAbortedSessionPing();

          // Write the first aborted-session ping as early as possible. Just do that
          // if we are not testing, since calling Telemetry.reset() will make a previous
          // aborted ping a pending ping.
          if (!this._testing) {
            await this._saveAbortedSessionPing();
          }

          // The last change date for the environment, used to throttle environment changes.
          this._lastEnvironmentChangeDate = Policy.monotonicNow();
          lazy.TelemetryEnvironment.registerChangeListener(
            ENVIRONMENT_CHANGE_LISTENER,
            (reason, data) => this._onEnvironmentChange(reason, data)
          );

          // Start the scheduler.
          // We skip this if unified telemetry is off, so we don't
          // trigger the new unified ping types.
          lazy.TelemetryScheduler.init();
        }

        this._delayedInitTask = null;
      } catch (e) {
        this._delayedInitTask = null;
        throw e;
      }
    })();

    return this._delayedInitTask;
  },

  /**
   * On Desktop: Save the "shutdown" ping to disk.
   * On Android: Save the "saved-session" ping to disk.
   * This needs to be called after TelemetrySend shuts down otherwise pings
   * would be sent instead of getting persisted to disk.
   */
  saveShutdownPings() {
    this._log.trace("saveShutdownPings");

    // We append the promises to this list and wait
    // on all pings to be saved after kicking off their collection.
    let p = [];

    if (IS_UNIFIED_TELEMETRY) {
      let shutdownPayload = this.getSessionPayload(REASON_SHUTDOWN, false);

      // Only send the shutdown ping using the pingsender from the second
      // browsing session on, to mitigate issues with "bot" profiles (see bug 1354482).
      const sendOnThisSession =
        Services.prefs.getBoolPref(
          Utils.Preferences.ShutdownPingSenderFirstSession,
          false
        ) || !lazy.TelemetryReportingPolicy.isFirstRun();
      let sendWithPingsender =
        Services.prefs.getBoolPref(
          TelemetryUtils.Preferences.ShutdownPingSender,
          false
        ) && sendOnThisSession;

      let options = {
        addClientId: true,
        addEnvironment: true,
        usePingSender: sendWithPingsender,
      };
      p.push(
        lazy.TelemetryController.submitExternalPing(
          getPingType(shutdownPayload),
          shutdownPayload,
          options
        ).catch(e =>
          this._log.error(
            "saveShutdownPings - failed to submit shutdown ping",
            e
          )
        )
      );

      // Send a duplicate of first-shutdown pings as a new ping type, in order to properly
      // evaluate first session profiles (see bug 1390095).
      const sendFirstShutdownPing =
        Services.prefs.getBoolPref(
          Utils.Preferences.ShutdownPingSender,
          false
        ) &&
        Services.prefs.getBoolPref(
          Utils.Preferences.FirstShutdownPingEnabled,
          false
        ) &&
        lazy.TelemetryReportingPolicy.isFirstRun();

      if (sendFirstShutdownPing) {
        let options = {
          addClientId: true,
          addEnvironment: true,
          usePingSender: true,
        };
        p.push(
          lazy.TelemetryController.submitExternalPing(
            "first-shutdown",
            shutdownPayload,
            options
          ).catch(e =>
            this._log.error(
              "saveShutdownPings - failed to submit first shutdown ping",
              e
            )
          )
        );
      }
    }

    if (
      AppConstants.platform == "android" &&
      Services.telemetry.canRecordExtended
    ) {
      let payload = this.getSessionPayload(REASON_SAVED_SESSION, false);

      let options = {
        addClientId: true,
        addEnvironment: true,
      };
      p.push(
        lazy.TelemetryController.submitExternalPing(
          getPingType(payload),
          payload,
          options
        ).catch(e =>
          this._log.error(
            "saveShutdownPings - failed to submit saved-session ping",
            e
          )
        )
      );
    }

    // Wait on pings to be saved.
    return Promise.all(p);
  },

  testSavePendingPing() {
    let payload = this.getSessionPayload(REASON_SAVED_SESSION, false);
    let options = {
      addClientId: true,
      addEnvironment: true,
      overwrite: true,
    };
    return lazy.TelemetryController.addPendingPing(
      getPingType(payload),
      payload,
      options
    );
  },

  /**
   * Do some shutdown work that is common to all process types.
   */
  uninstall() {
    for (let topic of this._observedTopics) {
      try {
        // Tests may flip Telemetry.canRecordExtended on and off. It can be the case
        // that the observer TOPIC_CYCLE_COLLECTOR_BEGIN was not added.
        this.removeObserver(topic);
      } catch (e) {
        this._log.warn("uninstall - Failed to remove " + topic, e);
      }
    }
  },

  getPayload: function getPayload(reason, clearSubsession) {
    this._log.trace("getPayload - clearSubsession: " + clearSubsession);
    reason = reason || REASON_GATHER_PAYLOAD;
    // This function returns the current Telemetry payload to the caller.
    // We only gather startup info once.
    if (!Object.keys(this._slowSQLStartup).length) {
      this._slowSQLStartup = Services.telemetry.slowSQL;
    }
    Services.telemetry.gatherMemory();
    return this.getSessionPayload(reason, clearSubsession);
  },

  gatherStartup: function gatherStartup() {
    this._log.trace("gatherStartup");
    let counters = processInfo.getCounters();
    if (counters) {
      [
        this._startupIO.startupSessionRestoreReadBytes,
        this._startupIO.startupSessionRestoreWriteBytes,
      ] = counters;
    }
    this._slowSQLStartup = Services.telemetry.slowSQL;
  },

  setAddOns: function setAddOns(aAddOns) {
    this._addons = aAddOns;
  },

  testPing: function testPing() {
    return this.send(REASON_TEST_PING);
  },

  /**
   * Tracks the number of "ticks" the user was active in.
   */
  _onActiveTick(aUserActive) {
    const needsUpdate = aUserActive && this._isUserActive;
    this._isUserActive = aUserActive;

    // Don't count the first active tick after we get out of
    // inactivity, because it is just the start of this active tick.
    if (needsUpdate) {
      this._sessionActiveTicks++;
      Services.telemetry.scalarAdd("browser.engagement.active_ticks", 1);
      Glean.browserEngagement.activeTicks.add(1);
    }
  },

  /**
   * This observer drives telemetry.
   */
  observe(aSubject, aTopic) {
    this._log.trace("observe - " + aTopic + " notified.");

    switch (aTopic) {
      case "xul-window-visible":
        this.removeObserver("xul-window-visible");
        var counters = processInfo.getCounters();
        if (counters) {
          [
            this._startupIO.startupWindowVisibleReadBytes,
            this._startupIO.startupWindowVisibleWriteBytes,
          ] = counters;
        }
        break;
      case "sessionstore-windows-restored":
        this.removeObserver("sessionstore-windows-restored");
        // Check whether debugger was attached during startup
        let debugService = Cc["@mozilla.org/xpcom/debug;1"].getService(
          Ci.nsIDebug2
        );
        gWasDebuggerAttached = debugService.isDebuggerAttached;
        this.gatherStartup();
        break;
      case "idle-daily":
        // Enqueue to main-thread, otherwise components may be inited by the
        // idle-daily category and miss the gather-telemetry notification.
        Services.tm.dispatchToMainThread(function () {
          // Notify that data should be gathered now.
          // TODO: We are keeping this behaviour for now but it will be removed as soon as
          // bug 1127907 lands.
          Services.obs.notifyObservers(null, "gather-telemetry");
        });
        break;

      case "application-background":
        if (AppConstants.platform !== "android") {
          break;
        }
        // On Android, we can get killed without warning once we are in the background,
        // but we may also submit data and/or come back into the foreground without getting
        // killed. To deal with this, we save the current session data to file when we are
        // put into the background. This handles the following post-backgrounding scenarios:
        // 1) We are killed immediately. In this case the current session data (which we
        //    save to a file) will be loaded and submitted on a future run.
        // 2) We submit the data while in the background, and then are killed. In this case
        //    the file that we saved will be deleted by the usual process in
        //    finishPingRequest after it is submitted.
        // 3) We submit the data, and then come back into the foreground. Same as case (2).
        // 4) We do not submit the data, but come back into the foreground. In this case
        //    we have the option of either deleting the file that we saved (since we will either
        //    send the live data while in the foreground, or create the file again on the next
        //    backgrounding), or not (in which case we will delete it on submit, or overwrite
        //    it on the next backgrounding). Not deleting it is faster, so that's what we do.
        let payload = this.getSessionPayload(REASON_SAVED_SESSION, false);
        let options = {
          addClientId: true,
          addEnvironment: true,
          overwrite: true,
        };
        lazy.TelemetryController.addPendingPing(
          getPingType(payload),
          payload,
          options
        );
        break;
      case "user-interaction-active":
        this._onActiveTick(true);
        break;
      case "user-interaction-inactive":
        this._onActiveTick(false);
        break;
    }
    return undefined;
  },

  /**
   * This tells TelemetrySession to uninitialize and save any pending pings.
   */
  shutdownChromeProcess() {
    this._log.trace("shutdownChromeProcess");

    let cleanup = () => {
      if (IS_UNIFIED_TELEMETRY) {
        lazy.TelemetryEnvironment.unregisterChangeListener(
          ENVIRONMENT_CHANGE_LISTENER
        );
        lazy.TelemetryScheduler.shutdown();
      }
      this.uninstall();

      let reset = () => {
        this._initStarted = false;
        this._initialized = false;
      };

      return (async () => {
        await this.saveShutdownPings();

        if (IS_UNIFIED_TELEMETRY) {
          await lazy.TelemetryController.removeAbortedSessionPing();
        }

        reset();
      })();
    };

    // We can be in one the following states here:
    // 1) delayedInit was never called
    // or it was called and
    //   2) _delayedInitTask is running now.
    //   3) _delayedInitTask finished running already.

    // This handles 1).
    if (!this._initStarted) {
      return Promise.resolve();
    }

    // This handles 3).
    if (!this._delayedInitTask) {
      // We already ran the delayed initialization.
      return cleanup();
    }

    // This handles 2).
    return this._delayedInitTask.then(cleanup);
  },

  /**
   * Gather and send a daily ping.
   * @return {Promise} Resolved when the ping is sent.
   */
  _sendDailyPing() {
    this._log.trace("_sendDailyPing");
    let payload = this.getSessionPayload(REASON_DAILY, true);

    let options = {
      addClientId: true,
      addEnvironment: true,
    };

    let promise = lazy.TelemetryController.submitExternalPing(
      getPingType(payload),
      payload,
      options
    );

    // Also save the payload as an aborted session. If we delay this, aborted-session can
    // lag behind for the profileSubsessionCounter and other state, complicating analysis.
    if (IS_UNIFIED_TELEMETRY) {
      this._saveAbortedSessionPing(payload).catch(e =>
        this._log.error(
          "_sendDailyPing - Failed to save the aborted session ping",
          e
        )
      );
    }

    return promise;
  },

  /** Loads session data from the session data file.
   * @return {Promise<object>} A promise which is resolved with an object when
   *                            loading has completed, with null otherwise.
   */
  async _loadSessionData() {
    let data = await lazy.TelemetryStorage.loadSessionData();

    if (!data) {
      return null;
    }

    if (
      !("profileSubsessionCounter" in data) ||
      !(typeof data.profileSubsessionCounter == "number") ||
      !("subsessionId" in data) ||
      !("sessionId" in data)
    ) {
      this._log.error("_loadSessionData - session data is invalid");
      Services.telemetry
        .getHistogramById("TELEMETRY_SESSIONDATA_FAILED_VALIDATION")
        .add(1);
      return null;
    }

    this._previousSessionId = data.sessionId;
    this._previousSubsessionId = data.subsessionId;
    // Add |_subsessionCounter| to the |_profileSubsessionCounter| to account for
    // new subsession while loading still takes place. This will always be exactly
    // 1 - the current subsessions.
    this._profileSubsessionCounter =
      data.profileSubsessionCounter + this._subsessionCounter;
    // If we don't have this flag in the state file, it means that this is an old profile.
    // We don't want to send the "new-profile" ping on new profile, so se this to true.
    this._newProfilePingSent =
      "newProfilePingSent" in data ? data.newProfilePingSent : true;
    return data;
  },

  /**
   * Get the session data object to serialise to disk.
   */
  _getSessionDataObject() {
    return {
      sessionId: this._sessionId,
      subsessionId: this._subsessionId,
      profileSubsessionCounter: this._profileSubsessionCounter,
      newProfilePingSent: this._newProfilePingSent,
    };
  },

  _onEnvironmentChange(reason, oldEnvironment) {
    this._log.trace("_onEnvironmentChange", reason);

    let now = Policy.monotonicNow();
    let timeDelta = now - this._lastEnvironmentChangeDate;
    if (timeDelta <= MIN_SUBSESSION_LENGTH_MS) {
      this._log.trace(
        `_onEnvironmentChange - throttling; last change was ${Math.round(
          timeDelta / 1000
        )}s ago.`
      );
      return;
    }

    this._lastEnvironmentChangeDate = now;
    let payload = this.getSessionPayload(REASON_ENVIRONMENT_CHANGE, true);
    lazy.TelemetryScheduler.rescheduleDailyPing(payload);

    let options = {
      addClientId: true,
      addEnvironment: true,
      overrideEnvironment: oldEnvironment,
    };
    lazy.TelemetryController.submitExternalPing(
      getPingType(payload),
      payload,
      options
    );
  },

  _isClassicReason(reason) {
    const classicReasons = [
      REASON_SAVED_SESSION,
      REASON_GATHER_PAYLOAD,
      REASON_TEST_PING,
    ];
    return classicReasons.includes(reason);
  },

  /**
   * Get an object describing the current state of this module for AsyncShutdown diagnostics.
   */
  _getState() {
    return {
      initialized: this._initialized,
      initStarted: this._initStarted,
      haveDelayedInitTask: !!this._delayedInitTask,
    };
  },

  /**
   * Saves the aborted session ping to disk.
   * @param {Object} [aProvidedPayload=null] A payload object to be used as an aborted
   *                 session ping. The reason of this payload is changed to aborted-session.
   *                 If not provided, a new payload is gathered.
   */
  _saveAbortedSessionPing(aProvidedPayload = null) {
    this._log.trace("_saveAbortedSessionPing");

    let payload = null;
    if (aProvidedPayload) {
      payload = Cu.cloneInto(aProvidedPayload, {});
      // Overwrite the original reason.
      payload.info.reason = REASON_ABORTED_SESSION;
    } else {
      payload = this.getSessionPayload(REASON_ABORTED_SESSION, false);
    }

    return lazy.TelemetryController.saveAbortedSessionPing(payload);
  },

  async markNewProfilePingSent() {
    this._log.trace("markNewProfilePingSent");
    this._newProfilePingSent = true;
    return lazy.TelemetryStorage.saveSessionData(this._getSessionDataObject());
  },
};
PK
!<:G��� modules/TelemetryStartup.sys.mjs/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  TelemetryController: "resource://gre/modules/TelemetryController.sys.mjs",
  TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
});

/**
 * TelemetryStartup is needed to forward the "profile-after-change" notification
 * to TelemetryController.sys.mjs.
 */
export function TelemetryStartup() {}

TelemetryStartup.prototype.QueryInterface = ChromeUtils.generateQI([
  "nsIObserver",
]);
TelemetryStartup.prototype.observe = function (aSubject, aTopic) {
  if (aTopic == "profile-after-change") {
    // In the content process, this is done in ContentProcessSingleton.js.
    lazy.TelemetryController.observe(null, aTopic, null);
  }
  if (aTopic == "profile-after-change") {
    annotateEnvironment();
    lazy.TelemetryEnvironment.registerChangeListener(
      "CrashAnnotator",
      annotateEnvironment
    );
    lazy.TelemetryEnvironment.onInitialized().then(() => annotateEnvironment());
  }
};

function annotateEnvironment() {
  try {
    let env = JSON.stringify(lazy.TelemetryEnvironment.currentEnvironment);
    Services.appinfo.annotateCrashReport("TelemetryEnvironment", env);
  } catch (e) {
    // crash reporting not built or disabled? Ignore errors
  }
}
PK
!<����#modules/TelemetryTimestamps.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This module's purpose is to collect timestamps for important
 * application-specific events.
 *
 * The TelemetryController component attaches the timestamps stored by this module to
 * the telemetry submission, substracting the process lifetime so that the times
 * are relative to process startup. The overall goal is to produce a basic
 * timeline of the startup process.
 */
var timeStamps = {};

export var TelemetryTimestamps = {
  /**
   * Adds a timestamp to the list. The addition of TimeStamps that already have
   * a value stored is ignored.
   *
   * @param name must be a unique, generally "camelCase" descriptor of what the
   *             timestamp represents. e.g.: "delayedStartupStarted"
   * @param value is a timeStamp in milliseconds since the epoch. If omitted,
   *              defaults to Date.now().
   */
  add: function TT_add(name, value) {
    // Default to "now" if not specified
    if (value == null) {
      value = Date.now();
    }

    if (isNaN(value)) {
      throw new Error("Value must be a timestamp");
    }

    // If there's an existing value, just ignore the new value.
    if (timeStamps.hasOwnProperty(name)) {
      return;
    }

    timeStamps[name] = value;
  },

  /**
   * Returns a JS object containing all of the timeStamps as properties (can be
   * easily serialized to JSON). Used by TelemetryController to retrieve the data
   * to attach to the telemetry submission.
   */
  get: function TT_get() {
    // Return a copy of the object.
    return Cu.cloneInto(timeStamps, {});
  },
};
PK
!<˘��$�$modules/TelemetryUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { TelemetryControllerBase } from "resource://gre/modules/TelemetryControllerBase.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
});

const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;

const IS_CONTENT_PROCESS = (function () {
  // We cannot use Services.appinfo here because in telemetry xpcshell tests,
  // appinfo is initially unavailable, and becomes available only later on.
  // eslint-disable-next-line mozilla/use-services
  let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
  return runtime.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
})();

export var TelemetryUtils = {
  /**
   * When telemetry is disabled, identifying information (such as client ID)
   * should be removed. A topic event is emitted with a subject that matches
   * this constant. When this happens, other systems that store identifying
   * information about the client should delete that data. Please ask the
   * Firefox Telemetry Team before relying on this topic.
   *
   * Here is an example of listening for that event:
   *
   *  const { TelemetryUtils } =
   *    ChromeUtils.importESModule("resource://gre/modules/TelemetryUtils.sys.mjs");
   *
   *  class YourClass {
   *    constructor() {
   *      Services.obs.addObserver(this, TelemetryUtils.TELEMETRY_UPLOAD_DISABLED_TOPIC);
   *    }
   *
   *    observe(subject, topic, data) {
   *      if (topic == TelemetryUtils.TELEMETRY_UPLOAD_DISABLED_TOPIC) {
   *        // Telemetry was disabled
   *        // subject and data are both unused
   *      }
   *    }
   *  }
   */
  TELEMETRY_UPLOAD_DISABLED_TOPIC: "telemetry.upload.disabled",

  Preferences: Object.freeze({
    ...TelemetryControllerBase.Preferences,

    // General Preferences
    ArchiveEnabled: "toolkit.telemetry.archive.enabled",
    CachedClientId: "toolkit.telemetry.cachedClientID",
    DisableFuzzingDelay: "toolkit.telemetry.testing.disableFuzzingDelay",
    FirstRun: "toolkit.telemetry.reportingpolicy.firstRun",
    FirstShutdownPingEnabled: "toolkit.telemetry.firstShutdownPing.enabled",
    HealthPingEnabled: "toolkit.telemetry.healthping.enabled",
    IPCBatchTimeout: "toolkit.telemetry.ipcBatchTimeout",
    OverrideOfficialCheck: "toolkit.telemetry.send.overrideOfficialCheck",
    OverrideUpdateChannel: "toolkit.telemetry.overrideUpdateChannel",
    Server: "toolkit.telemetry.server",
    ShutdownPingSender: "toolkit.telemetry.shutdownPingSender.enabled",
    ShutdownPingSenderFirstSession:
      "toolkit.telemetry.shutdownPingSender.enabledFirstSession",
    TelemetryEnabled: "toolkit.telemetry.enabled",
    UntrustedModulesPingFrequency:
      "toolkit.telemetry.untrustedModulesPing.frequency",
    UpdatePing: "toolkit.telemetry.updatePing.enabled",
    NewProfilePingEnabled: "toolkit.telemetry.newProfilePing.enabled",
    NewProfilePingDelay: "toolkit.telemetry.newProfilePing.delay",
    PreviousBuildID: "toolkit.telemetry.previousBuildID",

    // Event Ping Preferences
    EventPingMinimumFrequency: "toolkit.telemetry.eventping.minimumFrequency",
    EventPingMaximumFrequency: "toolkit.telemetry.eventping.maximumFrequency",

    // Data reporting Preferences
    AcceptedPolicyDate: "datareporting.policy.dataSubmissionPolicyNotifiedTime",
    AcceptedPolicyVersion:
      "datareporting.policy.dataSubmissionPolicyAcceptedVersion",
    BypassNotification:
      "datareporting.policy.dataSubmissionPolicyBypassNotification",
    CurrentPolicyVersion: "datareporting.policy.currentPolicyVersion",
    DataSubmissionEnabled: "datareporting.policy.dataSubmissionEnabled",
    FhrUploadEnabled: "datareporting.healthreport.uploadEnabled",
    MinimumPolicyVersion: "datareporting.policy.minimumPolicyVersion",
    FirstRunURL: "datareporting.policy.firstRunURL",
  }),

  /**
   * A fixed valid client ID used when Telemetry upload is disabled.
   */
  get knownClientID() {
    return "c0ffeec0-ffee-c0ff-eec0-ffeec0ffeec0";
  },

  /**
   * A fixed valid profile group ID used when Telemetry upload is disabled.
   */
  get knownProfileGroupID() {
    return "decafdec-afde-cafd-ecaf-decafdecafde";
  },

  /**
   * True if this is a content process.
   */
  get isContentProcess() {
    return IS_CONTENT_PROCESS;
  },

  /**
   * Turn a millisecond timestamp into a day timestamp.
   *
   * @param aMsec A number of milliseconds since Unix epoch.
   * @return The number of whole days since Unix epoch.
   */
  millisecondsToDays(aMsec) {
    return Math.floor(aMsec / MILLISECONDS_PER_DAY);
  },

  /**
   * Takes a date and returns it truncated to a date with daily precision.
   */
  truncateToDays(date) {
    return new Date(
      date.getFullYear(),
      date.getMonth(),
      date.getDate(),
      0,
      0,
      0,
      0
    );
  },

  /**
   * Takes a date and returns it truncated to a date with hourly precision.
   */
  truncateToHours(date) {
    return new Date(
      date.getFullYear(),
      date.getMonth(),
      date.getDate(),
      date.getHours(),
      0,
      0,
      0
    );
  },

  /**
   * Check if the difference between the times is within the provided tolerance.
   * @param {Number} t1 A time in milliseconds.
   * @param {Number} t2 A time in milliseconds.
   * @param {Number} tolerance The tolerance, in milliseconds.
   * @return {Boolean} True if the absolute time difference is within the tolerance, false
   *                   otherwise.
   */
  areTimesClose(t1, t2, tolerance) {
    return Math.abs(t1 - t2) <= tolerance;
  },

  /**
   * Get the next midnight for a date.
   * @param {Object} date The date object to check.
   * @return {Object} The Date object representing the next midnight.
   */
  getNextMidnight(date) {
    let nextMidnight = new Date(this.truncateToDays(date));
    nextMidnight.setDate(nextMidnight.getDate() + 1);
    return nextMidnight;
  },

  /**
   * Get the midnight which is closer to the provided date.
   * @param {Object} date The date object to check.
   * @param {Number} tolerance The tolerance within we find the closest midnight.
   * @return {Object} The Date object representing the closes midnight, or null if midnight
   *                  is not within the midnight tolerance.
   */
  getNearestMidnight(date, tolerance) {
    let lastMidnight = this.truncateToDays(date);
    if (this.areTimesClose(date.getTime(), lastMidnight.getTime(), tolerance)) {
      return lastMidnight;
    }

    const nextMidnightDate = this.getNextMidnight(date);
    if (
      this.areTimesClose(date.getTime(), nextMidnightDate.getTime(), tolerance)
    ) {
      return nextMidnightDate;
    }
    return null;
  },

  generateUUID() {
    let str = Services.uuid.generateUUID().toString();
    // strip {}
    return str.substring(1, str.length - 1);
  },

  /**
   * Find how many months passed between two dates.
   * @param {Object} aStartDate The starting date.
   * @param {Object} aEndDate The ending date.
   * @return {Integer} The number of months between the two dates.
   */
  getElapsedTimeInMonths(aStartDate, aEndDate) {
    return (
      aEndDate.getMonth() -
      aStartDate.getMonth() +
      12 * (aEndDate.getFullYear() - aStartDate.getFullYear())
    );
  },

  /**
   * Date.toISOString() gives us UTC times, this gives us local times in
   * the ISO date format. See http://www.w3.org/TR/NOTE-datetime
   * @param {Object} date The input date.
   * @return {String} The local time ISO string.
   */
  toLocalTimeISOString(date) {
    function padNumber(number, length) {
      return number.toString().padStart(length, "0");
    }

    let sign = n => (n >= 0 ? "+" : "-");
    // getTimezoneOffset counter-intuitively returns -60 for UTC+1.
    let tzOffset = -date.getTimezoneOffset();

    // YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
    return (
      padNumber(date.getFullYear(), 4) +
      "-" +
      padNumber(date.getMonth() + 1, 2) +
      "-" +
      padNumber(date.getDate(), 2) +
      "T" +
      padNumber(date.getHours(), 2) +
      ":" +
      padNumber(date.getMinutes(), 2) +
      ":" +
      padNumber(date.getSeconds(), 2) +
      "." +
      date.getMilliseconds() +
      sign(tzOffset) +
      padNumber(Math.floor(Math.abs(tzOffset / 60)), 2) +
      ":" +
      padNumber(Math.abs(tzOffset % 60), 2)
    );
  },

  /**
   * @returns {number} The monotonic time since the process start
   * or (non-monotonic) Date value if this fails back.
   */
  monotonicNow() {
    return Services.telemetry.msSinceProcessStart();
  },

  /**
   * @returns {string} The name of the update channel to report
   * in telemetry.
   * By default, this is the same as the name of the channel that
   * the browser uses to download its updates. However in certain
   * situations, a single update channel provides multiple (distinct)
   * build types, that need to be distinguishable on Telemetry.
   */
  getUpdateChannel() {
    let overrideChannel = Services.prefs.getCharPref(
      this.Preferences.OverrideUpdateChannel,
      undefined
    );
    if (overrideChannel) {
      return overrideChannel;
    }

    return lazy.UpdateUtils.getUpdateChannel(false);
  },
};
PK
!<�]����modules/Timer.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * JS module implementation of setTimeout and clearTimeout.
 */

// This gives us >=2^30 unique timer IDs, enough for 1 per ms for 12.4 days.
var gNextId = 1; // setTimeout and setInterval must return a positive integer

var gTimerTable = new Map(); // int -> nsITimer or idleCallback

// Don't generate this for every timer.
var setTimeout_timerCallbackQI = ChromeUtils.generateQI([
  "nsITimerCallback",
  "nsINamed",
]);

function _setTimeoutOrIsInterval(
  aCallback,
  aMilliseconds,
  aIsInterval,
  aTarget,
  aArgs
) {
  if (typeof aCallback !== "function") {
    throw new Error(
      `callback is not a function in ${
        aIsInterval ? "setInterval" : "setTimeout"
      }`
    );
  }
  let id = gNextId++;
  let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);

  if (aTarget) {
    timer.target = aTarget;
  }

  let callback = {
    QueryInterface: setTimeout_timerCallbackQI,

    // nsITimerCallback
    notify() {
      if (!aIsInterval) {
        gTimerTable.delete(id);
      }
      aCallback.apply(null, aArgs);
    },

    // nsINamed
    get name() {
      return `${
        aIsInterval ? "setInterval" : "setTimeout"
      }() for ${Cu.getDebugName(aCallback)}`;
    },
  };

  timer.initWithCallback(
    callback,
    aMilliseconds,
    aIsInterval ? timer.TYPE_REPEATING_SLACK : timer.TYPE_ONE_SHOT
  );

  gTimerTable.set(id, timer);
  return id;
}

export function setTimeout(aCallback, aMilliseconds, ...aArgs) {
  return _setTimeoutOrIsInterval(aCallback, aMilliseconds, false, null, aArgs);
}

export function setTimeoutWithTarget(
  aCallback,
  aMilliseconds,
  aTarget,
  ...aArgs
) {
  return _setTimeoutOrIsInterval(
    aCallback,
    aMilliseconds,
    false,
    aTarget,
    aArgs
  );
}

export function setInterval(aCallback, aMilliseconds, ...aArgs) {
  return _setTimeoutOrIsInterval(aCallback, aMilliseconds, true, null, aArgs);
}

export function setIntervalWithTarget(
  aCallback,
  aMilliseconds,
  aTarget,
  ...aArgs
) {
  return _setTimeoutOrIsInterval(
    aCallback,
    aMilliseconds,
    true,
    aTarget,
    aArgs
  );
}

function clear(aId) {
  if (gTimerTable.has(aId)) {
    gTimerTable.get(aId).cancel();
    gTimerTable.delete(aId);
  }
}
export var clearInterval = clear;
export var clearTimeout = clear;

export function requestIdleCallback(aCallback, aOptions) {
  if (typeof aCallback !== "function") {
    throw new Error("callback is not a function in requestIdleCallback");
  }
  let id = gNextId++;

  let callback = (...aArgs) => {
    if (gTimerTable.has(id)) {
      gTimerTable.delete(id);
      aCallback(...aArgs);
    }
  };

  ChromeUtils.idleDispatch(callback, aOptions);
  gTimerTable.set(id, callback);
  return id;
}

export function cancelIdleCallback(aId) {
  if (gTimerTable.has(aId)) {
    gTimerTable.delete(aId);
  }
}
PK
!<Z�pp#modules/TooltipTextProvider.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export function TooltipTextProvider() {}

function getFileInputTitleText(tipElement) {
  let files = tipElement.files;
  let bundle = Services.strings.createBundle(
    "chrome://global/locale/layout/HtmlForm.properties"
  );
  if (!files.length) {
    return bundle.GetStringFromName(
      tipElement.multiple ? "NoFilesSelected" : "NoFileSelected"
    );
  }
  let titleText = files[0].name;
  // For UX and performance (jank) reasons we cap the number of
  // files that we list in the tooltip to 20 plus a "and xxx more"
  // line, or to 21 if exactly 21 files were picked.
  const TRUNCATED_FILE_COUNT = 20;
  let count = Math.min(files.length, TRUNCATED_FILE_COUNT);
  for (let i = 1; i < count; ++i) {
    titleText += "\n" + files[i].name;
  }
  if (files.length == TRUNCATED_FILE_COUNT + 1) {
    titleText += "\n" + files[TRUNCATED_FILE_COUNT].name;
  } else if (files.length > TRUNCATED_FILE_COUNT + 1) {
    const l10n = new Localization(["toolkit/global/htmlForm.ftl"], true);
    const andXMoreStr = l10n.formatValueSync("input-file-and-more-files", {
      fileCount: files.length - TRUNCATED_FILE_COUNT,
    });
    titleText += "\n" + andXMoreStr;
  }
  return titleText;
}

TooltipTextProvider.prototype = {
  getNodeText(tipElement, textOut, directionOut) {
    // Don't show the tooltip if the tooltip node is a document or browser.
    // Caller should ensure the node is in (composed) document.
    if (
      !tipElement ||
      !tipElement.ownerDocument ||
      tipElement.localName == "browser"
    ) {
      return false;
    }

    var defView = tipElement.ownerGlobal;
    // XXX Work around bug 350679:
    // "Tooltips can be fired in documents with no view".
    if (!defView) {
      return false;
    }

    const XLinkNS = "http://www.w3.org/1999/xlink";
    const XUL_NS =
      "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

    var titleText = null;
    var XLinkTitleText = null;
    var lookingForSVGTitle = true;
    var direction = tipElement.ownerDocument.dir;

    for (; tipElement; tipElement = tipElement.flattenedTreeParentNode) {
      if (tipElement.nodeType != defView.Node.ELEMENT_NODE) {
        continue;
      }
      if (tipElement.namespaceURI == XUL_NS) {
        lookingForSVGTitle = false;
        titleText = tipElement.getAttribute("tooltiptext");
      } else if (!defView.SVGElement.isInstance(tipElement)) {
        lookingForSVGTitle = false;
        titleText = tipElement.getAttribute("title");
      }

      if (
        (defView.HTMLAnchorElement.isInstance(tipElement) ||
          defView.HTMLAreaElement.isInstance(tipElement) ||
          defView.HTMLLinkElement.isInstance(tipElement) ||
          defView.SVGAElement.isInstance(tipElement)) &&
        tipElement.href
      ) {
        XLinkTitleText = tipElement.getAttributeNS(XLinkNS, "title");
      }

      // If the element is invalid per HTML5 Forms specifications and has no title,
      // show the constraint validation error message.
      if (
        titleText == null &&
        (defView.HTMLInputElement.isInstance(tipElement) ||
          defView.HTMLTextAreaElement.isInstance(tipElement) ||
          defView.HTMLSelectElement.isInstance(tipElement) ||
          defView.HTMLButtonElement.isInstance(tipElement)) &&
        !tipElement.form?.noValidate
      ) {
        // If the element is barred from constraint validation or valid,
        // the validation message will be the empty string.
        titleText = tipElement.validationMessage || null;
      }

      // If the element is an <input type='file'> without a title, we should show
      // the current file selection.
      if (
        titleText == null &&
        defView.HTMLInputElement.isInstance(tipElement) &&
        tipElement.type == "file"
      ) {
        try {
          titleText = getFileInputTitleText(tipElement);
        } catch (ex) {}
      }

      if (
        lookingForSVGTitle &&
        tipElement.parentNode.nodeType != defView.Node.DOCUMENT_NODE
      ) {
        for (let childNode of tipElement.childNodes) {
          if (defView.SVGTitleElement.isInstance(childNode)) {
            titleText = childNode.textContent;
            break;
          }
        }
      }

      // Check texts against null so that title="" can be used to undefine a
      // title on a child element.
      if (titleText != null || XLinkTitleText != null) {
        break;
      }
    }

    return [titleText, XLinkTitleText].some(function (t) {
      if (t && /\S/.test(t)) {
        // Make CRLF and CR render one line break each.
        textOut.value = t.replace(/\r\n?/g, "\n");

        if (tipElement) {
          direction = defView
            .getComputedStyle(tipElement)
            .getPropertyValue("direction");
        }

        directionOut.value = direction;
        return true;
      }

      return false;
    });
  },

  classID: Components.ID("{f376627f-0bbc-47b8-887e-fc92574cc91f}"),
  QueryInterface: ChromeUtils.generateQI(["nsITooltipTextProvider"]),
};
PK
!<�o�y*�*�modules/Troubleshoot.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AddonManager } from "resource://gre/modules/AddonManager.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PlacesDBUtils: "resource://gre/modules/PlacesDBUtils.sys.mjs",
});

// We use a list of prefs for display to make sure we only show prefs that
// are useful for support and won't compromise the user's privacy.  Note that
// entries are *prefixes*: for example, "accessibility." applies to all prefs
// under the "accessibility.*" branch.
const PREFS_FOR_DISPLAY = [
  "accessibility.",
  "apz.",
  "browser.cache.",
  "browser.contentblocking.category",
  "browser.contentanalysis.",
  "browser.display.",
  "browser.download.always_ask_before_handling_new_types",
  "browser.download.enable_spam_prevention",
  "browser.download.folderList",
  "browser.download.improvements_to_download_panel",
  "browser.download.lastDir.savePerSite",
  "browser.download.manager.addToRecentDocs",
  "browser.download.manager.resumeOnWakeDelay",
  "browser.download.open_pdf_attachments_inline",
  "browser.download.preferred.",
  "browser.download.skipConfirmLaunchExecutable",
  "browser.download.start_downloads_in_tmp_dir",
  "browser.download.useDownloadDir",
  "browser.fixup.",
  "browser.history_expire_",
  "browser.link.open_newwindow",
  "browser.places.",
  "browser.privatebrowsing.",
  "browser.search.context.loadInBackground",
  "browser.search.log",
  "browser.search.openintab",
  "browser.search.param",
  "browser.search.region",
  "browser.search.searchEnginesURL",
  "browser.search.suggest.enabled",
  "browser.search.update",
  "browser.sessionstore.",
  "browser.startup.homepage",
  "browser.startup.page",
  "browser.tabs.",
  "browser.toolbars.",
  "browser.urlbar.",
  "browser.zoom.",
  "doh-rollout.",
  "dom.",
  "extensions.checkCompatibility",
  "extensions.eventPages.enabled",
  "extensions.formautofill.",
  "extensions.lastAppVersion",
  "extensions.manifestV3.enabled",
  "extensions.quarantinedDomains.enabled",
  "extensions.InstallTrigger.enabled",
  "extensions.InstallTriggerImpl.enabled",
  "fission.autostart",
  "font.",
  "general.autoScroll",
  "general.useragent.",
  "gfx.",
  "html5.",
  "identity.fxaccounts.enabled",
  "idle.",
  "image.",
  "javascript.",
  "keyword.",
  "layers.",
  "layout.css.dpi",
  "layout.display-list.",
  "layout.frame_rate",
  "media.",
  "mousewheel.",
  "network.",
  "permissions.default.image",
  "places.",
  "plugin.",
  "plugins.",
  "privacy.",
  "security.",
  "services.sync.declinedEngines",
  "services.sync.lastPing",
  "services.sync.lastSync",
  "services.sync.numClients",
  "services.sync.engine.",
  "signon.",
  "storage.vacuum.last.",
  "svg.",
  "toolkit.startup.recent_crashes",
  "ui.osk.enabled",
  "ui.osk.detect_physical_keyboard",
  "ui.osk.require_tablet_mode",
  "ui.osk.debug.keyboardDisplayReason",
  "webgl.",
  "widget.dmabuf",
  "widget.use-xdg-desktop-portal",
  "widget.use-xdg-desktop-portal.file-picker",
  "widget.use-xdg-desktop-portal.mime-handler",
  "widget.gtk.overlay-scrollbars.enabled",
  "widget.wayland",
];

// The list of prefs we don't display, unlike the list of prefs for display,
// is a list of regular expressions.
const PREF_REGEXES_NOT_TO_DISPLAY = [
  /^browser[.]fixup[.]domainwhitelist[.]/,
  /^dom[.]push[.]userAgentID/,
  /^media[.]webrtc[.]debug[.]aec_log_dir/,
  /^media[.]webrtc[.]debug[.]log_file/,
  /^print[.].*print_to_filename$/,
  /^network[.]proxy[.]/,
];

// Table of getters for various preference types.
const PREFS_GETTERS = {};

PREFS_GETTERS[Ci.nsIPrefBranch.PREF_STRING] = (prefs, name) =>
  prefs.getStringPref(name);
PREFS_GETTERS[Ci.nsIPrefBranch.PREF_INT] = (prefs, name) =>
  prefs.getIntPref(name);
PREFS_GETTERS[Ci.nsIPrefBranch.PREF_BOOL] = (prefs, name) =>
  prefs.getBoolPref(name);

// List of unimportant locked prefs (won't be shown on the troubleshooting
// session)
const PREFS_UNIMPORTANT_LOCKED = [
  "dom.postMessage.sharedArrayBuffer.bypassCOOP_COEP.insecure.enabled",
  "extensions.backgroundServiceWorkerEnabled.enabled",
  "privacy.restrict3rdpartystorage.url_decorations",
];

function getPref(name) {
  let type = Services.prefs.getPrefType(name);
  if (!(type in PREFS_GETTERS)) {
    throw new Error("Unknown preference type " + type + " for " + name);
  }
  return PREFS_GETTERS[type](Services.prefs, name);
}

// Return the preferences filtered by PREF_REGEXES_NOT_TO_DISPLAY and PREFS_FOR_DISPLAY
// and also by the custom 'filter'-ing function.
function getPrefList(filter, allowlist = PREFS_FOR_DISPLAY) {
  return allowlist.reduce(function (prefs, branch) {
    Services.prefs.getChildList(branch).forEach(function (name) {
      if (
        filter(name) &&
        !PREF_REGEXES_NOT_TO_DISPLAY.some(re => re.test(name))
      ) {
        prefs[name] = getPref(name);
      }
    });
    return prefs;
  }, {});
}

export var Troubleshoot = {
  /**
   * Captures a snapshot of data that may help troubleshooters troubleshoot
   * trouble.
   *
   * @returns {Promise}
   *   A promise that is resolved with the snapshot data.
   */
  snapshot() {
    return new Promise(resolve => {
      let snapshot = {};
      let numPending = Object.keys(dataProviders).length;
      function providerDone(providerName, providerData) {
        snapshot[providerName] = providerData;
        if (--numPending == 0) {
          // Ensure that done is always and truly called asynchronously.
          Services.tm.dispatchToMainThread(() => resolve(snapshot));
        }
      }
      for (let name in dataProviders) {
        try {
          dataProviders[name](providerDone.bind(null, name));
        } catch (err) {
          let msg = "Troubleshoot data provider failed: " + name + "\n" + err;
          console.error(msg);
          providerDone(name, msg);
        }
      }
    });
  },

  kMaxCrashAge: 3 * 24 * 60 * 60 * 1000, // 3 days
};

// Each data provider is a name => function mapping.  When a snapshot is
// captured, each provider's function is called, and it's the function's job to
// generate the provider's data.  The function is passed a "done" callback, and
// when done, it must pass its data to the callback.  The resulting snapshot
// object will contain a name => data entry for each provider.
var dataProviders = {
  application: async function application(done) {
    let data = {
      name: Services.appinfo.name,
      osVersion:
        Services.sysinfo.getProperty("name") +
        " " +
        Services.sysinfo.getProperty("version") +
        " " +
        Services.sysinfo.getProperty("build"),
      version: AppConstants.MOZ_APP_VERSION_DISPLAY,
      buildID: Services.appinfo.appBuildID,
      distributionID: Services.prefs
        .getDefaultBranch("")
        .getCharPref("distribution.id", ""),
      userAgent: Cc["@mozilla.org/network/protocol;1?name=http"].getService(
        Ci.nsIHttpProtocolHandler
      ).userAgent,
      safeMode: Services.appinfo.inSafeMode,
      memorySizeBytes: Services.sysinfo.getProperty("memsize"),
      diskAvailableBytes: Services.dirsvc.get("ProfD", Ci.nsIFile)
        .diskSpaceAvailable,
    };

    if (Services.sysinfo.getProperty("name") == "Windows_NT") {
      if ((await Services.sysinfo.processInfo).isWindowsSMode) {
        data.osVersion += " S";
      }
    }

    if (AppConstants.MOZ_UPDATER) {
      data.updateChannel = ChromeUtils.importESModule(
        "resource://gre/modules/UpdateUtils.sys.mjs"
      ).UpdateUtils.UpdateChannel;
    }

    // eslint-disable-next-line mozilla/use-default-preference-values
    try {
      data.vendor = Services.prefs.getCharPref("app.support.vendor");
    } catch (e) {}
    try {
      data.supportURL = Services.urlFormatter.formatURLPref(
        "app.support.baseURL"
      );
    } catch (e) {}

    data.osTheme = Services.sysinfo.getProperty("osThemeInfo");

    try {
      // MacOSX: Check for rosetta status, if it exists
      data.rosetta = Services.sysinfo.getProperty("rosettaStatus");
    } catch (e) {}

    try {
      // Windows - Get info about attached pointing devices
      data.pointingDevices = Services.sysinfo
        .getProperty("pointingDevices")
        .split(",");
    } catch (e) {}

    data.numTotalWindows = 0;
    data.numFissionWindows = 0;
    data.numRemoteWindows = 0;
    for (let { docShell } of Services.wm.getEnumerator(
      AppConstants.platform == "android"
        ? "navigator:geckoview"
        : "navigator:browser"
    )) {
      docShell.QueryInterface(Ci.nsILoadContext);
      data.numTotalWindows++;
      if (docShell.useRemoteSubframes) {
        data.numFissionWindows++;
      }
      if (docShell.useRemoteTabs) {
        data.numRemoteWindows++;
      }
    }

    try {
      data.launcherProcessState = Services.appinfo.launcherProcessState;
    } catch (e) {}

    data.fissionAutoStart = Services.appinfo.fissionAutostart;
    data.fissionDecisionStatus = Services.appinfo.fissionDecisionStatusString;

    data.remoteAutoStart = Services.appinfo.browserTabsRemoteAutostart;

    if (Services.policies) {
      data.policiesStatus = Services.policies.status;
    }

    const keyLocationServiceGoogle = Services.urlFormatter
      .formatURL("%GOOGLE_LOCATION_SERVICE_API_KEY%")
      .trim();
    data.keyLocationServiceGoogleFound =
      keyLocationServiceGoogle != "no-google-location-service-api-key" &&
      !!keyLocationServiceGoogle.length;

    const keySafebrowsingGoogle = Services.urlFormatter
      .formatURL("%GOOGLE_SAFEBROWSING_API_KEY%")
      .trim();
    data.keySafebrowsingGoogleFound =
      keySafebrowsingGoogle != "no-google-safebrowsing-api-key" &&
      !!keySafebrowsingGoogle.length;

    const keyMozilla = Services.urlFormatter
      .formatURL("%MOZILLA_API_KEY%")
      .trim();
    data.keyMozillaFound =
      keyMozilla != "no-mozilla-api-key" && !!keyMozilla.length;

    done(data);
  },

  addons: async function addons(done) {
    let addons = await AddonManager.getAddonsByTypes([
      "extension",
      "locale",
      "dictionary",
      "sitepermission",
      "theme",
    ]);
    addons = addons.filter(e => !e.isSystem);
    addons.sort(function (a, b) {
      if (a.isActive != b.isActive) {
        return b.isActive ? 1 : -1;
      }

      if (a.type != b.type) {
        return a.type.localeCompare(b.type);
      }

      // In some unfortunate cases add-on names can be null.
      let aname = a.name || "";
      let bname = b.name || "";
      let lc = aname.localeCompare(bname);
      if (lc != 0) {
        return lc;
      }
      if (a.version != b.version) {
        return a.version > b.version ? 1 : -1;
      }
      return 0;
    });
    let props = ["name", "type", "version", "isActive", "id"];
    done(
      addons.map(function (ext) {
        return props.reduce(function (extData, prop) {
          extData[prop] = ext[prop];
          return extData;
        }, {});
      })
    );
  },

  securitySoftware: function securitySoftware(done) {
    let data = {};

    const keys = [
      "registeredAntiVirus",
      "registeredAntiSpyware",
      "registeredFirewall",
    ];
    for (let key of keys) {
      let prop = "";
      try {
        prop = Services.sysinfo.getProperty(key);
      } catch (e) {}

      data[key] = prop;
    }

    done(data);
  },

  features: async function features(done) {
    let features = await AddonManager.getAddonsByTypes(["extension"]);
    features = features.filter(f => f.isSystem);
    features.sort(function (a, b) {
      // In some unfortunate cases addon names can be null.
      let aname = a.name || null;
      let bname = b.name || null;
      let lc = aname.localeCompare(bname);
      if (lc != 0) {
        return lc;
      }
      if (a.version != b.version) {
        return a.version > b.version ? 1 : -1;
      }
      return 0;
    });
    let props = ["name", "version", "id"];
    done(
      features.map(function (f) {
        return props.reduce(function (fData, prop) {
          fData[prop] = f[prop];
          return fData;
        }, {});
      })
    );
  },

  processes: async function processes(done) {
    let remoteTypes = {};
    const processInfo = await ChromeUtils.requestProcInfo();
    for (let i = 0; i < processInfo.children.length; i++) {
      let remoteType;
      try {
        remoteType = processInfo.children[i].type;
        // Workaround for bug 1790070, since requestProcInfo refers to the preallocated content
        // process as "preallocated", and the localization string mapping expects "prealloc".
        remoteType = remoteType === "preallocated" ? "prealloc" : remoteType;
      } catch (e) {}

      // We will split Utility by actor name, so do not do it now
      if (remoteType === "utility") {
        continue;
      }

      // The parent process is also managed by the ppmm (because
      // of non-remote tabs), but it doesn't have a remoteType.
      if (!remoteType) {
        continue;
      }

      if (remoteTypes[remoteType]) {
        remoteTypes[remoteType]++;
      } else {
        remoteTypes[remoteType] = 1;
      }
    }

    for (let i = 0; i < processInfo.children.length; i++) {
      if (processInfo.children[i].type === "utility") {
        for (let utilityWithActor of processInfo.children[i].utilityActors.map(
          e => `utility_${e.actorName}`
        )) {
          if (remoteTypes[utilityWithActor]) {
            remoteTypes[utilityWithActor]++;
          } else {
            remoteTypes[utilityWithActor] = 1;
          }
        }
      }
    }

    try {
      let winUtils = Services.wm.getMostRecentWindow("").windowUtils;
      if (winUtils.gpuProcessPid != -1) {
        remoteTypes.gpu = 1;
      }
    } catch (e) {}

    if (Services.io.socketProcessLaunched) {
      remoteTypes.socket = 1;
    }

    let data = {
      remoteTypes,
      maxWebContentProcesses: Services.appinfo.maxWebProcessCount,
    };

    done(data);
  },

  async experimentalFeatures(done) {
    if (AppConstants.MOZ_BUILD_APP != "browser") {
      done();
      return;
    }
    let { FeatureGate } = ChromeUtils.importESModule(
      "resource://featuregates/FeatureGate.sys.mjs"
    );

    let gates = await FeatureGate.all();
    done(
      gates.map(gate => {
        return [
          gate.title,
          gate.preference,
          Services.prefs.getBoolPref(gate.preference),
        ];
      })
    );
  },

  async legacyUserStylesheets(done) {
    if (AppConstants.platform == "android") {
      done({ active: false, types: [] });
      return;
    }

    let active = Services.prefs.getBoolPref(
      "toolkit.legacyUserProfileCustomizations.stylesheets"
    );
    let types = [];
    for (let name of ["userChrome.css", "userContent.css"]) {
      let path = PathUtils.join(PathUtils.profileDir, "chrome", name);
      if (await IOUtils.exists(path)) {
        types.push(name);
      }
    }
    done({ active, types });
  },

  async environmentVariables(done) {
    let Subprocess;
    try {
      // Subprocess is not available in all builds
      Subprocess = ChromeUtils.importESModule(
        "resource://gre/modules/Subprocess.sys.mjs"
      ).Subprocess;
    } catch (ex) {
      done({});
      return;
    }

    let environment = Subprocess.getEnvironment();
    let filteredEnvironment = {};
    // Limit the environment variables to those that we
    // know may affect Firefox to reduce leaking PII.
    let filteredEnvironmentKeys = ["xre_", "moz_", "gdk", "display"];
    for (let key of Object.keys(environment)) {
      if (filteredEnvironmentKeys.some(k => key.toLowerCase().startsWith(k))) {
        filteredEnvironment[key] = environment[key];
      }
    }
    done(filteredEnvironment);
  },

  modifiedPreferences: function modifiedPreferences(done) {
    done(getPrefList(name => Services.prefs.prefHasUserValue(name)));
  },

  lockedPreferences: function lockedPreferences(done) {
    done(
      getPrefList(
        name =>
          !PREFS_UNIMPORTANT_LOCKED.includes(name) &&
          Services.prefs.prefIsLocked(name)
      )
    );
  },

  places: async function places(done) {
    const data = AppConstants.MOZ_PLACES
      ? await lazy.PlacesDBUtils.getEntitiesStatsAndCounts()
      : [];
    done(data);
  },

  printingPreferences: function printingPreferences(done) {
    let filter = name => Services.prefs.prefHasUserValue(name);
    let prefs = getPrefList(filter, ["print."]);

    // print_printer is special and is the only pref that is outside of the
    // "print." branch... Maybe we should change it to print.printer or
    // something...
    if (filter("print_printer")) {
      prefs.print_printer = getPref("print_printer");
    }

    done(prefs);
  },

  graphics: function graphics(done) {
    function statusMsgForFeature(feature) {
      // We return an object because in the try-newer-driver case we need to
      // include the suggested version, which the consumer likely needs to plug
      // into a format string from a localization file. Rather than returning
      // a string in some cases and an object in others, return an object always.
      let msg = { key: "" };
      try {
        var status = gfxInfo.getFeatureStatus(feature);
      } catch (e) {}
      switch (status) {
        case Ci.nsIGfxInfo.FEATURE_BLOCKED_DEVICE:
        case Ci.nsIGfxInfo.FEATURE_DISCOURAGED:
          msg = { key: "blocked-gfx-card" };
          break;
        case Ci.nsIGfxInfo.FEATURE_BLOCKED_OS_VERSION:
          msg = { key: "blocked-os-version" };
          break;
        case Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION:
          try {
            var driverVersion =
              gfxInfo.getFeatureSuggestedDriverVersion(feature);
          } catch (e) {}
          msg = driverVersion
            ? { key: "try-newer-driver", args: { driverVersion } }
            : { key: "blocked-driver" };
          break;
        case Ci.nsIGfxInfo.FEATURE_BLOCKED_MISMATCHED_VERSION:
          msg = { key: "blocked-mismatched-version" };
          break;
      }
      return msg;
    }

    let data = {};

    try {
      // nsIGfxInfo may not be implemented on some platforms.
      var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
    } catch (e) {}

    data.desktopEnvironment = Services.appinfo.desktopEnvironment;
    data.numTotalWindows = 0;
    data.numAcceleratedWindows = 0;

    let devicePixelRatios = [];

    for (let win of Services.ww.getWindowEnumerator()) {
      let winUtils = win.windowUtils;
      try {
        // NOTE: windowless browser's windows should not be reported in the graphics troubleshoot report
        if (
          winUtils.layerManagerType == "None" ||
          !winUtils.layerManagerRemote
        ) {
          continue;
        }
        devicePixelRatios.push(win.devicePixelRatio);

        data.numTotalWindows++;
        data.windowLayerManagerType = winUtils.layerManagerType;
        data.windowLayerManagerRemote = winUtils.layerManagerRemote;
      } catch (e) {
        continue;
      }
      if (data.windowLayerManagerType != "Basic") {
        data.numAcceleratedWindows++;
      }
    }
    data.graphicsDevicePixelRatios = devicePixelRatios;

    // If we had no OMTC windows, report back Basic Layers.
    if (!data.windowLayerManagerType) {
      data.windowLayerManagerType = "Basic";
      data.windowLayerManagerRemote = false;
    }

    if (!data.numAcceleratedWindows && gfxInfo) {
      let win = AppConstants.platform == "win";
      let feature = win
        ? gfxInfo.FEATURE_DIRECT3D_9_LAYERS
        : gfxInfo.FEATURE_OPENGL_LAYERS;
      data.numAcceleratedWindowsMessage = statusMsgForFeature(feature);
    }

    if (gfxInfo) {
      // keys are the names of attributes on nsIGfxInfo, values become the names
      // of the corresponding properties in our data object.  A null value means
      // no change.  This is needed so that the names of properties in the data
      // object are the same as the names of keys in aboutSupport.properties.
      let gfxInfoProps = {
        adapterDescription: null,
        adapterVendorID: null,
        adapterDeviceID: null,
        adapterSubsysID: null,
        adapterRAM: null,
        adapterDriver: "adapterDrivers",
        adapterDriverVendor: "driverVendor",
        adapterDriverVersion: "driverVersion",
        adapterDriverDate: "driverDate",

        adapterDescription2: null,
        adapterVendorID2: null,
        adapterDeviceID2: null,
        adapterSubsysID2: null,
        adapterRAM2: null,
        adapterDriver2: "adapterDrivers2",
        adapterDriverVendor2: "driverVendor2",
        adapterDriverVersion2: "driverVersion2",
        adapterDriverDate2: "driverDate2",
        isGPU2Active: null,

        D2DEnabled: "direct2DEnabled",
        DWriteEnabled: "directWriteEnabled",
        DWriteVersion: "directWriteVersion",
        cleartypeParameters: "clearTypeParameters",
        TargetFrameRate: "targetFrameRate",
        windowProtocol: null,
        fontVisibilityDeterminationStr: "supportFontDetermination",
      };

      for (let prop in gfxInfoProps) {
        try {
          data[gfxInfoProps[prop] || prop] = gfxInfo[prop];
        } catch (e) {}
      }

      if ("direct2DEnabled" in data && !data.direct2DEnabled) {
        data.direct2DEnabledMessage = statusMsgForFeature(
          Ci.nsIGfxInfo.FEATURE_DIRECT2D
        );
      }
    }

    let doc = new DOMParser().parseFromString("<html/>", "text/html");

    function GetWebGLInfo(data, keyPrefix, contextType) {
      data[keyPrefix + "Renderer"] = "-";
      data[keyPrefix + "Version"] = "-";
      data[keyPrefix + "DriverExtensions"] = "-";
      data[keyPrefix + "Extensions"] = "-";
      data[keyPrefix + "WSIInfo"] = "-";

      // //

      let canvas = doc.createElement("canvas");
      canvas.width = 1;
      canvas.height = 1;

      // //

      let creationError = null;

      canvas.addEventListener(
        "webglcontextcreationerror",

        function (e) {
          creationError = e.statusMessage;
        }
      );

      let gl = null;
      try {
        gl = canvas.getContext(contextType);
      } catch (e) {
        if (!creationError) {
          creationError = e.toString();
        }
      }
      if (!gl) {
        data[keyPrefix + "Renderer"] =
          creationError || "(no creation error info)";
        return;
      }

      // //

      data[keyPrefix + "Extensions"] = gl.getSupportedExtensions().join(" ");

      // //

      let ext = gl.getExtension("MOZ_debug");
      // This extension is unconditionally available to chrome. No need to check.
      let vendor = ext.getParameter(gl.VENDOR);
      let renderer = ext.getParameter(gl.RENDERER);

      data[keyPrefix + "Renderer"] = vendor + " -- " + renderer;
      data[keyPrefix + "Version"] = ext.getParameter(gl.VERSION);
      data[keyPrefix + "DriverExtensions"] = ext.getParameter(ext.EXTENSIONS);
      data[keyPrefix + "WSIInfo"] = ext.getParameter(ext.WSI_INFO);

      // //

      // Eagerly free resources.
      let loseExt = gl.getExtension("WEBGL_lose_context");
      if (loseExt) {
        loseExt.loseContext();
      }
    }

    GetWebGLInfo(data, "webgl1", "webgl");
    GetWebGLInfo(data, "webgl2", "webgl2");

    if (gfxInfo) {
      let infoInfo = gfxInfo.getInfo();
      if (infoInfo) {
        data.info = infoInfo;
      }

      let failureIndices = {};

      let failures = gfxInfo.getFailures(failureIndices);
      if (failures.length) {
        data.failures = failures;
        if (failureIndices.value.length == failures.length) {
          data.indices = failureIndices.value;
        }
      }

      data.featureLog = gfxInfo.getFeatureLog();
      data.crashGuards = gfxInfo.getActiveCrashGuards();
    }

    function getNavigator() {
      for (let win of Services.ww.getWindowEnumerator()) {
        let winUtils = win.windowUtils;
        try {
          // NOTE: windowless browser's windows should not be reported in the graphics troubleshoot report
          if (
            winUtils.layerManagerType == "None" ||
            !winUtils.layerManagerRemote
          ) {
            continue;
          }
          const nav = win.navigator;
          if (nav) {
            return nav;
          }
        } catch (e) {
          continue;
        }
      }
      throw new Error("No window had window.navigator.");
    }

    const navigator = getNavigator();

    async function GetWebgpuInfo(adapterOpts) {
      const ret = {};
      if (!navigator.gpu) {
        ret["navigator.gpu"] = null;
        return ret;
      }

      const requestAdapterkey = `navigator.gpu.requestAdapter(${JSON.stringify(
        adapterOpts
      )})`;

      let adapter;
      try {
        adapter = await navigator.gpu.requestAdapter(adapterOpts);
      } catch (e) {
        // If WebGPU isn't supported or is blocked somehow, include
        // that in the report. Anything else is an error which should
        // have consequences (test failures, etc).
        if (DOMException.isInstance(e) && e.name == "NotSupportedError") {
          return { [requestAdapterkey]: { not_supported: e.message } };
        }
        throw e;
      }

      if (!adapter) {
        ret[requestAdapterkey] = null;
        return ret;
      }
      const desc = (ret[requestAdapterkey] = {});

      desc.isFallbackAdapter = adapter.isFallbackAdapter;

      const adapterInfo = await adapter.requestAdapterInfo();
      // We can't directly enumerate properties of instances of `GPUAdapterInfo`s, so use the prototype instead.
      const adapterInfoObj = {};
      for (const k of Object.keys(Object.getPrototypeOf(adapterInfo)).sort()) {
        adapterInfoObj[k] = adapterInfo[k];
      }
      desc[`requestAdapterInfo()`] = adapterInfoObj;

      desc.features = Array.from(adapter.features).sort();

      desc.limits = {};
      const keys = Object.keys(Object.getPrototypeOf(adapter.limits)).sort(); // limits not directly enumerable?
      for (const k of keys) {
        desc.limits[k] = adapter.limits[k];
      }

      return ret;
    }

    // Webgpu info is going to need awaits.
    (async () => {
      data.webgpuDefaultAdapter = await GetWebgpuInfo({});
      data.webgpuFallbackAdapter = await GetWebgpuInfo({
        forceFallbackAdapter: true,
      });

      done(data);
    })();
  },

  media: function media(done) {
    function convertDevices(devices) {
      if (!devices) {
        return undefined;
      }
      let infos = [];
      for (let i = 0; i < devices.length; ++i) {
        let device = devices.queryElementAt(i, Ci.nsIAudioDeviceInfo);
        infos.push({
          name: device.name,
          groupId: device.groupId,
          vendor: device.vendor,
          type: device.type,
          state: device.state,
          preferred: device.preferred,
          supportedFormat: device.supportedFormat,
          defaultFormat: device.defaultFormat,
          maxChannels: device.maxChannels,
          defaultRate: device.defaultRate,
          maxRate: device.maxRate,
          minRate: device.minRate,
          maxLatency: device.maxLatency,
          minLatency: device.minLatency,
        });
      }
      return infos;
    }

    let data = {};
    let winUtils = Services.wm.getMostRecentWindow("").windowUtils;
    data.currentAudioBackend = winUtils.currentAudioBackend;
    data.currentMaxAudioChannels = winUtils.currentMaxAudioChannels;
    data.currentPreferredSampleRate = winUtils.currentPreferredSampleRate;
    data.audioOutputDevices = convertDevices(
      winUtils
        .audioDevices(Ci.nsIDOMWindowUtils.AUDIO_OUTPUT)
        .QueryInterface(Ci.nsIArray)
    );
    data.audioInputDevices = convertDevices(
      winUtils
        .audioDevices(Ci.nsIDOMWindowUtils.AUDIO_INPUT)
        .QueryInterface(Ci.nsIArray)
    );

    data.codecSupportInfo = "Unknown";

    // We initialize gfxInfo here in the same way as in the media
    // section -- should we break this out into a separate function?
    try {
      // nsIGfxInfo may not be implemented on some platforms.
      var gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);

      // Note: CodecSupportInfo is not populated until we have
      // actually instantiated a PDM. We may want to add a button
      // or some other means of allowing the user to manually
      // instantiate a PDM to ensure that the data is available.
      data.codecSupportInfo = gfxInfo.CodecSupportInfo;
    } catch (e) {}

    done(data);
  },

  accessibility: function accessibility(done) {
    let data = {};
    data.isActive = Services.appinfo.accessibilityEnabled;
    // eslint-disable-next-line mozilla/use-default-preference-values
    try {
      data.forceDisabled = Services.prefs.getIntPref(
        "accessibility.force_disabled"
      );
    } catch (e) {}
    data.instantiator = Services.appinfo.accessibilityInstantiator;
    done(data);
  },

  startupCache: function startupCache(done) {
    const startupInfo = Cc["@mozilla.org/startupcacheinfo;1"].getService(
      Ci.nsIStartupCacheInfo
    );
    done({
      DiskCachePath: startupInfo.DiskCachePath,
      IgnoreDiskCache: startupInfo.IgnoreDiskCache,
      FoundDiskCacheOnInit: startupInfo.FoundDiskCacheOnInit,
      WroteToDiskCache: startupInfo.WroteToDiskCache,
    });
  },

  libraryVersions: function libraryVersions(done) {
    let data = {};
    let verInfo = Cc["@mozilla.org/security/nssversion;1"].getService(
      Ci.nsINSSVersion
    );
    for (let prop in verInfo) {
      let match = /^([^_]+)_((Min)?Version)$/.exec(prop);
      if (match) {
        let verProp = match[2][0].toLowerCase() + match[2].substr(1);
        data[match[1]] = data[match[1]] || {};
        data[match[1]][verProp] = verInfo[prop];
      }
    }
    done(data);
  },

  userJS: function userJS(done) {
    let userJSFile = Services.dirsvc.get("PrefD", Ci.nsIFile);
    userJSFile.append("user.js");
    done({
      exists: userJSFile.exists() && userJSFile.fileSize > 0,
    });
  },

  intl: function intl(done) {
    const osPrefs = Cc["@mozilla.org/intl/ospreferences;1"].getService(
      Ci.mozIOSPreferences
    );
    done({
      localeService: {
        requested: Services.locale.requestedLocales,
        available: Services.locale.availableLocales,
        supported: Services.locale.appLocalesAsBCP47,
        regionalPrefs: Services.locale.regionalPrefsLocales,
        defaultLocale: Services.locale.defaultLocale,
      },
      osPrefs: {
        systemLocales: osPrefs.systemLocales,
        regionalPrefsLocales: osPrefs.regionalPrefsLocales,
      },
    });
  },

  contentAnalysis: async function contentAnalysis(done) {
    const contentAnalysis = Cc["@mozilla.org/contentanalysis;1"].getService(
      Ci.nsIContentAnalysis
    );
    if (!contentAnalysis.isActive) {
      done({ active: false });
      return;
    }
    let info = await contentAnalysis.getDiagnosticInfo();
    done({
      active: true,
      connected: info.connectedToAgent,
      agentPath: info.agentPath,
      failedSignatureVerification: info.failedSignatureVerification,
      requestCount: info.requestCount,
    });
  },

  async normandy(done) {
    if (!AppConstants.MOZ_NORMANDY) {
      done();
      return;
    }

    const { PreferenceExperiments: NormandyPreferenceStudies } =
      ChromeUtils.importESModule(
        "resource://normandy/lib/PreferenceExperiments.sys.mjs"
      );
    const { AddonStudies: NormandyAddonStudies } = ChromeUtils.importESModule(
      "resource://normandy/lib/AddonStudies.sys.mjs"
    );
    const { PreferenceRollouts: NormandyPreferenceRollouts } =
      ChromeUtils.importESModule(
        "resource://normandy/lib/PreferenceRollouts.sys.mjs"
      );
    const { ExperimentManager } = ChromeUtils.importESModule(
      "resource://nimbus/lib/ExperimentManager.sys.mjs"
    );

    // Get Normandy data in parallel, and sort each group by slug.
    const [
      addonStudies,
      prefRollouts,
      prefStudies,
      nimbusExperiments,
      nimbusRollouts,
    ] = await Promise.all(
      [
        NormandyAddonStudies.getAllActive(),
        NormandyPreferenceRollouts.getAllActive(),
        NormandyPreferenceStudies.getAllActive(),
        ExperimentManager.store
          .ready()
          .then(() => ExperimentManager.store.getAllActiveExperiments()),
        ExperimentManager.store
          .ready()
          .then(() => ExperimentManager.store.getAllActiveRollouts()),
      ].map(promise =>
        promise
          .catch(error => {
            console.error(error);
            return [];
          })
          .then(items => items.sort((a, b) => a.slug.localeCompare(b.slug)))
      )
    );

    done({
      addonStudies,
      prefRollouts,
      prefStudies,
      nimbusExperiments,
      nimbusRollouts,
    });
  },

  async remoteSettings(done) {
    const { RemoteSettings } = ChromeUtils.importESModule(
      "resource://services-settings/remote-settings.sys.mjs"
    );

    let inspected;
    try {
      inspected = await RemoteSettings.inspect({ localOnly: true });
    } catch (error) {
      console.error(error);
      done({ isSynchronizationBroken: true, history: { "settings-sync": [] } });
      return;
    }

    // Show last check in standard format.
    inspected.lastCheck = inspected.lastCheck
      ? new Date(inspected.lastCheck * 1000).toISOString()
      : "";
    // Trim history entries.
    for (let h of Object.values(inspected.history)) {
      h.splice(10, Infinity);
    }

    done(inspected);
  },
};

if (AppConstants.MOZ_CRASHREPORTER) {
  dataProviders.crashes = function crashes(done) {
    const { CrashReports } = ChromeUtils.importESModule(
      "resource://gre/modules/CrashReports.sys.mjs"
    );
    let reports = CrashReports.getReports();
    let now = new Date();
    let reportsNew = reports.filter(
      report => now - report.date < Troubleshoot.kMaxCrashAge
    );
    let reportsSubmitted = reportsNew.filter(report => !report.pending);
    let reportsPendingCount = reportsNew.length - reportsSubmitted.length;
    let data = { submitted: reportsSubmitted, pending: reportsPendingCount };
    done(data);
  };
}

if (AppConstants.MOZ_SANDBOX) {
  dataProviders.sandbox = function sandbox(done) {
    let data = {};
    if (AppConstants.unixstyle == "linux") {
      const keys = [
        "hasSeccompBPF",
        "hasSeccompTSync",
        "hasPrivilegedUserNamespaces",
        "hasUserNamespaces",
        "canSandboxContent",
        "canSandboxMedia",
      ];

      for (let key of keys) {
        if (Services.sysinfo.hasKey(key)) {
          data[key] = Services.sysinfo.getPropertyAsBool(key);
        }
      }

      let reporter = Cc["@mozilla.org/sandbox/syscall-reporter;1"].getService(
        Ci.mozISandboxReporter
      );
      const snapshot = reporter.snapshot();
      let syscalls = [];
      for (let index = snapshot.begin; index < snapshot.end; ++index) {
        let report = snapshot.getElement(index);
        let { msecAgo, pid, tid, procType, syscall } = report;
        let args = [];
        for (let i = 0; i < report.numArgs; ++i) {
          args.push(report.getArg(i));
        }
        syscalls.push({ index, msecAgo, pid, tid, procType, syscall, args });
      }
      data.syscallLog = syscalls;
    }

    if (AppConstants.MOZ_SANDBOX) {
      let sandboxSettings = Cc[
        "@mozilla.org/sandbox/sandbox-settings;1"
      ].getService(Ci.mozISandboxSettings);
      data.contentSandboxLevel = Services.prefs.getIntPref(
        "security.sandbox.content.level"
      );
      data.effectiveContentSandboxLevel =
        sandboxSettings.effectiveContentSandboxLevel;

      if (AppConstants.platform == "win") {
        data.contentWin32kLockdownState =
          sandboxSettings.contentWin32kLockdownStateString;

        data.supportSandboxGpuLevel = Services.prefs.getIntPref(
          "security.sandbox.gpu.level"
        );
      }
    }

    done(data);
  };
}

if (AppConstants.ENABLE_WEBDRIVER) {
  dataProviders.remoteAgent = function remoteAgent(done) {
    const { RemoteAgent } = ChromeUtils.importESModule(
      "chrome://remote/content/components/RemoteAgent.sys.mjs"
    );
    const { running, scheme, host, port } = RemoteAgent;
    let url = "";
    if (running) {
      url = `${scheme}://${host}:${port}/`;
    }
    done({ running, url });
  };
}
PK
!<O���.�.�modules/URIFixup.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * vim: sw=2 ts=2 sts=2 expandtab
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This component handles fixing up URIs, by correcting obvious typos and adding
 * missing schemes.
 * URI references:
 *   http://www.faqs.org/rfcs/rfc1738.html
 *   http://www.faqs.org/rfcs/rfc2396.html
 */

// TODO (Bug 1641220) getFixupURIInfo has a complex logic, that likely could be
// simplified, but the risk of regressing its behavior is high.
/* eslint complexity: ["error", 43] */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "externalProtocolService",
  "@mozilla.org/uriloader/external-protocol-service;1",
  "nsIExternalProtocolService"
);

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "defaultProtocolHandler",
  "@mozilla.org/network/protocol;1?name=default",
  "nsIProtocolHandler"
);

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "fileProtocolHandler",
  "@mozilla.org/network/protocol;1?name=file",
  "nsIFileProtocolHandler"
);

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "handlerService",
  "@mozilla.org/uriloader/handler-service;1",
  "nsIHandlerService"
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "fixupSchemeTypos",
  "browser.fixup.typo.scheme",
  true
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "dnsFirstForSingleWords",
  "browser.fixup.dns_first_for_single_words",
  false
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "keywordEnabled",
  "keyword.enabled",
  true
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "alternateProtocol",
  "browser.fixup.alternate.protocol",
  "https"
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "dnsResolveFullyQualifiedNames",
  "browser.urlbar.dnsResolveFullyQualifiedNames",
  true
);

const {
  FIXUP_FLAG_NONE,
  FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP,
  FIXUP_FLAGS_MAKE_ALTERNATE_URI,
  FIXUP_FLAG_PRIVATE_CONTEXT,
  FIXUP_FLAG_FIX_SCHEME_TYPOS,
} = Ci.nsIURIFixup;

const COMMON_PROTOCOLS = ["http", "https", "file"];

// Regex used to identify user:password tokens in url strings.
// This is not a strict valid characters check, because we try to fixup this
// part of the url too.
ChromeUtils.defineLazyGetter(
  lazy,
  "userPasswordRegex",
  () => /^([a-z+.-]+:\/{0,3})*([^\/@]+@).+/i
);

// Regex used to identify the string that starts with port expression.
ChromeUtils.defineLazyGetter(lazy, "portRegex", () => /^:\d{1,5}([?#/]|$)/);

// Regex used to identify numbers.
ChromeUtils.defineLazyGetter(lazy, "numberRegex", () => /^[0-9]+(\.[0-9]+)?$/);

// Regex used to identify tab separated content (having at least 2 tabs).
ChromeUtils.defineLazyGetter(lazy, "maxOneTabRegex", () => /^[^\t]*\t?[^\t]*$/);

// Regex used to test if a string with a protocol might instead be a url
// without a protocol but with a port:
//
//   <hostname>:<port> or
//   <hostname>:<port>/
//
// Where <hostname> is a string of alphanumeric characters and dashes
// separated by dots.
// and <port> is a 5 or less digits. This actually breaks the rfc2396
// definition of a scheme which allows dots in schemes.
//
// Note:
//   People expecting this to work with
//   <user>:<password>@<host>:<port>/<url-path> will be disappointed!
//
// Note: Parser could be a lot tighter, tossing out silly hostnames
//       such as those containing consecutive dots and so on.
ChromeUtils.defineLazyGetter(
  lazy,
  "possiblyHostPortRegex",
  () => /^[a-z0-9-]+(\.[a-z0-9-]+)*:[0-9]{1,5}([/?#]|$)/i
);

// Regex used to strip newlines.
ChromeUtils.defineLazyGetter(lazy, "newLinesRegex", () => /[\r\n]/g);

// Regex used to match a possible protocol.
// This resembles the logic in Services.io.extractScheme, thus \t is admitted
// and stripped later. We don't use Services.io.extractScheme because of
// performance bottleneck caused by crossing XPConnect.
ChromeUtils.defineLazyGetter(
  lazy,
  "possibleProtocolRegex",
  () => /^([a-z][a-z0-9.+\t-]*)(:|;)?(\/\/)?/i
);

// Regex used to match IPs. Note that these are not made to validate IPs, but
// just to detect strings that look like an IP. They also skip protocol.
// For IPv4 this also accepts a shorthand format with just 2 dots.
ChromeUtils.defineLazyGetter(
  lazy,
  "IPv4LikeRegex",
  () => /^(?:[a-z+.-]+:\/*(?!\/))?(?:\d{1,3}\.){2,3}\d{1,3}(?::\d+|\/)?/i
);
ChromeUtils.defineLazyGetter(
  lazy,
  "IPv6LikeRegex",
  () =>
    /^(?:[a-z+.-]+:\/*(?!\/))?\[(?:[0-9a-f]{0,4}:){0,7}[0-9a-f]{0,4}\]?(?::\d+|\/)?/i
);

// Regex used to detect spaces in URL credentials.
ChromeUtils.defineLazyGetter(
  lazy,
  "DetectSpaceInCredentialsRegex",
  () => /^[^/]*\s[^/]*@/
);

// Cache of known domains.
ChromeUtils.defineLazyGetter(lazy, "knownDomains", () => {
  const branch = "browser.fixup.domainwhitelist.";
  let domains = new Set(
    Services.prefs
      .getChildList(branch)
      .filter(p => Services.prefs.getBoolPref(p, false))
      .map(p => p.substring(branch.length))
  );
  // Hold onto the observer to avoid it being GC-ed.
  domains._observer = {
    observe(subject, topic, data) {
      let domain = data.substring(branch.length);
      if (Services.prefs.getBoolPref(data, false)) {
        domains.add(domain);
      } else {
        domains.delete(domain);
      }
    },
    QueryInterface: ChromeUtils.generateQI([
      "nsIObserver",
      "nsISupportsWeakReference",
    ]),
  };
  Services.prefs.addObserver(branch, domains._observer, true);
  return domains;
});

// Cache of known suffixes.
// This works differently from the known domains, because when we examine a
// domain we can't tell how many dot-separated parts constitute the suffix.
// We create a Map keyed by the last dotted part, containing a Set of
// all the suffixes ending with that part:
//   "two" => ["two"]
//   "three" => ["some.three", "three"]
// When searching we can restrict the linear scan based on the last part.
// The ideal structure for this would be a Directed Acyclic Word Graph, but
// since we expect this list to be small it's not worth the complication.
ChromeUtils.defineLazyGetter(lazy, "knownSuffixes", () => {
  const branch = "browser.fixup.domainsuffixwhitelist.";
  let suffixes = new Map();
  let prefs = Services.prefs
    .getChildList(branch)
    .filter(p => Services.prefs.getBoolPref(p, false));
  for (let pref of prefs) {
    let suffix = pref.substring(branch.length);
    let lastPart = suffix.substr(suffix.lastIndexOf(".") + 1);
    if (lastPart) {
      let entries = suffixes.get(lastPart);
      if (!entries) {
        entries = new Set();
        suffixes.set(lastPart, entries);
      }
      entries.add(suffix);
    }
  }
  // Hold onto the observer to avoid it being GC-ed.
  suffixes._observer = {
    observe(subject, topic, data) {
      let suffix = data.substring(branch.length);
      let lastPart = suffix.substr(suffix.lastIndexOf(".") + 1);
      let entries = suffixes.get(lastPart);
      if (Services.prefs.getBoolPref(data, false)) {
        // Add the suffix.
        if (!entries) {
          entries = new Set();
          suffixes.set(lastPart, entries);
        }
        entries.add(suffix);
      } else if (entries) {
        // Remove the suffix.
        entries.delete(suffix);
        if (!entries.size) {
          suffixes.delete(lastPart);
        }
      }
    },
    QueryInterface: ChromeUtils.generateQI([
      "nsIObserver",
      "nsISupportsWeakReference",
    ]),
  };
  Services.prefs.addObserver(branch, suffixes._observer, true);
  return suffixes;
});

export function URIFixup() {
  // There are cases that nsIExternalProtocolService.externalProtocolHandlerExists() does
  // not work well and returns always true due to flatpak. In this case, in order to
  // fallback to nsIHandlerService.exits(), we test whether can trust
  // nsIExternalProtocolService here.
  this._trustExternalProtocolService =
    !lazy.externalProtocolService.externalProtocolHandlerExists(
      `__dummy${Date.now()}__`
    );
}

URIFixup.prototype = {
  get FIXUP_FLAG_NONE() {
    return FIXUP_FLAG_NONE;
  },
  get FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP() {
    return FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
  },
  get FIXUP_FLAGS_MAKE_ALTERNATE_URI() {
    return FIXUP_FLAGS_MAKE_ALTERNATE_URI;
  },
  get FIXUP_FLAG_PRIVATE_CONTEXT() {
    return FIXUP_FLAG_PRIVATE_CONTEXT;
  },
  get FIXUP_FLAG_FIX_SCHEME_TYPOS() {
    return FIXUP_FLAG_FIX_SCHEME_TYPOS;
  },

  getFixupURIInfo(uriString, fixupFlags = FIXUP_FLAG_NONE) {
    let isPrivateContext = fixupFlags & FIXUP_FLAG_PRIVATE_CONTEXT;
    let untrimmedURIString = uriString;

    // Eliminate embedded newlines, which single-line text fields now allow,
    // and cleanup the empty spaces and tabs that might be on each end.
    uriString = uriString.trim().replace(lazy.newLinesRegex, "");

    if (!uriString) {
      throw new Components.Exception(
        "Should pass a non-null uri",
        Cr.NS_ERROR_FAILURE
      );
    }

    let info = new URIFixupInfo(uriString);

    const { scheme, fixedSchemeUriString, fixupChangedProtocol } =
      extractScheme(uriString, fixupFlags);
    uriString = fixedSchemeUriString;
    info.fixupChangedProtocol = fixupChangedProtocol;

    if (scheme == "view-source") {
      let { preferredURI, postData } = fixupViewSource(uriString, fixupFlags);
      info.preferredURI = info.fixedURI = preferredURI;
      info.postData = postData;
      return info;
    }

    if (scheme.length < 2) {
      // Check if it is a file path. We skip most schemes because the only case
      // where a file path may look like having a scheme is "X:" on Windows.
      let fileURI = fileURIFixup(uriString);
      if (fileURI) {
        info.preferredURI = info.fixedURI = fileURI;
        info.fixupChangedProtocol = true;
        return info;
      }
    }

    const isCommonProtocol = COMMON_PROTOCOLS.includes(scheme);

    let canHandleProtocol =
      scheme &&
      (isCommonProtocol ||
        Services.io.getProtocolHandler(scheme) != lazy.defaultProtocolHandler ||
        this._isKnownExternalProtocol(scheme));

    if (
      canHandleProtocol ||
      // If it's an unknown handler and the given URL looks like host:port or
      // has a user:password we can't pass it to the external protocol handler.
      // We'll instead try fixing it with http later.
      (!lazy.possiblyHostPortRegex.test(uriString) &&
        !lazy.userPasswordRegex.test(uriString))
    ) {
      // Just try to create an URL out of it.
      try {
        info.fixedURI = Services.io.newURI(uriString);
      } catch (ex) {
        if (ex.result != Cr.NS_ERROR_MALFORMED_URI) {
          throw ex;
        }
      }
    }

    // We're dealing with a theoretically valid URI but we have no idea how to
    // load it. (e.g. "christmas:humbug")
    // It's more likely the user wants to search, and so we chuck this over to
    // their preferred search provider.
    // TODO (Bug 1588118): Should check FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP
    // instead of FIXUP_FLAG_FIX_SCHEME_TYPOS.
    if (
      info.fixedURI &&
      lazy.keywordEnabled &&
      fixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS &&
      scheme &&
      !canHandleProtocol
    ) {
      tryKeywordFixupForURIInfo(uriString, info, isPrivateContext);
    }

    if (info.fixedURI) {
      if (!info.preferredURI) {
        maybeSetAlternateFixedURI(info, fixupFlags);
        info.preferredURI = info.fixedURI;
      }
      fixupConsecutiveDotsHost(info);
      return info;
    }

    // Fix up protocol string before calling KeywordURIFixup, because
    // it cares about the hostname of such URIs.
    // Prune duff protocol schemes:
    //   ://totallybroken.url.com
    //   //shorthand.url.com
    let inputHadDuffProtocol =
      uriString.startsWith("://") || uriString.startsWith("//");
    if (inputHadDuffProtocol) {
      uriString = uriString.replace(/^:?\/\//, "");
    }

    // Avoid fixing up content that looks like tab-separated values.
    // Assume that 1 tab is accidental, but more than 1 implies this is
    // supposed to be tab-separated content.
    if (
      !isCommonProtocol &&
      lazy.maxOneTabRegex.test(uriString) &&
      !lazy.DetectSpaceInCredentialsRegex.test(untrimmedURIString)
    ) {
      let uriWithProtocol = fixupURIProtocol(uriString);
      if (uriWithProtocol) {
        info.fixedURI = uriWithProtocol;
        info.fixupChangedProtocol = true;
        info.wasSchemelessInput = true;
        maybeSetAlternateFixedURI(info, fixupFlags);
        info.preferredURI = info.fixedURI;
        // Check if it's a forced visit. The user can enforce a visit by
        // appending a slash, but the string must be in a valid uri format.
        if (uriString.endsWith("/")) {
          fixupConsecutiveDotsHost(info);
          return info;
        }
      }
    }

    // Handle "www.<something>" as a URI.
    const asciiHost = info.fixedURI?.asciiHost;
    if (
      asciiHost?.length > 4 &&
      asciiHost?.startsWith("www.") &&
      asciiHost?.lastIndexOf(".") == 3
    ) {
      return info;
    }

    // Memoize the public suffix check, since it may be expensive and should
    // only run once when necessary.
    let suffixInfo;
    function checkSuffix(info) {
      if (!suffixInfo) {
        suffixInfo = checkAndFixPublicSuffix(info);
      }
      return suffixInfo;
    }

    // See if it is a keyword and whether a keyword must be fixed up.
    if (
      lazy.keywordEnabled &&
      fixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP &&
      !inputHadDuffProtocol &&
      !checkSuffix(info).suffix &&
      keywordURIFixup(uriString, info, isPrivateContext)
    ) {
      fixupConsecutiveDotsHost(info);
      return info;
    }

    if (
      info.fixedURI &&
      (!info.fixupChangedProtocol || !checkSuffix(info).hasUnknownSuffix)
    ) {
      fixupConsecutiveDotsHost(info);
      return info;
    }

    // If we still haven't been able to construct a valid URI, try to force a
    // keyword match.
    if (lazy.keywordEnabled && fixupFlags & FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP) {
      tryKeywordFixupForURIInfo(info.originalInput, info, isPrivateContext);
    }

    if (!info.preferredURI) {
      // We couldn't salvage anything.
      throw new Components.Exception(
        "Couldn't build a valid uri",
        Cr.NS_ERROR_MALFORMED_URI
      );
    }

    fixupConsecutiveDotsHost(info);
    return info;
  },

  webNavigationFlagsToFixupFlags(href, navigationFlags) {
    try {
      Services.io.newURI(href);
      // Remove LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP for valid uris.
      navigationFlags &=
        ~Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
    } catch (ex) {}

    let fixupFlags = FIXUP_FLAG_NONE;
    if (
      navigationFlags & Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP
    ) {
      fixupFlags |= FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
    }
    if (navigationFlags & Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS) {
      fixupFlags |= FIXUP_FLAG_FIX_SCHEME_TYPOS;
    }
    return fixupFlags;
  },

  keywordToURI(keyword, isPrivateContext) {
    if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
      // There's no search service in the content process, thus all the calls
      // from it that care about keywords conversion should go through the
      // parent process.
      throw new Components.Exception(
        "Can't invoke URIFixup in the content process",
        Cr.NS_ERROR_NOT_AVAILABLE
      );
    }
    let info = new URIFixupInfo(keyword);

    // Strip leading "?" and leading/trailing spaces from aKeyword
    if (keyword.startsWith("?")) {
      keyword = keyword.substring(1);
    }
    keyword = keyword.trim();

    if (!Services.search.hasSuccessfullyInitialized) {
      return info;
    }

    // Try falling back to the search service's default search engine
    // We must use an appropriate search engine depending on the private
    // context.
    let engine = isPrivateContext
      ? Services.search.defaultPrivateEngine
      : Services.search.defaultEngine;

    // We allow default search plugins to specify alternate parameters that are
    // specific to keyword searches.
    let responseType = null;
    if (engine.supportsResponseType("application/x-moz-keywordsearch")) {
      responseType = "application/x-moz-keywordsearch";
    }
    let submission = engine.getSubmission(keyword, responseType, "keyword");
    if (
      !submission ||
      // For security reasons (avoid redirecting to file, data, or other unsafe
      // protocols) we only allow fixup to http/https search engines.
      !submission.uri.scheme.startsWith("http")
    ) {
      throw new Components.Exception(
        "Invalid search submission uri",
        Cr.NS_ERROR_NOT_AVAILABLE
      );
    }
    let submissionPostDataStream = submission.postData;
    if (submissionPostDataStream) {
      info.postData = submissionPostDataStream;
    }

    info.keywordProviderName = engine.name;
    info.keywordAsSent = keyword;
    info.preferredURI = submission.uri;
    return info;
  },

  forceHttpFixup(uriString) {
    if (!uriString) {
      throw new Components.Exception(
        "Should pass a non-null uri",
        Cr.NS_ERROR_FAILURE
      );
    }

    let info = new URIFixupInfo(uriString);
    let { scheme, fixedSchemeUriString, fixupChangedProtocol } = extractScheme(
      uriString,
      FIXUP_FLAG_FIX_SCHEME_TYPOS
    );

    if (scheme != "http" && scheme != "https") {
      throw new Components.Exception(
        "Scheme should be either http or https",
        Cr.NS_ERROR_FAILURE
      );
    }

    info.fixupChangedProtocol = fixupChangedProtocol;
    info.fixedURI = Services.io.newURI(fixedSchemeUriString);

    let host = info.fixedURI.host;
    if (host != "http" && host != "https" && host != "localhost") {
      let modifiedHostname = maybeAddPrefixAndSuffix(host);
      updateHostAndScheme(info, modifiedHostname);
      info.preferredURI = info.fixedURI;
    }

    return info;
  },

  checkHost(uri, listener, originAttributes) {
    let { displayHost, asciiHost } = uri;
    if (!displayHost) {
      throw new Components.Exception(
        "URI must have displayHost",
        Cr.NS_ERROR_FAILURE
      );
    }
    if (!asciiHost) {
      throw new Components.Exception(
        "URI must have asciiHost",
        Cr.NS_ERROR_FAILURE
      );
    }

    let isIPv4Address = host => {
      let parts = host.split(".");
      if (parts.length != 4) {
        return false;
      }
      return parts.every(part => {
        let n = parseInt(part, 10);
        return n >= 0 && n <= 255;
      });
    };

    // Avoid showing fixup information if we're suggesting an IP. Note that
    // decimal representations of IPs are normalized to a 'regular'
    // dot-separated IP address by network code, but that only happens for
    // numbers that don't overflow. Longer numbers do not get normalized,
    // but still work to access IP addresses. So for instance,
    // 1097347366913 (ff7f000001) gets resolved by using the final bytes,
    // making it the same as 7f000001, which is 127.0.0.1 aka localhost.
    // While 2130706433 would get normalized by network, 1097347366913
    // does not, and we have to deal with both cases here:
    if (isIPv4Address(asciiHost) || /^(?:\d+|0x[a-f0-9]+)$/i.test(asciiHost)) {
      return;
    }

    // For dotless hostnames, we want to ensure this ends with a '.' but don't
    // want the . showing up in the UI if we end up notifying the user, so we
    // use a separate variable.
    let lookupName = displayHost;
    if (lazy.dnsResolveFullyQualifiedNames && !lookupName.includes(".")) {
      lookupName += ".";
    }

    Services.obs.notifyObservers(null, "uri-fixup-check-dns");
    Services.dns.asyncResolve(
      lookupName,
      Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
      0,
      null,
      listener,
      Services.tm.mainThread,
      originAttributes
    );
  },

  isDomainKnown,

  _isKnownExternalProtocol(scheme) {
    if (this._trustExternalProtocolService) {
      return lazy.externalProtocolService.externalProtocolHandlerExists(scheme);
    }

    try {
      // nsIExternalProtocolService.getProtocolHandlerInfo() on Android throws
      // error due to not implemented.
      return lazy.handlerService.exists(
        lazy.externalProtocolService.getProtocolHandlerInfo(scheme)
      );
    } catch (e) {
      return false;
    }
  },

  classID: Components.ID("{c6cf88b7-452e-47eb-bdc9-86e3561648ef}"),
  QueryInterface: ChromeUtils.generateQI(["nsIURIFixup"]),
};

export function URIFixupInfo(originalInput = "") {
  this._originalInput = originalInput;
}

URIFixupInfo.prototype = {
  set consumer(consumer) {
    this._consumer = consumer || null;
  },
  get consumer() {
    return this._consumer || null;
  },

  set preferredURI(uri) {
    this._preferredURI = uri;
  },
  get preferredURI() {
    return this._preferredURI || null;
  },

  set fixedURI(uri) {
    this._fixedURI = uri;
  },
  get fixedURI() {
    return this._fixedURI || null;
  },

  set keywordProviderName(name) {
    this._keywordProviderName = name;
  },
  get keywordProviderName() {
    return this._keywordProviderName || "";
  },

  set keywordAsSent(keyword) {
    this._keywordAsSent = keyword;
  },
  get keywordAsSent() {
    return this._keywordAsSent || "";
  },

  set wasSchemelessInput(changed) {
    this._wasSchemelessInput = changed;
  },
  get wasSchemelessInput() {
    return !!this._wasSchemelessInput;
  },

  set fixupChangedProtocol(changed) {
    this._fixupChangedProtocol = changed;
  },
  get fixupChangedProtocol() {
    return !!this._fixupChangedProtocol;
  },

  set fixupCreatedAlternateURI(changed) {
    this._fixupCreatedAlternateURI = changed;
  },
  get fixupCreatedAlternateURI() {
    return !!this._fixupCreatedAlternateURI;
  },

  set originalInput(input) {
    this._originalInput = input;
  },
  get originalInput() {
    return this._originalInput || "";
  },

  set postData(postData) {
    this._postData = postData;
  },
  get postData() {
    return this._postData || null;
  },

  classID: Components.ID("{33d75835-722f-42c0-89cc-44f328e56a86}"),
  QueryInterface: ChromeUtils.generateQI(["nsIURIFixupInfo"]),
};

// Helpers

/**
 * Implementation of isDomainKnown, so we don't have to go through the
 * service.
 * @param {string} asciiHost
 * @returns {boolean} whether the domain is known
 */
function isDomainKnown(asciiHost) {
  if (lazy.dnsFirstForSingleWords) {
    return true;
  }
  // Check if this domain is known as an actual
  // domain (which will prevent a keyword query)
  // Note that any processing of the host here should stay in sync with
  // code in the front-end(s) that set the pref.
  let lastDotIndex = asciiHost.lastIndexOf(".");
  if (lastDotIndex == asciiHost.length - 1) {
    asciiHost = asciiHost.substring(0, asciiHost.length - 1);
    lastDotIndex = asciiHost.lastIndexOf(".");
  }
  if (lazy.knownDomains.has(asciiHost.toLowerCase())) {
    return true;
  }
  // If there's no dot or only a leading dot we are done, otherwise we'll check
  // against the known suffixes.
  if (lastDotIndex <= 0) {
    return false;
  }
  // Don't use getPublicSuffix here, since the suffix is not in the PSL,
  // thus it couldn't tell if the suffix is made up of one or multiple
  // dot-separated parts.
  let lastPart = asciiHost.substr(lastDotIndex + 1);
  let suffixes = lazy.knownSuffixes.get(lastPart);
  if (suffixes) {
    return Array.from(suffixes).some(s => asciiHost.endsWith(s));
  }
  return false;
}

/**
 * Checks the suffix of info.fixedURI against the Public Suffix List.
 * If the suffix is unknown due to a typo this will try to fix it up.
 * @param {URIFixupInfo} info about the uri to check.
 * @note this may modify the public suffix of info.fixedURI.
 * @returns {object} result The lookup result.
 * @returns {string} result.suffix The public suffix if one can be identified.
 * @returns {boolean} result.hasUnknownSuffix True when the suffix is not in the
 *     Public Suffix List and it's not in knownSuffixes. False in the other cases.
 */
function checkAndFixPublicSuffix(info) {
  let uri = info.fixedURI;
  let asciiHost = uri?.asciiHost;
  if (
    !asciiHost ||
    !asciiHost.includes(".") ||
    asciiHost.endsWith(".") ||
    isDomainKnown(asciiHost)
  ) {
    return { suffix: "", hasUnknownSuffix: false };
  }

  // Quick bailouts for most common cases, according to Alexa Top 1 million.
  if (
    /^\w/.test(asciiHost) &&
    (asciiHost.endsWith(".com") ||
      asciiHost.endsWith(".net") ||
      asciiHost.endsWith(".org") ||
      asciiHost.endsWith(".ru") ||
      asciiHost.endsWith(".de"))
  ) {
    return {
      suffix: asciiHost.substring(asciiHost.lastIndexOf(".") + 1),
      hasUnknownSuffix: false,
    };
  }
  try {
    let suffix = Services.eTLD.getKnownPublicSuffix(uri);
    if (suffix) {
      return { suffix, hasUnknownSuffix: false };
    }
  } catch (ex) {
    return { suffix: "", hasUnknownSuffix: false };
  }
  // Suffix is unknown, try to fix most common 3 chars TLDs typos.
  // .com is the most commonly mistyped tld, so it has more cases.
  let suffix = Services.eTLD.getPublicSuffix(uri);
  if (!suffix || lazy.numberRegex.test(suffix)) {
    return { suffix: "", hasUnknownSuffix: false };
  }
  for (let [typo, fixed] of [
    ["ocm", "com"],
    ["con", "com"],
    ["cmo", "com"],
    ["xom", "com"],
    ["vom", "com"],
    ["cpm", "com"],
    ["com'", "com"],
    ["ent", "net"],
    ["ner", "net"],
    ["nte", "net"],
    ["met", "net"],
    ["rog", "org"],
    ["ogr", "org"],
    ["prg", "org"],
    ["orh", "org"],
  ]) {
    if (suffix == typo) {
      let host = uri.host.substring(0, uri.host.length - typo.length) + fixed;
      let updatePreferredURI = info.preferredURI == info.fixedURI;
      info.fixedURI = uri.mutate().setHost(host).finalize();
      if (updatePreferredURI) {
        info.preferredURI = info.fixedURI;
      }
      return { suffix: fixed, hasUnknownSuffix: false };
    }
  }
  return { suffix: "", hasUnknownSuffix: true };
}

function tryKeywordFixupForURIInfo(uriString, fixupInfo, isPrivateContext) {
  try {
    let keywordInfo = Services.uriFixup.keywordToURI(
      uriString,
      isPrivateContext
    );
    fixupInfo.keywordProviderName = keywordInfo.keywordProviderName;
    fixupInfo.keywordAsSent = keywordInfo.keywordAsSent;
    fixupInfo.preferredURI = keywordInfo.preferredURI;
    return true;
  } catch (ex) {}
  return false;
}

/**
 * This generates an alternate fixedURI, by adding a prefix and a suffix to
 * the fixedURI host, if and only if the protocol is http. It should _never_
 * modify URIs with other protocols.
 * @param {URIFixupInfo} info an URIInfo object
 * @param {integer} fixupFlags the fixup flags
 * @returns {boolean} Whether an alternate uri was generated
 */
function maybeSetAlternateFixedURI(info, fixupFlags) {
  let uri = info.fixedURI;
  if (
    !(fixupFlags & FIXUP_FLAGS_MAKE_ALTERNATE_URI) ||
    // Code only works for http. Not for any other protocol including https!
    !uri.schemeIs("http") ||
    // Security - URLs with user / password info should NOT be fixed up
    uri.userPass ||
    // Don't fix up hosts with ports
    uri.port != -1
  ) {
    return false;
  }

  let oldHost = uri.host;
  // Don't create an alternate uri for localhost, because it would be confusing.
  // Ditto for 'http' and 'https' as these are frequently the result of typos, e.g.
  // 'https//foo' (note missing : ).
  if (oldHost == "localhost" || oldHost == "http" || oldHost == "https") {
    return false;
  }

  // Get the prefix and suffix to stick onto the new hostname. By default these
  // are www. & .com but they could be any other value, e.g. www. & .org
  let newHost = maybeAddPrefixAndSuffix(oldHost);

  if (newHost == oldHost) {
    return false;
  }

  return updateHostAndScheme(info, newHost);
}

/**
 * Try to fixup a file URI.
 * @param {string} uriString The file URI to fix.
 * @returns {nsIURI} a fixed uri or null.
 * @note FileURIFixup only returns a URI if it has to add the file: protocol.
 */
function fileURIFixup(uriString) {
  let attemptFixup = false;
  let path = uriString;
  if (AppConstants.platform == "win") {
    // Check for "\"" in the url-string, just a drive (e.g. C:),
    // or 'A:/...' where the "protocol" is also a single letter.
    attemptFixup =
      uriString.includes("\\") ||
      (uriString[1] == ":" && (uriString.length == 2 || uriString[2] == "/"));
    if (uriString[1] == ":" && uriString[2] == "/") {
      path = uriString.replace(/\//g, "\\");
    }
  } else {
    // UNIX: Check if it starts with "/" or "~".
    attemptFixup = /^[~/]/.test(uriString);
  }
  if (attemptFixup) {
    try {
      // Test if this is a valid path by trying to create a local file
      // object. The URL of that is returned if successful.
      let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
      file.initWithPath(path);
      return Services.io.newURI(
        lazy.fileProtocolHandler.getURLSpecFromActualFile(file)
      );
    } catch (ex) {
      // Not a file uri.
    }
  }
  return null;
}

/**
 * Tries to fixup a string to an nsIURI by adding the default protocol.
 *
 * Should fix things like:
 *    no-scheme.com
 *    ftp.no-scheme.com
 *    ftp4.no-scheme.com
 *    no-scheme.com/query?foo=http://www.foo.com
 *    user:pass@no-scheme.com
 *
 * @param {string} uriString The string to fixup.
 * @returns {nsIURI} an nsIURI built adding the default protocol to the string,
 *          or null if fixing was not possible.
 */
function fixupURIProtocol(uriString) {
  let schemePos = uriString.indexOf("://");
  if (schemePos == -1 || schemePos > uriString.search(/[:\/]/)) {
    uriString = "http://" + uriString;
  }
  try {
    return Services.io.newURI(uriString);
  } catch (ex) {
    // We generated an invalid uri.
  }
  return null;
}

/**
 * Tries to fixup a string to a search url.
 * @param {string} uriString the string to fixup.
 * @param {URIFixupInfo} fixupInfo The fixup info object, modified in-place.
 * @param {boolean} isPrivateContext Whether this happens in a private context.
 * @param {nsIInputStream} postData optional POST data for the search
 * @returns {boolean} Whether the keyword fixup was succesful.
 */
function keywordURIFixup(uriString, fixupInfo, isPrivateContext) {
  // Here is a few examples of strings that should be searched:
  // "what is mozilla"
  // "what is mozilla?"
  // "docshell site:mozilla.org" - has a space in the origin part
  // "?site:mozilla.org - anything that begins with a question mark
  // "mozilla'.org" - Things that have a quote before the first dot/colon
  // "mozilla/test" - unknown host
  // ".mozilla", "mozilla." - starts or ends with a dot ()
  // "user@nonQualifiedHost"

  // These other strings should not be searched, because they could be URIs:
  // "www.blah.com" - Domain with a standard or known suffix
  // "knowndomain" - known domain
  // "nonQualifiedHost:8888?something" - has a port
  // "user:pass@nonQualifiedHost"
  // "blah.com."

  // We do keyword lookups if the input starts with a question mark.
  if (uriString.startsWith("?")) {
    return tryKeywordFixupForURIInfo(
      fixupInfo.originalInput,
      fixupInfo,
      isPrivateContext
    );
  }

  // Check for IPs.
  const userPassword = lazy.userPasswordRegex.exec(uriString);
  const ipString = userPassword
    ? uriString.replace(userPassword[2], "")
    : uriString;
  if (lazy.IPv4LikeRegex.test(ipString) || lazy.IPv6LikeRegex.test(ipString)) {
    return false;
  }

  // Avoid keyword lookup if we can identify a host and it's known, or ends
  // with a dot and has some path.
  // Note that if dnsFirstForSingleWords is true isDomainKnown will always
  // return true, so we can avoid checking dnsFirstForSingleWords after this.
  let asciiHost = fixupInfo.fixedURI?.asciiHost;
  if (
    asciiHost &&
    (isDomainKnown(asciiHost) ||
      (asciiHost.endsWith(".") &&
        asciiHost.indexOf(".") != asciiHost.length - 1))
  ) {
    return false;
  }

  // Avoid keyword lookup if the url seems to have password.
  if (fixupInfo.fixedURI?.password) {
    return false;
  }

  // Even if the host is unknown, avoid keyword lookup if the string has
  // uri-like characteristics, unless it looks like "user@unknownHost".
  // Note we already excluded passwords at this point.
  if (
    !isURILike(uriString, fixupInfo.fixedURI?.displayHost) ||
    (fixupInfo.fixedURI?.userPass && fixupInfo.fixedURI?.pathQueryRef === "/")
  ) {
    return tryKeywordFixupForURIInfo(
      fixupInfo.originalInput,
      fixupInfo,
      isPrivateContext
    );
  }

  return false;
}

/**
 * Mimics the logic in Services.io.extractScheme, but avoids crossing XPConnect.
 * This also tries to fixup the scheme if it was clearly mistyped.
 * @param {string} uriString the string to examine
 * @param {integer} fixupFlags The original fixup flags
 * @returns {object}
 *          scheme: a typo fixed scheme or empty string if one could not be identified
 *          fixedSchemeUriString: uri string with a typo fixed scheme
 *          fixupChangedProtocol: true if the scheme is fixed up
 */
function extractScheme(uriString, fixupFlags = FIXUP_FLAG_NONE) {
  const matches = uriString.match(lazy.possibleProtocolRegex);
  const hasColon = matches?.[2] === ":";
  const hasSlash2 = matches?.[3] === "//";

  const isFixupSchemeTypos =
    lazy.fixupSchemeTypos && fixupFlags & FIXUP_FLAG_FIX_SCHEME_TYPOS;

  if (
    !matches ||
    (!hasColon && !hasSlash2) ||
    (!hasColon && !isFixupSchemeTypos)
  ) {
    return {
      scheme: "",
      fixedSchemeUriString: uriString,
      fixupChangedProtocol: false,
    };
  }

  let scheme = matches[1].replace("\t", "").toLowerCase();
  let fixedSchemeUriString = uriString;

  if (isFixupSchemeTypos && hasSlash2) {
    // Fix up typos for string that user would have intented as protocol.
    const afterProtocol = uriString.substring(matches[0].length);
    fixedSchemeUriString = `${scheme}://${afterProtocol}`;
  }

  let fixupChangedProtocol = false;

  if (isFixupSchemeTypos) {
    // Fix up common scheme typos.
    // TODO: Use levenshtein distance here?
    fixupChangedProtocol = [
      ["ttp", "http"],
      ["htp", "http"],
      ["ttps", "https"],
      ["tps", "https"],
      ["ps", "https"],
      ["htps", "https"],
      ["ile", "file"],
      ["le", "file"],
    ].some(([typo, fixed]) => {
      if (scheme === typo) {
        scheme = fixed;
        fixedSchemeUriString =
          scheme + fixedSchemeUriString.substring(typo.length);
        return true;
      }
      return false;
    });
  }

  return {
    scheme,
    fixedSchemeUriString,
    fixupChangedProtocol,
  };
}

/**
 * View-source is a pseudo scheme. We're interested in fixing up the stuff
 * after it. The easiest way to do that is to call this method again with
 * the "view-source:" lopped off and then prepend it again afterwards.
 * @param {string} uriString The original string to fixup
 * @param {integer} fixupFlags The original fixup flags
 * @param {nsIInputStream} postData Optional POST data for the search
 * @returns {object} {preferredURI, postData} The fixed URI and relative postData
 * @throws if it's not possible to fixup the url
 */
function fixupViewSource(uriString, fixupFlags) {
  // We disable keyword lookup and alternate URIs so that small typos don't
  // cause us to look at very different domains.
  let newFixupFlags = fixupFlags & ~FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP;
  let innerURIString = uriString.substring(12).trim();

  // Prevent recursion.
  const { scheme: innerScheme } = extractScheme(innerURIString);
  if (innerScheme == "view-source") {
    throw new Components.Exception(
      "Prevent view-source recursion",
      Cr.NS_ERROR_FAILURE
    );
  }

  let info = Services.uriFixup.getFixupURIInfo(innerURIString, newFixupFlags);
  if (!info.preferredURI) {
    throw new Components.Exception(
      "Couldn't build a valid uri",
      Cr.NS_ERROR_MALFORMED_URI
    );
  }
  return {
    preferredURI: Services.io.newURI("view-source:" + info.preferredURI.spec),
    postData: info.postData,
  };
}

/**
 * Fixup the host of fixedURI if it contains consecutive dots.
 * @param {URIFixupInfo} info an URIInfo object
 */
function fixupConsecutiveDotsHost(fixupInfo) {
  const uri = fixupInfo.fixedURI;

  try {
    if (!uri?.host.includes("..")) {
      return;
    }
  } catch (e) {
    return;
  }

  try {
    const isPreferredEqualsToFixed = fixupInfo.preferredURI?.equals(uri);

    fixupInfo.fixedURI = uri
      .mutate()
      .setHost(uri.host.replace(/\.+/g, "."))
      .finalize();

    if (isPreferredEqualsToFixed) {
      fixupInfo.preferredURI = fixupInfo.fixedURI;
    }
  } catch (e) {
    if (e.result !== Cr.NS_ERROR_MALFORMED_URI) {
      throw e;
    }
  }
}

/**
 * Return whether or not given string is uri like.
 * This function returns true like following strings.
 * - ":8080"
 * - "localhost:8080" (if given host is "localhost")
 * - "/foo?bar"
 * - "/foo#bar"
 * @param {string} uriString.
 * @param {string} host.
 * @param {boolean} true if uri like.
 */
function isURILike(uriString, host) {
  const indexOfSlash = uriString.indexOf("/");
  if (
    indexOfSlash >= 0 &&
    (indexOfSlash < uriString.indexOf("?", indexOfSlash) ||
      indexOfSlash < uriString.indexOf("#", indexOfSlash))
  ) {
    return true;
  }

  if (uriString.startsWith(host)) {
    uriString = uriString.substring(host.length);
  }

  return lazy.portRegex.test(uriString);
}

/**
 * Add prefix and suffix to a hostname if both are missing.
 *
 * If the host does not start with the prefix, add the prefix to
 * the hostname.
 *
 * By default the prefix and suffix are www. and .com but they could
 * be any value e.g. www. and .org as they use the preferences
 * "browser.fixup.alternate.prefix" and "browser.fixup.alternative.suffix"
 *
 * If no changes were made, it returns an empty string.
 *
 * @param {string} oldHost.
 * @return {String} Fixed up hostname or an empty string.
 */
function maybeAddPrefixAndSuffix(oldHost) {
  let prefix = Services.prefs.getCharPref(
    "browser.fixup.alternate.prefix",
    "www."
  );
  let suffix = Services.prefs.getCharPref(
    "browser.fixup.alternate.suffix",
    ".com"
  );
  let newHost = "";
  let numDots = (oldHost.match(/\./g) || []).length;
  if (numDots == 0) {
    newHost = prefix + oldHost + suffix;
  } else if (numDots == 1) {
    if (prefix && oldHost == prefix) {
      newHost = oldHost + suffix;
    } else if (suffix && !oldHost.startsWith(prefix)) {
      newHost = prefix + oldHost;
    }
  }
  return newHost ? newHost : oldHost;
}

/**
 * Given an instance of URIFixupInfo, update its fixedURI.
 *
 * First, change the protocol to the one stored in
 * "browser.fixup.alternate.protocol".
 *
 * Then, try to update fixedURI's host to newHost.
 *
 * @param {URIFixupInfo} info.
 * @param {string} newHost.
 * @return {boolean}
 *          True, if info was updated without any errors.
 *          False, if NS_ERROR_MALFORMED_URI error.
 * @throws If a non-NS_ERROR_MALFORMED_URI error occurs.
 */
function updateHostAndScheme(info, newHost) {
  let oldHost = info.fixedURI.host;
  let oldScheme = info.fixedURI.scheme;
  try {
    info.fixedURI = info.fixedURI
      .mutate()
      .setScheme(lazy.alternateProtocol)
      .setHost(newHost)
      .finalize();
  } catch (ex) {
    if (ex.result != Cr.NS_ERROR_MALFORMED_URI) {
      throw ex;
    }
    return false;
  }
  if (oldScheme != info.fixedURI.scheme) {
    info.fixupChangedProtocol = true;
  }
  if (oldHost != info.fixedURI.host) {
    info.fixupCreatedAlternateURI = true;
  }
  return true;
}
PK
!<�#t��/modules/URLDecorationAnnotationsService.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export function URLDecorationAnnotationsService() {}

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
});

const COLLECTION_NAME = "anti-tracking-url-decoration";
const PREF_NAME = "privacy.restrict3rdpartystorage.url_decorations";

URLDecorationAnnotationsService.prototype = {
  classID: Components.ID("{5874af6d-5719-4e1b-b155-ef4eae7fcb32}"),
  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsIURLDecorationAnnotationsService",
  ]),

  _initialized: false,
  _prefBranch: null,

  onDataAvailable(entries) {
    // Use this technique in order to ensure the pref cannot be changed by the
    // user e.g. through about:config.  This preferences is only intended as a
    // mechanism for reflecting this data to content processes.
    if (this._prefBranch === null) {
      this._prefBranch = Services.prefs.getDefaultBranch("");
    }

    const branch = this._prefBranch;
    branch.unlockPref(PREF_NAME);
    branch.setStringPref(
      PREF_NAME,
      entries.map(x => x.token.replace(/ /, "%20")).join(" ")
    );
    branch.lockPref(PREF_NAME);
  },

  observe(aSubject, aTopic) {
    if (aTopic == "profile-after-change") {
      this.ensureUpdated();
    }
  },

  ensureUpdated() {
    if (this._initialized) {
      return Promise.resolve();
    }
    this._initialized = true;

    const client = lazy.RemoteSettings(COLLECTION_NAME);
    client.on("sync", event => {
      let {
        data: { current },
      } = event;
      this.onDataAvailable(current);
    });

    // Now trigger an update from the server if necessary to get a fresh copy
    // of the data
    return client.get({}).then(entries => {
      this.onDataAvailable(entries);
      return undefined;
    });
  },
};
PK
!<m~�DDmodules/URLFormatter.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * @class nsURLFormatterService
 *
 * nsURLFormatterService exposes methods to substitute variables in URL formats.
 *
 * Mozilla Applications linking to Mozilla websites are strongly encouraged to use
 * URLs of the following format:
 *
 *   http[s]://%SERVICE%.mozilla.[com|org]/%LOCALE%/
 */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const PREF_APP_DISTRIBUTION = "distribution.id";
const PREF_APP_DISTRIBUTION_VERSION = "distribution.version";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Region: "resource://gre/modules/Region.sys.mjs",
  UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
});

export function nsURLFormatterService() {
  ChromeUtils.defineLazyGetter(this, "ABI", function UFS_ABI() {
    let ABI = "default";
    try {
      ABI = Services.appinfo.XPCOMABI;
    } catch (e) {}

    return ABI;
  });

  ChromeUtils.defineLazyGetter(this, "OSVersion", function UFS_OSVersion() {
    let OSVersion = "default";
    let { sysinfo } = Services;
    try {
      OSVersion =
        sysinfo.getProperty("name") + " " + sysinfo.getProperty("version");
      OSVersion += ` (${sysinfo.getProperty("secondaryLibrary")})`;
    } catch (e) {}

    return encodeURIComponent(OSVersion);
  });

  ChromeUtils.defineLazyGetter(
    this,
    "distribution",
    function UFS_distribution() {
      let defaults = Services.prefs.getDefaultBranch(null);
      let id = defaults.getCharPref(PREF_APP_DISTRIBUTION, "default");
      let version = defaults.getCharPref(
        PREF_APP_DISTRIBUTION_VERSION,
        "default"
      );

      return { id, version };
    }
  );
}

nsURLFormatterService.prototype = {
  classID: Components.ID("{e6156350-2be8-11db-a98b-0800200c9a66}"),
  QueryInterface: ChromeUtils.generateQI(["nsIURLFormatter"]),

  _defaults: {
    LOCALE: () => Services.locale.appLocaleAsBCP47,
    REGION() {
      try {
        // When the geoip lookup failed to identify the region, we fallback to
        // the 'ZZ' region code to mean 'unknown'.
        return lazy.Region.home || "ZZ";
      } catch (e) {
        return "ZZ";
      }
    },
    VENDOR() {
      return Services.appinfo.vendor;
    },
    NAME() {
      return Services.appinfo.name;
    },
    ID() {
      return Services.appinfo.ID;
    },
    VERSION() {
      return Services.appinfo.version;
    },
    MAJOR_VERSION() {
      return Services.appinfo.version.replace(
        /^([^\.]+\.[0-9]+[a-z]*).*/gi,
        "$1"
      );
    },
    APPBUILDID() {
      return Services.appinfo.appBuildID;
    },
    PLATFORMVERSION() {
      return Services.appinfo.platformVersion;
    },
    PLATFORMBUILDID() {
      return Services.appinfo.platformBuildID;
    },
    APP() {
      return Services.appinfo.name.toLowerCase().replace(/ /, "");
    },
    OS() {
      return Services.appinfo.OS;
    },
    XPCOMABI() {
      return this.ABI;
    },
    BUILD_TARGET() {
      return Services.appinfo.OS + "_" + this.ABI;
    },
    OS_VERSION() {
      return this.OSVersion;
    },
    CHANNEL: () => lazy.UpdateUtils.UpdateChannel,
    MOZILLA_API_KEY: () => AppConstants.MOZ_MOZILLA_API_KEY,
    GOOGLE_LOCATION_SERVICE_API_KEY: () =>
      AppConstants.MOZ_GOOGLE_LOCATION_SERVICE_API_KEY,
    GOOGLE_SAFEBROWSING_API_KEY: () =>
      AppConstants.MOZ_GOOGLE_SAFEBROWSING_API_KEY,
    BING_API_CLIENTID: () => AppConstants.MOZ_BING_API_CLIENTID,
    BING_API_KEY: () => AppConstants.MOZ_BING_API_KEY,
    DISTRIBUTION() {
      return this.distribution.id;
    },
    DISTRIBUTION_VERSION() {
      return this.distribution.version;
    },
  },

  formatURL: function uf_formatURL(aFormat) {
    var _this = this;
    var replacementCallback = function (aMatch, aKey) {
      if (aKey in _this._defaults) {
        return _this._defaults[aKey].call(_this);
      }
      console.error("formatURL: Couldn't find value for key: ", aKey);
      return aMatch;
    };
    return aFormat.replace(/%([A-Z_]+)%/g, replacementCallback);
  },

  formatURLPref: function uf_formatURLPref(aPref) {
    var format = null;

    try {
      format = Services.prefs.getStringPref(aPref);
    } catch (ex) {
      console.error("formatURLPref: Couldn't get pref: ", aPref);
      return "about:blank";
    }

    if (
      !Services.prefs.prefHasUserValue(aPref) &&
      /^(data:text\/plain,.+=.+|chrome:\/\/.+\/locale\/.+\.properties)$/.test(
        format
      )
    ) {
      // This looks as if it might be a localised preference
      try {
        format = Services.prefs.getComplexValue(
          aPref,
          Ci.nsIPrefLocalizedString
        ).data;
      } catch (ex) {}
    }

    return this.formatURL(format);
  },

  trimSensitiveURLs: function uf_trimSensitiveURLs(aMsg) {
    // Only the google API keys is sensitive for now.
    aMsg = AppConstants.MOZ_GOOGLE_LOCATION_SERVICE_API_KEY
      ? aMsg.replace(
          RegExp(AppConstants.MOZ_GOOGLE_LOCATION_SERVICE_API_KEY, "g"),
          "[trimmed-google-api-key]"
        )
      : aMsg;
    return AppConstants.MOZ_GOOGLE_SAFEBROWSING_API_KEY
      ? aMsg.replace(
          RegExp(AppConstants.MOZ_GOOGLE_SAFEBROWSING_API_KEY, "g"),
          "[trimmed-google-api-key]"
        )
      : aMsg;
  },
};
PK
!<�4�t�4�4,modules/URLQueryStrippingListService.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
});

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const COLLECTION_NAME = "query-stripping";
const SHARED_DATA_KEY = "URLQueryStripping";
const PREF_STRIP_LIST_NAME = "privacy.query_stripping.strip_list";
const PREF_ALLOW_LIST_NAME = "privacy.query_stripping.allow_list";
const PREF_TESTING_ENABLED = "privacy.query_stripping.testing";
const PREF_STRIP_IS_TEST =
  "privacy.query_stripping.strip_on_share.enableTestMode";

ChromeUtils.defineLazyGetter(lazy, "logger", () => {
  return console.createInstance({
    prefix: "URLQueryStrippingListService",
    maxLogLevelPref: "privacy.query_stripping.listService.logLevel",
  });
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "testStripOnShare",
  PREF_STRIP_IS_TEST
);

async function fetchList(fileName) {
  let response = await fetch(
    "chrome://global/content/antitracking/" + fileName
  );
  if (!response.ok) {
    lazy.logger.error(
      "Error fetching strip-on-share strip list" + response.status
    );
    throw new Error(
      "Error fetching strip-on-share strip list" + response.status
    );
  }
  return response.json();
}

// Lazy getter for the strip-on-share strip list.
ChromeUtils.defineLazyGetter(lazy, "StripOnShareList", async () => {
  let [stripOnShareList, stripOnShareLGPLParams] = await Promise.all([
    fetchList("StripOnShare.json"),
    fetchList("StripOnShareLGPL.json"),
  ]);

  if (!stripOnShareList || !stripOnShareLGPLParams) {
    lazy.logger.error("Error strip-on-share strip list were not loaded");
    throw new Error("Error fetching strip-on-share strip list were not loaded");
  }

  // Combines the mozilla licensed strip on share param
  // list and the LGPL licensed strip on share param list
  return combineAndParseLists(stripOnShareList, [stripOnShareLGPLParams]);
});

function combineAndParseLists(mainList, arrOfLists) {
  arrOfLists.forEach(additionalList => {
    for (let key in additionalList) {
      if (Object.hasOwn(mainList, key)) {
        mainList[key].queryParams.push(...additionalList[key].queryParams);

        mainList[key].topLevelSites.push(...additionalList[key].topLevelSites);
      } else {
        mainList[key] = additionalList[key];
      }
    }
  });

  for (let key in mainList) {
    mainList[key].queryParams = mainList[key].queryParams.map(param =>
      param.toLowerCase()
    );

    mainList[key].topLevelSites = mainList[key].topLevelSites.map(param =>
      param.toLowerCase()
    );

    // Removes duplicates topLevelSites
    mainList[key].topLevelSites = [...new Set(mainList[key].topLevelSites)];

    // Removes duplicates queryParams
    mainList[key].queryParams = [...new Set(mainList[key].queryParams)];
  }

  return mainList;
}

export class URLQueryStrippingListService {
  classId = Components.ID("{afff16f0-3fd2-4153-9ccd-c6d9abd879e4}");
  QueryInterface = ChromeUtils.generateQI(["nsIURLQueryStrippingListService"]);

  #isInitialized = false;
  #pendingInit = null;
  #initResolver;
  #stripOnShareTestList = null;

  #rs;
  #onSyncCallback;

  constructor() {
    lazy.logger.debug("constructor");
    this.observers = new Set();
    this.stripOnShareObservers = new Set();
    this.stripOnShareParams = null;
    this.prefStripList = new Set();
    this.prefAllowList = new Set();
    this.remoteStripList = new Set();
    this.remoteAllowList = new Set();
    this.isParentProcess =
      Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT;
  }

  #onSync(event) {
    lazy.logger.debug("onSync", event);
    let {
      data: { current },
    } = event;
    this._onRemoteSettingsUpdate(current);
  }

  async testSetList(testList) {
    this.#stripOnShareTestList = combineAndParseLists(testList, []);
    await this._notifyStripOnShareObservers();
  }

  testHasStripOnShareObservers() {
    return !!this.stripOnShareObservers.size;
  }

  testHasQPSObservers() {
    return !!this.observers.size;
  }

  async #init() {
    // If there is already an init pending wait for it to complete.
    if (this.#pendingInit) {
      lazy.logger.debug("#init: Waiting for pending init");
      await this.#pendingInit;
      return;
    }

    if (this.#isInitialized) {
      lazy.logger.debug("#init: Skip, already initialized");
      return;
    }
    // Create a promise that resolves when init is complete. This allows us to
    // handle incoming init calls while we're still initializing.
    this.#pendingInit = new Promise(initResolve => {
      this.#initResolver = initResolve;
    });
    this.#isInitialized = true;

    lazy.logger.debug("#init: Run");

    // We can only access the remote settings in the parent process. For content
    // processes, we will use sharedData to sync the list to content processes.
    if (this.isParentProcess) {
      this.#rs = lazy.RemoteSettings(COLLECTION_NAME);

      if (!this.#onSyncCallback) {
        this.#onSyncCallback = this.#onSync.bind(this);
        this.#rs.on("sync", this.#onSyncCallback);
      }

      // Get the initially available entries for remote settings.
      let entries;
      try {
        entries = await this.#rs.get();
      } catch (e) {}
      this._onRemoteSettingsUpdate(entries || []);
    } else {
      // Register the message listener for the remote settings update from the
      // sharedData.
      Services.cpmm.sharedData.addEventListener("change", this);

      // Get the remote settings data from the shared data.
      let data = this._getListFromSharedData();

      this._onRemoteSettingsUpdate(data);
    }

    // Get the list from pref.
    await this._onPrefUpdate(
      PREF_STRIP_LIST_NAME,
      Services.prefs.getStringPref(PREF_STRIP_LIST_NAME, "")
    );
    await this._onPrefUpdate(
      PREF_ALLOW_LIST_NAME,
      Services.prefs.getStringPref(PREF_ALLOW_LIST_NAME, "")
    );

    Services.prefs.addObserver(PREF_STRIP_LIST_NAME, this);
    Services.prefs.addObserver(PREF_ALLOW_LIST_NAME, this);

    Services.obs.addObserver(this, "xpcom-shutdown");

    this.#initResolver();
    this.#pendingInit = null;
  }

  async #shutdown() {
    // Ensure any pending init is done before shutdown.
    if (this.#pendingInit) {
      await this.#pendingInit;
    }

    // Already shut down.
    if (!this.#isInitialized) {
      return;
    }
    this.#isInitialized = false;

    lazy.logger.debug("#shutdown");

    // Unregister RemoteSettings listener (if it was registered).
    if (this.#onSyncCallback) {
      this.#rs.off("sync", this.#onSyncCallback);
      this.#onSyncCallback = null;
    }

    Services.obs.removeObserver(this, "xpcom-shutdown");
    Services.prefs.removeObserver(PREF_STRIP_LIST_NAME, this);
    Services.prefs.removeObserver(PREF_ALLOW_LIST_NAME, this);
  }

  get hasObservers() {
    return !this.observers.size && !this.stripOnShareObservers.size;
  }
  _onRemoteSettingsUpdate(entries) {
    this.remoteStripList.clear();
    this.remoteAllowList.clear();

    for (let entry of entries) {
      for (let item of entry.stripList) {
        this.remoteStripList.add(item);
      }

      for (let item of entry.allowList) {
        this.remoteAllowList.add(item);
      }
    }

    // Because only the parent process will get the remote settings update, so
    // we will sync the list to the shared data so that content processes can
    // get the list.
    if (this.isParentProcess) {
      Services.ppmm.sharedData.set(SHARED_DATA_KEY, {
        stripList: this.remoteStripList,
        allowList: this.remoteAllowList,
      });

      if (Services.prefs.getBoolPref(PREF_TESTING_ENABLED, false)) {
        Services.ppmm.sharedData.flush();
      }
    }

    this._notifyObservers();
  }

  async _onPrefUpdate(pref, value) {
    switch (pref) {
      case PREF_STRIP_LIST_NAME:
        this.prefStripList = new Set(value ? value.split(" ") : []);
        break;

      case PREF_ALLOW_LIST_NAME:
        this.prefAllowList = new Set(value ? value.split(",") : []);
        break;

      default:
        console.error(`Unexpected pref name ${pref}`);
        return;
    }

    this._notifyObservers();
    await this._notifyStripOnShareObservers();
  }

  _getListFromSharedData() {
    let data = Services.cpmm.sharedData.get(SHARED_DATA_KEY);

    return data ? [data] : [];
  }

  _notifyObservers(observer) {
    let stripEntries = new Set([
      ...this.prefStripList,
      ...this.remoteStripList,
    ]);
    let allowEntries = new Set([
      ...this.prefAllowList,
      ...this.remoteAllowList,
    ]);
    let stripEntriesAsString = Array.from(stripEntries).join(" ").toLowerCase();
    let allowEntriesAsString = Array.from(allowEntries).join(",").toLowerCase();

    let observers = observer ? [observer] : this.observers;

    if (observer || this.observers.size) {
      lazy.logger.debug("_notifyObservers", {
        observerCount: observers.length,
        runObserverAfterRegister: observer != null,
        stripEntriesAsString,
        allowEntriesAsString,
      });
    }

    for (let obs of observers) {
      obs.onQueryStrippingListUpdate(
        stripEntriesAsString,
        allowEntriesAsString
      );
    }
  }

  async _notifyStripOnShareObservers(observer) {
    this.stripOnShareParams = await lazy.StripOnShareList;

    // Changing to different test list allows us to test
    // site specific params as the websites that current have
    // site specific params cannot be opened in a test env
    if (lazy.testStripOnShare) {
      this.stripOnShareParams = this.#stripOnShareTestList;
    }

    if (!this.stripOnShareParams) {
      lazy.logger.error("StripOnShare list is undefined");
      return;
    }

    // Add the qps params to the global rules of the strip-on-share list.
    let qpsParams = [...this.prefStripList, ...this.remoteStripList].map(
      param => param.toLowerCase()
    );

    this.stripOnShareParams.global.queryParams.push(...qpsParams);
    // Getting rid of duplicates.
    this.stripOnShareParams.global.queryParams = [
      ...new Set(this.stripOnShareParams.global.queryParams),
    ];

    // Build an array of StripOnShareRules.
    let rules = Object.values(this.stripOnShareParams);
    let stringifiedRules = [];
    // We need to stringify the rules so later we can initialise WebIDL dictionaries from them.
    // The dictionaries init call needs stringified json.
    rules.forEach(rule => {
      stringifiedRules.push(JSON.stringify(rule));
    });

    let observers = observer ? new Set([observer]) : this.stripOnShareObservers;

    if (observers.size) {
      lazy.logger.debug("_notifyStripOnShareObservers", {
        observerCount: observers.size,
        runObserverAfterRegister: observer != null,
        stringifiedRules,
      });
    }
    for (let obs of observers) {
      obs.onStripOnShareUpdate(stringifiedRules);
    }
  }

  async registerAndRunObserver(observer) {
    lazy.logger.debug("registerAndRunObserver", {
      isInitialized: this.#isInitialized,
      pendingInit: this.#pendingInit,
    });

    await this.#init();
    this.observers.add(observer);
    this._notifyObservers(observer);
  }

  async registerAndRunObserverStripOnShare(observer) {
    lazy.logger.debug("registerAndRunObserverStripOnShare", {
      isInitialized: this.#isInitialized,
      pendingInit: this.#pendingInit,
    });

    await this.#init();
    this.stripOnShareObservers.add(observer);
    await this._notifyStripOnShareObservers(observer);
  }

  async unregisterObserver(observer) {
    this.observers.delete(observer);

    if (this.hasObservers) {
      lazy.logger.debug("Last observer unregistered, shutting down...");
      await this.#shutdown();
    }
  }

  async unregisterStripOnShareObserver(observer) {
    this.stripOnShareObservers.delete(observer);

    if (this.hasObservers) {
      lazy.logger.debug("Last observer unregistered, shutting down...");
      await this.#shutdown();
    }
  }

  async clearLists() {
    if (!this.isParentProcess) {
      return;
    }

    // Ensure init.
    await this.#init();

    // Clear the lists of remote settings.
    this._onRemoteSettingsUpdate([]);

    // Clear the user pref for the strip list. The pref change observer will
    // handle the rest of the work.
    Services.prefs.clearUserPref(PREF_STRIP_LIST_NAME);
    Services.prefs.clearUserPref(PREF_ALLOW_LIST_NAME);
  }

  observe(subject, topic, data) {
    lazy.logger.debug("observe", { topic, data });
    switch (topic) {
      case "xpcom-shutdown":
        this.#shutdown();
        break;
      case "nsPref:changed":
        let prefValue = Services.prefs.getStringPref(data, "");
        this._onPrefUpdate(data, prefValue);
        break;
      default:
        console.error(`Unexpected event ${topic}`);
    }
  }

  handleEvent(event) {
    if (event.type != "change") {
      return;
    }

    if (!event.changedKeys.includes(SHARED_DATA_KEY)) {
      return;
    }

    let data = this._getListFromSharedData();
    this._onRemoteSettingsUpdate(data);
    this._notifyObservers();
  }

  async testWaitForInit() {
    if (this.#pendingInit) {
      await this.#pendingInit;
    }

    return this.#isInitialized;
  }
}
PK
!<5ݚ��$modules/UntrustedModulesPing.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This module periodically sends a Telemetry ping containing information
 * about untrusted module loads on Windows.
 *
 * https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/data/third-party-modules-ping.html
 */

import { Log } from "resource://gre/modules/Log.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  TelemetryController: "resource://gre/modules/TelemetryController.sys.mjs",
  TelemetryUtils: "resource://gre/modules/TelemetryUtils.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetters(lazy, {
  UpdateTimerManager: [
    "@mozilla.org/updates/timer-manager;1",
    "nsIUpdateTimerManager",
  ],
});

const DEFAULT_INTERVAL_SECONDS = 24 * 60 * 60; // 1 day

const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetryUntrustedModulesPing::";
const TIMER_NAME = "telemetry_untrustedmodules_ping";
const PING_SUBMISSION_NAME = "third-party-modules";

export var TelemetryUntrustedModulesPing = Object.freeze({
  _log: Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX),

  start() {
    lazy.UpdateTimerManager.registerTimer(
      TIMER_NAME,
      this,
      Services.prefs.getIntPref(
        lazy.TelemetryUtils.Preferences.UntrustedModulesPingFrequency,
        DEFAULT_INTERVAL_SECONDS
      )
    );
  },

  notify() {
    try {
      Services.telemetry.getUntrustedModuleLoadEvents().then(payload => {
        try {
          if (payload) {
            lazy.TelemetryController.submitExternalPing(
              PING_SUBMISSION_NAME,
              payload,
              {
                addClientId: true,
                addEnvironment: true,
              }
            );
          }
        } catch (ex) {
          this._log.error("payload handler caught an exception", ex);
        }
      });
    } catch (ex) {
      this._log.error("notify() caught an exception", ex);
    }
  },
});
PK
!<R�'��modules/UpdatePing.sys.mjs/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Log } from "resource://gre/modules/Log.sys.mjs";

import { TelemetryUtils } from "resource://gre/modules/TelemetryUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  TelemetryController: "resource://gre/modules/TelemetryController.sys.mjs",
});

const LOGGER_NAME = "Toolkit.Telemetry";
const PING_TYPE = "update";
const UPDATE_DOWNLOADED_TOPIC = "update-downloaded";
const UPDATE_STAGED_TOPIC = "update-staged";

/**
 * This module is responsible for listening to all the relevant update
 * signals, gathering the needed information and assembling the "update"
 * ping.
 */
export var UpdatePing = {
  _enabled: false,

  earlyInit() {
    this._log = Log.repository.getLoggerWithMessagePrefix(
      LOGGER_NAME,
      "UpdatePing::"
    );
    this._enabled = Services.prefs.getBoolPref(
      TelemetryUtils.Preferences.UpdatePing,
      false
    );

    this._log.trace("init - enabled: " + this._enabled);

    if (!this._enabled) {
      return;
    }

    Services.obs.addObserver(this, UPDATE_DOWNLOADED_TOPIC);
    Services.obs.addObserver(this, UPDATE_STAGED_TOPIC);
  },

  /**
   * Generate an "update" ping with reason "success" and dispatch it
   * to the Telemetry system.
   *
   * @param {String} aPreviousVersion The browser version we updated from.
   * @param {String} aPreviousBuildId The browser build id we updated from.
   */
  handleUpdateSuccess(aPreviousVersion, aPreviousBuildId) {
    if (!this._enabled) {
      return;
    }

    this._log.trace("handleUpdateSuccess");

    // An update could potentially change the update channel. Moreover,
    // updates can only be applied if the update's channel matches with the build channel.
    // There's no way to pass this information from the caller nor the environment as,
    // in that case, the environment would report the "new" channel. However, the
    // update manager should still have information about the active update: given the
    // previous assumptions, we can simply get the channel from the update and assume
    // it matches with the state previous to the update.
    let updateManager = Cc["@mozilla.org/updates/update-manager;1"].getService(
      Ci.nsIUpdateManager
    );
    let update = updateManager ? updateManager.updateInstalledAtStartup : null;

    const payload = {
      reason: "success",
      previousChannel: update ? update.channel : null,
      previousVersion: aPreviousVersion,
      previousBuildId: aPreviousBuildId,
    };

    const options = {
      addClientId: true,
      addEnvironment: true,
      usePingSender: false,
    };

    lazy.TelemetryController.submitExternalPing(
      PING_TYPE,
      payload,
      options
    ).catch(e =>
      this._log.error("handleUpdateSuccess - failed to submit update ping", e)
    );
  },

  /**
   * Generate an "update" ping with reason "ready" and dispatch it
   * to the Telemetry system.
   *
   * @param {String} aUpdateState The state of the downloaded patch. See
   *        nsIUpdateService.idl for a list of possible values.
   */
  async _handleUpdateReady(aUpdateState) {
    const ALLOWED_STATES = [
      "applied",
      "applied-service",
      "pending",
      "pending-service",
      "pending-elevate",
    ];
    if (!ALLOWED_STATES.includes(aUpdateState)) {
      this._log.trace("Unexpected update state: " + aUpdateState);
      return;
    }

    // Get the information about the update we're going to apply from the
    // update manager.
    let updateManager = Cc["@mozilla.org/updates/update-manager;1"].getService(
      Ci.nsIUpdateManager
    );
    let update = updateManager ? await updateManager.getReadyUpdate() : null;
    if (!update) {
      this._log.trace(
        "Cannot get the update manager or no update is currently active."
      );
      return;
    }

    const payload = {
      reason: "ready",
      targetChannel: update.channel,
      targetVersion: update.appVersion,
      targetBuildId: update.buildID,
      targetDisplayVersion: update.displayVersion,
    };

    const options = {
      addClientId: true,
      addEnvironment: true,
      usePingSender: true,
    };

    lazy.TelemetryController.submitExternalPing(
      PING_TYPE,
      payload,
      options
    ).catch(e =>
      this._log.error("_handleUpdateReady - failed to submit update ping", e)
    );
  },

  /**
   * The notifications handler.
   */
  async observe(aSubject, aTopic, aData) {
    this._log.trace("observe - aTopic: " + aTopic);
    if (aTopic == UPDATE_DOWNLOADED_TOPIC || aTopic == UPDATE_STAGED_TOPIC) {
      await this._handleUpdateReady(aData);
    }
  },

  shutdown() {
    if (!this._enabled) {
      return;
    }
    Services.obs.removeObserver(this, UPDATE_DOWNLOADED_TOPIC);
    Services.obs.removeObserver(this, UPDATE_STAGED_TOPIC);
  },
};
PK
!<����o2o2"modules/UpdateTimerManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const PREF_APP_UPDATE_LASTUPDATETIME_FMT = "app.update.lastUpdateTime.%ID%";
const PREF_APP_UPDATE_TIMERMINIMUMDELAY = "app.update.timerMinimumDelay";
const PREF_APP_UPDATE_TIMERFIRSTINTERVAL = "app.update.timerFirstInterval";
const PREF_APP_UPDATE_LOG = "app.update.log";

const CATEGORY_UPDATE_TIMER = "update-timer";

const lazy = {};

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gLogEnabled",
  PREF_APP_UPDATE_LOG,
  false
);

/**
 *  Logs a string to the error console.
 *  @param   string
 *           The string to write to the error console.
 *  @param   bool
 *           Whether to log even if logging is disabled.
 */
function LOG(string, alwaysLog = false) {
  if (alwaysLog || lazy.gLogEnabled) {
    dump("*** UTM:SVC " + string + "\n");
    Services.console.logStringMessage("UTM:SVC " + string);
  }
}

/**
 *  A manager for timers. Manages timers that fire over long periods of time
 *  (e.g. days, weeks, months).
 *  @constructor
 */
export function TimerManager() {
  Services.obs.addObserver(this, "profile-before-change");
}

TimerManager.prototype = {
  /**
   * nsINamed
   */
  name: "UpdateTimerManager",

  /**
   * The Checker Timer
   */
  _timer: null,

  /**
   * The Checker Timer minimum delay interval as specified by the
   * app.update.timerMinimumDelay pref. If the app.update.timerMinimumDelay
   * pref doesn't exist this will default to 120000.
   */
  _timerMinimumDelay: null,

  /**
   * The set of registered timers.
   */
  _timers: {},

  /**
   * See nsIObserver.idl
   */
  observe: function TM_observe(aSubject, aTopic) {
    // Prevent setting the timer interval to a value of less than 30 seconds.
    var minInterval = 30000;
    // Prevent setting the first timer interval to a value of less than 10
    // seconds.
    var minFirstInterval = 10000;
    switch (aTopic) {
      case "utm-test-init":
        // Enforce a minimum timer interval of 500 ms for tests and fall through
        // to profile-after-change to initialize the timer.
        minInterval = 500;
        minFirstInterval = 500;
      // fall through
      case "profile-after-change":
        this._timerMinimumDelay = Math.max(
          1000 *
            Services.prefs.getIntPref(PREF_APP_UPDATE_TIMERMINIMUMDELAY, 120),
          minInterval
        );
        // Prevent the timer delay between notifications to other consumers from
        // being greater than 5 minutes which is 300000 milliseconds.
        this._timerMinimumDelay = Math.min(this._timerMinimumDelay, 300000);
        // Prevent the first interval from being less than the value of minFirstInterval
        let firstInterval = Math.max(
          Services.prefs.getIntPref(PREF_APP_UPDATE_TIMERFIRSTINTERVAL, 30000),
          minFirstInterval
        );
        // Prevent the first interval from being greater than 2 minutes which is
        // 120000 milliseconds.
        firstInterval = Math.min(firstInterval, 120000);
        // Cancel the timer if it has already been initialized. This is primarily
        // for tests.
        this._canEnsureTimer = true;
        this._ensureTimer(firstInterval);
        break;
      case "profile-before-change":
        Services.obs.removeObserver(this, "profile-before-change");

        // Release everything we hold onto.
        this._cancelTimer();
        for (var timerID in this._timers) {
          delete this._timers[timerID];
        }
        this._timers = null;
        break;
    }
  },

  /**
   * Called when the checking timer fires.
   *
   * We only fire one notification each time, so that the operations are
   * staggered. We don't want too many to happen at once, which could
   * negatively impact responsiveness.
   *
   * @param   timer
   *          The checking timer that fired.
   */
  notify: function TM_notify(timer) {
    var nextDelay = null;
    function updateNextDelay(delay) {
      if (nextDelay === null || delay < nextDelay) {
        nextDelay = delay;
      }
    }

    // Each timer calls tryFire(), which figures out which is the one that
    // wanted to be called earliest. That one will be fired; the others are
    // skipped and will be done later.
    var now = Math.round(Date.now() / 1000);

    var callbacksToFire = [];
    function tryFire(timerID, callback, intendedTime) {
      if (intendedTime <= now) {
        callbacksToFire.push({ timerID, callback, intendedTime });
      } else {
        updateNextDelay(intendedTime - now);
      }
    }

    for (let { value } of Services.catMan.enumerateCategory(
      CATEGORY_UPDATE_TIMER
    )) {
      let [cid, method, timerID, prefInterval, defaultInterval, maxInterval] =
        value.split(",");

      defaultInterval = parseInt(defaultInterval);
      // cid and method are validated below when calling notify.
      if (!timerID || !defaultInterval || isNaN(defaultInterval)) {
        LOG(
          "TimerManager:notify - update-timer category registered" +
            (cid ? " for " + cid : "") +
            " without required parameters - " +
            "skipping"
        );
        continue;
      }

      let interval = Services.prefs.getIntPref(prefInterval, defaultInterval);
      // Allow the update-timer category to specify a maximum value to prevent
      // values larger than desired.
      maxInterval = parseInt(maxInterval);
      if (maxInterval && !isNaN(maxInterval)) {
        interval = Math.min(interval, maxInterval);
      }
      let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(
        /%ID%/,
        timerID
      );
      // Initialize the last update time to 0 when the preference isn't set so
      // the timer will be notified soon after a new profile's first use.
      let lastUpdateTime = Services.prefs.getIntPref(prefLastUpdate, 0);

      // If the last update time is greater than the current time then reset
      // it to 0 and the timer manager will correct the value when it fires
      // next for this consumer.
      if (lastUpdateTime > now) {
        lastUpdateTime = 0;
        Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
      }

      tryFire(
        timerID,
        function () {
          ChromeUtils.idleDispatch(() => {
            try {
              let startTime = Cu.now();
              Cc[cid][method](Ci.nsITimerCallback).notify(timer);
              ChromeUtils.addProfilerMarker(
                "UpdateTimer",
                { category: "Timer", startTime },
                timerID
              );
              LOG("TimerManager:notify - notified " + cid);
            } catch (e) {
              LOG(
                "TimerManager:notify - error notifying component id: " +
                  cid +
                  " ,error: " +
                  e
              );
            }
          });
          Services.prefs.setIntPref(prefLastUpdate, now);
          updateNextDelay(interval);
        },
        lastUpdateTime + interval
      );
    }

    for (let _timerID in this._timers) {
      let timerID = _timerID; // necessary for the closure to work properly
      let timerData = this._timers[timerID];
      // If the last update time is greater than the current time then reset
      // it to 0 and the timer manager will correct the value when it fires
      // next for this consumer.
      if (timerData.lastUpdateTime > now) {
        let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(
          /%ID%/,
          timerID
        );
        timerData.lastUpdateTime = 0;
        Services.prefs.setIntPref(prefLastUpdate, timerData.lastUpdateTime);
      }
      tryFire(
        timerID,
        function () {
          if (timerData.callback && timerData.callback.notify) {
            ChromeUtils.idleDispatch(() => {
              try {
                let startTime = Cu.now();
                timerData.callback.notify(timer);
                ChromeUtils.addProfilerMarker(
                  "UpdateTimer",
                  { category: "Timer", startTime },
                  timerID
                );
                LOG(`TimerManager:notify - notified timerID: ${timerID}`);
              } catch (e) {
                LOG(
                  `TimerManager:notify - error notifying timerID: ${timerID}, error: ${e}`
                );
              }
            });
          } else {
            LOG(
              `TimerManager:notify - timerID: ${timerID} doesn't implement nsITimerCallback - skipping`
            );
          }
          timerData.lastUpdateTime = now;
          let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(
            /%ID%/,
            timerID
          );
          Services.prefs.setIntPref(prefLastUpdate, now);
          updateNextDelay(timerData.interval);
        },
        timerData.lastUpdateTime + timerData.interval
      );
    }

    if (callbacksToFire.length) {
      callbacksToFire.sort((a, b) => a.intendedTime - b.intendedTime);
      for (let { intendedTime, timerID, callback } of callbacksToFire) {
        LOG(
          `TimerManager:notify - fire timerID: ${timerID} ` +
            `intended time: ${intendedTime} (${new Date(
              intendedTime * 1000
            ).toISOString()})`
        );
        callback();
      }
    }

    if (nextDelay !== null) {
      timer.delay = Math.max(nextDelay * 1000, this._timerMinimumDelay);
      this.lastTimerReset = Date.now();
    } else {
      this._cancelTimer();
    }
  },

  /**
   * Starts the timer, if necessary, and ensures that it will fire soon enough
   * to happen after time |interval| (in milliseconds).
   */
  _ensureTimer(interval) {
    if (!this._canEnsureTimer) {
      return;
    }
    if (!this._timer) {
      this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
      this._timer.initWithCallback(
        this,
        interval,
        Ci.nsITimer.TYPE_REPEATING_SLACK
      );
      this.lastTimerReset = Date.now();
    } else if (
      Date.now() + interval <
      this.lastTimerReset + this._timer.delay
    ) {
      this._timer.delay = Math.max(
        this.lastTimerReset + interval - Date.now(),
        0
      );
    }
  },

  /**
   * Stops the timer, if it is running.
   */
  _cancelTimer() {
    if (this._timer) {
      this._timer.cancel();
      this._timer = null;
    }
  },

  /**
   * See nsIUpdateTimerManager.idl
   */
  registerTimer: function TM_registerTimer(id, callback, interval, skipFirst) {
    let markerText = `timerID: ${id} interval: ${interval}s`;
    if (skipFirst) {
      markerText += " skipFirst";
    }
    ChromeUtils.addProfilerMarker(
      "RegisterUpdateTimer",
      { category: "Timer" },
      markerText
    );
    LOG(
      `TimerManager:registerTimer - timerID: ${id} interval: ${interval} skipFirst: ${skipFirst}`
    );
    if (this._timers === null) {
      // Use normal logging since reportError is not available while shutting
      // down.
      LOG(
        "TimerManager:registerTimer called after profile-before-change " +
          "notification. Ignoring timer registration for id: " +
          id,
        true
      );
      return;
    }
    if (id in this._timers && callback != this._timers[id].callback) {
      LOG(
        "TimerManager:registerTimer - Ignoring second registration for " + id
      );
      return;
    }
    let prefLastUpdate = PREF_APP_UPDATE_LASTUPDATETIME_FMT.replace(/%ID%/, id);
    // Initialize the last update time to 0 when the preference isn't set so
    // the timer will be notified soon after a new profile's first use.
    let lastUpdateTime = Services.prefs.getIntPref(prefLastUpdate, 0);
    let now = Math.round(Date.now() / 1000);
    if (lastUpdateTime > now) {
      lastUpdateTime = 0;
    }
    if (lastUpdateTime == 0) {
      if (skipFirst) {
        lastUpdateTime = now;
      }
      Services.prefs.setIntPref(prefLastUpdate, lastUpdateTime);
    }
    this._timers[id] = { callback, interval, lastUpdateTime };

    this._ensureTimer(interval * 1000);
  },

  unregisterTimer: function TM_unregisterTimer(id) {
    ChromeUtils.addProfilerMarker(
      "UnregisterUpdateTimer",
      { category: "Timer" },
      id
    );
    LOG("TimerManager:unregisterTimer - id: " + id);
    if (id in this._timers) {
      delete this._timers[id];
    } else {
      LOG(
        "TimerManager:unregisterTimer - Ignoring unregistration request for " +
          "unknown id: " +
          id
      );
    }
  },

  classID: Components.ID("{B322A5C0-A419-484E-96BA-D7182163899F}"),
  QueryInterface: ChromeUtils.generateQI([
    "nsINamed",
    "nsIObserver",
    "nsITimerCallback",
    "nsIUpdateTimerManager",
  ]),
};
PK
!<2�ؒT�T�modules/UpdateUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs",
  WindowsVersionInfo:
    "resource://gre/modules/components-utils/WindowsVersionInfo.sys.mjs",
  ctypes: "resource://gre/modules/ctypes.sys.mjs",
});

const PER_INSTALLATION_PREFS_PLATFORMS = ["win"];

// The file that stores Application Update configuration settings. The file is
// located in the update directory which makes it a common setting across all
// application profiles and allows the Background Update Agent to read it.
const FILE_UPDATE_CONFIG_JSON = "update-config.json";
const FILE_UPDATE_LOCALE = "update.locale";
const PREF_APP_DISTRIBUTION = "distribution.id";
const PREF_APP_DISTRIBUTION_VERSION = "distribution.version";

export var UpdateUtils = {
  _locale: undefined,
  _configFilePath: undefined,

  /**
   * Read the update channel from defaults only.  We do this to ensure that
   * the channel is tightly coupled with the application and does not apply
   * to other instances of the application that may use the same profile.
   *
   * @param [optional] aIncludePartners
   *        Whether or not to include the partner bits. Default: true.
   */
  getUpdateChannel(aIncludePartners = true) {
    let defaults = Services.prefs.getDefaultBranch(null);
    let channel = defaults.getCharPref(
      "app.update.channel",
      AppConstants.MOZ_UPDATE_CHANNEL
    );

    if (aIncludePartners) {
      try {
        let partners = Services.prefs.getChildList("app.partner.").sort();
        if (partners.length) {
          channel += "-cck";
          partners.forEach(function (prefName) {
            channel += "-" + Services.prefs.getCharPref(prefName);
          });
        }
      } catch (e) {
        console.error(e);
      }
    }

    return channel;
  },

  get UpdateChannel() {
    return this.getUpdateChannel();
  },

  /**
   * Formats a URL by replacing %...% values with OS, build and locale specific
   * values.
   *
   * @param  url
   *         The URL to format.
   * @return The formatted URL.
   */
  async formatUpdateURL(url) {
    const locale = await this.getLocale();

    return url.replace(/%(\w+)%/g, (match, name) => {
      let replacement = match;
      switch (name) {
        case "PRODUCT":
          replacement = Services.appinfo.name;
          break;
        case "VERSION":
          replacement = Services.appinfo.version;
          break;
        case "BUILD_ID":
          replacement = Services.appinfo.appBuildID;
          break;
        case "BUILD_TARGET":
          replacement = Services.appinfo.OS + "_" + this.ABI;
          break;
        case "OS_VERSION":
          replacement = this.OSVersion;
          break;
        case "LOCALE":
          replacement = locale;
          break;
        case "CHANNEL":
          replacement = this.UpdateChannel;
          break;
        case "PLATFORM_VERSION":
          replacement = Services.appinfo.platformVersion;
          break;
        case "SYSTEM_CAPABILITIES":
          replacement = getSystemCapabilities();
          break;
        case "DISTRIBUTION":
          replacement = getDistributionPrefValue(PREF_APP_DISTRIBUTION);
          break;
        case "DISTRIBUTION_VERSION":
          replacement = getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION);
          break;
      }
      return encodeURIComponent(replacement);
    });
  },

  /**
   * Gets the locale from the update.locale file for replacing %LOCALE% in the
   * update url. The update.locale file can be located in the application
   * directory or the GRE directory with preference given to it being located in
   * the application directory.
   */
  async getLocale() {
    if (this._locale !== undefined) {
      return this._locale;
    }

    for (let res of ["app", "gre"]) {
      const url = "resource://" + res + "/" + FILE_UPDATE_LOCALE;
      let data;
      try {
        data = await fetch(url);
      } catch (e) {
        continue;
      }
      const locale = await data.text();
      if (locale) {
        return (this._locale = locale.trim());
      }
    }

    console.error(
      FILE_UPDATE_LOCALE,
      " file doesn't exist in either the application or GRE directories"
    );

    return (this._locale = null);
  },

  /* Get the path to the config file. */
  getConfigFilePath() {
    let path = PathUtils.join(
      Services.dirsvc.get("UpdRootD", Ci.nsIFile).path,
      FILE_UPDATE_CONFIG_JSON
    );
    return (this._configFilePath = path);
  },

  get configFilePath() {
    if (this._configFilePath !== undefined) {
      return this._configFilePath;
    }
    return this.getConfigFilePath();
  },

  /**
   * Determines whether or not the Application Update Service automatically
   * downloads and installs updates. This corresponds to whether or not the user
   * has selected "Automatically install updates" in about:preferences.
   *
   * On Windows, this setting is shared across all profiles for the installation
   * and is read asynchronously from the file. On other operating systems, this
   * setting is stored in a pref and is thus a per-profile setting.
   *
   * @return A Promise that resolves with a boolean.
   */
  async getAppUpdateAutoEnabled() {
    return this.readUpdateConfigSetting("app.update.auto");
  },

  /**
   * Toggles whether the Update Service automatically downloads and installs
   * updates. This effectively selects between the "Automatically install
   * updates" and "Check for updates but let you choose to install them" options
   * in about:preferences.
   *
   * On Windows, this setting is shared across all profiles for the installation
   * and is written asynchronously to the file. On other operating systems, this
   * setting is stored in a pref and is thus a per-profile setting.
   *
   * If this method is called when the setting is locked, the returned promise
   * will reject. The lock status can be determined with
   * UpdateUtils.appUpdateAutoSettingIsLocked()
   *
   * @param  enabled If set to true, automatic download and installation of
   *                 updates will be enabled. If set to false, this will be
   *                 disabled.
   * @return A Promise that, once the setting has been saved, resolves with the
   *         boolean value that was saved. If the setting could not be
   *         successfully saved, the Promise will reject.
   *         On Windows, where this setting is stored in a file, this Promise
   *         may reject with an I/O error.
   *         On other operating systems, this promise should not reject as
   *         this operation simply sets a pref.
   */
  async setAppUpdateAutoEnabled(enabledValue) {
    return this.writeUpdateConfigSetting("app.update.auto", !!enabledValue);
  },

  /**
   * This function should be used to determine if the automatic application
   * update setting is locked by an enterprise policy
   *
   * @return true if the automatic update setting is currently locked.
   *         Otherwise, false.
   */
  appUpdateAutoSettingIsLocked() {
    return this.appUpdateSettingIsLocked("app.update.auto");
  },

  /**
   * Indicates whether or not per-installation prefs are supported on this
   * platform.
   */
  PER_INSTALLATION_PREFS_SUPPORTED: PER_INSTALLATION_PREFS_PLATFORMS.includes(
    AppConstants.platform
  ),

  /**
   * Possible per-installation pref types.
   */
  PER_INSTALLATION_PREF_TYPE_BOOL: "boolean",
  PER_INSTALLATION_PREF_TYPE_ASCII_STRING: "ascii",
  PER_INSTALLATION_PREF_TYPE_INT: "integer",

  /**
   * We want the preference definitions to be part of UpdateUtils for a couple
   * of reasons. It's a clean way for consumers to look up things like observer
   * topic names. It also allows us to manipulate the supported prefs during
   * testing. However, we want to use values out of UpdateUtils (like pref
   * types) to construct this object. Therefore, this will initially be a
   * placeholder, which we will properly define after the UpdateUtils object
   * definition.
   */
  PER_INSTALLATION_PREFS: null,

  /**
   * This function initializes per-installation prefs. Note that it does not
   * need to be called manually; it is already called within the file.
   *
   * This function is called on startup, so it does not read or write to disk.
   */
  initPerInstallPrefs() {
    // If we don't have per-installation prefs, we store the update config in
    // preferences. In that case, the best way to notify observers of this
    // setting is just to propagate it from a pref observer. This ensures that
    // the expected observers still get notified, even if a user manually
    // changes the pref value.
    if (!UpdateUtils.PER_INSTALLATION_PREFS_SUPPORTED) {
      let initialConfig = {};
      for (const [prefName, pref] of Object.entries(
        UpdateUtils.PER_INSTALLATION_PREFS
      )) {
        const prefTypeFns = TYPE_SPECIFIC_PREF_FNS[pref.type];

        try {
          let initialValue = prefTypeFns.getProfilePref(prefName);
          initialConfig[prefName] = initialValue;
        } catch (e) {}

        Services.prefs.addObserver(prefName, async () => {
          let config = { ...gUpdateConfigCache };
          config[prefName] = await UpdateUtils.readUpdateConfigSetting(
            prefName
          );
          maybeUpdateConfigChanged(config);
        });
      }

      // On the first call to maybeUpdateConfigChanged, it has nothing to
      // compare its input to, so it just populates the cache and doesn't notify
      // any observers. This makes sense during normal usage, because the first
      // call will be on the first config file read, and we don't want to notify
      // observers of changes on the first read. But that means that when
      // propagating pref observers, we need to make one initial call to
      // simulate that initial read so that the cache will be populated when the
      // first pref observer fires.
      maybeUpdateConfigChanged(initialConfig);
    }
  },

  /**
   * Reads an installation-specific configuration setting from the update config
   * JSON file. This function is guaranteed not to throw. If there are problems
   * reading the file, the default value will be returned so that update can
   * proceed. This is particularly important since the configuration file is
   * writable by anyone and we don't want an unprivileged user to be able to
   * break update for other users.
   *
   * If relevant policies are active, this function will read the policy value
   * rather than the stored value.
   *
   * @param  prefName
   *           The preference to read. Must be a key of the
   *           PER_INSTALLATION_PREFS object.
   * @return A Promise that resolves with the pref's value.
   */
  readUpdateConfigSetting(prefName) {
    if (!(prefName in this.PER_INSTALLATION_PREFS)) {
      return Promise.reject(
        new Error(
          `UpdateUtils.readUpdateConfigSetting: Unknown per-installation ` +
            `pref '${prefName}'`
        )
      );
    }

    const pref = this.PER_INSTALLATION_PREFS[prefName];
    const prefTypeFns = TYPE_SPECIFIC_PREF_FNS[pref.type];

    if (Services.policies && "policyFn" in pref) {
      let policyValue = pref.policyFn();
      if (policyValue !== null) {
        return Promise.resolve(policyValue);
      }
    }

    if (!this.PER_INSTALLATION_PREFS_SUPPORTED) {
      // If we don't have per-installation prefs, we use regular preferences.
      let prefValue = prefTypeFns.getProfilePref(prefName, pref.defaultValue);
      return Promise.resolve(prefValue);
    }

    let readPromise = updateConfigIOPromise
      // All promises returned by (read|write)UpdateConfigSetting are part of a
      // single promise chain in order to serialize disk operations. But we
      // don't want the entire promise chain to reject when one operation fails.
      // So we are going to silently clear any rejections the promise chain
      // might contain.
      //
      // We will also pass an empty function for the first then() argument as
      // well, just to make sure we are starting fresh rather than potentially
      // propagating some stale value.
      .then(
        () => {},
        () => {}
      )
      .then(readUpdateConfig)
      .then(maybeUpdateConfigChanged)
      .then(config => {
        return readEffectiveValue(config, prefName);
      });
    updateConfigIOPromise = readPromise;
    return readPromise;
  },

  /**
   * Changes an installation-specific configuration setting by writing it to
   * the update config JSON file.
   *
   * If this method is called on a prefName that is locked, the returned promise
   * will reject. The lock status can be determined with
   * appUpdateSettingIsLocked().
   *
   * @param  prefName
   *           The preference to change. This must be a key of the
   *           PER_INSTALLATION_PREFS object.
   * @param  value
   *           The value to be written. Its type must match
   *           PER_INSTALLATION_PREFS[prefName].type
   * @param  options
   *           Optional. An object containing any of the following keys:
   *             setDefaultOnly
   *               If set to true, the default branch value will be set rather
   *               than user value. If a user value is set for this pref, this
   *               will have no effect on the pref's effective value.
   *               NOTE - The behavior of the default pref branch currently
   *                      differs depending on whether the current platform
   *                      supports per-installation prefs. If they are
   *                      supported, default branch values persist across
   *                      Firefox sessions. If they aren't supported, default
   *                      branch values reset when Firefox shuts down.
   * @return A Promise that, once the setting has been saved, resolves with the
   *         value that was saved.
   * @throw  If there is an I/O error when attempting to write to the config
   *         file, the returned Promise will reject with a DOMException.
   */
  writeUpdateConfigSetting(prefName, value, options) {
    if (!(prefName in this.PER_INSTALLATION_PREFS)) {
      return Promise.reject(
        new Error(
          `UpdateUtils.writeUpdateConfigSetting: Unknown per-installation ` +
            `pref '${prefName}'`
        )
      );
    }

    if (this.appUpdateSettingIsLocked(prefName)) {
      return Promise.reject(
        new Error(
          `UpdateUtils.writeUpdateConfigSetting: Unable to change value of ` +
            `setting '${prefName}' because it is locked by policy`
        )
      );
    }

    if (!options) {
      options = {};
    }

    const pref = this.PER_INSTALLATION_PREFS[prefName];
    const prefTypeFns = TYPE_SPECIFIC_PREF_FNS[pref.type];

    if (!prefTypeFns.isValid(value)) {
      return Promise.reject(
        new Error(
          `UpdateUtils.writeUpdateConfigSetting: Attempted to change pref ` +
            `'${prefName} to invalid value: ${JSON.stringify(value)}`
        )
      );
    }

    if (!this.PER_INSTALLATION_PREFS_SUPPORTED) {
      // If we don't have per-installation prefs, we use regular preferences.
      if (options.setDefaultOnly) {
        prefTypeFns.setProfileDefaultPref(prefName, value);
      } else {
        prefTypeFns.setProfilePref(prefName, value);
      }
      // Rather than call maybeUpdateConfigChanged, a pref observer has
      // been connected to the relevant pref. This allows us to catch direct
      // changes to prefs (which Firefox shouldn't be doing, but the user
      // might do in about:config).
      return Promise.resolve(value);
    }

    let writePromise = updateConfigIOPromise
      // All promises returned by (read|write)UpdateConfigSetting are part of a
      // single promise chain in order to serialize disk operations. But we
      // don't want the entire promise chain to reject when one operation fails.
      // So we are going to silently clear any rejections the promise chain
      // might contain.
      //
      // We will also pass an empty function for the first then() argument as
      // well, just to make sure we are starting fresh rather than potentially
      // propagating some stale value.
      .then(
        () => {},
        () => {}
      )
      // We always re-read the update config before writing, rather than using a
      // cached version. Otherwise, two simultaneous instances may overwrite
      // each other's changes.
      .then(readUpdateConfig)
      .then(async config => {
        setConfigValue(config, prefName, value, {
          setDefaultOnly: !!options.setDefaultOnly,
        });

        try {
          await writeUpdateConfig(config);
          return config;
        } catch (e) {
          console.error(
            "UpdateUtils.writeUpdateConfigSetting: App update configuration " +
              "file write failed. Exception: ",
            e
          );
          // Re-throw the error so the caller knows that writing the value in
          // the app update config file failed.
          throw e;
        }
      })
      .then(maybeUpdateConfigChanged)
      .then(() => {
        // If this value wasn't written, a previous promise in the chain will
        // have thrown, so we can unconditionally return the expected written
        // value as the value that was written.
        return value;
      });
    updateConfigIOPromise = writePromise;
    return writePromise;
  },

  /**
   * Returns true if the specified pref is controlled by policy and thus should
   * not be changeable by the user.
   */
  appUpdateSettingIsLocked(prefName) {
    if (!(prefName in UpdateUtils.PER_INSTALLATION_PREFS)) {
      return Promise.reject(
        new Error(
          `UpdateUtils.appUpdateSettingIsLocked: Unknown per-installation pref '${prefName}'`
        )
      );
    }

    // If we don't have policy support, nothing can be locked.
    if (!Services.policies) {
      return false;
    }

    const pref = UpdateUtils.PER_INSTALLATION_PREFS[prefName];
    if (!pref.policyFn) {
      return false;
    }
    const policyValue = pref.policyFn();
    return policyValue !== null;
  },
};

const PER_INSTALLATION_DEFAULTS_BRANCH = "__DEFAULTS__";

/**
 * Some prefs are specific to the installation, not the profile. They are
 * stored in JSON format in FILE_UPDATE_CONFIG_JSON.
 * Not all platforms currently support per-installation prefs, in which case
 * we fall back to using profile-specific prefs.
 *
 * Note: These prefs should always be accessed through UpdateUtils. Do NOT
 *       attempt to read or write their prefs directly.
 *
 * Keys in this object should be the name of the pref. The same name will be
 * used whether we are writing it to the per-installation or per-profile pref.
 * Values in this object should be objects with the following keys:
 *   type
 *     Must be one of the Update.PER_INSTALLATION_PREF_TYPE_* values, defined
 *     above.
 *   defaultValue
 *     The default value to use for this pref if no value is set. This must be
 *     of a type that is compatible with the type value specified.
 *   migrate
 *     Optional - defaults to false. A boolean indicating whether an existing
 *     value in the profile-specific prefs ought to be migrated to an
 *     installation specific pref. This is useful for prefs like
 *     app.update.auto that used to be profile-specific prefs.
 *     Note - Migration currently happens only on the creation of the JSON
 *            file. If we want to add more prefs that require migration, we
 *            will probably need to change this.
 *   observerTopic
 *     When a config value is changed, an observer will be fired, much like
 *     the existing preference observers. This specifies the topic of the
 *     observer that will be fired.
 *   policyFn
 *     Optional. If defined, should be a function that returns null or a value
 *     of the specified type of this pref. If null is returned, this has no
 *     effect. If another value is returned, it will be used rather than
 *     reading the pref. This function will only be called if
 *     Services.policies is defined. Asynchronous functions are not currently
 *     supported.
 */
UpdateUtils.PER_INSTALLATION_PREFS = {
  "app.update.auto": {
    type: UpdateUtils.PER_INSTALLATION_PREF_TYPE_BOOL,
    defaultValue: true,
    migrate: true,
    observerTopic: "auto-update-config-change",
    policyFn: () => {
      if (!Services.policies.isAllowed("app-auto-updates-off")) {
        // We aren't allowed to turn off auto-update - it is forced on.
        return true;
      }
      if (!Services.policies.isAllowed("app-auto-updates-on")) {
        // We aren't allowed to turn on auto-update - it is forced off.
        return false;
      }
      return null;
    },
  },
  "app.update.background.enabled": {
    type: UpdateUtils.PER_INSTALLATION_PREF_TYPE_BOOL,
    defaultValue: true,
    observerTopic: "background-update-config-change",
    policyFn: () => {
      if (!Services.policies.isAllowed("app-background-update-off")) {
        // We aren't allowed to turn off background update - it is forced on.
        return true;
      }
      if (!Services.policies.isAllowed("app-background-update-on")) {
        // We aren't allowed to turn on background update - it is forced off.
        return false;
      }
      return null;
    },
  },
};

const TYPE_SPECIFIC_PREF_FNS = {
  [UpdateUtils.PER_INSTALLATION_PREF_TYPE_BOOL]: {
    getProfilePref: Services.prefs.getBoolPref,
    setProfilePref: Services.prefs.setBoolPref,
    setProfileDefaultPref: (pref, value) => {
      let defaults = Services.prefs.getDefaultBranch("");
      defaults.setBoolPref(pref, value);
    },
    isValid: value => typeof value == "boolean",
  },
  [UpdateUtils.PER_INSTALLATION_PREF_TYPE_ASCII_STRING]: {
    getProfilePref: Services.prefs.getCharPref,
    setProfilePref: Services.prefs.setCharPref,
    setProfileDefaultPref: (pref, value) => {
      let defaults = Services.prefs.getDefaultBranch("");
      defaults.setCharPref(pref, value);
    },
    isValid: value => typeof value == "string",
  },
  [UpdateUtils.PER_INSTALLATION_PREF_TYPE_INT]: {
    getProfilePref: Services.prefs.getIntPref,
    setProfilePref: Services.prefs.setIntPref,
    setProfileDefaultPref: (pref, value) => {
      let defaults = Services.prefs.getDefaultBranch("");
      defaults.setIntPref(pref, value);
    },
    isValid: value => Number.isInteger(value),
  },
};

/**
 * Used for serializing reads and writes of the app update json config file so
 * the writes don't happen out of order and the last write is the one that
 * the sets the value.
 */
var updateConfigIOPromise = Promise.resolve();

/**
 * Returns a pref name that we will use to keep track of if the passed pref has
 * been migrated already, so we don't end up migrating it twice.
 */
function getPrefMigratedPref(prefName) {
  return prefName + ".migrated";
}

/**
 * @return true if prefs need to be migrated from profile-specific prefs to
 *         installation-specific prefs.
 */
function updateConfigNeedsMigration() {
  for (const [prefName, pref] of Object.entries(
    UpdateUtils.PER_INSTALLATION_PREFS
  )) {
    if (pref.migrate) {
      let migratedPrefName = getPrefMigratedPref(prefName);
      let migrated = Services.prefs.getBoolPref(migratedPrefName, false);
      if (!migrated) {
        return true;
      }
    }
  }
  return false;
}

function setUpdateConfigMigrationDone() {
  for (const [prefName, pref] of Object.entries(
    UpdateUtils.PER_INSTALLATION_PREFS
  )) {
    if (pref.migrate) {
      let migratedPrefName = getPrefMigratedPref(prefName);
      Services.prefs.setBoolPref(migratedPrefName, true);
    }
  }
}

/**
 * Deletes the migrated data.
 */
function onMigrationSuccessful() {
  for (const [prefName, pref] of Object.entries(
    UpdateUtils.PER_INSTALLATION_PREFS
  )) {
    if (pref.migrate) {
      Services.prefs.clearUserPref(prefName);
    }
  }
}

function makeMigrationUpdateConfig() {
  let config = makeDefaultUpdateConfig();

  for (const [prefName, pref] of Object.entries(
    UpdateUtils.PER_INSTALLATION_PREFS
  )) {
    if (!pref.migrate) {
      continue;
    }
    let migratedPrefName = getPrefMigratedPref(prefName);
    let alreadyMigrated = Services.prefs.getBoolPref(migratedPrefName, false);
    if (alreadyMigrated) {
      continue;
    }

    const prefTypeFns = TYPE_SPECIFIC_PREF_FNS[pref.type];

    let prefHasValue = true;
    let prefValue;
    try {
      // Without a second argument, this will throw if the pref has no user
      // value or default value.
      prefValue = prefTypeFns.getProfilePref(prefName);
    } catch (e) {
      prefHasValue = false;
    }
    if (prefHasValue) {
      setConfigValue(config, prefName, prefValue);
    }
  }

  return config;
}

function makeDefaultUpdateConfig() {
  let config = {};

  for (const [prefName, pref] of Object.entries(
    UpdateUtils.PER_INSTALLATION_PREFS
  )) {
    setConfigValue(config, prefName, pref.defaultValue, {
      setDefaultOnly: true,
    });
  }

  return config;
}

/**
 * Sets the specified value in the config object.
 *
 * @param  config
 *           The config object for which to set the value
 * @param  prefName
 *           The name of the preference to set.
 * @param  prefValue
 *           The value to set the preference to.
 * @param  options
 *           Optional. An object containing any of the following keys:
 *             setDefaultOnly
 *               If set to true, the default value will be set rather than
 *               user value. If a user value is set for this pref, this will
 *               have no effect on the pref's effective value.
 */
function setConfigValue(config, prefName, prefValue, options) {
  if (!options) {
    options = {};
  }

  if (options.setDefaultOnly) {
    if (!(PER_INSTALLATION_DEFAULTS_BRANCH in config)) {
      config[PER_INSTALLATION_DEFAULTS_BRANCH] = {};
    }
    config[PER_INSTALLATION_DEFAULTS_BRANCH][prefName] = prefValue;
  } else if (prefValue != readDefaultValue(config, prefName)) {
    config[prefName] = prefValue;
  } else {
    delete config[prefName];
  }
}

/**
 * Reads the specified pref out of the given configuration object.
 * If a user value of the pref is set, that will be returned. If only a default
 * branch value is set, that will be returned. Otherwise, the default value from
 * PER_INSTALLATION_PREFS will be returned.
 *
 * Values will be validated before being returned. Invalid values are ignored.
 *
 * @param  config
 *           The configuration object to read.
 * @param  prefName
 *           The name of the preference to read.
 * @return The value of the preference.
 */
function readEffectiveValue(config, prefName) {
  if (!(prefName in UpdateUtils.PER_INSTALLATION_PREFS)) {
    throw new Error(
      `readEffectiveValue: Unknown per-installation pref '${prefName}'`
    );
  }
  const pref = UpdateUtils.PER_INSTALLATION_PREFS[prefName];
  const prefTypeFns = TYPE_SPECIFIC_PREF_FNS[pref.type];

  if (prefName in config) {
    if (prefTypeFns.isValid(config[prefName])) {
      return config[prefName];
    }
    console.error(
      `readEffectiveValue: Got invalid value for update config's` +
        ` '${prefName}' value: "${config[prefName]}"`
    );
  }
  return readDefaultValue(config, prefName);
}

/**
 * Reads the default branch pref out of the given configuration object. If one
 * is not set, the default value from PER_INSTALLATION_PREFS will be returned.
 *
 * Values will be validated before being returned. Invalid values are ignored.
 *
 * @param  config
 *           The configuration object to read.
 * @param  prefName
 *           The name of the preference to read.
 * @return The value of the preference.
 */
function readDefaultValue(config, prefName) {
  if (!(prefName in UpdateUtils.PER_INSTALLATION_PREFS)) {
    throw new Error(
      `readDefaultValue: Unknown per-installation pref '${prefName}'`
    );
  }
  const pref = UpdateUtils.PER_INSTALLATION_PREFS[prefName];
  const prefTypeFns = TYPE_SPECIFIC_PREF_FNS[pref.type];

  if (PER_INSTALLATION_DEFAULTS_BRANCH in config) {
    let defaults = config[PER_INSTALLATION_DEFAULTS_BRANCH];
    if (prefName in defaults) {
      if (prefTypeFns.isValid(defaults[prefName])) {
        return defaults[prefName];
      }
      console.error(
        `readEffectiveValue: Got invalid default value for update` +
          ` config's '${prefName}' value: "${defaults[prefName]}"`
      );
    }
  }
  return pref.defaultValue;
}

/**
 * Reads the update config and, if necessary, performs migration of un-migrated
 * values. We don't want to completely give up on update if this file is
 * unavailable, so default values will be returned on failure rather than
 * throwing an error.
 *
 * @return An Update Config object.
 */
async function readUpdateConfig() {
  try {
    let config = await IOUtils.readJSON(UpdateUtils.getConfigFilePath());

    // We only migrate once. If we read something, the migration has already
    // happened so we should make sure it doesn't happen again.
    setUpdateConfigMigrationDone();

    return config;
  } catch (e) {
    if (DOMException.isInstance(e) && e.name == "NotFoundError") {
      if (updateConfigNeedsMigration()) {
        const migrationConfig = makeMigrationUpdateConfig();
        setUpdateConfigMigrationDone();
        try {
          await writeUpdateConfig(migrationConfig);
          onMigrationSuccessful();
          return migrationConfig;
        } catch (e) {
          console.error("readUpdateConfig: Migration failed: ", e);
        }
      }
    } else {
      // We only migrate once. If we got an error other than the file not
      // existing, the migration has already happened so we should make sure
      // it doesn't happen again.
      setUpdateConfigMigrationDone();

      console.error(
        "readUpdateConfig: Unable to read app update configuration file. " +
          "Exception: ",
        e
      );
    }
    return makeDefaultUpdateConfig();
  }
}

/**
 * Writes the given configuration to the disk.
 *
 * @param  config
 *           The configuration object to write.
 * @return The configuration object written.
 * @throw  A DOMException will be thrown on I/O error.
 */
async function writeUpdateConfig(config) {
  let path = UpdateUtils.getConfigFilePath();
  await IOUtils.writeJSON(path, config, { tmpPath: `${path}.tmp` });
  return config;
}

var gUpdateConfigCache;
/**
 * Notifies observers if any update config prefs have changed.
 *
 * @param  config
 *           The most up-to-date config object.
 * @return The same config object that was passed in.
 */
function maybeUpdateConfigChanged(config) {
  if (!gUpdateConfigCache) {
    // We don't want to generate a change notification for every pref on the
    // first read of the session.
    gUpdateConfigCache = config;
    return config;
  }

  for (const [prefName, pref] of Object.entries(
    UpdateUtils.PER_INSTALLATION_PREFS
  )) {
    let newPrefValue = readEffectiveValue(config, prefName);
    let oldPrefValue = readEffectiveValue(gUpdateConfigCache, prefName);
    if (newPrefValue != oldPrefValue) {
      Services.obs.notifyObservers(
        null,
        pref.observerTopic,
        newPrefValue.toString()
      );
    }
  }

  gUpdateConfigCache = config;
  return config;
}

/**
 * Note that this function sets up observers only, it does not do any I/O.
 */
UpdateUtils.initPerInstallPrefs();

/* Get the distribution pref values, from defaults only */
function getDistributionPrefValue(aPrefName) {
  let value = Services.prefs
    .getDefaultBranch(null)
    .getCharPref(aPrefName, "default");
  if (!value) {
    value = "default";
  }
  return value;
}

function getSystemCapabilities() {
  return "ISET:" + lazy.gInstructionSet + ",MEM:" + getMemoryMB();
}

/**
 * Gets the RAM size in megabytes. This will round the value because sysinfo
 * doesn't always provide RAM in multiples of 1024.
 */
function getMemoryMB() {
  let memoryMB = "unknown";
  try {
    memoryMB = Services.sysinfo.getProperty("memsize");
    if (memoryMB) {
      memoryMB = Math.round(memoryMB / 1024 / 1024);
    }
  } catch (e) {
    console.error("Error getting system info memsize property. Exception: ", e);
  }
  return memoryMB;
}

/**
 * Gets the supported CPU instruction set.
 */
ChromeUtils.defineLazyGetter(lazy, "gInstructionSet", function aus_gIS() {
  const CPU_EXTENSIONS = [
    "hasSSE4_2",
    "hasSSE4_1",
    "hasSSE4A",
    "hasSSSE3",
    "hasSSE3",
    "hasSSE2",
    "hasSSE",
    "hasMMX",
    "hasNEON",
    "hasARMv7",
    "hasARMv6",
  ];
  for (let ext of CPU_EXTENSIONS) {
    if (Services.sysinfo.getProperty(ext)) {
      return ext.substring(3);
    }
  }

  return "unknown";
});

/* Windows only getter that returns the processor architecture. */
ChromeUtils.defineLazyGetter(lazy, "gWinCPUArch", function aus_gWinCPUArch() {
  // Get processor architecture
  let arch = "unknown";

  const WORD = lazy.ctypes.uint16_t;
  const DWORD = lazy.ctypes.uint32_t;

  // This structure is described at:
  // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
  const SYSTEM_INFO = new lazy.ctypes.StructType("SYSTEM_INFO", [
    { wProcessorArchitecture: WORD },
    { wReserved: WORD },
    { dwPageSize: DWORD },
    { lpMinimumApplicationAddress: lazy.ctypes.voidptr_t },
    { lpMaximumApplicationAddress: lazy.ctypes.voidptr_t },
    { dwActiveProcessorMask: DWORD.ptr },
    { dwNumberOfProcessors: DWORD },
    { dwProcessorType: DWORD },
    { dwAllocationGranularity: DWORD },
    { wProcessorLevel: WORD },
    { wProcessorRevision: WORD },
  ]);

  let kernel32 = false;
  try {
    kernel32 = lazy.ctypes.open("Kernel32");
  } catch (e) {
    console.error("Unable to open kernel32! Exception: ", e);
  }

  if (kernel32) {
    try {
      let GetNativeSystemInfo = kernel32.declare(
        "GetNativeSystemInfo",
        lazy.ctypes.winapi_abi,
        lazy.ctypes.void_t,
        SYSTEM_INFO.ptr
      );
      let winSystemInfo = SYSTEM_INFO();
      // Default to unknown
      winSystemInfo.wProcessorArchitecture = 0xffff;

      GetNativeSystemInfo(winSystemInfo.address());
      switch (winSystemInfo.wProcessorArchitecture) {
        case 12:
          arch = "aarch64";
          break;
        case 9:
          arch = "x64";
          break;
        case 6:
          arch = "IA64";
          break;
        case 0:
          arch = "x86";
          break;
      }
    } catch (e) {
      console.error("Error getting processor architecture. Exception: ", e);
    } finally {
      kernel32.close();
    }
  }

  return arch;
});

ChromeUtils.defineLazyGetter(UpdateUtils, "ABI", function () {
  let abi = null;
  try {
    abi = Services.appinfo.XPCOMABI;
  } catch (e) {
    console.error("XPCOM ABI unknown");
  }

  if (AppConstants.platform == "win") {
    // Windows build should report the CPU architecture that it's running on.
    abi += "-" + lazy.gWinCPUArch;
  }

  if (AppConstants.ASAN) {
    // Allow ASan builds to receive their own updates
    abi += "-asan";
  }

  return abi;
});

ChromeUtils.defineLazyGetter(UpdateUtils, "OSVersion", function () {
  let osVersion;
  try {
    osVersion =
      Services.sysinfo.getProperty("name") +
      " " +
      Services.sysinfo.getProperty("version");
  } catch (e) {
    console.error("OS Version unknown.");
  }

  if (osVersion) {
    if (AppConstants.platform == "win") {
      // Add service pack and build number
      try {
        const { servicePackMajor, servicePackMinor, buildNumber } =
          lazy.WindowsVersionInfo.get();
        osVersion += `.${servicePackMajor}.${servicePackMinor}.${buildNumber}`;
      } catch (err) {
        console.error("Unable to retrieve windows version information: ", err);
        osVersion += ".unknown";
      }

      // add UBR if on Windows 10
      if (
        Services.vc.compare(Services.sysinfo.getProperty("version"), "10") >= 0
      ) {
        const WINDOWS_UBR_KEY_PATH =
          "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion";
        let ubr = lazy.WindowsRegistry.readRegKey(
          Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
          WINDOWS_UBR_KEY_PATH,
          "UBR",
          Ci.nsIWindowsRegKey.WOW64_64
        );
        if (ubr !== undefined) {
          osVersion += `.${ubr}`;
        } else {
          osVersion += ".unknown";
        }
      }

      // Add processor architecture
      osVersion += " (" + lazy.gWinCPUArch + ")";
    }

    try {
      osVersion +=
        " (" + Services.sysinfo.getProperty("secondaryLibrary") + ")";
    } catch (e) {
      // Not all platforms have a secondary widget library, so an error is nothing to worry about.
    }
    osVersion = encodeURIComponent(osVersion);
  }
  return osVersion;
});
PK
!<���p�p*modules/UrlClassifierHashCompleter.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// COMPLETE_LENGTH and PARTIAL_LENGTH copied from nsUrlClassifierDBService.h,
// they correspond to the length, in bytes, of a hash prefix and the total
// hash.
const COMPLETE_LENGTH = 32;
const PARTIAL_LENGTH = 4;

// Upper limit on the server response minimumWaitDuration
const MIN_WAIT_DURATION_MAX_VALUE = 24 * 60 * 60 * 1000;
const PREF_DEBUG_ENABLED = "browser.safebrowsing.debug";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { NetUtil } from "resource://gre/modules/NetUtil.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gDbService",
  "@mozilla.org/url-classifier/dbservice;1",
  "nsIUrlClassifierDBService"
);

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gUrlUtil",
  "@mozilla.org/url-classifier/utils;1",
  "nsIUrlClassifierUtils"
);

let loggingEnabled = false;

// Log only if browser.safebrowsing.debug is true
function log(...stuff) {
  if (!loggingEnabled) {
    return;
  }

  var d = new Date();
  let msg = "hashcompleter: " + d.toTimeString() + ": " + stuff.join(" ");
  dump(Services.urlFormatter.trimSensitiveURLs(msg) + "\n");
}

// Map the HTTP response code to a Telemetry bucket
// https://developers.google.com/safe-browsing/developers_guide_v2?hl=en
// eslint-disable-next-line complexity
function httpStatusToBucket(httpStatus) {
  var statusBucket;
  switch (httpStatus) {
    case 100:
    case 101:
      // Unexpected 1xx return code
      statusBucket = 0;
      break;
    case 200:
      // OK - Data is available in the HTTP response body.
      statusBucket = 1;
      break;
    case 201:
    case 202:
    case 203:
    case 205:
    case 206:
      // Unexpected 2xx return code
      statusBucket = 2;
      break;
    case 204:
      // No Content - There are no full-length hashes with the requested prefix.
      statusBucket = 3;
      break;
    case 300:
    case 301:
    case 302:
    case 303:
    case 304:
    case 305:
    case 307:
    case 308:
      // Unexpected 3xx return code
      statusBucket = 4;
      break;
    case 400:
      // Bad Request - The HTTP request was not correctly formed.
      // The client did not provide all required CGI parameters.
      statusBucket = 5;
      break;
    case 401:
    case 402:
    case 405:
    case 406:
    case 407:
    case 409:
    case 410:
    case 411:
    case 412:
    case 414:
    case 415:
    case 416:
    case 417:
    case 421:
    case 426:
    case 428:
    case 429:
    case 431:
    case 451:
      // Unexpected 4xx return code
      statusBucket = 6;
      break;
    case 403:
      // Forbidden - The client id is invalid.
      statusBucket = 7;
      break;
    case 404:
      // Not Found
      statusBucket = 8;
      break;
    case 408:
      // Request Timeout
      statusBucket = 9;
      break;
    case 413:
      // Request Entity Too Large - Bug 1150334
      statusBucket = 10;
      break;
    case 500:
    case 501:
    case 510:
      // Unexpected 5xx return code
      statusBucket = 11;
      break;
    case 502:
    case 504:
    case 511:
      // Local network errors, we'll ignore these.
      statusBucket = 12;
      break;
    case 503:
      // Service Unavailable - The server cannot handle the request.
      // Clients MUST follow the backoff behavior specified in the
      // Request Frequency section.
      statusBucket = 13;
      break;
    case 505:
      // HTTP Version Not Supported - The server CANNOT handle the requested
      // protocol major version.
      statusBucket = 14;
      break;
    default:
      statusBucket = 15;
  }
  return statusBucket;
}

function FullHashMatch(table, hash, duration) {
  this.tableName = table;
  this.fullHash = hash;
  this.cacheDuration = duration;
}

FullHashMatch.prototype = {
  QueryInterface: ChromeUtils.generateQI(["nsIFullHashMatch"]),

  tableName: null,
  fullHash: null,
  cacheDuration: null,
};

export function HashCompleter() {
  // The current HashCompleterRequest in flight. Once it is started, it is set
  // to null. It may be used by multiple calls to |complete| in succession to
  // avoid creating multiple requests to the same gethash URL.
  this._currentRequest = null;
  // An Array of ongoing gethash requests which is used to find requests for
  // the same hash prefix.
  this._ongoingRequests = [];
  // A map of gethashUrls to HashCompleterRequests that haven't yet begun.
  this._pendingRequests = {};

  // A map of gethash URLs to RequestBackoff objects.
  this._backoffs = {};

  // Whether we have been informed of a shutdown by the shutdown event.
  this._shuttingDown = false;

  // A map of gethash URLs to next gethash time in miliseconds
  this._nextGethashTimeMs = {};

  Services.obs.addObserver(this, "quit-application");
  Services.prefs.addObserver(PREF_DEBUG_ENABLED, this);

  loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED);
}

HashCompleter.prototype = {
  classID: Components.ID("{9111de73-9322-4bfc-8b65-2b727f3e6ec8}"),
  QueryInterface: ChromeUtils.generateQI([
    "nsIUrlClassifierHashCompleter",
    "nsIRunnable",
    "nsIObserver",
    "nsISupportsWeakReference",
    "nsITimerCallback",
  ]),

  // This is mainly how the HashCompleter interacts with other components.
  // Even though it only takes one partial hash and callback, subsequent
  // calls are made into the same HTTP request by using a thread dispatch.
  complete: function HC_complete(
    aPartialHash,
    aGethashUrl,
    aTableName,
    aCallback
  ) {
    if (!aGethashUrl) {
      throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED);
    }

    // Check ongoing requests before creating a new HashCompleteRequest
    for (let r of this._ongoingRequests) {
      if (r.find(aPartialHash, aGethashUrl, aTableName)) {
        log(
          "Merge gethash request in " +
            aTableName +
            " for prefix : " +
            btoa(aPartialHash)
        );
        r.add(aPartialHash, aCallback, aTableName);
        return;
      }
    }

    if (!this._currentRequest) {
      this._currentRequest = new HashCompleterRequest(this, aGethashUrl);
    }
    if (this._currentRequest.gethashUrl == aGethashUrl) {
      this._currentRequest.add(aPartialHash, aCallback, aTableName);
    } else {
      if (!this._pendingRequests[aGethashUrl]) {
        this._pendingRequests[aGethashUrl] = new HashCompleterRequest(
          this,
          aGethashUrl
        );
      }
      this._pendingRequests[aGethashUrl].add(
        aPartialHash,
        aCallback,
        aTableName
      );
    }

    if (!this._backoffs[aGethashUrl]) {
      // Initialize request backoffs separately, since requests are deleted
      // after they are dispatched.
      var jslib =
        Cc["@mozilla.org/url-classifier/jslib;1"].getService().wrappedJSObject;

      // Using the V4 backoff algorithm for both V2 and V4. See bug 1273398.
      this._backoffs[aGethashUrl] = new jslib.RequestBackoffV4(
        10 /* keep track of max requests */,
        0 /* don't throttle on successful requests per time period */,
        lazy.gUrlUtil.getProvider(aTableName) /* used by testcase */
      );
    }

    if (!this._nextGethashTimeMs[aGethashUrl]) {
      this._nextGethashTimeMs[aGethashUrl] = 0;
    }

    // Start off this request. Without dispatching to a thread, every call to
    // complete makes an individual HTTP request.
    Services.tm.dispatchToMainThread(this);
  },

  // This is called after several calls to |complete|, or after the
  // currentRequest has finished.  It starts off the HTTP request by making a
  // |begin| call to the HashCompleterRequest.
  run() {
    // Clear everything on shutdown
    if (this._shuttingDown) {
      this._currentRequest = null;
      this._pendingRequests = null;
      this._nextGethashTimeMs = null;

      for (var url in this._backoffs) {
        this._backoffs[url] = null;
      }
      throw Components.Exception("", Cr.NS_ERROR_NOT_INITIALIZED);
    }

    // If we don't have an in-flight request, make one
    let pendingUrls = Object.keys(this._pendingRequests);
    if (!this._currentRequest && pendingUrls.length) {
      let nextUrl = pendingUrls[0];
      this._currentRequest = this._pendingRequests[nextUrl];
      delete this._pendingRequests[nextUrl];
    }

    if (this._currentRequest) {
      try {
        if (this._currentRequest.begin()) {
          this._ongoingRequests.push(this._currentRequest);
        }
      } finally {
        // If |begin| fails, we should get rid of our request.
        this._currentRequest = null;
      }
    }
  },

  // Pass the server response status to the RequestBackoff for the given
  // gethashUrl and fetch the next pending request, if there is one.
  finishRequest(aRequest, aStatus) {
    this._ongoingRequests = this._ongoingRequests.filter(v => v != aRequest);

    this._backoffs[aRequest.gethashUrl].noteServerResponse(aStatus);
    Services.tm.dispatchToMainThread(this);
  },

  // Returns true if we can make a request from the given url, false otherwise.
  canMakeRequest(aGethashUrl) {
    return (
      this._backoffs[aGethashUrl].canMakeRequest() &&
      Date.now() >= this._nextGethashTimeMs[aGethashUrl]
    );
  },

  // Notifies the RequestBackoff of a new request so we can throttle based on
  // max requests/time period. This must be called before a channel is opened,
  // and finishRequest must be called once the response is received.
  noteRequest(aGethashUrl) {
    return this._backoffs[aGethashUrl].noteRequest();
  },

  observe: function HC_observe(aSubject, aTopic, aData) {
    switch (aTopic) {
      case "quit-application":
        this._shuttingDown = true;
        Services.obs.removeObserver(this, "quit-application");
        break;
      case "nsPref:changed":
        if (aData == PREF_DEBUG_ENABLED) {
          loggingEnabled = Services.prefs.getBoolPref(PREF_DEBUG_ENABLED);
        }
        break;
    }
  },
};

function HashCompleterRequest(aCompleter, aGethashUrl) {
  // HashCompleter object that created this HashCompleterRequest.
  this._completer = aCompleter;
  // The internal set of hashes and callbacks that this request corresponds to.
  this._requests = [];
  // nsIChannel that the hash completion query is transmitted over.
  this._channel = null;
  // Response body of hash completion. Created in onDataAvailable.
  this._response = "";
  // Whether we have been informed of a shutdown by the quit-application event.
  this._shuttingDown = false;
  this.gethashUrl = aGethashUrl;

  this.provider = "";
  // Multiple partial hashes can be associated with the same tables
  // so we use a map here.
  this.tableNames = new Map();

  this.telemetryProvider = "";
  this.telemetryClockStart = 0;
}
HashCompleterRequest.prototype = {
  QueryInterface: ChromeUtils.generateQI([
    "nsIRequestObserver",
    "nsIStreamListener",
    "nsIObserver",
  ]),

  // This is called by the HashCompleter to add a hash and callback to the
  // HashCompleterRequest. It must be called before calling |begin|.
  add: function HCR_add(aPartialHash, aCallback, aTableName) {
    this._requests.push({
      partialHash: aPartialHash,
      callback: aCallback,
      tableName: aTableName,
      response: { matches: [] },
    });

    if (aTableName) {
      let isTableNameV4 = aTableName.endsWith("-proto");
      if (0 === this.tableNames.size) {
        // Decide if this request is v4 by the first added partial hash.
        this.isV4 = isTableNameV4;
      } else if (this.isV4 !== isTableNameV4) {
        log(
          'ERROR: Cannot mix "proto" tables with other types within ' +
            "the same gethash URL."
        );
      }
      if (!this.tableNames.has(aTableName)) {
        this.tableNames.set(aTableName);
      }

      // Assuming all tables with the same gethash URL have the same provider
      if (this.provider == "") {
        this.provider = lazy.gUrlUtil.getProvider(aTableName);
      }

      if (this.telemetryProvider == "") {
        this.telemetryProvider = lazy.gUrlUtil.getTelemetryProvider(aTableName);
      }
    }
  },

  find: function HCR_find(aPartialHash, aGetHashUrl, aTableName) {
    if (this.gethashUrl != aGetHashUrl || !this.tableNames.has(aTableName)) {
      return false;
    }

    return this._requests.find(function (r) {
      return r.partialHash === aPartialHash;
    });
  },

  fillTableStatesBase64: function HCR_fillTableStatesBase64(aCallback) {
    lazy.gDbService.getTables(aTableData => {
      aTableData.split("\n").forEach(line => {
        let p = line.indexOf(";");
        if (-1 === p) {
          return;
        }
        // [tableName];[stateBase64]:[checksumBase64]
        let tableName = line.substring(0, p);
        if (this.tableNames.has(tableName)) {
          let metadata = line.substring(p + 1).split(":");
          let stateBase64 = metadata[0];
          this.tableNames.set(tableName, stateBase64);
        }
      });

      aCallback();
    });
  },

  // This initiates the HTTP request. It can fail due to backoff timings and
  // will notify all callbacks as necessary. We notify the backoff object on
  // begin.
  begin: function HCR_begin() {
    if (!this._completer.canMakeRequest(this.gethashUrl)) {
      log("Can't make request to " + this.gethashUrl + "\n");
      this.notifyFailure(Cr.NS_ERROR_ABORT);
      return false;
    }

    Services.obs.addObserver(this, "quit-application");

    // V4 requires table states to build the request so we need
    // a async call to retrieve the table states from disk.
    // Note that |HCR_begin| is fine to be sync because
    // it doesn't appear in a sync call chain.
    this.fillTableStatesBase64(() => {
      try {
        this.openChannel();
        // Notify the RequestBackoff if opening the channel succeeded. At this
        // point, finishRequest must be called.
        this._completer.noteRequest(this.gethashUrl);
      } catch (err) {
        this._completer._ongoingRequests =
          this._completer._ongoingRequests.filter(v => v != this);
        this.notifyFailure(err);
        throw err;
      }
    });

    return true;
  },

  notify: function HCR_notify() {
    // If we haven't gotten onStopRequest, just cancel. This will call us
    // with onStopRequest since we implement nsIStreamListener on the
    // channel.
    if (this._channel && this._channel.isPending()) {
      log("cancelling request to " + this.gethashUrl + " (timeout)\n");
      Services.telemetry
        .getKeyedHistogramById("URLCLASSIFIER_COMPLETE_TIMEOUT2")
        .add(this.telemetryProvider, 1);
      this._channel.cancel(Cr.NS_BINDING_ABORTED);
    }
  },

  // Creates an nsIChannel for the request and fills the body.
  // Enforce bypassing URL Classifier check because if the request is
  // blocked, it means SafeBrowsing is malfunction.
  openChannel: function HCR_openChannel() {
    let loadFlags =
      Ci.nsIChannel.INHIBIT_CACHING |
      Ci.nsIChannel.LOAD_BYPASS_CACHE |
      Ci.nsIChannel.LOAD_BYPASS_URL_CLASSIFIER;

    this.request = {
      url: this.gethashUrl,
      body: "",
    };

    if (this.isV4) {
      // As per spec, we add the request payload to the gethash url.
      this.request.url += "&$req=" + this.buildRequestV4();
    }

    log("actualGethashUrl: " + this.request.url);

    let channel = NetUtil.newChannel({
      uri: this.request.url,
      loadUsingSystemPrincipal: true,
    });
    channel.loadFlags = loadFlags;
    channel.loadInfo.originAttributes = {
      // The firstPartyDomain value should sync with NECKO_SAFEBROWSING_FIRST_PARTY_DOMAIN
      // defined in nsNetUtil.h.
      firstPartyDomain:
        "safebrowsing.86868755-6b82-4842-b301-72671a0db32e.mozilla",
    };

    // Disable keepalive.
    let httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
    httpChannel.setRequestHeader("Connection", "close", false);

    this._channel = channel;

    if (this.isV4) {
      httpChannel.setRequestHeader("X-HTTP-Method-Override", "POST", false);
    } else {
      let body = this.buildRequest();
      this.addRequestBody(body);
    }

    // Set a timer that cancels the channel after timeout_ms in case we
    // don't get a gethash response.
    this.timer_ = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    // Ask the timer to use nsITimerCallback (.notify()) when ready
    let timeout = Services.prefs.getIntPref("urlclassifier.gethash.timeout_ms");
    this.timer_.initWithCallback(this, timeout, this.timer_.TYPE_ONE_SHOT);
    channel.asyncOpen(this);
    this.telemetryClockStart = Date.now();
  },

  buildRequestV4: function HCR_buildRequestV4() {
    // Convert the "name to state" mapping to two equal-length arrays.
    let tableNameArray = [];
    let stateArray = [];
    this.tableNames.forEach((state, name) => {
      // We skip the table which is not associated with a state.
      if (state) {
        tableNameArray.push(name);
        stateArray.push(state);
      }
    });

    // Build the "distinct" prefix array.
    // The array is sorted to make sure the entries are arbitrary mixed in a
    // deterministic way
    let prefixSet = new Set();
    this._requests.forEach(r => prefixSet.add(btoa(r.partialHash)));
    let prefixArray = Array.from(prefixSet).sort();

    log(
      "Build v4 gethash request with " +
        JSON.stringify(tableNameArray) +
        ", " +
        JSON.stringify(stateArray) +
        ", " +
        JSON.stringify(prefixArray)
    );

    return lazy.gUrlUtil.makeFindFullHashRequestV4(
      tableNameArray,
      stateArray,
      prefixArray
    );
  },

  // Returns a string for the request body based on the contents of
  // this._requests.
  buildRequest: function HCR_buildRequest() {
    // Sometimes duplicate entries are sent to HashCompleter but we do not need
    // to propagate these to the server. (bug 633644)
    let prefixes = [];

    for (let i = 0; i < this._requests.length; i++) {
      let request = this._requests[i];
      if (!prefixes.includes(request.partialHash)) {
        prefixes.push(request.partialHash);
      }
    }

    // Sort to make sure the entries are arbitrary mixed in a deterministic way
    prefixes.sort();

    let body;
    body =
      PARTIAL_LENGTH +
      ":" +
      PARTIAL_LENGTH * prefixes.length +
      "\n" +
      prefixes.join("");

    log(
      "Requesting completions for " +
        prefixes.length +
        " " +
        PARTIAL_LENGTH +
        "-byte prefixes: " +
        body
    );
    return body;
  },

  // Sets the request body of this._channel.
  addRequestBody: function HCR_addRequestBody(aBody) {
    let inputStream = Cc[
      "@mozilla.org/io/string-input-stream;1"
    ].createInstance(Ci.nsIStringInputStream);

    inputStream.setData(aBody, aBody.length);

    let uploadChannel = this._channel.QueryInterface(Ci.nsIUploadChannel);
    uploadChannel.setUploadStream(inputStream, "text/plain", -1);

    let httpChannel = this._channel.QueryInterface(Ci.nsIHttpChannel);
    httpChannel.requestMethod = "POST";
  },

  // Parses the response body and eventually adds items to the |response.matches| array
  // for elements of |this._requests|.
  handleResponse: function HCR_handleResponse() {
    if (this._response == "") {
      return;
    }

    if (this.isV4) {
      this.handleResponseV4();
      return;
    }

    let start = 0;

    let length = this._response.length;
    while (start != length) {
      start = this.handleTable(start);
    }
  },

  handleResponseV4: function HCR_handleResponseV4() {
    let callback = {
      // onCompleteHashFound will be called for each fullhash found in
      // FullHashResponse.
      onCompleteHashFound: (
        aCompleteHash,
        aTableNames,
        aPerHashCacheDuration
      ) => {
        log(
          "V4 fullhash response complete hash found callback: " +
            aTableNames +
            ", CacheDuration(" +
            aPerHashCacheDuration +
            ")"
        );

        // Filter table names which we didn't requested.
        let filteredTables = aTableNames.split(",").filter(name => {
          return this.tableNames.get(name);
        });
        if (0 === filteredTables.length) {
          log("ERROR: Got complete hash which is from unknown table.");
          return;
        }
        if (filteredTables.length > 1) {
          log("WARNING: Got complete hash which has ambigious threat type.");
        }

        this.handleItem({
          completeHash: aCompleteHash,
          tableName: filteredTables[0],
          cacheDuration: aPerHashCacheDuration,
        });
      },

      // onResponseParsed will be called no matter if there is match in
      // FullHashResponse, the callback is mainly used to pass negative cache
      // duration and minimum wait duration.
      onResponseParsed: (aMinWaitDuration, aNegCacheDuration) => {
        log(
          "V4 fullhash response parsed callback: " +
            "MinWaitDuration(" +
            aMinWaitDuration +
            "), " +
            "NegativeCacheDuration(" +
            aNegCacheDuration +
            ")"
        );

        let minWaitDuration = aMinWaitDuration;

        if (aMinWaitDuration > MIN_WAIT_DURATION_MAX_VALUE) {
          log(
            "WARNING: Minimum wait duration too large, clamping it down " +
              "to a reasonable value."
          );
          minWaitDuration = MIN_WAIT_DURATION_MAX_VALUE;
        } else if (aMinWaitDuration < 0) {
          log("WARNING: Minimum wait duration is negative, reset it to 0");
          minWaitDuration = 0;
        }

        this._completer._nextGethashTimeMs[this.gethashUrl] =
          Date.now() + minWaitDuration;

        // A fullhash request may contain more than one prefix, so the negative
        // cache duration should be set for all the prefixes in the request.
        this._requests.forEach(request => {
          request.response.negCacheDuration = aNegCacheDuration;
        });
      },
    };

    lazy.gUrlUtil.parseFindFullHashResponseV4(this._response, callback);
  },

  // This parses a table entry in the response body and calls |handleItem|
  // for complete hash in the table entry.
  handleTable: function HCR_handleTable(aStart) {
    let body = this._response.substring(aStart);

    // deal with new line indexes as there could be
    // new line characters in the data parts.
    let newlineIndex = body.indexOf("\n");
    if (newlineIndex == -1) {
      throw errorWithStack();
    }
    let header = body.substring(0, newlineIndex);
    let entries = header.split(":");
    if (entries.length != 3) {
      throw errorWithStack();
    }

    let list = entries[0];
    let addChunk = parseInt(entries[1]);
    let dataLength = parseInt(entries[2]);

    log("Response includes add chunks for " + list + ": " + addChunk);
    if (
      dataLength % COMPLETE_LENGTH != 0 ||
      dataLength == 0 ||
      dataLength > body.length - (newlineIndex + 1)
    ) {
      throw errorWithStack();
    }

    let data = body.substr(newlineIndex + 1, dataLength);
    for (let i = 0; i < dataLength / COMPLETE_LENGTH; i++) {
      this.handleItem({
        completeHash: data.substr(i * COMPLETE_LENGTH, COMPLETE_LENGTH),
        tableName: list,
        chunkId: addChunk,
      });
    }

    return aStart + newlineIndex + 1 + dataLength;
  },

  // This adds a complete hash to any entry in |this._requests| that matches
  // the hash.
  handleItem: function HCR_handleItem(aData) {
    let provider = lazy.gUrlUtil.getProvider(aData.tableName);
    if (provider != this.provider) {
      log(
        "Ignoring table " +
          aData.tableName +
          " since it belongs to " +
          provider +
          " while the response came from " +
          this.provider +
          "."
      );
      return;
    }

    for (let i = 0; i < this._requests.length; i++) {
      let request = this._requests[i];
      if (aData.completeHash.startsWith(request.partialHash)) {
        request.response.matches.push(aData);
      }
    }
  },

  // notifySuccess and notifyFailure are used to alert the callbacks with
  // results. notifySuccess makes |completion| and |completionFinished| calls
  // while notifyFailure only makes a |completionFinished| call with the error
  // code.
  notifySuccess: function HCR_notifySuccess() {
    // V2 completion handler
    let completionV2 = req => {
      req.response.matches.forEach(m => {
        req.callback.completionV2(m.completeHash, m.tableName, m.chunkId);
      });

      req.callback.completionFinished(Cr.NS_OK);
    };

    // V4 completion handler
    let completionV4 = req => {
      let matches = Cc["@mozilla.org/array;1"].createInstance(
        Ci.nsIMutableArray
      );

      req.response.matches.forEach(m => {
        matches.appendElement(
          new FullHashMatch(m.tableName, m.completeHash, m.cacheDuration)
        );
      });

      req.callback.completionV4(
        req.partialHash,
        req.tableName,
        req.response.negCacheDuration,
        matches
      );

      req.callback.completionFinished(Cr.NS_OK);
    };

    let completion = this.isV4 ? completionV4 : completionV2;
    this._requests.forEach(req => {
      completion(req);
    });
  },

  notifyFailure: function HCR_notifyFailure(aStatus) {
    log("notifying failure\n");
    for (let i = 0; i < this._requests.length; i++) {
      let request = this._requests[i];
      request.callback.completionFinished(aStatus);
    }
  },

  onDataAvailable: function HCR_onDataAvailable(
    aRequest,
    aInputStream,
    aOffset,
    aCount
  ) {
    let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
      Ci.nsIScriptableInputStream
    );
    sis.init(aInputStream);
    this._response += sis.readBytes(aCount);
  },

  onStartRequest: function HCR_onStartRequest() {
    // At this point no data is available for us and we have no reason to
    // terminate the connection, so we do nothing until |onStopRequest|.
    this._completer._nextGethashTimeMs[this.gethashUrl] = 0;

    if (this.telemetryClockStart > 0) {
      let msecs = Date.now() - this.telemetryClockStart;
      Services.telemetry
        .getKeyedHistogramById("URLCLASSIFIER_COMPLETE_SERVER_RESPONSE_TIME")
        .add(this.telemetryProvider, msecs);
    }
  },

  onStopRequest: function HCR_onStopRequest(aRequest, aStatusCode) {
    Services.obs.removeObserver(this, "quit-application");

    if (this.timer_) {
      this.timer_.cancel();
      this.timer_ = null;
    }

    this.telemetryClockStart = 0;

    if (this._shuttingDown) {
      throw Components.Exception("", Cr.NS_ERROR_ABORT);
    }

    // Default HTTP status to service unavailable, in case we can't retrieve
    // the true status from the channel.
    let httpStatus = 503;
    if (Components.isSuccessCode(aStatusCode)) {
      let channel = aRequest.QueryInterface(Ci.nsIHttpChannel);
      let success = channel.requestSucceeded;
      httpStatus = channel.responseStatus;
      if (!success) {
        aStatusCode = Cr.NS_ERROR_ABORT;
      }
    }
    let success = Components.isSuccessCode(aStatusCode);
    log(
      "Received a " +
        httpStatus +
        " status code from the " +
        this.provider +
        " gethash server (success=" +
        success +
        "): " +
        btoa(this._response)
    );

    Services.telemetry
      .getKeyedHistogramById("URLCLASSIFIER_COMPLETE_REMOTE_STATUS2")
      .add(this.telemetryProvider, httpStatusToBucket(httpStatus));
    if (httpStatus == 400) {
      dump(
        "Safe Browsing server returned a 400 during completion: request= " +
          this.request.url +
          ",payload= " +
          this.request.body +
          "\n"
      );
    }

    Services.telemetry
      .getKeyedHistogramById("URLCLASSIFIER_COMPLETE_TIMEOUT2")
      .add(this.telemetryProvider, 0);

    // Notify the RequestBackoff once a response is received.
    this._completer.finishRequest(this, httpStatus);

    if (success) {
      try {
        this.handleResponse();
      } catch (err) {
        log(err.stack);
        aStatusCode = err.value;
        success = false;
      }
    }

    if (success) {
      this.notifySuccess();
    } else {
      this.notifyFailure(aStatusCode);
    }
  },

  observe: function HCR_observe(aSubject, aTopic) {
    if (aTopic == "quit-application") {
      this._shuttingDown = true;
      if (this._channel) {
        this._channel.cancel(Cr.NS_ERROR_ABORT);
        this.telemetryClockStart = 0;
      }

      Services.obs.removeObserver(this, "quit-application");
    }
  },
};

function errorWithStack() {
  let err = new Error();
  err.value = Cr.NS_ERROR_FAILURE;
  return err;
}
PK
!<�L�@AA2modules/UrlClassifierRemoteSettingsService.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
});

const COLLECTION_NAME = "tracking-protection-lists";

// SafeBrowsing protocol parameters.
export const SBRS_UPDATE_MINIMUM_DELAY = 21600; // Minimum delay before polling again in seconds

export function UrlClassifierRemoteSettingsService() {}
UrlClassifierRemoteSettingsService.prototype = {
  classID: Components.ID("{1980624c-c50b-4b46-a91c-dfaba7792706}"),
  QueryInterface: ChromeUtils.generateQI([
    "nsIUrlClassifierRemoteSettingsService",
  ]),

  _initialized: false,

  // Entries that are retrieved from RemoteSettings.get(). keyed by the table name.
  _entries: {},

  async lazyInit() {
    if (this._initialized) {
      return;
    }

    let rs = lazy.RemoteSettings(COLLECTION_NAME);
    // Bug 1750191: Notify listmanager to trigger an update instead
    // of polling data periodically.
    rs.on("sync", async event => {
      let {
        data: { current },
      } = event;
      this._onUpdateEntries(current);
    });

    this._initialized = true;

    let entries;
    try {
      entries = await rs.get();
    } catch (e) {}

    this._onUpdateEntries(entries || []);
  },

  _onUpdateEntries(aEntries) {
    aEntries.map(entry => {
      this._entries[entry.Name] = entry;
    });
  },

  // Parse the update request. See UrlClassifierListManager.sys.mjs makeUpdateRequest
  // for more details about how we build the update request.
  //
  // @param aRequest the request payload of the update request
  // @return array The array of requested tables. Each item in the array is composed
  //               with [table name, chunk numner]
  _parseRequest(aRequest) {
    let lines = aRequest.split("\n");
    let requests = [];
    for (let line of lines) {
      let fields = line.split(";");
      let chunkNum = fields[1]?.match(/(?<=a:).*/);
      requests.push([fields[0], chunkNum]);
    }
    return requests;
  },

  async _getLists(aRequest, aListener) {
    await this.lazyInit();

    let rs = lazy.RemoteSettings(COLLECTION_NAME);
    let payload = "n:" + SBRS_UPDATE_MINIMUM_DELAY + "\n";

    let requests = this._parseRequest(aRequest);
    for (let request of requests) {
      let [reqTableName, reqChunkNum] = request;
      let entry = this._entries[reqTableName];
      if (!entry?.attachment) {
        continue;
      }

      // If the request version is the same as what we have in Remote Settings,
      // we are up-to-date now.
      if (entry.Version == reqChunkNum) {
        continue;
      }

      let downloadError = false;
      try {
        // SafeBrowsing maintains its own files, so we can remove the downloaded
        // files after SafeBrowsing processes the data.
        let buffer = await rs.attachments.downloadAsBytes(entry);
        let bytes = new Uint8Array(buffer);
        let strData = "";
        for (let i = 0; i < bytes.length; i++) {
          strData += String.fromCharCode(bytes[i]);
        }

        // Construct the payload
        payload += "i:" + reqTableName + "\n";
        payload += strData;
      } catch (e) {
        downloadError = true;
      }

      if (downloadError) {
        try {
          aListener.onStartRequest(null);
          aListener.onStopRequest(null, Cr.NS_ERROR_FAILURE);
        } catch (e) {}
        return;
      }
    }

    // Send the update response over stream listener interface
    let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
      Ci.nsIStringInputStream
    );
    stream.setData(payload, payload.length);

    try {
      aListener.onStartRequest(null);
      aListener.onDataAvailable(null, stream, 0, payload.length);
      aListener.onStopRequest(null, Cr.NS_OK);
    } catch (e) {}
  },

  fetchList(aPayload, aListener) {
    this._getLists(aPayload, aListener);
  },

  clear() {
    this._initialized = false;
    this._entries = {};
  },
};
PK
!<�kW�e�e.modules/UserCharacteristicsPageService.sys.mjs// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at https://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  HiddenFrame: "resource://gre/modules/HiddenFrame.sys.mjs",
  Preferences: "resource://gre/modules/Preferences.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
});

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  return console.createInstance({
    prefix: "UserCharacteristicsPage",
    maxLogLevelPref: "toolkit.telemetry.user_characteristics_ping.logLevel",
  });
});

ChromeUtils.defineLazyGetter(lazy, "contentPrefs", () => {
  return Cc["@mozilla.org/content-pref/service;1"].getService(
    Ci.nsIContentPrefService2
  );
});

const BACKGROUND_WIDTH = 1024;
const BACKGROUND_HEIGHT = 768;

/**
 * A manager for hidden browsers. Responsible for creating and destroying a
 * hidden frame to hold them.
 * All of this is copied from PageDataService.sys.mjs
 */
class HiddenBrowserManager {
  /**
   * The hidden frame if one has been created.
   *
   * @type {HiddenFrame | null}
   */
  #frame = null;
  /**
   * The number of hidden browser elements currently in use.
   *
   * @type {number}
   */
  #browsers = 0;

  /**
   * Creates and returns a new hidden browser.
   *
   * @returns {Browser}
   */
  async #acquireBrowser() {
    this.#browsers++;
    if (!this.#frame) {
      this.#frame = new lazy.HiddenFrame();
    }

    let frame = await this.#frame.get();
    let doc = frame.document;
    let browser = doc.createXULElement("browser");
    browser.setAttribute("remote", "true");
    browser.setAttribute("type", "content");
    browser.setAttribute(
      "style",
      `
        width: ${BACKGROUND_WIDTH}px;
        min-width: ${BACKGROUND_WIDTH}px;
        height: ${BACKGROUND_HEIGHT}px;
        min-height: ${BACKGROUND_HEIGHT}px;
      `
    );
    browser.setAttribute("maychangeremoteness", "true");
    doc.documentElement.appendChild(browser);

    return browser;
  }

  /**
   * Releases the given hidden browser.
   *
   * @param {Browser} browser
   *   The hidden browser element.
   */
  #releaseBrowser(browser) {
    browser.remove();

    this.#browsers--;
    if (this.#browsers == 0) {
      this.#frame.destroy();
      this.#frame = null;
    }
  }

  /**
   * Calls a callback function with a new hidden browser.
   * This function will return whatever the callback function returns.
   *
   * @param {Callback} callback
   *   The callback function will be called with the browser element and may
   *   be asynchronous.
   * @returns {T}
   */
  async withHiddenBrowser(callback) {
    let browser = await this.#acquireBrowser();
    try {
      return await callback(browser);
    } finally {
      this.#releaseBrowser(browser);
    }
  }
}

export class UserCharacteristicsPageService {
  classId = Components.ID("{ce3e9659-e311-49fb-b18b-7f27c6659b23}");
  QueryInterface = ChromeUtils.generateQI([
    "nsIUserCharacteristicsPageService",
  ]);

  _initialized = false;
  _isParentProcess = false;

  /**
   * A manager for hidden browsers.
   *
   * @type {HiddenBrowserManager}
   */
  _browserManager = new HiddenBrowserManager();

  /**
   * A map of hidden browsers to a resolve function that should be passed the
   * actor that was created for the browser.
   *
   * @type {WeakMap<Browser, function(PageDataParent): void>}
   */
  _backgroundBrowsers = new WeakMap();

  constructor() {
    lazy.console.debug("Init");

    if (
      Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT
    ) {
      throw new Error(
        "Shouldn't init UserCharacteristicsPage in content processes."
      );
    }

    // Return if we have initiated.
    if (this._initialized) {
      lazy.console.warn("preventing re-initilization...");
      return;
    }
    this._initialized = true;
  }

  shutdown() {}

  createContentPage(principal) {
    lazy.console.debug("called createContentPage");

    lazy.console.debug("Registering actor");
    ChromeUtils.registerWindowActor("UserCharacteristics", {
      parent: {
        esModuleURI: "resource://gre/actors/UserCharacteristicsParent.sys.mjs",
      },
      child: {
        esModuleURI: "resource://gre/actors/UserCharacteristicsChild.sys.mjs",
        events: {
          UserCharacteristicsDataDone: { wantUntrusted: true },
        },
      },
      matches: ["about:fingerprintingprotection"],
      remoteTypes: ["privilegedabout"],
    });

    return this._browserManager.withHiddenBrowser(async browser => {
      lazy.console.debug(`In withHiddenBrowser`);
      try {
        let { promise, resolve } = Promise.withResolvers();
        this._backgroundBrowsers.set(browser, resolve);

        let loadURIOptions = {
          triggeringPrincipal: principal,
        };

        let userCharacteristicsPageURI = Services.io.newURI(
          "about:fingerprintingprotection" +
            (Cu.isInAutomation ? "#automation" : "")
        );

        browser.loadURI(userCharacteristicsPageURI, loadURIOptions);

        let data = await promise;
        if (data.debug) {
          lazy.console.debug(`Debugging Output:`);
          for (let line of data.debug) {
            lazy.console.debug(line);
          }
          lazy.console.debug(`(debugging output done)`);
        }
        lazy.console.debug(`Data:`, data.output);

        lazy.console.debug("Populating Glean metrics...");

        await this.populateAndCollectErrors(browser, data);

        // Notify test observers that the data has been populated.
        Services.obs.notifyObservers(
          null,
          "user-characteristics-populating-data-done"
        );
      } finally {
        lazy.console.debug("Unregistering actor");
        ChromeUtils.unregisterWindowActor("UserCharacteristics");
        this._backgroundBrowsers.delete(browser);
      }
    });
  }

  async populateAndCollectErrors(browser, data) {
    // List of functions to populate Glean metrics
    const populateFuncs = [
      [this.populateIntlLocale, []],
      [this.populateZoomPrefs, []],
      [this.populateDevicePixelRatio, [browser.ownerGlobal]],
      [this.populateDisabledMediaPrefs, []],
      [this.populateMathOps, []],
      [this.populateMapableData, [data.output]],
      [this.populateGamepads, [data.output.gamepads]],
      [this.populateClientInfo, []],
      [this.populateCPUInfo, []],
      [this.populateWindowInfo, []],
      [this.populateWebGlInfo, [browser.ownerGlobal, browser.ownerDocument]],
    ];
    // Bind them to the class and run them in parallel.
    // Timeout if any of them takes too long (5 minutes).
    const results = await Promise.allSettled(
      populateFuncs.map(([f, args]) =>
        timeoutPromise(f.bind(this)(...args), 5 * 60 * 1000)
      )
    );

    // data?.output?.errors is previous errors that happened in usercharacteristics.js
    const errors = JSON.parse(data?.output?.errors ?? "[]");
    for (const [i, [func]] of populateFuncs.entries()) {
      if (results[i].status == "rejected") {
        const error = `${func.name}: ${await stringifyError(
          results[i].reason
        )}`;
        errors.push(error);
        lazy.console.debug(error);
      }
    }

    Glean.characteristics.jsErrors.set(JSON.stringify(errors));
  }

  async collectGleanMetricsFromMap(data, operation = "set") {
    for (const [key, value] of Object.entries(data)) {
      Glean.characteristics[key][operation](value);
    }
  }

  async populateWindowInfo() {
    // We use two different methods to get any loaded document.
    // First one is, DOMContentLoaded event. If the user loads
    // a new document after actor registration, we will get it.
    // Second one is, we iterate over all open windows and tabs
    // and try to get the screen info from them.
    // The reason we do both is, for DOMContentLoaded, we can't
    // guarantee that all the documents were not loaded before the
    // actor registration.
    // We could only use the second method and add a load event
    // listener, but that assumes the user won't close already
    // existing tabs and continue on a new one before the page
    // is loaded. This is a rare case, but we want to cover it.

    if (Cu.isInAutomation) {
      // To safeguard against any possible weird empty
      // documents, we check if the document is empty. If it is
      // we wait for a valid document to be loaded.
      // During testing, we load empty.html which doesn't
      // have any body. So, we end up waiting forever.
      // Because of this, we skip this part during automation.
      return;
    }

    const { promise: screenInfoPromise, resolve: screenInfoResolve } =
      Promise.withResolvers();
    const { promise: pointerInfoPromise, resolve: pointerInfoResolve } =
      Promise.withResolvers();

    Services.obs.addObserver(function observe(_subject, topic, data) {
      Services.obs.removeObserver(observe, topic);
      screenInfoResolve(JSON.parse(data));
    }, "user-characteristics-screen-info-done");

    Services.obs.addObserver(function observe(_subject, topic, data) {
      Services.obs.removeObserver(observe, topic);
      pointerInfoResolve(JSON.parse(data));
    }, "user-characteristics-pointer-info-done");

    Services.obs.addObserver(function observe(_subject, topic, _data) {
      Services.obs.removeObserver(observe, topic);
      ChromeUtils.unregisterWindowActor("UserCharacteristicsWindowInfo");
    }, "user-characteristics-window-info-done");

    ChromeUtils.registerWindowActor("UserCharacteristicsWindowInfo", {
      parent: {
        esModuleURI: "resource://gre/actors/UserCharacteristicsParent.sys.mjs",
      },
      child: {
        esModuleURI:
          "resource://gre/actors/UserCharacteristicsWindowInfoChild.sys.mjs",
        events: {
          DOMContentLoaded: {},
        },
      },
    });

    for (const win of Services.wm.getEnumerator("navigator:browser")) {
      if (!win.closed) {
        for (const tab of win.gBrowser.tabs) {
          const actor =
            tab.linkedBrowser.browsingContext?.currentWindowGlobal.getActor(
              "UserCharacteristicsWindowInfo"
            );

          if (!actor) {
            continue;
          }

          actor.sendAsyncMessage("WindowInfo:PopulateFromDocument");
        }
      }
    }

    const screenResult = await screenInfoPromise;
    this.collectGleanMetricsFromMap(screenResult);

    const pointerResult = await pointerInfoPromise;
    this.collectGleanMetricsFromMap(pointerResult);
  }

  async populateZoomPrefs() {
    const zoomPrefsCount = await new Promise(resolve => {
      lazy.contentPrefs.getByName("browser.content.full-zoom", null, {
        _result: 0,
        handleResult(_) {
          this._result++;
        },
        handleCompletion() {
          resolve(this._result);
        },
      });
    });

    Glean.characteristics.zoomCount.set(zoomPrefsCount);
  }

  async populateDevicePixelRatio(window) {
    Glean.characteristics.pixelRatio.set(
      (window.browsingContext.overrideDPPX || window.devicePixelRatio) * 100
    );
  }

  async populateIntlLocale() {
    const locale = new Intl.DisplayNames(undefined, {
      type: "region",
    }).resolvedOptions().locale;
    Glean.characteristics.intlLocale.set(locale);
  }

  async populateGamepads(gamepads) {
    for (let gamepad of gamepads) {
      Glean.characteristics.gamepads.add(gamepad);
    }
  }

  async populateMapableData(data) {
    // We set data from usercharacteristics.js
    // We could do Object.keys(data), but this
    // is more explicit and provides better
    // readability and control.
    // Keys must match to data returned from
    // usercharacteristics.js and the metric defined
    const metrics = {
      set: [
        "canvasdata1",
        "canvasdata2",
        "canvasdata3",
        "canvasdata4",
        "canvasdata5",
        "canvasdata6",
        "canvasdata7",
        "canvasdata8",
        "canvasdata9",
        "canvasdata10",
        "canvasdata11Webgl",
        "canvasdata12Fingerprintjs1",
        "canvasdata13Fingerprintjs2",
        "canvasdata1software",
        "canvasdata2software",
        "canvasdata3software",
        "canvasdata4software",
        "canvasdata5software",
        "canvasdata6software",
        "canvasdata7software",
        "canvasdata8software",
        "canvasdata9software",
        "canvasdata10software",
        "canvasdata11Webglsoftware",
        "canvasdata12Fingerprintjs1software",
        "canvasdata13Fingerprintjs2software",
        "voices",
        "mediaCapabilities",
        "audioFingerprint",
        "jsErrors",
        "pointerType",
        "anyPointerType",
        "iceFoundations",
        "motionDecimals",
        "orientationDecimals",
        "orientationabsDecimals",
        "motionFreq",
        "orientationFreq",
        "orientationabsFreq",
        "mathml1",
        "mathml2",
        "mathml3",
        "mathml4",
        "mathml5",
        "mathml6",
        "mathml7",
        "mathml8",
        "mathml9",
        "mathml10",
        "monochrome",
        "oscpu",
        "pdfViewer",
        "platform",
        "audioFrames",
        "audioRate",
        "audioChannels",
      ],
    };

    for (const type in metrics) {
      for (const metric of metrics[type]) {
        Glean.characteristics[metric][type](data[metric]);
      }
    }
  }

  async populateMathOps() {
    // Taken from https://github.com/fingerprintjs/fingerprintjs/blob/da64ad07a9c1728af595068e4a306a4151c5d503/src/sources/math.ts
    // At the time, fingerprintjs was licensed under MIT. Slightly modified to reduce payload size.
    const ops = [
      // Native
      [Math.acos, 0.123124234234234242],
      [Math.acosh, 1e308],
      [Math.asin, 0.123124234234234242],
      [Math.asinh, 1],
      [Math.atanh, 0.5],
      [Math.atan, 0.5],
      [Math.sin, -1e300],
      [Math.sinh, 1],
      [Math.cos, 10.000000000123],
      [Math.cosh, 1],
      [Math.tan, -1e300],
      [Math.tanh, 1],
      [Math.exp, 1],
      [Math.expm1, 1],
      [Math.log1p, 10],
      // Polyfills (I'm not sure if we need polyfills since firefox seem to have all of these operations, but I'll leave it here just in case they yield different values due to chaining)
      [value => Math.pow(Math.PI, value), -100],
      [value => Math.log(value + Math.sqrt(value * value - 1)), 1e154],
      [value => Math.log(value + Math.sqrt(value * value + 1)), 1],
      [value => Math.log((1 + value) / (1 - value)) / 2, 0.5],
      [value => Math.exp(value) - 1 / Math.exp(value) / 2, 1],
      [value => (Math.exp(value) + 1 / Math.exp(value)) / 2, 1],
      [value => Math.exp(value) - 1, 1],
      [value => (Math.exp(2 * value) - 1) / (Math.exp(2 * value) + 1), 1],
      [value => Math.log(1 + value), 10],
    ].map(([op, value]) => [op || (() => 0), value]);

    Glean.characteristics.mathOps.set(
      JSON.stringify(ops.map(([op, value]) => op(value)))
    );
  }

  async populateClientInfo() {
    const buildID = Services.appinfo.appBuildID;
    const buildDate =
      new Date(
        buildID.slice(0, 4),
        buildID.slice(4, 6) - 1,
        buildID.slice(6, 8),
        buildID.slice(8, 10),
        buildID.slice(10, 12),
        buildID.slice(12, 14)
      ).getTime() / 1000;

    Glean.characteristics.version.set(Services.appinfo.version);
    Glean.characteristics.channel.set(AppConstants.MOZ_UPDATE_CHANNEL);
    Glean.characteristics.osName.set(Services.appinfo.OS);
    Glean.characteristics.osVersion.set(
      Services.sysinfo.getProperty("version")
    );
    Glean.characteristics.buildDate.set(buildDate);
  }

  async populateCPUInfo() {
    Glean.characteristics.cpuModel.set(
      await Services.sysinfo.processInfo.then(r => r.name)
    );
  }

  async populateWebGlInfo(window, document) {
    const results = {
      glVersion: 2,
      parameters: {
        v1: [],
        v2: [],
        extensions: [],
      },
      shader_precision: {
        FRAGMENT_SHADER: {},
        VERTEX_SHADER: {},
      },
      debug_shaders: {},
      debug_params: {},
    };

    const canvas = document.createElement("canvas");
    let gl = canvas.getContext("webgl2");
    if (!gl) {
      gl = canvas.getContext("webgl");
      results.glVersion = 1;
    }
    if (!gl) {
      lazy.console.error(
        "Unable to initialize WebGL. Your browser or machine may not support it."
      );
      results.glVersion = 0;
      Glean.characteristics.webglinfo.set(JSON.stringify(results));
      return;
    }

    // Some parameters are removed because they need to binded/set first.
    // We are only interested in fingerprintable parameters.
    // See https://phabricator.services.mozilla.com/D216337 for removed parameters.
    const PARAMS = {
      v1: [
        "ALIASED_LINE_WIDTH_RANGE",
        "ALIASED_POINT_SIZE_RANGE",
        "MAX_COMBINED_TEXTURE_IMAGE_UNITS",
        "MAX_CUBE_MAP_TEXTURE_SIZE",
        "MAX_FRAGMENT_UNIFORM_VECTORS",
        "MAX_RENDERBUFFER_SIZE",
        "MAX_TEXTURE_IMAGE_UNITS",
        "MAX_TEXTURE_SIZE",
        "MAX_VARYING_VECTORS",
        "MAX_VERTEX_ATTRIBS",
        "MAX_VERTEX_TEXTURE_IMAGE_UNITS",
        "MAX_VERTEX_UNIFORM_VECTORS",
        "MAX_VIEWPORT_DIMS",
        "SHADING_LANGUAGE_VERSION",
      ],
      v2: [
        "MAX_3D_TEXTURE_SIZE",
        "MAX_ARRAY_TEXTURE_LAYERS",
        "MAX_CLIENT_WAIT_TIMEOUT_WEBGL",
        "MAX_COLOR_ATTACHMENTS",
        "MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS",
        "MAX_COMBINED_UNIFORM_BLOCKS",
        "MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS",
        "MAX_DRAW_BUFFERS",
        "MAX_ELEMENT_INDEX",
        "MAX_ELEMENTS_INDICES",
        "MAX_ELEMENTS_VERTICES",
        "MAX_FRAGMENT_INPUT_COMPONENTS",
        "MAX_FRAGMENT_UNIFORM_BLOCKS",
        "MAX_FRAGMENT_UNIFORM_COMPONENTS",
        "MAX_PROGRAM_TEXEL_OFFSET",
        "MAX_SAMPLES",
        "MAX_SERVER_WAIT_TIMEOUT",
        "MAX_TEXTURE_LOD_BIAS",
        "MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS",
        "MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS",
        "MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS",
        "MAX_UNIFORM_BLOCK_SIZE",
        "MAX_UNIFORM_BUFFER_BINDINGS",
        "MAX_VARYING_COMPONENTS",
        "MAX_VERTEX_OUTPUT_COMPONENTS",
        "MAX_VERTEX_UNIFORM_BLOCKS",
        "MAX_VERTEX_UNIFORM_COMPONENTS",
        "MIN_PROGRAM_TEXEL_OFFSET",
        "UNIFORM_BUFFER_OFFSET_ALIGNMENT",
      ],
      extensions: {
        EXT_texture_filter_anisotropic: ["MAX_TEXTURE_MAX_ANISOTROPY_EXT"],
        WEBGL_draw_buffers: [
          "MAX_COLOR_ATTACHMENTS_WEBGL",
          "MAX_DRAW_BUFFERS_WEBGL",
        ],
        EXT_disjoint_timer_query: ["TIMESTAMP_EXT"],
        OVR_multiview2: ["MAX_VIEWS_OVR"],
      },
    };

    const attemptToArray = value => {
      if (ArrayBuffer.isView(value)) {
        return Array.from(value);
      }
      return value;
    };
    function getParam(param, ext = gl) {
      const constant = ext[param];
      const value = attemptToArray(gl.getParameter(constant));
      return value;
    }

    // Get all parameters available in WebGL1
    if (results.glVersion >= 1) {
      for (const parameter of PARAMS.v1) {
        results.parameters.v1.push(getParam(parameter));
      }
    }

    // Get all parameters available in WebGL2
    if (results.glVersion === 2) {
      for (const parameter of PARAMS.v2) {
        results.parameters.v2.push(getParam(parameter));
      }
    }

    // Get all extension parameters
    for (const extension in PARAMS.extensions) {
      const ext = gl.getExtension(extension);
      if (!ext) {
        results.parameters.extensions.push(null);
        continue;
      }
      results.parameters.extensions.push(
        PARAMS.extensions[extension].map(param => getParam(param, ext))
      );
    }

    for (const shaderType of ["FRAGMENT_SHADER", "VERTEX_SHADER"]) {
      for (const precisionType of [
        "LOW_FLOAT",
        "MEDIUM_FLOAT",
        "HIGH_FLOAT",
        "LOW_INT",
        "MEDIUM_INT",
        "HIGH_INT",
      ]) {
        let { rangeMin, rangeMax, precision } = gl.getShaderPrecisionFormat(
          gl[shaderType],
          gl[precisionType]
        );
        results.shader_precision[shaderType][precisionType] = {
          rangeMin,
          rangeMax,
          precision,
        };
      }
    }

    const mozDebugExt = gl.getExtension("MOZ_debug");
    const debugExt = gl.getExtension("WEBGL_debug_renderer_info");

    results.debug_params = {
      versionRaw: mozDebugExt.getParameter(gl.VERSION),
      vendorRaw: mozDebugExt.getParameter(gl.VENDOR),
      rendererRaw: mozDebugExt.getParameter(gl.RENDERER),
      extensions: gl.getSupportedExtensions().join(" "),
      extensionsRaw: mozDebugExt.getParameter(mozDebugExt.EXTENSIONS),
      vendorDebugInfo: gl.getParameter(debugExt.UNMASKED_VENDOR_WEBGL),
      rendererDebugInfo: gl.getParameter(debugExt.UNMASKED_RENDERER_WEBGL),
    };

    if (gl.getExtension("WEBGL_debug_shaders")) {
      // WEBGL_debug_shaders.getTranslatedShaderSource() produces GPU fingerprintable information

      // Taken from https://github.com/mdn/dom-examples/blob/b12b3a9e85747d3432135e6efa5bbc6581fc0774/webgl-examples/tutorial/sample3/webgl-demo.js#L29
      const vsSource = `
        attribute vec4 aVertexPosition;
        attribute vec4 aVertexColor;

        uniform mat4 uModelViewMatrix;
        uniform mat4 uProjectionMatrix;

        varying lowp vec4 vColor;

        void main(void) {
          gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
          vColor = aVertexColor;
        }
      `;

      // Taken from https://github.com/mdn/content/blob/acfe8c9f1f4145f77653a2bc64a9744b001358dc/files/en-us/web/api/webgl_api/tutorial/adding_2d_content_to_a_webgl_context/index.md?plain=1#L89
      const fsSource = `
        void main() {
          gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
        }
      `;

      const minimalSource = `void main() {}`;

      // To keep the payload small, we'll hash vsSource and fsSource, but keep minimalSource as is.
      const translationExt = gl.getExtension("WEBGL_debug_shaders");

      const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
      gl.shaderSource(fragmentShader, fsSource);
      gl.compileShader(fragmentShader);

      const vertexShader = gl.createShader(gl.VERTEX_SHADER);
      gl.shaderSource(vertexShader, vsSource);
      gl.compileShader(vertexShader);

      const minimalShader = gl.createShader(gl.FRAGMENT_SHADER);
      gl.shaderSource(minimalShader, minimalSource);
      gl.compileShader(minimalShader);

      async function sha1(message) {
        const msgUint8 = new TextEncoder().encode(message);
        const hashBuffer = await window.crypto.subtle.digest("SHA-1", msgUint8);
        const hashArray = Array.from(new Uint8Array(hashBuffer));
        const hashHex = hashArray
          .map(b => b.toString(16).padStart(2, "0"))
          .join("");
        return hashHex;
      }

      results.debug_shaders = {
        fs: await sha1(
          translationExt.getTranslatedShaderSource(fragmentShader)
        ),
        vs: await sha1(translationExt.getTranslatedShaderSource(vertexShader)),
        ms: translationExt.getTranslatedShaderSource(minimalShader),
      };
    }

    Glean.characteristics.webglinfo.set(JSON.stringify(results));
  }

  async pageLoaded(browsingContext, data) {
    lazy.console.debug(
      `pageLoaded browsingContext=${browsingContext} data=${data}`
    );

    let browser = browsingContext.embedderElement;

    let backgroundResolve = this._backgroundBrowsers.get(browser);
    if (backgroundResolve) {
      backgroundResolve(data);
      return;
    }
    throw new Error(`No backround resolve for ${browser} found`);
  }

  async populateDisabledMediaPrefs() {
    const PREFS = [
      "media.wave.enabled",
      "media.ogg.enabled",
      "media.opus.enabled",
      "media.mp4.enabled",
      "media.wmf.hevc.enabled",
      "media.webm.enabled",
      "media.av1.enabled",
      "media.encoder.webm.enabled",
      "media.mediasource.enabled",
      "media.mediasource.mp4.enabled",
      "media.mediasource.webm.enabled",
      "media.mediasource.vp9.enabled",
    ];

    const defaultPrefs = new lazy.Preferences({ defaultBranch: true });
    const changedPrefs = {};
    for (const pref of PREFS) {
      const value = lazy.Preferences.get(pref);
      if (lazy.Preferences.isSet(pref) && defaultPrefs.get(pref) !== value) {
        const key = pref.substring(6).substring(0, pref.length - 8 - 6);
        changedPrefs[key] = value;
      }
    }
    Glean.characteristics.changedMediaPrefs.set(JSON.stringify(changedPrefs));
  }
}

// =============================================================
// Utility Functions

async function stringifyError(error) {
  if (error instanceof Error) {
    const stack = (error.stack ?? "").replaceAll(
      /@chrome.+?UserCharacteristicsPageService.sys.mjs:/g,
      ""
    );
    return `${error.toString()} ${stack}`;
  }
  // A hacky attempt to extract as much as info from error
  const errStr = await (async () => {
    const asStr = await (async () => error.toString())().catch(() => "");
    const asJson = await (async () => JSON.stringify(error))().catch(() => "");
    return asStr.length > asJson.len ? asStr : asJson;
  })();
  return errStr;
}

function timeoutPromise(promise, ms) {
  return new Promise((resolve, reject) => {
    const timeoutId = lazy.setTimeout(() => {
      reject(new Error("TIMEOUT"));
    }, ms);

    promise.then(
      value => {
        lazy.clearTimeout(timeoutId);
        resolve(value);
      },
      error => {
        lazy.clearTimeout(timeoutId);
        reject(error);
      }
    );
  });
}
PK
!<?8i�� modules/UserSearchEngine.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint no-shadow: error, mozilla/no-aArgs: error */

import { SearchEngine } from "resource://gre/modules/SearchEngine.sys.mjs";

/**
 * UserSearchEngine represents a search engine defined by a user.
 */
export class UserSearchEngine extends SearchEngine {
  /**
   * Creates a UserSearchEngine.
   *
   * @param {object} options
   *   The options for this search engine.
   * @param {object} [options.details]
   *   General information about the search engine.
   * @param {string} [options.details.name]
   *   The search engine name.
   * @param {string} [options.details.url]
   *   The search url for the engine.
   * @param {string} [options.details.keyword]
   *   The keyword for the engine.
   * @param {object} [options.json]
   *   An object that represents the saved JSON settings for the engine.
   */
  constructor(options = {}) {
    super({
      loadPath: "[user]",
    });

    if (options.details) {
      this._initWithDetails({
        name: options.details.name,
        search_url: encodeURI(options.details.url),
        keyword: options.details.alias,
      });
    } else {
      this._initWithJSON(options.json);
    }
  }

  /**
   * Returns the appropriate identifier to use for telemetry.
   *
   * @returns {string}
   */
  get telemetryId() {
    return `other-${this.name}`;
  }
}
PK
!<*�44modules/ValueExtractor.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
/*
 * Helper functions extract values from manifest members
 * and reports conformance errors.
 */

export class ValueExtractor {
  constructor(errors, aBundle) {
    this.errors = errors;
    this.domBundle = aBundle;
  }

  /**
   * @param options
   *        The 'spec' object.
   * @note  This function takes a 'spec' object and destructures it to extract
   *        a value. If the value is of the wrong type, it warns the developer
   *        and returns undefined.
   *        expectedType: is the type of a JS primitive (string, number, etc.)
   *        object: is the object from which to extract the value.
   *        objectName: string used to construct the developer warning.
   *        property: the name of the property being extracted.
   *        throwTypeError: boolean, throw a TypeError if the type is incorrect.
   *        trim: boolean, if the value should be trimmed (used by string type).
   */
  extractValue(options) {
    const { expectedType, object, objectName, property, throwTypeError, trim } =
      options;
    const value = object[property];
    const isArray = Array.isArray(value);

    // We need to special-case "array", as it's not a JS primitive.
    const type = isArray ? "array" : typeof value;
    if (type !== expectedType) {
      if (type !== "undefined") {
        const warn = this.domBundle.formatStringFromName(
          "ManifestInvalidType",
          [objectName, property, expectedType]
        );
        this.errors.push({ warn });
        if (throwTypeError) {
          throw new TypeError(warn);
        }
      }
      return undefined;
    }

    // Trim string and returned undefined if the empty string.
    const shouldTrim = expectedType === "string" && value && trim;
    if (shouldTrim) {
      return value.trim() || undefined;
    }
    return value;
  }

  extractColorValue(spec) {
    const value = this.extractValue(spec);
    let color;
    if (InspectorUtils.isValidCSSColor(value)) {
      const rgba = InspectorUtils.colorToRGBA(value);
      color =
        "#" +
        rgba.r.toString(16).padStart(2, "0") +
        rgba.g.toString(16).padStart(2, "0") +
        rgba.b.toString(16).padStart(2, "0") +
        Math.round(rgba.a * 255)
          .toString(16)
          .padStart(2, "0");
    } else if (value) {
      const warn = this.domBundle.formatStringFromName(
        "ManifestInvalidCSSColor",
        [spec.property, value]
      );
      this.errors.push({ warn });
    }
    return color;
  }

  extractLanguageValue(spec) {
    let langTag;
    const value = this.extractValue(spec);
    if (value !== undefined) {
      try {
        langTag = Intl.getCanonicalLocales(value)[0];
      } catch (err) {
        const warn = this.domBundle.formatStringFromName(
          "ManifestLangIsInvalid",
          [spec.property, value]
        );
        this.errors.push({ warn });
      }
    }
    return langTag;
  }
}
PK
!<���_CCmodules/WebAuthnFeature.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import {
  LoginHelper,
  ParentAutocompleteOption,
} from "resource://gre/modules/LoginHelper.sys.mjs";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "webauthnService",
  "@mozilla.org/webauthn/service;1",
  "nsIWebAuthnService"
);

ChromeUtils.defineLazyGetter(
  lazy,
  "strings",
  () => new Localization(["browser/webauthnDialog.ftl"])
);
ChromeUtils.defineLazyGetter(lazy, "log", () =>
  LoginHelper.createLogger("WebAuthnFeature")
);

if (Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT) {
  throw new Error(
    "PasskeySupport.sys.mjs should only run in the parent process"
  );
}

class WebAuthnSupport {
  async *#getAutocompleteItemsAsync(browsingContextId, formOrigin) {
    let transactionId = lazy.webauthnService.hasPendingConditionalGet(
      browsingContextId,
      formOrigin
    );
    if (transactionId == 0) {
      // No pending transaction
      return;
    }
    let credentials = lazy.webauthnService.getAutoFillEntries(transactionId);

    let labels = credentials.map(x => ({
      id: "webauthn-specific-passkey-label",
      args: { domain: x.rpId },
    }));
    if (!credentials.length) {
      labels.push({ id: "webauthn-a-passkey-label" });
    } else {
      labels.push({ id: "webauthn-another-passkey-label" });
    }
    const formattedLabels = await lazy.strings.formatValues(labels);
    for (let i = 0; i < credentials.length; i++) {
      yield new ParentAutocompleteOption(
        "chrome://browser/content/logos/passkey.svg",
        credentials[i].userName,
        formattedLabels[i],
        "PasswordManager:promptForAuthenticator",
        {
          selection: {
            transactionId,
            credentialId: credentials[i].credentialId,
          },
        }
      );
    }
    // `getAutoFillEntries` may not return all of the credentials on the device
    // (in particular it will not include credentials with a protection policy
    // that forbids silent discovery), so we include a catch-all entry in the
    // list. If the user selects this entry, the WebAuthn transaction will
    // proceed using the modal UI.
    yield new ParentAutocompleteOption(
      "chrome://browser/content/logos/passkey.svg",
      formattedLabels[formattedLabels.length - 1],
      "",
      "PasswordManager:promptForAuthenticator",
      {
        selection: {
          transactionId,
        },
      }
    );
  }

  /**
   *
   * @param {int} browsingContextId the browsing context ID associated with this request
   * @param {string} formOrigin
   * @param {string} scenarioName can be "SignUpFormScenario" or undefined
   * @param {string} isWebAuthn indicates whether "webauthn" was included in the input's autocomplete value
   * @returns {ParentAutocompleteOption} the optional WebAuthn autocomplete item
   */
  async autocompleteItemsAsync(
    browsingContextId,
    formOrigin,
    scenarioName,
    isWebAuthn
  ) {
    const result = [];
    if (scenarioName !== "SignUpFormScenario" || isWebAuthn) {
      for await (const item of this.#getAutocompleteItemsAsync(
        browsingContextId,
        formOrigin
      )) {
        result.push(item);
      }
    }
    return result;
  }

  async promptForAuthenticator(browser, selection) {
    lazy.log.info("Prompting to authenticate with relying party.");
    if (selection.credentialId) {
      lazy.webauthnService.selectAutoFillEntry(
        selection.transactionId,
        selection.credentialId
      );
    } else {
      lazy.webauthnService.resumeConditionalGet(selection.transactionId);
    }
  }
}

export const WebAuthnFeature = new WebAuthnSupport();
PK
!<@���	$	$modules/WebChannel.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * WebChannel is an abstraction that uses the Message Manager and Custom Events
 * to create a two-way communication channel between chrome and content code.
 */

const ERRNO_UNKNOWN_ERROR = 999;
const ERROR_UNKNOWN = "UNKNOWN_ERROR";

/**
 * WebChannelBroker is a global object that helps manage WebChannel objects.
 * This object handles channel registration, origin validation and message multiplexing.
 */

export var WebChannelBroker = Object.create({
  /**
   * Register a new channel that callbacks messages
   * based on proper origin and channel name
   *
   * @param channel {WebChannel}
   */
  registerChannel(channel) {
    if (!this._channelMap.has(channel)) {
      this._channelMap.set(channel);
    } else {
      console.error("Failed to register the channel. Channel already exists.");
    }
  },

  /**
   * Unregister a channel
   *
   * @param channelToRemove {WebChannel}
   *        WebChannel to remove from the channel map
   *
   * Removes the specified channel from the channel map
   */
  unregisterChannel(channelToRemove) {
    if (!this._channelMap.delete(channelToRemove)) {
      console.error("Failed to unregister the channel. Channel not found.");
    }
  },

  /**
   * Object to store pairs of message origins and callback functions
   */
  _channelMap: new Map(),

  /**
   * Deliver a message to a registered channel.
   *
   * @returns bool whether we managed to find a registered channel.
   */
  tryToDeliver(data, sendingContext) {
    let validChannelFound = false;
    data.message = data.message || {};

    for (var channel of this._channelMap.keys()) {
      if (
        channel.id === data.id &&
        channel._originCheckCallback(sendingContext.principal)
      ) {
        validChannelFound = true;
        channel.deliver(data, sendingContext);
      }
    }
    return validChannelFound;
  },
});

/**
 * Creates a new WebChannel that listens and sends messages over some channel id
 *
 * @param id {String}
 *        WebChannel id
 * @param originOrPermission {nsIURI/string}
 *        If an nsIURI, incoming events will be accepted from any origin matching
 *        that URI's origin.
 *        If a string, it names a permission, and incoming events will be accepted
 *        from any https:// origin that has been granted that permission by the
 *        permission manager.
 * @constructor
 */
export var WebChannel = function (id, originOrPermission) {
  if (!id || !originOrPermission) {
    throw new Error("WebChannel id and originOrPermission are required.");
  }

  this.id = id;
  // originOrPermission can be either an nsIURI or a string representing a
  // permission name.
  if (typeof originOrPermission == "string") {
    this._originCheckCallback = requestPrincipal => {
      // Accept events from any secure origin having the named permission.
      // The permission manager operates on domain names rather than true
      // origins (bug 1066517).  To mitigate that, we explicitly check that
      // the scheme is https://.
      let uri = Services.io.newURI(requestPrincipal.originNoSuffix);
      if (uri.scheme != "https") {
        return false;
      }
      // OK - we have https - now we can check the permission.
      let perm = Services.perms.testExactPermissionFromPrincipal(
        requestPrincipal,
        originOrPermission
      );
      return perm == Ci.nsIPermissionManager.ALLOW_ACTION;
    };
  } else {
    // Accept events from any origin matching the given URI.
    // We deliberately use `originNoSuffix` here because we only want to
    // restrict based on the site's origin, not on other origin attributes
    // such as containers or private browsing.
    this._originCheckCallback = requestPrincipal => {
      return originOrPermission.prePath === requestPrincipal.originNoSuffix;
    };
  }
  this._originOrPermission = originOrPermission;
};

WebChannel.prototype = {
  /**
   * WebChannel id
   */
  id: null,

  /**
   * The originOrPermission value passed to the constructor, mainly for
   * debugging and tests.
   */
  _originOrPermission: null,

  /**
   * Callback that will be called with the principal of an incoming message
   * to check if the request should be dispatched to the listeners.
   */
  _originCheckCallback: null,

  /**
   * WebChannelBroker that manages WebChannels
   */
  _broker: WebChannelBroker,

  /**
   * Callback that will be called with the contents of an incoming message
   */
  _deliverCallback: null,

  /**
   * Registers the callback for messages on this channel
   * Registers the channel itself with the WebChannelBroker
   *
   * @param callback {Function}
   *        Callback that will be called when there is a message
   *        @param {String} id
   *        The WebChannel id that was used for this message
   *        @param {Object} message
   *        The message itself
   *        @param sendingContext {Object}
   *        The sending context of the source of the message. Can be passed to
   *        `send` to respond to a message.
   *               @param sendingContext.browser {browser}
   *                      The <browser> object that captured the
   *                      WebChannelMessageToChrome.
   *               @param sendingContext.eventTarget {EventTarget}
   *                      The <EventTarget> where the message was sent.
   *               @param sendingContext.principal {Principal}
   *                      The <Principal> of the EventTarget where the
   *                      message was sent.
   */
  listen(callback) {
    if (this._deliverCallback) {
      throw new Error("Failed to listen. Listener already attached.");
    } else if (!callback) {
      throw new Error("Failed to listen. Callback argument missing.");
    } else {
      this._deliverCallback = callback;
      this._broker.registerChannel(this);
    }
  },

  /**
   * Resets the callback for messages on this channel
   * Removes the channel from the WebChannelBroker
   */
  stopListening() {
    this._broker.unregisterChannel(this);
    this._deliverCallback = null;
  },

  /**
   * Sends messages over the WebChannel id using the "WebChannelMessageToContent" event
   *
   * @param message {Object}
   *        The message object that will be sent
   * @param target {Object}
   *        A <target> with the information of where to send the message.
   *        @param target.browsingContext {BrowsingContext}
   *               The browsingContext we should send the message to.
   *        @param target.principal {Principal}
   *               Principal of the target. Prevents messages from
   *               being dispatched to unexpected origins. The system principal
   *               can be specified to send to any target.
   *        @param [target.eventTarget] {EventTarget}
   *               Optional eventTarget within the browser, use to send to a
   *               specific element. Can be null; if not null, should be
   *               a ContentDOMReference.
   */
  send(message, target) {
    let { browsingContext, principal, eventTarget } = target;

    if (message && browsingContext && principal) {
      let { currentWindowGlobal } = browsingContext;
      if (!currentWindowGlobal) {
        console.error(
          "Failed to send a WebChannel message. No currentWindowGlobal."
        );
        return;
      }
      currentWindowGlobal
        .getActor("WebChannel")
        .sendAsyncMessage("WebChannelMessageToContent", {
          id: this.id,
          message,
          eventTarget,
          principal,
        });
    } else if (!message) {
      console.error("Failed to send a WebChannel message. Message not set.");
    } else {
      console.error("Failed to send a WebChannel message. Target invalid.");
    }
  },

  /**
   * Deliver WebChannel messages to the set "_channelCallback"
   *
   * @param data {Object}
   *        Message data
   * @param sendingContext {Object}
   *        Message sending context.
   *        @param sendingContext.browsingContext {BrowsingContext}
   *               The browsingcontext from which the
   *               WebChannelMessageToChrome was sent.
   *        @param sendingContext.eventTarget {EventTarget}
   *               The <EventTarget> where the message was sent.
   *               Can be null; if not null, should be a ContentDOMReference.
   *        @param sendingContext.principal {Principal}
   *               The <Principal> of the EventTarget where the message was sent.
   *
   */
  deliver(data, sendingContext) {
    if (this._deliverCallback) {
      try {
        this._deliverCallback(data.id, data.message, sendingContext);
      } catch (ex) {
        this.send(
          {
            errno: ERRNO_UNKNOWN_ERROR,
            error: ex.message ? ex.message : ERROR_UNKNOWN,
          },
          sendingContext
        );
        console.error("Failed to execute WebChannel callback:");
        console.error(ex);
      }
    } else {
      console.error("No callback set for this channel.");
    }
  },
};
PK
!<*y��modules/WebHandlerApp.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

export function nsWebHandlerApp() {}

nsWebHandlerApp.prototype = {
  classDescription: "A web handler for protocols and content",
  classID: Components.ID("8b1ae382-51a9-4972-b930-56977a57919d"),
  contractID: "@mozilla.org/uriloader/web-handler-app;1",
  QueryInterface: ChromeUtils.generateQI(["nsIWebHandlerApp", "nsIHandlerApp"]),

  _name: null,
  _detailedDescription: null,
  _uriTemplate: null,

  // nsIHandlerApp

  get name() {
    return this._name;
  },

  set name(aName) {
    this._name = aName;
  },

  get detailedDescription() {
    return this._detailedDescription;
  },

  set detailedDescription(aDesc) {
    this._detailedDescription = aDesc;
  },

  equals(aHandlerApp) {
    if (!aHandlerApp) {
      throw Components.Exception("", Cr.NS_ERROR_NULL_POINTER);
    }

    if (
      aHandlerApp instanceof Ci.nsIWebHandlerApp &&
      aHandlerApp.uriTemplate &&
      this.uriTemplate &&
      aHandlerApp.uriTemplate == this.uriTemplate
    ) {
      return true;
    }
    return false;
  },

  launchWithURI(aURI, aBrowsingContext) {
    // XXX need to strip passwd & username from URI to handle, as per the
    // WhatWG HTML5 draft.  nsSimpleURL, which is what we're going to get,
    // can't do this directly.  Ideally, we'd fix nsStandardURL to make it
    // possible to turn off all of its quirks handling, and use that...

    let { scheme } = aURI;
    if (scheme == "ftp" || scheme == "ftps" || scheme == "sftp") {
      // FTP URLs are parsed by nsStandardURL, so clearing the username and
      // password does not throw.
      aURI = aURI.mutate().setUserPass("").finalize();
    }

    // encode the URI to be handled
    var escapedUriSpecToHandle = encodeURIComponent(aURI.spec);

    // insert the encoded URI and create the object version.
    var uriToSend = Services.io.newURI(
      this.uriTemplate.replace("%s", escapedUriSpecToHandle)
    );

    let policy = WebExtensionPolicy.getByURI(uriToSend);
    let privateAllowed = !policy || policy.privateBrowsingAllowed;

    if (!scheme.startsWith("web+") && !scheme.startsWith("ext+")) {
      // If we're in a frame, check if we're a built-in scheme, in which case,
      // override the target browsingcontext. It's not a good idea to try to
      // load mail clients or other apps with potential for logged in data into
      // iframes, and in any case it's unlikely to work due to framing
      // restrictions employed by the target site.
      if (aBrowsingContext && aBrowsingContext != aBrowsingContext.top) {
        aBrowsingContext = null;
      }

      // Unset the browsing context when in a pinned tab and changing hosts
      // to force loading the mail handler in a new unpinned tab.
      if (aBrowsingContext?.top.isAppTab) {
        let docURI = aBrowsingContext.currentWindowGlobal.documentURI;
        let docHost, sendHost;

        try {
          docHost = docURI?.host;
          sendHost = uriToSend?.host;
        } catch (e) {}

        // Special case: ignore "www" prefix if it is part of host string
        if (docHost?.startsWith("www.")) {
          docHost = docHost.replace(/^www\./, "");
        }
        if (sendHost?.startsWith("www.")) {
          sendHost = sendHost.replace(/^www\./, "");
        }

        if (docHost != sendHost) {
          aBrowsingContext = null;
        }
      }
    }

    // if we have a context, use the URI loader to load there
    if (aBrowsingContext) {
      if (aBrowsingContext.usePrivateBrowsing && !privateAllowed) {
        throw Components.Exception(
          "Extension not allowed in private windows.",
          Cr.NS_ERROR_FILE_NOT_FOUND
        );
      }

      let triggeringPrincipal =
        Services.scriptSecurityManager.getSystemPrincipal();
      Services.tm.dispatchToMainThread(() =>
        aBrowsingContext.loadURI(uriToSend, { triggeringPrincipal })
      );
      return;
    }

    // The window type depends on the app.
    const windowType =
      AppConstants.MOZ_APP_NAME == "thunderbird"
        ? "mail:3pane"
        : "navigator:browser";
    let win = Services.wm.getMostRecentWindow(windowType);

    // If this is an extension handler, check private browsing access.
    if (!privateAllowed && lazy.PrivateBrowsingUtils.isWindowPrivate(win)) {
      throw Components.Exception(
        "Extension not allowed in private windows.",
        Cr.NS_ERROR_FILE_NOT_FOUND
      );
    }

    // If we get an exception, there are several possible reasons why:
    // a) this gecko embedding doesn't provide an nsIBrowserDOMWindow
    //    implementation (i.e. doesn't support browser-style functionality),
    //    so we need to kick the URL out to the OS default browser.  This is
    //    the subject of bug 394479.
    // b) this embedding does provide an nsIBrowserDOMWindow impl, but
    //    there doesn't happen to be a browser window open at the moment; one
    //    should be opened.  It's not clear whether this situation will really
    //    ever occur in real life.  If it does, the only API that I can find
    //    that seems reasonably likely to work for most embedders is the
    //    command line handler.
    // c) something else went wrong
    //
    // It's not clear how one would differentiate between the three cases
    // above, so for now we don't catch the exception.

    // openURI
    win.browserDOMWindow.openURI(
      uriToSend,
      null, // no window.opener
      Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW,
      Ci.nsIBrowserDOMWindow.OPEN_NEW,
      Services.scriptSecurityManager.getSystemPrincipal()
    );
  },

  // nsIWebHandlerApp

  get uriTemplate() {
    return this._uriTemplate;
  },

  set uriTemplate(aURITemplate) {
    this._uriTemplate = aURITemplate;
  },
};
PK
!<ř�I�/�/modules/WebNavigation.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

/** @type {Lazy} */
const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
  ClickHandlerParent: "resource:///actors/ClickHandlerParent.sys.mjs",
  UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs",
  WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.sys.mjs",
});

// Maximum amount of time that can be passed and still consider
// the data recent (similar to how is done in nsNavHistory,
// e.g. nsNavHistory::CheckIsRecentEvent, but with a lower threshold value).
const RECENT_DATA_THRESHOLD = 5 * 1000000;

function getBrowser(bc) {
  return bc.top.embedderElement;
}

export var WebNavigationManager = {
  /** @type {Map<string, Set<callback>>} */
  listeners: new Map(),

  /** @type {WeakMap<XULBrowserElement, object>} */
  recentTabTransitionData: new WeakMap(),

  init() {
    // Collect recent tab transition data in a WeakMap:
    //   browser -> tabTransitionData
    this.recentTabTransitionData = new WeakMap();

    Services.obs.addObserver(this, "urlbar-user-start-navigation", true);

    Services.obs.addObserver(this, "webNavigation-createdNavigationTarget");

    if (AppConstants.MOZ_BUILD_APP == "browser") {
      lazy.ClickHandlerParent.addContentClickListener(this);
    }
  },

  uninit() {
    // Stop collecting recent tab transition data and reset the WeakMap.
    Services.obs.removeObserver(this, "urlbar-user-start-navigation");
    Services.obs.removeObserver(this, "webNavigation-createdNavigationTarget");

    if (AppConstants.MOZ_BUILD_APP == "browser") {
      lazy.ClickHandlerParent.removeContentClickListener(this);
    }

    this.recentTabTransitionData = new WeakMap();
  },

  addListener(type, listener) {
    if (this.listeners.size == 0) {
      this.init();
    }

    if (!this.listeners.has(type)) {
      this.listeners.set(type, new Set());
    }
    let listeners = this.listeners.get(type);
    listeners.add(listener);
  },

  removeListener(type, listener) {
    let listeners = this.listeners.get(type);
    if (!listeners) {
      return;
    }
    listeners.delete(listener);
    if (listeners.size == 0) {
      this.listeners.delete(type);
    }

    if (this.listeners.size == 0) {
      this.uninit();
    }
  },

  /**
   * Support nsIObserver interface to observe the urlbar autocomplete events used
   * to keep track of the urlbar user interaction.
   */
  QueryInterface: ChromeUtils.generateQI([
    "extIWebNavigation",
    "nsIObserver",
    "nsISupportsWeakReference",
  ]),

  /**
   * Observe webNavigation-createdNavigationTarget (to fire the onCreatedNavigationTarget
   * related to windows or tabs opened from the main process) topics.
   *
   * @param {nsIAutoCompleteInput | object} subject
   * @param {string} topic
   */
  observe: function (subject, topic) {
    if (topic == "urlbar-user-start-navigation") {
      this.onURLBarUserStartNavigation(subject.wrappedJSObject);
    } else if (topic == "webNavigation-createdNavigationTarget") {
      // The observed notification is coming from privileged JavaScript components running
      // in the main process (e.g. when a new tab or window is opened using the context menu
      // or Ctrl/Shift + click on a link).
      const { createdTabBrowser, url, sourceFrameID, sourceTabBrowser } =
        subject.wrappedJSObject;

      this.fire("onCreatedNavigationTarget", createdTabBrowser, null, {
        sourceTabBrowser,
        sourceFrameId: sourceFrameID,
        url,
      });
    }
  },

  /**
   * Recognize the type of urlbar user interaction (e.g. typing a new url,
   * clicking on an url generated from a searchengine or a keyword, or a
   * bookmark found by the urlbar autocompletion).
   *
   * @param {object} acData
   *   The data for the autocompleted item.
   * @param {object} [acData.result]
   *   The result information associated with the navigation action.
   * @param {typeof lazy.UrlbarUtils.RESULT_TYPE} [acData.result.type]
   *   The result type associated with the navigation action.
   * @param {typeof lazy.UrlbarUtils.RESULT_SOURCE} [acData.result.source]
   *   The result source associated with the navigation action.
   */
  onURLBarUserStartNavigation(acData) {
    let tabTransitionData = {
      from_address_bar: true,
    };

    if (!acData.result) {
      tabTransitionData.typed = true;
    } else {
      switch (acData.result.type) {
        case lazy.UrlbarUtils.RESULT_TYPE.KEYWORD:
          tabTransitionData.keyword = true;
          break;
        case lazy.UrlbarUtils.RESULT_TYPE.SEARCH:
          tabTransitionData.generated = true;
          break;
        case lazy.UrlbarUtils.RESULT_TYPE.URL:
          if (
            acData.result.source == lazy.UrlbarUtils.RESULT_SOURCE.BOOKMARKS
          ) {
            tabTransitionData.auto_bookmark = true;
          } else {
            tabTransitionData.typed = true;
          }
          break;
        case lazy.UrlbarUtils.RESULT_TYPE.REMOTE_TAB:
          // Remote tab are autocomplete results related to
          // tab urls from a remote synchronized Firefox.
          tabTransitionData.typed = true;
          break;
        case lazy.UrlbarUtils.RESULT_TYPE.TAB_SWITCH:
        // This "switchtab" autocompletion should be ignored, because
        // it is not related to a navigation.
        // Fall through.
        case lazy.UrlbarUtils.RESULT_TYPE.OMNIBOX:
        // "Omnibox" should be ignored as the add-on may or may not initiate
        // a navigation on the item being selected.
        // Fall through.
        case lazy.UrlbarUtils.RESULT_TYPE.TIP:
          // "Tip" should be ignored since the tip will only initiate navigation
          // if there is a valid buttonUrl property, which is optional.
          throw new Error(
            `Unexpectedly received notification for ${acData.result.type}`
          );
        default:
          Cu.reportError(
            `Received unexpected result type ${acData.result.type}, falling back to typed transition.`
          );
          // Fallback on "typed" if the type is unknown.
          tabTransitionData.typed = true;
      }
    }

    this.setRecentTabTransitionData(tabTransitionData);
  },

  /**
   * Keep track of a recent user interaction and cache it in a
   * map associated to the current selected tab.
   *
   * @param {object} tabTransitionData
   * @param {boolean} [tabTransitionData.auto_bookmark]
   * @param {boolean} [tabTransitionData.from_address_bar]
   * @param {boolean} [tabTransitionData.generated]
   * @param {boolean} [tabTransitionData.keyword]
   * @param {boolean} [tabTransitionData.link]
   * @param {boolean} [tabTransitionData.typed]
   */
  setRecentTabTransitionData(tabTransitionData) {
    let window = lazy.BrowserWindowTracker.getTopWindow();
    if (
      window &&
      window.gBrowser &&
      window.gBrowser.selectedTab &&
      window.gBrowser.selectedTab.linkedBrowser
    ) {
      let browser = window.gBrowser.selectedTab.linkedBrowser;

      // Get recent tab transition data to update if any.
      let prevData = this.getAndForgetRecentTabTransitionData(browser);

      let newData = Object.assign(
        { time: Date.now() },
        prevData,
        tabTransitionData
      );
      this.recentTabTransitionData.set(browser, newData);
    }
  },

  /**
   * Retrieve recent data related to a recent user interaction give a
   * given tab's linkedBrowser (only if is is more recent than the
   * `RECENT_DATA_THRESHOLD`).
   *
   * NOTE: this method is used to retrieve the tab transition data
   * collected when one of the `onCommitted`, `onHistoryStateUpdated`
   * or `onReferenceFragmentUpdated` events has been received.
   *
   * @param {XULBrowserElement} browser
   * @returns {object}
   */
  getAndForgetRecentTabTransitionData(browser) {
    let data = this.recentTabTransitionData.get(browser);
    this.recentTabTransitionData.delete(browser);

    // Return an empty object if there isn't any tab transition data
    // or if it's less recent than RECENT_DATA_THRESHOLD.
    if (!data || data.time - Date.now() > RECENT_DATA_THRESHOLD) {
      return {};
    }

    return data;
  },

  onContentClick(target, data) {
    // We are interested only on clicks to links which are not "add to bookmark" commands
    if (data.href && !data.bookmark) {
      let where = lazy.BrowserUtils.whereToOpenLink(data);
      if (where == "current") {
        this.setRecentTabTransitionData({ link: true });
      }
    }
  },

  onCreatedNavigationTarget(bc, sourceBC, url) {
    if (!this.listeners.size) {
      return;
    }

    let browser = getBrowser(bc);

    this.fire("onCreatedNavigationTarget", browser, null, {
      sourceTabBrowser: getBrowser(sourceBC),
      sourceFrameId: lazy.WebNavigationFrames.getFrameId(sourceBC),
      url,
    });
  },

  onStateChange(bc, requestURI, status, stateFlags) {
    if (!this.listeners.size) {
      return;
    }

    let browser = getBrowser(bc);

    if (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
      let url = requestURI.spec;
      if (stateFlags & Ci.nsIWebProgressListener.STATE_START) {
        this.fire("onBeforeNavigate", browser, bc, { url });
      } else if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
        if (Components.isSuccessCode(status)) {
          this.fire("onCompleted", browser, bc, { url });
        } else {
          let error = `Error code ${status}`;
          this.fire("onErrorOccurred", browser, bc, { error, url });
        }
      }
    }
  },

  onDocumentChange(bc, frameTransitionData, location) {
    if (!this.listeners.size) {
      return;
    }

    let browser = getBrowser(bc);

    let extra = {
      url: location ? location.spec : "",
      // Transition data which is coming from the content process.
      frameTransitionData,
      tabTransitionData: this.getAndForgetRecentTabTransitionData(browser),
    };

    this.fire("onCommitted", browser, bc, extra);
  },

  onHistoryChange(
    bc,
    frameTransitionData,
    location,
    isHistoryStateUpdated,
    isReferenceFragmentUpdated
  ) {
    if (!this.listeners.size) {
      return;
    }

    let browser = getBrowser(bc);

    let extra = {
      url: location ? location.spec : "",
      // Transition data which is coming from the content process.
      frameTransitionData,
      tabTransitionData: this.getAndForgetRecentTabTransitionData(browser),
    };

    if (isReferenceFragmentUpdated) {
      this.fire("onReferenceFragmentUpdated", browser, bc, extra);
    } else if (isHistoryStateUpdated) {
      this.fire("onHistoryStateUpdated", browser, bc, extra);
    }
  },

  onDOMContentLoaded(bc, documentURI) {
    if (!this.listeners.size) {
      return;
    }

    let browser = getBrowser(bc);

    this.fire("onDOMContentLoaded", browser, bc, { url: documentURI.spec });
  },

  fire(type, browser, bc, extra) {
    if (!browser) {
      return;
    }

    let listeners = this.listeners.get(type);
    if (!listeners) {
      return;
    }

    let details = {
      browser,
    };

    if (bc) {
      details.frameId = lazy.WebNavigationFrames.getFrameId(bc);
      details.parentFrameId = lazy.WebNavigationFrames.getParentFrameId(bc);
    }

    for (let prop in extra) {
      details[prop] = extra[prop];
    }

    for (let listener of listeners) {
      listener(details);
    }
  },
};

const EVENTS = [
  "onBeforeNavigate",
  "onCommitted",
  "onDOMContentLoaded",
  "onCompleted",
  "onErrorOccurred",
  "onReferenceFragmentUpdated",
  "onHistoryStateUpdated",
  "onCreatedNavigationTarget",
];

export var WebNavigation = {};

for (let event of EVENTS) {
  WebNavigation[event] = {
    addListener: WebNavigationManager.addListener.bind(
      WebNavigationManager,
      event
    ),
    removeListener: WebNavigationManager.removeListener.bind(
      WebNavigationManager,
      event
    ),
  };
}
PK
!<籂@
@
#modules/WebNavigationFrames.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * The FrameDetail object which represents a frame in WebExtensions APIs.
 *
 * @typedef  {object}  FrameDetail
 * @inner
 * @property {number}  frameId        - Represents the numeric id which identify the frame in its tab.
 * @property {number}  parentFrameId  - Represents the numeric id which identify the parent frame.
 * @property {string}  url            - Represents the current location URL loaded in the frame.
 * @property {boolean} errorOccurred  - Indicates whether an error is occurred during the last load
 *                                      happened on this frame (NOT YET SUPPORTED).
 */

/**
 * Returns the frame ID of the given window. If the window is the
 * top-level content window, its frame ID is 0. Otherwise, its frame ID
 * is its outer window ID.
 *
 * @param {Window|BrowsingContext} bc - The window to retrieve the frame ID for.
 * @returns {number}
 */
function getFrameId(bc) {
  if (!BrowsingContext.isInstance(bc)) {
    bc = bc.browsingContext;
  }
  return bc.parent ? bc.id : 0;
}

/**
 * Returns the frame ID of the given window's parent.
 *
 * @param {Window|BrowsingContext} bc - The window to retrieve the parent frame ID for.
 * @returns {number}
 */
function getParentFrameId(bc) {
  if (!BrowsingContext.isInstance(bc)) {
    bc = bc.browsingContext;
  }
  return bc.parent ? getFrameId(bc.parent) : -1;
}

/**
 * Convert a BrowsingContext into internal FrameDetail json.
 *
 * @param {CanonicalBrowsingContext} bc
 * @returns {FrameDetail}
 */
function getFrameDetail(bc) {
  return {
    frameId: getFrameId(bc),
    parentFrameId: getParentFrameId(bc),
    url: bc.currentURI?.spec,
  };
}

export var WebNavigationFrames = {
  getFrame(bc, frameId) {
    // frameId 0 means the top-level frame; anything else is a child frame.
    let frame = BrowsingContext.get(frameId || bc.id);
    if (frame && frame.top === bc) {
      return getFrameDetail(/** @type {CanonicalBrowsingContext} */ (frame));
    }
    return null;
  },

  getFrameId,
  getParentFrameId,

  getAllFrames(bc) {
    let frames = [];

    // Recursively walk the BC tree, find all frames.
    function visit(bc) {
      frames.push(bc);
      bc.children.forEach(visit);
    }
    visit(bc);
    return frames.map(getFrameDetail);
  },

  getFromWindow(target) {
    if (Window.isInstance(target)) {
      return getFrameId(BrowsingContext.getFromWindow(target));
    }
    return -1;
  },
};
PK
!<���z?z? modules/WebRequestUpload.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";

const { DefaultMap } = ExtensionUtils;

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "mimeHeader",
  "@mozilla.org/network/mime-hdrparam;1",
  "nsIMIMEHeaderParam"
);

const BinaryInputStream = Components.Constructor(
  "@mozilla.org/binaryinputstream;1",
  "nsIBinaryInputStream",
  "setInputStream"
);
const ConverterInputStream = Components.Constructor(
  "@mozilla.org/intl/converter-input-stream;1",
  "nsIConverterInputStream",
  "init"
);

export var WebRequestUpload;

/**
 * Parses the given raw header block, and stores the value of each
 * lower-cased header name in the resulting map.
 */
class Headers extends Map {
  constructor(headerText) {
    super();

    if (headerText) {
      this.parseHeaders(headerText);
    }
  }

  parseHeaders(headerText) {
    let lines = headerText.split("\r\n");

    let lastHeader;
    for (let line of lines) {
      // The first empty line indicates the end of the header block.
      if (line === "") {
        return;
      }

      // Lines starting with whitespace are appended to the previous
      // header.
      if (/^\s/.test(line)) {
        if (lastHeader) {
          let val = this.get(lastHeader);
          this.set(lastHeader, `${val}\r\n${line}`);
        }
        continue;
      }

      let match = /^(.*?)\s*:\s+(.*)/.exec(line);
      if (match) {
        lastHeader = match[1].toLowerCase();
        this.set(lastHeader, match[2]);
      }
    }
  }

  /**
   * If the given header exists, and contains the given parameter,
   * returns the value of that parameter.
   *
   * @param {string} name
   *        The lower-cased header name.
   * @param {string} paramName
   *        The name of the parameter to retrieve, or empty to retrieve
   *        the first (possibly unnamed) parameter.
   * @returns {string | null}
   */
  getParam(name, paramName) {
    return Headers.getParam(this.get(name), paramName);
  }

  /**
   * If the given header value is non-null, and contains the given
   * parameter, returns the value of that parameter.
   *
   * @param {string | null} header
   *        The text of the header from which to retrieve the param.
   * @param {string} paramName
   *        The name of the parameter to retrieve, or empty to retrieve
   *        the first (possibly unnamed) parameter.
   * @returns {string | null}
   */
  static getParam(header, paramName) {
    if (header) {
      // The service expects this to be a raw byte string, so convert to
      // UTF-8.
      let bytes = new TextEncoder().encode(header);
      let binHeader = String.fromCharCode(...bytes);

      return lazy.mimeHeader.getParameterHTTP(
        binHeader,
        paramName,
        null,
        false,
        {}
      );
    }

    return null;
  }
}

/**
 * Creates a new Object with a corresponding property for every
 * key-value pair in the given Map.
 *
 * @param {Map} map
 *        The map to convert.
 * @returns {object}
 */
function mapToObject(map) {
  let result = {};
  for (let [key, value] of map) {
    result[key] = value;
  }
  return result;
}

/**
 * Rewinds the given seekable input stream to its beginning, and catches
 * any resulting errors.
 *
 * @param {nsISeekableStream} stream
 *        The stream to rewind.
 */
function rewind(stream) {
  // Do this outside the try-catch so that we throw if the stream is not
  // actually seekable.
  stream.QueryInterface(Ci.nsISeekableStream);

  try {
    stream.seek(0, 0);
  } catch (e) {
    // It might be already closed, e.g. because of a previous error.
    Cu.reportError(e);
  }
}

/**
 * Iterates over all of the sub-streams that make up the given stream,
 * or yields the stream itself if it is not a multi-part stream.
 *
 * @param {nsIIMultiplexInputStream|nsIStreamBufferAccess<nsIMultiplexInputStream>|nsIInputStream} outerStream
 *        The outer stream over which to iterate.
 */
function* getStreams(outerStream) {
  // If this is a multi-part stream, we need to iterate over its sub-streams,
  // rather than treating it as a simple input stream. Since it may be wrapped
  // in a buffered input stream, unwrap it before we do any checks.
  let unbuffered = outerStream;
  if (outerStream instanceof Ci.nsIStreamBufferAccess) {
    unbuffered = outerStream.unbufferedStream;
  }

  if (unbuffered instanceof Ci.nsIMultiplexInputStream) {
    let count = unbuffered.count;
    for (let i = 0; i < count; i++) {
      yield unbuffered.getStream(i);
    }
  } else {
    yield outerStream;
  }
}

/**
 * Parses the form data of the given stream as either multipart/form-data or
 * x-www-form-urlencoded, and returns a map of its fields.
 *
 * @param {nsIInputStream} stream
 *        The input stream from which to parse the form data.
 * @param {nsIHttpChannel} channel
 *        The channel to which the stream belongs.
 * @param {boolean} [lenient = false]
 *        If true, the operation will succeed even if there are UTF-8
 *        decoding errors.
 *
 * @returns {Map<string, Array<string>> | null}
 */
function parseFormData(stream, channel, lenient = false) {
  const BUFFER_SIZE = 8192;

  let touchedStreams = new Set();
  let converterStreams = [];

  /**
   * Creates a converter input stream from the given raw input stream,
   * and adds it to the list of streams to be rewound at the end of
   * parsing.
   *
   * Returns null if the given raw stream cannot be rewound.
   *
   * @param {nsIInputStream} stream
   *        The base stream from which to create a converter.
   * @returns {ConverterInputStream | null}
   */
  function createTextStream(stream) {
    if (!(stream instanceof Ci.nsISeekableStream)) {
      return null;
    }

    touchedStreams.add(stream);
    let converterStream = ConverterInputStream(
      stream,
      "UTF-8",
      0,
      lenient ? Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER : 0
    );
    converterStreams.push(converterStream);
    return converterStream;
  }

  /**
   * Reads a string of no more than the given length from the given text
   * stream.
   *
   * @param {ConverterInputStream} stream
   *        The stream to read.
   * @param {integer} [length = BUFFER_SIZE]
   *        The maximum length of data to read.
   * @returns {string}
   */
  function readString(stream, length = BUFFER_SIZE) {
    let data = {};
    stream.readString(length, data);
    return data.value;
  }

  /**
   * Iterates over all of the sub-streams of the given (possibly multi-part)
   * input stream, and yields a ConverterInputStream for each
   * nsIStringInputStream among them.
   *
   * @param {nsIInputStream|nsIMultiplexInputStream} outerStream
   *        The multi-part stream over which to iterate.
   */
  function* getTextStreams(outerStream) {
    for (let stream of getStreams(outerStream)) {
      if (stream instanceof Ci.nsIStringInputStream) {
        touchedStreams.add(outerStream);
        yield createTextStream(stream);
      }
    }
  }

  /**
   * Iterates over all of the string streams of the given (possibly
   * multi-part) input stream, and yields all of the available data in each as
   * chunked strings, each no more than BUFFER_SIZE in length.
   *
   * @param {nsIInputStream|nsIMultiplexInputStream} outerStream
   *        The multi-part stream over which to iterate.
   */
  function* readAllStrings(outerStream) {
    for (let textStream of getTextStreams(outerStream)) {
      let str;
      while ((str = readString(textStream))) {
        yield str;
      }
    }
  }

  /**
   * Iterates over the text contents of all of the string streams in the given
   * (possibly multi-part) input stream, splits them at occurrences of the
   * given boundary string, and yields each part.
   *
   * @param {nsIInputStream|nsIMultiplexInputStream} stream
   *        The multi-part stream over which to iterate.
   * @param {string} boundary
   *        The boundary at which to split the parts.
   * @param {string} [tail = ""]
   *        Any initial data to prepend to the start of the stream data.
   */
  function* getParts(stream, boundary, tail = "") {
    for (let chunk of readAllStrings(stream)) {
      chunk = tail + chunk;

      let parts = chunk.split(boundary);
      tail = parts.pop();

      yield* parts;
    }

    if (tail) {
      yield tail;
    }
  }

  /**
   * Parses the given stream as multipart/form-data and returns a map of its fields.
   *
   * @param {nsIMultiplexInputStream|nsIInputStream} stream
   *        The (possibly multi-part) stream to parse.
   * @param {string} boundary
   *        The boundary at which to split the parts.
   * @returns {Map<string, Array<string>>}
   */
  function parseMultiPart(stream, boundary) {
    let formData = new DefaultMap(() => []);

    for (let part of getParts(stream, boundary, "\r\n")) {
      if (part === "") {
        // The first part will always be empty.
        continue;
      }
      if (part === "--\r\n") {
        // This indicates the end of the stream.
        break;
      }

      let end = part.indexOf("\r\n\r\n");

      // All valid parts must begin with \r\n, and we can't process form
      // fields without any header block.
      if (!part.startsWith("\r\n") || end <= 0) {
        throw new Error("Invalid MIME stream");
      }

      let content = part.slice(end + 4);
      let headerText = part.slice(2, end);
      let headers = new Headers(headerText);

      let name = headers.getParam("content-disposition", "name");
      if (
        !name ||
        headers.getParam("content-disposition", "") !== "form-data"
      ) {
        throw new Error(
          "Invalid MIME stream: No valid Content-Disposition header"
        );
      }

      // Decode the percent-escapes in the name. Unlike with decodeURIComponent,
      // partial percent-escapes are passed through as is rather than throwing
      // exceptions.
      name = name.replace(/(%[0-9A-Fa-f]{2})+/g, match => {
        const bytes = new Uint8Array(match.length / 3);
        for (let i = 0; i < match.length / 3; i++) {
          bytes[i] = parseInt(match.substring(i * 3 + 1, (i + 1) * 3), 16);
        }
        return new TextDecoder("utf-8").decode(bytes);
      });

      if (headers.has("content-type")) {
        // For file upload fields, we return the filename, rather than the
        // file data. We're following Chrome in not percent-decoding the
        // filename.
        let filename = headers.getParam("content-disposition", "filename");
        content = filename || "";
      }
      formData.get(name).push(content);
    }

    return formData;
  }

  /**
   * Parses the given stream as x-www-form-urlencoded, and returns a map of its fields.
   *
   * @param {nsIInputStream} stream
   *        The stream to parse.
   * @returns {Map<string, Array<string>>}
   */
  function parseUrlEncoded(stream) {
    let formData = new DefaultMap(() => []);

    for (let part of getParts(stream, "&")) {
      let [name, value] = part
        .replace(/\+/g, " ")
        .split("=")
        .map(decodeURIComponent);
      formData.get(name).push(value);
    }

    return formData;
  }

  try {
    if (stream instanceof Ci.nsIMIMEInputStream && stream.data) {
      stream = stream.data;
    }

    channel.QueryInterface(Ci.nsIHttpChannel);
    let contentType = channel.getRequestHeader("Content-Type");

    switch (Headers.getParam(contentType, "")) {
      case "multipart/form-data":
        let boundary = Headers.getParam(contentType, "boundary");
        return parseMultiPart(stream, `\r\n--${boundary}`);

      case "application/x-www-form-urlencoded":
        return parseUrlEncoded(stream);
    }
  } finally {
    for (let stream of touchedStreams) {
      rewind(stream);
    }
    for (let converterStream of converterStreams) {
      // Release the reference to the underlying input stream, to prevent the
      // destructor of nsConverterInputStream from closing the stream, which
      // would cause uploads to break.
      converterStream.init(null, null, 0, 0);
    }
  }

  return null;
}

/**
 * Parses the form data of the given stream as either multipart/form-data or
 * x-www-form-urlencoded, and returns a map of its fields.
 *
 * Returns null if the stream is not seekable.
 *
 * @param {nsIMultiplexInputStream|nsIInputStream} stream
 *        The (possibly multi-part) stream from which to create the form data.
 * @param {nsIChannel} channel
 *        The channel to which the stream belongs.
 * @param {boolean} [lenient = false]
 *        If true, the operation will succeed even if there are UTF-8
 *        decoding errors.
 * @returns {Map<string, Array<string>> | null}
 */
function createFormData(stream, channel, lenient) {
  if (!(stream instanceof Ci.nsISeekableStream)) {
    return null;
  }

  try {
    let formData = parseFormData(stream, channel, lenient);
    if (formData) {
      return mapToObject(formData);
    }
  } catch (e) {
    Cu.reportError(e);
  } finally {
    rewind(stream);
  }
  return null;
}

/**
 * Iterates over all of the sub-streams of the given (possibly multi-part)
 * input stream, and yields an object containing the data for each chunk, up
 * to a total of `maxRead` bytes.
 *
 * @param {nsIMultiplexInputStream|nsIInputStream} outerStream
 *        The stream for which to return data.
 * @param {integer} [maxRead = WebRequestUpload.MAX_RAW_BYTES]
 *        The maximum total bytes to read.
 */
function* getRawDataChunked(
  outerStream,
  maxRead = WebRequestUpload.MAX_RAW_BYTES
) {
  for (let stream of getStreams(outerStream)) {
    // We need to inspect the stream to make sure it's not a file input
    // stream. If it's wrapped in a buffered input stream, unwrap it first,
    // so we can inspect the inner stream directly.
    let unbuffered = stream;
    if (stream instanceof Ci.nsIStreamBufferAccess) {
      unbuffered = stream.unbufferedStream;
    }

    // For file fields, we return an object containing the full path of
    // the file, rather than its data.
    if (
      unbuffered instanceof Ci.nsIFileInputStream ||
      unbuffered instanceof Ci.mozIRemoteLazyInputStream
    ) {
      // But this is not actually supported yet.
      yield { file: "<file>" };
      continue;
    }

    try {
      let binaryStream = BinaryInputStream(stream);
      let available;
      while ((available = binaryStream.available())) {
        let buffer = new ArrayBuffer(Math.min(maxRead, available));
        binaryStream.readArrayBuffer(buffer.byteLength, buffer);

        maxRead -= buffer.byteLength;

        let chunk = { bytes: buffer };

        if (buffer.byteLength < available) {
          chunk.truncated = true;
          chunk.originalSize = available;
        }

        yield chunk;

        if (maxRead <= 0) {
          return;
        }
      }
    } finally {
      rewind(stream);
    }
  }
}

WebRequestUpload = {
  createRequestBody(channel) {
    if (!(channel instanceof Ci.nsIUploadChannel) || !channel.uploadStream) {
      return null;
    }

    if (
      channel instanceof Ci.nsIUploadChannel2 &&
      channel.uploadStreamHasHeaders
    ) {
      return { error: "Upload streams with headers are unsupported" };
    }

    try {
      let stream = channel.uploadStream;

      let formData = createFormData(stream, channel);
      if (formData) {
        return { formData };
      }

      // If we failed to parse the stream as form data, return it as a
      // sequence of raw data chunks, along with a leniently-parsed form
      // data object, which ignores encoding errors.
      return {
        raw: Array.from(getRawDataChunked(stream)),
        lenientFormData: createFormData(stream, channel, true),
      };
    } catch (e) {
      Cu.reportError(e);
      return { error: e.message || String(e) };
    }
  },
};

XPCOMUtils.defineLazyPreferenceGetter(
  WebRequestUpload,
  "MAX_RAW_BYTES",
  "webextensions.webRequest.requestBodyMaxRawBytes"
);
PK
!<�|�==#modules/WebVTTParserWrapper.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { WebVTT } from "resource://gre/modules/vtt.sys.mjs";

export function WebVTTParserWrapper() {
  // Nothing
}

WebVTTParserWrapper.prototype = {
  loadParser(window) {
    this.parser = new WebVTT.Parser(window, new TextDecoder("utf8"));
  },

  parse(data) {
    // We can safely translate the string data to a Uint8Array as we are
    // guaranteed character codes only from \u0000 => \u00ff
    var buffer = new Uint8Array(data.length);
    for (var i = 0; i < data.length; i++) {
      buffer[i] = data.charCodeAt(i);
    }

    this.parser.parse(buffer);
  },

  flush() {
    this.parser.flush();
  },

  watch(callback) {
    this.parser.oncue = callback.onCue;
    this.parser.onregion = callback.onRegion;
    this.parser.onparsingerror = function (e) {
      // Passing the just the error code back is enough for our needs.
      callback.onParsingError("code" in e ? e.code : -1);
    };
  },

  cancel() {
    this.parser.oncue = null;
    this.parser.onregion = null;
    this.parser.onparsingerror = null;
  },

  convertCueToDOMTree(window, cue) {
    return WebVTT.convertCueToDOMTree(window, cue.text);
  },

  processCues(window, cues, overlay, controls) {
    WebVTT.processCues(window, cues, overlay, controls);
  },

  classDescription: "Wrapper for the JS WebVTT implementation (vtt.js)",
  QueryInterface: ChromeUtils.generateQI(["nsIWebVTTParserWrapper"]),
};
PK
!<��T���+modules/WellKnownOpportunisticUtils.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

export function WellKnownOpportunisticUtils() {
  this.valid = false;
  this.mixed = false;
  this.lifetime = 0;
}

WellKnownOpportunisticUtils.prototype = {
  QueryInterface: ChromeUtils.generateQI(["nsIWellKnownOpportunisticUtils"]),

  verify(aJSON, aOrigin) {
    try {
      let arr = JSON.parse(aJSON.toLowerCase());
      if (!arr.includes(aOrigin.toLowerCase())) {
        throw new Error("invalid origin");
      }
    } catch (e) {
      return;
    }
    this.valid = true;
  },
};
PK
!<��p��-�-modules/XPCOMUtils.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * vim: sw=2 ts=2 sts=2 et filetype=javascript
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

let global = Cu.getGlobalForObject({});

// Some global imports expose additional symbols; for example,
// `Cu.importGlobalProperties(["MessageChannel"])` imports `MessageChannel`
// and `MessagePort`. This table maps those extra symbols to the main
// import name.
const EXTRA_GLOBAL_NAME_TO_IMPORT_NAME = {
  MessagePort: "MessageChannel",
};

/**
 * Redefines the given property on the given object with the given
 * value. This can be used to redefine getter properties which do not
 * implement setters.
 */
function redefine(object, prop, value) {
  Object.defineProperty(object, prop, {
    configurable: true,
    enumerable: true,
    value,
    writable: true,
  });
  return value;
}

/**
 * XPCOMUtils contains helpers to make lazily loading scripts, modules, prefs
 * and XPCOM services more ergonomic for JS consumers.
 *
 * @class
 */
export var XPCOMUtils = {
  /**
   * DEPRECATED!
   *
   * Use ChromeUtils.defineLazyGetter instead.
   *
   * Defines a getter on a specified object that will be created upon first use.
   *
   * @param {object} aObject
   *        The object to define the lazy getter on.
   * @param {string} aName
   *        The name of the getter to define on aObject.
   * @param {Function} aLambda
   *        A function that returns what the getter should return.  This will
   *        only ever be called once.
   */
  defineLazyGetter(aObject, aName, aLambda) {
    console.warn(
      "Please use ChromeUtils.defineLazyGetter instead of XPCOMUtils.defineLazyGetter. XPCOMUtils.defineLazyGetter will be removed soon."
    );
    ChromeUtils.defineLazyGetter(aObject, aName, aLambda);
  },

  /**
   * Defines a getter on a specified object for a script.  The script will not
   * be loaded until first use.
   *
   * @param {object} aObject
   *        The object to define the lazy getter on.
   * @param {string|string[]} aNames
   *        The name of the getter to define on aObject for the script.
   *        This can be a string if the script exports only one symbol,
   *        or an array of strings if the script can be first accessed
   *        from several different symbols.
   * @param {string} aResource
   *        The URL used to obtain the script.
   */
  defineLazyScriptGetter(aObject, aNames, aResource) {
    if (!Array.isArray(aNames)) {
      aNames = [aNames];
    }
    for (let name of aNames) {
      Object.defineProperty(aObject, name, {
        get() {
          XPCOMUtils._scriptloader.loadSubScript(aResource, aObject);
          return aObject[name];
        },
        set(value) {
          redefine(aObject, name, value);
        },
        configurable: true,
        enumerable: true,
      });
    }
  },

  /**
   * Overrides the scriptloader definition for tests to help with globals
   * tracking. Should only be used for tests.
   *
   * @param {object} aObject
   *        The alternative script loader object to use.
   */
  overrideScriptLoaderForTests(aObject) {
    Cu.crashIfNotInAutomation();
    delete this._scriptloader;
    this._scriptloader = aObject;
  },

  /**
   * Defines a getter property on the given object for each of the given
   * global names as accepted by Cu.importGlobalProperties. These
   * properties are imported into the shared JSM module global, and then
   * copied onto the given object, no matter which global the object
   * belongs to.
   *
   * @param {object} aObject
   *        The object on which to define the properties.
   * @param {string[]} aNames
   *        The list of global properties to define.
   */
  defineLazyGlobalGetters(aObject, aNames) {
    for (let name of aNames) {
      ChromeUtils.defineLazyGetter(aObject, name, () => {
        if (!(name in global)) {
          let importName = EXTRA_GLOBAL_NAME_TO_IMPORT_NAME[name] || name;
          // eslint-disable-next-line mozilla/reject-importGlobalProperties, no-unused-vars
          Cu.importGlobalProperties([importName]);
        }
        return global[name];
      });
    }
  },

  /**
   * Defines a getter on a specified object for a service.  The service will not
   * be obtained until first use.
   *
   * @param {object} aObject
   *        The object to define the lazy getter on.
   * @param {string} aName
   *        The name of the getter to define on aObject for the service.
   * @param {string} aContract
   *        The contract used to obtain the service.
   * @param {string} aInterfaceName
   *        The name of the interface to query the service to.
   */
  defineLazyServiceGetter(aObject, aName, aContract, aInterfaceName) {
    ChromeUtils.defineLazyGetter(aObject, aName, () => {
      if (aInterfaceName) {
        return Cc[aContract].getService(Ci[aInterfaceName]);
      }
      return Cc[aContract].getService().wrappedJSObject;
    });
  },

  /**
   * Defines a lazy service getter on a specified object for each
   * property in the given object.
   *
   * @param {object} aObject
   *        The object to define the lazy getter on.
   * @param {object} aServices
   *        An object with a property for each service to be
   *        imported, where the property name is the name of the
   *        symbol to define, and the value is a 1 or 2 element array
   *        containing the contract ID and, optionally, the interface
   *        name of the service, as passed to defineLazyServiceGetter.
   */
  defineLazyServiceGetters(aObject, aServices) {
    for (let [name, service] of Object.entries(aServices)) {
      // Note: This is hot code, and cross-compartment array wrappers
      // are not JIT-friendly to destructuring or spread operators, so
      // we need to use indexed access instead.
      this.defineLazyServiceGetter(
        aObject,
        name,
        service[0],
        service[1] || null
      );
    }
  },

  /**
   * Defines a lazy module getter on a specified object for each
   * property in the given object.
   *
   * @param {object} aObject
   *        The object to define the lazy getter on.
   * @param {object} aModules
   *        An object with a property for each module property to be
   *        imported, where the property name is the name of the
   *        imported symbol and the value is the module URI.
   */
  defineLazyModuleGetters(aObject, aModules) {
    for (let [name, module] of Object.entries(aModules)) {
      ChromeUtils.defineModuleGetter(aObject, name, module);
    }
  },

  /**
   * Defines a getter on a specified object for preference value. The
   * preference is read the first time that the property is accessed,
   * and is thereafter kept up-to-date using a preference observer.
   *
   * @param {object} aObject
   *        The object to define the lazy getter on.
   * @param {string} aName
   *        The name of the getter property to define on aObject.
   * @param {string} aPreference
   *        The name of the preference to read.
   * @param {any} aDefaultPrefValue
   *        The default value to use, if the preference is not defined.
   *        This is the default value of the pref, before applying aTransform.
   * @param {Function} aOnUpdate
   *        A function to call upon update. Receives as arguments
   *         `(aPreference, previousValue, newValue)`
   * @param {Function} aTransform
   *        An optional function to transform the value.  If provided,
   *        this function receives the new preference value as an argument
   *        and its return value is used by the getter.
   */
  defineLazyPreferenceGetter(
    aObject,
    aName,
    aPreference,
    aDefaultPrefValue = null,
    aOnUpdate = null,
    aTransform = val => val
  ) {
    if (AppConstants.DEBUG && aDefaultPrefValue !== null) {
      let prefType = Services.prefs.getPrefType(aPreference);
      if (prefType != Ci.nsIPrefBranch.PREF_INVALID) {
        // The pref may get defined after the lazy getter is called
        // at which point the code here won't know the expected type.
        let prefTypeForDefaultValue = {
          boolean: Ci.nsIPrefBranch.PREF_BOOL,
          number: Ci.nsIPrefBranch.PREF_INT,
          string: Ci.nsIPrefBranch.PREF_STRING,
        }[typeof aDefaultPrefValue];
        if (prefTypeForDefaultValue != prefType) {
          throw new Error(
            `Default value does not match preference type (Got ${prefTypeForDefaultValue}, expected ${prefType}) for ${aPreference}`
          );
        }
      }
    }

    // Note: We need to keep a reference to this observer alive as long
    // as aObject is alive. This means that all of our getters need to
    // explicitly close over the variable that holds the object, and we
    // cannot define a value in place of a getter after we read the
    // preference.
    let observer = {
      QueryInterface: XPCU_lazyPreferenceObserverQI,

      value: undefined,

      observe(subject, topic, data) {
        if (data == aPreference) {
          if (aOnUpdate) {
            let previous = this.value;

            // Fetch and cache value.
            this.value = undefined;
            let latest = lazyGetter();
            aOnUpdate(data, previous, latest);
          } else {
            // Empty cache, next call to the getter will cause refetch.
            this.value = undefined;
          }
        }
      },
    };

    let defineGetter = get => {
      Object.defineProperty(aObject, aName, {
        configurable: true,
        enumerable: true,
        get,
      });
    };

    function lazyGetter() {
      if (observer.value === undefined) {
        let prefValue;
        switch (Services.prefs.getPrefType(aPreference)) {
          case Ci.nsIPrefBranch.PREF_STRING:
            prefValue = Services.prefs.getStringPref(aPreference);
            break;

          case Ci.nsIPrefBranch.PREF_INT:
            prefValue = Services.prefs.getIntPref(aPreference);
            break;

          case Ci.nsIPrefBranch.PREF_BOOL:
            prefValue = Services.prefs.getBoolPref(aPreference);
            break;

          case Ci.nsIPrefBranch.PREF_INVALID:
            prefValue = aDefaultPrefValue;
            break;

          default:
            // This should never happen.
            throw new Error(
              `Error getting pref ${aPreference}; its value's type is ` +
                `${Services.prefs.getPrefType(aPreference)}, which I don't ` +
                `know how to handle.`
            );
        }

        observer.value = aTransform(prefValue);
      }
      return observer.value;
    }

    defineGetter(() => {
      Services.prefs.addObserver(aPreference, observer, true);

      defineGetter(lazyGetter);
      return lazyGetter();
    });
  },

  /**
   * Defines a non-writable property on an object.
   *
   * @param {object} aObj
   *        The object to define the property on.
   *
   * @param {string} aName
   *        The name of the non-writable property to define on aObject.
   *
   * @param {any} aValue
   *        The value of the non-writable property.
   */
  defineConstant(aObj, aName, aValue) {
    Object.defineProperty(aObj, aName, {
      value: aValue,
      enumerable: true,
      writable: false,
    });
  },
};

ChromeUtils.defineLazyGetter(XPCOMUtils, "_scriptloader", () => {
  return Services.scriptloader;
});

var XPCU_lazyPreferenceObserverQI = ChromeUtils.generateQI([
  "nsIObserver",
  "nsISupportsWeakReference",
]);
PK
!<;�V�1"1"modules/XULStore.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Enables logging and shorter save intervals.
const debugMode = false;

// Delay when a change is made to when the file is saved.
// 30 seconds normally, or 3 seconds for testing
const WRITE_DELAY_MS = (debugMode ? 3 : 30) * 1000;

const XULSTORE_CID = Components.ID("{6f46b6f4-c8b1-4bd4-a4fa-9ebbed0753ea}");
const STOREDB_FILENAME = "xulstore.json";

/**
 * The XULStore retains XULElement attributes such as the sizing and styling. These are
 * stored in the user's profile directory inside of "xulstore.json". The JSON is
 * stored based on the chrome URL of the resource.
 *
 * For instance the "chrome://browser/content/browser.xhtml" main window sizing could be
 * stored like so:
 *
 * {
 *   "chrome://browser/content/browser.xhtml": {
 *     "main-window": {
 *       "screenX": "1104",
 *       "screenY": "25",
 *       "width": "1904",
 *       "height": "1612",
 *       "sizemode": "normal"
 *     },
 *     ...
 *   }
 * }
 *
 * The XULStore can only be loaded in the parent process. See the XULStore
 * and XULPersist C++ classes for how this is integrated from the C++ side.
 */
export function XULStore() {
  if (!Services.appinfo.inSafeMode) {
    this.load();
  }
}

XULStore.prototype = {
  classID: XULSTORE_CID,
  name: "XULStore",
  QueryInterface: ChromeUtils.generateQI([
    "nsINamed",
    "nsIObserver",
    "nsIXULStore",
    "nsISupportsWeakReference",
  ]),

  /* ---------- private members ---------- */

  /*
   * The format of _data is _data[docuri][elementid][attribute]. For example:
   *  {
   *      "chrome://blah/foo.xul" : {
   *                                    "main-window" : { aaa : 1, bbb : "c" },
   *                                    "barColumn"   : { ddd : 9, eee : "f" },
   *                                },
   *
   *      "chrome://foopy/b.xul" :  { ... },
   *      ...
   *  }
   */
  _data: {},
  _storeFile: null,
  _needsSaving: false,
  _saveAllowed: true,
  _writeTimer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),

  load() {
    Services.obs.addObserver(this, "profile-before-change", true);

    try {
      this._storeFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
    } catch (ex) {
      try {
        this._storeFile = Services.dirsvc.get("ProfDS", Ci.nsIFile);
      } catch (ex) {
        throw new Error("Can't find profile directory.");
      }
    }
    this._storeFile.append(STOREDB_FILENAME);

    this.readFile();
  },

  observe(subject, topic) {
    this.writeFile();
    if (topic == "profile-before-change") {
      this._saveAllowed = false;
    }
  },

  /*
   * Internal function for logging debug messages to the Error Console window
   */
  log(message) {
    if (!debugMode) {
      return;
    }
    console.log("XULStore: " + message);
  },

  readFile() {
    try {
      this._data = JSON.parse(Cu.readUTF8File(this._storeFile));
    } catch (e) {
      this.log("Error reading JSON: " + e);
      // This exception could mean that the file didn't exist.
      // We'll just ignore the error and start with a blank slate.
    }
  },

  async writeFile() {
    if (!this._needsSaving) {
      return;
    }

    this._needsSaving = false;

    this.log("Writing to xulstore.json");

    try {
      await IOUtils.writeJSON(this._storeFile.path, this._data, {
        tmpPath: this._storeFile.path + ".tmp",
      });
    } catch (e) {
      this.log("Failed to write xulstore.json: " + e);
      throw e;
    }
  },

  markAsChanged() {
    if (this._needsSaving || !this._storeFile) {
      return;
    }

    // Don't write the file more than once every 30 seconds.
    this._needsSaving = true;
    this._writeTimer.init(this, WRITE_DELAY_MS, Ci.nsITimer.TYPE_ONE_SHOT);
  },

  /* ---------- interface implementation ---------- */

  persist(node, attr) {
    if (!node.id) {
      throw new Error("Node without ID passed into persist()");
    }

    const uri = node.ownerDocument.documentURI;
    const value = node.getAttribute(attr) || "";

    if (node.localName == "window") {
      this.log("Persisting attributes to windows is handled by AppWindow.");
      return;
    }

    // See Bug 1476680 - we could drop the `hasValue` check so that
    // any time there's an empty attribute it gets removed from the
    // store. Since this is copying behavior from document.persist,
    // callers would need to be updated with that change.
    if (!value && this.hasValue(uri, node.id, attr)) {
      this.removeValue(uri, node.id, attr);
    } else {
      this.setValue(uri, node.id, attr, value);
    }
  },

  setValue(docURI, id, attr, value) {
    this.log(
      "Saving " + attr + "=" + value + " for id=" + id + ", doc=" + docURI
    );

    if (!this._saveAllowed) {
      Services.console.logStringMessage(
        "XULStore: Changes after profile-before-change are ignored!"
      );
      return;
    }

    // bug 319846 -- don't save really long attributes or values.
    if (id.length > 512 || attr.length > 512) {
      throw Components.Exception(
        "id or attribute name too long",
        Cr.NS_ERROR_ILLEGAL_VALUE
      );
    }

    if (value.length > 4096) {
      Services.console.logStringMessage(
        "XULStore: Warning, truncating long attribute value"
      );
      value = value.substr(0, 4096);
    }

    let obj = this._data;
    if (!(docURI in obj)) {
      obj[docURI] = {};
    }
    obj = obj[docURI];
    if (!(id in obj)) {
      obj[id] = {};
    }
    obj = obj[id];

    // Don't set the value if it is already set to avoid saving the file.
    if (attr in obj && obj[attr] == value) {
      return;
    }

    obj[attr] = value; // IE, this._data[docURI][id][attr] = value;

    this.markAsChanged();
  },

  hasValue(docURI, id, attr) {
    this.log(
      "has store value for id=" + id + ", attr=" + attr + ", doc=" + docURI
    );

    let ids = this._data[docURI];
    if (ids) {
      let attrs = ids[id];
      if (attrs) {
        return attr in attrs;
      }
    }

    return false;
  },

  getValue(docURI, id, attr) {
    this.log(
      "get store value for id=" + id + ", attr=" + attr + ", doc=" + docURI
    );

    let ids = this._data[docURI];
    if (ids) {
      let attrs = ids[id];
      if (attrs) {
        return attrs[attr] || "";
      }
    }

    return "";
  },

  removeValue(docURI, id, attr) {
    this.log(
      "remove store value for id=" + id + ", attr=" + attr + ", doc=" + docURI
    );

    if (!this._saveAllowed) {
      Services.console.logStringMessage(
        "XULStore: Changes after profile-before-change are ignored!"
      );
      return;
    }

    let ids = this._data[docURI];
    if (ids) {
      let attrs = ids[id];
      if (attrs && attr in attrs) {
        delete attrs[attr];

        if (!Object.getOwnPropertyNames(attrs).length) {
          delete ids[id];

          if (!Object.getOwnPropertyNames(ids).length) {
            delete this._data[docURI];
          }
        }

        this.markAsChanged();
      }
    }
  },

  removeDocument(docURI) {
    this.log("remove store values for doc=" + docURI);

    if (!this._saveAllowed) {
      Services.console.logStringMessage(
        "XULStore: Changes after profile-before-change are ignored!"
      );
      return;
    }

    if (this._data[docURI]) {
      delete this._data[docURI];
      this.markAsChanged();
    }
  },

  getIDsEnumerator(docURI) {
    this.log("Getting ID enumerator for doc=" + docURI);

    if (!(docURI in this._data)) {
      return new nsStringEnumerator([]);
    }

    let result = [];
    let ids = this._data[docURI];
    if (ids) {
      for (let id in this._data[docURI]) {
        result.push(id);
      }
    }

    return new nsStringEnumerator(result);
  },

  getAttributeEnumerator(docURI, id) {
    this.log("Getting attribute enumerator for id=" + id + ", doc=" + docURI);

    if (!(docURI in this._data) || !(id in this._data[docURI])) {
      return new nsStringEnumerator([]);
    }

    let attrs = [];
    for (let attr in this._data[docURI][id]) {
      attrs.push(attr);
    }

    return new nsStringEnumerator(attrs);
  },
};

function nsStringEnumerator(items) {
  this._items = items;
}

nsStringEnumerator.prototype = {
  QueryInterface: ChromeUtils.generateQI(["nsIStringEnumerator"]),
  _nextIndex: 0,
  [Symbol.iterator]() {
    return this._items.values();
  },
  hasMore() {
    return this._nextIndex < this._items.length;
  },
  getNext() {
    if (!this.hasMore()) {
      throw Components.Exception("", Cr.NS_ERROR_NOT_AVAILABLE);
    }
    return this._items[this._nextIndex++];
  },
};
PK
!<m��6��&modules/addons/AddonRepository.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs",
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
  ServiceRequest: "resource://gre/modules/ServiceRequest.sys.mjs",
});

// The current platform as specified in the AMO API:
// http://addons-server.readthedocs.io/en/latest/topics/api/addons.html#addon-detail-platform
ChromeUtils.defineLazyGetter(lazy, "PLATFORM", () => {
  let platform = Services.appinfo.OS;
  switch (platform) {
    case "Darwin":
      return "mac";

    case "Linux":
      return "linux";

    case "Android":
      return "android";

    case "WINNT":
      return "windows";
  }
  return platform;
});

const PREF_GETADDONS_CACHE_ENABLED = "extensions.getAddons.cache.enabled";

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "getAddonsCacheEnabled",
  PREF_GETADDONS_CACHE_ENABLED
);

const PREF_GETADDONS_CACHE_TYPES = "extensions.getAddons.cache.types";
const PREF_GETADDONS_CACHE_ID_ENABLED =
  "extensions.%ID%.getAddons.cache.enabled";
const PREF_GETADDONS_BROWSEADDONS = "extensions.getAddons.browseAddons";
const PREF_GETADDONS_BYIDS = "extensions.getAddons.get.url";
const PREF_GETADDONS_BROWSESEARCHRESULTS =
  "extensions.getAddons.search.browseURL";
const PREF_GETADDONS_DB_SCHEMA = "extensions.getAddons.databaseSchema";
const PREF_GET_LANGPACKS = "extensions.getAddons.langpacks.url";
const PREF_GET_BROWSER_MAPPINGS = "extensions.getAddons.browserMappings.url";

const PREF_METADATA_LASTUPDATE = "extensions.getAddons.cache.lastUpdate";
const PREF_METADATA_UPDATETHRESHOLD_SEC =
  "extensions.getAddons.cache.updateThreshold";
const DEFAULT_METADATA_UPDATETHRESHOLD_SEC = 172800; // two days

const DEFAULT_CACHE_TYPES = "extension,theme,locale,dictionary";

const FILE_DATABASE = "addons.json";
const DB_SCHEMA = 6;
const DB_MIN_JSON_SCHEMA = 5;
const DB_BATCH_TIMEOUT_MS = 50;

const BLANK_DB = function () {
  return {
    addons: new Map(),
    schema: DB_SCHEMA,
  };
};

import { Log } from "resource://gre/modules/Log.sys.mjs";

const LOGGER_ID = "addons.repository";

// Create a new logger for use by the Addons Repository
// (Requires AddonManager.sys.mjs)
var logger = Log.repository.getLogger(LOGGER_ID);

function convertHTMLToPlainText(html) {
  if (!html) {
    return html;
  }
  var converter = Cc[
    "@mozilla.org/widget/htmlformatconverter;1"
  ].createInstance(Ci.nsIFormatConverter);

  var input = Cc["@mozilla.org/supports-string;1"].createInstance(
    Ci.nsISupportsString
  );
  input.data = html.replace(/\n/g, "<br>");

  var output = {};
  converter.convert("text/html", input, "text/plain", output);

  if (output.value instanceof Ci.nsISupportsString) {
    return output.value.data.replace(/\r\n/g, "\n");
  }
  return html;
}

async function getAddonsToCache(aIds) {
  let types = Services.prefs.getStringPref(
    PREF_GETADDONS_CACHE_TYPES,
    DEFAULT_CACHE_TYPES
  );

  types = types.split(",");

  let addons = await lazy.AddonManager.getAddonsByIDs(aIds);
  let enabledIds = [];

  for (let [i, addon] of addons.entries()) {
    var preference = PREF_GETADDONS_CACHE_ID_ENABLED.replace("%ID%", aIds[i]);
    // If the preference doesn't exist caching is enabled by default
    if (!Services.prefs.getBoolPref(preference, true)) {
      continue;
    }

    // The add-ons manager may not know about this ID yet if it is a pending
    // install. In that case we'll just cache it regardless

    // Don't cache add-ons of the wrong types
    if (addon && !types.includes(addon.type)) {
      continue;
    }

    // Don't cache system add-ons
    if (addon && addon.isSystem) {
      continue;
    }

    enabledIds.push(aIds[i]);
  }

  return enabledIds;
}

function AddonSearchResult(aId) {
  this.id = aId;
  this.icons = {};
  this._unsupportedProperties = {};
}

AddonSearchResult.prototype = {
  /**
   * The ID of the add-on
   */
  id: null,

  /**
   * The add-on type (e.g. "extension" or "theme")
   */
  type: null,

  /**
   * The name of the add-on
   */
  name: null,

  /**
   * The version of the add-on
   */
  version: null,

  /**
   * The creator of the add-on
   */
  creator: null,

  /**
   * The developers of the add-on
   */
  developers: null,

  /**
   * A short description of the add-on
   */
  description: null,

  /**
   * The full description of the add-on
   */
  fullDescription: null,

  /**
   * The end-user licensing agreement (EULA) of the add-on
   */
  eula: null,

  /**
   * The url of the add-on's icon
   */
  get iconURL() {
    return this.icons && this.icons[32];
  },

  /**
   * The URLs of the add-on's icons, as an object with icon size as key
   */
  icons: null,

  /**
   * An array of screenshot urls for the add-on
   */
  screenshots: null,

  /**
   * The homepage for the add-on
   */
  homepageURL: null,

  /**
   * The support URL for the add-on
   */
  supportURL: null,

  /**
   * The contribution url of the add-on
   */
  contributionURL: null,

  /**
   * The rating of the add-on, 0-5
   */
  averageRating: null,

  /**
   * The number of reviews for this add-on
   */
  reviewCount: null,

  /**
   * The URL to the list of reviews for this add-on
   */
  reviewURL: null,

  /**
   * The number of times the add-on was downloaded the current week
   */
  weeklyDownloads: null,

  /**
   * The URL to the AMO detail page of this (listed) add-on
   */
  amoListingURL: null,

  /**
   * AddonInstall object generated from the add-on XPI url
   */
  install: null,

  /**
   * nsIURI storing where this add-on was installed from
   */
  sourceURI: null,

  /**
   * The Date that the add-on was most recently updated
   */
  updateDate: null,

  toJSON() {
    let json = {};

    for (let property of Object.keys(this)) {
      let value = this[property];
      if (property.startsWith("_") || typeof value === "function") {
        continue;
      }

      try {
        switch (property) {
          case "sourceURI":
            json.sourceURI = value ? value.spec : "";
            break;

          case "updateDate":
            json.updateDate = value ? value.getTime() : "";
            break;

          default:
            json[property] = value;
        }
      } catch (ex) {
        logger.warn("Error writing property value for " + property);
      }
    }

    for (let property of Object.keys(this._unsupportedProperties)) {
      let value = this._unsupportedProperties[property];
      if (!property.startsWith("_")) {
        json[property] = value;
      }
    }

    return json;
  },
};

/**
 * The add-on repository is a source of add-ons that can be installed. It can
 * be searched in three ways. The first takes a list of IDs and returns a
 * list of the corresponding add-ons. The second returns a list of add-ons that
 * come highly recommended. This list should change frequently. The third is to
 * search for specific search terms entered by the user. Searches are
 * asynchronous and results should be passed to the provided callback object
 * when complete. The results passed to the callback should only include add-ons
 * that are compatible with the current application and are not already
 * installed.
 */
export var AddonRepository = {
  /**
   * The homepage for visiting this repository. If the corresponding preference
   * is not defined, defaults to about:blank.
   */
  get homepageURL() {
    let url = this._formatURLPref(PREF_GETADDONS_BROWSEADDONS, {});
    return url != null ? url : "about:blank";
  },

  get appIsShuttingDown() {
    return Services.startup.shuttingDown;
  },

  /**
   * Retrieves the url that can be visited to see search results for the given
   * terms. If the corresponding preference is not defined, defaults to
   * about:blank.
   *
   * @param  aSearchTerms
   *         Search terms used to search the repository
   */
  getSearchURL(aSearchTerms) {
    let url = this._formatURLPref(PREF_GETADDONS_BROWSESEARCHRESULTS, {
      TERMS: aSearchTerms,
    });
    return url != null ? url : "about:blank";
  },

  /**
   * Whether caching is currently enabled
   */
  get cacheEnabled() {
    return lazy.getAddonsCacheEnabled;
  },

  /**
   * Shut down AddonRepository
   * return: promise{integer} resolves with the result of flushing
   *         the AddonRepository database
   */
  shutdown() {
    return AddonDatabase.shutdown(false);
  },

  metadataAge() {
    let now = Math.round(Date.now() / 1000);
    let lastUpdate = Services.prefs.getIntPref(PREF_METADATA_LASTUPDATE, 0);
    return Math.max(0, now - lastUpdate);
  },

  isMetadataStale() {
    let threshold = Services.prefs.getIntPref(
      PREF_METADATA_UPDATETHRESHOLD_SEC,
      DEFAULT_METADATA_UPDATETHRESHOLD_SEC
    );
    return this.metadataAge() > threshold;
  },

  /**
   * Asynchronously get a cached add-on by id. The add-on (or null if the
   * add-on is not found) is passed to the specified callback. If caching is
   * disabled, null is passed to the specified callback.
   *
   * The callback variant exists only for existing code in XPIProvider.sys.mjs
   * and XPIDatabase.sys.mjs that requires a synchronous callback, yuck.
   *
   * @param  aId
   *         The id of the add-on to get
   */
  async getCachedAddonByID(aId, aCallback) {
    if (!aId || !this.cacheEnabled) {
      if (aCallback) {
        aCallback(null);
      }
      return null;
    }

    if (aCallback && AddonDatabase._loaded) {
      let addon = AddonDatabase.getAddon(aId);
      aCallback(addon);
      return addon;
    }

    await AddonDatabase.openConnection();

    let addon = AddonDatabase.getAddon(aId);
    if (aCallback) {
      aCallback(addon);
    }
    return addon;
  },

  /*
   * Clear and delete the AddonRepository database
   * @return Promise{null} resolves when the database is deleted
   */
  _clearCache() {
    return AddonDatabase.delete().then(() =>
      lazy.AddonManagerPrivate.updateAddonRepositoryData()
    );
  },

  /*
   * Create a ServiceRequest instance.
   * @return ServiceRequest returns a ServiceRequest instance.
   */
  _createServiceRequest() {
    return new lazy.ServiceRequest({ mozAnon: true });
  },

  /**
   * Fetch data from an API where the results may span multiple "pages".
   * This function will take care of issuing multiple requests until all
   * the results have been fetched, and will coalesce them all into a
   * single return value.  The handling here is specific to the way AMO
   * implements paging (ie a JSON result with a "next" property).
   *
   * @param {string} pref
   *                 The pref name that contains the API URL to call.
   * @param {object} params
   *                 A key-value object that contains the parameters to replace
   *                 in the API URL.
   * @param {function} handler
   *                   This function will be called once per page of results,
   *                   it should return an array of objects (the type depends
   *                   on the particular API being called of course).
   *
   * @returns Promise{array} An array of all the individual results from
   *                         the API call(s).
   */
  _fetchPaged(pref, params, handler) {
    const startURL = this._formatURLPref(pref, params);

    let results = [];
    const fetchNextPage = url => {
      return new Promise((resolve, reject) => {
        if (this.appIsShuttingDown) {
          logger.debug(
            "Rejecting AddonRepository._fetchPaged call, shutdown already in progress"
          );
          reject(
            new Error(
              `Reject ServiceRequest for "${url}", shutdown already in progress`
            )
          );
          return;
        }
        let request = this._createServiceRequest();
        request.mozBackgroundRequest = true;
        request.open("GET", url, true);
        request.responseType = "json";

        request.addEventListener("error", () => {
          reject(new Error(`GET ${url} failed`));
        });
        request.addEventListener("timeout", () => {
          reject(new Error(`GET ${url} timed out`));
        });
        request.addEventListener("load", () => {
          let response = request.response;
          if (!response || (request.status != 200 && request.status != 0)) {
            reject(new Error(`GET ${url} failed (status ${request.status})`));
            return;
          }

          try {
            results.push(...handler(response.results));
          } catch (err) {
            reject(err);
          }

          if (response.next) {
            resolve(fetchNextPage(response.next));
          }

          resolve(results);
        });

        request.send(null);
      });
    };

    return fetchNextPage(startURL);
  },

  /**
   * Fetch metadata for a given set of addons from AMO.
   *
   * @param  aIDs
   *         The array of ids to retrieve metadata for.
   * @returns {array<AddonSearchResult>}
   */
  async getAddonsByIDs(aIDs) {
    const idCheck = aIDs.map(id => {
      if (id.startsWith("rta:")) {
        return atob(id.split(":")[1]);
      }
      return id;
    });

    const addons = await this._fetchPaged(
      PREF_GETADDONS_BYIDS,
      { IDS: aIDs.join(",") },
      results =>
        results
          .map(entry => this._parseAddon(entry))
          // Only return the add-ons corresponding the IDs passed to this method.
          .filter(addon => idCheck.includes(addon.id))
    );

    return addons;
  },

  /**
   * Fetch the Firefox add-ons mapped to the list of extension IDs for the
   * browser ID passed to this method.
   *
   * See: https://addons-server.readthedocs.io/en/latest/topics/api/addons.html#browser-mappings
   *
   * @param browserID
   *        The browser ID used to retrieve the mapping of IDs.
   * @param extensionIDs
   *        The array of browser (non-Firefox) extension IDs to retrieve
   *        metadata for.
   * @returns {object} result
   *        The result of the mapping.
   * @returns {array<AddonSearchResult>} result.addons
   *        The AddonSearchResults for the addons that were successfully mapped.
   * @returns {array<string>} result.matchedIDs
   *        The IDs of the extensions that were successfully matched to
   *        equivalents that can be installed in this browser. These are
   *        the IDs before matching to equivalents.
   * @returns {array<string>} result.unmatchedIDs
   *        The IDs of the extensions that were not matched to equivalents.
   */
  async getMappedAddons(browserID, extensionIDs) {
    let matchedExtensionIDs = new Set();
    let unmatchedExtensionIDs = new Set(extensionIDs);

    const addonIds = await this._fetchPaged(
      PREF_GET_BROWSER_MAPPINGS,
      { BROWSER: browserID },
      results =>
        results
          // Filter out all the entries with an extension ID not in the list
          // passed to the method.
          .filter(entry => {
            if (unmatchedExtensionIDs.has(entry.extension_id)) {
              unmatchedExtensionIDs.delete(entry.extension_id);
              matchedExtensionIDs.add(entry.extension_id);
              return true;
            }
            return false;
          })
          // Return the add-on ID (stored as `guid` on AMO).
          .map(entry => entry.addon_guid)
    );

    if (!addonIds.length) {
      return {
        addons: [],
        matchedIDs: [],
        unmatchedIDs: [...unmatchedExtensionIDs],
      };
    }

    return {
      addons: await this.getAddonsByIDs(addonIds),
      matchedIDs: [...matchedExtensionIDs],
      unmatchedIDs: [...unmatchedExtensionIDs],
    };
  },

  /**
   * Asynchronously add add-ons to the cache corresponding to the specified
   * ids. If caching is disabled, the cache is unchanged.
   *
   * @param  aIds
   *         The array of add-on ids to add to the cache
   * @returns {array<AddonSearchResult>} Add-ons to add to the cache.
   */
  async cacheAddons(aIds) {
    logger.debug(
      "cacheAddons: enabled " + this.cacheEnabled + " IDs " + aIds.toSource()
    );
    if (!this.cacheEnabled) {
      return [];
    }

    let ids = await getAddonsToCache(aIds);

    // If there are no add-ons to cache, act as if caching is disabled
    if (!ids.length) {
      return [];
    }

    let addons = [];
    try {
      addons = await this.getAddonsByIDs(ids);
    } catch (err) {
      logger.error(`Error in addon metadata check: ${err.message}`);
    }
    if (addons.length) {
      await AddonDatabase.update(addons);
    }
    return addons;
  },

  /**
   * Get all installed addons from the AddonManager singleton.
   *
   * @return Promise{array<AddonWrapper>} Resolves to an array of AddonWrapper instances.
   */
  _getAllInstalledAddons() {
    return lazy.AddonManager.getAllAddons();
  },

  /**
   * Performs the periodic background update check.
   *
   * In Firefox Desktop builds, the background update check is triggered on a
   * daily basis as part of the AOM background update check and registered
   * from: `toolkit/mozapps/extensions/extensions.manifest`
   *
   * In GeckoView builds, add-ons are checked for updates individually. The
   * `AddonRepository.backgroundUpdateCheck()` method is called by the
   * `updateWebExtension()` method defined in `GeckoViewWebExtensions.sys.mjs`
   * but only when `AddonRepository.isMetadataStale()` returns true.
   *
   * @return Promise{null} Resolves when the metadata update is complete.
   */
  async backgroundUpdateCheck() {
    let shutter = (async () => {
      if (this.appIsShuttingDown) {
        logger.debug(
          "Returning earlier from backgroundUpdateCheck, shutdown already in progress"
        );
        return;
      }

      let allAddons = await this._getAllInstalledAddons();

      // Completely remove cache if caching is not enabled
      if (!this.cacheEnabled) {
        logger.debug("Clearing cache because it is disabled");
        await this._clearCache();
        return;
      }

      let ids = allAddons.map(a => a.id);
      logger.debug("Repopulate add-on cache with " + ids.toSource());

      let addonsToCache = await getAddonsToCache(ids);

      // Completely remove cache if there are no add-ons to cache
      if (!addonsToCache.length) {
        logger.debug("Clearing cache because 0 add-ons were requested");
        await this._clearCache();
        return;
      }

      let addons;
      try {
        addons = await this.getAddonsByIDs(addonsToCache);
      } catch (err) {
        // This is likely to happen if the server is unreachable, e.g. when
        // there is no network connectivity.
        logger.error(`Error in addon metadata lookup: ${err.message}`);
        // Return now to avoid calling repopulate with an empty array;
        // doing so would clear the cache.
        return;
      }

      AddonDatabase.repopulate(addons);

      // Always call AddonManager updateAddonRepositoryData after we refill the cache
      await lazy.AddonManagerPrivate.updateAddonRepositoryData();
    })();
    lazy.AddonManager.beforeShutdown.addBlocker(
      "AddonRepository Background Updater",
      shutter
    );
    await shutter;
    lazy.AddonManager.beforeShutdown.removeBlocker(shutter);
  },

  /*
   * Creates an AddonSearchResult by parsing an entry from the AMO API.
   *
   * @param  aEntry
   *         An entry from the AMO search API to parse.
   * @return Result object containing the parsed AddonSearchResult
   */
  _parseAddon(aEntry) {
    let addon = new AddonSearchResult(aEntry.guid);

    addon.name = aEntry.name;
    if (typeof aEntry.current_version == "object") {
      addon.version = String(aEntry.current_version.version);
      if (Array.isArray(aEntry.current_version.files)) {
        for (let file of aEntry.current_version.files) {
          if (file.platform == "all" || file.platform == lazy.PLATFORM) {
            if (file.url) {
              addon.sourceURI = lazy.NetUtil.newURI(file.url);
            }
            break;
          }
        }
      }
    }
    addon.homepageURL = aEntry.homepage;
    addon.supportURL = aEntry.support_url;
    addon.amoListingURL = aEntry.url;

    addon.description = convertHTMLToPlainText(aEntry.summary);
    addon.fullDescription = convertHTMLToPlainText(aEntry.description);

    addon.weeklyDownloads = aEntry.weekly_downloads;

    switch (aEntry.type) {
      case "persona":
      case "statictheme":
        addon.type = "theme";
        break;

      case "language":
        addon.type = "locale";
        break;

      default:
        addon.type = aEntry.type;
        break;
    }

    if (Array.isArray(aEntry.authors)) {
      let authors = aEntry.authors.map(
        author =>
          new lazy.AddonManagerPrivate.AddonAuthor(author.name, author.url)
      );
      if (authors.length) {
        addon.creator = authors[0];
        addon.developers = authors.slice(1);
      }
    }

    if (typeof aEntry.previews == "object") {
      addon.screenshots = aEntry.previews.map(shot => {
        let safeSize = orig =>
          Array.isArray(orig) && orig.length >= 2 ? orig : [null, null];
        let imageSize = safeSize(shot.image_size);
        let thumbSize = safeSize(shot.thumbnail_size);
        return new lazy.AddonManagerPrivate.AddonScreenshot(
          shot.image_url,
          imageSize[0],
          imageSize[1],
          shot.thumbnail_url,
          thumbSize[0],
          thumbSize[1],
          shot.caption
        );
      });
    }

    addon.contributionURL = aEntry.contributions_url;

    if (typeof aEntry.ratings == "object") {
      addon.averageRating = Math.min(5, aEntry.ratings.average);
      addon.reviewCount = aEntry.ratings.text_count;
    }

    addon.reviewURL = aEntry.ratings_url;
    if (aEntry.last_updated) {
      addon.updateDate = new Date(aEntry.last_updated);
    }

    addon.icons = aEntry.icons || {};

    return addon;
  },

  // Create url from preference, returning null if preference does not exist
  _formatURLPref(aPreference, aSubstitutions = {}) {
    let url = Services.prefs.getCharPref(aPreference, "");
    if (!url) {
      logger.warn("_formatURLPref: Couldn't get pref: " + aPreference);
      return null;
    }

    url = url.replace(/%([A-Z_]+)%/g, function (aMatch, aKey) {
      return aKey in aSubstitutions
        ? encodeURIComponent(aSubstitutions[aKey])
        : aMatch;
    });

    return Services.urlFormatter.formatURL(url);
  },

  flush() {
    return AddonDatabase.flush();
  },

  async getAvailableLangpacks() {
    // This should be the API endpoint documented at:
    // http://addons-server.readthedocs.io/en/latest/topics/api/addons.html#language-tools
    let url = this._formatURLPref(PREF_GET_LANGPACKS);

    let response = await fetch(url, { credentials: "omit" });
    if (!response.ok) {
      throw new Error("fetching available language packs failed");
    }

    let data = await response.json();

    let result = [];
    for (let entry of data.results) {
      if (
        !entry.current_compatible_version ||
        !entry.current_compatible_version.files
      ) {
        continue;
      }

      for (let file of entry.current_compatible_version.files) {
        if (
          file.platform == "all" ||
          file.platform == Services.appinfo.OS.toLowerCase()
        ) {
          result.push({
            target_locale: entry.target_locale,
            url: file.url,
            hash: file.hash,
          });
        }
      }
    }

    return result;
  },
};

var AddonDatabase = {
  connectionPromise: null,
  _loaded: false,
  _saveTask: null,
  _blockerAdded: false,

  // the in-memory database
  DB: BLANK_DB(),

  /**
   * A getter to retrieve the path to the DB
   */
  get jsonFile() {
    return PathUtils.join(
      Services.dirsvc.get("ProfD", Ci.nsIFile).path,
      FILE_DATABASE
    );
  },

  /**
   * Asynchronously opens a new connection to the database file.
   *
   * @return {Promise} a promise that resolves to the database.
   */
  openConnection() {
    if (!this.connectionPromise) {
      this.connectionPromise = (async () => {
        let inputDB, schema;

        try {
          let data = await IOUtils.readUTF8(this.jsonFile);
          inputDB = JSON.parse(data);

          if (
            !inputDB.hasOwnProperty("addons") ||
            !Array.isArray(inputDB.addons)
          ) {
            throw new Error("No addons array.");
          }

          if (!inputDB.hasOwnProperty("schema")) {
            throw new Error("No schema specified.");
          }

          schema = parseInt(inputDB.schema, 10);

          if (!Number.isInteger(schema) || schema < DB_MIN_JSON_SCHEMA) {
            throw new Error("Invalid schema value.");
          }
        } catch (e) {
          if (e.name == "NotFoundError") {
            logger.debug("No " + FILE_DATABASE + " found.");
          } else {
            logger.error(
              `Malformed ${FILE_DATABASE}: ${e} - resetting to empty`
            );
          }

          // Create a blank addons.json file
          this.save();

          Services.prefs.setIntPref(PREF_GETADDONS_DB_SCHEMA, DB_SCHEMA);
          this._loaded = true;
          return this.DB;
        }

        Services.prefs.setIntPref(PREF_GETADDONS_DB_SCHEMA, DB_SCHEMA);

        // Convert the addon objects as necessary
        // and store them in our in-memory copy of the database.
        for (let addon of inputDB.addons) {
          let id = addon.id;

          let entry = this._parseAddon(addon);
          this.DB.addons.set(id, entry);
        }

        this._loaded = true;
        return this.DB;
      })();
    }

    return this.connectionPromise;
  },

  /**
   * Asynchronously shuts down the database connection and releases all
   * cached objects
   *
   * @param  aCallback
   *         An optional callback to call once complete
   * @param  aSkipFlush
   *         An optional boolean to skip flushing data to disk. Useful
   *         when the database is going to be deleted afterwards.
   */
  shutdown(aSkipFlush) {
    if (!this.connectionPromise) {
      return Promise.resolve();
    }

    this.connectionPromise = null;
    this._loaded = false;

    if (aSkipFlush) {
      return Promise.resolve();
    }

    return this.flush();
  },

  /**
   * Asynchronously deletes the database, shutting down the connection
   * first if initialized
   *
   * @param  aCallback
   *         An optional callback to call once complete
   * @return Promise{null} resolves when the database has been deleted
   */
  delete(aCallback) {
    this.DB = BLANK_DB();

    if (this._saveTask) {
      this._saveTask.disarm();
      this._saveTask = null;
    }

    // shutdown(true) never rejects
    this._deleting = this.shutdown(true)
      .then(() => IOUtils.remove(this.jsonFile))
      .catch(error =>
        logger.error(
          "Unable to delete Addon Repository file " + this.jsonFile,
          error
        )
      )
      .then(() => (this._deleting = null))
      .then(aCallback);

    return this._deleting;
  },

  async _saveNow() {
    let json = {
      schema: this.DB.schema,
      addons: Array.from(this.DB.addons.values()),
    };

    await IOUtils.writeUTF8(this.jsonFile, JSON.stringify(json), {
      tmpPath: `${this.jsonFile}.tmp`,
    });
  },

  save() {
    if (!this._saveTask) {
      this._saveTask = new lazy.DeferredTask(
        () => this._saveNow(),
        DB_BATCH_TIMEOUT_MS
      );

      if (!this._blockerAdded) {
        lazy.AsyncShutdown.profileBeforeChange.addBlocker(
          "Flush AddonRepository",
          () => this.flush()
        );
        this._blockerAdded = true;
      }
    }
    this._saveTask.arm();
  },

  /**
   * Flush any pending I/O on the addons.json file
   * @return: Promise{null}
   *          Resolves when the pending I/O (writing out or deleting
   *          addons.json) completes
   */
  flush() {
    if (this._deleting) {
      return this._deleting;
    }

    if (this._saveTask) {
      let promise = this._saveTask.finalize();
      this._saveTask = null;
      return promise;
    }

    return Promise.resolve();
  },

  /**
   * Get an individual addon entry from the in-memory cache.
   * Note: calling this function before the database is read will
   * return undefined.
   *
   * @param {string} aId The id of the addon to retrieve.
   */
  getAddon(aId) {
    return this.DB.addons.get(aId);
  },

  /**
   * Asynchronously repopulates the database so it only contains the
   * specified add-ons
   *
   * @param {array<AddonSearchResult>} aAddons
   *              Add-ons to repopulate the database with.
   */
  repopulate(aAddons) {
    this.DB = BLANK_DB();
    this._update(aAddons);

    let now = Math.round(Date.now() / 1000);
    logger.debug(
      "Cache repopulated, setting " + PREF_METADATA_LASTUPDATE + " to " + now
    );
    Services.prefs.setIntPref(PREF_METADATA_LASTUPDATE, now);
  },

  /**
   * Asynchronously insert new addons into the database.
   *
   * @param {array<AddonSearchResult>} aAddons
   *              Add-ons to insert/update in the database
   */
  async update(aAddons) {
    await this.openConnection();

    this._update(aAddons);
  },

  /**
   * Merge the given addons into the database.
   *
   * @param {array<AddonSearchResult>} aAddons
   *              Add-ons to insert/update in the database
   */
  _update(aAddons) {
    for (let addon of aAddons) {
      this.DB.addons.set(addon.id, this._parseAddon(addon));
    }

    this.save();
  },

  /*
   * Creates an AddonSearchResult by parsing an object structure
   * retrieved from the DB JSON representation.
   *
   * @param  aObj
   *         The object to parse
   * @return Returns an AddonSearchResult object.
   */
  _parseAddon(aObj) {
    if (aObj instanceof AddonSearchResult) {
      return aObj;
    }

    let id = aObj.id;
    if (!aObj.id) {
      return null;
    }

    let addon = new AddonSearchResult(id);

    for (let expectedProperty of Object.keys(AddonSearchResult.prototype)) {
      if (
        !(expectedProperty in aObj) ||
        typeof aObj[expectedProperty] === "function"
      ) {
        continue;
      }

      let value = aObj[expectedProperty];

      try {
        switch (expectedProperty) {
          case "sourceURI":
            addon.sourceURI = value ? lazy.NetUtil.newURI(value) : null;
            break;

          case "creator":
            addon.creator = value ? this._makeDeveloper(value) : null;
            break;

          case "updateDate":
            addon.updateDate = value ? new Date(value) : null;
            break;

          case "developers":
            if (!addon.developers) {
              addon.developers = [];
            }
            for (let developer of value) {
              addon.developers.push(this._makeDeveloper(developer));
            }
            break;

          case "screenshots":
            if (!addon.screenshots) {
              addon.screenshots = [];
            }
            for (let screenshot of value) {
              addon.screenshots.push(this._makeScreenshot(screenshot));
            }
            break;

          case "icons":
            if (!addon.icons) {
              addon.icons = {};
            }
            for (let size of Object.keys(aObj.icons)) {
              addon.icons[size] = aObj.icons[size];
            }
            break;

          case "iconURL":
            break;

          default:
            addon[expectedProperty] = value;
        }
      } catch (ex) {
        logger.warn(
          "Error in parsing property value for " + expectedProperty + " | " + ex
        );
      }

      // delete property from obj to indicate we've already
      // handled it. The remaining public properties will
      // be stored separately and just passed through to
      // be written back to the DB.
      delete aObj[expectedProperty];
    }

    // Copy remaining properties to a separate object
    // to prevent accidental access on downgraded versions.
    // The properties will be merged in the same object
    // prior to being written back through toJSON.
    for (let remainingProperty of Object.keys(aObj)) {
      switch (typeof aObj[remainingProperty]) {
        case "boolean":
        case "number":
        case "string":
        case "object":
          // these types are accepted
          break;
        default:
          continue;
      }

      if (!remainingProperty.startsWith("_")) {
        addon._unsupportedProperties[remainingProperty] =
          aObj[remainingProperty];
      }
    }

    return addon;
  },

  /**
   * Make a developer object from a vanilla
   * JS object from the JSON database
   *
   * @param  aObj
   *         The JS object to use
   * @return The created developer
   */
  _makeDeveloper(aObj) {
    let name = aObj.name;
    let url = aObj.url;
    return new lazy.AddonManagerPrivate.AddonAuthor(name, url);
  },

  /**
   * Make a screenshot object from a vanilla
   * JS object from the JSON database
   *
   * @param  aObj
   *         The JS object to use
   * @return The created screenshot
   */
  _makeScreenshot(aObj) {
    let url = aObj.url;
    let width = aObj.width;
    let height = aObj.height;
    let thumbnailURL = aObj.thumbnailURL;
    let thumbnailWidth = aObj.thumbnailWidth;
    let thumbnailHeight = aObj.thumbnailHeight;
    let caption = aObj.caption;
    return new lazy.AddonManagerPrivate.AddonScreenshot(
      url,
      width,
      height,
      thumbnailURL,
      thumbnailWidth,
      thumbnailHeight,
      caption
    );
  },
};
PK
!<x�
�$modules/addons/AddonSettings.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

import { AddonManager } from "resource://gre/modules/AddonManager.sys.mjs";

const PREF_SIGNATURES_REQUIRED = "xpinstall.signatures.required";
const PREF_LANGPACK_SIGNATURES = "extensions.langpacks.signatures.required";
const PREF_ALLOW_EXPERIMENTS = "extensions.experiments.enabled";
const PREF_EM_SIDELOAD_SCOPES = "extensions.sideloadScopes";
const PREF_IS_EMBEDDED = "extensions.isembedded";
const PREF_UPDATE_REQUIREBUILTINCERTS = "extensions.update.requireBuiltInCerts";
const PREF_INSTALL_REQUIREBUILTINCERTS =
  "extensions.install.requireBuiltInCerts";

export var AddonSettings = {};

// Make a non-changable property that can't be manipulated from other
// code in the app.
function makeConstant(name, value) {
  Object.defineProperty(AddonSettings, name, {
    configurable: false,
    enumerable: false,
    writable: false,
    value,
  });
}

if (AppConstants.MOZ_REQUIRE_SIGNING && !Cu.isInAutomation) {
  makeConstant("REQUIRE_SIGNING", true);
  makeConstant("LANGPACKS_REQUIRE_SIGNING", true);
} else {
  XPCOMUtils.defineLazyPreferenceGetter(
    AddonSettings,
    "REQUIRE_SIGNING",
    PREF_SIGNATURES_REQUIRED,
    false
  );
  XPCOMUtils.defineLazyPreferenceGetter(
    AddonSettings,
    "LANGPACKS_REQUIRE_SIGNING",
    PREF_LANGPACK_SIGNATURES,
    false
  );
}

/**
 * Require the use of certs shipped with Firefox for
 * addon install and update, if the distribution does
 * not require addon signing and is not ESR.
 */
XPCOMUtils.defineLazyPreferenceGetter(
  AddonSettings,
  "INSTALL_REQUIREBUILTINCERTS",
  PREF_INSTALL_REQUIREBUILTINCERTS,
  !AppConstants.MOZ_REQUIRE_SIGNING &&
    !AppConstants.MOZ_APP_VERSION_DISPLAY.endsWith("esr")
);

XPCOMUtils.defineLazyPreferenceGetter(
  AddonSettings,
  "UPDATE_REQUIREBUILTINCERTS",
  PREF_UPDATE_REQUIREBUILTINCERTS,
  !AppConstants.MOZ_REQUIRE_SIGNING &&
    !AppConstants.MOZ_APP_VERSION_DISPLAY.endsWith("esr")
);

// Whether or not we're running in GeckoView embedded in an Android app
if (Cu.isInAutomation) {
  XPCOMUtils.defineLazyPreferenceGetter(
    AddonSettings,
    "IS_EMBEDDED",
    PREF_IS_EMBEDDED,
    false
  );
} else {
  makeConstant("IS_EMBEDDED", AppConstants.platform === "android");
}

/**
 * AddonSettings.EXPERIMENTS_ENABLED
 *
 * Experimental APIs are always available to privileged signed addons.
 * This constant makes an optional preference available to enable experimental
 * APIs for developement purposes.
 *
 * Two features are toggled with this preference:
 *
 *   1. The ability to load an extension that contains an experimental
 *      API but is not privileged.
 *   2. The ability to load an unsigned extension that gains privilege
 *      if it is temporarily loaded (e.g. via about:debugging).
 *
 * MOZ_REQUIRE_SIGNING is set to zero in unbranded builds, we also
 * ensure nightly, dev-ed and our test infrastructure have access to
 * the preference.
 *
 * Official releases ignore this preference.
 */
if (
  !AppConstants.MOZ_REQUIRE_SIGNING ||
  AppConstants.NIGHTLY_BUILD ||
  AppConstants.MOZ_DEV_EDITION ||
  Cu.isInAutomation
) {
  XPCOMUtils.defineLazyPreferenceGetter(
    AddonSettings,
    "EXPERIMENTS_ENABLED",
    PREF_ALLOW_EXPERIMENTS,
    true
  );
} else {
  makeConstant("EXPERIMENTS_ENABLED", false);
}

if (AppConstants.MOZ_DEV_EDITION) {
  makeConstant("DEFAULT_THEME_ID", "firefox-compact-dark@mozilla.org");
} else {
  makeConstant("DEFAULT_THEME_ID", "default-theme@mozilla.org");
}

// SCOPES_SIDELOAD is a bitflag for what scopes we will load new extensions from when we scan the directories.
// If a build allows sideloading, or we're in automation, we'll also allow use of the preference.
if (AppConstants.MOZ_ALLOW_ADDON_SIDELOAD || Cu.isInAutomation) {
  XPCOMUtils.defineLazyPreferenceGetter(
    AddonSettings,
    "SCOPES_SIDELOAD",
    PREF_EM_SIDELOAD_SCOPES,
    AppConstants.MOZ_ALLOW_ADDON_SIDELOAD
      ? AddonManager.SCOPE_ALL
      : AddonManager.SCOPE_PROFILE
  );
} else {
  makeConstant("SCOPES_SIDELOAD", AddonManager.SCOPE_PROFILE);
}
PK
!<�%	�K�K)modules/addons/AddonUpdateChecker.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * The AddonUpdateChecker is responsible for retrieving the update information
 * from an add-on's remote update manifest.
 */

const TIMEOUT = 60 * 1000;
const TOOLKIT_ID = "toolkit@mozilla.org";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs",
  AddonSettings: "resource://gre/modules/addons/AddonSettings.sys.mjs",
  Blocklist: "resource://gre/modules/Blocklist.sys.mjs",
  CertUtils: "resource://gre/modules/CertUtils.sys.mjs",
  ServiceRequest: "resource://gre/modules/ServiceRequest.sys.mjs",
});

import { Log } from "resource://gre/modules/Log.sys.mjs";

const LOGGER_ID = "addons.update-checker";

// Create a new logger for use by the Addons Update Checker
// (Requires AddonManager.sys.mjs)
var logger = Log.repository.getLogger(LOGGER_ID);

/**
 * Sanitizes the update URL in an update item, as returned by
 * parseRDFManifest and parseJSONManifest. Ensures that:
 *
 * - The URL is secure, or secured by a strong enough hash.
 * - The security principal of the update manifest has permission to
 *   load the URL.
 *
 * @param aUpdate
 *        The update item to sanitize.
 * @param aRequest
 *        The XMLHttpRequest used to load the manifest.
 * @param aHashPattern
 *        The regular expression used to validate the update hash.
 * @param aHashString
 *        The human-readable string specifying which hash functions
 *        are accepted.
 */
function sanitizeUpdateURL(aUpdate, aRequest, aHashPattern, aHashString) {
  if (aUpdate.updateURL) {
    let scriptSecurity = Services.scriptSecurityManager;
    let principal = scriptSecurity.getChannelURIPrincipal(aRequest.channel);
    try {
      // This logs an error on failure, so no need to log it a second time
      scriptSecurity.checkLoadURIStrWithPrincipal(
        principal,
        aUpdate.updateURL,
        scriptSecurity.DISALLOW_SCRIPT
      );
    } catch (e) {
      delete aUpdate.updateURL;
      return;
    }

    if (
      lazy.AddonManager.checkUpdateSecurity &&
      !aUpdate.updateURL.startsWith("https:") &&
      !aHashPattern.test(aUpdate.updateHash)
    ) {
      logger.warn(
        `Update link ${aUpdate.updateURL} is not secure and is not verified ` +
          `by a strong enough hash (needs to be ${aHashString}).`
      );
      delete aUpdate.updateURL;
      delete aUpdate.updateHash;
    }
  }
}

/**
 * Parses an JSON update manifest into an array of update objects.
 *
 * @param  aId
 *         The ID of the add-on being checked for updates
 * @param  aRequest
 *         The XMLHttpRequest that has retrieved the update manifest
 * @param  aManifestData
 *         The pre-parsed manifest, as a JSON object tree
 * @return an array of update objects
 * @throws if the update manifest is invalid in any way
 */
function parseJSONManifest(aId, aRequest, aManifestData) {
  let TYPE_CHECK = {
    array: val => Array.isArray(val),
    object: val => val && typeof val == "object" && !Array.isArray(val),
  };

  function getProperty(aObj, aProperty, aType, aDefault = undefined) {
    if (!(aProperty in aObj)) {
      return aDefault;
    }

    let value = aObj[aProperty];

    let matchesType =
      aType in TYPE_CHECK ? TYPE_CHECK[aType](value) : typeof value == aType;
    if (!matchesType) {
      throw Components.Exception(
        `Update manifest property '${aProperty}' has incorrect type (expected ${aType})`
      );
    }

    return value;
  }

  function getRequiredProperty(aObj, aProperty, aType) {
    let value = getProperty(aObj, aProperty, aType);
    if (value === undefined) {
      throw Components.Exception(
        `Update manifest is missing a required ${aProperty} property.`
      );
    }
    return value;
  }

  let manifest = aManifestData;

  if (!TYPE_CHECK.object(manifest)) {
    throw Components.Exception(
      "Root element of update manifest must be a JSON object literal"
    );
  }

  // The set of add-ons this manifest has updates for
  let addons = getProperty(manifest, "addons", "object");

  if (!addons) {
    let keys = Object.keys(manifest);
    if (keys.length) {
      // "addons" property is optional. The presence of other properties may be
      // a sign of a mistake, so print a warning to help with debugging.
      logger.warn(
        `Update manifest for ${aId} is missing the "addons" property, found ${keys} instead.`
      );
    } else {
      // If the add-on isn't listed, the update server may return an empty
      // response.
      logger.warn(`Received empty update manifest for ${aId}.`);
    }
    return [];
  }

  // The entry for this particular add-on
  let addon = getProperty(addons, aId, "object");

  // A missing entry doesn't count as a failure, just as no avialable update
  // information
  if (!addon) {
    logger.warn("Update manifest did not contain an entry for " + aId);
    return [];
  }

  // The list of available updates
  let updates = getProperty(addon, "updates", "array", []);

  let results = [];

  for (let update of updates) {
    let version = getRequiredProperty(update, "version", "string");

    logger.debug(`Found an update entry for ${aId} version ${version}`);

    let applications = getProperty(update, "applications", "object", {
      gecko: {},
    });

    // "gecko" is currently the only supported application entry. If
    // it's missing, skip this update.
    if (!("gecko" in applications)) {
      logger.debug(
        "gecko not in application entry, skipping update of ${addon}"
      );
      continue;
    }

    let app = getProperty(applications, "gecko", "object");

    let appEntry = {
      id: TOOLKIT_ID,
      minVersion: getProperty(
        app,
        "strict_min_version",
        "string",
        lazy.AddonManagerPrivate.webExtensionsMinPlatformVersion
      ),
      maxVersion: "*",
    };

    let result = {
      id: aId,
      version,
      updateURL: getProperty(update, "update_link", "string"),
      updateHash: getProperty(update, "update_hash", "string"),
      updateInfoURL: getProperty(update, "update_info_url", "string"),
      strictCompatibility: false,
      targetApplications: [appEntry],
    };

    if ("strict_max_version" in app) {
      if ("advisory_max_version" in app) {
        logger.warn(
          "Ignoring 'advisory_max_version' update manifest property for " +
            aId +
            " property since 'strict_max_version' also present"
        );
      }

      appEntry.maxVersion = getProperty(app, "strict_max_version", "string");
      result.strictCompatibility = appEntry.maxVersion != "*";
    } else if ("advisory_max_version" in app) {
      appEntry.maxVersion = getProperty(app, "advisory_max_version", "string");
    }

    // Add an app entry for the current API ID, too, so that it overrides any
    // existing app-specific entries, which would take priority over the toolkit
    // entry.
    //
    // Note: This currently only has any effect on legacy extensions (mainly
    // those used in tests), since WebExtensions cannot yet specify app-specific
    // compatibility ranges.
    result.targetApplications.push(
      Object.assign({}, appEntry, { id: Services.appinfo.ID })
    );

    // The JSON update protocol requires an SHA-2 hash. RDF still
    // supports SHA-1, for compatibility reasons.
    sanitizeUpdateURL(result, aRequest, /^sha(256|512):/, "sha256 or sha512");

    results.push(result);
  }
  return results;
}

/**
 * Starts downloading an update manifest and then passes it to an appropriate
 * parser to convert to an array of update objects
 *
 * @param  aId
 *         The ID of the add-on being checked for updates
 * @param  aUrl
 *         The URL of the update manifest
 * @param  aObserver
 *         An observer to pass results to
 */
function UpdateParser(aId, aUrl, aObserver) {
  this.id = aId;
  this.observer = aObserver;
  this.url = aUrl;

  logger.debug("Requesting " + aUrl);
  try {
    this.request = new lazy.ServiceRequest({ mozAnon: true });
    this.request.open("GET", this.url, true);
    this.request.channel.notificationCallbacks =
      new lazy.CertUtils.BadCertHandler(
        !lazy.AddonSettings.UPDATE_REQUIREBUILTINCERTS
      );
    this.request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
    // Prevent the request from writing to cache.
    this.request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
    this.request.overrideMimeType("text/plain");
    this.request.timeout = TIMEOUT;
    this.request.addEventListener("load", () => this.onLoad());
    this.request.addEventListener("error", () => this.onError());
    this.request.addEventListener("timeout", () => this.onTimeout());
    this.request.send(null);
  } catch (e) {
    logger.error(`Failed to request update manifest for ${this.id}`, e);
  }
}

UpdateParser.prototype = {
  id: null,
  observer: null,
  request: null,
  url: null,

  /**
   * Called when the manifest has been successfully loaded.
   */
  onLoad() {
    let request = this.request;
    this.request = null;
    this._doneAt = new Error("place holder");

    try {
      lazy.CertUtils.checkCert(
        request.channel,
        !lazy.AddonSettings.UPDATE_REQUIREBUILTINCERTS
      );
    } catch (e) {
      logger.warn("Request failed: " + this.url + " - " + e);
      this.notifyError(lazy.AddonManager.ERROR_DOWNLOAD_ERROR);
      return;
    }

    if (!Components.isSuccessCode(request.status)) {
      logger.warn("Request failed: " + this.url + " - " + request.status);
      this.notifyError(lazy.AddonManager.ERROR_DOWNLOAD_ERROR);
      return;
    }

    let channel = request.channel;
    if (channel instanceof Ci.nsIHttpChannel && !channel.requestSucceeded) {
      logger.warn(
        "Request failed: " +
          this.url +
          " - " +
          channel.responseStatus +
          ": " +
          channel.responseStatusText
      );
      this.notifyError(lazy.AddonManager.ERROR_DOWNLOAD_ERROR);
      return;
    }

    let results;
    try {
      let json = JSON.parse(request.responseText);
      results = parseJSONManifest(this.id, request, json);
    } catch (e) {
      logger.warn(
        `onUpdateCheckComplete failed to parse update manifest for ${this.id}`,
        e
      );
      this.notifyError(lazy.AddonManager.ERROR_PARSE_ERROR);
      return;
    }

    if ("onUpdateCheckComplete" in this.observer) {
      try {
        this.observer.onUpdateCheckComplete(results);
      } catch (e) {
        logger.warn(
          `onUpdateCheckComplete notification failed for ${this.id}`,
          e
        );
      }
    } else {
      logger.warn(
        "onUpdateCheckComplete may not properly cancel",
        new Error("stack marker")
      );
    }
  },

  /**
   * Called when the request times out
   */
  onTimeout() {
    this.request = null;
    this._doneAt = new Error("Timed out");
    logger.warn("Request for " + this.url + " timed out");
    this.notifyError(lazy.AddonManager.ERROR_TIMEOUT);
  },

  /**
   * Called when the manifest failed to load.
   */
  onError() {
    if (!Components.isSuccessCode(this.request.status)) {
      logger.warn("Request failed: " + this.url + " - " + this.request.status);
    } else if (this.request.channel instanceof Ci.nsIHttpChannel) {
      try {
        if (this.request.channel.requestSucceeded) {
          logger.warn(
            "Request failed: " +
              this.url +
              " - " +
              this.request.channel.responseStatus +
              ": " +
              this.request.channel.responseStatusText
          );
        }
      } catch (e) {
        logger.warn("HTTP Request failed for an unknown reason");
      }
    } else {
      logger.warn("Request failed for an unknown reason");
    }

    this.request = null;
    this._doneAt = new Error("UP_onError");

    this.notifyError(lazy.AddonManager.ERROR_DOWNLOAD_ERROR);
  },

  /**
   * Helper method to notify the observer that an error occurred.
   */
  notifyError(aStatus) {
    if ("onUpdateCheckError" in this.observer) {
      try {
        this.observer.onUpdateCheckError(aStatus);
      } catch (e) {
        logger.warn("onUpdateCheckError notification failed", e);
      }
    }
  },

  /**
   * Called to cancel an in-progress update check.
   */
  cancel() {
    if (!this.request) {
      logger.error("Trying to cancel already-complete request", this._doneAt);
      return;
    }
    this.request.abort();
    this.request = null;
    this._doneAt = new Error("UP_cancel");
    this.notifyError(lazy.AddonManager.ERROR_CANCELLED);
  },
};

/**
 * Tests if an update matches a version of the application or platform
 *
 * @param  aUpdate
 *         The available update
 * @param  aAppVersion
 *         The application version to use
 * @param  aPlatformVersion
 *         The platform version to use
 * @param  aIgnoreMaxVersion
 *         Ignore maxVersion when testing if an update matches. Optional.
 * @param  aIgnoreStrictCompat
 *         Ignore strictCompatibility when testing if an update matches. Optional.
 * @return true if the update is compatible with the application/platform
 */
function matchesVersions(
  aUpdate,
  aAppVersion,
  aPlatformVersion,
  aIgnoreMaxVersion,
  aIgnoreStrictCompat
) {
  if (aUpdate.strictCompatibility && !aIgnoreStrictCompat) {
    aIgnoreMaxVersion = false;
  }

  let result = false;
  for (let app of aUpdate.targetApplications) {
    if (app.id == Services.appinfo.ID) {
      return (
        Services.vc.compare(aAppVersion, app.minVersion) >= 0 &&
        (aIgnoreMaxVersion ||
          Services.vc.compare(aAppVersion, app.maxVersion) <= 0)
      );
    }
    if (app.id == TOOLKIT_ID) {
      result =
        Services.vc.compare(aPlatformVersion, app.minVersion) >= 0 &&
        (aIgnoreMaxVersion ||
          Services.vc.compare(aPlatformVersion, app.maxVersion) <= 0);
    }
  }
  return result;
}

export var AddonUpdateChecker = {
  /**
   * Retrieves the best matching compatibility update for the application from
   * a list of available update objects.
   *
   * @param  aUpdates
   *         An array of update objects
   * @param  aVersion
   *         The version of the add-on to get new compatibility information for
   * @param  aIgnoreCompatibility
   *         An optional parameter to get the first compatibility update that
   *         is compatible with any version of the application or toolkit
   * @param  aAppVersion
   *         The version of the application or null to use the current version
   * @param  aPlatformVersion
   *         The version of the platform or null to use the current version
   * @param  aIgnoreMaxVersion
   *         Ignore maxVersion when testing if an update matches. Optional.
   * @param  aIgnoreStrictCompat
   *         Ignore strictCompatibility when testing if an update matches. Optional.
   * @return an update object if one matches or null if not
   */
  getCompatibilityUpdate(
    aUpdates,
    aVersion,
    aIgnoreCompatibility,
    aAppVersion,
    aPlatformVersion,
    aIgnoreMaxVersion,
    aIgnoreStrictCompat
  ) {
    if (!aAppVersion) {
      aAppVersion = Services.appinfo.version;
    }
    if (!aPlatformVersion) {
      aPlatformVersion = Services.appinfo.platformVersion;
    }

    for (let update of aUpdates) {
      if (Services.vc.compare(update.version, aVersion) == 0) {
        if (aIgnoreCompatibility) {
          for (let targetApp of update.targetApplications) {
            let id = targetApp.id;
            if (id == Services.appinfo.ID || id == TOOLKIT_ID) {
              return update;
            }
          }
        } else if (
          matchesVersions(
            update,
            aAppVersion,
            aPlatformVersion,
            aIgnoreMaxVersion,
            aIgnoreStrictCompat
          )
        ) {
          return update;
        }
      }
    }
    return null;
  },

  /**
   * Asynchronously returns the newest available update from a list of update objects.
   *
   * @param  aUpdates
   *         An array of update objects
   * @param  aAddon
   *         The add-on that is being updated.
   * @param  aAppVersion
   *         The version of the application or null to use the current version
   * @param  aPlatformVersion
   *         The version of the platform or null to use the current version
   * @param  aIgnoreMaxVersion
   *         When determining compatible updates, ignore maxVersion. Optional.
   * @param  aIgnoreStrictCompat
   *         When determining compatible updates, ignore strictCompatibility. Optional.
   * @return an update object if one matches or null if not
   */
  async getNewestCompatibleUpdate(
    aUpdates,
    aAddon,
    aAppVersion,
    aPlatformVersion,
    aIgnoreMaxVersion,
    aIgnoreStrictCompat
  ) {
    if (!aAppVersion) {
      aAppVersion = Services.appinfo.version;
    }
    if (!aPlatformVersion) {
      aPlatformVersion = Services.appinfo.platformVersion;
    }

    let newestVersion = aAddon.version;
    let newest = null;
    let blocked = null;
    let blockedState;
    for (let update of aUpdates) {
      if (!update.updateURL) {
        continue;
      }
      if (Services.vc.compare(newestVersion, update.version) >= 0) {
        // Update older than add-on version or older than previous result.
        continue;
      }
      if (
        !matchesVersions(
          update,
          aAppVersion,
          aPlatformVersion,
          aIgnoreMaxVersion,
          aIgnoreStrictCompat
        )
      ) {
        continue;
      }
      let state = await lazy.Blocklist.getAddonBlocklistState(
        update,
        aAppVersion,
        aPlatformVersion
      );
      if (state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
        if (
          !blocked ||
          Services.vc.compare(blocked.version, update.version) < 0
        ) {
          blocked = update;
          blockedState = state;
        }
        continue;
      }
      newest = update;
      newestVersion = update.version;
    }
    if (
      blocked &&
      (!newest || Services.vc.compare(blocked.version, newestVersion) >= 0)
    ) {
      // If |newest| has a higher version than |blocked|, then the add-on would
      // not be considered for installation. But if |blocked| would otherwise
      // be eligible for installation, then report to telemetry that installation
      // has been blocked because of the blocklist.
      lazy.Blocklist.recordAddonBlockChangeTelemetry(
        {
          id: aAddon.id,
          version: blocked.version,
          blocklistState: blockedState,
        },
        "addon_update_check"
      );
    }
    return newest;
  },

  /**
   * Starts an update check.
   *
   * @param  aId
   *         The ID of the add-on being checked for updates
   * @param  aUrl
   *         The URL of the add-on's update manifest
   * @param  aObserver
   *         An observer to notify of results
   * @return UpdateParser so that the caller can use UpdateParser.cancel() to shut
   *         down in-progress update requests
   */
  checkForUpdates(aId, aUrl, aObserver) {
    return new UpdateParser(aId, aUrl, aObserver);
  },
};
PK
!<���BcBc"modules/addons/GMPProvider.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs",
  GMPInstallManager: "resource://gre/modules/GMPInstallManager.sys.mjs",
  Log: "resource://gre/modules/Log.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

import {
  GMPPrefs,
  GMPUtils,
  OPEN_H264_ID,
  WIDEVINE_L1_ID,
  WIDEVINE_L3_ID,
} from "resource://gre/modules/GMPUtils.sys.mjs";

const SEC_IN_A_DAY = 24 * 60 * 60;
// How long to wait after a user enabled EME before attempting to download CDMs.
const GMP_CHECK_DELAY = 10 * 1000; // milliseconds

const XHTML = "http://www.w3.org/1999/xhtml";

const NS_GRE_DIR = "GreD";
const CLEARKEY_PLUGIN_ID = "gmp-clearkey";
const CLEARKEY_VERSION = "0.1";

const FIRST_CONTENT_PROCESS_TOPIC = "ipc:first-content-process-created";

const GMP_LICENSE_INFO = "plugins-gmp-license-info";
const GMP_PRIVACY_INFO = "plugins-gmp-privacy-info";
const GMP_LEARN_MORE = "learn_more_label";

const GMP_PLUGINS = [
  {
    id: OPEN_H264_ID,
    name: "plugins-openh264-name",
    description: "plugins-openh264-description",
    level: "",
    libName: "gmpopenh264",
    // The following licenseURL is part of an awful hack to include the OpenH264
    // license without having bug 624602 fixed yet, and intentionally ignores
    // localisation.
    licenseURL: "chrome://mozapps/content/extensions/OpenH264-license.txt",
    homepageURL: "https://www.openh264.org/",
  },
  {
    id: WIDEVINE_L1_ID,
    name: "plugins-widevine-name",
    description: "plugins-widevine-description",
    level: "L1",
    libName: "Google.Widevine.CDM",
    licenseURL: "https://www.google.com/policies/privacy/",
    homepageURL: "https://www.widevine.com/",
    isEME: true,
  },
  {
    id: WIDEVINE_L3_ID,
    name: "plugins-widevine-name",
    description: "plugins-widevine-description",
    level: "L3",
    libName: "widevinecdm",
    licenseURL: "https://www.google.com/policies/privacy/",
    homepageURL: "https://www.widevine.com/",
    isEME: true,
  },
];

ChromeUtils.defineLazyGetter(
  lazy,
  "addonsBundle",
  () => new Localization(["toolkit/about/aboutAddons.ftl"], true)
);
ChromeUtils.defineLazyGetter(lazy, "gmpService", () =>
  Cc["@mozilla.org/gecko-media-plugin-service;1"].getService(
    Ci.mozIGeckoMediaPluginChromeService
  )
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gmpProviderEnabled",
  GMPPrefs.KEY_PROVIDER_ENABLED
);

var gLogger;
var gLogAppenderDump = null;

function configureLogging() {
  if (!gLogger) {
    gLogger = lazy.Log.repository.getLogger("Toolkit.GMP");
    gLogger.addAppender(
      new lazy.Log.ConsoleAppender(new lazy.Log.BasicFormatter())
    );
  }
  gLogger.level = GMPPrefs.getInt(
    GMPPrefs.KEY_LOGGING_LEVEL,
    lazy.Log.Level.Warn
  );

  let logDumping = GMPPrefs.getBool(GMPPrefs.KEY_LOGGING_DUMP, false);
  if (logDumping != !!gLogAppenderDump) {
    if (logDumping) {
      gLogAppenderDump = new lazy.Log.DumpAppender(
        new lazy.Log.BasicFormatter()
      );
      gLogger.addAppender(gLogAppenderDump);
    } else {
      gLogger.removeAppender(gLogAppenderDump);
      gLogAppenderDump = null;
    }
  }
}

/**
 * The GMPWrapper provides the info for the various GMP plugins to public
 * callers through the API.
 */
function GMPWrapper(aPluginInfo, aRawPluginInfo) {
  this._plugin = aPluginInfo;
  this._rawPlugin = aRawPluginInfo;
  this._log = lazy.Log.repository.getLoggerWithMessagePrefix(
    "Toolkit.GMP",
    "GMPWrapper(" + this._plugin.id + ") "
  );
  Services.prefs.addObserver(
    GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_ENABLED, this._plugin.id),
    this,
    true
  );
  Services.prefs.addObserver(
    GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_VERSION, this._plugin.id),
    this,
    true
  );
  if (this._plugin.isEME) {
    Services.prefs.addObserver(GMPPrefs.KEY_EME_ENABLED, this, true);
    Services.obs.addObserver(this, "EMEVideo:CDMMissing");
  }
}

GMPWrapper.prototype = {
  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
  ]),

  // An active task that checks for plugin updates and installs them.
  _updateTask: null,
  _gmpPath: null,
  _isUpdateCheckPending: false,

  set gmpPath(aPath) {
    this._gmpPath = aPath;
  },
  get gmpPath() {
    if (!this._gmpPath && this.isInstalled) {
      this._gmpPath = PathUtils.join(
        Services.dirsvc.get("ProfD", Ci.nsIFile).path,
        this._plugin.id,
        GMPPrefs.getString(GMPPrefs.KEY_PLUGIN_VERSION, null, this._plugin.id)
      );
    }
    return this._gmpPath;
  },

  get id() {
    return this._plugin.id;
  },
  get libName() {
    return this._plugin.libName;
  },
  get type() {
    return "plugin";
  },
  get isGMPlugin() {
    return true;
  },
  get name() {
    return this._plugin.name;
  },
  get creator() {
    return null;
  },
  get homepageURL() {
    return this._plugin.homepageURL;
  },

  get description() {
    return this._plugin.description;
  },
  get fullDescription() {
    return null;
  },

  getFullDescription(doc) {
    let plugin = this._rawPlugin;

    let frag = doc.createDocumentFragment();
    for (let [urlProp, labelId] of [
      ["learnMoreURL", GMP_LEARN_MORE],
      [
        "licenseURL",
        this.id == WIDEVINE_L1_ID || this.id == WIDEVINE_L3_ID
          ? GMP_PRIVACY_INFO
          : GMP_LICENSE_INFO,
      ],
    ]) {
      if (plugin[urlProp]) {
        let a = doc.createElementNS(XHTML, "a");
        a.href = plugin[urlProp];
        a.target = "_blank";
        a.textContent = lazy.addonsBundle.formatValueSync(labelId);

        if (frag.childElementCount) {
          frag.append(
            doc.createElementNS(XHTML, "br"),
            doc.createElementNS(XHTML, "br")
          );
        }
        frag.append(a);
      }
    }

    return frag;
  },

  get version() {
    return GMPPrefs.getString(
      GMPPrefs.KEY_PLUGIN_VERSION,
      null,
      this._plugin.id
    );
  },

  get isActive() {
    return (
      !this.appDisabled &&
      !this.userDisabled &&
      !GMPUtils.isPluginHidden(this._plugin)
    );
  },
  get appDisabled() {
    if (
      this._plugin.isEME &&
      !GMPPrefs.getBool(GMPPrefs.KEY_EME_ENABLED, true)
    ) {
      // If "media.eme.enabled" is false, all EME plugins are disabled.
      return true;
    }
    return false;
  },

  get userDisabled() {
    return !GMPPrefs.getBool(
      GMPPrefs.KEY_PLUGIN_ENABLED,
      true,
      this._plugin.id
    );
  },
  set userDisabled(aVal) {
    GMPPrefs.setBool(
      GMPPrefs.KEY_PLUGIN_ENABLED,
      aVal === false,
      this._plugin.id
    );
  },

  async enable() {
    this.userDisabled = false;
  },
  async disable() {
    this.userDisabled = true;
  },

  get blocklistState() {
    return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
  },
  get size() {
    return 0;
  },
  get scope() {
    return lazy.AddonManager.SCOPE_APPLICATION;
  },
  get pendingOperations() {
    return lazy.AddonManager.PENDING_NONE;
  },

  get operationsRequiringRestart() {
    return lazy.AddonManager.OP_NEEDS_RESTART_NONE;
  },

  get permissions() {
    let permissions = 0;
    if (!this.appDisabled) {
      permissions |= lazy.AddonManager.PERM_CAN_UPGRADE;
      permissions |= this.userDisabled
        ? lazy.AddonManager.PERM_CAN_ENABLE
        : lazy.AddonManager.PERM_CAN_DISABLE;
    }
    return permissions;
  },

  get updateDate() {
    let time = Number(
      GMPPrefs.getInt(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, 0, this._plugin.id)
    );
    if (this.isInstalled) {
      return new Date(time * 1000);
    }
    return null;
  },

  get isCompatible() {
    return true;
  },

  get isPlatformCompatible() {
    return true;
  },

  get providesUpdatesSecurely() {
    return true;
  },

  get foreignInstall() {
    return false;
  },

  get installTelemetryInfo() {
    return { source: "gmp-plugin" };
  },

  isCompatibleWith() {
    return true;
  },

  get applyBackgroundUpdates() {
    if (!GMPPrefs.isSet(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, this._plugin.id)) {
      return lazy.AddonManager.AUTOUPDATE_DEFAULT;
    }

    return GMPPrefs.getBool(
      GMPPrefs.KEY_PLUGIN_AUTOUPDATE,
      true,
      this._plugin.id
    )
      ? lazy.AddonManager.AUTOUPDATE_ENABLE
      : lazy.AddonManager.AUTOUPDATE_DISABLE;
  },

  set applyBackgroundUpdates(aVal) {
    if (aVal == lazy.AddonManager.AUTOUPDATE_DEFAULT) {
      GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, this._plugin.id);
    } else if (aVal == lazy.AddonManager.AUTOUPDATE_ENABLE) {
      GMPPrefs.setBool(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, true, this._plugin.id);
    } else if (aVal == lazy.AddonManager.AUTOUPDATE_DISABLE) {
      GMPPrefs.setBool(GMPPrefs.KEY_PLUGIN_AUTOUPDATE, false, this._plugin.id);
    }
  },

  /**
   * Called by the addon manager to update GMP addons. For example this will be
   * used if a user manually checks for GMP plugin updates by using the
   * menu in about:addons.
   *
   * This function is not used if MediaKeySystemAccess is requested and
   * Widevine is not yet installed, or if the user toggles prefs to enable EME.
   * For the function used in those cases see `checkForUpdates`.
   */
  findUpdates(aListener, aReason) {
    this._log.trace(
      "findUpdates() - " + this._plugin.id + " - reason=" + aReason
    );

    // In the case of GMP addons we do not wish to implement AddonInstall, as
    // we don't want to display information as in a normal addon install such
    // as a download progress bar. As such, we short circuit our
    // listeners by indicating that no updates exist (though some may).
    lazy.AddonManagerPrivate.callNoUpdateListeners(this, aListener);

    if (aReason === lazy.AddonManager.UPDATE_WHEN_PERIODIC_UPDATE) {
      if (!lazy.AddonManager.shouldAutoUpdate(this)) {
        this._log.trace(
          "findUpdates() - " + this._plugin.id + " - no autoupdate"
        );
        return Promise.resolve(false);
      }

      let secSinceLastCheck =
        Date.now() / 1000 -
        Services.prefs.getIntPref(GMPPrefs.KEY_UPDATE_LAST_CHECK, 0);
      if (secSinceLastCheck <= SEC_IN_A_DAY) {
        this._log.trace(
          "findUpdates() - " +
            this._plugin.id +
            " - last check was less then a day ago"
        );
        return Promise.resolve(false);
      }
    } else if (aReason !== lazy.AddonManager.UPDATE_WHEN_USER_REQUESTED) {
      this._log.trace(
        "findUpdates() - " +
          this._plugin.id +
          " - the given reason to update is not supported"
      );
      return Promise.resolve(false);
    }

    if (this._updateTask !== null) {
      this._log.trace(
        "findUpdates() - " + this._plugin.id + " - update task already running"
      );
      return this._updateTask;
    }

    this._updateTask = (async () => {
      this._log.trace("findUpdates() - updateTask");
      try {
        let installManager = new lazy.GMPInstallManager();
        let res = await installManager.checkForAddons();
        let update = res.addons.find(addon => addon.id === this._plugin.id);
        if (update && update.isValid && !update.isInstalled) {
          this._log.trace(
            "findUpdates() - found update for " +
              this._plugin.id +
              ", installing"
          );
          await installManager.installAddon(update);
        } else {
          this._log.trace("findUpdates() - no updates for " + this._plugin.id);
        }
        this._log.info(
          "findUpdates() - updateTask succeeded for " + this._plugin.id
        );
      } catch (e) {
        this._log.error(
          "findUpdates() - updateTask for " + this._plugin.id + " threw",
          e
        );
        throw e;
      } finally {
        this._updateTask = null;
      }
      return true;
    })();

    return this._updateTask;
  },

  get pluginLibraries() {
    if (this.isInstalled) {
      let path = this.version;
      return [path];
    }
    return [];
  },
  get pluginFullpath() {
    if (this.isInstalled) {
      let path = PathUtils.join(
        Services.dirsvc.get("ProfD", Ci.nsIFile).path,
        this._plugin.id,
        this.version
      );
      return [path];
    }
    return [];
  },

  get isInstalled() {
    return this.version && !!this.version.length;
  },

  _handleEnabledChanged() {
    this._log.info(
      "_handleEnabledChanged() id=" +
        this._plugin.id +
        " isActive=" +
        this.isActive
    );

    lazy.AddonManagerPrivate.callAddonListeners(
      this.isActive ? "onEnabling" : "onDisabling",
      this,
      false
    );
    if (this._gmpPath) {
      if (this.isActive) {
        this._log.info(
          "onPrefEnabledChanged() - adding gmp directory " + this._gmpPath
        );
        lazy.gmpService.addPluginDirectory(this._gmpPath);
      } else {
        this._log.info(
          "onPrefEnabledChanged() - removing gmp directory " + this._gmpPath
        );
        lazy.gmpService.removePluginDirectory(this._gmpPath);
      }
    }
    lazy.AddonManagerPrivate.callAddonListeners(
      this.isActive ? "onEnabled" : "onDisabled",
      this
    );
  },

  onPrefEMEGlobalEnabledChanged() {
    this._log.info(
      "onPrefEMEGlobalEnabledChanged() id=" +
        this._plugin.id +
        " appDisabled=" +
        this.appDisabled +
        " isActive=" +
        this.isActive +
        " hidden=" +
        GMPUtils.isPluginHidden(this._plugin)
    );

    lazy.AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, [
      "appDisabled",
    ]);
    // If EME or the GMP itself are disabled, uninstall the GMP.
    // Otherwise, check for updates, so we download and install the GMP.
    if (this.appDisabled) {
      this.uninstallPlugin();
    } else if (!GMPUtils.isPluginHidden(this._plugin)) {
      lazy.AddonManagerPrivate.callInstallListeners(
        "onExternalInstall",
        null,
        this,
        null,
        false
      );
      lazy.AddonManagerPrivate.callAddonListeners("onInstalling", this, false);
      lazy.AddonManagerPrivate.callAddonListeners("onInstalled", this);
      this.checkForUpdates(GMP_CHECK_DELAY);
    }
    if (!this.userDisabled) {
      this._handleEnabledChanged();
    }
  },

  /**
   * This is called if prefs are changed to enable EME, or if Widevine
   * MediaKeySystemAccess is requested but the Widevine CDM is not installed.
   *
   * For the function used by the addon manager see `findUpdates`.
   */
  checkForUpdates(delay) {
    if (this._isUpdateCheckPending) {
      return;
    }
    this._isUpdateCheckPending = true;
    GMPPrefs.reset(GMPPrefs.KEY_UPDATE_LAST_CHECK, null);
    // Delay this in case the user changes his mind and doesn't want to
    // enable EME after all.
    lazy.setTimeout(() => {
      if (!this.appDisabled) {
        let gmpInstallManager = new lazy.GMPInstallManager();
        // We don't really care about the results, if someone is interested
        // they can check the log.
        gmpInstallManager.simpleCheckAndInstall().catch(() => {});
      }
      this._isUpdateCheckPending = false;
    }, delay);
  },

  onPrefEnabledChanged() {
    if (!this._plugin.isEME || !this.appDisabled) {
      this._handleEnabledChanged();
    }
  },

  onPrefVersionChanged() {
    lazy.AddonManagerPrivate.callAddonListeners("onUninstalling", this, false);
    if (this._gmpPath) {
      this._log.info(
        "onPrefVersionChanged() - unregistering gmp directory " + this._gmpPath
      );
      lazy.gmpService.removeAndDeletePluginDirectory(
        this._gmpPath,
        true /* can defer */
      );
    }
    lazy.AddonManagerPrivate.callAddonListeners("onUninstalled", this);

    lazy.AddonManagerPrivate.callInstallListeners(
      "onExternalInstall",
      null,
      this,
      null,
      false
    );
    lazy.AddonManagerPrivate.callAddonListeners("onInstalling", this, false);
    this._gmpPath = null;
    if (this.isInstalled) {
      this._gmpPath = PathUtils.join(
        Services.dirsvc.get("ProfD", Ci.nsIFile).path,
        this._plugin.id,
        GMPPrefs.getString(GMPPrefs.KEY_PLUGIN_VERSION, null, this._plugin.id)
      );
    }
    if (this._gmpPath && this.isActive) {
      this._log.info(
        "onPrefVersionChanged() - registering gmp directory " + this._gmpPath
      );
      lazy.gmpService.addPluginDirectory(this._gmpPath);
    }
    lazy.AddonManagerPrivate.callAddonListeners("onInstalled", this);
  },

  observe(subject, topic, data) {
    if (topic == "nsPref:changed") {
      let pref = data;
      if (
        pref ==
        GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_ENABLED, this._plugin.id)
      ) {
        this.onPrefEnabledChanged();
      } else if (
        pref ==
        GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_VERSION, this._plugin.id)
      ) {
        this.onPrefVersionChanged();
      } else if (pref == GMPPrefs.KEY_EME_ENABLED) {
        this.onPrefEMEGlobalEnabledChanged();
      }
    } else if (topic == "EMEVideo:CDMMissing") {
      this.checkForUpdates(0);
    }
  },

  uninstallPlugin() {
    lazy.AddonManagerPrivate.callAddonListeners("onUninstalling", this, false);
    if (this.gmpPath) {
      this._log.info(
        "uninstallPlugin() - unregistering gmp directory " + this.gmpPath
      );
      lazy.gmpService.removeAndDeletePluginDirectory(this.gmpPath);
    }
    GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_VERSION, this.id);
    GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_HASHVALUE, this.id);
    GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_ABI, this.id);
    GMPPrefs.reset(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, this.id);
    lazy.AddonManagerPrivate.callAddonListeners("onUninstalled", this);
  },

  shutdown() {
    Services.prefs.removeObserver(
      GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_ENABLED, this._plugin.id),
      this
    );
    Services.prefs.removeObserver(
      GMPPrefs.getPrefKey(GMPPrefs.KEY_PLUGIN_VERSION, this._plugin.id),
      this
    );
    if (this._plugin.isEME) {
      Services.prefs.removeObserver(GMPPrefs.KEY_EME_ENABLED, this);
      Services.obs.removeObserver(this, "EMEVideo:CDMMissing");
    }
    return this._updateTask;
  },

  _arePluginFilesOnDisk() {
    let fileExists = function (aGmpPath, aFileName) {
      let f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
      let path = PathUtils.join(aGmpPath, aFileName);
      f.initWithPath(path);
      return f.exists();
    };

    let libName =
      AppConstants.DLL_PREFIX + this._plugin.libName + AppConstants.DLL_SUFFIX;
    let infoName;
    if (
      this._plugin.id == WIDEVINE_L1_ID ||
      this._plugin.id == WIDEVINE_L3_ID
    ) {
      infoName = "manifest.json";
    } else {
      infoName = this._plugin.id.substring(4) + ".info";
    }

    return (
      fileExists(this.gmpPath, libName) && fileExists(this.gmpPath, infoName)
    );
  },

  validate() {
    if (!this.isInstalled) {
      // Not installed -> Valid.
      return {
        installed: false,
        valid: true,
      };
    }

    let expectedABI = GMPUtils._expectedABI(this._plugin);
    let abi = GMPPrefs.getString(
      GMPPrefs.KEY_PLUGIN_ABI,
      expectedABI,
      this._plugin.id
    );
    if (abi != expectedABI) {
      // ABI doesn't match. Possibly this is a profile migrated across platforms
      // or from 32 -> 64 bit.
      return {
        installed: true,
        mismatchedABI: true,
        valid: false,
      };
    }

    // Installed -> Check if files are missing.
    let filesOnDisk = this._arePluginFilesOnDisk();
    return {
      installed: true,
      valid: filesOnDisk,
    };
  },
};

var GMPProvider = {
  get name() {
    return "GMPProvider";
  },

  _plugins: null,

  startup() {
    configureLogging();
    this._log = lazy.Log.repository.getLoggerWithMessagePrefix(
      "Toolkit.GMP",
      "GMPProvider."
    );
    this.buildPluginList();
    this.ensureProperCDMInstallState();

    Services.prefs.addObserver(GMPPrefs.KEY_LOG_BASE, configureLogging);

    for (let plugin of this._plugins.values()) {
      let wrapper = plugin.wrapper;
      let gmpPath = wrapper.gmpPath;
      let isEnabled = wrapper.isActive;
      this._log.trace(
        "startup - enabled=" + isEnabled + ", gmpPath=" + gmpPath
      );

      if (gmpPath && isEnabled) {
        let validation = wrapper.validate();
        if (validation.mismatchedABI) {
          this._log.info(
            "startup - gmp " + plugin.id + " mismatched ABI, uninstalling"
          );
          wrapper.uninstallPlugin();
          continue;
        }
        if (!validation.valid) {
          this._log.info(
            "startup - gmp " + plugin.id + " invalid, uninstalling"
          );
          wrapper.uninstallPlugin();
          continue;
        }
        this._log.info("startup - adding gmp directory " + gmpPath);
        try {
          lazy.gmpService.addPluginDirectory(gmpPath);
        } catch (e) {
          if (e.name != "NS_ERROR_NOT_AVAILABLE") {
            throw e;
          }
          this._log.warn(
            "startup - adding gmp directory failed with " +
              e.name +
              " - sandboxing not available?",
            e
          );
        }
      }
    }

    try {
      let greDir = Services.dirsvc.get(NS_GRE_DIR, Ci.nsIFile);
      let path = greDir.path;
      if (
        GMPUtils._isWindowsOnARM64() &&
        GMPPrefs.getBool(
          GMPPrefs.KEY_PLUGIN_ALLOW_X64_ON_ARM64,
          true,
          CLEARKEY_PLUGIN_ID
        )
      ) {
        path = PathUtils.join(path, "i686");
      }
      let clearkeyPath = PathUtils.join(
        path,
        CLEARKEY_PLUGIN_ID,
        CLEARKEY_VERSION
      );
      this._log.info("startup - adding clearkey CDM directory " + clearkeyPath);
      lazy.gmpService.addPluginDirectory(clearkeyPath);
    } catch (e) {
      this._log.warn("startup - adding clearkey CDM failed", e);
    }
  },

  shutdown() {
    this._log.trace("shutdown");
    Services.prefs.removeObserver(GMPPrefs.KEY_LOG_BASE, configureLogging);

    let shutdownTask = (async () => {
      this._log.trace("shutdown - shutdownTask");
      let shutdownSucceeded = true;

      for (let plugin of this._plugins.values()) {
        try {
          await plugin.wrapper.shutdown();
        } catch (e) {
          shutdownSucceeded = false;
        }
      }

      this._plugins = null;

      if (!shutdownSucceeded) {
        throw new Error("Shutdown failed");
      }
    })();

    return shutdownTask;
  },

  async getAddonByID(aId) {
    if (!this.isEnabled) {
      return null;
    }

    let plugin = this._plugins.get(aId);
    if (plugin && !GMPUtils.isPluginHidden(plugin)) {
      return plugin.wrapper;
    }
    return null;
  },

  async getAddonsByTypes(aTypes) {
    if (!this.isEnabled || (aTypes && !aTypes.includes("plugin"))) {
      return [];
    }

    let results = Array.from(this._plugins.values())
      .filter(p => !GMPUtils.isPluginHidden(p))
      .map(p => p.wrapper);

    return results;
  },

  get isEnabled() {
    return lazy.gmpProviderEnabled;
  },

  buildPluginList() {
    this._plugins = new Map();
    for (let aPlugin of GMP_PLUGINS) {
      let plugin = {
        id: aPlugin.id,
        name: lazy.addonsBundle.formatValueSync(aPlugin.name),
        description: lazy.addonsBundle.formatValueSync(aPlugin.description),
        libName: aPlugin.libName,
        homepageURL: aPlugin.homepageURL,
        optionsURL: aPlugin.optionsURL,
        wrapper: null,
        isEME: aPlugin.isEME,
      };
      plugin.wrapper = new GMPWrapper(plugin, aPlugin);
      this._plugins.set(plugin.id, plugin);
    }
  },

  ensureProperCDMInstallState() {
    if (!GMPPrefs.getBool(GMPPrefs.KEY_EME_ENABLED, true)) {
      for (let plugin of this._plugins.values()) {
        if (plugin.isEME && plugin.wrapper.isInstalled) {
          lazy.gmpService.addPluginDirectory(plugin.wrapper.gmpPath);
          plugin.wrapper.uninstallPlugin();
        }
      }
    }
  },

  observe(subject, topic) {
    if (topic == FIRST_CONTENT_PROCESS_TOPIC) {
      lazy.AddonManagerPrivate.registerProvider(GMPProvider, ["plugin"]);
      Services.obs.notifyObservers(null, "gmp-provider-registered");

      Services.obs.removeObserver(this, FIRST_CONTENT_PROCESS_TOPIC);
    }
  },

  addObserver() {
    Services.obs.addObserver(this, FIRST_CONTENT_PROCESS_TOPIC);
  },
};

GMPProvider.addObserver();

// For test use only.
export const GMPTestUtils = {
  /**
   * Used to override the GMP service with a mock.
   *
   * @param {object} mockService
   *        The mocked gmpService object.
   * @param {function} callback
   *        Method called with the overridden gmpService. The override
   *        is undone after the callback returns.
   */
  async overrideGmpService(mockService, callback) {
    let originalGmpService = lazy.gmpService;
    lazy.gmpService = mockService;
    try {
      return await callback();
    } finally {
      lazy.gmpService = originalGmpService;
    }
  },
};
PK
!<�Ú�N�N-modules/addons/SitePermsAddonProvider.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { computeSha256HashAsString } from "resource://gre/modules/addons/crypto-utils.sys.mjs";
import {
  GATED_PERMISSIONS,
  SITEPERMS_ADDON_PROVIDER_PREF,
  SITEPERMS_ADDON_TYPE,
  isGatedPermissionType,
  isKnownPublicSuffix,
  isPrincipalInSitePermissionsBlocklist,
} from "resource://gre/modules/addons/siteperms-addon-utils.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs",
});
ChromeUtils.defineLazyGetter(
  lazy,
  "addonsBundle",
  () => new Localization(["toolkit/about/aboutAddons.ftl"], true)
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "SITEPERMS_ADDON_PROVIDER_ENABLED",
  SITEPERMS_ADDON_PROVIDER_PREF,
  false
);

const FIRST_CONTENT_PROCESS_TOPIC = "ipc:first-content-process-created";
const SITEPERMS_ADDON_ID_SUFFIX = "@siteperms.mozilla.org";

// Generate a per-session random salt, which is then used to generate
// per-siteOrigin hashed strings used as the addon id in SitePermsAddonWrapper constructor
// (expected to be matching new addon id generated for the same siteOrigin during
// the same browsing session and different ones in new browsing sessions).
//
// NOTE: `generateSalt` is exported for testing purpose, should not be
// used outside of tests.
let SALT;
export function generateSalt() {
  // Throw if we're not in test and SALT is already defined
  if (
    typeof SALT !== "undefined" &&
    !Services.env.exists("XPCSHELL_TEST_PROFILE_DIR")
  ) {
    throw new Error("This should only be called from XPCShell tests");
  }
  SALT = crypto.getRandomValues(new Uint8Array(12)).join("");
}

function getSalt() {
  if (!SALT) {
    generateSalt();
  }
  return SALT;
}

class SitePermsAddonWrapper {
  // An array of nsIPermission granted for the siteOrigin.
  // We can't use a Set as handlePermissionChange might be called with different
  // nsIPermission instance for the same permission (in the generic sense)
  #permissions = [];

  // This will be set to true in the `uninstall` method to recognize when a perm-changed notification
  // is actually triggered by the SitePermsAddonWrapper uninstall method itself.
  isUninstalling = false;

  /**
   * @param {string} siteOriginNoSuffix: The origin this addon is installed for
   *                                     WITHOUT the suffix generated from the
   *                                     origin attributes (see:
   *                                     nsIPrincipal.siteOriginNoSuffix).
   * @param {Array<nsIPermission>} permissions: An array of the initial
   *                                            permissions the user granted
   *                                            for the addon origin.
   */
  constructor(siteOriginNoSuffix, permissions = []) {
    this.siteOrigin = siteOriginNoSuffix;
    this.principal =
      Services.scriptSecurityManager.createContentPrincipalFromOrigin(
        this.siteOrigin
      );
    // Use a template string for the concat in case `siteOrigin` isn't a string.
    const saltedValue = `${this.siteOrigin}${getSalt()}`;
    this.id = `${computeSha256HashAsString(
      saltedValue
    )}${SITEPERMS_ADDON_ID_SUFFIX}`;

    for (const perm of permissions) {
      this.#permissions.push(perm);
    }
  }

  get isUninstalled() {
    return this.#permissions.length === 0;
  }

  /**
   * Returns the list of gated permissions types granted for the instance's origin
   *
   * @return {Array<String>}
   */
  get sitePermissions() {
    return Array.from(new Set(this.#permissions.map(perm => perm.type)));
  }

  /**
   * Update #permissions, and calls `uninstall` if there are no remaining gated permissions
   * granted. This is called by SitePermsAddonProvider when it gets a "perm-changed" notification for a gated
   * permission.
   *
   * @param {nsIPermission} permission: The permission being added/removed
   * @param {String} action: The action perm-changed notifies us about
   */
  handlePermissionChange(permission, action) {
    if (action == "added") {
      this.#permissions.push(permission);
    } else if (action == "deleted") {
      // We want to remove the registered permission for the right principal (looking into originSuffix so we
      // can unregister revoked permission on a specific context, private window, ...).
      this.#permissions = this.#permissions.filter(
        perm =>
          !(
            perm.type == permission.type &&
            perm.principal.originSuffix === permission.principal.originSuffix
          )
      );

      if (this.#permissions.length === 0) {
        this.uninstall();
      }
    }
  }

  get type() {
    return SITEPERMS_ADDON_TYPE;
  }

  get name() {
    return lazy.addonsBundle.formatValueSync("addon-sitepermission-host", {
      host: this.principal.host,
    });
  }

  get creator() {
    return undefined;
  }

  get homepageURL() {
    return undefined;
  }

  get description() {
    return undefined;
  }

  get fullDescription() {
    return undefined;
  }

  get version() {
    // We consider the previous implementation attempt (signed addons) to be the initial version,
    // hence the 2.0 for this approach.
    return "2.0";
  }
  get updateDate() {
    return undefined;
  }

  get isActive() {
    return true;
  }

  get appDisabled() {
    return false;
  }

  get userDisabled() {
    return false;
  }
  set userDisabled(aVal) {}

  get size() {
    return 0;
  }

  async updateBlocklistState() {}

  get blocklistState() {
    return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
  }

  get scope() {
    return lazy.AddonManager.SCOPE_APPLICATION;
  }

  get pendingOperations() {
    return lazy.AddonManager.PENDING_NONE;
  }

  get operationsRequiringRestart() {
    return lazy.AddonManager.OP_NEEDS_RESTART_NONE;
  }

  get permissions() {
    // The addon only supports PERM_CAN_UNINSTALL and no other AOM permission.
    return lazy.AddonManager.PERM_CAN_UNINSTALL;
  }

  get signedState() {
    // Will make the permission prompt use the webextSitePerms.headerUnsignedWithPerms string
    return lazy.AddonManager.SIGNEDSTATE_MISSING;
  }

  async enable() {}

  async disable() {}

  /**
   * Uninstall the addon, calling AddonManager hooks and removing all granted permissions.
   *
   * @throws Services.perms.removeFromPrincipal could throw, see PermissionManager::AddInternal.
   */
  async uninstall() {
    if (this.isUninstalling) {
      return;
    }
    try {
      this.isUninstalling = true;
      lazy.AddonManagerPrivate.callAddonListeners(
        "onUninstalling",
        this,
        false
      );
      const permissions = [...this.#permissions];
      for (const permission of permissions) {
        try {
          Services.perms.removeFromPrincipal(
            permission.principal,
            permission.type
          );
          // Only remove the permission from the array if it was successfully removed from the principal
          this.#permissions.splice(this.#permissions.indexOf(permission), 1);
        } catch (err) {
          Cu.reportError(err);
        }
      }
      lazy.AddonManagerPrivate.callAddonListeners("onUninstalled", this);
    } finally {
      this.isUninstalling = false;
    }
  }

  get isCompatible() {
    return true;
  }

  get isPlatformCompatible() {
    return true;
  }

  get providesUpdatesSecurely() {
    return true;
  }

  get foreignInstall() {
    return false;
  }

  get installTelemetryInfo() {
    return { source: "siteperm-addon-provider", method: "synthetic-install" };
  }

  isCompatibleWith() {
    return true;
  }
}

class SitePermsAddonInstalling extends SitePermsAddonWrapper {
  /**
   * @param {string} siteOriginNoSuffix: The origin this addon is installed
   *                                     for, WITHOUT the suffix generated from
   *                                     the origin attributes (see:
   *                                     nsIPrincipal.siteOriginNoSuffix).
   * @param {SitePermsAddonInstall} install: The SitePermsAddonInstall instance
   *                                         calling this constructor.
   */
  constructor(siteOriginNoSuffix, install) {
    // SitePermsAddonWrapper expect an array of nsIPermission as its second parameter.
    // Since we don't have a proper permission here, we pass an object with the properties
    // being used in the class.
    const permission = {
      principal: install.principal,
      type: install.newSitePerm,
    };

    super(siteOriginNoSuffix, [permission]);
  }

  get existingAddon() {
    return SitePermsAddonProvider.wrappersMapByOrigin.get(this.siteOrigin);
  }

  uninstall() {
    // While about:addons tab is already open, new addon cards for newly installed
    // addons are created from the `onInstalled` AOM events, for the `SitePermsAddonWrapper`
    // the `onInstalling` and `onInstalled` events are emitted by `SitePermsAddonInstall`
    // and the addon instance is going to be a `SitePermsAddonInstalling` instance if
    // there wasn't an AddonCard for the same addon id yet.
    //
    // To make sure that all permissions will be uninstalled if a user uninstall the
    // addon from an AddonCard created from a `SitePermsAddonInstalling` instance,
    // we forward calls to the uninstall method of the existing `SitePermsAddonWrapper`
    // instance being tracked by the `SitePermsAddonProvider`.
    // If there isn't any then removing only the single permission added along with the
    // `SitePremsAddonInstalling` is going to be enough.
    if (this.existingAddon) {
      return this.existingAddon.uninstall();
    }

    return super.uninstall();
  }

  validInstallOrigins() {
    // Always return true from here,
    // actual checks are done from AddonManagerInternal.getSitePermsAddonInstallForWebpage
    return true;
  }
}

// Numeric id included in the install telemetry events to correlate multiple events related
// to the same install or update flow.
let nextInstallId = 0;

class SitePermsAddonInstall {
  #listeners = new Set();
  #installEvents = {
    INSTALL_CANCELLED: "onInstallCancelled",
    INSTALL_ENDED: "onInstallEnded",
    INSTALL_FAILED: "onInstallFailed",
  };

  /**
   * @param {nsIPrincipal} installingPrincipal
   * @param {String} sitePerm
   */
  constructor(installingPrincipal, sitePerm) {
    this.principal = installingPrincipal;
    this.newSitePerm = sitePerm;
    this.state = lazy.AddonManager.STATE_DOWNLOADED;
    this.addon = new SitePermsAddonInstalling(
      this.principal.siteOriginNoSuffix,
      this
    );
    this.installId = ++nextInstallId;
  }

  get installTelemetryInfo() {
    return this.addon.installTelemetryInfo;
  }

  async checkPrompt() {
    // `promptHandler` can be set from `AddonManagerInternal.setupPromptHandler`
    if (this.promptHandler) {
      let info = {
        // TODO: Investigate if we need to handle addon "update", i.e. granting new
        // gated permission on an origin other permissions were already granted for (Bug 1790778).
        existingAddon: null,
        addon: this.addon,
        icon: "chrome://mozapps/skin/extensions/category-sitepermission.svg",
        // Used in AMTelemetry to detect the install flow related to this prompt.
        install: this,
      };

      try {
        await this.promptHandler(info);
      } catch (err) {
        if (this.error < 0) {
          this.state = lazy.AddonManager.STATE_INSTALL_FAILED;
          // In some cases onOperationCancelled is called during failures
          // to install/uninstall/enable/disable addons.  We may need to
          // do that here in the future.
          this.#callInstallListeners(this.#installEvents.INSTALL_FAILED);
        } else if (this.state !== lazy.AddonManager.STATE_CANCELLED) {
          this.cancel();
        }
        return;
      }
    }

    this.state = lazy.AddonManager.STATE_PROMPTS_DONE;
    this.install();
  }

  install() {
    if (this.state === lazy.AddonManager.STATE_PROMPTS_DONE) {
      lazy.AddonManagerPrivate.callAddonListeners("onInstalling", this.addon);
      Services.perms.addFromPrincipal(
        this.principal,
        this.newSitePerm,
        Services.perms.ALLOW_ACTION
      );
      this.state = lazy.AddonManager.STATE_INSTALLED;
      this.#callInstallListeners(this.#installEvents.INSTALL_ENDED);
      lazy.AddonManagerPrivate.callAddonListeners("onInstalled", this.addon);
      this.addon.install = null;
      return;
    }

    if (this.state !== lazy.AddonManager.STATE_DOWNLOADED) {
      this.state = lazy.AddonManager.STATE_INSTALL_FAILED;
      this.#callInstallListeners(this.#installEvents.INSTALL_FAILED);
      return;
    }

    this.checkPrompt();
  }

  cancel() {
    // This method can be called if the install is already cancelled.
    // We don't want to go further in such case as it would lead to duplicated Telemetry events.
    if (this.state == lazy.AddonManager.STATE_CANCELLED) {
      console.error("SitePermsAddonInstall#cancel called twice on ", this);
      return;
    }

    this.state = lazy.AddonManager.STATE_CANCELLED;
    this.#callInstallListeners(this.#installEvents.INSTALL_CANCELLED);
  }

  /**
   * Add a listener for the install events
   *
   * @param {Object} listener
   * @param {Function} [listener.onDownloadEnded]
   * @param {Function} [listener.onInstallCancelled]
   * @param {Function} [listener.onInstallEnded]
   * @param {Function} [listener.onInstallFailed]
   */
  addListener(listener) {
    this.#listeners.add(listener);
  }

  /**
   * Remove a listener
   *
   * @param {Object} listener: The same object reference that was used for `addListener`
   */
  removeListener(listener) {
    this.#listeners.delete(listener);
  }

  /**
   * Call the listeners callbacks for a given event.
   *
   * @param {String} eventName: The event to fire. Should be one of `this.#installEvents`
   */
  #callInstallListeners(eventName) {
    if (!Object.values(this.#installEvents).includes(eventName)) {
      console.warn(`Unknown "${eventName}" "event`);
      return;
    }

    lazy.AddonManagerPrivate.callInstallListeners(
      eventName,
      Array.from(this.#listeners),
      this
    );
  }
}

const SitePermsAddonProvider = {
  get name() {
    return "SitePermsAddonProvider";
  },

  wrappersMapByOrigin: new Map(),

  /**
   * Update wrappersMapByOrigin on perm-changed
   *
   * @param {nsIPermission} permission: The permission being added/removed
   * @param {String} action: The action perm-changed notifies us about
   */
  handlePermissionChange(permission, action = "added") {
    // Bail out if it it's not a gated perm
    if (!isGatedPermissionType(permission.type)) {
      return;
    }

    // Gated APIs should probably not be available on non-secure origins,
    // but let's double check here.
    if (permission.principal.scheme !== "https") {
      return;
    }

    if (isPrincipalInSitePermissionsBlocklist(permission.principal)) {
      return;
    }

    const { siteOriginNoSuffix } = permission.principal;

    // Install origin cannot be on a known etld (e.g. github.io).
    // We shouldn't get a permission change for those here, but let's
    // be  extra safe
    if (isKnownPublicSuffix(siteOriginNoSuffix)) {
      return;
    }

    // Pipe the change to the existing addon if there is one.
    if (this.wrappersMapByOrigin.has(siteOriginNoSuffix)) {
      this.wrappersMapByOrigin
        .get(siteOriginNoSuffix)
        .handlePermissionChange(permission, action);
    }

    if (action == "added") {
      // We only have one SitePermsAddon per origin, handling multiple permissions.
      if (this.wrappersMapByOrigin.has(siteOriginNoSuffix)) {
        return;
      }

      const addonWrapper = new SitePermsAddonWrapper(siteOriginNoSuffix, [
        permission,
      ]);
      this.wrappersMapByOrigin.set(siteOriginNoSuffix, addonWrapper);
      return;
    }

    if (action == "deleted") {
      if (!this.wrappersMapByOrigin.has(siteOriginNoSuffix)) {
        return;
      }
      // Only remove the addon if it doesn't have any permissions left.
      if (!this.wrappersMapByOrigin.get(siteOriginNoSuffix).isUninstalled) {
        return;
      }
      this.wrappersMapByOrigin.delete(siteOriginNoSuffix);
    }
  },

  /**
   * Returns a Promise that resolves when handled the list of gated permissions
   * and setup ther observer for the "perm-changed" event.
   *
   * @returns Promise
   */
  lazyInit() {
    if (!this._initPromise) {
      this._initPromise = new Promise(resolve => {
        // Build the initial list of addons per origin
        const perms = Services.perms.getAllByTypes(GATED_PERMISSIONS);
        for (const perm of perms) {
          this.handlePermissionChange(perm);
        }
        Services.obs.addObserver(this, "perm-changed");
        resolve();
      });
    }
    return this._initPromise;
  },

  shutdown() {
    if (this._initPromise) {
      Services.obs.removeObserver(this, "perm-changed");
    }
    this.wrappersMapByOrigin.clear();
    this._initPromise = null;
  },

  /**
   * Get a SitePermsAddonWrapper from an extension id
   *
   * @param {String|null|undefined} id: The extension id,
   * @returns {SitePermsAddonWrapper|undefined}
   */
  async getAddonByID(id) {
    await this.lazyInit();
    if (!id?.endsWith?.(SITEPERMS_ADDON_ID_SUFFIX)) {
      return undefined;
    }

    for (const addon of this.wrappersMapByOrigin.values()) {
      if (addon.id === id) {
        return addon;
      }
    }
    return undefined;
  },

  /**
   * Get a list of SitePermsAddonWrapper for a given list of extension types.
   *
   * @param {Array<String>|null|undefined} types: If null or undefined is passed,
   *        the callsites expect to get all the addons from the provider, without
   *        any filtering.
   * @returns {Array<SitePermsAddonWrapper>}
   */
  async getAddonsByTypes(types) {
    if (
      !this.isEnabled ||
      // `types` can be null/undefined, and in such case we _do_ want to return the addons.
      (Array.isArray(types) && !types.includes(SITEPERMS_ADDON_TYPE))
    ) {
      return [];
    }

    await this.lazyInit();
    return Array.from(this.wrappersMapByOrigin.values());
  },

  /**
   * Create and return a SitePermsAddonInstall instance for a permission on a given principal
   *
   * @param {nsIPrincipal} installingPrincipal
   * @param {String} sitePerm
   * @returns {SitePermsAddonInstall}
   */
  getSitePermsAddonInstallForWebpage(installingPrincipal, sitePerm) {
    return new SitePermsAddonInstall(installingPrincipal, sitePerm);
  },

  get isEnabled() {
    return lazy.SITEPERMS_ADDON_PROVIDER_ENABLED;
  },

  observe(subject, topic, data) {
    if (!this.isEnabled) {
      return;
    }

    if (topic == FIRST_CONTENT_PROCESS_TOPIC) {
      Services.obs.removeObserver(this, FIRST_CONTENT_PROCESS_TOPIC);

      lazy.AddonManagerPrivate.registerProvider(SitePermsAddonProvider, [
        SITEPERMS_ADDON_TYPE,
      ]);
      Services.obs.notifyObservers(null, "sitepermsaddon-provider-registered");
    } else if (topic === "perm-changed") {
      if (data === "cleared") {
        // In such case, `subject` is null, but we can simply uninstall all existing addons.
        for (const addon of this.wrappersMapByOrigin.values()) {
          addon.uninstall();
        }
        this.wrappersMapByOrigin.clear();
        return;
      }

      const perm = subject.QueryInterface(Ci.nsIPermission);
      this.handlePermissionChange(perm, data);
    }
  },

  addFirstContentProcessObserver() {
    Services.obs.addObserver(this, FIRST_CONTENT_PROCESS_TOPIC);
  },
};

// We want to register the SitePermsAddonProvider once the first content process gets created
// (and only if the feature is also enabled through the "dom.sitepermsaddon-provider.enabled"
// about:config pref).
SitePermsAddonProvider.addFirstContentProcessObserver();
PK
!<�OeEW�W�"modules/addons/XPIDatabase.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file contains most of the logic required to maintain the
 * extensions database, including querying and modifying extension
 * metadata. In general, we try to avoid loading it during startup when
 * at all possible. Please keep that in mind when deciding whether to
 * add code here or elsewhere.
 */

/* eslint "valid-jsdoc": [2, {requireReturn: false, requireReturnDescription: false, prefer: {return: "returns"}}] */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { XPIExports } from "resource://gre/modules/addons/XPIExports.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs",
  AddonRepository: "resource://gre/modules/addons/AddonRepository.sys.mjs",
  AddonSettings: "resource://gre/modules/addons/AddonSettings.sys.mjs",
  Blocklist: "resource://gre/modules/Blocklist.sys.mjs",
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  ExtensionData: "resource://gre/modules/Extension.sys.mjs",
  ExtensionUtils: "resource://gre/modules/ExtensionUtils.sys.mjs",
  ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
  PermissionsUtils: "resource://gre/modules/PermissionsUtils.sys.mjs",
  QuarantinedDomains: "resource://gre/modules/ExtensionPermissions.sys.mjs",
  ExtensionPermissions: "resource://gre/modules/ExtensionPermissions.sys.mjs",
});

// WARNING: BuiltInThemes.sys.mjs may be provided by the host application (e.g.
// Firefox), or it might not exist at all. Use with caution, as we don't
// want things to completely fail if that module can't be loaded.
ChromeUtils.defineLazyGetter(lazy, "BuiltInThemes", () => {
  try {
    let { BuiltInThemes } = ChromeUtils.importESModule(
      "resource:///modules/BuiltInThemes.sys.mjs"
    );
    return BuiltInThemes;
  } catch (e) {
    Cu.reportError(`Unable to load BuiltInThemes.sys.mjs: ${e}`);
  }
  return undefined;
});

// A set of helpers to account from a single place that in some builds
// (e.g. GeckoView and Thunderbird) the BuiltInThemes module may either
// not be bundled at all or not be exposing the same methods provided
// by the module as defined in Firefox Desktop.
export const BuiltInThemesHelpers = {
  getLocalizedColorwayGroupName(addonId) {
    return lazy.BuiltInThemes?.getLocalizedColorwayGroupName?.(addonId);
  },

  getLocalizedColorwayDescription(addonId) {
    return lazy.BuiltInThemes?.getLocalizedColorwayGroupDescription?.(addonId);
  },

  isActiveTheme(addonId) {
    return lazy.BuiltInThemes?.isActiveTheme?.(addonId);
  },

  isRetainedExpiredTheme(addonId) {
    return lazy.BuiltInThemes?.isRetainedExpiredTheme?.(addonId);
  },

  themeIsExpired(addonId) {
    return lazy.BuiltInThemes?.themeIsExpired?.(addonId);
  },

  // Helper function called form XPInstall.sys.mjs to remove from the retained
  // themes list the built-in colorways theme that have been migrated to a non
  // built-in.
  unretainMigratedColorwayTheme(addonId) {
    lazy.BuiltInThemes?.unretainMigratedColorwayTheme?.(addonId);
  },
};

XPCOMUtils.defineLazyPreferenceGetter(
  BuiltInThemesHelpers,
  "isColorwayMigrationEnabled",
  "browser.theme.colorway-migration",
  false
);

// A temporary hidden pref just meant to be used as a last resort, in case
// we need to force-disable the "per-addon quarantined domains user controls"
// feature during the beta cycle, e.g. if unexpected issues are caught late and
// it shouldn't  ride the train.
//
// TODO(Bug 1839616): remove this pref after the user controls features have been
// released.
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "isQuarantineUIDisabled",
  "extensions.quarantinedDomains.uiDisabled",
  false
);

const { nsIBlocklistService } = Ci;

import { Log } from "resource://gre/modules/Log.sys.mjs";

const LOGGER_ID = "addons.xpi-utils";

const nsIFile = Components.Constructor(
  "@mozilla.org/file/local;1",
  "nsIFile",
  "initWithPath"
);

// Create a new logger for use by the Addons XPI Provider Utils
// (Requires AddonManager.sys.mjs)
var logger = Log.repository.getLogger(LOGGER_ID);

const FILE_JSON_DB = "extensions.json";

const PREF_DB_SCHEMA = "extensions.databaseSchema";
const PREF_EM_AUTO_DISABLED_SCOPES = "extensions.autoDisableScopes";
const PREF_PENDING_OPERATIONS = "extensions.pendingOperations";
const PREF_XPI_PERMISSIONS_BRANCH = "xpinstall.";
const PREF_XPI_SIGNATURES_DEV_ROOT = "xpinstall.signatures.dev-root";

const TOOLKIT_ID = "toolkit@mozilla.org";

const KEY_APP_SYSTEM_ADDONS = "app-system-addons";
const KEY_APP_SYSTEM_DEFAULTS = "app-system-defaults";
const KEY_APP_SYSTEM_PROFILE = "app-system-profile";
const KEY_APP_BUILTINS = "app-builtin";
const KEY_APP_SYSTEM_LOCAL = "app-system-local";
const KEY_APP_SYSTEM_SHARE = "app-system-share";
const KEY_APP_GLOBAL = "app-global";
const KEY_APP_PROFILE = "app-profile";
const KEY_APP_TEMPORARY = "app-temporary";

const DEFAULT_THEME_ID = "default-theme@mozilla.org";

// Properties to cache and reload when an addon installation is pending
const PENDING_INSTALL_METADATA = [
  "syncGUID",
  "targetApplications",
  "userDisabled",
  "softDisabled",
  "embedderDisabled",
  "sourceURI",
  "releaseNotesURI",
  "installDate",
  "updateDate",
  "applyBackgroundUpdates",
  "installTelemetryInfo",
];

// Properties to save in JSON file
const PROP_JSON_FIELDS = [
  "id",
  "syncGUID",
  "version",
  "type",
  "loader",
  "updateURL",
  "installOrigins",
  "manifestVersion",
  "optionsURL",
  "optionsType",
  "optionsBrowserStyle",
  "aboutURL",
  "defaultLocale",
  "visible",
  "active",
  "userDisabled",
  "appDisabled",
  "embedderDisabled",
  "pendingUninstall",
  "installDate",
  "updateDate",
  "applyBackgroundUpdates",
  "path",
  "skinnable",
  "sourceURI",
  "releaseNotesURI",
  "softDisabled",
  "foreignInstall",
  "strictCompatibility",
  "locales",
  "targetApplications",
  "targetPlatforms",
  "signedState",
  "signedTypes",
  "signedDate",
  "seen",
  "dependencies",
  "incognito",
  "userPermissions",
  "optionalPermissions",
  "requestedPermissions",
  "icons",
  "iconURL",
  "blocklistState",
  "blocklistURL",
  "startupData",
  "previewImage",
  "hidden",
  "installTelemetryInfo",
  "recommendationState",
  "rootURI",
];

const SIGNED_TYPES = new Set(["extension", "locale", "theme"]);

// Time to wait before async save of XPI JSON database, in milliseconds
const ASYNC_SAVE_DELAY_MS = 20;

const l10n = new Localization(["browser/appExtensionFields.ftl"], true);

/**
 * Schedules an idle task, and returns a promise which resolves to an
 * IdleDeadline when an idle slice is available. The caller should
 * perform all of its idle work in the same micro-task, before the
 * deadline is reached.
 *
 * @returns {Promise<IdleDeadline>}
 */
function promiseIdleSlice() {
  return new Promise(resolve => {
    ChromeUtils.idleDispatch(resolve);
  });
}

let arrayForEach = Function.call.bind(Array.prototype.forEach);

/**
 * Loops over the given array, in the same way as Array forEach, but
 * splitting the work among idle tasks.
 *
 * @param {Array} array
 *        The array to loop over.
 * @param {function} func
 *        The function to call on each array element.
 * @param {integer} [taskTimeMS = 5]
 *        The minimum time to allocate to each task. If less time than
 *        this is available in a given idle slice, and there are more
 *        elements to loop over, they will be deferred until the next
 *        idle slice.
 */
async function idleForEach(array, func, taskTimeMS = 5) {
  let deadline;
  for (let i = 0; i < array.length; i++) {
    if (!deadline || deadline.timeRemaining() < taskTimeMS) {
      deadline = await promiseIdleSlice();
    }
    func(array[i], i);
  }
}

/**
 * Asynchronously fill in the _repositoryAddon field for one addon
 *
 * @param {AddonInternal} aAddon
 *        The add-on to annotate.
 * @returns {AddonInternal}
 *        The annotated add-on.
 */
async function getRepositoryAddon(aAddon) {
  if (aAddon) {
    aAddon._repositoryAddon = await lazy.AddonRepository.getCachedAddonByID(
      aAddon.id
    );
  }
  return aAddon;
}

/**
 * Copies properties from one object to another. If no target object is passed
 * a new object will be created and returned.
 *
 * @param {object} aObject
 *        An object to copy from
 * @param {string[]} aProperties
 *        An array of properties to be copied
 * @param {object?} [aTarget]
 *        An optional target object to copy the properties to
 * @returns {Object}
 *        The object that the properties were copied onto
 */
function copyProperties(aObject, aProperties, aTarget) {
  if (!aTarget) {
    aTarget = {};
  }
  aProperties.forEach(function (aProp) {
    if (aProp in aObject) {
      aTarget[aProp] = aObject[aProp];
    }
  });
  return aTarget;
}

// Maps instances of AddonInternal to AddonWrapper
const wrapperMap = new WeakMap();
let addonFor = wrapper => wrapperMap.get(wrapper);

const EMPTY_ARRAY = Object.freeze([]);

let AddonWrapper;

/**
 * The AddonInternal is an internal only representation of add-ons. It
 * may have come from the database or an extension manifest.
 */
export class AddonInternal {
  constructor(addonData) {
    this._wrapper = null;
    this._selectedLocale = null;
    this.active = false;
    this.visible = false;
    this.userDisabled = false;
    this.appDisabled = false;
    this.softDisabled = false;
    this.embedderDisabled = false;
    this.blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
    this.blocklistURL = null;
    this.sourceURI = null;
    this.releaseNotesURI = null;
    this.foreignInstall = false;
    this.seen = true;
    this.skinnable = false;
    this.startupData = null;
    this._hidden = false;
    this.installTelemetryInfo = null;
    this.rootURI = null;
    this._updateInstall = null;
    this.recommendationState = null;

    this.inDatabase = false;

    /**
     * @property {Array<string>} dependencies
     *   An array of bootstrapped add-on IDs on which this add-on depends.
     *   The add-on will remain appDisabled if any of the dependent
     *   add-ons is not installed and enabled.
     */
    this.dependencies = EMPTY_ARRAY;

    if (addonData) {
      copyProperties(addonData, PROP_JSON_FIELDS, this);
      this.location = addonData.location;

      if (!this.dependencies) {
        this.dependencies = [];
      }
      Object.freeze(this.dependencies);

      if (this.location) {
        this.addedToDatabase();
      }

      this.sourceBundle = addonData._sourceBundle;
    }
  }

  get sourceBundle() {
    return this._sourceBundle;
  }

  set sourceBundle(file) {
    this._sourceBundle = file;
    if (file) {
      this.rootURI = XPIExports.XPIInternal.getURIForResourceInFile(
        file,
        ""
      ).spec;
    }
  }

  get wrapper() {
    if (!this._wrapper) {
      this._wrapper = new AddonWrapper(this);
    }
    return this._wrapper;
  }

  get resolvedRootURI() {
    return XPIExports.XPIInternal.maybeResolveURI(
      Services.io.newURI(this.rootURI)
    );
  }

  get isBuiltinColorwayTheme() {
    return (
      this.type === "theme" &&
      this.location.isBuiltin &&
      this.id.endsWith("-colorway@mozilla.org")
    );
  }

  /**
   * Validate a list of origins are contained in the installOrigins array (defined in manifest.json).
   *
   * SitePermission addons are a special case, where the triggering install site may be a subdomain
   * of a valid xpi origin.
   *
   * @param {Object}  origins             Object containing URIs related to install.
   * @params {nsIURI} origins.installFrom The nsIURI of the website that has triggered the install flow.
   * @params {nsIURI} origins.source      The nsIURI where the xpi is hosted.
   * @returns {boolean}
   */
  validInstallOrigins({ installFrom, source }) {
    if (
      !Services.prefs.getBoolPref("extensions.install_origins.enabled", true)
    ) {
      return true;
    }

    let { installOrigins, manifestVersion } = this;
    if (!installOrigins) {
      // Install origins are mandatory in MV3 and optional
      // in MV2.  Old addons need to keep installing per the
      // old install flow.
      return manifestVersion < 3;
    }
    // An empty install_origins prevents any install from 3rd party websites.
    if (!installOrigins.length) {
      return false;
    }

    for (const [name, uri] of Object.entries({ installFrom, source })) {
      if (!installOrigins.includes(new URL(uri.spec).origin)) {
        logger.warn(
          `Addon ${this.id} Installation not allowed, ${name} "${uri.spec}" is not included in the Addon install_origins`
        );
        return false;
      }
    }
    return true;
  }

  addedToDatabase() {
    this._key = `${this.location.name}:${this.id}`;
    this.inDatabase = true;
  }

  get isWebExtension() {
    return this.loader == null;
  }

  get selectedLocale() {
    if (this._selectedLocale) {
      return this._selectedLocale;
    }

    /**
     * this.locales is a list of objects that have property `locales`.
     * It's value is an array of locale codes.
     *
     * First, we reduce this nested structure to a flat list of locale codes.
     */
    const locales = [].concat(...this.locales.map(loc => loc.locales));

    let requestedLocales = Services.locale.requestedLocales;

    /**
     * If en-US is not in the list, add it as the last fallback.
     */
    if (!requestedLocales.includes("en-US")) {
      requestedLocales.push("en-US");
    }

    /**
     * Then we negotiate best locale code matching the app locales.
     */
    let bestLocale = Services.locale.negotiateLanguages(
      requestedLocales,
      locales,
      "und",
      Services.locale.langNegStrategyLookup
    )[0];

    /**
     * If no match has been found, we'll assign the default locale as
     * the selected one.
     */
    if (bestLocale === "und") {
      this._selectedLocale = this.defaultLocale;
    } else {
      /**
       * Otherwise, we'll go through all locale entries looking for the one
       * that has the best match in it's locales list.
       */
      this._selectedLocale = this.locales.find(loc =>
        loc.locales.includes(bestLocale)
      );
    }

    return this._selectedLocale;
  }

  get providesUpdatesSecurely() {
    return !this.updateURL || this.updateURL.startsWith("https:");
  }

  get isCorrectlySigned() {
    switch (this.location.name) {
      case KEY_APP_SYSTEM_PROFILE:
        // Add-ons installed via Normandy must be signed by the system
        // key or the "Mozilla Extensions" key.
        return [
          lazy.AddonManager.SIGNEDSTATE_SYSTEM,
          lazy.AddonManager.SIGNEDSTATE_PRIVILEGED,
        ].includes(this.signedState);
      case KEY_APP_SYSTEM_ADDONS:
        // System add-ons must be signed by the system key.
        return this.signedState == lazy.AddonManager.SIGNEDSTATE_SYSTEM;

      case KEY_APP_SYSTEM_DEFAULTS:
      case KEY_APP_BUILTINS:
      case KEY_APP_TEMPORARY:
        // Temporary and built-in add-ons do not require signing.
        return true;

      case KEY_APP_SYSTEM_SHARE:
      case KEY_APP_SYSTEM_LOCAL:
        // On UNIX platforms except OSX, an additional location for system
        // add-ons exists in /usr/{lib,share}/mozilla/extensions. Add-ons
        // installed there do not require signing.
        if (Services.appinfo.OS != "Darwin") {
          return true;
        }
        break;
    }

    if (this.signedState === lazy.AddonManager.SIGNEDSTATE_NOT_REQUIRED) {
      return true;
    }
    return this.signedState > lazy.AddonManager.SIGNEDSTATE_MISSING;
  }

  get isCompatible() {
    return this.isCompatibleWith();
  }

  get isPrivileged() {
    return lazy.ExtensionData.getIsPrivileged({
      signedState: this.signedState,
      builtIn: this.location.isBuiltin,
      temporarilyInstalled: this.location.isTemporary,
    });
  }

  get hidden() {
    return (
      this.location.hidden ||
      // The hidden flag is intended to only be used for features that are part
      // of the application. Temporary add-ons should not be hidden.
      (this._hidden && this.isPrivileged && !this.location.isTemporary) ||
      false
    );
  }

  set hidden(val) {
    this._hidden = val;
  }

  get disabled() {
    return (
      this.userDisabled ||
      this.appDisabled ||
      this.softDisabled ||
      this.embedderDisabled
    );
  }

  get isPlatformCompatible() {
    if (!this.targetPlatforms.length) {
      return true;
    }

    let matchedOS = false;

    // If any targetPlatform matches the OS and contains an ABI then we will
    // only match a targetPlatform that contains both the current OS and ABI
    let needsABI = false;

    // Some platforms do not specify an ABI, test against null in that case.
    let abi = null;
    try {
      abi = Services.appinfo.XPCOMABI;
    } catch (e) {}

    // Something is causing errors in here
    try {
      for (let platform of this.targetPlatforms) {
        if (platform.os == Services.appinfo.OS) {
          if (platform.abi) {
            needsABI = true;
            if (platform.abi === abi) {
              return true;
            }
          } else {
            matchedOS = true;
          }
        }
      }
    } catch (e) {
      let message =
        "Problem with addon " +
        this.id +
        " targetPlatforms " +
        JSON.stringify(this.targetPlatforms);
      logger.error(message, e);
      lazy.AddonManagerPrivate.recordException("XPI", message, e);
      // don't trust this add-on
      return false;
    }

    return matchedOS && !needsABI;
  }

  isCompatibleWith(aAppVersion, aPlatformVersion) {
    let app = this.matchingTargetApplication;
    if (!app) {
      return false;
    }

    // set reasonable defaults for minVersion and maxVersion
    let minVersion = app.minVersion || "0";
    let maxVersion = app.maxVersion || "*";

    if (!aAppVersion) {
      aAppVersion = Services.appinfo.version;
    }
    if (!aPlatformVersion) {
      aPlatformVersion = Services.appinfo.platformVersion;
    }

    let version;
    if (app.id == Services.appinfo.ID) {
      version = aAppVersion;
    } else if (app.id == TOOLKIT_ID) {
      version = aPlatformVersion;
    }

    // Only extensions and dictionaries can be compatible by default; themes
    // and language packs always use strict compatibility checking.
    // Dictionaries are compatible by default unless requested by the dictinary.
    if (
      !this.strictCompatibility &&
      (!lazy.AddonManager.strictCompatibility || this.type == "dictionary")
    ) {
      return Services.vc.compare(version, minVersion) >= 0;
    }

    return (
      Services.vc.compare(version, minVersion) >= 0 &&
      Services.vc.compare(version, maxVersion) <= 0
    );
  }

  get matchingTargetApplication() {
    let app = null;
    for (let targetApp of this.targetApplications) {
      if (targetApp.id == Services.appinfo.ID) {
        return targetApp;
      }
      if (targetApp.id == TOOLKIT_ID) {
        app = targetApp;
      }
    }
    return app;
  }

  async findBlocklistEntry() {
    return lazy.Blocklist.getAddonBlocklistEntry(this.wrapper);
  }

  async updateBlocklistState(options = {}) {
    if (this.location.isSystem || this.location.isBuiltin) {
      return;
    }

    let { applySoftBlock = true, updateDatabase = true } = options;

    let oldState = this.blocklistState;

    let entry = await this.findBlocklistEntry();
    let newState = entry ? entry.state : Services.blocklist.STATE_NOT_BLOCKED;

    this.blocklistState = newState;
    this.blocklistURL = entry && entry.url;

    let userDisabled, softDisabled;
    // After a blocklist update, the blocklist service manually applies
    // new soft blocks after displaying a UI, in which cases we need to
    // skip updating it here.
    if (applySoftBlock && oldState != newState) {
      if (newState == Services.blocklist.STATE_SOFTBLOCKED) {
        if (this.type == "theme") {
          userDisabled = true;
        } else {
          softDisabled = !this.userDisabled;
        }
      } else {
        softDisabled = false;
      }
    }

    if (this.inDatabase && updateDatabase) {
      await XPIDatabase.updateAddonDisabledState(this, {
        userDisabled,
        softDisabled,
      });
      XPIDatabase.saveChanges();
    } else {
      this.appDisabled = !XPIDatabase.isUsableAddon(this);
      if (userDisabled !== undefined) {
        this.userDisabled = userDisabled;
      }
      if (softDisabled !== undefined) {
        this.softDisabled = softDisabled;
      }
    }
  }

  recordAddonBlockChangeTelemetry(reason) {
    lazy.Blocklist.recordAddonBlockChangeTelemetry(this.wrapper, reason);
  }

  async setUserDisabled(val, allowSystemAddons = false) {
    if (val == (this.userDisabled || this.softDisabled)) {
      return;
    }

    if (this.inDatabase) {
      // System add-ons should not be user disabled, as there is no UI to
      // re-enable them.
      if (this.location.isSystem && !allowSystemAddons) {
        throw new Error(`Cannot disable system add-on ${this.id}`);
      }
      await XPIDatabase.updateAddonDisabledState(this, { userDisabled: val });
    } else {
      this.userDisabled = val;
      // When enabling remove the softDisabled flag
      if (!val) {
        this.softDisabled = false;
      }
    }
  }

  applyCompatibilityUpdate(aUpdate, aSyncCompatibility) {
    let wasCompatible = this.isCompatible;

    for (let targetApp of this.targetApplications) {
      for (let updateTarget of aUpdate.targetApplications) {
        if (
          targetApp.id == updateTarget.id &&
          (aSyncCompatibility ||
            Services.vc.compare(targetApp.maxVersion, updateTarget.maxVersion) <
              0)
        ) {
          targetApp.minVersion = updateTarget.minVersion;
          targetApp.maxVersion = updateTarget.maxVersion;

          if (this.inDatabase) {
            XPIDatabase.saveChanges();
          }
        }
      }
    }

    if (wasCompatible != this.isCompatible) {
      if (this.inDatabase) {
        XPIDatabase.updateAddonDisabledState(this);
      } else {
        this.appDisabled = !XPIDatabase.isUsableAddon(this);
      }
    }
  }

  toJSON() {
    let obj = copyProperties(this, PROP_JSON_FIELDS);
    obj.location = this.location.name;
    return obj;
  }

  /**
   * When an add-on install is pending its metadata will be cached in a file.
   * This method reads particular properties of that metadata that may be newer
   * than that in the extension manifest, like compatibility information.
   *
   * @param {Object} aObj
   *        A JS object containing the cached metadata
   */
  importMetadata(aObj) {
    for (let prop of PENDING_INSTALL_METADATA) {
      if (!(prop in aObj)) {
        continue;
      }

      this[prop] = aObj[prop];
    }

    // Compatibility info may have changed so update appDisabled
    this.appDisabled = !XPIDatabase.isUsableAddon(this);
  }

  permissions() {
    let permissions = 0;

    // The permission to "toggle the private browsing access" is locked down
    // when the extension has opted out or it gets the permission automatically
    // on every extension startup (as system, privileged and builtin addons).
    if (
      this.type === "extension" &&
      this.incognito !== "not_allowed" &&
      this.signedState !== lazy.AddonManager.SIGNEDSTATE_PRIVILEGED &&
      this.signedState !== lazy.AddonManager.SIGNEDSTATE_SYSTEM &&
      !this.location.isBuiltin
    ) {
      // NOTE: This permission is computed even for addons not in the database because
      // it is being used in the first dialog part of the install flow, when the addon
      // may not be installed yet (and so also not in the database), to determine if
      // the private browsing permission toggle button should be shown.
      permissions |= lazy.AddonManager.PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS;
    }

    // Add-ons that aren't installed cannot be modified in any way
    if (!this.inDatabase) {
      return permissions;
    }

    if (!this.appDisabled) {
      if (this.userDisabled || this.softDisabled) {
        permissions |= lazy.AddonManager.PERM_CAN_ENABLE;
      } else if (this.type != "theme" || this.id != DEFAULT_THEME_ID) {
        // We do not expose disabling the default theme.
        permissions |= lazy.AddonManager.PERM_CAN_DISABLE;
      }
    }

    // Add-ons that are in locked install locations, or are pending uninstall
    // cannot be uninstalled or upgraded.  One caveat is extensions sideloaded
    // from non-profile locations. Since Firefox 73(?), new sideloaded extensions
    // from outside the profile have not been installed so any such extensions
    // must be from an older profile. Users may uninstall such an extension which
    // removes the related state from this profile but leaves the actual file alone
    // (since it is outside this profile and may be in use in other profiles)
    let changesAllowed = !this.location.locked && !this.pendingUninstall;
    if (changesAllowed) {
      // System add-on upgrades are triggered through a different mechanism (see updateSystemAddons())
      // Builtin addons are only upgraded with Firefox (or app) updates.
      let isSystem = this.location.isSystem || this.location.isBuiltin;
      // Add-ons that are installed by a file link cannot be upgraded.
      if (!isSystem && !this.location.isLinkedAddon(this.id)) {
        permissions |= lazy.AddonManager.PERM_CAN_UPGRADE;
      }
      // Allow active and retained colorways builtin themes to be updated to
      // the same theme hosted on AMO (the PERM_CAN_UPGRADE permission will
      // ensure we will be asking AMO for an update, then the AMO addon xpi
      // will be installed in the profile location, overridden in the
      // `createUpdate` defined in `XPIInstall.sys.mjs` and called from
      // `UpdateChecker` `onUpdateCheckComplete` method).
      if (
        this.isBuiltinColorwayTheme &&
        BuiltInThemesHelpers.isColorwayMigrationEnabled &&
        BuiltInThemesHelpers.themeIsExpired(this.id) &&
        (BuiltInThemesHelpers.isActiveTheme(this.id) ||
          BuiltInThemesHelpers.isRetainedExpiredTheme(this.id))
      ) {
        permissions |= lazy.AddonManager.PERM_CAN_UPGRADE;
      }
    }

    // We allow uninstall of legacy sideloaded extensions, even when in locked locations,
    // but we do not remove the addon file in that case.
    let isLegacySideload =
      this.foreignInstall &&
      !(this.location.scope & lazy.AddonSettings.SCOPES_SIDELOAD);
    if (changesAllowed || isLegacySideload) {
      permissions |= lazy.AddonManager.PERM_API_CAN_UNINSTALL;
      if (!this.location.isBuiltin) {
        permissions |= lazy.AddonManager.PERM_CAN_UNINSTALL;
      }
    }

    if (Services.policies) {
      if (!Services.policies.isAllowed(`uninstall-extension:${this.id}`)) {
        permissions &= ~lazy.AddonManager.PERM_CAN_UNINSTALL;
      }
      if (!Services.policies.isAllowed(`disable-extension:${this.id}`)) {
        permissions &= ~lazy.AddonManager.PERM_CAN_DISABLE;
      }
      if (Services.policies.getExtensionSettings(this.id)?.updates_disabled) {
        permissions &= ~lazy.AddonManager.PERM_CAN_UPGRADE;
      }
    }

    return permissions;
  }

  propagateDisabledState(oldAddon) {
    if (oldAddon) {
      this.userDisabled = oldAddon.userDisabled;
      this.embedderDisabled = oldAddon.embedderDisabled;
      this.softDisabled = oldAddon.softDisabled;
      this.blocklistState = oldAddon.blocklistState;
    }
  }
}

/**
 * The AddonWrapper wraps an Addon to provide the data visible to consumers of
 * the public API.
 *
 * NOTE: Do not add any new logic here.  Add it to AddonInternal and expose
 * through defineAddonWrapperProperty after this class definition.
 *
 * @param {AddonInternal} aAddon
 *        The add-on object to wrap.
 */
AddonWrapper = class {
  constructor(aAddon) {
    wrapperMap.set(this, aAddon);
  }

  get __AddonInternal__() {
    return addonFor(this);
  }

  get quarantineIgnoredByApp() {
    return this.isPrivileged || !!this.recommendationStates?.length;
  }

  get quarantineIgnoredByUser() {
    // NOTE: confirm if this getter could be replaced by a
    // lazy preference getter and the addon wrapper to not be
    // kept around longer by the pref observer registered
    // internally by the lazy getter.
    return lazy.QuarantinedDomains.isUserAllowedAddonId(this.id);
  }

  set quarantineIgnoredByUser(val) {
    lazy.QuarantinedDomains.setUserAllowedAddonIdPref(this.id, !!val);
  }

  get canChangeQuarantineIgnored() {
    // Never show the quarantined domains user controls UI if the
    // quarantined domains feature is disabled.
    return (
      WebExtensionPolicy.quarantinedDomainsEnabled &&
      !lazy.isQuarantineUIDisabled &&
      this.type === "extension" &&
      !this.quarantineIgnoredByApp
    );
  }

  get seen() {
    return addonFor(this).seen;
  }

  markAsSeen() {
    addonFor(this).seen = true;
    XPIDatabase.saveChanges();
  }

  get installTelemetryInfo() {
    const addon = addonFor(this);
    if (!addon.installTelemetryInfo && addon.location) {
      if (addon.location.isSystem) {
        return { source: "system-addon" };
      }

      if (addon.location.isTemporary) {
        return { source: "temporary-addon" };
      }
    }

    return addon.installTelemetryInfo;
  }

  get temporarilyInstalled() {
    return addonFor(this).location.isTemporary;
  }

  get aboutURL() {
    return this.isActive ? addonFor(this).aboutURL : null;
  }

  get optionsURL() {
    if (!this.isActive) {
      return null;
    }

    let addon = addonFor(this);
    if (addon.optionsURL) {
      if (this.isWebExtension) {
        // The internal object's optionsURL property comes from the addons
        // DB and should be a relative URL.  However, extensions with
        // options pages installed before bug 1293721 was fixed got absolute
        // URLs in the addons db.  This code handles both cases.
        let policy = WebExtensionPolicy.getByID(addon.id);
        if (!policy) {
          return null;
        }
        let base = policy.getURL();
        return new URL(addon.optionsURL, base).href;
      }
      return addon.optionsURL;
    }

    return null;
  }

  get optionsType() {
    if (!this.isActive) {
      return null;
    }

    let addon = addonFor(this);
    let hasOptionsURL = !!this.optionsURL;

    if (addon.optionsType) {
      switch (parseInt(addon.optionsType, 10)) {
        case lazy.AddonManager.OPTIONS_TYPE_TAB:
        case lazy.AddonManager.OPTIONS_TYPE_INLINE_BROWSER:
          return hasOptionsURL ? addon.optionsType : null;
      }
      return null;
    }

    return null;
  }

  get optionsBrowserStyle() {
    let addon = addonFor(this);
    return addon.optionsBrowserStyle;
  }

  get incognito() {
    return addonFor(this).incognito;
  }

  async getBlocklistURL() {
    return addonFor(this).blocklistURL;
  }

  get iconURL() {
    return lazy.AddonManager.getPreferredIconURL(this, 48);
  }

  get icons() {
    let addon = addonFor(this);
    let icons = {};

    if (addon._repositoryAddon) {
      for (let size in addon._repositoryAddon.icons) {
        icons[size] = addon._repositoryAddon.icons[size];
      }
    }

    if (addon.icons) {
      for (let size in addon.icons) {
        let path = addon.icons[size].replace(/^\//, "");
        icons[size] = this.getResourceURI(path).spec;
      }
    }

    let canUseIconURLs = this.isActive;
    if (canUseIconURLs && addon.iconURL) {
      icons[32] = addon.iconURL;
      icons[48] = addon.iconURL;
    }

    Object.freeze(icons);
    return icons;
  }

  get screenshots() {
    let addon = addonFor(this);
    let repositoryAddon = addon._repositoryAddon;
    if (repositoryAddon && "screenshots" in repositoryAddon) {
      let repositoryScreenshots = repositoryAddon.screenshots;
      if (repositoryScreenshots && repositoryScreenshots.length) {
        return repositoryScreenshots;
      }
    }

    if (addon.previewImage) {
      let url = this.getResourceURI(addon.previewImage).spec;
      return [new lazy.AddonManagerPrivate.AddonScreenshot(url)];
    }

    return null;
  }

  get recommendationStates() {
    let addon = addonFor(this);
    let state = addon.recommendationState;
    if (
      state &&
      state.validNotBefore < addon.updateDate &&
      state.validNotAfter > addon.updateDate &&
      addon.isCorrectlySigned &&
      !this.temporarilyInstalled
    ) {
      return state.states;
    }
    return [];
  }

  // NOTE: this boolean getter doesn't return true for all recommendation
  // states at the moment. For the states actually supported on the autograph
  // side see:
  // https://github.com/mozilla-services/autograph/blob/8a34847a/autograph.yaml#L1456-L1460
  get isRecommended() {
    return this.recommendationStates.includes("recommended");
  }

  get canBypassThirdParyInstallPrompt() {
    // We only bypass if the extension is signed (to support distributions
    // that turn off the signing requirement) and has recommendation states,
    // or the extension is signed as privileged.
    return (
      this.signedState == lazy.AddonManager.SIGNEDSTATE_PRIVILEGED ||
      (this.signedState >= lazy.AddonManager.SIGNEDSTATE_SIGNED &&
        this.recommendationStates.length)
    );
  }

  get applyBackgroundUpdates() {
    return addonFor(this).applyBackgroundUpdates;
  }
  set applyBackgroundUpdates(val) {
    let addon = addonFor(this);
    if (
      val != lazy.AddonManager.AUTOUPDATE_DEFAULT &&
      val != lazy.AddonManager.AUTOUPDATE_DISABLE &&
      val != lazy.AddonManager.AUTOUPDATE_ENABLE
    ) {
      val = val
        ? lazy.AddonManager.AUTOUPDATE_DEFAULT
        : lazy.AddonManager.AUTOUPDATE_DISABLE;
    }

    if (val == addon.applyBackgroundUpdates) {
      return;
    }

    XPIDatabase.setAddonProperties(addon, {
      applyBackgroundUpdates: val,
    });
    lazy.AddonManagerPrivate.callAddonListeners("onPropertyChanged", this, [
      "applyBackgroundUpdates",
    ]);
  }

  set syncGUID(val) {
    let addon = addonFor(this);
    if (addon.syncGUID == val) {
      return;
    }

    if (addon.inDatabase) {
      XPIDatabase.setAddonSyncGUID(addon, val);
    }

    addon.syncGUID = val;
  }

  get install() {
    let addon = addonFor(this);
    if (!("_install" in addon) || !addon._install) {
      return null;
    }
    return addon._install.wrapper;
  }

  get updateInstall() {
    let addon = addonFor(this);
    return addon._updateInstall ? addon._updateInstall.wrapper : null;
  }

  get pendingUpgrade() {
    let addon = addonFor(this);
    return addon.pendingUpgrade ? addon.pendingUpgrade.wrapper : null;
  }

  get scope() {
    let addon = addonFor(this);
    if (addon.location) {
      return addon.location.scope;
    }

    return lazy.AddonManager.SCOPE_PROFILE;
  }

  get pendingOperations() {
    let addon = addonFor(this);
    let pending = 0;
    if (!addon.inDatabase) {
      // Add-on is pending install if there is no associated install (shouldn't
      // happen here) or if the install is in the process of or has successfully
      // completed the install. If an add-on is pending install then we ignore
      // any other pending operations.
      if (
        !addon._install ||
        addon._install.state == lazy.AddonManager.STATE_INSTALLING ||
        addon._install.state == lazy.AddonManager.STATE_INSTALLED
      ) {
        return lazy.AddonManager.PENDING_INSTALL;
      }
    } else if (addon.pendingUninstall) {
      // If an add-on is pending uninstall then we ignore any other pending
      // operations
      return lazy.AddonManager.PENDING_UNINSTALL;
    }

    if (addon.active && addon.disabled) {
      pending |= lazy.AddonManager.PENDING_DISABLE;
    } else if (!addon.active && !addon.disabled) {
      pending |= lazy.AddonManager.PENDING_ENABLE;
    }

    if (addon.pendingUpgrade) {
      pending |= lazy.AddonManager.PENDING_UPGRADE;
    }

    return pending;
  }

  get operationsRequiringRestart() {
    return 0;
  }

  get isDebuggable() {
    return this.isActive;
  }

  get permissions() {
    return addonFor(this).permissions();
  }

  get isActive() {
    let addon = addonFor(this);
    if (!addon.active) {
      return false;
    }
    if (!Services.appinfo.inSafeMode) {
      return true;
    }
    return XPIExports.XPIInternal.canRunInSafeMode(addon);
  }

  get startupPromise() {
    let addon = addonFor(this);
    if (!this.isActive) {
      return null;
    }

    let activeAddon = XPIExports.XPIProvider.activeAddons.get(addon.id);
    if (activeAddon) {
      return activeAddon.startupPromise || null;
    }
    return null;
  }

  updateBlocklistState(applySoftBlock = true) {
    return addonFor(this).updateBlocklistState({ applySoftBlock });
  }

  get userDisabled() {
    let addon = addonFor(this);
    return addon.softDisabled || addon.userDisabled;
  }

  /**
   * Get the embedderDisabled property for this addon.
   *
   * This is intended for embedders of Gecko like GeckoView apps to control
   * which addons are usable on their app.
   *
   * @returns {boolean}
   */
  get embedderDisabled() {
    if (!lazy.AddonSettings.IS_EMBEDDED) {
      return undefined;
    }

    return addonFor(this).embedderDisabled;
  }

  /**
   * Set the embedderDisabled property for this addon.
   *
   * This is intended for embedders of Gecko like GeckoView apps to control
   * which addons are usable on their app.
   *
   * Embedders can disable addons for various reasons, e.g. the addon is not
   * compatible with their implementation of the WebExtension API.
   *
   * When an addon is embedderDisabled it will behave like it was appDisabled.
   *
   * @param {boolean} val
   *        whether this addon should be embedder disabled or not.
   */
  async setEmbedderDisabled(val) {
    if (!lazy.AddonSettings.IS_EMBEDDED) {
      throw new Error("Setting embedder disabled while not embedding.");
    }

    let addon = addonFor(this);
    if (addon.embedderDisabled == val) {
      return val;
    }

    if (addon.inDatabase) {
      await XPIDatabase.updateAddonDisabledState(addon, {
        embedderDisabled: val,
      });
    } else {
      addon.embedderDisabled = val;
    }

    return val;
  }

  enable(options = {}) {
    const { allowSystemAddons = false } = options;
    return addonFor(this).setUserDisabled(false, allowSystemAddons);
  }

  disable(options = {}) {
    const { allowSystemAddons = false } = options;
    return addonFor(this).setUserDisabled(true, allowSystemAddons);
  }

  async setSoftDisabled(val) {
    let addon = addonFor(this);
    if (val == addon.softDisabled) {
      return val;
    }

    if (addon.inDatabase) {
      // When softDisabling a theme just enable the active theme
      if (addon.type === "theme" && val && !addon.userDisabled) {
        if (addon.isWebExtension) {
          await XPIDatabase.updateAddonDisabledState(addon, {
            softDisabled: val,
          });
        }
      } else {
        await XPIDatabase.updateAddonDisabledState(addon, {
          softDisabled: val,
        });
      }
    } else if (!addon.userDisabled) {
      // Only set softDisabled if not already disabled
      addon.softDisabled = val;
    }

    return val;
  }

  get isPrivileged() {
    return addonFor(this).isPrivileged;
  }

  get hidden() {
    return addonFor(this).hidden;
  }

  get isSystem() {
    let addon = addonFor(this);
    return addon.location.isSystem;
  }

  get isBuiltin() {
    return addonFor(this).location.isBuiltin;
  }

  // Returns true if Firefox Sync should sync this addon. Only addons
  // in the profile install location are considered syncable.
  get isSyncable() {
    let addon = addonFor(this);
    return addon.location.name == KEY_APP_PROFILE;
  }

  /**
   * Returns true if the addon is configured to be installed
   * by enterprise policy.
   */
  get isInstalledByEnterprisePolicy() {
    const policySettings = Services.policies?.getExtensionSettings(this.id);
    return ["force_installed", "normal_installed"].includes(
      policySettings?.installation_mode
    );
  }

  /**
   * Required permissions that extension has access to based on its manifest.
   * In mv3 this doesn't include host_permissions.
   */
  get userPermissions() {
    return addonFor(this).userPermissions;
  }

  get optionalPermissions() {
    return addonFor(this).optionalPermissions;
  }

  /**
   * Additional permissions that extension is requesting in its manifest.
   * Currently this is host_permissions in MV3.
   */
  get requestedPermissions() {
    return addonFor(this).requestedPermissions;
  }

  /**
   * A helper that returns all permissions for the install prompt.
   */
  get installPermissions() {
    let required = this.userPermissions;
    if (!required) {
      return null;
    }
    let requested = this.requestedPermissions;
    // Currently this can't result in duplicates, but if logic of what goes
    // into these lists changes, make sure to check for dupes.
    let perms = {
      origins: required.origins.concat(requested?.origins ?? []),
      permissions: required.permissions.concat(requested?.permissions ?? []),
    };
    return perms;
  }

  get optionalOriginsNormalized() {
    const { permissions } = this.userPermissions ?? {};

    const priv = this.isPrivileged && permissions?.includes("mozillaAddons");
    const mps = new MatchPatternSet(this.optionalPermissions?.origins ?? [], {
      restrictSchemes: !priv,
      ignorePath: true,
    });

    let temp = [...lazy.ExtensionPermissions.tempOrigins.get(this.id)];
    let origins = [
      ...mps.patterns.map(matcher => matcher.pattern),
      ...temp.filter(o =>
        // Make sure origins are still in the current set of optional
        // permissions, which might have changed on extension update.
        mps.subsumes(new MatchPattern(o, { restrictSchemes: !priv }))
      ),
    ];

    // De-dup the normalized host permission patterns.
    return [...new Set(origins)];
  }

  isCompatibleWith(aAppVersion, aPlatformVersion) {
    return addonFor(this).isCompatibleWith(aAppVersion, aPlatformVersion);
  }

  async uninstall(alwaysAllowUndo) {
    let addon = addonFor(this);
    return XPIExports.XPIInstall.uninstallAddon(addon, alwaysAllowUndo);
  }

  cancelUninstall() {
    let addon = addonFor(this);
    XPIExports.XPIInstall.cancelUninstallAddon(addon);
  }

  findUpdates(aListener, aReason, aAppVersion, aPlatformVersion) {
    new XPIExports.UpdateChecker(
      addonFor(this),
      aListener,
      aReason,
      aAppVersion,
      aPlatformVersion
    );
  }

  // Returns true if there was an update in progress, false if there was no update to cancel
  cancelUpdate() {
    let addon = addonFor(this);
    if (addon._updateCheck) {
      addon._updateCheck.cancel();
      return true;
    }
    return false;
  }

  /**
   * Reloads the add-on.
   *
   * For temporarily installed add-ons, this uninstalls and re-installs the
   * add-on. Otherwise, the addon is disabled and then re-enabled, and the cache
   * is flushed.
   */
  async reload() {
    const addon = addonFor(this);

    logger.debug(`reloading add-on ${addon.id}`);

    if (!this.temporarilyInstalled) {
      await XPIDatabase.updateAddonDisabledState(addon, { userDisabled: true });
      await XPIDatabase.updateAddonDisabledState(addon, {
        userDisabled: false,
      });
    } else {
      // This function supports re-installing an existing add-on.
      await lazy.AddonManager.installTemporaryAddon(addon._sourceBundle);
    }
  }

  /**
   * Returns a URI to the selected resource or to the add-on bundle if aPath
   * is null. URIs to the bundle will always be file: URIs. URIs to resources
   * will be file: URIs if the add-on is unpacked or jar: URIs if the add-on is
   * still an XPI file.
   *
   * @param {string?} aPath
   *        The path in the add-on to get the URI for or null to get a URI to
   *        the file or directory the add-on is installed as.
   * @returns {nsIURI}
   */
  getResourceURI(aPath) {
    let addon = addonFor(this);
    let url = Services.io.newURI(addon.rootURI);
    if (aPath) {
      if (aPath.startsWith("/")) {
        throw new Error("getResourceURI() must receive a relative path");
      }
      url = Services.io.newURI(aPath, null, url);
    }
    return url;
  }
};

function chooseValue(aAddon, aObj, aProp) {
  let repositoryAddon = aAddon._repositoryAddon;
  let objValue = aObj[aProp];

  if (
    repositoryAddon &&
    aProp in repositoryAddon &&
    (aProp === "creator" || objValue == null)
  ) {
    return [repositoryAddon[aProp], true];
  }

  return [objValue, false];
}

function defineAddonWrapperProperty(name, getter) {
  Object.defineProperty(AddonWrapper.prototype, name, {
    get: getter,
    enumerable: true,
  });
}

[
  "id",
  "syncGUID",
  "version",
  "type",
  "isWebExtension",
  "isCompatible",
  "isPlatformCompatible",
  "providesUpdatesSecurely",
  "blocklistState",
  "appDisabled",
  "softDisabled",
  "skinnable",
  "foreignInstall",
  "strictCompatibility",
  "updateURL",
  "installOrigins",
  "manifestVersion",
  "validInstallOrigins",
  "dependencies",
  "signedState",
  "signedTypes",
  "isCorrectlySigned",
  "isBuiltinColorwayTheme",
].forEach(function (aProp) {
  defineAddonWrapperProperty(aProp, function () {
    let addon = addonFor(this);
    return aProp in addon ? addon[aProp] : undefined;
  });
});

[
  "fullDescription",
  "supportURL",
  "contributionURL",
  "averageRating",
  "reviewCount",
  "reviewURL",
  "weeklyDownloads",
  "amoListingURL",
].forEach(function (aProp) {
  defineAddonWrapperProperty(aProp, function () {
    let addon = addonFor(this);
    if (addon._repositoryAddon) {
      return addon._repositoryAddon[aProp];
    }

    return null;
  });
});

["installDate", "updateDate"].forEach(function (aProp) {
  defineAddonWrapperProperty(aProp, function () {
    let addon = addonFor(this);
    // installDate is always set, updateDate is sometimes missing.
    return new Date(addon[aProp] ?? addon.installDate);
  });
});

defineAddonWrapperProperty("signedDate", function () {
  let addon = addonFor(this);
  let { signedDate } = addon;
  if (signedDate != null) {
    return new Date(signedDate);
  }
  return null;
});

["sourceURI", "releaseNotesURI"].forEach(function (aProp) {
  defineAddonWrapperProperty(aProp, function () {
    let addon = addonFor(this);

    // Temporary Installed Addons do not have a "sourceURI",
    // But we can use the "_sourceBundle" as an alternative,
    // which points to the path of the addon xpi installed
    // or its source dir (if it has been installed from a
    // directory).
    if (aProp == "sourceURI" && this.temporarilyInstalled) {
      return Services.io.newFileURI(addon._sourceBundle);
    }

    let [target, fromRepo] = chooseValue(addon, addon, aProp);
    if (!target) {
      return null;
    }
    if (fromRepo) {
      return target;
    }
    return Services.io.newURI(target);
  });
});

// Add to this Map if you need to change an addon's Fluent ID. Keep it in sync
// with the list in browser_verify_l10n_strings.js
const updatedAddonFluentIds = new Map([
  ["extension-default-theme-name", "extension-default-theme-name-auto"],
]);

["name", "description", "creator", "homepageURL"].forEach(function (aProp) {
  defineAddonWrapperProperty(aProp, function () {
    let addon = addonFor(this);

    let formattedMessage;
    // We want to make sure that all built-in themes that are localizable can
    // actually localized, particularly those for thunderbird and desktop.
    if (
      (aProp === "name" || aProp === "description") &&
      addon.location.name === KEY_APP_BUILTINS &&
      addon.type === "theme"
    ) {
      // Built-in themes are localized with Fluent instead of the WebExtension API.
      let addonIdPrefix = addon.id.replace("@mozilla.org", "");
      const colorwaySuffix = "colorway";
      if (addonIdPrefix.endsWith(colorwaySuffix)) {
        // FIXME: Depending on BuiltInThemes here is sort of a hack. Bug 1733466
        // would provide a more generalized way of doing this.
        if (aProp == "description") {
          return BuiltInThemesHelpers.getLocalizedColorwayDescription(addon.id);
        }
        // Colorway collections are usually divided into and presented as
        // "groups". A group either contains closely related colorways, e.g.
        // stemming from the same base color but with different intensities, or
        // if the current collection doesn't have intensities, each colorway is
        // their own group. Colorway names combine the group name with an
        // intensity. Their ids have the format
        // {colorwayGroup}-{intensity}-colorway@mozilla.org or
        // {colorwayGroupName}-colorway@mozilla.org). L10n for colorway group
        // names is optional and falls back on the unlocalized name from the
        // theme's manifest. The intensity part, if present, must be localized.
        let localizedColorwayGroupName =
          BuiltInThemesHelpers.getLocalizedColorwayGroupName(addon.id);
        let [colorwayGroupName, intensity] = addonIdPrefix.split("-", 2);
        if (intensity == colorwaySuffix) {
          // This theme doesn't have an intensity.
          return localizedColorwayGroupName || addon.defaultLocale.name;
        }
        // We're not using toLocaleUpperCase because these color names are
        // always in English.
        colorwayGroupName =
          localizedColorwayGroupName ||
          colorwayGroupName[0].toUpperCase() + colorwayGroupName.slice(1);
        let defaultFluentId = `extension-colorways-${intensity}-name`;
        let fluentId =
          updatedAddonFluentIds.get(defaultFluentId) || defaultFluentId;
        [formattedMessage] = l10n.formatMessagesSync([
          {
            id: fluentId,
            args: {
              "colorway-name": colorwayGroupName,
            },
          },
        ]);
      } else {
        let defaultFluentId = `extension-${addonIdPrefix}-${aProp}`;
        let fluentId =
          updatedAddonFluentIds.get(defaultFluentId) || defaultFluentId;
        [formattedMessage] = l10n.formatMessagesSync([{ id: fluentId }]);
      }

      return formattedMessage.value;
    }

    let [result, usedRepository] = chooseValue(
      addon,
      addon.selectedLocale,
      aProp
    );

    if (result == null) {
      // Legacy add-ons may be partially localized. Fall back to the default
      // locale ensure that the result is a string where possible.
      [result, usedRepository] = chooseValue(addon, addon.defaultLocale, aProp);
    }

    if (result && !usedRepository && aProp == "creator") {
      return new lazy.AddonManagerPrivate.AddonAuthor(result);
    }

    return result;
  });
});

["developers", "translators", "contributors"].forEach(function (aProp) {
  defineAddonWrapperProperty(aProp, function () {
    let addon = addonFor(this);

    let [results, usedRepository] = chooseValue(
      addon,
      addon.selectedLocale,
      aProp
    );

    if (results && !usedRepository) {
      results = results.map(function (aResult) {
        return new lazy.AddonManagerPrivate.AddonAuthor(aResult);
      });
    }

    return results;
  });
});

/**
 * @typedef {Map<string, AddonInternal>} AddonDB
 */

/**
 * Internal interface: find an addon from an already loaded addonDB.
 *
 * @param {AddonDB} addonDB
 *        The add-on database.
 * @param {function(AddonInternal) : boolean} aFilter
 *        The filter predecate. The first add-on for which it returns
 *        true will be returned.
 * @returns {AddonInternal?}
 *        The first matching add-on, if one is found.
 */
function _findAddon(addonDB, aFilter) {
  for (let addon of addonDB.values()) {
    if (aFilter(addon)) {
      return addon;
    }
  }
  return null;
}

/**
 * Internal interface to get a filtered list of addons from a loaded addonDB
 *
 * @param {AddonDB} addonDB
 *        The add-on database.
 * @param {function(AddonInternal) : boolean} aFilter
 *        The filter predecate. Add-ons which match this predicate will
 *        be returned.
 * @returns {Array<AddonInternal>}
 *        The list of matching add-ons.
 */
function _filterDB(addonDB, aFilter) {
  return Array.from(addonDB.values()).filter(aFilter);
}

export const XPIDatabase = {
  // true if the database connection has been opened
  initialized: false,
  // The database file
  jsonFilePath: PathUtils.join(PathUtils.profileDir, FILE_JSON_DB),
  rebuildingDatabase: false,
  syncLoadingDB: false,
  // Add-ons from the database in locations which are no longer
  // supported.
  orphanedAddons: [],

  _saveTask: null,

  // Saved error object if we fail to read an existing database
  _loadError: null,

  // Saved error object if we fail to save the database
  _saveError: null,

  // Error reported by our most recent attempt to read or write the database, if any
  get lastError() {
    if (this._loadError) {
      return this._loadError;
    }
    if (this._saveError) {
      return this._saveError;
    }
    return null;
  },

  async _saveNow() {
    try {
      await IOUtils.writeJSON(this.jsonFilePath, this, {
        tmpPath: `${this.jsonFilePath}.tmp`,
      });

      if (!this._schemaVersionSet) {
        // Update the XPIDB schema version preference the first time we
        // successfully save the database.
        logger.debug(
          "XPI Database saved, setting schema version preference to " +
            XPIExports.XPIInternal.DB_SCHEMA
        );
        Services.prefs.setIntPref(
          PREF_DB_SCHEMA,
          XPIExports.XPIInternal.DB_SCHEMA
        );
        this._schemaVersionSet = true;

        // Reading the DB worked once, so we don't need the load error
        this._loadError = null;
      }
    } catch (error) {
      logger.warn("Failed to save XPI database", error);
      this._saveError = error;

      if (!DOMException.isInstance(error) || error.name !== "AbortError") {
        throw error;
      }
    }
  },

  /**
   * Mark the current stored data dirty, and schedule a flush to disk
   */
  saveChanges() {
    if (!this.initialized) {
      throw new Error("Attempt to use XPI database when it is not initialized");
    }

    if (XPIExports.XPIProvider._closing) {
      // use an Error here so we get a stack trace.
      let err = new Error("XPI database modified after shutdown began");
      logger.warn(err);
      lazy.AddonManagerPrivate.recordSimpleMeasure(
        "XPIDB_late_stack",
        Log.stackTrace(err)
      );
    }

    if (!this._saveTask) {
      this._saveTask = new lazy.DeferredTask(
        () => this._saveNow(),
        ASYNC_SAVE_DELAY_MS
      );
    }

    this._saveTask.arm();
  },

  async finalize() {
    // handle the "in memory only" and "saveChanges never called" cases
    if (!this._saveTask) {
      return;
    }

    await this._saveTask.finalize();
  },

  /**
   * Converts the current internal state of the XPI addon database to
   * a JSON.stringify()-ready structure
   *
   * @returns {Object}
   */
  toJSON() {
    if (!this.addonDB) {
      // We never loaded the database?
      throw new Error("Attempt to save database without loading it first");
    }

    let toSave = {
      schemaVersion: XPIExports.XPIInternal.DB_SCHEMA,
      addons: Array.from(this.addonDB.values()).filter(
        addon => !addon.location.isTemporary
      ),
    };
    return toSave;
  },

  /**
   * Synchronously loads the database, by running the normal async load
   * operation with idle dispatch disabled, and spinning the event loop
   * until it finishes.
   *
   * @param {boolean} aRebuildOnError
   *        A boolean indicating whether add-on information should be loaded
   *        from the install locations if the database needs to be rebuilt.
   *        (if false, caller is XPIProvider.checkForChanges() which will rebuild)
   */
  syncLoadDB(aRebuildOnError) {
    let err = new Error("Synchronously loading the add-ons database");
    logger.debug(err.message);
    lazy.AddonManagerPrivate.recordSimpleMeasure(
      "XPIDB_sync_stack",
      Log.stackTrace(err)
    );
    try {
      this.syncLoadingDB = true;
      XPIExports.XPIInternal.awaitPromise(this.asyncLoadDB(aRebuildOnError));
    } finally {
      this.syncLoadingDB = false;
    }
  },

  _recordStartupError(reason) {
    lazy.AddonManagerPrivate.recordSimpleMeasure("XPIDB_startupError", reason);
  },

  /**
   * Parse loaded data, reconstructing the database if the loaded data is not valid
   *
   * @param {object} aInputAddons
   *        The add-on JSON to parse.
   * @param {boolean} aRebuildOnError
   *        If true, synchronously reconstruct the database from installed add-ons
   */
  async parseDB(aInputAddons, aRebuildOnError) {
    try {
      let parseTimer = lazy.AddonManagerPrivate.simpleTimer("XPIDB_parseDB_MS");

      if (!("schemaVersion" in aInputAddons) || !("addons" in aInputAddons)) {
        let error = new Error("Bad JSON file contents");
        error.rebuildReason = "XPIDB_rebuildBadJSON_MS";
        throw error;
      }

      if (aInputAddons.schemaVersion <= 27) {
        // Types were translated in bug 857456.
        for (let addon of aInputAddons.addons) {
          XPIExports.XPIInternal.migrateAddonLoader(addon);
        }
      } else if (
        aInputAddons.schemaVersion != XPIExports.XPIInternal.DB_SCHEMA
      ) {
        // For now, we assume compatibility for JSON data with a
        // mismatched schema version, though we throw away any fields we
        // don't know about (bug 902956)
        this._recordStartupError(
          `schemaMismatch-${aInputAddons.schemaVersion}`
        );
        logger.debug(
          `JSON schema mismatch: expected ${XPIExports.XPIInternal.DB_SCHEMA}, actual ${aInputAddons.schemaVersion}`
        );
      }

      let forEach = this.syncLoadingDB ? arrayForEach : idleForEach;

      // If we got here, we probably have good data
      // Make AddonInternal instances from the loaded data and save them
      let addonDB = new Map();
      await forEach(aInputAddons.addons, loadedAddon => {
        if (loadedAddon.path) {
          try {
            loadedAddon._sourceBundle = new nsIFile(loadedAddon.path);
          } catch (e) {
            // We can fail here when the path is invalid, usually from the
            // wrong OS
            logger.warn(
              "Could not find source bundle for add-on " + loadedAddon.id,
              e
            );
          }
        }
        loadedAddon.location = XPIExports.XPIInternal.XPIStates.getLocation(
          loadedAddon.location
        );

        let newAddon = new AddonInternal(loadedAddon);
        if (loadedAddon.location) {
          addonDB.set(newAddon._key, newAddon);
        } else {
          this.orphanedAddons.push(newAddon);
        }
      });

      parseTimer.done();
      this.addonDB = addonDB;
      logger.debug("Successfully read XPI database");
      this.initialized = true;
    } catch (e) {
      if (e.name == "SyntaxError") {
        logger.error("Syntax error parsing saved XPI JSON data");
        this._recordStartupError("syntax");
      } else {
        logger.error("Failed to load XPI JSON data from profile", e);
        this._recordStartupError("other");
      }

      this.timeRebuildDatabase(
        e.rebuildReason || "XPIDB_rebuildReadFailed_MS",
        aRebuildOnError
      );
    }
  },

  async maybeIdleDispatch() {
    if (!this.syncLoadingDB) {
      await promiseIdleSlice();
    }
  },

  /**
   * Open and read the XPI database asynchronously, upgrading if
   * necessary. If any DB load operation fails, we need to
   * synchronously rebuild the DB from the installed extensions.
   *
   * @param {boolean} [aRebuildOnError = true]
   *        A boolean indicating whether add-on information should be loaded
   *        from the install locations if the database needs to be rebuilt.
   *        (if false, caller is XPIProvider.checkForChanges() which will rebuild)
   * @returns {Promise<AddonDB>}
   *        Resolves to the Map of loaded JSON data stored in
   *        this.addonDB; rejects in case of shutdown.
   */
  asyncLoadDB(aRebuildOnError = true) {
    // Already started (and possibly finished) loading
    if (this._dbPromise) {
      return this._dbPromise;
    }

    if (XPIExports.XPIProvider._closing) {
      // use an Error here so we get a stack trace.
      let err = new Error(
        "XPIDatabase.asyncLoadDB attempt after XPIProvider shutdown."
      );
      logger.warn("Fail to load AddonDB: ${error}", { error: err });
      lazy.AddonManagerPrivate.recordSimpleMeasure(
        "XPIDB_late_load",
        Log.stackTrace(err)
      );
      this._dbPromise = Promise.reject(err);

      XPIExports.XPIInternal.resolveDBReady(this._dbPromise);

      return this._dbPromise;
    }

    logger.debug(`Starting async load of XPI database ${this.jsonFilePath}`);
    this._dbPromise = (async () => {
      try {
        let json = await IOUtils.readJSON(this.jsonFilePath);

        logger.debug("Finished async read of XPI database, parsing...");
        await this.maybeIdleDispatch();
        await this.parseDB(json, true);
      } catch (error) {
        if (DOMException.isInstance(error) && error.name === "NotFoundError") {
          if (Services.prefs.getIntPref(PREF_DB_SCHEMA, 0)) {
            this._recordStartupError("dbMissing");
          }
        } else {
          logger.warn(
            `Extensions database ${this.jsonFilePath} exists but is not readable; rebuilding`,
            error
          );
          this._loadError = error;
        }
        this.timeRebuildDatabase(
          "XPIDB_rebuildUnreadableDB_MS",
          aRebuildOnError
        );
      }
      return this.addonDB;
    })();

    XPIExports.XPIInternal.resolveDBReady(this._dbPromise);

    return this._dbPromise;
  },

  timeRebuildDatabase(timerName, rebuildOnError) {
    lazy.AddonManagerPrivate.recordTiming(timerName, () => {
      return this.rebuildDatabase(rebuildOnError);
    });
  },

  /**
   * Rebuild the database from addon install directories.
   *
   * @param {boolean} aRebuildOnError
   *        A boolean indicating whether add-on information should be loaded
   *        from the install locations if the database needs to be rebuilt.
   *        (if false, caller is XPIProvider.checkForChanges() which will rebuild)
   */
  rebuildDatabase(aRebuildOnError) {
    this.addonDB = new Map();
    this.initialized = true;

    if (XPIExports.XPIInternal.XPIStates.size == 0) {
      // No extensions installed, so we're done
      logger.debug("Rebuilding XPI database with no extensions");
      return;
    }

    this.rebuildingDatabase = !!aRebuildOnError;

    if (aRebuildOnError) {
      logger.warn("Rebuilding add-ons database from installed extensions.");
      try {
        XPIDatabaseReconcile.processFileChanges({}, false);
      } catch (e) {
        logger.error(
          "Failed to rebuild XPI database from installed extensions",
          e
        );
      }
      // Make sure to update the active add-ons and add-ons list on shutdown
      Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
    }
  },

  /**
   * Shuts down the database connection and releases all cached objects.
   * Return: Promise{integer} resolves / rejects with the result of the DB
   *                          flush after the database is flushed and
   *                          all cleanup is done
   */
  async shutdown() {
    logger.debug("shutdown");
    if (this.initialized) {
      // If our last database I/O had an error, try one last time to save.
      if (this.lastError) {
        this.saveChanges();
      }

      this.initialized = false;

      // If we're shutting down while still loading, finish loading
      // before everything else!
      if (this._dbPromise) {
        await this._dbPromise;
      }

      // Await any pending DB writes and finish cleaning up.
      await this.finalize();

      if (this._saveError) {
        // If our last attempt to read or write the DB failed, force a new
        // extensions.ini to be written to disk on the next startup
        Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
      }

      // Clear out the cached addons data loaded from JSON
      delete this.addonDB;
      delete this._dbPromise;
      // same for the deferred save
      delete this._saveTask;
      // re-enable the schema version setter
      delete this._schemaVersionSet;
    }
  },

  /**
   * Verifies that all installed add-ons are still correctly signed.
   */
  async verifySignatures() {
    try {
      let addons = await this.getAddonList(() => true);

      let changes = {
        enabled: [],
        disabled: [],
      };

      for (let addon of addons) {
        // The add-on might have vanished, we'll catch that on the next startup
        if (!addon._sourceBundle || !addon._sourceBundle.exists()) {
          continue;
        }

        let { signedState, signedTypes } =
          await XPIExports.verifyBundleSignedState(addon._sourceBundle, addon);

        const changedProperties = [];

        if (signedState != addon.signedState) {
          addon.signedState = signedState;
          changedProperties.push("signedState");
        }

        if (
          addon.signedState === lazy.AddonManager.SIGNEDSTATE_SIGNED &&
          Services.policies
        ) {
          const addonDetailsFromFile =
            await XPIExports.XPIInstall.loadManifestFromFile(
              addon._sourceBundle,
              addon.location
            );
          addon.adminInstallOnly = addonDetailsFromFile.adminInstallOnly;
        }

        if (
          !lazy.ObjectUtils.deepEqual(
            signedTypes?.toSorted(),
            addon.signedTypes?.toSorted()
          )
        ) {
          addon.signedTypes = signedTypes;
          changedProperties.push("signedTypes");
        }

        if (changedProperties.length) {
          lazy.AddonManagerPrivate.callAddonListeners(
            "onPropertyChanged",
            addon.wrapper,
            changedProperties
          );
        }

        let disabled = await this.updateAddonDisabledState(addon);
        if (disabled !== undefined) {
          changes[disabled ? "disabled" : "enabled"].push(addon.id);
        }
      }

      this.saveChanges();

      Services.obs.notifyObservers(
        null,
        "xpi-signature-changed",
        JSON.stringify(changes)
      );
    } catch (err) {
      logger.error("XPI_verifySignature: " + err);
    }
  },

  /**
   * Imports the xpinstall permissions from preferences into the permissions
   * manager for the user to change later.
   */
  importPermissions() {
    lazy.PermissionsUtils.importFromPrefs(
      PREF_XPI_PERMISSIONS_BRANCH,
      XPIExports.XPIInternal.XPI_PERMISSION
    );
  },

  /**
   * Called when a new add-on has been enabled when only one add-on of that type
   * can be enabled.
   *
   * @param {string} aId
   *        The ID of the newly enabled add-on
   * @param {string} aType
   *        The type of the newly enabled add-on
   */
  async addonChanged(aId, aType) {
    // We only care about themes in this provider
    if (aType !== "theme") {
      return;
    }

    Services.prefs.setCharPref(
      "extensions.activeThemeID",
      aId || DEFAULT_THEME_ID
    );

    let enableTheme;

    let addons = this.getAddonsByType("theme");
    let updateDisabledStatePromises = [];

    for (let theme of addons) {
      if (theme.visible) {
        if (!aId && theme.id == DEFAULT_THEME_ID) {
          enableTheme = theme;
        } else if (theme.id != aId && !theme.pendingUninstall) {
          updateDisabledStatePromises.push(
            this.updateAddonDisabledState(theme, {
              userDisabled: true,
              becauseSelecting: true,
            })
          );
        }
      }
    }

    await Promise.all(updateDisabledStatePromises);

    if (enableTheme) {
      await this.updateAddonDisabledState(enableTheme, {
        userDisabled: false,
        becauseSelecting: true,
      });
    }
  },

  SIGNED_TYPES,

  /**
   * Asynchronously list all addons that match the filter function
   *
   * @param {function(AddonInternal) : boolean} aFilter
   *        Function that takes an addon instance and returns
   *        true if that addon should be included in the selected array
   *
   * @returns {Array<AddonInternal>}
   *        A Promise that resolves to the list of add-ons matching
   *        aFilter or an empty array if none match
   */
  async getAddonList(aFilter) {
    try {
      let addonDB = await this.asyncLoadDB();
      let addonList = _filterDB(addonDB, aFilter);
      let addons = await Promise.all(
        addonList.map(addon => getRepositoryAddon(addon))
      );
      return addons;
    } catch (error) {
      logger.error("getAddonList failed", error);
      return [];
    }
  },

  /**
   * Get the first addon that matches the filter function
   *
   * @param {function(AddonInternal) : boolean} aFilter
   *        Function that takes an addon instance and returns
   *        true if that addon should be selected
   * @returns {Promise<AddonInternal?>}
   */
  getAddon(aFilter) {
    return this.asyncLoadDB()
      .then(addonDB => getRepositoryAddon(_findAddon(addonDB, aFilter)))
      .catch(error => {
        logger.error("getAddon failed", error);
      });
  },

  /**
   * Asynchronously gets an add-on with a particular ID in a particular
   * install location.
   *
   * @param {string} aId
   *        The ID of the add-on to retrieve
   * @param {string} aLocation
   *        The name of the install location
   * @returns {Promise<AddonInternal?>}
   */
  getAddonInLocation(aId, aLocation) {
    return this.asyncLoadDB().then(addonDB =>
      getRepositoryAddon(addonDB.get(aLocation + ":" + aId))
    );
  },

  /**
   * Asynchronously get all the add-ons in a particular install location.
   *
   * @param {string} aLocation
   *        The name of the install location
   * @returns {Promise<Array<AddonInternal>>}
   */
  getAddonsInLocation(aLocation) {
    return this.getAddonList(aAddon => aAddon.location.name == aLocation);
  },

  /**
   * Asynchronously gets the add-on with the specified ID that is visible.
   *
   * @param {string} aId
   *        The ID of the add-on to retrieve
   * @returns {Promise<AddonInternal?>}
   */
  getVisibleAddonForID(aId) {
    return this.getAddon(aAddon => aAddon.id == aId && aAddon.visible);
  },

  /**
   * Asynchronously gets the visible add-ons, optionally restricting by type.
   *
   * @param {Set<string>?} aTypes
   *        An array of types to include or null to include all types
   * @returns {Promise<Array<AddonInternal>>}
   */
  getVisibleAddons(aTypes) {
    return this.getAddonList(
      aAddon => aAddon.visible && (!aTypes || aTypes.has(aAddon.type))
    );
  },

  /**
   * Synchronously gets all add-ons of a particular type(s).
   *
   * @param {Array<string>} aTypes
   *        The type(s) of add-on to retrieve
   * @returns {Array<AddonInternal>}
   */
  getAddonsByType(...aTypes) {
    if (!this.addonDB) {
      // jank-tastic! Must synchronously load DB if the theme switches from
      // an XPI theme to a lightweight theme before the DB has loaded,
      // because we're called from sync XPIProvider.addonChanged
      logger.warn(
        `Synchronous load of XPI database due to ` +
          `getAddonsByType([${aTypes.join(", ")}]) ` +
          `Stack: ${Error().stack}`
      );
      this.syncLoadDB(true);
    }

    return _filterDB(this.addonDB, aAddon => aTypes.includes(aAddon.type));
  },

  /**
   * Asynchronously gets all add-ons with pending operations.
   *
   * @param {Set<string>?} aTypes
   *        The types of add-ons to retrieve or null to get all types
   * @returns {Promise<Array<AddonInternal>>}
   */
  getVisibleAddonsWithPendingOperations(aTypes) {
    return this.getAddonList(
      aAddon =>
        aAddon.visible &&
        aAddon.pendingUninstall &&
        (!aTypes || aTypes.has(aAddon.type))
    );
  },

  /**
   * Synchronously gets all add-ons in the database.
   * This is only called from the preference observer for the default
   * compatibility version preference, so we can return an empty list if
   * we haven't loaded the database yet.
   *
   * @returns {Array<AddonInternal>}
   */
  getAddons() {
    if (!this.addonDB) {
      return [];
    }
    return _filterDB(this.addonDB, () => true);
  },

  /**
   * Called to get an Addon with a particular ID.
   *
   * @param {string} aId
   *        The ID of the add-on to retrieve
   * @returns {Addon?}
   */
  async getAddonByID(aId) {
    let aAddon = await this.getVisibleAddonForID(aId);
    return aAddon ? aAddon.wrapper : null;
  },

  /**
   * Obtain an Addon having the specified Sync GUID.
   *
   * @param {string} aGUID
   *        String GUID of add-on to retrieve
   * @returns {Addon?}
   */
  async getAddonBySyncGUID(aGUID) {
    let addon = await this.getAddon(aAddon => aAddon.syncGUID == aGUID);
    return addon ? addon.wrapper : null;
  },

  /**
   * Called to get Addons of a particular type.
   *
   * @param {Array<string>?} aTypes
   *        An array of types to fetch. Can be null to get all types.
   * @returns {Addon[]}
   */
  async getAddonsByTypes(aTypes) {
    let addons = await this.getVisibleAddons(aTypes ? new Set(aTypes) : null);
    return addons.map(a => a.wrapper);
  },

  /**
   * Returns true if signing is required for the given add-on type.
   *
   * @param {string} aType
   *        The add-on type to check.
   * @returns {boolean}
   */
  mustSign(aType) {
    if (!SIGNED_TYPES.has(aType)) {
      return false;
    }

    if (aType == "locale") {
      return lazy.AddonSettings.LANGPACKS_REQUIRE_SIGNING;
    }

    return lazy.AddonSettings.REQUIRE_SIGNING;
  },

  /**
   * Determine if this addon should be disabled due to being legacy
   *
   * @param {Addon} addon The addon to check
   *
   * @returns {boolean} Whether the addon should be disabled for being legacy
   */
  isDisabledLegacy(addon) {
    // We still have tests that use a legacy addon type, allow them
    // if we're in automation.  Otherwise, disable if not a webextension.
    if (!Cu.isInAutomation) {
      return !addon.isWebExtension;
    }

    return (
      !addon.isWebExtension &&
      addon.type === "extension" &&
      // Test addons are privileged unless forced otherwise.
      addon.signedState !== lazy.AddonManager.SIGNEDSTATE_PRIVILEGED
    );
  },

  /**
   * Calculates whether an add-on should be appDisabled or not.
   *
   * @param {AddonInternal} aAddon
   *        The add-on to check
   * @returns {boolean}
   *        True if the add-on should not be appDisabled
   */
  isUsableAddon(aAddon) {
    if (this.mustSign(aAddon.type) && !aAddon.isCorrectlySigned) {
      logger.warn(`Add-on ${aAddon.id} is not correctly signed.`);
      if (Services.prefs.getBoolPref(PREF_XPI_SIGNATURES_DEV_ROOT, false)) {
        logger.warn(`Preference ${PREF_XPI_SIGNATURES_DEV_ROOT} is set.`);
      }
      return false;
    }

    // When signatures are required, and the addon has the adminInstallOnly
    // flag set to true, then we want to confirm if there is still an active
    // enterprise policy setting for the same addon id, otherwise we should
    // mark if as appDisabled.
    //
    // NOTE: the adminInstallOnly boolean flag is not being stored in the Addon DB,
    // it is instead computed only when installing the addon and when we are
    // re-verify the signatures once per day.
    if (
      this.mustSign(aAddon.type) &&
      aAddon.adminInstallOnly &&
      !aAddon.wrapper.isInstalledByEnterprisePolicy
    ) {
      logger.warn(
        `Add-on ${aAddon.id} is installable only from policies, but no policy extension settings have been found.`
      );
      return false;
    }

    if (aAddon.blocklistState == nsIBlocklistService.STATE_BLOCKED) {
      logger.warn(`Add-on ${aAddon.id} is blocklisted.`);
      return false;
    }

    // If we can't read it, it's not usable:
    if (aAddon.brokenManifest) {
      return false;
    }

    if (
      lazy.AddonManager.checkUpdateSecurity &&
      !aAddon.providesUpdatesSecurely
    ) {
      logger.warn(
        `Updates for add-on ${aAddon.id} must be provided over HTTPS.`
      );
      return false;
    }

    if (!aAddon.isPlatformCompatible) {
      logger.warn(`Add-on ${aAddon.id} is not compatible with platform.`);
      return false;
    }

    if (aAddon.dependencies.length) {
      let isActive = id => {
        let active = XPIExports.XPIProvider.activeAddons.get(id);
        return active && !active._pendingDisable;
      };

      if (aAddon.dependencies.some(id => !isActive(id))) {
        return false;
      }
    }

    if (this.isDisabledLegacy(aAddon)) {
      logger.warn(`disabling legacy extension ${aAddon.id}`);
      return false;
    }

    if (lazy.AddonManager.checkCompatibility) {
      if (!aAddon.isCompatible) {
        logger.warn(
          `Add-on ${aAddon.id} is not compatible with application version.`
        );
        return false;
      }
    } else {
      let app = aAddon.matchingTargetApplication;
      if (!app) {
        logger.warn(
          `Add-on ${aAddon.id} is not compatible with target application.`
        );
        return false;
      }
    }

    if (aAddon.location.isSystem || aAddon.location.isBuiltin) {
      return true;
    }

    if (Services.policies && !Services.policies.mayInstallAddon(aAddon)) {
      return false;
    }

    return true;
  },

  /**
   * Synchronously adds an AddonInternal's metadata to the database.
   *
   * @param {AddonInternal} aAddon
   *        AddonInternal to add
   * @param {string} aPath
   *        The file path of the add-on
   * @returns {AddonInternal}
   *        the AddonInternal that was added to the database
   */
  addToDatabase(aAddon, aPath) {
    aAddon.addedToDatabase();
    aAddon.path = aPath;
    this.addonDB.set(aAddon._key, aAddon);
    if (aAddon.visible) {
      this.makeAddonVisible(aAddon);
    }

    this.saveChanges();
    return aAddon;
  },

  /**
   * Synchronously updates an add-on's metadata in the database. Currently just
   * removes and recreates.
   *
   * @param {AddonInternal} aOldAddon
   *        The AddonInternal to be replaced
   * @param {AddonInternal} aNewAddon
   *        The new AddonInternal to add
   * @param {string} aPath
   *        The file path of the add-on
   * @returns {AddonInternal}
   *        The AddonInternal that was added to the database
   */
  updateAddonMetadata(aOldAddon, aNewAddon, aPath) {
    this.removeAddonMetadata(aOldAddon);
    aNewAddon.syncGUID = aOldAddon.syncGUID;
    aNewAddon.installDate = aOldAddon.installDate;
    aNewAddon.applyBackgroundUpdates = aOldAddon.applyBackgroundUpdates;
    aNewAddon.foreignInstall = aOldAddon.foreignInstall;
    aNewAddon.seen = aOldAddon.seen;
    aNewAddon.active =
      aNewAddon.visible && !aNewAddon.disabled && !aNewAddon.pendingUninstall;
    aNewAddon.installTelemetryInfo = aOldAddon.installTelemetryInfo;

    return this.addToDatabase(aNewAddon, aPath);
  },

  /**
   * Synchronously removes an add-on from the database.
   *
   * @param {AddonInternal} aAddon
   *        The AddonInternal being removed
   */
  removeAddonMetadata(aAddon) {
    this.addonDB.delete(aAddon._key);
    this.saveChanges();
  },

  updateXPIStates(addon) {
    let state = addon.location && addon.location.get(addon.id);
    if (state) {
      state.syncWithDB(addon);
      XPIExports.XPIInternal.XPIStates.save();
    }
  },

  /**
   * Synchronously marks a AddonInternal as visible marking all other
   * instances with the same ID as not visible.
   *
   * @param {AddonInternal} aAddon
   *        The AddonInternal to make visible
   */
  makeAddonVisible(aAddon) {
    logger.debug("Make addon " + aAddon._key + " visible");
    for (let [, otherAddon] of this.addonDB) {
      if (otherAddon.id == aAddon.id && otherAddon._key != aAddon._key) {
        logger.debug("Hide addon " + otherAddon._key);
        otherAddon.visible = false;
        otherAddon.active = false;

        this.updateXPIStates(otherAddon);
      }
    }
    aAddon.visible = true;
    this.updateXPIStates(aAddon);
    this.saveChanges();
  },

  /**
   * Synchronously marks a given add-on ID visible in a given location,
   * instances with the same ID as not visible.
   *
   * @param {string} aId
   *        The ID of the add-on to make visible
   * @param {XPIStateLocation} aLocation
   *        The location in which to make the add-on visible.
   * @returns {AddonInternal?}
   *        The add-on instance which was marked visible, if any.
   */
  makeAddonLocationVisible(aId, aLocation) {
    logger.debug(`Make addon ${aId} visible in location ${aLocation}`);
    let result;
    for (let [, addon] of this.addonDB) {
      if (addon.id != aId) {
        continue;
      }
      if (addon.location == aLocation) {
        logger.debug("Reveal addon " + addon._key);
        addon.visible = true;
        addon.active = true;
        this.updateXPIStates(addon);
        result = addon;
      } else {
        logger.debug("Hide addon " + addon._key);
        addon.visible = false;
        addon.active = false;
        this.updateXPIStates(addon);
      }
    }
    this.saveChanges();
    return result;
  },

  /**
   * Synchronously sets properties for an add-on.
   *
   * @param {AddonInternal} aAddon
   *        The AddonInternal being updated
   * @param {Object} aProperties
   *        A dictionary of properties to set
   */
  setAddonProperties(aAddon, aProperties) {
    for (let key in aProperties) {
      aAddon[key] = aProperties[key];
    }
    this.saveChanges();
  },

  /**
   * Synchronously sets the Sync GUID for an add-on.
   * Only called when the database is already loaded.
   *
   * @param {AddonInternal} aAddon
   *        The AddonInternal being updated
   * @param {string} aGUID
   *        GUID string to set the value to
   * @throws if another addon already has the specified GUID
   */
  setAddonSyncGUID(aAddon, aGUID) {
    // Need to make sure no other addon has this GUID
    function excludeSyncGUID(otherAddon) {
      return otherAddon._key != aAddon._key && otherAddon.syncGUID == aGUID;
    }
    let otherAddon = _findAddon(this.addonDB, excludeSyncGUID);
    if (otherAddon) {
      throw new Error(
        "Addon sync GUID conflict for addon " +
          aAddon._key +
          ": " +
          otherAddon._key +
          " already has GUID " +
          aGUID
      );
    }
    aAddon.syncGUID = aGUID;
    this.saveChanges();
  },

  /**
   * Synchronously updates an add-on's active flag in the database.
   *
   * @param {AddonInternal} aAddon
   *        The AddonInternal to update
   * @param {boolean} aActive
   *        The new active state for the add-on.
   */
  updateAddonActive(aAddon, aActive) {
    logger.debug(
      "Updating active state for add-on " + aAddon.id + " to " + aActive
    );

    aAddon.active = aActive;
    this.saveChanges();
  },

  /**
   * Synchronously calculates and updates all the active flags in the database.
   */
  updateActiveAddons() {
    logger.debug("Updating add-on states");
    for (let [, addon] of this.addonDB) {
      let newActive =
        addon.visible && !addon.disabled && !addon.pendingUninstall;
      if (newActive != addon.active) {
        addon.active = newActive;
        this.saveChanges();
      }
    }

    Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false);
  },

  /**
   * Updates the disabled state for an add-on. Its appDisabled property will be
   * calculated and if the add-on is changed the database will be saved and
   * appropriate notifications will be sent out to the registered AddonListeners.
   *
   * @param {AddonInternal} aAddon
   *        The AddonInternal to update
   * @param {Object} properties - Properties to set on the addon
   * @param {boolean?} [properties.userDisabled]
   *        Value for the userDisabled property. If undefined the value will
   *        not change
   * @param {boolean?} [properties.softDisabled]
   *        Value for the softDisabled property. If undefined the value will
   *        not change. If true this will force userDisabled to be true
   * @param {boolean?} [properties.embedderDisabled]
   *        Value for the embedderDisabled property. If undefined the value will
   *        not change.
   * @param {boolean?} [properties.becauseSelecting]
   *        True if we're disabling this add-on because we're selecting
   *        another.
   * @returns {Promise<boolean?>}
   *       A tri-state indicating the action taken for the add-on:
   *           - undefined: The add-on did not change state
   *           - true: The add-on became disabled
   *           - false: The add-on became enabled
   * @throws if addon is not a AddonInternal
   */
  async updateAddonDisabledState(
    aAddon,
    { userDisabled, softDisabled, embedderDisabled, becauseSelecting } = {}
  ) {
    if (!aAddon.inDatabase) {
      throw new Error("Can only update addon states for installed addons.");
    }
    if (userDisabled !== undefined && softDisabled !== undefined) {
      throw new Error(
        "Cannot change userDisabled and softDisabled at the same time"
      );
    }

    if (userDisabled === undefined) {
      userDisabled = aAddon.userDisabled;
    } else if (!userDisabled) {
      // If enabling the add-on then remove softDisabled
      softDisabled = false;
    }

    // If not changing softDisabled or the add-on is already userDisabled then
    // use the existing value for softDisabled
    if (softDisabled === undefined || userDisabled) {
      softDisabled = aAddon.softDisabled;
    }

    if (!lazy.AddonSettings.IS_EMBEDDED) {
      // If embedderDisabled was accidentally set somehow, this will revert it
      // back to false.
      embedderDisabled = false;
    } else if (embedderDisabled === undefined) {
      embedderDisabled = aAddon.embedderDisabled;
    }

    let appDisabled = !this.isUsableAddon(aAddon);
    // No change means nothing to do here
    if (
      aAddon.userDisabled == userDisabled &&
      aAddon.appDisabled == appDisabled &&
      aAddon.softDisabled == softDisabled &&
      aAddon.embedderDisabled == embedderDisabled
    ) {
      return undefined;
    }

    let wasDisabled = aAddon.disabled;
    let isDisabled =
      userDisabled || softDisabled || appDisabled || embedderDisabled;

    // If appDisabled changes but addon.disabled doesn't,
    // no onDisabling/onEnabling is sent - so send a onPropertyChanged.
    let appDisabledChanged = aAddon.appDisabled != appDisabled;

    // Update the properties in the database.
    this.setAddonProperties(aAddon, {
      userDisabled,
      appDisabled,
      softDisabled,
      embedderDisabled,
    });

    let wrapper = aAddon.wrapper;

    if (appDisabledChanged) {
      lazy.AddonManagerPrivate.callAddonListeners(
        "onPropertyChanged",
        wrapper,
        ["appDisabled"]
      );
    }

    // If the add-on is not visible or the add-on is not changing state then
    // there is no need to do anything else
    if (!aAddon.visible || wasDisabled == isDisabled) {
      return undefined;
    }

    // Flag that active states in the database need to be updated on shutdown
    Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);

    this.updateXPIStates(aAddon);

    // Have we just gone back to the current state?
    if (isDisabled != aAddon.active) {
      lazy.AddonManagerPrivate.callAddonListeners(
        "onOperationCancelled",
        wrapper
      );
    } else {
      if (isDisabled) {
        lazy.AddonManagerPrivate.callAddonListeners(
          "onDisabling",
          wrapper,
          false
        );
      } else {
        lazy.AddonManagerPrivate.callAddonListeners(
          "onEnabling",
          wrapper,
          false
        );
      }

      this.updateAddonActive(aAddon, !isDisabled);

      let bootstrap = XPIExports.XPIInternal.BootstrapScope.get(aAddon);
      if (isDisabled) {
        await bootstrap.disable();
        lazy.AddonManagerPrivate.callAddonListeners("onDisabled", wrapper);
      } else {
        await bootstrap.startup(
          XPIExports.XPIInternal.BOOTSTRAP_REASONS.ADDON_ENABLE
        );
        lazy.AddonManagerPrivate.callAddonListeners("onEnabled", wrapper);
      }
    }

    // Notify any other providers that a new theme has been enabled
    if (aAddon.type === "theme") {
      if (!isDisabled) {
        await lazy.AddonManagerPrivate.notifyAddonChanged(
          aAddon.id,
          aAddon.type
        );
      } else if (isDisabled && !becauseSelecting) {
        await lazy.AddonManagerPrivate.notifyAddonChanged(null, "theme");
      }
    }

    return isDisabled;
  },

  /**
   * Update the appDisabled property for all add-ons.
   */
  updateAddonAppDisabledStates() {
    for (let addon of this.getAddons()) {
      this.updateAddonDisabledState(addon);
    }
  },

  /**
   * Update the repositoryAddon property for all add-ons.
   */
  async updateAddonRepositoryData() {
    let addons = await this.getVisibleAddons(null);
    logger.debug(
      "updateAddonRepositoryData found " + addons.length + " visible add-ons"
    );

    await Promise.all(
      addons.map(addon =>
        lazy.AddonRepository.getCachedAddonByID(addon.id).then(aRepoAddon => {
          if (aRepoAddon) {
            logger.debug("updateAddonRepositoryData got info for " + addon.id);
            addon._repositoryAddon = aRepoAddon;
            return this.updateAddonDisabledState(addon);
          }
          return undefined;
        })
      )
    );
  },

  /**
   * Adds the add-on's name and creator to the telemetry payload.
   *
   * @param {AddonInternal} aAddon
   *        The addon to record
   */
  recordAddonTelemetry(aAddon) {
    let locale = aAddon.defaultLocale;
    XPIExports.XPIProvider.addTelemetry(aAddon.id, {
      name: locale.name,
      creator: locale.creator,
    });
  },
};

export const XPIDatabaseReconcile = {
  /**
   * Returns a map of ID -> add-on. When the same add-on ID exists in multiple
   * install locations the highest priority location is chosen.
   *
   * @param {Map<String, AddonInternal>} addonMap
   *        The add-on map to flatten.
   * @param {string?} [hideLocation]
   *        An optional location from which to hide any add-ons.
   * @returns {Map<string, AddonInternal>}
   */
  flattenByID(addonMap, hideLocation) {
    let map = new Map();

    for (let loc of XPIExports.XPIInternal.XPIStates.locations()) {
      if (loc.name == hideLocation) {
        continue;
      }

      let locationMap = addonMap.get(loc.name);
      if (!locationMap) {
        continue;
      }

      for (let [id, addon] of locationMap) {
        if (!map.has(id)) {
          map.set(id, addon);
        }
      }
    }

    return map;
  },

  /**
   * Finds the visible add-ons from the map.
   *
   * @param {Map<String, AddonInternal>} addonMap
   *        The add-on map to filter.
   * @returns {Map<string, AddonInternal>}
   */
  getVisibleAddons(addonMap) {
    let map = new Map();

    for (let addons of addonMap.values()) {
      for (let [id, addon] of addons) {
        if (!addon.visible) {
          continue;
        }

        if (map.has(id)) {
          logger.warn(
            "Previous database listed more than one visible add-on with id " +
              id
          );
          continue;
        }

        map.set(id, addon);
      }
    }

    return map;
  },

  /**
   * Called to add the metadata for an add-on in one of the install locations
   * to the database. This can be called in three different cases. Either an
   * add-on has been dropped into the location from outside of Firefox, or
   * an add-on has been installed through the application, or the database
   * has been upgraded or become corrupt and add-on data has to be reloaded
   * into it.
   *
   * @param {XPIStateLocation} aLocation
   *        The install location containing the add-on
   * @param {string} aId
   *        The ID of the add-on
   * @param {XPIState} aAddonState
   *        The new state of the add-on
   * @param {AddonInternal?} [aNewAddon]
   *        The manifest for the new add-on if it has already been loaded
   * @returns {boolean}
   *        A boolean indicating if flushing caches is required to complete
   *        changing this add-on
   */
  addMetadata(aLocation, aId, aAddonState, aNewAddon) {
    logger.debug(`New add-on ${aId} installed in ${aLocation.name}`);

    // We treat this is a new install if,
    //
    // a) It was explicitly registered as a staged install in the last
    //    session, or,
    // b) We're not currently migrating or rebuilding a corrupt database. In
    //    that case, we can assume this add-on was found during a routine
    //    directory scan.
    let isNewInstall = !!aNewAddon || !XPIDatabase.rebuildingDatabase;

    // If it's a new install and we haven't yet loaded the manifest then it
    // must be something dropped directly into the install location
    let isDetectedInstall = isNewInstall && !aNewAddon;

    // Load the manifest if necessary and sanity check the add-on ID
    let unsigned;
    try {
      // Do not allow third party installs if xpinstall is disabled by policy
      if (
        isDetectedInstall &&
        Services.policies &&
        !Services.policies.isAllowed("xpinstall")
      ) {
        throw new Error(
          "Extension installs are disabled by enterprise policy."
        );
      }

      if (!aNewAddon) {
        // Load the manifest from the add-on.
        aNewAddon = XPIExports.XPIInstall.syncLoadManifest(
          aAddonState,
          aLocation
        );
      }
      // The add-on in the manifest should match the add-on ID.
      if (aNewAddon.id != aId) {
        throw new Error(
          `Invalid addon ID: expected addon ID ${aId}, found ${aNewAddon.id} in manifest`
        );
      }

      unsigned =
        XPIDatabase.mustSign(aNewAddon.type) && !aNewAddon.isCorrectlySigned;
      if (unsigned) {
        throw Error(`Extension ${aNewAddon.id} is not correctly signed`);
      }
    } catch (e) {
      logger.warn(`addMetadata: Add-on ${aId} is invalid`, e);

      // Remove the invalid add-on from the install location if the install
      // location isn't locked
      if (aLocation.isLinkedAddon(aId)) {
        logger.warn("Not uninstalling invalid item because it is a proxy file");
      } else if (aLocation.locked) {
        logger.warn(
          "Could not uninstall invalid item from locked install location"
        );
      } else if (unsigned && !isNewInstall) {
        logger.warn("Not uninstalling existing unsigned add-on");
      } else if (aLocation.name == KEY_APP_BUILTINS) {
        // If a builtin has been removed from the build, we need to remove it from our
        // data sets.  We cannot use location.isBuiltin since the system addon locations
        // mix it up.
        XPIDatabase.removeAddonMetadata(aAddonState);
        aLocation.removeAddon(aId);
      } else {
        aLocation.installer.uninstallAddon(aId);
      }
      return null;
    }

    // Update the AddonInternal properties.
    aNewAddon.installDate = aAddonState.mtime;
    aNewAddon.updateDate = aAddonState.mtime;

    // Assume that add-ons in the system add-ons install location aren't
    // foreign and should default to enabled.
    aNewAddon.foreignInstall =
      isDetectedInstall && !aLocation.isSystem && !aLocation.isBuiltin;

    // appDisabled depends on whether the add-on is a foreignInstall so update
    aNewAddon.appDisabled = !XPIDatabase.isUsableAddon(aNewAddon);

    if (isDetectedInstall && aNewAddon.foreignInstall) {
      // Add the installation source info for the sideloaded extension.
      aNewAddon.installTelemetryInfo = {
        source: aLocation.name,
        method: "sideload",
      };

      // If the add-on is a foreign install and is in a scope where add-ons
      // that were dropped in should default to disabled then disable it
      let disablingScopes = Services.prefs.getIntPref(
        PREF_EM_AUTO_DISABLED_SCOPES,
        0
      );
      if (aLocation.scope & disablingScopes) {
        logger.warn(
          `Disabling foreign installed add-on ${aNewAddon.id} in ${aLocation.name}`
        );
        aNewAddon.userDisabled = true;
        aNewAddon.seen = false;
      }
    }

    return XPIDatabase.addToDatabase(aNewAddon, aAddonState.path);
  },

  /**
   * Called when an add-on has been removed.
   *
   * @param {AddonInternal} aOldAddon
   *        The AddonInternal as it appeared the last time the application
   *        ran
   */
  removeMetadata(aOldAddon) {
    // This add-on has disappeared
    logger.debug(
      "Add-on " + aOldAddon.id + " removed from " + aOldAddon.location.name
    );
    XPIDatabase.removeAddonMetadata(aOldAddon);
  },

  /**
   * Updates an add-on's metadata and determines. This is called when either the
   * add-on's install directory path or last modified time has changed.
   *
   * @param {XPIStateLocation} aLocation
   *        The install location containing the add-on
   * @param {AddonInternal} aOldAddon
   *        The AddonInternal as it appeared the last time the application
   *        ran
   * @param {XPIState} aAddonState
   *        The new state of the add-on
   * @param {AddonInternal?} [aNewAddon]
   *        The manifest for the new add-on if it has already been loaded
   * @returns {AddonInternal}
   *        The AddonInternal that was added to the database
   */
  updateMetadata(aLocation, aOldAddon, aAddonState, aNewAddon) {
    logger.debug(`Add-on ${aOldAddon.id} modified in ${aLocation.name}`);

    try {
      // If there isn't an updated install manifest for this add-on then load it.
      if (!aNewAddon) {
        aNewAddon = XPIExports.XPIInstall.syncLoadManifest(
          aAddonState,
          aLocation,
          aOldAddon
        );
      } else {
        aNewAddon.rootURI = aOldAddon.rootURI;
      }

      // The ID in the manifest that was loaded must match the ID of the old
      // add-on.
      if (aNewAddon.id != aOldAddon.id) {
        throw new Error(
          `Incorrect id in install manifest for existing add-on ${aOldAddon.id}`
        );
      }
    } catch (e) {
      logger.warn(`updateMetadata: Add-on ${aOldAddon.id} is invalid`, e);

      XPIDatabase.removeAddonMetadata(aOldAddon);
      aOldAddon.location.removeAddon(aOldAddon.id);

      if (!aLocation.locked) {
        aLocation.installer.uninstallAddon(aOldAddon.id);
      } else {
        logger.warn(
          "Could not uninstall invalid item from locked install location"
        );
      }

      return null;
    }

    // Set the additional properties on the new AddonInternal
    aNewAddon.updateDate = aAddonState.mtime;

    XPIExports.XPIProvider.persistStartupData(aNewAddon, aAddonState);

    // Update the database
    return XPIDatabase.updateAddonMetadata(
      aOldAddon,
      aNewAddon,
      aAddonState.path
    );
  },

  /**
   * Updates an add-on's path for when the add-on has moved in the
   * filesystem but hasn't changed in any other way.
   *
   * @param {XPIStateLocation} aLocation
   *        The install location containing the add-on
   * @param {AddonInternal} aOldAddon
   *        The AddonInternal as it appeared the last time the application
   *        ran
   * @param {XPIState} aAddonState
   *        The new state of the add-on
   * @returns {AddonInternal}
   */
  updatePath(aLocation, aOldAddon, aAddonState) {
    logger.debug(`Add-on ${aOldAddon.id} moved to ${aAddonState.path}`);
    aOldAddon.path = aAddonState.path;
    aOldAddon._sourceBundle = new nsIFile(aAddonState.path);
    aOldAddon.rootURI = XPIExports.XPIInternal.getURIForResourceInFile(
      aOldAddon._sourceBundle,
      ""
    ).spec;

    return aOldAddon;
  },

  /**
   * Called when no change has been detected for an add-on's metadata but the
   * application has changed so compatibility may have changed.
   *
   * @param {XPIStateLocation} aLocation
   *        The install location containing the add-on
   * @param {AddonInternal} aOldAddon
   *        The AddonInternal as it appeared the last time the application
   *        ran
   * @param {XPIState} aAddonState
   *        The new state of the add-on
   * @param {boolean} [aReloadMetadata = false]
   *        A boolean which indicates whether metadata should be reloaded from
   *        the addon manifests. Default to false.
   * @returns {AddonInternal}
   *        The new addon.
   */
  updateCompatibility(aLocation, aOldAddon, aAddonState, aReloadMetadata) {
    logger.debug(
      `Updating compatibility for add-on ${aOldAddon.id} in ${aLocation.name}`
    );

    let checkSigning =
      aOldAddon.signedState === undefined && SIGNED_TYPES.has(aOldAddon.type);
    // signedDate must be set if signedState is set.
    let signedDateMissing =
      aOldAddon.signedDate === undefined &&
      (aOldAddon.signedState || checkSigning);
    // signedTypes must be set if signedState is set.
    let signedTypesMissing =
      aOldAddon.signedTypes === undefined &&
      (aOldAddon.signedState || checkSigning);

    // If maxVersion was inadvertently updated for a locale, force a reload
    // from the manifest.  See Bug 1646016 for details.
    if (
      !aReloadMetadata &&
      aOldAddon.type === "locale" &&
      aOldAddon.matchingTargetApplication
    ) {
      aReloadMetadata = aOldAddon.matchingTargetApplication.maxVersion === "*";
    }

    let manifest = null;
    if (
      checkSigning ||
      aReloadMetadata ||
      signedDateMissing ||
      signedTypesMissing
    ) {
      try {
        manifest = XPIExports.XPIInstall.syncLoadManifest(
          aAddonState,
          aLocation
        );
      } catch (err) {
        // If we can no longer read the manifest, it is no longer compatible.
        aOldAddon.brokenManifest = true;
        aOldAddon.appDisabled = true;
        return aOldAddon;
      }
    }

    // If updating from a version of the app that didn't support signedState
    // then update that property now
    if (checkSigning) {
      aOldAddon.signedState = manifest.signedState;
    }

    if (signedDateMissing) {
      aOldAddon.signedDate = manifest.signedDate;
    }

    if (signedTypesMissing) {
      aOldAddon.signedTypes = manifest.signedTypes;
    }

    // May be updating from a version of the app that didn't support all the
    // properties of the currently-installed add-ons.
    if (aReloadMetadata) {
      // Avoid re-reading these properties from manifest,
      // use existing addon instead.
      let remove = [
        "syncGUID",
        "foreignInstall",
        "visible",
        "active",
        "userDisabled",
        "embedderDisabled",
        "applyBackgroundUpdates",
        "sourceURI",
        "releaseNotesURI",
        "installTelemetryInfo",
      ];

      // TODO - consider re-scanning for targetApplications for other addon types.
      if (aOldAddon.type !== "locale") {
        remove.push("targetApplications");
      }

      let props = PROP_JSON_FIELDS.filter(a => !remove.includes(a));
      copyProperties(manifest, props, aOldAddon);
    }

    aOldAddon.appDisabled = !XPIDatabase.isUsableAddon(aOldAddon);

    return aOldAddon;
  },

  /**
   * Returns true if this install location is part of the application
   * bundle. Add-ons in these locations are expected to change whenever
   * the application updates.
   *
   * @param {XPIStateLocation} location
   *        The install location to check.
   * @returns {boolean}
   *        True if this location is part of the application bundle.
   */
  isAppBundledLocation(location) {
    return (
      location.name == KEY_APP_GLOBAL ||
      location.name == KEY_APP_SYSTEM_DEFAULTS ||
      location.name == KEY_APP_BUILTINS
    );
  },

  /**
   * Returns true if this install location holds system addons.
   *
   * @param {XPIStateLocation} location
   *        The install location to check.
   * @returns {boolean}
   *        True if this location contains system add-ons.
   */
  isSystemAddonLocation(location) {
    return (
      location.name === KEY_APP_SYSTEM_DEFAULTS ||
      location.name === KEY_APP_SYSTEM_ADDONS
    );
  },

  /**
   * Updates the databse metadata for an existing add-on during database
   * reconciliation.
   *
   * @param {AddonInternal} oldAddon
   *        The existing database add-on entry.
   * @param {XPIState} xpiState
   *        The XPIStates entry for this add-on.
   * @param {AddonInternal?} newAddon
   *        The new add-on metadata for the add-on, as loaded from a
   *        staged update in addonStartup.json.
   * @param {boolean} aUpdateCompatibility
   *        true to update add-ons appDisabled property when the application
   *        version has changed
   * @param {boolean} aSchemaChange
   *        The schema has changed and all add-on manifests should be re-read.
   * @returns {AddonInternal?}
   *        The updated AddonInternal object for the add-on, if one
   *        could be created.
   */
  updateExistingAddon(
    oldAddon,
    xpiState,
    newAddon,
    aUpdateCompatibility,
    aSchemaChange
  ) {
    XPIDatabase.recordAddonTelemetry(oldAddon);

    let installLocation = oldAddon.location;

    // Update the add-on's database metadata from on-disk metadata if:
    //
    //  a) The add-on was staged for install in the last session,
    //  b) The add-on has been modified since the last session, or,
    //  c) The app has been updated since the last session, and the
    //     add-on is part of the application bundle (and has therefore
    //     likely been replaced in the update process).
    if (
      newAddon ||
      oldAddon.updateDate != xpiState.mtime ||
      (aUpdateCompatibility && this.isAppBundledLocation(installLocation))
    ) {
      newAddon = this.updateMetadata(
        installLocation,
        oldAddon,
        xpiState,
        newAddon
      );
    } else if (oldAddon.path != xpiState.path) {
      newAddon = this.updatePath(installLocation, oldAddon, xpiState);
    } else if (aUpdateCompatibility || aSchemaChange) {
      newAddon = this.updateCompatibility(
        installLocation,
        oldAddon,
        xpiState,
        aSchemaChange
      );
    } else {
      newAddon = oldAddon;
    }

    if (newAddon) {
      newAddon.rootURI = newAddon.rootURI || xpiState.rootURI;
    }

    return newAddon;
  },

  /**
   * Compares the add-ons that are currently installed to those that were
   * known to be installed when the application last ran and applies any
   * changes found to the database.
   * Always called after XPIDatabase.sys.mjs and extensions.json have been
   * loaded.
   *
   * @param {Object} aManifests
   *        A dictionary of cached AddonInstalls for add-ons that have been
   *        installed
   * @param {boolean} aUpdateCompatibility
   *        true to update add-ons appDisabled property when the application
   *        version has changed
   * @param {string?} [aOldAppVersion]
   *        The version of the application last run with this profile or null
   *        if it is a new profile or the version is unknown
   * @param {string?} [aOldPlatformVersion]
   *        The version of the platform last run with this profile or null
   *        if it is a new profile or the version is unknown
   * @param {boolean} aSchemaChange
   *        The schema has changed and all add-on manifests should be re-read.
   * @returns {boolean}
   *        A boolean indicating if a change requiring flushing the caches was
   *        detected
   */
  processFileChanges(
    aManifests,
    aUpdateCompatibility,
    aOldAppVersion,
    aOldPlatformVersion,
    aSchemaChange
  ) {
    let findManifest = (loc, id) => {
      return (aManifests[loc.name] && aManifests[loc.name][id]) || null;
    };

    let previousAddons = new lazy.ExtensionUtils.DefaultMap(() => new Map());
    let currentAddons = new lazy.ExtensionUtils.DefaultMap(() => new Map());

    // Get the previous add-ons from the database and put them into maps by location
    for (let addon of XPIDatabase.getAddons()) {
      previousAddons.get(addon.location.name).set(addon.id, addon);
    }

    // Keep track of add-ons whose blocklist status may have changed. We'll check this
    // after everything else.
    let addonsToCheckAgainstBlocklist = [];

    // Build the list of current add-ons into similar maps. When add-ons are still
    // present we re-use the add-on objects from the database and update their
    // details directly
    let addonStates = new Map();
    for (let location of XPIExports.XPIInternal.XPIStates.locations()) {
      let locationAddons = currentAddons.get(location.name);

      // Get all the on-disk XPI states for this location, and keep track of which
      // ones we see in the database.
      let dbAddons = previousAddons.get(location.name) || new Map();
      for (let [id, oldAddon] of dbAddons) {
        // Check if the add-on is still installed
        let xpiState = location.get(id);
        if (xpiState && !xpiState.missing) {
          let newAddon = this.updateExistingAddon(
            oldAddon,
            xpiState,
            findManifest(location, id),
            aUpdateCompatibility,
            aSchemaChange
          );
          if (newAddon) {
            locationAddons.set(newAddon.id, newAddon);

            // We need to do a blocklist check later, but the add-on may have changed by then.
            // Avoid storing the current copy and just get one when we need one instead.
            addonsToCheckAgainstBlocklist.push(newAddon.id);
          }
        } else {
          // The add-on is in the DB, but not in xpiState (and thus not on disk).
          this.removeMetadata(oldAddon);
        }
      }

      for (let [id, xpiState] of location) {
        if (locationAddons.has(id) || xpiState.missing) {
          continue;
        }
        let newAddon = findManifest(location, id);
        let addon = this.addMetadata(
          location,
          id,
          xpiState,
          newAddon,
          aOldAppVersion,
          aOldPlatformVersion
        );
        if (addon) {
          locationAddons.set(addon.id, addon);
          addonStates.set(addon, xpiState);
        }
      }

      if (this.isSystemAddonLocation(location)) {
        for (let [id, addon] of locationAddons.entries()) {
          const pref = `extensions.${id.split("@")[0]}.enabled`;
          addon.userDisabled = !Services.prefs.getBoolPref(pref, true);
        }
      }
    }

    // Validate the updated system add-ons
    let hideLocation;
    {
      let systemAddonLocation = XPIExports.XPIInternal.XPIStates.getLocation(
        KEY_APP_SYSTEM_ADDONS
      );
      let addons = currentAddons.get(systemAddonLocation.name);

      if (!systemAddonLocation.installer.isValid(addons)) {
        // Hide the system add-on updates if any are invalid.
        logger.info(
          "One or more updated system add-ons invalid, falling back to defaults."
        );
        hideLocation = systemAddonLocation.name;
      }
    }

    // Apply startup changes to any currently-visible add-ons, and
    // uninstall any which were previously visible, but aren't anymore.
    let previousVisible = this.getVisibleAddons(previousAddons);
    let currentVisible = this.flattenByID(currentAddons, hideLocation);

    for (let addon of XPIDatabase.orphanedAddons.splice(0)) {
      if (addon.visible) {
        previousVisible.set(addon.id, addon);
      }
    }

    let promises = [];
    for (let [id, addon] of currentVisible) {
      // If we have a stored manifest for the add-on, it came from the
      // startup data cache, and supersedes any previous XPIStates entry.
      let xpiState =
        !findManifest(addon.location, id) && addonStates.get(addon);

      promises.push(
        this.applyStartupChange(addon, previousVisible.get(id), xpiState)
      );
      previousVisible.delete(id);
    }

    if (promises.some(p => p)) {
      XPIExports.XPIInternal.awaitPromise(Promise.all(promises));
    }

    for (let [id, addon] of previousVisible) {
      if (addon.location) {
        if (addon.location.name == KEY_APP_BUILTINS) {
          continue;
        }
        XPIExports.XPIInternal.BootstrapScope.get(addon).uninstall();
        addon.location.removeAddon(id);
        addon.visible = false;
        addon.active = false;
      }

      lazy.AddonManagerPrivate.addStartupChange(
        lazy.AddonManager.STARTUP_CHANGE_UNINSTALLED,
        id
      );
    }

    // Finally update XPIStates to match everything
    for (let [locationName, locationAddons] of currentAddons) {
      for (let [id, addon] of locationAddons) {
        let xpiState = XPIExports.XPIInternal.XPIStates.getAddon(
          locationName,
          id
        );
        xpiState.syncWithDB(addon);
      }
    }
    XPIExports.XPIInternal.XPIStates.save();
    XPIDatabase.saveChanges();
    XPIDatabase.rebuildingDatabase = false;

    if (aUpdateCompatibility || aSchemaChange) {
      // Do some blocklist checks. These will happen after we've just saved everything,
      // because they're async and depend on the blocklist loading. When we're done, save
      // the data if any of the add-ons' blocklist state has changed.
      lazy.AddonManager.beforeShutdown.addBlocker(
        "Update add-on blocklist state into add-on DB",
        (async () => {
          // Avoid querying the AddonManager immediately to give startup a chance
          // to complete.
          await Promise.resolve();

          let addons = await lazy.AddonManager.getAddonsByIDs(
            addonsToCheckAgainstBlocklist
          );
          await Promise.all(
            addons.map(async addon => {
              if (!addon) {
                return;
              }
              let oldState = addon.blocklistState;
              // TODO 1712316: updateBlocklistState with object parameter only
              // works if addon is an AddonInternal instance. But addon is an
              // AddonWrapper instead. Consequently updateDate:false is ignored.
              await addon.updateBlocklistState({ updateDatabase: false });
              if (oldState !== addon.blocklistState) {
                lazy.Blocklist.recordAddonBlockChangeTelemetry(
                  addon,
                  "addon_db_modified"
                );
              }
            })
          );

          XPIDatabase.saveChanges();
        })()
      );
    }

    return true;
  },

  /**
   * Applies a startup change for the given add-on.
   *
   * @param {AddonInternal} currentAddon
   *        The add-on as it exists in this session.
   * @param {AddonInternal?} previousAddon
   *        The add-on as it existed in the previous session.
   * @param {XPIState?} xpiState
   *        The XPIState entry for this add-on, if one exists.
   * @returns {Promise?}
   *        If an update was performed, returns a promise which resolves
   *        when the appropriate bootstrap methods have been called.
   */
  applyStartupChange(currentAddon, previousAddon, xpiState) {
    let promise;
    let { id } = currentAddon;

    let isActive = !currentAddon.disabled;
    let wasActive = previousAddon ? previousAddon.active : currentAddon.active;

    if (previousAddon) {
      if (previousAddon !== currentAddon) {
        lazy.AddonManagerPrivate.addStartupChange(
          lazy.AddonManager.STARTUP_CHANGE_CHANGED,
          id
        );

        // Bug 1664144:  If the addon changed on disk we will catch it during
        // the second scan initiated by getNewSideloads.  The addon may have
        // already started, if so we need to ensure it restarts during the
        // update, otherwise we're left in a state where the addon is enabled
        // but not started.  We use the bootstrap started state to check that.
        // isActive alone is not sufficient as that changes the characteristics
        // of other updates and breaks many tests.
        let restart =
          isActive &&
          XPIExports.XPIInternal.BootstrapScope.get(currentAddon).started;
        if (restart) {
          logger.warn(
            `Updating and restart addon ${previousAddon.id} that changed on disk after being already started.`
          );
        }
        promise = XPIExports.XPIInternal.BootstrapScope.get(
          previousAddon
        ).update(currentAddon, restart);
      }

      if (isActive != wasActive) {
        let change = isActive
          ? lazy.AddonManager.STARTUP_CHANGE_ENABLED
          : lazy.AddonManager.STARTUP_CHANGE_DISABLED;
        lazy.AddonManagerPrivate.addStartupChange(change, id);
      }
    } else if (xpiState && xpiState.wasRestored) {
      isActive = xpiState.enabled;

      if (currentAddon.isWebExtension && currentAddon.type == "theme") {
        currentAddon.userDisabled = !isActive;
      }

      // If the add-on wasn't active and it isn't already disabled in some way
      // then it was probably either softDisabled or userDisabled
      if (!isActive && !currentAddon.disabled) {
        // If the add-on is softblocked then assume it is softDisabled
        if (
          currentAddon.blocklistState == Services.blocklist.STATE_SOFTBLOCKED
        ) {
          currentAddon.softDisabled = true;
        } else {
          currentAddon.userDisabled = true;
        }
      }
    } else {
      lazy.AddonManagerPrivate.addStartupChange(
        lazy.AddonManager.STARTUP_CHANGE_INSTALLED,
        id
      );
      let scope = XPIExports.XPIInternal.BootstrapScope.get(currentAddon);
      scope.install();
    }

    XPIDatabase.makeAddonVisible(currentAddon);
    currentAddon.active = isActive;
    return promise;
  },
};
PK
!<�y�&��!modules/addons/XPIExports.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file wraps XPIDatabase, XPIInstall, and XPIProvider modules in order to
 * allow testing the shutdown+restart situation in AddonTestUtils.sys.mjs.
 */

// A shared `lazy` object for exports from XPIDatabase, XPIInternal, and
// XPIProvider modules.
//
// Consumers shouldn't store those property values to global variables, except
// for registering XPIProvider.
//
// The list of lazy getters should be in sync with resetXPIExports in
// AddonTestUtils.sys.mjs.
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  // XPIDatabase.sys.mjs
  AddonInternal: "resource://gre/modules/addons/XPIDatabase.sys.mjs",
  BuiltInThemesHelpers: "resource://gre/modules/addons/XPIDatabase.sys.mjs",
  XPIDatabase: "resource://gre/modules/addons/XPIDatabase.sys.mjs",
  XPIDatabaseReconcile: "resource://gre/modules/addons/XPIDatabase.sys.mjs",

  // XPIInstall.sys.mjs
  UpdateChecker: "resource://gre/modules/addons/XPIInstall.sys.mjs",
  XPIInstall: "resource://gre/modules/addons/XPIInstall.sys.mjs",
  verifyBundleSignedState: "resource://gre/modules/addons/XPIInstall.sys.mjs",

  // XPIProvider.sys.mjs
  XPIProvider: "resource://gre/modules/addons/XPIProvider.sys.mjs",
  XPIInternal: "resource://gre/modules/addons/XPIProvider.sys.mjs",
});

export { lazy as XPIExports };
PK
!<�`lo"Q"Q!modules/addons/XPIInstall.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file contains most of the logic required to install extensions.
 * In general, we try to avoid loading it until extension installation
 * or update is required. Please keep that in mind when deciding whether
 * to add code here or elsewhere.
 */

/**
 * @typedef {number} integer
 */

/* eslint "valid-jsdoc": [2, {requireReturn: false, requireReturnDescription: false, prefer: {return: "returns"}}] */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { XPIExports } from "resource://gre/modules/addons/XPIExports.sys.mjs";
import {
  computeSha256HashAsString,
  getHashStringForCrypto,
  hasStrongSignature,
} from "resource://gre/modules/addons/crypto-utils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import {
  AddonManager,
  AddonManagerPrivate,
} from "resource://gre/modules/AddonManager.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonRepository: "resource://gre/modules/addons/AddonRepository.sys.mjs",
  AddonSettings: "resource://gre/modules/addons/AddonSettings.sys.mjs",
  CertUtils: "resource://gre/modules/CertUtils.sys.mjs",
  ExtensionData: "resource://gre/modules/Extension.sys.mjs",
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
  ProductAddonChecker:
    "resource://gre/modules/addons/ProductAddonChecker.sys.mjs",
  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
  UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "IconDetails", () => {
  return ChromeUtils.importESModule(
    "resource://gre/modules/ExtensionParent.sys.mjs"
  ).ExtensionParent.IconDetails;
});

const { nsIBlocklistService } = Ci;

const nsIFile = Components.Constructor(
  "@mozilla.org/file/local;1",
  "nsIFile",
  "initWithPath"
);

const BinaryOutputStream = Components.Constructor(
  "@mozilla.org/binaryoutputstream;1",
  "nsIBinaryOutputStream",
  "setOutputStream"
);
const CryptoHash = Components.Constructor(
  "@mozilla.org/security/hash;1",
  "nsICryptoHash",
  "initWithString"
);
const FileInputStream = Components.Constructor(
  "@mozilla.org/network/file-input-stream;1",
  "nsIFileInputStream",
  "init"
);
const FileOutputStream = Components.Constructor(
  "@mozilla.org/network/file-output-stream;1",
  "nsIFileOutputStream",
  "init"
);
const ZipReader = Components.Constructor(
  "@mozilla.org/libjar/zip-reader;1",
  "nsIZipReader",
  "open"
);

XPCOMUtils.defineLazyServiceGetters(lazy, {
  gCertDB: ["@mozilla.org/security/x509certdb;1", "nsIX509CertDB"],
});

const PREF_INSTALL_REQUIRESECUREORIGIN =
  "extensions.install.requireSecureOrigin";
const PREF_PENDING_OPERATIONS = "extensions.pendingOperations";
const PREF_SYSTEM_ADDON_UPDATE_URL = "extensions.systemAddon.update.url";
const PREF_XPI_ENABLED = "xpinstall.enabled";
const PREF_XPI_DIRECT_WHITELISTED = "xpinstall.whitelist.directRequest";
const PREF_XPI_FILE_WHITELISTED = "xpinstall.whitelist.fileRequest";
const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required";
const PREF_XPI_WEAK_SIGNATURES_ALLOWED =
  "xpinstall.signatures.weakSignaturesTemporarilyAllowed";

const PREF_SELECTED_THEME = "extensions.activeThemeID";

const TOOLKIT_ID = "toolkit@mozilla.org";

ChromeUtils.defineLazyGetter(lazy, "MOZ_UNSIGNED_SCOPES", () => {
  let result = 0;
  if (AppConstants.MOZ_UNSIGNED_APP_SCOPE) {
    result |= AddonManager.SCOPE_APPLICATION;
  }
  if (AppConstants.MOZ_UNSIGNED_SYSTEM_SCOPE) {
    result |= AddonManager.SCOPE_SYSTEM;
  }
  return result;
});

/**
 * Returns a nsIFile instance for the given path, relative to the given
 * base file, if provided.
 *
 * @param {string} path
 *        The (possibly relative) path of the file.
 * @param {nsIFile} [base]
 *        An optional file to use as a base path if `path` is relative.
 * @returns {nsIFile}
 */
function getFile(path, base = null) {
  // First try for an absolute path, as we get in the case of proxy
  // files. Ideally we would try a relative path first, but on Windows,
  // paths which begin with a drive letter are valid as relative paths,
  // and treated as such.
  try {
    return new nsIFile(path);
  } catch (e) {
    // Ignore invalid relative paths. The only other error we should see
    // here is EOM, and either way, any errors that we care about should
    // be re-thrown below.
  }

  // If the path isn't absolute, we must have a base path.
  let file = base.clone();
  file.appendRelativePath(path);
  return file;
}

/**
 * Sends local and remote notifications to flush a JAR file cache entry
 *
 * @param {nsIFile} aJarFile
 *        The ZIP/XPI/JAR file as a nsIFile
 */
function flushJarCache(aJarFile) {
  Services.obs.notifyObservers(aJarFile, "flush-cache-entry");
  Services.ppmm.broadcastAsyncMessage(MSG_JAR_FLUSH, {
    path: aJarFile.path,
  });
}

const PREF_EM_UPDATE_BACKGROUND_URL = "extensions.update.background.url";
const PREF_EM_UPDATE_URL = "extensions.update.url";
const PREF_XPI_SIGNATURES_DEV_ROOT = "xpinstall.signatures.dev-root";

const KEY_TEMPDIR = "TmpD";

// This is a random number array that can be used as "salt" when generating
// an automatic ID based on the directory path of an add-on. It will prevent
// someone from creating an ID for a permanent add-on that could be replaced
// by a temporary add-on (because that would be confusing, I guess).
const TEMP_INSTALL_ID_GEN_SESSION = new Uint8Array(
  Float64Array.of(Math.random()).buffer
);

const MSG_JAR_FLUSH = "Extension:FlushJarCache";

/**
 * Valid IDs fit this pattern.
 */
var gIDTest =
  /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i;

import { Log } from "resource://gre/modules/Log.sys.mjs";

const LOGGER_ID = "addons.xpi";

// Create a new logger for use by all objects in this Addons XPI Provider module
// (Requires AddonManager.sys.mjs)
var logger = Log.repository.getLogger(LOGGER_ID);

// Stores the ID of the theme which was selected during the last session,
// if any. When installing a new built-in theme with this ID, it will be
// automatically enabled.
let lastSelectedTheme = null;

function getJarURI(file, path = "") {
  if (file instanceof Ci.nsIFile) {
    file = Services.io.newFileURI(file);
  }
  if (file instanceof Ci.nsIURI) {
    file = file.spec;
  }
  return Services.io.newURI(`jar:${file}!/${path}`);
}

let DirPackage;
let XPIPackage;
class Package {
  static get(file) {
    if (file.isFile()) {
      return new XPIPackage(file);
    }
    return new DirPackage(file);
  }

  constructor(file, rootURI) {
    this.file = file;
    this.filePath = file.path;
    this.rootURI = rootURI;
  }

  close() {}

  async readString(...path) {
    let buffer = await this.readBinary(...path);
    return new TextDecoder().decode(buffer);
  }

  async verifySignedState(addonId, addonType, addonLocation) {
    if (!shouldVerifySignedState(addonType, addonLocation)) {
      return {
        signedState: AddonManager.SIGNEDSTATE_NOT_REQUIRED,
        cert: null,
      };
    }

    let root = Ci.nsIX509CertDB.AddonsPublicRoot;
    if (
      (!AppConstants.MOZ_REQUIRE_SIGNING ||
        // Allow mochitests to switch to dev-root on all channels.
        Cu.isInAutomation ||
        // Allow xpcshell tests to switch to dev-root on all channels,
        // included tests where "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer"
        // pref is set to false and Cu.isInAutomation is going to be false (e.g. test_signed_langpack.js).
        // TODO(Bug 1598804): we should be able to remove the following checks once Cu.isAutomation is fixed.
        (Services.env.exists("XPCSHELL_TEST_PROFILE_DIR") &&
          Services.appinfo.name === "XPCShell")) &&
      Services.prefs.getBoolPref(PREF_XPI_SIGNATURES_DEV_ROOT, false)
    ) {
      root = Ci.nsIX509CertDB.AddonsStageRoot;
    }

    return this.verifySignedStateForRoot(addonId, root);
  }

  flushCache() {}
}

DirPackage = class DirPackage extends Package {
  constructor(file) {
    super(file, Services.io.newFileURI(file));
  }

  hasResource(...path) {
    return IOUtils.exists(PathUtils.join(this.filePath, ...path));
  }

  async iterDirectory(path, callback) {
    let fullPath = PathUtils.join(this.filePath, ...path);

    let children = await IOUtils.getChildren(fullPath);
    for (let path of children) {
      let { type } = await IOUtils.stat(path);
      callback({
        isDir: type == "directory",
        name: PathUtils.filename(path),
        path,
      });
    }
  }

  iterFiles(callback, path = []) {
    return this.iterDirectory(path, async entry => {
      let entryPath = [...path, entry.name];
      if (entry.isDir) {
        callback({
          path: entryPath.join("/"),
          isDir: true,
        });
        await this.iterFiles(callback, entryPath);
      } else {
        callback({
          path: entryPath.join("/"),
          isDir: false,
        });
      }
    });
  }

  readBinary(...path) {
    return IOUtils.read(PathUtils.join(this.filePath, ...path));
  }

  async verifySignedStateForRoot() {
    return { signedState: AddonManager.SIGNEDSTATE_UNKNOWN, cert: null };
  }
};

XPIPackage = class XPIPackage extends Package {
  constructor(file) {
    super(file, getJarURI(file));

    this.zipReader = new ZipReader(file);
  }

  close() {
    this.zipReader.close();
    this.zipReader = null;
    this.flushCache();
  }

  async hasResource(...path) {
    return this.zipReader.hasEntry(path.join("/"));
  }

  async iterFiles(callback) {
    for (let path of this.zipReader.findEntries("*")) {
      let entry = this.zipReader.getEntry(path);
      callback({
        path,
        isDir: entry.isDirectory,
      });
    }
  }

  async readBinary(...path) {
    let response = await fetch(this.rootURI.resolve(path.join("/")));
    return response.arrayBuffer();
  }

  verifySignedStateForRoot(addonId, root) {
    return new Promise(resolve => {
      let callback = {
        openSignedAppFileFinished(aRv, aZipReader, aSignatureInfos) {
          // aSignatureInfos is an array of nsIAppSignatureInfo.
          // In the future, this code can iterate through the array to
          // determine if one of the verified signatures used a satisfactory
          // algorithm and signing certificate.
          // For now, any verified signature is acceptable.
          let cert;
          if (aRv == Cr.NS_OK && aSignatureInfos.length) {
            cert = aSignatureInfos[0].signerCert;
          }
          if (aZipReader) {
            aZipReader.close();
          }
          resolve({
            cert,
            signedState: getSignedStatus(aRv, cert, addonId),
            signedTypes: aSignatureInfos?.map(
              signatureInfo => signatureInfo.signatureAlgorithm
            ),
          });
        },
      };
      // This allows the certificate DB to get the raw JS callback object so the
      // test code can pass through objects that XPConnect would reject.
      callback.wrappedJSObject = callback;

      lazy.gCertDB.openSignedAppFileAsync(root, this.file, callback);
    });
  }

  flushCache() {
    flushJarCache(this.file);
  }
};

/**
 * Return an object that implements enough of the Package interface
 * to allow loadManifest() to work for a built-in addon (ie, one loaded
 * from a resource: url)
 *
 * @param {nsIURL} baseURL The URL for the root of the add-on.
 * @returns {object}
 */
function builtinPackage(baseURL) {
  return {
    rootURI: baseURL,
    filePath: baseURL.spec,
    file: null,
    verifySignedState() {
      return {
        signedState: AddonManager.SIGNEDSTATE_NOT_REQUIRED,
        cert: null,
      };
    },
    async hasResource(path) {
      try {
        let response = await fetch(this.rootURI.resolve(path));
        return response.ok;
      } catch (e) {
        return false;
      }
    },
  };
}

/**
 * Determine the reason to pass to an extension's bootstrap methods when
 * switch between versions.
 *
 * @param {string} oldVersion The version of the existing extension instance.
 * @param {string} newVersion The version of the extension being installed.
 *
 * @returns {integer}
 *        BOOSTRAP_REASONS.ADDON_UPGRADE or BOOSTRAP_REASONS.ADDON_DOWNGRADE
 */
function newVersionReason(oldVersion, newVersion) {
  return Services.vc.compare(oldVersion, newVersion) <= 0
    ? XPIExports.XPIInternal.BOOTSTRAP_REASONS.ADDON_UPGRADE
    : XPIExports.XPIInternal.BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
}

// Behaves like Promise.all except waits for all promises to resolve/reject
// before resolving/rejecting itself
function waitForAllPromises(promises) {
  return new Promise((resolve, reject) => {
    let shouldReject = false;
    let rejectValue = null;

    let newPromises = promises.map(p =>
      p.catch(value => {
        shouldReject = true;
        rejectValue = value;
      })
    );
    Promise.all(newPromises).then(results =>
      shouldReject ? reject(rejectValue) : resolve(results)
    );
  });
}

/**
 * Reads an AddonInternal object from a webextension manifest.json
 *
 * @param {Package} aPackage
 *        The install package for the add-on
 * @param {XPIStateLocation} aLocation
 *        The install location the add-on is installed in, or will be
 *        installed to.
 * @returns {{ addon: AddonInternal, verifiedSignedState: object}}
 * @throws if the install manifest in the stream is corrupt or could not
 *         be read
 */
async function loadManifestFromWebManifest(aPackage, aLocation) {
  let verifiedSignedState;
  const temporarilyInstalled = aLocation.isTemporary;
  let extension = await lazy.ExtensionData.constructAsync({
    rootURI: XPIExports.XPIInternal.maybeResolveURI(aPackage.rootURI),
    temporarilyInstalled,
    async checkPrivileged(type, id) {
      verifiedSignedState = await aPackage.verifySignedState(
        id,
        type,
        aLocation
      );
      return lazy.ExtensionData.getIsPrivileged({
        signedState: verifiedSignedState.signedState,
        builtIn: aLocation.isBuiltin,
        temporarilyInstalled,
      });
    },
  });

  let manifest = await extension.loadManifest();

  // Read the list of available locales, and pre-load messages for
  // all locales.
  let locales = !extension.errors.length
    ? await extension.initAllLocales()
    : null;

  if (extension.errors.length) {
    let error = new Error("Extension is invalid");
    // Add detailed errors on the error object so that the front end can display them
    // if needed (eg in about:debugging).
    error.additionalErrors = extension.errors;
    throw error;
  }

  // Internally, we use the `applications` key but it is because we assign the value
  // of `browser_specific_settings` to `applications` in `ExtensionData.parseManifest()`.
  // Yet, as of MV3, only `browser_specific_settings` is accepted in manifest.json files.
  let bss = manifest.applications?.gecko || {};

  // A * is illegal in strict_min_version
  if (bss.strict_min_version?.split(".").some(part => part == "*")) {
    throw new Error("The use of '*' in strict_min_version is invalid");
  }

  let addon = new XPIExports.AddonInternal();
  addon.id = bss.id;
  addon.version = manifest.version;
  addon.manifestVersion = manifest.manifest_version;
  addon.name = manifest.name;
  addon.type = extension.type;
  addon.loader = null;
  addon.strictCompatibility = true;
  addon.internalName = null;
  addon.updateURL = bss.update_url;
  addon.installOrigins = manifest.install_origins;
  addon.optionsBrowserStyle = true;
  addon.optionsURL = null;
  addon.optionsType = null;
  addon.aboutURL = null;
  addon.dependencies = Object.freeze(Array.from(extension.dependencies));
  addon.startupData = extension.startupData;
  addon.hidden = extension.isPrivileged && manifest.hidden;
  addon.incognito = manifest.incognito;

  if (addon.type === "theme" && (await aPackage.hasResource("preview.png"))) {
    addon.previewImage = "preview.png";
  }

  const { optionsPageProperties } = extension;
  if (optionsPageProperties) {
    // Store just the relative path here, the AddonWrapper getURL
    // wrapper maps this to a full URL.
    addon.optionsURL = optionsPageProperties.page;
    if (optionsPageProperties.open_in_tab) {
      addon.optionsType = AddonManager.OPTIONS_TYPE_TAB;
    } else {
      addon.optionsType = AddonManager.OPTIONS_TYPE_INLINE_BROWSER;
    }

    addon.optionsBrowserStyle = optionsPageProperties.browser_style;
  }

  // WebExtensions don't use iconURLs
  addon.iconURL = null;
  addon.icons = manifest.icons || {};
  addon.userPermissions = extension.getRequiredPermissions();
  addon.optionalPermissions = extension.manifestOptionalPermissions;
  addon.requestedPermissions = extension.getRequestedPermissions();
  addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;

  function getLocale(aLocale) {
    // Use the raw manifest, here, since we need values with their
    // localization placeholders still in place.
    let rawManifest = extension.rawManifest;

    // As a convenience, allow author to be set if its a string bug 1313567.
    let creator =
      typeof rawManifest.author === "string" ? rawManifest.author : null;
    let homepageURL = rawManifest.homepage_url;

    // Allow developer to override creator and homepage_url.
    if (rawManifest.developer) {
      if (rawManifest.developer.name) {
        creator = rawManifest.developer.name;
      }
      if (rawManifest.developer.url) {
        homepageURL = rawManifest.developer.url;
      }
    }

    let result = {
      name: extension.localize(rawManifest.name, aLocale),
      description: extension.localize(rawManifest.description, aLocale),
      creator: extension.localize(creator, aLocale),
      homepageURL: extension.localize(homepageURL, aLocale),

      developers: null,
      translators: null,
      contributors: null,
      locales: [aLocale],
    };
    return result;
  }

  addon.defaultLocale = getLocale(extension.defaultLocale);
  addon.locales = Array.from(locales.keys(), getLocale);

  delete addon.defaultLocale.locales;

  addon.targetApplications = [
    {
      id: TOOLKIT_ID,
      minVersion: bss.strict_min_version,
      maxVersion: bss.strict_max_version,
    },
  ];

  addon.adminInstallOnly = bss.admin_install_only;

  addon.targetPlatforms = [];
  // Themes are disabled by default, except when they're installed from a web page.
  addon.userDisabled = extension.type === "theme";
  addon.softDisabled =
    addon.blocklistState == nsIBlocklistService.STATE_SOFTBLOCKED;

  return { addon, verifiedSignedState };
}

async function readRecommendationStates(aPackage, aAddonID) {
  let recommendationData;
  try {
    recommendationData = await aPackage.readString(
      "mozilla-recommendation.json"
    );
  } catch (e) {
    // Ignore I/O errors.
    return null;
  }

  try {
    recommendationData = JSON.parse(recommendationData);
  } catch (e) {
    logger.warn("Failed to parse recommendation", e);
  }

  if (recommendationData) {
    let { addon_id, states, validity } = recommendationData;

    if (addon_id === aAddonID && Array.isArray(states) && validity) {
      let validNotAfter = Date.parse(validity.not_after);
      let validNotBefore = Date.parse(validity.not_before);
      if (validNotAfter && validNotBefore) {
        return {
          validNotAfter,
          validNotBefore,
          states,
        };
      }
    }
    logger.warn(
      `Invalid recommendation for ${aAddonID}: ${JSON.stringify(
        recommendationData
      )}`
    );
  }

  return null;
}

function defineSyncGUID(aAddon) {
  // Define .syncGUID as a lazy property which is also settable
  Object.defineProperty(aAddon, "syncGUID", {
    get: () => {
      aAddon.syncGUID = Services.uuid.generateUUID().toString();
      return aAddon.syncGUID;
    },
    set: val => {
      delete aAddon.syncGUID;
      aAddon.syncGUID = val;
    },
    configurable: true,
    enumerable: true,
  });
}

// Generate a unique ID based on the path to this temporary add-on location.
function generateTemporaryInstallID(aFile) {
  const hasher = CryptoHash("sha1");
  const data = new TextEncoder().encode(aFile.path);
  // Make it so this ID cannot be guessed.
  const sess = TEMP_INSTALL_ID_GEN_SESSION;
  hasher.update(sess, sess.length);
  hasher.update(data, data.length);
  let id = `${getHashStringForCrypto(hasher)}${
    XPIExports.XPIInternal.TEMPORARY_ADDON_SUFFIX
  }`;
  logger.info(`Generated temp id ${id} (${sess.join("")}) for ${aFile.path}`);
  return id;
}

var loadManifest = async function (aPackage, aLocation, aOldAddon) {
  let addon;
  let verifiedSignedState;
  if (await aPackage.hasResource("manifest.json")) {
    ({ addon, verifiedSignedState } = await loadManifestFromWebManifest(
      aPackage,
      aLocation
    ));
  } else {
    // TODO bug 1674799: Remove this unused branch.
    for (let loader of AddonManagerPrivate.externalExtensionLoaders.values()) {
      if (await aPackage.hasResource(loader.manifestFile)) {
        addon = await loader.loadManifest(aPackage);
        addon.loader = loader.name;
        verifiedSignedState = await aPackage.verifySignedState(
          addon.id,
          addon.type,
          aLocation
        );
        break;
      }
    }
  }

  if (!addon) {
    throw new Error(
      `File ${aPackage.filePath} does not contain a valid manifest`
    );
  }

  addon._sourceBundle = aPackage.file;
  addon.rootURI = aPackage.rootURI.spec;
  addon.location = aLocation;

  let { cert, signedState, signedTypes } = verifiedSignedState;
  addon.signedState = signedState;
  addon.signedDate = cert?.validity?.notBefore / 1000 || null;
  // An array of the algorithms used by the signatures found in the signed XPI files,
  // as an array of integers (see nsIAppSignatureInfo_SignatureAlgorithm enum defined
  // in nsIX509CertDB.idl).
  addon.signedTypes = signedTypes;

  if (!addon.id) {
    if (cert) {
      addon.id = cert.commonName;
      if (!gIDTest.test(addon.id)) {
        throw new Error(`Extension is signed with an invalid id (${addon.id})`);
      }
    }
    if (!addon.id && aLocation.isTemporary) {
      addon.id = generateTemporaryInstallID(aPackage.file);
    }
  }

  addon.propagateDisabledState(aOldAddon);
  if (!aLocation.isSystem && !aLocation.isBuiltin) {
    if (addon.type === "extension" && !aLocation.isTemporary) {
      addon.recommendationState = await readRecommendationStates(
        aPackage,
        addon.id
      );
    }

    await addon.updateBlocklistState();
    addon.appDisabled = !XPIExports.XPIDatabase.isUsableAddon(addon);

    // Always report when there is an attempt to install a blocked add-on.
    // (transitions from STATE_BLOCKED to STATE_NOT_BLOCKED are checked
    //  in the individual AddonInstall subclasses).
    if (addon.blocklistState == nsIBlocklistService.STATE_BLOCKED) {
      addon.recordAddonBlockChangeTelemetry(
        aOldAddon ? "addon_update" : "addon_install"
      );
    }
  }

  defineSyncGUID(addon);

  return addon;
};

/**
 * Loads an add-on's manifest from the given file or directory.
 *
 * @param {nsIFile} aFile
 *        The file to load the manifest from.
 * @param {XPIStateLocation} aLocation
 *        The install location the add-on is installed in, or will be
 *        installed to.
 * @param {AddonInternal?} aOldAddon
 *        The currently-installed add-on with the same ID, if one exist.
 *        This is used to migrate user settings like the add-on's
 *        disabled state.
 * @returns {AddonInternal}
 *        The parsed Addon object for the file's manifest.
 */
var loadManifestFromFile = async function (aFile, aLocation, aOldAddon) {
  let pkg = Package.get(aFile);
  try {
    let addon = await loadManifest(pkg, aLocation, aOldAddon);
    return addon;
  } finally {
    pkg.close();
  }
};

/*
 * A synchronous method for loading an add-on's manifest. Do not use
 * this.
 */
function syncLoadManifest(state, location, oldAddon) {
  if (location.name == "app-builtin") {
    let pkg = builtinPackage(Services.io.newURI(state.rootURI));
    return XPIExports.XPIInternal.awaitPromise(
      loadManifest(pkg, location, oldAddon)
    );
  }

  let file = new nsIFile(state.path);
  let pkg = Package.get(file);
  return XPIExports.XPIInternal.awaitPromise(
    (async () => {
      try {
        let addon = await loadManifest(pkg, location, oldAddon);
        addon.rootURI = XPIExports.XPIInternal.getURIForResourceInFile(
          file,
          ""
        ).spec;
        return addon;
      } finally {
        pkg.close();
      }
    })()
  );
}

/**
 * Creates and returns a new unique temporary file. The caller should delete
 * the file when it is no longer needed.
 *
 * @returns {nsIFile}
 *       An nsIFile that points to a randomly named, initially empty file in
 *       the OS temporary files directory
 */
function getTemporaryFile() {
  let file = lazy.FileUtils.getDir(KEY_TEMPDIR, []);
  let random = Math.round(Math.random() * 36 ** 3).toString(36);
  file.append(`tmp-${random}.xpi`);
  file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, lazy.FileUtils.PERMS_FILE);
  return file;
}

function getHashForFile(file, algorithm) {
  let crypto = CryptoHash(algorithm);
  let fis = new FileInputStream(file, -1, -1, false);
  try {
    crypto.updateFromStream(fis, file.fileSize);
  } finally {
    fis.close();
  }
  return getHashStringForCrypto(crypto);
}

/**
 * Returns the signedState for a given return code and certificate by verifying
 * it against the expected ID.
 *
 * @param {nsresult} aRv
 *        The result code returned by the signature checker for the
 *        signature check operation.
 * @param {nsIX509Cert?} aCert
 *        The certificate the add-on was signed with, if a valid
 *        certificate exists.
 * @param {string?} aAddonID
 *        The expected ID of the add-on. If passed, this must match the
 *        ID in the certificate's CN field.
 * @returns {number}
 *        A SIGNEDSTATE result code constant, as defined on the
 *        AddonManager class.
 */
function getSignedStatus(aRv, aCert, aAddonID) {
  let expectedCommonName = aAddonID;
  if (aAddonID && aAddonID.length > 64) {
    expectedCommonName = computeSha256HashAsString(aAddonID);
  }

  switch (aRv) {
    case Cr.NS_OK:
      if (expectedCommonName && expectedCommonName != aCert.commonName) {
        return AddonManager.SIGNEDSTATE_BROKEN;
      }

      if (aCert.organizationalUnit == "Mozilla Components") {
        return AddonManager.SIGNEDSTATE_SYSTEM;
      }

      if (aCert.organizationalUnit == "Mozilla Extensions") {
        return AddonManager.SIGNEDSTATE_PRIVILEGED;
      }

      return /preliminary/i.test(aCert.organizationalUnit)
        ? AddonManager.SIGNEDSTATE_PRELIMINARY
        : AddonManager.SIGNEDSTATE_SIGNED;
    case Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED:
      return AddonManager.SIGNEDSTATE_MISSING;
    case Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID:
    case Cr.NS_ERROR_SIGNED_JAR_ENTRY_INVALID:
    case Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING:
    case Cr.NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE:
    case Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY:
    case Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY:
      return AddonManager.SIGNEDSTATE_BROKEN;
    default:
      // Any other error indicates that either the add-on isn't signed or it
      // is signed by a signature that doesn't chain to the trusted root.
      return AddonManager.SIGNEDSTATE_UNKNOWN;
  }
}

function shouldVerifySignedState(aAddonType, aLocation) {
  // TODO when KEY_APP_SYSTEM_DEFAULTS and KEY_APP_SYSTEM_ADDONS locations
  // are removed, we need to reorganize the logic here.  At that point we
  // should:
  //   if builtin or MOZ_UNSIGNED_SCOPES return false
  //   if system return true
  //   return SIGNED_TYPES.has(type)

  // We don't care about signatures for default system add-ons
  if (aLocation.name == XPIExports.XPIInternal.KEY_APP_SYSTEM_DEFAULTS) {
    return false;
  }

  // Updated system add-ons should always have their signature checked
  if (aLocation.isSystem) {
    return true;
  }

  if (aLocation.isBuiltin || aLocation.scope & lazy.MOZ_UNSIGNED_SCOPES) {
    return false;
  }

  // Otherwise only check signatures if the add-on is one of the signed
  // types.
  return XPIExports.XPIDatabase.SIGNED_TYPES.has(aAddonType);
}

/**
 * Verifies that a bundle's contents are all correctly signed by an
 * AMO-issued certificate
 *
 * @param {nsIFile} aBundle
 *        The nsIFile for the bundle to check, either a directory or zip file.
 * @param {AddonInternal} aAddon
 *        The add-on object to verify.
 * @returns {Promise<{ signedState: number, signedTypes: Array<number>}>?}
 *        A Promise that resolves to object including a signedState property set to
 *        an AddonManager.SIGNEDSTATE_* constant and a signedTypes property set to
 *        either an array of Ci.nsIAppSignatureInfo SignatureAlgorithm enum values
 *        or undefined if the file wasn't signed.
 */
export var verifyBundleSignedState = async function (aBundle, aAddon) {
  let pkg = Package.get(aBundle);
  try {
    let { signedState, signedTypes } = await pkg.verifySignedState(
      aAddon.id,
      aAddon.type,
      aAddon.location
    );
    return { signedState, signedTypes };
  } finally {
    pkg.close();
  }
};

/**
 * Replaces %...% strings in an addon url (update and updateInfo) with
 * appropriate values.
 *
 * @param {AddonInternal} aAddon
 *        The AddonInternal representing the add-on
 * @param {string} aUri
 *        The URI to escape
 * @param {integer?} aUpdateType
 *        An optional number representing the type of update, only applicable
 *        when creating a url for retrieving an update manifest
 * @param {string?} aAppVersion
 *        The optional application version to use for %APP_VERSION%
 * @returns {string}
 *       The appropriately escaped URI.
 */
function escapeAddonURI(aAddon, aUri, aUpdateType, aAppVersion) {
  let uri = AddonManager.escapeAddonURI(aAddon, aUri, aAppVersion);

  // If there is an updateType then replace the UPDATE_TYPE string
  if (aUpdateType) {
    uri = uri.replace(/%UPDATE_TYPE%/g, aUpdateType);
  }

  // If this add-on has compatibility information for either the current
  // application or toolkit then replace the ITEM_MAXAPPVERSION with the
  // maxVersion
  let app = aAddon.matchingTargetApplication;
  if (app) {
    var maxVersion = app.maxVersion;
  } else {
    maxVersion = "";
  }
  uri = uri.replace(/%ITEM_MAXAPPVERSION%/g, maxVersion);

  let compatMode = "normal";
  if (!AddonManager.checkCompatibility) {
    compatMode = "ignore";
  } else if (AddonManager.strictCompatibility) {
    compatMode = "strict";
  }
  uri = uri.replace(/%COMPATIBILITY_MODE%/g, compatMode);

  return uri;
}

/**
 * Converts an iterable of addon objects into a map with the add-on's ID as key.
 *
 * @param {sequence<AddonInternal>} addons
 *        A sequence of AddonInternal objects.
 *
 * @returns {Map<string, AddonInternal>}
 */
function addonMap(addons) {
  return new Map(addons.map(a => [a.id, a]));
}

async function removeAsync(aFile) {
  await IOUtils.remove(aFile.path, { ignoreAbsent: true, recursive: true });
}

/**
 * Recursively removes a directory or file fixing permissions when necessary.
 *
 * @param {nsIFile} aFile
 *        The nsIFile to remove
 */
function recursiveRemove(aFile) {
  let isDir = null;

  try {
    isDir = aFile.isDirectory();
  } catch (e) {
    // If the file has already gone away then don't worry about it, this can
    // happen on OSX where the resource fork is automatically moved with the
    // data fork for the file. See bug 733436.
    if (e.result == Cr.NS_ERROR_FILE_NOT_FOUND) {
      return;
    }

    throw e;
  }

  setFilePermissions(
    aFile,
    isDir ? lazy.FileUtils.PERMS_DIRECTORY : lazy.FileUtils.PERMS_FILE
  );

  try {
    aFile.remove(true);
    return;
  } catch (e) {
    if (!aFile.isDirectory() || aFile.isSymlink()) {
      logger.error("Failed to remove file " + aFile.path, e);
      throw e;
    }
  }

  // Use a snapshot of the directory contents to avoid possible issues with
  // iterating over a directory while removing files from it (the YAFFS2
  // embedded filesystem has this issue, see bug 772238), and to remove
  // normal files before their resource forks on OSX (see bug 733436).
  let entries = Array.from(XPIExports.XPIInternal.iterDirectory(aFile));
  entries.forEach(recursiveRemove);

  try {
    aFile.remove(true);
  } catch (e) {
    logger.error("Failed to remove empty directory " + aFile.path, e);
    throw e;
  }
}

/**
 * Sets permissions on a file
 *
 * @param {nsIFile} aFile
 *        The file or directory to operate on.
 * @param {integer} aPermissions
 *        The permissions to set
 */
function setFilePermissions(aFile, aPermissions) {
  try {
    aFile.permissions = aPermissions;
  } catch (e) {
    logger.warn(
      "Failed to set permissions " +
        aPermissions.toString(8) +
        " on " +
        aFile.path,
      e
    );
  }
}

/**
 * Write a given string to a file
 *
 * @param {nsIFile} file
 *        The nsIFile instance to write into
 * @param {string} string
 *        The string to write
 */
function writeStringToFile(file, string) {
  let fileStream = new FileOutputStream(
    file,
    lazy.FileUtils.MODE_WRONLY |
      lazy.FileUtils.MODE_CREATE |
      lazy.FileUtils.MODE_TRUNCATE,
    lazy.FileUtils.PERMS_FILE,
    0
  );

  try {
    let binStream = new BinaryOutputStream(fileStream);

    binStream.writeByteArray(new TextEncoder().encode(string));
  } finally {
    fileStream.close();
  }
}

/**
 * A safe way to install a file or the contents of a directory to a new
 * directory. The file or directory is moved or copied recursively and if
 * anything fails an attempt is made to rollback the entire operation. The
 * operation may also be rolled back to its original state after it has
 * completed by calling the rollback method.
 *
 * Operations can be chained. Calling move or copy multiple times will remember
 * the whole set and if one fails all of the operations will be rolled back.
 */
function SafeInstallOperation() {
  this._installedFiles = [];
  this._createdDirs = [];
}

SafeInstallOperation.prototype = {
  _installedFiles: null,
  _createdDirs: null,

  _installFile(aFile, aTargetDirectory, aCopy) {
    let oldFile = aCopy ? null : aFile.clone();
    let newFile = aFile.clone();
    try {
      if (aCopy) {
        newFile.copyTo(aTargetDirectory, null);
        // copyTo does not update the nsIFile with the new.
        newFile = getFile(aFile.leafName, aTargetDirectory);
        // Windows roaming profiles won't properly sync directories if a new file
        // has an older lastModifiedTime than a previous file, so update.
        newFile.lastModifiedTime = Date.now();
      } else {
        newFile.moveTo(aTargetDirectory, null);
      }
    } catch (e) {
      logger.error(
        "Failed to " +
          (aCopy ? "copy" : "move") +
          " file " +
          aFile.path +
          " to " +
          aTargetDirectory.path,
        e
      );
      throw e;
    }
    this._installedFiles.push({ oldFile, newFile });
  },

  /**
   * Moves a file or directory into a new directory. If an error occurs then all
   * files that have been moved will be moved back to their original location.
   *
   * @param {nsIFile} aFile
   *        The file or directory to be moved.
   * @param {nsIFile} aTargetDirectory
   *        The directory to move into, this is expected to be an empty
   *        directory.
   */
  moveUnder(aFile, aTargetDirectory) {
    try {
      this._installFile(aFile, aTargetDirectory, false);
    } catch (e) {
      this.rollback();
      throw e;
    }
  },

  /**
   * Renames a file to a new location.  If an error occurs then all
   * files that have been moved will be moved back to their original location.
   *
   * @param {nsIFile} aOldLocation
   *        The old location of the file.
   * @param {nsIFile} aNewLocation
   *        The new location of the file.
   */
  moveTo(aOldLocation, aNewLocation) {
    try {
      let oldFile = aOldLocation.clone(),
        newFile = aNewLocation.clone();
      oldFile.moveTo(newFile.parent, newFile.leafName);
      this._installedFiles.push({ oldFile, newFile, isMoveTo: true });
    } catch (e) {
      this.rollback();
      throw e;
    }
  },

  /**
   * Copies a file or directory into a new directory. If an error occurs then
   * all new files that have been created will be removed.
   *
   * @param {nsIFile} aFile
   *        The file or directory to be copied.
   * @param {nsIFile} aTargetDirectory
   *        The directory to copy into, this is expected to be an empty
   *        directory.
   */
  copy(aFile, aTargetDirectory) {
    try {
      this._installFile(aFile, aTargetDirectory, true);
    } catch (e) {
      this.rollback();
      throw e;
    }
  },

  /**
   * Rolls back all the moves that this operation performed. If an exception
   * occurs here then both old and new directories are left in an indeterminate
   * state
   */
  rollback() {
    while (this._installedFiles.length) {
      let move = this._installedFiles.pop();
      if (move.isMoveTo) {
        move.newFile.moveTo(move.oldDir.parent, move.oldDir.leafName);
      } else if (move.newFile.isDirectory() && !move.newFile.isSymlink()) {
        let oldDir = getFile(move.oldFile.leafName, move.oldFile.parent);
        oldDir.create(
          Ci.nsIFile.DIRECTORY_TYPE,
          lazy.FileUtils.PERMS_DIRECTORY
        );
      } else if (!move.oldFile) {
        // No old file means this was a copied file
        move.newFile.remove(true);
      } else {
        move.newFile.moveTo(move.oldFile.parent, null);
      }
    }

    while (this._createdDirs.length) {
      recursiveRemove(this._createdDirs.pop());
    }
  },
};

// A hash algorithm if the caller of AddonInstall did not specify one.
const DEFAULT_HASH_ALGO = "sha256";

/**
 * Base class for objects that manage the installation of an addon.
 * This class isn't instantiated directly, see the derived classes below.
 */
class AddonInstall {
  /**
   * Instantiates an AddonInstall.
   *
   * @param {XPIStateLocation} installLocation
   *        The install location the add-on will be installed into
   * @param {nsIURL} url
   *        The nsIURL to get the add-on from. If this is an nsIFileURL then
   *        the add-on will not need to be downloaded
   * @param {Object} [options = {}]
   *        Additional options for the install
   * @param {string} [options.hash]
   *        An optional hash for the add-on
   * @param {AddonInternal} [options.existingAddon]
   *        The add-on this install will update if known
   * @param {string} [options.name]
   *        An optional name for the add-on
   * @param {string} [options.type]
   *        An optional type for the add-on
   * @param {object} [options.icons]
   *        Optional icons for the add-on
   * @param {string} [options.version]
   *        The expected version for the add-on.
   *        Required for updates, i.e. when existingAddon is set.
   * @param {Object?} [options.telemetryInfo]
   *        An optional object which provides details about the installation source
   *        included in the addon manager telemetry events.
   * @param {boolean} [options.isUserRequestedUpdate]
   *        An optional boolean, true if the install object is related to a user triggered update.
   * @param {nsIURL} [options.releaseNotesURI]
   *        An optional nsIURL that release notes where release notes can be retrieved.
   * @param {function(string) : Promise<void>} [options.promptHandler]
   *        A callback to prompt the user before installing.
   */
  constructor(installLocation, url, options = {}) {
    this.wrapper = new AddonInstallWrapper(this);
    this.location = installLocation;
    this.sourceURI = url;

    if (options.hash) {
      let hashSplit = options.hash.toLowerCase().split(":");
      this.originalHash = {
        algorithm: hashSplit[0],
        data: hashSplit[1],
      };
    }
    this.hash = this.originalHash;
    this.fileHash = null;
    this.existingAddon = options.existingAddon || null;
    this.promptHandler = options.promptHandler || (() => Promise.resolve());
    this.releaseNotesURI = options.releaseNotesURI || null;

    this._startupPromise = null;

    this._installPromise = new Promise(resolve => {
      this._resolveInstallPromise = resolve;
    });
    // Ignore uncaught rejections for this promise, since they're
    // handled by install listeners.
    this._installPromise.catch(() => {});

    this.listeners = [];
    this.icons = options.icons || {};
    this.error = 0;

    this.progress = 0;
    this.maxProgress = -1;

    // Giving each instance of AddonInstall a reference to the logger.
    this.logger = logger;

    this.name = options.name || null;
    this.type = options.type || null;
    this.version = options.version || null;
    this.isUserRequestedUpdate = options.isUserRequestedUpdate;
    this.installTelemetryInfo = null;

    if (options.telemetryInfo) {
      this.installTelemetryInfo = options.telemetryInfo;
    } else if (this.existingAddon) {
      // Inherits the installTelemetryInfo on updates (so that the source of the original
      // installation telemetry data is being preserved across the extension updates).
      this.installTelemetryInfo = this.existingAddon.installTelemetryInfo;
      this.existingAddon._updateInstall = this;
    }

    this.file = null;
    this.ownsTempFile = null;

    this.addon = null;
    this.state = null;

    XPIInstall.installs.add(this);
  }

  /**
   * Called when we are finished with this install and are ready to remove
   * any external references to it.
   */
  _cleanup() {
    XPIInstall.installs.delete(this);
    if (this.addon && this.addon._install) {
      if (this.addon._install === this) {
        this.addon._install = null;
      } else {
        Cu.reportError(new Error("AddonInstall mismatch"));
      }
    }
    if (this.existingAddon && this.existingAddon._updateInstall) {
      if (this.existingAddon._updateInstall === this) {
        this.existingAddon._updateInstall = null;
      } else {
        Cu.reportError(new Error("AddonInstall existingAddon mismatch"));
      }
    }
  }

  /**
   * Starts installation of this add-on from whatever state it is currently at
   * if possible.
   *
   * Note this method is overridden to handle additional state in
   * the subclassses below.
   *
   * @returns {Promise<Addon>}
   * @throws if installation cannot proceed from the current state
   */
  install() {
    switch (this.state) {
      case AddonManager.STATE_DOWNLOADED:
        this.checkPrompt();
        break;
      case AddonManager.STATE_PROMPTS_DONE:
        this.checkForBlockers();
        break;
      case AddonManager.STATE_READY:
        this.startInstall();
        break;
      case AddonManager.STATE_POSTPONED:
        logger.debug(`Postponing install of ${this.addon.id}`);
        break;
      case AddonManager.STATE_DOWNLOADING:
      case AddonManager.STATE_CHECKING_UPDATE:
      case AddonManager.STATE_INSTALLING:
        // Installation is already running
        break;
      default:
        throw new Error("Cannot start installing from this state");
    }
    return this._installPromise;
  }

  continuePostponedInstall() {
    if (this.state !== AddonManager.STATE_POSTPONED) {
      throw new Error("AddonInstall not in postponed state");
    }

    // Force the postponed install to continue.
    logger.info(`${this.addon.id} has resumed a previously postponed upgrade`);
    this.state = AddonManager.STATE_READY;
    this.install();
  }

  /**
   * Called during XPIProvider shutdown so that we can do any necessary
   * pre-shutdown cleanup.
   */
  onShutdown() {
    switch (this.state) {
      case AddonManager.STATE_POSTPONED:
        this.removeTemporaryFile();
        break;
    }
  }

  /**
   * Cancels installation of this add-on.
   *
   * Note this method is overridden to handle additional state in
   * the subclass DownloadAddonInstall.
   *
   * @throws if installation cannot be cancelled from the current state
   */
  cancel() {
    switch (this.state) {
      case AddonManager.STATE_AVAILABLE:
      case AddonManager.STATE_DOWNLOADED:
        logger.debug("Cancelling download of " + this.sourceURI.spec);
        this.state = AddonManager.STATE_CANCELLED;
        this._cleanup();
        this._callInstallListeners("onDownloadCancelled");
        this.removeTemporaryFile();
        break;
      case AddonManager.STATE_POSTPONED:
        logger.debug(`Cancelling postponed install of ${this.addon.id}`);
        this.state = AddonManager.STATE_CANCELLED;
        this._cleanup();
        this._callInstallListeners(
          "onInstallCancelled",
          /* aCancelledByUser */ false
        );
        this.removeTemporaryFile();

        let stagingDir = this.location.installer.getStagingDir();
        let stagedAddon = stagingDir.clone();

        this.unstageInstall(stagedAddon);
        break;
      default:
        throw new Error(
          "Cannot cancel install of " +
            this.sourceURI.spec +
            " from this state (" +
            this.state +
            ")"
        );
    }
  }

  /**
   * Adds an InstallListener for this instance if the listener is not already
   * registered.
   *
   * @param {InstallListener} aListener
   *        The InstallListener to add
   */
  addListener(aListener) {
    if (
      !this.listeners.some(function (i) {
        return i == aListener;
      })
    ) {
      this.listeners.push(aListener);
    }
  }

  /**
   * Removes an InstallListener for this instance if it is registered.
   *
   * @param {InstallListener} aListener
   *        The InstallListener to remove
   */
  removeListener(aListener) {
    this.listeners = this.listeners.filter(function (i) {
      return i != aListener;
    });
  }

  /**
   * Removes the temporary file owned by this AddonInstall if there is one.
   */
  removeTemporaryFile() {
    // Only proceed if this AddonInstall owns its XPI file
    if (!this.ownsTempFile) {
      this.logger.debug(
        `removeTemporaryFile: ${this.sourceURI.spec} does not own temp file`
      );
      return;
    }

    try {
      this.logger.debug(
        `removeTemporaryFile: ${this.sourceURI.spec} removing temp file ` +
          this.file.path
      );
      flushJarCache(this.file);
      this.file.remove(true);
      this.ownsTempFile = false;
    } catch (e) {
      this.logger.warn(
        `Failed to remove temporary file ${this.file.path} for addon ` +
          this.sourceURI.spec,
        e
      );
    }
  }

  _setFileHash(calculatedHash) {
    this.fileHash = {
      algorithm: this.hash ? this.hash.algorithm : DEFAULT_HASH_ALGO,
      data: calculatedHash,
    };

    if (this.hash && calculatedHash != this.hash.data) {
      return false;
    }
    return true;
  }

  /**
   * Updates the addon metadata that has to be propagated across restarts.
   */
  updatePersistedMetadata() {
    this.addon.sourceURI = this.sourceURI.spec;

    if (this.releaseNotesURI) {
      this.addon.releaseNotesURI = this.releaseNotesURI.spec;
    }

    if (this.installTelemetryInfo) {
      this.addon.installTelemetryInfo = this.installTelemetryInfo;
    }
  }

  /**
   * Called after the add-on is a local file and the signature and install
   * manifest can be read.
   *
   * @param {nsIFile} file
   *        The file from which to load the manifest.
   * @returns {Promise<void>}
   */
  async loadManifest(file) {
    let pkg;
    try {
      pkg = Package.get(file);
    } catch (e) {
      return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, e]);
    }

    try {
      try {
        this.addon = await loadManifest(pkg, this.location, this.existingAddon);
        // Set the install.name property to the addon name if it is not set yet,
        // install.name is expected to be set to the addon name and used to
        // fill the addon name in the fluent strings when reporting install
        // errors.
        this.name = this.name ?? this.addon.name;
      } catch (e) {
        return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, e]);
      }

      if (!this.addon.id) {
        let msg = `Cannot find id for addon ${file.path}.`;
        if (Services.prefs.getBoolPref(PREF_XPI_SIGNATURES_DEV_ROOT, false)) {
          msg += ` Preference ${PREF_XPI_SIGNATURES_DEV_ROOT} is set.`;
        }

        return Promise.reject([
          AddonManager.ERROR_CORRUPT_FILE,
          new Error(msg),
        ]);
      }

      if (
        AppConstants.platform == "android" &&
        this.addon.type !== "extension"
      ) {
        return Promise.reject([
          AddonManager.ERROR_UNSUPPORTED_ADDON_TYPE,
          `Unsupported add-on type: ${this.addon.type}`,
        ]);
      }

      if (this.existingAddon) {
        // Check various conditions related to upgrades
        if (this.addon.id != this.existingAddon.id) {
          return Promise.reject([
            AddonManager.ERROR_INCORRECT_ID,
            `Refusing to upgrade addon ${this.existingAddon.id} to different ID ${this.addon.id}`,
          ]);
        }

        if (this.existingAddon.isWebExtension && !this.addon.isWebExtension) {
          // This condition is never met on regular Firefox builds.
          // Remove it along with externalExtensionLoaders (bug 1674799).
          return Promise.reject([
            AddonManager.ERROR_UNEXPECTED_ADDON_TYPE,
            "WebExtensions may not be updated to other extension types",
          ]);
        }
        if (this.existingAddon.type != this.addon.type) {
          return Promise.reject([
            AddonManager.ERROR_UNEXPECTED_ADDON_TYPE,
            `Refusing to change addon type from ${this.existingAddon.type} to ${this.addon.type}`,
          ]);
        }

        if (this.version !== this.addon.version) {
          return Promise.reject([
            AddonManager.ERROR_UNEXPECTED_ADDON_VERSION,
            `Expected addon version ${this.version} instead of ${this.addon.version}`,
          ]);
        }
      }

      if (XPIExports.XPIDatabase.mustSign(this.addon.type)) {
        if (this.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING) {
          // This add-on isn't properly signed by a signature that chains to the
          // trusted root.
          let state = this.addon.signedState;
          this.addon = null;

          if (state == AddonManager.SIGNEDSTATE_MISSING) {
            return Promise.reject([
              AddonManager.ERROR_SIGNEDSTATE_REQUIRED,
              "signature is required but missing",
            ]);
          }

          return Promise.reject([
            AddonManager.ERROR_CORRUPT_FILE,
            "signature verification failed",
          ]);
        }

        if (
          this.addon.adminInstallOnly &&
          !this.addon.wrapper.isInstalledByEnterprisePolicy
        ) {
          return Promise.reject([
            AddonManager.ERROR_ADMIN_INSTALL_ONLY,
            "This addon can only be installed through Enterprise Policies",
          ]);
        }

        // Restrict install for signed extension only signed with weak signature algorithms, unless the
        // restriction is explicitly disabled through prefs or enterprise policies.
        if (
          !XPIInstall.isWeakSignatureInstallAllowed() &&
          this.addon.signedDate &&
          !hasStrongSignature(this.addon)
        ) {
          const addonAllowedByPolicies =
            Services.policies?.getExtensionSettings(
              this.addon.id
            )?.temporarily_allow_weak_signatures;

          const globallyAllowedByPolicies =
            Services.policies?.getExtensionSettings(
              "*"
            )?.temporarily_allow_weak_signatures;

          const allowedByPolicies =
            (globallyAllowedByPolicies &&
              (addonAllowedByPolicies || addonAllowedByPolicies == null)) ||
            addonAllowedByPolicies;

          if (
            !allowedByPolicies &&
            (!this.existingAddon || hasStrongSignature(this.existingAddon))
          ) {
            // Reject if it is a new install or installing over an existing addon including
            // strong cryptographic signatures.
            return Promise.reject([
              AddonManager.ERROR_CORRUPT_FILE,
              "install rejected due to the package not including a strong cryptographic signature",
            ]);
          }

          // Still allow installs using weak signatures to install if either:
          // - it is explicitly allowed through Enterprise Policies Settings
          // - or there is an existing addon with a weak signature.
          logger.warn(
            allowedByPolicies
              ? `Allow weak signature install for ${this.addon.id} XPI due to Enterprise Policies`
              : `Allow weak signature install over existing "${this.existingAddon.id}" XPI`
          );
        }
      }
    } finally {
      pkg.close();
    }

    this.updatePersistedMetadata();

    this.addon._install = this;
    this.name = this.addon.selectedLocale.name;
    this.type = this.addon.type;
    this.version = this.addon.version;

    // Setting the iconURL to something inside the XPI locks the XPI and
    // makes it impossible to delete on Windows.

    // Try to load from the existing cache first
    let repoAddon = await lazy.AddonRepository.getCachedAddonByID(
      this.addon.id
    );

    // It wasn't there so try to re-download it
    if (!repoAddon) {
      try {
        [repoAddon] = await lazy.AddonRepository.cacheAddons([this.addon.id]);
      } catch (err) {
        logger.debug(
          `Error getting metadata for ${this.addon.id}: ${err.message}`
        );
      }
    }

    this.addon._repositoryAddon = repoAddon;
    this.name = this.name || this.addon._repositoryAddon.name;
    this.addon.appDisabled = !XPIExports.XPIDatabase.isUsableAddon(this.addon);
    return undefined;
  }

  getIcon(desiredSize = 64) {
    if (!this.addon.icons || !this.file) {
      return null;
    }

    let { icon } = lazy.IconDetails.getPreferredIcon(
      this.addon.icons,
      null,
      desiredSize
    );
    if (icon.startsWith("chrome://")) {
      return icon;
    }
    return getJarURI(this.file, icon).spec;
  }

  /**
   * This method should be called when the XPI is ready to be installed,
   * i.e., when a download finishes or when a local file has been verified.
   * It should only be called from install() when the install is in
   * STATE_DOWNLOADED (which actually means that the file is available
   * and has been verified).
   */
  checkPrompt() {
    (async () => {
      if (this.promptHandler) {
        let info = {
          existingAddon: this.existingAddon ? this.existingAddon.wrapper : null,
          addon: this.addon.wrapper,
          icon: this.getIcon(),
          // Used in AMTelemetry to detect the install flow related to this prompt.
          install: this.wrapper,
        };

        try {
          await this.promptHandler(info);
        } catch (err) {
          if (this.error < 0) {
            logger.info(`Install of ${this.addon.id} failed ${this.error}`);
            this.state = AddonManager.STATE_INSTALL_FAILED;
            this._cleanup();
            // In some cases onOperationCancelled is called during failures
            // to install/uninstall/enable/disable addons.  We may need to
            // do that here in the future.
            this._callInstallListeners("onInstallFailed");
            this.removeTemporaryFile();
          } else {
            logger.info(`Install of ${this.addon.id} cancelled by user`);
            this.state = AddonManager.STATE_CANCELLED;
            this._cleanup();
            this._callInstallListeners(
              "onInstallCancelled",
              /* aCancelledByUser */ true
            );
          }
          return;
        }
      }
      this.state = AddonManager.STATE_PROMPTS_DONE;
      this.install();
    })();
  }

  /**
   * This method should be called when we have the XPI and any needed
   * permissions prompts have been completed.  If there are any upgrade
   * listeners, they are invoked and the install moves into STATE_POSTPONED.
   * Otherwise, the install moves into STATE_INSTALLING
   */
  checkForBlockers() {
    // If an upgrade listener is registered for this add-on, pass control
    // over the upgrade to the add-on.
    if (AddonManagerPrivate.hasUpgradeListener(this.addon.id)) {
      logger.info(
        `add-on ${this.addon.id} has an upgrade listener, postponing upgrade until restart`
      );
      let resumeFn = () => {
        this.continuePostponedInstall();
      };
      this.postpone(resumeFn);
      return;
    }

    this.state = AddonManager.STATE_READY;
    this.install();
  }

  /**
   * Installs the add-on into the install location.
   */
  async startInstall() {
    this.state = AddonManager.STATE_INSTALLING;
    if (!this._callInstallListeners("onInstallStarted")) {
      this.state = AddonManager.STATE_DOWNLOADED;
      this.removeTemporaryFile();
      this._cleanup();
      this._callInstallListeners(
        "onInstallCancelled",
        /* aCancelledByUser */ false
      );
      return;
    }

    // Reinstall existing user-disabled addon (of the same installed version).
    // If addon is marked to be uninstalled - don't reinstall it.
    if (
      this.existingAddon &&
      this.existingAddon.location === this.location &&
      this.existingAddon.version === this.addon.version &&
      this.existingAddon.userDisabled &&
      !this.existingAddon.pendingUninstall
    ) {
      await XPIExports.XPIDatabase.updateAddonDisabledState(
        this.existingAddon,
        {
          userDisabled: false,
        }
      );
      this.state = AddonManager.STATE_INSTALLED;
      this._callInstallListeners("onInstallEnded", this.existingAddon.wrapper);
      this._cleanup();
      return;
    }

    let isSameLocation = this.existingAddon?.location == this.location;
    let willActivate =
      isSameLocation ||
      !this.existingAddon ||
      this.location.hasPrecedence(this.existingAddon.location);

    logger.debug(
      "Starting install of " + this.addon.id + " from " + this.sourceURI.spec
    );
    AddonManagerPrivate.callAddonListeners(
      "onInstalling",
      this.addon.wrapper,
      false
    );

    let stagedAddon = this.location.installer.getStagingDir();

    try {
      await this.location.installer.requestStagingDir();

      // remove any previously staged files
      await this.unstageInstall(stagedAddon);

      stagedAddon.append(`${this.addon.id}.xpi`);

      await this.stageInstall(false, stagedAddon, isSameLocation);

      this._cleanup();

      let install = async () => {
        // Mark this instance of the addon as inactive if it is being
        // superseded by an addon in a different location.
        if (
          willActivate &&
          this.existingAddon &&
          this.existingAddon.active &&
          !isSameLocation
        ) {
          XPIExports.XPIDatabase.updateAddonActive(this.existingAddon, false);
        }

        // Install the new add-on into its final location
        let file = await this.location.installer.installAddon({
          id: this.addon.id,
          source: stagedAddon,
        });

        // Update the metadata in the database
        this.addon.sourceBundle = file;
        // If this addon will be the active addon, make it visible.
        this.addon.visible = willActivate;

        if (isSameLocation) {
          this.addon = XPIExports.XPIDatabase.updateAddonMetadata(
            this.existingAddon,
            this.addon,
            file.path
          );
          let state = this.location.get(this.addon.id);
          if (state) {
            state.syncWithDB(this.addon, true);
          } else {
            logger.warn(
              "Unexpected missing XPI state for add-on ${id}",
              this.addon
            );
          }
        } else {
          this.addon.active = this.addon.visible && !this.addon.disabled;
          this.addon = XPIExports.XPIDatabase.addToDatabase(
            this.addon,
            file.path
          );
          XPIExports.XPIInternal.XPIStates.addAddon(this.addon);
          this.addon.installDate = this.addon.updateDate;
          XPIExports.XPIDatabase.saveChanges();
        }
        XPIExports.XPIInternal.XPIStates.save();

        AddonManagerPrivate.callAddonListeners(
          "onInstalled",
          this.addon.wrapper
        );

        logger.debug(`Install of ${this.sourceURI.spec} completed.`);
        this.state = AddonManager.STATE_INSTALLED;
        this._callInstallListeners("onInstallEnded", this.addon.wrapper);

        XPIExports.XPIDatabase.recordAddonTelemetry(this.addon);

        // Notify providers that a new theme has been enabled.
        if (this.addon.type === "theme" && this.addon.active) {
          AddonManagerPrivate.notifyAddonChanged(
            this.addon.id,
            this.addon.type
          );
        }

        // Clear the colorways builtins migrated to a non-builtin themes
        // form the list of the retained themes.
        if (
          this.existingAddon?.isBuiltinColorwayTheme &&
          !this.addon.isBuiltin &&
          XPIExports.BuiltInThemesHelpers.isColorwayMigrationEnabled
        ) {
          XPIExports.BuiltInThemesHelpers.unretainMigratedColorwayTheme(
            this.addon.id
          );
        }
      };

      this._startupPromise = (async () => {
        if (!willActivate) {
          await install();
        } else if (this.existingAddon) {
          await XPIExports.XPIInternal.BootstrapScope.get(
            this.existingAddon
          ).update(this.addon, !this.addon.disabled, install);

          if (this.addon.disabled) {
            flushJarCache(this.file);
          }
        } else {
          await install();
          await XPIExports.XPIInternal.BootstrapScope.get(this.addon).install(
            undefined,
            true
          );
        }
      })();

      await this._startupPromise;
    } catch (e) {
      logger.warn(
        `Failed to install ${this.file.path} from ${this.sourceURI.spec} to ${stagedAddon.path}`,
        e
      );

      if (stagedAddon.exists()) {
        recursiveRemove(stagedAddon);
      }
      this.state = AddonManager.STATE_INSTALL_FAILED;
      this.error = AddonManager.ERROR_FILE_ACCESS;
      this._cleanup();
      AddonManagerPrivate.callAddonListeners(
        "onOperationCancelled",
        this.addon.wrapper
      );
      this._callInstallListeners("onInstallFailed");
    } finally {
      this.removeTemporaryFile();
      this.location.installer.releaseStagingDir();
    }
  }

  /**
   * Stages an add-on for install.
   *
   * @param {boolean} restartRequired
   *        If true, the final installation will be deferred until the
   *        next app startup.
   * @param {nsIFile} stagedAddon
   *        The file where the add-on should be staged.
   * @param {boolean} isSameLocation
   *        True if this installation is an upgrade for an existing
   *        add-on in the same location.
   * @throws if the file cannot be staged.
   */
  async stageInstall(restartRequired, stagedAddon, isSameLocation) {
    logger.debug(`Addon ${this.addon.id} will be installed as a packed xpi`);
    stagedAddon.leafName = `${this.addon.id}.xpi`;

    try {
      await IOUtils.copy(this.file.path, stagedAddon.path);

      let calculatedHash = getHashForFile(stagedAddon, this.fileHash.algorithm);
      if (calculatedHash != this.fileHash.data) {
        logger.warn(
          `Staged file hash (${calculatedHash}) did not match initial hash (${this.fileHash.data})`
        );
        throw new Error("Refusing to stage add-on because it has been damaged");
      }
    } catch (e) {
      await IOUtils.remove(stagedAddon.path, { ignoreAbsent: true });
      throw e;
    }

    if (restartRequired) {
      // Point the add-on to its extracted files as the xpi may get deleted
      this.addon.sourceBundle = stagedAddon;

      logger.debug(
        `Staged install of ${this.addon.id} from ${this.sourceURI.spec} ready; waiting for restart.`
      );
      if (isSameLocation) {
        delete this.existingAddon.pendingUpgrade;
        this.existingAddon.pendingUpgrade = this.addon;
      }
    }

    if (this.state === AddonManager.STATE_POSTPONED) {
      // Cache the AddonInternal as it may have updated compatibility info. We
      // do that unconditionally in case the staged install isn't finalized in
      // the same session. That way, on the next app startup, the add-on will
      // be installed.
      this.location.stageAddon(this.addon.id, this.addon.toJSON());
    }
  }

  /**
   * Removes any previously staged upgrade.
   *
   * @param {nsIFile} stagingDir
   *        The staging directory from which to unstage the install.
   */
  async unstageInstall(stagingDir) {
    this.location.unstageAddon(this.addon.id);

    await removeAsync(getFile(this.addon.id, stagingDir));

    await removeAsync(getFile(`${this.addon.id}.xpi`, stagingDir));
  }

  /**
   * Postone a pending update, until restart or until the add-on resumes.
   *
   * @param {function} resumeFn
   *        A function for the add-on to run when resuming.
   * @param {boolean} requiresRestart
   *        Whether this add-on requires restart.
   */
  async postpone(resumeFn, requiresRestart = true) {
    this.state = AddonManager.STATE_POSTPONED;

    let stagingDir = this.location.installer.getStagingDir();

    try {
      await this.location.installer.requestStagingDir();
      await this.unstageInstall(stagingDir);

      let stagedAddon = getFile(`${this.addon.id}.xpi`, stagingDir);

      await this.stageInstall(requiresRestart, stagedAddon, true);
    } catch (e) {
      logger.warn(`Failed to postpone install of ${this.addon.id}`, e);
      this.state = AddonManager.STATE_INSTALL_FAILED;
      this.error = AddonManager.ERROR_FILE_ACCESS;
      this._cleanup();
      this.removeTemporaryFile();
      this.location.installer.releaseStagingDir();
      this._callInstallListeners("onInstallFailed");
      return;
    }

    this._callInstallListeners("onInstallPostponed");

    // upgrade has been staged for restart, provide a way for it to call the
    // resume function.
    let callback = AddonManagerPrivate.getUpgradeListener(this.addon.id);
    if (callback) {
      callback({
        version: this.version,
        install: () => {
          switch (this.state) {
            case AddonManager.STATE_POSTPONED:
              if (resumeFn) {
                resumeFn();
              }
              break;
            default:
              logger.warn(
                `${this.addon.id} cannot resume postponed upgrade from state (${this.state})`
              );
              break;
          }
        },
      });
    }
    // Release the staging directory lock, but since the staging dir is populated
    // it will not be removed until resumed or installed by restart.
    // See also cleanStagingDir()
    this.location.installer.releaseStagingDir();
  }

  _callInstallListeners(event, ...args) {
    switch (event) {
      case "onDownloadCancelled":
      case "onDownloadFailed":
      case "onInstallCancelled":
      case "onInstallFailed":
        let rej = Promise.reject(new Error(`Install failed: ${event}`));
        rej.catch(() => {});
        this._resolveInstallPromise(rej);
        break;
      case "onInstallEnded":
        this._resolveInstallPromise(
          Promise.resolve(this._startupPromise).then(() => args[0])
        );
        break;
    }
    return AddonManagerPrivate.callInstallListeners(
      event,
      this.listeners,
      this.wrapper,
      ...args
    );
  }
}

var LocalAddonInstall = class extends AddonInstall {
  /**
   * Initialises this install to be an install from a local file.
   */
  async init() {
    this.file = this.sourceURI.QueryInterface(Ci.nsIFileURL).file;

    if (!this.file.exists()) {
      logger.warn("XPI file " + this.file.path + " does not exist");
      this.state = AddonManager.STATE_DOWNLOAD_FAILED;
      this.error = AddonManager.ERROR_NETWORK_FAILURE;
      this._cleanup();
      return;
    }

    this.state = AddonManager.STATE_DOWNLOADED;
    this.progress = this.file.fileSize;
    this.maxProgress = this.file.fileSize;

    let algorithm = this.hash ? this.hash.algorithm : DEFAULT_HASH_ALGO;
    if (this.hash) {
      try {
        CryptoHash(this.hash.algorithm);
      } catch (e) {
        logger.warn(
          "Unknown hash algorithm '" +
            this.hash.algorithm +
            "' for addon " +
            this.sourceURI.spec,
          e
        );
        this.state = AddonManager.STATE_DOWNLOAD_FAILED;
        this.error = AddonManager.ERROR_INCORRECT_HASH;
        this._cleanup();
        return;
      }
    }

    if (!this._setFileHash(getHashForFile(this.file, algorithm))) {
      logger.warn(
        `File hash (${this.fileHash.data}) did not match provided hash (${this.hash.data})`
      );
      this.state = AddonManager.STATE_DOWNLOAD_FAILED;
      this.error = AddonManager.ERROR_INCORRECT_HASH;
      this._cleanup();
      return;
    }

    try {
      await this.loadManifest(this.file);
    } catch ([error, message]) {
      logger.warn("Invalid XPI", message);
      this.state = AddonManager.STATE_DOWNLOAD_FAILED;
      this.error = error;
      this._cleanup();
      this._callInstallListeners("onNewInstall");
      flushJarCache(this.file);
      return;
    }

    let addon = await XPIExports.XPIDatabase.getVisibleAddonForID(
      this.addon.id
    );

    this.existingAddon = addon;
    this.addon.propagateDisabledState(this.existingAddon);
    await this.addon.updateBlocklistState();
    this.addon.updateDate = Date.now();
    this.addon.installDate = addon ? addon.installDate : this.addon.updateDate;

    // Report if blocked add-on becomes unblocked through this install.
    if (
      addon?.blocklistState === nsIBlocklistService.STATE_BLOCKED &&
      this.addon.blocklistState === nsIBlocklistService.STATE_NOT_BLOCKED
    ) {
      this.addon.recordAddonBlockChangeTelemetry("addon_install");
    }

    if (this.addon.blocklistState === nsIBlocklistService.STATE_BLOCKED) {
      this.error = AddonManager.ERROR_BLOCKLISTED;
    }

    if (!this.addon.isCompatible) {
      this.state = AddonManager.STATE_CHECKING_UPDATE;

      await new Promise(resolve => {
        new UpdateChecker(
          this.addon,
          {
            onUpdateFinished: (aAddon, aError) => {
              this.state = AddonManager.STATE_DOWNLOADED;
              // If checking for an updated compatibility range fails or the
              // add-on is still incompatible, then set the expected
              // `install.error` to `ERROR_INCOMPATIBLE`.
              if (!this.addon.isCompatible) {
                this.error = AddonManager.ERROR_INCOMPATIBLE;
              }
              if (aError < 0) {
                logger.warn(
                  `UpdateChecker failed to download updates for ${this.addon.id}, error code: ${aError}`
                );
              } else {
                this._callInstallListeners("onNewInstall");
              }
              resolve();
            },
          },
          AddonManager.UPDATE_WHEN_ADDON_INSTALLED
        );
      });
    } else {
      this._callInstallListeners("onNewInstall");
    }
  }

  install() {
    if (this.state == AddonManager.STATE_DOWNLOAD_FAILED) {
      // For a local install, this state means that verification of the
      // file failed (e.g., the hash or signature or manifest contents
      // were invalid).  It doesn't make sense to retry anything in this
      // case but we have callers who don't know if their AddonInstall
      // object is a local file or a download so accommodate them here.
      this._callInstallListeners("onDownloadFailed");
      return this._installPromise;
    }
    return super.install();
  }
};

var DownloadAddonInstall = class extends AddonInstall {
  /**
   * Instantiates a DownloadAddonInstall
   *
   * @param {XPIStateLocation} installLocation
   *        The XPIStateLocation the add-on will be installed into
   * @param {nsIURL} url
   *        The nsIURL to get the add-on from
   * @param {Object} [options = {}]
   *        Additional options for the install
   * @param {string} [options.hash]
   *        An optional hash for the add-on
   * @param {AddonInternal} [options.existingAddon]
   *        The add-on this install will update if known
   * @param {XULElement} [options.browser]
   *        The browser performing the install, used to display
   *        authentication prompts.
   * @param {nsIPrincipal} [options.principal]
   *        The principal to use. If not present, will default to browser.contentPrincipal.
   * @param {string} [options.name]
   *        An optional name for the add-on
   * @param {string} [options.type]
   *        An optional type for the add-on
   * @param {Object} [options.icons]
   *        Optional icons for the add-on
   * @param {string} [options.version]
   *        The expected version for the add-on.
   *        Required for updates, i.e. when existingAddon is set.
   * @param {function(string) : Promise<void>} [options.promptHandler]
   *        A callback to prompt the user before installing.
   * @param {boolean} [options.sendCookies]
   *        Whether cookies should be sent when downloading the add-on.
   */
  constructor(installLocation, url, options = {}) {
    super(installLocation, url, options);

    this.browser = options.browser;
    this.loadingPrincipal =
      options.triggeringPrincipal ||
      (this.browser && this.browser.contentPrincipal) ||
      Services.scriptSecurityManager.getSystemPrincipal();
    this.sendCookies = Boolean(options.sendCookies);

    this.state = AddonManager.STATE_AVAILABLE;

    this.stream = null;
    this.crypto = null;
    this.badCertHandler = null;
    this.restartDownload = false;
    this.downloadStartedAt = null;

    this._callInstallListeners("onNewInstall", this.listeners, this.wrapper);
  }

  install() {
    switch (this.state) {
      case AddonManager.STATE_AVAILABLE:
        this.startDownload();
        break;
      case AddonManager.STATE_DOWNLOAD_FAILED:
      case AddonManager.STATE_INSTALL_FAILED:
      case AddonManager.STATE_CANCELLED:
        this.removeTemporaryFile();
        this.state = AddonManager.STATE_AVAILABLE;
        this.error = 0;
        this.progress = 0;
        this.maxProgress = -1;
        this.hash = this.originalHash;
        this.fileHash = null;
        this.startDownload();
        break;
      default:
        return super.install();
    }
    return this._installPromise;
  }

  cancel() {
    // If we're done downloading the file but still processing it we cannot
    // cancel the installation. We just call the base class which will handle
    // the request by throwing an error.
    if (this.channel && this.state == AddonManager.STATE_DOWNLOADING) {
      logger.debug("Cancelling download of " + this.sourceURI.spec);
      this.channel.cancel(Cr.NS_BINDING_ABORTED);
    } else {
      super.cancel();
    }
  }

  observe() {
    // Network is going offline
    this.cancel();
  }

  /**
   * Starts downloading the add-on's XPI file.
   */
  startDownload() {
    this.downloadStartedAt = Cu.now();

    this.state = AddonManager.STATE_DOWNLOADING;
    if (!this._callInstallListeners("onDownloadStarted")) {
      logger.debug(
        "onDownloadStarted listeners cancelled installation of addon " +
          this.sourceURI.spec
      );
      this.state = AddonManager.STATE_CANCELLED;
      this._cleanup();
      this._callInstallListeners("onDownloadCancelled");
      return;
    }

    // If a listener changed our state then do not proceed with the download
    if (this.state != AddonManager.STATE_DOWNLOADING) {
      return;
    }

    if (this.channel) {
      // A previous download attempt hasn't finished cleaning up yet, signal
      // that it should restart when complete
      logger.debug("Waiting for previous download to complete");
      this.restartDownload = true;
      return;
    }

    this.openChannel();
  }

  openChannel() {
    this.restartDownload = false;

    try {
      this.file = getTemporaryFile();
      this.ownsTempFile = true;
      this.stream = new FileOutputStream(
        this.file,
        lazy.FileUtils.MODE_WRONLY |
          lazy.FileUtils.MODE_CREATE |
          lazy.FileUtils.MODE_TRUNCATE,
        lazy.FileUtils.PERMS_FILE,
        0
      );
    } catch (e) {
      logger.warn(
        "Failed to start download for addon " + this.sourceURI.spec,
        e
      );
      this.state = AddonManager.STATE_DOWNLOAD_FAILED;
      this.error = AddonManager.ERROR_FILE_ACCESS;
      this._cleanup();
      this._callInstallListeners("onDownloadFailed");
      return;
    }

    let listener = Cc[
      "@mozilla.org/network/stream-listener-tee;1"
    ].createInstance(Ci.nsIStreamListenerTee);
    listener.init(this, this.stream);
    try {
      this.badCertHandler = new lazy.CertUtils.BadCertHandler(
        !lazy.AddonSettings.INSTALL_REQUIREBUILTINCERTS
      );

      this.channel = lazy.NetUtil.newChannel({
        uri: this.sourceURI,
        securityFlags:
          Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT,
        contentPolicyType: Ci.nsIContentPolicy.TYPE_SAVEAS_DOWNLOAD,
        loadingPrincipal: this.loadingPrincipal,
      });
      this.channel.notificationCallbacks = this;
      if (this.sendCookies) {
        if (this.channel instanceof Ci.nsIHttpChannelInternal) {
          this.channel.forceAllowThirdPartyCookie = true;
        }
      } else {
        this.channel.loadFlags |= Ci.nsIRequest.LOAD_ANONYMOUS;
      }
      this.channel.asyncOpen(listener);

      Services.obs.addObserver(this, "network:offline-about-to-go-offline");
    } catch (e) {
      logger.warn(
        "Failed to start download for addon " + this.sourceURI.spec,
        e
      );
      this.state = AddonManager.STATE_DOWNLOAD_FAILED;
      this.error = AddonManager.ERROR_NETWORK_FAILURE;
      this._cleanup();
      this._callInstallListeners("onDownloadFailed");
    }
  }

  /*
   * Update the crypto hasher with the new data and call the progress listeners.
   *
   * @see nsIStreamListener
   */
  onDataAvailable(aRequest, aInputstream, aOffset, aCount) {
    this.crypto.updateFromStream(aInputstream, aCount);
    this.progress += aCount;
    if (!this._callInstallListeners("onDownloadProgress")) {
      // TODO cancel the download and make it available again (bug 553024)
    }
  }

  /*
   * Check the redirect response for a hash of the target XPI and verify that
   * we don't end up on an insecure channel.
   *
   * @see nsIChannelEventSink
   */
  asyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, aCallback) {
    if (
      !this.hash &&
      aOldChannel.originalURI.schemeIs("https") &&
      aOldChannel instanceof Ci.nsIHttpChannel
    ) {
      try {
        let hashStr = aOldChannel.getResponseHeader("X-Target-Digest");
        let hashSplit = hashStr.toLowerCase().split(":");
        this.hash = {
          algorithm: hashSplit[0],
          data: hashSplit[1],
        };
      } catch (e) {}
    }

    // Verify that we don't end up on an insecure channel if we haven't got a
    // hash to verify with (see bug 537761 for discussion)
    if (!this.hash) {
      this.badCertHandler.asyncOnChannelRedirect(
        aOldChannel,
        aNewChannel,
        aFlags,
        aCallback
      );
    } else {
      aCallback.onRedirectVerifyCallback(Cr.NS_OK);
    }

    this.channel = aNewChannel;
  }

  /*
   * This is the first chance to get at real headers on the channel.
   *
   * @see nsIStreamListener
   */
  onStartRequest(aRequest) {
    if (this.hash) {
      try {
        this.crypto = CryptoHash(this.hash.algorithm);
      } catch (e) {
        logger.warn(
          "Unknown hash algorithm '" +
            this.hash.algorithm +
            "' for addon " +
            this.sourceURI.spec,
          e
        );
        this.state = AddonManager.STATE_DOWNLOAD_FAILED;
        this.error = AddonManager.ERROR_INCORRECT_HASH;
        this._cleanup();
        this._callInstallListeners("onDownloadFailed");
        aRequest.cancel(Cr.NS_BINDING_ABORTED);
        return;
      }
    } else {
      // We always need something to consume data from the inputstream passed
      // to onDataAvailable so just create a dummy cryptohasher to do that.
      this.crypto = CryptoHash(DEFAULT_HASH_ALGO);
    }

    this.progress = 0;
    if (aRequest instanceof Ci.nsIChannel) {
      try {
        this.maxProgress = aRequest.contentLength;
      } catch (e) {}
      logger.debug(
        "Download started for " +
          this.sourceURI.spec +
          " to file " +
          this.file.path
      );
    }
  }

  /*
   * The download is complete.
   *
   * @see nsIStreamListener
   */
  onStopRequest(aRequest, aStatus) {
    this.stream.close();
    this.channel = null;
    this.badCerthandler = null;
    Services.obs.removeObserver(this, "network:offline-about-to-go-offline");

    let crypto = this.crypto;
    this.crypto = null;

    // If the download was cancelled then update the state and send events
    if (aStatus == Cr.NS_BINDING_ABORTED) {
      if (this.state == AddonManager.STATE_DOWNLOADING) {
        logger.debug("Cancelled download of " + this.sourceURI.spec);
        this.state = AddonManager.STATE_CANCELLED;
        this._cleanup();
        this._callInstallListeners("onDownloadCancelled");
        // If a listener restarted the download then there is no need to
        // remove the temporary file
        if (this.state != AddonManager.STATE_CANCELLED) {
          return;
        }
      }

      this.removeTemporaryFile();
      if (this.restartDownload) {
        this.openChannel();
      }
      return;
    }

    logger.debug("Download of " + this.sourceURI.spec + " completed.");

    if (Components.isSuccessCode(aStatus)) {
      if (
        !(aRequest instanceof Ci.nsIHttpChannel) ||
        aRequest.requestSucceeded
      ) {
        if (!this.hash && aRequest instanceof Ci.nsIChannel) {
          try {
            lazy.CertUtils.checkCert(
              aRequest,
              !lazy.AddonSettings.INSTALL_REQUIREBUILTINCERTS
            );
          } catch (e) {
            this.downloadFailed(AddonManager.ERROR_NETWORK_FAILURE, e);
            return;
          }
        }

        if (!this._setFileHash(getHashStringForCrypto(crypto))) {
          this.downloadFailed(
            AddonManager.ERROR_INCORRECT_HASH,
            `Downloaded file hash (${this.fileHash.data}) did not match provided hash (${this.hash.data})`
          );
          return;
        }

        this.loadManifest(this.file).then(
          () => {
            if (this.addon.isCompatible) {
              this.downloadCompleted();
            } else {
              // TODO Should we send some event here (bug 557716)?
              this.state = AddonManager.STATE_CHECKING_UPDATE;
              new UpdateChecker(
                this.addon,
                {
                  onUpdateFinished: () => this.downloadCompleted(),
                },
                AddonManager.UPDATE_WHEN_ADDON_INSTALLED
              );
            }
          },
          ([error, message]) => {
            this.removeTemporaryFile();
            this.downloadFailed(error, message);
          }
        );
      } else if (aRequest instanceof Ci.nsIHttpChannel) {
        this.downloadFailed(
          AddonManager.ERROR_NETWORK_FAILURE,
          aRequest.responseStatus + " " + aRequest.responseStatusText
        );
      } else {
        this.downloadFailed(AddonManager.ERROR_NETWORK_FAILURE, aStatus);
      }
    } else {
      this.downloadFailed(AddonManager.ERROR_NETWORK_FAILURE, aStatus);
    }
  }

  /**
   * Notify listeners that the download failed.
   *
   * @param {string} aReason
   *        Something to log about the failure
   * @param {integer} aError
   *        The error code to pass to the listeners
   */
  downloadFailed(aReason, aError) {
    logger.warn("Download of " + this.sourceURI.spec + " failed", aError);
    this.state = AddonManager.STATE_DOWNLOAD_FAILED;
    this.error = aReason;
    this._cleanup();
    this._callInstallListeners("onDownloadFailed");

    // If the listener hasn't restarted the download then remove any temporary
    // file
    if (this.state == AddonManager.STATE_DOWNLOAD_FAILED) {
      logger.debug(
        "downloadFailed: removing temp file for " + this.sourceURI.spec
      );
      this.removeTemporaryFile();
    } else {
      logger.debug(
        "downloadFailed: listener changed AddonInstall state for " +
          this.sourceURI.spec +
          " to " +
          this.state
      );
    }
  }

  /**
   * Notify listeners that the download completed.
   */
  async downloadCompleted() {
    let wasUpdate = !!this.existingAddon;
    let aAddon = await XPIExports.XPIDatabase.getVisibleAddonForID(
      this.addon.id
    );
    if (aAddon) {
      this.existingAddon = aAddon;
    }

    this.state = AddonManager.STATE_DOWNLOADED;
    this.addon.updateDate = Date.now();

    if (this.existingAddon) {
      this.addon.installDate = this.existingAddon.installDate;
    } else {
      this.addon.installDate = this.addon.updateDate;
    }
    this.addon.propagateDisabledState(this.existingAddon);
    await this.addon.updateBlocklistState();

    // Report if blocked add-on becomes unblocked through this install/update.
    if (
      aAddon?.blocklistState === nsIBlocklistService.STATE_BLOCKED &&
      this.addon.blocklistState === nsIBlocklistService.STATE_NOT_BLOCKED
    ) {
      this.addon.recordAddonBlockChangeTelemetry(
        wasUpdate ? "addon_update" : "addon_install"
      );
    }

    if (this.addon.blocklistState === nsIBlocklistService.STATE_BLOCKED) {
      this.error = AddonManager.ERROR_BLOCKLISTED;
    } else if (!this.addon.isCompatible) {
      this.error = AddonManager.ERROR_INCOMPATIBLE;
    }

    if (this._callInstallListeners("onDownloadEnded")) {
      // If a listener changed our state then do not proceed with the install
      if (this.state != AddonManager.STATE_DOWNLOADED) {
        return;
      }

      // proceed with the install state machine.
      this.install();
    }
  }

  getInterface(iid) {
    if (iid.equals(Ci.nsIAuthPrompt2)) {
      let win = null;
      if (this.browser) {
        win = this.browser.contentWindow || this.browser.ownerGlobal;
      }

      let factory = Cc["@mozilla.org/prompter;1"].getService(
        Ci.nsIPromptFactory
      );
      let prompt = factory.getPrompt(win, Ci.nsIAuthPrompt2);

      if (this.browser && prompt instanceof Ci.nsILoginManagerAuthPrompter) {
        prompt.browser = this.browser;
      }

      return prompt;
    } else if (iid.equals(Ci.nsIChannelEventSink)) {
      return this;
    }

    return this.badCertHandler.getInterface(iid);
  }
};

/**
 * Creates a new AddonInstall for an update.
 *
 * @param {function} aCallback
 *        The callback to pass the new AddonInstall to
 * @param {AddonInternal} aAddon
 *        The add-on being updated
 * @param {Object} aUpdate
 *        The metadata about the new version from the update manifest
 * @param {boolean} isUserRequested
 *        An optional boolean, true if the install object is related to a user triggered update.
 */
function createUpdate(aCallback, aAddon, aUpdate, isUserRequested) {
  let url = Services.io.newURI(aUpdate.updateURL);

  (async function () {
    let opts = {
      hash: aUpdate.updateHash,
      existingAddon: aAddon,
      name: aAddon.selectedLocale.name,
      type: aAddon.type,
      icons: aAddon.icons,
      version: aUpdate.version,
      isUserRequestedUpdate: isUserRequested,
    };

    try {
      if (aUpdate.updateInfoURL) {
        opts.releaseNotesURI = Services.io.newURI(
          escapeAddonURI(aAddon, aUpdate.updateInfoURL)
        );
      }
    } catch (e) {
      // If the releaseNotesURI cannot be parsed then just ignore it.
    }

    let install;
    if (url instanceof Ci.nsIFileURL) {
      install = new LocalAddonInstall(aAddon.location, url, opts);
      await install.init();
    } else {
      let loc = aAddon.location;
      if (
        aAddon.isBuiltinColorwayTheme &&
        XPIExports.BuiltInThemesHelpers.isColorwayMigrationEnabled
      ) {
        // Builtin colorways theme needs to be updated by installing the version
        // got from AMO into the profile location and not using the location
        // where the builtin addon is currently installed.
        logger.info(
          `Overriding location to APP_PROFILE on builtin colorway theme update for "${aAddon.id}"`
        );
        loc = XPIExports.XPIInternal.XPIStates.getLocation(
          XPIExports.XPIInternal.KEY_APP_PROFILE
        );
      }
      install = new DownloadAddonInstall(loc, url, opts);
    }

    aCallback(install);
  })();
}

// Maps instances of AddonInstall to AddonInstallWrapper
const wrapperMap = new WeakMap();
let installFor = wrapper => wrapperMap.get(wrapper);

// Numeric id included in the install telemetry events to correlate multiple events related
// to the same install or update flow.
let nextInstallId = 0;

/**
 * Creates a wrapper for an AddonInstall that only exposes the public API
 *
 * @param {AddonInstall} aInstall
 *        The AddonInstall to create a wrapper for
 */
function AddonInstallWrapper(aInstall) {
  wrapperMap.set(this, aInstall);
  this.installId = ++nextInstallId;
}

AddonInstallWrapper.prototype = {
  get __AddonInstallInternal__() {
    return AppConstants.DEBUG ? installFor(this) : undefined;
  },

  get error() {
    return installFor(this).error;
  },

  set error(err) {
    installFor(this).error = err;
  },

  get type() {
    return installFor(this).type;
  },

  get iconURL() {
    return installFor(this).icons[32];
  },

  get existingAddon() {
    let install = installFor(this);
    return install.existingAddon ? install.existingAddon.wrapper : null;
  },

  get addon() {
    let install = installFor(this);
    return install.addon ? install.addon.wrapper : null;
  },

  get sourceURI() {
    return installFor(this).sourceURI;
  },

  set promptHandler(handler) {
    installFor(this).promptHandler = handler;
  },

  get promptHandler() {
    return installFor(this).promptHandler;
  },

  get installTelemetryInfo() {
    return installFor(this).installTelemetryInfo;
  },

  get isUserRequestedUpdate() {
    return Boolean(installFor(this).isUserRequestedUpdate);
  },

  get downloadStartedAt() {
    return installFor(this).downloadStartedAt;
  },

  get hashedAddonId() {
    const addon = this.addon;

    if (!addon) {
      return null;
    }

    return computeSha256HashAsString(addon.id);
  },

  install() {
    return installFor(this).install();
  },

  postpone(returnFn, requiresRestart) {
    return installFor(this).postpone(returnFn, requiresRestart);
  },

  cancel() {
    installFor(this).cancel();
  },

  continuePostponedInstall() {
    return installFor(this).continuePostponedInstall();
  },

  addListener(listener) {
    installFor(this).addListener(listener);
  },

  removeListener(listener) {
    installFor(this).removeListener(listener);
  },
};

[
  "name",
  "version",
  "icons",
  "releaseNotesURI",
  "file",
  "state",
  "progress",
  "maxProgress",
].forEach(function (aProp) {
  Object.defineProperty(AddonInstallWrapper.prototype, aProp, {
    get() {
      return installFor(this)[aProp];
    },
    enumerable: true,
  });
});

/**
 * Creates a new update checker.
 *
 * @param {AddonInternal} aAddon
 *        The add-on to check for updates
 * @param {UpdateListener} aListener
 *        An UpdateListener to notify of updates
 * @param {integer} aReason
 *        The reason for the update check
 * @param {string} [aAppVersion]
 *        An optional application version to check for updates for
 * @param {string} [aPlatformVersion]
 *        An optional platform version to check for updates for
 * @throws if the aListener or aReason arguments are not valid
 */
var AddonUpdateChecker;

export var UpdateChecker = function (
  aAddon,
  aListener,
  aReason,
  aAppVersion,
  aPlatformVersion
) {
  if (!aListener || !aReason) {
    throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
  }

  ({ AddonUpdateChecker } = ChromeUtils.importESModule(
    "resource://gre/modules/addons/AddonUpdateChecker.sys.mjs"
  ));

  this.addon = aAddon;
  aAddon._updateCheck = this;
  XPIInstall.doing(this);
  this.listener = aListener;
  this.appVersion = aAppVersion;
  this.platformVersion = aPlatformVersion;
  this.syncCompatibility =
    aReason == AddonManager.UPDATE_WHEN_NEW_APP_INSTALLED;
  this.isUserRequested = aReason == AddonManager.UPDATE_WHEN_USER_REQUESTED;

  let updateURL = aAddon.updateURL;
  if (!updateURL) {
    if (
      aReason == AddonManager.UPDATE_WHEN_PERIODIC_UPDATE &&
      Services.prefs.getPrefType(PREF_EM_UPDATE_BACKGROUND_URL) ==
        Services.prefs.PREF_STRING
    ) {
      updateURL = Services.prefs.getCharPref(PREF_EM_UPDATE_BACKGROUND_URL);
    } else {
      updateURL = Services.prefs.getCharPref(PREF_EM_UPDATE_URL);
    }
  }

  const UPDATE_TYPE_COMPATIBILITY = 32;
  const UPDATE_TYPE_NEWVERSION = 64;

  aReason |= UPDATE_TYPE_COMPATIBILITY;
  if ("onUpdateAvailable" in this.listener) {
    aReason |= UPDATE_TYPE_NEWVERSION;
  }

  let url = escapeAddonURI(aAddon, updateURL, aReason, aAppVersion);
  this._parser = AddonUpdateChecker.checkForUpdates(aAddon.id, url, this);
};

UpdateChecker.prototype = {
  addon: null,
  listener: null,
  appVersion: null,
  platformVersion: null,
  syncCompatibility: null,

  /**
   * Calls a method on the listener passing any number of arguments and
   * consuming any exceptions.
   *
   * @param {string} aMethod
   *        The method to call on the listener
   * @param {any[]} aArgs
   *        Additional arguments to pass to the listener.
   */
  callListener(aMethod, ...aArgs) {
    if (!(aMethod in this.listener)) {
      return;
    }

    try {
      this.listener[aMethod].apply(this.listener, aArgs);
    } catch (e) {
      logger.warn("Exception calling UpdateListener method " + aMethod, e);
    }
  },

  /**
   * Called when AddonUpdateChecker completes the update check
   *
   * @param {object[]} aUpdates
   *        The list of update details for the add-on
   */
  async onUpdateCheckComplete(aUpdates) {
    XPIInstall.done(this.addon._updateCheck);
    this.addon._updateCheck = null;
    let AUC = AddonUpdateChecker;
    let ignoreMaxVersion = false;
    // Ignore strict compatibility for dictionaries by default.
    let ignoreStrictCompat = this.addon.type == "dictionary";
    if (!AddonManager.checkCompatibility) {
      ignoreMaxVersion = true;
      ignoreStrictCompat = true;
    } else if (
      !AddonManager.strictCompatibility &&
      !this.addon.strictCompatibility
    ) {
      ignoreMaxVersion = true;
    }

    // Always apply any compatibility update for the current version
    let compatUpdate = AUC.getCompatibilityUpdate(
      aUpdates,
      this.addon.version,
      this.syncCompatibility,
      null,
      null,
      ignoreMaxVersion,
      ignoreStrictCompat
    );
    // Apply the compatibility update to the database
    if (compatUpdate) {
      this.addon.applyCompatibilityUpdate(compatUpdate, this.syncCompatibility);
    }

    // If the request is for an application or platform version that is
    // different to the current application or platform version then look for a
    // compatibility update for those versions.
    if (
      (this.appVersion &&
        Services.vc.compare(this.appVersion, Services.appinfo.version) != 0) ||
      (this.platformVersion &&
        Services.vc.compare(
          this.platformVersion,
          Services.appinfo.platformVersion
        ) != 0)
    ) {
      compatUpdate = AUC.getCompatibilityUpdate(
        aUpdates,
        this.addon.version,
        false,
        this.appVersion,
        this.platformVersion,
        ignoreMaxVersion,
        ignoreStrictCompat
      );
    }

    if (compatUpdate) {
      this.callListener("onCompatibilityUpdateAvailable", this.addon.wrapper);
    } else {
      this.callListener("onNoCompatibilityUpdateAvailable", this.addon.wrapper);
    }

    function sendUpdateAvailableMessages(aSelf, aInstall) {
      if (aInstall) {
        aSelf.callListener(
          "onUpdateAvailable",
          aSelf.addon.wrapper,
          aInstall.wrapper
        );
      } else {
        aSelf.callListener("onNoUpdateAvailable", aSelf.addon.wrapper);
      }
      aSelf.callListener(
        "onUpdateFinished",
        aSelf.addon.wrapper,
        AddonManager.UPDATE_STATUS_NO_ERROR
      );
    }

    let update = await AUC.getNewestCompatibleUpdate(
      aUpdates,
      this.addon,
      this.appVersion,
      this.platformVersion,
      ignoreMaxVersion,
      ignoreStrictCompat
    );

    if (update && !this.addon.location.locked) {
      for (let currentInstall of XPIInstall.installs) {
        // Skip installs that don't match the available update
        if (
          currentInstall.existingAddon != this.addon ||
          currentInstall.version != update.version
        ) {
          continue;
        }

        // If the existing install has not yet started downloading then send an
        // available update notification. If it is already downloading then
        // don't send any available update notification
        if (currentInstall.state == AddonManager.STATE_AVAILABLE) {
          logger.debug("Found an existing AddonInstall for " + this.addon.id);
          sendUpdateAvailableMessages(this, currentInstall);
        } else {
          sendUpdateAvailableMessages(this, null);
        }
        return;
      }

      createUpdate(
        aInstall => {
          sendUpdateAvailableMessages(this, aInstall);
        },
        this.addon,
        update,
        this.isUserRequested
      );
    } else {
      sendUpdateAvailableMessages(this, null);
    }
  },

  /**
   * Called when AddonUpdateChecker fails the update check
   *
   * @param {any} aError
   *        An error status
   */
  onUpdateCheckError(aError) {
    XPIInstall.done(this.addon._updateCheck);
    this.addon._updateCheck = null;
    this.callListener("onNoCompatibilityUpdateAvailable", this.addon.wrapper);
    this.callListener("onNoUpdateAvailable", this.addon.wrapper);
    this.callListener("onUpdateFinished", this.addon.wrapper, aError);
  },

  /**
   * Called to cancel an in-progress update check
   */
  cancel() {
    let parser = this._parser;
    if (parser) {
      this._parser = null;
      // This will call back to onUpdateCheckError with a CANCELLED error
      parser.cancel();
    }
  },
};

/**
 * Creates a new AddonInstall to install an add-on from a local file.
 *
 * @param {nsIFile} file
 *        The file to install
 * @param {XPIStateLocation} location
 *        The location to install to
 * @param {Object?} [telemetryInfo]
 *        An optional object which provides details about the installation source
 *        included in the addon manager telemetry events.
 * @returns {Promise<AddonInstall>}
 *        A Promise that resolves with the new install object.
 */
function createLocalInstall(file, location, telemetryInfo) {
  if (!location) {
    location = XPIExports.XPIInternal.XPIStates.getLocation(
      XPIExports.XPIInternal.KEY_APP_PROFILE
    );
  }
  let url = Services.io.newFileURI(file);

  try {
    let install = new LocalAddonInstall(location, url, { telemetryInfo });
    return install.init().then(() => install);
  } catch (e) {
    logger.error("Error creating install", e);
    return Promise.resolve(null);
  }
}

/**
 * Uninstall an addon from a location.  This allows removing non-visible
 * addons, such as system addon upgrades, when a higher precedence addon
 * is installed.
 *
 * @param {string} addonID
 *        ID of the addon being removed.
 * @param {XPIStateLocation} location
 *        The location to remove the addon from.
 */
async function uninstallAddonFromLocation(addonID, location) {
  let existing = await XPIExports.XPIDatabase.getAddonInLocation(
    addonID,
    location.name
  );
  if (!existing) {
    return;
  }
  if (existing.active) {
    let a = await AddonManager.getAddonByID(addonID);
    if (a) {
      await a.uninstall();
    }
  } else {
    XPIExports.XPIDatabase.removeAddonMetadata(existing);
    location.removeAddon(addonID);
    XPIExports.XPIInternal.XPIStates.save();
    AddonManagerPrivate.callAddonListeners("onUninstalled", existing);
  }
}

class DirectoryInstaller {
  constructor(location) {
    this.location = location;

    this._stagingDirLock = 0;
    this._stagingDirPromise = null;
  }

  get name() {
    return this.location.name;
  }

  get dir() {
    return this.location.dir;
  }
  set dir(val) {
    this.location.dir = val;
    this.location.path = val.path;
  }

  /**
   * Gets the staging directory to put add-ons that are pending install and
   * uninstall into.
   *
   * @returns {nsIFile}
   */
  getStagingDir() {
    return getFile(XPIExports.XPIInternal.DIR_STAGE, this.dir);
  }

  requestStagingDir() {
    this._stagingDirLock++;

    if (this._stagingDirPromise) {
      return this._stagingDirPromise;
    }

    let stagepath = PathUtils.join(
      this.dir.path,
      XPIExports.XPIInternal.DIR_STAGE
    );
    return (this._stagingDirPromise = IOUtils.makeDirectory(stagepath, {
      createAncestors: true,
      ignoreExisting: true,
    }).catch(e => {
      logger.error("Failed to create staging directory", e);
      throw e;
    }));
  }

  releaseStagingDir() {
    this._stagingDirLock--;

    if (this._stagingDirLock == 0) {
      this._stagingDirPromise = null;
      this.cleanStagingDir();
    }

    return Promise.resolve();
  }

  /**
   * Removes the specified files or directories in the staging directory and
   * then if the staging directory is empty attempts to remove it.
   *
   * @param {string[]} [aLeafNames = []]
   *        An array of file or directory to remove from the directory, the
   *        array may be empty
   */
  cleanStagingDir(aLeafNames = []) {
    let dir = this.getStagingDir();

    // SystemAddonInstaller getStatingDir may return null if there isn't
    // any addon set directory returned by SystemAddonInstaller._loadAddonSet.
    if (!dir) {
      return;
    }

    for (let name of aLeafNames) {
      let file = getFile(name, dir);
      recursiveRemove(file);
    }

    if (this._stagingDirLock > 0) {
      return;
    }

    // eslint-disable-next-line no-unused-vars
    for (let file of XPIExports.XPIInternal.iterDirectory(dir)) {
      return;
    }

    try {
      setFilePermissions(dir, lazy.FileUtils.PERMS_DIRECTORY);
      dir.remove(false);
    } catch (e) {
      logger.warn("Failed to remove staging dir", e);
      // Failing to remove the staging directory is ignorable
    }
  }

  /**
   * Returns a directory that is normally on the same filesystem as the rest of
   * the install location and can be used for temporarily storing files during
   * safe move operations. Calling this method will delete the existing trash
   * directory and its contents.
   *
   * @returns {nsIFile}
   */
  getTrashDir() {
    let trashDir = getFile(XPIExports.XPIInternal.DIR_TRASH, this.dir);
    let trashDirExists = trashDir.exists();
    try {
      if (trashDirExists) {
        recursiveRemove(trashDir);
      }
      trashDirExists = false;
    } catch (e) {
      logger.warn("Failed to remove trash directory", e);
    }
    if (!trashDirExists) {
      trashDir.create(
        Ci.nsIFile.DIRECTORY_TYPE,
        lazy.FileUtils.PERMS_DIRECTORY
      );
    }

    return trashDir;
  }

  /**
   * Installs an add-on into the install location.
   *
   * @param {Object} options
   *        Installation options.
   * @param {string} options.id
   *        The ID of the add-on to install
   * @param {nsIFile} options.source
   *        The source nsIFile to install from
   * @param {string} options.action
   *        What to we do with the given source file:
   *          "move"
   *          Default action, the source files will be moved to the new
   *          location,
   *          "copy"
   *          The source files will be copied,
   *          "proxy"
   *          A "proxy file" is going to refer to the source file path
   * @returns {nsIFile}
   *        An nsIFile indicating where the add-on was installed to
   */
  installAddon({ id, source, action = "move" }) {
    let trashDir = this.getTrashDir();

    let transaction = new SafeInstallOperation();

    let moveOldAddon = aId => {
      let file = getFile(aId, this.dir);
      if (file.exists()) {
        transaction.moveUnder(file, trashDir);
      }

      file = getFile(`${aId}.xpi`, this.dir);
      if (file.exists()) {
        flushJarCache(file);
        transaction.moveUnder(file, trashDir);
      }
    };

    // If any of these operations fails the finally block will clean up the
    // temporary directory
    try {
      moveOldAddon(id);
      if (action == "copy") {
        transaction.copy(source, this.dir);
      } else if (action == "move") {
        flushJarCache(source);
        transaction.moveUnder(source, this.dir);
      }
      // Do nothing for the proxy file as we sideload an addon permanently
    } finally {
      // It isn't ideal if this cleanup fails but it isn't worth rolling back
      // the install because of it.
      try {
        recursiveRemove(trashDir);
      } catch (e) {
        logger.warn(
          `Failed to remove trash directory when installing ${id}`,
          e
        );
      }
    }

    let newFile = this.dir.clone();

    if (action == "proxy") {
      // When permanently installing sideloaded addon, we just put a proxy file
      // referring to the addon sources
      newFile.append(id);

      writeStringToFile(newFile, source.path);
    } else {
      newFile.append(source.leafName);
    }

    try {
      newFile.lastModifiedTime = Date.now();
    } catch (e) {
      logger.warn(`failed to set lastModifiedTime on ${newFile.path}`, e);
    }

    return newFile;
  }

  /**
   * Uninstalls an add-on from this location.
   *
   * @param {string} aId
   *        The ID of the add-on to uninstall
   * @throws if the ID does not match any of the add-ons installed
   */
  uninstallAddon(aId) {
    let file = getFile(aId, this.dir);
    if (!file.exists()) {
      file.leafName += ".xpi";
    }

    if (!file.exists()) {
      logger.warn(
        `Attempted to remove ${aId} from ${this.name} but it was already gone`
      );
      this.location.delete(aId);
      return;
    }

    if (file.leafName != aId) {
      logger.debug(
        `uninstallAddon: flushing jar cache ${file.path} for addon ${aId}`
      );
      flushJarCache(file);
    }

    // In case this is a foreignInstall we do not want to remove the file if
    // the location is locked.
    if (!this.location.locked) {
      let trashDir = this.getTrashDir();
      let transaction = new SafeInstallOperation();

      try {
        transaction.moveUnder(file, trashDir);
      } finally {
        // It isn't ideal if this cleanup fails, but it is probably better than
        // rolling back the uninstall at this point
        try {
          recursiveRemove(trashDir);
        } catch (e) {
          logger.warn(
            `Failed to remove trash directory when uninstalling ${aId}`,
            e
          );
        }
      }
    }

    this.location.removeAddon(aId);
  }
}

class SystemAddonInstaller extends DirectoryInstaller {
  constructor(location) {
    super(location);

    this._baseDir = location._baseDir;
    this._nextDir = null;
  }

  get _addonSet() {
    return this.location._addonSet;
  }
  set _addonSet(val) {
    this.location._addonSet = val;
  }

  /**
   * Saves the current set of system add-ons
   *
   * @param {Object} aAddonSet - object containing schema, directory and set
   *                 of system add-on IDs and versions.
   */
  static _saveAddonSet(aAddonSet) {
    Services.prefs.setStringPref(
      XPIExports.XPIInternal.PREF_SYSTEM_ADDON_SET,
      JSON.stringify(aAddonSet)
    );
  }

  static _loadAddonSet() {
    return XPIExports.XPIInternal.SystemAddonLocation._loadAddonSet();
  }

  /**
   * Gets the staging directory to put add-ons that are pending install and
   * uninstall into.
   *
   * @returns {nsIFile}
   *        Staging directory for system add-on upgrades.
   */
  getStagingDir() {
    this._addonSet = SystemAddonInstaller._loadAddonSet();
    let dir = null;
    if (this._addonSet.directory) {
      this.dir = getFile(this._addonSet.directory, this._baseDir);
      dir = getFile(XPIExports.XPIInternal.DIR_STAGE, this.dir);
    } else {
      logger.info("SystemAddonInstaller directory is missing");
    }

    return dir;
  }

  requestStagingDir() {
    this._addonSet = SystemAddonInstaller._loadAddonSet();
    if (this._addonSet.directory) {
      this.dir = getFile(this._addonSet.directory, this._baseDir);
    }
    return super.requestStagingDir();
  }

  isValidAddon(aAddon) {
    if (aAddon.appDisabled) {
      logger.warn(
        `System add-on ${aAddon.id} isn't compatible with the application.`
      );
      return false;
    }

    return true;
  }

  /**
   * Tests whether the loaded add-on information matches what is expected.
   *
   * @param {Map<string, AddonInternal>} aAddons
   *        The set of add-ons to check.
   * @returns {boolean}
   *        True if all of the given add-ons are valid.
   */
  isValid(aAddons) {
    for (let id of Object.keys(this._addonSet.addons)) {
      if (!aAddons.has(id)) {
        logger.warn(
          `Expected add-on ${id} is missing from the system add-on location.`
        );
        return false;
      }

      let addon = aAddons.get(id);
      if (addon.version != this._addonSet.addons[id].version) {
        logger.warn(
          `Expected system add-on ${id} to be version ${this._addonSet.addons[id].version} but was ${addon.version}.`
        );
        return false;
      }

      if (!this.isValidAddon(addon)) {
        return false;
      }
    }

    return true;
  }

  /**
   * Resets the add-on set so on the next startup the default set will be used.
   */
  async resetAddonSet() {
    logger.info("Removing all system add-on upgrades.");

    // remove everything from the pref first, if uninstall
    // fails then at least they will not be re-activated on
    // next restart.
    let addonSet = this._addonSet;
    this._addonSet = { schema: 1, addons: {} };
    SystemAddonInstaller._saveAddonSet(this._addonSet);

    // If this is running at app startup, the pref being cleared
    // will cause later stages of startup to notice that the
    // old updates are now gone.
    //
    // Updates will only be explicitly uninstalled if they are
    // removed restartlessly, for instance if they are no longer
    // part of the latest update set.
    if (addonSet) {
      for (let addonID of Object.keys(addonSet.addons)) {
        await uninstallAddonFromLocation(addonID, this.location);
      }
    }
  }

  /**
   * Removes any directories not currently in use or pending use after a
   * restart. Any errors that happen here don't really matter as we'll attempt
   * to cleanup again next time.
   */
  async cleanDirectories() {
    try {
      let children = await IOUtils.getChildren(this._baseDir.path, {
        ignoreAbsent: true,
      });
      for (let path of children) {
        // Skip the directory currently in use
        if (this.dir && this.dir.path == path) {
          continue;
        }

        // Skip the next directory
        if (this._nextDir && this._nextDir.path == path) {
          continue;
        }

        await IOUtils.remove(path, {
          ignoreAbsent: true,
          recursive: true,
        });
      }
    } catch (e) {
      logger.error("Failed to clean updated system add-ons directories.", e);
    }
  }

  /**
   * Installs a new set of system add-ons into the location and updates the
   * add-on set in prefs.
   *
   * @param {Array} aAddons - An array of addons to install.
   */
  async installAddonSet(aAddons) {
    // Make sure the base dir exists
    await IOUtils.makeDirectory(this._baseDir.path, { ignoreExisting: true });

    let addonSet = SystemAddonInstaller._loadAddonSet();

    // Remove any add-ons that are no longer part of the set.
    const ids = aAddons.map(a => a.id);
    for (let addonID of Object.keys(addonSet.addons)) {
      if (!ids.includes(addonID)) {
        await uninstallAddonFromLocation(addonID, this.location);
      }
    }

    let newDir = this._baseDir.clone();
    newDir.append("blank");

    while (true) {
      newDir.leafName = Services.uuid.generateUUID().toString();
      try {
        await IOUtils.makeDirectory(newDir.path, { ignoreExisting: false });
        break;
      } catch (e) {
        logger.debug(
          "Could not create new system add-on updates dir, retrying",
          e
        );
      }
    }

    // Record the new upgrade directory.
    let state = { schema: 1, directory: newDir.leafName, addons: {} };
    SystemAddonInstaller._saveAddonSet(state);

    this._nextDir = newDir;

    let installs = [];
    for (let addon of aAddons) {
      let install = await createLocalInstall(
        addon._sourceBundle,
        this.location,
        // Make sure that system addons being installed for the first time through
        // Balrog have telemetryInfo associated with them (on the contrary the ones
        // updated through Balrog but part of the build will already have the same
        // `source`, but we expect no `method` to be set for them).
        {
          source: "system-addon",
          method: "product-updates",
        }
      );
      installs.push(install);
    }

    async function installAddon(install) {
      // Make the new install own its temporary file.
      install.ownsTempFile = true;
      install.install();
    }

    async function postponeAddon(install) {
      install.ownsTempFile = true;
      let resumeFn;
      if (AddonManagerPrivate.hasUpgradeListener(install.addon.id)) {
        logger.info(
          `system add-on ${install.addon.id} has an upgrade listener, postponing upgrade set until restart`
        );
        resumeFn = () => {
          logger.info(
            `${install.addon.id} has resumed a previously postponed addon set`
          );
          install.location.installer.resumeAddonSet(installs);
        };
      }
      await install.postpone(resumeFn);
    }

    let previousState;

    try {
      // All add-ons in position, create the new state and store it in prefs
      state = { schema: 1, directory: newDir.leafName, addons: {} };
      for (let addon of aAddons) {
        state.addons[addon.id] = {
          version: addon.version,
        };
      }

      previousState = SystemAddonInstaller._loadAddonSet();
      SystemAddonInstaller._saveAddonSet(state);

      let blockers = aAddons.filter(addon =>
        AddonManagerPrivate.hasUpgradeListener(addon.id)
      );

      if (blockers.length) {
        await waitForAllPromises(installs.map(postponeAddon));
      } else {
        await waitForAllPromises(installs.map(installAddon));
      }
    } catch (e) {
      // Roll back to previous upgrade set (if present) on restart.
      if (previousState) {
        SystemAddonInstaller._saveAddonSet(previousState);
      }
      // Otherwise, roll back to built-in set on restart.
      // TODO try to do these restartlessly
      await this.resetAddonSet();

      try {
        await IOUtils.remove(newDir.path, { recursive: true });
      } catch (e) {
        logger.warn(
          `Failed to remove failed system add-on directory ${newDir.path}.`,
          e
        );
      }
      throw e;
    }
  }

  /**
   * Resumes upgrade of a previously-delayed add-on set.
   *
   * @param {AddonInstall[]} installs
   *        The set of installs to resume.
   */
  async resumeAddonSet(installs) {
    async function resumeAddon(install) {
      install.state = AddonManager.STATE_DOWNLOADED;
      install.location.installer.releaseStagingDir();
      install.install();
    }

    let blockers = installs.filter(install =>
      AddonManagerPrivate.hasUpgradeListener(install.addon.id)
    );

    if (blockers.length > 1) {
      logger.warn(
        "Attempted to resume system add-on install but upgrade blockers are still present"
      );
    } else {
      await waitForAllPromises(installs.map(resumeAddon));
    }
  }

  /**
   * Returns a directory that is normally on the same filesystem as the rest of
   * the install location and can be used for temporarily storing files during
   * safe move operations. Calling this method will delete the existing trash
   * directory and its contents.
   *
   * @returns {nsIFile}
   */
  getTrashDir() {
    let trashDir = getFile(XPIExports.XPIInternal.DIR_TRASH, this.dir);
    let trashDirExists = trashDir.exists();
    try {
      if (trashDirExists) {
        recursiveRemove(trashDir);
      }
      trashDirExists = false;
    } catch (e) {
      logger.warn("Failed to remove trash directory", e);
    }
    if (!trashDirExists) {
      trashDir.create(
        Ci.nsIFile.DIRECTORY_TYPE,
        lazy.FileUtils.PERMS_DIRECTORY
      );
    }

    return trashDir;
  }

  /**
   * Installs an add-on into the install location.
   *
   * @param {string} id
   *        The ID of the add-on to install
   * @param {nsIFile} source
   *        The source nsIFile to install from
   * @returns {nsIFile}
   *        An nsIFile indicating where the add-on was installed to
   */
  installAddon({ id, source }) {
    let trashDir = this.getTrashDir();
    let transaction = new SafeInstallOperation();

    // If any of these operations fails the finally block will clean up the
    // temporary directory
    try {
      flushJarCache(source);

      transaction.moveUnder(source, this.dir);
    } finally {
      // It isn't ideal if this cleanup fails but it isn't worth rolling back
      // the install because of it.
      try {
        recursiveRemove(trashDir);
      } catch (e) {
        logger.warn(
          `Failed to remove trash directory when installing ${id}`,
          e
        );
      }
    }

    let newFile = getFile(source.leafName, this.dir);

    try {
      newFile.lastModifiedTime = Date.now();
    } catch (e) {
      logger.warn("failed to set lastModifiedTime on " + newFile.path, e);
    }

    return newFile;
  }

  // old system add-on upgrade dirs get automatically removed
  uninstallAddon() {}
}

var AppUpdate = {
  findAddonUpdates(addon, reason, appVersion, platformVersion) {
    return new Promise((resolve, reject) => {
      let update = null;
      addon.findUpdates(
        {
          onUpdateAvailable(addon2, install) {
            update = install;
          },

          onUpdateFinished(addon2, error) {
            if (error == AddonManager.UPDATE_STATUS_NO_ERROR) {
              resolve(update);
            } else {
              reject(error);
            }
          },
        },
        reason,
        appVersion,
        platformVersion || appVersion
      );
    });
  },

  stageInstall(installer) {
    return new Promise((resolve, reject) => {
      let listener = {
        onDownloadEnded: install => {
          install.postpone();
        },
        onInstallFailed: install => {
          install.removeListener(listener);
          reject();
        },
        onInstallEnded: install => {
          // We shouldn't end up here, but if we do, resolve
          // since we've installed.
          install.removeListener(listener);
          resolve();
        },
        onInstallPostponed: install => {
          // At this point the addon is staged for restart.
          install.removeListener(listener);
          resolve();
        },
      };

      installer.addListener(listener);
      installer.install();
    });
  },

  async stageLangpackUpdates(nextVersion, nextPlatformVersion) {
    let updates = [];
    let addons = await AddonManager.getAddonsByTypes(["locale"]);
    for (let addon of addons) {
      updates.push(
        this.findAddonUpdates(
          addon,
          AddonManager.UPDATE_WHEN_NEW_APP_DETECTED,
          nextVersion,
          nextPlatformVersion
        )
          .then(update => update && this.stageInstall(update))
          .catch(e => {
            logger.debug(`addon.findUpdate error: ${e}`);
          })
      );
    }
    return Promise.all(updates);
  },
};

export var XPIInstall = {
  // An array of currently active AddonInstalls
  installs: new Set(),

  createLocalInstall,
  flushJarCache,
  newVersionReason,
  recursiveRemove,
  syncLoadManifest,
  loadManifestFromFile,
  uninstallAddonFromLocation,

  stageLangpacksForAppUpdate(nextVersion, nextPlatformVersion) {
    return AppUpdate.stageLangpackUpdates(nextVersion, nextPlatformVersion);
  },

  // Keep track of in-progress operations that support cancel()
  _inProgress: [],

  doing(aCancellable) {
    this._inProgress.push(aCancellable);
  },

  done(aCancellable) {
    let i = this._inProgress.indexOf(aCancellable);
    if (i != -1) {
      this._inProgress.splice(i, 1);
      return true;
    }
    return false;
  },

  cancelAll() {
    // Cancelling one may alter _inProgress, so don't use a simple iterator
    while (this._inProgress.length) {
      let c = this._inProgress.shift();
      try {
        c.cancel();
      } catch (e) {
        logger.warn("Cancel failed", e);
      }
    }
  },

  /**
   * @param {string} id
   *        The expected ID of the add-on.
   * @param {nsIFile} file
   *        The XPI file to install the add-on from.
   * @param {XPIStateLocation} location
   *        The install location to install the add-on to.
   * @param {string?} [oldAppVersion]
   *        The version of the application last run with this profile or null
   *        if it is a new profile or the version is unknown
   * @returns {AddonInternal}
   *        The installed Addon object, upon success.
   */
  async installDistributionAddon(id, file, location, oldAppVersion) {
    let addon = await loadManifestFromFile(file, location);
    addon.installTelemetryInfo = { source: "distribution" };

    if (addon.id != id) {
      throw new Error(
        `File file ${file.path} contains an add-on with an incorrect ID`
      );
    }

    let state = location.get(id);

    if (state) {
      try {
        let existingAddon = await loadManifestFromFile(state.file, location);

        if (Services.vc.compare(addon.version, existingAddon.version) <= 0) {
          return null;
        }
      } catch (e) {
        // Bad add-on in the profile so just proceed and install over the top
        logger.warn(
          "Profile contains an add-on with a bad or missing install " +
            `manifest at ${state.path}, overwriting`,
          e
        );
      }
    } else if (
      addon.type === "locale" &&
      oldAppVersion &&
      Services.vc.compare(oldAppVersion, "67") < 0
    ) {
      /* Distribution language packs didn't get installed due to the signing
           issues so we need to force them to be reinstalled. */
      Services.prefs.clearUserPref(
        XPIExports.XPIInternal.PREF_BRANCH_INSTALLED_ADDON + id
      );
    } else if (
      Services.prefs.getBoolPref(
        XPIExports.XPIInternal.PREF_BRANCH_INSTALLED_ADDON + id,
        false
      )
    ) {
      return null;
    }

    // Install the add-on
    addon.sourceBundle = location.installer.installAddon({
      id,
      source: file,
      action: "copy",
    });

    XPIExports.XPIInternal.XPIStates.addAddon(addon);
    logger.debug(`Installed distribution add-on ${id}`);

    Services.prefs.setBoolPref(
      XPIExports.XPIInternal.PREF_BRANCH_INSTALLED_ADDON + id,
      true
    );

    return addon;
  },

  /**
   * Completes the install of an add-on which was staged during the last
   * session.
   *
   * @param {string} id
   *        The expected ID of the add-on.
   * @param {object} metadata
   *        The parsed metadata for the staged install.
   * @param {XPIStateLocation} location
   *        The install location to install the add-on to.
   * @returns {AddonInternal}
   *        The installed Addon object, upon success.
   */
  async installStagedAddon(id, metadata, location) {
    let source = getFile(`${id}.xpi`, location.installer.getStagingDir());

    // Check that the directory's name is a valid ID.
    if (!gIDTest.test(id) || !source.exists() || !source.isFile()) {
      throw new Error(`Ignoring invalid staging directory entry: ${id}`);
    }

    let addon = await loadManifestFromFile(source, location);

    if (
      XPIExports.XPIDatabase.mustSign(addon.type) &&
      addon.signedState <= AddonManager.SIGNEDSTATE_MISSING
    ) {
      throw new Error(
        `Refusing to install staged add-on ${id} with signed state ${addon.signedState}`
      );
    }

    // Import saved metadata before checking for compatibility.
    addon.importMetadata(metadata);

    // Ensure a staged addon is compatible with the current running version of
    // Firefox.  If a prior version of the addon is installed, it will remain.
    if (!addon.isCompatible) {
      throw new Error(
        `Add-on ${addon.id} is not compatible with application version.`
      );
    }

    logger.debug(`Processing install of ${id} in ${location.name}`);
    let existingAddon = XPIExports.XPIInternal.XPIStates.findAddon(id);
    // This part of the startup file changes is called from
    // processPendingFileChanges, no addons are started yet.
    // Here we handle copying the xpi into its proper place, later
    // processFileChanges will call update.
    try {
      addon.sourceBundle = location.installer.installAddon({
        id,
        source,
      });
      XPIExports.XPIInternal.XPIStates.addAddon(addon);
    } catch (e) {
      if (existingAddon) {
        // Re-install the old add-on
        XPIExports.XPIInternal.get(existingAddon).install();
      }
      throw e;
    }

    return addon;
  },

  async updateSystemAddons() {
    let systemAddonLocation = XPIExports.XPIInternal.XPIStates.getLocation(
      XPIExports.XPIInternal.KEY_APP_SYSTEM_ADDONS
    );
    if (!systemAddonLocation) {
      return;
    }

    let installer = systemAddonLocation.installer;

    // Don't do anything in safe mode
    if (Services.appinfo.inSafeMode) {
      return;
    }

    // Download the list of system add-ons
    let url = Services.prefs.getStringPref(PREF_SYSTEM_ADDON_UPDATE_URL, null);
    if (!url) {
      await installer.cleanDirectories();
      return;
    }

    url = await lazy.UpdateUtils.formatUpdateURL(url);

    logger.info(`Starting system add-on update check from ${url}.`);
    let res = await lazy.ProductAddonChecker.getProductAddonList(
      url,
      true
    ).catch(e => logger.error(`System addon update list error ${e}`));

    // If there was no list then do nothing.
    if (!res || !res.addons) {
      logger.info("No system add-ons list was returned.");
      await installer.cleanDirectories();
      return;
    }

    let addonList = new Map(
      res.addons.map(spec => [spec.id, { spec, path: null, addon: null }])
    );

    let setMatches = (wanted, existing) => {
      if (wanted.size != existing.size) {
        return false;
      }

      for (let [id, addon] of existing) {
        let wantedInfo = wanted.get(id);

        if (!wantedInfo) {
          return false;
        }
        if (wantedInfo.spec.version != addon.version) {
          return false;
        }
      }

      return true;
    };

    // If this matches the current set in the profile location then do nothing.
    let updatedAddons = addonMap(
      await XPIExports.XPIDatabase.getAddonsInLocation(
        XPIExports.XPIInternal.KEY_APP_SYSTEM_ADDONS
      )
    );
    if (setMatches(addonList, updatedAddons)) {
      logger.info("Retaining existing updated system add-ons.");
      await installer.cleanDirectories();
      return;
    }

    // If this matches the current set in the default location then reset the
    // updated set.
    let defaultAddons = addonMap(
      await XPIExports.XPIDatabase.getAddonsInLocation(
        XPIExports.XPIInternal.KEY_APP_SYSTEM_DEFAULTS
      )
    );
    if (setMatches(addonList, defaultAddons)) {
      logger.info("Resetting system add-ons.");
      await installer.resetAddonSet();
      await installer.cleanDirectories();
      return;
    }

    // Download all the add-ons
    async function downloadAddon(item) {
      try {
        let sourceAddon = updatedAddons.get(item.spec.id);
        if (sourceAddon && sourceAddon.version == item.spec.version) {
          // Copying the file to a temporary location has some benefits. If the
          // file is locked and cannot be read then we'll fall back to
          // downloading a fresh copy. We later mark the install object with
          // ownsTempFile so that we will cleanup later (see installAddonSet).
          try {
            let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile).path;
            let uniquePath = await IOUtils.createUniqueFile(tmpDir, "tmpaddon");
            await IOUtils.copy(sourceAddon._sourceBundle.path, uniquePath);
            // Make sure to update file modification times so this is detected
            // as a new add-on.
            await IOUtils.setModificationTime(uniquePath);
            item.path = uniquePath;
          } catch (e) {
            logger.warn(
              `Failed make temporary copy of ${sourceAddon._sourceBundle.path}.`,
              e
            );
          }
        }
        if (!item.path) {
          item.path = await lazy.ProductAddonChecker.downloadAddon(item.spec);
        }
        item.addon = await loadManifestFromFile(
          nsIFile(item.path),
          systemAddonLocation
        );
      } catch (e) {
        logger.error(`Failed to download system add-on ${item.spec.id}`, e);
      }
    }
    await Promise.all(Array.from(addonList.values()).map(downloadAddon));

    // The download promises all resolve regardless, now check if they all
    // succeeded
    let validateAddon = item => {
      if (item.spec.id != item.addon.id) {
        logger.warn(
          `Downloaded system add-on expected to be ${item.spec.id} but was ${item.addon.id}.`
        );
        return false;
      }

      if (item.spec.version != item.addon.version) {
        logger.warn(
          `Expected system add-on ${item.spec.id} to be version ${item.spec.version} but was ${item.addon.version}.`
        );
        return false;
      }

      if (!installer.isValidAddon(item.addon)) {
        return false;
      }

      return true;
    };

    if (
      !Array.from(addonList.values()).every(
        item => item.path && item.addon && validateAddon(item)
      )
    ) {
      throw new Error(
        "Rejecting updated system add-on set that either could not " +
          "be downloaded or contained unusable add-ons."
      );
    }

    // Install into the install location
    logger.info("Installing new system add-on set");
    await installer.installAddonSet(
      Array.from(addonList.values()).map(a => a.addon)
    );
  },

  /**
   * Called to test whether installing XPI add-ons is enabled.
   *
   * @returns {boolean}
   *        True if installing is enabled.
   */
  isInstallEnabled() {
    // Default to enabled if the preference does not exist
    return Services.prefs.getBoolPref(PREF_XPI_ENABLED, true);
  },

  /**
   * Called to test whether installing XPI add-ons by direct URL requests is
   * whitelisted.
   *
   * @returns {boolean}
   *        True if installing by direct requests is whitelisted
   */
  isDirectRequestWhitelisted() {
    // Default to whitelisted if the preference does not exist.
    return Services.prefs.getBoolPref(PREF_XPI_DIRECT_WHITELISTED, true);
  },

  /**
   * Called to test whether installing XPI add-ons from file referrers is
   * whitelisted.
   *
   * @returns {boolean}
   *       True if installing from file referrers is whitelisted
   */
  isFileRequestWhitelisted() {
    // Default to whitelisted if the preference does not exist.
    return Services.prefs.getBoolPref(PREF_XPI_FILE_WHITELISTED, true);
  },

  isWeakSignatureInstallAllowed() {
    return Services.prefs.getBoolPref(PREF_XPI_WEAK_SIGNATURES_ALLOWED, false);
  },

  getWeakSignatureInstallPrefName() {
    return PREF_XPI_WEAK_SIGNATURES_ALLOWED;
  },

  /**
   * Called to test whether installing XPI add-ons from a URI is allowed.
   *
   * @param {nsIPrincipal}  aInstallingPrincipal
   *        The nsIPrincipal that initiated the install
   * @returns {boolean}
   *        True if installing is allowed
   */
  isInstallAllowed(aInstallingPrincipal) {
    if (!this.isInstallEnabled()) {
      return false;
    }

    let uri = aInstallingPrincipal.URI;

    // Direct requests without a referrer are either whitelisted or blocked.
    if (!uri) {
      return this.isDirectRequestWhitelisted();
    }

    // Local referrers can be whitelisted.
    if (
      this.isFileRequestWhitelisted() &&
      (uri.schemeIs("chrome") || uri.schemeIs("file"))
    ) {
      return true;
    }

    XPIExports.XPIDatabase.importPermissions();

    let permission = Services.perms.testPermissionFromPrincipal(
      aInstallingPrincipal,
      XPIExports.XPIInternal.XPI_PERMISSION
    );
    if (permission == Ci.nsIPermissionManager.DENY_ACTION) {
      return false;
    }

    let requireWhitelist = Services.prefs.getBoolPref(
      PREF_XPI_WHITELIST_REQUIRED,
      true
    );
    if (
      requireWhitelist &&
      permission != Ci.nsIPermissionManager.ALLOW_ACTION
    ) {
      return false;
    }

    let requireSecureOrigin = Services.prefs.getBoolPref(
      PREF_INSTALL_REQUIRESECUREORIGIN,
      true
    );
    let safeSchemes = ["https", "chrome", "file"];
    if (requireSecureOrigin && !safeSchemes.includes(uri.scheme)) {
      return false;
    }

    return true;
  },

  /**
   * Called to get an AddonInstall to download and install an add-on from a URL.
   *
   * @param {nsIURI} aUrl
   *        The URL to be installed
   * @param {object} [aOptions]
   *        Additional options for this install.
   * @param {string?} [aOptions.hash]
   *        A hash for the install
   * @param {string} [aOptions.name]
   *        A name for the install
   * @param {Object} [aOptions.icons]
   *        Icon URLs for the install
   * @param {string} [aOptions.version]
   *        A version for the install
   * @param {XULElement} [aOptions.browser]
   *        The browser performing the install
   * @param {Object} [aOptions.telemetryInfo]
   *        An optional object which provides details about the installation source
   *        included in the addon manager telemetry events.
   * @param {boolean} [aOptions.sendCookies = false]
   *        Whether cookies should be sent when downloading the add-on.
   * @param {string} [aOptions.useSystemLocation = false]
   *        If true installs to the system profile location.
   * @returns {AddonInstall}
   */
  async getInstallForURL(aUrl, aOptions) {
    let locationName = aOptions.useSystemLocation
      ? XPIExports.XPIInternal.KEY_APP_SYSTEM_PROFILE
      : XPIExports.XPIInternal.KEY_APP_PROFILE;
    let location = XPIExports.XPIInternal.XPIStates.getLocation(locationName);
    if (!location) {
      throw Components.Exception(
        "Invalid location name",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    let url = Services.io.newURI(aUrl);

    if (url instanceof Ci.nsIFileURL) {
      let install = new LocalAddonInstall(location, url, aOptions);
      await install.init();
      return install.wrapper;
    }

    let install = new DownloadAddonInstall(location, url, aOptions);
    return install.wrapper;
  },

  /**
   * Called to get an AddonInstall to install an add-on from a local file.
   *
   * @param {nsIFile} aFile
   *        The file to be installed
   * @param {Object?} [aInstallTelemetryInfo]
   *        An optional object which provides details about the installation source
   *        included in the addon manager telemetry events.
   * @param {boolean} [aUseSystemLocation = false]
   *        If true install to the system profile location.
   * @returns {AddonInstall?}
   */
  async getInstallForFile(
    aFile,
    aInstallTelemetryInfo,
    aUseSystemLocation = false
  ) {
    let location = XPIExports.XPIInternal.XPIStates.getLocation(
      aUseSystemLocation
        ? XPIExports.XPIInternal.KEY_APP_SYSTEM_PROFILE
        : XPIExports.XPIInternal.KEY_APP_PROFILE
    );
    let install = await createLocalInstall(
      aFile,
      location,
      aInstallTelemetryInfo
    );
    return install ? install.wrapper : null;
  },

  /**
   * Called to get the current AddonInstalls, optionally limiting to a list of
   * types.
   *
   * @param {Array<string>?} aTypes
   *        An array of types or null to get all types
   * @returns {AddonInstall[]}
   */
  getInstallsByTypes(aTypes) {
    let results = [...this.installs];
    if (aTypes) {
      results = results.filter(install => {
        return aTypes.includes(install.type);
      });
    }

    return results.map(install => install.wrapper);
  },

  /**
   * Temporarily installs add-on from a local XPI file or directory.
   * As this is intended for development, the signature is not checked and
   * the add-on does not persist on application restart.
   *
   * @param {nsIFile} aFile
   *        An nsIFile for the unpacked add-on directory or XPI file.
   *
   * @returns {Promise<Addon>}
   *        A Promise that resolves to an Addon object on success, or rejects
   *        if the add-on is not a valid restartless add-on or if the
   *        same ID is already installed.
   */
  async installTemporaryAddon(aFile) {
    let installLocation = XPIExports.XPIInternal.TemporaryInstallLocation;

    if (XPIExports.XPIInternal.isXPI(aFile.leafName)) {
      flushJarCache(aFile);
    }
    let addon = await loadManifestFromFile(aFile, installLocation);
    addon.rootURI = XPIExports.XPIInternal.getURIForResourceInFile(
      aFile,
      ""
    ).spec;

    await this._activateAddon(addon, { temporarilyInstalled: true });

    logger.debug(`Install of temporary addon in ${aFile.path} completed.`);
    return addon.wrapper;
  },

  /**
   * Installs an add-on from a built-in location
   *  (ie a resource: url referencing assets shipped with the application)
   *
   * @param  {string} base
   *         A string containing the base URL.  Must be a resource: URL.
   * @returns {Promise<Addon>}
   *          A Promise that resolves to an Addon object when the addon is
   *          installed.
   */
  async installBuiltinAddon(base) {
    // We have to get this before the install, as the install will overwrite
    // the pref. We then keep the value for this run, so we can restore
    // the selected theme once it becomes available.
    if (lastSelectedTheme === null) {
      lastSelectedTheme = Services.prefs.getCharPref(PREF_SELECTED_THEME, "");
    }

    let baseURL = Services.io.newURI(base);

    // WebExtensions need to be able to iterate through the contents of
    // an extension (for localization).  It knows how to do this with
    // jar: and file: URLs, so translate the provided base URL to
    // something it can use.
    if (baseURL.scheme !== "resource") {
      throw new Error("Built-in addons must use resource: URLS");
    }

    let pkg = builtinPackage(baseURL);
    let addon = await loadManifest(pkg, XPIExports.XPIInternal.BuiltInLocation);
    addon.rootURI = base;

    // If this is a theme, decide whether to enable it. Themes are
    // disabled by default. However:
    //
    // We always want one theme to be active, falling back to the
    // default theme when the active theme is disabled.
    // During a theme migration, such as a change in the path to the addon, we
    // will need to ensure a correct theme is enabled.
    if (addon.type === "theme") {
      if (
        addon.id === lastSelectedTheme ||
        (!lastSelectedTheme.endsWith("@mozilla.org") &&
          addon.id === lazy.AddonSettings.DEFAULT_THEME_ID &&
          !XPIExports.XPIDatabase.getAddonsByType("theme").some(
            theme => !theme.disabled
          ))
      ) {
        addon.userDisabled = false;
      }
    }
    await this._activateAddon(addon);
    return addon.wrapper;
  },

  /**
   * Activate a newly installed addon.
   * This function handles all the bookkeeping related to a new addon
   * and invokes whatever bootstrap methods are necessary.
   * Note that this function is only used for temporary and built-in
   * installs, it is very similar to AddonInstall::startInstall().
   * It would be great to merge this function with that one some day.
   *
   * @param {AddonInternal} addon  The addon to activate
   * @param {object} [extraParams] Any extra parameters to pass to the
   *                               bootstrap install() method
   *
   * @returns {Promise<void>}
   */
  async _activateAddon(addon, extraParams = {}) {
    if (addon.appDisabled) {
      let message = `Add-on ${addon.id} is not compatible with application version.`;

      let app = addon.matchingTargetApplication;
      if (app) {
        if (app.minVersion) {
          message += ` add-on minVersion: ${app.minVersion}.`;
        }
        if (app.maxVersion) {
          message += ` add-on maxVersion: ${app.maxVersion}.`;
        }
      }
      throw new Error(message);
    }

    let oldAddon = await XPIExports.XPIDatabase.getVisibleAddonForID(addon.id);

    let willActivate =
      !oldAddon ||
      oldAddon.location == addon.location ||
      addon.location.hasPrecedence(oldAddon.location);

    let install = () => {
      addon.visible = willActivate;
      // Themes are generally not enabled by default at install time,
      // unless enabled by the front-end code. If they are meant to be
      // enabled, they will already have been enabled by this point.
      if (addon.type !== "theme" || addon.location.isTemporary) {
        addon.userDisabled = false;
      }
      addon.active = addon.visible && !addon.disabled;

      addon = XPIExports.XPIDatabase.addToDatabase(
        addon,
        addon._sourceBundle ? addon._sourceBundle.path : null
      );

      XPIExports.XPIInternal.XPIStates.addAddon(addon);
      XPIExports.XPIInternal.XPIStates.save();
    };

    AddonManagerPrivate.callAddonListeners("onInstalling", addon.wrapper);

    if (!willActivate) {
      addon.installDate = Date.now();

      install();
    } else if (oldAddon) {
      logger.warn(
        `Addon with ID ${oldAddon.id} already installed, ` +
          "older version will be disabled"
      );

      addon.installDate = oldAddon.installDate;

      await XPIExports.XPIInternal.BootstrapScope.get(oldAddon).update(
        addon,
        true,
        install
      );
    } else {
      addon.installDate = Date.now();

      install();
      let bootstrap = XPIExports.XPIInternal.BootstrapScope.get(addon);
      await bootstrap.install(undefined, true, extraParams);
    }

    AddonManagerPrivate.callInstallListeners(
      "onExternalInstall",
      null,
      addon.wrapper,
      oldAddon ? oldAddon.wrapper : null,
      false
    );
    AddonManagerPrivate.callAddonListeners("onInstalled", addon.wrapper);

    // Notify providers that a new theme has been enabled.
    if (addon.type === "theme" && !addon.userDisabled) {
      AddonManagerPrivate.notifyAddonChanged(addon.id, addon.type, false);
    }
  },

  /**
   * Uninstalls an add-on, immediately if possible or marks it as pending
   * uninstall if not.
   *
   * @param {DBAddonInternal} aAddon
   *        The DBAddonInternal to uninstall
   * @param {boolean} aForcePending
   *        Force this addon into the pending uninstall state (used
   *        e.g. while the add-on manager is open and offering an
   *        "undo" button)
   * @throws if the addon cannot be uninstalled because it is in an install
   *         location that does not allow it
   */
  async uninstallAddon(aAddon, aForcePending) {
    if (!aAddon.inDatabase) {
      throw new Error(
        `Cannot uninstall addon ${aAddon.id} because it is not installed`
      );
    }
    let { location } = aAddon;

    // If the addon is sideloaded into a location that does not allow
    // sideloads, it is a legacy sideload.  We allow those to be uninstalled.
    let isLegacySideload =
      aAddon.foreignInstall &&
      !(location.scope & lazy.AddonSettings.SCOPES_SIDELOAD);

    if (location.locked && !isLegacySideload) {
      throw new Error(
        `Cannot uninstall addon ${aAddon.id} ` +
          `from locked install location ${location.name}`
      );
    }

    if (aForcePending && aAddon.pendingUninstall) {
      throw new Error("Add-on is already marked to be uninstalled");
    }

    if (aAddon._updateCheck) {
      logger.debug(`Cancel in-progress update check for ${aAddon.id}`);
      aAddon._updateCheck.cancel();
    }

    let wasActive = aAddon.active;
    let wasPending = aAddon.pendingUninstall;

    if (aForcePending) {
      // We create an empty directory in the staging directory to indicate
      // that an uninstall is necessary on next startup. Temporary add-ons are
      // automatically uninstalled on shutdown anyway so there is no need to
      // do this for them.
      if (!aAddon.location.isTemporary && aAddon.location.installer) {
        let stage = getFile(
          aAddon.id,
          aAddon.location.installer.getStagingDir()
        );
        if (!stage.exists()) {
          stage.create(
            Ci.nsIFile.DIRECTORY_TYPE,
            lazy.FileUtils.PERMS_DIRECTORY
          );
        }
      }

      XPIExports.XPIDatabase.setAddonProperties(aAddon, {
        pendingUninstall: true,
      });
      Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);
      let xpiState = aAddon.location.get(aAddon.id);
      if (xpiState) {
        xpiState.enabled = false;
        XPIExports.XPIInternal.XPIStates.save();
      } else {
        logger.warn(
          "Can't find XPI state while uninstalling ${id} from ${location}",
          aAddon
        );
      }
    }

    // If the add-on is not visible then there is no need to notify listeners.
    if (!aAddon.visible) {
      return;
    }

    let wrapper = aAddon.wrapper;

    // If the add-on wasn't already pending uninstall then notify listeners.
    if (!wasPending) {
      AddonManagerPrivate.callAddonListeners(
        "onUninstalling",
        wrapper,
        !!aForcePending
      );
    }

    let existingAddon = XPIExports.XPIInternal.XPIStates.findAddon(
      aAddon.id,
      loc => loc != aAddon.location
    );

    let bootstrap = XPIExports.XPIInternal.BootstrapScope.get(aAddon);
    if (!aForcePending) {
      let existing;
      if (existingAddon) {
        existing = await XPIExports.XPIDatabase.getAddonInLocation(
          aAddon.id,
          existingAddon.location.name
        );
      }

      let uninstall = () => {
        XPIExports.XPIInternal.XPIStates.disableAddon(aAddon.id);
        if (aAddon.location.installer) {
          aAddon.location.installer.uninstallAddon(aAddon.id);
        }
        XPIExports.XPIDatabase.removeAddonMetadata(aAddon);
        aAddon.location.removeAddon(aAddon.id);
        AddonManagerPrivate.callAddonListeners("onUninstalled", wrapper);

        if (existing) {
          // Migrate back to the existing addon, unless it was a builtin colorway theme,
          // in that case we also make sure to remove the addon from the builtin location.
          if (
            existing.isBuiltinColorwayTheme &&
            XPIExports.BuiltInThemesHelpers.isColorwayMigrationEnabled
          ) {
            existing.location.removeAddon(existing.id);
          } else {
            XPIExports.XPIDatabase.makeAddonVisible(existing);
            AddonManagerPrivate.callAddonListeners(
              "onInstalling",
              existing.wrapper,
              false
            );

            if (!existing.disabled) {
              XPIExports.XPIDatabase.updateAddonActive(existing, true);
            }
          }
        }
      };

      // Migrate back to the existing addon, unless it was a builtin colorway theme.
      if (
        existing &&
        !(
          existing.isBuiltinColorwayTheme &&
          XPIExports.BuiltInThemesHelpers.isColorwayMigrationEnabled
        )
      ) {
        await bootstrap.update(existing, !existing.disabled, uninstall);

        AddonManagerPrivate.callAddonListeners("onInstalled", existing.wrapper);
      } else {
        aAddon.location.removeAddon(aAddon.id);
        await bootstrap.uninstall();
        uninstall();
      }
    } else if (aAddon.active) {
      XPIExports.XPIInternal.XPIStates.disableAddon(aAddon.id);
      bootstrap.shutdown(
        XPIExports.XPIInternal.BOOTSTRAP_REASONS.ADDON_UNINSTALL
      );
      XPIExports.XPIDatabase.updateAddonActive(aAddon, false);
    }

    // Notify any other providers that a new theme has been enabled
    // (when the active theme is uninstalled, the default theme is enabled).
    if (aAddon.type === "theme" && wasActive) {
      AddonManagerPrivate.notifyAddonChanged(null, aAddon.type);
    }
  },

  /**
   * Cancels the pending uninstall of an add-on.
   *
   * @param {DBAddonInternal} aAddon
   *        The DBAddonInternal to cancel uninstall for
   */
  cancelUninstallAddon(aAddon) {
    if (!aAddon.inDatabase) {
      throw new Error("Can only cancel uninstall for installed addons.");
    }
    if (!aAddon.pendingUninstall) {
      throw new Error("Add-on is not marked to be uninstalled");
    }

    if (!aAddon.location.isTemporary && aAddon.location.installer) {
      aAddon.location.installer.cleanStagingDir([aAddon.id]);
    }

    XPIExports.XPIDatabase.setAddonProperties(aAddon, {
      pendingUninstall: false,
    });

    if (!aAddon.visible) {
      return;
    }

    aAddon.location.get(aAddon.id).syncWithDB(aAddon);
    XPIExports.XPIInternal.XPIStates.save();

    Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, true);

    if (!aAddon.disabled) {
      XPIExports.XPIInternal.BootstrapScope.get(aAddon).startup(
        XPIExports.XPIInternal.BOOTSTRAP_REASONS.ADDON_INSTALL
      );
      XPIExports.XPIDatabase.updateAddonActive(aAddon, true);
    }

    let wrapper = aAddon.wrapper;
    AddonManagerPrivate.callAddonListeners("onOperationCancelled", wrapper);

    // Notify any other providers that this theme is now enabled again.
    if (aAddon.type === "theme" && aAddon.active) {
      AddonManagerPrivate.notifyAddonChanged(aAddon.id, aAddon.type, false);
    }
  },

  DirectoryInstaller,
  SystemAddonInstaller,
};
PK
!<����AzAz"modules/addons/XPIProvider.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file contains most of the logic required to load and run
 * extensions at startup. Anything which is not required immediately at
 * startup should go in XPIInstall.sys.mjs or XPIDatabase.sys.mjs if at all
 * possible, in order to minimize the impact on startup performance.
 */

/**
 * @typedef {number} integer
 */

/* eslint "valid-jsdoc": [2, {requireReturn: false, requireReturnDescription: false, prefer: {return: "returns"}}] */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { XPIExports } from "resource://gre/modules/addons/XPIExports.sys.mjs";
import {
  AddonManager,
  AddonManagerPrivate,
} from "resource://gre/modules/AddonManager.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonSettings: "resource://gre/modules/addons/AddonSettings.sys.mjs",
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  Dictionary: "resource://gre/modules/Extension.sys.mjs",
  Extension: "resource://gre/modules/Extension.sys.mjs",
  ExtensionData: "resource://gre/modules/Extension.sys.mjs",
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
  JSONFile: "resource://gre/modules/JSONFile.sys.mjs",
  Langpack: "resource://gre/modules/Extension.sys.mjs",
  TelemetrySession: "resource://gre/modules/TelemetrySession.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetters(lazy, {
  aomStartup: [
    "@mozilla.org/addons/addon-manager-startup;1",
    "amIAddonManagerStartup",
  ],
  resProto: [
    "@mozilla.org/network/protocol;1?name=resource",
    "nsISubstitutingProtocolHandler",
  ],
  spellCheck: ["@mozilla.org/spellchecker/engine;1", "mozISpellCheckingEngine"],
  timerManager: [
    "@mozilla.org/updates/timer-manager;1",
    "nsIUpdateTimerManager",
  ],
});

const nsIFile = Components.Constructor(
  "@mozilla.org/file/local;1",
  "nsIFile",
  "initWithPath"
);
const FileInputStream = Components.Constructor(
  "@mozilla.org/network/file-input-stream;1",
  "nsIFileInputStream",
  "init"
);

const PREF_DB_SCHEMA = "extensions.databaseSchema";
const PREF_PENDING_OPERATIONS = "extensions.pendingOperations";
const PREF_EM_ENABLED_SCOPES = "extensions.enabledScopes";
const PREF_EM_STARTUP_SCAN_SCOPES = "extensions.startupScanScopes";
// xpinstall.signatures.required only supported in dev builds
const PREF_XPI_SIGNATURES_REQUIRED = "xpinstall.signatures.required";
const PREF_LANGPACK_SIGNATURES = "extensions.langpacks.signatures.required";
const PREF_INSTALL_DISTRO_ADDONS = "extensions.installDistroAddons";
const PREF_BRANCH_INSTALLED_ADDON = "extensions.installedDistroAddon.";
const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet";

const PREF_EM_LAST_APP_BUILD_ID = "extensions.lastAppBuildId";

// Specify a list of valid built-in add-ons to load.
const BUILT_IN_ADDONS_URI = "chrome://browser/content/built_in_addons.json";

const DIR_EXTENSIONS = "extensions";
const DIR_SYSTEM_ADDONS = "features";
const DIR_APP_SYSTEM_PROFILE = "system-extensions";
const DIR_STAGE = "staged";
const DIR_TRASH = "trash";

const FILE_XPI_STATES = "addonStartup.json.lz4";

const KEY_PROFILEDIR = "ProfD";
const KEY_ADDON_APP_DIR = "XREAddonAppDir";
const KEY_APP_DISTRIBUTION = "XREAppDist";
const KEY_APP_FEATURES = "XREAppFeat";

const KEY_APP_PROFILE = "app-profile";
const KEY_APP_SYSTEM_PROFILE = "app-system-profile";
const KEY_APP_SYSTEM_ADDONS = "app-system-addons";
const KEY_APP_SYSTEM_DEFAULTS = "app-system-defaults";
const KEY_APP_BUILTINS = "app-builtin";
const KEY_APP_GLOBAL = "app-global";
const KEY_APP_SYSTEM_LOCAL = "app-system-local";
const KEY_APP_SYSTEM_SHARE = "app-system-share";
const KEY_APP_SYSTEM_USER = "app-system-user";
const KEY_APP_TEMPORARY = "app-temporary";

const TEMPORARY_ADDON_SUFFIX = "@temporary-addon";

const STARTUP_MTIME_SCOPES = [
  KEY_APP_GLOBAL,
  KEY_APP_SYSTEM_LOCAL,
  KEY_APP_SYSTEM_SHARE,
  KEY_APP_SYSTEM_USER,
];

const NOTIFICATION_FLUSH_PERMISSIONS = "flush-pending-permissions";
const XPI_PERMISSION = "install";

const XPI_SIGNATURE_CHECK_PERIOD = 24 * 60 * 60;

const DB_SCHEMA = 36;

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "enabledScopesPref",
  PREF_EM_ENABLED_SCOPES,
  AddonManager.SCOPE_ALL
);

Object.defineProperty(lazy, "enabledScopes", {
  get() {
    // The profile location is always enabled
    return lazy.enabledScopesPref | AddonManager.SCOPE_PROFILE;
  },
});

function encoded(strings, ...values) {
  let result = [];

  for (let [i, string] of strings.entries()) {
    result.push(string);
    if (i < values.length) {
      result.push(encodeURIComponent(values[i]));
    }
  }

  return result.join("");
}

const BOOTSTRAP_REASONS = {
  APP_STARTUP: 1,
  APP_SHUTDOWN: 2,
  ADDON_ENABLE: 3,
  ADDON_DISABLE: 4,
  ADDON_INSTALL: 5,
  ADDON_UNINSTALL: 6,
  ADDON_UPGRADE: 7,
  ADDON_DOWNGRADE: 8,
};

// All addonTypes supported by the XPIProvider. These values can be passed to
// AddonManager.getAddonsByTypes in order to get XPIProvider.getAddonsByTypes
// to return only supported add-ons. Without these, it is possible for
// AddonManager.getAddonsByTypes to return addons from other providers, or even
// add-on types that are no longer supported by XPIProvider.
const ALL_XPI_TYPES = new Set(["dictionary", "extension", "locale", "theme"]);

/**
 * Valid IDs fit this pattern.
 */
var gIDTest =
  /^(\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\}|[a-z0-9-\._]*\@[a-z0-9-\._]+)$/i;

import { Log } from "resource://gre/modules/Log.sys.mjs";

const LOGGER_ID = "addons.xpi";

// Create a new logger for use by all objects in this Addons XPI Provider module
// (Requires AddonManager.sys.mjs)
var logger = Log.repository.getLogger(LOGGER_ID);

/**
 * Spins the event loop until the given promise resolves, and then eiter returns
 * its success value or throws its rejection value.
 *
 * @param {Promise} promise
 *        The promise to await.
 * @returns {any}
 *        The promise's resolution value, if any.
 */
function awaitPromise(promise) {
  let success = undefined;
  let result = null;

  promise.then(
    val => {
      success = true;
      result = val;
    },
    val => {
      success = false;
      result = val;
    }
  );

  Services.tm.spinEventLoopUntil(
    "XPIProvider.sys.mjs:awaitPromise",
    () => success !== undefined
  );

  if (!success) {
    throw result;
  }
  return result;
}

/**
 * Returns a nsIFile instance for the given path, relative to the given
 * base file, if provided.
 *
 * @param {string} path
 *        The (possibly relative) path of the file.
 * @param {nsIFile} [base]
 *        An optional file to use as a base path if `path` is relative.
 * @returns {nsIFile}
 */
function getFile(path, base = null) {
  // First try for an absolute path, as we get in the case of proxy
  // files. Ideally we would try a relative path first, but on Windows,
  // paths which begin with a drive letter are valid as relative paths,
  // and treated as such.
  try {
    return new nsIFile(path);
  } catch (e) {
    // Ignore invalid relative paths. The only other error we should see
    // here is EOM, and either way, any errors that we care about should
    // be re-thrown below.
  }

  // If the path isn't absolute, we must have a base path.
  let file = base.clone();
  file.appendRelativePath(path);
  return file;
}

/**
 * Returns true if the given file, based on its name, should be treated
 * as an XPI. If the file does not have an appropriate extension, it is
 * assumed to be an unpacked add-on.
 *
 * @param {string} filename
 *        The filename to check.
 * @param {boolean} [strict = false]
 *        If true, this file is in a location maintained by the browser, and
 *        must have a strict, lower-case ".xpi" extension.
 * @returns {boolean}
 *        True if the file is an XPI.
 */
function isXPI(filename, strict) {
  if (strict) {
    return filename.endsWith(".xpi");
  }
  let ext = filename.slice(-4).toLowerCase();
  return ext === ".xpi" || ext === ".zip";
}

/**
 * Returns the extension expected ID for a given file in an extension install
 * directory.
 *
 * @param {nsIFile} file
 *        The extension XPI file or unpacked directory.
 * @returns {AddonId?}
 *        The add-on ID, if valid, or null otherwise.
 */
function getExpectedID(file) {
  let { leafName } = file;
  let id = isXPI(leafName, true) ? leafName.slice(0, -4) : leafName;
  if (gIDTest.test(id)) {
    return id;
  }
  return null;
}

/**
 * Evaluates whether an add-on is allowed to run in safe mode.
 *
 * @param {AddonInternal} aAddon
 *        The add-on to check
 * @returns {boolean}
 *        True if the add-on should run in safe mode
 */
function canRunInSafeMode(aAddon) {
  let location = aAddon.location || null;
  if (!location) {
    return false;
  }

  // Even though the updated system add-ons aren't generally run in safe mode we
  // include them here so their uninstall functions get called when switching
  // back to the default set.

  // TODO product should make the call about temporary add-ons running
  // in safe mode. assuming for now that they are.
  return location.isTemporary || location.isSystem || location.isBuiltin;
}

/**
 * Gets an nsIURI for a file within another file, either a directory or an XPI
 * file. If aFile is a directory then this will return a file: URI, if it is an
 * XPI file then it will return a jar: URI.
 *
 * @param {nsIFile} aFile
 *        The file containing the resources, must be either a directory or an
 *        XPI file
 * @param {string} aPath
 *        The path to find the resource at, "/" separated. If aPath is empty
 *        then the uri to the root of the contained files will be returned
 * @returns {nsIURI}
 *        An nsIURI pointing at the resource
 */
function getURIForResourceInFile(aFile, aPath) {
  if (!isXPI(aFile.leafName)) {
    let resource = aFile.clone();
    if (aPath) {
      aPath.split("/").forEach(part => resource.append(part));
    }

    return Services.io.newFileURI(resource);
  }

  return buildJarURI(aFile, aPath);
}

/**
 * Creates a jar: URI for a file inside a ZIP file.
 *
 * @param {nsIFile} aJarfile
 *        The ZIP file as an nsIFile
 * @param {string} aPath
 *        The path inside the ZIP file
 * @returns {nsIURI}
 *        An nsIURI for the file
 */
function buildJarURI(aJarfile, aPath) {
  let uri = Services.io.newFileURI(aJarfile);
  uri = "jar:" + uri.spec + "!/" + aPath;
  return Services.io.newURI(uri);
}

function maybeResolveURI(uri) {
  if (uri.schemeIs("resource")) {
    return Services.io.newURI(lazy.resProto.resolveURI(uri));
  }
  return uri;
}

/**
 * Iterates over the entries in a given directory.
 *
 * Fails silently if the given directory does not exist.
 *
 * @param {nsIFile} aDir
 *        Directory to iterate.
 */
function* iterDirectory(aDir) {
  let dirEnum;
  try {
    dirEnum = aDir.directoryEntries;
    let file;
    while ((file = dirEnum.nextFile)) {
      yield file;
    }
  } catch (e) {
    if (aDir.exists()) {
      logger.warn(`Can't iterate directory ${aDir.path}`, e);
    }
  } finally {
    if (dirEnum) {
      dirEnum.close();
    }
  }
}

/**
 * Migrate data about an addon to match the change made in bug 857456
 * in which "webextension-foo" types were converted to "foo" and the
 * "loader" property was added to distinguish different addon types.
 *
 * @param {Object} addon  The addon info to migrate.
 * @returns {boolean} True if the addon data was converted, false if not.
 */
function migrateAddonLoader(addon) {
  if (addon.hasOwnProperty("loader")) {
    return false;
  }

  switch (addon.type) {
    case "extension":
    case "dictionary":
    case "locale":
    case "theme":
      addon.loader = "bootstrap";
      break;

    case "webextension":
      addon.type = "extension";
      addon.loader = null;
      break;

    case "webextension-dictionary":
      addon.type = "dictionary";
      addon.loader = null;
      break;

    case "webextension-langpack":
      addon.type = "locale";
      addon.loader = null;
      break;

    case "webextension-theme":
      addon.type = "theme";
      addon.loader = null;
      break;

    default:
      logger.warn(`Not converting unknown addon type ${addon.type}`);
  }
  return true;
}

/**
 * The on-disk state of an individual XPI, created from an Object
 * as stored in the addonStartup.json file.
 */
const JSON_FIELDS = Object.freeze([
  "dependencies",
  "enabled",
  "file",
  "loader",
  "lastModifiedTime",
  "path",
  "recommendationState",
  "rootURI",
  "runInSafeMode",
  "signedState",
  "signedDate",
  "startupData",
  "telemetryKey",
  "type",
  "version",
]);

class XPIState {
  constructor(location, id, saved = {}) {
    this.location = location;
    this.id = id;

    // Set default values.
    this.type = "extension";

    for (let prop of JSON_FIELDS) {
      if (prop in saved) {
        this[prop] = saved[prop];
      }
    }

    // Builds prior to be 1512436 did not include the rootURI property.
    // If we're updating from such a build, add that property now.
    if (!("rootURI" in this) && this.file) {
      this.rootURI = getURIForResourceInFile(this.file, "").spec;
    }

    if (!this.telemetryKey) {
      this.telemetryKey = this.getTelemetryKey();
    }

    if (
      saved.currentModifiedTime &&
      saved.currentModifiedTime != this.lastModifiedTime
    ) {
      this.lastModifiedTime = saved.currentModifiedTime;
    } else if (saved.currentModifiedTime === null) {
      this.missing = true;
    }
  }

  // Compatibility shim getters for legacy callers in XPIDatabase.sys.mjs.
  get mtime() {
    return this.lastModifiedTime;
  }
  get active() {
    return this.enabled;
  }

  /**
   * @property {string} path
   *        The full on-disk path of the add-on.
   */
  get path() {
    return this.file && this.file.path;
  }
  set path(path) {
    this.file = path ? getFile(path, this.location.dir) : null;
  }

  /**
   * @property {string} relativePath
   *        The path to the add-on relative to its parent location, or
   *        the full path if its parent location has no on-disk path.
   */
  get relativePath() {
    if (this.location.dir && this.location.dir.contains(this.file)) {
      let path = this.file.getRelativePath(this.location.dir);
      if (AppConstants.platform == "win") {
        path = path.replace(/\//g, "\\");
      }
      return path;
    }
    return this.path;
  }

  /**
   * Returns a JSON-compatible representation of this add-on's state
   * data, to be saved to addonStartup.json.
   *
   * @returns {Object}
   */
  toJSON() {
    let json = {
      dependencies: this.dependencies,
      enabled: this.enabled,
      lastModifiedTime: this.lastModifiedTime,
      loader: this.loader,
      path: this.relativePath,
      recommendationState: this.recommendationState,
      rootURI: this.rootURI,
      runInSafeMode: this.runInSafeMode,
      signedState: this.signedState,
      signedDate: this.signedDate,
      telemetryKey: this.telemetryKey,
      version: this.version,
    };
    if (this.type != "extension") {
      json.type = this.type;
    }
    if (this.startupData) {
      json.startupData = this.startupData;
    }
    return json;
  }

  get isWebExtension() {
    return this.loader == null;
  }

  get isPrivileged() {
    return lazy.ExtensionData.getIsPrivileged({
      signedState: this.signedState,
      builtIn: this.location.isBuiltin,
      temporarilyInstalled: this.location.isTemporary,
    });
  }

  /**
   * Update the last modified time for an add-on on disk.
   *
   * @param {nsIFile} aFile
   *        The location of the add-on.
   * @returns {boolean}
   *       True if the time stamp has changed.
   */
  getModTime(aFile) {
    let mtime = 0;
    try {
      // Clone the file object so we always get the actual mtime, rather
      // than whatever value it may have cached.
      mtime = aFile.clone().lastModifiedTime;
    } catch (e) {
      logger.warn("Can't get modified time of ${path}", aFile, e);
    }

    let changed = mtime != this.lastModifiedTime;
    this.lastModifiedTime = mtime;
    return changed;
  }

  /**
   * Returns a string key by which to identify this add-on in telemetry
   * and crash reports.
   *
   * @returns {string}
   */
  getTelemetryKey() {
    return encoded`${this.id}:${this.version}`;
  }

  get resolvedRootURI() {
    return maybeResolveURI(Services.io.newURI(this.rootURI));
  }

  /**
   * Update the XPIState to match an XPIDatabase entry; if 'enabled' is changed to true,
   * update the last-modified time. This should probably be made async, but for now we
   * don't want to maintain parallel sync and async versions of the scan.
   *
   * Caller is responsible for doing XPIStates.save() if necessary.
   *
   * @param {DBAddonInternal} aDBAddon
   *        The DBAddonInternal for this add-on.
   * @param {boolean} [aUpdated = false]
   *        The add-on was updated, so we must record new modified time.
   */
  syncWithDB(aDBAddon, aUpdated = false) {
    logger.debug("Updating XPIState for " + JSON.stringify(aDBAddon));
    // If the add-on changes from disabled to enabled, we should re-check the modified time.
    // If this is a newly found add-on, it won't have an 'enabled' field but we
    // did a full recursive scan in that case, so we don't need to do it again.
    // We don't use aDBAddon.active here because it's not updated until after restart.
    let mustGetMod = aDBAddon.visible && !aDBAddon.disabled && !this.enabled;

    this.enabled = aDBAddon.visible && !aDBAddon.disabled;

    this.version = aDBAddon.version;
    this.type = aDBAddon.type;
    this.loader = aDBAddon.loader;

    if (aDBAddon.startupData) {
      this.startupData = aDBAddon.startupData;
    }

    this.telemetryKey = this.getTelemetryKey();

    this.dependencies = aDBAddon.dependencies;
    this.runInSafeMode = canRunInSafeMode(aDBAddon);
    this.signedState = aDBAddon.signedState;
    this.signedDate = aDBAddon.signedDate;
    this.file = aDBAddon._sourceBundle;
    this.rootURI = aDBAddon.rootURI;
    this.recommendationState = aDBAddon.recommendationState;

    if ((aUpdated || mustGetMod) && this.file) {
      this.getModTime(this.file);
      if (this.lastModifiedTime != aDBAddon.updateDate) {
        aDBAddon.updateDate = this.lastModifiedTime;
        if (XPIExports.XPIDatabase.initialized) {
          XPIExports.XPIDatabase.saveChanges();
        }
      }
    }
  }
}

/**
 * Manages the state data for add-ons in a given install location.
 *
 * @param {string} name
 *        The name of the install location (e.g., "app-profile").
 * @param {string | nsIFile | null} path
 *        The on-disk path of the install location. May be null for some
 *        locations which do not map to a specific on-disk path.
 * @param {integer} scope
 *        The scope of add-ons installed in this location.
 * @param {object} [saved]
 *        The persisted JSON state data to restore.
 */
class XPIStateLocation extends Map {
  constructor(name, path, scope, saved) {
    super();

    this.name = name;
    this.scope = scope;
    if (path instanceof Ci.nsIFile) {
      this.dir = path;
      this.path = path.path;
    } else {
      this.path = path;
      this.dir = this.path && new nsIFile(this.path);
    }
    this.staged = {};
    this.changed = false;

    if (saved) {
      this.restore(saved);
    }

    this._installer = undefined;
  }

  hasPrecedence(otherLocation) {
    let locations = Array.from(XPIStates.locations());
    return locations.indexOf(this) <= locations.indexOf(otherLocation);
  }

  get installer() {
    if (this._installer === undefined) {
      this._installer = this.makeInstaller();
    }
    return this._installer;
  }

  makeInstaller() {
    return null;
  }

  restore(saved) {
    if (!this.path && saved.path) {
      this.path = saved.path;
      this.dir = new nsIFile(this.path);
    }
    this.staged = saved.staged || {};
    this.changed = saved.changed || false;

    for (let [id, data] of Object.entries(saved.addons || {})) {
      let xpiState = this._addState(id, data);

      // Make a note that this state was restored from saved data. But
      // only if this location hasn't moved since the last startup,
      // since that causes problems for new system add-on bundles.
      if (!this.path || this.path == saved.path) {
        xpiState.wasRestored = true;
      }
    }
  }

  /**
   * Returns a JSON-compatible representation of this location's state
   * data, to be saved to addonStartup.json.
   *
   * @returns {Object}
   */
  toJSON() {
    let json = {
      addons: {},
      staged: this.staged,
    };

    if (this.path) {
      json.path = this.path;
    }

    if (STARTUP_MTIME_SCOPES.includes(this.name)) {
      json.checkStartupModifications = true;
    }

    for (let [id, addon] of this.entries()) {
      json.addons[id] = addon;
    }
    return json;
  }

  get hasStaged() {
    for (let key in this.staged) {
      return true;
    }
    return false;
  }

  _addState(addonId, saved) {
    let xpiState = new XPIState(this, addonId, saved);
    this.set(addonId, xpiState);
    return xpiState;
  }

  /**
   * Adds state data for the given DB add-on to the DB.
   *
   * @param {DBAddon} addon
   *        The DBAddon to add.
   */
  addAddon(addon) {
    logger.debug(
      "XPIStates adding add-on ${id} in ${location}: ${path}",
      addon
    );

    XPIProvider.persistStartupData(addon);

    let xpiState = this._addState(addon.id, { file: addon._sourceBundle });
    xpiState.syncWithDB(addon, true);

    XPIProvider.addTelemetry(addon.id, { location: this.name });
  }

  /**
   * Remove the XPIState for an add-on and save the new state.
   *
   * @param {string} aId
   *        The ID of the add-on.
   */
  removeAddon(aId) {
    if (this.has(aId)) {
      this.delete(aId);
      XPIStates.save();
    }
  }

  /**
   * Adds stub state data for the local file to the DB.
   *
   * @param {string} addonId
   *        The ID of the add-on represented by the given file.
   * @param {nsIFile} file
   *        The local file or directory containing the add-on.
   * @returns {XPIState}
   */
  addFile(addonId, file) {
    let xpiState = this._addState(addonId, {
      enabled: false,
      file: file.clone(),
    });
    xpiState.getModTime(xpiState.file);
    return xpiState;
  }

  /**
   * Adds metadata for a staged install which should be performed after
   * the next restart.
   *
   * @param {string} addonId
   *        The ID of the staged install. The leaf name of the XPI
   *        within the location's staging directory must correspond to
   *        this ID.
   * @param {object} metadata
   *        The JSON metadata of the parsed install, to be used during
   *        the next startup.
   */
  stageAddon(addonId, metadata) {
    this.staged[addonId] = metadata;
    XPIStates.save();
  }

  /**
   * Removes staged install metadata for the given add-on ID.
   *
   * @param {string} addonId
   *        The ID of the staged install.
   */
  unstageAddon(addonId) {
    if (addonId in this.staged) {
      delete this.staged[addonId];
      XPIStates.save();
    }
  }

  *getStagedAddons() {
    for (let [id, metadata] of Object.entries(this.staged)) {
      yield [id, metadata];
    }
  }

  /**
   * Returns true if the given addon was installed in this location by a text
   * file pointing to its real path.
   *
   * @param {string} aId
   *        The ID of the addon
   * @returns {boolean}
   */
  isLinkedAddon(aId) {
    if (!this.dir) {
      return true;
    }
    return this.has(aId) && !this.dir.contains(this.get(aId).file);
  }

  get isTemporary() {
    return false;
  }

  get isSystem() {
    return false;
  }

  get isBuiltin() {
    return false;
  }

  get hidden() {
    return this.isBuiltin;
  }

  // If this property is false, it does not implement readAddons()
  // interface.  This is used for the temporary and built-in locations
  // that do not correspond to a physical location that can be scanned.
  get enumerable() {
    return true;
  }
}

class TemporaryLocation extends XPIStateLocation {
  /**
   * @param {string} name
   *        The string identifier for the install location.
   */
  constructor(name) {
    super(name, null, AddonManager.SCOPE_TEMPORARY);
    this.locked = false;
  }

  makeInstaller() {
    // Installs are a no-op. We only register that add-ons exist, and
    // run them from their current location.
    return {
      installAddon() {},
      uninstallAddon() {},
    };
  }

  toJSON() {
    return {};
  }

  get isTemporary() {
    return true;
  }

  get enumerable() {
    return false;
  }
}

var TemporaryInstallLocation = new TemporaryLocation(KEY_APP_TEMPORARY);

/**
 * A "location" for addons installed from assets packged into the app.
 */
var BuiltInLocation = new (class _BuiltInLocation extends XPIStateLocation {
  constructor() {
    super(KEY_APP_BUILTINS, null, AddonManager.SCOPE_APPLICATION);
    this.locked = false;
  }

  // The installer object is responsible for moving files around on disk
  // when (un)installing an addon.  Since this location handles only addons
  // that are embedded within the browser, these are no-ops.
  makeInstaller() {
    return {
      installAddon() {},
      uninstallAddon() {},
    };
  }

  get hidden() {
    return false;
  }

  get isBuiltin() {
    return true;
  }

  get enumerable() {
    return false;
  }

  // Builtin addons are never linked, return false
  // here for correct behavior elsewhere.
  isLinkedAddon(/* aId */) {
    return false;
  }
})();

/**
 * An object which identifies a directory install location for add-ons. The
 * location consists of a directory which contains the add-ons installed in the
 * location.
 *
 */
class DirectoryLocation extends XPIStateLocation {
  /**
   * Each add-on installed in the location is either a directory containing the
   * add-on's files or a text file containing an absolute path to the directory
   * containing the add-ons files. The directory or text file must have the same
   * name as the add-on's ID.
   *
   * @param {string} name
   *        The string identifier for the install location.
   * @param {nsIFile} dir
   *        The directory for the install location.
   * @param {integer} scope
   *        The scope of add-ons installed in this location.
   * @param {boolean} [locked = true]
   *        If false, the location accepts new add-on installs.
   * @param {boolean} [system = false]
   *        If true, the location is a system addon location.
   */
  constructor(name, dir, scope, locked = true, system = false) {
    super(name, dir, scope);
    this.locked = locked;
    this._isSystem = system;
  }

  makeInstaller() {
    if (this.locked) {
      return null;
    }
    return new XPIExports.XPIInstall.DirectoryInstaller(this);
  }

  /**
   * Reads a single-line file containing the path to a directory, and
   * returns an nsIFile pointing to that directory, if successful.
   *
   * @param {nsIFile} aFile
   *        The file containing the directory path
   * @returns {nsIFile?}
   *        An nsIFile object representing the linked directory, or null
   *        on error.
   */
  _readLinkFile(aFile) {
    let linkedDirectory;
    if (aFile.isSymlink()) {
      linkedDirectory = aFile.clone();
      try {
        linkedDirectory.normalize();
      } catch (e) {
        logger.warn(
          `Symbolic link ${aFile.path} points to a path ` +
            `which does not exist`
        );
        return null;
      }
    } else {
      let fis = new FileInputStream(aFile, -1, -1, false);
      let line = {};
      fis.QueryInterface(Ci.nsILineInputStream).readLine(line);
      fis.close();

      if (line.value) {
        linkedDirectory = Cc["@mozilla.org/file/local;1"].createInstance(
          Ci.nsIFile
        );
        try {
          linkedDirectory.initWithPath(line.value);
        } catch (e) {
          linkedDirectory.setRelativeDescriptor(aFile.parent, line.value);
        }
      }
    }

    if (linkedDirectory) {
      if (!linkedDirectory.exists()) {
        logger.warn(
          `File pointer ${aFile.path} points to ${linkedDirectory.path} ` +
            "which does not exist"
        );
        return null;
      }

      if (!linkedDirectory.isDirectory()) {
        logger.warn(
          `File pointer ${aFile.path} points to ${linkedDirectory.path} ` +
            "which is not a directory"
        );
        return null;
      }

      return linkedDirectory;
    }

    logger.warn(`File pointer ${aFile.path} does not contain a path`);
    return null;
  }

  /**
   * Finds all the add-ons installed in this location.
   *
   * @returns {Map<AddonID, nsIFile>}
   *        A map of add-ons present in this location.
   */
  readAddons() {
    let addons = new Map();

    if (!this.dir) {
      return addons;
    }

    // Use a snapshot of the directory contents to avoid possible issues with
    // iterating over a directory while removing files from it (the YAFFS2
    // embedded filesystem has this issue, see bug 772238).
    for (let entry of Array.from(iterDirectory(this.dir))) {
      let id = getExpectedID(entry);
      if (!id) {
        if (![DIR_STAGE, DIR_TRASH].includes(entry.leafName)) {
          logger.debug(
            "Ignoring file: name is not a valid add-on ID: ${}",
            entry.path
          );
        }
        continue;
      }

      if (id == entry.leafName && (entry.isFile() || entry.isSymlink())) {
        let newEntry = this._readLinkFile(entry);
        if (!newEntry) {
          logger.debug(`Deleting stale pointer file ${entry.path}`);
          try {
            entry.remove(true);
          } catch (e) {
            logger.warn(`Failed to remove stale pointer file ${entry.path}`, e);
            // Failing to remove the stale pointer file is ignorable
          }
          continue;
        }

        entry = newEntry;
      }

      addons.set(id, entry);
    }
    return addons;
  }

  get isSystem() {
    return this._isSystem;
  }
}

/**
 * An object which identifies a built-in install location for add-ons, such
 * as default system add-ons.
 *
 * This location should point either to a XPI, or a directory in a local build.
 */
class SystemAddonDefaults extends DirectoryLocation {
  /**
   * Read the manifest of allowed add-ons and build a mapping between ID and URI
   * for each.
   *
   * @returns {Map<AddonID, nsIFile>}
   *        A map of add-ons present in this location.
   */
  readAddons() {
    let addons = new Map();

    let manifest = XPIProvider.builtInAddons;

    if (!("system" in manifest)) {
      logger.debug("No list of valid system add-ons found.");
      return addons;
    }

    for (let id of manifest.system) {
      let file = this.dir.clone();
      file.append(`${id}.xpi`);

      // Only attempt to load unpacked directory if unofficial build.
      if (!AppConstants.MOZILLA_OFFICIAL && !file.exists()) {
        file = this.dir.clone();
        file.append(`${id}`);
      }

      addons.set(id, file);
    }

    return addons;
  }

  get isSystem() {
    return true;
  }

  get isBuiltin() {
    return true;
  }
}

/**
 * An object which identifies a directory install location for system add-ons
 * updates.
 */
class SystemAddonLocation extends DirectoryLocation {
  /**
   * The location consists of a directory which contains the add-ons installed.
   *
   * @param {string} name
   *        The string identifier for the install location.
   * @param {nsIFile} dir
   *        The directory for the install location.
   * @param {integer} scope
   *        The scope of add-ons installed in this location.
   * @param {boolean} resetSet
   *        True to throw away the current add-on set
   */
  constructor(name, dir, scope, resetSet) {
    let addonSet = SystemAddonLocation._loadAddonSet();
    let directory = null;

    // The system add-on update directory is stored in a pref.
    // Therefore, this is looked up before calling the
    // constructor on the superclass.
    if (addonSet.directory) {
      directory = getFile(addonSet.directory, dir);
      logger.info(`SystemAddonLocation scanning directory ${directory.path}`);
    } else {
      logger.info("SystemAddonLocation directory is missing");
    }

    super(name, directory, scope, false);

    this._addonSet = addonSet;
    this._baseDir = dir;

    if (resetSet) {
      this.installer.resetAddonSet();
    }
  }

  makeInstaller() {
    if (this.locked) {
      return null;
    }
    return new XPIExports.XPIInstall.SystemAddonInstaller(this);
  }

  /**
   * Reads the current set of system add-ons
   *
   * @returns {Object}
   */
  static _loadAddonSet() {
    try {
      let setStr = Services.prefs.getStringPref(PREF_SYSTEM_ADDON_SET, null);
      if (setStr) {
        let addonSet = JSON.parse(setStr);
        if (typeof addonSet == "object" && addonSet.schema == 1) {
          return addonSet;
        }
      }
    } catch (e) {
      logger.error("Malformed system add-on set, resetting.");
    }

    return { schema: 1, addons: {} };
  }

  readAddons() {
    // Updated system add-ons are ignored in safe mode
    if (Services.appinfo.inSafeMode) {
      return new Map();
    }

    let addons = super.readAddons();

    // Strip out any unexpected add-ons from the list
    for (let id of addons.keys()) {
      if (!(id in this._addonSet.addons)) {
        addons.delete(id);
      }
    }

    return addons;
  }

  /**
   * Tests whether updated system add-ons are expected.
   *
   * @returns {boolean}
   */
  isActive() {
    return this.dir != null;
  }

  get isSystem() {
    return true;
  }

  get isBuiltin() {
    return true;
  }
}

/**
 * An object that identifies a registry install location for add-ons. The location
 * consists of a registry key which contains string values mapping ID to the
 * path where an add-on is installed
 *
 */
class WinRegLocation extends XPIStateLocation {
  /**
   * @param {string} name
   *        The string identifier for the install location.
   * @param {integer} rootKey
   *        The root key (one of the ROOT_KEY_ values from nsIWindowsRegKey).
   * @param {integer} scope
   *        The scope of add-ons installed in this location.
   */
  constructor(name, rootKey, scope) {
    super(name, undefined, scope);

    this.locked = true;
    this._rootKey = rootKey;
  }

  /**
   * Retrieves the path of this Application's data key in the registry.
   */
  get _appKeyPath() {
    let appVendor = Services.appinfo.vendor;
    let appName = Services.appinfo.name;

    // XXX Thunderbird doesn't specify a vendor string
    if (appVendor == "" && AppConstants.MOZ_APP_NAME == "thunderbird") {
      appVendor = "Mozilla";
    }

    return `SOFTWARE\\${appVendor}\\${appName}`;
  }

  /**
   * Read the registry and build a mapping between ID and path for each
   * installed add-on.
   *
   * @returns {Map<AddonID, nsIFile>}
   *        A map of add-ons in this location.
   */
  readAddons() {
    let addons = new Map();

    let path = `${this._appKeyPath}\\Extensions`;
    let key = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
      Ci.nsIWindowsRegKey
    );

    // Reading the registry may throw an exception, and that's ok.  In error
    // cases, we just leave ourselves in the empty state.
    try {
      key.open(this._rootKey, path, Ci.nsIWindowsRegKey.ACCESS_READ);
    } catch (e) {
      return addons;
    }

    try {
      let count = key.valueCount;
      for (let i = 0; i < count; ++i) {
        let id = key.getValueName(i);
        let file = new nsIFile(key.readStringValue(id));
        if (!file.exists()) {
          logger.warn(`Ignoring missing add-on in ${file.path}`);
          continue;
        }

        addons.set(id, file);
      }
    } finally {
      key.close();
    }

    return addons;
  }
}

/**
 * Keeps track of the state of XPI add-ons on the file system.
 */
var XPIStates = {
  // Map(location-name -> XPIStateLocation)
  db: new Map(),

  _jsonFile: null,

  /**
   * @property {Map<string, XPIState>} sideLoadedAddons
   *        A map of new add-ons detected during install location
   *        directory scans. Keys are add-on IDs, values are XPIState
   *        objects corresponding to those add-ons.
   */
  sideLoadedAddons: new Map(),

  get size() {
    let count = 0;
    for (let location of this.locations()) {
      count += location.size;
    }
    return count;
  },

  /**
   * Load extension state data from addonStartup.json.
   *
   * @returns {Object}
   */
  loadExtensionState() {
    let state;
    try {
      state = lazy.aomStartup.readStartupData();
    } catch (e) {
      logger.warn("Error parsing extensions state: ${error}", { error: e });
    }

    // When upgrading from a build prior to bug 857456, convert startup
    // metadata.
    let done = false;
    for (let location of Object.values(state || {})) {
      for (let data of Object.values(location.addons || {})) {
        if (!migrateAddonLoader(data)) {
          done = true;
          break;
        }
      }
      if (done) {
        break;
      }
    }

    logger.debug("Loaded add-on state: ${}", state);
    return state || {};
  },

  /**
   * Walk through all install locations, highest priority first,
   * comparing the on-disk state of extensions to what is stored in prefs.
   *
   * @param {boolean} [ignoreSideloads = true]
   *        If true, ignore changes in scopes where we don't accept
   *        side-loads.
   *
   * @returns {boolean}
   *        True if anything has changed.
   */
  scanForChanges(ignoreSideloads = true) {
    let oldState = this.initialStateData || this.loadExtensionState();
    // We're called twice, do not restore the second time as new data
    // may have been inserted since the first call.
    let shouldRestoreLocationData = !this.initialStateData;
    this.initialStateData = oldState;

    let changed = false;
    let oldLocations = new Set(Object.keys(oldState));

    let startupScanScopes;
    if (
      Services.appinfo.appBuildID ==
      Services.prefs.getCharPref(PREF_EM_LAST_APP_BUILD_ID, "")
    ) {
      startupScanScopes = Services.prefs.getIntPref(
        PREF_EM_STARTUP_SCAN_SCOPES,
        0
      );
    } else {
      // If the build id has changed, we need to do a full scan on first startup.
      Services.prefs.setCharPref(
        PREF_EM_LAST_APP_BUILD_ID,
        Services.appinfo.appBuildID
      );
      startupScanScopes = AddonManager.SCOPE_ALL;
    }

    for (let loc of XPIStates.locations()) {
      oldLocations.delete(loc.name);

      if (shouldRestoreLocationData && oldState[loc.name]) {
        loc.restore(oldState[loc.name]);
      }
      changed = changed || loc.changed;

      // Don't bother checking scopes where we don't accept side-loads.
      if (ignoreSideloads && !(loc.scope & startupScanScopes)) {
        continue;
      }

      if (!loc.enumerable) {
        continue;
      }

      // Don't bother scanning scopes where we don't have addons installed if they
      // do not allow sideloading new addons.  Once we have an addon in one of those
      // locations, we need to check the location for changes (updates/deletions).
      if (!loc.size && !(loc.scope & lazy.AddonSettings.SCOPES_SIDELOAD)) {
        continue;
      }

      let knownIds = new Set(loc.keys());
      for (let [id, file] of loc.readAddons()) {
        knownIds.delete(id);

        let xpiState = loc.get(id);
        if (!xpiState) {
          // If the location is not supported for sideloading, skip new
          // addons.  We handle this here so changes for existing sideloads
          // will function.
          if (
            !loc.isSystem &&
            !(loc.scope & lazy.AddonSettings.SCOPES_SIDELOAD)
          ) {
            continue;
          }
          logger.debug("New add-on ${id} in ${loc}", { id, loc: loc.name });

          changed = true;
          xpiState = loc.addFile(id, file);
          if (!loc.isSystem) {
            this.sideLoadedAddons.set(id, xpiState);
          }
        } else {
          let addonChanged =
            xpiState.getModTime(file) || file.path != xpiState.path;
          xpiState.file = file.clone();

          if (addonChanged) {
            changed = true;
            logger.debug("Changed add-on ${id} in ${loc}", {
              id,
              loc: loc.name,
            });
          } else {
            logger.debug("Existing add-on ${id} in ${loc}", {
              id,
              loc: loc.name,
            });
          }
        }
        XPIProvider.addTelemetry(id, { location: loc.name });
      }

      // Anything left behind in oldState was removed from the file system.
      for (let id of knownIds) {
        loc.delete(id);
        changed = true;
      }
    }

    // If there's anything left in oldState, an install location that held add-ons
    // was removed from the browser configuration.
    changed = changed || oldLocations.size > 0;

    logger.debug("scanForChanges changed: ${rv}, state: ${state}", {
      rv: changed,
      state: this.db,
    });
    return changed;
  },

  locations() {
    return this.db.values();
  },

  /**
   * @param {string} name
   *        The location name.
   * @param {XPIStateLocation} location
   *        The location object.
   */
  addLocation(name, location) {
    if (this.db.has(name)) {
      throw new Error(`Trying to add duplicate location: ${name}`);
    }
    this.db.set(name, location);
  },

  /**
   * Get the Map of XPI states for a particular location.
   *
   * @param {string} name
   *        The name of the install location.
   *
   * @returns {XPIStateLocation?}
   *        (id -> XPIState) or null if there are no add-ons in the location.
   */
  getLocation(name) {
    return this.db.get(name);
  },

  /**
   * Get the XPI state for a specific add-on in a location.
   * If the state is not in our cache, return null.
   *
   * @param {string} aLocation
   *        The name of the location where the add-on is installed.
   * @param {string} aId
   *        The add-on ID
   *
   * @returns {XPIState?}
   *        The XPIState entry for the add-on, or null.
   */
  getAddon(aLocation, aId) {
    let location = this.db.get(aLocation);
    return location && location.get(aId);
  },

  /**
   * Find the highest priority location of an add-on by ID and return the
   * XPIState.
   * @param {string} aId
   *        The add-on IDa
   * @param {function} aFilter
   *        An optional filter to apply to install locations.  If provided,
   *        addons in locations that do not match the filter are not considered.
   *
   * @returns {XPIState?}
   */
  findAddon(aId, aFilter = () => true) {
    // Fortunately the Map iterator returns in order of insertion, which is
    // also our highest -> lowest priority order.
    for (let location of this.locations()) {
      if (!aFilter(location)) {
        continue;
      }
      if (location.has(aId)) {
        return location.get(aId);
      }
    }
    return undefined;
  },

  /**
   * Iterates over the list of all enabled add-ons in any location.
   */
  *enabledAddons() {
    for (let location of this.locations()) {
      for (let entry of location.values()) {
        if (entry.enabled) {
          yield entry;
        }
      }
    }
  },

  /**
   * Add a new XPIState for an add-on and synchronize it with the DBAddonInternal.
   *
   * @param {DBAddonInternal} aAddon
   *        The add-on to add.
   */
  addAddon(aAddon) {
    aAddon.location.addAddon(aAddon);
  },

  /**
   * Save the current state of installed add-ons.
   */
  save() {
    if (!this._jsonFile) {
      this._jsonFile = new lazy.JSONFile({
        path: PathUtils.join(
          Services.dirsvc.get("ProfD", Ci.nsIFile).path,
          FILE_XPI_STATES
        ),
        finalizeAt: AddonManagerPrivate.finalShutdown,
        compression: "lz4",
      });
      this._jsonFile.data = this;
    }

    this._jsonFile.saveSoon();
  },

  toJSON() {
    let data = {};
    for (let [key, loc] of this.db.entries()) {
      if (!loc.isTemporary && (loc.size || loc.hasStaged)) {
        data[key] = loc;
      }
    }
    return data;
  },

  /**
   * Remove the XPIState for an add-on and save the new state.
   *
   * @param {string} aLocation
   *        The name of the add-on location.
   * @param {string} aId
   *        The ID of the add-on.
   *
   */
  removeAddon(aLocation, aId) {
    logger.debug(`Removing XPIState for ${aLocation}: ${aId}`);
    let location = this.db.get(aLocation);
    if (location) {
      location.removeAddon(aId);
      this.save();
    }
  },

  /**
   * Disable the XPIState for an add-on.
   *
   * @param {string} aId
   *        The ID of the add-on.
   */
  disableAddon(aId) {
    logger.debug(`Disabling XPIState for ${aId}`);
    let state = this.findAddon(aId);
    if (state) {
      state.enabled = false;
    }
  },
};

/**
 * A helper class to manage the lifetime of and interaction with
 * bootstrap scopes for an add-on.
 *
 * @param {Object} addon
 *        The add-on which owns this scope. Should be either an
 *        AddonInternal or XPIState object.
 */
class BootstrapScope {
  constructor(addon) {
    if (!addon.id || !addon.version || !addon.type) {
      throw new Error("Addon must include an id, version, and type");
    }

    this.addon = addon;
    this.instanceID = null;
    this.scope = null;
    this.started = false;
  }

  /**
   * Returns a BootstrapScope object for the given add-on. If an active
   * scope exists, it is returned. Otherwise a new one is created.
   *
   * @param {Object} addon
   *        The add-on which owns this scope, as accepted by the
   *        constructor.
   * @returns {BootstrapScope}
   */
  static get(addon) {
    let scope = XPIProvider.activeAddons.get(addon.id);
    if (!scope) {
      scope = new this(addon);
    }
    return scope;
  }

  get file() {
    return this.addon.file || this.addon._sourceBundle;
  }

  get runInSafeMode() {
    return "runInSafeMode" in this.addon
      ? this.addon.runInSafeMode
      : canRunInSafeMode(this.addon);
  }

  /**
   * Returns state information for use by an AsyncShutdown blocker. If
   * the wrapped bootstrap scope has a fetchState method, it is called,
   * and its result returned. If not, returns null.
   *
   * @returns {Object|null}
   */
  fetchState() {
    if (this.scope && this.scope.fetchState) {
      return this.scope.fetchState();
    }
    return null;
  }

  /**
   * Calls a bootstrap method for an add-on.
   *
   * @param {string} aMethod
   *        The name of the bootstrap method to call
   * @param {integer} aReason
   *        The reason flag to pass to the bootstrap's startup method
   * @param {Object} [aExtraParams = {}]
   *        An object of additional key/value pairs to pass to the method in
   *        the params argument
   * @returns {any}
   *        The return value of the bootstrap method.
   */
  async callBootstrapMethod(aMethod, aReason, aExtraParams = {}) {
    let { addon, runInSafeMode } = this;
    if (
      Services.appinfo.inSafeMode &&
      !runInSafeMode &&
      aMethod !== "uninstall"
    ) {
      return null;
    }

    try {
      if (!this.scope) {
        this.loadBootstrapScope(aReason);
      }

      if (aMethod == "startup" || aMethod == "shutdown") {
        aExtraParams.instanceID = this.instanceID;
      }

      let method = undefined;
      let { scope } = this;
      try {
        method = scope[aMethod];
      } catch (e) {
        // An exception will be caught if the expected method is not defined.
        // That will be logged below.
      }

      if (aMethod == "startup") {
        this.started = true;
      } else if (aMethod == "shutdown") {
        this.started = false;

        // Extensions are automatically deinitialized in the correct order at shutdown.
        if (aReason != BOOTSTRAP_REASONS.APP_SHUTDOWN) {
          this._pendingDisable = true;
          for (let addon of XPIProvider.getDependentAddons(this.addon)) {
            if (addon.active) {
              await XPIExports.XPIDatabase.updateAddonDisabledState(addon);
            }
          }
        }
      }

      let params = {
        id: addon.id,
        version: addon.version,
        type: addon.type,
        resourceURI: addon.resolvedRootURI,
        signedState: addon.signedState,
        temporarilyInstalled: addon.location.isTemporary,
        builtIn: addon.location.isBuiltin,
        isSystem: addon.location.isSystem,
        isPrivileged: addon.isPrivileged,
        locationHidden: addon.location.hidden,
        recommendationState: addon.recommendationState,
      };

      if (aMethod == "startup" && addon.startupData) {
        params.startupData = addon.startupData;
      }

      Object.assign(params, aExtraParams);

      let result;
      if (!method) {
        logger.warn(
          `Add-on ${addon.id} is missing bootstrap method ${aMethod}`
        );
      } else {
        logger.debug(
          `Calling bootstrap method ${aMethod} on ${addon.id} version ${addon.version}`
        );

        this._beforeCallBootstrapMethod(aMethod, params, aReason);

        try {
          result = await method.call(scope, params, aReason);
        } catch (e) {
          logger.warn(
            `Exception running bootstrap method ${aMethod} on ${addon.id}`,
            e
          );
        }
      }
      return result;
    } finally {
      // Extensions are automatically initialized in the correct order at startup.
      if (aMethod == "startup" && aReason != BOOTSTRAP_REASONS.APP_STARTUP) {
        for (let addon of XPIProvider.getDependentAddons(this.addon)) {
          XPIExports.XPIDatabase.updateAddonDisabledState(addon);
        }
      }
    }
  }

  // No-op method to be overridden by tests.
  _beforeCallBootstrapMethod() {}

  /**
   * Loads a bootstrapped add-on's bootstrap.js into a sandbox and the reason
   * values as constants in the scope.
   *
   * @param {integer?} [aReason]
   *        The reason this bootstrap is being loaded, as passed to a
   *        bootstrap method.
   */
  loadBootstrapScope(aReason) {
    this.instanceID = Symbol(this.addon.id);
    this._pendingDisable = false;

    XPIProvider.activeAddons.set(this.addon.id, this);

    // Mark the add-on as active for the crash reporter before loading.
    // But not at app startup, since we'll already have added all of our
    // annotations before starting any loads.
    if (aReason !== BOOTSTRAP_REASONS.APP_STARTUP) {
      XPIProvider.addAddonsToCrashReporter();
    }

    logger.debug(`Loading bootstrap scope from ${this.addon.rootURI}`);

    if (this.addon.isWebExtension) {
      switch (this.addon.type) {
        case "extension":
        case "theme":
          this.scope = lazy.Extension.getBootstrapScope();
          break;

        case "locale":
          this.scope = lazy.Langpack.getBootstrapScope();
          break;

        case "dictionary":
          this.scope = lazy.Dictionary.getBootstrapScope();
          break;

        default:
          throw new Error(`Unknown webextension type ${this.addon.type}`);
      }
    } else {
      let loader = AddonManagerPrivate.externalExtensionLoaders.get(
        this.addon.loader
      );
      if (!loader) {
        throw new Error(`Cannot find loader for ${this.addon.loader}`);
      }

      this.scope = loader.loadScope(this.addon);
    }
  }

  /**
   * Unloads a bootstrap scope by dropping all references to it and then
   * updating the list of active add-ons with the crash reporter.
   */
  unloadBootstrapScope() {
    XPIProvider.activeAddons.delete(this.addon.id);
    XPIProvider.addAddonsToCrashReporter();

    this.scope = null;
    this.startupPromise = null;
    this.instanceID = null;
  }

  /**
   * Calls the bootstrap scope's startup method, with the given reason
   * and extra parameters.
   *
   * @param {integer} reason
   *        The reason code for the startup call.
   * @param {Object} [aExtraParams]
   *        Optional extra parameters to pass to the bootstrap method.
   * @returns {Promise}
   *        Resolves when the startup method has run to completion, rejects
   *        if called late during shutdown.
   */
  async startup(reason, aExtraParams) {
    if (this.shutdownPromise) {
      await this.shutdownPromise;
    }

    if (
      Services.startup.isInOrBeyondShutdownPhase(
        Ci.nsIAppStartup.SHUTDOWN_PHASE_APPSHUTDOWNCONFIRMED
      )
    ) {
      let err = new Error(
        `XPIProvider can't start bootstrap scope for ${this.addon.id} after shutdown was already granted`
      );
      logger.warn("BoostrapScope startup failure: ${error}", { error: err });
      this.startupPromise = Promise.reject(err);
    } else {
      this.startupPromise = this.callBootstrapMethod(
        "startup",
        reason,
        aExtraParams
      );
    }

    return this.startupPromise;
  }

  /**
   * Calls the bootstrap scope's shutdown method, with the given reason
   * and extra parameters.
   *
   * @param {integer} reason
   *        The reason code for the shutdown call.
   * @param {Object} [aExtraParams]
   *        Optional extra parameters to pass to the bootstrap method.
   */
  async shutdown(reason, aExtraParams) {
    this.shutdownPromise = this._shutdown(reason, aExtraParams);
    await this.shutdownPromise;
    this.shutdownPromise = null;
  }

  async _shutdown(reason, aExtraParams) {
    await this.startupPromise;
    return this.callBootstrapMethod("shutdown", reason, aExtraParams);
  }

  /**
   * If the add-on is already running, calls its "shutdown" method, and
   * unloads its bootstrap scope.
   *
   * @param {integer} reason
   *        The reason code for the shutdown call.
   * @param {Object} [aExtraParams]
   *        Optional extra parameters to pass to the bootstrap method.
   */
  async disable() {
    if (this.started) {
      await this.shutdown(BOOTSTRAP_REASONS.ADDON_DISABLE);
      // If we disable and re-enable very quickly, it's possible that
      // the next startup() method will be called immediately after this
      // shutdown method finishes. This almost never happens outside of
      // tests. In tests, alas...
      if (!this.started) {
        this.unloadBootstrapScope();
      }
    }
  }

  /**
   * Calls the bootstrap scope's install method, and optionally its
   * startup method.
   *
   * @param {integer} reason
   *        The reason code for the calls.
   * @param {boolean} [startup = false]
   *        If true, and the add-on is active, calls its startup method
   *        after its install method.
   * @param {Object} [extraArgs]
   *        Optional extra parameters to pass to the bootstrap method.
   * @returns {Promise}
   *        Resolves when the startup method has run to completion, if
   *        startup is required.
   */
  install(reason = BOOTSTRAP_REASONS.ADDON_INSTALL, startup, extraArgs) {
    return this._install(reason, false, startup, extraArgs);
  }

  async _install(reason, callUpdate, startup, extraArgs) {
    if (callUpdate) {
      await this.callBootstrapMethod("update", reason, extraArgs);
    } else {
      this.callBootstrapMethod("install", reason, extraArgs);
    }

    if (startup && this.addon.active) {
      await this.startup(reason, extraArgs);
    } else if (this.addon.disabled) {
      this.unloadBootstrapScope();
    }
  }

  /**
   * Calls the bootstrap scope's uninstall method, and unloads its
   * bootstrap scope. If the extension is already running, its shutdown
   * method is called before its uninstall method.
   *
   * @param {integer} reason
   *        The reason code for the calls.
   * @param {Object} [extraArgs]
   *        Optional extra parameters to pass to the bootstrap method.
   * @returns {Promise}
   *        Resolves when the shutdown method has run to completion, if
   *        shutdown is required, and the uninstall method has been
   *        called.
   */
  uninstall(reason = BOOTSTRAP_REASONS.ADDON_UNINSTALL, extraArgs) {
    return this._uninstall(reason, false, extraArgs);
  }

  async _uninstall(reason, callUpdate, extraArgs) {
    if (this.started) {
      await this.shutdown(reason, extraArgs);
    }
    if (!callUpdate) {
      this.callBootstrapMethod("uninstall", reason, extraArgs);
    }
    this.unloadBootstrapScope();

    if (this.file) {
      XPIExports.XPIInstall.flushJarCache(this.file);
    }
  }

  /**
   * Calls the appropriate sequence of shutdown, uninstall, update,
   * startup, and install methods for updating the current scope's
   * add-on to the given new add-on, depending on the current state of
   * the scope.
   *
   * @param {XPIState} newAddon
   *        The new add-on which is being installed, as expected by the
   *        constructor.
   * @param {boolean} [startup = false]
   *        If true, and the new add-on is enabled, calls its startup
   *        method as its final operation.
   * @param {function} [updateCallback]
   *        An optional callback function to call between uninstalling
   *        the old add-on and installing the new one. This callback
   *        should update any database state which is necessary for the
   *        startup of the new add-on.
   * @returns {Promise}
   *        Resolves when all required bootstrap callbacks have
   *        completed.
   */
  async update(newAddon, startup = false, updateCallback) {
    let reason = XPIExports.XPIInstall.newVersionReason(
      this.addon.version,
      newAddon.version
    );

    let callUpdate = this.addon.isWebExtension && newAddon.isWebExtension;

    // BootstrapScope gets either an XPIState instance or an AddonInternal
    // instance, when we update, we need the latter to access permissions
    // from the manifest.
    let existingAddon = this.addon;

    let extraArgs = {
      oldVersion: existingAddon.version,
      newVersion: newAddon.version,
    };

    // If we're updating an extension, we may need to read data to
    // calculate permission changes.
    if (callUpdate && existingAddon.type === "extension") {
      if (this.addon instanceof XPIState) {
        // The existing addon will be cached in the database.
        existingAddon = await XPIExports.XPIDatabase.getAddonByID(
          this.addon.id
        );
      }

      if (newAddon instanceof XPIState) {
        newAddon = await XPIExports.XPIInstall.loadManifestFromFile(
          newAddon.file,
          newAddon.location
        );
      }

      Object.assign(extraArgs, {
        userPermissions: newAddon.userPermissions,
        optionalPermissions: newAddon.optionalPermissions,
        oldPermissions: existingAddon.userPermissions,
        oldOptionalPermissions: existingAddon.optionalPermissions,
      });
    }

    await this._uninstall(reason, callUpdate, extraArgs);

    if (updateCallback) {
      await updateCallback();
    }

    this.addon = newAddon;
    return this._install(reason, callUpdate, startup, extraArgs);
  }
}

let resolveDBReady;
let dbReadyPromise = new Promise(resolve => {
  resolveDBReady = resolve;
});
let resolveProviderReady;
let providerReadyPromise = new Promise(resolve => {
  resolveProviderReady = resolve;
});

export var XPIProvider = {
  get name() {
    return "XPIProvider";
  },

  BOOTSTRAP_REASONS: Object.freeze(BOOTSTRAP_REASONS),

  // A Map of active addons to their bootstrapScope by ID
  activeAddons: new Map(),
  // Per-addon telemetry information
  _telemetryDetails: {},
  // Have we started shutting down bootstrap add-ons?
  _closing: false,

  // Promises awaited by the XPIProvider before resolving providerReadyPromise,
  // (pushed into the array by XPIProvider maybeInstallBuiltinAddon and startup
  // methods).
  startupPromises: [],

  // Array of the bootstrap startup promises for the enabled addons being
  // initiated during the XPIProvider startup.
  //
  // NOTE: XPIProvider will wait for these promises (and the startupPromises one)
  // to have settled before allowing the application to proceed with shutting down
  // (see quitApplicationGranted blocker at the end of the XPIProvider.startup).
  enabledAddonsStartupPromises: [],

  databaseReady: Promise.all([dbReadyPromise, providerReadyPromise]),

  registerProvider() {
    AddonManagerPrivate.registerProvider(this, Array.from(ALL_XPI_TYPES));
  },

  // Check if the XPIDatabase has been loaded (without actually
  // triggering unwanted imports or I/O)
  get isDBLoaded() {
    // Make sure we don't touch the XPIDatabase getter before it's
    // actually loaded, and force an early load.
    return (
      (Object.getOwnPropertyDescriptor(XPIExports, "XPIDatabase").value &&
        XPIExports.XPIDatabase.initialized) ||
      false
    );
  },

  /**
   * Returns true if the add-on with the given ID is currently active,
   * without forcing the add-ons database to load.
   *
   * @param {string} addonId
   *        The ID of the add-on to check.
   * @returns {boolean}
   */
  addonIsActive(addonId) {
    let state = XPIStates.findAddon(addonId);
    return state && state.enabled;
  },

  /**
   * Returns an array of the add-on values in `enabledAddons`,
   * sorted so that all of an add-on's dependencies appear in the array
   * before itself.
   *
   * @returns {Array<object>}
   *   A sorted array of add-on objects. Each value is a copy of the
   *   corresponding value in the `enabledAddons` object, with an
   *   additional `id` property, which corresponds to the key in that
   *   object, which is the same as the add-ons ID.
   */
  sortBootstrappedAddons() {
    function compare(a, b) {
      if (a === b) {
        return 0;
      }
      return a < b ? -1 : 1;
    }

    // Sort the list so that ordering is deterministic.
    let list = Array.from(XPIStates.enabledAddons());
    list.sort((a, b) => compare(a.id, b.id));

    let addons = {};
    for (let entry of list) {
      addons[entry.id] = entry;
    }

    let res = new Set();
    let seen = new Set();

    let add = addon => {
      seen.add(addon.id);

      for (let id of addon.dependencies || []) {
        if (id in addons && !seen.has(id)) {
          add(addons[id]);
        }
      }

      res.add(addon.id);
    };

    Object.values(addons).forEach(add);

    return Array.from(res, id => addons[id]);
  },

  /*
   * Adds metadata to the telemetry payload for the given add-on.
   */
  addTelemetry(aId, aPayload) {
    if (!this._telemetryDetails[aId]) {
      this._telemetryDetails[aId] = {};
    }
    Object.assign(this._telemetryDetails[aId], aPayload);
  },

  setupInstallLocations(aAppChanged) {
    function DirectoryLoc(aName, aScope, aKey, aPaths, aLocked, aIsSystem) {
      try {
        var dir = lazy.FileUtils.getDir(aKey, aPaths);
      } catch (e) {
        return null;
      }
      return new DirectoryLocation(aName, dir, aScope, aLocked, aIsSystem);
    }

    function SystemDefaultsLoc(name, scope, key, paths) {
      try {
        var dir = lazy.FileUtils.getDir(key, paths);
      } catch (e) {
        return null;
      }
      return new SystemAddonDefaults(name, dir, scope);
    }

    function SystemLoc(aName, aScope, aKey, aPaths) {
      try {
        var dir = lazy.FileUtils.getDir(aKey, aPaths);
      } catch (e) {
        return null;
      }
      return new SystemAddonLocation(aName, dir, aScope, aAppChanged !== false);
    }

    function RegistryLoc(aName, aScope, aKey) {
      if ("nsIWindowsRegKey" in Ci) {
        return new WinRegLocation(aName, Ci.nsIWindowsRegKey[aKey], aScope);
      }
    }

    // These must be in order of priority, highest to lowest,
    // for processFileChanges etc. to work
    let locations = [
      [() => TemporaryInstallLocation, TemporaryInstallLocation.name, null],

      [
        DirectoryLoc,
        KEY_APP_PROFILE,
        AddonManager.SCOPE_PROFILE,
        KEY_PROFILEDIR,
        [DIR_EXTENSIONS],
        false,
      ],

      [
        DirectoryLoc,
        KEY_APP_SYSTEM_PROFILE,
        AddonManager.SCOPE_APPLICATION,
        KEY_PROFILEDIR,
        [DIR_APP_SYSTEM_PROFILE],
        false,
        true,
      ],

      [
        SystemLoc,
        KEY_APP_SYSTEM_ADDONS,
        AddonManager.SCOPE_PROFILE,
        KEY_PROFILEDIR,
        [DIR_SYSTEM_ADDONS],
      ],

      [
        SystemDefaultsLoc,
        KEY_APP_SYSTEM_DEFAULTS,
        AddonManager.SCOPE_PROFILE,
        KEY_APP_FEATURES,
        [],
      ],

      [() => BuiltInLocation, KEY_APP_BUILTINS, AddonManager.SCOPE_APPLICATION],

      [
        DirectoryLoc,
        KEY_APP_SYSTEM_USER,
        AddonManager.SCOPE_USER,
        "XREUSysExt",
        [Services.appinfo.ID],
        true,
      ],

      [
        RegistryLoc,
        "winreg-app-user",
        AddonManager.SCOPE_USER,
        "ROOT_KEY_CURRENT_USER",
      ],

      [
        DirectoryLoc,
        KEY_APP_GLOBAL,
        AddonManager.SCOPE_APPLICATION,
        KEY_ADDON_APP_DIR,
        [DIR_EXTENSIONS],
        true,
      ],

      [
        DirectoryLoc,
        KEY_APP_SYSTEM_SHARE,
        AddonManager.SCOPE_SYSTEM,
        "XRESysSExtPD",
        [Services.appinfo.ID],
        true,
      ],

      [
        DirectoryLoc,
        KEY_APP_SYSTEM_LOCAL,
        AddonManager.SCOPE_SYSTEM,
        "XRESysLExtPD",
        [Services.appinfo.ID],
        true,
      ],

      [
        RegistryLoc,
        "winreg-app-global",
        AddonManager.SCOPE_SYSTEM,
        "ROOT_KEY_LOCAL_MACHINE",
      ],
    ];

    for (let [constructor, name, scope, ...args] of locations) {
      if (!scope || lazy.enabledScopes & scope) {
        try {
          let loc = constructor(name, scope, ...args);
          if (loc) {
            XPIStates.addLocation(name, loc);
          }
        } catch (e) {
          logger.warn(
            `Failed to add ${constructor.name} install location ${name}`,
            e
          );
        }
      }
    }
  },

  /**
   * Registers the built-in set of dictionaries with the spell check
   * service.
   */
  registerBuiltinDictionaries() {
    this.dictionaries = {};
    for (let [lang, path] of Object.entries(
      this.builtInAddons.dictionaries || {}
    )) {
      path = path.slice(0, -4) + ".aff";
      let uri = Services.io.newURI(`resource://gre/${path}`);

      this.dictionaries[lang] = uri;
      lazy.spellCheck.addDictionary(lang, uri);
    }
  },

  /**
   * Unregisters the dictionaries in the given object, and re-registers
   * any built-in dictionaries in their place, when they exist.
   *
   * @param {Object<nsIURI>} aDicts
   *        An object containing a property with a dictionary language
   *        code and a nsIURI value for each dictionary to be
   *        unregistered.
   */
  unregisterDictionaries(aDicts) {
    let origDicts = lazy.spellCheck.dictionaries.slice();
    let toRemove = [];

    for (let [lang, uri] of Object.entries(aDicts)) {
      if (
        lazy.spellCheck.removeDictionary(lang, uri) &&
        this.dictionaries.hasOwnProperty(lang)
      ) {
        lazy.spellCheck.addDictionary(lang, this.dictionaries[lang]);
      } else {
        toRemove.push(lang);
      }
    }

    lazy.spellCheck.dictionaries = origDicts.filter(
      lang => !toRemove.includes(lang)
    );
  },

  /**
   * Starts the XPI provider initializes the install locations and prefs.
   *
   * @param {boolean?} aAppChanged
   *        A tri-state value. Undefined means the current profile was created
   *        for this session, true means the profile already existed but was
   *        last used with an application with a different version number,
   *        false means that the profile was last used by this version of the
   *        application.
   * @param {string?} [aOldAppVersion]
   *        The version of the application last run with this profile or null
   *        if it is a new profile or the version is unknown
   * @param {string?} [aOldPlatformVersion]
   *        The version of the platform last run with this profile or null
   *        if it is a new profile or the version is unknown
   */
  startup(aAppChanged, aOldAppVersion, aOldPlatformVersion) {
    try {
      AddonManagerPrivate.recordTimestamp("XPI_startup_begin");

      logger.debug("startup");

      this.builtInAddons = {};
      try {
        let url = Services.io.newURI(BUILT_IN_ADDONS_URI);
        let data = Cu.readUTF8URI(url);
        this.builtInAddons = JSON.parse(data);
      } catch (e) {
        if (AppConstants.DEBUG) {
          logger.debug("List of built-in add-ons is missing or invalid.", e);
        }
      }

      this.registerBuiltinDictionaries();

      // Clear this at startup for xpcshell test restarts
      this._telemetryDetails = {};
      // Register our details structure with AddonManager
      AddonManagerPrivate.setTelemetryDetails("XPI", this._telemetryDetails);

      this.setupInstallLocations(aAppChanged);

      if (!AppConstants.MOZ_REQUIRE_SIGNING || Cu.isInAutomation) {
        Services.prefs.addObserver(PREF_XPI_SIGNATURES_REQUIRED, this);
      }
      Services.prefs.addObserver(PREF_LANGPACK_SIGNATURES, this);
      Services.obs.addObserver(this, NOTIFICATION_FLUSH_PERMISSIONS);

      this.checkForChanges(aAppChanged, aOldAppVersion, aOldPlatformVersion);

      AddonManagerPrivate.markProviderSafe(this);

      const lastTheme = Services.prefs.getCharPref(
        "extensions.activeThemeID",
        null
      );

      if (
        lastTheme === "recommended-1" ||
        lastTheme === "recommended-2" ||
        lastTheme === "recommended-3" ||
        lastTheme === "recommended-4" ||
        lastTheme === "recommended-5"
      ) {
        // The user is using a theme that was once bundled with Firefox, but no longer
        // is. Clear their theme so that they will be forced to reset to the default.
        this.startupPromises.push(
          AddonManagerPrivate.notifyAddonChanged(null, "theme")
        );
      }
      this.maybeInstallBuiltinAddon(
        "default-theme@mozilla.org",
        "1.3",
        "resource://default-theme/"
      );

      resolveProviderReady(Promise.all(this.startupPromises));

      if (AppConstants.MOZ_CRASHREPORTER) {
        // Annotate the crash report with relevant add-on information.
        try {
          // The `EMCheckCompatibility` annotation represents a boolean, but
          // we've historically set it as a string so keep doing it for the
          // time being.
          Services.appinfo.annotateCrashReport(
            "EMCheckCompatibility",
            AddonManager.checkCompatibility.toString()
          );
        } catch (e) {}
        this.addAddonsToCrashReporter();
      }

      try {
        AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_begin");

        for (let addon of this.sortBootstrappedAddons()) {
          // The startup update check above may have already started some
          // extensions, make sure not to try to start them twice.
          let activeAddon = this.activeAddons.get(addon.id);
          if (activeAddon && activeAddon.started) {
            continue;
          }
          try {
            let reason = BOOTSTRAP_REASONS.APP_STARTUP;
            // Eventually set INSTALLED reason when a bootstrap addon
            // is dropped in profile folder and automatically installed
            if (
              AddonManager.getStartupChanges(
                AddonManager.STARTUP_CHANGE_INSTALLED
              ).includes(addon.id)
            ) {
              reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
            } else if (
              AddonManager.getStartupChanges(
                AddonManager.STARTUP_CHANGE_ENABLED
              ).includes(addon.id)
            ) {
              reason = BOOTSTRAP_REASONS.ADDON_ENABLE;
            }
            this.enabledAddonsStartupPromises.push(
              BootstrapScope.get(addon).startup(reason)
            );
          } catch (e) {
            logger.error(
              "Failed to load bootstrap addon " +
                addon.id +
                " from " +
                addon.descriptor,
              e
            );
          }
        }
        AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_end");
      } catch (e) {
        logger.error("bootstrap startup failed", e);
        AddonManagerPrivate.recordException(
          "XPI-BOOTSTRAP",
          "startup failed",
          e
        );
      }

      // Let these shutdown a little earlier when they still have access to most
      // of XPCOM
      lazy.AsyncShutdown.quitApplicationGranted.addBlocker(
        "XPIProvider shutdown",
        async () => {
          // Do not enter shutdown before we actually finished starting as this
          // can lead to hangs as seen in bug 1814104.
          await Promise.allSettled([
            ...this.startupPromises,
            ...this.enabledAddonsStartupPromises,
          ]);

          XPIProvider._closing = true;

          await XPIProvider.cleanupTemporaryAddons();
          for (let addon of XPIProvider.sortBootstrappedAddons().reverse()) {
            // If no scope has been loaded for this add-on then there is no need
            // to shut it down (should only happen when a bootstrapped add-on is
            // pending enable)
            let activeAddon = XPIProvider.activeAddons.get(addon.id);
            if (!activeAddon || !activeAddon.started) {
              continue;
            }

            // If the add-on was pending disable then shut it down and remove it
            // from the persisted data.
            let reason = BOOTSTRAP_REASONS.APP_SHUTDOWN;
            if (addon._pendingDisable) {
              reason = BOOTSTRAP_REASONS.ADDON_DISABLE;
            } else if (addon.location.name == KEY_APP_TEMPORARY) {
              reason = BOOTSTRAP_REASONS.ADDON_UNINSTALL;
              let existing = XPIStates.findAddon(
                addon.id,
                loc => !loc.isTemporary
              );
              if (existing) {
                reason = XPIExports.XPIInstall.newVersionReason(
                  addon.version,
                  existing.version
                );
              }
            }

            let scope = BootstrapScope.get(addon);
            let promise = scope.shutdown(reason);
            lazy.AsyncShutdown.profileChangeTeardown.addBlocker(
              `Extension shutdown: ${addon.id}`,
              promise,
              {
                fetchState: scope.fetchState.bind(scope),
              }
            );
          }
        }
      );

      // Detect final-ui-startup for telemetry reporting
      Services.obs.addObserver(function observer() {
        AddonManagerPrivate.recordTimestamp("XPI_finalUIStartup");
        Services.obs.removeObserver(observer, "final-ui-startup");
      }, "final-ui-startup");

      // If we haven't yet loaded the XPI database, schedule loading it
      // to occur once other important startup work is finished.  We want
      // this to happen relatively quickly after startup so the telemetry
      // environment has complete addon information.
      //
      // Unfortunately we have to use a variety of ways do detect when it
      // is time to load.  In a regular browser process we just wait for
      // sessionstore-windows-restored.  In a browser toolbox process
      // we wait for the toolbox to show up, based on xul-window-visible
      // and a visible toolbox window.
      //
      // TelemetryEnvironment's EnvironmentAddonBuilder awaits databaseReady
      // before releasing a blocker on AddonManager.beforeShutdown, which in its
      // turn is a blocker of a shutdown blocker at "profile-before-change".
      // To avoid a deadlock, trigger the DB load at "profile-before-change" if
      // the database hasn't started loading yet.
      //
      // Finally, we have a test-only event called test-load-xpi-database
      // as a temporary workaround for bug 1372845.  The latter can be
      // cleaned up when that bug is resolved.
      if (!this.isDBLoaded) {
        const EVENTS = [
          "sessionstore-windows-restored",
          "xul-window-visible",
          "profile-before-change",
          "test-load-xpi-database",
        ];
        let observer = (subject, topic) => {
          if (
            topic == "xul-window-visible" &&
            !Services.wm.getMostRecentWindow("devtools:toolbox")
          ) {
            return;
          }

          for (let event of EVENTS) {
            Services.obs.removeObserver(observer, event);
          }

          XPIExports.XPIDatabase.asyncLoadDB();
        };
        for (let event of EVENTS) {
          Services.obs.addObserver(observer, event);
        }
      }

      AddonManagerPrivate.recordTimestamp("XPI_startup_end");

      lazy.timerManager.registerTimer(
        "xpi-signature-verification",
        () => {
          XPIExports.XPIDatabase.verifySignatures();
        },
        XPI_SIGNATURE_CHECK_PERIOD
      );
    } catch (e) {
      logger.error("startup failed", e);
      AddonManagerPrivate.recordException("XPI", "startup failed", e);
    }
  },

  /**
   * Shuts down the database and releases all references.
   * Return: Promise{integer} resolves / rejects with the result of
   *                          flushing the XPI Database if it was loaded,
   *                          0 otherwise.
   */
  async shutdown() {
    logger.debug("shutdown");

    this.activeAddons.clear();
    this.allAppGlobal = true;

    // Stop anything we were doing asynchronously
    XPIExports.XPIInstall.cancelAll();

    for (let install of XPIExports.XPIInstall.installs) {
      if (install.onShutdown()) {
        install.onShutdown();
      }
    }

    // If there are pending operations then we must update the list of active
    // add-ons
    if (Services.prefs.getBoolPref(PREF_PENDING_OPERATIONS, false)) {
      XPIExports.XPIDatabase.updateActiveAddons();
      Services.prefs.setBoolPref(PREF_PENDING_OPERATIONS, false);
    }

    await XPIExports.XPIDatabase.shutdown();
  },

  cleanupTemporaryAddons() {
    let promises = [];
    let tempLocation = TemporaryInstallLocation;
    for (let [id, addon] of tempLocation.entries()) {
      tempLocation.delete(id);

      let bootstrap = BootstrapScope.get(addon);
      let existing = XPIStates.findAddon(id, loc => !loc.isTemporary);

      let cleanup = () => {
        tempLocation.installer.uninstallAddon(id);
        tempLocation.removeAddon(id);
      };

      let promise;
      if (existing) {
        promise = bootstrap.update(existing, false, () => {
          cleanup();
          XPIExports.XPIDatabase.makeAddonLocationVisible(
            id,
            existing.location
          );
        });
      } else {
        promise = bootstrap.uninstall().then(cleanup);
      }
      lazy.AsyncShutdown.profileChangeTeardown.addBlocker(
        `Temporary extension shutdown: ${addon.id}`,
        promise
      );
      promises.push(promise);
    }
    return Promise.all(promises);
  },

  /**
   * Adds a list of currently active add-ons to the next crash report.
   */
  addAddonsToCrashReporter() {
    void (Services.appinfo instanceof Ci.nsICrashReporter);
    if (!Services.appinfo.annotateCrashReport || Services.appinfo.inSafeMode) {
      return;
    }

    let data = Array.from(XPIStates.enabledAddons(), a => a.telemetryKey).join(
      ","
    );

    try {
      Services.appinfo.annotateCrashReport("Add-ons", data);
    } catch (e) {}

    lazy.TelemetrySession.setAddOns(data);
  },

  /**
   * Check the staging directories of install locations for any add-ons to be
   * installed or add-ons to be uninstalled.
   *
   * @param {Object} aManifests
   *         A dictionary to add detected install manifests to for the purpose
   *         of passing through updated compatibility information
   * @returns {boolean}
   *        True if an add-on was installed or uninstalled
   */
  processPendingFileChanges(aManifests) {
    let changed = false;
    for (let loc of XPIStates.locations()) {
      aManifests[loc.name] = {};
      // We can't install or uninstall anything in locked locations
      if (loc.locked) {
        continue;
      }

      // Collect any install errors for specific removal from the staged directory
      // during cleanStagingDir.  Successful installs remove the files.
      let stagedFailureNames = [];
      let promises = [];
      for (let [id, metadata] of loc.getStagedAddons()) {
        loc.unstageAddon(id);

        aManifests[loc.name][id] = null;
        promises.push(
          XPIExports.XPIInstall.installStagedAddon(id, metadata, loc).then(
            addon => {
              aManifests[loc.name][id] = addon;
            },
            error => {
              delete aManifests[loc.name][id];
              stagedFailureNames.push(`${id}.xpi`);

              logger.error(
                `Failed to install staged add-on ${id} in ${loc.name}`,
                error
              );
            }
          )
        );
      }

      if (promises.length) {
        changed = true;
        awaitPromise(Promise.all(promises));
      }

      try {
        if (changed || stagedFailureNames.length) {
          loc.installer.cleanStagingDir(stagedFailureNames);
        }
      } catch (e) {
        // Non-critical, just saves some perf on startup if we clean this up.
        logger.debug("Error cleaning staging dir", e);
      }
    }
    return changed;
  },

  /**
   * Installs any add-ons located in the extensions directory of the
   * application's distribution specific directory into the profile unless a
   * newer version already exists or the user has previously uninstalled the
   * distributed add-on.
   *
   * @param {Object} aManifests
   *        A dictionary to add new install manifests to to save having to
   *        reload them later
   * @param {string} [aAppChanged]
   *        See checkForChanges
   * @param {string?} [aOldAppVersion]
   *        The version of the application last run with this profile or null
   *        if it is a new profile or the version is unknown
   * @returns {boolean}
   *        True if any new add-ons were installed
   */
  installDistributionAddons(aManifests, aAppChanged, aOldAppVersion) {
    let distroDirs = [];
    try {
      distroDirs.push(
        lazy.FileUtils.getDir(KEY_APP_DISTRIBUTION, [DIR_EXTENSIONS])
      );
    } catch (e) {
      return false;
    }

    let availableLocales = [];
    for (let file of iterDirectory(distroDirs[0])) {
      if (file.isDirectory() && file.leafName.startsWith("locale-")) {
        availableLocales.push(file.leafName.replace("locale-", ""));
      }
    }

    let locales = Services.locale.negotiateLanguages(
      Services.locale.requestedLocales,
      availableLocales,
      undefined,
      Services.locale.langNegStrategyMatching
    );

    // Also install addons from subdirectories that correspond to the requested
    // locales. This allows for installing language packs and dictionaries.
    for (let locale of locales) {
      let langPackDir = distroDirs[0].clone();
      langPackDir.append(`locale-${locale}`);
      distroDirs.push(langPackDir);
    }

    let changed = false;
    for (let distroDir of distroDirs) {
      logger.warn(`Checking ${distroDir.path} for addons`);
      for (let file of iterDirectory(distroDir)) {
        if (!isXPI(file.leafName, true)) {
          // Only warn for files, not directories
          if (!file.isDirectory()) {
            logger.warn(`Ignoring distribution: not an XPI: ${file.path}`);
          }
          continue;
        }

        let id = getExpectedID(file);
        if (!id) {
          logger.warn(
            `Ignoring distribution: name is not a valid add-on ID: ${file.path}`
          );
          continue;
        }

        /* If this is not an upgrade and we've already handled this extension
         * just continue */
        if (
          !aAppChanged &&
          Services.prefs.prefHasUserValue(PREF_BRANCH_INSTALLED_ADDON + id)
        ) {
          continue;
        }

        try {
          let loc = XPIStates.getLocation(KEY_APP_PROFILE);
          let addon = awaitPromise(
            XPIExports.XPIInstall.installDistributionAddon(
              id,
              file,
              loc,
              aOldAppVersion
            )
          );

          if (addon) {
            // aManifests may contain a copy of a newly installed add-on's manifest
            // and we'll have overwritten that so instead cache our install manifest
            // which will later be put into the database in processFileChanges
            if (!(loc.name in aManifests)) {
              aManifests[loc.name] = {};
            }
            aManifests[loc.name][id] = addon;
            changed = true;
          }
        } catch (e) {
          logger.error(`Failed to install distribution add-on ${file.path}`, e);
        }
      }
    }

    return changed;
  },

  /**
   * Like `installBuiltinAddon`, but only installs the addon at `aBase`
   * if an existing built-in addon with the ID `aID` and version doesn't
   * already exist.
   *
   * @param {string} aID
   *        The ID of the add-on being registered.
   * @param {string} aVersion
   *        The version of the add-on being registered.
   * @param {string} aBase
   *        A string containing the base URL.  Must be a resource: URL.
   * @returns {Promise<Addon>} a Promise that resolves when the addon is installed.
   */
  async maybeInstallBuiltinAddon(aID, aVersion, aBase) {
    let installed;
    if (lazy.enabledScopes & BuiltInLocation.scope) {
      let existing = BuiltInLocation.get(aID);
      if (!existing || existing.version != aVersion) {
        installed = this.installBuiltinAddon(aBase);
        this.startupPromises.push(installed);
      }
    }
    return installed;
  },

  getDependentAddons(aAddon) {
    return Array.from(XPIExports.XPIDatabase.getAddons()).filter(addon =>
      addon.dependencies.includes(aAddon.id)
    );
  },

  /**
   * Checks for any changes that have occurred since the last time the
   * application was launched.
   *
   * @param {boolean?} [aAppChanged]
   *        A tri-state value. Undefined means the current profile was created
   *        for this session, true means the profile already existed but was
   *        last used with an application with a different version number,
   *        false means that the profile was last used by this version of the
   *        application.
   * @param {string?} [aOldAppVersion]
   *        The version of the application last run with this profile or null
   *        if it is a new profile or the version is unknown
   * @param {string?} [aOldPlatformVersion]
   *        The version of the platform last run with this profile or null
   *        if it is a new profile or the version is unknown
   */
  checkForChanges(aAppChanged, aOldAppVersion, aOldPlatformVersion) {
    logger.debug("checkForChanges");

    // Keep track of whether and why we need to open and update the database at
    // startup time.
    let updateReasons = [];
    if (aAppChanged) {
      updateReasons.push("appChanged");
    }

    let installChanged = XPIStates.scanForChanges(aAppChanged === false);
    if (installChanged) {
      updateReasons.push("directoryState");
    }

    // First install any new add-ons into the locations, if there are any
    // changes then we must update the database with the information in the
    // install locations
    let manifests = {};
    let updated = this.processPendingFileChanges(manifests);
    if (updated) {
      updateReasons.push("pendingFileChanges");
    }

    // This will be true if the previous session made changes that affect the
    // active state of add-ons but didn't commit them properly (normally due
    // to the application crashing)
    let hasPendingChanges = Services.prefs.getBoolPref(
      PREF_PENDING_OPERATIONS,
      false
    );
    if (hasPendingChanges) {
      updateReasons.push("hasPendingChanges");
    }

    // If the application has changed then check for new distribution add-ons
    if (Services.prefs.getBoolPref(PREF_INSTALL_DISTRO_ADDONS, true)) {
      updated = this.installDistributionAddons(
        manifests,
        aAppChanged,
        aOldAppVersion
      );
      if (updated) {
        updateReasons.push("installDistributionAddons");
      }
    }

    // If the schema appears to have changed then we should update the database
    if (DB_SCHEMA != Services.prefs.getIntPref(PREF_DB_SCHEMA, 0)) {
      // If we don't have any add-ons, just update the pref, since we don't need to
      // write the database
      if (!XPIStates.size) {
        logger.debug(
          "Empty XPI database, setting schema version preference to " +
            DB_SCHEMA
        );
        Services.prefs.setIntPref(PREF_DB_SCHEMA, DB_SCHEMA);
      } else {
        updateReasons.push("schemaChanged");
      }
    }

    // Catch and log any errors during the main startup
    try {
      let extensionListChanged = false;
      // If the database needs to be updated then open it and then update it
      // from the filesystem
      if (updateReasons.length) {
        AddonManagerPrivate.recordSimpleMeasure(
          "XPIDB_startup_load_reasons",
          updateReasons
        );
        XPIExports.XPIDatabase.syncLoadDB(false);
        try {
          extensionListChanged =
            XPIExports.XPIDatabaseReconcile.processFileChanges(
              manifests,
              aAppChanged,
              aOldAppVersion,
              aOldPlatformVersion,
              updateReasons.includes("schemaChanged")
            );
        } catch (e) {
          logger.error("Failed to process extension changes at startup", e);
        }
      }

      // If the application crashed before completing any pending operations then
      // we should perform them now.
      if (extensionListChanged || hasPendingChanges) {
        XPIExports.XPIDatabase.updateActiveAddons();
        return;
      }

      logger.debug("No changes found");
    } catch (e) {
      logger.error("Error during startup file checks", e);
    }
  },

  /**
   * Gets an array of add-ons which were placed in a known install location
   * prior to startup of the current session, were detected by a directory scan
   * of those locations, and are currently disabled.
   *
   * @returns {Promise<Array<Addon>>}
   */
  async getNewSideloads() {
    if (XPIStates.scanForChanges(false)) {
      // We detected changes. Update the database to account for them.
      await XPIExports.XPIDatabase.asyncLoadDB(false);
      XPIExports.XPIDatabaseReconcile.processFileChanges({}, false);
      XPIExports.XPIDatabase.updateActiveAddons();
    }

    let addons = await Promise.all(
      Array.from(XPIStates.sideLoadedAddons.keys(), id => this.getAddonByID(id))
    );

    return addons.filter(
      addon =>
        addon &&
        addon.seen === false &&
        addon.permissions & AddonManager.PERM_CAN_ENABLE
    );
  },

  /**
   * Called to test whether this provider supports installing a particular
   * mimetype.
   *
   * @param {string} aMimetype
   *        The mimetype to check for
   * @returns {boolean}
   *        True if the mimetype is application/x-xpinstall
   */
  supportsMimetype(aMimetype) {
    return aMimetype == "application/x-xpinstall";
  },

  // Identify temporary install IDs.
  isTemporaryInstallID(id) {
    return id.endsWith(TEMPORARY_ADDON_SUFFIX);
  },

  /**
   * Sets startupData for the given addon.  The provided data will be stored
   * in addonsStartup.json so it is available early during browser startup.
   * Note that this file is read synchronously at startup, so startupData
   * should be used with care.
   *
   * @param {string} aID
   *         The id of the addon to save startup data for.
   * @param {any} aData
   *        The data to store.  Must be JSON serializable.
   */
  setStartupData(aID, aData) {
    let state = XPIStates.findAddon(aID);
    state.startupData = aData;
    XPIStates.save();
  },

  /**
   * Persists some startupData into an addon if it is available in the current
   * XPIState for the addon id.
   *
   * @param {AddonInternal} addon An addon to receive the startup data, typically an update that is occuring.
   * @param {XPIState} state optional
   */
  persistStartupData(addon, state) {
    if (!addon.startupData) {
      state = state || XPIStates.findAddon(addon.id);
      if (state?.enabled) {
        // Save persistent listener data if available.  It will be
        // removed later if necessary.
        let persistentListeners = state.startupData?.persistentListeners;
        addon.startupData = { persistentListeners };
      }
    }
  },

  getAddonIDByInstanceID(aInstanceID) {
    if (!aInstanceID || typeof aInstanceID != "symbol") {
      throw Components.Exception(
        "aInstanceID must be a Symbol()",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    for (let [id, val] of this.activeAddons) {
      if (aInstanceID == val.instanceID) {
        return id;
      }
    }

    return null;
  },

  async getAddonsByTypes(aTypes) {
    if (aTypes && !aTypes.some(type => ALL_XPI_TYPES.has(type))) {
      return [];
    }
    return XPIExports.XPIDatabase.getAddonsByTypes(aTypes);
  },

  /**
   * Called to get active Addons of a particular type
   *
   * @param {Array<string>?} aTypes
   *        An array of types to fetch. Can be null to get all types.
   * @returns {Promise<Array<Addon>>}
   */
  async getActiveAddons(aTypes) {
    // If we already have the database loaded, returning full info is fast.
    if (this.isDBLoaded) {
      let addons = await this.getAddonsByTypes(aTypes);
      return {
        addons: addons.filter(addon => addon.isActive),
        fullData: true,
      };
    }

    let result = [];
    for (let addon of XPIStates.enabledAddons()) {
      if (aTypes && !aTypes.includes(addon.type)) {
        continue;
      }
      let { scope, isSystem } = addon.location;
      result.push({
        id: addon.id,
        version: addon.version,
        type: addon.type,
        updateDate: addon.lastModifiedTime,
        scope,
        isSystem,
        isWebExtension: addon.isWebExtension,
      });
    }

    return { addons: result, fullData: false };
  },

  /*
   * Notified when a preference we're interested in has changed.
   *
   * @see nsIObserver
   */
  observe(aSubject, aTopic, aData) {
    switch (aTopic) {
      case NOTIFICATION_FLUSH_PERMISSIONS:
        if (!aData || aData == XPI_PERMISSION) {
          XPIExports.XPIDatabase.importPermissions();
        }
        break;

      case "nsPref:changed":
        switch (aData) {
          case PREF_XPI_SIGNATURES_REQUIRED:
          case PREF_LANGPACK_SIGNATURES:
            XPIExports.XPIDatabase.updateAddonAppDisabledStates();
            break;
        }
    }
  },

  uninstallSystemProfileAddon(aID) {
    let location = XPIStates.getLocation(KEY_APP_SYSTEM_PROFILE);
    return XPIExports.XPIInstall.uninstallAddonFromLocation(aID, location);
  },
};

for (let meth of [
  "getInstallForFile",
  "getInstallForURL",
  "getInstallsByTypes",
  "installTemporaryAddon",
  "installBuiltinAddon",
  "isInstallAllowed",
  "isInstallEnabled",
  "updateSystemAddons",
  "stageLangpacksForAppUpdate",
]) {
  XPIProvider[meth] = function () {
    return XPIExports.XPIInstall[meth](...arguments);
  };
}

for (let meth of [
  "addonChanged",
  "getAddonByID",
  "getAddonBySyncGUID",
  "updateAddonRepositoryData",
  "updateAddonAppDisabledStates",
]) {
  XPIProvider[meth] = function () {
    return XPIExports.XPIDatabase[meth](...arguments);
  };
}

export var XPIInternal = {
  BOOTSTRAP_REASONS,
  BootstrapScope,
  BuiltInLocation,
  DB_SCHEMA,
  DIR_STAGE,
  DIR_TRASH,
  KEY_APP_PROFILE,
  KEY_APP_SYSTEM_PROFILE,
  KEY_APP_SYSTEM_ADDONS,
  KEY_APP_SYSTEM_DEFAULTS,
  PREF_BRANCH_INSTALLED_ADDON,
  PREF_SYSTEM_ADDON_SET,
  SystemAddonLocation,
  TEMPORARY_ADDON_SUFFIX,
  TemporaryInstallLocation,
  XPIStates,
  XPI_PERMISSION,
  awaitPromise,
  canRunInSafeMode,
  getURIForResourceInFile,
  isXPI,
  iterDirectory,
  maybeResolveURI,
  migrateAddonLoader,
  resolveDBReady,

  // Used by tests to shut down AddonManager.
  overrideAsyncShutdown(mockAsyncShutdown) {
    lazy.AsyncShutdown = mockAsyncShutdown;
  },
};
PK
!<��Z��#modules/addons/crypto-utils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const CryptoHash = Components.Constructor(
  "@mozilla.org/security/hash;1",
  "nsICryptoHash",
  "initWithString"
);

const XPI_WEAK_SIGNATURES = [Ci.nsIAppSignatureInfo.PKCS7_WITH_SHA1];

export function hasStrongSignature(addon) {
  return !!addon.signedTypes?.filter(
    algorithm => !XPI_WEAK_SIGNATURES.includes(algorithm)
  ).length;
}

export function computeHashAsString(hashType, input) {
  const data = new Uint8Array(new TextEncoder().encode(input));
  const crypto = CryptoHash(hashType);
  crypto.update(data, data.length);
  return getHashStringForCrypto(crypto);
}

/**
 * Returns the string representation (hex) of the SHA256 hash of `input`.
 *
 * @param {string} input
 *        The value to hash.
 * @returns {string}
 *          The hex representation of a SHA256 hash.
 */
export function computeSha256HashAsString(input) {
  return computeHashAsString("sha256", input);
}

/**
 * Returns the string representation (hex) of the SHA1 hash of `input`.
 *
 * @param {string} input
 *        The value to hash.
 * @returns {string}
 *          The hex representation of a SHA1 hash.
 */
export function computeSha1HashAsString(input) {
  return computeHashAsString("sha1", input);
}

/**
 * Returns the string representation (hex) of a given CryptoHashInstance.
 *
 * @param {nsICryptoHash} aCrypto
 * @returns {string}
 *          The hex representation of a SHA256 hash.
 */
export function getHashStringForCrypto(aCrypto) {
  // return the two-digit hexadecimal code for a byte
  let toHexString = charCode => ("0" + charCode.toString(16)).slice(-2);

  // convert the binary hash data to a hex string.
  let binary = aCrypto.finish(/* base64 */ false);
  let hash = Array.from(binary, c => toHexString(c.charCodeAt(0)));
  return hash.join("").toLowerCase();
}
PK
!<#T#���,modules/addons/siteperms-addon-utils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

export const GATED_PERMISSIONS = ["midi", "midi-sysex"];
export const SITEPERMS_ADDON_PROVIDER_PREF =
  "dom.sitepermsaddon-provider.enabled";
export const SITEPERMS_ADDON_TYPE = "sitepermission";
export const SITEPERMS_ADDON_BLOCKEDLIST_PREF =
  "dom.sitepermsaddon-provider.separatedBlocklistedDomains";

const lazy = {};
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "blocklistedOriginsSet",
  SITEPERMS_ADDON_BLOCKEDLIST_PREF,
  // Default value
  "",
  // onUpdate
  null,
  // transform
  prefValue => new Set(prefValue.split(","))
);

/**
 * @param {string} type
 * @returns {boolean}
 */
export function isGatedPermissionType(type) {
  return GATED_PERMISSIONS.includes(type);
}

/**
 * @param {string} siteOrigin
 * @returns {boolean}
 */
export function isKnownPublicSuffix(siteOrigin) {
  const { host } = new URL(siteOrigin);

  let isPublic = false;
  // getKnownPublicSuffixFromHost throws when passed an IP, in such case, assume
  // this is not a public etld.
  try {
    isPublic = Services.eTLD.getKnownPublicSuffixFromHost(host) == host;
  } catch (e) {}

  return isPublic;
}

/**
 * ⚠️ This should be only used for testing purpose ⚠️
 *
 * @param {Array<String>} permissionTypes
 * @throws if not called from xpcshell test
 */
export function addGatedPermissionTypesForXpcShellTests(permissionTypes) {
  if (!Services.env.exists("XPCSHELL_TEST_PROFILE_DIR")) {
    throw new Error("This should only be called from XPCShell tests");
  }

  GATED_PERMISSIONS.push(...permissionTypes);
}

/**
 * @param {nsIPrincipal} principal
 * @returns {Boolean}
 */
export function isPrincipalInSitePermissionsBlocklist(principal) {
  return lazy.blocklistedOriginsSet.has(principal.baseDomain);
}
PK
!<XyT��� modules/amContentHandler.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const XPI_CONTENT_TYPE = "application/x-xpinstall";
const MSG_INSTALL_ADDON = "WebInstallerInstallAddonFromWebpage";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetters(lazy, {
  ThirdPartyUtil: ["@mozilla.org/thirdpartyutil;1", "mozIThirdPartyUtil"],
});

export function amContentHandler() {}

amContentHandler.prototype = {
  /**
   * Handles a new request for an application/x-xpinstall file.
   *
   * @param  aMimetype
   *         The mimetype of the file
   * @param  aContext
   *         The context passed to nsIChannel.asyncOpen
   * @param  aRequest
   *         The nsIRequest dealing with the content
   */
  handleContent(aMimetype, aContext, aRequest) {
    if (aMimetype != XPI_CONTENT_TYPE) {
      throw Components.Exception("", Cr.NS_ERROR_WONT_HANDLE_CONTENT);
    }

    if (!(aRequest instanceof Ci.nsIChannel)) {
      throw Components.Exception("", Cr.NS_ERROR_WONT_HANDLE_CONTENT);
    }

    let uri = aRequest.URI;

    // This check will allow a link to an xpi clicked by the user to trigger the
    // addon install flow, but prevents window.open or window.location from triggering
    // an addon install even when called from inside a event listener triggered by
    // user input.
    if (
      !aRequest.loadInfo.hasValidUserGestureActivation &&
      Services.prefs.getBoolPref("xpinstall.userActivation.required", true)
    ) {
      const error = Components.Exception(
        `${uri.spec} install cancelled because of missing user gesture activation`,
        Cr.NS_ERROR_WONT_HANDLE_CONTENT
      );
      // Report the error in the BrowserConsole, the error thrown from here doesn't
      // seem to be visible anywhere.
      Cu.reportError(error);
      throw error;
    }

    aRequest.cancel(Cr.NS_BINDING_ABORTED);

    let { loadInfo } = aRequest;
    const { triggeringPrincipal } = loadInfo;

    let browsingContext = loadInfo.targetBrowsingContext;

    let sourceHost;
    let sourceURL;

    try {
      sourceURL =
        triggeringPrincipal.spec != "" ? triggeringPrincipal.spec : undefined;
      sourceHost = triggeringPrincipal.host;
    } catch (error) {
      // Ignore errors when retrieving the host for the principal (e.g. data URIs return
      // an NS_ERROR_FAILURE when principal.host is accessed).
    }

    let install = {
      uri: uri.spec,
      hash: null,
      name: null,
      icon: null,
      mimetype: XPI_CONTENT_TYPE,
      triggeringPrincipal,
      callbackID: -1,
      method: "link",
      sourceHost,
      sourceURL,
      browsingContext,
      hasCrossOriginAncestor: lazy.ThirdPartyUtil.isThirdPartyChannel(aRequest),
    };

    Services.cpmm.sendAsyncMessage(MSG_INSTALL_ADDON, install);
  },

  classID: Components.ID("{7beb3ba8-6ec3-41b4-b67c-da89b8518922}"),
  QueryInterface: ChromeUtils.generateQI(["nsIContentHandler"]),

  log(aMsg) {
    let msg = "amContentHandler.js: " + (aMsg.join ? aMsg.join("") : aMsg);
    Services.console.logStringMessage(msg);
    dump(msg + "\n");
  },
};
PK
!<
�P P  modules/amInstallTrigger.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Log } from "resource://gre/modules/Log.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetters(lazy, {
  ThirdPartyUtil: ["@mozilla.org/thirdpartyutil;1", "mozIThirdPartyUtil"],
});

const XPINSTALL_MIMETYPE = "application/x-xpinstall";

const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled";
const MSG_INSTALL_ADDON = "WebInstallerInstallAddonFromWebpage";
const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback";

const SUPPORTED_XPI_SCHEMES = ["http", "https"];

var log = Log.repository.getLogger("AddonManager.InstallTrigger");
log.level =
  Log.Level[
    Services.prefs.getBoolPref("extensions.logging.enabled", false)
      ? "Warn"
      : "Trace"
  ];

function CallbackObject(id, callback, mediator) {
  this.id = id;
  this.callback = callback;
  this.callCallback = function (url, status) {
    try {
      this.callback(url, status);
    } catch (e) {
      log.warn("InstallTrigger callback threw an exception: " + e);
    }

    mediator._callbacks.delete(id);
  };
}

function RemoteMediator(window) {
  this._windowID = window.windowGlobalChild.innerWindowId;

  this.mm = window.docShell.messageManager;
  this.mm.addWeakMessageListener(MSG_INSTALL_CALLBACK, this);

  this._lastCallbackID = 0;
  this._callbacks = new Map();
}

RemoteMediator.prototype = {
  receiveMessage(message) {
    if (message.name == MSG_INSTALL_CALLBACK) {
      let payload = message.data;
      let callbackHandler = this._callbacks.get(payload.callbackID);
      if (callbackHandler) {
        callbackHandler.callCallback(payload.url, payload.status);
      }
    }
  },

  enabled() {
    let params = {
      mimetype: XPINSTALL_MIMETYPE,
    };
    return this.mm.sendSyncMessage(MSG_INSTALL_ENABLED, params)[0];
  },

  install(install, principal, callback, window) {
    let callbackID = this._addCallback(callback);

    install.mimetype = XPINSTALL_MIMETYPE;
    install.triggeringPrincipal = principal;
    install.callbackID = callbackID;
    install.browsingContext = BrowsingContext.getFromWindow(window);

    return Services.cpmm.sendSyncMessage(MSG_INSTALL_ADDON, install)[0];
  },

  _addCallback(callback) {
    if (!callback || typeof callback != "function") {
      return -1;
    }

    let callbackID = this._windowID + "-" + ++this._lastCallbackID;
    let callbackObject = new CallbackObject(callbackID, callback, this);
    this._callbacks.set(callbackID, callbackObject);
    return callbackID;
  },

  QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]),
};

export function InstallTrigger() {}

InstallTrigger.prototype = {
  // We've declared ourselves as providing the nsIDOMGlobalPropertyInitializer
  // interface.  This means that when the InstallTrigger property is gotten from
  // the window that will createInstance this object and then call init(),
  // passing the window were bound to.  It will then automatically create the
  // WebIDL wrapper (InstallTriggerImpl) for this object.  This indirection is
  // necessary because webidl does not (yet) support statics (bug 863952). See
  // bug 926712 and then bug 1442360 for more details about this implementation.
  init(window) {
    this._window = window;
    this._principal = window.document.nodePrincipal;
    this._url = window.document.documentURIObject;

    this._mediator = new RemoteMediator(window);
    // If we can't set up IPC (e.g., because this is a top-level window or
    // something), then don't expose InstallTrigger.  The Window code handles
    // that, if we throw an exception here.
  },

  enabled() {
    return this._mediator.enabled(this._url.spec);
  },

  updateEnabled() {
    return this.enabled();
  },

  install(installs, callback) {
    if (Services.prefs.getBoolPref("xpinstall.userActivation.required", true)) {
      if (!this._window.windowUtils.isHandlingUserInput) {
        throw new this._window.Error(
          "InstallTrigger.install can only be called from a user input handler"
        );
      }
    }

    let keys = Object.keys(installs);
    if (keys.length > 1) {
      throw new this._window.Error("Only one XPI may be installed at a time");
    }

    let item = installs[keys[0]];

    if (typeof item === "string") {
      item = { URL: item };
    }
    if (!item.URL) {
      throw new this._window.Error(
        "Missing URL property for '" + keys[0] + "'"
      );
    }

    let url = this._resolveURL(item.URL);
    if (!this._checkLoadURIFromScript(url)) {
      throw new this._window.Error(
        "Insufficient permissions to install: " + url.spec
      );
    }

    if (!SUPPORTED_XPI_SCHEMES.includes(url.scheme)) {
      Cu.reportError(
        `InstallTrigger call disallowed on install url with unsupported scheme: ${JSON.stringify(
          {
            installPrincipal: this._principal.spec,
            installURL: url.spec,
          }
        )}`
      );
      throw new this._window.Error(`Unsupported scheme`);
    }

    let iconUrl = null;
    if (item.IconURL) {
      iconUrl = this._resolveURL(item.IconURL);
      if (!this._checkLoadURIFromScript(iconUrl)) {
        iconUrl = null; // If page can't load the icon, just ignore it
      }
    }

    const getTriggeringSource = () => {
      let url;
      let host;
      try {
        if (this._url?.schemeIs("http") || this._url?.schemeIs("https")) {
          url = this._url.spec;
          host = this._url.host;
        } else if (this._url?.schemeIs("blob")) {
          // For a blob url, we keep the blob url as the sourceURL and
          // we pick the related sourceHost from either the principal
          // or the precursorPrincipal (if the principal is a null principal
          // and the precursor one is defined).
          url = this._url.spec;
          host =
            this._principal.isNullPrincipal &&
            this._principal.precursorPrincipal
              ? this._principal.precursorPrincipal.host
              : this._principal.host;
        } else if (!this._principal.isNullPrincipal) {
          url = this._principal.exposableSpec;
          host = this._principal.host;
        } else if (this._principal.precursorPrincipal) {
          url = this._principal.precursorPrincipal.exposableSpec;
          host = this._principal.precursorPrincipal.host;
        } else {
          // Fallback to this._url as last resort.
          url = this._url.spec;
          host = this._url.host;
        }
      } catch (err) {
        Cu.reportError(err);
      }
      // Fallback to an empty string if url and host are still undefined.
      return {
        sourceURL: url || "",
        sourceHost: host || "",
      };
    };

    const { sourceHost, sourceURL } = getTriggeringSource();

    let installData = {
      uri: url.spec,
      hash: item.Hash || null,
      name: item.name,
      icon: iconUrl ? iconUrl.spec : null,
      method: "installTrigger",
      sourceHost,
      sourceURL,
      hasCrossOriginAncestor: lazy.ThirdPartyUtil.isThirdPartyWindow(
        this._window
      ),
    };

    return this._mediator.install(
      installData,
      this._principal,
      callback,
      this._window
    );
  },

  startSoftwareUpdate(url) {
    let filename = Services.io.newURI(url).QueryInterface(Ci.nsIURL).filename;
    let args = {};
    args[filename] = { URL: url };
    return this.install(args);
  },

  installChrome(type, url) {
    return this.startSoftwareUpdate(url);
  },

  _resolveURL(url) {
    return Services.io.newURI(url, null, this._url);
  },

  _checkLoadURIFromScript(uri) {
    let secman = Services.scriptSecurityManager;
    try {
      secman.checkLoadURIWithPrincipal(
        this._principal,
        uri,
        secman.DISALLOW_INHERIT_PRINCIPAL
      );
      return true;
    } catch (e) {
      return false;
    }
  },

  classID: Components.ID("{9df8ef2b-94da-45c9-ab9f-132eb55fddf1}"),
  contractID: "@mozilla.org/addons/installtrigger;1",
  QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
};
PK
!<Ѣ�Z�(�(modules/amManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This component serves as integration between the platform and AddonManager.
 * It is responsible for initializing and shutting down the AddonManager as well
 * as passing new installs from webpages to the AddonManager.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "separatePrivilegedMozillaWebContentProcess",
  "browser.tabs.remote.separatePrivilegedMozillaWebContentProcess",
  false
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "extensionsWebAPITesting",
  "extensions.webapi.testing",
  false
);

// The old XPInstall error codes
const EXECUTION_ERROR = -203;
const CANT_READ_ARCHIVE = -207;
const USER_CANCELLED = -210;
const DOWNLOAD_ERROR = -228;
const UNSUPPORTED_TYPE = -244;
const SUCCESS = 0;

const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled";
const MSG_INSTALL_ADDON = "WebInstallerInstallAddonFromWebpage";
const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback";

const MSG_PROMISE_REQUEST = "WebAPIPromiseRequest";
const MSG_PROMISE_RESULT = "WebAPIPromiseResult";
const MSG_INSTALL_EVENT = "WebAPIInstallEvent";
const MSG_INSTALL_CLEANUP = "WebAPICleanup";
const MSG_ADDON_EVENT_REQ = "WebAPIAddonEventRequest";
const MSG_ADDON_EVENT = "WebAPIAddonEvent";

var AddonManager, AddonManagerPrivate;

export function amManager() {
  ({ AddonManager, AddonManagerPrivate } = ChromeUtils.importESModule(
    "resource://gre/modules/AddonManager.sys.mjs"
  ));

  Services.mm.addMessageListener(MSG_INSTALL_ENABLED, this);
  Services.mm.addMessageListener(MSG_PROMISE_REQUEST, this);
  Services.mm.addMessageListener(MSG_INSTALL_CLEANUP, this);
  Services.mm.addMessageListener(MSG_ADDON_EVENT_REQ, this);

  Services.ppmm.addMessageListener(MSG_INSTALL_ADDON, this);

  Services.obs.addObserver(this, "message-manager-close");
  Services.obs.addObserver(this, "message-manager-disconnect");

  AddonManager.webAPI.setEventHandler(this.sendEvent);

  // Needed so receiveMessage can be called directly by JS callers
  this.wrappedJSObject = this;
}

amManager.prototype = {
  observe(aSubject, aTopic) {
    switch (aTopic) {
      case "addons-startup":
        AddonManagerPrivate.startup();
        break;

      case "message-manager-close":
      case "message-manager-disconnect":
        this.childClosed(aSubject);
        break;
    }
  },

  installAddonFromWebpage(aPayload, aBrowser, aCallback) {
    let retval = true;

    const { mimetype, triggeringPrincipal, hash, icon, name, uri } = aPayload;

    // NOTE: consider removing this call to isInstallAllowed from here, later it is going to be called
    // again from inside AddonManager.installAddonFromWebpage as part of the block/allow logic.
    //
    // The sole purpose of the call here seems to be "clearing the optional InstallTrigger callback",
    // which seems to be actually wrong if we are still proceeding to call getInstallForURL and the same
    // logic used to block the install flow using the exact same method call later on.
    if (!AddonManager.isInstallAllowed(mimetype, triggeringPrincipal)) {
      aCallback = null;
      retval = false;
    }

    let telemetryInfo = {
      source: AddonManager.getInstallSourceFromHost(aPayload.sourceHost),
      sourceURL: aPayload.sourceURL,
    };

    if ("method" in aPayload) {
      telemetryInfo.method = aPayload.method;
    }

    AddonManager.getInstallForURL(uri, {
      hash,
      name,
      icon,
      browser: aBrowser,
      triggeringPrincipal,
      telemetryInfo,
      sendCookies: true,
    }).then(aInstall => {
      function callCallback(status) {
        try {
          aCallback?.onInstallEnded(uri, status);
        } catch (e) {
          Cu.reportError(e);
        }
      }

      if (!aInstall) {
        callCallback(UNSUPPORTED_TYPE);
        return;
      }

      if (aCallback) {
        aInstall.addListener({
          onDownloadCancelled() {
            callCallback(USER_CANCELLED);
          },

          onDownloadFailed(aInstall) {
            if (aInstall.error == AddonManager.ERROR_CORRUPT_FILE) {
              callCallback(CANT_READ_ARCHIVE);
            } else {
              callCallback(DOWNLOAD_ERROR);
            }
          },

          onInstallFailed() {
            callCallback(EXECUTION_ERROR);
          },

          onInstallEnded() {
            callCallback(SUCCESS);
          },
        });
      }

      AddonManager.installAddonFromWebpage(
        mimetype,
        aBrowser,
        triggeringPrincipal,
        aInstall,
        {
          hasCrossOriginAncestor: aPayload.hasCrossOriginAncestor,
        }
      );
    });

    return retval;
  },

  notify() {
    AddonManagerPrivate.backgroundUpdateTimerHandler();
  },

  // Maps message manager instances for content processes to the associated
  // AddonListener instances.
  addonListeners: new Map(),

  _addAddonListener(target) {
    if (!this.addonListeners.has(target)) {
      let handler = (event, id) => {
        target.sendAsyncMessage(MSG_ADDON_EVENT, { event, id });
      };
      let listener = {
        onEnabling: addon => handler("onEnabling", addon.id),
        onEnabled: addon => handler("onEnabled", addon.id),
        onDisabling: addon => handler("onDisabling", addon.id),
        onDisabled: addon => handler("onDisabled", addon.id),
        onInstalling: addon => handler("onInstalling", addon.id),
        onInstalled: addon => handler("onInstalled", addon.id),
        onUninstalling: addon => handler("onUninstalling", addon.id),
        onUninstalled: addon => handler("onUninstalled", addon.id),
        onOperationCancelled: addon =>
          handler("onOperationCancelled", addon.id),
      };
      this.addonListeners.set(target, listener);
      AddonManager.addAddonListener(listener);
    }
  },

  _removeAddonListener(target) {
    if (this.addonListeners.has(target)) {
      AddonManager.removeAddonListener(this.addonListeners.get(target));
      this.addonListeners.delete(target);
    }
  },

  /**
   * messageManager callback function.
   *
   * Listens to requests from child processes for InstallTrigger
   * activity, and sends back callbacks.
   */
  receiveMessage(aMessage) {
    let payload = aMessage.data;

    switch (aMessage.name) {
      case MSG_INSTALL_ENABLED:
        return AddonManager.isInstallEnabled(payload.mimetype);

      case MSG_INSTALL_ADDON: {
        let browser = payload.browsingContext.top.embedderElement;

        let callback = null;
        if (payload.callbackID != -1) {
          let mm = browser.messageManager;
          callback = {
            onInstallEnded(url, status) {
              mm.sendAsyncMessage(MSG_INSTALL_CALLBACK, {
                callbackID: payload.callbackID,
                url,
                status,
              });
            },
          };
        }

        return this.installAddonFromWebpage(payload, browser, callback);
      }

      case MSG_PROMISE_REQUEST: {
        if (
          !lazy.extensionsWebAPITesting &&
          lazy.separatePrivilegedMozillaWebContentProcess &&
          aMessage.target &&
          aMessage.target.remoteType != null &&
          aMessage.target.remoteType !== "privilegedmozilla"
        ) {
          return undefined;
        }

        let mm = aMessage.target.messageManager;
        let resolve = value => {
          mm.sendAsyncMessage(MSG_PROMISE_RESULT, {
            callbackID: payload.callbackID,
            resolve: value,
          });
        };
        let reject = value => {
          mm.sendAsyncMessage(MSG_PROMISE_RESULT, {
            callbackID: payload.callbackID,
            reject: value,
          });
        };

        let API = AddonManager.webAPI;
        if (payload.type in API) {
          API[payload.type](aMessage.target, ...payload.args).then(
            resolve,
            reject
          );
        } else {
          reject("Unknown Add-on API request.");
        }
        break;
      }

      case MSG_INSTALL_CLEANUP: {
        if (
          !lazy.extensionsWebAPITesting &&
          lazy.separatePrivilegedMozillaWebContentProcess &&
          aMessage.target &&
          aMessage.target.remoteType != null &&
          aMessage.target.remoteType !== "privilegedmozilla"
        ) {
          return undefined;
        }

        AddonManager.webAPI.clearInstalls(payload.ids);
        break;
      }

      case MSG_ADDON_EVENT_REQ: {
        if (
          !lazy.extensionsWebAPITesting &&
          lazy.separatePrivilegedMozillaWebContentProcess &&
          aMessage.target &&
          aMessage.target.remoteType != null &&
          aMessage.target.remoteType !== "privilegedmozilla"
        ) {
          return undefined;
        }

        let target = aMessage.target.messageManager;
        if (payload.enabled) {
          this._addAddonListener(target);
        } else {
          this._removeAddonListener(target);
        }
      }
    }
    return undefined;
  },

  childClosed(target) {
    AddonManager.webAPI.clearInstallsFrom(target);
    this._removeAddonListener(target);
  },

  sendEvent(mm, data) {
    mm.sendAsyncMessage(MSG_INSTALL_EVENT, data);
  },

  classID: Components.ID("{4399533d-08d1-458c-a87a-235f74451cfa}"),
  QueryInterface: ChromeUtils.generateQI([
    "amIAddonManager",
    "nsITimerCallback",
    "nsIObserver",
  ]),
};

const BLOCKLIST_SYS_MJS = "resource://gre/modules/Blocklist.sys.mjs";
ChromeUtils.defineESModuleGetters(lazy, { Blocklist: BLOCKLIST_SYS_MJS });

export function BlocklistService() {
  this.wrappedJSObject = this;
}

BlocklistService.prototype = {
  STATE_NOT_BLOCKED: Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
  STATE_SOFTBLOCKED: Ci.nsIBlocklistService.STATE_SOFTBLOCKED,
  STATE_BLOCKED: Ci.nsIBlocklistService.STATE_BLOCKED,

  get isLoaded() {
    return Cu.isESModuleLoaded(BLOCKLIST_SYS_MJS) && lazy.Blocklist.isLoaded;
  },

  observe(...args) {
    return lazy.Blocklist.observe(...args);
  },

  notify() {
    lazy.Blocklist.notify();
  },

  classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"),
  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsIBlocklistService",
    "nsITimerCallback",
  ]),
};
PK
!<eI�[ [ modules/amWebAPI.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const MSG_PROMISE_REQUEST = "WebAPIPromiseRequest";
const MSG_PROMISE_RESULT = "WebAPIPromiseResult";
const MSG_INSTALL_EVENT = "WebAPIInstallEvent";
const MSG_INSTALL_CLEANUP = "WebAPICleanup";
const MSG_ADDON_EVENT_REQ = "WebAPIAddonEventRequest";
const MSG_ADDON_EVENT = "WebAPIAddonEvent";

class APIBroker {
  constructor(mm) {
    this.mm = mm;

    this._promises = new Map();

    // _installMap maps integer ids to DOM AddonInstall instances
    this._installMap = new Map();

    this.mm.addMessageListener(MSG_PROMISE_RESULT, this);
    this.mm.addMessageListener(MSG_INSTALL_EVENT, this);

    this._eventListener = null;
  }

  receiveMessage(message) {
    let payload = message.data;

    switch (message.name) {
      case MSG_PROMISE_RESULT: {
        if (!this._promises.has(payload.callbackID)) {
          return;
        }

        let resolve = this._promises.get(payload.callbackID);
        this._promises.delete(payload.callbackID);
        resolve(payload);
        break;
      }

      case MSG_INSTALL_EVENT: {
        let install = this._installMap.get(payload.id);
        if (!install) {
          let err = new Error(
            `Got install event for unknown install ${payload.id}`
          );
          Cu.reportError(err);
          return;
        }
        install._dispatch(payload);
        break;
      }

      case MSG_ADDON_EVENT: {
        if (this._eventListener) {
          this._eventListener(payload);
        }
      }
    }
  }

  sendRequest(type, ...args) {
    return new Promise(resolve => {
      let callbackID = APIBroker._nextID++;

      this._promises.set(callbackID, resolve);
      this.mm.sendAsyncMessage(MSG_PROMISE_REQUEST, { type, callbackID, args });
    });
  }

  setAddonListener(callback) {
    this._eventListener = callback;
    if (callback) {
      this.mm.addMessageListener(MSG_ADDON_EVENT, this);
      this.mm.sendAsyncMessage(MSG_ADDON_EVENT_REQ, { enabled: true });
    } else {
      this.mm.removeMessageListener(MSG_ADDON_EVENT, this);
      this.mm.sendAsyncMessage(MSG_ADDON_EVENT_REQ, { enabled: false });
    }
  }

  sendCleanup(ids) {
    this.setAddonListener(null);
    this.mm.sendAsyncMessage(MSG_INSTALL_CLEANUP, { ids });
  }
}

APIBroker._nextID = 0;

// Base class for building classes to back content-exposed interfaces.
class APIObject {
  init(window, broker, properties) {
    this.window = window;
    this.broker = broker;

    // Copy any provided properties onto this object, webidl bindings
    // will only expose to content what should be exposed.
    for (let key of Object.keys(properties)) {
      this[key] = properties[key];
    }
  }

  /**
   * Helper to implement an asychronous method visible to content, where
   * the method is implemented by sending a message to the parent process
   * and then wrapping the returned object or error in an appropriate object.
   * This helper method ensures that:
   *  - Returned Promise objects are from the content window
   *  - Rejected Promises have Error objects from the content window
   *  - Only non-internal errors are exposed to the caller
   *
   * @param {string} apiRequest The command to invoke in the parent process.
   * @param {array<cloneable>} apiArgs The arguments to include with the
   *                                   request to the parent process.
   * @param {function} resultConvert If provided, a function called with the
   *                                 result from the parent process as an
   *                                 argument.  Used to convert the result
   *                                 into something appropriate for content.
   * @returns {Promise<any>} A Promise suitable for passing directly to content.
   */
  _apiTask(apiRequest, apiArgs, resultConverter) {
    let win = this.window;
    let broker = this.broker;
    return new win.Promise((resolve, reject) => {
      (async function () {
        let result = await broker.sendRequest(apiRequest, ...apiArgs);
        if ("reject" in result) {
          let err = new win.Error(result.reject.message);
          // We don't currently put any other properties onto Errors
          // generated by mozAddonManager.  If/when we do, they will
          // need to get copied here.
          reject(err);
          return;
        }

        let obj = result.resolve;
        if (resultConverter) {
          obj = resultConverter(obj);
        }
        resolve(obj);
      })().catch(err => {
        Cu.reportError(err);
        reject(new win.Error("Unexpected internal error"));
      });
    });
  }
}

class Addon extends APIObject {
  constructor(...args) {
    super();
    this.init(...args);
  }

  uninstall() {
    return this._apiTask("addonUninstall", [this.id]);
  }

  setEnabled(value) {
    return this._apiTask("addonSetEnabled", [this.id, value]);
  }
}

class AddonInstall extends APIObject {
  constructor(window, broker, properties) {
    super();
    this.init(window, broker, properties);

    broker._installMap.set(properties.id, this);
  }

  _dispatch(data) {
    // The message for the event includes updated copies of all install
    // properties.  Use the usual "let webidl filter visible properties" trick.
    for (let key of Object.keys(data)) {
      this[key] = data[key];
    }

    let event = new this.window.Event(data.event);
    this.__DOM_IMPL__.dispatchEvent(event);
  }

  install() {
    return this._apiTask("addonInstallDoInstall", [this.id]);
  }

  cancel() {
    return this._apiTask("addonInstallCancel", [this.id]);
  }
}

export class WebAPI extends APIObject {
  constructor() {
    super();
    this.allInstalls = [];
    this.listenerCount = 0;
  }

  init(window) {
    let mm = window.docShell.messageManager;
    let broker = new APIBroker(mm);

    super.init(window, broker, {});

    window.addEventListener("unload", () => {
      this.broker.sendCleanup(this.allInstalls);
    });
  }

  getAddonByID(id) {
    return this._apiTask("getAddonByID", [id], addonInfo => {
      if (!addonInfo) {
        return null;
      }
      let addon = new Addon(this.window, this.broker, addonInfo);
      return this.window.Addon._create(this.window, addon);
    });
  }

  createInstall(options) {
    if (!Services.prefs.getBoolPref("xpinstall.enabled", true)) {
      throw new this.window.Error("Software installation is disabled.");
    }

    const triggeringPrincipal = this.window.document.nodePrincipal;

    let installOptions = {
      ...options,
      triggeringPrincipal,
      // Provide the host from which the amWebAPI is being called
      // (so that we can detect if the API is being used from the disco pane,
      // AMO, testpilot or another unknown webpage).
      sourceHost: this.window.location?.host,
      sourceURL: this.window.location?.href,
    };
    return this._apiTask("createInstall", [installOptions], installInfo => {
      if (!installInfo) {
        return null;
      }
      let install = new AddonInstall(this.window, this.broker, installInfo);
      this.allInstalls.push(installInfo.id);
      return this.window.AddonInstall._create(this.window, install);
    });
  }

  sendAbuseReport(addonId, data, options) {
    return this._apiTask(
      "sendAbuseReport",
      [addonId, data, options],
      result => {
        // The result below is a JS object coming from the expected AMO API
        // endpoint response in JSON format.
        return Cu.cloneInto(result, this.window);
      }
    );
  }

  eventListenerAdded() {
    if (this.listenerCount == 0) {
      this.broker.setAddonListener(data => {
        let event = new this.window.AddonEvent(data.event, data);
        this.__DOM_IMPL__.dispatchEvent(event);
      });
    }
    this.listenerCount++;
  }

  eventListenerRemoved() {
    this.listenerCount--;
    if (this.listenerCount == 0) {
      this.broker.setAddonListener(null);
    }
  }
}

WebAPI.prototype.QueryInterface = ChromeUtils.generateQI([
  "nsIDOMGlobalPropertyInitializer",
]);
WebAPI.prototype.classID = Components.ID(
  "{8866d8e3-4ea5-48b7-a891-13ba0ac15235}"
);
PK
!<5R:�~~8modules/backgroundtasks/BackgroundTask_exception.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export async function runBackgroundTask() {
  console.error("runBackgroundTask: exception");

  throw new Error("test");
}
PK
!<F��mm6modules/backgroundtasks/BackgroundTask_failure.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export async function runBackgroundTask() {
  console.error("runBackgroundTask: failure");

  return 1;
}
PK
!<��b�+�+6modules/backgroundtasks/BackgroundTask_message.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Invoke this task like `firefox.exe --backgroundtask message ...`.
//
// This task is complicated because it's meant for manual testing by QA but also
// for automated testing.  We might split these two functions at some point.
//
// First, note that the task takes significant configuration from the command
// line.  This is different than the eventual home for this functionality, the
// background update task, which will take this configuration from the default
// browsing profile.
//
// This task accepts the following command line arguments:
//
// --debug: enable very verbose debug logging.  Note that the `MOZ_LOG`
// environment variables still apply.
//
// --stage: use stage Remote Settings
//   (`https://firefox.settings.services.allizom.org/v1`) rather than production
//   (`https://firefox.settings.services.mozilla.com/v1`)
//
// --preview: enable Remote Settings and Experiment previews.
//
// `--url about:studies?...` (as copy-pasted from Experimenter): opt in to
//   `optin_branch` of experiment with `optin_slug` from `optin_collection`.
//
// `--url file:///path/to/recipe.json?optin_branch=...` (as downloaded from
//   Experimenter): opt in to `optin_branch` of given experiment recipe.
//
// `--experiments file:///path/to/recipe.json` (as downloaded from
//   Experimenter): enable given experiment recipe, possibly enrolling into a
//   branch via regular bucketing.
//
// `--targeting-snapshot /path/to/targeting.snapshot.json`: take default profile
//   targeting information from given JSON file.
//
// `--reset-storage`: clear Activity Stream storage, including lifetime limit
//   caps.
//
// The following flags are intended for automated testing.
//
// --sentinel: bracket important output with given sentinel for easy parsing.
// --randomizationId: set Nimbus/Normandy randomization ID for deterministic bucketing.
// --disable-alerts-service: do not show system/OS-level alerts.
// --no-experiments: don't talk to Remote Settings server at all.
// --no-datareporting: set `datareporting.healthreport.uploadEnabled=false` in
//   the background task profile.
// --no-optoutstudies: set `app.shield.optoutstudies.enabled=false` in the
//   background task profile.

import { EXIT_CODE } from "resource://gre/modules/BackgroundTasksManager.sys.mjs";
// eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
import { ASRouter } from "resource:///modules/asrouter/ASRouter.sys.mjs";
import { BackgroundTasksUtils } from "resource://gre/modules/BackgroundTasksUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ClientEnvironmentBase:
    "resource://gre/modules/components-utils/ClientEnvironment.sys.mjs",
  ExtensionUtils: "resource://gre/modules/ExtensionUtils.sys.mjs",
  IndexedDB: "resource://gre/modules/IndexedDB.sys.mjs",
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
  RemoteSettingsClient:
    "resource://services-settings/RemoteSettingsClient.sys.mjs",
  // eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
  ToastNotification: "resource:///modules/asrouter/ToastNotification.sys.mjs",
  Utils: "resource://services-settings/Utils.sys.mjs",
});

const SERVER_STAGE = "https://firefox.settings.services.allizom.org/v1";

// Default profile targeting snapshot.
let defaultProfileTargetingSnapshot = {};

// Bracket important output with given sentinel for easy parsing.
let outputInfo;
outputInfo = (sentinel, info) => {
  dump(`${sentinel}${JSON.stringify(info)}${sentinel}\n`);
};

function monkeyPatchRemoteSettingsClient({ data = [] }) {
  lazy.RemoteSettingsClient.prototype.get = async (options = {}) => {
    outputInfo({ "RemoteSettingsClient.get": { options, response: { data } } });
    return data;
  };
}

async function handleCommandLine(commandLine) {
  const CASE_INSENSITIVE = false;

  // Output data over stdout for tests to consume and inspect.
  let sentinel = commandLine.handleFlagWithParam("sentinel", CASE_INSENSITIVE);
  outputInfo = outputInfo.bind(null, sentinel || "");

  // We always want `nimbus.debug=true` for `about:studies?...` URLs.
  Services.prefs.setBoolPref("nimbus.debug", true);

  // Maybe drive up logging for this testing task.
  Services.prefs.clearUserPref("services.settings.preview_enabled");
  Services.prefs.clearUserPref(
    "browser.newtabpage.activity-stream.asrouter.debugLogLevel"
  );
  Services.prefs.clearUserPref("messaging-system.log");
  Services.prefs.clearUserPref("services.settings.loglevel");
  Services.prefs.clearUserPref("toolkit.backgroundtasks.loglevel");
  if (commandLine.handleFlag("debug", CASE_INSENSITIVE)) {
    console.log("Saw --debug, making logging verbose");
    Services.prefs.setBoolPref("services.settings.preview_enabled", true);
    Services.prefs.setCharPref(
      "browser.newtabpage.activity-stream.asrouter.debugLogLevel",
      "debug"
    );
    Services.prefs.setCharPref("messaging-system.log", "debug");
    Services.prefs.setCharPref("services.settings.loglevel", "debug");
    Services.prefs.setCharPref("toolkit.backgroundtasks.loglevel", "debug");
  }

  // Always make alert service display message when showing an alert.
  // Optionally suppress actually showing OS-level alerts.
  let origAlertsService = lazy.ToastNotification.AlertsService;
  let disableAlertsService = commandLine.handleFlag(
    "disable-alerts-service",
    CASE_INSENSITIVE
  );
  if (disableAlertsService) {
    console.log("Saw --disable-alerts-service, not showing any alerts");
  }
  // Remove getter so that we can redefine property.
  delete lazy.ToastNotification.AlertsService;
  lazy.ToastNotification.AlertsService = {
    showAlert(...args) {
      outputInfo({ showAlert: { args } });
      if (!disableAlertsService) {
        origAlertsService.showAlert(...args);
      }
    },
  };

  let targetingSnapshotPath = commandLine.handleFlagWithParam(
    "targeting-snapshot",
    CASE_INSENSITIVE
  );
  if (targetingSnapshotPath) {
    defaultProfileTargetingSnapshot = await IOUtils.readJSON(
      targetingSnapshotPath
    );
    console.log(
      `Saw --targeting-snapshot, read snapshot from ${targetingSnapshotPath}`
    );
  }
  outputInfo({ defaultProfileTargetingSnapshot });

  lazy.RemoteSettings.enablePreviewMode(false);
  Services.prefs.clearUserPref(
    "messaging-system.rsexperimentloader.collection_id"
  );
  if (commandLine.handleFlag("preview", CASE_INSENSITIVE)) {
    console.log(
      `Saw --preview, invoking 'RemoteSettings.enablePreviewMode(true)' and ` +
        `setting 'messaging-system.rsexperimentloader.collection_id=\"nimbus-preview\"'`
    );
    lazy.RemoteSettings.enablePreviewMode(true);
    Services.prefs.setCharPref(
      "messaging-system.rsexperimentloader.collection_id",
      "nimbus-preview"
    );
  }

  Services.prefs.clearUserPref("services.settings.server");
  Services.prefs.clearUserPref("services.settings.load_dump");
  if (commandLine.handleFlag("stage", CASE_INSENSITIVE)) {
    console.log(
      `Saw --stage, setting 'services.settings.server="${SERVER_STAGE}"'`
    );
    Services.prefs.setCharPref("services.settings.server", SERVER_STAGE);
    Services.prefs.setBoolPref("services.settings.load_dump", false);

    if (lazy.Utils.SERVER_URL !== SERVER_STAGE) {
      throw new Error(
        "Pref services.settings.server ignored!" +
          "remember to set MOZ_REMOTE_SETTINGS_DEVTOOLS=1 in beta and release."
      );
    }
  }

  // Allow to override Nimbus randomization ID with `--randomizationId ...`.
  let randomizationId = commandLine.handleFlagWithParam(
    "randomizationId",
    CASE_INSENSITIVE
  );
  if (randomizationId) {
    console.log(`Saw --randomizationId: ${randomizationId}`);
    Services.prefs.setCharPref("app.normandy.user_id", randomizationId);
  }
  outputInfo({ randomizationId: lazy.ClientEnvironmentBase.randomizationId });

  // Allow to override Nimbus experiments with `--experiments /path/to/data.json`.
  let experiments = commandLine.handleFlagWithParam(
    "experiments",
    CASE_INSENSITIVE
  );
  if (experiments) {
    let experimentsPath = commandLine.resolveFile(experiments).path;
    let data = await IOUtils.readJSON(experimentsPath);
    if (!Array.isArray(data)) {
      if (data.permissions) {
        data = data.data;
      }
      data = [data];
    }

    monkeyPatchRemoteSettingsClient({ data });

    console.log(`Saw --experiments, read recipes from ${experimentsPath}`);
  }

  // Allow to turn off querying Remote Settings entirely, for tests.
  if (
    !experiments &&
    commandLine.handleFlag("no-experiments", CASE_INSENSITIVE)
  ) {
    monkeyPatchRemoteSettingsClient({ data: [] });

    console.log(`Saw --no-experiments, returning [] recipes`);
  }

  // Allow to test various red buttons.
  Services.prefs.clearUserPref("datareporting.healthreport.uploadEnabled");
  if (commandLine.handleFlag("no-datareporting", CASE_INSENSITIVE)) {
    Services.prefs.setBoolPref(
      "datareporting.healthreport.uploadEnabled",
      false
    );
    console.log(
      `Saw --no-datareporting, set 'datareporting.healthreport.uploadEnabled=false'`
    );
  }

  Services.prefs.clearUserPref("app.shield.optoutstudies.enabled");
  if (commandLine.handleFlag("no-optoutstudies", CASE_INSENSITIVE)) {
    Services.prefs.setBoolPref("app.shield.optoutstudies.enabled", false);
    console.log(
      `Saw --no-datareporting, set 'app.shield.optoutstudies.enabled=false'`
    );
  }

  outputInfo({
    taskProfilePrefs: {
      "app.shield.optoutstudies.enabled": Services.prefs.getBoolPref(
        "app.shield.optoutstudies.enabled"
      ),
      "datareporting.healthreport.uploadEnabled": Services.prefs.getBoolPref(
        "datareporting.healthreport.uploadEnabled"
      ),
    },
  });

  if (commandLine.handleFlag("reset-storage", CASE_INSENSITIVE)) {
    console.log("Saw --reset-storage, deleting database 'ActivityStream'");
    console.log(
      `To hard reset, remove the contents of '${PathUtils.profileDir}'`
    );
    await lazy.IndexedDB.deleteDatabase("ActivityStream");
  }
}

export async function runBackgroundTask(commandLine) {
  console.error("runBackgroundTask: message");

  // Most of the task is arranging configuration.
  await handleCommandLine(commandLine);

  // Here's where we actually start Nimbus and the Firefox Messaging
  // System.
  await BackgroundTasksUtils.enableNimbus(
    commandLine,
    defaultProfileTargetingSnapshot.environment
  );

  await BackgroundTasksUtils.enableFirefoxMessagingSystem(
    defaultProfileTargetingSnapshot.environment
  );

  // At the time of writing, toast notifications are torn down as the
  // process exits.  Give the user a chance to see the notification.
  await lazy.ExtensionUtils.promiseTimeout(1000);

  // Everything in `ASRouter` is asynchronous, so we need to give everything a
  // chance to complete.
  outputInfo({ ASRouterState: ASRouter.state });

  return EXIT_CODE.SUCCESS;
}
PK
!<�S���9modules/backgroundtasks/BackgroundTask_pingsender.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { sendStandalonePing } from "resource://gre/modules/TelemetrySend.sys.mjs";

export async function runBackgroundTask(commandLine) {
  let sends = [];
  for (let i = 0; i < commandLine.length; i += 2) {
    sends.push(
      sendPing(commandLine.getArgument(i), commandLine.getArgument(i + 1))
    );
  }

  return Promise.all(sends);
}

// The standalone pingsender utility had an allowlist of endpoints, which was
// added to prevent it from being used as a generic exfiltration utility by
// unrelated malware running on the same system. It's unclear whether a gecko-
// based pingsender would be similarly desirable for that use-case, but it's
// easy enough to implement an allowlist here as well.
const ALLOWED_ENDPOINTS = ["localhost", "incoming.telemetry.mozilla.org"];

async function sendPing(endpoint, path) {
  console.log(`pingsender: sending ${path} to ${endpoint}`);

  let hostname = new URL(endpoint).hostname;
  if (!ALLOWED_ENDPOINTS.includes(hostname)) {
    throw new Error(`pingsender: Endpoint ${endpoint} is not allowed`);
  }

  let json = await IOUtils.readUTF8(path);
  await sendStandalonePing(endpoint, json, {
    "User-Agent": "pingsender/2.0",
    "X-PingSender-Version": "2.0",
  });

  return IOUtils.remove(path);
}
PK
!<��:5�(�(>modules/backgroundtasks/BackgroundTask_removeDirectory.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { EXIT_CODE } from "resource://gre/modules/BackgroundTasksManager.sys.mjs";

class Metrics {
  /**
   * @param {string} metricsId
   */
  constructor(metricsId) {
    this.metricsId = metricsId;
    this.startedTime = new Date();

    this.wasFirst = true;
    this.retryCount = 0;
    this.removalCountObj = { value: 0 };
    this.succeeded = true;

    this.suffixRemovalCountObj = { value: 0 };
    this.suffixEverFailed = false;
  }

  async report() {
    if (!this.metricsId) {
      console.warn(`Skipping Glean as no metrics id is passed`);
      return;
    }
    if (AppConstants.MOZ_APP_NAME !== "firefox") {
      console.warn(
        `Skipping Glean as the app is not Firefox: ${AppConstants.MOZ_APP_NAME}`
      );
      return;
    }

    const elapsedMs = new Date().valueOf() - this.startedTime.valueOf();

    // Note(krosylight): This FOG initialization happens within a unique
    // temporary directory created for each background task, which will
    // be removed after each run.
    // That means any failed submission will be lost, but we are fine with
    // that as we only have a single submission.
    Services.fog.initializeFOG(undefined, "firefox.desktop.background.tasks");

    const gleanMetrics = Glean[`backgroundTasksRmdir${this.metricsId}`];
    if (!gleanMetrics) {
      throw new Error(
        `The metrics id "${this.metricsId}" is not available in toolkit/components/backgroundtasks/metrics.yaml. ` +
          `Make sure that the id has no typo and is in PascalCase. ` +
          `Note that you can omit the id for testing.`
      );
    }

    gleanMetrics.elapsedMs.set(elapsedMs);
    gleanMetrics.wasFirst.set(this.wasFirst);
    gleanMetrics.retryCount.set(this.retryCount);
    gleanMetrics.removalCount.set(this.removalCountObj.value);
    gleanMetrics.succeeded.set(this.succeeded);
    gleanMetrics.suffixRemovalCount.set(this.suffixRemovalCountObj.value);
    gleanMetrics.suffixEverFailed.set(this.suffixEverFailed);

    GleanPings.backgroundTasks.submit();

    // XXX: We wait for arbitrary time for Glean to submit telemetry.
    // Bug 1790702 should add a better way.
    console.error("Pinged glean, waiting for submission.");
    await new Promise(resolve => lazy.setTimeout(resolve, 5000));
  }
}

// Recursively removes a directory.
// Returns true if it succeeds, false otherwise.
function tryRemoveDir(aFile, countObj) {
  try {
    aFile.remove(true, countObj);
  } catch (e) {
    return false;
  }

  return true;
}

const FILE_CHECK_ITERATION_TIMEOUT_MS = 1000;

function cleanupDirLockFile(aLock, aProfileName) {
  let lockFile = aLock.getLockFile(aProfileName);
  try {
    // Try to clean up the lock file
    lockFile.remove(false);
  } catch (ex) {}
}

async function deleteChildDirectory(
  parentDirPath,
  childDirName,
  secondsToWait,
  metrics
) {
  if (!childDirName || !childDirName.length) {
    return;
  }

  let targetFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
  targetFile.initWithPath(parentDirPath);
  targetFile.append(childDirName);

  // We create the lock before the file is actually there so this task
  // is the first one to acquire the lock. Otherwise a different task
  // could be cleaning suffixes and start deleting the folder while this
  // task is waiting for it to show up.
  let dirLock = Cc["@mozilla.org/net/CachePurgeLock;1"].createInstance(
    Ci.nsICachePurgeLock
  );

  let locked = false;
  try {
    dirLock.lock(childDirName);
    locked = true;
    metrics.wasFirst = !dirLock.isOtherInstanceRunning();
  } catch (e) {
    console.error("Failed to check dirLock");
  }

  if (!metrics.wasFirst) {
    if (locked) {
      dirLock.unlock();
      locked = false;
    }
    console.error("Another instance is already purging this directory");
    return;
  }

  // This backgroundtask process is spawned by the call to
  // PR_CreateProcessDetached in CacheFileIOManager::SyncRemoveAllCacheFiles
  // Only if spawning the process is successful is the cache folder renamed,
  // so we need to wait until that is done.
  while (!targetFile.exists()) {
    if (
      metrics.retryCount * FILE_CHECK_ITERATION_TIMEOUT_MS >
      secondsToWait * 1000
    ) {
      // We don't know for sure if the folder was renamed or if a different
      // task removed it already. The second variant is more likely but to
      // be sure we'd have to consult a log file, which introduces
      // more complexity.
      console.error(`couldn't find cache folder ${targetFile.path}`);
      if (locked) {
        dirLock.unlock();
        locked = false;
      }
      return;
    }
    await new Promise(resolve =>
      lazy.setTimeout(resolve, FILE_CHECK_ITERATION_TIMEOUT_MS)
    );
    metrics.retryCount++;
    console.error(`Cache folder attempt no ${metrics.retryCount}`);
  }

  if (!targetFile.isDirectory()) {
    if (locked) {
      dirLock.unlock();
      locked = false;
    }
    throw new Error("Path was not a directory");
  }

  console.error(`started removing ${targetFile.path}`);
  try {
    targetFile.remove(true, metrics.removalCountObj);
  } catch (err) {
    console.error(
      `failed removing ${targetFile.path}. removed ${metrics.removalCountObj.value} entries.`
    );
    throw err;
  } finally {
    console.error(
      `done removing ${targetFile.path}. removed ${metrics.removalCountObj.value} entries.`
    );
    if (locked) {
      dirLock.unlock();
      locked = false;
      cleanupDirLockFile(dirLock, childDirName);
    }
  }
}

async function cleanupOtherDirectories(
  parentDirPath,
  otherFoldersSuffix,
  metrics
) {
  if (!otherFoldersSuffix || !otherFoldersSuffix.length) {
    return;
  }

  let targetFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
  targetFile.initWithPath(parentDirPath);

  let entries = targetFile.directoryEntries;
  while (entries.hasMoreElements()) {
    let entry = entries.nextFile;

    if (
      otherFoldersSuffix !== "*" &&
      !entry.leafName.endsWith(otherFoldersSuffix)
    ) {
      continue;
    }

    let shouldProcessEntry = false;
    // The folder could already be gone, so isDirectory could throw
    try {
      shouldProcessEntry = entry.isDirectory();
    } catch (e) {}

    if (!shouldProcessEntry) {
      continue;
    }

    let dirLock = Cc["@mozilla.org/net/CachePurgeLock;1"].createInstance(
      Ci.nsICachePurgeLock
    );
    let wasFirst = false;

    try {
      dirLock.lock(entry.leafName);
      wasFirst = !dirLock.isOtherInstanceRunning();
    } catch (e) {
      console.error("Failed to check dirlock. Skipping folder");
      dirLock.unlock();
      continue;
    }

    if (!wasFirst) {
      dirLock.unlock();
      continue;
    }

    // Remove directory recursively.
    let removedDir = tryRemoveDir(entry, metrics.suffixRemovalCountObj);
    if (!removedDir && entry.exists()) {
      // If first deletion of the directory failed, then we try again once more
      // just in case.
      metrics.suffixEverFailed = true;
      removedDir = tryRemoveDir(entry, metrics.suffixRemovalCountObj);
    }
    console.error(
      `Deletion of folder ${entry.leafName} - success=${removedDir}`
    );
    dirLock.unlock();
    cleanupDirLockFile(dirLock, entry.leafName);
  }
}

// Usage:
// removeDirectory parentDirPath childDirName secondsToWait [otherFoldersSuffix]
//                  arg0           arg1     arg2            arg3
//                 [--test-sleep testSleep]
//                 [--metrics-id metricsId]
// parentDirPath - The path to the parent directory that includes the target directory
// childDirName - The "leaf name" of the moved cache directory
//                If empty, the background task will only purge folders that have the "otherFoldersSuffix".
// secondsToWait - String representing the number of seconds to wait for the cacheDir to be moved
// otherFoldersSuffix - [optional] The suffix of directories that should be removed
//                      When not empty, this task will also attempt to remove all directories in
//                      the parent dir that end with this suffix
//                      As a special command, "*" will remove all subdirectories.
// testSleep - [optional] A test-only argument to sleep for a given milliseconds before removal.
//             This exists to test whether a long-running task can survive.
// metricsId - [optional] The identifier for Glean metrics, in PascalCase.
//             It'll be submitted only when the matching identifier exists in
//             toolkit/components/backgroundtasks/metrics.yaml.
export async function runBackgroundTask(commandLine) {
  const testSleep = Number.parseInt(
    commandLine.handleFlagWithParam("test-sleep", false)
  );
  const metricsId = commandLine.handleFlagWithParam("metrics-id", false) || "";

  if (commandLine.length < 3) {
    throw new Error("Insufficient arguments");
  }

  const parentDirPath = commandLine.getArgument(0);
  const childDirName = commandLine.getArgument(1);
  let secondsToWait = parseInt(commandLine.getArgument(2));
  if (isNaN(secondsToWait)) {
    secondsToWait = 10;
  }
  commandLine.removeArguments(0, 2);

  let otherFoldersSuffix = "";
  if (commandLine.length) {
    otherFoldersSuffix = commandLine.getArgument(0);
    commandLine.removeArguments(0, 0);
  }

  if (commandLine.length) {
    throw new Error(
      `${commandLine.length} unknown command args exist, closing.`
    );
  }

  console.error(
    parentDirPath,
    childDirName,
    secondsToWait,
    otherFoldersSuffix,
    metricsId
  );

  if (!Number.isNaN(testSleep)) {
    await new Promise(resolve => lazy.setTimeout(resolve, testSleep));
  }

  const metrics = new Metrics(metricsId);

  try {
    await deleteChildDirectory(
      parentDirPath,
      childDirName,
      secondsToWait,
      metrics
    );
    await cleanupOtherDirectories(parentDirPath, otherFoldersSuffix, metrics);
  } catch (err) {
    metrics.succeeded = false;
    throw err;
  } finally {
    await metrics.report();
  }

  return EXIT_CODE.SUCCESS;
}
PK
!<	�W���6modules/backgroundtasks/BackgroundTask_success.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { EXIT_CODE } from "resource://gre/modules/BackgroundTasksManager.sys.mjs";

export async function runBackgroundTask() {
  console.error("runBackgroundTask: success");

  return EXIT_CODE.SUCCESS;
}
PK
!<v&S���%modules/backgroundtasks/dbg-actors.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* globals require, exports */

"use strict";

const { DevToolsServer } = require("devtools/server/devtools-server");
const { RootActor } = require("devtools/server/actors/root");
const { BrowserTabList } = require("devtools/server/actors/webbrowser");
const { ProcessActorList } = require("devtools/server/actors/process");
const {
  ActorRegistry,
} = require("devtools/server/actors/utils/actor-registry");

/**
 * background-task specific actors.
 *
 */

/**
 * Construct a root actor appropriate for use in a server running a background task.
 */
function createRootActor(connection) {
  let parameters = {
    tabList: new BackgroundTaskTabList(connection),
    processList: new ProcessActorList(),
    globalActorFactories: ActorRegistry.globalActorFactories,
    onShutdown() {},
  };
  return new RootActor(connection, parameters);
}
exports.createRootActor = createRootActor;

/**
 * A "stub" TabList implementation that provides no tabs.
 */
class BackgroundTaskTabList extends BrowserTabList {
  getList() {
    return Promise.resolve([]);
  }
}
PK
!<$*CCC2modules/components-utils/ClientEnvironment.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  AttributionCode: "resource:///modules/AttributionCode.sys.mjs",
  NormandyUtils: "resource://normandy/lib/NormandyUtils.sys.mjs",
  ShellService: "resource:///modules/ShellService.sys.mjs",
  TelemetryArchive: "resource://gre/modules/TelemetryArchive.sys.mjs",
  TelemetryController: "resource://gre/modules/TelemetryController.sys.mjs",
  UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
  WindowsVersionInfo:
    "resource://gre/modules/components-utils/WindowsVersionInfo.sys.mjs",
});

/**
 * Create an object that provides general information about the client application.
 *
 * Components like Normandy RecipeRunner use this as part of the context for filter expressions,
 * so avoid adding non-getter functions as attributes, as filter expressions
 * cannot execute functions.
 *
 * Also note that, because filter expressions implicitly resolve promises, you
 * can add getter functions that return promises for async data.
 */
export class ClientEnvironmentBase {
  static get distribution() {
    return Services.prefs
      .getDefaultBranch(null)
      .getCharPref("distribution.id", "default");
  }

  static get telemetry() {
    return (async () => {
      const pings = await lazy.TelemetryArchive.promiseArchivedPingList();

      // get most recent ping per type
      const mostRecentPings = {};
      for (const ping of pings) {
        if (ping.type in mostRecentPings) {
          if (
            mostRecentPings[ping.type].timestampCreated < ping.timestampCreated
          ) {
            mostRecentPings[ping.type] = ping;
          }
        } else {
          mostRecentPings[ping.type] = ping;
        }
      }

      const telemetry = {};
      for (const key in mostRecentPings) {
        const ping = mostRecentPings[key];
        telemetry[ping.type] =
          await lazy.TelemetryArchive.promiseArchivedPingById(ping.id);
      }
      return telemetry;
    })();
  }

  static get liveTelemetry() {
    // Construct a proxy object that forwards access to the main ping, and
    // throws errors for other ping types. The intent is to allow using
    // `telemetry` and `liveTelemetry` in similar ways, but to fail fast if
    // the wrong telemetry types are accessed.
    let target = {};
    try {
      target.main = lazy.TelemetryController.getCurrentPingData();
    } catch (err) {
      console.error(err);
    }

    return new Proxy(target, {
      get(target, prop) {
        if (prop == "main") {
          return target.main;
        }
        if (prop == "then") {
          // this isn't a Promise, but it's not a problem to check
          return undefined;
        }
        throw new Error(
          `Live telemetry only includes the main ping, not the ${prop} ping`
        );
      },
      has(target, prop) {
        return prop == "main";
      },
    });
  }

  // Note that we intend to replace usages of this with client_id in https://bugzilla.mozilla.org/show_bug.cgi?id=1542955
  static get randomizationId() {
    let id = Services.prefs.getCharPref("app.normandy.user_id", "");
    if (!id) {
      id = lazy.NormandyUtils.generateUuid();
      Services.prefs.setCharPref("app.normandy.user_id", id);
    }
    return id;
  }

  static get version() {
    return AppConstants.MOZ_APP_VERSION_DISPLAY;
  }

  static get channel() {
    return lazy.UpdateUtils.getUpdateChannel(false);
  }

  static get isDefaultBrowser() {
    return lazy.ShellService.isDefaultBrowser();
  }

  static get searchEngine() {
    return (async () => {
      const defaultEngineInfo = await Services.search.getDefault();
      return defaultEngineInfo.telemetryId;
    })();
  }

  static get syncSetup() {
    return Services.prefs.prefHasUserValue("services.sync.username");
  }

  static get syncDesktopDevices() {
    return Services.prefs.getIntPref(
      "services.sync.clients.devices.desktop",
      0
    );
  }

  static get syncMobileDevices() {
    return Services.prefs.getIntPref("services.sync.clients.devices.mobile", 0);
  }

  static get syncTotalDevices() {
    return this.syncDesktopDevices + this.syncMobileDevices;
  }

  static get addons() {
    return (async () => {
      const addons = await lazy.AddonManager.getAllAddons();
      return addons.reduce((acc, addon) => {
        const {
          id,
          isActive,
          name,
          type,
          version,
          installDate: installDateN,
        } = addon;
        const installDate = new Date(installDateN);
        acc[id] = { id, isActive, name, type, version, installDate };
        return acc;
      }, {});
    })();
  }

  static get plugins() {
    return (async () => {
      const plugins = await lazy.AddonManager.getAddonsByTypes(["plugin"]);
      return plugins.reduce((acc, plugin) => {
        const { name, description, version } = plugin;
        acc[name] = { name, description, version };
        return acc;
      }, {});
    })();
  }

  static get locale() {
    return Services.locale.appLocaleAsBCP47;
  }

  static get doNotTrack() {
    return Services.prefs.getBoolPref(
      "privacy.donottrackheader.enabled",
      false
    );
  }

  static get os() {
    function coerceToNumber(version) {
      const parts = version.split(".");
      return parseFloat(parts.slice(0, 2).join("."));
    }

    function getOsVersion() {
      let version = null;
      try {
        version = Services.sysinfo.getProperty("version", null);
      } catch (_e) {
        // getProperty can throw if the version does not exist
      }
      if (version) {
        version = coerceToNumber(version);
      }
      return version;
    }

    let osInfo = {
      isWindows: AppConstants.platform == "win",
      isMac: AppConstants.platform === "macosx",
      isLinux: AppConstants.platform === "linux",

      get windowsVersion() {
        if (!osInfo.isWindows) {
          return null;
        }
        return getOsVersion();
      },

      /**
       * Gets the windows build number by querying the OS directly. The initial
       * version was copied from toolkit/components/telemetry/app/TelemetryEnvironment.sys.mjs
       * @returns {number | null} The build number, or null on non-Windows platform or if there is an error.
       */
      get windowsBuildNumber() {
        if (!osInfo.isWindows) {
          return null;
        }

        return lazy.WindowsVersionInfo.get({ throwOnError: false }).buildNumber;
      },

      get macVersion() {
        const darwinVersion = osInfo.darwinVersion;
        // Versions of OSX with Darwin < 5 don't follow this pattern
        if (darwinVersion >= 5) {
          // OSX 10.1 used Darwin 5, OSX 10.2 used Darwin 6, and so on.
          const intPart = Math.floor(darwinVersion);
          return 10 + 0.1 * (intPart - 4);
        }
        return null;
      },

      get darwinVersion() {
        if (!osInfo.isMac) {
          return null;
        }
        return getOsVersion();
      },

      // Version information on linux is a lot harder and a lot less useful, so
      // don't do anything about it here.
    };

    return osInfo;
  }

  static get attribution() {
    return lazy.AttributionCode.getAttrDataAsync();
  }

  static get appinfo() {
    Services.appinfo.QueryInterface(Ci.nsIXULAppInfo);
    Services.appinfo.QueryInterface(Ci.nsIPlatformInfo);
    return Services.appinfo;
  }
}
PK
!<`�w**2modules/components-utils/FilterExpressions.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  mozjexl: "resource://gre/modules/components-utils/mozjexl.sys.mjs",
  Sampling: "resource://gre/modules/components-utils/Sampling.sys.mjs",
});

function getPrefValue(prefKey, defaultValue) {
  switch (Services.prefs.getPrefType(prefKey)) {
    case Ci.nsIPrefBranch.PREF_STRING:
      return Services.prefs.getStringPref(prefKey);

    case Ci.nsIPrefBranch.PREF_INT:
      return Services.prefs.getIntPref(prefKey);

    case Ci.nsIPrefBranch.PREF_BOOL:
      return Services.prefs.getBoolPref(prefKey);

    case Ci.nsIPrefBranch.PREF_INVALID:
      return defaultValue;

    default:
      throw new Error(`Error getting pref ${prefKey}.`);
  }
}

ChromeUtils.defineLazyGetter(lazy, "jexl", () => {
  const jexl = new lazy.mozjexl.Jexl();
  jexl.addTransforms({
    date: dateString => new Date(dateString),
    stableSample: lazy.Sampling.stableSample,
    bucketSample: lazy.Sampling.bucketSample,
    preferenceValue: getPrefValue,
    preferenceIsUserSet: prefKey => Services.prefs.prefHasUserValue(prefKey),
    preferenceExists: prefKey =>
      Services.prefs.getPrefType(prefKey) != Ci.nsIPrefBranch.PREF_INVALID,
    keys,
    length,
    mapToProperty,
    regExpMatch,
    versionCompare,
  });
  jexl.addBinaryOp("intersect", 40, operatorIntersect);
  return jexl;
});

export var FilterExpressions = {
  getAvailableTransforms() {
    return Object.keys(lazy.jexl._transforms);
  },

  eval(expr, context = {}) {
    const onelineExpr = expr.replace(/[\t\n\r]/g, " ");
    return lazy.jexl.eval(onelineExpr, context);
  },
};

/**
 * Return an array of the given object's own keys (specifically, its enumerable
 * properties), or undefined if the argument isn't an object.
 * @param {Object} obj
 * @return {Array[String]|undefined}
 */
function keys(obj) {
  if (typeof obj !== "object" || obj === null) {
    return undefined;
  }

  return Object.keys(obj);
}

/**
 * Return the length of an array
 * @param {Array} arr
 * @return {number}
 */
function length(arr) {
  return Array.isArray(arr) ? arr.length : undefined;
}

/**
 * Given an input array and property name, return an array with each element of
 * the original array replaced with the given property of that element.
 * @param {Array} arr
 * @param {string} prop
 * @return {Array}
 */
function mapToProperty(arr, prop) {
  return Array.isArray(arr) ? arr.map(elem => elem[prop]) : undefined;
}

/**
 * Find all the values that are present in both lists. Returns undefined if
 * the arguments are not both Arrays.
 * @param {Array} listA
 * @param {Array} listB
 * @return {Array|undefined}
 */
function operatorIntersect(listA, listB) {
  if (!Array.isArray(listA) || !Array.isArray(listB)) {
    return undefined;
  }

  return listA.filter(item => listB.includes(item));
}

/**
 * Matches a string against a regular expression. Returns null if there are
 * no matches or an Array of matches.
 * @param {string} str
 * @param {string} pattern
 * @param {string} flags
 * @return {Array|null}
 */
function regExpMatch(str, pattern, flags) {
  const re = new RegExp(pattern, flags);
  return str.match(re);
}

/**
 * Compares v1 to v2 and returns 0 if they are equal, a negative number if
 * v1 < v2 or a positive number if v1 > v2.
 * @param {string} v1
 * @param {string} v2
 * @return {number}
 */
function versionCompare(v1, v2) {
  return Services.vc.compare(v1, v2);
}
PK
!<�m�I�I4modules/components-utils/JsonSchemaValidator.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* This file implements a not-quite standard JSON schema validator. It differs
 * from the spec in a few ways:
 *
 *  - the spec doesn't allow custom types to be defined, but this validator
 *    defines "URL", "URLorEmpty", "origin" etc.
 * - Strings are automatically converted to `URL` objects for the appropriate
 *   types.
 * - It doesn't support "pattern" when matching strings.
 * - The boolean type accepts (and casts) 0 and 1 as valid values.
 */

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  let { ConsoleAPI } = ChromeUtils.importESModule(
    "resource://gre/modules/Console.sys.mjs"
  );
  return new ConsoleAPI({
    prefix: "JsonSchemaValidator",
    // tip: set maxLogLevel to "debug" and use log.debug() to create detailed
    // messages during development. See LOG_LEVELS in Console.sys.mjs for details.
    maxLogLevel: "error",
  });
});

/**
 * To validate a single value, use the static `JsonSchemaValidator.validate`
 * method.  If you need to validate multiple values, you instead might want to
 * make a JsonSchemaValidator instance with the options you need and then call
 * the `validate` instance method.
 */
export class JsonSchemaValidator {
  /**
   * Validates a value against a schema.
   *
   * @param {*} value
   *   The value to validate.
   * @param {object} schema
   *   The schema to validate against.
   * @param {boolean} allowArrayNonMatchingItems
   *   When true:
   *     Invalid items in arrays will be ignored, and they won't be included in
   *     result.parsedValue.
   *   When false:
   *     Invalid items in arrays will cause validation to fail.
   * @param {boolean} allowExplicitUndefinedProperties
   *   When true:
   *     `someProperty: undefined` will be allowed for non-required properties.
   *   When false:
   *     `someProperty: undefined` will cause validation to fail even for
   *     properties that are not required.
   * @param {boolean} allowNullAsUndefinedProperties
   *   When true:
   *     `someProperty: null` will be allowed for non-required properties whose
   *     expected types are non-null.
   *   When false:
   *     `someProperty: null` will cause validation to fail for non-required
   *     properties, except for properties whose expected types are null.
   * @param {boolean} allowAdditionalProperties
   *   When true:
   *     Properties that are not defined in the schema will be ignored, and they
   *     won't be included in result.parsedValue.
   *   When false:
   *     Properties that are not defined in the schema will cause validation to
   *     fail.
   *   Note: Schema objects of type "object" can also contain a boolean property
   *     called `additionalProperties` that functions as a local version of this
   *     param. When true, extra properties will be allowed in the corresponding
   *     input objects regardless of `allowAdditionalProperties`, and as with
   *     `allowAdditionalProperties`, extra properties won't be included in
   *     `result.parsedValue`. (The inverse is not true: If a schema object
   *     defines `additionalProperties: false` but `allowAdditionalProperties`
   *     is true, extra properties will be allowed.)
   * @return {object}
   *   The result of the validation, an object that looks like this:
   *
   *   {
   *     valid,
   *     parsedValue,
   *     error: {
   *       message,
   *       rootValue,
   *       rootSchema,
   *       invalidValue,
   *       invalidPropertyNameComponents,
   *     }
   *   }
   *
   *   {boolean} valid
   *     True if validation is successful, false if not.
   *   {*} parsedValue
   *     If validation is successful, this is the validated value.  It can
   *     differ from the passed-in value in the following ways:
   *       * If a type in the schema is "URL" or "URLorEmpty", the passed-in
   *         value can use a string instead and it will be converted into a
   *         `URL` object in parsedValue.
   *       * Some of the `allow*` parameters control the properties that appear.
   *         See above.
   *   {Error} error
   *     If validation fails, `error` will be present.  It contains a number of
   *     properties useful for understanding the validation failure.
   *   {string} error.message
   *     The validation failure message.
   *   {*} error.rootValue
   *     The passed-in value.
   *   {object} error.rootSchema
   *     The passed-in schema.
   *   {*} invalidValue
   *     The value that caused validation to fail.  If the passed-in value is a
   *     scalar type, this will be the value itself.  If the value is an object
   *     or array, it will be the specific nested value in the object or array
   *     that caused validation to fail.
   *   {array} invalidPropertyNameComponents
   *     If the passed-in value is an object or array, this will contain the
   *     names of the object properties or array indexes where invalidValue can
   *     be found.  For example, assume the passed-in value is:
   *       { foo: { bar: { baz: 123 }}}
   *     And assume `baz` should be a string instead of a number.  Then
   *     invalidValue will be 123, and invalidPropertyNameComponents will be
   *     ["foo", "bar", "baz"], indicating that the erroneous property in the
   *     passed-in object is `foo.bar.baz`.
   */
  static validate(
    value,
    schema,
    {
      allowArrayNonMatchingItems = false,
      allowExplicitUndefinedProperties = false,
      allowNullAsUndefinedProperties = false,
      allowAdditionalProperties = false,
    } = {}
  ) {
    let validator = new JsonSchemaValidator({
      allowArrayNonMatchingItems,
      allowExplicitUndefinedProperties,
      allowNullAsUndefinedProperties,
      allowAdditionalProperties,
    });
    return validator.validate(value, schema);
  }

  /**
   * Constructor.
   *
   * @param {boolean} allowArrayNonMatchingItems
   *   See the static `validate` method above.
   * @param {boolean} allowExplicitUndefinedProperties
   *   See the static `validate` method above.
   * @param {boolean} allowNullAsUndefinedProperties
   *   See the static `validate` method above.
   * @param {boolean} allowAdditionalProperties
   *   See the static `validate` method above.
   */
  constructor({
    allowArrayNonMatchingItems = false,
    allowExplicitUndefinedProperties = false,
    allowNullAsUndefinedProperties = false,
    allowAdditionalProperties = false,
  } = {}) {
    this.allowArrayNonMatchingItems = allowArrayNonMatchingItems;
    this.allowExplicitUndefinedProperties = allowExplicitUndefinedProperties;
    this.allowNullAsUndefinedProperties = allowNullAsUndefinedProperties;
    this.allowAdditionalProperties = allowAdditionalProperties;
  }

  /**
   * Validates a value against a schema.
   *
   * @param {*} value
   *   The value to validate.
   * @param {object} schema
   *   The schema to validate against.
   * @return {object}
   *   The result object.  See the static `validate` method above.
   */
  validate(value, schema) {
    return this._validateRecursive(value, schema, [], {
      rootValue: value,
      rootSchema: schema,
    });
  }

  // eslint-disable-next-line complexity
  _validateRecursive(param, properties, keyPath, state) {
    lazy.log.debug(`checking @${param}@ for type ${properties.type}`);

    if (Array.isArray(properties.type)) {
      lazy.log.debug("type is an array");
      // For an array of types, the value is valid if it matches any of the
      // listed types. To check this, make versions of the object definition
      // that include only one type at a time, and check the value against each
      // one.
      for (const type of properties.type) {
        let typeProperties = Object.assign({}, properties, { type });
        lazy.log.debug(`checking subtype ${type}`);
        let result = this._validateRecursive(
          param,
          typeProperties,
          keyPath,
          state
        );
        if (result.valid) {
          return result;
        }
      }
      // None of the types matched
      return {
        valid: false,
        error: new JsonSchemaValidatorError({
          message:
            `The value '${valueToString(param)}' does not match any type in ` +
            valueToString(properties.type),
          value: param,
          keyPath,
          state,
        }),
      };
    }

    switch (properties.type) {
      case "boolean":
      case "number":
      case "integer":
      case "string":
      case "URL":
      case "URLorEmpty":
      case "origin":
      case "null": {
        let result = this._validateSimpleParam(
          param,
          properties.type,
          keyPath,
          state
        );
        if (!result.valid) {
          return result;
        }
        if (properties.enum && typeof result.parsedValue !== "boolean") {
          if (!properties.enum.includes(param)) {
            return {
              valid: false,
              error: new JsonSchemaValidatorError({
                message:
                  `The value '${valueToString(param)}' is not one of the ` +
                  `enumerated values ${valueToString(properties.enum)}`,
                value: param,
                keyPath,
                state,
              }),
            };
          }
        }
        return result;
      }

      case "array":
        if (!Array.isArray(param)) {
          return {
            valid: false,
            error: new JsonSchemaValidatorError({
              message:
                `The value '${valueToString(param)}' does not match the ` +
                `expected type 'array'`,
              value: param,
              keyPath,
              state,
            }),
          };
        }

        let parsedArray = [];
        for (let i = 0; i < param.length; i++) {
          let item = param[i];
          lazy.log.debug(
            `in array, checking @${item}@ for type ${properties.items.type}`
          );
          let result = this._validateRecursive(
            item,
            properties.items,
            keyPath.concat(i),
            state
          );
          if (!result.valid) {
            if (
              ("strict" in properties && properties.strict) ||
              (!("strict" in properties) && !this.allowArrayNonMatchingItems)
            ) {
              return result;
            }
            continue;
          }

          parsedArray.push(result.parsedValue);
        }

        return { valid: true, parsedValue: parsedArray };

      case "object": {
        if (typeof param != "object" || !param) {
          return {
            valid: false,
            error: new JsonSchemaValidatorError({
              message:
                `The value '${valueToString(param)}' does not match the ` +
                `expected type 'object'`,
              value: param,
              keyPath,
              state,
            }),
          };
        }

        let parsedObj = {};
        let patternProperties = [];
        if ("patternProperties" in properties) {
          for (let prop of Object.keys(properties.patternProperties || {})) {
            let pattern;
            try {
              pattern = new RegExp(prop);
            } catch (e) {
              throw new Error(
                `Internal error: Invalid property pattern ${prop}`
              );
            }
            patternProperties.push({
              pattern,
              schema: properties.patternProperties[prop],
            });
          }
        }

        if (properties.required) {
          for (let required of properties.required) {
            if (!(required in param)) {
              lazy.log.error(`Object is missing required property ${required}`);
              return {
                valid: false,
                error: new JsonSchemaValidatorError({
                  message: `Object is missing required property '${required}'`,
                  value: param,
                  keyPath,
                  state,
                }),
              };
            }
          }
        }

        for (let item of Object.keys(param)) {
          let schema;
          if (
            "properties" in properties &&
            properties.properties.hasOwnProperty(item)
          ) {
            schema = properties.properties[item];
          } else if (patternProperties.length) {
            for (let patternProperty of patternProperties) {
              if (patternProperty.pattern.test(item)) {
                schema = patternProperty.schema;
                break;
              }
            }
          }
          if (!schema) {
            let allowAdditionalProperties =
              properties.additionalProperties ||
              (!properties.strict && this.allowAdditionalProperties);
            if (allowAdditionalProperties) {
              continue;
            }
            return {
              valid: false,
              error: new JsonSchemaValidatorError({
                message: `Object has unexpected property '${item}'`,
                value: param,
                keyPath,
                state,
              }),
            };
          }
          let allowExplicitUndefinedProperties =
            !properties.strict && this.allowExplicitUndefinedProperties;
          let allowNullAsUndefinedProperties =
            !properties.strict && this.allowNullAsUndefinedProperties;
          let isUndefined =
            (!allowExplicitUndefinedProperties && !(item in param)) ||
            (allowExplicitUndefinedProperties && param[item] === undefined) ||
            (allowNullAsUndefinedProperties && param[item] === null);
          if (isUndefined) {
            continue;
          }
          let result = this._validateRecursive(
            param[item],
            schema,
            keyPath.concat(item),
            state
          );
          if (!result.valid) {
            return result;
          }
          parsedObj[item] = result.parsedValue;
        }
        return { valid: true, parsedValue: parsedObj };
      }

      case "JSON":
        if (typeof param == "object") {
          return { valid: true, parsedValue: param };
        }
        try {
          let json = JSON.parse(param);
          if (typeof json != "object") {
            return {
              valid: false,
              error: new JsonSchemaValidatorError({
                message: `JSON was not an object: ${valueToString(param)}`,
                value: param,
                keyPath,
                state,
              }),
            };
          }
          return { valid: true, parsedValue: json };
        } catch (e) {
          lazy.log.error("JSON string couldn't be parsed");
          return {
            valid: false,
            error: new JsonSchemaValidatorError({
              message: `JSON string could not be parsed: ${valueToString(
                param
              )}`,
              value: param,
              keyPath,
              state,
            }),
          };
        }
    }

    return {
      valid: false,
      error: new JsonSchemaValidatorError({
        message: `Invalid schema property type: ${valueToString(
          properties.type
        )}`,
        value: param,
        keyPath,
        state,
      }),
    };
  }

  _validateSimpleParam(param, type, keyPath, state) {
    let valid = false;
    let parsedParam = param;
    let error = undefined;

    switch (type) {
      case "boolean":
        if (typeof param == "boolean") {
          valid = true;
        } else if (typeof param == "number" && (param == 0 || param == 1)) {
          valid = true;
          parsedParam = !!param;
        }
        break;

      case "number":
      case "string":
        valid = typeof param == type;
        break;

      // integer is an alias to "number" that some JSON schema tools use
      case "integer":
        valid = typeof param == "number";
        break;

      case "null":
        valid = param === null;
        break;

      case "origin":
        if (typeof param != "string") {
          break;
        }

        try {
          parsedParam = new URL(param);

          if (parsedParam.protocol == "file:") {
            // Treat the entire file URL as an origin.
            // Note this is stricter than the current Firefox policy,
            // but consistent with Chrome.
            // See https://bugzilla.mozilla.org/show_bug.cgi?id=803143
            valid = true;
          } else {
            let pathQueryRef = parsedParam.pathname + parsedParam.hash;
            // Make sure that "origin" types won't accept full URLs.
            if (pathQueryRef != "/" && pathQueryRef != "") {
              lazy.log.error(
                `Ignoring parameter "${param}" - origin was expected but received full URL.`
              );
              valid = false;
            } else {
              valid = true;
            }
          }
        } catch (ex) {
          lazy.log.error(`Ignoring parameter "${param}" - not a valid origin.`);
          valid = false;
        }
        break;

      case "URL":
      case "URLorEmpty":
        if (typeof param != "string") {
          break;
        }

        if (type == "URLorEmpty" && param === "") {
          valid = true;
          break;
        }

        try {
          parsedParam = new URL(param);
          valid = true;
        } catch (ex) {
          if (!param.startsWith("http")) {
            lazy.log.error(
              `Ignoring parameter "${param}" - scheme (http or https) must be specified.`
            );
          }
          valid = false;
        }
        break;
    }

    if (!valid && !error) {
      error = new JsonSchemaValidatorError({
        message:
          `The value '${valueToString(param)}' does not match the expected ` +
          `type '${type}'`,
        value: param,
        keyPath,
        state,
      });
    }

    let result = {
      valid,
      parsedValue: parsedParam,
    };
    if (error) {
      result.error = error;
    }
    return result;
  }
}

class JsonSchemaValidatorError extends Error {
  constructor({ message, value, keyPath, state } = {}, ...args) {
    if (keyPath.length) {
      message +=
        ". " +
        `The invalid value is property '${keyPath.join(".")}' in ` +
        JSON.stringify(state.rootValue);
    }
    super(message, ...args);
    this.name = "JsonSchemaValidatorError";
    this.rootValue = state.rootValue;
    this.rootSchema = state.rootSchema;
    this.invalidPropertyNameComponents = keyPath;
    this.invalidValue = value;
  }
}

function valueToString(value) {
  try {
    return JSON.stringify(value);
  } catch (ex) {}
  return String(value);
}
PK
!<6����)modules/components-utils/Sampling.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const hashBits = 48;
const hashLength = hashBits / 4; // each hexadecimal digit represents 4 bits
const hashMultiplier = Math.pow(2, hashBits) - 1;

export var Sampling = {
  /**
   * Map from the range [0, 1] to [0, 2^48].
   * @param  {number} frac A float from 0.0 to 1.0.
   * @return {string} A 48 bit number represented in hex, padded to 12 characters.
   */
  fractionToKey(frac) {
    if (frac < 0 || frac > 1) {
      throw new Error(`frac must be between 0 and 1 inclusive (got ${frac})`);
    }

    return Math.floor(frac * hashMultiplier)
      .toString(16)
      .padStart(hashLength, "0");
  },

  /**
   * @param {ArrayBuffer} buffer Data to convert
   * @returns {String}    `buffer`'s content, converted to a hexadecimal string.
   */
  bufferToHex(buffer) {
    const hexCodes = [];
    const view = new DataView(buffer);
    for (let i = 0; i < view.byteLength; i += 4) {
      // Using getUint32 reduces the number of iterations needed (we process 4 bytes each time)
      const value = view.getUint32(i);
      // toString(16) will give the hex representation of the number without padding
      hexCodes.push(value.toString(16).padStart(8, "0"));
    }

    // Join all the hex strings into one
    return hexCodes.join("");
  },

  /**
   * Check if an input hash is contained in a bucket range.
   *
   * isHashInBucket(fractionToKey(0.5), 3, 6, 10) -> returns true
   *
   *              minBucket
   *              |     hash
   *              v     v
   *    [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
   *                       ^
   *                       maxBucket
   *
   * @param inputHash {String}
   * @param minBucket {int} The lower boundary, inclusive, of the range to check.
   * @param maxBucket {int} The upper boundary, exclusive, of the range to check.
   * @param bucketCount {int} The total number of buckets. Should be greater than
   *                          or equal to maxBucket.
   */
  isHashInBucket(inputHash, minBucket, maxBucket, bucketCount) {
    const minHash = Sampling.fractionToKey(minBucket / bucketCount);
    const maxHash = Sampling.fractionToKey(maxBucket / bucketCount);
    return minHash <= inputHash && inputHash < maxHash;
  },

  /**
   * @promise A hash of `data`, truncated to the 12 most significant characters.
   */
  async truncatedHash(data) {
    const hasher = crypto.subtle;
    const input = new TextEncoder().encode(JSON.stringify(data));
    const hash = await hasher.digest("SHA-256", input);
    // truncate hash to 12 characters (2^48), because the full hash is larger
    // than JS can meaningfully represent as a number.
    return Sampling.bufferToHex(hash).slice(0, 12);
  },

  /**
   * Sample by splitting the input into two buckets, one with a size (rate) and
   * another with a size (1.0 - rate), and then check if the input's hash falls
   * into the first bucket.
   *
   * @param    {object}  input Input to hash to determine the sample.
   * @param    {Number}  rate  Number between 0.0 and 1.0 to sample at. A value of
   *                           0.25 returns true 25% of the time.
   * @promises {boolean} True if the input is in the sample.
   */
  async stableSample(input, rate) {
    const inputHash = await Sampling.truncatedHash(input);
    const samplePoint = Sampling.fractionToKey(rate);

    return inputHash < samplePoint;
  },

  /**
   * Sample by splitting the input space into a series of buckets, and checking
   * if the given input is in a range of buckets.
   *
   * The range to check is defined by a start point and length, and can wrap
   * around the input space. For example, if there are 100 buckets, and we ask to
   * check 50 buckets starting from bucket 70, then buckets 70-99 and 0-19 will
   * be checked.
   *
   * @param {object}     input Input to hash to determine the matching bucket.
   * @param {integer}    start Index of the bucket to start checking.
   * @param {integer}    count Number of buckets to check.
   * @param {integer}    total Total number of buckets to group inputs into.
   * @promises {boolean} True if the given input is within the range of buckets
   *                     we're checking. */
  async bucketSample(input, start, count, total) {
    const inputHash = await Sampling.truncatedHash(input);
    const wrappedStart = start % total;
    const end = wrappedStart + count;

    // If the range we're testing wraps, we have to check two ranges: from start
    // to max, and from min to end.
    if (end > total) {
      return (
        Sampling.isHashInBucket(inputHash, 0, end % total, total) ||
        Sampling.isHashInBucket(inputHash, wrappedStart, total, total)
      );
    }

    return Sampling.isHashInBucket(inputHash, wrappedStart, end, total);
  },

  /**
   * Sample over a list of ratios such that, over the input space, each ratio
   * has a number of matches in correct proportion to the other ratios.
   *
   * For example, given the ratios:
   *
   * [1, 2, 3, 4]
   *
   * 10% of all inputs will return 0, 20% of all inputs will return 1, 30% will
   * return 2, and 40% will return 3. You can determine the percent of inputs
   * that will return an index by dividing the ratio by the sum of all ratios
   * passed in. In the case above, 4 / (1 + 2 + 3 + 4) == 0.4, or 40% of the
   * inputs.
   *
   * @param {object} input
   * @param {Array<integer>} ratios
   * @promises {integer}
   *   Index of the ratio that matched the input
   * @rejects {Error}
   *   If the list of ratios doesn't have at least one element
   */
  async ratioSample(input, ratios) {
    if (ratios.length < 1) {
      throw new Error(
        `ratios must be at least 1 element long (got length: ${ratios.length})`
      );
    }

    const inputHash = await Sampling.truncatedHash(input);
    const ratioTotal = ratios.reduce((acc, ratio) => acc + ratio);

    let samplePoint = 0;
    for (let k = 0; k < ratios.length - 1; k++) {
      samplePoint += ratios[k];
      if (inputHash <= Sampling.fractionToKey(samplePoint / ratioTotal)) {
        return k;
      }
    }

    // No need to check the last bucket if the others didn't match.
    return ratios.length - 1;
  },
};
PK
!<��Pw��4modules/components-utils/WindowsInstallsInfo.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export var WindowsInstallsInfo = {
  /**
   * Retrieve install paths of this app, based on the values in the TaskBarIDs registry key.
   *
   * Installs from unarchived packages do not have a TaskBarID registry key and
   * therefore won't appear in the result.
   *
   * @param {Number} [limit] Optional, maximum number of installation paths to count.
            Defaults to 1024.
   * @param {Set} [exclude] Optional, an Set of paths to exclude from the count.
   * @returns {Set} Set of install paths, lower cased.
   */
  getInstallPaths(limit = 1024, exclude = new Set()) {
    // This is somewhat more complicated than just collecting all values because
    // the same install can be listed in both HKCU and HKLM.  The strategy is to
    // add all paths to a Set to deduplicate.

    const lcExclude = new Set();
    exclude.forEach(p => lcExclude.add(p.toLowerCase()));

    // Add the names of the values under `rootKey\subKey` to `set`.
    // All strings are lower cased first, as Windows paths are not case-sensitive.
    function collectValues(rootKey, wowFlag, subKey, set) {
      const key = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
        Ci.nsIWindowsRegKey
      );

      try {
        key.open(rootKey, subKey, key.ACCESS_READ | wowFlag);
      } catch (_e) {
        // The key may not exist, ignore.
        // (nsWindowsRegKey::Open doesn't provide detailed error codes)
        return;
      }
      const valueCount = key.valueCount;

      try {
        for (let i = 0; i < valueCount; ++i) {
          const path = key.getValueName(i).toLowerCase();
          if (!lcExclude.has(path)) {
            set.add(path);
          }
          if (set.size >= limit) {
            break;
          }
        }
      } finally {
        key.close();
      }
    }

    const subKeyName = `Software\\Mozilla\\${Services.appinfo.name}\\TaskBarIDs`;

    const paths = new Set();

    // First collect from HKLM for both 32-bit and 64-bit installs regardless of the architecture
    // of the current application.
    for (const wowFlag of [
      Ci.nsIWindowsRegKey.WOW64_32,
      Ci.nsIWindowsRegKey.WOW64_64,
    ]) {
      collectValues(
        Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
        wowFlag,
        subKeyName,
        paths
      );
      if (paths.size >= limit) {
        break;
      }
    }

    if (paths.size < limit) {
      // Then collect from HKCU.
      // HKCU\Software is shared between 32 and 64 so nothing special is needed for WOW64,
      // ref https://docs.microsoft.com/en-us/windows/win32/winprog64/shared-registry-keys
      collectValues(
        Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
        0 /* wowFlag */,
        subKeyName,
        paths
      );
    }

    return paths;
  },
};
PK
!<�8]jm
m
3modules/components-utils/WindowsVersionInfo.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { ctypes } from "resource://gre/modules/ctypes.sys.mjs";

const BYTE = ctypes.uint8_t;
const WORD = ctypes.uint16_t;
const DWORD = ctypes.uint32_t;
const WCHAR = ctypes.char16_t;
const BOOL = ctypes.int;

export var WindowsVersionInfo = {
  UNKNOWN_VERSION_INFO: {
    servicePackMajor: null,
    servicePackMinor: null,
    buildNumber: null,
  },

  /**
   * Gets the service pack and build number on Windows platforms.
   *
   * @param opts {Object} keyword arguments
   * @param opts.throwOnError {boolean} Optional, defaults to true. If set to
   *    false will return an object with keys set to null instead of throwing an
   *    error. If set to true, errors will be thrown instead.
   * @throws If `throwOnError` is true and version information cannot be
   *    determined.
   * @return {object} An object containing keys `servicePackMajor`,
   *    `servicePackMinor`, and `buildNumber`. If `throwOnError` is false, these
   *    values may be null.
   */
  get({ throwOnError = true } = {}) {
    function throwOrUnknown(err) {
      if (throwOnError) {
        throw err;
      }
      console.error(err);
      return WindowsVersionInfo.UNKNOWN_VERSION_INFO;
    }

    if (AppConstants.platform !== "win") {
      return throwOrUnknown(
        WindowsVersionInfo.NotWindowsError(
          `Cannot get Windows version info on platform ${AppConstants.platform}`
        )
      );
    }

    // This structure is described at:
    // http://msdn.microsoft.com/en-us/library/ms724833%28v=vs.85%29.aspx
    const SZCSDVERSIONLENGTH = 128;
    const OSVERSIONINFOEXW = new ctypes.StructType("OSVERSIONINFOEXW", [
      { dwOSVersionInfoSize: DWORD },
      { dwMajorVersion: DWORD },
      { dwMinorVersion: DWORD },
      { dwBuildNumber: DWORD },
      { dwPlatformId: DWORD },
      { szCSDVersion: ctypes.ArrayType(WCHAR, SZCSDVERSIONLENGTH) },
      { wServicePackMajor: WORD },
      { wServicePackMinor: WORD },
      { wSuiteMask: WORD },
      { wProductType: BYTE },
      { wReserved: BYTE },
    ]);

    let kernel32;
    try {
      kernel32 = ctypes.open("kernel32");
    } catch (err) {
      return throwOrUnknown(
        new WindowsVersionInfo.CannotOpenKernelError(
          `Unable to open kernel32! ${err}`
        )
      );
    }

    try {
      let GetVersionEx = kernel32.declare(
        "GetVersionExW",
        ctypes.winapi_abi,
        BOOL,
        OSVERSIONINFOEXW.ptr
      );
      let winVer = OSVERSIONINFOEXW();
      winVer.dwOSVersionInfoSize = OSVERSIONINFOEXW.size;

      if (GetVersionEx(winVer.address()) === 0) {
        throw new WindowsVersionInfo.GetVersionExError(
          "Failure in GetVersionEx (returned 0)"
        );
      }

      return {
        servicePackMajor: winVer.wServicePackMajor,
        servicePackMinor: winVer.wServicePackMinor,
        buildNumber: winVer.dwBuildNumber,
      };
    } catch (err) {
      return throwOrUnknown(err);
    } finally {
      if (kernel32) {
        kernel32.close();
      }
    }
  },

  CannotOpenKernelError: class extends Error {},
  GetVersionExError: class extends Error {},
  NotWindowsError: class extends Error {},
};
PK
!<���*5*5(modules/components-utils/mozjexl.sys.mjs/* eslint-disable */export const mozjexl=function(a){function b(d){if(c[d])return c[d].exports;var e=c[d]={i:d,l:!1,exports:{}};return a[d].call(e.exports,e,e.exports,b),e.l=!0,e.exports}var c={};return b.m=a,b.c=c,b.d=function(a,c,d){b.o(a,c)||Object.defineProperty(a,c,{configurable:!1,enumerable:!0,get:d})},b.n=function(a){var c=a&&a.__esModule?function(){return a['default']}:function(){return a};return b.d(c,'a',c),c},b.o=function(a,b){return Object.prototype.hasOwnProperty.call(a,b)},b.p='',b(b.s=93)}({65:function(a,b){b.argVal=function(a){this._cursor.args.push(a)},b.arrayStart=function(){this._placeAtCursor({type:'ArrayLiteral',value:[]})},b.arrayVal=function(a){a&&this._cursor.value.push(a)},b.binaryOp=function(a){for(var b=this._grammar[a.value].precedence||0,c=this._cursor._parent;c&&c.operator&&this._grammar[c.operator].precedence>=b;)this._cursor=c,c=c._parent;var d={type:'BinaryExpression',operator:a.value,left:this._cursor};this._setParent(this._cursor,d),this._cursor=c,this._placeAtCursor(d)},b.dot=function(){this._nextIdentEncapsulate=this._cursor&&('BinaryExpression'!=this._cursor.type||'BinaryExpression'==this._cursor.type&&this._cursor.right)&&'UnaryExpression'!=this._cursor.type,this._nextIdentRelative=!this._cursor||this._cursor&&!this._nextIdentEncapsulate,this._nextIdentRelative&&(this._relative=!0)},b.filter=function(a){this._placeBeforeCursor({type:'FilterExpression',expr:a,relative:this._subParser.isRelative(),subject:this._cursor})},b.identifier=function(a){var b={type:'Identifier',value:a.value};this._nextIdentEncapsulate?(b.from=this._cursor,this._placeBeforeCursor(b),this._nextIdentEncapsulate=!1):(this._nextIdentRelative&&(b.relative=!0),this._placeAtCursor(b))},b.literal=function(a){this._placeAtCursor({type:'Literal',value:a.value})},b.objKey=function(a){this._curObjKey=a.value},b.objStart=function(){this._placeAtCursor({type:'ObjectLiteral',value:{}})},b.objVal=function(a){this._cursor.value[this._curObjKey]=a},b.subExpression=function(a){this._placeAtCursor(a)},b.ternaryEnd=function(a){this._cursor.alternate=a},b.ternaryMid=function(a){this._cursor.consequent=a},b.ternaryStart=function(){this._tree={type:'ConditionalExpression',test:this._tree},this._cursor=this._tree},b.transform=function(a){this._placeBeforeCursor({type:'Transform',name:a.value,args:[],subject:this._cursor})},b.unaryOp=function(a){this._placeAtCursor({type:'UnaryExpression',operator:a.value})}},93:function(a,b,c){function d(){this._customGrammar=null,this._lexer=null,this._transforms={}}var e=c(94),f=c(96),g=c(97),h=c(99).elements;d.prototype.addBinaryOp=function(a,b,c){this._addGrammarElement(a,{type:'binaryOp',precedence:b,eval:c})},d.prototype.addUnaryOp=function(a,b){this._addGrammarElement(a,{type:'unaryOp',weight:Infinity,eval:b})},d.prototype.addTransform=function(a,b){this._transforms[a]=b},d.prototype.addTransforms=function(a){for(var b in a)a.hasOwnProperty(b)&&(this._transforms[b]=a[b])},d.prototype.getTransform=function(a){return this._transforms[a]},d.prototype.eval=function(a,b,c){'function'==typeof b?(c=b,b={}):!b&&(b={});var d=this._eval(a,b);if(c){var e=!1;return d.then(function(a){e=!0,setTimeout(c.bind(null,null,a),0)}).catch(function(a){e||setTimeout(c.bind(null,a),0)})}return d},d.prototype.removeOp=function(a){var b=this._getCustomGrammar();b[a]&&('binaryOp'==b[a].type||'unaryOp'==b[a].type)&&(delete b[a],this._lexer=null)},d.prototype._addGrammarElement=function(a,b){var c=this._getCustomGrammar();c[a]=b,this._lexer=null},d.prototype._eval=function(a,b){var c=this,d=this._getGrammar(),f=new g(d),h=new e(d,this._transforms,b);return Promise.resolve().then(function(){return f.addTokens(c._getLexer().tokenize(a)),h.eval(f.complete())})},d.prototype._getCustomGrammar=function(){if(!this._customGrammar)for(var a in this._customGrammar={},h)h.hasOwnProperty(a)&&(this._customGrammar[a]=h[a]);return this._customGrammar},d.prototype._getGrammar=function(){return this._customGrammar||h},d.prototype._getLexer=function(){return this._lexer||(this._lexer=new f(this._getGrammar())),this._lexer},a.exports=new d,a.exports.Jexl=d},94:function(a,b,c){var d=c(95),e=function(a,b,c,d){this._grammar=a,this._transforms=b||{},this._context=c||{},this._relContext=d||this._context};e.prototype.eval=function(a){var b=this;return Promise.resolve().then(function(){return d[a.type].call(b,a)})},e.prototype.evalArray=function(a){return Promise.all(a.map(function(a){return this.eval(a)},this))},e.prototype.evalMap=function(a){var b=Object.keys(a),c={},d=b.map(function(b){return this.eval(a[b])},this);return Promise.all(d).then(function(a){return a.forEach(function(a,d){c[b[d]]=a}),c})},e.prototype._filterRelative=function(a,b){if(void 0!==a){var c=[];return Array.isArray(a)||(a=[a]),a.forEach(function(a){var d=new e(this._grammar,this._transforms,this._context,a);c.push(d.eval(b))},this),Promise.all(c).then(function(b){var c=[];return b.forEach(function(b,d){b&&c.push(a[d])}),c})}},e.prototype._filterStatic=function(a,b){return this.eval(b).then(function(b){return'boolean'==typeof b?b?a:void 0:void 0===a?void 0:a[b]})},a.exports=e},95:function(a,b){b.ArrayLiteral=function(a){return this.evalArray(a.value)},b.BinaryExpression=function(a){var b=this;return Promise.all([this.eval(a.left),this.eval(a.right)]).then(function(c){return b._grammar[a.operator].eval(c[0],c[1])})},b.ConditionalExpression=function(a){var b=this;return this.eval(a.test).then(function(c){return c?a.consequent?b.eval(a.consequent):c:b.eval(a.alternate)})},b.FilterExpression=function(a){var b=this;return this.eval(a.subject).then(function(c){return a.relative?b._filterRelative(c,a.expr):b._filterStatic(c,a.expr)})},b.Identifier=function(a){return a.from?this.eval(a.from).then(function(b){if(void 0!==b)return Array.isArray(b)&&(b=b[0]),b[a.value]}):a.relative?this._relContext[a.value]:this._context[a.value]},b.Literal=function(a){return a.value},b.ObjectLiteral=function(a){return this.evalMap(a.value)},b.Transform=function(a){var b=this._transforms[a.name];if(!b)throw new Error('Transform \''+a.name+'\' is not defined.');return Promise.all([this.eval(a.subject),this.evalArray(a.args||[])]).then(function(a){return b.apply(null,[a[0]].concat(a[1]))})},b.UnaryExpression=function(a){var b=this;return this.eval(a.right).then(function(c){return b._grammar[a.operator].eval(c)})}},96:function(a){function b(a){this._grammar=a}var c=/^-?(?:(?:[0-9]*\.[0-9]+)|[0-9]+)$/,d=/^[a-zA-Z_\$][a-zA-Z0-9_\$]*$/,e=/\\\\/,f=['\'(?:(?:\\\\\')?[^\'])*\'','"(?:(?:\\\\")?[^"])*"','\\s+','\\btrue\\b','\\bfalse\\b'],g=['\\b[a-zA-Z_\\$][a-zA-Z0-9_\\$]*\\b','(?:(?:[0-9]*\\.[0-9]+)|[0-9]+)'],h=['binaryOp','unaryOp','openParen','openBracket','question','colon'];b.prototype.getElements=function(a){var b=this._getSplitRegex();return a.split(b).filter(function(a){return a})},b.prototype.getTokens=function(a){for(var b=[],c=!1,d=0;d<a.length;d++)this._isWhitespace(a[d])?b.length&&(b[b.length-1].raw+=a[d]):'-'===a[d]&&this._isNegative(b)?c=!0:(c&&(a[d]='-'+a[d],c=!1),b.push(this._createToken(a[d])));return c&&b.push(this._createToken('-')),b},b.prototype.tokenize=function(a){var b=this.getElements(a);return this.getTokens(b)},b.prototype._createToken=function(a){var b={type:'literal',value:a,raw:a};if('"'==a[0]||'\''==a[0])b.value=this._unquote(a);else if(a.match(c))b.value=parseFloat(a);else if('true'===a||'false'===a)b.value='true'==a;else if(this._grammar[a])b.type=this._grammar[a].type;else if(a.match(d))b.type='identifier';else throw new Error('Invalid expression token: '+a);return b},b.prototype._escapeRegExp=function(a){return a=a.replace(/[.*+?^${}()|[\]\\]/g,'\\$&'),a.match(d)&&(a='\\b'+a+'\\b'),a},b.prototype._getSplitRegex=function(){if(!this._splitRegex){var a=Object.keys(this._grammar);a=a.sort(function(c,a){return a.length-c.length}).map(function(a){return this._escapeRegExp(a)},this),this._splitRegex=new RegExp('('+[f.join('|'),a.join('|'),g.join('|')].join('|')+')')}return this._splitRegex},b.prototype._isNegative=function(a){return!a.length||h.some(function(b){return b===a[a.length-1].type})};var i=/^\s*$/;b.prototype._isWhitespace=function(a){return i.test(a)},b.prototype._unquote=function(a){var b=a[0],c=new RegExp('\\\\'+b,'g');return a.substr(1,a.length-2).replace(c,b).replace(e,'\\')},a.exports=b},97:function(a,b,c){function d(a,b,c){this._grammar=a,this._state='expectOperand',this._tree=null,this._exprStr=b||'',this._relative=!1,this._stopMap=c||{}}var e=c(65),f=c(98).states;d.prototype.addToken=function(a){if('complete'==this._state)throw new Error('Cannot add a new token to a completed Parser');var b=f[this._state],c=this._exprStr;if(this._exprStr+=a.raw,b.subHandler){this._subParser||this._startSubExpression(c);var d=this._subParser.addToken(a);if(d){if(this._endSubExpression(),this._parentStop)return d;this._state=d}}else if(b.tokenTypes[a.type]){var g=b.tokenTypes[a.type],h=e[a.type];g.handler&&(h=g.handler),h&&h.call(this,a),g.toState&&(this._state=g.toState)}else{if(this._stopMap[a.type])return this._stopMap[a.type];throw new Error('Token '+a.raw+' ('+a.type+') unexpected in expression: '+this._exprStr)}return!1},d.prototype.addTokens=function(a){a.forEach(this.addToken,this)},d.prototype.complete=function(){if(this._cursor&&!f[this._state].completable)throw new Error('Unexpected end of expression: '+this._exprStr);return this._subParser&&this._endSubExpression(),this._state='complete',this._cursor?this._tree:null},d.prototype.isRelative=function(){return this._relative},d.prototype._endSubExpression=function(){f[this._state].subHandler.call(this,this._subParser.complete()),this._subParser=null},d.prototype._placeAtCursor=function(a){this._cursor?(this._cursor.right=a,this._setParent(a,this._cursor)):this._tree=a,this._cursor=a},d.prototype._placeBeforeCursor=function(a){this._cursor=this._cursor._parent,this._placeAtCursor(a)},d.prototype._setParent=function(a,b){Object.defineProperty(a,'_parent',{value:b,writable:!0})},d.prototype._startSubExpression=function(a){var b=f[this._state].endStates;b||(this._parentStop=!0,b=this._stopMap),this._subParser=new d(this._grammar,a,b)},a.exports=d},98:function(a,b,c){var d=c(65);b.states={expectOperand:{tokenTypes:{literal:{toState:'expectBinOp'},identifier:{toState:'identifier'},unaryOp:{},openParen:{toState:'subExpression'},openCurl:{toState:'expectObjKey',handler:d.objStart},dot:{toState:'traverse'},openBracket:{toState:'arrayVal',handler:d.arrayStart}}},expectBinOp:{tokenTypes:{binaryOp:{toState:'expectOperand'},pipe:{toState:'expectTransform'},dot:{toState:'traverse'},question:{toState:'ternaryMid',handler:d.ternaryStart}},completable:!0},expectTransform:{tokenTypes:{identifier:{toState:'postTransform',handler:d.transform}}},expectObjKey:{tokenTypes:{identifier:{toState:'expectKeyValSep',handler:d.objKey},closeCurl:{toState:'expectBinOp'}}},expectKeyValSep:{tokenTypes:{colon:{toState:'objVal'}}},postTransform:{tokenTypes:{openParen:{toState:'argVal'},binaryOp:{toState:'expectOperand'},dot:{toState:'traverse'},openBracket:{toState:'filter'},pipe:{toState:'expectTransform'}},completable:!0},postTransformArgs:{tokenTypes:{binaryOp:{toState:'expectOperand'},dot:{toState:'traverse'},openBracket:{toState:'filter'},pipe:{toState:'expectTransform'}},completable:!0},identifier:{tokenTypes:{binaryOp:{toState:'expectOperand'},dot:{toState:'traverse'},openBracket:{toState:'filter'},pipe:{toState:'expectTransform'},question:{toState:'ternaryMid',handler:d.ternaryStart}},completable:!0},traverse:{tokenTypes:{identifier:{toState:'identifier'}}},filter:{subHandler:d.filter,endStates:{closeBracket:'identifier'}},subExpression:{subHandler:d.subExpression,endStates:{closeParen:'expectBinOp'}},argVal:{subHandler:d.argVal,endStates:{comma:'argVal',closeParen:'postTransformArgs'}},objVal:{subHandler:d.objVal,endStates:{comma:'expectObjKey',closeCurl:'expectBinOp'}},arrayVal:{subHandler:d.arrayVal,endStates:{comma:'arrayVal',closeBracket:'expectBinOp'}},ternaryMid:{subHandler:d.ternaryMid,endStates:{colon:'ternaryEnd'}},ternaryEnd:{subHandler:d.ternaryEnd,completable:!0}}},99:function(a,b){b.elements={".":{type:'dot'},"[":{type:'openBracket'},"]":{type:'closeBracket'},"|":{type:'pipe'},"{":{type:'openCurl'},"}":{type:'closeCurl'},":":{type:'colon'},",":{type:'comma'},"(":{type:'openParen'},")":{type:'closeParen'},"?":{type:'question'},"+":{type:'binaryOp',precedence:30,eval:function(a,b){return a+b}},"-":{type:'binaryOp',precedence:30,eval:function(a,b){return a-b}},"*":{type:'binaryOp',precedence:40,eval:function(a,b){return a*b}},"/":{type:'binaryOp',precedence:40,eval:function(a,b){return a/b}},"//":{type:'binaryOp',precedence:40,eval:function(a,b){return Math.floor(a/b)}},"%":{type:'binaryOp',precedence:50,eval:function(a,b){return a%b}},"^":{type:'binaryOp',precedence:50,eval:function(a,b){return Math.pow(a,b)}},"==":{type:'binaryOp',precedence:20,eval:function(a,b){return a==b}},"!=":{type:'binaryOp',precedence:20,eval:function(a,b){return a!=b}},">":{type:'binaryOp',precedence:20,eval:function(a,b){return a>b}},">=":{type:'binaryOp',precedence:20,eval:function(a,b){return a>=b}},"<":{type:'binaryOp',precedence:20,eval:function(a,b){return a<b}},"<=":{type:'binaryOp',precedence:20,eval:function(a,b){return a<=b}},"&&":{type:'binaryOp',precedence:10,eval:function(a,b){return a&&b}},"||":{type:'binaryOp',precedence:10,eval:function(a,b){return a||b}},in:{type:'binaryOp',precedence:20,eval:function(a,b){return'string'==typeof b?-1!==b.indexOf(a):!!Array.isArray(b)&&b.some(function(b){return b==a})}},"!":{type:'unaryOp',precedence:Infinity,eval:function(a){return!a}}}}});PK
!<�{��
�
3modules/contentrelevancy/private/InputUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  NewTabUtils: "resource://gre/modules/NewTabUtils.sys.mjs",
});

/**
 * Get the URLs with the top frecency scores.
 *
 * Note:
 *   - Blocked URLs are excluded
 *   - Allow multiple URLs from the same domain (www vs non-www urls)
 *
 * @param {number} maxUrls
 *   The maximum number of URLs to fetch.
 * @param {number} frecencyThreshold
 *   The minimal frecency score of the URL. Will use the default set by
 *   the upstream module if unspecified. For reference, "100" means one
 *   visit in the past 3 days. see more details at:
 *   `/browser/components/urlbar/docs/ranking.rst`
 * @returns {Array}
 *   An array of URLs. Note that the actual number could be less than `maxUrls`.
 */
export async function getTopFrecentUrls(
  maxUrls,
  frecencyThreshold /* for test */
) {
  const options = {
    ignoreBlocked: true,
    onePerDomain: false,
    includeFavicon: false,
    topsiteFrecency: frecencyThreshold,
    numItems: maxUrls,
  };
  const records = await lazy.NewTabUtils.activityStreamLinks.getTopSites(
    options
  );

  return records.map(site => site.url);
}

/**
 * Get the URLs of the most recent browsing history.
 *
 * Note:
 *   - Blocked URLs are excluded
 *   - Recent bookmarks are excluded
 *   - Recent "Save-to-Pocket" URLs are excluded
 *   - It would only return URLs if the page meta data is present. We can relax
 *     this in the future.
 *   - Multiple URLs may be returned for the same domain
 *
 * @param {number} maxUrls
 *   The maximum number of URLs to fetch.
 * @returns {Array}
 *   An array of URLs. Note that the actual number could be less than `maxUrls`.
 */
export async function getMostRecentUrls(maxUrls) {
  const options = {
    ignoreBlocked: true,
    excludeBookmarks: true,
    excludeHistory: false,
    excludePocket: true,
    withFavicons: false,
    numItems: maxUrls,
  };
  const records = await lazy.NewTabUtils.activityStreamLinks.getHighlights(
    options
  );

  return records.map(site => site.url);
}

/**
 * Get the URLs as a combination of the top frecent and the most recent
 * browsing history.
 *
 * It will fetch `maxUrls` URLs from top frecent URLs and use most recent URLs
 * as a fallback if the former is insufficient. Duplicates will be removed
 * As a result, the returned URLs might be fewer than requested.
 *
 * @param {number} maxUrls
 *   The maximum number of URLs to fetch.
 * @returns {Array}
 *   An array of URLs.
 */
export async function getFrecentRecentCombinedUrls(maxUrls) {
  let urls = await getTopFrecentUrls(maxUrls);
  if (urls.length < maxUrls) {
    const n = Math.round((maxUrls - urls.length) * 1.2); // Over-fetch for deduping
    const recentUrls = await getMostRecentUrls(n);
    urls = dedupUrls(urls, recentUrls).slice(0, maxUrls);
  }

  return urls;
}

/**
 * A helper to deduplicate items from any number of grouped URLs.
 *
 * Note:
 *   - Currently, all the elements (URLs) of the input arrays are treated as keys.
 *   - It doesn't assume the uniqueness within the group, therefore, in-group
 *     duplicates will be deduped as well.
 *
 * @param {Array} groups
 *   Contains an arbitrary number of arrays of URLs.
 * @returns {Array}
 *   An array of unique URLs from the input groups.
 */
function dedupUrls(...groups) {
  const uniques = new Set(groups.flat());
  return [...uniques];
}
PK
!<�Z"�#�#modules/crypto-SDR.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
});

export function LoginManagerCrypto_SDR() {
  this.init();
}

LoginManagerCrypto_SDR.prototype = {
  classID: Components.ID("{dc6c2976-0f73-4f1f-b9ff-3d72b4e28309}"),
  QueryInterface: ChromeUtils.generateQI(["nsILoginManagerCrypto"]),

  __decoderRing: null, // nsSecretDecoderRing service
  get _decoderRing() {
    if (!this.__decoderRing) {
      this.__decoderRing = Cc["@mozilla.org/security/sdr;1"].getService(
        Ci.nsISecretDecoderRing
      );
    }
    return this.__decoderRing;
  },

  __utfConverter: null, // UCS2 <--> UTF8 string conversion
  get _utfConverter() {
    if (!this.__utfConverter) {
      this.__utfConverter = Cc[
        "@mozilla.org/intl/scriptableunicodeconverter"
      ].createInstance(Ci.nsIScriptableUnicodeConverter);
      this.__utfConverter.charset = "UTF-8";
    }
    return this.__utfConverter;
  },

  _utfConverterReset() {
    this.__utfConverter = null;
  },

  _uiBusy: false,

  init() {
    // Check to see if the internal PKCS#11 token has been initialized.
    // If not, set a blank password.
    let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"].getService(
      Ci.nsIPK11TokenDB
    );

    let token = tokenDB.getInternalKeyToken();
    if (token.needsUserInit) {
      this.log("Initializing key3.db with default blank password.");
      token.initPassword("");
    }
  },

  /*
   * encrypt
   *
   * Encrypts the specified string, using the SecretDecoderRing.
   *
   * Returns the encrypted string, or throws an exception if there was a
   * problem.
   */
  encrypt(plainText) {
    let cipherText = null;

    let wasLoggedIn = this.isLoggedIn;
    let canceledMP = false;

    this._uiBusy = !wasLoggedIn;
    try {
      let plainOctet = this._utfConverter.ConvertFromUnicode(plainText);
      plainOctet += this._utfConverter.Finish();
      cipherText = this._decoderRing.encryptString(plainOctet);
    } catch (e) {
      this.log(`Failed to encrypt string with error ${e.name}.`);
      // If the user clicks Cancel, we get NS_ERROR_FAILURE.
      // (unlike decrypting, which gets NS_ERROR_NOT_AVAILABLE).
      if (e.result == Cr.NS_ERROR_FAILURE) {
        canceledMP = true;
        throw Components.Exception(
          "User canceled primary password entry",
          Cr.NS_ERROR_ABORT
        );
      } else {
        throw Components.Exception(
          "Couldn't encrypt string",
          Cr.NS_ERROR_FAILURE
        );
      }
    } finally {
      this._uiBusy = false;
      // If we triggered a primary password prompt, notify observers.
      if (!wasLoggedIn && this.isLoggedIn) {
        this._notifyObservers("passwordmgr-crypto-login");
      } else if (canceledMP) {
        this._notifyObservers("passwordmgr-crypto-loginCanceled");
      }
    }
    return cipherText;
  },

  /*
   * encryptMany
   *
   * Encrypts the specified strings, using the SecretDecoderRing.
   *
   * Returns a promise which resolves with the the encrypted strings,
   * or throws/rejects with an error if there was a problem.
   */
  async encryptMany(plaintexts) {
    if (!Array.isArray(plaintexts) || !plaintexts.length) {
      throw Components.Exception(
        "Need at least one plaintext to encrypt",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    let cipherTexts;

    let wasLoggedIn = this.isLoggedIn;
    let canceledMP = false;

    this._uiBusy = !wasLoggedIn;
    try {
      cipherTexts = await this._decoderRing.asyncEncryptStrings(plaintexts);
    } catch (e) {
      this.log(`Failed to encrypt strings with error ${e.name}.`);
      // If the user clicks Cancel, we get NS_ERROR_FAILURE.
      // (unlike decrypting, which gets NS_ERROR_NOT_AVAILABLE).
      if (e.result == Cr.NS_ERROR_FAILURE) {
        canceledMP = true;
        throw Components.Exception(
          "User canceled primary password entry",
          Cr.NS_ERROR_ABORT
        );
      } else {
        throw Components.Exception(
          "Couldn't encrypt strings",
          Cr.NS_ERROR_FAILURE
        );
      }
    } finally {
      this._uiBusy = false;
      // If we triggered a primary password prompt, notify observers.
      if (!wasLoggedIn && this.isLoggedIn) {
        this._notifyObservers("passwordmgr-crypto-login");
      } else if (canceledMP) {
        this._notifyObservers("passwordmgr-crypto-loginCanceled");
      }
    }
    return cipherTexts;
  },

  /*
   * decrypt
   *
   * Decrypts the specified string, using the SecretDecoderRing.
   *
   * Returns the decrypted string, or throws an exception if there was a
   * problem.
   */
  decrypt(cipherText) {
    let plainText = null;

    let wasLoggedIn = this.isLoggedIn;
    let canceledMP = false;

    this._uiBusy = !wasLoggedIn;
    try {
      let plainOctet;
      plainOctet = this._decoderRing.decryptString(cipherText);
      plainText = this._utfConverter.ConvertToUnicode(plainOctet);
    } catch (e) {
      this.log(
        `Failed to decrypt cipher text of length ${cipherText.length} with error ${e.name}.`
      );

      // In the unlikely event the converter threw, reset it.
      this._utfConverterReset();

      // If the user clicks Cancel, we get NS_ERROR_NOT_AVAILABLE.
      // If the cipherText is bad / wrong key, we get NS_ERROR_FAILURE
      // Wrong passwords are handled by the decoderRing reprompting;
      // we get no notification.
      if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
        canceledMP = true;
        throw Components.Exception(
          "User canceled primary password entry",
          Cr.NS_ERROR_ABORT
        );
      } else {
        throw Components.Exception(
          "Couldn't decrypt string",
          Cr.NS_ERROR_FAILURE
        );
      }
    } finally {
      this._uiBusy = false;
      // If we triggered a primary password prompt, notify observers.
      if (!wasLoggedIn && this.isLoggedIn) {
        this._notifyObservers("passwordmgr-crypto-login");
      } else if (canceledMP) {
        this._notifyObservers("passwordmgr-crypto-loginCanceled");
      }
    }

    return plainText;
  },

  /**
   * Decrypts the specified strings, using the SecretDecoderRing.
   *
   * @resolve {string[]} The decrypted strings. If a string cannot
   * be decrypted, the empty string is returned for that instance.
   * Callers will need to use decrypt() to determine if the encrypted
   * string is invalid or intentionally empty. Throws/reject with
   * an error if there was a problem.
   */
  async decryptMany(cipherTexts) {
    if (!Array.isArray(cipherTexts) || !cipherTexts.length) {
      throw Components.Exception(
        "Need at least one ciphertext to decrypt",
        Cr.NS_ERROR_INVALID_ARG
      );
    }

    let plainTexts = [];

    let wasLoggedIn = this.isLoggedIn;
    let canceledMP = false;

    this._uiBusy = !wasLoggedIn;
    try {
      plainTexts = await this._decoderRing.asyncDecryptStrings(cipherTexts);
    } catch (e) {
      this.log(`Failed to decrypt strings with error ${e.name}.`);
      // If the user clicks Cancel, we get NS_ERROR_NOT_AVAILABLE.
      // If the cipherText is bad / wrong key, we get NS_ERROR_FAILURE
      // Wrong passwords are handled by the decoderRing reprompting;
      // we get no notification.
      if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
        canceledMP = true;
        throw Components.Exception(
          "User canceled primary password entry",
          Cr.NS_ERROR_ABORT
        );
      } else {
        throw Components.Exception(
          "Couldn't decrypt strings: " + e.result,
          Cr.NS_ERROR_FAILURE
        );
      }
    } finally {
      this._uiBusy = false;
      // If we triggered a primary password prompt, notify observers.
      if (!wasLoggedIn && this.isLoggedIn) {
        this._notifyObservers("passwordmgr-crypto-login");
      } else if (canceledMP) {
        this._notifyObservers("passwordmgr-crypto-loginCanceled");
      }
    }
    return plainTexts;
  },

  /*
   * uiBusy
   */
  get uiBusy() {
    return this._uiBusy;
  },

  /*
   * isLoggedIn
   */
  get isLoggedIn() {
    let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"].getService(
      Ci.nsIPK11TokenDB
    );
    let token = tokenDB.getInternalKeyToken();
    return !token.hasPassword || token.isLoggedIn();
  },

  /*
   * defaultEncType
   */
  get defaultEncType() {
    return Ci.nsILoginManagerCrypto.ENCTYPE_SDR;
  },

  /*
   * _notifyObservers
   */
  _notifyObservers(topic) {
    this.log(`Prompted for a primary password, notifying for ${topic}`);
    Services.obs.notifyObservers(null, topic);
  },
}; // end of nsLoginManagerCrypto_SDR implementation

ChromeUtils.defineLazyGetter(LoginManagerCrypto_SDR.prototype, "log", () => {
  let logger = lazy.LoginHelper.createLogger("Login crypto");
  return logger.log.bind(logger);
});
PK
!<?���modules/ctypes.sys.mjs/* -*-  indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This is the js module for ctypes. Import it like so:
 *   const { ctypes } =
 *     ChromeUtils.importESModule("resource://gre/modules/ctypes.sys.mjs");
 *
 * This will create a 'ctypes' object, which provides an interface to describe
 * and instantiate C types and call C functions from a dynamic library.
 */

// Initialize the ctypes object. You do not need to do this yourself.
const init = Cc["@mozilla.org/jsctypes;1"].createInstance();
init();

export const ctypes = globalThis.ctypes;
PK
!<ـ�VV$modules/handlers/HandlerList.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

export const kHandlerListVersion = 1;

export const kHandlerList = {
  default: {
    schemes: {
      mailto: {
        handlers: [
          {
            name: "Gmail",
            uriTemplate: "https://mail.google.com/mail/?extsrc=mailto&url=%s",
          },
        ],
      },
    },
  },
  cs: {
    schemes: {
      mailto: {
        handlers: [
          {
            name: "Seznam",
            uriTemplate: "https://email.seznam.cz/newMessageScreen?mailto=%s",
          },
          {
            name: "Gmail",
            uriTemplate: "https://mail.google.com/mail/?extsrc=mailto&url=%s",
          },
        ],
      },
    },
  },
  "es-CL": {
    schemes: {
      mailto: {
        handlers: [
          {
            name: "Gmail",
            uriTemplate: "https://mail.google.com/mail/?extsrc=mailto&url=%s",
          },
          {
            name: "Outlook",
            uriTemplate:
              "https://outlook.live.com/default.aspx?rru=compose&to=%s",
          },
        ],
      },
    },
  },
  "ja-JP-mac": {
    schemes: {
      mailto: {
        handlers: [
          {
            name: "Yahoo!メール",
            uriTemplate: "https://mail.yahoo.co.jp/compose/?To=%s",
          },
          {
            name: "Gmail",
            uriTemplate: "https://mail.google.com/mail/?extsrc=mailto&url=%s",
          },
        ],
      },
    },
  },
  ja: {
    schemes: {
      mailto: {
        handlers: [
          {
            name: "Yahoo!メール",
            uriTemplate: "https://mail.yahoo.co.jp/compose/?To=%s",
          },
          {
            name: "Gmail",
            uriTemplate: "https://mail.google.com/mail/?extsrc=mailto&url=%s",
          },
        ],
      },
    },
  },
  kk: {
    schemes: {
      mailto: {
        handlers: [
          {
            name: "Яндекс.Почта",
            uriTemplate: "https://mail.yandex.ru/compose?mailto=%s",
          },
          {
            name: "Mail.Ru",
            uriTemplate: "https://e.mail.ru/cgi-bin/sentmsg?mailto=%s",
          },
          {
            name: "Gmail",
            uriTemplate: "https://mail.google.com/mail/?extsrc=mailto&url=%s",
          },
        ],
      },
    },
  },
  ltg: {
    schemes: {
      mailto: {
        handlers: [
          {
            name: "Gmail",
            uriTemplate: "https://mail.google.com/mail/?extsrc=mailto&url=%s",
          },
          {
            name: "inbox.lv mail",
            uriTemplate: "https://mail.inbox.lv/compose?to=%s",
          },
        ],
      },
    },
  },
  lv: {
    schemes: {
      mailto: {
        handlers: [
          {
            name: "Gmail",
            uriTemplate: "https://mail.google.com/mail/?extsrc=mailto&url=%s",
          },
          {
            name: "inbox.lv mail",
            uriTemplate: "https://mail.inbox.lv/compose?to=%s",
          },
        ],
      },
    },
  },
  pl: {
    schemes: {
      mailto: {
        handlers: [
          {
            name: "Poczta Interia.pl",
            uriTemplate: "https://poczta.interia.pl/mh/?mailto=%s",
          },
          {
            name: "Gmail",
            uriTemplate: "https://mail.google.com/mail/?extsrc=mailto&url=%s",
          },
        ],
      },
    },
  },
  ru: {
    schemes: {
      mailto: {
        handlers: [
          {
            name: "Яндекс.Почту",
            uriTemplate: "https://mail.yandex.ru/compose?mailto=%s",
          },
          {
            name: "Mail.Ru",
            uriTemplate: "https://e.mail.ru/cgi-bin/sentmsg?mailto=%s",
          },
          {
            name: "Gmail",
            uriTemplate: "https://mail.google.com/mail/?extsrc=mailto&url=%s",
          },
        ],
      },
    },
  },
  uk: {
    schemes: {
      mailto: {
        handlers: [
          {
            name: "Gmail",
            uriTemplate: "https://mail.google.com/mail/?extsrc=mailto&url=%s",
          },
          {
            name: "Outlook",
            uriTemplate:
              "https://outlook.live.com/default.aspx?rru=compose&to=%s",
          },
        ],
      },
    },
  },
  uz: {
    schemes: {
      mailto: {
        handlers: [
          {
            name: "Gmail",
            uriTemplate: "https://mail.google.com/mail/?extsrc=mailto&url=%s",
          },
          {
            name: "Mail.Ru",
            uriTemplate: "https://e.mail.ru/cgi-bin/sentmsg?mailto=%s",
          },
        ],
      },
    },
  },
};
PK
!<�9���modules/jsdebugger.sys.mjs/* -*-  indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This is the js module for Debugger. Import it like so:
 *   const { addDebuggerToGlobal } = ChromeUtils.importESModule(
 *     "resource://gre/modules/jsdebugger.sys.mjs"
 *   );
 *   addDebuggerToGlobal(globalThis);
 *
 * This will create a 'Debugger' object, which provides an interface to debug
 * JavaScript code running in other compartments in the same process, on the
 * same thread.
 *
 * For documentation on the API, see:
 *   https://developer.mozilla.org/en-US/docs/Tools/Debugger-API
 */

const init = Cc["@mozilla.org/jsdebugger;1"].createInstance(Ci.IJSDebugger);

export function addDebuggerToGlobal(global) {
  init.addClass(global);
  initPromiseDebugging(global);
}

// Defines the Debugger in a sandbox global in a separate compartment. This
// ensures the debugger and debuggee are in different compartments.
export function addSandboxedDebuggerToGlobal(global) {
  const sb = Cu.Sandbox(global, { freshCompartment: true });
  addDebuggerToGlobal(sb);
  global.Debugger = sb.Debugger;
}

function initPromiseDebugging(global) {
  if (global.Debugger.Object.prototype.PromiseDebugging) {
    return;
  }

  // If the PromiseDebugging object doesn't have all legacy functions, we're
  // using the new accessors on Debugger.Object already.
  if (!PromiseDebugging.getDependentPromises) {
    return;
  }

  // Otherwise, polyfill them using PromiseDebugging.
  global.Debugger.Object.prototype.PromiseDebugging = PromiseDebugging;
  global.eval(polyfillSource);
}

const polyfillSource = `
  Object.defineProperty(Debugger.Object.prototype, "promiseState", {
    get() {
      const state = this.PromiseDebugging.getState(this.unsafeDereference());
      return {
        state: state.state,
        value: this.makeDebuggeeValue(state.value),
        reason: this.makeDebuggeeValue(state.reason)
      };
    }
  });
  Object.defineProperty(Debugger.Object.prototype, "promiseLifetime", {
    get() {
      return this.PromiseDebugging.getPromiseLifetime(this.unsafeDereference());
    }
  });
  Object.defineProperty(Debugger.Object.prototype, "promiseTimeToResolution", {
    get() {
      return this.PromiseDebugging.getTimeToSettle(this.unsafeDereference());
    }
  });
  Object.defineProperty(Debugger.Object.prototype, "promiseDependentPromises", {
    get() {
      let promises = this.PromiseDebugging.getDependentPromises(this.unsafeDereference());
      return promises.map(p => this.makeDebuggeeValue(p));
    }
  });
  Object.defineProperty(Debugger.Object.prototype, "promiseAllocationSite", {
    get() {
      return this.PromiseDebugging.getAllocationStack(this.unsafeDereference());
    }
  });
  Object.defineProperty(Debugger.Object.prototype, "promiseResolutionSite", {
    get() {
      let state = this.promiseState.state;
      if (state === "fulfilled") {
        return this.PromiseDebugging.getFullfillmentStack(this.unsafeDereference());
      } else {
        return this.PromiseDebugging.getRejectionStack(this.unsafeDereference());
      }
    }
  });
`;
PK
!<�L��MMmodules/kvstore.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This module wraps the nsIKeyValue* interfaces in a Promise-based API.
 * To use it, import it, then call the KeyValueService.getOrCreate() method
 * with a database's path and (optionally) its name:
 *
 * ```
 *     let { keyValueService } =
 *       ChromeUtils.importESModule("resource://gre/modules/kvstore.sys.mjs");
 *     let database = await KeyValueService.getOrCreate(path, name);
 * ```
 *
 * See the documentation in nsIKeyValue.idl for more information about the API
 * for key/value storage.
 */

function promisify(fn, ...args) {
  return new Promise((resolve, reject) => {
    fn({ resolve, reject }, ...args);
  });
}

export class KeyValueService {
  static RecoveryStrategy = {
    ERROR: Ci.nsIKeyValueService.ERROR,
    DISCARD: Ci.nsIKeyValueService.DISCARD,
    RENAME: Ci.nsIKeyValueService.RENAME,
  };

  static #service = Cc["@mozilla.org/key-value-service;1"].getService(
    Ci.nsIKeyValueService
  );

  static async getOrCreate(dir, name) {
    return new KeyValueDatabase(
      await promisify(this.#service.getOrCreate, dir, name)
    );
  }

  static async getOrCreateWithOptions(
    dir,
    name,
    { strategy = Ci.nsIKeyValueService.RENAME } = {}
  ) {
    return new KeyValueDatabase(
      await promisify(this.#service.getOrCreateWithOptions, dir, name, strategy)
    );
  }
}

/**
 * An experimental key-value storage service that uses
 * SQLite for persistence.
 */
export class SQLiteKeyValueService {
  static #service = Cc["@mozilla.org/sqlite-key-value-service;1"].getService(
    Ci.nsIKeyValueService
  );

  static async getOrCreate(dir, name) {
    return new KeyValueDatabase(
      await promisify(this.#service.getOrCreate, dir, name)
    );
  }
}

/**
 * A class that wraps an nsIKeyValueDatabase in a Promise-based API.
 *
 * This class isn't exported, so you can't instantiate it directly, but you
 * can retrieve an instance of this class via KeyValueService.getOrCreate():
 *
 * ```
 *     const database = await KeyValueService.getOrCreate(path, name);
 * ```
 *
 * You can then call its put(), get(), has(), and delete() methods to access
 * and manipulate key/value pairs:
 *
 * ```
 *     await database.put("foo", 1);
 *     await database.get("foo") === 1; // true
 *     await database.has("foo"); // true
 *     await database.delete("foo");
 *     await database.has("foo"); // false
 * ```
 *
 * You can also call writeMany() to put/delete multiple key/value pairs:
 *
 * ```
 *     await database.writeMany({
 *       key1: "value1",
 *       key2: "value2",
 *       key3: "value3",
 *       key4: null, // delete
 *     });
 * ```
 *
 * And you can call its enumerate() method to retrieve a KeyValueEnumerator,
 * which is described below.
 */
class KeyValueDatabase {
  constructor(database) {
    this.database = database;
  }

  isEmpty() {
    return promisify(this.database.isEmpty);
  }

  count() {
    return promisify(this.database.count);
  }

  size() {
    return promisify(this.database.size);
  }

  put(key, value) {
    return promisify(this.database.put, key, value);
  }

  /**
   * Writes multiple key/value pairs to the database.
   *
   * Note:
   *   * Each write could be either put or delete.
   *   * Given multiple values with the same key, only the last value will be stored.
   *   * If the same key gets put and deleted for multiple times, the final state
   *     of that key is subject to the ordering of the put(s) and delete(s).
   *
   * @param pairs Pairs could be any of following types:
   *        * An Object, all its properties and the corresponding values will
   *          be used as key value pairs. A property with null or undefined indicating
   *          a deletion.
   *        * An Array or an iterable whose elements are key-value pairs. such as
   *          [["key1", "value1"], ["key2", "value2"]]. Use a pair with value null
   *          to delete a key-value pair, e.g. ["delete-key", null].
   *        * A Map. A key with null or undefined value indicating a deletion.
   * @return A promise that is fulfilled when all the key/value pairs are written
   *         to the database.
   */
  writeMany(pairs) {
    if (!pairs) {
      throw new Error("writeMany(): unexpected argument.");
    }

    let entries;

    if (
      pairs instanceof Map ||
      pairs instanceof Array ||
      typeof pairs[Symbol.iterator] === "function"
    ) {
      try {
        // Let Map constructor validate the argument. Note that Map remembers
        // the original insertion order of the keys, which satisfies the ordering
        // premise of this function.
        const map = pairs instanceof Map ? pairs : new Map(pairs);
        entries = Array.from(map, ([key, value]) => ({ key, value }));
      } catch (error) {
        throw new Error("writeMany(): unexpected argument.");
      }
    } else if (typeof pairs === "object") {
      entries = Array.from(Object.entries(pairs), ([key, value]) => ({
        key,
        value,
      }));
    } else {
      throw new Error("writeMany(): unexpected argument.");
    }

    if (entries.length) {
      return promisify(this.database.writeMany, entries);
    }
    return Promise.resolve();
  }

  has(key) {
    return promisify(this.database.has, key);
  }

  get(key, defaultValue) {
    return promisify(this.database.get, key, defaultValue);
  }

  delete(key) {
    return promisify(this.database.delete, key);
  }

  clear() {
    return promisify(this.database.clear);
  }

  async enumerate(from_key, to_key) {
    return new KeyValueEnumerator(
      await promisify(this.database.enumerate, from_key, to_key)
    );
  }

  async close() {
    return promisify(this.database.close);
  }
}

/**
 * A class that wraps an nsIKeyValueEnumerator in a Promise-based API.
 *
 * This class isn't exported, so you can't instantiate it directly, but you
 * can retrieve an instance of this class by calling enumerate() on an instance
 * of KeyValueDatabase:
 *
 * ```
 *     const database = await KeyValueService.getOrCreate(path, name);
 *     const enumerator = await database.enumerate();
 * ```
 *
 * And then iterate pairs via its hasMoreElements() and getNext() methods:
 *
 * ```
 *     while (enumerator.hasMoreElements()) {
 *       const { key, value } = enumerator.getNext();
 *       …
 *     }
 * ```
 *
 * Or with a `for...of` statement:
 *
 * ```
 *     for (const { key, value } of enumerator) {
 *         …
 *     }
 * ```
 */
class KeyValueEnumerator {
  constructor(enumerator) {
    this.enumerator = enumerator;
  }

  hasMoreElements() {
    return this.enumerator.hasMoreElements();
  }

  getNext() {
    return this.enumerator.getNext();
  }

  *[Symbol.iterator]() {
    while (this.enumerator.hasMoreElements()) {
      yield this.enumerator.getNext();
    }
  }
}
PK
!<��ɕL L  modules/media/IdpSandbox.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { NetUtil } from "resource://gre/modules/NetUtil.sys.mjs";

/** This little class ensures that redirects maintain an https:// origin */
function RedirectHttpsOnly() {}

RedirectHttpsOnly.prototype = {
  asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
    if (newChannel.URI.scheme !== "https") {
      callback.onRedirectVerifyCallback(Cr.NS_ERROR_ABORT);
    } else {
      callback.onRedirectVerifyCallback(Cr.NS_OK);
    }
  },

  getInterface(iid) {
    return this.QueryInterface(iid);
  },
  QueryInterface: ChromeUtils.generateQI(["nsIChannelEventSink"]),
};

/** This class loads a resource into a single string. ResourceLoader.load() is
 * the entry point. */
function ResourceLoader(res, rej) {
  this.resolve = res;
  this.reject = rej;
  this.data = "";
}

/** Loads the identified https:// URL. */
ResourceLoader.load = function (uri, doc) {
  return new Promise((resolve, reject) => {
    let listener = new ResourceLoader(resolve, reject);
    let ioChannel = NetUtil.newChannel({
      uri,
      loadingNode: doc,
      securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
      contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_SCRIPT,
    });

    ioChannel.loadGroup = doc.documentLoadGroup.QueryInterface(Ci.nsILoadGroup);
    ioChannel.notificationCallbacks = new RedirectHttpsOnly();
    ioChannel.asyncOpen(listener);
  });
};

ResourceLoader.prototype = {
  onDataAvailable(request, input, offset, count) {
    let stream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
      Ci.nsIScriptableInputStream
    );
    stream.init(input);
    this.data += stream.read(count);
  },

  onStartRequest() {},

  onStopRequest(request, status) {
    if (Components.isSuccessCode(status)) {
      var statusCode = request.QueryInterface(Ci.nsIHttpChannel).responseStatus;
      if (statusCode === 200) {
        this.resolve({ request, data: this.data });
      } else {
        this.reject(new Error("Non-200 response from server: " + statusCode));
      }
    } else {
      this.reject(new Error("Load failed: " + status));
    }
  },

  getInterface(iid) {
    return this.QueryInterface(iid);
  },
  QueryInterface: ChromeUtils.generateQI(["nsIStreamListener"]),
};

/**
 * A simple implementation of the WorkerLocation interface.
 */
function createLocationFromURI(uri) {
  return {
    href: uri.spec,
    protocol: uri.scheme + ":",
    host: uri.host + (uri.port >= 0 ? ":" + uri.port : ""),
    port: uri.port,
    hostname: uri.host,
    pathname: uri.pathQueryRef.replace(/[#\?].*/, ""),
    search: uri.pathQueryRef.replace(/^[^\?]*/, "").replace(/#.*/, ""),
    hash: uri.hasRef ? "#" + uri.ref : "",
    origin: uri.prePath,
    toString() {
      return uri.spec;
    },
  };
}

/**
 * A javascript sandbox for running an IdP.
 *
 * @param domain (string) the domain of the IdP
 * @param protocol (string?) the protocol of the IdP [default: 'default']
 * @param win (obj) the current window
 * @throws if the domain or protocol aren't valid
 */
export function IdpSandbox(domain, protocol, win) {
  this.source = IdpSandbox.createIdpUri(domain, protocol || "default");
  this.active = null;
  this.sandbox = null;
  this.window = win;
}

IdpSandbox.checkDomain = function (domain) {
  if (!domain || typeof domain !== "string") {
    throw new Error(
      "Invalid domain for identity provider: " +
        "must be a non-zero length string"
    );
  }
};

/**
 * Checks that the IdP protocol is superficially sane.  In particular, we don't
 * want someone adding relative paths (e.g., '../../myuri'), which could be used
 * to move outside of /.well-known/ and into space that they control.
 */
IdpSandbox.checkProtocol = function (protocol) {
  let message = "Invalid protocol for identity provider: ";
  if (!protocol || typeof protocol !== "string") {
    throw new Error(message + "must be a non-zero length string");
  }
  if (decodeURIComponent(protocol).match(/[\/\\]/)) {
    throw new Error(message + "must not include '/' or '\\'");
  }
};

/**
 * Turns a domain and protocol into a URI.  This does some aggressive checking
 * to make sure that we aren't being fooled somehow.  Throws on fooling.
 */
IdpSandbox.createIdpUri = function (domain, protocol) {
  IdpSandbox.checkDomain(domain);
  IdpSandbox.checkProtocol(protocol);

  let message = "Invalid IdP parameters: ";
  try {
    let wkIdp = "https://" + domain + "/.well-known/idp-proxy/" + protocol;
    let uri = Services.io.newURI(wkIdp);

    if (uri.hostPort !== domain) {
      throw new Error(message + "domain is invalid");
    }
    if (uri.pathQueryRef.indexOf("/.well-known/idp-proxy/") !== 0) {
      throw new Error(message + "must produce a /.well-known/idp-proxy/ URI");
    }

    return uri;
  } catch (e) {
    if (
      typeof e.result !== "undefined" &&
      e.result === Cr.NS_ERROR_MALFORMED_URI
    ) {
      throw new Error(message + "must produce a valid URI");
    }
    throw e;
  }
};

IdpSandbox.prototype = {
  isSame(domain, protocol) {
    return this.source.spec === IdpSandbox.createIdpUri(domain, protocol).spec;
  },

  start() {
    if (!this.active) {
      this.active = ResourceLoader.load(this.source, this.window.document).then(
        result => this._createSandbox(result)
      );
    }
    return this.active;
  },

  // Provides the sandbox with some useful facilities.  Initially, this is only
  // a minimal set; it is far easier to add more as the need arises, than to
  // take them back if we discover a mistake.
  _populateSandbox(uri) {
    this.sandbox.location = Cu.cloneInto(
      createLocationFromURI(uri),
      this.sandbox,
      { cloneFunctions: true }
    );
  },

  _createSandbox(result) {
    let principal = Services.scriptSecurityManager.getChannelResultPrincipal(
      result.request
    );

    this.sandbox = Cu.Sandbox(principal, {
      sandboxName: "IdP-" + this.source.host,
      wantComponents: false,
      wantExportHelpers: false,
      wantGlobalProperties: [
        "indexedDB",
        "XMLHttpRequest",
        "TextEncoder",
        "TextDecoder",
        "URL",
        "URLSearchParams",
        "atob",
        "btoa",
        "Blob",
        "crypto",
        "rtcIdentityProvider",
        "fetch",
      ],
    });
    let registrar = this.sandbox.rtcIdentityProvider;
    if (!Cu.isXrayWrapper(registrar)) {
      throw new Error("IdP setup failed");
    }

    // have to use the ultimate URI, not the starting one to avoid
    // that origin stealing from the one that redirected to it
    this._populateSandbox(result.request.URI);
    try {
      Cu.evalInSandbox(
        result.data,
        this.sandbox,
        "latest",
        result.request.URI.spec,
        1
      );
    } catch (e) {
      // These can be passed straight on, because they are explicitly labelled
      // as being IdP errors by the IdP and we drop line numbers as a result.
      if (e.name === "IdpError" || e.name === "IdpLoginError") {
        throw e;
      }
      this._logError(e);
      throw new Error("Error in IdP, check console for details");
    }

    if (!registrar.hasIdp) {
      throw new Error("IdP failed to call rtcIdentityProvider.register()");
    }
    return registrar;
  },

  // Capture all the details from the error and log them to the console.  This
  // can't rethrow anything else because that could leak information about the
  // internal workings of the IdP across origins.
  _logError(e) {
    let winID = this.window.windowGlobalChild.innerWindowId;
    let scriptError = Cc["@mozilla.org/scripterror;1"].createInstance(
      Ci.nsIScriptError
    );
    scriptError.initWithWindowID(
      e.message,
      e.fileName,
      e.lineNumber,
      e.columnNumber,
      Ci.nsIScriptError.errorFlag,
      "content javascript",
      winID
    );
    Services.console.logMessage(scriptError);
  },

  stop() {
    if (this.sandbox) {
      Cu.nukeSandbox(this.sandbox);
    }
    this.sandbox = null;
    this.active = null;
  },

  toString() {
    return this.source.spec;
  },
};
PK
!<!7)E�E�$modules/media/PeerConnection.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  PeerConnectionIdp: "resource://gre/modules/media/PeerConnectionIdp.sys.mjs",
});

const PC_CONTRACT = "@mozilla.org/dom/peerconnection;1";
const PC_OBS_CONTRACT = "@mozilla.org/dom/peerconnectionobserver;1";
const PC_ICE_CONTRACT = "@mozilla.org/dom/rtcicecandidate;1";
const PC_SESSION_CONTRACT = "@mozilla.org/dom/rtcsessiondescription;1";
const PC_STATIC_CONTRACT = "@mozilla.org/dom/peerconnectionstatic;1";
const PC_COREQUEST_CONTRACT = "@mozilla.org/dom/createofferrequest;1";

const PC_CID = Components.ID("{bdc2e533-b308-4708-ac8e-a8bfade6d851}");
const PC_OBS_CID = Components.ID("{d1748d4c-7f6a-4dc5-add6-d55b7678537e}");
const PC_ICE_CID = Components.ID("{02b9970c-433d-4cc2-923d-f7028ac66073}");
const PC_SESSION_CID = Components.ID("{1775081b-b62d-4954-8ffe-a067bbf508a7}");
const PC_MANAGER_CID = Components.ID("{7293e901-2be3-4c02-b4bd-cbef6fc24f78}");
const PC_STATIC_CID = Components.ID("{0fb47c47-a205-4583-a9fc-cbadf8c95880}");
const PC_COREQUEST_CID = Components.ID(
  "{74b2122d-65a8-4824-aa9e-3d664cb75dc2}"
);

function logWebRTCMsg(msg, file, line, flag, win) {
  let scriptErrorClass = Cc["@mozilla.org/scripterror;1"];
  let scriptError = scriptErrorClass.createInstance(Ci.nsIScriptError);
  scriptError.initWithWindowID(
    `WebRTC: ${msg}`,
    file,
    line,
    0,
    flag,
    "content javascript",
    win.windowGlobalChild.innerWindowId
  );
  Services.console.logMessage(scriptError);
  if (
    Services.prefs.getBoolPref("media.peerconnection.treat_warnings_as_errors")
  ) {
    throw new win.TypeError(msg);
  }
}

let setupPrototype = (_class, dict) => {
  _class.prototype.classDescription = _class.name;
  Object.assign(_class.prototype, dict);
};

// Global list of PeerConnection objects, so they can be cleaned up when
// a page is torn down. (Maps inner window ID to an array of PC objects).
export class GlobalPCList {
  constructor() {
    this._list = {};
    this._networkdown = false; // XXX Need to query current state somehow
    this._lifecycleobservers = {};
    this._nextId = 1;
    Services.obs.addObserver(this, "inner-window-destroyed", true);
    Services.obs.addObserver(this, "profile-change-net-teardown", true);
    Services.obs.addObserver(this, "network:offline-about-to-go-offline", true);
    Services.obs.addObserver(this, "network:offline-status-changed", true);
    Services.obs.addObserver(this, "gmp-plugin-crash", true);
    Services.obs.addObserver(this, "PeerConnection:response:allow", true);
    Services.obs.addObserver(this, "PeerConnection:response:deny", true);
    if (Services.cpmm) {
      Services.cpmm.addMessageListener("gmp-plugin-crash", this);
    }
  }

  notifyLifecycleObservers(pc, type) {
    for (var key of Object.keys(this._lifecycleobservers)) {
      this._lifecycleobservers[key](pc, pc._winID, type);
    }
  }

  addPC(pc) {
    let winID = pc._winID;
    if (this._list[winID]) {
      this._list[winID].push(Cu.getWeakReference(pc));
    } else {
      this._list[winID] = [Cu.getWeakReference(pc)];
    }
    pc._globalPCListId = this._nextId++;
    this.removeNullRefs(winID);
  }

  findPC(globalPCListId) {
    for (let winId in this._list) {
      if (this._list.hasOwnProperty(winId)) {
        for (let pcref of this._list[winId]) {
          let pc = pcref.get();
          if (pc && pc._globalPCListId == globalPCListId) {
            return pc;
          }
        }
      }
    }
    return null;
  }

  removeNullRefs(winID) {
    if (this._list[winID] === undefined) {
      return;
    }
    this._list[winID] = this._list[winID].filter(function (e) {
      return e.get() !== null;
    });

    if (this._list[winID].length === 0) {
      delete this._list[winID];
    }
  }

  handleGMPCrash(data) {
    let broadcastPluginCrash = function (list, winID, pluginID, pluginName) {
      if (list.hasOwnProperty(winID)) {
        list[winID].forEach(function (pcref) {
          let pc = pcref.get();
          if (pc) {
            pc._pc.pluginCrash(pluginID, pluginName);
          }
        });
      }
    };

    // a plugin crashed; if it's associated with any of our PCs, fire an
    // event to the DOM window
    for (let winId in this._list) {
      broadcastPluginCrash(this._list, winId, data.pluginID, data.pluginName);
    }
  }

  receiveMessage({ name, data }) {
    if (name == "gmp-plugin-crash") {
      this.handleGMPCrash(data);
    }
  }

  observe(subject, topic, data) {
    let cleanupPcRef = function (pcref) {
      let pc = pcref.get();
      if (pc) {
        pc._suppressEvents = true;
        pc.close();
      }
    };

    let cleanupWinId = function (list, winID) {
      if (list.hasOwnProperty(winID)) {
        list[winID].forEach(cleanupPcRef);
        delete list[winID];
      }
    };

    if (topic == "inner-window-destroyed") {
      let winID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
      cleanupWinId(this._list, winID);

      if (this._lifecycleobservers.hasOwnProperty(winID)) {
        delete this._lifecycleobservers[winID];
      }
    } else if (
      topic == "profile-change-net-teardown" ||
      topic == "network:offline-about-to-go-offline"
    ) {
      // As Necko doesn't prevent us from accessing the network we still need to
      // monitor the network offline/online state here. See bug 1326483
      this._networkdown = true;
    } else if (topic == "network:offline-status-changed") {
      if (data == "offline") {
        this._networkdown = true;
      } else if (data == "online") {
        this._networkdown = false;
      }
    } else if (topic == "gmp-plugin-crash") {
      if (subject instanceof Ci.nsIWritablePropertyBag2) {
        let pluginID = subject.getPropertyAsUint32("pluginID");
        let pluginName = subject.getPropertyAsAString("pluginName");
        let data = { pluginID, pluginName };
        this.handleGMPCrash(data);
      }
    } else if (
      topic == "PeerConnection:response:allow" ||
      topic == "PeerConnection:response:deny"
    ) {
      var pc = this.findPC(data);
      if (pc) {
        if (topic == "PeerConnection:response:allow") {
          pc._settlePermission.allow();
        } else {
          let err = new pc._win.DOMException(
            "The request is not allowed by " +
              "the user agent or the platform in the current context.",
            "NotAllowedError"
          );
          pc._settlePermission.deny(err);
        }
      }
    }
  }

  _registerPeerConnectionLifecycleCallback(winID, cb) {
    this._lifecycleobservers[winID] = cb;
  }
}

setupPrototype(GlobalPCList, {
  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
  ]),
  classID: PC_MANAGER_CID,
});

var _globalPCList = new GlobalPCList();

// Parses grammar in RFC5245 section 15 and ICE TCP from RFC6544 section 4.5.
function parseCandidate(line) {
  const match = line.match(
    /^(a=)?candidate:([A-Za-z0-9+\/]{1,32}) (\d+) (UDP|TCP) (\d+) ([A-Za-z0-9.:-]+) (\d+) typ (host|srflx|prflx|relay)(?: raddr ([A-Za-z0-9.:-]+) rport (\d+))?(.*)$/i
  );
  if (!match) {
    return null;
  }
  const candidate = {
    foundation: match[2],
    componentId: parseInt(match[3], 10),
    transport: match[4],
    priority: parseInt(match[5], 10),
    address: match[6],
    port: parseInt(match[7], 10),
    type: match[8],
    relatedAddress: match[9],
    relatedPort: match[10],
  };
  if (candidate.componentId < 1 || candidate.componentId > 256) {
    return null;
  }
  if (candidate.priority < 0 || candidate.priority > 4294967295) {
    return null;
  }
  if (candidate.port < 0 || candidate.port > 65535) {
    return null;
  }
  candidate.component = { 1: "rtp", 2: "rtcp" }[candidate.componentId] || null;
  candidate.protocol =
    { udp: "udp", tcp: "tcp" }[candidate.transport.toLowerCase()] || null;

  const tcpTypeMatch = match[11].match(/tcptype (\S+)/i);
  if (tcpTypeMatch) {
    candidate.tcpType = tcpTypeMatch[1];
    if (
      candidate.protocol != "tcp" ||
      !["active", "passive", "so"].includes(candidate.tcpType)
    ) {
      return null;
    }
  }
  return candidate;
}

export class RTCIceCandidate {
  init(win) {
    this._win = win;
  }

  __init(dict) {
    if (dict.sdpMid == null && dict.sdpMLineIndex == null) {
      throw new this._win.TypeError(
        "Either sdpMid or sdpMLineIndex must be specified"
      );
    }
    Object.assign(this, dict);
    const candidate = parseCandidate(this.candidate);
    if (!candidate) {
      return;
    }
    Object.assign(this, candidate);
  }

  toJSON() {
    return {
      candidate: this.candidate,
      sdpMid: this.sdpMid,
      sdpMLineIndex: this.sdpMLineIndex,
      usernameFragment: this.usernameFragment,
    };
  }
}

setupPrototype(RTCIceCandidate, {
  classID: PC_ICE_CID,
  contractID: PC_ICE_CONTRACT,
  QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
});

export class RTCSessionDescription {
  init(win) {
    this._win = win;
    this._winID = this._win.windowGlobalChild.innerWindowId;
    this._legacyPref = Services.prefs.getBoolPref(
      "media.peerconnection.description.legacy.enabled"
    );
  }

  __init({ type, sdp }) {
    Object.assign(this, { _type: type, _sdp: sdp });
  }

  get type() {
    return this._type;
  }
  set type(type) {
    if (!this._legacyPref) {
      // TODO: this throws even in sloppy mode. Remove in bug 1883992
      throw new this._win.TypeError("setting getter-only property type");
    }
    this.warn();
    this._type = type;
  }

  get sdp() {
    return this._sdp;
  }
  set sdp(sdp) {
    if (!this._legacyPref) {
      // TODO: this throws even in sloppy mode. Remove in bug 1883992
      throw new this._win.TypeError("setting getter-only property sdp");
    }
    this.warn();
    this._sdp = sdp;
  }

  warn() {
    if (!this._warned) {
      // Warn once per RTCSessionDescription about deprecated writable usage.
      if (this._legacyPref) {
        this.logMsg(
          "RTCSessionDescription's members are readonly! " +
            "Writing to them is deprecated and will break soon!",
          Ci.nsIScriptError.warningFlag
        );
      } else {
        this.logMsg(
          "RTCSessionDescription's members are readonly! " +
            "Writing to them no longer works!",
          Ci.nsIScriptError.errorFlag
        );
      }
      this._warned = true;
    }
  }

  logMsg(msg, flag) {
    let err = this._win.Error();
    logWebRTCMsg(msg, err.fileName, err.lineNumber, flag, this._win);
  }
}

setupPrototype(RTCSessionDescription, {
  classID: PC_SESSION_CID,
  contractID: PC_SESSION_CONTRACT,
  QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
});

// Records PC related telemetry
class PeerConnectionTelemetry {
  // ICE connection state enters connected or completed.
  recordConnected() {
    Services.telemetry.scalarAdd("webrtc.peerconnection.connected", 1);
    this.recordConnected = () => {};
  }
  // DataChannel is created
  _recordDataChannelCreated() {
    Services.telemetry.scalarAdd(
      "webrtc.peerconnection.datachannel_created",
      1
    );
    this._recordDataChannelCreated = () => {};
  }
  // DataChannel initialized with maxRetransmitTime
  _recordMaxRetransmitTime(maxRetransmitTime) {
    if (maxRetransmitTime === undefined) {
      return false;
    }
    Services.telemetry.scalarAdd(
      "webrtc.peerconnection.datachannel_max_retx_used",
      1
    );
    this._recordMaxRetransmitTime = () => true;
    return true;
  }
  // DataChannel initialized with maxPacketLifeTime
  _recordMaxPacketLifeTime(maxPacketLifeTime) {
    if (maxPacketLifeTime === undefined) {
      return false;
    }
    Services.telemetry.scalarAdd(
      "webrtc.peerconnection.datachannel_max_life_used",
      1
    );
    this._recordMaxPacketLifeTime = () => true;
    return true;
  }
  // DataChannel initialized
  recordDataChannelInit(maxRetransmitTime, maxPacketLifeTime) {
    const retxUsed = this._recordMaxRetransmitTime(maxRetransmitTime);
    if (this._recordMaxPacketLifeTime(maxPacketLifeTime) && retxUsed) {
      Services.telemetry.scalarAdd(
        "webrtc.peerconnection.datachannel_max_retx_and_life_used",
        1
      );
      this.recordDataChannelInit = () => {};
    }
    this._recordDataChannelCreated();
  }
}

export class RTCPeerConnection {
  constructor() {
    this._pc = null;
    this._closed = false;
    this._pendingLocalDescription = null;
    this._pendingRemoteDescription = null;
    this._currentLocalDescription = null;
    this._currentRemoteDescription = null;
    this._legacyPref = Services.prefs.getBoolPref(
      "media.peerconnection.description.legacy.enabled"
    );

    // http://rtcweb-wg.github.io/jsep/#rfc.section.4.1.9
    // canTrickle == null means unknown; when a remote description is received it
    // is set to true or false based on the presence of the "trickle" ice-option
    this._canTrickle = null;

    // So we can record telemetry on state transitions
    this._iceConnectionState = "new";

    this._hasStunServer = this._hasTurnServer = false;
    this._iceGatheredRelayCandidates = false;
    // Records telemetry
    this._pcTelemetry = new PeerConnectionTelemetry();
  }

  init(win) {
    this._win = win;
  }

  // Pref-based overrides; will _not_ be reflected in getConfiguration
  _applyPrefsToConfig(rtcConfig) {
    if (
      rtcConfig.iceTransportPolicy == "all" &&
      Services.prefs.getBoolPref("media.peerconnection.ice.relay_only")
    ) {
      rtcConfig.iceTransportPolicy = "relay";
    }

    if (
      !rtcConfig.iceServers ||
      !Services.prefs.getBoolPref(
        "media.peerconnection.use_document_iceservers"
      )
    ) {
      try {
        rtcConfig.iceServers = JSON.parse(
          Services.prefs.getCharPref(
            "media.peerconnection.default_iceservers"
          ) || "[]"
        );
      } catch (e) {
        this.logWarning(
          "Ignoring invalid media.peerconnection.default_iceservers in about:config"
        );
        rtcConfig.iceServers = [];
      }
      try {
        this._validateIceServers(
          rtcConfig.iceServers,
          "Ignoring invalid media.peerconnection.default_iceservers in about:config"
        );
      } catch (e) {
        this.logWarning(e.message);
        rtcConfig.iceServers = [];
      }
    }
  }

  _validateConfig(rtcConfig) {
    if ("sdpSemantics" in rtcConfig) {
      if (rtcConfig.sdpSemantics == "plan-b") {
        this.logWarning(
          `Outdated and non-standard {sdpSemantics: "plan-b"} is not ` +
            `supported! WebRTC may be unreliable. Please update code to ` +
            `follow standard "unified-plan".`
        );
      }
      // Don't let it show up in getConfiguration.
      delete rtcConfig.sdpSemantics;
    }

    if (this._config) {
      // certificates must match
      if (rtcConfig.certificates.length != this._config.certificates.length) {
        throw new this._win.DOMException(
          "Cannot change certificates with setConfiguration (length differs)",
          "InvalidModificationError"
        );
      }
      for (let i = 0; i < rtcConfig.certificates.length; i++) {
        if (rtcConfig.certificates[i] != this._config.certificates[i]) {
          throw new this._win.DOMException(
            `Cannot change certificates with setConfiguration ` +
              `(cert at index ${i} differs)`,
            "InvalidModificationError"
          );
        }
      }

      // bundlePolicy must match
      if (rtcConfig.bundlePolicy != this._config.bundlePolicy) {
        throw new this._win.DOMException(
          "Cannot change bundlePolicy with setConfiguration",
          "InvalidModificationError"
        );
      }

      // peerIdentity must match
      if (
        rtcConfig.peerIdentity &&
        rtcConfig.peerIdentity != this._config.peerIdentity
      ) {
        throw new this._win.DOMException(
          "Cannot change peerIdentity with setConfiguration",
          "InvalidModificationError"
        );
      }

      // TODO (bug 1339203): rtcpMuxPolicy must match
      // TODO (bug 1529398): iceCandidatePoolSize must match if sLD has ever
      // been called.
    }

    // This gets executed in the typical case when iceServers
    // are passed in through the web page.
    this._validateIceServers(
      rtcConfig.iceServers,
      "RTCPeerConnection constructor passed invalid RTCConfiguration"
    );
  }

  _checkIfIceRestartRequired(rtcConfig) {
    if (this._config) {
      if (rtcConfig.iceTransportPolicy != this._config.iceTransportPolicy) {
        this._pc.restartIceNoRenegotiationNeeded();
        return;
      }
      if (
        JSON.stringify(this._config.iceServers) !=
        JSON.stringify(rtcConfig.iceServers)
      ) {
        this._pc.restartIceNoRenegotiationNeeded();
      }
    }
  }

  __init(rtcConfig) {
    this._winID = this._win.windowGlobalChild.innerWindowId;
    let certificates = rtcConfig.certificates || [];

    if (certificates.some(c => c.expires <= Date.now())) {
      throw new this._win.DOMException(
        "Unable to create RTCPeerConnection with an expired certificate",
        "InvalidAccessError"
      );
    }

    // TODO(bug 1531875): Check origin of certs

    // TODO(bug 1176518): Remove this code once we support multiple certs
    let certificate;
    if (certificates.length == 1) {
      certificate = certificates[0];
    } else if (certificates.length) {
      throw new this._win.DOMException(
        "RTCPeerConnection does not currently support multiple certificates",
        "NotSupportedError"
      );
    }

    this._documentPrincipal = Cu.getWebIDLCallerPrincipal();

    if (_globalPCList._networkdown) {
      throw new this._win.DOMException(
        "Can't create RTCPeerConnections when the network is down",
        "InvalidStateError"
      );
    }

    this.makeGetterSetterEH("ontrack");
    this.makeLegacyGetterSetterEH(
      "onaddstream",
      "Use peerConnection.ontrack instead."
    );
    this.makeLegacyGetterSetterEH(
      "onaddtrack",
      "Use peerConnection.ontrack instead."
    );
    this.makeGetterSetterEH("onicecandidate");
    this.makeGetterSetterEH("onnegotiationneeded");
    this.makeGetterSetterEH("onsignalingstatechange");
    this.makeGetterSetterEH("ondatachannel");
    this.makeGetterSetterEH("oniceconnectionstatechange");
    this.makeGetterSetterEH("onicegatheringstatechange");
    this.makeGetterSetterEH("onconnectionstatechange");
    this.makeGetterSetterEH("onidentityresult");
    this.makeGetterSetterEH("onpeeridentity");
    this.makeGetterSetterEH("onidpassertionerror");
    this.makeGetterSetterEH("onidpvalidationerror");

    this._pc = new this._win.PeerConnectionImpl();

    this.__DOM_IMPL__._innerObject = this;
    const observer = new this._win.PeerConnectionObserver(this.__DOM_IMPL__);

    // Add a reference to the PeerConnection to global list (before init).
    _globalPCList.addPC(this);

    this._pc.initialize(observer, this._win);

    this.setConfiguration(rtcConfig);

    this._certificateReady = this._initCertificate(certificate);
    this._initIdp();
    _globalPCList.notifyLifecycleObservers(this, "initialized");
  }

  getConfiguration() {
    const config = Object.assign({}, this._config);
    delete config.sdpSemantics;
    return config;
  }

  setConfiguration(rtcConfig) {
    this._checkClosed();
    this._validateConfig(rtcConfig);
    this._checkIfIceRestartRequired(rtcConfig);

    // Allow prefs to tweak these settings before passing to c++, but hide all
    // of that from JS.
    const configWithPrefTweaks = Object.assign({}, rtcConfig);
    this._applyPrefsToConfig(configWithPrefTweaks);
    this._pc.setConfiguration(configWithPrefTweaks);

    this._config = Object.assign({}, rtcConfig);
  }

  async _initCertificate(certificate) {
    if (!certificate) {
      certificate = await this._win.RTCPeerConnection.generateCertificate({
        name: "ECDSA",
        namedCurve: "P-256",
      });
    }
    this._pc.certificate = certificate;
  }

  _resetPeerIdentityPromise() {
    this._peerIdentity = new this._win.Promise((resolve, reject) => {
      this._resolvePeerIdentity = resolve;
      this._rejectPeerIdentity = reject;
    });
  }

  _initIdp() {
    this._resetPeerIdentityPromise();
    this._lastIdentityValidation = this._win.Promise.resolve();

    let prefName = "media.peerconnection.identity.timeout";
    let idpTimeout = Services.prefs.getIntPref(prefName);
    this._localIdp = new lazy.PeerConnectionIdp(this._win, idpTimeout);
    this._remoteIdp = new lazy.PeerConnectionIdp(this._win, idpTimeout);
  }

  // Add a function to the internal operations chain.

  _chain(operation) {
    return this._pc.chain(operation);
  }

  // It's basically impossible to use async directly in JSImplemented code,
  // because the implicit promise must be wrapped to the right type for content.
  //
  // The _async wrapper takes care of this. The _legacy wrapper implements
  // legacy callbacks in a manner that produces correct line-numbers in errors,
  // provided that methods validate their inputs before putting themselves on
  // the pc's operations chain.
  //
  // These wrappers also serve as guards against settling promises past close().

  _async(func) {
    return this._win.Promise.resolve(this._closeWrapper(func));
  }

  _legacy(...args) {
    return this._win.Promise.resolve(this._legacyCloseWrapper(...args));
  }

  _auto(onSucc, onErr, func) {
    return typeof onSucc == "function"
      ? this._legacy(onSucc, onErr, func)
      : this._async(func);
  }

  async _closeWrapper(func) {
    let closed = this._closed;
    try {
      let result = await func();
      if (!closed && this._closed) {
        await new Promise(() => {});
      }
      return result;
    } catch (e) {
      if (!closed && this._closed) {
        await new Promise(() => {});
      }
      throw e;
    }
  }

  async _legacyCloseWrapper(onSucc, onErr, func) {
    let wrapCallback = cb => result => {
      try {
        cb && cb(result);
      } catch (e) {
        this.logErrorAndCallOnError(e);
      }
    };

    try {
      wrapCallback(onSucc)(await func());
    } catch (e) {
      wrapCallback(onErr)(e);
    }
  }

  // This implements the fairly common "Queue a task" logic
  async _queueTaskWithClosedCheck(func) {
    const pc = this;
    return new this._win.Promise((resolve, reject) => {
      Services.tm.dispatchToMainThread({
        run() {
          try {
            if (!pc._closed) {
              func();
              resolve();
            }
          } catch (e) {
            reject(e);
          }
        },
      });
    });
  }

  /**
   * An RTCConfiguration may look like this:
   *
   * { "iceServers": [ { urls: "stun:stun.example.org", },
   *                   { url: "stun:stun.example.org", }, // deprecated version
   *                   { urls: ["turn:turn1.x.org", "turn:turn2.x.org"],
   *                     username:"jib", credential:"mypass"} ] }
   *
   * This function normalizes the structure of the input for rtcConfig.iceServers for us,
   * so we test well-formed stun/turn urls before passing along to C++.
   *   msg - Error message to detail which array-entry failed, if any.
   */
  _validateIceServers(iceServers, msg) {
    // Normalize iceServers input
    iceServers.forEach(server => {
      if (typeof server.urls === "string") {
        server.urls = [server.urls];
      } else if (!server.urls && server.url) {
        // TODO: Remove support for legacy iceServer.url eventually (Bug 1116766)
        server.urls = [server.url];
        this.logWarning("RTCIceServer.url is deprecated! Use urls instead.");
      }
    });

    let nicerNewURI = uriStr => {
      try {
        return Services.io.newURI(uriStr);
      } catch (e) {
        if (e.result == Cr.NS_ERROR_MALFORMED_URI) {
          throw new this._win.DOMException(
            `${msg} - malformed URI: ${uriStr}`,
            "SyntaxError"
          );
        }
        throw e;
      }
    };

    let stunServers = 0;

    iceServers.forEach(({ urls, username, credential, credentialType }) => {
      if (!urls) {
        // TODO: Remove once url is deprecated (Bug 1369563)
        throw new this._win.TypeError(
          "Missing required 'urls' member of RTCIceServer"
        );
      }
      if (!urls.length) {
        throw new this._win.DOMException(
          `${msg} - urls is empty`,
          "SyntaxError"
        );
      }
      urls
        .map(url => nicerNewURI(url))
        .forEach(({ scheme, spec, query }) => {
          if (scheme in { turn: 1, turns: 1 }) {
            if (username == undefined) {
              throw new this._win.DOMException(
                `${msg} - missing username: ${spec}`,
                "InvalidAccessError"
              );
            }
            if (username.length > 512) {
              throw new this._win.DOMException(
                `${msg} - username longer then 512 bytes: ${username}`,
                "InvalidAccessError"
              );
            }
            if (credential == undefined) {
              throw new this._win.DOMException(
                `${msg} - missing credential: ${spec}`,
                "InvalidAccessError"
              );
            }
            if (credentialType != "password") {
              this.logWarning(
                `RTCConfiguration TURN credentialType \"${credentialType}\"` +
                  " is not yet implemented. Treating as password." +
                  " https://bugzil.la/1247616"
              );
            }
            this._hasTurnServer = true;
            // If this is not a TURN TCP/TLS server, it is also a STUN server
            const parameters = query.split("&");
            if (!parameters.includes("transport=tcp")) {
              this._hasStunServer = true;
            }
            stunServers += 1;
          } else if (scheme in { stun: 1, stuns: 1 }) {
            this._hasStunServer = true;
            stunServers += 1;
          } else {
            throw new this._win.DOMException(
              `${msg} - improper scheme: ${scheme}`,
              "SyntaxError"
            );
          }
          if (scheme in { stuns: 1 }) {
            this.logWarning(scheme.toUpperCase() + " is not yet supported.");
          }
          if (stunServers >= 5) {
            this.logError(
              "Using five or more STUN/TURN servers slows down discovery"
            );
          }
        });
    });
  }

  // Ideally, this should be of the form _checkState(state),
  // where the state is taken from an enumeration containing
  // the valid peer connection states defined in the WebRTC
  // spec. See Bug 831756.
  _checkClosed() {
    if (this._closed) {
      throw new this._win.DOMException(
        "Peer connection is closed",
        "InvalidStateError"
      );
    }
  }

  dispatchEvent(event) {
    // PC can close while events are firing if there is an async dispatch
    // in c++ land. But let through "closed" signaling and ice connection events.
    if (!this._suppressEvents) {
      this.__DOM_IMPL__.dispatchEvent(event);
    }
  }

  // Log error message to web console and window.onerror, if present.
  logErrorAndCallOnError(e) {
    this.logMsg(
      e.message,
      e.fileName,
      e.lineNumber,
      Ci.nsIScriptError.errorFlag
    );

    // Safely call onerror directly if present (necessary for testing)
    try {
      if (typeof this._win.onerror === "function") {
        this._win.onerror(e.message, e.fileName, e.lineNumber);
      }
    } catch (e) {
      // If onerror itself throws, service it.
      try {
        this.logMsg(
          e.message,
          e.fileName,
          e.lineNumber,
          Ci.nsIScriptError.errorFlag
        );
      } catch (e) {}
    }
  }

  logError(msg) {
    this.logStackMsg(msg, Ci.nsIScriptError.errorFlag);
  }

  logWarning(msg) {
    this.logStackMsg(msg, Ci.nsIScriptError.warningFlag);
  }

  logStackMsg(msg, flag) {
    let err = this._win.Error();
    this.logMsg(msg, err.fileName, err.lineNumber, flag);
  }

  logMsg(msg, file, line, flag) {
    return logWebRTCMsg(msg, file, line, flag, this._win);
  }

  getEH(type) {
    return this.__DOM_IMPL__.getEventHandler(type);
  }

  setEH(type, handler) {
    this.__DOM_IMPL__.setEventHandler(type, handler);
  }

  makeGetterSetterEH(name) {
    Object.defineProperty(this, name, {
      get() {
        return this.getEH(name);
      },
      set(h) {
        this.setEH(name, h);
      },
    });
  }

  makeLegacyGetterSetterEH(name, msg) {
    Object.defineProperty(this, name, {
      get() {
        return this.getEH(name);
      },
      set(h) {
        this.logWarning(name + " is deprecated! " + msg);
        this.setEH(name, h);
      },
    });
  }

  createOffer(optionsOrOnSucc, onErr, options) {
    let onSuccess = null;
    if (typeof optionsOrOnSucc == "function") {
      onSuccess = optionsOrOnSucc;
    } else {
      options = optionsOrOnSucc;
    }
    // This entry-point handles both new and legacy call sig. Decipher which one
    if (onSuccess) {
      return this._legacy(onSuccess, onErr, () => this._createOffer(options));
    }
    return this._async(() => this._createOffer(options));
  }

  // Ensures that we have at least one transceiver of |kind| that is
  // configured to receive. It will create one if necessary.
  _ensureOfferToReceive(kind) {
    let hasRecv = this.getTransceivers().some(
      transceiver =>
        transceiver.getKind() == kind &&
        (transceiver.direction == "sendrecv" ||
          transceiver.direction == "recvonly") &&
        !transceiver.stopped
    );

    if (!hasRecv) {
      this._addTransceiverNoEvents(kind, { direction: "recvonly" });
    }
  }

  // Handles offerToReceiveAudio/Video
  _ensureTransceiversForOfferToReceive(options) {
    if (options.offerToReceiveAudio) {
      this._ensureOfferToReceive("audio");
    }

    if (options.offerToReceiveVideo) {
      this._ensureOfferToReceive("video");
    }

    this.getTransceivers()
      .filter(transceiver => {
        return (
          (options.offerToReceiveVideo === false &&
            transceiver.receiver.track.kind == "video") ||
          (options.offerToReceiveAudio === false &&
            transceiver.receiver.track.kind == "audio")
        );
      })
      .forEach(transceiver => {
        if (transceiver.direction == "sendrecv") {
          transceiver.setDirectionInternal("sendonly");
        } else if (transceiver.direction == "recvonly") {
          transceiver.setDirectionInternal("inactive");
        }
      });
  }

  _maybeDuplicateFingerprints(sdp) {
    if (this._pc.duplicateFingerprintQuirk) {
      // hack to put a=fingerprint under all m= lines
      const lines = sdp.split("\n");
      const fingerprint = lines.find(line => line.startsWith("a=fingerprint"));
      if (fingerprint) {
        for (let i = 0; i < lines.length; i++) {
          if (lines[i].startsWith("m=")) {
            let j = i + 1;
            while (j < lines.length && lines[j].startsWith("c=")) {
              j++;
            }
            lines.splice(j, 0, fingerprint);
          }
        }
        sdp = lines.join("\n");
      }
    }
    return sdp;
  }

  _createOffer(options) {
    this._checkClosed();
    this._ensureTransceiversForOfferToReceive(options);
    return this._chain(async () =>
      Cu.cloneInto(await this._createAnOffer(options), this._win)
    );
  }

  async _createAnOffer(options = {}) {
    switch (this.signalingState) {
      case "stable":
      case "have-local-offer":
        break;
      default:
        throw new this._win.DOMException(
          `Cannot create offer in ${this.signalingState}`,
          "InvalidStateError"
        );
    }
    let haveAssertion;
    if (this._localIdp.enabled) {
      haveAssertion = this._getIdentityAssertion();
    }
    await this._getPermission();
    await this._certificateReady;
    let sdp = await new Promise((resolve, reject) => {
      this._onCreateOfferSuccess = resolve;
      this._onCreateOfferFailure = reject;
      this._pc.createOffer(options);
    });
    sdp = this._maybeDuplicateFingerprints(sdp);
    if (haveAssertion) {
      await haveAssertion;
      sdp = this._localIdp.addIdentityAttribute(sdp);
    }
    return { type: "offer", sdp };
  }

  createAnswer(optionsOrOnSucc, onErr) {
    // This entry-point handles both new and legacy call sig. Decipher which one
    if (typeof optionsOrOnSucc == "function") {
      return this._legacy(optionsOrOnSucc, onErr, () => this._createAnswer({}));
    }
    return this._async(() => this._createAnswer(optionsOrOnSucc));
  }

  _createAnswer() {
    this._checkClosed();
    return this._chain(async () =>
      Cu.cloneInto(await this._createAnAnswer(), this._win)
    );
  }

  async _createAnAnswer() {
    if (this.signalingState != "have-remote-offer") {
      throw new this._win.DOMException(
        `Cannot create answer in ${this.signalingState}`,
        "InvalidStateError"
      );
    }
    let haveAssertion;
    if (this._localIdp.enabled) {
      haveAssertion = this._getIdentityAssertion();
    }
    await this._getPermission();
    await this._certificateReady;
    let sdp = await new Promise((resolve, reject) => {
      this._onCreateAnswerSuccess = resolve;
      this._onCreateAnswerFailure = reject;
      this._pc.createAnswer();
    });
    sdp = this._maybeDuplicateFingerprints(sdp);
    if (haveAssertion) {
      await haveAssertion;
      sdp = this._localIdp.addIdentityAttribute(sdp);
    }
    return { type: "answer", sdp };
  }

  async _getPermission() {
    if (!this._havePermission) {
      const privileged =
        this._documentPrincipal.isSystemPrincipal ||
        Services.prefs.getBoolPref("media.navigator.permission.disabled");

      if (privileged) {
        this._havePermission = Promise.resolve();
      } else {
        this._havePermission = new Promise((resolve, reject) => {
          this._settlePermission = { allow: resolve, deny: reject };
          let outerId = this._win.docShell.outerWindowID;

          let chrome = new CreateOfferRequest(
            outerId,
            this._winID,
            this._globalPCListId,
            false
          );
          let request = this._win.CreateOfferRequest._create(this._win, chrome);
          Services.obs.notifyObservers(request, "PeerConnection:request");
        });
      }
    }
    return this._havePermission;
  }

  _sanityCheckSdp(sdp) {
    // The fippo butter finger filter AKA non-ASCII chars
    // Note: SDP allows non-ASCII character in the subject (who cares?)
    // eslint-disable-next-line no-control-regex
    let pos = sdp.search(/[^\u0000-\u007f]/);
    if (pos != -1) {
      throw new this._win.DOMException(
        "SDP contains non ASCII characters at position " + pos,
        "InvalidParameterError"
      );
    }
  }

  setLocalDescription(desc, onSucc, onErr) {
    return this._auto(onSucc, onErr, () => this._setLocalDescription(desc));
  }

  _setLocalDescription({ type, sdp }) {
    if (type == "pranswer") {
      throw new this._win.DOMException(
        "pranswer not yet implemented",
        "NotSupportedError"
      );
    }
    this._checkClosed();
    return this._chain(async () => {
      // Avoid Promise.all ahead of synchronous part of spec algorithm, since it
      // defers. NOTE: The spec says to return an already-rejected promise in
      // some cases, which is difficult to achieve in practice from JS (would
      // require avoiding await and then() entirely), but we want to come as
      // close as we reasonably can.
      const p = this._getPermission();
      if (!type) {
        switch (this.signalingState) {
          case "stable":
          case "have-local-offer":
          case "have-remote-pranswer":
            type = "offer";
            break;
          default:
            type = "answer";
            break;
        }
      }
      if (!sdp) {
        if (type == "offer") {
          sdp = (await this._createAnOffer()).sdp;
        } else if (type == "answer") {
          sdp = (await this._createAnAnswer()).sdp;
        }
      } else {
        this._sanityCheckSdp(sdp);
      }

      try {
        await new Promise((resolve, reject) => {
          this._onSetDescriptionSuccess = resolve;
          this._onSetDescriptionFailure = reject;
          this._pc.setLocalDescription(this._actions[type], sdp);
        });
        await p;
      } catch (e) {
        this._pc.onSetDescriptionError();
        throw e;
      }
      await this._pc.onSetDescriptionSuccess(type, false);
    });
  }

  async _validateIdentity(sdp, origin) {
    // Only run a single identity verification at a time.  We have to do this to
    // avoid problems with the fact that identity validation doesn't block the
    // resolution of setRemoteDescription().
    const validate = async () => {
      // Access this._pc synchronously in case pc is closed later
      const identity = this._pc.peerIdentity;
      await this._lastIdentityValidation;
      const msg = await this._remoteIdp.verifyIdentityFromSDP(sdp, origin);
      // If this pc has an identity already, then the identity in sdp must match
      if (identity && (!msg || msg.identity !== identity)) {
        throw new this._win.DOMException(
          "Peer Identity mismatch, expected: " + identity,
          "OperationError"
        );
      }
      if (this._closed) {
        return;
      }
      if (msg) {
        // Set new identity and generate an event.
        this._pc.peerIdentity = msg.identity;
        this._resolvePeerIdentity(
          Cu.cloneInto(
            {
              idp: this._remoteIdp.provider,
              name: msg.identity,
            },
            this._win
          )
        );
      }
    };

    const haveValidation = validate();

    // Always eat errors on this chain
    this._lastIdentityValidation = haveValidation.catch(() => {});

    // If validation fails, we have some work to do. Fork it so it cannot
    // interfere with the validation chain itself, even if the catch function
    // throws.
    haveValidation.catch(e => {
      if (this._closed) {
        return;
      }
      this._rejectPeerIdentity(e);

      // If we don't expect a specific peer identity, failure to get a valid
      // peer identity is not a terminal state, so replace the promise to
      // allow another attempt.
      if (!this._pc.peerIdentity) {
        this._resetPeerIdentityPromise();
      }
    });

    if (this._closed) {
      return;
    }
    // Only wait for IdP validation if we need identity matching
    if (this._pc.peerIdentity) {
      await haveValidation;
    }
  }

  setRemoteDescription(desc, onSucc, onErr) {
    return this._auto(onSucc, onErr, () => this._setRemoteDescription(desc));
  }

  _setRemoteDescription({ type, sdp }) {
    if (type == "pranswer") {
      throw new this._win.DOMException(
        "pranswer not yet implemented",
        "NotSupportedError"
      );
    }
    this._checkClosed();
    return this._chain(async () => {
      try {
        if (type == "offer" && this.signalingState == "have-local-offer") {
          await new Promise((resolve, reject) => {
            this._onSetDescriptionSuccess = resolve;
            this._onSetDescriptionFailure = reject;
            this._pc.setLocalDescription(
              Ci.IPeerConnection.kActionRollback,
              ""
            );
          });
          await this._pc.onSetDescriptionSuccess("rollback", false);
          this._updateCanTrickle();
        }

        if (this._closed) {
          return;
        }

        this._sanityCheckSdp(sdp);

        const p = this._getPermission();

        const haveSetRemote = new Promise((resolve, reject) => {
          this._onSetDescriptionSuccess = resolve;
          this._onSetDescriptionFailure = reject;
          this._pc.setRemoteDescription(this._actions[type], sdp);
        });

        if (type != "rollback") {
          // Do setRemoteDescription and identity validation in parallel
          await this._validateIdentity(sdp);
        }
        await p;
        await haveSetRemote;
      } catch (e) {
        this._pc.onSetDescriptionError();
        throw e;
      }

      await this._pc.onSetDescriptionSuccess(type, true);
      this._updateCanTrickle();
    });
  }

  setIdentityProvider(provider, { protocol, usernameHint, peerIdentity } = {}) {
    this._checkClosed();
    peerIdentity = peerIdentity || this._pc.peerIdentity;
    this._localIdp.setIdentityProvider(
      provider,
      protocol,
      usernameHint,
      peerIdentity
    );
  }

  async _getIdentityAssertion() {
    await this._certificateReady;
    return this._localIdp.getIdentityAssertion(
      this._pc.fingerprint,
      this._documentPrincipal.origin
    );
  }

  getIdentityAssertion() {
    this._checkClosed();
    return this._win.Promise.resolve(
      this._chain(() => this._getIdentityAssertion())
    );
  }

  get canTrickleIceCandidates() {
    return this._canTrickle;
  }

  _updateCanTrickle() {
    let containsTrickle = section => {
      let lines = section.toLowerCase().split(/(?:\r\n?|\n)/);
      return lines.some(line => {
        let prefix = "a=ice-options:";
        if (line.substring(0, prefix.length) !== prefix) {
          return false;
        }
        let tokens = line.substring(prefix.length).split(" ");
        return tokens.some(x => x === "trickle");
      });
    };

    let desc = null;
    try {
      // The getter for remoteDescription can throw if the pc is closed.
      desc = this.remoteDescription;
    } catch (e) {}
    if (!desc) {
      this._canTrickle = null;
      return;
    }

    let sections = desc.sdp.split(/(?:\r\n?|\n)m=/);
    let topSection = sections.shift();
    this._canTrickle =
      containsTrickle(topSection) || sections.every(containsTrickle);
  }

  addIceCandidate(cand, onSucc, onErr) {
    if (
      cand.candidate != "" &&
      cand.sdpMid == null &&
      cand.sdpMLineIndex == null
    ) {
      throw new this._win.TypeError(
        "Cannot add a candidate without specifying either sdpMid or sdpMLineIndex"
      );
    }
    return this._auto(onSucc, onErr, () => this._addIceCandidate(cand));
  }

  async _addIceCandidate({
    candidate,
    sdpMid,
    sdpMLineIndex,
    usernameFragment,
  }) {
    this._checkClosed();
    return this._chain(async () => {
      if (
        !this._pc.pendingRemoteDescription.length &&
        !this._pc.currentRemoteDescription.length
      ) {
        throw new this._win.DOMException(
          "No remoteDescription.",
          "InvalidStateError"
        );
      }
      return new Promise((resolve, reject) => {
        this._onAddIceCandidateSuccess = resolve;
        this._onAddIceCandidateError = reject;
        this._pc.addIceCandidate(
          candidate,
          sdpMid || "",
          usernameFragment || "",
          sdpMLineIndex
        );
      });
    });
  }

  restartIce() {
    this._pc.restartIce();
  }

  addStream(stream) {
    stream.getTracks().forEach(track => this.addTrack(track, stream));
  }

  addTrack(track, ...streams) {
    this._checkClosed();

    if (
      this.getTransceivers().some(
        transceiver => transceiver.sender.track == track
      )
    ) {
      throw new this._win.DOMException(
        "This track is already set on a sender.",
        "InvalidAccessError"
      );
    }

    let transceiver = this.getTransceivers().find(transceiver => {
      return (
        transceiver.sender.track == null &&
        transceiver.getKind() == track.kind &&
        !transceiver.stopped &&
        !transceiver.hasBeenUsedToSend()
      );
    });

    if (transceiver) {
      transceiver.sender.setTrack(track);
      transceiver.sender.setStreamsImpl(...streams);
      if (transceiver.direction == "recvonly") {
        transceiver.setDirectionInternal("sendrecv");
      } else if (transceiver.direction == "inactive") {
        transceiver.setDirectionInternal("sendonly");
      }
    } else {
      transceiver = this._addTransceiverNoEvents(
        track,
        {
          streams,
          direction: "sendrecv",
        },
        true
      );
    }

    this.updateNegotiationNeeded();
    return transceiver.sender;
  }

  removeTrack(sender) {
    this._checkClosed();

    if (!this._pc.createdSender(sender)) {
      throw new this._win.DOMException(
        "This sender was not created by this PeerConnection",
        "InvalidAccessError"
      );
    }

    let transceiver = this.getTransceivers().find(
      transceiver => !transceiver.stopped && transceiver.sender == sender
    );

    // If the transceiver was removed due to rollback, let it slide.
    if (!transceiver || !sender.track) {
      return;
    }

    sender.setTrack(null);
    if (transceiver.direction == "sendrecv") {
      transceiver.setDirectionInternal("recvonly");
    } else if (transceiver.direction == "sendonly") {
      transceiver.setDirectionInternal("inactive");
    }

    this.updateNegotiationNeeded();
  }

  _addTransceiverNoEvents(sendTrackOrKind, init, addTrackMagic) {
    let sendTrack = null;
    let kind;
    if (typeof sendTrackOrKind == "string") {
      kind = sendTrackOrKind;
      switch (kind) {
        case "audio":
        case "video":
          break;
        default:
          throw new this._win.TypeError("Invalid media kind");
      }
    } else {
      sendTrack = sendTrackOrKind;
      kind = sendTrack.kind;
    }

    try {
      return this._pc.addTransceiver(init, kind, sendTrack, addTrackMagic);
    } catch (e) {
      // Exceptions thrown by c++ code do not propagate. In most cases, that's
      // fine because we're using Promises, which can be copied. But this is
      // not promise-based, so we have to do this sketchy stuff.
      const holder = new StructuredCloneHolder(
        "",
        "",
        new ClonedErrorHolder(e)
      );
      throw holder.deserialize(this._win);
    }
  }

  addTransceiver(sendTrackOrKind, init) {
    this._checkClosed();
    let transceiver = this._addTransceiverNoEvents(sendTrackOrKind, init);
    this.updateNegotiationNeeded();
    return transceiver;
  }

  updateNegotiationNeeded() {
    this._pc.updateNegotiationNeeded();
  }

  close() {
    if (this._closed) {
      return;
    }
    this._closed = true;
    this.changeIceConnectionState("closed");
    if (this._localIdp) {
      this._localIdp.close();
    }
    if (this._remoteIdp) {
      this._remoteIdp.close();
    }
    this._pc.close();
    this._suppressEvents = true;
  }

  getLocalStreams() {
    this._checkClosed();
    let localStreams = new Set();
    this.getTransceivers().forEach(transceiver => {
      transceiver.sender.getStreams().forEach(stream => {
        localStreams.add(stream);
      });
    });
    return [...localStreams.values()];
  }

  getRemoteStreams() {
    this._checkClosed();
    return this._pc.getRemoteStreams();
  }

  getSenders() {
    return this.getTransceivers()
      .filter(transceiver => !transceiver.stopped)
      .map(transceiver => transceiver.sender);
  }

  getReceivers() {
    return this.getTransceivers()
      .filter(transceiver => !transceiver.stopped)
      .map(transceiver => transceiver.receiver);
  }

  mozSetPacketCallback(callback) {
    this._onPacket = callback;
  }

  mozEnablePacketDump(level, type, sending) {
    this._pc.enablePacketDump(level, type, sending);
  }

  mozDisablePacketDump(level, type, sending) {
    this._pc.disablePacketDump(level, type, sending);
  }

  getTransceivers() {
    return this._pc.getTransceivers();
  }

  get localDescription() {
    return this.pendingLocalDescription || this.currentLocalDescription;
  }

  cacheDescription(name, type, sdp) {
    if (
      !this[name] ||
      this[name].type != type ||
      this[name].sdp != sdp ||
      this._legacyPref
    ) {
      this[name] = sdp.length
        ? new this._win.RTCSessionDescription({ type, sdp })
        : null;
    }
    return this[name];
  }

  get currentLocalDescription() {
    this._checkClosed();
    return this.cacheDescription(
      "_currentLocalDescription",
      this._pc.currentOfferer ? "offer" : "answer",
      this._pc.currentLocalDescription
    );
  }

  get pendingLocalDescription() {
    this._checkClosed();
    return this.cacheDescription(
      "_pendingLocalDescription",
      this._pc.pendingOfferer ? "offer" : "answer",
      this._pc.pendingLocalDescription
    );
  }

  get remoteDescription() {
    return this.pendingRemoteDescription || this.currentRemoteDescription;
  }

  get currentRemoteDescription() {
    this._checkClosed();
    return this.cacheDescription(
      "_currentRemoteDescription",
      this._pc.currentOfferer ? "answer" : "offer",
      this._pc.currentRemoteDescription
    );
  }

  get pendingRemoteDescription() {
    this._checkClosed();
    return this.cacheDescription(
      "_pendingRemoteDescription",
      this._pc.pendingOfferer ? "answer" : "offer",
      this._pc.pendingRemoteDescription
    );
  }

  get peerIdentity() {
    return this._peerIdentity;
  }
  get idpLoginUrl() {
    return this._localIdp.idpLoginUrl;
  }
  get id() {
    return this._pc.id;
  }
  set id(s) {
    this._pc.id = s;
  }
  get iceGatheringState() {
    return this._pc.iceGatheringState;
  }
  get iceConnectionState() {
    return this._iceConnectionState;
  }
  get connectionState() {
    return this._pc.connectionState;
  }

  get signalingState() {
    // checking for our local pc closed indication
    // before invoking the pc methods.
    if (this._closed) {
      return "closed";
    }
    return this._pc.signalingState;
  }

  handleIceGatheringStateChange() {
    _globalPCList.notifyLifecycleObservers(this, "icegatheringstatechange");
    this.dispatchEvent(new this._win.Event("icegatheringstatechange"));
    if (this.iceGatheringState === "complete") {
      this.dispatchEvent(
        new this._win.RTCPeerConnectionIceEvent("icecandidate", {
          candidate: null,
        })
      );
    }
  }

  changeIceConnectionState(state) {
    if (state != this._iceConnectionState) {
      this._iceConnectionState = state;
      _globalPCList.notifyLifecycleObservers(this, "iceconnectionstatechange");
      if (!this._closed) {
        this.dispatchEvent(new this._win.Event("iceconnectionstatechange"));
      }
    }
  }

  getStats(selector, onSucc, onErr) {
    if (selector !== null) {
      let matchingSenders = this.getSenders().filter(s => s.track === selector);
      let matchingReceivers = this.getReceivers().filter(
        r => r.track === selector
      );

      if (matchingSenders.length + matchingReceivers.length != 1) {
        throw new this._win.DOMException(
          "track must be associated with a unique sender or receiver, but " +
            " is associated with " +
            matchingSenders.length +
            " senders and " +
            matchingReceivers.length +
            " receivers.",
          "InvalidAccessError"
        );
      }
    }

    return this._auto(onSucc, onErr, () => this._pc.getStats(selector));
  }

  get sctp() {
    return this._pc.sctp;
  }

  createDataChannel(
    label,
    {
      maxRetransmits,
      ordered,
      negotiated,
      id = null,
      maxRetransmitTime,
      maxPacketLifeTime,
      protocol,
    } = {}
  ) {
    this._checkClosed();
    this._pcTelemetry.recordDataChannelInit(
      maxRetransmitTime,
      maxPacketLifeTime
    );

    if (maxPacketLifeTime === undefined) {
      maxPacketLifeTime = maxRetransmitTime;
    }

    if (maxRetransmitTime !== undefined) {
      this.logWarning(
        "Use maxPacketLifeTime instead of deprecated maxRetransmitTime which will stop working soon in createDataChannel!"
      );
    }

    if (protocol.length > 32767) {
      // At least 65536/2 UTF-16 characters. UTF-8 might be too long.
      // Spec says to check how long |protocol| and |label| are in _bytes_. This
      // is a little ambiguous. For now, examine the length of the utf-8 encoding.
      const byteCounter = new TextEncoder();

      if (byteCounter.encode(protocol).length > 65535) {
        throw new this._win.TypeError(
          "protocol cannot be longer than 65535 bytes"
        );
      }
    }

    if (label.length > 32767) {
      const byteCounter = new TextEncoder();
      if (byteCounter.encode(label).length > 65535) {
        throw new this._win.TypeError(
          "label cannot be longer than 65535 bytes"
        );
      }
    }

    if (!negotiated) {
      id = null;
    } else if (id === null) {
      throw new this._win.TypeError("id is required when negotiated is true");
    }
    if (maxPacketLifeTime !== undefined && maxRetransmits !== undefined) {
      throw new this._win.TypeError(
        "Both maxPacketLifeTime and maxRetransmits cannot be provided"
      );
    }
    if (id == 65535) {
      throw new this._win.TypeError("id cannot be 65535");
    }
    // Must determine the type where we still know if entries are undefined.
    let type;
    if (maxPacketLifeTime !== undefined) {
      type = Ci.IPeerConnection.kDataChannelPartialReliableTimed;
    } else if (maxRetransmits !== undefined) {
      type = Ci.IPeerConnection.kDataChannelPartialReliableRexmit;
    } else {
      type = Ci.IPeerConnection.kDataChannelReliable;
    }
    // Synchronous since it doesn't block.
    let dataChannel;
    try {
      dataChannel = this._pc.createDataChannel(
        label,
        protocol,
        type,
        ordered,
        maxPacketLifeTime,
        maxRetransmits,
        negotiated,
        id
      );
    } catch (e) {
      if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
        throw e;
      }

      const msg =
        id === null ? "No available id could be generated" : "Id is in use";
      throw new this._win.DOMException(msg, "OperationError");
    }

    // Spec says to only do this if this is the first DataChannel created,
    // but the c++ code that does the "is negotiation needed" checking will
    // only ever return true on the first one.
    this.updateNegotiationNeeded();

    return dataChannel;
  }
}

setupPrototype(RTCPeerConnection, {
  classID: PC_CID,
  contractID: PC_CONTRACT,
  QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
  _actions: {
    offer: Ci.IPeerConnection.kActionOffer,
    answer: Ci.IPeerConnection.kActionAnswer,
    pranswer: Ci.IPeerConnection.kActionPRAnswer,
    rollback: Ci.IPeerConnection.kActionRollback,
  },
});

// This is a separate class because we don't want to expose it to DOM.

export class PeerConnectionObserver {
  init(win) {
    this._win = win;
  }

  __init(dompc) {
    this._dompc = dompc._innerObject;
  }

  newError({ message, name }) {
    return new this._dompc._win.DOMException(message, name);
  }

  dispatchEvent(event) {
    this._dompc.dispatchEvent(event);
  }

  onCreateOfferSuccess(sdp) {
    this._dompc._onCreateOfferSuccess(sdp);
  }

  onCreateOfferError(error) {
    this._dompc._onCreateOfferFailure(this.newError(error));
  }

  onCreateAnswerSuccess(sdp) {
    this._dompc._onCreateAnswerSuccess(sdp);
  }

  onCreateAnswerError(error) {
    this._dompc._onCreateAnswerFailure(this.newError(error));
  }

  onSetDescriptionSuccess() {
    this._dompc._onSetDescriptionSuccess();
  }

  onSetDescriptionError(error) {
    this._dompc._onSetDescriptionFailure(this.newError(error));
  }

  onAddIceCandidateSuccess() {
    this._dompc._onAddIceCandidateSuccess();
  }

  onAddIceCandidateError(error) {
    this._dompc._onAddIceCandidateError(this.newError(error));
  }

  onIceCandidate(sdpMLineIndex, sdpMid, candidate, usernameFragment) {
    let win = this._dompc._win;
    if (candidate || sdpMid || usernameFragment) {
      if (candidate.includes(" typ relay ")) {
        this._dompc._iceGatheredRelayCandidates = true;
      }
      candidate = new win.RTCIceCandidate({
        candidate,
        sdpMid,
        sdpMLineIndex,
        usernameFragment,
      });
    }
    this.dispatchEvent(
      new win.RTCPeerConnectionIceEvent("icecandidate", { candidate })
    );
  }

  // This method is primarily responsible for updating iceConnectionState.
  // This state is defined in the WebRTC specification as follows:
  //
  // iceConnectionState:
  // -------------------
  //   new           Any of the RTCIceTransports are in the new state and none
  //                 of them are in the checking, failed or disconnected state.
  //
  //   checking      Any of the RTCIceTransports are in the checking state and
  //                 none of them are in the failed or disconnected state.
  //
  //   connected     All RTCIceTransports are in the connected, completed or
  //                 closed state and at least one of them is in the connected
  //                 state.
  //
  //   completed     All RTCIceTransports are in the completed or closed state
  //                 and at least one of them is in the completed state.
  //
  //   failed        Any of the RTCIceTransports are in the failed state.
  //
  //   disconnected  Any of the RTCIceTransports are in the disconnected state
  //                 and none of them are in the failed state.
  //
  //   closed        All of the RTCIceTransports are in the closed state.

  handleIceConnectionStateChange(iceConnectionState) {
    let pc = this._dompc;
    if (pc.iceConnectionState === iceConnectionState) {
      return;
    }

    if (iceConnectionState === "failed") {
      if (!pc._hasStunServer) {
        pc.logError(
          "ICE failed, add a STUN server and see about:webrtc for more details"
        );
      } else if (!pc._hasTurnServer) {
        pc.logError(
          "ICE failed, add a TURN server and see about:webrtc for more details"
        );
      } else if (pc._hasTurnServer && !pc._iceGatheredRelayCandidates) {
        pc.logError(
          "ICE failed, your TURN server appears to be broken, see about:webrtc for more details"
        );
      } else {
        pc.logError("ICE failed, see about:webrtc for more details");
      }
    }

    pc.changeIceConnectionState(iceConnectionState);
  }

  onStateChange(state) {
    if (!this._dompc) {
      return;
    }

    if (state == "SignalingState") {
      this.dispatchEvent(new this._win.Event("signalingstatechange"));
      return;
    }

    if (!this._dompc._pc) {
      return;
    }

    switch (state) {
      case "IceConnectionState":
        this.handleIceConnectionStateChange(this._dompc._pc.iceConnectionState);
        break;

      case "IceGatheringState":
        this._dompc.handleIceGatheringStateChange();
        break;

      case "ConnectionState":
        _globalPCList.notifyLifecycleObservers(this, "connectionstatechange");
        this.dispatchEvent(new this._win.Event("connectionstatechange"));
        break;

      default:
        this._dompc.logWarning("Unhandled state type: " + state);
        break;
    }
  }

  onTransceiverNeeded(kind, transceiverImpl) {
    this._dompc._onTransceiverNeeded(kind, transceiverImpl);
  }

  notifyDataChannel(channel) {
    this.dispatchEvent(
      new this._dompc._win.RTCDataChannelEvent("datachannel", { channel })
    );
  }

  fireTrackEvent(receiver, streams) {
    const pc = this._dompc;
    const transceiver = pc.getTransceivers().find(t => t.receiver == receiver);
    if (!transceiver) {
      return;
    }
    const track = receiver.track;
    this.dispatchEvent(
      new this._win.RTCTrackEvent("track", {
        transceiver,
        receiver,
        track,
        streams,
      })
    );
    // Fire legacy event as well for a little bit.
    this.dispatchEvent(
      new this._win.MediaStreamTrackEvent("addtrack", { track })
    );
  }

  fireStreamEvent(stream) {
    const ev = new this._win.MediaStreamEvent("addstream", { stream });
    this.dispatchEvent(ev);
  }

  fireNegotiationNeededEvent() {
    this.dispatchEvent(new this._win.Event("negotiationneeded"));
  }

  onPacket(level, type, sending, packet) {
    var pc = this._dompc;
    if (pc._onPacket) {
      pc._onPacket(level, type, sending, packet);
    }
  }
}

setupPrototype(PeerConnectionObserver, {
  classID: PC_OBS_CID,
  contractID: PC_OBS_CONTRACT,
  QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
});

export class RTCPeerConnectionStatic {
  init(win) {
    this._winID = win.windowGlobalChild.innerWindowId;
  }

  registerPeerConnectionLifecycleCallback(cb) {
    _globalPCList._registerPeerConnectionLifecycleCallback(this._winID, cb);
  }
}

setupPrototype(RTCPeerConnectionStatic, {
  classID: PC_STATIC_CID,
  contractID: PC_STATIC_CONTRACT,
  QueryInterface: ChromeUtils.generateQI(["nsIDOMGlobalPropertyInitializer"]),
});

export class CreateOfferRequest {
  constructor(windowID, innerWindowID, callID, isSecure) {
    Object.assign(this, { windowID, innerWindowID, callID, isSecure });
  }
}

setupPrototype(CreateOfferRequest, {
  classID: PC_COREQUEST_CID,
  contractID: PC_COREQUEST_CONTRACT,
  QueryInterface: ChromeUtils.generateQI([]),
});
PK
!<���i+i+'modules/media/PeerConnectionIdp.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  IdpSandbox: "resource://gre/modules/media/IdpSandbox.sys.mjs",
});

/**
 * Creates an IdP helper.
 *
 * @param win (object) the window we are working for
 * @param timeout (int) the timeout in milliseconds
 */
export function PeerConnectionIdp(win, timeout) {
  this._win = win;
  this._timeout = timeout || 5000;

  this.provider = null;
  this._resetAssertion();
}

(function () {
  PeerConnectionIdp._mLinePattern = new RegExp("^m=", "m");
  // attributes are funny, the 'a' is case sensitive, the name isn't
  let pattern = "^a=[iI][dD][eE][nN][tT][iI][tT][yY]:(\\S+)";
  PeerConnectionIdp._identityPattern = new RegExp(pattern, "m");
  pattern = "^a=[fF][iI][nN][gG][eE][rR][pP][rR][iI][nN][tT]:(\\S+) (\\S+)";
  PeerConnectionIdp._fingerprintPattern = new RegExp(pattern, "m");
})();

PeerConnectionIdp.prototype = {
  get enabled() {
    return !!this._idp;
  },

  _resetAssertion() {
    this.assertion = null;
    this.idpLoginUrl = null;
  },

  setIdentityProvider(provider, protocol, usernameHint, peerIdentity) {
    this._resetAssertion();
    this.provider = provider;
    this.protocol = protocol;
    this.username = usernameHint;
    this.peeridentity = peerIdentity;
    if (this._idp) {
      if (this._idp.isSame(provider, protocol)) {
        return; // noop
      }
      this._idp.stop();
    }
    this._idp = new lazy.IdpSandbox(provider, protocol, this._win);
  },

  // start the IdP and do some error fixup
  start() {
    return this._idp.start().catch(e => {
      throw new this._win.DOMException(e.message, "IdpError");
    });
  },

  close() {
    this._resetAssertion();
    this.provider = null;
    this.protocol = null;
    this.username = null;
    this.peeridentity = null;
    if (this._idp) {
      this._idp.stop();
      this._idp = null;
    }
  },

  _getFingerprintsFromSdp(sdp) {
    let fingerprints = {};
    let m = sdp.match(PeerConnectionIdp._fingerprintPattern);
    while (m) {
      fingerprints[m[0]] = { algorithm: m[1], digest: m[2] };
      sdp = sdp.substring(m.index + m[0].length);
      m = sdp.match(PeerConnectionIdp._fingerprintPattern);
    }

    return Object.keys(fingerprints).map(k => fingerprints[k]);
  },

  _isValidAssertion(assertion) {
    return (
      assertion &&
      assertion.idp &&
      typeof assertion.idp.domain === "string" &&
      (!assertion.idp.protocol || typeof assertion.idp.protocol === "string") &&
      typeof assertion.assertion === "string"
    );
  },

  _getSessionLevelEnd(sdp) {
    const match = sdp.match(PeerConnectionIdp._mLinePattern);
    if (!match) {
      return sdp.length;
    }
    return match.index;
  },

  _getIdentityFromSdp(sdp) {
    // a=identity is session level
    let idMatch;
    const index = this._getSessionLevelEnd(sdp);
    const sessionLevel = sdp.substring(0, index);
    idMatch = sessionLevel.match(PeerConnectionIdp._identityPattern);
    if (!idMatch) {
      return undefined; // undefined === no identity
    }

    let assertion;
    try {
      assertion = JSON.parse(atob(idMatch[1]));
    } catch (e) {
      throw new this._win.DOMException(
        "invalid identity assertion: " + e,
        "InvalidSessionDescriptionError"
      );
    }
    if (!this._isValidAssertion(assertion)) {
      throw new this._win.DOMException(
        "assertion missing idp/idp.domain/assertion",
        "InvalidSessionDescriptionError"
      );
    }
    return assertion;
  },

  /**
   * Verifies the a=identity line the given SDP contains, if any.
   * If the verification succeeds callback is called with the message from the
   * IdP proxy as parameter, else (verification failed OR no a=identity line in
   * SDP at all) null is passed to callback.
   *
   * Note that this only verifies that the SDP is coherent.  We still rely on
   * the fact that the RTCPeerConnection won't connect to a peer if the
   * fingerprint of the certificate they offer doesn't appear in the SDP.
   */
  verifyIdentityFromSDP(sdp, origin) {
    let identity = this._getIdentityFromSdp(sdp);
    let fingerprints = this._getFingerprintsFromSdp(sdp);
    if (!identity || fingerprints.length <= 0) {
      return this._win.Promise.resolve(); // undefined result = no identity
    }

    this.setIdentityProvider(identity.idp.domain, identity.idp.protocol);
    return this._verifyIdentity(identity.assertion, fingerprints, origin);
  },

  /**
   * Checks that the name in the identity provided by the IdP is OK.
   *
   * @param name (string) the name to validate
   * @throws if the name isn't valid
   */
  _validateName(name) {
    let error = msg => {
      throw new this._win.DOMException(
        "assertion name error: " + msg,
        "IdpError"
      );
    };

    if (typeof name !== "string") {
      error("name not a string");
    }
    let atIdx = name.indexOf("@");
    if (atIdx <= 0) {
      error("missing authority in name from IdP");
    }

    // no third party assertions... for now
    let tail = name.substring(atIdx + 1);

    // strip the port number, if present
    let provider = this.provider;
    let providerPortIdx = provider.indexOf(":");
    if (providerPortIdx > 0) {
      provider = provider.substring(0, providerPortIdx);
    }
    let idnService = Cc["@mozilla.org/network/idn-service;1"].getService(
      Ci.nsIIDNService
    );
    if (idnService.domainToASCII(tail) !== idnService.domainToASCII(provider)) {
      error('name "' + name + '" doesn\'t match IdP: "' + this.provider + '"');
    }
  },

  /**
   * Check the validation response.  We are very defensive here when handling
   * the message from the IdP proxy.  That way, broken IdPs aren't likely to
   * cause catastrophic damage.
   */
  _checkValidation(validation, sdpFingerprints) {
    let error = msg => {
      throw new this._win.DOMException(
        "IdP validation error: " + msg,
        "IdpError"
      );
    };

    if (!this.provider) {
      error("IdP closed");
    }

    if (
      typeof validation !== "object" ||
      typeof validation.contents !== "string" ||
      typeof validation.identity !== "string"
    ) {
      error("no payload in validation response");
    }

    let fingerprints;
    try {
      fingerprints = JSON.parse(validation.contents).fingerprint;
    } catch (e) {
      error("invalid JSON");
    }

    let isFingerprint = f =>
      typeof f.digest === "string" && typeof f.algorithm === "string";
    if (!Array.isArray(fingerprints) || !fingerprints.every(isFingerprint)) {
      error(
        "fingerprints must be an array of objects" +
          " with digest and algorithm attributes"
      );
    }

    // everything in `innerSet` is found in `outerSet`
    let isSubsetOf = (outerSet, innerSet, comparator) => {
      return innerSet.every(i => {
        return outerSet.some(o => comparator(i, o));
      });
    };
    let compareFingerprints = (a, b) => {
      return a.digest === b.digest && a.algorithm === b.algorithm;
    };
    if (!isSubsetOf(fingerprints, sdpFingerprints, compareFingerprints)) {
      error("the fingerprints must be covered by the assertion");
    }
    this._validateName(validation.identity);
    return validation;
  },

  /**
   * Asks the IdP proxy to verify an identity assertion.
   */
  _verifyIdentity(assertion, fingerprints, origin) {
    let p = this.start()
      .then(idp =>
        this._wrapCrossCompartmentPromise(
          idp.validateAssertion(assertion, origin)
        )
      )
      .then(validation => this._checkValidation(validation, fingerprints));

    return this._applyTimeout(p);
  },

  /**
   * Enriches the given SDP with an `a=identity` line.  getIdentityAssertion()
   * must have already run successfully, otherwise this does nothing to the sdp.
   */
  addIdentityAttribute(sdp) {
    if (!this.assertion) {
      return sdp;
    }

    const index = this._getSessionLevelEnd(sdp);
    return (
      sdp.substring(0, index) +
      "a=identity:" +
      this.assertion +
      "\r\n" +
      sdp.substring(index)
    );
  },

  /**
   * Asks the IdP proxy for an identity assertion.  Don't call this unless you
   * have checked .enabled, or you really like exceptions.  Also, don't call
   * this when another call is still running, because it's not certain which
   * call will finish first and the final state will be similarly uncertain.
   */
  getIdentityAssertion(fingerprint, origin) {
    if (!this.enabled) {
      throw new this._win.DOMException(
        "no IdP set, call setIdentityProvider() to set one",
        "InvalidStateError"
      );
    }

    let [algorithm, digest] = fingerprint.split(" ", 2);
    let content = {
      fingerprint: [
        {
          algorithm,
          digest,
        },
      ],
    };

    this._resetAssertion();
    let p = this.start()
      .then(idp => {
        let options = {
          protocol: this.protocol,
          usernameHint: this.username,
          peerIdentity: this.peeridentity,
        };
        return this._wrapCrossCompartmentPromise(
          idp.generateAssertion(JSON.stringify(content), origin, options)
        );
      })
      .then(assertion => {
        if (!this._isValidAssertion(assertion)) {
          throw new this._win.DOMException(
            "IdP generated invalid assertion",
            "IdpError"
          );
        }
        // save the base64+JSON assertion, since that is all that is used
        this.assertion = btoa(JSON.stringify(assertion));
        return this.assertion;
      });

    return this._applyTimeout(p);
  },

  /**
   * Promises generated by the sandbox need to be very carefully treated so that
   * they can chain into promises in the `this._win` compartment.  Results need
   * to be cloned across; errors need to be converted.
   */
  _wrapCrossCompartmentPromise(sandboxPromise) {
    return new this._win.Promise((resolve, reject) => {
      sandboxPromise.then(
        result => resolve(Cu.cloneInto(result, this._win)),
        e => {
          let message = "" + (e.message || JSON.stringify(e) || "IdP error");
          if (e.name === "IdpLoginError") {
            if (typeof e.loginUrl === "string") {
              this.idpLoginUrl = e.loginUrl;
            }
            reject(new this._win.DOMException(message, "IdpLoginError"));
          } else {
            reject(new this._win.DOMException(message, "IdpError"));
          }
        }
      );
    });
  },

  /**
   * Wraps a promise, adding a timeout guard on it so that it can't take longer
   * than the specified time.  Returns a promise that rejects if the timeout
   * elapses before `p` resolves.
   */
  _applyTimeout(p) {
    let timeout = new this._win.Promise(r =>
      this._win.setTimeout(r, this._timeout)
    ).then(() => {
      throw new this._win.DOMException("IdP timed out", "IdpError");
    });
    return this._win.Promise.race([timeout, p]);
  },
};
PK
!<.	���*modules/megalist/MegalistViewModel.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { DefaultAggregator } from "resource://gre/modules/megalist/aggregator/DefaultAggregator.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  OSKeyStore: "resource://gre/modules/OSKeyStore.sys.mjs",
});

/**
 *
 * Responsible for filtering lines data from the Aggregator and receiving user
 * commands from the view.
 *
 * Refers to the same MegalistAggregator in the parent process to access data.
 * Paired to exactly one MegalistView in the child process to present to the user.
 *
 */
export class MegalistViewModel {
  /**
   *
   * View Model prepares snapshots in the parent process to be displayed
   * by the View in the child process.
   *
   * View requests line data by providing snapshotId.
   *
   */
  #snapshots = [];
  #searchText = "";
  #messageToView;

  static #aggregator = new DefaultAggregator();

  constructor(messageToView) {
    this.#messageToView = messageToView;
    MegalistViewModel.#aggregator.attachViewModel(this);
  }

  willDestroy() {
    MegalistViewModel.#aggregator.detachViewModel(this);
  }

  refreshAllLinesOnScreen() {
    this.#rebuildSnapshots();
  }

  refreshSingleLineOnScreen(line) {
    if (this.#searchText) {
      // TODO: we should be throttling search input
      this.#rebuildSnapshots();
    } else {
      const snapshotIndex = this.#snapshots.indexOf(line);
      if (snapshotIndex >= 0) {
        const snapshot = this.#processSnapshotView(line, snapshotIndex);
        this.#messageToView("Snapshot", {
          snapshotId: snapshotIndex,
          snapshot,
        });
      }
    }
  }

  /**
   *
   * Send snapshot of necessary line data across parent-child boundary.
   *
   * @param {object} snapshotData
   * @param {number} index
   */
  #processSnapshotView(snapshotData, index) {
    const snapshot = {
      label: snapshotData.label,
      value: snapshotData.value,
      field: snapshotData.field,
      lineIndex: index,
    };

    if ("commands" in snapshotData) {
      snapshot.commands = snapshotData.commands;
    }

    if ("valueIcon" in snapshotData) {
      snapshot.valueIcon = snapshotData.valueIcon;
    }

    if ("href" in snapshotData) {
      snapshot.href = snapshotData.href;
    }

    if (snapshotData.stickers) {
      for (const sticker of snapshotData.stickers()) {
        snapshot.stickers ??= [];
        snapshot.stickers.push(sticker);
      }
    }

    if ("toggleTooltip" in snapshotData) {
      snapshot.toggleTooltip = snapshotData.toggleTooltip;
    }

    if ("concealed" in snapshotData) {
      snapshot.concealed = snapshotData.concealed;
    }

    return snapshot;
  }

  handleViewMessage({ name, data }) {
    const handlerName = `receive${name}`;
    if (!(handlerName in this)) {
      throw new Error(`Received unknown message "${name}"`);
    }
    return this[handlerName](data);
  }

  receiveRefresh() {
    this.#rebuildSnapshots();
  }

  #rebuildSnapshots() {
    this.#snapshots = Array.from(
      MegalistViewModel.#aggregator.enumerateLines(this.#searchText)
    );

    // Expose relevant line properties to view
    const viewSnapshots = this.#snapshots.map((snapshot, i) =>
      this.#processSnapshotView(snapshot, i)
    );

    // Update snapshots on screen
    this.#messageToView("ShowSnapshots", {
      snapshots: viewSnapshots,
    });
  }

  receiveUpdateFilter({ searchText } = { searchText: "" }) {
    if (this.#searchText != searchText) {
      this.#searchText = searchText;
      this.#messageToView("MegalistUpdateFilter", { searchText });
      this.#rebuildSnapshots();
    }
  }

  async receiveCommand({ commandId, snapshotId, value } = {}) {
    const dotIndex = commandId?.indexOf(".");
    const index = snapshotId;
    const snapshot = this.#snapshots[index];

    if (dotIndex >= 0) {
      const dataSourceName = commandId.substring(0, dotIndex);
      const functionName = commandId.substring(dotIndex + 1);
      MegalistViewModel.#aggregator.callFunction(
        dataSourceName,
        functionName,
        snapshot?.record
      );
      return;
    }

    if (snapshot) {
      const commands = snapshot.commands;
      commandId = commandId ?? commands[0]?.id;
      const mustVerify = commands.find(c => c.id == commandId)?.verify;
      if (!mustVerify || (await this.#verifyUser())) {
        // TODO:Enter the prompt message and pref for #verifyUser()
        await snapshot[`execute${commandId}`]?.(value);
      }
    }
  }

  async #verifyUser(promptMessage, prefName) {
    if (!this.getOSAuthEnabled(prefName)) {
      promptMessage = false;
    }
    let result = await lazy.OSKeyStore.ensureLoggedIn(promptMessage);
    return result.authenticated;
  }

  /**
   * Get the decrypted value for a string pref.
   *
   * @param {string} prefName -> The pref whose value is needed.
   * @param {string} safeDefaultValue -> Value to be returned incase the pref is not yet set.
   * @returns {string}
   */
  #getSecurePref(prefName, safeDefaultValue) {
    try {
      let encryptedValue = Services.prefs.getStringPref(prefName, "");
      return this._crypto.decrypt(encryptedValue);
    } catch {
      return safeDefaultValue;
    }
  }

  /**
   * Set the pref to the encrypted form of the value.
   *
   * @param {string} prefName -> The pref whose value is to be set.
   * @param {string} value -> The value to be set in its encryoted form.
   */
  #setSecurePref(prefName, value) {
    let encryptedValue = this._crypto.encrypt(value);
    Services.prefs.setStringPref(prefName, encryptedValue);
  }

  /**
   * Get whether the OSAuth is enabled or not.
   *
   * @param {string} prefName -> The name of the pref (creditcards or addresses)
   * @returns {boolean}
   */
  getOSAuthEnabled(prefName) {
    return this.#getSecurePref(prefName, "") !== "opt out";
  }

  /**
   * Set whether the OSAuth is enabled or not.
   *
   * @param {string} prefName -> The pref to encrypt.
   * @param {boolean} enable -> Whether the pref is to be enabled.
   */
  setOSAuthEnabled(prefName, enable) {
    if (enable) {
      Services.prefs.clearUserPref(prefName);
    } else {
      this.#setSecurePref(prefName, "opt out");
    }
  }
}
PK
!<#�g%
%
.modules/megalist/aggregator/Aggregator.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Connects multiple Data Sources with multiple View Models.
 * Aggregator owns Data Sources.
 * Aggregator weakly refers to View Models.
 */
export class Aggregator {
  #sources = [];
  #attachedViewModels = [];

  attachViewModel(viewModel) {
    // Weak reference the View Model so we do not keep it in memory forever
    this.#attachedViewModels.push(new WeakRef(viewModel));
  }

  detachViewModel(viewModel) {
    for (let i = this.#attachedViewModels.length - 1; i >= 0; i--) {
      const knownViewModel = this.#attachedViewModels[i].deref();
      if (viewModel == knownViewModel || !knownViewModel) {
        this.#attachedViewModels.splice(i, 1);
      }
    }
  }

  /**
   * Run action on each of the alive attached view models.
   * Remove dead consumers.
   *
   * @param {Function} action to perform on each alive consumer
   */
  forEachViewModel(action) {
    for (let i = this.#attachedViewModels.length - 1; i >= 0; i--) {
      const viewModel = this.#attachedViewModels[i].deref();
      if (viewModel) {
        action(viewModel);
      } else {
        this.#attachedViewModels.splice(i, 1);
      }
    }
  }

  *enumerateLines(searchText) {
    for (let source of this.#sources) {
      yield* source.enumerateLines(searchText);
    }
  }

  /**
   *
   * @param {Function} createSourceFn (aggregatorApi) used to create Data Source.
   *                   aggregatorApi is the way for Data Source to push data
   *                   to the Aggregator.
   */
  addSource(createSourceFn) {
    const api = this.#apiForDataSource();
    const source = createSourceFn(api);
    this.#sources.push(source);
  }

  callFunction(dataSource, functionName, ...params) {
    const source = this.#sources.find(
      source => source.constructor.name === dataSource
    );

    if (source && source[functionName]) {
      source[functionName](params);
    }
  }

  /**
   * Exposes interface for a datasource to communicate with Aggregator.
   */
  #apiForDataSource() {
    const aggregator = this;
    return {
      refreshSingleLineOnScreen(line) {
        aggregator.forEachViewModel(vm => vm.refreshSingleLineOnScreen(line));
      },

      refreshAllLinesOnScreen() {
        aggregator.forEachViewModel(vm => vm.refreshAllLinesOnScreen());
      },

      setLayout(layout) {
        aggregator.forEachViewModel(vm => vm.setLayout(layout));
      },
    };
  }
}
PK
!<���??5modules/megalist/aggregator/DefaultAggregator.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Aggregator } from "resource://gre/modules/megalist/aggregator/Aggregator.sys.mjs";
import { LoginDataSource } from "resource://gre/modules/megalist/aggregator/datasources/LoginDataSource.sys.mjs";

export class DefaultAggregator extends Aggregator {
  constructor() {
    super();
    this.addSource(aggregatorApi => new LoginDataSource(aggregatorApi));
  }
}
PK
!<�|ؖ�#�#>modules/megalist/aggregator/datasources/DataSourceBase.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { BinarySearch } from "resource://gre/modules/BinarySearch.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "ClipboardHelper",
  "@mozilla.org/widget/clipboardhelper;1",
  "nsIClipboardHelper"
);

/**
 * Create a function to format messages.
 *
 * @param  {...any} ftlFiles to be used for formatting messages
 * @returns {Function} a function that can be used to format messsages
 */
function createFormatMessages(...ftlFiles) {
  const strings = new Localization(ftlFiles);

  return async (...ids) => {
    for (const i in ids) {
      if (typeof ids[i] == "string") {
        ids[i] = { id: ids[i] };
      }
    }

    const messages = await strings.formatMessages(ids);
    return messages.map(message => {
      if (message.attributes) {
        return message.attributes.reduce(
          (result, { name, value }) => ({ ...result, [name]: value }),
          {}
        );
      }
      return message.value;
    });
  };
}

/**
 * Base datasource class
 */
export class DataSourceBase {
  #aggregatorApi;

  constructor(aggregatorApi) {
    this.#aggregatorApi = aggregatorApi;
  }

  // proxy consumer api functions to datasource interface

  refreshSingleLineOnScreen(line) {
    this.#aggregatorApi.refreshSingleLineOnScreen(line);
  }

  refreshAllLinesOnScreen() {
    this.#aggregatorApi.refreshAllLinesOnScreen();
  }

  setLayout(layout) {
    this.#aggregatorApi.setLayout(layout);
  }

  formatMessages = createFormatMessages("preview/megalist.ftl");
  static ftl = new Localization(["branding/brand.ftl", "preview/megalist.ftl"]);

  async localizeStrings(strings) {
    const keys = Object.keys(strings);
    const localisationIds = Object.values(strings)
      .filter(id => id)
      .map(id => ({ id }));
    const messages = await DataSourceBase.ftl.formatMessages(localisationIds);

    for (let i = 0; i < messages.length; i++) {
      let { attributes, value } = messages[i];
      if (attributes) {
        value = attributes.reduce(
          (result, { name, value }) => ({ ...result, [name]: value }),
          {}
        );
      }
      strings[keys[i]] = value;
    }
    return strings;
  }

  getPlatformFtl(messageId) {
    // OS auth is only supported on Windows and macOS
    if (
      AppConstants.platform == "linux" &&
      "passwords-export-os-auth-dialog-message"
    ) {
      return null;
    }

    if (AppConstants.platform == "macosx") {
      messageId += "-macosx";
    } else if (AppConstants.platform == "win") {
      messageId += "-win";
    }

    return messageId;
  }

  /**
   * Prototype for the each line.
   * See this link for details:
   * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties#props
   */
  #linePrototype = {
    /**
     * Reference to the Data Source that owns this line.
     */
    source: this,

    /**
     * Each line has a reference to the actual data record.
     */
    record: { writable: true },

    /**
     * Is line ready to be displayed?
     * Used by the View Model.
     *
     * @returns {boolean} true if line can be sent to the view.
     *          false if line is not ready to be displayed. In this case
     *          data source will start pulling value from the underlying
     *          storage and will push data to screen when it's ready.
     */
    lineIsReady() {
      return true;
    },

    isEditing() {
      return this.editingValue !== undefined;
    },

    copyToClipboard(text) {
      lazy.ClipboardHelper.copyString(
        text,
        null,
        lazy.ClipboardHelper.Sensitive
      );

      this.refreshOnScreen();
    },

    openLinkInTab(url) {
      const { BrowserWindowTracker } = ChromeUtils.importESModule(
        "resource:///modules/BrowserWindowTracker.sys.mjs"
      );
      const browser = BrowserWindowTracker.getTopWindow().gBrowser;
      browser.addWebTab(url, { inBackground: false });
    },

    /**
     * Simple version of Copy command. Line still needs to add "Copy" command.
     * Override if copied value != displayed value.
     */
    executeCopy() {
      this.copyToClipboard(this.value);
    },

    executeOpen() {
      this.openLinkInTab(this.href);
    },

    executeEditInProgress(value) {
      this.editingValue = value;
      this.refreshOnScreen();
    },

    executeCancel() {
      delete this.editingValue;
      this.refreshOnScreen();
    },

    get template() {
      return "editingValue" in this ? "editingLineTemplate" : undefined;
    },

    refreshOnScreen() {
      this.source.refreshSingleLineOnScreen(this);
    },
    setLayout(data) {
      this.source.setLayout(data);
    },
  };

  /**
   * Creates collapsible section header line.
   *
   * @param {string} label for the section
   * @returns {object} section header line
   */
  createHeaderLine(label, tooltip) {
    const result = {
      label,
      value: {},
      collapsed: false,
      start: true,
      end: true,
      source: this,

      /**
       * Use different templates depending on the collapsed state.
       */
      get template() {
        return this.collapsed
          ? "collapsedSectionTemplate"
          : "expandedSectionTemplate";
      },

      lineIsReady: () => true,

      commands: [{ id: "Toggle", label: "command-toggle" }],

      get toggleTooltip() {
        return this.collapsed ? tooltip.expand : tooltip.collapse;
      },

      executeToggle() {
        this.collapsed = !this.collapsed;
        this.source.refreshAllLinesOnScreen();
      },
    };

    return result;
  }

  /**
   * Create a prototype to be used for data lines,
   * provides common set of features like Copy command.
   *
   * @param {object} properties to customize data line
   * @returns {object} data line prototype
   */
  prototypeDataLine(properties) {
    return Object.create(this.#linePrototype, properties);
  }

  lines = [];
  #collator = new Intl.Collator();
  #linesToForget;

  /**
   * Code to run before reloading data source.
   * It will start tracking which lines are no longer at the source so
   * afterReloadingDataSource() can remove them.
   */
  beforeReloadingDataSource() {
    this.#linesToForget = new Set(this.lines);
  }

  /**
   * Code to run after reloading data source.
   * It will forget lines that are no longer at the source and refresh screen.
   */
  afterReloadingDataSource() {
    // We do a null checks on `linesToForget` despite being initialized to a
    // Set in `beforeReloadingDataSource`. We should re-evaluate the callsites
    // of before/afterReloadingDataSource.
    if (this.#linesToForget?.size) {
      for (let i = this.lines.length; i >= 0; i--) {
        if (this.#linesToForget.has(this.lines[i])) {
          this.lines.splice(i, 1);
        }
      }
    }

    this.#linesToForget = null;
    this.refreshAllLinesOnScreen();
  }

  /**
   * Add or update line associated with the record.
   *
   * @param {object} record with which line is associated
   * @param {*} id sortable line id
   * @param {*} fieldPrototype to be used when creating a line.
   */
  addOrUpdateLine(record, id, fieldPrototype) {
    let [found, index] = BinarySearch.search(
      (target, value) => this.#collator.compare(target, value.id),
      this.lines,
      id
    );

    if (found) {
      this.#linesToForget?.delete(this.lines[index]);
    } else {
      const line = Object.create(fieldPrototype, { id: { value: id } });
      this.lines.splice(index, 0, line);
    }
    this.lines[index].record = record;
    return this.lines[index];
  }

  cancelDialog() {
    this.setLayout(null);
  }

  *enumerateLinesForMatchingRecords(searchText, stats, match) {
    stats.total = 0;
    stats.count = 0;

    if (searchText) {
      let i = 0;
      while (i < this.lines.length) {
        const currentRecord = this.lines[i].record;
        stats.total += 1;

        if (match(currentRecord)) {
          // Record matches, yield all it's lines
          while (
            i < this.lines.length &&
            currentRecord == this.lines[i].record
          ) {
            yield this.lines[i];
            i += 1;
          }
          stats.count += 1;
        } else {
          // Record does not match, skip until the next one
          while (
            i < this.lines.length &&
            currentRecord == this.lines[i].record
          ) {
            i += 1;
          }
        }
      }
    } else {
      // No search text is provided - send all lines out, count records
      let currentRecord;
      for (const line of this.lines) {
        yield line;

        if (line.record != currentRecord) {
          stats.total += 1;
          currentRecord = line.record;
        }
      }
      stats.count = stats.total;
    }
  }
}
PK
!<�X߫�O�O?modules/megalist/aggregator/datasources/LoginDataSource.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { LoginHelper } from "resource://gre/modules/LoginHelper.sys.mjs";
import { DataSourceBase } from "resource://gre/modules/megalist/aggregator/datasources/DataSourceBase.sys.mjs";
import { LoginCSVImport } from "resource://gre/modules/LoginCSVImport.sys.mjs";
import { LoginExport } from "resource://gre/modules/LoginExport.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  LoginBreaches: "resource:///modules/LoginBreaches.sys.mjs",
  MigrationUtils: "resource:///modules/MigrationUtils.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "BREACH_ALERTS_ENABLED",
  "signon.management.page.breach-alerts.enabled",
  false
);

// Precedence values when sorting logins by alerts.
const ALERT_VALUES = {
  breached: 0,
  vulnerable: 1,
  none: 2,
};

const SUPPORT_URL =
  Services.urlFormatter.formatURLPref("app.support.baseURL") +
  "password-manager-remember-delete-edit-logins";

const PREFRENCES_URL = "about:preferences#privacy-logins";

/**
 * Data source for Logins.
 *
 * Each login is represented by 3 lines: origin, username and password.
 *
 * Protypes are used to reduce memory need because for different records
 * similar lines will differ in values only.
 */
export class LoginDataSource extends DataSourceBase {
  doneReloadDataSource;
  #originPrototype;
  #usernamePrototype;
  #passwordPrototype;
  #enabled;
  #header;
  #exportPasswordsStrings;
  #sortId;

  constructor(...args) {
    super(...args);
    // Wait for Fluent to provide strings before loading data
    this.localizeStrings({
      headerLabel: "passwords-section-label",
      expandSection: "passwords-expand-section-tooltip",
      collapseSection: "passwords-collapse-section-tooltip",
      originLabel: "passwords-origin-label",
      usernameLabel: "passwords-username-label",
      passwordLabel: "passwords-password-label",
      passwordsDisabled: "passwords-disabled",
      passwordOSAuthDialogCaption: "passwords-os-auth-dialog-caption",
      passwordsImportFilePickerTitle: "passwords-import-file-picker-title",
      passwordsImportFilePickerImportButton:
        "passwords-import-file-picker-import-button",
      passwordsImportFilePickerCsvFilterTitle:
        "passwords-import-file-picker-csv-filter-title",
      passwordsImportFilePickerTsvFilterTitle:
        "passwords-import-file-picker-tsv-filter-title",
      exportPasswordsOSReauthMessage: this.getPlatformFtl(
        "passwords-export-os-auth-dialog-message"
      ),
      passwordsExportFilePickerTitle: "passwords-export-file-picker-title",
      passwordsExportFilePickerDefaultFileName:
        "passwords-export-file-picker-default-filename",
      passwordsExportFilePickerExportButton:
        "passwords-export-file-picker-export-button",
      passwordsExportFilePickerCsvFilterTitle:
        "passwords-export-file-picker-csv-filter-title",
      dismissBreachCommandLabel: "passwords-dismiss-breach-alert-command",
    }).then(strings => {
      const copyCommand = { id: "Copy", label: "command-copy" };
      const editCommand = { id: "Edit", label: "command-edit" };
      const deleteCommand = { id: "Delete", label: "command-delete" };
      const dismissBreachCommand = {
        id: "DismissBreach",
        label: strings.dismissBreachCommandLabel,
      };
      const noOriginSticker = { type: "error", label: "😾 Missing origin" };
      const noPasswordSticker = { type: "error", label: "😾 Missing password" };
      const breachedSticker = { type: "warning", label: "BREACH" };
      const vulnerableSticker = { type: "risk", label: "🤮 Vulnerable" };
      const tooltip = {
        expand: strings.expandSection,
        collapse: strings.collapseSection,
      };
      this.#header = this.createHeaderLine(strings.headerLabel, tooltip);
      this.#header.commands.push(
        { id: "Create", label: "passwords-command-create" },
        {
          id: "ImportFromBrowser",
          label: "passwords-command-import-from-browser",
        },
        { id: "Import", label: "passwords-command-import" },
        { id: "Export", label: "passwords-command-export" },
        { id: "RemoveAll", label: "passwords-command-remove-all" },
        { id: "Settings", label: "passwords-command-settings" },
        { id: "Help", label: "passwords-command-help" },
        { id: "SortByName", label: "passwords-command-sort-name" },
        {
          id: "SortByAlerts",
          label: "passwords-command-sort-alerts",
        }
      );
      this.#header.executeImport = async () =>
        this.#importFromFile(
          strings.passwordsImportFilePickerTitle,
          strings.passwordsImportFilePickerImportButton,
          strings.passwordsImportFilePickerCsvFilterTitle,
          strings.passwordsImportFilePickerTsvFilterTitle
        );

      this.#header.executeImportFromBrowser = () => this.#importFromBrowser();
      this.#header.executeRemoveAll = () => this.#removeAllPasswords();
      this.#header.executeSettings = () => this.#openMenuLink(PREFRENCES_URL);
      this.#header.executeHelp = () => this.#openMenuLink(SUPPORT_URL);
      this.#header.executeExport = async () => this.#exportAllPasswords();
      this.#exportPasswordsStrings = {
        OSReauthMessage: strings.exportPasswordsOSReauthMessage,
        OSAuthDialogCaption: strings.passwordOSAuthDialogCaption,
        ExportFilePickerTitle: strings.passwordsExportFilePickerTitle,
        FilePickerExportButton: strings.passwordsExportFilePickerExportButton,
        FilePickerDefaultFileName:
          strings.passwordsExportFilePickerDefaultFileName.concat(".csv"),
        FilePickerCsvFilterTitle:
          strings.passwordsExportFilePickerCsvFilterTitle,
      };

      this.#header.executeSortByName = () => {
        if (this.#sortId !== "name") {
          this.#sortId = "name";
          this.#reloadDataSource();
        }
      };

      this.#header.executeSortByAlerts = async () => {
        if (this.#sortId !== "alerts") {
          this.#sortId = "alerts";
          this.#reloadDataSource();
        }
      };

      this.#originPrototype = this.prototypeDataLine({
        field: { value: "origin" },
        label: { value: strings.originLabel },
        start: { value: true },
        value: {
          get() {
            return this.record.displayOrigin;
          },
        },
        valueIcon: {
          get() {
            return `page-icon:${this.record.origin}`;
          },
        },
        href: {
          get() {
            return this.record.origin;
          },
        },
        commands: {
          value: [
            { id: "Open", label: "command-open" },
            copyCommand,
            editCommand,
            deleteCommand,
            dismissBreachCommand,
          ],
        },
        executeDismissBreach: {
          value() {
            lazy.LoginBreaches.recordBreachAlertDismissal(this.record.guid);
            delete this.breached;
            this.refreshOnScreen();
          },
        },
        executeCopy: {
          value() {
            this.copyToClipboard(this.record.origin);
          },
        },
        executeDelete: {
          value() {
            this.setLayout({ id: "remove-login" });
          },
        },
        stickers: {
          *value() {
            if (this.isEditing() && !this.editingValue.length) {
              yield noOriginSticker;
            }

            if (this.breached) {
              yield breachedSticker;
            }
          },
        },
      });
      this.#usernamePrototype = this.prototypeDataLine({
        field: { value: "username" },
        label: { value: strings.usernameLabel },
        value: {
          get() {
            return this.editingValue ?? this.record.username;
          },
        },
        commands: { value: [copyCommand, editCommand, "-", deleteCommand] },
        executeEdit: {
          value() {
            this.editingValue = this.record.username ?? "";
            this.refreshOnScreen();
          },
        },
        executeSave: {
          value(value) {
            try {
              const modifiedLogin = this.record.clone();
              modifiedLogin.username = value;
              Services.logins.modifyLogin(this.record, modifiedLogin);
            } catch (error) {
              //todo
              console.error("failed to modify login", error);
            }
            this.executeCancel();
          },
        },
      });
      this.#passwordPrototype = this.prototypeDataLine({
        field: { value: "password" },
        label: { value: strings.passwordLabel },
        concealed: { value: true, writable: true },
        end: { value: true },
        value: {
          get() {
            return (
              this.editingValue ??
              (this.concealed ? "••••••••" : this.record.password)
            );
          },
        },
        stickers: {
          *value() {
            if (this.isEditing() && !this.editingValue.length) {
              yield noPasswordSticker;
            }

            if (this.vulnerable) {
              yield vulnerableSticker;
            }
          },
        },
        commands: {
          value: [
            { ...copyCommand, verify: true },
            {
              id: "Reveal",
              label: "command-reveal",
              verify: true,
            },
            { id: "Conceal", label: "command-conceal" },
            editCommand,
            deleteCommand,
          ],
        },
        executeReveal: {
          value() {
            this.concealed = false;
            this.refreshOnScreen();
          },
        },
        executeConceal: {
          value() {
            this.concealed = true;
            this.refreshOnScreen();
          },
        },
        executeCopy: {
          value() {
            this.copyToClipboard(this.record.password);
          },
        },
        executeEdit: {
          value() {
            this.editingValue = this.record.password ?? "";
            this.refreshOnScreen();
          },
        },
        executeSave: {
          value(value) {
            if (!value) {
              return;
            }

            try {
              const modifiedLogin = this.record.clone();
              modifiedLogin.password = value;
              Services.logins.modifyLogin(this.record, modifiedLogin);
            } catch (error) {
              //todo
              console.error("failed to modify login", error);
            }
            this.executeCancel();
          },
        },
      });

      // Sort by origin, then by username, then by GUID
      this.#sortId = "name";
      Services.obs.addObserver(this, "passwordmgr-storage-changed");
      Services.prefs.addObserver("signon.rememberSignons", this);
      Services.prefs.addObserver(
        "signon.management.page.breach-alerts.enabled",
        this
      );
      Services.prefs.addObserver(
        "signon.management.page.vulnerable-passwords.enabled",
        this
      );
      this.#reloadDataSource();
    });
  }

  async #importFromFile(title, buttonLabel, csvTitle, tsvTitle) {
    const { BrowserWindowTracker } = ChromeUtils.importESModule(
      "resource:///modules/BrowserWindowTracker.sys.mjs"
    );
    const browsingContext = BrowserWindowTracker.getTopWindow().browsingContext;
    let { result, path } = await this.openFilePickerDialog(
      title,
      buttonLabel,
      [
        {
          title: csvTitle,
          extensionPattern: "*.csv",
        },
        {
          title: tsvTitle,
          extensionPattern: "*.tsv",
        },
      ],
      browsingContext
    );

    if (result != Ci.nsIFilePicker.returnCancel) {
      try {
        const summary = await LoginCSVImport.importFromCSV(path);
        const counts = { added: 0, modified: 0, no_change: 0, error: 0 };

        for (const item of summary) {
          counts[item.result] += 1;
        }
        const l10nArgs = Object.values(counts).map(count => ({ count }));

        this.setLayout({
          id: "import-logins",
          l10nArgs,
        });
      } catch (e) {
        this.setLayout({ id: "import-error" });
      }
    }
  }

  async openFilePickerDialog(
    title,
    okButtonLabel,
    appendFilters,
    browsingContext
  ) {
    return new Promise(resolve => {
      let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
      fp.init(browsingContext, title, Ci.nsIFilePicker.modeOpen);
      for (const appendFilter of appendFilters) {
        fp.appendFilter(appendFilter.title, appendFilter.extensionPattern);
      }
      fp.appendFilters(Ci.nsIFilePicker.filterAll);
      fp.okButtonLabel = okButtonLabel;
      fp.open(async result => {
        resolve({ result, path: fp.file.path });
      });
    });
  }

  #importFromBrowser() {
    const { BrowserWindowTracker } = ChromeUtils.importESModule(
      "resource:///modules/BrowserWindowTracker.sys.mjs"
    );
    const browser = BrowserWindowTracker.getTopWindow().gBrowser;
    try {
      lazy.MigrationUtils.showMigrationWizard(browser.ownerGlobal, {
        entrypoint: lazy.MigrationUtils.MIGRATION_ENTRYPOINTS.PASSWORDS,
      });
    } catch (ex) {
      console.error(ex);
    }
  }

  #removeAllPasswords() {
    let count = 0;
    let currentRecord;
    for (const line of this.lines) {
      if (line.record != currentRecord) {
        count += 1;
        currentRecord = line.record;
      }
    }

    this.setLayout({ id: "remove-logins", l10nArgs: [{ count }] });
  }

  confirmRemoveAll() {
    Services.logins.removeAllLogins();
    this.cancelDialog();
  }

  confirmRemoveLogin([record]) {
    Services.logins.removeLogin(record);
    this.cancelDialog();
  }

  confirmRetryImport() {
    this.#header.executeImport();
    this.cancelDialog();
  }

  #exportAllPasswords() {
    this.setLayout({ id: "export-logins" });
  }

  async confirmExportLogins() {
    const { BrowserWindowTracker } = ChromeUtils.importESModule(
      "resource:///modules/BrowserWindowTracker.sys.mjs"
    );
    const browsingContext = BrowserWindowTracker.getTopWindow().browsingContext;

    const isOSAuthEnabled = LoginHelper.getOSAuthEnabled(
      LoginHelper.OS_AUTH_FOR_PASSWORDS_PREF
    );

    let { isAuthorized, telemetryEvent } = await LoginHelper.requestReauth(
      browsingContext,
      isOSAuthEnabled,
      null, // Prompt regardless of a recent prompt
      this.#exportPasswordsStrings.OSReauthMessage,
      this.#exportPasswordsStrings.OSAuthDialogCaption
    );

    let { method, object, extra = {}, value = null } = telemetryEvent;
    Services.telemetry.recordEvent("pwmgr", method, object, value, extra);

    if (!isAuthorized) {
      this.cancelDialog();
      return;
    }
    this.exportFilePickerDialog(browsingContext);
    this.cancelDialog();
  }

  exportFilePickerDialog(browsingContext) {
    let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
    function fpCallback(aResult) {
      if (aResult != Ci.nsIFilePicker.returnCancel) {
        LoginExport.exportAsCSV(fp.file.path);
        Services.telemetry.recordEvent(
          "pwmgr",
          "mgmt_menu_item_used",
          "export_complete"
        );
      }
    }
    fp.init(
      browsingContext,
      this.#exportPasswordsStrings.ExportFilePickerTitle,
      Ci.nsIFilePicker.modeSave
    );
    fp.appendFilter(
      this.#exportPasswordsStrings.FilePickerCsvFilterTitle,
      "*.csv"
    );
    fp.appendFilters(Ci.nsIFilePicker.filterAll);
    fp.defaultString = this.#exportPasswordsStrings.FilePickerDefaultFileName;
    fp.defaultExtension = "csv";
    fp.okButtonLabel = this.#exportPasswordsStrings.FilePickerExportButton;
    fp.open(fpCallback);
  }

  #openMenuLink(url) {
    const { BrowserWindowTracker } = ChromeUtils.importESModule(
      "resource:///modules/BrowserWindowTracker.sys.mjs"
    );
    const browser = BrowserWindowTracker.getTopWindow().gBrowser;
    browser.ownerGlobal.switchToTabHavingURI(url, true, {
      ignoreFragment: "whenComparingAndReplace",
    });
  }

  /**
   * Enumerate all the lines provided by this data source.
   *
   * @param {string} searchText used to filter data
   */
  *enumerateLines(searchText) {
    if (this.#enabled === undefined) {
      // Async Fluent API makes it possible to have data source waiting
      // for the localized strings, which can be detected by undefined in #enabled.
      return;
    }

    yield this.#header;
    if (this.#header.collapsed || !this.#enabled) {
      return;
    }

    const stats = { count: 0, total: 0 };
    searchText = searchText.toUpperCase();
    yield* this.enumerateLinesForMatchingRecords(
      searchText,
      stats,
      login =>
        login.displayOrigin.toUpperCase().includes(searchText) ||
        login.username.toUpperCase().includes(searchText) ||
        login.password.toUpperCase().includes(searchText)
    );

    this.#header.value.statsTotal = stats.total;
  }

  /**
   * Sync lines array with the actual data source.
   * This function reads all logins from the storage, adds or updates lines and
   * removes lines for the removed logins.
   */
  async #reloadDataSource() {
    this.doneReloadDataSource = false;
    this.#enabled = Services.prefs.getBoolPref("signon.rememberSignons");
    if (!this.#enabled) {
      this.#reloadEmptyDataSource();
      this.doneReloadDataSource = true;
      return;
    }

    const logins = await LoginHelper.getAllUserFacingLogins();
    this.beforeReloadingDataSource();

    const breachesMap = lazy.BREACH_ALERTS_ENABLED
      ? await lazy.LoginBreaches.getPotentialBreachesByLoginGUID(logins)
      : new Map();

    const breachedOrVulnerableLogins = logins.filter(
      login =>
        breachesMap.has(login.guid) ||
        lazy.LoginBreaches.isVulnerablePassword(login)
    );

    const filteredLogins =
      this.#sortId === "alerts" ? breachedOrVulnerableLogins : logins;

    filteredLogins.forEach(login => {
      // Similar domains will be grouped together
      // www. will have least effect on the sorting
      const parts = login.displayOrigin.split(".");

      // Exclude TLD domain
      //todo support eTLD and use public suffix here https://publicsuffix.org
      if (parts.length > 1) {
        parts.length -= 1;
      }
      const isLoginBreached = breachesMap.has(login.guid);
      const isLoginVulnerable = lazy.LoginBreaches.isVulnerablePassword(login);

      let alertValue;
      if (isLoginBreached) {
        alertValue = ALERT_VALUES.breached;
      } else if (isLoginVulnerable) {
        alertValue = ALERT_VALUES.vulnerable;
      } else {
        alertValue = ALERT_VALUES.none;
      }

      const domain = parts.reverse().join(".");
      const lineId =
        this.#sortId === "alerts"
          ? `${alertValue}:${domain}:${login.username}:${login.guid}`
          : `${domain}:${login.username}:${login.guid}`;

      let originLine = this.addOrUpdateLine(
        login,
        lineId + "0",
        this.#originPrototype
      );
      this.addOrUpdateLine(login, lineId + "1", this.#usernamePrototype);
      let passwordLine = this.addOrUpdateLine(
        login,
        lineId + "2",
        this.#passwordPrototype
      );

      originLine.breached = isLoginBreached;
      passwordLine.vulnerable = isLoginVulnerable;
    });

    this.#header.value.total = logins.length;
    this.#header.value.alerts = breachedOrVulnerableLogins.length;
    this.afterReloadingDataSource();
    this.doneReloadDataSource = true;
  }

  #reloadEmptyDataSource() {
    this.lines.length = 0;
    //todo: user can enable passwords by activating Passwords header line
    this.#header.value.total = 0;
    this.#header.value.alerts = 0;
    this.refreshAllLinesOnScreen();
  }

  observe(_subj, topic, message) {
    if (
      topic == "passwordmgr-storage-changed" ||
      message == "signon.rememberSignons" ||
      message == "signon.management.page.breach-alerts.enabled" ||
      message == "signon.management.page.vulnerable-passwords.enabled"
    ) {
      this.#reloadDataSource();
    }
  }
}
PK
!<_��U9:9:'modules/narrate/NarrateControls.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AsyncPrefs } from "resource://gre/modules/AsyncPrefs.sys.mjs";
import { Narrator } from "resource://gre/modules/narrate/Narrator.sys.mjs";
import { VoiceSelect } from "resource://gre/modules/narrate/VoiceSelect.sys.mjs";

var gStrings = Services.strings.createBundle(
  "chrome://global/locale/narrate.properties"
);

export function NarrateControls(win, languagePromise) {
  this._winRef = Cu.getWeakReference(win);
  this._languagePromise = languagePromise;

  win.addEventListener("unload", this);

  let improvedTextMenuEnabled = Services.prefs.getBoolPref(
    "reader.improved_text_menu.enabled",
    false
  );

  // Append content style sheet in document head
  let style = win.document.createElement("link");
  style.rel = "stylesheet";
  if (improvedTextMenuEnabled) {
    style.href = "chrome://global/skin/narrate-improved.css";
  } else {
    style.href = "chrome://global/skin/narrate.css";
  }
  win.document.head.appendChild(style);

  let elemL10nMap = {
    ".narrate-skip-previous": "previous-label",
    ".narrate-start-stop": "start-label",
    ".narrate-skip-next": "next-label",
    ".narrate-rate-input": "speed",
  };

  let dropdown = win.document.createElement("ul");
  dropdown.className = "dropdown narrate-dropdown";

  let toggle = win.document.createElement("li");
  let toggleButton = win.document.createElement("button");
  toggleButton.className = "dropdown-toggle toolbar-button narrate-toggle";
  toggleButton.dataset.telemetryId = "reader-listen";
  let tip = win.document.createElement("span");
  let shortcutNarrateKey = gStrings.GetStringFromName("narrate-key-shortcut");
  let labelText = gStrings.formatStringFromName("read-aloud-label", [
    shortcutNarrateKey,
  ]);
  tip.textContent = labelText;
  tip.className = "hover-label";
  toggleButton.append(tip);
  toggleButton.setAttribute("aria-label", labelText);
  toggleButton.hidden = true;
  dropdown.appendChild(toggle);
  toggle.appendChild(toggleButton);

  let dropdownList = win.document.createElement("li");
  dropdownList.className = "dropdown-popup";
  dropdown.appendChild(dropdownList);

  if (improvedTextMenuEnabled) {
    let narrateHeader = win.document.createElement("h2");
    narrateHeader.id = "narrate-header";
    narrateHeader.textContent = gStrings.GetStringFromName("read-aloud-header");
    dropdownList.appendChild(narrateHeader);
  }

  let narrateControl = win.document.createElement("div");
  narrateControl.className = "narrate-row narrate-control";
  dropdownList.appendChild(narrateControl);

  let narrateRate = win.document.createElement("div");
  narrateRate.className = "narrate-row narrate-rate";
  dropdownList.appendChild(narrateRate);

  let selectLabel = "";
  if (improvedTextMenuEnabled) {
    let hr = win.document.createElement("hr");
    let voiceHeader = win.document.createElement("h2");
    voiceHeader.id = "voice-header";
    voiceHeader.textContent = gStrings.GetStringFromName("select-voice-header");
    dropdownList.appendChild(hr);
    dropdownList.appendChild(voiceHeader);
  } else {
    selectLabel = gStrings.GetStringFromName("selectvoicelabel");
  }

  let narrateVoices = win.document.createElement("div");
  narrateVoices.className = "narrate-row narrate-voices";
  dropdownList.appendChild(narrateVoices);

  let narrateSkipPrevious = win.document.createElement("button");
  narrateSkipPrevious.className = "narrate-skip-previous";
  narrateSkipPrevious.disabled = true;
  narrateSkipPrevious.ariaKeyShortcuts = "ArrowLeft";
  narrateControl.appendChild(narrateSkipPrevious);

  let narrateStartStop = win.document.createElement("button");
  narrateStartStop.className = "narrate-start-stop";
  narrateStartStop.ariaKeyShortcuts = "N";
  narrateControl.appendChild(narrateStartStop);

  let narrateSkipNext = win.document.createElement("button");
  narrateSkipNext.className = "narrate-skip-next";
  narrateSkipNext.disabled = true;
  narrateSkipNext.ariaKeyShortcuts = "ArrowRight";
  narrateControl.appendChild(narrateSkipNext);

  win.document.addEventListener("keydown", function (event) {
    if (win.document.hasFocus() && event.key === "n") {
      narrateStartStop.click();
    }
    //Arrow key direction also hardcoded for RTL in order to be
    //consistent with playback arrows in UI panel
    if (win.document.hasFocus() && event.key === "ArrowLeft") {
      narrateSkipPrevious.click();
    }
    if (win.document.hasFocus() && event.key === "ArrowRight") {
      narrateSkipNext.click();
    }
  });

  let narrateRateInput = win.document.createElement("input");
  narrateRateInput.className = "narrate-rate-input";
  narrateRateInput.setAttribute("value", "0");
  narrateRateInput.setAttribute("step", "5");
  narrateRateInput.setAttribute("max", "100");
  narrateRateInput.setAttribute("min", "-100");
  narrateRateInput.setAttribute("type", "range");
  narrateRateInput.setAttribute(
    "aria-label",
    "Choose a narration speed from -100 to 100, where 0 is the default speed."
  );

  let narrateRateSlowIcon = win.document.createElement("span");
  narrateRateSlowIcon.className = "narrate-rate-icon slow";
  narrateRateSlowIcon.title = gStrings.GetStringFromName("slow-speed-label");

  let narrateRateFastIcon = win.document.createElement("span");
  narrateRateFastIcon.className = "narrate-rate-icon fast";
  narrateRateFastIcon.title = gStrings.GetStringFromName("fast-speed-label");

  narrateRate.appendChild(narrateRateSlowIcon);
  narrateRate.appendChild(narrateRateInput);
  narrateRate.appendChild(narrateRateFastIcon);

  function setShortcutAttribute(
    keyShortcut,
    stringID,
    selector,
    isString = false
  ) {
    let shortcut;
    if (isString) {
      shortcut = keyShortcut;
    } else {
      shortcut = gStrings.GetStringFromName(keyShortcut);
    }
    let label = gStrings.formatStringFromName(stringID, [shortcut]);

    dropdown.querySelector(selector).setAttribute("title", label);
  }

  for (const [selector, stringID] of Object.entries(elemL10nMap)) {
    switch (selector) {
      case ".narrate-start-stop":
        setShortcutAttribute("narrate-key-shortcut", stringID, selector);
        break;

      // Arrow direction also hardcoded for RTL in order to be
      // consistent with playback arrows in UI panel
      case ".narrate-skip-previous":
        setShortcutAttribute("←", stringID, selector, true);
        break;

      case ".narrate-skip-next":
        setShortcutAttribute("→", stringID, selector, true);
        break;

      default:
        dropdown
          .querySelector(selector)
          .setAttribute("title", gStrings.GetStringFromName(stringID));
        break;
    }
  }

  this.narrator = new Narrator(win, languagePromise);

  let branch = Services.prefs.getBranch("narrate.");
  this.voiceSelect = new VoiceSelect(win, selectLabel);
  this.voiceSelect.element.addEventListener("change", this);
  this.voiceSelect.element.classList.add("voice-select");
  this.voiceSelect.selectToggle.setAttribute("aria-labelledby", "voice-header");
  win.speechSynthesis.addEventListener("voiceschanged", this);
  dropdown
    .querySelector(".narrate-voices")
    .appendChild(this.voiceSelect.element);

  dropdown.addEventListener("click", this, true);
  dropdown.addEventListener("keydown", this, true);

  let rateRange = dropdown.querySelector(".narrate-rate > input");
  rateRange.addEventListener("change", this);

  // The rate is stored as an integer.
  rateRange.value = branch.getIntPref("rate");

  this._setupVoices();

  let tb = win.document.querySelector(".reader-controls");
  tb.appendChild(dropdown);
}

NarrateControls.prototype = {
  handleEvent(evt) {
    switch (evt.type) {
      case "change":
        if (evt.target.classList.contains("narrate-rate-input")) {
          this._onRateInput(evt);
        } else {
          this._onVoiceChange();
        }
        break;
      case "click":
        this._onButtonClick(evt);
        break;
      case "keydown":
        if (evt.key === "Tab" && !evt.shiftKey) {
          this._handleFocus(evt);
        }
        break;
      case "voiceschanged":
        this._setupVoices();
        break;
      case "unload":
        this.narrator.stop();
        break;
    }
  },

  /**
   * Returns true if synth voices are available.
   */
  _setupVoices() {
    return this._languagePromise.then(language => {
      this.voiceSelect.clear();
      let win = this._win;
      let voicePrefs = this._getVoicePref();
      let selectedVoice = voicePrefs[language || "default"];
      let comparer = new Services.intl.Collator().compare;
      let filter = !Services.prefs.getBoolPref("narrate.filter-voices");
      let options = win.speechSynthesis
        .getVoices()
        .filter(v => {
          return filter || !language || v.lang.split("-")[0] == language;
        })
        .map(v => {
          return {
            label: this._createVoiceLabel(v),
            value: v.voiceURI,
            selected: selectedVoice == v.voiceURI,
          };
        })
        .sort((a, b) => comparer(a.label, b.label));

      if (options.length) {
        options.unshift({
          label: gStrings.GetStringFromName("defaultvoice"),
          value: "automatic",
          selected: selectedVoice == "automatic",
        });
        this.voiceSelect.addOptions(options);
      }

      let narrateToggle = win.document.querySelector(".narrate-toggle");
      // We disable this entire feature if there are no available voices.
      narrateToggle.hidden = !options.length;
    });
  },

  _getVoicePref() {
    let voicePref = Services.prefs.getCharPref("narrate.voice");
    try {
      return JSON.parse(voicePref);
    } catch (e) {
      return { default: voicePref };
    }
  },

  _onRateInput(evt) {
    AsyncPrefs.set("narrate.rate", parseInt(evt.target.value, 10));
    this.narrator.setRate(this._convertRate(evt.target.value));
  },

  _onVoiceChange() {
    let voice = this.voice;
    this.narrator.setVoice(voice);
    this._languagePromise.then(language => {
      if (language) {
        let voicePref = this._getVoicePref();
        voicePref[language || "default"] = voice;
        AsyncPrefs.set("narrate.voice", JSON.stringify(voicePref));
      }
    });
  },

  _onButtonClick(evt) {
    let classList = evt.target.classList;
    if (classList.contains("narrate-skip-previous")) {
      this.narrator.skipPrevious();
    } else if (classList.contains("narrate-skip-next")) {
      this.narrator.skipNext();
    } else if (classList.contains("narrate-start-stop")) {
      if (this.narrator.speaking) {
        this.narrator.stop();
      } else {
        this._updateSpeechControls(true);
        let options = { rate: this.rate, voice: this.voice };
        this.narrator
          .start(options)
          .catch(err => {
            console.error("Narrate failed:", err);
          })
          .then(() => {
            this._updateSpeechControls(false);
          });
      }
    }
  },

  _updateSpeechControls(speaking) {
    let dropdown = this._doc.querySelector(".narrate-dropdown");
    if (!dropdown) {
      // Elements got destroyed, but window lingers on for a bit.
      return;
    }

    dropdown.classList.toggle("keep-open", speaking);
    dropdown.classList.toggle("speaking", speaking);

    let startStopButton = this._doc.querySelector(".narrate-start-stop");
    let skipPreviousButton = this._doc.querySelector(".narrate-skip-previous");
    let skipNextButton = this._doc.querySelector(".narrate-skip-next");

    skipPreviousButton.disabled = !speaking;
    skipNextButton.disabled = !speaking;

    let narrateShortcutId = gStrings.GetStringFromName("narrate-key-shortcut");
    let skipPreviousShortcut = "←";
    let skipNextShortcut = "→";

    startStopButton.title = gStrings.formatStringFromName(
      speaking ? "stop-label" : "start-label",
      [narrateShortcutId]
    );
    skipPreviousButton.title = gStrings.formatStringFromName("previous-label", [
      skipPreviousShortcut,
    ]);
    skipNextButton.title = gStrings.formatStringFromName("next-label", [
      skipNextShortcut,
    ]);
  },

  _createVoiceLabel(voice) {
    // This is a highly imperfect method of making human-readable labels
    // for system voices. Because each platform has a different naming scheme
    // for voices, we use a different method for each platform.
    switch (Services.appinfo.OS) {
      case "WINNT":
        // On windows the language is included in the name, so just use the name
        return voice.name;
      case "Linux":
        // On Linux, the name is usually the unlocalized language name.
        // Use a localized language name, and have the language tag in
        // parenthisis. This is to avoid six languages called "English".
        return gStrings.formatStringFromName("voiceLabel", [
          this._getLanguageName(voice.lang) || voice.name,
          voice.lang,
        ]);
      default:
        // On Mac the language is not included in the name, find a localized
        // language name or show the tag if none exists.
        // This is the ideal naming scheme so it is also the "default".
        return gStrings.formatStringFromName("voiceLabel", [
          voice.name,
          this._getLanguageName(voice.lang) || voice.lang,
        ]);
    }
  },

  _getLanguageName(lang) {
    try {
      // This may throw if the lang can't be parsed.
      let langCode = new Services.intl.Locale(lang).language;

      return Services.intl.getLanguageDisplayNames(undefined, [langCode]);
    } catch {
      return "";
    }
  },

  _convertRate(rate) {
    // We need to convert a relative percentage value to a fraction rate value.
    // eg. -100 is half the speed, 100 is twice the speed in percentage,
    // 0.5 is half the speed and 2 is twice the speed in fractions.
    return Math.pow(Math.abs(rate / 100) + 1, rate < 0 ? -1 : 1);
  },

  get _win() {
    return this._winRef.get();
  },

  get _doc() {
    return this._win.document;
  },

  get rate() {
    return this._convertRate(
      this._doc.querySelector(".narrate-rate-input").value
    );
  },

  get voice() {
    return this.voiceSelect.value;
  },

  _handleFocus(e) {
    let classList = e.target.classList;
    if (classList.contains("option") || classList.contains("select-toggle")) {
      e.preventDefault();
    } else {
      return;
    }

    let narrateDropdown = this._doc.querySelector(".narrate-dropdown");
    if (narrateDropdown.classList.contains("speaking")) {
      let skipPrevious = this._doc.querySelector(".narrate-skip-previous");
      skipPrevious.focus();
    } else {
      let startStop = this._doc.querySelector(".narrate-start-stop");
      startStop.focus();
    }
  },
};
PK
!<l���]1]1 modules/narrate/Narrator.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

// Maximum time into paragraph when pressing "skip previous" will go
// to previous paragraph and not the start of current one.
const PREV_THRESHOLD = 2000;
// All text-related style rules that we should copy over to the highlight node.
const kTextStylesRules = [
  "font-family",
  "font-kerning",
  "font-size",
  "font-size-adjust",
  "font-stretch",
  "font-variant",
  "font-weight",
  "line-height",
  "letter-spacing",
  "text-orientation",
  "text-transform",
  "word-spacing",
];

export function Narrator(win, languagePromise) {
  this._winRef = Cu.getWeakReference(win);
  this._languagePromise = languagePromise;
  this._inTest = Services.prefs.getBoolPref("narrate.test");
  this._speechOptions = {};
  this._startTime = 0;
  this._stopped = false;
}

Narrator.prototype = {
  get _doc() {
    return this._winRef.get().document;
  },

  get _win() {
    return this._winRef.get();
  },

  get _treeWalker() {
    if (!this._treeWalkerRef) {
      let wu = this._win.windowUtils;
      let nf = this._win.NodeFilter;

      let filter = {
        _matches: new Set(),

        // We want high-level elements that have non-empty text nodes.
        // For example, paragraphs. But nested anchors and other elements
        // are not interesting since their text already appears in their
        // parent's textContent.
        acceptNode(node) {
          if (this._matches.has(node.parentNode)) {
            // Reject sub-trees of accepted nodes.
            return nf.FILTER_REJECT;
          }

          if (!/\S/.test(node.textContent)) {
            // Reject nodes with no text.
            return nf.FILTER_REJECT;
          }

          let bb = wu.getBoundsWithoutFlushing(node);
          if (!bb.width || !bb.height) {
            // Skip non-rendered nodes. We don't reject because a zero-sized
            // container can still have visible, "overflowed", content.
            return nf.FILTER_SKIP;
          }

          for (let c = node.firstChild; c; c = c.nextSibling) {
            if (c.nodeType == c.TEXT_NODE && /\S/.test(c.textContent)) {
              // If node has a non-empty text child accept it.
              this._matches.add(node);
              return nf.FILTER_ACCEPT;
            }
          }

          return nf.FILTER_SKIP;
        },
      };

      this._treeWalkerRef = new WeakMap();

      // We can't hold a weak reference on the treewalker, because there
      // are no other strong references, and it will be GC'ed. Instead,
      // we rely on the window's lifetime and use it as a weak reference.
      this._treeWalkerRef.set(
        this._win,
        this._doc.createTreeWalker(
          this._doc.querySelector(".container"),
          nf.SHOW_ELEMENT,
          filter,
          false
        )
      );
    }

    return this._treeWalkerRef.get(this._win);
  },

  get _timeIntoParagraph() {
    let rv = Date.now() - this._startTime;
    return rv;
  },

  get speaking() {
    return (
      this._win.speechSynthesis.speaking || this._win.speechSynthesis.pending
    );
  },

  _getVoice(voiceURI) {
    if (!this._voiceMap || !this._voiceMap.has(voiceURI)) {
      this._voiceMap = new Map(
        this._win.speechSynthesis.getVoices().map(v => [v.voiceURI, v])
      );
    }

    return this._voiceMap.get(voiceURI);
  },

  _isParagraphInView(paragraph) {
    if (!paragraph) {
      return false;
    }

    let bb = paragraph.getBoundingClientRect();
    return bb.top >= 0 && bb.top < this._win.innerHeight;
  },

  _sendTestEvent(eventType, detail) {
    let win = this._win;
    win.dispatchEvent(
      new win.CustomEvent(eventType, {
        detail: Cu.cloneInto(detail, win.document),
      })
    );
  },

  _speakInner() {
    this._win.speechSynthesis.cancel();
    let tw = this._treeWalker;
    let paragraph = tw.currentNode;
    if (paragraph == tw.root) {
      this._sendTestEvent("paragraphsdone", {});
      return Promise.resolve();
    }

    let utterance = new this._win.SpeechSynthesisUtterance(
      paragraph.textContent.replace(/\r?\n/g, " ")
    );
    utterance.rate = this._speechOptions.rate;
    if (this._speechOptions.voice) {
      utterance.voice = this._speechOptions.voice;
    } else {
      utterance.lang = this._speechOptions.lang;
    }

    this._startTime = Date.now();

    let highlighter = new Highlighter(paragraph);

    if (this._inTest) {
      let onTestSynthEvent = e => {
        if (e.detail.type == "boundary") {
          let args = Object.assign({ utterance }, e.detail.args);
          let evt = new this._win.SpeechSynthesisEvent(e.detail.type, args);
          utterance.dispatchEvent(evt);
        }
      };

      let removeListeners = () => {
        this._win.removeEventListener("testsynthevent", onTestSynthEvent);
      };

      this._win.addEventListener("testsynthevent", onTestSynthEvent);
      utterance.addEventListener("end", removeListeners);
      utterance.addEventListener("error", removeListeners);
    }

    return new Promise((resolve, reject) => {
      utterance.addEventListener("start", () => {
        paragraph.classList.add("narrating");
        let bb = paragraph.getBoundingClientRect();
        if (bb.top < 0 || bb.bottom > this._win.innerHeight) {
          paragraph.scrollIntoView({ behavior: "smooth", block: "start" });
        }

        if (this._inTest) {
          this._sendTestEvent("paragraphstart", {
            voice: utterance.chosenVoiceURI,
            rate: utterance.rate,
            paragraph: paragraph.textContent,
            tag: paragraph.localName,
          });
        }
      });

      utterance.addEventListener("end", () => {
        if (!this._win) {
          // page got unloaded, don't do anything.
          return;
        }

        highlighter.remove();
        paragraph.classList.remove("narrating");
        this._startTime = 0;
        if (this._inTest) {
          this._sendTestEvent("paragraphend", {});
        }

        if (this._stopped) {
          // User pressed stopped.
          resolve();
        } else {
          tw.currentNode = tw.nextNode() || tw.root;
          this._speakInner().then(resolve, reject);
        }
      });

      utterance.addEventListener("error", () => {
        reject("speech synthesis failed");
      });

      utterance.addEventListener("boundary", e => {
        if (e.name != "word") {
          // We are only interested in word boundaries for now.
          return;
        }

        if (e.charLength) {
          highlighter.highlight(e.charIndex, e.charLength);
          if (this._inTest) {
            this._sendTestEvent("wordhighlight", {
              start: e.charIndex,
              end: e.charIndex + e.charLength,
            });
          }
        }
      });

      this._win.speechSynthesis.speak(utterance);
    });
  },

  start(speechOptions) {
    this._speechOptions = {
      rate: speechOptions.rate,
      voice: this._getVoice(speechOptions.voice),
    };

    this._stopped = false;
    return this._languagePromise.then(language => {
      if (!this._speechOptions.voice) {
        this._speechOptions.lang = language;
      }

      let tw = this._treeWalker;
      if (!this._isParagraphInView(tw.currentNode)) {
        tw.currentNode = tw.root;
        while (tw.nextNode()) {
          if (this._isParagraphInView(tw.currentNode)) {
            break;
          }
        }
      }
      if (tw.currentNode == tw.root) {
        tw.nextNode();
      }

      return this._speakInner();
    });
  },

  stop() {
    this._stopped = true;
    this._win.speechSynthesis.cancel();
  },

  skipNext() {
    this._win.speechSynthesis.cancel();
  },

  skipPrevious() {
    this._goBackParagraphs(this._timeIntoParagraph < PREV_THRESHOLD ? 2 : 1);
  },

  setRate(rate) {
    this._speechOptions.rate = rate;
    /* repeat current paragraph */
    this._goBackParagraphs(1);
  },

  setVoice(voice) {
    this._speechOptions.voice = this._getVoice(voice);
    /* repeat current paragraph */
    this._goBackParagraphs(1);
  },

  _goBackParagraphs(count) {
    let tw = this._treeWalker;
    for (let i = 0; i < count; i++) {
      if (!tw.previousNode()) {
        tw.currentNode = tw.root;
      }
    }
    this._win.speechSynthesis.cancel();
  },
};

/**
 * The Highlighter class is used to highlight a range of text in a container.
 *
 * @param {Element} container a text container
 */
function Highlighter(container) {
  this.container = container;
}

Highlighter.prototype = {
  /**
   * Highlight the range within offsets relative to the container.
   *
   * @param {number} startOffset the start offset
   * @param {number} length the length in characters of the range
   */
  highlight(startOffset, length) {
    let containerRect = this.container.getBoundingClientRect();
    let range = this._getRange(startOffset, startOffset + length);
    let rangeRects = range.getClientRects();
    let win = this.container.ownerGlobal;
    let computedStyle = win.getComputedStyle(range.endContainer.parentNode);
    let nodes = this._getFreshHighlightNodes(rangeRects.length);

    let textStyle = {};
    for (let textStyleRule of kTextStylesRules) {
      textStyle[textStyleRule] = computedStyle[textStyleRule];
    }

    for (let i = 0; i < rangeRects.length; i++) {
      let r = rangeRects[i];
      let node = nodes[i];

      let style = Object.assign(
        {
          top: `${r.top - containerRect.top + r.height / 2}px`,
          left: `${r.left - containerRect.left + r.width / 2}px`,
          width: `${r.width}px`,
          height: `${r.height}px`,
        },
        textStyle
      );

      // Enables us to vary the CSS transition on a line change.
      node.classList.toggle("newline", style.top != node.dataset.top);
      node.dataset.top = style.top;

      // Enables CSS animations.
      node.classList.remove("animate");
      win.requestAnimationFrame(() => {
        node.classList.add("animate");
      });

      // Enables alternative word display with a CSS pseudo-element.
      node.dataset.word = range.toString();

      // Apply style
      node.style = Object.entries(style)
        .map(s => `${s[0]}: ${s[1]};`)
        .join(" ");
    }
  },

  /**
   * Releases reference to container and removes all highlight nodes.
   */
  remove() {
    for (let node of this._nodes) {
      node.remove();
    }

    this.container = null;
  },

  /**
   * Returns specified amount of highlight nodes. Creates new ones if necessary
   * and purges any additional nodes that are not needed.
   *
   * @param {number} count number of nodes needed
   */
  _getFreshHighlightNodes(count) {
    let doc = this.container.ownerDocument;
    let nodes = Array.from(this._nodes);

    // Remove nodes we don't need anymore (nodes.length - count > 0).
    for (let toRemove = 0; toRemove < nodes.length - count; toRemove++) {
      nodes.shift().remove();
    }

    // Add additional nodes if we need them (count - nodes.length > 0).
    for (let toAdd = 0; toAdd < count - nodes.length; toAdd++) {
      let node = doc.createElement("div");
      node.className = "narrate-word-highlight";
      this.container.appendChild(node);
      nodes.push(node);
    }

    return nodes;
  },

  /**
   * Create and return a range object with the start and end offsets relative
   * to the container node.
   *
   * @param {number} startOffset the start offset
   * @param {number} endOffset the end offset
   */
  _getRange(startOffset, endOffset) {
    let doc = this.container.ownerDocument;
    let i = 0;
    let treeWalker = doc.createTreeWalker(
      this.container,
      doc.defaultView.NodeFilter.SHOW_TEXT
    );
    let node = treeWalker.nextNode();

    function _findNodeAndOffset(offset) {
      do {
        let length = node.data.length;
        if (offset >= i && offset <= i + length) {
          return [node, offset - i];
        }
        i += length;
      } while ((node = treeWalker.nextNode()));

      // Offset is out of bounds, return last offset of last node.
      node = treeWalker.lastChild();
      return [node, node.data.length];
    }

    let range = doc.createRange();
    range.setStart(..._findNodeAndOffset(startOffset));
    range.setEnd(..._findNodeAndOffset(endOffset));

    return range;
  },

  /*
   * Get all existing highlight nodes for container.
   */
  get _nodes() {
    return this.container.querySelectorAll(".narrate-word-highlight");
  },
};
PK
!<�*L��#modules/narrate/VoiceSelect.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export function VoiceSelect(win, label) {
  this._winRef = Cu.getWeakReference(win);

  let element = win.document.createElement("div");
  element.classList.add("voiceselect");
  // TODO: remove unused .label span with Bug 1903156.
  // eslint-disable-next-line no-unsanitized/property
  element.innerHTML = `<button class="select-toggle" aria-controls="voice-options" aria-expanded="false" role="combobox">
      <span class="label">${label}</span> <span class="current-voice"></span>
    </button>
    <div class="options" id="voice-options" role="listbox"></div>`;

  this._elementRef = Cu.getWeakReference(element);

  let button = this.selectToggle;
  button.addEventListener("click", this);
  button.addEventListener("keydown", this);

  let listbox = this.listbox;
  listbox.addEventListener("click", this);
  listbox.addEventListener("mousemove", this);
  listbox.addEventListener("keydown", this);
  listbox.addEventListener("wheel", this, true);

  win.addEventListener("resize", () => {
    this._updateDropdownHeight();
  });
}

VoiceSelect.prototype = {
  add(label, value) {
    let option = this._doc.createElement("button");
    option.dataset.value = value;
    option.classList.add("option");
    option.tabIndex = "-1";
    option.setAttribute("role", "option");
    option.textContent = label;
    this.listbox.appendChild(option);
    return option;
  },

  addOptions(options) {
    let selected = null;
    for (let option of options) {
      if (option.selected) {
        selected = this.add(option.label, option.value);
      } else {
        this.add(option.label, option.value);
      }
    }

    this._select(selected || this.options[0], true);
  },

  clear() {
    this.listbox.innerHTML = "";
  },

  toggleList(force, focus = true) {
    if (this.element.classList.toggle("open", force)) {
      if (focus) {
        (this.selected || this.options[0]).focus();
      }

      this._updateDropdownHeight(true);
      this.selectToggle.setAttribute("aria-expanded", true);
      this._win.addEventListener("focus", this, true);
    } else {
      if (focus) {
        this.element.querySelector(".select-toggle").focus();
      }

      this.selectToggle.setAttribute("aria-expanded", false);
      this._win.removeEventListener("focus", this, true);
    }
  },

  handleEvent(evt) {
    let target = evt.target;

    switch (evt.type) {
      case "click":
        target = target.closest(".option, .select-toggle") || target;
        if (target.classList.contains("option")) {
          if (!target.classList.contains("selected")) {
            this.selected = target;
          }

          this.toggleList(false);
        } else if (target.classList.contains("select-toggle")) {
          this.toggleList();
        }
        break;

      case "mousemove":
        this.listbox.classList.add("hovering");
        break;

      case "keydown":
        if (target.classList.contains("select-toggle")) {
          if (evt.altKey) {
            this.toggleList(true);
          } else {
            this._keyDownedButton(evt);
          }
        } else {
          this.listbox.classList.remove("hovering");
          this._keyDownedInBox(evt);
        }
        break;

      case "wheel":
        // Don't let wheel events bubble to document. It will scroll the page
        // and close the entire narrate dialog.
        evt.stopPropagation();
        break;

      case "focus":
        if (!target.closest(".voiceselect")) {
          this.toggleList(false, false);
        }
        break;
    }
  },

  _getPagedOption(option, up) {
    let height = elem => elem.getBoundingClientRect().height;
    let listboxHeight = height(this.listbox);

    let next = option;
    for (let delta = 0; delta < listboxHeight; delta += height(next)) {
      let sibling = up ? next.previousElementSibling : next.nextElementSibling;
      if (!sibling) {
        break;
      }

      next = sibling;
    }

    return next;
  },

  _keyDownedButton(evt) {
    if (evt.altKey && (evt.key === "ArrowUp" || evt.key === "ArrowUp")) {
      this.toggleList(true);
      return;
    }

    let toSelect;
    switch (evt.key) {
      case "PageUp":
      case "ArrowUp":
        toSelect = this.selected.previousElementSibling;
        break;
      case "PageDown":
      case "ArrowDown":
        toSelect = this.selected.nextElementSibling;
        break;
      case "Home":
        toSelect = this.selected.parentNode.firstElementChild;
        break;
      case "End":
        toSelect = this.selected.parentNode.lastElementChild;
        break;
    }

    if (toSelect && toSelect.classList.contains("option")) {
      evt.preventDefault();
      this.selected = toSelect;
    }
  },

  _keyDownedInBox(evt) {
    let toFocus;
    let cur = this._doc.activeElement;

    switch (evt.key) {
      case "ArrowUp":
        toFocus = cur.previousElementSibling || this.listbox.lastElementChild;
        break;
      case "ArrowDown":
        toFocus = cur.nextElementSibling || this.listbox.firstElementChild;
        break;
      case "PageUp":
        toFocus = this._getPagedOption(cur, true);
        break;
      case "PageDown":
        toFocus = this._getPagedOption(cur, false);
        break;
      case "Home":
        toFocus = cur.parentNode.firstElementChild;
        break;
      case "End":
        toFocus = cur.parentNode.lastElementChild;
        break;
      case "Escape":
        this.toggleList(false);
        break;
    }

    if (toFocus && toFocus.classList.contains("option")) {
      evt.preventDefault();
      toFocus.focus();
    }
  },

  _select(option, suppressEvent = false) {
    let oldSelected = this.selected;
    if (oldSelected) {
      oldSelected.removeAttribute("aria-selected");
      oldSelected.classList.remove("selected");
    }

    if (option) {
      option.setAttribute("aria-selected", true);
      option.classList.add("selected");
      this.element.querySelector(".current-voice").textContent =
        option.textContent;
    }

    if (!suppressEvent) {
      let evt = this.element.ownerDocument.createEvent("Event");
      evt.initEvent("change", true, true);
      this.element.dispatchEvent(evt);
    }
  },

  _updateDropdownHeight(now) {
    let updateInner = () => {
      let winHeight = this._win.innerHeight;
      let listbox = this.listbox;
      let listboxTop = listbox.getBoundingClientRect().top;
      listbox.style.maxHeight = winHeight - listboxTop - 32 + "px";
    };

    if (now) {
      updateInner();
    } else if (!this._pendingDropdownUpdate) {
      this._pendingDropdownUpdate = true;
      this._win.requestAnimationFrame(() => {
        updateInner();
        delete this._pendingDropdownUpdate;
      });
    }
  },

  _getOptionFromValue(value) {
    return Array.from(this.options).find(o => o.dataset.value === value);
  },

  get element() {
    return this._elementRef.get();
  },

  get listbox() {
    return this._elementRef.get().querySelector(".options");
  },

  get selectToggle() {
    return this._elementRef.get().querySelector(".select-toggle");
  },

  get _win() {
    return this._winRef.get();
  },

  get _doc() {
    return this._win.document;
  },

  set selected(option) {
    this._select(option);
  },

  get selected() {
    return this.element.querySelector(".options > .option.selected");
  },

  get options() {
    return this.element.querySelectorAll(".options > .option");
  },

  set value(value) {
    this._select(this._getOptionFromValue(value));
  },

  get value() {
    let selected = this.selected;
    return selected ? selected.dataset.value : "";
  },
};
PK
!<��MMmodules/nsAsyncShutdown.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * An implementation of nsIAsyncShutdown* based on AsyncShutdown.sys.mjs
 */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
});

/**
 * Conversion between nsIPropertyBags and JS values.
 * This uses a conservative approach to avoid losing data and doesn't throw.
 * Don't use this if you need perfect serialization and deserialization.
 */
class PropertyBagConverter {
  /**
   * When the js value to convert is a primitive, it is stored in the property
   * bag under a key with this name.
   */
  get primitiveProperty() {
    return "PropertyBagConverter_primitive";
  }

  /**
   * Converts from a PropertyBag to a JS value.
   * @param {nsIPropertyBag} bag The PropertyBag to convert.
   * @returns {jsval} A JS value.
   */
  propertyBagToJsValue(bag) {
    if (!(bag instanceof Ci.nsIPropertyBag)) {
      return null;
    }
    let result = {};
    for (let { name, value: property } of bag.enumerator) {
      let value = this.#toValue(property);
      if (name == this.primitiveProperty) {
        return value;
      }
      result[name] = value;
    }
    return result;
  }

  #toValue(property) {
    if (property instanceof Ci.nsIPropertyBag) {
      return this.propertyBagToJsValue(property);
    }
    if (["number", "boolean"].includes(typeof property)) {
      return property;
    }
    try {
      return JSON.parse(property);
    } catch (ex) {
      // Not JSON.
    }
    return property;
  }

  /**
   * Converts from a JS value to a PropertyBag.
   * @param {jsval} val JS value to convert.
   * @returns {nsIPropertyBag} A PropertyBag.
   * @note function is converted to "(function)" and undefined to null.
   */
  jsValueToPropertyBag(val) {
    let bag = Cc["@mozilla.org/hash-property-bag;1"].createInstance(
      Ci.nsIWritablePropertyBag
    );
    if (val && typeof val == "object") {
      for (let k of Object.keys(val)) {
        bag.setProperty(k, this.#fromValue(val[k]));
      }
    } else {
      bag.setProperty(this.primitiveProperty, this.#fromValue(val));
    }
    return bag;
  }

  #fromValue(value) {
    if (typeof value == "function") {
      return "(function)";
    }
    if (value === undefined) {
      value = null;
    }
    if (["number", "boolean", "string"].includes(typeof value)) {
      return value;
    }
    return JSON.stringify(value);
  }
}

/**
 * Construct an instance of nsIAsyncShutdownClient from a
 * AsyncShutdown.Barrier client.
 *
 * @param {object} moduleClient A client, as returned from the `client`
 * property of an instance of `AsyncShutdown.Barrier`. This client will
 * serve as back-end for methods `addBlocker` and `removeBlocker`.
 * @constructor
 */
function nsAsyncShutdownClient(moduleClient) {
  if (!moduleClient) {
    throw new TypeError("nsAsyncShutdownClient expects one argument");
  }
  this._moduleClient = moduleClient;
  this._byXpcomBlocker = new Map();
}
nsAsyncShutdownClient.prototype = {
  get jsclient() {
    return this._moduleClient;
  },
  get name() {
    return this._moduleClient.name;
  },
  get isClosed() {
    return this._moduleClient.isClosed;
  },
  addBlocker(
    /* nsIAsyncShutdownBlocker*/ xpcomBlocker,
    fileName,
    lineNumber,
    stack
  ) {
    // We need a Promise-based function with the same behavior as
    // `xpcomBlocker`. Furthermore, to support `removeBlocker`, we
    // need to ensure that we always get the same Promise-based
    // function if we call several `addBlocker`/`removeBlocker` several
    // times with the same `xpcomBlocker`.

    if (this._byXpcomBlocker.has(xpcomBlocker)) {
      throw new Error(
        `We have already registered the blocker (${xpcomBlocker.name})`
      );
    }

    // Ideally, this should be done with a WeakMap() with xpcomBlocker
    // as a key, but XPCWrappedNative objects cannot serve as WeakMap keys, see
    // bug 1834365.
    const moduleBlocker = () =>
      new Promise(
        // This promise is never resolved. By opposition to AsyncShutdown
        // blockers, `nsIAsyncShutdownBlocker`s are always lifted by calling
        // `removeBlocker`.
        () => xpcomBlocker.blockShutdown(this)
      );

    this._byXpcomBlocker.set(xpcomBlocker, moduleBlocker);
    this._moduleClient.addBlocker(xpcomBlocker.name, moduleBlocker, {
      fetchState: () =>
        new PropertyBagConverter().propertyBagToJsValue(xpcomBlocker.state),
      filename: fileName,
      lineNumber,
      stack,
    });
  },

  removeBlocker(xpcomBlocker) {
    let moduleBlocker = this._byXpcomBlocker.get(xpcomBlocker);
    if (!moduleBlocker) {
      return false;
    }
    this._byXpcomBlocker.delete(xpcomBlocker);
    return this._moduleClient.removeBlocker(moduleBlocker);
  },

  QueryInterface: ChromeUtils.generateQI(["nsIAsyncShutdownClient"]),
};

/**
 * Construct an instance of nsIAsyncShutdownBarrier from an instance
 * of AsyncShutdown.Barrier.
 *
 * @param {object} moduleBarrier an instance if
 * `AsyncShutdown.Barrier`. This instance will serve as back-end for
 * all methods.
 * @constructor
 */
function nsAsyncShutdownBarrier(moduleBarrier) {
  this._client = new nsAsyncShutdownClient(moduleBarrier.client);
  this._moduleBarrier = moduleBarrier;
}
nsAsyncShutdownBarrier.prototype = {
  get state() {
    return new PropertyBagConverter().jsValueToPropertyBag(
      this._moduleBarrier.state
    );
  },
  get client() {
    return this._client;
  },
  wait(onReady) {
    this._moduleBarrier.wait().then(() => {
      onReady.done();
    });
    // By specification, _moduleBarrier.wait() cannot reject.
  },

  QueryInterface: ChromeUtils.generateQI(["nsIAsyncShutdownBarrier"]),
};

export function nsAsyncShutdownService() {
  // Cache for the getters

  for (let _k of [
    // Parent process
    "profileBeforeChange",
    "profileChangeTeardown",
    "quitApplicationGranted",
    "sendTelemetry",

    // Child processes
    "contentChildShutdown",

    // All processes
    "webWorkersShutdown",
    "xpcomWillShutdown",
  ]) {
    let k = _k;
    Object.defineProperty(this, k, {
      configurable: true,
      get() {
        delete this[k];
        let wrapped = lazy.AsyncShutdown[k]; // May be undefined, if we're on the wrong process.
        let result = wrapped ? new nsAsyncShutdownClient(wrapped) : undefined;
        Object.defineProperty(this, k, {
          value: result,
        });
        return result;
      },
    });
  }

  // Hooks for testing purpose
  this.wrappedJSObject = {
    _propertyBagConverter: PropertyBagConverter,
  };
}

nsAsyncShutdownService.prototype = {
  makeBarrier(name) {
    return new nsAsyncShutdownBarrier(new lazy.AsyncShutdown.Barrier(name));
  },

  QueryInterface: ChromeUtils.generateQI(["nsIAsyncShutdownService"]),
};
PK
!<�j����modules/nsCrashMonitor.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

var MonitorAPI = ChromeUtils.importESModule(
  "resource://gre/modules/CrashMonitor.sys.mjs"
).CrashMonitor;

export function CrashMonitor() {}

CrashMonitor.prototype = {
  classID: Components.ID("{d9d75e86-8f17-4c57-993e-f738f0d86d42}"),
  contractID: "@mozilla.org/toolkit/crashmonitor;1",

  QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),

  observe(aSubject, aTopic) {
    switch (aTopic) {
      case "profile-after-change":
        MonitorAPI.init();
    }
  },
};
PK
!<������modules/pdfjs.sys.mjs/* Copyright 2018 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PdfStreamConverter: "resource://pdf.js/PdfStreamConverter.sys.mjs",
});

// Register/unregister a constructor as a factory.
export function StreamConverterFactory() {
  if (!Services.prefs.getBoolPref("pdfjs.disabled", false)) {
    return new lazy.PdfStreamConverter();
  }
  throw Components.Exception("", Cr.NS_ERROR_FACTORY_NOT_REGISTERED);
}
PK
!<N��;

+modules/psm/ClientAuthDialogService.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// ClientAuthDialogService implements nsIClientAuthDialogService, and aims to
// open a dialog asking the user to select a client authentication certificate.
// Ideally the dialog will be tab-modal to the tab corresponding to the load
// that resulted in the request for the client authentication certificate.
export function ClientAuthDialogService() {}

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PromptUtils: "resource://gre/modules/PromptUtils.sys.mjs",
});

// Given a loadContext (CanonicalBrowsingContext), attempts to return a
// TabDialogBox for the browser corresponding to loadContext.
function getTabDialogBoxForLoadContext(loadContext) {
  let tabBrowser = loadContext?.topFrameElement?.getTabBrowser();
  if (!tabBrowser) {
    return null;
  }
  for (let browser of tabBrowser.browsers) {
    if (browser.browserId == loadContext.top?.browserId) {
      return tabBrowser.getTabDialogBox(browser);
    }
  }
  return null;
}

ClientAuthDialogService.prototype = {
  classID: Components.ID("{d7d2490d-2640-411b-9f09-a538803c11ee}"),
  QueryInterface: ChromeUtils.generateQI(["nsIClientAuthDialogService"]),

  chooseCertificate: function ClientAuthDialogService_chooseCertificate(
    hostname,
    certArray,
    loadContext,
    callback
  ) {
    const clientAuthAskURI = "chrome://pippki/content/clientauthask.xhtml";
    let retVals = { cert: null, rememberDecision: false };
    let args = lazy.PromptUtils.objectToPropBag({
      hostname,
      certArray,
      retVals,
    });

    // First attempt to find a TabDialogBox for the loadContext. This allows
    // for a tab-modal dialog specific to the tab causing the load, which is a
    // better user experience.
    let tabDialogBox = getTabDialogBoxForLoadContext(loadContext);
    if (tabDialogBox) {
      tabDialogBox.open(clientAuthAskURI, {}, args).closedPromise.then(() => {
        callback.certificateChosen(retVals.cert, retVals.rememberDecision);
      });
      return;
    }
    // Otherwise, attempt to open a window-modal dialog on the window that at
    // least has the tab the load is occurring in.
    let browserWindow = loadContext?.topFrameElement?.ownerGlobal;
    // Failing that, open a window-modal dialog on the most recent window.
    if (!browserWindow?.gDialogBox) {
      browserWindow = Services.wm.getMostRecentBrowserWindow();
    }

    if (browserWindow?.gDialogBox) {
      browserWindow.gDialogBox.open(clientAuthAskURI, args).then(() => {
        callback.certificateChosen(retVals.cert, retVals.rememberDecision);
      });
      return;
    }

    let mostRecentWindow = Services.wm.getMostRecentWindow("");
    Services.ww.openWindow(
      mostRecentWindow,
      clientAuthAskURI,
      "_blank",
      "centerscreen,chrome,modal,titlebar",
      args
    );
    callback.certificateChosen(retVals.cert, retVals.rememberDecision);
  },
};
PK
!<_W.	�	�modules/reader/JSDOMParser.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This is a relatively lightweight DOMParser that is safe to use in a web
 * worker. This is far from a complete DOM implementation; however, it should
 * contain the minimal set of functionality necessary for Readability.js.
 *
 * Aside from not implementing the full DOM API, there are other quirks to be
 * aware of when using the JSDOMParser:
 *
 *   1) Properly formed HTML/XML must be used. This means you should be extra
 *      careful when using this parser on anything received directly from an
 *      XMLHttpRequest. Providing a serialized string from an XMLSerializer,
 *      however, should be safe (since the browser's XMLSerializer should
 *      generate valid HTML/XML). Therefore, if parsing a document from an XHR,
 *      the recommended approach is to do the XHR in the main thread, use
 *      XMLSerializer.serializeToString() on the responseXML, and pass the
 *      resulting string to the worker.
 *
 *   2) Live NodeLists are not supported. DOM methods and properties such as
 *      getElementsByTagName() and childNodes return standard arrays. If you
 *      want these lists to be updated when nodes are removed or added to the
 *      document, you must take care to manually update them yourself.
 */
(function (global) {

  // XML only defines these and the numeric ones:

  var entityTable = {
    "lt": "<",
    "gt": ">",
    "amp": "&",
    "quot": '"',
    "apos": "'",
  };

  var reverseEntityTable = {
    "<": "&lt;",
    ">": "&gt;",
    "&": "&amp;",
    '"': "&quot;",
    "'": "&apos;",
  };

  function encodeTextContentHTML(s) {
    return s.replace(/[&<>]/g, function(x) {
      return reverseEntityTable[x];
    });
  }

  function encodeHTML(s) {
    return s.replace(/[&<>'"]/g, function(x) {
      return reverseEntityTable[x];
    });
  }

  function decodeHTML(str) {
    return str.replace(/&(quot|amp|apos|lt|gt);/g, function(match, tag) {
      return entityTable[tag];
    }).replace(/&#(?:x([0-9a-z]{1,4})|([0-9]{1,4}));/gi, function(match, hex, numStr) {
      var num = parseInt(hex || numStr, hex ? 16 : 10); // read num
      return String.fromCharCode(num);
    });
  }

  // When a style is set in JS, map it to the corresponding CSS attribute
  var styleMap = {
    "alignmentBaseline": "alignment-baseline",
    "background": "background",
    "backgroundAttachment": "background-attachment",
    "backgroundClip": "background-clip",
    "backgroundColor": "background-color",
    "backgroundImage": "background-image",
    "backgroundOrigin": "background-origin",
    "backgroundPosition": "background-position",
    "backgroundPositionX": "background-position-x",
    "backgroundPositionY": "background-position-y",
    "backgroundRepeat": "background-repeat",
    "backgroundRepeatX": "background-repeat-x",
    "backgroundRepeatY": "background-repeat-y",
    "backgroundSize": "background-size",
    "baselineShift": "baseline-shift",
    "border": "border",
    "borderBottom": "border-bottom",
    "borderBottomColor": "border-bottom-color",
    "borderBottomLeftRadius": "border-bottom-left-radius",
    "borderBottomRightRadius": "border-bottom-right-radius",
    "borderBottomStyle": "border-bottom-style",
    "borderBottomWidth": "border-bottom-width",
    "borderCollapse": "border-collapse",
    "borderColor": "border-color",
    "borderImage": "border-image",
    "borderImageOutset": "border-image-outset",
    "borderImageRepeat": "border-image-repeat",
    "borderImageSlice": "border-image-slice",
    "borderImageSource": "border-image-source",
    "borderImageWidth": "border-image-width",
    "borderLeft": "border-left",
    "borderLeftColor": "border-left-color",
    "borderLeftStyle": "border-left-style",
    "borderLeftWidth": "border-left-width",
    "borderRadius": "border-radius",
    "borderRight": "border-right",
    "borderRightColor": "border-right-color",
    "borderRightStyle": "border-right-style",
    "borderRightWidth": "border-right-width",
    "borderSpacing": "border-spacing",
    "borderStyle": "border-style",
    "borderTop": "border-top",
    "borderTopColor": "border-top-color",
    "borderTopLeftRadius": "border-top-left-radius",
    "borderTopRightRadius": "border-top-right-radius",
    "borderTopStyle": "border-top-style",
    "borderTopWidth": "border-top-width",
    "borderWidth": "border-width",
    "bottom": "bottom",
    "boxShadow": "box-shadow",
    "boxSizing": "box-sizing",
    "captionSide": "caption-side",
    "clear": "clear",
    "clip": "clip",
    "clipPath": "clip-path",
    "clipRule": "clip-rule",
    "color": "color",
    "colorInterpolation": "color-interpolation",
    "colorInterpolationFilters": "color-interpolation-filters",
    "colorProfile": "color-profile",
    "colorRendering": "color-rendering",
    "content": "content",
    "counterIncrement": "counter-increment",
    "counterReset": "counter-reset",
    "cursor": "cursor",
    "direction": "direction",
    "display": "display",
    "dominantBaseline": "dominant-baseline",
    "emptyCells": "empty-cells",
    "enableBackground": "enable-background",
    "fill": "fill",
    "fillOpacity": "fill-opacity",
    "fillRule": "fill-rule",
    "filter": "filter",
    "cssFloat": "float",
    "floodColor": "flood-color",
    "floodOpacity": "flood-opacity",
    "font": "font",
    "fontFamily": "font-family",
    "fontSize": "font-size",
    "fontStretch": "font-stretch",
    "fontStyle": "font-style",
    "fontVariant": "font-variant",
    "fontWeight": "font-weight",
    "glyphOrientationHorizontal": "glyph-orientation-horizontal",
    "glyphOrientationVertical": "glyph-orientation-vertical",
    "height": "height",
    "imageRendering": "image-rendering",
    "kerning": "kerning",
    "left": "left",
    "letterSpacing": "letter-spacing",
    "lightingColor": "lighting-color",
    "lineHeight": "line-height",
    "listStyle": "list-style",
    "listStyleImage": "list-style-image",
    "listStylePosition": "list-style-position",
    "listStyleType": "list-style-type",
    "margin": "margin",
    "marginBottom": "margin-bottom",
    "marginLeft": "margin-left",
    "marginRight": "margin-right",
    "marginTop": "margin-top",
    "marker": "marker",
    "markerEnd": "marker-end",
    "markerMid": "marker-mid",
    "markerStart": "marker-start",
    "mask": "mask",
    "maxHeight": "max-height",
    "maxWidth": "max-width",
    "minHeight": "min-height",
    "minWidth": "min-width",
    "opacity": "opacity",
    "orphans": "orphans",
    "outline": "outline",
    "outlineColor": "outline-color",
    "outlineOffset": "outline-offset",
    "outlineStyle": "outline-style",
    "outlineWidth": "outline-width",
    "overflow": "overflow",
    "overflowX": "overflow-x",
    "overflowY": "overflow-y",
    "padding": "padding",
    "paddingBottom": "padding-bottom",
    "paddingLeft": "padding-left",
    "paddingRight": "padding-right",
    "paddingTop": "padding-top",
    "page": "page",
    "pageBreakAfter": "page-break-after",
    "pageBreakBefore": "page-break-before",
    "pageBreakInside": "page-break-inside",
    "pointerEvents": "pointer-events",
    "position": "position",
    "quotes": "quotes",
    "resize": "resize",
    "right": "right",
    "shapeRendering": "shape-rendering",
    "size": "size",
    "speak": "speak",
    "src": "src",
    "stopColor": "stop-color",
    "stopOpacity": "stop-opacity",
    "stroke": "stroke",
    "strokeDasharray": "stroke-dasharray",
    "strokeDashoffset": "stroke-dashoffset",
    "strokeLinecap": "stroke-linecap",
    "strokeLinejoin": "stroke-linejoin",
    "strokeMiterlimit": "stroke-miterlimit",
    "strokeOpacity": "stroke-opacity",
    "strokeWidth": "stroke-width",
    "tableLayout": "table-layout",
    "textAlign": "text-align",
    "textAnchor": "text-anchor",
    "textDecoration": "text-decoration",
    "textIndent": "text-indent",
    "textLineThrough": "text-line-through",
    "textLineThroughColor": "text-line-through-color",
    "textLineThroughMode": "text-line-through-mode",
    "textLineThroughStyle": "text-line-through-style",
    "textLineThroughWidth": "text-line-through-width",
    "textOverflow": "text-overflow",
    "textOverline": "text-overline",
    "textOverlineColor": "text-overline-color",
    "textOverlineMode": "text-overline-mode",
    "textOverlineStyle": "text-overline-style",
    "textOverlineWidth": "text-overline-width",
    "textRendering": "text-rendering",
    "textShadow": "text-shadow",
    "textTransform": "text-transform",
    "textUnderline": "text-underline",
    "textUnderlineColor": "text-underline-color",
    "textUnderlineMode": "text-underline-mode",
    "textUnderlineStyle": "text-underline-style",
    "textUnderlineWidth": "text-underline-width",
    "top": "top",
    "unicodeBidi": "unicode-bidi",
    "unicodeRange": "unicode-range",
    "vectorEffect": "vector-effect",
    "verticalAlign": "vertical-align",
    "visibility": "visibility",
    "whiteSpace": "white-space",
    "widows": "widows",
    "width": "width",
    "wordBreak": "word-break",
    "wordSpacing": "word-spacing",
    "wordWrap": "word-wrap",
    "writingMode": "writing-mode",
    "zIndex": "z-index",
    "zoom": "zoom",
  };

  // Elements that can be self-closing
  var voidElems = {
    "area": true,
    "base": true,
    "br": true,
    "col": true,
    "command": true,
    "embed": true,
    "hr": true,
    "img": true,
    "input": true,
    "link": true,
    "meta": true,
    "param": true,
    "source": true,
    "wbr": true
  };

  var whitespace = [" ", "\t", "\n", "\r"];

  // See https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
  var nodeTypes = {
    ELEMENT_NODE: 1,
    ATTRIBUTE_NODE: 2,
    TEXT_NODE: 3,
    CDATA_SECTION_NODE: 4,
    ENTITY_REFERENCE_NODE: 5,
    ENTITY_NODE: 6,
    PROCESSING_INSTRUCTION_NODE: 7,
    COMMENT_NODE: 8,
    DOCUMENT_NODE: 9,
    DOCUMENT_TYPE_NODE: 10,
    DOCUMENT_FRAGMENT_NODE: 11,
    NOTATION_NODE: 12
  };

  function getElementsByTagName(tag) {
    tag = tag.toUpperCase();
    var elems = [];
    var allTags = (tag === "*");
    function getElems(node) {
      var length = node.children.length;
      for (var i = 0; i < length; i++) {
        var child = node.children[i];
        if (allTags || (child.tagName === tag))
          elems.push(child);
        getElems(child);
      }
    }
    getElems(this);
    elems._isLiveNodeList = true;
    return elems;
  }

  var Node = function () {};

  Node.prototype = {
    attributes: null,
    childNodes: null,
    localName: null,
    nodeName: null,
    parentNode: null,
    textContent: null,
    nextSibling: null,
    previousSibling: null,

    get firstChild() {
      return this.childNodes[0] || null;
    },

    get firstElementChild() {
      return this.children[0] || null;
    },

    get lastChild() {
      return this.childNodes[this.childNodes.length - 1] || null;
    },

    get lastElementChild() {
      return this.children[this.children.length - 1] || null;
    },

    appendChild: function (child) {
      if (child.parentNode) {
        child.parentNode.removeChild(child);
      }

      var last = this.lastChild;
      if (last)
        last.nextSibling = child;
      child.previousSibling = last;

      if (child.nodeType === Node.ELEMENT_NODE) {
        child.previousElementSibling = this.children[this.children.length - 1] || null;
        this.children.push(child);
        child.previousElementSibling && (child.previousElementSibling.nextElementSibling = child);
      }
      this.childNodes.push(child);
      child.parentNode = this;
    },

    removeChild: function (child) {
      var childNodes = this.childNodes;
      var childIndex = childNodes.indexOf(child);
      if (childIndex === -1) {
        throw "removeChild: node not found";
      } else {
        child.parentNode = null;
        var prev = child.previousSibling;
        var next = child.nextSibling;
        if (prev)
          prev.nextSibling = next;
        if (next)
          next.previousSibling = prev;

        if (child.nodeType === Node.ELEMENT_NODE) {
          prev = child.previousElementSibling;
          next = child.nextElementSibling;
          if (prev)
            prev.nextElementSibling = next;
          if (next)
            next.previousElementSibling = prev;
          this.children.splice(this.children.indexOf(child), 1);
        }

        child.previousSibling = child.nextSibling = null;
        child.previousElementSibling = child.nextElementSibling = null;

        return childNodes.splice(childIndex, 1)[0];
      }
    },

    replaceChild: function (newNode, oldNode) {
      var childNodes = this.childNodes;
      var childIndex = childNodes.indexOf(oldNode);
      if (childIndex === -1) {
        throw "replaceChild: node not found";
      } else {
        // This will take care of updating the new node if it was somewhere else before:
        if (newNode.parentNode)
          newNode.parentNode.removeChild(newNode);

        childNodes[childIndex] = newNode;

        // update the new node's sibling properties, and its new siblings' sibling properties
        newNode.nextSibling = oldNode.nextSibling;
        newNode.previousSibling = oldNode.previousSibling;
        if (newNode.nextSibling)
          newNode.nextSibling.previousSibling = newNode;
        if (newNode.previousSibling)
          newNode.previousSibling.nextSibling = newNode;

        newNode.parentNode = this;

        // Now deal with elements before we clear out those values for the old node,
        // because it can help us take shortcuts here:
        if (newNode.nodeType === Node.ELEMENT_NODE) {
          if (oldNode.nodeType === Node.ELEMENT_NODE) {
            // Both were elements, which makes this easier, we just swap things out:
            newNode.previousElementSibling = oldNode.previousElementSibling;
            newNode.nextElementSibling = oldNode.nextElementSibling;
            if (newNode.previousElementSibling)
              newNode.previousElementSibling.nextElementSibling = newNode;
            if (newNode.nextElementSibling)
              newNode.nextElementSibling.previousElementSibling = newNode;
            this.children[this.children.indexOf(oldNode)] = newNode;
          } else {
            // Hard way:
            newNode.previousElementSibling = (function() {
              for (var i = childIndex - 1; i >= 0; i--) {
                if (childNodes[i].nodeType === Node.ELEMENT_NODE)
                  return childNodes[i];
              }
              return null;
            })();
            if (newNode.previousElementSibling) {
              newNode.nextElementSibling = newNode.previousElementSibling.nextElementSibling;
            } else {
              newNode.nextElementSibling = (function() {
                for (var i = childIndex + 1; i < childNodes.length; i++) {
                  if (childNodes[i].nodeType === Node.ELEMENT_NODE)
                    return childNodes[i];
                }
                return null;
              })();
            }
            if (newNode.previousElementSibling)
              newNode.previousElementSibling.nextElementSibling = newNode;
            if (newNode.nextElementSibling)
              newNode.nextElementSibling.previousElementSibling = newNode;

            if (newNode.nextElementSibling)
              this.children.splice(this.children.indexOf(newNode.nextElementSibling), 0, newNode);
            else
              this.children.push(newNode);
          }
        } else if (oldNode.nodeType === Node.ELEMENT_NODE) {
          // new node is not an element node.
          // if the old one was, update its element siblings:
          if (oldNode.previousElementSibling)
            oldNode.previousElementSibling.nextElementSibling = oldNode.nextElementSibling;
          if (oldNode.nextElementSibling)
            oldNode.nextElementSibling.previousElementSibling = oldNode.previousElementSibling;
          this.children.splice(this.children.indexOf(oldNode), 1);

          // If the old node wasn't an element, neither the new nor the old node was an element,
          // and the children array and its members shouldn't need any updating.
        }


        oldNode.parentNode = null;
        oldNode.previousSibling = null;
        oldNode.nextSibling = null;
        if (oldNode.nodeType === Node.ELEMENT_NODE) {
          oldNode.previousElementSibling = null;
          oldNode.nextElementSibling = null;
        }
        return oldNode;
      }
    },

    __JSDOMParser__: true,
  };

  for (var nodeType in nodeTypes) {
    Node[nodeType] = Node.prototype[nodeType] = nodeTypes[nodeType];
  }

  var Attribute = function (name, value) {
    this.name = name;
    this._value = value;
  };

  Attribute.prototype = {
    get value() {
      return this._value;
    },
    setValue: function(newValue) {
      this._value = newValue;
    },
    getEncodedValue: function() {
      return encodeHTML(this._value);
    },
  };

  var Comment = function () {
    this.childNodes = [];
  };

  Comment.prototype = {
    __proto__: Node.prototype,

    nodeName: "#comment",
    nodeType: Node.COMMENT_NODE
  };

  var Text = function () {
    this.childNodes = [];
  };

  Text.prototype = {
    __proto__: Node.prototype,

    nodeName: "#text",
    nodeType: Node.TEXT_NODE,
    get textContent() {
      if (typeof this._textContent === "undefined") {
        this._textContent = decodeHTML(this._innerHTML || "");
      }
      return this._textContent;
    },
    get innerHTML() {
      if (typeof this._innerHTML === "undefined") {
        this._innerHTML = encodeTextContentHTML(this._textContent || "");
      }
      return this._innerHTML;
    },

    set innerHTML(newHTML) {
      this._innerHTML = newHTML;
      delete this._textContent;
    },
    set textContent(newText) {
      this._textContent = newText;
      delete this._innerHTML;
    },
  };

  var Document = function (url) {
    this.documentURI = url;
    this.styleSheets = [];
    this.childNodes = [];
    this.children = [];
  };

  Document.prototype = {
    __proto__: Node.prototype,

    nodeName: "#document",
    nodeType: Node.DOCUMENT_NODE,
    title: "",

    getElementsByTagName: getElementsByTagName,

    getElementById: function (id) {
      function getElem(node) {
        var length = node.children.length;
        if (node.id === id)
          return node;
        for (var i = 0; i < length; i++) {
          var el = getElem(node.children[i]);
          if (el)
            return el;
        }
        return null;
      }
      return getElem(this);
    },

    createElement: function (tag) {
      var node = new Element(tag);
      return node;
    },

    createTextNode: function (text) {
      var node = new Text();
      node.textContent = text;
      return node;
    },

    get baseURI() {
      if (!this.hasOwnProperty("_baseURI")) {
        this._baseURI = this.documentURI;
        var baseElements = this.getElementsByTagName("base");
        var href = baseElements[0] && baseElements[0].getAttribute("href");
        if (href) {
          try {
            this._baseURI = (new URL(href, this._baseURI)).href;
          } catch (ex) {/* Just fall back to documentURI */}
        }
      }
      return this._baseURI;
    },
  };

  var Element = function (tag) {
    // We use this to find the closing tag.
    this._matchingTag = tag;
    // We're explicitly a non-namespace aware parser, we just pretend it's all HTML.
    var lastColonIndex = tag.lastIndexOf(":");
    if (lastColonIndex != -1) {
      tag = tag.substring(lastColonIndex + 1);
    }
    this.attributes = [];
    this.childNodes = [];
    this.children = [];
    this.nextElementSibling = this.previousElementSibling = null;
    this.localName = tag.toLowerCase();
    this.tagName = tag.toUpperCase();
    this.style = new Style(this);
  };

  Element.prototype = {
    __proto__: Node.prototype,

    nodeType: Node.ELEMENT_NODE,

    getElementsByTagName: getElementsByTagName,

    get className() {
      return this.getAttribute("class") || "";
    },

    set className(str) {
      this.setAttribute("class", str);
    },

    get id() {
      return this.getAttribute("id") || "";
    },

    set id(str) {
      this.setAttribute("id", str);
    },

    get href() {
      return this.getAttribute("href") || "";
    },

    set href(str) {
      this.setAttribute("href", str);
    },

    get src() {
      return this.getAttribute("src") || "";
    },

    set src(str) {
      this.setAttribute("src", str);
    },

    get srcset() {
      return this.getAttribute("srcset") || "";
    },

    set srcset(str) {
      this.setAttribute("srcset", str);
    },

    get nodeName() {
      return this.tagName;
    },

    get innerHTML() {
      function getHTML(node) {
        var i = 0;
        for (i = 0; i < node.childNodes.length; i++) {
          var child = node.childNodes[i];
          if (child.localName) {
            arr.push("<" + child.localName);

            // serialize attribute list
            for (var j = 0; j < child.attributes.length; j++) {
              var attr = child.attributes[j];
              // the attribute value will be HTML escaped.
              var val = attr.getEncodedValue();
              var quote = (val.indexOf('"') === -1 ? '"' : "'");
              arr.push(" " + attr.name + "=" + quote + val + quote);
            }

            if (child.localName in voidElems && !child.childNodes.length) {
              // if this is a self-closing element, end it here
              arr.push("/>");
            } else {
              // otherwise, add its children
              arr.push(">");
              getHTML(child);
              arr.push("</" + child.localName + ">");
            }
          } else {
            // This is a text node, so asking for innerHTML won't recurse.
            arr.push(child.innerHTML);
          }
        }
      }

      // Using Array.join() avoids the overhead from lazy string concatenation.
      var arr = [];
      getHTML(this);
      return arr.join("");
    },

    set innerHTML(html) {
      var parser = new JSDOMParser();
      var node = parser.parse(html);
      var i;
      for (i = this.childNodes.length; --i >= 0;) {
        this.childNodes[i].parentNode = null;
      }
      this.childNodes = node.childNodes;
      this.children = node.children;
      for (i = this.childNodes.length; --i >= 0;) {
        this.childNodes[i].parentNode = this;
      }
    },

    set textContent(text) {
      // clear parentNodes for existing children
      for (var i = this.childNodes.length; --i >= 0;) {
        this.childNodes[i].parentNode = null;
      }

      var node = new Text();
      this.childNodes = [ node ];
      this.children = [];
      node.textContent = text;
      node.parentNode = this;
    },

    get textContent() {
      function getText(node) {
        var nodes = node.childNodes;
        for (var i = 0; i < nodes.length; i++) {
          var child = nodes[i];
          if (child.nodeType === 3) {
            text.push(child.textContent);
          } else {
            getText(child);
          }
        }
      }

      // Using Array.join() avoids the overhead from lazy string concatenation.
      // See http://blog.cdleary.com/2012/01/string-representation-in-spidermonkey/#ropes
      var text = [];
      getText(this);
      return text.join("");
    },

    getAttribute: function (name) {
      for (var i = this.attributes.length; --i >= 0;) {
        var attr = this.attributes[i];
        if (attr.name === name) {
          return attr.value;
        }
      }
      return undefined;
    },

    setAttribute: function (name, value) {
      for (var i = this.attributes.length; --i >= 0;) {
        var attr = this.attributes[i];
        if (attr.name === name) {
          attr.setValue(value);
          return;
        }
      }
      this.attributes.push(new Attribute(name, value));
    },

    removeAttribute: function (name) {
      for (var i = this.attributes.length; --i >= 0;) {
        var attr = this.attributes[i];
        if (attr.name === name) {
          this.attributes.splice(i, 1);
          break;
        }
      }
    },

    hasAttribute: function (name) {
      return this.attributes.some(function (attr) {
        return attr.name == name;
      });
    },
  };

  var Style = function (node) {
    this.node = node;
  };

  // getStyle() and setStyle() use the style attribute string directly. This
  // won't be very efficient if there are a lot of style manipulations, but
  // it's the easiest way to make sure the style attribute string and the JS
  // style property stay in sync. Readability.js doesn't do many style
  // manipulations, so this should be okay.
  Style.prototype = {
    getStyle: function (styleName) {
      var attr = this.node.getAttribute("style");
      if (!attr)
        return undefined;

      var styles = attr.split(";");
      for (var i = 0; i < styles.length; i++) {
        var style = styles[i].split(":");
        var name = style[0].trim();
        if (name === styleName)
          return style[1].trim();
      }

      return undefined;
    },

    setStyle: function (styleName, styleValue) {
      var value = this.node.getAttribute("style") || "";
      var index = 0;
      do {
        var next = value.indexOf(";", index) + 1;
        var length = next - index - 1;
        var style = (length > 0 ? value.substr(index, length) : value.substr(index));
        if (style.substr(0, style.indexOf(":")).trim() === styleName) {
          value = value.substr(0, index).trim() + (next ? " " + value.substr(next).trim() : "");
          break;
        }
        index = next;
      } while (index);

      value += " " + styleName + ": " + styleValue + ";";
      this.node.setAttribute("style", value.trim());
    }
  };

  // For each item in styleMap, define a getter and setter on the style
  // property.
  for (var jsName in styleMap) {
    (function (cssName) {
      Style.prototype.__defineGetter__(jsName, function () {
        return this.getStyle(cssName);
      });
      Style.prototype.__defineSetter__(jsName, function (value) {
        this.setStyle(cssName, value);
      });
    })(styleMap[jsName]);
  }

  var JSDOMParser = function () {
    this.currentChar = 0;

    // In makeElementNode() we build up many strings one char at a time. Using
    // += for this results in lots of short-lived intermediate strings. It's
    // better to build an array of single-char strings and then join() them
    // together at the end. And reusing a single array (i.e. |this.strBuf|)
    // over and over for this purpose uses less memory than using a new array
    // for each string.
    this.strBuf = [];

    // Similarly, we reuse this array to return the two arguments from
    // makeElementNode(), which saves us from having to allocate a new array
    // every time.
    this.retPair = [];

    this.errorState = "";
  };

  JSDOMParser.prototype = {
    error: function(m) {
      if (typeof console !== "undefined") {
        console.log("JSDOMParser error: " + m + "\n");
      } else if (typeof dump !== "undefined") {
        /* global dump */
        dump("JSDOMParser error: " + m + "\n");
      }
      this.errorState += m + "\n";
    },

    /**
     * Look at the next character without advancing the index.
     */
    peekNext: function () {
      return this.html[this.currentChar];
    },

    /**
     * Get the next character and advance the index.
     */
    nextChar: function () {
      return this.html[this.currentChar++];
    },

    /**
     * Called after a quote character is read. This finds the next quote
     * character and returns the text string in between.
     */
    readString: function (quote) {
      var str;
      var n = this.html.indexOf(quote, this.currentChar);
      if (n === -1) {
        this.currentChar = this.html.length;
        str = null;
      } else {
        str = this.html.substring(this.currentChar, n);
        this.currentChar = n + 1;
      }

      return str;
    },

    /**
     * Called when parsing a node. This finds the next name/value attribute
     * pair and adds the result to the attributes list.
     */
    readAttribute: function (node) {
      var name = "";

      var n = this.html.indexOf("=", this.currentChar);
      if (n === -1) {
        this.currentChar = this.html.length;
      } else {
        // Read until a '=' character is hit; this will be the attribute key
        name = this.html.substring(this.currentChar, n);
        this.currentChar = n + 1;
      }

      if (!name)
        return;

      // After a '=', we should see a '"' for the attribute value
      var c = this.nextChar();
      if (c !== '"' && c !== "'") {
        this.error("Error reading attribute " + name + ", expecting '\"'");
        return;
      }

      // Read the attribute value (and consume the matching quote)
      var value = this.readString(c);

      node.attributes.push(new Attribute(name, decodeHTML(value)));

      return;
    },

    /**
     * Parses and returns an Element node. This is called after a '<' has been
     * read.
     *
     * @returns an array; the first index of the array is the parsed node;
     *          the second index is a boolean indicating whether this is a void
     *          Element
     */
    makeElementNode: function (retPair) {
      var c = this.nextChar();

      // Read the Element tag name
      var strBuf = this.strBuf;
      strBuf.length = 0;
      while (whitespace.indexOf(c) == -1 && c !== ">" && c !== "/") {
        if (c === undefined)
          return false;
        strBuf.push(c);
        c = this.nextChar();
      }
      var tag = strBuf.join("");

      if (!tag)
        return false;

      var node = new Element(tag);

      // Read Element attributes
      while (c !== "/" && c !== ">") {
        if (c === undefined)
          return false;
        while (whitespace.indexOf(this.html[this.currentChar++]) != -1) {
          // Advance cursor to first non-whitespace char.
        }
        this.currentChar--;
        c = this.nextChar();
        if (c !== "/" && c !== ">") {
          --this.currentChar;
          this.readAttribute(node);
        }
      }

      // If this is a self-closing tag, read '/>'
      var closed = false;
      if (c === "/") {
        closed = true;
        c = this.nextChar();
        if (c !== ">") {
          this.error("expected '>' to close " + tag);
          return false;
        }
      }

      retPair[0] = node;
      retPair[1] = closed;
      return true;
    },

    /**
     * If the current input matches this string, advance the input index;
     * otherwise, do nothing.
     *
     * @returns whether input matched string
     */
    match: function (str) {
      var strlen = str.length;
      if (this.html.substr(this.currentChar, strlen).toLowerCase() === str.toLowerCase()) {
        this.currentChar += strlen;
        return true;
      }
      return false;
    },

    /**
     * Searches the input until a string is found and discards all input up to
     * and including the matched string.
     */
    discardTo: function (str) {
      var index = this.html.indexOf(str, this.currentChar) + str.length;
      if (index === -1)
        this.currentChar = this.html.length;
      this.currentChar = index;
    },

    /**
     * Reads child nodes for the given node.
     */
    readChildren: function (node) {
      var child;
      while ((child = this.readNode())) {
        // Don't keep Comment nodes
        if (child.nodeType !== 8) {
          node.appendChild(child);
        }
      }
    },

    discardNextComment: function() {
      if (this.match("--")) {
        this.discardTo("-->");
      } else {
        var c = this.nextChar();
        while (c !== ">") {
          if (c === undefined)
            return null;
          if (c === '"' || c === "'")
            this.readString(c);
          c = this.nextChar();
        }
      }
      return new Comment();
    },


    /**
     * Reads the next child node from the input. If we're reading a closing
     * tag, or if we've reached the end of input, return null.
     *
     * @returns the node
     */
    readNode: function () {
      var c = this.nextChar();

      if (c === undefined)
        return null;

      // Read any text as Text node
      var textNode;
      if (c !== "<") {
        --this.currentChar;
        textNode = new Text();
        var n = this.html.indexOf("<", this.currentChar);
        if (n === -1) {
          textNode.innerHTML = this.html.substring(this.currentChar, this.html.length);
          this.currentChar = this.html.length;
        } else {
          textNode.innerHTML = this.html.substring(this.currentChar, n);
          this.currentChar = n;
        }
        return textNode;
      }

      if (this.match("![CDATA[")) {
        var endChar = this.html.indexOf("]]>", this.currentChar);
        if (endChar === -1) {
          this.error("unclosed CDATA section");
          return null;
        }
        textNode = new Text();
        textNode.textContent = this.html.substring(this.currentChar, endChar);
        this.currentChar = endChar + ("]]>").length;
        return textNode;
      }

      c = this.peekNext();

      // Read Comment node. Normally, Comment nodes know their inner
      // textContent, but we don't really care about Comment nodes (we throw
      // them away in readChildren()). So just returning an empty Comment node
      // here is sufficient.
      if (c === "!" || c === "?") {
        // We're still before the ! or ? that is starting this comment:
        this.currentChar++;
        return this.discardNextComment();
      }

      // If we're reading a closing tag, return null. This means we've reached
      // the end of this set of child nodes.
      if (c === "/") {
        --this.currentChar;
        return null;
      }

      // Otherwise, we're looking at an Element node
      var result = this.makeElementNode(this.retPair);
      if (!result)
        return null;

      var node = this.retPair[0];
      var closed = this.retPair[1];
      var localName = node.localName;

      // If this isn't a void Element, read its child nodes
      if (!closed) {
        this.readChildren(node);
        var closingTag = "</" + node._matchingTag + ">";
        if (!this.match(closingTag)) {
          this.error("expected '" + closingTag + "' and got " + this.html.substr(this.currentChar, closingTag.length));
          return null;
        }
      }

      // Only use the first title, because SVG might have other
      // title elements which we don't care about (medium.com
      // does this, at least).
      if (localName === "title" && !this.doc.title) {
        this.doc.title = node.textContent.trim();
      } else if (localName === "head") {
        this.doc.head = node;
      } else if (localName === "body") {
        this.doc.body = node;
      } else if (localName === "html") {
        this.doc.documentElement = node;
      }

      return node;
    },

    /**
     * Parses an HTML string and returns a JS implementation of the Document.
     */
    parse: function (html, url) {
      this.html = html;
      var doc = this.doc = new Document(url);
      this.readChildren(doc);

      // If this is an HTML document, remove root-level children except for the
      // <html> node
      if (doc.documentElement) {
        for (var i = doc.childNodes.length; --i >= 0;) {
          var child = doc.childNodes[i];
          if (child !== doc.documentElement) {
            doc.removeChild(child);
          }
        }
      }

      return doc;
    }
  };

  // Attach the standard DOM types to the global scope
  global.Node = Node;
  global.Comment = Comment;
  global.Document = Document;
  global.Element = Element;
  global.Text = Text;

  // Attach JSDOMParser to the global scope
  global.JSDOMParser = JSDOMParser;

})(this);

if (typeof module === "object") {
  /* global module */
  module.exports = this.JSDOMParser;
}
PK
!<�AHAHmodules/reader/Readability.js/*
 * Copyright (c) 2010 Arc90 Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * This code is heavily based on Arc90's readability.js (1.7.1) script
 * available at: http://code.google.com/p/arc90labs-readability
 */

/**
 * Public constructor.
 * @param {HTMLDocument} doc     The document to parse.
 * @param {Object}       options The options object.
 */
function Readability(doc, options) {
  // In some older versions, people passed a URI as the first argument. Cope:
  if (options && options.documentElement) {
    doc = options;
    options = arguments[2];
  } else if (!doc || !doc.documentElement) {
    throw new Error("First argument to Readability constructor should be a document object.");
  }
  options = options || {};

  this._doc = doc;
  this._docJSDOMParser = this._doc.firstChild.__JSDOMParser__;
  this._articleTitle = null;
  this._articleByline = null;
  this._articleDir = null;
  this._articleSiteName = null;
  this._attempts = [];

  // Configurable options
  this._debug = !!options.debug;
  this._maxElemsToParse = options.maxElemsToParse || this.DEFAULT_MAX_ELEMS_TO_PARSE;
  this._nbTopCandidates = options.nbTopCandidates || this.DEFAULT_N_TOP_CANDIDATES;
  this._charThreshold = options.charThreshold || this.DEFAULT_CHAR_THRESHOLD;
  this._classesToPreserve = this.CLASSES_TO_PRESERVE.concat(options.classesToPreserve || []);
  this._keepClasses = !!options.keepClasses;
  this._serializer = options.serializer || function(el) {
    return el.innerHTML;
  };
  this._disableJSONLD = !!options.disableJSONLD;
  this._allowedVideoRegex = options.allowedVideoRegex || this.REGEXPS.videos;

  // Start with all flags set
  this._flags = this.FLAG_STRIP_UNLIKELYS |
                this.FLAG_WEIGHT_CLASSES |
                this.FLAG_CLEAN_CONDITIONALLY;


  // Control whether log messages are sent to the console
  if (this._debug) {
    let logNode = function(node) {
      if (node.nodeType == node.TEXT_NODE) {
        return `${node.nodeName} ("${node.textContent}")`;
      }
      let attrPairs = Array.from(node.attributes || [], function(attr) {
        return `${attr.name}="${attr.value}"`;
      }).join(" ");
      return `<${node.localName} ${attrPairs}>`;
    };
    this.log = function () {
      if (typeof console !== "undefined") {
        let args = Array.from(arguments, arg => {
          if (arg && arg.nodeType == this.ELEMENT_NODE) {
            return logNode(arg);
          }
          return arg;
        });
        args.unshift("Reader: (Readability)");
        console.log.apply(console, args);
      } else if (typeof dump !== "undefined") {
        /* global dump */
        var msg = Array.prototype.map.call(arguments, function(x) {
          return (x && x.nodeName) ? logNode(x) : x;
        }).join(" ");
        dump("Reader: (Readability) " + msg + "\n");
      }
    };
  } else {
    this.log = function () {};
  }
}

Readability.prototype = {
  FLAG_STRIP_UNLIKELYS: 0x1,
  FLAG_WEIGHT_CLASSES: 0x2,
  FLAG_CLEAN_CONDITIONALLY: 0x4,

  // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
  ELEMENT_NODE: 1,
  TEXT_NODE: 3,

  // Max number of nodes supported by this parser. Default: 0 (no limit)
  DEFAULT_MAX_ELEMS_TO_PARSE: 0,

  // The number of top candidates to consider when analysing how
  // tight the competition is among candidates.
  DEFAULT_N_TOP_CANDIDATES: 5,

  // Element tags to score by default.
  DEFAULT_TAGS_TO_SCORE: "section,h2,h3,h4,h5,h6,p,td,pre".toUpperCase().split(","),

  // The default number of chars an article must have in order to return a result
  DEFAULT_CHAR_THRESHOLD: 500,

  // All of the regular expressions in use within readability.
  // Defined up here so we don't instantiate them repeatedly in loops.
  REGEXPS: {
    // NOTE: These two regular expressions are duplicated in
    // Readability-readerable.js. Please keep both copies in sync.
    unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote/i,
    okMaybeItsACandidate: /and|article|body|column|content|main|shadow/i,

    positive: /article|body|content|entry|hentry|h-entry|main|page|pagination|post|text|blog|story/i,
    negative: /-ad-|hidden|^hid$| hid$| hid |^hid |banner|combx|comment|com-|contact|foot|footer|footnote|gdpr|masthead|media|meta|outbrain|promo|related|scroll|share|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i,
    extraneous: /print|archive|comment|discuss|e[\-]?mail|share|reply|all|login|sign|single|utility/i,
    byline: /byline|author|dateline|writtenby|p-author/i,
    replaceFonts: /<(\/?)font[^>]*>/gi,
    normalize: /\s{2,}/g,
    videos: /\/\/(www\.)?((dailymotion|youtube|youtube-nocookie|player\.vimeo|v\.qq)\.com|(archive|upload\.wikimedia)\.org|player\.twitch\.tv)/i,
    shareElements: /(\b|_)(share|sharedaddy)(\b|_)/i,
    nextLink: /(next|weiter|continue|>([^\|]|$)|»([^\|]|$))/i,
    prevLink: /(prev|earl|old|new|<|«)/i,
    tokenize: /\W+/g,
    whitespace: /^\s*$/,
    hasContent: /\S$/,
    hashUrl: /^#.+/,
    srcsetUrl: /(\S+)(\s+[\d.]+[xw])?(\s*(?:,|$))/g,
    b64DataUrl: /^data:\s*([^\s;,]+)\s*;\s*base64\s*,/i,
    // Commas as used in Latin, Sindhi, Chinese and various other scripts.
    // see: https://en.wikipedia.org/wiki/Comma#Comma_variants
    commas: /\u002C|\u060C|\uFE50|\uFE10|\uFE11|\u2E41|\u2E34|\u2E32|\uFF0C/g,
    // See: https://schema.org/Article
    jsonLdArticleTypes: /^Article|AdvertiserContentArticle|NewsArticle|AnalysisNewsArticle|AskPublicNewsArticle|BackgroundNewsArticle|OpinionNewsArticle|ReportageNewsArticle|ReviewNewsArticle|Report|SatiricalArticle|ScholarlyArticle|MedicalScholarlyArticle|SocialMediaPosting|BlogPosting|LiveBlogPosting|DiscussionForumPosting|TechArticle|APIReference$/
  },

  UNLIKELY_ROLES: [ "menu", "menubar", "complementary", "navigation", "alert", "alertdialog", "dialog" ],

  DIV_TO_P_ELEMS: new Set([ "BLOCKQUOTE", "DL", "DIV", "IMG", "OL", "P", "PRE", "TABLE", "UL" ]),

  ALTER_TO_DIV_EXCEPTIONS: ["DIV", "ARTICLE", "SECTION", "P"],

  PRESENTATIONAL_ATTRIBUTES: [ "align", "background", "bgcolor", "border", "cellpadding", "cellspacing", "frame", "hspace", "rules", "style", "valign", "vspace" ],

  DEPRECATED_SIZE_ATTRIBUTE_ELEMS: [ "TABLE", "TH", "TD", "HR", "PRE" ],

  // The commented out elements qualify as phrasing content but tend to be
  // removed by readability when put into paragraphs, so we ignore them here.
  PHRASING_ELEMS: [
    // "CANVAS", "IFRAME", "SVG", "VIDEO",
    "ABBR", "AUDIO", "B", "BDO", "BR", "BUTTON", "CITE", "CODE", "DATA",
    "DATALIST", "DFN", "EM", "EMBED", "I", "IMG", "INPUT", "KBD", "LABEL",
    "MARK", "MATH", "METER", "NOSCRIPT", "OBJECT", "OUTPUT", "PROGRESS", "Q",
    "RUBY", "SAMP", "SCRIPT", "SELECT", "SMALL", "SPAN", "STRONG", "SUB",
    "SUP", "TEXTAREA", "TIME", "VAR", "WBR"
  ],

  // These are the classes that readability sets itself.
  CLASSES_TO_PRESERVE: [ "page" ],

  // These are the list of HTML entities that need to be escaped.
  HTML_ESCAPE_MAP: {
    "lt": "<",
    "gt": ">",
    "amp": "&",
    "quot": '"',
    "apos": "'",
  },

  /**
   * Run any post-process modifications to article content as necessary.
   *
   * @param Element
   * @return void
  **/
  _postProcessContent: function(articleContent) {
    // Readability cannot open relative uris so we convert them to absolute uris.
    this._fixRelativeUris(articleContent);

    this._simplifyNestedElements(articleContent);

    if (!this._keepClasses) {
      // Remove classes.
      this._cleanClasses(articleContent);
    }
  },

  /**
   * Iterates over a NodeList, calls `filterFn` for each node and removes node
   * if function returned `true`.
   *
   * If function is not passed, removes all the nodes in node list.
   *
   * @param NodeList nodeList The nodes to operate on
   * @param Function filterFn the function to use as a filter
   * @return void
   */
  _removeNodes: function(nodeList, filterFn) {
    // Avoid ever operating on live node lists.
    if (this._docJSDOMParser && nodeList._isLiveNodeList) {
      throw new Error("Do not pass live node lists to _removeNodes");
    }
    for (var i = nodeList.length - 1; i >= 0; i--) {
      var node = nodeList[i];
      var parentNode = node.parentNode;
      if (parentNode) {
        if (!filterFn || filterFn.call(this, node, i, nodeList)) {
          parentNode.removeChild(node);
        }
      }
    }
  },

  /**
   * Iterates over a NodeList, and calls _setNodeTag for each node.
   *
   * @param NodeList nodeList The nodes to operate on
   * @param String newTagName the new tag name to use
   * @return void
   */
  _replaceNodeTags: function(nodeList, newTagName) {
    // Avoid ever operating on live node lists.
    if (this._docJSDOMParser && nodeList._isLiveNodeList) {
      throw new Error("Do not pass live node lists to _replaceNodeTags");
    }
    for (const node of nodeList) {
      this._setNodeTag(node, newTagName);
    }
  },

  /**
   * Iterate over a NodeList, which doesn't natively fully implement the Array
   * interface.
   *
   * For convenience, the current object context is applied to the provided
   * iterate function.
   *
   * @param  NodeList nodeList The NodeList.
   * @param  Function fn       The iterate function.
   * @return void
   */
  _forEachNode: function(nodeList, fn) {
    Array.prototype.forEach.call(nodeList, fn, this);
  },

  /**
   * Iterate over a NodeList, and return the first node that passes
   * the supplied test function
   *
   * For convenience, the current object context is applied to the provided
   * test function.
   *
   * @param  NodeList nodeList The NodeList.
   * @param  Function fn       The test function.
   * @return void
   */
  _findNode: function(nodeList, fn) {
    return Array.prototype.find.call(nodeList, fn, this);
  },

  /**
   * Iterate over a NodeList, return true if any of the provided iterate
   * function calls returns true, false otherwise.
   *
   * For convenience, the current object context is applied to the
   * provided iterate function.
   *
   * @param  NodeList nodeList The NodeList.
   * @param  Function fn       The iterate function.
   * @return Boolean
   */
  _someNode: function(nodeList, fn) {
    return Array.prototype.some.call(nodeList, fn, this);
  },

  /**
   * Iterate over a NodeList, return true if all of the provided iterate
   * function calls return true, false otherwise.
   *
   * For convenience, the current object context is applied to the
   * provided iterate function.
   *
   * @param  NodeList nodeList The NodeList.
   * @param  Function fn       The iterate function.
   * @return Boolean
   */
  _everyNode: function(nodeList, fn) {
    return Array.prototype.every.call(nodeList, fn, this);
  },

  /**
   * Concat all nodelists passed as arguments.
   *
   * @return ...NodeList
   * @return Array
   */
  _concatNodeLists: function() {
    var slice = Array.prototype.slice;
    var args = slice.call(arguments);
    var nodeLists = args.map(function(list) {
      return slice.call(list);
    });
    return Array.prototype.concat.apply([], nodeLists);
  },

  _getAllNodesWithTag: function(node, tagNames) {
    if (node.querySelectorAll) {
      return node.querySelectorAll(tagNames.join(","));
    }
    return [].concat.apply([], tagNames.map(function(tag) {
      var collection = node.getElementsByTagName(tag);
      return Array.isArray(collection) ? collection : Array.from(collection);
    }));
  },

  /**
   * Removes the class="" attribute from every element in the given
   * subtree, except those that match CLASSES_TO_PRESERVE and
   * the classesToPreserve array from the options object.
   *
   * @param Element
   * @return void
   */
  _cleanClasses: function(node) {
    var classesToPreserve = this._classesToPreserve;
    var className = (node.getAttribute("class") || "")
      .split(/\s+/)
      .filter(function(cls) {
        return classesToPreserve.indexOf(cls) != -1;
      })
      .join(" ");

    if (className) {
      node.setAttribute("class", className);
    } else {
      node.removeAttribute("class");
    }

    for (node = node.firstElementChild; node; node = node.nextElementSibling) {
      this._cleanClasses(node);
    }
  },

  /**
   * Converts each <a> and <img> uri in the given element to an absolute URI,
   * ignoring #ref URIs.
   *
   * @param Element
   * @return void
   */
  _fixRelativeUris: function(articleContent) {
    var baseURI = this._doc.baseURI;
    var documentURI = this._doc.documentURI;
    function toAbsoluteURI(uri) {
      // Leave hash links alone if the base URI matches the document URI:
      if (baseURI == documentURI && uri.charAt(0) == "#") {
        return uri;
      }

      // Otherwise, resolve against base URI:
      try {
        return new URL(uri, baseURI).href;
      } catch (ex) {
        // Something went wrong, just return the original:
      }
      return uri;
    }

    var links = this._getAllNodesWithTag(articleContent, ["a"]);
    this._forEachNode(links, function(link) {
      var href = link.getAttribute("href");
      if (href) {
        // Remove links with javascript: URIs, since
        // they won't work after scripts have been removed from the page.
        if (href.indexOf("javascript:") === 0) {
          // if the link only contains simple text content, it can be converted to a text node
          if (link.childNodes.length === 1 && link.childNodes[0].nodeType === this.TEXT_NODE) {
            var text = this._doc.createTextNode(link.textContent);
            link.parentNode.replaceChild(text, link);
          } else {
            // if the link has multiple children, they should all be preserved
            var container = this._doc.createElement("span");
            while (link.firstChild) {
              container.appendChild(link.firstChild);
            }
            link.parentNode.replaceChild(container, link);
          }
        } else {
          link.setAttribute("href", toAbsoluteURI(href));
        }
      }
    });

    var medias = this._getAllNodesWithTag(articleContent, [
      "img", "picture", "figure", "video", "audio", "source"
    ]);

    this._forEachNode(medias, function(media) {
      var src = media.getAttribute("src");
      var poster = media.getAttribute("poster");
      var srcset = media.getAttribute("srcset");

      if (src) {
        media.setAttribute("src", toAbsoluteURI(src));
      }

      if (poster) {
        media.setAttribute("poster", toAbsoluteURI(poster));
      }

      if (srcset) {
        var newSrcset = srcset.replace(this.REGEXPS.srcsetUrl, function(_, p1, p2, p3) {
          return toAbsoluteURI(p1) + (p2 || "") + p3;
        });

        media.setAttribute("srcset", newSrcset);
      }
    });
  },

  _simplifyNestedElements: function(articleContent) {
    var node = articleContent;

    while (node) {
      if (node.parentNode && ["DIV", "SECTION"].includes(node.tagName) && !(node.id && node.id.startsWith("readability"))) {
        if (this._isElementWithoutContent(node)) {
          node = this._removeAndGetNext(node);
          continue;
        } else if (this._hasSingleTagInsideElement(node, "DIV") || this._hasSingleTagInsideElement(node, "SECTION")) {
          var child = node.children[0];
          for (var i = 0; i < node.attributes.length; i++) {
            child.setAttribute(node.attributes[i].name, node.attributes[i].value);
          }
          node.parentNode.replaceChild(child, node);
          node = child;
          continue;
        }
      }

      node = this._getNextNode(node);
    }
  },

  /**
   * Get the article title as an H1.
   *
   * @return string
   **/
  _getArticleTitle: function() {
    var doc = this._doc;
    var curTitle = "";
    var origTitle = "";

    try {
      curTitle = origTitle = doc.title.trim();

      // If they had an element with id "title" in their HTML
      if (typeof curTitle !== "string")
        curTitle = origTitle = this._getInnerText(doc.getElementsByTagName("title")[0]);
    } catch (e) {/* ignore exceptions setting the title. */}

    var titleHadHierarchicalSeparators = false;
    function wordCount(str) {
      return str.split(/\s+/).length;
    }

    // If there's a separator in the title, first remove the final part
    if ((/ [\|\-\\\/>»] /).test(curTitle)) {
      titleHadHierarchicalSeparators = / [\\\/>»] /.test(curTitle);
      curTitle = origTitle.replace(/(.*)[\|\-\\\/>»] .*/gi, "$1");

      // If the resulting title is too short (3 words or fewer), remove
      // the first part instead:
      if (wordCount(curTitle) < 3)
        curTitle = origTitle.replace(/[^\|\-\\\/>»]*[\|\-\\\/>»](.*)/gi, "$1");
    } else if (curTitle.indexOf(": ") !== -1) {
      // Check if we have an heading containing this exact string, so we
      // could assume it's the full title.
      var headings = this._concatNodeLists(
        doc.getElementsByTagName("h1"),
        doc.getElementsByTagName("h2")
      );
      var trimmedTitle = curTitle.trim();
      var match = this._someNode(headings, function(heading) {
        return heading.textContent.trim() === trimmedTitle;
      });

      // If we don't, let's extract the title out of the original title string.
      if (!match) {
        curTitle = origTitle.substring(origTitle.lastIndexOf(":") + 1);

        // If the title is now too short, try the first colon instead:
        if (wordCount(curTitle) < 3) {
          curTitle = origTitle.substring(origTitle.indexOf(":") + 1);
          // But if we have too many words before the colon there's something weird
          // with the titles and the H tags so let's just use the original title instead
        } else if (wordCount(origTitle.substr(0, origTitle.indexOf(":"))) > 5) {
          curTitle = origTitle;
        }
      }
    } else if (curTitle.length > 150 || curTitle.length < 15) {
      var hOnes = doc.getElementsByTagName("h1");

      if (hOnes.length === 1)
        curTitle = this._getInnerText(hOnes[0]);
    }

    curTitle = curTitle.trim().replace(this.REGEXPS.normalize, " ");
    // If we now have 4 words or fewer as our title, and either no
    // 'hierarchical' separators (\, /, > or ») were found in the original
    // title or we decreased the number of words by more than 1 word, use
    // the original title.
    var curTitleWordCount = wordCount(curTitle);
    if (curTitleWordCount <= 4 &&
        (!titleHadHierarchicalSeparators ||
         curTitleWordCount != wordCount(origTitle.replace(/[\|\-\\\/>»]+/g, "")) - 1)) {
      curTitle = origTitle;
    }

    return curTitle;
  },

  /**
   * Prepare the HTML document for readability to scrape it.
   * This includes things like stripping javascript, CSS, and handling terrible markup.
   *
   * @return void
   **/
  _prepDocument: function() {
    var doc = this._doc;

    // Remove all style tags in head
    this._removeNodes(this._getAllNodesWithTag(doc, ["style"]));

    if (doc.body) {
      this._replaceBrs(doc.body);
    }

    this._replaceNodeTags(this._getAllNodesWithTag(doc, ["font"]), "SPAN");
  },

  /**
   * Finds the next node, starting from the given node, and ignoring
   * whitespace in between. If the given node is an element, the same node is
   * returned.
   */
  _nextNode: function (node) {
    var next = node;
    while (next
        && (next.nodeType != this.ELEMENT_NODE)
        && this.REGEXPS.whitespace.test(next.textContent)) {
      next = next.nextSibling;
    }
    return next;
  },

  /**
   * Replaces 2 or more successive <br> elements with a single <p>.
   * Whitespace between <br> elements are ignored. For example:
   *   <div>foo<br>bar<br> <br><br>abc</div>
   * will become:
   *   <div>foo<br>bar<p>abc</p></div>
   */
  _replaceBrs: function (elem) {
    this._forEachNode(this._getAllNodesWithTag(elem, ["br"]), function(br) {
      var next = br.nextSibling;

      // Whether 2 or more <br> elements have been found and replaced with a
      // <p> block.
      var replaced = false;

      // If we find a <br> chain, remove the <br>s until we hit another node
      // or non-whitespace. This leaves behind the first <br> in the chain
      // (which will be replaced with a <p> later).
      while ((next = this._nextNode(next)) && (next.tagName == "BR")) {
        replaced = true;
        var brSibling = next.nextSibling;
        next.parentNode.removeChild(next);
        next = brSibling;
      }

      // If we removed a <br> chain, replace the remaining <br> with a <p>. Add
      // all sibling nodes as children of the <p> until we hit another <br>
      // chain.
      if (replaced) {
        var p = this._doc.createElement("p");
        br.parentNode.replaceChild(p, br);

        next = p.nextSibling;
        while (next) {
          // If we've hit another <br><br>, we're done adding children to this <p>.
          if (next.tagName == "BR") {
            var nextElem = this._nextNode(next.nextSibling);
            if (nextElem && nextElem.tagName == "BR")
              break;
          }

          if (!this._isPhrasingContent(next))
            break;

          // Otherwise, make this node a child of the new <p>.
          var sibling = next.nextSibling;
          p.appendChild(next);
          next = sibling;
        }

        while (p.lastChild && this._isWhitespace(p.lastChild)) {
          p.removeChild(p.lastChild);
        }

        if (p.parentNode.tagName === "P")
          this._setNodeTag(p.parentNode, "DIV");
      }
    });
  },

  _setNodeTag: function (node, tag) {
    this.log("_setNodeTag", node, tag);
    if (this._docJSDOMParser) {
      node.localName = tag.toLowerCase();
      node.tagName = tag.toUpperCase();
      return node;
    }

    var replacement = node.ownerDocument.createElement(tag);
    while (node.firstChild) {
      replacement.appendChild(node.firstChild);
    }
    node.parentNode.replaceChild(replacement, node);
    if (node.readability)
      replacement.readability = node.readability;

    for (var i = 0; i < node.attributes.length; i++) {
      try {
        replacement.setAttribute(node.attributes[i].name, node.attributes[i].value);
      } catch (ex) {
        /* it's possible for setAttribute() to throw if the attribute name
         * isn't a valid XML Name. Such attributes can however be parsed from
         * source in HTML docs, see https://github.com/whatwg/html/issues/4275,
         * so we can hit them here and then throw. We don't care about such
         * attributes so we ignore them.
         */
      }
    }
    return replacement;
  },

  /**
   * Prepare the article node for display. Clean out any inline styles,
   * iframes, forms, strip extraneous <p> tags, etc.
   *
   * @param Element
   * @return void
   **/
  _prepArticle: function(articleContent) {
    this._cleanStyles(articleContent);

    // Check for data tables before we continue, to avoid removing items in
    // those tables, which will often be isolated even though they're
    // visually linked to other content-ful elements (text, images, etc.).
    this._markDataTables(articleContent);

    this._fixLazyImages(articleContent);

    // Clean out junk from the article content
    this._cleanConditionally(articleContent, "form");
    this._cleanConditionally(articleContent, "fieldset");
    this._clean(articleContent, "object");
    this._clean(articleContent, "embed");
    this._clean(articleContent, "footer");
    this._clean(articleContent, "link");
    this._clean(articleContent, "aside");

    // Clean out elements with little content that have "share" in their id/class combinations from final top candidates,
    // which means we don't remove the top candidates even they have "share".

    var shareElementThreshold = this.DEFAULT_CHAR_THRESHOLD;

    this._forEachNode(articleContent.children, function (topCandidate) {
      this._cleanMatchedNodes(topCandidate, function (node, matchString) {
        return this.REGEXPS.shareElements.test(matchString) && node.textContent.length < shareElementThreshold;
      });
    });

    this._clean(articleContent, "iframe");
    this._clean(articleContent, "input");
    this._clean(articleContent, "textarea");
    this._clean(articleContent, "select");
    this._clean(articleContent, "button");
    this._cleanHeaders(articleContent);

    // Do these last as the previous stuff may have removed junk
    // that will affect these
    this._cleanConditionally(articleContent, "table");
    this._cleanConditionally(articleContent, "ul");
    this._cleanConditionally(articleContent, "div");

    // replace H1 with H2 as H1 should be only title that is displayed separately
    this._replaceNodeTags(this._getAllNodesWithTag(articleContent, ["h1"]), "h2");

    // Remove extra paragraphs
    this._removeNodes(this._getAllNodesWithTag(articleContent, ["p"]), function (paragraph) {
      var imgCount = paragraph.getElementsByTagName("img").length;
      var embedCount = paragraph.getElementsByTagName("embed").length;
      var objectCount = paragraph.getElementsByTagName("object").length;
      // At this point, nasty iframes have been removed, only remain embedded video ones.
      var iframeCount = paragraph.getElementsByTagName("iframe").length;
      var totalCount = imgCount + embedCount + objectCount + iframeCount;

      return totalCount === 0 && !this._getInnerText(paragraph, false);
    });

    this._forEachNode(this._getAllNodesWithTag(articleContent, ["br"]), function(br) {
      var next = this._nextNode(br.nextSibling);
      if (next && next.tagName == "P")
        br.parentNode.removeChild(br);
    });

    // Remove single-cell tables
    this._forEachNode(this._getAllNodesWithTag(articleContent, ["table"]), function(table) {
      var tbody = this._hasSingleTagInsideElement(table, "TBODY") ? table.firstElementChild : table;
      if (this._hasSingleTagInsideElement(tbody, "TR")) {
        var row = tbody.firstElementChild;
        if (this._hasSingleTagInsideElement(row, "TD")) {
          var cell = row.firstElementChild;
          cell = this._setNodeTag(cell, this._everyNode(cell.childNodes, this._isPhrasingContent) ? "P" : "DIV");
          table.parentNode.replaceChild(cell, table);
        }
      }
    });
  },

  /**
   * Initialize a node with the readability object. Also checks the
   * className/id for special names to add to its score.
   *
   * @param Element
   * @return void
  **/
  _initializeNode: function(node) {
    node.readability = {"contentScore": 0};

    switch (node.tagName) {
      case "DIV":
        node.readability.contentScore += 5;
        break;

      case "PRE":
      case "TD":
      case "BLOCKQUOTE":
        node.readability.contentScore += 3;
        break;

      case "ADDRESS":
      case "OL":
      case "UL":
      case "DL":
      case "DD":
      case "DT":
      case "LI":
      case "FORM":
        node.readability.contentScore -= 3;
        break;

      case "H1":
      case "H2":
      case "H3":
      case "H4":
      case "H5":
      case "H6":
      case "TH":
        node.readability.contentScore -= 5;
        break;
    }

    node.readability.contentScore += this._getClassWeight(node);
  },

  _removeAndGetNext: function(node) {
    var nextNode = this._getNextNode(node, true);
    node.parentNode.removeChild(node);
    return nextNode;
  },

  /**
   * Traverse the DOM from node to node, starting at the node passed in.
   * Pass true for the second parameter to indicate this node itself
   * (and its kids) are going away, and we want the next node over.
   *
   * Calling this in a loop will traverse the DOM depth-first.
   */
  _getNextNode: function(node, ignoreSelfAndKids) {
    // First check for kids if those aren't being ignored
    if (!ignoreSelfAndKids && node.firstElementChild) {
      return node.firstElementChild;
    }
    // Then for siblings...
    if (node.nextElementSibling) {
      return node.nextElementSibling;
    }
    // And finally, move up the parent chain *and* find a sibling
    // (because this is depth-first traversal, we will have already
    // seen the parent nodes themselves).
    do {
      node = node.parentNode;
    } while (node && !node.nextElementSibling);
    return node && node.nextElementSibling;
  },

  // compares second text to first one
  // 1 = same text, 0 = completely different text
  // works the way that it splits both texts into words and then finds words that are unique in second text
  // the result is given by the lower length of unique parts
  _textSimilarity: function(textA, textB) {
    var tokensA = textA.toLowerCase().split(this.REGEXPS.tokenize).filter(Boolean);
    var tokensB = textB.toLowerCase().split(this.REGEXPS.tokenize).filter(Boolean);
    if (!tokensA.length || !tokensB.length) {
      return 0;
    }
    var uniqTokensB = tokensB.filter(token => !tokensA.includes(token));
    var distanceB = uniqTokensB.join(" ").length / tokensB.join(" ").length;
    return 1 - distanceB;
  },

  _checkByline: function(node, matchString) {
    if (this._articleByline) {
      return false;
    }

    if (node.getAttribute !== undefined) {
      var rel = node.getAttribute("rel");
      var itemprop = node.getAttribute("itemprop");
    }

    if ((rel === "author" || (itemprop && itemprop.indexOf("author") !== -1) || this.REGEXPS.byline.test(matchString)) && this._isValidByline(node.textContent)) {
      this._articleByline = node.textContent.trim();
      return true;
    }

    return false;
  },

  _getNodeAncestors: function(node, maxDepth) {
    maxDepth = maxDepth || 0;
    var i = 0, ancestors = [];
    while (node.parentNode) {
      ancestors.push(node.parentNode);
      if (maxDepth && ++i === maxDepth)
        break;
      node = node.parentNode;
    }
    return ancestors;
  },

  /***
   * grabArticle - Using a variety of metrics (content score, classname, element types), find the content that is
   *         most likely to be the stuff a user wants to read. Then return it wrapped up in a div.
   *
   * @param page a document to run upon. Needs to be a full document, complete with body.
   * @return Element
  **/
  _grabArticle: function (page) {
    this.log("**** grabArticle ****");
    var doc = this._doc;
    var isPaging = page !== null;
    page = page ? page : this._doc.body;

    // We can't grab an article if we don't have a page!
    if (!page) {
      this.log("No body found in document. Abort.");
      return null;
    }

    var pageCacheHtml = page.innerHTML;

    while (true) {
      this.log("Starting grabArticle loop");
      var stripUnlikelyCandidates = this._flagIsActive(this.FLAG_STRIP_UNLIKELYS);

      // First, node prepping. Trash nodes that look cruddy (like ones with the
      // class name "comment", etc), and turn divs into P tags where they have been
      // used inappropriately (as in, where they contain no other block level elements.)
      var elementsToScore = [];
      var node = this._doc.documentElement;

      let shouldRemoveTitleHeader = true;

      while (node) {

        if (node.tagName === "HTML") {
          this._articleLang = node.getAttribute("lang");
        }

        var matchString = node.className + " " + node.id;

        if (!this._isProbablyVisible(node)) {
          this.log("Removing hidden node - " + matchString);
          node = this._removeAndGetNext(node);
          continue;
        }

        // User is not able to see elements applied with both "aria-modal = true" and "role = dialog"
        if (node.getAttribute("aria-modal") == "true" && node.getAttribute("role") == "dialog") {
          node = this._removeAndGetNext(node);
          continue;
        }

        // Check to see if this node is a byline, and remove it if it is.
        if (this._checkByline(node, matchString)) {
          node = this._removeAndGetNext(node);
          continue;
        }

        if (shouldRemoveTitleHeader && this._headerDuplicatesTitle(node)) {
          this.log("Removing header: ", node.textContent.trim(), this._articleTitle.trim());
          shouldRemoveTitleHeader = false;
          node = this._removeAndGetNext(node);
          continue;
        }

        // Remove unlikely candidates
        if (stripUnlikelyCandidates) {
          if (this.REGEXPS.unlikelyCandidates.test(matchString) &&
              !this.REGEXPS.okMaybeItsACandidate.test(matchString) &&
              !this._hasAncestorTag(node, "table") &&
              !this._hasAncestorTag(node, "code") &&
              node.tagName !== "BODY" &&
              node.tagName !== "A") {
            this.log("Removing unlikely candidate - " + matchString);
            node = this._removeAndGetNext(node);
            continue;
          }

          if (this.UNLIKELY_ROLES.includes(node.getAttribute("role"))) {
            this.log("Removing content with role " + node.getAttribute("role") + " - " + matchString);
            node = this._removeAndGetNext(node);
            continue;
          }
        }

        // Remove DIV, SECTION, and HEADER nodes without any content(e.g. text, image, video, or iframe).
        if ((node.tagName === "DIV" || node.tagName === "SECTION" || node.tagName === "HEADER" ||
             node.tagName === "H1" || node.tagName === "H2" || node.tagName === "H3" ||
             node.tagName === "H4" || node.tagName === "H5" || node.tagName === "H6") &&
            this._isElementWithoutContent(node)) {
          node = this._removeAndGetNext(node);
          continue;
        }

        if (this.DEFAULT_TAGS_TO_SCORE.indexOf(node.tagName) !== -1) {
          elementsToScore.push(node);
        }

        // Turn all divs that don't have children block level elements into p's
        if (node.tagName === "DIV") {
          // Put phrasing content into paragraphs.
          var p = null;
          var childNode = node.firstChild;
          while (childNode) {
            var nextSibling = childNode.nextSibling;
            if (this._isPhrasingContent(childNode)) {
              if (p !== null) {
                p.appendChild(childNode);
              } else if (!this._isWhitespace(childNode)) {
                p = doc.createElement("p");
                node.replaceChild(p, childNode);
                p.appendChild(childNode);
              }
            } else if (p !== null) {
              while (p.lastChild && this._isWhitespace(p.lastChild)) {
                p.removeChild(p.lastChild);
              }
              p = null;
            }
            childNode = nextSibling;
          }

          // Sites like http://mobile.slate.com encloses each paragraph with a DIV
          // element. DIVs with only a P element inside and no text content can be
          // safely converted into plain P elements to avoid confusing the scoring
          // algorithm with DIVs with are, in practice, paragraphs.
          if (this._hasSingleTagInsideElement(node, "P") && this._getLinkDensity(node) < 0.25) {
            var newNode = node.children[0];
            node.parentNode.replaceChild(newNode, node);
            node = newNode;
            elementsToScore.push(node);
          } else if (!this._hasChildBlockElement(node)) {
            node = this._setNodeTag(node, "P");
            elementsToScore.push(node);
          }
        }
        node = this._getNextNode(node);
      }

      /**
       * Loop through all paragraphs, and assign a score to them based on how content-y they look.
       * Then add their score to their parent node.
       *
       * A score is determined by things like number of commas, class names, etc. Maybe eventually link density.
      **/
      var candidates = [];
      this._forEachNode(elementsToScore, function(elementToScore) {
        if (!elementToScore.parentNode || typeof(elementToScore.parentNode.tagName) === "undefined")
          return;

        // If this paragraph is less than 25 characters, don't even count it.
        var innerText = this._getInnerText(elementToScore);
        if (innerText.length < 25)
          return;

        // Exclude nodes with no ancestor.
        var ancestors = this._getNodeAncestors(elementToScore, 5);
        if (ancestors.length === 0)
          return;

        var contentScore = 0;

        // Add a point for the paragraph itself as a base.
        contentScore += 1;

        // Add points for any commas within this paragraph.
        contentScore += innerText.split(this.REGEXPS.commas).length;

        // For every 100 characters in this paragraph, add another point. Up to 3 points.
        contentScore += Math.min(Math.floor(innerText.length / 100), 3);

        // Initialize and score ancestors.
        this._forEachNode(ancestors, function(ancestor, level) {
          if (!ancestor.tagName || !ancestor.parentNode || typeof(ancestor.parentNode.tagName) === "undefined")
            return;

          if (typeof(ancestor.readability) === "undefined") {
            this._initializeNode(ancestor);
            candidates.push(ancestor);
          }

          // Node score divider:
          // - parent:             1 (no division)
          // - grandparent:        2
          // - great grandparent+: ancestor level * 3
          if (level === 0)
            var scoreDivider = 1;
          else if (level === 1)
            scoreDivider = 2;
          else
            scoreDivider = level * 3;
          ancestor.readability.contentScore += contentScore / scoreDivider;
        });
      });

      // After we've calculated scores, loop through all of the possible
      // candidate nodes we found and find the one with the highest score.
      var topCandidates = [];
      for (var c = 0, cl = candidates.length; c < cl; c += 1) {
        var candidate = candidates[c];

        // Scale the final candidates score based on link density. Good content
        // should have a relatively small link density (5% or less) and be mostly
        // unaffected by this operation.
        var candidateScore = candidate.readability.contentScore * (1 - this._getLinkDensity(candidate));
        candidate.readability.contentScore = candidateScore;

        this.log("Candidate:", candidate, "with score " + candidateScore);

        for (var t = 0; t < this._nbTopCandidates; t++) {
          var aTopCandidate = topCandidates[t];

          if (!aTopCandidate || candidateScore > aTopCandidate.readability.contentScore) {
            topCandidates.splice(t, 0, candidate);
            if (topCandidates.length > this._nbTopCandidates)
              topCandidates.pop();
            break;
          }
        }
      }

      var topCandidate = topCandidates[0] || null;
      var neededToCreateTopCandidate = false;
      var parentOfTopCandidate;

      // If we still have no top candidate, just use the body as a last resort.
      // We also have to copy the body node so it is something we can modify.
      if (topCandidate === null || topCandidate.tagName === "BODY") {
        // Move all of the page's children into topCandidate
        topCandidate = doc.createElement("DIV");
        neededToCreateTopCandidate = true;
        // Move everything (not just elements, also text nodes etc.) into the container
        // so we even include text directly in the body:
        while (page.firstChild) {
          this.log("Moving child out:", page.firstChild);
          topCandidate.appendChild(page.firstChild);
        }

        page.appendChild(topCandidate);

        this._initializeNode(topCandidate);
      } else if (topCandidate) {
        // Find a better top candidate node if it contains (at least three) nodes which belong to `topCandidates` array
        // and whose scores are quite closed with current `topCandidate` node.
        var alternativeCandidateAncestors = [];
        for (var i = 1; i < topCandidates.length; i++) {
          if (topCandidates[i].readability.contentScore / topCandidate.readability.contentScore >= 0.75) {
            alternativeCandidateAncestors.push(this._getNodeAncestors(topCandidates[i]));
          }
        }
        var MINIMUM_TOPCANDIDATES = 3;
        if (alternativeCandidateAncestors.length >= MINIMUM_TOPCANDIDATES) {
          parentOfTopCandidate = topCandidate.parentNode;
          while (parentOfTopCandidate.tagName !== "BODY") {
            var listsContainingThisAncestor = 0;
            for (var ancestorIndex = 0; ancestorIndex < alternativeCandidateAncestors.length && listsContainingThisAncestor < MINIMUM_TOPCANDIDATES; ancestorIndex++) {
              listsContainingThisAncestor += Number(alternativeCandidateAncestors[ancestorIndex].includes(parentOfTopCandidate));
            }
            if (listsContainingThisAncestor >= MINIMUM_TOPCANDIDATES) {
              topCandidate = parentOfTopCandidate;
              break;
            }
            parentOfTopCandidate = parentOfTopCandidate.parentNode;
          }
        }
        if (!topCandidate.readability) {
          this._initializeNode(topCandidate);
        }

        // Because of our bonus system, parents of candidates might have scores
        // themselves. They get half of the node. There won't be nodes with higher
        // scores than our topCandidate, but if we see the score going *up* in the first
        // few steps up the tree, that's a decent sign that there might be more content
        // lurking in other places that we want to unify in. The sibling stuff
        // below does some of that - but only if we've looked high enough up the DOM
        // tree.
        parentOfTopCandidate = topCandidate.parentNode;
        var lastScore = topCandidate.readability.contentScore;
        // The scores shouldn't get too low.
        var scoreThreshold = lastScore / 3;
        while (parentOfTopCandidate.tagName !== "BODY") {
          if (!parentOfTopCandidate.readability) {
            parentOfTopCandidate = parentOfTopCandidate.parentNode;
            continue;
          }
          var parentScore = parentOfTopCandidate.readability.contentScore;
          if (parentScore < scoreThreshold)
            break;
          if (parentScore > lastScore) {
            // Alright! We found a better parent to use.
            topCandidate = parentOfTopCandidate;
            break;
          }
          lastScore = parentOfTopCandidate.readability.contentScore;
          parentOfTopCandidate = parentOfTopCandidate.parentNode;
        }

        // If the top candidate is the only child, use parent instead. This will help sibling
        // joining logic when adjacent content is actually located in parent's sibling node.
        parentOfTopCandidate = topCandidate.parentNode;
        while (parentOfTopCandidate.tagName != "BODY" && parentOfTopCandidate.children.length == 1) {
          topCandidate = parentOfTopCandidate;
          parentOfTopCandidate = topCandidate.parentNode;
        }
        if (!topCandidate.readability) {
          this._initializeNode(topCandidate);
        }
      }

      // Now that we have the top candidate, look through its siblings for content
      // that might also be related. Things like preambles, content split by ads
      // that we removed, etc.
      var articleContent = doc.createElement("DIV");
      if (isPaging)
        articleContent.id = "readability-content";

      var siblingScoreThreshold = Math.max(10, topCandidate.readability.contentScore * 0.2);
      // Keep potential top candidate's parent node to try to get text direction of it later.
      parentOfTopCandidate = topCandidate.parentNode;
      var siblings = parentOfTopCandidate.children;

      for (var s = 0, sl = siblings.length; s < sl; s++) {
        var sibling = siblings[s];
        var append = false;

        this.log("Looking at sibling node:", sibling, sibling.readability ? ("with score " + sibling.readability.contentScore) : "");
        this.log("Sibling has score", sibling.readability ? sibling.readability.contentScore : "Unknown");

        if (sibling === topCandidate) {
          append = true;
        } else {
          var contentBonus = 0;

          // Give a bonus if sibling nodes and top candidates have the example same classname
          if (sibling.className === topCandidate.className && topCandidate.className !== "")
            contentBonus += topCandidate.readability.contentScore * 0.2;

          if (sibling.readability &&
              ((sibling.readability.contentScore + contentBonus) >= siblingScoreThreshold)) {
            append = true;
          } else if (sibling.nodeName === "P") {
            var linkDensity = this._getLinkDensity(sibling);
            var nodeContent = this._getInnerText(sibling);
            var nodeLength = nodeContent.length;

            if (nodeLength > 80 && linkDensity < 0.25) {
              append = true;
            } else if (nodeLength < 80 && nodeLength > 0 && linkDensity === 0 &&
                       nodeContent.search(/\.( |$)/) !== -1) {
              append = true;
            }
          }
        }

        if (append) {
          this.log("Appending node:", sibling);

          if (this.ALTER_TO_DIV_EXCEPTIONS.indexOf(sibling.nodeName) === -1) {
            // We have a node that isn't a common block level element, like a form or td tag.
            // Turn it into a div so it doesn't get filtered out later by accident.
            this.log("Altering sibling:", sibling, "to div.");

            sibling = this._setNodeTag(sibling, "DIV");
          }

          articleContent.appendChild(sibling);
          // Fetch children again to make it compatible
          // with DOM parsers without live collection support.
          siblings = parentOfTopCandidate.children;
          // siblings is a reference to the children array, and
          // sibling is removed from the array when we call appendChild().
          // As a result, we must revisit this index since the nodes
          // have been shifted.
          s -= 1;
          sl -= 1;
        }
      }

      if (this._debug)
        this.log("Article content pre-prep: " + articleContent.innerHTML);
      // So we have all of the content that we need. Now we clean it up for presentation.
      this._prepArticle(articleContent);
      if (this._debug)
        this.log("Article content post-prep: " + articleContent.innerHTML);

      if (neededToCreateTopCandidate) {
        // We already created a fake div thing, and there wouldn't have been any siblings left
        // for the previous loop, so there's no point trying to create a new div, and then
        // move all the children over. Just assign IDs and class names here. No need to append
        // because that already happened anyway.
        topCandidate.id = "readability-page-1";
        topCandidate.className = "page";
      } else {
        var div = doc.createElement("DIV");
        div.id = "readability-page-1";
        div.className = "page";
        while (articleContent.firstChild) {
          div.appendChild(articleContent.firstChild);
        }
        articleContent.appendChild(div);
      }

      if (this._debug)
        this.log("Article content after paging: " + articleContent.innerHTML);

      var parseSuccessful = true;

      // Now that we've gone through the full algorithm, check to see if
      // we got any meaningful content. If we didn't, we may need to re-run
      // grabArticle with different flags set. This gives us a higher likelihood of
      // finding the content, and the sieve approach gives us a higher likelihood of
      // finding the -right- content.
      var textLength = this._getInnerText(articleContent, true).length;
      if (textLength < this._charThreshold) {
        parseSuccessful = false;
        page.innerHTML = pageCacheHtml;

        if (this._flagIsActive(this.FLAG_STRIP_UNLIKELYS)) {
          this._removeFlag(this.FLAG_STRIP_UNLIKELYS);
          this._attempts.push({articleContent: articleContent, textLength: textLength});
        } else if (this._flagIsActive(this.FLAG_WEIGHT_CLASSES)) {
          this._removeFlag(this.FLAG_WEIGHT_CLASSES);
          this._attempts.push({articleContent: articleContent, textLength: textLength});
        } else if (this._flagIsActive(this.FLAG_CLEAN_CONDITIONALLY)) {
          this._removeFlag(this.FLAG_CLEAN_CONDITIONALLY);
          this._attempts.push({articleContent: articleContent, textLength: textLength});
        } else {
          this._attempts.push({articleContent: articleContent, textLength: textLength});
          // No luck after removing flags, just return the longest text we found during the different loops
          this._attempts.sort(function (a, b) {
            return b.textLength - a.textLength;
          });

          // But first check if we actually have something
          if (!this._attempts[0].textLength) {
            return null;
          }

          articleContent = this._attempts[0].articleContent;
          parseSuccessful = true;
        }
      }

      if (parseSuccessful) {
        // Find out text direction from ancestors of final top candidate.
        var ancestors = [parentOfTopCandidate, topCandidate].concat(this._getNodeAncestors(parentOfTopCandidate));
        this._someNode(ancestors, function(ancestor) {
          if (!ancestor.tagName)
            return false;
          var articleDir = ancestor.getAttribute("dir");
          if (articleDir) {
            this._articleDir = articleDir;
            return true;
          }
          return false;
        });
        return articleContent;
      }
    }
  },

  /**
   * Check whether the input string could be a byline.
   * This verifies that the input is a string, and that the length
   * is less than 100 chars.
   *
   * @param possibleByline {string} - a string to check whether its a byline.
   * @return Boolean - whether the input string is a byline.
   */
  _isValidByline: function(byline) {
    if (typeof byline == "string" || byline instanceof String) {
      byline = byline.trim();
      return (byline.length > 0) && (byline.length < 100);
    }
    return false;
  },

  /**
   * Converts some of the common HTML entities in string to their corresponding characters.
   *
   * @param str {string} - a string to unescape.
   * @return string without HTML entity.
   */
  _unescapeHtmlEntities: function(str) {
    if (!str) {
      return str;
    }

    var htmlEscapeMap = this.HTML_ESCAPE_MAP;
    return str.replace(/&(quot|amp|apos|lt|gt);/g, function(_, tag) {
      return htmlEscapeMap[tag];
    }).replace(/&#(?:x([0-9a-z]{1,4})|([0-9]{1,4}));/gi, function(_, hex, numStr) {
      var num = parseInt(hex || numStr, hex ? 16 : 10);
      return String.fromCharCode(num);
    });
  },

  /**
   * Try to extract metadata from JSON-LD object.
   * For now, only Schema.org objects of type Article or its subtypes are supported.
   * @return Object with any metadata that could be extracted (possibly none)
   */
  _getJSONLD: function (doc) {
    var scripts = this._getAllNodesWithTag(doc, ["script"]);

    var metadata;

    this._forEachNode(scripts, function(jsonLdElement) {
      if (!metadata && jsonLdElement.getAttribute("type") === "application/ld+json") {
        try {
          // Strip CDATA markers if present
          var content = jsonLdElement.textContent.replace(/^\s*<!\[CDATA\[|\]\]>\s*$/g, "");
          var parsed = JSON.parse(content);
          if (
            !parsed["@context"] ||
            !parsed["@context"].match(/^https?\:\/\/schema\.org$/)
          ) {
            return;
          }

          if (!parsed["@type"] && Array.isArray(parsed["@graph"])) {
            parsed = parsed["@graph"].find(function(it) {
              return (it["@type"] || "").match(
                this.REGEXPS.jsonLdArticleTypes
              );
            });
          }

          if (
            !parsed ||
            !parsed["@type"] ||
            !parsed["@type"].match(this.REGEXPS.jsonLdArticleTypes)
          ) {
            return;
          }

          metadata = {};

          if (typeof parsed.name === "string" && typeof parsed.headline === "string" && parsed.name !== parsed.headline) {
            // we have both name and headline element in the JSON-LD. They should both be the same but some websites like aktualne.cz
            // put their own name into "name" and the article title to "headline" which confuses Readability. So we try to check if either
            // "name" or "headline" closely matches the html title, and if so, use that one. If not, then we use "name" by default.

            var title = this._getArticleTitle();
            var nameMatches = this._textSimilarity(parsed.name, title) > 0.75;
            var headlineMatches = this._textSimilarity(parsed.headline, title) > 0.75;

            if (headlineMatches && !nameMatches) {
              metadata.title = parsed.headline;
            } else {
              metadata.title = parsed.name;
            }
          } else if (typeof parsed.name === "string") {
            metadata.title = parsed.name.trim();
          } else if (typeof parsed.headline === "string") {
            metadata.title = parsed.headline.trim();
          }
          if (parsed.author) {
            if (typeof parsed.author.name === "string") {
              metadata.byline = parsed.author.name.trim();
            } else if (Array.isArray(parsed.author) && parsed.author[0] && typeof parsed.author[0].name === "string") {
              metadata.byline = parsed.author
                .filter(function(author) {
                  return author && typeof author.name === "string";
                })
                .map(function(author) {
                  return author.name.trim();
                })
                .join(", ");
            }
          }
          if (typeof parsed.description === "string") {
            metadata.excerpt = parsed.description.trim();
          }
          if (
            parsed.publisher &&
            typeof parsed.publisher.name === "string"
          ) {
            metadata.siteName = parsed.publisher.name.trim();
          }
          if (typeof parsed.datePublished === "string") {
            metadata.datePublished = parsed.datePublished.trim();
          }
          return;
        } catch (err) {
          this.log(err.message);
        }
      }
    });
    return metadata ? metadata : {};
  },

  /**
   * Attempts to get excerpt and byline metadata for the article.
   *
   * @param {Object} jsonld — object containing any metadata that
   * could be extracted from JSON-LD object.
   *
   * @return Object with optional "excerpt" and "byline" properties
   */
  _getArticleMetadata: function(jsonld) {
    var metadata = {};
    var values = {};
    var metaElements = this._doc.getElementsByTagName("meta");

    // property is a space-separated list of values
    var propertyPattern = /\s*(article|dc|dcterm|og|twitter)\s*:\s*(author|creator|description|published_time|title|site_name)\s*/gi;

    // name is a single value
    var namePattern = /^\s*(?:(dc|dcterm|og|twitter|weibo:(article|webpage))\s*[\.:]\s*)?(author|creator|description|title|site_name)\s*$/i;

    // Find description tags.
    this._forEachNode(metaElements, function(element) {
      var elementName = element.getAttribute("name");
      var elementProperty = element.getAttribute("property");
      var content = element.getAttribute("content");
      if (!content) {
        return;
      }
      var matches = null;
      var name = null;

      if (elementProperty) {
        matches = elementProperty.match(propertyPattern);
        if (matches) {
          // Convert to lowercase, and remove any whitespace
          // so we can match below.
          name = matches[0].toLowerCase().replace(/\s/g, "");
          // multiple authors
          values[name] = content.trim();
        }
      }
      if (!matches && elementName && namePattern.test(elementName)) {
        name = elementName;
        if (content) {
          // Convert to lowercase, remove any whitespace, and convert dots
          // to colons so we can match below.
          name = name.toLowerCase().replace(/\s/g, "").replace(/\./g, ":");
          values[name] = content.trim();
        }
      }
    });

    // get title
    metadata.title = jsonld.title ||
                     values["dc:title"] ||
                     values["dcterm:title"] ||
                     values["og:title"] ||
                     values["weibo:article:title"] ||
                     values["weibo:webpage:title"] ||
                     values["title"] ||
                     values["twitter:title"];

    if (!metadata.title) {
      metadata.title = this._getArticleTitle();
    }

    // get author
    metadata.byline = jsonld.byline ||
                      values["dc:creator"] ||
                      values["dcterm:creator"] ||
                      values["author"];

    // get description
    metadata.excerpt = jsonld.excerpt ||
                       values["dc:description"] ||
                       values["dcterm:description"] ||
                       values["og:description"] ||
                       values["weibo:article:description"] ||
                       values["weibo:webpage:description"] ||
                       values["description"] ||
                       values["twitter:description"];

    // get site name
    metadata.siteName = jsonld.siteName ||
                        values["og:site_name"];

    // get article published time
    metadata.publishedTime = jsonld.datePublished ||
      values["article:published_time"] || null;

    // in many sites the meta value is escaped with HTML entities,
    // so here we need to unescape it
    metadata.title = this._unescapeHtmlEntities(metadata.title);
    metadata.byline = this._unescapeHtmlEntities(metadata.byline);
    metadata.excerpt = this._unescapeHtmlEntities(metadata.excerpt);
    metadata.siteName = this._unescapeHtmlEntities(metadata.siteName);
    metadata.publishedTime = this._unescapeHtmlEntities(metadata.publishedTime);

    return metadata;
  },

  /**
   * Check if node is image, or if node contains exactly only one image
   * whether as a direct child or as its descendants.
   *
   * @param Element
  **/
  _isSingleImage: function(node) {
    if (node.tagName === "IMG") {
      return true;
    }

    if (node.children.length !== 1 || node.textContent.trim() !== "") {
      return false;
    }

    return this._isSingleImage(node.children[0]);
  },

  /**
   * Find all <noscript> that are located after <img> nodes, and which contain only one
   * <img> element. Replace the first image with the image from inside the <noscript> tag,
   * and remove the <noscript> tag. This improves the quality of the images we use on
   * some sites (e.g. Medium).
   *
   * @param Element
  **/
  _unwrapNoscriptImages: function(doc) {
    // Find img without source or attributes that might contains image, and remove it.
    // This is done to prevent a placeholder img is replaced by img from noscript in next step.
    var imgs = Array.from(doc.getElementsByTagName("img"));
    this._forEachNode(imgs, function(img) {
      for (var i = 0; i < img.attributes.length; i++) {
        var attr = img.attributes[i];
        switch (attr.name) {
          case "src":
          case "srcset":
          case "data-src":
          case "data-srcset":
            return;
        }

        if (/\.(jpg|jpeg|png|webp)/i.test(attr.value)) {
          return;
        }
      }

      img.parentNode.removeChild(img);
    });

    // Next find noscript and try to extract its image
    var noscripts = Array.from(doc.getElementsByTagName("noscript"));
    this._forEachNode(noscripts, function(noscript) {
      // Parse content of noscript and make sure it only contains image
      var tmp = doc.createElement("div");
      tmp.innerHTML = noscript.innerHTML;
      if (!this._isSingleImage(tmp)) {
        return;
      }

      // If noscript has previous sibling and it only contains image,
      // replace it with noscript content. However we also keep old
      // attributes that might contains image.
      var prevElement = noscript.previousElementSibling;
      if (prevElement && this._isSingleImage(prevElement)) {
        var prevImg = prevElement;
        if (prevImg.tagName !== "IMG") {
          prevImg = prevElement.getElementsByTagName("img")[0];
        }

        var newImg = tmp.getElementsByTagName("img")[0];
        for (var i = 0; i < prevImg.attributes.length; i++) {
          var attr = prevImg.attributes[i];
          if (attr.value === "") {
            continue;
          }

          if (attr.name === "src" || attr.name === "srcset" || /\.(jpg|jpeg|png|webp)/i.test(attr.value)) {
            if (newImg.getAttribute(attr.name) === attr.value) {
              continue;
            }

            var attrName = attr.name;
            if (newImg.hasAttribute(attrName)) {
              attrName = "data-old-" + attrName;
            }

            newImg.setAttribute(attrName, attr.value);
          }
        }

        noscript.parentNode.replaceChild(tmp.firstElementChild, prevElement);
      }
    });
  },

  /**
   * Removes script tags from the document.
   *
   * @param Element
  **/
  _removeScripts: function(doc) {
    this._removeNodes(this._getAllNodesWithTag(doc, ["script", "noscript"]));
  },

  /**
   * Check if this node has only whitespace and a single element with given tag
   * Returns false if the DIV node contains non-empty text nodes
   * or if it contains no element with given tag or more than 1 element.
   *
   * @param Element
   * @param string tag of child element
  **/
  _hasSingleTagInsideElement: function(element, tag) {
    // There should be exactly 1 element child with given tag
    if (element.children.length != 1 || element.children[0].tagName !== tag) {
      return false;
    }

    // And there should be no text nodes with real content
    return !this._someNode(element.childNodes, function(node) {
      return node.nodeType === this.TEXT_NODE &&
             this.REGEXPS.hasContent.test(node.textContent);
    });
  },

  _isElementWithoutContent: function(node) {
    return node.nodeType === this.ELEMENT_NODE &&
      node.textContent.trim().length == 0 &&
      (node.children.length == 0 ||
       node.children.length == node.getElementsByTagName("br").length + node.getElementsByTagName("hr").length);
  },

  /**
   * Determine whether element has any children block level elements.
   *
   * @param Element
   */
  _hasChildBlockElement: function (element) {
    return this._someNode(element.childNodes, function(node) {
      return this.DIV_TO_P_ELEMS.has(node.tagName) ||
             this._hasChildBlockElement(node);
    });
  },

  /***
   * Determine if a node qualifies as phrasing content.
   * https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories#Phrasing_content
  **/
  _isPhrasingContent: function(node) {
    return node.nodeType === this.TEXT_NODE || this.PHRASING_ELEMS.indexOf(node.tagName) !== -1 ||
      ((node.tagName === "A" || node.tagName === "DEL" || node.tagName === "INS") &&
        this._everyNode(node.childNodes, this._isPhrasingContent));
  },

  _isWhitespace: function(node) {
    return (node.nodeType === this.TEXT_NODE && node.textContent.trim().length === 0) ||
           (node.nodeType === this.ELEMENT_NODE && node.tagName === "BR");
  },

  /**
   * Get the inner text of a node - cross browser compatibly.
   * This also strips out any excess whitespace to be found.
   *
   * @param Element
   * @param Boolean normalizeSpaces (default: true)
   * @return string
  **/
  _getInnerText: function(e, normalizeSpaces) {
    normalizeSpaces = (typeof normalizeSpaces === "undefined") ? true : normalizeSpaces;
    var textContent = e.textContent.trim();

    if (normalizeSpaces) {
      return textContent.replace(this.REGEXPS.normalize, " ");
    }
    return textContent;
  },

  /**
   * Get the number of times a string s appears in the node e.
   *
   * @param Element
   * @param string - what to split on. Default is ","
   * @return number (integer)
  **/
  _getCharCount: function(e, s) {
    s = s || ",";
    return this._getInnerText(e).split(s).length - 1;
  },

  /**
   * Remove the style attribute on every e and under.
   * TODO: Test if getElementsByTagName(*) is faster.
   *
   * @param Element
   * @return void
  **/
  _cleanStyles: function(e) {
    if (!e || e.tagName.toLowerCase() === "svg")
      return;

    // Remove `style` and deprecated presentational attributes
    for (var i = 0; i < this.PRESENTATIONAL_ATTRIBUTES.length; i++) {
      e.removeAttribute(this.PRESENTATIONAL_ATTRIBUTES[i]);
    }

    if (this.DEPRECATED_SIZE_ATTRIBUTE_ELEMS.indexOf(e.tagName) !== -1) {
      e.removeAttribute("width");
      e.removeAttribute("height");
    }

    var cur = e.firstElementChild;
    while (cur !== null) {
      this._cleanStyles(cur);
      cur = cur.nextElementSibling;
    }
  },

  /**
   * Get the density of links as a percentage of the content
   * This is the amount of text that is inside a link divided by the total text in the node.
   *
   * @param Element
   * @return number (float)
  **/
  _getLinkDensity: function(element) {
    var textLength = this._getInnerText(element).length;
    if (textLength === 0)
      return 0;

    var linkLength = 0;

    // XXX implement _reduceNodeList?
    this._forEachNode(element.getElementsByTagName("a"), function(linkNode) {
      var href = linkNode.getAttribute("href");
      var coefficient = href && this.REGEXPS.hashUrl.test(href) ? 0.3 : 1;
      linkLength += this._getInnerText(linkNode).length * coefficient;
    });

    return linkLength / textLength;
  },

  /**
   * Get an elements class/id weight. Uses regular expressions to tell if this
   * element looks good or bad.
   *
   * @param Element
   * @return number (Integer)
  **/
  _getClassWeight: function(e) {
    if (!this._flagIsActive(this.FLAG_WEIGHT_CLASSES))
      return 0;

    var weight = 0;

    // Look for a special classname
    if (typeof(e.className) === "string" && e.className !== "") {
      if (this.REGEXPS.negative.test(e.className))
        weight -= 25;

      if (this.REGEXPS.positive.test(e.className))
        weight += 25;
    }

    // Look for a special ID
    if (typeof(e.id) === "string" && e.id !== "") {
      if (this.REGEXPS.negative.test(e.id))
        weight -= 25;

      if (this.REGEXPS.positive.test(e.id))
        weight += 25;
    }

    return weight;
  },

  /**
   * Clean a node of all elements of type "tag".
   * (Unless it's a youtube/vimeo video. People love movies.)
   *
   * @param Element
   * @param string tag to clean
   * @return void
   **/
  _clean: function(e, tag) {
    var isEmbed = ["object", "embed", "iframe"].indexOf(tag) !== -1;

    this._removeNodes(this._getAllNodesWithTag(e, [tag]), function(element) {
      // Allow youtube and vimeo videos through as people usually want to see those.
      if (isEmbed) {
        // First, check the elements attributes to see if any of them contain youtube or vimeo
        for (var i = 0; i < element.attributes.length; i++) {
          if (this._allowedVideoRegex.test(element.attributes[i].value)) {
            return false;
          }
        }

        // For embed with <object> tag, check inner HTML as well.
        if (element.tagName === "object" && this._allowedVideoRegex.test(element.innerHTML)) {
          return false;
        }
      }

      return true;
    });
  },

  /**
   * Check if a given node has one of its ancestor tag name matching the
   * provided one.
   * @param  HTMLElement node
   * @param  String      tagName
   * @param  Number      maxDepth
   * @param  Function    filterFn a filter to invoke to determine whether this node 'counts'
   * @return Boolean
   */
  _hasAncestorTag: function(node, tagName, maxDepth, filterFn) {
    maxDepth = maxDepth || 3;
    tagName = tagName.toUpperCase();
    var depth = 0;
    while (node.parentNode) {
      if (maxDepth > 0 && depth > maxDepth)
        return false;
      if (node.parentNode.tagName === tagName && (!filterFn || filterFn(node.parentNode)))
        return true;
      node = node.parentNode;
      depth++;
    }
    return false;
  },

  /**
   * Return an object indicating how many rows and columns this table has.
   */
  _getRowAndColumnCount: function(table) {
    var rows = 0;
    var columns = 0;
    var trs = table.getElementsByTagName("tr");
    for (var i = 0; i < trs.length; i++) {
      var rowspan = trs[i].getAttribute("rowspan") || 0;
      if (rowspan) {
        rowspan = parseInt(rowspan, 10);
      }
      rows += (rowspan || 1);

      // Now look for column-related info
      var columnsInThisRow = 0;
      var cells = trs[i].getElementsByTagName("td");
      for (var j = 0; j < cells.length; j++) {
        var colspan = cells[j].getAttribute("colspan") || 0;
        if (colspan) {
          colspan = parseInt(colspan, 10);
        }
        columnsInThisRow += (colspan || 1);
      }
      columns = Math.max(columns, columnsInThisRow);
    }
    return {rows: rows, columns: columns};
  },

  /**
   * Look for 'data' (as opposed to 'layout') tables, for which we use
   * similar checks as
   * https://searchfox.org/mozilla-central/rev/f82d5c549f046cb64ce5602bfd894b7ae807c8f8/accessible/generic/TableAccessible.cpp#19
   */
  _markDataTables: function(root) {
    var tables = root.getElementsByTagName("table");
    for (var i = 0; i < tables.length; i++) {
      var table = tables[i];
      var role = table.getAttribute("role");
      if (role == "presentation") {
        table._readabilityDataTable = false;
        continue;
      }
      var datatable = table.getAttribute("datatable");
      if (datatable == "0") {
        table._readabilityDataTable = false;
        continue;
      }
      var summary = table.getAttribute("summary");
      if (summary) {
        table._readabilityDataTable = true;
        continue;
      }

      var caption = table.getElementsByTagName("caption")[0];
      if (caption && caption.childNodes.length > 0) {
        table._readabilityDataTable = true;
        continue;
      }

      // If the table has a descendant with any of these tags, consider a data table:
      var dataTableDescendants = ["col", "colgroup", "tfoot", "thead", "th"];
      var descendantExists = function(tag) {
        return !!table.getElementsByTagName(tag)[0];
      };
      if (dataTableDescendants.some(descendantExists)) {
        this.log("Data table because found data-y descendant");
        table._readabilityDataTable = true;
        continue;
      }

      // Nested tables indicate a layout table:
      if (table.getElementsByTagName("table")[0]) {
        table._readabilityDataTable = false;
        continue;
      }

      var sizeInfo = this._getRowAndColumnCount(table);
      if (sizeInfo.rows >= 10 || sizeInfo.columns > 4) {
        table._readabilityDataTable = true;
        continue;
      }
      // Now just go by size entirely:
      table._readabilityDataTable = sizeInfo.rows * sizeInfo.columns > 10;
    }
  },

  /* convert images and figures that have properties like data-src into images that can be loaded without JS */
  _fixLazyImages: function (root) {
    this._forEachNode(this._getAllNodesWithTag(root, ["img", "picture", "figure"]), function (elem) {
      // In some sites (e.g. Kotaku), they put 1px square image as base64 data uri in the src attribute.
      // So, here we check if the data uri is too short, just might as well remove it.
      if (elem.src && this.REGEXPS.b64DataUrl.test(elem.src)) {
        // Make sure it's not SVG, because SVG can have a meaningful image in under 133 bytes.
        var parts = this.REGEXPS.b64DataUrl.exec(elem.src);
        if (parts[1] === "image/svg+xml") {
          return;
        }

        // Make sure this element has other attributes which contains image.
        // If it doesn't, then this src is important and shouldn't be removed.
        var srcCouldBeRemoved = false;
        for (var i = 0; i < elem.attributes.length; i++) {
          var attr = elem.attributes[i];
          if (attr.name === "src") {
            continue;
          }

          if (/\.(jpg|jpeg|png|webp)/i.test(attr.value)) {
            srcCouldBeRemoved = true;
            break;
          }
        }

        // Here we assume if image is less than 100 bytes (or 133B after encoded to base64)
        // it will be too small, therefore it might be placeholder image.
        if (srcCouldBeRemoved) {
          var b64starts = elem.src.search(/base64\s*/i) + 7;
          var b64length = elem.src.length - b64starts;
          if (b64length < 133) {
            elem.removeAttribute("src");
          }
        }
      }

      // also check for "null" to work around https://github.com/jsdom/jsdom/issues/2580
      if ((elem.src || (elem.srcset && elem.srcset != "null")) && elem.className.toLowerCase().indexOf("lazy") === -1) {
        return;
      }

      for (var j = 0; j < elem.attributes.length; j++) {
        attr = elem.attributes[j];
        if (attr.name === "src" || attr.name === "srcset" || attr.name === "alt") {
          continue;
        }
        var copyTo = null;
        if (/\.(jpg|jpeg|png|webp)\s+\d/.test(attr.value)) {
          copyTo = "srcset";
        } else if (/^\s*\S+\.(jpg|jpeg|png|webp)\S*\s*$/.test(attr.value)) {
          copyTo = "src";
        }
        if (copyTo) {
          //if this is an img or picture, set the attribute directly
          if (elem.tagName === "IMG" || elem.tagName === "PICTURE") {
            elem.setAttribute(copyTo, attr.value);
          } else if (elem.tagName === "FIGURE" && !this._getAllNodesWithTag(elem, ["img", "picture"]).length) {
            //if the item is a <figure> that does not contain an image or picture, create one and place it inside the figure
            //see the nytimes-3 testcase for an example
            var img = this._doc.createElement("img");
            img.setAttribute(copyTo, attr.value);
            elem.appendChild(img);
          }
        }
      }
    });
  },

  _getTextDensity: function(e, tags) {
    var textLength = this._getInnerText(e, true).length;
    if (textLength === 0) {
      return 0;
    }
    var childrenLength = 0;
    var children = this._getAllNodesWithTag(e, tags);
    this._forEachNode(children, (child) => childrenLength += this._getInnerText(child, true).length);
    return childrenLength / textLength;
  },

  /**
   * Clean an element of all tags of type "tag" if they look fishy.
   * "Fishy" is an algorithm based on content length, classnames, link density, number of images & embeds, etc.
   *
   * @return void
   **/
  _cleanConditionally: function(e, tag) {
    if (!this._flagIsActive(this.FLAG_CLEAN_CONDITIONALLY))
      return;

    // Gather counts for other typical elements embedded within.
    // Traverse backwards so we can remove nodes at the same time
    // without effecting the traversal.
    //
    // TODO: Consider taking into account original contentScore here.
    this._removeNodes(this._getAllNodesWithTag(e, [tag]), function(node) {
      // First check if this node IS data table, in which case don't remove it.
      var isDataTable = function(t) {
        return t._readabilityDataTable;
      };

      var isList = tag === "ul" || tag === "ol";
      if (!isList) {
        var listLength = 0;
        var listNodes = this._getAllNodesWithTag(node, ["ul", "ol"]);
        this._forEachNode(listNodes, (list) => listLength += this._getInnerText(list).length);
        isList = listLength / this._getInnerText(node).length > 0.9;
      }

      if (tag === "table" && isDataTable(node)) {
        return false;
      }

      // Next check if we're inside a data table, in which case don't remove it as well.
      if (this._hasAncestorTag(node, "table", -1, isDataTable)) {
        return false;
      }

      if (this._hasAncestorTag(node, "code")) {
        return false;
      }

      var weight = this._getClassWeight(node);

      this.log("Cleaning Conditionally", node);

      var contentScore = 0;

      if (weight + contentScore < 0) {
        return true;
      }

      if (this._getCharCount(node, ",") < 10) {
        // If there are not very many commas, and the number of
        // non-paragraph elements is more than paragraphs or other
        // ominous signs, remove the element.
        var p = node.getElementsByTagName("p").length;
        var img = node.getElementsByTagName("img").length;
        var li = node.getElementsByTagName("li").length - 100;
        var input = node.getElementsByTagName("input").length;
        var headingDensity = this._getTextDensity(node, ["h1", "h2", "h3", "h4", "h5", "h6"]);

        var embedCount = 0;
        var embeds = this._getAllNodesWithTag(node, ["object", "embed", "iframe"]);

        for (var i = 0; i < embeds.length; i++) {
          // If this embed has attribute that matches video regex, don't delete it.
          for (var j = 0; j < embeds[i].attributes.length; j++) {
            if (this._allowedVideoRegex.test(embeds[i].attributes[j].value)) {
              return false;
            }
          }

          // For embed with <object> tag, check inner HTML as well.
          if (embeds[i].tagName === "object" && this._allowedVideoRegex.test(embeds[i].innerHTML)) {
            return false;
          }

          embedCount++;
        }

        var linkDensity = this._getLinkDensity(node);
        var contentLength = this._getInnerText(node).length;

        var haveToRemove =
          (img > 1 && p / img < 0.5 && !this._hasAncestorTag(node, "figure")) ||
          (!isList && li > p) ||
          (input > Math.floor(p/3)) ||
          (!isList && headingDensity < 0.9 && contentLength < 25 && (img === 0 || img > 2) && !this._hasAncestorTag(node, "figure")) ||
          (!isList && weight < 25 && linkDensity > 0.2) ||
          (weight >= 25 && linkDensity > 0.5) ||
          ((embedCount === 1 && contentLength < 75) || embedCount > 1);
        // Allow simple lists of images to remain in pages
        if (isList && haveToRemove) {
          for (var x = 0; x < node.children.length; x++) {
            let child = node.children[x];
            // Don't filter in lists with li's that contain more than one child
            if (child.children.length > 1) {
              return haveToRemove;
            }
          }
          let li_count = node.getElementsByTagName("li").length;
          // Only allow the list to remain if every li contains an image
          if (img == li_count) {
            return false;
          }
        }
        return haveToRemove;
      }
      return false;
    });
  },

  /**
   * Clean out elements that match the specified conditions
   *
   * @param Element
   * @param Function determines whether a node should be removed
   * @return void
   **/
  _cleanMatchedNodes: function(e, filter) {
    var endOfSearchMarkerNode = this._getNextNode(e, true);
    var next = this._getNextNode(e);
    while (next && next != endOfSearchMarkerNode) {
      if (filter.call(this, next, next.className + " " + next.id)) {
        next = this._removeAndGetNext(next);
      } else {
        next = this._getNextNode(next);
      }
    }
  },

  /**
   * Clean out spurious headers from an Element.
   *
   * @param Element
   * @return void
  **/
  _cleanHeaders: function(e) {
    let headingNodes = this._getAllNodesWithTag(e, ["h1", "h2"]);
    this._removeNodes(headingNodes, function(node) {
      let shouldRemove = this._getClassWeight(node) < 0;
      if (shouldRemove) {
        this.log("Removing header with low class weight:", node);
      }
      return shouldRemove;
    });
  },

  /**
   * Check if this node is an H1 or H2 element whose content is mostly
   * the same as the article title.
   *
   * @param Element  the node to check.
   * @return boolean indicating whether this is a title-like header.
   */
  _headerDuplicatesTitle: function(node) {
    if (node.tagName != "H1" && node.tagName != "H2") {
      return false;
    }
    var heading = this._getInnerText(node, false);
    this.log("Evaluating similarity of header:", heading, this._articleTitle);
    return this._textSimilarity(this._articleTitle, heading) > 0.75;
  },

  _flagIsActive: function(flag) {
    return (this._flags & flag) > 0;
  },

  _removeFlag: function(flag) {
    this._flags = this._flags & ~flag;
  },

  _isProbablyVisible: function(node) {
    // Have to null-check node.style and node.className.indexOf to deal with SVG and MathML nodes.
    return (!node.style || node.style.display != "none")
      && (!node.style || node.style.visibility != "hidden")
      && !node.hasAttribute("hidden")
      //check for "fallback-image" so that wikimedia math images are displayed
      && (!node.hasAttribute("aria-hidden") || node.getAttribute("aria-hidden") != "true" || (node.className && node.className.indexOf && node.className.indexOf("fallback-image") !== -1));
  },

  /**
   * Runs readability.
   *
   * Workflow:
   *  1. Prep the document by removing script tags, css, etc.
   *  2. Build readability's DOM tree.
   *  3. Grab the article content from the current dom tree.
   *  4. Replace the current DOM tree with the new one.
   *  5. Read peacefully.
   *
   * @return void
   **/
  parse: function () {
    // Avoid parsing too large documents, as per configuration option
    if (this._maxElemsToParse > 0) {
      var numTags = this._doc.getElementsByTagName("*").length;
      if (numTags > this._maxElemsToParse) {
        throw new Error("Aborting parsing document; " + numTags + " elements found");
      }
    }

    // Unwrap image from noscript
    this._unwrapNoscriptImages(this._doc);

    // Extract JSON-LD metadata before removing scripts
    var jsonLd = this._disableJSONLD ? {} : this._getJSONLD(this._doc);

    // Remove script tags from the document.
    this._removeScripts(this._doc);

    this._prepDocument();

    var metadata = this._getArticleMetadata(jsonLd);
    this._articleTitle = metadata.title;

    var articleContent = this._grabArticle();
    if (!articleContent)
      return null;

    this.log("Grabbed: " + articleContent.innerHTML);

    this._postProcessContent(articleContent);

    // If we haven't found an excerpt in the article's metadata, use the article's
    // first paragraph as the excerpt. This is used for displaying a preview of
    // the article's content.
    if (!metadata.excerpt) {
      var paragraphs = articleContent.getElementsByTagName("p");
      if (paragraphs.length > 0) {
        metadata.excerpt = paragraphs[0].textContent.trim();
      }
    }

    var textContent = articleContent.textContent;
    return {
      title: this._articleTitle,
      byline: metadata.byline || this._articleByline,
      dir: this._articleDir,
      lang: this._articleLang,
      content: this._serializer(articleContent),
      textContent: textContent,
      length: textContent.length,
      excerpt: metadata.excerpt,
      siteName: metadata.siteName || this._articleSiteName,
      publishedTime: metadata.publishedTime
    };
  }
};

if (typeof module === "object") {
  /* global module */
  module.exports = Readability;
}
PK
!<o���modules/reader/Reader.worker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * A worker dedicated to handle parsing documents for reader view.
 */

/* import-globals-from /toolkit/components/workerloader/require.js */
/* global JSDOMParser */
/* import-globals-from /toolkit/components/reader/readability/Readability.js */
importScripts(
  "resource://gre/modules/workers/require.js",
  "resource://gre/modules/reader/JSDOMParser.js",
  "resource://gre/modules/reader/Readability.js"
);

var PromiseWorker = require("resource://gre/modules/workers/PromiseWorker.js");

const DEBUG = false;

var worker = new PromiseWorker.AbstractWorker();
worker.dispatch = function (method, args = []) {
  return Agent[method](...args);
};
worker.postMessage = function (result, ...transfers) {
  self.postMessage(result, ...transfers);
};
worker.close = function () {
  self.close();
};
worker.log = function (...args) {
  if (DEBUG) {
    dump("ReaderWorker: " + args.join(" ") + "\n");
  }
};

self.addEventListener("message", msg => worker.handleMessage(msg));
self.addEventListener("unhandledrejection", function (error) {
  throw error.reason;
});

var Agent = {
  /**
   * Parses structured article data from a document.
   *
   * @param {object} uri URI data for the document.
   * @param {string} serializedDoc The serialized document.
   * @param {object} options Options object to pass to Readability.
   *
   * @return {object} Article object returned from Readability.
   */
  parseDocument(uri, serializedDoc, options) {
    let doc = new JSDOMParser().parse(serializedDoc, uri.spec);
    return new Readability(doc, options).parse();
  },
};
PK
!<�!���#modules/reader/ReaderWorker.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Interface to a dedicated thread handling readability parsing.
 */

import { BasePromiseWorker } from "resource://gre/modules/PromiseWorker.sys.mjs";

export var ReaderWorker = new BasePromiseWorker(
  "resource://gre/modules/reader/Reader.worker.js"
);
PK
!<�s˔modules/reflect.sys.mjs/* -*-  indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This is the js module for Reflect. Import it like so:
 *   const { Reflect } = ChromeUtils.importESModule(
 *     "resource://gre/modules/reflect.sys.mjs"
 *   );
 *
 * This will create a 'Reflect' object, which provides an interface to the
 * SpiderMonkey parser API.
 */

// Initialize the Reflect object. You do not need to do this yourself.
const init = Cc["@mozilla.org/jsreflect;1"].createInstance();
init();

// Reflect is a standard built-in defined on the shared global.
export const Reflect = globalThis.Reflect;
PK
!<��0�0 0 %modules/services-common/async.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const Timer = Components.Constructor("@mozilla.org/timer;1", "nsITimer");

/*
 * Helpers for various async operations.
 */
export var Async = {
  /**
   * Execute an arbitrary number of asynchronous functions one after the
   * other, passing the callback arguments on to the next one.  All functions
   * must take a callback function as their last argument.  The 'this' object
   * will be whatever chain()'s is.
   *
   * @usage this._chain = Async.chain;
   *        this._chain(this.foo, this.bar, this.baz)(args, for, foo)
   *
   * This is equivalent to:
   *
   *   let self = this;
   *   self.foo(args, for, foo, function (bars, args) {
   *     self.bar(bars, args, function (baz, params) {
   *       self.baz(baz, params);
   *     });
   *   });
   */
  chain: function chain(...funcs) {
    let thisObj = this;
    return function callback() {
      if (funcs.length) {
        let args = [...arguments, callback];
        let f = funcs.shift();
        f.apply(thisObj, args);
      }
    };
  },

  /**
   * Check if the app is still ready (not quitting). Returns true, or throws an
   * exception if not ready.
   */
  checkAppReady: function checkAppReady() {
    // Watch for app-quit notification to stop any sync calls
    Services.obs.addObserver(function onQuitApplication() {
      Services.obs.removeObserver(onQuitApplication, "quit-application");
      Async.checkAppReady = Async.promiseYield = function () {
        let exception = Components.Exception(
          "App. Quitting",
          Cr.NS_ERROR_ABORT
        );
        exception.appIsShuttingDown = true;
        throw exception;
      };
    }, "quit-application");
    // In the common case, checkAppReady just returns true
    return (Async.checkAppReady = function () {
      return true;
    })();
  },

  /**
   * Check if the app is still ready (not quitting). Returns true if the app
   * is ready, or false if it is being shut down.
   */
  isAppReady() {
    try {
      return Async.checkAppReady();
    } catch (ex) {
      if (!Async.isShutdownException(ex)) {
        throw ex;
      }
    }
    return false;
  },

  /**
   * Check if the passed exception is one raised by checkAppReady. Typically
   * this will be used in exception handlers to allow such exceptions to
   * make their way to the top frame and allow the app to actually terminate.
   */
  isShutdownException(exception) {
    return exception && exception.appIsShuttingDown === true;
  },

  /**
   * A "tight loop" of promises can still lock up the browser for some time.
   * Periodically waiting for a promise returned by this function will solve
   * that.
   * You should probably not use this method directly and instead use jankYielder
   * below.
   * Some reference here:
   * - https://gist.github.com/jesstelford/bbb30b983bddaa6e5fef2eb867d37678
   * - https://bugzilla.mozilla.org/show_bug.cgi?id=1094248
   */
  promiseYield() {
    return new Promise(resolve => {
      Services.tm.currentThread.dispatch(resolve, Ci.nsIThread.DISPATCH_NORMAL);
    });
  },

  /**
   * Shared state for yielding every N calls.
   *
   * Can be passed to multiple Async.yieldingForEach to have them overall yield
   * every N iterations.
   */
  yieldState(yieldEvery = 50) {
    let iterations = 0;

    return {
      shouldYield() {
        ++iterations;
        return iterations % yieldEvery === 0;
      },
    };
  },

  /**
   * Apply the given function to each element of the iterable, yielding the
   * event loop every yieldEvery iterations.
   *
   * @param iterable {Iterable}
   *        The iterable or iterator to iterate through.
   *
   * @param fn {(*) -> void|boolean}
   *        The function to be called on each element of the iterable.
   *
   *        Returning true from the function will stop the iteration.
   *
   * @param [yieldEvery = 50] {number|object}
   *        The number of iterations to complete before yielding back to the event
   *        loop.
   *
   * @return {boolean}
   *         Whether or not the function returned early.
   */
  async yieldingForEach(iterable, fn, yieldEvery = 50) {
    const yieldState =
      typeof yieldEvery === "number"
        ? Async.yieldState(yieldEvery)
        : yieldEvery;
    let iteration = 0;

    for (const item of iterable) {
      let result = fn(item, iteration++);
      if (typeof result !== "undefined" && typeof result.then !== "undefined") {
        // If we await result when it is not a Promise, we create an
        // automatically resolved promise, which is exactly the case that we
        // are trying to avoid.
        result = await result;
      }

      if (result === true) {
        return true;
      }

      if (yieldState.shouldYield()) {
        await Async.promiseYield();
        Async.checkAppReady();
      }
    }

    return false;
  },

  asyncQueueCaller(log) {
    return new AsyncQueueCaller(log);
  },

  asyncObserver(log, obj) {
    return new AsyncObserver(log, obj);
  },

  watchdog() {
    return new Watchdog();
  },
};

/**
 * Allows consumers to enqueue asynchronous callbacks to be called in order.
 * Typically this is used when providing a callback to a caller that doesn't
 * await on promises.
 */
class AsyncQueueCaller {
  constructor(log) {
    this._log = log;
    this._queue = Promise.resolve();
    this.QueryInterface = ChromeUtils.generateQI([
      "nsIObserver",
      "nsISupportsWeakReference",
    ]);
  }

  /**
   * /!\ Never await on another function that calls enqueueCall /!\
   *     on the same queue or we will deadlock.
   */
  enqueueCall(func) {
    this._queue = (async () => {
      await this._queue;
      try {
        return await func();
      } catch (e) {
        this._log.error(e);
        return false;
      }
    })();
  }

  promiseCallsComplete() {
    return this._queue;
  }
}

/*
 * Subclass of AsyncQueueCaller that can be used with Services.obs directly.
 * When this observe() is called, it will enqueue a call to the consumers's
 * observe().
 */
class AsyncObserver extends AsyncQueueCaller {
  constructor(obj, log) {
    super(log);
    this.obj = obj;
  }

  observe(subject, topic, data) {
    this.enqueueCall(() => this.obj.observe(subject, topic, data));
  }

  promiseObserversComplete() {
    return this.promiseCallsComplete();
  }
}

/**
 * Woof! Signals an operation to abort, either at shutdown or after a timeout.
 * The buffered engine uses this to abort long-running merges, so that they
 * don't prevent Firefox from quitting, or block future syncs.
 */
class Watchdog {
  constructor() {
    this.controller = new AbortController();
    this.timer = new Timer();

    /**
     * The reason for signaling an abort. `null` if not signaled,
     * `"timeout"` if the watchdog timer fired, or `"shutdown"` if the app is
     * is quitting.
     *
     * @type {String?}
     */
    this.abortReason = null;
  }

  /**
   * Returns the abort signal for this watchdog. This can be passed to APIs
   * that take a signal for cancellation, like `SyncedBookmarksMirror::apply`
   * or `fetch`.
   *
   * @type {AbortSignal}
   */
  get signal() {
    return this.controller.signal;
  }

  /**
   * Starts the watchdog timer, and listens for the app quitting.
   *
   * @param {Number} delay
   *                 The time to wait before signaling the operation to abort.
   */
  start(delay) {
    if (!this.signal.aborted) {
      Services.obs.addObserver(this, "quit-application");
      this.timer.init(this, delay, Ci.nsITimer.TYPE_ONE_SHOT);
    }
  }

  /**
   * Stops the watchdog timer and removes any listeners. This should be called
   * after the operation finishes.
   */
  stop() {
    if (!this.signal.aborted) {
      Services.obs.removeObserver(this, "quit-application");
      this.timer.cancel();
    }
  }

  observe(subject, topic) {
    if (topic == "timer-callback") {
      this.abortReason = "timeout";
    } else if (topic == "quit-application") {
      this.abortReason = "shutdown";
    }
    this.stop();
    this.controller.abort();
  }
}
PK
!<i���q,q,*modules/services-common/hawkclient.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * HAWK is an HTTP authentication scheme using a message authentication code
 * (MAC) algorithm to provide partial HTTP request cryptographic verification.
 *
 * For details, see: https://github.com/hueniverse/hawk
 *
 * With HAWK, it is essential that the clocks on clients and server not have an
 * absolute delta of greater than one minute, as the HAWK protocol uses
 * timestamps to reduce the possibility of replay attacks.  However, it is
 * likely that some clients' clocks will be more than a little off, especially
 * in mobile devices, which would break HAWK-based services (like sync and
 * firefox accounts) for those clients.
 *
 * This library provides a stateful HAWK client that calculates (roughly) the
 * clock delta on the client vs the server.  The library provides an interface
 * for deriving HAWK credentials and making HAWK-authenticated REST requests to
 * a single remote server.  Therefore, callers who want to interact with
 * multiple HAWK services should instantiate one HawkClient per service.
 */

import { HAWKAuthenticatedRESTRequest } from "resource://services-common/hawkrequest.sys.mjs";

import { Observers } from "resource://services-common/observers.sys.mjs";
import { Log } from "resource://gre/modules/Log.sys.mjs";

// log.appender.dump should be one of "Fatal", "Error", "Warn", "Info", "Config",
// "Debug", "Trace" or "All". If none is specified, "Error" will be used by
// default.
// Note however that Sync will also add this log to *its* DumpAppender, so
// in a Sync context it shouldn't be necessary to adjust this - however, that
// also means error logs are likely to be dump'd twice but that's OK.
const PREF_LOG_LEVEL = "services.common.hawk.log.appender.dump";

// A pref that can be set so "sensitive" information (eg, personally
// identifiable info, credentials, etc) will be logged.
const PREF_LOG_SENSITIVE_DETAILS = "services.common.hawk.log.sensitive";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "log", function () {
  let log = Log.repository.getLogger("Hawk");
  // We set the log itself to "debug" and set the level from the preference to
  // the appender.  This allows other things to send the logs to different
  // appenders, while still allowing the pref to control what is seen via dump()
  log.level = Log.Level.Debug;
  let appender = new Log.DumpAppender();
  log.addAppender(appender);
  appender.level = Log.Level.Error;
  try {
    let level =
      Services.prefs.getPrefType(PREF_LOG_LEVEL) ==
        Ci.nsIPrefBranch.PREF_STRING &&
      Services.prefs.getStringPref(PREF_LOG_LEVEL);
    appender.level = Log.Level[level] || Log.Level.Error;
  } catch (e) {
    log.error(e);
  }

  return log;
});

// A boolean to indicate if personally identifiable information (or anything
// else sensitive, such as credentials) should be logged.
ChromeUtils.defineLazyGetter(lazy, "logPII", function () {
  try {
    return Services.prefs.getBoolPref(PREF_LOG_SENSITIVE_DETAILS);
  } catch (_) {
    return false;
  }
});

/*
 * A general purpose client for making HAWK authenticated requests to a single
 * host.  Keeps track of the clock offset between the client and the host for
 * computation of the timestamp in the HAWK Authorization header.
 *
 * Clients should create one HawkClient object per each server they wish to
 * interact with.
 *
 * @param host
 *        The url of the host
 */
export var HawkClient = function (host) {
  this.host = host;

  // Clock offset in milliseconds between our client's clock and the date
  // reported in responses from our host.
  this._localtimeOffsetMsec = 0;
};

HawkClient.prototype = {
  /*
   * Construct an error message for a response.  Private.
   *
   * @param restResponse
   *        A RESTResponse object from a RESTRequest
   *
   * @param error
   *        A string or object describing the error
   */
  _constructError(restResponse, error) {
    let errorObj = {
      error,
      // This object is likely to be JSON.stringify'd, but neither Error()
      // objects nor Components.Exception objects do the right thing there,
      // so we add a new element which is simply the .toString() version of
      // the error object, so it does appear in JSON'd values.
      errorString: error.toString(),
      message: restResponse.statusText,
      code: restResponse.status,
      errno: restResponse.status,
      toString() {
        return this.code + ": " + this.message;
      },
    };
    let retryAfter =
      restResponse.headers && restResponse.headers["retry-after"];
    retryAfter = retryAfter ? parseInt(retryAfter) : retryAfter;
    if (retryAfter) {
      errorObj.retryAfter = retryAfter;
      // and notify observers of the retry interval
      if (this.observerPrefix) {
        Observers.notify(this.observerPrefix + ":backoff:interval", retryAfter);
      }
    }
    return errorObj;
  },

  /*
   *
   * Update clock offset by determining difference from date gives in the (RFC
   * 1123) Date header of a server response.  Because HAWK tolerates a window
   * of one minute of clock skew (so two minutes total since the skew can be
   * positive or negative), the simple method of calculating offset here is
   * probably good enough.  We keep the value in milliseconds to make life
   * easier, even though the value will not have millisecond accuracy.
   *
   * @param dateString
   *        An RFC 1123 date string (e.g., "Mon, 13 Jan 2014 21:45:06 GMT")
   *
   * For HAWK clock skew and replay protection, see
   * https://github.com/hueniverse/hawk#replay-protection
   */
  _updateClockOffset(dateString) {
    try {
      let serverDateMsec = Date.parse(dateString);
      this._localtimeOffsetMsec = serverDateMsec - this.now();
      lazy.log.debug(
        "Clock offset vs " + this.host + ": " + this._localtimeOffsetMsec
      );
    } catch (err) {
      lazy.log.warn("Bad date header in server response: " + dateString);
    }
  },

  /*
   * Get the current clock offset in milliseconds.
   *
   * The offset is the number of milliseconds that must be added to the client
   * clock to make it equal to the server clock.  For example, if the client is
   * five minutes ahead of the server, the localtimeOffsetMsec will be -300000.
   */
  get localtimeOffsetMsec() {
    return this._localtimeOffsetMsec;
  },

  /*
   * return current time in milliseconds
   */
  now() {
    return Date.now();
  },

  /* A general method for sending raw RESTRequest calls authorized using HAWK
   *
   * @param path
   *        API endpoint path
   * @param method
   *        The HTTP request method
   * @param credentials
   *        Hawk credentials
   * @param payloadObj
   *        An object that can be encodable as JSON as the payload of the
   *        request
   * @param extraHeaders
   *        An object with header/value pairs to send with the request.
   * @return Promise
   *        Returns a promise that resolves to the response of the API call,
   *        or is rejected with an error.  If the server response can be parsed
   *        as JSON and contains an 'error' property, the promise will be
   *        rejected with this JSON-parsed response.
   */
  async request(
    path,
    method,
    credentials = null,
    payloadObj = {},
    extraHeaders = {},
    retryOK = true
  ) {
    method = method.toLowerCase();

    let uri = this.host + path;

    let extra = {
      now: this.now(),
      localtimeOffsetMsec: this.localtimeOffsetMsec,
      headers: extraHeaders,
    };

    let request = this.newHAWKAuthenticatedRESTRequest(uri, credentials, extra);
    let error;
    let restResponse = await request[method](payloadObj).catch(e => {
      // Keep a reference to the error, log a message about it, and return the
      // response anyway.
      error = e;
      lazy.log.warn("hawk request error", error);
      return request.response;
    });

    // This shouldn't happen anymore, but it's not exactly difficult to handle.
    if (!restResponse) {
      throw error;
    }

    let status = restResponse.status;

    lazy.log.debug(
      "(Response) " +
        path +
        ": code: " +
        status +
        " - Status text: " +
        restResponse.statusText
    );
    if (lazy.logPII) {
      lazy.log.debug("Response text", restResponse.body);
    }

    // All responses may have backoff headers, which are a server-side safety
    // valve to allow slowing down clients without hurting performance.
    this._maybeNotifyBackoff(restResponse, "x-weave-backoff");
    this._maybeNotifyBackoff(restResponse, "x-backoff");

    if (error) {
      // When things really blow up, reconstruct an error object that follows
      // the general format of the server on error responses.
      throw this._constructError(restResponse, error);
    }

    this._updateClockOffset(restResponse.headers.date);

    if (status === 401 && retryOK && !("retry-after" in restResponse.headers)) {
      // Retry once if we were rejected due to a bad timestamp.
      // Clock offset is adjusted already in the top of this function.
      lazy.log.debug("Received 401 for " + path + ": retrying");
      return this.request(
        path,
        method,
        credentials,
        payloadObj,
        extraHeaders,
        false
      );
    }

    // If the server returned a json error message, use it in the rejection
    // of the promise.
    //
    // In the case of a 401, in which we are probably being rejected for a
    // bad timestamp, retry exactly once, during which time clock offset will
    // be adjusted.

    let jsonResponse = {};
    try {
      jsonResponse = JSON.parse(restResponse.body);
    } catch (notJSON) {}

    let okResponse = 200 <= status && status < 300;
    if (!okResponse || jsonResponse.error) {
      if (jsonResponse.error) {
        throw jsonResponse;
      }
      throw this._constructError(restResponse, "Request failed");
    }

    // It's up to the caller to know how to decode the response.
    // We just return the whole response.
    return restResponse;
  },

  /*
   * The prefix used for all notifications sent by this module.  This
   * allows the handler of notifications to be sure they are handling
   * notifications for the service they expect.
   *
   * If not set, no notifications will be sent.
   */
  observerPrefix: null,

  // Given an optional header value, notify that a backoff has been requested.
  _maybeNotifyBackoff(response, headerName) {
    if (!this.observerPrefix || !response.headers) {
      return;
    }
    let headerVal = response.headers[headerName];
    if (!headerVal) {
      return;
    }
    let backoffInterval;
    try {
      backoffInterval = parseInt(headerVal, 10);
    } catch (ex) {
      lazy.log.error(
        "hawkclient response had invalid backoff value in '" +
          headerName +
          "' header: " +
          headerVal
      );
      return;
    }
    Observers.notify(
      this.observerPrefix + ":backoff:interval",
      backoffInterval
    );
  },

  // override points for testing.
  newHAWKAuthenticatedRESTRequest(uri, credentials, extra) {
    return new HAWKAuthenticatedRESTRequest(uri, credentials, extra);
  },
};
PK
!<
����+modules/services-common/hawkrequest.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Log } from "resource://gre/modules/Log.sys.mjs";

import { RESTRequest } from "resource://services-common/rest.sys.mjs";
import { CommonUtils } from "resource://services-common/utils.sys.mjs";
import { Credentials } from "resource://gre/modules/Credentials.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  CryptoUtils: "resource://services-crypto/utils.sys.mjs",
});

/**
 * Single-use HAWK-authenticated HTTP requests to RESTish resources.
 *
 * @param uri
 *        (String) URI for the RESTRequest constructor
 *
 * @param credentials
 *        (Object) Optional credentials for computing HAWK authentication
 *        header.
 *
 * @param payloadObj
 *        (Object) Optional object to be converted to JSON payload
 *
 * @param extra
 *        (Object) Optional extra params for HAWK header computation.
 *        Valid properties are:
 *
 *          now:                 <current time in milliseconds>,
 *          localtimeOffsetMsec: <local clock offset vs server>,
 *          headers:             <An object with header/value pairs to be sent
 *                                as headers on the request>
 *
 * extra.localtimeOffsetMsec is the value in milliseconds that must be added to
 * the local clock to make it agree with the server's clock.  For instance, if
 * the local clock is two minutes ahead of the server, the time offset in
 * milliseconds will be -120000.
 */

export var HAWKAuthenticatedRESTRequest = function HawkAuthenticatedRESTRequest(
  uri,
  credentials,
  extra = {}
) {
  RESTRequest.call(this, uri);

  this.credentials = credentials;
  this.now = extra.now || Date.now();
  this.localtimeOffsetMsec = extra.localtimeOffsetMsec || 0;
  this._log.trace(
    "local time, offset: " + this.now + ", " + this.localtimeOffsetMsec
  );
  this.extraHeaders = extra.headers || {};

  // Expose for testing
  this._intl = getIntl();
};

HAWKAuthenticatedRESTRequest.prototype = {
  async dispatch(method, data) {
    let contentType = "text/plain";
    if (method == "POST" || method == "PUT" || method == "PATCH") {
      contentType = "application/json";
    }
    if (this.credentials) {
      let options = {
        now: this.now,
        localtimeOffsetMsec: this.localtimeOffsetMsec,
        credentials: this.credentials,
        payload: (data && JSON.stringify(data)) || "",
        contentType,
      };
      let header = await lazy.CryptoUtils.computeHAWK(
        this.uri,
        method,
        options
      );
      this.setHeader("Authorization", header.field);
    }

    for (let header in this.extraHeaders) {
      this.setHeader(header, this.extraHeaders[header]);
    }

    this.setHeader("Content-Type", contentType);

    this.setHeader("Accept-Language", this._intl.accept_languages);

    return super.dispatch(method, data);
  },
};

Object.setPrototypeOf(
  HAWKAuthenticatedRESTRequest.prototype,
  RESTRequest.prototype
);

/**
 * Generic function to derive Hawk credentials.
 *
 * Hawk credentials are derived using shared secrets, which depend on the token
 * in use.
 *
 * @param tokenHex
 *        The current session token encoded in hex
 * @param context
 *        A context for the credentials. A protocol version will be prepended
 *        to the context, see Credentials.keyWord for more information.
 * @param size
 *        The size in bytes of the expected derived buffer,
 *        defaults to 3 * 32.
 * @return credentials
 *        Returns an object:
 *        {
 *          id: the Hawk id (from the first 32 bytes derived)
 *          key: the Hawk key (from bytes 32 to 64)
 *          extra: size - 64 extra bytes (if size > 64)
 *        }
 */
export async function deriveHawkCredentials(tokenHex, context, size = 96) {
  let token = CommonUtils.hexToBytes(tokenHex);
  let out = await lazy.CryptoUtils.hkdfLegacy(
    token,
    undefined,
    Credentials.keyWord(context),
    size
  );

  let result = {
    key: out.slice(32, 64),
    id: CommonUtils.bytesAsHex(out.slice(0, 32)),
  };
  if (size > 64) {
    result.extra = out.slice(64);
  }

  return result;
}

// With hawk request, we send the user's accepted-languages with each request.
// To keep the number of times we read this pref at a minimum, maintain the
// preference in a stateful object that notices and updates itself when the
// pref is changed.
function Intl() {
  // We won't actually query the pref until the first time we need it
  this._accepted = "";
  this._everRead = false;
  this.init();
}

Intl.prototype = {
  init() {
    Services.prefs.addObserver("intl.accept_languages", this);
  },

  uninit() {
    Services.prefs.removeObserver("intl.accept_languages", this);
  },

  observe() {
    this.readPref();
  },

  readPref() {
    this._everRead = true;
    try {
      this._accepted = Services.prefs.getComplexValue(
        "intl.accept_languages",
        Ci.nsIPrefLocalizedString
      ).data;
    } catch (err) {
      let log = Log.repository.getLogger("Services.Common.RESTRequest");
      log.error("Error reading intl.accept_languages pref", err);
    }
  },

  get accept_languages() {
    if (!this._everRead) {
      this.readPref();
    }
    return this._accepted;
  },
};

// Singleton getter for Intl, creating an instance only when we first need it.
var intl = null;
function getIntl() {
  if (!intl) {
    intl = new Intl();
  }
  return intl;
}
PK
!<fo@g��1modules/services-common/kinto-http-client.sys.mjs/*
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * This file is generated from kinto.js - do not modify directly.
 */

import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";

/*
 * Version 15.0.0 - c8775d9
 */

import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs";

/******************************************************************************
Copyright (c) Microsoft Corporation.

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise */

function __decorate(decorators, target, key, desc) {
  var c = arguments.length,
    r =
      c < 3
        ? target
        : desc === null
        ? (desc = Object.getOwnPropertyDescriptor(target, key))
        : desc,
    d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
    r = Reflect.decorate(decorators, target, key, desc);
  else
    for (var i = decorators.length - 1; i >= 0; i--)
      if ((d = decorators[i]))
        r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
}

/**
 * Chunks an array into n pieces.
 *
 * @private
 * @param  {Array}  array
 * @param  {Number} n
 * @return {Array}
 */
function partition(array, n) {
  if (n <= 0) {
    return [array];
  }
  return array.reduce((acc, x, i) => {
    if (i === 0 || i % n === 0) {
      acc.push([x]);
    } else {
      acc[acc.length - 1].push(x);
    }
    return acc;
  }, []);
}
/**
 * Returns a Promise always resolving after the specified amount in milliseconds.
 *
 * @return Promise<void>
 */
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
/**
 * Always returns a resource data object from the provided argument.
 *
 * @private
 * @param  {Object|String} resource
 * @return {Object}
 */
function toDataBody(resource) {
  if (isObject(resource)) {
    return resource;
  }
  if (typeof resource === "string") {
    return { id: resource };
  }
  throw new Error("Invalid argument.");
}
/**
 * Transforms an object into an URL query string, stripping out any undefined
 * values.
 *
 * @param  {Object} obj
 * @return {String}
 */
function qsify(obj) {
  const encode = v =>
    encodeURIComponent(typeof v === "boolean" ? String(v) : v);
  const stripped = cleanUndefinedProperties(obj);
  return Object.keys(stripped)
    .map(k => {
      const ks = encode(k) + "=";
      if (Array.isArray(stripped[k])) {
        return ks + stripped[k].map(v => encode(v)).join(",");
      }
      return ks + encode(stripped[k]);
    })
    .join("&");
}
/**
 * Checks if a version is within the provided range.
 *
 * @param  {String} version    The version to check.
 * @param  {String} minVersion The minimum supported version (inclusive).
 * @param  {String} maxVersion The minimum supported version (exclusive).
 * @throws {Error} If the version is outside of the provided range.
 */
function checkVersion(version, minVersion, maxVersion) {
  const extract = str => str.split(".").map(x => parseInt(x, 10));
  const [verMajor, verMinor] = extract(version);
  const [minMajor, minMinor] = extract(minVersion);
  const [maxMajor, maxMinor] = extract(maxVersion);
  const checks = [
    verMajor < minMajor,
    verMajor === minMajor && verMinor < minMinor,
    verMajor > maxMajor,
    verMajor === maxMajor && verMinor >= maxMinor,
  ];
  if (checks.some(x => x)) {
    throw new Error(
      `Version ${version} doesn't satisfy ${minVersion} <= x < ${maxVersion}`
    );
  }
}
/**
 * Generates a decorator function ensuring a version check is performed against
 * the provided requirements before executing it.
 *
 * @param  {String} min The required min version (inclusive).
 * @param  {String} max The required max version (inclusive).
 * @return {Function}
 */
function support(min, max) {
  return function (
    // @ts-ignore
    target,
    key,
    descriptor
  ) {
    const fn = descriptor.value;
    return {
      configurable: true,
      get() {
        const wrappedMethod = (...args) => {
          // "this" is the current instance which its method is decorated.
          const client = this.client ? this.client : this;
          return client
            .fetchHTTPApiVersion()
            .then(version => checkVersion(version, min, max))
            .then(() => fn.apply(this, args));
        };
        Object.defineProperty(this, key, {
          value: wrappedMethod,
          configurable: true,
          writable: true,
        });
        return wrappedMethod;
      },
    };
  };
}
/**
 * Generates a decorator function ensuring that the specified capabilities are
 * available on the server before executing it.
 *
 * @param  {Array<String>} capabilities The required capabilities.
 * @return {Function}
 */
function capable(capabilities) {
  return function (
    // @ts-ignore
    target,
    key,
    descriptor
  ) {
    const fn = descriptor.value;
    return {
      configurable: true,
      get() {
        const wrappedMethod = (...args) => {
          // "this" is the current instance which its method is decorated.
          const client = this.client ? this.client : this;
          return client
            .fetchServerCapabilities()
            .then(available => {
              const missing = capabilities.filter(c => !(c in available));
              if (missing.length) {
                const missingStr = missing.join(", ");
                throw new Error(
                  `Required capabilities ${missingStr} not present on server`
                );
              }
            })
            .then(() => fn.apply(this, args));
        };
        Object.defineProperty(this, key, {
          value: wrappedMethod,
          configurable: true,
          writable: true,
        });
        return wrappedMethod;
      },
    };
  };
}
/**
 * Generates a decorator function ensuring an operation is not performed from
 * within a batch request.
 *
 * @param  {String} message The error message to throw.
 * @return {Function}
 */
function nobatch(message) {
  return function (
    // @ts-ignore
    target,
    key,
    descriptor
  ) {
    const fn = descriptor.value;
    return {
      configurable: true,
      get() {
        const wrappedMethod = (...args) => {
          // "this" is the current instance which its method is decorated.
          if (this._isBatch) {
            throw new Error(message);
          }
          return fn.apply(this, args);
        };
        Object.defineProperty(this, key, {
          value: wrappedMethod,
          configurable: true,
          writable: true,
        });
        return wrappedMethod;
      },
    };
  };
}
/**
 * Returns true if the specified value is an object (i.e. not an array nor null).
 * @param  {Object} thing The value to inspect.
 * @return {bool}
 */
function isObject(thing) {
  return typeof thing === "object" && thing !== null && !Array.isArray(thing);
}
/**
 * Parses a data url.
 * @param  {String} dataURL The data url.
 * @return {Object}
 */
function parseDataURL(dataURL) {
  const regex = /^data:(.*);base64,(.*)/;
  const match = dataURL.match(regex);
  if (!match) {
    throw new Error(`Invalid data-url: ${String(dataURL).substring(0, 32)}...`);
  }
  const props = match[1];
  const base64 = match[2];
  const [type, ...rawParams] = props.split(";");
  const params = rawParams.reduce((acc, param) => {
    const [key, value] = param.split("=");
    return { ...acc, [key]: value };
  }, {});
  return { ...params, type, base64 };
}
/**
 * Extracts file information from a data url.
 * @param  {String} dataURL The data url.
 * @return {Object}
 */
function extractFileInfo(dataURL) {
  const { name, type, base64 } = parseDataURL(dataURL);
  const binary = atob(base64);
  const array = [];
  for (let i = 0; i < binary.length; i++) {
    array.push(binary.charCodeAt(i));
  }
  const blob = new Blob([new Uint8Array(array)], { type });
  return { blob, name };
}
/**
 * Creates a FormData instance from a data url and an existing JSON response
 * body.
 * @param  {String} dataURL            The data url.
 * @param  {Object} body               The response body.
 * @param  {Object} [options={}]       The options object.
 * @param  {Object} [options.filename] Force attachment file name.
 * @return {FormData}
 */
function createFormData(dataURL, body, options = {}) {
  const { filename = "untitled" } = options;
  const { blob, name } = extractFileInfo(dataURL);
  const formData = new FormData();
  formData.append("attachment", blob, name || filename);
  for (const property in body) {
    if (typeof body[property] !== "undefined") {
      formData.append(property, JSON.stringify(body[property]));
    }
  }
  return formData;
}
/**
 * Clones an object with all its undefined keys removed.
 * @private
 */
function cleanUndefinedProperties(obj) {
  const result = {};
  for (const key in obj) {
    if (typeof obj[key] !== "undefined") {
      result[key] = obj[key];
    }
  }
  return result;
}
/**
 * Handle common query parameters for Kinto requests.
 *
 * @param  {String}  [path]  The endpoint base path.
 * @param  {Array}   [options.fields]    Fields to limit the
 *   request to.
 * @param  {Object}  [options.query={}]  Additional query arguments.
 */
function addEndpointOptions(path, options = {}) {
  const query = { ...options.query };
  if (options.fields) {
    query._fields = options.fields;
  }
  const queryString = qsify(query);
  if (queryString) {
    return path + "?" + queryString;
  }
  return path;
}
/**
 * Replace authorization header with an obscured version
 */
function obscureAuthorizationHeader(headers) {
  const h = new Headers(headers);
  if (h.has("authorization")) {
    h.set("authorization", "**** (suppressed)");
  }
  const obscuredHeaders = {};
  for (const [header, value] of h.entries()) {
    obscuredHeaders[header] = value;
  }
  return obscuredHeaders;
}

/**
 * Kinto server error code descriptors.
 */
const ERROR_CODES = {
  104: "Missing Authorization Token",
  105: "Invalid Authorization Token",
  106: "Request body was not valid JSON",
  107: "Invalid request parameter",
  108: "Missing request parameter",
  109: "Invalid posted data",
  110: "Invalid Token / id",
  111: "Missing Token / id",
  112: "Content-Length header was not provided",
  113: "Request body too large",
  114: "Resource was created, updated or deleted meanwhile",
  115: "Method not allowed on this end point (hint: server may be readonly)",
  116: "Requested version not available on this server",
  117: "Client has sent too many requests",
  121: "Resource access is forbidden for this user",
  122: "Another resource violates constraint",
  201: "Service Temporary unavailable due to high load",
  202: "Service deprecated",
  999: "Internal Server Error",
};
class NetworkTimeoutError extends Error {
  constructor(url, options) {
    super(
      `Timeout while trying to access ${url} with ${JSON.stringify(options)}`
    );
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, NetworkTimeoutError);
    }
    this.url = url;
    this.options = options;
  }
}
class UnparseableResponseError extends Error {
  constructor(response, body, error) {
    const { status } = response;
    super(
      `Response from server unparseable (HTTP ${
        status || 0
      }; ${error}): ${body}`
    );
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, UnparseableResponseError);
    }
    this.status = status;
    this.response = response;
    this.stack = error.stack;
    this.error = error;
  }
}
/**
 * "Error" subclass representing a >=400 response from the server.
 *
 * Whether or not this is an error depends on your application.
 *
 * The `json` field can be undefined if the server responded with an
 * empty response body. This shouldn't generally happen. Most "bad"
 * responses come with a JSON error description, or (if they're
 * fronted by a CDN or nginx or something) occasionally non-JSON
 * responses (which become UnparseableResponseErrors, above).
 */
class ServerResponse extends Error {
  constructor(response, json) {
    const { status } = response;
    let { statusText } = response;
    let errnoMsg;
    if (json) {
      // Try to fill in information from the JSON error.
      statusText = json.error || statusText;
      // Take errnoMsg from either ERROR_CODES or json.message.
      if (json.errno && json.errno in ERROR_CODES) {
        errnoMsg = ERROR_CODES[json.errno];
      } else if (json.message) {
        errnoMsg = json.message;
      }
      // If we had both ERROR_CODES and json.message, and they differ,
      // combine them.
      if (errnoMsg && json.message && json.message !== errnoMsg) {
        errnoMsg += ` (${json.message})`;
      }
    }
    let message = `HTTP ${status} ${statusText}`;
    if (errnoMsg) {
      message += `: ${errnoMsg}`;
    }
    super(message.trim());
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, ServerResponse);
    }
    this.response = response;
    this.data = json;
  }
}

var errors = /*#__PURE__*/ Object.freeze({
  __proto__: null,
  NetworkTimeoutError,
  ServerResponse,
  UnparseableResponseError,
  default: ERROR_CODES,
});

/**
 * Enhanced HTTP client for the Kinto protocol.
 * @private
 */
class HTTP {
  /**
   * Default HTTP request headers applied to each outgoing request.
   *
   * @type {Object}
   */
  static get DEFAULT_REQUEST_HEADERS() {
    return {
      Accept: "application/json",
      "Content-Type": "application/json",
    };
  }
  /**
   * Default options.
   *
   * @type {Object}
   */
  static get defaultOptions() {
    return { timeout: null, requestMode: "cors" };
  }
  /**
   * Constructor.
   *
   * @param {EventEmitter} events                       The event handler.
   * @param {Object}       [options={}}                 The options object.
   * @param {Number}       [options.timeout=null]       The request timeout in ms, if any (default: `null`).
   * @param {String}       [options.requestMode="cors"] The HTTP request mode (default: `"cors"`).
   */
  constructor(events, options = {}) {
    // public properties
    /**
     * The event emitter instance.
     * @type {EventEmitter}
     */
    this.events = events;
    /**
     * The request mode.
     * @see  https://fetch.spec.whatwg.org/#requestmode
     * @type {String}
     */
    this.requestMode = options.requestMode || HTTP.defaultOptions.requestMode;
    /**
     * The request timeout.
     * @type {Number}
     */
    this.timeout = options.timeout || HTTP.defaultOptions.timeout;
    /**
     * The fetch() function.
     * @type {Function}
     */
    this.fetchFunc = options.fetchFunc || globalThis.fetch.bind(globalThis);
  }
  /**
   * @private
   */
  timedFetch(url, options) {
    let hasTimedout = false;
    return new Promise((resolve, reject) => {
      // Detect if a request has timed out.
      let _timeoutId;
      if (this.timeout) {
        _timeoutId = setTimeout(() => {
          hasTimedout = true;
          if (options && options.headers) {
            options = {
              ...options,
              headers: obscureAuthorizationHeader(options.headers),
            };
          }
          reject(new NetworkTimeoutError(url, options));
        }, this.timeout);
      }
      function proceedWithHandler(fn) {
        return arg => {
          if (!hasTimedout) {
            if (_timeoutId) {
              clearTimeout(_timeoutId);
            }
            fn(arg);
          }
        };
      }
      this.fetchFunc(url, options)
        .then(proceedWithHandler(resolve))
        .catch(proceedWithHandler(reject));
    });
  }
  /**
   * @private
   */
  async processResponse(response) {
    const { status, headers } = response;
    const text = await response.text();
    // Check if we have a body; if so parse it as JSON.
    let json;
    if (text.length !== 0) {
      try {
        json = JSON.parse(text);
      } catch (err) {
        throw new UnparseableResponseError(response, text, err);
      }
    }
    if (status >= 400) {
      throw new ServerResponse(response, json);
    }
    return { status, json: json, headers };
  }
  /**
   * @private
   */
  async retry(url, retryAfter, request, options) {
    await delay(retryAfter);
    return this.request(url, request, {
      ...options,
      retry: options.retry - 1,
    });
  }
  /**
   * Performs an HTTP request to the Kinto server.
   *
   * Resolves with an objet containing the following HTTP response properties:
   * - `{Number}  status`  The HTTP status code.
   * - `{Object}  json`    The JSON response body.
   * - `{Headers} headers` The response headers object; see the ES6 fetch() spec.
   *
   * @param  {String} url               The URL.
   * @param  {Object} [request={}]      The request object, passed to
   *     fetch() as its options object.
   * @param  {Object} [request.headers] The request headers object (default: {})
   * @param  {Object} [options={}]      Options for making the
   *     request
   * @param  {Number} [options.retry]   Number of retries (default: 0)
   * @return {Promise}
   */
  async request(url, request = { headers: {} }, options = { retry: 0 }) {
    // Ensure default request headers are always set
    request.headers = { ...HTTP.DEFAULT_REQUEST_HEADERS, ...request.headers };
    // If a multipart body is provided, remove any custom Content-Type header as
    // the fetch() implementation will add the correct one for us.
    if (request.body && request.body instanceof FormData) {
      if (request.headers instanceof Headers) {
        request.headers.delete("Content-Type");
      } else if (!Array.isArray(request.headers)) {
        delete request.headers["Content-Type"];
      }
    }
    request.mode = this.requestMode;
    const response = await this.timedFetch(url, request);
    const { headers } = response;
    this._checkForDeprecationHeader(headers);
    this._checkForBackoffHeader(headers);
    // Check if the server summons the client to retry after a while.
    const retryAfter = this._checkForRetryAfterHeader(headers);
    // If number of allowed of retries is not exhausted, retry the same request.
    if (retryAfter && options.retry > 0) {
      return this.retry(url, retryAfter, request, options);
    }
    return this.processResponse(response);
  }
  _checkForDeprecationHeader(headers) {
    const alertHeader = headers.get("Alert");
    if (!alertHeader) {
      return;
    }
    let alert;
    try {
      alert = JSON.parse(alertHeader);
    } catch (err) {
      console.warn("Unable to parse Alert header message", alertHeader);
      return;
    }
    console.warn(alert.message, alert.url);
    if (this.events) {
      this.events.emit("deprecated", alert);
    }
  }
  _checkForBackoffHeader(headers) {
    let backoffMs;
    const backoffHeader = headers.get("Backoff");
    const backoffSeconds = backoffHeader ? parseInt(backoffHeader, 10) : 0;
    if (backoffSeconds > 0) {
      backoffMs = new Date().getTime() + backoffSeconds * 1000;
    } else {
      backoffMs = 0;
    }
    if (this.events) {
      this.events.emit("backoff", backoffMs);
    }
  }
  _checkForRetryAfterHeader(headers) {
    const retryAfter = headers.get("Retry-After");
    if (!retryAfter) {
      return null;
    }
    const delay = parseInt(retryAfter, 10) * 1000;
    const tryAgainAfter = new Date().getTime() + delay;
    if (this.events) {
      this.events.emit("retry-after", tryAgainAfter);
    }
    return delay;
  }
}

/**
 * Endpoints templates.
 * @type {Object}
 */
const ENDPOINTS = {
  root: () => "/",
  batch: () => "/batch",
  permissions: () => "/permissions",
  bucket: bucket => "/buckets" + (bucket ? `/${bucket}` : ""),
  history: bucket => `${ENDPOINTS.bucket(bucket)}/history`,
  collection: (bucket, coll) =>
    `${ENDPOINTS.bucket(bucket)}/collections` + (coll ? `/${coll}` : ""),
  group: (bucket, group) =>
    `${ENDPOINTS.bucket(bucket)}/groups` + (group ? `/${group}` : ""),
  record: (bucket, coll, id) =>
    `${ENDPOINTS.collection(bucket, coll)}/records` + (id ? `/${id}` : ""),
  attachment: (bucket, coll, id) =>
    `${ENDPOINTS.record(bucket, coll, id)}/attachment`,
};

const requestDefaults = {
  safe: false,
  // check if we should set default content type here
  headers: {},
  patch: false,
};
/**
 * @private
 */
function safeHeader(safe, last_modified) {
  if (!safe) {
    return {};
  }
  if (last_modified) {
    return { "If-Match": `"${last_modified}"` };
  }
  return { "If-None-Match": "*" };
}
/**
 * @private
 */
function createRequest(path, { data, permissions }, options = {}) {
  const { headers, safe } = {
    ...requestDefaults,
    ...options,
  };
  const method = options.method || (data && data.id) ? "PUT" : "POST";
  return {
    method,
    path,
    headers: { ...headers, ...safeHeader(safe) },
    body: { data, permissions },
  };
}
/**
 * @private
 */
function updateRequest(path, { data, permissions }, options = {}) {
  const { headers, safe, patch } = { ...requestDefaults, ...options };
  const { last_modified } = { ...data, ...options };
  const hasNoData =
    data &&
    Object.keys(data).filter(k => k !== "id" && k !== "last_modified")
      .length === 0;
  if (hasNoData) {
    data = undefined;
  }
  return {
    method: patch ? "PATCH" : "PUT",
    path,
    headers: { ...headers, ...safeHeader(safe, last_modified) },
    body: { data, permissions },
  };
}
/**
 * @private
 */
function jsonPatchPermissionsRequest(path, permissions, opType, options = {}) {
  const { headers, safe, last_modified } = { ...requestDefaults, ...options };
  const ops = [];
  for (const [type, principals] of Object.entries(permissions)) {
    if (principals) {
      for (const principal of principals) {
        ops.push({
          op: opType,
          path: `/permissions/${type}/${principal}`,
        });
      }
    }
  }
  return {
    method: "PATCH",
    path,
    headers: {
      ...headers,
      ...safeHeader(safe, last_modified),
      "Content-Type": "application/json-patch+json",
    },
    body: ops,
  };
}
/**
 * @private
 */
function deleteRequest(path, options = {}) {
  const { headers, safe, last_modified } = {
    ...requestDefaults,
    ...options,
  };
  if (safe && !last_modified) {
    throw new Error("Safe concurrency check requires a last_modified value.");
  }
  return {
    method: "DELETE",
    path,
    headers: { ...headers, ...safeHeader(safe, last_modified) },
  };
}
/**
 * @private
 */
function addAttachmentRequest(
  path,
  dataURI,
  { data, permissions } = {},
  options = {}
) {
  const { headers, safe, gzipped } = { ...requestDefaults, ...options };
  const { last_modified } = { ...data, ...options };
  const body = { data, permissions };
  const formData = createFormData(dataURI, body, options);
  const customPath = `${path}${
    gzipped !== null ? "?gzipped=" + (gzipped ? "true" : "false") : ""
  }`;
  return {
    method: "POST",
    path: customPath,
    headers: { ...headers, ...safeHeader(safe, last_modified) },
    body: formData,
  };
}

/**
 * Exports batch responses as a result object.
 *
 * @private
 * @param  {Array} responses The batch subrequest responses.
 * @param  {Array} requests  The initial issued requests.
 * @return {Object}
 */
function aggregate(responses = [], requests = []) {
  if (responses.length !== requests.length) {
    throw new Error("Responses length should match requests one.");
  }
  const results = {
    errors: [],
    published: [],
    conflicts: [],
    skipped: [],
  };
  return responses.reduce((acc, response, index) => {
    const { status } = response;
    const request = requests[index];
    if (status >= 200 && status < 400) {
      acc.published.push(response.body);
    } else if (status === 404) {
      // Extract the id manually from request path while waiting for Kinto/kinto#818
      const regex = /(buckets|groups|collections|records)\/([^/]+)$/;
      const extracts = request.path.match(regex);
      const id = extracts && extracts.length === 3 ? extracts[2] : undefined;
      acc.skipped.push({
        id,
        path: request.path,
        error: response.body,
      });
    } else if (status === 412) {
      acc.conflicts.push({
        // XXX: specifying the type is probably superfluous
        type: "outgoing",
        local: request.body,
        remote:
          (response.body.details && response.body.details.existing) || null,
      });
    } else {
      acc.errors.push({
        path: request.path,
        sent: request,
        error: response.body,
      });
    }
    return acc;
  }, results);
}

// Unique ID creation requires a high quality random # generator. In the browser we therefore
// require the crypto API and do not support built-in fallback to lower quality random number
// generators (like Math.random()).
let getRandomValues;
const rnds8 = new Uint8Array(16);
function rng() {
  // lazy load so that environments that need to polyfill have a chance to do so
  if (!getRandomValues) {
    // getRandomValues needs to be invoked in a context where "this" is a Crypto implementation.
    getRandomValues =
      typeof crypto !== "undefined" &&
      crypto.getRandomValues &&
      crypto.getRandomValues.bind(crypto);

    if (!getRandomValues) {
      throw new Error(
        "crypto.getRandomValues() not supported. See https://github.com/uuidjs/uuid#getrandomvalues-not-supported"
      );
    }
  }

  return getRandomValues(rnds8);
}

/**
 * Convert array of 16 byte values to UUID string format of the form:
 * XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
 */

const byteToHex = [];

for (let i = 0; i < 256; ++i) {
  byteToHex.push((i + 0x100).toString(16).slice(1));
}

function unsafeStringify(arr, offset = 0) {
  // Note: Be careful editing this code!  It's been tuned for performance
  // and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434
  return (
    byteToHex[arr[offset + 0]] +
    byteToHex[arr[offset + 1]] +
    byteToHex[arr[offset + 2]] +
    byteToHex[arr[offset + 3]] +
    "-" +
    byteToHex[arr[offset + 4]] +
    byteToHex[arr[offset + 5]] +
    "-" +
    byteToHex[arr[offset + 6]] +
    byteToHex[arr[offset + 7]] +
    "-" +
    byteToHex[arr[offset + 8]] +
    byteToHex[arr[offset + 9]] +
    "-" +
    byteToHex[arr[offset + 10]] +
    byteToHex[arr[offset + 11]] +
    byteToHex[arr[offset + 12]] +
    byteToHex[arr[offset + 13]] +
    byteToHex[arr[offset + 14]] +
    byteToHex[arr[offset + 15]]
  ).toLowerCase();
}

const randomUUID =
  typeof crypto !== "undefined" &&
  crypto.randomUUID &&
  crypto.randomUUID.bind(crypto);
var native = {
  randomUUID,
};

function v4(options, buf, offset) {
  if (native.randomUUID && !buf && !options) {
    return native.randomUUID();
  }

  options = options || {};
  const rnds = options.random || (options.rng || rng)(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`

  rnds[6] = (rnds[6] & 0x0f) | 0x40;
  rnds[8] = (rnds[8] & 0x3f) | 0x80; // Copy bytes to buffer, if provided

  if (buf) {
    offset = offset || 0;

    for (let i = 0; i < 16; ++i) {
      buf[offset + i] = rnds[i];
    }

    return buf;
  }

  return unsafeStringify(rnds);
}

/**
 * Abstract representation of a selected collection.
 *
 */
class Collection {
  /**
   * Constructor.
   *
   * @param  {KintoClient}  client            The client instance.
   * @param  {Bucket}       bucket            The bucket instance.
   * @param  {String}       name              The collection name.
   * @param  {Object}       [options={}]      The options object.
   * @param  {Object}       [options.headers] The headers object option.
   * @param  {Boolean}      [options.safe]    The safe option.
   * @param  {Number}       [options.retry]   The retry option.
   * @param  {Boolean}      [options.batch]   (Private) Whether this
   *     Collection is operating as part of a batch.
   */
  constructor(client, bucket, name, options = {}) {
    /**
     * @ignore
     */
    this.client = client;
    /**
     * @ignore
     */
    this.bucket = bucket;
    /**
     * The collection name.
     * @type {String}
     */
    this.name = name;
    this._endpoints = client.endpoints;
    /**
     * @ignore
     */
    this._retry = options.retry || 0;
    this._safe = !!options.safe;
    // FIXME: This is kind of ugly; shouldn't the bucket be responsible
    // for doing the merge?
    this._headers = {
      ...this.bucket.headers,
      ...options.headers,
    };
  }
  get execute() {
    return this.client.execute.bind(this.client);
  }
  /**
   * Get the value of "headers" for a given request, merging the
   * per-request headers with our own "default" headers.
   *
   * @private
   */
  _getHeaders(options) {
    return {
      ...this._headers,
      ...options.headers,
    };
  }
  /**
   * Get the value of "safe" for a given request, using the
   * per-request option if present or falling back to our default
   * otherwise.
   *
   * @private
   * @param {Object} options The options for a request.
   * @returns {Boolean}
   */
  _getSafe(options) {
    return { safe: this._safe, ...options }.safe;
  }
  /**
   * As _getSafe, but for "retry".
   *
   * @private
   */
  _getRetry(options) {
    return { retry: this._retry, ...options }.retry;
  }
  /**
   * Retrieves the total number of records in this collection.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Number, Error>}
   */
  async getTotalRecords(options = {}) {
    const path = this._endpoints.record(this.bucket.name, this.name);
    const request = {
      headers: this._getHeaders(options),
      path,
      method: "HEAD",
    };
    const { headers } = await this.client.execute(request, {
      raw: true,
      retry: this._getRetry(options),
    });
    return parseInt(headers.get("Total-Records"), 10);
  }
  /**
   * Retrieves the ETag of the records list, for use with the `since` filtering option.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<String, Error>}
   */
  async getRecordsTimestamp(options = {}) {
    const path = this._endpoints.record(this.bucket.name, this.name);
    const request = {
      headers: this._getHeaders(options),
      path,
      method: "HEAD",
    };
    const { headers } = await this.client.execute(request, {
      raw: true,
      retry: this._getRetry(options),
    });
    return headers.get("ETag");
  }
  /**
   * Retrieves collection data.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Object} [options.query]   Query parameters to pass in
   *     the request. This might be useful for features that aren't
   *     yet supported by this library.
   * @param  {Array}  [options.fields]  Limit response to
   *     just some fields.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async getData(options = {}) {
    const path = this._endpoints.collection(this.bucket.name, this.name);
    const request = { headers: this._getHeaders(options), path };
    const { data } = await this.client.execute(request, {
      retry: this._getRetry(options),
      query: options.query,
      fields: options.fields,
    });
    return data;
  }
  /**
   * Set collection data.
   * @param  {Object}   data                    The collection data object.
   * @param  {Object}   [options={}]            The options object.
   * @param  {Object}   [options.headers]       The headers object option.
   * @param  {Number}   [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean}  [options.safe]          The safe option.
   * @param  {Boolean}  [options.patch]         The patch option.
   * @param  {Number}   [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async setData(data, options = {}) {
    if (!isObject(data)) {
      throw new Error("A collection object is required.");
    }
    const { patch, permissions } = options;
    const { last_modified } = { ...data, ...options };
    const path = this._endpoints.collection(this.bucket.name, this.name);
    const request = updateRequest(
      path,
      { data, permissions },
      {
        last_modified,
        patch,
        headers: this._getHeaders(options),
        safe: this._getSafe(options),
      }
    );
    return this.client.execute(request, {
      retry: this._getRetry(options),
    });
  }
  /**
   * Retrieves the list of permissions for this collection.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async getPermissions(options = {}) {
    const path = this._endpoints.collection(this.bucket.name, this.name);
    const request = { headers: this._getHeaders(options), path };
    const { permissions } = await this.client.execute(request, {
      retry: this._getRetry(options),
    });
    return permissions;
  }
  /**
   * Replaces all existing collection permissions with the ones provided.
   *
   * @param  {Object}   permissions             The permissions object.
   * @param  {Object}   [options={}]            The options object
   * @param  {Object}   [options.headers]       The headers object option.
   * @param  {Number}   [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean}  [options.safe]          The safe option.
   * @param  {Number}   [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async setPermissions(permissions, options = {}) {
    if (!isObject(permissions)) {
      throw new Error("A permissions object is required.");
    }
    const path = this._endpoints.collection(this.bucket.name, this.name);
    const data = { last_modified: options.last_modified };
    const request = updateRequest(
      path,
      { data, permissions },
      {
        headers: this._getHeaders(options),
        safe: this._getSafe(options),
      }
    );
    return this.client.execute(request, {
      retry: this._getRetry(options),
    });
  }
  /**
   * Append principals to the collection permissions.
   *
   * @param  {Object}  permissions             The permissions object.
   * @param  {Object}  [options={}]            The options object
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Object}  [options.headers]       The headers object option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Object}  [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async addPermissions(permissions, options = {}) {
    if (!isObject(permissions)) {
      throw new Error("A permissions object is required.");
    }
    const path = this._endpoints.collection(this.bucket.name, this.name);
    const { last_modified } = options;
    const request = jsonPatchPermissionsRequest(path, permissions, "add", {
      last_modified,
      headers: this._getHeaders(options),
      safe: this._getSafe(options),
    });
    return this.client.execute(request, {
      retry: this._getRetry(options),
    });
  }
  /**
   * Remove principals from the collection permissions.
   *
   * @param  {Object}  permissions             The permissions object.
   * @param  {Object}  [options={}]            The options object
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Object}  [options.headers]       The headers object option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Object}  [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async removePermissions(permissions, options = {}) {
    if (!isObject(permissions)) {
      throw new Error("A permissions object is required.");
    }
    const path = this._endpoints.collection(this.bucket.name, this.name);
    const { last_modified } = options;
    const request = jsonPatchPermissionsRequest(path, permissions, "remove", {
      last_modified,
      headers: this._getHeaders(options),
      safe: this._getSafe(options),
    });
    return this.client.execute(request, {
      retry: this._getRetry(options),
    });
  }
  /**
   * Creates a record in current collection.
   *
   * @param  {Object}  record                The record to create.
   * @param  {Object}  [options={}]          The options object.
   * @param  {Object}  [options.headers]     The headers object option.
   * @param  {Number}  [options.retry=0]     Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean} [options.safe]        The safe option.
   * @param  {Object}  [options.permissions] The permissions option.
   * @return {Promise<Object, Error>}
   */
  async createRecord(record, options = {}) {
    const { permissions } = options;
    const path = this._endpoints.record(this.bucket.name, this.name, record.id);
    const request = createRequest(
      path,
      { data: record, permissions },
      {
        headers: this._getHeaders(options),
        safe: this._getSafe(options),
      }
    );
    return this.client.execute(request, {
      retry: this._getRetry(options),
    });
  }
  /**
   * Adds an attachment to a record, creating the record when it doesn't exist.
   *
   * @param  {String}  dataURL                 The data url.
   * @param  {Object}  [record={}]             The record data.
   * @param  {Object}  [options={}]            The options object.
   * @param  {Object}  [options.headers]       The headers object option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Number}  [options.last_modified] The last_modified option.
   * @param  {Object}  [options.permissions]   The permissions option.
   * @param  {String}  [options.filename]      Force the attachment filename.
   * @param  {String}  [options.gzipped]       Force the attachment to be gzipped or not.
   * @return {Promise<Object, Error>}
   */
  async addAttachment(dataURI, record = {}, options = {}) {
    const { permissions } = options;
    const id = record.id || v4();
    const path = this._endpoints.attachment(this.bucket.name, this.name, id);
    const { last_modified } = { ...record, ...options };
    const addAttachmentRequest$1 = addAttachmentRequest(
      path,
      dataURI,
      { data: record, permissions },
      {
        last_modified,
        filename: options.filename,
        gzipped: options.gzipped,
        headers: this._getHeaders(options),
        safe: this._getSafe(options),
      }
    );
    await this.client.execute(addAttachmentRequest$1, {
      stringify: false,
      retry: this._getRetry(options),
    });
    return this.getRecord(id);
  }
  /**
   * Removes an attachment from a given record.
   *
   * @param  {Object}  recordId                The record id.
   * @param  {Object}  [options={}]            The options object.
   * @param  {Object}  [options.headers]       The headers object option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Number}  [options.last_modified] The last_modified option.
   */
  async removeAttachment(recordId, options = {}) {
    const { last_modified } = options;
    const path = this._endpoints.attachment(
      this.bucket.name,
      this.name,
      recordId
    );
    const request = deleteRequest(path, {
      last_modified,
      headers: this._getHeaders(options),
      safe: this._getSafe(options),
    });
    return this.client.execute(request, {
      retry: this._getRetry(options),
    });
  }
  /**
   * Updates a record in current collection.
   *
   * @param  {Object}  record                  The record to update.
   * @param  {Object}  [options={}]            The options object.
   * @param  {Object}  [options.headers]       The headers object option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Number}  [options.last_modified] The last_modified option.
   * @param  {Object}  [options.permissions]   The permissions option.
   * @return {Promise<Object, Error>}
   */
  async updateRecord(record, options = {}) {
    if (!isObject(record)) {
      throw new Error("A record object is required.");
    }
    if (!record.id) {
      throw new Error("A record id is required.");
    }
    const { permissions } = options;
    const { last_modified } = { ...record, ...options };
    const path = this._endpoints.record(this.bucket.name, this.name, record.id);
    const request = updateRequest(
      path,
      { data: record, permissions },
      {
        headers: this._getHeaders(options),
        safe: this._getSafe(options),
        last_modified,
        patch: !!options.patch,
      }
    );
    return this.client.execute(request, {
      retry: this._getRetry(options),
    });
  }
  /**
   * Deletes a record from the current collection.
   *
   * @param  {Object|String} record                  The record to delete.
   * @param  {Object}        [options={}]            The options object.
   * @param  {Object}        [options.headers]       The headers object option.
   * @param  {Number}        [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean}       [options.safe]          The safe option.
   * @param  {Number}        [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async deleteRecord(record, options = {}) {
    const recordObj = toDataBody(record);
    if (!recordObj.id) {
      throw new Error("A record id is required.");
    }
    const { id } = recordObj;
    const { last_modified } = { ...recordObj, ...options };
    const path = this._endpoints.record(this.bucket.name, this.name, id);
    const request = deleteRequest(path, {
      last_modified,
      headers: this._getHeaders(options),
      safe: this._getSafe(options),
    });
    return this.client.execute(request, {
      retry: this._getRetry(options),
    });
  }
  /**
   * Deletes records from the current collection.
   *
   * Sorting is done by passing a `sort` string option:
   *
   * - The field to order the results by, prefixed with `-` for descending.
   * Default: `-last_modified`.
   *
   * @see http://kinto.readthedocs.io/en/stable/api/1.x/sorting.html
   *
   * Filtering is done by passing a `filters` option object:
   *
   * - `{fieldname: "value"}`
   * - `{min_fieldname: 4000}`
   * - `{in_fieldname: "1,2,3"}`
   * - `{not_fieldname: 0}`
   * - `{exclude_fieldname: "0,1"}`
   *
   * @see http://kinto.readthedocs.io/en/stable/api/1.x/filtering.html
   *
   * @param  {Object}   [options={}]                    The options object.
   * @param  {Object}   [options.headers]               The headers object option.
   * @param  {Number}   [options.retry=0]               Number of retries to make
   *     when faced with transient errors.
   * @param  {Object}   [options.filters={}]            The filters object.
   * @param  {String}   [options.sort="-last_modified"] The sort field.
   * @param  {String}   [options.at]                    The timestamp to get a snapshot at.
   * @param  {String}   [options.limit=null]            The limit field.
   * @param  {String}   [options.pages=1]               The number of result pages to aggregate.
   * @param  {Number}   [options.since=null]            Only retrieve records modified since the provided timestamp.
   * @param  {Array}    [options.fields]                Limit response to just some fields.
   * @return {Promise<Object, Error>}
   */
  async deleteRecords(options = {}) {
    const path = this._endpoints.record(this.bucket.name, this.name);
    return this.client.paginatedDelete(path, options, {
      headers: this._getHeaders(options),
      retry: this._getRetry(options),
    });
  }
  /**
   * Retrieves a record from the current collection.
   *
   * @param  {String} id                The record id to retrieve.
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Object} [options.query]   Query parameters to pass in
   *     the request. This might be useful for features that aren't
   *     yet supported by this library.
   * @param  {Array}  [options.fields]  Limit response to
   *     just some fields.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async getRecord(id, options = {}) {
    const path = this._endpoints.record(this.bucket.name, this.name, id);
    const request = { headers: this._getHeaders(options), path };
    return this.client.execute(request, {
      retry: this._getRetry(options),
      query: options.query,
      fields: options.fields,
    });
  }
  /**
   * Lists records from the current collection.
   *
   * Sorting is done by passing a `sort` string option:
   *
   * - The field to order the results by, prefixed with `-` for descending.
   * Default: `-last_modified`.
   *
   * @see http://kinto.readthedocs.io/en/stable/api/1.x/sorting.html
   *
   * Filtering is done by passing a `filters` option object:
   *
   * - `{fieldname: "value"}`
   * - `{min_fieldname: 4000}`
   * - `{in_fieldname: "1,2,3"}`
   * - `{not_fieldname: 0}`
   * - `{exclude_fieldname: "0,1"}`
   *
   * @see http://kinto.readthedocs.io/en/stable/api/1.x/filtering.html
   *
   * Paginating is done by passing a `limit` option, then calling the `next()`
   * method from the resolved result object to fetch the next page, if any.
   *
   * @param  {Object}   [options={}]                    The options object.
   * @param  {Object}   [options.headers]               The headers object option.
   * @param  {Number}   [options.retry=0]               Number of retries to make
   *     when faced with transient errors.
   * @param  {Object}   [options.filters={}]            The filters object.
   * @param  {String}   [options.sort="-last_modified"] The sort field.
   * @param  {String}   [options.at]                    The timestamp to get a snapshot at.
   * @param  {String}   [options.limit=null]            The limit field.
   * @param  {String}   [options.pages=1]               The number of result pages to aggregate.
   * @param  {Number}   [options.since=null]            Only retrieve records modified since the provided timestamp.
   * @param  {Array}    [options.fields]                Limit response to just some fields.
   * @return {Promise<Object, Error>}
   */
  async listRecords(options = {}) {
    const path = this._endpoints.record(this.bucket.name, this.name);
    if (options.at) {
      return this.getSnapshot(options.at);
    }
    return this.client.paginatedList(path, options, {
      headers: this._getHeaders(options),
      retry: this._getRetry(options),
    });
  }
  /**
   * @private
   */
  async isHistoryComplete() {
    // We consider that if we have the collection creation event part of the
    // history, then all records change events have been tracked.
    const {
      data: [oldestHistoryEntry],
    } = await this.bucket.listHistory({
      limit: 1,
      filters: {
        action: "create",
        resource_name: "collection",
        collection_id: this.name,
      },
    });
    return !!oldestHistoryEntry;
  }
  /**
   * @private
   */
  async getSnapshot(at) {
    if (!at || !Number.isInteger(at) || at <= 0) {
      throw new Error("Invalid argument, expected a positive integer.");
    }
    // Retrieve history and check it covers the required time range.
    // Ensure we have enough history data to retrieve the complete list of
    // changes.
    if (!(await this.isHistoryComplete())) {
      throw new Error(
        "Computing a snapshot is only possible when the full history for a " +
          "collection is available. Here, the history plugin seems to have " +
          "been enabled after the creation of the collection."
      );
    }
    // Because of https://github.com/Kinto/kinto-http.js/issues/963
    // we cannot simply rely on the history endpoint.
    // Our strategy here is to clean-up the history entries from the
    // records that were deleted via the plural endpoint.
    // We will detect them by comparing the current state of the collection
    // and the full history of the collection since its genesis.
    // List full history of collection.
    const { data: fullHistory } = await this.bucket.listHistory({
      pages: Infinity,
      sort: "last_modified",
      filters: {
        resource_name: "record",
        collection_id: this.name,
      },
    });
    // Keep latest entry ever, and latest within snapshot window.
    // (history is sorted chronologically)
    const latestEver = new Map();
    const latestInSnapshot = new Map();
    for (const entry of fullHistory) {
      if (entry.target.data.last_modified <= at) {
        // Snapshot includes changes right on timestamp.
        latestInSnapshot.set(entry.record_id, entry);
      }
      latestEver.set(entry.record_id, entry);
    }
    // Current records ids in the collection.
    const { data: current } = await this.listRecords({
      pages: Infinity,
      fields: ["id"], // we don't need attributes.
    });
    const currentIds = new Set(current.map(record => record.id));
    // If a record is not in the current collection, and its
    // latest history entry isn't a delete then this means that
    // it was deleted via the plural endpoint (and that we lost track
    // of this deletion because of bug #963)
    const deletedViaPlural = new Set();
    for (const entry of latestEver.values()) {
      if (entry.action != "delete" && !currentIds.has(entry.record_id)) {
        deletedViaPlural.add(entry.record_id);
      }
    }
    // Now reconstruct the collection based on latest version in snapshot
    // filtering all deleted records.
    const reconstructed = [];
    for (const entry of latestInSnapshot.values()) {
      if (entry.action != "delete" && !deletedViaPlural.has(entry.record_id)) {
        reconstructed.push(entry.target.data);
      }
    }
    return {
      last_modified: String(at),
      data: Array.from(reconstructed).sort(
        (a, b) => b.last_modified - a.last_modified
      ),
      next: () => {
        throw new Error("Snapshots don't support pagination");
      },
      hasNextPage: false,
      totalRecords: reconstructed.length,
    };
  }
  /**
   * Performs batch operations at the current collection level.
   *
   * @param  {Function} fn                   The batch operation function.
   * @param  {Object}   [options={}]         The options object.
   * @param  {Object}   [options.headers]    The headers object option.
   * @param  {Boolean}  [options.safe]       The safe option.
   * @param  {Number}   [options.retry]      The retry option.
   * @param  {Boolean}  [options.aggregate]  Produces a grouped result object.
   * @return {Promise<Object, Error>}
   */
  async batch(fn, options = {}) {
    return this.client.batch(fn, {
      bucket: this.bucket.name,
      collection: this.name,
      headers: this._getHeaders(options),
      retry: this._getRetry(options),
      safe: this._getSafe(options),
      aggregate: !!options.aggregate,
    });
  }
}
__decorate(
  [capable(["attachments"])],
  Collection.prototype,
  "addAttachment",
  null
);
__decorate(
  [capable(["attachments"])],
  Collection.prototype,
  "removeAttachment",
  null
);
__decorate([capable(["history"])], Collection.prototype, "getSnapshot", null);

/**
 * Abstract representation of a selected bucket.
 *
 */
class Bucket {
  /**
   * Constructor.
   *
   * @param  {KintoClient} client            The client instance.
   * @param  {String}      name              The bucket name.
   * @param  {Object}      [options={}]      The headers object option.
   * @param  {Object}      [options.headers] The headers object option.
   * @param  {Boolean}     [options.safe]    The safe option.
   * @param  {Number}      [options.retry]   The retry option.
   */
  constructor(client, name, options = {}) {
    /**
     * @ignore
     */
    this.client = client;
    /**
     * The bucket name.
     * @type {String}
     */
    this.name = name;
    this._endpoints = client.endpoints;
    /**
     * @ignore
     */
    this._headers = options.headers || {};
    this._retry = options.retry || 0;
    this._safe = !!options.safe;
  }
  get execute() {
    return this.client.execute.bind(this.client);
  }
  get headers() {
    return this._headers;
  }
  /**
   * Get the value of "headers" for a given request, merging the
   * per-request headers with our own "default" headers.
   *
   * @private
   */
  _getHeaders(options) {
    return {
      ...this._headers,
      ...options.headers,
    };
  }
  /**
   * Get the value of "safe" for a given request, using the
   * per-request option if present or falling back to our default
   * otherwise.
   *
   * @private
   * @param {Object} options The options for a request.
   * @returns {Boolean}
   */
  _getSafe(options) {
    return { safe: this._safe, ...options }.safe;
  }
  /**
   * As _getSafe, but for "retry".
   *
   * @private
   */
  _getRetry(options) {
    return { retry: this._retry, ...options }.retry;
  }
  /**
   * Selects a collection.
   *
   * @param  {String}  name              The collection name.
   * @param  {Object}  [options={}]      The options object.
   * @param  {Object}  [options.headers] The headers object option.
   * @param  {Boolean} [options.safe]    The safe option.
   * @return {Collection}
   */
  collection(name, options = {}) {
    return new Collection(this.client, this, name, {
      headers: this._getHeaders(options),
      retry: this._getRetry(options),
      safe: this._getSafe(options),
    });
  }
  /**
   * Retrieves the ETag of the collection list, for use with the `since` filtering option.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<String, Error>}
   */
  async getCollectionsTimestamp(options = {}) {
    const path = this._endpoints.collection(this.name);
    const request = {
      headers: this._getHeaders(options),
      path,
      method: "HEAD",
    };
    const { headers } = await this.client.execute(request, {
      raw: true,
      retry: this._getRetry(options),
    });
    return headers.get("ETag");
  }
  /**
   * Retrieves the ETag of the group list, for use with the `since` filtering option.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<String, Error>}
   */
  async getGroupsTimestamp(options = {}) {
    const path = this._endpoints.group(this.name);
    const request = {
      headers: this._getHeaders(options),
      path,
      method: "HEAD",
    };
    const { headers } = await this.client.execute(request, {
      raw: true,
      retry: this._getRetry(options),
    });
    return headers.get("ETag");
  }
  /**
   * Retrieves bucket data.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Object} [options.query]   Query parameters to pass in
   *     the request. This might be useful for features that aren't
   *     yet supported by this library.
   * @param  {Array}  [options.fields]  Limit response to
   *     just some fields.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async getData(options = {}) {
    const path = this._endpoints.bucket(this.name);
    const request = {
      headers: this._getHeaders(options),
      path,
    };
    const { data } = await this.client.execute(request, {
      retry: this._getRetry(options),
      query: options.query,
      fields: options.fields,
    });
    return data;
  }
  /**
   * Set bucket data.
   * @param  {Object}  data                    The bucket data object.
   * @param  {Object}  [options={}]            The options object.
   * @param  {Object}  [options.headers={}]    The headers object option.
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean} [options.patch]         The patch option.
   * @param  {Number}  [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async setData(data, options = {}) {
    if (!isObject(data)) {
      throw new Error("A bucket object is required.");
    }
    const bucket = {
      ...data,
      id: this.name,
    };
    // For default bucket, we need to drop the id from the data object.
    // Bug in Kinto < 3.1.1
    const bucketId = bucket.id;
    if (bucket.id === "default") {
      delete bucket.id;
    }
    const path = this._endpoints.bucket(bucketId);
    const { patch, permissions } = options;
    const { last_modified } = { ...data, ...options };
    const request = updateRequest(
      path,
      { data: bucket, permissions },
      {
        last_modified,
        patch,
        headers: this._getHeaders(options),
        safe: this._getSafe(options),
      }
    );
    return this.client.execute(request, {
      retry: this._getRetry(options),
    });
  }
  /**
   * Retrieves the list of history entries in the current bucket.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Array<Object>, Error>}
   */
  async listHistory(options = {}) {
    const path = this._endpoints.history(this.name);
    return this.client.paginatedList(path, options, {
      headers: this._getHeaders(options),
      retry: this._getRetry(options),
    });
  }
  /**
   * Retrieves the list of collections in the current bucket.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.filters={}] The filters object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @param  {Array}  [options.fields]  Limit response to
   *     just some fields.
   * @return {Promise<Array<Object>, Error>}
   */
  async listCollections(options = {}) {
    const path = this._endpoints.collection(this.name);
    return this.client.paginatedList(path, options, {
      headers: this._getHeaders(options),
      retry: this._getRetry(options),
    });
  }
  /**
   * Creates a new collection in current bucket.
   *
   * @param  {String|undefined}  id          The collection id.
   * @param  {Object}  [options={}]          The options object.
   * @param  {Boolean} [options.safe]        The safe option.
   * @param  {Object}  [options.headers]     The headers object option.
   * @param  {Number}  [options.retry=0]     Number of retries to make
   *     when faced with transient errors.
   * @param  {Object}  [options.permissions] The permissions object.
   * @param  {Object}  [options.data]        The data object.
   * @return {Promise<Object, Error>}
   */
  async createCollection(id, options = {}) {
    const { permissions, data = {} } = options;
    data.id = id;
    const path = this._endpoints.collection(this.name, id);
    const request = createRequest(
      path,
      { data, permissions },
      {
        headers: this._getHeaders(options),
        safe: this._getSafe(options),
      }
    );
    return this.client.execute(request, {
      retry: this._getRetry(options),
    });
  }
  /**
   * Deletes a collection from the current bucket.
   *
   * @param  {Object|String} collection              The collection to delete.
   * @param  {Object}        [options={}]            The options object.
   * @param  {Object}        [options.headers]       The headers object option.
   * @param  {Number}        [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean}       [options.safe]          The safe option.
   * @param  {Number}        [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async deleteCollection(collection, options = {}) {
    const collectionObj = toDataBody(collection);
    if (!collectionObj.id) {
      throw new Error("A collection id is required.");
    }
    const { id } = collectionObj;
    const { last_modified } = { ...collectionObj, ...options };
    const path = this._endpoints.collection(this.name, id);
    const request = deleteRequest(path, {
      last_modified,
      headers: this._getHeaders(options),
      safe: this._getSafe(options),
    });
    return this.client.execute(request, {
      retry: this._getRetry(options),
    });
  }
  /**
   * Deletes collections from the current bucket.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.filters={}] The filters object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @param  {Array}  [options.fields]  Limit response to
   *     just some fields.
   * @return {Promise<Array<Object>, Error>}
   */
  async deleteCollections(options = {}) {
    const path = this._endpoints.collection(this.name);
    return this.client.paginatedDelete(path, options, {
      headers: this._getHeaders(options),
      retry: this._getRetry(options),
    });
  }
  /**
   * Retrieves the list of groups in the current bucket.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.filters={}] The filters object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @param  {Array}  [options.fields]  Limit response to
   *     just some fields.
   * @return {Promise<Array<Object>, Error>}
   */
  async listGroups(options = {}) {
    const path = this._endpoints.group(this.name);
    return this.client.paginatedList(path, options, {
      headers: this._getHeaders(options),
      retry: this._getRetry(options),
    });
  }
  /**
   * Fetches a group in current bucket.
   *
   * @param  {String} id                The group id.
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @param  {Object} [options.query]   Query parameters to pass in
   *     the request. This might be useful for features that aren't
   *     yet supported by this library.
   * @param  {Array}  [options.fields]  Limit response to
   *     just some fields.
   * @return {Promise<Object, Error>}
   */
  async getGroup(id, options = {}) {
    const path = this._endpoints.group(this.name, id);
    const request = {
      headers: this._getHeaders(options),
      path,
    };
    return this.client.execute(request, {
      retry: this._getRetry(options),
      query: options.query,
      fields: options.fields,
    });
  }
  /**
   * Creates a new group in current bucket.
   *
   * @param  {String|undefined}  id                    The group id.
   * @param  {Array<String>}     [members=[]]          The list of principals.
   * @param  {Object}            [options={}]          The options object.
   * @param  {Object}            [options.data]        The data object.
   * @param  {Object}            [options.permissions] The permissions object.
   * @param  {Boolean}           [options.safe]        The safe option.
   * @param  {Object}            [options.headers]     The headers object option.
   * @param  {Number}            [options.retry=0]     Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async createGroup(id, members = [], options = {}) {
    const data = {
      ...options.data,
      id,
      members,
    };
    const path = this._endpoints.group(this.name, id);
    const { permissions } = options;
    const request = createRequest(
      path,
      { data, permissions },
      {
        headers: this._getHeaders(options),
        safe: this._getSafe(options),
      }
    );
    return this.client.execute(request, {
      retry: this._getRetry(options),
    });
  }
  /**
   * Updates an existing group in current bucket.
   *
   * @param  {Object}  group                   The group object.
   * @param  {Object}  [options={}]            The options object.
   * @param  {Object}  [options.data]          The data object.
   * @param  {Object}  [options.permissions]   The permissions object.
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Object}  [options.headers]       The headers object option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Number}  [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async updateGroup(group, options = {}) {
    if (!isObject(group)) {
      throw new Error("A group object is required.");
    }
    if (!group.id) {
      throw new Error("A group id is required.");
    }
    const data = {
      ...options.data,
      ...group,
    };
    const path = this._endpoints.group(this.name, group.id);
    const { patch, permissions } = options;
    const { last_modified } = { ...data, ...options };
    const request = updateRequest(
      path,
      { data, permissions },
      {
        last_modified,
        patch,
        headers: this._getHeaders(options),
        safe: this._getSafe(options),
      }
    );
    return this.client.execute(request, {
      retry: this._getRetry(options),
    });
  }
  /**
   * Deletes a group from the current bucket.
   *
   * @param  {Object|String} group                   The group to delete.
   * @param  {Object}        [options={}]            The options object.
   * @param  {Object}        [options.headers]       The headers object option.
   * @param  {Number}        [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Boolean}       [options.safe]          The safe option.
   * @param  {Number}        [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async deleteGroup(group, options = {}) {
    const groupObj = toDataBody(group);
    const { id } = groupObj;
    const { last_modified } = { ...groupObj, ...options };
    const path = this._endpoints.group(this.name, id);
    const request = deleteRequest(path, {
      last_modified,
      headers: this._getHeaders(options),
      safe: this._getSafe(options),
    });
    return this.client.execute(request, {
      retry: this._getRetry(options),
    });
  }
  /**
   * Deletes groups from the current bucket.
   *
   * @param  {Object} [options={}]          The options object.
   * @param  {Object} [options.filters={}]  The filters object.
   * @param  {Object} [options.headers]     The headers object option.
   * @param  {Number} [options.retry=0]     Number of retries to make
   *     when faced with transient errors.
   * @param  {Array}  [options.fields]      Limit response to
   *     just some fields.
   * @return {Promise<Array<Object>, Error>}
   */
  async deleteGroups(options = {}) {
    const path = this._endpoints.group(this.name);
    return this.client.paginatedDelete(path, options, {
      headers: this._getHeaders(options),
      retry: this._getRetry(options),
    });
  }
  /**
   * Retrieves the list of permissions for this bucket.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers] The headers object option.
   * @param  {Number} [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async getPermissions(options = {}) {
    const request = {
      headers: this._getHeaders(options),
      path: this._endpoints.bucket(this.name),
    };
    const { permissions } = await this.client.execute(request, {
      retry: this._getRetry(options),
    });
    return permissions;
  }
  /**
   * Replaces all existing bucket permissions with the ones provided.
   *
   * @param  {Object}  permissions             The permissions object.
   * @param  {Object}  [options={}]            The options object
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Object}  [options.headers={}]    The headers object option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Object}  [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async setPermissions(permissions, options = {}) {
    if (!isObject(permissions)) {
      throw new Error("A permissions object is required.");
    }
    const path = this._endpoints.bucket(this.name);
    const { last_modified } = options;
    const data = { last_modified };
    const request = updateRequest(
      path,
      { data, permissions },
      {
        headers: this._getHeaders(options),
        safe: this._getSafe(options),
      }
    );
    return this.client.execute(request, {
      retry: this._getRetry(options),
    });
  }
  /**
   * Append principals to the bucket permissions.
   *
   * @param  {Object}  permissions             The permissions object.
   * @param  {Object}  [options={}]            The options object
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Object}  [options.headers]       The headers object option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Object}  [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async addPermissions(permissions, options = {}) {
    if (!isObject(permissions)) {
      throw new Error("A permissions object is required.");
    }
    const path = this._endpoints.bucket(this.name);
    const { last_modified } = options;
    const request = jsonPatchPermissionsRequest(path, permissions, "add", {
      last_modified,
      headers: this._getHeaders(options),
      safe: this._getSafe(options),
    });
    return this.client.execute(request, {
      retry: this._getRetry(options),
    });
  }
  /**
   * Remove principals from the bucket permissions.
   *
   * @param  {Object}  permissions             The permissions object.
   * @param  {Object}  [options={}]            The options object
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Object}  [options.headers]       The headers object option.
   * @param  {Number}  [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Object}  [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async removePermissions(permissions, options = {}) {
    if (!isObject(permissions)) {
      throw new Error("A permissions object is required.");
    }
    const path = this._endpoints.bucket(this.name);
    const { last_modified } = options;
    const request = jsonPatchPermissionsRequest(path, permissions, "remove", {
      last_modified,
      headers: this._getHeaders(options),
      safe: this._getSafe(options),
    });
    return this.client.execute(request, {
      retry: this._getRetry(options),
    });
  }
  /**
   * Performs batch operations at the current bucket level.
   *
   * @param  {Function} fn                   The batch operation function.
   * @param  {Object}   [options={}]         The options object.
   * @param  {Object}   [options.headers]    The headers object option.
   * @param  {Boolean}  [options.safe]       The safe option.
   * @param  {Number}   [options.retry=0]    The retry option.
   * @param  {Boolean}  [options.aggregate]  Produces a grouped result object.
   * @return {Promise<Object, Error>}
   */
  async batch(fn, options = {}) {
    return this.client.batch(fn, {
      bucket: this.name,
      headers: this._getHeaders(options),
      retry: this._getRetry(options),
      safe: this._getSafe(options),
      aggregate: !!options.aggregate,
    });
  }
}
__decorate([capable(["history"])], Bucket.prototype, "listHistory", null);

/**
 * Currently supported protocol version.
 * @type {String}
 */
const SUPPORTED_PROTOCOL_VERSION = "v1";
/**
 * High level HTTP client for the Kinto API.
 *
 * @example
 * const client = new KintoClient("https://demo.kinto-storage.org/v1");
 * client.bucket("default")
 *    .collection("my-blog")
 *    .createRecord({title: "First article"})
 *   .then(console.log.bind(console))
 *   .catch(console.error.bind(console));
 */
class KintoClientBase {
  /**
   * Constructor.
   *
   * @param  {String}       remote  The remote URL.
   * @param  {Object}       [options={}]                  The options object.
   * @param  {Boolean}      [options.safe=true]           Adds concurrency headers to every requests.
   * @param  {EventEmitter} [options.events=EventEmitter] The events handler instance.
   * @param  {Object}       [options.headers={}]          The key-value headers to pass to each request.
   * @param  {Object}       [options.retry=0]             Number of retries when request fails (default: 0)
   * @param  {String}       [options.bucket="default"]    The default bucket to use.
   * @param  {String}       [options.requestMode="cors"]  The HTTP request mode (from ES6 fetch spec).
   * @param  {Number}       [options.timeout=null]        The request timeout in ms, if any.
   * @param  {Function}     [options.fetchFunc=fetch]     The function to be used to execute HTTP requests.
   */
  constructor(remote, options) {
    if (typeof remote !== "string" || !remote.length) {
      throw new Error("Invalid remote URL: " + remote);
    }
    if (remote[remote.length - 1] === "/") {
      remote = remote.slice(0, -1);
    }
    this._backoffReleaseTime = null;
    this._requests = [];
    this._isBatch = !!options.batch;
    this._retry = options.retry || 0;
    this._safe = !!options.safe;
    this._headers = options.headers || {};
    // public properties
    /**
     * The remote server base URL.
     * @type {String}
     */
    this.remote = remote;
    /**
     * Current server information.
     * @ignore
     * @type {Object|null}
     */
    this.serverInfo = null;
    /**
     * The event emitter instance. Should comply with the `EventEmitter`
     * interface.
     * @ignore
     * @type {Class}
     */
    this.events = options.events;
    this.endpoints = ENDPOINTS;
    const { fetchFunc, requestMode, timeout } = options;
    /**
     * The HTTP instance.
     * @ignore
     * @type {HTTP}
     */
    this.http = new HTTP(this.events, { fetchFunc, requestMode, timeout });
    this._registerHTTPEvents();
  }
  /**
   * The remote endpoint base URL. Setting the value will also extract and
   * validate the version.
   * @type {String}
   */
  get remote() {
    return this._remote;
  }
  /**
   * @ignore
   */
  set remote(url) {
    let version;
    try {
      version = url.match(/\/(v\d+)\/?$/)[1];
    } catch (err) {
      throw new Error("The remote URL must contain the version: " + url);
    }
    if (version !== SUPPORTED_PROTOCOL_VERSION) {
      throw new Error(`Unsupported protocol version: ${version}`);
    }
    this._remote = url;
    this._version = version;
  }
  /**
   * The current server protocol version, eg. `v1`.
   * @type {String}
   */
  get version() {
    return this._version;
  }
  /**
   * Backoff remaining time, in milliseconds. Defaults to zero if no backoff is
   * ongoing.
   *
   * @type {Number}
   */
  get backoff() {
    const currentTime = new Date().getTime();
    if (this._backoffReleaseTime && currentTime < this._backoffReleaseTime) {
      return this._backoffReleaseTime - currentTime;
    }
    return 0;
  }
  /**
   * Registers HTTP events.
   * @private
   */
  _registerHTTPEvents() {
    // Prevent registering event from a batch client instance
    if (!this._isBatch && this.events) {
      this.events.on("backoff", backoffMs => {
        this._backoffReleaseTime = backoffMs;
      });
    }
  }
  /**
   * Retrieve a bucket object to perform operations on it.
   *
   * @param  {String}  name              The bucket name.
   * @param  {Object}  [options={}]      The request options.
   * @param  {Boolean} [options.safe]    The resulting safe option.
   * @param  {Number}  [options.retry]   The resulting retry option.
   * @param  {Object}  [options.headers] The extended headers object option.
   * @return {Bucket}
   */
  bucket(name, options = {}) {
    return new Bucket(this, name, {
      headers: this._getHeaders(options),
      safe: this._getSafe(options),
      retry: this._getRetry(options),
    });
  }
  /**
   * Set client "headers" for every request, updating previous headers (if any).
   *
   * @param {Object} headers The headers to merge with existing ones.
   */
  setHeaders(headers) {
    this._headers = {
      ...this._headers,
      ...headers,
    };
    this.serverInfo = null;
  }
  /**
   * Get the value of "headers" for a given request, merging the
   * per-request headers with our own "default" headers.
   *
   * Note that unlike other options, headers aren't overridden, but
   * merged instead.
   *
   * @private
   * @param {Object} options The options for a request.
   * @returns {Object}
   */
  _getHeaders(options) {
    return {
      ...this._headers,
      ...options.headers,
    };
  }
  /**
   * Get the value of "safe" for a given request, using the
   * per-request option if present or falling back to our default
   * otherwise.
   *
   * @private
   * @param {Object} options The options for a request.
   * @returns {Boolean}
   */
  _getSafe(options) {
    return { safe: this._safe, ...options }.safe;
  }
  /**
   * As _getSafe, but for "retry".
   *
   * @private
   */
  _getRetry(options) {
    return { retry: this._retry, ...options }.retry;
  }
  /**
   * Retrieves the server's "hello" endpoint. This endpoint reveals
   * server capabilities and settings as well as telling the client
   * "who they are" according to their given authorization headers.
   *
   * @private
   * @param  {Object}  [options={}] The request options.
   * @param  {Object}  [options.headers={}] Headers to use when making
   *     this request.
   * @param  {Number}  [options.retry=0]    Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async _getHello(options = {}) {
    const path = this.remote + ENDPOINTS.root();
    const { json } = await this.http.request(
      path,
      { headers: this._getHeaders(options) },
      { retry: this._getRetry(options) }
    );
    return json;
  }
  /**
   * Retrieves server information and persist them locally. This operation is
   * usually performed a single time during the instance lifecycle.
   *
   * @param  {Object}  [options={}] The request options.
   * @param  {Number}  [options.retry=0]    Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async fetchServerInfo(options = {}) {
    if (this.serverInfo) {
      return this.serverInfo;
    }
    this.serverInfo = await this._getHello({ retry: this._getRetry(options) });
    return this.serverInfo;
  }
  /**
   * Retrieves Kinto server settings.
   *
   * @param  {Object}  [options={}] The request options.
   * @param  {Number}  [options.retry=0]    Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async fetchServerSettings(options = {}) {
    const { settings } = await this.fetchServerInfo(options);
    return settings;
  }
  /**
   * Retrieve server capabilities information.
   *
   * @param  {Object}  [options={}] The request options.
   * @param  {Number}  [options.retry=0]    Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async fetchServerCapabilities(options = {}) {
    const { capabilities } = await this.fetchServerInfo(options);
    return capabilities;
  }
  /**
   * Retrieve authenticated user information.
   *
   * @param  {Object}  [options={}] The request options.
   * @param  {Object}  [options.headers={}] Headers to use when making
   *     this request.
   * @param  {Number}  [options.retry=0]    Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async fetchUser(options = {}) {
    const { user } = await this._getHello(options);
    return user;
  }
  /**
   * Retrieve authenticated user information.
   *
   * @param  {Object}  [options={}] The request options.
   * @param  {Number}  [options.retry=0]    Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async fetchHTTPApiVersion(options = {}) {
    const { http_api_version } = await this.fetchServerInfo(options);
    return http_api_version;
  }
  /**
   * Process batch requests, chunking them according to the batch_max_requests
   * server setting when needed.
   *
   * @param  {Array}  requests     The list of batch subrequests to perform.
   * @param  {Object} [options={}] The options object.
   * @return {Promise<Object, Error>}
   */
  async _batchRequests(requests, options = {}) {
    const headers = this._getHeaders(options);
    if (!requests.length) {
      return [];
    }
    const serverSettings = await this.fetchServerSettings({
      retry: this._getRetry(options),
    });
    const maxRequests = serverSettings.batch_max_requests;
    if (maxRequests && requests.length > maxRequests) {
      const chunks = partition(requests, maxRequests);
      const results = [];
      for (const chunk of chunks) {
        const result = await this._batchRequests(chunk, options);
        results.push(...result);
      }
      return results;
    }
    const { responses } = await this.execute(
      {
        // FIXME: is this really necessary, since it's also present in
        // the "defaults"?
        headers,
        path: ENDPOINTS.batch(),
        method: "POST",
        body: {
          defaults: { headers },
          requests,
        },
      },
      { retry: this._getRetry(options) }
    );
    return responses;
  }
  /**
   * Sends batch requests to the remote server.
   *
   * Note: Reserved for internal use only.
   *
   * @ignore
   * @param  {Function} fn                        The function to use for describing batch ops.
   * @param  {Object}   [options={}]              The options object.
   * @param  {Boolean}  [options.safe]            The safe option.
   * @param  {Number}   [options.retry]           The retry option.
   * @param  {String}   [options.bucket]          The bucket name option.
   * @param  {String}   [options.collection]      The collection name option.
   * @param  {Object}   [options.headers]         The headers object option.
   * @param  {Boolean}  [options.aggregate=false] Produces an aggregated result object.
   * @return {Promise<Object, Error>}
   */
  async batch(fn, options = {}) {
    const rootBatch = new KintoClientBase(this.remote, {
      events: this.events,
      batch: true,
      safe: this._getSafe(options),
      retry: this._getRetry(options),
    });
    if (options.bucket && options.collection) {
      fn(rootBatch.bucket(options.bucket).collection(options.collection));
    } else if (options.bucket) {
      fn(rootBatch.bucket(options.bucket));
    } else {
      fn(rootBatch);
    }
    const responses = await this._batchRequests(rootBatch._requests, options);
    if (options.aggregate) {
      return aggregate(responses, rootBatch._requests);
    }
    return responses;
  }
  async execute(request, options = {}) {
    const { raw = false, stringify = true } = options;
    // If we're within a batch, add the request to the stack to send at once.
    if (this._isBatch) {
      this._requests.push(request);
      // Resolve with a message in case people attempt at consuming the result
      // from within a batch operation.
      const msg =
        "This result is generated from within a batch " +
        "operation and should not be consumed.";
      return raw ? { status: 0, json: msg, headers: new Headers() } : msg;
    }
    const uri = this.remote + addEndpointOptions(request.path, options);
    const result = await this.http.request(
      uri,
      cleanUndefinedProperties({
        // Limit requests to only those parts that would be allowed in
        // a batch request -- don't pass through other fancy fetch()
        // options like integrity, redirect, mode because they will
        // break on a batch request.  A batch request only allows
        // headers, method, path (above), and body.
        method: request.method,
        headers: request.headers,
        body: stringify ? JSON.stringify(request.body) : request.body,
      }),
      { retry: this._getRetry(options) }
    );
    return raw ? result : result.json;
  }
  /**
   * Perform an operation with a given HTTP method on some pages from
   * a paginated list, following the `next-page` header automatically
   * until we have processed the requested number of pages. Return a
   * response with a `.next()` method that can be called to perform
   * the requested HTTP method on more results.
   *
   * @private
   * @param  {String}  path
   *     The path to make the request to.
   * @param  {Object}  params
   *     The parameters to use when making the request.
   * @param  {String}  [params.sort="-last_modified"]
   *     The sorting order to use when doing operation on pages.
   * @param  {Object}  [params.filters={}]
   *     The filters to send in the request.
   * @param  {Number}  [params.limit=undefined]
   *     The limit to send in the request. Undefined means no limit.
   * @param  {Number}  [params.pages=undefined]
   *     The number of pages to operate on. Undefined means one page. Pass
   *     Infinity to operate on everything.
   * @param  {String}  [params.since=undefined]
   *     The ETag from which to start doing operation on pages.
   * @param  {Array}   [params.fields]
   *     Limit response to just some fields.
   * @param  {Object}  [options={}]
   *     Additional request-level parameters to use in all requests.
   * @param  {Object}  [options.headers={}]
   *     Headers to use during all requests.
   * @param  {Number}  [options.retry=0]
   *     Number of times to retry each request if the server responds
   *     with Retry-After.
   * @param  {String}  [options.method="GET"]
   *     The method to use in the request.
   */
  async paginatedOperation(path, params = {}, options = {}) {
    // FIXME: this is called even in batch requests, which doesn't
    // make any sense (since all batch requests get a "dummy"
    // response; see execute() above).
    const { sort, filters, limit, pages, since, fields } = {
      sort: "-last_modified",
      ...params,
    };
    // Safety/Consistency check on ETag value.
    if (since && typeof since !== "string") {
      throw new Error(
        `Invalid value for since (${since}), should be ETag value.`
      );
    }
    const query = {
      ...filters,
      _sort: sort,
      _limit: limit,
      _since: since,
    };
    if (fields) {
      query._fields = fields;
    }
    const querystring = qsify(query);
    let results = [],
      current = 0;
    const next = async function (nextPage) {
      if (!nextPage) {
        throw new Error("Pagination exhausted.");
      }
      return processNextPage(nextPage);
    };
    const processNextPage = async nextPage => {
      const { headers } = options;
      return handleResponse(await this.http.request(nextPage, { headers }));
    };
    const pageResults = (results, nextPage, etag) => {
      // ETag string is supposed to be opaque and stored «as-is».
      // ETag header values are quoted (because of * and W/"foo").
      return {
        last_modified: etag ? etag.replace(/"/g, "") : etag,
        data: results,
        next: next.bind(null, nextPage),
        hasNextPage: !!nextPage,
        totalRecords: -1,
      };
    };
    const handleResponse = async function ({
      headers = new Headers(),
      json = {},
    }) {
      const nextPage = headers.get("Next-Page");
      const etag = headers.get("ETag");
      if (!pages) {
        return pageResults(json.data, nextPage, etag);
      }
      // Aggregate new results with previous ones
      results = results.concat(json.data);
      current += 1;
      if (current >= pages || !nextPage) {
        // Pagination exhausted
        return pageResults(results, nextPage, etag);
      }
      // Follow next page
      return processNextPage(nextPage);
    };
    return handleResponse(
      await this.execute(
        // N.B.: This doesn't use _getHeaders, because all calls to
        // `paginatedList` are assumed to come from calls that already
        // have headers merged at e.g. the bucket or collection level.
        {
          headers: options.headers ? options.headers : {},
          path: path + "?" + querystring,
          method: options.method,
        },
        // N.B. This doesn't use _getRetry, because all calls to
        // `paginatedList` are assumed to come from calls that already
        // used `_getRetry` at e.g. the bucket or collection level.
        { raw: true, retry: options.retry || 0 }
      )
    );
  }
  /**
   * Fetch some pages from a paginated list, following the `next-page`
   * header automatically until we have fetched the requested number
   * of pages. Return a response with a `.next()` method that can be
   * called to fetch more results.
   *
   * @private
   * @param  {String}  path
   *     The path to make the request to.
   * @param  {Object}  params
   *     The parameters to use when making the request.
   * @param  {String}  [params.sort="-last_modified"]
   *     The sorting order to use when fetching.
   * @param  {Object}  [params.filters={}]
   *     The filters to send in the request.
   * @param  {Number}  [params.limit=undefined]
   *     The limit to send in the request. Undefined means no limit.
   * @param  {Number}  [params.pages=undefined]
   *     The number of pages to fetch. Undefined means one page. Pass
   *     Infinity to fetch everything.
   * @param  {String}  [params.since=undefined]
   *     The ETag from which to start fetching.
   * @param  {Array}   [params.fields]
   *     Limit response to just some fields.
   * @param  {Object}  [options={}]
   *     Additional request-level parameters to use in all requests.
   * @param  {Object}  [options.headers={}]
   *     Headers to use during all requests.
   * @param  {Number}  [options.retry=0]
   *     Number of times to retry each request if the server responds
   *     with Retry-After.
   */
  async paginatedList(path, params = {}, options = {}) {
    return this.paginatedOperation(path, params, options);
  }
  /**
   * Delete multiple objects, following the pagination if the number of
   * objects exceeds the page limit until we have deleted the requested
   * number of pages. Return a response with a `.next()` method that can
   * be called to delete more results.
   *
   * @private
   * @param  {String}  path
   *     The path to make the request to.
   * @param  {Object}  params
   *     The parameters to use when making the request.
   * @param  {String}  [params.sort="-last_modified"]
   *     The sorting order to use when deleting.
   * @param  {Object}  [params.filters={}]
   *     The filters to send in the request.
   * @param  {Number}  [params.limit=undefined]
   *     The limit to send in the request. Undefined means no limit.
   * @param  {Number}  [params.pages=undefined]
   *     The number of pages to delete. Undefined means one page. Pass
   *     Infinity to delete everything.
   * @param  {String}  [params.since=undefined]
   *     The ETag from which to start deleting.
   * @param  {Array}   [params.fields]
   *     Limit response to just some fields.
   * @param  {Object}  [options={}]
   *     Additional request-level parameters to use in all requests.
   * @param  {Object}  [options.headers={}]
   *     Headers to use during all requests.
   * @param  {Number}  [options.retry=0]
   *     Number of times to retry each request if the server responds
   *     with Retry-After.
   */
  paginatedDelete(path, params = {}, options = {}) {
    const { headers, safe, last_modified } = options;
    const deleteRequest$1 = deleteRequest(path, {
      headers,
      safe: safe ? safe : false,
      last_modified,
    });
    return this.paginatedOperation(path, params, {
      ...options,
      headers: deleteRequest$1.headers,
      method: "DELETE",
    });
  }
  /**
   * Lists all permissions.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers={}] Headers to use when making
   *     this request.
   * @param  {Number} [options.retry=0]    Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object[], Error>}
   */
  async listPermissions(options = {}) {
    const path = ENDPOINTS.permissions();
    // Ensure the default sort parameter is something that exists in permissions
    // entries, as `last_modified` doesn't; here, we pick "id".
    const paginationOptions = { sort: "id", ...options };
    return this.paginatedList(path, paginationOptions, {
      headers: this._getHeaders(options),
      retry: this._getRetry(options),
    });
  }
  /**
   * Retrieves the list of buckets.
   *
   * @param  {Object} [options={}]      The options object.
   * @param  {Object} [options.headers={}] Headers to use when making
   *     this request.
   * @param  {Number} [options.retry=0]    Number of retries to make
   *     when faced with transient errors.
   * @param  {Object} [options.filters={}] The filters object.
   * @param  {Array}  [options.fields]     Limit response to
   *     just some fields.
   * @return {Promise<Object[], Error>}
   */
  async listBuckets(options = {}) {
    const path = ENDPOINTS.bucket();
    return this.paginatedList(path, options, {
      headers: this._getHeaders(options),
      retry: this._getRetry(options),
    });
  }
  /**
   * Creates a new bucket on the server.
   *
   * @param  {String|null}  id                The bucket name (optional).
   * @param  {Object}       [options={}]      The options object.
   * @param  {Boolean}      [options.data]    The bucket data option.
   * @param  {Boolean}      [options.safe]    The safe option.
   * @param  {Object}       [options.headers] The headers object option.
   * @param  {Number}       [options.retry=0] Number of retries to make
   *     when faced with transient errors.
   * @return {Promise<Object, Error>}
   */
  async createBucket(id, options = {}) {
    const { data, permissions } = options;
    const _data = { ...data, id: id ? id : undefined };
    const path = _data.id ? ENDPOINTS.bucket(_data.id) : ENDPOINTS.bucket();
    return this.execute(
      createRequest(
        path,
        { data: _data, permissions },
        {
          headers: this._getHeaders(options),
          safe: this._getSafe(options),
        }
      ),
      { retry: this._getRetry(options) }
    );
  }
  /**
   * Deletes a bucket from the server.
   *
   * @ignore
   * @param  {Object|String} bucket                  The bucket to delete.
   * @param  {Object}        [options={}]            The options object.
   * @param  {Boolean}       [options.safe]          The safe option.
   * @param  {Object}        [options.headers]       The headers object option.
   * @param  {Number}        [options.retry=0]       Number of retries to make
   *     when faced with transient errors.
   * @param  {Number}        [options.last_modified] The last_modified option.
   * @return {Promise<Object, Error>}
   */
  async deleteBucket(bucket, options = {}) {
    const bucketObj = toDataBody(bucket);
    if (!bucketObj.id) {
      throw new Error("A bucket id is required.");
    }
    const path = ENDPOINTS.bucket(bucketObj.id);
    const { last_modified } = { ...bucketObj, ...options };
    return this.execute(
      deleteRequest(path, {
        last_modified,
        headers: this._getHeaders(options),
        safe: this._getSafe(options),
      }),
      { retry: this._getRetry(options) }
    );
  }
  /**
   * Deletes buckets.
   *
   * @param  {Object} [options={}]             The options object.
   * @param  {Boolean} [options.safe]          The safe option.
   * @param  {Object} [options.headers={}]     Headers to use when making
   *     this request.
   * @param  {Number} [options.retry=0]        Number of retries to make
   *     when faced with transient errors.
   * @param  {Object} [options.filters={}]     The filters object.
   * @param  {Array}  [options.fields]         Limit response to
   *     just some fields.
   * @param  {Number}  [options.last_modified] The last_modified option.
   * @return {Promise<Object[], Error>}
   */
  async deleteBuckets(options = {}) {
    const path = ENDPOINTS.bucket();
    return this.paginatedDelete(path, options, {
      headers: this._getHeaders(options),
      retry: this._getRetry(options),
      safe: options.safe,
      last_modified: options.last_modified,
    });
  }
  async createAccount(username, password) {
    return this.execute(
      createRequest(
        `/accounts/${username}`,
        { data: { password } },
        { method: "PUT" }
      )
    );
  }
}
__decorate(
  [nobatch("This operation is not supported within a batch operation.")],
  KintoClientBase.prototype,
  "fetchServerSettings",
  null
);
__decorate(
  [nobatch("This operation is not supported within a batch operation.")],
  KintoClientBase.prototype,
  "fetchServerCapabilities",
  null
);
__decorate(
  [nobatch("This operation is not supported within a batch operation.")],
  KintoClientBase.prototype,
  "fetchUser",
  null
);
__decorate(
  [nobatch("This operation is not supported within a batch operation.")],
  KintoClientBase.prototype,
  "fetchHTTPApiVersion",
  null
);
__decorate(
  [nobatch("Can't use batch within a batch!")],
  KintoClientBase.prototype,
  "batch",
  null
);
__decorate(
  [capable(["permissions_endpoint"])],
  KintoClientBase.prototype,
  "listPermissions",
  null
);
__decorate(
  [support("1.4", "2.0")],
  KintoClientBase.prototype,
  "deleteBuckets",
  null
);
__decorate(
  [capable(["accounts"])],
  KintoClientBase.prototype,
  "createAccount",
  null
);

/*
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/* @ts-ignore */
class KintoHttpClient extends KintoClientBase {
  constructor(remote, options = {}) {
    const events = {};
    EventEmitter.decorate(events);
    super(remote, { events: events, ...options });
  }
}
KintoHttpClient.errors = errors;

export { KintoHttpClient };
PK
!<��sC~C~4modules/services-common/kinto-offline-client.sys.mjs/*
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
 * This file is generated from kinto.js - do not modify directly.
 */

/*
 * Version 13.0.0 - 7fbf95d
 */

/**
 * Base db adapter.
 *
 * @abstract
 */
class BaseAdapter {
    /**
     * Deletes every records present in the database.
     *
     * @abstract
     * @return {Promise}
     */
    clear() {
        throw new Error("Not Implemented.");
    }
    /**
     * Executes a batch of operations within a single transaction.
     *
     * @abstract
     * @param  {Function} callback The operation callback.
     * @param  {Object}   options  The options object.
     * @return {Promise}
     */
    execute(callback, options = { preload: [] }) {
        throw new Error("Not Implemented.");
    }
    /**
     * Retrieve a record by its primary key from the database.
     *
     * @abstract
     * @param  {String} id The record id.
     * @return {Promise}
     */
    get(id) {
        throw new Error("Not Implemented.");
    }
    /**
     * Lists all records from the database.
     *
     * @abstract
     * @param  {Object} params  The filters and order to apply to the results.
     * @return {Promise}
     */
    list(params = { filters: {}, order: "" }) {
        throw new Error("Not Implemented.");
    }
    /**
     * Store the lastModified value.
     *
     * @abstract
     * @param  {Number}  lastModified
     * @return {Promise}
     */
    saveLastModified(lastModified) {
        throw new Error("Not Implemented.");
    }
    /**
     * Retrieve saved lastModified value.
     *
     * @abstract
     * @return {Promise}
     */
    getLastModified() {
        throw new Error("Not Implemented.");
    }
    /**
     * Load records in bulk that were exported from a server.
     *
     * @abstract
     * @param  {Array} records The records to load.
     * @return {Promise}
     */
    importBulk(records) {
        throw new Error("Not Implemented.");
    }
    /**
     * Load a dump of records exported from a server.
     *
     * @deprecated Use {@link importBulk} instead.
     * @abstract
     * @param  {Array} records The records to load.
     * @return {Promise}
     */
    loadDump(records) {
        throw new Error("Not Implemented.");
    }
    saveMetadata(metadata) {
        throw new Error("Not Implemented.");
    }
    getMetadata() {
        throw new Error("Not Implemented.");
    }
}

const RE_RECORD_ID = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
/**
 * Checks if a value is undefined.
 * @param  {Any}  value
 * @return {Boolean}
 */
function _isUndefined(value) {
    return typeof value === "undefined";
}
/**
 * Sorts records in a list according to a given ordering.
 *
 * @param  {String} order The ordering, eg. `-last_modified`.
 * @param  {Array}  list  The collection to order.
 * @return {Array}
 */
function sortObjects(order, list) {
    const hasDash = order[0] === "-";
    const field = hasDash ? order.slice(1) : order;
    const direction = hasDash ? -1 : 1;
    return list.slice().sort((a, b) => {
        if (a[field] && _isUndefined(b[field])) {
            return direction;
        }
        if (b[field] && _isUndefined(a[field])) {
            return -direction;
        }
        if (_isUndefined(a[field]) && _isUndefined(b[field])) {
            return 0;
        }
        return a[field] > b[field] ? direction : -direction;
    });
}
/**
 * Test if a single object matches all given filters.
 *
 * @param  {Object} filters  The filters object.
 * @param  {Object} entry    The object to filter.
 * @return {Boolean}
 */
function filterObject(filters, entry) {
    return Object.keys(filters).every(filter => {
        const value = filters[filter];
        if (Array.isArray(value)) {
            return value.some(candidate => candidate === entry[filter]);
        }
        else if (typeof value === "object") {
            return filterObject(value, entry[filter]);
        }
        else if (!Object.prototype.hasOwnProperty.call(entry, filter)) {
            console.error(`The property ${filter} does not exist`);
            return false;
        }
        return entry[filter] === value;
    });
}
/**
 * Resolves a list of functions sequentially, which can be sync or async; in
 * case of async, functions must return a promise.
 *
 * @param  {Array} fns  The list of functions.
 * @param  {Any}   init The initial value.
 * @return {Promise}
 */
function waterfall(fns, init) {
    if (!fns.length) {
        return Promise.resolve(init);
    }
    return fns.reduce((promise, nextFn) => {
        return promise.then(nextFn);
    }, Promise.resolve(init));
}
/**
 * Simple deep object comparison function. This only supports comparison of
 * serializable JavaScript objects.
 *
 * @param  {Object} a The source object.
 * @param  {Object} b The compared object.
 * @return {Boolean}
 */
function deepEqual(a, b) {
    if (a === b) {
        return true;
    }
    if (typeof a !== typeof b) {
        return false;
    }
    if (!(a && typeof a == "object") || !(b && typeof b == "object")) {
        return false;
    }
    if (Object.keys(a).length !== Object.keys(b).length) {
        return false;
    }
    for (const k in a) {
        if (!deepEqual(a[k], b[k])) {
            return false;
        }
    }
    return true;
}
/**
 * Return an object without the specified keys.
 *
 * @param  {Object} obj        The original object.
 * @param  {Array}  keys       The list of keys to exclude.
 * @return {Object}            A copy without the specified keys.
 */
function omitKeys(obj, keys = []) {
    const result = Object.assign({}, obj);
    for (const key of keys) {
        delete result[key];
    }
    return result;
}
function arrayEqual(a, b) {
    if (a.length !== b.length) {
        return false;
    }
    for (let i = a.length; i--;) {
        if (a[i] !== b[i]) {
            return false;
        }
    }
    return true;
}
function makeNestedObjectFromArr(arr, val, nestedFiltersObj) {
    const last = arr.length - 1;
    return arr.reduce((acc, cv, i) => {
        if (i === last) {
            return (acc[cv] = val);
        }
        else if (Object.prototype.hasOwnProperty.call(acc, cv)) {
            return acc[cv];
        }
        else {
            return (acc[cv] = {});
        }
    }, nestedFiltersObj);
}
function transformSubObjectFilters(filtersObj) {
    const transformedFilters = {};
    for (const key in filtersObj) {
        const keysArr = key.split(".");
        const val = filtersObj[key];
        makeNestedObjectFromArr(keysArr, val, transformedFilters);
    }
    return transformedFilters;
}

const INDEXED_FIELDS = ["id", "_status", "last_modified"];
/**
 * Small helper that wraps the opening of an IndexedDB into a Promise.
 *
 * @param dbname          {String}   The database name.
 * @param version         {Integer}  Schema version
 * @param onupgradeneeded {Function} The callback to execute if schema is
 *                                   missing or different.
 * @return {Promise<IDBDatabase>}
 */
async function open(dbname, { version, onupgradeneeded }) {
    return new Promise((resolve, reject) => {
        const request = indexedDB.open(dbname, version);
        request.onupgradeneeded = event => {
            const db = event.target.result;
            db.onerror = event => reject(event.target.error);
            // When an upgrade is needed, a transaction is started.
            const transaction = event.target.transaction;
            transaction.onabort = event => {
                const error = event.target.error ||
                    transaction.error ||
                    new DOMException("The operation has been aborted", "AbortError");
                reject(error);
            };
            // Callback for store creation etc.
            return onupgradeneeded(event);
        };
        request.onerror = event => {
            reject(event.target.error);
        };
        request.onsuccess = event => {
            const db = event.target.result;
            resolve(db);
        };
    });
}
/**
 * Helper to run the specified callback in a single transaction on the
 * specified store.
 * The helper focuses on transaction wrapping into a promise.
 *
 * @param db           {IDBDatabase} The database instance.
 * @param name         {String}      The store name.
 * @param callback     {Function}    The piece of code to execute in the transaction.
 * @param options      {Object}      Options.
 * @param options.mode {String}      Transaction mode (default: read).
 * @return {Promise} any value returned by the callback.
 */
async function execute(db, name, callback, options = {}) {
    const { mode } = options;
    return new Promise((resolve, reject) => {
        // On Safari, calling IDBDatabase.transaction with mode == undefined raises
        // a TypeError.
        const transaction = mode
            ? db.transaction([name], mode)
            : db.transaction([name]);
        const store = transaction.objectStore(name);
        // Let the callback abort this transaction.
        const abort = e => {
            transaction.abort();
            reject(e);
        };
        // Execute the specified callback **synchronously**.
        let result;
        try {
            result = callback(store, abort);
        }
        catch (e) {
            abort(e);
        }
        transaction.onerror = event => reject(event.target.error);
        transaction.oncomplete = event => resolve(result);
        transaction.onabort = event => {
            const error = event.target.error ||
                transaction.error ||
                new DOMException("The operation has been aborted", "AbortError");
            reject(error);
        };
    });
}
/**
 * Helper to wrap the deletion of an IndexedDB database into a promise.
 *
 * @param dbName {String} the database to delete
 * @return {Promise}
 */
async function deleteDatabase(dbName) {
    return new Promise((resolve, reject) => {
        const request = indexedDB.deleteDatabase(dbName);
        request.onsuccess = event => resolve(event.target);
        request.onerror = event => reject(event.target.error);
    });
}
/**
 * IDB cursor handlers.
 * @type {Object}
 */
const cursorHandlers = {
    all(filters, done) {
        const results = [];
        return event => {
            const cursor = event.target.result;
            if (cursor) {
                const { value } = cursor;
                if (filterObject(filters, value)) {
                    results.push(value);
                }
                cursor.continue();
            }
            else {
                done(results);
            }
        };
    },
    in(values, filters, done) {
        const results = [];
        let i = 0;
        return function (event) {
            const cursor = event.target.result;
            if (!cursor) {
                done(results);
                return;
            }
            const { key, value } = cursor;
            // `key` can be an array of two values (see `keyPath` in indices definitions).
            // `values` can be an array of arrays if we filter using an index whose key path
            // is an array (eg. `cursorHandlers.in([["bid/cid", 42], ["bid/cid", 43]], ...)`)
            while (key > values[i]) {
                // The cursor has passed beyond this key. Check next.
                ++i;
                if (i === values.length) {
                    done(results); // There is no next. Stop searching.
                    return;
                }
            }
            const isEqual = Array.isArray(key)
                ? arrayEqual(key, values[i])
                : key === values[i];
            if (isEqual) {
                if (filterObject(filters, value)) {
                    results.push(value);
                }
                cursor.continue();
            }
            else {
                cursor.continue(values[i]);
            }
        };
    },
};
/**
 * Creates an IDB request and attach it the appropriate cursor event handler to
 * perform a list query.
 *
 * Multiple matching values are handled by passing an array.
 *
 * @param  {String}           cid        The collection id (ie. `{bid}/{cid}`)
 * @param  {IDBStore}         store      The IDB store.
 * @param  {Object}           filters    Filter the records by field.
 * @param  {Function}         done       The operation completion handler.
 * @return {IDBRequest}
 */
function createListRequest(cid, store, filters, done) {
    const filterFields = Object.keys(filters);
    // If no filters, get all results in one bulk.
    if (filterFields.length == 0) {
        const request = store.index("cid").getAll(IDBKeyRange.only(cid));
        request.onsuccess = event => done(event.target.result);
        return request;
    }
    // Introspect filters and check if they leverage an indexed field.
    const indexField = filterFields.find(field => {
        return INDEXED_FIELDS.includes(field);
    });
    if (!indexField) {
        // Iterate on all records for this collection (ie. cid)
        const isSubQuery = Object.keys(filters).some(key => key.includes(".")); // (ie. filters: {"article.title": "hello"})
        if (isSubQuery) {
            const newFilter = transformSubObjectFilters(filters);
            const request = store.index("cid").openCursor(IDBKeyRange.only(cid));
            request.onsuccess = cursorHandlers.all(newFilter, done);
            return request;
        }
        const request = store.index("cid").openCursor(IDBKeyRange.only(cid));
        request.onsuccess = cursorHandlers.all(filters, done);
        return request;
    }
    // If `indexField` was used already, don't filter again.
    const remainingFilters = omitKeys(filters, [indexField]);
    // value specified in the filter (eg. `filters: { _status: ["created", "updated"] }`)
    const value = filters[indexField];
    // For the "id" field, use the primary key.
    const indexStore = indexField == "id" ? store : store.index(indexField);
    // WHERE IN equivalent clause
    if (Array.isArray(value)) {
        if (value.length === 0) {
            return done([]);
        }
        const values = value.map(i => [cid, i]).sort();
        const range = IDBKeyRange.bound(values[0], values[values.length - 1]);
        const request = indexStore.openCursor(range);
        request.onsuccess = cursorHandlers.in(values, remainingFilters, done);
        return request;
    }
    // If no filters on custom attribute, get all results in one bulk.
    if (remainingFilters.length == 0) {
        const request = indexStore.getAll(IDBKeyRange.only([cid, value]));
        request.onsuccess = event => done(event.target.result);
        return request;
    }
    // WHERE field = value clause
    const request = indexStore.openCursor(IDBKeyRange.only([cid, value]));
    request.onsuccess = cursorHandlers.all(remainingFilters, done);
    return request;
}
class IDBError extends Error {
    constructor(method, err) {
        super(`IndexedDB ${method}() ${err.message}`);
        this.name = err.name;
        this.stack = err.stack;
    }
}
/**
 * IndexedDB adapter.
 *
 * This adapter doesn't support any options.
 */
class IDB extends BaseAdapter {
    /* Expose the IDBError class publicly */
    static get IDBError() {
        return IDBError;
    }
    /**
     * Constructor.
     *
     * @param  {String} cid  The key base for this collection (eg. `bid/cid`)
     * @param  {Object} options
     * @param  {String} options.dbName         The IndexedDB name (default: `"KintoDB"`)
     * @param  {String} options.migrateOldData Whether old database data should be migrated (default: `false`)
     */
    constructor(cid, options = {}) {
        super();
        this.cid = cid;
        this.dbName = options.dbName || "KintoDB";
        this._options = options;
        this._db = null;
    }
    _handleError(method, err) {
        throw new IDBError(method, err);
    }
    /**
     * Ensures a connection to the IndexedDB database has been opened.
     *
     * @override
     * @return {Promise}
     */
    async open() {
        if (this._db) {
            return this;
        }
        // In previous versions, we used to have a database with name `${bid}/${cid}`.
        // Check if it exists, and migrate data once new schema is in place.
        // Note: the built-in migrations from IndexedDB can only be used if the
        // database name does not change.
        const dataToMigrate = this._options.migrateOldData
            ? await migrationRequired(this.cid)
            : null;
        this._db = await open(this.dbName, {
            version: 2,
            onupgradeneeded: event => {
                const db = event.target.result;
                if (event.oldVersion < 1) {
                    // Records store
                    const recordsStore = db.createObjectStore("records", {
                        keyPath: ["_cid", "id"],
                    });
                    // An index to obtain all the records in a collection.
                    recordsStore.createIndex("cid", "_cid");
                    // Here we create indices for every known field in records by collection.
                    // Local record status ("synced", "created", "updated", "deleted")
                    recordsStore.createIndex("_status", ["_cid", "_status"]);
                    // Last modified field
                    recordsStore.createIndex("last_modified", ["_cid", "last_modified"]);
                    // Timestamps store
                    db.createObjectStore("timestamps", {
                        keyPath: "cid",
                    });
                }
                if (event.oldVersion < 2) {
                    // Collections store
                    db.createObjectStore("collections", {
                        keyPath: "cid",
                    });
                }
            },
        });
        if (dataToMigrate) {
            const { records, timestamp } = dataToMigrate;
            await this.importBulk(records);
            await this.saveLastModified(timestamp);
            console.log(`${this.cid}: data was migrated successfully.`);
            // Delete the old database.
            await deleteDatabase(this.cid);
            console.warn(`${this.cid}: old database was deleted.`);
        }
        return this;
    }
    /**
     * Closes current connection to the database.
     *
     * @override
     * @return {Promise}
     */
    close() {
        if (this._db) {
            this._db.close(); // indexedDB.close is synchronous
            this._db = null;
        }
        return Promise.resolve();
    }
    /**
     * Returns a transaction and an object store for a store name.
     *
     * To determine if a transaction has completed successfully, we should rather
     * listen to the transaction’s complete event rather than the IDBObjectStore
     * request’s success event, because the transaction may still fail after the
     * success event fires.
     *
     * @param  {String}      name  Store name
     * @param  {Function}    callback to execute
     * @param  {Object}      options Options
     * @param  {String}      options.mode  Transaction mode ("readwrite" or undefined)
     * @return {Object}
     */
    async prepare(name, callback, options) {
        await this.open();
        await execute(this._db, name, callback, options);
    }
    /**
     * Deletes every records in the current collection.
     *
     * @override
     * @return {Promise}
     */
    async clear() {
        try {
            await this.prepare("records", store => {
                const range = IDBKeyRange.only(this.cid);
                const request = store.index("cid").openKeyCursor(range);
                request.onsuccess = event => {
                    const cursor = event.target.result;
                    if (cursor) {
                        store.delete(cursor.primaryKey);
                        cursor.continue();
                    }
                };
                return request;
            }, { mode: "readwrite" });
        }
        catch (e) {
            this._handleError("clear", e);
        }
    }
    /**
     * Executes the set of synchronous CRUD operations described in the provided
     * callback within an IndexedDB transaction, for current db store.
     *
     * The callback will be provided an object exposing the following synchronous
     * CRUD operation methods: get, create, update, delete.
     *
     * Important note: because limitations in IndexedDB implementations, no
     * asynchronous code should be performed within the provided callback; the
     * promise will therefore be rejected if the callback returns a Promise.
     *
     * Options:
     * - {Array} preload: The list of record IDs to fetch and make available to
     *   the transaction object get() method (default: [])
     *
     * @example
     * const db = new IDB("example");
     * const result = await db.execute(transaction => {
     *   transaction.create({id: 1, title: "foo"});
     *   transaction.update({id: 2, title: "bar"});
     *   transaction.delete(3);
     *   return "foo";
     * });
     *
     * @override
     * @param  {Function} callback The operation description callback.
     * @param  {Object}   options  The options object.
     * @return {Promise}
     */
    async execute(callback, options = { preload: [] }) {
        // Transactions in IndexedDB are autocommited when a callback does not
        // perform any additional operation.
        // The way Promises are implemented in Firefox (see https://bugzilla.mozilla.org/show_bug.cgi?id=1193394)
        // prevents using within an opened transaction.
        // To avoid managing asynchronocity in the specified `callback`, we preload
        // a list of record in order to execute the `callback` synchronously.
        // See also:
        // - http://stackoverflow.com/a/28388805/330911
        // - http://stackoverflow.com/a/10405196
        // - https://jakearchibald.com/2015/tasks-microtasks-queues-and-schedules/
        let result;
        await this.prepare("records", (store, abort) => {
            const runCallback = (preloaded = []) => {
                // Expose a consistent API for every adapter instead of raw store methods.
                const proxy = transactionProxy(this, store, preloaded);
                // The callback is executed synchronously within the same transaction.
                try {
                    const returned = callback(proxy);
                    if (returned instanceof Promise) {
                        // XXX: investigate how to provide documentation details in error.
                        throw new Error("execute() callback should not return a Promise.");
                    }
                    // Bring to scope that will be returned (once promise awaited).
                    result = returned;
                }
                catch (e) {
                    // The callback has thrown an error explicitly. Abort transaction cleanly.
                    abort(e);
                }
            };
            // No option to preload records, go straight to `callback`.
            if (!options.preload.length) {
                return runCallback();
            }
            // Preload specified records using a list request.
            const filters = { id: options.preload };
            createListRequest(this.cid, store, filters, records => {
                // Store obtained records by id.
                const preloaded = {};
                for (const record of records) {
                    delete record["_cid"];
                    preloaded[record.id] = record;
                }
                runCallback(preloaded);
            });
        }, { mode: "readwrite" });
        return result;
    }
    /**
     * Retrieve a record by its primary key from the IndexedDB database.
     *
     * @override
     * @param  {String} id The record id.
     * @return {Promise}
     */
    async get(id) {
        try {
            let record;
            await this.prepare("records", store => {
                store.get([this.cid, id]).onsuccess = e => (record = e.target.result);
            });
            return record;
        }
        catch (e) {
            this._handleError("get", e);
        }
    }
    /**
     * Lists all records from the IndexedDB database.
     *
     * @override
     * @param  {Object} params  The filters and order to apply to the results.
     * @return {Promise}
     */
    async list(params = { filters: {} }) {
        const { filters } = params;
        try {
            let results = [];
            await this.prepare("records", store => {
                createListRequest(this.cid, store, filters, _results => {
                    // we have received all requested records that match the filters,
                    // we now park them within current scope and hide the `_cid` attribute.
                    for (const result of _results) {
                        delete result["_cid"];
                    }
                    results = _results;
                });
            });
            // The resulting list of records is sorted.
            // XXX: with some efforts, this could be fully implemented using IDB API.
            return params.order ? sortObjects(params.order, results) : results;
        }
        catch (e) {
            this._handleError("list", e);
        }
    }
    /**
     * Store the lastModified value into metadata store.
     *
     * @override
     * @param  {Number}  lastModified
     * @return {Promise}
     */
    async saveLastModified(lastModified) {
        const value = parseInt(lastModified, 10) || null;
        try {
            await this.prepare("timestamps", store => {
                if (value === null) {
                    store.delete(this.cid);
                }
                else {
                    store.put({ cid: this.cid, value });
                }
            }, { mode: "readwrite" });
            return value;
        }
        catch (e) {
            this._handleError("saveLastModified", e);
        }
    }
    /**
     * Retrieve saved lastModified value.
     *
     * @override
     * @return {Promise}
     */
    async getLastModified() {
        try {
            let entry = null;
            await this.prepare("timestamps", store => {
                store.get(this.cid).onsuccess = e => (entry = e.target.result);
            });
            return entry ? entry.value : null;
        }
        catch (e) {
            this._handleError("getLastModified", e);
        }
    }
    /**
     * Load a dump of records exported from a server.
     *
     * @deprecated Use {@link importBulk} instead.
     * @abstract
     * @param  {Array} records The records to load.
     * @return {Promise}
     */
    async loadDump(records) {
        return this.importBulk(records);
    }
    /**
     * Load records in bulk that were exported from a server.
     *
     * @abstract
     * @param  {Array} records The records to load.
     * @return {Promise}
     */
    async importBulk(records) {
        try {
            await this.execute(transaction => {
                // Since the put operations are asynchronous, we chain
                // them together. The last one will be waited for the
                // `transaction.oncomplete` callback. (see #execute())
                let i = 0;
                putNext();
                function putNext() {
                    if (i == records.length) {
                        return;
                    }
                    // On error, `transaction.onerror` is called.
                    transaction.update(records[i]).onsuccess = putNext;
                    ++i;
                }
            });
            const previousLastModified = await this.getLastModified();
            const lastModified = Math.max(...records.map(record => record.last_modified));
            if (lastModified > previousLastModified) {
                await this.saveLastModified(lastModified);
            }
            return records;
        }
        catch (e) {
            this._handleError("importBulk", e);
        }
    }
    async saveMetadata(metadata) {
        try {
            await this.prepare("collections", store => store.put({ cid: this.cid, metadata }), { mode: "readwrite" });
            return metadata;
        }
        catch (e) {
            this._handleError("saveMetadata", e);
        }
    }
    async getMetadata() {
        try {
            let entry = null;
            await this.prepare("collections", store => {
                store.get(this.cid).onsuccess = e => (entry = e.target.result);
            });
            return entry ? entry.metadata : null;
        }
        catch (e) {
            this._handleError("getMetadata", e);
        }
    }
}
/**
 * IDB transaction proxy.
 *
 * @param  {IDB} adapter        The call IDB adapter
 * @param  {IDBStore} store     The IndexedDB database store.
 * @param  {Array}    preloaded The list of records to make available to
 *                              get() (default: []).
 * @return {Object}
 */
function transactionProxy(adapter, store, preloaded = []) {
    const _cid = adapter.cid;
    return {
        create(record) {
            store.add(Object.assign(Object.assign({}, record), { _cid }));
        },
        update(record) {
            return store.put(Object.assign(Object.assign({}, record), { _cid }));
        },
        delete(id) {
            store.delete([_cid, id]);
        },
        get(id) {
            return preloaded[id];
        },
    };
}
/**
 * Up to version 10.X of kinto.js, each collection had its own collection.
 * The database name was `${bid}/${cid}` (eg. `"blocklists/certificates"`)
 * and contained only one store with the same name.
 */
async function migrationRequired(dbName) {
    let exists = true;
    const db = await open(dbName, {
        version: 1,
        onupgradeneeded: event => {
            exists = false;
        },
    });
    // Check that the DB we're looking at is really a legacy one,
    // and not some remainder of the open() operation above.
    exists &=
        db.objectStoreNames.contains("__meta__") &&
            db.objectStoreNames.contains(dbName);
    if (!exists) {
        db.close();
        // Testing the existence creates it, so delete it :)
        await deleteDatabase(dbName);
        return null;
    }
    console.warn(`${dbName}: old IndexedDB database found.`);
    try {
        // Scan all records.
        let records;
        await execute(db, dbName, store => {
            store.openCursor().onsuccess = cursorHandlers.all({}, res => (records = res));
        });
        console.log(`${dbName}: found ${records.length} records.`);
        // Check if there's a entry for this.
        let timestamp = null;
        await execute(db, "__meta__", store => {
            store.get(`${dbName}-lastModified`).onsuccess = e => {
                timestamp = e.target.result ? e.target.result.value : null;
            };
        });
        // Some previous versions, also used to store the timestamps without prefix.
        if (!timestamp) {
            await execute(db, "__meta__", store => {
                store.get("lastModified").onsuccess = e => {
                    timestamp = e.target.result ? e.target.result.value : null;
                };
            });
        }
        console.log(`${dbName}: ${timestamp ? "found" : "no"} timestamp.`);
        // Those will be inserted in the new database/schema.
        return { records, timestamp };
    }
    catch (e) {
        console.error("Error occured during migration", e);
        return null;
    }
    finally {
        db.close();
    }
}

var uuid4 = {};

const RECORD_FIELDS_TO_CLEAN = ["_status"];
const AVAILABLE_HOOKS = ["incoming-changes"];
const IMPORT_CHUNK_SIZE = 200;
/**
 * Compare two records omitting local fields and synchronization
 * attributes (like _status and last_modified)
 * @param {Object} a    A record to compare.
 * @param {Object} b    A record to compare.
 * @param {Array} localFields Additional fields to ignore during the comparison
 * @return {boolean}
 */
function recordsEqual(a, b, localFields = []) {
    const fieldsToClean = RECORD_FIELDS_TO_CLEAN.concat(["last_modified"]).concat(localFields);
    const cleanLocal = r => omitKeys(r, fieldsToClean);
    return deepEqual(cleanLocal(a), cleanLocal(b));
}
/**
 * Synchronization result object.
 */
class SyncResultObject {
    /**
     * Public constructor.
     */
    constructor() {
        /**
         * Current synchronization result status; becomes `false` when conflicts or
         * errors are registered.
         * @type {Boolean}
         */
        this.lastModified = null;
        this._lists = {};
        [
            "errors",
            "created",
            "updated",
            "deleted",
            "published",
            "conflicts",
            "skipped",
            "resolved",
            "void",
        ].forEach(l => (this._lists[l] = []));
        this._cached = {};
    }
    /**
     * Adds entries for a given result type.
     *
     * @param {String} type    The result type.
     * @param {Array}  entries The result entries.
     * @return {SyncResultObject}
     */
    add(type, entries) {
        if (!Array.isArray(this._lists[type])) {
            console.warn(`Unknown type "${type}"`);
            return;
        }
        if (!Array.isArray(entries)) {
            entries = [entries];
        }
        this._lists[type] = this._lists[type].concat(entries);
        delete this._cached[type];
        return this;
    }
    get ok() {
        return this.errors.length + this.conflicts.length === 0;
    }
    get errors() {
        return this._lists["errors"];
    }
    get conflicts() {
        return this._lists["conflicts"];
    }
    get skipped() {
        return this._deduplicate("skipped");
    }
    get resolved() {
        return this._deduplicate("resolved");
    }
    get created() {
        return this._deduplicate("created");
    }
    get updated() {
        return this._deduplicate("updated");
    }
    get deleted() {
        return this._deduplicate("deleted");
    }
    get published() {
        return this._deduplicate("published");
    }
    _deduplicate(list) {
        if (!(list in this._cached)) {
            // Deduplicate entries by id. If the values don't have `id` attribute, just
            // keep all.
            const recordsWithoutId = new Set();
            const recordsById = new Map();
            this._lists[list].forEach(record => {
                if (!record.id) {
                    recordsWithoutId.add(record);
                }
                else {
                    recordsById.set(record.id, record);
                }
            });
            this._cached[list] = Array.from(recordsById.values()).concat(Array.from(recordsWithoutId));
        }
        return this._cached[list];
    }
    /**
     * Reinitializes result entries for a given result type.
     *
     * @param  {String} type The result type.
     * @return {SyncResultObject}
     */
    reset(type) {
        this._lists[type] = [];
        delete this._cached[type];
        return this;
    }
    toObject() {
        // Only used in tests.
        return {
            ok: this.ok,
            lastModified: this.lastModified,
            errors: this.errors,
            created: this.created,
            updated: this.updated,
            deleted: this.deleted,
            skipped: this.skipped,
            published: this.published,
            conflicts: this.conflicts,
            resolved: this.resolved,
        };
    }
}
class ServerWasFlushedError extends Error {
    constructor(clientTimestamp, serverTimestamp, message) {
        super(message);
        if (Error.captureStackTrace) {
            Error.captureStackTrace(this, ServerWasFlushedError);
        }
        this.clientTimestamp = clientTimestamp;
        this.serverTimestamp = serverTimestamp;
    }
}
function createUUIDSchema() {
    return {
        generate() {
            return uuid4();
        },
        validate(id) {
            return typeof id == "string" && RE_RECORD_ID.test(id);
        },
    };
}
function markStatus(record, status) {
    return Object.assign(Object.assign({}, record), { _status: status });
}
function markDeleted(record) {
    return markStatus(record, "deleted");
}
function markSynced(record) {
    return markStatus(record, "synced");
}
/**
 * Import a remote change into the local database.
 *
 * @param  {IDBTransactionProxy} transaction The transaction handler.
 * @param  {Object}              remote      The remote change object to import.
 * @param  {Array<String>}       localFields The list of fields that remain local.
 * @param  {String}              strategy    The {@link Collection.strategy}.
 * @return {Object}
 */
function importChange(transaction, remote, localFields, strategy) {
    const local = transaction.get(remote.id);
    if (!local) {
        // Not found locally but remote change is marked as deleted; skip to
        // avoid recreation.
        if (remote.deleted) {
            return { type: "skipped", data: remote };
        }
        const synced = markSynced(remote);
        transaction.create(synced);
        return { type: "created", data: synced };
    }
    // Apply remote changes on local record.
    const synced = Object.assign(Object.assign({}, local), markSynced(remote));
    // With pull only, we don't need to compare records since we override them.
    if (strategy === Collection.strategy.PULL_ONLY) {
        if (remote.deleted) {
            transaction.delete(remote.id);
            return { type: "deleted", data: local };
        }
        transaction.update(synced);
        return { type: "updated", data: { old: local, new: synced } };
    }
    // With other sync strategies, we detect conflicts,
    // by comparing local and remote, ignoring local fields.
    const isIdentical = recordsEqual(local, remote, localFields);
    // Detect or ignore conflicts if record has also been modified locally.
    if (local._status !== "synced") {
        // Locally deleted, unsynced: scheduled for remote deletion.
        if (local._status === "deleted") {
            return { type: "skipped", data: local };
        }
        if (isIdentical) {
            // If records are identical, import anyway, so we bump the
            // local last_modified value from the server and set record
            // status to "synced".
            transaction.update(synced);
            return { type: "updated", data: { old: local, new: synced } };
        }
        if (local.last_modified !== undefined &&
            local.last_modified === remote.last_modified) {
            // If our local version has the same last_modified as the remote
            // one, this represents an object that corresponds to a resolved
            // conflict. Our local version represents the final output, so
            // we keep that one. (No transaction operation to do.)
            // But if our last_modified is undefined,
            // that means we've created the same object locally as one on
            // the server, which *must* be a conflict.
            return { type: "void" };
        }
        return {
            type: "conflicts",
            data: { type: "incoming", local: local, remote: remote },
        };
    }
    // Local record was synced.
    if (remote.deleted) {
        transaction.delete(remote.id);
        return { type: "deleted", data: local };
    }
    // Import locally.
    transaction.update(synced);
    // if identical, simply exclude it from all SyncResultObject lists
    const type = isIdentical ? "void" : "updated";
    return { type, data: { old: local, new: synced } };
}
/**
 * Abstracts a collection of records stored in the local database, providing
 * CRUD operations and synchronization helpers.
 */
class Collection {
    /**
     * Constructor.
     *
     * Options:
     * - `{BaseAdapter} adapter` The DB adapter (default: `IDB`)
     *
     * @param  {String}    bucket  The bucket identifier.
     * @param  {String}    name    The collection name.
     * @param  {KintoBase} kinto   The Kinto instance.
     * @param  {Object}    options The options object.
     */
    constructor(bucket, name, kinto, options = {}) {
        this._bucket = bucket;
        this._name = name;
        this._lastModified = null;
        const DBAdapter = options.adapter || IDB;
        if (!DBAdapter) {
            throw new Error("No adapter provided");
        }
        const db = new DBAdapter(`${bucket}/${name}`, options.adapterOptions);
        if (!(db instanceof BaseAdapter)) {
            throw new Error("Unsupported adapter.");
        }
        // public properties
        /**
         * The db adapter instance
         * @type {BaseAdapter}
         */
        this.db = db;
        /**
         * The KintoBase instance.
         * @type {KintoBase}
         */
        this.kinto = kinto;
        /**
         * The event emitter instance.
         * @type {EventEmitter}
         */
        this.events = options.events;
        /**
         * The IdSchema instance.
         * @type {Object}
         */
        this.idSchema = this._validateIdSchema(options.idSchema);
        /**
         * The list of remote transformers.
         * @type {Array}
         */
        this.remoteTransformers = this._validateRemoteTransformers(options.remoteTransformers);
        /**
         * The list of hooks.
         * @type {Object}
         */
        this.hooks = this._validateHooks(options.hooks);
        /**
         * The list of fields names that will remain local.
         * @type {Array}
         */
        this.localFields = options.localFields || [];
    }
    /**
     * The HTTP client.
     * @type {KintoClient}
     */
    get api() {
        return this.kinto.api;
    }
    /**
     * The collection name.
     * @type {String}
     */
    get name() {
        return this._name;
    }
    /**
     * The bucket name.
     * @type {String}
     */
    get bucket() {
        return this._bucket;
    }
    /**
     * The last modified timestamp.
     * @type {Number}
     */
    get lastModified() {
        return this._lastModified;
    }
    /**
     * Synchronization strategies. Available strategies are:
     *
     * - `MANUAL`: Conflicts will be reported in a dedicated array.
     * - `SERVER_WINS`: Conflicts are resolved using remote data.
     * - `CLIENT_WINS`: Conflicts are resolved using local data.
     *
     * @type {Object}
     */
    static get strategy() {
        return {
            CLIENT_WINS: "client_wins",
            SERVER_WINS: "server_wins",
            PULL_ONLY: "pull_only",
            MANUAL: "manual",
        };
    }
    /**
     * Validates an idSchema.
     *
     * @param  {Object|undefined} idSchema
     * @return {Object}
     */
    _validateIdSchema(idSchema) {
        if (typeof idSchema === "undefined") {
            return createUUIDSchema();
        }
        if (typeof idSchema !== "object") {
            throw new Error("idSchema must be an object.");
        }
        else if (typeof idSchema.generate !== "function") {
            throw new Error("idSchema must provide a generate function.");
        }
        else if (typeof idSchema.validate !== "function") {
            throw new Error("idSchema must provide a validate function.");
        }
        return idSchema;
    }
    /**
     * Validates a list of remote transformers.
     *
     * @param  {Array|undefined} remoteTransformers
     * @return {Array}
     */
    _validateRemoteTransformers(remoteTransformers) {
        if (typeof remoteTransformers === "undefined") {
            return [];
        }
        if (!Array.isArray(remoteTransformers)) {
            throw new Error("remoteTransformers should be an array.");
        }
        return remoteTransformers.map(transformer => {
            if (typeof transformer !== "object") {
                throw new Error("A transformer must be an object.");
            }
            else if (typeof transformer.encode !== "function") {
                throw new Error("A transformer must provide an encode function.");
            }
            else if (typeof transformer.decode !== "function") {
                throw new Error("A transformer must provide a decode function.");
            }
            return transformer;
        });
    }
    /**
     * Validate the passed hook is correct.
     *
     * @param {Array|undefined} hook.
     * @return {Array}
     **/
    _validateHook(hook) {
        if (!Array.isArray(hook)) {
            throw new Error("A hook definition should be an array of functions.");
        }
        return hook.map(fn => {
            if (typeof fn !== "function") {
                throw new Error("A hook definition should be an array of functions.");
            }
            return fn;
        });
    }
    /**
     * Validates a list of hooks.
     *
     * @param  {Object|undefined} hooks
     * @return {Object}
     */
    _validateHooks(hooks) {
        if (typeof hooks === "undefined") {
            return {};
        }
        if (Array.isArray(hooks)) {
            throw new Error("hooks should be an object, not an array.");
        }
        if (typeof hooks !== "object") {
            throw new Error("hooks should be an object.");
        }
        const validatedHooks = {};
        for (const hook in hooks) {
            if (!AVAILABLE_HOOKS.includes(hook)) {
                throw new Error("The hook should be one of " + AVAILABLE_HOOKS.join(", "));
            }
            validatedHooks[hook] = this._validateHook(hooks[hook]);
        }
        return validatedHooks;
    }
    /**
     * Deletes every records in the current collection and marks the collection as
     * never synced.
     *
     * @return {Promise}
     */
    async clear() {
        await this.db.clear();
        await this.db.saveMetadata(null);
        await this.db.saveLastModified(null);
        return { data: [], permissions: {} };
    }
    /**
     * Encodes a record.
     *
     * @param  {String} type   Either "remote" or "local".
     * @param  {Object} record The record object to encode.
     * @return {Promise}
     */
    _encodeRecord(type, record) {
        if (!this[`${type}Transformers`].length) {
            return Promise.resolve(record);
        }
        return waterfall(this[`${type}Transformers`].map(transformer => {
            return record => transformer.encode(record);
        }), record);
    }
    /**
     * Decodes a record.
     *
     * @param  {String} type   Either "remote" or "local".
     * @param  {Object} record The record object to decode.
     * @return {Promise}
     */
    _decodeRecord(type, record) {
        if (!this[`${type}Transformers`].length) {
            return Promise.resolve(record);
        }
        return waterfall(this[`${type}Transformers`].reverse().map(transformer => {
            return record => transformer.decode(record);
        }), record);
    }
    /**
     * Adds a record to the local database, asserting that none
     * already exist with this ID.
     *
     * Note: If either the `useRecordId` or `synced` options are true, then the
     * record object must contain the id field to be validated. If none of these
     * options are true, an id is generated using the current IdSchema; in this
     * case, the record passed must not have an id.
     *
     * Options:
     * - {Boolean} synced       Sets record status to "synced" (default: `false`).
     * - {Boolean} useRecordId  Forces the `id` field from the record to be used,
     *                          instead of one that is generated automatically
     *                          (default: `false`).
     *
     * @param  {Object} record
     * @param  {Object} options
     * @return {Promise}
     */
    create(record, options = { useRecordId: false, synced: false }) {
        // Validate the record and its ID (if any), even though this
        // validation is also done in the CollectionTransaction method,
        // because we need to pass the ID to preloadIds.
        const reject = msg => Promise.reject(new Error(msg));
        if (typeof record !== "object") {
            return reject("Record is not an object.");
        }
        if ((options.synced || options.useRecordId) &&
            !Object.prototype.hasOwnProperty.call(record, "id")) {
            return reject("Missing required Id; synced and useRecordId options require one");
        }
        if (!options.synced &&
            !options.useRecordId &&
            Object.prototype.hasOwnProperty.call(record, "id")) {
            return reject("Extraneous Id; can't create a record having one set.");
        }
        const newRecord = Object.assign(Object.assign({}, record), { id: options.synced || options.useRecordId
                ? record.id
                : this.idSchema.generate(record), _status: options.synced ? "synced" : "created" });
        if (!this.idSchema.validate(newRecord.id)) {
            return reject(`Invalid Id: ${newRecord.id}`);
        }
        return this.execute(txn => txn.create(newRecord), {
            preloadIds: [newRecord.id],
        }).catch(err => {
            if (options.useRecordId) {
                throw new Error("Couldn't create record. It may have been virtually deleted.");
            }
            throw err;
        });
    }
    /**
     * Like {@link CollectionTransaction#update}, but wrapped in its own transaction.
     *
     * Options:
     * - {Boolean} synced: Sets record status to "synced" (default: false)
     * - {Boolean} patch:  Extends the existing record instead of overwriting it
     *   (default: false)
     *
     * @param  {Object} record
     * @param  {Object} options
     * @return {Promise}
     */
    update(record, options = { synced: false, patch: false }) {
        // Validate the record and its ID, even though this validation is
        // also done in the CollectionTransaction method, because we need
        // to pass the ID to preloadIds.
        if (typeof record !== "object") {
            return Promise.reject(new Error("Record is not an object."));
        }
        if (!Object.prototype.hasOwnProperty.call(record, "id")) {
            return Promise.reject(new Error("Cannot update a record missing id."));
        }
        if (!this.idSchema.validate(record.id)) {
            return Promise.reject(new Error(`Invalid Id: ${record.id}`));
        }
        return this.execute(txn => txn.update(record, options), {
            preloadIds: [record.id],
        });
    }
    /**
     * Like {@link CollectionTransaction#upsert}, but wrapped in its own transaction.
     *
     * @param  {Object} record
     * @return {Promise}
     */
    upsert(record) {
        // Validate the record and its ID, even though this validation is
        // also done in the CollectionTransaction method, because we need
        // to pass the ID to preloadIds.
        if (typeof record !== "object") {
            return Promise.reject(new Error("Record is not an object."));
        }
        if (!Object.prototype.hasOwnProperty.call(record, "id")) {
            return Promise.reject(new Error("Cannot update a record missing id."));
        }
        if (!this.idSchema.validate(record.id)) {
            return Promise.reject(new Error(`Invalid Id: ${record.id}`));
        }
        return this.execute(txn => txn.upsert(record), { preloadIds: [record.id] });
    }
    /**
     * Like {@link CollectionTransaction#get}, but wrapped in its own transaction.
     *
     * Options:
     * - {Boolean} includeDeleted: Include virtually deleted records.
     *
     * @param  {String} id
     * @param  {Object} options
     * @return {Promise}
     */
    get(id, options = { includeDeleted: false }) {
        return this.execute(txn => txn.get(id, options), { preloadIds: [id] });
    }
    /**
     * Like {@link CollectionTransaction#getAny}, but wrapped in its own transaction.
     *
     * @param  {String} id
     * @return {Promise}
     */
    getAny(id) {
        return this.execute(txn => txn.getAny(id), { preloadIds: [id] });
    }
    /**
     * Same as {@link Collection#delete}, but wrapped in its own transaction.
     *
     * Options:
     * - {Boolean} virtual: When set to `true`, doesn't actually delete the record,
     *   update its `_status` attribute to `deleted` instead (default: true)
     *
     * @param  {String} id       The record's Id.
     * @param  {Object} options  The options object.
     * @return {Promise}
     */
    delete(id, options = { virtual: true }) {
        return this.execute(transaction => {
            return transaction.delete(id, options);
        }, { preloadIds: [id] });
    }
    /**
     * Same as {@link Collection#deleteAll}, but wrapped in its own transaction, execulding the parameter.
     *
     * @return {Promise}
     */
    async deleteAll() {
        const { data } = await this.list({}, { includeDeleted: false });
        const recordIds = data.map(record => record.id);
        return this.execute(transaction => {
            return transaction.deleteAll(recordIds);
        }, { preloadIds: recordIds });
    }
    /**
     * The same as {@link CollectionTransaction#deleteAny}, but wrapped
     * in its own transaction.
     *
     * @param  {String} id       The record's Id.
     * @return {Promise}
     */
    deleteAny(id) {
        return this.execute(txn => txn.deleteAny(id), { preloadIds: [id] });
    }
    /**
     * Lists records from the local database.
     *
     * Params:
     * - {Object} filters Filter the results (default: `{}`).
     * - {String} order   The order to apply   (default: `-last_modified`).
     *
     * Options:
     * - {Boolean} includeDeleted: Include virtually deleted records.
     *
     * @param  {Object} params  The filters and order to apply to the results.
     * @param  {Object} options The options object.
     * @return {Promise}
     */
    async list(params = {}, options = { includeDeleted: false }) {
        params = Object.assign({ order: "-last_modified", filters: {} }, params);
        const results = await this.db.list(params);
        let data = results;
        if (!options.includeDeleted) {
            data = results.filter(record => record._status !== "deleted");
        }
        return { data, permissions: {} };
    }
    /**
     * Imports remote changes into the local database.
     * This method is in charge of detecting the conflicts, and resolve them
     * according to the specified strategy.
     * @param  {SyncResultObject} syncResultObject The sync result object.
     * @param  {Array}            decodedChanges   The list of changes to import in the local database.
     * @param  {String}           strategy         The {@link Collection.strategy} (default: MANUAL)
     * @return {Promise}
     */
    async importChanges(syncResultObject, decodedChanges, strategy = Collection.strategy.MANUAL) {
        // Retrieve records matching change ids.
        try {
            for (let i = 0; i < decodedChanges.length; i += IMPORT_CHUNK_SIZE) {
                const slice = decodedChanges.slice(i, i + IMPORT_CHUNK_SIZE);
                const { imports, resolved } = await this.db.execute(transaction => {
                    const imports = slice.map(remote => {
                        // Store remote change into local database.
                        return importChange(transaction, remote, this.localFields, strategy);
                    });
                    const conflicts = imports
                        .filter(i => i.type === "conflicts")
                        .map(i => i.data);
                    const resolved = this._handleConflicts(transaction, conflicts, strategy);
                    return { imports, resolved };
                }, { preload: slice.map(record => record.id) });
                // Lists of created/updated/deleted records
                imports.forEach(({ type, data }) => syncResultObject.add(type, data));
                // Automatically resolved conflicts (if not manual)
                if (resolved.length > 0) {
                    syncResultObject.reset("conflicts").add("resolved", resolved);
                }
            }
        }
        catch (err) {
            const data = {
                type: "incoming",
                message: err.message,
                stack: err.stack,
            };
            // XXX one error of the whole transaction instead of per atomic op
            syncResultObject.add("errors", data);
        }
        return syncResultObject;
    }
    /**
     * Imports the responses of pushed changes into the local database.
     * Basically it stores the timestamp assigned by the server into the local
     * database.
     * @param  {SyncResultObject} syncResultObject The sync result object.
     * @param  {Array}            toApplyLocally   The list of changes to import in the local database.
     * @param  {Array}            conflicts        The list of conflicts that have to be resolved.
     * @param  {String}           strategy         The {@link Collection.strategy}.
     * @return {Promise}
     */
    async _applyPushedResults(syncResultObject, toApplyLocally, conflicts, strategy = Collection.strategy.MANUAL) {
        const toDeleteLocally = toApplyLocally.filter(r => r.deleted);
        const toUpdateLocally = toApplyLocally.filter(r => !r.deleted);
        const { published, resolved } = await this.db.execute(transaction => {
            const updated = toUpdateLocally.map(record => {
                const synced = markSynced(record);
                transaction.update(synced);
                return synced;
            });
            const deleted = toDeleteLocally.map(record => {
                transaction.delete(record.id);
                // Amend result data with the deleted attribute set
                return { id: record.id, deleted: true };
            });
            const published = updated.concat(deleted);
            // Handle conflicts, if any
            const resolved = this._handleConflicts(transaction, conflicts, strategy);
            return { published, resolved };
        });
        syncResultObject.add("published", published);
        if (resolved.length > 0) {
            syncResultObject
                .reset("conflicts")
                .reset("resolved")
                .add("resolved", resolved);
        }
        return syncResultObject;
    }
    /**
     * Handles synchronization conflicts according to specified strategy.
     *
     * @param  {SyncResultObject} result    The sync result object.
     * @param  {String}           strategy  The {@link Collection.strategy}.
     * @return {Promise<Array<Object>>} The resolved conflicts, as an
     *    array of {accepted, rejected} objects
     */
    _handleConflicts(transaction, conflicts, strategy) {
        if (strategy === Collection.strategy.MANUAL) {
            return [];
        }
        return conflicts.map(conflict => {
            const resolution = strategy === Collection.strategy.CLIENT_WINS
                ? conflict.local
                : conflict.remote;
            const rejected = strategy === Collection.strategy.CLIENT_WINS
                ? conflict.remote
                : conflict.local;
            let accepted, status, id;
            if (resolution === null) {
                // We "resolved" with the server-side deletion. Delete locally.
                // This only happens during SERVER_WINS because the local
                // version of a record can never be null.
                // We can get "null" from the remote side if we got a conflict
                // and there is no remote version available; see kinto-http.js
                // batch.js:aggregate.
                transaction.delete(conflict.local.id);
                accepted = null;
                // The record was deleted, but that status is "synced" with
                // the server, so we don't need to push the change.
                status = "synced";
                id = conflict.local.id;
            }
            else {
                const updated = this._resolveRaw(conflict, resolution);
                transaction.update(updated);
                accepted = updated;
                status = updated._status;
                id = updated.id;
            }
            return { rejected, accepted, id, _status: status };
        });
    }
    /**
     * Execute a bunch of operations in a transaction.
     *
     * This transaction should be atomic -- either all of its operations
     * will succeed, or none will.
     *
     * The argument to this function is itself a function which will be
     * called with a {@link CollectionTransaction}. Collection methods
     * are available on this transaction, but instead of returning
     * promises, they are synchronous. execute() returns a Promise whose
     * value will be the return value of the provided function.
     *
     * Most operations will require access to the record itself, which
     * must be preloaded by passing its ID in the preloadIds option.
     *
     * Options:
     * - {Array} preloadIds: list of IDs to fetch at the beginning of
     *   the transaction
     *
     * @return {Promise} Resolves with the result of the given function
     *    when the transaction commits.
     */
    execute(doOperations, { preloadIds = [] } = {}) {
        for (const id of preloadIds) {
            if (!this.idSchema.validate(id)) {
                return Promise.reject(Error(`Invalid Id: ${id}`));
            }
        }
        return this.db.execute(transaction => {
            const txn = new CollectionTransaction(this, transaction);
            const result = doOperations(txn);
            txn.emitEvents();
            return result;
        }, { preload: preloadIds });
    }
    /**
     * Resets the local records as if they were never synced; existing records are
     * marked as newly created, deleted records are dropped.
     *
     * A next call to {@link Collection.sync} will thus republish the whole
     * content of the local collection to the server.
     *
     * @return {Promise} Resolves with the number of processed records.
     */
    async resetSyncStatus() {
        const unsynced = await this.list({ filters: { _status: ["deleted", "synced"] }, order: "" }, { includeDeleted: true });
        await this.db.execute(transaction => {
            unsynced.data.forEach(record => {
                if (record._status === "deleted") {
                    // Garbage collect deleted records.
                    transaction.delete(record.id);
                }
                else {
                    // Records that were synced become «created».
                    transaction.update(Object.assign(Object.assign({}, record), { last_modified: undefined, _status: "created" }));
                }
            });
        });
        this._lastModified = null;
        await this.db.saveLastModified(null);
        return unsynced.data.length;
    }
    /**
     * Returns an object containing two lists:
     *
     * - `toDelete`: unsynced deleted records we can safely delete;
     * - `toSync`: local updates to send to the server.
     *
     * @return {Promise}
     */
    async gatherLocalChanges() {
        const unsynced = await this.list({
            filters: { _status: ["created", "updated"] },
            order: "",
        });
        const deleted = await this.list({ filters: { _status: "deleted" }, order: "" }, { includeDeleted: true });
        return await Promise.all(unsynced.data
            .concat(deleted.data)
            .map(this._encodeRecord.bind(this, "remote")));
    }
    /**
     * Fetch remote changes, import them to the local database, and handle
     * conflicts according to `options.strategy`. Then, updates the passed
     * {@link SyncResultObject} with import results.
     *
     * Options:
     * - {String} strategy: The selected sync strategy.
     * - {String} expectedTimestamp: A timestamp to use as a "cache busting" query parameter.
     * - {Array<String>} exclude: A list of record ids to exclude from pull.
     * - {Object} headers: The HTTP headers to use in the request.
     * - {int} retry: The number of retries to do if the HTTP request fails.
     * - {int} lastModified: The timestamp to use in `?_since` query.
     *
     * @param  {KintoClient.Collection} client           Kinto client Collection instance.
     * @param  {SyncResultObject}       syncResultObject The sync result object.
     * @param  {Object}                 options          The options object.
     * @return {Promise}
     */
    async pullChanges(client, syncResultObject, options = {}) {
        if (!syncResultObject.ok) {
            return syncResultObject;
        }
        const since = this.lastModified
            ? this.lastModified
            : await this.db.getLastModified();
        options = Object.assign({ strategy: Collection.strategy.MANUAL, lastModified: since, headers: {} }, options);
        // Optionally ignore some records when pulling for changes.
        // (avoid redownloading our own changes on last step of #sync())
        let filters;
        if (options.exclude) {
            // Limit the list of excluded records to the first 50 records in order
            // to remain under de-facto URL size limit (~2000 chars).
            // http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers/417184#417184
            const exclude_id = options.exclude
                .slice(0, 50)
                .map(r => r.id)
                .join(",");
            filters = { exclude_id };
        }
        if (options.expectedTimestamp) {
            filters = Object.assign(Object.assign({}, filters), { _expected: options.expectedTimestamp });
        }
        // First fetch remote changes from the server
        const { data, last_modified } = await client.listRecords({
            // Since should be ETag (see https://github.com/Kinto/kinto.js/issues/356)
            since: options.lastModified ? `${options.lastModified}` : undefined,
            headers: options.headers,
            retry: options.retry,
            // Fetch every page by default (FIXME: option to limit pages, see #277)
            pages: Infinity,
            filters,
        });
        // last_modified is the ETag header value (string).
        // For retro-compatibility with first kinto.js versions
        // parse it to integer.
        const unquoted = last_modified ? parseInt(last_modified, 10) : undefined;
        // Check if server was flushed.
        // This is relevant for the Kinto demo server
        // (and thus for many new comers).
        const localSynced = options.lastModified;
        const serverChanged = unquoted > options.lastModified;
        const emptyCollection = data.length === 0;
        if (!options.exclude && localSynced && serverChanged && emptyCollection) {
            const e = new ServerWasFlushedError(localSynced, unquoted, "Server has been flushed. Client Side Timestamp: " +
                localSynced +
                " Server Side Timestamp: " +
                unquoted);
            throw e;
        }
        // Atomic updates are not sensible here because unquoted is not
        // computed as a function of syncResultObject.lastModified.
        // eslint-disable-next-line require-atomic-updates
        syncResultObject.lastModified = unquoted;
        // Decode incoming changes.
        const decodedChanges = await Promise.all(data.map(change => {
            return this._decodeRecord("remote", change);
        }));
        // Hook receives decoded records.
        const payload = { lastModified: unquoted, changes: decodedChanges };
        const afterHooks = await this.applyHook("incoming-changes", payload);
        // No change, nothing to import.
        if (afterHooks.changes.length > 0) {
            // Reflect these changes locally
            await this.importChanges(syncResultObject, afterHooks.changes, options.strategy);
        }
        return syncResultObject;
    }
    applyHook(hookName, payload) {
        if (typeof this.hooks[hookName] == "undefined") {
            return Promise.resolve(payload);
        }
        return waterfall(this.hooks[hookName].map(hook => {
            return record => {
                const result = hook(payload, this);
                const resultThenable = result && typeof result.then === "function";
                const resultChanges = result && Object.prototype.hasOwnProperty.call(result, "changes");
                if (!(resultThenable || resultChanges)) {
                    throw new Error(`Invalid return value for hook: ${JSON.stringify(result)} has no 'then()' or 'changes' properties`);
                }
                return result;
            };
        }), payload);
    }
    /**
     * Publish local changes to the remote server and updates the passed
     * {@link SyncResultObject} with publication results.
     *
     * Options:
     * - {String} strategy: The selected sync strategy.
     * - {Object} headers: The HTTP headers to use in the request.
     * - {int} retry: The number of retries to do if the HTTP request fails.
     *
     * @param  {KintoClient.Collection} client           Kinto client Collection instance.
     * @param  {SyncResultObject}       syncResultObject The sync result object.
     * @param  {Object}                 changes          The change object.
     * @param  {Array}                  changes.toDelete The list of records to delete.
     * @param  {Array}                  changes.toSync   The list of records to create/update.
     * @param  {Object}                 options          The options object.
     * @return {Promise}
     */
    async pushChanges(client, changes, syncResultObject, options = {}) {
        if (!syncResultObject.ok) {
            return syncResultObject;
        }
        const safe = !options.strategy || options.strategy !== Collection.CLIENT_WINS;
        const toDelete = changes.filter(r => r._status == "deleted");
        const toSync = changes.filter(r => r._status != "deleted");
        // Perform a batch request with every changes.
        const synced = await client.batch(batch => {
            toDelete.forEach(r => {
                // never published locally deleted records should not be pusblished
                if (r.last_modified) {
                    batch.deleteRecord(r);
                }
            });
            toSync.forEach(r => {
                // Clean local fields (like _status) before sending to server.
                const published = this.cleanLocalFields(r);
                if (r._status === "created") {
                    batch.createRecord(published);
                }
                else {
                    batch.updateRecord(published);
                }
            });
        }, {
            headers: options.headers,
            retry: options.retry,
            safe,
            aggregate: true,
        });
        // Store outgoing errors into sync result object
        syncResultObject.add("errors", synced.errors.map(e => (Object.assign(Object.assign({}, e), { type: "outgoing" }))));
        // Store outgoing conflicts into sync result object
        const conflicts = [];
        for (const { type, local, remote } of synced.conflicts) {
            // Note: we ensure that local data are actually available, as they may
            // be missing in the case of a published deletion.
            const safeLocal = (local && local.data) || { id: remote.id };
            const realLocal = await this._decodeRecord("remote", safeLocal);
            // We can get "null" from the remote side if we got a conflict
            // and there is no remote version available; see kinto-http.js
            // batch.js:aggregate.
            const realRemote = remote && (await this._decodeRecord("remote", remote));
            const conflict = { type, local: realLocal, remote: realRemote };
            conflicts.push(conflict);
        }
        syncResultObject.add("conflicts", conflicts);
        // Records that must be deleted are either deletions that were pushed
        // to server (published) or deleted records that were never pushed (skipped).
        const missingRemotely = synced.skipped.map(r => (Object.assign(Object.assign({}, r), { deleted: true })));
        // For created and updated records, the last_modified coming from server
        // will be stored locally.
        // Reflect publication results locally using the response from
        // the batch request.
        const published = synced.published.map(c => c.data);
        const toApplyLocally = published.concat(missingRemotely);
        // Apply the decode transformers, if any
        const decoded = await Promise.all(toApplyLocally.map(record => {
            return this._decodeRecord("remote", record);
        }));
        // We have to update the local records with the responses of the server
        // (eg. last_modified values etc.).
        if (decoded.length > 0 || conflicts.length > 0) {
            await this._applyPushedResults(syncResultObject, decoded, conflicts, options.strategy);
        }
        return syncResultObject;
    }
    /**
     * Return a copy of the specified record without the local fields.
     *
     * @param  {Object} record  A record with potential local fields.
     * @return {Object}
     */
    cleanLocalFields(record) {
        const localKeys = RECORD_FIELDS_TO_CLEAN.concat(this.localFields);
        return omitKeys(record, localKeys);
    }
    /**
     * Resolves a conflict, updating local record according to proposed
     * resolution — keeping remote record `last_modified` value as a reference for
     * further batch sending.
     *
     * @param  {Object} conflict   The conflict object.
     * @param  {Object} resolution The proposed record.
     * @return {Promise}
     */
    resolve(conflict, resolution) {
        return this.db.execute(transaction => {
            const updated = this._resolveRaw(conflict, resolution);
            transaction.update(updated);
            return { data: updated, permissions: {} };
        });
    }
    /**
     * @private
     */
    _resolveRaw(conflict, resolution) {
        const resolved = Object.assign(Object.assign({}, resolution), {
            // Ensure local record has the latest authoritative timestamp
            last_modified: conflict.remote && conflict.remote.last_modified });
        // If the resolution object is strictly equal to the
        // remote record, then we can mark it as synced locally.
        // Otherwise, mark it as updated (so that the resolution is pushed).
        const synced = deepEqual(resolved, conflict.remote);
        return markStatus(resolved, synced ? "synced" : "updated");
    }
    /**
     * Synchronize remote and local data. The promise will resolve with a
     * {@link SyncResultObject}, though will reject:
     *
     * - if the server is currently backed off;
     * - if the server has been detected flushed.
     *
     * Options:
     * - {Object} headers: HTTP headers to attach to outgoing requests.
     * - {String} expectedTimestamp: A timestamp to use as a "cache busting" query parameter.
     * - {Number} retry: Number of retries when server fails to process the request (default: 1).
     * - {Collection.strategy} strategy: See {@link Collection.strategy}.
     * - {Boolean} ignoreBackoff: Force synchronization even if server is currently
     *   backed off.
     * - {String} bucket: The remove bucket id to use (default: null)
     * - {String} collection: The remove collection id to use (default: null)
     * - {String} remote The remote Kinto server endpoint to use (default: null).
     *
     * @param  {Object} options Options.
     * @return {Promise}
     * @throws {Error} If an invalid remote option is passed.
     */
    async sync(options = {
        strategy: Collection.strategy.MANUAL,
        headers: {},
        retry: 1,
        ignoreBackoff: false,
        bucket: null,
        collection: null,
        remote: null,
        expectedTimestamp: null,
    }) {
        options = Object.assign(Object.assign({}, options), { bucket: options.bucket || this.bucket, collection: options.collection || this.name });
        const previousRemote = this.api.remote;
        if (options.remote) {
            // Note: setting the remote ensures it's valid, throws when invalid.
            this.api.remote = options.remote;
        }
        if (!options.ignoreBackoff && this.api.backoff > 0) {
            const seconds = Math.ceil(this.api.backoff / 1000);
            return Promise.reject(new Error(`Server is asking clients to back off; retry in ${seconds}s or use the ignoreBackoff option.`));
        }
        const client = this.api
            .bucket(options.bucket)
            .collection(options.collection);
        const result = new SyncResultObject();
        try {
            // Fetch collection metadata.
            await this.pullMetadata(client, options);
            // Fetch last changes from the server.
            await this.pullChanges(client, result, options);
            const { lastModified } = result;
            if (options.strategy != Collection.strategy.PULL_ONLY) {
                // Fetch local changes
                const toSync = await this.gatherLocalChanges();
                // Publish local changes and pull local resolutions
                await this.pushChanges(client, toSync, result, options);
                // Publish local resolution of push conflicts to server (on CLIENT_WINS)
                const resolvedUnsynced = result.resolved.filter(r => r._status !== "synced");
                if (resolvedUnsynced.length > 0) {
                    const resolvedEncoded = await Promise.all(resolvedUnsynced.map(resolution => {
                        let record = resolution.accepted;
                        if (record === null) {
                            record = { id: resolution.id, _status: resolution._status };
                        }
                        return this._encodeRecord("remote", record);
                    }));
                    await this.pushChanges(client, resolvedEncoded, result, options);
                }
                // Perform a last pull to catch changes that occured after the last pull,
                // while local changes were pushed. Do not do it nothing was pushed.
                if (result.published.length > 0) {
                    // Avoid redownloading our own changes during the last pull.
                    const pullOpts = Object.assign(Object.assign({}, options), { lastModified, exclude: result.published });
                    await this.pullChanges(client, result, pullOpts);
                }
            }
            // Don't persist lastModified value if any conflict or error occured
            if (result.ok) {
                // No conflict occured, persist collection's lastModified value
                this._lastModified = await this.db.saveLastModified(result.lastModified);
            }
        }
        catch (e) {
            this.events.emit("sync:error", Object.assign(Object.assign({}, options), { error: e }));
            throw e;
        }
        finally {
            // Ensure API default remote is reverted if a custom one's been used
            this.api.remote = previousRemote;
        }
        this.events.emit("sync:success", Object.assign(Object.assign({}, options), { result }));
        return result;
    }
    /**
     * Load a list of records already synced with the remote server.
     *
     * The local records which are unsynced or whose timestamp is either missing
     * or superior to those being loaded will be ignored.
     *
     * @deprecated Use {@link importBulk} instead.
     * @param  {Array} records The previously exported list of records to load.
     * @return {Promise} with the effectively imported records.
     */
    async loadDump(records) {
        return this.importBulk(records);
    }
    /**
     * Load a list of records already synced with the remote server.
     *
     * The local records which are unsynced or whose timestamp is either missing
     * or superior to those being loaded will be ignored.
     *
     * @param  {Array} records The previously exported list of records to load.
     * @return {Promise} with the effectively imported records.
     */
    async importBulk(records) {
        if (!Array.isArray(records)) {
            throw new Error("Records is not an array.");
        }
        for (const record of records) {
            if (!Object.prototype.hasOwnProperty.call(record, "id") ||
                !this.idSchema.validate(record.id)) {
                throw new Error("Record has invalid ID: " + JSON.stringify(record));
            }
            if (!record.last_modified) {
                throw new Error("Record has no last_modified value: " + JSON.stringify(record));
            }
        }
        // Fetch all existing records from local database,
        // and skip those who are newer or not marked as synced.
        // XXX filter by status / ids in records
        const { data } = await this.list({}, { includeDeleted: true });
        const existingById = data.reduce((acc, record) => {
            acc[record.id] = record;
            return acc;
        }, {});
        const newRecords = records.filter(record => {
            const localRecord = existingById[record.id];
            const shouldKeep =
            // No local record with this id.
            localRecord === undefined ||
                // Or local record is synced
                (localRecord._status === "synced" &&
                    // And was synced from server
                    localRecord.last_modified !== undefined &&
                    // And is older than imported one.
                    record.last_modified > localRecord.last_modified);
            return shouldKeep;
        });
        return await this.db.importBulk(newRecords.map(markSynced));
    }
    async pullMetadata(client, options = {}) {
        const { expectedTimestamp, headers } = options;
        const query = expectedTimestamp
            ? { query: { _expected: expectedTimestamp } }
            : undefined;
        const metadata = await client.getData(Object.assign(Object.assign({}, query), { headers }));
        return this.db.saveMetadata(metadata);
    }
    async metadata() {
        return this.db.getMetadata();
    }
}
/**
 * A Collection-oriented wrapper for an adapter's transaction.
 *
 * This defines the high-level functions available on a collection.
 * The collection itself offers functions of the same name. These will
 * perform just one operation in its own transaction.
 */
class CollectionTransaction {
    constructor(collection, adapterTransaction) {
        this.collection = collection;
        this.adapterTransaction = adapterTransaction;
        this._events = [];
    }
    _queueEvent(action, payload) {
        this._events.push({ action, payload });
    }
    /**
     * Emit queued events, to be called once every transaction operations have
     * been executed successfully.
     */
    emitEvents() {
        for (const { action, payload } of this._events) {
            this.collection.events.emit(action, payload);
        }
        if (this._events.length > 0) {
            const targets = this._events.map(({ action, payload }) => (Object.assign({ action }, payload)));
            this.collection.events.emit("change", { targets });
        }
        this._events = [];
    }
    /**
     * Retrieve a record by its id from the local database, or
     * undefined if none exists.
     *
     * This will also return virtually deleted records.
     *
     * @param  {String} id
     * @return {Object}
     */
    getAny(id) {
        const record = this.adapterTransaction.get(id);
        return { data: record, permissions: {} };
    }
    /**
     * Retrieve a record by its id from the local database.
     *
     * Options:
     * - {Boolean} includeDeleted: Include virtually deleted records.
     *
     * @param  {String} id
     * @param  {Object} options
     * @return {Object}
     */
    get(id, options = { includeDeleted: false }) {
        const res = this.getAny(id);
        if (!res.data ||
            (!options.includeDeleted && res.data._status === "deleted")) {
            throw new Error(`Record with id=${id} not found.`);
        }
        return res;
    }
    /**
     * Deletes a record from the local database.
     *
     * Options:
     * - {Boolean} virtual: When set to `true`, doesn't actually delete the record,
     *   update its `_status` attribute to `deleted` instead (default: true)
     *
     * @param  {String} id       The record's Id.
     * @param  {Object} options  The options object.
     * @return {Object}
     */
    delete(id, options = { virtual: true }) {
        // Ensure the record actually exists.
        const existing = this.adapterTransaction.get(id);
        const alreadyDeleted = existing && existing._status == "deleted";
        if (!existing || (alreadyDeleted && options.virtual)) {
            throw new Error(`Record with id=${id} not found.`);
        }
        // Virtual updates status.
        if (options.virtual) {
            this.adapterTransaction.update(markDeleted(existing));
        }
        else {
            // Delete for real.
            this.adapterTransaction.delete(id);
        }
        this._queueEvent("delete", { data: existing });
        return { data: existing, permissions: {} };
    }
    /**
     * Soft delete all records from the local database.
     *
     * @param  {Array} ids        Array of non-deleted Record Ids.
     * @return {Object}
     */
    deleteAll(ids) {
        const existingRecords = [];
        ids.forEach(id => {
            existingRecords.push(this.adapterTransaction.get(id));
            this.delete(id);
        });
        this._queueEvent("deleteAll", { data: existingRecords });
        return { data: existingRecords, permissions: {} };
    }
    /**
     * Deletes a record from the local database, if any exists.
     * Otherwise, do nothing.
     *
     * @param  {String} id       The record's Id.
     * @return {Object}
     */
    deleteAny(id) {
        const existing = this.adapterTransaction.get(id);
        if (existing) {
            this.adapterTransaction.update(markDeleted(existing));
            this._queueEvent("delete", { data: existing });
        }
        return { data: Object.assign({ id }, existing), deleted: !!existing, permissions: {} };
    }
    /**
     * Adds a record to the local database, asserting that none
     * already exist with this ID.
     *
     * @param  {Object} record, which must contain an ID
     * @return {Object}
     */
    create(record) {
        if (typeof record !== "object") {
            throw new Error("Record is not an object.");
        }
        if (!Object.prototype.hasOwnProperty.call(record, "id")) {
            throw new Error("Cannot create a record missing id");
        }
        if (!this.collection.idSchema.validate(record.id)) {
            throw new Error(`Invalid Id: ${record.id}`);
        }
        this.adapterTransaction.create(record);
        this._queueEvent("create", { data: record });
        return { data: record, permissions: {} };
    }
    /**
     * Updates a record from the local database.
     *
     * Options:
     * - {Boolean} synced: Sets record status to "synced" (default: false)
     * - {Boolean} patch:  Extends the existing record instead of overwriting it
     *   (default: false)
     *
     * @param  {Object} record
     * @param  {Object} options
     * @return {Object}
     */
    update(record, options = { synced: false, patch: false }) {
        if (typeof record !== "object") {
            throw new Error("Record is not an object.");
        }
        if (!Object.prototype.hasOwnProperty.call(record, "id")) {
            throw new Error("Cannot update a record missing id.");
        }
        if (!this.collection.idSchema.validate(record.id)) {
            throw new Error(`Invalid Id: ${record.id}`);
        }
        const oldRecord = this.adapterTransaction.get(record.id);
        if (!oldRecord) {
            throw new Error(`Record with id=${record.id} not found.`);
        }
        const newRecord = options.patch ? Object.assign(Object.assign({}, oldRecord), record) : record;
        const updated = this._updateRaw(oldRecord, newRecord, options);
        this.adapterTransaction.update(updated);
        this._queueEvent("update", { data: updated, oldRecord });
        return { data: updated, oldRecord, permissions: {} };
    }
    /**
     * Lower-level primitive for updating a record while respecting
     * _status and last_modified.
     *
     * @param  {Object} oldRecord: the record retrieved from the DB
     * @param  {Object} newRecord: the record to replace it with
     * @return {Object}
     */
    _updateRaw(oldRecord, newRecord, { synced = false } = {}) {
        const updated = Object.assign({}, newRecord);
        // Make sure to never loose the existing timestamp.
        if (oldRecord && oldRecord.last_modified && !updated.last_modified) {
            updated.last_modified = oldRecord.last_modified;
        }
        // If only local fields have changed, then keep record as synced.
        // If status is created, keep record as created.
        // If status is deleted, mark as updated.
        const isIdentical = oldRecord &&
            recordsEqual(oldRecord, updated, this.collection.localFields);
        const keepSynced = isIdentical && oldRecord._status == "synced";
        const neverSynced = !oldRecord || (oldRecord && oldRecord._status == "created");
        const newStatus = keepSynced || synced ? "synced" : neverSynced ? "created" : "updated";
        return markStatus(updated, newStatus);
    }
    /**
     * Upsert a record into the local database.
     *
     * This record must have an ID.
     *
     * If a record with this ID already exists, it will be replaced.
     * Otherwise, this record will be inserted.
     *
     * @param  {Object} record
     * @return {Object}
     */
    upsert(record) {
        if (typeof record !== "object") {
            throw new Error("Record is not an object.");
        }
        if (!Object.prototype.hasOwnProperty.call(record, "id")) {
            throw new Error("Cannot update a record missing id.");
        }
        if (!this.collection.idSchema.validate(record.id)) {
            throw new Error(`Invalid Id: ${record.id}`);
        }
        let oldRecord = this.adapterTransaction.get(record.id);
        const updated = this._updateRaw(oldRecord, record);
        this.adapterTransaction.update(updated);
        // Don't return deleted records -- pretend they are gone
        if (oldRecord && oldRecord._status == "deleted") {
            oldRecord = undefined;
        }
        if (oldRecord) {
            this._queueEvent("update", { data: updated, oldRecord });
        }
        else {
            this._queueEvent("create", { data: updated });
        }
        return { data: updated, oldRecord, permissions: {} };
    }
}

const DEFAULT_BUCKET_NAME = "default";
const DEFAULT_REMOTE = "http://localhost:8888/v1";
const DEFAULT_RETRY = 1;
/**
 * KintoBase class.
 */
class KintoBase {
    /**
     * Provides a public access to the base adapter class. Users can create a
     * custom DB adapter by extending {@link BaseAdapter}.
     *
     * @type {Object}
     */
    static get adapters() {
        return {
            BaseAdapter: BaseAdapter,
        };
    }
    /**
     * Synchronization strategies. Available strategies are:
     *
     * - `MANUAL`: Conflicts will be reported in a dedicated array.
     * - `SERVER_WINS`: Conflicts are resolved using remote data.
     * - `CLIENT_WINS`: Conflicts are resolved using local data.
     *
     * @type {Object}
     */
    static get syncStrategy() {
        return Collection.strategy;
    }
    /**
     * Constructor.
     *
     * Options:
     * - `{String}`       `remote`         The server URL to use.
     * - `{String}`       `bucket`         The collection bucket name.
     * - `{EventEmitter}` `events`         Events handler.
     * - `{BaseAdapter}`  `adapter`        The base DB adapter class.
     * - `{Object}`       `adapterOptions` Options given to the adapter.
     * - `{Object}`       `headers`        The HTTP headers to use.
     * - `{Object}`       `retry`          Number of retries when the server fails to process the request (default: `1`)
     * - `{String}`       `requestMode`    The HTTP CORS mode to use.
     * - `{Number}`       `timeout`        The requests timeout in ms (default: `5000`).
     *
     * @param  {Object} options The options object.
     */
    constructor(options = {}) {
        const defaults = {
            bucket: DEFAULT_BUCKET_NAME,
            remote: DEFAULT_REMOTE,
            retry: DEFAULT_RETRY,
        };
        this._options = Object.assign(Object.assign({}, defaults), options);
        if (!this._options.adapter) {
            throw new Error("No adapter provided");
        }
        this._api = null;
        /**
         * The event emitter instance.
         * @type {EventEmitter}
         */
        this.events = this._options.events;
    }
    /**
     * The kinto HTTP client instance.
     * @type {KintoClient}
     */
    get api() {
        const { events, headers, remote, requestMode, retry, timeout, } = this._options;
        if (!this._api) {
            this._api = new this.ApiClass(remote, {
                events,
                headers,
                requestMode,
                retry,
                timeout,
            });
        }
        return this._api;
    }
    /**
     * Creates a {@link Collection} instance. The second (optional) parameter
     * will set collection-level options like e.g. `remoteTransformers`.
     *
     * @param  {String} collName The collection name.
     * @param  {Object} [options={}]                 Extra options or override client's options.
     * @param  {Object} [options.idSchema]           IdSchema instance (default: UUID)
     * @param  {Object} [options.remoteTransformers] Array<RemoteTransformer> (default: `[]`])
     * @param  {Object} [options.hooks]              Array<Hook> (default: `[]`])
     * @param  {Object} [options.localFields]        Array<Field> (default: `[]`])
     * @return {Collection}
     */
    collection(collName, options = {}) {
        if (!collName) {
            throw new Error("missing collection name");
        }
        const { bucket, events, adapter, adapterOptions } = Object.assign(Object.assign({}, this._options), options);
        const { idSchema, remoteTransformers, hooks, localFields } = options;
        return new Collection(bucket, collName, this, {
            events,
            adapter,
            adapterOptions,
            idSchema,
            remoteTransformers,
            hooks,
            localFields,
        });
    }
}

/*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
    EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
    // Use standalone kinto-http module landed in FFx.
    KintoHttpClient: "resource://services-common/kinto-http-client.sys.mjs"
});
export class Kinto extends KintoBase {
    static get adapters() {
        return {
            BaseAdapter,
            IDB,
        };
    }
    get ApiClass() {
        return lazy.KintoHttpClient;
    }
    constructor(options = {}) {
        const events = {};
        lazy.EventEmitter.decorate(events);
        const defaults = {
            adapter: IDB,
            events,
        };
        super(Object.assign(Object.assign({}, defaults), options));
    }
    collection(collName, options = {}) {
        const idSchema = {
            validate(id) {
                return typeof id == "string" && RE_RECORD_ID.test(id);
            },
            generate() {
                return Services.uuid.generateUUID()
                    .toString()
                    .replace(/[{}]/g, "");
            },
        };
        return super.collection(collName, Object.assign({ idSchema }, options));
    }
}

PK
!<>�R��>�>5modules/services-common/kinto-storage-adapter.sys.mjs/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { Sqlite } from "resource://gre/modules/Sqlite.sys.mjs";

import { Kinto } from "resource://services-common/kinto-offline-client.sys.mjs";

/**
 * Filter and sort list against provided filters and order.
 *
 * @param  {Object} filters  The filters to apply.
 * @param  {String} order    The order to apply.
 * @param  {Array}  list     The list to reduce.
 * @return {Array}
 */
function reduceRecords(filters, order, list) {
  const filtered = filters ? filterObjects(filters, list) : list;
  return order ? sortObjects(order, filtered) : filtered;
}

/**
 * Checks if a value is undefined.
 *
 * This is a copy of `_isUndefined` from kinto.js/src/utils.js.
 * @param  {Any}  value
 * @return {Boolean}
 */
function _isUndefined(value) {
  return typeof value === "undefined";
}

/**
 * Sorts records in a list according to a given ordering.
 *
 * This is a copy of `sortObjects` from kinto.js/src/utils.js.
 *
 * @param  {String} order The ordering, eg. `-last_modified`.
 * @param  {Array}  list  The collection to order.
 * @return {Array}
 */
function sortObjects(order, list) {
  const hasDash = order[0] === "-";
  const field = hasDash ? order.slice(1) : order;
  const direction = hasDash ? -1 : 1;
  return list.slice().sort((a, b) => {
    if (a[field] && _isUndefined(b[field])) {
      return direction;
    }
    if (b[field] && _isUndefined(a[field])) {
      return -direction;
    }
    if (_isUndefined(a[field]) && _isUndefined(b[field])) {
      return 0;
    }
    return a[field] > b[field] ? direction : -direction;
  });
}

/**
 * Test if a single object matches all given filters.
 *
 * This is a copy of `filterObject` from kinto.js/src/utils.js.
 *
 * @param  {Object} filters  The filters object.
 * @param  {Object} entry    The object to filter.
 * @return {Function}
 */
function filterObject(filters, entry) {
  return Object.keys(filters).every(filter => {
    const value = filters[filter];
    if (Array.isArray(value)) {
      return value.some(candidate => candidate === entry[filter]);
    }
    return entry[filter] === value;
  });
}

/**
 * Filters records in a list matching all given filters.
 *
 * This is a copy of `filterObjects` from kinto.js/src/utils.js.
 *
 * @param  {Object} filters  The filters object.
 * @param  {Array}  list     The collection to filter.
 * @return {Array}
 */
function filterObjects(filters, list) {
  return list.filter(entry => {
    return filterObject(filters, entry);
  });
}

const statements = {
  createCollectionData: `
    CREATE TABLE collection_data (
      collection_name TEXT,
      record_id TEXT,
      record TEXT
    );`,

  createCollectionMetadata: `
    CREATE TABLE collection_metadata (
      collection_name TEXT PRIMARY KEY,
      last_modified INTEGER,
      metadata TEXT
    ) WITHOUT ROWID;`,

  createCollectionDataRecordIdIndex: `
    CREATE UNIQUE INDEX unique_collection_record
      ON collection_data(collection_name, record_id);`,

  clearData: `
    DELETE FROM collection_data
      WHERE collection_name = :collection_name;`,

  createData: `
    INSERT INTO collection_data (collection_name, record_id, record)
      VALUES (:collection_name, :record_id, :record);`,

  updateData: `
    INSERT OR REPLACE INTO collection_data (collection_name, record_id, record)
      VALUES (:collection_name, :record_id, :record);`,

  deleteData: `
    DELETE FROM collection_data
      WHERE collection_name = :collection_name
      AND record_id = :record_id;`,

  saveLastModified: `
    INSERT INTO collection_metadata(collection_name, last_modified)
      VALUES(:collection_name, :last_modified)
        ON CONFLICT(collection_name) DO UPDATE SET last_modified = :last_modified`,

  getLastModified: `
    SELECT last_modified
      FROM collection_metadata
        WHERE collection_name = :collection_name;`,

  saveMetadata: `
    INSERT INTO collection_metadata(collection_name, metadata)
      VALUES(:collection_name, :metadata)
        ON CONFLICT(collection_name) DO UPDATE SET metadata = :metadata`,

  getMetadata: `
    SELECT metadata
      FROM collection_metadata
        WHERE collection_name = :collection_name;`,

  getRecord: `
    SELECT record
      FROM collection_data
        WHERE collection_name = :collection_name
        AND record_id = :record_id;`,

  listRecords: `
    SELECT record
      FROM collection_data
        WHERE collection_name = :collection_name;`,

  // N.B. we have to have a dynamic number of placeholders, which you
  // can't do without building your own statement. See `execute` for details
  listRecordsById: `
    SELECT record_id, record
      FROM collection_data
        WHERE collection_name = ?
          AND record_id IN `,

  importData: `
    REPLACE INTO collection_data (collection_name, record_id, record)
      VALUES (:collection_name, :record_id, :record);`,

  scanAllRecords: `SELECT * FROM collection_data;`,

  clearCollectionMetadata: `DELETE FROM collection_metadata;`,

  calculateStorage: `
    SELECT collection_name, SUM(LENGTH(record)) as size, COUNT(record) as num_records
      FROM collection_data
        GROUP BY collection_name;`,

  addMetadataColumn: `
    ALTER TABLE collection_metadata
      ADD COLUMN metadata TEXT;`,
};

const createStatements = [
  "createCollectionData",
  "createCollectionMetadata",
  "createCollectionDataRecordIdIndex",
];

const currentSchemaVersion = 2;

/**
 * Firefox adapter.
 *
 * Uses Sqlite as a backing store.
 *
 * Options:
 *  - sqliteHandle: a handle to the Sqlite database this adapter will
 *    use as its backing store. To open such a handle, use the
 *    static openConnection() method.
 */
export class FirefoxAdapter extends Kinto.adapters.BaseAdapter {
  constructor(collection, options = {}) {
    super();
    const { sqliteHandle = null } = options;
    this.collection = collection;
    this._connection = sqliteHandle;
    this._options = options;
  }

  /**
   * Initialize a Sqlite connection to be suitable for use with Kinto.
   *
   * This will be called automatically by open().
   */
  static async _init(connection) {
    await connection.executeTransaction(async function doSetup() {
      const schema = await connection.getSchemaVersion();

      if (schema == 0) {
        for (let statementName of createStatements) {
          await connection.execute(statements[statementName]);
        }
        await connection.setSchemaVersion(currentSchemaVersion);
      } else if (schema == 1) {
        await connection.execute(statements.addMetadataColumn);
        await connection.setSchemaVersion(currentSchemaVersion);
      } else if (schema != 2) {
        throw new Error("Unknown database schema: " + schema);
      }
    });
    return connection;
  }

  _executeStatement(statement, params) {
    return this._connection.executeCached(statement, params);
  }

  /**
   * Open and initialize a Sqlite connection to a database that Kinto
   * can use. When you are done with this connection, close it by
   * calling close().
   *
   * Options:
   *   - path: The path for the Sqlite database
   *
   * @returns SqliteConnection
   */
  static async openConnection(options) {
    const opts = Object.assign({}, { sharedMemoryCache: false }, options);
    const conn = await Sqlite.openConnection(opts).then(this._init);
    try {
      Sqlite.shutdown.addBlocker(
        "Kinto storage adapter connection closing",
        () => conn.close()
      );
    } catch (e) {
      // It's too late to block shutdown, just close the connection.
      await conn.close();
      throw e;
    }
    return conn;
  }

  clear() {
    const params = { collection_name: this.collection };
    return this._executeStatement(statements.clearData, params);
  }

  execute(callback, options = { preload: [] }) {
    let result;
    const conn = this._connection;
    const collection = this.collection;

    return conn
      .executeTransaction(async function doExecuteTransaction() {
        // Preload specified records from DB, within transaction.

        // if options.preload has more elements than the sqlite variable
        // limit, split it up.
        const limit = 100;
        let preloaded = {};
        let preload;
        let more = options.preload;

        while (more.length) {
          preload = more.slice(0, limit);
          more = more.slice(limit, more.length);

          const parameters = [collection, ...preload];
          const placeholders = preload.map(_ => "?");
          const stmt =
            statements.listRecordsById + "(" + placeholders.join(",") + ");";
          const rows = await conn.execute(stmt, parameters);

          rows.reduce((acc, row) => {
            const record = JSON.parse(row.getResultByName("record"));
            acc[row.getResultByName("record_id")] = record;
            return acc;
          }, preloaded);
        }
        const proxy = transactionProxy(collection, preloaded);
        result = callback(proxy);

        for (let { statement, params } of proxy.operations) {
          await conn.executeCached(statement, params);
        }
      }, conn.TRANSACTION_EXCLUSIVE)
      .then(_ => result);
  }

  get(id) {
    const params = {
      collection_name: this.collection,
      record_id: id,
    };
    return this._executeStatement(statements.getRecord, params).then(result => {
      if (!result.length) {
        return null;
      }
      return JSON.parse(result[0].getResultByName("record"));
    });
  }

  list(params = { filters: {}, order: "" }) {
    const parameters = {
      collection_name: this.collection,
    };
    return this._executeStatement(statements.listRecords, parameters)
      .then(result => {
        const records = [];
        for (let k = 0; k < result.length; k++) {
          const row = result[k];
          records.push(JSON.parse(row.getResultByName("record")));
        }
        return records;
      })
      .then(results => {
        // The resulting list of records is filtered and sorted.
        // XXX: with some efforts, this could be implemented using SQL.
        return reduceRecords(params.filters, params.order, results);
      });
  }

  async loadDump(records) {
    return this.importBulk(records);
  }

  /**
   * Load a list of records into the local database.
   *
   * Note: The adapter is not in charge of filtering the already imported
   * records. This is done in `Collection#loadDump()`, as a common behaviour
   * between every adapters.
   *
   * @param  {Array} records.
   * @return {Array} imported records.
   */
  async importBulk(records) {
    const connection = this._connection;
    const collection_name = this.collection;
    await connection.executeTransaction(async function doImport() {
      for (let record of records) {
        const params = {
          collection_name,
          record_id: record.id,
          record: JSON.stringify(record),
        };
        await connection.execute(statements.importData, params);
      }
      const lastModified = Math.max(
        ...records.map(record => record.last_modified)
      );
      const params = {
        collection_name,
      };
      const previousLastModified = await connection
        .execute(statements.getLastModified, params)
        .then(result => {
          return result.length
            ? result[0].getResultByName("last_modified")
            : -1;
        });
      if (lastModified > previousLastModified) {
        const params = {
          collection_name,
          last_modified: lastModified,
        };
        await connection.execute(statements.saveLastModified, params);
      }
    });
    return records;
  }

  saveLastModified(lastModified) {
    const parsedLastModified = parseInt(lastModified, 10) || null;
    const params = {
      collection_name: this.collection,
      last_modified: parsedLastModified,
    };
    return this._executeStatement(statements.saveLastModified, params).then(
      () => parsedLastModified
    );
  }

  getLastModified() {
    const params = {
      collection_name: this.collection,
    };
    return this._executeStatement(statements.getLastModified, params).then(
      result => {
        if (!result.length) {
          return 0;
        }
        return result[0].getResultByName("last_modified");
      }
    );
  }

  async saveMetadata(metadata) {
    const params = {
      collection_name: this.collection,
      metadata: JSON.stringify(metadata),
    };
    await this._executeStatement(statements.saveMetadata, params);
    return metadata;
  }

  async getMetadata() {
    const params = {
      collection_name: this.collection,
    };
    const result = await this._executeStatement(statements.getMetadata, params);
    if (!result.length) {
      return null;
    }
    return JSON.parse(result[0].getResultByName("metadata"));
  }

  calculateStorage() {
    return this._executeStatement(statements.calculateStorage, {}).then(
      result => {
        return Array.from(result, row => ({
          collectionName: row.getResultByName("collection_name"),
          size: row.getResultByName("size"),
          numRecords: row.getResultByName("num_records"),
        }));
      }
    );
  }

  /**
   * Reset the sync status of every record and collection we have
   * access to.
   */
  resetSyncStatus() {
    // We're going to use execute instead of executeCached, so build
    // in our own sanity check
    if (!this._connection) {
      throw new Error("The storage adapter is not open");
    }

    return this._connection.executeTransaction(async function (conn) {
      const promises = [];
      await conn.execute(statements.scanAllRecords, null, function (row) {
        const record = JSON.parse(row.getResultByName("record"));
        const record_id = row.getResultByName("record_id");
        const collection_name = row.getResultByName("collection_name");
        if (record._status === "deleted") {
          // Garbage collect deleted records.
          promises.push(
            conn.execute(statements.deleteData, { collection_name, record_id })
          );
        } else {
          const newRecord = Object.assign({}, record, {
            _status: "created",
            last_modified: undefined,
          });
          promises.push(
            conn.execute(statements.updateData, {
              record: JSON.stringify(newRecord),
              record_id,
              collection_name,
            })
          );
        }
      });
      await Promise.all(promises);
      await conn.execute(statements.clearCollectionMetadata);
    });
  }
}

function transactionProxy(collection, preloaded) {
  const _operations = [];

  return {
    get operations() {
      return _operations;
    },

    create(record) {
      _operations.push({
        statement: statements.createData,
        params: {
          collection_name: collection,
          record_id: record.id,
          record: JSON.stringify(record),
        },
      });
    },

    update(record) {
      _operations.push({
        statement: statements.updateData,
        params: {
          collection_name: collection,
          record_id: record.id,
          record: JSON.stringify(record),
        },
      });
    },

    delete(id) {
      _operations.push({
        statement: statements.deleteData,
        params: {
          collection_name: collection,
          record_id: id,
        },
      });
    },

    get(id) {
      // Gecko JS engine outputs undesired warnings if id is not in preloaded.
      return id in preloaded ? preloaded[id] : undefined;
    },
  };
}
PK
!<.0����)modules/services-common/observers.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * A service for adding, removing and notifying observers of notifications.
 * Wraps the nsIObserverService interface.
 *
 * @version 0.2
 */
export var Observers = {
  /**
   * Register the given callback as an observer of the given topic.
   *
   * @param   topic       {String}
   *          the topic to observe
   *
   * @param   callback    {Object}
   *          the callback; an Object that implements nsIObserver or a Function
   *          that gets called when the notification occurs
   *
   * @param   thisObject  {Object}  [optional]
   *          the object to use as |this| when calling a Function callback
   *
   * @returns the observer
   */
  add(topic, callback, thisObject) {
    let observer = new Observer(topic, callback, thisObject);
    this._cache.push(observer);
    Services.obs.addObserver(observer, topic, true);

    return observer;
  },

  /**
   * Unregister the given callback as an observer of the given topic.
   *
   * @param topic       {String}
   *        the topic being observed
   *
   * @param callback    {Object}
   *        the callback doing the observing
   *
   * @param thisObject  {Object}  [optional]
   *        the object being used as |this| when calling a Function callback
   */
  remove(topic, callback, thisObject) {
    // This seems fairly inefficient, but I'm not sure how much better
    // we can make it.  We could index by topic, but we can't index by callback
    // or thisObject, as far as I know, since the keys to JavaScript hashes
    // (a.k.a. objects) can apparently only be primitive values.
    let [observer] = this._cache.filter(
      v =>
        v.topic == topic && v.callback == callback && v.thisObject == thisObject
    );
    if (observer) {
      Services.obs.removeObserver(observer, topic);
      this._cache.splice(this._cache.indexOf(observer), 1);
    } else {
      throw new Error("Attempt to remove non-existing observer");
    }
  },

  /**
   * Notify observers about something.
   *
   * @param topic   {String}
   *        the topic to notify observers about
   *
   * @param subject {Object}  [optional]
   *        some information about the topic; can be any JS object or primitive
   *
   * @param data    {String}  [optional] [deprecated]
   *        some more information about the topic; deprecated as the subject
   *        is sufficient to pass all needed information to the JS observers
   *        that this module targets; if you have multiple values to pass to
   *        the observer, wrap them in an object and pass them via the subject
   *        parameter (i.e.: { foo: 1, bar: "some string", baz: myObject })
   */
  notify(topic, subject, data) {
    subject = typeof subject == "undefined" ? null : new Subject(subject);
    data = typeof data == "undefined" ? null : data;
    Services.obs.notifyObservers(subject, topic, data);
  },

  /**
   * A cache of observers that have been added.
   *
   * We use this to remove observers when a caller calls |remove|.
   *
   * XXX This might result in reference cycles, causing memory leaks,
   * if we hold a reference to an observer that holds a reference to us.
   * Could we fix that by making this an independent top-level object
   * rather than a property of this object?
   */
  _cache: [],
};

function Observer(topic, callback, thisObject) {
  this.topic = topic;
  this.callback = callback;
  this.thisObject = thisObject;
}

Observer.prototype = {
  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
  ]),
  observe(subject, topic, data) {
    // Extract the wrapped object for subjects that are one of our wrappers
    // around a JS object.  This way we support both wrapped subjects created
    // using this module and those that are real XPCOM components.
    if (
      subject &&
      typeof subject == "object" &&
      "wrappedJSObject" in subject &&
      "observersModuleSubjectWrapper" in subject.wrappedJSObject
    ) {
      subject = subject.wrappedJSObject.object;
    }

    if (typeof this.callback == "function") {
      if (this.thisObject) {
        this.callback.call(this.thisObject, subject, data);
      } else {
        this.callback(subject, data);
      }
    } else {
      // typeof this.callback == "object" (nsIObserver)
      this.callback.observe(subject, topic, data);
    }
  },
};

function Subject(object) {
  // Double-wrap the object and set a property identifying the wrappedJSObject
  // as one of our wrappers to distinguish between subjects that are one of our
  // wrappers (which we should unwrap when notifying our observers) and those
  // that are real JS XPCOM components (which we should pass through unaltered).
  this.wrappedJSObject = { observersModuleSubjectWrapper: true, object };
}

Subject.prototype = {
  QueryInterface: ChromeUtils.generateQI([]),
  getScriptableHelper() {},
  getInterfaces() {},
};
PK
!<.A��K�K$modules/services-common/rest.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { NetUtil } from "resource://gre/modules/NetUtil.sys.mjs";

import { Log } from "resource://gre/modules/Log.sys.mjs";

import { CommonUtils } from "resource://services-common/utils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  CryptoUtils: "resource://services-crypto/utils.sys.mjs",
});

function decodeString(data, charset) {
  if (!data || !charset) {
    return data;
  }

  // This could be simpler if we assumed the charset is only ever UTF-8.
  // It's unclear to me how willing we are to assume this, though...
  let stringStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
    Ci.nsIStringInputStream
  );
  stringStream.setData(data, data.length);

  let converterStream = Cc[
    "@mozilla.org/intl/converter-input-stream;1"
  ].createInstance(Ci.nsIConverterInputStream);

  converterStream.init(
    stringStream,
    charset,
    0,
    converterStream.DEFAULT_REPLACEMENT_CHARACTER
  );

  let remaining = data.length;
  let body = "";
  while (remaining > 0) {
    let str = {};
    let num = converterStream.readString(remaining, str);
    if (!num) {
      break;
    }
    remaining -= num;
    body += str.value;
  }
  return body;
}

/**
 * Single use HTTP requests to RESTish resources.
 *
 * @param uri
 *        URI for the request. This can be an nsIURI object or a string
 *        that can be used to create one. An exception will be thrown if
 *        the string is not a valid URI.
 *
 * Examples:
 *
 * (1) Quick GET request:
 *
 *   let response = await new RESTRequest("http://server/rest/resource").get();
 *   if (!response.success) {
 *     // Bail out if we're not getting an HTTP 2xx code.
 *     processHTTPError(response.status);
 *     return;
 *   }
 *   processData(response.body);
 *
 * (2) Quick PUT request (non-string data is automatically JSONified)
 *
 *   let response = await new RESTRequest("http://server/rest/resource").put(data);
 */
export function RESTRequest(uri) {
  this.status = this.NOT_SENT;

  // If we don't have an nsIURI object yet, make one. This will throw if
  // 'uri' isn't a valid URI string.
  if (!(uri instanceof Ci.nsIURI)) {
    uri = Services.io.newURI(uri);
  }
  this.uri = uri;

  this._headers = {};
  this._deferred = Promise.withResolvers();
  this._log = Log.repository.getLogger(this._logName);
  this._log.manageLevelFromPref("services.common.log.logger.rest.request");
}

RESTRequest.prototype = {
  _logName: "Services.Common.RESTRequest",

  QueryInterface: ChromeUtils.generateQI([
    "nsIInterfaceRequestor",
    "nsIChannelEventSink",
  ]),

  /** Public API: **/

  /**
   * URI for the request (an nsIURI object).
   */
  uri: null,

  /**
   * HTTP method (e.g. "GET")
   */
  method: null,

  /**
   * RESTResponse object
   */
  response: null,

  /**
   * nsIRequest load flags. Don't do any caching by default. Don't send user
   * cookies and such over the wire (Bug 644734).
   */
  loadFlags:
    Ci.nsIRequest.LOAD_BYPASS_CACHE |
    Ci.nsIRequest.INHIBIT_CACHING |
    Ci.nsIRequest.LOAD_ANONYMOUS,

  /**
   * nsIHttpChannel
   */
  channel: null,

  /**
   * Flag to indicate the status of the request.
   *
   * One of NOT_SENT, SENT, IN_PROGRESS, COMPLETED, ABORTED.
   */
  status: null,

  NOT_SENT: 0,
  SENT: 1,
  IN_PROGRESS: 2,
  COMPLETED: 4,
  ABORTED: 8,

  /**
   * HTTP status text of response
   */
  statusText: null,

  /**
   * Request timeout (in seconds, though decimal values can be used for
   * up to millisecond granularity.)
   *
   * 0 for no timeout. Default is 300 seconds (5 minutes), the same as Sync uses
   * in resource.js.
   */
  timeout: 300,

  /**
   * The encoding with which the response to this request must be treated.
   * If a charset parameter is available in the HTTP Content-Type header for
   * this response, that will always be used, and this value is ignored. We
   * default to UTF-8 because that is a reasonable default.
   */
  charset: "utf-8",

  /**
   * Set a request header.
   */
  setHeader(name, value) {
    this._headers[name.toLowerCase()] = value;
  },

  /**
   * Perform an HTTP GET.
   *
   * @return Promise<RESTResponse>
   */
  async get() {
    return this.dispatch("GET", null);
  },

  /**
   * Perform an HTTP PATCH.
   *
   * @param data
   *        Data to be used as the request body. If this isn't a string
   *        it will be JSONified automatically.
   *
   * @return Promise<RESTResponse>
   */
  async patch(data) {
    return this.dispatch("PATCH", data);
  },

  /**
   * Perform an HTTP PUT.
   *
   * @param data
   *        Data to be used as the request body. If this isn't a string
   *        it will be JSONified automatically.
   *
   * @return Promise<RESTResponse>
   */
  async put(data) {
    return this.dispatch("PUT", data);
  },

  /**
   * Perform an HTTP POST.
   *
   * @param data
   *        Data to be used as the request body. If this isn't a string
   *        it will be JSONified automatically.
   *
   * @return Promise<RESTResponse>
   */
  async post(data) {
    return this.dispatch("POST", data);
  },

  /**
   * Perform an HTTP DELETE.
   *
   * @return Promise<RESTResponse>
   */
  async delete() {
    return this.dispatch("DELETE", null);
  },

  /**
   * Abort an active request.
   */
  abort(rejectWithError = null) {
    if (this.status != this.SENT && this.status != this.IN_PROGRESS) {
      throw new Error("Can only abort a request that has been sent.");
    }

    this.status = this.ABORTED;
    this.channel.cancel(Cr.NS_BINDING_ABORTED);

    if (this.timeoutTimer) {
      // Clear the abort timer now that the channel is done.
      this.timeoutTimer.clear();
    }
    if (rejectWithError) {
      this._deferred.reject(rejectWithError);
    }
  },

  /** Implementation stuff **/

  async dispatch(method, data) {
    if (this.status != this.NOT_SENT) {
      throw new Error("Request has already been sent!");
    }

    this.method = method;

    // Create and initialize HTTP channel.
    let channel = NetUtil.newChannel({
      uri: this.uri,
      loadUsingSystemPrincipal: true,
    })
      .QueryInterface(Ci.nsIRequest)
      .QueryInterface(Ci.nsIHttpChannel);
    this.channel = channel;
    channel.loadFlags |= this.loadFlags;
    channel.notificationCallbacks = this;

    this._log.debug(`${method} request to ${this.uri.spec}`);
    // Set request headers.
    let headers = this._headers;
    for (let key in headers) {
      if (key == "authorization" || key == "x-client-state") {
        this._log.trace("HTTP Header " + key + ": ***** (suppressed)");
      } else {
        this._log.trace("HTTP Header " + key + ": " + headers[key]);
      }
      channel.setRequestHeader(key, headers[key], false);
    }

    // REST requests accept JSON by default
    if (!headers.accept) {
      channel.setRequestHeader(
        "accept",
        "application/json;q=0.9,*/*;q=0.2",
        false
      );
    }

    // Set HTTP request body.
    if (method == "PUT" || method == "POST" || method == "PATCH") {
      // Convert non-string bodies into JSON with utf-8 encoding. If a string
      // is passed we assume they've already encoded it.
      let contentType = headers["content-type"];
      if (typeof data != "string") {
        data = JSON.stringify(data);
        if (!contentType) {
          contentType = "application/json";
        }
        if (!contentType.includes("charset")) {
          data = CommonUtils.encodeUTF8(data);
          contentType += "; charset=utf-8";
        } else {
          // If someone handed us an object but also a custom content-type
          // it's probably confused. We could go to even further lengths to
          // respect it, but this shouldn't happen in practice.
          console.error(
            "rest.js found an object to JSON.stringify but also a " +
              "content-type header with a charset specification. " +
              "This probably isn't going to do what you expect"
          );
        }
      }
      if (!contentType) {
        contentType = "text/plain";
      }

      this._log.debug(method + " Length: " + data.length);
      if (this._log.level <= Log.Level.Trace) {
        this._log.trace(method + " Body: " + data);
      }

      let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
        Ci.nsIStringInputStream
      );
      stream.setData(data, data.length);

      channel.QueryInterface(Ci.nsIUploadChannel);
      channel.setUploadStream(stream, contentType, data.length);
    }
    // We must set this after setting the upload stream, otherwise it
    // will always be 'PUT'. Yeah, I know.
    channel.requestMethod = method;

    // Before opening the channel, set the charset that serves as a hint
    // as to what the response might be encoded as.
    channel.contentCharset = this.charset;

    // Blast off!
    try {
      channel.asyncOpen(this);
    } catch (ex) {
      // asyncOpen can throw in a bunch of cases -- e.g., a forbidden port.
      this._log.warn("Caught an error in asyncOpen", ex);
      this._deferred.reject(ex);
    }
    this.status = this.SENT;
    this.delayTimeout();
    return this._deferred.promise;
  },

  /**
   * Create or push back the abort timer that kills this request.
   */
  delayTimeout() {
    if (this.timeout) {
      CommonUtils.namedTimer(
        this.abortTimeout,
        this.timeout * 1000,
        this,
        "timeoutTimer"
      );
    }
  },

  /**
   * Abort the request based on a timeout.
   */
  abortTimeout() {
    this.abort(
      Components.Exception(
        "Aborting due to channel inactivity.",
        Cr.NS_ERROR_NET_TIMEOUT
      )
    );
  },

  /** nsIStreamListener **/

  onStartRequest(channel) {
    if (this.status == this.ABORTED) {
      this._log.trace(
        "Not proceeding with onStartRequest, request was aborted."
      );
      // We might have already rejected, but just in case.
      this._deferred.reject(
        Components.Exception("Request aborted", Cr.NS_BINDING_ABORTED)
      );
      return;
    }

    try {
      channel.QueryInterface(Ci.nsIHttpChannel);
    } catch (ex) {
      this._log.error("Unexpected error: channel is not a nsIHttpChannel!");
      this.status = this.ABORTED;
      channel.cancel(Cr.NS_BINDING_ABORTED);
      this._deferred.reject(ex);
      return;
    }

    this.status = this.IN_PROGRESS;

    this._log.trace(
      "onStartRequest: " + channel.requestMethod + " " + channel.URI.spec
    );

    // Create a new response object.
    this.response = new RESTResponse(this);

    this.delayTimeout();
  },

  onStopRequest(channel, statusCode) {
    if (this.timeoutTimer) {
      // Clear the abort timer now that the channel is done.
      this.timeoutTimer.clear();
    }

    // We don't want to do anything for a request that's already been aborted.
    if (this.status == this.ABORTED) {
      this._log.trace(
        "Not proceeding with onStopRequest, request was aborted."
      );
      // We might not have already rejected if the user called reject() manually.
      // If we have already rejected, then this is a no-op
      this._deferred.reject(
        Components.Exception("Request aborted", Cr.NS_BINDING_ABORTED)
      );
      return;
    }

    try {
      channel.QueryInterface(Ci.nsIHttpChannel);
    } catch (ex) {
      this._log.error("Unexpected error: channel not nsIHttpChannel!");
      this.status = this.ABORTED;
      this._deferred.reject(ex);
      return;
    }

    this.status = this.COMPLETED;

    try {
      this.response.body = decodeString(
        this.response._rawBody,
        this.response.charset
      );
      this.response._rawBody = null;
    } catch (ex) {
      this._log.warn(
        `Exception decoding response - ${this.method} ${channel.URI.spec}`,
        ex
      );
      this._deferred.reject(ex);
      return;
    }

    let statusSuccess = Components.isSuccessCode(statusCode);
    let uri = (channel && channel.URI && channel.URI.spec) || "<unknown>";
    this._log.trace(
      "Channel for " +
        channel.requestMethod +
        " " +
        uri +
        " returned status code " +
        statusCode
    );

    // Throw the failure code and stop execution.  Use Components.Exception()
    // instead of Error() so the exception is QI-able and can be passed across
    // XPCOM borders while preserving the status code.
    if (!statusSuccess) {
      let message = Components.Exception("", statusCode).name;
      let error = Components.Exception(message, statusCode);
      this._log.debug(
        this.method + " " + uri + " failed: " + statusCode + " - " + message
      );
      // Additionally give the full response body when Trace logging.
      if (this._log.level <= Log.Level.Trace) {
        this._log.trace(this.method + " body", this.response.body);
      }
      this._deferred.reject(error);
      return;
    }

    this._log.debug(this.method + " " + uri + " " + this.response.status);

    // Note that for privacy/security reasons we don't log this response body

    delete this._inputStream;

    this._deferred.resolve(this.response);
  },

  onDataAvailable(channel, stream, off, count) {
    // We get an nsIRequest, which doesn't have contentCharset.
    try {
      channel.QueryInterface(Ci.nsIHttpChannel);
    } catch (ex) {
      this._log.error("Unexpected error: channel not nsIHttpChannel!");
      this.abort(ex);
      return;
    }

    if (channel.contentCharset) {
      this.response.charset = channel.contentCharset;
    } else {
      this.response.charset = null;
    }

    if (!this._inputStream) {
      this._inputStream = Cc[
        "@mozilla.org/scriptableinputstream;1"
      ].createInstance(Ci.nsIScriptableInputStream);
    }
    this._inputStream.init(stream);

    this.response._rawBody += this._inputStream.read(count);

    this.delayTimeout();
  },

  /** nsIInterfaceRequestor **/

  getInterface(aIID) {
    return this.QueryInterface(aIID);
  },

  /**
   * Returns true if headers from the old channel should be
   * copied to the new channel. Invoked when a channel redirect
   * is in progress.
   */
  shouldCopyOnRedirect(oldChannel, newChannel, flags) {
    let isInternal = !!(flags & Ci.nsIChannelEventSink.REDIRECT_INTERNAL);
    let isSameURI = newChannel.URI.equals(oldChannel.URI);
    this._log.debug(
      "Channel redirect: " +
        oldChannel.URI.spec +
        ", " +
        newChannel.URI.spec +
        ", internal = " +
        isInternal
    );
    return isInternal && isSameURI;
  },

  /** nsIChannelEventSink **/
  asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
    let oldSpec =
      oldChannel && oldChannel.URI ? oldChannel.URI.spec : "<undefined>";
    let newSpec =
      newChannel && newChannel.URI ? newChannel.URI.spec : "<undefined>";
    this._log.debug(
      "Channel redirect: " + oldSpec + ", " + newSpec + ", " + flags
    );

    try {
      newChannel.QueryInterface(Ci.nsIHttpChannel);
    } catch (ex) {
      this._log.error("Unexpected error: channel not nsIHttpChannel!");
      callback.onRedirectVerifyCallback(Cr.NS_ERROR_NO_INTERFACE);
      return;
    }

    // For internal redirects, copy the headers that our caller set.
    try {
      if (this.shouldCopyOnRedirect(oldChannel, newChannel, flags)) {
        this._log.trace("Copying headers for safe internal redirect.");
        for (let key in this._headers) {
          newChannel.setRequestHeader(key, this._headers[key], false);
        }
      }
    } catch (ex) {
      this._log.error("Error copying headers", ex);
    }

    this.channel = newChannel;

    // We let all redirects proceed.
    callback.onRedirectVerifyCallback(Cr.NS_OK);
  },
};

/**
 * Response object for a RESTRequest. This will be created automatically by
 * the RESTRequest.
 */
export function RESTResponse(request = null) {
  this.body = "";
  this._rawBody = "";
  this.request = request;
  this._log = Log.repository.getLogger(this._logName);
  this._log.manageLevelFromPref("services.common.log.logger.rest.response");
}

RESTResponse.prototype = {
  _logName: "Services.Common.RESTResponse",

  /**
   * Corresponding REST request
   */
  request: null,

  /**
   * HTTP status code
   */
  get status() {
    let status;
    try {
      status = this.request.channel.responseStatus;
    } catch (ex) {
      this._log.debug("Caught exception fetching HTTP status code", ex);
      return null;
    }
    Object.defineProperty(this, "status", { value: status });
    return status;
  },

  /**
   * HTTP status text
   */
  get statusText() {
    let statusText;
    try {
      statusText = this.request.channel.responseStatusText;
    } catch (ex) {
      this._log.debug("Caught exception fetching HTTP status text", ex);
      return null;
    }
    Object.defineProperty(this, "statusText", { value: statusText });
    return statusText;
  },

  /**
   * Boolean flag that indicates whether the HTTP status code is 2xx or not.
   */
  get success() {
    let success;
    try {
      success = this.request.channel.requestSucceeded;
    } catch (ex) {
      this._log.debug("Caught exception fetching HTTP success flag", ex);
      return null;
    }
    Object.defineProperty(this, "success", { value: success });
    return success;
  },

  /**
   * Object containing HTTP headers (keyed as lower case)
   */
  get headers() {
    let headers = {};
    try {
      this._log.trace("Processing response headers.");
      let channel = this.request.channel.QueryInterface(Ci.nsIHttpChannel);
      channel.visitResponseHeaders(function (header, value) {
        headers[header.toLowerCase()] = value;
      });
    } catch (ex) {
      this._log.debug("Caught exception processing response headers", ex);
      return null;
    }

    Object.defineProperty(this, "headers", { value: headers });
    return headers;
  },

  /**
   * HTTP body (string)
   */
  body: null,
};

/**
 * Single use MAC authenticated HTTP requests to RESTish resources.
 *
 * @param uri
 *        URI going to the RESTRequest constructor.
 * @param authToken
 *        (Object) An auth token of the form {id: (string), key: (string)}
 *        from which the MAC Authentication header for this request will be
 *        derived. A token as obtained from
 *        TokenServerClient.getTokenUsingOAuth is accepted.
 * @param extra
 *        (Object) Optional extra parameters. Valid keys are: nonce_bytes, ts,
 *        nonce, and ext. See CrytoUtils.computeHTTPMACSHA1 for information on
 *        the purpose of these values.
 */
export function TokenAuthenticatedRESTRequest(uri, authToken, extra) {
  RESTRequest.call(this, uri);
  this.authToken = authToken;
  this.extra = extra || {};
}

TokenAuthenticatedRESTRequest.prototype = {
  async dispatch(method, data) {
    let sig = await lazy.CryptoUtils.computeHTTPMACSHA1(
      this.authToken.id,
      this.authToken.key,
      method,
      this.uri,
      this.extra
    );

    this.setHeader("Authorization", sig.getHeader());

    return super.dispatch(method, data);
  },
};

Object.setPrototypeOf(
  TokenAuthenticatedRESTRequest.prototype,
  RESTRequest.prototype
);
PK
!<p�~B�2�21modules/services-common/tokenserverclient.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Log } from "resource://gre/modules/Log.sys.mjs";

import { RESTRequest } from "resource://services-common/rest.sys.mjs";
import { Observers } from "resource://services-common/observers.sys.mjs";

const PREF_LOG_LEVEL = "services.common.log.logger.tokenserverclient";

/**
 * Represents a TokenServerClient error that occurred on the client.
 *
 * This is the base type for all errors raised by client operations.
 *
 * @param message
 *        (string) Error message.
 */
export function TokenServerClientError(message) {
  this.name = "TokenServerClientError";
  this.message = message || "Client error.";
  // Without explicitly setting .stack, all stacks from these errors will point
  // to the "new Error()" call a few lines down, which isn't helpful.
  this.stack = Error().stack;
}

TokenServerClientError.prototype = new Error();
TokenServerClientError.prototype.constructor = TokenServerClientError;
TokenServerClientError.prototype._toStringFields = function () {
  return { message: this.message };
};
TokenServerClientError.prototype.toString = function () {
  return this.name + "(" + JSON.stringify(this._toStringFields()) + ")";
};
TokenServerClientError.prototype.toJSON = function () {
  let result = this._toStringFields();
  result.name = this.name;
  return result;
};

/**
 * Represents a TokenServerClient error that occurred in the network layer.
 *
 * @param error
 *        The underlying error thrown by the network layer.
 */
export function TokenServerClientNetworkError(error) {
  this.name = "TokenServerClientNetworkError";
  this.error = error;
  this.stack = Error().stack;
}

TokenServerClientNetworkError.prototype = new TokenServerClientError();
TokenServerClientNetworkError.prototype.constructor =
  TokenServerClientNetworkError;
TokenServerClientNetworkError.prototype._toStringFields = function () {
  return { error: this.error };
};

/**
 * Represents a TokenServerClient error that occurred on the server.
 *
 * This type will be encountered for all non-200 response codes from the
 * server. The type of error is strongly enumerated and is stored in the
 * `cause` property. This property can have the following string values:
 *
 *   invalid-credentials -- A token could not be obtained because
 *     the credentials presented by the client were invalid.
 *
 *   unknown-service -- The requested service was not found.
 *
 *   malformed-request -- The server rejected the request because it
 *     was invalid. If you see this, code in this file is likely wrong.
 *
 *   malformed-response -- The response from the server was not what was
 *     expected.
 *
 *   general -- A general server error has occurred. Clients should
 *     interpret this as an opaque failure.
 *
 * @param message
 *        (string) Error message.
 */
export function TokenServerClientServerError(message, cause = "general") {
  this.now = new Date().toISOString(); // may be useful to diagnose time-skew issues.
  this.name = "TokenServerClientServerError";
  this.message = message || "Server error.";
  this.cause = cause;
  this.stack = Error().stack;
}

TokenServerClientServerError.prototype = new TokenServerClientError();
TokenServerClientServerError.prototype.constructor =
  TokenServerClientServerError;

TokenServerClientServerError.prototype._toStringFields = function () {
  let fields = {
    now: this.now,
    message: this.message,
    cause: this.cause,
  };
  if (this.response) {
    fields.response_body = this.response.body;
    fields.response_headers = this.response.headers;
    fields.response_status = this.response.status;
  }
  return fields;
};

/**
 * Represents a client to the Token Server.
 *
 * http://docs.services.mozilla.com/token/index.html
 *
 * The Token Server was designed to support obtaining tokens for arbitrary apps by
 * constructing URI paths of the form <app>/<app_version>. In practice this was
 * never used and it only supports an <app> value of `sync`, and the API presented
 * here reflects that.
 *
 * Areas to Improve:
 *
 *  - The server sends a JSON response on error. The client does not currently
 *    parse this. It might be convenient if it did.
 *  - Currently most non-200 status codes are rolled into one error type. It
 *    might be helpful if callers had a richer API that communicated who was
 *    at fault (e.g. differentiating a 503 from a 401).
 */
export function TokenServerClient() {
  this._log = Log.repository.getLogger("Services.Common.TokenServerClient");
  this._log.manageLevelFromPref(PREF_LOG_LEVEL);
}

TokenServerClient.prototype = {
  /**
   * Logger instance.
   */
  _log: null,

  /**
   * Obtain a token from a provided OAuth token against a specific URL.
   *
   * This asynchronously obtains the token.
   * It returns a Promise that resolves or rejects:
   *
   *  Rejects with:
   *   (TokenServerClientError) If no token could be obtained, this
   *     will be a TokenServerClientError instance describing why. The
   *     type seen defines the type of error encountered. If an HTTP response
   *     was seen, a RESTResponse instance will be stored in the `response`
   *     property of this object. If there was no error and a token is
   *     available, this will be null.
   *
   *  Resolves with:
   *   (map) On success, this will be a map containing the results from
   *     the server. If there was an error, this will be null. The map has the
   *     following properties:
   *
   *       id       (string) HTTP MAC public key identifier.
   *       key      (string) HTTP MAC shared symmetric key.
   *       endpoint (string) URL where service can be connected to.
   *       uid      (string) user ID for requested service.
   *       duration (string) the validity duration of the issued token.
   *
   * Example Usage
   * -------------
   *
   *   let client = new TokenServerClient();
   *   let access_token = getOAuthAccessTokenFromSomewhere();
   *   let url = "https://token.services.mozilla.com/1.0/sync/2.0";
   *
   *   try {
   *     const result = await client.getTokenUsingOAuth(url, access_token);
   *     let {id, key, uid, endpoint, duration} = result;
   *     // Do stuff with data and carry on.
   *   } catch (error) {
   *     // Handle errors.
   *   }
   * Obtain a token from a provided OAuth token against a specific URL.
   *
   * @param  url
   *         (string) URL to fetch token from.
   * @param  oauthToken
   *         (string) FxA OAuth Token to exchange token for.
   * @param  addHeaders
   *         (object) Extra headers for the request.
   */
  async getTokenUsingOAuth(url, oauthToken, addHeaders = {}) {
    this._log.debug("Beginning OAuth token exchange: " + url);

    if (!oauthToken) {
      throw new TokenServerClientError("oauthToken argument is not valid.");
    }

    return this._tokenServerExchangeRequest(
      url,
      `Bearer ${oauthToken}`,
      addHeaders
    );
  },

  /**
   * Performs the exchange request to the token server to
   * produce a token based on the authorizationHeader input.
   *
   * @param  url
   *         (string) URL to fetch token from.
   * @param  authorizationHeader
   *         (string) The auth header string that populates the 'Authorization' header.
   * @param  addHeaders
   *         (object) Extra headers for the request.
   */
  async _tokenServerExchangeRequest(url, authorizationHeader, addHeaders = {}) {
    if (!url) {
      throw new TokenServerClientError("url argument is not valid.");
    }

    if (!authorizationHeader) {
      throw new TokenServerClientError(
        "authorizationHeader argument is not valid."
      );
    }

    let req = this.newRESTRequest(url);
    req.setHeader("Accept", "application/json");
    req.setHeader("Authorization", authorizationHeader);

    for (let header in addHeaders) {
      req.setHeader(header, addHeaders[header]);
    }
    let response;
    try {
      response = await req.get();
    } catch (err) {
      throw new TokenServerClientNetworkError(err);
    }

    try {
      return this._processTokenResponse(response);
    } catch (ex) {
      if (ex instanceof TokenServerClientServerError) {
        throw ex;
      }
      this._log.warn("Error processing token server response", ex);
      let error = new TokenServerClientError(ex);
      error.response = response;
      throw error;
    }
  },

  /**
   * Handler to process token request responses.
   *
   * @param response
   *        RESTResponse from token HTTP request.
   */
  _processTokenResponse(response) {
    this._log.debug("Got token response: " + response.status);

    // Responses should *always* be JSON, even in the case of 4xx and 5xx
    // errors. If we don't see JSON, the server is likely very unhappy.
    let ct = response.headers["content-type"] || "";
    if (ct != "application/json" && !ct.startsWith("application/json;")) {
      this._log.warn("Did not receive JSON response. Misconfigured server?");
      this._log.debug("Content-Type: " + ct);
      this._log.debug("Body: " + response.body);

      let error = new TokenServerClientServerError(
        "Non-JSON response.",
        "malformed-response"
      );
      error.response = response;
      throw error;
    }

    let result;
    try {
      result = JSON.parse(response.body);
    } catch (ex) {
      this._log.warn("Invalid JSON returned by server: " + response.body);
      let error = new TokenServerClientServerError(
        "Malformed JSON.",
        "malformed-response"
      );
      error.response = response;
      throw error;
    }

    // Any response status can have X-Backoff or X-Weave-Backoff headers.
    this._maybeNotifyBackoff(response, "x-weave-backoff");
    this._maybeNotifyBackoff(response, "x-backoff");

    // The service shouldn't have any 3xx, so we don't need to handle those.
    if (response.status != 200) {
      // We /should/ have a Cornice error report in the JSON. We log that to
      // help with debugging.
      if ("errors" in result) {
        // This could throw, but this entire function is wrapped in a try. If
        // the server is sending something not an array of objects, it has
        // failed to keep its contract with us and there is little we can do.
        for (let error of result.errors) {
          this._log.info("Server-reported error: " + JSON.stringify(error));
        }
      }

      let error = new TokenServerClientServerError();
      error.response = response;

      if (response.status == 400) {
        error.message = "Malformed request.";
        error.cause = "malformed-request";
      } else if (response.status == 401) {
        // Cause can be invalid-credentials, invalid-timestamp, or
        // invalid-generation.
        error.message = "Authentication failed.";
        error.cause = result.status;
      } else if (response.status == 404) {
        error.message = "Unknown service.";
        error.cause = "unknown-service";
      }

      // A Retry-After header should theoretically only appear on a 503, but
      // we'll look for it on any error response.
      this._maybeNotifyBackoff(response, "retry-after");

      throw error;
    }

    for (let k of ["id", "key", "api_endpoint", "uid", "duration"]) {
      if (!(k in result)) {
        let error = new TokenServerClientServerError(
          "Expected key not present in result: " + k
        );
        error.cause = "malformed-response";
        error.response = response;
        throw error;
      }
    }

    this._log.debug("Successful token response");
    return {
      id: result.id,
      key: result.key,
      endpoint: result.api_endpoint,
      uid: result.uid,
      duration: result.duration,
      hashed_fxa_uid: result.hashed_fxa_uid,
      node_type: result.node_type,
    };
  },

  /*
   * The prefix used for all notifications sent by this module.  This
   * allows the handler of notifications to be sure they are handling
   * notifications for the service they expect.
   *
   * If not set, no notifications will be sent.
   */
  observerPrefix: null,

  // Given an optional header value, notify that a backoff has been requested.
  _maybeNotifyBackoff(response, headerName) {
    if (!this.observerPrefix) {
      return;
    }
    let headerVal = response.headers[headerName];
    if (!headerVal) {
      return;
    }
    let backoffInterval;
    try {
      backoffInterval = parseInt(headerVal, 10);
    } catch (ex) {
      this._log.error(
        "TokenServer response had invalid backoff value in '" +
          headerName +
          "' header: " +
          headerVal
      );
      return;
    }
    Observers.notify(
      this.observerPrefix + ":backoff:interval",
      backoffInterval
    );
  },

  // override points for testing.
  newRESTRequest(url) {
    return new RESTRequest(url);
  },
};
PK
!<�$��+modules/services-crypto/WeaveCrypto.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const CRYPT_ALGO = "AES-CBC";
const CRYPT_ALGO_LENGTH = 256;
const CRYPT_ALGO_USAGES = ["encrypt", "decrypt"];
const AES_CBC_IV_SIZE = 16;
const OPERATIONS = { ENCRYPT: 0, DECRYPT: 1 };
const UTF_LABEL = "utf-8";

export function WeaveCrypto() {
  this.init();
}

WeaveCrypto.prototype = {
  prefBranch: null,
  debug: true, // services.sync.log.cryptoDebug

  observer: {
    _self: null,

    QueryInterface: ChromeUtils.generateQI([
      "nsIObserver",
      "nsISupportsWeakReference",
    ]),

    observe(subject, topic) {
      let self = this._self;
      self.log("Observed " + topic + " topic.");
      if (topic == "nsPref:changed") {
        self.debug = self.prefBranch.getBoolPref("cryptoDebug");
      }
    },
  },

  init() {
    // Preferences. Add observer so we get notified of changes.
    this.prefBranch = Services.prefs.getBranch("services.sync.log.");
    this.prefBranch.addObserver("cryptoDebug", this.observer);
    this.observer._self = this;
    this.debug = this.prefBranch.getBoolPref("cryptoDebug", false);
    ChromeUtils.defineLazyGetter(
      this,
      "encoder",
      () => new TextEncoder(UTF_LABEL)
    );
    ChromeUtils.defineLazyGetter(
      this,
      "decoder",
      () => new TextDecoder(UTF_LABEL, { fatal: true })
    );
  },

  log(message) {
    if (!this.debug) {
      return;
    }
    dump("WeaveCrypto: " + message + "\n");
    Services.console.logStringMessage("WeaveCrypto: " + message);
  },

  // /!\ Only use this for tests! /!\
  _getCrypto() {
    return crypto;
  },

  async encrypt(clearTextUCS2, symmetricKey, iv) {
    this.log("encrypt() called");
    let clearTextBuffer = this.encoder.encode(clearTextUCS2).buffer;
    let encrypted = await this._commonCrypt(
      clearTextBuffer,
      symmetricKey,
      iv,
      OPERATIONS.ENCRYPT
    );
    return this.encodeBase64(encrypted);
  },

  async decrypt(cipherText, symmetricKey, iv) {
    this.log("decrypt() called");
    if (cipherText.length) {
      cipherText = atob(cipherText);
    }
    let cipherTextBuffer = this.byteCompressInts(cipherText);
    let decrypted = await this._commonCrypt(
      cipherTextBuffer,
      symmetricKey,
      iv,
      OPERATIONS.DECRYPT
    );
    return this.decoder.decode(decrypted);
  },

  /**
   * _commonCrypt
   *
   * @args
   * data: data to encrypt/decrypt (ArrayBuffer)
   * symKeyStr: symmetric key (Base64 String)
   * ivStr: initialization vector (Base64 String)
   * operation: operation to apply (either OPERATIONS.ENCRYPT or OPERATIONS.DECRYPT)
   * @returns
   * the encrypted/decrypted data (ArrayBuffer)
   */
  async _commonCrypt(data, symKeyStr, ivStr, operation) {
    this.log("_commonCrypt() called");
    ivStr = atob(ivStr);

    if (operation !== OPERATIONS.ENCRYPT && operation !== OPERATIONS.DECRYPT) {
      throw new Error("Unsupported operation in _commonCrypt.");
    }
    // We never want an IV longer than the block size, which is 16 bytes
    // for AES, neither do we want one smaller; throw in both cases.
    if (ivStr.length !== AES_CBC_IV_SIZE) {
      throw new Error(`Invalid IV size; must be ${AES_CBC_IV_SIZE} bytes.`);
    }

    let iv = this.byteCompressInts(ivStr);
    let symKey = await this.importSymKey(symKeyStr, operation);
    let cryptMethod = (
      operation === OPERATIONS.ENCRYPT
        ? crypto.subtle.encrypt
        : crypto.subtle.decrypt
    ).bind(crypto.subtle);
    let algo = { name: CRYPT_ALGO, iv };

    let keyBytes = await cryptMethod.call(crypto.subtle, algo, symKey, data);
    return new Uint8Array(keyBytes);
  },

  async generateRandomKey() {
    this.log("generateRandomKey() called");
    let algo = {
      name: CRYPT_ALGO,
      length: CRYPT_ALGO_LENGTH,
    };
    let key = await crypto.subtle.generateKey(algo, true, CRYPT_ALGO_USAGES);
    let keyBytes = await crypto.subtle.exportKey("raw", key);
    return this.encodeBase64(new Uint8Array(keyBytes));
  },

  generateRandomIV() {
    return this.generateRandomBytes(AES_CBC_IV_SIZE);
  },

  generateRandomBytes(byteCount) {
    this.log("generateRandomBytes() called");

    let randBytes = new Uint8Array(byteCount);
    crypto.getRandomValues(randBytes);

    return this.encodeBase64(randBytes);
  },

  //
  // SymKey CryptoKey memoization.
  //

  // Memoize the import of symmetric keys. We do this by using the base64
  // string itself as a key.
  _encryptionSymKeyMemo: {},
  _decryptionSymKeyMemo: {},
  async importSymKey(encodedKeyString, operation) {
    let memo;

    // We use two separate memos for thoroughness: operation is an input to
    // key import.
    switch (operation) {
      case OPERATIONS.ENCRYPT:
        memo = this._encryptionSymKeyMemo;
        break;
      case OPERATIONS.DECRYPT:
        memo = this._decryptionSymKeyMemo;
        break;
      default:
        throw new Error("Unsupported operation in importSymKey.");
    }

    if (encodedKeyString in memo) {
      return memo[encodedKeyString];
    }

    let symmetricKeyBuffer = this.makeUint8Array(encodedKeyString, true);
    let algo = { name: CRYPT_ALGO };
    let usages = [operation === OPERATIONS.ENCRYPT ? "encrypt" : "decrypt"];
    let symKey = await crypto.subtle.importKey(
      "raw",
      symmetricKeyBuffer,
      algo,
      false,
      usages
    );
    memo[encodedKeyString] = symKey;
    return symKey;
  },

  //
  // Utility functions
  //

  /**
   * Returns an Uint8Array filled with a JS string,
   * which means we only keep utf-16 characters from 0x00 to 0xFF.
   */
  byteCompressInts(str) {
    let arrayBuffer = new Uint8Array(str.length);
    for (let i = 0; i < str.length; i++) {
      arrayBuffer[i] = str.charCodeAt(i) & 0xff;
    }
    return arrayBuffer;
  },

  expandData(data) {
    let expanded = "";
    for (let i = 0; i < data.length; i++) {
      expanded += String.fromCharCode(data[i]);
    }
    return expanded;
  },

  encodeBase64(data) {
    return btoa(this.expandData(data));
  },

  makeUint8Array(input, isEncoded) {
    if (isEncoded) {
      input = atob(input);
    }
    return this.byteCompressInts(input);
  },
};
PK
!<9S4		(modules/services-crypto/jwcrypto.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const ECDH_PARAMS = {
  name: "ECDH",
  namedCurve: "P-256",
};
const AES_PARAMS = {
  name: "AES-GCM",
  length: 256,
};
const AES_TAG_LEN = 128;
const AES_GCM_IV_SIZE = 12;
const UTF8_ENCODER = new TextEncoder();
const UTF8_DECODER = new TextDecoder();

class JWCrypto {
  /**
   * Encrypts the given data into a JWE using AES-256-GCM content encryption.
   *
   * This function implements a very small subset of the JWE encryption standard
   * from https://tools.ietf.org/html/rfc7516. The only supported content encryption
   * algorithm is enc="A256GCM" [1] and the only supported key encryption algorithm
   * is alg="ECDH-ES" [2].
   *
   * @param {Object} key Peer Public JWK.
   * @param {ArrayBuffer} data
   *
   * [1] https://tools.ietf.org/html/rfc7518#section-5.3
   * [2] https://tools.ietf.org/html/rfc7518#section-4.6
   *
   * @returns {Promise<String>}
   */
  async generateJWE(key, data) {
    // Generate an ephemeral key to use just for this encryption.
    // The public component gets embedded in the JWE header.
    const epk = await crypto.subtle.generateKey(ECDH_PARAMS, true, [
      "deriveKey",
    ]);
    const ownPublicJWK = await crypto.subtle.exportKey("jwk", epk.publicKey);
    // Remove properties added by our WebCrypto implementation but that aren't typically
    // used with JWE in the wild. This saves space in the resulting JWE, and makes it easier
    // to re-import the resulting JWK.
    delete ownPublicJWK.key_ops;
    delete ownPublicJWK.ext;
    let header = { alg: "ECDH-ES", enc: "A256GCM", epk: ownPublicJWK };
    // Import the peer's public key.
    const peerPublicKey = await crypto.subtle.importKey(
      "jwk",
      key,
      ECDH_PARAMS,
      false,
      ["deriveKey"]
    );
    if (key.hasOwnProperty("kid")) {
      header.kid = key.kid;
    }
    // Do ECDH agreement to get the content encryption key.
    const contentKey = await deriveECDHSharedAESKey(
      epk.privateKey,
      peerPublicKey,
      ["encrypt"]
    );
    // Encrypt with AES-GCM using the generated key.
    // Note that the IV is generated randomly, which *in general* is not safe to do with AES-GCM because
    // it's too short to guarantee uniqueness. But we know that the AES-GCM key itself is unique and will
    // only be used for this single encryption, making a random IV safe to use for this particular use-case.
    let iv = crypto.getRandomValues(new Uint8Array(AES_GCM_IV_SIZE));
    // Yes, additionalData is the byte representation of the base64 representation of the stringified header.
    const additionalData = UTF8_ENCODER.encode(
      ChromeUtils.base64URLEncode(UTF8_ENCODER.encode(JSON.stringify(header)), {
        pad: false,
      })
    );
    const encrypted = await crypto.subtle.encrypt(
      {
        name: "AES-GCM",
        iv,
        additionalData,
        tagLength: AES_TAG_LEN,
      },
      contentKey,
      data
    );
    // JWE needs the authentication tag as a separate string.
    const tagIdx = encrypted.byteLength - ((AES_TAG_LEN + 7) >> 3);
    let ciphertext = encrypted.slice(0, tagIdx);
    let tag = encrypted.slice(tagIdx);
    // JWE serialization in compact format.
    header = UTF8_ENCODER.encode(JSON.stringify(header));
    header = ChromeUtils.base64URLEncode(header, { pad: false });
    tag = ChromeUtils.base64URLEncode(tag, { pad: false });
    ciphertext = ChromeUtils.base64URLEncode(ciphertext, { pad: false });
    iv = ChromeUtils.base64URLEncode(iv, { pad: false });
    return `${header}..${iv}.${ciphertext}.${tag}`; // No CEK
  }

  /**
   * Decrypts the given JWE using AES-256-GCM content encryption into a byte array.
   * This function does the opposite of `JWCrypto.generateJWE`.
   * The only supported content encryption algorithm is enc="A256GCM" [1]
   * and the only supported key encryption algorithm is alg="ECDH-ES" [2].
   *
   * @param {"ECDH-ES"} algorithm
   * @param {CryptoKey} key Local private key
   *
   * [1] https://tools.ietf.org/html/rfc7518#section-5.3
   * [2] https://tools.ietf.org/html/rfc7518#section-4.6
   *
   * @returns {Promise<Uint8Array>}
   */
  async decryptJWE(jwe, key) {
    let [header, cek, iv, ciphertext, authTag] = jwe.split(".");
    const additionalData = UTF8_ENCODER.encode(header);
    header = JSON.parse(
      UTF8_DECODER.decode(
        ChromeUtils.base64URLDecode(header, { padding: "reject" })
      )
    );
    if (!!cek.length || header.enc !== "A256GCM" || header.alg !== "ECDH-ES") {
      throw new Error("Unknown algorithm.");
    }
    if ("apu" in header || "apv" in header) {
      throw new Error("apu and apv header values are not supported.");
    }
    const peerPublicKey = await crypto.subtle.importKey(
      "jwk",
      header.epk,
      ECDH_PARAMS,
      false,
      ["deriveKey"]
    );
    // Do ECDH agreement to get the content encryption key.
    const contentKey = await deriveECDHSharedAESKey(key, peerPublicKey, [
      "decrypt",
    ]);
    iv = ChromeUtils.base64URLDecode(iv, { padding: "reject" });
    ciphertext = new Uint8Array(
      ChromeUtils.base64URLDecode(ciphertext, { padding: "reject" })
    );
    authTag = new Uint8Array(
      ChromeUtils.base64URLDecode(authTag, { padding: "reject" })
    );
    const bundle = new Uint8Array([...ciphertext, ...authTag]);

    const decrypted = await crypto.subtle.decrypt(
      {
        name: "AES-GCM",
        iv,
        tagLength: AES_TAG_LEN,
        additionalData,
      },
      contentKey,
      bundle
    );
    return new Uint8Array(decrypted);
  }
}

/**
 * Do an ECDH agreement between a public and private key,
 * returning the derived encryption key as specced by
 * JWA RFC.
 * The raw ECDH secret is derived into a key using
 * Concat KDF, as defined in Section 5.8.1 of [NIST.800-56A].
 * @param {CryptoKey} privateKey
 * @param {CryptoKey} publicKey
 * @param {String[]} keyUsages See `SubtleCrypto.deriveKey` 5th paramater documentation.
 * @returns {Promise<CryptoKey>}
 */
async function deriveECDHSharedAESKey(privateKey, publicKey, keyUsages) {
  const params = { ...ECDH_PARAMS, ...{ public: publicKey } };
  const sharedKey = await crypto.subtle.deriveKey(
    params,
    privateKey,
    AES_PARAMS,
    true,
    keyUsages
  );
  // This is the NIST Concat KDF specialized to a specific set of parameters,
  // which basically turn it into a single application of SHA256.
  // The details are from the JWA RFC.
  let sharedKeyBytes = await crypto.subtle.exportKey("raw", sharedKey);
  sharedKeyBytes = new Uint8Array(sharedKeyBytes);
  const info = [
    "\x00\x00\x00\x07A256GCM", // 7-byte algorithm identifier
    "\x00\x00\x00\x00", // empty PartyUInfo
    "\x00\x00\x00\x00", // empty PartyVInfo
    "\x00\x00\x01\x00", // keylen == 256
  ].join("");
  const pkcs = `\x00\x00\x00\x01${String.fromCharCode.apply(
    null,
    sharedKeyBytes
  )}${info}`;
  const pkcsBuf = Uint8Array.from(
    Array.prototype.map.call(pkcs, c => c.charCodeAt(0))
  );
  const derivedKeyBytes = await crypto.subtle.digest(
    {
      name: "SHA-256",
    },
    pkcsBuf
  );
  return crypto.subtle.importKey(
    "raw",
    derivedKeyBytes,
    AES_PARAMS,
    false,
    keyUsages
  );
}

export const jwcrypto = new JWCrypto();
PK
!<;�[�[-modules/services-settings/Attachments.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  RemoteSettingsWorker:
    "resource://services-settings/RemoteSettingsWorker.sys.mjs",
  Utils: "resource://services-settings/Utils.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "console", () => lazy.Utils.log);

class DownloadError extends Error {
  constructor(url, resp) {
    super(`Could not download ${url}`);
    this.name = "DownloadError";
    this.resp = resp;
  }
}

class DownloadBundleError extends Error {
  constructor(url, resp) {
    super(`Could not download bundle ${url}`);
    this.name = "DownloadBundleError";
    this.resp = resp;
  }
}

class BadContentError extends Error {
  constructor(path) {
    super(`${path} content does not match server hash`);
    this.name = "BadContentError";
  }
}

class ServerInfoError extends Error {
  constructor(error) {
    super(`Server response is invalid ${error}`);
    this.name = "ServerInfoError";
    this.original = error;
  }
}

class NotFoundError extends Error {
  constructor(url, resp) {
    super(`Could not find ${url} in cache or dump`);
    this.name = "NotFoundError";
    this.resp = resp;
  }
}

// Helper for the `download` method for commonly used methods, to help with
// lazily accessing the record and attachment content.
class LazyRecordAndBuffer {
  constructor(getRecordAndLazyBuffer) {
    this.getRecordAndLazyBuffer = getRecordAndLazyBuffer;
  }

  async _ensureRecordAndLazyBuffer() {
    if (!this.recordAndLazyBufferPromise) {
      this.recordAndLazyBufferPromise = this.getRecordAndLazyBuffer();
    }
    return this.recordAndLazyBufferPromise;
  }

  /**
   * @returns {object} The attachment record, if found. null otherwise.
   **/
  async getRecord() {
    try {
      return (await this._ensureRecordAndLazyBuffer()).record;
    } catch (e) {
      return null;
    }
  }

  /**
   * @param {object} requestedRecord An attachment record
   * @returns {boolean} Whether the requested record matches this record.
   **/
  async isMatchingRequestedRecord(requestedRecord) {
    const record = await this.getRecord();
    return (
      record &&
      record.last_modified === requestedRecord.last_modified &&
      record.attachment.size === requestedRecord.attachment.size &&
      record.attachment.hash === requestedRecord.attachment.hash
    );
  }

  /**
   * Generate the return value for the "download" method.
   *
   * @throws {*} if the record or attachment content is unavailable.
   * @returns {Object} An object with two properties:
   *   buffer: ArrayBuffer with the file content.
   *   record: Record associated with the bytes.
   **/
  async getResult() {
    const { record, readBuffer } = await this._ensureRecordAndLazyBuffer();
    if (!this.bufferPromise) {
      this.bufferPromise = readBuffer();
    }
    return { record, buffer: await this.bufferPromise };
  }
}

export class Downloader {
  static get DownloadError() {
    return DownloadError;
  }
  static get DownloadBundleError() {
    return DownloadBundleError;
  }
  static get BadContentError() {
    return BadContentError;
  }
  static get ServerInfoError() {
    return ServerInfoError;
  }
  static get NotFoundError() {
    return NotFoundError;
  }

  constructor(bucketName, collectionName, ...subFolders) {
    this.folders = ["settings", bucketName, collectionName, ...subFolders];
    this.bucketName = bucketName;
    this.collectionName = collectionName;
  }

  /**
   * @returns {Object} An object with async "get", "set" and "delete" methods.
   *                   The keys are strings, the values may be any object that
   *                   can be stored in IndexedDB (including Blob).
   */
  get cacheImpl() {
    throw new Error("This Downloader does not support caching");
  }

  /**
   * Download attachment and return the result together with the record.
   * If the requested record cannot be downloaded and fallbacks are enabled, the
   * returned attachment may have a different record than the input record.
   *
   * @param {Object} record A Remote Settings entry with attachment.
   *                        If omitted, the attachmentId option must be set.
   * @param {Object} options Some download options.
   * @param {Number} [options.retries] Number of times download should be retried (default: `3`)
   * @param {Boolean} [options.checkHash] Check content integrity (default: `true`)
   * @param {string} [options.attachmentId] The attachment identifier to use for
   *                                      caching and accessing the attachment.
   *                                      (default: `record.id`)
   * @param {Boolean} [options.fallbackToCache] Return the cached attachment when the
   *                                          input record cannot be fetched.
   *                                          (default: `false`)
   * @param {Boolean} [options.fallbackToDump] Use the remote settings dump as a
   *                                         potential source of the attachment.
   *                                         (default: `false`)
   * @throws {Downloader.DownloadError} if the file could not be fetched.
   * @throws {Downloader.BadContentError} if the downloaded content integrity is not valid.
   * @throws {Downloader.ServerInfoError} if the server response is not valid.
   * @throws {NetworkError} if fetching the server infos and fetching the attachment fails.
   * @returns {Object} An object with two properties:
   *   `buffer` `ArrayBuffer`: the file content.
   *   `record` `Object`: record associated with the attachment.
   *   `_source` `String`: identifies the source of the result. Used for testing.
   */
  async download(record, options) {
    return this.#fetchAttachment(record, options);
  }

  /**
   * Downloads an attachment bundle for a given collection, if one exists. Fills in the cache
   * for all attachments provided by the bundle.
   *
   * @param {Boolean} force Set to true to force a sync even when local data exists
   * @returns {Boolean} True if all attachments were processed successfully, false if failed, null if skipped.
   */
  async cacheAll(force = false) {
    // If we're offline, don't try
    if (lazy.Utils.isOffline) {
      return null;
    }

    // Do nothing if local cache has some data and force is not true
    if (!force && (await this.cacheImpl.hasData())) {
      return null;
    }

    const url =
      (await lazy.Utils.baseAttachmentsURL()) +
      `bundles/${this.bucketName}--${this.collectionName}.zip`;
    const tmpZipFilePath = PathUtils.join(
      PathUtils.tempDir,
      `${Services.uuid.generateUUID().toString().slice(1, -1)}.zip`
    );
    let allSuccess = true;

    try {
      // 1. Download the zip archive to disk
      const resp = await lazy.Utils.fetch(url);
      if (!resp.ok) {
        throw new Downloader.DownloadBundleError(url, resp);
      }

      const downloaded = await resp.arrayBuffer();
      await IOUtils.write(tmpZipFilePath, new Uint8Array(downloaded), {
        tmpPath: `${tmpZipFilePath}.tmp`,
      });

      // 2. Read the zipped content
      const zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].createInstance(
        Ci.nsIZipReader
      );

      let tmpZipFile = await IOUtils.getFile(tmpZipFilePath);
      zipReader.open(tmpZipFile);

      for (const entryName of zipReader.findEntries("*.meta.json")) {
        try {
          // 3. Read the meta.json entry
          const recordZStream = zipReader.getInputStream(entryName);
          const recordDataLength = recordZStream.available();
          const recordStream = Cc[
            "@mozilla.org/scriptableinputstream;1"
          ].createInstance(Ci.nsIScriptableInputStream);
          recordStream.init(recordZStream);
          const recordBytes = recordStream.readBytes(recordDataLength);
          const recordBlob = new Blob([recordBytes], {
            type: "application/json",
          });
          const record = JSON.parse(await recordBlob.text());
          recordZStream.close();
          recordStream.close();

          // 4. Read the attachment entry
          const zStream = zipReader.getInputStream(record.id);
          const dataLength = zStream.available();
          const stream = Cc[
            "@mozilla.org/scriptableinputstream;1"
          ].createInstance(Ci.nsIScriptableInputStream);
          stream.init(zStream);
          const fileBytes = stream.readBytes(dataLength);
          const blob = new Blob([fileBytes]);

          // 5. Save to cache
          await this.cacheImpl.set(record.id, { record, blob });

          stream.close();
          zStream.close();
        } catch (ex) {
          lazy.console.warn(
            `${this.bucketName}/${this.collectionName}: Unable to save attachment entry for ${entryName}.`,
            ex
          );
          allSuccess = false;
        }
      }
    } catch (ex) {
      lazy.console.warn(
        `${this.bucketName}/${this.collectionName}: Unable to retrieve remote-settings attachment bundle.`,
        ex
      );
      return false;
    } finally {
      // 6. Temp file cleanup
      await IOUtils.remove(tmpZipFilePath, { ignoreAbsent: true });
    }

    return allSuccess;
  }

  /**
   * Gets an attachment from the cache or local dump, avoiding requesting it
   * from the server.
   * If the only found attachment hash does not match the requested record, the
   * returned attachment may have a different record, e.g. packaged in binary
   * resources or one that is outdated.
   *
   * @param {Object} record A Remote Settings entry with attachment.
   *                        If omitted, the attachmentId option must be set.
   * @param {Object} options Some download options.
   * @param {Number} [options.retries] Number of times download should be retried (default: `3`)
   * @param {Boolean} [options.checkHash] Check content integrity (default: `true`)
   * @param {string} [options.attachmentId] The attachment identifier to use for
   *                                      caching and accessing the attachment.
   *                                      (default: `record.id`)
   * @throws {Downloader.DownloadError} if the file could not be fetched.
   * @throws {Downloader.BadContentError} if the downloaded content integrity is not valid.
   * @throws {Downloader.ServerInfoError} if the server response is not valid.
   * @throws {NetworkError} if fetching the server infos and fetching the attachment fails.
   * @returns {Object} An object with two properties:
   *   `buffer` `ArrayBuffer`: the file content.
   *   `record` `Object`: record associated with the attachment.
   *   `_source` `String`: identifies the source of the result. Used for testing.
   */
  async get(
    record,
    options = {
      attachmentId: record?.id,
    }
  ) {
    return this.#fetchAttachment(record, {
      ...options,
      avoidDownload: true,
      fallbackToCache: true,
      fallbackToDump: true,
    });
  }

  async #fetchAttachment(record, options) {
    let {
      retries,
      checkHash,
      attachmentId = record?.id,
      fallbackToCache = false,
      fallbackToDump = false,
      avoidDownload = false,
    } = options || {};
    if (!attachmentId) {
      // Check for pre-condition. This should not happen, but it is explicitly
      // checked to avoid mixing up attachments, which could be dangerous.
      throw new Error(
        "download() was called without attachmentId or `record.id`"
      );
    }

    if (!lazy.Utils.LOAD_DUMPS) {
      if (fallbackToDump) {
        lazy.console.warn(
          "#fetchAttachment: Forcing fallbackToDump to false due to Utils.LOAD_DUMPS being false"
        );
      }
      fallbackToDump = false;
    }

    const dumpInfo = new LazyRecordAndBuffer(() =>
      this._readAttachmentDump(attachmentId)
    );
    const cacheInfo = new LazyRecordAndBuffer(() =>
      this._readAttachmentCache(attachmentId)
    );

    // Check if an attachment dump has been packaged with the client.
    // The dump is checked before the cache because dumps are expected to match
    // the requested record, at least shortly after the release of the client.
    if (fallbackToDump && record) {
      if (await dumpInfo.isMatchingRequestedRecord(record)) {
        try {
          return { ...(await dumpInfo.getResult()), _source: "dump_match" };
        } catch (e) {
          // Failed to read dump: record found but attachment file is missing.
          console.error(e);
        }
      }
    }

    // Check if the requested attachment has already been cached.
    if (record) {
      if (await cacheInfo.isMatchingRequestedRecord(record)) {
        try {
          return { ...(await cacheInfo.getResult()), _source: "cache_match" };
        } catch (e) {
          // Failed to read cache, e.g. IndexedDB unusable.
          console.error(e);
        }
      }
    }

    let errorIfAllFails;

    // There is no local version that matches the requested record.
    // Try to download the attachment specified in record.
    if (!avoidDownload && record && record.attachment) {
      try {
        const newBuffer = await this.downloadAsBytes(record, {
          retries,
          checkHash,
        });
        const blob = new Blob([newBuffer]);
        // Store in cache but don't wait for it before returning.
        this.cacheImpl
          .set(attachmentId, { record, blob })
          .catch(e => console.error(e));
        return { buffer: newBuffer, record, _source: "remote_match" };
      } catch (e) {
        // No network, corrupted content, etc.
        errorIfAllFails = e;
      }
    }

    // Unable to find an attachment that matches the record. Consider falling
    // back to local versions, even if their attachment hash do not match the
    // one from the requested record.

    // Unable to find a valid attachment, fall back to the cached attachment.
    const cacheRecord = fallbackToCache && (await cacheInfo.getRecord());
    if (cacheRecord) {
      const dumpRecord = fallbackToDump && (await dumpInfo.getRecord());
      if (dumpRecord?.last_modified >= cacheRecord.last_modified) {
        // The dump can be more recent than the cache when the client (and its
        // packaged dump) is updated.
        try {
          return { ...(await dumpInfo.getResult()), _source: "dump_fallback" };
        } catch (e) {
          // Failed to read dump: record found but attachment file is missing.
          console.error(e);
        }
      }

      try {
        return { ...(await cacheInfo.getResult()), _source: "cache_fallback" };
      } catch (e) {
        // Failed to read from cache, e.g. IndexedDB unusable.
        console.error(e);
      }
    }

    // Unable to find a valid attachment, fall back to the packaged dump.
    if (fallbackToDump && (await dumpInfo.getRecord())) {
      try {
        return { ...(await dumpInfo.getResult()), _source: "dump_fallback" };
      } catch (e) {
        errorIfAllFails = e;
      }
    }

    if (errorIfAllFails) {
      throw errorIfAllFails;
    }

    if (avoidDownload) {
      throw new Downloader.NotFoundError(attachmentId);
    }
    throw new Downloader.DownloadError(attachmentId);
  }

  /**
   * Is the record downloaded? This does not check if it was bundled.
   *
   * @param record A Remote Settings entry with attachment.
   * @returns {Promise<boolean>}
   */
  isDownloaded(record) {
    const cacheInfo = new LazyRecordAndBuffer(() =>
      this._readAttachmentCache(record.id)
    );
    return cacheInfo.isMatchingRequestedRecord(record);
  }

  /**
   * Delete the record attachment downloaded locally.
   * No-op if the attachment does not exist.
   *
   * @param record A Remote Settings entry with attachment.
   * @param {Object} options Some options.
   * @param {string} options.attachmentId The attachment identifier to use for
   *                                      accessing and deleting the attachment.
   *                                      (default: `record.id`)
   */
  async deleteDownloaded(record, options) {
    let { attachmentId = record?.id } = options || {};
    if (!attachmentId) {
      // Check for pre-condition. This should not happen, but it is explicitly
      // checked to avoid mixing up attachments, which could be dangerous.
      throw new Error(
        "deleteDownloaded() was called without attachmentId or `record.id`"
      );
    }
    return this.cacheImpl.delete(attachmentId);
  }

  /**
   * Clear the cache from obsolete downloaded attachments.
   *
   * @param {Array<String>} excludeIds List of attachments IDs to exclude from pruning.
   */
  async prune(excludeIds) {
    return this.cacheImpl.prune(excludeIds);
  }

  /**
   * @deprecated See https://bugzilla.mozilla.org/show_bug.cgi?id=1634127
   *
   * Download the record attachment into the local profile directory
   * and return a file:// URL that points to the local path.
   *
   * No-op if the file was already downloaded and not corrupted.
   *
   * @param {Object} record A Remote Settings entry with attachment.
   * @param {Object} options Some download options.
   * @param {Number} options.retries Number of times download should be retried (default: `3`)
   * @throws {Downloader.DownloadError} if the file could not be fetched.
   * @throws {Downloader.BadContentError} if the downloaded file integrity is not valid.
   * @throws {Downloader.ServerInfoError} if the server response is not valid.
   * @throws {NetworkError} if fetching the attachment fails.
   * @returns {String} the absolute file path to the downloaded attachment.
   */
  async downloadToDisk(record, options = {}) {
    const { retries = 3 } = options;
    const {
      attachment: { filename, size, hash },
    } = record;
    const localFilePath = PathUtils.join(
      PathUtils.localProfileDir,
      ...this.folders,
      filename
    );
    const localFileUrl = PathUtils.toFileURI(localFilePath);

    await this._makeDirs();

    let retried = 0;
    while (true) {
      if (
        await lazy.RemoteSettingsWorker.checkFileHash(localFileUrl, size, hash)
      ) {
        return localFileUrl;
      }
      // File does not exist or is corrupted.
      if (retried > retries) {
        throw new Downloader.BadContentError(localFilePath);
      }
      try {
        // Download and write on disk.
        const buffer = await this.downloadAsBytes(record, {
          checkHash: false, // Hash will be checked on file.
          retries: 0, // Already in a retry loop.
        });
        await IOUtils.write(localFilePath, new Uint8Array(buffer), {
          tmpPath: `${localFilePath}.tmp`,
        });
      } catch (e) {
        if (retried >= retries) {
          throw e;
        }
      }
      retried++;
    }
  }

  /**
   * Download the record attachment and return its content as bytes.
   *
   * @param {Object} record A Remote Settings entry with attachment.
   * @param {Object} options Some download options.
   * @param {Number} options.retries Number of times download should be retried (default: `3`)
   * @param {Boolean} options.checkHash Check content integrity (default: `true`)
   * @throws {Downloader.DownloadError} if the file could not be fetched.
   * @throws {Downloader.BadContentError} if the downloaded content integrity is not valid.
   * @returns {ArrayBuffer} the file content.
   */
  async downloadAsBytes(record, options = {}) {
    const {
      attachment: { location, hash, size },
    } = record;

    let baseURL;
    try {
      baseURL = await lazy.Utils.baseAttachmentsURL();
    } catch (error) {
      throw new Downloader.ServerInfoError(error);
    }

    const remoteFileUrl = baseURL + location;

    const { retries = 3, checkHash = true } = options;
    let retried = 0;
    while (true) {
      try {
        const buffer = await this._fetchAttachment(remoteFileUrl);
        if (!checkHash) {
          return buffer;
        }
        if (
          await lazy.RemoteSettingsWorker.checkContentHash(buffer, size, hash)
        ) {
          return buffer;
        }
        // Content is corrupted.
        throw new Downloader.BadContentError(location);
      } catch (e) {
        if (retried >= retries) {
          throw e;
        }
      }
      retried++;
    }
  }

  /**
   * @deprecated See https://bugzilla.mozilla.org/show_bug.cgi?id=1634127
   *
   * Delete the record attachment downloaded locally.
   * This is the counterpart of `downloadToDisk()`.
   * Use `deleteDownloaded()` if `download()` was used to retrieve
   * the attachment.
   *
   * No-op if the related file does not exist.
   *
   * @param record A Remote Settings entry with attachment.
   */
  async deleteFromDisk(record) {
    const {
      attachment: { filename },
    } = record;
    const path = PathUtils.join(
      PathUtils.localProfileDir,
      ...this.folders,
      filename
    );
    await IOUtils.remove(path);
    await this._rmDirs();
  }

  async _fetchAttachment(url) {
    const headers = new Headers();
    headers.set("Accept-Encoding", "gzip");
    const resp = await lazy.Utils.fetch(url, { headers });
    if (!resp.ok) {
      throw new Downloader.DownloadError(url, resp);
    }
    return resp.arrayBuffer();
  }

  async _readAttachmentCache(attachmentId) {
    const cached = await this.cacheImpl.get(attachmentId);
    if (!cached) {
      throw new Downloader.DownloadError(attachmentId);
    }
    return {
      record: cached.record,
      async readBuffer() {
        const buffer = await cached.blob.arrayBuffer();
        const { size, hash } = cached.record.attachment;
        if (
          await lazy.RemoteSettingsWorker.checkContentHash(buffer, size, hash)
        ) {
          return buffer;
        }
        // Really unexpected, could indicate corruption in IndexedDB.
        throw new Downloader.BadContentError(attachmentId);
      },
    };
  }

  async _readAttachmentDump(attachmentId) {
    async function fetchResource(resourceUrl) {
      try {
        return await fetch(resourceUrl);
      } catch (e) {
        throw new Downloader.DownloadError(resourceUrl);
      }
    }
    const resourceUrlPrefix =
      Downloader._RESOURCE_BASE_URL + "/" + this.folders.join("/") + "/";
    const recordUrl = `${resourceUrlPrefix}${attachmentId}.meta.json`;
    const attachmentUrl = `${resourceUrlPrefix}${attachmentId}`;
    const record = await (await fetchResource(recordUrl)).json();
    return {
      record,
      async readBuffer() {
        return (await fetchResource(attachmentUrl)).arrayBuffer();
      },
    };
  }

  // Separate variable to allow tests to override this.
  static _RESOURCE_BASE_URL = "resource://app/defaults";

  async _makeDirs() {
    const dirPath = PathUtils.join(PathUtils.localProfileDir, ...this.folders);
    await IOUtils.makeDirectory(dirPath, { createAncestors: true });
  }

  async _rmDirs() {
    for (let i = this.folders.length; i > 0; i--) {
      const dirPath = PathUtils.join(
        PathUtils.localProfileDir,
        ...this.folders.slice(0, i)
      );
      try {
        await IOUtils.remove(dirPath);
      } catch (e) {
        // This could fail if there's something in
        // the folder we're not permitted to remove.
        break;
      }
    }
  }
}
PK
!<��I��L�L*modules/services-settings/Database.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  CommonUtils: "resource://services-common/utils.sys.mjs",
  IDBHelpers: "resource://services-settings/IDBHelpers.sys.mjs",
  ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
  Utils: "resource://services-settings/Utils.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "console", () => lazy.Utils.log);

/**
 * Database is a tiny wrapper with the objective
 * of providing major kinto-offline-client collection API.
 * (with the objective of getting rid of kinto-offline-client)
 */
export class Database {
  static destroy() {
    return destroyIDB();
  }

  constructor(identifier) {
    ensureShutdownBlocker();
    this.identifier = identifier;
  }

  async list(options = {}) {
    const { filters = {}, order = "" } = options;
    let results = [];
    try {
      await executeIDB(
        "records",
        (store, rejectTransaction) => {
          // Fast-path the (very common) no-filters case
          if (lazy.ObjectUtils.isEmpty(filters)) {
            const range = IDBKeyRange.only(this.identifier);
            const request = store.index("cid").getAll(range);
            request.onsuccess = e => {
              results = e.target.result;
            };
            return;
          }
          const request = store
            .index("cid")
            .openCursor(IDBKeyRange.only(this.identifier));
          const objFilters = transformSubObjectFilters(filters);
          request.onsuccess = event => {
            try {
              const cursor = event.target.result;
              if (cursor) {
                const { value } = cursor;
                if (lazy.Utils.filterObject(objFilters, value)) {
                  results.push(value);
                }
                cursor.continue();
              }
            } catch (ex) {
              rejectTransaction(ex);
            }
          };
        },
        { mode: "readonly" }
      );
    } catch (e) {
      throw new lazy.IDBHelpers.IndexedDBError(e, "list()", this.identifier);
    }
    // Remove IDB key field from results.
    for (const result of results) {
      delete result._cid;
    }
    return order ? lazy.Utils.sortObjects(order, results) : results;
  }

  async importChanges(metadata, timestamp, records = [], options = {}) {
    const { clear = false } = options;
    const _cid = this.identifier;
    try {
      await executeIDB(
        ["collections", "timestamps", "records"],
        (stores, rejectTransaction) => {
          const [storeMetadata, storeTimestamps, storeRecords] = stores;

          if (clear) {
            // Our index is over the _cid and id fields. We want to remove
            // all of the items in the collection for which the object was
            // created, ie with _cid == this.identifier.
            // We would like to just tell IndexedDB:
            // store.index(IDBKeyRange.only(this.identifier)).delete();
            // to delete all records matching the first part of the 2-part key.
            // Unfortunately such an API does not exist.
            // While we could iterate over the index with a cursor, we'd do
            // a roundtrip to PBackground for each item. Once you have 1000
            // items, the result is very slow because of all the overhead of
            // jumping between threads and serializing/deserializing.
            // So instead, we tell the store to delete everything between
            // "our" _cid identifier, and what would be the next identifier
            // (via lexicographical sorting). Unfortunately there does not
            // seem to be a way to specify bounds for all items that share
            // the same first part of the key using just that first part, hence
            // the use of the hypothetical [] for the second part of the end of
            // the bounds.
            storeRecords.delete(
              IDBKeyRange.bound([_cid], [_cid, []], false, true)
            );
          }

          // Store or erase metadata.
          if (metadata === null) {
            storeMetadata.delete(_cid);
          } else if (metadata) {
            storeMetadata.put({ cid: _cid, metadata });
          }
          // Store or erase timestamp.
          if (timestamp === null) {
            storeTimestamps.delete(_cid);
          } else if (timestamp) {
            storeTimestamps.put({ cid: _cid, value: timestamp });
          }

          if (!records.length) {
            return;
          }

          // Separate tombstones from creations/updates.
          const toDelete = records.filter(r => r.deleted);
          const toInsert = records.filter(r => !r.deleted);
          lazy.console.debug(
            `${_cid} ${toDelete.length} to delete, ${toInsert.length} to insert`
          );
          // Delete local records for each tombstone.
          lazy.IDBHelpers.bulkOperationHelper(
            storeRecords,
            {
              reject: rejectTransaction,
              completion() {
                // Overwrite all other data.
                lazy.IDBHelpers.bulkOperationHelper(
                  storeRecords,
                  {
                    reject: rejectTransaction,
                  },
                  "put",
                  toInsert.map(item => ({ ...item, _cid }))
                );
              },
            },
            "delete",
            toDelete.map(item => [_cid, item.id])
          );
        },
        { desc: "importChanges() in " + _cid }
      );
    } catch (e) {
      throw new lazy.IDBHelpers.IndexedDBError(e, "importChanges()", _cid);
    }
  }

  async getLastModified() {
    let entry = null;
    try {
      await executeIDB(
        "timestamps",
        store => {
          store.get(this.identifier).onsuccess = e => (entry = e.target.result);
        },
        { mode: "readonly" }
      );
    } catch (e) {
      throw new lazy.IDBHelpers.IndexedDBError(
        e,
        "getLastModified()",
        this.identifier
      );
    }
    if (!entry) {
      return null;
    }
    // Some distributions where released with a modified dump that did not
    // contain timestamps for last_modified. Work around this here, and return
    // the timestamp as zero, so that the entries should get updated.
    if (isNaN(entry.value)) {
      lazy.console.warn(`Local timestamp is NaN for ${this.identifier}`);
      return 0;
    }
    return entry.value;
  }

  async getMetadata() {
    let entry = null;
    try {
      await executeIDB(
        "collections",
        store => {
          store.get(this.identifier).onsuccess = e => (entry = e.target.result);
        },
        { mode: "readonly" }
      );
    } catch (e) {
      throw new lazy.IDBHelpers.IndexedDBError(
        e,
        "getMetadata()",
        this.identifier
      );
    }
    return entry ? entry.metadata : null;
  }

  async getAttachment(attachmentId) {
    let entry = null;
    try {
      await executeIDB(
        "attachments",
        store => {
          store.get([this.identifier, attachmentId]).onsuccess = e => {
            entry = e.target.result;
          };
        },
        { mode: "readonly" }
      );
    } catch (e) {
      throw new lazy.IDBHelpers.IndexedDBError(
        e,
        "getAttachment()",
        this.identifier
      );
    }
    return entry ? entry.attachment : null;
  }

  async saveAttachment(attachmentId, attachment) {
    try {
      await executeIDB(
        "attachments",
        store => {
          if (attachment) {
            store.put({ cid: this.identifier, attachmentId, attachment });
          } else {
            store.delete([this.identifier, attachmentId]);
          }
        },
        { desc: "saveAttachment(" + attachmentId + ") in " + this.identifier }
      );
    } catch (e) {
      throw new lazy.IDBHelpers.IndexedDBError(
        e,
        "saveAttachment()",
        this.identifier
      );
    }
  }

  async hasAttachments() {
    let count = 0;
    try {
      const range = IDBKeyRange.bound(
        [this.identifier],
        [this.identifier, []],
        false,
        true
      );
      await executeIDB(
        "attachments",
        store => {
          store.count(range).onsuccess = e => {
            count = e.target.result;
          };
        },
        { mode: "readonly" }
      );
    } catch (e) {
      throw new lazy.IDBHelpers.IndexedDBError(
        e,
        "hasAttachments()",
        this.identifier
      );
    }
    return count > 0;
  }

  /**
   * Delete all attachments which don't match any record.
   *
   * Attachments are linked to records, except when a fixed `attachmentId` is used.
   * A record can be updated or deleted, potentially by deleting a record and restoring an updated version
   * of the record with the same ID. Potentially leaving orphaned attachments in the database.
   * Since we run the pruning logic after syncing, any attachment without a
   * matching record can be discarded as they will be unreachable forever.
   *
   * @param {Array<String>} excludeIds List of attachments IDs to exclude from pruning.
   */
  async pruneAttachments(excludeIds) {
    const _cid = this.identifier;
    let deletedCount = 0;
    try {
      await executeIDB(
        ["attachments", "records"],
        async (stores, rejectTransaction) => {
          const [attachmentsStore, recordsStore] = stores;

          // List all stored attachments.
          // All keys ≥ [_cid, ..] && < [_cid, []]. See comment in `importChanges()`
          const rangeAllKeys = IDBKeyRange.bound(
            [_cid],
            [_cid, []],
            false,
            true
          );
          const allAttachments = await new Promise((resolve, reject) => {
            const request = attachmentsStore.getAll(rangeAllKeys);
            request.onsuccess = e => resolve(e.target.result);
            request.onerror = e => reject(e);
          });
          if (!allAttachments.length) {
            lazy.console.debug(
              `${this.identifier} No attachments in IDB cache. Nothing to do.`
            );
            return;
          }

          // List all stored records.
          const allRecords = await new Promise((resolve, reject) => {
            const rangeAllIndexed = IDBKeyRange.only(_cid);
            const request = recordsStore.index("cid").getAll(rangeAllIndexed);
            request.onsuccess = e => resolve(e.target.result);
            request.onerror = e => reject(e);
          });

          // Compare known records IDs to those stored along the attachments.
          const currentRecordsIDs = new Set(allRecords.map(r => r.id));
          const attachmentsToDelete = allAttachments.reduce((acc, entry) => {
            // Skip excluded attachments.
            if (excludeIds.includes(entry.attachmentId)) {
              return acc;
            }
            // Delete attachment if associated record does not exist.
            if (!currentRecordsIDs.has(entry.attachment.record.id)) {
              acc.push([_cid, entry.attachmentId]);
            }
            return acc;
          }, []);

          // Perform a bulk delete of all obsolete attachments.
          lazy.console.debug(
            `${this.identifier} Bulk delete ${attachmentsToDelete.length} obsolete attachments`
          );
          lazy.IDBHelpers.bulkOperationHelper(
            attachmentsStore,
            {
              reject: rejectTransaction,
            },
            "delete",
            attachmentsToDelete
          );
          deletedCount = attachmentsToDelete.length;
        },
        { desc: "pruneAttachments() in " + this.identifier }
      );
    } catch (e) {
      throw new lazy.IDBHelpers.IndexedDBError(
        e,
        "pruneAttachments()",
        this.identifier
      );
    }
    return deletedCount;
  }

  async clear() {
    try {
      await this.importChanges(null, null, [], { clear: true });
    } catch (e) {
      throw new lazy.IDBHelpers.IndexedDBError(e, "clear()", this.identifier);
    }
  }

  /*
   * Methods used by unit tests.
   */

  async create(record) {
    if (!("id" in record)) {
      record = { ...record, id: lazy.CommonUtils.generateUUID() };
    }
    try {
      await executeIDB(
        "records",
        store => {
          store.add({ ...record, _cid: this.identifier });
        },
        { desc: "create() in " + this.identifier }
      );
    } catch (e) {
      throw new lazy.IDBHelpers.IndexedDBError(e, "create()", this.identifier);
    }
    return record;
  }

  async update(record) {
    try {
      await executeIDB(
        "records",
        store => {
          store.put({ ...record, _cid: this.identifier });
        },
        { desc: "update() in " + this.identifier }
      );
    } catch (e) {
      throw new lazy.IDBHelpers.IndexedDBError(e, "update()", this.identifier);
    }
  }

  async delete(recordId) {
    try {
      await executeIDB(
        "records",
        store => {
          store.delete([this.identifier, recordId]); // [_cid, id]
        },
        { desc: "delete() in " + this.identifier }
      );
    } catch (e) {
      throw new lazy.IDBHelpers.IndexedDBError(e, "delete()", this.identifier);
    }
  }
}

let gDB = null;
let gDBPromise = null;

/**
 * This function attempts to ensure `gDB` points to a valid database value.
 * If gDB is already a database, it will do no-op (but this may take a
 * microtask or two).
 * If opening the database fails, it will throw an IndexedDBError.
 */
async function openIDB() {
  // We can be called multiple times in a race; always ensure that when
  // we complete, `gDB` is no longer null, but avoid doing the actual
  // IndexedDB work more than once.
  if (!gDBPromise) {
    // Open and initialize/upgrade if needed.
    gDBPromise = lazy.IDBHelpers.openIDB();
  }
  let db = await gDBPromise;
  if (!gDB) {
    gDB = db;
  }
}

const gPendingReadOnlyTransactions = new Set();
const gPendingWriteOperations = new Set();
/**
 * Helper to wrap some IDBObjectStore operations into a promise.
 *
 * @param {IDBDatabase} db
 * @param {String|String[]} storeNames - either a string or an array of strings.
 * @param {function} callback
 * @param {Object} options
 * @param {String} options.mode
 * @param {String} options.desc   for shutdown tracking.
 */
async function executeIDB(storeNames, callback, options = {}) {
  if (!gDB) {
    // Check if we're shutting down. Services.startup.shuttingDown will
    // be true sooner, but is never true in xpcshell tests, so we check
    // both that and a bool we set ourselves when `profile-before-change`
    // starts.
    if (gShutdownStarted || Services.startup.shuttingDown) {
      throw new lazy.IDBHelpers.ShutdownError(
        "The application is shutting down",
        "execute()"
      );
    }
    await openIDB();
  } else {
    // Even if we have a db, wait a tick to avoid making IndexedDB sad.
    // We should be able to remove this once bug 1626935 is fixed.
    await Promise.resolve();
  }

  // Check for shutdown again as we've await'd something...
  if (!gDB && (gShutdownStarted || Services.startup.shuttingDown)) {
    throw new lazy.IDBHelpers.ShutdownError(
      "The application is shutting down",
      "execute()"
    );
  }

  // Start the actual transaction:
  const { mode = "readwrite", desc = "" } = options;
  let { promise, transaction } = lazy.IDBHelpers.executeIDB(
    gDB,
    storeNames,
    mode,
    callback,
    desc
  );

  // We track all readonly transactions and abort them at shutdown.
  // We track all readwrite ones and await their completion at shutdown
  // (to avoid dataloss when writes fail).
  // We use a `.finally()` clause for this; it'll run the function irrespective
  // of whether the promise resolves or rejects, and the promise it returns
  // will resolve/reject with the same value.
  let finishedFn;
  if (mode == "readonly") {
    gPendingReadOnlyTransactions.add(transaction);
    finishedFn = () => gPendingReadOnlyTransactions.delete(transaction);
  } else {
    let obj = { promise, desc };
    gPendingWriteOperations.add(obj);
    finishedFn = () => gPendingWriteOperations.delete(obj);
  }
  return promise.finally(finishedFn);
}

async function destroyIDB() {
  if (gDB) {
    if (gShutdownStarted || Services.startup.shuttingDown) {
      throw new lazy.IDBHelpers.ShutdownError(
        "The application is shutting down",
        "destroyIDB()"
      );
    }

    // This will return immediately; the actual close will happen once
    // there are no more running transactions.
    gDB.close();
    const allTransactions = new Set([
      ...gPendingWriteOperations,
      ...gPendingReadOnlyTransactions,
    ]);
    for (let transaction of Array.from(allTransactions)) {
      try {
        transaction.abort();
      } catch (ex) {
        // Ignore errors to abort transactions, we'll destroy everything.
      }
    }
  }
  gDB = null;
  gDBPromise = null;
  return lazy.IDBHelpers.destroyIDB();
}

function makeNestedObjectFromArr(arr, val, nestedFiltersObj) {
  const last = arr.length - 1;
  return arr.reduce((acc, cv, i) => {
    if (i === last) {
      return (acc[cv] = val);
    } else if (Object.prototype.hasOwnProperty.call(acc, cv)) {
      return acc[cv];
    }
    return (acc[cv] = {});
  }, nestedFiltersObj);
}

function transformSubObjectFilters(filtersObj) {
  const transformedFilters = {};
  for (const [key, val] of Object.entries(filtersObj)) {
    const keysArr = key.split(".");
    makeNestedObjectFromArr(keysArr, val, transformedFilters);
  }
  return transformedFilters;
}

// We need to expose this wrapper function so we can test
// shutdown handling.
Database._executeIDB = executeIDB;

let gShutdownStarted = false;
// Test-only helper to be able to test shutdown multiple times:
Database._cancelShutdown = () => {
  gShutdownStarted = false;
};

let gShutdownBlocker = false;
Database._shutdownHandler = () => {
  gShutdownStarted = true;
  const NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR = 0x80660006;
  // Duplicate the list (to avoid it being modified) and then
  // abort all read-only transactions.
  for (let transaction of Array.from(gPendingReadOnlyTransactions)) {
    try {
      transaction.abort();
    } catch (ex) {
      // Ensure we don't throw/break, because either way we're in shutdown.

      // In particular, `transaction.abort` can throw if the transaction
      // is complete, ie if we manage to get called in between the
      // transaction completing, and our completion handler being called
      // to remove the item from the set. We don't care about that.
      if (ex.result != NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR) {
        // Report any other errors:
        console.error(ex);
      }
    }
  }
  if (gDB) {
    // This will return immediately; the actual close will happen once
    // there are no more running transactions.
    gDB.close();
    gDB = null;
  }
  gDBPromise = null;
  return Promise.allSettled(
    Array.from(gPendingWriteOperations).map(op => op.promise)
  );
};

function ensureShutdownBlocker() {
  if (gShutdownBlocker) {
    return;
  }
  gShutdownBlocker = true;
  lazy.AsyncShutdown.profileBeforeChange.addBlocker(
    "RemoteSettingsClient - finish IDB access.",
    Database._shutdownHandler,
    {
      fetchState() {
        return Array.from(gPendingWriteOperations).map(op => op.desc);
      },
    }
  );
}
PK
!<m�?��6modules/services-settings/RemoteSettingsClient.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

import { Downloader } from "resource://services-settings/Attachments.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ClientEnvironmentBase:
    "resource://gre/modules/components-utils/ClientEnvironment.sys.mjs",
  Database: "resource://services-settings/Database.sys.mjs",
  IDBHelpers: "resource://services-settings/IDBHelpers.sys.mjs",
  KintoHttpClient: "resource://services-common/kinto-http-client.sys.mjs",
  ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
  RemoteSettingsWorker:
    "resource://services-settings/RemoteSettingsWorker.sys.mjs",
  SharedUtils: "resource://services-settings/SharedUtils.sys.mjs",
  UptakeTelemetry: "resource://services-common/uptake-telemetry.sys.mjs",
  Utils: "resource://services-settings/Utils.sys.mjs",
});

const TELEMETRY_COMPONENT = "remotesettings";

ChromeUtils.defineLazyGetter(lazy, "console", () => lazy.Utils.log);

/**
 * cacheProxy returns an object Proxy that will memoize properties of the target.
 * @param {Object} target the object to wrap.
 * @returns {Proxy}
 */
function cacheProxy(target) {
  const cache = new Map();
  return new Proxy(target, {
    get(target, prop) {
      if (!cache.has(prop)) {
        cache.set(prop, target[prop]);
      }
      return cache.get(prop);
    },
  });
}

/**
 * Minimalist event emitter.
 *
 * Note: we don't use `toolkit/modules/EventEmitter` because **we want** to throw
 * an error when a listener fails to execute.
 */
class EventEmitter {
  constructor(events) {
    this._listeners = new Map();
    for (const event of events) {
      this._listeners.set(event, []);
    }
  }

  /**
   * Event emitter: will execute the registered listeners in the order and
   * sequentially.
   *
   * @param {string} event    the event name
   * @param {Object} payload  the event payload to call the listeners with
   */
  async emit(event, payload) {
    const callbacks = this._listeners.get(event);
    let lastError;
    for (const cb of callbacks) {
      try {
        await cb(payload);
      } catch (e) {
        lastError = e;
      }
    }
    if (lastError) {
      throw lastError;
    }
  }

  hasListeners(event) {
    return this._listeners.has(event) && !!this._listeners.get(event).length;
  }

  on(event, callback) {
    if (!this._listeners.has(event)) {
      throw new Error(`Unknown event type ${event}`);
    }
    this._listeners.get(event).push(callback);
  }

  off(event, callback) {
    if (!this._listeners.has(event)) {
      throw new Error(`Unknown event type ${event}`);
    }
    const callbacks = this._listeners.get(event);
    const i = callbacks.indexOf(callback);
    if (i < 0) {
      throw new Error(`Unknown callback`);
    } else {
      callbacks.splice(i, 1);
    }
  }
}

class APIError extends Error {}

class NetworkError extends APIError {
  constructor(e) {
    super(`Network error: ${e}`, { cause: e });
    this.name = "NetworkError";
  }
}

class NetworkOfflineError extends APIError {
  constructor() {
    super("Network is offline");
    this.name = "NetworkOfflineError";
  }
}

class ServerContentParseError extends APIError {
  constructor(e) {
    super(`Cannot parse server content: ${e}`, { cause: e });
    this.name = "ServerContentParseError";
  }
}

class BackendError extends APIError {
  constructor(e) {
    super(`Backend error: ${e}`, { cause: e });
    this.name = "BackendError";
  }
}

class BackoffError extends APIError {
  constructor(e) {
    super(`Server backoff: ${e}`, { cause: e });
    this.name = "BackoffError";
  }
}

class TimeoutError extends APIError {
  constructor(e) {
    super(`API timeout: ${e}`, { cause: e });
    this.name = "TimeoutError";
  }
}

class StorageError extends Error {
  constructor(e) {
    super(`Storage error: ${e}`, { cause: e });
    this.name = "StorageError";
  }
}

class InvalidSignatureError extends Error {
  constructor(cid, x5u) {
    let message = `Invalid content signature (${cid})`;
    if (x5u) {
      const chain = x5u.split("/").pop();
      message += ` using '${chain}'`;
    }
    super(message);
    this.name = "InvalidSignatureError";
  }
}

class MissingSignatureError extends InvalidSignatureError {
  constructor(cid) {
    super(cid);
    this.message = `Missing signature (${cid})`;
    this.name = "MissingSignatureError";
  }
}

class CorruptedDataError extends InvalidSignatureError {
  constructor(cid) {
    super(cid);
    this.message = `Corrupted local data (${cid})`;
    this.name = "CorruptedDataError";
  }
}

class UnknownCollectionError extends Error {
  constructor(cid) {
    super(`Unknown Collection "${cid}"`);
    this.name = "UnknownCollectionError";
  }
}

class AttachmentDownloader extends Downloader {
  constructor(client) {
    super(client.bucketName, client.collectionName);
    this._client = client;
  }

  get cacheImpl() {
    const cacheImpl = {
      get: async attachmentId => {
        return this._client.db.getAttachment(attachmentId);
      },
      set: async (attachmentId, attachment) => {
        return this._client.db.saveAttachment(attachmentId, attachment);
      },
      delete: async attachmentId => {
        return this._client.db.saveAttachment(attachmentId, null);
      },
      prune: async excludeIds => {
        return this._client.db.pruneAttachments(excludeIds);
      },
      hasData: async () => {
        return this._client.db.hasAttachments();
      },
    };
    Object.defineProperty(this, "cacheImpl", { value: cacheImpl });
    return cacheImpl;
  }

  /**
   * Download attachment and report Telemetry on failure.
   *
   * @see Downloader.download
   */
  async download(record, options) {
    try {
      // Explicitly await here to ensure we catch a network error.
      return await super.download(record, options);
    } catch (err) {
      // Report download error.
      let status = lazy.UptakeTelemetry.STATUS.DOWNLOAD_ERROR;
      if (lazy.Utils.isOffline) {
        status = lazy.UptakeTelemetry.STATUS.NETWORK_OFFLINE_ERROR;
      } else if (/NetworkError/.test(err.message)) {
        status = lazy.UptakeTelemetry.STATUS.NETWORK_ERROR;
      }
      // If the file failed to be downloaded, report it as such in Telemetry.
      await lazy.UptakeTelemetry.report(TELEMETRY_COMPONENT, status, {
        source: this._client.identifier,
        errorName: err.name,
      });
      throw err;
    }
  }

  /**
   * Delete all downloaded records attachments.
   *
   * Note: the list of attachments to be deleted is based on the
   * current list of records.
   */
  async deleteAll() {
    let allRecords = await this._client.db.list();
    return Promise.all(
      allRecords
        .filter(r => !!r.attachment)
        .map(r =>
          Promise.all([this.deleteDownloaded(r), this.deleteFromDisk(r)])
        )
    );
  }
}

export class RemoteSettingsClient extends EventEmitter {
  static get APIError() {
    return APIError;
  }
  static get NetworkError() {
    return NetworkError;
  }
  static get NetworkOfflineError() {
    return NetworkOfflineError;
  }
  static get ServerContentParseError() {
    return ServerContentParseError;
  }
  static get BackendError() {
    return BackendError;
  }
  static get BackoffError() {
    return BackoffError;
  }
  static get TimeoutError() {
    return TimeoutError;
  }
  static get StorageError() {
    return StorageError;
  }
  static get InvalidSignatureError() {
    return InvalidSignatureError;
  }
  static get MissingSignatureError() {
    return MissingSignatureError;
  }
  static get CorruptedDataError() {
    return CorruptedDataError;
  }
  static get UnknownCollectionError() {
    return UnknownCollectionError;
  }

  constructor(
    collectionName,
    {
      bucketName = AppConstants.REMOTE_SETTINGS_DEFAULT_BUCKET,
      signerName,
      filterFunc,
      localFields = [],
      keepAttachmentsIds = [],
      lastCheckTimePref,
    } = {}
  ) {
    // Remote Settings cannot be used in child processes (no access to disk,
    // easily killed, isolated observer notifications etc.).
    // Since our goal here is to prevent consumers to instantiate while developing their
    // feature, throwing in Nightly only is enough, and prevents unexpected crashes
    // in release or beta.
    if (
      !AppConstants.RELEASE_OR_BETA &&
      Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT
    ) {
      throw new Error(
        "Cannot instantiate Remote Settings client in child processes."
      );
    }

    super(["sync"]); // emitted events

    this.collectionName = collectionName;
    // Client is constructed with the raw bucket name (eg. "main", "security-state", "blocklist")
    // The `bucketName` will contain the `-preview` suffix if the preview mode is enabled.
    this.bucketName = lazy.Utils.actualBucketName(bucketName);
    this.signerName = signerName;
    this.filterFunc = filterFunc;
    this.localFields = localFields;
    this.keepAttachmentsIds = keepAttachmentsIds;
    this._lastCheckTimePref = lastCheckTimePref;
    this._verifier = null;
    this._syncRunning = false;

    // This attribute allows signature verification to be disabled, when running tests
    // or when pulling data from a dev server.
    this.verifySignature = AppConstants.REMOTE_SETTINGS_VERIFY_SIGNATURE;

    ChromeUtils.defineLazyGetter(
      this,
      "db",
      () => new lazy.Database(this.identifier)
    );

    ChromeUtils.defineLazyGetter(
      this,
      "attachments",
      () => new AttachmentDownloader(this)
    );
  }

  /**
   * Internal method to refresh the client bucket name after the preview mode
   * was toggled.
   *
   * See `RemoteSettings.enabledPreviewMode()`.
   */
  refreshBucketName() {
    this.bucketName = lazy.Utils.actualBucketName(this.bucketName);
    this.db.identifier = this.identifier;
  }

  get identifier() {
    return `${this.bucketName}/${this.collectionName}`;
  }

  get lastCheckTimePref() {
    return (
      this._lastCheckTimePref ||
      `services.settings.${this.bucketName}.${this.collectionName}.last_check`
    );
  }

  httpClient() {
    const api = new lazy.KintoHttpClient(lazy.Utils.SERVER_URL, {
      fetchFunc: lazy.Utils.fetch, // Use fetch() wrapper.
    });
    return api.bucket(this.bucketName).collection(this.collectionName);
  }

  /**
   * Retrieve the collection timestamp for the last synchronization.
   * This is an opaque and comparable value assigned automatically by
   * the server.
   *
   * @returns {number}
   *          The timestamp in milliseconds, returns -1 if retrieving
   *          the timestamp from the kinto collection fails.
   */
  async getLastModified() {
    let timestamp = -1;
    try {
      timestamp = await this.db.getLastModified();
    } catch (err) {
      lazy.console.warn(
        `Error retrieving the getLastModified timestamp from ${this.identifier} RemoteSettingsClient`,
        err
      );
    }

    return timestamp;
  }

  /**
   * Lists settings.
   *
   * @param  {Object} options                    The options object.
   * @param  {Object} options.filters            Filter the results (default: `{}`).
   * @param  {String} options.order              The order to apply (eg. `"-last_modified"`).
   * @param  {boolean} options.dumpFallback      Fallback to dump data if read of local DB fails (default: `true`).
   * @param  {boolean} options.emptyListFallback Fallback to empty list if no dump data and read of local DB fails (default: `true`).
   * @param  {boolean} options.loadDumpIfNewer   Use dump data if it is newer than local data (default: `true`).
   * @param  {boolean} options.forceSync         Always synchronize from server before returning results (default: `false`).
   * @param  {boolean} options.syncIfEmpty       Synchronize from server if local data is empty (default: `true`).
   * @param  {boolean} options.verifySignature   Verify the signature of the local data (default: `false`).
   * @return {Promise}
   */
  async get(options = {}) {
    const {
      filters = {},
      order = "", // not sorted by default.
      dumpFallback = true,
      emptyListFallback = true,
      forceSync = false,
      loadDumpIfNewer = true,
      syncIfEmpty = true,
    } = options;
    let { verifySignature = false } = options;

    const hasParallelCall = !!this._importingPromise;
    let data;
    try {
      let lastModified = forceSync ? null : await this.db.getLastModified();
      let hasLocalData = lastModified !== null;

      if (forceSync) {
        if (!this._importingPromise) {
          this._importingPromise = (async () => {
            await this.sync({ sendEvents: false, trigger: "forced" });
            return true; // No need to re-verify signature after sync.
          })();
        }
      } else if (syncIfEmpty && !hasLocalData) {
        // .get() was called before we had the chance to synchronize the local database.
        // We'll try to avoid returning an empty list.
        if (!this._importingPromise) {
          // Prevent parallel loading when .get() is called multiple times.
          this._importingPromise = (async () => {
            const importedFromDump = lazy.Utils.LOAD_DUMPS
              ? await this._importJSONDump()
              : -1;
            if (importedFromDump < 0) {
              // There is no JSON dump to load, force a synchronization from the server.
              // We don't want the "sync" event to be sent, since some consumers use `.get()`
              // in "sync" callbacks. See Bug 1761953
              lazy.console.debug(
                `${this.identifier} Local DB is empty, pull data from server`
              );
              await this.sync({ loadDump: false, sendEvents: false });
            }
            // Return `true` to indicate we don't need to `verifySignature`,
            // since a trusted dump was loaded or a signature verification
            // happened during synchronization.
            return true;
          })();
        } else {
          lazy.console.debug(`${this.identifier} Awaiting existing import.`);
        }
      } else if (hasLocalData && loadDumpIfNewer && lazy.Utils.LOAD_DUMPS) {
        // Check whether the local data is older than the packaged dump.
        // If it is and we are on production, load the packaged dump (which
        // overwrites the local data).
        let lastModifiedDump = await lazy.Utils.getLocalDumpLastModified(
          this.bucketName,
          this.collectionName
        );
        if (lastModified < lastModifiedDump) {
          lazy.console.debug(
            `${this.identifier} Local DB is stale (${lastModified}), using dump instead (${lastModifiedDump})`
          );
          if (!this._importingPromise) {
            // As part of importing, any existing data is wiped.
            this._importingPromise = (async () => {
              const importedFromDump = await this._importJSONDump();
              // Return `true` to skip signature verification if a dump was found.
              // The dump can be missing if a collection is listed in the timestamps summary file,
              // because its dump is present in the source tree, but the dump was not
              // included in the `package-manifest.in` file. (eg. android, thunderbird)
              return importedFromDump >= 0;
            })();
          } else {
            lazy.console.debug(`${this.identifier} Awaiting existing import.`);
          }
        }
      }

      if (this._importingPromise) {
        try {
          if (await this._importingPromise) {
            // No need to verify signature, because either we've just loaded a trusted
            // dump (here or in a parallel call), or it was verified during sync.
            verifySignature = false;
          }
        } catch (e) {
          if (!hasParallelCall) {
            // Sync or load dump failed. Throw.
            throw e;
          }
          // Report error, but continue because there could have been data
          // loaded from a parallel call.
          console.error(e);
        } finally {
          // then delete this promise again, as now we should have local data:
          delete this._importingPromise;
        }
      }

      // Read from the local DB.
      data = await this.db.list({ filters, order });
    } catch (e) {
      // If the local DB cannot be read (for unknown reasons, Bug 1649393)
      // or sync failed, we fallback to the packaged data, and filter/sort in memory.
      if (!dumpFallback) {
        throw e;
      }
      console.error(e);
      let { data } = await lazy.SharedUtils.loadJSONDump(
        this.bucketName,
        this.collectionName
      );
      if (data !== null) {
        lazy.console.info(`${this.identifier} falling back to JSON dump`);
      } else if (emptyListFallback) {
        lazy.console.info(
          `${this.identifier} no dump fallback, return empty list`
        );
        data = [];
      } else {
        // Obtaining the records failed, there is no dump, and we don't fallback
        // to an empty list. Throw the original error.
        throw e;
      }
      if (!lazy.ObjectUtils.isEmpty(filters)) {
        data = data.filter(r => lazy.Utils.filterObject(filters, r));
      }
      if (order) {
        data = lazy.Utils.sortObjects(order, data);
      }
      // No need to verify signature on JSON dumps.
      // If local DB cannot be read, then we don't even try to do anything,
      // we return results early.
      return this._filterEntries(data);
    }

    lazy.console.debug(
      `${this.identifier} ${data.length} records before filtering.`
    );

    if (verifySignature) {
      lazy.console.debug(
        `${this.identifier} verify signature of local data on read`
      );
      const allData = lazy.ObjectUtils.isEmpty(filters)
        ? data
        : await this.db.list();
      const localRecords = allData.map(r => this._cleanLocalFields(r));
      const timestamp = await this.db.getLastModified();
      let metadata = await this.db.getMetadata();
      if (syncIfEmpty && lazy.ObjectUtils.isEmpty(metadata)) {
        // No sync occured yet, may have records from dump but no metadata.
        // We don't want the "sync" event to be sent, since some consumers use `.get()`
        // in "sync" callbacks. See Bug 1761953
        await this.sync({ loadDump: false, sendEvents: false });
        metadata = await this.db.getMetadata();
      }
      // Will throw MissingSignatureError if no metadata and `syncIfEmpty` is false.
      await this._validateCollectionSignature(
        localRecords,
        timestamp,
        metadata
      );
    }

    // Filter the records based on `this.filterFunc` results.
    const final = await this._filterEntries(data);
    lazy.console.debug(
      `${this.identifier} ${final.length} records after filtering.`
    );
    return final;
  }

  /**
   * Synchronize the local database with the remote server.
   *
   * @param {Object} options See #maybeSync() options.
   */
  async sync(options) {
    if (lazy.Utils.shouldSkipRemoteActivityDueToTests) {
      return;
    }

    // We want to know which timestamp we are expected to obtain in order to leverage
    // cache busting. We don't provide ETag because we don't want a 304.
    const { changes } = await lazy.Utils.fetchLatestChanges(
      lazy.Utils.SERVER_URL,
      {
        filters: {
          collection: this.collectionName,
          bucket: this.bucketName,
        },
      }
    );
    if (changes.length === 0) {
      throw new RemoteSettingsClient.UnknownCollectionError(this.identifier);
    }
    // According to API, there will be one only (fail if not).
    const [{ last_modified: expectedTimestamp }] = changes;

    await this.maybeSync(expectedTimestamp, { ...options, trigger: "forced" });
  }

  /**
   * Synchronize the local database with the remote server, **only if necessary**.
   *
   * @param {int}    expectedTimestamp  the lastModified date (on the server) for the remote collection.
   *                                    This will be compared to the local timestamp, and will be used for
   *                                    cache busting if local data is out of date.
   * @param {Object} options            additional advanced options.
   * @param {bool}   options.loadDump   load initial dump from disk on first sync (default: true if server is prod)
   * @param {bool}   options.sendEvents send `"sync"` events (default: `true`)
   * @param {string} options.trigger    label to identify what triggered this sync (eg. ``"timer"``, default: `"manual"`)
   * @return {Promise}                  which rejects on sync or process failure.
   */
  async maybeSync(expectedTimestamp, options = {}) {
    // Should the clients try to load JSON dump? (mainly disabled in tests)
    const {
      loadDump = lazy.Utils.LOAD_DUMPS,
      trigger = "manual",
      sendEvents = true,
    } = options;

    // Make sure we don't run several synchronizations in parallel, mainly
    // in order to avoid race conditions in "sync" events listeners.
    if (this._syncRunning) {
      lazy.console.warn(`${this.identifier} sync already running`);
      return;
    }

    // Prevent network requests and IndexedDB calls to be initiated
    // during shutdown.
    if (Services.startup.shuttingDown) {
      lazy.console.warn(`${this.identifier} sync interrupted by shutdown`);
      return;
    }

    this._syncRunning = true;

    let importedFromDump = [];
    const startedAt = new Date();
    let reportStatus = null;
    let thrownError = null;
    try {
      // If network is offline, we can't synchronize.
      if (lazy.Utils.isOffline) {
        throw new RemoteSettingsClient.NetworkOfflineError();
      }

      // Read last timestamp and local data before sync.
      let collectionLastModified = await this.db.getLastModified();
      const allData = await this.db.list();
      // Local data can contain local fields, strip them.
      let localRecords = allData.map(r => this._cleanLocalFields(r));
      const localMetadata = await this.db.getMetadata();

      // If there is no data currently in the collection, attempt to import
      // initial data from the application defaults.
      // This allows to avoid synchronizing the whole collection content on
      // cold start.
      if (!collectionLastModified && loadDump) {
        try {
          const imported = await this._importJSONDump();
          // The worker only returns an integer. List the imported records to build the sync event.
          if (imported > 0) {
            lazy.console.debug(
              `${this.identifier} ${imported} records loaded from JSON dump`
            );
            importedFromDump = await this.db.list();
            // Local data is the data loaded from dump. We will need this later
            // to compute the sync result.
            localRecords = importedFromDump;
          }
          collectionLastModified = await this.db.getLastModified();
        } catch (e) {
          // Report but go-on.
          console.error(e);
        }
      }
      let syncResult;
      try {
        // Is local timestamp up to date with the server?
        if (expectedTimestamp == collectionLastModified) {
          lazy.console.debug(`${this.identifier} local data is up-to-date`);
          reportStatus = lazy.UptakeTelemetry.STATUS.UP_TO_DATE;

          // If the data is up-to-date but don't have metadata (records loaded from dump),
          // we fetch them and validate the signature immediately.
          if (this.verifySignature && lazy.ObjectUtils.isEmpty(localMetadata)) {
            lazy.console.debug(`${this.identifier} pull collection metadata`);
            const metadata = await this.httpClient().getData({
              query: { _expected: expectedTimestamp },
            });
            await this.db.importChanges(metadata);
            // We don't bother validating the signature if the dump was just loaded. We do
            // if the dump was loaded at some other point (eg. from .get()).
            if (this.verifySignature && !importedFromDump.length) {
              lazy.console.debug(
                `${this.identifier} verify signature of local data`
              );
              await this._validateCollectionSignature(
                localRecords,
                collectionLastModified,
                metadata
              );
            }
          }

          // Since the data is up-to-date, if we didn't load any dump then we're done here.
          if (!importedFromDump.length) {
            return;
          }
          // Otherwise we want to continue with sending the sync event to notify about the created records.
          syncResult = {
            current: importedFromDump,
            created: importedFromDump,
            updated: [],
            deleted: [],
          };
        } else {
          // Local data is either outdated or tampered.
          // In both cases we will fetch changes from server,
          // and make sure we overwrite local data.
          syncResult = await this._importChanges(
            localRecords,
            collectionLastModified,
            localMetadata,
            expectedTimestamp
          );
          if (sendEvents && this.hasListeners("sync")) {
            // If we have listeners for the "sync" event, then compute the lists of changes.
            // The records imported from the dump should be considered as "created" for the
            // listeners.
            const importedById = importedFromDump.reduce((acc, r) => {
              acc.set(r.id, r);
              return acc;
            }, new Map());
            // Deleted records should not appear as created.
            syncResult.deleted.forEach(r => importedById.delete(r.id));
            // Records from dump that were updated should appear in their newest form.
            syncResult.updated.forEach(u => {
              if (importedById.has(u.old.id)) {
                importedById.set(u.old.id, u.new);
              }
            });
            syncResult.created = syncResult.created.concat(
              Array.from(importedById.values())
            );
          }

          // When triggered from the daily timer, and if the sync was successful, and once
          // all sync listeners have been executed successfully, we prune potential
          // obsolete attachments that may have been left in the local cache.
          if (trigger == "timer") {
            const deleted = await this.attachments.prune(
              this.keepAttachmentsIds
            );
            if (deleted > 0) {
              lazy.console.warn(
                `${this.identifier} Pruned ${deleted} obsolete attachments`
              );
            }
          }
        }
      } catch (e) {
        if (e instanceof InvalidSignatureError) {
          // Signature verification failed during synchronization.
          reportStatus =
            e instanceof CorruptedDataError
              ? lazy.UptakeTelemetry.STATUS.CORRUPTION_ERROR
              : lazy.UptakeTelemetry.STATUS.SIGNATURE_ERROR;
          // If sync fails with a signature error, it's likely that our
          // local data has been modified in some way.
          // We will attempt to fix this by retrieving the whole
          // remote collection.
          try {
            lazy.console.warn(
              `${this.identifier} Signature verified failed. Retry from scratch`
            );
            syncResult = await this._importChanges(
              localRecords,
              collectionLastModified,
              localMetadata,
              expectedTimestamp,
              { retry: true }
            );
          } catch (e) {
            // If the signature fails again, or if an error occured during wiping out the
            // local data, then we report it as a *signature retry* error.
            reportStatus = lazy.UptakeTelemetry.STATUS.SIGNATURE_RETRY_ERROR;
            throw e;
          }
        } else {
          // The sync has thrown for other reason than signature verification.
          // Obtain a more precise error than original one.
          const adjustedError = this._adjustedError(e);
          // Default status for errors at this step is SYNC_ERROR.
          reportStatus = this._telemetryFromError(adjustedError, {
            default: lazy.UptakeTelemetry.STATUS.SYNC_ERROR,
          });
          throw adjustedError;
        }
      }
      if (sendEvents) {
        // Filter the synchronization results using `filterFunc` (ie. JEXL).
        const filteredSyncResult = await this._filterSyncResult(syncResult);
        // If every changed entry is filtered, we don't even fire the event.
        if (filteredSyncResult) {
          try {
            await this.emit("sync", { data: filteredSyncResult });
          } catch (e) {
            reportStatus = lazy.UptakeTelemetry.STATUS.APPLY_ERROR;
            throw e;
          }
        } else {
          lazy.console.info(
            `All changes are filtered by JEXL expressions for ${this.identifier}`
          );
        }
      }
    } catch (e) {
      thrownError = e;
      // Obtain a more precise error than original one.
      const adjustedError = this._adjustedError(e);
      // If browser is shutting down, then we can report a specific status.
      // (eg. IndexedDB will abort transactions)
      if (Services.startup.shuttingDown) {
        reportStatus = lazy.UptakeTelemetry.STATUS.SHUTDOWN_ERROR;
      }
      // If no Telemetry status was determined yet (ie. outside sync step),
      // then introspect error, default status at this step is UNKNOWN.
      else if (reportStatus == null) {
        reportStatus = this._telemetryFromError(adjustedError, {
          default: lazy.UptakeTelemetry.STATUS.UNKNOWN_ERROR,
        });
      }
      throw e;
    } finally {
      const durationMilliseconds = new Date() - startedAt;
      // No error was reported, this is a success!
      if (reportStatus === null) {
        reportStatus = lazy.UptakeTelemetry.STATUS.SUCCESS;
      }
      // Report success/error status to Telemetry.
      let reportArgs = {
        source: this.identifier,
        trigger,
        duration: durationMilliseconds,
      };
      // In Bug 1617133, we will try to break down specific errors into
      // more precise statuses by reporting the JavaScript error name
      // ("TypeError", etc.) to Telemetry on Nightly.
      const channel = lazy.UptakeTelemetry.Policy.getChannel();
      if (
        thrownError !== null &&
        channel == "nightly" &&
        [
          lazy.UptakeTelemetry.STATUS.SYNC_ERROR,
          lazy.UptakeTelemetry.STATUS.CUSTOM_1_ERROR, // IndexedDB.
          lazy.UptakeTelemetry.STATUS.UNKNOWN_ERROR,
          lazy.UptakeTelemetry.STATUS.SHUTDOWN_ERROR,
        ].includes(reportStatus)
      ) {
        // List of possible error names for IndexedDB:
        // https://searchfox.org/mozilla-central/rev/49ed791/dom/base/DOMException.cpp#28-53
        reportArgs = { ...reportArgs, errorName: thrownError.name };
      }

      await lazy.UptakeTelemetry.report(
        TELEMETRY_COMPONENT,
        reportStatus,
        reportArgs
      );

      lazy.console.debug(`${this.identifier} sync status is ${reportStatus}`);
      this._syncRunning = false;
    }
  }

  /**
   * Return a more precise error instance, based on the specified
   * error and its message.
   * @param {Error} e the original error
   * @returns {Error}
   */
  _adjustedError(e) {
    if (/unparseable/.test(e.message)) {
      return new RemoteSettingsClient.ServerContentParseError(e);
    }
    if (/NetworkError/.test(e.message)) {
      return new RemoteSettingsClient.NetworkError(e);
    }
    if (/Timeout/.test(e.message)) {
      return new RemoteSettingsClient.TimeoutError(e);
    }
    if (/HTTP 5??/.test(e.message)) {
      return new RemoteSettingsClient.BackendError(e);
    }
    if (/Backoff/.test(e.message)) {
      return new RemoteSettingsClient.BackoffError(e);
    }
    if (
      // Errors from kinto.js IDB adapter.
      e instanceof lazy.IDBHelpers.IndexedDBError ||
      // Other IndexedDB errors (eg. RemoteSettingsWorker).
      /IndexedDB/.test(e.message)
    ) {
      return new RemoteSettingsClient.StorageError(e);
    }
    return e;
  }

  /**
   * Determine the Telemetry uptake status based on the specified
   * error.
   */
  _telemetryFromError(e, options = { default: null }) {
    let reportStatus = options.default;

    if (e instanceof RemoteSettingsClient.NetworkOfflineError) {
      reportStatus = lazy.UptakeTelemetry.STATUS.NETWORK_OFFLINE_ERROR;
    } else if (e instanceof lazy.IDBHelpers.ShutdownError) {
      reportStatus = lazy.UptakeTelemetry.STATUS.SHUTDOWN_ERROR;
    } else if (e instanceof RemoteSettingsClient.ServerContentParseError) {
      reportStatus = lazy.UptakeTelemetry.STATUS.PARSE_ERROR;
    } else if (e instanceof RemoteSettingsClient.NetworkError) {
      reportStatus = lazy.UptakeTelemetry.STATUS.NETWORK_ERROR;
    } else if (e instanceof RemoteSettingsClient.TimeoutError) {
      reportStatus = lazy.UptakeTelemetry.STATUS.TIMEOUT_ERROR;
    } else if (e instanceof RemoteSettingsClient.BackendError) {
      reportStatus = lazy.UptakeTelemetry.STATUS.SERVER_ERROR;
    } else if (e instanceof RemoteSettingsClient.BackoffError) {
      reportStatus = lazy.UptakeTelemetry.STATUS.BACKOFF;
    } else if (e instanceof RemoteSettingsClient.StorageError) {
      reportStatus = lazy.UptakeTelemetry.STATUS.CUSTOM_1_ERROR;
    }

    return reportStatus;
  }

  /**
   * Import the JSON files from services/settings/dump into the local DB.
   */
  async _importJSONDump() {
    lazy.console.info(`${this.identifier} try to restore dump`);
    const result = await lazy.RemoteSettingsWorker.importJSONDump(
      this.bucketName,
      this.collectionName
    );
    if (result < 0) {
      lazy.console.debug(`${this.identifier} no dump available`);
    } else {
      lazy.console.info(
        `${this.identifier} imported ${result} records from dump`
      );
    }
    return result;
  }

  /**
   * Fetch the signature info from the collection metadata and verifies that the
   * local set of records has the same.
   *
   * @param {Array<Object>} records The list of records to validate.
   * @param {int} timestamp         The timestamp associated with the list of remote records.
   * @param {Object} metadata       The collection metadata, that contains the signature payload.
   * @returns {Promise}
   */
  async _validateCollectionSignature(records, timestamp, metadata) {
    if (!metadata?.signature) {
      throw new MissingSignatureError(this.identifier);
    }

    if (!this._verifier) {
      this._verifier = Cc[
        "@mozilla.org/security/contentsignatureverifier;1"
      ].createInstance(Ci.nsIContentSignatureVerifier);
    }

    // This is a content-signature field from an autograph response.
    const {
      signature: { x5u, signature },
    } = metadata;
    const certChain = await (await lazy.Utils.fetch(x5u)).text();
    // Merge remote records with local ones and serialize as canonical JSON.
    const serialized = await lazy.RemoteSettingsWorker.canonicalStringify(
      records,
      timestamp
    );

    lazy.console.debug(`${this.identifier} verify signature using ${x5u}`);
    if (
      !(await this._verifier.asyncVerifyContentSignature(
        serialized,
        "p384ecdsa=" + signature,
        certChain,
        this.signerName,
        lazy.Utils.CERT_CHAIN_ROOT_IDENTIFIER
      ))
    ) {
      throw new InvalidSignatureError(this.identifier, x5u);
    }
  }

  /**
   * This method is in charge of fetching data from server, applying the diff-based
   * changes to the local DB, validating the signature, and computing a synchronization
   * result with the list of creation, updates, and deletions.
   *
   * @param {Array<Object>} localRecords      Current list of records in local DB.
   * @param {int}           localTimestamp    Current timestamp in local DB.
   * @param {Object}        localMetadata     Current metadata in local DB.
   * @param {int}           expectedTimestamp Cache busting of collection metadata
   * @param {Object}        options
   * @param {bool}          options.retry     Whether this method is called in the
   *                                          retry situation.
   *
   * @returns {Promise<Object>} the computed sync result.
   */
  async _importChanges(
    localRecords,
    localTimestamp,
    localMetadata,
    expectedTimestamp,
    options = {}
  ) {
    const { retry = false } = options;
    const since = retry || !localTimestamp ? undefined : `"${localTimestamp}"`;

    // Fetch collection metadata and list of changes from server.
    lazy.console.debug(
      `${this.identifier} Fetch changes from server (expected=${expectedTimestamp}, since=${since})`
    );
    const { metadata, remoteTimestamp, remoteRecords } =
      await this._fetchChangeset(expectedTimestamp, since);

    // We build a sync result, based on remote changes.
    const syncResult = {
      current: localRecords,
      created: [],
      updated: [],
      deleted: [],
    };
    // If data wasn't changed, return empty sync result.
    // This can happen when we update the signature but not the data.
    lazy.console.debug(
      `${this.identifier} local timestamp: ${localTimestamp}, remote: ${remoteTimestamp}`
    );
    if (localTimestamp && remoteTimestamp < localTimestamp) {
      return syncResult;
    }

    await this.db.importChanges(metadata, remoteTimestamp, remoteRecords, {
      clear: retry,
    });

    // Read the new local data, after updating.
    const newLocal = await this.db.list();
    const newRecords = newLocal.map(r => this._cleanLocalFields(r));
    // And verify the signature on what is now stored.
    if (this.verifySignature) {
      try {
        await this._validateCollectionSignature(
          newRecords,
          remoteTimestamp,
          metadata
        );
      } catch (e) {
        lazy.console.error(
          `${this.identifier} Signature failed ${retry ? "again" : ""} ${e}`
        );
        if (!(e instanceof InvalidSignatureError)) {
          // If it failed for any other kind of error (eg. shutdown)
          // then give up quickly.
          throw e;
        }

        // In order to distinguish signature errors that happen
        // during sync, from hijacks of local DBs, we will verify
        // the signature on the data that we had before syncing.
        let localTrustworthy = false;
        lazy.console.debug(`${this.identifier} verify data before sync`);
        try {
          await this._validateCollectionSignature(
            localRecords,
            localTimestamp,
            localMetadata
          );
          localTrustworthy = true;
        } catch (sigerr) {
          if (!(sigerr instanceof InvalidSignatureError)) {
            // If it fails for other reason, keep original error and give up.
            throw sigerr;
          }
          lazy.console.debug(`${this.identifier} previous data was invalid`);
        }

        if (!localTrustworthy && !retry) {
          // Signature failed, clear local DB because it contains
          // bad data (local + remote changes).
          lazy.console.debug(`${this.identifier} clear local data`);
          await this.db.clear();
          // Local data was tampered, throw and it will retry from empty DB.
          lazy.console.error(`${this.identifier} local data was corrupted`);
          throw new CorruptedDataError(this.identifier);
        } else if (retry) {
          // We retried already, we will restore the previous local data
          // before throwing eventually.
          if (localTrustworthy) {
            await this.db.importChanges(
              localMetadata,
              localTimestamp,
              localRecords,
              {
                clear: true, // clear before importing.
              }
            );
          } else {
            // Restore the dump if available (no-op if no dump)
            const imported = await this._importJSONDump();
            // _importJSONDump() only clears DB if dump is available,
            // therefore do it here!
            if (imported < 0) {
              await this.db.clear();
            }
          }
        }
        throw e;
      }
    } else {
      lazy.console.warn(`${this.identifier} has signature disabled`);
    }

    if (this.hasListeners("sync")) {
      // If we have some listeners for the "sync" event,
      // Compute the changes, comparing records before and after.
      syncResult.current = newRecords;
      const oldById = new Map(localRecords.map(e => [e.id, e]));
      for (const r of newRecords) {
        const old = oldById.get(r.id);
        if (old) {
          oldById.delete(r.id);
          if (r.last_modified != old.last_modified) {
            syncResult.updated.push({ old, new: r });
          }
        } else {
          syncResult.created.push(r);
        }
      }
      syncResult.deleted = syncResult.deleted.concat(
        Array.from(oldById.values())
      );
      lazy.console.debug(
        `${this.identifier} ${syncResult.created.length} created. ${syncResult.updated.length} updated. ${syncResult.deleted.length} deleted.`
      );
    }

    return syncResult;
  }

  /**
   * Fetch information from changeset endpoint.
   *
   * @param expectedTimestamp cache busting value
   * @param since timestamp of last sync (optional)
   */
  async _fetchChangeset(expectedTimestamp, since) {
    const client = this.httpClient();
    const {
      metadata,
      timestamp: remoteTimestamp,
      changes: remoteRecords,
    } = await client.execute(
      {
        path: `/buckets/${this.bucketName}/collections/${this.collectionName}/changeset`,
      },
      {
        query: {
          _expected: expectedTimestamp,
          _since: since,
        },
      }
    );
    return {
      remoteTimestamp,
      metadata,
      remoteRecords,
    };
  }

  /**
   * Use the filter func to filter the lists of changes obtained from synchronization,
   * and return them along with the filtered list of local records.
   *
   * If the filtered lists of changes are all empty, we return null (and thus don't
   * bother listing local DB).
   *
   * @param {Object}     syncResult       Synchronization result without filtering.
   *
   * @returns {Promise<Object>} the filtered list of local records, plus the filtered
   *                            list of created, updated and deleted records.
   */
  async _filterSyncResult(syncResult) {
    // Handle the obtained records (ie. apply locally through events).
    // Build the event data list. It should be filtered (ie. by application target)
    const {
      current: allData,
      created: allCreated,
      updated: allUpdated,
      deleted: allDeleted,
    } = syncResult;
    const [created, deleted, updatedFiltered] = await Promise.all(
      [allCreated, allDeleted, allUpdated.map(e => e.new)].map(
        this._filterEntries.bind(this)
      )
    );
    // For updates, keep entries whose updated form matches the target.
    const updatedFilteredIds = new Set(updatedFiltered.map(e => e.id));
    const updated = allUpdated.filter(({ new: { id } }) =>
      updatedFilteredIds.has(id)
    );

    if (!created.length && !updated.length && !deleted.length) {
      return null;
    }
    // Read local collection of records (also filtered).
    const current = await this._filterEntries(allData);
    return { created, updated, deleted, current };
  }

  /**
   * Filter entries for which calls to `this.filterFunc` returns null.
   *
   * @param {Array<Objet>} data
   * @returns {Array<Object>}
   */
  async _filterEntries(data) {
    if (!this.filterFunc) {
      return data;
    }
    const environment = cacheProxy(lazy.ClientEnvironmentBase);
    const dataPromises = data.map(e => this.filterFunc(e, environment));
    const results = await Promise.all(dataPromises);
    return results.filter(Boolean);
  }

  /**
   * Remove the fields from the specified record
   * that are not present on server.
   *
   * @param {Object} record
   */
  _cleanLocalFields(record) {
    const keys = ["_status"].concat(this.localFields);
    const result = { ...record };
    for (const key of keys) {
      delete result[key];
    }
    return result;
  }
}
PK
!<�bJ��6modules/services-settings/RemoteSettingsWorker.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Interface to a dedicated thread handling for Remote Settings heavy operations.
 */
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gMaxIdleMilliseconds",
  "services.settings.worker_idle_max_milliseconds",
  30 * 1000 // Default of 30 seconds.
);

ChromeUtils.defineESModuleGetters(lazy, {
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  SharedUtils: "resource://services-settings/SharedUtils.sys.mjs",
});

// Note: we currently only ever construct one instance of Worker.
// If it stops being a singleton, the AsyncShutdown code at the bottom
// of this file, as well as these globals, will need adjusting.
let gShutdown = false;
let gShutdownResolver = null;

class RemoteSettingsWorkerError extends Error {
  constructor(message) {
    super(message);
    this.name = "RemoteSettingsWorkerError";
  }
}

class Worker {
  constructor(source) {
    if (gShutdown) {
      console.error("Can't create worker once shutdown has started");
    }
    this.source = source;
    this.worker = null;

    this.callbacks = new Map();
    this.lastCallbackId = 0;
    this.idleTimeoutId = null;
  }

  async _execute(method, args = [], options = {}) {
    // Check if we're shutting down.
    if (gShutdown && method != "prepareShutdown") {
      throw new RemoteSettingsWorkerError("Remote Settings has shut down.");
    }
    // Don't instantiate the worker to shut it down.
    if (method == "prepareShutdown" && !this.worker) {
      return null;
    }

    const { mustComplete = false } = options;
    // (Re)instantiate the worker if it was terminated.
    if (!this.worker) {
      this.worker = new ChromeWorker(this.source, { type: "module" });
      this.worker.onmessage = this._onWorkerMessage.bind(this);
      this.worker.onerror = error => {
        // Worker crashed. Reject each pending callback.
        for (const { reject } of this.callbacks.values()) {
          reject(error);
        }
        this.callbacks.clear();
        // And terminate it.
        this.stop();
      };
    }
    // New activity: reset the idle timer.
    if (this.idleTimeoutId) {
      clearTimeout(this.idleTimeoutId);
    }
    let identifier = method + "-";
    // Include the collection details in the importJSONDump case.
    if (identifier == "importJSONDump-") {
      identifier += `${args[0]}-${args[1]}-`;
    }
    return new Promise((resolve, reject) => {
      const callbackId = `${identifier}${++this.lastCallbackId}`;
      this.callbacks.set(callbackId, { resolve, reject, mustComplete });
      this.worker.postMessage({ callbackId, method, args });
    });
  }

  _onWorkerMessage(event) {
    const { callbackId, result, error } = event.data;
    // If we're shutting down, we may have already rejected this operation
    // and removed its callback from our map:
    if (!this.callbacks.has(callbackId)) {
      return;
    }
    const { resolve, reject } = this.callbacks.get(callbackId);
    if (error) {
      reject(new RemoteSettingsWorkerError(error));
    } else {
      resolve(result);
    }
    this.callbacks.delete(callbackId);

    // Terminate the worker when it's unused for some time.
    // But don't terminate it if an operation is pending.
    if (!this.callbacks.size) {
      if (gShutdown) {
        this.stop();
        if (gShutdownResolver) {
          gShutdownResolver();
        }
      } else {
        this.idleTimeoutId = setTimeout(() => {
          this.stop();
        }, lazy.gMaxIdleMilliseconds);
      }
    }
  }

  /**
   * Called at shutdown to abort anything the worker is doing that isn't
   * critical.
   */
  _abortCancelableRequests() {
    // End all tasks that we can.
    const callbackCopy = Array.from(this.callbacks.entries());
    const error = new Error("Shutdown, aborting read-only worker requests.");
    for (const [id, { reject, mustComplete }] of callbackCopy) {
      if (!mustComplete) {
        this.callbacks.delete(id);
        reject(error);
      }
    }
    // There might be nothing left now:
    if (!this.callbacks.size) {
      this.stop();
      if (gShutdownResolver) {
        gShutdownResolver();
      }
    }
    // If there was something left, we'll stop as soon as we get messages from
    // those tasks, too.
    // Let's hurry them along a bit:
    this._execute("prepareShutdown");
  }

  stop() {
    this.worker.terminate();
    this.worker = null;
    this.idleTimeoutId = null;
  }

  async canonicalStringify(localRecords, remoteRecords, timestamp) {
    return this._execute("canonicalStringify", [
      localRecords,
      remoteRecords,
      timestamp,
    ]);
  }

  async importJSONDump(bucket, collection) {
    return this._execute("importJSONDump", [bucket, collection], {
      mustComplete: true,
    });
  }

  async checkFileHash(filepath, size, hash) {
    return this._execute("checkFileHash", [filepath, size, hash]);
  }

  async checkContentHash(buffer, size, hash) {
    // The implementation does little work on the current thread, so run the
    // task on the current thread instead of the worker thread.
    return lazy.SharedUtils.checkContentHash(buffer, size, hash);
  }
}

// Now, first add a shutdown blocker. If that fails, we must have
// shut down already.
// We're doing this here rather than in the Worker constructor because in
// principle having just 1 shutdown blocker for the entire file should be
// fine. If we ever start creating more than one Worker instance, this
// code will need adjusting to deal with that.
try {
  lazy.AsyncShutdown.profileBeforeChange.addBlocker(
    "Remote Settings profile-before-change",
    async () => {
      // First, indicate we've shut down.
      gShutdown = true;
      // Then, if we have no worker or no callbacks, we're done.
      if (
        !RemoteSettingsWorker.worker ||
        !RemoteSettingsWorker.callbacks.size
      ) {
        return null;
      }
      // Otherwise, there's something left to do. Set up a promise:
      let finishedPromise = new Promise(resolve => {
        gShutdownResolver = resolve;
      });

      // Try to cancel most of the work:
      RemoteSettingsWorker._abortCancelableRequests();

      // Return a promise that the worker will resolve.
      return finishedPromise;
    },
    {
      fetchState() {
        const remainingCallbacks = RemoteSettingsWorker.callbacks;
        const details = Array.from(remainingCallbacks.keys()).join(", ");
        return `Remaining: ${remainingCallbacks.size} callbacks (${details}).`;
      },
    }
  );
} catch (ex) {
  console.error(
    "Couldn't add shutdown blocker, assuming shutdown has started."
  );
  console.error(ex);
  // If AsyncShutdown throws, `profileBeforeChange` has already fired. Ignore it
  // and mark shutdown. Constructing the worker will report an error and do
  // nothing.
  gShutdown = true;
}

export var RemoteSettingsWorker = new Worker(
  "resource://services-settings/RemoteSettings.worker.mjs"
);
PK
!<F&�7@7@'modules/services-settings/Utils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { ServiceRequest } from "resource://gre/modules/ServiceRequest.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  SharedUtils: "resource://services-settings/SharedUtils.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "CaptivePortalService",
  "@mozilla.org/network/captive-portal-service;1",
  "nsICaptivePortalService"
);
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gNetworkLinkService",
  "@mozilla.org/network/network-link-service;1",
  "nsINetworkLinkService"
);

// Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
// See LOG_LEVELS in Console.sys.mjs. Common examples: "all", "debug", "info",
// "warn", "error".
const log = (() => {
  const { ConsoleAPI } = ChromeUtils.importESModule(
    "resource://gre/modules/Console.sys.mjs"
  );
  return new ConsoleAPI({
    maxLogLevel: "warn",
    maxLogLevelPref: "services.settings.loglevel",
    prefix: "services.settings",
  });
})();

ChromeUtils.defineLazyGetter(lazy, "isRunningTests", () => {
  if (Services.env.get("MOZ_DISABLE_NONLOCAL_CONNECTIONS") === "1") {
    // Allow to override the server URL if non-local connections are disabled,
    // usually true when running tests.
    return true;
  }
  return false;
});

// Overriding the server URL is normally disabled on Beta and Release channels,
// except under some conditions.
ChromeUtils.defineLazyGetter(lazy, "allowServerURLOverride", () => {
  if (!AppConstants.RELEASE_OR_BETA) {
    // Always allow to override the server URL on Nightly/DevEdition.
    return true;
  }

  if (lazy.isRunningTests) {
    return true;
  }

  if (Services.env.get("MOZ_REMOTE_SETTINGS_DEVTOOLS") === "1") {
    // Allow to override the server URL when using remote settings devtools.
    return true;
  }

  if (lazy.gServerURL != AppConstants.REMOTE_SETTINGS_SERVER_URL) {
    log.warn("Ignoring preference override of remote settings server");
    log.warn(
      "Allow by setting MOZ_REMOTE_SETTINGS_DEVTOOLS=1 in the environment"
    );
  }

  return false;
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gServerURL",
  "services.settings.server",
  AppConstants.REMOTE_SETTINGS_SERVER_URL
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gPreviewEnabled",
  "services.settings.preview_enabled",
  false
);

function _isUndefined(value) {
  return typeof value === "undefined";
}

const _cdnURLs = {};

export var Utils = {
  get SERVER_URL() {
    return lazy.allowServerURLOverride
      ? lazy.gServerURL
      : AppConstants.REMOTE_SETTINGS_SERVER_URL;
  },

  CHANGES_PATH: "/buckets/monitor/collections/changes/changeset",

  /**
   * Logger instance.
   */
  log,

  get shouldSkipRemoteActivityDueToTests() {
    return (
      (lazy.isRunningTests || Cu.isInAutomation) &&
      this.SERVER_URL == "data:,#remote-settings-dummy/v1"
    );
  },

  get CERT_CHAIN_ROOT_IDENTIFIER() {
    if (this.SERVER_URL == AppConstants.REMOTE_SETTINGS_SERVER_URL) {
      return Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot;
    }
    if (this.SERVER_URL.includes("allizom.")) {
      return Ci.nsIContentSignatureVerifier.ContentSignatureStageRoot;
    }
    if (this.SERVER_URL.includes("dev.")) {
      return Ci.nsIContentSignatureVerifier.ContentSignatureDevRoot;
    }
    if (Services.env.exists("XPCSHELL_TEST_PROFILE_DIR")) {
      return Ci.nsIX509CertDB.AppXPCShellRoot;
    }
    return Ci.nsIContentSignatureVerifier.ContentSignatureLocalRoot;
  },

  get LOAD_DUMPS() {
    // Load dumps only if pulling data from the production server, or in tests.
    return (
      this.SERVER_URL == AppConstants.REMOTE_SETTINGS_SERVER_URL ||
      lazy.isRunningTests
    );
  },

  get PREVIEW_MODE() {
    // We want to offer the ability to set preview mode via a preference
    // for consumers who want to pull from the preview bucket on startup.
    if (_isUndefined(this._previewModeEnabled) && lazy.allowServerURLOverride) {
      return lazy.gPreviewEnabled;
    }
    return !!this._previewModeEnabled;
  },

  /**
   * Internal method to enable pulling data from preview buckets.
   * @param enabled
   */
  enablePreviewMode(enabled) {
    const bool2str = v =>
      // eslint-disable-next-line no-nested-ternary
      _isUndefined(v) ? "unset" : v ? "enabled" : "disabled";
    this.log.debug(
      `Preview mode: ${bool2str(this._previewModeEnabled)} -> ${bool2str(
        enabled
      )}`
    );
    this._previewModeEnabled = enabled;
  },

  /**
   * Returns the actual bucket name to be used. When preview mode is enabled,
   * this adds the *preview* suffix.
   *
   * See also `SharedUtils.loadJSONDump()` which strips the preview suffix to identify
   * the packaged JSON file.
   *
   * @param bucketName the client bucket
   * @returns the final client bucket depending whether preview mode is enabled.
   */
  actualBucketName(bucketName) {
    let actual = bucketName.replace("-preview", "");
    if (this.PREVIEW_MODE) {
      actual += "-preview";
    }
    return actual;
  },

  /**
   * Check if network is down.
   *
   * Note that if this returns false, it does not guarantee
   * that network is up.
   *
   * @return {bool} Whether network is down or not.
   */
  get isOffline() {
    try {
      return (
        Services.io.offline ||
        lazy.CaptivePortalService.state ==
          lazy.CaptivePortalService.LOCKED_PORTAL ||
        !lazy.gNetworkLinkService.isLinkUp
      );
    } catch (ex) {
      log.warn("Could not determine network status.", ex);
    }
    return false;
  },

  /**
   * A wrapper around `ServiceRequest` that behaves like `fetch()`.
   *
   * Use this in order to leverage the `beConservative` flag, for
   * example to avoid using HTTP3 to fetch critical data.
   *
   * @param input a resource
   * @param init request options
   * @returns a Response object
   */
  async fetch(input, init = {}) {
    return new Promise(function (resolve, reject) {
      const request = new ServiceRequest();
      function fallbackOrReject(err) {
        if (
          // At most one recursive Utils.fetch call (bypassProxy=false to true).
          bypassProxy ||
          Services.startup.shuttingDown ||
          Utils.isOffline ||
          !request.isProxied ||
          !request.bypassProxyEnabled
        ) {
          reject(err);
          return;
        }
        ServiceRequest.logProxySource(request.channel, "remote-settings");
        resolve(Utils.fetch(input, { ...init, bypassProxy: true }));
      }

      request.onerror = () =>
        fallbackOrReject(new TypeError("NetworkError: Network request failed"));
      request.ontimeout = () =>
        fallbackOrReject(new TypeError("Timeout: Network request failed"));
      request.onabort = () =>
        fallbackOrReject(new DOMException("Aborted", "AbortError"));
      request.onload = () => {
        // Parse raw response headers into `Headers` object.
        const headers = new Headers();
        const rawHeaders = request.getAllResponseHeaders();
        rawHeaders
          .trim()
          .split(/[\r\n]+/)
          .forEach(line => {
            const parts = line.split(": ");
            const header = parts.shift();
            const value = parts.join(": ");
            headers.set(header, value);
          });

        const responseAttributes = {
          status: request.status,
          statusText: request.statusText,
          url: request.responseURL,
          headers,
        };
        resolve(new Response(request.response, responseAttributes));
      };

      const { method = "GET", headers = {}, bypassProxy = false } = init;

      request.open(method, input, { bypassProxy });
      // By default, XMLHttpRequest converts the response based on the
      // Content-Type header, or UTF-8 otherwise. This may mangle binary
      // responses. Avoid that by requesting the raw bytes.
      request.responseType = "arraybuffer";

      for (const [name, value] of Object.entries(headers)) {
        request.setRequestHeader(name, value);
      }

      request.send();
    });
  },

  /**
   * Retrieves the base URL for attachments from the server configuration.
   *
   * If the URL has been previously fetched and cached, it returns the cached URL.
   *
   * @async
   * @function baseAttachmentsURL
   * @memberof Utils
   * @returns {Promise<string>} A promise that resolves to the base URL for attachments.
   *
   * @throws {Error} If there is an error fetching or parsing the server response.
   *
   * @example
   * const attachmentsURL = await Downloader.baseAttachmentsURL();
   * console.log(attachmentsURL);
   */
  async baseAttachmentsURL() {
    if (!_cdnURLs[Utils.SERVER_URL]) {
      const resp = await Utils.fetch(`${Utils.SERVER_URL}/`);
      const serverInfo = await resp.json();
      // Server capabilities expose attachments configuration.
      const {
        capabilities: {
          attachments: { base_url },
        },
      } = serverInfo;
      // Make sure the URL always has a trailing slash.
      _cdnURLs[Utils.SERVER_URL] =
        base_url + (base_url.endsWith("/") ? "" : "/");
    }
    return _cdnURLs[Utils.SERVER_URL];
  },

  /**
   * Check if local data exist for the specified client.
   *
   * @param {RemoteSettingsClient} client
   * @return {bool} Whether it exists or not.
   */
  async hasLocalData(client) {
    const timestamp = await client.db.getLastModified();
    return timestamp !== null;
  },

  /**
   * Check if we ship a JSON dump for the specified bucket and collection.
   *
   * @param {String} bucket
   * @param {String} collection
   * @return {bool} Whether it is present or not.
   */
  async hasLocalDump(bucket, collection) {
    try {
      await fetch(
        `resource://app/defaults/settings/${bucket}/${collection}.json`,
        {
          method: "HEAD",
        }
      );
      return true;
    } catch (e) {
      return false;
    }
  },

  /**
   * Look up the last modification time of the JSON dump.
   *
   * @param {String} bucket
   * @param {String} collection
   * @return {int} The last modification time of the dump. -1 if non-existent.
   */
  async getLocalDumpLastModified(bucket, collection) {
    if (!this._dumpStats) {
      if (!this._dumpStatsInitPromise) {
        this._dumpStatsInitPromise = (async () => {
          try {
            let res = await fetch(
              "resource://app/defaults/settings/last_modified.json"
            );
            this._dumpStats = await res.json();
          } catch (e) {
            log.warn(`Failed to load last_modified.json: ${e}`);
            this._dumpStats = {};
          }
          delete this._dumpStatsInitPromise;
        })();
      }
      await this._dumpStatsInitPromise;
    }
    const identifier = `${bucket}/${collection}`;
    let lastModified = this._dumpStats[identifier];
    if (lastModified === undefined) {
      const { timestamp: dumpTimestamp } = await lazy.SharedUtils.loadJSONDump(
        bucket,
        collection
      );
      // Client recognize -1 as missing dump.
      lastModified = dumpTimestamp ?? -1;
      this._dumpStats[identifier] = lastModified;
    }
    return lastModified;
  },

  /**
   * Fetch the list of remote collections and their timestamp.
   * ```
   *   {
   *     "timestamp": 1486545678,
   *     "changes":[
   *       {
   *         "host":"kinto-ota.dev.mozaws.net",
   *         "last_modified":1450717104423,
   *         "bucket":"blocklists",
   *         "collection":"certificates"
   *       },
   *       ...
   *     ],
   *     "metadata": {}
   *   }
   * ```
   * @param {String} serverUrl         The server URL (eg. `https://server.org/v1`)
   * @param {int}    expectedTimestamp The timestamp that the server is supposed to return.
   *                                   We obtained it from the Megaphone notification payload,
   *                                   and we use it only for cache busting (Bug 1497159).
   * @param {String} lastEtag          (optional) The Etag of the latest poll to be matched
   *                                   by the server (eg. `"123456789"`).
   * @param {Object} filters
   */
  async fetchLatestChanges(serverUrl, options = {}) {
    const { expectedTimestamp, lastEtag = "", filters = {} } = options;

    let url = serverUrl + Utils.CHANGES_PATH;
    const params = {
      ...filters,
      _expected: expectedTimestamp ?? 0,
    };
    if (lastEtag != "") {
      params._since = lastEtag;
    }
    if (params) {
      url +=
        "?" +
        Object.entries(params)
          .map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
          .join("&");
    }
    const response = await Utils.fetch(url);

    if (response.status >= 500) {
      throw new Error(`Server error ${response.status} ${response.statusText}`);
    }

    const is404FromCustomServer =
      response.status == 404 &&
      Services.prefs.prefHasUserValue("services.settings.server");

    const ct = response.headers.get("Content-Type");
    if (!is404FromCustomServer && (!ct || !ct.includes("application/json"))) {
      throw new Error(`Unexpected content-type "${ct}"`);
    }

    let payload;
    try {
      payload = await response.json();
    } catch (e) {
      payload = e.message;
    }

    if (!payload.hasOwnProperty("changes")) {
      // If the server is failing, the JSON response might not contain the
      // expected data. For example, real server errors (Bug 1259145)
      // or dummy local server for tests (Bug 1481348)
      if (!is404FromCustomServer) {
        throw new Error(
          `Server error ${url} ${response.status} ${
            response.statusText
          }: ${JSON.stringify(payload)}`
        );
      }
    }

    const { changes = [], timestamp } = payload;

    let serverTimeMillis = Date.parse(response.headers.get("Date"));
    // Since the response is served via a CDN, the Date header value could have been cached.
    const cacheAgeSeconds = response.headers.has("Age")
      ? parseInt(response.headers.get("Age"), 10)
      : 0;
    serverTimeMillis += cacheAgeSeconds * 1000;

    // Age of data (time between publication and now).
    const ageSeconds = (serverTimeMillis - timestamp) / 1000;

    // Check if the server asked the clients to back off.
    let backoffSeconds;
    if (response.headers.has("Backoff")) {
      const value = parseInt(response.headers.get("Backoff"), 10);
      if (!isNaN(value)) {
        backoffSeconds = value;
      }
    }

    return {
      changes,
      currentEtag: `"${timestamp}"`,
      serverTimeMillis,
      backoffSeconds,
      ageSeconds,
    };
  },

  /**
   * Test if a single object matches all given filters.
   *
   * @param  {Object} filters  The filters object.
   * @param  {Object} entry    The object to filter.
   * @return {Boolean}
   */
  filterObject(filters, entry) {
    return Object.entries(filters).every(([filter, value]) => {
      if (Array.isArray(value)) {
        return value.some(candidate => candidate === entry[filter]);
      } else if (typeof value === "object") {
        return Utils.filterObject(value, entry[filter]);
      } else if (!Object.prototype.hasOwnProperty.call(entry, filter)) {
        console.error(`The property ${filter} does not exist`);
        return false;
      }
      return entry[filter] === value;
    });
  },

  /**
   * Sorts records in a list according to a given ordering.
   *
   * @param  {String} order The ordering, eg. `-last_modified`.
   * @param  {Array}  list  The collection to order.
   * @return {Array}
   */
  sortObjects(order, list) {
    const hasDash = order[0] === "-";
    const field = hasDash ? order.slice(1) : order;
    const direction = hasDash ? -1 : 1;
    return list.slice().sort((a, b) => {
      if (a[field] && _isUndefined(b[field])) {
        return direction;
      }
      if (b[field] && _isUndefined(a[field])) {
        return -direction;
      }
      if (_isUndefined(a[field]) && _isUndefined(b[field])) {
        return 0;
      }
      return a[field] > b[field] ? direction : -direction;
    });
  },
};
PK
!<.�rwUwU1modules/services-settings/remote-settings.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Database: "resource://services-settings/Database.sys.mjs",
  FilterExpressions:
    "resource://gre/modules/components-utils/FilterExpressions.sys.mjs",
  pushBroadcastService: "resource://gre/modules/PushBroadcastService.sys.mjs",
  RemoteSettingsClient:
    "resource://services-settings/RemoteSettingsClient.sys.mjs",
  SyncHistory: "resource://services-settings/SyncHistory.sys.mjs",
  UptakeTelemetry: "resource://services-common/uptake-telemetry.sys.mjs",
  Utils: "resource://services-settings/Utils.sys.mjs",
});

const PREF_SETTINGS_BRANCH = "services.settings.";
const PREF_SETTINGS_SERVER_BACKOFF = "server.backoff";
const PREF_SETTINGS_LAST_UPDATE = "last_update_seconds";
const PREF_SETTINGS_LAST_ETAG = "last_etag";
const PREF_SETTINGS_CLOCK_SKEW_SECONDS = "clock_skew_seconds";
const PREF_SETTINGS_SYNC_HISTORY_SIZE = "sync_history_size";
const PREF_SETTINGS_SYNC_HISTORY_ERROR_THRESHOLD =
  "sync_history_error_threshold";

// Telemetry identifiers.
const TELEMETRY_COMPONENT = "remotesettings";
const TELEMETRY_SOURCE_POLL = "settings-changes-monitoring";
const TELEMETRY_SOURCE_SYNC = "settings-sync";

// Push broadcast id.
const BROADCAST_ID = "remote-settings/monitor_changes";

// Signer to be used when not specified (see Ci.nsIContentSignatureVerifier).
const DEFAULT_SIGNER = "remote-settings.content-signature.mozilla.org";

ChromeUtils.defineLazyGetter(lazy, "gPrefs", () => {
  return Services.prefs.getBranch(PREF_SETTINGS_BRANCH);
});
ChromeUtils.defineLazyGetter(lazy, "console", () => lazy.Utils.log);

ChromeUtils.defineLazyGetter(lazy, "gSyncHistory", () => {
  const prefSize = lazy.gPrefs.getIntPref(PREF_SETTINGS_SYNC_HISTORY_SIZE, 100);
  const size = Math.min(Math.max(prefSize, 1000), 10);
  return new lazy.SyncHistory(TELEMETRY_SOURCE_SYNC, { size });
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gPrefBrokenSyncThreshold",
  PREF_SETTINGS_BRANCH + PREF_SETTINGS_SYNC_HISTORY_ERROR_THRESHOLD,
  10
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gPrefDestroyBrokenEnabled",
  PREF_SETTINGS_BRANCH + "destroy_broken_db_enabled",
  true
);

/**
 * Default entry filtering function, in charge of excluding remote settings entries
 * where the JEXL expression evaluates into a falsy value.
 * @param {Object}            entry       The Remote Settings entry to be excluded or kept.
 * @param {ClientEnvironment} environment Information about version, language, platform etc.
 * @returns {?Object} the entry or null if excluded.
 */
export async function jexlFilterFunc(entry, environment) {
  const { filter_expression } = entry;
  if (!filter_expression) {
    return entry;
  }
  let result;
  try {
    const context = {
      env: environment,
    };
    result = await lazy.FilterExpressions.eval(filter_expression, context);
  } catch (e) {
    console.error(e);
  }
  return result ? entry : null;
}

function remoteSettingsFunction() {
  const _clients = new Map();
  let _invalidatePolling = false;

  // If not explicitly specified, use the default signer.
  const defaultOptions = {
    signerName: DEFAULT_SIGNER,
    filterFunc: jexlFilterFunc,
  };

  /**
   * RemoteSettings constructor.
   *
   * @param {String} collectionName The remote settings identifier
   * @param {Object} options Advanced options
   * @returns {RemoteSettingsClient} An instance of a Remote Settings client.
   */
  const remoteSettings = function (collectionName, options) {
    // Get or instantiate a remote settings client.
    if (!_clients.has(collectionName)) {
      // Register a new client!
      const c = new lazy.RemoteSettingsClient(collectionName, {
        ...defaultOptions,
        ...options,
      });
      // Store instance for later call.
      _clients.set(collectionName, c);
      // Invalidate the polling status, since we want the new collection to
      // be taken into account.
      _invalidatePolling = true;
      lazy.console.debug(`Instantiated new client ${c.identifier}`);
    }
    return _clients.get(collectionName);
  };

  /**
   * Internal helper to retrieve existing instances of clients or new instances
   * with default options if possible, or `null` if bucket/collection are unknown.
   */
  async function _client(bucketName, collectionName) {
    // Check if a client was registered for this bucket/collection. Potentially
    // with some specific options like signer, filter function etc.
    const client = _clients.get(collectionName);
    if (client && client.bucketName == bucketName) {
      return client;
    }
    // There was no client registered for this collection, but it's the main bucket,
    // therefore we can instantiate a client with the default options.
    // So if we have a local database or if we ship a JSON dump, then it means that
    // this client is known but it was not registered yet (eg. calling module not "imported" yet).
    if (
      bucketName ==
      lazy.Utils.actualBucketName(AppConstants.REMOTE_SETTINGS_DEFAULT_BUCKET)
    ) {
      const c = new lazy.RemoteSettingsClient(collectionName, defaultOptions);
      const [dbExists, localDump] = await Promise.all([
        lazy.Utils.hasLocalData(c),
        lazy.Utils.hasLocalDump(bucketName, collectionName),
      ]);
      if (dbExists || localDump) {
        return c;
      }
    }
    // Else, we cannot return a client instance because we are not able to synchronize data in specific buckets.
    // Mainly because we cannot guess which `signerName` has to be used for example.
    // And we don't want to synchronize data for collections in the main bucket that are
    // completely unknown (ie. no database and no JSON dump).
    lazy.console.debug(`No known client for ${bucketName}/${collectionName}`);
    return null;
  }

  /**
   * Helper to introspect the synchronization history and determine whether it is
   * consistently failing and thus, broken.
   * @returns {bool} true if broken.
   */
  async function isSynchronizationBroken() {
    // The minimum number of errors is customizable, but with a maximum.
    const threshold = Math.min(lazy.gPrefBrokenSyncThreshold, 20);
    // Read history of synchronization past statuses.
    const pastEntries = await lazy.gSyncHistory.list();
    const lastSuccessIdx = pastEntries.findIndex(
      e => e.status == lazy.UptakeTelemetry.STATUS.SUCCESS
    );
    return (
      // Only errors since last success.
      lastSuccessIdx >= threshold ||
      // Or only errors with a minimum number of history entries.
      (lastSuccessIdx < 0 && pastEntries.length >= threshold)
    );
  }

  /**
   * Main polling method, called by the ping mechanism.
   *
   * @param {Object} options
.  * @param {Object} options.expectedTimestamp (optional) The expected timestamp to be received — used by servers for cache busting.
   * @param {string} options.trigger           (optional) label to identify what triggered this sync (eg. ``"timer"``, default: `"manual"`)
   * @param {bool}   options.full              (optional) Ignore last polling status and fetch all changes (default: `false`)
   * @returns {Promise} or throws error if something goes wrong.
   */
  remoteSettings.pollChanges = async ({
    expectedTimestamp,
    trigger = "manual",
    full = false,
  } = {}) => {
    if (lazy.Utils.shouldSkipRemoteActivityDueToTests) {
      return;
    }
    // When running in full mode, we ignore last polling status.
    if (full) {
      lazy.gPrefs.clearUserPref(PREF_SETTINGS_SERVER_BACKOFF);
      lazy.gPrefs.clearUserPref(PREF_SETTINGS_LAST_UPDATE);
      lazy.gPrefs.clearUserPref(PREF_SETTINGS_LAST_ETAG);
    }

    let pollTelemetryArgs = {
      source: TELEMETRY_SOURCE_POLL,
      trigger,
    };

    if (lazy.Utils.isOffline) {
      lazy.console.info("Network is offline. Give up.");
      await lazy.UptakeTelemetry.report(
        TELEMETRY_COMPONENT,
        lazy.UptakeTelemetry.STATUS.NETWORK_OFFLINE_ERROR,
        pollTelemetryArgs
      );
      return;
    }

    const startedAt = new Date();

    // Check if the server backoff time is elapsed.
    if (lazy.gPrefs.prefHasUserValue(PREF_SETTINGS_SERVER_BACKOFF)) {
      const backoffReleaseTime = lazy.gPrefs.getStringPref(
        PREF_SETTINGS_SERVER_BACKOFF
      );
      const remainingMilliseconds =
        parseInt(backoffReleaseTime, 10) - Date.now();
      if (remainingMilliseconds > 0) {
        // Backoff time has not elapsed yet.
        await lazy.UptakeTelemetry.report(
          TELEMETRY_COMPONENT,
          lazy.UptakeTelemetry.STATUS.BACKOFF,
          pollTelemetryArgs
        );
        throw new Error(
          `Server is asking clients to back off; retry in ${Math.ceil(
            remainingMilliseconds / 1000
          )}s.`
        );
      } else {
        lazy.gPrefs.clearUserPref(PREF_SETTINGS_SERVER_BACKOFF);
      }
    }

    // When triggered from the daily timer, we try to recover a broken
    // sync state by destroying the local DB completely and retrying from scratch.
    if (
      lazy.gPrefDestroyBrokenEnabled &&
      trigger == "timer" &&
      (await isSynchronizationBroken())
    ) {
      // We don't want to destroy the local DB if the failures are related to
      // network or server errors though.
      const lastStatus = await lazy.gSyncHistory.last();
      const lastErrorClass =
        lazy.RemoteSettingsClient[lastStatus?.infos?.errorName] || Error;
      const isLocalError = !(
        lastErrorClass.prototype instanceof lazy.RemoteSettingsClient.APIError
      );
      if (isLocalError) {
        console.warn(
          "Synchronization has failed consistently. Destroy database."
        );
        // Clear the last ETag to refetch everything.
        lazy.gPrefs.clearUserPref(PREF_SETTINGS_LAST_ETAG);
        // Clear the history, to avoid re-destroying several times in a row.
        await lazy.gSyncHistory.clear().catch(error => console.error(error));
        // Delete the whole IndexedDB database.
        await lazy.Database.destroy().catch(error => console.error(error));
      } else {
        console.warn(
          `Synchronization is broken, but last error is ${lastStatus}`
        );
      }
    }

    lazy.console.info("Start polling for changes");
    Services.obs.notifyObservers(
      null,
      "remote-settings:changes-poll-start",
      JSON.stringify({ expectedTimestamp })
    );

    // Do we have the latest version already?
    // Every time we register a new client, we have to fetch the whole list again.
    const lastEtag = _invalidatePolling
      ? ""
      : lazy.gPrefs.getStringPref(PREF_SETTINGS_LAST_ETAG, "");

    let pollResult;
    try {
      pollResult = await lazy.Utils.fetchLatestChanges(lazy.Utils.SERVER_URL, {
        expectedTimestamp,
        lastEtag,
      });
    } catch (e) {
      // Report polling error to Uptake Telemetry.
      let reportStatus;
      if (/JSON\.parse/.test(e.message)) {
        reportStatus = lazy.UptakeTelemetry.STATUS.PARSE_ERROR;
      } else if (/content-type/.test(e.message)) {
        reportStatus = lazy.UptakeTelemetry.STATUS.CONTENT_ERROR;
      } else if (/Server/.test(e.message)) {
        reportStatus = lazy.UptakeTelemetry.STATUS.SERVER_ERROR;
        // If the server replied with bad request, clear the last ETag
        // value to unblock the next run of synchronization.
        lazy.gPrefs.clearUserPref(PREF_SETTINGS_LAST_ETAG);
      } else if (/Timeout/.test(e.message)) {
        reportStatus = lazy.UptakeTelemetry.STATUS.TIMEOUT_ERROR;
      } else if (/NetworkError/.test(e.message)) {
        reportStatus = lazy.UptakeTelemetry.STATUS.NETWORK_ERROR;
      } else {
        reportStatus = lazy.UptakeTelemetry.STATUS.UNKNOWN_ERROR;
      }
      await lazy.UptakeTelemetry.report(
        TELEMETRY_COMPONENT,
        reportStatus,
        pollTelemetryArgs
      );
      // No need to go further.
      throw new Error(`Polling for changes failed: ${e.message}.`);
    }

    const {
      serverTimeMillis,
      changes,
      currentEtag,
      backoffSeconds,
      ageSeconds,
    } = pollResult;

    // Report age of server data in Telemetry.
    pollTelemetryArgs = { age: ageSeconds, ...pollTelemetryArgs };

    // Report polling success to Uptake Telemetry.
    const reportStatus =
      changes.length === 0
        ? lazy.UptakeTelemetry.STATUS.UP_TO_DATE
        : lazy.UptakeTelemetry.STATUS.SUCCESS;
    await lazy.UptakeTelemetry.report(
      TELEMETRY_COMPONENT,
      reportStatus,
      pollTelemetryArgs
    );

    // Check if the server asked the clients to back off (for next poll).
    if (backoffSeconds) {
      lazy.console.info(
        "Server asks clients to backoff for ${backoffSeconds} seconds"
      );
      const backoffReleaseTime = Date.now() + backoffSeconds * 1000;
      lazy.gPrefs.setStringPref(
        PREF_SETTINGS_SERVER_BACKOFF,
        backoffReleaseTime
      );
    }

    // Record new update time and the difference between local and server time.
    // Negative clockDifference means local time is behind server time
    // by the absolute of that value in seconds (positive means it's ahead)
    const clockDifference = Math.floor((Date.now() - serverTimeMillis) / 1000);
    lazy.gPrefs.setIntPref(PREF_SETTINGS_CLOCK_SKEW_SECONDS, clockDifference);
    const checkedServerTimeInSeconds = Math.round(serverTimeMillis / 1000);
    lazy.gPrefs.setIntPref(
      PREF_SETTINGS_LAST_UPDATE,
      checkedServerTimeInSeconds
    );

    // Iterate through the collections version info and initiate a synchronization
    // on the related remote settings clients.
    let firstError;
    for (const change of changes) {
      const { bucket, collection, last_modified } = change;

      const client = await _client(bucket, collection);
      if (!client) {
        // This collection has no associated client (eg. preview, other platform...)
        continue;
      }
      // Start synchronization! It will be a no-op if the specified `lastModified` equals
      // the one in the local database.
      try {
        await client.maybeSync(last_modified, { trigger });

        // Save last time this client was successfully synced.
        Services.prefs.setIntPref(
          client.lastCheckTimePref,
          checkedServerTimeInSeconds
        );
      } catch (e) {
        lazy.console.error(e);
        if (!firstError) {
          firstError = e;
          firstError.details = change;
        }
      }
    }

    // Polling is done.
    _invalidatePolling = false;

    // Report total synchronization duration to Telemetry.
    const durationMilliseconds = new Date() - startedAt;
    const syncTelemetryArgs = {
      source: TELEMETRY_SOURCE_SYNC,
      duration: durationMilliseconds,
      timestamp: `${currentEtag}`,
      trigger,
    };

    if (firstError) {
      // Report the global synchronization failure. Individual uptake reports will also have been sent for each collection.
      const status = lazy.UptakeTelemetry.STATUS.SYNC_ERROR;
      await lazy.UptakeTelemetry.report(
        TELEMETRY_COMPONENT,
        status,
        syncTelemetryArgs
      );
      // Keep track of sync failure in history.
      await lazy.gSyncHistory
        .store(currentEtag, status, {
          expectedTimestamp,
          errorName: firstError.name,
        })
        .catch(error => console.error(error));
      // Notify potential observers of the error.
      Services.obs.notifyObservers(
        { wrappedJSObject: { error: firstError } },
        "remote-settings:sync-error"
      );

      // If synchronization has been consistently failing, send a specific signal.
      // See https://bugzilla.mozilla.org/show_bug.cgi?id=1729400
      // and https://bugzilla.mozilla.org/show_bug.cgi?id=1658597
      if (await isSynchronizationBroken()) {
        await lazy.UptakeTelemetry.report(
          TELEMETRY_COMPONENT,
          lazy.UptakeTelemetry.STATUS.SYNC_BROKEN_ERROR,
          syncTelemetryArgs
        );

        Services.obs.notifyObservers(
          { wrappedJSObject: { error: firstError } },
          "remote-settings:broken-sync-error"
        );
      }

      // Rethrow the first observed error
      throw firstError;
    }

    // Save current Etag for next poll.
    lazy.gPrefs.setStringPref(PREF_SETTINGS_LAST_ETAG, currentEtag);

    // Report the global synchronization success.
    const status = lazy.UptakeTelemetry.STATUS.SUCCESS;
    await lazy.UptakeTelemetry.report(
      TELEMETRY_COMPONENT,
      status,
      syncTelemetryArgs
    );
    // Keep track of sync success in history.
    await lazy.gSyncHistory
      .store(currentEtag, status)
      .catch(error => console.error(error));

    lazy.console.info("Polling for changes done");
    Services.obs.notifyObservers(null, "remote-settings:changes-poll-end");
  };

  /**
   * Enables or disables preview mode.
   *
   * When enabled, all existing and future clients will pull data from
   * the `*-preview` buckets. This allows developers and QA to test their
   * changes before publishing them for all clients.
   */
  remoteSettings.enablePreviewMode = enabled => {
    // Set the flag for future clients.
    lazy.Utils.enablePreviewMode(enabled);
    // Enable it on existing clients.
    for (const client of _clients.values()) {
      client.refreshBucketName();
    }
  };

  /**
   * Returns an object with polling status information and the list of
   * known remote settings collections.
   * @param {Object} options
   * @param {boolean?} options.localOnly (optional) If set to `true`, do not contact the server.
   */
  remoteSettings.inspect = async (options = {}) => {
    const { localOnly = false } = options;

    let changes = [];
    let serverTimestamp = null;
    if (!localOnly) {
      // Make sure we fetch the latest server info, use a random cache bust value.
      const randomCacheBust = 99990000 + Math.floor(Math.random() * 9999);
      ({ changes, currentEtag: serverTimestamp } =
        await lazy.Utils.fetchLatestChanges(lazy.Utils.SERVER_URL, {
          expected: randomCacheBust,
        }));
    }
    const collections = await Promise.all(
      changes.map(async change => {
        const { bucket, collection, last_modified: serverTimestamp } = change;
        const client = await _client(bucket, collection);
        if (!client) {
          return null;
        }
        const localTimestamp = await client.getLastModified();
        const lastCheck = Services.prefs.getIntPref(
          client.lastCheckTimePref,
          0
        );
        return {
          bucket,
          collection,
          localTimestamp,
          serverTimestamp,
          lastCheck,
          signerName: client.signerName,
        };
      })
    );

    return {
      serverURL: lazy.Utils.SERVER_URL,
      pollingEndpoint: lazy.Utils.SERVER_URL + lazy.Utils.CHANGES_PATH,
      serverTimestamp,
      localTimestamp: lazy.gPrefs.getStringPref(PREF_SETTINGS_LAST_ETAG, null),
      lastCheck: lazy.gPrefs.getIntPref(PREF_SETTINGS_LAST_UPDATE, 0),
      mainBucket: lazy.Utils.actualBucketName(
        AppConstants.REMOTE_SETTINGS_DEFAULT_BUCKET
      ),
      defaultSigner: DEFAULT_SIGNER,
      previewMode: lazy.Utils.PREVIEW_MODE,
      collections: collections.filter(c => !!c),
      history: {
        [TELEMETRY_SOURCE_SYNC]: await lazy.gSyncHistory.list(),
      },
      isSynchronizationBroken: await isSynchronizationBroken(),
    };
  };

  /**
   * Delete all local data, of every collection.
   */
  remoteSettings.clearAll = async () => {
    const { collections } = await remoteSettings.inspect();
    await Promise.all(
      collections.map(async ({ collection }) => {
        const client = RemoteSettings(collection);
        // Delete all potential attachments.
        await client.attachments.deleteAll();
        // Delete local data.
        await client.db.clear();
        // Remove status pref.
        Services.prefs.clearUserPref(client.lastCheckTimePref);
      })
    );
  };

  /**
   * Startup function called from nsBrowserGlue.
   */
  remoteSettings.init = () => {
    lazy.console.info("Initialize Remote Settings");
    // Hook the Push broadcast and RemoteSettings polling.
    // When we start on a new profile there will be no ETag stored.
    // Use an arbitrary ETag that is guaranteed not to occur.
    // This will trigger a broadcast message but that's fine because we
    // will check the changes on each collection and retrieve only the
    // changes (e.g. nothing if we have a dump with the same data).
    const currentVersion = lazy.gPrefs.getStringPref(
      PREF_SETTINGS_LAST_ETAG,
      '"0"'
    );

    const moduleInfo = {
      moduleURI: import.meta.url,
      symbolName: "remoteSettingsBroadcastHandler",
    };
    lazy.pushBroadcastService.addListener(
      BROADCAST_ID,
      currentVersion,
      moduleInfo
    );
  };

  return remoteSettings;
}

export var RemoteSettings = remoteSettingsFunction();

export var remoteSettingsBroadcastHandler = {
  async receivedBroadcastMessage(version, broadcastID, context) {
    const { phase } = context;
    const isStartup = [
      lazy.pushBroadcastService.PHASES.HELLO,
      lazy.pushBroadcastService.PHASES.REGISTER,
    ].includes(phase);

    lazy.console.info(
      `Push notification received (version=${version} phase=${phase})`
    );

    return RemoteSettings.pollChanges({
      expectedTimestamp: version,
      trigger: isStartup ? "startup" : "broadcast",
    });
  },
};
PK
!<�Q��8�8(modules/services-sync/SyncedTabs.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  CLIENT_NOT_CONFIGURED: "resource://services-sync/constants.sys.mjs",
  Weave: "resource://services-sync/main.sys.mjs",
  getRemoteCommandStore: "resource://services-sync/TabsStore.sys.mjs",
  RemoteCommand: "resource://services-sync/TabsStore.sys.mjs",
  FxAccounts: "resource://gre/modules/FxAccounts.sys.mjs",
});

// The Sync XPCOM service
ChromeUtils.defineLazyGetter(lazy, "weaveXPCService", function () {
  return Cc["@mozilla.org/weave/service;1"].getService(
    Ci.nsISupports
  ).wrappedJSObject;
});

// from MDN...
function escapeRegExp(string) {
  return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

// A topic we fire whenever we have new tabs available. This might be due
// to a request made by this module to refresh the tab list, or as the result
// of a regularly scheduled sync. The intent is that consumers just listen
// for this notification and update their UI in response.
const TOPIC_TABS_CHANGED = "services.sync.tabs.changed";

// A topic we fire whenever we have queued a new remote tabs command.
const TOPIC_TABS_COMMAND_QUEUED = "services.sync.tabs.command-queued";

// The interval, in seconds, before which we consider the existing list
// of tabs "fresh enough" and don't force a new sync.
const TABS_FRESH_ENOUGH_INTERVAL_SECONDS = 30;

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  const { Log } = ChromeUtils.importESModule(
    "resource://gre/modules/Log.sys.mjs"
  );
  let log = Log.repository.getLogger("Sync.RemoteTabs");
  log.manageLevelFromPref("services.sync.log.logger.tabs");
  return log;
});

// We allow some test preferences to simulate many and inactive tabs.
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "NUM_FAKE_INACTIVE_TABS",
  "services.sync.syncedTabs.numFakeInactiveTabs",
  0
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "NUM_FAKE_ACTIVE_TABS",
  "services.sync.syncedTabs.numFakeActiveTabs",
  0
);

// A private singleton that does the work.
let SyncedTabsInternal = {
  /* Make a "tab" record. Returns a promise */
  async _makeTab(client, tab, url, showRemoteIcons) {
    let icon;
    if (showRemoteIcons) {
      icon = tab.icon;
    }
    if (!icon) {
      // By not specifying a size the favicon service will pick the default,
      // that is usually set through setDefaultIconURIPreferredSize by the
      // first browser window. Commonly it's 16px at current dpi.
      icon = "page-icon:" + url;
    }
    return {
      type: "tab",
      title: tab.title || url,
      url,
      icon,
      client: client.id,
      lastUsed: tab.lastUsed,
      inactive: tab.inactive,
    };
  },

  /* Make a "client" record. Returns a promise for consistency with _makeTab */
  async _makeClient(client) {
    return {
      id: client.id,
      type: "client",
      name: lazy.Weave.Service.clientsEngine.getClientName(client.id),
      clientType: lazy.Weave.Service.clientsEngine.getClientType(client.id),
      lastModified: client.lastModified * 1000, // sec to ms
      tabs: [],
    };
  },

  _tabMatchesFilter(tab, filter) {
    let reFilter = new RegExp(escapeRegExp(filter), "i");
    return reFilter.test(tab.url) || reFilter.test(tab.title);
  },

  _createRecentTabsList(
    clients,
    maxCount,
    extraParams = { removeAllDupes: true, removeDeviceDupes: false }
  ) {
    let tabs = [];

    for (let client of clients) {
      if (extraParams.removeDeviceDupes) {
        client.tabs = this._filterRecentTabsDupes(client.tabs);
      }
      for (let tab of client.tabs) {
        tab.device = client.name;
        tab.deviceType = client.clientType;
      }
      tabs = [...tabs, ...client.tabs.reverse()];
    }
    if (extraParams.removeAllDupes) {
      tabs = this._filterRecentTabsDupes(tabs);
    }
    tabs = tabs.sort((a, b) => b.lastUsed - a.lastUsed).slice(0, maxCount);
    return tabs;
  },

  // Filter out any tabs with duplicate URLs preserving
  // the duplicate with the most recent lastUsed value
  _filterRecentTabsDupes(tabs) {
    const tabMap = new Map();
    for (const tab of tabs) {
      const existingTab = tabMap.get(tab.url);
      if (!existingTab || tab.lastUsed > existingTab.lastUsed) {
        tabMap.set(tab.url, tab);
      }
    }
    return Array.from(tabMap.values());
  },

  async getTabClients(filter) {
    lazy.log.info("Generating tab list with filter", filter);
    let result = [];

    // If Sync isn't ready, don't try and get anything.
    if (!lazy.weaveXPCService.ready) {
      lazy.log.debug("Sync isn't yet ready, so returning an empty tab list");
      return result;
    }

    // A boolean that controls whether we should show the icon from the remote tab.
    const showRemoteIcons = Services.prefs.getBoolPref(
      "services.sync.syncedTabs.showRemoteIcons",
      true
    );

    let engine = lazy.Weave.Service.engineManager.get("tabs");

    let ntabs = 0;
    let clientTabList = await engine.getAllClients();
    for (let client of clientTabList) {
      if (!lazy.Weave.Service.clientsEngine.remoteClientExists(client.id)) {
        continue;
      }
      let clientRepr = await this._makeClient(client);
      lazy.log.debug("Processing client", clientRepr);

      let tabs = Array.from(client.tabs); // avoid modifying in-place.
      // For QA, UX, etc, we allow "fake tabs" to be added to each device.
      for (let i = 0; i < lazy.NUM_FAKE_INACTIVE_TABS; i++) {
        tabs.push({
          icon: null,
          lastUsed: 1000,
          title: `Fake inactive tab ${i}`,
          urlHistory: [`https://example.com/inactive/${i}`],
          inactive: true,
        });
      }
      for (let i = 0; i < lazy.NUM_FAKE_ACTIVE_TABS; i++) {
        tabs.push({
          icon: null,
          lastUsed: Date.now() - 1000 + i,
          title: `Fake tab ${i}`,
          urlHistory: [`https://example.com/${i}`],
        });
      }

      for (let tab of tabs) {
        let url = tab.urlHistory[0];
        lazy.log.trace("remote tab", url);

        if (!url) {
          continue;
        }
        let tabRepr = await this._makeTab(client, tab, url, showRemoteIcons);
        if (filter && !this._tabMatchesFilter(tabRepr, filter)) {
          continue;
        }
        clientRepr.tabs.push(tabRepr);
      }

      // Filter out duplicate tabs based on URL
      clientRepr.tabs = this._filterRecentTabsDupes(clientRepr.tabs);

      // We return all clients, even those without tabs - the consumer should
      // filter it if they care.
      ntabs += clientRepr.tabs.length;
      result.push(clientRepr);
    }
    lazy.log.info(
      `Final tab list has ${result.length} clients with ${ntabs} tabs.`
    );
    return result;
  },

  async syncTabs(force) {
    if (!force) {
      // Don't bother refetching tabs if we already did so recently
      let lastFetch = Services.prefs.getIntPref(
        "services.sync.lastTabFetch",
        0
      );
      let now = Math.floor(Date.now() / 1000);
      if (now - lastFetch < TABS_FRESH_ENOUGH_INTERVAL_SECONDS) {
        lazy.log.info("_refetchTabs was done recently, do not doing it again");
        return false;
      }
    }

    // If Sync isn't configured don't try and sync, else we will get reports
    // of a login failure.
    if (lazy.Weave.Status.checkSetup() === lazy.CLIENT_NOT_CONFIGURED) {
      lazy.log.info(
        "Sync client is not configured, so not attempting a tab sync"
      );
      return false;
    }
    // If the primary pass is locked, we should not try to sync
    if (lazy.Weave.Utils.mpLocked()) {
      lazy.log.info(
        "Can't sync tabs due to the primary password being locked",
        lazy.Weave.Status.login
      );
      return false;
    }
    // Ask Sync to just do the tabs engine if it can.
    try {
      lazy.log.info("Doing a tab sync.");
      await lazy.Weave.Service.sync({ why: "tabs", engines: ["tabs"] });
      return true;
    } catch (ex) {
      lazy.log.error("Sync failed", ex);
      throw ex;
    }
  },

  observe(subject, topic, data) {
    lazy.log.trace(`observed topic=${topic}, data=${data}, subject=${subject}`);
    switch (topic) {
      case "weave:engine:sync:finish":
        if (data != "tabs") {
          return;
        }
        // The tabs engine just finished syncing
        // Set our lastTabFetch pref here so it tracks both explicit sync calls
        // and normally scheduled ones.
        Services.prefs.setIntPref(
          "services.sync.lastTabFetch",
          Math.floor(Date.now() / 1000)
        );
        Services.obs.notifyObservers(null, TOPIC_TABS_CHANGED);
        break;
      case "weave:service:start-over":
        // start-over needs to notify so consumers find no tabs.
        Services.prefs.clearUserPref("services.sync.lastTabFetch");
        Services.obs.notifyObservers(null, TOPIC_TABS_CHANGED);
        break;
      case "nsPref:changed":
        Services.obs.notifyObservers(null, TOPIC_TABS_CHANGED);
        break;
      default:
        break;
    }
  },

  // Returns true if Sync is configured to Sync tabs, false otherwise
  get isConfiguredToSyncTabs() {
    if (!lazy.weaveXPCService.ready) {
      lazy.log.debug("Sync isn't yet ready; assuming tab engine is enabled");
      return true;
    }

    let engine = lazy.Weave.Service.engineManager.get("tabs");
    return engine && engine.enabled;
  },

  get hasSyncedThisSession() {
    let engine = lazy.Weave.Service.engineManager.get("tabs");
    return engine && engine.hasSyncedThisSession;
  },
};

Services.obs.addObserver(SyncedTabsInternal, "weave:engine:sync:finish");
Services.obs.addObserver(SyncedTabsInternal, "weave:service:start-over");
// Observe the pref the indicates the state of the tabs engine has changed.
// This will force consumers to re-evaluate the state of sync and update
// accordingly.
Services.prefs.addObserver("services.sync.engine.tabs", SyncedTabsInternal);

// The public interface.
export var SyncedTabs = {
  // A mock-point for tests.
  _internal: SyncedTabsInternal,

  // We make the topic for the observer notification public.
  TOPIC_TABS_CHANGED,

  // Expose the interval used to determine if synced tabs data needs a new sync
  TABS_FRESH_ENOUGH_INTERVAL_SECONDS,

  // Returns true if Sync is configured to Sync tabs, false otherwise
  get isConfiguredToSyncTabs() {
    return this._internal.isConfiguredToSyncTabs;
  },

  // Returns true if a tab sync has completed once this session. If this
  // returns false, then getting back no clients/tabs possibly just means we
  // are waiting for that first sync to complete.
  get hasSyncedThisSession() {
    return this._internal.hasSyncedThisSession;
  },

  // Return a promise that resolves with an array of client records, each with
  // a .tabs array. Note that part of the contract for this module is that the
  // returned objects are not shared between invocations, so callers are free
  // to mutate the returned objects (eg, sort, truncate) however they see fit.
  getTabClients(query) {
    return this._internal.getTabClients(query);
  },

  // Starts a background request to start syncing tabs. Returns a promise that
  // resolves when the sync is complete, but there's no resolved value -
  // callers should be listening for TOPIC_TABS_CHANGED.
  // If |force| is true we always sync. If false, we only sync if the most
  // recent sync wasn't "recently".
  syncTabs(force) {
    return this._internal.syncTabs(force);
  },

  createRecentTabsList(clients, maxCount, extraParams) {
    return this._internal._createRecentTabsList(clients, maxCount, extraParams);
  },

  sortTabClientsByLastUsed(clients) {
    // First sort the list of tabs for each client. Note that
    // this module promises that the objects it returns are never
    // shared, so we are free to mutate those objects directly.
    for (let client of clients) {
      let tabs = client.tabs;
      tabs.sort((a, b) => b.lastUsed - a.lastUsed);
    }
    // Now sort the clients - the clients are sorted in the order of the
    // most recent tab for that client (ie, it is important the tabs for
    // each client are already sorted.)
    clients.sort((a, b) => {
      if (!a.tabs.length) {
        return 1; // b comes first.
      }
      if (!b.tabs.length) {
        return -1; // a comes first.
      }
      return b.tabs[0].lastUsed - a.tabs[0].lastUsed;
    });
  },

  recordSyncedTabsTelemetry(object, tabEvent, extraOptions) {
    Services.telemetry.setEventRecordingEnabled("synced_tabs", true);
    Services.telemetry.recordEvent(
      "synced_tabs",
      tabEvent,
      object,
      null,
      extraOptions
    );
  },

  // Get list of synced tabs across all devices/clients
  // truncated by value of maxCount param, sorted by
  // lastUsed value, and filtered for duplicate URLs
  async getRecentTabs(maxCount, extraParams) {
    let clients = await this.getTabClients();
    return this._internal._createRecentTabsList(clients, maxCount, extraParams);
  },
};

// Remote tab management public interface.
export var SyncedTabsManagement = {
  // A mock-point for tests.
  async _getStore() {
    return await lazy.getRemoteCommandStore();
  },

  /// Enqueue a tab to close on a remote device.
  async enqueueTabToClose(deviceId, url) {
    let store = await this._getStore();
    let command = new lazy.RemoteCommand.CloseTab(url);
    if (!store.addRemoteCommand(deviceId, command)) {
      lazy.log.warn(
        "Could not queue a remote tab close - it was already queued"
      );
    } else {
      lazy.log.info("Queued remote tab close command.");
    }
    // fxAccounts commands infrastructure is lazily initialized, at which point
    // it registers observers etc - make sure it's initialized;
    lazy.FxAccounts.commands;
    Services.obs.notifyObservers(null, TOPIC_TABS_COMMAND_QUEUED);
  },

  /// Remove a tab from the queue of commands for a remote device.
  async removePendingTabToClose(deviceId, url) {
    let store = await this._getStore();
    let command = new lazy.RemoteCommand.CloseTab(url);
    if (!store.removeRemoteCommand(deviceId, command)) {
      lazy.log.warn("Could not remove a remote tab close - it was not queued");
    } else {
      lazy.log.info("Removed queued remote tab close command.");
    }
  },
};
PK
!<�K-L]]'modules/services-sync/TabsStore.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { TabsStore } from "resource://gre/modules/RustTabs.sys.mjs";

var storePromise = null;

export async function getTabsStore() {
  if (storePromise == null) {
    const path = PathUtils.join(PathUtils.profileDir, "synced-tabs.db");
    storePromise = TabsStore.init(path);
  }
  return await storePromise;
}

export async function getRemoteCommandStore() {
  const store = await getTabsStore();
  // creating a new remote command store is cheap (but not free, so maybe we should cache this in the future?)
  return await store.newRemoteCommandStore();
}

export {
  RemoteCommand,
  PendingCommand,
} from "resource://gre/modules/RustTabs.sys.mjs";
PK
!<��Q�� � %modules/services-sync/UIState.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * @typedef {Object} UIState
 * @property {string} status The Sync/FxA status, see STATUS_* constants.
 * @property {string} [email] The FxA email configured to log-in with Sync.
 * @property {string} [displayName] The user's FxA display name.
 * @property {string} [avatarURL] The user's FxA avatar URL.
 * @property {Date} [lastSync] The last sync time.
 * @property {boolean} [syncing] Whether or not we are currently syncing.
 */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  LOGIN_FAILED_LOGIN_REJECTED: "resource://services-sync/constants.sys.mjs",
  Weave: "resource://services-sync/main.sys.mjs",
});

const TOPICS = [
  "weave:connected",
  "weave:service:login:got-hashed-id",
  "weave:service:login:error",
  "weave:service:ready",
  "weave:service:sync:start",
  "weave:service:sync:finish",
  "weave:service:sync:error",
  "weave:service:start-over:finish",
  "fxaccounts:onverified",
  "fxaccounts:onlogin", // Defined in FxAccountsCommon, pulling it is expensive.
  "fxaccounts:onlogout",
  "fxaccounts:profilechange",
  "fxaccounts:statechange",
];

const ON_UPDATE = "sync-ui-state:update";

const STATUS_NOT_CONFIGURED = "not_configured";
const STATUS_LOGIN_FAILED = "login_failed";
const STATUS_NOT_VERIFIED = "not_verified";
const STATUS_SIGNED_IN = "signed_in";

const DEFAULT_STATE = {
  status: STATUS_NOT_CONFIGURED,
};

const UIStateInternal = {
  _initialized: false,
  _state: null,

  // We keep _syncing out of the state object because we can only track it
  // using sync events and we can't determine it at any point in time.
  _syncing: false,

  get state() {
    if (!this._state) {
      return DEFAULT_STATE;
    }
    return Object.assign({}, this._state, { syncing: this._syncing });
  },

  isReady() {
    if (!this._initialized) {
      this.init();
      return false;
    }
    return true;
  },

  init() {
    this._initialized = true;
    // Because the FxA toolbar is usually visible, this module gets loaded at
    // browser startup, and we want to avoid pulling in all of FxA or Sync at
    // that time, so we refresh the state after the browser has settled.
    Services.tm.idleDispatchToMainThread(() => {
      this.refreshState().catch(e => {
        console.error(e);
      });
    }, 2000);
  },

  // Used for testing.
  reset() {
    this._state = null;
    this._syncing = false;
    this._initialized = false;
  },

  observe(subject, topic) {
    switch (topic) {
      case "weave:service:sync:start":
        this.toggleSyncActivity(true);
        break;
      case "weave:service:sync:finish":
      case "weave:service:sync:error":
        this.toggleSyncActivity(false);
        break;
      default:
        this.refreshState().catch(e => {
          console.error(e);
        });
        break;
    }
  },

  // Builds a new state from scratch.
  async refreshState() {
    const newState = {};
    await this._refreshFxAState(newState);
    // Optimize the "not signed in" case to avoid refreshing twice just after
    // startup - if there's currently no _state, and we still aren't configured,
    // just early exit.
    if (this._state == null && newState.status == DEFAULT_STATE.status) {
      return this.state;
    }
    if (newState.syncEnabled) {
      this._setLastSyncTime(newState); // We want this in case we change accounts.
    }
    this._state = newState;

    this.notifyStateUpdated();
    return this.state;
  },

  // Update the current state with the last sync time/currently syncing status.
  toggleSyncActivity(syncing) {
    this._syncing = syncing;
    this._setLastSyncTime(this._state);

    this.notifyStateUpdated();
  },

  notifyStateUpdated() {
    Services.obs.notifyObservers(null, ON_UPDATE);
  },

  async _refreshFxAState(newState) {
    let userData = await this._getUserData();
    await this._populateWithUserData(newState, userData);
  },

  async _populateWithUserData(state, userData) {
    let status;
    let syncUserName = Services.prefs.getStringPref(
      "services.sync.username",
      ""
    );
    if (!userData) {
      // If Sync thinks it is configured but there's no FxA user, then we
      // want to enter the "login failed" state so the user can get
      // reconfigured.
      if (syncUserName) {
        state.email = syncUserName;
        status = STATUS_LOGIN_FAILED;
      } else {
        // everyone agrees nothing is configured.
        status = STATUS_NOT_CONFIGURED;
      }
    } else {
      let loginFailed = await this._loginFailed();
      if (loginFailed) {
        status = STATUS_LOGIN_FAILED;
      } else if (!userData.verified) {
        status = STATUS_NOT_VERIFIED;
      } else {
        status = STATUS_SIGNED_IN;
      }
      state.uid = userData.uid;
      state.email = userData.email;
      state.displayName = userData.displayName;
      // for better or worse, this module renames these attribues.
      state.avatarURL = userData.avatar;
      state.avatarIsDefault = userData.avatarDefault;
      state.syncEnabled = !!syncUserName;
    }
    state.status = status;
  },

  async _getUserData() {
    try {
      return await this.fxAccounts.getSignedInUser();
    } catch (e) {
      // This is most likely in tests, where we quickly log users in and out.
      // The most likely scenario is a user logged out, so reflect that.
      // Bug 995134 calls for better errors so we could retry if we were
      // sure this was the failure reason.
      console.error("Error updating FxA account info:", e);
      return null;
    }
  },

  _setLastSyncTime(state) {
    if (state?.status == UIState.STATUS_SIGNED_IN) {
      const lastSync = Services.prefs.getStringPref(
        "services.sync.lastSync",
        null
      );
      state.lastSync = lastSync ? new Date(lastSync) : null;
    }
  },

  async _loginFailed() {
    // First ask FxA if it thinks the user needs re-authentication. In practice,
    // this check is probably canonical (ie, we probably don't really need
    // the check below at all as we drop local session info on the first sign
    // of a problem) - but we keep it for now to keep the risk down.
    let hasLocalSession = await this.fxAccounts.hasLocalSession();
    if (!hasLocalSession) {
      return true;
    }

    // Referencing Weave.Service will implicitly initialize sync, and we don't
    // want to force that - so first check if it is ready.
    let service = Cc["@mozilla.org/weave/service;1"].getService(
      Ci.nsISupports
    ).wrappedJSObject;
    if (!service.ready) {
      return false;
    }
    // LOGIN_FAILED_LOGIN_REJECTED explicitly means "you must log back in".
    // All other login failures are assumed to be transient and should go
    // away by themselves, so aren't reflected here.
    return lazy.Weave.Status.login == lazy.LOGIN_FAILED_LOGIN_REJECTED;
  },

  set fxAccounts(mockFxAccounts) {
    delete this.fxAccounts;
    this.fxAccounts = mockFxAccounts;
  },
};

ChromeUtils.defineLazyGetter(UIStateInternal, "fxAccounts", () => {
  return ChromeUtils.importESModule(
    "resource://gre/modules/FxAccounts.sys.mjs"
  ).getFxAccountsSingleton();
});

for (let topic of TOPICS) {
  Services.obs.addObserver(UIStateInternal, topic);
}

export var UIState = {
  _internal: UIStateInternal,

  ON_UPDATE,

  STATUS_NOT_CONFIGURED,
  STATUS_LOGIN_FAILED,
  STATUS_NOT_VERIFIED,
  STATUS_SIGNED_IN,

  /**
   * Returns true if the module has been initialized and the state set.
   * If not, return false and trigger an init in the background.
   */
  isReady() {
    return this._internal.isReady();
  },

  /**
   * @returns {UIState} The current Sync/FxA UI State.
   */
  get() {
    return this._internal.state;
  },

  /**
   * Refresh the state. Used for testing, don't call this directly since
   * UIState already listens to Sync/FxA notifications to determine if the state
   * needs to be refreshed. ON_UPDATE will be fired once the state is refreshed.
   *
   * @returns {Promise<UIState>} Resolved once the state is refreshed.
   */
  refresh() {
    return this._internal.refreshState();
  },

  /**
   * Reset the state of the whole module. Used for testing.
   */
  reset() {
    this._internal.reset();
  },
};
PK
!<�G�:NDND.modules/services-sync/addonsreconciler.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file contains middleware to reconcile state of AddonManager for
 * purposes of tracking events for Sync. The content in this file exists
 * because AddonManager does not have a getChangesSinceX() API and adding
 * that functionality properly was deemed too time-consuming at the time
 * add-on sync was originally written. If/when AddonManager adds this API,
 * this file can go away and the add-ons engine can be rewritten to use it.
 *
 * It was decided to have this tracking functionality exist in a separate
 * standalone file so it could be more easily understood, tested, and
 * hopefully ported.
 */

import { Log } from "resource://gre/modules/Log.sys.mjs";

import { Svc, Utils } from "resource://services-sync/util.sys.mjs";

import { AddonManager } from "resource://gre/modules/AddonManager.sys.mjs";

const DEFAULT_STATE_FILE = "addonsreconciler";

export var CHANGE_INSTALLED = 1;
export var CHANGE_UNINSTALLED = 2;
export var CHANGE_ENABLED = 3;
export var CHANGE_DISABLED = 4;

/**
 * Maintains state of add-ons.
 *
 * State is maintained in 2 data structures, an object mapping add-on IDs
 * to metadata and an array of changes over time. The object mapping can be
 * thought of as a minimal copy of data from AddonManager which is needed for
 * Sync. The array is effectively a log of changes over time.
 *
 * The data structures are persisted to disk by serializing to a JSON file in
 * the current profile. The data structures are updated by 2 mechanisms. First,
 * they can be refreshed from the global state of the AddonManager. This is a
 * sure-fire way of ensuring the reconciler is up to date. Second, the
 * reconciler adds itself as an AddonManager listener. When it receives change
 * notifications, it updates its internal state incrementally.
 *
 * The internal state is persisted to a JSON file in the profile directory.
 *
 * An instance of this is bound to an AddonsEngine instance. In reality, it
 * likely exists as a singleton. To AddonsEngine, it functions as a store and
 * an entity which emits events for tracking.
 *
 * The usage pattern for instances of this class is:
 *
 *   let reconciler = new AddonsReconciler(...);
 *   await reconciler.ensureStateLoaded();
 *
 *   // At this point, your instance should be ready to use.
 *
 * When you are finished with the instance, please call:
 *
 *   reconciler.stopListening();
 *   await reconciler.saveState(...);
 *
 * This class uses the AddonManager AddonListener interface.
 * When an add-on is installed, listeners are called in the following order:
 *  AL.onInstalling, AL.onInstalled
 *
 * For uninstalls, we see AL.onUninstalling then AL.onUninstalled.
 *
 * Enabling and disabling work by sending:
 *
 *   AL.onEnabling, AL.onEnabled
 *   AL.onDisabling, AL.onDisabled
 *
 * Actions can be undone. All undoable actions notify the same
 * AL.onOperationCancelled event. We treat this event like any other.
 *
 * When an add-on is uninstalled from about:addons, the user is offered an
 * "Undo" option, which leads to the following sequence of events as
 * observed by an AddonListener:
 * Add-ons are first disabled then they are actually uninstalled. So, we will
 * see AL.onDisabling and AL.onDisabled. The onUninstalling and onUninstalled
 * events only come after the Addon Manager is closed or another view is
 * switched to. In the case of Sync performing the uninstall, the uninstall
 * events will occur immediately. However, we still see disabling events and
 * heed them like they were normal. In the end, the state is proper.
 */
export function AddonsReconciler(queueCaller) {
  this._log = Log.repository.getLogger("Sync.AddonsReconciler");
  this._log.manageLevelFromPref("services.sync.log.logger.addonsreconciler");
  this.queueCaller = queueCaller;

  Svc.Obs.add("xpcom-shutdown", this.stopListening, this);
}

AddonsReconciler.prototype = {
  /** Flag indicating whether we are listening to AddonManager events. */
  _listening: false,

  /**
   * Define this as false if the reconciler should not persist state
   * to disk when handling events.
   *
   * This allows test code to avoid spinning to write during observer
   * notifications and xpcom shutdown, which appears to cause hangs on WinXP
   * (Bug 873861).
   */
  _shouldPersist: true,

  /** Log logger instance */
  _log: null,

  /**
   * Container for add-on metadata.
   *
   * Keys are add-on IDs. Values are objects which describe the state of the
   * add-on. This is a minimal mirror of data that can be queried from
   * AddonManager. In some cases, we retain data longer than AddonManager.
   */
  _addons: {},

  /**
   * List of add-on changes over time.
   *
   * Each element is an array of [time, change, id].
   */
  _changes: [],

  /**
   * Objects subscribed to changes made to this instance.
   */
  _listeners: [],

  /**
   * Accessor for add-ons in this object.
   *
   * Returns an object mapping add-on IDs to objects containing metadata.
   */
  get addons() {
    return this._addons;
  },

  async ensureStateLoaded() {
    if (!this._promiseStateLoaded) {
      this._promiseStateLoaded = this.loadState();
    }
    return this._promiseStateLoaded;
  },

  /**
   * Load reconciler state from a file.
   *
   * The path is relative to the weave directory in the profile. If no
   * path is given, the default one is used.
   *
   * If the file does not exist or there was an error parsing the file, the
   * state will be transparently defined as empty.
   *
   * @param file
   *        Path to load. ".json" is appended automatically. If not defined,
   *        a default path will be consulted.
   */
  async loadState(file = DEFAULT_STATE_FILE) {
    let json = await Utils.jsonLoad(file, this);
    this._addons = {};
    this._changes = [];

    if (!json) {
      this._log.debug("No data seen in loaded file: " + file);
      return false;
    }

    let version = json.version;
    if (!version || version != 1) {
      this._log.error(
        "Could not load JSON file because version not " +
          "supported: " +
          version
      );
      return false;
    }

    this._addons = json.addons;
    for (let id in this._addons) {
      let record = this._addons[id];
      record.modified = new Date(record.modified);
    }

    for (let [time, change, id] of json.changes) {
      this._changes.push([new Date(time), change, id]);
    }

    return true;
  },

  /**
   * Saves the current state to a file in the local profile.
   *
   * @param  file
   *         String path in profile to save to. If not defined, the default
   *         will be used.
   */
  async saveState(file = DEFAULT_STATE_FILE) {
    let state = { version: 1, addons: {}, changes: [] };

    for (let [id, record] of Object.entries(this._addons)) {
      state.addons[id] = {};
      for (let [k, v] of Object.entries(record)) {
        if (k == "modified") {
          state.addons[id][k] = v.getTime();
        } else {
          state.addons[id][k] = v;
        }
      }
    }

    for (let [time, change, id] of this._changes) {
      state.changes.push([time.getTime(), change, id]);
    }

    this._log.info("Saving reconciler state to file: " + file);
    await Utils.jsonSave(file, this, state);
  },

  /**
   * Registers a change listener with this instance.
   *
   * Change listeners are called every time a change is recorded. The listener
   * is an object with the function "changeListener" that takes 3 arguments,
   * the Date at which the change happened, the type of change (a CHANGE_*
   * constant), and the add-on state object reflecting the current state of
   * the add-on at the time of the change.
   *
   * @param listener
   *        Object containing changeListener function.
   */
  addChangeListener: function addChangeListener(listener) {
    if (!this._listeners.includes(listener)) {
      this._log.debug("Adding change listener.");
      this._listeners.push(listener);
    }
  },

  /**
   * Removes a previously-installed change listener from the instance.
   *
   * @param listener
   *        Listener instance to remove.
   */
  removeChangeListener: function removeChangeListener(listener) {
    this._listeners = this._listeners.filter(element => {
      if (element == listener) {
        this._log.debug("Removing change listener.");
        return false;
      }
      return true;
    });
  },

  /**
   * Tells the instance to start listening for AddonManager changes.
   *
   * This is typically called automatically when Sync is loaded.
   */
  startListening: function startListening() {
    if (this._listening) {
      return;
    }

    this._log.info("Registering as Add-on Manager listener.");
    AddonManager.addAddonListener(this);
    this._listening = true;
  },

  /**
   * Tells the instance to stop listening for AddonManager changes.
   *
   * The reconciler should always be listening. This should only be called when
   * the instance is being destroyed.
   *
   * This function will get called automatically on XPCOM shutdown. However, it
   * is a best practice to call it yourself.
   */
  stopListening: function stopListening() {
    if (!this._listening) {
      return;
    }

    this._log.debug("Stopping listening and removing AddonManager listener.");
    AddonManager.removeAddonListener(this);
    this._listening = false;
  },

  /**
   * Refreshes the global state of add-ons by querying the AddonManager.
   */
  async refreshGlobalState() {
    this._log.info("Refreshing global state from AddonManager.");

    let installs;
    let addons = await AddonManager.getAllAddons();

    let ids = {};

    for (let addon of addons) {
      ids[addon.id] = true;
      await this.rectifyStateFromAddon(addon);
    }

    // Look for locally-defined add-ons that no longer exist and update their
    // record.
    for (let [id, addon] of Object.entries(this._addons)) {
      if (id in ids) {
        continue;
      }

      // If the id isn't in ids, it means that the add-on has been deleted or
      // the add-on is in the process of being installed. We detect the
      // latter by seeing if an AddonInstall is found for this add-on.

      if (!installs) {
        installs = await AddonManager.getAllInstalls();
      }

      let installFound = false;
      for (let install of installs) {
        if (
          install.addon &&
          install.addon.id == id &&
          install.state == AddonManager.STATE_INSTALLED
        ) {
          installFound = true;
          break;
        }
      }

      if (installFound) {
        continue;
      }

      if (addon.installed) {
        addon.installed = false;
        this._log.debug(
          "Adding change because add-on not present in " +
            "Add-on Manager: " +
            id
        );
        await this._addChange(new Date(), CHANGE_UNINSTALLED, addon);
      }
    }

    // See note for _shouldPersist.
    if (this._shouldPersist) {
      await this.saveState();
    }
  },

  /**
   * Rectifies the state of an add-on from an Addon instance.
   *
   * This basically says "given an Addon instance, assume it is truth and
   * apply changes to the local state to reflect it."
   *
   * This function could result in change listeners being called if the local
   * state differs from the passed add-on's state.
   *
   * @param addon
   *        Addon instance being updated.
   */
  async rectifyStateFromAddon(addon) {
    this._log.debug(
      `Rectifying state for addon ${addon.name} (version=${addon.version}, id=${addon.id})`
    );

    let id = addon.id;
    let enabled = !addon.userDisabled;
    let guid = addon.syncGUID;
    let now = new Date();

    if (!(id in this._addons)) {
      let record = {
        id,
        guid,
        enabled,
        installed: true,
        modified: now,
        type: addon.type,
        scope: addon.scope,
        foreignInstall: addon.foreignInstall,
        isSyncable: addon.isSyncable,
      };
      this._addons[id] = record;
      this._log.debug(
        "Adding change because add-on not present locally: " + id
      );
      await this._addChange(now, CHANGE_INSTALLED, record);
      return;
    }

    let record = this._addons[id];
    record.isSyncable = addon.isSyncable;

    if (!record.installed) {
      // It is possible the record is marked as uninstalled because an
      // uninstall is pending.
      if (!(addon.pendingOperations & AddonManager.PENDING_UNINSTALL)) {
        record.installed = true;
        record.modified = now;
      }
    }

    if (record.enabled != enabled) {
      record.enabled = enabled;
      record.modified = now;
      let change = enabled ? CHANGE_ENABLED : CHANGE_DISABLED;
      this._log.debug("Adding change because enabled state changed: " + id);
      await this._addChange(new Date(), change, record);
    }

    if (record.guid != guid) {
      record.guid = guid;
      // We don't record a change because the Sync engine rectifies this on its
      // own. This is tightly coupled with Sync. If this code is ever lifted
      // outside of Sync, this exception should likely be removed.
    }
  },

  /**
   * Record a change in add-on state.
   *
   * @param date
   *        Date at which the change occurred.
   * @param change
   *        The type of the change. A CHANGE_* constant.
   * @param state
   *        The new state of the add-on. From this.addons.
   */
  async _addChange(date, change, state) {
    this._log.info("Change recorded for " + state.id);
    this._changes.push([date, change, state.id]);

    for (let listener of this._listeners) {
      try {
        await listener.changeListener(date, change, state);
      } catch (ex) {
        this._log.error("Exception calling change listener", ex);
      }
    }
  },

  /**
   * Obtain the set of changes to add-ons since the date passed.
   *
   * This will return an array of arrays. Each entry in the array has the
   * elements [date, change_type, id], where
   *
   *   date - Date instance representing when the change occurred.
   *   change_type - One of CHANGE_* constants.
   *   id - ID of add-on that changed.
   */
  getChangesSinceDate(date) {
    let length = this._changes.length;
    for (let i = 0; i < length; i++) {
      if (this._changes[i][0] >= date) {
        return this._changes.slice(i);
      }
    }

    return [];
  },

  /**
   * Prunes all recorded changes from before the specified Date.
   *
   * @param date
   *        Entries older than this Date will be removed.
   */
  pruneChangesBeforeDate(date) {
    this._changes = this._changes.filter(function test_age(change) {
      return change[0] >= date;
    });
  },

  /**
   * Obtains the set of all known Sync GUIDs for add-ons.
   */
  getAllSyncGUIDs() {
    let result = {};
    for (let id in this.addons) {
      result[id] = true;
    }

    return result;
  },

  /**
   * Obtain the add-on state record for an add-on by Sync GUID.
   *
   * If the add-on could not be found, returns null.
   *
   * @param  guid
   *         Sync GUID of add-on to retrieve.
   */
  getAddonStateFromSyncGUID(guid) {
    for (let id in this.addons) {
      let addon = this.addons[id];
      if (addon.guid == guid) {
        return addon;
      }
    }

    return null;
  },

  /**
   * Handler that is invoked as part of the AddonManager listeners.
   */
  async _handleListener(action, addon) {
    // Since this is called as an observer, we explicitly trap errors and
    // log them to ourselves so we don't see errors reported elsewhere.
    try {
      let id = addon.id;
      this._log.debug("Add-on change: " + action + " to " + id);

      switch (action) {
        case "onEnabled":
        case "onDisabled":
        case "onInstalled":
        case "onInstallEnded":
        case "onOperationCancelled":
          await this.rectifyStateFromAddon(addon);
          break;

        case "onUninstalled":
          let id = addon.id;
          let addons = this.addons;
          if (id in addons) {
            let now = new Date();
            let record = addons[id];
            record.installed = false;
            record.modified = now;
            this._log.debug(
              "Adding change because of uninstall listener: " + id
            );
            await this._addChange(now, CHANGE_UNINSTALLED, record);
          }
      }

      // See note for _shouldPersist.
      if (this._shouldPersist) {
        await this.saveState();
      }
    } catch (ex) {
      this._log.warn("Exception", ex);
    }
  },

  // AddonListeners
  onEnabled: function onEnabled(addon) {
    this.queueCaller.enqueueCall(() =>
      this._handleListener("onEnabled", addon)
    );
  },
  onDisabled: function onDisabled(addon) {
    this.queueCaller.enqueueCall(() =>
      this._handleListener("onDisabled", addon)
    );
  },
  onInstalled: function onInstalled(addon) {
    this.queueCaller.enqueueCall(() =>
      this._handleListener("onInstalled", addon)
    );
  },
  onUninstalled: function onUninstalled(addon) {
    this.queueCaller.enqueueCall(() =>
      this._handleListener("onUninstalled", addon)
    );
  },
  onOperationCancelled: function onOperationCancelled(addon) {
    this.queueCaller.enqueueCall(() =>
      this._handleListener("onOperationCancelled", addon)
    );
  },
};
PK
!<:̧m/m/(modules/services-sync/addonutils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Log } from "resource://gre/modules/Log.sys.mjs";

import { Svc } from "resource://services-sync/util.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  AddonRepository: "resource://gre/modules/addons/AddonRepository.sys.mjs",
});

function AddonUtilsInternal() {
  this._log = Log.repository.getLogger("Sync.AddonUtils");
  this._log.Level =
    Log.Level[Svc.PrefBranch.getStringPref("log.logger.addonutils", null)];
}
AddonUtilsInternal.prototype = {
  /**
   * Obtain an AddonInstall object from an AddonSearchResult instance.
   *
   * The returned promise will be an AddonInstall on success or null (failure or
   * addon not found)
   *
   * @param addon
   *        AddonSearchResult to obtain install from.
   */
  getInstallFromSearchResult(addon) {
    this._log.debug("Obtaining install for " + addon.id);

    // We should theoretically be able to obtain (and use) addon.install if
    // it is available. However, the addon.sourceURI rewriting won't be
    // reflected in the AddonInstall, so we can't use it. If we ever get rid
    // of sourceURI rewriting, we can avoid having to reconstruct the
    // AddonInstall.
    return lazy.AddonManager.getInstallForURL(addon.sourceURI.spec, {
      name: addon.name,
      icons: addon.iconURL,
      version: addon.version,
      telemetryInfo: { source: "sync" },
    });
  },

  /**
   * Installs an add-on from an AddonSearchResult instance.
   *
   * The options argument defines extra options to control the install.
   * Recognized keys in this map are:
   *
   *   syncGUID - Sync GUID to use for the new add-on.
   *   enabled - Boolean indicating whether the add-on should be enabled upon
   *             install.
   *
   * The result object has the following keys:
   *
   *   id      ID of add-on that was installed.
   *   install AddonInstall that was installed.
   *   addon   Addon that was installed.
   *
   * @param addon
   *        AddonSearchResult to install add-on from.
   * @param options
   *        Object with additional metadata describing how to install add-on.
   */
  async installAddonFromSearchResult(addon, options) {
    this._log.info("Trying to install add-on from search result: " + addon.id);

    const install = await this.getInstallFromSearchResult(addon);
    if (!install) {
      throw new Error("AddonInstall not available: " + addon.id);
    }

    try {
      this._log.info("Installing " + addon.id);
      let log = this._log;

      return new Promise((res, rej) => {
        let listener = {
          onInstallStarted: function onInstallStarted(install) {
            if (!options) {
              return;
            }

            if (options.syncGUID) {
              log.info(
                "Setting syncGUID of " + install.name + ": " + options.syncGUID
              );
              install.addon.syncGUID = options.syncGUID;
            }

            // We only need to change userDisabled if it is disabled because
            // enabled is the default.
            if ("enabled" in options && !options.enabled) {
              log.info(
                "Marking add-on as disabled for install: " + install.name
              );
              install.addon.disable();
            }
          },
          onInstallEnded(install, addon) {
            install.removeListener(listener);

            res({ id: addon.id, install, addon });
          },
          onInstallFailed(install) {
            install.removeListener(listener);

            rej(new Error("Install failed: " + install.error));
          },
          onDownloadFailed(install) {
            install.removeListener(listener);

            rej(new Error("Download failed: " + install.error));
          },
        };
        install.addListener(listener);
        install.install();
      });
    } catch (ex) {
      this._log.error("Error installing add-on", ex);
      throw ex;
    }
  },

  /**
   * Uninstalls the addon instance.
   *
   * @param addon
   *        Addon instance to uninstall.
   */
  async uninstallAddon(addon) {
    return new Promise(res => {
      let listener = {
        onUninstalling(uninstalling, needsRestart) {
          if (addon.id != uninstalling.id) {
            return;
          }

          // We assume restartless add-ons will send the onUninstalled event
          // soon.
          if (!needsRestart) {
            return;
          }

          // For non-restartless add-ons, we issue the callback on uninstalling
          // because we will likely never see the uninstalled event.
          lazy.AddonManager.removeAddonListener(listener);
          res(addon);
        },
        onUninstalled(uninstalled) {
          if (addon.id != uninstalled.id) {
            return;
          }

          lazy.AddonManager.removeAddonListener(listener);
          res(addon);
        },
      };
      lazy.AddonManager.addAddonListener(listener);
      addon.uninstall();
    });
  },

  /**
   * Installs multiple add-ons specified by metadata.
   *
   * The first argument is an array of objects. Each object must have the
   * following keys:
   *
   *   id - public ID of the add-on to install.
   *   syncGUID - syncGUID for new add-on.
   *   enabled - boolean indicating whether the add-on should be enabled.
   *   requireSecureURI - Boolean indicating whether to require a secure
   *     URI when installing from a remote location. This defaults to
   *     true.
   *
   * The callback will be called when activity on all add-ons is complete. The
   * callback receives 2 arguments, error and result.
   *
   * If error is truthy, it contains a string describing the overall error.
   *
   * The 2nd argument to the callback is always an object with details on the
   * overall execution state. It contains the following keys:
   *
   *   installedIDs  Array of add-on IDs that were installed.
   *   installs      Array of AddonInstall instances that were installed.
   *   addons        Array of Addon instances that were installed.
   *   errors        Array of errors encountered. Only has elements if error is
   *                 truthy.
   *
   * @param installs
   *        Array of objects describing add-ons to install.
   */
  async installAddons(installs) {
    let ids = [];
    for (let addon of installs) {
      ids.push(addon.id);
    }

    let addons = await lazy.AddonRepository.getAddonsByIDs(ids);
    this._log.info(
      `Found ${addons.length} / ${ids.length}` +
        " add-ons during repository search."
    );

    let ourResult = {
      installedIDs: [],
      installs: [],
      addons: [],
      skipped: [],
      errors: [],
    };

    let toInstall = [];

    // Rewrite the "src" query string parameter of the source URI to note
    // that the add-on was installed by Sync and not something else so
    // server-side metrics aren't skewed (bug 708134). The server should
    // ideally send proper URLs, but this solution was deemed too
    // complicated at the time the functionality was implemented.
    for (let addon of addons) {
      // Find the specified options for this addon.
      let options;
      for (let install of installs) {
        if (install.id == addon.id) {
          options = install;
          break;
        }
      }
      if (!this.canInstallAddon(addon, options)) {
        ourResult.skipped.push(addon.id);
        continue;
      }

      // We can go ahead and attempt to install it.
      toInstall.push(addon);

      // We should always be able to QI the nsIURI to nsIURL. If not, we
      // still try to install the add-on, but we don't rewrite the URL,
      // potentially skewing metrics.
      try {
        addon.sourceURI.QueryInterface(Ci.nsIURL);
      } catch (ex) {
        this._log.warn(
          "Unable to QI sourceURI to nsIURL: " + addon.sourceURI.spec
        );
        continue;
      }

      let params = addon.sourceURI.query
        .split("&")
        .map(function rewrite(param) {
          if (param.indexOf("src=") == 0) {
            return "src=sync";
          }
          return param;
        });

      addon.sourceURI = addon.sourceURI
        .mutate()
        .setQuery(params.join("&"))
        .finalize();
    }

    if (!toInstall.length) {
      return ourResult;
    }

    const installPromises = [];
    // Start all the installs asynchronously. They will report back to us
    // as they finish, eventually triggering the global callback.
    for (let addon of toInstall) {
      let options = {};
      for (let install of installs) {
        if (install.id == addon.id) {
          options = install;
          break;
        }
      }

      installPromises.push(
        (async () => {
          try {
            const result = await this.installAddonFromSearchResult(
              addon,
              options
            );
            ourResult.installedIDs.push(result.id);
            ourResult.installs.push(result.install);
            ourResult.addons.push(result.addon);
          } catch (error) {
            ourResult.errors.push(error);
          }
        })()
      );
    }

    await Promise.all(installPromises);

    if (ourResult.errors.length) {
      throw new Error("1 or more add-ons failed to install");
    }
    return ourResult;
  },

  /**
   * Returns true if we are able to install the specified addon, false
   * otherwise. It is expected that this will log the reason if it returns
   * false.
   *
   * @param addon
   *        (Addon) Add-on instance to check.
   * @param options
   *        (object) The options specified for this addon. See installAddons()
   *        for the valid elements.
   */
  canInstallAddon(addon, options) {
    // sourceURI presence isn't enforced by AddonRepository. So, we skip
    // add-ons without a sourceURI.
    if (!addon.sourceURI) {
      this._log.info(
        "Skipping install of add-on because missing sourceURI: " + addon.id
      );
      return false;
    }
    // Verify that the source URI uses TLS. We don't allow installs from
    // insecure sources for security reasons. The Addon Manager ensures
    // that cert validation etc is performed.
    // (We should also consider just dropping this entirely and calling
    // XPIProvider.isInstallAllowed, but that has additional semantics we might
    // need to think through...)
    let requireSecureURI = true;
    if (options && options.requireSecureURI !== undefined) {
      requireSecureURI = options.requireSecureURI;
    }

    if (requireSecureURI) {
      let scheme = addon.sourceURI.scheme;
      if (scheme != "https") {
        this._log.info(
          `Skipping install of add-on "${addon.id}" because sourceURI's scheme of "${scheme}" is not trusted`
        );
        return false;
      }
    }

    // Policy prevents either installing this addon or any addon
    if (
      Services.policies &&
      (!Services.policies.mayInstallAddon(addon) ||
        !Services.policies.isAllowed("xpinstall"))
    ) {
      this._log.info(
        `Skipping install of "${addon.id}" due to enterprise policy`
      );
      return false;
    }

    this._log.info(`Add-on "${addon.id}" is able to be installed`);
    return true;
  },

  /**
   * Update the user disabled flag for an add-on.
   *
   * If the new flag matches the existing or if the add-on
   * isn't currently active, the function will return immediately.
   *
   * @param addon
   *        (Addon) Add-on instance to operate on.
   * @param value
   *        (bool) New value for add-on's userDisabled property.
   */
  updateUserDisabled(addon, value) {
    if (addon.userDisabled == value) {
      return;
    }

    this._log.info("Updating userDisabled flag: " + addon.id + " -> " + value);
    if (value) {
      addon.disable();
    } else {
      addon.enable();
    }
  },
};

export const AddonUtils = new AddonUtilsInternal();
PK
!<��P�<�<,modules/services-sync/bridged_engine.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file has all the machinery for hooking up bridged engines implemented
 * in Rust. It's the JavaScript side of the Golden Gate bridge that connects
 * Desktop Sync to a Rust `BridgedEngine`, via the `mozIBridgedSyncEngine`
 * XPCOM interface.
 *
 * Creating a bridged engine only takes a few lines of code, since most of the
 * hard work is done on the Rust side. On the JS side, you'll need to subclass
 * `BridgedEngine` (instead of `SyncEngine`), supply a `mozIBridgedSyncEngine`
 * for your subclass to wrap, and optionally implement and override the tracker.
 */

import { SyncEngine, Tracker } from "resource://services-sync/engines.sys.mjs";
import { RawCryptoWrapper } from "resource://services-sync/record.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "resource://gre/modules/Log.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
});

/**
 * A stub store that converts between raw decrypted incoming records and
 * envelopes. Since the interface we need is so minimal, this class doesn't
 * inherit from the base `Store` implementation...it would take more code to
 * override all those behaviors!
 *
 * This class isn't meant to be subclassed, because bridged engines shouldn't
 * override their store classes in `_storeObj`.
 */
class BridgedStore {
  constructor(name, engine) {
    if (!engine) {
      throw new Error("Store must be associated with an Engine instance.");
    }
    this.engine = engine;
    this._log = lazy.Log.repository.getLogger(`Sync.Engine.${name}.Store`);
    this._batchChunkSize = 500;
  }

  async applyIncomingBatch(records) {
    for (let chunk of lazy.PlacesUtils.chunkArray(
      records,
      this._batchChunkSize
    )) {
      let incomingEnvelopesAsJSON = chunk.map(record =>
        JSON.stringify(record.toIncomingBso())
      );
      this._log.trace("incoming envelopes", incomingEnvelopesAsJSON);
      await this.engine._bridge.storeIncoming(incomingEnvelopesAsJSON);
    }
    // Array of failed records.
    return [];
  }

  async wipe() {
    await this.engine._bridge.wipe();
  }
}

/**
 * A wrapper class to convert between BSOs on the JS side, and envelopes on the
 * Rust side. This class intentionally subclasses `RawCryptoWrapper`, because we
 * don't want the stringification and parsing machinery in `CryptoWrapper`.
 *
 * This class isn't meant to be subclassed, because bridged engines shouldn't
 * override their record classes in `_recordObj`.
 */
class BridgedRecord extends RawCryptoWrapper {
  /**
   * Creates an outgoing record from a BSO returned by a bridged engine.
   *
   * @param  {String} collection The collection name.
   * @param  {Object} bso   The outgoing bso (ie, a sync15::bso::OutgoingBso) returned from
   *                        `mozIBridgedSyncEngine::apply`.
   * @return {BridgedRecord}     A Sync record ready to encrypt and upload.
   */
  static fromOutgoingBso(collection, bso) {
    // The BSO has already been JSON serialized coming out of Rust, so the
    // envelope has been flattened.
    if (typeof bso.id != "string") {
      throw new TypeError("Outgoing BSO missing ID");
    }
    if (typeof bso.payload != "string") {
      throw new TypeError("Outgoing BSO missing payload");
    }
    let record = new BridgedRecord(collection, bso.id);
    record.cleartext = bso.payload;
    return record;
  }

  transformBeforeEncrypt(cleartext) {
    if (typeof cleartext != "string") {
      throw new TypeError("Outgoing bridged engine records must be strings");
    }
    return cleartext;
  }

  transformAfterDecrypt(cleartext) {
    if (typeof cleartext != "string") {
      throw new TypeError("Incoming bridged engine records must be strings");
    }
    return cleartext;
  }

  /*
   * Converts this incoming record into an envelope to pass to a bridged engine.
   * This object must be kept in sync with `sync15::IncomingBso`.
   *
   * @return {Object} The incoming envelope, to pass to
   *                  `mozIBridgedSyncEngine::storeIncoming`.
   */
  toIncomingBso() {
    return {
      id: this.data.id,
      modified: this.data.modified,
      payload: this.cleartext,
    };
  }
}

class BridgeError extends Error {
  constructor(code, message) {
    super(message);
    this.name = "BridgeError";
    // TODO: We may want to use a different name for this, since errors with
    // a `result` property are treated specially by telemetry, discarding the
    // message...but, unlike other `nserror`s, the message is actually useful,
    // and we still want to capture it.
    this.result = code;
  }
}

class InterruptedError extends Error {
  constructor(message) {
    super(message);
    this.name = "InterruptedError";
  }
}

/**
 * Adapts a `Log.sys.mjs` logger to a `mozIServicesLogSink`. This class is copied
 * from `SyncedBookmarksMirror.sys.mjs`.
 */
export class LogAdapter {
  constructor(log) {
    this.log = log;
  }

  get maxLevel() {
    let level = this.log.level;
    if (level <= lazy.Log.Level.All) {
      return Ci.mozIServicesLogSink.LEVEL_TRACE;
    }
    if (level <= lazy.Log.Level.Info) {
      return Ci.mozIServicesLogSink.LEVEL_DEBUG;
    }
    if (level <= lazy.Log.Level.Warn) {
      return Ci.mozIServicesLogSink.LEVEL_WARN;
    }
    if (level <= lazy.Log.Level.Error) {
      return Ci.mozIServicesLogSink.LEVEL_ERROR;
    }
    return Ci.mozIServicesLogSink.LEVEL_OFF;
  }

  trace(message) {
    this.log.trace(message);
  }

  debug(message) {
    this.log.debug(message);
  }

  warn(message) {
    this.log.warn(message);
  }

  error(message) {
    this.log.error(message);
  }
}

// This converts the XPCOM-defined, callback-based mozIBridgedSyncEngine to
// a promise-based implementation.
export class BridgeWrapperXPCOM {
  constructor(component) {
    this.comp = component;
  }

  // A few sync, non-callback based attributes.
  get storageVersion() {
    return this.comp.storageVersion;
  }

  get allowSkippedRecord() {
    return this.comp.allowSkippedRecord;
  }

  get logger() {
    return this.comp.logger;
  }

  // And the async functions we promisify.
  // Note this is `lastSync` via uniffi but `getLastSync` via xpcom
  lastSync() {
    return BridgeWrapperXPCOM.#promisify(this.comp.getLastSync);
  }

  setLastSync(lastSyncMillis) {
    return BridgeWrapperXPCOM.#promisify(this.comp.setLastSync, lastSyncMillis);
  }

  getSyncId() {
    return BridgeWrapperXPCOM.#promisify(this.comp.getSyncId);
  }

  resetSyncId() {
    return BridgeWrapperXPCOM.#promisify(this.comp.resetSyncId);
  }

  ensureCurrentSyncId(newSyncId) {
    return BridgeWrapperXPCOM.#promisify(
      this.comp.ensureCurrentSyncId,
      newSyncId
    );
  }

  syncStarted() {
    return BridgeWrapperXPCOM.#promisify(this.comp.syncStarted);
  }

  storeIncoming(incomingEnvelopesAsJSON) {
    return BridgeWrapperXPCOM.#promisify(
      this.comp.storeIncoming,
      incomingEnvelopesAsJSON
    );
  }

  apply() {
    return BridgeWrapperXPCOM.#promisify(this.comp.apply);
  }

  setUploaded(newTimestampMillis, uploadedIds) {
    return BridgeWrapperXPCOM.#promisify(
      this.comp.setUploaded,
      newTimestampMillis,
      uploadedIds
    );
  }

  syncFinished() {
    return BridgeWrapperXPCOM.#promisify(this.comp.syncFinished);
  }

  reset() {
    return BridgeWrapperXPCOM.#promisify(this.comp.reset);
  }

  wipe() {
    return BridgeWrapperXPCOM.#promisify(this.comp.wipe);
  }

  // Converts a XPCOM bridged function that takes a callback into one that returns a
  // promise.
  static #promisify(func, ...params) {
    return new Promise((resolve, reject) => {
      func(...params, {
        // This object implicitly implements all three callback interfaces
        // (`mozIBridgedSyncEngine{Apply, Result}Callback`), because they have
        // the same methods. The only difference is the type of the argument
        // passed to `handleSuccess`, which doesn't matter in JS.
        handleSuccess: resolve,
        handleError(code, message) {
          reject(transformError(code, message));
        },
      });
    });
  }
}

/**
 * A base class used to plug a Rust engine into Sync, and have it work like any
 * other engine. The constructor takes a bridge as its first argument, which is
 * a "bridged sync engine", as defined by UniFFI in the application-services
 * crate.
 * For backwards compatibility, this can also be an instance of an XPCOM
 * component class that implements `mozIBridgedSyncEngine`, wrapped in
 * a `BridgeWrapperXPCOM` wrapper.
 * (Note that at time of writing, the above is slightly aspirational; the
 * actual definition of the UniFFI shared bridged engine is still in flux.)
 *
 * This class inherits from `SyncEngine`, which has a lot of machinery that we
 * don't need, but that's fairly easy to override. It would be harder to
 * reimplement the machinery that we _do_ need here. However, because of that,
 * this class has lots of methods that do nothing, or return empty data. The
 * docs above each method explain what it's overriding, and why.
 *
 * This class is designed to be subclassed, but the only part that your engine
 * may want to override is `_trackerObj`. Even then, using the default (no-op)
 * tracker is fine, because the shape of the `Tracker` interface may not make
 * sense for all engines.
 */
export function BridgedEngine(name, service) {
  SyncEngine.call(this, name, service);
}

BridgedEngine.prototype = {
  /**
   * The Rust implemented bridge. Must be set by the engine which subclasses us.
   */
  _bridge: null,
  /**
   * The tracker class for this engine. Subclasses may want to override this
   * with their own tracker, though using the default `Tracker` is fine.
   */
  _trackerObj: Tracker,

  /** Returns the record class for all bridged engines. */
  get _recordObj() {
    return BridgedRecord;
  },

  set _recordObj(obj) {
    throw new TypeError("Don't override the record class for bridged engines");
  },

  /** Returns the store class for all bridged engines. */
  get _storeObj() {
    return BridgedStore;
  },

  set _storeObj(obj) {
    throw new TypeError("Don't override the store class for bridged engines");
  },

  /** Returns the storage version for this engine. */
  get version() {
    return this._bridge.storageVersion;
  },

  // Legacy engines allow sync to proceed if some records are too large to
  // upload (eg, a payload that's bigger than the server's published limits).
  // If this returns true, we will just skip the record without even attempting
  // to upload. If this is false, we'll abort the entire batch.
  // If the engine allows this, it will need to detect this scenario by noticing
  // the ID is not in the 'success' records reported to `setUploaded`.
  // (Note that this is not to be confused with the fact server's can currently
  // reject records as part of a POST - but we hope to remove this ability from
  // the server API. Note also that this is not bullet-proof - if the count of
  // records is high, it's possible that we will have committed a previous
  // batch before we hit the relevant limits, so things might have been written.
  // We hope to fix this by ensuring batch limits are such that this is
  // impossible)
  get allowSkippedRecord() {
    return this._bridge.allowSkippedRecord;
  },

  /**
   * Returns the sync ID for this engine. This is exposed for tests, but
   * Sync code always calls `resetSyncID()` and `ensureCurrentSyncID()`,
   * not this.
   *
   * @returns {String?} The sync ID, or `null` if one isn't set.
   */
  async getSyncID() {
    // Note that all methods on an XPCOM class instance are automatically bound,
    // so we don't need to write `this._bridge.getSyncId.bind(this._bridge)`.
    let syncID = await this._bridge.getSyncId();
    return syncID;
  },

  async resetSyncID() {
    await this._deleteServerCollection();
    let newSyncID = await this.resetLocalSyncID();
    return newSyncID;
  },

  async resetLocalSyncID() {
    let newSyncID = await this._bridge.resetSyncId();
    return newSyncID;
  },

  async ensureCurrentSyncID(newSyncID) {
    let assignedSyncID = await this._bridge.ensureCurrentSyncId(newSyncID);
    return assignedSyncID;
  },

  async getLastSync() {
    // The bridge defines lastSync as integer ms, but sync itself wants to work
    // in a float seconds with 2 decimal places.
    let lastSyncMS = await this._bridge.lastSync();
    return Math.round(lastSyncMS / 10) / 100;
  },

  async setLastSync(lastSyncSeconds) {
    await this._bridge.setLastSync(Math.round(lastSyncSeconds * 1000));
  },

  /**
   * Returns the initial changeset for the sync. Bridged engines handle
   * reconciliation internally, so we don't know what changed until after we've
   * stored and applied all incoming records. So we return an empty changeset
   * here, and replace it with the real one in `_processIncoming`.
   */
  async pullChanges() {
    return {};
  },

  async trackRemainingChanges() {
    await this._bridge.syncFinished();
  },

  /**
   * Marks a record for a hard-`DELETE` at the end of the sync. The base method
   * also removes it from the tracker, but we don't use the tracker for that,
   * so we override the method to just mark.
   */
  _deleteId(id) {
    this._noteDeletedId(id);
  },

  /**
   * Always stage incoming records, bypassing the base engine's reconciliation
   * machinery.
   */
  async _reconcile() {
    return true;
  },

  async _syncStartup() {
    await super._syncStartup();
    await this._bridge.syncStarted();
  },

  async _processIncoming(newitems) {
    await super._processIncoming(newitems);

    let outgoingBsosAsJSON = await this._bridge.apply();
    let changeset = {};
    for (let bsoAsJSON of outgoingBsosAsJSON) {
      this._log.trace("outgoing bso", bsoAsJSON);
      let record = BridgedRecord.fromOutgoingBso(
        this.name,
        JSON.parse(bsoAsJSON)
      );
      changeset[record.id] = {
        synced: false,
        record,
      };
    }
    this._modified.replace(changeset);
  },

  /**
   * Notify the bridged engine that we've successfully uploaded a batch, so
   * that it can update its local state. For example, if the engine uses a
   * mirror and a temp table for outgoing records, it can write the uploaded
   * records from the outgoing table back to the mirror.
   */
  async _onRecordsWritten(succeeded, failed, serverModifiedTime) {
    // JS uses seconds but Rust uses milliseconds so we'll need to convert
    let serverModifiedMS = Math.round(serverModifiedTime * 1000);
    await this._bridge.setUploaded(Math.floor(serverModifiedMS), succeeded);
  },

  async _createTombstone() {
    throw new Error("Bridged engines don't support weak uploads");
  },

  async _createRecord(id) {
    let change = this._modified.changes[id];
    if (!change) {
      throw new TypeError("Can't create record for unchanged item");
    }
    return change.record;
  },

  async _resetClient() {
    await super._resetClient();
    await this._bridge.reset();
  },
};
Object.setPrototypeOf(BridgedEngine.prototype, SyncEngine.prototype);

function transformError(code, message) {
  switch (code) {
    case Cr.NS_ERROR_ABORT:
      return new InterruptedError(message);

    default:
      return new BridgeError(code, message);
  }
}
PK
!<�c� � 2modules/services-sync/collection_validator.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Async: "resource://services-common/async.sys.mjs",
});

export class CollectionProblemData {
  constructor() {
    this.missingIDs = 0;
    this.clientDuplicates = [];
    this.duplicates = [];
    this.clientMissing = [];
    this.serverMissing = [];
    this.serverDeleted = [];
    this.serverUnexpected = [];
    this.differences = [];
  }

  /**
   * Produce a list summarizing problems found. Each entry contains {name, count},
   * where name is the field name for the problem, and count is the number of times
   * the problem was encountered.
   *
   * Validation has failed if all counts are not 0.
   */
  getSummary() {
    return [
      { name: "clientMissing", count: this.clientMissing.length },
      { name: "serverMissing", count: this.serverMissing.length },
      { name: "serverDeleted", count: this.serverDeleted.length },
      { name: "serverUnexpected", count: this.serverUnexpected.length },
      { name: "differences", count: this.differences.length },
      { name: "missingIDs", count: this.missingIDs },
      { name: "clientDuplicates", count: this.clientDuplicates.length },
      { name: "duplicates", count: this.duplicates.length },
    ];
  }
}

export class CollectionValidator {
  // Construct a generic collection validator. This is intended to be called by
  // subclasses.
  // - name: Name of the engine
  // - idProp: Property that identifies a record. That is, if a client and server
  //   record have the same value for the idProp property, they should be
  //   compared against eachother.
  // - props: Array of properties that should be compared
  constructor(name, idProp, props) {
    this.name = name;
    this.props = props;
    this.idProp = idProp;

    // This property deals with the fact that form history records are never
    // deleted from the server. The FormValidator subclass needs to ignore the
    // client missing records, and it uses this property to achieve it -
    // (Bug 1354016).
    this.ignoresMissingClients = false;
  }

  // Should a custom ProblemData type be needed, return it here.
  emptyProblemData() {
    return new CollectionProblemData();
  }

  async getServerItems(engine) {
    let collection = engine.itemSource();
    let collectionKey = engine.service.collectionKeys.keyForCollection(
      engine.name
    );
    collection.full = true;
    let result = await collection.getBatched();
    if (!result.response.success) {
      throw result.response;
    }
    let cleartexts = [];

    await lazy.Async.yieldingForEach(result.records, async record => {
      await record.decrypt(collectionKey);
      cleartexts.push(record.cleartext);
    });

    return cleartexts;
  }

  // Should return a promise that resolves to an array of client items.
  getClientItems() {
    return Promise.reject("Must implement");
  }

  /**
   * Can we guarantee validation will fail with a reason that isn't actually a
   * problem? For example, if we know there are pending changes left over from
   * the last sync, this should resolve to false. By default resolves to true.
   */
  async canValidate() {
    return true;
  }

  // Turn the client item into something that can be compared with the server item,
  // and is also safe to mutate.
  normalizeClientItem(item) {
    return Cu.cloneInto(item, {});
  }

  // Turn the server item into something that can be easily compared with the client
  // items.
  async normalizeServerItem(item) {
    return item;
  }

  // Return whether or not a server item should be present on the client. Expected
  // to be overridden.
  clientUnderstands() {
    return true;
  }

  // Return whether or not a client item should be present on the server. Expected
  // to be overridden
  async syncedByClient() {
    return true;
  }

  // Compare the server item and the client item, and return a list of property
  // names that are different. Can be overridden if needed.
  getDifferences(client, server) {
    let differences = [];
    for (let prop of this.props) {
      let clientProp = client[prop];
      let serverProp = server[prop];
      if ((clientProp || "") !== (serverProp || "")) {
        differences.push(prop);
      }
    }
    return differences;
  }

  // Returns an object containing
  //   problemData: an instance of the class returned by emptyProblemData(),
  //   clientRecords: Normalized client records
  //   records: Normalized server records,
  //   deletedRecords: Array of ids that were marked as deleted by the server.
  async compareClientWithServer(clientItems, serverItems) {
    const yieldState = lazy.Async.yieldState();

    const clientRecords = [];

    await lazy.Async.yieldingForEach(
      clientItems,
      item => {
        clientRecords.push(this.normalizeClientItem(item));
      },
      yieldState
    );

    const serverRecords = [];
    await lazy.Async.yieldingForEach(
      serverItems,
      async item => {
        serverRecords.push(await this.normalizeServerItem(item));
      },
      yieldState
    );

    let problems = this.emptyProblemData();
    let seenServer = new Map();
    let serverDeleted = new Set();
    let allRecords = new Map();

    for (let record of serverRecords) {
      let id = record[this.idProp];
      if (!id) {
        ++problems.missingIDs;
        continue;
      }
      if (record.deleted) {
        serverDeleted.add(record);
      } else {
        let serverHasPossibleDupe = seenServer.has(id);
        if (serverHasPossibleDupe) {
          problems.duplicates.push(id);
        } else {
          seenServer.set(id, record);
          allRecords.set(id, { server: record, client: null });
        }
        record.understood = this.clientUnderstands(record);
      }
    }

    let seenClient = new Map();
    for (let record of clientRecords) {
      let id = record[this.idProp];
      record.shouldSync = await this.syncedByClient(record);
      let clientHasPossibleDupe = seenClient.has(id);
      if (clientHasPossibleDupe && record.shouldSync) {
        // Only report duplicate client IDs for syncable records.
        problems.clientDuplicates.push(id);
        continue;
      }
      seenClient.set(id, record);
      let combined = allRecords.get(id);
      if (combined) {
        combined.client = record;
      } else {
        allRecords.set(id, { client: record, server: null });
      }
    }

    for (let [id, { server, client }] of allRecords) {
      if (!client && !server) {
        throw new Error("Impossible: no client or server record for " + id);
      } else if (server && !client) {
        if (!this.ignoresMissingClients && server.understood) {
          problems.clientMissing.push(id);
        }
      } else if (client && !server) {
        if (client.shouldSync) {
          problems.serverMissing.push(id);
        }
      } else {
        if (!client.shouldSync) {
          if (!problems.serverUnexpected.includes(id)) {
            problems.serverUnexpected.push(id);
          }
          continue;
        }
        let differences = this.getDifferences(client, server);
        if (differences && differences.length) {
          problems.differences.push({ id, differences });
        }
      }
    }
    return {
      problemData: problems,
      clientRecords,
      records: serverRecords,
      deletedRecords: [...serverDeleted],
    };
  }

  async validate(engine) {
    let start = Cu.now();
    let clientItems = await this.getClientItems();
    let serverItems = await this.getServerItems(engine);
    let serverRecordCount = serverItems.length;
    let result = await this.compareClientWithServer(clientItems, serverItems);
    let end = Cu.now();
    let duration = end - start;
    engine._log.debug(`Validated ${this.name} in ${duration}ms`);
    engine._log.debug(`Problem summary`);
    for (let { name, count } of result.problemData.getSummary()) {
      engine._log.debug(`  ${name}: ${count}`);
    }
    return {
      duration,
      version: this.version,
      problems: result.problemData,
      recordCount: serverRecordCount,
    };
  }
}

// Default to 0, some engines may override.
CollectionValidator.prototype.version = 0;
PK
!<)$f��'modules/services-sync/constants.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Don't manually modify this line, as it is automatically replaced on merge day
// by the gecko_migration.py script.
export const WEAVE_VERSION = "1.133.0";

// Sync Server API version that the client supports.
export const SYNC_API_VERSION = "1.5";

// Version of the data format this client supports. The data format describes
// how records are packaged; this is separate from the Server API version and
// the per-engine cleartext formats.
export const STORAGE_VERSION = 5;
export const PREFS_BRANCH = "services.sync.";

// Put in [] because those aren't allowed in a collection name.
export const DEFAULT_KEYBUNDLE_NAME = "[default]";

// Key dimensions.
export const SYNC_KEY_ENCODED_LENGTH = 26;
export const SYNC_KEY_DECODED_LENGTH = 16;

export const NO_SYNC_NODE_INTERVAL = 10 * 60 * 1000; // 10 minutes

export const MAX_ERROR_COUNT_BEFORE_BACKOFF = 3;

// Backoff intervals
export const MINIMUM_BACKOFF_INTERVAL = 15 * 60 * 1000; // 15 minutes
export const MAXIMUM_BACKOFF_INTERVAL = 8 * 60 * 60 * 1000; // 8 hours

// HMAC event handling timeout.
// 10 minutes = a compromise between the multi-desktop sync interval
// and the mobile sync interval.
export const HMAC_EVENT_INTERVAL = 600000;

// How long to wait between sync attempts if the Master Password is locked.
export const MASTER_PASSWORD_LOCKED_RETRY_INTERVAL = 15 * 60 * 1000; // 15 minutes

// 50 is hardcoded here because of URL length restrictions.
// (GUIDs can be up to 64 chars long.)
// Individual engines can set different values for their limit if their
// identifiers are shorter.
export const DEFAULT_GUID_FETCH_BATCH_SIZE = 50;

// Default batch size for download batching
// (how many records are fetched at a time from the server when batching is used).
export const DEFAULT_DOWNLOAD_BATCH_SIZE = 1000;

// score thresholds for early syncs
export const SINGLE_USER_THRESHOLD = 1000;
export const MULTI_DEVICE_THRESHOLD = 300;

// Other score increment constants
export const SCORE_INCREMENT_SMALL = 1;
export const SCORE_INCREMENT_MEDIUM = 10;

// Instant sync score increment
export const SCORE_INCREMENT_XLARGE = 300 + 1; //MULTI_DEVICE_THRESHOLD + 1

// Delay before incrementing global score
export const SCORE_UPDATE_DELAY = 100;

// Delay for the back observer debouncer. This is chosen to be longer than any
// observed spurious idle/back events and short enough to pre-empt user activity.
export const IDLE_OBSERVER_BACK_DELAY = 100;

// Duplicate URI_LENGTH_MAX from Places (from nsNavHistory.h), used to discard
// tabs with huge uris during tab sync.
export const URI_LENGTH_MAX = 65536;

export const MAX_HISTORY_UPLOAD = 5000;
export const MAX_HISTORY_DOWNLOAD = 5000;

// Top-level statuses
export const STATUS_OK = "success.status_ok";
export const SYNC_FAILED = "error.sync.failed";
export const LOGIN_FAILED = "error.login.failed";
export const SYNC_FAILED_PARTIAL = "error.sync.failed_partial";
export const CLIENT_NOT_CONFIGURED = "service.client_not_configured";
export const STATUS_DISABLED = "service.disabled";
export const MASTER_PASSWORD_LOCKED = "service.master_password_locked";

// success states
export const LOGIN_SUCCEEDED = "success.login";
export const SYNC_SUCCEEDED = "success.sync";
export const ENGINE_SUCCEEDED = "success.engine";

// login failure status codes
export const LOGIN_FAILED_NO_USERNAME = "error.login.reason.no_username";
export const LOGIN_FAILED_NO_PASSPHRASE = "error.login.reason.no_recoverykey";
export const LOGIN_FAILED_NETWORK_ERROR = "error.login.reason.network";
export const LOGIN_FAILED_SERVER_ERROR = "error.login.reason.server";
export const LOGIN_FAILED_INVALID_PASSPHRASE = "error.login.reason.recoverykey";
export const LOGIN_FAILED_LOGIN_REJECTED = "error.login.reason.account";

// sync failure status codes
export const METARECORD_DOWNLOAD_FAIL =
  "error.sync.reason.metarecord_download_fail";
export const VERSION_OUT_OF_DATE = "error.sync.reason.version_out_of_date";
export const CREDENTIALS_CHANGED = "error.sync.reason.credentials_changed";
export const ABORT_SYNC_COMMAND = "aborting sync, process commands said so";
export const NO_SYNC_NODE_FOUND = "error.sync.reason.no_node_found";
export const OVER_QUOTA = "error.sync.reason.over_quota";
export const SERVER_MAINTENANCE = "error.sync.reason.serverMaintenance";

export const RESPONSE_OVER_QUOTA = "14";

// engine failure status codes
export const ENGINE_UPLOAD_FAIL = "error.engine.reason.record_upload_fail";
export const ENGINE_DOWNLOAD_FAIL = "error.engine.reason.record_download_fail";
export const ENGINE_UNKNOWN_FAIL = "error.engine.reason.unknown_fail";
export const ENGINE_APPLY_FAIL = "error.engine.reason.apply_fail";
// an upload failure where the batch was interrupted with a 412
export const ENGINE_BATCH_INTERRUPTED = "error.engine.reason.batch_interrupted";

// Ways that a sync can be disabled (messages only to be printed in debug log)
export const kSyncMasterPasswordLocked =
  "User elected to leave Primary Password locked";
export const kSyncWeaveDisabled = "Weave is disabled";
export const kSyncNetworkOffline = "Network is offline";
export const kSyncBackoffNotMet =
  "Trying to sync before the server said it's okay";
export const kFirstSyncChoiceNotMade =
  "User has not selected an action for first sync";
export const kSyncNotConfigured = "Sync is not configured";
export const kFirefoxShuttingDown = "Firefox is about to shut down";

export const DEVICE_TYPE_DESKTOP = "desktop";
export const DEVICE_TYPE_MOBILE = "mobile";
export const DEVICE_TYPE_TABLET = "tablet";

export const SQLITE_MAX_VARIABLE_NUMBER = 999;
PK
!<��"yy$modules/services-sync/doctor.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// A doctor for our collections. She can be asked to make a consultation, and
// may just diagnose an issue without attempting to cure it, may diagnose and
// attempt to cure, or may decide she is overworked and underpaid.
// Or something - naming is hard :)

import { Log } from "resource://gre/modules/Log.sys.mjs";

import { Async } from "resource://services-common/async.sys.mjs";
import { Observers } from "resource://services-common/observers.sys.mjs";
import { Service } from "resource://services-sync/service.sys.mjs";
import { Resource } from "resource://services-sync/resource.sys.mjs";
import { Svc } from "resource://services-sync/util.sys.mjs";

const log = Log.repository.getLogger("Sync.Doctor");

export var Doctor = {
  async consult(recentlySyncedEngines) {
    if (!Services.telemetry.canRecordBase) {
      log.info("Skipping consultation: telemetry reporting is disabled");
      return;
    }

    let engineInfos = this._getEnginesToValidate(recentlySyncedEngines);

    await this._runValidators(engineInfos);
  },

  _getEnginesToValidate(recentlySyncedEngines) {
    let result = {};
    for (let e of recentlySyncedEngines) {
      let prefPrefix = `engine.${e.name}.`;
      if (
        !Svc.PrefBranch.getBoolPref(prefPrefix + "validation.enabled", false)
      ) {
        log.info(`Skipping check of ${e.name} - disabled via preferences`);
        continue;
      }
      // Check the last validation time for the engine.
      let lastValidation = Svc.PrefBranch.getIntPref(
        prefPrefix + "validation.lastTime",
        0
      );
      let validationInterval = Svc.PrefBranch.getIntPref(
        prefPrefix + "validation.interval"
      );
      let nowSeconds = this._now();

      if (nowSeconds - lastValidation < validationInterval) {
        log.info(
          `Skipping validation of ${e.name}: too recent since last validation attempt`
        );
        continue;
      }
      // Update the time now, even if we decline to actually perform a
      // validation. We don't want to check the rest of these more frequently
      // than once a day.
      Svc.PrefBranch.setIntPref(
        prefPrefix + "validation.lastTime",
        Math.floor(nowSeconds)
      );

      // Validation only occurs a certain percentage of the time.
      let validationProbability =
        Svc.PrefBranch.getIntPref(
          prefPrefix + "validation.percentageChance",
          0
        ) / 100.0;
      if (validationProbability < Math.random()) {
        log.info(
          `Skipping validation of ${e.name}: Probability threshold not met`
        );
        continue;
      }

      let maxRecords = Svc.PrefBranch.getIntPref(
        prefPrefix + "validation.maxRecords"
      );
      if (!maxRecords) {
        log.info(`Skipping validation of ${e.name}: No maxRecords specified`);
        continue;
      }
      // OK, so this is a candidate - the final decision will be based on the
      // number of records actually found.
      result[e.name] = { engine: e, maxRecords };
    }
    return result;
  },

  async _runValidators(engineInfos) {
    if (!Object.keys(engineInfos).length) {
      log.info("Skipping validation: no engines qualify");
      return;
    }

    if (Object.values(engineInfos).filter(i => i.maxRecords != -1).length) {
      // at least some of the engines have maxRecord restrictions which require
      // us to ask the server for the counts.
      let countInfo = await this._fetchCollectionCounts();
      for (let [engineName, recordCount] of Object.entries(countInfo)) {
        if (engineName in engineInfos) {
          engineInfos[engineName].recordCount = recordCount;
        }
      }
    }

    for (let [
      engineName,
      { engine, maxRecords, recordCount },
    ] of Object.entries(engineInfos)) {
      // maxRecords of -1 means "any number", so we can skip asking the server.
      // Used for tests.
      if (maxRecords >= 0 && recordCount > maxRecords) {
        log.debug(
          `Skipping validation for ${engineName} because ` +
            `the number of records (${recordCount}) is greater ` +
            `than the maximum allowed (${maxRecords}).`
        );
        continue;
      }
      let validator = engine.getValidator();
      if (!validator) {
        // This is probably only possible in profile downgrade cases.
        log.warn(
          `engine.getValidator returned null for ${engineName} but the pref that controls validation is enabled.`
        );
        continue;
      }

      if (!(await validator.canValidate())) {
        log.debug(
          `Skipping validation for ${engineName} because validator.canValidate() is false`
        );
        continue;
      }

      // Let's do it!
      Services.console.logStringMessage(
        `Sync is about to run a consistency check of ${engine.name}. This may be slow, and ` +
          `can be controlled using the pref "services.sync.${engine.name}.validation.enabled".\n` +
          `If you encounter any problems because of this, please file a bug.`
      );

      try {
        log.info(`Running validator for ${engine.name}`);
        let result = await validator.validate(engine);
        let { problems, version, duration, recordCount } = result;
        Observers.notify(
          "weave:engine:validate:finish",
          {
            version,
            checked: recordCount,
            took: duration,
            problems: problems ? problems.getSummary(true) : null,
          },
          engine.name
        );
      } catch (ex) {
        if (Async.isShutdownException(ex)) {
          throw ex;
        }
        log.error(`Failed to run validation on ${engine.name}!`, ex);
        Observers.notify("weave:engine:validate:error", ex, engine.name);
        // Keep validating -- there's no reason to think that a failure for one
        // validator would mean the others will fail.
      }
    }
  },

  // mainly for mocking.
  async _fetchCollectionCounts() {
    let collectionCountsURL = Service.userBaseURL + "info/collection_counts";
    try {
      let infoResp = await Service._fetchInfo(collectionCountsURL);
      if (!infoResp.success) {
        log.error(
          "Can't fetch collection counts: request to info/collection_counts responded with " +
            infoResp.status
        );
        return {};
      }
      return infoResp.obj; // might throw because obj is a getter which parses json.
    } catch (ex) {
      if (Async.isShutdownException(ex)) {
        throw ex;
      }
      // Not running validation is totally fine, so we just write an error log and return.
      log.error("Caught error when fetching counts", ex);
      return {};
    }
  },

  // functions used so tests can mock them
  _now() {
    // We use the server time, which is SECONDS
    return Resource.serverTime;
  },
};
PK
!<��CcCc,modules/services-sync/engines/addons.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This file defines the add-on sync functionality.
 *
 * There are currently a number of known limitations:
 *  - We only sync XPI extensions and themes available from addons.mozilla.org.
 *    We hope to expand support for other add-ons eventually.
 *  - We only attempt syncing of add-ons between applications of the same type.
 *    This means add-ons will not synchronize between Firefox desktop and
 *    Firefox mobile, for example. This is because of significant add-on
 *    incompatibility between application types.
 *
 * Add-on records exist for each known {add-on, app-id} pair in the Sync client
 * set. Each record has a randomly chosen GUID. The records then contain
 * basic metadata about the add-on.
 *
 * We currently synchronize:
 *
 *  - Installations
 *  - Uninstallations
 *  - User enabling and disabling
 *
 * Synchronization is influenced by the following preferences:
 *
 *  - services.sync.addons.ignoreUserEnabledChanges
 *  - services.sync.addons.trustedSourceHostnames
 *
 *  and also influenced by whether addons have repository caching enabled and
 *  whether they allow installation of addons from insecure options (both of
 *  which are themselves influenced by the "extensions." pref branch)
 *
 * See the documentation in all.js for the behavior of these prefs.
 */

import { AddonUtils } from "resource://services-sync/addonutils.sys.mjs";
import { AddonsReconciler } from "resource://services-sync/addonsreconciler.sys.mjs";
import {
  Store,
  SyncEngine,
  LegacyTracker,
} from "resource://services-sync/engines.sys.mjs";
import { CryptoWrapper } from "resource://services-sync/record.sys.mjs";
import { Svc, Utils } from "resource://services-sync/util.sys.mjs";

import { SCORE_INCREMENT_XLARGE } from "resource://services-sync/constants.sys.mjs";
import { CollectionValidator } from "resource://services-sync/collection_validator.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  AddonRepository: "resource://gre/modules/addons/AddonRepository.sys.mjs",
});

// 7 days in milliseconds.
const PRUNE_ADDON_CHANGES_THRESHOLD = 60 * 60 * 24 * 7 * 1000;

/**
 * AddonRecord represents the state of an add-on in an application.
 *
 * Each add-on has its own record for each application ID it is installed
 * on.
 *
 * The ID of add-on records is a randomly-generated GUID. It is random instead
 * of deterministic so the URIs of the records cannot be guessed and so
 * compromised server credentials won't result in disclosure of the specific
 * add-ons present in a Sync account.
 *
 * The record contains the following fields:
 *
 *  addonID
 *    ID of the add-on. This correlates to the "id" property on an Addon type.
 *
 *  applicationID
 *    The application ID this record is associated with.
 *
 *  enabled
 *    Boolean stating whether add-on is enabled or disabled by the user.
 *
 *  source
 *    String indicating where an add-on is from. Currently, we only support
 *    the value "amo" which indicates that the add-on came from the official
 *    add-ons repository, addons.mozilla.org. In the future, we may support
 *    installing add-ons from other sources. This provides a future-compatible
 *    mechanism for clients to only apply records they know how to handle.
 */
function AddonRecord(collection, id) {
  CryptoWrapper.call(this, collection, id);
}
AddonRecord.prototype = {
  _logName: "Record.Addon",
};
Object.setPrototypeOf(AddonRecord.prototype, CryptoWrapper.prototype);

Utils.deferGetSet(AddonRecord, "cleartext", [
  "addonID",
  "applicationID",
  "enabled",
  "source",
]);

/**
 * The AddonsEngine handles synchronization of add-ons between clients.
 *
 * The engine maintains an instance of an AddonsReconciler, which is the entity
 * maintaining state for add-ons. It provides the history and tracking APIs
 * that AddonManager doesn't.
 *
 * The engine instance overrides a handful of functions on the base class. The
 * rationale for each is documented by that function.
 */
export function AddonsEngine(service) {
  SyncEngine.call(this, "Addons", service);

  this._reconciler = new AddonsReconciler(this._tracker.asyncObserver);
}

AddonsEngine.prototype = {
  _storeObj: AddonsStore,
  _trackerObj: AddonsTracker,
  _recordObj: AddonRecord,
  version: 1,

  syncPriority: 5,

  _reconciler: null,

  async initialize() {
    await SyncEngine.prototype.initialize.call(this);
    await this._reconciler.ensureStateLoaded();
  },

  /**
   * Override parent method to find add-ons by their public ID, not Sync GUID.
   */
  async _findDupe(item) {
    let id = item.addonID;

    // The reconciler should have been updated at the top of the sync, so we
    // can assume it is up to date when this function is called.
    let addons = this._reconciler.addons;
    if (!(id in addons)) {
      return null;
    }

    let addon = addons[id];
    if (addon.guid != item.id) {
      return addon.guid;
    }

    return null;
  },

  /**
   * Override getChangedIDs to pull in tracker changes plus changes from the
   * reconciler log.
   */
  async getChangedIDs() {
    let changes = {};
    const changedIDs = await this._tracker.getChangedIDs();
    for (let [id, modified] of Object.entries(changedIDs)) {
      changes[id] = modified;
    }

    let lastSync = await this.getLastSync();
    let lastSyncDate = new Date(lastSync * 1000);

    // The reconciler should have been refreshed at the beginning of a sync and
    // we assume this function is only called from within a sync.
    let reconcilerChanges = this._reconciler.getChangesSinceDate(lastSyncDate);
    let addons = this._reconciler.addons;
    for (let change of reconcilerChanges) {
      let changeTime = change[0];
      let id = change[2];

      if (!(id in addons)) {
        continue;
      }

      // Keep newest modified time.
      if (id in changes && changeTime < changes[id]) {
        continue;
      }

      if (!(await this.isAddonSyncable(addons[id]))) {
        continue;
      }

      this._log.debug("Adding changed add-on from changes log: " + id);
      let addon = addons[id];
      changes[addon.guid] = changeTime.getTime() / 1000;
    }

    return changes;
  },

  /**
   * Override start of sync function to refresh reconciler.
   *
   * Many functions in this class assume the reconciler is refreshed at the
   * top of a sync. If this ever changes, those functions should be revisited.
   *
   * Technically speaking, we don't need to refresh the reconciler on every
   * sync since it is installed as an AddonManager listener. However, add-ons
   * are complicated and we force a full refresh, just in case the listeners
   * missed something.
   */
  async _syncStartup() {
    // We refresh state before calling parent because syncStartup in the parent
    // looks for changed IDs, which is dependent on add-on state being up to
    // date.
    await this._refreshReconcilerState();
    return SyncEngine.prototype._syncStartup.call(this);
  },

  /**
   * Override end of sync to perform a little housekeeping on the reconciler.
   *
   * We prune changes to prevent the reconciler state from growing without
   * bound. Even if it grows unbounded, there would have to be many add-on
   * changes (thousands) for it to slow things down significantly. This is
   * highly unlikely to occur. Still, we exercise defense just in case.
   */
  async _syncCleanup() {
    let lastSync = await this.getLastSync();
    let ms = 1000 * lastSync - PRUNE_ADDON_CHANGES_THRESHOLD;
    this._reconciler.pruneChangesBeforeDate(new Date(ms));
    return SyncEngine.prototype._syncCleanup.call(this);
  },

  /**
   * Helper function to ensure reconciler is up to date.
   *
   * This will load the reconciler's state from the file
   * system (if needed) and refresh the state of the reconciler.
   */
  async _refreshReconcilerState() {
    this._log.debug("Refreshing reconciler state");
    return this._reconciler.refreshGlobalState();
  },

  // Returns a promise
  isAddonSyncable(addon, ignoreRepoCheck) {
    return this._store.isAddonSyncable(addon, ignoreRepoCheck);
  },
};
Object.setPrototypeOf(AddonsEngine.prototype, SyncEngine.prototype);

/**
 * This is the primary interface between Sync and the Addons Manager.
 *
 * In addition to the core store APIs, we provide convenience functions to wrap
 * Add-on Manager APIs with Sync-specific semantics.
 */
function AddonsStore(name, engine) {
  Store.call(this, name, engine);
}
AddonsStore.prototype = {
  // Define the add-on types (.type) that we support.
  _syncableTypes: ["extension", "theme"],

  _extensionsPrefs: Services.prefs.getBranch("extensions."),

  get reconciler() {
    return this.engine._reconciler;
  },

  /**
   * Override applyIncoming to filter out records we can't handle.
   */
  async applyIncoming(record) {
    // The fields we look at aren't present when the record is deleted.
    if (!record.deleted) {
      // Ignore records not belonging to our application ID because that is the
      // current policy.
      if (record.applicationID != Services.appinfo.ID) {
        this._log.info(
          "Ignoring incoming record from other App ID: " + record.id
        );
        return;
      }

      // Ignore records that aren't from the official add-on repository, as that
      // is our current policy.
      if (record.source != "amo") {
        this._log.info(
          "Ignoring unknown add-on source (" +
            record.source +
            ")" +
            " for " +
            record.id
        );
        return;
      }
    }

    // Ignore incoming records for which an existing non-syncable addon
    // exists. Note that we do not insist that the addon manager already have
    // metadata for this addon - it's possible our reconciler previously saw the
    // addon but the addon-manager cache no longer has it - which is fine for a
    // new incoming addon.
    // (Note that most other cases where the addon-manager cache is invalid
    // doesn't get this treatment because that cache self-repairs after some
    // time - but it only re-populates addons which are currently installed.)
    let existingMeta = this.reconciler.addons[record.addonID];
    if (
      existingMeta &&
      !(await this.isAddonSyncable(existingMeta, /* ignoreRepoCheck */ true))
    ) {
      this._log.info(
        "Ignoring incoming record for an existing but non-syncable addon",
        record.addonID
      );
      return;
    }

    await Store.prototype.applyIncoming.call(this, record);
  },

  /**
   * Provides core Store API to create/install an add-on from a record.
   */
  async create(record) {
    // This will throw if there was an error. This will get caught by the sync
    // engine and the record will try to be applied later.
    const results = await AddonUtils.installAddons([
      {
        id: record.addonID,
        syncGUID: record.id,
        enabled: record.enabled,
        requireSecureURI: this._extensionsPrefs.getBoolPref(
          "install.requireSecureOrigin",
          true
        ),
      },
    ]);

    if (results.skipped.includes(record.addonID)) {
      this._log.info("Add-on skipped: " + record.addonID);
      // Just early-return for skipped addons - we don't want to arrange to
      // try again next time because the condition that caused up to skip
      // will remain true for this addon forever.
      return;
    }

    let addon;
    for (let a of results.addons) {
      if (a.id == record.addonID) {
        addon = a;
        break;
      }
    }

    // This should never happen, but is present as a fail-safe.
    if (!addon) {
      throw new Error("Add-on not found after install: " + record.addonID);
    }

    this._log.info("Add-on installed: " + record.addonID);
  },

  /**
   * Provides core Store API to remove/uninstall an add-on from a record.
   */
  async remove(record) {
    // If this is called, the payload is empty, so we have to find by GUID.
    let addon = await this.getAddonByGUID(record.id);
    if (!addon) {
      // We don't throw because if the add-on could not be found then we assume
      // it has already been uninstalled and there is nothing for this function
      // to do.
      return;
    }

    this._log.info("Uninstalling add-on: " + addon.id);
    await AddonUtils.uninstallAddon(addon);
  },

  /**
   * Provides core Store API to update an add-on from a record.
   */
  async update(record) {
    let addon = await this.getAddonByID(record.addonID);

    // update() is called if !this.itemExists. And, since itemExists consults
    // the reconciler only, we need to take care of some corner cases.
    //
    // First, the reconciler could know about an add-on that was uninstalled
    // and no longer present in the add-ons manager.
    if (!addon) {
      await this.create(record);
      return;
    }

    // It's also possible that the add-on is non-restartless and has pending
    // install/uninstall activity.
    //
    // We wouldn't get here if the incoming record was for a deletion. So,
    // check for pending uninstall and cancel if necessary.
    if (addon.pendingOperations & lazy.AddonManager.PENDING_UNINSTALL) {
      addon.cancelUninstall();

      // We continue with processing because there could be state or ID change.
    }

    await this.updateUserDisabled(addon, !record.enabled);
  },

  /**
   * Provide core Store API to determine if a record exists.
   */
  async itemExists(guid) {
    let addon = this.reconciler.getAddonStateFromSyncGUID(guid);

    return !!addon;
  },

  /**
   * Create an add-on record from its GUID.
   *
   * @param guid
   *        Add-on GUID (from extensions DB)
   * @param collection
   *        Collection to add record to.
   *
   * @return AddonRecord instance
   */
  async createRecord(guid, collection) {
    let record = new AddonRecord(collection, guid);
    record.applicationID = Services.appinfo.ID;

    let addon = this.reconciler.getAddonStateFromSyncGUID(guid);

    // If we don't know about this GUID or if it has been uninstalled, we mark
    // the record as deleted.
    if (!addon || !addon.installed) {
      record.deleted = true;
      return record;
    }

    record.modified = addon.modified.getTime() / 1000;

    record.addonID = addon.id;
    record.enabled = addon.enabled;

    // This needs to be dynamic when add-ons don't come from AddonRepository.
    record.source = "amo";

    return record;
  },

  /**
   * Changes the id of an add-on.
   *
   * This implements a core API of the store.
   */
  async changeItemID(oldID, newID) {
    // We always update the GUID in the reconciler because it will be
    // referenced later in the sync process.
    let state = this.reconciler.getAddonStateFromSyncGUID(oldID);
    if (state) {
      state.guid = newID;
      await this.reconciler.saveState();
    }

    let addon = await this.getAddonByGUID(oldID);
    if (!addon) {
      this._log.debug(
        "Cannot change item ID (" +
          oldID +
          ") in Add-on " +
          "Manager because old add-on not present: " +
          oldID
      );
      return;
    }

    addon.syncGUID = newID;
  },

  /**
   * Obtain the set of all syncable add-on Sync GUIDs.
   *
   * This implements a core Store API.
   */
  async getAllIDs() {
    let ids = {};

    let addons = this.reconciler.addons;
    for (let id in addons) {
      let addon = addons[id];
      if (await this.isAddonSyncable(addon)) {
        ids[addon.guid] = true;
      }
    }

    return ids;
  },

  /**
   * Wipe engine data.
   *
   * This uninstalls all syncable addons from the application. In case of
   * error, it logs the error and keeps trying with other add-ons.
   */
  async wipe() {
    this._log.info("Processing wipe.");

    await this.engine._refreshReconcilerState();

    // We only wipe syncable add-ons. Wipe is a Sync feature not a security
    // feature.
    let ids = await this.getAllIDs();
    for (let guid in ids) {
      let addon = await this.getAddonByGUID(guid);
      if (!addon) {
        this._log.debug(
          "Ignoring add-on because it couldn't be obtained: " + guid
        );
        continue;
      }

      this._log.info("Uninstalling add-on as part of wipe: " + addon.id);
      await Utils.catch.call(this, () => addon.uninstall())();
    }
  },

  /** *************************************************************************
   * Functions below are unique to this store and not part of the Store API  *
   ***************************************************************************/

  /**
   * Obtain an add-on from its public ID.
   *
   * @param id
   *        Add-on ID
   * @return Addon or undefined if not found
   */
  async getAddonByID(id) {
    return lazy.AddonManager.getAddonByID(id);
  },

  /**
   * Obtain an add-on from its Sync GUID.
   *
   * @param  guid
   *         Add-on Sync GUID
   * @return DBAddonInternal or null
   */
  async getAddonByGUID(guid) {
    return lazy.AddonManager.getAddonBySyncGUID(guid);
  },

  /**
   * Determines whether an add-on is suitable for Sync.
   *
   * @param  addon
   *         Addon instance
   * @param ignoreRepoCheck
   *         Should we skip checking the Addons repository (primarially useful
   *         for testing and validation).
   * @return Boolean indicating whether it is appropriate for Sync
   */
  async isAddonSyncable(addon, ignoreRepoCheck = false) {
    // Currently, we limit syncable add-ons to those that are:
    //   1) In a well-defined set of types
    //   2) Installed in the current profile
    //   3) Not installed by a foreign entity (i.e. installed by the app)
    //      since they act like global extensions.
    //   4) Is not a hotfix.
    //   5) The addons XPIProvider doesn't veto it (i.e not being installed in
    //      the profile directory, or any other reasons it says the addon can't
    //      be synced)
    //   6) Are installed from AMO

    // We could represent the test as a complex boolean expression. We go the
    // verbose route so the failure reason is logged.
    if (!addon) {
      this._log.debug("Null object passed to isAddonSyncable.");
      return false;
    }

    if (!this._syncableTypes.includes(addon.type)) {
      this._log.debug(
        addon.id + " not syncable: type not in allowed list: " + addon.type
      );
      return false;
    }

    if (!(addon.scope & lazy.AddonManager.SCOPE_PROFILE)) {
      this._log.debug(addon.id + " not syncable: not installed in profile.");
      return false;
    }

    // If the addon manager says it's not syncable, we skip it.
    if (!addon.isSyncable) {
      this._log.debug(addon.id + " not syncable: vetoed by the addon manager.");
      return false;
    }

    // This may be too aggressive. If an add-on is downloaded from AMO and
    // manually placed in the profile directory, foreignInstall will be set.
    // Arguably, that add-on should be syncable.
    // TODO Address the edge case and come up with more robust heuristics.
    if (addon.foreignInstall) {
      this._log.debug(addon.id + " not syncable: is foreign install.");
      return false;
    }

    // If the AddonRepository's cache isn't enabled (which it typically isn't
    // in tests), getCachedAddonByID always returns null - so skip the check
    // in that case. We also provide a way to specifically opt-out of the check
    // even if the cache is enabled, which is used by the validators.
    if (ignoreRepoCheck || !lazy.AddonRepository.cacheEnabled) {
      return true;
    }

    let result = await new Promise(res => {
      lazy.AddonRepository.getCachedAddonByID(addon.id, res);
    });

    if (!result) {
      this._log.debug(
        addon.id + " not syncable: add-on not found in add-on repository."
      );
      return false;
    }

    return this.isSourceURITrusted(result.sourceURI);
  },

  /**
   * Determine whether an add-on's sourceURI field is trusted and the add-on
   * can be installed.
   *
   * This function should only ever be called from isAddonSyncable(). It is
   * exposed as a separate function to make testing easier.
   *
   * @param  uri
   *         nsIURI instance to validate
   * @return bool
   */
  isSourceURITrusted: function isSourceURITrusted(uri) {
    // For security reasons, we currently limit synced add-ons to those
    // installed from trusted hostname(s). We additionally require TLS with
    // the add-ons site to help prevent forgeries.
    let trustedHostnames = Svc.PrefBranch.getStringPref(
      "addons.trustedSourceHostnames",
      ""
    ).split(",");

    if (!uri) {
      this._log.debug("Undefined argument to isSourceURITrusted().");
      return false;
    }

    // Scheme is validated before the hostname because uri.host may not be
    // populated for certain schemes. It appears to always be populated for
    // https, so we avoid the potential NS_ERROR_FAILURE on field access.
    if (uri.scheme != "https") {
      this._log.debug("Source URI not HTTPS: " + uri.spec);
      return false;
    }

    if (!trustedHostnames.includes(uri.host)) {
      this._log.debug("Source hostname not trusted: " + uri.host);
      return false;
    }

    return true;
  },

  /**
   * Update the userDisabled flag on an add-on.
   *
   * This will enable or disable an add-on. It has no return value and does
   * not catch or handle exceptions thrown by the addon manager. If no action
   * is needed it will return immediately.
   *
   * @param addon
   *        Addon instance to manipulate.
   * @param value
   *        Boolean to which to set userDisabled on the passed Addon.
   */
  async updateUserDisabled(addon, value) {
    if (addon.userDisabled == value) {
      return;
    }

    // A pref allows changes to the enabled flag to be ignored.
    if (Svc.PrefBranch.getBoolPref("addons.ignoreUserEnabledChanges", false)) {
      this._log.info(
        "Ignoring enabled state change due to preference: " + addon.id
      );
      return;
    }

    AddonUtils.updateUserDisabled(addon, value);
    // updating this flag doesn't send a notification for appDisabled addons,
    // meaning the reconciler will not update its state and may resync the
    // addon - so explicitly rectify the state (bug 1366994)
    if (addon.appDisabled) {
      await this.reconciler.rectifyStateFromAddon(addon);
    }
  },
};

Object.setPrototypeOf(AddonsStore.prototype, Store.prototype);

/**
 * The add-ons tracker keeps track of real-time changes to add-ons.
 *
 * It hooks up to the reconciler and receives notifications directly from it.
 */
function AddonsTracker(name, engine) {
  LegacyTracker.call(this, name, engine);
}
AddonsTracker.prototype = {
  get reconciler() {
    return this.engine._reconciler;
  },

  get store() {
    return this.engine._store;
  },

  /**
   * This callback is executed whenever the AddonsReconciler sends out a change
   * notification. See AddonsReconciler.addChangeListener().
   */
  async changeListener(date, change, addon) {
    this._log.debug("changeListener invoked: " + change + " " + addon.id);
    // Ignore changes that occur during sync.
    if (this.ignoreAll) {
      return;
    }

    if (!(await this.store.isAddonSyncable(addon))) {
      this._log.debug(
        "Ignoring change because add-on isn't syncable: " + addon.id
      );
      return;
    }

    const added = await this.addChangedID(addon.guid, date.getTime() / 1000);
    if (added) {
      this.score += SCORE_INCREMENT_XLARGE;
    }
  },

  onStart() {
    this.reconciler.startListening();
    this.reconciler.addChangeListener(this);
  },

  onStop() {
    this.reconciler.removeChangeListener(this);
    this.reconciler.stopListening();
  },
};

Object.setPrototypeOf(AddonsTracker.prototype, LegacyTracker.prototype);

export class AddonValidator extends CollectionValidator {
  constructor(engine = null) {
    super("addons", "id", ["addonID", "enabled", "applicationID", "source"]);
    this.engine = engine;
  }

  async getClientItems() {
    return lazy.AddonManager.getAllAddons();
  }

  normalizeClientItem(item) {
    let enabled = !item.userDisabled;
    if (item.pendingOperations & lazy.AddonManager.PENDING_ENABLE) {
      enabled = true;
    } else if (item.pendingOperations & lazy.AddonManager.PENDING_DISABLE) {
      enabled = false;
    }
    return {
      enabled,
      id: item.syncGUID,
      addonID: item.id,
      applicationID: Services.appinfo.ID,
      source: "amo", // check item.foreignInstall?
      original: item,
    };
  }

  async normalizeServerItem(item) {
    let guid = await this.engine._findDupe(item);
    if (guid) {
      item.id = guid;
    }
    return item;
  }

  clientUnderstands(item) {
    return item.applicationID === Services.appinfo.ID;
  }

  async syncedByClient(item) {
    return (
      !item.original.hidden &&
      !item.original.isSystem &&
      !(
        item.original.pendingOperations & lazy.AddonManager.PENDING_UNINSTALL
      ) &&
      // No need to await the returned promise explicitely:
      // |expr1 && expr2| evaluates to expr2 if expr1 is true.
      this.engine.isAddonSyncable(item.original, true)
    );
  }
}
PK
!<a�s�r�r/modules/services-sync/engines/bookmarks.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { SCORE_INCREMENT_XLARGE } from "resource://services-sync/constants.sys.mjs";
import {
  Changeset,
  Store,
  SyncEngine,
  Tracker,
} from "resource://services-sync/engines.sys.mjs";
import { CryptoWrapper } from "resource://services-sync/record.sys.mjs";
import { Svc, Utils } from "resource://services-sync/util.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Async: "resource://services-common/async.sys.mjs",
  Observers: "resource://services-common/observers.sys.mjs",
  PlacesBackups: "resource://gre/modules/PlacesBackups.sys.mjs",
  PlacesDBUtils: "resource://gre/modules/PlacesDBUtils.sys.mjs",
  PlacesSyncUtils: "resource://gre/modules/PlacesSyncUtils.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
  Resource: "resource://services-sync/resource.sys.mjs",
  SyncedBookmarksMirror: "resource://gre/modules/SyncedBookmarksMirror.sys.mjs",
});

const PLACES_MAINTENANCE_INTERVAL_SECONDS = 4 * 60 * 60; // 4 hours.

const FOLDER_SORTINDEX = 1000000;

// Roots that should be deleted from the server, instead of applied locally.
// This matches `AndroidBrowserBookmarksRepositorySession::forbiddenGUID`,
// but allows tags because we don't want to reparent tag folders or tag items
// to "unfiled".
const FORBIDDEN_INCOMING_IDS = ["pinned", "places", "readinglist"];

// Items with these parents should be deleted from the server. We allow
// children of the Places root, to avoid orphaning left pane queries and other
// descendants of custom roots.
const FORBIDDEN_INCOMING_PARENT_IDS = ["pinned", "readinglist"];

// The tracker ignores changes made by import and restore, to avoid bumping the
// score and triggering syncs during the process, as well as changes made by
// Sync.
ChromeUtils.defineLazyGetter(lazy, "IGNORED_SOURCES", () => [
  lazy.PlacesUtils.bookmarks.SOURCES.SYNC,
  lazy.PlacesUtils.bookmarks.SOURCES.IMPORT,
  lazy.PlacesUtils.bookmarks.SOURCES.RESTORE,
  lazy.PlacesUtils.bookmarks.SOURCES.RESTORE_ON_STARTUP,
  lazy.PlacesUtils.bookmarks.SOURCES.SYNC_REPARENT_REMOVED_FOLDER_CHILDREN,
]);

// The validation telemetry version for the engine. Version 1 is collected
// by `bookmark_validator.js`, and checks value as well as structure
// differences. Version 2 is collected by the engine as part of building the
// remote tree, and checks structure differences only.
const BOOKMARK_VALIDATOR_VERSION = 2;

// The maximum time that the engine should wait before aborting a bookmark
// merge.
const BOOKMARK_APPLY_TIMEOUT_MS = 5 * 60 * 60 * 1000; // 5 minutes

// The default frecency value to use when not known.
const FRECENCY_UNKNOWN = -1;

// Returns the constructor for a bookmark record type.
function getTypeObject(type) {
  switch (type) {
    case "bookmark":
      return Bookmark;
    case "query":
      return BookmarkQuery;
    case "folder":
      return BookmarkFolder;
    case "livemark":
      return Livemark;
    case "separator":
      return BookmarkSeparator;
    case "item":
      return PlacesItem;
  }
  return null;
}

export function PlacesItem(collection, id, type) {
  CryptoWrapper.call(this, collection, id);
  this.type = type || "item";
}

PlacesItem.prototype = {
  async decrypt(keyBundle) {
    // Do the normal CryptoWrapper decrypt, but change types before returning
    let clear = await CryptoWrapper.prototype.decrypt.call(this, keyBundle);

    // Convert the abstract places item to the actual object type
    if (!this.deleted) {
      Object.setPrototypeOf(this, this.getTypeObject(this.type).prototype);
    }

    return clear;
  },

  getTypeObject: function PlacesItem_getTypeObject(type) {
    let recordObj = getTypeObject(type);
    if (!recordObj) {
      throw new Error("Unknown places item object type: " + type);
    }
    return recordObj;
  },

  _logName: "Sync.Record.PlacesItem",

  // Converts the record to a Sync bookmark object that can be passed to
  // `PlacesSyncUtils.bookmarks.{insert, update}`.
  toSyncBookmark() {
    let result = {
      kind: this.type,
      recordId: this.id,
      parentRecordId: this.parentid,
    };
    let dateAdded = lazy.PlacesSyncUtils.bookmarks.ratchetTimestampBackwards(
      this.dateAdded,
      +this.modified * 1000
    );
    if (dateAdded > 0) {
      result.dateAdded = dateAdded;
    }
    return result;
  },

  // Populates the record from a Sync bookmark object returned from
  // `PlacesSyncUtils.bookmarks.fetch`.
  fromSyncBookmark(item) {
    this.parentid = item.parentRecordId;
    this.parentName = item.parentTitle;
    if (item.dateAdded) {
      this.dateAdded = item.dateAdded;
    }
  },
};

Object.setPrototypeOf(PlacesItem.prototype, CryptoWrapper.prototype);

Utils.deferGetSet(PlacesItem, "cleartext", [
  "hasDupe",
  "parentid",
  "parentName",
  "type",
  "dateAdded",
]);

export function Bookmark(collection, id, type) {
  PlacesItem.call(this, collection, id, type || "bookmark");
}

Bookmark.prototype = {
  _logName: "Sync.Record.Bookmark",

  toSyncBookmark() {
    let info = PlacesItem.prototype.toSyncBookmark.call(this);
    info.title = this.title;
    info.url = this.bmkUri;
    info.description = this.description;
    info.tags = this.tags;
    info.keyword = this.keyword;
    return info;
  },

  fromSyncBookmark(item) {
    PlacesItem.prototype.fromSyncBookmark.call(this, item);
    this.title = item.title;
    this.bmkUri = item.url.href;
    this.description = item.description;
    this.tags = item.tags;
    this.keyword = item.keyword;
  },
};

Object.setPrototypeOf(Bookmark.prototype, PlacesItem.prototype);

Utils.deferGetSet(Bookmark, "cleartext", [
  "title",
  "bmkUri",
  "description",
  "tags",
  "keyword",
]);

export function BookmarkQuery(collection, id) {
  Bookmark.call(this, collection, id, "query");
}

BookmarkQuery.prototype = {
  _logName: "Sync.Record.BookmarkQuery",

  toSyncBookmark() {
    let info = Bookmark.prototype.toSyncBookmark.call(this);
    info.folder = this.folderName || undefined; // empty string -> undefined
    info.query = this.queryId;
    return info;
  },

  fromSyncBookmark(item) {
    Bookmark.prototype.fromSyncBookmark.call(this, item);
    this.folderName = item.folder || undefined; // empty string -> undefined
    this.queryId = item.query;
  },
};

Object.setPrototypeOf(BookmarkQuery.prototype, Bookmark.prototype);

Utils.deferGetSet(BookmarkQuery, "cleartext", ["folderName", "queryId"]);

export function BookmarkFolder(collection, id, type) {
  PlacesItem.call(this, collection, id, type || "folder");
}

BookmarkFolder.prototype = {
  _logName: "Sync.Record.Folder",

  toSyncBookmark() {
    let info = PlacesItem.prototype.toSyncBookmark.call(this);
    info.description = this.description;
    info.title = this.title;
    return info;
  },

  fromSyncBookmark(item) {
    PlacesItem.prototype.fromSyncBookmark.call(this, item);
    this.title = item.title;
    this.description = item.description;
    this.children = item.childRecordIds;
  },
};

Object.setPrototypeOf(BookmarkFolder.prototype, PlacesItem.prototype);

Utils.deferGetSet(BookmarkFolder, "cleartext", [
  "description",
  "title",
  "children",
]);

export function Livemark(collection, id) {
  BookmarkFolder.call(this, collection, id, "livemark");
}

Livemark.prototype = {
  _logName: "Sync.Record.Livemark",

  toSyncBookmark() {
    let info = BookmarkFolder.prototype.toSyncBookmark.call(this);
    info.feed = this.feedUri;
    info.site = this.siteUri;
    return info;
  },

  fromSyncBookmark(item) {
    BookmarkFolder.prototype.fromSyncBookmark.call(this, item);
    this.feedUri = item.feed.href;
    if (item.site) {
      this.siteUri = item.site.href;
    }
  },
};

Object.setPrototypeOf(Livemark.prototype, BookmarkFolder.prototype);

Utils.deferGetSet(Livemark, "cleartext", ["siteUri", "feedUri"]);

export function BookmarkSeparator(collection, id) {
  PlacesItem.call(this, collection, id, "separator");
}

BookmarkSeparator.prototype = {
  _logName: "Sync.Record.Separator",

  fromSyncBookmark(item) {
    PlacesItem.prototype.fromSyncBookmark.call(this, item);
    this.pos = item.index;
  },
};

Object.setPrototypeOf(BookmarkSeparator.prototype, PlacesItem.prototype);

Utils.deferGetSet(BookmarkSeparator, "cleartext", "pos");

/**
 * The bookmarks engine uses a different store that stages downloaded bookmarks
 * in a separate database, instead of writing directly to Places. The buffer
 * handles reconciliation, so we stub out `_reconcile`, and wait to pull changes
 * until we're ready to upload.
 */
export function BookmarksEngine(service) {
  SyncEngine.call(this, "Bookmarks", service);
}

BookmarksEngine.prototype = {
  _recordObj: PlacesItem,
  _trackerObj: BookmarksTracker,
  _storeObj: BookmarksStore,
  version: 2,
  // Used to override the engine name in telemetry, so that we can distinguish
  // this engine from the old, now removed non-buffered engine.
  overrideTelemetryName: "bookmarks-buffered",

  // Needed to ensure we don't miss items when resuming a sync that failed or
  // aborted early.
  _defaultSort: "oldest",

  syncPriority: 4,
  allowSkippedRecord: false,

  async _ensureCurrentSyncID(newSyncID) {
    await lazy.PlacesSyncUtils.bookmarks.ensureCurrentSyncId(newSyncID);
    let buf = await this._store.ensureOpenMirror();
    await buf.ensureCurrentSyncId(newSyncID);
  },

  async ensureCurrentSyncID(newSyncID) {
    let shouldWipeRemote =
      await lazy.PlacesSyncUtils.bookmarks.shouldWipeRemote();
    if (!shouldWipeRemote) {
      this._log.debug(
        "Checking if server sync ID ${newSyncID} matches existing",
        { newSyncID }
      );
      await this._ensureCurrentSyncID(newSyncID);
      return newSyncID;
    }
    // We didn't take the new sync ID because we need to wipe the server
    // and other clients after a restore. Send the command, wipe the
    // server, and reset our sync ID to reupload everything.
    this._log.debug(
      "Ignoring server sync ID ${newSyncID} after restore; " +
        "wiping server and resetting sync ID",
      { newSyncID }
    );
    await this.service.clientsEngine.sendCommand(
      "wipeEngine",
      [this.name],
      null,
      { reason: "bookmark-restore" }
    );
    let assignedSyncID = await this.resetSyncID();
    return assignedSyncID;
  },

  async getSyncID() {
    return lazy.PlacesSyncUtils.bookmarks.getSyncId();
  },

  async resetSyncID() {
    await this._deleteServerCollection();
    return this.resetLocalSyncID();
  },

  async resetLocalSyncID() {
    let newSyncID = await lazy.PlacesSyncUtils.bookmarks.resetSyncId();
    this._log.debug("Assigned new sync ID ${newSyncID}", { newSyncID });
    let buf = await this._store.ensureOpenMirror();
    await buf.ensureCurrentSyncId(newSyncID);
    return newSyncID;
  },

  async getLastSync() {
    let mirror = await this._store.ensureOpenMirror();
    return mirror.getCollectionHighWaterMark();
  },

  async setLastSync(lastSync) {
    let mirror = await this._store.ensureOpenMirror();
    await mirror.setCollectionLastModified(lastSync);
    // Update the last sync time in Places so that reverting to the original
    // bookmarks engine doesn't download records we've already applied.
    await lazy.PlacesSyncUtils.bookmarks.setLastSync(lastSync);
  },

  async _syncStartup() {
    await super._syncStartup();

    try {
      // For first syncs, back up the user's bookmarks.
      let lastSync = await this.getLastSync();
      if (!lastSync) {
        this._log.debug("Bookmarks backup starting");
        await lazy.PlacesBackups.create(null, true);
        this._log.debug("Bookmarks backup done");
      }
    } catch (ex) {
      // Failure to create a backup is somewhat bad, but probably not bad
      // enough to prevent syncing of bookmarks - so just log the error and
      // continue.
      this._log.warn(
        "Error while backing up bookmarks, but continuing with sync",
        ex
      );
    }
  },

  async _sync() {
    try {
      await super._sync();
      if (this._ranMaintenanceOnLastSync) {
        // If the last sync failed, we ran maintenance, and this sync succeeded,
        // maintenance likely fixed the issue.
        this._ranMaintenanceOnLastSync = false;
        this.service.recordTelemetryEvent("maintenance", "fix", "bookmarks");
      }
    } catch (ex) {
      if (
        lazy.Async.isShutdownException(ex) ||
        ex.status > 0 ||
        ex.name == "InterruptedError"
      ) {
        // Don't run maintenance on shutdown or HTTP errors, or if we aborted
        // the sync because the user changed their bookmarks during merging.
        throw ex;
      }
      if (ex.name == "MergeConflictError") {
        this._log.warn(
          "Bookmark syncing ran into a merge conflict error...will retry later"
        );
        return;
      }
      // Run Places maintenance periodically to try to recover from corruption
      // that might have caused the sync to fail. We cap the interval because
      // persistent failures likely indicate a problem that won't be fixed by
      // running maintenance after every failed sync.
      let elapsedSinceMaintenance =
        Date.now() / 1000 -
        Services.prefs.getIntPref("places.database.lastMaintenance", 0);
      if (elapsedSinceMaintenance >= PLACES_MAINTENANCE_INTERVAL_SECONDS) {
        this._log.error(
          "Bookmark sync failed, ${elapsedSinceMaintenance}s " +
            "elapsed since last run; running Places maintenance",
          { elapsedSinceMaintenance }
        );
        await lazy.PlacesDBUtils.maintenanceOnIdle();
        this._ranMaintenanceOnLastSync = true;
        this.service.recordTelemetryEvent("maintenance", "run", "bookmarks");
      } else {
        this._ranMaintenanceOnLastSync = false;
      }
      throw ex;
    }
  },

  async _syncFinish() {
    await SyncEngine.prototype._syncFinish.call(this);
    await lazy.PlacesSyncUtils.bookmarks.ensureMobileQuery();
  },

  async pullAllChanges() {
    return this.pullNewChanges();
  },

  async trackRemainingChanges() {
    let changes = this._modified.changes;
    await lazy.PlacesSyncUtils.bookmarks.pushChanges(changes);
  },

  _deleteId(id) {
    this._noteDeletedId(id);
  },

  // The bookmarks engine rarely calls this method directly, except in tests or
  // when handling a `reset{All, Engine}` command from another client. We
  // usually reset local Sync metadata on a sync ID mismatch, which both engines
  // override with logic that lives in Places and the mirror.
  async _resetClient() {
    await super._resetClient();
    await lazy.PlacesSyncUtils.bookmarks.reset();
    let buf = await this._store.ensureOpenMirror();
    await buf.reset();
  },

  // Cleans up the Places root, reading list items (ignored in bug 762118,
  // removed in bug 1155684), and pinned sites.
  _shouldDeleteRemotely(incomingItem) {
    return (
      FORBIDDEN_INCOMING_IDS.includes(incomingItem.id) ||
      FORBIDDEN_INCOMING_PARENT_IDS.includes(incomingItem.parentid)
    );
  },

  emptyChangeset() {
    return new BookmarksChangeset();
  },

  async _apply() {
    let buf = await this._store.ensureOpenMirror();
    let watchdog = this._newWatchdog();
    watchdog.start(BOOKMARK_APPLY_TIMEOUT_MS);

    try {
      let recordsToUpload = await buf.apply({
        remoteTimeSeconds: lazy.Resource.serverTime,
        signal: watchdog.signal,
      });
      this._modified.replace(recordsToUpload);
    } finally {
      watchdog.stop();
      if (watchdog.abortReason) {
        this._log.warn(`Aborting bookmark merge: ${watchdog.abortReason}`);
      }
    }
  },

  async _processIncoming(newitems) {
    await super._processIncoming(newitems);
    await this._apply();
  },

  async _reconcile() {
    return true;
  },

  async _createRecord(id) {
    let record = await this._doCreateRecord(id);
    if (!record.deleted) {
      // Set hasDupe on all (non-deleted) records since we don't use it and we
      // want to minimize the risk of older clients corrupting records. Note
      // that the SyncedBookmarksMirror sets it for all records that it created,
      // but we would like to ensure that weakly uploaded records are marked as
      // hasDupe as well.
      record.hasDupe = true;
    }
    return record;
  },

  async _doCreateRecord(id) {
    let change = this._modified.changes[id];
    if (!change) {
      this._log.error(
        "Creating record for item ${id} not in strong changeset",
        { id }
      );
      throw new TypeError("Can't create record for unchanged item");
    }
    let record = this._recordFromCleartext(id, change.cleartext);
    record.sortindex = await this._store._calculateIndex(record);
    return record;
  },

  _recordFromCleartext(id, cleartext) {
    let recordObj = getTypeObject(cleartext.type);
    if (!recordObj) {
      this._log.warn(
        "Creating record for item ${id} with unknown type ${type}",
        { id, type: cleartext.type }
      );
      recordObj = PlacesItem;
    }
    let record = new recordObj(this.name, id);
    record.cleartext = cleartext;
    return record;
  },

  async pullChanges() {
    return {};
  },

  /**
   * Writes successfully uploaded records back to the mirror, so that the
   * mirror matches the server. We update the mirror before updating Places,
   * which has implications for interrupted syncs.
   *
   * 1. Sync interrupted during upload; server doesn't support atomic uploads.
   *    We'll download and reapply everything that we uploaded before the
   *    interruption. All locally changed items retain their change counters.
   * 2. Sync interrupted during upload; atomic uploads enabled. The server
   *    discards the batch. All changed local items retain their change
   *    counters, so the next sync resumes cleanly.
   * 3. Sync interrupted during upload; outgoing records can't fit in a single
   *    batch. We'll download and reapply all records through the most recent
   *    committed batch. This is a variation of (1).
   * 4. Sync interrupted after we update the mirror, but before cleanup. The
   *    mirror matches the server, but locally changed items retain their change
   *    counters. Reuploading them on the next sync should be idempotent, though
   *    unnecessary. If another client makes a conflicting remote change before
   *    we sync again, we may incorrectly prefer the local state.
   * 5. Sync completes successfully. We'll update the mirror, and reset the
   *    change counters for all items.
   */
  async _onRecordsWritten(succeeded, failed, serverModifiedTime) {
    let records = [];
    for (let id of succeeded) {
      let change = this._modified.changes[id];
      if (!change) {
        // TODO (Bug 1433178): Write weakly uploaded records back to the mirror.
        this._log.info("Uploaded record not in strong changeset", id);
        continue;
      }
      if (!change.synced) {
        this._log.info("Record in strong changeset not uploaded", id);
        continue;
      }
      let cleartext = change.cleartext;
      if (!cleartext) {
        this._log.error(
          "Missing Sync record cleartext for ${id} in ${change}",
          { id, change }
        );
        throw new TypeError("Missing cleartext for uploaded Sync record");
      }
      let record = this._recordFromCleartext(id, cleartext);
      record.modified = serverModifiedTime;
      records.push(record);
    }
    let buf = await this._store.ensureOpenMirror();
    await buf.store(records, { needsMerge: false });
  },

  async finalize() {
    await super.finalize();
    await this._store.finalize();
  },
};

Object.setPrototypeOf(BookmarksEngine.prototype, SyncEngine.prototype);

/**
 * The bookmarks store delegates to the mirror for staging and applying
 * records. Most `Store` methods intentionally remain abstract, so you can't use
 * this store to create or update bookmarks in Places. All changes must go
 * through the mirror, which takes care of merging and producing a valid tree.
 */
function BookmarksStore(name, engine) {
  Store.call(this, name, engine);
}

BookmarksStore.prototype = {
  _openMirrorPromise: null,

  // For tests.
  _batchChunkSize: 500,

  // Create a record starting from the weave id (places guid)
  async createRecord(id, collection) {
    let item = await lazy.PlacesSyncUtils.bookmarks.fetch(id);
    if (!item) {
      // deleted item
      let record = new PlacesItem(collection, id);
      record.deleted = true;
      return record;
    }

    let recordObj = getTypeObject(item.kind);
    if (!recordObj) {
      this._log.warn("Unknown item type, cannot serialize: " + item.kind);
      recordObj = PlacesItem;
    }
    let record = new recordObj(collection, id);
    record.fromSyncBookmark(item);

    record.sortindex = await this._calculateIndex(record);

    return record;
  },

  async _calculateIndex(record) {
    // Ensure folders have a very high sort index so they're not synced last.
    if (record.type == "folder") {
      return FOLDER_SORTINDEX;
    }

    // For anything directly under the toolbar, give it a boost of more than an
    // unvisited bookmark
    let index = 0;
    if (record.parentid == "toolbar") {
      index += 150;
    }

    // Add in the bookmark's frecency if we have something.
    if (record.bmkUri != null) {
      let frecency = FRECENCY_UNKNOWN;
      try {
        frecency = await lazy.PlacesSyncUtils.history.fetchURLFrecency(
          record.bmkUri
        );
      } catch (ex) {
        this._log.warn(
          `Failed to fetch frecency for ${record.id}; assuming default`,
          ex
        );
        this._log.trace("Record {id} has invalid URL ${bmkUri}", record);
      }
      if (frecency != FRECENCY_UNKNOWN) {
        index += frecency;
      }
    }

    return index;
  },

  async wipe() {
    // Save a backup before clearing out all bookmarks.
    await lazy.PlacesBackups.create(null, true);
    await lazy.PlacesSyncUtils.bookmarks.wipe();
  },

  ensureOpenMirror() {
    if (!this._openMirrorPromise) {
      this._openMirrorPromise = this._openMirror().catch(err => {
        // We may have failed to open the mirror temporarily; for example, if
        // the database is locked. Clear the promise so that subsequent
        // `ensureOpenMirror` calls can try to open the mirror again.
        this._openMirrorPromise = null;
        throw err;
      });
    }
    return this._openMirrorPromise;
  },

  async _openMirror() {
    let mirrorPath = PathUtils.join(
      PathUtils.profileDir,
      "weave",
      "bookmarks.sqlite"
    );
    await IOUtils.makeDirectory(PathUtils.parent(mirrorPath), {
      createAncestors: true,
    });

    return lazy.SyncedBookmarksMirror.open({
      path: mirrorPath,
      recordStepTelemetry: (name, took, counts) => {
        lazy.Observers.notify(
          "weave:engine:sync:step",
          {
            name,
            took,
            counts,
          },
          this.name
        );
      },
      recordValidationTelemetry: (took, checked, problems) => {
        lazy.Observers.notify(
          "weave:engine:validate:finish",
          {
            version: BOOKMARK_VALIDATOR_VERSION,
            took,
            checked,
            problems,
          },
          this.name
        );
      },
    });
  },

  async applyIncomingBatch(records) {
    let buf = await this.ensureOpenMirror();
    for (let chunk of lazy.PlacesUtils.chunkArray(
      records,
      this._batchChunkSize
    )) {
      await buf.store(chunk);
    }
    // Array of failed records.
    return [];
  },

  async applyIncoming(record) {
    let buf = await this.ensureOpenMirror();
    await buf.store([record]);
  },

  async finalize() {
    if (!this._openMirrorPromise) {
      return;
    }
    let buf = await this._openMirrorPromise;
    await buf.finalize();
  },
};

Object.setPrototypeOf(BookmarksStore.prototype, Store.prototype);

// The bookmarks tracker is a special flower. Instead of listening for changes
// via observer notifications, it queries Places for the set of items that have
// changed since the last sync. Because it's a "pull-based" tracker, it ignores
// all concepts of "add a changed ID." However, it still registers an observer
// to bump the score, so that changed bookmarks are synced immediately.
function BookmarksTracker(name, engine) {
  Tracker.call(this, name, engine);
}
BookmarksTracker.prototype = {
  onStart() {
    this._placesListener = new PlacesWeakCallbackWrapper(
      this.handlePlacesEvents.bind(this)
    );
    lazy.PlacesUtils.observers.addListener(
      [
        "bookmark-added",
        "bookmark-removed",
        "bookmark-moved",
        "bookmark-guid-changed",
        "bookmark-keyword-changed",
        "bookmark-tags-changed",
        "bookmark-time-changed",
        "bookmark-title-changed",
        "bookmark-url-changed",
      ],
      this._placesListener
    );
    Svc.Obs.add("bookmarks-restore-begin", this);
    Svc.Obs.add("bookmarks-restore-success", this);
    Svc.Obs.add("bookmarks-restore-failed", this);
  },

  onStop() {
    lazy.PlacesUtils.observers.removeListener(
      [
        "bookmark-added",
        "bookmark-removed",
        "bookmark-moved",
        "bookmark-guid-changed",
        "bookmark-keyword-changed",
        "bookmark-tags-changed",
        "bookmark-time-changed",
        "bookmark-title-changed",
        "bookmark-url-changed",
      ],
      this._placesListener
    );
    Svc.Obs.remove("bookmarks-restore-begin", this);
    Svc.Obs.remove("bookmarks-restore-success", this);
    Svc.Obs.remove("bookmarks-restore-failed", this);
  },

  async getChangedIDs() {
    return lazy.PlacesSyncUtils.bookmarks.pullChanges();
  },

  observe(subject, topic, data) {
    switch (topic) {
      case "bookmarks-restore-begin":
        this._log.debug("Ignoring changes from importing bookmarks.");
        break;
      case "bookmarks-restore-success":
        this._log.debug("Tracking all items on successful import.");

        if (data == "json") {
          this._log.debug(
            "Restore succeeded: wiping server and other clients."
          );
          // Trigger an immediate sync. `ensureCurrentSyncID` will notice we
          // restored, wipe the server and other clients, reset the sync ID, and
          // upload the restored tree.
          this.score += SCORE_INCREMENT_XLARGE;
        } else {
          // "html", "html-initial", or "json-append"
          this._log.debug("Import succeeded.");
        }
        break;
      case "bookmarks-restore-failed":
        this._log.debug("Tracking all items on failed import.");
        break;
    }
  },

  QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]),

  /* Every add/remove/change will trigger a sync for MULTI_DEVICE */
  _upScore: function BMT__upScore() {
    this.score += SCORE_INCREMENT_XLARGE;
  },

  handlePlacesEvents(events) {
    for (let event of events) {
      switch (event.type) {
        case "bookmark-added":
        case "bookmark-removed":
        case "bookmark-moved":
        case "bookmark-keyword-changed":
        case "bookmark-tags-changed":
        case "bookmark-time-changed":
        case "bookmark-title-changed":
        case "bookmark-url-changed":
          if (lazy.IGNORED_SOURCES.includes(event.source)) {
            continue;
          }

          this._log.trace(`'${event.type}': ${event.id}`);
          this._upScore();
          break;
        case "bookmark-guid-changed":
          if (event.source !== lazy.PlacesUtils.bookmarks.SOURCES.SYNC) {
            this._log.warn(
              "The source of bookmark-guid-changed event shoud be sync."
            );
            continue;
          }

          this._log.trace(`'${event.type}': ${event.id}`);
          this._upScore();
          break;
        case "purge-caches":
          this._log.trace("purge-caches");
          this._upScore();
          break;
      }
    }
  },
};

Object.setPrototypeOf(BookmarksTracker.prototype, Tracker.prototype);

/**
 * A changeset that stores extra metadata in a change record for each ID. The
 * engine updates this metadata when uploading Sync records, and writes it back
 * to Places in `BookmarksEngine#trackRemainingChanges`.
 *
 * The `synced` property on a change record means its corresponding item has
 * been uploaded, and we should pretend it doesn't exist in the changeset.
 */
class BookmarksChangeset extends Changeset {
  // Only `_reconcile` calls `getModifiedTimestamp` and `has`, and the engine
  // does its own reconciliation.
  getModifiedTimestamp() {
    throw new Error("Don't use timestamps to resolve bookmark conflicts");
  }

  has() {
    throw new Error("Don't use the changeset to resolve bookmark conflicts");
  }

  delete(id) {
    let change = this.changes[id];
    if (change) {
      // Mark the change as synced without removing it from the set. We do this
      // so that we can update Places in `trackRemainingChanges`.
      change.synced = true;
    }
  }

  ids() {
    let results = new Set();
    for (let id in this.changes) {
      if (!this.changes[id].synced) {
        results.add(id);
      }
    }
    return [...results];
  }
}
PK
!<L��-modules/services-sync/engines/clients.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * How does the clients engine work?
 *
 * - We use 2 files - commands.json and commands-syncing.json.
 *
 * - At sync upload time, we attempt a rename of commands.json to
 *   commands-syncing.json, and ignore errors (helps for crash during sync!).
 * - We load commands-syncing.json and stash the contents in
 *   _currentlySyncingCommands which lives for the duration of the upload process.
 * - We use _currentlySyncingCommands to build the outgoing records
 * - Immediately after successful upload, we delete commands-syncing.json from
 *   disk (and clear _currentlySyncingCommands). We reconcile our local records
 *   with what we just wrote in the server, and add failed IDs commands
 *   back in commands.json
 * - Any time we need to "save" a command for future syncs, we load
 *   commands.json, update it, and write it back out.
 */

import { Async } from "resource://services-common/async.sys.mjs";

import {
  DEVICE_TYPE_DESKTOP,
  DEVICE_TYPE_MOBILE,
  DEVICE_TYPE_TABLET,
  SINGLE_USER_THRESHOLD,
  SYNC_API_VERSION,
} from "resource://services-sync/constants.sys.mjs";

import {
  Store,
  SyncEngine,
  LegacyTracker,
} from "resource://services-sync/engines.sys.mjs";
import { CryptoWrapper } from "resource://services-sync/record.sys.mjs";
import { Resource } from "resource://services-sync/resource.sys.mjs";
import { Svc, Utils } from "resource://services-sync/util.sys.mjs";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => {
  return ChromeUtils.importESModule(
    "resource://gre/modules/FxAccounts.sys.mjs"
  ).getFxAccountsSingleton();
});

import { PREF_ACCOUNT_ROOT } from "resource://gre/modules/FxAccountsCommon.sys.mjs";

const CLIENTS_TTL = 15552000; // 180 days
const CLIENTS_TTL_REFRESH = 604800; // 7 days
const STALE_CLIENT_REMOTE_AGE = 604800; // 7 days

// TTL of the message sent to another device when sending a tab
const NOTIFY_TAB_SENT_TTL_SECS = 1 * 3600; // 1 hour

// How often we force a refresh of the FxA device list.
const REFRESH_FXA_DEVICE_INTERVAL_MS = 2 * 60 * 60 * 1000; // 2 hours

// Reasons behind sending collection_changed push notifications.
const COLLECTION_MODIFIED_REASON_SENDTAB = "sendtab";
const COLLECTION_MODIFIED_REASON_FIRSTSYNC = "firstsync";

const SUPPORTED_PROTOCOL_VERSIONS = [SYNC_API_VERSION];
const LAST_MODIFIED_ON_PROCESS_COMMAND_PREF =
  "services.sync.clients.lastModifiedOnProcessCommands";

function hasDupeCommand(commands, action) {
  if (!commands) {
    return false;
  }
  return commands.some(
    other =>
      other.command == action.command &&
      Utils.deepEquals(other.args, action.args)
  );
}

export function ClientsRec(collection, id) {
  CryptoWrapper.call(this, collection, id);
}

ClientsRec.prototype = {
  _logName: "Sync.Record.Clients",
  ttl: CLIENTS_TTL,
};
Object.setPrototypeOf(ClientsRec.prototype, CryptoWrapper.prototype);

Utils.deferGetSet(ClientsRec, "cleartext", [
  "name",
  "type",
  "commands",
  "version",
  "protocols",
  "formfactor",
  "os",
  "appPackage",
  "application",
  "device",
  "fxaDeviceId",
]);

export function ClientEngine(service) {
  SyncEngine.call(this, "Clients", service);

  this.fxAccounts = lazy.fxAccounts;
  this.addClientCommandQueue = Async.asyncQueueCaller(this._log);
  Utils.defineLazyIDProperty(this, "localID", "services.sync.client.GUID");
}

ClientEngine.prototype = {
  _storeObj: ClientStore,
  _recordObj: ClientsRec,
  _trackerObj: ClientsTracker,
  allowSkippedRecord: false,
  _knownStaleFxADeviceIds: null,
  _lastDeviceCounts: null,
  _lastFxaDeviceRefresh: 0,

  async initialize() {
    // Reset the last sync timestamp on every startup so that we fetch all clients
    await this.resetLastSync();
  },

  // These two properties allow us to avoid replaying the same commands
  // continuously if we cannot manage to upload our own record.
  _localClientLastModified: 0,
  get _lastModifiedOnProcessCommands() {
    return Services.prefs.getIntPref(LAST_MODIFIED_ON_PROCESS_COMMAND_PREF, -1);
  },

  set _lastModifiedOnProcessCommands(value) {
    Services.prefs.setIntPref(LAST_MODIFIED_ON_PROCESS_COMMAND_PREF, value);
  },

  get isFirstSync() {
    return !this.lastRecordUpload;
  },

  // Always sync client data as it controls other sync behavior
  get enabled() {
    return true;
  },

  get lastRecordUpload() {
    return Svc.PrefBranch.getIntPref(this.name + ".lastRecordUpload", 0);
  },
  set lastRecordUpload(value) {
    Svc.PrefBranch.setIntPref(
      this.name + ".lastRecordUpload",
      Math.floor(value)
    );
  },

  get remoteClients() {
    // return all non-stale clients for external consumption.
    return Object.values(this._store._remoteClients).filter(v => !v.stale);
  },

  remoteClient(id) {
    let client = this._store._remoteClients[id];
    return client && !client.stale ? client : null;
  },

  remoteClientExists(id) {
    return !!this.remoteClient(id);
  },

  // Aggregate some stats on the composition of clients on this account
  get stats() {
    const ALL_MOBILE_TYPES = [DEVICE_TYPE_MOBILE, DEVICE_TYPE_TABLET];
    let stats = {
      // Currently this should never be true as this code only runs on Desktop, but
      // it doesn't cause harm.
      hasMobile: ALL_MOBILE_TYPES.includes(this.localType),
      names: [this.localName],
      numClients: 1,
    };

    for (let id in this._store._remoteClients) {
      let { name, type, stale } = this._store._remoteClients[id];
      if (!stale) {
        stats.hasMobile = stats.hasMobile || ALL_MOBILE_TYPES.includes(type);
        stats.names.push(name);
        stats.numClients++;
      }
    }

    return stats;
  },

  /**
   * Obtain information about device types.
   *
   * Returns a Map of device types to integer counts. Guaranteed to include
   * "desktop" (which will have at least 1 - this device) and "mobile" (which
   * may have zero) counts. It almost certainly will include only these 2.
   */
  get deviceTypes() {
    let counts = new Map();

    counts.set(this.localType, 1); // currently this must be DEVICE_TYPE_DESKTOP
    counts.set(DEVICE_TYPE_MOBILE, 0);

    for (let id in this._store._remoteClients) {
      let record = this._store._remoteClients[id];
      if (record.stale) {
        continue; // pretend "stale" records don't exist.
      }
      let type = record.type;
      // "tablet" and "mobile" are combined.
      if (type == DEVICE_TYPE_TABLET) {
        type = DEVICE_TYPE_MOBILE;
      }
      if (!counts.has(type)) {
        counts.set(type, 0);
      }

      counts.set(type, counts.get(type) + 1);
    }

    return counts;
  },

  get brandName() {
    let brand = Services.strings.createBundle(
      "chrome://branding/locale/brand.properties"
    );
    return brand.GetStringFromName("brandShortName");
  },

  get localName() {
    return this.fxAccounts.device.getLocalName();
  },
  set localName(value) {
    this.fxAccounts.device.setLocalName(value);
  },

  get localType() {
    return this.fxAccounts.device.getLocalType();
  },

  getClientName(id) {
    if (id == this.localID) {
      return this.localName;
    }
    let client = this._store._remoteClients[id];
    if (!client) {
      return "";
    }
    // Sometimes the sync clients don't always correctly update the device name
    // However FxA always does, so try to pull the name from there first
    let fxaDevice = this.fxAccounts.device.recentDeviceList?.find(
      device => device.id === client.fxaDeviceId
    );

    // should be very rare, but could happen if we have yet to fetch devices,
    // or the client recently disconnected
    if (!fxaDevice) {
      this._log.warn(
        "Couldn't find associated FxA device, falling back to client name"
      );
      return client.name;
    }
    return fxaDevice.name;
  },

  getClientFxaDeviceId(id) {
    if (this._store._remoteClients[id]) {
      return this._store._remoteClients[id].fxaDeviceId;
    }
    return null;
  },

  getClientByFxaDeviceId(fxaDeviceId) {
    for (let id in this._store._remoteClients) {
      let client = this._store._remoteClients[id];
      if (client.stale) {
        continue;
      }
      if (client.fxaDeviceId == fxaDeviceId) {
        return client;
      }
    }
    return null;
  },

  getClientType(id) {
    const client = this._store._remoteClients[id];
    if (client.type == DEVICE_TYPE_DESKTOP) {
      return "desktop";
    }
    if (client.formfactor && client.formfactor.includes("tablet")) {
      return "tablet";
    }
    return "phone";
  },

  async _readCommands() {
    let commands = await Utils.jsonLoad("commands", this);
    return commands || {};
  },

  /**
   * Low level function, do not use directly (use _addClientCommand instead).
   */
  async _saveCommands(commands) {
    try {
      await Utils.jsonSave("commands", this, commands);
    } catch (error) {
      this._log.error("Failed to save JSON outgoing commands", error);
    }
  },

  async _prepareCommandsForUpload() {
    try {
      await Utils.jsonMove("commands", "commands-syncing", this);
    } catch (e) {
      // Ignore errors
    }
    let commands = await Utils.jsonLoad("commands-syncing", this);
    return commands || {};
  },

  async _deleteUploadedCommands() {
    delete this._currentlySyncingCommands;
    try {
      await Utils.jsonRemove("commands-syncing", this);
    } catch (err) {
      this._log.error("Failed to delete syncing-commands file", err);
    }
  },

  // Gets commands for a client we are yet to write to the server. Doesn't
  // include commands for that client which are already on the server.
  // We should rename this!
  async getClientCommands(clientId) {
    const allCommands = await this._readCommands();
    return allCommands[clientId] || [];
  },

  async removeLocalCommand(command) {
    // the implementation of this engine is such that adding a command to
    // the local client is how commands are deleted! ¯\_(ツ)_/¯
    await this._addClientCommand(this.localID, command);
  },

  async _addClientCommand(clientId, command) {
    this.addClientCommandQueue.enqueueCall(async () => {
      try {
        const localCommands = await this._readCommands();
        const localClientCommands = localCommands[clientId] || [];
        const remoteClient = this._store._remoteClients[clientId];
        let remoteClientCommands = [];
        if (remoteClient && remoteClient.commands) {
          remoteClientCommands = remoteClient.commands;
        }
        const clientCommands = localClientCommands.concat(remoteClientCommands);
        if (hasDupeCommand(clientCommands, command)) {
          return false;
        }
        localCommands[clientId] = localClientCommands.concat(command);
        await this._saveCommands(localCommands);
        return true;
      } catch (e) {
        // Failing to save a command should not "break the queue" of pending operations.
        this._log.error(e);
        return false;
      }
    });

    return this.addClientCommandQueue.promiseCallsComplete();
  },

  async _removeClientCommands(clientId) {
    const allCommands = await this._readCommands();
    delete allCommands[clientId];
    await this._saveCommands(allCommands);
  },

  async updateKnownStaleClients() {
    this._log.debug("Updating the known stale clients");
    // _fetchFxADevices side effect updates this._knownStaleFxADeviceIds.
    await this._fetchFxADevices();
    let localFxADeviceId = await lazy.fxAccounts.device.getLocalId();
    // Process newer records first, so that if we hit a record with a device ID
    // we've seen before, we can mark it stale immediately.
    let clientList = Object.values(this._store._remoteClients).sort(
      (a, b) => b.serverLastModified - a.serverLastModified
    );
    let seenDeviceIds = new Set([localFxADeviceId]);
    for (let client of clientList) {
      // Clients might not have an `fxaDeviceId` if they fail the FxA
      // registration process.
      if (!client.fxaDeviceId) {
        continue;
      }
      if (this._knownStaleFxADeviceIds.includes(client.fxaDeviceId)) {
        this._log.info(
          `Hiding stale client ${client.id} - in known stale clients list`
        );
        client.stale = true;
      } else if (seenDeviceIds.has(client.fxaDeviceId)) {
        this._log.info(
          `Hiding stale client ${client.id}` +
            ` - duplicate device id ${client.fxaDeviceId}`
        );
        client.stale = true;
      } else {
        seenDeviceIds.add(client.fxaDeviceId);
      }
    }
  },

  async _fetchFxADevices() {
    // We only force a refresh periodically to keep the load on the servers
    // down, and because we expect FxA to have received a push message in
    // most cases when the FxA device list would have changed. For this reason
    // we still go ahead and check the stale list even if we didn't force a
    // refresh.
    let now = this.fxAccounts._internal.now(); // tests mock this .now() impl.
    if (now - REFRESH_FXA_DEVICE_INTERVAL_MS > this._lastFxaDeviceRefresh) {
      this._lastFxaDeviceRefresh = now;
      try {
        await this.fxAccounts.device.refreshDeviceList();
      } catch (e) {
        this._log.error("Could not refresh the FxA device list", e);
      }
    }

    // We assume that clients not present in the FxA Device Manager list have been
    // disconnected and so are stale
    this._log.debug("Refreshing the known stale clients list");
    let localClients = Object.values(this._store._remoteClients)
      .filter(client => client.fxaDeviceId) // iOS client records don't have fxaDeviceId
      .map(client => client.fxaDeviceId);
    const fxaClients = this.fxAccounts.device.recentDeviceList
      ? this.fxAccounts.device.recentDeviceList.map(device => device.id)
      : [];
    this._knownStaleFxADeviceIds = Utils.arraySub(localClients, fxaClients);
  },

  async _syncStartup() {
    // Reupload new client record periodically.
    if (Date.now() / 1000 - this.lastRecordUpload > CLIENTS_TTL_REFRESH) {
      await this._tracker.addChangedID(this.localID);
    }
    return SyncEngine.prototype._syncStartup.call(this);
  },

  async _processIncoming() {
    // Fetch all records from the server.
    await this.resetLastSync();
    this._incomingClients = {};
    try {
      await SyncEngine.prototype._processIncoming.call(this);
      // Update FxA Device list.
      await this._fetchFxADevices();
      // Since clients are synced unconditionally, any records in the local store
      // that don't exist on the server must be for disconnected clients. Remove
      // them, so that we don't upload records with commands for clients that will
      // never see them. We also do this to filter out stale clients from the
      // tabs collection, since showing their list of tabs is confusing.
      for (let id in this._store._remoteClients) {
        if (!this._incomingClients[id]) {
          this._log.info(`Removing local state for deleted client ${id}`);
          await this._removeRemoteClient(id);
        }
      }
      let localFxADeviceId = await lazy.fxAccounts.device.getLocalId();
      // Bug 1264498: Mobile clients don't remove themselves from the clients
      // collection when the user disconnects Sync, so we mark as stale clients
      // with the same name that haven't synced in over a week.
      // (Note we can't simply delete them, or we re-apply them next sync - see
      // bug 1287687)
      this._localClientLastModified = Math.round(
        this._incomingClients[this.localID]
      );
      delete this._incomingClients[this.localID];
      let names = new Set([this.localName]);
      let seenDeviceIds = new Set([localFxADeviceId]);
      let idToLastModifiedList = Object.entries(this._incomingClients).sort(
        (a, b) => b[1] - a[1]
      );
      for (let [id, serverLastModified] of idToLastModifiedList) {
        let record = this._store._remoteClients[id];
        // stash the server last-modified time on the record.
        record.serverLastModified = serverLastModified;
        if (
          record.fxaDeviceId &&
          this._knownStaleFxADeviceIds.includes(record.fxaDeviceId)
        ) {
          this._log.info(
            `Hiding stale client ${id} - in known stale clients list`
          );
          record.stale = true;
        }
        if (!names.has(record.name)) {
          if (record.fxaDeviceId) {
            seenDeviceIds.add(record.fxaDeviceId);
          }
          names.add(record.name);
          continue;
        }
        let remoteAge = Resource.serverTime - this._incomingClients[id];
        if (remoteAge > STALE_CLIENT_REMOTE_AGE) {
          this._log.info(`Hiding stale client ${id} with age ${remoteAge}`);
          record.stale = true;
          continue;
        }
        if (record.fxaDeviceId && seenDeviceIds.has(record.fxaDeviceId)) {
          this._log.info(
            `Hiding stale client ${record.id}` +
              ` - duplicate device id ${record.fxaDeviceId}`
          );
          record.stale = true;
        } else if (record.fxaDeviceId) {
          seenDeviceIds.add(record.fxaDeviceId);
        }
      }
    } finally {
      this._incomingClients = null;
    }
  },

  async _uploadOutgoing() {
    this._currentlySyncingCommands = await this._prepareCommandsForUpload();
    const clientWithPendingCommands = Object.keys(
      this._currentlySyncingCommands
    );
    for (let clientId of clientWithPendingCommands) {
      if (this._store._remoteClients[clientId] || this.localID == clientId) {
        this._modified.set(clientId, 0);
      }
    }
    let updatedIDs = this._modified.ids();
    await SyncEngine.prototype._uploadOutgoing.call(this);
    // Record the response time as the server time for each item we uploaded.
    let lastSync = await this.getLastSync();
    for (let id of updatedIDs) {
      if (id == this.localID) {
        this.lastRecordUpload = lastSync;
      } else {
        this._store._remoteClients[id].serverLastModified = lastSync;
      }
    }
  },

  async _onRecordsWritten(succeeded, failed) {
    // Reconcile the status of the local records with what we just wrote on the
    // server
    for (let id of succeeded) {
      const commandChanges = this._currentlySyncingCommands[id];
      if (id == this.localID) {
        if (this.isFirstSync) {
          this._log.info(
            "Uploaded our client record for the first time, notifying other clients."
          );
          this._notifyClientRecordUploaded();
        }
        if (this.localCommands) {
          this.localCommands = this.localCommands.filter(
            command => !hasDupeCommand(commandChanges, command)
          );
        }
      } else {
        const clientRecord = this._store._remoteClients[id];
        if (!commandChanges || !clientRecord) {
          // should be impossible, else we wouldn't have been writing it.
          this._log.warn(
            "No command/No record changes for a client we uploaded"
          );
          continue;
        }
        // fixup the client record, so our copy of _remoteClients matches what we uploaded.
        this._store._remoteClients[id] = await this._store.createRecord(id);
        // we could do better and pass the reference to the record we just uploaded,
        // but this will do for now
      }
    }

    // Re-add failed commands
    for (let id of failed) {
      const commandChanges = this._currentlySyncingCommands[id];
      if (!commandChanges) {
        continue;
      }
      await this._addClientCommand(id, commandChanges);
    }

    await this._deleteUploadedCommands();

    // Notify other devices that their own client collection changed
    const idsToNotify = succeeded.reduce((acc, id) => {
      if (id == this.localID) {
        return acc;
      }
      const fxaDeviceId = this.getClientFxaDeviceId(id);
      return fxaDeviceId ? acc.concat(fxaDeviceId) : acc;
    }, []);
    if (idsToNotify.length) {
      this._notifyOtherClientsModified(idsToNotify);
    }
  },

  _notifyOtherClientsModified(ids) {
    // We are not waiting on this promise on purpose.
    this._notifyCollectionChanged(
      ids,
      NOTIFY_TAB_SENT_TTL_SECS,
      COLLECTION_MODIFIED_REASON_SENDTAB
    );
  },

  _notifyClientRecordUploaded() {
    // We are not waiting on this promise on purpose.
    this._notifyCollectionChanged(
      null,
      0,
      COLLECTION_MODIFIED_REASON_FIRSTSYNC
    );
  },

  /**
   * @param {?string[]} ids FxA Client IDs to notify. null means everyone else.
   * @param {number} ttl TTL of the push notification.
   * @param {string} reason Reason for sending this push notification.
   */
  async _notifyCollectionChanged(ids, ttl, reason) {
    const message = {
      version: 1,
      command: "sync:collection_changed",
      data: {
        collections: ["clients"],
        reason,
      },
    };
    let excludedIds = null;
    if (!ids) {
      const localFxADeviceId = await lazy.fxAccounts.device.getLocalId();
      excludedIds = [localFxADeviceId];
    }
    try {
      await this.fxAccounts.notifyDevices(ids, excludedIds, message, ttl);
    } catch (e) {
      this._log.error("Could not notify of changes in the collection", e);
    }
  },

  async _syncFinish() {
    // Record histograms for our device types, and also write them to a pref
    // so non-histogram telemetry (eg, UITelemetry) and the sync scheduler
    // has easy access to them, and so they are accurate even before we've
    // successfully synced the first time after startup.
    let deviceTypeCounts = this.deviceTypes;
    for (let [deviceType, count] of deviceTypeCounts) {
      let hid;
      let prefName = this.name + ".devices.";
      switch (deviceType) {
        case DEVICE_TYPE_DESKTOP:
          hid = "WEAVE_DEVICE_COUNT_DESKTOP";
          prefName += "desktop";
          break;
        case DEVICE_TYPE_MOBILE:
        case DEVICE_TYPE_TABLET:
          hid = "WEAVE_DEVICE_COUNT_MOBILE";
          prefName += "mobile";
          break;
        default:
          this._log.warn(
            `Unexpected deviceType "${deviceType}" recording device telemetry.`
          );
          continue;
      }
      Services.telemetry.getHistogramById(hid).add(count);
      // Optimization: only write the pref if it changed since our last sync.
      if (
        this._lastDeviceCounts == null ||
        this._lastDeviceCounts.get(prefName) != count
      ) {
        Svc.PrefBranch.setIntPref(prefName, count);
      }
    }
    this._lastDeviceCounts = deviceTypeCounts;
    return SyncEngine.prototype._syncFinish.call(this);
  },

  async _reconcile(item) {
    // Every incoming record is reconciled, so we use this to track the
    // contents of the collection on the server.
    this._incomingClients[item.id] = item.modified;

    if (!(await this._store.itemExists(item.id))) {
      return true;
    }
    // Clients are synced unconditionally, so we'll always have new records.
    // Unfortunately, this will cause the scheduler to use the immediate sync
    // interval for the multi-device case, instead of the active interval. We
    // work around this by updating the record during reconciliation, and
    // returning false to indicate that the record doesn't need to be applied
    // later.
    await this._store.update(item);
    return false;
  },

  // Treat reset the same as wiping for locally cached clients
  async _resetClient() {
    await this._wipeClient();
  },

  async _wipeClient() {
    await SyncEngine.prototype._resetClient.call(this);
    this._knownStaleFxADeviceIds = null;
    delete this.localCommands;
    await this._store.wipe();
    try {
      await Utils.jsonRemove("commands", this);
    } catch (err) {
      this._log.warn("Could not delete commands.json", err);
    }
    try {
      await Utils.jsonRemove("commands-syncing", this);
    } catch (err) {
      this._log.warn("Could not delete commands-syncing.json", err);
    }
  },

  async removeClientData() {
    let res = this.service.resource(this.engineURL + "/" + this.localID);
    await res.delete();
  },

  // Override the default behavior to delete bad records from the server.
  async handleHMACMismatch(item, mayRetry) {
    this._log.debug("Handling HMAC mismatch for " + item.id);

    let base = await SyncEngine.prototype.handleHMACMismatch.call(
      this,
      item,
      mayRetry
    );
    if (base != SyncEngine.kRecoveryStrategy.error) {
      return base;
    }

    // It's a bad client record. Save it to be deleted at the end of the sync.
    this._log.debug("Bad client record detected. Scheduling for deletion.");
    await this._deleteId(item.id);

    // Neither try again nor error; we're going to delete it.
    return SyncEngine.kRecoveryStrategy.ignore;
  },

  /**
   * A hash of valid commands that the client knows about. The key is a command
   * and the value is a hash containing information about the command such as
   * number of arguments, description, and importance (lower importance numbers
   * indicate higher importance.
   */
  _commands: {
    resetAll: {
      args: 0,
      importance: 0,
      desc: "Clear temporary local data for all engines",
    },
    resetEngine: {
      args: 1,
      importance: 0,
      desc: "Clear temporary local data for engine",
    },
    wipeEngine: {
      args: 1,
      importance: 0,
      desc: "Delete all client data for engine",
    },
    logout: { args: 0, importance: 0, desc: "Log out client" },
  },

  /**
   * Sends a command+args pair to a specific client.
   *
   * @param command Command string
   * @param args Array of arguments/data for command
   * @param clientId Client to send command to
   */
  async _sendCommandToClient(command, args, clientId, telemetryExtra) {
    this._log.trace("Sending " + command + " to " + clientId);

    let client = this._store._remoteClients[clientId];
    if (!client) {
      throw new Error("Unknown remote client ID: '" + clientId + "'.");
    }
    if (client.stale) {
      throw new Error("Stale remote client ID: '" + clientId + "'.");
    }

    let action = {
      command,
      args,
      // We send the flowID to the other client so *it* can report it in its
      // telemetry - we record it in ours below.
      flowID: telemetryExtra.flowID,
    };

    if (await this._addClientCommand(clientId, action)) {
      this._log.trace(`Client ${clientId} got a new action`, [command, args]);
      await this._tracker.addChangedID(clientId);
      try {
        telemetryExtra.deviceID =
          this.service.identity.hashedDeviceID(clientId);
      } catch (_) {}

      this.service.recordTelemetryEvent(
        "sendcommand",
        command,
        undefined,
        telemetryExtra
      );
    } else {
      this._log.trace(`Client ${clientId} got a duplicate action`, [
        command,
        args,
      ]);
    }
  },

  /**
   * Check if the local client has any remote commands and perform them.
   *
   * @return false to abort sync
   */
  async processIncomingCommands() {
    return this._notify("clients:process-commands", "", async function () {
      if (
        !this.localCommands ||
        (this._lastModifiedOnProcessCommands == this._localClientLastModified &&
          !this.ignoreLastModifiedOnProcessCommands)
      ) {
        return true;
      }
      this._lastModifiedOnProcessCommands = this._localClientLastModified;

      const clearedCommands = await this._readCommands()[this.localID];
      const commands = this.localCommands.filter(
        command => !hasDupeCommand(clearedCommands, command)
      );
      let didRemoveCommand = false;
      // Process each command in order.
      for (let rawCommand of commands) {
        let shouldRemoveCommand = true; // most commands are auto-removed.
        let { command, args, flowID } = rawCommand;
        this._log.debug("Processing command " + command, args);

        this.service.recordTelemetryEvent(
          "processcommand",
          command,
          undefined,
          { flowID }
        );

        let engines = [args[0]];
        switch (command) {
          case "resetAll":
            engines = null;
          // Fallthrough
          case "resetEngine":
            await this.service.resetClient(engines);
            break;
          case "wipeEngine":
            await this.service.wipeClient(engines);
            break;
          case "logout":
            this.service.logout();
            return false;
          default:
            this._log.warn("Received an unknown command: " + command);
            break;
        }
        // Add the command to the "cleared" commands list
        if (shouldRemoveCommand) {
          await this.removeLocalCommand(rawCommand);
          didRemoveCommand = true;
        }
      }
      if (didRemoveCommand) {
        await this._tracker.addChangedID(this.localID);
      }

      return true;
    })();
  },

  /**
   * Validates and sends a command to a client or all clients.
   *
   * Calling this does not actually sync the command data to the server. If the
   * client already has the command/args pair, it won't receive a duplicate
   * command.
   * This method is async since it writes the command to a file.
   *
   * @param command
   *        Command to invoke on remote clients
   * @param args
   *        Array of arguments to give to the command
   * @param clientId
   *        Client ID to send command to. If undefined, send to all remote
   *        clients.
   * @param flowID
   *        A unique identifier used to track success for this operation across
   *        devices.
   */
  async sendCommand(command, args, clientId = null, telemetryExtra = {}) {
    let commandData = this._commands[command];
    // Don't send commands that we don't know about.
    if (!commandData) {
      this._log.error("Unknown command to send: " + command);
      return;
    } else if (!args || args.length != commandData.args) {
      // Don't send a command with the wrong number of arguments.
      this._log.error(
        "Expected " +
          commandData.args +
          " args for '" +
          command +
          "', but got " +
          args
      );
      return;
    }

    // We allocate a "flowID" here, so it is used for each client.
    telemetryExtra = Object.assign({}, telemetryExtra); // don't clobber the caller's object
    if (!telemetryExtra.flowID) {
      telemetryExtra.flowID = Utils.makeGUID();
    }

    if (clientId) {
      await this._sendCommandToClient(command, args, clientId, telemetryExtra);
    } else {
      for (let [id, record] of Object.entries(this._store._remoteClients)) {
        if (!record.stale) {
          await this._sendCommandToClient(command, args, id, telemetryExtra);
        }
      }
    }
  },

  async _removeRemoteClient(id) {
    delete this._store._remoteClients[id];
    await this._tracker.removeChangedID(id);
    await this._removeClientCommands(id);
    this._modified.delete(id);
  },
};
Object.setPrototypeOf(ClientEngine.prototype, SyncEngine.prototype);

function ClientStore(name, engine) {
  Store.call(this, name, engine);
}
ClientStore.prototype = {
  _remoteClients: {},

  async create(record) {
    await this.update(record);
  },

  async update(record) {
    if (record.id == this.engine.localID) {
      // Only grab commands from the server; local name/type always wins
      this.engine.localCommands = record.commands;
    } else {
      this._remoteClients[record.id] = record.cleartext;
    }
  },

  async createRecord(id, collection) {
    let record = new ClientsRec(collection, id);

    const commandsChanges = this.engine._currentlySyncingCommands
      ? this.engine._currentlySyncingCommands[id]
      : [];

    // Package the individual components into a record for the local client
    if (id == this.engine.localID) {
      try {
        record.fxaDeviceId = await this.engine.fxAccounts.device.getLocalId();
      } catch (error) {
        this._log.warn("failed to get fxa device id", error);
      }
      record.name = this.engine.localName;
      record.type = this.engine.localType;
      record.version = Services.appinfo.version;
      record.protocols = SUPPORTED_PROTOCOL_VERSIONS;

      // Substract the commands we recorded that we've already executed
      if (
        commandsChanges &&
        commandsChanges.length &&
        this.engine.localCommands &&
        this.engine.localCommands.length
      ) {
        record.commands = this.engine.localCommands.filter(
          command => !hasDupeCommand(commandsChanges, command)
        );
      }

      // Optional fields.
      record.os = Services.appinfo.OS; // "Darwin"
      record.appPackage = Services.appinfo.ID;
      record.application = this.engine.brandName; // "Nightly"

      // We can't compute these yet.
      // record.device = "";            // Bug 1100723
      // record.formfactor = "";        // Bug 1100722
    } else {
      record.cleartext = Object.assign({}, this._remoteClients[id]);
      delete record.cleartext.serverLastModified; // serverLastModified is a local only attribute.

      // Add the commands we have to send
      if (commandsChanges && commandsChanges.length) {
        const recordCommands = record.cleartext.commands || [];
        const newCommands = commandsChanges.filter(
          command => !hasDupeCommand(recordCommands, command)
        );
        record.cleartext.commands = recordCommands.concat(newCommands);
      }

      if (record.cleartext.stale) {
        // It's almost certainly a logic error for us to upload a record we
        // consider stale, so make log noise, but still remove the flag.
        this._log.error(
          `Preparing to upload record ${id} that we consider stale`
        );
        delete record.cleartext.stale;
      }
    }
    if (record.commands) {
      const maxPayloadSize =
        this.engine.service.getMemcacheMaxRecordPayloadSize();
      let origOrder = new Map(record.commands.map((c, i) => [c, i]));
      // we sort first by priority, and second by age (indicated by order in the
      // original list)
      let commands = record.commands.slice().sort((a, b) => {
        let infoA = this.engine._commands[a.command];
        let infoB = this.engine._commands[b.command];
        // Treat unknown command types as highest priority, to allow us to add
        // high priority commands in the future without worrying about clients
        // removing them on each-other unnecessarially.
        let importA = infoA ? infoA.importance : 0;
        let importB = infoB ? infoB.importance : 0;
        // Higher importantance numbers indicate that we care less, so they
        // go to the end of the list where they'll be popped off.
        let importDelta = importA - importB;
        if (importDelta != 0) {
          return importDelta;
        }
        let origIdxA = origOrder.get(a);
        let origIdxB = origOrder.get(b);
        // Within equivalent priorities, we put older entries near the end
        // of the list, so that they are removed first.
        return origIdxB - origIdxA;
      });
      let truncatedCommands = Utils.tryFitItems(commands, maxPayloadSize);
      if (truncatedCommands.length != record.commands.length) {
        this._log.warn(
          `Removing commands from client ${id} (from ${record.commands.length} to ${truncatedCommands.length})`
        );
        // Restore original order.
        record.commands = truncatedCommands.sort(
          (a, b) => origOrder.get(a) - origOrder.get(b)
        );
      }
    }
    return record;
  },

  async itemExists(id) {
    return id in (await this.getAllIDs());
  },

  async getAllIDs() {
    let ids = {};
    ids[this.engine.localID] = true;
    for (let id in this._remoteClients) {
      ids[id] = true;
    }
    return ids;
  },

  async wipe() {
    this._remoteClients = {};
  },
};
Object.setPrototypeOf(ClientStore.prototype, Store.prototype);

function ClientsTracker(name, engine) {
  LegacyTracker.call(this, name, engine);
}
ClientsTracker.prototype = {
  _enabled: false,

  onStart() {
    Svc.Obs.add("fxaccounts:new_device_id", this.asyncObserver);
    Services.prefs.addObserver(
      PREF_ACCOUNT_ROOT + "device.name",
      this.asyncObserver
    );
  },
  onStop() {
    Services.prefs.removeObserver(
      PREF_ACCOUNT_ROOT + "device.name",
      this.asyncObserver
    );
    Svc.Obs.remove("fxaccounts:new_device_id", this.asyncObserver);
  },

  async observe(subject, topic) {
    switch (topic) {
      case "nsPref:changed":
        this._log.debug("client.name preference changed");
      // Fallthrough intended.
      case "fxaccounts:new_device_id":
        await this.addChangedID(this.engine.localID);
        this.score += SINGLE_USER_THRESHOLD + 1; // ALWAYS SYNC NOW.
        break;
    }
  },
};
Object.setPrototypeOf(ClientsTracker.prototype, LegacyTracker.prototype);
PK
!<{b�&U%U%7modules/services-sync/engines/extension-storage.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import {
  BridgedEngine,
  BridgeWrapperXPCOM,
  LogAdapter,
} from "resource://services-sync/bridged_engine.sys.mjs";
import { SyncEngine, Tracker } from "resource://services-sync/engines.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  MULTI_DEVICE_THRESHOLD: "resource://services-sync/constants.sys.mjs",
  Observers: "resource://services-common/observers.sys.mjs",
  SCORE_INCREMENT_MEDIUM: "resource://services-sync/constants.sys.mjs",
  Svc: "resource://services-sync/util.sys.mjs",
  extensionStorageSync: "resource://gre/modules/ExtensionStorageSync.sys.mjs",

  extensionStorageSyncKinto:
    "resource://gre/modules/ExtensionStorageSyncKinto.sys.mjs",
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "StorageSyncService",
  "@mozilla.org/extensions/storage/sync;1",
  "nsIInterfaceRequestor"
);

const PREF_FORCE_ENABLE = "engine.extension-storage.force";

// A helper to indicate whether extension-storage is enabled - it's based on
// the "addons" pref. The same logic is shared between both engine impls.
function getEngineEnabled() {
  // By default, we sync extension storage if we sync addons. This
  // lets us simplify the UX since users probably don't consider
  // "extension preferences" a separate category of syncing.
  // However, we also respect engine.extension-storage.force, which
  // can be set to true or false, if a power user wants to customize
  // the behavior despite the lack of UI.
  if (
    lazy.Svc.PrefBranch.getPrefType(PREF_FORCE_ENABLE) !=
    Ci.nsIPrefBranch.PREF_INVALID
  ) {
    return lazy.Svc.PrefBranch.getBoolPref(PREF_FORCE_ENABLE);
  }
  return lazy.Svc.PrefBranch.getBoolPref("engine.addons", false);
}

function setEngineEnabled(enabled) {
  // This will be called by the engine manager when declined on another device.
  // Things will go a bit pear-shaped if the engine manager tries to end up
  // with 'addons' and 'extension-storage' in different states - however, this
  // *can* happen given we support the `engine.extension-storage.force`
  // preference. So if that pref exists, we set it to this value. If that pref
  // doesn't exist, we just ignore it and hope that the 'addons' engine is also
  // going to be set to the same state.
  if (
    lazy.Svc.PrefBranch.getPrefType(PREF_FORCE_ENABLE) !=
    Ci.nsIPrefBranch.PREF_INVALID
  ) {
    lazy.Svc.PrefBranch.setBoolPref(PREF_FORCE_ENABLE, enabled);
  }
}

// A "bridged engine" to our webext-storage component.
export function ExtensionStorageEngineBridge(service) {
  this.component = lazy.StorageSyncService.getInterface(
    Ci.mozIBridgedSyncEngine
  );
  BridgedEngine.call(this, "Extension-Storage", service);
  this._bridge = new BridgeWrapperXPCOM(this.component);

  let app_services_logger = Cc["@mozilla.org/appservices/logger;1"].getService(
    Ci.mozIAppServicesLogger
  );
  let logger_target = "app-services:webext_storage:sync";
  app_services_logger.register(logger_target, new LogAdapter(this._log));
}

ExtensionStorageEngineBridge.prototype = {
  syncPriority: 10,

  // Used to override the engine name in telemetry, so that we can distinguish .
  overrideTelemetryName: "rust-webext-storage",

  _notifyPendingChanges() {
    return new Promise(resolve => {
      this.component
        .QueryInterface(Ci.mozISyncedExtensionStorageArea)
        .fetchPendingSyncChanges({
          QueryInterface: ChromeUtils.generateQI([
            "mozIExtensionStorageListener",
            "mozIExtensionStorageCallback",
          ]),
          onChanged: (extId, json) => {
            try {
              lazy.extensionStorageSync.notifyListeners(
                extId,
                JSON.parse(json)
              );
            } catch (ex) {
              this._log.warn(
                `Error notifying change listeners for ${extId}`,
                ex
              );
            }
          },
          handleSuccess: resolve,
          handleError: (code, message) => {
            this._log.warn(
              "Error fetching pending synced changes",
              message,
              code
            );
            resolve();
          },
        });
    });
  },

  _takeMigrationInfo() {
    return new Promise(resolve => {
      this.component
        .QueryInterface(Ci.mozIExtensionStorageArea)
        .takeMigrationInfo({
          QueryInterface: ChromeUtils.generateQI([
            "mozIExtensionStorageCallback",
          ]),
          handleSuccess: result => {
            resolve(result ? JSON.parse(result) : null);
          },
          handleError: (code, message) => {
            this._log.warn("Error fetching migration info", message, code);
            // `takeMigrationInfo` doesn't actually perform the migration,
            // just reads (and clears) any data stored in the DB from the
            // previous migration.
            //
            // Any errors here are very likely occurring a good while
            // after the migration ran, so we just warn and pretend
            // nothing was there.
            resolve(null);
          },
        });
    });
  },

  async _syncStartup() {
    let result = await super._syncStartup();
    let info = await this._takeMigrationInfo();
    if (info) {
      lazy.Observers.notify(
        "weave:telemetry:migration",
        info,
        "webext-storage"
      );
    }
    return result;
  },

  async _processIncoming() {
    await super._processIncoming();
    try {
      await this._notifyPendingChanges();
    } catch (ex) {
      // Failing to notify `storage.onChanged` observers is bad, but shouldn't
      // interrupt syncing.
      this._log.warn("Error notifying about synced changes", ex);
    }
  },

  get enabled() {
    return getEngineEnabled();
  },
  set enabled(enabled) {
    setEngineEnabled(enabled);
  },
};
Object.setPrototypeOf(
  ExtensionStorageEngineBridge.prototype,
  BridgedEngine.prototype
);

/**
 *****************************************************************************
 *
 * Deprecated support for Kinto
 *
 *****************************************************************************
 */

/**
 * The Engine that manages syncing for the web extension "storage"
 * API, and in particular ext.storage.sync.
 *
 * ext.storage.sync is implemented using Kinto, so it has mechanisms
 * for syncing that we do not need to integrate in the Firefox Sync
 * framework, so this is something of a stub.
 */
export function ExtensionStorageEngineKinto(service) {
  SyncEngine.call(this, "Extension-Storage", service);
  XPCOMUtils.defineLazyPreferenceGetter(
    this,
    "_skipPercentageChance",
    "services.sync.extension-storage.skipPercentageChance",
    0
  );
}

ExtensionStorageEngineKinto.prototype = {
  _trackerObj: ExtensionStorageTracker,
  // we don't need these since we implement our own sync logic
  _storeObj: undefined,
  _recordObj: undefined,

  syncPriority: 10,
  allowSkippedRecord: false,

  async _sync() {
    return lazy.extensionStorageSyncKinto.syncAll();
  },

  get enabled() {
    return getEngineEnabled();
  },
  // We only need the enabled setter for the edge-case where info/collections
  // has `extension-storage` - which could happen if the pref to flip the new
  // engine on was once set but no longer is.
  set enabled(enabled) {
    setEngineEnabled(enabled);
  },

  _wipeClient() {
    return lazy.extensionStorageSyncKinto.clearAll();
  },

  shouldSkipSync(syncReason) {
    if (syncReason == "user" || syncReason == "startup") {
      this._log.info(
        `Not skipping extension storage sync: reason == ${syncReason}`
      );
      // Always sync if a user clicks the button, or if we're starting up.
      return false;
    }
    // Ensure this wouldn't cause a resync...
    if (this._tracker.score >= lazy.MULTI_DEVICE_THRESHOLD) {
      this._log.info(
        "Not skipping extension storage sync: Would trigger resync anyway"
      );
      return false;
    }

    let probability = this._skipPercentageChance / 100.0;
    // Math.random() returns a value in the interval [0, 1), so `>` is correct:
    // if `probability` is 1 skip every time, and if it's 0, never skip.
    let shouldSkip = probability > Math.random();

    this._log.info(
      `Skipping extension-storage sync with a chance of ${probability}: ${shouldSkip}`
    );
    return shouldSkip;
  },
};
Object.setPrototypeOf(
  ExtensionStorageEngineKinto.prototype,
  SyncEngine.prototype
);

function ExtensionStorageTracker(name, engine) {
  Tracker.call(this, name, engine);
  this._ignoreAll = false;
}
ExtensionStorageTracker.prototype = {
  get ignoreAll() {
    return this._ignoreAll;
  },

  set ignoreAll(value) {
    this._ignoreAll = value;
  },

  onStart() {
    lazy.Svc.Obs.add("ext.storage.sync-changed", this.asyncObserver);
  },

  onStop() {
    lazy.Svc.Obs.remove("ext.storage.sync-changed", this.asyncObserver);
  },

  async observe(subject, topic) {
    if (this.ignoreAll) {
      return;
    }

    if (topic !== "ext.storage.sync-changed") {
      return;
    }

    // Single adds, removes and changes are not so important on their
    // own, so let's just increment score a bit.
    this.score += lazy.SCORE_INCREMENT_MEDIUM;
  },
};
Object.setPrototypeOf(ExtensionStorageTracker.prototype, Tracker.prototype);
PK
!<�o6���+modules/services-sync/engines/forms.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import {
  Store,
  SyncEngine,
  LegacyTracker,
} from "resource://services-sync/engines.sys.mjs";

import { CryptoWrapper } from "resource://services-sync/record.sys.mjs";
import { Svc, Utils } from "resource://services-sync/util.sys.mjs";

import { SCORE_INCREMENT_MEDIUM } from "resource://services-sync/constants.sys.mjs";
import {
  CollectionProblemData,
  CollectionValidator,
} from "resource://services-sync/collection_validator.sys.mjs";

import { Async } from "resource://services-common/async.sys.mjs";
import { Log } from "resource://gre/modules/Log.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  FormHistory: "resource://gre/modules/FormHistory.sys.mjs",
});

const FORMS_TTL = 3 * 365 * 24 * 60 * 60; // Three years in seconds.

export function FormRec(collection, id) {
  CryptoWrapper.call(this, collection, id);
}

FormRec.prototype = {
  _logName: "Sync.Record.Form",
  ttl: FORMS_TTL,
};
Object.setPrototypeOf(FormRec.prototype, CryptoWrapper.prototype);

Utils.deferGetSet(FormRec, "cleartext", ["name", "value"]);

var FormWrapper = {
  _log: Log.repository.getLogger("Sync.Engine.Forms"),

  _getEntryCols: ["fieldname", "value"],
  _guidCols: ["guid"],

  _search(terms, searchData) {
    return lazy.FormHistory.search(terms, searchData);
  },

  async _update(changes) {
    if (!lazy.FormHistory.enabled) {
      return; // update isn't going to do anything.
    }
    await lazy.FormHistory.update(changes).catch(console.error);
  },

  async getEntry(guid) {
    let results = await this._search(this._getEntryCols, { guid });
    if (!results.length) {
      return null;
    }
    return { name: results[0].fieldname, value: results[0].value };
  },

  async getGUID(name, value) {
    // Query for the provided entry.
    let query = { fieldname: name, value };
    let results = await this._search(this._guidCols, query);
    return results.length ? results[0].guid : null;
  },

  async hasGUID(guid) {
    // We could probably use a count function here, but search exists...
    let results = await this._search(this._guidCols, { guid });
    return !!results.length;
  },

  async replaceGUID(oldGUID, newGUID) {
    let changes = {
      op: "update",
      guid: oldGUID,
      newGuid: newGUID,
    };
    await this._update(changes);
  },
};

export function FormEngine(service) {
  SyncEngine.call(this, "Forms", service);
}

FormEngine.prototype = {
  _storeObj: FormStore,
  _trackerObj: FormTracker,
  _recordObj: FormRec,

  syncPriority: 6,

  get prefName() {
    return "history";
  },

  async _findDupe(item) {
    return FormWrapper.getGUID(item.name, item.value);
  },
};
Object.setPrototypeOf(FormEngine.prototype, SyncEngine.prototype);

function FormStore(name, engine) {
  Store.call(this, name, engine);
}
FormStore.prototype = {
  async _processChange(change) {
    // If this._changes is defined, then we are applying a batch, so we
    // can defer it.
    if (this._changes) {
      this._changes.push(change);
      return;
    }

    // Otherwise we must handle the change right now.
    await FormWrapper._update(change);
  },

  async applyIncomingBatch(records, countTelemetry) {
    Async.checkAppReady();
    // We collect all the changes to be made then apply them all at once.
    this._changes = [];
    let failures = await Store.prototype.applyIncomingBatch.call(
      this,
      records,
      countTelemetry
    );
    if (this._changes.length) {
      await FormWrapper._update(this._changes);
    }
    delete this._changes;
    return failures;
  },

  async getAllIDs() {
    let results = await FormWrapper._search(["guid"], []);
    let guids = {};
    for (let result of results) {
      guids[result.guid] = true;
    }
    return guids;
  },

  async changeItemID(oldID, newID) {
    await FormWrapper.replaceGUID(oldID, newID);
  },

  async itemExists(id) {
    return FormWrapper.hasGUID(id);
  },

  async createRecord(id, collection) {
    let record = new FormRec(collection, id);
    let entry = await FormWrapper.getEntry(id);
    if (entry != null) {
      record.name = entry.name;
      record.value = entry.value;
    } else {
      record.deleted = true;
    }
    return record;
  },

  async create(record) {
    this._log.trace("Adding form record for " + record.name);
    let change = {
      op: "add",
      guid: record.id,
      fieldname: record.name,
      value: record.value,
    };
    await this._processChange(change);
  },

  async remove(record) {
    this._log.trace("Removing form record: " + record.id);
    let change = {
      op: "remove",
      guid: record.id,
    };
    await this._processChange(change);
  },

  async update() {
    this._log.trace("Ignoring form record update request!");
  },

  async wipe() {
    let change = {
      op: "remove",
    };
    await FormWrapper._update(change);
  },
};
Object.setPrototypeOf(FormStore.prototype, Store.prototype);

function FormTracker(name, engine) {
  LegacyTracker.call(this, name, engine);
}
FormTracker.prototype = {
  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
  ]),

  onStart() {
    Svc.Obs.add("satchel-storage-changed", this.asyncObserver);
  },

  onStop() {
    Svc.Obs.remove("satchel-storage-changed", this.asyncObserver);
  },

  async observe(subject, topic, data) {
    if (this.ignoreAll) {
      return;
    }
    switch (topic) {
      case "satchel-storage-changed":
        if (data == "formhistory-add" || data == "formhistory-remove") {
          let guid = subject.QueryInterface(Ci.nsISupportsString).toString();
          await this.trackEntry(guid);
        }
        break;
    }
  },

  async trackEntry(guid) {
    const added = await this.addChangedID(guid);
    if (added) {
      this.score += SCORE_INCREMENT_MEDIUM;
    }
  },
};
Object.setPrototypeOf(FormTracker.prototype, LegacyTracker.prototype);

class FormsProblemData extends CollectionProblemData {
  getSummary() {
    // We don't support syncing deleted form data, so "clientMissing" isn't a problem
    return super.getSummary().filter(entry => entry.name !== "clientMissing");
  }
}

export class FormValidator extends CollectionValidator {
  constructor() {
    super("forms", "id", ["name", "value"]);
    this.ignoresMissingClients = true;
  }

  emptyProblemData() {
    return new FormsProblemData();
  }

  async getClientItems() {
    return FormWrapper._search(["guid", "fieldname", "value"], {});
  }

  normalizeClientItem(item) {
    return {
      id: item.guid,
      guid: item.guid,
      name: item.fieldname,
      fieldname: item.fieldname,
      value: item.value,
      original: item,
    };
  }

  async normalizeServerItem(item) {
    let res = Object.assign(
      {
        guid: item.id,
        fieldname: item.name,
        original: item,
      },
      item
    );
    // Missing `name` or `value` causes the getGUID call to throw
    if (item.name !== undefined && item.value !== undefined) {
      let guid = await FormWrapper.getGUID(item.name, item.value);
      if (guid) {
        res.guid = guid;
        res.id = guid;
        res.duped = true;
      }
    }

    return res;
  }
}
PK
!<:-�xRR-modules/services-sync/engines/history.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const HISTORY_TTL = 5184000; // 60 days in milliseconds
const THIRTY_DAYS_IN_MS = 2592000000; // 30 days in milliseconds
// Sync may bring new fields from other clients, not yet understood by our engine.
// Unknown fields outside these fields are aggregated into 'unknownFields' and
// safely synced to prevent data loss.
const VALID_HISTORY_FIELDS = ["id", "title", "histUri", "visits"];
const VALID_VISIT_FIELDS = ["date", "type", "transition"];

import { Async } from "resource://services-common/async.sys.mjs";
import { CommonUtils } from "resource://services-common/utils.sys.mjs";

import {
  MAX_HISTORY_DOWNLOAD,
  MAX_HISTORY_UPLOAD,
  SCORE_INCREMENT_SMALL,
  SCORE_INCREMENT_XLARGE,
} from "resource://services-sync/constants.sys.mjs";

import {
  Store,
  SyncEngine,
  LegacyTracker,
} from "resource://services-sync/engines.sys.mjs";
import { CryptoWrapper } from "resource://services-sync/record.sys.mjs";
import { Utils } from "resource://services-sync/util.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PlacesSyncUtils: "resource://gre/modules/PlacesSyncUtils.sys.mjs",
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
});

export function HistoryRec(collection, id) {
  CryptoWrapper.call(this, collection, id);
}

HistoryRec.prototype = {
  _logName: "Sync.Record.History",
  ttl: HISTORY_TTL,
};
Object.setPrototypeOf(HistoryRec.prototype, CryptoWrapper.prototype);

Utils.deferGetSet(HistoryRec, "cleartext", ["histUri", "title", "visits"]);

export function HistoryEngine(service) {
  SyncEngine.call(this, "History", service);
}

HistoryEngine.prototype = {
  _recordObj: HistoryRec,
  _storeObj: HistoryStore,
  _trackerObj: HistoryTracker,
  downloadLimit: MAX_HISTORY_DOWNLOAD,

  syncPriority: 7,

  async getSyncID() {
    return lazy.PlacesSyncUtils.history.getSyncId();
  },

  async ensureCurrentSyncID(newSyncID) {
    this._log.debug(
      "Checking if server sync ID ${newSyncID} matches existing",
      { newSyncID }
    );
    await lazy.PlacesSyncUtils.history.ensureCurrentSyncId(newSyncID);
    return newSyncID;
  },

  async resetSyncID() {
    // First, delete the collection on the server. It's fine if we're
    // interrupted here: on the next sync, we'll detect that our old sync ID is
    // now stale, and start over as a first sync.
    await this._deleteServerCollection();
    // Then, reset our local sync ID.
    return this.resetLocalSyncID();
  },

  async resetLocalSyncID() {
    let newSyncID = await lazy.PlacesSyncUtils.history.resetSyncId();
    this._log.debug("Assigned new sync ID ${newSyncID}", { newSyncID });
    return newSyncID;
  },

  async getLastSync() {
    let lastSync = await lazy.PlacesSyncUtils.history.getLastSync();
    return lastSync;
  },

  async setLastSync(lastSync) {
    await lazy.PlacesSyncUtils.history.setLastSync(lastSync);
  },

  shouldSyncURL(url) {
    return !url.startsWith("file:");
  },

  async pullNewChanges() {
    const changedIDs = await this._tracker.getChangedIDs();
    let modifiedGUIDs = Object.keys(changedIDs);
    if (!modifiedGUIDs.length) {
      return {};
    }

    let guidsToRemove =
      await lazy.PlacesSyncUtils.history.determineNonSyncableGuids(
        modifiedGUIDs
      );
    await this._tracker.removeChangedID(...guidsToRemove);
    return changedIDs;
  },

  async _resetClient() {
    await super._resetClient();
    await lazy.PlacesSyncUtils.history.reset();
  },
};
Object.setPrototypeOf(HistoryEngine.prototype, SyncEngine.prototype);

function HistoryStore(name, engine) {
  Store.call(this, name, engine);
}

HistoryStore.prototype = {
  // We try and only update this many visits at one time.
  MAX_VISITS_PER_INSERT: 500,

  // Some helper functions to handle GUIDs
  async setGUID(uri, guid) {
    if (!guid) {
      guid = Utils.makeGUID();
    }

    try {
      await lazy.PlacesSyncUtils.history.changeGuid(uri, guid);
    } catch (e) {
      this._log.error("Error setting GUID ${guid} for URI ${uri}", guid, uri);
    }

    return guid;
  },

  async GUIDForUri(uri, create) {
    // Use the existing GUID if it exists
    let guid;
    try {
      guid = await lazy.PlacesSyncUtils.history.fetchGuidForURL(uri);
    } catch (e) {
      this._log.error("Error fetching GUID for URL ${uri}", uri);
    }

    // If the URI has an existing GUID, return it.
    if (guid) {
      return guid;
    }

    // If the URI doesn't have a GUID and we were indicated to create one.
    if (create) {
      return this.setGUID(uri);
    }

    // If the URI doesn't have a GUID and we didn't create one for it.
    return null;
  },

  async changeItemID(oldID, newID) {
    let info = await lazy.PlacesSyncUtils.history.fetchURLInfoForGuid(oldID);
    if (!info) {
      throw new Error(`Can't change ID for nonexistent history entry ${oldID}`);
    }
    this.setGUID(info.url, newID);
  },

  async getAllIDs() {
    let urls = await lazy.PlacesSyncUtils.history.getAllURLs({
      since: new Date(Date.now() - THIRTY_DAYS_IN_MS),
      limit: MAX_HISTORY_UPLOAD,
    });

    let urlsByGUID = {};
    for (let url of urls) {
      if (!this.engine.shouldSyncURL(url)) {
        continue;
      }
      let guid = await this.GUIDForUri(url, true);
      urlsByGUID[guid] = url;
    }
    return urlsByGUID;
  },

  async applyIncomingBatch(records, countTelemetry) {
    // Convert incoming records to mozIPlaceInfo objects which are applied as
    // either history additions or removals.
    let failed = [];
    let toAdd = [];
    let toRemove = [];
    let pageGuidsWithUnknownFields = new Map();
    let visitTimesWithUnknownFields = new Map();
    await Async.yieldingForEach(records, async record => {
      if (record.deleted) {
        toRemove.push(record);
      } else {
        try {
          let pageInfo = await this._recordToPlaceInfo(record);
          if (pageInfo) {
            toAdd.push(pageInfo);

            // Pull any unknown fields that may have come from other clients
            let unknownFields = lazy.PlacesSyncUtils.extractUnknownFields(
              record.cleartext,
              VALID_HISTORY_FIELDS
            );
            if (unknownFields) {
              pageGuidsWithUnknownFields.set(pageInfo.guid, { unknownFields });
            }

            // Visits themselves could also contain unknown fields
            for (const visit of pageInfo.visits) {
              let unknownVisitFields =
                lazy.PlacesSyncUtils.extractUnknownFields(
                  visit,
                  VALID_VISIT_FIELDS
                );
              if (unknownVisitFields) {
                // Visits don't have an id at the time of sync so we'll need
                // to use the time instead until it's inserted in the DB
                visitTimesWithUnknownFields.set(visit.date.getTime(), {
                  unknownVisitFields,
                });
              }
            }
          }
        } catch (ex) {
          if (Async.isShutdownException(ex)) {
            throw ex;
          }
          this._log.error("Failed to create a place info", ex);
          this._log.trace("The record that failed", record);
          failed.push(record.id);
          countTelemetry.addIncomingFailedReason(ex.message);
        }
      }
    });
    if (toAdd.length || toRemove.length) {
      if (toRemove.length) {
        // PlacesUtils.history.remove takes an array of visits to remove,
        // but the error semantics are tricky - a single "bad" entry will cause
        // an exception before anything is removed. So we do remove them one at
        // a time.
        await Async.yieldingForEach(toRemove, async record => {
          try {
            await this.remove(record);
          } catch (ex) {
            if (Async.isShutdownException(ex)) {
              throw ex;
            }
            this._log.error("Failed to delete a place info", ex);
            this._log.trace("The record that failed", record);
            failed.push(record.id);
            countTelemetry.addIncomingFailedReason(ex.message);
          }
        });
      }
      for (let chunk of this._generateChunks(toAdd)) {
        // Per bug 1415560, we ignore any exceptions returned by insertMany
        // as they are likely to be spurious. We do supply an onError handler
        // and log the exceptions seen there as they are likely to be
        // informative, but we still never abort the sync based on them.
        let unknownFieldsToInsert = [];
        try {
          await lazy.PlacesUtils.history.insertMany(
            chunk,
            result => {
              const placeToUpdate = pageGuidsWithUnknownFields.get(result.guid);
              // Extract the placeId from this result so we can add the unknownFields
              // to the proper table
              if (placeToUpdate) {
                unknownFieldsToInsert.push({
                  placeId: result.placeId,
                  unknownFields: placeToUpdate.unknownFields,
                });
              }
              // same for visits
              result.visits.forEach(visit => {
                let visitToUpdate = visitTimesWithUnknownFields.get(
                  visit.date.getTime()
                );
                if (visitToUpdate) {
                  unknownFieldsToInsert.push({
                    visitId: visit.visitId,
                    unknownFields: visitToUpdate.unknownVisitFields,
                  });
                }
              });
            },
            failedVisit => {
              this._log.info(
                "Failed to insert a history record",
                failedVisit.guid
              );
              this._log.trace("The record that failed", failedVisit);
              failed.push(failedVisit.guid);
            }
          );
        } catch (ex) {
          this._log.info("Failed to insert history records", ex);
          countTelemetry.addIncomingFailedReason(ex.message);
        }

        // All the top level places or visits that had unknown fields are sent
        // to be added to the appropiate tables
        await lazy.PlacesSyncUtils.history.updateUnknownFieldsBatch(
          unknownFieldsToInsert
        );
      }
    }

    return failed;
  },

  /**
   * Returns a generator that splits records into sanely sized chunks suitable
   * for passing to places to prevent places doing bad things at shutdown.
   */
  *_generateChunks(records) {
    // We chunk based on the number of *visits* inside each record. However,
    // we do not split a single record into multiple records, because at some
    // time in the future, we intend to ensure these records are ordered by
    // lastModified, and advance the engine's timestamp as we process them,
    // meaning we can resume exactly where we left off next sync - although
    // currently that's not done, so we will retry the entire batch next sync
    // if interrupted.
    // ie, this means that if a single record has more than MAX_VISITS_PER_INSERT
    // visits, we will call insertMany() with exactly 1 record, but with
    // more than MAX_VISITS_PER_INSERT visits.
    let curIndex = 0;
    this._log.debug(`adding ${records.length} records to history`);
    while (curIndex < records.length) {
      Async.checkAppReady(); // may throw if we are shutting down.
      let toAdd = []; // what we are going to insert.
      let count = 0; // a counter which tells us when toAdd is full.
      do {
        let record = records[curIndex];
        curIndex += 1;
        toAdd.push(record);
        count += record.visits.length;
      } while (
        curIndex < records.length &&
        count + records[curIndex].visits.length <= this.MAX_VISITS_PER_INSERT
      );
      this._log.trace(`adding ${toAdd.length} items in this chunk`);
      yield toAdd;
    }
  },

  /* An internal helper to determine if we can add an entry to places.
     Exists primarily so tests can override it.
   */
  _canAddURI(uri) {
    return lazy.PlacesUtils.history.canAddURI(uri);
  },

  /**
   * Converts a Sync history record to a mozIPlaceInfo.
   *
   * Throws if an invalid record is encountered (invalid URI, etc.),
   * returns a new PageInfo object if the record is to be applied, null
   * otherwise (no visits to add, etc.),
   */
  async _recordToPlaceInfo(record) {
    // Sort out invalid URIs and ones Places just simply doesn't want.
    record.url = lazy.PlacesUtils.normalizeToURLOrGUID(record.histUri);
    record.uri = CommonUtils.makeURI(record.histUri);

    if (!Utils.checkGUID(record.id)) {
      this._log.warn("Encountered record with invalid GUID: " + record.id);
      return null;
    }
    record.guid = record.id;

    if (
      !this._canAddURI(record.uri) ||
      !this.engine.shouldSyncURL(record.uri.spec)
    ) {
      this._log.trace(
        "Ignoring record " +
          record.id +
          " with URI " +
          record.uri.spec +
          ": can't add this URI."
      );
      return null;
    }

    // We dupe visits by date and type. So an incoming visit that has
    // the same timestamp and type as a local one won't get applied.
    // To avoid creating new objects, we rewrite the query result so we
    // can simply check for containment below.
    let curVisitsAsArray = [];
    let curVisits = new Set();
    try {
      curVisitsAsArray = await lazy.PlacesSyncUtils.history.fetchVisitsForURL(
        record.histUri
      );
    } catch (e) {
      this._log.error(
        "Error while fetching visits for URL ${record.histUri}",
        record.histUri
      );
    }
    let oldestAllowed =
      lazy.PlacesSyncUtils.bookmarks.EARLIEST_BOOKMARK_TIMESTAMP;
    if (curVisitsAsArray.length == 20) {
      let oldestVisit = curVisitsAsArray[curVisitsAsArray.length - 1];
      oldestAllowed = lazy.PlacesSyncUtils.history.clampVisitDate(
        lazy.PlacesUtils.toDate(oldestVisit.date).getTime()
      );
    }

    let i, k;
    for (i = 0; i < curVisitsAsArray.length; i++) {
      // Same logic as used in the loop below to generate visitKey.
      let { date, type } = curVisitsAsArray[i];
      let dateObj = lazy.PlacesUtils.toDate(date);
      let millis = lazy.PlacesSyncUtils.history
        .clampVisitDate(dateObj)
        .getTime();
      curVisits.add(`${millis},${type}`);
    }

    // Walk through the visits, make sure we have sound data, and eliminate
    // dupes. The latter is done by rewriting the array in-place.
    for (i = 0, k = 0; i < record.visits.length; i++) {
      let visit = (record.visits[k] = record.visits[i]);

      if (
        !visit.date ||
        typeof visit.date != "number" ||
        !Number.isInteger(visit.date)
      ) {
        this._log.warn(
          "Encountered record with invalid visit date: " + visit.date
        );
        continue;
      }

      if (
        !visit.type ||
        !Object.values(lazy.PlacesUtils.history.TRANSITIONS).includes(
          visit.type
        )
      ) {
        this._log.warn(
          "Encountered record with invalid visit type: " +
            visit.type +
            "; ignoring."
        );
        continue;
      }

      // Dates need to be integers. Future and far past dates are clamped to the
      // current date and earliest sensible date, respectively.
      let originalVisitDate = lazy.PlacesUtils.toDate(Math.round(visit.date));
      visit.date =
        lazy.PlacesSyncUtils.history.clampVisitDate(originalVisitDate);

      if (visit.date.getTime() < oldestAllowed) {
        // Visit is older than the oldest visit we have, and we have so many
        // visits for this uri that we hit our limit when inserting.
        continue;
      }
      let visitKey = `${visit.date.getTime()},${visit.type}`;
      if (curVisits.has(visitKey)) {
        // Visit is a dupe, don't increment 'k' so the element will be
        // overwritten.
        continue;
      }

      // Note the visit key, so that we don't add duplicate visits with
      // clamped timestamps.
      curVisits.add(visitKey);

      visit.transition = visit.type;
      k += 1;
    }
    record.visits.length = k; // truncate array

    // No update if there aren't any visits to apply.
    // History wants at least one visit.
    // In any case, the only thing we could change would be the title
    // and that shouldn't change without a visit.
    if (!record.visits.length) {
      this._log.trace(
        "Ignoring record " +
          record.id +
          " with URI " +
          record.uri.spec +
          ": no visits to add."
      );
      return null;
    }

    // PageInfo is validated using validateItemProperties which does a shallow
    // copy of the properties. Since record uses getters some of the properties
    // are not copied over. Thus we create and return a new object.
    let pageInfo = {
      title: record.title,
      url: record.url,
      guid: record.guid,
      visits: record.visits,
    };

    return pageInfo;
  },

  async remove(record) {
    this._log.trace("Removing page: " + record.id);
    let removed = await lazy.PlacesUtils.history.remove(record.id);
    if (removed) {
      this._log.trace("Removed page: " + record.id);
    } else {
      this._log.debug("Page already removed: " + record.id);
    }
  },

  async itemExists(id) {
    return !!(await lazy.PlacesSyncUtils.history.fetchURLInfoForGuid(id));
  },

  async createRecord(id, collection) {
    let foo = await lazy.PlacesSyncUtils.history.fetchURLInfoForGuid(id);
    let record = new HistoryRec(collection, id);
    if (foo) {
      record.histUri = foo.url;
      record.title = foo.title;
      record.sortindex = foo.frecency;

      // If we had any unknown fields, ensure we put it back on the
      // top-level record
      if (foo.unknownFields) {
        let unknownFields = JSON.parse(foo.unknownFields);
        Object.assign(record.cleartext, unknownFields);
      }

      try {
        record.visits = await lazy.PlacesSyncUtils.history.fetchVisitsForURL(
          record.histUri
        );
      } catch (e) {
        this._log.error(
          "Error while fetching visits for URL ${record.histUri}",
          record.histUri
        );
        record.visits = [];
      }
    } else {
      record.deleted = true;
    }

    return record;
  },

  async wipe() {
    return lazy.PlacesSyncUtils.history.wipe();
  },
};
Object.setPrototypeOf(HistoryStore.prototype, Store.prototype);

function HistoryTracker(name, engine) {
  LegacyTracker.call(this, name, engine);
}
HistoryTracker.prototype = {
  onStart() {
    this._log.info("Adding Places observer.");
    this._placesObserver = new PlacesWeakCallbackWrapper(
      this.handlePlacesEvents.bind(this)
    );
    PlacesObservers.addListener(
      ["page-visited", "history-cleared", "page-removed"],
      this._placesObserver
    );
  },

  onStop() {
    this._log.info("Removing Places observer.");
    if (this._placesObserver) {
      PlacesObservers.removeListener(
        ["page-visited", "history-cleared", "page-removed"],
        this._placesObserver
      );
    }
  },

  QueryInterface: ChromeUtils.generateQI(["nsISupportsWeakReference"]),

  handlePlacesEvents(aEvents) {
    this.asyncObserver.enqueueCall(() => this._handlePlacesEvents(aEvents));
  },

  async _handlePlacesEvents(aEvents) {
    if (this.ignoreAll) {
      this._log.trace(
        "ignoreAll: ignoring visits [" +
          aEvents.map(v => v.guid).join(",") +
          "]"
      );
      return;
    }
    for (let event of aEvents) {
      switch (event.type) {
        case "page-visited": {
          this._log.trace("'page-visited': " + event.url);
          if (
            this.engine.shouldSyncURL(event.url) &&
            (await this.addChangedID(event.pageGuid))
          ) {
            this.score += SCORE_INCREMENT_SMALL;
          }
          break;
        }
        case "history-cleared": {
          this._log.trace("history-cleared");
          // Note that we're going to trigger a sync, but none of the cleared
          // pages are tracked, so the deletions will not be propagated.
          // See Bug 578694.
          this.score += SCORE_INCREMENT_XLARGE;
          break;
        }
        case "page-removed": {
          if (event.reason === PlacesVisitRemoved.REASON_EXPIRED) {
            return;
          }

          this._log.trace(
            "page-removed: " + event.url + ", reason " + event.reason
          );
          const added = await this.addChangedID(event.pageGuid);
          if (added) {
            this.score += event.isRemovedFromStore
              ? SCORE_INCREMENT_XLARGE
              : SCORE_INCREMENT_SMALL;
          }
          break;
        }
      }
    }
  },
};
Object.setPrototypeOf(HistoryTracker.prototype, LegacyTracker.prototype);
PK
!<E=5�9�9/modules/services-sync/engines/passwords.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { CryptoWrapper } from "resource://services-sync/record.sys.mjs";

import { SCORE_INCREMENT_XLARGE } from "resource://services-sync/constants.sys.mjs";
import { CollectionValidator } from "resource://services-sync/collection_validator.sys.mjs";
import {
  Changeset,
  Store,
  SyncEngine,
  Tracker,
} from "resource://services-sync/engines.sys.mjs";
import { Svc, Utils } from "resource://services-sync/util.sys.mjs";

// These are valid fields the server could have for a logins record
// we mainly use this to detect if there are any unknownFields and
// store (but don't process) those fields to roundtrip them back
const VALID_LOGIN_FIELDS = [
  "id",
  "displayOrigin",
  "formSubmitURL",
  "formActionOrigin",
  "httpRealm",
  "hostname",
  "origin",
  "password",
  "passwordField",
  "timeCreated",
  "timeLastUsed",
  "timePasswordChanged",
  "timesUsed",
  "username",
  "usernameField",
  "everSynced",
  "syncCounter",
  "unknownFields",
];

import { LoginManagerStorage } from "resource://passwordmgr/passwordstorage.sys.mjs";

// Sync and many tests rely on having an time that is rounded to the nearest
// 100th of a second otherwise tests can fail intermittently.
function roundTimeForSync(time) {
  return Math.round(time / 10) / 100;
}

export function LoginRec(collection, id) {
  CryptoWrapper.call(this, collection, id);
}

LoginRec.prototype = {
  _logName: "Sync.Record.Login",

  cleartextToString() {
    let o = Object.assign({}, this.cleartext);
    if (o.password) {
      o.password = "X".repeat(o.password.length);
    }
    return JSON.stringify(o);
  },
};
Object.setPrototypeOf(LoginRec.prototype, CryptoWrapper.prototype);

Utils.deferGetSet(LoginRec, "cleartext", [
  "hostname",
  "formSubmitURL",
  "httpRealm",
  "username",
  "password",
  "usernameField",
  "passwordField",
  "timeCreated",
  "timePasswordChanged",
]);

export function PasswordEngine(service) {
  SyncEngine.call(this, "Passwords", service);
}

PasswordEngine.prototype = {
  _storeObj: PasswordStore,
  _trackerObj: PasswordTracker,
  _recordObj: LoginRec,

  syncPriority: 2,

  emptyChangeset() {
    return new PasswordsChangeset();
  },

  async ensureCurrentSyncID(newSyncID) {
    return Services.logins.ensureCurrentSyncID(newSyncID);
  },

  async getLastSync() {
    let legacyValue = await super.getLastSync();
    if (legacyValue) {
      await this.setLastSync(legacyValue);
      Svc.PrefBranch.clearUserPref(this.name + ".lastSync");
      this._log.debug(
        `migrated timestamp of ${legacyValue} to the logins store`
      );
      return legacyValue;
    }
    return this._store.storage.getLastSync();
  },

  async setLastSync(timestamp) {
    await this._store.storage.setLastSync(timestamp);
  },

  // Testing function to emulate that a login has been synced.
  async markSynced(guid) {
    this._store.storage.resetSyncCounter(guid, 0);
  },

  async pullAllChanges() {
    return this._getChangedIDs(true);
  },

  async getChangedIDs() {
    return this._getChangedIDs(false);
  },

  async _getChangedIDs(getAll) {
    let changes = {};

    let logins = await this._store.storage.getAllLogins(true);
    for (let login of logins) {
      if (getAll || login.syncCounter > 0) {
        if (Utils.getSyncCredentialsHosts().has(login.origin)) {
          continue;
        }

        changes[login.guid] = {
          counter: login.syncCounter, // record the initial counter value
          modified: roundTimeForSync(login.timePasswordChanged),
          deleted: this._store.storage.loginIsDeleted(login.guid),
        };
      }
    }

    return changes;
  },

  async trackRemainingChanges() {
    // Reset the syncCounter on the items that were changed.
    for (let [guid, { counter, synced }] of Object.entries(
      this._modified.changes
    )) {
      if (synced) {
        this._store.storage.resetSyncCounter(guid, counter);
      }
    }
  },

  async _findDupe(item) {
    let login = this._store._nsLoginInfoFromRecord(item);
    if (!login) {
      return null;
    }

    let logins = await this._store.storage.searchLoginsAsync({
      origin: login.origin,
      formActionOrigin: login.formActionOrigin,
      httpRealm: login.httpRealm,
    });

    // Look for existing logins that match the origin, but ignore the password.
    for (let local of logins) {
      if (login.matches(local, true) && local instanceof Ci.nsILoginMetaInfo) {
        return local.guid;
      }
    }

    return null;
  },

  _deleteId(id) {
    this._noteDeletedId(id);
  },

  getValidator() {
    return new PasswordValidator();
  },
};
Object.setPrototypeOf(PasswordEngine.prototype, SyncEngine.prototype);

function PasswordStore(name, engine) {
  Store.call(this, name, engine);
  this._nsLoginInfo = new Components.Constructor(
    "@mozilla.org/login-manager/loginInfo;1",
    Ci.nsILoginInfo,
    "init"
  );
  this.storage = LoginManagerStorage.create();
}
PasswordStore.prototype = {
  _newPropertyBag() {
    return Cc["@mozilla.org/hash-property-bag;1"].createInstance(
      Ci.nsIWritablePropertyBag2
    );
  },

  // Returns an stringified object of any fields not "known" by this client
  // mainly used to to prevent data loss for other clients by roundtripping
  // these fields without processing them
  _processUnknownFields(record) {
    let unknownFields = {};
    let keys = Object.keys(record);
    keys
      .filter(key => !VALID_LOGIN_FIELDS.includes(key))
      .forEach(key => {
        unknownFields[key] = record[key];
      });
    // If we found some unknown fields, we stringify it to be able
    // to properly encrypt it for roundtripping since we can't know if
    // it contained sensitive fields or not
    if (Object.keys(unknownFields).length) {
      return JSON.stringify(unknownFields);
    }
    return null;
  },

  /**
   * Return an instance of nsILoginInfo (and, implicitly, nsILoginMetaInfo).
   */
  _nsLoginInfoFromRecord(record) {
    function nullUndefined(x) {
      return x == undefined ? null : x;
    }

    function stringifyNullUndefined(x) {
      return x == undefined || x == null ? "" : x;
    }

    if (record.formSubmitURL && record.httpRealm) {
      this._log.warn(
        "Record " +
          record.id +
          " has both formSubmitURL and httpRealm. Skipping."
      );
      return null;
    }

    // Passing in "undefined" results in an empty string, which later
    // counts as a value. Explicitly `|| null` these fields according to JS
    // truthiness. Records with empty strings or null will be unmolested.
    let info = new this._nsLoginInfo(
      record.hostname,
      nullUndefined(record.formSubmitURL),
      nullUndefined(record.httpRealm),
      stringifyNullUndefined(record.username),
      record.password,
      record.usernameField,
      record.passwordField
    );

    info.QueryInterface(Ci.nsILoginMetaInfo);
    info.guid = record.id;
    if (record.timeCreated && !isNaN(new Date(record.timeCreated).getTime())) {
      info.timeCreated = record.timeCreated;
    }
    if (
      record.timePasswordChanged &&
      !isNaN(new Date(record.timePasswordChanged).getTime())
    ) {
      info.timePasswordChanged = record.timePasswordChanged;
    }

    // Check the record if there are any unknown fields from other clients
    // that we want to roundtrip during sync to prevent data loss
    let unknownFields = this._processUnknownFields(record.cleartext);
    if (unknownFields) {
      info.unknownFields = unknownFields;
    }
    return info;
  },

  async _getLoginFromGUID(guid) {
    let logins = await this.storage.searchLoginsAsync({ guid }, true);
    if (logins.length) {
      this._log.trace(logins.length + " items matching " + guid + " found.");
      return logins[0];
    }

    this._log.trace("No items matching " + guid + " found. Ignoring");
    return null;
  },

  async applyIncoming(record) {
    if (record.deleted) {
      // Need to supply the sourceSync flag.
      await this.remove(record, { sourceSync: true });
      return;
    }

    await super.applyIncoming(record);
  },

  async getAllIDs() {
    let items = {};
    let logins = await this.storage.getAllLogins(true);

    for (let i = 0; i < logins.length; i++) {
      // Skip over Weave password/passphrase entries.
      let metaInfo = logins[i].QueryInterface(Ci.nsILoginMetaInfo);
      if (Utils.getSyncCredentialsHosts().has(metaInfo.origin)) {
        continue;
      }

      items[metaInfo.guid] = metaInfo;
    }

    return items;
  },

  async changeItemID(oldID, newID) {
    this._log.trace("Changing item ID: " + oldID + " to " + newID);

    if (!(await this.itemExists(oldID))) {
      this._log.trace("Can't change item ID: item doesn't exist");
      return;
    }
    if (await this._getLoginFromGUID(newID)) {
      this._log.trace("Can't change item ID: new ID already in use");
      return;
    }

    let prop = this._newPropertyBag();
    prop.setPropertyAsAUTF8String("guid", newID);

    let oldLogin = await this._getLoginFromGUID(oldID);
    this.storage.modifyLogin(oldLogin, prop, true);
  },

  async itemExists(id) {
    let login = await this._getLoginFromGUID(id);
    return login && !this.storage.loginIsDeleted(id);
  },

  async createRecord(id, collection) {
    let record = new LoginRec(collection, id);
    let login = await this._getLoginFromGUID(id);

    if (!login || this.storage.loginIsDeleted(id)) {
      record.deleted = true;
      return record;
    }

    record.hostname = login.origin;
    record.formSubmitURL = login.formActionOrigin;
    record.httpRealm = login.httpRealm;
    record.username = login.username;
    record.password = login.password;
    record.usernameField = login.usernameField;
    record.passwordField = login.passwordField;

    // Optional fields.
    login.QueryInterface(Ci.nsILoginMetaInfo);
    record.timeCreated = login.timeCreated;
    record.timePasswordChanged = login.timePasswordChanged;

    // put the unknown fields back to the top-level record
    // during upload
    if (login.unknownFields) {
      let unknownFields = JSON.parse(login.unknownFields);
      if (unknownFields) {
        Object.keys(unknownFields).forEach(key => {
          // We have to manually add it to the cleartext since that's
          // what gets processed during upload
          record.cleartext[key] = unknownFields[key];
        });
      }
    }

    return record;
  },

  async create(record) {
    let login = this._nsLoginInfoFromRecord(record);
    if (!login) {
      return;
    }

    login.everSynced = true;

    this._log.trace("Adding login for " + record.hostname);
    this._log.trace(
      "httpRealm: " +
        JSON.stringify(login.httpRealm) +
        "; " +
        "formSubmitURL: " +
        JSON.stringify(login.formActionOrigin)
    );
    await Services.logins.addLoginAsync(login);
  },

  async remove(record, { sourceSync = false } = {}) {
    this._log.trace("Removing login " + record.id);

    let loginItem = await this._getLoginFromGUID(record.id);
    if (!loginItem) {
      this._log.trace("Asked to remove record that doesn't exist, ignoring");
      return;
    }

    this.storage.removeLogin(loginItem, sourceSync);
  },

  async update(record) {
    let loginItem = await this._getLoginFromGUID(record.id);
    if (!loginItem || this.storage.loginIsDeleted(record.id)) {
      this._log.trace("Skipping update for unknown item: " + record.hostname);
      return;
    }

    this._log.trace("Updating " + record.hostname);
    let newinfo = this._nsLoginInfoFromRecord(record);
    if (!newinfo) {
      return;
    }

    loginItem.everSynced = true;

    this.storage.modifyLogin(loginItem, newinfo, true);
  },

  async wipe() {
    this.storage.removeAllUserFacingLogins(true);
  },
};
Object.setPrototypeOf(PasswordStore.prototype, Store.prototype);

function PasswordTracker(name, engine) {
  Tracker.call(this, name, engine);
}
PasswordTracker.prototype = {
  onStart() {
    Svc.Obs.add("passwordmgr-storage-changed", this.asyncObserver);
  },

  onStop() {
    Svc.Obs.remove("passwordmgr-storage-changed", this.asyncObserver);
  },

  async observe(subject, topic, data) {
    if (this.ignoreAll) {
      return;
    }

    switch (data) {
      case "modifyLogin":
        // The syncCounter should have been incremented only for
        // those items that need to be sycned.
        if (
          subject.QueryInterface(Ci.nsIArrayExtensions).GetElementAt(1)
            .syncCounter > 0
        ) {
          this.score += SCORE_INCREMENT_XLARGE;
        }
        break;

      case "addLogin":
      case "removeLogin":
      case "importLogins":
        this.score += SCORE_INCREMENT_XLARGE;
        break;

      case "removeAllLogins":
        this.score +=
          SCORE_INCREMENT_XLARGE *
          (subject.QueryInterface(Ci.nsIArrayExtensions).Count() + 1);
        break;
    }
  },
};
Object.setPrototypeOf(PasswordTracker.prototype, Tracker.prototype);

export class PasswordValidator extends CollectionValidator {
  constructor() {
    super("passwords", "id", [
      "hostname",
      "formSubmitURL",
      "httpRealm",
      "password",
      "passwordField",
      "username",
      "usernameField",
    ]);
  }

  async getClientItems() {
    let logins = await Services.logins.getAllLogins();
    let syncHosts = Utils.getSyncCredentialsHosts();
    let result = logins
      .map(l => l.QueryInterface(Ci.nsILoginMetaInfo))
      .filter(l => !syncHosts.has(l.origin));
    return Promise.resolve(result);
  }

  normalizeClientItem(item) {
    return {
      id: item.guid,
      guid: item.guid,
      hostname: item.hostname,
      formSubmitURL: item.formSubmitURL,
      httpRealm: item.httpRealm,
      password: item.password,
      passwordField: item.passwordField,
      username: item.username,
      usernameField: item.usernameField,
      original: item,
    };
  }

  async normalizeServerItem(item) {
    return Object.assign({ guid: item.id }, item);
  }
}

export class PasswordsChangeset extends Changeset {
  getModifiedTimestamp(id) {
    return this.changes[id].modified;
  }

  has(id) {
    let change = this.changes[id];
    if (change) {
      return !change.synced;
    }
    return false;
  }

  delete(id) {
    let change = this.changes[id];
    if (change) {
      // Mark the change as synced without removing it from the set.
      // This allows the sync counter to be reset when sync is complete
      // within trackRemainingChanges.
      change.synced = true;
    }
  }
}
PK
!<�3ò�@�@+modules/services-sync/engines/prefs.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Prefs which start with this prefix are our "control" prefs - they indicate
// which preferences should be synced.
const PREF_SYNC_PREFS_PREFIX = "services.sync.prefs.sync.";

// Prefs which have a default value are usually not synced - however, if the
// preference exists under this prefix and the value is:
// * `true`, then we do sync default values.
// * `false`, then as soon as we ever sync a non-default value out, or sync
//    any value in, then we toggle the value to `true`.
//
// We never explicitly set this pref back to false, so it's one-shot.
// Some preferences which are known to have a different default value on
// different platforms have this preference with a default value of `false`,
// so they don't sync until one device changes to the non-default value, then
// that value forever syncs, even if it gets reset back to the default.
// Note that preferences handled this way *must also* have the "normal"
// control pref set.
// A possible future enhancement would be to sync these prefs so that
// other distributions can flag them if they change the default, but that
// doesn't seem worthwhile until we can be confident they'd actually create
// this special control pref at the same time they flip the default.
const PREF_SYNC_SEEN_PREFIX = "services.sync.prefs.sync-seen.";

import {
  Store,
  SyncEngine,
  Tracker,
} from "resource://services-sync/engines.sys.mjs";
import { CryptoWrapper } from "resource://services-sync/record.sys.mjs";
import { Svc, Utils } from "resource://services-sync/util.sys.mjs";
import { SCORE_INCREMENT_XLARGE } from "resource://services-sync/constants.sys.mjs";
import { CommonUtils } from "resource://services-common/utils.sys.mjs";

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "PREFS_GUID", () =>
  CommonUtils.encodeBase64URL(Services.appinfo.ID)
);

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
});

// In bug 1538015, we decided that it isn't always safe to allow all "incoming"
// preferences to be applied locally. So we introduced another preference to control
// this for backward compatibility. We removed that capability in bug 1854698, but in the
// interests of working well between different versions of Firefox, we still forever
// want to prevent this preference from syncing.
// This was the name of the "control" pref.
const PREF_SYNC_PREFS_ARBITRARY =
  "services.sync.prefs.dangerously_allow_arbitrary";

// Check for a local control pref or PREF_SYNC_PREFS_ARBITRARY
function isAllowedPrefName(prefName) {
  if (prefName == PREF_SYNC_PREFS_ARBITRARY) {
    return false; // never allow this.
  }
  // The pref must already have a control pref set, although it doesn't matter
  // here whether that value is true or false. We can't use prefHasUserValue
  // here because we also want to check prefs still with default values.
  try {
    Services.prefs.getBoolPref(PREF_SYNC_PREFS_PREFIX + prefName);
    // pref exists!
    return true;
  } catch (_) {
    return false;
  }
}

export function PrefRec(collection, id) {
  CryptoWrapper.call(this, collection, id);
}

PrefRec.prototype = {
  _logName: "Sync.Record.Pref",
};
Object.setPrototypeOf(PrefRec.prototype, CryptoWrapper.prototype);

Utils.deferGetSet(PrefRec, "cleartext", ["value"]);

export function PrefsEngine(service) {
  SyncEngine.call(this, "Prefs", service);
}

PrefsEngine.prototype = {
  _storeObj: PrefStore,
  _trackerObj: PrefTracker,
  _recordObj: PrefRec,
  version: 2,

  syncPriority: 1,
  allowSkippedRecord: false,

  async getChangedIDs() {
    // No need for a proper timestamp (no conflict resolution needed).
    let changedIDs = {};
    if (this._tracker.modified) {
      changedIDs[lazy.PREFS_GUID] = 0;
    }
    return changedIDs;
  },

  async _wipeClient() {
    await SyncEngine.prototype._wipeClient.call(this);
    this.justWiped = true;
  },

  async _reconcile(item) {
    // Apply the incoming item if we don't care about the local data
    if (this.justWiped) {
      this.justWiped = false;
      return true;
    }
    return SyncEngine.prototype._reconcile.call(this, item);
  },

  async _uploadOutgoing() {
    try {
      await SyncEngine.prototype._uploadOutgoing.call(this);
    } finally {
      this._store._incomingPrefs = null;
    }
  },

  async trackRemainingChanges() {
    if (this._modified.count() > 0) {
      this._tracker.modified = true;
    }
  },
};
Object.setPrototypeOf(PrefsEngine.prototype, SyncEngine.prototype);

// We don't use services.sync.engine.tabs.filteredSchemes since it includes
// about: pages and the like, which we want to be syncable in preferences.
// Blob, moz-extension, data and file uris are never safe to sync,
// so we limit our check to those.
const UNSYNCABLE_URL_REGEXP = /^(moz-extension|blob|data|file):/i;
function isUnsyncableURLPref(prefName) {
  if (Services.prefs.getPrefType(prefName) != Ci.nsIPrefBranch.PREF_STRING) {
    return false;
  }
  const prefValue = Services.prefs.getStringPref(prefName, "");
  return UNSYNCABLE_URL_REGEXP.test(prefValue);
}

function PrefStore(name, engine) {
  Store.call(this, name, engine);
  Svc.Obs.add(
    "profile-before-change",
    function () {
      this.__prefs = null;
    },
    this
  );
}
PrefStore.prototype = {
  __prefs: null,
  // used just for logging so we can work out why we chose to re-upload
  _incomingPrefs: null,
  get _prefs() {
    if (!this.__prefs) {
      this.__prefs = Services.prefs.getBranch("");
    }
    return this.__prefs;
  },

  _getSyncPrefs() {
    let syncPrefs = Services.prefs
      .getBranch(PREF_SYNC_PREFS_PREFIX)
      .getChildList("")
      .filter(pref => isAllowedPrefName(pref) && !isUnsyncableURLPref(pref));
    // Also sync preferences that determine which prefs get synced.
    let controlPrefs = syncPrefs.map(pref => PREF_SYNC_PREFS_PREFIX + pref);
    return controlPrefs.concat(syncPrefs);
  },

  _isSynced(pref) {
    if (pref.startsWith(PREF_SYNC_PREFS_PREFIX)) {
      // this is an incoming control pref, which is ignored if there's not already
      // a local control pref for the preference.
      let controlledPref = pref.slice(PREF_SYNC_PREFS_PREFIX.length);
      return isAllowedPrefName(controlledPref);
    }

    // This is the pref itself - it must be both allowed, and have a control
    // pref which is true.
    if (!this._prefs.getBoolPref(PREF_SYNC_PREFS_PREFIX + pref, false)) {
      return false;
    }
    return isAllowedPrefName(pref);
  },

  // Given a preference name, returns either a string, bool, number or null.
  _getPrefValue(pref) {
    switch (this._prefs.getPrefType(pref)) {
      case Ci.nsIPrefBranch.PREF_STRING:
        return this._prefs.getStringPref(pref);
      case Ci.nsIPrefBranch.PREF_INT:
        return this._prefs.getIntPref(pref);
      case Ci.nsIPrefBranch.PREF_BOOL:
        return this._prefs.getBoolPref(pref);
      //  case Ci.nsIPrefBranch.PREF_INVALID: handled by the fallthrough
    }
    return null;
  },

  _getAllPrefs() {
    let values = {};
    for (let pref of this._getSyncPrefs()) {
      // Note: _isSynced doesn't call isUnsyncableURLPref since it would cause
      // us not to apply (syncable) changes to preferences that are set locally
      // which have unsyncable urls.
      if (this._isSynced(pref) && !isUnsyncableURLPref(pref)) {
        let isSet = this._prefs.prefHasUserValue(pref);
        // Missing and default prefs get the null value, unless that `seen`
        // pref is set, in which case it always gets the value.
        let forceValue = this._prefs.getBoolPref(
          PREF_SYNC_SEEN_PREFIX + pref,
          false
        );
        if (isSet || forceValue) {
          values[pref] = this._getPrefValue(pref);
        } else {
          values[pref] = null;
        }
        // If incoming and outgoing don't match then either the user toggled a
        // pref that doesn't match an incoming non-default value for that pref
        // during a sync (unlikely!) or it refused to stick and is behaving oddly.
        if (this._incomingPrefs) {
          let inValue = this._incomingPrefs[pref];
          let outValue = values[pref];
          if (inValue != null && outValue != null && inValue != outValue) {
            this._log.debug(`Incoming pref '${pref}' refused to stick?`);
            this._log.trace(`Incoming: '${inValue}', outgoing: '${outValue}'`);
          }
        }
        // If this is a special "sync-seen" pref, and it's not the default value,
        // set the seen pref to true.
        if (
          isSet &&
          this._prefs.getBoolPref(PREF_SYNC_SEEN_PREFIX + pref, false) === false
        ) {
          this._log.trace(`toggling sync-seen pref for '${pref}' to true`);
          this._prefs.setBoolPref(PREF_SYNC_SEEN_PREFIX + pref, true);
        }
      }
    }
    return values;
  },

  _maybeLogPrefChange(pref, incomingValue, existingValue) {
    if (incomingValue != existingValue) {
      this._log.debug(`Adjusting preference "${pref}" to the incoming value`);
      // values are PII, so must only be logged at trace.
      this._log.trace(`Existing: ${existingValue}. Incoming: ${incomingValue}`);
    }
  },

  _setAllPrefs(values) {
    const selectedThemeIDPref = "extensions.activeThemeID";
    let selectedThemeIDBefore = this._prefs.getStringPref(
      selectedThemeIDPref,
      null
    );
    let selectedThemeIDAfter = selectedThemeIDBefore;

    // Update 'services.sync.prefs.sync.foo.pref' before 'foo.pref', otherwise
    // _isSynced returns false when 'foo.pref' doesn't exist (e.g., on a new device).
    let prefs = Object.keys(values).sort(
      a => -a.indexOf(PREF_SYNC_PREFS_PREFIX)
    );
    for (let pref of prefs) {
      let value = values[pref];
      if (!this._isSynced(pref)) {
        // It's unusual for us to find an incoming preference (ie, a pref some other
        // instance thinks is syncable) which we don't think is syncable.
        this._log.trace(`Ignoring incoming unsyncable preference "${pref}"`);
        continue;
      }

      if (typeof value == "string" && UNSYNCABLE_URL_REGEXP.test(value)) {
        this._log.trace(`Skipping incoming unsyncable url for pref: ${pref}`);
        continue;
      }

      switch (pref) {
        // Some special prefs we don't want to set directly.
        case selectedThemeIDPref:
          selectedThemeIDAfter = value;
          break;

        // default is to just set the pref
        default:
          if (value == null) {
            // Pref has gone missing. The best we can do is reset it.
            if (this._prefs.prefHasUserValue(pref)) {
              this._log.debug(`Clearing existing local preference "${pref}"`);
              this._log.trace(
                `Existing local value for preference: ${this._getPrefValue(
                  pref
                )}`
              );
            }
            this._prefs.clearUserPref(pref);
          } else {
            try {
              switch (typeof value) {
                case "string":
                  this._maybeLogPrefChange(
                    pref,
                    value,
                    this._prefs.getStringPref(pref, undefined)
                  );
                  this._prefs.setStringPref(pref, value);
                  break;
                case "number":
                  this._maybeLogPrefChange(
                    pref,
                    value,
                    this._prefs.getIntPref(pref, undefined)
                  );
                  this._prefs.setIntPref(pref, value);
                  break;
                case "boolean":
                  this._maybeLogPrefChange(
                    pref,
                    value,
                    this._prefs.getBoolPref(pref, undefined)
                  );
                  this._prefs.setBoolPref(pref, value);
                  break;
              }
            } catch (ex) {
              this._log.trace(`Failed to set pref: ${pref}`, ex);
            }
          }
          // If there's a "sync-seen" pref for this it gets toggled to true
          // regardless of the value.
          let seenPref = PREF_SYNC_SEEN_PREFIX + pref;
          if (
            this._prefs.getPrefType(seenPref) != Ci.nsIPrefBranch.PREF_INVALID
          ) {
            this._prefs.setBoolPref(PREF_SYNC_SEEN_PREFIX + pref, true);
          }
      }
    }
    // Themes are a little messy. Themes which have been installed are handled
    // by the addons engine - but default themes aren't seen by that engine.
    // So if there's a new default theme ID and that ID corresponds to a
    // system addon, then we arrange to enable that addon here.
    if (selectedThemeIDBefore != selectedThemeIDAfter) {
      this._maybeEnableBuiltinTheme(selectedThemeIDAfter).catch(e => {
        this._log.error("Failed to maybe update the default theme", e);
      });
    }
  },

  async _maybeEnableBuiltinTheme(themeId) {
    let addon = null;
    try {
      addon = await lazy.AddonManager.getAddonByID(themeId);
    } catch (ex) {
      this._log.trace(
        `There's no addon with ID '${themeId} - it can't be a builtin theme`
      );
      return;
    }
    if (addon && addon.isBuiltin && addon.type == "theme") {
      this._log.trace(`Enabling builtin theme '${themeId}'`);
      await addon.enable();
    } else {
      this._log.trace(
        `Have incoming theme ID of '${themeId}' but it's not a builtin theme`
      );
    }
  },

  async getAllIDs() {
    /* We store all prefs in just one WBO, with just one GUID */
    let allprefs = {};
    allprefs[lazy.PREFS_GUID] = true;
    return allprefs;
  },

  async changeItemID() {
    this._log.trace("PrefStore GUID is constant!");
  },

  async itemExists(id) {
    return id === lazy.PREFS_GUID;
  },

  async createRecord(id, collection) {
    let record = new PrefRec(collection, id);

    if (id == lazy.PREFS_GUID) {
      record.value = this._getAllPrefs();
    } else {
      record.deleted = true;
    }

    return record;
  },

  async create() {
    this._log.trace("Ignoring create request");
  },

  async remove() {
    this._log.trace("Ignoring remove request");
  },

  async update(record) {
    // Silently ignore pref updates that are for other apps.
    if (record.id != lazy.PREFS_GUID) {
      return;
    }

    this._log.trace("Received pref updates, applying...");
    this._incomingPrefs = record.value;
    this._setAllPrefs(record.value);
  },

  async wipe() {
    this._log.trace("Ignoring wipe request");
  },
};
Object.setPrototypeOf(PrefStore.prototype, Store.prototype);

function PrefTracker(name, engine) {
  Tracker.call(this, name, engine);
  this._ignoreAll = false;
  Svc.Obs.add("profile-before-change", this.asyncObserver);
}
PrefTracker.prototype = {
  get ignoreAll() {
    return this._ignoreAll;
  },

  set ignoreAll(value) {
    this._ignoreAll = value;
  },

  get modified() {
    return Svc.PrefBranch.getBoolPref("engine.prefs.modified", false);
  },
  set modified(value) {
    Svc.PrefBranch.setBoolPref("engine.prefs.modified", value);
  },

  clearChangedIDs: function clearChangedIDs() {
    this.modified = false;
  },

  __prefs: null,
  get _prefs() {
    if (!this.__prefs) {
      this.__prefs = Services.prefs.getBranch("");
    }
    return this.__prefs;
  },

  onStart() {
    Services.prefs.addObserver("", this.asyncObserver);
  },

  onStop() {
    this.__prefs = null;
    Services.prefs.removeObserver("", this.asyncObserver);
  },

  async observe(subject, topic, data) {
    switch (topic) {
      case "profile-before-change":
        await this.stop();
        break;
      case "nsPref:changed":
        if (this.ignoreAll) {
          break;
        }
        // Trigger a sync for MULTI-DEVICE for a change that determines
        // which prefs are synced or a regular pref change.
        if (
          data.indexOf(PREF_SYNC_PREFS_PREFIX) == 0 ||
          this._prefs.getBoolPref(PREF_SYNC_PREFS_PREFIX + data, false)
        ) {
          this.score += SCORE_INCREMENT_XLARGE;
          this.modified = true;
          this._log.trace("Preference " + data + " changed");
        }
        break;
    }
  },
};
Object.setPrototypeOf(PrefTracker.prototype, Tracker.prototype);

export function getPrefsGUIDForTest() {
  return lazy.PREFS_GUID;
}
PK
!<�%DRDR*modules/services-sync/engines/tabs.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const STORAGE_VERSION = 1; // This needs to be kept in-sync with the rust storage version

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { SyncEngine, Tracker } from "resource://services-sync/engines.sys.mjs";
import { Svc, Utils } from "resource://services-sync/util.sys.mjs";
import { Log } from "resource://gre/modules/Log.sys.mjs";
import {
  SCORE_INCREMENT_SMALL,
  STATUS_OK,
  URI_LENGTH_MAX,
} from "resource://services-sync/constants.sys.mjs";
import { CommonUtils } from "resource://services-common/utils.sys.mjs";
import { Async } from "resource://services-common/async.sys.mjs";
import {
  SyncRecord,
  SyncTelemetry,
} from "resource://services-sync/telemetry.sys.mjs";
import { BridgedEngine } from "resource://services-sync/bridged_engine.sys.mjs";

const FAR_FUTURE = 4102405200000; // 2100/01/01

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
  ReaderMode: "resource://gre/modules/ReaderMode.sys.mjs",
  getTabsStore: "resource://services-sync/TabsStore.sys.mjs",
  RemoteTabRecord: "resource://gre/modules/RustTabs.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "TABS_FILTERED_SCHEMES",
  "services.sync.engine.tabs.filteredSchemes",
  "",
  null,
  val => {
    return new Set(val.split("|"));
  }
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "SYNC_AFTER_DELAY_MS",
  "services.sync.syncedTabs.syncDelayAfterTabChange",
  0
);

// A "bridged engine" to our tabs component.
export function TabEngine(service) {
  BridgedEngine.call(this, "Tabs", service);
}

TabEngine.prototype = {
  _trackerObj: TabTracker,
  syncPriority: 3,

  async prepareTheBridge(isQuickWrite) {
    let clientsEngine = this.service.clientsEngine;
    // Tell the bridged engine about clients.
    // This is the same shape as ClientData in app-services.
    // schema: https://github.com/mozilla/application-services/blob/a1168751231ed4e88c44d85f6dccc09c3b412bd2/components/sync15/src/client_types.rs#L14
    let clientData = {
      local_client_id: clientsEngine.localID,
      recent_clients: {},
    };

    // We shouldn't upload tabs past what the server will accept
    let tabs = await this.getTabsWithinPayloadSize();
    await this._rustStore.setLocalTabs(
      tabs.map(tab => {
        // rust wants lastUsed in MS but the provider gives it in seconds
        tab.lastUsed = tab.lastUsed * 1000;
        return new lazy.RemoteTabRecord(tab);
      })
    );

    for (let remoteClient of clientsEngine.remoteClients) {
      let id = remoteClient.id;
      if (!id) {
        throw new Error("Remote client somehow did not have an id");
      }
      let client = {
        fxa_device_id: remoteClient.fxaDeviceId,
        // device_name and device_type are soft-deprecated - every client
        // prefers what's in the FxA record. But fill them correctly anyway.
        device_name: clientsEngine.getClientName(id) ?? "",
        device_type: clientsEngine.getClientType(id),
      };
      clientData.recent_clients[id] = client;
    }

    // put ourself in there too so we record the correct device info in our sync record.
    clientData.recent_clients[clientsEngine.localID] = {
      fxa_device_id: await clientsEngine.fxAccounts.device.getLocalId(),
      device_name: clientsEngine.localName,
      device_type: clientsEngine.localType,
    };

    // Quick write needs to adjust the lastSync so we can POST to the server
    // see quickWrite() for details
    if (isQuickWrite) {
      await this.setLastSync(FAR_FUTURE);
      await this._bridge.prepareForSync(JSON.stringify(clientData));
      return;
    }

    // Just incase we crashed while the lastSync timestamp was FAR_FUTURE, we
    // reset it to zero
    if ((await this.getLastSync()) === FAR_FUTURE) {
      await this._bridge.setLastSync(0);
    }
    await this._bridge.prepareForSync(JSON.stringify(clientData));
  },

  async _syncStartup() {
    await super._syncStartup();
    await this.prepareTheBridge();
  },

  async initialize() {
    await SyncEngine.prototype.initialize.call(this);

    this._rustStore = await lazy.getTabsStore();
    this._bridge = await this._rustStore.bridgedEngine();

    // Uniffi doesn't currently only support async methods, so we'll need to hardcode
    // these values for now (which is fine for now as these hardly ever change)
    this._bridge.storageVersion = STORAGE_VERSION;
    this._bridge.allowSkippedRecord = true;

    this._log.info("Got a bridged engine!");
    this._tracker.modified = true;
  },

  async getChangedIDs() {
    // No need for a proper timestamp (no conflict resolution needed).
    let changedIDs = {};
    if (this._tracker.modified) {
      changedIDs[this.service.clientsEngine.localID] = 0;
    }
    return changedIDs;
  },

  // API for use by Sync UI code to give user choices of tabs to open.
  async getAllClients() {
    let remoteTabs = await this._rustStore.getAll();
    let remoteClientTabs = [];
    for (let remoteClient of this.service.clientsEngine.remoteClients) {
      // We get the some client info from the rust tabs engine and some from
      // the clients engine.
      let rustClient = remoteTabs.find(
        x => x.clientId === remoteClient.fxaDeviceId
      );
      if (!rustClient) {
        continue;
      }
      let client = {
        // rust gives us ms but js uses seconds, so fix them up.
        tabs: rustClient.remoteTabs.map(tab => {
          tab.lastUsed = tab.lastUsed / 1000;
          return tab;
        }),
        lastModified: rustClient.lastModified / 1000,
        ...remoteClient,
      };
      remoteClientTabs.push(client);
    }
    return remoteClientTabs;
  },

  async removeClientData() {
    let url = this.engineURL + "/" + this.service.clientsEngine.localID;
    await this.service.resource(url).delete();
  },

  async trackRemainingChanges() {
    if (this._modified.count() > 0) {
      this._tracker.modified = true;
    }
  },

  async getTabsWithinPayloadSize() {
    const maxPayloadSize = this.service.getMaxRecordPayloadSize();
    // See bug 535326 comment 8 for an explanation of the estimation
    const maxSerializedSize = (maxPayloadSize / 4) * 3 - 1500;
    return TabProvider.getAllTabsWithEstimatedMax(true, maxSerializedSize);
  },

  // Support for "quick writes"
  _engineLock: Utils.lock,
  _engineLocked: false,

  // Tabs has a special lock to help support its "quick write"
  get locked() {
    return this._engineLocked;
  },
  lock() {
    if (this._engineLocked) {
      return false;
    }
    this._engineLocked = true;
    return true;
  },
  unlock() {
    this._engineLocked = false;
  },

  // Quickly do a POST of our current tabs if possible.
  // This does things that would be dangerous for other engines - eg, posting
  // without checking what's on the server could cause data-loss for other
  // engines, but because each device exclusively owns exactly 1 tabs record
  // with a known ID, it's safe here.
  // Returns true if we successfully synced, false otherwise (either on error
  // or because we declined to sync for any reason.) The return value is
  // primarily for tests.
  async quickWrite() {
    if (!this.enabled) {
      // this should be very rare, and only if tabs are disabled after the
      // timer is created.
      this._log.info("Can't do a quick-sync as tabs is disabled");
      return false;
    }
    // This quick-sync doesn't drive the login state correctly, so just
    // decline to sync if out status is bad
    if (this.service.status.checkSetup() != STATUS_OK) {
      this._log.info(
        "Can't do a quick-sync due to the service status",
        this.service.status.toString()
      );
      return false;
    }
    if (!this.service.serverConfiguration) {
      this._log.info("Can't do a quick sync before the first full sync");
      return false;
    }
    try {
      return await this._engineLock("tabs.js: quickWrite", async () => {
        // We want to restore the lastSync timestamp when complete so next sync
        // takes tabs written by other devices since our last real sync.
        // And for this POST we don't want the protections offered by
        // X-If-Unmodified-Since - we want the POST to work even if the remote
        // has moved on and we will catch back up next full sync.
        const origLastSync = await this.getLastSync();
        try {
          return this._doQuickWrite();
        } finally {
          // set the lastSync to it's original value for regular sync
          await this.setLastSync(origLastSync);
        }
      })();
    } catch (ex) {
      if (!Utils.isLockException(ex)) {
        throw ex;
      }
      this._log.info(
        "Can't do a quick-write as another tab sync is in progress"
      );
      return false;
    }
  },

  // The guts of the quick-write sync, after we've taken the lock, checked
  // the service status etc.
  async _doQuickWrite() {
    // We need to track telemetry for these syncs too!
    const name = "tabs";
    let telemetryRecord = new SyncRecord(
      SyncTelemetry.allowedEngines,
      "quick-write"
    );
    telemetryRecord.onEngineStart(name);
    try {
      Async.checkAppReady();
      // We need to prep the bridge before we try to POST since it grabs
      // the most recent local client id and properly sets a lastSync
      // which is needed for a proper POST request
      await this.prepareTheBridge(true);
      this._tracker.clearChangedIDs();
      this._tracker.resetScore();

      Async.checkAppReady();
      // now just the "upload" part of a sync,
      // which for a rust engine is  not obvious.
      // We need to do is ask the rust engine for the changes. Although
      // this is kinda abusing the bridged-engine interface, we know the tabs
      // implementation of it works ok
      let outgoing = await this._bridge.apply();
      // We know we always have exactly 1 record.
      let mine = outgoing[0];
      this._log.trace("outgoing bso", mine);
      // `this._recordObj` is a `BridgedRecord`, which isn't exported.
      let record = this._recordObj.fromOutgoingBso(this.name, JSON.parse(mine));
      let changeset = {};
      changeset[record.id] = { synced: false, record };
      this._modified.replace(changeset);

      Async.checkAppReady();
      await this._uploadOutgoing();
      telemetryRecord.onEngineStop(name, null);
      return true;
    } catch (ex) {
      this._log.warn("quicksync sync failed", ex);
      telemetryRecord.onEngineStop(name, ex);
      return false;
    } finally {
      // The top-level sync is never considered to fail here, just the engine
      telemetryRecord.finished(null);
      SyncTelemetry.takeTelemetryRecord(telemetryRecord);
    }
  },

  async _sync() {
    try {
      await this._engineLock("tabs.js: fullSync", async () => {
        await super._sync();
      })();
    } catch (ex) {
      if (!Utils.isLockException(ex)) {
        throw ex;
      }
      this._log.info(
        "Can't do full tabs sync as a quick-write is currently running"
      );
    }
  },
};
Object.setPrototypeOf(TabEngine.prototype, BridgedEngine.prototype);

export const TabProvider = {
  getWindowEnumerator() {
    return Services.wm.getEnumerator("navigator:browser");
  },

  shouldSkipWindow(win) {
    return win.closed || lazy.PrivateBrowsingUtils.isWindowPrivate(win);
  },

  getAllBrowserTabs() {
    let tabs = [];
    for (let win of this.getWindowEnumerator()) {
      if (this.shouldSkipWindow(win)) {
        continue;
      }
      // Get all the tabs from the browser
      for (let tab of win.gBrowser.tabs) {
        tabs.push(tab);
      }
    }

    return tabs.sort(function (a, b) {
      return b.lastAccessed - a.lastAccessed;
    });
  },

  // This function creates tabs records up to a specified amount of bytes
  // It is an "estimation" since we don't accurately calculate how much the
  // favicon and JSON overhead is and give a rough estimate (for optimization purposes)
  async getAllTabsWithEstimatedMax(filter, bytesMax) {
    let log = Log.repository.getLogger(`Sync.Engine.Tabs.Provider`);
    let tabRecords = [];
    let iconPromises = [];
    let runningByteLength = 0;
    let encoder = new TextEncoder();

    // Fetch all the tabs the user has open
    let winTabs = this.getAllBrowserTabs();

    for (let tab of winTabs) {
      // We don't want to process any more tabs than we can sync
      if (runningByteLength >= bytesMax) {
        log.warn(
          `Can't fit all tabs in sync payload: have ${winTabs.length},
              but can only fit ${tabRecords.length}.`
        );
        break;
      }

      // Note that we used to sync "tab history" (ie, the "back button") state,
      // but in practice this hasn't been used - only the current URI is of
      // interest to clients.
      // We stopped recording this in bug 1783991.
      if (!tab?.linkedBrowser) {
        continue;
      }
      let acceptable = !filter
        ? url => url
        : url =>
            url &&
            !lazy.TABS_FILTERED_SCHEMES.has(Services.io.extractScheme(url));

      let url = tab.linkedBrowser.currentURI?.spec;
      // Special case for reader mode.
      if (url && url.startsWith("about:reader?")) {
        url = lazy.ReaderMode.getOriginalUrl(url);
      }
      // We ignore the tab completely if the current entry url is
      // not acceptable (we need something accurate to open).
      if (!acceptable(url)) {
        continue;
      }

      if (url.length > URI_LENGTH_MAX) {
        log.trace("Skipping over-long URL.");
        continue;
      }

      let thisTab = new lazy.RemoteTabRecord({
        title: tab.linkedBrowser.contentTitle || "",
        urlHistory: [url],
        icon: "",
        lastUsed: Math.floor((tab.lastAccessed || 0) / 1000),
      });
      tabRecords.push(thisTab);

      // we don't want to wait for each favicon to resolve to get the bytes
      // so we estimate a conservative 100 chars for the favicon and json overhead
      // Rust will further optimize and trim if we happened to be wildly off
      runningByteLength +=
        encoder.encode(thisTab.title + thisTab.lastUsed + url).byteLength + 100;

      // Use the favicon service for the icon url - we can wait for the promises at the end.
      let iconPromise = lazy.PlacesUtils.promiseFaviconData(url)
        .then(iconData => {
          thisTab.icon = iconData.uri.spec;
        })
        .catch(() => {
          log.trace(
            `Failed to fetch favicon for ${url}`,
            thisTab.urlHistory[0]
          );
        });
      iconPromises.push(iconPromise);
    }

    await Promise.allSettled(iconPromises);
    return tabRecords;
  },
};

function TabTracker(name, engine) {
  Tracker.call(this, name, engine);

  // Make sure "this" pointer is always set correctly for event listeners.
  this.onTab = Utils.bind2(this, this.onTab);
  this._unregisterListeners = Utils.bind2(this, this._unregisterListeners);
}
TabTracker.prototype = {
  QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),

  clearChangedIDs() {
    this.modified = false;
  },

  // We do not track TabSelect because that almost always triggers
  // the web progress listeners (onLocationChange), which we already track
  _topics: ["TabOpen", "TabClose"],

  _registerListenersForWindow(window) {
    this._log.trace("Registering tab listeners in window");
    for (let topic of this._topics) {
      window.addEventListener(topic, this.onTab);
    }
    window.addEventListener("unload", this._unregisterListeners);
    // If it's got a tab browser we can listen for things like navigation.
    if (window.gBrowser) {
      window.gBrowser.addProgressListener(this);
    }
  },

  _unregisterListeners(event) {
    this._unregisterListenersForWindow(event.target);
  },

  _unregisterListenersForWindow(window) {
    this._log.trace("Removing tab listeners in window");
    window.removeEventListener("unload", this._unregisterListeners);
    for (let topic of this._topics) {
      window.removeEventListener(topic, this.onTab);
    }
    if (window.gBrowser) {
      window.gBrowser.removeProgressListener(this);
    }
  },

  onStart() {
    Svc.Obs.add("domwindowopened", this.asyncObserver);
    for (let win of Services.wm.getEnumerator("navigator:browser")) {
      this._registerListenersForWindow(win);
    }
  },

  onStop() {
    Svc.Obs.remove("domwindowopened", this.asyncObserver);
    for (let win of Services.wm.getEnumerator("navigator:browser")) {
      this._unregisterListenersForWindow(win);
    }
  },

  async observe(subject, topic) {
    switch (topic) {
      case "domwindowopened":
        let onLoad = () => {
          subject.removeEventListener("load", onLoad);
          // Only register after the window is done loading to avoid unloads.
          this._registerListenersForWindow(subject);
        };

        // Add tab listeners now that a window has opened.
        subject.addEventListener("load", onLoad);
        break;
    }
  },

  onTab(event) {
    if (event.originalTarget.linkedBrowser) {
      let browser = event.originalTarget.linkedBrowser;
      if (
        lazy.PrivateBrowsingUtils.isBrowserPrivate(browser) &&
        !lazy.PrivateBrowsingUtils.permanentPrivateBrowsing
      ) {
        this._log.trace("Ignoring tab event from private browsing.");
        return;
      }
    }
    this._log.trace("onTab event: " + event.type);

    switch (event.type) {
      case "TabOpen":
        /* We do not have a reliable way of checking the URI on the TabOpen
         * so we will rely on the other methods (onLocationChange, getAllTabsWithEstimatedMax)
         * to filter these when going through sync
         */
        this.callScheduleSync(SCORE_INCREMENT_SMALL);
        break;
      case "TabClose":
        // If event target has `linkedBrowser`, the event target can be assumed <tab> element.
        // Else, event target is assumed <browser> element, use the target as it is.
        const tab = event.target.linkedBrowser || event.target;

        // TabClose means the tab has already loaded and we can check the URI
        // and ignore if it's a scheme we don't care about
        if (lazy.TABS_FILTERED_SCHEMES.has(tab.currentURI.scheme)) {
          return;
        }
        this.callScheduleSync(SCORE_INCREMENT_SMALL);
        break;
    }
  },

  // web progress listeners.
  onLocationChange(webProgress, request, locationURI, flags) {
    // We only care about top-level location changes. We do want location changes in the
    // same document because if a page uses the `pushState()` API, they *appear* as though
    // they are in the same document even if the URL changes. It also doesn't hurt to accurately
    // reflect the fragment changing - so we allow LOCATION_CHANGE_SAME_DOCUMENT
    if (
      flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_RELOAD ||
      !webProgress.isTopLevel ||
      !locationURI
    ) {
      return;
    }

    // We can't filter out tabs that we don't sync here, because we might be
    // navigating from a tab that we *did* sync to one we do not, and that
    // tab we *did* sync should no longer be synced.
    this.callScheduleSync();
  },

  callScheduleSync(scoreIncrement) {
    this.modified = true;
    let { scheduler } = this.engine.service;
    let delayInMs = lazy.SYNC_AFTER_DELAY_MS;

    // Schedule a sync once we detect a tab change
    // to ensure the server always has the most up to date tabs
    if (
      delayInMs > 0 &&
      scheduler.numClients > 1 // Only schedule quick syncs for multi client users
    ) {
      if (this.tabsQuickWriteTimer) {
        this._log.debug(
          "Detected a tab change, but a quick-write is already scheduled"
        );
        return;
      }
      this._log.debug(
        "Detected a tab change: scheduling a quick-write in " + delayInMs + "ms"
      );
      CommonUtils.namedTimer(
        () => {
          this._log.trace("tab quick-sync timer fired.");
          this.engine
            .quickWrite()
            .then(() => {
              this._log.trace("tab quick-sync done.");
            })
            .catch(ex => {
              this._log.error("tab quick-sync failed.", ex);
            });
        },
        delayInMs,
        this,
        "tabsQuickWriteTimer"
      );
    } else if (scoreIncrement) {
      this._log.debug(
        "Detected a tab change, but conditions aren't met for a quick write - bumping score"
      );
      this.score += scoreIncrement;
    } else {
      this._log.debug(
        "Detected a tab change, but conditions aren't met for a quick write or a score bump"
      );
    }
  },
};
Object.setPrototypeOf(TabTracker.prototype, Tracker.prototype);
PK
!<R�
W��%modules/services-sync/engines.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { JSONFile } from "resource://gre/modules/JSONFile.sys.mjs";
import { Log } from "resource://gre/modules/Log.sys.mjs";

import { Async } from "resource://services-common/async.sys.mjs";
import { Observers } from "resource://services-common/observers.sys.mjs";

import {
  DEFAULT_DOWNLOAD_BATCH_SIZE,
  DEFAULT_GUID_FETCH_BATCH_SIZE,
  ENGINE_BATCH_INTERRUPTED,
  ENGINE_DOWNLOAD_FAIL,
  ENGINE_UPLOAD_FAIL,
  VERSION_OUT_OF_DATE,
  PREFS_BRANCH,
} from "resource://services-sync/constants.sys.mjs";

import {
  Collection,
  CryptoWrapper,
} from "resource://services-sync/record.sys.mjs";
import { Resource } from "resource://services-sync/resource.sys.mjs";
import {
  SerializableSet,
  Svc,
  Utils,
} from "resource://services-sync/util.sys.mjs";
import { SyncedRecordsTelemetry } from "resource://services-sync/telemetry.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
});

function ensureDirectory(path) {
  return IOUtils.makeDirectory(PathUtils.parent(path), {
    createAncestors: true,
  });
}

/**
 * Trackers are associated with a single engine and deal with
 * listening for changes to their particular data type.
 *
 * The base `Tracker` only supports listening for changes, and bumping the score
 * to indicate how urgently the engine wants to sync. It does not persist any
 * data. Engines that track changes directly in the storage layer (like
 * bookmarks, bridged engines, addresses, and credit cards) or only upload a
 * single record (tabs and preferences) should subclass `Tracker`.
 */
export function Tracker(name, engine) {
  if (!engine) {
    throw new Error("Tracker must be associated with an Engine instance.");
  }

  name = name || "Unnamed";
  this.name = name.toLowerCase();
  this.engine = engine;

  this._log = Log.repository.getLogger(`Sync.Engine.${name}.Tracker`);

  this._score = 0;

  this.asyncObserver = Async.asyncObserver(this, this._log);
}

Tracker.prototype = {
  // New-style trackers use change sources to filter out changes made by Sync in
  // observer notifications, so we don't want to let the engine ignore all
  // changes during a sync.
  get ignoreAll() {
    return false;
  },

  // Define an empty setter so that the engine doesn't throw a `TypeError`
  // setting a read-only property.
  set ignoreAll(value) {},

  /*
   * Score can be called as often as desired to decide which engines to sync
   *
   * Valid values for score:
   * -1: Do not sync unless the user specifically requests it (almost disabled)
   * 0: Nothing has changed
   * 100: Please sync me ASAP!
   *
   * Setting it to other values should (but doesn't currently) throw an exception
   */
  get score() {
    return this._score;
  },

  set score(value) {
    this._score = value;
    Observers.notify("weave:engine:score:updated", this.name);
  },

  // Should be called by service everytime a sync has been done for an engine
  resetScore() {
    this._score = 0;
  },

  // Unsupported, and throws a more descriptive error to ensure callers aren't
  // accidentally using persistence.
  async getChangedIDs() {
    throw new TypeError("This tracker doesn't store changed IDs");
  },

  // Also unsupported.
  async addChangedID() {
    throw new TypeError("Can't add changed ID to this tracker");
  },

  // Ditto.
  async removeChangedID() {
    throw new TypeError("Can't remove changed IDs from this tracker");
  },

  // This method is called at various times, so we override with a no-op
  // instead of throwing.
  clearChangedIDs() {},

  _now() {
    return Date.now() / 1000;
  },

  _isTracking: false,

  start() {
    if (!this.engineIsEnabled()) {
      return;
    }
    this._log.trace("start().");
    if (!this._isTracking) {
      this.onStart();
      this._isTracking = true;
    }
  },

  async stop() {
    this._log.trace("stop().");
    if (this._isTracking) {
      await this.asyncObserver.promiseObserversComplete();
      this.onStop();
      this._isTracking = false;
    }
  },

  // Override these in your subclasses.
  onStart() {},
  onStop() {},
  async observe() {},

  engineIsEnabled() {
    if (!this.engine) {
      // Can't tell -- we must be running in a test!
      return true;
    }
    return this.engine.enabled;
  },

  /**
   * Starts or stops listening for changes depending on the associated engine's
   * enabled state.
   *
   * @param {Boolean} engineEnabled Whether the engine was enabled.
   */
  async onEngineEnabledChanged(engineEnabled) {
    if (engineEnabled == this._isTracking) {
      return;
    }

    if (engineEnabled) {
      this.start();
    } else {
      await this.stop();
      this.clearChangedIDs();
    }
  },

  async finalize() {
    await this.stop();
  },
};

/*
 * A tracker that persists a list of IDs for all changed items that need to be
 * synced. This is 🚨 _extremely deprecated_ 🚨 and only kept around for current
 * engines. ⚠️ Please **don't use it** for new engines! ⚠️
 *
 * Why is this kind of external change tracking deprecated? Because it causes
 * consistency issues due to missed notifications, interrupted syncs, and the
 * tracker's view of what changed diverging from the data store's.
 */
export function LegacyTracker(name, engine) {
  Tracker.call(this, name, engine);

  this._ignored = [];
  this.file = this.name;
  this._storage = new JSONFile({
    path: Utils.jsonFilePath("changes", this.file),
    dataPostProcessor: json => this._dataPostProcessor(json),
    beforeSave: () => this._beforeSave(),
  });
  this._ignoreAll = false;
}

LegacyTracker.prototype = {
  get ignoreAll() {
    return this._ignoreAll;
  },

  set ignoreAll(value) {
    this._ignoreAll = value;
  },

  // Default to an empty object if the file doesn't exist.
  _dataPostProcessor(json) {
    return (typeof json == "object" && json) || {};
  },

  // Ensure the Weave storage directory exists before writing the file.
  _beforeSave() {
    return ensureDirectory(this._storage.path);
  },

  async getChangedIDs() {
    await this._storage.load();
    return this._storage.data;
  },

  _saveChangedIDs() {
    this._storage.saveSoon();
  },

  // ignore/unignore specific IDs.  Useful for ignoring items that are
  // being processed, or that shouldn't be synced.
  // But note: not persisted to disk

  ignoreID(id) {
    this.unignoreID(id);
    this._ignored.push(id);
  },

  unignoreID(id) {
    let index = this._ignored.indexOf(id);
    if (index != -1) {
      this._ignored.splice(index, 1);
    }
  },

  async _saveChangedID(id, when) {
    this._log.trace(`Adding changed ID: ${id}, ${JSON.stringify(when)}`);
    const changedIDs = await this.getChangedIDs();
    changedIDs[id] = when;
    this._saveChangedIDs();
  },

  async addChangedID(id, when) {
    if (!id) {
      this._log.warn("Attempted to add undefined ID to tracker");
      return false;
    }

    if (this.ignoreAll || this._ignored.includes(id)) {
      return false;
    }

    // Default to the current time in seconds if no time is provided.
    if (when == null) {
      when = this._now();
    }

    const changedIDs = await this.getChangedIDs();
    // Add/update the entry if we have a newer time.
    if ((changedIDs[id] || -Infinity) < when) {
      await this._saveChangedID(id, when);
    }

    return true;
  },

  async removeChangedID(...ids) {
    if (!ids.length || this.ignoreAll) {
      return false;
    }
    for (let id of ids) {
      if (!id) {
        this._log.warn("Attempted to remove undefined ID from tracker");
        continue;
      }
      if (this._ignored.includes(id)) {
        this._log.debug(`Not removing ignored ID ${id} from tracker`);
        continue;
      }
      const changedIDs = await this.getChangedIDs();
      if (changedIDs[id] != null) {
        this._log.trace("Removing changed ID " + id);
        delete changedIDs[id];
      }
    }
    this._saveChangedIDs();
    return true;
  },

  clearChangedIDs() {
    this._log.trace("Clearing changed ID list");
    this._storage.data = {};
    this._saveChangedIDs();
  },

  async finalize() {
    // Persist all pending tracked changes to disk, and wait for the final write
    // to finish.
    await super.finalize();
    this._saveChangedIDs();
    await this._storage.finalize();
  },
};
Object.setPrototypeOf(LegacyTracker.prototype, Tracker.prototype);

/**
 * The Store serves as the interface between Sync and stored data.
 *
 * The name "store" is slightly a misnomer because it doesn't actually "store"
 * anything. Instead, it serves as a gateway to something that actually does
 * the "storing."
 *
 * The store is responsible for record management inside an engine. It tells
 * Sync what items are available for Sync, converts items to and from Sync's
 * record format, and applies records from Sync into changes on the underlying
 * store.
 *
 * Store implementations require a number of functions to be implemented. These
 * are all documented below.
 *
 * For stores that deal with many records or which have expensive store access
 * routines, it is highly recommended to implement a custom applyIncomingBatch
 * and/or applyIncoming function on top of the basic APIs.
 */

export function Store(name, engine) {
  if (!engine) {
    throw new Error("Store must be associated with an Engine instance.");
  }

  name = name || "Unnamed";
  this.name = name.toLowerCase();
  this.engine = engine;

  this._log = Log.repository.getLogger(`Sync.Engine.${name}.Store`);

  ChromeUtils.defineLazyGetter(this, "_timer", function () {
    return Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  });
}

Store.prototype = {
  /**
   * Apply multiple incoming records against the store.
   *
   * This is called with a set of incoming records to process. The function
   * should look at each record, reconcile with the current local state, and
   * make the local changes required to bring its state in alignment with the
   * record.
   *
   * The default implementation simply iterates over all records and calls
   * applyIncoming(). Store implementations may overwrite this function
   * if desired.
   *
   * @param  records Array of records to apply
   * @param  a SyncedRecordsTelemetry obj that will keep track of failed reasons
   * @return Array of record IDs which did not apply cleanly
   */
  async applyIncomingBatch(records, countTelemetry) {
    let failed = [];

    await Async.yieldingForEach(records, async record => {
      try {
        await this.applyIncoming(record);
      } catch (ex) {
        if (ex.code == SyncEngine.prototype.eEngineAbortApplyIncoming) {
          // This kind of exception should have a 'cause' attribute, which is an
          // originating exception.
          // ex.cause will carry its stack with it when rethrown.
          throw ex.cause;
        }
        if (Async.isShutdownException(ex)) {
          throw ex;
        }
        this._log.warn("Failed to apply incoming record " + record.id, ex);
        failed.push(record.id);
        countTelemetry.addIncomingFailedReason(ex.message);
      }
    });

    return failed;
  },

  /**
   * Apply a single record against the store.
   *
   * This takes a single record and makes the local changes required so the
   * local state matches what's in the record.
   *
   * The default implementation calls one of remove(), create(), or update()
   * depending on the state obtained from the store itself. Store
   * implementations may overwrite this function if desired.
   *
   * @param record
   *        Record to apply
   */
  async applyIncoming(record) {
    if (record.deleted) {
      await this.remove(record);
    } else if (!(await this.itemExists(record.id))) {
      await this.create(record);
    } else {
      await this.update(record);
    }
  },

  // override these in derived objects

  /**
   * Create an item in the store from a record.
   *
   * This is called by the default implementation of applyIncoming(). If using
   * applyIncomingBatch(), this won't be called unless your store calls it.
   *
   * @param record
   *        The store record to create an item from
   */
  async create() {
    throw new Error("override create in a subclass");
  },

  /**
   * Remove an item in the store from a record.
   *
   * This is called by the default implementation of applyIncoming(). If using
   * applyIncomingBatch(), this won't be called unless your store calls it.
   *
   * @param record
   *        The store record to delete an item from
   */
  async remove() {
    throw new Error("override remove in a subclass");
  },

  /**
   * Update an item from a record.
   *
   * This is called by the default implementation of applyIncoming(). If using
   * applyIncomingBatch(), this won't be called unless your store calls it.
   *
   * @param record
   *        The record to use to update an item from
   */
  async update() {
    throw new Error("override update in a subclass");
  },

  /**
   * Determine whether a record with the specified ID exists.
   *
   * Takes a string record ID and returns a booleans saying whether the record
   * exists.
   *
   * @param  id
   *         string record ID
   * @return boolean indicating whether record exists locally
   */
  async itemExists() {
    throw new Error("override itemExists in a subclass");
  },

  /**
   * Create a record from the specified ID.
   *
   * If the ID is known, the record should be populated with metadata from
   * the store. If the ID is not known, the record should be created with the
   * delete field set to true.
   *
   * @param  id
   *         string record ID
   * @param  collection
   *         Collection to add record to. This is typically passed into the
   *         constructor for the newly-created record.
   * @return record type for this engine
   */
  async createRecord() {
    throw new Error("override createRecord in a subclass");
  },

  /**
   * Change the ID of a record.
   *
   * @param  oldID
   *         string old/current record ID
   * @param  newID
   *         string new record ID
   */
  async changeItemID() {
    throw new Error("override changeItemID in a subclass");
  },

  /**
   * Obtain the set of all known record IDs.
   *
   * @return Object with ID strings as keys and values of true. The values
   *         are ignored.
   */
  async getAllIDs() {
    throw new Error("override getAllIDs in a subclass");
  },

  /**
   * Wipe all data in the store.
   *
   * This function is called during remote wipes or when replacing local data
   * with remote data.
   *
   * This function should delete all local data that the store is managing. It
   * can be thought of as clearing out all state and restoring the "new
   * browser" state.
   */
  async wipe() {
    throw new Error("override wipe in a subclass");
  },
};

export function EngineManager(service) {
  this.service = service;

  this._engines = {};

  this._altEngineInfo = {};

  // This will be populated by Service on startup.
  this._declined = new Set();
  this._log = Log.repository.getLogger("Sync.EngineManager");
  this._log.manageLevelFromPref("services.sync.log.logger.service.engines");
  // define the default level for all engine logs here (although each engine
  // allows its level to be controlled via a specific, non-default pref)
  Log.repository
    .getLogger(`Sync.Engine`)
    .manageLevelFromPref("services.sync.log.logger.engine");
}

EngineManager.prototype = {
  get(name) {
    // Return an array of engines if we have an array of names
    if (Array.isArray(name)) {
      let engines = [];
      name.forEach(function (name) {
        let engine = this.get(name);
        if (engine) {
          engines.push(engine);
        }
      }, this);
      return engines;
    }

    return this._engines[name]; // Silently returns undefined for unknown names.
  },

  getAll() {
    let engines = [];
    for (let [, engine] of Object.entries(this._engines)) {
      engines.push(engine);
    }
    return engines;
  },

  /**
   * If a user has changed a pref that controls which variant of a sync engine
   * for a given collection we use, unregister the old engine and register the
   * new one.
   *
   * This is called by EngineSynchronizer before every sync.
   */
  async switchAlternatives() {
    for (let [name, info] of Object.entries(this._altEngineInfo)) {
      let prefValue = info.prefValue;
      if (prefValue === info.lastValue) {
        this._log.trace(
          `No change for engine ${name} (${info.pref} is still ${prefValue})`
        );
        continue;
      }
      // Unregister the old engine, register the new one.
      this._log.info(
        `Switching ${name} engine ("${info.pref}" went from ${info.lastValue} => ${prefValue})`
      );
      try {
        await this._removeAndFinalize(name);
      } catch (e) {
        this._log.warn(`Failed to remove previous ${name} engine...`, e);
      }
      let engineType = prefValue ? info.whenTrue : info.whenFalse;
      try {
        // If register throws, we'll try again next sync, but until then there
        // won't be an engine registered for this collection.
        await this.register(engineType);
        info.lastValue = prefValue;
        // Note: engineType.name is using Function.prototype.name.
        this._log.info(`Switched the ${name} engine to use ${engineType.name}`);
      } catch (e) {
        this._log.warn(
          `Switching the ${name} engine to use ${engineType.name} failed (couldn't register)`,
          e
        );
      }
    }
  },

  async registerAlternatives(name, pref, whenTrue, whenFalse) {
    let info = { name, pref, whenTrue, whenFalse };

    XPCOMUtils.defineLazyPreferenceGetter(info, "prefValue", pref, false);

    let chosen = info.prefValue ? info.whenTrue : info.whenFalse;
    info.lastValue = info.prefValue;
    this._altEngineInfo[name] = info;

    await this.register(chosen);
  },

  /**
   * N.B., does not pay attention to the declined list.
   */
  getEnabled() {
    return this.getAll()
      .filter(engine => engine.enabled)
      .sort((a, b) => a.syncPriority - b.syncPriority);
  },

  get enabledEngineNames() {
    return this.getEnabled().map(e => e.name);
  },

  persistDeclined() {
    Svc.PrefBranch.setStringPref(
      "declinedEngines",
      [...this._declined].join(",")
    );
  },

  /**
   * Returns an array.
   */
  getDeclined() {
    return [...this._declined];
  },

  setDeclined(engines) {
    this._declined = new Set(engines);
    this.persistDeclined();
  },

  isDeclined(engineName) {
    return this._declined.has(engineName);
  },

  /**
   * Accepts a Set or an array.
   */
  decline(engines) {
    for (let e of engines) {
      this._declined.add(e);
    }
    this.persistDeclined();
  },

  undecline(engines) {
    for (let e of engines) {
      this._declined.delete(e);
    }
    this.persistDeclined();
  },

  /**
   * Register an Engine to the service. Alternatively, give an array of engine
   * objects to register.
   *
   * @param engineObject
   *        Engine object used to get an instance of the engine
   * @return The engine object if anything failed
   */
  async register(engineObject) {
    if (Array.isArray(engineObject)) {
      for (const e of engineObject) {
        await this.register(e);
      }
      return;
    }

    try {
      let engine = new engineObject(this.service);
      let name = engine.name;
      if (name in this._engines) {
        this._log.error("Engine '" + name + "' is already registered!");
      } else {
        if (engine.initialize) {
          await engine.initialize();
        }
        this._engines[name] = engine;
      }
    } catch (ex) {
      let name = engineObject || "";
      name = name.prototype || "";
      name = name.name || "";

      this._log.error(`Could not initialize engine ${name}`, ex);
    }
  },

  async unregister(val) {
    let name = val;
    if (val instanceof SyncEngine) {
      name = val.name;
    }
    await this._removeAndFinalize(name);
    delete this._altEngineInfo[name];
  },

  // Common code for disabling an engine by name, that doesn't complain if the
  // engine doesn't exist. Doesn't touch the engine's alternative info (if any
  // exists).
  async _removeAndFinalize(name) {
    if (name in this._engines) {
      let engine = this._engines[name];
      delete this._engines[name];
      await engine.finalize();
    }
  },

  async clear() {
    for (let name in this._engines) {
      let engine = this._engines[name];
      delete this._engines[name];
      await engine.finalize();
    }
    this._altEngineInfo = {};
  },
};

export function SyncEngine(name, service) {
  if (!service) {
    throw new Error("SyncEngine must be associated with a Service instance.");
  }

  this.Name = name || "Unnamed";
  this.name = name.toLowerCase();
  this.service = service;

  this._notify = Utils.notify("weave:engine:");
  this._log = Log.repository.getLogger("Sync.Engine." + this.Name);
  this._log.manageLevelFromPref(`services.sync.log.logger.engine.${this.name}`);

  this._modified = this.emptyChangeset();
  this._tracker; // initialize tracker to load previously changed IDs
  this._log.debug("Engine constructed");

  this._toFetchStorage = new JSONFile({
    path: Utils.jsonFilePath("toFetch", this.name),
    dataPostProcessor: json => this._metadataPostProcessor(json),
    beforeSave: () => this._beforeSaveMetadata(),
  });

  this._previousFailedStorage = new JSONFile({
    path: Utils.jsonFilePath("failed", this.name),
    dataPostProcessor: json => this._metadataPostProcessor(json),
    beforeSave: () => this._beforeSaveMetadata(),
  });

  XPCOMUtils.defineLazyPreferenceGetter(
    this,
    "_enabled",
    `services.sync.engine.${this.prefName}`,
    false
  );
  XPCOMUtils.defineLazyPreferenceGetter(
    this,
    "_syncID",
    `services.sync.${this.name}.syncID`,
    ""
  );
  XPCOMUtils.defineLazyPreferenceGetter(
    this,
    "_lastSync",
    `services.sync.${this.name}.lastSync`,
    "0",
    null,
    v => parseFloat(v)
  );
  // Async initializations can be made in the initialize() method.

  this.asyncObserver = Async.asyncObserver(this, this._log);
}

// Enumeration to define approaches to handling bad records.
// Attached to the constructor to allow use as a kind of static enumeration.
SyncEngine.kRecoveryStrategy = {
  ignore: "ignore",
  retry: "retry",
  error: "error",
};

SyncEngine.prototype = {
  _recordObj: CryptoWrapper,
  // _storeObj, and _trackerObj should to be overridden in subclasses
  _storeObj: Store,
  _trackerObj: Tracker,
  version: 1,

  // Local 'constant'.
  // Signal to the engine that processing further records is pointless.
  eEngineAbortApplyIncoming: "error.engine.abort.applyincoming",

  // Should we keep syncing if we find a record that cannot be uploaded (ever)?
  // If this is false, we'll throw, otherwise, we'll ignore the record and
  // continue. This currently can only happen due to the record being larger
  // than the record upload limit.
  allowSkippedRecord: true,

  // Which sortindex to use when retrieving records for this engine.
  _defaultSort: undefined,

  _hasSyncedThisSession: false,

  _metadataPostProcessor(json) {
    if (Array.isArray(json)) {
      // Pre-`JSONFile` storage stored an array, but `JSONFile` defaults to
      // an object, so we wrap the array for consistency.
      json = { ids: json };
    }
    if (!json.ids) {
      json.ids = [];
    }
    // The set serializes the same way as an array, but offers more efficient
    // methods of manipulation.
    json.ids = new SerializableSet(json.ids);
    return json;
  },

  async _beforeSaveMetadata() {
    await ensureDirectory(this._toFetchStorage.path);
    await ensureDirectory(this._previousFailedStorage.path);
  },

  // A relative priority to use when computing an order
  // for engines to be synced. Higher-priority engines
  // (lower numbers) are synced first.
  // It is recommended that a unique value be used for each engine,
  // in order to guarantee a stable sequence.
  syncPriority: 0,

  // How many records to pull in a single sync. This is primarily to avoid very
  // long first syncs against profiles with many history records.
  downloadLimit: null,

  // How many records to pull at one time when specifying IDs. This is to avoid
  // URI length limitations.
  guidFetchBatchSize: DEFAULT_GUID_FETCH_BATCH_SIZE,

  downloadBatchSize: DEFAULT_DOWNLOAD_BATCH_SIZE,

  async initialize() {
    await this._toFetchStorage.load();
    await this._previousFailedStorage.load();
    Services.prefs.addObserver(
      `${PREFS_BRANCH}engine.${this.prefName}`,
      this.asyncObserver,
      true
    );
    this._log.debug("SyncEngine initialized", this.name);
  },

  get prefName() {
    return this.name;
  },

  get enabled() {
    return this._enabled;
  },

  set enabled(val) {
    if (!!val != this._enabled) {
      Svc.PrefBranch.setBoolPref("engine." + this.prefName, !!val);
    }
  },

  get score() {
    return this._tracker.score;
  },

  get _store() {
    let store = new this._storeObj(this.Name, this);
    this.__defineGetter__("_store", () => store);
    return store;
  },

  get _tracker() {
    let tracker = new this._trackerObj(this.Name, this);
    this.__defineGetter__("_tracker", () => tracker);
    return tracker;
  },

  get storageURL() {
    return this.service.storageURL;
  },

  get engineURL() {
    return this.storageURL + this.name;
  },

  get cryptoKeysURL() {
    return this.storageURL + "crypto/keys";
  },

  get metaURL() {
    return this.storageURL + "meta/global";
  },

  startTracking() {
    this._tracker.start();
  },

  // Returns a promise
  stopTracking() {
    return this._tracker.stop();
  },

  // Listens for engine enabled state changes, and updates the tracker's state.
  // This is an async observer because the tracker waits on all its async
  // observers to finish when it's stopped.
  async observe(subject, topic, data) {
    if (
      topic == "nsPref:changed" &&
      data == `services.sync.engine.${this.prefName}`
    ) {
      await this._tracker.onEngineEnabledChanged(this._enabled);
    }
  },

  async sync() {
    if (!this.enabled) {
      return false;
    }

    if (!this._sync) {
      throw new Error("engine does not implement _sync method");
    }

    return this._notify("sync", this.name, this._sync)();
  },

  // Override this method to return a new changeset type.
  emptyChangeset() {
    return new Changeset();
  },

  /**
   * Returns the local sync ID for this engine, or `""` if the engine hasn't
   * synced for the first time. This is exposed for tests.
   *
   * @return the current sync ID.
   */
  async getSyncID() {
    return this._syncID;
  },

  /**
   * Ensures that the local sync ID for the engine matches the sync ID for the
   * collection on the server. A mismatch indicates that another client wiped
   * the collection; we're syncing after a node reassignment, and another
   * client synced before us; or the store was replaced since the last sync.
   * In case of a mismatch, we need to reset all local Sync state and start
   * over as a first sync.
   *
   * In most cases, this method should return the new sync ID as-is. However, an
   * engine may ignore the given ID and assign a different one, if it determines
   * that the sync ID on the server is out of date. The bookmarks engine uses
   * this to wipe the server and other clients on the first sync after the user
   * restores from a backup.
   *
   * @param  newSyncID
   *         The new sync ID for the collection from `meta/global`.
   * @return The assigned sync ID. If this doesn't match `newSyncID`, we'll
   *         replace the sync ID in `meta/global` with the assigned ID.
   */
  async ensureCurrentSyncID(newSyncID) {
    let existingSyncID = this._syncID;
    if (existingSyncID == newSyncID) {
      return existingSyncID;
    }
    this._log.debug(
      `Engine syncIDs differ (old="${existingSyncID}", new="${newSyncID}") - resetting the engine`
    );
    await this.resetClient();
    Svc.PrefBranch.setStringPref(this.name + ".syncID", newSyncID);
    Svc.PrefBranch.setStringPref(this.name + ".lastSync", "0");
    return newSyncID;
  },

  /**
   * Resets the local sync ID for the engine, wipes the server, and resets all
   * local Sync state to start over as a first sync.
   *
   * @return the new sync ID.
   */
  async resetSyncID() {
    let newSyncID = await this.resetLocalSyncID();
    await this.wipeServer();
    return newSyncID;
  },

  /**
   * Resets the local sync ID for the engine, signaling that we're starting over
   * as a first sync.
   *
   * @return the new sync ID.
   */
  async resetLocalSyncID() {
    return this.ensureCurrentSyncID(Utils.makeGUID());
  },

  /**
   * Allows overriding scheduler logic -- added to help reduce kinto server
   * getting hammered because our scheduler never got tuned for it.
   *
   * Note: Overriding engines must take resyncs into account -- score will not
   * be cleared.
   */
  shouldSkipSync() {
    return false;
  },

  /*
   * lastSync is a timestamp in server time.
   */
  async getLastSync() {
    return this._lastSync;
  },
  async setLastSync(lastSync) {
    // Store the value as a string to keep floating point precision
    Svc.PrefBranch.setStringPref(this.name + ".lastSync", lastSync.toString());
  },
  async resetLastSync() {
    this._log.debug("Resetting " + this.name + " last sync time");
    await this.setLastSync(0);
  },

  get hasSyncedThisSession() {
    return this._hasSyncedThisSession;
  },

  set hasSyncedThisSession(hasSynced) {
    this._hasSyncedThisSession = hasSynced;
  },

  get toFetch() {
    this._toFetchStorage.ensureDataReady();
    return this._toFetchStorage.data.ids;
  },

  set toFetch(ids) {
    if (ids.constructor.name != "SerializableSet") {
      throw new Error(
        "Bug: Attempted to set toFetch to something that isn't a SerializableSet"
      );
    }
    this._toFetchStorage.data = { ids };
    this._toFetchStorage.saveSoon();
  },

  get previousFailed() {
    this._previousFailedStorage.ensureDataReady();
    return this._previousFailedStorage.data.ids;
  },

  set previousFailed(ids) {
    if (ids.constructor.name != "SerializableSet") {
      throw new Error(
        "Bug: Attempted to set previousFailed to something that isn't a SerializableSet"
      );
    }
    this._previousFailedStorage.data = { ids };
    this._previousFailedStorage.saveSoon();
  },

  /*
   * Returns a changeset for this sync. Engine implementations can override this
   * method to bypass the tracker for certain or all changed items.
   */
  async getChangedIDs() {
    return this._tracker.getChangedIDs();
  },

  // Create a new record using the store and add in metadata.
  async _createRecord(id) {
    let record = await this._store.createRecord(id, this.name);
    record.id = id;
    record.collection = this.name;
    return record;
  },

  // Creates a tombstone Sync record with additional metadata.
  _createTombstone(id) {
    let tombstone = new this._recordObj(this.name, id);
    tombstone.id = id;
    tombstone.collection = this.name;
    tombstone.deleted = true;
    return tombstone;
  },

  // Any setup that needs to happen at the beginning of each sync.
  async _syncStartup() {
    // Determine if we need to wipe on outdated versions
    let metaGlobal = await this.service.recordManager.get(this.metaURL);
    let engines = metaGlobal.payload.engines || {};
    let engineData = engines[this.name] || {};

    // Assume missing versions are 0 and wipe the server
    if ((engineData.version || 0) < this.version) {
      this._log.debug("Old engine data: " + [engineData.version, this.version]);

      // Clear the server and reupload everything on bad version or missing
      // meta. Note that we don't regenerate per-collection keys here.
      let newSyncID = await this.resetSyncID();

      // Set the newer version and newly generated syncID
      engineData.version = this.version;
      engineData.syncID = newSyncID;

      // Put the new data back into meta/global and mark for upload
      engines[this.name] = engineData;
      metaGlobal.payload.engines = engines;
      metaGlobal.changed = true;
    } else if (engineData.version > this.version) {
      // Don't sync this engine if the server has newer data

      let error = new Error("New data: " + [engineData.version, this.version]);
      error.failureCode = VERSION_OUT_OF_DATE;
      throw error;
    } else {
      // Changes to syncID mean we'll need to upload everything
      let assignedSyncID = await this.ensureCurrentSyncID(engineData.syncID);
      if (assignedSyncID != engineData.syncID) {
        engineData.syncID = assignedSyncID;
        metaGlobal.changed = true;
      }
    }

    // Save objects that need to be uploaded in this._modified. As we
    // successfully upload objects we remove them from this._modified. If an
    // error occurs or any objects fail to upload, they will remain in
    // this._modified. At the end of a sync, or after an error, we add all
    // objects remaining in this._modified to the tracker.
    let initialChanges = await this.pullChanges();
    this._modified.replace(initialChanges);
    // Clear the tracker now. If the sync fails we'll add the ones we failed
    // to upload back.
    this._tracker.clearChangedIDs();
    this._tracker.resetScore();

    // Keep track of what to delete at the end of sync
    this._delete = {};
  },

  async pullChanges() {
    let lastSync = await this.getLastSync();
    if (lastSync) {
      return this.pullNewChanges();
    }
    this._log.debug("First sync, uploading all items");
    return this.pullAllChanges();
  },

  /**
   * A tiny abstraction to make it easier to test incoming record
   * application.
   */
  itemSource() {
    return new Collection(this.engineURL, this._recordObj, this.service);
  },

  /**
   * Download and apply remote records changed since the last sync. This
   * happens in three stages.
   *
   * In the first stage, we fetch full records for all changed items, newest
   * first, up to the download limit. The limit lets us make progress for large
   * collections, where the sync is likely to be interrupted before we
   * can fetch everything.
   *
   * In the second stage, we fetch the IDs of any remaining records changed
   * since the last sync, add them to our backlog, and fast-forward our last
   * sync time.
   *
   * In the third stage, we fetch and apply records for all backlogged IDs,
   * as well as any records that failed to apply during the last sync. We
   * request records for the IDs in chunks, to avoid exceeding URL length
   * limits, then remove successfully applied records from the backlog, and
   * record IDs of any records that failed to apply to retry on the next sync.
   */
  async _processIncoming() {
    this._log.trace("Downloading & applying server changes");

    let newitems = this.itemSource();
    let lastSync = await this.getLastSync();

    newitems.newer = lastSync;
    newitems.full = true;

    let downloadLimit = Infinity;
    if (this.downloadLimit) {
      // Fetch new records up to the download limit. Currently, only the history
      // engine sets a limit, since the history collection has the highest volume
      // of changed records between syncs. The other engines fetch all records
      // changed since the last sync.
      if (this._defaultSort) {
        // A download limit with a sort order doesn't make sense: we won't know
        // which records to backfill.
        throw new Error("Can't specify download limit with default sort order");
      }
      newitems.sort = "newest";
      downloadLimit = newitems.limit = this.downloadLimit;
    } else if (this._defaultSort) {
      // The bookmarks engine fetches records by sort index; other engines leave
      // the order unspecified. We can remove `_defaultSort` entirely after bug
      // 1305563: the sort index won't matter because we'll buffer all bookmarks
      // before applying.
      newitems.sort = this._defaultSort;
    }

    // applied    => number of items that should be applied.
    // failed     => number of items that failed in this sync.
    // newFailed  => number of items that failed for the first time in this sync.
    // reconciled => number of items that were reconciled.
    // failedReasons => {name, count} of reasons a record failed
    let countTelemetry = new SyncedRecordsTelemetry();
    let count = countTelemetry.incomingCounts;
    let recordsToApply = [];
    let failedInCurrentSync = new SerializableSet();

    let oldestModified = this.lastModified;
    let downloadedIDs = new Set();

    // Stage 1: Fetch new records from the server, up to the download limit.
    if (this.lastModified == null || this.lastModified > lastSync) {
      let { response, records } = await newitems.getBatched(
        this.downloadBatchSize
      );
      if (!response.success) {
        response.failureCode = ENGINE_DOWNLOAD_FAIL;
        throw response;
      }

      await Async.yieldingForEach(records, async record => {
        downloadedIDs.add(record.id);

        if (record.modified < oldestModified) {
          oldestModified = record.modified;
        }

        let { shouldApply, error } = await this._maybeReconcile(record);
        if (error) {
          failedInCurrentSync.add(record.id);
          count.failed++;
          countTelemetry.addIncomingFailedReason(error.message);
          return;
        }
        if (!shouldApply) {
          count.reconciled++;
          return;
        }
        recordsToApply.push(record);
      });

      let failedToApply = await this._applyRecords(
        recordsToApply,
        countTelemetry
      );
      Utils.setAddAll(failedInCurrentSync, failedToApply);

      // `applied` is a bit of a misnomer: it counts records that *should* be
      // applied, so it also includes records that we tried to apply and failed.
      // `recordsToApply.length - failedToApply.length` is the number of records
      // that we *successfully* applied.
      count.failed += failedToApply.length;
      count.applied += recordsToApply.length;
    }

    // Stage 2: If we reached our download limit, we might still have records
    // on the server that changed since the last sync. Fetch the IDs for the
    // remaining records, and add them to the backlog. Note that this stage
    // only runs for engines that set a download limit.
    if (downloadedIDs.size == downloadLimit) {
      let guidColl = this.itemSource();

      guidColl.newer = lastSync;
      guidColl.older = oldestModified;
      guidColl.sort = "oldest";

      let guids = await guidColl.get();
      if (!guids.success) {
        throw guids;
      }

      // Filtering out already downloaded IDs here isn't necessary. We only do
      // that in case the Sync server doesn't support `older` (bug 1316110).
      let remainingIDs = guids.obj.filter(id => !downloadedIDs.has(id));
      if (remainingIDs.length) {
        this.toFetch = Utils.setAddAll(this.toFetch, remainingIDs);
      }
    }

    // Fast-foward the lastSync timestamp since we have backlogged the
    // remaining items.
    if (lastSync < this.lastModified) {
      lastSync = this.lastModified;
      await this.setLastSync(lastSync);
    }

    // Stage 3: Backfill records from the backlog, and those that failed to
    // decrypt or apply during the last sync. We only backfill up to the
    // download limit, to prevent a large backlog for one engine from blocking
    // the others. We'll keep processing the backlog on subsequent engine syncs.
    let failedInPreviousSync = this.previousFailed;
    let idsToBackfill = Array.from(
      Utils.setAddAll(
        Utils.subsetOfSize(this.toFetch, downloadLimit),
        failedInPreviousSync
      )
    );

    // Note that we intentionally overwrite the previously failed list here.
    // Records that fail to decrypt or apply in two consecutive syncs are likely
    // corrupt; we remove them from the list because retrying and failing on
    // every subsequent sync just adds noise.
    this.previousFailed = failedInCurrentSync;

    let backfilledItems = this.itemSource();

    backfilledItems.sort = "newest";
    backfilledItems.full = true;

    // `getBatched` includes the list of IDs as a query parameter, so we need to fetch
    // records in chunks to avoid exceeding URI length limits.
    if (this.guidFetchBatchSize) {
      for (let ids of lazy.PlacesUtils.chunkArray(
        idsToBackfill,
        this.guidFetchBatchSize
      )) {
        backfilledItems.ids = ids;

        let { response, records } = await backfilledItems.getBatched(
          this.downloadBatchSize
        );
        if (!response.success) {
          response.failureCode = ENGINE_DOWNLOAD_FAIL;
          throw response;
        }

        let backfilledRecordsToApply = [];
        let failedInBackfill = [];

        await Async.yieldingForEach(records, async record => {
          let { shouldApply, error } = await this._maybeReconcile(record);
          if (error) {
            failedInBackfill.push(record.id);
            count.failed++;
            countTelemetry.addIncomingFailedReason(error.message);
            return;
          }
          if (!shouldApply) {
            count.reconciled++;
            return;
          }
          backfilledRecordsToApply.push(record);
        });

        let failedToApply = await this._applyRecords(
          backfilledRecordsToApply,
          countTelemetry
        );
        failedInBackfill.push(...failedToApply);

        count.failed += failedToApply.length;
        count.applied += backfilledRecordsToApply.length;

        this.toFetch = Utils.setDeleteAll(this.toFetch, ids);
        this.previousFailed = Utils.setAddAll(
          this.previousFailed,
          failedInBackfill
        );

        if (lastSync < this.lastModified) {
          lastSync = this.lastModified;
          await this.setLastSync(lastSync);
        }
      }
    }

    count.newFailed = 0;
    for (let item of this.previousFailed) {
      // Anything that failed in the current sync that also failed in
      // the previous sync means there is likely something wrong with
      // the record, we remove it from trying again to prevent
      // infinitely syncing corrupted records
      if (failedInPreviousSync.has(item)) {
        this.previousFailed.delete(item);
      } else {
        // otherwise it's a new failed and we count it as so
        ++count.newFailed;
      }
    }

    count.succeeded = Math.max(0, count.applied - count.failed);
    this._log.info(
      [
        "Records:",
        count.applied,
        "applied,",
        count.succeeded,
        "successfully,",
        count.failed,
        "failed to apply,",
        count.newFailed,
        "newly failed to apply,",
        count.reconciled,
        "reconciled.",
      ].join(" ")
    );
    Observers.notify("weave:engine:sync:applied", count, this.name);
  },

  async _maybeReconcile(item) {
    let key = this.service.collectionKeys.keyForCollection(this.name);

    // Grab a later last modified if possible
    if (this.lastModified == null || item.modified > this.lastModified) {
      this.lastModified = item.modified;
    }

    try {
      try {
        await item.decrypt(key);
      } catch (ex) {
        if (!Utils.isHMACMismatch(ex)) {
          throw ex;
        }
        let strategy = await this.handleHMACMismatch(item, true);
        if (strategy == SyncEngine.kRecoveryStrategy.retry) {
          // You only get one retry.
          try {
            // Try decrypting again, typically because we've got new keys.
            this._log.info("Trying decrypt again...");
            key = this.service.collectionKeys.keyForCollection(this.name);
            await item.decrypt(key);
            strategy = null;
          } catch (ex) {
            if (!Utils.isHMACMismatch(ex)) {
              throw ex;
            }
            strategy = await this.handleHMACMismatch(item, false);
          }
        }

        switch (strategy) {
          case null:
            // Retry succeeded! No further handling.
            break;
          case SyncEngine.kRecoveryStrategy.retry:
            this._log.debug("Ignoring second retry suggestion.");
          // Fall through to error case.
          case SyncEngine.kRecoveryStrategy.error:
            this._log.warn("Error decrypting record", ex);
            return { shouldApply: false, error: ex };
          case SyncEngine.kRecoveryStrategy.ignore:
            this._log.debug(
              "Ignoring record " + item.id + " with bad HMAC: already handled."
            );
            return { shouldApply: false, error: null };
        }
      }
    } catch (ex) {
      if (Async.isShutdownException(ex)) {
        throw ex;
      }
      this._log.warn("Error decrypting record", ex);
      return { shouldApply: false, error: ex };
    }

    if (this._shouldDeleteRemotely(item)) {
      this._log.trace("Deleting item from server without applying", item);
      await this._deleteId(item.id);
      return { shouldApply: false, error: null };
    }

    let shouldApply;
    try {
      shouldApply = await this._reconcile(item);
    } catch (ex) {
      if (ex.code == SyncEngine.prototype.eEngineAbortApplyIncoming) {
        this._log.warn("Reconciliation failed: aborting incoming processing.");
        throw ex.cause;
      } else if (!Async.isShutdownException(ex)) {
        this._log.warn("Failed to reconcile incoming record " + item.id, ex);
        return { shouldApply: false, error: ex };
      } else {
        throw ex;
      }
    }

    if (!shouldApply) {
      this._log.trace("Skipping reconciled incoming item " + item.id);
    }

    return { shouldApply, error: null };
  },

  async _applyRecords(records, countTelemetry) {
    this._tracker.ignoreAll = true;
    try {
      let failedIDs = await this._store.applyIncomingBatch(
        records,
        countTelemetry
      );
      return failedIDs;
    } catch (ex) {
      // Catch any error that escapes from applyIncomingBatch. At present
      // those will all be abort events.
      this._log.warn("Got exception, aborting processIncoming", ex);
      throw ex;
    } finally {
      this._tracker.ignoreAll = false;
    }
  },

  // Indicates whether an incoming item should be deleted from the server at
  // the end of the sync. Engines can override this method to clean up records
  // that shouldn't be on the server.
  _shouldDeleteRemotely() {
    return false;
  },

  /**
   * Find a GUID of an item that is a duplicate of the incoming item but happens
   * to have a different GUID
   *
   * @return GUID of the similar item; falsy otherwise
   */
  async _findDupe() {
    // By default, assume there's no dupe items for the engine
  },

  /**
   * Called before a remote record is discarded due to failed reconciliation.
   * Used by bookmark sync to merge folder child orders.
   */
  beforeRecordDiscard() {},

  // Called when the server has a record marked as deleted, but locally we've
  // changed it more recently than the deletion. If we return false, the
  // record will be deleted locally. If we return true, we'll reupload the
  // record to the server -- any extra work that's needed as part of this
  // process should be done at this point (such as mark the record's parent
  // for reuploading in the case of bookmarks).
  async _shouldReviveRemotelyDeletedRecord() {
    return true;
  },

  async _deleteId(id) {
    await this._tracker.removeChangedID(id);
    this._noteDeletedId(id);
  },

  // Marks an ID for deletion at the end of the sync.
  _noteDeletedId(id) {
    if (this._delete.ids == null) {
      this._delete.ids = [id];
    } else {
      this._delete.ids.push(id);
    }
  },

  async _switchItemToDupe(localDupeGUID, incomingItem) {
    // The local, duplicate ID is always deleted on the server.
    await this._deleteId(localDupeGUID);

    // We unconditionally change the item's ID in case the engine knows of
    // an item but doesn't expose it through itemExists. If the API
    // contract were stronger, this could be changed.
    this._log.debug(
      "Switching local ID to incoming: " +
        localDupeGUID +
        " -> " +
        incomingItem.id
    );
    return this._store.changeItemID(localDupeGUID, incomingItem.id);
  },

  /**
   * Reconcile incoming record with local state.
   *
   * This function essentially determines whether to apply an incoming record.
   *
   * @param  item
   *         Record from server to be tested for application.
   * @return boolean
   *         Truthy if incoming record should be applied. False if not.
   */
  async _reconcile(item) {
    if (this._log.level <= Log.Level.Trace) {
      this._log.trace("Incoming: " + item);
    }

    // We start reconciling by collecting a bunch of state. We do this here
    // because some state may change during the course of this function and we
    // need to operate on the original values.
    let existsLocally = await this._store.itemExists(item.id);
    let locallyModified = this._modified.has(item.id);

    // TODO Handle clock drift better. Tracked in bug 721181.
    let remoteAge = Resource.serverTime - item.modified;
    let localAge = locallyModified
      ? Date.now() / 1000 - this._modified.getModifiedTimestamp(item.id)
      : null;
    let remoteIsNewer = remoteAge < localAge;

    this._log.trace(
      "Reconciling " +
        item.id +
        ". exists=" +
        existsLocally +
        "; modified=" +
        locallyModified +
        "; local age=" +
        localAge +
        "; incoming age=" +
        remoteAge
    );

    // We handle deletions first so subsequent logic doesn't have to check
    // deleted flags.
    if (item.deleted) {
      // If the item doesn't exist locally, there is nothing for us to do. We
      // can't check for duplicates because the incoming record has no data
      // which can be used for duplicate detection.
      if (!existsLocally) {
        this._log.trace(
          "Ignoring incoming item because it was deleted and " +
            "the item does not exist locally."
        );
        return false;
      }

      // We decide whether to process the deletion by comparing the record
      // ages. If the item is not modified locally, the remote side wins and
      // the deletion is processed. If it is modified locally, we take the
      // newer record.
      if (!locallyModified) {
        this._log.trace(
          "Applying incoming delete because the local item " +
            "exists and isn't modified."
        );
        return true;
      }
      this._log.trace("Incoming record is deleted but we had local changes.");

      if (remoteIsNewer) {
        this._log.trace("Remote record is newer -- deleting local record.");
        return true;
      }
      // If the local record is newer, we defer to individual engines for
      // how to handle this. By default, we revive the record.
      let willRevive = await this._shouldReviveRemotelyDeletedRecord(item);
      this._log.trace("Local record is newer -- reviving? " + willRevive);

      return !willRevive;
    }

    // At this point the incoming record is not for a deletion and must have
    // data. If the incoming record does not exist locally, we check for a local
    // duplicate existing under a different ID. The default implementation of
    // _findDupe() is empty, so engines have to opt in to this functionality.
    //
    // If we find a duplicate, we change the local ID to the incoming ID and we
    // refresh the metadata collected above. See bug 710448 for the history
    // of this logic.
    if (!existsLocally) {
      let localDupeGUID = await this._findDupe(item);
      if (localDupeGUID) {
        this._log.trace(
          "Local item " +
            localDupeGUID +
            " is a duplicate for " +
            "incoming item " +
            item.id
        );

        // The current API contract does not mandate that the ID returned by
        // _findDupe() actually exists. Therefore, we have to perform this
        // check.
        existsLocally = await this._store.itemExists(localDupeGUID);

        // If the local item was modified, we carry its metadata forward so
        // appropriate reconciling can be performed.
        if (this._modified.has(localDupeGUID)) {
          locallyModified = true;
          localAge =
            this._tracker._now() -
            this._modified.getModifiedTimestamp(localDupeGUID);
          remoteIsNewer = remoteAge < localAge;

          this._modified.changeID(localDupeGUID, item.id);
        } else {
          locallyModified = false;
          localAge = null;
        }

        // Tell the engine to do whatever it needs to switch the items.
        await this._switchItemToDupe(localDupeGUID, item);

        this._log.debug(
          "Local item after duplication: age=" +
            localAge +
            "; modified=" +
            locallyModified +
            "; exists=" +
            existsLocally
        );
      } else {
        this._log.trace("No duplicate found for incoming item: " + item.id);
      }
    }

    // At this point we've performed duplicate detection. But, nothing here
    // should depend on duplicate detection as the above should have updated
    // state seamlessly.

    if (!existsLocally) {
      // If the item doesn't exist locally and we have no local modifications
      // to the item (implying that it was not deleted), always apply the remote
      // item.
      if (!locallyModified) {
        this._log.trace(
          "Applying incoming because local item does not exist " +
            "and was not deleted."
        );
        return true;
      }

      // If the item was modified locally but isn't present, it must have
      // been deleted. If the incoming record is younger, we restore from
      // that record.
      if (remoteIsNewer) {
        this._log.trace(
          "Applying incoming because local item was deleted " +
            "before the incoming item was changed."
        );
        this._modified.delete(item.id);
        return true;
      }

      this._log.trace(
        "Ignoring incoming item because the local item's " +
          "deletion is newer."
      );
      return false;
    }

    // If the remote and local records are the same, there is nothing to be
    // done, so we don't do anything. In the ideal world, this logic wouldn't
    // be here and the engine would take a record and apply it. The reason we
    // want to defer this logic is because it would avoid a redundant and
    // possibly expensive dip into the storage layer to query item state.
    // This should get addressed in the async rewrite, so we ignore it for now.
    let localRecord = await this._createRecord(item.id);
    let recordsEqual = Utils.deepEquals(item.cleartext, localRecord.cleartext);

    // If the records are the same, we don't need to do anything. This does
    // potentially throw away a local modification time. But, if the records
    // are the same, does it matter?
    if (recordsEqual) {
      this._log.trace(
        "Ignoring incoming item because the local item is identical."
      );

      this._modified.delete(item.id);
      return false;
    }

    // At this point the records are different.

    // If we have no local modifications, always take the server record.
    if (!locallyModified) {
      this._log.trace("Applying incoming record because no local conflicts.");
      return true;
    }

    // At this point, records are different and the local record is modified.
    // We resolve conflicts by record age, where the newest one wins. This does
    // result in data loss and should be handled by giving the engine an
    // opportunity to merge the records. Bug 720592 tracks this feature.
    this._log.warn(
      "DATA LOSS: Both local and remote changes to record: " + item.id
    );
    if (!remoteIsNewer) {
      this.beforeRecordDiscard(localRecord, item, remoteIsNewer);
    }
    return remoteIsNewer;
  },

  // Upload outgoing records.
  async _uploadOutgoing() {
    this._log.trace("Uploading local changes to server.");

    // collection we'll upload
    let up = new Collection(this.engineURL, null, this.service);
    let modifiedIDs = new Set(this._modified.ids());
    let countTelemetry = new SyncedRecordsTelemetry();
    let counts = countTelemetry.outgoingCounts;
    this._log.info(`Uploading ${modifiedIDs.size} outgoing records`);
    if (modifiedIDs.size) {
      counts.sent = modifiedIDs.size;

      let failed = [];
      let successful = [];
      let lastSync = await this.getLastSync();
      let handleResponse = async (postQueue, resp, batchOngoing) => {
        // Note: We don't want to update this.lastSync, or this._modified until
        // the batch is complete, however we want to remember success/failure
        // indicators for when that happens.
        if (!resp.success) {
          this._log.debug(`Uploading records failed: ${resp.status}`);
          resp.failureCode =
            resp.status == 412 ? ENGINE_BATCH_INTERRUPTED : ENGINE_UPLOAD_FAIL;
          throw resp;
        }

        // Update server timestamp from the upload.
        failed = failed.concat(Object.keys(resp.obj.failed));
        successful = successful.concat(resp.obj.success);

        if (batchOngoing) {
          // Nothing to do yet
          return;
        }

        if (failed.length && this._log.level <= Log.Level.Debug) {
          this._log.debug(
            "Records that will be uploaded again because " +
              "the server couldn't store them: " +
              failed.join(", ")
          );
        }

        counts.failed += failed.length;
        Object.values(failed).forEach(message => {
          countTelemetry.addOutgoingFailedReason(message);
        });

        for (let id of successful) {
          this._modified.delete(id);
        }

        await this._onRecordsWritten(
          successful,
          failed,
          postQueue.lastModified
        );

        // Advance lastSync since we've finished the batch.
        if (postQueue.lastModified > lastSync) {
          lastSync = postQueue.lastModified;
          await this.setLastSync(lastSync);
        }

        // clear for next batch
        failed.length = 0;
        successful.length = 0;
      };

      let postQueue = up.newPostQueue(this._log, lastSync, handleResponse);

      for (let id of modifiedIDs) {
        let out;
        let ok = false;
        try {
          out = await this._createRecord(id);
          if (this._log.level <= Log.Level.Trace) {
            this._log.trace("Outgoing: " + out);
          }
          await out.encrypt(
            this.service.collectionKeys.keyForCollection(this.name)
          );
          ok = true;
        } catch (ex) {
          this._log.warn("Error creating record", ex);
          ++counts.failed;
          countTelemetry.addOutgoingFailedReason(ex.message);
          if (Async.isShutdownException(ex) || !this.allowSkippedRecord) {
            if (!this.allowSkippedRecord) {
              // Don't bother for shutdown errors
              Observers.notify("weave:engine:sync:uploaded", counts, this.name);
            }
            throw ex;
          }
        }
        if (ok) {
          let { enqueued, error } = await postQueue.enqueue(out);
          if (!enqueued) {
            ++counts.failed;
            countTelemetry.addOutgoingFailedReason(error.message);
            if (!this.allowSkippedRecord) {
              Observers.notify("weave:engine:sync:uploaded", counts, this.name);
              this._log.warn(
                `Failed to enqueue record "${id}" (aborting)`,
                error
              );
              throw error;
            }
            this._modified.delete(id);
            this._log.warn(
              `Failed to enqueue record "${id}" (skipping)`,
              error
            );
          }
        }
        await Async.promiseYield();
      }
      await postQueue.flush(true);
    }

    if (counts.sent || counts.failed) {
      Observers.notify("weave:engine:sync:uploaded", counts, this.name);
    }
  },

  async _onRecordsWritten() {
    // Implement this method to take specific actions against successfully
    // uploaded records and failed records.
  },

  // Any cleanup necessary.
  // Save the current snapshot so as to calculate changes at next sync
  async _syncFinish() {
    this._log.trace("Finishing up sync");

    let doDelete = async (key, val) => {
      let coll = new Collection(this.engineURL, this._recordObj, this.service);
      coll[key] = val;
      await coll.delete();
    };

    for (let [key, val] of Object.entries(this._delete)) {
      // Remove the key for future uses
      delete this._delete[key];

      this._log.trace("doing post-sync deletions", { key, val });
      // Send a simple delete for the property
      if (key != "ids" || val.length <= 100) {
        await doDelete(key, val);
      } else {
        // For many ids, split into chunks of at most 100
        while (val.length) {
          await doDelete(key, val.slice(0, 100));
          val = val.slice(100);
        }
      }
    }
    this.hasSyncedThisSession = true;
    await this._tracker.asyncObserver.promiseObserversComplete();
  },

  async _syncCleanup() {
    try {
      // Mark failed WBOs as changed again so they are reuploaded next time.
      await this.trackRemainingChanges();
    } finally {
      this._modified.clear();
    }
  },

  async _sync() {
    try {
      Async.checkAppReady();
      await this._syncStartup();
      Async.checkAppReady();
      Observers.notify("weave:engine:sync:status", "process-incoming");
      await this._processIncoming();
      Async.checkAppReady();
      Observers.notify("weave:engine:sync:status", "upload-outgoing");
      try {
        await this._uploadOutgoing();
        Async.checkAppReady();
        await this._syncFinish();
      } catch (ex) {
        if (!ex.status || ex.status != 412) {
          throw ex;
        }
        // a 412 posting just means another client raced - but we don't want
        // to treat that as a sync error - the next sync is almost certain
        // to work.
        this._log.warn("412 error during sync - will retry.");
      }
    } finally {
      await this._syncCleanup();
    }
  },

  async canDecrypt() {
    // Report failure even if there's nothing to decrypt
    let canDecrypt = false;

    // Fetch the most recently uploaded record and try to decrypt it
    let test = new Collection(this.engineURL, this._recordObj, this.service);
    test.limit = 1;
    test.sort = "newest";
    test.full = true;

    let key = this.service.collectionKeys.keyForCollection(this.name);

    // Any failure fetching/decrypting will just result in false
    try {
      this._log.trace("Trying to decrypt a record from the server..");
      let json = (await test.get()).obj[0];
      let record = new this._recordObj();
      record.deserialize(json);
      await record.decrypt(key);
      canDecrypt = true;
    } catch (ex) {
      if (Async.isShutdownException(ex)) {
        throw ex;
      }
      this._log.debug("Failed test decrypt", ex);
    }

    return canDecrypt;
  },

  /**
   * Deletes the collection for this engine on the server, and removes all local
   * Sync metadata for this engine. This does *not* remove any existing data on
   * other clients. This is called when we reset the sync ID.
   */
  async wipeServer() {
    await this._deleteServerCollection();
    await this._resetClient();
  },

  /**
   * Deletes the collection for this engine on the server, without removing
   * any local Sync metadata or user data. Deleting the collection will not
   * remove any user data on other clients, but will force other clients to
   * start over as a first sync.
   */
  async _deleteServerCollection() {
    let response = await this.service.resource(this.engineURL).delete();
    if (response.status != 200 && response.status != 404) {
      throw response;
    }
  },

  async removeClientData() {
    // Implement this method in engines that store client specific data
    // on the server.
  },

  /*
   * Decide on (and partially effect) an error-handling strategy.
   *
   * Asks the Service to respond to an HMAC error, which might result in keys
   * being downloaded. That call returns true if an action which might allow a
   * retry to occur.
   *
   * If `mayRetry` is truthy, and the Service suggests a retry,
   * handleHMACMismatch returns kRecoveryStrategy.retry. Otherwise, it returns
   * kRecoveryStrategy.error.
   *
   * Subclasses of SyncEngine can override this method to allow for different
   * behavior -- e.g., to delete and ignore erroneous entries.
   *
   * All return values will be part of the kRecoveryStrategy enumeration.
   */
  async handleHMACMismatch(item, mayRetry) {
    // By default we either try again, or bail out noisily.
    return (await this.service.handleHMACEvent()) && mayRetry
      ? SyncEngine.kRecoveryStrategy.retry
      : SyncEngine.kRecoveryStrategy.error;
  },

  /**
   * Returns a changeset containing all items in the store. The default
   * implementation returns a changeset with timestamps from long ago, to
   * ensure we always use the remote version if one exists.
   *
   * This function is only called for the first sync. Subsequent syncs call
   * `pullNewChanges`.
   *
   * @return A `Changeset` object.
   */
  async pullAllChanges() {
    let changes = {};
    let ids = await this._store.getAllIDs();
    for (let id in ids) {
      changes[id] = 0;
    }
    return changes;
  },

  /*
   * Returns a changeset containing entries for all currently tracked items.
   * The default implementation returns a changeset with timestamps indicating
   * when the item was added to the tracker.
   *
   * @return A `Changeset` object.
   */
  async pullNewChanges() {
    await this._tracker.asyncObserver.promiseObserversComplete();
    return this.getChangedIDs();
  },

  /**
   * Adds all remaining changeset entries back to the tracker, typically for
   * items that failed to upload. This method is called at the end of each sync.
   *
   */
  async trackRemainingChanges() {
    for (let [id, change] of this._modified.entries()) {
      await this._tracker.addChangedID(id, change);
    }
  },

  /**
   * Removes all local Sync metadata for this engine, but keeps all existing
   * local user data.
   */
  async resetClient() {
    return this._notify("reset-client", this.name, this._resetClient)();
  },

  async _resetClient() {
    await this.resetLastSync();
    this.hasSyncedThisSession = false;
    this.previousFailed = new SerializableSet();
    this.toFetch = new SerializableSet();
  },

  /**
   * Removes all local Sync metadata and user data for this engine.
   */
  async wipeClient() {
    return this._notify("wipe-client", this.name, this._wipeClient)();
  },

  async _wipeClient() {
    await this.resetClient();
    this._log.debug("Deleting all local data");
    this._tracker.ignoreAll = true;
    await this._store.wipe();
    this._tracker.ignoreAll = false;
    this._tracker.clearChangedIDs();
  },

  /**
   * If one exists, initialize and return a validator for this engine (which
   * must have a `validate(engine)` method that returns a promise to an object
   * with a getSummary method). Otherwise return null.
   */
  getValidator() {
    return null;
  },

  async finalize() {
    Services.prefs.removeObserver(
      `${PREFS_BRANCH}engine.${this.prefName}`,
      this.asyncObserver
    );
    await this.asyncObserver.promiseObserversComplete();
    await this._tracker.finalize();
    await this._toFetchStorage.finalize();
    await this._previousFailedStorage.finalize();
  },

  // Returns a new watchdog. Exposed for tests.
  _newWatchdog() {
    return Async.watchdog();
  },
};

/**
 * A changeset is created for each sync in `Engine::get{Changed, All}IDs`,
 * and stores opaque change data for tracked IDs. The default implementation
 * only records timestamps, though engines can extend this to store additional
 * data for each entry.
 */
export class Changeset {
  // Creates an empty changeset.
  constructor() {
    this.changes = {};
  }

  // Returns the last modified time, in seconds, for an entry in the changeset.
  // `id` is guaranteed to be in the set.
  getModifiedTimestamp(id) {
    return this.changes[id];
  }

  // Adds a change for a tracked ID to the changeset.
  set(id, change) {
    this.changes[id] = change;
  }

  // Adds multiple entries to the changeset, preserving existing entries.
  insert(changes) {
    Object.assign(this.changes, changes);
  }

  // Overwrites the existing set of tracked changes with new entries.
  replace(changes) {
    this.changes = changes;
  }

  // Indicates whether an entry is in the changeset.
  has(id) {
    return id in this.changes;
  }

  // Deletes an entry from the changeset. Used to clean up entries for
  // reconciled and successfully uploaded records.
  delete(id) {
    delete this.changes[id];
  }

  // Changes the ID of an entry in the changeset. Used when reconciling
  // duplicates that have local changes.
  changeID(oldID, newID) {
    this.changes[newID] = this.changes[oldID];
    delete this.changes[oldID];
  }

  // Returns an array of all tracked IDs in this changeset.
  ids() {
    return Object.keys(this.changes);
  }

  // Returns an array of `[id, change]` tuples. Used to repopulate the tracker
  // with entries for failed uploads at the end of a sync.
  entries() {
    return Object.entries(this.changes);
  }

  // Returns the number of entries in this changeset.
  count() {
    return this.ids().length;
  }

  // Clears the changeset.
  clear() {
    this.changes = {};
  }
}
PK
!<��ff"modules/services-sync/keys.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { CommonUtils } from "resource://services-common/utils.sys.mjs";

import { Log } from "resource://gre/modules/Log.sys.mjs";

import { Weave } from "resource://services-sync/main.sys.mjs";

/**
 * Represents a pair of keys.
 *
 * Each key stored in a key bundle is 256 bits. One key is used for symmetric
 * encryption. The other is used for HMAC.
 *
 * A KeyBundle by itself is just an anonymous pair of keys. Other types
 * deriving from this one add semantics, such as associated collections or
 * generating a key bundle via HKDF from another key.
 */
function KeyBundle() {
  this._encrypt = null;
  this._encryptB64 = null;
  this._hmac = null;
  this._hmacB64 = null;
}
KeyBundle.prototype = {
  _encrypt: null,
  _encryptB64: null,
  _hmac: null,
  _hmacB64: null,

  equals: function equals(bundle) {
    return (
      bundle &&
      bundle.hmacKey == this.hmacKey &&
      bundle.encryptionKey == this.encryptionKey
    );
  },

  /*
   * Accessors for the two keys.
   */
  get encryptionKey() {
    return this._encrypt;
  },

  set encryptionKey(value) {
    if (!value || typeof value != "string") {
      throw new Error("Encryption key can only be set to string values.");
    }

    if (value.length < 16) {
      throw new Error("Encryption key must be at least 128 bits long.");
    }

    this._encrypt = value;
    this._encryptB64 = btoa(value);
  },

  get encryptionKeyB64() {
    return this._encryptB64;
  },

  get hmacKey() {
    return this._hmac;
  },

  set hmacKey(value) {
    if (!value || typeof value != "string") {
      throw new Error("HMAC key can only be set to string values.");
    }

    if (value.length < 16) {
      throw new Error("HMAC key must be at least 128 bits long.");
    }

    this._hmac = value;
    this._hmacB64 = btoa(value);
  },

  get hmacKeyB64() {
    return this._hmacB64;
  },

  /**
   * Populate this key pair with 2 new, randomly generated keys.
   */
  async generateRandom() {
    // Compute both at that same time
    let [generatedHMAC, generatedEncr] = await Promise.all([
      Weave.Crypto.generateRandomKey(),
      Weave.Crypto.generateRandomKey(),
    ]);
    this.keyPairB64 = [generatedEncr, generatedHMAC];
  },
};

/**
 * Represents a KeyBundle associated with a collection.
 *
 * This is just a KeyBundle with a collection attached.
 */
export function BulkKeyBundle(collection) {
  let log = Log.repository.getLogger("Sync.BulkKeyBundle");
  log.info("BulkKeyBundle being created for " + collection);
  KeyBundle.call(this);

  this._collection = collection;
}

BulkKeyBundle.fromHexKey = function (hexKey) {
  let key = CommonUtils.hexToBytes(hexKey);
  let bundle = new BulkKeyBundle();
  // [encryptionKey, hmacKey]
  bundle.keyPair = [key.slice(0, 32), key.slice(32, 64)];
  return bundle;
};

BulkKeyBundle.fromJWK = function (jwk) {
  if (!jwk || !jwk.k || jwk.kty !== "oct") {
    throw new Error("Invalid JWK provided to BulkKeyBundle.fromJWK");
  }
  return BulkKeyBundle.fromHexKey(CommonUtils.base64urlToHex(jwk.k));
};

BulkKeyBundle.prototype = {
  get collection() {
    return this._collection;
  },

  /**
   * Obtain the key pair in this key bundle.
   *
   * The returned keys are represented as raw byte strings.
   */
  get keyPair() {
    return [this.encryptionKey, this.hmacKey];
  },

  set keyPair(value) {
    if (!Array.isArray(value) || value.length != 2) {
      throw new Error("BulkKeyBundle.keyPair value must be array of 2 keys.");
    }

    this.encryptionKey = value[0];
    this.hmacKey = value[1];
  },

  get keyPairB64() {
    return [this.encryptionKeyB64, this.hmacKeyB64];
  },

  set keyPairB64(value) {
    if (!Array.isArray(value) || value.length != 2) {
      throw new Error(
        "BulkKeyBundle.keyPairB64 value must be an array of 2 keys."
      );
    }

    this.encryptionKey = CommonUtils.safeAtoB(value[0]);
    this.hmacKey = CommonUtils.safeAtoB(value[1]);
  },
};

Object.setPrototypeOf(BulkKeyBundle.prototype, KeyBundle.prototype);
PK
!<#B��;;"modules/services-sync/main.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export { lazy as Weave };

const lazy = {};

// We want these to be lazily loaded, which helps performance and also tests
// to not have these loaded before they are ready.
ChromeUtils.defineESModuleGetters(lazy, {
  Service: "resource://services-sync/service.sys.mjs",
  Status: "resource://services-sync/status.sys.mjs",
  Svc: "resource://services-sync/util.sys.mjs",
  Utils: "resource://services-sync/util.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "Crypto", () => {
  let { WeaveCrypto } = ChromeUtils.importESModule(
    "resource://services-crypto/WeaveCrypto.sys.mjs"
  );
  return new WeaveCrypto();
});
PK
!<V�Y�U�U�&modules/services-sync/policies.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { Log } from "resource://gre/modules/Log.sys.mjs";

import {
  CREDENTIALS_CHANGED,
  ENGINE_APPLY_FAIL,
  ENGINE_UNKNOWN_FAIL,
  IDLE_OBSERVER_BACK_DELAY,
  LOGIN_FAILED_INVALID_PASSPHRASE,
  LOGIN_FAILED_LOGIN_REJECTED,
  LOGIN_FAILED_NETWORK_ERROR,
  LOGIN_FAILED_NO_PASSPHRASE,
  LOGIN_SUCCEEDED,
  MASTER_PASSWORD_LOCKED,
  MASTER_PASSWORD_LOCKED_RETRY_INTERVAL,
  MAX_ERROR_COUNT_BEFORE_BACKOFF,
  MINIMUM_BACKOFF_INTERVAL,
  MULTI_DEVICE_THRESHOLD,
  NO_SYNC_NODE_FOUND,
  NO_SYNC_NODE_INTERVAL,
  OVER_QUOTA,
  RESPONSE_OVER_QUOTA,
  SCORE_UPDATE_DELAY,
  SERVER_MAINTENANCE,
  SINGLE_USER_THRESHOLD,
  STATUS_OK,
  SYNC_FAILED_PARTIAL,
  SYNC_SUCCEEDED,
  kSyncBackoffNotMet,
  kSyncMasterPasswordLocked,
} from "resource://services-sync/constants.sys.mjs";

import { Svc, Utils } from "resource://services-sync/util.sys.mjs";

import { logManager } from "resource://gre/modules/FxAccountsCommon.sys.mjs";
import { Async } from "resource://services-common/async.sys.mjs";
import { CommonUtils } from "resource://services-common/utils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  Status: "resource://services-sync/status.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => {
  return ChromeUtils.importESModule(
    "resource://gre/modules/FxAccounts.sys.mjs"
  ).getFxAccountsSingleton();
});
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "IdleService",
  "@mozilla.org/widget/useridleservice;1",
  "nsIUserIdleService"
);
XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "CaptivePortalService",
  "@mozilla.org/network/captive-portal-service;1",
  "nsICaptivePortalService"
);

// Get the value for an interval that's stored in preferences. To save users
// from themselves (and us from them!) the minimum time they can specify
// is 60s.
function getThrottledIntervalPreference(prefName) {
  return Math.max(Svc.PrefBranch.getIntPref(prefName), 60) * 1000;
}

export function SyncScheduler(service) {
  this.service = service;
  this.init();
}

SyncScheduler.prototype = {
  _log: Log.repository.getLogger("Sync.SyncScheduler"),

  _fatalLoginStatus: [
    LOGIN_FAILED_NO_PASSPHRASE,
    LOGIN_FAILED_INVALID_PASSPHRASE,
    LOGIN_FAILED_LOGIN_REJECTED,
  ],

  /**
   * The nsITimer object that schedules the next sync. See scheduleNextSync().
   */
  syncTimer: null,

  setDefaults: function setDefaults() {
    this._log.trace("Setting SyncScheduler policy values to defaults.");

    this.singleDeviceInterval = getThrottledIntervalPreference(
      "scheduler.fxa.singleDeviceInterval"
    );
    this.idleInterval = getThrottledIntervalPreference(
      "scheduler.idleInterval"
    );
    this.activeInterval = getThrottledIntervalPreference(
      "scheduler.activeInterval"
    );
    this.immediateInterval = getThrottledIntervalPreference(
      "scheduler.immediateInterval"
    );

    // A user is non-idle on startup by default.
    this.idle = false;

    this.hasIncomingItems = false;
    // This is the last number of clients we saw when previously updating the
    // client mode. If this != currentNumClients (obtained from prefs written
    // by the clients engine) then we need to transition to and from
    // single and multi-device mode.
    this.numClientsLastSync = 0;

    this._resyncs = 0;

    this.clearSyncTriggers();
  },

  // nextSync is in milliseconds, but prefs can't hold that much
  get nextSync() {
    return Svc.PrefBranch.getIntPref("nextSync", 0) * 1000;
  },
  set nextSync(value) {
    Svc.PrefBranch.setIntPref("nextSync", Math.floor(value / 1000));
  },

  get missedFxACommandsFetchInterval() {
    return Services.prefs.getIntPref(
      "identity.fxaccounts.commands.missed.fetch_interval"
    );
  },

  get missedFxACommandsLastFetch() {
    return Services.prefs.getIntPref(
      "identity.fxaccounts.commands.missed.last_fetch",
      0
    );
  },

  set missedFxACommandsLastFetch(val) {
    Services.prefs.setIntPref(
      "identity.fxaccounts.commands.missed.last_fetch",
      val
    );
  },

  get syncInterval() {
    return this._syncInterval;
  },
  set syncInterval(value) {
    if (value != this._syncInterval) {
      Services.prefs.setIntPref("services.sync.syncInterval", value);
    }
  },

  get syncThreshold() {
    return this._syncThreshold;
  },
  set syncThreshold(value) {
    if (value != this._syncThreshold) {
      Services.prefs.setIntPref("services.sync.syncThreshold", value);
    }
  },

  get globalScore() {
    return this._globalScore;
  },
  set globalScore(value) {
    if (this._globalScore != value) {
      Services.prefs.setIntPref("services.sync.globalScore", value);
    }
  },

  // Managed by the clients engine (by way of prefs)
  get numClients() {
    return this.numDesktopClients + this.numMobileClients;
  },
  set numClients(value) {
    throw new Error("Don't set numClients - the clients engine manages it.");
  },

  get offline() {
    // Services.io.offline has slowly become fairly useless over the years - it
    // no longer attempts to track the actual network state by default, but one
    // thing stays true: if it says we're offline then we are definitely not online.
    //
    // We also ask the captive portal service if we are behind a locked captive
    // portal.
    //
    // We don't check on the NetworkLinkService however, because it gave us
    // false positives in the past in a vm environment.
    try {
      if (
        Services.io.offline ||
        lazy.CaptivePortalService.state ==
          lazy.CaptivePortalService.LOCKED_PORTAL
      ) {
        return true;
      }
    } catch (ex) {
      this._log.warn("Could not determine network status.", ex);
    }
    return false;
  },

  _initPrefGetters() {
    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "idleTime",
      "services.sync.scheduler.idleTime"
    );
    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "maxResyncs",
      "services.sync.maxResyncs",
      0
    );

    // The number of clients we have is maintained in preferences via the
    // clients engine, and only updated after a successsful sync.
    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "numDesktopClients",
      "services.sync.clients.devices.desktop",
      0
    );
    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "numMobileClients",
      "services.sync.clients.devices.mobile",
      0
    );

    // Scheduler state that seems to be read more often than it's written.
    // We also check if the value has changed before writing in the setters.
    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "_syncThreshold",
      "services.sync.syncThreshold",
      SINGLE_USER_THRESHOLD
    );
    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "_syncInterval",
      "services.sync.syncInterval",
      this.singleDeviceInterval
    );
    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "_globalScore",
      "services.sync.globalScore",
      0
    );
  },

  init: function init() {
    this._log.manageLevelFromPref("services.sync.log.logger.service.main");
    this.setDefaults();
    this._initPrefGetters();
    Svc.Obs.add("weave:engine:score:updated", this);
    Svc.Obs.add("network:offline-status-changed", this);
    Svc.Obs.add("network:link-status-changed", this);
    Svc.Obs.add("captive-portal-detected", this);
    Svc.Obs.add("weave:service:sync:start", this);
    Svc.Obs.add("weave:service:sync:finish", this);
    Svc.Obs.add("weave:engine:sync:finish", this);
    Svc.Obs.add("weave:engine:sync:error", this);
    Svc.Obs.add("weave:service:login:error", this);
    Svc.Obs.add("weave:service:logout:finish", this);
    Svc.Obs.add("weave:service:sync:error", this);
    Svc.Obs.add("weave:service:backoff:interval", this);
    Svc.Obs.add("weave:engine:sync:applied", this);
    Svc.Obs.add("weave:service:setup-complete", this);
    Svc.Obs.add("weave:service:start-over", this);
    Svc.Obs.add("FxA:hawk:backoff:interval", this);

    if (lazy.Status.checkSetup() == STATUS_OK) {
      Svc.Obs.add("wake_notification", this);
      Svc.Obs.add("captive-portal-login-success", this);
      Svc.Obs.add("sleep_notification", this);
      lazy.IdleService.addIdleObserver(this, this.idleTime);
    }
  },

  // eslint-disable-next-line complexity
  observe: function observe(subject, topic, data) {
    this._log.trace("Handling " + topic);
    switch (topic) {
      case "weave:engine:score:updated":
        if (lazy.Status.login == LOGIN_SUCCEEDED) {
          CommonUtils.namedTimer(
            this.calculateScore,
            SCORE_UPDATE_DELAY,
            this,
            "_scoreTimer"
          );
        }
        break;
      case "network:link-status-changed":
        // Note: NetworkLinkService is unreliable, we get false negatives for it
        // in cases such as VMs (bug 1420802), so we don't want to use it in
        // `get offline`, but we assume that it's probably reliable if we're
        // getting status changed events. (We might be wrong about this, but
        // if that's true, then the only downside is that we won't sync as
        // promptly).
        let isOffline = this.offline;
        this._log.debug(
          `Network link status changed to "${data}". Offline?`,
          isOffline
        );
        // Data may be one of `up`, `down`, `change`, or `unknown`. We only want
        // to sync if it's "up".
        if (data == "up" && !isOffline) {
          this._log.debug("Network link looks up. Syncing.");
          this.scheduleNextSync(0, { why: topic });
        } else if (data == "down") {
          // Unschedule pending syncs if we know we're going down. We don't do
          // this via `checkSyncStatus`, since link status isn't reflected in
          // `this.offline`.
          this.clearSyncTriggers();
        }
        break;
      case "network:offline-status-changed":
      case "captive-portal-detected":
        // Whether online or offline, we'll reschedule syncs
        this._log.trace("Network offline status change: " + data);
        this.checkSyncStatus();
        break;
      case "weave:service:sync:start":
        // Clear out any potentially pending syncs now that we're syncing
        this.clearSyncTriggers();

        // reset backoff info, if the server tells us to continue backing off,
        // we'll handle that later
        lazy.Status.resetBackoff();

        this.globalScore = 0;
        break;
      case "weave:service:sync:finish":
        this.nextSync = 0;
        this.adjustSyncInterval();

        if (
          lazy.Status.service == SYNC_FAILED_PARTIAL &&
          this.requiresBackoff
        ) {
          this.requiresBackoff = false;
          this.handleSyncError();
          return;
        }

        let sync_interval;
        let nextSyncReason = "schedule";
        this.updateGlobalScore();
        if (
          this.globalScore > this.syncThreshold &&
          lazy.Status.service == STATUS_OK
        ) {
          // The global score should be 0 after a sync. If it's not, either
          // items were changed during the last sync (and we should schedule an
          // immediate follow-up sync), or an engine skipped
          this._resyncs++;
          if (this._resyncs <= this.maxResyncs) {
            sync_interval = 0;
            nextSyncReason = "resync";
          } else {
            this._log.warn(
              `Resync attempt ${this._resyncs} exceeded ` +
                `maximum ${this.maxResyncs}`
            );
            Svc.Obs.notify("weave:service:resyncs-finished");
          }
        } else {
          this._resyncs = 0;
          Svc.Obs.notify("weave:service:resyncs-finished");
        }

        this._syncErrors = 0;
        if (lazy.Status.sync == NO_SYNC_NODE_FOUND) {
          // If we don't have a Sync node, override the interval, even if we've
          // scheduled a follow-up sync.
          this._log.trace("Scheduling a sync at interval NO_SYNC_NODE_FOUND.");
          sync_interval = NO_SYNC_NODE_INTERVAL;
        }
        this.scheduleNextSync(sync_interval, { why: nextSyncReason });
        break;
      case "weave:engine:sync:finish":
        if (data == "clients") {
          // Update the client mode because it might change what we sync.
          this.updateClientMode();
        }
        break;
      case "weave:engine:sync:error":
        // `subject` is the exception thrown by an engine's sync() method.
        let exception = subject;
        if (exception.status >= 500 && exception.status <= 504) {
          this.requiresBackoff = true;
        }
        break;
      case "weave:service:login:error":
        this.clearSyncTriggers();

        if (lazy.Status.login == MASTER_PASSWORD_LOCKED) {
          // Try again later, just as if we threw an error... only without the
          // error count.
          this._log.debug("Couldn't log in: master password is locked.");
          this._log.trace(
            "Scheduling a sync at MASTER_PASSWORD_LOCKED_RETRY_INTERVAL"
          );
          this.scheduleAtInterval(MASTER_PASSWORD_LOCKED_RETRY_INTERVAL);
        } else if (!this._fatalLoginStatus.includes(lazy.Status.login)) {
          // Not a fatal login error, just an intermittent network or server
          // issue. Keep on syncin'.
          this.checkSyncStatus();
        }
        break;
      case "weave:service:logout:finish":
        // Start or cancel the sync timer depending on if
        // logged in or logged out
        this.checkSyncStatus();
        break;
      case "weave:service:sync:error":
        // There may be multiple clients but if the sync fails, client mode
        // should still be updated so that the next sync has a correct interval.
        this.updateClientMode();
        this.adjustSyncInterval();
        this.nextSync = 0;
        this.handleSyncError();
        break;
      case "FxA:hawk:backoff:interval":
      case "weave:service:backoff:interval":
        let requested_interval = subject * 1000;
        this._log.debug(
          "Got backoff notification: " + requested_interval + "ms"
        );
        // Leave up to 25% more time for the back off.
        let interval = requested_interval * (1 + Math.random() * 0.25);
        lazy.Status.backoffInterval = interval;
        lazy.Status.minimumNextSync = Date.now() + requested_interval;
        this._log.debug(
          "Fuzzed minimum next sync: " + lazy.Status.minimumNextSync
        );
        break;
      case "weave:engine:sync:applied":
        let numItems = subject.succeeded;
        this._log.trace(
          "Engine " + data + " successfully applied " + numItems + " items."
        );
        // Bug 1800186 - the tabs engine always reports incoming items, so we don't
        // want special scheduling in this scenario.
        // (However, even when we fix the underlying cause of that, we probably still can
        // ignore tabs here - new incoming tabs don't need to trigger the extra syncs we do
        // based on this flag.)
        if (data != "tabs" && numItems) {
          this.hasIncomingItems = true;
        }
        if (subject.newFailed) {
          this._log.error(
            `Engine ${data} found ${subject.newFailed} new records that failed to apply`
          );
        }
        break;
      case "weave:service:setup-complete":
        Services.prefs.savePrefFile(null);
        lazy.IdleService.addIdleObserver(this, this.idleTime);
        Svc.Obs.add("wake_notification", this);
        Svc.Obs.add("captive-portal-login-success", this);
        Svc.Obs.add("sleep_notification", this);
        break;
      case "weave:service:start-over":
        this.setDefaults();
        try {
          lazy.IdleService.removeIdleObserver(this, this.idleTime);
        } catch (ex) {
          if (ex.result != Cr.NS_ERROR_FAILURE) {
            throw ex;
          }
          // In all likelihood we didn't have an idle observer registered yet.
          // It's all good.
        }
        break;
      case "idle":
        this._log.trace("We're idle.");
        this.idle = true;
        // Adjust the interval for future syncs. This won't actually have any
        // effect until the next pending sync (which will happen soon since we
        // were just active.)
        this.adjustSyncInterval();
        break;
      case "active":
        this._log.trace("Received notification that we're back from idle.");
        this.idle = false;
        CommonUtils.namedTimer(
          function onBack() {
            if (this.idle) {
              this._log.trace(
                "... and we're idle again. " +
                  "Ignoring spurious back notification."
              );
              return;
            }

            this._log.trace("Genuine return from idle. Syncing.");
            // Trigger a sync if we have multiple clients.
            if (this.numClients > 1) {
              this.scheduleNextSync(0, { why: topic });
            }
          },
          IDLE_OBSERVER_BACK_DELAY,
          this,
          "idleDebouncerTimer"
        );
        break;
      case "wake_notification":
        this._log.debug("Woke from sleep.");
        CommonUtils.nextTick(() => {
          // Trigger a sync if we have multiple clients. We give it 2 seconds
          // so the browser can recover from the wake and do more important
          // operations first (timers etc).
          if (this.numClients > 1) {
            if (!this.offline) {
              this._log.debug("Online, will sync in 2s.");
              this.scheduleNextSync(2000, { why: topic });
            }
          }
        });
        break;
      case "captive-portal-login-success":
        this._log.debug("Captive portal login success. Scheduling a sync.");
        CommonUtils.nextTick(() => {
          this.scheduleNextSync(3000, { why: topic });
        });
        break;
      case "sleep_notification":
        if (this.service.engineManager.get("tabs")._tracker.modified) {
          this._log.debug("Going to sleep, doing a quick sync.");
          this.scheduleNextSync(0, { engines: ["tabs"], why: "sleep" });
        }
        break;
    }
  },

  adjustSyncInterval: function adjustSyncInterval() {
    if (this.numClients <= 1) {
      this._log.trace("Adjusting syncInterval to singleDeviceInterval.");
      this.syncInterval = this.singleDeviceInterval;
      return;
    }

    // Only MULTI_DEVICE clients will enter this if statement
    // since SINGLE_USER clients will be handled above.
    if (this.idle) {
      this._log.trace("Adjusting syncInterval to idleInterval.");
      this.syncInterval = this.idleInterval;
      return;
    }

    if (this.hasIncomingItems) {
      this._log.trace("Adjusting syncInterval to immediateInterval.");
      this.hasIncomingItems = false;
      this.syncInterval = this.immediateInterval;
    } else {
      this._log.trace("Adjusting syncInterval to activeInterval.");
      this.syncInterval = this.activeInterval;
    }
  },

  updateGlobalScore() {
    let engines = [this.service.clientsEngine].concat(
      this.service.engineManager.getEnabled()
    );
    let globalScore = this.globalScore;
    for (let i = 0; i < engines.length; i++) {
      this._log.trace(engines[i].name + ": score: " + engines[i].score);
      globalScore += engines[i].score;
      engines[i]._tracker.resetScore();
    }
    this.globalScore = globalScore;
    this._log.trace("Global score updated: " + globalScore);
  },

  calculateScore() {
    this.updateGlobalScore();
    this.checkSyncStatus();
  },

  /**
   * Query the number of known clients to figure out what mode to be in
   */
  updateClientMode: function updateClientMode() {
    // Nothing to do if it's the same amount
    let numClients = this.numClients;
    if (numClients == this.numClientsLastSync) {
      return;
    }

    this._log.debug(
      `Client count: ${this.numClientsLastSync} -> ${numClients}`
    );
    this.numClientsLastSync = numClients;

    if (numClients <= 1) {
      this._log.trace("Adjusting syncThreshold to SINGLE_USER_THRESHOLD");
      this.syncThreshold = SINGLE_USER_THRESHOLD;
    } else {
      this._log.trace("Adjusting syncThreshold to MULTI_DEVICE_THRESHOLD");
      this.syncThreshold = MULTI_DEVICE_THRESHOLD;
    }
    this.adjustSyncInterval();
  },

  /**
   * Check if we should be syncing and schedule the next sync, if it's not scheduled
   */
  checkSyncStatus: function checkSyncStatus() {
    // Should we be syncing now, if not, cancel any sync timers and return
    // if we're in backoff, we'll schedule the next sync.
    let ignore = [kSyncBackoffNotMet, kSyncMasterPasswordLocked];
    let skip = this.service._checkSync(ignore);
    this._log.trace('_checkSync returned "' + skip + '".');
    if (skip) {
      this.clearSyncTriggers();
      return;
    }

    let why = "schedule";
    // Only set the wait time to 0 if we need to sync right away
    let wait;
    if (this.globalScore > this.syncThreshold) {
      this._log.debug("Global Score threshold hit, triggering sync.");
      wait = 0;
      why = "score";
    }
    this.scheduleNextSync(wait, { why });
  },

  /**
   * Call sync() if Master Password is not locked.
   *
   * Otherwise, reschedule a sync for later.
   */
  syncIfMPUnlocked(engines, why) {
    // No point if we got kicked out by the master password dialog.
    if (lazy.Status.login == MASTER_PASSWORD_LOCKED && Utils.mpLocked()) {
      this._log.debug(
        "Not initiating sync: Login status is " + lazy.Status.login
      );

      // If we're not syncing now, we need to schedule the next one.
      this._log.trace(
        "Scheduling a sync at MASTER_PASSWORD_LOCKED_RETRY_INTERVAL"
      );
      this.scheduleAtInterval(MASTER_PASSWORD_LOCKED_RETRY_INTERVAL);
      return;
    }

    if (!Async.isAppReady()) {
      this._log.debug("Not initiating sync: app is shutting down");
      return;
    }
    Services.tm.dispatchToMainThread(() => {
      this.service.sync({ engines, why });
      const now = Math.round(new Date().getTime() / 1000);
      // Only fetch missed messages in a "scheduled" sync so we don't race against
      // the Push service reconnecting on a network link change for example.
      if (
        why == "schedule" &&
        now >=
          this.missedFxACommandsLastFetch + this.missedFxACommandsFetchInterval
      ) {
        lazy.fxAccounts.commands
          .pollDeviceCommands()
          .then(() => {
            this.missedFxACommandsLastFetch = now;
          })
          .catch(e => {
            this._log.error("Fetching missed remote commands failed.", e);
          });
      }
    });
  },

  /**
   * Set a timer for the next sync
   */
  scheduleNextSync(interval, { engines = null, why = null } = {}) {
    // If no interval was specified, use the current sync interval.
    if (interval == null) {
      interval = this.syncInterval;
    }

    // Ensure the interval is set to no less than the backoff.
    if (lazy.Status.backoffInterval && interval < lazy.Status.backoffInterval) {
      this._log.trace(
        "Requested interval " +
          interval +
          " ms is smaller than the backoff interval. " +
          "Using backoff interval " +
          lazy.Status.backoffInterval +
          " ms instead."
      );
      interval = lazy.Status.backoffInterval;
    }
    let nextSync = this.nextSync;
    if (nextSync != 0) {
      // There's already a sync scheduled. Don't reschedule if there's already
      // a timer scheduled for sooner than requested.
      let currentInterval = nextSync - Date.now();
      this._log.trace(
        "There's already a sync scheduled in " + currentInterval + " ms."
      );
      if (currentInterval < interval && this.syncTimer) {
        this._log.trace(
          "Ignoring scheduling request for next sync in " + interval + " ms."
        );
        return;
      }
    }

    // Start the sync right away if we're already late.
    if (interval <= 0) {
      this._log.trace(`Requested sync should happen right away. (why=${why})`);
      this.syncIfMPUnlocked(engines, why);
      return;
    }

    this._log.debug(`Next sync in ${interval} ms. (why=${why})`);
    CommonUtils.namedTimer(
      () => {
        this.syncIfMPUnlocked(engines, why);
      },
      interval,
      this,
      "syncTimer"
    );

    // Save the next sync time in-case sync is disabled (logout/offline/etc.)
    this.nextSync = Date.now() + interval;
  },

  /**
   * Incorporates the backoff/retry logic used in error handling and elective
   * non-syncing.
   */
  scheduleAtInterval: function scheduleAtInterval(minimumInterval) {
    let interval = Utils.calculateBackoff(
      this._syncErrors,
      MINIMUM_BACKOFF_INTERVAL,
      lazy.Status.backoffInterval
    );
    if (minimumInterval) {
      interval = Math.max(minimumInterval, interval);
    }

    this._log.debug(
      "Starting client-initiated backoff. Next sync in " + interval + " ms."
    );
    this.scheduleNextSync(interval, { why: "client-backoff-schedule" });
  },

  autoConnect: function autoConnect() {
    if (this.service._checkSetup() == STATUS_OK && !this.service._checkSync()) {
      // Schedule a sync based on when a previous sync was scheduled.
      // scheduleNextSync() will do the right thing if that time lies in
      // the past.
      this.scheduleNextSync(this.nextSync - Date.now(), { why: "startup" });
    }
  },

  _syncErrors: 0,
  /**
   * Deal with sync errors appropriately
   */
  handleSyncError: function handleSyncError() {
    this._log.trace("In handleSyncError. Error count: " + this._syncErrors);
    this._syncErrors++;

    // Do nothing on the first couple of failures, if we're not in
    // backoff due to 5xx errors.
    if (!lazy.Status.enforceBackoff) {
      if (this._syncErrors < MAX_ERROR_COUNT_BEFORE_BACKOFF) {
        this.scheduleNextSync(null, { why: "reschedule" });
        return;
      }
      this._log.debug(
        "Sync error count has exceeded " +
          MAX_ERROR_COUNT_BEFORE_BACKOFF +
          "; enforcing backoff."
      );
      lazy.Status.enforceBackoff = true;
    }

    this.scheduleAtInterval();
  },

  /**
   * Remove any timers/observers that might trigger a sync
   */
  clearSyncTriggers: function clearSyncTriggers() {
    this._log.debug("Clearing sync triggers and the global score.");
    this.globalScore = this.nextSync = 0;

    // Clear out any scheduled syncs
    if (this.syncTimer) {
      this.syncTimer.clear();
    }
  },
};

export function ErrorHandler(service) {
  this.service = service;
  this.init();
}

ErrorHandler.prototype = {
  init() {
    Svc.Obs.add("weave:engine:sync:applied", this);
    Svc.Obs.add("weave:engine:sync:error", this);
    Svc.Obs.add("weave:service:login:error", this);
    Svc.Obs.add("weave:service:sync:error", this);
    Svc.Obs.add("weave:service:sync:finish", this);
    Svc.Obs.add("weave:service:start-over:finish", this);

    this.initLogs();
  },

  initLogs: function initLogs() {
    // Set the root Sync logger level based on a pref. All other logs will
    // inherit this level unless they specifically override it.
    Log.repository
      .getLogger("Sync")
      .manageLevelFromPref(`services.sync.log.logger`);
    // And allow our specific log to have a custom level via a pref.
    this._log = Log.repository.getLogger("Sync.ErrorHandler");
    this._log.manageLevelFromPref("services.sync.log.logger.service.main");
  },

  observe(subject, topic, data) {
    this._log.trace("Handling " + topic);
    switch (topic) {
      case "weave:engine:sync:applied":
        if (subject.newFailed) {
          // An engine isn't able to apply one or more incoming records.
          // We don't fail hard on this, but it usually indicates a bug,
          // so for now treat it as sync error (c.f. Service._syncEngine())
          lazy.Status.engines = [data, ENGINE_APPLY_FAIL];
          this._log.debug(data + " failed to apply some records.");
        }
        break;
      case "weave:engine:sync:error": {
        let exception = subject; // exception thrown by engine's sync() method
        let engine_name = data; // engine name that threw the exception

        this.checkServerError(exception);

        lazy.Status.engines = [
          engine_name,
          exception.failureCode || ENGINE_UNKNOWN_FAIL,
        ];
        if (Async.isShutdownException(exception)) {
          this._log.debug(
            engine_name +
              " was interrupted due to the application shutting down"
          );
        } else {
          this._log.debug(engine_name + " failed", exception);
        }
        break;
      }
      case "weave:service:login:error":
        this._log.error("Sync encountered a login error");
        this.resetFileLog();
        break;
      case "weave:service:sync:error": {
        if (lazy.Status.sync == CREDENTIALS_CHANGED) {
          this.service.logout();
        }

        let exception = subject;
        if (Async.isShutdownException(exception)) {
          // If we are shutting down we just log the fact, attempt to flush
          // the log file and get out of here!
          this._log.error(
            "Sync was interrupted due to the application shutting down"
          );
          this.resetFileLog();
          break;
        }

        // Not a shutdown related exception...
        this._log.error("Sync encountered an error", exception);
        this.resetFileLog();
        break;
      }
      case "weave:service:sync:finish":
        this._log.trace("Status.service is " + lazy.Status.service);

        // Check both of these status codes: in the event of a failure in one
        // engine, Status.service will be SYNC_FAILED_PARTIAL despite
        // Status.sync being SYNC_SUCCEEDED.
        // *facepalm*
        if (
          lazy.Status.sync == SYNC_SUCCEEDED &&
          lazy.Status.service == STATUS_OK
        ) {
          // Great. Let's clear our mid-sync 401 note.
          this._log.trace("Clearing lastSyncReassigned.");
          Svc.PrefBranch.clearUserPref("lastSyncReassigned");
        }

        if (lazy.Status.service == SYNC_FAILED_PARTIAL) {
          this._log.error("Some engines did not sync correctly.");
        }
        this.resetFileLog();
        break;
      case "weave:service:start-over:finish":
        // ensure we capture any logs between the last sync and the reset completing.
        this.resetFileLog()
          .then(() => {
            // although for privacy reasons we also delete all logs (but we allow
            // a preference to avoid this to help with debugging.)
            if (!Svc.PrefBranch.getBoolPref("log.keepLogsOnReset", false)) {
              return logManager.removeAllLogs().then(() => {
                Svc.Obs.notify("weave:service:remove-file-log");
              });
            }
            return null;
          })
          .catch(err => {
            // So we failed to delete the logs - take the ironic option of
            // writing this error to the logs we failed to delete!
            this._log.error("Failed to delete logs on reset", err);
          });
        break;
    }
  },

  async _dumpAddons() {
    // Just dump the items that sync may be concerned with. Specifically,
    // active extensions that are not hidden.
    let addons = [];
    try {
      addons = await lazy.AddonManager.getAddonsByTypes(["extension"]);
    } catch (e) {
      this._log.warn("Failed to dump addons", e);
    }

    let relevantAddons = addons.filter(x => x.isActive && !x.hidden);
    this._log.trace("Addons installed", relevantAddons.length);
    for (let addon of relevantAddons) {
      this._log.trace(" - ${name}, version ${version}, id ${id}", addon);
    }
  },

  /**
   * Generate a log file for the sync that just completed
   * and refresh the input & output streams.
   */
  async resetFileLog() {
    // If we're writing an error log, dump extensions that may be causing problems.
    if (logManager.sawError) {
      await this._dumpAddons();
    }
    const logType = await logManager.resetFileLog();
    if (logType == logManager.ERROR_LOG_WRITTEN) {
      console.error(
        "Sync encountered an error - see about:sync-log for the log file."
      );
    }
    Svc.Obs.notify("weave:service:reset-file-log");
  },

  /**
   * Handle HTTP response results or exceptions and set the appropriate
   * Status.* bits.
   *
   * This method also looks for "side-channel" warnings.
   */
  checkServerError(resp) {
    // In this case we were passed a resolved value of Resource#_doRequest.
    switch (resp.status) {
      case 400:
        if (resp == RESPONSE_OVER_QUOTA) {
          lazy.Status.sync = OVER_QUOTA;
        }
        break;

      case 401:
        this.service.logout();
        this._log.info("Got 401 response; resetting clusterURL.");
        this.service.clusterURL = null;

        let delay = 0;
        if (Svc.PrefBranch.getBoolPref("lastSyncReassigned", false)) {
          // We got a 401 in the middle of the previous sync, and we just got
          // another. Login must have succeeded in order for us to get here, so
          // the password should be correct.
          // This is likely to be an intermittent server issue, so back off and
          // give it time to recover.
          this._log.warn("Last sync also failed for 401. Delaying next sync.");
          delay = MINIMUM_BACKOFF_INTERVAL;
        } else {
          this._log.debug("New mid-sync 401 failure. Making a note.");
          Svc.PrefBranch.setBoolPref("lastSyncReassigned", true);
        }
        this._log.info("Attempting to schedule another sync.");
        this.service.scheduler.scheduleNextSync(delay, { why: "reschedule" });
        break;

      case 500:
      case 502:
      case 503:
      case 504:
        lazy.Status.enforceBackoff = true;
        if (resp.status == 503 && resp.headers["retry-after"]) {
          let retryAfter = resp.headers["retry-after"];
          this._log.debug("Got Retry-After: " + retryAfter);
          if (this.service.isLoggedIn) {
            lazy.Status.sync = SERVER_MAINTENANCE;
          } else {
            lazy.Status.login = SERVER_MAINTENANCE;
          }
          Svc.Obs.notify(
            "weave:service:backoff:interval",
            parseInt(retryAfter, 10)
          );
        }
        break;
    }

    // In this other case we were passed a rejection value.
    switch (resp.result) {
      case Cr.NS_ERROR_UNKNOWN_HOST:
      case Cr.NS_ERROR_CONNECTION_REFUSED:
      case Cr.NS_ERROR_NET_TIMEOUT:
      case Cr.NS_ERROR_NET_RESET:
      case Cr.NS_ERROR_NET_INTERRUPT:
      case Cr.NS_ERROR_PROXY_CONNECTION_REFUSED:
        // The constant says it's about login, but in fact it just
        // indicates general network error.
        if (this.service.isLoggedIn) {
          lazy.Status.sync = LOGIN_FAILED_NETWORK_ERROR;
        } else {
          lazy.Status.login = LOGIN_FAILED_NETWORK_ERROR;
        }
        break;
    }
  },
};
PK
!<cz���$modules/services-sync/record.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const CRYPTO_COLLECTION = "crypto";
const KEYS_WBO = "keys";

import { Log } from "resource://gre/modules/Log.sys.mjs";

import {
  DEFAULT_DOWNLOAD_BATCH_SIZE,
  DEFAULT_KEYBUNDLE_NAME,
} from "resource://services-sync/constants.sys.mjs";
import { BulkKeyBundle } from "resource://services-sync/keys.sys.mjs";
import { Weave } from "resource://services-sync/main.sys.mjs";
import { Resource } from "resource://services-sync/resource.sys.mjs";
import { Utils } from "resource://services-sync/util.sys.mjs";

import { Async } from "resource://services-common/async.sys.mjs";
import { CommonUtils } from "resource://services-common/utils.sys.mjs";
import { CryptoUtils } from "resource://services-crypto/utils.sys.mjs";

/**
 * The base class for all Sync basic storage objects (BSOs). This is the format
 * used to store all records on the Sync server. In an earlier version of the
 * Sync protocol, BSOs used to be called WBOs, or Weave Basic Objects. This
 * class retains the old name.
 *
 * @class
 * @param {String} collection The collection name for this BSO.
 * @param {String} id         The ID of this BSO.
 */
export function WBORecord(collection, id) {
  this.data = {};
  this.payload = {};
  this.collection = collection; // Optional.
  this.id = id; // Optional.
}

WBORecord.prototype = {
  _logName: "Sync.Record.WBO",

  get sortindex() {
    if (this.data.sortindex) {
      return this.data.sortindex;
    }
    return 0;
  },

  // Get thyself from your URI, then deserialize.
  // Set thine 'response' field.
  async fetch(resource) {
    if (!(resource instanceof Resource)) {
      throw new Error("First argument must be a Resource instance.");
    }

    let r = await resource.get();
    if (r.success) {
      this.deserialize(r.obj); // Warning! Muffles exceptions!
    }
    this.response = r;
    return this;
  },

  upload(resource) {
    if (!(resource instanceof Resource)) {
      throw new Error("First argument must be a Resource instance.");
    }

    return resource.put(this);
  },

  // Take a base URI string, with trailing slash, and return the URI of this
  // WBO based on collection and ID.
  uri(base) {
    if (this.collection && this.id) {
      let url = CommonUtils.makeURI(base + this.collection + "/" + this.id);
      url.QueryInterface(Ci.nsIURL);
      return url;
    }
    return null;
  },

  deserialize: function deserialize(json) {
    if (!json || typeof json !== "object") {
      throw new TypeError("Can't deserialize record from: " + json);
    }
    this.data = json;
    try {
      // The payload is likely to be JSON, but if not, keep it as a string
      this.payload = JSON.parse(this.payload);
    } catch (ex) {}
  },

  toJSON: function toJSON() {
    // Copy fields from data to be stringified, making sure payload is a string
    let obj = {};
    for (let [key, val] of Object.entries(this.data)) {
      obj[key] = key == "payload" ? JSON.stringify(val) : val;
    }
    if (this.ttl) {
      obj.ttl = this.ttl;
    }
    return obj;
  },

  toString: function toString() {
    return (
      "{ " +
      "id: " +
      this.id +
      "  " +
      "index: " +
      this.sortindex +
      "  " +
      "modified: " +
      this.modified +
      "  " +
      "ttl: " +
      this.ttl +
      "  " +
      "payload: " +
      JSON.stringify(this.payload) +
      " }"
    );
  },
};

Utils.deferGetSet(WBORecord, "data", [
  "id",
  "modified",
  "sortindex",
  "payload",
]);

/**
 * An encrypted BSO record. This subclass handles encrypting and decrypting the
 * BSO payload, but doesn't parse or interpret the cleartext string. Subclasses
 * must override `transformBeforeEncrypt` and `transformAfterDecrypt` to process
 * the cleartext.
 *
 * This class is only exposed for bridged engines, which handle serialization
 * and deserialization in Rust. Sync engines implemented in JS should subclass
 * `CryptoWrapper` instead, which takes care of transforming the cleartext into
 * an object, and ensuring its contents are valid.
 *
 * @class
 * @template Cleartext
 * @param    {String} collection The collection name for this BSO.
 * @param    {String} id         The ID of this BSO.
 */
export function RawCryptoWrapper(collection, id) {
  // Setting properties before calling the superclass constructor isn't allowed
  // in new-style classes (`class MyRecord extends RawCryptoWrapper`), but
  // allowed with plain functions. This is also why `defaultCleartext` is a
  // method, and not simply set in the subclass constructor.
  this.cleartext = this.defaultCleartext();
  WBORecord.call(this, collection, id);
  this.ciphertext = null;
}

RawCryptoWrapper.prototype = {
  _logName: "Sync.Record.RawCryptoWrapper",

  /**
   * Returns the default empty cleartext for this record type. This is exposed
   * as a method so that subclasses can override it, and access the default
   * cleartext in their constructors. `CryptoWrapper`, for example, overrides
   * this to return an empty object, so that initializing the `id` in its
   * constructor calls its overridden `id` setter.
   *
   * @returns {Cleartext} An empty cleartext.
   */
  defaultCleartext() {
    return null;
  },

  /**
   * Transforms the cleartext into a string that can be encrypted and wrapped
   * in a BSO payload. This is called before uploading the record to the server.
   *
   * @param   {Cleartext} outgoingCleartext The cleartext to upload.
   * @returns {String}                      The serialized cleartext.
   */
  transformBeforeEncrypt() {
    throw new TypeError("Override to stringify outgoing records");
  },

  /**
   * Transforms an incoming cleartext string into an instance of the
   * `Cleartext` type. This is called when fetching the record from the
   * server.
   *
   * @param   {String} incomingCleartext The decrypted cleartext string.
   * @returns {Cleartext}                The parsed cleartext.
   */
  transformAfterDecrypt() {
    throw new TypeError("Override to parse incoming records");
  },

  ciphertextHMAC: async function ciphertextHMAC(keyBundle) {
    let hmacKeyByteString = keyBundle.hmacKey;
    if (!hmacKeyByteString) {
      throw new Error("Cannot compute HMAC without an HMAC key.");
    }
    let hmacKey = CommonUtils.byteStringToArrayBuffer(hmacKeyByteString);
    // NB: this.ciphertext is a base64-encoded string. For some reason this
    // implementation computes the HMAC on the encoded value.
    let data = CommonUtils.byteStringToArrayBuffer(this.ciphertext);
    let hmac = await CryptoUtils.hmac("SHA-256", hmacKey, data);
    return CommonUtils.bytesAsHex(CommonUtils.arrayBufferToByteString(hmac));
  },

  /*
   * Don't directly use the sync key. Instead, grab a key for this
   * collection, which is decrypted with the sync key.
   *
   * Cache those keys; invalidate the cache if the time on the keys collection
   * changes, or other auth events occur.
   *
   * Optional key bundle overrides the collection key lookup.
   */
  async encrypt(keyBundle) {
    if (!keyBundle) {
      throw new Error("A key bundle must be supplied to encrypt.");
    }

    this.IV = Weave.Crypto.generateRandomIV();
    this.ciphertext = await Weave.Crypto.encrypt(
      this.transformBeforeEncrypt(this.cleartext),
      keyBundle.encryptionKeyB64,
      this.IV
    );
    this.hmac = await this.ciphertextHMAC(keyBundle);
    this.cleartext = null;
  },

  // Optional key bundle.
  async decrypt(keyBundle) {
    if (!this.ciphertext) {
      throw new Error("No ciphertext: nothing to decrypt?");
    }

    if (!keyBundle) {
      throw new Error("A key bundle must be supplied to decrypt.");
    }

    // Authenticate the encrypted blob with the expected HMAC
    let computedHMAC = await this.ciphertextHMAC(keyBundle);

    if (computedHMAC != this.hmac) {
      Utils.throwHMACMismatch(this.hmac, computedHMAC);
    }

    let cleartext = await Weave.Crypto.decrypt(
      this.ciphertext,
      keyBundle.encryptionKeyB64,
      this.IV
    );
    this.cleartext = this.transformAfterDecrypt(cleartext);
    this.ciphertext = null;

    return this.cleartext;
  },
};

Object.setPrototypeOf(RawCryptoWrapper.prototype, WBORecord.prototype);

Utils.deferGetSet(RawCryptoWrapper, "payload", ["ciphertext", "IV", "hmac"]);

/**
 * An encrypted BSO record with a JSON payload. All engines implemented in JS
 * should subclass this class to describe their own record types.
 *
 * @class
 * @param {String} collection The collection name for this BSO.
 * @param {String} id         The ID of this BSO.
 */
export function CryptoWrapper(collection, id) {
  RawCryptoWrapper.call(this, collection, id);
}

CryptoWrapper.prototype = {
  _logName: "Sync.Record.CryptoWrapper",

  defaultCleartext() {
    return {};
  },

  transformBeforeEncrypt(cleartext) {
    return JSON.stringify(cleartext);
  },

  transformAfterDecrypt(cleartext) {
    // Handle invalid data here. Elsewhere we assume that cleartext is an object.
    let json_result = JSON.parse(cleartext);

    if (!(json_result && json_result instanceof Object)) {
      throw new Error(
        `Decryption failed: result is <${json_result}>, not an object.`
      );
    }

    // If the payload has an encrypted id ensure it matches the requested record's id.
    if (json_result.id && json_result.id != this.id) {
      throw new Error(`Record id mismatch: ${json_result.id} != ${this.id}`);
    }

    return json_result;
  },

  cleartextToString() {
    return JSON.stringify(this.cleartext);
  },

  toString: function toString() {
    let payload = this.deleted ? "DELETED" : this.cleartextToString();

    return (
      "{ " +
      "id: " +
      this.id +
      "  " +
      "index: " +
      this.sortindex +
      "  " +
      "modified: " +
      this.modified +
      "  " +
      "ttl: " +
      this.ttl +
      "  " +
      "payload: " +
      payload +
      "  " +
      "collection: " +
      (this.collection || "undefined") +
      " }"
    );
  },

  // The custom setter below masks the parent's getter, so explicitly call it :(
  get id() {
    return super.id;
  },

  // Keep both plaintext and encrypted versions of the id to verify integrity
  set id(val) {
    super.id = val;
    this.cleartext.id = val;
  },
};

Object.setPrototypeOf(CryptoWrapper.prototype, RawCryptoWrapper.prototype);

Utils.deferGetSet(CryptoWrapper, "cleartext", "deleted");

/**
 * An interface and caching layer for records.
 */
export function RecordManager(service) {
  this.service = service;

  this._log = Log.repository.getLogger(this._logName);
  this._records = {};
}

RecordManager.prototype = {
  _recordType: CryptoWrapper,
  _logName: "Sync.RecordManager",

  async import(url) {
    this._log.trace("Importing record: " + (url.spec ? url.spec : url));
    try {
      // Clear out the last response with empty object if GET fails
      this.response = {};
      this.response = await this.service.resource(url).get();

      // Don't parse and save the record on failure
      if (!this.response.success) {
        return null;
      }

      let record = new this._recordType(url);
      record.deserialize(this.response.obj);

      return this.set(url, record);
    } catch (ex) {
      if (Async.isShutdownException(ex)) {
        throw ex;
      }
      this._log.debug("Failed to import record", ex);
      return null;
    }
  },

  get(url) {
    // Use a url string as the key to the hash
    let spec = url.spec ? url.spec : url;
    if (spec in this._records) {
      return Promise.resolve(this._records[spec]);
    }
    return this.import(url);
  },

  set: function RecordMgr_set(url, record) {
    let spec = url.spec ? url.spec : url;
    return (this._records[spec] = record);
  },

  contains: function RecordMgr_contains(url) {
    if ((url.spec || url) in this._records) {
      return true;
    }
    return false;
  },

  clearCache: function recordMgr_clearCache() {
    this._records = {};
  },

  del: function RecordMgr_del(url) {
    delete this._records[url];
  },
};

/**
 * Keeps track of mappings between collection names ('tabs') and KeyBundles.
 *
 * You can update this thing simply by giving it /info/collections. It'll
 * use the last modified time to bring itself up to date.
 */
export function CollectionKeyManager(lastModified, default_, collections) {
  this.lastModified = lastModified || 0;
  this._default = default_ || null;
  this._collections = collections || {};

  this._log = Log.repository.getLogger("Sync.CollectionKeyManager");
}

// TODO: persist this locally as an Identity. Bug 610913.
// Note that the last modified time needs to be preserved.
CollectionKeyManager.prototype = {
  /**
   * Generate a new CollectionKeyManager that has the same attributes
   * as this one.
   */
  clone() {
    const newCollections = {};
    for (let c in this._collections) {
      newCollections[c] = this._collections[c];
    }

    return new CollectionKeyManager(
      this.lastModified,
      this._default,
      newCollections
    );
  },

  // Return information about old vs new keys:
  // * same: true if two collections are equal
  // * changed: an array of collection names that changed.
  _compareKeyBundleCollections: function _compareKeyBundleCollections(m1, m2) {
    let changed = [];

    function process(m1, m2) {
      for (let k1 in m1) {
        let v1 = m1[k1];
        let v2 = m2[k1];
        if (!(v1 && v2 && v1.equals(v2))) {
          changed.push(k1);
        }
      }
    }

    // Diffs both ways.
    process(m1, m2);
    process(m2, m1);

    // Return a sorted, unique array.
    changed.sort();
    let last;
    changed = changed.filter(x => x != last && (last = x));
    return { same: !changed.length, changed };
  },

  get isClear() {
    return !this._default;
  },

  clear: function clear() {
    this._log.info("Clearing collection keys...");
    this.lastModified = 0;
    this._collections = {};
    this._default = null;
  },

  keyForCollection(collection) {
    if (collection && this._collections[collection]) {
      return this._collections[collection];
    }

    return this._default;
  },

  /**
   * If `collections` (an array of strings) is provided, iterate
   * over it and generate random keys for each collection.
   * Create a WBO for the given data.
   */
  _makeWBO(collections, defaultBundle) {
    let wbo = new CryptoWrapper(CRYPTO_COLLECTION, KEYS_WBO);
    let c = {};
    for (let k in collections) {
      c[k] = collections[k].keyPairB64;
    }
    wbo.cleartext = {
      default: defaultBundle ? defaultBundle.keyPairB64 : null,
      collections: c,
      collection: CRYPTO_COLLECTION,
      id: KEYS_WBO,
    };
    return wbo;
  },

  /**
   * Create a WBO for the current keys.
   */
  asWBO() {
    return this._makeWBO(this._collections, this._default);
  },

  /**
   * Compute a new default key, and new keys for any specified collections.
   */
  async newKeys(collections) {
    let newDefaultKeyBundle = await this.newDefaultKeyBundle();

    let newColls = {};
    if (collections) {
      for (let c of collections) {
        let b = new BulkKeyBundle(c);
        await b.generateRandom();
        newColls[c] = b;
      }
    }
    return [newDefaultKeyBundle, newColls];
  },

  /**
   * Generates new keys, but does not replace our local copy. Use this to
   * verify an upload before storing.
   */
  async generateNewKeysWBO(collections) {
    let newDefaultKey, newColls;
    [newDefaultKey, newColls] = await this.newKeys(collections);

    return this._makeWBO(newColls, newDefaultKey);
  },

  /**
   * Create a new default key.
   *
   * @returns {BulkKeyBundle}
   */
  async newDefaultKeyBundle() {
    const key = new BulkKeyBundle(DEFAULT_KEYBUNDLE_NAME);
    await key.generateRandom();
    return key;
  },

  /**
   * Create a new default key and store it as this._default, since without one you cannot use setContents.
   */
  async generateDefaultKey() {
    this._default = await this.newDefaultKeyBundle();
  },

  /**
   * Return true if keys are already present for each of the given
   * collections.
   */
  hasKeysFor(collections) {
    // We can't use filter() here because sometimes collections is an iterator.
    for (let collection of collections) {
      if (!this._collections[collection]) {
        return false;
      }
    }
    return true;
  },

  /**
   * Return a new CollectionKeyManager that has keys for each of the
   * given collections (creating new ones for collections where we
   * don't already have keys).
   */
  async ensureKeysFor(collections) {
    const newKeys = Object.assign({}, this._collections);
    for (let c of collections) {
      if (newKeys[c]) {
        continue; // don't replace existing keys
      }

      const b = new BulkKeyBundle(c);
      await b.generateRandom();
      newKeys[c] = b;
    }
    return new CollectionKeyManager(this.lastModified, this._default, newKeys);
  },

  // Take the fetched info/collections WBO, checking the change
  // time of the crypto collection.
  updateNeeded(info_collections) {
    this._log.info(
      "Testing for updateNeeded. Last modified: " + this.lastModified
    );

    // No local record of modification time? Need an update.
    if (!this.lastModified) {
      return true;
    }

    // No keys on the server? We need an update, though our
    // update handling will be a little more drastic...
    if (!(CRYPTO_COLLECTION in info_collections)) {
      return true;
    }

    // Otherwise, we need an update if our modification time is stale.
    return info_collections[CRYPTO_COLLECTION] > this.lastModified;
  },

  //
  // Set our keys and modified time to the values fetched from the server.
  // Returns one of three values:
  //
  // * If the default key was modified, return true.
  // * If the default key was not modified, but per-collection keys were,
  //   return an array of such.
  // * Otherwise, return false -- we were up-to-date.
  //
  setContents: function setContents(payload, modified) {
    let self = this;

    this._log.info(
      "Setting collection keys contents. Our last modified: " +
        this.lastModified +
        ", input modified: " +
        modified +
        "."
    );

    if (!payload) {
      throw new Error("No payload in CollectionKeyManager.setContents().");
    }

    if (!payload.default) {
      this._log.warn("No downloaded default key: this should not occur.");
      this._log.warn("Not clearing local keys.");
      throw new Error(
        "No default key in CollectionKeyManager.setContents(). Cannot proceed."
      );
    }

    // Process the incoming default key.
    let b = new BulkKeyBundle(DEFAULT_KEYBUNDLE_NAME);
    b.keyPairB64 = payload.default;
    let newDefault = b;

    // Process the incoming collections.
    let newCollections = {};
    if ("collections" in payload) {
      this._log.info("Processing downloaded per-collection keys.");
      let colls = payload.collections;
      for (let k in colls) {
        let v = colls[k];
        if (v) {
          let keyObj = new BulkKeyBundle(k);
          keyObj.keyPairB64 = v;
          newCollections[k] = keyObj;
        }
      }
    }

    // Check to see if these are already our keys.
    let sameDefault = this._default && this._default.equals(newDefault);
    let collComparison = this._compareKeyBundleCollections(
      newCollections,
      this._collections
    );
    let sameColls = collComparison.same;

    if (sameDefault && sameColls) {
      self._log.info("New keys are the same as our old keys!");
      if (modified) {
        self._log.info("Bumped local modified time.");
        self.lastModified = modified;
      }
      return false;
    }

    // Make sure things are nice and tidy before we set.
    this.clear();

    this._log.info("Saving downloaded keys.");
    this._default = newDefault;
    this._collections = newCollections;

    // Always trust the server.
    if (modified) {
      self._log.info("Bumping last modified to " + modified);
      self.lastModified = modified;
    }

    return sameDefault ? collComparison.changed : true;
  },

  async updateContents(syncKeyBundle, storage_keys) {
    let log = this._log;
    log.info("Updating collection keys...");

    // storage_keys is a WBO, fetched from storage/crypto/keys.
    // Its payload is the default key, and a map of collections to keys.
    // We lazily compute the key objects from the strings we're given.

    let payload;
    try {
      payload = await storage_keys.decrypt(syncKeyBundle);
    } catch (ex) {
      log.warn("Got exception decrypting storage keys with sync key.", ex);
      log.info("Aborting updateContents. Rethrowing.");
      throw ex;
    }

    let r = this.setContents(payload, storage_keys.modified);
    log.info("Collection keys updated.");
    return r;
  },
};

export function Collection(uri, recordObj, service) {
  if (!service) {
    throw new Error("Collection constructor requires a service.");
  }

  Resource.call(this, uri);

  // This is a bit hacky, but gets the job done.
  let res = service.resource(uri);
  this.authenticator = res.authenticator;

  this._recordObj = recordObj;
  this._service = service;

  this._full = false;
  this._ids = null;
  this._limit = 0;
  this._older = 0;
  this._newer = 0;
  this._data = [];
  // optional members used by batch upload operations.
  this._batch = null;
  this._commit = false;
  // Used for batch download operations -- note that this is explicitly an
  // opaque value and not (necessarily) a number.
  this._offset = null;
}

Collection.prototype = {
  _logName: "Sync.Collection",

  _rebuildURL: function Coll__rebuildURL() {
    // XXX should consider what happens if it's not a URL...
    this.uri.QueryInterface(Ci.nsIURL);

    let args = [];
    if (this.older) {
      args.push("older=" + this.older);
    }
    if (this.newer) {
      args.push("newer=" + this.newer);
    }
    if (this.full) {
      args.push("full=1");
    }
    if (this.sort) {
      args.push("sort=" + this.sort);
    }
    if (this.ids != null) {
      args.push("ids=" + this.ids);
    }
    if (this.limit > 0 && this.limit != Infinity) {
      args.push("limit=" + this.limit);
    }
    if (this._batch) {
      args.push("batch=" + encodeURIComponent(this._batch));
    }
    if (this._commit) {
      args.push("commit=true");
    }
    if (this._offset) {
      args.push("offset=" + encodeURIComponent(this._offset));
    }

    this.uri = this.uri
      .mutate()
      .setQuery(args.length ? "?" + args.join("&") : "")
      .finalize();
  },

  // get full items
  get full() {
    return this._full;
  },
  set full(value) {
    this._full = value;
    this._rebuildURL();
  },

  // Apply the action to a certain set of ids
  get ids() {
    return this._ids;
  },
  set ids(value) {
    this._ids = value;
    this._rebuildURL();
  },

  // Limit how many records to get
  get limit() {
    return this._limit;
  },
  set limit(value) {
    this._limit = value;
    this._rebuildURL();
  },

  // get only items modified before some date
  get older() {
    return this._older;
  },
  set older(value) {
    this._older = value;
    this._rebuildURL();
  },

  // get only items modified since some date
  get newer() {
    return this._newer;
  },
  set newer(value) {
    this._newer = value;
    this._rebuildURL();
  },

  // get items sorted by some criteria. valid values:
  // oldest (oldest first)
  // newest (newest first)
  // index
  get sort() {
    return this._sort;
  },
  set sort(value) {
    if (value && value != "oldest" && value != "newest" && value != "index") {
      throw new TypeError(
        `Illegal value for sort: "${value}" (should be "oldest", "newest", or "index").`
      );
    }
    this._sort = value;
    this._rebuildURL();
  },

  get offset() {
    return this._offset;
  },
  set offset(value) {
    this._offset = value;
    this._rebuildURL();
  },

  // Set information about the batch for this request.
  get batch() {
    return this._batch;
  },
  set batch(value) {
    this._batch = value;
    this._rebuildURL();
  },

  get commit() {
    return this._commit;
  },
  set commit(value) {
    this._commit = value && true;
    this._rebuildURL();
  },

  // Similar to get(), but will page through the items `batchSize` at a time,
  // deferring calling the record handler until we've gotten them all.
  //
  // Returns the last response processed, and doesn't run the record handler
  // on any items if a non-success status is received while downloading the
  // records (or if a network error occurs).
  async getBatched(batchSize = DEFAULT_DOWNLOAD_BATCH_SIZE) {
    let totalLimit = Number(this.limit) || Infinity;
    if (batchSize <= 0 || batchSize >= totalLimit) {
      throw new Error("Invalid batch size");
    }

    if (!this.full) {
      throw new Error("getBatched is unimplemented for guid-only GETs");
    }

    // _onComplete and _onProgress are reset after each `get` by Resource.
    let { _onComplete, _onProgress } = this;
    let recordBuffer = [];
    let resp;
    try {
      let lastModifiedTime;
      this.limit = batchSize;

      do {
        this._onProgress = _onProgress;
        this._onComplete = _onComplete;
        if (batchSize + recordBuffer.length > totalLimit) {
          this.limit = totalLimit - recordBuffer.length;
        }
        this._log.trace("Performing batched GET", {
          limit: this.limit,
          offset: this.offset,
        });
        // Actually perform the request
        resp = await this.get();
        if (!resp.success) {
          recordBuffer = [];
          break;
        }
        for (let json of resp.obj) {
          let record = new this._recordObj();
          record.deserialize(json);
          recordBuffer.push(record);
        }

        // Initialize last modified, or check that something broken isn't happening.
        let lastModified = resp.headers["x-last-modified"];
        if (!lastModifiedTime) {
          lastModifiedTime = lastModified;
          this.setHeader("X-If-Unmodified-Since", lastModified);
        } else if (lastModified != lastModifiedTime) {
          // Should be impossible -- We'd get a 412 in this case.
          throw new Error(
            "X-Last-Modified changed in the middle of a download batch! " +
              `${lastModified} => ${lastModifiedTime}`
          );
        }

        // If this is missing, we're finished.
        this.offset = resp.headers["x-weave-next-offset"];
      } while (this.offset && totalLimit > recordBuffer.length);
    } finally {
      // Ensure we undo any temporary state so that subsequent calls to get()
      // or getBatched() work properly. We do this before calling the record
      // handler so that we can more convincingly pretend to be a normal get()
      // call. Note: we're resetting these to the values they had before this
      // function was called.
      this._limit = totalLimit;
      this._offset = null;
      delete this._headers["x-if-unmodified-since"];
      this._rebuildURL();
    }
    return { response: resp, records: recordBuffer };
  },

  // This object only supports posting via the postQueue object.
  post() {
    throw new Error(
      "Don't directly post to a collection - use newPostQueue instead"
    );
  },

  newPostQueue(log, timestamp, postCallback) {
    let poster = (data, headers, batch, commit) => {
      this.batch = batch;
      this.commit = commit;
      for (let [header, value] of headers) {
        this.setHeader(header, value);
      }
      return Resource.prototype.post.call(this, data);
    };
    return new PostQueue(
      poster,
      timestamp,
      this._service.serverConfiguration || {},
      log,
      postCallback
    );
  },
};

Object.setPrototypeOf(Collection.prototype, Resource.prototype);

// These are limits for requests provided by the server at the
// info/configuration endpoint -- server documentation is available here:
// http://moz-services-docs.readthedocs.io/en/latest/storage/apis-1.5.html#api-instructions
//
// All are optional, however we synthesize (non-infinite) default values for the
// "max_request_bytes" and "max_record_payload_bytes" options. For the others,
// we ignore them (we treat the limit is infinite) if they're missing.
//
// These are also the only ones that all servers (even batching-disabled
// servers) should support, at least once this sync-serverstorage patch is
// everywhere https://github.com/mozilla-services/server-syncstorage/pull/74
//
// Batching enabled servers also limit the amount of payload data and number
// of and records we can send in a single post as well as in the whole batch.
// Note that the byte limits for these there are just with respect to the
// *payload* data, e.g. the data appearing in the payload property (a
// string) of the object.
//
// Note that in practice, these limits should be sensible, but the code makes
// no assumptions about this. If we hit any of the limits, we perform the
// corresponding action (e.g. submit a request, possibly committing the
// current batch).
const DefaultPostQueueConfig = Object.freeze({
  // Number of total bytes allowed in a request
  max_request_bytes: 260 * 1024,

  // Maximum number of bytes allowed in the "payload" property of a record.
  max_record_payload_bytes: 256 * 1024,

  // The limit for how many bytes worth of data appearing in "payload"
  // properties are allowed in a single post.
  max_post_bytes: Infinity,

  // The limit for the number of records allowed in a single post.
  max_post_records: Infinity,

  // The limit for how many bytes worth of data appearing in "payload"
  // properties are allowed in a batch. (Same as max_post_bytes, but for
  // batches).
  max_total_bytes: Infinity,

  // The limit for the number of records allowed in a single post. (Same
  // as max_post_records, but for batches).
  max_total_records: Infinity,
});

// Manages a pair of (byte, count) limits for a PostQueue, such as
// (max_post_bytes, max_post_records) or (max_total_bytes, max_total_records).
class LimitTracker {
  constructor(maxBytes, maxRecords) {
    this.maxBytes = maxBytes;
    this.maxRecords = maxRecords;
    this.curBytes = 0;
    this.curRecords = 0;
  }

  clear() {
    this.curBytes = 0;
    this.curRecords = 0;
  }

  canAddRecord(payloadSize) {
    // The record counts are inclusive, but depending on the version of the
    // server, the byte counts may or may not be inclusive (See
    // https://github.com/mozilla-services/server-syncstorage/issues/73).
    return (
      this.curRecords + 1 <= this.maxRecords &&
      this.curBytes + payloadSize < this.maxBytes
    );
  }

  canNeverAdd(recordSize) {
    return recordSize >= this.maxBytes;
  }

  didAddRecord(recordSize) {
    if (!this.canAddRecord(recordSize)) {
      // This is a bug, caller is expected to call canAddRecord first.
      throw new Error(
        "LimitTracker.canAddRecord must be checked before adding record"
      );
    }
    this.curRecords += 1;
    this.curBytes += recordSize;
  }
}

/* A helper to manage the posting of records while respecting the various
   size limits.

   This supports the concept of a server-side "batch". The general idea is:
   * We queue as many records as allowed in memory, then make a single POST.
   * This first POST (optionally) gives us a batch ID, which we use for
     all subsequent posts, until...
   * At some point we hit a batch-maximum, and jump through a few hoops to
     commit the current batch (ie, all previous POSTs) and start a new one.
   * Eventually commit the final batch.

  In most cases we expect there to be exactly 1 batch consisting of possibly
  multiple POSTs.
*/
export function PostQueue(poster, timestamp, serverConfig, log, postCallback) {
  // The "post" function we should use when it comes time to do the post.
  this.poster = poster;
  this.log = log;

  let config = Object.assign({}, DefaultPostQueueConfig, serverConfig);

  if (!serverConfig.max_request_bytes && serverConfig.max_post_bytes) {
    // Use max_post_bytes for max_request_bytes if it's missing. Only needed
    // until server-syncstorage/pull/74 is everywhere, and even then it's
    // unnecessary if the server limits are configured sanely (there's no
    // guarantee of -- at least before that is fully deployed)
    config.max_request_bytes = serverConfig.max_post_bytes;
  }

  this.log.trace("new PostQueue config (after defaults): ", config);

  // The callback we make with the response when we do get around to making the
  // post (which could be during any of the enqueue() calls or the final flush())
  // This callback may be called multiple times and must not add new items to
  // the queue.
  // The second argument passed to this callback is a boolean value that is true
  // if we're in the middle of a batch, and false if either the batch is
  // complete, or it's a post to a server that does not understand batching.
  this.postCallback = postCallback;

  // Tracks the count and combined payload size for the records we've queued
  // so far but are yet to POST.
  this.postLimits = new LimitTracker(
    config.max_post_bytes,
    config.max_post_records
  );

  // As above, but for the batch size.
  this.batchLimits = new LimitTracker(
    config.max_total_bytes,
    config.max_total_records
  );

  // Limit for the size of `this.queued` before we do a post.
  this.maxRequestBytes = config.max_request_bytes;

  // Limit for the size of incoming record payloads.
  this.maxPayloadBytes = config.max_record_payload_bytes;

  // The string where we are capturing the stringified version of the records
  // queued so far. It will always be invalid JSON as it is always missing the
  // closing bracket. It's also used to track whether or not we've gone past
  // maxRequestBytes.
  this.queued = "";

  // The ID of our current batch. Can be undefined (meaning we are yet to make
  // the first post of a patch, so don't know if we have a batch), null (meaning
  // we've made the first post but the server response indicated no batching
  // semantics), otherwise we have made the first post and it holds the batch ID
  // returned from the server.
  this.batchID = undefined;

  // Time used for X-If-Unmodified-Since -- should be the timestamp from the last GET.
  this.lastModified = timestamp;
}

PostQueue.prototype = {
  async enqueue(record) {
    // We want to ensure the record has a .toJSON() method defined - even
    // though JSON.stringify() would implicitly call it, the stringify might
    // still work even if it isn't defined, which isn't what we want.
    let jsonRepr = record.toJSON();
    if (!jsonRepr) {
      throw new Error(
        "You must only call this with objects that explicitly support JSON"
      );
    }

    let bytes = JSON.stringify(jsonRepr);

    // We use the payload size for the LimitTrackers, since that's what the
    // byte limits other than max_request_bytes refer to.
    let payloadLength = jsonRepr.payload.length;

    // The `+ 2` is to account for the 2-byte (maximum) overhead (one byte for
    // the leading comma or "[", which all records will have, and the other for
    // the final trailing "]", only present for the last record).
    let encodedLength = bytes.length + 2;

    // Check first if there's some limit that indicates we cannot ever enqueue
    // this record.
    let isTooBig =
      this.postLimits.canNeverAdd(payloadLength) ||
      this.batchLimits.canNeverAdd(payloadLength) ||
      encodedLength >= this.maxRequestBytes ||
      payloadLength >= this.maxPayloadBytes;

    if (isTooBig) {
      return {
        enqueued: false,
        error: new Error("Single record too large to submit to server"),
      };
    }

    let canPostRecord = this.postLimits.canAddRecord(payloadLength);
    let canBatchRecord = this.batchLimits.canAddRecord(payloadLength);
    let canSendRecord =
      this.queued.length + encodedLength < this.maxRequestBytes;

    if (!canPostRecord || !canBatchRecord || !canSendRecord) {
      this.log.trace("PostQueue flushing: ", {
        canPostRecord,
        canSendRecord,
        canBatchRecord,
      });
      // We need to write the queue out before handling this one, but we only
      // commit the batch (and thus start a new one) if the record couldn't fit
      // inside the batch.
      await this.flush(!canBatchRecord);
    }

    this.postLimits.didAddRecord(payloadLength);
    this.batchLimits.didAddRecord(payloadLength);

    // Either a ',' or a '[' depending on whether this is the first record.
    this.queued += this.queued.length ? "," : "[";
    this.queued += bytes;
    return { enqueued: true };
  },

  async flush(finalBatchPost) {
    if (!this.queued) {
      // nothing queued - we can't be in a batch, and something has gone very
      // bad if we think we are.
      if (this.batchID) {
        throw new Error(
          `Flush called when no queued records but we are in a batch ${this.batchID}`
        );
      }
      return;
    }
    // the batch query-param and headers we'll send.
    let batch;
    let headers = [];
    if (this.batchID === undefined) {
      // First commit in a (possible) batch.
      batch = "true";
    } else if (this.batchID) {
      // We have an existing batch.
      batch = this.batchID;
    } else {
      // Not the first post and we know we have no batch semantics.
      batch = null;
    }

    headers.push(["x-if-unmodified-since", this.lastModified]);

    let numQueued = this.postLimits.curRecords;
    this.log.info(
      `Posting ${numQueued} records of ${
        this.queued.length + 1
      } bytes with batch=${batch}`
    );
    let queued = this.queued + "]";
    if (finalBatchPost) {
      this.batchLimits.clear();
    }
    this.postLimits.clear();
    this.queued = "";
    let response = await this.poster(
      queued,
      headers,
      batch,
      !!(finalBatchPost && this.batchID !== null)
    );

    if (!response.success) {
      this.log.trace("Server error response during a batch", response);
      // not clear what we should do here - we expect the consumer of this to
      // abort by throwing in the postCallback below.
      await this.postCallback(this, response, !finalBatchPost);
      return;
    }

    if (finalBatchPost) {
      this.log.trace("Committed batch", this.batchID);
      this.batchID = undefined; // we are now in "first post for the batch" state.
      this.lastModified = response.headers["x-last-modified"];
      await this.postCallback(this, response, false);
      return;
    }

    if (response.status != 202) {
      if (this.batchID) {
        throw new Error(
          "Server responded non-202 success code while a batch was in progress"
        );
      }
      this.batchID = null; // no batch semantics are in place.
      this.lastModified = response.headers["x-last-modified"];
      await this.postCallback(this, response, false);
      return;
    }

    // this response is saying the server has batch semantics - we should
    // always have a batch ID in the response.
    let responseBatchID = response.obj.batch;
    this.log.trace("Server responsed 202 with batch", responseBatchID);
    if (!responseBatchID) {
      this.log.error(
        "Invalid server response: 202 without a batch ID",
        response
      );
      throw new Error("Invalid server response: 202 without a batch ID");
    }

    if (this.batchID === undefined) {
      this.batchID = responseBatchID;
      if (!this.lastModified) {
        this.lastModified = response.headers["x-last-modified"];
        if (!this.lastModified) {
          throw new Error("Batch response without x-last-modified");
        }
      }
    }

    if (this.batchID != responseBatchID) {
      throw new Error(
        `Invalid client/server batch state - client has ${this.batchID}, server has ${responseBatchID}`
      );
    }

    await this.postCallback(this, response, true);
  },
};
PK
!<�nAa a &modules/services-sync/resource.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { Log } from "resource://gre/modules/Log.sys.mjs";

import { Observers } from "resource://services-common/observers.sys.mjs";
import { CommonUtils } from "resource://services-common/utils.sys.mjs";
import { Utils } from "resource://services-sync/util.sys.mjs";
import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";

/* global AbortController */

/*
 * Resource represents a remote network resource, identified by a URI.
 * Create an instance like so:
 *
 *   let resource = new Resource("http://foobar.com/path/to/resource");
 *
 * The 'resource' object has the following methods to issue HTTP requests
 * of the corresponding HTTP methods:
 *
 *   get(callback)
 *   put(data, callback)
 *   post(data, callback)
 *   delete(callback)
 */
export function Resource(uri) {
  this._log = Log.repository.getLogger(this._logName);
  this._log.manageLevelFromPref("services.sync.log.logger.network.resources");
  this.uri = uri;
  this._headers = {};
}

// (static) Caches the latest server timestamp (X-Weave-Timestamp header).
Resource.serverTime = null;

XPCOMUtils.defineLazyPreferenceGetter(
  Resource,
  "SEND_VERSION_INFO",
  "services.sync.sendVersionInfo",
  true
);
Resource.prototype = {
  _logName: "Sync.Resource",

  /**
   * Callback to be invoked at request time to add authentication details.
   * If the callback returns a promise, it will be awaited upon.
   *
   * By default, a global authenticator is provided. If this is set, it will
   * be used instead of the global one.
   */
  authenticator: null,

  // Wait 5 minutes before killing a request.
  ABORT_TIMEOUT: 300000,

  // Headers to be included when making a request for the resource.
  // Note: Header names should be all lower case, there's no explicit
  // check for duplicates due to case!
  get headers() {
    return this._headers;
  },
  set headers(_) {
    throw new Error("headers can't be mutated directly. Please use setHeader.");
  },
  setHeader(header, value) {
    this._headers[header.toLowerCase()] = value;
  },

  // URI representing this resource.
  get uri() {
    return this._uri;
  },
  set uri(value) {
    if (typeof value == "string") {
      this._uri = CommonUtils.makeURI(value);
    } else {
      this._uri = value;
    }
  },

  // Get the string representation of the URI.
  get spec() {
    if (this._uri) {
      return this._uri.spec;
    }
    return null;
  },

  /**
   * @param {string} method HTTP method
   * @returns {Headers}
   */
  async _buildHeaders(method) {
    const headers = new Headers(this._headers);

    if (Resource.SEND_VERSION_INFO) {
      headers.append("user-agent", Utils.userAgent);
    }

    if (this.authenticator) {
      const result = await this.authenticator(this, method);
      if (result && result.headers) {
        for (const [k, v] of Object.entries(result.headers)) {
          headers.append(k.toLowerCase(), v);
        }
      }
    } else {
      this._log.debug("No authenticator found.");
    }

    // PUT and POST are treated differently because they have payload data.
    if (("PUT" == method || "POST" == method) && !headers.has("content-type")) {
      headers.append("content-type", "text/plain");
    }

    if (this._log.level <= Log.Level.Trace) {
      for (const [k, v] of headers) {
        if (k == "authorization" || k == "x-client-state") {
          this._log.trace(`HTTP Header ${k}: ***** (suppressed)`);
        } else {
          this._log.trace(`HTTP Header ${k}: ${v}`);
        }
      }
    }

    if (!headers.has("accept")) {
      headers.append("accept", "application/json;q=0.9,*/*;q=0.2");
    }

    return headers;
  },

  /**
   * @param {string} method HTTP method
   * @param {string} data HTTP body
   * @param {object} signal AbortSignal instance
   * @returns {Request}
   */
  async _createRequest(method, data, signal) {
    const headers = await this._buildHeaders(method);
    const init = {
      cache: "no-store", // No cache.
      headers,
      method,
      signal,
      mozErrors: true, // Return nsresult error codes instead of a generic
      // NetworkError when fetch rejects.
    };

    if (data) {
      if (!(typeof data == "string" || data instanceof String)) {
        data = JSON.stringify(data);
      }
      this._log.debug(`${method} Length: ${data.length}`);
      this._log.trace(`${method} Body: ${data}`);
      init.body = data;
    }
    return new Request(this.uri.spec, init);
  },

  /**
   * @param {string} method HTTP method
   * @param {string} [data] HTTP body
   * @returns {Response}
   */
  async _doRequest(method, data = null) {
    const controller = new AbortController();
    const request = await this._createRequest(method, data, controller.signal);
    const responsePromise = fetch(request); // Rejects on network failure.
    let didTimeout = false;
    const timeoutId = setTimeout(() => {
      didTimeout = true;
      this._log.error(
        `Request timed out after ${this.ABORT_TIMEOUT}ms. Aborting.`
      );
      controller.abort();
    }, this.ABORT_TIMEOUT);
    let response;
    try {
      response = await responsePromise;
    } catch (e) {
      this._log.warn(`${method} request to ${this.uri.spec} failed`, e);
      if (!didTimeout) {
        throw e;
      }
      throw Components.Exception(
        "Request aborted (timeout)",
        Cr.NS_ERROR_NET_TIMEOUT
      );
    } finally {
      clearTimeout(timeoutId);
    }
    return this._processResponse(response, method);
  },

  async _processResponse(response, method) {
    const data = await response.text();
    this._logResponse(response, method, data);
    this._processResponseHeaders(response);

    const ret = {
      data,
      url: response.url,
      status: response.status,
      success: response.ok,
      headers: {},
    };
    for (const [k, v] of response.headers) {
      ret.headers[k] = v;
    }

    // Make a lazy getter to convert the json response into an object.
    // Note that this can cause a parse error to be thrown far away from the
    // actual fetch, so be warned!
    ChromeUtils.defineLazyGetter(ret, "obj", () => {
      try {
        return JSON.parse(ret.data);
      } catch (ex) {
        this._log.warn("Got exception parsing response body", ex);
        // Stringify to avoid possibly printing non-printable characters.
        this._log.debug(
          "Parse fail: Response body starts",
          (ret.data + "").slice(0, 100)
        );
        throw ex;
      }
    });

    return ret;
  },

  _logResponse(response, method, data) {
    const { status, ok: success, url } = response;

    // Log the status of the request.
    this._log.debug(
      `${method} ${success ? "success" : "fail"} ${status} ${url}`
    );

    // Additionally give the full response body when Trace logging.
    if (this._log.level <= Log.Level.Trace) {
      this._log.trace(`${method} body`, data);
    }

    if (!success) {
      this._log.warn(
        `${method} request to ${url} failed with status ${status}`
      );
    }
  },

  _processResponseHeaders({ headers, ok: success }) {
    if (headers.has("x-weave-timestamp")) {
      Resource.serverTime = parseFloat(headers.get("x-weave-timestamp"));
    }
    // This is a server-side safety valve to allow slowing down
    // clients without hurting performance.
    if (headers.has("x-weave-backoff")) {
      let backoff = headers.get("x-weave-backoff");
      this._log.debug(`Got X-Weave-Backoff: ${backoff}`);
      Observers.notify("weave:service:backoff:interval", parseInt(backoff, 10));
    }

    if (success && headers.has("x-weave-quota-remaining")) {
      Observers.notify(
        "weave:service:quota:remaining",
        parseInt(headers.get("x-weave-quota-remaining"), 10)
      );
    }
  },

  get() {
    return this._doRequest("GET");
  },

  put(data) {
    return this._doRequest("PUT", data);
  },

  post(data) {
    return this._doRequest("POST", data);
  },

  delete() {
    return this._doRequest("DELETE");
  },
};
PK
!<�H|�����%modules/services-sync/service.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const CRYPTO_COLLECTION = "crypto";
const KEYS_WBO = "keys";

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { Log } from "resource://gre/modules/Log.sys.mjs";

import { Async } from "resource://services-common/async.sys.mjs";
import { CommonUtils } from "resource://services-common/utils.sys.mjs";

import {
  CLIENT_NOT_CONFIGURED,
  CREDENTIALS_CHANGED,
  HMAC_EVENT_INTERVAL,
  LOGIN_FAILED,
  LOGIN_FAILED_INVALID_PASSPHRASE,
  LOGIN_FAILED_NETWORK_ERROR,
  LOGIN_FAILED_NO_PASSPHRASE,
  LOGIN_FAILED_NO_USERNAME,
  LOGIN_FAILED_SERVER_ERROR,
  LOGIN_SUCCEEDED,
  MASTER_PASSWORD_LOCKED,
  METARECORD_DOWNLOAD_FAIL,
  NO_SYNC_NODE_FOUND,
  PREFS_BRANCH,
  STATUS_DISABLED,
  STATUS_OK,
  STORAGE_VERSION,
  VERSION_OUT_OF_DATE,
  WEAVE_VERSION,
  kFirefoxShuttingDown,
  kFirstSyncChoiceNotMade,
  kSyncBackoffNotMet,
  kSyncMasterPasswordLocked,
  kSyncNetworkOffline,
  kSyncNotConfigured,
  kSyncWeaveDisabled,
} from "resource://services-sync/constants.sys.mjs";

import { EngineManager } from "resource://services-sync/engines.sys.mjs";
import { ClientEngine } from "resource://services-sync/engines/clients.sys.mjs";
import { Weave } from "resource://services-sync/main.sys.mjs";
import {
  ErrorHandler,
  SyncScheduler,
} from "resource://services-sync/policies.sys.mjs";
import {
  CollectionKeyManager,
  CryptoWrapper,
  RecordManager,
  WBORecord,
} from "resource://services-sync/record.sys.mjs";
import { Resource } from "resource://services-sync/resource.sys.mjs";
import { EngineSynchronizer } from "resource://services-sync/stages/enginesync.sys.mjs";
import { DeclinedEngines } from "resource://services-sync/stages/declined.sys.mjs";
import { Status } from "resource://services-sync/status.sys.mjs";

ChromeUtils.importESModule("resource://services-sync/telemetry.sys.mjs");
import { Svc, Utils } from "resource://services-sync/util.sys.mjs";

import { getFxAccountsSingleton } from "resource://gre/modules/FxAccounts.sys.mjs";

const fxAccounts = getFxAccountsSingleton();

function getEngineModules() {
  let result = {
    Addons: { module: "addons.sys.mjs", symbol: "AddonsEngine" },
    Password: { module: "passwords.sys.mjs", symbol: "PasswordEngine" },
    Prefs: { module: "prefs.sys.mjs", symbol: "PrefsEngine" },
  };
  if (AppConstants.MOZ_APP_NAME != "thunderbird") {
    result.Bookmarks = {
      module: "bookmarks.sys.mjs",
      symbol: "BookmarksEngine",
    };
    result.Form = { module: "forms.sys.mjs", symbol: "FormEngine" };
    result.History = { module: "history.sys.mjs", symbol: "HistoryEngine" };
    result.Tab = { module: "tabs.sys.mjs", symbol: "TabEngine" };
  }
  if (Svc.PrefBranch.getBoolPref("engine.addresses.available", false)) {
    result.Addresses = {
      module: "resource://autofill/FormAutofillSync.sys.mjs",
      symbol: "AddressesEngine",
    };
  }
  if (Svc.PrefBranch.getBoolPref("engine.creditcards.available", false)) {
    result.CreditCards = {
      module: "resource://autofill/FormAutofillSync.sys.mjs",
      symbol: "CreditCardsEngine",
    };
  }
  result["Extension-Storage"] = {
    module: "extension-storage.sys.mjs",
    controllingPref: "webextensions.storage.sync.kinto",
    whenTrue: "ExtensionStorageEngineKinto",
    whenFalse: "ExtensionStorageEngineBridge",
  };
  return result;
}

const lazy = {};

// A unique identifier for this browser session. Used for logging so
// we can easily see whether 2 logs are in the same browser session or
// after the browser restarted.
ChromeUtils.defineLazyGetter(lazy, "browserSessionID", Utils.makeGUID);

function Sync11Service() {
  this._notify = Utils.notify("weave:service:");
  Utils.defineLazyIDProperty(this, "syncID", "services.sync.client.syncID");
}
Sync11Service.prototype = {
  _lock: Utils.lock,
  _locked: false,
  _loggedIn: false,

  infoURL: null,
  storageURL: null,
  metaURL: null,
  cryptoKeyURL: null,
  // The cluster URL comes via the identity object, which in the FxA
  // world is ebbedded in the token returned from the token server.
  _clusterURL: null,

  get clusterURL() {
    return this._clusterURL || "";
  },
  set clusterURL(value) {
    if (value != null && typeof value != "string") {
      throw new Error("cluster must be a string, got " + typeof value);
    }
    this._clusterURL = value;
    this._updateCachedURLs();
  },

  get isLoggedIn() {
    return this._loggedIn;
  },

  get locked() {
    return this._locked;
  },
  lock: function lock() {
    if (this._locked) {
      return false;
    }
    this._locked = true;
    return true;
  },
  unlock: function unlock() {
    this._locked = false;
  },

  // A specialized variant of Utils.catch.
  // This provides a more informative error message when we're already syncing:
  // see Bug 616568.
  _catch(func) {
    function lockExceptions(ex) {
      if (Utils.isLockException(ex)) {
        // This only happens if we're syncing already.
        this._log.info("Cannot start sync: already syncing?");
      }
    }

    return Utils.catch.call(this, func, lockExceptions);
  },

  get userBaseURL() {
    // The user URL is the cluster URL.
    return this.clusterURL;
  },

  _updateCachedURLs: function _updateCachedURLs() {
    // Nothing to cache yet if we don't have the building blocks
    if (!this.clusterURL) {
      // Also reset all other URLs used by Sync to ensure we aren't accidentally
      // using one cached earlier - if there's no cluster URL any cached ones
      // are invalid.
      this.infoURL = undefined;
      this.storageURL = undefined;
      this.metaURL = undefined;
      this.cryptoKeysURL = undefined;
      return;
    }

    this._log.debug(
      "Caching URLs under storage user base: " + this.userBaseURL
    );

    // Generate and cache various URLs under the storage API for this user
    this.infoURL = this.userBaseURL + "info/collections";
    this.storageURL = this.userBaseURL + "storage/";
    this.metaURL = this.storageURL + "meta/global";
    this.cryptoKeysURL = this.storageURL + CRYPTO_COLLECTION + "/" + KEYS_WBO;
  },

  _checkCrypto: function _checkCrypto() {
    let ok = false;

    try {
      let iv = Weave.Crypto.generateRandomIV();
      if (iv.length == 24) {
        ok = true;
      }
    } catch (e) {
      this._log.debug("Crypto check failed: " + e);
    }

    return ok;
  },

  /**
   * Here is a disgusting yet reasonable way of handling HMAC errors deep in
   * the guts of Sync. The astute reader will note that this is a hacky way of
   * implementing something like continuable conditions.
   *
   * A handler function is glued to each engine. If the engine discovers an
   * HMAC failure, we fetch keys from the server and update our keys, just as
   * we would on startup.
   *
   * If our key collection changed, we signal to the engine (via our return
   * value) that it should retry decryption.
   *
   * If our key collection did not change, it means that we already had the
   * correct keys... and thus a different client has the wrong ones. Reupload
   * the bundle that we fetched, which will bump the modified time on the
   * server and (we hope) prompt a broken client to fix itself.
   *
   * We keep track of the time at which we last applied this reasoning, because
   * thrashing doesn't solve anything. We keep a reasonable interval between
   * these remedial actions.
   */
  lastHMACEvent: 0,

  /*
   * Returns whether to try again.
   */
  async handleHMACEvent() {
    let now = Date.now();

    // Leave a sizable delay between HMAC recovery attempts. This gives us
    // time for another client to fix themselves if we touch the record.
    if (now - this.lastHMACEvent < HMAC_EVENT_INTERVAL) {
      return false;
    }

    this._log.info(
      "Bad HMAC event detected. Attempting recovery " +
        "or signaling to other clients."
    );

    // Set the last handled time so that we don't act again.
    this.lastHMACEvent = now;

    // Fetch keys.
    let cryptoKeys = new CryptoWrapper(CRYPTO_COLLECTION, KEYS_WBO);
    try {
      let cryptoResp = (
        await cryptoKeys.fetch(this.resource(this.cryptoKeysURL))
      ).response;

      // Save out the ciphertext for when we reupload. If there's a bug in
      // CollectionKeyManager, this will prevent us from uploading junk.
      let cipherText = cryptoKeys.ciphertext;

      if (!cryptoResp.success) {
        this._log.warn("Failed to download keys.");
        return false;
      }

      let keysChanged = await this.handleFetchedKeys(
        this.identity.syncKeyBundle,
        cryptoKeys,
        true
      );
      if (keysChanged) {
        // Did they change? If so, carry on.
        this._log.info("Suggesting retry.");
        return true; // Try again.
      }

      // If not, reupload them and continue the current sync.
      cryptoKeys.ciphertext = cipherText;
      cryptoKeys.cleartext = null;

      let uploadResp = await this._uploadCryptoKeys(
        cryptoKeys,
        cryptoResp.obj.modified
      );
      if (uploadResp.success) {
        this._log.info("Successfully re-uploaded keys. Continuing sync.");
      } else {
        this._log.warn(
          "Got error response re-uploading keys. " +
            "Continuing sync; let's try again later."
        );
      }

      return false; // Don't try again: same keys.
    } catch (ex) {
      this._log.warn(
        "Got exception fetching and handling crypto keys. " +
          "Will try again later.",
        ex
      );
      return false;
    }
  },

  async handleFetchedKeys(syncKey, cryptoKeys, skipReset) {
    // Don't want to wipe if we're just starting up!
    let wasBlank = this.collectionKeys.isClear;
    let keysChanged = await this.collectionKeys.updateContents(
      syncKey,
      cryptoKeys
    );

    if (keysChanged && !wasBlank) {
      this._log.debug("Keys changed: " + JSON.stringify(keysChanged));

      if (!skipReset) {
        this._log.info("Resetting client to reflect key change.");

        if (keysChanged.length) {
          // Collection keys only. Reset individual engines.
          await this.resetClient(keysChanged);
        } else {
          // Default key changed: wipe it all.
          await this.resetClient();
        }

        this._log.info("Downloaded new keys, client reset. Proceeding.");
      }
      return true;
    }
    return false;
  },

  /**
   * Prepare to initialize the rest of Weave after waiting a little bit
   */
  async onStartup() {
    this.status = Status;
    this.identity = Status._authManager;
    this.collectionKeys = new CollectionKeyManager();

    this.scheduler = new SyncScheduler(this);
    this.errorHandler = new ErrorHandler(this);

    this._log = Log.repository.getLogger("Sync.Service");
    this._log.manageLevelFromPref("services.sync.log.logger.service.main");

    this._log.info("Loading Weave " + WEAVE_VERSION);

    this.recordManager = new RecordManager(this);

    this.enabled = true;

    await this._registerEngines();

    let ua = Cc["@mozilla.org/network/protocol;1?name=http"].getService(
      Ci.nsIHttpProtocolHandler
    ).userAgent;
    this._log.info(ua);

    if (!this._checkCrypto()) {
      this.enabled = false;
      this._log.info(
        "Could not load the Weave crypto component. Disabling " +
          "Weave, since it will not work correctly."
      );
    }

    Svc.Obs.add("weave:service:setup-complete", this);
    Svc.Obs.add("sync:collection_changed", this); // Pulled from FxAccountsCommon
    Svc.Obs.add("fxaccounts:device_disconnected", this);
    Services.prefs.addObserver(PREFS_BRANCH + "engine.", this);

    if (!this.enabled) {
      this._log.info("Firefox Sync disabled.");
    }

    this._updateCachedURLs();

    let status = this._checkSetup();
    if (status != STATUS_DISABLED && status != CLIENT_NOT_CONFIGURED) {
      this._startTracking();
    }

    // Send an event now that Weave service is ready.  We don't do this
    // synchronously so that observers can import this module before
    // registering an observer.
    CommonUtils.nextTick(() => {
      this.status.ready = true;

      // UI code uses the flag on the XPCOM service so it doesn't have
      // to load a bunch of modules.
      let xps = Cc["@mozilla.org/weave/service;1"].getService(
        Ci.nsISupports
      ).wrappedJSObject;
      xps.ready = true;

      Svc.Obs.notify("weave:service:ready");
    });
  },

  _checkSetup: function _checkSetup() {
    if (!this.enabled) {
      return (this.status.service = STATUS_DISABLED);
    }
    return this.status.checkSetup();
  },

  /**
   * Register the built-in engines for certain applications
   */
  async _registerEngines() {
    this.engineManager = new EngineManager(this);

    let engineModules = getEngineModules();

    let engines = [];
    // We allow a pref, which has no default value, to limit the engines
    // which are registered. We expect only tests will use this.
    if (
      Svc.PrefBranch.getPrefType("registerEngines") !=
      Ci.nsIPrefBranch.PREF_INVALID
    ) {
      engines = Svc.PrefBranch.getStringPref("registerEngines").split(",");
      this._log.info("Registering custom set of engines", engines);
    } else {
      // default is all engines.
      engines = Object.keys(engineModules);
    }

    let declined = [];
    let pref = Svc.PrefBranch.getStringPref("declinedEngines", null);
    if (pref) {
      declined = pref.split(",");
    }

    let clientsEngine = new ClientEngine(this);
    // Ideally clientsEngine should not exist
    // (or be a promise that calls initialize() before returning the engine)
    await clientsEngine.initialize();
    this.clientsEngine = clientsEngine;

    for (let name of engines) {
      if (!(name in engineModules)) {
        this._log.info("Do not know about engine: " + name);
        continue;
      }
      let modInfo = engineModules[name];
      if (!modInfo.module.includes(":")) {
        modInfo.module = "resource://services-sync/engines/" + modInfo.module;
      }
      try {
        let ns = ChromeUtils.importESModule(modInfo.module);
        if (modInfo.symbol) {
          let symbol = modInfo.symbol;
          if (!(symbol in ns)) {
            this._log.warn(
              "Could not find exported engine instance: " + symbol
            );
            continue;
          }
          await this.engineManager.register(ns[symbol]);
        } else {
          let { whenTrue, whenFalse, controllingPref } = modInfo;
          if (!(whenTrue in ns) || !(whenFalse in ns)) {
            this._log.warn("Could not find all exported engine instances", {
              whenTrue,
              whenFalse,
            });
            continue;
          }
          await this.engineManager.registerAlternatives(
            name.toLowerCase(),
            controllingPref,
            ns[whenTrue],
            ns[whenFalse]
          );
        }
      } catch (ex) {
        this._log.warn("Could not register engine " + name, ex);
      }
    }

    this.engineManager.setDeclined(declined);
  },

  /**
   * This method updates the local engines state from an existing meta/global
   * when Sync is disabled.
   * Running this code if sync is enabled would end up in very weird results
   * (but we're nice and we check before doing anything!).
   */
  async updateLocalEnginesState() {
    await this.promiseInitialized;

    // Sanity check, this method is not meant to be run if Sync is enabled!
    if (Svc.PrefBranch.getStringPref("username", "")) {
      throw new Error("Sync is enabled!");
    }

    // For historical reasons the behaviour of setCluster() is bizarre,
    // so just check what we care about - the meta URL.
    if (!this.metaURL) {
      await this.identity.setCluster();
      if (!this.metaURL) {
        this._log.warn("Could not find a cluster.");
        return;
      }
    }
    // Clear the cache so we always fetch the latest meta/global.
    this.recordManager.clearCache();
    let meta = await this.recordManager.get(this.metaURL);
    if (!meta) {
      this._log.info("Meta record is null, aborting engine state update.");
      return;
    }
    const declinedEngines = meta.payload.declined;
    const allEngines = this.engineManager.getAll().map(e => e.name);
    // We don't want our observer of the enabled prefs to treat the change as
    // a user-change, otherwise we will do the wrong thing with declined etc.
    this._ignorePrefObserver = true;
    try {
      for (const engine of allEngines) {
        Svc.PrefBranch.setBoolPref(
          `engine.${engine}`,
          !declinedEngines.includes(engine)
        );
      }
    } finally {
      this._ignorePrefObserver = false;
    }
  },

  QueryInterface: ChromeUtils.generateQI([
    "nsIObserver",
    "nsISupportsWeakReference",
  ]),

  observe(subject, topic, data) {
    switch (topic) {
      // Ideally this observer should be in the SyncScheduler, but it would require
      // some work to know about the sync specific engines. We should move this there once it does.
      case "sync:collection_changed":
        // We check if we're running TPS here to avoid TPS failing because it
        // couldn't get to get the sync lock, due to us currently syncing the
        // clients engine.
        if (
          data.includes("clients") &&
          !Svc.PrefBranch.getBoolPref("testing.tps", false)
        ) {
          // Sync in the background (it's fine not to wait on the returned promise
          // because sync() has a lock).
          // [] = clients collection only
          this.sync({ why: "collection_changed", engines: [] }).catch(e => {
            this._log.error(e);
          });
        }
        break;
      case "fxaccounts:device_disconnected":
        data = JSON.parse(data);
        if (!data.isLocalDevice) {
          // Refresh the known stale clients list in the background.
          this.clientsEngine.updateKnownStaleClients().catch(e => {
            this._log.error(e);
          });
        }
        break;
      case "weave:service:setup-complete":
        let status = this._checkSetup();
        if (status != STATUS_DISABLED && status != CLIENT_NOT_CONFIGURED) {
          this._startTracking();
        }
        break;
      case "nsPref:changed":
        if (this._ignorePrefObserver) {
          return;
        }
        const engine = data.slice((PREFS_BRANCH + "engine.").length);
        if (engine.includes(".")) {
          // A sub-preference of the engine was changed. For example
          // `services.sync.engine.bookmarks.validation.percentageChance`.
          return;
        }
        this._handleEngineStatusChanged(engine);
        break;
    }
  },

  _handleEngineStatusChanged(engine) {
    this._log.trace("Status for " + engine + " engine changed.");
    if (Svc.PrefBranch.getBoolPref("engineStatusChanged." + engine, false)) {
      // The enabled status being changed back to what it was before.
      Svc.PrefBranch.clearUserPref("engineStatusChanged." + engine);
    } else {
      // Remember that the engine status changed locally until the next sync.
      Svc.PrefBranch.setBoolPref("engineStatusChanged." + engine, true);
    }
  },

  _startTracking() {
    const engines = [this.clientsEngine, ...this.engineManager.getAll()];
    for (let engine of engines) {
      try {
        engine.startTracking();
      } catch (e) {
        this._log.error(`Could not start ${engine.name} engine tracker`, e);
      }
    }
    // This is for TPS. We should try to do better.
    Svc.Obs.notify("weave:service:tracking-started");
  },

  async _stopTracking() {
    const engines = [this.clientsEngine, ...this.engineManager.getAll()];
    for (let engine of engines) {
      try {
        await engine.stopTracking();
      } catch (e) {
        this._log.error(`Could not stop ${engine.name} engine tracker`, e);
      }
    }
    Svc.Obs.notify("weave:service:tracking-stopped");
  },

  /**
   * Obtain a Resource instance with authentication credentials.
   */
  resource: function resource(url) {
    let res = new Resource(url);
    res.authenticator = this.identity.getResourceAuthenticator();

    return res;
  },

  /**
   * Perform the info fetch as part of a login or key fetch, or
   * inside engine sync.
   */
  async _fetchInfo(url) {
    let infoURL = url || this.infoURL;

    this._log.trace("In _fetchInfo: " + infoURL);
    let info;
    try {
      info = await this.resource(infoURL).get();
    } catch (ex) {
      this.errorHandler.checkServerError(ex);
      throw ex;
    }

    // Always check for errors.
    this.errorHandler.checkServerError(info);
    if (!info.success) {
      this._log.error("Aborting sync: failed to get collections.");
      throw info;
    }
    return info;
  },

  async verifyAndFetchSymmetricKeys(infoResponse) {
    this._log.debug(
      "Fetching and verifying -- or generating -- symmetric keys."
    );

    let syncKeyBundle = this.identity.syncKeyBundle;
    if (!syncKeyBundle) {
      this.status.login = LOGIN_FAILED_NO_PASSPHRASE;
      this.status.sync = CREDENTIALS_CHANGED;
      return false;
    }

    try {
      if (!infoResponse) {
        infoResponse = await this._fetchInfo(); // Will throw an exception on failure.
      }

      // This only applies when the server is already at version 4.
      if (infoResponse.status != 200) {
        this._log.warn(
          "info/collections returned non-200 response. Failing key fetch."
        );
        this.status.login = LOGIN_FAILED_SERVER_ERROR;
        this.errorHandler.checkServerError(infoResponse);
        return false;
      }

      let infoCollections = infoResponse.obj;

      this._log.info(
        "Testing info/collections: " + JSON.stringify(infoCollections)
      );

      if (this.collectionKeys.updateNeeded(infoCollections)) {
        this._log.info("collection keys reports that a key update is needed.");

        // Don't always set to CREDENTIALS_CHANGED -- we will probably take care of this.

        // Fetch storage/crypto/keys.
        let cryptoKeys;

        if (infoCollections && CRYPTO_COLLECTION in infoCollections) {
          try {
            cryptoKeys = new CryptoWrapper(CRYPTO_COLLECTION, KEYS_WBO);
            let cryptoResp = (
              await cryptoKeys.fetch(this.resource(this.cryptoKeysURL))
            ).response;

            if (cryptoResp.success) {
              await this.handleFetchedKeys(syncKeyBundle, cryptoKeys);
              return true;
            } else if (cryptoResp.status == 404) {
              // On failure, ask to generate new keys and upload them.
              // Fall through to the behavior below.
              this._log.warn(
                "Got 404 for crypto/keys, but 'crypto' in info/collections. Regenerating."
              );
              cryptoKeys = null;
            } else {
              // Some other problem.
              this.status.login = LOGIN_FAILED_SERVER_ERROR;
              this.errorHandler.checkServerError(cryptoResp);
              this._log.warn(
                "Got status " + cryptoResp.status + " fetching crypto keys."
              );
              return false;
            }
          } catch (ex) {
            this._log.warn("Got exception fetching cryptoKeys.", ex);
            // TODO: Um, what exceptions might we get here? Should we re-throw any?

            // One kind of exception: HMAC failure.
            if (Utils.isHMACMismatch(ex)) {
              this.status.login = LOGIN_FAILED_INVALID_PASSPHRASE;
              this.status.sync = CREDENTIALS_CHANGED;
            } else {
              // In the absence of further disambiguation or more precise
              // failure constants, just report failure.
              this.status.login = LOGIN_FAILED;
            }
            return false;
          }
        } else {
          this._log.info(
            "... 'crypto' is not a reported collection. Generating new keys."
          );
        }

        if (!cryptoKeys) {
          this._log.info("No keys! Generating new ones.");

          // Better make some and upload them, and wipe the server to ensure
          // consistency. This is all achieved via _freshStart.
          // If _freshStart fails to clear the server or upload keys, it will
          // throw.
          await this._freshStart();
          return true;
        }

        // Last-ditch case.
        return false;
      }
      // No update needed: we're good!
      return true;
    } catch (ex) {
      // This means no keys are present, or there's a network error.
      this._log.debug("Failed to fetch and verify keys", ex);
      this.errorHandler.checkServerError(ex);
      return false;
    }
  },

  getMaxRecordPayloadSize() {
    let config = this.serverConfiguration;
    if (!config || !config.max_record_payload_bytes) {
      this._log.warn(
        "No config or incomplete config in getMaxRecordPayloadSize." +
          " Are we running tests?"
      );
      return 256 * 1024;
    }
    let payloadMax = config.max_record_payload_bytes;
    if (config.max_post_bytes && payloadMax <= config.max_post_bytes) {
      return config.max_post_bytes - 4096;
    }
    return payloadMax;
  },

  getMemcacheMaxRecordPayloadSize() {
    // Collections stored in memcached ("tabs", "clients" or "meta") have a
    // different max size than ones stored in the normal storage server db.
    // In practice, the real limit here is 1M (bug 1300451 comment 40), but
    // there's overhead involved that is hard to calculate on the client, so we
    // use 512k to be safe (at the recommendation of the server team). Note
    // that if the server reports a lower limit (via info/configuration), we
    // respect that limit instead. See also bug 1403052.
    return Math.min(512 * 1024, this.getMaxRecordPayloadSize());
  },

  async verifyLogin(allow40XRecovery = true) {
    // Attaching auth credentials to a request requires access to
    // passwords, which means that Resource.get can throw MP-related
    // exceptions!
    // So we ask the identity to verify the login state after unlocking the
    // master password (ie, this call is expected to prompt for MP unlock
    // if necessary) while we still have control.
    this.status.login = await this.identity.unlockAndVerifyAuthState();
    this._log.debug(
      "Fetching unlocked auth state returned " + this.status.login
    );
    if (this.status.login != STATUS_OK) {
      return false;
    }

    try {
      // Make sure we have a cluster to verify against.
      // This is a little weird, if we don't get a node we pretend
      // to succeed, since that probably means we just don't have storage.
      if (this.clusterURL == "" && !(await this.identity.setCluster())) {
        this.status.sync = NO_SYNC_NODE_FOUND;
        return true;
      }

      // Fetch collection info on every startup.
      let test = await this.resource(this.infoURL).get();

      switch (test.status) {
        case 200:
          // The user is authenticated.

          // We have no way of verifying the passphrase right now,
          // so wait until remoteSetup to do so.
          // Just make the most trivial checks.
          if (!this.identity.syncKeyBundle) {
            this._log.warn("No passphrase in verifyLogin.");
            this.status.login = LOGIN_FAILED_NO_PASSPHRASE;
            return false;
          }

          // Go ahead and do remote setup, so that we can determine
          // conclusively that our passphrase is correct.
          if (await this._remoteSetup(test)) {
            // Username/password verified.
            this.status.login = LOGIN_SUCCEEDED;
            return true;
          }

          this._log.warn("Remote setup failed.");
          // Remote setup must have failed.
          return false;

        case 401:
          this._log.warn("401: login failed.");
        // Fall through to the 404 case.

        case 404:
          // Check that we're verifying with the correct cluster
          if (allow40XRecovery && (await this.identity.setCluster())) {
            return await this.verifyLogin(false);
          }

          // We must have the right cluster, but the server doesn't expect us.
          // For FxA this almost certainly means "transient error fetching token".
          this.status.login = LOGIN_FAILED_NETWORK_ERROR;
          return false;

        default:
          // Server didn't respond with something that we expected
          this.status.login = LOGIN_FAILED_SERVER_ERROR;
          this.errorHandler.checkServerError(test);
          return false;
      }
    } catch (ex) {
      // Must have failed on some network issue
      this._log.debug("verifyLogin failed", ex);
      this.status.login = LOGIN_FAILED_NETWORK_ERROR;
      this.errorHandler.checkServerError(ex);
      return false;
    }
  },

  async generateNewSymmetricKeys() {
    this._log.info("Generating new keys WBO...");
    let wbo = await this.collectionKeys.generateNewKeysWBO();
    this._log.info("Encrypting new key bundle.");
    await wbo.encrypt(this.identity.syncKeyBundle);

    let uploadRes = await this._uploadCryptoKeys(wbo, 0);
    if (uploadRes.status != 200) {
      this._log.warn(
        "Got status " +
          uploadRes.status +
          " uploading new keys. What to do? Throw!"
      );
      this.errorHandler.checkServerError(uploadRes);
      throw new Error("Unable to upload symmetric keys.");
    }
    this._log.info("Got status " + uploadRes.status + " uploading keys.");
    let serverModified = uploadRes.obj; // Modified timestamp according to server.
    this._log.debug("Server reports crypto modified: " + serverModified);

    // Now verify that info/collections shows them!
    this._log.debug("Verifying server collection records.");
    let info = await this._fetchInfo();
    this._log.debug("info/collections is: " + info.data);

    if (info.status != 200) {
      this._log.warn("Non-200 info/collections response. Aborting.");
      throw new Error("Unable to upload symmetric keys.");
    }

    info = info.obj;
    if (!(CRYPTO_COLLECTION in info)) {
      this._log.error(
        "Consistency failure: info/collections excludes " +
          "crypto after successful upload."
      );
      throw new Error("Symmetric key upload failed.");
    }

    // Can't check against local modified: clock drift.
    if (info[CRYPTO_COLLECTION] < serverModified) {
      this._log.error(
        "Consistency failure: info/collections crypto entry " +
          "is stale after successful upload."
      );
      throw new Error("Symmetric key upload failed.");
    }

    // Doesn't matter if the timestamp is ahead.

    // Download and install them.
    let cryptoKeys = new CryptoWrapper(CRYPTO_COLLECTION, KEYS_WBO);
    let cryptoResp = (await cryptoKeys.fetch(this.resource(this.cryptoKeysURL)))
      .response;
    if (cryptoResp.status != 200) {
      this._log.warn("Failed to download keys.");
      throw new Error("Symmetric key download failed.");
    }
    let keysChanged = await this.handleFetchedKeys(
      this.identity.syncKeyBundle,
      cryptoKeys,
      true
    );
    if (keysChanged) {
      this._log.info("Downloaded keys differed, as expected.");
    }
  },

  // configures/enabled/turns-on sync. There must be an FxA user signed in.
  async configure() {
    // We don't, and must not, throw if sync is already configured, because we
    // might end up being called as part of a "reconnect" flow. We also want to
    // avoid checking the FxA user is the same as the pref because the email
    // address for the FxA account can change - we'd need to use the uid.
    let user = await fxAccounts.getSignedInUser();
    if (!user) {
      throw new Error("No FxA user is signed in");
    }
    this._log.info("Configuring sync with current FxA user");
    Svc.PrefBranch.setStringPref("username", user.email);
    Svc.Obs.notify("weave:connected");
  },

  // resets/turns-off sync.
  async startOver() {
    this._log.trace("Invoking Service.startOver.");
    await this._stopTracking();
    this.status.resetSync();

    // Deletion doesn't make sense if we aren't set up yet!
    if (this.clusterURL != "") {
      // Clear client-specific data from the server, including disabled engines.
      const engines = [this.clientsEngine, ...this.engineManager.getAll()];
      for (let engine of engines) {
        try {
          await engine.removeClientData();
        } catch (ex) {
          this._log.warn(`Deleting client data for ${engine.name} failed`, ex);
        }
      }
      this._log.debug("Finished deleting client data.");
    } else {
      this._log.debug("Skipping client data removal: no cluster URL.");
    }

    this.identity.resetCredentials();
    this.status.login = LOGIN_FAILED_NO_USERNAME;
    this.logout();
    Svc.Obs.notify("weave:service:start-over");

    // Reset all engines and clear keys.
    await this.resetClient();
    this.collectionKeys.clear();
    this.status.resetBackoff();

    // Reset Weave prefs.
    this._ignorePrefObserver = true;
    for (const pref of Svc.PrefBranch.getChildList("")) {
      Svc.PrefBranch.clearUserPref(pref);
    }
    this._ignorePrefObserver = false;
    this.clusterURL = null;

    Svc.PrefBranch.setStringPref("lastversion", WEAVE_VERSION);

    try {
      this.identity.finalize();
      this.status.__authManager = null;
      this.identity = Status._authManager;
      Svc.Obs.notify("weave:service:start-over:finish");
    } catch (err) {
      this._log.error(
        "startOver failed to re-initialize the identity manager",
        err
      );
      // Still send the observer notification so the current state is
      // reflected in the UI.
      Svc.Obs.notify("weave:service:start-over:finish");
    }
  },

  async login() {
    async function onNotify() {
      this._loggedIn = false;
      if (this.scheduler.offline) {
        this.status.login = LOGIN_FAILED_NETWORK_ERROR;
        throw new Error("Application is offline, login should not be called");
      }

      this._log.info("User logged in successfully - verifying login.");
      if (!(await this.verifyLogin())) {
        // verifyLogin sets the failure states here.
        throw new Error(`Login failed: ${this.status.login}`);
      }

      this._updateCachedURLs();

      this._loggedIn = true;

      return true;
    }

    let notifier = this._notify("login", "", onNotify.bind(this));
    return this._catch(this._lock("service.js: login", notifier))();
  },

  logout: function logout() {
    // If we failed during login, we aren't going to have this._loggedIn set,
    // but we still want to ask the identity to logout, so it doesn't try and
    // reuse any old credentials next time we sync.
    this._log.info("Logging out");
    this.identity.logout();
    this._loggedIn = false;

    Svc.Obs.notify("weave:service:logout:finish");
  },

  // Note: returns false if we failed for a reason other than the server not yet
  // supporting the api.
  async _fetchServerConfiguration() {
    // This is similar to _fetchInfo, but with different error handling.

    let infoURL = this.userBaseURL + "info/configuration";
    this._log.debug("Fetching server configuration", infoURL);
    let configResponse;
    try {
      configResponse = await this.resource(infoURL).get();
    } catch (ex) {
      // This is probably a network or similar error.
      this._log.warn("Failed to fetch info/configuration", ex);
      this.errorHandler.checkServerError(ex);
      return false;
    }

    if (configResponse.status == 404) {
      // This server doesn't support the URL yet - that's OK.
      this._log.debug(
        "info/configuration returned 404 - using default upload semantics"
      );
    } else if (configResponse.status != 200) {
      this._log.warn(
        `info/configuration returned ${configResponse.status} - using default configuration`
      );
      this.errorHandler.checkServerError(configResponse);
      return false;
    } else {
      this.serverConfiguration = configResponse.obj;
    }
    this._log.trace(
      "info/configuration for this server",
      this.serverConfiguration
    );
    return true;
  },

  // Stuff we need to do after login, before we can really do
  // anything (e.g. key setup).
  async _remoteSetup(infoResponse, fetchConfig = true) {
    if (fetchConfig && !(await this._fetchServerConfiguration())) {
      return false;
    }

    this._log.debug("Fetching global metadata record");
    let meta = await this.recordManager.get(this.metaURL);

    // Checking modified time of the meta record.
    if (
      infoResponse &&
      infoResponse.obj.meta != this.metaModified &&
      (!meta || !meta.isNew)
    ) {
      // Delete the cached meta record...
      this._log.debug(
        "Clearing cached meta record. metaModified is " +
          JSON.stringify(this.metaModified) +
          ", setting to " +
          JSON.stringify(infoResponse.obj.meta)
      );

      this.recordManager.del(this.metaURL);

      // ... fetch the current record from the server, and COPY THE FLAGS.
      let newMeta = await this.recordManager.get(this.metaURL);

      // If we got a 401, we do not want to create a new meta/global - we
      // should be able to get the existing meta after we get a new node.
      if (this.recordManager.response.status == 401) {
        this._log.debug(
          "Fetching meta/global record on the server returned 401."
        );
        this.errorHandler.checkServerError(this.recordManager.response);
        return false;
      }

      if (this.recordManager.response.status == 404) {
        this._log.debug("No meta/global record on the server. Creating one.");
        try {
          await this._uploadNewMetaGlobal();
        } catch (uploadRes) {
          this._log.warn(
            "Unable to upload new meta/global. Failing remote setup."
          );
          this.errorHandler.checkServerError(uploadRes);
          return false;
        }
      } else if (!newMeta) {
        this._log.warn("Unable to get meta/global. Failing remote setup.");
        this.errorHandler.checkServerError(this.recordManager.response);
        return false;
      } else {
        // If newMeta, then it stands to reason that meta != null.
        newMeta.isNew = meta.isNew;
        newMeta.changed = meta.changed;
      }

      // Switch in the new meta object and record the new time.
      meta = newMeta;
      this.metaModified = infoResponse.obj.meta;
    }

    let remoteVersion =
      meta && meta.payload.storageVersion ? meta.payload.storageVersion : "";

    this._log.debug(
      [
        "Weave Version:",
        WEAVE_VERSION,
        "Local Storage:",
        STORAGE_VERSION,
        "Remote Storage:",
        remoteVersion,
      ].join(" ")
    );

    // Check for cases that require a fresh start. When comparing remoteVersion,
    // we need to convert it to a number as older clients used it as a string.
    if (
      !meta ||
      !meta.payload.storageVersion ||
      !meta.payload.syncID ||
      STORAGE_VERSION > parseFloat(remoteVersion)
    ) {
      this._log.info(
        "One of: no meta, no meta storageVersion, or no meta syncID. Fresh start needed."
      );

      // abort the server wipe if the GET status was anything other than 404 or 200
      let status = this.recordManager.response.status;
      if (status != 200 && status != 404) {
        this.status.sync = METARECORD_DOWNLOAD_FAIL;
        this.errorHandler.checkServerError(this.recordManager.response);
        this._log.warn(
          "Unknown error while downloading metadata record. Aborting sync."
        );
        return false;
      }

      if (!meta) {
        this._log.info("No metadata record, server wipe needed");
      }
      if (meta && !meta.payload.syncID) {
        this._log.warn("No sync id, server wipe needed");
      }

      this._log.info("Wiping server data");
      await this._freshStart();

      if (status == 404) {
        this._log.info(
          "Metadata record not found, server was wiped to ensure " +
            "consistency."
        );
      } else {
        // 200
        this._log.info("Wiped server; incompatible metadata: " + remoteVersion);
      }
      return true;
    } else if (remoteVersion > STORAGE_VERSION) {
      this.status.sync = VERSION_OUT_OF_DATE;
      this._log.warn("Upgrade required to access newer storage version.");
      return false;
    } else if (meta.payload.syncID != this.syncID) {
      this._log.info(
        "Sync IDs differ. Local is " +
          this.syncID +
          ", remote is " +
          meta.payload.syncID
      );
      await this.resetClient();
      this.collectionKeys.clear();
      this.syncID = meta.payload.syncID;
      this._log.debug("Clear cached values and take syncId: " + this.syncID);

      if (!(await this.verifyAndFetchSymmetricKeys(infoResponse))) {
        this._log.warn("Failed to fetch symmetric keys. Failing remote setup.");
        return false;
      }

      // bug 545725 - re-verify creds and fail sanely
      if (!(await this.verifyLogin())) {
        this.status.sync = CREDENTIALS_CHANGED;
        this._log.info(
          "Credentials have changed, aborting sync and forcing re-login."
        );
        return false;
      }

      return true;
    }
    if (!(await this.verifyAndFetchSymmetricKeys(infoResponse))) {
      this._log.warn("Failed to fetch symmetric keys. Failing remote setup.");
      return false;
    }

    return true;
  },

  /**
   * Return whether we should attempt login at the start of a sync.
   *
   * Note that this function has strong ties to _checkSync: callers
   * of this function should typically use _checkSync to verify that
   * any necessary login took place.
   */
  _shouldLogin: function _shouldLogin() {
    return (
      this.enabled &&
      !this.scheduler.offline &&
      !this.isLoggedIn &&
      Async.isAppReady()
    );
  },

  /**
   * Determine if a sync should run.
   *
   * @param ignore [optional]
   *        array of reasons to ignore when checking
   *
   * @return Reason for not syncing; not-truthy if sync should run
   */
  _checkSync: function _checkSync(ignore) {
    let reason = "";
    // Ideally we'd call _checkSetup() here but that has too many side-effects.
    if (Status.service == CLIENT_NOT_CONFIGURED) {
      reason = kSyncNotConfigured;
    } else if (Status.service == STATUS_DISABLED || !this.enabled) {
      reason = kSyncWeaveDisabled;
    } else if (this.scheduler.offline) {
      reason = kSyncNetworkOffline;
    } else if (this.status.minimumNextSync > Date.now()) {
      reason = kSyncBackoffNotMet;
    } else if (
      this.status.login == MASTER_PASSWORD_LOCKED &&
      Utils.mpLocked()
    ) {
      reason = kSyncMasterPasswordLocked;
    } else if (Svc.PrefBranch.getStringPref("firstSync", null) == "notReady") {
      reason = kFirstSyncChoiceNotMade;
    } else if (!Async.isAppReady()) {
      reason = kFirefoxShuttingDown;
    }

    if (ignore && ignore.includes(reason)) {
      return "";
    }

    return reason;
  },

  async sync({ engines, why } = {}) {
    let dateStr = Utils.formatTimestamp(new Date());
    this._log.debug("User-Agent: " + Utils.userAgent);
    await this.promiseInitialized;
    this._log.info(
      `Starting sync at ${dateStr} in browser session ${lazy.browserSessionID}`
    );
    return this._catch(async function () {
      // Make sure we're logged in.
      if (this._shouldLogin()) {
        this._log.debug("In sync: should login.");
        if (!(await this.login())) {
          this._log.debug("Not syncing: login returned false.");
          return;
        }
      } else {
        this._log.trace("In sync: no need to login.");
      }
      await this._lockedSync(engines, why);
    })();
  },

  /**
   * Sync up engines with the server.
   */
  async _lockedSync(engineNamesToSync, why) {
    return this._lock(
      "service.js: sync",
      this._notify("sync", JSON.stringify({ why }), async function onNotify() {
        let histogram =
          Services.telemetry.getHistogramById("WEAVE_START_COUNT");
        histogram.add(1);

        let synchronizer = new EngineSynchronizer(this);
        await synchronizer.sync(engineNamesToSync, why); // Might throw!

        histogram = Services.telemetry.getHistogramById(
          "WEAVE_COMPLETE_SUCCESS_COUNT"
        );
        histogram.add(1);

        // We successfully synchronized.
        // Check if the identity wants to pre-fetch a migration sentinel from
        // the server.
        // If we have no clusterURL, we are probably doing a node reassignment
        // so don't attempt to get it in that case.
        if (this.clusterURL) {
          this.identity.prefetchMigrationSentinel(this);
        }

        // Now let's update our declined engines
        await this._maybeUpdateDeclined();
      })
    )();
  },

  /**
   * Update the "declined" information in meta/global if necessary.
   */
  async _maybeUpdateDeclined() {
    // if Sync failed due to no node we will not have a meta URL, so can't
    // update anything.
    if (!this.metaURL) {
      return;
    }
    let meta = await this.recordManager.get(this.metaURL);
    if (!meta) {
      this._log.warn("No meta/global; can't update declined state.");
      return;
    }

    let declinedEngines = new DeclinedEngines(this);
    let didChange = declinedEngines.updateDeclined(meta, this.engineManager);
    if (!didChange) {
      this._log.info(
        "No change to declined engines. Not reuploading meta/global."
      );
      return;
    }

    await this.uploadMetaGlobal(meta);
  },

  /**
   * Upload a fresh meta/global record
   * @throws the response object if the upload request was not a success
   */
  async _uploadNewMetaGlobal() {
    let meta = new WBORecord("meta", "global");
    meta.payload.syncID = this.syncID;
    meta.payload.storageVersion = STORAGE_VERSION;
    meta.payload.declined = this.engineManager.getDeclined();
    meta.modified = 0;
    meta.isNew = true;

    await this.uploadMetaGlobal(meta);
  },

  /**
   * Upload meta/global, throwing the response on failure
   * @param {WBORecord} meta meta/global record
   * @throws the response object if the request was not a success
   */
  async uploadMetaGlobal(meta) {
    this._log.debug("Uploading meta/global", meta);
    let res = this.resource(this.metaURL);
    res.setHeader("X-If-Unmodified-Since", meta.modified);
    let response = await res.put(meta);
    if (!response.success) {
      throw response;
    }
    // From https://docs.services.mozilla.com/storage/apis-1.5.html:
    // "Successful responses will return the new last-modified time for the collection."
    meta.modified = response.obj;
    this.recordManager.set(this.metaURL, meta);
  },

  /**
   * Upload crypto/keys
   * @param {WBORecord} cryptoKeys crypto/keys record
   * @param {Number} lastModified known last modified timestamp (in decimal seconds),
   *                 will be used to set the X-If-Unmodified-Since header
   */
  async _uploadCryptoKeys(cryptoKeys, lastModified) {
    this._log.debug(`Uploading crypto/keys (lastModified: ${lastModified})`);
    let res = this.resource(this.cryptoKeysURL);
    res.setHeader("X-If-Unmodified-Since", lastModified);
    return res.put(cryptoKeys);
  },

  async _freshStart() {
    this._log.info("Fresh start. Resetting client.");
    await this.resetClient();
    this.collectionKeys.clear();

    // Wipe the server.
    await this.wipeServer();

    // Upload a new meta/global record.
    // _uploadNewMetaGlobal throws on failure -- including race conditions.
    // If we got into a race condition, we'll abort the sync this way, too.
    // That's fine. We'll just wait till the next sync. The client that we're
    // racing is probably busy uploading stuff right now anyway.
    await this._uploadNewMetaGlobal();

    // Wipe everything we know about except meta because we just uploaded it
    // TODO: there's a bug here. We should be calling resetClient, no?

    // Generate, upload, and download new keys. Do this last so we don't wipe
    // them...
    await this.generateNewSymmetricKeys();
  },

  /**
   * Wipe user data from the server.
   *
   * @param collections [optional]
   *        Array of collections to wipe. If not given, all collections are
   *        wiped by issuing a DELETE request for `storageURL`.
   *
   * @return the server's timestamp of the (last) DELETE.
   */
  async wipeServer(collections) {
    let response;
    if (!collections) {
      // Strip the trailing slash.
      let res = this.resource(this.storageURL.slice(0, -1));
      res.setHeader("X-Confirm-Delete", "1");
      try {
        response = await res.delete();
      } catch (ex) {
        this._log.debug("Failed to wipe server", ex);
        throw ex;
      }
      if (response.status != 200 && response.status != 404) {
        this._log.debug(
          "Aborting wipeServer. Server responded with " +
            response.status +
            " response for " +
            this.storageURL
        );
        throw response;
      }
      return response.headers["x-weave-timestamp"];
    }

    let timestamp;
    for (let name of collections) {
      let url = this.storageURL + name;
      try {
        response = await this.resource(url).delete();
      } catch (ex) {
        this._log.debug("Failed to wipe '" + name + "' collection", ex);
        throw ex;
      }

      if (response.status != 200 && response.status != 404) {
        this._log.debug(
          "Aborting wipeServer. Server responded with " +
            response.status +
            " response for " +
            url
        );
        throw response;
      }

      if ("x-weave-timestamp" in response.headers) {
        timestamp = response.headers["x-weave-timestamp"];
      }
    }
    return timestamp;
  },

  /**
   * Wipe all local user data.
   *
   * @param engines [optional]
   *        Array of engine names to wipe. If not given, all engines are used.
   */
  async wipeClient(engines) {
    // If we don't have any engines, reset the service and wipe all engines
    if (!engines) {
      // Clear out any service data
      await this.resetService();

      engines = [this.clientsEngine, ...this.engineManager.getAll()];
    } else {
      // Convert the array of names into engines
      engines = this.engineManager.get(engines);
    }

    // Fully wipe each engine if it's able to decrypt data
    for (let engine of engines) {
      if (await engine.canDecrypt()) {
        await engine.wipeClient();
      }
    }
  },

  /**
   * Wipe all remote user data by wiping the server then telling each remote
   * client to wipe itself.
   *
   * @param engines
   *        Array of engine names to wipe.
   */
  async wipeRemote(engines) {
    try {
      // Make sure stuff gets uploaded.
      await this.resetClient(engines);

      // Clear out any server data.
      await this.wipeServer(engines);

      // Only wipe the engines provided.
      let extra = { reason: "wipe-remote" };
      for (const e of engines) {
        await this.clientsEngine.sendCommand("wipeEngine", [e], null, extra);
      }

      // Make sure the changed clients get updated.
      await this.clientsEngine.sync();
    } catch (ex) {
      this.errorHandler.checkServerError(ex);
      throw ex;
    }
  },

  /**
   * Reset local service information like logs, sync times, caches.
   */
  async resetService() {
    return this._catch(async function reset() {
      this._log.info("Service reset.");

      // Pretend we've never synced to the server and drop cached data
      this.syncID = "";
      this.recordManager.clearCache();
    })();
  },

  /**
   * Reset the client by getting rid of any local server data and client data.
   *
   * @param engines [optional]
   *        Array of engine names to reset. If not given, all engines are used.
   */
  async resetClient(engines) {
    return this._catch(async function doResetClient() {
      // If we don't have any engines, reset everything including the service
      if (!engines) {
        // Clear out any service data
        await this.resetService();

        engines = [this.clientsEngine, ...this.engineManager.getAll()];
      } else {
        // Convert the array of names into engines
        engines = this.engineManager.get(engines);
      }

      // Have each engine drop any temporary meta data
      for (let engine of engines) {
        await engine.resetClient();
      }
    })();
  },

  recordTelemetryEvent(object, method, value, extra = undefined) {
    Svc.Obs.notify("weave:telemetry:event", { object, method, value, extra });
  },
};

export var Service = new Sync11Service();
Service.promiseInitialized = new Promise(resolve => {
  Service.onStartup().then(resolve);
});
PK
!<1�93
3
-modules/services-sync/stages/declined.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file contains code for maintaining the set of declined engines,
 * in conjunction with EngineManager.
 */

import { Log } from "resource://gre/modules/Log.sys.mjs";

import { CommonUtils } from "resource://services-common/utils.sys.mjs";
import { Observers } from "resource://services-common/observers.sys.mjs";

export var DeclinedEngines = function (service) {
  this._log = Log.repository.getLogger("Sync.Declined");
  this._log.manageLevelFromPref("services.sync.log.logger.declined");

  this.service = service;
};

DeclinedEngines.prototype = {
  updateDeclined(meta, engineManager = this.service.engineManager) {
    let enabled = new Set(engineManager.getEnabled().map(e => e.name));
    let known = new Set(engineManager.getAll().map(e => e.name));
    let remoteDeclined = new Set(meta.payload.declined || []);
    let localDeclined = new Set(engineManager.getDeclined());

    this._log.debug(
      "Handling remote declined: " + JSON.stringify([...remoteDeclined])
    );
    this._log.debug(
      "Handling local declined: " + JSON.stringify([...localDeclined])
    );

    // Any engines that are locally enabled should be removed from the remote
    // declined list.
    //
    // Any engines that are locally declined should be added to the remote
    // declined list.
    let newDeclined = CommonUtils.union(
      localDeclined,
      CommonUtils.difference(remoteDeclined, enabled)
    );

    // If our declined set has changed, put it into the meta object and mark
    // it as changed.
    let declinedChanged = !CommonUtils.setEqual(newDeclined, remoteDeclined);
    this._log.debug("Declined changed? " + declinedChanged);
    if (declinedChanged) {
      meta.changed = true;
      meta.payload.declined = [...newDeclined];
    }

    // Update the engine manager regardless.
    engineManager.setDeclined(newDeclined);

    // Any engines that are locally known, locally disabled, and not remotely
    // or locally declined, are candidates for enablement.
    let undecided = CommonUtils.difference(
      CommonUtils.difference(known, enabled),
      newDeclined
    );
    if (undecided.size) {
      let subject = {
        declined: newDeclined,
        enabled,
        known,
        undecided,
      };
      CommonUtils.nextTick(() => {
        Observers.notify("weave:engines:notdeclined", subject);
      });
    }

    return declinedChanged;
  },
};
PK
!<׺��66/modules/services-sync/stages/enginesync.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file contains code for synchronizing engines.
 */

import { Log } from "resource://gre/modules/Log.sys.mjs";

import {
  ABORT_SYNC_COMMAND,
  LOGIN_FAILED_NETWORK_ERROR,
  NO_SYNC_NODE_FOUND,
  STATUS_OK,
  SYNC_FAILED_PARTIAL,
  SYNC_SUCCEEDED,
  WEAVE_VERSION,
  kSyncNetworkOffline,
} from "resource://services-sync/constants.sys.mjs";

import { Svc, Utils } from "resource://services-sync/util.sys.mjs";

import { Async } from "resource://services-common/async.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  Doctor: "resource://services-sync/doctor.sys.mjs",
});

/**
 * Perform synchronization of engines.
 *
 * This was originally split out of service.js. The API needs lots of love.
 */
export function EngineSynchronizer(service) {
  this._log = Log.repository.getLogger("Sync.Synchronizer");
  this._log.manageLevelFromPref("services.sync.log.logger.synchronizer");

  this.service = service;
}

EngineSynchronizer.prototype = {
  async sync(engineNamesToSync, why) {
    let fastSync = why && why == "sleep";
    let startTime = Date.now();

    this.service.status.resetSync();

    // Make sure we should sync or record why we shouldn't.
    let reason = this.service._checkSync();
    if (reason) {
      if (reason == kSyncNetworkOffline) {
        this.service.status.sync = LOGIN_FAILED_NETWORK_ERROR;
      }

      // this is a purposeful abort rather than a failure, so don't set
      // any status bits
      reason = "Can't sync: " + reason;
      throw new Error(reason);
    }

    // If we don't have a node, get one. If that fails, retry in 10 minutes.
    if (
      !this.service.clusterURL &&
      !(await this.service.identity.setCluster())
    ) {
      this.service.status.sync = NO_SYNC_NODE_FOUND;
      this._log.info("No cluster URL found. Cannot sync.");
      return;
    }

    // Ping the server with a special info request once a day.
    let infoURL = this.service.infoURL;
    let now = Math.floor(Date.now() / 1000);
    let lastPing = Svc.PrefBranch.getIntPref("lastPing", 0);
    if (now - lastPing > 86400) {
      // 60 * 60 * 24
      infoURL += "?v=" + WEAVE_VERSION;
      Svc.PrefBranch.setIntPref("lastPing", now);
    }

    let engineManager = this.service.engineManager;

    // Figure out what the last modified time is for each collection
    let info = await this.service._fetchInfo(infoURL);

    // Convert the response to an object and read out the modified times
    for (let engine of [this.service.clientsEngine].concat(
      engineManager.getAll()
    )) {
      engine.lastModified = info.obj[engine.name] || 0;
    }

    if (!(await this.service._remoteSetup(info, !fastSync))) {
      throw new Error("Aborting sync, remote setup failed");
    }

    if (!fastSync) {
      // Make sure we have an up-to-date list of clients before sending commands
      this._log.debug("Refreshing client list.");
      if (!(await this._syncEngine(this.service.clientsEngine))) {
        // Clients is an engine like any other; it can fail with a 401,
        // and we can elect to abort the sync.
        this._log.warn("Client engine sync failed. Aborting.");
        return;
      }
    }

    // We only honor the "hint" of what engines to Sync if this isn't
    // a first sync.
    let allowEnginesHint = false;
    // Wipe data in the desired direction if necessary
    switch (Svc.PrefBranch.getStringPref("firstSync", null)) {
      case "resetClient":
        await this.service.resetClient(engineManager.enabledEngineNames);
        break;
      case "wipeClient":
        await this.service.wipeClient(engineManager.enabledEngineNames);
        break;
      case "wipeRemote":
        await this.service.wipeRemote(engineManager.enabledEngineNames);
        break;
      default:
        allowEnginesHint = true;
        break;
    }

    if (!fastSync && this.service.clientsEngine.localCommands) {
      try {
        if (!(await this.service.clientsEngine.processIncomingCommands())) {
          this.service.status.sync = ABORT_SYNC_COMMAND;
          throw new Error("Processed command aborted sync.");
        }

        // Repeat remoteSetup in-case the commands forced us to reset
        if (!(await this.service._remoteSetup(info))) {
          throw new Error("Remote setup failed after processing commands.");
        }
      } finally {
        // Always immediately attempt to push back the local client (now
        // without commands).
        // Note that we don't abort here; if there's a 401 because we've
        // been reassigned, we'll handle it around another engine.
        await this._syncEngine(this.service.clientsEngine);
      }
    }

    // Update engines because it might change what we sync.
    try {
      await this._updateEnabledEngines();
    } catch (ex) {
      this._log.debug("Updating enabled engines failed", ex);
      this.service.errorHandler.checkServerError(ex);
      throw ex;
    }

    await this.service.engineManager.switchAlternatives();

    // If the engines to sync has been specified, we sync in the order specified.
    let enginesToSync;
    if (allowEnginesHint && engineNamesToSync) {
      this._log.info("Syncing specified engines", engineNamesToSync);
      enginesToSync = engineManager
        .get(engineNamesToSync)
        .filter(e => e.enabled);
    } else {
      this._log.info("Syncing all enabled engines.");
      enginesToSync = engineManager.getEnabled();
    }
    try {
      // We don't bother validating engines that failed to sync.
      let enginesToValidate = [];
      for (let engine of enginesToSync) {
        if (engine.shouldSkipSync(why)) {
          this._log.info(`Engine ${engine.name} asked to be skipped`);
          continue;
        }
        // If there's any problems with syncing the engine, report the failure
        if (
          !(await this._syncEngine(engine)) ||
          this.service.status.enforceBackoff
        ) {
          this._log.info("Aborting sync for failure in " + engine.name);
          break;
        }
        enginesToValidate.push(engine);
      }

      // If _syncEngine fails for a 401, we might not have a cluster URL here.
      // If that's the case, break out of this immediately, rather than
      // throwing an exception when trying to fetch metaURL.
      if (!this.service.clusterURL) {
        this._log.debug(
          "Aborting sync, no cluster URL: not uploading new meta/global."
        );
        return;
      }

      // Upload meta/global if any engines changed anything.
      let meta = await this.service.recordManager.get(this.service.metaURL);
      if (meta.isNew || meta.changed) {
        this._log.info("meta/global changed locally: reuploading.");
        try {
          await this.service.uploadMetaGlobal(meta);
          delete meta.isNew;
          delete meta.changed;
        } catch (error) {
          this._log.error(
            "Unable to upload meta/global. Leaving marked as new."
          );
        }
      }

      if (!fastSync) {
        await lazy.Doctor.consult(enginesToValidate);
      }

      // If there were no sync engine failures
      if (this.service.status.service != SYNC_FAILED_PARTIAL) {
        this.service.status.sync = SYNC_SUCCEEDED;
      }

      // Even if there were engine failures, bump lastSync even on partial since
      // it's reflected in the UI (bug 1439777).
      if (
        this.service.status.service == SYNC_FAILED_PARTIAL ||
        this.service.status.service == STATUS_OK
      ) {
        Svc.PrefBranch.setStringPref("lastSync", new Date().toString());
      }
    } finally {
      Svc.PrefBranch.clearUserPref("firstSync");

      let syncTime = ((Date.now() - startTime) / 1000).toFixed(2);
      let dateStr = Utils.formatTimestamp(new Date());
      this._log.info(
        "Sync completed at " + dateStr + " after " + syncTime + " secs."
      );
    }
  },

  // Returns true if sync should proceed.
  // false / no return value means sync should be aborted.
  async _syncEngine(engine) {
    try {
      await engine.sync();
    } catch (e) {
      if (e.status == 401) {
        // Maybe a 401, cluster update perhaps needed?
        // We rely on ErrorHandler observing the sync failure notification to
        // schedule another sync and clear node assignment values.
        // Here we simply want to muffle the exception and return an
        // appropriate value.
        return false;
      }
      // Note that policies.js has already logged info about the exception...
      if (Async.isShutdownException(e)) {
        // Failure due to a shutdown exception should prevent other engines
        // trying to start and immediately failing.
        this._log.info(
          `${engine.name} was interrupted by shutdown; no other engines will sync`
        );
        return false;
      }
    }

    return true;
  },

  async _updateEnabledFromMeta(
    meta,
    numClients,
    engineManager = this.service.engineManager
  ) {
    this._log.info("Updating enabled engines: " + numClients + " clients.");

    if (meta.isNew || !meta.payload.engines) {
      this._log.debug(
        "meta/global isn't new, or is missing engines. Not updating enabled state."
      );
      return;
    }

    // If we're the only client, and no engines are marked as enabled,
    // thumb our noses at the server data: it can't be right.
    // Belt-and-suspenders approach to Bug 615926.
    let hasEnabledEngines = false;
    for (let e in meta.payload.engines) {
      if (e != "clients") {
        hasEnabledEngines = true;
        break;
      }
    }

    if (numClients <= 1 && !hasEnabledEngines) {
      this._log.info(
        "One client and no enabled engines: not touching local engine status."
      );
      return;
    }

    this.service._ignorePrefObserver = true;

    let enabled = engineManager.enabledEngineNames;

    let toDecline = new Set();
    let toUndecline = new Set();

    for (let engineName in meta.payload.engines) {
      if (engineName == "clients") {
        // Clients is special.
        continue;
      }
      let index = enabled.indexOf(engineName);
      if (index != -1) {
        // The engine is enabled locally. Nothing to do.
        enabled.splice(index, 1);
        continue;
      }
      let engine = engineManager.get(engineName);
      if (!engine) {
        // The engine doesn't exist locally. Nothing to do.
        continue;
      }

      let attemptedEnable = false;
      // If the engine was enabled remotely, enable it locally.
      if (
        !Svc.PrefBranch.getBoolPref(
          "engineStatusChanged." + engine.prefName,
          false
        )
      ) {
        this._log.trace(
          "Engine " + engineName + " was enabled. Marking as non-declined."
        );
        toUndecline.add(engineName);
        this._log.trace(engineName + " engine was enabled remotely.");
        engine.enabled = true;
        // Note that setting engine.enabled to true might not have worked for
        // the password engine if a master-password is enabled.  However, it's
        // still OK that we added it to undeclined - the user *tried* to enable
        // it remotely - so it still winds up as not being flagged as declined
        // even though it's disabled remotely.
        attemptedEnable = true;
      }

      // If either the engine was disabled locally or enabling the engine
      // failed (see above re master-password) then wipe server data and
      // disable it everywhere.
      if (!engine.enabled) {
        this._log.trace("Wiping data for " + engineName + " engine.");
        await engine.wipeServer();
        delete meta.payload.engines[engineName];
        meta.changed = true; // the new enabled state must propagate
        // We also here mark the engine as declined, because the pref
        // was explicitly changed to false - unless we tried, and failed,
        // to enable it - in which case we leave the declined state alone.
        if (!attemptedEnable) {
          // This will be reflected in meta/global in the next stage.
          this._log.trace(
            "Engine " +
              engineName +
              " was disabled locally. Marking as declined."
          );
          toDecline.add(engineName);
        }
      }
    }

    // Any remaining engines were either enabled locally or disabled remotely.
    for (let engineName of enabled) {
      let engine = engineManager.get(engineName);
      if (
        Svc.PrefBranch.getBoolPref(
          "engineStatusChanged." + engine.prefName,
          false
        )
      ) {
        this._log.trace("The " + engineName + " engine was enabled locally.");
        toUndecline.add(engineName);
      } else {
        this._log.trace("The " + engineName + " engine was disabled remotely.");

        // Don't automatically mark it as declined!
        try {
          engine.enabled = false;
        } catch (e) {
          this._log.trace("Failed to disable engine " + engineName);
        }
      }
    }

    engineManager.decline(toDecline);
    engineManager.undecline(toUndecline);

    for (const pref of Svc.PrefBranch.getChildList("engineStatusChanged.")) {
      Svc.PrefBranch.clearUserPref(pref);
    }
    this.service._ignorePrefObserver = false;
  },

  async _updateEnabledEngines() {
    let meta = await this.service.recordManager.get(this.service.metaURL);
    let numClients = this.service.scheduler.numClients;
    let engineManager = this.service.engineManager;

    await this._updateEnabledFromMeta(meta, numClients, engineManager);
  },
};
Object.freeze(EngineSynchronizer.prototype);
PK
!<ͨ�J��$modules/services-sync/status.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import {
  CLIENT_NOT_CONFIGURED,
  ENGINE_SUCCEEDED,
  LOGIN_FAILED,
  LOGIN_FAILED_NO_PASSPHRASE,
  LOGIN_FAILED_NO_USERNAME,
  LOGIN_SUCCEEDED,
  STATUS_OK,
  SYNC_FAILED,
  SYNC_FAILED_PARTIAL,
  SYNC_SUCCEEDED,
} from "resource://services-sync/constants.sys.mjs";

import { Log } from "resource://gre/modules/Log.sys.mjs";

import { SyncAuthManager } from "resource://services-sync/sync_auth.sys.mjs";

export var Status = {
  _log: Log.repository.getLogger("Sync.Status"),
  __authManager: null,
  ready: false,

  get _authManager() {
    if (this.__authManager) {
      return this.__authManager;
    }
    this.__authManager = new SyncAuthManager();
    return this.__authManager;
  },

  get service() {
    return this._service;
  },

  set service(code) {
    this._log.debug(
      "Status.service: " + (this._service || undefined) + " => " + code
    );
    this._service = code;
  },

  get login() {
    return this._login;
  },

  set login(code) {
    this._log.debug("Status.login: " + this._login + " => " + code);
    this._login = code;

    if (
      code == LOGIN_FAILED_NO_USERNAME ||
      code == LOGIN_FAILED_NO_PASSPHRASE
    ) {
      this.service = CLIENT_NOT_CONFIGURED;
    } else if (code != LOGIN_SUCCEEDED) {
      this.service = LOGIN_FAILED;
    } else {
      this.service = STATUS_OK;
    }
  },

  get sync() {
    return this._sync;
  },

  set sync(code) {
    this._log.debug("Status.sync: " + this._sync + " => " + code);
    this._sync = code;
    this.service = code == SYNC_SUCCEEDED ? STATUS_OK : SYNC_FAILED;
  },

  get engines() {
    return this._engines;
  },

  set engines([name, code]) {
    this._log.debug("Status for engine " + name + ": " + code);
    this._engines[name] = code;

    if (code != ENGINE_SUCCEEDED) {
      this.service = SYNC_FAILED_PARTIAL;
    }
  },

  // Implement toString because adding a logger introduces a cyclic object
  // value, so we can't trivially debug-print Status as JSON.
  toString: function toString() {
    return (
      "<Status" +
      ": login: " +
      Status.login +
      ", service: " +
      Status.service +
      ", sync: " +
      Status.sync +
      ">"
    );
  },

  checkSetup: function checkSetup() {
    if (!this._authManager.username) {
      Status.login = LOGIN_FAILED_NO_USERNAME;
      Status.service = CLIENT_NOT_CONFIGURED;
    } else if (Status.login == STATUS_OK) {
      Status.service = STATUS_OK;
    }
    return Status.service;
  },

  resetBackoff: function resetBackoff() {
    this.enforceBackoff = false;
    this.backoffInterval = 0;
    this.minimumNextSync = 0;
  },

  resetSync: function resetSync() {
    // Logger setup.
    this._log.manageLevelFromPref("services.sync.log.logger.status");

    this._log.info("Resetting Status.");
    this.service = STATUS_OK;
    this._login = LOGIN_SUCCEEDED;
    this._sync = SYNC_SUCCEEDED;
    this._engines = {};
    this.partial = false;
  },
};

// Initialize various status values.
Status.resetBackoff();
Status.resetSync();
PK
!<(�CJ\\'modules/services-sync/sync_auth.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { Log } from "resource://gre/modules/Log.sys.mjs";

import { Async } from "resource://services-common/async.sys.mjs";
import { TokenServerClient } from "resource://services-common/tokenserverclient.sys.mjs";
import { CryptoUtils } from "resource://services-crypto/utils.sys.mjs";
import { Svc, Utils } from "resource://services-sync/util.sys.mjs";

import {
  LOGIN_FAILED_LOGIN_REJECTED,
  LOGIN_FAILED_NETWORK_ERROR,
  LOGIN_FAILED_NO_USERNAME,
  LOGIN_SUCCEEDED,
  MASTER_PASSWORD_LOCKED,
  STATUS_OK,
} from "resource://services-sync/constants.sys.mjs";

const lazy = {};

// Lazy imports to prevent unnecessary load on startup.
ChromeUtils.defineESModuleGetters(lazy, {
  BulkKeyBundle: "resource://services-sync/keys.sys.mjs",
  Weave: "resource://services-sync/main.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => {
  return ChromeUtils.importESModule(
    "resource://gre/modules/FxAccounts.sys.mjs"
  ).getFxAccountsSingleton();
});

ChromeUtils.defineLazyGetter(lazy, "log", function () {
  let log = Log.repository.getLogger("Sync.SyncAuthManager");
  log.manageLevelFromPref("services.sync.log.logger.identity");
  return log;
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "IGNORE_CACHED_AUTH_CREDENTIALS",
  "services.sync.debug.ignoreCachedAuthCredentials"
);

// FxAccountsCommon.js doesn't use a "namespace", so create one here.
import * as fxAccountsCommon from "resource://gre/modules/FxAccountsCommon.sys.mjs";

const SCOPE_APP_SYNC = fxAccountsCommon.SCOPE_APP_SYNC;

const OBSERVER_TOPICS = [
  fxAccountsCommon.ONLOGIN_NOTIFICATION,
  fxAccountsCommon.ONVERIFIED_NOTIFICATION,
  fxAccountsCommon.ONLOGOUT_NOTIFICATION,
  fxAccountsCommon.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION,
  "weave:connected",
];

/*
  General authentication error for abstracting authentication
  errors from multiple sources (e.g., from FxAccounts, TokenServer).
  details is additional details about the error - it might be a string, or
  some other error object (which should do the right thing when toString() is
  called on it)
*/
export function AuthenticationError(details, source) {
  this.details = details;
  this.source = source;
}

AuthenticationError.prototype = {
  toString() {
    return "AuthenticationError(" + this.details + ")";
  },
};

// The `SyncAuthManager` coordinates access authorization to the Sync server.
// Its job is essentially to get us from having a signed-in Firefox Accounts user,
// to knowing the user's sync storage node and having the necessary short-lived
// credentials in order to access it.
//

export function SyncAuthManager() {
  // NOTE: _fxaService and _tokenServerClient are replaced with mocks by
  // the test suite.
  this._fxaService = lazy.fxAccounts;
  this._tokenServerClient = new TokenServerClient();
  this._tokenServerClient.observerPrefix = "weave:service";
  this._log = lazy.log;
  XPCOMUtils.defineLazyPreferenceGetter(
    this,
    "_username",
    "services.sync.username"
  );

  this.asyncObserver = Async.asyncObserver(this, lazy.log);
  for (let topic of OBSERVER_TOPICS) {
    Services.obs.addObserver(this.asyncObserver, topic);
  }
}

SyncAuthManager.prototype = {
  _fxaService: null,
  _tokenServerClient: null,
  // https://docs.services.mozilla.com/token/apis.html
  _token: null,
  // protection against the user changing underneath us - the uid
  // of the current user.
  _userUid: null,

  hashedUID() {
    const id = this._fxaService.telemetry.getSanitizedUID();
    if (!id) {
      throw new Error("hashedUID: Don't seem to have previously seen a token");
    }
    return id;
  },

  // Return a hashed version of a deviceID, suitable for telemetry.
  hashedDeviceID(deviceID) {
    const id = this._fxaService.telemetry.sanitizeDeviceId(deviceID);
    if (!id) {
      throw new Error("hashedUID: Don't seem to have previously seen a token");
    }
    return id;
  },

  // The "node type" reported to telemetry or null if not specified.
  get telemetryNodeType() {
    return this._token && this._token.node_type ? this._token.node_type : null;
  },

  finalize() {
    // After this is called, we can expect Service.identity != this.
    for (let topic of OBSERVER_TOPICS) {
      Services.obs.removeObserver(this.asyncObserver, topic);
    }
    this.resetCredentials();
    this._userUid = null;
  },

  async getSignedInUser() {
    let data = await this._fxaService.getSignedInUser();
    if (!data) {
      this._userUid = null;
      return null;
    }
    if (this._userUid == null) {
      this._userUid = data.uid;
    } else if (this._userUid != data.uid) {
      throw new Error("The signed in user has changed");
    }
    return data;
  },

  logout() {
    // This will be called when sync fails (or when the account is being
    // unlinked etc).  It may have failed because we got a 401 from a sync
    // server, so we nuke the token.  Next time sync runs and wants an
    // authentication header, we will notice the lack of the token and fetch a
    // new one.
    this._token = null;
  },

  async observe(subject, topic) {
    this._log.debug("observed " + topic);
    if (!this.username) {
      this._log.info("Sync is not configured, so ignoring the notification");
      return;
    }
    switch (topic) {
      case "weave:connected":
      case fxAccountsCommon.ONLOGIN_NOTIFICATION: {
        this._log.info("Sync has been connected to a logged in user");
        this.resetCredentials();
        let accountData = await this.getSignedInUser();

        if (!accountData.verified) {
          // wait for a verified notification before we kick sync off.
          this._log.info("The user is not verified");
          break;
        }
      }
      // We've been configured with an already verified user, so fall-through.
      // intentional fall-through - the user is verified.
      case fxAccountsCommon.ONVERIFIED_NOTIFICATION: {
        this._log.info("The user became verified");
        lazy.Weave.Status.login = LOGIN_SUCCEEDED;

        // And actually sync. If we've never synced before, we force a full sync.
        // If we have, then we are probably just reauthenticating so it's a normal sync.
        // We can use any pref that must be set if we've synced before, and check
        // the sync lock state because we might already be doing that first sync.
        let isFirstSync =
          !lazy.Weave.Service.locked &&
          !Svc.PrefBranch.getStringPref("client.syncID", null);
        if (isFirstSync) {
          this._log.info("Doing initial sync actions");
          Svc.PrefBranch.setStringPref("firstSync", "resetClient");
          Services.obs.notifyObservers(null, "weave:service:setup-complete");
        }
        // There's no need to wait for sync to complete and it would deadlock
        // our AsyncObserver.
        if (!Svc.PrefBranch.getBoolPref("testing.tps", false)) {
          lazy.Weave.Service.sync({ why: "login" });
        }
        break;
      }

      case fxAccountsCommon.ONLOGOUT_NOTIFICATION:
        lazy.Weave.Service.startOver()
          .then(() => {
            this._log.trace("startOver completed");
          })
          .catch(err => {
            this._log.warn("Failed to reset sync", err);
          });
        // startOver will cause this instance to be thrown away, so there's
        // nothing else to do.
        break;

      case fxAccountsCommon.ON_ACCOUNT_STATE_CHANGE_NOTIFICATION:
        // throw away token forcing us to fetch a new one later.
        this.resetCredentials();
        break;
    }
  },

  /**
   * Provide override point for testing token expiration.
   */
  _now() {
    return this._fxaService._internal.now();
  },

  get _localtimeOffsetMsec() {
    return this._fxaService._internal.localtimeOffsetMsec;
  },

  get syncKeyBundle() {
    return this._syncKeyBundle;
  },

  get username() {
    return this._username;
  },

  /**
   * Set the username value.
   *
   * Changing the username has the side-effect of wiping credentials.
   */
  set username(value) {
    // setting .username is an old throwback, but it should no longer happen.
    throw new Error("don't set the username");
  },

  /**
   * Resets all calculated credentials we hold for the current user. This will
   * *not* force the user to reauthenticate, but instead will force us to
   * calculate a new key bundle, fetch a new token, etc.
   */
  resetCredentials() {
    this._syncKeyBundle = null;
    this._token = null;
    // The cluster URL comes from the token, so resetting it to empty will
    // force Sync to not accidentally use a value from an earlier token.
    lazy.Weave.Service.clusterURL = null;
  },

  /**
   * Pre-fetches any information that might help with migration away from this
   * identity.  Called after every sync and is really just an optimization that
   * allows us to avoid a network request for when we actually need the
   * migration info.
   */
  prefetchMigrationSentinel() {
    // nothing to do here until we decide to migrate away from FxA.
  },

  /**
   * Verify the current auth state, unlocking the master-password if necessary.
   *
   * Returns a promise that resolves with the current auth state after
   * attempting to unlock.
   */
  async unlockAndVerifyAuthState() {
    let data = await this.getSignedInUser();
    const fxa = this._fxaService;
    if (!data) {
      lazy.log.debug("unlockAndVerifyAuthState has no FxA user");
      return LOGIN_FAILED_NO_USERNAME;
    }
    if (!this.username) {
      lazy.log.debug(
        "unlockAndVerifyAuthState finds that sync isn't configured"
      );
      return LOGIN_FAILED_NO_USERNAME;
    }
    if (!data.verified) {
      // Treat not verified as if the user needs to re-auth, so the browser
      // UI reflects the state.
      lazy.log.debug("unlockAndVerifyAuthState has an unverified user");
      return LOGIN_FAILED_LOGIN_REJECTED;
    }
    if (await fxa.keys.canGetKeyForScope(SCOPE_APP_SYNC)) {
      lazy.log.debug(
        "unlockAndVerifyAuthState already has (or can fetch) sync keys"
      );
      return STATUS_OK;
    }
    // so no keys - ensure MP unlocked.
    if (!Utils.ensureMPUnlocked()) {
      // user declined to unlock, so we don't know if they are stored there.
      lazy.log.debug(
        "unlockAndVerifyAuthState: user declined to unlock master-password"
      );
      return MASTER_PASSWORD_LOCKED;
    }
    // If we still can't get keys it probably means the user authenticated
    // without unlocking the MP or cleared the saved logins, so we've now
    // lost them - the user will need to reauth before continuing.
    let result;
    if (await fxa.keys.canGetKeyForScope(SCOPE_APP_SYNC)) {
      result = STATUS_OK;
    } else {
      result = LOGIN_FAILED_LOGIN_REJECTED;
    }
    lazy.log.debug(
      "unlockAndVerifyAuthState re-fetched credentials and is returning",
      result
    );
    return result;
  },

  /**
   * Do we have a non-null, not yet expired token for the user currently
   * signed in?
   */
  _hasValidToken() {
    // If pref is set to ignore cached authentication credentials for debugging,
    // then return false to force the fetching of a new token.
    if (lazy.IGNORE_CACHED_AUTH_CREDENTIALS) {
      return false;
    }
    if (!this._token) {
      return false;
    }
    if (this._token.expiration < this._now()) {
      return false;
    }
    return true;
  },

  // Get our tokenServerURL - a private helper. Returns a string.
  get _tokenServerUrl() {
    // We used to support services.sync.tokenServerURI but this was a
    // pain-point for people using non-default servers as Sync may auto-reset
    // all services.sync prefs. So if that still exists, it wins.
    let url = Svc.PrefBranch.getStringPref("tokenServerURI", null); // Svc.PrefBranch "root" is services.sync
    if (!url) {
      url = Services.prefs.getStringPref("identity.sync.tokenserver.uri");
    }
    while (url.endsWith("/")) {
      // trailing slashes cause problems...
      url = url.slice(0, -1);
    }
    return url;
  },

  // Refresh the sync token for our user. Returns a promise that resolves
  // with a token, or rejects with an error.
  async _fetchTokenForUser() {
    const fxa = this._fxaService;
    // We need keys for things to work.  If we don't have them, just
    // return null for the token - sync calling unlockAndVerifyAuthState()
    // before actually syncing will setup the error states if necessary.
    if (!(await fxa.keys.canGetKeyForScope(SCOPE_APP_SYNC))) {
      this._log.info(
        "Unable to fetch keys (master-password locked?), so aborting token fetch"
      );
      throw new Error("Can't fetch a token as we can't get keys");
    }

    // Do the token dance, with a retry in case of transient auth failure.
    // We need to prove that we know the sync key in order to get a token
    // from the tokenserver.
    let getToken = async (key, accessToken) => {
      this._log.info("Getting a sync token from", this._tokenServerUrl);
      let token = await this._fetchTokenUsingOAuth(key, accessToken);
      this._log.trace("Successfully got a token");
      return token;
    };

    const ttl = fxAccountsCommon.OAUTH_TOKEN_FOR_SYNC_LIFETIME_SECONDS;
    try {
      let token, key;
      try {
        this._log.info("Getting sync key");
        const tokenAndKey = await fxa.getOAuthTokenAndKey({
          scope: SCOPE_APP_SYNC,
          ttl,
        });

        key = tokenAndKey.key;
        if (!key) {
          throw new Error("browser does not have the sync key, cannot sync");
        }
        token = await getToken(key, tokenAndKey.token);
      } catch (err) {
        // If we get a 401 fetching the token it may be that our auth tokens needed
        // to be regenerated; retry exactly once.
        if (!err.response || err.response.status !== 401) {
          throw err;
        }
        this._log.warn(
          "Token server returned 401, retrying token fetch with fresh credentials"
        );
        const tokenAndKey = await fxa.getOAuthTokenAndKey({
          scope: SCOPE_APP_SYNC,
          ttl,
        });
        token = await getToken(tokenAndKey.key, tokenAndKey.token);
      }
      // TODO: Make it be only 80% of the duration, so refresh the token
      // before it actually expires. This is to avoid sync storage errors
      // otherwise, we may briefly enter a "needs reauthentication" state.
      // (XXX - the above may no longer be true - someone should check ;)
      token.expiration = this._now() + token.duration * 1000 * 0.8;
      if (!this._syncKeyBundle) {
        this._syncKeyBundle = lazy.BulkKeyBundle.fromJWK(key);
      }
      lazy.Weave.Status.login = LOGIN_SUCCEEDED;
      this._token = token;
      return token;
    } catch (caughtErr) {
      let err = caughtErr; // The error we will rethrow.

      // TODO: unify these errors - we need to handle errors thrown by
      // both tokenserverclient and hawkclient.
      // A tokenserver error thrown based on a bad response.
      if (err.response && err.response.status === 401) {
        err = new AuthenticationError(err, "tokenserver");
        // A hawkclient error.
      } else if (err.code && err.code === 401) {
        err = new AuthenticationError(err, "hawkclient");
        // An FxAccounts.sys.mjs error.
      } else if (err.message == fxAccountsCommon.ERROR_AUTH_ERROR) {
        err = new AuthenticationError(err, "fxaccounts");
      }

      // TODO: write tests to make sure that different auth error cases are handled here
      // properly: auth error getting oauth token, auth error getting sync token (invalid
      // generation or client-state error)
      if (err instanceof AuthenticationError) {
        this._log.error("Authentication error in _fetchTokenForUser", err);
        // set it to the "fatal" LOGIN_FAILED_LOGIN_REJECTED reason.
        lazy.Weave.Status.login = LOGIN_FAILED_LOGIN_REJECTED;
      } else {
        this._log.error("Non-authentication error in _fetchTokenForUser", err);
        // for now assume it is just a transient network related problem
        // (although sadly, it might also be a regular unhandled exception)
        lazy.Weave.Status.login = LOGIN_FAILED_NETWORK_ERROR;
      }
      throw err;
    }
  },

  /**
   * Exchanges an OAuth access_token for a TokenServer token.
   * @returns {Promise}
   * @private
   */
  async _fetchTokenUsingOAuth(key, accessToken) {
    this._log.debug("Getting a token using OAuth");
    const fxa = this._fxaService;
    const headers = {
      "X-KeyId": key.kid,
    };

    return this._tokenServerClient
      .getTokenUsingOAuth(this._tokenServerUrl, accessToken, headers)
      .catch(async err => {
        if (err.response && err.response.status === 401) {
          // remove the cached token if we cannot authorize with it.
          // we have to do this here because we know which `token` to remove
          // from cache.
          await fxa.removeCachedOAuthToken({ token: accessToken });
        }

        // continue the error chain, so other handlers can deal with the error.
        throw err;
      });
  },

  // Returns a promise that is resolved with a valid token for the current
  // user, or rejects if one can't be obtained.
  // NOTE: This does all the authentication for Sync - it both sets the
  // key bundle (ie, decryption keys) and does the token fetch. These 2
  // concepts could be decoupled, but there doesn't seem any value in that
  // currently.
  async _ensureValidToken(forceNewToken = false) {
    let signedInUser = await this.getSignedInUser();
    if (!signedInUser) {
      throw new Error("no user is logged in");
    }
    if (!signedInUser.verified) {
      throw new Error("user is not verified");
    }

    await this.asyncObserver.promiseObserversComplete();

    if (!forceNewToken && this._hasValidToken()) {
      this._log.trace("_ensureValidToken already has one");
      return this._token;
    }

    // We are going to grab a new token - re-use the same promise if we are
    // already fetching one.
    if (!this._ensureValidTokenPromise) {
      this._ensureValidTokenPromise = this.__ensureValidToken().finally(() => {
        this._ensureValidTokenPromise = null;
      });
    }
    return this._ensureValidTokenPromise;
  },

  async __ensureValidToken() {
    // reset this._token as a safety net to reduce the possibility of us
    // repeatedly attempting to use an invalid token if _fetchTokenForUser throws.
    this._token = null;
    try {
      let token = await this._fetchTokenForUser();
      this._token = token;
      // This is a little bit of a hack. The tokenserver tells us a HMACed version
      // of the FxA uid which we can use for metrics purposes without revealing the
      // user's true uid. It conceptually belongs to FxA but we get it from tokenserver
      // for legacy reasons. Hand it back to the FxA client code to deal with.
      this._fxaService.telemetry._setHashedUID(token.hashed_fxa_uid);
      return token;
    } finally {
      Services.obs.notifyObservers(null, "weave:service:login:got-hashed-id");
    }
  },

  getResourceAuthenticator() {
    return this._getAuthenticationHeader.bind(this);
  },

  /**
   * @return a Hawk HTTP Authorization Header, lightly wrapped, for the .uri
   * of a RESTRequest or AsyncResponse object.
   */
  async _getAuthenticationHeader(httpObject, method) {
    // Note that in failure states we return null, causing the request to be
    // made without authorization headers, thereby presumably causing a 401,
    // which causes Sync to log out. If we throw, this may not happen as
    // expected.
    try {
      await this._ensureValidToken();
    } catch (ex) {
      this._log.error("Failed to fetch a token for authentication", ex);
      return null;
    }
    if (!this._token) {
      return null;
    }
    let credentials = { id: this._token.id, key: this._token.key };
    method = method || httpObject.method;

    // Get the local clock offset from the Firefox Accounts server.  This should
    // be close to the offset from the storage server.
    let options = {
      now: this._now(),
      localtimeOffsetMsec: this._localtimeOffsetMsec,
      credentials,
    };

    let headerValue = await CryptoUtils.computeHAWK(
      httpObject.uri,
      method,
      options
    );
    return { headers: { authorization: headerValue.field } };
  },

  /**
   * Determine the cluster for the current user and update state.
   * Returns true if a new cluster URL was found and it is different from
   * the existing cluster URL, false otherwise.
   */
  async setCluster() {
    // Make sure we didn't get some unexpected response for the cluster.
    let cluster = await this._findCluster();
    this._log.debug("Cluster value = " + cluster);
    if (cluster == null) {
      return false;
    }

    // Convert from the funky "String object with additional properties" that
    // resource.js returns to a plain-old string.
    cluster = cluster.toString();
    // Don't update stuff if we already have the right cluster
    if (cluster == lazy.Weave.Service.clusterURL) {
      return false;
    }

    this._log.debug("Setting cluster to " + cluster);
    lazy.Weave.Service.clusterURL = cluster;

    return true;
  },

  async _findCluster() {
    try {
      // Ensure we are ready to authenticate and have a valid token.
      // We need to handle node reassignment here.  If we are being asked
      // for a clusterURL while the service already has a clusterURL, then
      // it's likely a 401 was received using the existing token - in which
      // case we just discard the existing token and fetch a new one.
      let forceNewToken = false;
      if (lazy.Weave.Service.clusterURL) {
        this._log.debug(
          "_findCluster has a pre-existing clusterURL, so fetching a new token token"
        );
        forceNewToken = true;
      }
      let token = await this._ensureValidToken(forceNewToken);
      let endpoint = token.endpoint;
      // For Sync 1.5 storage endpoints, we use the base endpoint verbatim.
      // However, it should end in "/" because we will extend it with
      // well known path components. So we add a "/" if it's missing.
      if (!endpoint.endsWith("/")) {
        endpoint += "/";
      }
      this._log.debug("_findCluster returning " + endpoint);
      return endpoint;
    } catch (err) {
      this._log.info("Failed to fetch the cluster URL", err);
      // service.js's verifyLogin() method will attempt to fetch a cluster
      // URL when it sees a 401.  If it gets null, it treats it as a "real"
      // auth error and sets Status.login to LOGIN_FAILED_LOGIN_REJECTED, which
      // in turn causes a notification bar to appear informing the user they
      // need to re-authenticate.
      // On the other hand, if fetching the cluster URL fails with an exception,
      // verifyLogin() assumes it is a transient error, and thus doesn't show
      // the notification bar under the assumption the issue will resolve
      // itself.
      // Thus:
      // * On a real 401, we must return null.
      // * On any other problem we must let an exception bubble up.
      if (err instanceof AuthenticationError) {
        return null;
      }
      throw err;
    }
  },
};
PK
!<3`�٢٢'modules/services-sync/telemetry.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

// Support for Sync-and-FxA-related telemetry, which is submitted in a special-purpose
// telemetry ping called the "sync ping", documented here:
//
//  ../../../toolkit/components/telemetry/docs/data/sync-ping.rst
//
// The sync ping contains identifiers that are linked to the user's Firefox Account
// and are separate from the main telemetry client_id, so this file is also responsible
// for ensuring that we can delete those pings upon user request, by plumbing its
// identifiers into the "deletion-request" ping.

import { Log } from "resource://gre/modules/Log.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Async: "resource://services-common/async.sys.mjs",
  AuthenticationError: "resource://services-sync/sync_auth.sys.mjs",
  FxAccounts: "resource://gre/modules/FxAccounts.sys.mjs",
  ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
  Observers: "resource://services-common/observers.sys.mjs",
  Resource: "resource://services-sync/resource.sys.mjs",
  Status: "resource://services-sync/status.sys.mjs",
  Svc: "resource://services-sync/util.sys.mjs",
  TelemetryController: "resource://gre/modules/TelemetryController.sys.mjs",
  TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
  TelemetryUtils: "resource://gre/modules/TelemetryUtils.sys.mjs",
  Weave: "resource://services-sync/main.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => {
  return ChromeUtils.importESModule(
    "resource://gre/modules/FxAccounts.sys.mjs"
  ).getFxAccountsSingleton();
});

import * as constants from "resource://services-sync/constants.sys.mjs";

ChromeUtils.defineLazyGetter(
  lazy,
  "WeaveService",
  () => Cc["@mozilla.org/weave/service;1"].getService().wrappedJSObject
);
const log = Log.repository.getLogger("Sync.Telemetry");

const TOPICS = [
  // For tracking change to account/device identifiers.
  "fxaccounts:new_device_id",
  "fxaccounts:onlogout",
  "weave:service:ready",
  "weave:service:login:got-hashed-id",

  // For whole-of-sync metrics.
  "weave:service:sync:start",
  "weave:service:sync:finish",
  "weave:service:sync:error",

  // For individual engine metrics.
  "weave:engine:sync:start",
  "weave:engine:sync:finish",
  "weave:engine:sync:error",
  "weave:engine:sync:applied",
  "weave:engine:sync:step",
  "weave:engine:sync:uploaded",
  "weave:engine:validate:finish",
  "weave:engine:validate:error",

  // For ad-hoc telemetry events.
  "weave:telemetry:event",
  "weave:telemetry:histogram",
  "fxa:telemetry:event",

  "weave:telemetry:migration",
];

const PING_FORMAT_VERSION = 1;

const EMPTY_UID = "0".repeat(32);

// The set of engines we record telemetry for - any other engines are ignored.
const ENGINES = new Set([
  "addons",
  "bookmarks",
  "clients",
  "forms",
  "history",
  "passwords",
  "prefs",
  "tabs",
  "extension-storage",
  "addresses",
  "creditcards",
]);

function tryGetMonotonicTimestamp() {
  try {
    return Services.telemetry.msSinceProcessStart();
  } catch (e) {
    log.warn("Unable to get a monotonic timestamp!");
    return -1;
  }
}

function timeDeltaFrom(monotonicStartTime) {
  let now = tryGetMonotonicTimestamp();
  if (monotonicStartTime !== -1 && now !== -1) {
    return Math.round(now - monotonicStartTime);
  }
  return -1;
}

const NS_ERROR_MODULE_BASE_OFFSET = 0x45;
const NS_ERROR_MODULE_NETWORK = 6;

// A reimplementation of NS_ERROR_GET_MODULE, which surprisingly doesn't seem
// to exist anywhere in .js code in a way that can be reused.
// This is taken from DownloadCore.sys.mjs.
function NS_ERROR_GET_MODULE(code) {
  return ((code & 0x7fff0000) >> 16) - NS_ERROR_MODULE_BASE_OFFSET;
}

// Converts extra integer fields to strings, rounds floats to three
// decimal places (nanosecond precision for timings), and removes profile
// directory paths and URLs from potential error messages.
function normalizeExtraTelemetryFields(extra) {
  let result = {};
  for (let key in extra) {
    let value = extra[key];
    let type = typeof value;
    if (type == "string") {
      result[key] = ErrorSanitizer.cleanErrorMessage(value);
    } else if (type == "number") {
      result[key] = Number.isInteger(value)
        ? value.toString(10)
        : value.toFixed(3);
    } else if (type != "undefined") {
      throw new TypeError(
        `Invalid type ${type} for extra telemetry field ${key}`
      );
    }
  }
  return lazy.ObjectUtils.isEmpty(result) ? undefined : result;
}

// Keps track of the counts of individual records fate during a sync cycle
// The main reason this is a class is to keep track of reasons individual records
// failure reasons without huge memory overhead.
export class SyncedRecordsTelemetry {
  // applied    => number of items that should be applied.
  // failed     => number of items that failed in this sync.
  // newFailed  => number of items that failed for the first time in this sync.
  // reconciled => number of items that were reconciled.
  // failedReasons => {name, count} of reasons a record failed
  incomingCounts = {
    applied: 0,
    failed: 0,
    newFailed: 0,
    reconciled: 0,
    failedReasons: null,
  };
  outgoingCounts = { failed: 0, sent: 0, failedReasons: null };

  addIncomingFailedReason(reason) {
    if (!this.incomingCounts.failedReasons) {
      this.incomingCounts.failedReasons = [];
    }
    let transformedReason = SyncTelemetry.transformError(reason);
    // Some errors like http/nss errors don't have an error object
    // those will be caught by the higher level telemetry
    if (!transformedReason.error) {
      return;
    }

    let index = this.incomingCounts.failedReasons.findIndex(
      reasons => reasons.name === transformedReason.error
    );

    if (index >= 0) {
      this.incomingCounts.failedReasons[index].count += 1;
    } else {
      this.incomingCounts.failedReasons.push({
        name: transformedReason.error,
        count: 1,
      });
    }
  }

  addOutgoingFailedReason(reason) {
    if (!this.outgoingCounts.failedReasons) {
      this.outgoingCounts.failedReasons = [];
    }
    let transformedReason = SyncTelemetry.transformError(reason);
    // Some errors like http/nss errors don't have an error object
    // those will be caught by the higher level telemetry
    if (!transformedReason.error) {
      return;
    }
    let index = this.outgoingCounts.failedReasons.findIndex(
      reasons => reasons.name === transformedReason.error
    );
    if (index >= 0) {
      this.outgoingCounts.failedReasons[index].count += 1;
    } else {
      this.outgoingCounts.failedReasons.push({
        name: transformedReason.error,
        count: 1,
      });
    }
  }
}

// The `ErrorSanitizer` has 2 main jobs:
// * Remove PII from errors, such as the profile dir or URLs the user might
//   have visited.
// * Normalize errors so different locales or operating systems etc don't
//   generate different messages for the same underlying error.
// * [TODO] Normalize errors so environmental factors don't influence message.
//   For example, timestamps or GUIDs should be replaced with something static.
export class ErrorSanitizer {
  // Things we normalize - this isn't exhaustive, but covers the common error messages we see.
  // Eg:
  // > Win error 112 during operation write on file [profileDir]\weave\addonsreconciler.json (Espacio en disco insuficiente. )
  // > Win error 112 during operation write on file [profileDir]\weave\addonsreconciler.json (Diskte yeterli yer yok. )
  // > <snip many other translations of the error>
  // > Unix error 28 during operation write on file [profileDir]/weave/addonsreconciler.json (No space left on device)
  // These tend to crowd out other errors we might care about (eg, top 5 errors for some engines are
  // variations of the "no space left on device")

  // Note that only errors that have same-but-different errors on Windows and Unix are here - we
  // still sanitize ones that aren't in these maps to remove the translations etc - eg,
  // `ERROR_SHARING_VIOLATION` doesn't really have a unix equivalent, so doesn't appear here, but
  // we still strip the translations to avoid the variants.
  static E_PERMISSION_DENIED = "OS error [Permission denied]";
  static E_NO_FILE_OR_DIR = "OS error [File/Path not found]";

  static DOMErrorSubstitutions = {
    NotFoundError: this.E_NO_FILE_OR_DIR,
    NotAllowedError: this.E_PERMISSION_DENIED,
  };

  // IOUtils error messages include the specific nsresult error code that caused them.
  static NS_ERROR_RE = new RegExp(/ \(NS_ERROR_.*\)$/);

  static #cleanOSErrorMessage(message, error = undefined) {
    if (DOMException.isInstance(error)) {
      const sub = this.DOMErrorSubstitutions[error.name];
      message = message.replaceAll("\\", "/");
      message = message.replace(this.NS_ERROR_RE, "");
      if (sub) {
        return `${sub} ${message}`;
      }
    }

    return message;
  }

  // A regex we can use to replace the profile dir in error messages. We use a
  // regexp so we can simply replace all case-insensitive occurences.
  // This escaping function is from:
  // https://developer.mozilla.org/en/docs/Web/JavaScript/Guide/Regular_Expressions
  static reProfileDir = new RegExp(
    PathUtils.profileDir.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"),
    "gi"
  );

  /**
   * Clean an error message, removing PII and normalizing OS-specific messages.
   *
   * @param {string} message The error message
   * @param {Error?} error The error class instance, if any.
   */
  static cleanErrorMessage(message, error = undefined) {
    // There's a chance the profiledir is in the error string which is PII we
    // want to avoid including in the ping.
    message = message.replace(this.reProfileDir, "[profileDir]");
    // MSG_INVALID_URL from /dom/bindings/Errors.msg -- no way to access this
    // directly from JS.
    if (message.endsWith("is not a valid URL.")) {
      message = "<URL> is not a valid URL.";
    }
    // Try to filter things that look somewhat like a URL (in that they contain a
    // colon in the middle of non-whitespace), in case anything else is including
    // these in error messages. Note that JSON.stringified stuff comes through
    // here, so we explicitly ignore double-quotes as well.
    message = message.replace(/[^\s"]+:[^\s"]+/g, "<URL>");

    // Anywhere that's normalized the guid in errors we can easily filter
    // to make it easier to aggregate these types of errors
    message = message.replace(/<guid: ([^>]+)>/g, "<GUID>");

    return this.#cleanOSErrorMessage(message, error);
  }
}

// This function validates the payload of a telemetry "event" - this can be
// removed once there are APIs available for the telemetry modules to collect
// these events (bug 1329530) - but for now we simulate that planned API as
// best we can.
function validateTelemetryEvent(eventDetails) {
  let { object, method, value, extra } = eventDetails;
  // Do do basic validation of the params - everything except "extra" must
  // be a string. method and object are required.
  if (
    typeof method != "string" ||
    typeof object != "string" ||
    (value && typeof value != "string") ||
    (extra && typeof extra != "object")
  ) {
    log.warn("Invalid event parameters - wrong types", eventDetails);
    return false;
  }
  // length checks.
  if (
    method.length > 20 ||
    object.length > 20 ||
    (value && value.length > 80)
  ) {
    log.warn("Invalid event parameters - wrong lengths", eventDetails);
    return false;
  }

  // extra can be falsey, or an object with string names and values.
  if (extra) {
    if (Object.keys(extra).length > 10) {
      log.warn("Invalid event parameters - too many extra keys", eventDetails);
      return false;
    }
    for (let [ename, evalue] of Object.entries(extra)) {
      if (
        typeof ename != "string" ||
        ename.length > 15 ||
        typeof evalue != "string" ||
        evalue.length > 85
      ) {
        log.warn(
          `Invalid event parameters: extra item "${ename} is invalid`,
          eventDetails
        );
        return false;
      }
    }
  }
  return true;
}

class EngineRecord {
  constructor(name) {
    // startTime is in ms from process start, but is monotonic (unlike Date.now())
    // so we need to keep both it and when.
    this.startTime = tryGetMonotonicTimestamp();
    this.name = name;

    // This allows cases like bookmarks-buffered to have a separate name from
    // the bookmarks engine.
    let engineImpl = lazy.Weave.Service.engineManager.get(name);
    if (engineImpl && engineImpl.overrideTelemetryName) {
      this.overrideTelemetryName = engineImpl.overrideTelemetryName;
    }
  }

  toJSON() {
    let result = { name: this.overrideTelemetryName || this.name };
    let properties = [
      "took",
      "status",
      "failureReason",
      "incoming",
      "outgoing",
      "validation",
      "steps",
    ];
    for (let property of properties) {
      result[property] = this[property];
    }
    return result;
  }

  finished(error) {
    let took = timeDeltaFrom(this.startTime);
    if (took > 0) {
      this.took = took;
    }
    if (error) {
      this.failureReason = SyncTelemetry.transformError(error);
    }
  }

  recordApplied(counts) {
    if (this.incoming) {
      log.error(
        `Incoming records applied multiple times for engine ${this.name}!`
      );
      return;
    }
    if (this.name === "clients" && !counts.failed) {
      // ignore successful application of client records
      // since otherwise they show up every time and are meaningless.
      return;
    }

    let incomingData = {};

    if (counts.failedReasons) {
      // sort the failed reasons in desc by count, then take top 10
      counts.failedReasons = counts.failedReasons
        .sort((a, b) => b.count - a.count)
        .slice(0, 10);
    }
    // Counts has extra stuff used for logging, but we only care about a few
    let properties = ["applied", "failed", "failedReasons"];
    // Only record non-zero properties and only record incoming at all if
    // there's at least one property we care about.
    for (let property of properties) {
      if (counts[property]) {
        incomingData[property] = counts[property];
        this.incoming = incomingData;
      }
    }
  }

  recordStep(stepResult) {
    let step = {
      name: stepResult.name,
    };
    if (stepResult.took > 0) {
      step.took = Math.round(stepResult.took);
    }
    if (stepResult.counts) {
      let counts = stepResult.counts.filter(({ count }) => count > 0);
      if (counts.length) {
        step.counts = counts;
      }
    }
    if (this.steps) {
      this.steps.push(step);
    } else {
      this.steps = [step];
    }
  }

  recordValidation(validationResult) {
    if (this.validation) {
      log.error(`Multiple validations occurred for engine ${this.name}!`);
      return;
    }
    let { problems, version, took, checked } = validationResult;
    let validation = {
      version: version || 0,
      checked: checked || 0,
    };
    if (took > 0) {
      validation.took = Math.round(took);
    }
    let summarized = problems.filter(({ count }) => count > 0);
    if (summarized.length) {
      validation.problems = summarized;
    }
    this.validation = validation;
  }

  recordValidationError(e) {
    if (this.validation) {
      log.error(`Multiple validations occurred for engine ${this.name}!`);
      return;
    }

    this.validation = {
      failureReason: SyncTelemetry.transformError(e),
    };
  }

  recordUploaded(counts) {
    if (counts.sent || counts.failed) {
      if (!this.outgoing) {
        this.outgoing = [];
      }
      if (counts.failedReasons) {
        // sort the failed reasons in desc by count, then take top 10
        counts.failedReasons = counts.failedReasons
          .sort((a, b) => b.count - a.count)
          .slice(0, 10);
      }
      this.outgoing.push({
        sent: counts.sent || undefined,
        failed: counts.failed || undefined,
        failedReasons: counts.failedReasons || undefined,
      });
    }
  }
}

// The record of a single "sync" - typically many of these are submitted in
// a single ping (ie, as a 'syncs' array)
export class SyncRecord {
  constructor(allowedEngines, why) {
    this.allowedEngines = allowedEngines;
    // Our failure reason. This property only exists in the generated ping if an
    // error actually occurred.
    this.failureReason = undefined;
    this.syncNodeType = null;
    this.when = Date.now();
    this.startTime = tryGetMonotonicTimestamp();
    this.took = 0; // will be set later.
    this.why = why;

    // All engines that have finished (ie, does not include the "current" one)
    // We omit this from the ping if it's empty.
    this.engines = [];
    // The engine that has started but not yet stopped.
    this.currentEngine = null;
  }

  toJSON() {
    let result = {
      when: this.when,
      took: this.took,
      failureReason: this.failureReason,
      status: this.status,
    };
    if (this.why) {
      result.why = this.why;
    }
    let engines = [];
    for (let engine of this.engines) {
      engines.push(engine.toJSON());
    }
    if (engines.length) {
      result.engines = engines;
    }
    return result;
  }

  finished(error) {
    this.took = timeDeltaFrom(this.startTime);
    if (this.currentEngine != null) {
      log.error(
        "Finished called for the sync before the current engine finished"
      );
      this.currentEngine.finished(null);
      this.onEngineStop(this.currentEngine.name);
    }
    if (error) {
      this.failureReason = SyncTelemetry.transformError(error);
    }

    this.syncNodeType = lazy.Weave.Service.identity.telemetryNodeType;

    // Check for engine statuses. -- We do this now, and not in engine.finished
    // to make sure any statuses that get set "late" are recorded
    for (let engine of this.engines) {
      let status = lazy.Status.engines[engine.name];
      if (status && status !== constants.ENGINE_SUCCEEDED) {
        engine.status = status;
      }
    }

    let statusObject = {};

    let serviceStatus = lazy.Status.service;
    if (serviceStatus && serviceStatus !== constants.STATUS_OK) {
      statusObject.service = serviceStatus;
      this.status = statusObject;
    }
    let syncStatus = lazy.Status.sync;
    if (syncStatus && syncStatus !== constants.SYNC_SUCCEEDED) {
      statusObject.sync = syncStatus;
      this.status = statusObject;
    }
  }

  onEngineStart(engineName) {
    if (this._shouldIgnoreEngine(engineName, false)) {
      return;
    }

    if (this.currentEngine) {
      log.error(
        `Being told that engine ${engineName} has started, but current engine ${this.currentEngine.name} hasn't stopped`
      );
      // Just discard the current engine rather than making up data for it.
    }
    this.currentEngine = new EngineRecord(engineName);
  }

  onEngineStop(engineName, error) {
    // We only care if it's the current engine if we have a current engine.
    if (this._shouldIgnoreEngine(engineName, !!this.currentEngine)) {
      return;
    }
    if (!this.currentEngine) {
      // It's possible for us to get an error before the start message of an engine
      // (somehow), in which case we still want to record that error.
      if (!error) {
        return;
      }
      log.error(
        `Error triggered on ${engineName} when no current engine exists: ${error}`
      );
      this.currentEngine = new EngineRecord(engineName);
    }
    this.currentEngine.finished(error);
    this.engines.push(this.currentEngine);
    this.currentEngine = null;
  }

  onEngineApplied(engineName, counts) {
    if (this._shouldIgnoreEngine(engineName)) {
      return;
    }
    this.currentEngine.recordApplied(counts);
  }

  onEngineStep(engineName, step) {
    if (this._shouldIgnoreEngine(engineName)) {
      return;
    }
    this.currentEngine.recordStep(step);
  }

  onEngineValidated(engineName, validationData) {
    if (this._shouldIgnoreEngine(engineName, false)) {
      return;
    }
    let engine = this.engines.find(e => e.name === engineName);
    if (
      !engine &&
      this.currentEngine &&
      engineName === this.currentEngine.name
    ) {
      engine = this.currentEngine;
    }
    if (engine) {
      engine.recordValidation(validationData);
    } else {
      log.warn(
        `Validation event triggered for engine ${engineName}, which hasn't been synced!`
      );
    }
  }

  onEngineValidateError(engineName, error) {
    if (this._shouldIgnoreEngine(engineName, false)) {
      return;
    }
    let engine = this.engines.find(e => e.name === engineName);
    if (
      !engine &&
      this.currentEngine &&
      engineName === this.currentEngine.name
    ) {
      engine = this.currentEngine;
    }
    if (engine) {
      engine.recordValidationError(error);
    } else {
      log.warn(
        `Validation failure event triggered for engine ${engineName}, which hasn't been synced!`
      );
    }
  }

  onEngineUploaded(engineName, counts) {
    if (this._shouldIgnoreEngine(engineName)) {
      return;
    }
    this.currentEngine.recordUploaded(counts);
  }

  _shouldIgnoreEngine(engineName, shouldBeCurrent = true) {
    if (!this.allowedEngines.has(engineName)) {
      log.info(
        `Notification for engine ${engineName}, but we aren't recording telemetry for it`
      );
      return true;
    }
    if (shouldBeCurrent) {
      if (!this.currentEngine || engineName != this.currentEngine.name) {
        log.info(`Notification for engine ${engineName} but it isn't current`);
        return true;
      }
    }
    return false;
  }
}

// The entire "sync ping" - it includes all the syncs, events etc recorded in
// the ping.
class SyncTelemetryImpl {
  constructor(allowedEngines) {
    log.manageLevelFromPref("services.sync.log.logger.telemetry");
    // This is accessible so we can enable custom engines during tests.
    this.allowedEngines = allowedEngines;
    this.current = null;
    this.setupObservers();

    this.payloads = [];
    this.discarded = 0;
    this.events = [];
    this.histograms = {};
    this.migrations = [];
    this.maxEventsCount = lazy.Svc.PrefBranch.getIntPref(
      "telemetry.maxEventsCount",
      1000
    );
    this.maxPayloadCount = lazy.Svc.PrefBranch.getIntPref(
      "telemetry.maxPayloadCount"
    );
    this.submissionInterval =
      lazy.Svc.PrefBranch.getIntPref("telemetry.submissionInterval") * 1000;
    this.lastSubmissionTime = Services.telemetry.msSinceProcessStart();
    this.lastUID = EMPTY_UID;
    this.lastSyncNodeType = null;
    this.currentSyncNodeType = null;
    // Note that the sessionStartDate is somewhat arbitrary - the telemetry
    // modules themselves just use `new Date()`. This means that our startDate
    // isn't going to be the same as the sessionStartDate in the main pings,
    // but that's OK for now - if it's a problem we'd need to change the
    // telemetry modules to expose what it thinks the sessionStartDate is.
    let sessionStartDate = new Date();
    this.sessionStartDate = lazy.TelemetryUtils.toLocalTimeISOString(
      lazy.TelemetryUtils.truncateToHours(sessionStartDate)
    );
    lazy.TelemetryController.registerSyncPingShutdown(() => this.shutdown());
  }

  sanitizeFxaDeviceId(deviceId) {
    return lazy.fxAccounts.telemetry.sanitizeDeviceId(deviceId);
  }

  prepareFxaDevices(devices) {
    // For non-sync users, the data per device is limited -- just an id and a
    // type (and not even the id yet). For sync users, if we can correctly map
    // the fxaDevice to a sync device, then we can get os and version info,
    // which would be quite unfortunate to lose.
    let extraInfoMap = new Map();
    if (this.syncIsEnabled()) {
      for (let client of this.getClientsEngineRecords()) {
        if (client.fxaDeviceId) {
          extraInfoMap.set(client.fxaDeviceId, {
            os: client.os,
            version: client.version,
            syncID: this.sanitizeFxaDeviceId(client.id),
          });
        }
      }
    }
    // Finally, sanitize and convert to the proper format.
    return devices.map(d => {
      let { os, version, syncID } = extraInfoMap.get(d.id) || {
        os: undefined,
        version: undefined,
        syncID: undefined,
      };
      return {
        id: this.sanitizeFxaDeviceId(d.id) || EMPTY_UID,
        type: d.type,
        os,
        version,
        syncID,
      };
    });
  }

  syncIsEnabled() {
    return lazy.WeaveService.enabled && lazy.WeaveService.ready;
  }

  // Separate for testing.
  getClientsEngineRecords() {
    if (!this.syncIsEnabled()) {
      throw new Error("Bug: syncIsEnabled() must be true, check it first");
    }
    return lazy.Weave.Service.clientsEngine.remoteClients;
  }

  updateFxaDevices(devices) {
    if (!devices) {
      return {};
    }
    let me = devices.find(d => d.isCurrentDevice);
    let id = me ? this.sanitizeFxaDeviceId(me.id) : undefined;
    let cleanDevices = this.prepareFxaDevices(devices);
    return { deviceID: id, devices: cleanDevices };
  }

  getFxaDevices() {
    return lazy.fxAccounts.device.recentDeviceList;
  }

  getPingJSON(reason) {
    let { devices, deviceID } = this.updateFxaDevices(this.getFxaDevices());
    return {
      os: lazy.TelemetryEnvironment.currentEnvironment.system.os,
      why: reason,
      devices,
      discarded: this.discarded || undefined,
      version: PING_FORMAT_VERSION,
      syncs: this.payloads.slice(),
      uid: this.lastUID,
      syncNodeType: this.lastSyncNodeType || undefined,
      deviceID,
      sessionStartDate: this.sessionStartDate,
      events: !this.events.length ? undefined : this.events,
      migrations: !this.migrations.length ? undefined : this.migrations,
      histograms: !Object.keys(this.histograms).length
        ? undefined
        : this.histograms,
    };
  }

  _addMigrationRecord(type, info) {
    log.debug("Saw telemetry migration info", type, info);
    // Updates to this need to be documented in `sync-ping.rst`
    switch (type) {
      case "webext-storage":
        this.migrations.push({
          type: "webext-storage",
          entries: +info.entries,
          entriesSuccessful: +info.entries_successful,
          extensions: +info.extensions,
          extensionsSuccessful: +info.extensions_successful,
          openFailure: !!info.open_failure,
        });
        break;
      default:
        throw new Error("Bug: Unknown migration record type " + type);
    }
  }

  finish(reason) {
    // Note that we might be in the middle of a sync right now, and so we don't
    // want to touch this.current.
    let result = this.getPingJSON(reason);
    this.payloads = [];
    this.discarded = 0;
    this.events = [];
    this.migrations = [];
    this.histograms = {};
    this.submit(result);
  }

  setupObservers() {
    for (let topic of TOPICS) {
      lazy.Observers.add(topic, this, this);
    }
  }

  shutdown() {
    this.finish("shutdown");
    for (let topic of TOPICS) {
      lazy.Observers.remove(topic, this, this);
    }
  }

  submit(record) {
    if (!this.isProductionSyncUser()) {
      return false;
    }
    // We still call submit() with possibly illegal payloads so that tests can
    // know that the ping was built. We don't end up submitting them, however.
    let numEvents = record.events ? record.events.length : 0;
    let numMigrations = record.migrations ? record.migrations.length : 0;
    if (record.syncs.length || numEvents || numMigrations) {
      log.trace(
        `submitting ${record.syncs.length} sync record(s) and ` +
          `${numEvents} event(s) to telemetry`
      );
      lazy.TelemetryController.submitExternalPing("sync", record, {
        usePingSender: true,
      }).catch(err => {
        log.error("failed to submit ping", err);
      });
      return true;
    }
    return false;
  }

  isProductionSyncUser() {
    // If FxA isn't production then we treat sync as not being production.
    // Further, there's the deprecated "services.sync.tokenServerURI" pref we
    // need to consider - fxa doesn't consider that as if that's the only
    // pref set, they *are* running a production fxa, just not production sync.
    if (
      !lazy.FxAccounts.config.isProductionConfig() ||
      Services.prefs.prefHasUserValue("services.sync.tokenServerURI")
    ) {
      log.trace(`Not sending telemetry ping for self-hosted Sync user`);
      return false;
    }
    return true;
  }

  onSyncStarted(data) {
    const why = data && JSON.parse(data).why;
    if (this.current) {
      log.warn(
        "Observed weave:service:sync:start, but we're already recording a sync!"
      );
      // Just discard the old record, consistent with our handling of engines, above.
      this.current = null;
    }
    this.current = new SyncRecord(this.allowedEngines, why);
  }

  // We need to ensure that the telemetry `deletion-request` ping always contains the user's
  // current sync device ID, because if the user opts out of telemetry then the deletion ping
  // will be immediately triggered for sending, and we won't have a chance to fill it in later.
  // This keeps the `deletion-ping` up-to-date when the user's account state changes.
  onAccountInitOrChange() {
    // We don't submit sync pings for self-hosters, so don't need to collect their device ids either.
    if (!this.isProductionSyncUser()) {
      return;
    }
    // Awkwardly async, but no need to await. If the user's account state changes while
    // this promise is in flight, it will reject and we won't record any data in the ping.
    // (And a new notification will trigger us to try again with the new state).
    lazy.fxAccounts.device
      .getLocalId()
      .then(deviceId => {
        let sanitizedDeviceId =
          lazy.fxAccounts.telemetry.sanitizeDeviceId(deviceId);
        // In the past we did not persist the FxA metrics identifiers to disk,
        // so this might be missing until we can fetch it from the server for the
        // first time. There will be a fresh notification tirggered when it's available.
        if (sanitizedDeviceId) {
          // Sanitized device ids are 64 characters long, but telemetry limits scalar strings to 50.
          // The first 32 chars are sufficient to uniquely identify the device, so just send those.
          // It's hard to change the sync ping itself to only send 32 chars, to b/w compat reasons.
          sanitizedDeviceId = sanitizedDeviceId.substr(0, 32);
          Services.telemetry.scalarSet(
            "deletion.request.sync_device_id",
            sanitizedDeviceId
          );
        }
      })
      .catch(err => {
        log.warn(
          `Failed to set sync identifiers in the deletion-request ping: ${err}`
        );
      });
  }

  // This keeps the `deletion-request` ping up-to-date when the user signs out,
  // clearing the now-nonexistent sync device id.
  onAccountLogout() {
    Services.telemetry.scalarSet("deletion.request.sync_device_id", "");
  }

  _checkCurrent(topic) {
    if (!this.current) {
      // This is only `info` because it happens when we do a tabs "quick-write"
      log.info(
        `Observed notification ${topic} but no current sync is being recorded.`
      );
      return false;
    }
    return true;
  }

  _shouldSubmitForDataChange() {
    let newID = lazy.fxAccounts.telemetry.getSanitizedUID() || EMPTY_UID;
    let oldID = this.lastUID;
    if (
      newID != EMPTY_UID &&
      oldID != EMPTY_UID &&
      // Both are "real" uids, so we care if they've changed.
      newID != oldID
    ) {
      log.trace(
        `shouldSubmitForDataChange - uid from '${oldID}' -> '${newID}'`
      );
      return true;
    }
    // We've gone from knowing one of the ids to not knowing it (which we
    // ignore) or we've gone from not knowing it to knowing it (which is fine),
    // Now check the node type because a change there also means we should
    // submit.
    if (
      this.lastSyncNodeType &&
      this.currentSyncNodeType != this.lastSyncNodeType
    ) {
      log.trace(
        `shouldSubmitForDataChange - nodeType from '${this.lastSyncNodeType}' -> '${this.currentSyncNodeType}'`
      );
      return true;
    }
    log.trace("shouldSubmitForDataChange - no need to submit");
    return false;
  }

  maybeSubmitForDataChange() {
    if (this._shouldSubmitForDataChange()) {
      log.info(
        "Early submission of sync telemetry due to changed IDs/NodeType"
      );
      this.finish("idchange"); // this actually submits.
      this.lastSubmissionTime = Services.telemetry.msSinceProcessStart();
    }

    // Only update the last UIDs if we actually know them.
    let current_uid = lazy.fxAccounts.telemetry.getSanitizedUID();
    if (current_uid) {
      this.lastUID = current_uid;
    }
    if (this.currentSyncNodeType) {
      this.lastSyncNodeType = this.currentSyncNodeType;
    }
  }

  maybeSubmitForInterval() {
    // We want to submit the ping every `this.submissionInterval` but only when
    // there's no current sync in progress, otherwise we may end up submitting
    // the sync and the events caused by it in different pings.
    if (
      this.current == null &&
      Services.telemetry.msSinceProcessStart() - this.lastSubmissionTime >
        this.submissionInterval
    ) {
      this.finish("schedule");
      this.lastSubmissionTime = Services.telemetry.msSinceProcessStart();
    }
  }

  onSyncFinished(error) {
    if (!this.current) {
      log.warn("onSyncFinished but we aren't recording");
      return;
    }
    this.current.finished(error);
    this.currentSyncNodeType = this.current.syncNodeType;
    let current = this.current;
    this.current = null;
    this.takeTelemetryRecord(current);
  }

  takeTelemetryRecord(record) {
    // We check for "data change" before appending the current sync to payloads,
    // as it is the current sync which has the data with the new data, and thus
    // must go in the *next* submission.
    this.maybeSubmitForDataChange();
    if (this.payloads.length < this.maxPayloadCount) {
      this.payloads.push(record.toJSON());
    } else {
      ++this.discarded;
    }
    // If we are submitting due to timing, it's desirable that the most recent
    // sync is included, so we check after appending the record.
    this.maybeSubmitForInterval();
  }

  _addHistogram(hist) {
    let histogram = Services.telemetry.getHistogramById(hist);
    let s = histogram.snapshot();
    this.histograms[hist] = s;
  }

  _recordEvent(eventDetails) {
    this.maybeSubmitForDataChange();

    if (this.events.length >= this.maxEventsCount) {
      log.warn("discarding event - already queued our maximum", eventDetails);
      return;
    }

    let { object, method, value, extra } = eventDetails;
    if (extra) {
      extra = normalizeExtraTelemetryFields(extra);
      eventDetails = { object, method, value, extra };
    }

    if (!validateTelemetryEvent(eventDetails)) {
      // we've already logged what the problem is...
      return;
    }
    log.debug("recording event", eventDetails);

    if (extra && lazy.Resource.serverTime && !extra.serverTime) {
      extra.serverTime = String(lazy.Resource.serverTime);
    }
    let category = "sync";
    let ts = Math.floor(tryGetMonotonicTimestamp());

    // An event record is a simple array with at least 4 items.
    let event = [ts, category, method, object];
    // It may have up to 6 elements if |extra| is defined
    if (value) {
      event.push(value);
      if (extra) {
        event.push(extra);
      }
    } else if (extra) {
      event.push(null); // a null for the empty value.
      event.push(extra);
    }
    this.events.push(event);
    this.maybeSubmitForInterval();
  }

  observe(subject, topic, data) {
    log.trace(`observed ${topic} ${data}`);

    switch (topic) {
      case "weave:service:ready":
      case "weave:service:login:got-hashed-id":
      case "fxaccounts:new_device_id":
        this.onAccountInitOrChange();
        break;

      case "fxaccounts:onlogout":
        this.onAccountLogout();
        break;

      /* sync itself state changes */
      case "weave:service:sync:start":
        this.onSyncStarted(data);
        break;

      case "weave:service:sync:finish":
        if (this._checkCurrent(topic)) {
          this.onSyncFinished(null);
        }
        break;

      case "weave:service:sync:error":
        // argument needs to be truthy (this should always be the case)
        this.onSyncFinished(subject || "Unknown");
        break;

      /* engine sync state changes */
      case "weave:engine:sync:start":
        if (this._checkCurrent(topic)) {
          this.current.onEngineStart(data);
        }
        break;
      case "weave:engine:sync:finish":
        if (this._checkCurrent(topic)) {
          this.current.onEngineStop(data, null);
        }
        break;

      case "weave:engine:sync:error":
        if (this._checkCurrent(topic)) {
          // argument needs to be truthy (this should always be the case)
          this.current.onEngineStop(data, subject || "Unknown");
        }
        break;

      /* engine counts */
      case "weave:engine:sync:applied":
        if (this._checkCurrent(topic)) {
          this.current.onEngineApplied(data, subject);
        }
        break;

      case "weave:engine:sync:step":
        if (this._checkCurrent(topic)) {
          this.current.onEngineStep(data, subject);
        }
        break;

      case "weave:engine:sync:uploaded":
        if (this._checkCurrent(topic)) {
          this.current.onEngineUploaded(data, subject);
        }
        break;

      case "weave:engine:validate:finish":
        if (this._checkCurrent(topic)) {
          this.current.onEngineValidated(data, subject);
        }
        break;

      case "weave:engine:validate:error":
        if (this._checkCurrent(topic)) {
          this.current.onEngineValidateError(data, subject || "Unknown");
        }
        break;

      case "weave:telemetry:event":
      case "fxa:telemetry:event":
        this._recordEvent(subject);
        break;

      case "weave:telemetry:histogram":
        this._addHistogram(data);
        break;

      case "weave:telemetry:migration":
        this._addMigrationRecord(data, subject);
        break;

      default:
        log.warn(`unexpected observer topic ${topic}`);
        break;
    }
  }

  // Transform an exception into a standard description. Exposed here for when
  // this module isn't directly responsible for knowing the transform should
  // happen (for example, when including an error in the |extra| field of
  // event telemetry)
  transformError(error) {
    // Certain parts of sync will use this pattern as a way to communicate to
    // processIncoming to abort the processing. However, there's no guarantee
    // this can only happen then.
    if (typeof error == "object" && error.code && error.cause) {
      error = error.cause;
    }
    if (lazy.Async.isShutdownException(error)) {
      return { name: "shutdownerror" };
    }

    if (typeof error === "string") {
      if (error.startsWith("error.")) {
        // This is hacky, but I can't imagine that it's not also accurate.
        return { name: "othererror", error };
      }
      error = ErrorSanitizer.cleanErrorMessage(error);
      return { name: "unexpectederror", error };
    }

    if (error instanceof lazy.AuthenticationError) {
      return { name: "autherror", from: error.source };
    }

    if (DOMException.isInstance(error)) {
      return {
        name: "unexpectederror",
        error: ErrorSanitizer.cleanErrorMessage(error.message, error),
      };
    }

    let httpCode =
      error.status || (error.response && error.response.status) || error.code;

    if (httpCode) {
      return { name: "httperror", code: httpCode };
    }

    if (error.failureCode) {
      return { name: "othererror", error: error.failureCode };
    }

    if (error.result) {
      // many "nsresult" errors are actually network errors - if they are
      // associated with the "network" module we assume that's true.
      // We also assume NS_ERROR_ABORT is such an error - for almost everything
      // we care about, it acually is (eg, if the connection fails early enough
      // or if we have a captive portal etc) - we don't lose anything by this
      // assumption, it's just that the error will no longer be in the "nserror"
      // category, so our analysis can still find them.
      if (
        error.result == Cr.NS_ERROR_ABORT ||
        NS_ERROR_GET_MODULE(error.result) == NS_ERROR_MODULE_NETWORK
      ) {
        return { name: "httperror", code: error.result };
      }
      return { name: "nserror", code: error.result };
    }
    // It's probably an Error object, but it also could be some
    // other object that may or may not override toString to do
    // something useful.
    let msg = String(error);
    if (msg.startsWith("[object")) {
      // Nothing useful in the default, check for a string "message" property.
      if (typeof error.message == "string") {
        msg = String(error.message);
      } else {
        // Hopefully it won't come to this...
        msg = JSON.stringify(error);
      }
    }
    return {
      name: "unexpectederror",
      error: ErrorSanitizer.cleanErrorMessage(msg),
    };
  }
}

export var SyncTelemetry = new SyncTelemetryImpl(ENGINES);
PK
!<`�8��T�T"modules/services-sync/util.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Observers } from "resource://services-common/observers.sys.mjs";

import { CommonUtils } from "resource://services-common/utils.sys.mjs";
import { CryptoUtils } from "resource://services-crypto/utils.sys.mjs";

import {
  DEVICE_TYPE_DESKTOP,
  MAXIMUM_BACKOFF_INTERVAL,
  PREFS_BRANCH,
  SYNC_KEY_DECODED_LENGTH,
  SYNC_KEY_ENCODED_LENGTH,
  WEAVE_VERSION,
} from "resource://services-sync/constants.sys.mjs";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};
import * as FxAccountsCommon from "resource://gre/modules/FxAccountsCommon.sys.mjs";

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "cryptoSDR",
  "@mozilla.org/login-manager/crypto/SDR;1",
  "nsILoginManagerCrypto"
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "localDeviceType",
  "services.sync.client.type",
  DEVICE_TYPE_DESKTOP
);

/*
 * Custom exception types.
 */
class LockException extends Error {
  constructor(message) {
    super(message);
    this.name = "LockException";
  }
}

class HMACMismatch extends Error {
  constructor(message) {
    super(message);
    this.name = "HMACMismatch";
  }
}

/*
 * Utility functions
 */
export var Utils = {
  // Aliases from CryptoUtils.
  generateRandomBytesLegacy: CryptoUtils.generateRandomBytesLegacy,
  computeHTTPMACSHA1: CryptoUtils.computeHTTPMACSHA1,
  digestUTF8: CryptoUtils.digestUTF8,
  digestBytes: CryptoUtils.digestBytes,
  sha256: CryptoUtils.sha256,
  hkdfExpand: CryptoUtils.hkdfExpand,
  pbkdf2Generate: CryptoUtils.pbkdf2Generate,
  getHTTPMACSHA1Header: CryptoUtils.getHTTPMACSHA1Header,

  /**
   * The string to use as the base User-Agent in Sync requests.
   * This string will look something like
   *
   *   Firefox/49.0a1 (Windows NT 6.1; WOW64; rv:46.0) FxSync/1.51.0.20160516142357.desktop
   */
  _userAgent: null,
  get userAgent() {
    if (!this._userAgent) {
      let hph = Cc["@mozilla.org/network/protocol;1?name=http"].getService(
        Ci.nsIHttpProtocolHandler
      );
      /* eslint-disable no-multi-spaces */
      this._userAgent =
        Services.appinfo.name +
        "/" +
        Services.appinfo.version + // Product.
        " (" +
        hph.oscpu +
        ")" + // (oscpu)
        " FxSync/" +
        WEAVE_VERSION +
        "." + // Sync.
        Services.appinfo.appBuildID +
        "."; // Build.
      /* eslint-enable no-multi-spaces */
    }
    return this._userAgent + lazy.localDeviceType;
  },

  /**
   * Wrap a [promise-returning] function to catch all exceptions and log them.
   *
   * @usage MyObj._catch = Utils.catch;
   *        MyObj.foo = function() { this._catch(func)(); }
   *
   * Optionally pass a function which will be called if an
   * exception occurs.
   */
  catch(func, exceptionCallback) {
    let thisArg = this;
    return async function WrappedCatch() {
      try {
        return await func.call(thisArg);
      } catch (ex) {
        thisArg._log.debug(
          "Exception calling " + (func.name || "anonymous function"),
          ex
        );
        if (exceptionCallback) {
          return exceptionCallback.call(thisArg, ex);
        }
        return null;
      }
    };
  },

  throwLockException(label) {
    throw new LockException(`Could not acquire lock. Label: "${label}".`);
  },

  /**
   * Wrap a [promise-returning] function to call lock before calling the function
   * then unlock when it finishes executing or if it threw an error.
   *
   * @usage MyObj._lock = Utils.lock;
   *        MyObj.foo = async function() { await this._lock(func)(); }
   */
  lock(label, func) {
    let thisArg = this;
    return async function WrappedLock() {
      if (!thisArg.lock()) {
        Utils.throwLockException(label);
      }

      try {
        return await func.call(thisArg);
      } finally {
        thisArg.unlock();
      }
    };
  },

  isLockException: function isLockException(ex) {
    return ex instanceof LockException;
  },

  /**
   * Wrap [promise-returning] functions to notify when it starts and
   * finishes executing or if it threw an error.
   *
   * The message is a combination of a provided prefix, the local name, and
   * the event. Possible events are: "start", "finish", "error". The subject
   * is the function's return value on "finish" or the caught exception on
   * "error". The data argument is the predefined data value.
   *
   * Example:
   *
   * @usage function MyObj(name) {
   *          this.name = name;
   *          this._notify = Utils.notify("obj:");
   *        }
   *        MyObj.prototype = {
   *          foo: function() this._notify("func", "data-arg", async function () {
   *            //...
   *          }(),
   *        };
   */
  notify(prefix) {
    return function NotifyMaker(name, data, func) {
      let thisArg = this;
      let notify = function (state, subject) {
        let mesg = prefix + name + ":" + state;
        thisArg._log.trace("Event: " + mesg);
        Observers.notify(mesg, subject, data);
      };

      return async function WrappedNotify() {
        notify("start", null);
        try {
          let ret = await func.call(thisArg);
          notify("finish", ret);
          return ret;
        } catch (ex) {
          notify("error", ex);
          throw ex;
        }
      };
    };
  },

  /**
   * GUIDs are 9 random bytes encoded with base64url (RFC 4648).
   * That makes them 12 characters long with 72 bits of entropy.
   */
  makeGUID: function makeGUID() {
    return CommonUtils.encodeBase64URL(Utils.generateRandomBytesLegacy(9));
  },

  _base64url_regex: /^[-abcdefghijklmnopqrstuvwxyz0123456789_]{12}$/i,
  checkGUID: function checkGUID(guid) {
    return !!guid && this._base64url_regex.test(guid);
  },

  /**
   * Add a simple getter/setter to an object that defers access of a property
   * to an inner property.
   *
   * @param obj
   *        Object to add properties to defer in its prototype
   * @param defer
   *        Property of obj to defer to
   * @param prop
   *        Property name to defer (or an array of property names)
   */
  deferGetSet: function Utils_deferGetSet(obj, defer, prop) {
    if (Array.isArray(prop)) {
      return prop.map(prop => Utils.deferGetSet(obj, defer, prop));
    }

    let prot = obj.prototype;

    // Create a getter if it doesn't exist yet
    if (!prot.__lookupGetter__(prop)) {
      prot.__defineGetter__(prop, function () {
        return this[defer][prop];
      });
    }

    // Create a setter if it doesn't exist yet
    if (!prot.__lookupSetter__(prop)) {
      prot.__defineSetter__(prop, function (val) {
        this[defer][prop] = val;
      });
    }
  },

  deepEquals: function eq(a, b) {
    // If they're triple equals, then it must be equals!
    if (a === b) {
      return true;
    }

    // If they weren't equal, they must be objects to be different
    if (typeof a != "object" || typeof b != "object") {
      return false;
    }

    // But null objects won't have properties to compare
    if (a === null || b === null) {
      return false;
    }

    // Make sure all of a's keys have a matching value in b
    for (let k in a) {
      if (!eq(a[k], b[k])) {
        return false;
      }
    }

    // Do the same for b's keys but skip those that we already checked
    for (let k in b) {
      if (!(k in a) && !eq(a[k], b[k])) {
        return false;
      }
    }

    return true;
  },

  // Generator and discriminator for HMAC exceptions.
  // Split these out in case we want to make them richer in future, and to
  // avoid inevitable confusion if the message changes.
  throwHMACMismatch: function throwHMACMismatch(shouldBe, is) {
    throw new HMACMismatch(
      `Record SHA256 HMAC mismatch: should be ${shouldBe}, is ${is}`
    );
  },

  isHMACMismatch: function isHMACMismatch(ex) {
    return ex instanceof HMACMismatch;
  },

  /**
   * Turn RFC 4648 base32 into our own user-friendly version.
   *   ABCDEFGHIJKLMNOPQRSTUVWXYZ234567
   * becomes
   *   abcdefghijk8mn9pqrstuvwxyz234567
   */
  base32ToFriendly: function base32ToFriendly(input) {
    return input.toLowerCase().replace(/l/g, "8").replace(/o/g, "9");
  },

  base32FromFriendly: function base32FromFriendly(input) {
    return input.toUpperCase().replace(/8/g, "L").replace(/9/g, "O");
  },

  /**
   * Key manipulation.
   */

  // Return an octet string in friendly base32 *with no trailing =*.
  encodeKeyBase32: function encodeKeyBase32(keyData) {
    return Utils.base32ToFriendly(CommonUtils.encodeBase32(keyData)).slice(
      0,
      SYNC_KEY_ENCODED_LENGTH
    );
  },

  decodeKeyBase32: function decodeKeyBase32(encoded) {
    return CommonUtils.decodeBase32(
      Utils.base32FromFriendly(Utils.normalizePassphrase(encoded))
    ).slice(0, SYNC_KEY_DECODED_LENGTH);
  },

  jsonFilePath(...args) {
    let [fileName] = args.splice(-1);

    return PathUtils.join(
      PathUtils.profileDir,
      "weave",
      ...args,
      `${fileName}.json`
    );
  },

  /**
   * Load a JSON file from disk in the profile directory.
   *
   * @param filePath
   *        JSON file path load from profile. Loaded file will be
   *        extension.
   * @param that
   *        Object to use for logging.
   *
   * @return Promise<>
   *        Promise resolved when the write has been performed.
   */
  async jsonLoad(filePath, that) {
    let path;
    if (Array.isArray(filePath)) {
      path = Utils.jsonFilePath(...filePath);
    } else {
      path = Utils.jsonFilePath(filePath);
    }

    if (that._log && that._log.trace) {
      that._log.trace("Loading json from disk: " + path);
    }

    try {
      return await IOUtils.readJSON(path);
    } catch (e) {
      if (!DOMException.isInstance(e) || e.name !== "NotFoundError") {
        if (that._log) {
          that._log.debug("Failed to load json", e);
        }
      }
      return null;
    }
  },

  /**
   * Save a json-able object to disk in the profile directory.
   *
   * @param filePath
   *        JSON file path save to <filePath>.json
   * @param that
   *        Object to use for logging.
   * @param obj
   *        Function to provide json-able object to save. If this isn't a
   *        function, it'll be used as the object to make a json string.*
   *        Function called when the write has been performed. Optional.
   *
   * @return Promise<>
   *        Promise resolved when the write has been performed.
   */
  async jsonSave(filePath, that, obj) {
    let path = PathUtils.join(
      PathUtils.profileDir,
      "weave",
      ...(filePath + ".json").split("/")
    );
    let dir = PathUtils.parent(path);

    await IOUtils.makeDirectory(dir, { createAncestors: true });

    if (that._log) {
      that._log.trace("Saving json to disk: " + path);
    }

    let json = typeof obj == "function" ? obj.call(that) : obj;

    return IOUtils.writeJSON(path, json);
  },

  /**
   * Helper utility function to fit an array of records so that when serialized,
   * they will be within payloadSizeMaxBytes. Returns a new array without the
   * items.
   *
   * Note: This shouldn't be used for extremely large record sizes as
   * it uses JSON.stringify, which could lead to a heavy performance hit.
   * See Bug 1815151 for more details.
   *
   */
  tryFitItems(records, payloadSizeMaxBytes) {
    // Copy this so that callers don't have to do it in advance.
    records = records.slice();
    let encoder = Utils.utf8Encoder;
    const computeSerializedSize = () =>
      encoder.encode(JSON.stringify(records)).byteLength;
    // Figure out how many records we can pack into a payload.
    // We use byteLength here because the data is not encrypted in ascii yet.
    let size = computeSerializedSize();
    // See bug 535326 comment 8 for an explanation of the estimation
    const maxSerializedSize = (payloadSizeMaxBytes / 4) * 3 - 1500;
    if (maxSerializedSize < 0) {
      // This is probably due to a test, but it causes very bad behavior if a
      // test causes this accidentally. We could throw, but there's an obvious/
      // natural way to handle it, so we do that instead (otherwise we'd have a
      // weird lower bound of ~1125b on the max record payload size).
      return [];
    }
    if (size > maxSerializedSize) {
      // Estimate a little more than the direct fraction to maximize packing
      let cutoff = Math.ceil((records.length * maxSerializedSize) / size);
      records = records.slice(0, cutoff + 1);

      // Keep dropping off the last entry until the data fits.
      while (computeSerializedSize() > maxSerializedSize) {
        records.pop();
      }
    }
    return records;
  },

  /**
   * Move a json file in the profile directory. Will fail if a file exists at the
   * destination.
   *
   * @returns a promise that resolves to undefined on success, or rejects on failure
   *
   * @param aFrom
   *        Current path to the JSON file saved on disk, relative to profileDir/weave
   *        .json will be appended to the file name.
   * @param aTo
   *        New path to the JSON file saved on disk, relative to profileDir/weave
   *        .json will be appended to the file name.
   * @param that
   *        Object to use for logging
   */
  jsonMove(aFrom, aTo, that) {
    let pathFrom = PathUtils.join(
      PathUtils.profileDir,
      "weave",
      ...(aFrom + ".json").split("/")
    );
    let pathTo = PathUtils.join(
      PathUtils.profileDir,
      "weave",
      ...(aTo + ".json").split("/")
    );
    if (that._log) {
      that._log.trace("Moving " + pathFrom + " to " + pathTo);
    }
    return IOUtils.move(pathFrom, pathTo, { noOverwrite: true });
  },

  /**
   * Removes a json file in the profile directory.
   *
   * @returns a promise that resolves to undefined on success, or rejects on failure
   *
   * @param filePath
   *        Current path to the JSON file saved on disk, relative to profileDir/weave
   *        .json will be appended to the file name.
   * @param that
   *        Object to use for logging
   */
  jsonRemove(filePath, that) {
    let path = PathUtils.join(
      PathUtils.profileDir,
      "weave",
      ...(filePath + ".json").split("/")
    );
    if (that._log) {
      that._log.trace("Deleting " + path);
    }
    return IOUtils.remove(path, { ignoreAbsent: true });
  },

  /**
   * The following are the methods supported for UI use:
   *
   * * isPassphrase:
   *     determines whether a string is either a normalized or presentable
   *     passphrase.
   * * normalizePassphrase:
   *     take a presentable passphrase and reduce it to a normalized
   *     representation for storage. normalizePassphrase can safely be called
   *     on normalized input.
   */

  isPassphrase(s) {
    if (s) {
      return /^[abcdefghijkmnpqrstuvwxyz23456789]{26}$/.test(
        Utils.normalizePassphrase(s)
      );
    }
    return false;
  },

  normalizePassphrase: function normalizePassphrase(pp) {
    // Short var name... have you seen the lines below?!
    // Allow leading and trailing whitespace.
    pp = pp.trim().toLowerCase();

    // 20-char sync key.
    if (pp.length == 23 && [5, 11, 17].every(i => pp[i] == "-")) {
      return (
        pp.slice(0, 5) + pp.slice(6, 11) + pp.slice(12, 17) + pp.slice(18, 23)
      );
    }

    // "Modern" 26-char key.
    if (pp.length == 31 && [1, 7, 13, 19, 25].every(i => pp[i] == "-")) {
      return (
        pp.slice(0, 1) +
        pp.slice(2, 7) +
        pp.slice(8, 13) +
        pp.slice(14, 19) +
        pp.slice(20, 25) +
        pp.slice(26, 31)
      );
    }

    // Something else -- just return.
    return pp;
  },

  /**
   * Create an array like the first but without elements of the second. Reuse
   * arrays if possible.
   */
  arraySub: function arraySub(minuend, subtrahend) {
    if (!minuend.length || !subtrahend.length) {
      return minuend;
    }
    let setSubtrahend = new Set(subtrahend);
    return minuend.filter(i => !setSubtrahend.has(i));
  },

  /**
   * Build the union of two arrays. Reuse arrays if possible.
   */
  arrayUnion: function arrayUnion(foo, bar) {
    if (!foo.length) {
      return bar;
    }
    if (!bar.length) {
      return foo;
    }
    return foo.concat(Utils.arraySub(bar, foo));
  },

  /**
   * Add all the items in `items` to the provided Set in-place.
   *
   * @return The provided set.
   */
  setAddAll(set, items) {
    for (let item of items) {
      set.add(item);
    }
    return set;
  },

  /**
   * Delete every items in `items` to the provided Set in-place.
   *
   * @return The provided set.
   */
  setDeleteAll(set, items) {
    for (let item of items) {
      set.delete(item);
    }
    return set;
  },

  /**
   * Take the first `size` items from the Set `items`.
   *
   * @return A Set of size at most `size`
   */
  subsetOfSize(items, size) {
    let result = new Set();
    let count = 0;
    for (let item of items) {
      if (count++ == size) {
        return result;
      }
      result.add(item);
    }
    return result;
  },

  bind2: function Async_bind2(object, method) {
    return function innerBind() {
      return method.apply(object, arguments);
    };
  },

  /**
   * Is there a master password configured and currently locked?
   */
  mpLocked() {
    return !lazy.cryptoSDR.isLoggedIn;
  },

  // If Master Password is enabled and locked, present a dialog to unlock it.
  // Return whether the system is unlocked.
  ensureMPUnlocked() {
    if (lazy.cryptoSDR.uiBusy) {
      return false;
    }
    try {
      lazy.cryptoSDR.encrypt("bacon");
      return true;
    } catch (e) {}
    return false;
  },

  /**
   * Return a value for a backoff interval.  Maximum is eight hours, unless
   * Status.backoffInterval is higher.
   *
   */
  calculateBackoff: function calculateBackoff(
    attempts,
    baseInterval,
    statusInterval
  ) {
    let backoffInterval =
      attempts * (Math.floor(Math.random() * baseInterval) + baseInterval);
    return Math.max(
      Math.min(backoffInterval, MAXIMUM_BACKOFF_INTERVAL),
      statusInterval
    );
  },

  /**
   * Return a set of hostnames (including the protocol) which may have
   * credentials for sync itself stored in the login manager.
   *
   * In general, these hosts will not have their passwords synced, will be
   * reset when we drop sync credentials, etc.
   */
  getSyncCredentialsHosts() {
    let result = new Set();
    // the FxA host
    result.add(FxAccountsCommon.FXA_PWDMGR_HOST);
    // We used to include the FxA hosts (hence the Set() result) but we now
    // don't give them special treatment (hence the Set() with exactly 1 item)
    return result;
  },

  /**
   * Helper to implement a more efficient version of fairly common pattern:
   *
   * Utils.defineLazyIDProperty(this, "syncID", "services.sync.client.syncID")
   *
   * is equivalent to (but more efficient than) the following:
   *
   * Foo.prototype = {
   *   ...
   *   get syncID() {
   *     let syncID = Svc.PrefBranch.getStringPref("client.syncID", "");
   *     return syncID == "" ? this.syncID = Utils.makeGUID() : syncID;
   *   },
   *   set syncID(value) {
   *     Svc.PrefBranch.setStringPref("client.syncID", value);
   *   },
   *   ...
   * };
   */
  defineLazyIDProperty(object, propName, prefName) {
    // An object that exists to be the target of the lazy pref getter.
    // We can't use `object` (at least, not using `propName`) since XPCOMUtils
    // will stomp on any setter we define.
    const storage = {};
    XPCOMUtils.defineLazyPreferenceGetter(storage, "value", prefName, "");
    Object.defineProperty(object, propName, {
      configurable: true,
      enumerable: true,
      get() {
        let value = storage.value;
        if (!value) {
          value = Utils.makeGUID();
          Services.prefs.setStringPref(prefName, value);
        }
        return value;
      },
      set(value) {
        Services.prefs.setStringPref(prefName, value);
      },
    });
  },

  getDeviceType() {
    return lazy.localDeviceType;
  },

  formatTimestamp(date) {
    // Format timestamp as: "%Y-%m-%d %H:%M:%S"
    let year = String(date.getFullYear());
    let month = String(date.getMonth() + 1).padStart(2, "0");
    let day = String(date.getDate()).padStart(2, "0");
    let hours = String(date.getHours()).padStart(2, "0");
    let minutes = String(date.getMinutes()).padStart(2, "0");
    let seconds = String(date.getSeconds()).padStart(2, "0");

    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  },

  *walkTree(tree, parent = null) {
    if (tree) {
      // Skip root node
      if (parent) {
        yield [tree, parent];
      }
      if (tree.children) {
        for (let child of tree.children) {
          yield* Utils.walkTree(child, tree);
        }
      }
    }
  },
};

/**
 * A subclass of Set that serializes as an Array when passed to JSON.stringify.
 */
export class SerializableSet extends Set {
  toJSON() {
    return Array.from(this);
  }
}

ChromeUtils.defineLazyGetter(Utils, "_utf8Converter", function () {
  let converter = Cc[
    "@mozilla.org/intl/scriptableunicodeconverter"
  ].createInstance(Ci.nsIScriptableUnicodeConverter);
  converter.charset = "UTF-8";
  return converter;
});

ChromeUtils.defineLazyGetter(Utils, "utf8Encoder", () => new TextEncoder());

/*
 * Commonly-used services
 */
export var Svc = {};

Svc.PrefBranch = Services.prefs.getBranch(PREFS_BRANCH);
Svc.Obs = Observers;

Svc.Obs.add("xpcom-shutdown", function () {
  for (let name in Svc) {
    delete Svc[name];
  }
});
PK
!<w!/�3
3
/modules/sessionstore/SessionStoreHelper.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * The external API exported by this module.
 */
export var SessionStoreHelper = Object.freeze({
  buildRestoreData(formdata, scroll) {
    return SessionStoreHelperInternal.buildRestoreData(formdata, scroll);
  },
});

/**
 * The internal API for the SessionStoreHelper module.
 */
var SessionStoreHelperInternal = {
  /**
   * Builds a single nsISessionStoreRestoreData tree for the provided |formdata|
   * and |scroll| trees.
   */
  buildRestoreData(formdata, scroll) {
    function addFormEntries(root, fields, isXpath) {
      for (let [key, value] of Object.entries(fields)) {
        switch (typeof value) {
          case "string":
            root.addTextField(isXpath, key, value);
            break;
          case "boolean":
            root.addCheckbox(isXpath, key, value);
            break;
          case "object": {
            if (value === null) {
              break;
            }
            if (
              value.hasOwnProperty("type") &&
              value.hasOwnProperty("fileList")
            ) {
              root.addFileList(isXpath, key, value.type, value.fileList);
              break;
            }
            if (
              value.hasOwnProperty("selectedIndex") &&
              value.hasOwnProperty("value")
            ) {
              root.addSingleSelect(
                isXpath,
                key,
                value.selectedIndex,
                value.value
              );
              break;
            }
            if (
              value.hasOwnProperty("value") &&
              value.hasOwnProperty("state")
            ) {
              root.addCustomElement(isXpath, key, value.value, value.state);
              break;
            }
            if (
              key === "sessionData" &&
              ["about:sessionrestore", "about:welcomeback"].includes(
                formdata.url
              )
            ) {
              root.addTextField(isXpath, key, JSON.stringify(value));
              break;
            }
            if (Array.isArray(value)) {
              root.addMultipleSelect(isXpath, key, value);
              break;
            }
          }
        }
      }
    }

    let root = SessionStoreUtils.constructSessionStoreRestoreData();
    if (scroll?.hasOwnProperty("scroll")) {
      root.scroll = scroll.scroll;
    }
    if (formdata?.hasOwnProperty("url")) {
      root.url = formdata.url;
      if (formdata.hasOwnProperty("innerHTML")) {
        // eslint-disable-next-line no-unsanitized/property
        root.innerHTML = formdata.innerHTML;
      }
      if (formdata.hasOwnProperty("xpath")) {
        addFormEntries(root, formdata.xpath, /* isXpath */ true);
      }
      if (formdata.hasOwnProperty("id")) {
        addFormEntries(root, formdata.id, /* isXpath */ false);
      }
    }
    let childrenLength = Math.max(
      scroll?.children?.length || 0,
      formdata?.children?.length || 0
    );
    for (let i = 0; i < childrenLength; i++) {
      root.addChild(
        this.buildRestoreData(formdata?.children?.[i], scroll?.children?.[i]),
        i
      );
    }
    return root;
  },
};
PK
!<�J��x�x'modules/shared/AddressComponent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddressParser: "resource://gre/modules/shared/AddressParser.sys.mjs",
  FormAutofillNameUtils:
    "resource://gre/modules/shared/FormAutofillNameUtils.sys.mjs",
  FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
  PhoneNumber: "resource://gre/modules/shared/PhoneNumber.sys.mjs",
  PhoneNumberNormalizer:
    "resource://gre/modules/shared/PhoneNumberNormalizer.sys.mjs",
});

/**
 * Class representing a collection of tokens extracted from a string.
 */
class Tokens {
  #tokens = null;

  // By default we split passed string with whitespace.
  constructor(value, sep = /\s+/) {
    this.#tokens = value.split(sep);
  }

  get tokens() {
    return this.#tokens;
  }

  /**
   * Checks if all the tokens in the current object can be found in another
   * token object.
   *
   * @param   {Tokens}   other   The other Tokens instance to compare with.
   * @param   {Function} compare An optional custom comparison function.
   * @returns {boolean}          True if the current Token object is a subset of the
   *                             other Token object, false otherwise.
   */
  isSubset(other, compare = (a, b) => a == b) {
    return this.tokens.every(tokenSelf => {
      for (const tokenOther of other.tokens) {
        if (compare(tokenSelf, tokenOther)) {
          return true;
        }
      }
      return false;
    });
  }

  /**
   * Checks if all the tokens in the current object can be found in another
   * Token object's tokens (in order).
   * For example, ["John", "Doe"] is a subset of ["John", "Michael", "Doe"]
   * in order but not a subset of ["Doe", "Michael", "John"] in order.
   *
   * @param   {Tokens}   other   The other Tokens instance to compare with.
   * @param   {Function} compare An optional custom comparison function.
   * @returns {boolean}          True if the current Token object is a subset of the
   *                             other Token object, false otherwise.
   */
  isSubsetInOrder(other, compare = (a, b) => a == b) {
    if (this.tokens.length > other.tokens.length) {
      return false;
    }

    let idx = 0;
    return this.tokens.every(tokenSelf => {
      for (; idx < other.tokens.length; idx++) {
        if (compare(tokenSelf, other.tokens[idx])) {
          return true;
        }
      }
      return false;
    });
  }
}

/**
 * The AddressField class is a base class representing a single address field.
 */
class AddressField {
  #userValue = null;

  #region = null;

  /**
   * Create a representation of a single address field.
   *
   * @param {string} value
   *        The unnormalized value of an address field.
   *
   * @param {string} region
   *        The region of a single address field. Used to determine what collator should be
   *        for string comparisons of the address's field value.
   */
  constructor(value, region) {
    this.#userValue = value?.trim();
    this.#region = region;
  }

  /**
   * Get the unnormalized value of the address field.
   *
   * @returns {string} The unnormalized field value.
   */
  get userValue() {
    return this.#userValue;
  }

  /**
   * Get the collator used for string comparisons.
   *
   * @returns {Intl.Collator} The collator.
   */
  get collator() {
    return lazy.FormAutofillUtils.getSearchCollators(this.#region, {
      ignorePunctuation: false,
    });
  }

  get region() {
    return this.#region;
  }

  /**
   * Compares two strings using the collator.
   *
   * @param   {string} a The first string to compare.
   * @param   {string} b The second string to compare.
   * @returns {number} A negative, zero, or positive value, depending on the comparison result.
   */
  localeCompare(a, b) {
    return lazy.FormAutofillUtils.strCompare(a, b, this.collator);
  }

  /**
   * Checks if the field value is empty.
   *
   * @returns {boolean} True if the field value is empty, false otherwise.
   */
  isEmpty() {
    return !this.#userValue;
  }

  /**
   * Normalizes the unnormalized field value using the provided options.
   *
   * @param {object} options - Options for normalization.
   * @returns {string} The normalized field value.
   */
  normalizeUserValue(options) {
    return lazy.AddressParser.normalizeString(this.#userValue, options);
  }

  /**
   * Returns a string representation of the address field.
   * Ex. "Country: US", "PostalCode: 55123", etc.
   */
  toString() {
    return `${this.constructor.name}: ${this.#userValue}\n`;
  }

  /**
   * Checks if the field value is valid.
   *
   * @returns {boolean} True if the field value is valid, false otherwise.
   */
  isValid() {
    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
  }

  /**
   * Compares the current field value with another field value for equality.
   */
  equals() {
    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
  }

  /**
   * Checks if the current field value contains another field value.
   */
  contains() {
    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
  }
}

/**
 * A street address.
 * See autocomplete="street-address".
 */
class StreetAddress extends AddressField {
  static ac = "street-address";

  #structuredStreetAddress = null;

  constructor(value, region) {
    super(value, region);

    this.#structuredStreetAddress = lazy.AddressParser.parseStreetAddress(
      lazy.AddressParser.replaceControlCharacters(this.userValue)
    );
  }

  get structuredStreetAddress() {
    return this.#structuredStreetAddress;
  }
  get street_number() {
    return this.#structuredStreetAddress?.street_number;
  }
  get street_name() {
    return this.#structuredStreetAddress?.street_name;
  }
  get floor_number() {
    return this.#structuredStreetAddress?.floor_number;
  }
  get apartment_number() {
    return this.#structuredStreetAddress?.apartment_number;
  }

  isValid() {
    return this.userValue ? !!/[\p{Letter}]/u.exec(this.userValue) : true;
  }

  equals(other) {
    if (this.structuredStreetAddress && other.structuredStreetAddress) {
      return (
        this.street_number?.toLowerCase() ==
          other.street_number?.toLowerCase() &&
        this.street_name?.toLowerCase() == other.street_name?.toLowerCase() &&
        this.apartment_number?.toLowerCase() ==
          other.apartment_number?.toLowerCase() &&
        this.floor_number?.toLowerCase() == other.floor_number?.toLowerCase()
      );
    }

    const options = {
      ignore_case: true,
    };

    return (
      this.normalizeUserValue(options) == other.normalizeUserValue(options)
    );
  }

  contains(other) {
    let selfStreetName = this.userValue;
    let otherStreetName = other.userValue;

    // Compare street number, apartment number and floor number if
    // both addresses are parsed successfully.
    if (this.structuredStreetAddress && other.structuredStreetAddress) {
      if (
        (other.street_number && this.street_number != other.street_number) ||
        (other.apartment_number &&
          this.apartment_number != other.apartment_number) ||
        (other.floor_number && this.floor_number != other.floor_number)
      ) {
        return false;
      }

      // Use parsed street name to compare
      selfStreetName = this.street_name;
      otherStreetName = other.street_name;
    }

    // Check if one street name contains the other
    const options = {
      ignore_case: true,
      replace_punctuation: " ",
    };
    const selfTokens = new Tokens(
      lazy.AddressParser.normalizeString(selfStreetName, options),
      /[\s\n\r]+/
    );
    const otherTokens = new Tokens(
      lazy.AddressParser.normalizeString(otherStreetName, options),
      /[\s\n\r]+/
    );

    return otherTokens.isSubsetInOrder(selfTokens, (a, b) =>
      this.localeCompare(a, b)
    );
  }

  static fromRecord(record, region) {
    return new StreetAddress(record[StreetAddress.ac], region);
  }
}

/**
 * A postal code / zip code
 * See autocomplete="postal-code"
 */
class PostalCode extends AddressField {
  static ac = "postal-code";

  constructor(value, region) {
    super(value, region);
  }

  isValid() {
    const { postalCodePattern } = lazy.FormAutofillUtils.getFormFormat(
      this.region
    );
    const regexp = new RegExp(`^${postalCodePattern}$`);
    return regexp.test(this.userValue);
  }

  equals(other) {
    const options = {
      ignore_case: true,
      remove_whitespace: true,
      remove_punctuation: true,
    };

    return (
      this.normalizeUserValue(options) == other.normalizeUserValue(options)
    );
  }

  contains(other) {
    const options = {
      ignore_case: true,
      remove_whitespace: true,
      remove_punctuation: true,
    };

    const self_normalized_value = this.normalizeUserValue(options);
    const other_normalized_value = other.normalizeUserValue(options);

    return (
      self_normalized_value.endsWith(other_normalized_value) ||
      self_normalized_value.startsWith(other_normalized_value)
    );
  }

  static fromRecord(record, region) {
    return new PostalCode(record[PostalCode.ac], region);
  }
}

/**
 * City name.
 * See autocomplete="address-level2"
 */
class City extends AddressField {
  static ac = "address-level2";

  #city = null;

  constructor(value, region) {
    super(value, region);

    const options = {
      ignore_case: true,
    };
    this.#city = this.normalizeUserValue(options);
  }

  get city() {
    return this.#city;
  }

  isValid() {
    return this.userValue ? !!/[\p{Letter}]/u.exec(this.userValue) : true;
  }

  equals(other) {
    return this.city == other.city;
  }

  contains(other) {
    const options = {
      ignore_case: true,
      replace_punctuation: " ",
      merge_whitespace: true,
    };

    const selfTokens = new Tokens(this.normalizeUserValue(options));
    const otherTokens = new Tokens(other.normalizeUserValue(options));

    return otherTokens.isSubsetInOrder(selfTokens, (a, b) =>
      this.localeCompare(a, b)
    );
  }

  static fromRecord(record, region) {
    return new City(record[City.ac], region);
  }
}

/**
 * State.
 * See autocomplete="address-level1"
 */
class State extends AddressField {
  static ac = "address-level1";

  // The abbreviated region name. For example, California is abbreviated as CA
  #state = null;

  constructor(value, region) {
    super(value, region);

    if (!this.userValue) {
      return;
    }

    const options = {
      merge_whitespace: true,
    };
    this.#state = lazy.FormAutofillUtils.getAbbreviatedSubregionName(
      this.normalizeUserValue(options),
      region
    );
  }

  get state() {
    return this.#state;
  }

  isValid() {
    // If we can't get the abbreviated name, assume this is an invalid state name
    return !!this.#state;
  }

  equals(other) {
    // If we have an abbreviated name, compare with it.
    if (this.state) {
      return this.state == other.state;
    }

    // If we don't have an abbreviated name, just compare the userValue
    return this.userValue == other.userValue;
  }

  contains(other) {
    return this.equals(other);
  }

  static fromRecord(record, region) {
    return new State(record[State.ac], region);
  }
}

/**
 * A country or territory code.
 * See autocomplete="country"
 */
class Country extends AddressField {
  static ac = "country";

  // iso 3166 2-alpha code
  #country_code = null;

  constructor(value, region) {
    super(value, region);

    if (this.isEmpty()) {
      return;
    }

    const options = {
      merge_whitespace: true,
      remove_punctuation: true,
    };

    const country = this.normalizeUserValue(options);
    this.#country_code = lazy.FormAutofillUtils.identifyCountryCode(country);

    // When the country name is not a valid one, we use the current region instead
    if (!this.#country_code) {
      this.#country_code = lazy.FormAutofillUtils.identifyCountryCode(region);
    }
  }

  get country_code() {
    return this.#country_code;
  }

  isValid() {
    return !!this.#country_code;
  }

  equals(other) {
    return this.country_code == other.country_code;
  }

  contains(_other) {
    return false;
  }

  static fromRecord(record, region) {
    return new Country(record[Country.ac], region);
  }
}

/**
 * The field expects the value to be a person's full name.
 * See autocomplete="name"
 */
class Name extends AddressField {
  static ac = "name";

  constructor(value, region) {
    super(value, region);
  }

  // Reference:
  // https://source.chromium.org/chromium/chromium/src/+/main:components/autofill/core/browser/data_model/autofill_profile_comparator.cc;drc=566369da19275cc306eeb51a3d3451885299dabb;bpv=1;bpt=1;l=935
  static createNameVariants(name) {
    let tokens = name.trim().split(" ");

    let variants = [""];
    if (!tokens[0]) {
      return variants;
    }

    for (const token of tokens) {
      let tmp = [];
      for (const variant of variants) {
        tmp.push(variant + " " + token);
        tmp.push(variant + " " + token[0]);
      }
      variants = variants.concat(tmp);
    }

    const options = {
      merge_whitespace: true,
    };
    return variants.map(v => lazy.AddressParser.normalizeString(v, options));
  }

  isValid() {
    return this.userValue ? !!/[\p{Letter}]/u.exec(this.userValue) : true;
  }

  equals(other) {
    const options = {
      ignore_case: true,
    };
    return (
      this.normalizeUserValue(options) == other.normalizeUserValue(options)
    );
  }

  contains(other) {
    // Unify puncutation while comparing so users can choose the right one
    // if the only different part is puncutation
    // Ex. John O'Brian is similar to John O`Brian
    let options = {
      ignore_case: true,
      replace_punctuation: " ",
      merge_whitespace: true,
    };
    let selfName = this.normalizeUserValue(options);
    let otherName = other.normalizeUserValue(options);
    let selfTokens = new Tokens(selfName);
    let otherTokens = new Tokens(otherName);

    if (
      otherTokens.isSubsetInOrder(selfTokens, (a, b) =>
        this.localeCompare(a, b)
      )
    ) {
      return true;
    }

    // Remove puncutation from self and test whether current contains other
    // Ex. John O'Brian is similar to John OBrian
    selfName = this.normalizeUserValue({
      ignore_case: true,
      remove_punctuation: true,
      merge_whitespace: true,
    });
    otherName = other.normalizeUserValue({
      ignore_case: true,
      remove_punctuation: true,
      merge_whitespace: true,
    });

    selfTokens = new Tokens(selfName);
    otherTokens = new Tokens(otherName);
    if (
      otherTokens.isSubsetInOrder(selfTokens, (a, b) =>
        this.localeCompare(a, b)
      )
    ) {
      return true;
    }

    // Create variants of the names by generating initials for given and middle names.

    selfName = lazy.FormAutofillNameUtils.splitName(selfName);
    otherName = lazy.FormAutofillNameUtils.splitName(otherName);
    // In the following we compare cases when people abbreviate first name
    // and middle name with initials. So if family name is different,
    // we can just skip and assume the two names are different
    if (!this.localeCompare(selfName.family, otherName.family)) {
      return false;
    }

    const otherNameWithoutFamily = lazy.FormAutofillNameUtils.joinNameParts({
      given: otherName.given,
      middle: otherName.middle,
    });
    let givenVariants = Name.createNameVariants(selfName.given);
    let middleVariants = Name.createNameVariants(selfName.middle);

    for (const given of givenVariants) {
      for (const middle of middleVariants) {
        const nameVariant = lazy.FormAutofillNameUtils.joinNameParts({
          given,
          middle,
        });

        if (this.localeCompare(nameVariant, otherNameWithoutFamily)) {
          return true;
        }
      }
    }

    // Check cases when given name and middle name are abbreviated with initial
    // and the initials are put together. ex. John Michael Doe to JM. Doe
    if (selfName.given && selfName.middle) {
      const nameVariant = [
        ...selfName.given.split(" "),
        ...selfName.middle.split(" "),
      ].reduce((initials, name) => {
        initials += name[0];
        return initials;
      }, "");

      if (this.localeCompare(nameVariant, otherNameWithoutFamily)) {
        return true;
      }
    }

    return false;
  }

  static fromRecord(record, region) {
    return new Name(record[Name.ac], region);
  }
}

/**
 * A full telephone number, including the country code.
 * See autocomplete="tel"
 */
class Tel extends AddressField {
  static ac = "tel";

  #valid = false;

  // The country code part of a telphone number, such as "1" for the United States
  #country_code = null;

  // The national part of a telphone number. For example, the phone number "+1 520-248-6621"
  // national part is "520-248-6621".
  #national_number = null;

  constructor(value, region) {
    super(value, region);

    if (!this.userValue) {
      return;
    }

    // TODO: Support parse telephone extension
    // We compress all tel-related fields into a single tel field when an an form
    // is submitted, so we need to decompress it here.
    const parsed_tel = lazy.PhoneNumber.Parse(this.userValue, region);
    if (parsed_tel) {
      this.#national_number = parsed_tel?.nationalNumber;
      this.#country_code = parsed_tel?.countryCode;

      this.#valid = true;
    } else {
      this.#national_number = lazy.PhoneNumberNormalizer.Normalize(
        this.userValue
      );

      const md = lazy.PhoneNumber.FindMetaDataForRegion(region);
      this.#country_code = md ? "+" + md.nationalPrefix : null;

      this.#valid = lazy.PhoneNumber.IsValid(this.#national_number, md);
    }
  }

  get country_code() {
    return this.#country_code;
  }

  get national_number() {
    return this.#national_number;
  }

  isValid() {
    return this.#valid;
  }

  equals(other) {
    return (
      this.national_number == other.national_number &&
      this.country_code == other.country_code
    );
  }

  contains(other) {
    if (!this.country_code || this.country_code != other.country_code) {
      return false;
    }

    return this.national_number.endsWith(other.national_number);
  }

  toString() {
    return `${this.constructor.name}: ${this.country_code} ${this.national_number}\n`;
  }

  static fromRecord(record, region) {
    return new Tel(record[Tel.ac], region);
  }
}

/**
 * A company or organization name.
 * See autocomplete="organization".
 */
class Organization extends AddressField {
  static ac = "organization";

  constructor(value, region) {
    super(value, region);
  }

  isValid() {
    return this.userValue
      ? !!/[\p{Letter}\p{Number}]/u.exec(this.userValue)
      : true;
  }

  /**
   * Two company names are considered equal only when everything is the same.
   */
  equals(other) {
    return this.userValue == other.userValue;
  }

  // Mergeable use locale compare
  contains(other) {
    const options = {
      replace_punctuation: " ", // mozilla org vs mozilla-org
      merge_whitespace: true,
      ignore_case: true, // mozilla vs Mozilla
    };

    // If every token in B can be found in A without considering order
    // Example, 'Food & Pharmacy' contains 'Pharmacy & Food'
    const selfTokens = new Tokens(this.normalizeUserValue(options));
    const otherTokens = new Tokens(other.normalizeUserValue(options));

    return otherTokens.isSubset(selfTokens, (a, b) => this.localeCompare(a, b));
  }

  static fromRecord(record, region) {
    return new Organization(record[Organization.ac], region);
  }
}

/**
 * An email address
 * See autocomplete="email".
 */
class Email extends AddressField {
  static ac = "email";

  constructor(value, region) {
    super(value, region);
  }

  // Since we are using the valid check to determine whether we capture the email field when users submitting a forma,
  // use a less restrict email verification method so we capture an email for most of the cases.
  // The current algorithm is based on the regular expression defined in
  // https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
  //
  // We might also change this to something similar to the algorithm used in
  // EmailInputType::IsValidEmailAddress if we want a more strict email validation algorithm.
  isValid() {
    const regex =
      /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
    const match = this.userValue.match(regex);
    if (!match) {
      return false;
    }

    return true;
  }

  /*
  // JS version of EmailInputType::IsValidEmailAddress
  isValid() {
    const regex = /^([a-zA-Z0-9.!#$%&'*+-/=?^_`{|}~]+)@([a-zA-Z0-9-]+\.[a-zA-Z]{2,})$/;
    const match = this.userValue.match(regex);
    if (!match) {
      return false;
    }
    const local = match[1];
    const domain = match[2];

    // The domain name can't begin with a dot or a dash.
    if (['-', '.'].includes(domain[0])) {
      return false;
    }

    // A dot can't follow a dot or a dash.
    // A dash can't follow a dot.
    const pattern = /(\.\.)|(\.-)|(-\.)/;
    if (pattern.test(domain)) {
      return false;
    }

    return true;
  }
*/

  equals(other) {
    const options = {
      ignore_case: true,
    };

    // email is case-insenstive
    return (
      this.normalizeUserValue(options) == other.normalizeUserValue(options)
    );
  }

  contains(_other) {
    return false;
  }

  static fromRecord(record, region) {
    return new Email(record[Email.ac], region);
  }
}

/**
 * The AddressComparison class compares two AddressComponent instances and
 * provides information about the differences or similarities between them.
 *
 * The comparison result is stored and the object and can be retrieved by calling
 * 'result' getter.
 */
export class AddressComparison {
  // Const to define the comparison result for two address fields
  static BOTH_EMPTY = 0;
  static A_IS_EMPTY = 1;
  static B_IS_EMPTY = 2;
  static A_CONTAINS_B = 3;
  static B_CONTAINS_A = 4;
  // When A contains B and B contains A Ex. "Pizza & Food vs Food & Pizza"
  static SIMILAR = 5;
  static SAME = 6;
  static DIFFERENT = 7;

  // The comparion result, keyed by field name.
  #result = {};

  /**
   * Constructs AddressComparison by comparing two AddressComponent objects.
   *
   * @class
   * @param {AddressComponent} addressA - The first address to compare.
   * @param {AddressComponent} addressB - The second address to compare.
   */
  constructor(addressA, addressB) {
    for (const fieldA of addressA.getAllFields()) {
      const fieldName = fieldA.constructor.ac;
      const fieldB = addressB.getField(fieldName);
      if (fieldB) {
        this.#result[fieldName] = AddressComparison.compare(fieldA, fieldB);
      } else {
        this.#result[fieldName] = AddressComparison.B_IS_EMPTY;
      }
    }

    for (const fieldB of addressB.getAllFields()) {
      const fieldName = fieldB.constructor.ac;
      if (!addressB.getField(fieldName)) {
        this.#result[fieldName] = AddressComparison.A_IS_EMPTY;
      }
    }
  }

  /**
   * Retrieves the result object containing the comparison results.
   *
   * @returns {object} The result object with keys corresponding to field names
   *                  and values being comparison constants.
   */
  get result() {
    return this.#result;
  }

  /**
   * Compares two address fields and returns the comparison result.
   *
   * @param  {AddressField} fieldA The first field to compare.
   * @param  {AddressField} fieldB The second field to compare.
   * @returns {number}       A constant representing the comparison result.
   */
  static compare(fieldA, fieldB) {
    if (fieldA.isEmpty()) {
      return fieldB.isEmpty()
        ? AddressComparison.BOTH_EMPTY
        : AddressComparison.A_IS_EMPTY;
    } else if (fieldB.isEmpty()) {
      return AddressComparison.B_IS_EMPTY;
    }

    if (fieldA.equals(fieldB)) {
      return AddressComparison.SAME;
    }

    if (fieldB.contains(fieldA)) {
      if (fieldA.contains(fieldB)) {
        return AddressComparison.SIMILAR;
      }
      return AddressComparison.B_CONTAINS_A;
    } else if (fieldA.contains(fieldB)) {
      return AddressComparison.A_CONTAINS_B;
    }

    return AddressComparison.DIFFERENT;
  }

  /**
   * Converts a comparison result constant to a readable string.
   *
   * @param  {number} result The comparison result constant.
   * @returns {string}        A readable string representing the comparison result.
   */
  static resultToString(result) {
    switch (result) {
      case AddressComparison.BOTH_EMPTY:
        return "both fields are empty";
      case AddressComparison.A_IS_EMPTY:
        return "field A is empty";
      case AddressComparison.B_IS_EMPTY:
        return "field B is empty";
      case AddressComparison.A_CONTAINS_B:
        return "field A contains field B";
      case AddressComparison.B_CONTAINS_B:
        return "field B contains field A";
      case AddressComparison.SIMILAR:
        return "field A and field B are similar";
      case AddressComparison.SAME:
        return "two fields are the same";
      case AddressComparison.DIFFERENT:
        return "two fields are different";
    }
    return "";
  }

  /**
   * Returns a formatted string representing the comparison results for each field.
   *
   * @returns {string} A formatted string with field names and their respective
   *                  comparison results.
   */
  toString() {
    let string = "Comparison Result:\n";
    for (const [name, result] of Object.entries(this.#result)) {
      string += `${name}: ${AddressComparison.resultToString(result)}\n`;
    }
    return string;
  }
}

/**
 * The AddressComponent class represents a structured address that is transformed
 * from address record created in FormAutofillHandler 'createRecord' function.
 *
 * An AddressComponent object consisting of various fields such as state, city,
 * country, postal code, etc. The class provides a compare methods
 * to compare another AddressComponent against the current instance.
 *
 * Note: This class assumes records that pass to it have already been normalized.
 */
export class AddressComponent {
  /**
   * An object that stores individual address field instances
   * (e.g., class State, class City, class Country, etc.), keyed by the
   * field's clas name.
   */
  #fields = {};

  /**
   * Constructs an AddressComponent object by converting passed address record object.
   *
   * @class
   * @param {object}  record         The address record object containing address data.
   * @param {object}  [options = {}] a list of options for this method
   * @param {boolean} [options.ignoreInvalid = true]  Whether to ignore invalid address
   *                                 fields in the AddressComponent object. If set to true,
   *                                 invalid fields will be ignored.
   */
  constructor(record, { ignoreInvalid = true } = {}) {
    this.record = {};

    // Get country code first so we can use it to parse other fields
    const country = new Country(
      record[Country.ac],
      FormAutofill.DEFAULT_REGION
    );
    const region =
      country.country_code ||
      lazy.FormAutofillUtils.identifyCountryCode(FormAutofill.DEFAULT_REGION);

    // Build an mapping that the key is field name and the value is the AddressField object
    [
      country,
      new StreetAddress(record[StreetAddress.ac], region),
      new PostalCode(record[PostalCode.ac], region),
      new State(record[State.ac], region),
      new City(record[City.ac], region),
      new Name(record[Name.ac], region),
      new Tel(record[Tel.ac], region),
      new Organization(record[Organization.ac], region),
      new Email(record[Email.ac], region),
    ].forEach(addressField => {
      if (
        !addressField.isEmpty() &&
        (!ignoreInvalid || addressField.isValid())
      ) {
        const fieldName = addressField.constructor.ac;
        this.#fields[fieldName] = addressField;
        this.record[fieldName] = record[fieldName];
      }
    });
  }

  /**
   * Retrieves all the address fields.
   *
   * @returns {Array} An array of address field objects.
   */
  getAllFields() {
    return Object.values(this.#fields);
  }

  /**
   * Retrieves the field object with the specified name.
   *
   * @param  {string} name The name of the field to retrieve.
   * @returns {object}      The address field object with the specified name,
   *                       or undefined if the field is not found.
   */
  getField(name) {
    return this.#fields[name];
  }

  /**
   * Compares the current AddressComponent with another AddressComponent.
   *
   * @param  {AddressComponent} address The AddressComponent object to compare
   *                                    against the current one.
   * @returns {object} An object containing comparison results. The keys of the object represent
   *                  individual address field, and the values are strings indicating the comparison result:
   *                  - "same" if both components are either empty or the same,
   *                  - "superset" if the current contains the input or the input is empty,
   *                  - "subset" if the input contains the current or the current is empty,
   *                  - "similar" if the two address components are similar,
   *                  - "different" if the two address components are different.
   */
  compare(address) {
    let result = {};

    const comparison = new AddressComparison(this, address);
    for (const [k, v] of Object.entries(comparison.result)) {
      if ([AddressComparison.BOTH_EMPTY, AddressComparison.SAME].includes(v)) {
        result[k] = "same";
      } else if (
        [AddressComparison.B_IS_EMPTY, AddressComparison.A_CONTAINS_B].includes(
          v
        )
      ) {
        result[k] = "superset";
      } else if (
        [AddressComparison.A_IS_EMPTY, AddressComparison.B_CONTAINS_A].includes(
          v
        )
      ) {
        result[k] = "subset";
      } else if ([AddressComparison.SIMILAR].includes(v)) {
        result[k] = "similar";
      } else {
        result[k] = "different";
      }
    }
    return result;
  }

  /**
   * Print all the fields in this AddressComponent object.
   */
  toString() {
    let string = "";
    for (const field of Object.values(this.#fields)) {
      string += field.toString();
    }
    return string;
  }
}
PK
!<'�y����&modules/shared/AddressMetaData.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// The data below is initially copied from
// https://chromium-i18n.appspot.com/ssl-aggregate-address

// See https://github.com/googlei18n/libaddressinput/wiki/AddressValidationMetadata for
// documentation on how to use the data.

// WARNING: DO NOT change any value or add additional properties in addressData.
// We only accept the metadata of the supported countries that is copied from libaddressinput directly.
// Please edit AddressMetaDataExtension.sys.mjs instead if you want to add new property as complement
// or overwrite the existing properties.

export const AddressMetaData = {
  "data/AD": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/AD",
    key: "AD",
    lang: "ca",
    languages: "ca",
    name: "ANDORRA",
    posturl:
      "http://www.correos.es/comun/CodigosPostales/1010_s-CodPostal.asp?Provincia=",
    sub_isoids: "07~02~03~08~04~05~06",
    sub_keys:
      "Parròquia d'Andorra la Vella~Canillo~Encamp~Escaldes-Engordany~La Massana~Ordino~Sant Julià de Lòria",
    sub_names:
      "Andorra la Vella~Canillo~Encamp~Escaldes-Engordany~La Massana~Ordino~Sant Julià de Lòria",
    sub_zipexs: "AD500~AD100~AD200~AD700~AD400~AD300~AD600",
    sub_zips: "AD50[01]~AD10[01]~AD20[01]~AD70[01]~AD40[01]~AD30[01]~AD60[01]",
    zip: "AD[1-7]0\\d",
    zipex: "AD100,AD501,AD700",
  },
  "data/AE": {
    fmt: "%N%n%O%n%A%n%S",
    id: "data/AE",
    key: "AE",
    lang: "ar",
    languages: "ar",
    lfmt: "%N%n%O%n%A%n%S",
    name: "UNITED ARAB EMIRATES",
    require: "AS",
    state_name_type: "emirate",
    sub_isoids: "AZ~SH~FU~UQ~DU~RK~AJ",
    sub_keys:
      "أبو ظبي~إمارة الشارقةّ~الفجيرة~ام القيوين~إمارة دبيّ~إمارة رأس الخيمة~عجمان",
    sub_lnames:
      "Abu Dhabi~Sharjah~Fujairah~Umm Al Quwain~Dubai~Ras al Khaimah~Ajman",
    sub_names: "أبو ظبي~الشارقة~الفجيرة~ام القيوين~دبي~رأس الخيمة~عجمان",
  },
  "data/AF": {
    fmt: "%N%n%O%n%A%n%C%n%Z",
    id: "data/AF",
    key: "AF",
    name: "AFGHANISTAN",
    zip: "\\d{4}",
    zipex: "1001,2601,3801",
  },
  "data/AG": {
    id: "data/AG",
    key: "AG",
    name: "ANTIGUA AND BARBUDA",
    require: "A",
  },
  "data/AI": {
    fmt: "%N%n%O%n%A%n%C%n%Z",
    id: "data/AI",
    key: "AI",
    name: "ANGUILLA",
    zip: "(?:AI-)?2640",
    zipex: "2640",
  },
  "data/AL": {
    fmt: "%N%n%O%n%A%n%Z%n%C",
    id: "data/AL",
    key: "AL",
    name: "ALBANIA",
    zip: "\\d{4}",
    zipex: "1001,1017,3501",
  },
  "data/AM": {
    fmt: "%N%n%O%n%A%n%Z%n%C%n%S",
    id: "data/AM",
    key: "AM",
    lang: "hy",
    languages: "hy",
    lfmt: "%N%n%O%n%A%n%Z%n%C%n%S",
    name: "ARMENIA",
    sub_isoids: "AG~AR~AV~GR~ER~LO~KT~SH~SU~VD~TV",
    sub_keys:
      "Արագածոտն~Արարատ~Արմավիր~Գեղարքունիք~Երևան~Լոռի~Կոտայք~Շիրակ~Սյունիք~Վայոց ձոր~Տավուշ",
    sub_lnames:
      "Aragatsotn~Ararat~Armavir~Gegharkunik~Yerevan~Lori~Kotayk~Shirak~Syunik~Vayots Dzor~Tavush",
    sub_zipexs:
      "0201,0514~0601,0823~0901,1149~1201,1626~0000,0099~1701,2117~2201,2506~2601,3126~3201,3519~3601,3810~3901,4216",
    sub_zips:
      "0[2-5]~0[6-8]~09|1[01]~1[2-6]~00~1[7-9]|2[01]~2[2-5]~2[6-9]|3[01]~3[2-5]~3[6-8]~39|4[0-2]",
    zip: "(?:37)?\\d{4}",
    zipex: "375010,0002,0010",
  },
  "data/AO": { id: "data/AO", key: "AO", name: "ANGOLA" },
  "data/AQ": { id: "data/AQ", key: "AQ", name: "ANTARCTICA" },
  "data/AR": {
    fmt: "%N%n%O%n%A%n%Z %C%n%S",
    id: "data/AR",
    key: "AR",
    lang: "es",
    languages: "es",
    name: "ARGENTINA",
    posturl: "http://www.correoargentino.com.ar/formularios/cpa",
    sub_isoids: "B~K~H~U~C~X~W~E~P~Y~L~F~M~N~Q~R~A~J~D~Z~S~G~V~T",
    sub_keys:
      "Buenos Aires~Catamarca~Chaco~Chubut~Ciudad Autónoma de Buenos Aires~Córdoba~Corrientes~Entre Ríos~Formosa~Jujuy~La Pampa~La Rioja~Mendoza~Misiones~Neuquén~Río Negro~Salta~San Juan~San Luis~Santa Cruz~Santa Fe~Santiago del Estero~Tierra del Fuego~Tucumán",
    sub_names:
      "Buenos Aires~Catamarca~Chaco~Chubut~Ciudad Autónoma de Buenos Aires~Córdoba~Corrientes~Entre Ríos~Formosa~Jujuy~La Pampa~La Rioja~Mendoza~Misiones~Neuquén~Río Negro~Salta~San Juan~San Luis~Santa Cruz~Santa Fe~Santiago del Estero~Tierra del Fuego~Tucumán",
    sub_zips:
      "B?[1-36-8]~K?[45]~H?3~U?[89]~C?1~X?[235-8]~W?3~E?[1-3]~P?[37]~Y?4~L?[3568]~F?5~M?[56]~N?3~Q?[38]~R?[89]~A?[34]~J?5~D?[4-6]~Z?[89]~S?[2368]~G?[2-5]~V?9~T?[45]",
    upper: "ACZ",
    zip: "((?:[A-HJ-NP-Z])?\\d{4})([A-Z]{3})?",
    zipex: "C1070AAM,C1000WAM,B1000TBU,X5187XAB",
  },
  "data/AS": {
    fmt: "%N%n%O%n%A%n%C %S %Z",
    id: "data/AS",
    key: "AS",
    name: "AMERICAN SAMOA",
    posturl: "http://zip4.usps.com/zip4/welcome.jsp",
    require: "ACSZ",
    state_name_type: "state",
    upper: "ACNOS",
    zip: "(96799)(?:[ \\-](\\d{4}))?",
    zip_name_type: "zip",
    zipex: "96799",
  },
  "data/AT": {
    fmt: "%O%n%N%n%A%n%Z %C",
    id: "data/AT",
    key: "AT",
    name: "AUSTRIA",
    posturl: "http://www.post.at/post_subsite_postleitzahlfinder.php",
    require: "ACZ",
    zip: "\\d{4}",
    zipex: "1010,3741",
  },
  "data/AU": {
    fmt: "%O%n%N%n%A%n%C %S %Z",
    id: "data/AU",
    key: "AU",
    lang: "en",
    languages: "en",
    locality_name_type: "suburb",
    name: "AUSTRALIA",
    posturl: "http://www1.auspost.com.au/postcodes/",
    require: "ACSZ",
    state_name_type: "state",
    sub_isoids: "ACT~NSW~NT~QLD~SA~TAS~VIC~WA",
    sub_keys: "ACT~NSW~NT~QLD~SA~TAS~VIC~WA",
    sub_names:
      "Australian Capital Territory~New South Wales~Northern Territory~Queensland~South Australia~Tasmania~Victoria~Western Australia",
    sub_zipexs:
      "0200,2540,2618,2999~1000,2888,3585,3707~0800,0999~4000,9999~5000~7000,7999~3000,8000~6000,0872",
    sub_zips:
      "29|2540|260|261[0-8]|02|2620~1|2[0-57-8]|26[2-9]|261[189]|3500|358[56]|3644|3707~0[89]~[49]~5|0872~7~[38]~6|0872",
    upper: "CS",
    zip: "\\d{4}",
    zipex: "2060,3171,6430,4000,4006,3001",
  },
  "data/AW": { id: "data/AW", key: "AW", name: "ARUBA" },
  "data/AZ": {
    fmt: "%N%n%O%n%A%nAZ %Z %C",
    id: "data/AZ",
    key: "AZ",
    name: "AZERBAIJAN",
    postprefix: "AZ ",
    zip: "\\d{4}",
    zipex: "1000",
  },
  "data/BA": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/BA",
    key: "BA",
    name: "BOSNIA AND HERZEGOVINA",
    zip: "\\d{5}",
    zipex: "71000",
  },
  "data/BB": {
    fmt: "%N%n%O%n%A%n%C, %S %Z",
    id: "data/BB",
    key: "BB",
    name: "BARBADOS",
    state_name_type: "parish",
    zip: "BB\\d{5}",
    zipex: "BB23026,BB22025",
  },
  "data/BD": {
    fmt: "%N%n%O%n%A%n%C - %Z",
    id: "data/BD",
    key: "BD",
    name: "BANGLADESH",
    posturl: "http://www.bangladeshpost.gov.bd/PostCode.asp",
    zip: "\\d{4}",
    zipex: "1340,1000",
  },
  "data/BE": {
    fmt: "%O%n%N%n%A%n%Z %C",
    id: "data/BE",
    key: "BE",
    name: "BELGIUM",
    posturl:
      "http://www.post.be/site/nl/residential/customerservice/search/postal_codes.html",
    require: "ACZ",
    zip: "\\d{4}",
    zipex: "4000,1000",
  },
  "data/BF": {
    fmt: "%N%n%O%n%A%n%C %X",
    id: "data/BF",
    key: "BF",
    name: "BURKINA FASO",
  },
  "data/BG": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/BG",
    key: "BG",
    name: "BULGARIA (REP.)",
    posturl: "http://www.bgpost.bg/?cid=5",
    zip: "\\d{4}",
    zipex: "1000,1700",
  },
  "data/BH": {
    fmt: "%N%n%O%n%A%n%C %Z",
    id: "data/BH",
    key: "BH",
    name: "BAHRAIN",
    zip: "(?:\\d|1[0-2])\\d{2}",
    zipex: "317",
  },
  "data/BI": { id: "data/BI", key: "BI", name: "BURUNDI" },
  "data/BJ": { id: "data/BJ", key: "BJ", name: "BENIN", upper: "AC" },
  "data/BL": {
    fmt: "%O%n%N%n%A%n%Z %C %X",
    id: "data/BL",
    key: "BL",
    name: "SAINT BARTHELEMY",
    posturl:
      "http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal",
    require: "ACZ",
    upper: "ACX",
    zip: "9[78][01]\\d{2}",
    zipex: "97100",
  },
  "data/BM": {
    fmt: "%N%n%O%n%A%n%C %Z",
    id: "data/BM",
    key: "BM",
    name: "BERMUDA",
    posturl: "http://www.landvaluation.bm/",
    zip: "[A-Z]{2} ?[A-Z0-9]{2}",
    zipex: "FL 07,HM GX,HM 12",
  },
  "data/BN": {
    fmt: "%N%n%O%n%A%n%C %Z",
    id: "data/BN",
    key: "BN",
    name: "BRUNEI DARUSSALAM",
    posturl: "http://www.post.gov.bn/SitePages/postcodes.aspx",
    zip: "[A-Z]{2} ?\\d{4}",
    zipex: "BT2328,KA1131,BA1511",
  },
  "data/BO": { id: "data/BO", key: "BO", name: "BOLIVIA", upper: "AC" },
  "data/BQ": {
    id: "data/BQ",
    key: "BQ",
    name: "BONAIRE, SINT EUSTATIUS, AND SABA",
  },
  "data/BR": {
    fmt: "%O%n%N%n%A%n%D%n%C-%S%n%Z",
    id: "data/BR",
    key: "BR",
    lang: "pt",
    languages: "pt",
    name: "BRAZIL",
    posturl: "http://www.buscacep.correios.com.br/",
    require: "ASCZ",
    state_name_type: "state",
    sub_isoids:
      "AC~AL~AP~AM~BA~CE~DF~ES~GO~MA~MT~MS~MG~PA~PB~PR~PE~PI~RJ~RN~RS~RO~RR~SC~SP~SE~TO",
    sub_keys:
      "AC~AL~AP~AM~BA~CE~DF~ES~GO~MA~MT~MS~MG~PA~PB~PR~PE~PI~RJ~RN~RS~RO~RR~SC~SP~SE~TO",
    sub_mores:
      "true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true",
    sub_names:
      "Acre~Alagoas~Amapá~Amazonas~Bahia~Ceará~Distrito Federal~Espírito Santo~Goiás~Maranhão~Mato Grosso~Mato Grosso do Sul~Minas Gerais~Pará~Paraíba~Paraná~Pernambuco~Piauí~Rio de Janeiro~Rio Grande do Norte~Rio Grande do Sul~Rondônia~Roraima~Santa Catarina~São Paulo~Sergipe~Tocantins",
    sub_zipexs:
      "69900-000,69999-999~57000-000,57999-999~68900-000,68999-999~69000-000,69400-123~40000-000,48999-999~60000-000,63999-999~70000-000,73500-123~29000-000,29999-999~72800-000,73700-123~65000-000,65999-999~78000-000,78899-999~79000-000,79999-999~30000-000,39999-999~66000-000,68899-999~58000-000,58999-999~80000-000,87999-999~50000-000,56999-999~64000-000,64999-999~20000-000,28999-999~59000-000,59999-999~90000-000,99999-999~76800-000,78900-000,78999-999~69300-000,69399-999~88000-000,89999-999~01000-000,13000-123~49000-000,49999-999~77000-000,77999-999",
    sub_zips:
      "699~57~689~69[0-24-8]~4[0-8]~6[0-3]~7[0-1]|72[0-7]|73[0-6]~29~72[89]|73[7-9]|7[4-6]~65~78[0-8]~79~3~6[6-7]|68[0-8]~58~8[0-7]~5[0-6]~64~2[0-8]~59~9~76[89]|789~693~8[89]~[01][1-9]~49~77",
    sublocality_name_type: "neighborhood",
    upper: "CS",
    zip: "\\d{5}-?\\d{3}",
    zipex: "40301-110,70002-900",
  },
  "data/BS": {
    fmt: "%N%n%O%n%A%n%C, %S",
    id: "data/BS",
    key: "BS",
    lang: "en",
    languages: "en",
    name: "BAHAMAS",
    state_name_type: "island",
    sub_isoids: "~AK~~BY~BI~CI~~~EX~~HI~IN~LI~MG~~RI~RC~SS~SW",
    sub_keys:
      "Abaco~Acklins~Andros~Berry Islands~Bimini~Cat Island~Crooked Island~Eleuthera~Exuma~Grand Bahama~Harbour Island~Inagua~Long Island~Mayaguana~N.P.~Ragged Island~Rum Cay~San Salvador~Spanish Wells",
    sub_names:
      "Abaco Islands~Acklins~Andros Island~Berry Islands~Bimini~Cat Island~Crooked Island~Eleuthera~Exuma and Cays~Grand Bahama~Harbour Island~Inagua~Long Island~Mayaguana~New Providence~Ragged Island~Rum Cay~San Salvador~Spanish Wells",
  },
  "data/BT": {
    fmt: "%N%n%O%n%A%n%C %Z",
    id: "data/BT",
    key: "BT",
    name: "BHUTAN",
    posturl: "http://www.bhutanpost.bt/postcodes/",
    zip: "\\d{5}",
    zipex: "11001,31101,35003",
  },
  "data/BV": { id: "data/BV", key: "BV", name: "BOUVET ISLAND" },
  "data/BW": { id: "data/BW", key: "BW", name: "BOTSWANA" },
  "data/BY": {
    fmt: "%S%n%Z %C%n%A%n%O%n%N",
    id: "data/BY",
    key: "BY",
    name: "BELARUS",
    posturl: "http://ex.belpost.by/addressbook/",
    zip: "\\d{6}",
    zipex: "223016,225860,220050",
  },
  "data/BZ": { id: "data/BZ", key: "BZ", name: "BELIZE" },
  "data/CA": {
    fmt: "%N%n%O%n%A%n%C %S %Z",
    id: "data/CA",
    key: "CA",
    lang: "en",
    languages: "en~fr",
    name: "CANADA",
    posturl: "https://www.canadapost.ca/cpo/mc/personal/postalcode/fpc.jsf",
    require: "ACSZ",
    sub_isoids: "AB~BC~MB~NB~NL~NT~NS~NU~ON~PE~QC~SK~YT",
    sub_keys: "AB~BC~MB~NB~NL~NT~NS~NU~ON~PE~QC~SK~YT",
    sub_names:
      "Alberta~British Columbia~Manitoba~New Brunswick~Newfoundland and Labrador~Northwest Territories~Nova Scotia~Nunavut~Ontario~Prince Edward Island~Quebec~Saskatchewan~Yukon",
    sub_zips:
      "T~V~R~E~A~X0E|X0G|X1A~B~X0A|X0B|X0C~K|L|M|N|P~C~G|H|J|K1A~S|R8A~Y",
    upper: "ACNOSZ",
    zip: "[ABCEGHJKLMNPRSTVXY]\\d[ABCEGHJ-NPRSTV-Z] ?\\d[ABCEGHJ-NPRSTV-Z]\\d",
    zipex: "H3Z 2Y7,V8X 3X4,T0L 1K0,T0H 1A0,K1A 0B1",
  },
  "data/CA--fr": {
    fmt: "%N%n%O%n%A%n%C %S %Z",
    id: "data/CA--fr",
    key: "CA",
    lang: "fr",
    name: "CANADA",
    posturl: "https://www.canadapost.ca/cpo/mc/personal/postalcode/fpc.jsf",
    require: "ACSZ",
    sub_isoids: "AB~BC~PE~MB~NB~NS~NU~ON~QC~SK~NL~NT~YT",
    sub_keys: "AB~BC~PE~MB~NB~NS~NU~ON~QC~SK~NL~NT~YT",
    sub_names:
      "Alberta~Colombie-Britannique~Île-du-Prince-Édouard~Manitoba~Nouveau-Brunswick~Nouvelle-Écosse~Nunavut~Ontario~Québec~Saskatchewan~Terre-Neuve-et-Labrador~Territoires du Nord-Ouest~Yukon",
    sub_zips:
      "T~V~C~R~E~B~X0A|X0B|X0C~K|L|M|N|P~G|H|J|K1A~S|R8A~A~X0E|X0G|X1A~Y",
    upper: "ACNOSZ",
    zip: "[ABCEGHJKLMNPRSTVXY]\\d[ABCEGHJ-NPRSTV-Z] ?\\d[ABCEGHJ-NPRSTV-Z]\\d",
    zipex: "H3Z 2Y7,V8X 3X4,T0L 1K0,T0H 1A0,K1A 0B1",
  },
  "data/CC": {
    fmt: "%O%n%N%n%A%n%C %S %Z",
    id: "data/CC",
    key: "CC",
    name: "COCOS (KEELING) ISLANDS",
    upper: "CS",
    zip: "6799",
    zipex: "6799",
  },
  "data/CD": { id: "data/CD", key: "CD", name: "CONGO (DEM. REP.)" },
  "data/CF": { id: "data/CF", key: "CF", name: "CENTRAL AFRICAN REPUBLIC" },
  "data/CG": { id: "data/CG", key: "CG", name: "CONGO (REP.)" },
  "data/CH": {
    fmt: "%O%n%N%n%A%nCH-%Z %C",
    id: "data/CH",
    key: "CH",
    name: "SWITZERLAND",
    postprefix: "CH-",
    posturl: "http://www.post.ch/db/owa/pv_plz_pack/pr_main",
    require: "ACZ",
    upper: "",
    zip: "\\d{4}",
    zipex: "2544,1211,1556,3030",
  },
  "data/CI": {
    fmt: "%N%n%O%n%X %A %C %X",
    id: "data/CI",
    key: "CI",
    name: "COTE D'IVOIRE",
  },
  "data/CK": { id: "data/CK", key: "CK", name: "COOK ISLANDS" },
  "data/CL": {
    fmt: "%N%n%O%n%A%n%Z %C%n%S",
    id: "data/CL",
    key: "CL",
    lang: "es",
    languages: "es",
    name: "CHILE",
    posturl: "http://www.correos.cl/SitePages/home.aspx",
    sub_isoids: "AN~AR~AP~AT~AI~BI~CO~LI~LL~LR~MA~ML~RM~TA~VS",
    sub_keys:
      "Antofagasta~Araucanía~Arica y Parinacota~Atacama~Aysén~Biobío~Coquimbo~O'Higgins~Los Lagos~Los Ríos~Magallanes~Maule~Región Metropolitana~Tarapacá~Valparaíso",
    sub_mores:
      "true~true~true~true~true~true~true~true~true~true~true~true~true~true~true",
    sub_names:
      "Antofagasta~Araucanía~Arica y Parinacota~Atacama~Aysén del General Carlos Ibáñez del Campo~Biobío~Coquimbo~Libertador General Bernardo O'Higgins~Los Lagos~Los Ríos~Magallanes y de la Antártica Chilena~Maule~Metropolitana de Santiago~Tarapacá~Valparaíso",
    zip: "\\d{7}",
    zipex: "8340457,8720019,1230000,8329100",
  },
  "data/CM": { id: "data/CM", key: "CM", name: "CAMEROON" },
  "data/CN": {
    fmt: "%Z%n%S%C%D%n%A%n%O%n%N",
    id: "data/CN",
    key: "CN",
    lang: "zh",
    languages: "zh",
    lfmt: "%N%n%O%n%A%n%D%n%C%n%S, %Z",
    name: "CHINA",
    posturl: "http://www.ems.com.cn/serviceguide/you_bian_cha_xun.html",
    require: "ACSZ",
    sub_isoids:
      "34~92~11~50~35~62~44~45~52~46~13~41~23~42~43~22~32~36~21~15~64~63~37~14~61~31~51~71~12~54~91~65~53~33",
    sub_keys:
      "安徽省~澳门~北京市~重庆市~福建省~甘肃省~广东省~广西壮族自治区~贵州省~海南省~河北省~河南省~黑龙江省~湖北省~湖南省~吉林省~江苏省~江西省~辽宁省~内蒙古自治区~宁夏回族自治区~青海省~山东省~山西省~陕西省~上海市~四川省~台湾~天津市~西藏自治区~香港~新疆维吾尔自治区~云南省~浙江省",
    sub_lnames:
      "Anhui Sheng~Macau~Beijing Shi~Chongqing Shi~Fujian Sheng~Gansu Sheng~Guangdong Sheng~Guangxi Zhuangzuzizhiqu~Guizhou Sheng~Hainan Sheng~Hebei Sheng~Henan Sheng~Heilongjiang Sheng~Hubei Sheng~Hunan Sheng~Jilin Sheng~Jiangsu Sheng~Jiangxi Sheng~Liaoning Sheng~Neimenggu Zizhiqu~Ningxia Huizuzizhiqu~Qinghai Sheng~Shandong Sheng~Shanxi Sheng~Shaanxi Sheng~Shanghai Shi~Sichuan Sheng~Taiwan~Tianjin Shi~Xizang Zizhiqu~Hong Kong~Xinjiang Weiwuerzizhiqu~Yunnan Sheng~Zhejiang Sheng",
    sub_mores:
      "true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true",
    sub_names:
      "安徽省~澳门~北京市~重庆市~福建省~甘肃省~广东省~广西~贵州省~海南省~河北省~河南省~黑龙江省~湖北省~湖南省~吉林省~江苏省~江西省~辽宁省~内蒙古~宁夏~青海省~山东省~山西省~陕西省~上海市~四川省~台湾~天津市~西藏~香港~新疆~云南省~浙江省",
    sub_xrequires: "~A~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ACS~~~",
    sub_xzips: "~999078~~~~~~~~~~~~~~~~~~~~~~~~~~\\d{3}(\\d{2})?~~~999077~~~",
    sublocality_name_type: "district",
    upper: "S",
    zip: "\\d{6}",
    zipex: "266033,317204,100096,100808",
  },
  "data/CO": {
    fmt: "%N%n%O%n%A%n%C, %S, %Z",
    id: "data/CO",
    key: "CO",
    name: "COLOMBIA",
    posturl: "http://www.codigopostal.gov.co/",
    require: "AS",
    state_name_type: "department",
    zip: "\\d{6}",
    zipex: "111221,130001,760011",
  },
  "data/CR": {
    fmt: "%N%n%O%n%A%n%S, %C%n%Z",
    id: "data/CR",
    key: "CR",
    name: "COSTA RICA",
    posturl: "https://www.correos.go.cr/nosotros/codigopostal/busqueda.html",
    require: "ACS",
    zip: "\\d{4,5}|\\d{3}-\\d{4}",
    zipex: "1000,2010,1001",
  },
  "data/CU": {
    fmt: "%N%n%O%n%A%n%C %S%n%Z",
    id: "data/CU",
    key: "CU",
    lang: "es",
    languages: "es",
    name: "CUBA",
    sub_isoids: "15~09~08~06~12~14~11~99~03~10~04~16~01~07~13~05",
    sub_keys:
      "Artemisa~Camagüey~Ciego de Ávila~Cienfuegos~Granma~Guantánamo~Holguín~Isla de la Juventud~La Habana~Las Tunas~Matanzas~Mayabeque~Pinar del Río~Sancti Spíritus~Santiago de Cuba~Villa Clara",
    zip: "\\d{5}",
    zipex: "10700",
  },
  "data/CV": {
    fmt: "%N%n%O%n%A%n%Z %C%n%S",
    id: "data/CV",
    key: "CV",
    lang: "pt",
    languages: "pt",
    name: "CAPE VERDE",
    state_name_type: "island",
    sub_isoids: "BV~BR~~MA~SL~~~~SV",
    sub_keys:
      "Boa Vista~Brava~Fogo~Maio~Sal~Santiago~Santo Antão~São Nicolau~São Vicente",
    zip: "\\d{4}",
    zipex: "7600",
  },
  "data/CW": { id: "data/CW", key: "CW", name: "CURACAO" },
  "data/CX": {
    fmt: "%O%n%N%n%A%n%C %S %Z",
    id: "data/CX",
    key: "CX",
    name: "CHRISTMAS ISLAND",
    upper: "CS",
    zip: "6798",
    zipex: "6798",
  },
  "data/CY": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/CY",
    key: "CY",
    name: "CYPRUS",
    zip: "\\d{4}",
    zipex: "2008,3304,1900",
  },
  "data/CZ": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/CZ",
    key: "CZ",
    name: "CZECH REP.",
    posturl: "http://psc.ceskaposta.cz/CleanForm.action",
    require: "ACZ",
    zip: "\\d{3} ?\\d{2}",
    zipex: "100 00,251 66,530 87,110 00,225 99",
  },
  "data/DE": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/DE",
    key: "DE",
    name: "GERMANY",
    posturl: "http://www.postdirekt.de/plzserver/",
    require: "ACZ",
    zip: "\\d{5}",
    zipex: "26133,53225",
  },
  "data/DJ": { id: "data/DJ", key: "DJ", name: "DJIBOUTI" },
  "data/DK": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/DK",
    key: "DK",
    name: "DENMARK",
    posturl:
      "http://www.postdanmark.dk/da/Privat/Kundeservice/postnummerkort/Sider/Find-postnummer.aspx",
    require: "ACZ",
    zip: "\\d{4}",
    zipex: "8660,1566",
  },
  "data/DM": { id: "data/DM", key: "DM", name: "DOMINICA" },
  "data/DO": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/DO",
    key: "DO",
    name: "DOMINICAN REP.",
    posturl: "http://inposdom.gob.do/codigo-postal/",
    zip: "\\d{5}",
    zipex: "11903,10101",
  },
  "data/DZ": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/DZ",
    key: "DZ",
    name: "ALGERIA",
    zip: "\\d{5}",
    zipex: "40304,16027",
  },
  "data/EC": {
    fmt: "%N%n%O%n%A%n%Z%n%C",
    id: "data/EC",
    key: "EC",
    name: "ECUADOR",
    posturl: "http://www.codigopostal.gob.ec/",
    upper: "CZ",
    zip: "\\d{6}",
    zipex: "090105,092301",
  },
  "data/EE": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/EE",
    key: "EE",
    name: "ESTONIA",
    posturl: "https://www.omniva.ee/era/sihtnumbrite_otsing",
    zip: "\\d{5}",
    zipex: "69501,11212",
  },
  "data/EG": {
    fmt: "%N%n%O%n%A%n%C%n%S%n%Z",
    id: "data/EG",
    key: "EG",
    lang: "ar",
    languages: "ar",
    lfmt: "%N%n%O%n%A%n%C%n%S%n%Z",
    name: "EGYPT",
    sub_isoids:
      "ASN~AST~ALX~IS~LX~BA~BH~GZ~DK~SUZ~SHR~GH~FYM~C~KB~MNF~MN~WAD~BNS~PTS~JS~DT~SHG~SIN~KN~KFS~MT",
    sub_keys:
      "أسوان~أسيوط~الإسكندرية~الإسماعيلية~الأقصر~البحر الأحمر~البحيرة~الجيزة~الدقهلية~السويس~الشرقية~الغربية~الفيوم~القاهرة~القليوبية~المنوفية~المنيا~الوادي الجديد~بني سويف~بورسعيد~جنوب سيناء~دمياط~سوهاج~شمال سيناء~قنا~كفر الشيخ~مطروح",
    sub_lnames:
      "Aswan Governorate~Asyut Governorate~Alexandria Governorate~Ismailia Governorate~Luxor Governorate~Red Sea Governorate~El Beheira Governorate~Giza Governorate~Dakahlia Governorate~Suez Governorate~Ash Sharqia Governorate~Gharbia Governorate~Faiyum Governorate~Cairo Governorate~Qalyubia Governorate~Menofia Governorate~Menia Governorate~New Valley Governorate~Beni Suef Governorate~Port Said Governorate~South Sinai Governorate~Damietta Governorate~Sohag Governorate~North Sinai Governorate~Qena Governorate~Kafr El Sheikh Governorate~Matrouh Governorate",
    sub_zipexs:
      "81000~71000~21000,23000~41000~85000~84000~22000~12000~35000~43000~44000~31000~63000~11000~13000~32000~61000~72000~62000~42000~46000~34000~82000~45000~83000~33000~51000",
    sub_zips:
      "81~71~2[13]~41~85~84~22~12~35~43~44~31~63~11~13~32~61~72~62~42~46~34~82~45~83~33~51",
    zip: "\\d{5}",
    zipex: "12411,11599",
  },
  "data/EH": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/EH",
    key: "EH",
    name: "WESTERN SAHARA",
    zip: "\\d{5}",
    zipex: "70000,72000",
  },
  "data/ER": { id: "data/ER", key: "ER", name: "ERITREA" },
  "data/ES": {
    fmt: "%N%n%O%n%A%n%Z %C %S",
    id: "data/ES",
    key: "ES",
    lang: "es",
    languages: "es~ca~gl~eu",
    name: "SPAIN",
    posturl:
      "http://www.correos.es/contenido/13-MenuRec2/04-MenuRec24/1010_s-CodPostal.asp",
    require: "ACSZ",
    sub_keys:
      "VI~AB~A~AL~O~AV~BA~B~BU~CC~CA~S~CS~CE~CR~CO~CU~GI~GR~GU~SS~H~HU~PM~J~C~LO~GC~LE~L~LU~M~MA~ML~MU~NA~OR~P~PO~SA~TF~SG~SE~SO~T~TE~TO~V~VA~BI~ZA~Z",
    sub_names:
      "Álava~Albacete~Alicante~Almería~Asturias~Ávila~Badajoz~Barcelona~Burgos~Cáceres~Cádiz~Cantabria~Castellón~Ceuta~Ciudad Real~Córdoba~Cuenca~Girona~Granada~Guadalajara~Guipúzcoa~Huelva~Huesca~Islas Baleares~Jaén~La Coruña~La Rioja~Las Palmas~León~Lérida~Lugo~Madrid~Málaga~Melilla~Murcia~Navarra~Ourense~Palencia~Pontevedra~Salamanca~Santa Cruz de Tenerife~Segovia~Sevilla~Soria~Tarragona~Teruel~Toledo~Valencia~Valladolid~Vizcaya~Zamora~Zaragoza",
    sub_zips:
      "01~02~03~04~33~05~06~08~09~10~11~39~12~51~13~14~16~17~18~19~20~21~22~07~23~15~26~35~24~25~27~28~29~52~30~31~32~34~36~37~38~40~41~42~43~44~45~46~47~48~49~50",
    upper: "CS",
    zip: "\\d{5}",
    zipex: "28039,28300,28070",
  },
  "data/ES--ca": {
    fmt: "%N%n%O%n%A%n%Z %C %S",
    id: "data/ES--ca",
    key: "ES",
    lang: "ca",
    name: "SPAIN",
    posturl:
      "http://www.correos.es/contenido/13-MenuRec2/04-MenuRec24/1010_s-CodPostal.asp",
    require: "ACSZ",
    sub_keys:
      "A~AB~AL~VI~O~AV~BA~B~BI~BU~CC~CA~S~CS~CE~CR~CO~CU~GI~GR~GU~SS~H~HU~PM~J~C~LO~GC~LE~L~LU~M~MA~ML~MU~NA~OR~P~PO~SA~TF~SG~SE~SO~T~TE~TO~V~VA~ZA~Z",
    sub_names:
      "Alacant~Albacete~Almeria~Araba~Asturias~Àvila~Badajoz~Barcelona~Bizkaia~Burgos~Cáceres~Cadis~Cantabria~Castelló~Ceuta~Ciudad Real~Córdoba~Cuenca~Girona~Granada~Guadalajara~Guipúscoa~Huelva~Huesca~Illes Balears~Jaén~La Corunya~La Rioja~Las Palmas~León~Lleida~Lugo~Madrid~Málaga~Melilla~Murcia~Navarra~Ourense~Palencia~Pontevedra~Salamanca~Santa Cruz de Tenerife~Segovia~Sevilla~Soria~Tarragona~Teruel~Toledo~València~Valladolid~Zamora~Zaragoza",
    sub_zips:
      "03~02~04~01~33~05~06~08~48~09~10~11~39~12~51~13~14~16~17~18~19~20~21~22~07~23~15~26~35~24~25~27~28~29~52~30~31~32~34~36~37~38~40~41~42~43~44~45~46~47~49~50",
    upper: "CS",
    zip: "\\d{5}",
    zipex: "28039,28300,28070",
  },
  "data/ES--eu": {
    fmt: "%N%n%O%n%A%n%Z %C %S",
    id: "data/ES--eu",
    key: "ES",
    lang: "eu",
    name: "SPAIN",
    posturl:
      "http://www.correos.es/contenido/13-MenuRec2/04-MenuRec24/1010_s-CodPostal.asp",
    require: "ACSZ",
    sub_keys:
      "A~AB~AL~VI~O~AV~BA~B~BI~BU~CC~CA~S~CS~CE~CR~C~CU~SS~GI~GR~GU~H~HU~PM~J~CO~LO~GC~LE~L~LU~M~MA~ML~MU~NA~OR~P~PO~SA~TF~SG~SE~SO~T~TE~TO~V~VA~ZA~Z",
    sub_names:
      "Alacant~Albacete~Almería~Araba~Asturias~Ávila~Badajoz~Barcelona~Bizkaia~Burgos~Cáceres~Cádiz~Cantabria~Castelló~Ceuta~Ciudad Real~Coruña~Cuenca~Gipuzkoa~Girona~Granada~Guadalajara~Huelva~Huesca~Illes Balears~Jaén~Kordoba~La Rioja~Las Palmas~León~Lleida~Lugo~Madrid~Málaga~Melilla~Murtzia~Nafarroa~Ourense~Palentzia~Pontevedra~Salamanca~Santa Cruz Tenerifekoa~Segovia~Sevilla~Soria~Tarragona~Teruel~Toledo~Valentzia~Valladolid~Zamora~Zaragoza",
    sub_zips:
      "03~02~04~01~33~05~06~08~48~09~10~11~39~12~51~13~15~16~20~17~18~19~21~22~07~23~14~26~35~24~25~27~28~29~52~30~31~32~34~36~37~38~40~41~42~43~44~45~46~47~49~50",
    upper: "CS",
    zip: "\\d{5}",
    zipex: "28039,28300,28070",
  },
  "data/ES--gl": {
    fmt: "%N%n%O%n%A%n%Z %C %S",
    id: "data/ES--gl",
    key: "ES",
    lang: "gl",
    name: "SPAIN",
    posturl:
      "http://www.correos.es/contenido/13-MenuRec2/04-MenuRec24/1010_s-CodPostal.asp",
    require: "ACSZ",
    sub_keys:
      "C~A~VI~AB~AL~GC~O~AV~BA~B~BI~BU~CC~CA~S~CS~CE~CR~CO~CU~GR~GU~SS~H~HU~PM~LO~LE~L~LU~M~MA~ML~MU~NA~OR~P~PO~SA~TF~SG~SE~SO~T~TE~TO~V~VA~J~GI~ZA~Z",
    sub_names:
      "A Coruña~Alacant~Álava~Albacete~Almería~As Palmas~Asturias~Ávila~Badaxoz~Barcelona~Biscaia~Burgos~Cáceres~Cádiz~Cantabria~Castelló~Ceuta~Cidade Real~Córdoba~Cuenca~Granada~Guadalajara~Guipúscoa~Huelva~Huesca~Illas Baleares~La Rioja~León~Lleida~Lugo~Madrid~Málaga~Melilla~Murcia~Navarra~Ourense~Palencia~Pontevedra~Salamanca~Santa Cruz de Tenerife~Segovia~Sevilla~Soria~Tarragona~Teruel~Toledo~Valencia~Valladolid~Xaén~Xirona~Zamora~Zaragoza",
    sub_zips:
      "15~03~01~02~04~35~33~05~06~08~48~09~10~11~39~12~51~13~14~16~18~19~20~21~22~07~26~24~25~27~28~29~52~30~31~32~34~36~37~38~40~41~42~43~44~45~46~47~23~17~49~50",
    upper: "CS",
    zip: "\\d{5}",
    zipex: "28039,28300,28070",
  },
  "data/ET": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/ET",
    key: "ET",
    name: "ETHIOPIA",
    zip: "\\d{4}",
    zipex: "1000",
  },
  "data/FI": {
    fmt: "%O%n%N%n%A%nFI-%Z %C",
    id: "data/FI",
    key: "FI",
    name: "FINLAND",
    postprefix: "FI-",
    posturl: "http://www.verkkoposti.com/e3/postinumeroluettelo",
    require: "ACZ",
    zip: "\\d{5}",
    zipex: "00550,00011",
  },
  "data/FJ": { id: "data/FJ", key: "FJ", name: "FIJI" },
  "data/FK": {
    fmt: "%N%n%O%n%A%n%C%n%Z",
    id: "data/FK",
    key: "FK",
    name: "FALKLAND ISLANDS (MALVINAS)",
    require: "ACZ",
    upper: "CZ",
    zip: "FIQQ 1ZZ",
    zipex: "FIQQ 1ZZ",
  },
  "data/FM": {
    fmt: "%N%n%O%n%A%n%C %S %Z",
    id: "data/FM",
    key: "FM",
    name: "MICRONESIA (Federated State of)",
    posturl: "http://zip4.usps.com/zip4/welcome.jsp",
    require: "ACSZ",
    state_name_type: "state",
    upper: "ACNOS",
    zip: "(9694[1-4])(?:[ \\-](\\d{4}))?",
    zip_name_type: "zip",
    zipex: "96941,96944",
  },
  "data/FO": {
    fmt: "%N%n%O%n%A%nFO%Z %C",
    id: "data/FO",
    key: "FO",
    name: "FAROE ISLANDS",
    postprefix: "FO",
    posturl: "http://www.postur.fo/",
    zip: "\\d{3}",
    zipex: "100",
  },
  "data/FR": {
    fmt: "%O%n%N%n%A%n%Z %C %X",
    id: "data/FR",
    key: "FR",
    name: "FRANCE",
    posturl:
      "http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal",
    require: "ACZ",
    upper: "CX",
    zip: "\\d{2} ?\\d{3}",
    zipex: "33380,34092,33506",
  },
  "data/GA": { id: "data/GA", key: "GA", name: "GABON" },
  "data/GB": {
    fmt: "%N%n%O%n%A%n%C%n%Z",
    id: "data/GB",
    key: "GB",
    locality_name_type: "post_town",
    name: "UNITED KINGDOM",
    posturl: "http://www.royalmail.com/postcode-finder",
    require: "ACZ",
    upper: "CZ",
    zip: "GIR ?0AA|(?:(?:AB|AL|B|BA|BB|BD|BF|BH|BL|BN|BR|BS|BT|BX|CA|CB|CF|CH|CM|CO|CR|CT|CV|CW|DA|DD|DE|DG|DH|DL|DN|DT|DY|E|EC|EH|EN|EX|FK|FY|G|GL|GY|GU|HA|HD|HG|HP|HR|HS|HU|HX|IG|IM|IP|IV|JE|KA|KT|KW|KY|L|LA|LD|LE|LL|LN|LS|LU|M|ME|MK|ML|N|NE|NG|NN|NP|NR|NW|OL|OX|PA|PE|PH|PL|PO|PR|RG|RH|RM|S|SA|SE|SG|SK|SL|SM|SN|SO|SP|SR|SS|ST|SW|SY|TA|TD|TF|TN|TQ|TR|TS|TW|UB|W|WA|WC|WD|WF|WN|WR|WS|WV|YO|ZE)(?:\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}))|BFPO ?\\d{1,4}",
    zipex:
      "EC1Y 8SY,GIR 0AA,M2 5BQ,M34 4AB,CR0 2YR,DN16 9AA,W1A 4ZZ,EC1A 1HQ,OX14 4PG,BS18 8HF,NR25 7HG,RH6 0NP,BH23 6AA,B6 5BA,SO23 9AP,PO1 3AX,BFPO 61",
  },
  "data/GD": { id: "data/GD", key: "GD", name: "GRENADA (WEST INDIES)" },
  "data/GE": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/GE",
    key: "GE",
    name: "GEORGIA",
    posturl: "http://www.georgianpost.ge/index.php?page=10",
    zip: "\\d{4}",
    zipex: "0101",
  },
  "data/GF": {
    fmt: "%O%n%N%n%A%n%Z %C %X",
    id: "data/GF",
    key: "GF",
    name: "FRENCH GUIANA",
    posturl:
      "http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal",
    require: "ACZ",
    upper: "ACX",
    zip: "9[78]3\\d{2}",
    zipex: "97300",
  },
  "data/GG": {
    fmt: "%N%n%O%n%A%n%C%nGUERNSEY%n%Z",
    id: "data/GG",
    key: "GG",
    name: "CHANNEL ISLANDS",
    posturl: "http://www.guernseypost.com/postcode_finder/",
    require: "ACZ",
    upper: "CZ",
    zip: "GY\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}",
    zipex: "GY1 1AA,GY2 2BT",
  },
  "data/GH": { id: "data/GH", key: "GH", name: "GHANA" },
  "data/GI": {
    fmt: "%N%n%O%n%A%nGIBRALTAR%n%Z",
    id: "data/GI",
    key: "GI",
    name: "GIBRALTAR",
    require: "A",
    zip: "GX11 1AA",
    zipex: "GX11 1AA",
  },
  "data/GL": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/GL",
    key: "GL",
    name: "GREENLAND",
    require: "ACZ",
    zip: "39\\d{2}",
    zipex: "3900,3950,3911",
  },
  "data/GM": { id: "data/GM", key: "GM", name: "GAMBIA" },
  "data/GN": {
    fmt: "%N%n%O%n%Z %A %C",
    id: "data/GN",
    key: "GN",
    name: "GUINEA",
    zip: "\\d{3}",
    zipex: "001,200,100",
  },
  "data/GP": {
    fmt: "%O%n%N%n%A%n%Z %C %X",
    id: "data/GP",
    key: "GP",
    name: "GUADELOUPE",
    posturl:
      "http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal",
    require: "ACZ",
    upper: "ACX",
    zip: "9[78][01]\\d{2}",
    zipex: "97100",
  },
  "data/GQ": { id: "data/GQ", key: "GQ", name: "EQUATORIAL GUINEA" },
  "data/GR": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/GR",
    key: "GR",
    name: "GREECE",
    posturl: "http://www.elta.gr/findapostcode.aspx",
    require: "ACZ",
    zip: "\\d{3} ?\\d{2}",
    zipex: "151 24,151 10,101 88",
  },
  "data/GS": {
    fmt: "%N%n%O%n%A%n%n%C%n%Z",
    id: "data/GS",
    key: "GS",
    name: "SOUTH GEORGIA",
    require: "ACZ",
    upper: "CZ",
    zip: "SIQQ 1ZZ",
    zipex: "SIQQ 1ZZ",
  },
  "data/GT": {
    fmt: "%N%n%O%n%A%n%Z- %C",
    id: "data/GT",
    key: "GT",
    name: "GUATEMALA",
    zip: "\\d{5}",
    zipex: "09001,01501",
  },
  "data/GU": {
    fmt: "%N%n%O%n%A%n%C %Z",
    id: "data/GU",
    key: "GU",
    name: "GUAM",
    posturl: "http://zip4.usps.com/zip4/welcome.jsp",
    require: "ACZ",
    upper: "ACNO",
    zip: "(969(?:[12]\\d|3[12]))(?:[ \\-](\\d{4}))?",
    zip_name_type: "zip",
    zipex: "96910,96931",
  },
  "data/GW": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/GW",
    key: "GW",
    name: "GUINEA-BISSAU",
    zip: "\\d{4}",
    zipex: "1000,1011",
  },
  "data/GY": { id: "data/GY", key: "GY", name: "GUYANA" },
  "data/HK": {
    fmt: "%S%n%C%n%A%n%O%n%N",
    id: "data/HK",
    key: "HK",
    lang: "zh-Hant",
    languages: "zh-Hant~en",
    lfmt: "%N%n%O%n%A%n%C%n%S",
    locality_name_type: "district",
    name: "HONG KONG",
    require: "AS",
    state_name_type: "area",
    sub_keys: "Kowloon~Hong Kong Island~New Territories",
    sub_mores: "true~true~true",
    sub_names: "九龍~香港島~新界",
    upper: "S",
  },
  "data/HK--en": {
    fmt: "%S%n%C%n%A%n%O%n%N",
    id: "data/HK--en",
    key: "HK",
    lang: "en",
    lfmt: "%N%n%O%n%A%n%C%n%S",
    locality_name_type: "district",
    name: "HONG KONG",
    require: "AS",
    state_name_type: "area",
    sub_keys: "Hong Kong Island~Kowloon~New Territories",
    sub_lnames: "Hong Kong Island~Kowloon~New Territories",
    sub_mores: "true~true~true",
    upper: "S",
  },
  "data/HM": {
    fmt: "%O%n%N%n%A%n%C %S %Z",
    id: "data/HM",
    key: "HM",
    name: "HEARD AND MCDONALD ISLANDS",
    upper: "CS",
    zip: "\\d{4}",
    zipex: "7050",
  },
  "data/HN": {
    fmt: "%N%n%O%n%A%n%C, %S%n%Z",
    id: "data/HN",
    key: "HN",
    name: "HONDURAS",
    require: "ACS",
    zip: "\\d{5}",
    zipex: "31301",
  },
  "data/HR": {
    fmt: "%N%n%O%n%A%nHR-%Z %C",
    id: "data/HR",
    key: "HR",
    name: "CROATIA",
    postprefix: "HR-",
    posturl: "http://www.posta.hr/default.aspx?pretpum",
    zip: "\\d{5}",
    zipex: "10000,21001,10002",
  },
  "data/HT": {
    fmt: "%N%n%O%n%A%nHT%Z %C",
    id: "data/HT",
    key: "HT",
    name: "HAITI",
    postprefix: "HT",
    zip: "\\d{4}",
    zipex: "6120,5310,6110,8510",
  },
  "data/HU": {
    fmt: "%N%n%O%n%C%n%A%n%Z",
    id: "data/HU",
    key: "HU",
    name: "HUNGARY (Rep.)",
    posturl: "http://posta.hu/ugyfelszolgalat/iranyitoszam_kereso",
    require: "ACZ",
    upper: "ACNO",
    zip: "\\d{4}",
    zipex: "1037,2380,1540",
  },
  "data/ID": {
    fmt: "%N%n%O%n%A%n%C%n%S %Z",
    id: "data/ID",
    key: "ID",
    lang: "id",
    languages: "id",
    name: "INDONESIA",
    require: "AS",
    sub_isoids:
      "AC~BA~BT~BE~YO~JK~GO~JA~JB~JT~JI~KB~KS~KT~KI~KU~BB~KR~LA~MA~MU~NB~NT~PA~PB~RI~SR~SN~ST~SG~SA~SB~SS~SU",
    sub_keys:
      "Aceh~Bali~Banten~Bengkulu~Daerah Istimewa Yogyakarta~DKI Jakarta~Gorontalo~Jambi~Jawa Barat~Jawa Tengah~Jawa Timur~Kalimantan Barat~Kalimantan Selatan~Kalimantan Tengah~Kalimantan Timur~Kalimantan Utara~Kepulauan Bangka Belitung~Kepulauan Riau~Lampung~Maluku~Maluku Utara~Nusa Tenggara Barat~Nusa Tenggara Timur~Papua~Papua Barat~Riau~Sulawesi Barat~Sulawesi Selatan~Sulawesi Tengah~Sulawesi Tenggara~Sulawesi Utara~Sumatera Barat~Sumatera Selatan~Sumatera Utara",
    zip: "\\d{5}",
    zipex: "40115",
  },
  "data/IE": {
    fmt: "%N%n%O%n%A%n%D%n%C%n%S %Z",
    id: "data/IE",
    key: "IE",
    lang: "en",
    languages: "en",
    name: "IRELAND",
    posturl: "https://finder.eircode.ie",
    state_name_type: "county",
    sub_isoids:
      "CW~CN~CE~C~DL~D~G~KY~KE~KK~LS~LM~LK~LD~LH~MO~MH~MN~OY~RN~SO~TA~WD~WH~WX~WW",
    sub_keys:
      "Co. Carlow~Co. Cavan~Co. Clare~Co. Cork~Co. Donegal~Co. Dublin~Co. Galway~Co. Kerry~Co. Kildare~Co. Kilkenny~Co. Laois~Co. Leitrim~Co. Limerick~Co. Longford~Co. Louth~Co. Mayo~Co. Meath~Co. Monaghan~Co. Offaly~Co. Roscommon~Co. Sligo~Co. Tipperary~Co. Waterford~Co. Westmeath~Co. Wexford~Co. Wicklow",
    sublocality_name_type: "townland",
    zip: "[\\dA-Z]{3} ?[\\dA-Z]{4}",
    zip_name_type: "eircode",
    zipex: "A65 F4E2",
  },
  "data/IL": {
    fmt: "%N%n%O%n%A%n%C %Z",
    id: "data/IL",
    key: "IL",
    name: "ISRAEL",
    posturl: "http://www.israelpost.co.il/zipcode.nsf/demozip?openform",
    zip: "\\d{5}(?:\\d{2})?",
    zipex: "9614303",
  },
  "data/IM": {
    fmt: "%N%n%O%n%A%n%C%n%Z",
    id: "data/IM",
    key: "IM",
    name: "ISLE OF MAN",
    posturl: "https://www.iompost.com/tools-forms/postcode-finder/",
    require: "ACZ",
    upper: "CZ",
    zip: "IM\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}",
    zipex: "IM2 1AA,IM99 1PS",
  },
  "data/IN": {
    fmt: "%N%n%O%n%A%n%C %Z%n%S",
    id: "data/IN",
    key: "IN",
    lang: "en",
    languages: "en~hi",
    name: "INDIA",
    posturl: "https://www.indiapost.gov.in/vas/pages/FindPinCode.aspx",
    require: "ACSZ",
    state_name_type: "state",
    sub_isoids:
      "AN~AP~AR~AS~BR~CH~CT~DN~DD~DL~GA~GJ~HR~HP~JK~JH~KA~KL~LD~MP~MH~MN~ML~MZ~NL~OR~PY~PB~RJ~SK~TN~TG~TR~UP~UT~WB",
    sub_keys:
      "Andaman and Nicobar Islands~Andhra Pradesh~Arunachal Pradesh~Assam~Bihar~Chandigarh~Chhattisgarh~Dadra and Nagar Haveli~Daman and Diu~Delhi~Goa~Gujarat~Haryana~Himachal Pradesh~Jammu and Kashmir~Jharkhand~Karnataka~Kerala~Lakshadweep~Madhya Pradesh~Maharashtra~Manipur~Meghalaya~Mizoram~Nagaland~Odisha~Puducherry~Punjab~Rajasthan~Sikkim~Tamil Nadu~Telangana~Tripura~Uttar Pradesh~Uttarakhand~West Bengal",
    sub_names:
      "Andaman & Nicobar~Andhra Pradesh~Arunachal Pradesh~Assam~Bihar~Chandigarh~Chhattisgarh~Dadra & Nagar Haveli~Daman & Diu~Delhi~Goa~Gujarat~Haryana~Himachal Pradesh~Jammu & Kashmir~Jharkhand~Karnataka~Kerala~Lakshadweep~Madhya Pradesh~Maharashtra~Manipur~Meghalaya~Mizoram~Nagaland~Odisha~Puducherry~Punjab~Rajasthan~Sikkim~Tamil Nadu~Telangana~Tripura~Uttar Pradesh~Uttarakhand~West Bengal",
    sub_zips:
      "744~5[0-3]~79[0-2]~78~8[0-5]~16|1440[3-9]~49~396~396~11~403~3[6-9]~1[23]~17~1[89]~81[4-9]|82|83[0-5]~5[4-9]|53[7-9]~6[7-9]|6010|607008|777~682~4[5-8]|490~4[0-4]~79[56]~79[34]~796~79[78]~7[5-7]~60[579]~1[456]~3[0-4]~737|750~6[0-6]|536~5[0-3]~799~2[0-35-8]|24[0-7]|26[12]~24[46-9]|254|26[23]~7[0-4]",
    zip: "\\d{6}",
    zip_name_type: "pin",
    zipex: "110034,110001",
  },
  "data/IN--hi": {
    fmt: "%N%n%O%n%A%n%C %Z%n%S",
    id: "data/IN--hi",
    key: "IN",
    lang: "hi",
    name: "INDIA",
    posturl: "https://www.indiapost.gov.in/vas/pages/FindPinCode.aspx",
    require: "ACSZ",
    state_name_type: "state",
    sub_isoids:
      "AN~AR~AS~AP~UP~UT~OR~KA~KL~GJ~GA~CH~CT~JK~JH~TN~TG~TR~DD~DN~DL~NL~PB~WB~PY~BR~MN~MP~MH~MZ~ML~RJ~LD~SK~HR~HP",
    sub_keys:
      "Andaman & Nicobar~Arunachal Pradesh~Assam~Andhra Pradesh~Uttar Pradesh~Uttarakhand~Odisha~Karnataka~Kerala~Gujarat~Goa~Chandigarh~Chhattisgarh~Jammu & Kashmir~Jharkhand~Tamil Nadu~Telangana~Tripura~Daman & Diu~Dadra & Nagar Haveli~Delhi~Nagaland~Punjab~West Bengal~Puducherry~Bihar~Manipur~Madhya Pradesh~Maharashtra~Mizoram~Meghalaya~Rajasthan~Lakshadweep~Sikkim~Haryana~Himachal Pradesh",
    sub_names:
      "अंडमान और निकोबार द्वीपसमूह~अरुणाचल प्रदेश~असम~आंध्र प्रदेश~उत्तर प्रदेश~उत्तराखण्ड~ओड़िशा~कर्नाटक~केरल~गुजरात~गोआ~चंडीगढ़~छत्तीसगढ़~जम्मू और कश्मीर~झारखण्ड~तमिल नाडु~तेलंगाना~त्रिपुरा~दमन और दीव~दादरा और नगर हवेली~दिल्ली~नागालैंड~पंजाब~पश्चिम बंगाल~पांडिचेरी~बिहार~मणिपुर~मध्य प्रदेश~महाराष्ट्र~मिजोरम~मेघालय~राजस्थान~लक्षद्वीप~सिक्किम~हरियाणा~हिमाचल प्रदेश",
    sub_zips:
      "744~79[0-2]~78~5[0-3]~2[0-35-8]|24[0-7]|26[12]~24[46-9]|254|26[23]~7[5-7]~5[4-9]|53[7-9]~6[7-9]|6010|607008|777~3[6-9]~403~16|1440[3-9]~49~1[89]~81[4-9]|82|83[0-5]~6[0-6]|536~5[0-3]~799~396~396~11~79[78]~1[456]~7[0-4]~60[579]~8[0-5]~79[56]~4[5-8]|490~4[0-4]~796~79[34]~3[0-4]~682~737|750~1[23]~17",
    zip: "\\d{6}",
    zip_name_type: "pin",
    zipex: "110034,110001",
  },
  "data/IO": {
    fmt: "%N%n%O%n%A%n%C%n%Z",
    id: "data/IO",
    key: "IO",
    name: "BRITISH INDIAN OCEAN TERRITORY",
    require: "ACZ",
    upper: "CZ",
    zip: "BBND 1ZZ",
    zipex: "BBND 1ZZ",
  },
  "data/IQ": {
    fmt: "%O%n%N%n%A%n%C, %S%n%Z",
    id: "data/IQ",
    key: "IQ",
    name: "IRAQ",
    require: "ACS",
    upper: "CS",
    zip: "\\d{5}",
    zipex: "31001",
  },
  "data/IR": {
    fmt: "%O%n%N%n%S%n%C, %D%n%A%n%Z",
    id: "data/IR",
    key: "IR",
    lang: "fa",
    languages: "fa",
    name: "IRAN",
    sub_isoids:
      "01~02~03~04~32~05~06~07~08~29~30~31~10~11~12~13~14~28~26~16~15~17~18~27~19~20~21~22~23~24~25",
    sub_keys:
      "استان آذربایجان شرقی~استان آذربایجان غربی~استان اردبیل~استان اصفهان~استان البرز~استان ایلام~استان بوشهر~استان تهران~استان چهارمحال و بختیاری~استان خراسان جنوبی~استان خراسان رضوی~استان خراسان شمالی~استان خوزستان~استان زنجان~استان سمنان~استان سیستان و بلوچستان~استان فارس~استان قزوین~استان قم~استان کردستان~استان کرمان~استان کرمانشاه~استان کهگیلویه و بویراحمد~استان گلستان~استان گیلان~استان لرستان~استان مازندران~استان مرکزی~استان هرمزگان~استان همدان~استان یزد",
    sub_lnames:
      "East Azerbaijan Province~West Azerbaijan Province~Ardabil Province~Isfahan Province~Alborz Province~Ilam Province~Bushehr Province~Tehran Province~Chaharmahal and Bakhtiari Province~South Khorasan Province~Razavi Khorasan Province~North Khorasan Province~Khuzestan Province~Zanjan Province~Semnan Province~Sistan and Baluchestan Province~Fars Province~Qazvin Province~Qom Province~Kurdistan Province~Kerman Province~Kermanshah Province~Kohgiluyeh and Boyer-Ahmad Province~Golestan Province~Gilan Province~Lorestan Province~Mazandaran Province~Markazi Province~Hormozgan Province~Hamadan Province~Yazd Province",
    sublocality_name_type: "neighborhood",
    zip: "\\d{5}-?\\d{5}",
    zipex: "11936-12345",
  },
  "data/IS": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/IS",
    key: "IS",
    name: "ICELAND",
    posturl: "http://www.postur.is/einstaklingar/posthus/postnumer/",
    zip: "\\d{3}",
    zipex: "320,121,220,110",
  },
  "data/IT": {
    fmt: "%N%n%O%n%A%n%Z %C %S",
    id: "data/IT",
    key: "IT",
    lang: "it",
    languages: "it",
    name: "ITALY",
    posturl: "http://www.poste.it/online/cercacap/",
    require: "ACSZ",
    sub_isoids:
      "AG~AL~AN~AO~AR~AP~AT~AV~BA~BT~BL~BN~BG~BI~BO~BZ~BS~BR~CA~CL~CB~CI~CE~CT~CZ~CH~CO~CS~CR~KR~CN~EN~FM~FE~FI~FG~FC~FR~GE~GO~GR~IM~IS~AQ~SP~LT~LE~LC~LI~LO~LU~MC~MN~MS~MT~VS~ME~MI~MO~MB~NA~NO~NU~OG~OT~OR~PD~PA~PR~PV~PG~PU~PE~PC~PI~PT~PN~PZ~PO~RG~RA~RC~RE~RI~RN~RM~RO~SA~SS~SV~SI~SR~SO~TA~TE~TR~TO~TP~TN~TV~TS~UD~VA~VE~VB~VC~VR~VV~VI~VT",
    sub_keys:
      "AG~AL~AN~AO~AR~AP~AT~AV~BA~BT~BL~BN~BG~BI~BO~BZ~BS~BR~CA~CL~CB~CI~CE~CT~CZ~CH~CO~CS~CR~KR~CN~EN~FM~FE~FI~FG~FC~FR~GE~GO~GR~IM~IS~AQ~SP~LT~LE~LC~LI~LO~LU~MC~MN~MS~MT~VS~ME~MI~MO~MB~NA~NO~NU~OG~OT~OR~PD~PA~PR~PV~PG~PU~PE~PC~PI~PT~PN~PZ~PO~RG~RA~RC~RE~RI~RN~RM~RO~SA~SS~SV~SI~SR~SO~TA~TE~TR~TO~TP~TN~TV~TS~UD~VA~VE~VB~VC~VR~VV~VI~VT",
    sub_names:
      "Agrigento~Alessandria~Ancona~Aosta~Arezzo~Ascoli Piceno~Asti~Avellino~Bari~Barletta-Andria-Trani~Belluno~Benevento~Bergamo~Biella~Bologna~Bolzano~Brescia~Brindisi~Cagliari~Caltanissetta~Campobasso~Carbonia-Iglesias~Caserta~Catania~Catanzaro~Chieti~Como~Cosenza~Cremona~Crotone~Cuneo~Enna~Fermo~Ferrara~Firenze~Foggia~Forlì-Cesena~Frosinone~Genova~Gorizia~Grosseto~Imperia~Isernia~L'Aquila~La Spezia~Latina~Lecce~Lecco~Livorno~Lodi~Lucca~Macerata~Mantova~Massa-Carrara~Matera~Medio Campidano~Messina~Milano~Modena~Monza e Brianza~Napoli~Novara~Nuoro~Ogliastra~Olbia-Tempio~Oristano~Padova~Palermo~Parma~Pavia~Perugia~Pesaro e Urbino~Pescara~Piacenza~Pisa~Pistoia~Pordenone~Potenza~Prato~Ragusa~Ravenna~Reggio Calabria~Reggio Emilia~Rieti~Rimini~Roma~Rovigo~Salerno~Sassari~Savona~Siena~Siracusa~Sondrio~Taranto~Teramo~Terni~Torino~Trapani~Trento~Treviso~Trieste~Udine~Varese~Venezia~Verbano-Cusio-Ossola~Vercelli~Verona~Vibo Valentia~Vicenza~Viterbo",
    sub_zips:
      "92~15~60~11~52~63~14~83~70~76[01]~32~82~24~13[89]~40~39~25~72~0912[1-9]|0913[0-4]|0901[0289]|0902[03468]|0903[0234]|0904|0803[035]|08043~93~860[1-4]|86100~0901[013-7]~81~95~88[01]~66~22~87~26[01]~88[89]~12|18025~94~638|63900~44~50~71~47[015]~03~16~34[01]7~58~18~860[7-9]|86170~67~19~04~73~23[89]~57~26[89]~55~62~46~54~75~0902[012579]|0903[015-9]|09040~98~20~41~208|20900~80~28[01]~080[1-3]|08100~08037|0804[024-9]~08020|0702|0703[08]~090[7-9]|09170|0801[039]|0803[04]~35~90~43~27~06~61~65~29~56~51~330[7-9]|33170~85~59~97~48~89[01]~42~02~47[89]~00~45~84~070[14]|0703[0-79]|07100~17|12071~53~96~23[01]~74~64~05~10~91~38~31~3401|341[0-689]|34062~330[1-5]|33100~21~30~28[89]~13[01]~37~89[89]~36~01",
    upper: "CS",
    zip: "\\d{5}",
    zipex: "00144,47037,39049",
  },
  "data/JE": {
    fmt: "%N%n%O%n%A%n%C%nJERSEY%n%Z",
    id: "data/JE",
    key: "JE",
    name: "CHANNEL ISLANDS",
    posturl: "http://www.jerseypost.com/tools/postcode-address-finder/",
    require: "ACZ",
    upper: "CZ",
    zip: "JE\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}",
    zipex: "JE1 1AA,JE2 2BT",
  },
  "data/JM": {
    fmt: "%N%n%O%n%A%n%C%n%S %X",
    id: "data/JM",
    key: "JM",
    lang: "en",
    languages: "en",
    name: "JAMAICA",
    require: "ACS",
    state_name_type: "parish",
    sub_isoids: "13~09~01~12~04~02~06~14~11~08~05~03~07~10",
    sub_keys:
      "Clarendon~Hanover~Kingston~Manchester~Portland~St. Andrew~St. Ann~St. Catherine~St. Elizabeth~St. James~St. Mary~St. Thomas~Trelawny~Westmoreland",
  },
  "data/JO": {
    fmt: "%N%n%O%n%A%n%C %Z",
    id: "data/JO",
    key: "JO",
    name: "JORDAN",
    zip: "\\d{5}",
    zipex: "11937,11190",
  },
  "data/JP": {
    fmt: "〒%Z%n%S%n%A%n%O%n%N",
    id: "data/JP",
    key: "JP",
    lang: "ja",
    languages: "ja",
    lfmt: "%N%n%O%n%A, %S%n%Z",
    name: "JAPAN",
    posturl: "http://www.post.japanpost.jp/zipcode/",
    require: "ASZ",
    state_name_type: "prefecture",
    sub_isoids:
      "01~02~03~04~05~06~07~08~09~10~11~12~13~14~15~16~17~18~19~20~21~22~23~24~25~26~27~28~29~30~31~32~33~34~35~36~37~38~39~40~41~42~43~44~45~46~47",
    sub_keys:
      "北海道~青森県~岩手県~宮城県~秋田県~山形県~福島県~茨城県~栃木県~群馬県~埼玉県~千葉県~東京都~神奈川県~新潟県~富山県~石川県~福井県~山梨県~長野県~岐阜県~静岡県~愛知県~三重県~滋賀県~京都府~大阪府~兵庫県~奈良県~和歌山県~鳥取県~島根県~岡山県~広島県~山口県~徳島県~香川県~愛媛県~高知県~福岡県~佐賀県~長崎県~熊本県~大分県~宮崎県~鹿児島県~沖縄県",
    sub_lnames:
      "Hokkaido~Aomori~Iwate~Miyagi~Akita~Yamagata~Fukushima~Ibaraki~Tochigi~Gunma~Saitama~Chiba~Tokyo~Kanagawa~Niigata~Toyama~Ishikawa~Fukui~Yamanashi~Nagano~Gifu~Shizuoka~Aichi~Mie~Shiga~Kyoto~Osaka~Hyogo~Nara~Wakayama~Tottori~Shimane~Okayama~Hiroshima~Yamaguchi~Tokushima~Kagawa~Ehime~Kochi~Fukuoka~Saga~Nagasaki~Kumamoto~Oita~Miyazaki~Kagoshima~Okinawa",
    sub_zips:
      "0[4-9]|00[1-7]~03|018~02~98~01~99~9[67]~3[01]~32|311|349~37|38[49]~3[3-6]~2[6-9]~1[0-8]|19[0-8]|20~2[1-5]|199~9[45]|389~93~92|939~91|922~40~3[89]|949~50~4[1-9]~4[4-9]|431~51|498|647~52~6[0-2]|520~5[3-9]|618|630~6[5-7]|563~63|64[78]~64|519~68~69|68[45]~7[01]~7[23]~7[45]~77~76~79~78~8[0-3]|871~84~85|81[17]|848~86~87|839~88~89~90",
    upper: "S",
    zip: "\\d{3}-?\\d{4}",
    zipex: "154-0023,350-1106,951-8073,112-0001,208-0032,231-0012",
  },
  "data/KE": {
    fmt: "%N%n%O%n%A%n%C%n%Z",
    id: "data/KE",
    key: "KE",
    name: "KENYA",
    zip: "\\d{5}",
    zipex: "20100,00100",
  },
  "data/KG": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/KG",
    key: "KG",
    name: "KYRGYZSTAN",
    zip: "\\d{6}",
    zipex: "720001",
  },
  "data/KH": {
    fmt: "%N%n%O%n%A%n%C %Z",
    id: "data/KH",
    key: "KH",
    name: "CAMBODIA",
    zip: "\\d{5}",
    zipex: "12203,14206,12000",
  },
  "data/KI": {
    fmt: "%N%n%O%n%A%n%S%n%C",
    id: "data/KI",
    key: "KI",
    name: "KIRIBATI",
    state_name_type: "island",
    upper: "ACNOS",
  },
  "data/KM": { id: "data/KM", key: "KM", name: "COMOROS", upper: "AC" },
  "data/KN": {
    fmt: "%N%n%O%n%A%n%C, %S",
    id: "data/KN",
    key: "KN",
    lang: "en",
    languages: "en",
    name: "SAINT KITTS AND NEVIS",
    require: "ACS",
    state_name_type: "island",
    sub_isoids: "N~K",
    sub_keys: "Nevis~St. Kitts",
  },
  "data/KP": {
    fmt: "%Z%n%S%n%C%n%A%n%O%n%N",
    id: "data/KP",
    key: "KP",
    lang: "ko",
    languages: "ko",
    lfmt: "%N%n%O%n%A%n%C%n%S, %Z",
    name: "NORTH KOREA",
    sub_isoids: "07~13~10~04~02~03~01~08~09~05~06",
    sub_keys:
      "강원도~라선 특별시~량강도~자강도~평안 남도~평안 북도~평양 직할시~함경 남도~함경 북도~황해남도~황해북도",
    sub_lnames:
      "Kangwon~Rason~Ryanggang~Chagang~South Pyongan~North Pyongan~Pyongyang~South Hamgyong~North Hamgyong~South Hwanghae~North Hwanghae",
  },
  "data/KR": {
    fmt: "%S %C%D%n%A%n%O%n%N%n%Z",
    id: "data/KR",
    key: "KR",
    lang: "ko",
    languages: "ko",
    lfmt: "%N%n%O%n%A%n%D%n%C%n%S%n%Z",
    name: "SOUTH KOREA",
    posturl: "http://www.epost.go.kr/search/zipcode/search5.jsp",
    require: "ACSZ",
    state_name_type: "do_si",
    sub_isoids: "42~41~48~47~29~27~30~26~11~50~31~28~46~45~49~44~43",
    sub_keys:
      "강원도~경기도~경상남도~경상북도~광주광역시~대구광역시~대전광역시~부산광역시~서울특별시~세종특별자치시~울산광역시~인천광역시~전라남도~전라북도~제주특별자치도~충청남도~충청북도",
    sub_lnames:
      "Gangwon-do~Gyeonggi-do~Gyeongsangnam-do~Gyeongsangbuk-do~Gwangju~Daegu~Daejeon~Busan~Seoul~Sejong~Ulsan~Incheon~Jeollanam-do~Jeollabuk-do~Jeju-do~Chungcheongnam-do~Chungcheongbuk-do",
    sub_mores:
      "true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true",
    sub_names:
      "강원~경기~경남~경북~광주~대구~대전~부산~서울~세종~울산~인천~전남~전북~제주~충남~충북",
    sub_zipexs:
      "25627~12410~53286~38540~62394~42456~34316~46706~06321~30065~44782~23024~59222~56445~63563~32832~28006",
    sub_zips:
      "2[456]\\d{2}~1[0-8]\\d{2}~5[0-3]\\d{2}~(?:3[6-9]|40)\\d{2}~6[12]\\d{2}~4[12]\\d{2}~3[45]\\d{2}~4[6-9]\\d{2}~0[1-8]\\d{2}~30[01]\\d~4[45]\\d{2}~2[1-3]\\d{2}~5[7-9]\\d{2}~5[4-6]\\d{2}~63[0-356]\\d~3[1-3]\\d{2}~2[789]\\d{2}",
    sublocality_name_type: "district",
    upper: "Z",
    zip: "\\d{5}",
    zipex: "03051",
  },
  "data/KW": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/KW",
    key: "KW",
    name: "KUWAIT",
    zip: "\\d{5}",
    zipex: "54541,54551,54404,13009",
  },
  "data/KY": {
    fmt: "%N%n%O%n%A%n%S %Z",
    id: "data/KY",
    key: "KY",
    lang: "en",
    languages: "en",
    name: "CAYMAN ISLANDS",
    posturl: "http://www.caymanpost.gov.ky/",
    require: "AS",
    state_name_type: "island",
    sub_keys: "Cayman Brac~Grand Cayman~Little Cayman",
    zip: "KY\\d-\\d{4}",
    zipex: "KY1-1100,KY1-1702,KY2-2101",
  },
  "data/KZ": {
    fmt: "%Z%n%S%n%C%n%A%n%O%n%N",
    id: "data/KZ",
    key: "KZ",
    name: "KAZAKHSTAN",
    zip: "\\d{6}",
    zipex: "040900,050012",
  },
  "data/LA": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/LA",
    key: "LA",
    name: "LAO (PEOPLE'S DEM. REP.)",
    zip: "\\d{5}",
    zipex: "01160,01000",
  },
  "data/LB": {
    fmt: "%N%n%O%n%A%n%C %Z",
    id: "data/LB",
    key: "LB",
    name: "LEBANON",
    zip: "(?:\\d{4})(?: ?(?:\\d{4}))?",
    zipex: "2038 3054,1107 2810,1000",
  },
  "data/LC": { id: "data/LC", key: "LC", name: "SAINT LUCIA" },
  "data/LI": {
    fmt: "%O%n%N%n%A%nFL-%Z %C",
    id: "data/LI",
    key: "LI",
    name: "LIECHTENSTEIN",
    postprefix: "FL-",
    posturl: "http://www.post.ch/db/owa/pv_plz_pack/pr_main",
    require: "ACZ",
    zip: "948[5-9]|949[0-8]",
    zipex: "9496,9491,9490,9485",
  },
  "data/LK": {
    fmt: "%N%n%O%n%A%n%C%n%Z",
    id: "data/LK",
    key: "LK",
    name: "SRI LANKA",
    posturl: "http://www.slpost.gov.lk/",
    zip: "\\d{5}",
    zipex: "20000,00100",
  },
  "data/LR": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/LR",
    key: "LR",
    name: "LIBERIA",
    zip: "\\d{4}",
    zipex: "1000",
  },
  "data/LS": {
    fmt: "%N%n%O%n%A%n%C %Z",
    id: "data/LS",
    key: "LS",
    name: "LESOTHO",
    zip: "\\d{3}",
    zipex: "100",
  },
  "data/LT": {
    fmt: "%O%n%N%n%A%nLT-%Z %C",
    id: "data/LT",
    key: "LT",
    name: "LITHUANIA",
    postprefix: "LT-",
    posturl: "http://www.post.lt/lt/?id=316",
    zip: "\\d{5}",
    zipex: "04340,03500",
  },
  "data/LU": {
    fmt: "%O%n%N%n%A%nL-%Z %C",
    id: "data/LU",
    key: "LU",
    name: "LUXEMBOURG",
    postprefix: "L-",
    posturl:
      "https://www.post.lu/fr/grandes-entreprises/solutions-postales/rechercher-un-code-postal",
    require: "ACZ",
    zip: "\\d{4}",
    zipex: "4750,2998",
  },
  "data/LV": {
    fmt: "%N%n%O%n%A%n%C, %Z",
    id: "data/LV",
    key: "LV",
    name: "LATVIA",
    posturl: "http://www.pasts.lv/lv/uzzinas/nodalas/",
    zip: "LV-\\d{4}",
    zipex: "LV-1073,LV-1000",
  },
  "data/LY": { id: "data/LY", key: "LY", name: "LIBYA" },
  "data/MA": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/MA",
    key: "MA",
    name: "MOROCCO",
    zip: "\\d{5}",
    zipex: "53000,10000,20050,16052",
  },
  "data/MC": {
    fmt: "%N%n%O%n%A%nMC-%Z %C %X",
    id: "data/MC",
    key: "MC",
    name: "MONACO",
    postprefix: "MC-",
    zip: "980\\d{2}",
    zipex: "98000,98020,98011,98001",
  },
  "data/MD": {
    fmt: "%N%n%O%n%A%nMD-%Z %C",
    id: "data/MD",
    key: "MD",
    name: "Rep. MOLDOVA",
    postprefix: "MD-",
    zip: "\\d{4}",
    zipex: "2012,2019",
  },
  "data/ME": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/ME",
    key: "ME",
    name: "MONTENEGRO",
    zip: "8\\d{4}",
    zipex: "81257,81258,81217,84314,85366",
  },
  "data/MF": {
    fmt: "%O%n%N%n%A%n%Z %C %X",
    id: "data/MF",
    key: "MF",
    name: "SAINT MARTIN",
    posturl:
      "http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal",
    require: "ACZ",
    upper: "ACX",
    zip: "9[78][01]\\d{2}",
    zipex: "97100",
  },
  "data/MG": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/MG",
    key: "MG",
    name: "MADAGASCAR",
    zip: "\\d{3}",
    zipex: "501,101",
  },
  "data/MH": {
    fmt: "%N%n%O%n%A%n%C %S %Z",
    id: "data/MH",
    key: "MH",
    name: "MARSHALL ISLANDS",
    posturl: "http://zip4.usps.com/zip4/welcome.jsp",
    require: "ACSZ",
    state_name_type: "state",
    upper: "ACNOS",
    zip: "(969[67]\\d)(?:[ \\-](\\d{4}))?",
    zip_name_type: "zip",
    zipex: "96960,96970",
  },
  "data/MK": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/MK",
    key: "MK",
    name: "MACEDONIA",
    zip: "\\d{4}",
    zipex: "1314,1321,1443,1062",
  },
  "data/ML": { id: "data/ML", key: "ML", name: "MALI" },
  "data/MM": {
    fmt: "%N%n%O%n%A%n%C, %Z",
    id: "data/MM",
    key: "MM",
    name: "MYANMAR",
    zip: "\\d{5}",
    zipex: "11181",
  },
  "data/MN": {
    fmt: "%N%n%O%n%A%n%C%n%S %Z",
    id: "data/MN",
    key: "MN",
    name: "MONGOLIA",
    posturl: "http://www.zipcode.mn/",
    zip: "\\d{5}",
    zipex: "65030,65270",
  },
  "data/MO": {
    fmt: "%A%n%O%n%N",
    id: "data/MO",
    key: "MO",
    lfmt: "%N%n%O%n%A",
    name: "MACAO",
    require: "A",
  },
  "data/MP": {
    fmt: "%N%n%O%n%A%n%C %S %Z",
    id: "data/MP",
    key: "MP",
    name: "NORTHERN MARIANA ISLANDS",
    posturl: "http://zip4.usps.com/zip4/welcome.jsp",
    require: "ACSZ",
    state_name_type: "state",
    upper: "ACNOS",
    zip: "(9695[012])(?:[ \\-](\\d{4}))?",
    zip_name_type: "zip",
    zipex: "96950,96951,96952",
  },
  "data/MQ": {
    fmt: "%O%n%N%n%A%n%Z %C %X",
    id: "data/MQ",
    key: "MQ",
    name: "MARTINIQUE",
    posturl:
      "http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal",
    require: "ACZ",
    upper: "ACX",
    zip: "9[78]2\\d{2}",
    zipex: "97220",
  },
  "data/MR": { id: "data/MR", key: "MR", name: "MAURITANIA", upper: "AC" },
  "data/MS": { id: "data/MS", key: "MS", name: "MONTSERRAT" },
  "data/MT": {
    fmt: "%N%n%O%n%A%n%C %Z",
    id: "data/MT",
    key: "MT",
    name: "MALTA",
    posturl: "http://postcodes.maltapost.com/",
    upper: "CZ",
    zip: "[A-Z]{3} ?\\d{2,4}",
    zipex: "NXR 01,ZTN 05,GPO 01,BZN 1130,SPB 6031,VCT 1753",
  },
  "data/MU": {
    fmt: "%N%n%O%n%A%n%Z%n%C",
    id: "data/MU",
    key: "MU",
    name: "MAURITIUS",
    upper: "CZ",
    zip: "\\d{3}(?:\\d{2}|[A-Z]{2}\\d{3})",
    zipex: "42602",
  },
  "data/MV": {
    fmt: "%N%n%O%n%A%n%C %Z",
    id: "data/MV",
    key: "MV",
    name: "MALDIVES",
    posturl: "http://www.maldivespost.com/?lid=10",
    zip: "\\d{5}",
    zipex: "20026",
  },
  "data/MW": {
    fmt: "%N%n%O%n%A%n%C %X",
    id: "data/MW",
    key: "MW",
    name: "MALAWI",
  },
  "data/MX": {
    fmt: "%N%n%O%n%A%n%D%n%Z %C, %S",
    id: "data/MX",
    key: "MX",
    lang: "es",
    languages: "es",
    name: "MEXICO",
    posturl:
      "http://www.correosdemexico.gob.mx/ServiciosLinea/Paginas/ccpostales.aspx",
    require: "ACZ",
    state_name_type: "state",
    sub_isoids:
      "AGU~BCN~BCS~CAM~CHP~CHH~CMX~COA~COL~DUR~MEX~GUA~GRO~HID~JAL~MIC~MOR~NAY~NLE~OAX~PUE~QUE~ROO~SLP~SIN~SON~TAB~TAM~TLA~VER~YUC~ZAC",
    sub_keys:
      "Ags.~B.C.~B.C.S.~Camp.~Chis.~Chih.~CDMX~Coah.~Col.~Dgo.~Méx.~Gto.~Gro.~Hgo.~Jal.~Mich.~Mor.~Nay.~N.L.~Oax.~Pue.~Qro.~Q.R.~S.L.P.~Sin.~Son.~Tab.~Tamps.~Tlax.~Ver.~Yuc.~Zac.",
    sub_names:
      "Aguascalientes~Baja California~Baja California Sur~Campeche~Chiapas~Chihuahua~Ciudad de México~Coahuila de Zaragoza~Colima~Durango~Estado de México~Guanajuato~Guerrero~Hidalgo~Jalisco~Michoacán~Morelos~Nayarit~Nuevo León~Oaxaca~Puebla~Querétaro~Quintana Roo~San Luis Potosí~Sinaloa~Sonora~Tabasco~Tamaulipas~Tlaxcala~Veracruz~Yucatán~Zacatecas",
    sub_zipexs:
      "20000,20999~21000,22999~23000,23999~24000,24999~29000,30999~31000,33999~00000,16999~25000,27999~28000,28999~34000,35999~50000,57999~36000,38999~39000,41999~42000,43999~44000,49999~58000,61999~62000,62999~63000,63999~64000,67999~68000,71999~72000,75999~76000,76999~77000,77999~78000,79999~80000,82999~83000,85999~86000,86999~87000,89999~90000,90999~91000,96999~97000,97999~98000,99999",
    sub_zips:
      "20~2[12]~23~24~29|30~3[1-3]~0|1[0-6]~2[5-7]~28~3[45]~5[0-7]~3[6-8]~39|4[01]~4[23]~4[4-9]~5[89]|6[01]~62~63~6[4-7]~6[89]|7[01]~7[2-5]~76~77~7[89]~8[0-2]~8[3-5]~86~8[7-9]~90~9[1-6]~97~9[89]",
    sublocality_name_type: "neighborhood",
    upper: "CSZ",
    zip: "\\d{5}",
    zipex: "02860,77520,06082",
  },
  "data/MY": {
    fmt: "%N%n%O%n%A%n%D%n%Z %C%n%S",
    id: "data/MY",
    key: "MY",
    lang: "ms",
    languages: "ms",
    name: "MALAYSIA",
    posturl: "http://www.pos.com.my",
    require: "ACZ",
    state_name_type: "state",
    sub_isoids: "01~02~03~14~15~04~05~06~08~09~07~16~12~13~10~11",
    sub_keys:
      "Johor~Kedah~Kelantan~Kuala Lumpur~Labuan~Melaka~Negeri Sembilan~Pahang~Perak~Perlis~Pulau Pinang~Putrajaya~Sabah~Sarawak~Selangor~Terengganu",
    sub_zipexs:
      "79000,86999~05000,09999,34950~15000,18599~50000,60000~87000,87999~75000,78399~70000,73599~25000,28999,39000,49000,69000~30000,36899,39000~01000,02799~10000,14999~62000,62999~88000,91999~93000,98999~40000,48999,63000,68199~20000,24999",
    sub_zips:
      "79|8[0-6]~0[5-9]|34950~1[5-9]~5|60~87~7[5-8]~7[0-4]~2[5-8]|[346]9~3[0-6]|39000~0[12]~1[0-4]~62~8[89]|9[01]~9[3-8]~4[0-8]|6[3-8]~2[0-4]",
    sublocality_name_type: "village_township",
    upper: "CS",
    zip: "\\d{5}",
    zipex: "43000,50754,88990,50670",
  },
  "data/MZ": {
    fmt: "%N%n%O%n%A%n%Z %C%S",
    id: "data/MZ",
    key: "MZ",
    lang: "pt",
    languages: "pt",
    name: "MOZAMBIQUE",
    sub_isoids: "P~MPM~G~I~B~L~N~A~S~T~Q",
    sub_keys:
      "Cabo Delgado~Cidade de Maputo~Gaza~Inhambane~Manica~Maputo~Nampula~Niassa~Sofala~Tete~Zambezia",
    zip: "\\d{4}",
    zipex: "1102,1119,3212",
  },
  "data/NA": { id: "data/NA", key: "NA", name: "NAMIBIA" },
  "data/NC": {
    fmt: "%O%n%N%n%A%n%Z %C %X",
    id: "data/NC",
    key: "NC",
    name: "NEW CALEDONIA",
    posturl:
      "http://poste.opt.nc/index.php?option=com_content&view=article&id=80&Itemid=131",
    require: "ACZ",
    upper: "ACX",
    zip: "988\\d{2}",
    zipex: "98814,98800,98810",
  },
  "data/NE": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/NE",
    key: "NE",
    name: "NIGER",
    zip: "\\d{4}",
    zipex: "8001",
  },
  "data/NF": {
    fmt: "%O%n%N%n%A%n%C %S %Z",
    id: "data/NF",
    key: "NF",
    name: "NORFOLK ISLAND",
    upper: "CS",
    zip: "2899",
    zipex: "2899",
  },
  "data/NG": {
    fmt: "%N%n%O%n%A%n%D%n%C %Z%n%S",
    id: "data/NG",
    key: "NG",
    lang: "en",
    languages: "en",
    name: "NIGERIA",
    posturl: "http://www.nigeriapostcodes.com/",
    state_name_type: "state",
    sub_isoids:
      "AB~AD~AK~AN~BA~BY~BE~BO~CR~DE~EB~ED~EK~EN~FC~GO~IM~JI~KD~KN~KT~KE~KO~KW~LA~NA~NI~OG~ON~OS~OY~PL~RI~SO~TA~YO~ZA",
    sub_keys:
      "Abia~Adamawa~Akwa Ibom~Anambra~Bauchi~Bayelsa~Benue~Borno~Cross River~Delta~Ebonyi~Edo~Ekiti~Enugu~Federal Capital Territory~Gombe~Imo~Jigawa~Kaduna~Kano~Katsina~Kebbi~Kogi~Kwara~Lagos~Nasarawa~Niger~Ogun State~Ondo~Osun~Oyo~Plateau~Rivers~Sokoto~Taraba~Yobe~Zamfara",
    upper: "CS",
    zip: "\\d{6}",
    zipex: "930283,300001,931104",
  },
  "data/NI": {
    fmt: "%N%n%O%n%A%n%Z%n%C, %S",
    id: "data/NI",
    key: "NI",
    lang: "es",
    languages: "es",
    name: "NICARAGUA",
    posturl: "http://www.correos.gob.ni/index.php/codigo-postal-2",
    state_name_type: "department",
    sub_isoids: "BO~CA~CI~CO~ES~GR~JI~LE~MD~MN~MS~MT~NS~AN~AS~SJ~RI",
    sub_keys:
      "Boaco~Carazo~Chinandega~Chontales~Esteli~Granada~Jinotega~Leon~Madriz~Managua~Masaya~Matagalpa~Nueva Segovia~Raan~Raas~Rio San Juan~Rivas",
    sub_zips:
      "5[12]~4[56]~2[5-7]~5[56]~3[12]~4[34]~6[56]~2[12]~3[45]~1[0-6]~4[12]~6[1-3]~3[7-9]~7[12]~8[1-3]~9[12]~4[78]",
    upper: "CS",
    zip: "\\d{5}",
    zipex: "52000",
  },
  "data/NL": {
    fmt: "%O%n%N%n%A%n%Z %C",
    id: "data/NL",
    key: "NL",
    name: "NETHERLANDS",
    posturl: "http://www.postnl.nl/voorthuis/",
    require: "ACZ",
    zip: "\\d{4} ?[A-Z]{2}",
    zipex: "1234 AB,2490 AA",
  },
  "data/NO": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/NO",
    key: "NO",
    locality_name_type: "post_town",
    name: "NORWAY",
    posturl: "http://adressesok.posten.no/nb/postal_codes/search",
    require: "ACZ",
    zip: "\\d{4}",
    zipex: "0025,0107,6631",
  },
  "data/NP": {
    fmt: "%N%n%O%n%A%n%C %Z",
    id: "data/NP",
    key: "NP",
    name: "NEPAL",
    posturl: "http://www.gpo.gov.np/Home/Postalcode",
    zip: "\\d{5}",
    zipex: "44601",
  },
  "data/NR": {
    fmt: "%N%n%O%n%A%n%S",
    id: "data/NR",
    key: "NR",
    lang: "en",
    languages: "en",
    name: "NAURU CENTRAL PACIFIC",
    require: "AS",
    state_name_type: "district",
    sub_isoids: "01~02~03~04~05~06~07~08~09~10~11~12~13~14",
    sub_keys:
      "Aiwo District~Anabar District~Anetan District~Anibare District~Baiti District~Boe District~Buada District~Denigomodu District~Ewa District~Ijuw District~Meneng District~Nibok District~Uaboe District~Yaren District",
  },
  "data/NU": { id: "data/NU", key: "NU", name: "NIUE" },
  "data/NZ": {
    fmt: "%N%n%O%n%A%n%D%n%C %Z",
    id: "data/NZ",
    key: "NZ",
    name: "NEW ZEALAND",
    posturl:
      "http://www.nzpost.co.nz/Cultures/en-NZ/OnlineTools/PostCodeFinder/",
    require: "ACZ",
    zip: "\\d{4}",
    zipex: "6001,6015,6332,8252,1030",
  },
  "data/OM": {
    fmt: "%N%n%O%n%A%n%Z%n%C",
    id: "data/OM",
    key: "OM",
    name: "OMAN",
    zip: "(?:PC )?\\d{3}",
    zipex: "133,112,111",
  },
  "data/PA": {
    fmt: "%N%n%O%n%A%n%C%n%S",
    id: "data/PA",
    key: "PA",
    name: "PANAMA (REP.)",
    upper: "CS",
  },
  "data/PE": {
    fmt: "%N%n%O%n%A%n%C %Z%n%S",
    id: "data/PE",
    key: "PE",
    lang: "es",
    languages: "es",
    locality_name_type: "district",
    name: "PERU",
    posturl: "http://www.serpost.com.pe/cpostal/codigo",
    sub_isoids:
      "AMA~ANC~APU~ARE~AYA~CAJ~CAL~CUS~LIM~HUV~HUC~ICA~JUN~LAL~LAM~LOR~MDD~MOQ~LMA~PAS~PIU~PUN~SAM~TAC~TUM~UCA",
    sub_keys:
      "Amazonas~Áncash~Apurímac~Arequipa~Ayacucho~Cajamarca~Callao~Cuzco~Gobierno Regional de Lima~Huancavelica~Huánuco~Ica~Junín~La Libertad~Lambayeque~Loreto~Madre de Dios~Moquegua~Municipalidad Metropolitana de Lima~Pasco~Piura~Puno~San Martín~Tacna~Tumbes~Ucayali",
    zip: "(?:LIMA \\d{1,2}|CALLAO 0?\\d)|[0-2]\\d{4}",
    zipex: "LIMA 23,LIMA 42,CALLAO 2,02001",
  },
  "data/PF": {
    fmt: "%N%n%O%n%A%n%Z %C %S",
    id: "data/PF",
    key: "PF",
    name: "FRENCH POLYNESIA",
    require: "ACSZ",
    state_name_type: "island",
    upper: "CS",
    zip: "987\\d{2}",
    zipex: "98709",
  },
  "data/PG": {
    fmt: "%N%n%O%n%A%n%C %Z %S",
    id: "data/PG",
    key: "PG",
    name: "PAPUA NEW GUINEA",
    require: "ACS",
    zip: "\\d{3}",
    zipex: "111",
  },
  "data/PH": {
    fmt: "%N%n%O%n%A%n%D, %C%n%Z %S",
    id: "data/PH",
    key: "PH",
    lang: "en",
    languages: "en",
    name: "PHILIPPINES",
    posturl: "http://www.philpost.gov.ph/",
    sub_isoids:
      "ABR~AGN~AGS~AKL~ALB~ANT~APA~AUR~BAS~BAN~BTN~BTG~BEN~BIL~BOH~BUK~BUL~CAG~CAN~CAS~CAM~CAP~CAT~CAV~CEB~COM~NCO~DAV~DAS~DVO~DAO~DIN~EAS~GUI~IFU~ILN~ILS~ILI~ISA~KAL~LUN~LAG~LAN~LAS~LEY~MAG~MAD~MAS~00~MDC~MDR~MSC~MSR~MOU~NEC~NER~NSA~NUE~NUV~PLW~PAM~PAN~QUE~QUI~RIZ~ROM~WSA~SAR~SIG~SOR~SCO~SLE~SUK~SLU~SUN~SUR~TAR~TAW~ZMB~ZAN~ZAS~ZSI",
    sub_keys:
      "Abra~Agusan del Norte~Agusan del Sur~Aklan~Albay~Antique~Apayao~Aurora~Basilan~Bataan~Batanes~Batangas~Benguet~Biliran~Bohol~Bukidnon~Bulacan~Cagayan~Camarines Norte~Camarines Sur~Camiguin~Capiz~Catanduanes~Cavite~Cebu~Compostela Valley~Cotabato~Davao del Norte~Davao del Sur~Davao Occidental~Davao Oriental~Dinagat Islands~Eastern Samar~Guimaras~Ifugao~Ilocos Norte~Ilocos Sur~Iloilo~Isabela~Kalinga~La Union~Laguna~Lanao del Norte~Lanao del Sur~Leyte~Maguindanao~Marinduque~Masbate~Metro Manila~Mindoro Occidental~Mindoro Oriental~Misamis Occidental~Misamis Oriental~Mountain Province~Negros Occidental~Negros Oriental~Northern Samar~Nueva Ecija~Nueva Vizcaya~Palawan~Pampanga~Pangasinan~Quezon Province~Quirino~Rizal~Romblon~Samar~Sarangani~Siquijor~Sorsogon~South Cotabato~Southern Leyte~Sultan Kudarat~Sulu~Surigao del Norte~Surigao del Sur~Tarlac~Tawi-Tawi~Zambales~Zamboanga del Norte~Zamboanga del Sur~Zamboanga Sibuguey",
    sub_zipexs:
      "2800,2826~8600,8611~8500,8513~5600,5616~4500,4517~5700,5717~3800,3806,3808~3200,3207~7300,7306~2100,2114~3900,3905~4200,4234~2600,2615~6543,6550~6300,6337~8700,8723~3000,3024~3500,3528~4600,4612~4400,4436~9100,9104~5800,5816~4800,4810~4100,4126~6000,6053~8800,8810~9400,9417~8100,8120~8000,8010~8015,8013~8200,8210~8426,8412~6800,6822~5044,5046~3600,3610~2900,2922~2700,2733~5000,5043~3300,3336~3807,3809,3814~2500,2520~4000,4033~9200,9223~9300,9321,9700,9716~6500,6542~9600,9619~4900,4905~5400,5421~~5100,5111~5200,5214~7200,7215~9000,9025~2616,2625~6100,6132~6200,6224~6400,6423~3100,3133~3700,3714~5300,5322~2000,2022~2400,2447~4300,4342~3400,3405~1850,1990~5500,5516~6700,6725~8015~6225,6230~4700,4715~9500,9513~6600,6613~9800,9811~7400,7416~8400,8425~8300,8319~2300,2318~7500,7509~2200,2213~7100,7124~7000,7043~7000,7043",
    sub_zips:
      "28[0-2]~86[01]~85[01]~56[01]~45[01]~57[01]~380[0-68]~320~730~21[01]~390~42[0-3]~26(0|1[0-5])~65(4[3-9]|5)~63[0-3]~87[0-2]~30[0-2]~35[0-2]~46[01]~44[0-3]~910~58[01]~48[01]~41[0-2]~60[0-5]~88[01]~94[01]~81[0-2]~80[01]~801[1-5]~82[01]~84[12]~68[0-2]~504[4-6]~36[01]~29[0-2]~27[0-3]~50([0-3]|4[0-3])~33[0-3]~38(0[79]|1[0-4])~25[0-2]~40[0-3]~92[0-2]~9(3[0-2]|7[01])~65([0-3]|4[0-2])~96[01]~490~54[0-2]~~51[01]~52[01]~72[01]~90[0-2]~26(1[6-9]|2[0-5])~61[0-3]~62[0-2]~64[0-2]~31[0-3]~37[01]~53[0-2]~20[0-2]~24[0-4]~43[0-4]~340~1[89]~55[01]~67[0-2]~8015~62(2[5-9]|30)~47[01]~95[01]~66[10]~98[01]~74[01]~84[0-2]~83[01]~23[01]~750~22[01]~71[0-2]~70[0-4]~70[0-4]",
    zip: "\\d{4}",
    zipex: "1008,1050,1135,1207,2000,1000",
  },
  "data/PK": {
    fmt: "%N%n%O%n%A%n%C-%Z",
    id: "data/PK",
    key: "PK",
    name: "PAKISTAN",
    posturl: "http://www.pakpost.gov.pk/postcode.php",
    zip: "\\d{5}",
    zipex: "44000",
  },
  "data/PL": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/PL",
    key: "PL",
    name: "POLAND",
    posturl: "http://kody.poczta-polska.pl/",
    require: "ACZ",
    zip: "\\d{2}-\\d{3}",
    zipex: "00-950,05-470,48-300,32-015,00-940",
  },
  "data/PM": {
    fmt: "%O%n%N%n%A%n%Z %C %X",
    id: "data/PM",
    key: "PM",
    name: "ST. PIERRE AND MIQUELON",
    require: "ACZ",
    upper: "ACX",
    zip: "9[78]5\\d{2}",
    zipex: "97500",
  },
  "data/PN": {
    fmt: "%N%n%O%n%A%n%C%n%Z",
    id: "data/PN",
    key: "PN",
    name: "PITCAIRN",
    require: "ACZ",
    upper: "CZ",
    zip: "PCRN 1ZZ",
    zipex: "PCRN 1ZZ",
  },
  "data/PR": {
    fmt: "%N%n%O%n%A%n%C PR %Z",
    id: "data/PR",
    key: "PR",
    name: "PUERTO RICO",
    postprefix: "PR ",
    posturl: "http://zip4.usps.com/zip4/welcome.jsp",
    require: "ACZ",
    upper: "ACNO",
    zip: "(00[679]\\d{2})(?:[ \\-](\\d{4}))?",
    zip_name_type: "zip",
    zipex: "00930",
  },
  "data/PT": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/PT",
    key: "PT",
    name: "PORTUGAL",
    posturl: "http://www.ctt.pt/feapl_2/app/open/tools.jspx?tool=1",
    require: "ACZ",
    zip: "\\d{4}-\\d{3}",
    zipex: "2725-079,1250-096,1201-950,2860-571,1208-148",
  },
  "data/PW": {
    fmt: "%N%n%O%n%A%n%C %S %Z",
    id: "data/PW",
    key: "PW",
    name: "PALAU",
    posturl: "http://zip4.usps.com/zip4/welcome.jsp",
    require: "ACSZ",
    state_name_type: "state",
    upper: "ACNOS",
    zip: "(969(?:39|40))(?:[ \\-](\\d{4}))?",
    zip_name_type: "zip",
    zipex: "96940",
  },
  "data/PY": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/PY",
    key: "PY",
    name: "PARAGUAY",
    zip: "\\d{4}",
    zipex: "1536,1538,1209",
  },
  "data/QA": { id: "data/QA", key: "QA", name: "QATAR", upper: "AC" },
  "data/RE": {
    fmt: "%O%n%N%n%A%n%Z %C %X",
    id: "data/RE",
    key: "RE",
    name: "REUNION",
    posturl:
      "http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal",
    require: "ACZ",
    upper: "ACX",
    zip: "9[78]4\\d{2}",
    zipex: "97400",
  },
  "data/RO": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/RO",
    key: "RO",
    name: "ROMANIA",
    posturl: "http://www.posta-romana.ro/zip_codes",
    upper: "AC",
    zip: "\\d{6}",
    zipex: "060274,061357,200716",
  },
  "data/RS": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/RS",
    key: "RS",
    name: "REPUBLIC OF SERBIA",
    posturl:
      "http://www.posta.rs/struktura/lat/aplikacije/pronadji/nadji-postu.asp",
    zip: "\\d{5,6}",
    zipex: "106314",
  },
  "data/RU": {
    fmt: "%N%n%O%n%A%n%C%n%S%n%Z",
    id: "data/RU",
    key: "RU",
    lang: "ru",
    languages: "ru",
    lfmt: "%N%n%O%n%A%n%C%n%S%n%Z",
    name: "RUSSIAN FEDERATION",
    posturl: "http://info.russianpost.ru/servlet/department",
    require: "ACSZ",
    state_name_type: "oblast",
    sub_isoids:
      "ALT~AMU~ARK~AST~BEL~BRY~VLA~VGG~VLG~VOR~YEV~ZAB~IVA~IRK~KB~KGD~KLU~KAM~KC~KEM~KIR~KOS~KDA~KYA~KGN~KRS~LEN~LIP~MAG~MOW~MOS~MUR~NEN~NIZ~NGR~NVS~OMS~ORE~ORL~PNZ~PER~PRI~PSK~AD~AL~BA~BU~DA~IN~KL~KR~KO~~ME~MO~SA~SE~TA~TY~UD~KK~ROS~RYA~SAM~SPE~SAR~SAK~SVE~~SMO~STA~TAM~TVE~TOM~TUL~TYU~ULY~KHA~KHM~CHE~CE~CU~CHU~YAN~YAR",
    sub_keys:
      "Алтайский край~Амурская область~Архангельская область~Астраханская область~Белгородская область~Брянская область~Владимирская область~Волгоградская область~Вологодская область~Воронежская область~Еврейская автономная область~Забайкальский край~Ивановская область~Иркутская область~Кабардино-Балкарская Республика~Калининградская область~Калужская область~Камчатский край~Карачаево-Черкесская Республика~Кемеровская область~Кировская область~Костромская область~Краснодарский край~Красноярский край~Курганская область~Курская область~Ленинградская область~Липецкая область~Магаданская область~Москва~Московская область~Мурманская область~Ненецкий автономный округ~Нижегородская область~Новгородская область~Новосибирская область~Омская область~Оренбургская область~Орловская область~Пензенская область~Пермский край~Приморский край~Псковская область~Республика Адыгея~Республика Алтай~Республика Башкортостан~Республика Бурятия~Республика Дагестан~Республика Ингушетия~Республика Калмыкия~Республика Карелия~Республика Коми~Автономна Республіка Крим~Республика Марий Эл~Республика Мордовия~Республика Саха (Якутия)~Республика Северная Осетия-Алания~Республика Татарстан~Республика Тыва~Республика Удмуртия~Республика Хакасия~Ростовская область~Рязанская область~Самарская область~Санкт-Петербург~Саратовская область~Сахалинская область~Свердловская область~Севастополь~Смоленская область~Ставропольский край~Тамбовская область~Тверская область~Томская область~Тульская область~Тюменская область~Ульяновская область~Хабаровский край~Ханты-Мансийский автономный округ~Челябинская область~Чеченская Республика~Чувашская Республика~Чукотский автономный округ~Ямало-Ненецкий автономный округ~Ярославская область",
    sub_lnames:
      "Altayskiy kray~Amurskaya oblast'~Arkhangelskaya oblast'~Astrakhanskaya oblast'~Belgorodskaya oblast'~Bryanskaya oblast'~Vladimirskaya oblast'~Volgogradskaya oblast'~Vologodskaya oblast'~Voronezhskaya oblast'~Evreyskaya avtonomnaya oblast'~Zabaykalskiy kray~Ivanovskaya oblast'~Irkutskaya oblast'~Kabardino-Balkarskaya Republits~Kaliningradskaya oblast'~Kaluzhskaya oblast'~Kamchatskiy kray~Karachaevo-Cherkesskaya Republits~Kemerovskaya oblast'~Kirovskaya oblast'~Kostromskaya oblast'~Krasnodarskiy kray~Krasnoyarskiy kray~Kurganskaya oblast'~Kurskaya oblast'~Leningradskaya oblast'~Lipetskaya oblast'~Magadanskaya oblast'~Moskva~Moskovskaya oblast'~Murmanskaya oblast'~Nenetskiy~Nizhegorodskaya oblast'~Novgorodskaya oblast'~Novosibirskaya oblast'~Omskaya oblast'~Orenburgskaya oblast'~Orlovskaya oblast'~Penzenskaya oblast'~Permskiy kray~Primorskiy kray~Pskovskaya oblast'~Respublika Adygeya~Altay Republits~Bashkortostan Republits~Buryatiya Republits~Dagestan Republits~Ingushetiya Republits~Respublika Kalmykiya~Kareliya Republits~Komi Republits~Respublika Krym~Respublika Mariy El~Respublika Mordoviya~Sakha (Yakutiya) Republits~Respublika Severnaya Osetiya-Alaniya~Respublika Tatarstan~Tyva Republits~Respublika Udmurtiya~Khakasiya Republits~Rostovskaya oblast'~Ryazanskaya oblast'~Samarskaya oblast'~Sankt-Peterburg~Saratovskaya oblast'~Sakhalinskaya oblast'~Sverdlovskaya oblast'~Sevastopol'~Smolenskaya oblast'~Stavropolskiy kray~Tambovskaya oblast'~Tverskaya oblast'~Tomskaya oblast'~Tulskaya oblast'~Tyumenskaya oblast'~Ulyanovskaya oblast'~Khabarovskiy kray~Khanty-Mansiyskiy avtonomnyy okrug~Chelyabinskaya oblast'~Chechenskaya Republits~Chuvashia~Chukotskiy~Yamalo-Nenetskiy~Yaroslavskaya oblast'",
    sub_names:
      "Алтайский край~Амурская область~Архангельская область~Астраханская область~Белгородская область~Брянская область~Владимирская область~Волгоградская область~Вологодская область~Воронежская область~Еврейская автономная область~Забайкальский край~Ивановская область~Иркутская область~Кабардино-Балкарская Республика~Калининградская область~Калужская область~Камчатский край~Карачаево-Черкесская Республика~Кемеровская область~Кировская область~Костромская область~Краснодарский край~Красноярский край~Курганская область~Курская область~Ленинградская область~Липецкая область~Магаданская область~Москва~Московская область~Мурманская область~Ненецкий автономный округ~Нижегородская область~Новгородская область~Новосибирская область~Омская область~Оренбургская область~Орловская область~Пензенская область~Пермский край~Приморский край~Псковская область~Республика Адыгея~Республика Алтай~Республика Башкортостан~Республика Бурятия~Республика Дагестан~Республика Ингушетия~Республика Калмыкия~Республика Карелия~Республика Коми~Республика Крым~Республика Марий Эл~Республика Мордовия~Республика Саха (Якутия)~Республика Северная Осетия-Алания~Республика Татарстан~Республика Тыва~Республика Удмуртия~Республика Хакасия~Ростовская область~Рязанская область~Самарская область~Санкт-Петербург~Саратовская область~Сахалинская область~Свердловская область~Севастополь~Смоленская область~Ставропольский край~Тамбовская область~Тверская область~Томская область~Тульская область~Тюменская область~Ульяновская область~Хабаровский край~Ханты-Мансийский автономный округ~Челябинская область~Чеченская Республика~Чувашская Республика~Чукотский автономный округ~Ямало-Ненецкий автономный округ~Ярославская область",
    sub_zips:
      "65[6-9]~67[56]~16[3-5]~41[4-6]~30[89]~24[1-3]~60[0-2]~40[0-4]~16[0-2]~39[4-7]~679~6(?:7[2-4]|87)~15[3-5]~66[4-9]~36[01]~23[6-8]~24[89]~68[348]~369~65[0-4]~61[0-3]~15[67]~35[0-4]~6(?:6[0-3]|4[78])~64[01]~30[5-7]~18[78]~39[89]~68[56]~1(?:0[1-9]|1|2|3[0-5]|4[0-4])~14[0-4]~18[34]~166~60[3-7]~17[3-5]~63[0-3]~64[4-6]~46[0-2]~30[23]~44[0-2]~61[4-9]~69[0-2]~18[0-2]~385~649~45[0-3]~67[01]~36[78]~386~35[89]~18[56]~16[7-9]~29[5-8]~42[45]~43[01]~67[78]~36[23]~42[0-3]~66[78]~42[67]~655~34[4-7]~39[01]~44[3-6]~19~41[0-3]~69[34]~62[0-4]~299~21[4-6]~35[5-7]~39[23]~17[0-2]~63[4-6]~30[01]~62[5-7]~43[23]~68[0-2]~628~45[4-7]~36[4-6]~42[89]~689~629~15[0-2]",
    upper: "AC",
    zip: "\\d{6}",
    zipex: "247112,103375,188300",
  },
  "data/RW": { id: "data/RW", key: "RW", name: "RWANDA", upper: "AC" },
  "data/SA": {
    fmt: "%N%n%O%n%A%n%C %Z",
    id: "data/SA",
    key: "SA",
    name: "SAUDI ARABIA",
    zip: "\\d{5}",
    zipex: "11564,11187,11142",
  },
  "data/SB": { id: "data/SB", key: "SB", name: "SOLOMON ISLANDS" },
  "data/SC": {
    fmt: "%N%n%O%n%A%n%C%n%S",
    id: "data/SC",
    key: "SC",
    name: "SEYCHELLES",
    state_name_type: "island",
    upper: "S",
  },
  "data/SD": {
    fmt: "%N%n%O%n%A%n%C%n%Z",
    id: "data/SD",
    key: "SD",
    locality_name_type: "district",
    name: "SUDAN",
    zip: "\\d{5}",
    zipex: "11042,11113",
  },
  "data/SE": {
    fmt: "%O%n%N%n%A%nSE-%Z %C",
    id: "data/SE",
    key: "SE",
    locality_name_type: "post_town",
    name: "SWEDEN",
    postprefix: "SE-",
    posturl:
      "http://www.posten.se/sv/Kundservice/Sidor/Sok-postnummer-resultat.aspx",
    require: "ACZ",
    zip: "\\d{3} ?\\d{2}",
    zipex: "11455,12345,10500",
  },
  "data/SG": {
    fmt: "%N%n%O%n%A%nSINGAPORE %Z",
    id: "data/SG",
    key: "SG",
    name: "REP. OF SINGAPORE",
    posturl: "https://www.singpost.com/find-postal-code",
    require: "AZ",
    zip: "\\d{6}",
    zipex: "546080,308125,408600",
  },
  "data/SH": {
    fmt: "%N%n%O%n%A%n%C%n%Z",
    id: "data/SH",
    key: "SH",
    name: "SAINT HELENA",
    require: "ACZ",
    upper: "CZ",
    zip: "(?:ASCN|STHL) 1ZZ",
    zipex: "STHL 1ZZ",
  },
  "data/SI": {
    fmt: "%N%n%O%n%A%nSI-%Z %C",
    id: "data/SI",
    key: "SI",
    name: "SLOVENIA",
    postprefix: "SI-",
    zip: "\\d{4}",
    zipex: "4000,1001,2500",
  },
  "data/SK": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/SK",
    key: "SK",
    name: "SLOVAKIA",
    posturl: "http://psc.posta.sk",
    require: "ACZ",
    zip: "\\d{3} ?\\d{2}",
    zipex: "010 01,023 14,972 48,921 01,975 99",
  },
  "data/SL": { id: "data/SL", key: "SL", name: "SIERRA LEONE" },
  "data/SM": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/SM",
    key: "SM",
    name: "SAN MARINO",
    posturl: "http://www.poste.it/online/cercacap/",
    require: "AZ",
    zip: "4789\\d",
    zipex: "47890,47891,47895,47899",
  },
  "data/SN": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/SN",
    key: "SN",
    name: "SENEGAL",
    zip: "\\d{5}",
    zipex: "12500,46024,16556,10000",
  },
  "data/SO": {
    fmt: "%N%n%O%n%A%n%C, %S %Z",
    id: "data/SO",
    key: "SO",
    lang: "so",
    languages: "so",
    name: "SOMALIA",
    require: "ACS",
    sub_isoids: "AW~BK~BN~BR~BY~GA~GE~HI~JD~JH~MU~NU~SA~SD~SH~SO~TO~WO",
    sub_keys: "AD~BK~BN~BR~BY~GG~GD~HR~JD~JH~MD~NG~SG~SD~SH~SL~TG~WG",
    sub_names:
      "Awdal~Bakool~Banaadir~Bari~Bay~Galguduud~Gedo~Hiiraan~Jubbada Dhexe~Jubbada Hoose~Mudug~Nugaal~Sanaag~Shabeellaha Dhexe~Shabeellaha Hoose~Sool~Togdheer~Woqooyi Galbeed",
    upper: "ACS",
    zip: "[A-Z]{2} ?\\d{5}",
    zipex: "JH 09010,AD 11010",
  },
  "data/SR": {
    fmt: "%N%n%O%n%A%n%C%n%S",
    id: "data/SR",
    key: "SR",
    lang: "nl",
    languages: "nl",
    name: "SURINAME",
    sub_isoids: "BR~CM~CR~MA~NI~PR~PM~SA~SI~WA",
    sub_keys:
      "Brokopondo~Commewijne~Coronie~Marowijne~Nickerie~Para~Paramaribo~Saramacca~Sipaliwini~Wanica",
    upper: "AS",
  },
  "data/SS": { id: "data/SS", key: "SS", name: "SOUTH SUDAN" },
  "data/ST": { id: "data/ST", key: "ST", name: "SAO TOME AND PRINCIPE" },
  "data/SV": {
    fmt: "%N%n%O%n%A%n%Z-%C%n%S",
    id: "data/SV",
    key: "SV",
    lang: "es",
    languages: "es",
    name: "EL SALVADOR",
    require: "ACS",
    sub_isoids: "AH~CA~CH~CU~LI~PA~UN~MO~SM~SS~SV~SA~SO~US",
    sub_keys:
      "Ahuachapan~Cabanas~Calatenango~Cuscatlan~La Libertad~La Paz~La Union~Morazan~San Miguel~San Salvador~San Vicente~Santa Ana~Sonsonate~Usulutan",
    sub_names:
      "Ahuachapán~Cabañas~Chalatenango~Cuscatlán~La Libertad~La Paz~La Unión~Morazán~San Miguel~San Salvador~San Vicente~Santa Ana~Sonsonate~Usulután",
    sub_zipexs:
      "CP 2101~CP 1201~CP 1301~CP 1401~CP 1501~CP 1601~CP 3101~CP 3201~CP 3301~CP 1101~CP 1701~CP 2201~CP 2301~CP 3401",
    sub_zips:
      "CP 21~CP 12~CP 13~CP 14~CP 15~CP 16~CP 31~CP 32~CP 33~CP 11~CP 17~CP 22~CP 23~CP 34",
    upper: "CSZ",
    zip: "CP [1-3][1-7][0-2]\\d",
    zipex: "CP 1101",
  },
  "data/SX": { id: "data/SX", key: "SX", name: "SINT MAARTEN" },
  "data/SY": {
    id: "data/SY",
    key: "SY",
    locality_name_type: "district",
    name: "SYRIA",
  },
  "data/SZ": {
    fmt: "%N%n%O%n%A%n%C%n%Z",
    id: "data/SZ",
    key: "SZ",
    name: "SWAZILAND",
    posturl: "http://www.sptc.co.sz/swazipost/codes/index.php",
    upper: "ACZ",
    zip: "[HLMS]\\d{3}",
    zipex: "H100",
  },
  "data/TC": {
    fmt: "%N%n%O%n%A%n%C%n%Z",
    id: "data/TC",
    key: "TC",
    name: "TURKS AND CAICOS ISLANDS",
    require: "ACZ",
    upper: "CZ",
    zip: "TKCA 1ZZ",
    zipex: "TKCA 1ZZ",
  },
  "data/TD": { id: "data/TD", key: "TD", name: "CHAD" },
  "data/TF": { id: "data/TF", key: "TF", name: "FRENCH SOUTHERN TERRITORIES" },
  "data/TG": { id: "data/TG", key: "TG", name: "TOGO" },
  "data/TH": {
    fmt: "%N%n%O%n%A%n%D %C%n%S %Z",
    id: "data/TH",
    key: "TH",
    lang: "th",
    languages: "th",
    lfmt: "%N%n%O%n%A%n%D, %C%n%S %Z",
    name: "THAILAND",
    sub_isoids:
      "81~10~71~46~62~40~38~22~24~20~18~36~86~57~50~92~23~63~26~73~48~30~80~60~12~96~55~31~13~77~25~94~14~56~82~93~66~65~76~67~54~83~44~49~58~35~95~45~85~21~70~16~52~51~42~33~47~90~91~11~75~74~27~19~17~64~72~84~32~43~39~15~37~41~53~61~34",
    sub_keys:
      "กระบี่~กรุงเทพมหานคร~กาญจนบุรี~กาฬสินธุ์~กำแพงเพชร~ขอนแก่น~จังหวัด บึงกาฬ~จันทบุรี~ฉะเชิงเทรา~ชลบุรี~ชัยนาท~ชัยภูมิ~ชุมพร~เชียงราย~เชียงใหม่~ตรัง~ตราด~ตาก~นครนายก~นครปฐม~นครพนม~นครราชสีมา~นครศรีธรรมราช~นครสวรรค์~นนทบุรี~นราธิวาส~น่าน~บุรีรัมย์~ปทุมธานี~ประจวบคีรีขันธ์~ปราจีนบุรี~ปัตตานี~พระนครศรีอยุธยา~พะเยา~พังงา~พัทลุง~พิจิตร~พิษณุโลก~เพชรบุรี~เพชรบูรณ์~แพร่~ภูเก็ต~มหาสารคาม~มุกดาหาร~แม่ฮ่องสอน~ยโสธร~ยะลา~ร้อยเอ็ด~ระนอง~ระยอง~ราชบุรี~ลพบุรี~ลำปาง~ลำพูน~เลย~ศรีสะเกษ~สกลนคร~สงขลา~สตูล~สมุทรปราการ~สมุทรสงคราม~สมุทรสาคร~สระแก้ว~สระบุรี~สิงห์บุรี~สุโขทัย~สุพรรณบุรี~สุราษฎร์ธานี~สุรินทร์~หนองคาย~หนองบัวลำภู~อ่างทอง~อำนาจเจริญ~อุดรธานี~อุตรดิตถ์~อุทัยธานี~อุบลราชธานี",
    sub_lnames:
      "Krabi~Bangkok~Kanchanaburi~Kalasin~Kamphaeng Phet~Khon Kaen~Bueng Kan~Chanthaburi~Chachoengsao~Chon Buri~Chai Nat~Chaiyaphum~Chumpon~Chiang Rai~Chiang Mai~Trang~Trat~Tak~Nakhon Nayok~Nakhon Pathom~Nakhon Phanom~Nakhon Ratchasima~Nakhon Si Thammarat~Nakhon Sawan~Nonthaburi~Narathiwat~Nan~Buri Ram~Pathum Thani~Prachuap Khiri Khan~Prachin Buri~Pattani~Phra Nakhon Si Ayutthaya~Phayao~Phang Nga~Phattalung~Phichit~Phitsanulok~Phetchaburi~Phetchabun~Phrae~Phuket~Maha Sarakham~Mukdahan~Mae Hong Son~Yasothon~Yala~Roi Et~Ranong~Rayong~Ratchaburi~Lop Buri~Lampang~Lamphun~Loei~Si Sa Ket~Sakon Nakhon~Songkhla~Satun~Samut Prakan~Samut Songkhram~Samut Sakhon~Sa Kaeo~Saraburi~Sing Buri~Sukhothai~Suphanburi~Surat Thani~Surin~Nong Khai~Nong Bua Lam Phu~Ang Thong~Amnat Charoen~Udon Thani~Uttaradit~Uthai Thani~Ubon Ratchathani",
    sub_zips:
      "81~10~71~46~62~40~~22~24~20~17~36~86~57~50~92~23~63~26~73~48~30~80~60~11~96~55~31~12~77~25~94~13~56~82~93~66~65~76~67~54~83~44~49~58~35~95~45~85~21~70~15~52~51~42~33~47~90~91~10~75~74~27~18~16~64~72~84~32~43~39~14~37~41~53~61~34",
    upper: "S",
    zip: "\\d{5}",
    zipex: "10150,10210",
  },
  "data/TJ": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/TJ",
    key: "TJ",
    name: "TAJIKISTAN",
    zip: "\\d{6}",
    zipex: "735450,734025",
  },
  "data/TK": { id: "data/TK", key: "TK", name: "TOKELAU" },
  "data/TL": { id: "data/TL", key: "TL", name: "TIMOR-LESTE" },
  "data/TM": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/TM",
    key: "TM",
    name: "TURKMENISTAN",
    zip: "\\d{6}",
    zipex: "744000",
  },
  "data/TN": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/TN",
    key: "TN",
    name: "TUNISIA",
    posturl: "http://www.poste.tn/codes.php",
    zip: "\\d{4}",
    zipex: "1002,8129,3100,1030",
  },
  "data/TO": { id: "data/TO", key: "TO", name: "TONGA" },
  "data/TR": {
    fmt: "%N%n%O%n%A%n%Z %C/%S",
    id: "data/TR",
    key: "TR",
    lang: "tr",
    languages: "tr",
    locality_name_type: "district",
    name: "TURKEY",
    posturl: "http://postakodu.ptt.gov.tr/",
    require: "ACZ",
    sub_isoids:
      "01~02~03~04~68~05~06~07~75~08~09~10~74~72~69~11~12~13~14~15~16~17~18~19~20~21~81~22~23~24~25~26~27~28~29~30~31~76~32~34~35~46~78~70~36~37~38~71~39~40~79~41~42~43~44~45~47~33~48~49~50~51~52~80~53~54~55~56~57~58~63~73~59~60~61~62~64~65~77~66~67",
    sub_keys:
      "Adana~Adıyaman~Afyon~Ağrı~Aksaray~Amasya~Ankara~Antalya~Ardahan~Artvin~Aydın~Balıkesir~Bartın~Batman~Bayburt~Bilecik~Bingöl~Bitlis~Bolu~Burdur~Bursa~Çanakkale~Çankırı~Çorum~Denizli~Diyarbakır~Düzce~Edirne~Elazığ~Erzincan~Erzurum~Eskişehir~Gaziantep~Giresun~Gümüşhane~Hakkari~Hatay~Iğdır~Isparta~İstanbul~İzmir~Kahramanmaraş~Karabük~Karaman~Kars~Kastamonu~Kayseri~Kırıkkale~Kırklareli~Kırşehir~Kilis~Kocaeli~Konya~Kütahya~Malatya~Manisa~Mardin~Mersin~Muğla~Muş~Nevşehir~Niğde~Ordu~Osmaniye~Rize~Sakarya~Samsun~Siirt~Sinop~Sivas~Şanlıurfa~Şırnak~Tekirdağ~Tokat~Trabzon~Tunceli~Uşak~Van~Yalova~Yozgat~Zonguldak",
    sub_zips:
      "01~02~03~04~68~05~06~07~75~08~09~10~74~72~69~11~12~13~14~15~16~17~18~19~20~21~81~22~23~24~25~26~27~28~29~30~31~76~32~34~35~46~78~70~36~37~38~71~39~40~79~41~42~43~44~45~47~33~48~49~50~51~52~80~53~54~55~56~57~58~63~73~59~60~61~62~64~65~77~66~67",
    zip: "\\d{5}",
    zipex: "01960,06101",
  },
  "data/TT": { id: "data/TT", key: "TT", name: "TRINIDAD AND TOBAGO" },
  "data/TV": {
    fmt: "%N%n%O%n%A%n%C%n%S",
    id: "data/TV",
    key: "TV",
    lang: "tyv",
    languages: "tyv",
    name: "TUVALU",
    state_name_type: "island",
    sub_isoids: "FUN~NMG~NMA~~NIT~NUI~NKF~NKL~VAI",
    sub_keys:
      "Funafuti~Nanumanga~Nanumea~Niulakita~Niutao~Nui~Nukufetau~Nukulaelae~Vaitupu",
    upper: "ACS",
  },
  "data/TW": {
    fmt: "%Z%n%S%C%n%A%n%O%n%N",
    id: "data/TW",
    key: "TW",
    lang: "zh-Hant",
    languages: "zh-Hant",
    lfmt: "%N%n%O%n%A%n%C, %S %Z",
    name: "TAIWAN",
    posturl:
      "http://www.post.gov.tw/post/internet/f_searchzone/index.jsp?ID=190102",
    require: "ACSZ",
    state_name_type: "county",
    sub_isoids:
      "TXG~TPE~TTT~TNN~ILA~HUA~~NAN~PIF~MIA~TAO~KHH~KEE~~YUN~NWT~HSZ~HSQ~CYI~CYQ~CHA~PEN",
    sub_keys:
      "台中市~台北市~台東縣~台南市~宜蘭縣~花蓮縣~金門縣~南投縣~屏東縣~苗栗縣~桃園市~高雄市~基隆市~連江縣~雲林縣~新北市~新竹市~新竹縣~嘉義市~嘉義縣~彰化縣~澎湖縣",
    sub_lnames:
      "Taichung City~Taipei City~Taitung County~Tainan City~Yilan County~Hualien County~Kinmen County~Nantou County~Pingtung County~Miaoli County~Taoyuan City~Kaohsiung City~Keelung City~Lienchiang County~Yunlin County~New Taipei City~Hsinchu City~Hsinchu County~Chiayi City~Chiayi County~Changhua County~Penghu County",
    sub_mores:
      "true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true",
    sub_zipexs:
      "400,408,411,439~100,119~950,966~700,745~260,272~970,983~890,896~540,558~900,947~350,369~320,338~800,815,817,852~200,206~209,212~630,655~207,208,220,253~~302,315~~602,625~500,530~880,885",
    sub_zips:
      "4[0-3]~1[01]~9[56]~7[0-4]~2[67]~9[78]~89~5[45]~9[0-4]~3[56]~3[23]~8[02-5]|81[1-579]~20[0-6]~209|21[012]~6[3-5]~20[78]|2[2345]~300~30[2-8]|31~600~60[1-9]|6[12]~5[0123]~88",
    zip: "\\d{3}(?:\\d{2})?",
    zipex: "104,106,10603,40867",
  },
  "data/TZ": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/TZ",
    key: "TZ",
    name: "TANZANIA (UNITED REP.)",
    zip: "\\d{4,5}",
    zipex: "6090,34413",
  },
  "data/UA": {
    fmt: "%N%n%O%n%A%n%C%n%S%n%Z",
    id: "data/UA",
    key: "UA",
    lang: "uk",
    languages: "uk",
    lfmt: "%N%n%O%n%A%n%C%n%S%n%Z",
    name: "UKRAINE",
    posturl: "http://services.ukrposhta.com/postindex_new/",
    require: "ACSZ",
    state_name_type: "oblast",
    sub_isoids:
      "43~05~07~12~14~18~21~23~26~30~32~35~09~46~48~51~53~56~40~59~61~63~65~68~71~77~74",
    sub_keys:
      "Автономна Республіка Крим~Вінницька область~Волинська область~Дніпропетровська область~Донецька область~Житомирська область~Закарпатська область~Запорізька область~Івано-Франківська область~місто Київ~Київська область~Кіровоградська область~Луганська область~Львівська область~Миколаївська область~Одеська область~Полтавська область~Рівненська область~місто Севастополь~Сумська область~Тернопільська область~Харківська область~Херсонська область~Хмельницька область~Черкаська область~Чернівецька область~Чернігівська область",
    sub_lnames:
      "Crimea~Vinnyts'ka oblast~Volyns'ka oblast~Dnipropetrovsk oblast~Donetsk oblast~Zhytomyrs'ka oblast~Zakarpats'ka oblast~Zaporiz'ka oblast~Ivano-Frankivs'ka oblast~Kyiv city~Kiev oblast~Kirovohrads'ka oblast~Luhans'ka oblast~Lviv oblast~Mykolaivs'ka oblast~Odessa oblast~Poltavs'ka oblast~Rivnens'ka oblast~Sevastopol' city~Sums'ka oblast~Ternopil's'ka oblast~Kharkiv oblast~Khersons'ka oblast~Khmel'nyts'ka oblast~Cherkas'ka oblast~Chernivets'ka oblast~Chernihivs'ka oblast",
    sub_names:
      "Автономна Республіка Крим~Вінницька область~Волинська область~Дніпропетровська область~Донецька область~Житомирська область~Закарпатська область~Запорізька область~Івано-Франківська область~Київ~Київська область~Кіровоградська область~Луганська область~Львівська область~Миколаївська область~Одеська область~Полтавська область~Рівненська область~Севастополь~Сумська область~Тернопільська область~Харківська область~Херсонська область~Хмельницька область~Черкаська область~Чернівецька область~Чернігівська область",
    sub_zips:
      "9[5-8]~2[1-4]~4[3-5]~49|5[0-3]~8[3-7]~1[0-3]~8[89]|90~69|7[0-2]~7[6-8]~0[1-6]~0[7-9]~2[5-8]~9[1-4]~79|8[0-2]~5[4-7]~6[5-8]~3[6-9]~3[3-5]~99~4[0-2]~4[6-8]~6[1-4]~7[3-5]~29|3[0-2]~1[89]|20~5[89]|60~1[4-7]",
    zip: "\\d{5}",
    zipex: "15432,01055,01001",
  },
  "data/UG": { id: "data/UG", key: "UG", name: "UGANDA" },
  "data/US": {
    fmt: "%N%n%O%n%A%n%C, %S %Z",
    id: "data/US",
    key: "US",
    lang: "en",
    languages: "en",
    name: "UNITED STATES",
    posturl: "https://tools.usps.com/go/ZipLookupAction!input.action",
    require: "ACSZ",
    state_name_type: "state",
    sub_isoids:
      "AL~AK~~AZ~AR~~~~CA~CO~CT~DE~DC~FL~GA~~HI~ID~IL~IN~IA~KS~KY~LA~ME~~MD~MA~MI~~MN~MS~MO~MT~NE~NV~NH~NJ~NM~NY~NC~ND~~OH~OK~OR~~PA~~RI~SC~SD~TN~TX~UT~VT~~VA~WA~WV~WI~WY",
    sub_keys:
      "AL~AK~AS~AZ~AR~AA~AE~AP~CA~CO~CT~DE~DC~FL~GA~GU~HI~ID~IL~IN~IA~KS~KY~LA~ME~MH~MD~MA~MI~FM~MN~MS~MO~MT~NE~NV~NH~NJ~NM~NY~NC~ND~MP~OH~OK~OR~PW~PA~PR~RI~SC~SD~TN~TX~UT~VT~VI~VA~WA~WV~WI~WY",
    sub_names:
      "Alabama~Alaska~American Samoa~Arizona~Arkansas~Armed Forces (AA)~Armed Forces (AE)~Armed Forces (AP)~California~Colorado~Connecticut~Delaware~District of Columbia~Florida~Georgia~Guam~Hawaii~Idaho~Illinois~Indiana~Iowa~Kansas~Kentucky~Louisiana~Maine~Marshall Islands~Maryland~Massachusetts~Michigan~Micronesia~Minnesota~Mississippi~Missouri~Montana~Nebraska~Nevada~New Hampshire~New Jersey~New Mexico~New York~North Carolina~North Dakota~Northern Mariana Islands~Ohio~Oklahoma~Oregon~Palau~Pennsylvania~Puerto Rico~Rhode Island~South Carolina~South Dakota~Tennessee~Texas~Utah~Vermont~Virgin Islands~Virginia~Washington~West Virginia~Wisconsin~Wyoming",
    sub_zipexs:
      "35000,36999~99500,99999~96799~85000,86999~71600,72999~34000,34099~09000,09999~96200,96699~90000,96199~80000,81999~06000,06999~19700,19999~20000,56999~32000,34999~30000,39901~96910,96932~96700,96899~83200,83999~60000,62999~46000,47999~50000,52999~66000,67999~40000,42799~70000,71599~03900,04999~96960,96979~20600,21999~01000,05544~48000,49999~96941,96944~55000,56799~38600,39799~63000,65999~59000,59999~68000,69999~88900,89999~03000,03899~07000,08999~87000,88499~10000,00544~27000,28999~58000,58999~96950,96952~43000,45999~73000,74999~97000,97999~96940~15000,19699~00600,00999~02800,02999~29000,29999~57000,57999~37000,38599~75000,73344~84000,84999~05000,05999~00800,00899~20100,24699~98000,99499~24700,26999~53000,54999~82000,83414",
    sub_zips:
      "3[56]~99[5-9]~96799~8[56]~71[6-9]|72~340~09~96[2-6]~9[0-5]|96[01]~8[01]~06~19[7-9]~20[02-5]|569~3[23]|34[1-9]~3[01]|398|39901~969([1-2]\\d|3[12])~967[0-8]|9679[0-8]|968~83[2-9]~6[0-2]~4[67]~5[0-2]~6[67]~4[01]|42[0-7]~70|71[0-5]~039|04~969[67]~20[6-9]|21~01|02[0-7]|05501|05544~4[89]~9694[1-4]~55|56[0-7]~38[6-9]|39[0-7]~6[3-5]~59~6[89]~889|89~03[0-8]~0[78]~87|88[0-4]~1[0-4]|06390|00501|00544~2[78]~58~9695[0-2]~4[3-5]~7[34]~97~969(39|40)~1[5-8]|19[0-6]~00[679]~02[89]~29~57~37|38[0-5]~7[5-9]|885|73301|73344~84~05~008~201|2[23]|24[0-6]~98|99[0-4]~24[7-9]|2[56]~5[34]~82|83[01]|83414",
    upper: "CS",
    zip: "(\\d{5})(?:[ \\-](\\d{4}))?",
    zip_name_type: "zip",
    zipex: "95014,22162-1010",
  },
  "data/UY": {
    fmt: "%N%n%O%n%A%n%Z %C %S",
    id: "data/UY",
    key: "UY",
    lang: "es",
    languages: "es",
    name: "URUGUAY",
    posturl:
      "http://www.correo.com.uy/index.asp?codPag=codPost&switchMapa=codPost",
    sub_isoids: "AR~CA~CL~CO~DU~FS~FD~LA~MA~MO~PA~RN~RV~RO~SA~SJ~SO~TA~TT",
    sub_keys:
      "Artigas~Canelones~Cerro Largo~Colonia~Durazno~Flores~Florida~Lavalleja~Maldonado~Montevideo~Paysandú~Río Negro~Rivera~Rocha~Salto~San José~Soriano~Tacuarembó~Treinta y Tres",
    sub_zips:
      "55~9[01]|1[456]~37~70|75204~97~85~94|9060|97005~30~20~1|91600~60~65|60002~40~27~50~80~75|70003~45~33|30203|30204|30302|37007",
    upper: "CS",
    zip: "\\d{5}",
    zipex: "11600",
  },
  "data/UZ": {
    fmt: "%N%n%O%n%A%n%Z %C%n%S",
    id: "data/UZ",
    key: "UZ",
    name: "UZBEKISTAN",
    posturl: "http://www.pochta.uz/ru/uslugi/indexsearch.html",
    upper: "CS",
    zip: "\\d{6}",
    zipex: "702100,700000",
  },
  "data/VA": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/VA",
    key: "VA",
    name: "VATICAN",
    zip: "00120",
    zipex: "00120",
  },
  "data/VC": {
    fmt: "%N%n%O%n%A%n%C %Z",
    id: "data/VC",
    key: "VC",
    name: "SAINT VINCENT AND THE GRENADINES (ANTILLES)",
    posturl:
      "http://www.svgpost.gov.vc/?option=com_content&view=article&id=3&Itemid=16",
    zip: "VC\\d{4}",
    zipex: "VC0100,VC0110,VC0400",
  },
  "data/VE": {
    fmt: "%N%n%O%n%A%n%C %Z, %S",
    id: "data/VE",
    key: "VE",
    lang: "es",
    languages: "es",
    name: "VENEZUELA",
    posturl: "http://www.ipostel.gob.ve/index.php/oficinas-postales",
    require: "ACS",
    state_name_type: "state",
    sub_isoids: "Z~B~C~D~E~F~G~H~Y~W~A~I~J~K~L~M~N~O~P~R~S~T~X~U~V",
    sub_keys:
      "Amazonas~Anzoátegui~Apure~Aragua~Barinas~Bolívar~Carabobo~Cojedes~Delta Amacuro~Dependencias Federales~Distrito Federal~Falcón~Guárico~Lara~Mérida~Miranda~Monagas~Nueva Esparta~Portuguesa~Sucre~Táchira~Trujillo~Vargas~Yaracuy~Zulia",
    upper: "CS",
    zip: "\\d{4}",
    zipex: "1010,3001,8011,1020",
  },
  "data/VG": {
    fmt: "%N%n%O%n%A%n%C%n%Z",
    id: "data/VG",
    key: "VG",
    name: "VIRGIN ISLANDS (BRITISH)",
    require: "A",
    zip: "VG\\d{4}",
    zipex: "VG1110,VG1150,VG1160",
  },
  "data/VI": {
    fmt: "%N%n%O%n%A%n%C %S %Z",
    id: "data/VI",
    key: "VI",
    name: "VIRGIN ISLANDS (U.S.)",
    posturl: "http://zip4.usps.com/zip4/welcome.jsp",
    require: "ACSZ",
    state_name_type: "state",
    upper: "ACNOS",
    zip: "(008(?:(?:[0-4]\\d)|(?:5[01])))(?:[ \\-](\\d{4}))?",
    zip_name_type: "zip",
    zipex: "00802-1222,00850-9802",
  },
  "data/VN": {
    fmt: "%N%n%O%n%A%n%C%n%S %Z",
    id: "data/VN",
    key: "VN",
    lang: "vi",
    languages: "vi",
    lfmt: "%N%n%O%n%A%n%C%n%S %Z",
    name: "VIET NAM",
    posturl: "http://postcode.vnpost.vn/services/search.aspx",
    sub_isoids:
      "44~43~55~54~53~56~50~57~31~58~40~59~04~CT~DN~33~72~71~39~45~30~03~63~HN~23~61~HP~73~14~66~34~47~28~01~09~02~35~41~67~22~18~36~68~32~24~27~29~13~25~52~05~37~20~69~21~SG~26~46~51~07~49~70~06",
    sub_keys:
      "An Giang~Bà Rịa–Vũng Tàu~Bạc Liêu~Bắc Giang~Bắc Kạn~Bắc Ninh~Bến Tre~Bình Dương~Bình Định~Bình Phước~Bình Thuận~Cà Mau~Cao Bằng~Cần Thơ~Đà Nẵng~Đắk Lắk~Đăk Nông~Điện Biên~Đồng Nai~Đồng Tháp~Gia Lai~Hà Giang~Hà Nam~Hà Nội~Hà Tĩnh~Hải Dương~Hải Phòng~Hậu Giang~Hòa Bình~Hưng Yên~Khánh Hòa~Kiên Giang~Kon Tum~Lai Châu~Lạng Sơn~Lào Cai~Lâm Đồng~Long An~Nam Định~Nghệ An~Ninh Bình~Ninh Thuận~Phú Thọ~Phú Yên~Quảng Bình~Quảng Nam~Quảng Ngãi~Quảng Ninh~Quảng Trị~Sóc Trăng~Sơn La~Tây Ninh~Thái Bình~Thái Nguyên~Thanh Hóa~Thành phố Hồ Chí Minh~Thừa Thiên–Huế~Tiền Giang~Trà Vinh~Tuyên Quang~Vĩnh Long~Vĩnh Phúc~Yên Bái",
    sub_lnames:
      "An Giang Province~Ba Ria-Vung Tau Province~Bac Lieu Province~Bac Giang Province~Bac Kan Province~Bac Ninh Province~Ben Tre Province~Binh Duong Province~Binh Dinh Province~Binh Phuoc Province~Binh Thuan Province~Ca Mau Province~Cao Bang Province~Can Tho City~Da Nang City~Dak Lak Province~Dak Nong Province~Dien Bien Province~Dong Nai Province~Dong Thap Province~Gia Lai Province~Ha Giang Province~Ha Nam Province~Hanoi City~Ha Tinh Province~Hai Duong Province~Haiphong City~Hau Giang Province~Hoa Binh Province~Hung Yen Province~Khanh Hoa Province~Kien Giang Province~Kon Tum Province~Lai Chau Province~Lang Song Province~Lao Cai Province~Lam Dong Province~Long An Province~Nam Dinh Province~Nghe An Province~Ninh Binh Province~Ninh Thuan Province~Phu Tho Province~Phu Yen Province~Quang Binh Province~Quang Nam Province~Quang Ngai Province~Quang Ninh Province~Quang Tri Province~Soc Trang Province~Son La Province~Tay Ninh Province~Thai Binh Province~Thai Nguyen Province~Thanh Hoa Province~Ho Chi Minh City~Thua Thien-Hue Province~Tien Giang Province~Tra Vinh Province~Tuyen Quang Province~Vinh Long Province~Vinh Phuc Province~Yen Bai Province",
    zip: "\\d{5}\\d?",
    zipex: "70010,55999",
  },
  "data/VU": { id: "data/VU", key: "VU", name: "VANUATU" },
  "data/WF": {
    fmt: "%O%n%N%n%A%n%Z %C %X",
    id: "data/WF",
    key: "WF",
    name: "WALLIS AND FUTUNA ISLANDS",
    require: "ACZ",
    upper: "ACX",
    zip: "986\\d{2}",
    zipex: "98600",
  },
  "data/WS": { id: "data/WS", key: "WS", name: "SAMOA" },
  "data/XK": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/XK",
    key: "XK",
    name: "KOSOVO",
    zip: "[1-7]\\d{4}",
    zipex: "10000",
  },
  "data/YE": { id: "data/YE", key: "YE", name: "YEMEN" },
  "data/YT": {
    fmt: "%O%n%N%n%A%n%Z %C %X",
    id: "data/YT",
    key: "YT",
    name: "MAYOTTE",
    require: "ACZ",
    upper: "ACX",
    zip: "976\\d{2}",
    zipex: "97600",
  },
  "data/ZA": {
    fmt: "%N%n%O%n%A%n%D%n%C%n%Z",
    id: "data/ZA",
    key: "ZA",
    name: "SOUTH AFRICA",
    posturl: "https://www.postoffice.co.za/Questions/postalcode.html",
    require: "ACZ",
    zip: "\\d{4}",
    zipex: "0083,1451,0001",
  },
  "data/ZM": {
    fmt: "%N%n%O%n%A%n%Z %C",
    id: "data/ZM",
    key: "ZM",
    name: "ZAMBIA",
    zip: "\\d{5}",
    zipex: "50100,50101",
  },
  "data/ZW": { id: "data/ZW", key: "ZW", name: "ZIMBABWE" },
};
export default AddressMetaData;
PK
!<I%���-�-/modules/shared/AddressMetaDataExtension.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export const AddressMetaDataExtension = {
  "data/AF": {
    alpha_3_code: "AFG",
  },
  "data/AX": {
    alpha_3_code: "ALA",
  },
  "data/AL": {
    alpha_3_code: "ALB",
  },
  "data/DZ": {
    alpha_3_code: "DZA",
  },
  "data/AS": {
    alpha_3_code: "ASM",
  },
  "data/AD": {
    alpha_3_code: "AND",
  },
  "data/AO": {
    alpha_3_code: "AGO",
  },
  "data/AI": {
    alpha_3_code: "AIA",
  },
  "data/AQ": {
    alpha_3_code: "ATA",
  },
  "data/AG": {
    alpha_3_code: "ATG",
  },
  "data/AR": {
    alpha_3_code: "ARG",
  },
  "data/AM": {
    alpha_3_code: "ARM",
  },
  "data/AW": {
    alpha_3_code: "ABW",
  },
  "data/AU": {
    alpha_3_code: "AUS",
  },
  "data/AT": {
    alpha_3_code: "AUT",
  },
  "data/AZ": {
    alpha_3_code: "AZE",
  },
  "data/BS": {
    alpha_3_code: "BHS",
  },
  "data/BH": {
    alpha_3_code: "BHR",
  },
  "data/BD": {
    alpha_3_code: "BGD",
  },
  "data/BB": {
    alpha_3_code: "BRB",
  },
  "data/BY": {
    alpha_3_code: "BLR",
  },
  "data/BE": {
    alpha_3_code: "BEL",
  },
  "data/BZ": {
    alpha_3_code: "BLZ",
  },
  "data/BJ": {
    alpha_3_code: "BEN",
  },
  "data/BM": {
    alpha_3_code: "BMU",
  },
  "data/BT": {
    alpha_3_code: "BTN",
  },
  "data/BO": {
    alpha_3_code: "BOL",
  },
  "data/BQ": {
    alpha_3_code: "BES",
  },
  "data/BA": {
    alpha_3_code: "BIH",
  },
  "data/BW": {
    alpha_3_code: "BWA",
  },
  "data/BV": {
    alpha_3_code: "BVT",
  },
  "data/BR": {
    alpha_3_code: "BRA",
  },
  "data/IO": {
    alpha_3_code: "IOT",
  },
  "data/BN": {
    alpha_3_code: "BRN",
  },
  "data/BG": {
    alpha_3_code: "BGR",
  },
  "data/BF": {
    alpha_3_code: "BFA",
  },
  "data/BI": {
    alpha_3_code: "BDI",
  },
  "data/CV": {
    alpha_3_code: "CPV",
  },
  "data/KH": {
    alpha_3_code: "KHM",
  },
  "data/CM": {
    alpha_3_code: "CMR",
  },
  "data/CA": {
    alpha_3_code: "CAN",
  },
  "data/KY": {
    alpha_3_code: "CYM",
  },
  "data/CF": {
    alpha_3_code: "CAF",
  },
  "data/TD": {
    alpha_3_code: "TCD",
  },
  "data/CL": {
    alpha_3_code: "CHL",
  },
  "data/CN": {
    alpha_3_code: "CHN",
  },
  "data/CX": {
    alpha_3_code: "CXR",
  },
  "data/CC": {
    alpha_3_code: "CCK",
  },
  "data/CO": {
    alpha_3_code: "COL",
  },
  "data/KM": {
    alpha_3_code: "COM",
  },
  "data/CG": {
    alpha_3_code: "COG",
  },
  "data/CD": {
    alpha_3_code: "COD",
  },
  "data/CK": {
    alpha_3_code: "COK",
  },
  "data/CR": {
    alpha_3_code: "CRI",
  },
  "data/CI": {
    alpha_3_code: "CIV",
  },
  "data/HR": {
    alpha_3_code: "HRV",
  },
  "data/CU": {
    alpha_3_code: "CUB",
  },
  "data/CW": {
    alpha_3_code: "CUW",
  },
  "data/CY": {
    alpha_3_code: "CYP",
  },
  "data/CZ": {
    alpha_3_code: "CZE",
  },
  "data/DK": {
    alpha_3_code: "DNK",
  },
  "data/DJ": {
    alpha_3_code: "DJI",
  },
  "data/DM": {
    alpha_3_code: "DMA",
  },
  "data/DO": {
    alpha_3_code: "DOM",
  },
  "data/EC": {
    alpha_3_code: "ECU",
  },
  "data/EG": {
    alpha_3_code: "EGY",
  },
  "data/SV": {
    alpha_3_code: "SLV",
  },
  "data/GQ": {
    alpha_3_code: "GNQ",
  },
  "data/ER": {
    alpha_3_code: "ERI",
  },
  "data/EE": {
    alpha_3_code: "EST",
  },
  "data/SZ": {
    alpha_3_code: "SWZ",
  },
  "data/ET": {
    alpha_3_code: "ETH",
  },
  "data/FK": {
    alpha_3_code: "FLK",
  },
  "data/FO": {
    alpha_3_code: "FRO",
  },
  "data/FJ": {
    alpha_3_code: "FJI",
  },
  "data/FI": {
    alpha_3_code: "FIN",
  },
  "data/FR": {
    alpha_3_code: "FRA",
  },
  "data/GF": {
    alpha_3_code: "GUF",
  },
  "data/PF": {
    alpha_3_code: "PYF",
  },
  "data/TF": {
    alpha_3_code: "ATF",
  },
  "data/GA": {
    alpha_3_code: "GAB",
  },
  "data/GM": {
    alpha_3_code: "GMB",
  },
  "data/GE": {
    alpha_3_code: "GEO",
  },
  "data/DE": {
    alpha_3_code: "DEU",
  },
  "data/GH": {
    alpha_3_code: "GHA",
  },
  "data/GI": {
    alpha_3_code: "GIB",
  },
  "data/GR": {
    alpha_3_code: "GRC",
  },
  "data/GL": {
    alpha_3_code: "GRL",
  },
  "data/GD": {
    alpha_3_code: "GRD",
  },
  "data/GP": {
    alpha_3_code: "GLP",
  },
  "data/GU": {
    alpha_3_code: "GUM",
  },
  "data/GT": {
    alpha_3_code: "GTM",
  },
  "data/GG": {
    alpha_3_code: "GGY",
  },
  "data/GN": {
    alpha_3_code: "GIN",
  },
  "data/GW": {
    alpha_3_code: "GNB",
  },
  "data/GY": {
    alpha_3_code: "GUY",
  },
  "data/HT": {
    alpha_3_code: "HTI",
  },
  "data/HM": {
    alpha_3_code: "HMD",
  },
  "data/VA": {
    alpha_3_code: "VAT",
  },
  "data/HN": {
    alpha_3_code: "HND",
  },
  "data/HK": {
    alpha_3_code: "HKG",
  },
  "data/HU": {
    alpha_3_code: "HUN",
  },
  "data/IS": {
    alpha_3_code: "ISL",
  },
  "data/IN": {
    alpha_3_code: "IND",
  },
  "data/ID": {
    alpha_3_code: "IDN",
  },
  "data/IR": {
    alpha_3_code: "IRN",
  },
  "data/IQ": {
    alpha_3_code: "IRQ",
  },
  "data/IE": {
    alpha_3_code: "IRL",
  },
  "data/IM": {
    alpha_3_code: "IMN",
  },
  "data/IL": {
    alpha_3_code: "ISR",
  },
  "data/IT": {
    alpha_3_code: "ITA",
  },
  "data/JM": {
    alpha_3_code: "JAM",
  },
  "data/JP": {
    alpha_3_code: "JPN",
  },
  "data/JE": {
    alpha_3_code: "JEY",
  },
  "data/JO": {
    alpha_3_code: "JOR",
  },
  "data/KZ": {
    alpha_3_code: "KAZ",
  },
  "data/KE": {
    alpha_3_code: "KEN",
  },
  "data/KI": {
    alpha_3_code: "KIR",
  },
  "data/KP": {
    alpha_3_code: "PRK",
  },
  "data/KR": {
    alpha_3_code: "KOR",
  },
  "data/KW": {
    alpha_3_code: "KWT",
  },
  "data/KG": {
    alpha_3_code: "KGZ",
  },
  "data/LA": {
    alpha_3_code: "LAO",
  },
  "data/LV": {
    alpha_3_code: "LVA",
  },
  "data/LB": {
    alpha_3_code: "LBN",
  },
  "data/LS": {
    alpha_3_code: "LSO",
  },
  "data/LR": {
    alpha_3_code: "LBR",
  },
  "data/LY": {
    alpha_3_code: "LBY",
  },
  "data/LI": {
    alpha_3_code: "LIE",
  },
  "data/LT": {
    alpha_3_code: "LTU",
  },
  "data/LU": {
    alpha_3_code: "LUX",
  },
  "data/MO": {
    alpha_3_code: "MAC",
  },
  "data/MG": {
    alpha_3_code: "MDG",
  },
  "data/MW": {
    alpha_3_code: "MWI",
  },
  "data/MY": {
    alpha_3_code: "MYS",
  },
  "data/MV": {
    alpha_3_code: "MDV",
  },
  "data/ML": {
    alpha_3_code: "MLI",
  },
  "data/MT": {
    alpha_3_code: "MLT",
  },
  "data/MH": {
    alpha_3_code: "MHL",
  },
  "data/MQ": {
    alpha_3_code: "MTQ",
  },
  "data/MR": {
    alpha_3_code: "MRT",
  },
  "data/MU": {
    alpha_3_code: "MUS",
  },
  "data/YT": {
    alpha_3_code: "MYT",
  },
  "data/MX": {
    alpha_3_code: "MEX",
  },
  "data/FM": {
    alpha_3_code: "FSM",
  },
  "data/MD": {
    alpha_3_code: "MDA",
  },
  "data/MC": {
    alpha_3_code: "MCO",
  },
  "data/MN": {
    alpha_3_code: "MNG",
  },
  "data/ME": {
    alpha_3_code: "MNE",
  },
  "data/MS": {
    alpha_3_code: "MSR",
  },
  "data/MA": {
    alpha_3_code: "MAR",
  },
  "data/MZ": {
    alpha_3_code: "MOZ",
  },
  "data/MM": {
    alpha_3_code: "MMR",
  },
  "data/NA": {
    alpha_3_code: "NAM",
  },
  "data/NR": {
    alpha_3_code: "NRU",
  },
  "data/NP": {
    alpha_3_code: "NPL",
  },
  "data/NL": {
    alpha_3_code: "NLD",
  },
  "data/NC": {
    alpha_3_code: "NCL",
  },
  "data/NZ": {
    alpha_3_code: "NZL",
  },
  "data/NI": {
    alpha_3_code: "NIC",
  },
  "data/NE": {
    alpha_3_code: "NER",
  },
  "data/NG": {
    alpha_3_code: "NGA",
  },
  "data/NU": {
    alpha_3_code: "NIU",
  },
  "data/NF": {
    alpha_3_code: "NFK",
  },
  "data/MK": {
    alpha_3_code: "MKD",
  },
  "data/MP": {
    alpha_3_code: "MNP",
  },
  "data/NO": {
    alpha_3_code: "NOR",
  },
  "data/OM": {
    alpha_3_code: "OMN",
  },
  "data/PK": {
    alpha_3_code: "PAK",
  },
  "data/PW": {
    alpha_3_code: "PLW",
  },
  "data/PS": {
    alpha_3_code: "PSE",
  },
  "data/PA": {
    alpha_3_code: "PAN",
  },
  "data/PG": {
    alpha_3_code: "PNG",
  },
  "data/PY": {
    alpha_3_code: "PRY",
  },
  "data/PE": {
    alpha_3_code: "PER",
  },
  "data/PH": {
    alpha_3_code: "PHL",
  },
  "data/PN": {
    alpha_3_code: "PCN",
  },
  "data/PL": {
    alpha_3_code: "POL",
  },
  "data/PT": {
    alpha_3_code: "PRT",
  },
  "data/PR": {
    alpha_3_code: "PRI",
  },
  "data/QA": {
    alpha_3_code: "QAT",
  },
  "data/RE": {
    alpha_3_code: "REU",
  },
  "data/RO": {
    alpha_3_code: "ROU",
  },
  "data/RU": {
    alpha_3_code: "RUS",
  },
  "data/RW": {
    alpha_3_code: "RWA",
  },
  "data/BL": {
    alpha_3_code: "BLM",
  },
  "data/SH": {
    alpha_3_code: "SHN",
  },
  "data/KN": {
    alpha_3_code: "KNA",
  },
  "data/LC": {
    alpha_3_code: "LCA",
  },
  "data/MF": {
    alpha_3_code: "MAF",
  },
  "data/PM": {
    alpha_3_code: "SPM",
  },
  "data/VC": {
    alpha_3_code: "VCT",
  },
  "data/WS": {
    alpha_3_code: "WSM",
  },
  "data/SM": {
    alpha_3_code: "SMR",
  },
  "data/ST": {
    alpha_3_code: "STP",
  },
  "data/SA": {
    alpha_3_code: "SAU",
  },
  "data/SN": {
    alpha_3_code: "SEN",
  },
  "data/RS": {
    alpha_3_code: "SRB",
  },
  "data/SC": {
    alpha_3_code: "SYC",
  },
  "data/SL": {
    alpha_3_code: "SLE",
  },
  "data/SG": {
    alpha_3_code: "SGP",
  },
  "data/SX": {
    alpha_3_code: "SXM",
  },
  "data/SK": {
    alpha_3_code: "SVK",
  },
  "data/SI": {
    alpha_3_code: "SVN",
  },
  "data/SB": {
    alpha_3_code: "SLB",
  },
  "data/SO": {
    alpha_3_code: "SOM",
  },
  "data/ZA": {
    alpha_3_code: "ZAF",
  },
  "data/GS": {
    alpha_3_code: "SGS",
  },
  "data/SS": {
    alpha_3_code: "SSD",
  },
  "data/ES": {
    alpha_3_code: "ESP",
  },
  "data/LK": {
    alpha_3_code: "LKA",
  },
  "data/SD": {
    alpha_3_code: "SDN",
  },
  "data/SR": {
    alpha_3_code: "SUR",
  },
  "data/SJ": {
    alpha_3_code: "SJM",
  },
  "data/SE": {
    alpha_3_code: "SWE",
  },
  "data/CH": {
    alpha_3_code: "CHE",
  },
  "data/SY": {
    alpha_3_code: "SYR",
  },
  "data/TW": {
    alpha_3_code: "TWN",
  },
  "data/TJ": {
    alpha_3_code: "TJK",
  },
  "data/TZ": {
    alpha_3_code: "TZA",
  },
  "data/TH": {
    alpha_3_code: "THA",
  },
  "data/TL": {
    alpha_3_code: "TLS",
  },
  "data/TG": {
    alpha_3_code: "TGO",
  },
  "data/TK": {
    alpha_3_code: "TKL",
  },
  "data/TO": {
    alpha_3_code: "TON",
  },
  "data/TT": {
    alpha_3_code: "TTO",
  },
  "data/TN": {
    alpha_3_code: "TUN",
  },
  "data/TR": {
    alpha_3_code: "TUR",
  },
  "data/TM": {
    alpha_3_code: "TKM",
  },
  "data/TC": {
    alpha_3_code: "TCA",
  },
  "data/TV": {
    alpha_3_code: "TUV",
  },
  "data/UG": {
    alpha_3_code: "UGA",
  },
  "data/UA": {
    alpha_3_code: "UKR",
  },
  "data/AE": {
    alpha_3_code: "ARE",
  },
  "data/GB": {
    alpha_3_code: "GBR",
  },
  "data/US": {
    alternative_names: [
      "US",
      "United States of America",
      "United States",
      "America",
      "U.S.",
      "USA",
      "U.S.A.",
      "U.S.A",
    ],
    alpha_3_code: "USA",
  },
  "data/UM": {
    alpha_3_code: "UMI",
  },
  "data/UY": {
    alpha_3_code: "URY",
  },
  "data/UZ": {
    alpha_3_code: "UZB",
  },
  "data/VU": {
    alpha_3_code: "VUT",
  },
  "data/VE": {
    alpha_3_code: "VEN",
  },
  "data/VN": {
    alpha_3_code: "VNM",
  },
  "data/VG": {
    alpha_3_code: "VGB",
  },
  "data/VI": {
    alpha_3_code: "VIR",
  },
  "data/WF": {
    alpha_3_code: "WLF",
  },
  "data/EH": {
    alpha_3_code: "ESH",
  },
  "data/YE": {
    alpha_3_code: "YEM",
  },
  "data/ZM": {
    alpha_3_code: "ZMB",
  },
  "data/ZW": {
    alpha_3_code: "ZWE",
  },
};

export default AddressMetaDataExtension;
PK
!<R����,modules/shared/AddressMetaDataLoader.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  AddressMetaData: "resource://gre/modules/shared/AddressMetaData.sys.mjs",
  AddressMetaDataExtension:
    "resource://gre/modules/shared/AddressMetaDataExtension.sys.mjs",
});

export class AddressMetaDataLoader {
  // Status of address data loading. We'll load all the countries with basic level 1
  // information while requesting conutry information, and set country to true.
  // Level 1 Set is for recording which country's level 1/level 2 data is loaded,
  // since we only load this when getCountryAddressData called with level 1 parameter.
  static dataLoaded = {
    country: false,
    level1: new Set(),
  };

  static addressData = {};

  static DATA_PREFIX = "data/";

  /**
   * Load address meta data and extension into one object.
   *
   * @returns {object}
   *          An object containing address data object with properties from extension.
   */
  static loadAddressMetaData() {
    const addressMetaData = lazy.AddressMetaData;

    for (const key in lazy.AddressMetaDataExtension) {
      let addressDataForKey = addressMetaData[key];
      if (!addressDataForKey) {
        addressDataForKey = addressMetaData[key] = {};
      }

      Object.assign(addressDataForKey, lazy.AddressMetaDataExtension[key]);
    }
    return addressMetaData;
  }

  /**
   * Convert certain properties' string value into array. We should make sure
   * the cached data is parsed.
   *
   * @param   {object} data Original metadata from addressReferences.
   * @returns {object} parsed metadata with property value that converts to array.
   */
  static #parse(data) {
    if (!data) {
      return null;
    }

    const properties = [
      "languages",
      "sub_keys",
      "sub_isoids",
      "sub_names",
      "sub_lnames",
    ];
    for (const key of properties) {
      if (!data[key]) {
        continue;
      }
      // No need to normalize data if the value is array already.
      if (Array.isArray(data[key])) {
        return data;
      }

      data[key] = data[key].split("~");
    }
    return data;
  }

  /**
   * We'll cache addressData in the loader once the data loaded from scripts.
   * It'll become the example below after loading addressReferences with extension:
   * addressData: {
   *               "data/US": {"lang": ["en"], ...// Data defined in libaddressinput metadata
   *                           "alternative_names": ... // Data defined in extension }
   *               "data/CA": {} // Other supported country metadata
   *               "data/TW": {} // Other supported country metadata
   *               "data/TW/台北市": {} // Other supported country level 1 metadata
   *              }
   *
   * @param   {string} country
   * @param   {string?} level1
   * @returns {object} Default locale metadata
   */
  static #loadData(country, level1 = null) {
    // Load the addressData if needed
    if (!this.dataLoaded.country) {
      this.addressData = this.loadAddressMetaData();
      this.dataLoaded.country = true;
    }
    if (!level1) {
      return this.#parse(this.addressData[`${this.DATA_PREFIX}${country}`]);
    }
    // If level1 is set, load addressReferences under country folder with specific
    // country/level 1 for level 2 information.
    if (!this.dataLoaded.level1.has(country)) {
      Object.assign(this.addressData, this.loadAddressMetaData());
      this.dataLoaded.level1.add(country);
    }
    return this.#parse(
      this.addressData[`${this.DATA_PREFIX}${country}/${level1}`]
    );
  }

  /**
   * Return the region metadata with default locale and other locales (if exists).
   *
   * @param   {string} country
   * @param   {string?} level1
   * @returns {object} Return default locale and other locales metadata.
   */
  static getData(country, level1 = null) {
    const defaultLocale = this.#loadData(country, level1);
    if (!defaultLocale) {
      return null;
    }

    const countryData = this.#parse(
      this.addressData[`${this.DATA_PREFIX}${country}`]
    );
    let locales = [];
    // TODO: Should be able to support multi-locale level 1/ level 2 metadata query
    //      in Bug 1421886
    if (countryData.languages) {
      const list = countryData.languages.filter(
        key => key !== countryData.lang
      );
      locales = list.map(key =>
        this.#parse(this.addressData[`${defaultLocale.id}--${key}`])
      );
    }
    return { defaultLocale, locales };
  }

  /**
   * Return an array containing countries alpha2 codes.
   *
   * @returns {Array} Return an array containing countries alpha2 codes.
   */
  static get #countryCodes() {
    return Object.keys(lazy.AddressMetaDataExtension).map(dataKey =>
      dataKey.replace(this.DATA_PREFIX, "")
    );
  }

  static getCountries(locales = []) {
    const displayNames = new Intl.DisplayNames(locales, {
      type: "region",
      fallback: "none",
    });
    const countriesMap = new Map();
    for (const countryCode of this.#countryCodes) {
      countriesMap.set(countryCode, displayNames.of(countryCode));
    }
    return countriesMap;
  }
}

export default AddressMetaDataLoader;
PK
!<c���$modules/shared/AddressParser.sys.mjs/* eslint-disable no-useless-concat */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// NamedCaptureGroup class represents a named capturing group in a regular expression
class NamedCaptureGroup {
  // The named of this capturing group
  #name = null;

  // The capturing group
  #capture = null;

  // The matched result
  #match = null;

  constructor(name, capture) {
    this.#name = name;
    this.#capture = capture;
  }

  get name() {
    return this.#name;
  }

  get capture() {
    return this.#capture;
  }

  get match() {
    return this.#match;
  }

  // Setter for the matched result based on the match groups
  setMatch(matchGroups) {
    this.#match = matchGroups[this.#name];
  }
}

// Base class for different part of a street address regular expression.
// The regular expression is constructed with prefix, pattern, suffix
// and separator to extract "value" part.
// For examplem, when we write "apt 4." to for floor number, its prefix is `apt`,
// suffix is `.` and value to represent apartment number is `4`.
class StreetAddressPartRegExp extends NamedCaptureGroup {
  constructor(name, prefix, pattern, suffix, sep, optional = false) {
    prefix = prefix ?? "";
    suffix = suffix ?? "";
    super(
      name,
      `((?:${prefix})(?<${name}>${pattern})(?:${suffix})(?:${sep})+)${
        optional ? "?" : ""
      }`
    );
  }
}

// A regular expression to match the street number portion of a street address,
class StreetNumberRegExp extends StreetAddressPartRegExp {
  static PREFIX = "((no|°|º|number)(\\.|-|\\s)*)?"; // From chromium source

  static PATTERN = "\\d+\\w?";

  // TODO: possible suffix : (th\\.|\\.)?
  static SUFFIX = null;

  constructor(sep, optional) {
    super(
      StreetNumberRegExp.name,
      StreetNumberRegExp.PREFIX,
      StreetNumberRegExp.PATTERN,
      StreetNumberRegExp.SUFFIX,
      sep,
      optional
    );
  }
}

// A regular expression to match the street name portion of a street address,
class StreetNameRegExp extends StreetAddressPartRegExp {
  static PREFIX = null;

  static PATTERN = "(?:[^\\s,]+(?:[^\\S\\r\\n]+[^\\s,]+)*?)"; // From chromium source

  // TODO: Should we consider suffix like (ave|st)?
  static SUFFIX = null;

  constructor(sep, optional) {
    super(
      StreetNameRegExp.name,
      StreetNameRegExp.PREFIX,
      StreetNameRegExp.PATTERN,
      StreetNameRegExp.SUFFIX,
      sep,
      optional
    );
  }
}

// A regular expression to match the apartment number portion of a street address,
class ApartmentNumberRegExp extends StreetAddressPartRegExp {
  static keyword = "apt|apartment|wohnung|apto|-" + "|unit|suite|ste|#|room"; // From chromium source // Firefox specific
  static PREFIX = `(${ApartmentNumberRegExp.keyword})(\\.|\\s|-)*`;

  static PATTERN = "\\w*([-|\\/]\\w*)?";

  static SUFFIX = "(\\.|\\s|-)*(ª)?"; // From chromium source

  constructor(sep, optional) {
    super(
      ApartmentNumberRegExp.name,
      ApartmentNumberRegExp.PREFIX,
      ApartmentNumberRegExp.PATTERN,
      ApartmentNumberRegExp.SUFFIX,
      sep,
      optional
    );
  }
}

// A regular expression to match the floor number portion of a street address,
class FloorNumberRegExp extends StreetAddressPartRegExp {
  static keyword =
    "floor|flur|fl|og|obergeschoss|ug|untergeschoss|geschoss|andar|piso|º" + // From chromium source
    "|level|lvl"; // Firefox specific
  static PREFIX = `(${FloorNumberRegExp.keyword})?(\\.|\\s|-)*`; // TODO
  static PATTERN = "\\d{1,3}\\w?";
  static SUFFIX = `(st|nd|rd|th)?(\\.|\\s|-)*(${FloorNumberRegExp.keyword})?`; // TODO

  constructor(sep, optional) {
    super(
      FloorNumberRegExp.name,
      FloorNumberRegExp.PREFIX,
      FloorNumberRegExp.PATTERN,
      FloorNumberRegExp.SUFFIX,
      sep,
      optional
    );
  }
}

/**
 * Class represents a street address with the following fields:
 * - street number
 * - street name
 * - apartment number
 * - floor number
 */
export class StructuredStreetAddress {
  #street_number = null;
  #street_name = null;
  #apartment_number = null;
  #floor_number = null;

  constructor(street_number, street_name, apartment_number, floor_number) {
    this.#street_number = street_number?.toString();
    this.#street_name = street_name?.toString();
    this.#apartment_number = apartment_number?.toString();
    this.#floor_number = floor_number?.toString();
  }

  get street_number() {
    return this.#street_number;
  }

  get street_name() {
    return this.#street_name;
  }

  get apartment_number() {
    return this.#apartment_number;
  }

  get floor_number() {
    return this.#floor_number;
  }

  toString() {
    return `
      street number: ${this.#street_number}\n
      street name: ${this.#street_name}\n
      apartment number: ${this.#apartment_number}\n
      floor number: ${this.#floor_number}\n
    `;
  }
}

export class AddressParser {
  /**
   * Parse street address with the following pattern.
   * street number, street name, apartment number(optional), floor number(optional)
   * For example, 2 Harrison St #175 floor 2
   *
   * @param {string} address The street address to be parsed.
   * @returns {StructuredStreetAddress}
   */
  static parseStreetAddress(address) {
    if (!address) {
      return null;
    }

    const separator = "(\\s|,|$)";

    const regexpes = [
      new StreetNumberRegExp(separator),
      new StreetNameRegExp(separator),
      new ApartmentNumberRegExp(separator, true),
      new FloorNumberRegExp(separator, true),
    ];

    return AddressParser.parse(address, regexpes)
      ? new StructuredStreetAddress(...regexpes.map(regexp => regexp.match))
      : null;
  }

  static parse(address, regexpes) {
    const options = {
      trim: true,
      merge_whitespace: true,
      ignore_case: true,
    };
    address = AddressParser.normalizeString(address, options);

    const match = address.match(
      new RegExp(`^(${regexpes.map(regexp => regexp.capture).join("")})$`)
    );
    if (!match) {
      return null;
    }

    regexpes.forEach(regexp => regexp.setMatch(match.groups));
    return regexpes.reduce((acc, current) => {
      return { ...acc, [current.name]: current.match };
    }, {});
  }

  static normalizeString(s, options) {
    if (typeof s != "string") {
      return s;
    }

    if (options.ignore_case) {
      s = s.toLowerCase();
    }

    // process punctuation before whitespace because if a punctuation
    // is replaced with whitespace, we might want to merge it later
    if (options.remove_punctuation) {
      s = AddressParser.replacePunctuation(s, "");
    } else if ("replace_punctuation" in options) {
      const replace = options.replace_punctuation;
      s = AddressParser.replacePunctuation(s, replace);
    }

    // process whitespace
    if (options.merge_whitespace) {
      s = AddressParser.mergeWhitespace(s);
    } else if (options.remove_whitespace) {
      s = AddressParser.removeWhitespace(s);
    }

    return s.trim();
  }

  static replacePunctuation(s, replace) {
    const regex = /\p{Punctuation}/gu;
    return s?.replace(regex, replace);
  }

  static removePunctuation(s) {
    return s?.replace(/[.,\/#!$%\^&\*;:{}=\-_~()]/g, "");
  }

  static replaceControlCharacters(s) {
    return s?.replace(/[\t\n\r]/g, " ");
  }

  static removeWhitespace(s) {
    return s?.replace(/[\s]/g, "");
  }

  static mergeWhitespace(s) {
    return s?.replace(/\s{2,}/g, " ");
  }
}
PK
!<ϴhM	M	'modules/shared/CreditCardRecord.sys.mjs/* eslint-disable no-useless-concat */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { CreditCard } from "resource://gre/modules/CreditCard.sys.mjs";
import { FormAutofillNameUtils } from "resource://gre/modules/shared/FormAutofillNameUtils.sys.mjs";

/**
 * The CreditCardRecord class serves to handle and normalize internal credit card records.
 * Unlike the CreditCard class, which represents actual card data, CreditCardRecord is used
 * for processing and consistent data representation.
 */
export class CreditCardRecord {
  static normalizeFields(creditCard) {
    this.#normalizeCCNameFields(creditCard);
    this.#normalizeCCNumberFields(creditCard);
    this.#normalizeCCExpirationDateFields(creditCard);
    this.#normalizeCCTypeFields(creditCard);
  }

  static #normalizeCCNameFields(creditCard) {
    if (!creditCard["cc-name"]) {
      creditCard["cc-name"] = FormAutofillNameUtils.joinNameParts({
        given: creditCard["cc-given-name"] ?? "",
        middle: creditCard["cc-additional-name"] ?? "",
        family: creditCard["cc-family-name"] ?? "",
      });
    }

    delete creditCard["cc-given-name"];
    delete creditCard["cc-additional-name"];
    delete creditCard["cc-family-name"];
  }

  static #normalizeCCNumberFields(creditCard) {
    if (!("cc-number" in creditCard)) {
      return;
    }

    if (!CreditCard.isValidNumber(creditCard["cc-number"])) {
      delete creditCard["cc-number"];
      return;
    }

    const card = new CreditCard({ number: creditCard["cc-number"] });
    creditCard["cc-number"] = card.number;
  }

  static #normalizeCCExpirationDateFields(creditCard) {
    let normalizedExpiration = CreditCard.normalizeExpiration({
      expirationMonth: creditCard["cc-exp-month"],
      expirationYear: creditCard["cc-exp-year"],
      expirationString: creditCard["cc-exp"],
    });

    creditCard["cc-exp-month"] = normalizedExpiration.month ?? "";
    creditCard["cc-exp-year"] = normalizedExpiration.year ?? "";
    delete creditCard["cc-exp"];
  }

  static #normalizeCCTypeFields(creditCard) {
    // Let's overwrite the credit card type with auto-detect algorithm
    creditCard["cc-type"] = CreditCard.getType(creditCard["cc-number"]) ?? "";
  }
}
PK
!<�[x�@�@�(modules/shared/CreditCardRuleset.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Fathom ML model for identifying the fields of credit-card forms
 *
 * This is developed out-of-tree at https://github.com/mozilla-services/fathom-
 * form-autofill, where there is also over a GB of training, validation, and
 * testing data. To make changes, do your edits there (whether adding new
 * training pages, adding new rules, or both), retrain and evaluate as
 * documented at https://mozilla.github.io/fathom/training.html, paste the
 * coefficients emitted by the trainer into the ruleset, and finally copy the
 * ruleset's "CODE TO COPY INTO PRODUCTION" section to this file's "CODE FROM
 * TRAINING REPOSITORY" section.
 */

/**
 * CODE UNIQUE TO PRODUCTION--NOT IN THE TRAINING REPOSITORY:
 */

import {
  element as clickedElement,
  out,
  rule,
  ruleset,
  score,
  type,
} from "resource://gre/modules/third_party/fathom/fathom.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { FormAutofillUtils } from "resource://gre/modules/shared/FormAutofillUtils.sys.mjs";
import {
  CreditCard,
  NETWORK_NAMES,
} from "resource://gre/modules/CreditCard.sys.mjs";

import { FormLikeFactory } from "resource://gre/modules/FormLikeFactory.sys.mjs";
import { LabelUtils } from "resource://gre/modules/shared/LabelUtils.sys.mjs";

/**
 * Callthrough abstraction to allow .getAutocompleteInfo() to be mocked out
 * during training
 *
 * @param {Element} element DOM element to get info about
 * @returns {object} Page-author-provided autocomplete metadata
 */
function getAutocompleteInfo(element) {
  return element.getAutocompleteInfo();
}

/**
 * @param {string} selector A CSS selector that prunes away ineligible elements
 * @returns {Lhs} An LHS yielding the element the user has clicked or, if
 *  pruned, none
 */
function queriedOrClickedElements(selector) {
  return clickedElement(selector);
}

/**
 * START OF CODE PASTED FROM TRAINING REPOSITORY
 */

var FathomHeuristicsRegExp = {
  RULES: {
    "cc-name": undefined,
    "cc-number": undefined,
    "cc-exp-month": undefined,
    "cc-exp-year": undefined,
    "cc-exp": undefined,
    "cc-type": undefined,
  },

  RULE_SETS: [
    {
      /* eslint-disable */
      // Let us keep our consistent wrapping.
      "cc-name":
        // Firefox-specific rules
        "account.*holder.*name" +
        "|^(credit[-\\s]?card|card).*name" +
        // de-DE
        "|^(kredit)?(karten|konto)inhaber" +
        "|^(name).*karte" +
        // fr-FR
        "|nom.*(titulaire|détenteur)" +
        "|(titulaire|détenteur).*(carte)" +
        // it-IT
        "|titolare.*carta" +
        // pl-PL
        "|posiadacz.*karty" +
        // es-ES
        "|nombre.*(titular|tarjeta)" +
        // nl-NL
        "|naam.*op.*kaart" +
        // Rules from Bitwarden
        "|cc-?name" +
        "|card-?name" +
        "|cardholder-?name" +
        "|(^nom$)" +
        // Rules are from Chromium source codes
        "|card.?(?:holder|owner)|name.*(\\b)?on(\\b)?.*card" +
        "|(?:card|cc).?name|cc.?full.?name" +
        "|(?:card|cc).?owner" +
        "|nom.*carte" + // fr-FR
        "|nome.*cart" + // it-IT
        "|名前" + // ja-JP
        "|Имя.*карты" + // ru
        "|信用卡开户名|开户名|持卡人姓名" + // zh-CN
        "|持卡人姓名", // zh-TW

      "cc-number":
        // Firefox-specific rules
        // de-DE
        "(cc|kk)nr" +
        "|(kredit)?(karten)(nummer|nr)" +
        // it-IT
        "|numero.*carta" +
        // fr-FR
        "|(numero|número|numéro).*(carte)" +
        // pl-PL
        "|numer.*karty" +
        // es-ES
        "|(número|numero).*tarjeta" +
        // nl-NL
        "|kaartnummer" +
        // Rules from Bitwarden
        "|cc-?number" +
        "|cc-?num" +
        "|card-?number" +
        "|card-?num" +
        "|cc-?no" +
        "|card-?no" +
        "|numero-?carte" +
        "|num-?carte" +
        "|cb-?num" +
        // Rules are from Chromium source codes
        "|(add)?(?:card|cc|acct).?(?:number|#|no|num)" +
        "|カード番号" + // ja-JP
        "|Номер.*карты" + // ru
        "|信用卡号|信用卡号码" + // zh-CN
        "|信用卡卡號" + // zh-TW
        "|카드", // ko-KR

      "cc-exp":
        // Firefox-specific rules
        "mm\\s*(\/|\\|-)\\s*(yy|jj|aa)" +
        "|(month|mois)\\s*(\/|\\|-|et)\\s*(year|année)" +
        // de-DE
        // fr-FR
        // Rules from Bitwarden
        "|(^cc-?exp$)" +
        "|(^card-?exp$)" +
        "|(^cc-?expiration$)" +
        "|(^card-?expiration$)" +
        "|(^cc-?ex$)" +
        "|(^card-?ex$)" +
        "|(^card-?expire$)" +
        "|(^card-?expiry$)" +
        "|(^validite$)" +
        "|(^expiration$)" +
        "|(^expiry$)" +
        "|mm-?yy" +
        "|mm-?yyyy" +
        "|yy-?mm" +
        "|yyyy-?mm" +
        "|expiration-?date" +
        "|payment-?card-?expiration" +
        "|(^payment-?cc-?date$)" +
        // Rules are from Chromium source codes
        "|expir|exp.*date|^expfield$" +
        "|ablaufdatum|gueltig|gültig" + // de-DE
        "|fecha" + // es
        "|date.*exp" + // fr-FR
        "|scadenza" + // it-IT
        "|有効期限" + // ja-JP
        "|validade" + // pt-BR, pt-PT
        "|Срок действия карты", // ru

      "cc-exp-month":
        // Firefox-specific rules
        "(cc|kk)month" + // de-DE
        // Rules from Bitwarden
        "|(^exp-?month$)" +
        "|(^cc-?exp-?month$)" +
        "|(^cc-?month$)" +
        "|(^card-?month$)" +
        "|(^cc-?mo$)" +
        "|(^card-?mo$)" +
        "|(^exp-?mo$)" +
        "|(^card-?exp-?mo$)" +
        "|(^cc-?exp-?mo$)" +
        "|(^card-?expiration-?month$)" +
        "|(^expiration-?month$)" +
        "|(^cc-?mm$)" +
        "|(^cc-?m$)" +
        "|(^card-?mm$)" +
        "|(^card-?m$)" +
        "|(^card-?exp-?mm$)" +
        "|(^cc-?exp-?mm$)" +
        "|(^exp-?mm$)" +
        "|(^exp-?m$)" +
        "|(^expire-?month$)" +
        "|(^expire-?mo$)" +
        "|(^expiry-?month$)" +
        "|(^expiry-?mo$)" +
        "|(^card-?expire-?month$)" +
        "|(^card-?expire-?mo$)" +
        "|(^card-?expiry-?month$)" +
        "|(^card-?expiry-?mo$)" +
        "|(^mois-?validite$)" +
        "|(^mois-?expiration$)" +
        "|(^m-?validite$)" +
        "|(^m-?expiration$)" +
        "|(^expiry-?date-?field-?month$)" +
        "|(^expiration-?date-?month$)" +
        "|(^expiration-?date-?mm$)" +
        "|(^exp-?mon$)" +
        "|(^validity-?mo$)" +
        "|(^exp-?date-?mo$)" +
        "|(^cb-?date-?mois$)" +
        "|(^date-?m$)" +
        // Rules are from Chromium source codes
        "|exp.*mo|ccmonth|cardmonth|addmonth" +
        "|monat" + // de-DE
        // "|fecha" + // es
        // "|date.*exp" + // fr-FR
        // "|scadenza" + // it-IT
        // "|有効期限" + // ja-JP
        // "|validade" + // pt-BR, pt-PT
        // "|Срок действия карты" + // ru
        "|月", // zh-CN

      "cc-exp-year":
        // Firefox-specific rules
        "(cc|kk)year" + // de-DE
        // Rules from Bitwarden
        "|(^exp-?year$)" +
        "|(^cc-?exp-?year$)" +
        "|(^cc-?year$)" +
        "|(^card-?year$)" +
        "|(^cc-?yr$)" +
        "|(^card-?yr$)" +
        "|(^exp-?yr$)" +
        "|(^card-?exp-?yr$)" +
        "|(^cc-?exp-?yr$)" +
        "|(^card-?expiration-?year$)" +
        "|(^expiration-?year$)" +
        "|(^cc-?yy$)" +
        "|(^cc-?y$)" +
        "|(^card-?yy$)" +
        "|(^card-?y$)" +
        "|(^card-?exp-?yy$)" +
        "|(^cc-?exp-?yy$)" +
        "|(^exp-?yy$)" +
        "|(^exp-?y$)" +
        "|(^cc-?yyyy$)" +
        "|(^card-?yyyy$)" +
        "|(^card-?exp-?yyyy$)" +
        "|(^cc-?exp-?yyyy$)" +
        "|(^expire-?year$)" +
        "|(^expire-?yr$)" +
        "|(^expiry-?year$)" +
        "|(^expiry-?yr$)" +
        "|(^card-?expire-?year$)" +
        "|(^card-?expire-?yr$)" +
        "|(^card-?expiry-?year$)" +
        "|(^card-?expiry-?yr$)" +
        "|(^an-?validite$)" +
        "|(^an-?expiration$)" +
        "|(^annee-?validite$)" +
        "|(^annee-?expiration$)" +
        "|(^expiry-?date-?field-?year$)" +
        "|(^expiration-?date-?year$)" +
        "|(^cb-?date-?ann$)" +
        "|(^expiration-?date-?yy$)" +
        "|(^expiration-?date-?yyyy$)" +
        "|(^validity-?year$)" +
        "|(^exp-?date-?year$)" +
        "|(^date-?y$)" +
        // Rules are from Chromium source codes
        "|(add)?year" +
        "|jahr" + // de-DE
        // "|fecha" + // es
        // "|scadenza" + // it-IT
        // "|有効期限" + // ja-JP
        // "|validade" + // pt-BR, pt-PT
        // "|Срок действия карты" + // ru
        "|年|有效期", // zh-CN

      "cc-type":
        // Firefox-specific rules
        "type" +
        // de-DE
        "|Kartenmarke" +
        // Rules from Bitwarden
        "|(^cc-?type$)" +
        "|(^card-?type$)" +
        "|(^card-?brand$)" +
        "|(^cc-?brand$)" +
        "|(^cb-?type$)",
        // Rules are from Chromium source codes
    },
  ],

  _getRule(name) {
    let rules = [];
    this.RULE_SETS.forEach(set => {
      if (set[name]) {
        rules.push(`(${set[name]})`.normalize("NFKC"));
      }
    });

    const value = new RegExp(rules.join("|"), "iu");
    Object.defineProperty(this.RULES, name, { get: undefined });
    Object.defineProperty(this.RULES, name, { value });
    return value;
  },

  init() {
    Object.keys(this.RULES).forEach(field =>
      Object.defineProperty(this.RULES, field, {
        get() {
          return FathomHeuristicsRegExp._getRule(field);
        },
      })
    );
  },
};

FathomHeuristicsRegExp.init();

const MMRegExp = /^mm$|\(mm\)/i;
const YYorYYYYRegExp = /^(yy|yyyy)$|\(yy\)|\(yyyy\)/i;
const monthRegExp = /month/i;
const yearRegExp = /year/i;
const MMYYRegExp = /mm\s*(\/|\\)\s*yy/i;
const VisaCheckoutRegExp = /visa(-|\s)checkout/i;
const CREDIT_CARD_NETWORK_REGEXP = new RegExp(
  CreditCard.getSupportedNetworks()
    .concat(Object.keys(NETWORK_NAMES))
    .join("|"),
  "gui"
  );
const TwoDigitYearRegExp = /(?:exp.*date[^y\\n\\r]*|mm\\s*[-/]?\\s*)yy(?:[^y]|$)/i;
const FourDigitYearRegExp = /(?:exp.*date[^y\\n\\r]*|mm\\s*[-/]?\\s*)yyyy(?:[^y]|$)/i;
const dwfrmRegExp = /^dwfrm/i;
const bmlRegExp = /bml/i;
const templatedValue = /^\{\{.*\}\}$/;
const firstRegExp = /first/i;
const lastRegExp = /last/i;
const giftRegExp = /gift/i;
const subscriptionRegExp = /subscription/i;

function autocompleteStringMatches(element, ccString) {
  const info = getAutocompleteInfo(element);
  return info.fieldName === ccString;
}

function getFillableFormElements(element) {
  const formLike = FormLikeFactory.createFromField(element);
  return Array.from(formLike.elements).filter(el =>
    FormAutofillUtils.isCreditCardOrAddressFieldType(el)
  );
}

function nextFillableFormField(element) {
  const fillableFormElements = getFillableFormElements(element);
  const elementIndex = fillableFormElements.indexOf(element);
  return fillableFormElements[elementIndex + 1];
}

function previousFillableFormField(element) {
  const fillableFormElements = getFillableFormElements(element);
  const elementIndex = fillableFormElements.indexOf(element);
  return fillableFormElements[elementIndex - 1];
}

function nextFieldPredicateIsTrue(element, predicate) {
  const nextField = nextFillableFormField(element);
  return !!nextField && predicate(nextField);
}

function previousFieldPredicateIsTrue(element, predicate) {
  const previousField = previousFillableFormField(element);
  return !!previousField && predicate(previousField);
}

function nextFieldMatchesExpYearAutocomplete(fnode) {
  return nextFieldPredicateIsTrue(fnode.element, nextField =>
    autocompleteStringMatches(nextField, "cc-exp-year")
  );
}

function previousFieldMatchesExpMonthAutocomplete(fnode) {
  return previousFieldPredicateIsTrue(fnode.element, previousField =>
    autocompleteStringMatches(previousField, "cc-exp-month")
  );
}

//////////////////////////////////////////////
// Attribute Regular Expression Rules
function idOrNameMatchRegExp(element, regExp) {
  for (const str of [element.id, element.name]) {
    if (regExp.test(str)) {
      return true;
    }
  }
  return false;
}

function getElementLabels(element) {
  return {
    *[Symbol.iterator]() {
      const labels = LabelUtils.findLabelElements(element);
      for (let label of labels) {
        yield* LabelUtils.extractLabelStrings(label);
      }
    },
  };
}

function labelsMatchRegExp(element, regExp) {
  const elemStrings = getElementLabels(element);
  for (const str of elemStrings) {
    if (regExp.test(str)) {
      return true;
    }
  }

  const parentElement = element.parentElement;
  // Bug 1634819: element.parentElement is null if element.parentNode is a ShadowRoot
  if (!parentElement) {
    return false;
  }
  // Check if the input is in a <td>, and, if so, check the textContent of the containing <tr>
  if (parentElement.tagName === "TD" && parentElement.parentElement) {
    // TODO: How bad is the assumption that the <tr> won't be the parent of the <td>?
    return regExp.test(parentElement.parentElement.textContent);
  }

  // Check if the input is in a <dd>, and, if so, check the textContent of the preceding <dt>
  if (
    parentElement.tagName === "DD" &&
    // previousElementSibling can be null
    parentElement.previousElementSibling
  ) {
    return regExp.test(parentElement.previousElementSibling.textContent);
  }
  return false;
}

function closestLabelMatchesRegExp(element, regExp) {
  const previousElementSibling = element.previousElementSibling;
  if (
    previousElementSibling !== null &&
    previousElementSibling.tagName === "LABEL"
  ) {
    return regExp.test(previousElementSibling.textContent);
  }

  const nextElementSibling = element.nextElementSibling;
  if (nextElementSibling !== null && nextElementSibling.tagName === "LABEL") {
    return regExp.test(nextElementSibling.textContent);
  }

  return false;
}

function ariaLabelMatchesRegExp(element, regExp) {
  const ariaLabel = element.getAttribute("aria-label");
  return !!ariaLabel && regExp.test(ariaLabel);
}

function placeholderMatchesRegExp(element, regExp) {
  const placeholder = element.getAttribute("placeholder");
  return !!placeholder && regExp.test(placeholder);
}

function nextFieldIdOrNameMatchRegExp(element, regExp) {
  return nextFieldPredicateIsTrue(element, nextField =>
    idOrNameMatchRegExp(nextField, regExp)
  );
}

function nextFieldLabelsMatchRegExp(element, regExp) {
  return nextFieldPredicateIsTrue(element, nextField =>
    labelsMatchRegExp(nextField, regExp)
  );
}

function nextFieldPlaceholderMatchesRegExp(element, regExp) {
  return nextFieldPredicateIsTrue(element, nextField =>
    placeholderMatchesRegExp(nextField, regExp)
  );
}

function nextFieldAriaLabelMatchesRegExp(element, regExp) {
  return nextFieldPredicateIsTrue(element, nextField =>
    ariaLabelMatchesRegExp(nextField, regExp)
  );
}

function previousFieldIdOrNameMatchRegExp(element, regExp) {
  return previousFieldPredicateIsTrue(element, previousField =>
    idOrNameMatchRegExp(previousField, regExp)
  );
}

function previousFieldLabelsMatchRegExp(element, regExp) {
  return previousFieldPredicateIsTrue(element, previousField =>
    labelsMatchRegExp(previousField, regExp)
  );
}

function previousFieldPlaceholderMatchesRegExp(element, regExp) {
  return previousFieldPredicateIsTrue(element, previousField =>
    placeholderMatchesRegExp(previousField, regExp)
  );
}

function previousFieldAriaLabelMatchesRegExp(element, regExp) {
  return previousFieldPredicateIsTrue(element, previousField =>
    ariaLabelMatchesRegExp(previousField, regExp)
  );
}
//////////////////////////////////////////////

function isSelectWithCreditCardOptions(fnode) {
  // Check every select for options that match credit card network names in
  // value or label.
  const element = fnode.element;
  if (element.tagName === "SELECT") {
    for (let option of element.querySelectorAll("option")) {
      if (
        CreditCard.getNetworkFromName(option.value) ||
        CreditCard.getNetworkFromName(option.text)
      ) {
        return true;
      }
    }
  }
  return false;
}

/**
 * If any of the regular expressions match multiple times, we assume the tested
 * string belongs to a radio button for payment type instead of card type.
 *
 * @param {Fnode} fnode
 * @returns {boolean}
 */
function isRadioWithCreditCardText(fnode) {
  const element = fnode.element;
  const inputType = element.type;
  if (!!inputType && inputType === "radio") {
    const valueMatches = element.value.match(CREDIT_CARD_NETWORK_REGEXP);
    if (valueMatches) {
      return valueMatches.length === 1;
    }

    // Here we are checking that only one label matches only one entry in the regular expression.
    const labels = getElementLabels(element);
    let labelsMatched = 0;
    for (const label of labels) {
      const labelMatches = label.match(CREDIT_CARD_NETWORK_REGEXP);
      if (labelMatches) {
        if (labelMatches.length > 1) {
          return false;
        }
        labelsMatched++;
      }
    }
    if (labelsMatched > 0) {
      return labelsMatched === 1;
    }

    const textContentMatches = element.textContent.match(
      CREDIT_CARD_NETWORK_REGEXP
    );
    if (textContentMatches) {
      return textContentMatches.length === 1;
    }
  }
  return false;
}

function matchContiguousSubArray(array, subArray) {
  return array.some((elm, i) =>
    subArray.every((sElem, j) => sElem === array[i + j])
  );
}

function isExpirationMonthLikely(element) {
  if (element.tagName !== "SELECT") {
    return false;
  }

  const options = [...element.options];
  const desiredValues = Array(12)
    .fill(1)
    .map((v, i) => v + i);

  // The number of month options shouldn't be less than 12 or larger than 13
  // including the default option.
  if (options.length < 12 || options.length > 13) {
    return false;
  }

  return (
    matchContiguousSubArray(
      options.map(e => +e.value),
      desiredValues
    ) ||
    matchContiguousSubArray(
      options.map(e => +e.label),
      desiredValues
    )
  );
}

function isExpirationYearLikely(element) {
  if (element.tagName !== "SELECT") {
    return false;
  }

  const options = [...element.options];
  // A normal expiration year select should contain at least the last three years
  // in the list.
  const curYear = new Date().getFullYear();
  const desiredValues = Array(3)
    .fill(0)
    .map((v, i) => v + curYear + i);

  return (
    matchContiguousSubArray(
      options.map(e => +e.value),
      desiredValues
    ) ||
    matchContiguousSubArray(
      options.map(e => +e.label),
      desiredValues
    )
  );
}

function nextFieldIsExpirationYearLikely(fnode) {
  return nextFieldPredicateIsTrue(fnode.element, isExpirationYearLikely);
}

function previousFieldIsExpirationMonthLikely(fnode) {
  return previousFieldPredicateIsTrue(fnode.element, isExpirationMonthLikely);
}

function attrsMatchExpWith2Or4DigitYear(fnode, regExpMatchingFunction) {
  const element = fnode.element;
  return (
    regExpMatchingFunction(element, TwoDigitYearRegExp) ||
    regExpMatchingFunction(element, FourDigitYearRegExp)
  );
}

function maxLengthIs(fnode, maxLengthValue) {
  return fnode.element.maxLength === maxLengthValue;
}

function roleIsMenu(fnode) {
  const role = fnode.element.getAttribute("role");
  return !!role && role === "menu";
}

function idOrNameMatchDwfrmAndBml(fnode) {
  return (
    idOrNameMatchRegExp(fnode.element, dwfrmRegExp) &&
    idOrNameMatchRegExp(fnode.element, bmlRegExp)
  );
}

function hasTemplatedValue(fnode) {
  const value = fnode.element.getAttribute("value");
  return !!value && templatedValue.test(value);
}

function inputTypeNotNumbery(fnode) {
  const inputType = fnode.element.type;
  if (inputType) {
    return !["text", "tel", "number"].includes(inputType);
  }
  return false;
}

function idOrNameMatchFirstAndLast(fnode) {
  return (
    idOrNameMatchRegExp(fnode.element, firstRegExp) &&
    idOrNameMatchRegExp(fnode.element, lastRegExp)
  );
}

/**
 * Compactly generate a series of rules that all take a single LHS type with no
 * .when() clause and have only a score() call on the right- hand side.
 *
 * @param {Lhs} inType The incoming fnode type that all rules take
 * @param {object} ruleMap A simple object used as a map with rule names
 *   pointing to scoring callbacks
 * @yields {Rule}
 */
function* simpleScoringRules(inType, ruleMap) {
  for (const [name, scoringCallback] of Object.entries(ruleMap)) {
    yield rule(type(inType), score(scoringCallback), { name });
  }
}

function makeRuleset(coeffs, biases) {
  return ruleset(
    [
      /**
       * Factor out the page scan just for a little more speed during training.
       * This selector is good for most fields. cardType is an exception: it
       * cannot be type=month.
       */
      rule(
        queriedOrClickedElements(
          "input:not([type]), input[type=text], input[type=textbox], input[type=email], input[type=tel], input[type=number], input[type=month], select, button"
        ),
        type("typicalCandidates")
      ),

      /**
       * number rules
       */
      rule(type("typicalCandidates"), type("cc-number")),
      ...simpleScoringRules("cc-number", {
        idOrNameMatchNumberRegExp: fnode =>
          idOrNameMatchRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-number"]
          ),
        labelsMatchNumberRegExp: fnode =>
          labelsMatchRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-number"]),
        closestLabelMatchesNumberRegExp: fnode =>
          closestLabelMatchesRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-number"]),
        placeholderMatchesNumberRegExp: fnode =>
          placeholderMatchesRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-number"]
          ),
        ariaLabelMatchesNumberRegExp: fnode =>
          ariaLabelMatchesRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-number"]
          ),
        idOrNameMatchGift: fnode =>
          idOrNameMatchRegExp(fnode.element, giftRegExp),
        labelsMatchGift: fnode => labelsMatchRegExp(fnode.element, giftRegExp),
        placeholderMatchesGift: fnode =>
          placeholderMatchesRegExp(fnode.element, giftRegExp),
        ariaLabelMatchesGift: fnode =>
          ariaLabelMatchesRegExp(fnode.element, giftRegExp),
        idOrNameMatchSubscription: fnode =>
          idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
        idOrNameMatchDwfrmAndBml,
        hasTemplatedValue,
        inputTypeNotNumbery,
      }),
      rule(type("cc-number"), out("cc-number")),

      /**
       * name rules
       */
      rule(type("typicalCandidates"), type("cc-name")),
      ...simpleScoringRules("cc-name", {
        idOrNameMatchNameRegExp: fnode =>
          idOrNameMatchRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-name"]),
        labelsMatchNameRegExp: fnode =>
          labelsMatchRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-name"]),
        closestLabelMatchesNameRegExp: fnode =>
          closestLabelMatchesRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-name"]),
        placeholderMatchesNameRegExp: fnode =>
          placeholderMatchesRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-name"]
          ),
        ariaLabelMatchesNameRegExp: fnode =>
          ariaLabelMatchesRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-name"]
          ),
        idOrNameMatchFirst: fnode =>
          idOrNameMatchRegExp(fnode.element, firstRegExp),
        labelsMatchFirst: fnode =>
          labelsMatchRegExp(fnode.element, firstRegExp),
        placeholderMatchesFirst: fnode =>
          placeholderMatchesRegExp(fnode.element, firstRegExp),
        ariaLabelMatchesFirst: fnode =>
          ariaLabelMatchesRegExp(fnode.element, firstRegExp),
        idOrNameMatchLast: fnode =>
          idOrNameMatchRegExp(fnode.element, lastRegExp),
        labelsMatchLast: fnode => labelsMatchRegExp(fnode.element, lastRegExp),
        placeholderMatchesLast: fnode =>
          placeholderMatchesRegExp(fnode.element, lastRegExp),
        ariaLabelMatchesLast: fnode =>
          ariaLabelMatchesRegExp(fnode.element, lastRegExp),
        idOrNameMatchSubscription: fnode =>
          idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
        idOrNameMatchFirstAndLast,
        idOrNameMatchDwfrmAndBml,
        hasTemplatedValue,
      }),
      rule(type("cc-name"), out("cc-name")),

      /**
       * cardType rules
       */
      rule(
        queriedOrClickedElements(
          "input:not([type]), input[type=text], input[type=textbox], input[type=email], input[type=tel], input[type=number], input[type=radio], select, button"
        ),
        type("cc-type")
      ),
      ...simpleScoringRules("cc-type", {
        idOrNameMatchTypeRegExp: fnode =>
          idOrNameMatchRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-type"]),
        labelsMatchTypeRegExp: fnode =>
          labelsMatchRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-type"]),
        closestLabelMatchesTypeRegExp: fnode =>
          closestLabelMatchesRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-type"]),
        idOrNameMatchVisaCheckout: fnode =>
          idOrNameMatchRegExp(fnode.element, VisaCheckoutRegExp),
        ariaLabelMatchesVisaCheckout: fnode =>
          ariaLabelMatchesRegExp(fnode.element, VisaCheckoutRegExp),
        isSelectWithCreditCardOptions,
        isRadioWithCreditCardText,
        idOrNameMatchSubscription: fnode =>
          idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
        idOrNameMatchDwfrmAndBml,
        hasTemplatedValue,
      }),
      rule(type("cc-type"), out("cc-type")),

      /**
       * expiration rules
       */
      rule(type("typicalCandidates"), type("cc-exp")),
      ...simpleScoringRules("cc-exp", {
        labelsMatchExpRegExp: fnode =>
          labelsMatchRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-exp"]),
        closestLabelMatchesExpRegExp: fnode =>
          closestLabelMatchesRegExp(fnode.element, FathomHeuristicsRegExp.RULES["cc-exp"]),
        placeholderMatchesExpRegExp: fnode =>
          placeholderMatchesRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp"]
          ),
        labelsMatchExpWith2Or4DigitYear: fnode =>
          attrsMatchExpWith2Or4DigitYear(fnode, labelsMatchRegExp),
        placeholderMatchesExpWith2Or4DigitYear: fnode =>
          attrsMatchExpWith2Or4DigitYear(fnode, placeholderMatchesRegExp),
        labelsMatchMMYY: fnode => labelsMatchRegExp(fnode.element, MMYYRegExp),
        placeholderMatchesMMYY: fnode =>
          placeholderMatchesRegExp(fnode.element, MMYYRegExp),
        maxLengthIs7: fnode => maxLengthIs(fnode, 7),
        idOrNameMatchSubscription: fnode =>
          idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
        idOrNameMatchDwfrmAndBml,
        hasTemplatedValue,
        isExpirationMonthLikely: fnode =>
          isExpirationMonthLikely(fnode.element),
        isExpirationYearLikely: fnode => isExpirationYearLikely(fnode.element),
        idOrNameMatchMonth: fnode =>
          idOrNameMatchRegExp(fnode.element, monthRegExp),
        idOrNameMatchYear: fnode =>
          idOrNameMatchRegExp(fnode.element, yearRegExp),
        idOrNameMatchExpMonthRegExp: fnode =>
          idOrNameMatchRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-month"]
          ),
        idOrNameMatchExpYearRegExp: fnode =>
          idOrNameMatchRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-year"]
          ),
        idOrNameMatchValidation: fnode =>
          idOrNameMatchRegExp(fnode.element, /validate|validation/i),
      }),
      rule(type("cc-exp"), out("cc-exp")),

      /**
       * expirationMonth rules
       */
      rule(type("typicalCandidates"), type("cc-exp-month")),
      ...simpleScoringRules("cc-exp-month", {
        idOrNameMatchExpMonthRegExp: fnode =>
          idOrNameMatchRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-month"]
          ),
        labelsMatchExpMonthRegExp: fnode =>
          labelsMatchRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-month"]
          ),
        closestLabelMatchesExpMonthRegExp: fnode =>
          closestLabelMatchesRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-month"]
          ),
        placeholderMatchesExpMonthRegExp: fnode =>
          placeholderMatchesRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-month"]
          ),
        ariaLabelMatchesExpMonthRegExp: fnode =>
          ariaLabelMatchesRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-month"]
          ),
        idOrNameMatchMonth: fnode =>
          idOrNameMatchRegExp(fnode.element, monthRegExp),
        labelsMatchMonth: fnode =>
          labelsMatchRegExp(fnode.element, monthRegExp),
        placeholderMatchesMonth: fnode =>
          placeholderMatchesRegExp(fnode.element, monthRegExp),
        ariaLabelMatchesMonth: fnode =>
          ariaLabelMatchesRegExp(fnode.element, monthRegExp),
        nextFieldIdOrNameMatchExpYearRegExp: fnode =>
          nextFieldIdOrNameMatchRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-year"]
          ),
        nextFieldLabelsMatchExpYearRegExp: fnode =>
          nextFieldLabelsMatchRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-year"]
          ),
        nextFieldPlaceholderMatchExpYearRegExp: fnode =>
          nextFieldPlaceholderMatchesRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-year"]
          ),
        nextFieldAriaLabelMatchExpYearRegExp: fnode =>
          nextFieldAriaLabelMatchesRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-year"]
          ),
        nextFieldIdOrNameMatchYear: fnode =>
          nextFieldIdOrNameMatchRegExp(fnode.element, yearRegExp),
        nextFieldLabelsMatchYear: fnode =>
          nextFieldLabelsMatchRegExp(fnode.element, yearRegExp),
        nextFieldPlaceholderMatchesYear: fnode =>
          nextFieldPlaceholderMatchesRegExp(fnode.element, yearRegExp),
        nextFieldAriaLabelMatchesYear: fnode =>
          nextFieldAriaLabelMatchesRegExp(fnode.element, yearRegExp),
        nextFieldMatchesExpYearAutocomplete,
        isExpirationMonthLikely: fnode =>
          isExpirationMonthLikely(fnode.element),
        nextFieldIsExpirationYearLikely,
        maxLengthIs2: fnode => maxLengthIs(fnode, 2),
        placeholderMatchesMM: fnode =>
          placeholderMatchesRegExp(fnode.element, MMRegExp),
        roleIsMenu,
        idOrNameMatchSubscription: fnode =>
          idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
        idOrNameMatchDwfrmAndBml,
        hasTemplatedValue,
      }),
      rule(type("cc-exp-month"), out("cc-exp-month")),

      /**
       * expirationYear rules
       */
      rule(type("typicalCandidates"), type("cc-exp-year")),
      ...simpleScoringRules("cc-exp-year", {
        idOrNameMatchExpYearRegExp: fnode =>
          idOrNameMatchRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-year"]
          ),
        labelsMatchExpYearRegExp: fnode =>
          labelsMatchRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-year"]
          ),
        closestLabelMatchesExpYearRegExp: fnode =>
          closestLabelMatchesRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-year"]
          ),
        placeholderMatchesExpYearRegExp: fnode =>
          placeholderMatchesRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-year"]
          ),
        ariaLabelMatchesExpYearRegExp: fnode =>
          ariaLabelMatchesRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-year"]
          ),
        idOrNameMatchYear: fnode =>
          idOrNameMatchRegExp(fnode.element, yearRegExp),
        labelsMatchYear: fnode => labelsMatchRegExp(fnode.element, yearRegExp),
        placeholderMatchesYear: fnode =>
          placeholderMatchesRegExp(fnode.element, yearRegExp),
        ariaLabelMatchesYear: fnode =>
          ariaLabelMatchesRegExp(fnode.element, yearRegExp),
        previousFieldIdOrNameMatchExpMonthRegExp: fnode =>
          previousFieldIdOrNameMatchRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-month"]
          ),
        previousFieldLabelsMatchExpMonthRegExp: fnode =>
          previousFieldLabelsMatchRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-month"]
          ),
        previousFieldPlaceholderMatchExpMonthRegExp: fnode =>
          previousFieldPlaceholderMatchesRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-month"]
          ),
        previousFieldAriaLabelMatchExpMonthRegExp: fnode =>
          previousFieldAriaLabelMatchesRegExp(
            fnode.element,
            FathomHeuristicsRegExp.RULES["cc-exp-month"]
          ),
        previousFieldIdOrNameMatchMonth: fnode =>
          previousFieldIdOrNameMatchRegExp(fnode.element, monthRegExp),
        previousFieldLabelsMatchMonth: fnode =>
          previousFieldLabelsMatchRegExp(fnode.element, monthRegExp),
        previousFieldPlaceholderMatchesMonth: fnode =>
          previousFieldPlaceholderMatchesRegExp(fnode.element, monthRegExp),
        previousFieldAriaLabelMatchesMonth: fnode =>
          previousFieldAriaLabelMatchesRegExp(fnode.element, monthRegExp),
        previousFieldMatchesExpMonthAutocomplete,
        isExpirationYearLikely: fnode => isExpirationYearLikely(fnode.element),
        previousFieldIsExpirationMonthLikely,
        placeholderMatchesYYOrYYYY: fnode =>
          placeholderMatchesRegExp(fnode.element, YYorYYYYRegExp),
        roleIsMenu,
        idOrNameMatchSubscription: fnode =>
          idOrNameMatchRegExp(fnode.element, subscriptionRegExp),
        idOrNameMatchDwfrmAndBml,
        hasTemplatedValue,
      }),
      rule(type("cc-exp-year"), out("cc-exp-year")),
    ],
    coeffs,
    biases
  );
}

const coefficients = {
  "cc-number": [
    ["idOrNameMatchNumberRegExp", 7.679469585418701],
    ["labelsMatchNumberRegExp", 5.122580051422119],
    ["closestLabelMatchesNumberRegExp", 2.1256935596466064],
    ["placeholderMatchesNumberRegExp", 9.471800804138184],
    ["ariaLabelMatchesNumberRegExp", 6.067715644836426],
    ["idOrNameMatchGift", -22.946273803710938],
    ["labelsMatchGift", -7.852959632873535],
    ["placeholderMatchesGift", -2.355496406555176],
    ["ariaLabelMatchesGift", -2.940307855606079],
    ["idOrNameMatchSubscription", 0.11255314946174622],
    ["idOrNameMatchDwfrmAndBml", -0.0006645023822784424],
    ["hasTemplatedValue", -0.11370040476322174],
    ["inputTypeNotNumbery", -3.750155210494995]
  ],
  "cc-name": [
    ["idOrNameMatchNameRegExp", 7.496212959289551],
    ["labelsMatchNameRegExp", 6.081472873687744],
    ["closestLabelMatchesNameRegExp", 2.600574254989624],
    ["placeholderMatchesNameRegExp", 5.750874042510986],
    ["ariaLabelMatchesNameRegExp", 5.162227153778076],
    ["idOrNameMatchFirst", -6.742659091949463],
    ["labelsMatchFirst", -0.5234538912773132],
    ["placeholderMatchesFirst", -3.4615235328674316],
    ["ariaLabelMatchesFirst", -1.3145145177841187],
    ["idOrNameMatchLast", -12.561869621276855],
    ["labelsMatchLast", -0.27417105436325073],
    ["placeholderMatchesLast", -1.434966802597046],
    ["ariaLabelMatchesLast", -2.9319725036621094],
    ["idOrNameMatchFirstAndLast", 24.123435974121094],
    ["idOrNameMatchSubscription", 0.08349418640136719],
    ["idOrNameMatchDwfrmAndBml", 0.01882520318031311],
    ["hasTemplatedValue", 0.182317852973938]
  ],
  "cc-type": [
    ["idOrNameMatchTypeRegExp", 2.0581533908843994],
    ["labelsMatchTypeRegExp", 1.0784518718719482],
    ["closestLabelMatchesTypeRegExp", 0.6995877623558044],
    ["idOrNameMatchVisaCheckout", -3.320356845855713],
    ["ariaLabelMatchesVisaCheckout", -3.4196767807006836],
    ["isSelectWithCreditCardOptions", 10.337477684020996],
    ["isRadioWithCreditCardText", 4.530318737030029],
    ["idOrNameMatchSubscription", -3.7206356525421143],
    ["idOrNameMatchDwfrmAndBml", -0.08782318234443665],
    ["hasTemplatedValue", 0.1772511601448059]
  ],
  "cc-exp": [
    ["labelsMatchExpRegExp", 7.588159561157227],
    ["closestLabelMatchesExpRegExp", 1.41484534740448],
    ["placeholderMatchesExpRegExp", 8.759064674377441],
    ["labelsMatchExpWith2Or4DigitYear", -3.876218795776367],
    ["placeholderMatchesExpWith2Or4DigitYear", 2.8364884853363037],
    ["labelsMatchMMYY", 8.836017608642578],
    ["placeholderMatchesMMYY", -0.5231751799583435],
    ["maxLengthIs7", 1.3565447330474854],
    ["idOrNameMatchSubscription", 0.1779913753271103],
    ["idOrNameMatchDwfrmAndBml", 0.21037884056568146],
    ["hasTemplatedValue", 0.14900512993335724],
    ["isExpirationMonthLikely", -3.223409652709961],
    ["isExpirationYearLikely", -2.536919593811035],
    ["idOrNameMatchMonth", -3.6893014907836914],
    ["idOrNameMatchYear", -3.108184337615967],
    ["idOrNameMatchExpMonthRegExp", -2.264357089996338],
    ["idOrNameMatchExpYearRegExp", -2.7957723140716553],
    ["idOrNameMatchValidation", -2.29402756690979]
  ],
  "cc-exp-month": [
    ["idOrNameMatchExpMonthRegExp", 0.2787344455718994],
    ["labelsMatchExpMonthRegExp", 1.298413634300232],
    ["closestLabelMatchesExpMonthRegExp", -11.206244468688965],
    ["placeholderMatchesExpMonthRegExp", 1.2605619430541992],
    ["ariaLabelMatchesExpMonthRegExp", 1.1330018043518066],
    ["idOrNameMatchMonth", 6.1464314460754395],
    ["labelsMatchMonth", 0.7051732540130615],
    ["placeholderMatchesMonth", 0.7463492751121521],
    ["ariaLabelMatchesMonth", 1.8244760036468506],
    ["nextFieldIdOrNameMatchExpYearRegExp", 0.06347066164016724],
    ["nextFieldLabelsMatchExpYearRegExp", -0.1692247837781906],
    ["nextFieldPlaceholderMatchExpYearRegExp", 1.0434566736221313],
    ["nextFieldAriaLabelMatchExpYearRegExp", 1.751156210899353],
    ["nextFieldIdOrNameMatchYear", -0.532447338104248],
    ["nextFieldLabelsMatchYear", 1.3248541355133057],
    ["nextFieldPlaceholderMatchesYear", 0.604235827922821],
    ["nextFieldAriaLabelMatchesYear", 1.5364223718643188],
    ["nextFieldMatchesExpYearAutocomplete", 6.285938262939453],
    ["isExpirationMonthLikely", 13.117807388305664],
    ["nextFieldIsExpirationYearLikely", 7.182341575622559],
    ["maxLengthIs2", 4.477289199829102],
    ["placeholderMatchesMM", 14.403288841247559],
    ["roleIsMenu", 5.770959854125977],
    ["idOrNameMatchSubscription", -0.043085768818855286],
    ["idOrNameMatchDwfrmAndBml", 0.02823038399219513],
    ["hasTemplatedValue", 0.07234494388103485]
  ],
  "cc-exp-year": [
    ["idOrNameMatchExpYearRegExp", 5.426016807556152],
    ["labelsMatchExpYearRegExp", 1.3240209817886353],
    ["closestLabelMatchesExpYearRegExp", -8.702284812927246],
    ["placeholderMatchesExpYearRegExp", 0.9059725999832153],
    ["ariaLabelMatchesExpYearRegExp", 0.5550334453582764],
    ["idOrNameMatchYear", 5.362994194030762],
    ["labelsMatchYear", 2.7185044288635254],
    ["placeholderMatchesYear", 0.7883157134056091],
    ["ariaLabelMatchesYear", 0.311492383480072],
    ["previousFieldIdOrNameMatchExpMonthRegExp", 1.8155208826065063],
    ["previousFieldLabelsMatchExpMonthRegExp", -0.46133187413215637],
    ["previousFieldPlaceholderMatchExpMonthRegExp", 1.0374903678894043],
    ["previousFieldAriaLabelMatchExpMonthRegExp", -0.5901495814323425],
    ["previousFieldIdOrNameMatchMonth", -5.960310935974121],
    ["previousFieldLabelsMatchMonth", 0.6495584845542908],
    ["previousFieldPlaceholderMatchesMonth", 0.7198042273521423],
    ["previousFieldAriaLabelMatchesMonth", 3.4590985774993896],
    ["previousFieldMatchesExpMonthAutocomplete", 2.986003875732422],
    ["isExpirationYearLikely", 4.021566390991211],
    ["previousFieldIsExpirationMonthLikely", 9.298635482788086],
    ["placeholderMatchesYYOrYYYY", 10.457176208496094],
    ["roleIsMenu", 1.1051956415176392],
    ["idOrNameMatchSubscription", 0.000688597559928894],
    ["idOrNameMatchDwfrmAndBml", 0.15687309205532074],
    ["hasTemplatedValue", -0.19141331315040588]
  ],
};

const biases = [
  ["cc-number", -4.948795795440674],
  ["cc-name", -5.3578081130981445],
  ["cc-type", -5.979659557342529],
  ["cc-exp", -5.849575996398926],
  ["cc-exp-month", -8.844199180603027],
  ["cc-exp-year", -6.499860763549805],
];

/**
 * END OF CODE PASTED FROM TRAINING REPOSITORY
 */

/**
 * MORE CODE UNIQUE TO PRODUCTION--NOT IN THE TRAINING REPOSITORY:
 */
// Currently there is a bug when a ruleset has multple types (ex, cc-name, cc-number)
// and those types also has the same rules (ex. rule `hasTemplatedValue` is used in
// all the tyoes). When the above case exists, the coefficient of the rule will be
// overwritten, which means, we can't have different coefficient for the same rule on
// different types. To workaround this issue, we create a new ruleset for each type.
export var CreditCardRulesets = {
  init() {
    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "supportedTypes",
      "extensions.formautofill.creditCards.heuristics.fathom.types",
      null,
      null,
      val => val.split(",")
    );

    for (const type of this.types) {
      this[type] = makeRuleset([...coefficients[type]], biases);
    }
  },

  get types() {
    return this.supportedTypes;
  },
};

CreditCardRulesets.init();

export default CreditCardRulesets;
PK
!<���?T"T"#modules/shared/FieldScanner.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
});

/**
 * Represents the detailed information about a form field, including
 * the inferred field name, the approach used for inferring, and additional metadata.
 */
export class FieldDetail {
  // Reference to the elemenet
  elementWeakRef = null;

  // The identifier generated via ContentDOMReference for the associated DOM element
  // of this field
  elementId = null;

  // The identifier generated via ContentDOMReference for the root element of
  // this field
  rootElementId = null;

  // string with `${element.id}/{element.name}`. This is only used for debugging.
  identifier = "";

  // tag name attribute of the element
  tagName = null;

  // The inferred field name for this element.
  fieldName = null;

  // The approach we use to infer the information for this element
  // The possible values are "autocomplete", "fathom", and "regex-heuristic"
  reason = null;

  /*
   * The "section", "addressType", and "contactType" values are
   * used to identify the exact field when the serializable data is received
   * from the backend.  There cannot be multiple fields which have
   * the same exact combination of these values.
   */

  // Which section the field belongs to. The value comes from autocomplete attribute.
  // See https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#autofill-detail-tokens for more details
  section = "";
  addressType = "";
  contactType = "";
  credentialType = "";

  // When a field is split into N fields, we use part to record which field it is
  // For example, a credit card number field is split into 4 fields, the value of
  // "part" for the first cc-number field is 1, for the last one is 4.
  // If the field is not split, the value is null
  part = null;

  // Confidence value when the field name is inferred by "fathom"
  confidence = null;

  constructor(
    element,
    form,
    fieldName = null,
    { autocompleteInfo = {}, confidence = null } = {}
  ) {
    this.elementWeakRef = new WeakRef(element);
    this.elementId = lazy.FormAutofillUtils.getElementIdentifier(element);
    this.rootElementId = lazy.FormAutofillUtils.getElementIdentifier(
      form.rootElement
    );
    this.identifier = `${element.id}/${element.name}`;
    this.tagName = element.tagName;
    this.fieldName = fieldName;

    if (autocompleteInfo) {
      this.reason = "autocomplete";
      this.section = autocompleteInfo.section;
      this.addressType = autocompleteInfo.addressType;
      this.contactType = autocompleteInfo.contactType;
      this.credentialType = autocompleteInfo.credentialType;
      this.sectionName = this.section || this.addressType;
    } else if (confidence) {
      this.reason = "fathom";
      this.confidence = confidence;

      // TODO: This should be removed once we support reference field info across iframe.
      // Temporarily add an addtional "the field is the only visible input" constraint
      // when determining whether a form has only a high-confidence cc-* field a valid
      // credit card section. We can remove this restriction once we are confident
      // about only using fathom.
      this.isOnlyVisibleFieldWithHighConfidence = false;
      if (
        this.confidence > lazy.FormAutofillUtils.ccFathomHighConfidenceThreshold
      ) {
        const root = element.form || element.ownerDocument;
        const inputs = root.querySelectorAll("input:not([type=hidden])");
        if (inputs.length == 1 && inputs[0] == element) {
          this.isOnlyVisibleFieldWithHighConfidence = true;
        }
      }
    } else {
      this.reason = "regex-heuristic";
    }

    this.isVisible = lazy.FormAutofillUtils.isFieldVisible(this.element);
  }

  get element() {
    return this.elementWeakRef.deref();
  }

  /**
   * Convert FieldDetail class to an object that is suitable for
   * sending over IPC. Avoid using this in other case.
   */
  toVanillaObject() {
    const json = { ...this };
    delete json.elementWeakRef;
    return json;
  }
}

/**
 * A scanner for traversing all elements in a form. It also provides a
 * cursor (parsingIndex) to indicate which element is waiting for parsing.
 *
 * The scanner retrives the field detail by calling heuristics handlers
 * `inferFieldInfo` function.
 */
export class FieldScanner {
  #form = null;
  #elementsWeakRef = null;
  #inferFieldInfoFn = null;

  #parsingIndex = 0;

  fieldDetails = [];

  /**
   * Create a FieldScanner based on form elements with the existing
   * fieldDetails.
   *
   * @param {FormLike} form
   * @param {Funcion} inferFieldInfoFn
   *        The callback function that is used to infer the field info of a given element
   */
  constructor(form, inferFieldInfoFn) {
    const elements = Array.from(form.elements).filter(element =>
      lazy.FormAutofillUtils.isCreditCardOrAddressFieldType(element)
    );

    this.#form = form;
    this.#elementsWeakRef = new WeakRef(elements);
    this.#inferFieldInfoFn = inferFieldInfoFn;
  }

  get #elements() {
    return this.#elementsWeakRef.deref();
  }

  /**
   * This cursor means the index of the element which is waiting for parsing.
   *
   * @returns {number}
   *          The index of the element which is waiting for parsing.
   */
  get parsingIndex() {
    return this.#parsingIndex;
  }

  get parsingFinished() {
    return this.parsingIndex >= this.#elements.length;
  }

  /**
   * Move the parsingIndex to the next elements. Any elements behind this index
   * means the parsing tasks are finished.
   *
   * @param {number} index
   *        The latest index of elements waiting for parsing.
   */
  set parsingIndex(index) {
    if (index > this.#elements.length) {
      throw new Error("The parsing index is out of range.");
    }
    this.#parsingIndex = index;
  }

  /**
   * Retrieve the field detail by the index. If the field detail is not ready,
   * the elements will be traversed until matching the index.
   *
   * @param {number} index
   *        The index of the element that you want to retrieve.
   * @returns {object}
   *          The field detail at the specific index.
   */
  getFieldDetailByIndex(index) {
    if (index >= this.#elements.length) {
      return null;
    }

    if (index < this.fieldDetails.length) {
      return this.fieldDetails[index];
    }

    for (let i = this.fieldDetails.length; i < index + 1; i++) {
      this.pushDetail();
    }

    return this.fieldDetails[index];
  }

  /**
   * This function retrieves the first unparsed element and obtains its
   * information by invoking the `inferFieldInfoFn` callback function.
   * The field information is then stored in a FieldDetail object and
   * appended to the `fieldDetails` array.
   *
   * Any element without the related detail will be used for adding the detail
   * to the end of field details.
   */
  pushDetail() {
    const elementIndex = this.fieldDetails.length;
    if (elementIndex >= this.#elements.length) {
      throw new Error("Try to push the non-existing element info.");
    }
    const element = this.#elements[elementIndex];
    const [fieldName, autocompleteInfo, confidence] = this.#inferFieldInfoFn(
      element,
      this.#elements
    );
    const fieldDetail = new FieldDetail(element, this.#form, fieldName, {
      autocompleteInfo,
      confidence,
    });

    this.fieldDetails.push(fieldDetail);
  }

  /**
   * When a field detail should be changed its fieldName after parsing, use
   * this function to update the fieldName which is at a specific index.
   *
   * @param {number} index
   *        The index indicates a field detail to be updated.
   * @param {string} fieldName
   *        The new name of the field
   * @param {boolean} [ignoreAutocomplete=false]
   *        Whether to change the field name when the field name is determined by
   *        autocomplete attribute
   */
  updateFieldName(index, fieldName, ignoreAutocomplete = false) {
    if (index >= this.fieldDetails.length) {
      throw new Error("Try to update the non-existing field detail.");
    }

    const fieldDetail = this.fieldDetails[index];
    if (fieldDetail.fieldName == fieldName) {
      return;
    }

    if (!ignoreAutocomplete && fieldDetail.reason == "autocomplete") {
      return;
    }

    this.fieldDetails[index].fieldName = fieldName;
    this.fieldDetails[index].reason = "update-heuristic";
  }

  elementExisting(index) {
    return index < this.#elements.length;
  }
}

export default FieldScanner;
PK
!<
�n}|}|*modules/shared/FormAutofillHandler.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs";
import { FormAutofillUtils } from "resource://gre/modules/shared/FormAutofillUtils.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  CreditCard: "resource://gre/modules/CreditCard.sys.mjs",
  FormAutofillHeuristics:
    "resource://gre/modules/shared/FormAutofillHeuristics.sys.mjs",
  FormAutofillNameUtils:
    "resource://gre/modules/shared/FormAutofillNameUtils.sys.mjs",
  FormLikeFactory: "resource://gre/modules/FormLikeFactory.sys.mjs",
  LabelUtils: "resource://gre/modules/shared/LabelUtils.sys.mjs",
});

const { FIELD_STATES } = FormAutofillUtils;

/**
 * Handles profile autofill for a DOM Form element.
 */
export class FormAutofillHandler {
  // The window to which this form belongs
  window = null;

  // DOM Form element to which this object is attached
  form = null;

  // Keeps track of filled state for all identified elements
  #filledStateByElement = new WeakMap();

  // An object that caches the current selected option, keyed by element.
  #matchingSelectOption = null;

  /**
   * Array of collected data about relevant form fields.  Each item is an object
   * storing the identifying details of the field and a reference to the
   * originally associated element from the form.
   *
   * The "section", "addressType", "contactType", and "fieldName" values are
   * used to identify the exact field when the serializable data is received
   * from the backend.  There cannot be multiple fields which have
   * the same exact combination of these values.
   *
   * A direct reference to the associated element cannot be sent to the user
   * interface because processing may be done in the parent process.
   */
  fieldDetails = [];

  /**
   * Initialize the form from `FormLike` object to handle the section or form
   * operations.
   *
   * @param {FormLike} form Form that need to be auto filled
   * @param {Function} onFilledModifiedCallback Function that can be invoked
   *                   when we want to suggest autofill on a form.
   */
  constructor(form, onFilledModifiedCallback = () => {}) {
    this._updateForm(form);

    this.window = this.form.rootElement.ownerGlobal;

    this.onFilledModifiedCallback = onFilledModifiedCallback;

    // The identifier generated via ContentDOMReference for the root element.
    this.rootElementId = FormAutofillUtils.getElementIdentifier(
      form.rootElement
    );

    ChromeUtils.defineLazyGetter(this, "log", () =>
      FormAutofill.defineLogGetter(this, "FormAutofillHandler")
    );
  }

  handleEvent(event) {
    switch (event.type) {
      case "input": {
        if (!event.isTrusted) {
          return;
        }

        // This uses the #filledStateByElement map instead of
        // autofillState as the state has already been cleared by the time
        // the input event fires.
        const fieldDetail = this.getFieldDetailByElement(event.target);
        const previousState = this.getFilledStateByElement(event.target);
        const newState = FIELD_STATES.NORMAL;

        if (previousState != newState) {
          this.changeFieldState(fieldDetail, newState);
        }

        this.onFilledModifiedCallback?.(fieldDetail, previousState, newState);
      }
    }
  }

  getFieldDetailByName(fieldName) {
    return this.fieldDetails.find(detail => detail.fieldName == fieldName);
  }

  getFieldDetailByNamePreferVisible(fieldName) {
    const fieldDetail = this.fieldDetails.find(
      detail => detail.fieldName == fieldName && detail.isVisible
    );
    return fieldDetail || this.getFieldDetailByName(fieldName);
  }

  getFieldDetailByElement(element) {
    return this.fieldDetails.find(detail => detail.element == element);
  }

  getFieldDetailByElementId(elementId) {
    return this.fieldDetails.find(detail => detail.elementId == elementId);
  }

  /**
   * Only use this API within handleEvent
   */
  getFilledStateByElement(element) {
    return this.#filledStateByElement.get(element);
  }

  /**
   * Check the form is necessary to be updated. This function should be able to
   * detect any changes including all control elements in the form.
   *
   * @param {HTMLElement} element The element supposed to be in the form.
   * @returns {boolean} FormAutofillHandler.form is updated or not.
   */
  updateFormIfNeeded(element) {
    // When the following condition happens, FormAutofillHandler.form should be
    // updated:
    // * The count of form controls is changed.
    // * When the element can not be found in the current form.
    //
    // However, we should improve the function to detect the element changes.
    // e.g. a tel field is changed from type="hidden" to type="tel".

    let _formLike;
    const getFormLike = () => {
      if (!_formLike) {
        _formLike = lazy.FormLikeFactory.createFromField(element);
      }
      return _formLike;
    };

    const currentForm = element.form ?? getFormLike();
    if (currentForm.elements.length != this.form.elements.length) {
      this.log.debug("The count of form elements is changed.");
      this._updateForm(getFormLike());
      return true;
    }

    if (!this.form.elements.includes(element)) {
      this.log.debug("The element can not be found in the current form.");
      this._updateForm(getFormLike());
      return true;
    }

    return false;
  }

  /**
   * Update the form with a new FormLike, and the related fields should be
   * updated or clear to ensure the data consistency.
   *
   * @param {FormLike} form a new FormLike to replace the original one.
   */
  _updateForm(form) {
    this.form = form;

    this.fieldDetails = [];
  }

  /**
   * Set fieldDetails from the form about fields that can be autofilled.
   *
   * @returns {Array} The valid address and credit card details.
   */
  collectFormFields() {
    const fields = lazy.FormAutofillHeuristics.getFormInfo(this.form) ?? [];
    this.fieldDetails = fields.filter(field => !!field.fieldName);
    return this.fieldDetails;
  }

  /**
   * Change the state of a field to correspond with different presentations.
   *
   * @param {object} fieldDetail
   *        A fieldDetail of which its element is about to update the state.
   * @param {string} state
   *        The state to apply.
   */
  changeFieldState(fieldDetail, state) {
    const element = fieldDetail.element;
    if (!element) {
      this.log.warn(
        fieldDetail.fieldName,
        "is unreachable while changing state"
      );
      return;
    }

    if (!Object.values(FIELD_STATES).includes(state)) {
      this.log.warn(
        fieldDetail.fieldName,
        "is trying to change to an invalid state"
      );
      return;
    }

    element.autofillState = state;
    this.#filledStateByElement.set(element, state);

    if (state == FIELD_STATES.AUTO_FILLED) {
      element.addEventListener("input", this, { mozSystemGroup: true });
    }
  }

  /**
   * Populates result to the preview layers with given profile.
   *
   * @param {Array} elementIds
   * @param {object} profile
   *        A profile to be previewed with
   */
  previewFields(elementIds, profile) {
    this.getAdaptedProfiles([profile]);

    for (const fieldDetail of this.fieldDetails) {
      const element = fieldDetail.element;

      // Skip the field if it is null or readonly or disabled
      if (
        !elementIds.includes(fieldDetail.elementId) ||
        !FormAutofillUtils.isFieldAutofillable(element)
      ) {
        continue;
      }

      let value = this.getFilledValueFromProfile(fieldDetail, profile);
      if (!value) {
        this.changeFieldState(fieldDetail, FIELD_STATES.NORMAL);
        continue;
      }

      if (HTMLInputElement.isInstance(element)) {
        if (element.value && element.value != element.defaultValue) {
          // Skip the field if the user has already entered text and that text
          // is not the site prefilled value.
          continue;
        }
      } else if (HTMLSelectElement.isInstance(element)) {
        // Unlike text input, select element is always previewed even if
        // the option is already selected.
        if (value) {
          const cache = this.#matchingSelectOption.get(element) ?? {};
          const option = cache[value]?.deref();
          value = option?.text ?? "";
        }
      } else {
        continue;
      }

      element.previewValue = value?.toString().replaceAll("*", "•");
      this.changeFieldState(fieldDetail, FIELD_STATES.PREVIEW);
    }
  }

  /**
   * Processes form fields that can be autofilled, and populates them with the
   * profile provided by backend.
   *
   * @param {string} focusedElementId
   * @param {Array} elementIds
   * @param {object} profile
   *        A profile to be filled in.
   */
  async fillFields(focusedElementId, elementIds, profile) {
    this.getAdaptedProfiles([profile]);

    for (const fieldDetail of this.fieldDetails) {
      const { element, elementId } = fieldDetail;

      if (
        !elementIds.includes(elementId) ||
        !FormAutofillUtils.isFieldAutofillable(element)
      ) {
        continue;
      }

      element.previewValue = "";
      // Bug 1687679: Since profile appears to be presentation ready data, we need to utilize the "x-formatted" field
      // that is generated when presentation ready data doesn't fit into the autofilling element.
      // For example, autofilling expiration month into an input element will not work as expected if
      // the month is less than 10, since the input is expected a zero-padded string.
      // See Bug 1722941 for follow up.
      const value = this.getFilledValueFromProfile(fieldDetail, profile);
      if (!value) {
        continue;
      }

      if (HTMLInputElement.isInstance(element)) {
        // For the focused input element, it will be filled with a valid value
        // anyway.
        // For the others, the fields should be only filled when their values are empty
        // or their values are equal to the site prefill value
        // or are the result of an earlier auto-fill.
        if (
          elementId == focusedElementId ||
          (elementId != focusedElementId &&
            (!element.value || element.value == element.defaultValue)) ||
          element.autofillState == FIELD_STATES.AUTO_FILLED
        ) {
          this.fillFieldValue(element, value);
          this.changeFieldState(fieldDetail, FIELD_STATES.AUTO_FILLED);
        }
      } else if (HTMLSelectElement.isInstance(element)) {
        let cache = this.#matchingSelectOption.get(element) || {};
        let option = cache[value] && cache[value].deref();
        if (!option) {
          continue;
        }
        // Do not change value or dispatch events if the option is already selected.
        // Use case for multiple select is not considered here.
        if (!option.selected) {
          option.selected = true;
          this.fillFieldValue(element, option.value);
        }
        // Autofill highlight appears regardless if value is changed or not
        this.changeFieldState(fieldDetail, FIELD_STATES.AUTO_FILLED);
      } else {
        continue;
      }
    }

    FormAutofillUtils.getElementByIdentifier(focusedElementId)?.focus({
      preventScroll: true,
    });

    this.registerFormChangeHandler();
  }

  registerFormChangeHandler() {
    if (this.onChangeHandler) {
      return;
    }

    this.log.debug("register change handler for filled form:", this.form);

    this.onChangeHandler = e => {
      if (!e.isTrusted) {
        return;
      }
      if (e.type == "reset") {
        for (const fieldDetail of this.fieldDetails) {
          const element = fieldDetail.element;
          element.removeEventListener("input", this, { mozSystemGroup: true });
          this.changeFieldState(fieldDetail, FIELD_STATES.NORMAL);
        }
      }

      // Unregister listeners once no field is in AUTO_FILLED state.
      if (
        this.fieldDetails.every(
          detail => detail.element.autofillState != FIELD_STATES.AUTO_FILLED
        )
      ) {
        this.form.rootElement.removeEventListener(
          "input",
          this.onChangeHandler,
          {
            mozSystemGroup: true,
          }
        );
        this.form.rootElement.removeEventListener(
          "reset",
          this.onChangeHandler,
          {
            mozSystemGroup: true,
          }
        );
        this.onChangeHandler = null;
      }
    };

    // Handle the highlight style resetting caused by user's correction afterward.
    this.log.debug("register change handler for filled form:", this.form);
    this.form.rootElement.addEventListener("input", this.onChangeHandler, {
      mozSystemGroup: true,
    });
    this.form.rootElement.addEventListener("reset", this.onChangeHandler, {
      mozSystemGroup: true,
    });
  }

  computeFillingValue(fieldDetail) {
    const element = fieldDetail.element;
    if (!element) {
      return null;
    }

    let value = element.value.trim();
    switch (fieldDetail.fieldName) {
      case "address-level1":
        if (HTMLSelectElement.isInstance(element)) {
          // Don't save the record when the option value is empty *OR* there
          // are multiple options being selected. The empty option is usually
          // assumed to be default along with a meaningless text to users.
          if (!value || element.selectedOptions.length != 1) {
            // Keep the property and preserve more information for address updating
            value = "";
          } else {
            const text = element.selectedOptions[0].text.trim();
            value =
              FormAutofillUtils.getAbbreviatedSubregionName([value, text]) ||
              text;
          }
        }
        break;
      case "country":
        // This is a temporary fix. Ideally we should have either case-insensitive comparison of country codes
        // or handle this elsewhere see Bug 1889234 for more context.
        value = value.toUpperCase();
        break;
      case "cc-type":
        if (
          HTMLSelectElement.isInstance(element) &&
          !lazy.CreditCard.isValidNetwork(value)
        ) {
          // Don't save the record when the option value is empty *OR* there
          // are multiple options being selected. The empty option is usually
          // assumed to be default along with a meaningless text to users.
          if (value && element.selectedOptions.length == 1) {
            const selectedOption = element.selectedOptions[0];
            const networkType =
              lazy.CreditCard.getNetworkFromName(selectedOption.text) ??
              lazy.CreditCard.getNetworkFromName(selectedOption.value);
            if (networkType) {
              value = networkType;
            }
          }
        }
        break;
    }

    return value;
  }

  /*
   * Apply both address and credit card related transformers.
   *
   * @param {Object} profile
   *        A profile for adjusting credit card related value.
   * @override
   */
  applyTransformers(profile) {
    // The matchSelectOptions transformer must be placed after the expiry transformers.
    // This ensures that the expiry value that is cached in the matchSelectOptions
    // matches the expiry value that is stored in the profile ensuring that autofill works
    // correctly when dealing with option elements.
    this.addressTransformer(profile);
    this.telTransformer(profile);
    this.creditCardExpiryDateTransformer(profile);
    this.creditCardExpMonthAndYearTransformer(profile);
    this.creditCardNameTransformer(profile);
    this.matchSelectOptions(profile);
    this.adaptFieldMaxLength(profile);
  }

  getAdaptedProfiles(originalProfiles) {
    for (let profile of originalProfiles) {
      this.applyTransformers(profile);
    }
    return originalProfiles;
  }

  // This is only used by test cases
  get matchingSelectOption() {
    return this.#matchingSelectOption;
  }

  matchSelectOptions(profile) {
    if (!this.#matchingSelectOption) {
      this.#matchingSelectOption = new WeakMap();
    }

    for (const fieldName in profile) {
      const fieldDetail = this.getFieldDetailByNamePreferVisible(fieldName);
      const element = fieldDetail?.element;

      if (!HTMLSelectElement.isInstance(element)) {
        continue;
      }

      const cache = this.#matchingSelectOption.get(element) || {};
      const value = profile[fieldName];
      if (cache[value] && cache[value].deref()) {
        continue;
      }

      const option = FormAutofillUtils.findSelectOption(
        element,
        profile,
        fieldName
      );

      if (option) {
        cache[value] = new WeakRef(option);
        this.#matchingSelectOption.set(element, cache);
      } else {
        if (cache[value]) {
          delete cache[value];
          this.#matchingSelectOption.set(element, cache);
        }
        // Skip removing cc-type since this is needed for displaying the icon for credit card network
        // TODO(Bug 1874339): Cleanup transformation and normalization of data to not remove any
        // fields and be more consistent
        if (!["cc-type"].includes(fieldName)) {
          // Delete the field so the phishing hint won't treat it as a "also fill"
          // field.
          delete profile[fieldName];
        }
      }
    }
  }

  adaptFieldMaxLength(profile) {
    for (let key in profile) {
      let detail = this.getFieldDetailByName(key);
      if (!detail || detail.part) {
        continue;
      }

      let element = detail.element;
      if (!element) {
        continue;
      }

      let maxLength = element.maxLength;
      if (
        maxLength === undefined ||
        maxLength < 0 ||
        profile[key].toString().length <= maxLength
      ) {
        continue;
      }

      if (maxLength) {
        switch (typeof profile[key]) {
          case "string":
            // If this is an expiration field and our previous
            // adaptations haven't resulted in a string that is
            // short enough to satisfy the field length, and the
            // field is constrained to a length of 4 or 5, then we
            // assume it is intended to hold an expiration of the
            // form "MMYY" or "MM/YY".
            if (key == "cc-exp" && (maxLength == 4 || maxLength == 5)) {
              const month2Digits = (
                "0" + profile["cc-exp-month"].toString()
              ).slice(-2);
              const year2Digits = profile["cc-exp-year"].toString().slice(-2);
              const separator = maxLength == 5 ? "/" : "";
              profile[key] = `${month2Digits}${separator}${year2Digits}`;
            } else if (key == "cc-number") {
              // We want to show the last four digits of credit card so that
              // the masked credit card previews correctly and appears correctly
              // in the autocomplete menu
              profile[key] = profile[key].substr(
                profile[key].length - maxLength
              );
            } else {
              profile[key] = profile[key].substr(0, maxLength);
            }
            break;
          case "number":
            // There's no way to truncate a number smaller than a
            // single digit.
            if (maxLength < 1) {
              maxLength = 1;
            }
            // The only numbers we store are expiration month/year,
            // and if they truncate, we want the final digits, not
            // the initial ones.
            profile[key] = profile[key] % Math.pow(10, maxLength);
            break;
          default:
        }
      } else {
        delete profile[key];
        delete profile[`${key}-formatted`];
      }
    }
  }

  /**
   * Handles credit card expiry date transformation when
   * the expiry date exists in a cc-exp field.
   *
   * @param {object} profile
   */
  creditCardExpiryDateTransformer(profile) {
    if (!profile["cc-exp"]) {
      return;
    }

    const element = this.getFieldDetailByName("cc-exp")?.element;
    if (!element) {
      return;
    }

    function updateExpiry(_string, _month, _year) {
      // Bug 1687681: This is a short term fix to other locales having
      // different characters to represent year.
      // - FR locales may use "A" to represent year.
      // - DE locales may use "J" to represent year.
      // - PL locales may use "R" to represent year.
      // This approach will not scale well and should be investigated in a follow up bug.
      const monthChars = "m";
      const yearChars = "yy|aa|jj|rr";
      const expiryDateFormatRegex = (firstChars, secondChars) =>
        new RegExp(
          "(?:\\b|^)((?:[" +
            firstChars +
            "]{2}){1,2})\\s*([\\-/])\\s*((?:[" +
            secondChars +
            "]{2}){1,2})(?:\\b|$)",
          "i"
        );

      // If the month first check finds a result, where placeholder is "mm - yyyy",
      // the result will be structured as such: ["mm - yyyy", "mm", "-", "yyyy"]
      let result = expiryDateFormatRegex(monthChars, yearChars).exec(_string);
      if (result) {
        return (
          _month.padStart(result[1].length, "0") +
          result[2] +
          _year.substr(-1 * result[3].length)
        );
      }

      // If the year first check finds a result, where placeholder is "yyyy mm",
      // the result will be structured as such: ["yyyy mm", "yyyy", " ", "mm"]
      result = expiryDateFormatRegex(yearChars, monthChars).exec(_string);
      if (result) {
        return (
          _year.substr(-1 * result[1].length) +
          result[2] +
          _month.padStart(result[3].length, "0")
        );
      }
      return null;
    }

    let newExpiryString = null;
    const month = profile["cc-exp-month"].toString();
    const year = profile["cc-exp-year"].toString();
    if (element.tagName == "INPUT") {
      // Use the placeholder or label to determine the expiry string format.
      const possibleExpiryStrings = [];
      if (element.placeholder) {
        possibleExpiryStrings.push(element.placeholder);
      }
      const labels = lazy.LabelUtils.findLabelElements(element);
      if (labels) {
        // Not consider multiple lable for now.
        possibleExpiryStrings.push(element.labels[0]?.textContent);
      }
      if (element.previousElementSibling?.tagName == "LABEL") {
        possibleExpiryStrings.push(element.previousElementSibling.textContent);
      }

      possibleExpiryStrings.some(string => {
        newExpiryString = updateExpiry(string, month, year);
        return !!newExpiryString;
      });
    }

    // Bug 1688576: Change YYYY-MM to MM/YYYY since MM/YYYY is the
    // preferred presentation format for credit card expiry dates.
    profile["cc-exp"] = newExpiryString ?? `${month.padStart(2, "0")}/${year}`;
  }

  /**
   * Handles credit card expiry date transformation when the expiry date exists in
   * the separate cc-exp-month and cc-exp-year fields
   *
   * @param {object} profile
   */
  creditCardExpMonthAndYearTransformer(profile) {
    const getInputElementByField = (field, self) => {
      if (!field) {
        return null;
      }
      const detail = self.getFieldDetailByName(field);
      if (!detail) {
        return null;
      }
      const element = detail.element;
      return element.tagName === "INPUT" ? element : null;
    };
    const month = getInputElementByField("cc-exp-month", this);
    if (month) {
      // Transform the expiry month to MM since this is a common format needed for filling.
      profile["cc-exp-month-formatted"] = profile["cc-exp-month"]
        ?.toString()
        .padStart(2, "0");
    }
    const year = getInputElementByField("cc-exp-year", this);
    // If the expiration year element is an input,
    // then we examine any placeholder to see if we should format the expiration year
    // as a zero padded string in order to autofill correctly.
    if (year) {
      const placeholder = year.placeholder;

      // Checks for 'YY'|'AA'|'JJ'|'RR' placeholder and converts the year to a two digit string using the last two digits.
      const result = /\b(yy|aa|jj|rr)\b/i.test(placeholder);
      if (result) {
        profile["cc-exp-year-formatted"] = profile["cc-exp-year"]
          ?.toString()
          .substring(2);
      }
    }
  }

  /**
   * Handles credit card name transformation when the name exists in
   * the separate cc-given-name, cc-middle-name, and cc-family name fields
   *
   * @param {object} profile
   */
  creditCardNameTransformer(profile) {
    const name = profile["cc-name"];
    if (!name) {
      return;
    }

    const given = this.getFieldDetailByName("cc-given-name");
    const middle = this.getFieldDetailByName("cc-middle-name");
    const family = this.getFieldDetailByName("cc-family-name");
    if (given || middle || family) {
      const nameParts = lazy.FormAutofillNameUtils.splitName(name);
      if (given && nameParts.given) {
        profile["cc-given-name"] = nameParts.given;
      }
      if (middle && nameParts.middle) {
        profile["cc-middle-name"] = nameParts.middle;
      }
      if (family && nameParts.family) {
        profile["cc-family-name"] = nameParts.family;
      }
    }
  }

  addressTransformer(profile) {
    if (profile["street-address"]) {
      // "-moz-street-address-one-line" is used by the labels in
      // ProfileAutoCompleteResult.
      profile["-moz-street-address-one-line"] =
        FormAutofillUtils.toOneLineAddress(profile["street-address"]);
      let streetAddressDetail = this.getFieldDetailByName("street-address");
      if (
        streetAddressDetail &&
        HTMLInputElement.isInstance(streetAddressDetail.element)
      ) {
        profile["street-address"] = profile["-moz-street-address-one-line"];
      }

      let waitForConcat = [];
      for (let f of ["address-line3", "address-line2", "address-line1"]) {
        waitForConcat.unshift(profile[f]);
        if (this.getFieldDetailByName(f)) {
          if (waitForConcat.length > 1) {
            profile[f] = FormAutofillUtils.toOneLineAddress(waitForConcat);
          }
          waitForConcat = [];
        }
      }
    }
  }

  /**
   * Replace tel with tel-national if tel violates the input element's
   * restriction.
   *
   * @param {object} profile
   *        A profile to be converted.
   */
  telTransformer(profile) {
    if (!profile.tel || !profile["tel-national"]) {
      return;
    }

    let detail = this.getFieldDetailByName("tel");
    if (!detail) {
      return;
    }

    let element = detail.element;
    let _pattern;
    let testPattern = str => {
      if (!_pattern) {
        // The pattern has to match the entire value.
        _pattern = new RegExp("^(?:" + element.pattern + ")$", "u");
      }
      return _pattern.test(str);
    };
    if (element.pattern) {
      if (testPattern(profile.tel)) {
        return;
      }
    } else if (element.maxLength) {
      if (
        detail.reason == "autocomplete" &&
        profile.tel.length <= element.maxLength
      ) {
        return;
      }
    }

    if (detail.reason != "autocomplete") {
      // Since we only target people living in US and using en-US websites in
      // MVP, it makes more sense to fill `tel-national` instead of `tel`
      // if the field is identified by heuristics and no other clues to
      // determine which one is better.
      // TODO: [Bug 1407545] This should be improved once more countries are
      // supported.
      profile.tel = profile["tel-national"];
    } else if (element.pattern) {
      if (testPattern(profile["tel-national"])) {
        profile.tel = profile["tel-national"];
      }
    } else if (element.maxLength) {
      if (profile["tel-national"].length <= element.maxLength) {
        profile.tel = profile["tel-national"];
      }
    }
  }

  /**
   *
   * @param {object} fieldDetail A fieldDetail of the related element.
   * @param {object} profile The profile to fill.
   * @returns {string} The value to fill for the given field.
   */
  getFilledValueFromProfile(fieldDetail, profile) {
    let value =
      profile[`${fieldDetail.fieldName}-formatted`] ||
      profile[fieldDetail.fieldName];

    if (fieldDetail.fieldName == "cc-number" && fieldDetail.part != null) {
      const part = fieldDetail.part;
      return value.slice((part - 1) * 4, part * 4);
    }
    return value;
  }

  fillFieldValue(element, value) {
    if (FormAutofillUtils.focusOnAutofill) {
      element.focus({ preventScroll: true });
    }
    if (HTMLInputElement.isInstance(element)) {
      element.setUserInput(value);
    } else if (HTMLSelectElement.isInstance(element)) {
      // Set the value of the select element so that web event handlers can react accordingly
      element.value = value;
      element.dispatchEvent(
        new element.ownerGlobal.Event("input", { bubbles: true })
      );
      element.dispatchEvent(
        new element.ownerGlobal.Event("change", { bubbles: true })
      );
    }
  }

  clearPreviewedFields(elementIds) {
    for (const elementId of elementIds) {
      const fieldDetail = this.getFieldDetailByElementId(elementId);
      const element = fieldDetail?.element;
      if (!element) {
        this.log.warn(fieldDetail.fieldName, "is unreachable");
        continue;
      }

      element.previewValue = "";
      if (element.autofillState == FIELD_STATES.AUTO_FILLED) {
        continue;
      }
      this.changeFieldState(fieldDetail, FIELD_STATES.NORMAL);
    }
  }

  clearFilledFields(elementIds) {
    for (const elementId of elementIds) {
      const fieldDetail = this.getFieldDetailByElementId(elementId);
      const element = fieldDetail?.element;
      if (!element) {
        this.log.warn(fieldDetail.fieldName, "is unreachable");
        continue;
      }

      if (element.autofillState == FIELD_STATES.AUTO_FILLED) {
        if (HTMLInputElement.isInstance(element)) {
          element.setUserInput("");
        } else if (HTMLSelectElement.isInstance(element)) {
          // If we can't find a selected option, then we should just reset to the first option's value
          this.#resetSelectElementValue(element);
        }
      }
    }
  }

  /**
   * Resets a <select> element to its selected option or the first option if there is none selected.
   *
   * @param {HTMLElement} element
   */
  #resetSelectElementValue(element) {
    if (!element.options.length) {
      return;
    }
    const selected = [...element.options].find(option =>
      option.hasAttribute("selected")
    );
    element.value = selected ? selected.value : element.options[0].value;
    element.dispatchEvent(
      new element.ownerGlobal.Event("input", { bubbles: true })
    );
    element.dispatchEvent(
      new element.ownerGlobal.Event("change", { bubbles: true })
    );
  }

  /**
   * Return the record that is keyed by element id and value is the normalized value
   * done by computeFillingValue
   *
   * @returns {object} An object keyed by element id, and the value is
   *                   an object that includes the following properties:
   * filledState: The autofill state of the element
   * filledValue: The computed value for autofilling
   * value: The value of the element
   */
  collectFormFilledData() {
    const filledData = new Map();

    for (const fieldDetail of this.fieldDetails) {
      const element = fieldDetail.element;
      filledData.set(fieldDetail.elementId, {
        filledState: element.autofillState,
        filledValue: this.computeFillingValue(fieldDetail),
        value: element.value,
      });
    }
    return filledData;
  }
}
PK
!<���ˋˋ-modules/shared/FormAutofillHeuristics.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs";
import { HeuristicsRegExp } from "resource://gre/modules/shared/HeuristicsRegExp.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  CreditCard: "resource://gre/modules/CreditCard.sys.mjs",
  CreditCardRulesets: "resource://gre/modules/shared/CreditCardRuleset.sys.mjs",
  FieldScanner: "resource://gre/modules/shared/FieldScanner.sys.mjs",
  FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
  LabelUtils: "resource://gre/modules/shared/LabelUtils.sys.mjs",
});

/**
 * To help us classify sections that can appear only N times in a row.
 * For example, the only time multiple cc-number fields are valid is when
 * there are four of these fields in a row.
 * Otherwise, multiple cc-number fields should be in separate sections.
 */
const MULTI_N_FIELD_NAMES = {
  "cc-number": 4,
};

/**
 * Returns the autocomplete information of fields according to heuristics.
 */
export const FormAutofillHeuristics = {
  RULES: HeuristicsRegExp.getRules(),
  LABEL_RULES: HeuristicsRegExp.getLabelRules(),

  CREDIT_CARD_FIELDNAMES: [],
  ADDRESS_FIELDNAMES: [],
  /**
   * Try to find a contiguous sub-array within an array.
   *
   * @param {Array} array
   * @param {Array} subArray
   *
   * @returns {boolean}
   *          Return whether subArray was found within the array or not.
   */
  _matchContiguousSubArray(array, subArray) {
    return array.some((elm, i) =>
      subArray.every((sElem, j) => sElem == array[i + j])
    );
  },

  /**
   * Try to find the field that is look like a month select.
   *
   * @param {DOMElement} element
   * @returns {boolean}
   *          Return true if we observe the trait of month select in
   *          the current element.
   */
  _isExpirationMonthLikely(element) {
    if (!HTMLSelectElement.isInstance(element)) {
      return false;
    }

    const options = [...element.options];
    const desiredValues = Array(12)
      .fill(1)
      .map((v, i) => v + i);

    // The number of month options shouldn't be less than 12 or larger than 13
    // including the default option.
    if (options.length < 12 || options.length > 13) {
      return false;
    }

    return (
      this._matchContiguousSubArray(
        options.map(e => +e.value),
        desiredValues
      ) ||
      this._matchContiguousSubArray(
        options.map(e => +e.label),
        desiredValues
      )
    );
  },

  /**
   * Try to find the field that is look like a year select.
   *
   * @param {DOMElement} element
   * @returns {boolean}
   *          Return true if we observe the trait of year select in
   *          the current element.
   */
  _isExpirationYearLikely(element) {
    if (!HTMLSelectElement.isInstance(element)) {
      return false;
    }

    const options = [...element.options];
    // A normal expiration year select should contain at least the last three years
    // in the list.
    const curYear = new Date().getFullYear();
    const desiredValues = Array(3)
      .fill(0)
      .map((v, i) => v + curYear + i);

    return (
      this._matchContiguousSubArray(
        options.map(e => +e.value),
        desiredValues
      ) ||
      this._matchContiguousSubArray(
        options.map(e => +e.label),
        desiredValues
      )
    );
  },

  /**
   * Try to match the telephone related fields to the grammar
   * list to see if there is any valid telephone set and correct their
   * field names.
   *
   * @param {FieldScanner} scanner
   *        The current parsing status for all elements
   * @returns {boolean}
   *          Return true if there is any field can be recognized in the parser,
   *          otherwise false.
   */
  _parsePhoneFields(scanner, _fieldDetail) {
    let matchingResult;
    const GRAMMARS = this.PHONE_FIELD_GRAMMARS;

    function isGrammarSeparator(index) {
      return !GRAMMARS[index][0];
    }

    const savedIndex = scanner.parsingIndex;
    for (let ruleFrom = 0; ruleFrom < GRAMMARS.length; ) {
      const detailStart = scanner.parsingIndex;
      let ruleTo = ruleFrom;
      for (let count = 0; ruleTo < GRAMMARS.length; ruleTo++, count++) {
        // Bail out when reaching the end of the current set of grammars
        // or there are no more elements to parse
        if (
          isGrammarSeparator(ruleTo) ||
          !scanner.elementExisting(detailStart + count)
        ) {
          break;
        }

        const [category, , length] = GRAMMARS[ruleTo];
        const detail = scanner.getFieldDetailByIndex(detailStart + count);

        // If the field is not what this grammar rule is interested in, skip processing.
        if (
          !detail ||
          detail.fieldName != category ||
          detail.reason == "autocomplete"
        ) {
          break;
        }

        const element = detail.element;
        if (length && (!element.maxLength || length < element.maxLength)) {
          break;
        }
      }

      // if we reach the grammar separator, that means all the previous rules are matched.
      // Set the matchingResult so we update field names accordingly.
      if (isGrammarSeparator(ruleTo)) {
        matchingResult = { ruleFrom, ruleTo };
        break;
      }

      // Fast forward to the next rule set.
      for (; ruleFrom < GRAMMARS.length; ) {
        if (isGrammarSeparator(ruleFrom++)) {
          break;
        }
      }
    }

    if (matchingResult) {
      const { ruleFrom, ruleTo } = matchingResult;
      for (let i = ruleFrom; i < ruleTo; i++) {
        scanner.updateFieldName(scanner.parsingIndex, GRAMMARS[i][1]);
        scanner.parsingIndex++;
      }
    }

    // If the previous parsed field is a "tel" field, run heuristic to see
    // if the current field is a "tel-extension" field
    const field = scanner.getFieldDetailByIndex(scanner.parsingIndex);
    if (field && field.reason != "autocomplete") {
      const prev = scanner.getFieldDetailByIndex(scanner.parsingIndex - 1);
      if (
        prev &&
        lazy.FormAutofillUtils.getCategoryFromFieldName(prev.fieldName) == "tel"
      ) {
        const regExpTelExtension = new RegExp(
          "\\bext|ext\\b|extension|ramal", // pt-BR, pt-PT
          "iug"
        );
        if (this._matchRegexp(field.element, regExpTelExtension)) {
          scanner.updateFieldName(scanner.parsingIndex, "tel-extension");
          scanner.parsingIndex++;
        }
      }
    }
    return savedIndex != scanner.parsingIndex;
  },

  /**
   * Try to find the correct address-line[1-3] sequence and correct their field
   * names.
   *
   * @param {FieldScanner} scanner
   *        The current parsing status for all elements
   * @returns {boolean}
   *          Return true if there is any field can be recognized in the parser,
   *          otherwise false.
   */
  _parseStreetAddressFields(scanner, _fieldDetail) {
    const INTERESTED_FIELDS = [
      "street-address",
      "address-line1",
      "address-line2",
      "address-line3",
    ];

    const fields = [];
    for (let idx = scanner.parsingIndex; !scanner.parsingFinished; idx++) {
      const detail = scanner.getFieldDetailByIndex(idx);
      if (!INTERESTED_FIELDS.includes(detail?.fieldName)) {
        break;
      }
      fields.push(detail);
    }

    if (!fields.length) {
      return false;
    }

    switch (fields.length) {
      case 1:
        if (
          fields[0].reason != "autocomplete" &&
          ["address-line2", "address-line3"].includes(fields[0].fieldName)
        ) {
          scanner.updateFieldName(scanner.parsingIndex, "address-line1");
        }
        break;
      case 2:
        if (fields[0].reason == "autocomplete") {
          if (
            fields[0].fieldName == "street-address" &&
            (fields[1].fieldName == "address-line2" ||
              fields[1].reason != "autocomplete")
          ) {
            scanner.updateFieldName(
              scanner.parsingIndex,
              "address-line1",
              true
            );
          }
        } else {
          scanner.updateFieldName(scanner.parsingIndex, "address-line1");
        }

        scanner.updateFieldName(scanner.parsingIndex + 1, "address-line2");
        break;
      case 3:
      default:
        scanner.updateFieldName(scanner.parsingIndex, "address-line1");
        scanner.updateFieldName(scanner.parsingIndex + 1, "address-line2");
        scanner.updateFieldName(scanner.parsingIndex + 2, "address-line3");
        break;
    }

    scanner.parsingIndex += fields.length;
    return true;
  },

  _parseAddressFields(scanner, fieldDetail) {
    const INTERESTED_FIELDS = ["address-level1", "address-level2"];

    if (!INTERESTED_FIELDS.includes(fieldDetail.fieldName)) {
      return false;
    }

    const fields = [];
    for (let idx = scanner.parsingIndex; !scanner.parsingFinished; idx++) {
      const detail = scanner.getFieldDetailByIndex(idx);
      if (!INTERESTED_FIELDS.includes(detail?.fieldName)) {
        break;
      }
      fields.push(detail);
    }

    if (!fields.length) {
      return false;
    }

    // State & City(address-level2)
    if (fields.length == 1) {
      if (fields[0].fieldName == "address-level2") {
        const prev = scanner.getFieldDetailByIndex(scanner.parsingIndex - 1);
        if (
          prev &&
          !prev.fieldName &&
          HTMLSelectElement.isInstance(prev.element)
        ) {
          scanner.updateFieldName(scanner.parsingIndex - 1, "address-level1");
          scanner.parsingIndex += 1;
          return true;
        }
        const next = scanner.getFieldDetailByIndex(scanner.parsingIndex + 1);
        if (
          next &&
          !next.fieldName &&
          HTMLSelectElement.isInstance(next.element)
        ) {
          scanner.updateFieldName(scanner.parsingIndex + 1, "address-level1");
          scanner.parsingIndex += 2;
          return true;
        }
      }
    }

    scanner.parsingIndex += fields.length;
    return true;
  },

  /**
   * Try to look for expiration date fields and revise the field names if needed.
   *
   * @param {FieldScanner} scanner
   *        The current parsing status for all elements
   * @returns {boolean}
   *          Return true if there is any field can be recognized in the parser,
   *          otherwise false.
   */
  _parseCreditCardExpiryFields(scanner, fieldDetail) {
    const INTERESTED_FIELDS = ["cc-exp", "cc-exp-month", "cc-exp-year"];

    if (!INTERESTED_FIELDS.includes(fieldDetail.fieldName)) {
      return false;
    }

    const fields = [];
    for (let idx = scanner.parsingIndex; ; idx++) {
      const detail = scanner.getFieldDetailByIndex(idx);
      if (!INTERESTED_FIELDS.includes(detail?.fieldName)) {
        break;
      }
      fields.push(detail);
    }

    // Don't process the fields if expiration month and expiration year are already
    // matched by regex in correct order.
    if (
      (fields.length == 1 && fields[0].fieldName == "cc-exp") ||
      (fields.length == 2 &&
        fields[0].fieldName == "cc-exp-month" &&
        fields[1].fieldName == "cc-exp-year")
    ) {
      scanner.parsingIndex += fields.length;
      return true;
    }

    const prevCCFields = new Set();
    for (let idx = scanner.parsingIndex - 1; ; idx--) {
      const detail = scanner.getFieldDetailByIndex(idx);
      if (
        lazy.FormAutofillUtils.getCategoryFromFieldName(detail?.fieldName) !=
        "creditCard"
      ) {
        break;
      }
      prevCCFields.add(detail.fieldName);
    }
    // We update the "cc-exp-*" fields to correct "cc-ex-*" fields order when
    // the following conditions are met:
    // 1. The previous elements are identified as credit card fields and
    //    cc-number is in it
    // 2. There is no "cc-exp-*" fields in the previous credit card elements
    if (
      ["cc-number", "cc-name"].some(f => prevCCFields.has(f)) &&
      !["cc-exp", "cc-exp-month", "cc-exp-year"].some(f => prevCCFields.has(f))
    ) {
      if (fields.length == 1) {
        scanner.updateFieldName(scanner.parsingIndex, "cc-exp");
      } else if (fields.length == 2) {
        scanner.updateFieldName(scanner.parsingIndex, "cc-exp-month");
        scanner.updateFieldName(scanner.parsingIndex + 1, "cc-exp-year");
      }
      scanner.parsingIndex += fields.length;
      return true;
    }

    // Set field name to null as it failed to match any patterns.
    for (let idx = 0; idx < fields.length; idx++) {
      scanner.updateFieldName(scanner.parsingIndex + idx, null);
    }
    return false;
  },

  _parseCreditCardNumberFields(scanner, fieldDetail) {
    const INTERESTED_FIELDS = ["cc-number"];

    if (!INTERESTED_FIELDS.includes(fieldDetail.fieldName)) {
      return false;
    }

    const fieldDetails = [];
    for (let idx = scanner.parsingIndex; ; idx++) {
      const detail = scanner.getFieldDetailByIndex(idx);
      if (!INTERESTED_FIELDS.includes(detail?.fieldName)) {
        break;
      }
      fieldDetails.push(detail);
    }

    // This rule only applies when all the fields are visible
    if (fieldDetails.some(field => !field.isVisible)) {
      scanner.parsingIndex += fieldDetails.length;
      return true;
    }

    // This is the heuristic to handle special cases where we can have multiple
    // fields in one section, but only if the field has appeared N times in a row.
    // For example, websites can use 4 consecutive 4-digit `cc-number` fields
    // instead of one 16-digit `cc-number` field.
    const N = MULTI_N_FIELD_NAMES["cc-number"];
    if (fieldDetails.length == N) {
      fieldDetails.forEach((fieldDetail, index) => {
        // part starts with 1
        fieldDetail.part = index + 1;
      });
      scanner.parsingIndex += fieldDetails.length;
      return true;
    }

    return false;
  },
  /**
   * Look for cc-*-name fields when *-name field is present
   *
   * @param {FieldScanner} scanner
   *        The current parsing status for all elements
   * @returns {boolean}
   *          Return true if there is any field can be recognized in the parser,
   *          otherwise false.
   */
  _parseCreditCardNameFields(scanner, fieldDetail) {
    const INTERESTED_FIELDS = [
      "name",
      "given-name",
      "additional-name",
      "family-name",
    ];

    if (!INTERESTED_FIELDS.includes(fieldDetail.fieldName)) {
      return false;
    }

    const fields = [];
    for (let idx = scanner.parsingIndex; ; idx++) {
      const detail = scanner.getFieldDetailByIndex(idx);
      if (!INTERESTED_FIELDS.includes(detail?.fieldName)) {
        break;
      }
      fields.push(detail);
    }

    const prevCCFields = new Set();
    for (let idx = scanner.parsingIndex - 1; ; idx--) {
      const detail = scanner.getFieldDetailByIndex(idx);
      if (
        lazy.FormAutofillUtils.getCategoryFromFieldName(detail?.fieldName) !=
        "creditCard"
      ) {
        break;
      }
      prevCCFields.add(detail.fieldName);
    }

    // We update the "name" fields to "cc-name" fields when the following
    // conditions are met:
    // 1. The preceding fields are identified as credit card fields and
    //    contain the "cc-number" field.
    // 2. No "cc-name-*" field is found among the preceding credit card fields.
    // 3. The "cc-csc" field is not present among the preceding credit card fields.
    if (
      ["cc-number"].some(f => prevCCFields.has(f)) &&
      !["cc-name", "cc-given-name", "cc-family-name", "cc-csc"].some(f =>
        prevCCFields.has(f)
      )
    ) {
      // If there is only one field, assume the name field a `cc-name` field
      if (fields.length == 1) {
        scanner.updateFieldName(scanner.parsingIndex, `cc-name`);
        scanner.parsingIndex += 1;
      } else {
        // update *-name to cc-*-name
        for (const field of fields) {
          scanner.updateFieldName(
            scanner.parsingIndex,
            `cc-${field.fieldName}`
          );
          scanner.parsingIndex += 1;
        }
      }
      return true;
    }

    return false;
  },

  /**
   * This function should provide all field details of a form which are placed
   * in the belonging section. The details contain the autocomplete info
   * (e.g. fieldName, section, etc).
   *
   * @param {HTMLFormElement} form
   *        the elements in this form to be predicted the field info.
   * @returns {Array<FormSection>}
   *        all sections within its field details in the form.
   */
  getFormInfo(form) {
    const scanner = new lazy.FieldScanner(form, this.inferFieldInfo.bind(this));

    while (!scanner.parsingFinished) {
      const savedIndex = scanner.parsingIndex;

      // First, we get the inferred field info
      const fieldDetail = scanner.getFieldDetailByIndex(scanner.parsingIndex);

      if (
        this._parsePhoneFields(scanner, fieldDetail) ||
        this._parseStreetAddressFields(scanner, fieldDetail) ||
        this._parseAddressFields(scanner, fieldDetail) ||
        this._parseCreditCardExpiryFields(scanner, fieldDetail) ||
        this._parseCreditCardNameFields(scanner, fieldDetail) ||
        this._parseCreditCardNumberFields(scanner, fieldDetail)
      ) {
        continue;
      }

      // If there is no field parsed, the parsing cursor can be moved
      // forward to the next one.
      if (savedIndex == scanner.parsingIndex) {
        scanner.parsingIndex++;
      }
    }

    lazy.LabelUtils.clearLabelMap();

    return scanner.fieldDetails;
  },

  _getPossibleFieldNames(element) {
    let fieldNames = [];
    const isAutoCompleteOff =
      element.autocomplete == "off" || element.form?.autocomplete == "off";
    if (!isAutoCompleteOff || FormAutofill.creditCardsAutocompleteOff) {
      fieldNames.push(...this.CREDIT_CARD_FIELDNAMES);
    }
    if (!isAutoCompleteOff || FormAutofill.addressesAutocompleteOff) {
      fieldNames.push(...this.ADDRESS_FIELDNAMES);
    }

    if (HTMLSelectElement.isInstance(element)) {
      const FIELDNAMES_FOR_SELECT_ELEMENT = [
        "address-level1",
        "address-level2",
        "country",
        "cc-exp-month",
        "cc-exp-year",
        "cc-exp",
        "cc-type",
      ];
      fieldNames = fieldNames.filter(name =>
        FIELDNAMES_FOR_SELECT_ELEMENT.includes(name)
      );
    }

    return fieldNames;
  },

  /**
   * Get inferred information about an input element using autocomplete info, fathom and regex-based heuristics.
   *
   * @param {HTMLElement} element - The input element to infer information about.
   * @param {Array<HTMLElement>} elements - See `getFathomField` for details
   * @returns {Array} - An array containing:
   *                    [0]the inferred field name
   *                    [1]autocomplete information if the element has autocomplete attribute, null otherwise.
   *                    [2]fathom confidence if fathom considers it a cc field, null otherwise.
   */
  inferFieldInfo(element, elements = []) {
    const autocompleteInfo = element.getAutocompleteInfo();

    // An input[autocomplete="on"] will not be early return here since it stll
    // needs to find the field name.
    if (
      autocompleteInfo?.fieldName &&
      !["on", "off"].includes(autocompleteInfo.fieldName)
    ) {
      return [autocompleteInfo.fieldName, autocompleteInfo, null];
    }

    const fields = this._getPossibleFieldNames(element);

    // "email" type of input is accurate for heuristics to determine its Email
    // field or not. However, "tel" type is used for ZIP code for some web site
    // (e.g. HomeDepot, BestBuy), so "tel" type should be not used for "tel"
    // prediction.
    if (element.type == "email" && fields.includes("email")) {
      return ["email", null, null];
    }

    if (lazy.FormAutofillUtils.isFathomCreditCardsEnabled()) {
      // We don't care fields that are not supported by fathom
      const fathomFields = fields.filter(r =>
        lazy.CreditCardRulesets.types.includes(r)
      );
      const [matchedFieldName, confidence] = this.getFathomField(
        element,
        fathomFields,
        elements
      );
      // At this point, use fathom's recommendation if it has one
      if (matchedFieldName) {
        return [matchedFieldName, null, confidence];
      }

      // Continue to run regex-based heuristics even when fathom doesn't recognize
      // the field. Since the regex-based heuristic has good search coverage but
      // has a worse precision. We use it in conjunction with fathom to maximize
      // our search coverage. For example, when a <input> is not considered cc-name
      // by fathom but is considered cc-name by regex-based heuristic, if the form
      // also contains a cc-number identified by fathom, we will treat the form as a
      // valid cc form; hence both cc-number & cc-name are identified.
    }

    // Check every select for options that
    // match credit card network names in value or label.
    if (HTMLSelectElement.isInstance(element)) {
      if (this._isExpirationMonthLikely(element)) {
        return ["cc-exp-month", null, null];
      } else if (this._isExpirationYearLikely(element)) {
        return ["cc-exp-year", null, null];
      }

      const options = Array.from(element.querySelectorAll("option"));
      if (
        options.find(
          option =>
            lazy.CreditCard.getNetworkFromName(option.value) ||
            lazy.CreditCard.getNetworkFromName(option.text)
        )
      ) {
        return ["cc-type", null, null];
      }

      // At least two options match the country name, otherwise some state name might
      // also match a country name, ex, Georgia. We check the last two
      // options rather than the first, as selects often start with a non-country display option.
      const countryDisplayNames = Array.from(FormAutofill.countries.values());
      if (
        options.length >= 2 &&
        options
          .slice(-2)
          .every(
            option =>
              countryDisplayNames.includes(option.value) ||
              countryDisplayNames.includes(option.text)
          )
      ) {
        return ["country", null, null];
      }
    }

    // Find a matched field name using regexp-based heuristics
    const matchedFieldName = this._findMatchedFieldName(element, fields);
    return [matchedFieldName, null, null];
  },

  /**
   * Using Fathom, say what kind of CC field an element is most likely to be.
   * This function deoesn't only run fathom on the passed elements. It also
   * runs fathom for all elements in the FieldScanner for optimization purpose.
   *
   * @param {HTMLElement} element
   * @param {Array} fields
   * @param {Array<HTMLElement>} elements - All other eligible elements in the same form. This is mainly used as an
   *                                        optimization approach to run fathom model on all eligible elements
   *                                        once instead of one by one
   * @returns {Array} A tuple of [field name, probability] describing the
   *   highest-confidence classification
   */
  getFathomField(element, fields, elements = []) {
    if (!fields.length) {
      return [null, null];
    }

    if (!this._fathomConfidences?.get(element)) {
      this._fathomConfidences = new Map();

      // This should not throw unless we run into an OOM situation, at which
      // point we have worse problems and this failing is not a big deal.
      elements = elements.includes(element) ? elements : [element];
      const confidences = this.getFormAutofillConfidences(elements);

      for (let i = 0; i < elements.length; i++) {
        this._fathomConfidences.set(elements[i], confidences[i]);
      }
    }

    const elementConfidences = this._fathomConfidences.get(element);
    if (!elementConfidences) {
      return [null, null];
    }

    let highestField = null;
    let highestConfidence = lazy.FormAutofillUtils.ccFathomConfidenceThreshold; // Start with a threshold of 0.5
    for (let [key, value] of Object.entries(elementConfidences)) {
      if (!fields.includes(key)) {
        // ignore field that we don't care
        continue;
      }

      if (value > highestConfidence) {
        highestConfidence = value;
        highestField = key;
      }
    }

    if (!highestField) {
      return [null, null];
    }

    // Used by test ONLY! This ensure testcases always get the same confidence
    if (lazy.FormAutofillUtils.ccFathomTestConfidence > 0) {
      highestConfidence = lazy.FormAutofillUtils.ccFathomTestConfidence;
    }

    return [highestField, highestConfidence];
  },

  /**
   * @param {Array} elements Array of elements that we want to get result from fathom cc rules
   * @returns {object} Fathom confidence keyed by field-type.
   */
  getFormAutofillConfidences(elements) {
    if (
      lazy.FormAutofillUtils.ccHeuristicsMode ==
      lazy.FormAutofillUtils.CC_FATHOM_NATIVE
    ) {
      const confidences = ChromeUtils.getFormAutofillConfidences(elements);
      return confidences.map(c => {
        let result = {};
        for (let [fieldName, confidence] of Object.entries(c)) {
          let type =
            lazy.FormAutofillUtils.formAutofillConfidencesKeyToCCFieldType(
              fieldName
            );
          result[type] = confidence;
        }
        return result;
      });
    }

    return elements.map(element => {
      /**
       * Return how confident our ML model is that `element` is a field of the
       * given type.
       *
       * @param {string} fieldName The Fathom type to check against. This is
       *   conveniently the same as the autocomplete attribute value that means
       *   the same thing.
       * @returns {number} Confidence in range [0, 1]
       */
      function confidence(fieldName) {
        const ruleset = lazy.CreditCardRulesets[fieldName];
        const fnodes = ruleset.against(element).get(fieldName);

        // fnodes is either 0 or 1 item long, since we ran the ruleset
        // against a single element:
        return fnodes.length ? fnodes[0].scoreFor(fieldName) : 0;
      }

      // Bang the element against the ruleset for every type of field:
      const confidences = {};
      lazy.CreditCardRulesets.types.map(fieldName => {
        confidences[fieldName] = confidence(fieldName);
      });

      return confidences;
    });
  },

  /**
   * @typedef ElementStrings
   * @type {object}
   * @yields {string} id - element id.
   * @yields {string} name - element name.
   * @yields {Array<string>} labels - extracted labels.
   */

  /**
   * Extract all the signature strings of an element.
   *
   * @param {HTMLElement} element
   * @returns {Array<string>}
   */
  _getElementStrings(element) {
    return [element.id, element.name, element.placeholder?.trim()];
  },

  /**
   * Extract all the label strings associated with an element.
   *
   * @param {HTMLElement} element
   * @returns {ElementStrings}
   */
  _getElementLabelStrings(element) {
    return {
      *[Symbol.iterator]() {
        const labels = lazy.LabelUtils.findLabelElements(element);
        for (let label of labels) {
          yield* lazy.LabelUtils.extractLabelStrings(label);
        }

        const ariaLabels = element.getAttribute("aria-label");
        if (ariaLabels) {
          yield* [ariaLabels];
        }
      },
    };
  },

  // In order to support webkit we need to avoid usage of negative lookbehind due to low support
  // First safari version with support is 16.4 (Release Date: 27th March 2023)
  // https://caniuse.com/js-regexp-lookbehind
  // We can mimic the behaviour of negative lookbehinds by using a named capture group
  // (?<!not)word -> (?<neg>notword)|word
  // TODO: Bug 1829583
  testRegex(regex, string) {
    const matches = string?.matchAll(regex);
    if (!matches) {
      return false;
    }

    const excludeNegativeCaptureGroups = [];

    for (const match of matches) {
      excludeNegativeCaptureGroups.push(
        ...match.filter(m => m !== match?.groups?.neg).filter(Boolean)
      );
    }
    return excludeNegativeCaptureGroups?.length > 0;
  },

  /**
   * Find the first matching field name from a given list of field names
   * that matches an HTML element.
   *
   * The function first tries to match the element against a set of
   * pre-defined regular expression rules. If no match is found, it
   * then checks for label-specific rules, if they exist.
   *
   * Note: For label rules, the keyword is often more general
   * (e.g., "^\\W*address"), hence they are only searched within labels
   * to reduce the occurrence of false positives.
   *
   * @param {HTMLElement} element The element to match.
   * @param {Array<string>} fieldNames An array of field names to compare against.
   * @returns {string|null} The name of the matched field, or null if no match was found.
   */
  _findMatchedFieldName(element, fieldNames) {
    if (!fieldNames.length) {
      return null;
    }

    // Attempt to match the element against the default set of rules
    let matchedFieldName = fieldNames.find(fieldName =>
      this._matchRegexp(element, this.RULES[fieldName])
    );

    // If no match is found, and if a label rule exists for the field,
    // attempt to match against the label rules
    if (!matchedFieldName) {
      matchedFieldName = fieldNames.find(fieldName => {
        const regexp = this.LABEL_RULES[fieldName];
        return this._matchRegexp(element, regexp, { attribute: false });
      });
    }
    return matchedFieldName;
  },

  /**
   * Determine whether the regexp can match any of element strings.
   *
   * @param {HTMLElement} element The HTML element to match.
   * @param {RegExp} regexp       The regular expression to match against.
   * @param {object} [options]    Optional parameters for matching.
   * @param {boolean} [options.attribute=true]
   *                              Whether to match against the element's attributes.
   * @param {boolean} [options.label=true]
   *                              Whether to match against the element's labels.
   * @returns {boolean} True if a match is found, otherwise false.
   */
  _matchRegexp(element, regexp, { attribute = true, label = true } = {}) {
    if (!regexp) {
      return false;
    }

    if (attribute) {
      const elemStrings = this._getElementStrings(element);
      if (elemStrings.find(s => this.testRegex(regexp, s?.toLowerCase()))) {
        return true;
      }
    }

    if (label) {
      const elementLabelStrings = this._getElementLabelStrings(element);
      for (const s of elementLabelStrings) {
        if (this.testRegex(regexp, s?.toLowerCase())) {
          return true;
        }
      }
    }

    return false;
  },

  /**
   * Phone field grammars - first matched grammar will be parsed. Grammars are
   * separated by { REGEX_SEPARATOR, FIELD_NONE, 0 }. Suffix and extension are
   * parsed separately unless they are necessary parts of the match.
   * The following notation is used to describe the patterns:
   * <cc> - country code field.
   * <ac> - area code field.
   * <phone> - phone or prefix.
   * <suffix> - suffix.
   * <ext> - extension.
   * :N means field is limited to N characters, otherwise it is unlimited.
   * (pattern <field>)? means pattern is optional and matched separately.
   *
   * This grammar list from Chromium will be enabled partially once we need to
   * support more cases of Telephone fields.
   */
  PHONE_FIELD_GRAMMARS: [
    // Country code: <cc> Area Code: <ac> Phone: <phone> (- <suffix>

    // (Ext: <ext>)?)?
    // {REGEX_COUNTRY, FIELD_COUNTRY_CODE, 0},
    // {REGEX_AREA, FIELD_AREA_CODE, 0},
    // {REGEX_PHONE, FIELD_PHONE, 0},
    // {REGEX_SEPARATOR, FIELD_NONE, 0},

    // \( <ac> \) <phone>:3 <suffix>:4 (Ext: <ext>)?
    // {REGEX_AREA_NOTEXT, FIELD_AREA_CODE, 3},
    // {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 3},
    // {REGEX_PHONE, FIELD_SUFFIX, 4},
    // {REGEX_SEPARATOR, FIELD_NONE, 0},

    // Phone: <cc> <ac>:3 - <phone>:3 - <suffix>:4 (Ext: <ext>)?
    // {REGEX_PHONE, FIELD_COUNTRY_CODE, 0},
    // {REGEX_PHONE, FIELD_AREA_CODE, 3},
    // {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 3},
    // {REGEX_SUFFIX_SEPARATOR, FIELD_SUFFIX, 4},
    // {REGEX_SEPARATOR, FIELD_NONE, 0},

    // Phone: <cc>:3 <ac>:3 <phone>:3 <suffix>:4 (Ext: <ext>)?
    ["tel", "tel-country-code", 3],
    ["tel", "tel-area-code", 3],
    ["tel", "tel-local-prefix", 3],
    ["tel", "tel-local-suffix", 4],
    [null, null, 0],

    // Area Code: <ac> Phone: <phone> (- <suffix> (Ext: <ext>)?)?
    // {REGEX_AREA, FIELD_AREA_CODE, 0},
    // {REGEX_PHONE, FIELD_PHONE, 0},
    // {REGEX_SEPARATOR, FIELD_NONE, 0},

    // Phone: <ac> <phone>:3 <suffix>:4 (Ext: <ext>)?
    // {REGEX_PHONE, FIELD_AREA_CODE, 0},
    // {REGEX_PHONE, FIELD_PHONE, 3},
    // {REGEX_PHONE, FIELD_SUFFIX, 4},
    // {REGEX_SEPARATOR, FIELD_NONE, 0},

    // Phone: <cc> \( <ac> \) <phone> (- <suffix> (Ext: <ext>)?)?
    // {REGEX_PHONE, FIELD_COUNTRY_CODE, 0},
    // {REGEX_AREA_NOTEXT, FIELD_AREA_CODE, 0},
    // {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 0},
    // {REGEX_SEPARATOR, FIELD_NONE, 0},

    // Phone: \( <ac> \) <phone> (- <suffix> (Ext: <ext>)?)?
    // {REGEX_PHONE, FIELD_COUNTRY_CODE, 0},
    // {REGEX_AREA_NOTEXT, FIELD_AREA_CODE, 0},
    // {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 0},
    // {REGEX_SEPARATOR, FIELD_NONE, 0},

    // Phone: <cc> - <ac> - <phone> - <suffix> (Ext: <ext>)?
    // {REGEX_PHONE, FIELD_COUNTRY_CODE, 0},
    // {REGEX_PREFIX_SEPARATOR, FIELD_AREA_CODE, 0},
    // {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 0},
    // {REGEX_SUFFIX_SEPARATOR, FIELD_SUFFIX, 0},
    // {REGEX_SEPARATOR, FIELD_NONE, 0},

    // Area code: <ac>:3 Prefix: <prefix>:3 Suffix: <suffix>:4 (Ext: <ext>)?
    // {REGEX_AREA, FIELD_AREA_CODE, 3},
    // {REGEX_PREFIX, FIELD_PHONE, 3},
    // {REGEX_SUFFIX, FIELD_SUFFIX, 4},
    // {REGEX_SEPARATOR, FIELD_NONE, 0},

    // Phone: <ac> Prefix: <phone> Suffix: <suffix> (Ext: <ext>)?
    // {REGEX_PHONE, FIELD_AREA_CODE, 0},
    // {REGEX_PREFIX, FIELD_PHONE, 0},
    // {REGEX_SUFFIX, FIELD_SUFFIX, 0},
    // {REGEX_SEPARATOR, FIELD_NONE, 0},

    // Phone: <ac> - <phone>:3 - <suffix>:4 (Ext: <ext>)?
    ["tel", "tel-area-code", 0],
    ["tel", "tel-local-prefix", 3],
    ["tel", "tel-local-suffix", 4],
    [null, null, 0],

    // Phone: <cc> - <ac> - <phone> (Ext: <ext>)?
    // {REGEX_PHONE, FIELD_COUNTRY_CODE, 0},
    // {REGEX_PREFIX_SEPARATOR, FIELD_AREA_CODE, 0},
    // {REGEX_SUFFIX_SEPARATOR, FIELD_PHONE, 0},
    // {REGEX_SEPARATOR, FIELD_NONE, 0},

    // Phone: <ac> - <phone> (Ext: <ext>)?
    // {REGEX_AREA, FIELD_AREA_CODE, 0},
    // {REGEX_PHONE, FIELD_PHONE, 0},
    // {REGEX_SEPARATOR, FIELD_NONE, 0},

    // Phone: <cc>:3 - <phone>:10 (Ext: <ext>)?
    // {REGEX_PHONE, FIELD_COUNTRY_CODE, 3},
    // {REGEX_PHONE, FIELD_PHONE, 10},
    // {REGEX_SEPARATOR, FIELD_NONE, 0},

    // Ext: <ext>
    // {REGEX_EXTENSION, FIELD_EXTENSION, 0},
    // {REGEX_SEPARATOR, FIELD_NONE, 0},

    // Phone: <phone> (Ext: <ext>)?
    // {REGEX_PHONE, FIELD_PHONE, 0},
    // {REGEX_SEPARATOR, FIELD_NONE, 0},
  ],
};

ChromeUtils.defineLazyGetter(
  FormAutofillHeuristics,
  "CREDIT_CARD_FIELDNAMES",
  () =>
    Object.keys(FormAutofillHeuristics.RULES).filter(name =>
      lazy.FormAutofillUtils.isCreditCardField(name)
    )
);

ChromeUtils.defineLazyGetter(FormAutofillHeuristics, "ADDRESS_FIELDNAMES", () =>
  Object.keys(FormAutofillHeuristics.RULES).filter(name =>
    lazy.FormAutofillUtils.isAddressField(name)
  )
);

export default FormAutofillHeuristics;
PK
!<�^`^����(modules/shared/FormAutofillUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  ContentDOMReference: "resource://gre/modules/ContentDOMReference.sys.mjs",
  CreditCard: "resource://gre/modules/CreditCard.sys.mjs",
  FormAutofillNameUtils:
    "resource://gre/modules/shared/FormAutofillNameUtils.sys.mjs",
  OSKeyStore: "resource://gre/modules/OSKeyStore.sys.mjs",
  AddressMetaDataLoader:
    "resource://gre/modules/shared/AddressMetaDataLoader.sys.mjs",
});

ChromeUtils.defineLazyGetter(
  lazy,
  "l10n",
  () =>
    new Localization(
      ["toolkit/formautofill/formAutofill.ftl", "branding/brand.ftl"],
      true
    )
);

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "Crypto",
  "@mozilla.org/login-manager/crypto/SDR;1",
  "nsILoginManagerCrypto"
);

export let FormAutofillUtils;

const ADDRESSES_COLLECTION_NAME = "addresses";
const CREDITCARDS_COLLECTION_NAME = "creditCards";
const AUTOFILL_CREDITCARDS_REAUTH_PREF =
  FormAutofill.AUTOFILL_CREDITCARDS_REAUTH_PREF;
const MANAGE_ADDRESSES_L10N_IDS = [
  "autofill-add-address-title",
  "autofill-manage-addresses-title",
];
const EDIT_ADDRESS_L10N_IDS = [
  "autofill-address-name",
  "autofill-address-organization",
  "autofill-address-street",
  "autofill-address-state",
  "autofill-address-province",
  "autofill-address-city",
  "autofill-address-country",
  "autofill-address-zip",
  "autofill-address-postal-code",
  "autofill-address-email",
  "autofill-address-tel",
  "autofill-edit-address-title",
  "autofill-address-neighborhood",
  "autofill-address-village-township",
  "autofill-address-island",
  "autofill-address-townland",
  "autofill-address-district",
  "autofill-address-county",
  "autofill-address-post-town",
  "autofill-address-suburb",
  "autofill-address-parish",
  "autofill-address-prefecture",
  "autofill-address-area",
  "autofill-address-do-si",
  "autofill-address-department",
  "autofill-address-emirate",
  "autofill-address-oblast",
  "autofill-address-pin",
  "autofill-address-eircode",
  "autofill-address-country-only",
  "autofill-cancel-button",
  "autofill-save-button",
];
const MANAGE_CREDITCARDS_L10N_IDS = [
  "autofill-add-card-title",
  "autofill-manage-payment-methods-title",
];
const EDIT_CREDITCARD_L10N_IDS = [
  "autofill-card-number",
  "autofill-card-name-on-card",
  "autofill-card-expires-month",
  "autofill-card-expires-year",
  "autofill-card-network",
];
const FIELD_STATES = {
  NORMAL: "",
  AUTO_FILLED: "autofill",
  PREVIEW: "preview",
};
const FORM_SUBMISSION_REASON = {
  FORM_SUBMIT_EVENT: "form-submit-event",
  FORM_REMOVAL_AFTER_FETCH: "form-removal-after-fetch",
  IFRAME_PAGEHIDE: "iframe-pagehide",
  PAGE_NAVIGATION: "page-navigation",
};

const ELIGIBLE_INPUT_TYPES = ["text", "email", "tel", "number", "month"];

// The maximum length of data to be saved in a single field for preventing DoS
// attacks that fill the user's hard drive(s).
const MAX_FIELD_VALUE_LENGTH = 200;

FormAutofillUtils = {
  get AUTOFILL_FIELDS_THRESHOLD() {
    return 3;
  },

  ADDRESSES_COLLECTION_NAME,
  CREDITCARDS_COLLECTION_NAME,
  AUTOFILL_CREDITCARDS_REAUTH_PREF,
  MANAGE_ADDRESSES_L10N_IDS,
  EDIT_ADDRESS_L10N_IDS,
  MANAGE_CREDITCARDS_L10N_IDS,
  EDIT_CREDITCARD_L10N_IDS,
  MAX_FIELD_VALUE_LENGTH,
  FIELD_STATES,
  FORM_SUBMISSION_REASON,

  _fieldNameInfo: {
    name: "name",
    "given-name": "name",
    "additional-name": "name",
    "family-name": "name",
    organization: "organization",
    "street-address": "address",
    "address-line1": "address",
    "address-line2": "address",
    "address-line3": "address",
    "address-level1": "address",
    "address-level2": "address",
    "postal-code": "address",
    country: "address",
    "country-name": "address",
    tel: "tel",
    "tel-country-code": "tel",
    "tel-national": "tel",
    "tel-area-code": "tel",
    "tel-local": "tel",
    "tel-local-prefix": "tel",
    "tel-local-suffix": "tel",
    "tel-extension": "tel",
    email: "email",
    "cc-name": "creditCard",
    "cc-given-name": "creditCard",
    "cc-additional-name": "creditCard",
    "cc-family-name": "creditCard",
    "cc-number": "creditCard",
    "cc-exp-month": "creditCard",
    "cc-exp-year": "creditCard",
    "cc-exp": "creditCard",
    "cc-type": "creditCard",
    "cc-csc": "creditCard",
  },

  _collators: {},
  _reAlternativeCountryNames: {},

  isAddressField(fieldName) {
    return (
      !!this._fieldNameInfo[fieldName] && !this.isCreditCardField(fieldName)
    );
  },

  isCreditCardField(fieldName) {
    return this._fieldNameInfo?.[fieldName] == "creditCard";
  },

  isCCNumber(ccNumber) {
    return ccNumber && lazy.CreditCard.isValidNumber(ccNumber);
  },

  /**
   * Get the decrypted value for a string pref.
   *
   * @param {string} prefName -> The pref whose value is needed.
   * @param {string} safeDefaultValue -> Value to be returned incase the pref is not yet set.
   * @returns {string}
   */
  getSecurePref(prefName, safeDefaultValue) {
    if (Services.prefs.getBoolPref("security.nocertdb", false)) {
      return false;
    }
    try {
      const encryptedValue = Services.prefs.getStringPref(prefName, "");
      return encryptedValue === ""
        ? safeDefaultValue
        : lazy.Crypto.decrypt(encryptedValue);
    } catch {
      return safeDefaultValue;
    }
  },

  /**
   * Set the pref to the encrypted form of the value.
   *
   * @param {string} prefName -> The pref whose value is to be set.
   * @param {string} value -> The value to be set in its encrypted form.
   */
  setSecurePref(prefName, value) {
    if (Services.prefs.getBoolPref("security.nocertdb", false)) {
      return;
    }
    if (value) {
      const encryptedValue = lazy.Crypto.encrypt(value);
      Services.prefs.setStringPref(prefName, encryptedValue);
    } else {
      Services.prefs.clearUserPref(prefName);
    }
  },

  /**
   * Get whether the OSAuth is enabled or not.
   *
   * @param {string} prefName -> The name of the pref (creditcards or addresses)
   * @returns {boolean}
   */
  getOSAuthEnabled(prefName) {
    return (
      lazy.OSKeyStore.canReauth() &&
      this.getSecurePref(prefName, "") !== "opt out"
    );
  },

  /**
   * Set whether the OSAuth is enabled or not.
   *
   * @param {string} prefName -> The pref to encrypt.
   * @param {boolean} enable -> Whether the pref is to be enabled.
   */
  setOSAuthEnabled(prefName, enable) {
    this.setSecurePref(prefName, enable ? null : "opt out");
  },

  async verifyUserOSAuth(
    prefName,
    promptMessage,
    captionDialog = "",
    parentWindow = null,
    generateKeyIfNotAvailable = true
  ) {
    if (!this.getOSAuthEnabled(prefName)) {
      promptMessage = false;
    }
    try {
      return (
        await lazy.OSKeyStore.ensureLoggedIn(
          promptMessage,
          captionDialog,
          parentWindow,
          generateKeyIfNotAvailable
        )
      ).authenticated;
    } catch (ex) {
      // Since Win throws an exception whereas Mac resolves to false upon cancelling.
      if (ex.result !== Cr.NS_ERROR_FAILURE) {
        throw ex;
      }
    }
    return false;
  },

  /**
   * Get the array of credit card network ids ("types") we expect and offer as valid choices
   *
   * @returns {Array}
   */
  getCreditCardNetworks() {
    return lazy.CreditCard.getSupportedNetworks();
  },

  getCategoryFromFieldName(fieldName) {
    return this._fieldNameInfo[fieldName];
  },

  getCategoriesFromFieldNames(fieldNames) {
    let categories = new Set();
    for (let fieldName of fieldNames) {
      let info = this.getCategoryFromFieldName(fieldName);
      if (info) {
        categories.add(info);
      }
    }
    return Array.from(categories);
  },

  getCollectionNameFromFieldName(fieldName) {
    return this.isCreditCardField(fieldName)
      ? CREDITCARDS_COLLECTION_NAME
      : ADDRESSES_COLLECTION_NAME;
  },

  getAddressSeparator() {
    // The separator should be based on the L10N address format, and using a
    // white space is a temporary solution.
    return " ";
  },

  /**
   * Get address display label. It should display information separated
   * by a comma.
   *
   * @param  {object} address
   * @returns {string}
   */
  getAddressLabel(address) {
    // TODO: Implement a smarter way for deciding what to display
    //       as option text. Possibly improve the algorithm in
    //       ProfileAutoCompleteResult.sys.mjs and reuse it here.
    let fieldOrder = [
      "name",
      "-moz-street-address-one-line", // Street address
      "address-level3", // Townland / Neighborhood / Village
      "address-level2", // City/Town
      "organization", // Company or organization name
      "address-level1", // Province/State (Standardized code if possible)
      "country", // Country name
      "postal-code", // Postal code
      "tel", // Phone number
      "email", // Email address
    ];

    address = { ...address };
    let parts = [];
    if (address["street-address"]) {
      address["-moz-street-address-one-line"] = this.toOneLineAddress(
        address["street-address"]
      );
    }

    if (!("name" in address)) {
      address.name = lazy.FormAutofillNameUtils.joinNameParts({
        given: address["given-name"],
        middle: address["additional-name"],
        family: address["family-name"],
      });
    }

    for (const fieldName of fieldOrder) {
      let string = address[fieldName];
      if (string) {
        parts.push(string);
      }
    }
    return parts.join(", ");
  },

  /**
   * Internal method to split an address to multiple parts per the provided delimiter,
   * removing blank parts.
   *
   * @param {string} address The address the split
   * @param {string} [delimiter] The separator that is used between lines in the address
   * @returns {string[]}
   */
  _toStreetAddressParts(address, delimiter = "\n") {
    let array = typeof address == "string" ? address.split(delimiter) : address;

    if (!Array.isArray(array)) {
      return [];
    }
    return array.map(s => (s ? s.trim() : "")).filter(s => s);
  },

  /**
   * Converts a street address to a single line, removing linebreaks marked by the delimiter
   *
   * @param {string} address The address the convert
   * @param {string} [delimiter] The separator that is used between lines in the address
   * @returns {string}
   */
  toOneLineAddress(address, delimiter = "\n") {
    let addressParts = this._toStreetAddressParts(address, delimiter);
    return addressParts.join(this.getAddressSeparator());
  },

  /**
   * In-place concatenate tel-related components into a single "tel" field and
   * delete unnecessary fields.
   *
   * @param {object} address An address record.
   */
  compressTel(address) {
    let telCountryCode = address["tel-country-code"] || "";
    let telAreaCode = address["tel-area-code"] || "";

    if (!address.tel) {
      if (address["tel-national"]) {
        address.tel = telCountryCode + address["tel-national"];
      } else if (address["tel-local"]) {
        address.tel = telCountryCode + telAreaCode + address["tel-local"];
      } else if (address["tel-local-prefix"] && address["tel-local-suffix"]) {
        address.tel =
          telCountryCode +
          telAreaCode +
          address["tel-local-prefix"] +
          address["tel-local-suffix"];
      }
    }

    for (let field in address) {
      if (field != "tel" && this.getCategoryFromFieldName(field) == "tel") {
        delete address[field];
      }
    }
  },

  /**
   * Determines if an element can be autofilled or not.
   *
   * @param {HTMLElement} element
   * @returns {boolean} true if the element can be autofilled
   */
  isFieldAutofillable(element) {
    return element && !element.readOnly && !element.disabled;
  },

  /**
   * Determines if an element is visually hidden or not.
   *
   * @param {HTMLElement} element
   * @param {boolean} visibilityCheck true to run visiblity check against
   *                  element.checkVisibility API. Otherwise, test by only checking
   *                  `hidden` and `display` attributes
   * @returns {boolean} true if the element is visible
   */
  isFieldVisible(element, visibilityCheck = true) {
    if (
      visibilityCheck &&
      element.checkVisibility &&
      !FormAutofillUtils.ignoreVisibilityCheck
    ) {
      if (
        !element.checkVisibility({
          checkOpacity: true,
          checkVisibilityCSS: true,
        })
      ) {
        return false;
      }
    } else if (element.hidden || element.style.display == "none") {
      return false;
    }

    return element.getAttribute("aria-hidden") != "true";
  },

  /**
   * Determines if an element is eligible to be used by credit card or address autofill.
   *
   * @param {HTMLElement} element
   * @returns {boolean} true if element can be used by credit card or address autofill
   */
  isCreditCardOrAddressFieldType(element) {
    if (!element) {
      return false;
    }

    if (HTMLInputElement.isInstance(element)) {
      // `element.type` can be recognized as `text`, if it's missing or invalid.
      return ELIGIBLE_INPUT_TYPES.includes(element.type);
    }

    return HTMLSelectElement.isInstance(element);
  },

  loadDataFromScript(url, sandbox = {}) {
    Services.scriptloader.loadSubScript(url, sandbox);
    return sandbox;
  },

  /**
   * Get country address data and fallback to US if not found.
   * See AddressMetaDataLoader.#loadData for more details of addressData structure.
   *
   * @param {string} [country=FormAutofill.DEFAULT_REGION]
   *        The country code for requesting specific country's metadata. It'll be
   *        default region if parameter is not set.
   * @param {string} [level1=null]
   *        Return address level 1/level 2 metadata if parameter is set.
   * @returns {object|null}
   *          Return metadata of specific region with default locale and other supported
   *          locales. We need to return a default country metadata for layout format
   *          and collator, but for sub-region metadata we'll just return null if not found.
   */
  getCountryAddressRawData(
    country = FormAutofill.DEFAULT_REGION,
    level1 = null
  ) {
    let metadata = lazy.AddressMetaDataLoader.getData(country, level1);
    if (!metadata) {
      if (level1) {
        return null;
      }
      // Fallback to default region if we couldn't get data from given country.
      if (country != FormAutofill.DEFAULT_REGION) {
        metadata = lazy.AddressMetaDataLoader.getData(
          FormAutofill.DEFAULT_REGION
        );
      }
    }

    // TODO: Now we fallback to US if we couldn't get data from default region,
    //       but it could be removed in bug 1423464 if it's not necessary.
    if (!metadata) {
      metadata = lazy.AddressMetaDataLoader.getData("US");
    }
    return metadata;
  },

  /**
   * Get country address data with default locale.
   *
   * @param {string} country
   * @param {string} level1
   * @returns {object|null} Return metadata of specific region with default locale.
   *          NOTE: The returned data may be for a default region if the
   *          specified one cannot be found. Callers who only want the specific
   *          region should check the returned country code.
   */
  getCountryAddressData(country, level1) {
    let metadata = this.getCountryAddressRawData(country, level1);
    return metadata && metadata.defaultLocale;
  },

  /**
   * Get country address data with all locales.
   *
   * @param {string} country
   * @param {string} level1
   * @returns {Array<object> | null}
   *          Return metadata of specific region with all the locales.
   *          NOTE: The returned data may be for a default region if the
   *          specified one cannot be found. Callers who only want the specific
   *          region should check the returned country code.
   */
  getCountryAddressDataWithLocales(country, level1) {
    let metadata = this.getCountryAddressRawData(country, level1);
    return metadata && [metadata.defaultLocale, ...metadata.locales];
  },

  /**
   * Get the collators based on the specified country.
   *
   * @param {string}  country The specified country.
   * @param {object}  [options = {}] a list of options for this method
   * @param {boolean} [options.ignorePunctuation = true] Whether punctuation should be ignored.
   * @param {string}  [options.sensitivity = 'base'] Which differences in the strings should lead to non-zero result values
   * @param {string}  [options.usage = 'search'] Whether the comparison is for sorting or for searching for matching strings
   * @returns {Array} An array containing several collator objects.
   */
  getSearchCollators(
    country,
    { ignorePunctuation = true, sensitivity = "base", usage = "search" } = {}
  ) {
    // TODO: Only one language should be used at a time per country. The locale
    //       of the page should be taken into account to do this properly.
    //       We are going to support more countries in bug 1370193 and this
    //       should be addressed when we start to implement that bug.

    if (!this._collators[country]) {
      let dataset = this.getCountryAddressData(country);
      let languages = dataset.languages || [dataset.lang];
      let options = {
        ignorePunctuation,
        sensitivity,
        usage,
      };
      this._collators[country] = languages.map(
        lang => new Intl.Collator(lang, options)
      );
    }
    return this._collators[country];
  },

  // Based on the list of fields abbreviations in
  // https://github.com/googlei18n/libaddressinput/wiki/AddressValidationMetadata
  FIELDS_LOOKUP: {
    N: "name",
    O: "organization",
    A: "street-address",
    S: "address-level1",
    C: "address-level2",
    D: "address-level3",
    Z: "postal-code",
    n: "newLine",
  },

  /**
   * Parse a country address format string and outputs an array of fields.
   * Spaces, commas, and other literals are ignored in this implementation.
   * For example, format string "%A%n%C, %S" should return:
   * [
   *   {fieldId: "street-address", newLine: true},
   *   {fieldId: "address-level2"},
   *   {fieldId: "address-level1"},
   * ]
   *
   * @param   {string} fmt Country address format string
   * @returns {Array<object>} List of fields
   */
  parseAddressFormat(fmt) {
    if (!fmt) {
      throw new Error("fmt string is missing.");
    }

    return fmt.match(/%[^%]/g).reduce((parsed, part) => {
      // Take the first letter of each segment and try to identify it
      let fieldId = this.FIELDS_LOOKUP[part[1]];
      // Early return if cannot identify part.
      if (!fieldId) {
        return parsed;
      }
      // If a new line is detected, add an attribute to the previous field.
      if (fieldId == "newLine") {
        let size = parsed.length;
        if (size) {
          parsed[size - 1].newLine = true;
        }
        return parsed;
      }
      return parsed.concat({ fieldId });
    }, []);
  },

  /**
   * Used to populate dropdowns in the UI (e.g. FormAutofill preferences).
   * Use findAddressSelectOption for matching a value to a region.
   *
   * @param {string[]} subKeys An array of regionCode strings
   * @param {string[]} subIsoids An array of ISO ID strings, if provided will be preferred over the key
   * @param {string[]} subNames An array of regionName strings
   * @param {string[]} subLnames An array of latinised regionName strings
   * @returns {Map?} Returns null if subKeys or subNames are not truthy.
   *                   Otherwise, a Map will be returned mapping keys -> names.
   */
  buildRegionMapIfAvailable(subKeys, subIsoids, subNames, subLnames) {
    // Not all regions have sub_keys. e.g. DE
    if (
      !subKeys ||
      !subKeys.length ||
      (!subNames && !subLnames) ||
      (subNames && subKeys.length != subNames.length) ||
      (subLnames && subKeys.length != subLnames.length)
    ) {
      return null;
    }

    // Overwrite subKeys with subIsoids, when available
    if (subIsoids && subIsoids.length && subIsoids.length == subKeys.length) {
      for (let i = 0; i < subIsoids.length; i++) {
        if (subIsoids[i]) {
          subKeys[i] = subIsoids[i];
        }
      }
    }

    // Apply sub_lnames if sub_names does not exist
    let names = subNames || subLnames;
    return new Map(subKeys.map((key, index) => [key, names[index]]));
  },

  /**
   * Parse a require string and outputs an array of fields.
   * Spaces, commas, and other literals are ignored in this implementation.
   * For example, a require string "ACS" should return:
   * ["street-address", "address-level2", "address-level1"]
   *
   * @param   {string} requireString Country address require string
   * @returns {Array<string>} List of fields
   */
  parseRequireString(requireString) {
    if (!requireString) {
      throw new Error("requireString string is missing.");
    }

    return requireString.split("").map(fieldId => this.FIELDS_LOOKUP[fieldId]);
  },

  /**
   * Use address data and alternative country name list to identify a country code from a
   * specified country name.
   *
   * @param   {string} countryName A country name to be identified
   * @param   {string} [countrySpecified] A country code indicating that we only
   *                                      search its alternative names if specified.
   * @returns {string} The matching country code.
   */
  identifyCountryCode(countryName, countrySpecified) {
    if (!countryName) {
      return null;
    }

    if (lazy.AddressMetaDataLoader.getData(countryName)) {
      return countryName;
    }

    const countries = countrySpecified
      ? [countrySpecified]
      : [...FormAutofill.countries.keys()];

    for (const country of countries) {
      let collators = this.getSearchCollators(country);
      let metadata = this.getCountryAddressData(country);
      if (country != metadata.key) {
        // We hit the fallback logic in getCountryAddressRawData so ignore it as
        // it's not related to `country` and use the name from l10n instead.
        metadata = {
          id: `data/${country}`,
          key: country,
          name: FormAutofill.countries.get(country),
        };
      }
      let alternativeCountryNames = metadata.alternative_names || [
        metadata.name,
      ];
      let reAlternativeCountryNames = this._reAlternativeCountryNames[country];
      if (!reAlternativeCountryNames) {
        reAlternativeCountryNames = this._reAlternativeCountryNames[country] =
          [];
      }

      if (countryName.length == 3) {
        if (this.strCompare(metadata.alpha_3_code, countryName, collators)) {
          return country;
        }
      }

      for (let i = 0; i < alternativeCountryNames.length; i++) {
        let name = alternativeCountryNames[i];
        let reName = reAlternativeCountryNames[i];
        if (!reName) {
          reName = reAlternativeCountryNames[i] = new RegExp(
            "\\b" + this.escapeRegExp(name) + "\\b",
            "i"
          );
        }

        if (
          this.strCompare(name, countryName, collators) ||
          reName.test(countryName)
        ) {
          return country;
        }
      }
    }

    return null;
  },

  findSelectOption(selectEl, record, fieldName) {
    if (this.isAddressField(fieldName)) {
      return this.findAddressSelectOption(selectEl.options, record, fieldName);
    }
    if (this.isCreditCardField(fieldName)) {
      return this.findCreditCardSelectOption(selectEl, record, fieldName);
    }
    return null;
  },

  /**
   * Try to find the abbreviation of the given sub-region name
   *
   * @param   {string[]} subregionValues A list of inferable sub-region values.
   * @param   {string} [country] A country name to be identified.
   * @returns {string} The matching sub-region abbreviation.
   */
  getAbbreviatedSubregionName(subregionValues, country) {
    let values = Array.isArray(subregionValues)
      ? subregionValues
      : [subregionValues];

    let collators = this.getSearchCollators(country);
    for (let metadata of this.getCountryAddressDataWithLocales(country)) {
      let {
        sub_keys: subKeys,
        sub_names: subNames,
        sub_lnames: subLnames,
      } = metadata;
      if (!subKeys) {
        // Not all regions have sub_keys. e.g. DE
        continue;
      }
      // Apply sub_lnames if sub_names does not exist
      subNames = subNames || subLnames;

      let speculatedSubIndexes = [];
      for (const val of values) {
        let identifiedValue = this.identifyValue(
          subKeys,
          subNames,
          val,
          collators
        );
        if (identifiedValue) {
          return identifiedValue;
        }

        // Predict the possible state by partial-matching if no exact match.
        [subKeys, subNames].forEach(sub => {
          speculatedSubIndexes.push(
            sub.findIndex(token => {
              let pattern = new RegExp(
                "\\b" + this.escapeRegExp(token) + "\\b"
              );

              return pattern.test(val);
            })
          );
        });
      }
      let subKey = subKeys[speculatedSubIndexes.find(i => !!~i)];
      if (subKey) {
        return subKey;
      }
    }
    return null;
  },

  /**
   * Find the option element from select element.
   * 1. Try to find the locale using the country from address.
   * 2. First pass try to find exact match.
   * 3. Second pass try to identify values from address value and options,
   *    and look for a match.
   *
   * @param   {Array<{text: string, value: string}>} options
   * @param   {object} address
   * @param   {string} fieldName
   * @returns {DOMElement}
   */
  findAddressSelectOption(options, address, fieldName) {
    if (options.length > 512) {
      // Allow enough space for all countries (roughly 300 distinct values) and all
      // timezones (roughly 400 distinct values), plus some extra wiggle room.
      return null;
    }
    let value = address[fieldName];
    if (!value) {
      return null;
    }

    let collators = this.getSearchCollators(address.country);

    for (const option of options) {
      if (
        this.strCompare(value, option.value, collators) ||
        this.strCompare(value, option.text, collators)
      ) {
        return option;
      }
    }

    switch (fieldName) {
      case "address-level1": {
        let { country } = address;
        let identifiedValue = this.getAbbreviatedSubregionName(
          [value],
          country
        );
        // No point going any further if we cannot identify value from address level 1
        if (!identifiedValue) {
          return null;
        }
        for (let dataset of this.getCountryAddressDataWithLocales(country)) {
          let keys = dataset.sub_keys;
          if (!keys) {
            // Not all regions have sub_keys. e.g. DE
            continue;
          }
          // Apply sub_lnames if sub_names does not exist
          let names = dataset.sub_names || dataset.sub_lnames;

          // Go through options one by one to find a match.
          // Also check if any option contain the address-level1 key.
          let pattern = new RegExp(
            "\\b" + this.escapeRegExp(identifiedValue) + "\\b",
            "i"
          );
          for (const option of options) {
            let optionValue = this.identifyValue(
              keys,
              names,
              option.value,
              collators
            );
            let optionText = this.identifyValue(
              keys,
              names,
              option.text,
              collators,
              true
            );
            if (
              identifiedValue === optionValue ||
              identifiedValue === optionText ||
              pattern.test(option.value)
            ) {
              return option;
            }
          }
        }
        break;
      }
      case "country": {
        if (this.getCountryAddressData(value)) {
          for (const option of options) {
            if (
              this.identifyCountryCode(option.text, value) ||
              this.identifyCountryCode(option.value, value)
            ) {
              return option;
            }
          }
        }
        break;
      }
    }

    return null;
  },

  /**
   * Find the option element from xul menu popups, as used in address capture
   * doorhanger.
   *
   * This is a proxy to `findAddressSelectOption`, which expects HTML select
   * DOM nodes and operates on options instead of xul menuitems.
   *
   * NOTE: This is a temporary solution until Bug 1886949 is landed. This
   * method will then be removed `findAddressSelectOption` will be used
   * directly.
   *
   * @param   {XULPopupElement} menupopup
   * @param   {object} address
   * @param   {string} fieldName
   * @returns {XULElement}
   */
  findAddressSelectOptionWithMenuPopup(menupopup, address, fieldName) {
    const options = Array.from(menupopup.childNodes).map(menuitem => ({
      text: menuitem.label,
      value: menuitem.value,
      menuitem,
    }));

    return this.findAddressSelectOption(options, address, fieldName)?.menuitem;
  },

  findCreditCardSelectOption(selectEl, creditCard, fieldName) {
    let oneDigitMonth = creditCard["cc-exp-month"]
      ? creditCard["cc-exp-month"].toString()
      : null;
    let twoDigitsMonth = oneDigitMonth ? oneDigitMonth.padStart(2, "0") : null;
    let fourDigitsYear = creditCard["cc-exp-year"]
      ? creditCard["cc-exp-year"].toString()
      : null;
    let twoDigitsYear = fourDigitsYear ? fourDigitsYear.substr(2, 2) : null;
    let options = Array.from(selectEl.options);

    switch (fieldName) {
      case "cc-exp-month": {
        if (!oneDigitMonth) {
          return null;
        }
        for (let option of options) {
          if (
            [option.text, option.label, option.value].some(s => {
              let result = /[1-9]\d*/.exec(s);
              return result && result[0] == oneDigitMonth;
            })
          ) {
            return option;
          }
        }
        break;
      }
      case "cc-exp-year": {
        if (!fourDigitsYear) {
          return null;
        }
        for (let option of options) {
          if (
            [option.text, option.label, option.value].some(
              s => s == twoDigitsYear || s == fourDigitsYear
            )
          ) {
            return option;
          }
        }
        break;
      }
      case "cc-exp": {
        if (!oneDigitMonth || !fourDigitsYear) {
          return null;
        }
        let patterns = [
          oneDigitMonth + "/" + twoDigitsYear, // 8/22
          oneDigitMonth + "/" + fourDigitsYear, // 8/2022
          twoDigitsMonth + "/" + twoDigitsYear, // 08/22
          twoDigitsMonth + "/" + fourDigitsYear, // 08/2022
          oneDigitMonth + "-" + twoDigitsYear, // 8-22
          oneDigitMonth + "-" + fourDigitsYear, // 8-2022
          twoDigitsMonth + "-" + twoDigitsYear, // 08-22
          twoDigitsMonth + "-" + fourDigitsYear, // 08-2022
          twoDigitsYear + "-" + twoDigitsMonth, // 22-08
          fourDigitsYear + "-" + twoDigitsMonth, // 2022-08
          fourDigitsYear + "/" + oneDigitMonth, // 2022/8
          twoDigitsMonth + twoDigitsYear, // 0822
          twoDigitsYear + twoDigitsMonth, // 2208
        ];

        for (let option of options) {
          if (
            [option.text, option.label, option.value].some(str =>
              patterns.some(pattern => str.includes(pattern))
            )
          ) {
            return option;
          }
        }
        break;
      }
      case "cc-type": {
        let network = creditCard["cc-type"] || "";
        for (let option of options) {
          if (
            [option.text, option.label, option.value].some(
              s => lazy.CreditCard.getNetworkFromName(s) == network
            )
          ) {
            return option;
          }
        }
        break;
      }
    }

    return null;
  },

  /**
   * Try to match value with keys and names, but always return the key.
   * If inexactMatch is true, then a substring match is performed, otherwise
   * the string must match exactly.
   *
   * @param   {Array<string>} keys
   * @param   {Array<string>} names
   * @param   {string} value
   * @param   {Array} collators
   * @param   {bool} inexactMatch
   * @returns {string}
   */
  identifyValue(keys, names, value, collators, inexactMatch = false) {
    let resultKey = keys.find(key => this.strCompare(value, key, collators));
    if (resultKey) {
      return resultKey;
    }

    let index = names.findIndex(name =>
      inexactMatch
        ? this.strInclude(value, name, collators)
        : this.strCompare(value, name, collators)
    );
    if (index !== -1) {
      return keys[index];
    }

    return null;
  },

  /**
   * Compare if two strings are the same.
   *
   * @param   {string} a
   * @param   {string} b
   * @param   {Array} collators
   * @returns {boolean}
   */
  strCompare(a = "", b = "", collators) {
    return collators.some(collator => !collator.compare(a, b));
  },

  /**
   * Determine whether one string(b) may be found within another string(a)
   *
   * @param   {string} a
   * @param   {string} b
   * @param   {Array} collators
   * @returns {boolean} True if the string is found
   */
  strInclude(a = "", b = "", collators) {
    const len = a.length - b.length;
    for (let i = 0; i <= len; i++) {
      if (this.strCompare(a.substring(i, i + b.length), b, collators)) {
        return true;
      }
    }
    return false;
  },

  /**
   * Escaping user input to be treated as a literal string within a regular
   * expression.
   *
   * @param   {string} string
   * @returns {string}
   */
  escapeRegExp(string) {
    return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  },

  /**
   * Get formatting information of a given country
   *
   * @param   {string} country
   * @returns {object}
   *         {
   *           {string} addressLevel3L10nId
   *           {string} addressLevel2L10nId
   *           {string} addressLevel1L10nId
   *           {string} postalCodeL10nId
   *           {object} fieldsOrder
   *           {string} postalCodePattern
   *         }
   */
  getFormFormat(country) {
    let dataset = this.getCountryAddressData(country);
    // We hit a country fallback in `getCountryAddressRawData` but it's not relevant here.
    if (country != dataset.key) {
      // Use a sparse object so the below default values take effect.
      dataset = {
        /**
         * Even though data/ZZ only has address-level2, include the other levels
         * in case they are needed for unknown countries. Users can leave the
         * unnecessary fields blank which is better than forcing users to enter
         * the data in incorrect fields.
         */
        fmt: "%N%n%O%n%A%n%C %S %Z",
      };
    }
    return {
      // When particular values are missing for a country, the
      // data/ZZ value should be used instead:
      // https://chromium-i18n.appspot.com/ssl-aggregate-address/data/ZZ
      addressLevel3L10nId: this.getAddressFieldL10nId(
        dataset.sublocality_name_type || "suburb"
      ),
      addressLevel2L10nId: this.getAddressFieldL10nId(
        dataset.locality_name_type || "city"
      ),
      addressLevel1L10nId: this.getAddressFieldL10nId(
        dataset.state_name_type || "province"
      ),
      addressLevel1Options: this.buildRegionMapIfAvailable(
        dataset.sub_keys,
        dataset.sub_isoids,
        dataset.sub_names,
        dataset.sub_lnames
      ),
      countryRequiredFields: this.parseRequireString(dataset.require || "AC"),
      fieldsOrder: this.parseAddressFormat(dataset.fmt || "%N%n%O%n%A%n%C"),
      postalCodeL10nId: this.getAddressFieldL10nId(
        dataset.zip_name_type || "postal-code"
      ),
      postalCodePattern: dataset.zip,
    };
  },
  /**
   * Converts a Map to an array of objects with `value` and `text` properties ( option like).
   *
   * @param {Map} optionsMap
   * @returns {Array<{ value: string, text: string }>|null}
   */
  optionsMapToArray(optionsMap) {
    return optionsMap?.size
      ? [...optionsMap].map(([value, text]) => ({ value, text }))
      : null;
  },

  /**
   * Get flattened form layout information of a given country
   * TODO(Bug 1891730): Remove getFormFormat and use this instead.
   *
   * @param {object} record - An object containing at least the 'country' property.
   * @returns {Array} Flattened array with the address fiels in order.
   */
  getFormLayout(record) {
    const formFormat = this.getFormFormat(record.country);
    let fieldsInOrder = formFormat.fieldsOrder;

    // Add missing fields that are always present but not in the .fmt of addresses
    // TODO: extend libaddress later to support this if possible
    fieldsInOrder = [
      ...fieldsInOrder,
      {
        fieldId: "country",
        options: this.optionsMapToArray(FormAutofill.countries),
        required: true,
      },
      { fieldId: "tel", type: "tel" },
      { fieldId: "email", type: "email" },
    ];

    const addressLevel1Options = this.optionsMapToArray(
      formFormat.addressLevel1Options
    );

    const addressLevel1SelectedValue = addressLevel1Options
      ? this.findAddressSelectOption(
          addressLevel1Options,
          record,
          "address-level1"
        )?.value
      : record["address-level1"];

    for (const field of fieldsInOrder) {
      const flattenedObject = {
        fieldId: field.fieldId,
        newLine: field.newLine,
        l10nId: this.getAddressFieldL10nId(field.fieldId),
        required: formFormat.countryRequiredFields.includes(field.fieldId),
        value: record[field.fieldId] ?? "",
        ...(field.fieldId === "street-address" && {
          l10nId: "autofill-address-street",
          multiline: true,
        }),
        ...(field.fieldId === "address-level1" && {
          l10nId: formFormat.addressLevel1L10nId,
          options: addressLevel1Options,
          value: addressLevel1SelectedValue,
        }),
        ...(field.fieldId === "address-level2" && {
          l10nId: formFormat.addressLevel2L10nId,
        }),
        ...(field.fieldId === "address-level3" && {
          l10nId: formFormat.addressLevel3L10nId,
        }),
        ...(field.fieldId === "postal-code" && {
          pattern: formFormat.postalCodePattern,
          l10nId: formFormat.postalCodeL10nId,
        }),
      };
      Object.assign(field, flattenedObject);
    }

    return fieldsInOrder;
  },

  getAddressFieldL10nId(type) {
    return "autofill-address-" + type.replace(/_/g, "-");
  },

  CC_FATHOM_NONE: 0,
  CC_FATHOM_JS: 1,
  CC_FATHOM_NATIVE: 2,
  isFathomCreditCardsEnabled() {
    return this.ccHeuristicsMode != this.CC_FATHOM_NONE;
  },

  /**
   * Transform the key in FormAutofillConfidences (defined in ChromeUtils.webidl)
   * to fathom recognized field type.
   *
   * @param {string} key key from FormAutofillConfidences dictionary
   * @returns {string} fathom field type
   */
  formAutofillConfidencesKeyToCCFieldType(key) {
    const MAP = {
      ccNumber: "cc-number",
      ccName: "cc-name",
      ccType: "cc-type",
      ccExp: "cc-exp",
      ccExpMonth: "cc-exp-month",
      ccExpYear: "cc-exp-year",
    };
    return MAP[key];
  },
  /**
   * Generates the localized os dialog message that
   * prompts the user to reauthenticate
   *
   * @param {string} msgMac fluent message id for macos clients
   * @param {string} msgWin fluent message id for windows clients
   * @param {string} msgOther fluent message id for other clients
   * @param {string} msgLin (optional) fluent message id for linux clients
   * @returns {string} localized os prompt message
   */
  reauthOSPromptMessage(msgMac, msgWin, msgOther, msgLin = null) {
    const platform = AppConstants.platform;
    let messageID;

    switch (platform) {
      case "win":
        messageID = msgWin;
        break;
      case "macosx":
        messageID = msgMac;
        break;
      case "linux":
        messageID = msgLin ?? msgOther;
        break;
      default:
        messageID = msgOther;
    }
    return lazy.l10n.formatValueSync(messageID);
  },

  /**
   * Retrieves a unique identifier for a given DOM element.
   * Note that the identifier generated by ContentDOMReference is an object but
   * this API serializes it to string to make lookup easier.
   *
   * @param {Element} element The DOM element from which to generate an identifier.
   * @returns {string} A unique identifier for the element.
   */
  getElementIdentifier(element) {
    let id;
    try {
      id = JSON.stringify(lazy.ContentDOMReference.get(element));
    } catch {
      // This is needed because when running in xpc-shell test, we don't have
      const entry = Object.entries(this._elementByElementId).find(
        e => e[1] == element
      );
      if (entry) {
        id = entry[0];
      } else {
        id = Services.uuid.generateUUID().toString();
        this._elementByElementId[id] = element;
      }
    }
    return id;
  },

  /**
   * Maps element identifiers to their corresponding DOM elements.
   * Only used when we can't get the identifier via ContentDOMReference,
   * for example, xpcshell test.
   */
  _elementByElementId: {},

  /**
   * Retrieves the DOM element associated with the specific identifier.
   * The identifier should be generated with the `getElementIdentifier` API
   *
   * @param {string} elementId The identifier of the element.
   * @returns {Element} The DOM element associated with the given identifier.
   */
  getElementByIdentifier(elementId) {
    let element;
    try {
      element = lazy.ContentDOMReference.resolve(JSON.parse(elementId));
    } catch {
      element = this._elementByElementId[elementId];
    }
    return element;
  },
};

ChromeUtils.defineLazyGetter(FormAutofillUtils, "stringBundle", function () {
  return Services.strings.createBundle(
    "chrome://formautofill/locale/formautofill.properties"
  );
});

ChromeUtils.defineLazyGetter(FormAutofillUtils, "brandBundle", function () {
  return Services.strings.createBundle(
    "chrome://branding/locale/brand.properties"
  );
});

XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofillUtils,
  "_reauthEnabledByUser",
  "extensions.formautofill.reauth.enabled",
  false
);

XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofillUtils,
  "ccHeuristicsMode",
  "extensions.formautofill.creditCards.heuristics.mode",
  0
);

XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofillUtils,
  "ccFathomConfidenceThreshold",
  "extensions.formautofill.creditCards.heuristics.fathom.confidenceThreshold",
  null,
  null,
  pref => parseFloat(pref)
);

XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofillUtils,
  "ccFathomHighConfidenceThreshold",
  "extensions.formautofill.creditCards.heuristics.fathom.highConfidenceThreshold",
  null,
  null,
  pref => parseFloat(pref)
);

XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofillUtils,
  "ccFathomTestConfidence",
  "extensions.formautofill.creditCards.heuristics.fathom.testConfidence",
  null,
  null,
  pref => parseFloat(pref)
);

// This is only used in iOS
XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofillUtils,
  "focusOnAutofill",
  "extensions.formautofill.focusOnAutofill",
  true
);

// This is only used for testing
XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofillUtils,
  "ignoreVisibilityCheck",
  "extensions.formautofill.test.ignoreVisibilityCheck",
  false
);
PK
!</�		'modules/shared/FormStateManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  FormLikeFactory: "resource://gre/modules/FormLikeFactory.sys.mjs",
  FormAutofillHandler:
    "resource://gre/modules/shared/FormAutofillHandler.sys.mjs",
});

export class FormStateManager {
  constructor(onFilledModifiedCallback) {
    /**
     * @type {WeakMap} mapping FormLike root HTML elements to FormAutofillHandler objects.
     */
    this._formsDetails = new WeakMap();

    this.onFilledModifiedCallback = onFilledModifiedCallback;
  }

  /**
   * Get the form's handler from cache which is created after page identified.
   *
   * @param {HTMLInputElement} element Focused input which triggered profile searching
   * @returns {Array<object> | null}
   *          Return target form's handler from content cache
   *          (or return null if the information is not found in the cache).
   */
  getFormHandler(element) {
    if (!element) {
      return null;
    }
    let rootElement = lazy.FormLikeFactory.findRootForField(element);
    return this._formsDetails.get(rootElement);
  }

  /**
   * Identifies and handles autofill fields in a form element.
   *
   * This function retrieves a form handler for the given element and returns the
   * form handler. If the form handler already exists and the form does not change
   * since last time we identify its fields, it sets `newFieldsIdentifided` to false.
   *
   * @param {HTMLElement} element The form element to identify autofill fields for.
   * @returns {object} a {handler, newFieldsIdentified} object
   */
  identifyAutofillFields(element) {
    let handler = this.getFormHandler(element);
    if (handler && !handler.updateFormIfNeeded(element)) {
      return { handler, newFieldsIdentified: false };
    }

    if (!handler) {
      handler = new lazy.FormAutofillHandler(
        lazy.FormLikeFactory.createFromField(element),
        this.onFilledModifiedCallback
      );
      this._formsDetails.set(handler.form.rootElement, handler);
    }

    handler.collectFormFields();
    return { handler, newFieldsIdentified: true };
  }
}

export default FormStateManager;
PK
!<}�~�Y�Y'modules/shared/HeuristicsRegExp.sys.mjs/* eslint-disable no-useless-concat */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// prettier-ignore
export const HeuristicsRegExp = {
  RULES: {
    email: undefined,
    tel: undefined,
    organization: undefined,
    "street-address": undefined,
    "address-line1": undefined,
    "address-line2": undefined,
    "address-line3": undefined,
    "address-level2": undefined,
    "address-level1": undefined,
    "postal-code": undefined,
    country: undefined,
    // Note: We place the `cc-name` field for Credit Card first, because
    // it is more specific than the `name` field below and we want to check
    // for it before we catch the more generic one.
    "cc-name": undefined,
    name: undefined,
    "given-name": undefined,
    "additional-name": undefined,
    "family-name": undefined,
    "cc-csc": undefined,
    "cc-number": undefined,
    "cc-exp-month": undefined,
    "cc-exp-year": undefined,
    "cc-exp": undefined,
    "cc-type": undefined,
  },

  // regular expressions that only apply to label
  LABEL_RULES: {
    "address-line1": undefined,
    "address-line2": undefined,
  },

  RULE_SETS: [
    //=========================================================================
    // Firefox-specific rules
    {
      "address-line1": "addrline1|address_1|addl1",
      "address-line2": "addrline2|address_2|addl2",
      "address-line3": "addrline3|address_3|addl3",
      "address-level1": "land", // de-DE
      "additional-name": "apellido.?materno|lastlastname",
      "cc-name":
        "accountholdername" +
        "|titulaire", // fr-FR
      "cc-number":
        "(cc|kk)nr",    // de-DE
      "cc-exp":
        "ważna.*do" +        // pl-PL
        "|data.*ważności" +  // pl-PL
        "|mm\\s*[\\-\\/]\\s*yy" +  // en-US
        "|mm\\s*[\\-\\/]\\s*aa" +  // es-ES
        "|mm\\s*[\\-\\/]\\s*jj" +  // de-AT
        "|vervaldatum",            // nl-NL
      "cc-exp-month":
        "month" +
        "|(cc|kk)month" +    // de-DE
        "|miesiąc" +         // pl-PL
        "|mes" +             // es-ES
        "|maand",            // nl-NL
      "cc-exp-year":
        "year" +
        "|(cc|kk)year" +     // de-DE
        "|rok" +             // pl-PL
        "|(anno|año)" +      // es-ES
        "|jaar",             // nl-NL
      "cc-type":
        "type" +
        "|kartenmarke" +     // de-DE
        "|typ.*karty",       // pl-PL
      "cc-csc":
        "(\\bcvn\\b|\\bcvv\\b|\\bcvc\\b|\\bcsc\\b|\\bcvd\\b|\\bcid\\b|\\bccv\\b)",
    },

    //=========================================================================
    // These are the rules used by Bitwarden [0], converted into RegExp form.
    // [0] https://github.com/bitwarden/browser/blob/c2b8802201fac5e292d55d5caf3f1f78088d823c/src/services/autofill.service.ts#L436
    {
      email: "(^e-?mail$)|(^email-?address$)",

      tel:
        "(^phone$)" +
        "|(^mobile$)" +
        "|(^mobile-?phone$)" +
        "|(^tel$)" +
        "|(^telephone$)" +
        "|(^phone-?number$)",

      organization:
        "(^company$)" +
        "|(^company-?name$)" +
        "|(^organization$)" +
        "|(^organization-?name$)",

      "street-address":
        "(^address$)" +
        "|(^street-?address$)" +
        "|(^addr$)" +
        "|(^street$)" +
        "|(^mailing-?addr(ess)?$)" + // Modified to not grab lines, below
        "|(^billing-?addr(ess)?$)" + // Modified to not grab lines, below
        "|(^mail-?addr(ess)?$)" + // Modified to not grab lines, below
        "|(^bill-?addr(ess)?$)", // Modified to not grab lines, below

      "address-line1":
        "(^address-?1$)" +
        "|(^address-?line-?1$)" +
        "|(^addr-?1$)" +
        "|(^street-?1$)",

      "address-line2":
        "(^address-?2$)" +
        "|(^address-?line-?2$)" +
        "|(^addr-?2$)" +
        "|(^street-?2$)",

      "address-line3":
        "(^address-?3$)" +
        "|(^address-?line-?3$)" +
        "|(^addr-?3$)" +
        "|(^street-?3$)",

      "address-level2":
        "(^city$)" +
        "|(^town$)" +
        "|(^address-?level-?2$)" +
        "|(^address-?city$)" +
        "|(^address-?town$)",

      "address-level1":
        "(^state$)" +
        "|(^province$)" +
        "|(^provence$)" +
        "|(^address-?level-?1$)" +
        "|(^address-?state$)" +
        "|(^address-?province$)",

      "postal-code":
        "(^postal$)" +
        "|(^zip$)" +
        "|(^zip2$)" +
        "|(^zip-?code$)" +
        "|(^postal-?code$)" +
        "|(^post-?code$)" +
        "|(^address-?zip$)" +
        "|(^address-?postal$)" +
        "|(^address-?code$)" +
        "|(^address-?postal-?code$)" +
        "|(^address-?zip-?code$)",

      country:
        "(^country$)" +
        "|(^country-?code$)" +
        "|(^country-?name$)" +
        "|(^address-?country$)" +
        "|(^address-?country-?name$)" +
        "|(^address-?country-?code$)",

      name: "(^name$)|full-?name|your-?name",

      "given-name":
        "(^f-?name$)" +
        "|(^first-?name$)" +
        "|(^given-?name$)" +
        "|(^first-?n$)",

      "additional-name":
        "(^m-?name$)" +
        "|(^middle-?name$)" +
        "|(^additional-?name$)" +
        "|(^middle-?initial$)" +
        "|(^middle-?n$)" +
        "|(^middle-?i$)",

      "family-name":
        "(^l-?name$)" +
        "|(^last-?name$)" +
        "|(^s-?name$)" +
        "|(^surname$)" +
        "|(^family-?name$)" +
        "|(^family-?n$)" +
        "|(^last-?n$)",

      "cc-name":
        "cc-?name" +
        "|card-?name" +
        "|cardholder-?name" +
        "|cardholder" +
        // "|(^name$)" + // Removed to avoid overwriting "name", above.
        "|(^nom$)",

      "cc-number":
        "cc-?number" +
        "|cc-?num" +
        "|card-?number" +
        "|card-?num" +
        "|(^number$)" +
        "|(^cc$)" +
        "|cc-?no" +
        "|card-?no" +
        "|(^credit-?card$)" +
        "|numero-?carte" +
        "|(^carte$)" +
        "|(^carte-?credit$)" +
        "|num-?carte" +
        "|cb-?num",

      "cc-exp":
        "(^cc-?exp$)" +
        "|(^card-?exp$)" +
        "|(^cc-?expiration$)" +
        "|(^card-?expiration$)" +
        "|(^cc-?ex$)" +
        "|(^card-?ex$)" +
        "|(^card-?expire$)" +
        "|(^card-?expiry$)" +
        "|(^validite$)" +
        "|(^expiration$)" +
        "|(^expiry$)" +
        "|mm-?yy" +
        "|mm-?yyyy" +
        "|yy-?mm" +
        "|yyyy-?mm" +
        "|expiration-?date" +
        "|payment-?card-?expiration" +
        "|(^payment-?cc-?date$)",

      "cc-exp-month":
        "(^exp-?month$)" +
        "|(^cc-?exp-?month$)" +
        "|(^cc-?month$)" +
        "|(^card-?month$)" +
        "|(^cc-?mo$)" +
        "|(^card-?mo$)" +
        "|(^exp-?mo$)" +
        "|(^card-?exp-?mo$)" +
        "|(^cc-?exp-?mo$)" +
        "|(^card-?expiration-?month$)" +
        "|(^expiration-?month$)" +
        "|(^cc-?mm$)" +
        "|(^cc-?m$)" +
        "|(^card-?mm$)" +
        "|(^card-?m$)" +
        "|(^card-?exp-?mm$)" +
        "|(^cc-?exp-?mm$)" +
        "|(^exp-?mm$)" +
        "|(^exp-?m$)" +
        "|(^expire-?month$)" +
        "|(^expire-?mo$)" +
        "|(^expiry-?month$)" +
        "|(^expiry-?mo$)" +
        "|(^card-?expire-?month$)" +
        "|(^card-?expire-?mo$)" +
        "|(^card-?expiry-?month$)" +
        "|(^card-?expiry-?mo$)" +
        "|(^mois-?validite$)" +
        "|(^mois-?expiration$)" +
        "|(^m-?validite$)" +
        "|(^m-?expiration$)" +
        "|(^expiry-?date-?field-?month$)" +
        "|(^expiration-?date-?month$)" +
        "|(^expiration-?date-?mm$)" +
        "|(^exp-?mon$)" +
        "|(^validity-?mo$)" +
        "|(^exp-?date-?mo$)" +
        "|(^cb-?date-?mois$)" +
        "|(^date-?m$)",

      "cc-exp-year":
        "(^exp-?year$)" +
        "|(^cc-?exp-?year$)" +
        "|(^cc-?year$)" +
        "|(^card-?year$)" +
        "|(^cc-?yr$)" +
        "|(^card-?yr$)" +
        "|(^exp-?yr$)" +
        "|(^card-?exp-?yr$)" +
        "|(^cc-?exp-?yr$)" +
        "|(^card-?expiration-?year$)" +
        "|(^expiration-?year$)" +
        "|(^cc-?yy$)" +
        "|(^cc-?y$)" +
        "|(^card-?yy$)" +
        "|(^card-?y$)" +
        "|(^card-?exp-?yy$)" +
        "|(^cc-?exp-?yy$)" +
        "|(^exp-?yy$)" +
        "|(^exp-?y$)" +
        "|(^cc-?yyyy$)" +
        "|(^card-?yyyy$)" +
        "|(^card-?exp-?yyyy$)" +
        "|(^cc-?exp-?yyyy$)" +
        "|(^expire-?year$)" +
        "|(^expire-?yr$)" +
        "|(^expiry-?year$)" +
        "|(^expiry-?yr$)" +
        "|(^card-?expire-?year$)" +
        "|(^card-?expire-?yr$)" +
        "|(^card-?expiry-?year$)" +
        "|(^card-?expiry-?yr$)" +
        "|(^an-?validite$)" +
        "|(^an-?expiration$)" +
        "|(^annee-?validite$)" +
        "|(^annee-?expiration$)" +
        "|(^expiry-?date-?field-?year$)" +
        "|(^expiration-?date-?year$)" +
        "|(^cb-?date-?ann$)" +
        "|(^expiration-?date-?yy$)" +
        "|(^expiration-?date-?yyyy$)" +
        "|(^validity-?year$)" +
        "|(^exp-?date-?year$)" +
        "|(^date-?y$)",

      "cc-type":
        "(^cc-?type$)" +
        "|(^card-?type$)" +
        "|(^card-?brand$)" +
        "|(^cc-?brand$)" +
        "|(^cb-?type$)",
    },

    //=========================================================================
    // These rules are from Chromium source codes [1]. Most of them
    // converted to JS format have the same meaning with the original ones except
    // the first line of "address-level1".
    // [1] https://source.chromium.org/chromium/chromium/src/+/master:components/autofill/core/common/autofill_regex_constants.cc
    {
      // ==== Email ====
      email:
        "e.?mail" +
        "|courriel" + // fr
        "|correo.*electr(o|ó)nico" + // es-ES
        "|メールアドレス" + // ja-JP
        "|Электронной.?Почты" + // ru
        "|邮件|邮箱" + // zh-CN
        "|電郵地址" + // zh-TW
        "|ഇ-മെയില്‍|ഇലക്ട്രോണിക്.?" +
        "മെയിൽ" + // ml
        "|ایمیل|پست.*الکترونیک" + // fa
        "|ईमेल|इलॅक्ट्रॉनिक.?मेल" + // hi
        "|(\\b|_)eposta(\\b|_)" + // tr
        "|(?:이메일|전자.?우편|[Ee]-?mail)(.?주소)?", // ko-KR

      // ==== Telephone ====
      tel:
        "phone|mobile|contact.?number" +
        "|telefonnummer" + // de-DE
        "|telefono|teléfono" + // es
        "|telfixe" + // fr-FR
        "|電話" + // ja-JP
        "|telefone|telemovel" + // pt-BR, pt-PT
        "|телефон" + // ru
        "|मोबाइल" + // hi for mobile
        "|(\\b|_|\\*)telefon(\\b|_|\\*)" + // tr
        "|电话" + // zh-CN
        "|മൊബൈല്‍" + // ml for mobile
        "|(?:전화|핸드폰|휴대폰|휴대전화)(?:.?번호)?", // ko-KR

      // ==== Address Fields ====
      organization:
        "company|business|organization|organisation" +
        // In order to support webkit we convert all negative lookbehinds to a capture group
        // (?<!not)word -> (?<neg>notword)|word
        // TODO: Bug 1829583
        "|(?<neg>confirma)" +
        "|firma|firmenname" + // de-DE
        "|empresa" + // es
        "|societe|société" + // fr-FR
        "|ragione.?sociale" + // it-IT
        "|会社" + // ja-JP
        "|название.?компании" + // ru
        "|单位|公司" + // zh-CN
        "|شرکت" + // fa
        "|회사|직장", // ko-KR

      "street-address": "streetaddress|street-address",
      "address-line1":
        "^address$|address[_-]?line(one)?|address1|addr1|street" +
        "|(?:shipping|billing)address$" +
        "|strasse|straße|hausnummer|housenumber" + // de-DE
        "|house.?name" + // en-GB
        "|direccion|dirección" + // es
        "|adresse" + // fr-FR
        "|indirizzo" + // it-IT
        "|^住所$|住所1" + // ja-JP
        "|morada" + // pt-BR, pt-PT
        // In order to support webkit we convert all negative lookbehinds to a capture group
        // (?<!not)word -> (?<neg>notword)|word
        // TODO: Bug 1829583
        "|(?<neg>identificação do endereço)" +
        "|(endereço)" + // pt-BR, pt-PT
        "|Адрес" + // ru
        "|地址" + // zh-CN
        "|(\\b|_)adres(?! (başlığı(nız)?|tarifi))(\\b|_)" + // tr
        "|^주소.?$|주소.?1", // ko-KR

      "address-line2":
        "address[_-]?line(2|two)|address2|addr2|street|suite|unit(?!e)" + // Firefox adds `(?!e)` to unit to skip `United State`
        "|adresszusatz|ergänzende.?angaben" + // de-DE
        "|direccion2|colonia|adicional" + // es
        "|addresssuppl|complementnom|appartement" + // fr-FR
        "|indirizzo2" + // it-IT
        "|住所2" + // ja-JP
        "|complemento|addrcomplement" + // pt-BR, pt-PT
        "|Улица" + // ru
        "|地址2" + // zh-CN
        "|주소.?2", // ko-KR

      "address-line3":
        "address[_-]?line(3|three)|address3|addr3|street|suite|unit(?!e)" + // Firefox adds `(?!e)` to unit to skip `United State`
        "|adresszusatz|ergänzende.?angaben" + // de-DE
        "|direccion3|colonia|adicional" + // es
        "|addresssuppl|complementnom|appartement" + // fr-FR
        "|indirizzo3" + // it-IT
        "|住所3" + // ja-JP
        "|complemento|addrcomplement" + // pt-BR, pt-PT
        "|Улица" + // ru
        "|地址3" + // zh-CN
        "|주소.?3", // ko-KR

      "address-level2":
        "city|town" +
        "|\\bort\\b|stadt" + // de-DE
        "|suburb" + // en-AU
        "|ciudad|provincia|localidad|poblacion" + // es
        "|ville|commune" + // fr-FR
        "|localita" + // it-IT
        "|市区町村" + // ja-JP
        "|cidade" + // pt-BR, pt-PT
        "|Город" + // ru
        "|市" + // zh-CN
        "|分區" + // zh-TW
        "|شهر" + // fa
        "|शहर" + // hi for city
        "|ग्राम|गाँव" + // hi for village
        "|നഗരം|ഗ്രാമം" + // ml for town|village
        "|((\\b|_|\\*)([İii̇]l[cç]e(miz|niz)?)(\\b|_|\\*))" + // tr
        "|^시[^도·・]|시[·・]?군[·・]?구", // ko-KR

      "address-level1":
        // In order to support webkit we convert all negative lookbehinds to a capture group
        // (?<!not)word -> (?<neg>notword)|word
        // TODO: Bug 1829583
        "(?<neg>united?.state|hist?.state|history?.state)" +
        "|state|county|region|province" +
        "|principality" + // en-UK
        "|都道府県" + // ja-JP
        "|estado|provincia" + // pt-BR, pt-PT
        "|область" + // ru
        "|省" + // zh-CN
        "|地區" + // zh-TW
        "|സംസ്ഥാനം" + // ml
        "|استان" + // fa
        "|राज्य" + // hi
        "|((\\b|_|\\*)(eyalet|[şs]ehir|[İii̇]l(imiz)?|kent)(\\b|_|\\*))" + // tr
        "|^시[·・]?도", // ko-KR

      "postal-code":
        "zip|postal|post.*code|pcode" +
        "|pin.?code" + // en-IN
        "|postleitzahl" + // de-DE
        "|\\bcp\\b" + // es
        "|\\bcdp\\b" + // fr-FR
        "|\\bcap\\b" + // it-IT
        "|郵便番号" + // ja-JP
        "|codigo|codpos|\\bcep\\b" + // pt-BR, pt-PT
        "|Почтовый.?Индекс" + // ru
        "|पिन.?कोड" + // hi
        "|പിന്‍കോഡ്" + // ml
        "|邮政编码|邮编" + // zh-CN
        "|郵遞區號" + // zh-TW
        "|(\\b|_)posta kodu(\\b|_)" + // tr
        "|우편.?번호", // ko-KR

      country:
        "country|countries" +
        "|país|pais" + // es
        "|(\\b|_)land(\\b|_)(?!.*(mark.*))" + // de-DE landmark is a type in india.
        // In order to support webkit we convert all negative lookbehinds to a capture group
        // (?<!not)word -> (?<neg>notword)|word
        // TODO: Bug 1829583
        "|(?<neg>入国|出国)" +
        "|国" + // ja-JP
        "|国家" + // zh-CN
        "|국가|나라" + // ko-KR
        "|(\\b|_)(ülke|ulce|ulke)(\\b|_)" + // tr
        "|کشور", // fa

      // ==== Name Fields ====
      "cc-name":
        "card.?(?:holder|owner)|name.*(\\b)?on(\\b)?.*card" +
        "|^(credit[-\\s]?card|card).*name|cc.?full.?name" +
        "|karteninhaber" + // de-DE
        "|nombre.*tarjeta" + // es
        "|nom.*carte" + // fr-FR
        "|nome.*cart" + // it-IT
        "|名前" + // ja-JP
        "|Имя.*карты" + // ru
        "|信用卡开户名|开户名|持卡人姓名" + // zh-CN
        "|持卡人姓名", // zh-TW

      name:
        "^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name" +
        "|name.*first.*last|firstandlastname" +
        "|nombre.*y.*apellidos" + // es
        "|^nom(?!bre)" + // fr-FR
        "|お名前|氏名" + // ja-JP
        "|^nome" + // pt-BR, pt-PT
        "|نام.*نام.*خانوادگی" + // fa
        "|姓名" + // zh-CN
        "|(\\b|_|\\*)ad[ı]? soyad[ı]?(\\b|_|\\*)" + // tr
        "|성명", // ko-KR

      "given-name":
        "first.*name|initials|fname|first$|given.*name" +
        "|vorname" + // de-DE
        "|nombre" + // es
        "|forename|prénom|prenom" + // fr-FR
        "|名" + // ja-JP
        "|nome" + // pt-BR, pt-PT
        "|Имя" + // ru
        "|نام" + // fa
        "|이름" + // ko-KR
        "|പേര്" + // ml
        "|(\\b|_|\\*)(isim|ad|ad(i|ı|iniz|ınız)?)(\\b|_|\\*)" + // tr
        "|नाम", // hi

      "additional-name":
        "middle.*name|mname|middle$|middle.*initial|m\\.i\\.|mi$|\\bmi\\b",

      "family-name":
        "last.*name|lname|surname|last$|secondname|family.*name" +
        "|nachname" + // de-DE
        "|apellidos?" + // es
        "|famille|^nom(?!bre)" + // fr-FR
        "|cognome" + // it-IT
        "|姓" + // ja-JP
        "|apelidos|surename|sobrenome" + // pt-BR, pt-PT
        "|Фамилия" + // ru
        "|نام.*خانوادگی" + // fa
        "|उपनाम" + // hi
        "|മറുപേര്" + // ml
        "|(\\b|_|\\*)(soyisim|soyad(i|ı|iniz|ınız)?)(\\b|_|\\*)" + // tr
        "|\\b성(?:[^명]|\\b)", // ko-KR

      // ==== Credit Card Fields ====
      // Note: `cc-name` expression has been moved up, above `name`, in
      // order to handle specialization through ordering.
      "cc-number":
        "(add)?(?:card|cc|acct).?(?:number|#|no|num|field(?!s)|pan)" +
        // In order to support webkit we convert all negative lookbehinds to a capture group
        // (?<!not)word -> (?<neg>notword)|word
        // TODO: Bug 1829583
        "|(?<neg>telefonnummer|hausnummer|personnummer|fødselsnummer)" + // de-DE, sv-SE, no
        "|nummer" +
        "|カード番号" + // ja-JP
        "|Номер.*карты" + // ru
        "|信用卡号|信用卡号码" + // zh-CN
        "|信用卡卡號" + // zh-TW
        "|카드" + // ko-KR
        // es/pt/fr
        "|(numero|número|numéro)(?!.*(document|fono|phone|réservation))",

      "cc-exp-month":
        "expir|exp.*mo|exp.*date|ccmonth|cardmonth|addmonth" +
        "|gueltig|gültig|monat" + // de-DE
        "|fecha" + // es
        "|date.*exp" + // fr-FR
        "|scadenza" + // it-IT
        "|有効期限" + // ja-JP
        "|validade" + // pt-BR, pt-PT
        "|Срок действия карты" + // ru
        "|月", // zh-CN

      "cc-exp-year":
        "exp|^/|(add)?year" +
        "|ablaufdatum|gueltig|gültig|jahr" + // de-DE
        "|fecha" + // es
        "|scadenza" + // it-IT
        "|有効期限" + // ja-JP
        "|validade" + // pt-BR, pt-PT
        "|Срок действия карты" + // ru
        "|年|有效期", // zh-CN

      "cc-exp":
        "expir|exp.*date|^expfield$" +
        "|gueltig|gültig" + // de-DE
        "|fecha" + // es
        "|date.*exp" + // fr-FR
        "|scadenza" + // it-IT
        "|有効期限" + // ja-JP
        "|validade" + // pt-BR, pt-PT
        "|Срок действия карты", // ru

      "cc-csc":
        "verification|card.?identification|security.?code|card.?code" +
        "|security.?value" +
        "|security.?number|card.?pin|c-v-v" +
        // We omit this regexp in favor of being less generic.
        // See "Firefox-specific" rules for cc-csc
        // "|(cvn|cvv|cvc|csc|cvd|cid|ccv)(field)?" +
        "|\\bcid\\b",
    },
  ],

  LABEL_RULE_SETS: [
    {
      "address-line1":
        "(^\\W*address)" +
        "|(address\\W*$)" +
        "|(?:shipping|billing|mailing|pick.?up|drop.?off|delivery|sender|postal|" +
        "recipient|home|work|office|school|business|mail)[\\s\\-]+address" +
        "|address\\s+(of|for|to|from)" +
        "|adresse" +                         // fr-FR
        "|indirizzo" +                       // it-IT
        "|住所" +                            // ja-JP
        "|地址" +                            // zh-CN
        "|(\\b|_)adres(?! tarifi)(\\b|_)" +  // tr
        "|주소" +                            // ko-KR
        "|^alamat" +                         // id
        // Should contain street and any other address component, in any order
        "|street.*(house|building|apartment|floor)" +  // en
        "|(house|building|apartment|floor).*street" +
        "|(sokak|cadde).*(apartman|bina|daire|mahalle)" +  // tr
        "|(apartman|bina|daire|mahalle).*(sokak|cadde)" +
        "|улиц.*(дом|корпус|квартир|этаж)|(дом|корпус|квартир|этаж).*улиц",  // ru
    },
    {
      "address-line2":
        "address|line" +
        "|adresse" +      // fr-FR
        "|indirizzo" +    // it-IT
        "|地址" +         // zh-CN
        "|주소",          // ko-KR
    },
  ],

  _getRules(rules, rulesets) {
    function computeRule(name) {
      let regexps = [];
      rulesets.forEach(set => {
        if (set[name]) {
          // Add the rule.
          // We make the regex lower case so that we can match it against the
          // lower-cased field name and get a rough equivalent of a case-insensitive
          // match. This avoids a performance cliff with the "iu" flag on regular
          // expressions.
          regexps.push(`(${set[name].toLowerCase()})`.normalize("NFKC"));
        }
      });

      const value = new RegExp(regexps.join("|"), "gu");

      Object.defineProperty(rules, name, { get: undefined });
      Object.defineProperty(rules, name, { value });
      return value;
    }

    Object.keys(rules).forEach(field =>
      Object.defineProperty(rules, field, {
        get() {
          return computeRule(field);
        },
      })
    );

    return rules;
  },

  getLabelRules() {
    return this._getRules(this.LABEL_RULES, this.LABEL_RULE_SETS);
  },

  getRules() {
    return this._getRules(this.RULES, this.RULE_SETS);
  },
};

export default HeuristicsRegExp;
PK
!<�_��##!modules/shared/LabelUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This is a utility object to work with HTML labels in web pages,
 * including finding label elements and label text extraction.
 */
export const LabelUtils = {
  // The tag name list is from Chromium except for "STYLE":
  // eslint-disable-next-line max-len
  // https://cs.chromium.org/chromium/src/components/autofill/content/renderer/form_autofill_util.cc?l=216&rcl=d33a171b7c308a64dc3372fac3da2179c63b419e
  EXCLUDED_TAGS: ["SCRIPT", "NOSCRIPT", "OPTION", "STYLE"],

  // A map object, whose keys are the id's of form fields and each value is an
  // array consisting of label elements correponding to the id.
  // @type {Map<string, array>}
  _mappedLabels: null,

  // An array consisting of label elements whose correponding form field doesn't
  // have an id attribute.
  // @type {Array<[HTMLLabelElement, HTMLElement]>}
  _unmappedLabelControls: null,

  // A weak map consisting of label element and extracted strings pairs.
  // @type {WeakMap<HTMLLabelElement, array>}
  _labelStrings: null,

  /**
   * Extract all strings of an element's children to an array.
   * "element.textContent" is a string which is merged of all children nodes,
   * and this function provides an array of the strings contains in an element.
   *
   * @param  {object} element
   *         A DOM element to be extracted.
   * @returns {Array}
   *          All strings in an element.
   */
  extractLabelStrings(element) {
    if (this._labelStrings.has(element)) {
      return this._labelStrings.get(element);
    }
    let strings = [];
    let _extractLabelStrings = el => {
      if (this.EXCLUDED_TAGS.includes(el.tagName)) {
        return;
      }

      if (el.nodeType == el.TEXT_NODE || !el.childNodes.length) {
        let trimmedText = el.textContent.trim();
        if (trimmedText) {
          strings.push(trimmedText);
        }
        return;
      }

      for (let node of el.childNodes) {
        let nodeType = node.nodeType;
        if (nodeType != node.ELEMENT_NODE && nodeType != node.TEXT_NODE) {
          continue;
        }
        _extractLabelStrings(node);
      }
    };
    _extractLabelStrings(element);
    this._labelStrings.set(element, strings);
    return strings;
  },

  generateLabelMap(doc) {
    this._mappedLabels = new Map();
    this._unmappedLabelControls = [];
    this._labelStrings = new WeakMap();

    for (let label of doc.querySelectorAll("label")) {
      let id = label.htmlFor;
      let control;
      if (!id) {
        control = label.control;
        if (!control) {
          continue;
        }
        id = control.id;
      }
      if (id) {
        let labels = this._mappedLabels.get(id);
        if (labels) {
          labels.push(label);
        } else {
          this._mappedLabels.set(id, [label]);
        }
      } else {
        // control must be non-empty here
        this._unmappedLabelControls.push({ label, control });
      }
    }
  },

  clearLabelMap() {
    this._mappedLabels = null;
    this._unmappedLabelControls = null;
    this._labelStrings = null;
  },

  findLabelElements(element) {
    if (!this._mappedLabels) {
      this.generateLabelMap(element.ownerDocument);
    }

    let id = element.id;
    if (!id) {
      return this._unmappedLabelControls
        .filter(lc => lc.control == element)
        .map(lc => lc.label);
    }
    return this._mappedLabels.get(id) || [];
  },
};

export default LabelUtils;
PK
!<���8��'modules/shared/LoginFormFactory.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * A factory to generate LoginForm objects that represent a set of login fields
 * which aren't necessarily marked up with a <form> element.
 */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  FormLikeFactory: "resource://gre/modules/FormLikeFactory.sys.mjs",
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  return lazy.LoginHelper.createLogger("LoginFormFactory");
});

export const LoginFormFactory = {
  /**
   * WeakMap of the root element of a LoginForm to the LoginForm representing its fields.
   *
   * This is used to be able to lookup an existing LoginForm for a given root element since multiple
   * calls to LoginFormFactory.createFrom* won't give the exact same object. When batching fills we don't always
   * want to use the most recent list of elements for a LoginForm since we may end up doing multiple
   * fills for the same set of elements when a field gets added between arming and running the
   * DeferredTask.
   *
   * @type {WeakMap}
   */
  _loginFormsByRootElement: new WeakMap(),

  /**
   * Maps all DOM content documents in this content process, including those in
   * frames, to a WeakSet of LoginForm.rootElement for the document.
   */
  _loginFormRootElementsByDocument: new WeakMap(),

  /**
   * Create a LoginForm object from a <form>.
   *
   * @param {HTMLFormElement} aForm
   * @return {LoginForm}
   * @throws Error if aForm isn't an HTMLFormElement
   */
  createFromForm(aForm) {
    let formLike = lazy.FormLikeFactory.createFromForm(aForm);
    formLike.action = lazy.LoginHelper.getFormActionOrigin(aForm);

    this._addLoginFormToRootElementsSet(formLike);

    return formLike;
  },

  /**
   * Create a LoginForm object from an elememt that is the root of the document
   *
   * Currently all <input> not in a <form> are one LoginForm but this
   * shouldn't be relied upon as the heuristics may change to detect multiple
   * "forms" (e.g. registration and login) on one page with a <form>.
   *
   * @param {HTMLElement} aDocumentRoot
   * @return {LoginForm}
   * @throws Error if aDocumentRoot is null
   */
  createFromDocumentRoot(aDocumentRoot) {
    const formLike = lazy.FormLikeFactory.createFromDocumentRoot(aDocumentRoot);
    formLike.action = lazy.LoginHelper.getLoginOrigin(aDocumentRoot.baseURI);

    lazy.log.debug(
      "Created non-form LoginForm for rootElement:",
      aDocumentRoot
    );

    this._addLoginFormToRootElementsSet(formLike);

    return formLike;
  },

  /**
   * Create a LoginForm object from a password or username field.
   *
   * If the field is in a <form>, construct the LoginForm from the form.
   * Otherwise, create a LoginForm with a rootElement (wrapper) according to
   * heuristics. Currently all <input> not in a <form> are one LoginForm but this
   * shouldn't be relied upon as the heuristics may change to detect multiple
   * "forms" (e.g. registration and login) on one page with a <form>.
   *
   * Note that two LoginForms created from the same field won't return the same LoginForm object.
   * Use the `rootElement` property on the LoginForm as a key instead.
   *
   * @param {HTMLInputElement} aField - a password or username field in a document
   * @return {LoginForm}
   * @throws Error if aField isn't a password or username field in a document
   */
  createFromField(aField) {
    if (
      !HTMLInputElement.isInstance(aField) ||
      (!aField.hasBeenTypePassword &&
        !lazy.LoginHelper.isUsernameFieldType(aField)) ||
      !aField.ownerDocument
    ) {
      throw new Error(
        "createFromField requires a password or username field in a document"
      );
    }

    let form =
      aField.form ||
      lazy.FormLikeFactory.closestFormIgnoringShadowRoots(aField);
    if (form) {
      return this.createFromForm(form);
    } else if (aField.hasAttribute("form")) {
      lazy.log.debug(
        "createFromField: field has form attribute but no form: ",
        aField.getAttribute("form")
      );
    }

    let formLike = lazy.FormLikeFactory.createFromField(aField);
    formLike.action = lazy.LoginHelper.getLoginOrigin(
      aField.ownerDocument.baseURI
    );
    lazy.log.debug(
      "Created non-form LoginForm for rootElement:",
      aField.ownerDocument.documentElement
    );

    this._addLoginFormToRootElementsSet(formLike);
    return formLike;
  },

  getRootElementsWeakSetForDocument(aDocument) {
    let rootElementsSet = this._loginFormRootElementsByDocument.get(aDocument);
    if (!rootElementsSet) {
      rootElementsSet = new WeakSet();
      this._loginFormRootElementsByDocument.set(aDocument, rootElementsSet);
    }
    return rootElementsSet;
  },

  getForRootElement(aRootElement) {
    return this._loginFormsByRootElement.get(aRootElement);
  },

  setForRootElement(aRootElement, aLoginForm) {
    return this._loginFormsByRootElement.set(aRootElement, aLoginForm);
  },

  _addLoginFormToRootElementsSet(formLike) {
    let rootElementsSet = this.getRootElementsWeakSetForDocument(
      formLike.ownerDocument
    );
    rootElementsSet.add(formLike.rootElement);
    lazy.log.debug(
      "adding",
      formLike.rootElement,
      "to root elements for",
      formLike.ownerDocument
    );

    this._loginFormsByRootElement.set(formLike.rootElement, formLike);
  },
};
PK
!<�'l��f�f'modules/shared/NewPasswordModel.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Machine learning model for identifying new password input elements
 * using Fathom.
 */

import {
  dom,
  element,
  out,
  rule,
  ruleset,
  score,
  type,
  utils,
  clusters,
} from "resource://gre/modules/third_party/fathom/fathom.mjs";

let { identity, isVisible, min, setDefault } = utils;
let { euclidean } = clusters;

/**
 * ----- Start of model -----
 *
 * Everything below this comment up to the "End of model" comment is copied from:
 * https://github.com/mozilla-services/fathom-login-forms/blob/78d4bf8f301b5aa6d62c06b45e826a0dd9df1afa/new-password/rulesets.js#L14-L613
 * Deviations from that file:
 *   - Remove import statements, instead using ``ChromeUtils.defineModuleGetter`` and destructuring assignments above.
 *   - Set ``DEVELOPMENT`` constant to ``false``.
 */

// Whether this is running in the Vectorizer, rather than in-application, in a
// privileged Chrome context
const DEVELOPMENT = false;

// Run me with confidence cutoff = 0.75.
const coefficients = {
  new: [
    ["hasNewLabel", 2.9195094108581543],
    ["hasConfirmLabel", 2.1672143936157227],
    ["hasCurrentLabel", -2.1813206672668457],
    ["closestLabelMatchesNew", 2.965045213699341],
    ["closestLabelMatchesConfirm", 2.698647975921631],
    ["closestLabelMatchesCurrent", -2.147423505783081],
    ["hasNewAriaLabel", 2.8312134742736816],
    ["hasConfirmAriaLabel", 1.5153108835220337],
    ["hasCurrentAriaLabel", -4.368860244750977],
    ["hasNewPlaceholder", 1.4374250173568726],
    ["hasConfirmPlaceholder", 1.717592477798462],
    ["hasCurrentPlaceholder", -1.9401700496673584],
    ["forgotPasswordInFormLinkTextContent", -0.6736700534820557],
    ["forgotPasswordInFormLinkHref", -1.3025357723236084],
    ["forgotPasswordInFormLinkTitle", -2.9019577503204346],
    ["forgotInFormLinkTextContent", -1.2455425262451172],
    ["forgotInFormLinkHref", 0.4884686768054962],
    ["forgotPasswordInFormButtonTextContent", -0.8015769720077515],
    ["forgotPasswordOnPageLinkTextContent", 0.04422328248620033],
    ["forgotPasswordOnPageLinkHref", -1.0331494808197021],
    ["forgotPasswordOnPageLinkTitle", -0.08798415213823318],
    ["forgotPasswordOnPageButtonTextContent", -1.5396910905838013],
    ["elementAttrsMatchNew", 2.8492355346679688],
    ["elementAttrsMatchConfirm", 1.9043376445770264],
    ["elementAttrsMatchCurrent", -2.056903839111328],
    ["elementAttrsMatchPassword1", 1.5833512544631958],
    ["elementAttrsMatchPassword2", 1.3928000926971436],
    ["elementAttrsMatchLogin", 1.738782525062561],
    ["formAttrsMatchRegister", 2.1345033645629883],
    ["formHasRegisterAction", 1.9337323904037476],
    ["formButtonIsRegister", 3.0930404663085938],
    ["formAttrsMatchLogin", -0.5816961526870728],
    ["formHasLoginAction", -0.18886367976665497],
    ["formButtonIsLogin", -2.332860231399536],
    ["hasAutocompleteCurrentPassword", -0.029974736273288727],
    ["formHasRememberMeCheckbox", 0.8600837588310242],
    ["formHasRememberMeLabel", 0.06663893908262253],
    ["formHasNewsletterCheckbox", -1.4851698875427246],
    ["formHasNewsletterLabel", 2.416919231414795],
    ["closestHeaderAboveIsLoginy", -2.0047383308410645],
    ["closestHeaderAboveIsRegistery", 2.19451642036438],
    ["nextInputIsConfirmy", 2.5344431400299072],
    ["formHasMultipleVisibleInput", 2.81270694732666],
    ["firstFieldInFormWithThreePasswordFields", -2.8964080810546875],
  ],
};

const biases = [["new", -1.3525885343551636]];

const passwordStringRegex =
  /password|passwort|رمز عبور|mot de passe|パスワード|비밀번호|암호|wachtwoord|senha|Пароль|parol|密码|contraseña|heslo|كلمة السر|kodeord|Κωδικός|pass code|Kata sandi|hasło|รหัสผ่าน|Şifre/i;
const passwordAttrRegex = /pw|pwd|passwd|pass/i;
const newStringRegex =
  /new|erstellen|create|choose|設定|신규|Créer|Nouveau|baru|nouă|nieuw/i;
const newAttrRegex = /new/i;
const confirmStringRegex =
  /wiederholen|wiederholung|confirm|repeat|confirmation|verify|retype|repite|確認|の確認|تکرار|re-enter|확인|bevestigen|confirme|Повторите|tassyklamak|再次输入|ještě jednou|gentag|re-type|confirmar|Répéter|conferma|Repetaţi|again|reenter|再入力|재입력|Ulangi|Bekræft/i;
const confirmAttrRegex = /confirm|retype/i;
const currentAttrAndStringRegex =
  /current|old|aktuelles|derzeitiges|当前|Atual|actuel|curentă|sekarang/i;
const forgotStringRegex =
  /vergessen|vergeten|forgot|oublié|dimenticata|Esqueceu|esqueci|Забыли|忘记|找回|Zapomenuté|lost|忘れた|忘れられた|忘れの方|재설정|찾기|help|فراموشی| را فراموش کرده اید|Восстановить|Unuttu|perdus|重新設定|reset|recover|change|remind|find|request|restore|trouble/i;
const forgotHrefRegex =
  /forgot|reset|recover|change|lost|remind|find|request|restore/i;
const password1Regex =
  /pw1|pwd1|pass1|passwd1|password1|pwone|pwdone|passone|passwdone|passwordone|pwfirst|pwdfirst|passfirst|passwdfirst|passwordfirst/i;
const password2Regex =
  /pw2|pwd2|pass2|passwd2|password2|pwtwo|pwdtwo|passtwo|passwdtwo|passwordtwo|pwsecond|pwdsecond|passsecond|passwdsecond|passwordsecond/i;
const loginRegex =
  /login|log in|log on|log-on|Войти|sign in|sigin|sign\/in|sign-in|sign on|sign-on|ورود|登录|Přihlásit se|Přihlaste|Авторизоваться|Авторизация|entrar|ログイン|로그인|inloggen|Συνδέσου|accedi|ログオン|Giriş Yap|登入|connecter|connectez-vous|Connexion|Вход/i;
const loginFormAttrRegex =
  /login|log in|log on|log-on|sign in|sigin|sign\/in|sign-in|sign on|sign-on/i;
const registerStringRegex =
  /create[a-zA-Z\s]+account|activate[a-zA-Z\s]+account|Zugang anlegen|Angaben prüfen|Konto erstellen|register|sign up|ثبت نام|登録|注册|cadastr|Зарегистрироваться|Регистрация|Bellige alynmak|تسجيل|ΕΓΓΡΑΦΗΣ|Εγγραφή|Créer mon compte|Créer un compte|Mendaftar|가입하기|inschrijving|Zarejestruj się|Deschideți un cont|Создать аккаунт|ร่วม|Üye Ol|registr|new account|ساخت حساب کاربری|Schrijf je|S'inscrire/i;
const registerActionRegex =
  /register|signup|sign-up|create-account|account\/create|join|new_account|user\/create|sign\/up|membership\/create/i;
const registerFormAttrRegex =
  /signup|join|register|regform|registration|new_user|AccountCreate|create_customer|CreateAccount|CreateAcct|create-account|reg-form|newuser|new-reg|new-form|new_membership/i;
const rememberMeAttrRegex =
  /remember|auto_login|auto-login|save_mail|save-mail|ricordami|manter|mantenha|savelogin|auto login/i;
const rememberMeStringRegex =
  /remember me|keep me logged in|keep me signed in|save email address|save id|stay signed in|ricordami|次回からログオンIDの入力を省略する|メールアドレスを保存する|を保存|아이디저장|아이디 저장|로그인 상태 유지|lembrar|manter conectado|mantenha-me conectado|Запомни меня|запомнить меня|Запомните меня|Не спрашивать в следующий раз|下次自动登录|记住我/i;
const newsletterStringRegex = /newsletter|ニュースレター/i;
const passwordStringAndAttrRegex = new RegExp(
  passwordStringRegex.source + "|" + passwordAttrRegex.source,
  "i"
);

function makeRuleset(coeffs, biases) {
  // HTMLElement => (selector => Array<HTMLElement>) nested map to cache querySelectorAll calls.
  let elementToSelectors;
  // We want to clear the cache each time the model is executed to get the latest DOM snapshot
  // for each classification.
  function clearCache() {
    // WeakMaps do not have a clear method
    elementToSelectors = new WeakMap();
  }

  function hasLabelMatchingRegex(element, regex) {
    // Check element.labels
    const labels = element.labels;
    // TODO: Should I be concerned with multiple labels?
    if (labels !== null && labels.length) {
      return regex.test(labels[0].textContent);
    }

    // Check element.aria-labelledby
    let labelledBy = element.getAttribute("aria-labelledby");
    if (labelledBy !== null) {
      labelledBy = labelledBy
        .split(" ")
        .map(id => element.getRootNode().getElementById(id))
        .filter(el => el);
      if (labelledBy.length === 1) {
        return regex.test(labelledBy[0].textContent);
      } else if (labelledBy.length > 1) {
        return regex.test(
          min(labelledBy, node => euclidean(node, element)).textContent
        );
      }
    }

    const parentElement = element.parentElement;
    // Bug 1634819: element.parentElement is null if element.parentNode is a ShadowRoot
    if (!parentElement) {
      return false;
    }
    // Check if the input is in a <td>, and, if so, check the textContent of the containing <tr>
    if (parentElement.tagName === "TD" && parentElement.parentElement) {
      // TODO: How bad is the assumption that the <tr> won't be the parent of the <td>?
      return regex.test(parentElement.parentElement.textContent);
    }

    // Check if the input is in a <dd>, and, if so, check the textContent of the preceding <dt>
    if (
      parentElement.tagName === "DD" &&
      // previousElementSibling can be null
      parentElement.previousElementSibling
    ) {
      return regex.test(parentElement.previousElementSibling.textContent);
    }
    return false;
  }

  function closestLabelMatchesRegex(element, regex) {
    const previousElementSibling = element.previousElementSibling;
    if (
      previousElementSibling !== null &&
      previousElementSibling.tagName === "LABEL"
    ) {
      return regex.test(previousElementSibling.textContent);
    }

    const nextElementSibling = element.nextElementSibling;
    if (nextElementSibling !== null && nextElementSibling.tagName === "LABEL") {
      return regex.test(nextElementSibling.textContent);
    }

    const closestLabelWithinForm = closestSelectorElementWithinElement(
      element,
      element.form,
      "label"
    );
    return containsRegex(
      regex,
      closestLabelWithinForm,
      closestLabelWithinForm => closestLabelWithinForm.textContent
    );
  }

  function containsRegex(regex, thingOrNull, thingToString = identity) {
    return thingOrNull !== null && regex.test(thingToString(thingOrNull));
  }

  function closestSelectorElementWithinElement(
    toElement,
    withinElement,
    querySelector
  ) {
    if (withinElement !== null) {
      let nodeList = Array.from(withinElement.querySelectorAll(querySelector));
      if (nodeList.length) {
        return min(nodeList, node => euclidean(node, toElement));
      }
    }
    return null;
  }

  function hasAriaLabelMatchingRegex(element, regex) {
    return containsRegex(regex, element.getAttribute("aria-label"));
  }

  function hasPlaceholderMatchingRegex(element, regex) {
    return containsRegex(regex, element.getAttribute("placeholder"));
  }

  function testRegexesAgainstAnchorPropertyWithinElement(
    property,
    element,
    ...regexes
  ) {
    return hasSomeMatchingPredicateForSelectorWithinElement(
      element,
      "a",
      anchor => {
        const propertyValue = anchor[property];
        return regexes.every(regex => regex.test(propertyValue));
      }
    );
  }

  function testFormButtonsAgainst(element, stringRegex) {
    const form = element.form;
    if (form !== null) {
      let inputs = Array.from(
        form.querySelectorAll("input[type=submit],input[type=button]")
      );
      inputs = inputs.filter(input => {
        return stringRegex.test(input.value);
      });
      if (inputs.length) {
        return true;
      }

      return hasSomeMatchingPredicateForSelectorWithinElement(
        form,
        "button",
        button => {
          return (
            stringRegex.test(button.value) ||
            stringRegex.test(button.textContent) ||
            stringRegex.test(button.id) ||
            stringRegex.test(button.title)
          );
        }
      );
    }
    return false;
  }

  function hasAutocompleteCurrentPassword(fnode) {
    return fnode.element.autocomplete === "current-password";
  }

  // Check cache before calling querySelectorAll on element
  function getElementDescendants(element, selector) {
    // Use the element to look up the selector map:
    const selectorToDescendants = setDefault(
      elementToSelectors,
      element,
      () => new Map()
    );

    // Use the selector to grab the descendants:
    return setDefault(selectorToDescendants, selector, () =>
      Array.from(element.querySelectorAll(selector))
    );
  }

  /**
   * Return whether the form element directly after this one looks like a
   * confirm-password input.
   */
  function nextInputIsConfirmy(fnode) {
    const form = fnode.element.form;
    const me = fnode.element;
    if (form !== null) {
      let afterMe = false;
      for (const formEl of form.elements) {
        if (formEl === me) {
          afterMe = true;
        } else if (afterMe) {
          if (
            formEl.type === "password" &&
            !formEl.disabled &&
            formEl.getAttribute("aria-hidden") !== "true"
          ) {
            // Now we're looking at a passwordy, visible input[type=password]
            // directly after me.
            return elementAttrsMatchRegex(formEl, confirmAttrRegex);
            // We could check other confirmy smells as well. Balance accuracy
            // against time and complexity.
          }
          // We look only at the very next element, so we may be thrown off by
          // Hide buttons and such.
          break;
        }
      }
    }
    return false;
  }

  /**
   * Returns true when the number of visible input found in the form is over
   * the given threshold.
   *
   * Since the idea in the signal is based on the fact that registration pages
   * often have multiple inputs, this rule only selects inputs whose type is
   * either email, password, text, tel or empty, which are more likely a input
   * field for users to fill their information.
   */
  function formHasMultipleVisibleInput(element, selector, threshold) {
    let form = element.form;
    if (!form) {
      // For password fields that don't have an associated form, we apply a heuristic
      // to find a "form" for it. The heuristic works as follow:
      // 1. Locate the closest preceding input.
      // 2. Find the lowest common ancestor of the password field and the closet
      //    preceding input.
      // 3. Assume the common ancestor is the "form" of the password input.
      const previous = closestElementAbove(element, selector);
      if (!previous) {
        return false;
      }
      form = findLowestCommonAncestor(previous, element);
      if (!form) {
        return false;
      }
    }
    const inputs = Array.from(form.querySelectorAll(selector));
    for (const input of inputs) {
      // don't need to check visibility for the element we're testing against
      if (element === input || isVisible(input)) {
        threshold--;
        if (threshold === 0) {
          return true;
        }
      }
    }
    return false;
  }

  /**
   * Returns true when there are three password fields in the form and the passed
   * element is the first one.
   *
   * The signal is based on that change-password forms with 3 password fields often
   * have the "current password", "new password", and "confirm password" pattern.
   */
  function firstFieldInFormWithThreePasswordFields(fnode) {
    const element = fnode.element;
    const form = element.form;
    if (form) {
      let elements = form.querySelectorAll(
        "input[type=password]:not([disabled], [aria-hidden=true])"
      );
      // Only care forms with three password fields. If there are more than three password
      // fields found, probably we include some hidden fields, so just ignore it.
      if (elements.length == 3 && elements[0] == element) {
        return true;
      }
    }
    return false;
  }

  function hasSomeMatchingPredicateForSelectorWithinElement(
    element,
    selector,
    matchingPredicate
  ) {
    if (element === null) {
      return false;
    }
    const elements = getElementDescendants(element, selector);
    return elements.some(matchingPredicate);
  }

  function textContentMatchesRegexes(element, ...regexes) {
    const textContent = element.textContent;
    return regexes.every(regex => regex.test(textContent));
  }

  function closestHeaderAboveMatchesRegex(element, regex) {
    const closestHeader = closestElementAbove(
      element,
      "h1,h2,h3,h4,h5,h6,div[class*=heading],div[class*=header],div[class*=title],legend"
    );
    if (closestHeader !== null) {
      return regex.test(closestHeader.textContent);
    }
    return false;
  }

  function closestElementAbove(element, selector) {
    let elements = Array.from(element.ownerDocument.querySelectorAll(selector));
    for (let i = elements.length - 1; i >= 0; --i) {
      if (
        element.compareDocumentPosition(elements[i]) &
        Node.DOCUMENT_POSITION_PRECEDING
      ) {
        return elements[i];
      }
    }
    return null;
  }

  function findLowestCommonAncestor(elementA, elementB) {
    // Walk up the ancestor chain of both elements and compare whether the
    // ancestors in the depth are the same. If they are not the same, the
    // ancestor in the previous run is the lowest common ancestor.
    function getAncestorChain(element) {
      let ancestors = [];
      let p = element.parentNode;
      while (p) {
        ancestors.push(p);
        p = p.parentNode;
      }
      return ancestors;
    }

    let aAncestors = getAncestorChain(elementA);
    let bAncestors = getAncestorChain(elementB);
    let posA = aAncestors.length - 1;
    let posB = bAncestors.length - 1;
    for (; posA >= 0 && posB >= 0; posA--, posB--) {
      if (aAncestors[posA] != bAncestors[posB]) {
        return aAncestors[posA + 1];
      }
    }
    return null;
  }

  function elementAttrsMatchRegex(element, regex) {
    if (element !== null) {
      return (
        regex.test(element.id) ||
        regex.test(element.name) ||
        regex.test(element.className)
      );
    }
    return false;
  }

  /**
   * Let us compactly represent a collection of rules that all take a single
   * type with no .when() clause and have only a score() call on the right-hand
   * side.
   */
  function* simpleScoringRulesTakingType(inType, ruleMap) {
    for (const [name, scoringCallback] of Object.entries(ruleMap)) {
      yield rule(type(inType), score(scoringCallback), { name });
    }
  }

  return ruleset(
    [
      rule(
        DEVELOPMENT
          ? dom(
              "input[type=password]:not([disabled], [aria-hidden=true])"
            ).when(isVisible)
          : element("input"),
        type("new").note(clearCache)
      ),
      ...simpleScoringRulesTakingType("new", {
        hasNewLabel: fnode =>
          hasLabelMatchingRegex(fnode.element, newStringRegex),
        hasConfirmLabel: fnode =>
          hasLabelMatchingRegex(fnode.element, confirmStringRegex),
        hasCurrentLabel: fnode =>
          hasLabelMatchingRegex(fnode.element, currentAttrAndStringRegex),
        closestLabelMatchesNew: fnode =>
          closestLabelMatchesRegex(fnode.element, newStringRegex),
        closestLabelMatchesConfirm: fnode =>
          closestLabelMatchesRegex(fnode.element, confirmStringRegex),
        closestLabelMatchesCurrent: fnode =>
          closestLabelMatchesRegex(fnode.element, currentAttrAndStringRegex),
        hasNewAriaLabel: fnode =>
          hasAriaLabelMatchingRegex(fnode.element, newStringRegex),
        hasConfirmAriaLabel: fnode =>
          hasAriaLabelMatchingRegex(fnode.element, confirmStringRegex),
        hasCurrentAriaLabel: fnode =>
          hasAriaLabelMatchingRegex(fnode.element, currentAttrAndStringRegex),
        hasNewPlaceholder: fnode =>
          hasPlaceholderMatchingRegex(fnode.element, newStringRegex),
        hasConfirmPlaceholder: fnode =>
          hasPlaceholderMatchingRegex(fnode.element, confirmStringRegex),
        hasCurrentPlaceholder: fnode =>
          hasPlaceholderMatchingRegex(fnode.element, currentAttrAndStringRegex),
        forgotPasswordInFormLinkTextContent: fnode =>
          testRegexesAgainstAnchorPropertyWithinElement(
            "textContent",
            fnode.element.form,
            passwordStringRegex,
            forgotStringRegex
          ),
        forgotPasswordInFormLinkHref: fnode =>
          testRegexesAgainstAnchorPropertyWithinElement(
            "href",
            fnode.element.form,
            passwordStringAndAttrRegex,
            forgotHrefRegex
          ),
        forgotPasswordInFormLinkTitle: fnode =>
          testRegexesAgainstAnchorPropertyWithinElement(
            "title",
            fnode.element.form,
            passwordStringRegex,
            forgotStringRegex
          ),
        forgotInFormLinkTextContent: fnode =>
          testRegexesAgainstAnchorPropertyWithinElement(
            "textContent",
            fnode.element.form,
            forgotStringRegex
          ),
        forgotInFormLinkHref: fnode =>
          testRegexesAgainstAnchorPropertyWithinElement(
            "href",
            fnode.element.form,
            forgotHrefRegex
          ),
        forgotPasswordInFormButtonTextContent: fnode =>
          hasSomeMatchingPredicateForSelectorWithinElement(
            fnode.element.form,
            "button",
            button =>
              textContentMatchesRegexes(
                button,
                passwordStringRegex,
                forgotStringRegex
              )
          ),
        forgotPasswordOnPageLinkTextContent: fnode =>
          testRegexesAgainstAnchorPropertyWithinElement(
            "textContent",
            fnode.element.ownerDocument,
            passwordStringRegex,
            forgotStringRegex
          ),
        forgotPasswordOnPageLinkHref: fnode =>
          testRegexesAgainstAnchorPropertyWithinElement(
            "href",
            fnode.element.ownerDocument,
            passwordStringAndAttrRegex,
            forgotHrefRegex
          ),
        forgotPasswordOnPageLinkTitle: fnode =>
          testRegexesAgainstAnchorPropertyWithinElement(
            "title",
            fnode.element.ownerDocument,
            passwordStringRegex,
            forgotStringRegex
          ),
        forgotPasswordOnPageButtonTextContent: fnode =>
          hasSomeMatchingPredicateForSelectorWithinElement(
            fnode.element.ownerDocument,
            "button",
            button =>
              textContentMatchesRegexes(
                button,
                passwordStringRegex,
                forgotStringRegex
              )
          ),
        elementAttrsMatchNew: fnode =>
          elementAttrsMatchRegex(fnode.element, newAttrRegex),
        elementAttrsMatchConfirm: fnode =>
          elementAttrsMatchRegex(fnode.element, confirmAttrRegex),
        elementAttrsMatchCurrent: fnode =>
          elementAttrsMatchRegex(fnode.element, currentAttrAndStringRegex),
        elementAttrsMatchPassword1: fnode =>
          elementAttrsMatchRegex(fnode.element, password1Regex),
        elementAttrsMatchPassword2: fnode =>
          elementAttrsMatchRegex(fnode.element, password2Regex),
        elementAttrsMatchLogin: fnode =>
          elementAttrsMatchRegex(fnode.element, loginRegex),
        formAttrsMatchRegister: fnode =>
          elementAttrsMatchRegex(fnode.element.form, registerFormAttrRegex),
        formHasRegisterAction: fnode =>
          containsRegex(
            registerActionRegex,
            fnode.element.form,
            form => form.action
          ),
        formButtonIsRegister: fnode =>
          testFormButtonsAgainst(fnode.element, registerStringRegex),
        formAttrsMatchLogin: fnode =>
          elementAttrsMatchRegex(fnode.element.form, loginFormAttrRegex),
        formHasLoginAction: fnode =>
          containsRegex(loginRegex, fnode.element.form, form => form.action),
        formButtonIsLogin: fnode =>
          testFormButtonsAgainst(fnode.element, loginRegex),
        hasAutocompleteCurrentPassword,
        formHasRememberMeCheckbox: fnode =>
          hasSomeMatchingPredicateForSelectorWithinElement(
            fnode.element.form,
            "input[type=checkbox]",
            checkbox =>
              rememberMeAttrRegex.test(checkbox.id) ||
              rememberMeAttrRegex.test(checkbox.name)
          ),
        formHasRememberMeLabel: fnode =>
          hasSomeMatchingPredicateForSelectorWithinElement(
            fnode.element.form,
            "label",
            label => rememberMeStringRegex.test(label.textContent)
          ),
        formHasNewsletterCheckbox: fnode =>
          hasSomeMatchingPredicateForSelectorWithinElement(
            fnode.element.form,
            "input[type=checkbox]",
            checkbox =>
              checkbox.id.includes("newsletter") ||
              checkbox.name.includes("newsletter")
          ),
        formHasNewsletterLabel: fnode =>
          hasSomeMatchingPredicateForSelectorWithinElement(
            fnode.element.form,
            "label",
            label => newsletterStringRegex.test(label.textContent)
          ),
        closestHeaderAboveIsLoginy: fnode =>
          closestHeaderAboveMatchesRegex(fnode.element, loginRegex),
        closestHeaderAboveIsRegistery: fnode =>
          closestHeaderAboveMatchesRegex(fnode.element, registerStringRegex),
        nextInputIsConfirmy,
        formHasMultipleVisibleInput: fnode =>
          formHasMultipleVisibleInput(
            fnode.element,
            "input[type=email],input[type=password],input[type=text],input[type=tel]",
            3
          ),
        firstFieldInFormWithThreePasswordFields,
      }),
      rule(type("new"), out("new")),
    ],
    coeffs,
    biases
  );
}

/*
 * ----- End of model -----
 */

export const NewPasswordModel = {
  type: "new",
  rules: makeRuleset([...coefficients.new], biases),
};
PK
!<�l�,,(modules/shared/PasswordGenerator.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This file is a port of a subset of Chromium's implementation from
 * https://cs.chromium.org/chromium/src/components/password_manager/core/browser/generation/password_generator.cc?l=93&rcl=a896a3ac4ea731b5ab3d2ab5bd76a139885d5c4f
 * which is Copyright 2018 The Chromium Authors. All rights reserved.
 */

const DEFAULT_PASSWORD_LENGTH = 15;
const MAX_UINT8 = Math.pow(2, 8) - 1;
const MAX_UINT32 = Math.pow(2, 32) - 1;

// Some characters are removed due to visual similarity:
const LOWER_CASE_ALPHA = "abcdefghijkmnpqrstuvwxyz"; // no 'l' or 'o'
const UPPER_CASE_ALPHA = "ABCDEFGHJKLMNPQRSTUVWXYZ"; // no 'I' or 'O'
const DIGITS = "23456789"; // no '1' or '0'
const SPECIAL_CHARACTERS = "-~!@#$%^&*_+=)}:;\"'>,.?]";

const REQUIRED_CHARACTER_CLASSES = [
  LOWER_CASE_ALPHA,
  UPPER_CASE_ALPHA,
  DIGITS,
  SPECIAL_CHARACTERS,
];

// Consts for different password rules
const REQUIRED = "required";
const MAX_LENGTH = "maxlength";
const MIN_LENGTH = "minlength";
const MAX_CONSECUTIVE = "max-consecutive";
const UPPER = "upper";
const LOWER = "lower";
const DIGIT = "digit";
const SPECIAL = "special";

// Default password rules
const DEFAULT_RULES = new Map();
DEFAULT_RULES.set(MIN_LENGTH, REQUIRED_CHARACTER_CLASSES.length);
DEFAULT_RULES.set(MAX_LENGTH, MAX_UINT8);
DEFAULT_RULES.set(REQUIRED, [UPPER, LOWER, DIGIT, SPECIAL]);

export const PasswordGenerator = {
  /**
   * @param {Object} options
   * @param {number} options.length - length of the generated password if there are no rules that override the length
   * @param {Map} options.rules - map of password rules
   * @returns {string} password that was generated
   * @throws Error if `length` is invalid
   * @copyright 2018 The Chromium Authors. All rights reserved.
   * @see https://cs.chromium.org/chromium/src/components/password_manager/core/browser/generation/password_generator.cc?l=93&rcl=a896a3ac4ea731b5ab3d2ab5bd76a139885d5c4f
   */
  generatePassword({
    length = DEFAULT_PASSWORD_LENGTH,
    rules = DEFAULT_RULES,
    inputMaxLength,
  }) {
    rules = new Map([...DEFAULT_RULES, ...rules]);
    if (rules.get(MIN_LENGTH) > length) {
      length = rules.get(MIN_LENGTH);
    }
    if (rules.get(MAX_LENGTH) < length) {
      length = rules.get(MAX_LENGTH);
    }
    if (inputMaxLength > 0 && inputMaxLength < length) {
      length = inputMaxLength;
    }

    let password = "";
    let requiredClasses = [];
    let allRequiredCharacters = "";

    // Generate one character of each required class and/or required character list from the rules
    this._addRequiredClassesAndCharacters(rules, requiredClasses);

    // Generate one of each required class
    for (const charClassString of requiredClasses) {
      password +=
        charClassString[this._randomUInt8Index(charClassString.length)];
      if (Array.isArray(charClassString)) {
        // Convert array into single string so that commas aren't
        // concatenated with each character in the arbitrary character array.
        allRequiredCharacters += charClassString.join("");
      } else {
        allRequiredCharacters += charClassString;
      }
    }

    // Now fill the rest of the password with random characters.
    while (password.length < length) {
      password +=
        allRequiredCharacters[
          this._randomUInt8Index(allRequiredCharacters.length)
        ];
    }

    // So far the password contains the minimally required characters at the
    // the beginning. Therefore, we create a random permutation.
    password = this._shuffleString(password);

    // Make sure the password passes the "max-consecutive" rule, if the rule exists
    if (rules.has(MAX_CONSECUTIVE)) {
      // Ensures that a password isn't shuffled an infinite number of times.
      const DEFAULT_NUMBER_OF_SHUFFLES = 15;
      let shuffleCount = 0;
      let consecutiveFlag = this._checkConsecutiveCharacters(
        password,
        rules.get(MAX_CONSECUTIVE)
      );
      while (!consecutiveFlag) {
        password = this._shuffleString(password);
        consecutiveFlag = this._checkConsecutiveCharacters(
          password,
          rules.get(MAX_CONSECUTIVE)
        );
        ++shuffleCount;
        if (shuffleCount === DEFAULT_NUMBER_OF_SHUFFLES) {
          consecutiveFlag = true;
        }
      }
    }

    return password;
  },

  /**
   * Adds special characters and/or other required characters to the requiredCharacters array.
   * @param {Map} rules
   * @param {string[]} requiredClasses
   */
  _addRequiredClassesAndCharacters(rules, requiredClasses) {
    for (const charClass of rules.get(REQUIRED)) {
      if (charClass === UPPER) {
        requiredClasses.push(UPPER_CASE_ALPHA);
      } else if (charClass === LOWER) {
        requiredClasses.push(LOWER_CASE_ALPHA);
      } else if (charClass === DIGIT) {
        requiredClasses.push(DIGITS);
      } else if (charClass === SPECIAL) {
        requiredClasses.push(SPECIAL_CHARACTERS);
      } else {
        requiredClasses.push(charClass);
      }
    }
  },

  /**
   * @param range to generate the number in
   * @returns a random number in range [0, range).
   * @copyright 2018 The Chromium Authors. All rights reserved.
   * @see https://cs.chromium.org/chromium/src/base/rand_util.cc?l=58&rcl=648a59893e4ed5303b5c381b03ce0c75e4165617
   */
  _randomUInt8Index(range) {
    if (range > MAX_UINT8) {
      throw new Error("`range` cannot fit into uint8");
    }
    // We must discard random results above this number, as they would
    // make the random generator non-uniform (consider e.g. if
    // MAX_UINT64 was 7 and |range| was 5, then a result of 1 would be twice
    // as likely as a result of 3 or 4).
    // See https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#Modulo_bias
    const MAX_ACCEPTABLE_VALUE = Math.floor(MAX_UINT8 / range) * range - 1;

    const randomValueArr = new Uint8Array(1);
    do {
      crypto.getRandomValues(randomValueArr);
    } while (randomValueArr[0] > MAX_ACCEPTABLE_VALUE);
    return randomValueArr[0] % range;
  },

  /**
   * Shuffle the order of characters in a string.
   * @param {string} str to shuffle
   * @returns {string} shuffled string
   */
  _shuffleString(str) {
    let arr = Array.from(str);
    // Generate all the random numbers that will be needed.
    const randomValues = new Uint32Array(arr.length - 1);
    crypto.getRandomValues(randomValues);

    // Fisher-Yates Shuffle
    // https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
    for (let i = arr.length - 1; i > 0; i--) {
      const j = Math.floor((randomValues[i - 1] / MAX_UINT32) * (i + 1));
      [arr[i], arr[j]] = [arr[j], arr[i]];
    }
    return arr.join("");
  },

  /**
   * Determine the number of consecutive characters in a string.
   * This is primarily used to validate the "max-consecutive" rule
   * of a generated password.
   * @param {string} generatedPassword
   * @param {number} value the number of consecutive characters allowed
   * @return {boolean} `true` if the generatePassword has less than the value argument number of characters, `false` otherwise
   */
  _checkConsecutiveCharacters(generatedPassword, value) {
    let max = 0;
    for (let start = 0, end = 1; end < generatedPassword.length; ) {
      if (generatedPassword[end] === generatedPassword[start]) {
        if (max < end - start + 1) {
          max = end - start + 1;
          if (max > value) {
            return false;
          }
        }
        end++;
      } else {
        start = end++;
      }
    }
    return true;
  },
  _getUpperCaseCharacters() {
    return UPPER_CASE_ALPHA;
  },
  _getLowerCaseCharacters() {
    return LOWER_CASE_ALPHA;
  },
  _getDigits() {
    return DIGITS;
  },
  _getSpecialCharacters() {
    return SPECIAL_CHARACTERS;
  },
};
PK
!<m��'�I�I*modules/shared/PasswordRulesParser.sys.mjs// Sourced from https://github.com/apple/password-manager-resources/blob/5f6da89483e75cdc4165a6fc4756796e0ced7a21/tools/PasswordRulesParser.js
// Copyright (c) 2019 - 2020 Apple Inc. Licensed under MIT License.

export const PasswordRulesParser = {
  parsePasswordRules,
};

const Identifier = {
  ASCII_PRINTABLE: "ascii-printable",
  DIGIT: "digit",
  LOWER: "lower",
  SPECIAL: "special",
  UNICODE: "unicode",
  UPPER: "upper",
};

const RuleName = {
  ALLOWED: "allowed",
  MAX_CONSECUTIVE: "max-consecutive",
  REQUIRED: "required",
  MIN_LENGTH: "minlength",
  MAX_LENGTH: "maxlength",
};

const CHARACTER_CLASS_START_SENTINEL = "[";
const CHARACTER_CLASS_END_SENTINEL = "]";
const PROPERTY_VALUE_SEPARATOR = ",";
const PROPERTY_SEPARATOR = ";";
const PROPERTY_VALUE_START_SENTINEL = ":";

const SPACE_CODE_POINT = " ".codePointAt(0);

const SHOULD_NOT_BE_REACHED = "Should not be reached";

class Rule {
  constructor(name, value) {
    this._name = name;
    this.value = value;
  }
  get name() {
    return this._name;
  }
  toString() {
    return JSON.stringify(this);
  }
}

class NamedCharacterClass {
  constructor(name) {
    console.assert(_isValidRequiredOrAllowedPropertyValueIdentifier(name));
    this._name = name;
  }
  get name() {
    return this._name.toLowerCase();
  }
  toString() {
    return this._name;
  }
  toHTMLString() {
    return this._name;
  }
}

class CustomCharacterClass {
  constructor(characters) {
    console.assert(characters instanceof Array);
    this._characters = characters;
  }
  get characters() {
    return this._characters;
  }
  toString() {
    return `[${this._characters.join("")}]`;
  }
  toHTMLString() {
    return `[${this._characters.join("").replace('"', "&quot;")}]`;
  }
}

// MARK: Lexer functions

function _isIdentifierCharacter(c) {
  console.assert(c.length === 1);
  return (c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || c === "-";
}

function _isASCIIDigit(c) {
  console.assert(c.length === 1);
  return c >= "0" && c <= "9";
}

function _isASCIIPrintableCharacter(c) {
  console.assert(c.length === 1);
  return c >= " " && c <= "~";
}

function _isASCIIWhitespace(c) {
  console.assert(c.length === 1);
  return c === " " || c === "\f" || c === "\n" || c === "\r" || c === "\t";
}

// MARK: ASCII printable character bit set and canonicalization functions

function _bitSetIndexForCharacter(c) {
  console.assert(c.length == 1);
  return c.codePointAt(0) - SPACE_CODE_POINT;
}

function _characterAtBitSetIndex(index) {
  return String.fromCodePoint(index + SPACE_CODE_POINT);
}

function _markBitsForNamedCharacterClass(bitSet, namedCharacterClass) {
  console.assert(bitSet instanceof Array);
  console.assert(namedCharacterClass.name !== Identifier.UNICODE);
  console.assert(namedCharacterClass.name !== Identifier.ASCII_PRINTABLE);
  if (namedCharacterClass.name === Identifier.UPPER) {
    bitSet.fill(
      true,
      _bitSetIndexForCharacter("A"),
      _bitSetIndexForCharacter("Z") + 1
    );
  } else if (namedCharacterClass.name === Identifier.LOWER) {
    bitSet.fill(
      true,
      _bitSetIndexForCharacter("a"),
      _bitSetIndexForCharacter("z") + 1
    );
  } else if (namedCharacterClass.name === Identifier.DIGIT) {
    bitSet.fill(
      true,
      _bitSetIndexForCharacter("0"),
      _bitSetIndexForCharacter("9") + 1
    );
  } else if (namedCharacterClass.name === Identifier.SPECIAL) {
    bitSet.fill(
      true,
      _bitSetIndexForCharacter(" "),
      _bitSetIndexForCharacter("/") + 1
    );
    bitSet.fill(
      true,
      _bitSetIndexForCharacter(":"),
      _bitSetIndexForCharacter("@") + 1
    );
    bitSet.fill(
      true,
      _bitSetIndexForCharacter("["),
      _bitSetIndexForCharacter("`") + 1
    );
    bitSet.fill(
      true,
      _bitSetIndexForCharacter("{"),
      _bitSetIndexForCharacter("~") + 1
    );
  } else {
    console.assert(false, SHOULD_NOT_BE_REACHED, namedCharacterClass);
  }
}

function _markBitsForCustomCharacterClass(bitSet, customCharacterClass) {
  for (let character of customCharacterClass.characters) {
    bitSet[_bitSetIndexForCharacter(character)] = true;
  }
}

function _canonicalizedPropertyValues(
  propertyValues,
  keepCustomCharacterClassFormatCompliant
) {
  let asciiPrintableBitSet = new Array(
    "~".codePointAt(0) - " ".codePointAt(0) + 1
  );

  for (let propertyValue of propertyValues) {
    if (propertyValue instanceof NamedCharacterClass) {
      if (propertyValue.name === Identifier.UNICODE) {
        return [new NamedCharacterClass(Identifier.UNICODE)];
      }

      if (propertyValue.name === Identifier.ASCII_PRINTABLE) {
        return [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)];
      }

      _markBitsForNamedCharacterClass(asciiPrintableBitSet, propertyValue);
    } else if (propertyValue instanceof CustomCharacterClass) {
      _markBitsForCustomCharacterClass(asciiPrintableBitSet, propertyValue);
    }
  }

  let charactersSeen = [];

  function checkRange(start, end) {
    let temp = [];
    for (
      let i = _bitSetIndexForCharacter(start);
      i <= _bitSetIndexForCharacter(end);
      ++i
    ) {
      if (asciiPrintableBitSet[i]) {
        temp.push(_characterAtBitSetIndex(i));
      }
    }

    let result =
      temp.length ===
      _bitSetIndexForCharacter(end) - _bitSetIndexForCharacter(start) + 1;
    if (!result) {
      charactersSeen = charactersSeen.concat(temp);
    }
    return result;
  }

  let hasAllUpper = checkRange("A", "Z");
  let hasAllLower = checkRange("a", "z");
  let hasAllDigits = checkRange("0", "9");

  // Check for special characters, accounting for characters that are given special treatment (i.e. '-' and ']')
  let hasAllSpecial = false;
  let hasDash = false;
  let hasRightSquareBracket = false;
  let temp = [];
  for (
    let i = _bitSetIndexForCharacter(" ");
    i <= _bitSetIndexForCharacter("/");
    ++i
  ) {
    if (!asciiPrintableBitSet[i]) {
      continue;
    }

    let character = _characterAtBitSetIndex(i);
    if (keepCustomCharacterClassFormatCompliant && character === "-") {
      hasDash = true;
    } else {
      temp.push(character);
    }
  }
  for (
    let i = _bitSetIndexForCharacter(":");
    i <= _bitSetIndexForCharacter("@");
    ++i
  ) {
    if (asciiPrintableBitSet[i]) {
      temp.push(_characterAtBitSetIndex(i));
    }
  }
  for (
    let i = _bitSetIndexForCharacter("[");
    i <= _bitSetIndexForCharacter("`");
    ++i
  ) {
    if (!asciiPrintableBitSet[i]) {
      continue;
    }

    let character = _characterAtBitSetIndex(i);
    if (keepCustomCharacterClassFormatCompliant && character === "]") {
      hasRightSquareBracket = true;
    } else {
      temp.push(character);
    }
  }
  for (
    let i = _bitSetIndexForCharacter("{");
    i <= _bitSetIndexForCharacter("~");
    ++i
  ) {
    if (asciiPrintableBitSet[i]) {
      temp.push(_characterAtBitSetIndex(i));
    }
  }

  if (hasDash) {
    temp.unshift("-");
  }
  if (hasRightSquareBracket) {
    temp.push("]");
  }

  let numberOfSpecialCharacters =
    _bitSetIndexForCharacter("/") -
    _bitSetIndexForCharacter(" ") +
    1 +
    (_bitSetIndexForCharacter("@") - _bitSetIndexForCharacter(":") + 1) +
    (_bitSetIndexForCharacter("`") - _bitSetIndexForCharacter("[") + 1) +
    (_bitSetIndexForCharacter("~") - _bitSetIndexForCharacter("{") + 1);
  hasAllSpecial = temp.length === numberOfSpecialCharacters;
  if (!hasAllSpecial) {
    charactersSeen = charactersSeen.concat(temp);
  }

  let result = [];
  if (hasAllUpper && hasAllLower && hasAllDigits && hasAllSpecial) {
    return [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)];
  }
  if (hasAllUpper) {
    result.push(new NamedCharacterClass(Identifier.UPPER));
  }
  if (hasAllLower) {
    result.push(new NamedCharacterClass(Identifier.LOWER));
  }
  if (hasAllDigits) {
    result.push(new NamedCharacterClass(Identifier.DIGIT));
  }
  if (hasAllSpecial) {
    result.push(new NamedCharacterClass(Identifier.SPECIAL));
  }
  if (charactersSeen.length) {
    result.push(new CustomCharacterClass(charactersSeen));
  }
  return result;
}

// MARK: Parser functions

function _indexOfNonWhitespaceCharacter(input, position = 0) {
  console.assert(position >= 0);
  console.assert(position <= input.length);

  let length = input.length;
  while (position < length && _isASCIIWhitespace(input[position])) {
    ++position;
  }

  return position;
}

function _parseIdentifier(input, position) {
  console.assert(position >= 0);
  console.assert(position < input.length);
  console.assert(_isIdentifierCharacter(input[position]));

  let length = input.length;
  let seenIdentifiers = [];
  do {
    let c = input[position];
    if (!_isIdentifierCharacter(c)) {
      break;
    }

    seenIdentifiers.push(c);
    ++position;
  } while (position < length);

  return [seenIdentifiers.join(""), position];
}

function _isValidRequiredOrAllowedPropertyValueIdentifier(identifier) {
  return (
    identifier && Object.values(Identifier).includes(identifier.toLowerCase())
  );
}

function _parseCustomCharacterClass(input, position) {
  console.assert(position >= 0);
  console.assert(position < input.length);
  console.assert(input[position] === CHARACTER_CLASS_START_SENTINEL);

  let length = input.length;
  ++position;
  if (position >= length) {
    console.error("Found end-of-line instead of character class character");
    return [null, position];
  }

  let initialPosition = position;
  let result = [];
  do {
    let c = input[position];
    if (!_isASCIIPrintableCharacter(c)) {
      ++position;
      continue;
    }

    if (c === "-" && position - initialPosition > 0) {
      // FIXME: Should this be an error?
      console.warn(
        "Ignoring '-'; a '-' may only appear as the first character in a character class"
      );
      ++position;
      continue;
    }

    result.push(c);
    ++position;
    if (c === CHARACTER_CLASS_END_SENTINEL) {
      break;
    }
  } while (position < length);

  if (
    (position < length && input[position] !== CHARACTER_CLASS_END_SENTINEL) ||
    (position == length && input[position - 1] == CHARACTER_CLASS_END_SENTINEL)
  ) {
    // Fix up result; we over consumed.
    result.pop();
    return [result, position];
  }

  if (position < length && input[position] == CHARACTER_CLASS_END_SENTINEL) {
    return [result, position + 1];
  }

  console.error("Found end-of-line instead of end of character class");
  return [null, position];
}

function _parsePasswordRequiredOrAllowedPropertyValue(input, position) {
  console.assert(position >= 0);
  console.assert(position < input.length);

  let length = input.length;
  let propertyValues = [];
  while (true) {
    if (_isIdentifierCharacter(input[position])) {
      let identifierStartPosition = position;
      var [propertyValue, position] = _parseIdentifier(input, position);
      if (!_isValidRequiredOrAllowedPropertyValueIdentifier(propertyValue)) {
        console.error(
          "Unrecognized property value identifier: " + propertyValue
        );
        return [null, identifierStartPosition];
      }
      propertyValues.push(new NamedCharacterClass(propertyValue));
    } else if (input[position] == CHARACTER_CLASS_START_SENTINEL) {
      var [propertyValue, position] = _parseCustomCharacterClass(
        input,
        position
      );
      if (propertyValue && propertyValue.length) {
        propertyValues.push(new CustomCharacterClass(propertyValue));
      }
    } else {
      console.error(
        "Failed to find start of property value: " + input.substr(position)
      );
      return [null, position];
    }

    position = _indexOfNonWhitespaceCharacter(input, position);
    if (position >= length || input[position] === PROPERTY_SEPARATOR) {
      break;
    }

    if (input[position] === PROPERTY_VALUE_SEPARATOR) {
      position = _indexOfNonWhitespaceCharacter(input, position + 1);
      if (position >= length) {
        console.error(
          "Found end-of-line instead of start of next property value"
        );
        return [null, position];
      }
      continue;
    }

    console.error(
      "Failed to find start of next property or property value: " +
        input.substr(position)
    );
    return [null, position];
  }
  return [propertyValues, position];
}

function _parsePasswordRule(input, position) {
  console.assert(position >= 0);
  console.assert(position < input.length);
  console.assert(_isIdentifierCharacter(input[position]));

  let length = input.length;

  let mayBeIdentifierStartPosition = position;
  var [identifier, position] = _parseIdentifier(input, position);
  if (!Object.values(RuleName).includes(identifier)) {
    console.error("Unrecognized property name: " + identifier);
    return [null, mayBeIdentifierStartPosition];
  }

  if (position >= length) {
    console.error("Found end-of-line instead of start of property value");
    return [null, position];
  }

  if (input[position] !== PROPERTY_VALUE_START_SENTINEL) {
    console.error(
      "Failed to find start of property value: " + input.substr(position)
    );
    return [null, position];
  }

  let property = { name: identifier, value: null };

  position = _indexOfNonWhitespaceCharacter(input, position + 1);
  // Empty value
  if (position >= length || input[position] === PROPERTY_SEPARATOR) {
    return [new Rule(property.name, property.value), position];
  }

  switch (identifier) {
    case RuleName.ALLOWED:
    case RuleName.REQUIRED: {
      var [
        propertyValue,
        position,
      ] = _parsePasswordRequiredOrAllowedPropertyValue(input, position);
      if (propertyValue) {
        property.value = propertyValue;
      }
      return [new Rule(property.name, property.value), position];
    }
    case RuleName.MAX_CONSECUTIVE: {
      var [propertyValue, position] = _parseMaxConsecutivePropertyValue(
        input,
        position
      );
      if (propertyValue) {
        property.value = propertyValue;
      }
      return [new Rule(property.name, property.value), position];
    }
    case RuleName.MIN_LENGTH:
    case RuleName.MAX_LENGTH: {
      var [propertyValue, position] = _parseMinLengthMaxLengthPropertyValue(
        input,
        position
      );
      if (propertyValue) {
        property.value = propertyValue;
      }
      return [new Rule(property.name, property.value), position];
    }
  }
  console.assert(false, SHOULD_NOT_BE_REACHED);
}

function _parseMinLengthMaxLengthPropertyValue(input, position) {
  return _parseInteger(input, position);
}

function _parseMaxConsecutivePropertyValue(input, position) {
  return _parseInteger(input, position);
}

function _parseInteger(input, position) {
  console.assert(position >= 0);
  console.assert(position < input.length);

  if (!_isASCIIDigit(input[position])) {
    console.error(
      "Failed to parse value of type integer; not a number: " +
        input.substr(position)
    );
    return [null, position];
  }

  let length = input.length;
  let initialPosition = position;
  let result = 0;
  do {
    result = 10 * result + parseInt(input[position], 10);
    ++position;
  } while (
    position < length &&
    input[position] !== PROPERTY_SEPARATOR &&
    _isASCIIDigit(input[position])
  );

  if (position >= length || input[position] === PROPERTY_SEPARATOR) {
    return [result, position];
  }

  console.error(
    "Failed to parse value of type integer; not a number: " +
      input.substr(initialPosition)
  );
  return [null, position];
}

function _parsePasswordRulesInternal(input) {
  let parsedProperties = [];
  let length = input.length;

  var position = _indexOfNonWhitespaceCharacter(input);
  while (position < length) {
    if (!_isIdentifierCharacter(input[position])) {
      console.warn(
        "Failed to find start of property: " + input.substr(position)
      );
      return parsedProperties;
    }

    var [parsedProperty, position] = _parsePasswordRule(input, position);
    if (parsedProperty && parsedProperty.value) {
      parsedProperties.push(parsedProperty);
    }

    position = _indexOfNonWhitespaceCharacter(input, position);
    if (position >= length) {
      break;
    }

    if (input[position] === PROPERTY_SEPARATOR) {
      position = _indexOfNonWhitespaceCharacter(input, position + 1);
      if (position >= length) {
        return parsedProperties;
      }

      continue;
    }

    console.error(
      "Failed to find start of next property: " + input.substr(position)
    );
    return null;
  }

  return parsedProperties;
}

function parsePasswordRules(input, formatRulesForMinifiedVersion) {
  let passwordRules = _parsePasswordRulesInternal(input) || [];

  // When formatting rules for minified version, we should keep the formatted rules
  // as similar to the input as possible. Avoid copying required rules to allowed rules.
  let suppressCopyingRequiredToAllowed = formatRulesForMinifiedVersion;

  let newPasswordRules = [];
  let newAllowedValues = [];
  let minimumMaximumConsecutiveCharacters = null;
  let maximumMinLength = 0;
  let minimumMaxLength = null;

  for (let rule of passwordRules) {
    switch (rule.name) {
      case RuleName.MAX_CONSECUTIVE:
        minimumMaximumConsecutiveCharacters = minimumMaximumConsecutiveCharacters
          ? Math.min(rule.value, minimumMaximumConsecutiveCharacters)
          : rule.value;
        break;

      case RuleName.MIN_LENGTH:
        maximumMinLength = Math.max(rule.value, maximumMinLength);
        break;

      case RuleName.MAX_LENGTH:
        minimumMaxLength = minimumMaxLength
          ? Math.min(rule.value, minimumMaxLength)
          : rule.value;
        break;

      case RuleName.REQUIRED:
        rule.value = _canonicalizedPropertyValues(
          rule.value,
          formatRulesForMinifiedVersion
        );
        newPasswordRules.push(rule);
        if (!suppressCopyingRequiredToAllowed) {
          newAllowedValues = newAllowedValues.concat(rule.value);
        }
        break;

      case RuleName.ALLOWED:
        newAllowedValues = newAllowedValues.concat(rule.value);
        break;
    }
  }

  newAllowedValues = _canonicalizedPropertyValues(
    newAllowedValues,
    suppressCopyingRequiredToAllowed
  );
  if (!suppressCopyingRequiredToAllowed && !newAllowedValues.length) {
    newAllowedValues = [new NamedCharacterClass(Identifier.ASCII_PRINTABLE)];
  }
  if (newAllowedValues.length) {
    newPasswordRules.push(new Rule(RuleName.ALLOWED, newAllowedValues));
  }

  if (minimumMaximumConsecutiveCharacters !== null) {
    newPasswordRules.push(
      new Rule(RuleName.MAX_CONSECUTIVE, minimumMaximumConsecutiveCharacters)
    );
  }

  if (maximumMinLength > 0) {
    newPasswordRules.push(new Rule(RuleName.MIN_LENGTH, maximumMinLength));
  }

  if (minimumMaxLength !== null) {
    newPasswordRules.push(new Rule(RuleName.MAX_LENGTH, minimumMaxLength));
  }

  return newPasswordRules;
}
PK
!<�ˇ��,modules/shared/PhoneNumberNormalizer.sys.mjs/* This Source Code Form is subject to the terms of the Apache License, Version
 * 2.0. If a copy of the Apache License was not distributed with this file, You
 * can obtain one at https://www.apache.org/licenses/LICENSE-2.0 */

// This library came from https://github.com/andreasgal/PhoneNumber.js but will
// be further maintained by our own in Form Autofill codebase.

export var PhoneNumberNormalizer = (function () {
  const UNICODE_DIGITS = /[\uFF10-\uFF19\u0660-\u0669\u06F0-\u06F9]/g;
  const VALID_ALPHA_PATTERN = /[a-zA-Z]/g;
  const LEADING_PLUS_CHARS_PATTERN = /^[+\uFF0B]+/g;
  const NON_DIALABLE_CHARS = /[^,#+\*\d]/g;

  // Map letters to numbers according to the ITU E.161 standard
  let E161 = {
    a: 2,
    b: 2,
    c: 2,
    d: 3,
    e: 3,
    f: 3,
    g: 4,
    h: 4,
    i: 4,
    j: 5,
    k: 5,
    l: 5,
    m: 6,
    n: 6,
    o: 6,
    p: 7,
    q: 7,
    r: 7,
    s: 7,
    t: 8,
    u: 8,
    v: 8,
    w: 9,
    x: 9,
    y: 9,
    z: 9,
  };

  // Normalize a number by converting unicode numbers and symbols to their
  // ASCII equivalents and removing all non-dialable characters.
  function NormalizeNumber(number, numbersOnly) {
    if (typeof number !== "string") {
      return "";
    }

    number = number.replace(UNICODE_DIGITS, function (ch) {
      return String.fromCharCode(48 + (ch.charCodeAt(0) & 0xf));
    });
    if (!numbersOnly) {
      number = number.replace(VALID_ALPHA_PATTERN, function (ch) {
        return String(E161[ch.toLowerCase()] || 0);
      });
    }
    number = number.replace(LEADING_PLUS_CHARS_PATTERN, "+");
    number = number.replace(NON_DIALABLE_CHARS, "");
    return number;
  }

  return {
    Normalize: NormalizeNumber,
  };
})();
PK
!<�ޙ^>S>S,modules/subprocess/subprocess_common.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-disable mozilla/balanced-listeners */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

var obj = {};
Services.scriptloader.loadSubScript(
  "resource://gre/modules/subprocess/subprocess_shared.js",
  obj
);

const { ArrayBuffer_transfer } = obj;
export const SubprocessConstants = obj.SubprocessConstants;

const BUFFER_SIZE = 32768;

let nextResponseId = 0;

/**
 * Wraps a ChromeWorker so that messages sent to it return a promise which
 * resolves when the message has been received and the operation it triggers is
 * complete.
 */
export class PromiseWorker extends ChromeWorker {
  constructor(url) {
    super(url);

    this.listeners = new Map();
    this.pendingResponses = new Map();

    this.addListener("close", this.onClose.bind(this));
    this.addListener("failure", this.onFailure.bind(this));
    this.addListener("success", this.onSuccess.bind(this));
    this.addListener("debug", this.onDebug.bind(this));

    this.addEventListener("message", this.onmessage);

    this.shutdown = this.shutdown.bind(this);
    lazy.AsyncShutdown.webWorkersShutdown.addBlocker(
      "Subprocess.sys.mjs: Shut down IO worker",
      this.shutdown
    );
  }

  onClose() {
    lazy.AsyncShutdown.webWorkersShutdown.removeBlocker(this.shutdown);
  }

  shutdown() {
    return this.call("shutdown", []);
  }

  /**
   * Adds a listener for the given message from the worker. Any message received
   * from the worker with a `data.msg` property matching the given `msg`
   * parameter are passed to the given listener.
   *
   * @param {string} msg
   *        The message to listen for.
   * @param {function(Event)} listener
   *        The listener to call when matching messages are received.
   */
  addListener(msg, listener) {
    if (!this.listeners.has(msg)) {
      this.listeners.set(msg, new Set());
    }
    this.listeners.get(msg).add(listener);
  }

  /**
   * Removes the given message listener.
   *
   * @param {string} msg
   *        The message to stop listening for.
   * @param {function(Event)} listener
   *        The listener to remove.
   */
  removeListener(msg, listener) {
    let listeners = this.listeners.get(msg);
    if (listeners) {
      listeners.delete(listener);

      if (!listeners.size) {
        this.listeners.delete(msg);
      }
    }
  }

  onmessage(event) {
    let { msg } = event.data;
    let listeners = this.listeners.get(msg) || new Set();

    for (let listener of listeners) {
      try {
        listener(event.data);
      } catch (e) {
        console.error(e);
      }
    }
  }

  /**
   * Called when a message sent to the worker has failed, and rejects its
   * corresponding promise.
   *
   * @private
   */
  onFailure({ msgId, error }) {
    this.pendingResponses.get(msgId).reject(error);
    this.pendingResponses.delete(msgId);
  }

  /**
   * Called when a message sent to the worker has succeeded, and resolves its
   * corresponding promise.
   *
   * @private
   */
  onSuccess({ msgId, data }) {
    this.pendingResponses.get(msgId).resolve(data);
    this.pendingResponses.delete(msgId);
  }

  onDebug({ message }) {
    dump(`Worker debug: ${message}\n`);
  }

  /**
   * Calls the given method in the worker, and returns a promise which resolves
   * or rejects when the method has completed.
   *
   * @param {string} method
   *        The name of the method to call.
   * @param {Array} args
   *        The arguments to pass to the method.
   * @param {Array} [transferList]
   *        A list of objects to transfer to the worker, rather than cloning.
   * @returns {Promise}
   */
  call(method, args, transferList = []) {
    let msgId = nextResponseId++;

    return new Promise((resolve, reject) => {
      this.pendingResponses.set(msgId, { resolve, reject });

      let message = {
        msg: method,
        msgId,
        args,
      };

      this.postMessage(message, transferList);
    });
  }
}

/**
 * Represents an input or output pipe connected to a subprocess.
 *
 * @property {integer} fd
 *           The file descriptor number of the pipe on the child process's side.
 *           @readonly
 */
class Pipe {
  /**
   * @param {Process} process
   *        The child process that this pipe is connected to.
   * @param {integer} fd
   *        The file descriptor number of the pipe on the child process's side.
   * @param {integer} id
   *        The internal ID of the pipe, which ties it to the corresponding Pipe
   *        object on the Worker side.
   */
  constructor(process, fd, id) {
    this.id = id;
    this.fd = fd;
    this.processId = process.id;
    this.worker = process.worker;

    /**
     * @property {boolean} closed
     *           True if the file descriptor has been closed, and can no longer
     *           be read from or written to. Pending IO operations may still
     *           complete, but new operations may not be initiated.
     *           @readonly
     */
    this.closed = false;
  }

  /**
   * Closes the end of the pipe which belongs to this process.
   *
   * @param {boolean} force
   *        If true, the pipe is closed immediately, regardless of any pending
   *        IO operations. If false, the pipe is closed after any existing
   *        pending IO operations have completed.
   * @returns {Promise<object>}
   *          Resolves to an object with no properties once the pipe has been
   *          closed.
   */
  close(force = false) {
    this.closed = true;
    return this.worker.call("close", [this.id, force]);
  }
}

/**
 * Represents an output-only pipe, to which data may be written.
 */
class OutputPipe extends Pipe {
  constructor(...args) {
    super(...args);

    this.encoder = new TextEncoder();
  }

  /**
   * Writes the given data to the stream.
   *
   * When given an array buffer or typed array, ownership of the buffer is
   * transferred to the IO worker, and it may no longer be used from this
   * thread.
   *
   * @param {ArrayBuffer|TypedArray|string} buffer
   *        Data to write to the stream.
   * @returns {Promise<object>}
   *          Resolves to an object with a `bytesWritten` property, containing
   *          the number of bytes successfully written, once the operation has
   *          completed.
   *
   * @throws {object}
   *          May be rejected with an Error object, or an object with similar
   *          properties. The object will include an `errorCode` property with
   *          one of the following values if it was rejected for the
   *          corresponding reason:
   *
   *            - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
   *              all of the data in `buffer` could be written to it.
   */
  write(buffer) {
    if (typeof buffer === "string") {
      buffer = this.encoder.encode(buffer);
    }

    if (Cu.getClassName(buffer, true) !== "ArrayBuffer") {
      if (buffer.byteLength === buffer.buffer.byteLength) {
        buffer = buffer.buffer;
      } else {
        buffer = buffer.buffer.slice(
          buffer.byteOffset,
          buffer.byteOffset + buffer.byteLength
        );
      }
    }

    let args = [this.id, buffer];

    return this.worker.call("write", args, [buffer]);
  }
}

/**
 * Represents an input-only pipe, from which data may be read.
 */
class InputPipe extends Pipe {
  constructor(...args) {
    super(...args);

    this.buffers = [];

    /**
     * @property {integer} dataAvailable
     *           The number of readable bytes currently stored in the input
     *           buffer.
     *           @readonly
     */
    this.dataAvailable = 0;

    this.decoder = new TextDecoder();

    this.pendingReads = [];

    this._pendingBufferRead = null;

    this.fillBuffer();
  }

  /**
   * @property {integer} bufferSize
   *           The current size of the input buffer. This varies depending on
   *           the size of pending read operations.
   *           @readonly
   */
  get bufferSize() {
    if (this.pendingReads.length) {
      return Math.max(this.pendingReads[0].length, BUFFER_SIZE);
    }
    return BUFFER_SIZE;
  }

  /**
   * Attempts to fill the input buffer.
   *
   * @private
   */
  fillBuffer() {
    let dataWanted = this.bufferSize - this.dataAvailable;

    if (!this._pendingBufferRead && dataWanted > 0) {
      this._pendingBufferRead = this._read(dataWanted);

      this._pendingBufferRead.then(result => {
        this._pendingBufferRead = null;

        if (result) {
          this.onInput(result.buffer);

          this.fillBuffer();
        }
      });
    }
  }

  _read(size) {
    let args = [this.id, size];

    return this.worker.call("read", args).catch(e => {
      this.closed = true;

      for (let { length, resolve, reject } of this.pendingReads.splice(0)) {
        if (
          length === null &&
          e.errorCode === SubprocessConstants.ERROR_END_OF_FILE
        ) {
          resolve(new ArrayBuffer(0));
        } else {
          reject(e);
        }
      }
    });
  }

  /**
   * Adds the given data to the end of the input buffer.
   *
   * @param {ArrayBuffer} buffer
   *        An input buffer to append to the current buffered input.
   * @private
   */
  onInput(buffer) {
    this.buffers.push(buffer);
    this.dataAvailable += buffer.byteLength;
    this.checkPendingReads();
  }

  /**
   * Checks the topmost pending read operations and fulfills as many as can be
   * filled from the current input buffer.
   *
   * @private
   */
  checkPendingReads() {
    this.fillBuffer();

    let reads = this.pendingReads;
    while (
      reads.length &&
      this.dataAvailable &&
      reads[0].length <= this.dataAvailable
    ) {
      let pending = this.pendingReads.shift();

      let length = pending.length || this.dataAvailable;

      let result;
      let byteLength = this.buffers[0].byteLength;
      if (byteLength == length) {
        result = this.buffers.shift();
      } else if (byteLength > length) {
        let buffer = this.buffers[0];

        this.buffers[0] = buffer.slice(length);
        result = ArrayBuffer_transfer(buffer, length);
      } else {
        result = ArrayBuffer_transfer(this.buffers.shift(), length);
        let u8result = new Uint8Array(result);

        while (byteLength < length) {
          let buffer = this.buffers[0];
          let u8buffer = new Uint8Array(buffer);

          let remaining = length - byteLength;

          if (buffer.byteLength <= remaining) {
            this.buffers.shift();

            u8result.set(u8buffer, byteLength);
          } else {
            this.buffers[0] = buffer.slice(remaining);

            u8result.set(u8buffer.subarray(0, remaining), byteLength);
          }

          byteLength += Math.min(buffer.byteLength, remaining);
        }
      }

      this.dataAvailable -= result.byteLength;
      pending.resolve(result);
    }
  }

  /**
   * Reads exactly `length` bytes of binary data from the input stream, or, if
   * length is not provided, reads the first chunk of data to become available.
   * In the latter case, returns an empty array buffer on end of file.
   *
   * The read operation will not complete until enough data is available to
   * fulfill the request. If the pipe closes without enough available data to
   * fulfill the read, the operation fails, and any remaining buffered data is
   * lost.
   *
   * @param {integer} [length]
   *        The number of bytes to read.
   * @returns {Promise<ArrayBuffer>}
   *
   * @throws {object}
   *          May be rejected with an Error object, or an object with similar
   *          properties. The object will include an `errorCode` property with
   *          one of the following values if it was rejected for the
   *          corresponding reason:
   *
   *            - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
   *              enough input could be read to satisfy the request.
   */
  read(length = null) {
    if (length !== null && !(Number.isInteger(length) && length >= 0)) {
      throw new RangeError("Length must be a non-negative integer");
    }

    if (length == 0) {
      return Promise.resolve(new ArrayBuffer(0));
    }

    return new Promise((resolve, reject) => {
      this.pendingReads.push({ length, resolve, reject });
      this.checkPendingReads();
    });
  }

  /**
   * Reads exactly `length` bytes from the input stream, and parses them as
   * UTF-8 JSON data.
   *
   * @param {integer} length
   *        The number of bytes to read.
   * @returns {Promise<object>}
   *
   * @throws {object}
   *          May be rejected with an Error object, or an object with similar
   *          properties. The object will include an `errorCode` property with
   *          one of the following values if it was rejected for the
   *          corresponding reason:
   *
   *            - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
   *              enough input could be read to satisfy the request.
   *            - Subprocess.ERROR_INVALID_JSON: The data read from the pipe
   *              could not be parsed as a valid JSON string.
   */
  readJSON(length) {
    if (!Number.isInteger(length) || length <= 0) {
      throw new RangeError("Length must be a positive integer");
    }

    return this.readString(length).then(string => {
      try {
        return JSON.parse(string);
      } catch (e) {
        e.errorCode = SubprocessConstants.ERROR_INVALID_JSON;
        throw e;
      }
    });
  }

  /**
   * Reads a chunk of UTF-8 data from the input stream, and converts it to a
   * JavaScript string.
   *
   * If `length` is provided, reads exactly `length` bytes. Otherwise, reads the
   * first chunk of data to become available, and returns an empty string on end
   * of file. In the latter case, the chunk is decoded in streaming mode, and
   * any incomplete UTF-8 sequences at the end of a chunk are returned at the
   * start of a subsequent read operation.
   *
   * @param {integer} [length]
   *        The number of bytes to read.
   * @param {object} [options]
   *        An options object as expected by TextDecoder.decode.
   * @returns {Promise<string>}
   *
   * @throws {object}
   *          May be rejected with an Error object, or an object with similar
   *          properties. The object will include an `errorCode` property with
   *          one of the following values if it was rejected for the
   *          corresponding reason:
   *
   *            - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
   *              enough input could be read to satisfy the request.
   */
  readString(length = null, options = { stream: length === null }) {
    if (length !== null && !(Number.isInteger(length) && length >= 0)) {
      throw new RangeError("Length must be a non-negative integer");
    }

    return this.read(length).then(buffer => {
      return this.decoder.decode(buffer, options);
    });
  }

  /**
   * Reads 4 bytes from the input stream, and parses them as an unsigned
   * integer, in native byte order.
   *
   * @returns {Promise<integer>}
   *
   * @throws {object}
   *          May be rejected with an Error object, or an object with similar
   *          properties. The object will include an `errorCode` property with
   *          one of the following values if it was rejected for the
   *          corresponding reason:
   *
   *            - Subprocess.ERROR_END_OF_FILE: The pipe was closed before
   *              enough input could be read to satisfy the request.
   */
  readUint32() {
    return this.read(4).then(buffer => {
      return new Uint32Array(buffer)[0];
    });
  }
}

/**
 * @class Process
 * @augments BaseProcess
 */

/**
 * Represents a currently-running process, and allows interaction with it.
 */
export class BaseProcess {
  /**
   * @param {PromiseWorker} worker
   *        The worker instance which owns the process.
   * @param {integer} processId
   *        The internal ID of the Process object, which ties it to the
   *        corresponding process on the Worker side.
   * @param {integer[]} fds
   *        An array of internal Pipe IDs, one for each standard file descriptor
   *        in the child process.
   * @param {integer} pid
   *        The operating system process ID of the process.
   */
  constructor(worker, processId, fds, pid) {
    this.id = processId;
    this.worker = worker;

    /**
     * @property {integer} pid
     *           The process ID of the process, assigned by the operating system.
     *           @readonly
     */
    this.pid = pid;

    /**
     * @property {boolean} managed
     *           Whether the process is externally managed, or spawned by us.
     *           @readonly
     */
    this.managed = pid == 0;

    this.exitCode = null;

    this.exitPromise = new Promise(resolve => {
      if (!this.managed) {
        this.worker.call("wait", [this.id]).then(({ exitCode }) => {
          resolve(Object.freeze({ exitCode }));
          this.exitCode = exitCode;
        });
      }
    });

    if (fds[0] !== undefined) {
      /**
       * @property {OutputPipe} stdin
       *           A Pipe object which allows writing to the process's standard
       *           input.
       *           @readonly
       */
      this.stdin = new OutputPipe(this, 0, fds[0]);
    }
    if (fds[1] !== undefined) {
      /**
       * @property {InputPipe} stdout
       *           A Pipe object which allows reading from the process's standard
       *           output.
       *           @readonly
       */
      this.stdout = new InputPipe(this, 1, fds[1]);
    }
    if (fds[2] !== undefined) {
      /**
       * @property {InputPipe} [stderr]
       *           An optional Pipe object which allows reading from the
       *           process's standard error output.
       *           @readonly
       */
      this.stderr = new InputPipe(this, 2, fds[2]);
    }
  }

  /**
   * Spawns a process, and resolves to a BaseProcess instance on success.
   *
   * @param {object} options
   *        An options object as passed to `Subprocess.call`.
   *
   * @returns {Promise<BaseProcess>}
   */
  static create(options) {
    let worker = this.getWorker();

    return worker.call("spawn", [options]).then(({ processId, fds, pid }) => {
      return new this(worker, processId, fds, pid);
    });
  }

  static fromRunning(fds) {
    let worker = this.getWorker();

    return worker.call("connectRunning", [fds]).then(({ processId, fds }) => {
      return new this(worker, processId, fds, 0);
    });
  }

  static get WORKER_URL() {
    throw new Error("Not implemented");
  }

  static get WorkerClass() {
    return PromiseWorker;
  }

  /**
   * Gets the current subprocess worker, or spawns a new one if it does not
   * currently exist.
   *
   * @returns {PromiseWorker}
   */
  static getWorker() {
    if (!this._worker) {
      this._worker = new this.WorkerClass(this.WORKER_URL);
    }
    return this._worker;
  }

  /**
   * Kills the process.
   *
   * @param {integer} [timeout=300]
   *        A timeout, in milliseconds, after which the process will be forcibly
   *        killed. On platforms which support it, the process will be sent
   *        a `SIGTERM` signal immediately, so that it has a chance to terminate
   *        gracefully, and a `SIGKILL` signal if it hasn't exited within
   *        `timeout` milliseconds. On other platforms (namely Windows), the
   *        process will be forcibly terminated immediately.
   *
   * @returns {Promise<object>}
   *          Resolves to an object with an `exitCode` property when the process
   *          has exited.
   */
  kill(timeout = 300) {
    if (this.managed) {
      throw new Error("Cannot kill a process managed externally");
    }

    // If the process has already exited, don't bother sending a signal.
    if (this.exitCode != null) {
      return this.wait();
    }

    let force = timeout <= 0;
    this.worker.call("kill", [this.id, force]);

    if (!force) {
      lazy.setTimeout(() => {
        if (this.exitCode == null) {
          this.worker.call("kill", [this.id, true]);
        }
      }, timeout);
    }

    return this.wait();
  }

  /**
   * Returns a promise which resolves to the process's exit code, once it has
   * exited.
   *
   * @returns {Promise<object>}
   * Resolves to an object with an `exitCode` property, containing the
   * process's exit code, once the process has exited.
   *
   * On Unix-like systems, a negative exit code indicates that the
   * process was killed by a signal whose signal number is the absolute
   * value of the error code. On Windows, an exit code of -9 indicates
   * that the process was killed via the {@linkcode BaseProcess#kill kill()}
   * method.
   */
  wait() {
    if (this.managed) {
      throw new Error("Cannot wait on a process managed externally");
    }
    return this.exitPromise;
  }
}
PK
!<��	/��'modules/subprocess/subprocess_shared.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

/* exported ArrayBuffer_transfer, Library, SubprocessConstants */

// ctypes is either already available in the chrome worker scope, or defined
// in scope via loadSubScript.
/* global ctypes */

/**
 * Returns a new ArrayBuffer whose contents have been taken from the `buffer`'s
 * data and then is either truncated or zero-extended by `size`. If `size` is
 * undefined, the `byteLength` of the `buffer` is used. This operation leaves
 * `buffer` in a detached state.
 *
 * @param {ArrayBuffer} buffer
 * @param {integer} [size = buffer.byteLength]
 * @returns {ArrayBuffer}
 */
var ArrayBuffer_transfer = function (buffer, size = buffer.byteLength) {
  let u8out = new Uint8Array(size);
  let u8buffer = new Uint8Array(buffer, 0, Math.min(size, buffer.byteLength));
  u8out.set(u8buffer);
  return u8out.buffer;
};

var libraries = {};

var Library = class Library {
  constructor(name, names, definitions) {
    if (name in libraries) {
      return libraries[name];
    }

    for (let name of names) {
      try {
        if (!this.library) {
          this.library = ctypes.open(name);
        }
      } catch (e) {
        // Ignore errors until we've tried all the options.
      }
    }
    if (!this.library) {
      throw new Error("Could not load libc");
    }

    libraries[name] = this;

    for (let symbol of Object.keys(definitions)) {
      this.declare(symbol, ...definitions[symbol]);
    }
  }

  declare(name, ...args) {
    Object.defineProperty(this, name, {
      configurable: true,
      get() {
        Object.defineProperty(this, name, {
          configurable: true,
          value: this.library.declare(name, ...args),
        });

        return this[name];
      },
    });
  }
};

/**
 * Holds constants which apply to various Subprocess operations.
 *
 * @namespace
 * @lends Subprocess
 */
var SubprocessConstants = {
  /**
   * @property {integer} ERROR_END_OF_FILE
   *           The operation failed because the end of the file was reached.
   *           @constant
   */
  ERROR_END_OF_FILE: 0xff7a0001,
  /**
   * @property {integer} ERROR_INVALID_JSON
   *           The operation failed because an invalid JSON was encountered.
   *           @constant
   */
  ERROR_INVALID_JSON: 0xff7a0002,
  /**
   * @property {integer} ERROR_BAD_EXECUTABLE
   *           The operation failed because the given file did not exist, or
   *           could not be executed.
   *           @constant
   */
  ERROR_BAD_EXECUTABLE: 0xff7a0003,
  /**
   * @property {integer} ERROR_INVALID_OPTION
   *           The operation failed because an invalid option was provided.
   *           @constant
   */
  ERROR_INVALID_OPTION: 0xff7a0004,
};

Object.freeze(SubprocessConstants);
PK
!<�J[��	�	,modules/subprocess/subprocess_shared_unix.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

/* exported LIBC, libc */

// ctypes is either already available in the chrome worker scope, or defined
// in scope via loadSubScript.
/* global ctypes */

// This file is loaded into the same scope as subprocess_shared.js.
/* import-globals-from subprocess_shared.js */

var LIBC = ChromeUtils.getLibcConstants();

const LIBC_CHOICES = ["a.out"];

const unix = {
  pid_t: ctypes.int32_t,

  pollfd: new ctypes.StructType("pollfd", [
    { fd: ctypes.int },
    { events: ctypes.short },
    { revents: ctypes.short },
  ]),

  WEXITSTATUS(status) {
    return (status >> 8) & 0xff;
  },

  WTERMSIG(status) {
    return status & 0x7f;
  },
};

var libc = new Library("libc", LIBC_CHOICES, {
  environ: [ctypes.char.ptr.ptr],

  // Darwin-only.
  _NSGetEnviron: [ctypes.default_abi, ctypes.char.ptr.ptr.ptr],

  setenv: [
    ctypes.default_abi,
    ctypes.int,
    ctypes.char.ptr,
    ctypes.char.ptr,
    ctypes.int,
  ],

  chdir: [ctypes.default_abi, ctypes.int, ctypes.char.ptr /* path */],

  close: [ctypes.default_abi, ctypes.int, ctypes.int /* fildes */],

  fcntl: [
    ctypes.default_abi,
    ctypes.int,
    ctypes.int /* fildes */,
    ctypes.int /* cmd */,
    ctypes.int /* ... */,
  ],

  getcwd: [
    ctypes.default_abi,
    ctypes.char.ptr,
    ctypes.char.ptr /* buf */,
    ctypes.size_t /* size */,
  ],

  kill: [
    ctypes.default_abi,
    ctypes.int,
    unix.pid_t /* pid */,
    ctypes.int /* signal */,
  ],

  pipe: [ctypes.default_abi, ctypes.int, ctypes.int.array(2) /* pipefd */],

  poll: [
    ctypes.default_abi,
    ctypes.int,
    unix.pollfd.array() /* fds */,
    ctypes.unsigned_int /* nfds */,
    ctypes.int /* timeout */,
  ],

  read: [
    ctypes.default_abi,
    ctypes.ssize_t,
    ctypes.int /* fildes */,
    ctypes.char.ptr /* buf */,
    ctypes.size_t /* nbyte */,
  ],

  waitpid: [
    ctypes.default_abi,
    unix.pid_t,
    unix.pid_t /* pid */,
    ctypes.int.ptr /* status */,
    ctypes.int /* options */,
  ],

  write: [
    ctypes.default_abi,
    ctypes.ssize_t,
    ctypes.int /* fildes */,
    ctypes.char.ptr /* buf */,
    ctypes.size_t /* nbyte */,
  ],
});

unix.Fd = function (fd) {
  return ctypes.CDataFinalizer(ctypes.int(fd), libc.close);
};
PK
!<2��0��*modules/subprocess/subprocess_unix.sys.mjs/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import {
  BaseProcess,
  PromiseWorker,
} from "resource://gre/modules/subprocess/subprocess_common.sys.mjs";

import { ctypes } from "resource://gre/modules/ctypes.sys.mjs";

var obj = { ctypes };
Services.scriptloader.loadSubScript(
  "resource://gre/modules/subprocess/subprocess_shared.js",
  obj
);
Services.scriptloader.loadSubScript(
  "resource://gre/modules/subprocess/subprocess_shared_unix.js",
  obj
);

const { SubprocessConstants, LIBC } = obj;

// libc is exported for tests.
export var libc = obj.libc;

class UnixPromiseWorker extends PromiseWorker {
  constructor(...args) {
    super(...args);

    let fds = ctypes.int.array(2)();
    let res = libc.pipe(fds);
    if (res == -1) {
      throw new Error("Unable to create pipe");
    }

    this.signalFd = fds[1];

    libc.fcntl(fds[0], LIBC.F_SETFL, LIBC.O_NONBLOCK);
    libc.fcntl(fds[0], LIBC.F_SETFD, LIBC.FD_CLOEXEC);
    libc.fcntl(fds[1], LIBC.F_SETFD, LIBC.FD_CLOEXEC);

    this.call("init", [{ signalFd: fds[0] }]);
  }

  closePipe() {
    if (this.signalFd) {
      libc.close(this.signalFd);
      this.signalFd = null;
    }
  }

  onClose() {
    this.closePipe();
    super.onClose();
  }

  signalWorker() {
    libc.write(this.signalFd, new ArrayBuffer(1), 1);
  }

  postMessage(...args) {
    this.signalWorker();
    return super.postMessage(...args);
  }
}

class Process extends BaseProcess {
  static get WORKER_URL() {
    return "resource://gre/modules/subprocess/subprocess_unix.worker.js";
  }

  static get WorkerClass() {
    return UnixPromiseWorker;
  }
}

// Convert a null-terminated char pointer into a sized char array, and then
// convert that into a JS typed array.
// The resulting array will not be null-terminated.
function ptrToUint8Array(input) {
  let { cast, uint8_t } = ctypes;

  let len = 0;
  for (
    let ptr = cast(input, uint8_t.ptr);
    ptr.contents;
    ptr = ptr.increment()
  ) {
    len++;
  }

  let aryPtr = cast(input, uint8_t.array(len).ptr);
  return new Uint8Array(aryPtr.contents);
}

var SubprocessUnix = {
  Process,

  call(options) {
    return Process.create(options);
  },

  *getEnvironment() {
    let environ;
    if (Services.appinfo.OS === "Darwin") {
      environ = libc._NSGetEnviron().contents;
    } else {
      environ = libc.environ;
    }

    const EQUAL = "=".charCodeAt(0);
    let decoder = new TextDecoder("utf-8", { fatal: true });

    function decode(array) {
      try {
        return decoder.decode(array);
      } catch (e) {
        return array;
      }
    }

    for (
      let envp = environ;
      !envp.isNull() && !envp.contents.isNull();
      envp = envp.increment()
    ) {
      let buf = ptrToUint8Array(envp.contents);

      for (let i = 0; i < buf.length; i++) {
        if (buf[i] == EQUAL) {
          yield [decode(buf.subarray(0, i)), decode(buf.subarray(i + 1))];
          break;
        }
      }
    }
  },

  isExecutableFile: async function isExecutable(path) {
    if (!PathUtils.isAbsolute(path)) {
      return false;
    }

    try {
      let info = await IOUtils.stat(path);

      // FIXME: We really want access(path, X_OK) here, but IOUtils does not
      // support it.
      return info.type !== "directory" && info.permissions & 0o111;
    } catch (e) {
      return false;
    }
  },

  /**
   * Searches for the given executable file in the system executable
   * file paths as specified by the PATH environment variable.
   *
   * On Windows, if the unadorned filename cannot be found, the
   * extensions in the semicolon-separated list in the PATHEXT
   * environment variable are successively appended to the original
   * name and searched for in turn.
   *
   * @param {string} bin
   *        The name of the executable to find.
   * @param {object} environment
   *        An object containing a key for each environment variable to be used
   *        in the search.
   * @returns {Promise<string>}
   */
  async pathSearch(bin, environment) {
    if (PathUtils.isAbsolute(bin)) {
      if (await this.isExecutableFile(bin)) {
        return bin;
      }
      let error = new Error(
        `File at path "${bin}" does not exist, or is not executable`
      );
      error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
      throw error;
    }

    let dirs = [];
    if (typeof environment.PATH === "string") {
      dirs = environment.PATH.split(":");
    }

    for (let dir of dirs) {
      let path = PathUtils.join(dir, bin);

      if (await this.isExecutableFile(path)) {
        return path;
      }
    }
    let error = new Error(`Executable not found: ${bin}`);
    error.errorCode = SubprocessConstants.ERROR_BAD_EXECUTABLE;
    throw error;
  },

  connectRunning(fds) {
    return Process.fromRunning(fds);
  },
};

export var SubprocessImpl = SubprocessUnix;
PK
!<�/~�<�<,modules/subprocess/subprocess_unix.worker.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

/* exported Process */

/* import-globals-from subprocess_shared.js */
/* import-globals-from subprocess_shared_unix.js */
/* import-globals-from subprocess_worker_common.js */
importScripts(
  "resource://gre/modules/subprocess/subprocess_shared.js",
  "resource://gre/modules/subprocess/subprocess_shared_unix.js",
  "resource://gre/modules/subprocess/subprocess_worker_common.js"
);

const POLL_TIMEOUT = 5000;

let io;

let nextPipeId = 0;

class Pipe extends BasePipe {
  constructor(process, fd) {
    super();

    this.process = process;
    this.fd = fd;
    this.id = nextPipeId++;
  }

  get pollEvents() {
    throw new Error("Not implemented");
  }

  /**
   * Closes the file descriptor.
   *
   * @param {boolean} [force=false]
   *        If true, the file descriptor is closed immediately. If false, the
   *        file descriptor is closed after all current pending IO operations
   *        have completed.
   *
   * @returns {Promise<void>}
   *          Resolves when the file descriptor has been closed.
   */
  close(force = false) {
    if (!force && this.pending.length) {
      this.closing = true;
      return this.closedPromise;
    }

    for (let { reject } of this.pending) {
      let error = new Error("File closed");
      error.errorCode = SubprocessConstants.ERROR_END_OF_FILE;
      reject(error);
    }
    this.pending.length = 0;

    if (!this.closed) {
      this.fd.dispose();

      this.closed = true;
      this.resolveClosed();

      io.pipes.delete(this.id);
      io.updatePollFds();
    }
    return this.closedPromise;
  }

  /**
   * Called when an error occurred while polling our file descriptor.
   */
  onError() {
    this.close(true);
    this.process.wait();
  }
}

class InputPipe extends Pipe {
  /**
   * A bit mask of poll() events which we currently wish to be notified of on
   * this file descriptor.
   */
  get pollEvents() {
    if (this.pending.length) {
      return LIBC.POLLIN;
    }
    return 0;
  }

  /**
   * Asynchronously reads at most `length` bytes of binary data from the file
   * descriptor into an ArrayBuffer of the same size. Returns a promise which
   * resolves when the operation is complete.
   *
   * @param {integer} length
   *        The number of bytes to read.
   *
   * @returns {Promise<ArrayBuffer>}
   */
  read(length) {
    if (this.closing || this.closed) {
      throw new Error("Attempt to read from closed pipe");
    }

    return new Promise((resolve, reject) => {
      this.pending.push({ resolve, reject, length });
      io.updatePollFds();
    });
  }

  /**
   * Synchronously reads at most `count` bytes of binary data into an
   * ArrayBuffer, and returns that buffer. If no data can be read without
   * blocking, returns null instead.
   *
   * @param {integer} count
   *        The number of bytes to read.
   *
   * @returns {ArrayBuffer|null}
   */
  readBuffer(count) {
    let buffer = new ArrayBuffer(count);

    let read = +libc.read(this.fd, buffer, buffer.byteLength);
    if (read < 0 && ctypes.errno != LIBC.EAGAIN) {
      this.onError();
    }

    if (read <= 0) {
      return null;
    }

    if (read < buffer.byteLength) {
      return ArrayBuffer_transfer(buffer, read);
    }

    return buffer;
  }

  /**
   * Called when one of the IO operations matching the `pollEvents` mask may be
   * performed without blocking.
   *
   * @returns {boolean}
   *        True if any data was successfully read.
   */
  onReady() {
    let result = false;
    let reads = this.pending;
    while (reads.length) {
      let { resolve, length } = reads[0];

      let buffer = this.readBuffer(length);
      if (buffer) {
        result = true;
        this.shiftPending();
        resolve(buffer);
      } else {
        break;
      }
    }

    if (!reads.length) {
      io.updatePollFds();
    }
    return result;
  }
}

class OutputPipe extends Pipe {
  /**
   * A bit mask of poll() events which we currently wish to be notified of on
   * this file discriptor.
   */
  get pollEvents() {
    if (this.pending.length) {
      return LIBC.POLLOUT;
    }
    return 0;
  }

  /**
   * Asynchronously writes the given buffer to our file descriptor, and returns
   * a promise which resolves when the operation is complete.
   *
   * @param {ArrayBuffer} buffer
   *        The buffer to write.
   *
   * @returns {Promise<integer>}
   *          Resolves to the number of bytes written when the operation is
   *          complete.
   */
  write(buffer) {
    if (this.closing || this.closed) {
      throw new Error("Attempt to write to closed pipe");
    }

    return new Promise((resolve, reject) => {
      this.pending.push({ resolve, reject, buffer, length: buffer.byteLength });
      io.updatePollFds();
    });
  }

  /**
   * Attempts to synchronously write the given buffer to our file descriptor.
   * Writes only as many bytes as can be written without blocking, and returns
   * the number of byes successfully written.
   *
   * Closes the file descriptor if an IO error occurs.
   *
   * @param {ArrayBuffer} buffer
   *        The buffer to write.
   *
   * @returns {integer}
   *          The number of bytes successfully written.
   */
  writeBuffer(buffer) {
    let bytesWritten = libc.write(this.fd, buffer, buffer.byteLength);

    if (bytesWritten < 0 && ctypes.errno != LIBC.EAGAIN) {
      this.onError();
    }

    return bytesWritten;
  }

  /**
   * Called when one of the IO operations matching the `pollEvents` mask may be
   * performed without blocking.
   */
  onReady() {
    let writes = this.pending;
    while (writes.length) {
      let { buffer, resolve, length } = writes[0];

      let written = this.writeBuffer(buffer);

      if (written == buffer.byteLength) {
        resolve(length);
        this.shiftPending();
      } else if (written > 0) {
        writes[0].buffer = buffer.slice(written);
      } else {
        break;
      }
    }

    if (!writes.length) {
      io.updatePollFds();
    }
  }
}

class Signal {
  constructor(fd) {
    this.fd = fd;
  }

  cleanup() {
    libc.close(this.fd);
    this.fd = null;
  }

  get pollEvents() {
    return LIBC.POLLIN;
  }

  /**
   * Called when an error occurred while polling our file descriptor.
   */
  onError() {
    io.shutdown();
  }

  /**
   * Called when one of the IO operations matching the `pollEvents` mask may be
   * performed without blocking.
   */
  onReady() {
    let buffer = new ArrayBuffer(16);
    let count = +libc.read(this.fd, buffer, buffer.byteLength);
    if (count > 0) {
      io.messageCount += count;
    }
  }
}

class Process extends BaseProcess {
  /**
   * Each Process object opens an additional pipe from the target object, which
   * will be automatically closed when the process exits, but otherwise
   * carries no data.
   *
   * This property contains a bit mask of poll() events which we wish to be
   * notified of on this descriptor. We're not expecting any input from this
   * pipe, but we need to poll for input until the process exits in order to be
   * notified when the pipe closes.
   */
  get pollEvents() {
    if (this.exitCode === null) {
      return LIBC.POLLIN;
    }
    return 0;
  }

  /**
   * Kills the process with the given signal.
   *
   * @param {integer} signal
   */
  kill(signal) {
    libc.kill(this.pid, signal);
    this.wait();
  }

  /**
   * Initializes the IO pipes for use as standard input, output, and error
   * descriptors in the spawned process.
   *
   * @param {object} options
   *        The Subprocess options object for this process.
   * @returns {unix.Fd[]}
   *          The array of file descriptors belonging to the spawned process.
   */
  initPipes(options) {
    let stderr = options.stderr;

    let our_pipes = [];
    let their_pipes = new Map();

    let pipe = input => {
      let fds = ctypes.int.array(2)();

      let res = libc.pipe(fds);
      if (res == -1) {
        throw new Error("Unable to create pipe");
      }

      fds = Array.from(fds, unix.Fd);

      if (input) {
        fds.reverse();
      }

      if (input) {
        our_pipes.push(new InputPipe(this, fds[1]));
      } else {
        our_pipes.push(new OutputPipe(this, fds[1]));
      }

      libc.fcntl(fds[0], LIBC.F_SETFD, LIBC.FD_CLOEXEC);
      libc.fcntl(fds[1], LIBC.F_SETFD, LIBC.FD_CLOEXEC);
      libc.fcntl(fds[1], LIBC.F_SETFL, LIBC.O_NONBLOCK);

      return fds[0];
    };

    their_pipes.set(0, pipe(false));
    their_pipes.set(1, pipe(true));

    if (stderr == "pipe") {
      their_pipes.set(2, pipe(true));
    } else if (stderr == "stdout") {
      their_pipes.set(2, their_pipes.get(1));
    }

    // Create an additional pipe that we can use to monitor for process exit.
    their_pipes.set(3, pipe(true));
    this.fd = our_pipes.pop().fd;

    this.pipes = our_pipes;

    return their_pipes;
  }

  spawn(options) {
    let fds = this.initPipes(options);

    let launchOptions = {
      environment: options.environment,
      disclaim: options.disclaim,
      fdMap: [],
    };

    // Check for truthiness to avoid chdir("null")
    if (options.workdir) {
      launchOptions.workdir = options.workdir;
    }

    for (let [dst, src] of fds.entries()) {
      launchOptions.fdMap.push({ src, dst });
    }

    try {
      this.pid = IOUtils.launchProcess(options.arguments, launchOptions);
    } finally {
      for (let fd of new Set(fds.values())) {
        fd.dispose();
      }
    }
  }

  /**
   * Connect to an already running process that was spawned externally.
   *
   * @param {object} options
            An object with a 'fds' attribute that's an array
            of file descriptors (stdin, stdout and stderr).
   */
  connectRunning(options) {
    this.pid = 0;
    this.pipes = [];
    this.pipes.push(new OutputPipe(this, unix.Fd(options.fds[0])));
    this.pipes.push(new InputPipe(this, unix.Fd(options.fds[1])));
    this.pipes.push(new InputPipe(this, unix.Fd(options.fds[2])));
    // Not creating a poll fd here, because this process is managed externally.
  }

  /**
   * Called when input is available on our sentinel file descriptor.
   *
   * @see pollEvents
   */
  onReady() {
    // We're not actually expecting any input on this pipe. If we get any, we
    // can't poll the pipe any further without reading it.
    if (this.wait() == undefined) {
      this.kill(9);
    }
  }

  /**
   * Called when an error occurred while polling our sentinel file descriptor.
   *
   * @see pollEvents
   */
  onError() {
    this.wait();
  }

  /**
   * Attempts to wait for the process's exit status, without blocking. If
   * successful, resolves the `exitPromise` to the process's exit value.
   *
   * @returns {integer|null}
   *          The process's exit status, if it has already exited.
   */
  wait() {
    if (this.exitCode !== null) {
      return this.exitCode;
    }

    let status = ctypes.int();

    let res = libc.waitpid(this.pid, status.address(), LIBC.WNOHANG);
    // If there's a failure here and we get any errno other than EINTR, it
    // means that the process has been reaped by another thread (most likely
    // the nspr process wait thread), and its actual exit status is not
    // available to us. In that case, we have to assume success.
    if (res == 0 || (res == -1 && ctypes.errno == LIBC.EINTR)) {
      return null;
    }

    let sig = unix.WTERMSIG(status.value);
    if (sig) {
      this.exitCode = -sig;
    } else {
      this.exitCode = unix.WEXITSTATUS(status.value);
    }

    if (this.fd !== undefined) {
      this.fd.dispose();
    }
    io.updatePollFds();
    this.resolveExit(this.exitCode);
    return this.exitCode;
  }
}

io = {
  pollFds: null,
  pollHandlers: null,

  pipes: new Map(),

  processes: new Map(),

  messageCount: 0,

  running: true,

  polling: false,

  init(details) {
    this.signal = new Signal(details.signalFd);
    this.updatePollFds();

    setTimeout(this.loop.bind(this), 0);
  },

  shutdown() {
    if (this.running) {
      this.running = false;

      this.signal.cleanup();
      this.signal = null;

      self.postMessage({ msg: "close" });
      self.close();
    }
  },

  getPipe(pipeId) {
    let pipe = this.pipes.get(pipeId);

    if (!pipe) {
      let error = new Error("File closed");
      error.errorCode = SubprocessConstants.ERROR_END_OF_FILE;
      throw error;
    }
    return pipe;
  },

  getProcess(processId) {
    let process = this.processes.get(processId);

    if (!process) {
      throw new Error(`Invalid process ID: ${processId}`);
    }
    return process;
  },

  updatePollFds() {
    let handlers = [
      this.signal,
      ...this.pipes.values(),
      // Filter out processes without a poll fd, because those are managed
      // externally, not spawned by us.
      ...Array.from(this.processes.values()).filter(p => p.fd !== undefined),
    ];

    handlers = handlers.filter(handler => handler.pollEvents);

    // Our poll loop is only useful if we've got at least 1 thing to poll other than our own
    // signal.
    if (handlers.length == 1) {
      this.polling = false;
    } else if (!this.polling && this.running) {
      // Restart the poll loop if necessary:
      setTimeout(this.loop.bind(this), 0);
      this.polling = true;
    }

    let pollfds = unix.pollfd.array(handlers.length)();

    for (let [i, handler] of handlers.entries()) {
      let pollfd = pollfds[i];

      pollfd.fd = handler.fd;
      pollfd.events = handler.pollEvents;
      pollfd.revents = 0;
    }

    this.pollFds = pollfds;
    this.pollHandlers = handlers;
  },

  loop() {
    this.poll();
    if (this.running && this.polling) {
      setTimeout(this.loop.bind(this), 0);
    }
  },

  poll() {
    let handlers = this.pollHandlers;
    let pollfds = this.pollFds;

    let timeout = this.messageCount > 0 ? 0 : POLL_TIMEOUT;
    let count = libc.poll(pollfds, pollfds.length, timeout);

    for (let i = 0; count && i < pollfds.length; i++) {
      let pollfd = pollfds[i];
      if (pollfd.revents) {
        count--;

        let handler = handlers[i];
        try {
          let success = false;
          if (pollfd.revents & handler.pollEvents) {
            success = handler.onReady();
          }
          // Only call the error handler in this iteration if we didn't also
          // have a success. This is necessary because Linux systems set POLLHUP
          // on a pipe when it's closed but there's still buffered data to be
          // read, and Darwin sets POLLIN and POLLHUP on a closed pipe, even
          // when there's no data to be read.
          if (
            !success &&
            pollfd.revents & (LIBC.POLLERR | LIBC.POLLHUP | LIBC.POLLNVAL)
          ) {
            handler.onError();
          }
        } catch (e) {
          console.error(e);
          debug(`Worker error: ${e} :: ${e.stack}`);
          handler.onError();
        }

        pollfd.revents = 0;
      }
    }
  },

  addProcess(process) {
    this.processes.set(process.id, process);

    for (let pipe of process.pipes) {
      this.pipes.set(pipe.id, pipe);
    }
  },

  cleanupProcess(process) {
    this.processes.delete(process.id);
  },
};
PK
!<��||.modules/subprocess/subprocess_worker_common.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

// This file is loaded into the same context as subprocess_unix.worker.js
// and subprocess_win.worker.js
/* import-globals-from subprocess_unix.worker.js */

/* exported BasePipe, BaseProcess, debug */

function debug(message) {
  self.postMessage({ msg: "debug", message });
}

class BasePipe {
  constructor() {
    this.closing = false;
    this.closed = false;

    this.closedPromise = new Promise(resolve => {
      this.resolveClosed = resolve;
    });

    this.pending = [];
  }

  shiftPending() {
    let result = this.pending.shift();

    if (this.closing && !this.pending.length) {
      this.close();
    }

    return result;
  }
}

let nextProcessId = 0;

class BaseProcess {
  constructor(options) {
    this.id = nextProcessId++;

    this.exitCode = null;

    this.exitPromise = new Promise(resolve => {
      this.resolveExit = resolve;
    });
    this.exitPromise.then(() => {
      // The input file descriptors will be closed after poll
      // reports that their input buffers are empty. If we close
      // them now, we may lose output.
      this.pipes[0].close(true);
    });

    this.pid = null;
    this.pipes = [];

    if (options.managed) {
      this.connectRunning(options);
    } else {
      this.spawn(options);
    }
  }

  /**
   * Waits for the process to exit and all of its pending IO operations to
   * complete.
   *
   * @returns {Promise<void>}
   */
  awaitFinished() {
    return Promise.all([
      this.exitPromise,
      ...this.pipes.map(pipe => pipe.closedPromise),
    ]);
  }
}

let requests = {
  init(details) {
    io.init(details);

    return { data: {} };
  },

  shutdown() {
    io.shutdown();

    return { data: {} };
  },

  close(pipeId, force = false) {
    let pipe = io.getPipe(pipeId);

    return pipe.close(force).then(() => ({ data: {} }));
  },

  spawn(options) {
    options.managed = false;
    let process = new Process(options);
    let processId = process.id;

    io.addProcess(process);

    let fds = process.pipes.map(pipe => pipe.id);

    return { data: { processId, fds, pid: process.pid } };
  },

  connectRunning(fds) {
    let options = {};
    options.managed = true;
    options.fds = fds;
    let process = new Process(options);
    let processId = process.id;

    io.addProcess(process);

    return { data: { processId, fds: process.pipes.map(pipe => pipe.id) } };
  },

  kill(processId, force = false) {
    let process = io.getProcess(processId);

    process.kill(force ? 9 : 15);

    return { data: {} };
  },

  wait(processId) {
    let process = io.getProcess(processId);

    process.wait();

    process.awaitFinished().then(() => {
      io.cleanupProcess(process);
    });

    return process.exitPromise.then(exitCode => {
      return { data: { exitCode } };
    });
  },

  read(pipeId, count) {
    let pipe = io.getPipe(pipeId);

    return pipe.read(count).then(buffer => {
      return { data: { buffer } };
    });
  },

  write(pipeId, buffer) {
    let pipe = io.getPipe(pipeId);

    return pipe.write(buffer).then(bytesWritten => {
      return { data: { bytesWritten } };
    });
  },

  getOpenFiles() {
    return { data: new Set(io.pipes.keys()) };
  },

  getProcesses() {
    let data = new Map(
      Array.from(io.processes.values())
        .filter(proc => proc.exitCode == null)
        .map(proc => [proc.id, proc.pid])
    );
    return { data };
  },

  waitForNoProcesses() {
    return Promise.all(
      Array.from(io.processes.values(), proc => proc.awaitFinished())
    );
  },

  getFds(processId) {
    let process = io.getProcess(processId);
    let pipes = process.pipes;
    return {
      data: [
        pipes[0].fd.toString(),
        pipes[1].fd.toString(),
        pipes[2].fd.toString(),
      ],
    };
  },
};

onmessage = event => {
  io.messageCount--;

  let { msg, msgId, args } = event.data;

  new Promise(resolve => {
    resolve(requests[msg](...args));
  })
    .then(result => {
      let response = {
        msg: "success",
        msgId,
        data: result.data,
      };

      self.postMessage(response, result.transfer || []);
    })
    .catch(error => {
      if (error instanceof Error) {
        error = {
          message: error.message,
          fileName: error.fileName,
          lineNumber: error.lineNumber,
          column: error.column,
          stack: error.stack,
          errorCode: error.errorCode,
        };
      }

      self.postMessage({
        msg: "failure",
        msgId,
        error,
      });
    })
    .catch(error => {
      console.error(error);

      self.postMessage({
        msg: "failure",
        msgId,
        error: {},
      });
    });
};
PK
!<�0��&�&�%modules/third_party/fathom/fathom.mjs/*
DO NOT TOUCH fathom.mjs DIRECTLY. See the README for instructions.
*/

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * A :func:`rule` depends on another rule which itself depends on the first
 * rule again, either directly or indirectly.
 */
class CycleError extends Error {
}

/**
  * An examined element was not contained in a browser ``window`` object, but
  * something needed it to be.
  */
class NoWindowError extends Error {
}

var exceptions = /*#__PURE__*/Object.freeze({
  __proto__: null,
  CycleError: CycleError,
  NoWindowError: NoWindowError
});

/**
 * Return the passed-in arg. Useful as a default.
 */
function identity(x) {
    return x;
}

/*eslint-env browser*/

/**
 * From an iterable return the best item, according to an arbitrary comparator
 * function. In case of a tie, the first item wins.
 *
 * @arg by {function} Given an item of the iterable, return a value to compare
 * @arg isBetter {function} Return whether its first arg is better than its
 *     second
 */
function best(iterable, by, isBetter) {
    let bestSoFar, bestKeySoFar;
    let isFirst = true;
    forEach(
        function (item) {
            const key = by(item);
            if (isBetter(key, bestKeySoFar) || isFirst) {
                bestSoFar = item;
                bestKeySoFar = key;
                isFirst = false;
            }
        },
        iterable);
    if (isFirst) {
        throw new Error('Tried to call best() on empty iterable');
    }
    return bestSoFar;
}

/**
 * Return the maximum item from an iterable, as defined by >.
 *
 * Works with any type that works with >. If multiple items are equally great,
 * return the first.
 *
 * @arg by {function} Given an item of the iterable, returns a value to
 *     compare
 */
function max(iterable, by = identity) {
    return best(iterable, by, (a, b) => a > b);
}

/**
 * Return an Array of maximum items from an iterable, as defined by > and ===.
 *
 * If an empty iterable is passed in, return [].
 */
function maxes(iterable, by = identity) {
    let bests = [];
    let bestKeySoFar;
    let isFirst = true;
    forEach(
        function (item) {
            const key = by(item);
            if (key > bestKeySoFar || isFirst) {
                bests = [item];
                bestKeySoFar = key;
                isFirst = false;
            } else if (key === bestKeySoFar) {
                bests.push(item);
            }
        },
        iterable);
    return bests;
}

/**
 * Return the minimum item from an iterable, as defined by <.
 *
 * If multiple items are equally great, return the first.
 */
function min(iterable, by = identity) {
    return best(iterable, by, (a, b) => a < b);
}

/**
 * Return the sum of an iterable, as defined by the + operator.
 */
function sum(iterable) {
    let total;
    let isFirst = true;
    forEach(
        function assignOrAdd(addend) {
            if (isFirst) {
                total = addend;
                isFirst = false;
            } else {
                total += addend;
            }
        },
        iterable);
    return total;
}

/**
 * Return the number of items in an iterable, consuming it as a side effect.
 */
function length(iterable) {
    let num = 0;
    // eslint-disable-next-line no-unused-vars
    for (let item of iterable) {
        num++;
    }
    return num;
}

/**
 * Iterate, depth first, over a DOM node. Return the original node first.
 *
 * @arg shouldTraverse {function} Given a node, say whether we should
 *     include it and its children. Default: always true.
 */
function *walk(element, shouldTraverse = element => true) {
    yield element;
    for (let child of element.childNodes) {
        if (shouldTraverse(child)) {
            for (let w of walk(child, shouldTraverse)) {
                yield w;
            }
        }
    }
}

const blockTags = new Set(
    ['ADDRESS', 'BLOCKQUOTE', 'BODY', 'CENTER', 'DIR', 'DIV', 'DL',
     'FIELDSET', 'FORM', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HR',
     'ISINDEX', 'MENU', 'NOFRAMES', 'NOSCRIPT', 'OL', 'P', 'PRE',
     'TABLE', 'UL', 'DD', 'DT', 'FRAMESET', 'LI', 'TBODY', 'TD',
     'TFOOT', 'TH', 'THEAD', 'TR', 'HTML']);
/**
 * Return whether a DOM element is a block element by default (rather than by
 * styling).
 */
function isBlock(element) {
    return blockTags.has(element.tagName);
}

/**
 * Yield strings of text nodes within a normalized DOM node and its children,
 * without venturing into any contained block elements.
 *
 * @arg shouldTraverse {function} Specify additional elements to exclude by
 *     returning false
 */
function *inlineTexts(element, shouldTraverse = element => true) {
    // TODO: Could we just use querySelectorAll() with a really long
    // selector rather than walk(), for speed?
    for (let child of walk(element,
                           element => !(isBlock(element) ||
                                        element.tagName === 'SCRIPT' &&
                                        element.tagName === 'STYLE')
                                      && shouldTraverse(element))) {
        if (child.nodeType === child.TEXT_NODE) {
            // wholeText() is not implemented by jsdom, so we use
            // textContent(). The result should be the same, since
            // we're calling it on only text nodes, but it may be
            // slower. On the positive side, it means we don't need to
            // normalize the DOM tree first.
            yield child.textContent;
        }
    }
}

/**
 * Return the total length of the inline text within an element, with
 * whitespace collapsed.
 *
 * @arg shouldTraverse {function} Specify additional elements to exclude by
 *     returning false
 */
function inlineTextLength(element, shouldTraverse = element => true) {
    return sum(map(text => collapseWhitespace(text).length,
                   inlineTexts(element, shouldTraverse)));
}

/**
 * Return a string with each run of whitespace collapsed to a single space.
 */
function collapseWhitespace(str) {
    return str.replace(/\s{2,}/g, ' ');
}

/**
 * Return the ratio of the inline text length of the links in an element to the
 * inline text length of the entire element.
 *
 * @arg inlineLength {number} Optionally, the precalculated inline
 *     length of the fnode. If omitted, we will calculate it ourselves.
 */
function linkDensity(fnode, inlineLength) {
    if (inlineLength === undefined) {
        inlineLength = inlineTextLength(fnode.element);
    }
    const lengthWithoutLinks = inlineTextLength(fnode.element,
                                                element => element.tagName !== 'A');
    return (inlineLength - lengthWithoutLinks) / inlineLength;
}

/**
 * Return whether an element is a text node that consist wholly of whitespace.
 */
function isWhitespace(element) {
    return (element.nodeType === element.TEXT_NODE &&
            element.textContent.trim().length === 0);
}

/**
 * Get a key of a map, first setting it to a default value if it's missing.
 */
function setDefault(map, key, defaultMaker) {
    if (map.has(key)) {
        return map.get(key);
    }
    const defaultValue = defaultMaker();
    map.set(key, defaultValue);
    return defaultValue;
}

/**
 * Get a key of a map or, if it's missing, a default value.
 */
function getDefault(map, key, defaultMaker) {
    if (map.has(key)) {
        return map.get(key);
    }
    return defaultMaker();
}

/**
 * Return an Array, the reverse topological sort of the given nodes.
 *
 * @arg nodes An iterable of arbitrary things
 * @arg nodesThatNeed {function} Take a node and returns an Array of nodes
 *     that depend on it
 */
function toposort(nodes, nodesThatNeed) {
    const ret = [];
    const todo = new Set(nodes);
    const inProgress = new Set();

    function visit(node) {
        if (inProgress.has(node)) {
            throw new CycleError('The graph has a cycle.');
        }
        if (todo.has(node)) {
            inProgress.add(node);
            for (let needer of nodesThatNeed(node)) {
                visit(needer);
            }
            inProgress.delete(node);
            todo.delete(node);
            ret.push(node);
        }
    }

    while (todo.size > 0) {
        visit(first(todo));
    }
    return ret;
}

/**
 * A Set with the additional methods it ought to have had
 */
class NiceSet extends Set {
    /**
     * Remove and return an arbitrary item. Throw an Error if I am empty.
     */
    pop() {
        for (let v of this.values()) {
            this.delete(v);
            return v;
        }
        throw new Error('Tried to pop from an empty NiceSet.');
    }

    /**
     * Union another set or other iterable into myself.
     *
     * @return myself, for chaining
     */
    extend(otherSet) {
        for (let item of otherSet) {
            this.add(item);
        }
        return this;
    }

    /**
     * Subtract another set from a copy of me.
     *
     * @return a copy of myself excluding the elements in ``otherSet``.
     */
    minus(otherSet) {
        const ret = new NiceSet(this);
        for (const item of otherSet) {
            ret.delete(item);
        }
        return ret;
    }

    /**
     * Actually show the items in me.
     */
    toString() {
        return '{' + Array.from(this).join(', ') + '}';
    }
}

/**
 * Return the first item of an iterable.
 */
function first(iterable) {
    for (let i of iterable) {
        return i;
    }
}

/**
 * Given any node in a DOM tree, return the root element of the tree, generally
 * an HTML element.
 */
function rootElement(element) {
    return element.ownerDocument.documentElement;
}

/**
 * Return the number of times a regex occurs within the string `haystack`.
 *
 * Caller must make sure `regex` has the 'g' option set.
 */
function numberOfMatches(regex, haystack) {
    return (haystack.match(regex) || []).length;
}

/**
 * Wrap a scoring callback, and set its element to the page root iff a score is
 * returned.
 *
 * This is used to build rulesets which classify entire pages rather than
 * picking out specific elements.
 *
 * For example, these rules might classify a page as a "login page", influenced
 * by whether they have login buttons or username fields:
 *
 * ``rule(type('loginPage'), score(page(pageContainsLoginButton))),``
 * ``rule(type('loginPage'), score(page(pageContainsUsernameField)))``
 */
function page(scoringFunction) {
    function wrapper(fnode) {
        const scoreAndTypeAndNote = scoringFunction(fnode);
        if (scoreAndTypeAndNote.score !== undefined) {
            scoreAndTypeAndNote.element = rootElement(fnode.element);
        }
        return scoreAndTypeAndNote;
    }
    return wrapper;
}

/**
 * Sort the elements by their position in the DOM.
 *
 * @arg fnodes {iterable} fnodes to sort
 * @return {Array} sorted fnodes
 */
function domSort(fnodes) {
    function compare(a, b) {
        const element = a.element;
        const position = element.compareDocumentPosition(b.element);
        if (position & element.DOCUMENT_POSITION_FOLLOWING) {
            return -1;
        } else if (position & element.DOCUMENT_POSITION_PRECEDING) {
            return 1;
        } else {
            return 0;
        }
    }
    return Array.from(fnodes).sort(compare);
}

/* istanbul ignore next */
/**
 * Return the DOM element contained in a passed-in fnode. Return passed-in DOM
 * elements verbatim.
 *
 * @arg fnodeOrElement {Node|Fnode}
 */
function toDomElement(fnodeOrElement) {
    return isDomElement(fnodeOrElement) ? fnodeOrElement : fnodeOrElement.element;
}

/**
 * Checks whether any of the element's attribute values satisfy some condition.
 *
 * Example::
 *
 *     rule(type('foo'),
 *          score(attributesMatch(element,
 *                                attr => attr.includes('good'),
 *                                ['id', 'alt']) ? 2 : 1))
 *
 * @arg element {Node} Element whose attributes you want to search
 * @arg predicate {function} A condition to check. Take a string and
 *     return a boolean. If an attribute has multiple values (e.g. the class
 *     attribute), attributesMatch will check each one.
 * @arg attrs {string[]} An Array of attributes you want to search. If none are
 *     provided, search all.
 * @return Whether any of the attribute values satisfy the predicate function
 */
function attributesMatch(element, predicate, attrs = []) {
    const attributes = attrs.length === 0 ? Array.from(element.attributes).map(a => a.name) : attrs;
    for (let i = 0; i < attributes.length; i++) {
        const attr = element.getAttribute(attributes[i]);
        // If the attribute is an array, apply the scoring function to each element
        if (attr && ((Array.isArray(attr) && attr.some(predicate)) || predicate(attr))) {
            return true;
        }
    }
    return false;
}

/* istanbul ignore next */
/**
 * Yield an element and each of its ancestors.
 */
function *ancestors(element) {
    yield element;
    let parent;
    while ((parent = element.parentNode) !== null && parent.nodeType === parent.ELEMENT_NODE) {
        yield parent;
        element = parent;
    }
}

/**
 * Return the sigmoid of the argument: 1 / (1 + exp(-x)). This is useful for
 * crunching a feature value that may have a wide range into the range (0, 1)
 * without a hard ceiling: the sigmoid of even a very large number will be a
 * little larger than that of a slightly smaller one.
 *
 * @arg x {Number} a number to be compressed into the range (0, 1)
 */
function sigmoid(x) {
    return 1 / (1 + Math.exp(-x));
}

/* istanbul ignore next */
/**
 * Return whether an element is practically visible, considering things like 0
 * size or opacity, ``visibility: hidden`` and ``overflow: hidden``.
 *
 * Merely being scrolled off the page in either horizontally or vertically
 * doesn't count as invisible; the result of this function is meant to be
 * independent of viewport size.
 *
 * @throws {NoWindowError} The element (or perhaps one of its ancestors) is not
 *     in a window, so we can't find the `getComputedStyle()` routine to call.
 *     That routine is the source of most of the information we use, so you
 *     should pick a different strategy for non-window contexts.
 */
function isVisible(fnodeOrElement) {
    // This could be 5x more efficient if https://github.com/w3c/csswg-drafts/issues/4122 happens.
    const element = toDomElement(fnodeOrElement);
    const elementWindow = windowForElement(element);
    const elementRect = element.getBoundingClientRect();
    const elementStyle = elementWindow.getComputedStyle(element);
    // Alternative to reading ``display: none`` due to Bug 1381071.
    if (elementRect.width === 0 && elementRect.height === 0 && elementStyle.overflow !== 'hidden') {
        return false;
    }
    if (elementStyle.visibility === 'hidden') {
        return false;
    }
    // Check if the element is irrevocably off-screen:
    if (elementRect.x + elementRect.width < 0 ||
        elementRect.y + elementRect.height < 0
    ) {
        return false;
    }
    for (const ancestor of ancestors(element)) {
        const isElement = ancestor === element;
        const style = isElement ? elementStyle : elementWindow.getComputedStyle(ancestor);
        if (style.opacity === '0') {
            return false;
        }
        if (style.display === 'contents') {
            // ``display: contents`` elements have no box themselves, but children are
            // still rendered.
            continue;
        }
        const rect = isElement ? elementRect : ancestor.getBoundingClientRect();
        if ((rect.width === 0 || rect.height === 0) && elementStyle.overflow === 'hidden') {
            // Zero-sized ancestors don’t make descendants hidden unless the descendant
            // has ``overflow: hidden``.
            return false;
        }
    }
    return true;
}

/**
 * Return the extracted [r, g, b, a] values from a string like "rgba(0, 5, 255, 0.8)",
 * and scale them to 0..1. If no alpha is specified, return undefined for it.
 */
function rgbaFromString(str) {
    const m = str.match(/^rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d+(?:\.\d+)?)\s*)?\)$/i);
    if (m) {
        return [m[1] / 255, m[2] / 255, m[3] / 255, m[4] === undefined ? undefined : parseFloat(m[4])];
    } else {
        throw new Error('Color ' + str + ' did not match pattern rgb[a](r, g, b[, a]).');
    }
}

/**
 * Return the saturation 0..1 of a color defined by RGB values 0..1.
 */
function saturation(r, g, b) {
    const cMax = Math.max(r, g, b);
    const cMin = Math.min(r, g, b);
    const delta = cMax - cMin;
    const lightness = (cMax + cMin) / 2;
    const denom = (1 - (Math.abs(2 * lightness - 1)));
    // Return 0 if it's black (R, G, and B all 0).
    return (denom === 0) ? 0 : delta / denom;
}

/**
 * Scale a number to the range [0, 1] using a linear slope.
 *
 * For a rising line, the result is 0 until the input reaches zeroAt, then
 * increases linearly until oneAt, at which it becomes 1. To make a falling
 * line, where the result is 1 to the left and 0 to the right, use a zeroAt
 * greater than oneAt.
 */
function linearScale(number, zeroAt, oneAt) {
    const isRising = zeroAt < oneAt;
    if (isRising) {
        if (number <= zeroAt) {
            return 0;
        } else if (number >= oneAt) {
            return 1;
        }
    } else {
        if (number >= zeroAt) {
            return 0;
        } else if (number <= oneAt) {
            return 1;
        }
    }
    const slope = 1 / (oneAt - zeroAt);
    return slope * (number - zeroAt);
}

// -------- Routines below this point are private to the framework. --------

/**
 * Flatten out an iterable of iterables into a single iterable of non-
 * iterables. Does not consider strings to be iterable.
 */
function *flatten(iterable) {
    for (const i of iterable) {
        if (typeof i !== 'string' && isIterable(i)) {
            yield *(flatten(i));
        } else {
            yield i;
        }
    }
}

/**
 * A lazy, top-level ``Array.map()`` workalike that works on anything iterable
 */
function *map(fn, iterable) {
    for (const i of iterable) {
        yield fn(i);
    }
}

/**
 * A lazy, top-level ``Array.forEach()`` workalike that works on anything
 * iterable
 */
function forEach(fn, iterable) {
    for (const i of iterable) {
        fn(i);
    }
}

/* istanbul ignore next */
/**
 * @return whether a thing appears to be a DOM element.
 */
function isDomElement(thing) {
    return thing.nodeName !== undefined;
}

function isIterable(thing) {
    return thing && typeof thing[Symbol.iterator] === 'function';
}

/**
 * Return an backward iterator over an Array without reversing it in place.
 */
function *reversed(array) {
    for (let i = array.length - 1; i >= 0; i--) {
        yield array[i];
    }
}

/* istanbul ignore next */
/*
 * Return the window an element is in.
 *
 * @throws {NoWindowError} There isn't such a window.
 */
function windowForElement(element) {
    let doc = element.ownerDocument;
    if (doc === null) {
        // The element itself was a document.
        doc = element;
    }
    const win = doc.defaultView;
    if (win === null) {
        throw new NoWindowError();
    }
    return win;
}

var utilsForFrontend = /*#__PURE__*/Object.freeze({
  __proto__: null,
  identity: identity,
  best: best,
  max: max,
  maxes: maxes,
  min: min,
  sum: sum,
  length: length,
  walk: walk,
  isBlock: isBlock,
  inlineTexts: inlineTexts,
  inlineTextLength: inlineTextLength,
  collapseWhitespace: collapseWhitespace,
  linkDensity: linkDensity,
  isWhitespace: isWhitespace,
  setDefault: setDefault,
  getDefault: getDefault,
  toposort: toposort,
  NiceSet: NiceSet,
  first: first,
  rootElement: rootElement,
  numberOfMatches: numberOfMatches,
  page: page,
  domSort: domSort,
  toDomElement: toDomElement,
  attributesMatch: attributesMatch,
  ancestors: ancestors,
  sigmoid: sigmoid,
  isVisible: isVisible,
  rgbaFromString: rgbaFromString,
  saturation: saturation,
  linearScale: linearScale,
  flatten: flatten,
  map: map,
  forEach: forEach,
  isDomElement: isDomElement,
  reversed: reversed,
  windowForElement: windowForElement
});

/**
 * Return the number of stride nodes between 2 DOM nodes *at the same
 * level of the tree*, without going up or down the tree.
 *
 * ``left`` xor ``right`` may also be undefined.
 */
function numStrides(left, right) {
    let num = 0;

    // Walk right from left node until we hit the right node or run out:
    let sibling = left;
    let shouldContinue = sibling && sibling !== right;
    while (shouldContinue) {
        sibling = sibling.nextSibling;
        if ((shouldContinue = sibling && sibling !== right) &&
            !isWhitespace(sibling)) {
            num += 1;
        }
    }
    if (sibling !== right) {  // Don't double-punish if left and right are siblings.
        // Walk left from right node:
        sibling = right;
        while (sibling) {
            sibling = sibling.previousSibling;
            if (sibling && !isWhitespace(sibling)) {
                num += 1;
            }
        }
    }
    return num;
}

/**
 * Return a topological distance between 2 DOM nodes or :term:`fnodes<fnode>`
 * weighted according to the similarity of their ancestry in the DOM. For
 * instance, if one node is situated inside ``<div><span><b><theNode>`` and the
 * other node is at ``<differentDiv><span><b><otherNode>``, they are considered
 * close to each other for clustering purposes. This is useful for picking out
 * nodes which have similar purposes.
 *
 * Return ``Number.MAX_VALUE`` if one of the nodes contains the other.
 *
 * This is largely an implementation detail of :func:`clusters`, but you can
 * call it yourself if you wish to implement your own clustering. Takes O(n log
 * n) time.
 *
 * Note that the default costs may change; pass them in explicitly if they are
 * important to you.
 *
 * @arg fnodeA {Node|Fnode}
 * @arg fnodeB {Node|Fnode}
 * @arg differentDepthCost {number} Cost for each level deeper one node is than
 *    the other below their common ancestor
 * @arg differentTagCost {number} Cost for a level below the common ancestor
 *    where tagNames differ
 * @arg sameTagCost {number} Cost for a level below the common ancestor where
 *    tagNames are the same
 * @arg strideCost {number} Cost for each stride node between A and B. Stride
 *     nodes are siblings or siblings-of-ancestors that lie between the 2
 *     nodes. These interposed nodes make it less likely that the 2 nodes
 *     should be together in a cluster.
 * @arg additionalCost {function} Return an additional cost, given 2 fnodes or
 *    nodes.
 *
 */
function distance(fnodeA,
                         fnodeB,
                         {differentDepthCost = 2,
                          differentTagCost = 2,
                          sameTagCost = 1,
                          strideCost = 1,
                          additionalCost = (fnodeA, fnodeB) => 0} = {}) {
    // I was thinking of something that adds little cost for siblings. Up
    // should probably be more expensive than down (see middle example in the
    // Nokia paper).

    // TODO: Test and tune default costs. They're off the cuff at the moment.

    if (fnodeA === fnodeB) {
        return 0;
    }

    const elementA = isDomElement(fnodeA) ? fnodeA : fnodeA.element;
    const elementB = isDomElement(fnodeB) ? fnodeB : fnodeB.element;

    // Stacks that go from the common ancestor all the way to A and B:
    const aAncestors = [elementA];
    const bAncestors = [elementB];

    let aAncestor = elementA;
    let bAncestor = elementB;

    // Ascend to common parent, stacking them up for later reference:
    while (!aAncestor.contains(elementB)) {  // Note: an element does contain() itself.
        aAncestor = aAncestor.parentNode;
        aAncestors.push(aAncestor); //aAncestors = [a, b]. aAncestor = b // if a is outer: no loop here; aAncestors = [a]. aAncestor = a.
    }

    // In compareDocumentPosition()'s opinion, inside implies after. Basically,
    // before and after pertain to opening tags.
    const comparison = elementA.compareDocumentPosition(elementB);

    // If either contains the other, abort. We'd either return a misleading
    // number or else walk upward right out of the document while trying to
    // make the ancestor stack.
    if (comparison & (elementA.DOCUMENT_POSITION_CONTAINS | elementA.DOCUMENT_POSITION_CONTAINED_BY)) {
        return Number.MAX_VALUE;
    }
    // Make an ancestor stack for the right node too so we can walk
    // efficiently down to it:
    do {
        bAncestor = bAncestor.parentNode;  // Assumes we've early-returned above if A === B. This walks upward from the outer node and up out of the tree. It STARTS OUT with aAncestor === bAncestor!
        bAncestors.push(bAncestor);
    } while (bAncestor !== aAncestor);

    // Figure out which node is left and which is right, so we can follow
    // sibling links in the appropriate directions when looking for stride
    // nodes:
    let left = aAncestors;
    let right = bAncestors;
    let cost = 0;
    if (comparison & elementA.DOCUMENT_POSITION_FOLLOWING) {
        // A is before, so it could contain the other node. What did I mean to do if one contained the other?
        left = aAncestors;
        right = bAncestors;
    } else if (comparison & elementA.DOCUMENT_POSITION_PRECEDING) {
        // A is after, so it might be contained by the other node.
        left = bAncestors;
        right = aAncestors;
    }

    // Descend to both nodes in parallel, discounting the traversal
    // cost iff the nodes we hit look similar, implying the nodes dwell
    // within similar structures.
    while (left.length || right.length) {
        const l = left.pop();
        const r = right.pop();
        if (l === undefined || r === undefined) {
            // Punishment for being at different depths: same as ordinary
            // dissimilarity punishment for now
            cost += differentDepthCost;
        } else {
            // TODO: Consider similarity of classList.
            cost += l.tagName === r.tagName ? sameTagCost : differentTagCost;
        }
        // Optimization: strides might be a good dimension to eliminate.
        if (strideCost !== 0) {
            cost += numStrides(l, r) * strideCost;
        }
    }

    return cost + additionalCost(fnodeA, fnodeB);
}

/**
 * Return the spatial distance between 2 fnodes or elements, assuming a
 * rendered page.
 *
 * Specifically, return the distance in pixels between the centers of
 * ``fnodeA.element.getBoundingClientRect()`` and
 * ``fnodeB.element.getBoundingClientRect()``.
 */
function euclidean(fnodeA, fnodeB) {
    /**
     * Return the horizontal distance from the left edge of the viewport to the
     * center of an element, given a DOMRect object for it. It doesn't matter
     * that the distance is affected by the page's scroll offset, since the 2
     * elements have the same offset.
     */
    function xCenter(domRect) {
        return domRect.left + domRect.width / 2;
    }
    function yCenter(domRect) {
        return domRect.top + domRect.height / 2;
    }

    const elementA = toDomElement(fnodeA);
    const elementB = toDomElement(fnodeB);
    const aRect = elementA.getBoundingClientRect();
    const bRect = elementB.getBoundingClientRect();
    return Math.sqrt((xCenter(aRect) - xCenter(bRect)) ** 2 +
                     (yCenter(aRect) - yCenter(bRect)) ** 2);
}

/** A lower-triangular matrix of inter-cluster distances */
class DistanceMatrix {
    /**
     * @arg distance {function} Some notion of distance between 2 given nodes
     */
    constructor(elements, distance) {
        // A sparse adjacency matrix:
        // {A => {},
        //  B => {A => 4},
        //  C => {A => 4, B => 4},
        //  D => {A => 4, B => 4, C => 4}
        //  E => {A => 4, B => 4, C => 4, D => 4}}
        //
        // A, B, etc. are arrays of [arrays of arrays of...] nodes, each
        // array being a cluster. In this way, they not only accumulate a
        // cluster but retain the steps along the way.
        //
        // This is an efficient data structure in terms of CPU and memory, in
        // that we don't have to slide a lot of memory around when we delete a
        // row or column from the middle of the matrix while merging. Of
        // course, we lose some practical efficiency by using hash tables, and
        // maps in particular are slow in their early implementations.
        this._matrix = new Map();

        // Convert elements to clusters:
        const clusters = elements.map(el => [el]);

        // Init matrix:
        for (let outerCluster of clusters) {
            const innerMap = new Map();
            for (let innerCluster of this._matrix.keys()) {
                innerMap.set(innerCluster, distance(outerCluster[0],
                                                    innerCluster[0]));
            }
            this._matrix.set(outerCluster, innerMap);
        }
        this._numClusters = clusters.length;
    }

    // Return (distance, a: clusterA, b: clusterB) of closest-together clusters.
    // Replace this to change linkage criterion.
    closest() {
        const self = this;

        if (this._numClusters < 2) {
            throw new Error('There must be at least 2 clusters in order to return the closest() ones.');
        }

        // Return the distances between every pair of clusters.
        function clustersAndDistances() {
            const ret = [];
            for (let [outerKey, row] of self._matrix.entries()) {
                for (let [innerKey, storedDistance] of row.entries()) {
                    ret.push({a: outerKey, b: innerKey, distance: storedDistance});
                }
            }
            return ret;
        }
        // Optimizing this by inlining the loop and writing it less
        // functionally doesn't help:
        return min(clustersAndDistances(), x => x.distance);
    }

    // Look up the distance between 2 clusters in me. Try the lookup in the
    // other direction if the first one falls in the nonexistent half of the
    // triangle.
    _cachedDistance(clusterA, clusterB) {
        let ret = this._matrix.get(clusterA).get(clusterB);
        if (ret === undefined) {
            ret = this._matrix.get(clusterB).get(clusterA);
        }
        return ret;
    }

    // Merge two clusters.
    merge(clusterA, clusterB) {
        // An example showing how rows merge:
        //  A: {}
        //  B: {A: 1}
        //  C: {A: 4, B: 4},
        //  D: {A: 4, B: 4, C: 4}
        //  E: {A: 4, B: 4, C: 2, D: 4}}
        //
        // Step 2:
        //  C: {}
        //  D: {C: 4}
        //  E: {C: 2, D: 4}}
        //  AB: {C: 4, D: 4, E: 4}
        //
        // Step 3:
        //  D:  {}
        //  AB: {D: 4}
        //  CE: {D: 4, AB: 4}

        // Construct new row, finding min distances from either subcluster of
        // the new cluster to old clusters.
        //
        // There will be no repetition in the matrix because, after all,
        // nothing pointed to this new cluster before it existed.
        const newRow = new Map();
        for (let outerKey of this._matrix.keys()) {
            if (outerKey !== clusterA && outerKey !== clusterB) {
                newRow.set(outerKey, Math.min(this._cachedDistance(clusterA, outerKey),
                                              this._cachedDistance(clusterB, outerKey)));
            }
        }

        // Delete the rows of the clusters we're merging.
        this._matrix.delete(clusterA);
        this._matrix.delete(clusterB);

        // Remove inner refs to the clusters we're merging.
        for (let inner of this._matrix.values()) {
            inner.delete(clusterA);
            inner.delete(clusterB);
        }

        // Attach new row.
        this._matrix.set([clusterA, clusterB], newRow);

        // There is a net decrease of 1 cluster:
        this._numClusters -= 1;
    }

    numClusters() {
        return this._numClusters;
    }

    // Return an Array of nodes for each cluster in me.
    clusters() {
        // TODO: Can't get map to work here. Don't know why.
        return Array.from(this._matrix.keys()).map(e => Array.from(flatten(e)));
    }
}

/**
 * Partition the given nodes into one or more clusters by position in the DOM
 * tree.
 *
 * This implements an agglomerative clustering. It uses single linkage, since
 * we're talking about adjacency here more than Euclidean proximity: the
 * clusters we're talking about in the DOM will tend to be adjacent, not
 * overlapping. We haven't tried other linkage criteria yet.
 *
 * In a later release, we may consider score or notes.
 *
 * @arg {Fnode[]|Node[]} fnodes :term:`fnodes<fnode>` or DOM nodes to group
 *     into clusters
 * @arg {number} splittingDistance The closest-nodes :func:`distance` beyond
 *     which we will not attempt to unify 2 clusters. Make this larger to make
 *     larger clusters.
 * @arg getDistance {function} A function that returns some notion of numerical
 *    distance between 2 nodes. Default: :func:`distance`
 * @return {Array} An Array of Arrays, with each Array containing all the
 *     nodes in one cluster. Note that neither the clusters nor the nodes are
 *     in any particular order. You may find :func:`domSort` helpful to remedy
 *     the latter.
 */
function clusters(fnodes, splittingDistance, getDistance = distance) {
    const matrix = new DistanceMatrix(fnodes, getDistance);
    let closest;

    while (matrix.numClusters() > 1 && (closest = matrix.closest()).distance < splittingDistance) {
        matrix.merge(closest.a, closest.b);
    }

    return matrix.clusters();
}

var clusters$1 = /*#__PURE__*/Object.freeze({
  __proto__: null,
  distance: distance,
  euclidean: euclidean,
  clusters: clusters
});

// The left-hand side of a rule


/**
 * Take nodes that match a given DOM selector. Example:
 * ``dom('meta[property="og:title"]')``
 *
 * Every ruleset has at least one ``dom`` or :func:`element` rule, as that is
 * where nodes begin to flow into the system. If run against a subtree of a
 * document, the root of the subtree is not considered as a possible match.
 */
function dom(selector) {
    return new DomLhs(selector);
}

/**
 * Take a single given node if it matches a given DOM selector, without looking
 * through its descendents or ancestors. Otherwise, take no nodes. Example:
 * ``element('input')``
 *
 * This is useful for applications in which you want Fathom to classify an
 * element the user has selected, rather than scanning the whole page for
 * candidates.
 */
function element(selector) {
    return new ElementLhs(selector);
}

/**
 * Rules and the LHSs and RHSs that comprise them have no mutable state. This
 * lets us make BoundRulesets from Rulesets without duplicating the rules. It
 * also lets us share a common cache among rules: multiple ones might care
 * about a cached type(), for instance; there isn't a one-to-one relationship
 * of storing with caring. There would also, because of the interdependencies
 * of rules in a ruleset, be little use in segmenting the caches: if you do
 * something that causes one to need to be cleared, you'll need to clear many
 * more as well.
 *
 * Lhses are responsible for maintaining ruleset.maxCache.
 *
 * Lhs and its subclasses are private to the Fathom framework.
 */
class Lhs {
    constructor() {
        this._predicate = () => true;
    }

    /** Return a new Lhs of the appropriate kind, given its first call. */
    static fromFirstCall(firstCall) {
        // firstCall is never 'dom', because dom() directly returns a DomLhs.
        if (firstCall.method === 'type') {
            return new TypeLhs(...firstCall.args);
        } else if (firstCall.method === 'and') {
            return new AndLhs(firstCall.args);
        } else if (firstCall.method === 'nearest') {
            return new NearestLhs(firstCall.args);
        } else {
            throw new Error('The left-hand side of a rule() must start with dom(), type(), and(), or nearest().');
        }
    }

    /**
     * Prune nodes from consideration early in run execution, before scoring is
     * done.
     *
     * Reserve this for where you are sure it is always correct or when
     * performance demands it. It is generally preferable to use :func:`score`
     * and let the :doc:`trainer<training>` determine the relative significance
     * of each rule. Human intuition as to what is important is often wrong:
     * for example, one might assume that a music player website would include
     * the word "play", but this does not hold once you include sites in other
     * languages.
     *
     * Can be chained after :func:`type` or :func:`dom`.
     *
     * Example: ``dom('p').when(isVisible)``
     *
     * @arg {function} predicate Accepts a fnode and returns a boolean
     */
    when(predicate) {
        let lhs = this.clone();
        lhs._predicate = predicate;
        return lhs;
    }

    /**
     * Of all the dom nodes selected by type() or dom(), return only
     * the fnodes that satisfy all the predicates imposed by calls to
     * when()
     */
    fnodesSatisfyingWhen(fnodes) {
        return Array.from(fnodes).filter(this._predicate);
    }

    /**
     * Return an iterable of output fnodes selected by this left-hand-side
     * expression.
     *
     * Pre: The rules I depend on have already been run, and their results are
     * in ruleset.typeCache.
     *
     * @arg ruleset {BoundRuleset}
     */
    // fnodes (ruleset) {}

    /**
     * Check that a RHS-emitted fact is legal for this kind of LHS, and throw
     * an error if it isn't.
     */
    checkFact(fact) {}

    /**
     * Return the single type the output of the LHS is guaranteed to have.
     * Return undefined if there is no such single type we can ascertain.
     */
    guaranteedType() {}

    /**
     * Return the type I aggregate if I am an aggregate LHS; return undefined
     * otherwise.
     */
    aggregatedType() {}

    /**
     * Return each combination of types my selected nodes could be locally (that
     * is, by this rule only) constrained to have.
     *
     * For example, type(A) would return [A]. and(A, or(B, C)) would return
     * [AB, AC, ABC]. More examples:
     *
     * or(A, B) → typeIn(A, B, C)  # Finalizes A, B.   combos A, B, AB: finalizes AB. Optimization: there's no point in returning the last combo in ors. Compilation into 2 rules with identical RHSs will inherently implement this optimization.
     * or(A, B) → typeIn(A, B)  # Finalizes A, B
     * or(A, B) → A  # Finalizes B
     * and(A) -> A  # Finalizes nothing
     * and(A, B) -> A  # Finalizes nothing.   AB: Ø
     * and(A) -> typeIn(A, B)  # Finalizes A.   A
     * and(A, B) -> typeIn(A, B)  # Finalizes nothing.   AB
     * and(A, B) -> typeIn(A, B, C)  # Finalizes A, B.   AB
     * and(A, or(B, C)) -> D  # Finalizes A, B, C.   AB, AC, ABC: ABC
     * and(A, or(B, C)) -> B  # Finalizes A, C.   AB, AC, ABC: AC
     * type(A).not(and(A, B)) ->
     *
     * @return {NiceSet[]}
     */
    // possibleTypeCombinations() {}

    /**
     * Types mentioned in this LHS.
     *
     * In other words, the types I need to know the assignment status of before
     * I can make my selections
     *
     * @return NiceSet of strings
     */
    // typesMentioned() {}
}

class DomLhs extends Lhs {
    constructor(selector) {
        super();
        if (selector === undefined) {
            throw new Error('A querySelector()-style selector is required as the argument to ' + this._callName() + '().');
        }
        this.selector = selector;
    }

    /**
     * Return the name of this kind of LHS, for use in error messages.
     */
    _callName() {
        return 'dom';
    }

    clone() {
        return new this.constructor(this.selector);
    }

    fnodes(ruleset) {
        return this._domNodesToFilteredFnodes(
            ruleset,
            ruleset.doc.querySelectorAll(this.selector));
    }

    /**
     * Turn a NodeList of DOM nodes into an array of fnodes, and filter out
     * those that don't match the :func:`when()` clause.
     */
    _domNodesToFilteredFnodes(ruleset, domNodes) {
        let ret = [];
        for (let i = 0; i < domNodes.length; i++) {
            ret.push(ruleset.fnodeForElement(domNodes[i]));
        }
        return this.fnodesSatisfyingWhen(ret);
    }

    checkFact(fact) {
        if (fact.type === undefined) {
            throw new Error(`The right-hand side of a ${this._callName()}() rule failed to specify a type. This means there is no way for its output to be used by later rules. All it specified was ${fact}.`);
        }
    }

    asLhs() {
        return this;
    }

    possibleTypeCombinations() {
        return [];
    }

    typesMentioned() {
        return new NiceSet();
    }
}

class ElementLhs extends DomLhs {
    _callName() {
        return 'element';
    }

    fnodes(ruleset) {
        return this._domNodesToFilteredFnodes(
            ruleset,
            ruleset.doc.matches(this.selector) ? [ruleset.doc] : []);
    }
}

/** Internal representation of a LHS constrained by type but not by max() */
class TypeLhs extends Lhs {
    constructor(type) {
        super();
        if (type === undefined) {
            throw new Error('A type name is required when calling type().');
        }
        this._type = type;  // the input type
    }

    clone() {
        return new this.constructor(this._type);
    }

    fnodes(ruleset) {
        const cached = getDefault(ruleset.typeCache, this._type, () => []);
        return this.fnodesSatisfyingWhen(cached);
    }

    /** Override the type previously specified by this constraint. */
    type(inputType) {
        // Preserve the class in case this is a TypeMaxLhs.
        return new this.constructor(inputType);
    }

    /**
     * Of the nodes selected by a ``type`` call to the left, constrain the LHS
     * to return only the max-scoring one. If there is a tie, more than 1 node
     * will be returned. Example: ``type('titley').max()``
     */
    max() {
        return new TypeMaxLhs(this._type);
    }

    /**
     * Take the nodes selected by a ``type`` call to the left, group them into
     * clusters, and return the nodes in the cluster that has the highest total
     * score (on the relevant type).
     *
     * Nodes come out in arbitrary order, so, if you plan to emit them,
     * consider using ``.out('whatever').allThrough(domSort)``. See
     * :func:`domSort`.
     *
     * If multiple clusters have equally high scores, return an arbitrary one,
     * because Fathom has no way to represent arrays of arrays in rulesets.
     *
     * @arg options {Object} The same depth costs taken by :func:`distance`,
     *     plus ``splittingDistance``, which is the distance beyond which 2
     *     clusters will be considered separate. ``splittingDistance``, if
     *     omitted, defaults to 3.
     */
    bestCluster(options) {
        return new BestClusterLhs(this._type, options);
    }

    // Other clustering calls could be called biggestCluster() (having the most
    // nodes) and bestAverageCluster() (having the highest average score).

    guaranteedType() {
        return this._type;
    }

    possibleTypeCombinations() {
        return [this.typesMentioned()];
    }

    typesMentioned() {
        return new NiceSet([this._type]);
    }
}

/**
 * Abstract LHS that is an aggregate function taken across all fnodes of a type
 *
 * The main point here is that any aggregate function over a (typed) set of
 * nodes depends on first computing all the rules that could emit those nodes
 * (nodes of that type).
 */
class AggregateTypeLhs extends TypeLhs {
    aggregatedType() {
        return this._type;
    }
}

/**
 * Internal representation of a LHS that has both type and max([NUMBER])
 * constraints. max(NUMBER != 1) support is not yet implemented.
 */
class TypeMaxLhs extends AggregateTypeLhs {
    /**
     * Return the max-scoring node (or nodes if there is a tie) of the given
     * type.
     */
    fnodes(ruleset) {
        // TODO: Optimize better. Walk the dependency tree, and run only the
        // rules that could possibly lead to a max result. As part of this,
        // make RHSs expose their max potential scores.
        const self = this;
        // Work around V8 bug:
        // https://stackoverflow.com/questions/32943776/using-super-within-an-
        // arrow-function-within-an-arrow-function-within-a-method
        const getSuperFnodes = () => super.fnodes(ruleset);
        return setDefault(
            ruleset.maxCache,
            this._type,
            function maxFnodesOfType() {
                return maxes(getSuperFnodes(), fnode => ruleset.weightedScore(fnode.scoresSoFarFor(self._type)));
            });
    }
}

class BestClusterLhs extends AggregateTypeLhs {
    constructor(type, options) {
        super(type);
        this._options = options || {splittingDistance: 3};
    }

    /**
     * Group the nodes of my type into clusters, and return the cluster with
     * the highest total score for that type.
     */
    fnodes(ruleset) {
        // Get the nodes of the type:
        const fnodesOfType = Array.from(super.fnodes(ruleset));
        if (fnodesOfType.length === 0) {
            return [];
        }
        // Cluster them:
        const clusts = clusters(
            fnodesOfType,
            this._options.splittingDistance,
            (a, b) => distance(a, b, this._options));
        // Tag each cluster with the total of its nodes' scores:
        const clustsAndSums = clusts.map(
            clust => [clust,
                      sum(clust.map(fnode => fnode.scoreFor(this._type)))]);
        // Return the highest-scoring cluster:
        return max(clustsAndSums, clustAndSum => clustAndSum[1])[0];
    }
}

class AndLhs extends Lhs {
    constructor(lhss) {
        super();

        // For the moment, we accept only type()s as args. TODO: Generalize to
        // type().max() and such later.
        this._args = lhss.map(sideToTypeLhs);
    }

    *fnodes(ruleset) {
        // Take an arbitrary one for starters. Optimization: we could always
        // choose the pickiest one to start with.
        const fnodes = this._args[0].fnodes(ruleset);
        // Then keep only the fnodes that have the type of every other arg:
        fnodeLoop: for (let fnode of fnodes) {
            for (let otherLhs of this._args.slice(1)) {
                // Optimization: could use a .hasTypeSoFar() below
                if (!fnode.hasType(otherLhs.guaranteedType())) {
                    // TODO: This is n^2. Why is there no set intersection in JS?!
                    continue fnodeLoop;
                }
            }
            yield fnode;
        }
    }

    possibleTypeCombinations() {
        return [this.typesMentioned()];
    }

    typesMentioned() {
        return new NiceSet(this._args.map(arg => arg.guaranteedType()));
    }
}

function sideToTypeLhs(side) {
    const lhs = side.asLhs();
    if (!(lhs.constructor === TypeLhs)) {
        throw new Error('and() and nearest() support only simple type() calls as arguments for now.');
        // TODO: Though we could solve this with a compilation step: and(type(A), type(B).max()) is equivalent to type(B).max() -> type(Bmax); and(type(A), type(Bmax)).
        // In fact, we should be able to compile most (any?) arbitrary and()s, including nested ands and and(type(...).max(), ...) constructions into several and(type(A), type(B), ...) rules.
    }
    return lhs;
}

class NearestLhs extends Lhs {
    constructor([a, b, distance]) {
        super();
        this._a = sideToTypeLhs(a);
        this._b = sideToTypeLhs(b);
        this._distance = distance;
    }

    /**
     * Return an iterable of {fnodes, transformer} pairs.
     */
    *fnodes(ruleset) {
        // Go through all the left arg's nodes. For each one, find the closest
        // right-arg's node. O(a * b). Once a right-arg's node is used, we
        // don't eliminate it from consideration, because then order of left-
        // args' nodes would matter.

        // TODO: Still not sure how to get the distance to factor into the
        // score unless we hard-code nearest() to do that. It's a
        // matter of not being able to bind on the RHS to the output of the
        // distance function on the LHS. Perhaps we could at least make
        // distance part of the note and read it in a props() callback.

        // We're assuming here that simple type() calls return just plain
        // fnodes, not {fnode, rhsTransformer} pairs:
        const as_ = this._a.fnodes(ruleset);
        const bs = Array.from(this._b.fnodes(ruleset));
        if (bs.length > 0) {
            // If bs is empty, there can be no nearest nodes, so don't emit any.
            for (const a of as_) {
                const nearest = min(bs, b => this._distance(a, b));
                yield {fnode: a,
                       rhsTransformer: function setNoteIfEmpty(fact) {
                           // If note is explicitly set by the RHS, let it take
                           // precedence, even though that makes this entire LHS
                           // pointless.
                           if (fact.note === undefined) {
                               fact.note = nearest;  // TODO: Wrap this in an object to make room to return distance later.
                           }
                           return fact;
                       }};
            }
        }
    }

    checkFact(fact) {
        // Barf if the fact doesn't set a type at least. It should be a *new* type or at least one that doesn't result in cycles, but we can't deduce that.
    }

    possibleTypeCombinations() {
        return [new NiceSet([this._a.guaranteedType()])];
    }

    typesMentioned() {
        return new NiceSet([this._a.guaranteedType(),
                            this._b.guaranteedType()]);
    }

    guaranteedType() {
        return this._a.guaranteedType();
    }
}

// The right-hand side of a rule


const TYPE = 1;
const NOTE = 2;
const SCORE = 4;
const ELEMENT = 8;
const SUBFACTS = {
    type: TYPE,
    note: NOTE,
    score: SCORE,
    element: ELEMENT
};

/**
 * Expose the output of this rule's LHS as a "final result" to the surrounding
 * program. It will be available by calling :func:`~BoundRuleset.get` on the
 * ruleset and passing the key. You can run each node through a callback
 * function first by adding :func:`through()`, or you can run the entire set of
 * nodes through a callback function by adding :func:`allThrough()`.
 */
function out(key) {
    return new OutwardRhs(key);
}

class InwardRhs {
    constructor(calls = [], max = Infinity, types) {
        this._calls = calls.slice();
        this._max = max;  // max score
        this._types = new NiceSet(types);  // empty set if unconstrained
    }

    /**
     * Declare that the maximum returned subscore is such and such,
     * which helps the optimizer plan efficiently. This doesn't force it to be
     * true; it merely throws an error at runtime if it isn't. To lift an
     * ``atMost`` constraint, call ``atMost()`` (with no args). The reason
     * ``atMost`` and ``typeIn`` apply until explicitly cleared is so that, if
     * someone used them for safety reasons on a lexically distant rule you are
     * extending, you won't stomp on their constraint and break their
     * invariants accidentally.
     */
    atMost(score) {
        return new this.constructor(this._calls, score, this._types);
    }

    _checkAtMost(fact) {
        if (fact.score !== undefined && fact.score > this._max) {
            throw new Error(`Score of ${fact.score} exceeds the declared atMost(${this._max}).`);
        }
    }

    /**
      * Determine any of type, note, score, and element using a callback. This
      * overrides any previous call to `props` and, depending on what
      * properties of the callback's return value are filled out, may override
      * the effects of other previous calls as well.
      *
      * The callback should return...
      *
      * * An optional :term:`subscore`
      * * A type (required on ``dom(...)`` rules, defaulting to the input one on
      *   ``type(...)`` rules)
      * * Optional notes
      * * An element, defaulting to the input one. Overriding the default
      *   enables a callback to walk around the tree and say things about nodes
      *   other than the input one.
      */
    props(callback) {
        function getSubfacts(fnode) {
            const subfacts = callback(fnode);
            // Filter the raw result down to okayed properties so callbacks
            // can't insert arbitrary keys (like conserveScore, which might
            // mess up the optimizer).
            for (let subfact in subfacts) {
                if (!SUBFACTS.hasOwnProperty(subfact) || !(SUBFACTS[subfact] & getSubfacts.possibleSubfacts)) {
                    // The ES5.1 spec says in 12.6.4 that it's fine to delete
                    // as we iterate.
                    delete subfacts[subfact];
                }
            }
            return subfacts;
        }
        // Thse are the subfacts this call could affect:
        getSubfacts.possibleSubfacts = TYPE | NOTE | SCORE | ELEMENT;
        getSubfacts.kind = 'props';
        return new this.constructor(this._calls.concat(getSubfacts),
                                    this._max,
                                    this._types);
    }

    /**
     * Set the type applied to fnodes processed by this RHS.
     */
    type(theType) {
        // In the future, we might also support providing a callback that receives
        // the fnode and returns a type. We couldn't reason based on these, but the
        // use would be rather a consise way to to override part of what a previous
        // .props() call provides.

        // Actually emit a given type.
        function getSubfacts() {
            return {type: theType};
        }
        getSubfacts.possibleSubfacts = TYPE;
        getSubfacts.type = theType;
        getSubfacts.kind = 'type';
        return new this.constructor(this._calls.concat(getSubfacts),
                                    this._max,
                                    this._types);
    }

    /**
     * Constrain this rule to emit 1 of a set of given types. Pass no args to lift
     * a previous ``typeIn`` constraint, as you might do when basing a LHS on a
     * common value to factor out repetition.
     *
     * ``typeIn`` is mostly a hint for the query planner when you're emitting types
     * dynamically from ``props`` calls—in fact, an error will be raised if
     * ``props`` is used without a ``typeIn`` or ``type`` to constrain it—but it
     * also checks conformance at runtime to ensure validity.
     */
    typeIn(...types) {
        // Rationale: If we used the spelling "type('a', 'b', ...)" instead of
        // this, one might expect type('a', 'b').type(fn) to have the latter
        // call override, while expecting type(fn).type('a', 'b') to keep both
        // in effect. Then different calls to type() don't consistently
        // override each other, and the rules get complicated. Plus you can't
        // inherit a type constraint and then sub in another type-returning
        // function that still gets the constraint applied.
        return new this.constructor(this._calls,
                                    this._max,
                                    types);
    }

    /**
     * Check a fact for conformance with any typeIn() call.
     *
     * @arg leftType the type of the LHS, which becomes my emitted type if the
     *    fact doesn't specify one
     */
    _checkTypeIn(result, leftType) {
        if (this._types.size > 0) {
            if (result.type === undefined) {
                if (!this._types.has(leftType)) {
                    throw new Error(`A right-hand side claimed, via typeIn(...) to emit one of the types ${this._types} but actually inherited ${leftType} from the left-hand side.`);
                }
            } else if (!this._types.has(result.type)) {
                throw new Error(`A right-hand side claimed, via typeIn(...) to emit one of the types ${this._types} but actually emitted ${result.type}.`);
            }
        }
    }

    /**
     * Whatever the callback returns (even ``undefined``) becomes the note of
     * the fact. This overrides any previous call to ``note``.
     */
    note(callback) {
        function getSubfacts(fnode) {
            return {note: callback(fnode)};
        }
        getSubfacts.possibleSubfacts = NOTE;
        getSubfacts.kind = 'note';
        return new this.constructor(this._calls.concat(getSubfacts),
                                    this._max,
                                    this._types);
    }

    /**
     * Affect the confidence with which the input node should be considered a
     * member of a type.
     *
     * The parameter is generally between 0 and 1 (inclusive), with 0 meaning
     * the node does not have the "smell" this rule checks for and 1 meaning it
     * does. The range between 0 and 1 is available to represent "fuzzy"
     * confidences. If you have an unbounded range to compress down to [0, 1],
     * consider using :func:`sigmoid` or a scaling thereof.
     *
     * Since every node can have multiple, independent scores (one for each
     * type), this applies to the type explicitly set by the RHS or, if none,
     * to the type named by the ``type`` call on the LHS. If the LHS has none
     * because it's a ``dom(...)`` LHS, an error is raised.
     *
     * @arg {number|function} scoreOrCallback Can either be a static number,
     *     generally 0 to 1 inclusive, or else a callback which takes the fnode
     *     and returns such a number. If the callback returns a boolean, it is
     *     cast to a number.
     */
    score(scoreOrCallback) {
        let getSubfacts;

        function getSubfactsFromNumber(fnode) {
            return {score: scoreOrCallback};
        }

        function getSubfactsFromFunction(fnode) {
            let result = scoreOrCallback(fnode);
            if (typeof result === 'boolean') {
                // Case bools to numbers for convenience. Boolean features are
                // common. Don't cast other things, as it frustrates ruleset
                // debugging.
                result = Number(result);
            }
            return {score: result};
        }

        if (typeof scoreOrCallback === 'number') {
            getSubfacts = getSubfactsFromNumber;
        } else {
            getSubfacts = getSubfactsFromFunction;
        }
        getSubfacts.possibleSubfacts = SCORE;
        getSubfacts.kind = 'score';

        return new this.constructor(this._calls.concat(getSubfacts),
                                    this._max,
                                    this._types);
    }

    // Future: why not have an .element() method for completeness?

    // -------- Methods below this point are private to the framework. --------

    /**
     * Run all my props().type().note().score() stuff across a given fnode,
     * enforce my max() stuff, and return a fact ({element, type, score,
     * notes}) for incorporation into that fnode (or a different one, if
     * element is specified). Any of the 4 fact properties can be missing;
     * filling in defaults is a job for the caller.
     *
     * @arg leftType The type the LHS takes in
     */
    fact(fnode, leftType) {
        const doneKinds = new Set();
        const result = {};
        let haveSubfacts = 0;
        for (let call of reversed(this._calls)) {
            // If we've already called a call of this kind, then forget it.
            if (!doneKinds.has(call.kind)) {
                doneKinds.add(call.kind);

                if (~haveSubfacts & call.possibleSubfacts) {
                    // This call might provide a subfact we are missing.
                    const newSubfacts = call(fnode);

                    // We start with an empty object, so we're okay here.
                    // eslint-disable-next-line guard-for-in
                    for (let subfact in newSubfacts) {
                        // A props() callback could insert arbitrary keys into
                        // the result, but it shouldn't matter, because nothing
                        // pays any attention to them.
                        if (!result.hasOwnProperty(subfact)) {
                            result[subfact] = newSubfacts[subfact];
                        }
                        haveSubfacts |= SUBFACTS[subfact];
                    }
                }
            }
        }
        this._checkAtMost(result);
        this._checkTypeIn(result, leftType);
        return result;
    }

    /**
     * Return a record describing the types I might emit (which means either to
     * add a type to a fnode or to output a fnode that already has that type).
     * {couldChangeType: whether I might add a type to the fnode,
     *  possibleTypes: If couldChangeType, the types I might emit; empty set if
     *      we cannot infer it. If not couldChangeType, undefined.}
     */
    possibleEmissions() {
        // If there is a typeIn() constraint or there is a type() call to the
        // right of all props() calls, we have a constraint. We hunt for the
        // tightest constraint we can find, favoring a type() call because it
        // gives us a single type but then falling back to a typeIn().
        let couldChangeType = false;
        for (let call of reversed(this._calls)) {
            if (call.kind === 'props') {
                couldChangeType = true;
                break;
            } else if (call.kind === 'type') {
                return {couldChangeType: true,
                        possibleTypes: new Set([call.type])};
            }
        }
        return {couldChangeType,
                possibleTypes: this._types};
    }
}

class OutwardRhs {
    constructor(key, through = x => x, allThrough = x => x) {
        this.key = key;
        this.callback = through;
        this.allCallback = allThrough;
    }

    /**
     * Append ``.through`` to :func:`out` to run each :term:`fnode` emitted
     * from the LHS through an arbitrary function before returning it to the
     * containing program. Example::
     *
     *     out('titleLengths').through(fnode => fnode.noteFor('title').length)
     */
    through(callback) {
        return new this.constructor(this.key, callback, this.allCallback);
    }

    /**
     * Append ``.allThrough`` to :func:`out` to run the entire iterable of
     * emitted :term:`fnodes<fnode>` through an arbitrary function before
     * returning them to the containing program. Example::
     *
     *     out('sortedTitles').allThrough(domSort)
     */
    allThrough(callback) {
        return new this.constructor(this.key, this.callback, callback);
    }

    asRhs() {
        return this;
    }
}

function props(callback) {
    return new Side({method: 'props', args: [callback]});
}

/** Constrain to an input type on the LHS, or apply a type on the RHS. */
function type(theType) {
    return new Side({method: 'type', args: [theType]});
}

function note(callback) {
    return new Side({method: 'note', args: [callback]});
}

function score(scoreOrCallback) {
    return new Side({method: 'score', args: [scoreOrCallback]});
}

function atMost(score) {
    return new Side({method: 'atMost', args: [score]});
}

function typeIn(...types) {
    return new Side({method: 'typeIn', args: types});
}

/**
 * Pull nodes that conform to multiple conditions at once.
 *
 * For example: ``and(type('title'), type('english'))``
 *
 * Caveats: ``and`` supports only simple ``type`` calls as arguments for now,
 * and it may fire off more rules as prerequisites than strictly necessary.
 * ``not`` and ``or`` don't exist yet, but you can express ``or`` the long way
 * around by having 2 rules with identical RHSs.
 */
function and(...lhss) {
    return new Side({method: 'and', args: lhss});
}

/**
 * Experimental. For each :term:`fnode` from ``typeCallA``, find the closest
 * node from ``typeCallB``, and attach it as a note. The note is attached to
 * the type specified by the RHS, defaulting to the type of ``typeCallA``. If
 * no nodes are emitted from ``typeCallB``, do nothing.
 *
 * For example... ::
 *
 *     nearest(type('image'), type('price'))
 *
 * The score of the ``typeCallA`` can be added to the new type's score by using
 * :func:`conserveScore` (though this routine has since been removed)::
 *
 *     rule(nearest(type('image'), type('price')),
 *          type('imageWithPrice').score(2).conserveScore())
 *
 * Caveats: ``nearest`` supports only simple ``type`` calls as arguments ``a``
 * and ``b`` for now.
 *
 * @arg distance {function} A function that takes 2 fnodes and returns a
 *     numerical distance between them. Included options are :func:`distance`,
 *     which is a weighted topological distance, and :func:`euclidean`, which
 *     is a spatial distance.
 */
function nearest(typeCallA, typeCallB, distance = euclidean) {
    return new Side({method: 'nearest', args: [typeCallA, typeCallB, distance]});
}

/**
 * A chain of calls that can be compiled into a Rhs or Lhs, depending on its
 * position in a Rule. This lets us use type() as a leading call for both RHSs
 * and LHSs. I would prefer to do this dynamically, but that wouldn't compile
 * down to old versions of ES.
 */
class Side {
    constructor(...calls) {
        // A "call" is like {method: 'dom', args: ['p.smoo']}.
        this._calls = calls;
    }

    max() {
        return this._and('max');
    }

    bestCluster(options) {
        return this._and('bestCluster', options);
    }

    props(callback) {
        return this._and('props', callback);
    }

    type(...types) {
        return this._and('type', ...types);
    }

    note(callback) {
        return this._and('note', callback);
    }

    score(scoreOrCallback) {
        return this._and('score', scoreOrCallback);
    }

    atMost(score) {
        return this._and('atMost', score);
    }

    typeIn(...types) {
        return this._and('typeIn', ...types);
    }

    and(...lhss) {
        return this._and('and', lhss);
    }

    _and(method, ...args) {
        return new this.constructor(...this._calls.concat({method, args}));
    }

    asLhs() {
        return this._asSide(Lhs.fromFirstCall(this._calls[0]), this._calls.slice(1));
    }

    asRhs() {
        return this._asSide(new InwardRhs(), this._calls);
    }

    _asSide(side, calls) {
        for (let call of calls) {
            side = side[call.method](...call.args);
        }
        return side;
    }

    when(pred) {
        return this._and('when', pred);
    }
}

/**
 * A wrapper around a DOM node, storing :term:`types<type>`,
 * :term:`scores<score>`, and :term:`notes<note>` that apply to it
 */
class Fnode {
    /**
     * @arg element The DOM element described by the fnode.
     * @arg ruleset The ruleset which created the fnode.
     */
    constructor(element, ruleset) {
        if (element === undefined) {
            throw new Error("Someone tried to make a fnode without specifying the element they're talking about.");
        }
        /**
         * The raw DOM element this fnode describes
         */
        this.element = element;
        this._ruleset = ruleset;

        // A map of type => {score: number, note: anything}. `score` is always
        // present and defaults to 1. A note is set iff `note` is present and
        // not undefined.
        this._types = new Map();

        // Note: conserveScore() is temporarily absent in 3.0.
        //
        // By default, a fnode has an independent score for each of its types.
        // However, a RHS can opt to conserve the score of an upstream type,
        // carrying it forward into another type. To avoid runaway scores in
        // the case that multiple rules choose to do this, we limit the
        // contribution of an upstream type's score to being multiplied in a
        // single time. In this set, we keep track of which upstream types'
        // scores have already been multiplied into each type. LHS fnode => Set
        // of types whose score for that node have been multiplied into this
        // node's score.
        this._conservedScores = new Map();
    }

    /**
     * Return whether the given type is one of the ones attached to the wrapped
     * HTML node.
     */
    hasType(type) {
        // Run type(theType) against the ruleset to make sure this doesn't
        // return false just because we haven't lazily run certain rules yet.
        this._computeType(type);
        return this._types.has(type);
    }

    /**
     * Return the confidence, in the range (0, 1), that the fnode belongs to the
     * given type, 0 by default.
     */
    scoreFor(type) {
        this._computeType(type);
        return sigmoid(this._ruleset.weightedScore(this.scoresSoFarFor(type)) +
                       getDefault(this._ruleset.biases, type, () => 0));
    }

    /**
     * Return the fnode's note for the given type, ``undefined`` if none.
     */
    noteFor(type) {
        this._computeType(type);
        return this._noteSoFarFor(type);
    }

    /**
     * Return whether this fnode has a note for the given type.
     *
     * ``undefined`` is not considered a note and may be overwritten with
     * impunity.
     */
    hasNoteFor(type) {
        this._computeType(type);
        return this._hasNoteSoFarFor(type);
    }

    // -------- Methods below this point are private to the framework. --------

    /**
     * Return an iterable of the types tagged onto me by rules that have
     * already executed.
     */
    typesSoFar() {
        return this._types.keys();
    }

    _noteSoFarFor(type) {
        return this._typeRecordForGetting(type).note;
    }

    _hasNoteSoFarFor(type) {
        return this._noteSoFarFor(type) !== undefined;
    }

    /**
     * Return the score thus far computed on me for a certain type. Doesn't
     * implicitly run any rules. If no score has yet been determined for the
     * given type, return undefined.
     */
    scoresSoFarFor(type) {
        return this._typeRecordForGetting(type).score;
    }

    /**
     * Add a given number to one of our per-type scores. Implicitly assign us
     * the given type. Keep track of which rule it resulted from so we can
     * later mess with the coeffs.
     */
    addScoreFor(type, score, ruleName) {
        this._typeRecordForSetting(type).score.set(ruleName, score);
    }

    /**
     * Set the note attached to one of our types. Implicitly assign us that
     * type if we don't have it already.
     */
    setNoteFor(type, note) {
        if (this._hasNoteSoFarFor(type)) {
            if (note !== undefined) {
                throw new Error(`Someone (likely the right-hand side of a rule) tried to add a note of type ${type} to an element, but one of that type already exists. Overwriting notes is not allowed, since it would make the order of rules matter.`);
            }
            // else the incoming note is undefined and we already have the
            // type, so it's a no-op
        } else {
            // Apply either a type and note or just a type (which means a note
            // that is undefined):
            this._typeRecordForSetting(type).note = note;
        }
    }

    /**
     * Return a score/note record for a type, creating it if it doesn't exist.
     */
    _typeRecordForSetting(type) {
        return setDefault(this._types, type, () => ({score: new Map()}));
    }

    /**
     * Manifest a temporary type record for reading, working around the lack of
     * a .? operator in JS.
     */
    _typeRecordForGetting(type) {
        return getDefault(this._types, type, () => ({score: new Map()}));
    }

    /**
     * Make sure any scores, notes, and type-tagging for the given type are
     * computed for my element.
     */
    _computeType(theType) {
        if (!this._types.has(theType)) {  // Prevent infinite recursion when an A->A rule looks at A's note in a callback.
            this._ruleset.get(type(theType));
        }
    }
}

/**
 * Construct and return the proper type of rule class based on the
 * inwardness/outwardness of the RHS.
 *
 * @arg lhs {Lhs} The left-hand side of the rule
 * @arg rhs {Rhs} The right-hand side of the rule
 * @arg options {object} Other, optional information about the rule.
 *     Currently, the only recognized option is ``name``, which points to a
 *     string that uniquely identifies this rule in a ruleset. The name
 *     correlates this rule with one of the coefficients passed into
 *     :func:`ruleset`. If no name is given, an identifier is assigned based on
 *     the index of this rule in the ruleset, but that is, of course, brittle.
 */
function rule(lhs, rhs, options) {
    // Since out() is a valid call only on the RHS (unlike type()), we can take
    // a shortcut here: any outward RHS will already be an OutwardRhs; we don't
    // need to sidetrack it through being a Side. And OutwardRhs has an asRhs()
    // that just returns itself.
    if (typeof rhs === 'string') {
        rhs = out(rhs);
    }
    return new ((rhs instanceof OutwardRhs) ? OutwardRule : InwardRule)(lhs, rhs, options);
}

let nextRuleNumber = 0;
function newInternalRuleName() {
    return '_' + nextRuleNumber++;
}

/**
 * We place the in/out distinction in Rules because it determines whether the
 * RHS result is cached, and Rules are responsible for maintaining the rulewise
 * cache ruleset.ruleCache.
 */
class Rule {  // abstract
    constructor(lhs, rhs, options) {
        this.lhs = lhs.asLhs();
        this.rhs = rhs.asRhs();
        // TODO: Make auto-generated rule names be based on the out types of
        // the rules, e.g. _priceish_4. That way, adding rules for one type
        // won't make the coeffs misalign for another.
        this.name = (options ? options.name : undefined) || newInternalRuleName();
    }

    /**
     * Return a NiceSet of the rules that this one shallowly depends on in the
     * given ruleset. In a BoundRuleset, this may include rules that have
     * already been executed.
     *
     * Depend on emitters of any LHS type this rule finalizes. (See
     * _typesFinalized for a definition.) Depend on adders of any other LHS
     * types (because, after all, we need to know what nodes have that type in
     * order to find the set of LHS nodes). This works for simple rules and
     * complex ones like and().
     *
     * Specific examples (where A is a type):
     * * A.max->* depends on anything emitting A.
     * * Even A.max->A depends on A emitters, because we have to have all the
     *   scores factored in first. For example, what if we did
     *   max(A)->score(.5)?
     * * A->A depends on anything adding A.
     * * A->(something other than A) depends on anything emitting A. (For
     *   example, we need the A score finalized before we could transfer it to
     *   B using conserveScore().)
     * * A->out() also depends on anything emitting A. Fnode methods aren't
     *   smart enough to lazily run emitter rules as needed. We could make them
     *   so if it was shown to be an advantage.
     */
    prerequisites(ruleset) {
        // Optimization: we could cache the result of this when in a compiled (immutable) ruleset.

        // Extend prereqs with rules derived from each of the given types. If
        // no rules are found, raise an exception, as that indicates a
        // malformed ruleset.
        function extendOrThrow(prereqs, types, ruleGetter, verb) {
            for (let type of types) {
                const rules = ruleGetter(type);
                if (rules.length > 0) {
                    prereqs.extend(rules);
                } else {
                    throw new Error(`No rule ${verb} the "${type}" type, but another rule needs it as input.`);
                }
            }
        }

        const prereqs = new NiceSet();

        // Add finalized types:
        extendOrThrow(prereqs, this._typesFinalized(), type => ruleset.inwardRulesThatCouldEmit(type), 'emits');

        // Add mentioned types:
        // We could say this.lhs.typesMentioned().minus(typesFinalized) as an
        // optimization. But since types mentioned are a superset of types
        // finalized and rules adding are a subset of rules emitting, we get
        // the same result without.
        extendOrThrow(prereqs, this.lhs.typesMentioned(), type => ruleset.inwardRulesThatCouldAdd(type), 'adds');

        return prereqs;
    }

    /**
     * Return the types that this rule finalizes.
     *
     * To "finalize" a type means to make sure we're finished running all
     * possible rules that might change a node's score or notes w.r.t. a given
     * type. This is generally done because we're about to use those data for
     * something, like computing a new type's score or or an aggregate
     * function. Exhaustively, we're about to...
     * * change the type of the nodes or
     * * aggregate all nodes of a type
     *
     * This base-class implementation just returns what aggregate functions
     * need, since that need spans inward and outward rules.
     *
     * @return Set of types
     */
    _typesFinalized() {
        // Get the types that are fed to aggregate functions. Aggregate
        // functions are more demanding than a simple type() LHS. A type() LHS
        // itself does not finalize its nodes because the things it could do to
        // them without changing their type (adding notes, adding to score)
        // are immutable or commutative (respectively). Thus, we require a RHS
        // type change in order to require finalization of a simple type()
        // mention. A max(B), OTOH, is not commutative with other B->B rules
        // (imagine type(B).max()->score(.5)), so it must depend on B emitters
        // and thus finalize B. (This will have to be relaxed or rethought when
        // we do the max()/atMost() optimization. Perhaps we can delegate to
        // aggregate functions up in Rule.prerequisites() to ask what their
        // prereqs are. If they implement such an optimization, they can reply.
        // Otherwise, we can assume they are all the nodes of their type.)
        //
        // TODO: Could arbitrary predicates (once we implement those) matter
        // too? Maybe it's not just aggregations.
        const type = this.lhs.aggregatedType();
        return (type === undefined) ? new NiceSet() : new NiceSet([type]);
    }
}

/**
 * A normal rule, whose results head back into the Fathom knowledgebase, to be
 * operated on by further rules.
 */
class InwardRule extends Rule {
    // TODO: On construct, complain about useless rules, like a dom() rule that
    // doesn't assign a type.

    /**
     * Return an iterable of the fnodes emitted by the RHS of this rule.
     * Side effect: update ruleset's store of fnodes, its accounting of which
     * rules are done executing, and its cache of results per type.
     */
    results(ruleset) {
        if (ruleset.doneRules.has(this)) {  // shouldn't happen
            throw new Error('A bug in Fathom caused results() to be called on an inward rule twice. That could cause redundant score contributions, etc.');
        }
        const self = this;
        // For now, we consider most of what a LHS computes to be cheap, aside
        // from type() and type().max(), which are cached by their specialized
        // LHS subclasses.
        const leftResults = this.lhs.fnodes(ruleset);
        // Avoid returning a single fnode more than once. LHSs uniquify
        // themselves, but the RHS can change the element it's talking
        // about and thus end up with dupes.
        const returnedFnodes = new Set();

        // Merge facts into fnodes:
        forEach(
            // leftResult can be either a fnode or a {fnode, rhsTransformer} pair.
            function updateFnode(leftResult) {
                const leftType = self.lhs.guaranteedType();
                // Get a fnode and a RHS transformer, whether a plain fnode is
                // returned or a {fnode, rhsTransformer} pair:
                const {fnode: leftFnode = leftResult, rhsTransformer = identity} = leftResult;
                // Grab the fact from the RHS, and run the LHS's optional
                // transformer over it to pick up anything special it wants to
                // do:
                const fact = rhsTransformer(self.rhs.fact(leftFnode, leftType));
                self.lhs.checkFact(fact);
                const rightFnode = ruleset.fnodeForElement(fact.element || leftFnode.element);
                // If the RHS doesn't specify a type, default to the
                // type of the LHS, if any:
                const rightType = fact.type || self.lhs.guaranteedType();
                if (fact.score !== undefined) {
                    if (rightType !== undefined) {
                        rightFnode.addScoreFor(rightType, fact.score, self.name);
                    } else {
                        throw new Error(`The right-hand side of a rule specified a score (${fact.score}) with neither an explicit type nor one we could infer from the left-hand side.`);
                    }
                }
                if (fact.type !== undefined || fact.note !== undefined) {
                    // There's a reason to call setNoteFor.
                    if (rightType === undefined) {
                        throw new Error(`The right-hand side of a rule specified a note (${fact.note}) with neither an explicit type nor one we could infer from the left-hand side. Notes are per-type, per-node, so that's a problem.`);
                    } else {
                        rightFnode.setNoteFor(rightType, fact.note);
                    }
                }
                returnedFnodes.add(rightFnode);
            },
            leftResults);

        // Update ruleset lookup tables.
        // First, mark this rule as done:
        ruleset.doneRules.add(this);
        // Then, stick each fnode in typeCache under all applicable types.
        // Optimization: we really only need to loop over the types
        // this rule can possibly add.
        for (let fnode of returnedFnodes) {
            for (let type of fnode.typesSoFar()) {
                setDefault(ruleset.typeCache, type, () => new Set()).add(fnode);
            }
        }
        return returnedFnodes.values();
    }

    /**
     * Return a Set of the types that could be emitted back into the system.
     * To emit a type means to either to add it to a fnode emitted from the RHS
     * or to leave it on such a fnode where it already exists.
     */
    typesItCouldEmit() {
        const rhs = this.rhs.possibleEmissions();
        if (!rhs.couldChangeType && this.lhs.guaranteedType() !== undefined) {
            // It's a b -> b rule.
            return new Set([this.lhs.guaranteedType()]);
        } else if (rhs.possibleTypes.size > 0) {
            // We can prove the type emission from the RHS alone.
            return rhs.possibleTypes;
        } else {
            throw new Error('Could not determine the emitted type of a rule because its right-hand side calls props() without calling typeIn().');
        }
    }

    /**
     * Return a Set of types I could add to fnodes I output (where the fnodes
     * did not already have them).
     */
    typesItCouldAdd() {
        const ret = new Set(this.typesItCouldEmit());
        ret.delete(this.lhs.guaranteedType());
        return ret;
    }

    /**
     * Add the types we could change to the superclass's result.
     */
    _typesFinalized() {
        const self = this;
        function typesThatCouldChange() {
            const ret = new NiceSet();

            // Get types that could change:
            const emissions = self.rhs.possibleEmissions();
            if (emissions.couldChangeType) {
                // Get the possible guaranteed combinations of types on the LHS
                // (taking just this LHS into account). For each combo, if the RHS
                // adds a type that's not in the combo, the types in the combo get
                // unioned into ret.
                for (let combo of self.lhs.possibleTypeCombinations()) {
                    for (let rhsType of emissions.possibleTypes) {
                        if (!combo.has(rhsType)) {
                            ret.extend(combo);
                            break;
                        }
                    }
                }
            }
            // Optimization: the possible combos could be later expanded to be
            // informed by earlier rules which add the types mentioned in the LHS.
            // If the only way for something to get B is to have Q first, then we
            // can add Q to each combo and end up with fewer types finalized. Would
            // this imply the existence of a Q->B->Q cycle and thus be impossible?
            // Think about it. If we do this, we can centralize that logic here,
            // rather than repeating it in all the Lhs subclasses).
            return ret;
        }

        return typesThatCouldChange().extend(super._typesFinalized());
    }
}

/**
 * A rule whose RHS is an out(). This represents a final goal of a ruleset.
 * Its results go out into the world, not inward back into the Fathom
 * knowledgebase.
 */
class OutwardRule extends Rule {
    /**
     * Compute the whole thing, including any .through() and .allThrough().
     * Do not mark me done in ruleset.doneRules; out rules are never marked as
     * done so they can be requested many times without having to cache their
     * (potentially big, since they aren't necessarily fnodes?) results. (We
     * can add caching later if it proves beneficial.)
     */
    results(ruleset) {
        /**
         * From a LHS's ``{fnode, rhsTransform}`` object or plain fnode, pick off just
         * the fnode and return it.
         */
        function justFnode(fnodeOrStruct) {
            return (fnodeOrStruct instanceof Fnode) ? fnodeOrStruct : fnodeOrStruct.fnode;
        }

        return this.rhs.allCallback(map(this.rhs.callback, map(justFnode, this.lhs.fnodes(ruleset))));
    }

    /**
     * @return the key under which the output of this rule will be available
     */
    key() {
        return this.rhs.key;
    }

    /**
     * OutwardRules finalize all types mentioned.
     */
    _typesFinalized() {
        return this.lhs.typesMentioned().extend(super._typesFinalized());
    }
}

/**
 * A shortcut for creating a new :class:`Ruleset`, for symmetry with
 * :func:`rule`
 */
function ruleset(rules, coeffs = [], biases = []) {
    return new Ruleset(rules, coeffs, biases);
}

/**
 * An unbound ruleset. When you bind it by calling :func:`~Ruleset.against()`,
 * the resulting :class:`BoundRuleset` will be immutable.
 */
class Ruleset {
    /**
     * @arg rules {Array} Rules returned from :func:`rule`
     * @arg coeffs {Map} A map of rule names to numerical weights, typically
     *     returned by the :doc:`trainer<training>`. Example:
     *     ``[['someRuleName', 5.04], ...]``. If not given, coefficients
     *     default to 1.
     * @arg biases {object} A map of type names to neural-net biases. These
     *      enable accurate confidence estimates. Example: ``[['someType',
     *      -2.08], ...]``. If absent, biases default to 0.
     */
    constructor(rules, coeffs = [], biases = []) {
        this._inRules = [];
        this._outRules = new Map();  // key -> rule
        this._rulesThatCouldEmit = new Map();  // type -> [rules]
        this._rulesThatCouldAdd = new Map();  // type -> [rules]
        // Private to the framework:
        this._coeffs = new Map(coeffs);  // rule name => coefficient
        this.biases = new Map(biases);  // type name => bias

        // Separate rules into out ones and in ones, and sock them away. We do
        // this here so mistakes raise errors early.
        for (let rule of rules) {
            if (rule instanceof InwardRule) {
                this._inRules.push(rule);

                // Keep track of what inward rules can emit or add:
                // TODO: Combine these hashes for space efficiency:
                const emittedTypes = rule.typesItCouldEmit();
                for (let type of emittedTypes) {
                    setDefault(this._rulesThatCouldEmit, type, () => []).push(rule);
                }
                for (let type of rule.typesItCouldAdd()) {
                    setDefault(this._rulesThatCouldAdd, type, () => []).push(rule);
                }
            } else if (rule instanceof OutwardRule) {
                this._outRules.set(rule.key(), rule);
            } else {
                throw new Error(`This element of ruleset()'s first param wasn't a rule: ${rule}`);
            }
        }
    }

    /**
     * Commit this ruleset to running against a specific DOM tree or subtree.
     *
     * When run against a subtree, the root of the subtree is not considered as
     * a possible match.
     *
     * This doesn't actually modify the Ruleset but rather returns a fresh
     * :class:`BoundRuleset`, which contains caches and other stateful, per-DOM
     * bric-a-brac.
     */
    against(doc) {
        return new BoundRuleset(doc,
                                this._inRules,
                                this._outRules,
                                this._rulesThatCouldEmit,
                                this._rulesThatCouldAdd,
                                this._coeffs,
                                this.biases);
    }

    /**
     * Return all the rules (both inward and outward) that make up this ruleset.
     *
     * From this, you can construct another ruleset like this one but with your
     * own rules added.
     */
    rules() {
        return Array.from([...this._inRules, ...this._outRules.values()]);
    }
}

/**
 * A ruleset that is earmarked to analyze a certain DOM
 *
 * Carries a cache of rule results on that DOM. Typically comes from
 * :meth:`~Ruleset.against`.
 */
class BoundRuleset {
    /**
     * @arg inRules {Array} Non-out() rules
     * @arg outRules {Map} Output key -> out() rule
     */
    constructor(doc, inRules, outRules, rulesThatCouldEmit, rulesThatCouldAdd, coeffs, biases) {
        this.doc = doc;
        this._inRules = inRules;
        this._outRules = outRules;
        this._rulesThatCouldEmit = rulesThatCouldEmit;
        this._rulesThatCouldAdd = rulesThatCouldAdd;
        this._coeffs = coeffs;

        // Private, for the use of only helper classes:
        this.biases = biases;
        this._clearCaches();
        this.elementCache = new WeakMap();  // DOM element => fnode about it
        this.doneRules = new Set();  // InwardRules that have been executed. OutwardRules can be executed more than once because they don't change any fnodes and are thus idempotent.
    }

    /**
     * Change my coefficients and biases after construction.
     *
     * @arg coeffs See the :class:`Ruleset` constructor.
     * @arg biases See the :class:`Ruleset` constructor.
     */
    setCoeffsAndBiases(coeffs, biases = []) {
        // Destructuring assignment doesn't make it through rollup properly
        // (https://github.com/rollup/rollup-plugin-commonjs/issues/358):
        this._coeffs = new Map(coeffs);
        this.biases = new Map(biases);
        this._clearCaches();
    }

    /**
     * Clear the typeCache and maxCache, usually in the wake of changing
     * ``this._coeffs``, because both of thise depend on weighted scores.
     */
    _clearCaches() {
        this.maxCache = new Map();  // type => Array of max fnode (or fnodes, if tied) of this type
        this.typeCache = new Map();  // type => Set of all fnodes of this type found so far. (The dependency resolution during execution ensures that individual types will be comprehensive just in time.)
    }

    /**
     * Return an array of zero or more fnodes.
     * @arg thing {string|Lhs|Node} Can be
     *
     *       (1) A string which matches up with an "out" rule in the ruleset.
     *           If the out rule uses through(), the results of through's
     *           callback (which might not be fnodes) will be returned.
     *       (2) An arbitrary LHS which we calculate and return the results of.
     *       (3) A DOM node, for which we will return the corresponding fnode.
     *
     *     Results are cached for cases (1) and (3).
     */
    get(thing) {
        if (typeof thing === 'string') {
            if (this._outRules.has(thing)) {
                return Array.from(this._execute(this._outRules.get(thing)));
            } else {
                throw new Error(`There is no out() rule with key "${thing}".`);
            }
        } else if (isDomElement(thing)) {
            // Return the fnode and let it run type(foo) on demand, as people
            // ask it things like scoreFor(foo).
            return this.fnodeForElement(thing);
        } else if (thing.asLhs !== undefined) {
            // Make a temporary out rule, and run it. This may add things to
            // the ruleset's cache, but that's fine: it doesn't change any
            // future results; it just might make them faster. For example, if
            // you ask for .get(type('smoo')) twice, the second time will be a
            // cache hit.
            const outRule = rule(thing, out(Symbol('outKey')));
            return Array.from(this._execute(outRule));
        } else {
            throw new Error('ruleset.get() expects a string, an expression like on the left-hand side of a rule, or a DOM node.');
        }
    }

    /**
     * Return the weighted sum of the per-rule, per-type scores from a fnode.
     *
     * @arg mapOfScores a Map of rule name to the [0, 1] score it computed for
     *      the type in question
     */
    weightedScore(mapOfScores) {
        let total = 0;
        for (const [name, score] of mapOfScores) {
            total += score * getDefault(this._coeffs, name, () => 1);
        }
        return total;
    }

    // Provide an opaque context object to be made available to all ranker
    // functions.
    // context (object) {
    //     self.context = object;
    // }

    // -------- Methods below this point are private to the framework. --------

    /**
     * Return all the thus-far-unexecuted rules that will have to run to run
     * the requested rule, in the form of Map(prereq: [rulesItIsNeededBy]).
     */
    _prerequisitesTo(rule, undonePrereqs = new Map()) {
        for (let prereq of rule.prerequisites(this)) {
            if (!this.doneRules.has(prereq)) {
                // prereq is not already run. (If it were, we wouldn't care
                // about adding it to the graph.)
                const alreadyAdded = undonePrereqs.has(prereq);
                setDefault(undonePrereqs, prereq, () => []).push(rule);

                // alreadyAdded means we've already computed the prereqs of
                // this prereq and added them to undonePrereqs. So, now
                // that we've hooked up the rule to this prereq in the
                // graph, we can stop. But, if we haven't, then...
                if (!alreadyAdded) {
                    this._prerequisitesTo(prereq, undonePrereqs);
                }
            }
        }
        return undonePrereqs;
    }

    /**
     * Run the given rule (and its dependencies, in the proper order), and
     * return its results.
     *
     * The caller is responsible for ensuring that _execute() is not called
     * more than once for a given InwardRule, lest non-idempotent
     * transformations, like score contributions, be applied to fnodes more
     * than once.
     *
     * The basic idea is to sort rules in topological order (according to input
     * and output types) and then run them. On top of that, we do some
     * optimizations. We keep a cache of results by type (whether partial or
     * comprehensive--either way, the topology ensures that any
     * non-comprehensive typeCache entry is made comprehensive before another
     * rule needs it). And we prune our search for prerequisite rules at the
     * first encountered already-executed rule.
     */
    _execute(rule) {
        const prereqs = this._prerequisitesTo(rule);
        let sorted;
        try {
            sorted = [rule].concat(toposort(prereqs.keys(),
                                            prereq => prereqs.get(prereq)));
        } catch (exc) {
            if (exc instanceof CycleError) {
                throw new CycleError('There is a cyclic dependency in the ruleset.');
            } else {
                throw exc;
            }
        }
        let fnodes;
        for (let eachRule of reversed(sorted)) {
            // Sock each set of results away in this.typeCache:
            fnodes = eachRule.results(this);
        }
        return Array.from(fnodes);
    }

    /** @return {Rule[]} */
    inwardRulesThatCouldEmit(type) {
        return getDefault(this._rulesThatCouldEmit, type, () => []);
    }

    /** @return {Rule[]} */
    inwardRulesThatCouldAdd(type) {
        return getDefault(this._rulesThatCouldAdd, type, () => []);
    }

    /**
     * @return the Fathom node that describes the given DOM element. This does
     *     not trigger any execution, so the result may be incomplete.
     */
    fnodeForElement(element) {
        return setDefault(this.elementCache,
                          element,
                          () => new Fnode(element, this));
    }
}

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const version = '3.7.3';

export { and, atMost, clusters$1 as clusters, dom, element, exceptions, nearest, note, out, props, rule, ruleset, score, type, typeIn, utilsForFrontend as utils, version };
PK
!<b�L@@,modules/translation/LanguageDetector.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

// workerManager is exported for tests.
import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs";

const WORKER_URL = "resource://gre/modules/translation/cld-worker.js";

/**
 * The length of the substring to pull from the document's text for language
 * identification.
 *
 * This value should ideally be one that is large enough to yield a confident
 * identification result without being too large or expensive to extract.
 *
 * At this time, this value is not driven by statistical data or analysis.
 *
 * For the moment, while we investigate which language identification library
 * we would like to use, keep this logic in sync with language-id-engine.sys.mjs
 */
const DOC_TEXT_TO_IDENTIFY_LENGTH = 1024;

export var workerManager = {
  // Since Emscripten can handle heap growth, but not heap shrinkage, we
  // need to refresh the worker after we've processed a particularly large
  // string in order to prevent unnecessary resident memory growth.
  //
  // These values define the cut-off string length and the idle timeout
  // (in milliseconds) before destroying a worker. Once a string of the
  // maximum size has been processed, the worker is marked for
  // destruction, and is terminated as soon as it has been idle for the
  // given timeout.
  //
  // 1.5MB. This is the approximate string length that forces heap growth
  // for a 2MB heap.
  LARGE_STRING: 1.5 * 1024 * 1024,
  IDLE_TIMEOUT: 10 * 1000,

  detectionQueue: [],

  detectLanguage(aParams) {
    return this.workerReady
      .then(worker => {
        return new Promise(resolve => {
          this.detectionQueue.push({ resolve });
          worker.postMessage(aParams);
        });
      })
      .then(result => {
        // We have our asynchronous result from the worker.
        //
        // Determine if our input was large enough to trigger heap growth,
        // or if we're already waiting to destroy the worker when it's
        // idle. If so, schedule termination after the idle timeout.
        if (
          aParams.text.length >= this.LARGE_STRING ||
          this._idleTimeout != null
        ) {
          this.flushWorker();
        }

        return result;
      });
  },

  _worker: null,
  _workerReadyPromise: null,

  get workerReady() {
    if (!this._workerReadyPromise) {
      this._workerReadyPromise = new Promise(resolve => {
        let worker = new Worker(WORKER_URL);
        worker.onmessage = aMsg => {
          if (aMsg.data == "ready") {
            resolve(worker);
          } else {
            this.detectionQueue.shift().resolve(aMsg.data);
          }
        };
        this._worker = worker;
      });
    }

    return this._workerReadyPromise;
  },

  // Holds the ID of the current pending idle cleanup setTimeout.
  _idleTimeout: null,

  // Schedule the current worker to be terminated after the idle timeout.
  flushWorker() {
    if (this._idleTimeout != null) {
      clearTimeout(this._idleTimeout);
    }

    this._idleTimeout = setTimeout(
      this._flushWorker.bind(this),
      this.IDLE_TIMEOUT
    );
  },

  // Immediately terminate the worker, as long as there no pending
  // results. Otherwise, reschedule termination until after the next
  // idle timeout.
  _flushWorker() {
    if (this.detectionQueue.length) {
      this.flushWorker();
    } else {
      if (this._worker) {
        this._worker.terminate();
      }

      this._worker = null;
      this._workerReadyPromise = null;
      this._idleTimeout = null;
    }
  },
};

export var LanguageDetector = {
  /**
   * Detect the language of a given string.
   *
   * The argument may be either a string containing the text to analyze,
   * or an object with the following properties:
   *
   *  - 'text' The text to analyze.
   *
   *  - 'isHTML' (optional) A boolean, indicating whether the text
   *      should be analyzed as HTML rather than plain text.
   *
   *  - 'language' (optional) A string indicating the expected language.
   *      For text extracted from HTTP documents, this is expected to
   *      come from the Content-Language header.
   *
   *  - 'tld' (optional) A string indicating the top-level domain of the
   *      document the text was extracted from.
   *
   *  - 'encoding' (optional) A string describing the encoding of the
   *      document the string was extracted from. Note that, regardless
   *      of the value of this property, the 'text' property must be a
   *      UTF-16 JavaScript string.
   *
   * @returns {Promise<Object>}
   * @resolves When detection is finished, with a object containing
   * these fields:
   *  - 'language' (string with a language code)
   *  - 'confident' (boolean) Whether the detector is confident of the
   *      result.
   *  - 'languages' (array) An array of up to three elements, containing
   *      the most prevalent languages detected. It contains a
   *      'languageCode' property, containing the ISO language code of
   *      the language, and a 'percent' property, describing the
   *      approximate percentage of the input which is in that language.
   *      For text of an unknown language, the result may contain an
   *      entry with the languge code 'un', indicating the percent of
   *      the text which is unknown.
   */
  detectLanguage(aParams) {
    if (typeof aParams == "string") {
      aParams = { text: aParams };
    }

    return workerManager.detectLanguage(aParams);
  },

  /**
   * Attempts to determine the language in which the document's content is written.
   *
   * For the moment, while we investigate which language identification library
   * we would like to use, keep this logic in sync with language-id-engine.sys.mjs
   * @returns {string | null}
   */
  async detectLanguageFromDocument(aDocument) {
    // Grab a selection of text.
    let encoder = Cu.createDocumentEncoder("text/plain");
    encoder.init(aDocument, "text/plain", encoder.SkipInvisibleContent);
    let text = encoder
      .encodeToStringWithMaxLength(DOC_TEXT_TO_IDENTIFY_LENGTH)
      .replaceAll("\r", "")
      .replaceAll("\n", " ");

    const { language, confident } = await workerManager.detectLanguage({
      text,
    });

    workerManager.flushWorker();

    return confident ? language : null;
  },
};
PK
!<
�=A�_�_!modules/translation/cld-worker.js'use strict';var c;c||(c=eval("(function() { try { return Module || {} } catch(e) { return {} } })()"));var aa={},g;for(g in c)c.hasOwnProperty(g)&&(aa[g]=c[g]);var ba=!1,k=!1,m=!1,ca=!1;
if(c.ENVIRONMENT)if("WEB"===c.ENVIRONMENT)ba=!0;else if("WORKER"===c.ENVIRONMENT)k=!0;else if("NODE"===c.ENVIRONMENT)m=!0;else if("SHELL"===c.ENVIRONMENT)ca=!0;else throw Error("The provided Module['ENVIRONMENT'] value is not valid. It must be one of: WEB|WORKER|NODE|SHELL.");else ba="object"===typeof window,k="function"===typeof importScripts,m="object"===typeof process&&"function"===typeof require&&!ba&&!k,ca=!ba&&!m&&!k;
if(m){c.print||(c.print=console.log);c.printErr||(c.printErr=console.warn);var da,ea;c.read=function(a,b){da||(da=require("fs"));ea||(ea=require("path"));a=ea.normalize(a);var d=da.readFileSync(a);d||a==ea.resolve(a)||(a=path.join(__dirname,"..","src",a),d=da.readFileSync(a));d&&!b&&(d=d.toString());return d};c.readBinary=function(a){a=c.read(a,!0);a.buffer||(a=new Uint8Array(a));assert(a.buffer);return a};c.load=function(a){fa(read(a))};c.thisProgram||(c.thisProgram=1<process.argv.length?process.argv[1].replace(/\\/g,
"/"):"unknown-program");c.arguments=process.argv.slice(2);"undefined"!==typeof module&&(module.exports=c);process.on("uncaughtException",function(a){if(!(a instanceof n))throw a;});c.inspect=function(){return"[Emscripten Module object]"}}else if(ca)c.print||(c.print=print),"undefined"!=typeof printErr&&(c.printErr=printErr),c.read="undefined"!=typeof read?read:function(){throw"no read() available (jsc?)";},c.readBinary=function(a){if("function"===typeof readbuffer)return new Uint8Array(readbuffer(a));
a=read(a,"binary");assert("object"===typeof a);return a},"undefined"!=typeof scriptArgs?c.arguments=scriptArgs:"undefined"!=typeof arguments&&(c.arguments=arguments),eval("if (typeof gc === 'function' && gc.toString().indexOf('[native code]') > 0) var gc = undefined");else if(ba||k)c.read=function(a){var b=new XMLHttpRequest;b.open("GET",a,!1);b.send(null);return b.responseText},c.readAsync=function(a,b,d){var e=new XMLHttpRequest;e.open("GET",a,!0);e.responseType="arraybuffer";e.onload=function(){200==
e.status||0==e.status&&e.response?b(e.response):d()};e.onerror=d;e.send(null)},"undefined"!=typeof arguments&&(c.arguments=arguments),"undefined"!==typeof console?(c.print||(c.print=function(a){console.log(a)}),c.printErr||(c.printErr=function(a){console.warn(a)})):c.print||(c.print=function(){}),k&&(c.load=importScripts),"undefined"===typeof c.setWindowTitle&&(c.setWindowTitle=function(a){document.title=a});else throw"Unknown runtime environment. Where are we?";function fa(a){eval.call(null,a)}
!c.load&&c.read&&(c.load=function(a){fa(c.read(a))});c.print||(c.print=function(){});c.printErr||(c.printErr=c.print);c.arguments||(c.arguments=[]);c.thisProgram||(c.thisProgram="./this.program");c.print=c.print;c.u=c.printErr;c.preRun=[];c.postRun=[];for(g in aa)aa.hasOwnProperty(g)&&(c[g]=aa[g]);
var aa=void 0,t={V:function(a){tempRet0=a},R:function(){return tempRet0},w:function(){return p},o:function(a){p=a},H:function(a){switch(a){case "i1":case "i8":return 1;case "i16":return 2;case "i32":return 4;case "i64":return 8;case "float":return 4;case "double":return 8;default:return"*"===a[a.length-1]?t.q:"i"===a[0]?(a=parseInt(a.substr(1)),assert(0===a%8),a/8):0}},O:function(a){return Math.max(t.H(a),t.q)},W:16,la:function(a,b){"double"===b||"i64"===b?a&7&&(assert(4===(a&7)),a+=4):assert(0===
(a&3));return a},ea:function(a,b,d){return d||"i64"!=a&&"double"!=a?a?Math.min(b||(a?t.O(a):0),t.q):Math.min(b,8):8},h:function(a,b,d){return d&&d.length?(d.splice||(d=Array.prototype.slice.call(d)),d.splice(0,0,b),c["dynCall_"+a].apply(null,d)):c["dynCall_"+a].call(null,b)},l:[],K:function(a){for(var b=0;b<t.l.length;b++)if(!t.l[b])return t.l[b]=a,2*(1+b);throw"Finished up all reserved function pointers. Use a higher value for RESERVED_FUNCTION_POINTERS.";},U:function(a){t.l[(a-2)/2]=null},k:function(a){t.k.v||
(t.k.v={});t.k.v[a]||(t.k.v[a]=1,c.u(a))},s:{},ga:function(a,b){assert(b);t.s[b]||(t.s[b]={});var d=t.s[b];d[a]||(d[a]=function(){return t.h(b,a,arguments)});return d[a]},fa:function(){throw"You must build with -s RETAIN_COMPILER_SETTINGS=1 for Runtime.getCompilerSetting or emscripten_get_compiler_setting to work";},n:function(a){var b=p;p=p+a|0;p=p+15&-16;return b},A:function(a){var b=u;u=u+a|0;u=u+15&-16;return b},d:function(a){var b=v;v=v+a|0;v=v+15&-16;return v>=w&&!ga()?(v=b,0):b},F:function(a,
b){return Math.ceil(a/(b?b:16))*(b?b:16)},ka:function(a,b,d){return d?+(a>>>0)+4294967296*+(b>>>0):+(a>>>0)+4294967296*+(b|0)},C:8,q:4,X:0};c.Runtime=t;t.addFunction=t.K;t.removeFunction=t.U;var ia=!1;function assert(a,b){a||y("Assertion failed: "+b)}function ja(a){var b=c["_"+a];if(!b)try{b=eval("_"+a)}catch(d){}assert(b,"Cannot call unknown function "+a+" (perhaps LLVM optimizations or closure removed it?)");return b}var ka,la;
(function(){function a(a){a=a.toString().match(f).slice(1);return{arguments:a[0],body:a[1],returnValue:a[2]}}function b(){if(!l){l={};for(var b in d)d.hasOwnProperty(b)&&(l[b]=a(d[b]))}}var d={stackSave:function(){t.w()},stackRestore:function(){t.o()},arrayToC:function(a){var b=t.n(a.length);ma(a,b);return b},stringToC:function(a){var b=0;null!==a&&void 0!==a&&0!==a&&(b=t.n((a.length<<2)+1),na(a,b));return b}},e={string:d.stringToC,array:d.arrayToC};la=function(a,b,d,f,l){a=ja(a);var O=[],P=0;if(f)for(var x=
0;x<f.length;x++){var ha=e[d[x]];ha?(0===P&&(P=t.w()),O[x]=ha(f[x])):O[x]=f[x]}d=a.apply(null,O);"string"===b&&(d=z(d));if(0!==P){if(l&&l.async){EmterpreterAsync.Y.push(function(){t.o(P)});return}t.o(P)}return d};var f=/^function\s*[a-zA-Z$_0-9]*\s*\(([^)]*)\)\s*{\s*([^*]*?)[\s;]*(?:return\s*(.*?)[;\s]*)?}$/,l=null;ka=function(d,e,f){f=f||[];var A=ja(d);d=f.every(function(a){return"number"===a});var X="string"!==e;if(X&&d)return A;var O=f.map(function(a,b){return"$"+b});e="(function("+O.join(",")+
") {";var P=f.length;if(!d){b();e+="var stack = "+l.stackSave.body+";";for(var x=0;x<P;x++){var ha=O[x],Y=f[x];"number"!==Y&&(Y=l[Y+"ToC"],e+="var "+Y.arguments+" = "+ha+";",e+=Y.body+";",e+=ha+"=("+Y.returnValue+");")}}f=a(function(){return A}).returnValue;e+="var ret = "+f+"("+O.join(",")+");";X||(f=a(function(){return z}).returnValue,e+="ret = "+f+"(ret);");d||(b(),e+=l.stackRestore.body.replace("()","(stack)")+";");return eval(e+"return ret})")}})();c.ccall=la;c.cwrap=ka;
function oa(a,b,d){d=d||"i8";"*"===d.charAt(d.length-1)&&(d="i32");switch(d){case "i1":B[a>>0]=b;break;case "i8":B[a>>0]=b;break;case "i16":pa[a>>1]=b;break;case "i32":C[a>>2]=b;break;case "i64":tempI64=[b>>>0,(tempDouble=b,1<=+qa(tempDouble)?0<tempDouble?(ra(+sa(tempDouble/4294967296),4294967295)|0)>>>0:~~+ta((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)];C[a>>2]=tempI64[0];C[a+4>>2]=tempI64[1];break;case "float":ua[a>>2]=b;break;case "double":va[a>>3]=b;break;default:y("invalid type for setValue: "+
d)}}c.setValue=oa;function wa(a,b){b=b||"i8";"*"===b.charAt(b.length-1)&&(b="i32");switch(b){case "i1":return B[a>>0];case "i8":return B[a>>0];case "i16":return pa[a>>1];case "i32":return C[a>>2];case "i64":return C[a>>2];case "float":return ua[a>>2];case "double":return va[a>>3];default:y("invalid type for setValue: "+b)}return null}c.getValue=wa;c.ALLOC_NORMAL=0;c.ALLOC_STACK=1;c.ALLOC_STATIC=2;c.ALLOC_DYNAMIC=3;c.ALLOC_NONE=4;
function xa(a,b,d,e){var f,l;"number"===typeof a?(f=!0,l=a):(f=!1,l=a.length);var h="string"===typeof b?b:null;d=4==d?e:["function"===typeof D?D:t.A,t.n,t.A,t.d][void 0===d?2:d](Math.max(l,h?1:b.length));if(f){e=d;assert(0==(d&3));for(a=d+(l&-4);e<a;e+=4)C[e>>2]=0;for(a=d+l;e<a;)B[e++>>0]=0;return d}if("i8"===h)return a.subarray||a.slice?E.set(a,d):E.set(new Uint8Array(a),d),d;e=0;for(var q,r;e<l;){var A=a[e];"function"===typeof A&&(A=t.ha(A));f=h||b[e];0===f?e++:("i64"==f&&(f="i32"),oa(d+e,A,f),
r!==f&&(q=t.H(f),r=f),e+=q)}return d}c.allocate=xa;c.getMemory=function(a){return ya?"undefined"!==typeof F&&!F.b||!za?t.d(a):D(a):t.A(a)};function z(a,b){if(0===b||!a)return"";for(var d=0,e,f=0;;){e=E[a+f>>0];d|=e;if(0==e&&!b)break;f++;if(b&&f==b)break}b||(b=f);e="";if(128>d){for(;0<b;)d=String.fromCharCode.apply(String,E.subarray(a,a+Math.min(b,1024))),e=e?e+d:d,a+=1024,b-=1024;return e}return c.UTF8ToString(a)}c.Pointer_stringify=z;
c.AsciiToString=function(a){for(var b="";;){var d=B[a++>>0];if(!d)return b;b+=String.fromCharCode(d)}};c.stringToAscii=function(a,b){return Aa(a,b,!1)};
function Ba(a,b){for(var d,e,f,l,h,q,r="";;){d=a[b++];if(!d)return r;d&128?(e=a[b++]&63,192==(d&224)?r+=String.fromCharCode((d&31)<<6|e):(f=a[b++]&63,224==(d&240)?d=(d&15)<<12|e<<6|f:(l=a[b++]&63,240==(d&248)?d=(d&7)<<18|e<<12|f<<6|l:(h=a[b++]&63,248==(d&252)?d=(d&3)<<24|e<<18|f<<12|l<<6|h:(q=a[b++]&63,d=(d&1)<<30|e<<24|f<<18|l<<12|h<<6|q))),65536>d?r+=String.fromCharCode(d):(d-=65536,r+=String.fromCharCode(55296|d>>10,56320|d&1023)))):r+=String.fromCharCode(d)}}c.UTF8ArrayToString=Ba;
c.UTF8ToString=function(a){return Ba(E,a)};
function Ca(a,b,d,e){if(!(0<e))return 0;var f=d;e=d+e-1;for(var l=0;l<a.length;++l){var h=a.charCodeAt(l);55296<=h&&57343>=h&&(h=65536+((h&1023)<<10)|a.charCodeAt(++l)&1023);if(127>=h){if(d>=e)break;b[d++]=h}else{if(2047>=h){if(d+1>=e)break;b[d++]=192|h>>6}else{if(65535>=h){if(d+2>=e)break;b[d++]=224|h>>12}else{if(2097151>=h){if(d+3>=e)break;b[d++]=240|h>>18}else{if(67108863>=h){if(d+4>=e)break;b[d++]=248|h>>24}else{if(d+5>=e)break;b[d++]=252|h>>30;b[d++]=128|h>>24&63}b[d++]=128|h>>18&63}b[d++]=128|
h>>12&63}b[d++]=128|h>>6&63}b[d++]=128|h&63}}b[d]=0;return d-f}c.stringToUTF8Array=Ca;c.stringToUTF8=function(a,b,d){return Ca(a,E,b,d)};function Da(a){for(var b=0,d=0;d<a.length;++d){var e=a.charCodeAt(d);55296<=e&&57343>=e&&(e=65536+((e&1023)<<10)|a.charCodeAt(++d)&1023);127>=e?++b:b=2047>=e?b+2:65535>=e?b+3:2097151>=e?b+4:67108863>=e?b+5:b+6}return b}c.lengthBytesUTF8=Da;
function Ea(){return Fa().replace(/__Z[\w\d_]+/g,function(a){var b;a:{if(c.___cxa_demangle)try{var d=D(a.length);na(a.substr(1),d);var e=D(4),f=c.___cxa_demangle(d,0,0,e);if(0===wa(e,"i32")&&f){b=z(f);break a}}catch(l){b=a;break a}finally{d&&Ga(d),e&&Ga(e),f&&Ga(f)}t.k("warning: build with  -s DEMANGLE_SUPPORT=1  to link in libcxxabi demangling");b=a}return a===b?a:a+" ["+b+"]"})}
function Fa(){var a=Error();if(!a.stack){try{throw Error(0);}catch(b){a=b}if(!a.stack)return"(no stack trace available)"}return a.stack.toString()}c.stackTrace=function(){return Ea()};function Ha(a){0<a%4096&&(a+=4096-a%4096);return a}var buffer,B,E,pa,Ia,C,Ja,ua,va;
function Ka(){c.HEAP8=B=new Int8Array(buffer);c.HEAP16=pa=new Int16Array(buffer);c.HEAP32=C=new Int32Array(buffer);c.HEAPU8=E=new Uint8Array(buffer);c.HEAPU16=Ia=new Uint16Array(buffer);c.HEAPU32=Ja=new Uint32Array(buffer);c.HEAPF32=ua=new Float32Array(buffer);c.HEAPF64=va=new Float64Array(buffer)}var La=0,u=0,ya=!1,Ma=0,p=0,Na=0,v=0;
c.reallocBuffer||(c.reallocBuffer=function(a){var b;try{if(ArrayBuffer.b)b=ArrayBuffer.b(buffer,a);else{var d=B;b=new ArrayBuffer(a);(new Int8Array(b)).set(d)}}catch(e){return!1}return Oa(b)?b:!1});function ga(){var a=Math.pow(2,31);if(v>=a)return!1;for(;w<=v;)if(w<a/2)w=Ha(2*w);else{var b=w;w=Ha((3*w+a)/4);if(w<=b)return!1}w=Math.max(w,16777216);if(w>=a)return!1;a=c.reallocBuffer(w);if(!a)return!1;c.buffer=buffer=a;Ka();return!0}var Pa;
try{Pa=Function.prototype.call.bind(Object.getOwnPropertyDescriptor(ArrayBuffer.prototype,"byteLength").get),Pa(new ArrayBuffer(4))}catch(Qa){Pa=function(a){return a.byteLength}}for(var Ra=c.TOTAL_STACK||8192,w=c.TOTAL_MEMORY||2097152,G=65536;G<w||G<2*Ra;)G=16777216>G?2*G:G+16777216;G=Math.max(G,16777216);G!==w&&(w=G);c.buffer?buffer=c.buffer:buffer=new ArrayBuffer(w);Ka();C[0]=255;if(255!==E[0]||0!==E[3])throw"Typed arrays 2 must be run on a little-endian system";c.HEAP=void 0;c.buffer=buffer;
c.HEAP8=B;c.HEAP16=pa;c.HEAP32=C;c.HEAPU8=E;c.HEAPU16=Ia;c.HEAPU32=Ja;c.HEAPF32=ua;c.HEAPF64=va;function H(a){for(;0<a.length;){var b=a.shift();if("function"==typeof b)b();else{var d=b.da;"number"===typeof d?void 0===b.r?t.h("v",d):t.h("vi",d,[b.r]):d(void 0===b.r?null:b.r)}}}var Sa=[],Ta=[],Ua=[],I=[],Va=[],za=!1;function Wa(a){Sa.unshift(a)}c.addOnPreRun=Wa;c.addOnInit=function(a){Ta.unshift(a)};function Xa(a){Ua.unshift(a)}c.addOnPreMain=Xa;c.addOnExit=function(a){I.unshift(a)};
function Ya(a){Va.unshift(a)}c.addOnPostRun=Ya;function Za(a,b,d){d=Array(0<d?d:Da(a)+1);a=Ca(a,d,0,d.length);b&&(d.length=a);return d}c.intArrayFromString=Za;c.intArrayToString=function(a){for(var b=[],d=0;d<a.length;d++){var e=a[d];255<e&&(e&=255);b.push(String.fromCharCode(e))}return b.join("")};function na(a,b,d){a=Za(a,d);for(d=0;d<a.length;)B[b+d>>0]=a[d],d+=1}c.writeStringToMemory=na;function ma(a,b){for(var d=0;d<a.length;d++)B[b++>>0]=a[d]}c.writeArrayToMemory=ma;
function Aa(a,b,d){for(var e=0;e<a.length;++e)B[b++>>0]=a.charCodeAt(e);d||(B[b>>0]=0)}c.writeAsciiToMemory=Aa;Math.imul&&-5===Math.imul(4294967295,5)||(Math.imul=function(a,b){var d=a&65535,e=b&65535;return d*e+((a>>>16)*e+d*(b>>>16)<<16)|0});Math.ia=Math.imul;Math.clz32||(Math.clz32=function(a){a=a>>>0;for(var b=0;32>b;b++)if(a&1<<31-b)return b;return 32});Math.$=Math.clz32;var qa=Math.abs,ta=Math.ceil,sa=Math.floor,ra=Math.min,J=0,$a=null,ab=null;
function bb(){J++;c.monitorRunDependencies&&c.monitorRunDependencies(J)}c.addRunDependency=bb;function cb(){J--;c.monitorRunDependencies&&c.monitorRunDependencies(J);if(0==J&&(null!==$a&&(clearInterval($a),$a=null),ab)){var a=ab;ab=null;a()}}c.removeRunDependency=cb;c.preloadedImages={};c.preloadedAudios={};var K=null,db=[function(a,b){throw"Array index "+a+" out of bounds: [0,"+b+")";}],La=8,u=La+1097872;Ta.push();var K="cld-worker.js.mem",eb=u,u=u+16;c._i64Add=fb;c._i64Subtract=gb;
function hb(a){c.___errno_location&&(C[c.___errno_location()>>2]=a);return a}function ib(){return!!ib.b}var jb=0,kb=[],L={};function lb(a){if(!a||L[a])return a;for(var b in L)if(L[b].D===a)return b;return a}
function mb(){var a=jb;if(!a)return(M.setTempRet0(0),0)|0;var b=L[a],d=b.type;if(!d)return(M.setTempRet0(0),a)|0;var e=Array.prototype.slice.call(arguments);c.___cxa_is_pointer_type(d);mb.buffer||(mb.buffer=D(4));C[mb.buffer>>2]=a;for(var a=mb.buffer,f=0;f<e.length;f++)if(e[f]&&c.___cxa_can_catch(e[f],d,a))return a=C[a>>2],b.D=a,(M.setTempRet0(e[f]),a)|0;a=C[a>>2];return(M.setTempRet0(d),a)|0}c._memset=nb;function ob(a,b){I.push(function(){t.h("vi",a,[b])});ob.level=I.length}c._bitshift64Lshr=pb;
c._bitshift64Shl=qb;function rb(a,b){rb.b||(rb.b={});a in rb.b||(t.h("v",b),rb.b[a]=1)}c._memcpy=sb;var tb=0;function N(){tb+=4;return C[tb-4>>2]}var ub={},vb={};function F(a){F.b||(v=Ha(v),F.b=!0,assert(t.d),F.f=t.d,t.d=function(){y("cannot dynamically allocate, sbrk now has control")});var b=v;return 0==a||F.f(a)?b:4294967295}c._memmove=wb;var xb=1;
function Q(a,b){tb=b;try{var d=N(),e=N(),f=N(),l=0;Q.buffer||(Q.b=[null,[],[]],Q.g=function(a,b){var d=Q.b[a];assert(d);0===b||10===b?((1===a?c.print:c.printErr)(Ba(d,0)),d.length=0):d.push(b)});for(var h=0;h<f;h++){for(var q=C[e+8*h>>2],r=C[e+(8*h+4)>>2],A=0;A<r;A++)Q.g(d,E[q+A]);l+=r}return l}catch(X){return"undefined"!==typeof FS&&X instanceof FS.B||y(X),-X.G}}function D(a){return t.d(a+8)+8&4294967288}c._malloc=D;
I.push(function(){var a=c._fflush;a&&a(0);if(a=Q.g){var b=Q.b;b[1].length&&a(1,10);b[2].length&&a(2,10)}});
var Ma=p=t.F(u),ya=!0,Na=Ma+Ra,v=t.F(Na),yb=xa([8,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,6,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,7,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,6,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0,5,0,1,
0,2,0,1,0,3,0,1,0,2,0,1,0,4,0,1,0,2,0,1,0,3,0,1,0,2,0,1,0],"i8",3);c.L={Math:Math,Int8Array:Int8Array,Int16Array:Int16Array,Int32Array:Int32Array,Uint8Array:Uint8Array,Uint16Array:Uint16Array,Uint32Array:Uint32Array,Float32Array:Float32Array,Float64Array:Float64Array,NaN:NaN,Infinity:Infinity,byteLength:Pa};
c.M={abort:y,assert:assert,invoke_iiii:function(a,b,d,e){try{return c.dynCall_iiii(a,b,d,e)}catch(f){if("number"!==typeof f&&"longjmp"!==f)throw f;M.setThrew(1,0)}},invoke_viiiii:function(a,b,d,e,f,l){try{c.dynCall_viiiii(a,b,d,e,f,l)}catch(h){if("number"!==typeof h&&"longjmp"!==h)throw h;M.setThrew(1,0)}},invoke_vi:function(a,b){try{c.dynCall_vi(a,b)}catch(d){if("number"!==typeof d&&"longjmp"!==d)throw d;M.setThrew(1,0)}},invoke_ii:function(a,b){try{return c.dynCall_ii(a,b)}catch(d){if("number"!==
typeof d&&"longjmp"!==d)throw d;M.setThrew(1,0)}},invoke_v:function(a){try{c.dynCall_v(a)}catch(b){if("number"!==typeof b&&"longjmp"!==b)throw b;M.setThrew(1,0)}},invoke_viiiiii:function(a,b,d,e,f,l,h){try{c.dynCall_viiiiii(a,b,d,e,f,l,h)}catch(q){if("number"!==typeof q&&"longjmp"!==q)throw q;M.setThrew(1,0)}},invoke_viiii:function(a,b,d,e,f){try{c.dynCall_viiii(a,b,d,e,f)}catch(l){if("number"!==typeof l&&"longjmp"!==l)throw l;M.setThrew(1,0)}},_pthread_cleanup_pop:function(){assert(ob.level==I.length,
"cannot pop if something else added meanwhile!");I.pop();ob.level=I.length},___syscall6:function(a,b){tb=b;try{var d=ub.Q();FS.close(d);return 0}catch(e){return"undefined"!==typeof FS&&e instanceof FS.B||y(e),-e.G}},___gxx_personality_v0:function(){},___assert_fail:function(a,b,d,e){ia=!0;throw"Assertion failed: "+z(a)+", at: "+[b?z(b):"unknown filename",d,e?z(e):"unknown function"]+" at "+Ea();},___cxa_allocate_exception:function(a){return D(a)},___cxa_find_matching_catch:mb,___setErrNo:hb,_sbrk:F,
___cxa_begin_catch:function(a){ib.b--;kb.push(a);var b=lb(a);b&&L[b].I++;return a},_emscripten_memcpy_big:function(a,b,d){E.set(E.subarray(b,b+d),a);return a},___resumeException:function(a){jb||(jb=a);var b=lb(a);b&&(L[b].I=0);throw a+" - Exception catching is disabled, this exception cannot be caught. Compile with -s DISABLE_EXCEPTION_CATCHING=0 or DISABLE_EXCEPTION_CATCHING=2 to catch.";},__ZSt18uncaught_exceptionv:ib,_sysconf:function(a){switch(a){case 30:return 4096;case 85:return G/4096;case 132:case 133:case 12:case 137:case 138:case 15:case 235:case 16:case 17:case 18:case 19:case 20:case 149:case 13:case 10:case 236:case 153:case 9:case 21:case 22:case 159:case 154:case 14:case 77:case 78:case 139:case 80:case 81:case 82:case 68:case 67:case 164:case 11:case 29:case 47:case 48:case 95:case 52:case 51:case 46:return 200809;
case 79:return 0;case 27:case 246:case 127:case 128:case 23:case 24:case 160:case 161:case 181:case 182:case 242:case 183:case 184:case 243:case 244:case 245:case 165:case 178:case 179:case 49:case 50:case 168:case 169:case 175:case 170:case 171:case 172:case 97:case 76:case 32:case 173:case 35:return-1;case 176:case 177:case 7:case 155:case 8:case 157:case 125:case 126:case 92:case 93:case 129:case 130:case 131:case 94:case 91:return 1;case 74:case 60:case 69:case 70:case 4:return 1024;case 31:case 42:case 72:return 32;
case 87:case 26:case 33:return 2147483647;case 34:case 1:return 47839;case 38:case 36:return 99;case 43:case 37:return 2048;case 0:return 2097152;case 3:return 65536;case 28:return 32768;case 44:return 32767;case 75:return 16384;case 39:return 1E3;case 89:return 700;case 71:return 256;case 40:return 255;case 2:return 100;case 180:return 64;case 25:return 20;case 5:return 16;case 6:return 6;case 73:return 4;case 84:return"object"===typeof navigator?navigator.hardwareConcurrency||1:1}hb(22);return-1},
_pthread_getspecific:function(a){return vb[a]||0},_pthread_self:function(){return 0},_pthread_once:rb,_pthread_key_create:function(a){if(0==a)return 22;C[a>>2]=xb;vb[xb]=0;xb++;return 0},_emscripten_asm_const_iii:function(a,b,d){return db[a](b,d)},_pthread_setspecific:function(a,b){if(!(a in vb))return 22;vb[a]=b;return 0},___cxa_throw:function(a,b,d){L[a]={a:a,D:a,type:b,aa:d,I:0};jb=a;"uncaught_exception"in ib?ib.b++:ib.b=1;throw a+" - Exception catching is disabled, this exception cannot be caught. Compile with -s DISABLE_EXCEPTION_CATCHING=0 or DISABLE_EXCEPTION_CATCHING=2 to catch.";
},_abort:function(){c.abort()},_pthread_cleanup_push:ob,_time:function(a){var b=Date.now()/1E3|0;a&&(C[a>>2]=b);return b},___syscall140:function(a,b){tb=b;try{var d=ub.Q(),e=N(),f=N(),l=N(),h=N();assert(0===e);FS.ja(d,f,h);C[l>>2]=d.position;d.T&&0===f&&0===h&&(d.T=null);return 0}catch(q){return"undefined"!==typeof FS&&q instanceof FS.B||y(q),-q.G}},___syscall146:Q,STACKTOP:p,STACK_MAX:Na,tempDoublePtr:eb,ABORT:ia,cttz_i8:yb};// EMSCRIPTEN_START_ASM

var M=(function(global,env,buffer) {
"almost asm";var a=global.Int8Array;var b=global.Int16Array;var c=global.Int32Array;var d=global.Uint8Array;var e=global.Uint16Array;var f=global.Uint32Array;var g=global.Float32Array;var h=global.Float64Array;var i=new a(buffer);var j=new b(buffer);var k=new c(buffer);var l=new d(buffer);var m=new e(buffer);var n=new f(buffer);var o=new g(buffer);var p=new h(buffer);var q=global.byteLength;var r=env.STACKTOP|0;var s=env.STACK_MAX|0;var t=env.tempDoublePtr|0;var u=env.ABORT|0;var v=env.cttz_i8|0;var w=0;var x=0;var y=0;var z=0;var A=global.NaN,B=global.Infinity;var C=0,D=0,E=0,F=0,G=0.0,H=0,I=0,J=0,K=0.0;var L=0;var M=0;var N=0;var O=0;var P=0;var Q=0;var R=0;var S=0;var T=0;var U=0;var V=global.Math.floor;var W=global.Math.abs;var X=global.Math.sqrt;var Y=global.Math.pow;var Z=global.Math.cos;var _=global.Math.sin;var $=global.Math.tan;var aa=global.Math.acos;var ba=global.Math.asin;var ca=global.Math.atan;var da=global.Math.atan2;var ea=global.Math.exp;var fa=global.Math.log;var ga=global.Math.ceil;var ha=global.Math.imul;var ia=global.Math.min;var ja=global.Math.clz32;var ka=env.abort;var la=env.assert;var ma=env.invoke_iiii;var na=env.invoke_viiiii;var oa=env.invoke_vi;var pa=env.invoke_ii;var qa=env.invoke_v;var ra=env.invoke_viiiiii;var sa=env.invoke_viiii;var ta=env._pthread_cleanup_pop;var ua=env.___syscall6;var va=env.___gxx_personality_v0;var wa=env.___assert_fail;var xa=env.___cxa_allocate_exception;var ya=env.___cxa_find_matching_catch;var za=env.___setErrNo;var Aa=env._sbrk;var Ba=env.___cxa_begin_catch;var Ca=env._emscripten_memcpy_big;var Da=env.___resumeException;var Ea=env.__ZSt18uncaught_exceptionv;var Fa=env._sysconf;var Ga=env._pthread_getspecific;var Ha=env._pthread_self;var Ia=env._pthread_once;var Ja=env._pthread_key_create;var Ka=env._emscripten_asm_const_iii;var La=env._pthread_setspecific;var Ma=env.___cxa_throw;var Na=env._abort;var Oa=env._pthread_cleanup_push;var Pa=env._time;var Qa=env.___syscall140;var Ra=env.___syscall146;var Sa=0.0;function Ta(newBuffer){if(q(newBuffer)&16777215||q(newBuffer)<=16777215||q(newBuffer)>2147483648)return false;i=new a(newBuffer);j=new b(newBuffer);k=new c(newBuffer);l=new d(newBuffer);m=new e(newBuffer);n=new f(newBuffer);o=new g(newBuffer);p=new h(newBuffer);buffer=newBuffer;return true}
// EMSCRIPTEN_START_FUNCS
function $a(a){a=a|0;var b=0;b=r;r=r+a|0;r=r+15&-16;return b|0}function ab(){return r|0}function bb(a){a=a|0;r=a}function cb(a,b){a=a|0;b=b|0;r=a;s=b}function db(a,b){a=a|0;b=b|0;if(!w){w=a;x=b}}function eb(a){a=a|0;i[t>>0]=i[a>>0];i[t+1>>0]=i[a+1>>0];i[t+2>>0]=i[a+2>>0];i[t+3>>0]=i[a+3>>0]}function fb(a){a=a|0;i[t>>0]=i[a>>0];i[t+1>>0]=i[a+1>>0];i[t+2>>0]=i[a+2>>0];i[t+3>>0]=i[a+3>>0];i[t+4>>0]=i[a+4>>0];i[t+5>>0]=i[a+5>>0];i[t+6>>0]=i[a+6>>0];i[t+7>>0]=i[a+7>>0]}function gb(a){a=a|0;L=a}function hb(){return L|0}function ib(a,b){a=a|0;b=b|0;var c=0,d=0;d=980497+(a<<3&2040)|0;c=a>>>8;if(c&255|0)nd(b,c&255,l[d+5>>0]|0);c=a>>>16;if(c&255|0)nd(b,c&255,l[d+6>>0]|0);c=a>>>24;if(c|0)nd(b,c&255,l[d+7>>0]|0);return}function jb(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,m=0,n=0,o=0;o=r;r=r+16|0;n=o+4|0;m=o;f=a+b|0;g=a+c|0;j=e+8|0;h=k[e+4>>2]|0;b=k[j>>2]|0;c=(i[f>>0]|0)==32?f+1|0:f;do{if(c>>>0>=g>>>0)break;k[n>>2]=c;f=l[1009576+(l[c>>0]|0)>>0]|0;k[m>>2]=f;c=c+f|0;f=ud(d,n,m)|0;if(f<<24>>24){k[e+32+(b<<3)>>2]=c-a;k[e+32+(b<<3)+4>>2]=f&255;b=b+1|0}}while((b|0)<(h|0));k[j>>2]=b;n=c-a|0;k[e+32+(b<<3)>>2]=n;k[e+32+(k[j>>2]<<3)+4>>2]=0;r=o;return n|0}function kb(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,i=0,j=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0;q=a+c|0;z=f+12|0;u=k[f+4>>2]|0;A=f+16|0;v=u+-1|0;w=d+16|0;r=d+12|0;y=a;s=e+16|0;t=e+12|0;h=k[z>>2]|0;g=k[A>>2]|0;p=a+b|0;while(1){if(p>>>0>=q>>>0){c=p;break}b=l[1009576+(l[p>>0]|0)>>0]|0;c=p+b|0;b=(l[1009576+(l[c>>0]|0)>>0]|0)+b|0;do if(b>>>0>5){o=qb(p,b)|0;a=k[d>>2]|0;m=k[w>>2]|0;n=(o>>>12)+o|0;i=(k[r>>2]|0)+-1&n;j=m&o;b=k[a+(i<<4)>>2]|0;if((b^j)&m){b=k[a+(i<<4)+4>>2]|0;if((b^j)&m){b=k[a+(i<<4)+8>>2]|0;if((b^j)&m){b=k[a+(i<<4)+12>>2]|0;if(!((b^j)&m))x=8}else x=8}else x=8}else x=8;if((x|0)==8){x=0;if(b){k[f+8040+(h<<3)>>2]=p-y;k[f+8040+(h<<3)+4>>2]=b&~m;h=h+1|0}}j=k[e>>2]|0;m=k[s>>2]|0;a=(k[t>>2]|0)+-1&n;i=m&o;b=k[j+(a<<4)>>2]|0;if((b^i)&m){b=k[j+(a<<4)+4>>2]|0;if((b^i)&m){b=k[j+(a<<4)+8>>2]|0;if((b^i)&m){b=k[j+(a<<4)+12>>2]|0;if((b^i)&m)break}}}if(b){k[f+16048+(g<<3)>>2]=p-y;k[f+16048+(g<<3)+4>>2]=b&~m;g=g+1|0}}while(0);if((g|0)<(v|0)&(h|0)<(u|0))p=c;else break}k[z>>2]=h;k[A>>2]=g;z=c-y|0;k[f+8040+(h<<3)>>2]=z;k[f+8040+(h<<3)+4>>2]=0;k[f+16048+(k[A>>2]<<3)>>2]=z;k[f+16048+(k[A>>2]<<3)+4>>2]=0;return}function lb(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,j=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0;D=r;r=r+16|0;C=D;t=a+b|0;u=a+c|0;B=f+8|0;b=k[B>>2]|0;A=k[f+4>>2]|0;v=C;k[v>>2]=0;k[v+4>>2]=0;v=C+4|0;w=d+16|0;x=d+12|0;y=e+12|0;z=e+16|0;g=0;c=(i[t>>0]|0)==32?t+1|0:t;do{if(c>>>0>=u>>>0)break;s=c+(l[979972+(l[c>>0]|0)>>0]|0)|0;s=s+(l[979972+(l[s>>0]|0)>>0]|0)|0;t=s+(l[979972+(l[s>>0]|0)>>0]|0)|0;t=t+(l[979972+(l[t>>0]|0)>>0]|0)|0;q=c;p=sb(c,t-q|0)|0;do if(!((p|0)==(k[C>>2]|0)?1:(p|0)==(k[v>>2]|0))){j=k[d>>2]|0;h=k[w>>2]|0;o=(p>>>12)+p|0;m=(k[x>>2]|0)+-1&o;n=h&p;c=k[j+(m<<4)>>2]|0;if((c^n)&h){c=k[j+(m<<4)+4>>2]|0;if((c^n)&h){c=k[j+(m<<4)+8>>2]|0;if((c^n)&h){c=k[j+(m<<4)+12>>2]|0;if(!((c^n)&h))m=8;else m=9}else m=8}else m=8}else m=8;if((m|0)==8){m=0;if(!c)m=9;else j=0}if((m|0)==9){c=k[y>>2]|0;if(!c)break;n=k[e>>2]|0;h=k[z>>2]|0;j=c+-1&o;m=h&p;c=k[n+(j<<4)>>2]|0;if((c^m)&h){c=k[n+(j<<4)+4>>2]|0;if((c^m)&h){c=k[n+(j<<4)+8>>2]|0;if((c^m)&h){c=k[n+(j<<4)+12>>2]|0;if((c^m)&h)break}}}if(!c)break;else j=-2147483648}k[C+(g<<2)>>2]=p;k[f+32+(b<<3)>>2]=q-a;k[f+32+(b<<3)+4>>2]=c&~h|j;b=b+1|0;g=g&1^1}while(0);c=(i[t>>0]|0)==32?t:s;if(c>>>0<u>>>0)c=c+(l[980228+(l[c>>0]|0)>>0]|0)|0;else c=u}while((b|0)<(A|0));k[B>>2]=b;e=c-a|0;k[f+32+(b<<3)>>2]=e;k[f+32+(k[B>>2]<<3)+4>>2]=0;r=D;return e|0}function mb(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,j=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0;J=r;r=r+16|0;F=J;u=a+b|0;A=a+(c+1)|0;H=f+12|0;g=k[H>>2]|0;B=k[f+4>>2]|0;I=f+16|0;b=k[I>>2]|0;C=B+-1|0;k[F>>2]=0;k[F+4>>2]=0;k[F+8>>2]=0;k[F+12>>2]=0;u=(i[u>>0]|0)==32?u+1|0:u;x=F+8|0;y=e+16|0;z=e+12|0;E=a;v=d+16|0;w=d+12|0;n=0;a=g;g=0;q=u;D=u;j=u;while(1){if(D>>>0>=A>>>0){c=D;break}c=i[D>>0]|0;if(c<<24>>24==32){t=u;p=ub(u,j-t|0)|0;s=L;n=F;o=x;do if(!(((p|0)==(k[n>>2]|0)?(s|0)==(k[n+4>>2]|0):0)|((p|0)==(k[o>>2]|0)?(s|0)==(k[o+4>>2]|0):0))){h=F+(g<<3)|0;k[h>>2]=p;k[h+4>>2]=s;g=1-g|0;h=F+(g<<3)|0;c=k[h>>2]|0;h=k[h+4>>2]|0;do if(!((c|0)==0&(h|0)==0|(c|0)==(p|0)&(h|0)==(s|0))){c=vb(c,h,p,s)|0;j=L;m=k[e>>2]|0;o=k[y>>2]|0;n=k[z>>2]|0;h=rf(c|0,j|0,12)|0;h=of(h|0,L|0,c|0,j|0)|0;h=n+-1&h;j=rf(c|0,j|0,4)|0;j=o&j;c=k[m+(h<<4)>>2]|0;if((c^j)&o){c=k[m+(h<<4)+4>>2]|0;if((c^j)&o){c=k[m+(h<<4)+8>>2]|0;if((c^j)&o){c=k[m+(h<<4)+12>>2]|0;if((c^j)&o){c=n;break}}}}if(!c)c=n;else{k[f+16048+(b<<3)>>2]=q-E;k[f+16048+(b<<3)+4>>2]=c&~o;c=n;b=b+1|0}}else{c=k[z>>2]|0;o=k[y>>2]|0;m=k[e>>2]|0}while(0);q=rf(p|0,s|0,12)|0;q=of(q|0,L|0,p|0,s|0)|0;j=c+-1&q;p=rf(p|0,s|0,4)|0;h=o&p;c=k[m+(j<<4)>>2]|0;if((c^h)&o){c=k[m+(j<<4)+4>>2]|0;if((c^h)&o){c=k[m+(j<<4)+8>>2]|0;if((c^h)&o){c=k[m+(j<<4)+12>>2]|0;if(!((c^h)&o))G=17}else G=17}else G=17}else G=17;if((G|0)==17){G=0;if(c){k[f+16048+(b<<3)>>2]=t-E;k[f+16048+(b<<3)+4>>2]=c&~o;b=b+1|0}}m=k[d>>2]|0;n=k[v>>2]|0;h=(k[w>>2]|0)+-1&q;j=n&p;c=k[m+(h<<4)>>2]|0;if((c^j)&n){c=k[m+(h<<4)+4>>2]|0;if((c^j)&n){c=k[m+(h<<4)+8>>2]|0;if((c^j)&n){c=k[m+(h<<4)+12>>2]|0;if((c^j)&n)break}}}if(c){k[f+8040+(a<<3)>>2]=t-E;k[f+8040+(a<<3)+4>>2]=c&~n;a=a+1|0}}while(0);m=D+1|0;c=i[D>>0]|0;o=0;h=u;j=m}else{o=n+1|0;h=q;m=u}c=D+(l[1009576+(c&255)>>0]|0)|0;if((b|0)<(C|0)&(a|0)<(B|0)){n=o;q=h;D=c;j=(o|0)<9?c:j;u=m}else break}k[H>>2]=a;k[I>>2]=b;H=c-E|0;k[f+8040+(a<<3)>>2]=H;k[f+8040+(a<<3)+4>>2]=0;k[f+16048+(k[I>>2]<<3)>>2]=H;k[f+16048+(k[I>>2]<<3)+4>>2]=0;r=J;return}function nb(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;d=(c|0)<8?c*12|0:100;e=c*5>>3;e=(e|0)<3?3:(e|0)>16?16:e;c=a-b|0;if((c|0)<(e|0))if((c|0)<1)d=0;else{b=(c*100|0)/(e|0)|0;d=(d|0)<(b|0)?d:b}return d|0}function ob(a,b){a=a|0;b=b|0;var c=0.0;if(b)if(a){if((b|0)>(a|0))c=+(b|0)/+(a|0);else c=+(a|0)/+(b|0);if(!(c<=1.5))if(c>4.0)a=0;else a=~~((4.0-c)*100.0/2.5);else a=100}else a=0;else a=100;return a|0}function pb(a,b){a=a|0;b=b|0;a=((Hc(a)|0)&255)<<8;return a|(l[980484+b>>0]|0)|0}function qb(a,b){a=a|0;b=b|0;var c=0;do if(b){c=l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24;if((b|0)<5){c=k[120+((b&3)<<2)>>2]&c;c=c>>>3^c;break}else{a=a+4|0;a=k[120+((b&3)<<2)>>2]&(l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24);c=(a<<18^a)+(c>>>3^c)|0;break}}else c=0;while(0);return c|0}function rb(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;do if((b|0)>=5){d=l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24;d=d>>>3^d;e=a+4|0;e=l[e>>0]|l[e+1>>0]<<8|l[e+2>>0]<<16|l[e+3>>0]<<24;if((b|0)<9){a=k[120+((b&3)<<2)>>2]&e;d=(a<<4^a)+(d^c)|0;break}else{a=a+8|0;a=k[120+((b&3)<<2)>>2]&(l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24);d=(d^c)+(e<<4^e)+(a<<2^a)|0;break}}else{d=k[120+((b&3)<<2)>>2]&(l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24);d=d^c^d>>>3}while(0);return d|0}function sb(a,b){a=a|0;b=b|0;var c=0;if(!b)b=0;else{c=(i[a+-1>>0]|0)==32?17476:0;b=rb(a,b,(i[a+b>>0]|0)==32?c|1145307136:c)|0}return b|0}function tb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,m=0;f=(i[a+-1>>0]|0)==32;e=f?c|17476:c;f=f?d:d;d=(i[a+b>>0]|0)==32;e=d?e|1145307136:e;f=d?f:f;switch(b+-1>>2|0){case 0:{b=k[120+((b&3)<<2)>>2]&(l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24);c=b;d=0;b=(rf(b|0,0,3)|0)^b;a=L;break}case 1:{h=l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24;j=rf(h|0,0,3)|0;g=L;c=a+4|0;b=k[120+((b&3)<<2)>>2]&(l[c>>0]|l[c+1>>0]<<8|l[c+2>>0]<<16|l[c+3>>0]<<24);c=of(b|0,0,h|0,0)|0;d=L;b=of((sf(b|0,0,4)|0)^b|0,L|0,j^h|0,g|0)|0;a=L;break}case 2:{j=l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24;g=rf(j|0,0,3)|0;h=L;m=a+4|0;m=l[m>>0]|l[m+1>>0]<<8|l[m+2>>0]<<16|l[m+3>>0]<<24;d=of(m|0,0,j|0,0)|0;c=L;h=of((sf(m|0,0,4)|0)^m|0,L|0,g^j|0,h|0)|0;j=L;a=a+8|0;b=k[120+((b&3)<<2)>>2]&(l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24);c=of(d|0,c|0,b|0,0)|0;d=L;b=of(h|0,j|0,(sf(b|0,0,2)|0)^b|0,L|0)|0;a=L;break}case 3:{m=l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24;j=rf(m|0,0,3)|0;h=L;g=a+4|0;g=l[g>>0]|l[g+1>>0]<<8|l[g+2>>0]<<16|l[g+3>>0]<<24;c=of(g|0,0,m|0,0)|0;d=L;h=of((sf(g|0,0,4)|0)^g|0,L|0,j^m|0,h|0)|0;m=L;j=a+8|0;j=l[j>>0]|l[j+1>>0]<<8|l[j+2>>0]<<16|l[j+3>>0]<<24;d=of(c|0,d|0,j|0,0)|0;c=L;j=of(h|0,m|0,(sf(j|0,0,2)|0)^j|0,L|0)|0;m=L;a=a+12|0;b=k[120+((b&3)<<2)>>2]&(l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24);c=of(d|0,c|0,b|0,0)|0;d=L;b=of(j|0,m|0,(rf(b|0,0,8)|0)^b|0,L|0)|0;a=L;break}case 4:{m=l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24;h=rf(m|0,0,3)|0;j=L;g=a+4|0;g=l[g>>0]|l[g+1>>0]<<8|l[g+2>>0]<<16|l[g+3>>0]<<24;d=of(g|0,0,m|0,0)|0;c=L;j=of((sf(g|0,0,4)|0)^g|0,L|0,h^m|0,j|0)|0;m=L;h=a+8|0;h=l[h>>0]|l[h+1>>0]<<8|l[h+2>>0]<<16|l[h+3>>0]<<24;c=of(d|0,c|0,h|0,0)|0;d=L;h=of(j|0,m|0,(sf(h|0,0,2)|0)^h|0,L|0)|0;m=L;j=a+12|0;j=l[j>>0]|l[j+1>>0]<<8|l[j+2>>0]<<16|l[j+3>>0]<<24;d=of(c|0,d|0,j|0,0)|0;c=L;j=of(h|0,m|0,(rf(j|0,0,8)|0)^j|0,L|0)|0;m=L;a=a+16|0;b=k[120+((b&3)<<2)>>2]&(l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24);c=of(d|0,c|0,b|0,0)|0;d=L;b=of(j|0,m|0,(rf(b|0,0,4)|0)^b|0,L|0)|0;a=L;break}default:{m=l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24;j=rf(m|0,0,3)|0;h=L;g=a+4|0;g=l[g>>0]|l[g+1>>0]<<8|l[g+2>>0]<<16|l[g+3>>0]<<24;c=of(g|0,0,m|0,0)|0;d=L;h=of((sf(g|0,0,4)|0)^g|0,L|0,j^m|0,h|0)|0;m=L;j=a+8|0;j=l[j>>0]|l[j+1>>0]<<8|l[j+2>>0]<<16|l[j+3>>0]<<24;d=of(c|0,d|0,j|0,0)|0;c=L;j=of(h|0,m|0,(sf(j|0,0,2)|0)^j|0,L|0)|0;m=L;h=a+12|0;h=l[h>>0]|l[h+1>>0]<<8|l[h+2>>0]<<16|l[h+3>>0]<<24;c=of(d|0,c|0,h|0,0)|0;d=L;h=of(j|0,m|0,(rf(h|0,0,8)|0)^h|0,L|0)|0;m=L;j=a+16|0;j=l[j>>0]|l[j+1>>0]<<8|l[j+2>>0]<<16|l[j+3>>0]<<24;d=of(c|0,d|0,j|0,0)|0;c=L;j=of(h|0,m|0,(rf(j|0,0,4)|0)^j|0,L|0)|0;m=L;a=a+20|0;b=k[120+((b&3)<<2)>>2]&(l[a>>0]|l[a+1>>0]<<8|l[a+2>>0]<<16|l[a+3>>0]<<24);c=of(d|0,c|0,b|0,0)|0;d=L;b=of(j|0,m|0,(rf(b|0,0,6)|0)^b|0,L|0)|0;a=L}}j=rf(c|0,d|0,17)|0;j=of(j|0,L|0,c|0,d|0)|0;m=L;h=rf(j|0,m|0,9)|0;m=of(h|0,L|0,j|0,m|0)|0;m=of(0,m&255|0,b^e|0,a^f|0)|0;return m|0}function ub(a,b){a=a|0;b=b|0;var c=0,d=0,e=0;if(!b){b=0;a=0}else{c=(i[a+-1>>0]|0)==32;e=c?17476:0;c=c?0:0;d=(i[a+b>>0]|0)==32;a=tb(a,b,d?e|1145307136:e,d?c:c)|0;b=L}L=b;return a|0}function vb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0;f=rf(a|0,b|0,13)|0;e=L;b=sf(a|0,b|0,51)|0;d=of(f|b|0,e|L|0,c|0,d|0)|0;return d|0}function wb(a,b,c,d,e,f,g){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,i=0;h=r;r=r+48|0;i=h+24|0;k[i>>2]=0;k[i+4>>2]=1097857;k[i+8>>2]=23;k[i+12>>2]=26;f=kc(a,b,c,i,0,d,e,h,f,g)|0;r=h;return ((f|0)==26?0:f)|0}function xb(a,b,c,d,e,f,g,h,i){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;i=i|0;return kc(a,b,c,d,0,e,f,g,h,i)|0}function yb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0;a:do if(a<<16>>16){d=a&1023;e=k[b>>2]|0;do if((e|0)>0){c=0;while(1){f=b+4+(c<<1)|0;g=j[f>>1]|0;c=c+1|0;if((g&1023|0)==(d|0)){c=5;break}if((c|0)>=(e|0)){c=6;break}}if((c|0)==5){b=g<<16>>16>>10;a=a<<16>>16>>10;j[f>>1]=((b|0)>=(a|0)?b:a)<<10|d;break a}else if((c|0)==6)if((e|0)>13)break a;else break}while(0);k[b>>2]=e+1;j[b+4+(e<<1)>>1]=a}while(0);return}function zb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0;a:do if(a<<16>>16){d=a&1023;e=k[b>>2]|0;do if((e|0)>0){c=0;while(1){f=b+4+(c<<1)|0;g=m[f>>1]|0;c=c+1|0;if((g&1023|0)==(d|0)){c=5;break}if((c|0)>=(e|0)){c=6;break}}if((c|0)==5){j[f>>1]=g+2048&64512|d;break a}else if((c|0)==6)if((e|0)>13)break a;else break}while(0);k[b>>2]=e+1;j[b+4+(e<<1)>>1]=a}while(0);return}function Ab(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0;if((k[a>>2]|0)>4){g=0;do{f=j[a+4+(g<<1)>>1]|0;d=f<<16>>16>>10;d=(d|0)>-1?d:0-d|0;a:do if((g|0)>0){e=g;while(1){b=e+-1|0;c=j[a+4+(b<<1)>>1]|0;h=c<<16>>16>>10;if((((h|0)>-1?h:0-h|0)|0)>=(d|0)){b=e;break a}j[a+4+(e<<1)>>1]=c;if((e|0)>1)e=b;else break}}else b=g;while(0);j[a+4+(b<<1)>>1]=f;g=g+1|0}while((g|0)<(k[a>>2]|0));k[a>>2]=4}return}function Bb(a){a=a|0;var b=0,c=0,d=0;d=i[a>>0]|0;b=(d&1)==0;d=b?(d&255)>>>1:k[a+4>>2]|0;if((d|0)>0){a=b?a+1|0:k[a+8>>2]|0;b=0;c=0;do{b=((i[a+c>>0]|0)==44&1)+b|0;c=c+1|0}while((c|0)!=(d|0))}else b=0;return b|0}function Cb(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;g=c;c=0;a:while(1){if((c|0)<(g|0))f=c;else{c=0;break}while(1){d=f+g>>1;c=b+(d*12|0)|0;e=_d(k[c>>2]|0,a)|0;if((e|0)>=0)break;c=d+1|0;if((c|0)<(g|0))f=c;else{c=0;break a}}if((e|0)>0){g=d;c=f}else break}return c|0}function Db(a){a=a|0;var b=0,c=0,d=0,e=0,f=0;f=181;b=0;a:while(1){if((b|0)<(f|0))e=b;else{b=0;break}while(1){c=e+f>>1;b=5776+(c<<3)|0;d=_d(k[b>>2]|0,a)|0;if((d|0)>=0)break;b=c+1|0;if((b|0)<(f|0))e=b;else{b=0;break a}}if((d|0)>0){f=c;b=e}else break}return b|0}function Eb(a){a=a|0;Ba(a|0)|0;cf()}function Fb(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;d=c+-3|0;a:do if((d|0)>(b|0))do{e=a+b|0;e=l[e>>0]|l[e+1>>0]<<8|l[e+2>>0]<<16|l[e+3>>0]<<24;if((e^1010580540)+-16843009&(e&-2139062144^-2139062144)|0)break a;b=b+4|0}while((b|0)<(d|0));while(0);b:do if((b|0)<(c|0))while(1){if((i[a+b>>0]|0)==60)break b;b=b+1|0;if((b|0)>=(c|0)){b=-1;break}}else b=-1;while(0);return b|0}function Gb(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;a:do if((b|0)<(c|0))while(1){b:do switch(i[a+b>>0]|0){case 61:break a;case 34:{e=b+1|0;if((e|0)<(c|0)){d=b;b=e;while(1){switch(i[a+b>>0]|0){case 34:break b;case 92:{d=d+2|0;break}default:d=b}b=d+1|0;if((b|0)>=(c|0))break b}}else b=e;break}case 39:{e=b+1|0;if((e|0)<(c|0)){d=b;b=e;while(1){switch(i[a+b>>0]|0){case 39:break b;case 92:{d=d+2|0;break}default:d=b}b=d+1|0;if((b|0)>=(c|0))break b}}else b=e;break}default:{}}while(0);b=b+1|0;if((b|0)>=(c|0)){b=-1;break a}}else b=-1;while(0);return b|0}function Hb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0;g=Vd(d)|0;a:do if((c-b|0)>=(g|0)){f=g+b|0;while(1){if((c|0)<=(f|0))break;e=c+-1|0;if((i[a+e>>0]|0)==32)c=e;else break}c=c-g|0;if((c|0)>=(b|0)){c=a+c|0;if((g|0)>0){e=0;while(1){if((i[c+e>>0]|32|0)!=(i[d+e>>0]|0)){c=0;break a}e=e+1|0;if((e|0)>=(g|0)){c=1;break}}}else c=1}else c=0}else c=0;while(0);return c|0}function Ib(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0;e=Vd(d)|0;a:do if((c-b|0)>=(e|0)){c=c-e|0;b:do if((c|0)>(b|0))while(1){switch(i[a+b>>0]|0){case 39:case 34:case 32:break;default:break b}b=b+1|0;if((b|0)>=(c|0))break b}while(0);b=a+b|0;if((e|0)>0){c=0;while(1){if((i[b+c>>0]|32|0)!=(i[d+c>>0]|0)){b=0;break a}c=c+1|0;if((c|0)>=(e|0)){b=1;break}}}else b=1}else b=0;while(0);return b|0}function Jb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0;k[a>>2]=0;k[a+4>>2]=0;k[a+8>>2]=0;if((c|0)<(d|0)){f=1;do{e=l[b+c>>0]|0;g=(l[984554+e>>0]|0)>>>(f*3|0);f=g&3;do if(g&4|0)if(!f){Ce(a,1,i[984810+e>>0]|0);break}else{Ce(a,1,44);break}while(0);c=c+1|0}while((c|0)!=(d|0));if(!f)Ce(a,1,44)}return}function Kb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0;a:do if((c|0)<(d|0)){b:while(1){switch(i[b+c>>0]|0){case 39:case 34:break b;case 32:break;default:{f=5;break a}}c=c+1|0;if((c|0)>=(d|0)){f=5;break a}}if((c|0)>=0){e=c+1|0;c:do if((e|0)<(d|0)){c=e;d:while(1){switch(i[b+c>>0]|0){case 39:case 34:break d;case 62:{f=8;break d}case 61:{f=9;break d}case 60:{f=10;break d}case 38:{f=11;break d}default:{}}c=c+1|0;if((c|0)>=(d|0))break c}if((f|0)==8)c=c+-1|0;else if((f|0)==9)c=c+-1|0;else if((f|0)==10)c=c+-1|0;else if((f|0)==11)c=c+-1|0;if((c|0)>=0){Jb(a,b,e,c);break a}}while(0);ze(a,1097857,0)}else f=5}else f=5;while(0);if((f|0)==5)ze(a,1097857,0);return}function Lb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,l=0,m=0,n=0,o=0;o=r;r=r+32|0;n=o;d=i[a>>0]|0;l=a+4|0;e=k[l>>2]|0;c=(d&1)==0?(d&255)>>>1:e;if((c|0?(Bb(a)|0)<=4:0)?(c|0)>0:0){g=a+8|0;h=a+1|0;f=0;do{c=Ie(a,f)|0;if((c|0)==-1){if(!(d&1))e=(d&255)>>>1}else e=c;c=e-f|0;do if((c|0)<17){tf(n|0,((d&1)==0?h:k[g>>2]|0)+f|0,c|0)|0;i[n+c>>0]=0;c=Cb(n,136,213)|0;if(c|0){yb(j[c+8>>1]|0,b);yb(j[c+10>>1]|0,b);break}c=me(n,45)|0;if(c|0)i[c>>0]=0;if((Vd(n)|0)<4?(m=Cb(n,2692,257)|0,m|0):0){yb(j[m+8>>1]|0,b);yb(j[m+10>>1]|0,b)}}while(0);f=e+1|0;d=i[a>>0]|0;e=k[l>>2]|0}while((f|0)<(((d&1)==0?(d&255)>>>1:e)|0))}r=o;return}function Mb(a,b){a=a|0;b=b|0;var c=0,d=0;c=r;r=r+16|0;d=c;Jb(d,a,0,Vd(a)|0);Lb(d,b);Ae(d);r=c;return}function Nb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0;e=r;r=r+16|0;d=e;c=Vd(a)|0;if((c|0)<=3){ne(d,a);i[d+3>>0]=0;if((c|0)>0){a=0;do{f=d+a|0;i[f>>0]=l[f>>0]|0|32;a=a+1|0}while((a|0)!=(c|0))}a=Db(d)|0;if(a|0){zb(j[a+4>>1]|0,b);zb(j[a+6>>1]|0,b)}}r=e;return}function Ob(a,b){a=a|0;b=b|0;switch(a|0){case 62:case 48:case 46:case 45:case 14:{zb(4112,b);break}case 47:case 20:case 13:{zb(4165,b);break}case 12:case 21:case 11:case 10:{zb(4104,b);break}case 44:case 16:{zb(4105,b);break}default:{}}return}function Pb(a,b){a=a|0;b=b|0;zb(a+8192&65535,b);return}function Qb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0;f=r;r=r+80|0;e=f;d=f+8|0;k[a>>2]=0;k[a+4>>2]=0;k[a+8>>2]=0;if((k[b>>2]|0)>0){c=0;do{g=j[b+4+(c<<1)>>1]|0;h=Ec(g&1023)|0;k[e>>2]=h;k[e+4>>2]=g<<16>>16>>10;ue(d,985273,e);Fe(a,d);c=c+1|0}while((c|0)<(k[b>>2]|0))}r=f;return}function Rb(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0;D=r;r=r+16|0;C=D;k[a>>2]=0;k[a+4>>2]=0;k[a+8>>2]=0;y=(c|0)<8192?c:8192;a:do if((y|0)>0){s=C+4|0;t=a+1|0;u=C+8|0;v=C+1|0;w=a+8|0;x=a+4|0;c=0;do{c=Fb(b,c,y)|0;if((c|0)<0)break a;d=c+1|0;if((d|0)<(y|0))c=d;else break a;b:while(1){switch(i[b+c>>0]|0){case 62:break b;case 60:{z=6;break b}case 38:{z=7;break b}default:{}}c=c+1|0;if((c|0)>=(y|0))break a}if((z|0)==6){z=0;c=c+-1|0}else if((z|0)==7){z=0;c=c+-1|0}if((c|0)<0)break a;if((((((!(Ib(b,d,c,985280)|0)?!(Ib(b,d,c,985284)|0):0)?!(Ib(b,d,c,985290)|0):0)?!(Ib(b,d,c,985298)|0):0)?!(Ib(b,d,c,985304)|0):0)?!(Ib(b,d,c,985309)|0):0)?(A=Ib(b,d,c,985312)|0,B=Gb(b,d,c)|0,(B|0)>-1):0){g=B;f=0;e=d;while(1){do if(A){if(Hb(b,e,g,985318)|0?Ib(b,g+1|0,c,985330)|0:0){z=25;break}if(Hb(b,e,g,985348)|0){d=g+1|0;if(!(Ib(b,d,c,985354)|0)?!(f|(Ib(b,d,c,985367)|0)):0){d=0;z=26}else z=25}else z=24}else z=24;while(0);if((z|0)==24)if(f)z=25;else{d=0;z=26}if((z|0)==25)if(Hb(b,e,g,985377)|0){d=1;z=29}else{d=1;z=26}if((z|0)==26){z=0;if(!(Hb(b,e,g,985386)|0)?!(Hb(b,e,g,985392)|0):0)e=g+1|0;else z=29}if((z|0)==29){z=0;p=g+1|0;Kb(C,b,p,c);q=i[C>>0]|0;f=(q&1)==0;q=f?(q&255)>>>1:k[s>>2]|0;c:do if(q|0){e=i[a>>0]|0;if(!(e&1)){e=(e&255)>>>1;g=t}else{e=k[x>>2]|0;g=k[w>>2]|0}l=f?v:k[u>>2]|0;d:do if(e>>>0>=q>>>0){m=g+e|0;n=l+q|0;o=g;if((e|0)<(q|0))break;h=m+(1-q)|0;if((h|0)==(g|0))break;j=i[l>>0]|0;e=g;e:while(1){if((i[e>>0]|0)==j<<24>>24){f=e;g=l;do{g=g+1|0;if((g|0)==(n|0))break e;f=f+1|0}while((i[f>>0]|0)==(i[g>>0]|0))}e=e+1|0;if((e|0)==(h|0))break d}if(!((e|0)==(m|0)|(e-o|0)==-1))break c}while(0);Ee(a,l,q)}while(0);Ae(C);e=p}g=Gb(b,e,c)|0;if((g|0)<=-1)break;else f=d}}c=c+1|0}while((c|0)<(y|0))}while(0);c=i[a>>0]|0;d=(c&1)==0;if(d)e=(c&255)>>>1;else e=k[a+4>>2]|0;if(e>>>0>1){if(d)c=(c&255)>>>1;else c=k[a+4>>2]|0;He(a,c+-1|0)}r=D;return}function Sb(a,b){a=a|0;b=b|0;var c=0;c=(b|0)<32?b:32;a:do if((c|0)>0){b=0;while(1){if((i[a+~b>>0]|0)==32)break a;b=b+1|0;if((b|0)>=(c|0)){b=0;break}}while(1){if((i[a+(0-b)>>0]&-64)<<24>>24!=-128)break a;b=b+1|0;if((b|0)>=(c|0)){b=0;break}}}else b=0;while(0);return b|0}function Tb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0;d=(b|0)<32?b:32;c=0;while(1){if((c|0)>=(d|0)){e=3;break}b=c+1|0;if((i[a+c>>0]|0)==32)break;else c=b}a:do if((e|0)==3)if((d|0)>0){b=0;while(1){if((i[a+b>>0]&-64)<<24>>24!=-128)break a;b=b+1|0;if((b|0)>=(d|0)){b=0;break}}}else b=0;while(0);return b|0}function Ub(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,m=0;j=a+b|0;e=k[c>>2]|0;if((b|0)>0){b=0;h=a;do{f=i[h>>0]|0;g=f&255;do if((f&255)>=192){if((g&224|0)==192){g=l[h+1>>0]|0|g<<8;a=2;break}a=i[h+1>>0]|0;f=i[h+2>>0]|0;if((g&240|0)==224){g=(a&255)<<8|g<<16|f&255;a=3;break}else{g=(a&255)<<16|g<<24|(f&255)<<8|(l[h+3>>0]|0);a=4;break}}else a=1;while(0);h=h+a|0;m=d+(e<<2)|0;f=k[m>>2]|0;k[m>>2]=g;b=((g|0)==(f|0)?a:0)+b|0;e=(g^e<<4)&4095}while(h>>>0<j>>>0)}else b=0;k[c>>2]=e;return b|0}function Vb(a,b){a=a|0;b=b|0;var c=0,d=0;c=b&-4;if((c|0)>0){d=0;b=0;do{b=((i[a+d>>0]|0)==32&1)+b+((i[a+(d|1)>>0]|0)==32&1)+((i[a+(d|2)>>0]|0)==32&1)+((i[a+(d|3)>>0]|0)==32&1)|0;d=d+4|0}while((d|0)<(c|0))}else b=0;return b|0}function Wb(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0;s=a+b|0;e=k[c>>2]|0;if((b|0)>0){q=a;p=0;r=a;m=a;n=0;while(1){g=i[r>>0]|0;j=g&255;f=q+1|0;i[q>>0]=g;do if(g<<24>>24!=32)if((g&255)>=192){if((j&224|0)==192){h=r+1|0;i[f>>0]=i[h>>0]|0;j=l[h>>0]|0|j<<8;f=q+2|0;h=p;o=2;break}h=r+1|0;i[f>>0]=i[h>>0]|0;g=r+2|0;f=q+3|0;i[q+2>>0]=i[g>>0]|0;if((j&240|0)==224){j=(l[h>>0]|0)<<8|j<<16|(l[g>>0]|0);h=p;o=3;break}else{o=r+3|0;i[f>>0]=i[o>>0]|0;j=(l[h>>0]|0)<<16|j<<24|(l[g>>0]|0)<<8|(l[o>>0]|0);f=q+4|0;h=p;o=4;break}}else{h=p;o=1}else{m=(p<<1|0)>(n|0)?m:f;f=m;h=0;o=1;n=0}while(0);r=r+o|0;q=d+(e<<2)|0;g=k[q>>2]|0;k[q>>2]=j;e=(j^e<<4)&4095;if(r>>>0>=s>>>0)break;else{q=f;p=((j|0)==(g|0)?o:0)+h|0;n=o+n|0}}}else f=a;k[c>>2]=e;e=f-a|0;if((e|0)>=(b+-3|0)){if((e|0)<(b|0))i[f>>0]=32}else{i[f>>0]=32;i[f+1>>0]=32;i[f+2>>0]=32;i[f+3>>0]=0}return e|0}function Xb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,j=0,l=0,m=0,n=0,o=0,p=0;o=r;r=r+16|0;m=o;h=a+b|0;k[m>>2]=0;n=jf(16384)|0;qf(n|0,0,16384)|0;if((b|0)>0){j=h;c=a;l=a+1|0;d=a;e=0;f=a;do{g=j-f|0;g=(g|0)>48?48:g;while(1)if((i[f+g>>0]&-64)<<24>>24==-128)g=g+1|0;else break;p=Vb(f,g)|0;if((p|0)<12&(Ub(f,g,m,n)|0)<19){if(e){p=Tb(f,g)|0;e=g-p|0;f=f+p|0}else e=g;if((e|0)>0){uf(d|0,f|0,e|0)|0;d=d+e|0;g=e;e=0}else{g=e;e=0}}else if(!e){d=d+(0-(Sb(d,d-c|0)|0))|0;if((d|0)==(a|0)){i[a>>0]=32;d=l;e=1}else e=1}else e=1;f=f+g|0}while(f>>>0<h>>>0)}else{c=a;d=a}c=d-c|0;if((c|0)>=(b+-3|0)){if((c|0)<(b|0))i[d>>0]=32}else{i[d>>0]=32;i[d+1>>0]=32;i[d+2>>0]=32;i[d+3>>0]=0}kf(n);r=o;return c|0}function Yb(a,b){a=a|0;b=b|0;var c=0,d=0,e=0;e=r;r=r+16|0;d=e;if((b|0)<256)b=0;else{k[d>>2]=0;c=jf(16384)|0;qf(c|0,0,16384)|0;if((Vb(a,256)|0)<64?(Ub(a,256,d,c)|0)<171:0)b=0;else b=1;kf(c)}r=e;return b|0}function Zb(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,l=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0;y=r;r=r+48|0;x=y+32|0;t=y+16|0;s=y;q=0;do{d=j[a+568+(q<<1)>>1]|0;n=d&65535;do if(((((d<<16>>16!=-1?(o=k[a+616+(q<<2)>>2]|0,o|0):0)?(p=(k[a+808+(q<<2)>>2]|0)/(o|0)|0,(d&65535)<165&(p|0)<41):0)?(i=k[7224+(n<<2)>>2]|0,(i|0)!=26):0)?(l=rd(a,i&65535)|0,(l|0)>=0):0)?(m=k[a+616+(l<<2)>>2]|0,m|0):0){d=(k[a+808+(l<<2)>>2]|0)/(m|0)|0;e=(d|0)<(p|0);if(!e?!((n|0)<(i|0)&(d|0)==(p|0)):0){f=q;g=0;h=l}else{f=l;g=1;h=q}z=e?p:d;e=m+o|0;z=ha((z|0)>41?z:41,e)|0;j[a+568+(f<<1)>>1]=-1;k[a+712+(f<<2)>>2]=0;k[a+808+(f<<2)>>2]=0;k[a+712+(h<<2)>>2]=e;k[a+808+(h<<2)>>2]=z;if(!(c|(e|0)>9&b^1))if(g){h=Ec(i)|0;z=Ec(n)|0;k[s>>2]=h;k[s+4>>2]=d;k[s+8>>2]=m;k[s+12>>2]=z;pe(941016,985398,s);break}else{n=Ec(n)|0;z=Ec(i)|0;k[t>>2]=n;k[t+4>>2]=p;k[t+8>>2]=o;k[t+12>>2]=z;pe(941016,985398,t);break}}while(0);q=q+1|0}while((q|0)!=24);f=0;do{d=a+568+(f<<1)|0;z=j[d>>1]|0;e=z&65535;if(((z<<16>>16!=-1?(u=k[a+616+(f<<2)>>2]|0,v=a+808+(f<<2)|0,u|0):0)?(w=(k[v>>2]|0)/(u|0)|0,(w|0)<=40):0)?(j[d>>1]=-1,k[a+712+(f<<2)>>2]=0,k[v>>2]=0,!(c|(u|0)>9&b^1)):0){z=Ec(e)|0;k[x>>2]=z;k[x+4>>2]=w;k[x+8>>2]=u;pe(941016,985425,x)}f=f+1|0}while((f|0)!=24);r=y;return}function _b(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;e=c+616+(b<<2)|0;k[e>>2]=(k[c+616+(a<<2)>>2]|0)+(k[e>>2]|0);e=c+712+(b<<2)|0;d=c+712+(a<<2)|0;k[e>>2]=(k[d>>2]|0)+(k[e>>2]|0);e=c+808+(b<<2)|0;b=c+808+(a<<2)|0;k[e>>2]=(k[b>>2]|0)+(k[e>>2]|0);j[c+568+(a<<1)>>1]=-1;k[d>>2]=0;k[b>>2]=0;return}function $b(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,l=0,n=0,o=0;j=r;r=r+16|0;i=j;f=b^1|c;g=0;do{d=m[a+568+(g<<1)>>1]|0;b=Fc(d)|0;a:do if(b|0){h=g;do{h=h+1|0;if((h|0)>=24)break a;e=m[a+568+(h<<1)>>1]|0}while((Fc(e)|0)!=(b|0));b=(k[a+616+(g<<2)>>2]|0)<(k[a+616+(h<<2)>>2]|0);c=b?g:h;if(!f){l=k[a+616+(c<<2)>>2]|0;n=(k[a+808+(c<<2)>>2]|0)/((l|0?l:1)|0)|0;o=Ec(b?d:e)|0;e=Ec(b?e:d)|0;k[i>>2]=o;k[i+4>>2]=n;k[i+8>>2]=l;k[i+12>>2]=e;pe(941016,985446,i)}_b(c,b?h:g,a)}while(0);g=g+1|0}while((g|0)!=24);r=j;return}function ac(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,l=0,m=0;l=r;r=r+48|0;h=l+32|0;j=l+24|0;g=l+16|0;f=l;se(985487,34,1,941016);e=k[a>>2]|0;if((e|0)!=26){m=Dc(e)|0;e=i[d>>0]|0?1097857:985485;d=k[b>>2]|0;k[f>>2]=m;k[f+4>>2]=e;k[f+8>>2]=d;pe(941016,985522,f)}e=k[a+4>>2]|0;if((e|0)!=26){f=Dc(e)|0;m=k[b+4>>2]|0;k[g>>2]=f;k[g+4>>2]=m;pe(941016,985535,g)}e=k[a+8>>2]|0;if((e|0)!=26){g=Dc(e)|0;m=k[b+8>>2]|0;k[j>>2]=g;k[j+4>>2]=m;pe(941016,985535,j)}k[h>>2]=k[c>>2];pe(941016,985546,h);se(1017206,5,1,941016);r=l;return}function bc(a,b,c,d,e,f,g,h){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;var l=0.0,m=0,n=0,o=0,q=0,r=0,s=0,t=0,u=0,v=0;k[c>>2]=0;n=c+4|0;k[n>>2]=0;r=c+8|0;k[r>>2]=0;k[d>>2]=26;m=d+4|0;k[m>>2]=26;q=d+8|0;k[q>>2]=26;k[e>>2]=0;u=e+4|0;k[u>>2]=0;v=e+8|0;k[v>>2]=0;o=f+8|0;s=f+16|0;k[f>>2]=0;k[f+4>>2]=0;k[f+8>>2]=0;k[f+12>>2]=0;k[f+16>>2]=0;k[f+20>>2]=0;k[g>>2]=b;i[h>>0]=0;t=j[a+568>>1]|0;switch(t<<16>>16){case 26:case -1:{f=0;break}default:{k[d>>2]=t&65535;d=k[a+616>>2]|0;k[c>>2]=(k[a+808>>2]|0)/((d|0?d:1)|0)|0;if((d|0)<1)l=0.0;else l=+((k[a+712>>2]<<10|0)/(d|0)|0|0);p[f>>3]=l;f=d}}d=j[a+570>>1]|0;switch(d<<16>>16){case 26:case -1:{c=0;break}default:{k[m>>2]=d&65535;d=k[a+620>>2]|0;k[n>>2]=(k[a+812>>2]|0)/((d|0?d:1)|0)|0;if((d|0)<1)l=0.0;else l=+((k[a+716>>2]<<10|0)/(d|0)|0|0);p[o>>3]=l;c=d}}d=j[a+572>>1]|0;switch(d<<16>>16){case 26:case -1:{d=0;break}default:{k[q>>2]=d&65535;d=k[a+624>>2]|0;k[r>>2]=(k[a+816>>2]|0)/((d|0?d:1)|0)|0;if((d|0)<1)l=0.0;else l=+((k[a+720>>2]<<10|0)/(d|0)|0|0);p[s>>3]=l}}c=c+f|0;d=d+c|0;if((d|0)>(b|0)){k[g>>2]=d;b=d}r=(b|0)<1?1:b;f=(f*100|0)/(r|0)|0;k[e>>2]=f;s=(c*100|0)/(r|0)|0;c=((d*100|0)/(r|0)|0)-s|0;k[v>>2]=c;d=s-f|0;k[u>>2]=d;if((d|0)<(c|0)){d=d+1|0;k[u>>2]=d;k[v>>2]=c+-1}if((f|0)<(d|0)){k[e>>2]=f+1;k[u>>2]=d+-1}k[g>>2]=b;switch(t<<16>>16){case 26:case -1:{d=0;break}default:{d=k[a+616>>2]|0;d=((k[a+808>>2]|0)/((d|0?d:1)|0)|0|0)>40&1}}i[h>>0]=d;i[h>>0]=(100-(k[e>>2]|0)-(k[u>>2]|0)-(k[v>>2]|0)|0)>20?0:d;return}function cc(a,b,c,d,e,f,g){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0;y=r;r=r+32|0;w=y+8|0;v=y;q=y+12|0;k[q>>2]=k[1971];k[q+4>>2]=k[1972];k[q+8>>2]=k[1973];h=k[c>>2]|0;k[d>>2]=k[b>>2];i[e>>0]=(h|0)>1&1;j=0;m=0;l=0;p=3;while(1){if((k[b+(m<<2)>>2]|0)==25){o=(k[c+(m<<2)>>2]|0)+l|0;n=m+1|0;if((n|0)<3){h=m;j=n;while(1){k[q+(h<<2)>>2]=k[q+(j<<2)>>2];h=j+1|0;if((h|0)==3)break;else{u=j;j=h;h=u}}j=k[q>>2]|0}l=p+-1|0;h=((k[c>>2]|0)*100|0)/(101-o|0)|0;k[d>>2]=k[b+(j<<2)>>2];if((k[c+(j<<2)>>2]|0)<2){i[e>>0]=0;m=n;s=j;t=o;u=l}else{m=n;s=j;t=o;u=l}}else{m=m+1|0;s=j;t=l;u=p}if((m|0)==3)break;else{j=s;l=t;p=u}}o=k[q+4>>2]|0;p=k[c+(o<<2)>>2]|0;n=ha(p,a)|0;o=b+(o<<2)|0;l=k[b+(s<<2)>>2]|0;m=(l|0)==0;a:do if(m){j=k[o>>2]|0;switch(j|0){case 26:case 0:{x=24;break a}default:{}}if((n|0)>1499&(p|0)>16){h=(p*100|0)/(101-t-(k[c+(s<<2)>>2]|0)|0)|0;k[d>>2]=j;if((p|0)<2)i[e>>0]=0}else x=23}else{b=l+-4|0;if(b>>>0<11?(1035>>>(b&2047)&1)!=0:0){j=k[o>>2]|0;if(j>>>0<15){if(16561>>>(j&32767)&1){x=23;break}}else if((j|0)==26){x=23;break}if((n|0)>1499&(p|0)>19){h=(p*100|0)/(101-t-(k[c+(s<<2)>>2]|0)|0)|0;k[d>>2]=j;if((p|0)<2)i[e>>0]=0}else x=23}else x=23}while(0);if((x|0)==23){j=k[o>>2]|0;x=24}do if((x|0)==24){if(!j){if(m)break;h=((k[c+(s<<2)>>2]|0)*100|0)/(101-t-p|0)|0;break}x=j+-4|0;if(x>>>0<11?(1035>>>(x&2047)&1)!=0:0){if(l>>>0<15?16561>>>(l&32767)&1:0)break;h=((k[c+(s<<2)>>2]|0)*100|0)/(101-t-p|0)|0}}while(0);if((h|0)<26){if(!(f^1|g)){x=Ec(k[d>>2]|0)|0;k[v>>2]=x;k[v+4>>2]=h;pe(941016,985557,v)}k[d>>2]=26;i[e>>0]=0}if((h|0)<51)i[e>>0]=0;if((100-(k[c>>2]|0)-(k[c+4>>2]|0)-(k[c+8>>2]|0)|0)>20)i[e>>0]=0;if(!u){if(!(f^1|g)){x=Ec(k[d>>2]|0)|0;k[w>>2]=x;pe(941016,985593,w)}k[d>>2]=26;i[e>>0]=0}r=y;return}function dc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;if(Jc(a)|0){d=c+16|0;e=k[d>>2]|0;k[c+20+(e<<2)>>2]=b;k[d>>2]=e+1&3}if(Kc(a)|0){e=c+36|0;d=k[e>>2]|0;k[c+40+(d<<2)>>2]=b;k[e>>2]=d+1&3}return}function ec(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0;d=pb(b,1)|0;if(Jc(a)|0?Jc(b)|0:0){e=c+56|0;f=k[e>>2]|0;k[c+60+(f<<2)>>2]=d;k[e>>2]=f+1&3}if(Kc(a)|0?Kc(b)|0:0){f=c+76|0;e=k[f>>2]|0;k[c+80+(e<<2)>>2]=d;k[f>>2]=e+1&3}return}function fc(a,b){a=a|0;b=b|0;var c=0,d=0;switch(a|0){case 16:{ec(16,69,b);break}case 69:{ec(69,16,b);break}default:{c=Fc(a)|0;if(c|0){d=0;do{if(!((d|0)==(a|0)|(c|0)!=(Fc(d)|0)))ec(a,d,b);d=d+1|0}while((d|0)!=512)}}}return}function gc(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0,l=0,n=0,o=0,p=0;p=r;r=r+64|0;h=p+8|0;g=p;n=p+32|0;o=p+16|0;l=p+12|0;k[n>>2]=0;if(!c){Rb(o,a,b);Lb(o,n);if(i[e+5>>0]|0?(c=i[o>>0]|0,f=(c&1)==0,(f?(c&255)>>>1:k[o+4>>2]|0)|0):0){c=k[e>>2]|0;k[g>>2]=f?o+1|0:k[o+8>>2]|0;pe(c,985624,g)}Ae(o)}if(d|0){f=k[d>>2]|0;if(f|0?i[f>>0]|0:0)Mb(f,n);f=k[d+4>>2]|0;if(f|0?i[f>>0]|0:0)Nb(f,n);f=k[d+8>>2]|0;if((f|0)!=23)Ob(f,n);f=k[d+12>>2]|0;if((f|0)!=26)Pb(f,n)}Ab(n);if(i[e+5>>0]|0){Qb(o,n);d=i[o>>0]|0;f=(d&1)==0;if((f?(d&255)>>>1:k[o+4>>2]|0)|0){d=k[e>>2]|0;k[h>>2]=f?o+1|0:k[o+8>>2]|0;pe(d,985648,h)}Ae(o)}d=k[n>>2]|0;c=(d|0)>0;if(c){b=0;do{a=j[n+4+(b<<1)>>1]|0;f=a&1023;a=a<<16>>16>>10;if((a|0)>0)dc(f,pb(f,a)|0,e);b=b+1|0}while((b|0)<(d|0))}k[l>>2]=0;hc(o,l);g=k[o>>2]|0;if(c){b=g+40|0;f=0;while(1){l=m[n+4+(f<<1)>>1]&1023;h=g+((Fc(l)|0)<<2)|0;k[h>>2]=(k[h>>2]|0)+1;switch(l|0){case 16:{k[b>>2]=(k[b>>2]|0)+1;break}case 69:{k[b>>2]=(k[b>>2]|0)+1;break}default:{}}f=f+1|0;if((f|0)==(d|0)){a=0;break}}do{l=j[n+4+(a<<1)>>1]|0;f=l&1023;a:do if((l<<16>>16>>10|0)>0){l=Fc(f)|0;if((l|0)>0?(k[g+(l<<2)>>2]|0)==1:0)fc(f,e);switch(f|0){case 16:case 69:break;default:break a}if((k[b>>2]|0)==1)fc(f,e)}while(0);a=a+1|0}while((a|0)!=(d|0))}b=g;if(g|0){f=o+4|0;a=k[f>>2]|0;if((a|0)!=(g|0))k[f>>2]=a+(~((a+-4-b|0)>>>2)<<2);Qe(g)}r=p;return}function hc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0;k[a>>2]=0;e=a+4|0;k[e>>2]=0;k[a+8>>2]=0;ic(a);c=k[e>>2]|0;a=11;d=c;while(1){k[d>>2]=k[b>>2];a=a+-1|0;if(!a)break;else d=d+4|0}k[e>>2]=c+44;return}function ic(a){a=a|0;var b=0;b=gf(44)|0;k[a+4>>2]=b;k[a>>2]=b;k[a+8>>2]=b+44;return}function jc(a){a=a|0;var b=0,c=0,d=0;c=k[a>>2]|0;d=c;if(c|0){a=a+4|0;b=k[a>>2]|0;if((b|0)!=(c|0))k[a>>2]=b+(~((b+-4-d|0)>>>2)<<2);Qe(c)}return}function kc(a,b,c,d,e,f,g,h,j,l){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;j=j|0;l=l|0;var m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,L=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0;U=r;r=r+3488|0;S=U+3112|0;O=U+3104|0;F=U+3096|0;E=U+3088|0;R=U+3080|0;L=U+3072|0;I=U+3064|0;H=U+3056|0;J=U+3040|0;C=U+3032|0;p=U+3024|0;o=U+3016|0;D=U+2112|0;m=U+3468|0;n=U+3308|0;z=U+3320|0;q=U;T=U+3168|0;A=U+3144|0;y=U+3136|0;G=U+3124|0;Q=U+3120|0;k[f>>2]=26;K=f+4|0;k[K>>2]=26;M=f+8|0;k[M>>2]=26;k[g>>2]=0;N=g+4|0;k[N>>2]=0;P=g+8|0;k[P>>2]=0;k[h>>2]=0;k[h+4>>2]=0;k[h+8>>2]=0;k[h+12>>2]=0;k[h+16>>2]=0;k[h+20>>2]=0;k[j>>2]=0;i[l>>0]=0;if(e&8192|0){ze(D,a,b);if(!(e&512)){lc(n);B=(i[n>>0]&1)==0?n+1|0:k[n+8>>2]|0;k[p>>2]=b;k[p+4>>2]=B;pe(941016,985693,p);Ae(n)}else{mc(m);B=(i[m>>0]&1)==0?m+1|0:k[m+8>>2]|0;k[o>>2]=b;k[o+4>>2]=B;pe(941016,985674,o);Ae(m)}Ae(D)}if(!b)m=26;else{pd(D);k[z>>2]=941016;i[z+4>>0]=e>>>8&1;w=e>>>9&1;i[z+5>>0]=w;x=z+6|0;i[x>>0]=e>>>10&1;i[z+7>>0]=e>>>11&1;k[z+12>>2]=26;t=z+8|0;k[t>>2]=0;k[z+140>>2]=7896;o=z+144|0;k[o>>2]=0;m=z+16|0;n=m+120|0;do{k[m>>2]=0;m=m+4|0}while((m|0)<(n|0));u=e>>>12;gc(a,b,c,d,z);kd(q);kd(q+528|0);kd(q+1056|0);kd(q+1584|0);uc(T,a,b,c);k[o>>2]=T;n=A+4|0;o=A+12|0;k[A>>2]=0;k[A+4>>2]=0;k[A+8>>2]=0;k[A+12>>2]=0;k[A+16>>2]=26;k[y>>2]=0;s=jf(16384)|0;p=(e&4|0)!=0;if(p)qf(s|0,0,16384)|0;q=(e&2|0)==0;v=(e&1|0)!=0;B=0;while(1){if(!(Ac(T,A)|0)){o=21;break}m=k[n>>2]|0;if(q){if(!(v|(m|0)<2049)?Yb(k[A>>2]|0,m)|0:0){o=15;break}}else{m=Xb(k[A>>2]|0,m)|0;k[n>>2]=m}if(p){m=Wb(k[A>>2]|0,m,y,s)|0;k[n>>2]=m}k[t>>2]=k[o>>2];jd(A,z,D);B=m+B|0}if((o|0)==15){if(w|0){k[C>>2]=B;pe(941016,985708,C)}kf(s);m=kc(a,b,c,d,e|2,f,g,h,j,l)|0}else if((o|0)==21){kf(s);n=(w|0)!=0;m=u&1;if(n&(m|0)==0){if(!(i[x>>0]|0))se(1017206,5,1,941016);td(D)}p=(m|0)!=0;$b(D,n,p);sd(D);bc(D,B,G,f,g,h,j,l);do if(!(v|(B|0)<257)){if(i[l>>0]|0){m=k[g>>2]|0;if((m|0)>69){o=29;break}if(((k[N>>2]|0)+m|0)>92){o=29;break}}if(!(p|n^1))ac(f,g,j,l);if((B|0)<256){if(n){k[E>>2]=B;pe(941016,985862,E)}m=kc(a,b,c,d,e|93,f,g,h,j,l)|0;break}else{if(n){k[F>>2]=B;pe(941016,985937,F)}m=kc(a,b,c,d,e|13,f,g,h,j,l)|0;break}}else o=29;while(0);if((o|0)==29){Zb(D,n,p);sd(D);bc(D,B,G,f,g,h,j,l);cc(B,f,g,Q,l,n,p);m=n^1;if(!(p|m)){o=0;do{n=k[f+(o<<2)>>2]|0;if((n|0)!=26){j=Ec(n)|0;E=k[G+(o<<2)>>2]|0;F=k[g+(o<<2)>>2]|0;k[J>>2]=j;k[J+4>>2]=E;k[J+8>>2]=F;pe(941016,985771,J)}o=o+1|0}while((o|0)!=3);k[H>>2]=B;pe(941016,985761,H);H=Dc(k[Q>>2]|0)|0;J=i[l>>0]|0?32:42;k[I>>2]=H;k[I+4>>2]=J;pe(941016,985785,I);se(985793,9,1,941016)}if(m|p^1)m=k[Q>>2]|0;else{se(985803,37,1,941016);m=k[f>>2]|0;if((m|0)!=26){J=Ec(m)|0;g=k[g>>2]|0;k[L>>2]=J;k[L+4>>2]=g;pe(941016,985841,L)}m=k[K>>2]|0;if((m|0)!=26){L=Ec(m)|0;N=k[N>>2]|0;k[O>>2]=L;k[O+4>>2]=N;pe(941016,985841,O)}m=k[M>>2]|0;if((m|0)!=26){O=Ec(m)|0;P=k[P>>2]|0;k[S>>2]=O;k[S+4>>2]=P;pe(941016,985841,S)}m=k[Q>>2]|0;Q=Dc(m)|0;S=i[l>>0]|0?32:42;k[R>>2]=Q;k[R+4>>2]=S;pe(941016,985785,R);se(1017206,5,1,941016)}}}vc(T)}r=U;return m|0}function lc(a){a=a|0;ze(a,1097857,0);return}function mc(a){a=a|0;ze(a,1097857,0);return}function nc(a){a=a|0;a:do if(a>>>0>=256){if(a>>>0>=55296){switch(a&-16|0){case 64992:case 64976:{a=65533;break a}default:{}}if((a&65534|0)==65534)a=65533;else a=(a+-57344|0)>>>0<1056768?a:65533}}else a=k[7932+(a<<2)>>2]|0;while(0);return a|0}function oc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0;a:do if((b|0)>0){g=0;c=0;d=0;while(1){f=i[a+g>>0]|0;if((f&-64)<<24>>24!=-128){e=(d|0)>7|((c|0)>24?1:((l[993817+((f&255)>>>4)>>0]|0)+g|0)>(b|0));if(e)break a;else d=(e&1^1)+d|0}switch(f<<24>>24){case 60:{f=1097824+c|0;i[f>>0]=38;i[f+1>>0]=108;i[f+2>>0]=116;i[f+3>>0]=59;c=c+4|0;break}case 62:{f=1097824+c|0;i[f>>0]=38;i[f+1>>0]=103;i[f+2>>0]=116;i[f+3>>0]=59;c=c+4|0;break}case 38:{f=1097824+c|0;i[f>>0]=i[993833]|0;i[f+1>>0]=i[993834]|0;i[f+2>>0]=i[993835]|0;i[f+3>>0]=i[993836]|0;i[f+4>>0]=i[993837]|0;c=c+5|0;break}case 39:{f=1097824+c|0;i[f>>0]=i[993839]|0;i[f+1>>0]=i[993840]|0;i[f+2>>0]=i[993841]|0;i[f+3>>0]=i[993842]|0;i[f+4>>0]=i[993843]|0;i[f+5>>0]=i[993844]|0;c=c+6|0;break}case 34:{f=1097824+c|0;i[f>>0]=i[993846]|0;i[f+1>>0]=i[993847]|0;i[f+2>>0]=i[993848]|0;i[f+3>>0]=i[993849]|0;i[f+4>>0]=i[993850]|0;i[f+5>>0]=i[993851]|0;c=c+6|0;break}default:{i[1097824+c>>0]=f;c=c+1|0}}g=g+1|0;if((g|0)>=(b|0))break a}}else c=0;while(0);i[1097824+c>>0]=0;return}function pc(a,b){a=a|0;b=b|0;do if(b>>>0>=128){if(b>>>0<2048){i[a>>0]=b>>>6|192;i[a+1>>0]=b&63|128;b=2;break}b=b>>>0>1114111?65533:b;if(b>>>0<65536){i[a>>0]=b>>>12|224;i[a+1>>0]=b>>>6&63|128;i[a+2>>0]=b&63|128;b=3;break}else{i[a>>0]=b>>>18|240;i[a+1>>0]=b>>>12&63|128;i[a+2>>0]=b>>>6&63|128;i[a+3>>0]=b&63|128;b=4;break}}else{i[a>>0]=b;b=1}while(0);return b|0}function qc(a,b){a=a|0;b=b|0;var c=0,d=0;d=r;r=r+16|0;c=d;if((b|0)>15)b=-1;else{tf(c|0,a|0,b|0)|0;i[c+b>>0]=0;b=Lc(c)|0;if((b|0)>-1)b=k[8956+(b<<3)+4>>2]|0;else b=-1}r=d;return b|0}function rc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,j=0,l=0;j=a+b|0;a:do if((b|0)!=0?(i[a>>0]|0)==38:0){k[c>>2]=1;f=a+1|0;d=i[f>>0]|0;if(d<<24>>24==35){if((b|0)<4){d=-1;break}d=a+2|0;b:do switch(i[d>>0]|0){case 88:case 120:{d=a+3|0;do{if((i[d>>0]|0)!=48)break;d=d+1|0}while(d>>>0<j>>>0);if((d|0)==(j|0)){d=-1;break a}f=i[d>>0]|0;if((f+-48&255)>=10)switch(f<<24>>24){case 65:case 66:case 67:case 68:case 69:case 70:case 97:case 98:case 99:case 100:case 101:case 102:break;default:{d=-1;break a}}c:do if(d>>>0<j>>>0){e=f;b=d;while(1){if((e+-48&255)>=10)switch(e<<24>>24){case 65:case 66:case 67:case 68:case 69:case 70:case 97:case 98:case 99:case 100:case 101:case 102:break;default:{h=b;break c}}b=b+1|0;if(b>>>0>=j>>>0){h=b;break c}e=i[b>>0]|0}}else h=d;while(0);b=h;g=b-d|0;if((g|0)>=8?!((g|0)==8&f<<24>>24<56):0){d=65533;break b}d:do if(d>>>0<h>>>0){g=f;e=0;while(1){f=e<<4;e=g<<24>>24;do if((g+-48&255)>=10)if((g+-97&255)<6){e=e+-87|0;break}else{e=(g+-65&255)<6?e+-55|0:0;break}else e=e+-48|0;while(0);f=e+f|0;e=d+1|0;if((e|0)==(h|0)){d=f;break d}d=e;g=i[e>>0]|0;e=f}}else d=0;while(0);d=nc(d)|0;break}case 48:{while(1){d=d+1|0;if(d>>>0>=j>>>0){l=32;break b}if((i[d>>0]|0)!=48){l=32;break}}break}default:l=32}while(0);do if((l|0)==32){if((d|0)==(j|0)){d=-1;break a}f=i[d>>0]|0;if((f+-48&255)>=10){d=-1;break a}e:do if(d>>>0<j>>>0){b=d;while(1){b=b+1|0;if(b>>>0>=j>>>0){g=b;break e}if(((i[b>>0]|0)+-48&255)>=10){g=b;break}}}else g=d;while(0);b=g;e=b-d|0;if((e|0)>=9){if((e|0)!=10){d=65533;break}if((Yd(d,993853,10)|0)>=1){d=65533;break}}if(d>>>0<g>>>0){e=(f<<24>>24)+-48|0;d=d+1|0;if((d|0)==(g|0))d=e;else{f=d;d=e;do{d=(d*10|0)+-48+(i[f>>0]|0)|0;f=f+1|0}while((f|0)!=(g|0))}}else d=0;d=nc(d)|0}while(0);if((d|0)==-1|b>>>0>j>>>0){d=-1;break}}else{g=f;if((b|0)>1?(d+-48&255)<10|((d&-33)+-65&255)<26:0){d=f;do{d=d+1|0;if(d>>>0>=j>>>0)break;h=i[d>>0]|0}while((h+-48&255)<10|((h&-33)+-65&255)<26);b=d;e=d}else{b=g;e=f}d=qc(f,b-g|0)|0;if((d|0)<0){d=-1;break}if((d|0)>255){if(e>>>0>=j>>>0){d=-1;break}if((i[e>>0]|0)!=59){d=-1;break}}}e=b;if(e>>>0<j>>>0?(i[e>>0]|0)==59:0)b=e+1|0;k[c>>2]=b-a}else l=3;while(0);if((l|0)==3){k[c>>2]=0;d=-1}return d|0}function sc(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;b=rc(a,b,d)|0;if((b|0)>0)b=pc(c,b)|0;else{k[d>>2]=1;b=0}k[e>>2]=b;return}function tc(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0;f=a+b|0;a:do if((b|0)>0){d=a;e=1008264;while(1){e=l[e+(l[1009064+(l[d>>0]|0)>>0]|0)>>0]|0;if((e|0)<=(c|0))break;d=d+1|0;if(d>>>0<f>>>0)e=1008264+(e*20|0)|0;else break a}switch(e|0){case 0:case 2:{b=d-a|0;break a}default:{}}b=d-a|0;while(1){d=b+-1|0;if((b|0)<=1)break a;if((i[a+d>>0]|0)==60)break;else b=d}}while(0);return b|0}function uc(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;k[a>>2]=b;k[a+4>>2]=b;k[a+8>>2]=b+c;k[a+12>>2]=c;i[a+16>>0]=d&1;i[a+28>>0]=1;i[a+29>>0]=1;k[a+32>>2]=1;c=a+36|0;Nc(c);d=a+88|0;Nc(d);b=jf(40960)|0;k[a+20>>2]=b;b=jf(61440)|0;k[a+24>>2]=b;Oc(c);Oc(d);return}function vc(a){a=a|0;var b=0;b=k[a+20>>2]|0;if(b|0)kf(b);b=k[a+24>>2]|0;if(b|0)kf(b);Pc(a+88|0);Pc(a+36|0);return}function wc(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0;u=r;r=r+32|0;o=u+12|0;s=u+8|0;q=u+4|0;n=u;p=u+16|0;k[q>>2]=0;a:do if((c|0)>0){m=a+16|0;j=a+32|0;a=0;e=0;while(1){wd(b+e|0,c-e|0,o);e=(k[o>>2]|0)+e|0;if((e|0)>=(c|0)){e=c;a=0;break a}g=b+e|0;h=i[g>>0]|0;f=h&255;b:do if(((h&-32)<<24>>24==32?(i[1009320+f>>0]|0)!=0:0)?(i[m>>0]|0)==0:0)switch(h<<24>>24){case 60:{a=tc(g,c-e|0,k[j>>2]|0)|0;k[q>>2]=a;break b}case 62:{k[q>>2]=1;a=1;break b}case 38:{sc(g,c-e|0,p,q,n);k[o>>2]=l[1009576+(l[p>>0]|0)>>0];k[s>>2]=p;a=(vd(s,o)|0)&255;t=12;break b}default:break b}else t=11;while(0);if((t|0)==11){a=l[1009576+f>>0]|0;k[q>>2]=a;k[o>>2]=a;k[s>>2]=g;a=(vd(s,o)|0)&255;t=12}if((t|0)==12){t=0;if(a|0)break a;a=k[q>>2]|0}e=a+e|0;if((e|0)>=(c|0)){a=0;break}}}else{e=0;a=0}while(0);k[d>>2]=a;r=u;return e|0}function xc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0;w=r;r=r+16|0;p=w+4|0;o=w;t=a+20|0;m=k[t>>2]|0;k[b>>2]=m;u=b+4|0;k[u>>2]=0;v=a+4|0;k[b+8>>2]=(k[v>>2]|0)-(k[a>>2]|0);k[b+12>>2]=0;k[b+16>>2]=26;l=b+20|0;i[l>>0]=0;s=a+12|0;n=k[s>>2]|0;n=(n+-40928|0)>>>0<40928?(n|0)/2|0:40896;i[m>>0]=32;i[(k[t>>2]|0)+1>>0]=0;b=k[s>>2]|0;if((b|0)<1)b=0;else{m=a+16|0;j=a+32|0;c=0;g=b;h=0;a=1;b=0;while(1){d=k[v>>2]|0;e=d+b|0;f=i[e>>0]|0;f=f<<24>>24==13?10:f;if(((f&-32)<<24>>24==32?(i[1009320+(f&255)>>0]|0)!=0:0)?(i[m>>0]|0)==0:0){a:do switch(f<<24>>24){case 60:{b:do if((b|0)<(g+-3|0))switch(i[d+(b+1)>>0]|32|0){case 112:{d=(i[d+(b+2)>>0]|0)<64?10:32;break b}case 98:{if((i[d+(b+2)>>0]|32|0)!=114){d=32;break b}d=(i[d+(b+3)>>0]|0)<64?10:32;break b}case 116:{if((i[d+(b+2)>>0]|32|0)!=114){d=32;break b}d=(i[d+(b+3)>>0]|0)<64?10:32;break b}default:{d=32;break b}}else d=32;while(0);c=(tc(e,g-b|0,k[j>>2]|0)|0)+1|0;k[p>>2]=c;if(h)switch(d<<24>>24){case 10:case 32:{e=d;d=1;break a}default:{}}i[(k[t>>2]|0)+a>>0]=d;e=d;d=d<<24>>24==32|d<<24>>24==10;a=a+1|0;break}case 62:{k[p>>2]=1;i[(k[t>>2]|0)+a>>0]=62;c=1;e=62;d=h;a=a+1|0;break}case 38:{sc(e,g-b|0,(k[t>>2]|0)+a|0,p,o);c=k[p>>2]|0;e=38;d=h;a=(k[o>>2]|0)+a|0;break}default:{e=f;d=h}}while(0);h=c;b=c+b|0}else{if(h)switch(f<<24>>24){case 10:case 32:{d=1;break}default:q=22}else q=22;if((q|0)==22){q=0;i[(k[t>>2]|0)+a>>0]=f;d=f<<24>>24==32|f<<24>>24==10;a=a+1|0}h=c;e=f;b=b+1|0}if(!((a|0)<(n|0)|e<<24>>24!=10&e<<24>>24!=32)){q=25;break}if((a|0)>40927){q=28;break}g=k[s>>2]|0;if((g|0)<=(b|0))break;else{c=h;h=d}}if((q|0)==25)i[l>>0]=1;else if((q|0)==28)i[l>>0]=1;d=k[v>>2]|0;c:do if((b|0)>0){c=b;while(1){if((i[d+c>>0]&-64)<<24>>24!=-128){b=c;break c}b=c+-1|0;a=a+-1|0;if((c|0)>1)c=b;else break}}while(0);k[v>>2]=d+b;k[s>>2]=(k[s>>2]|0)-b;i[(k[t>>2]|0)+a>>0]=32;i[(k[t>>2]|0)+(a+1)>>0]=32;i[(k[t>>2]|0)+(a+2)>>0]=32;i[(k[t>>2]|0)+(a+3)>>0]=0;k[u>>2]=a;b=1}r=w;return b|0}function yc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,j=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0;C=r;r=r+32|0;s=C+16|0;v=C+12|0;c=C+8|0;u=C+4|0;t=C;if(!(i[a+28>>0]|0))b=xc(a,b)|0;else{z=a+20|0;y=k[z>>2]|0;k[b>>2]=y;A=b+4|0;k[A>>2]=0;B=a+4|0;o=b+8|0;k[o>>2]=(k[B>>2]|0)-(k[a>>2]|0);d=b+12|0;k[d>>2]=0;k[b+16>>2]=26;p=b+20|0;i[p>>0]=0;x=a+12|0;q=k[x>>2]|0;q=(q+-40928|0)>>>0<40928?(q|0)/2|0:40896;k[u>>2]=0;k[t>>2]=0;i[y>>0]=32;i[(k[z>>2]|0)+1>>0]=0;y=a+36|0;Oc(y);Vc(y,k[o>>2]|0);b=wc(a,k[B>>2]|0,k[x>>2]|0,c)|0;k[B>>2]=(k[B>>2]|0)+b;k[x>>2]=(k[x>>2]|0)-b;if((b|0)==1)Tc(y,1);else{Vc(y,b);Uc(y,1)}b=k[x>>2]|0;if((b|0)<1){Rc(y);b=0}else{o=k[c>>2]|0;k[d>>2]=o;m=a+16|0;n=a+29|0;j=a+32|0;d=b;c=1;a=0;b=0;do{a:do if((d|0)>(b|0)){g=a;while(1){f=(k[B>>2]|0)+b|0;a=i[f>>0]|0;e=a&255;b:do if(((a&-32)<<24>>24==32?(i[1009320+e>>0]|0)!=0:0)?(i[m>>0]|0)==0:0){switch(a<<24>>24){case 62:case 60:{h=c;c=0;break a}case 38:break;default:break b}sc(f,d-b|0,(k[z>>2]|0)+c|0,u,t);g=(k[z>>2]|0)+c|0;k[s>>2]=l[1009576+(l[g>>0]|0)>>0];k[v>>2]=g;g=(vd(v,s)|0)&255}else w=15;while(0);if((w|0)==15){w=0;a=l[1009576+e>>0]|0;k[t>>2]=a;k[u>>2]=a;e=(k[z>>2]|0)+c|0;if((b|0)<(d+-3|0)){h=l[f>>0]|l[f+1>>0]<<8|l[f+2>>0]<<16|l[f+3>>0]<<24;i[e>>0]=h;i[e+1>>0]=h>>8;i[e+2>>0]=h>>16;i[e+3>>0]=h>>24}else tf(e|0,f|0,a|0)|0;g=(k[B>>2]|0)+b|0;k[s>>2]=l[1009576+(l[g>>0]|0)>>0];k[v>>2]=g;g=(vd(v,s)|0)&255}if((g|0)!=40&(g|0)!=(o|0)){if(!g){h=c;c=0;break a}d=k[u>>2]|0;h=(k[B>>2]|0)+b+d|0;k[s>>2]=l[1009576+(l[h>>0]|0)>>0];k[v>>2]=h;h=vd(v,s)|0;if(!(h<<24>>24==0|(h&255|0)==(o|0))?(i[n>>0]|0)!=0:0){h=c;c=g;break a}}else d=k[u>>2]|0;b=d+b|0;a=k[t>>2]|0;c=a+c|0;do if((d|0)!=(a|0))if((d|0)<(a|0)){Tc(y,d);Uc(y,a-d|0);break}else{Tc(y,a);Vc(y,d-a|0);break}else Tc(y,d);while(0);if((c|0)>40927)break;d=k[x>>2]|0;if((d|0)<=(b|0)){h=c;c=g;break a}}i[p>>0]=1;h=c;c=g}else{h=c;c=a}while(0);d=k[x>>2]|0;c:do if((d|0)>(b|0))while(1){wd((k[B>>2]|0)+b|0,d-b|0,s);d=k[s>>2]|0;k[u>>2]=d;b=d+b|0;Vc(y,d);d=k[x>>2]|0;if((d|0)<=(b|0)){a=c;break c}a=(k[B>>2]|0)+b|0;e=i[a>>0]|0;f=e&255;d:do if(((e&-32)<<24>>24==32?(i[1009320+f>>0]|0)!=0:0)?(i[m>>0]|0)==0:0)switch(e<<24>>24){case 60:{c=tc(a,d-b|0,k[j>>2]|0)|0;k[u>>2]=c;break d}case 62:{k[u>>2]=1;c=1;break d}case 38:{sc(a,d-b|0,(k[z>>2]|0)+h|0,u,t);c=(k[z>>2]|0)+h|0;k[s>>2]=l[1009576+(l[c>>0]|0)>>0];k[v>>2]=c;c=(vd(v,s)|0)&255;w=42;break d}default:{w=42;break d}}else w=41;while(0);if((w|0)==41){c=l[1009576+f>>0]|0;k[u>>2]=c;k[s>>2]=c;k[v>>2]=a;c=(vd(v,s)|0)&255;w=42}if((w|0)==42){w=0;if(c|0){a=c;break c}c=k[u>>2]|0}b=c+b|0;Vc(y,c);d=k[x>>2]|0;if((d|0)<=(b|0)){a=0;break}else c=0}else a=c;while(0);c=h+1|0;i[(k[z>>2]|0)+h>>0]=32;Uc(y,1);if((a|0)!=40&(a|0)!=(o|0))break;if((c|0)>=(q|0)){w=48;break}d=k[x>>2]|0}while((b|0)<(d|0));if((w|0)==48)i[p>>0]=1;e=k[x>>2]|0;e:do if((b|0)>0){a=(b|0)<(e|0);d=b;while(1){if(!a){b=d;break e}if((i[(k[B>>2]|0)+d>>0]&-64)<<24>>24!=-128){b=d;break e}b=d+-1|0;c=c+-1|0;if((d|0)>1)d=b;else break}}while(0);k[B>>2]=(k[B>>2]|0)+b;k[x>>2]=e-b;i[(k[z>>2]|0)+c>>0]=32;i[(k[z>>2]|0)+(c+1)>>0]=32;i[(k[z>>2]|0)+(c+2)>>0]=32;i[(k[z>>2]|0)+(c+3)>>0]=0;Uc(y,4);Rc(y);k[A>>2]=c;b=1}}r=C;return b|0}function zc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0;c=r;r=r+16|0;g=c+4|0;d=a+88|0;Oc(d);e=b+4|0;f=a+24|0;xd(k[b>>2]|0,(k[e>>2]|0)+3|0,k[f>>2]|0,61440,(i[a+16>>0]|0)!=0,c+8|0,g,c,d);a=k[g>>2]|0;i[(k[f>>2]|0)+a>>0]=0;k[b>>2]=k[f>>2];k[e>>2]=a+-3;Rc(d);r=c;return}function Ac(a,b){a=a|0;b=b|0;var c=0;c=yc(a,b)|0;zc(a,b);return c|0}function Bc(a){a=a|0;a=(a|0)<0?0:a;return k[15988+(((a|0)>101?0:a)<<2)>>2]|0}function Cc(a){a=a|0;a=(a|0)<0?0:a;return k[16396+(((a|0)>101?0:a)<<2)>>2]|0}function Dc(a){a=a|0;a=(a|0)<0?26:a;return k[11076+(((a|0)>613?26:a)<<2)>>2]|0}function Ec(a){a=a|0;a=(a|0)<0?26:a;return k[13532+(((a|0)>613?26:a)<<2)>>2]|0}function Fc(a){a=a|0;do switch(a|0){case 40:case 38:{a=1;break}case 105:{a=2;break}case 135:{a=2;break}case 17:{a=3;break}case 68:{a=3;break}case 84:{a=4;break}case 83:{a=4;break}case 78:{a=5;break}case 28:{a=5;break}case 29:{a=5;break}case 160:{a=5;break}case 35:{a=6;break}case 64:{a=6;break}case 51:{a=6;break}case 43:{a=6;break}case 10:{a=7;break}case 80:{a=7;break}case 1:{a=7;break}case 31:{a=8;break}case 14:{a=8;break}case 12:{a=8;break}case 143:{a=9;break}case 147:{a=9;break}default:a=0}while(0);return a|0}function Gc(a){a=a|0;if(a>>>0>101)a=26;else a=k[16804+(a<<2)>>2]|0;return a|0}function Hc(a){a=a|0;if((a|0)<512)a=i[992810+a>>0]|0;else a=0;return a|0}function Ic(a,b){a=a|0;b=b|0;do if(a>>>0<=101){if((k[16396+(a<<2)>>2]|0)>>>0<2){b=k[16804+(a<<2)>>2]|0;break}b=b&255;if((a|0)==1){b=m[941344+(b<<1)>>1]|0;break}else{b=m[941856+(b<<1)>>1]|0;break}}else b=26;while(0);return b|0}function Jc(a){a=a|0;if((a|0)<512)a=(m[941344+((l[992810+a>>0]|0)<<1)>>1]|0|0)==(a|0);else a=0;return a|0}function Kc(a){a=a|0;if((a|0)<512)a=(m[941856+((l[992810+a>>0]|0)<<1)>>1]|0|0)==(a|0);else a=0;return a|0}function Lc(a){a=a|0;var b=0,c=0,d=0,e=0;c=0;e=265;a:while(1)while(1){if((c|0)>=(e|0)){b=-1;break a}b=c+e>>1;d=_d(a,k[8956+(b<<3)>>2]|0)|0;if((d|0)<0){e=b;continue a}if((d|0)>0)c=b+1|0;else break a}return b|0}function Mc(a){a=a|0;switch(a|0){case 1:{a=0;break}case 3:{a=1;break}case 6:{a=2;break}default:a=3}return a|0}function Nc(a){a=a|0;var b=0;k[a>>2]=0;k[a+4>>2]=0;k[a+8>>2]=0;k[a+12>>2]=1;a=a+16|0;b=a+36|0;do{k[a>>2]=0;a=a+4|0}while((a|0)<(b|0));return}function Oc(a){a=a|0;var b=0;if(!(i[a>>0]&1)){i[a+1>>0]=0;i[a>>0]=0}else{i[k[a+8>>2]>>0]=0;k[a+4>>2]=0}k[a+12>>2]=1;a=a+16|0;b=a+36|0;do{k[a>>2]=0;a=a+4|0}while((a|0)<(b|0));return}function Pc(a){a=a|0;Ae(a);return}function Qc(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0,j=0;j=a+16|0;g=k[j>>2]|0;do if(g|0){h=a+12|0;b=k[h>>2]|0;if((b|0)==1){e=i[a>>0]|0;f=(e&1)==0;if(f)c=(e&255)>>>1;else c=k[a+4>>2]|0;if(c|0){if(f){c=(e&255)>>>1;d=a+1|0}else{c=k[a+4>>2]|0;d=k[a+8>>2]|0}d=i[d+(c+-1)>>0]|0;if((d&-64)<<24>>24==64?(g+(d&63)|0)>>>0<64:0){if(f){c=(e&255)>>>1;b=a+1|0}else{c=k[a+4>>2]|0;b=k[a+8>>2]|0}a=b+(c+-1)|0;i[a>>0]=(l[a>>0]|0)+g;k[j>>2]=0;break}}}if(g>>>0>63){c=g;d=0;e=30;while(1){b=c>>>e&63;if(d|(b|0)!=0){Ge(a,b&255);b=1}else b=0;if((e|0)<=6)break;c=k[j>>2]|0;d=b;e=e+-6|0}c=k[j>>2]|0;b=k[h>>2]|0}else c=g;Ge(a,(c&63|b<<6)&255);k[j>>2]=0}while(0);return}function Rc(a){a=a|0;Sc(a);a=a+20|0;k[a>>2]=0;k[a+4>>2]=0;k[a+8>>2]=0;k[a+12>>2]=0;k[a+16>>2]=0;k[a+20>>2]=0;return}function Sc(a){a=a|0;var b=0,c=0;if(!(k[a+16>>2]|0)){b=i[a>>0]|0;if(!(b&1))b=(b&255)>>>1;else b=k[a+4>>2]|0;if(!b)c=6}else c=6;if((c|0)==6){Tc(a,1);Qc(a)}return}function Tc(a,b){a=a|0;b=b|0;var c=0;do if(b|0){c=a+44|0;k[c>>2]=(k[c>>2]|0)+b;c=a+48|0;k[c>>2]=(k[c>>2]|0)+b;c=a+12|0;if((k[c>>2]|0)==1){a=a+16|0;k[a>>2]=(k[a>>2]|0)+b;break}else{Qc(a);k[c>>2]=1;k[a+16>>2]=b;break}}while(0);return}function Uc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0;do if(b|0){d=a+48|0;k[d>>2]=(k[d>>2]|0)+b;d=a+12|0;e=k[d>>2]|0;if((e|0)==2){a=a+16|0;k[a>>2]=(k[a>>2]|0)+b;break}c=a+16|0;if((b|0)==1&(e|0)==3?(k[c>>2]|0)==1:0){k[d>>2]=1;break}Qc(a);k[d>>2]=2;k[c>>2]=b}while(0);return}function Vc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0;do if(b|0){d=a+44|0;k[d>>2]=(k[d>>2]|0)+b;d=a+12|0;e=k[d>>2]|0;if((e|0)==3){a=a+16|0;k[a>>2]=(k[a>>2]|0)+b;break}c=a+16|0;if((b|0)==1&(e|0)==2?(k[c>>2]|0)==1:0){k[d>>2]=1;break}Qc(a);k[d>>2]=3;k[c>>2]=b}while(0);return}function Wc(a,b,c,d,e,f,g){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,l=0,n=0,o=0,p=0,q=0,s=0,t=0;t=r;r=r+16|0;l=t;od(f,l);h=k[l>>2]|0;o=Ic(a,h&255)|0;l=k[l+4>>2]|0;q=Ic(a,l&255)|0;n=f+16|0;if((d|0)>0)s=(m[n+(h<<1)>>1]<<10|0)/(d|0)|0;else s=0;p=(Mc(a)|0)+(o<<2)|0;p=j[(k[(k[e+140>>2]|0)+32>>2]|0)+(p<<1)>>1]|0;j[g>>1]=c;j[g+2>>1]=b;j[g+4>>1]=o;j[g+6>>1]=q;c=j[n+(h<<1)>>1]|0;j[g+8>>1]=c;h=j[n+(l<<1)>>1]|0;j[g+10>>1]=h;j[g+12>>1]=d;e=k[f+12>>2]|0;j[g+14>>1]=e;j[g+16>>1]=a;e=(nb(c&65535,h&65535,e&65535)|0)&255;h=g+18|0;i[h>>0]=e;e=Fc(o)|0;if(e|0?(e|0)==(Fc(q)|0):0)i[h>>0]=100;s=(ob(s,p)|0)&255;i[g+19>>0]=s;r=t;return}function Xc(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0;e=(k[a+8>>2]|0)==1;f=e?a+96|0:a+116|0;d=e?a+16|0:a+36|0;c=k[d+4>>2]|0;if(c|0)ib(c,b);c=k[d+8>>2]|0;if(c|0)ib(c,b);c=k[d+12>>2]|0;if(c|0)ib(c,b);c=k[d+16>>2]|0;if(c|0)ib(c,b);e=e?a+56|0:a+76|0;c=k[f+4>>2]|0;if(c|0)ib(c,b);c=k[f+8>>2]|0;if(c|0)ib(c,b);c=k[f+12>>2]|0;if(c|0)ib(c,b);c=k[f+16>>2]|0;if(c|0)ib(c,b);d=b+16|0;c=k[e+4>>2]|0;if(c|0)j[d+((c>>>8&255)<<1)>>1]=0;c=k[e+8>>2]|0;if(c|0)j[d+((c>>>8&255)<<1)>>1]=0;c=k[e+12>>2]|0;if(c|0)j[d+((c>>>8&255)<<1)>>1]=0;c=k[e+16>>2]|0;if(c|0)j[d+((c>>>8&255)<<1)>>1]=0;return}function Yc(a,b,c,d,e,f,g){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;var h=0,l=0,n=0,o=0,p=0,q=0,s=0,t=0;t=r;r=r+16|0;h=t;s=k[b+56064+(c<<2)>>2]|0;q=k[b+56064+(c+1<<2)>>2]|0;ld(f);k[e+16>>2]=0;k[e+20>>2]=0;if(i[d+7>>0]|0){p=k[d>>2]|0;k[h>>2]=s;k[h+4>>2]=q;pe(p,1017069,h)}k[e>>2]=s;k[e+12>>2]=q-s;if((q|0)>(s|0)){h=d+96|0;l=d+8|0;n=d+116|0;p=s;do{o=k[b+24056+(p<<3)+4>>2]|0;ib(o,f);e=b+24056+(p<<3)+2|0;c=j[e>>1]|0;if((c&65535)<2){md(f);c=j[e>>1]|0}if(c<<16>>16==3){e=(k[l>>2]|0)==1?h:n;c=k[e>>2]|0;k[e+4+(c<<2)>>2]=o;k[e>>2]=c+1&3}p=p+1|0}while((p|0)!=(q|0))}Xc(d,f);p=m[b+24056+(s<<3)>>1]|0;Wc(a,s,p,(m[b+24056+(q<<3)>>1]|0)-p|0,d,f,g);k[d+12>>2]=m[g+4>>1];r=t;return}function Zc(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0,i=0,l=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0,u=0,v=0,w=0,x=0;x=r;r=r+608|0;w=x+552|0;s=x+528|0;p=x;q=x+576|0;k[w>>2]=0;k[w+4>>2]=0;k[w+8>>2]=0;k[w+12>>2]=0;k[w+16>>2]=0;k[w+20>>2]=0;k[s>>2]=0;k[s+4>>2]=0;k[s+8>>2]=0;k[s+12>>2]=0;k[s+16>>2]=0;k[s+20>>2]=0;g=b+24|0;if((k[g>>2]|0)>0){i=s+12|0;l=s+16|0;m=s+4|0;n=s+20|0;o=s+8|0;t=0;do{kd(p);Yc(a,b,t,c,s,p,q);h=k[d>>2]|0;if((h|0)<50){u=d+4+(h*20|0)|0;f=q;v=u+20|0;do{j[u>>1]=j[f>>1]|0;u=u+2|0;f=f+2|0}while((u|0)<(v|0));h=(k[d>>2]|0)+1|0;k[d>>2]=h};k[w>>2]=k[s>>2];k[w+4>>2]=k[s+4>>2];k[w+8>>2]=k[s+8>>2];k[w+12>>2]=k[s+12>>2];k[w+16>>2]=k[s+16>>2];k[w+20>>2]=k[s+20>>2];k[s>>2]=(k[s>>2]|0)+(k[i>>2]|0);k[m>>2]=(k[m>>2]|0)+(k[l>>2]|0);k[o>>2]=(k[o>>2]|0)+(k[n>>2]|0);t=t+1|0}while((t|0)<(k[g>>2]|0))}else h=k[d>>2]|0;i=k[b+20>>2]|0;f=j[b+24056+(i<<3)>>1]|0;g=d+4+(h*20|0)|0;u=g;v=u+20|0;do{j[u>>1]=0;u=u+2|0}while((u|0)<(v|0));j[g>>1]=f;j[d+4+(h*20|0)+2>>1]=i;k[e>>2]=k[w>>2];k[e+4>>2]=k[w+4>>2];k[e+8>>2]=k[w+8>>2];k[e+12>>2]=k[w+12>>2];k[e+16>>2]=k[w+16>>2];k[e+20>>2]=k[w+20>>2];r=x;return}function _c(a,b){a=a|0;b=b|0;var c=0,d=0,e=0;if((k[a>>2]|0)>0){c=0;do{e=l[a+4+(c*20|0)+18>>0]|0;d=l[a+4+(c*20|0)+19>>0]|0;qd(b,j[a+4+(c*20|0)+4>>1]|0,m[a+4+(c*20|0)+12>>1]|0,m[a+4+(c*20|0)+8>>1]|0,e>>>0<d>>>0?e:d);c=c+1|0}while((c|0)<(k[a>>2]|0))}return}function $c(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0,s=0,t=0;p=r;r=r+112|0;o=p+96|0;n=p+80|0;l=p+64|0;h=p+48|0;g=p+32|0;i=p+16|0;e=p;t=Bc(k[c>>2]|0)|0;d=c+8|0;s=k[d>>2]|0;j=c+12|0;q=k[j>>2]|0;m=c+16|0;f=k[m>>2]|0;k[e>>2]=t;k[e+4>>2]=s;k[e+8>>2]=q;k[e+12>>2]=f;pe(a,1017096,e);e=c+4|0;a:do if((k[e>>2]|0)>0){f=0;do{if((f|0)<(k[d>>2]|0)){s=k[c+32+(f<<3)>>2]|0;t=k[c+32+(f<<3)+4>>2]|0;oc(b+s|0,6);k[i>>2]=f;k[i+4>>2]=s;k[i+8>>2]=(t|0)<0?(t&2147483647)+2e9|0:t;k[i+12>>2]=1097824;pe(a,1017160,i)}if((f|0)<(k[j>>2]|0)){s=k[c+8040+(f<<3)>>2]|0;t=k[c+8040+(f<<3)+4>>2]|0;oc(b+s|0,12);k[g>>2]=f;k[g+4>>2]=s;k[g+8>>2]=t;k[g+12>>2]=1097824;pe(a,1017175,g)}if((f|0)<(k[m>>2]|0)){s=k[c+16048+(f<<3)>>2]|0;t=k[c+16048+(f<<3)+4>>2]|0;oc(b+s|0,12);k[h>>2]=f;k[h+4>>2]=s;k[h+8>>2]=t;k[h+12>>2]=1097824;pe(a,1017191,h)}if((f|0)<(k[d>>2]|0))se(1017206,5,1,a);if((f|0)>50)break a;f=f+1|0}while((f|0)<(k[e>>2]|0))}while(0);d=k[d>>2]|0;if((d|0)>50){s=k[c+32+(d<<3)>>2]|0;t=k[c+32+(d<<3)+4>>2]|0;oc(b+s|0,6);k[l>>2]=d;k[l+4>>2]=s;k[l+8>>2]=(t|0)<0?(t&2147483647)+2e9|0:t;k[l+12>>2]=1097824;pe(a,1017160,l)}d=k[j>>2]|0;if((d|0)>50){s=k[c+8040+(d<<3)>>2]|0;t=k[c+8040+(d<<3)+4>>2]|0;oc(b+s|0,12);k[n>>2]=d;k[n+4>>2]=s;k[n+8>>2]=t;k[n+12>>2]=1097824;pe(a,1017175,n)}d=k[m>>2]|0;if((d|0)>50){s=k[c+16048+(d<<3)>>2]|0;t=k[c+16048+(d<<3)+4>>2]|0;oc(b+s|0,12);k[o>>2]=d;k[o+4>>2]=s;k[o+8>>2]=t;k[o+12>>2]=1097824;pe(a,1017191,o)}se(1017206,5,1,a);r=p;return}function ad(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,j=0,l=0,n=0,o=0;l=r;r=r+48|0;j=l+40|0;g=l+16|0;h=l+8|0;d=l;f=c+20|0;k[d>>2]=k[f>>2];pe(a,1017212,d);d=k[f>>2]|0;if((d|0)>=0){e=0;while(1){if(!((e|0)>50&(e|0)<(d+-1|0))){o=m[c+24056+(e<<3)>>1]|0;n=i[1017266+(m[c+24056+(e<<3)+2>>1]|0)>>0]|0;d=k[c+24056+(e<<3)+4>>2]|0;oc(b+o|0,6);k[g>>2]=e;k[g+4>>2]=o;k[g+8>>2]=n;k[g+12>>2]=d;k[g+16>>2]=1097824;pe(a,1017271,g);d=k[f>>2]|0}if((e|0)<(d|0))e=e+1|0;else break}}se(1017206,5,1,a);e=c+24|0;k[h>>2]=k[e>>2];pe(a,1017242,h);if((k[e>>2]|0)>=0){d=0;while(1){o=k[c+56064+(d<<2)>>2]|0;k[j>>2]=d;k[j+4>>2]=o;pe(a,1017294,j);if((d|0)<(k[e>>2]|0))d=d+1|0;else break}}se(1017206,5,1,a);r=l;return}function bd(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,l=0,n=0,o=0,p=0;c=r;r=r+48|0;d=c;p=m[b>>1]|0;o=m[b+2>>1]|0;n=Ec(m[b+4>>1]|0)|0;l=m[b+8>>1]|0;i=Ec(m[b+6>>1]|0)|0;h=m[b+10>>1]|0;g=m[b+12>>1]|0;f=m[b+14>>1]|0;e=Bc(m[b+16>>1]|0)|0;b=j[b+18>>1]|0;k[d>>2]=p;k[d+4>>2]=o;k[d+8>>2]=n;k[d+12>>2]=l;k[d+16>>2]=i;k[d+20>>2]=h;k[d+24>>2]=g;k[d+28>>2]=f;k[d+32>>2]=e;k[d+36>>2]=b&255;k[d+40>>2]=(b&65535)>>>8&65535;pe(a,1017302,d);r=c;return}function cd(a,b){a=a|0;b=b|0;var c=0,d=0,e=0;e=r;r=r+16|0;d=e+8|0;c=e;k[c>>2]=k[b>>2];pe(a,1017351,c);se(1017382,101,1,a);if((k[b>>2]|0)>=0){c=0;while(1){k[d>>2]=c;pe(a,1017484,d);bd(a,b+4+(c*20|0)|0);if((c|0)<(k[b>>2]|0))c=c+1|0;else break}}se(1017206,5,1,a);r=e;return}function dd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0;d=k[a+140>>2]|0;if(b){t=k[d+4>>2]|0;r=0;s=t;b=d+8|0;d=d+12|0}else{r=1;s=k[d+16>>2]|0;t=k[d+20>>2]|0;b=d+24|0;d=d+28|0}e=k[b>>2]|0;b=k[d>>2]|0;o=c+8|0;d=k[o>>2]|0;p=k[c+12>>2]|0;q=k[c+16>>2]|0;j[c+24056>>1]=k[c+28>>2];j[c+24058>>1]=r;n=pb(Gc(k[a+8>>2]|0)|0,1)|0;k[c+24060>>2]=n;if((q|0)>0|((p|0)>0|(d|0)>0)){n=b+4|0;m=e+4|0;g=0;i=0;l=0;b=1;while(1){h=k[c+32+(g<<3)>>2]|0;f=k[c+8040+(i<<3)>>2]|0;a=k[c+16048+(l<<3)>>2]|0;do if((i|0)>=(p|0)|(f|0)>(h|0)|(f|0)>(a|0)){if(!((a|0)>(f|0)|((l|0)>=(q|0)|(a|0)>(h|0)))){f=l+1|0;e=k[(k[n>>2]|0)+(k[c+16048+(l<<3)+4>>2]<<2)>>2]|0;if(!e){a=i;e=f;break}j[c+24056+(b<<3)>>1]=a;j[c+24056+(b<<3)+2>>1]=3;k[c+24056+(b<<3)+4>>2]=e;a=i;e=f;b=b+1|0;break}f=k[c+32+(g<<3)+4>>2]|0;a=f&2147483647;f=(f|0)<0?t:s;g=g+1|0;e=k[f+8>>2]|0;if((a|0)<(e|0)){e=k[(k[f+4>>2]|0)+(a<<2)>>2]|0;if(!e){a=i;e=l;break}j[c+24056+(b<<3)>>1]=h;j[c+24056+(b<<3)+2>>1]=r;k[c+24056+(b<<3)+4>>2]=e;a=i;e=l;b=b+1|0;break}a=a-e+a|0;f=k[f+4>>2]|0;e=k[f+(a<<2)>>2]|0;a=k[f+(a+1<<2)>>2]|0;if(e){j[c+24056+(b<<3)>>1]=h;j[c+24056+(b<<3)+2>>1]=r;k[c+24056+(b<<3)+4>>2]=e;b=b+1|0}if(!a){a=i;e=l}else{j[c+24056+(b<<3)>>1]=h;j[c+24056+(b<<3)+2>>1]=r;k[c+24056+(b<<3)+4>>2]=a;a=i;e=l;b=b+1|0}}else{a=i+1|0;e=k[(k[m>>2]|0)+(k[c+8040+(i<<3)+4>>2]<<2)>>2]|0;if(!e)e=l;else{j[c+24056+(b<<3)>>1]=f;j[c+24056+(b<<3)+2>>1]=2;k[c+24056+(b<<3)+4>>2]=e;e=l;b=b+1|0}}while(0);if((e|0)<(q|0)|((a|0)<(p|0)|(g|0)<(d|0))){i=a;l=e}else break}d=k[o>>2]|0}else b=1;k[c+20>>2]=b;j[c+24056+(b<<3)>>1]=k[c+32+(d<<3)>>2];k[c+24056+(b<<3)+4>>2]=0;return}function ed(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,l=0,n=0,o=0;o=b&1^1;i=b?50:20;j=c+20|0;e=k[j>>2]|0;b=k[c+8>>2]|0;if((b|0)>0){l=(i>>>1)+i|0;n=i<<1;h=b;f=0;d=0;b=a;do{if((h|0)>=(l|0))if((h|0)<(n|0))g=h+1>>1;else g=i;else g=h;k[c+56064+(d<<2)>>2]=f;k[c+56268+(d<<2)>>2]=b;d=d+1|0;if((f|0)<(e|0)&(g|0)>0){a=0;b=f;do{a=((m[c+24056+(b<<3)+2>>1]|0|0)==(o|0)&1)+a|0;b=b+1|0}while((b|0)<(e|0)&(a|0)<(g|0));f=b}b=m[c+24056+(f<<3)>>1]|0;h=h-g|0}while((h|0)>0);e=k[j>>2]|0}else{k[c+56064>>2]=0;k[c+56268>>2]=m[c+24056>>1];d=1;b=a}k[c+24>>2]=d;k[c+56064+(d<<2)>>2]=e;k[c+56268+(d<<2)>>2]=b;return}function fd(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;var g=0,h=0,j=0;j=r;r=r+1056|0;h=j+24|0;g=c+7|0;if(i[g>>0]|0){se(1017490,12,1,k[c>>2]|0);$c(k[c>>2]|0,k[a>>2]|0,f)}dd(c,e,f);ed(b,e,f);if(i[g>>0]|0){se(1017503,9,1,k[c>>2]|0);ad(k[c>>2]|0,k[a>>2]|0,f)}k[h>>2]=0;Zc(k[a+12>>2]|0,f,c,h,j);if(i[g>>0]|0)cd(k[c>>2]|0,h);_c(h,d);r=j;return}function gd(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;qd(d,(Gc(b)|0)&65535,a,a,100);k[c+12>>2]=26;return}function hd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,l=0,m=0,n=0,o=0,p=0,q=0,s=0;p=r;r=r+16|0;o=p;d=gf(56472)|0;k[d+4>>2]=1e3;l=d+8|0;k[d+8040>>2]=0;k[d+8044>>2]=0;k[d+16048>>2]=0;k[d+16052>>2]=0;j[d+24056>>1]=0;k[d+24060>>2]=0;k[d+56064>>2]=0;k[d+56268>>2]=0;k[l>>2]=0;k[l+4>>2]=0;k[l+8>>2]=0;k[l+12>>2]=0;k[l+16>>2]=0;k[l+20>>2]=0;k[l+24>>2]=0;k[l+28>>2]=0;k[d>>2]=k[a+12>>2];e=b+12|0;k[e>>2]=26;k[b+136>>2]=0;f=d+28|0;k[f>>2]=1;g=k[a+4>>2]|0;if((g|0)>1){h=b+7|0;n=b+140|0;m=1;do{if(i[h>>0]|0){q=k[b>>2]|0;k[o>>2]=m;k[o+4>>2]=g;pe(q,1017513,o)}q=m;m=jb(k[a>>2]|0,m,g,k[k[n>>2]>>2]|0,d)|0;s=k[n>>2]|0;kb(k[a>>2]|0,q,m,k[s+8>>2]|0,k[s+12>>2]|0,d);fd(a,q,b,c,1,d);k[l>>2]=0;k[l+4>>2]=0;k[l+8>>2]=0;k[l+12>>2]=0;k[l+16>>2]=0;k[f>>2]=m}while((m|0)<(g|0))}Qe(d);k[e>>2]=26;r=p;return}function id(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,l=0,m=0;d=gf(56472)|0;k[d+4>>2]=1e3;g=d+8|0;k[d+8040>>2]=0;k[d+8044>>2]=0;k[d+16048>>2]=0;k[d+16052>>2]=0;j[d+24056>>1]=0;k[d+24060>>2]=0;k[d+56064>>2]=0;k[d+56268>>2]=0;k[g>>2]=0;k[g+4>>2]=0;k[g+8>>2]=0;k[g+12>>2]=0;k[g+16>>2]=0;k[g+20>>2]=0;k[g+24>>2]=0;k[g+28>>2]=0;k[d>>2]=k[a+12>>2];k[b+12>>2]=26;k[b+136>>2]=0;e=d+28|0;k[e>>2]=1;f=k[a+4>>2]|0;if((f|0)>1){i=b+140|0;h=1;do{m=k[i>>2]|0;l=h;h=lb(k[a>>2]|0,h,f,k[m+16>>2]|0,k[m+20>>2]|0,d)|0;m=k[i>>2]|0;mb(k[a>>2]|0,l,h,k[m+24>>2]|0,k[m+28>>2]|0,d);fd(a,l,b,c,0,d);k[g>>2]=0;k[g+4>>2]=0;k[g+8>>2]=0;k[g+12>>2]=0;k[g+16>>2]=0;k[e>>2]=h}while((h|0)<(f|0))}Qe(d);return}function jd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,j=0,l=0,m=0,n=0;g=r;r=r+32|0;f=g+16|0;e=g;d=g+20|0;if(!(i[b+7>>0]|0))d=a+12|0;else{l=k[b>>2]|0;h=a+12|0;n=Bc(k[h>>2]|0)|0;j=a+4|0;m=k[j>>2]|0;k[e>>2]=n;k[e+4>>2]=m;pe(l,1017545,e);ze(e,k[a>>2]|0,k[j>>2]|0);j=k[b>>2]|0;mc(d);k[f>>2]=(i[d>>0]&1)==0?d+1|0:k[d+8>>2]|0;pe(j,1017576,f);Ae(d);se(1017206,5,1,k[b>>2]|0);Ae(e);d=h}k[b+12>>2]=26;k[b+136>>2]=0;n=Cc(k[d>>2]|0)|0;switch(((n|0)!=3&(i[b+4>>0]|0)!=0?2:n)|0){case 1:case 0:{gd(k[a+4>>2]|0,k[a+12>>2]|0,b,c);break}case 3:{hd(a,b,c);break}case 2:{id(a,b,c);break}default:{}}r=g;return}function kd(a){a=a|0;k[a>>2]=0;k[a+4>>2]=0;k[a+8>>2]=0;k[a+12>>2]=0;return}function ld(a){a=a|0;k[a>>2]=0;k[a+4>>2]=0;k[a+8>>2]=0;k[a+12>>2]=0;return}function md(a){a=a|0;a=a+12|0;k[a>>2]=(k[a>>2]|0)+1;return}function nd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0;b=b&255;d=b>>>2;e=sf(1,0,d|0)|0;f=L;h=a;g=k[h>>2]|0;h=k[h+4>>2]|0;if((g&e|0)==0&(h&f|0)==0){d=a+16+(d<<3)|0;k[d>>2]=0;k[d+4>>2]=0;d=a;k[d>>2]=g|e;k[d+4>>2]=h|f}a=a+16+(b<<1)|0;j[a>>1]=(m[a>>1]|0)+c;return}function od(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,l=0,n=0,o=0,p=0,q=0;q=r;r=r+16|0;p=q;k[b>>2]=-1;n=b+4|0;k[n>>2]=-1;o=b+8|0;k[o>>2]=-1;k[p>>2]=-1;k[p+4>>2]=-1;k[p+8>>2]=-1;d=a;c=k[d>>2]|0;d=k[d+4>>2]|0;if(!((c|0)==0&(d|0)==0)){i=a+16|0;j=p+8|0;l=p+4|0;g=0;while(1){if(!((c&1|0)==0&0==0)){h=0;do{e=h+g|0;f=m[i+(e<<1)>>1]|0;if((f|0)>(k[j>>2]|0)){a=k[l>>2]|0;if((f|0)>(a|0)){k[j>>2]=a;k[o>>2]=k[n>>2];a=k[p>>2]|0;if((f|0)>(a|0)){k[l>>2]=a;k[n>>2]=k[b>>2];a=0}else a=1}else a=2;k[p+(a<<2)>>2]=f;k[b+(a<<2)>>2]=e}h=h+1|0}while((h|0)!=4)}c=rf(c|0,d|0,1)|0;d=L;if((c|0)==0&(d|0)==0)break;else g=g+4|0}}r=q;return}function pd(a){a=a|0;var b=0;b=a+536|0;k[b>>2]=0;k[b+4>>2]=0;k[b+8>>2]=0;k[b+12>>2]=0;k[b+16>>2]=0;k[b+20>>2]=0;k[b+24>>2]=0;k[b+28>>2]=0;k[a>>2]=0;k[a+4>>2]=0;k[a+8>>2]=0;k[a+12>>2]=0;k[a+16>>2]=0;k[a+20>>2]=0;a=a+568|0;b=a+48|0;do{k[a>>2]=-1;a=a+4|0}while((a|0)<(b|0));return}function qd(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0,i=0,l=0,m=0;k[a>>2]=(k[a>>2]|0)+1;g=b&65535;l=g&15;m=j[a+568+(l<<1)>>1]|0;do if(m<<16>>16==b<<16>>16){b=a+616+(l<<2)|0;k[b>>2]=(k[b>>2]|0)+c;b=a+712+(l<<2)|0;k[b>>2]=(k[b>>2]|0)+d;d=ha(e,c)|0;a=a+808+(l<<2)|0;k[a>>2]=(k[a>>2]|0)+d}else{f=l^8;i=j[a+568+(f<<1)>>1]|0;if(i<<16>>16==b<<16>>16){b=a+616+(f<<2)|0;k[b>>2]=(k[b>>2]|0)+c;b=a+712+(f<<2)|0;k[b>>2]=(k[b>>2]|0)+d;d=ha(e,c)|0;a=a+808+(f<<2)|0;k[a>>2]=(k[a>>2]|0)+d;break}g=g&7|16;h=j[a+568+(g<<1)>>1]|0;if(h<<16>>16==b<<16>>16){b=a+616+(g<<2)|0;k[b>>2]=(k[b>>2]|0)+c;b=a+712+(g<<2)|0;k[b>>2]=(k[b>>2]|0)+d;d=ha(e,c)|0;a=a+808+(g<<2)|0;k[a>>2]=(k[a>>2]|0)+d;break}if(m<<16>>16!=-1){if(i<<16>>16!=-1)if(h<<16>>16==-1)f=g;else{f=(k[a+616+(f<<2)>>2]|0)<(k[a+616+(l<<2)>>2]|0)?f:l;f=(k[a+616+(g<<2)>>2]|0)<(k[a+616+(f<<2)>>2]|0)?g:f}}else f=l;j[a+568+(f<<1)>>1]=b;k[a+616+(f<<2)>>2]=c;k[a+712+(f<<2)>>2]=d;d=ha(e,c)|0;k[a+808+(f<<2)>>2]=d}while(0);return}function rd(a,b){a=a|0;b=b|0;var c=0,d=0;a:do if(!(k[a+4>>2]|0)){d=b&65535;c=d&15;if((j[a+568+(c<<1)>>1]|0)!=b<<16>>16){c=c^8;if((j[a+568+(c<<1)>>1]|0)!=b<<16>>16){c=d&7|16;c=(j[a+568+(c<<1)>>1]|0)==b<<16>>16?c:-1}}}else{c=0;while(1){if((j[a+568+(c<<1)>>1]|0)==b<<16>>16)break a;c=c+1|0;if((c|0)>=24){c=-1;break}}}while(0);return c|0}function sd(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,l=0;l=0;while(1){g=a+568+(l<<1)|0;if((j[g>>1]|0)==-1)k[a+616+(l<<2)>>2]=-1;b=l;l=l+1|0;if((l|0)>=24)continue;h=a+616+(b<<2)|0;i=a+712+(b<<2)|0;e=a+808+(b<<2)|0;f=l;do{b=a+568+(f<<1)|0;c=a+616+(f<<2)|0;if((j[b>>1]|0)==-1){k[c>>2]=-1;d=-1}else d=k[c>>2]|0;if((k[h>>2]|0)<(d|0)){d=j[g>>1]|0;j[g>>1]=j[b>>1]|0;j[b>>1]=d;d=k[h>>2]|0;k[h>>2]=k[c>>2];k[c>>2]=d;d=k[i>>2]|0;c=a+712+(f<<2)|0;k[i>>2]=k[c>>2];k[c>>2]=d;c=k[e>>2]|0;d=a+808+(f<<2)|0;k[e>>2]=k[d>>2];k[d>>2]=c}f=f+1|0}while((f|0)!=24);if((l|0)==3)break}k[a+4>>2]=1;return}function td(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0;f=r;r=r+32|0;e=f+8|0;d=f;se(1017581,14,1,941016);c=0;do{b=j[a+568+(c<<1)>>1]|0;if(b<<16>>16!=-1){i=Ec(b&65535)|0;h=k[a+616+(c<<2)>>2]|0;g=k[a+712+(c<<2)>>2]|0;b=k[a+808+(c<<2)>>2]|0;k[e>>2]=c;k[e+4>>2]=i;k[e+8>>2]=h;k[e+12>>2]=g;k[e+16>>2]=b;pe(941016,1017620,e)}c=c+1|0}while((c|0)!=24);k[d>>2]=k[a>>2];pe(941016,1017596,d);r=f;return}function ud(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0;e=k[c>>2]|0;do if((e|0)>=1){f=k[b>>2]|0;g=(k[a+32>>2]|0)+(k[a>>2]|0)|0;d=k[a+16>>2]|0;h=i[f>>0]|0;a=h&255;if(h<<24>>24>-1){a=i[g+a>>0]|0;k[b>>2]=f+1;k[c>>2]=e+-1;break}if((e|0)>1&(a&224|0)==192){a=i[g+(l[g+a>>0]<<d)+(l[f+1>>0]|0)>>0]|0;k[b>>2]=f+2;k[c>>2]=e+-2;break}if((e|0)>2&(a&240|0)==224){a=g+(l[g+a>>0]<<d+4)|0;a=i[a+(i[a+(l[f+1>>0]|0)>>0]<<d)+(l[f+2>>0]|0)>>0]|0;k[b>>2]=f+3;k[c>>2]=e+-3;break}if((a&248|0)==240&(e|0)>3){a=g+(l[g+(l[g+a>>0]<<d)+(l[f+1>>0]|0)>>0]<<d+4)|0;a=i[a+(i[a+(l[f+2>>0]|0)>>0]<<d)+(l[f+3>>0]|0)>>0]|0;k[b>>2]=f+4;k[c>>2]=e+-4;break}else{k[b>>2]=f+1;k[c>>2]=e+-1;a=0;break}}else a=0;while(0);return a|0}function vd(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0;c=k[b>>2]|0;do if((c|0)>=1){d=k[a>>2]|0;f=i[d>>0]|0;e=f&255;if(f<<24>>24>-1){f=j[942368+(e<<1)>>1]|0;k[a>>2]=d+1;k[b>>2]=c+-1;c=f&255;break}if((c|0)>1&(e&224|0)==192){f=j[942368+((m[942368+(e<<1)>>1]|0)<<6<<1)+((l[d+1>>0]|0)<<1)>>1]|0;k[a>>2]=d+2;k[b>>2]=c+-2;c=f&255;break}if((c|0)>2&(e&240|0)==224){f=j[942368+((m[942368+((m[942368+(e<<1)>>1]|0)<<6<<1)+((l[d+1>>0]|0)<<1)>>1]|0)<<6<<1)+((l[d+2>>0]|0)<<1)>>1]|0;k[a>>2]=d+3;k[b>>2]=c+-3;c=f&255;break}if((e&248|0)==240&(c|0)>3){f=j[942368+((m[942368+((m[942368+((m[942368+(e<<1)>>1]|0)<<6<<1)+((l[d+1>>0]|0)<<1)>>1]|0)<<6<<1)+((l[d+2>>0]|0)<<1)>>1]|0)<<6<<1)+((l[d+3>>0]|0)<<1)>>1]|0;k[a>>2]=d+4;k[b>>2]=c+-4;c=f&255;break}else{k[a>>2]=d+1;k[b>>2]=c+-1;c=0;break}}else c=0;while(0);return c|0}function wd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,j=0,m=0;h=a+b|0;j=h+-7|0;k[c>>2]=0;if(b|0){b=a;a:do{b:do if(b>>>0<j>>>0){g=b;while(1){d=k[g>>2]|0;b=g+4|0;e=k[b>>2]|0;f=g+8|0;if((d+-656877351|d+1145324612|e+-656877351|e+1145324612)&-2139062144|0){if((i[1008008+(l[g+1>>0]|0)>>0]|i[1008008+(d&255)>>0]|i[1008008+(l[g+2>>0]|0)>>0]|i[1008008+(l[g+3>>0]|0)>>0])<<24>>24){b=g;break b}if((i[1008008+(l[g+5>>0]|0)>>0]|i[1008008+(e&255)>>0]|i[1008008+(l[g+6>>0]|0)>>0]|i[1008008+(l[g+7>>0]|0)>>0])<<24>>24)break b}if(f>>>0<j>>>0)g=f;else{b=f;break}}}while(0);if(b>>>0<h>>>0)d=993864;else{d=993864;m=13;break}while(1){f=i[d+(l[b>>0]|0)>>0]|0;e=b+1|0;if((f&255)>239)break;b=993864+((f&255)<<6)|0;if(e>>>0<h>>>0){d=b;b=e}else{d=b;b=e;m=13;break a}}c:do if((d-993864|0)>>>0>=64)do{b=b+-1|0;if(b>>>0<=a>>>0)break c}while((i[b>>0]&-64)<<24>>24==-128);while(0)}while(f<<24>>24==-3);d:do if((m|0)==13)if((d-993864|0)>>>0>=64)do{b=b+-1|0;if(b>>>0<=a>>>0)break d}while((i[b>>0]&-64)<<24>>24==-128);while(0);k[c>>2]=b-a}return}function xd(a,b,c,d,e,f,g,h,j){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;j=j|0;var n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0;H=(j|0)==0;I=(j|0)!=0;E=0;F=0;G=0;while(1){C=a;x=a+b|0;D=c;y=c+d|0;z=x;if((d|0)<(b|0))if(H){r=239;q=0;o=0;n=0}else{Tc(j,0);r=239;q=0;o=0;n=0}else{A=1009832;u=0;B=a;p=D;r=0;o=C;n=0;a:while(1){s=o;if(s>>>0<x>>>0){q=i[s>>0]|0;w=i[A+(q&255)>>0]|0;r=w&255;i[p>>0]=q;o=s+1|0;p=p+1|0;if((w&255)>239)w=q;else{w=B;A=1009832+(r<<6)|0;u=q;B=w;continue}}else w=u;if((r|0)<=239){J=43;break}b:do switch(r|0){case 247:{if(H)q=B;else{Tc(j,-2-B+o|0);Vc(j,2);q=o}i[p+-3>>0]=i[A+((w&255)+64)>>0]|0;A=1009832;u=0;B=q;p=p+-2|0;r=0;n=n+1|0;continue a}case 248:{if(H)q=B;else{Tc(j,o+~B|0);Vc(j,1);q=o}u=w&255;i[p+-3>>0]=i[A+(u+128)>>0]|0;i[p+-2>>0]=i[A+(u+64)>>0]|0;A=1009832;u=0;B=q;p=p+-1|0;r=0;n=n+1|0;continue a}case 246:{if(H)q=B;else{Tc(j,o+~B|0);Vc(j,1);q=o}i[p+-2>>0]=i[A+((w&255)+64)>>0]|0;A=1009832;u=0;B=q;p=p+-1|0;r=0;n=n+1|0;continue a}case 245:{q=w&255;r=p;i[r+-3>>0]=i[A+(q+192)>>0]|0;J=23;break}case 244:{q=w&255;r=p;J=23;break}case 243:{q=w&255;r=p;J=24;break}case 251:{i[p+-1>>0]=i[A+(w&255|256)>>0]|0;w=B;A=1009832;u=0;r=0;n=n+1|0;B=w;continue a}case 250:{q=A;r=w&255;if((q-1009832|0)>>>0<320){r=l[A+(r|512)>>0]<<8;break b}else{r=l[A+(r+128)>>0]<<8;J=29;break b}}case 249:case 252:{r=0;J=29;break}default:{J=10;break a}}while(0);if((J|0)==23){i[r+-2>>0]=i[A+(q+128)>>0]|0;J=24}else if((J|0)==29){J=0;q=A}if((J|0)==24){J=0;i[r+-1>>0]=i[A+(q+64)>>0]|0;w=B;A=1009832;u=0;r=0;n=n+1|0;B=w;continue}s=w&255;r=l[A+((q-1009832|0)>>>0<320?s|256:s+64|0)>>0]|r;s=974880+(r<<2)|0;v=l[s>>0]&127;q=l[974880+(r<<2)+1>>0]|0;if(!((q&128|0)==0|e)){s=r+1|0;q=l[974880+(s<<2)+1>>0]|0;s=974880+(s<<2)|0}u=q&127;r=m[s+2>>1]|0;q=p+(0-v)|0;t=q+u|0;if((y-t|0)<(z-o|0)){r=239;J=39;break}tf(q|0,1016936+r|0,u|0)|0;n=n+1|0;do if(!H){if(u>>>0>v>>>0){Tc(j,o-B|0);Uc(j,u-v|0);p=o;break}if(u>>>0<v>>>0){Tc(j,o-B-v+u|0);Vc(j,v-u|0);p=o}else p=B}else p=B;while(0);if((i[s>>0]|0)>=0){A=1009832;u=0;B=p;p=t;r=0;continue}r=l[1016936+(r+u)>>0]|0;A=1009832+(r<<6)|0;u=w;B=p;p=t}c:do if((J|0)==10)J=39;else if((J|0)==43){J=0;if((A-1009832|0)>>>0<320)r=241;else while(1){q=o+-1|0;o=q;p=p+-1|0;if(q>>>0<=a>>>0){r=240;break c}if((i[q>>0]&-64)<<24>>24!=-128){r=240;break}}}while(0);d:do if((J|0)==39){J=0;o=o+-1|0;p=p+-1|0;if((A-1009832|0)>>>0>=320)do{q=o+-1|0;o=q;p=p+-1|0;if(q>>>0<=a>>>0)break d}while((i[q>>0]&-64)<<24>>24==-128)}while(0);if(I&o>>>0>B>>>0)Tc(j,o-B|0);q=o-C|0;o=p-D|0}F=q+F|0;G=o+G|0;E=n+E|0;if((r|0)!=253)break;else{a=a+q|0;b=b-q|0;c=c+o|0;d=d-o|0}}k[f>>2]=F;k[g>>2]=G;k[h>>2]=E;return}function yd(a){a=a|0;if(a|0)Qe(a);return}function zd(a){a=a|0;return Ec(k[a>>2]|0)|0}function Ad(a){a=a|0;return i[a+4>>0]|0}function Bd(a){a=a|0;if(a|0){Cd(a);Qe(a)}return}function Cd(a){a=a|0;var b=0;b=k[a+4>>2]|0;if(b|0)Qe(b);b=k[a+8>>2]|0;if(b|0)Qe(b);b=k[a+12>>2]|0;if(b|0)Qe(b);return}function Dd(a,b,c){a=a|0;b=b|0;c=c|0;return Ed(b,c)|0}function Ed(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0;c=r;r=r+32|0;e=c+16|0;d=c+4|0;f=c+28|0;k[e>>2]=0;k[e+4>>2]=0;k[e+8>>2]=0;k[d>>2]=0;k[d+4>>2]=0;k[d+8>>2]=0;i[f>>0]=0;a=wb(a,Vd(a)|0,b,e,d,c,f)|0;b=gf(20)|0;Fd(b,(i[f>>0]|0)!=0,a,e,d);r=c;return b|0}function Fd(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;k[a>>2]=c;i[a+16>>0]=b&1;b=gf(8)|0;c=k[e>>2]&255;k[b>>2]=k[d>>2];i[b+4>>0]=c;k[a+4>>2]=b;b=gf(8)|0;c=k[e+4>>2]&255;k[b>>2]=k[d+4>>2];i[b+4>>0]=c;k[a+8>>2]=b;b=gf(8)|0;e=k[e+8>>2]&255;k[b>>2]=k[d+8>>2];i[b+4>>0]=e;k[a+12>>2]=b;return}function Gd(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;return Hd(b,c,d,e,f)|0}function Hd(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0,j=0,l=0;f=r;r=r+80|0;l=f+56|0;h=f+40|0;g=f+28|0;j=f+72|0;k[l>>2]=e;k[l+4>>2]=c;k[l+8>>2]=d;k[l+12>>2]=26;k[h>>2]=0;k[h+4>>2]=0;k[h+8>>2]=0;k[g>>2]=0;k[g+4>>2]=0;k[g+8>>2]=0;i[j>>0]=0;e=xb(a,Vd(a)|0,b,l,h,g,f,f+24|0,j)|0;c=gf(20)|0;Fd(c,(i[j>>0]|0)!=0,e,h,g);r=f;return c|0}function Id(a){a=a|0;return (i[a+16>>0]|0)!=0|0}function Jd(a){a=a|0;return Ec(k[a>>2]|0)|0}function Kd(a,b){a=a|0;b=b|0;if(b>>>0>=3)Ka(0,b|0,3)|0;return k[a+4+(b<<2)>>2]|0}function Ld(a){a=a|0;if(a|0)Qe(a);return}function Md(a){a=a|0;return Ec(k[a>>2]|0)|0}function Nd(a){a=a|0;if(a|0)Qe(a);return}function Od(a){a=a|0;var b=0,c=0;b=r;r=r+16|0;c=b;k[c>>2]=k[a+60>>2];a=Pd(ua(6,c|0)|0)|0;r=b;return a|0}function Pd(a){a=a|0;var b=0;if(a>>>0>4294963200){b=Qd()|0;k[b>>2]=0-a;a=-1}return a|0}function Qd(){var a=0;if(!0)a=1097308;else{a=(Ha()|0)+64|0;a=k[a>>2]|0}return a|0}function Rd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,i=0,j=0,l=0,m=0,n=0,o=0,p=0;p=r;r=r+48|0;m=p+16|0;l=p;i=p+32|0;n=a+28|0;g=k[n>>2]|0;k[i>>2]=g;o=a+20|0;g=(k[o>>2]|0)-g|0;k[i+4>>2]=g;k[i+8>>2]=b;k[i+12>>2]=c;h=a+60|0;j=a+44|0;e=2;b=g+c|0;while(1){if(!0){k[m>>2]=k[h>>2];k[m+4>>2]=i;k[m+8>>2]=e;f=Pd(Ra(146,m|0)|0)|0}else{Oa(8,a|0);k[l>>2]=k[h>>2];k[l+4>>2]=i;k[l+8>>2]=e;f=Pd(Ra(146,l|0)|0)|0;ta(0)}if((b|0)==(f|0)){b=6;break}if((f|0)<0){b=8;break}b=b-f|0;d=k[i+4>>2]|0;if(f>>>0<=d>>>0)if((e|0)==2){k[n>>2]=(k[n>>2]|0)+f;g=d;d=i;e=2}else{g=d;d=i}else{g=k[j>>2]|0;k[n>>2]=g;k[o>>2]=g;g=k[i+12>>2]|0;f=f-d|0;d=i+8|0;e=e+-1|0}k[d>>2]=(k[d>>2]|0)+f;k[d+4>>2]=g-f;i=d}if((b|0)==6){m=k[j>>2]|0;k[a+16>>2]=m+(k[a+48>>2]|0);a=m;k[n>>2]=a;k[o>>2]=a}else if((b|0)==8){k[a+16>>2]=0;k[n>>2]=0;k[o>>2]=0;k[a>>2]=k[a>>2]|32;if((e|0)==2)c=0;else c=c-(k[i+4>>2]|0)|0}r=p;return c|0}function Sd(a){a=a|0;return}function Td(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0;e=r;r=r+32|0;f=e;d=e+20|0;k[f>>2]=k[a+60>>2];k[f+4>>2]=0;k[f+8>>2]=b;k[f+12>>2]=d;k[f+16>>2]=c;if((Pd(Qa(140,f|0)|0)|0)<0){k[d>>2]=-1;a=-1}else a=k[d>>2]|0;r=e;return a|0}function Ud(a,b){a=a|0;b=b|0;var c=0,d=0,e=0;d=b&255;a:do if(!d)a=a+(Vd(a)|0)|0;else{if(a&3){c=b&255;do{e=i[a>>0]|0;if(e<<24>>24==0?1:e<<24>>24==c<<24>>24)break a;a=a+1|0}while((a&3|0)!=0)}d=ha(d,16843009)|0;c=k[a>>2]|0;b:do if(!((c&-2139062144^-2139062144)&c+-16843009))do{e=c^d;if((e&-2139062144^-2139062144)&e+-16843009|0)break b;a=a+4|0;c=k[a>>2]|0}while(!((c&-2139062144^-2139062144)&c+-16843009|0));while(0);c=b&255;while(1){e=i[a>>0]|0;if(e<<24>>24==0?1:e<<24>>24==c<<24>>24)break;else a=a+1|0}}while(0);return a|0}function Vd(a){a=a|0;var b=0,c=0,d=0;d=a;a:do if(!(d&3))c=4;else{b=a;a=d;while(1){if(!(i[b>>0]|0))break a;b=b+1|0;a=b;if(!(a&3)){a=b;c=4;break}}}while(0);if((c|0)==4){while(1){b=k[a>>2]|0;if(!((b&-2139062144^-2139062144)&b+-16843009))a=a+4|0;else break}if((b&255)<<24>>24)do a=a+1|0;while((i[a>>0]|0)!=0)}return a-d|0}function Wd(a,b){a=+a;b=b|0;var c=0,d=0,e=0;p[t>>3]=a;c=k[t>>2]|0;d=k[t+4>>2]|0;e=rf(c|0,d|0,52)|0;e=e&2047;switch(e|0){case 0:{if(a!=0.0){a=+Wd(a*18446744073709551616.0,b);c=(k[b>>2]|0)+-64|0}else c=0;k[b>>2]=c;break}case 2047:break;default:{k[b>>2]=e+-1022;k[t>>2]=c;k[t+4>>2]=d&-2146435073|1071644672;a=+p[t>>3]}}return +a}function Xd(a,b){a=+a;b=b|0;return +(+Wd(a,b))}function Yd(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;a:do if(!c)c=0;else{while(1){d=i[a>>0]|0;e=i[b>>0]|0;if(d<<24>>24!=e<<24>>24)break;c=c+-1|0;if(!c){c=0;break a}else{a=a+1|0;b=b+1|0}}c=(d&255)-(e&255)|0}while(0);return c|0}function Zd(a){a=a|0;var b=0,c=0;b=0;while(1){if((l[1094098+b>>0]|0)==(a|0)){c=2;break}b=b+1|0;if((b|0)==87){b=87;a=1094186;c=5;break}}if((c|0)==2)if(!b)b=1094186;else{a=1094186;c=5}if((c|0)==5)while(1){do{c=a;a=a+1|0}while((i[c>>0]|0)!=0);b=b+-1|0;if(!b){b=a;break}else c=5}return b|0}function _d(a,b){a=a|0;b=b|0;var c=0,d=0;d=i[a>>0]|0;c=i[b>>0]|0;if(d<<24>>24==0?1:d<<24>>24!=c<<24>>24)b=d;else{do{a=a+1|0;b=b+1|0;d=i[a>>0]|0;c=i[b>>0]|0}while(!(d<<24>>24==0?1:d<<24>>24!=c<<24>>24));b=d}return (b&255)-(c&255)|0}function $d(a,b){a=a|0;b=b|0;if(!a)a=0;else a=ae(a,b)|0;return a|0}function ae(a,b){a=a|0;b=b|0;do if(a){if(b>>>0<128){i[a>>0]=b;a=1;break}if(b>>>0<2048){i[a>>0]=b>>>6|192;i[a+1>>0]=b&63|128;a=2;break}if(b>>>0<55296|(b&-8192|0)==57344){i[a>>0]=b>>>12|224;i[a+1>>0]=b>>>6&63|128;i[a+2>>0]=b&63|128;a=3;break}if((b+-65536|0)>>>0<1048576){i[a>>0]=b>>>18|240;i[a+1>>0]=b>>>12&63|128;i[a+2>>0]=b>>>6&63|128;i[a+3>>0]=b&63|128;a=4;break}else{a=Qd()|0;k[a>>2]=84;a=-1;break}}else a=1;while(0);return a|0}function be(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0;o=r;r=r+128|0;j=o+112|0;l=o;m=l;p=941128;q=m+112|0;do{k[m>>2]=k[p>>2];m=m+4|0;p=p+4|0}while((m|0)<(q|0));if((b+-1|0)>>>0>2147483646)if(!b){e=j;f=1;n=4}else{q=Qd()|0;k[q>>2]=75}else{e=a;f=b;n=4}if((n|0)==4?(q=-2-e|0,q=f>>>0>q>>>0?q:f,k[l+48>>2]=q,h=l+20|0,k[h>>2]=e,k[l+44>>2]=e,p=e+q|0,g=l+16|0,k[g>>2]=p,k[l+28>>2]=p,de(l,c,d)|0,q|0):0){q=k[h>>2]|0;i[q+(((q|0)==(k[g>>2]|0))<<31>>31)>>0]=0}r=o;return}function ce(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;d=a+20|0;e=k[d>>2]|0;a=(k[a+16>>2]|0)-e|0;a=a>>>0>c>>>0?c:a;tf(e|0,b|0,a|0)|0;k[d>>2]=(k[d>>2]|0)+a;return c|0}function de(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0,h=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0;q=r;r=r+224|0;m=q+120|0;p=q+80|0;o=q;n=q+136|0;d=p;e=d+40|0;do{k[d>>2]=0;d=d+4|0}while((d|0)<(e|0));k[m>>2]=k[c>>2];if((ee(0,b,m,o,p)|0)<0)c=-1;else{c=k[a>>2]|0;l=c&32;if((i[a+74>>0]|0)<1)k[a>>2]=c&-33;j=a+48|0;if(!(k[j>>2]|0)){d=a+44|0;e=k[d>>2]|0;k[d>>2]=n;f=a+28|0;k[f>>2]=n;g=a+20|0;k[g>>2]=n;k[j>>2]=80;h=a+16|0;k[h>>2]=n+80;c=ee(a,b,m,o,p)|0;if(e){Ua[k[a+36>>2]&7](a,0,0)|0;c=(k[g>>2]|0)==0?-1:c;k[d>>2]=e;k[j>>2]=0;k[h>>2]=0;k[f>>2]=0;k[g>>2]=0}}else c=ee(a,b,m,o,p)|0;p=k[a>>2]|0;k[a>>2]=p|l;c=(p&32|0)==0?c:-1}r=q;return c|0}function ee(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0,m=0.0,n=0,o=0,q=0,s=0,u=0,v=0,w=0.0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0,H=0,I=0,J=0,K=0,M=0,N=0,O=0,P=0,Q=0,R=0,S=0,T=0,U=0,V=0,W=0,X=0,Y=0,Z=0,_=0,$=0,aa=0,ba=0,ca=0,da=0,ea=0,fa=0,ga=0,ia=0;ia=r;r=r+624|0;ca=ia+24|0;ea=ia+16|0;da=ia+588|0;$=ia+576|0;ba=ia;W=ia+536|0;ga=ia+8|0;fa=ia+528|0;M=(a|0)!=0;N=W+40|0;V=N;W=W+39|0;X=ga+4|0;Y=da;Z=0-Y|0;_=$+12|0;$=$+11|0;aa=_;O=aa-Y|0;P=-2-Y|0;Q=aa+2|0;R=ca+288|0;S=da+9|0;T=S;U=da+8|0;f=0;g=0;o=0;x=b;a:while(1){do if((f|0)>-1)if((g|0)>(2147483647-f|0)){f=Qd()|0;k[f>>2]=75;f=-1;break}else{f=g+f|0;break}while(0);b=i[x>>0]|0;if(!(b<<24>>24)){K=244;break}else g=x;b:while(1){switch(b<<24>>24){case 37:{b=g;K=9;break b}case 0:{b=g;break b}default:{}}J=g+1|0;b=i[J>>0]|0;g=J}c:do if((K|0)==9)while(1){K=0;if((i[b+1>>0]|0)!=37)break c;g=g+1|0;b=b+2|0;if((i[b>>0]|0)==37)K=9;else break}while(0);v=g-x|0;if(M?(k[a>>2]&32|0)==0:0)fe(x,v,a)|0;if((g|0)!=(x|0)){g=v;x=b;continue}n=b+1|0;g=i[n>>0]|0;h=(g<<24>>24)+-48|0;if(h>>>0<10){J=(i[b+2>>0]|0)==36;n=J?b+3|0:n;g=i[n>>0]|0;s=J?h:-1;o=J?1:o}else s=-1;b=g<<24>>24;d:do if((b&-32|0)==32){h=0;do{if(!(1<<b+-32&75913))break d;h=1<<(g<<24>>24)+-32|h;n=n+1|0;g=i[n>>0]|0;b=g<<24>>24}while((b&-32|0)==32)}else h=0;while(0);do if(g<<24>>24==42){g=n+1|0;b=(i[g>>0]|0)+-48|0;if(b>>>0<10?(i[n+2>>0]|0)==36:0){k[e+(b<<2)>>2]=10;b=1;n=n+3|0;g=k[d+((i[g>>0]|0)+-48<<3)>>2]|0}else{if(o|0){f=-1;break a}if(!M){u=h;J=0;n=g;I=0;break}b=(k[c>>2]|0)+(4-1)&~(4-1);J=k[b>>2]|0;k[c>>2]=b+4;b=0;n=g;g=J}if((g|0)<0){u=h|8192;J=b;I=0-g|0}else{u=h;J=b;I=g}}else{b=(g<<24>>24)+-48|0;if(b>>>0<10){g=0;do{g=(g*10|0)+b|0;n=n+1|0;b=(i[n>>0]|0)+-48|0}while(b>>>0<10);if((g|0)<0){f=-1;break a}else{u=h;J=o;I=g}}else{u=h;J=o;I=0}}while(0);e:do if((i[n>>0]|0)==46){b=n+1|0;g=i[b>>0]|0;if(g<<24>>24!=42){h=(g<<24>>24)+-48|0;if(h>>>0<10)g=0;else{o=0;break}while(1){g=(g*10|0)+h|0;b=b+1|0;h=(i[b>>0]|0)+-48|0;if(h>>>0>=10){o=g;break e}}}b=n+2|0;g=(i[b>>0]|0)+-48|0;if(g>>>0<10?(i[n+3>>0]|0)==36:0){k[e+(g<<2)>>2]=10;o=k[d+((i[b>>0]|0)+-48<<3)>>2]|0;b=n+4|0;break}if(J|0){f=-1;break a}if(M){H=(k[c>>2]|0)+(4-1)&~(4-1);o=k[H>>2]|0;k[c>>2]=H+4}else o=0}else{o=-1;b=n}while(0);q=0;while(1){g=(i[b>>0]|0)+-65|0;if(g>>>0>57){f=-1;break a}H=b+1|0;g=i[1095990+(q*58|0)+g>>0]|0;h=g&255;if((h+-1|0)>>>0<8){b=H;q=h}else break}if(!(g<<24>>24)){f=-1;break}n=(s|0)>-1;do if(g<<24>>24==19)if(n){f=-1;break a}else K=52;else{if(n){k[e+(s<<2)>>2]=h;F=d+(s<<3)|0;G=k[F+4>>2]|0;K=ba;k[K>>2]=k[F>>2];k[K+4>>2]=G;K=52;break}if(!M){f=0;break a}he(ba,h,c)}while(0);if((K|0)==52?(K=0,!M):0){g=v;o=J;x=H;continue}s=i[b>>0]|0;s=(q|0)!=0&(s&15|0)==3?s&-33:s;h=u&-65537;G=(u&8192|0)==0?u:h;f:do switch(s|0){case 110:switch(q|0){case 0:{k[k[ba>>2]>>2]=f;g=v;o=J;x=H;continue a}case 1:{k[k[ba>>2]>>2]=f;g=v;o=J;x=H;continue a}case 2:{g=k[ba>>2]|0;k[g>>2]=f;k[g+4>>2]=((f|0)<0)<<31>>31;g=v;o=J;x=H;continue a}case 3:{j[k[ba>>2]>>1]=f;g=v;o=J;x=H;continue a}case 4:{i[k[ba>>2]>>0]=f;g=v;o=J;x=H;continue a}case 6:{k[k[ba>>2]>>2]=f;g=v;o=J;x=H;continue a}case 7:{g=k[ba>>2]|0;k[g>>2]=f;k[g+4>>2]=((f|0)<0)<<31>>31;g=v;o=J;x=H;continue a}default:{g=v;o=J;x=H;continue a}}case 112:{q=G|8;o=o>>>0>8?o:8;s=120;K=64;break}case 88:case 120:{q=G;K=64;break}case 111:{h=ba;g=k[h>>2]|0;h=k[h+4>>2]|0;if((g|0)==0&(h|0)==0)b=N;else{b=N;do{b=b+-1|0;i[b>>0]=g&7|48;g=rf(g|0,h|0,3)|0;h=L}while(!((g|0)==0&(h|0)==0))}if(!(G&8)){g=G;q=0;n=1096470;K=77}else{q=V-b|0;g=G;o=(o|0)>(q|0)?o:q+1|0;q=0;n=1096470;K=77}break}case 105:case 100:{g=ba;b=k[g>>2]|0;g=k[g+4>>2]|0;if((g|0)<0){b=pf(0,0,b|0,g|0)|0;g=L;h=ba;k[h>>2]=b;k[h+4>>2]=g;h=1;n=1096470;K=76;break f}if(!(G&2048)){n=G&1;h=n;n=(n|0)==0?1096470:1096472;K=76}else{h=1;n=1096471;K=76}break}case 117:{g=ba;b=k[g>>2]|0;g=k[g+4>>2]|0;h=0;n=1096470;K=76;break}case 99:{i[W>>0]=k[ba>>2];b=W;s=1;v=0;u=1096470;g=N;break}case 109:{g=Qd()|0;g=Zd(k[g>>2]|0)|0;K=82;break}case 115:{g=k[ba>>2]|0;g=g|0?g:1096480;K=82;break}case 67:{k[ga>>2]=k[ba>>2];k[X>>2]=0;k[ba>>2]=ga;b=ga;o=-1;K=86;break}case 83:{b=k[ba>>2]|0;if(!o){ke(a,32,I,0,G);b=0;K=97}else K=86;break}case 65:case 71:case 70:case 69:case 97:case 103:case 102:case 101:{m=+p[ba>>3];k[ea>>2]=0;p[t>>3]=m;if((k[t+4>>2]|0)>=0)if(!(G&2048)){F=G&1;E=F;F=(F|0)==0?1096488:1096493}else{E=1;F=1096490}else{m=-m;E=1;F=1096487}p[t>>3]=m;D=k[t+4>>2]&2146435072;do if(D>>>0<2146435072|(D|0)==2146435072&0<0){w=+Xd(m,ea)*2.0;g=w!=0.0;if(g)k[ea>>2]=(k[ea>>2]|0)+-1;B=s|32;if((B|0)==97){u=s&32;x=(u|0)==0?F:F+9|0;v=E|2;b=12-o|0;do if(!(o>>>0>11|(b|0)==0)){m=8.0;do{b=b+-1|0;m=m*16.0}while((b|0)!=0);if((i[x>>0]|0)==45){m=-(m+(-w-m));break}else{m=w+m-m;break}}else m=w;while(0);g=k[ea>>2]|0;b=(g|0)<0?0-g|0:g;b=ie(b,((b|0)<0)<<31>>31,_)|0;if((b|0)==(_|0)){i[$>>0]=48;b=$}i[b+-1>>0]=(g>>31&2)+43;q=b+-2|0;i[q>>0]=s+15;n=(o|0)<1;h=(G&8|0)==0;g=da;while(1){F=~~m;b=g+1|0;i[g>>0]=l[1096454+F>>0]|u;m=(m-+(F|0))*16.0;do if((b-Y|0)==1){if(h&(n&m==0.0))break;i[b>>0]=46;b=g+2|0}while(0);if(!(m!=0.0))break;else g=b}h=q;o=(o|0)!=0&(P+b|0)<(o|0)?Q+o-h|0:O-h+b|0;n=o+v|0;ke(a,32,I,n,G);if(!(k[a>>2]&32))fe(x,v,a)|0;ke(a,48,I,n,G^65536);g=b-Y|0;if(!(k[a>>2]&32))fe(da,g,a)|0;b=aa-h|0;ke(a,48,o-(g+b)|0,0,0);if(!(k[a>>2]&32))fe(q,b,a)|0;ke(a,32,I,n,G^8192);b=(n|0)<(I|0)?I:n;break}b=(o|0)<0?6:o;if(g){g=(k[ea>>2]|0)+-28|0;k[ea>>2]=g;m=w*268435456.0}else{m=w;g=k[ea>>2]|0}D=(g|0)<0?ca:R;C=D;h=D;do{A=~~m>>>0;k[h>>2]=A;h=h+4|0;m=(m-+(A>>>0))*1.0e9}while(m!=0.0);g=k[ea>>2]|0;if((g|0)>0){n=D;o=h;while(1){q=(g|0)>29?29:g;g=o+-4|0;do if(g>>>0>=n>>>0){h=0;do{z=sf(k[g>>2]|0,0,q|0)|0;z=of(z|0,L|0,h|0,0)|0;A=L;y=Cf(z|0,A|0,1e9,0)|0;k[g>>2]=y;h=Bf(z|0,A|0,1e9,0)|0;g=g+-4|0}while(g>>>0>=n>>>0);if(!h)break;n=n+-4|0;k[n>>2]=h}while(0);h=o;while(1){if(h>>>0<=n>>>0)break;g=h+-4|0;if(!(k[g>>2]|0))h=g;else break}g=(k[ea>>2]|0)-q|0;k[ea>>2]=g;if((g|0)>0)o=h;else break}}else n=D;if((g|0)<0){x=((b+25|0)/9|0)+1|0;y=(B|0)==102;do{v=0-g|0;v=(v|0)>9?9:v;do if(n>>>0<h>>>0){g=(1<<v)+-1|0;o=1e9>>>v;u=0;q=n;do{A=k[q>>2]|0;k[q>>2]=(A>>>v)+u;u=ha(A&g,o)|0;q=q+4|0}while(q>>>0<h>>>0);g=(k[n>>2]|0)==0?n+4|0:n;if(!u){n=g;g=h;break}k[h>>2]=u;n=g;g=h+4|0}else{n=(k[n>>2]|0)==0?n+4|0:n;g=h}while(0);h=y?D:n;h=(g-h>>2|0)>(x|0)?h+(x<<2)|0:g;g=(k[ea>>2]|0)+v|0;k[ea>>2]=g}while((g|0)<0);x=n;y=h}else{x=n;y=h}do if(x>>>0<y>>>0){g=(C-x>>2)*9|0;n=k[x>>2]|0;if(n>>>0<10)break;else h=10;do{h=h*10|0;g=g+1|0}while(n>>>0>=h>>>0)}else g=0;while(0);z=(B|0)==103;A=(b|0)!=0;h=b-((B|0)!=102?g:0)+((A&z)<<31>>31)|0;if((h|0)<(((y-C>>2)*9|0)+-9|0)){o=h+9216|0;h=D+4+(((o|0)/9|0)+-1024<<2)|0;o=((o|0)%9|0)+1|0;if((o|0)<9){n=10;do{n=n*10|0;o=o+1|0}while((o|0)!=9)}else n=10;u=k[h>>2]|0;v=(u>>>0)%(n>>>0)|0;o=(h+4|0)==(y|0);do if(o&(v|0)==0)n=x;else{w=(((u>>>0)/(n>>>0)|0)&1|0)==0?9007199254740992.0:9007199254740994.0;q=(n|0)/2|0;if(v>>>0<q>>>0)m=.5;else m=o&(v|0)==(q|0)?1.0:1.5;do if(E){if((i[F>>0]|0)!=45)break;w=-w;m=-m}while(0);o=u-v|0;k[h>>2]=o;if(!(w+m!=w)){n=x;break}B=o+n|0;k[h>>2]=B;if(B>>>0>999999999){g=x;while(1){n=h+-4|0;k[h>>2]=0;if(n>>>0<g>>>0){g=g+-4|0;k[g>>2]=0}B=(k[n>>2]|0)+1|0;k[n>>2]=B;if(B>>>0>999999999)h=n;else{q=g;h=n;break}}}else q=x;g=(C-q>>2)*9|0;o=k[q>>2]|0;if(o>>>0<10){n=q;break}else n=10;do{n=n*10|0;g=g+1|0}while(o>>>0>=n>>>0);n=q}while(0);h=h+4|0;x=n;h=y>>>0>h>>>0?h:y}else h=y;v=0-g|0;B=h;while(1){if(B>>>0<=x>>>0){y=0;break}h=B+-4|0;if(!(k[h>>2]|0))B=h;else{y=1;break}}do if(z){b=(A&1^1)+b|0;if((b|0)>(g|0)&(g|0)>-5){s=s+-1|0;b=b+-1-g|0}else{s=s+-2|0;b=b+-1|0}h=G&8;if(h|0)break;do if(y){h=k[B+-4>>2]|0;if(!h){n=9;break}if(!((h>>>0)%10|0)){o=10;n=0}else{n=0;break}do{o=o*10|0;n=n+1|0}while(!((h>>>0)%(o>>>0)|0|0))}else n=9;while(0);h=((B-C>>2)*9|0)+-9|0;if((s|32|0)==102){h=h-n|0;h=(h|0)<0?0:h;b=(b|0)<(h|0)?b:h;h=0;break}else{h=h+g-n|0;h=(h|0)<0?0:h;b=(b|0)<(h|0)?b:h;h=0;break}}else h=G&8;while(0);u=b|h;o=(u|0)!=0&1;q=(s|32|0)==102;if(q){g=(g|0)>0?g:0;s=0}else{n=(g|0)<0?v:g;n=ie(n,((n|0)<0)<<31>>31,_)|0;if((aa-n|0)<2)do{n=n+-1|0;i[n>>0]=48}while((aa-n|0)<2);i[n+-1>>0]=(g>>31&2)+43;C=n+-2|0;i[C>>0]=s;g=aa-C|0;s=C}v=E+1+b+o+g|0;ke(a,32,I,v,G);if(!(k[a>>2]&32))fe(F,E,a)|0;ke(a,48,I,v,G^65536);do if(q){n=x>>>0>D>>>0?D:x;h=n;do{g=ie(k[h>>2]|0,0,S)|0;do if((h|0)==(n|0)){if((g|0)!=(S|0))break;i[U>>0]=48;g=U}else{if(g>>>0<=da>>>0)break;qf(da|0,48,g-Y|0)|0;do g=g+-1|0;while(g>>>0>da>>>0)}while(0);if(!(k[a>>2]&32))fe(g,T-g|0,a)|0;h=h+4|0}while(h>>>0<=D>>>0);do if(u|0){if(k[a>>2]&32|0)break;fe(1096522,1,a)|0}while(0);if((b|0)>0&h>>>0<B>>>0)while(1){g=ie(k[h>>2]|0,0,S)|0;if(g>>>0>da>>>0){qf(da|0,48,g-Y|0)|0;do g=g+-1|0;while(g>>>0>da>>>0)}if(!(k[a>>2]&32))fe(g,(b|0)>9?9:b,a)|0;h=h+4|0;g=b+-9|0;if(!((b|0)>9&h>>>0<B>>>0)){b=g;break}else b=g}ke(a,48,b+9|0,9,0)}else{q=y?B:x+4|0;if((b|0)>-1){o=(h|0)==0;n=x;do{g=ie(k[n>>2]|0,0,S)|0;if((g|0)==(S|0)){i[U>>0]=48;g=U}do if((n|0)==(x|0)){h=g+1|0;if(!(k[a>>2]&32))fe(g,1,a)|0;if(o&(b|0)<1){g=h;break}if(k[a>>2]&32|0){g=h;break}fe(1096522,1,a)|0;g=h}else{if(g>>>0<=da>>>0)break;qf(da|0,48,g+Z|0)|0;do g=g+-1|0;while(g>>>0>da>>>0)}while(0);h=T-g|0;if(!(k[a>>2]&32))fe(g,(b|0)>(h|0)?h:b,a)|0;b=b-h|0;n=n+4|0}while(n>>>0<q>>>0&(b|0)>-1)}ke(a,48,b+18|0,18,0);if(k[a>>2]&32|0)break;fe(s,aa-s|0,a)|0}while(0);ke(a,32,I,v,G^8192);b=(v|0)<(I|0)?I:v}else{q=(s&32|0)!=0;o=m!=m|0.0!=0.0;g=o?0:E;n=g+3|0;ke(a,32,I,n,h);b=k[a>>2]|0;if(!(b&32)){fe(F,g,a)|0;b=k[a>>2]|0}if(!(b&32))fe(o?(q?1096514:1096518):q?1096506:1096510,3,a)|0;ke(a,32,I,n,G^8192);b=(n|0)<(I|0)?I:n}while(0);g=b;o=J;x=H;continue a}default:{b=x;h=G;s=o;v=0;u=1096470;g=N}}while(0);g:do if((K|0)==64){h=ba;g=k[h>>2]|0;h=k[h+4>>2]|0;n=s&32;if(!((g|0)==0&(h|0)==0)){b=N;do{b=b+-1|0;i[b>>0]=l[1096454+(g&15)>>0]|n;g=rf(g|0,h|0,4)|0;h=L}while(!((g|0)==0&(h|0)==0));K=ba;if((q&8|0)==0|(k[K>>2]|0)==0&(k[K+4>>2]|0)==0){g=q;q=0;n=1096470;K=77}else{g=q;q=2;n=1096470+(s>>4)|0;K=77}}else{b=N;g=q;q=0;n=1096470;K=77}}else if((K|0)==76){b=ie(b,g,N)|0;g=G;q=h;K=77}else if((K|0)==82){K=0;G=je(g,0,o)|0;F=(G|0)==0;b=g;s=F?o:G-g|0;v=0;u=1096470;g=F?g+o|0:G}else if((K|0)==86){K=0;h=0;g=0;q=b;while(1){n=k[q>>2]|0;if(!n)break;g=$d(fa,n)|0;if((g|0)<0|g>>>0>(o-h|0)>>>0)break;h=g+h|0;if(o>>>0>h>>>0)q=q+4|0;else break}if((g|0)<0){f=-1;break a}ke(a,32,I,h,G);if(!h){b=0;K=97}else{n=0;while(1){g=k[b>>2]|0;if(!g){b=h;K=97;break g}g=$d(fa,g)|0;n=g+n|0;if((n|0)>(h|0)){b=h;K=97;break g}if(!(k[a>>2]&32))fe(fa,g,a)|0;if(n>>>0>=h>>>0){b=h;K=97;break}else b=b+4|0}}}while(0);if((K|0)==97){K=0;ke(a,32,I,b,G^8192);g=(I|0)>(b|0)?I:b;o=J;x=H;continue}if((K|0)==77){K=0;h=(o|0)>-1?g&-65537:g;g=ba;g=(k[g>>2]|0)!=0|(k[g+4>>2]|0)!=0;if((o|0)!=0|g){s=(g&1^1)+(V-b)|0;s=(o|0)>(s|0)?o:s;v=q;u=n;g=N}else{b=N;s=0;v=q;u=n;g=N}}q=g-b|0;n=(s|0)<(q|0)?q:s;o=v+n|0;g=(I|0)<(o|0)?o:I;ke(a,32,g,o,h);if(!(k[a>>2]&32))fe(u,v,a)|0;ke(a,48,g,o,h^65536);ke(a,48,n,q,0);if(!(k[a>>2]&32))fe(b,q,a)|0;ke(a,32,g,o,h^8192);o=J;x=H}h:do if((K|0)==244)if(!a)if(!o)f=0;else{f=1;while(1){b=k[e+(f<<2)>>2]|0;if(!b){b=0;break}he(d+(f<<3)|0,b,c);f=f+1|0;if((f|0)>=10){f=1;break h}}while(1){f=f+1|0;if(b|0){f=-1;break h}if((f|0)>=10){f=1;break h}b=k[e+(f<<2)>>2]|0}}while(0);r=ia;return f|0}function fe(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;d=c+16|0;e=k[d>>2]|0;if(!e)if(!(ge(c)|0)){e=k[d>>2]|0;f=5}else d=0;else f=5;a:do if((f|0)==5){g=c+20|0;d=k[g>>2]|0;f=d;if((e-d|0)>>>0<b>>>0){d=Ua[k[c+36>>2]&7](c,a,b)|0;break}b:do if((i[c+75>>0]|0)>-1){d=b;while(1){if(!d){e=f;d=0;break b}e=d+-1|0;if((i[a+e>>0]|0)==10)break;else d=e}if((Ua[k[c+36>>2]&7](c,a,d)|0)>>>0<d>>>0)break a;b=b-d|0;a=a+d|0;e=k[g>>2]|0}else{e=f;d=0}while(0);tf(e|0,a|0,b|0)|0;k[g>>2]=(k[g>>2]|0)+b;d=d+b|0}while(0);return d|0}function ge(a){a=a|0;var b=0,c=0;b=a+74|0;c=i[b>>0]|0;i[b>>0]=c+255|c;b=k[a>>2]|0;if(!(b&8)){k[a+8>>2]=0;k[a+4>>2]=0;b=k[a+44>>2]|0;k[a+28>>2]=b;k[a+20>>2]=b;k[a+16>>2]=b+(k[a+48>>2]|0);b=0}else{k[a>>2]=b|32;b=-1}return b|0}function he(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0.0;a:do if(b>>>0<=20)do switch(b|0){case 9:{d=(k[c>>2]|0)+(4-1)&~(4-1);b=k[d>>2]|0;k[c>>2]=d+4;k[a>>2]=b;break a}case 10:{d=(k[c>>2]|0)+(4-1)&~(4-1);b=k[d>>2]|0;k[c>>2]=d+4;d=a;k[d>>2]=b;k[d+4>>2]=((b|0)<0)<<31>>31;break a}case 11:{d=(k[c>>2]|0)+(4-1)&~(4-1);b=k[d>>2]|0;k[c>>2]=d+4;d=a;k[d>>2]=b;k[d+4>>2]=0;break a}case 12:{d=(k[c>>2]|0)+(8-1)&~(8-1);b=d;e=k[b>>2]|0;b=k[b+4>>2]|0;k[c>>2]=d+8;d=a;k[d>>2]=e;k[d+4>>2]=b;break a}case 13:{e=(k[c>>2]|0)+(4-1)&~(4-1);d=k[e>>2]|0;k[c>>2]=e+4;d=(d&65535)<<16>>16;e=a;k[e>>2]=d;k[e+4>>2]=((d|0)<0)<<31>>31;break a}case 14:{e=(k[c>>2]|0)+(4-1)&~(4-1);d=k[e>>2]|0;k[c>>2]=e+4;e=a;k[e>>2]=d&65535;k[e+4>>2]=0;break a}case 15:{e=(k[c>>2]|0)+(4-1)&~(4-1);d=k[e>>2]|0;k[c>>2]=e+4;d=(d&255)<<24>>24;e=a;k[e>>2]=d;k[e+4>>2]=((d|0)<0)<<31>>31;break a}case 16:{e=(k[c>>2]|0)+(4-1)&~(4-1);d=k[e>>2]|0;k[c>>2]=e+4;e=a;k[e>>2]=d&255;k[e+4>>2]=0;break a}case 17:{e=(k[c>>2]|0)+(8-1)&~(8-1);f=+p[e>>3];k[c>>2]=e+8;p[a>>3]=f;break a}case 18:{e=(k[c>>2]|0)+(8-1)&~(8-1);f=+p[e>>3];k[c>>2]=e+8;p[a>>3]=f;break a}default:break a}while(0);while(0);return}function ie(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;if(b>>>0>0|(b|0)==0&a>>>0>4294967295)while(1){d=Cf(a|0,b|0,10,0)|0;c=c+-1|0;i[c>>0]=d|48;d=a;a=Bf(a|0,b|0,10,0)|0;if(!(b>>>0>9|(b|0)==9&d>>>0>4294967295))break;else b=L}if(a)while(1){c=c+-1|0;i[c>>0]=(a>>>0)%10|0|48;if(a>>>0<10)break;else a=(a>>>0)/10|0}return c|0}function je(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;f=b&255;d=(c|0)!=0;a:do if(d&(a&3|0)!=0){e=b&255;while(1){if((i[a>>0]|0)==e<<24>>24)break a;a=a+1|0;c=c+-1|0;d=(c|0)!=0;if(!(d&(a&3|0)!=0)){g=5;break}}}else g=5;while(0);b:do if((g|0)==5)if(d){e=b&255;if((i[a>>0]|0)!=e<<24>>24){d=ha(f,16843009)|0;c:do if(c>>>0>3)while(1){f=k[a>>2]^d;if((f&-2139062144^-2139062144)&f+-16843009|0)break;a=a+4|0;c=c+-4|0;if(c>>>0<=3){g=11;break c}}else g=11;while(0);if((g|0)==11)if(!c){c=0;break}while(1){if((i[a>>0]|0)==e<<24>>24)break b;a=a+1|0;c=c+-1|0;if(!c){c=0;break}}}}else c=0;while(0);return (c|0?a:0)|0}function ke(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0;g=r;r=r+256|0;f=g;do if((c|0)>(d|0)&(e&73728|0)==0){e=c-d|0;qf(f|0,b|0,(e>>>0>256?256:e)|0)|0;d=k[a>>2]|0;c=(d&32|0)==0;if(e>>>0>255){b=e;do{if(c){fe(f,256,a)|0;d=k[a>>2]|0}b=b+-256|0;c=(d&32|0)==0}while(b>>>0>255);if(c)e=e&255;else break}else if(!c)break;fe(f,e,a)|0}while(0);r=g;return}function le(){return 0}function me(a,b){a=a|0;b=b|0;a=Ud(a,b)|0;return ((i[a>>0]|0)==(b&255)<<24>>24?a:0)|0}function ne(a,b){a=a|0;b=b|0;oe(a,b,4);return}function oe(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0;e=b;a:do if(!((e^a)&3)){d=(c|0)!=0;if(d&(e&3|0)!=0)do{e=i[b>>0]|0;i[a>>0]=e;if(!(e<<24>>24))break a;c=c+-1|0;b=b+1|0;a=a+1|0;d=(c|0)!=0}while(d&(b&3|0)!=0);if(d){if(i[b>>0]|0){b:do if(c>>>0>3)do{d=k[b>>2]|0;if((d&-2139062144^-2139062144)&d+-16843009|0)break b;k[a>>2]=d;c=c+-4|0;b=b+4|0;a=a+4|0}while(c>>>0>3);while(0);f=11}}else c=0}else f=11;while(0);c:do if((f|0)==11)if(!c)c=0;else while(1){f=i[b>>0]|0;i[a>>0]=f;if(!(f<<24>>24))break c;c=c+-1|0;a=a+1|0;if(!c){c=0;break}else b=b+1|0}while(0);qf(a|0,0,c|0)|0;return}function pe(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;d=r;r=r+16|0;e=d;k[e>>2]=c;de(a,b,e)|0;r=d;return}function qe(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,j=0;j=r;r=r+16|0;h=j;g=b&255;i[h>>0]=g;d=a+16|0;e=k[d>>2]|0;if(!e)if(!(ge(a)|0)){e=k[d>>2]|0;f=4}else c=-1;else f=4;do if((f|0)==4){d=a+20|0;f=k[d>>2]|0;if(f>>>0<e>>>0?(c=b&255,(c|0)!=(i[a+75>>0]|0)):0){k[d>>2]=f+1;i[f>>0]=g;break}if((Ua[k[a+36>>2]&7](a,h,1)|0)==1)c=l[h>>0]|0;else c=-1}while(0);r=j;return c|0}function re(){var a=0,b=0,c=0;do if((k[235273]|0)>=0?(le()|0)!=0:0){if((i[941091]|0)!=10?(a=k[235259]|0,a>>>0<(k[235258]|0)>>>0):0){k[235259]=a+1;i[a>>0]=10;break}qe(941016,10)|0}else c=3;while(0);do if((c|0)==3){if((i[941091]|0)!=10?(b=k[235259]|0,b>>>0<(k[235258]|0)>>>0):0){k[235259]=b+1;i[b>>0]=10;break}qe(941016,10)|0}while(0);return}function se(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;fe(a,ha(c,b)|0,d)|0;return}function te(a,b,c){a=a|0;b=b|0;c=c|0;be(a,2147483647,b,c);return}function ue(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;d=r;r=r+16|0;e=d;k[e>>2]=c;te(a,b,e);r=d;return}function ve(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0,r=0,s=0,t=0,u=0,v=0,w=0,x=0,y=0,z=0,A=0,B=0,C=0,D=0,E=0,F=0,G=0;do if(a>>>0<245){o=a>>>0<11?16:a+11&-8;a=o>>>3;i=k[274328]|0;b=i>>>a;if(b&3|0){b=(b&1^1)+a|0;c=1097352+(b<<1<<2)|0;d=c+8|0;e=k[d>>2]|0;f=e+8|0;g=k[f>>2]|0;do if((c|0)!=(g|0)){if(g>>>0<(k[274332]|0)>>>0)Na();a=g+12|0;if((k[a>>2]|0)==(e|0)){k[a>>2]=c;k[d>>2]=g;break}else Na()}else k[274328]=i&~(1<<b);while(0);G=b<<3;k[e+4>>2]=G|3;G=e+G+4|0;k[G>>2]=k[G>>2]|1;G=f;return G|0}g=k[274330]|0;if(o>>>0>g>>>0){if(b|0){c=2<<a;c=b<<a&(c|0-c);c=(c&0-c)+-1|0;h=c>>>12&16;c=c>>>h;e=c>>>5&8;c=c>>>e;f=c>>>2&4;c=c>>>f;d=c>>>1&2;c=c>>>d;b=c>>>1&1;b=(e|h|f|d|b)+(c>>>b)|0;c=1097352+(b<<1<<2)|0;d=c+8|0;f=k[d>>2]|0;h=f+8|0;e=k[h>>2]|0;do if((c|0)!=(e|0)){if(e>>>0<(k[274332]|0)>>>0)Na();a=e+12|0;if((k[a>>2]|0)==(f|0)){k[a>>2]=c;k[d>>2]=e;j=k[274330]|0;break}else Na()}else{k[274328]=i&~(1<<b);j=g}while(0);g=(b<<3)-o|0;k[f+4>>2]=o|3;d=f+o|0;k[d+4>>2]=g|1;k[d+g>>2]=g;if(j|0){e=k[274333]|0;b=j>>>3;c=1097352+(b<<1<<2)|0;a=k[274328]|0;b=1<<b;if(a&b){a=c+8|0;b=k[a>>2]|0;if(b>>>0<(k[274332]|0)>>>0)Na();else{l=a;m=b}}else{k[274328]=a|b;l=c+8|0;m=c}k[l>>2]=e;k[m+12>>2]=e;k[e+8>>2]=m;k[e+12>>2]=c}k[274330]=g;k[274333]=d;G=h;return G|0}a=k[274329]|0;if(a){h=(a&0-a)+-1|0;F=h>>>12&16;h=h>>>F;E=h>>>5&8;h=h>>>E;G=h>>>2&4;h=h>>>G;b=h>>>1&2;h=h>>>b;i=h>>>1&1;i=k[1097616+((E|F|G|b|i)+(h>>>i)<<2)>>2]|0;h=(k[i+4>>2]&-8)-o|0;b=i;while(1){a=k[b+16>>2]|0;if(!a){a=k[b+20>>2]|0;if(!a)break}b=(k[a+4>>2]&-8)-o|0;G=b>>>0<h>>>0;h=G?b:h;b=a;i=G?a:i}e=k[274332]|0;if(i>>>0<e>>>0)Na();g=i+o|0;if(i>>>0>=g>>>0)Na();f=k[i+24>>2]|0;c=k[i+12>>2]|0;do if((c|0)==(i|0)){b=i+20|0;a=k[b>>2]|0;if(!a){b=i+16|0;a=k[b>>2]|0;if(!a){n=0;break}}while(1){c=a+20|0;d=k[c>>2]|0;if(d|0){a=d;b=c;continue}c=a+16|0;d=k[c>>2]|0;if(!d)break;else{a=d;b=c}}if(b>>>0<e>>>0)Na();else{k[b>>2]=0;n=a;break}}else{d=k[i+8>>2]|0;if(d>>>0<e>>>0)Na();a=d+12|0;if((k[a>>2]|0)!=(i|0))Na();b=c+8|0;if((k[b>>2]|0)==(i|0)){k[a>>2]=c;k[b>>2]=d;n=c;break}else Na()}while(0);do if(f|0){a=k[i+28>>2]|0;b=1097616+(a<<2)|0;if((i|0)==(k[b>>2]|0)){k[b>>2]=n;if(!n){k[274329]=k[274329]&~(1<<a);break}}else{if(f>>>0<(k[274332]|0)>>>0)Na();a=f+16|0;if((k[a>>2]|0)==(i|0))k[a>>2]=n;else k[f+20>>2]=n;if(!n)break}b=k[274332]|0;if(n>>>0<b>>>0)Na();k[n+24>>2]=f;a=k[i+16>>2]|0;do if(a|0)if(a>>>0<b>>>0)Na();else{k[n+16>>2]=a;k[a+24>>2]=n;break}while(0);a=k[i+20>>2]|0;if(a|0)if(a>>>0<(k[274332]|0)>>>0)Na();else{k[n+20>>2]=a;k[a+24>>2]=n;break}}while(0);if(h>>>0<16){G=h+o|0;k[i+4>>2]=G|3;G=i+G+4|0;k[G>>2]=k[G>>2]|1}else{k[i+4>>2]=o|3;k[g+4>>2]=h|1;k[g+h>>2]=h;a=k[274330]|0;if(a|0){d=k[274333]|0;b=a>>>3;c=1097352+(b<<1<<2)|0;a=k[274328]|0;b=1<<b;if(a&b){a=c+8|0;b=k[a>>2]|0;if(b>>>0<(k[274332]|0)>>>0)Na();else{p=a;q=b}}else{k[274328]=a|b;p=c+8|0;q=c}k[p>>2]=d;k[q+12>>2]=d;k[d+8>>2]=q;k[d+12>>2]=c}k[274330]=h;k[274333]=g}G=i+8|0;return G|0}}}else if(a>>>0<=4294967231){a=a+11|0;o=a&-8;j=k[274329]|0;if(j){c=0-o|0;a=a>>>8;if(a)if(o>>>0>16777215)i=31;else{q=(a+1048320|0)>>>16&8;z=a<<q;p=(z+520192|0)>>>16&4;z=z<<p;i=(z+245760|0)>>>16&2;i=14-(p|q|i)+(z<<i>>>15)|0;i=o>>>(i+7|0)&1|i<<1}else i=0;b=k[1097616+(i<<2)>>2]|0;a:do if(!b){a=0;b=0;z=86}else{e=c;a=0;g=o<<((i|0)==31?0:25-(i>>>1)|0);h=b;b=0;while(1){d=k[h+4>>2]&-8;c=d-o|0;if(c>>>0<e>>>0)if((d|0)==(o|0)){a=h;b=h;z=90;break a}else b=h;else c=e;d=k[h+20>>2]|0;h=k[h+16+(g>>>31<<2)>>2]|0;a=(d|0)==0|(d|0)==(h|0)?a:d;d=(h|0)==0;if(d){z=86;break}else{e=c;g=g<<(d&1^1)}}}while(0);if((z|0)==86){if((a|0)==0&(b|0)==0){a=2<<i;a=j&(a|0-a);if(!a)break;q=(a&0-a)+-1|0;m=q>>>12&16;q=q>>>m;l=q>>>5&8;q=q>>>l;n=q>>>2&4;q=q>>>n;p=q>>>1&2;q=q>>>p;a=q>>>1&1;a=k[1097616+((l|m|n|p|a)+(q>>>a)<<2)>>2]|0}if(!a){h=c;i=b}else z=90}if((z|0)==90)while(1){z=0;q=(k[a+4>>2]&-8)-o|0;d=q>>>0<c>>>0;c=d?q:c;b=d?a:b;d=k[a+16>>2]|0;if(d|0){a=d;z=90;continue}a=k[a+20>>2]|0;if(!a){h=c;i=b;break}else z=90}if((i|0)!=0?h>>>0<((k[274330]|0)-o|0)>>>0:0){e=k[274332]|0;if(i>>>0<e>>>0)Na();g=i+o|0;if(i>>>0>=g>>>0)Na();f=k[i+24>>2]|0;c=k[i+12>>2]|0;do if((c|0)==(i|0)){b=i+20|0;a=k[b>>2]|0;if(!a){b=i+16|0;a=k[b>>2]|0;if(!a){s=0;break}}while(1){c=a+20|0;d=k[c>>2]|0;if(d|0){a=d;b=c;continue}c=a+16|0;d=k[c>>2]|0;if(!d)break;else{a=d;b=c}}if(b>>>0<e>>>0)Na();else{k[b>>2]=0;s=a;break}}else{d=k[i+8>>2]|0;if(d>>>0<e>>>0)Na();a=d+12|0;if((k[a>>2]|0)!=(i|0))Na();b=c+8|0;if((k[b>>2]|0)==(i|0)){k[a>>2]=c;k[b>>2]=d;s=c;break}else Na()}while(0);do if(f|0){a=k[i+28>>2]|0;b=1097616+(a<<2)|0;if((i|0)==(k[b>>2]|0)){k[b>>2]=s;if(!s){k[274329]=k[274329]&~(1<<a);break}}else{if(f>>>0<(k[274332]|0)>>>0)Na();a=f+16|0;if((k[a>>2]|0)==(i|0))k[a>>2]=s;else k[f+20>>2]=s;if(!s)break}b=k[274332]|0;if(s>>>0<b>>>0)Na();k[s+24>>2]=f;a=k[i+16>>2]|0;do if(a|0)if(a>>>0<b>>>0)Na();else{k[s+16>>2]=a;k[a+24>>2]=s;break}while(0);a=k[i+20>>2]|0;if(a|0)if(a>>>0<(k[274332]|0)>>>0)Na();else{k[s+20>>2]=a;k[a+24>>2]=s;break}}while(0);do if(h>>>0>=16){k[i+4>>2]=o|3;k[g+4>>2]=h|1;k[g+h>>2]=h;a=h>>>3;if(h>>>0<256){c=1097352+(a<<1<<2)|0;b=k[274328]|0;a=1<<a;if(b&a){a=c+8|0;b=k[a>>2]|0;if(b>>>0<(k[274332]|0)>>>0)Na();else{t=a;v=b}}else{k[274328]=b|a;t=c+8|0;v=c}k[t>>2]=g;k[v+12>>2]=g;k[g+8>>2]=v;k[g+12>>2]=c;break}a=h>>>8;if(a)if(h>>>0>16777215)c=31;else{F=(a+1048320|0)>>>16&8;G=a<<F;E=(G+520192|0)>>>16&4;G=G<<E;c=(G+245760|0)>>>16&2;c=14-(E|F|c)+(G<<c>>>15)|0;c=h>>>(c+7|0)&1|c<<1}else c=0;d=1097616+(c<<2)|0;k[g+28>>2]=c;a=g+16|0;k[a+4>>2]=0;k[a>>2]=0;a=k[274329]|0;b=1<<c;if(!(a&b)){k[274329]=a|b;k[d>>2]=g;k[g+24>>2]=d;k[g+12>>2]=g;k[g+8>>2]=g;break}c=h<<((c|0)==31?0:25-(c>>>1)|0);d=k[d>>2]|0;while(1){if((k[d+4>>2]&-8|0)==(h|0)){z=148;break}b=d+16+(c>>>31<<2)|0;a=k[b>>2]|0;if(!a){z=145;break}else{c=c<<1;d=a}}if((z|0)==145)if(b>>>0<(k[274332]|0)>>>0)Na();else{k[b>>2]=g;k[g+24>>2]=d;k[g+12>>2]=g;k[g+8>>2]=g;break}else if((z|0)==148){a=d+8|0;b=k[a>>2]|0;G=k[274332]|0;if(b>>>0>=G>>>0&d>>>0>=G>>>0){k[b+12>>2]=g;k[a>>2]=g;k[g+8>>2]=b;k[g+12>>2]=d;k[g+24>>2]=0;break}else Na()}}else{G=h+o|0;k[i+4>>2]=G|3;G=i+G+4|0;k[G>>2]=k[G>>2]|1}while(0);G=i+8|0;return G|0}}}else o=-1;while(0);c=k[274330]|0;if(c>>>0>=o>>>0){a=c-o|0;b=k[274333]|0;if(a>>>0>15){G=b+o|0;k[274333]=G;k[274330]=a;k[G+4>>2]=a|1;k[G+a>>2]=a;k[b+4>>2]=o|3}else{k[274330]=0;k[274333]=0;k[b+4>>2]=c|3;G=b+c+4|0;k[G>>2]=k[G>>2]|1}G=b+8|0;return G|0}a=k[274331]|0;if(a>>>0>o>>>0){E=a-o|0;k[274331]=E;G=k[274334]|0;F=G+o|0;k[274334]=F;k[F+4>>2]=E|1;k[G+4>>2]=o|3;G=G+8|0;return G|0}do if(!(k[274446]|0)){a=Fa(30)|0;if(!(a+-1&a)){k[274448]=a;k[274447]=a;k[274449]=-1;k[274450]=-1;k[274451]=0;k[274439]=0;v=(Pa(0)|0)&-16^1431655768;k[274446]=v;break}else Na()}while(0);g=o+48|0;d=k[274448]|0;h=o+47|0;c=d+h|0;d=0-d|0;i=c&d;if(i>>>0<=o>>>0){G=0;return G|0}a=k[274438]|0;if(a|0?(t=k[274436]|0,v=t+i|0,v>>>0<=t>>>0|v>>>0>a>>>0):0){G=0;return G|0}b:do if(!(k[274439]&4)){b=k[274334]|0;c:do if(b){e=1097760;while(1){a=k[e>>2]|0;if(a>>>0<=b>>>0?(r=e+4|0,(a+(k[r>>2]|0)|0)>>>0>b>>>0):0)break;a=k[e+8>>2]|0;if(!a){z=173;break c}else e=a}a=c-(k[274331]|0)&d;if(a>>>0<2147483647){b=Aa(a|0)|0;if((b|0)==((k[e>>2]|0)+(k[r>>2]|0)|0)){if((b|0)!=(-1|0)){g=b;f=a;z=193;break b}}else z=183}}else z=173;while(0);do if((z|0)==173?(u=Aa(0)|0,(u|0)!=(-1|0)):0){a=u;b=k[274447]|0;c=b+-1|0;if(!(c&a))a=i;else a=i-a+(c+a&0-b)|0;b=k[274436]|0;c=b+a|0;if(a>>>0>o>>>0&a>>>0<2147483647){v=k[274438]|0;if(v|0?c>>>0<=b>>>0|c>>>0>v>>>0:0)break;b=Aa(a|0)|0;if((b|0)==(u|0)){g=u;f=a;z=193;break b}else z=183}}while(0);d:do if((z|0)==183){c=0-a|0;do if(g>>>0>a>>>0&(a>>>0<2147483647&(b|0)!=(-1|0))?(w=k[274448]|0,w=h-a+w&0-w,w>>>0<2147483647):0)if((Aa(w|0)|0)==(-1|0)){Aa(c|0)|0;break d}else{a=w+a|0;break}while(0);if((b|0)!=(-1|0)){g=b;f=a;z=193;break b}}while(0);k[274439]=k[274439]|4;z=190}else z=190;while(0);if((((z|0)==190?i>>>0<2147483647:0)?(x=Aa(i|0)|0,y=Aa(0)|0,x>>>0<y>>>0&((x|0)!=(-1|0)&(y|0)!=(-1|0))):0)?(f=y-x|0,f>>>0>(o+40|0)>>>0):0){g=x;z=193}if((z|0)==193){a=(k[274436]|0)+f|0;k[274436]=a;if(a>>>0>(k[274437]|0)>>>0)k[274437]=a;j=k[274334]|0;do if(j){e=1097760;while(1){a=k[e>>2]|0;b=e+4|0;c=k[b>>2]|0;if((g|0)==(a+c|0)){z=203;break}d=k[e+8>>2]|0;if(!d)break;else e=d}if(((z|0)==203?(k[e+12>>2]&8|0)==0:0)?j>>>0<g>>>0&j>>>0>=a>>>0:0){k[b>>2]=c+f;G=j+8|0;G=(G&7|0)==0?0:0-G&7;F=j+G|0;G=f-G+(k[274331]|0)|0;k[274334]=F;k[274331]=G;k[F+4>>2]=G|1;k[F+G+4>>2]=40;k[274335]=k[274450];break}a=k[274332]|0;if(g>>>0<a>>>0){k[274332]=g;h=g}else h=a;b=g+f|0;a=1097760;while(1){if((k[a>>2]|0)==(b|0)){z=211;break}a=k[a+8>>2]|0;if(!a){b=1097760;break}}if((z|0)==211)if(!(k[a+12>>2]&8)){k[a>>2]=g;m=a+4|0;k[m>>2]=(k[m>>2]|0)+f;m=g+8|0;m=g+((m&7|0)==0?0:0-m&7)|0;a=b+8|0;a=b+((a&7|0)==0?0:0-a&7)|0;l=m+o|0;i=a-m-o|0;k[m+4>>2]=o|3;do if((a|0)!=(j|0)){if((a|0)==(k[274333]|0)){G=(k[274330]|0)+i|0;k[274330]=G;k[274333]=l;k[l+4>>2]=G|1;k[l+G>>2]=G;break}b=k[a+4>>2]|0;if((b&3|0)==1){g=b&-8;e=b>>>3;e:do if(b>>>0>=256){f=k[a+24>>2]|0;d=k[a+12>>2]|0;do if((d|0)==(a|0)){d=a+16|0;c=d+4|0;b=k[c>>2]|0;if(!b){b=k[d>>2]|0;if(!b){E=0;break}else c=d}while(1){d=b+20|0;e=k[d>>2]|0;if(e|0){b=e;c=d;continue}d=b+16|0;e=k[d>>2]|0;if(!e)break;else{b=e;c=d}}if(c>>>0<h>>>0)Na();else{k[c>>2]=0;E=b;break}}else{e=k[a+8>>2]|0;if(e>>>0<h>>>0)Na();b=e+12|0;if((k[b>>2]|0)!=(a|0))Na();c=d+8|0;if((k[c>>2]|0)==(a|0)){k[b>>2]=d;k[c>>2]=e;E=d;break}else Na()}while(0);if(!f)break;b=k[a+28>>2]|0;c=1097616+(b<<2)|0;do if((a|0)!=(k[c>>2]|0)){if(f>>>0<(k[274332]|0)>>>0)Na();b=f+16|0;if((k[b>>2]|0)==(a|0))k[b>>2]=E;else k[f+20>>2]=E;if(!E)break e}else{k[c>>2]=E;if(E|0)break;k[274329]=k[274329]&~(1<<b);break e}while(0);d=k[274332]|0;if(E>>>0<d>>>0)Na();k[E+24>>2]=f;b=a+16|0;c=k[b>>2]|0;do if(c|0)if(c>>>0<d>>>0)Na();else{k[E+16>>2]=c;k[c+24>>2]=E;break}while(0);b=k[b+4>>2]|0;if(!b)break;if(b>>>0<(k[274332]|0)>>>0)Na();else{k[E+20>>2]=b;k[b+24>>2]=E;break}}else{c=k[a+8>>2]|0;d=k[a+12>>2]|0;b=1097352+(e<<1<<2)|0;do if((c|0)!=(b|0)){if(c>>>0<h>>>0)Na();if((k[c+12>>2]|0)==(a|0))break;Na()}while(0);if((d|0)==(c|0)){k[274328]=k[274328]&~(1<<e);break}do if((d|0)==(b|0))B=d+8|0;else{if(d>>>0<h>>>0)Na();b=d+8|0;if((k[b>>2]|0)==(a|0)){B=b;break}Na()}while(0);k[c+12>>2]=d;k[B>>2]=c}while(0);a=a+g|0;e=g+i|0}else e=i;a=a+4|0;k[a>>2]=k[a>>2]&-2;k[l+4>>2]=e|1;k[l+e>>2]=e;a=e>>>3;if(e>>>0<256){c=1097352+(a<<1<<2)|0;b=k[274328]|0;a=1<<a;do if(!(b&a)){k[274328]=b|a;F=c+8|0;G=c}else{a=c+8|0;b=k[a>>2]|0;if(b>>>0>=(k[274332]|0)>>>0){F=a;G=b;break}Na()}while(0);k[F>>2]=l;k[G+12>>2]=l;k[l+8>>2]=G;k[l+12>>2]=c;break}a=e>>>8;do if(!a)c=0;else{if(e>>>0>16777215){c=31;break}F=(a+1048320|0)>>>16&8;G=a<<F;E=(G+520192|0)>>>16&4;G=G<<E;c=(G+245760|0)>>>16&2;c=14-(E|F|c)+(G<<c>>>15)|0;c=e>>>(c+7|0)&1|c<<1}while(0);d=1097616+(c<<2)|0;k[l+28>>2]=c;a=l+16|0;k[a+4>>2]=0;k[a>>2]=0;a=k[274329]|0;b=1<<c;if(!(a&b)){k[274329]=a|b;k[d>>2]=l;k[l+24>>2]=d;k[l+12>>2]=l;k[l+8>>2]=l;break}c=e<<((c|0)==31?0:25-(c>>>1)|0);d=k[d>>2]|0;while(1){if((k[d+4>>2]&-8|0)==(e|0)){z=281;break}b=d+16+(c>>>31<<2)|0;a=k[b>>2]|0;if(!a){z=278;break}else{c=c<<1;d=a}}if((z|0)==278)if(b>>>0<(k[274332]|0)>>>0)Na();else{k[b>>2]=l;k[l+24>>2]=d;k[l+12>>2]=l;k[l+8>>2]=l;break}else if((z|0)==281){a=d+8|0;b=k[a>>2]|0;G=k[274332]|0;if(b>>>0>=G>>>0&d>>>0>=G>>>0){k[b+12>>2]=l;k[a>>2]=l;k[l+8>>2]=b;k[l+12>>2]=d;k[l+24>>2]=0;break}else Na()}}else{G=(k[274331]|0)+i|0;k[274331]=G;k[274334]=l;k[l+4>>2]=G|1}while(0);G=m+8|0;return G|0}else b=1097760;while(1){a=k[b>>2]|0;if(a>>>0<=j>>>0?(A=a+(k[b+4>>2]|0)|0,A>>>0>j>>>0):0)break;b=k[b+8>>2]|0}e=A+-47|0;b=e+8|0;b=e+((b&7|0)==0?0:0-b&7)|0;e=j+16|0;b=b>>>0<e>>>0?j:b;a=b+8|0;c=g+8|0;c=(c&7|0)==0?0:0-c&7;G=g+c|0;c=f+-40-c|0;k[274334]=G;k[274331]=c;k[G+4>>2]=c|1;k[G+c+4>>2]=40;k[274335]=k[274450];c=b+4|0;k[c>>2]=27;k[a>>2]=k[274440];k[a+4>>2]=k[274441];k[a+8>>2]=k[274442];k[a+12>>2]=k[274443];k[274440]=g;k[274441]=f;k[274443]=0;k[274442]=a;a=b+24|0;do{a=a+4|0;k[a>>2]=7}while((a+4|0)>>>0<A>>>0);if((b|0)!=(j|0)){f=b-j|0;k[c>>2]=k[c>>2]&-2;k[j+4>>2]=f|1;k[b>>2]=f;a=f>>>3;if(f>>>0<256){c=1097352+(a<<1<<2)|0;b=k[274328]|0;a=1<<a;if(b&a){a=c+8|0;b=k[a>>2]|0;if(b>>>0<(k[274332]|0)>>>0)Na();else{C=a;D=b}}else{k[274328]=b|a;C=c+8|0;D=c}k[C>>2]=j;k[D+12>>2]=j;k[j+8>>2]=D;k[j+12>>2]=c;break}a=f>>>8;if(a)if(f>>>0>16777215)c=31;else{F=(a+1048320|0)>>>16&8;G=a<<F;E=(G+520192|0)>>>16&4;G=G<<E;c=(G+245760|0)>>>16&2;c=14-(E|F|c)+(G<<c>>>15)|0;c=f>>>(c+7|0)&1|c<<1}else c=0;d=1097616+(c<<2)|0;k[j+28>>2]=c;k[j+20>>2]=0;k[e>>2]=0;a=k[274329]|0;b=1<<c;if(!(a&b)){k[274329]=a|b;k[d>>2]=j;k[j+24>>2]=d;k[j+12>>2]=j;k[j+8>>2]=j;break}c=f<<((c|0)==31?0:25-(c>>>1)|0);d=k[d>>2]|0;while(1){if((k[d+4>>2]&-8|0)==(f|0)){z=307;break}b=d+16+(c>>>31<<2)|0;a=k[b>>2]|0;if(!a){z=304;break}else{c=c<<1;d=a}}if((z|0)==304)if(b>>>0<(k[274332]|0)>>>0)Na();else{k[b>>2]=j;k[j+24>>2]=d;k[j+12>>2]=j;k[j+8>>2]=j;break}else if((z|0)==307){a=d+8|0;b=k[a>>2]|0;G=k[274332]|0;if(b>>>0>=G>>>0&d>>>0>=G>>>0){k[b+12>>2]=j;k[a>>2]=j;k[j+8>>2]=b;k[j+12>>2]=d;k[j+24>>2]=0;break}else Na()}}}else{G=k[274332]|0;if((G|0)==0|g>>>0<G>>>0)k[274332]=g;k[274440]=g;k[274441]=f;k[274443]=0;k[274337]=k[274446];k[274336]=-1;a=0;do{G=1097352+(a<<1<<2)|0;k[G+12>>2]=G;k[G+8>>2]=G;a=a+1|0}while((a|0)!=32);G=g+8|0;G=(G&7|0)==0?0:0-G&7;F=g+G|0;G=f+-40-G|0;k[274334]=F;k[274331]=G;k[F+4>>2]=G|1;k[F+G+4>>2]=40;k[274335]=k[274450]}while(0);a=k[274331]|0;if(a>>>0>o>>>0){E=a-o|0;k[274331]=E;G=k[274334]|0;F=G+o|0;k[274334]=F;k[F+4>>2]=E|1;k[G+4>>2]=o|3;G=G+8|0;return G|0}}G=Qd()|0;k[G>>2]=12;G=0;return G|0}
function we(a){a=a|0;var b=0,c=0,d=0,e=0,f=0,g=0,h=0,i=0,j=0,l=0,m=0,n=0,o=0,p=0,q=0;if(!a)return;c=a+-8|0;g=k[274332]|0;if(c>>>0<g>>>0)Na();a=k[a+-4>>2]|0;b=a&3;if((b|0)==1)Na();d=a&-8;m=c+d|0;do if(!(a&1)){a=k[c>>2]|0;if(!b)return;j=c+(0-a)|0;i=a+d|0;if(j>>>0<g>>>0)Na();if((j|0)==(k[274333]|0)){a=m+4|0;b=k[a>>2]|0;if((b&3|0)!=3){q=j;e=i;break}k[274330]=i;k[a>>2]=b&-2;k[j+4>>2]=i|1;k[j+i>>2]=i;return}d=a>>>3;if(a>>>0<256){b=k[j+8>>2]|0;c=k[j+12>>2]|0;a=1097352+(d<<1<<2)|0;if((b|0)!=(a|0)){if(b>>>0<g>>>0)Na();if((k[b+12>>2]|0)!=(j|0))Na()}if((c|0)==(b|0)){k[274328]=k[274328]&~(1<<d);q=j;e=i;break}if((c|0)!=(a|0)){if(c>>>0<g>>>0)Na();a=c+8|0;if((k[a>>2]|0)==(j|0))f=a;else Na()}else f=c+8|0;k[b+12>>2]=c;k[f>>2]=b;q=j;e=i;break}f=k[j+24>>2]|0;c=k[j+12>>2]|0;do if((c|0)==(j|0)){c=j+16|0;b=c+4|0;a=k[b>>2]|0;if(!a){a=k[c>>2]|0;if(!a){h=0;break}else b=c}while(1){c=a+20|0;d=k[c>>2]|0;if(d|0){a=d;b=c;continue}c=a+16|0;d=k[c>>2]|0;if(!d)break;else{a=d;b=c}}if(b>>>0<g>>>0)Na();else{k[b>>2]=0;h=a;break}}else{d=k[j+8>>2]|0;if(d>>>0<g>>>0)Na();a=d+12|0;if((k[a>>2]|0)!=(j|0))Na();b=c+8|0;if((k[b>>2]|0)==(j|0)){k[a>>2]=c;k[b>>2]=d;h=c;break}else Na()}while(0);if(f){a=k[j+28>>2]|0;b=1097616+(a<<2)|0;if((j|0)==(k[b>>2]|0)){k[b>>2]=h;if(!h){k[274329]=k[274329]&~(1<<a);q=j;e=i;break}}else{if(f>>>0<(k[274332]|0)>>>0)Na();a=f+16|0;if((k[a>>2]|0)==(j|0))k[a>>2]=h;else k[f+20>>2]=h;if(!h){q=j;e=i;break}}c=k[274332]|0;if(h>>>0<c>>>0)Na();k[h+24>>2]=f;a=j+16|0;b=k[a>>2]|0;do if(b|0)if(b>>>0<c>>>0)Na();else{k[h+16>>2]=b;k[b+24>>2]=h;break}while(0);a=k[a+4>>2]|0;if(a)if(a>>>0<(k[274332]|0)>>>0)Na();else{k[h+20>>2]=a;k[a+24>>2]=h;q=j;e=i;break}else{q=j;e=i}}else{q=j;e=i}}else{q=c;e=d}while(0);if(q>>>0>=m>>>0)Na();a=m+4|0;b=k[a>>2]|0;if(!(b&1))Na();if(!(b&2)){if((m|0)==(k[274334]|0)){p=(k[274331]|0)+e|0;k[274331]=p;k[274334]=q;k[q+4>>2]=p|1;if((q|0)!=(k[274333]|0))return;k[274333]=0;k[274330]=0;return}if((m|0)==(k[274333]|0)){p=(k[274330]|0)+e|0;k[274330]=p;k[274333]=q;k[q+4>>2]=p|1;k[q+p>>2]=p;return}e=(b&-8)+e|0;d=b>>>3;do if(b>>>0>=256){f=k[m+24>>2]|0;a=k[m+12>>2]|0;do if((a|0)==(m|0)){c=m+16|0;b=c+4|0;a=k[b>>2]|0;if(!a){a=k[c>>2]|0;if(!a){n=0;break}else b=c}while(1){c=a+20|0;d=k[c>>2]|0;if(d|0){a=d;b=c;continue}c=a+16|0;d=k[c>>2]|0;if(!d)break;else{a=d;b=c}}if(b>>>0<(k[274332]|0)>>>0)Na();else{k[b>>2]=0;n=a;break}}else{b=k[m+8>>2]|0;if(b>>>0<(k[274332]|0)>>>0)Na();c=b+12|0;if((k[c>>2]|0)!=(m|0))Na();d=a+8|0;if((k[d>>2]|0)==(m|0)){k[c>>2]=a;k[d>>2]=b;n=a;break}else Na()}while(0);if(f|0){a=k[m+28>>2]|0;b=1097616+(a<<2)|0;if((m|0)==(k[b>>2]|0)){k[b>>2]=n;if(!n){k[274329]=k[274329]&~(1<<a);break}}else{if(f>>>0<(k[274332]|0)>>>0)Na();a=f+16|0;if((k[a>>2]|0)==(m|0))k[a>>2]=n;else k[f+20>>2]=n;if(!n)break}c=k[274332]|0;if(n>>>0<c>>>0)Na();k[n+24>>2]=f;a=m+16|0;b=k[a>>2]|0;do if(b|0)if(b>>>0<c>>>0)Na();else{k[n+16>>2]=b;k[b+24>>2]=n;break}while(0);a=k[a+4>>2]|0;if(a|0)if(a>>>0<(k[274332]|0)>>>0)Na();else{k[n+20>>2]=a;k[a+24>>2]=n;break}}}else{b=k[m+8>>2]|0;c=k[m+12>>2]|0;a=1097352+(d<<1<<2)|0;if((b|0)!=(a|0)){if(b>>>0<(k[274332]|0)>>>0)Na();if((k[b+12>>2]|0)!=(m|0))Na()}if((c|0)==(b|0)){k[274328]=k[274328]&~(1<<d);break}if((c|0)!=(a|0)){if(c>>>0<(k[274332]|0)>>>0)Na();a=c+8|0;if((k[a>>2]|0)==(m|0))l=a;else Na()}else l=c+8|0;k[b+12>>2]=c;k[l>>2]=b}while(0);k[q+4>>2]=e|1;k[q+e>>2]=e;if((q|0)==(k[274333]|0)){k[274330]=e;return}}else{k[a>>2]=b&-2;k[q+4>>2]=e|1;k[q+e>>2]=e}a=e>>>3;if(e>>>0<256){c=1097352+(a<<1<<2)|0;b=k[274328]|0;a=1<<a;if(b&a){a=c+8|0;b=k[a>>2]|0;if(b>>>0<(k[274332]|0)>>>0)Na();else{o=a;p=b}}else{k[274328]=b|a;o=c+8|0;p=c}k[o>>2]=q;k[p+12>>2]=q;k[q+8>>2]=p;k[q+12>>2]=c;return}a=e>>>8;if(a)if(e>>>0>16777215)c=31;else{o=(a+1048320|0)>>>16&8;p=a<<o;n=(p+520192|0)>>>16&4;p=p<<n;c=(p+245760|0)>>>16&2;c=14-(n|o|c)+(p<<c>>>15)|0;c=e>>>(c+7|0)&1|c<<1}else c=0;d=1097616+(c<<2)|0;k[q+28>>2]=c;k[q+20>>2]=0;k[q+16>>2]=0;a=k[274329]|0;b=1<<c;do if(a&b){c=e<<((c|0)==31?0:25-(c>>>1)|0);d=k[d>>2]|0;while(1){if((k[d+4>>2]&-8|0)==(e|0)){a=130;break}b=d+16+(c>>>31<<2)|0;a=k[b>>2]|0;if(!a){a=127;break}else{c=c<<1;d=a}}if((a|0)==127)if(b>>>0<(k[274332]|0)>>>0)Na();else{k[b>>2]=q;k[q+24>>2]=d;k[q+12>>2]=q;k[q+8>>2]=q;break}else if((a|0)==130){a=d+8|0;b=k[a>>2]|0;p=k[274332]|0;if(b>>>0>=p>>>0&d>>>0>=p>>>0){k[b+12>>2]=q;k[a>>2]=q;k[q+8>>2]=b;k[q+12>>2]=d;k[q+24>>2]=0;break}else Na()}}else{k[274329]=a|b;k[d>>2]=q;k[q+24>>2]=d;k[q+12>>2]=q;k[q+8>>2]=q}while(0);q=(k[274336]|0)+-1|0;k[274336]=q;if(!q)a=1097768;else return;while(1){a=k[a>>2]|0;if(!a)break;else a=a+8|0}k[274336]=-1;return}function xe(){wa(1096524,1096553,1164,1096636)}function ye(){wa(1096657,1096553,1175,1096686)}function ze(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;if(c>>>0>4294967279)xe();if(c>>>0<11){i[a>>0]=c<<1;a=a+1|0}else{e=c+16&-16;d=gf(e)|0;k[a+8>>2]=d;k[a>>2]=e|1;k[a+4>>2]=c;a=d}tf(a|0,b|0,c|0)|0;i[a+c>>0]=0;return}function Ae(a){a=a|0;if(i[a>>0]&1)Qe(k[a+8>>2]|0);return}function Be(a,b,c,d,e,f,g,h){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;h=h|0;var j=0,l=0,m=0;if((-18-b|0)>>>0<c>>>0)xe();if(!(i[a>>0]&1))m=a+1|0;else m=k[a+8>>2]|0;if(b>>>0<2147483623){j=c+b|0;l=b<<1;j=j>>>0<l>>>0?l:j;j=j>>>0<11?11:j+16&-16}else j=-17;l=gf(j)|0;if(e|0)tf(l|0,m|0,e|0)|0;if(g|0)tf(l+e|0,h|0,g|0)|0;c=d-f|0;if((c|0)!=(e|0))tf(l+e+g|0,m+e+f|0,c-e|0)|0;if((b|0)!=10)Qe(m);k[a+8>>2]=l;k[a>>2]=j|1;b=c+g|0;k[a+4>>2]=b;i[l+b>>0]=0;return}function Ce(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0;if(b|0){d=i[a>>0]|0;if(!(d&1))e=10;else{d=k[a>>2]|0;e=(d&-2)+-1|0;d=d&255}if(!(d&1))f=(d&255)>>>1;else f=k[a+4>>2]|0;if((e-f|0)>>>0<b>>>0){De(a,e,b-e+f|0,f,f);d=i[a>>0]|0}if(!(d&1))e=a+1|0;else e=k[a+8>>2]|0;qf(e+f|0,c|0,b|0)|0;d=f+b|0;if(!(i[a>>0]&1))i[a>>0]=d<<1;else k[a+4>>2]=d;i[e+d>>0]=0}return}function De(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0;if((-17-b|0)>>>0<c>>>0)xe();if(!(i[a>>0]&1))g=a+1|0;else g=k[a+8>>2]|0;if(b>>>0<2147483623){c=c+b|0;f=b<<1;c=c>>>0<f>>>0?f:c;c=c>>>0<11?11:c+16&-16}else c=-17;f=gf(c)|0;if(e|0)tf(f|0,g|0,e|0)|0;if((d|0)!=(e|0))tf(f+e|0,g+e|0,d-e|0)|0;if((b|0)!=10)Qe(g);k[a+8>>2]=f;k[a>>2]=c|1;return}function Ee(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0;d=i[a>>0]|0;if(!(d&1))f=10;else{d=k[a>>2]|0;f=(d&-2)+-1|0;d=d&255}e=(d&1)==0;if(e)d=(d&255)>>>1;else d=k[a+4>>2]|0;if((f-d|0)>>>0>=c>>>0){if(c|0){if(e)e=a+1|0;else e=k[a+8>>2]|0;tf(e+d|0,b|0,c|0)|0;d=d+c|0;if(!(i[a>>0]&1))i[a>>0]=d<<1;else k[a+4>>2]=d;i[e+d>>0]=0}}else Be(a,f,c-f+d|0,d,d,0,c,b);return}function Fe(a,b){a=a|0;b=b|0;Ee(a,b,Vd(b)|0);return}function Ge(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0;c=i[a>>0]|0;d=(c&1)!=0;if(d){e=(k[a>>2]&-2)+-1|0;f=k[a+4>>2]|0}else{e=10;f=(c&255)>>>1}if((f|0)==(e|0)){De(a,e,1,e,e);if(!(i[a>>0]&1))d=7;else d=8}else if(d)d=8;else d=7;if((d|0)==7){i[a>>0]=(f<<1)+2;c=a+1|0}else if((d|0)==8){c=k[a+8>>2]|0;k[a+4>>2]=f+1}a=c+f|0;i[a>>0]=b;i[a+1>>0]=0;return}function He(a,b){a=a|0;b=b|0;var c=0,d=0;c=i[a>>0]|0;d=(c&1)==0;if(d)c=(c&255)>>>1;else c=k[a+4>>2]|0;if(c>>>0<b>>>0)ye();if(d){i[a>>0]=b<<1;c=a+1|0}else{c=k[a+8>>2]|0;k[a+4>>2]=b}i[c+b>>0]=0;return}function Ie(a,b){a=a|0;b=b|0;var c=0,d=0;c=i[a>>0]|0;if(!(c&1)){d=(c&255)>>>1;c=a+1|0}else{d=k[a+4>>2]|0;c=k[a+8>>2]|0}if(d>>>0>b>>>0){b=je(c+b|0,44,d-b|0)|0;c=(b|0)==0?-1:b-c|0}else c=-1;return c|0}function Je(){var a=0,b=0,c=0,d=0,e=0,f=0,g=0,h=0;e=r;r=r+48|0;g=e+32|0;c=e+24|0;h=e+16|0;f=e;e=e+36|0;a=Ke()|0;if(a|0?(d=k[a>>2]|0,d|0):0){a=d+48|0;b=k[a>>2]|0;a=k[a+4>>2]|0;if(!((b&-256|0)==1126902528&(a|0)==1129074247)){k[c>>2]=1096989;Ne(1097084,c)}if((b|0)==1126902529&(a|0)==1129074247)a=k[d+44>>2]|0;else a=d+80|0;k[e>>2]=a;d=k[d>>2]|0;a=k[d+4>>2]|0;if(Te(8,d,e)|0){h=k[e>>2]|0;h=Xa[k[(k[h>>2]|0)+8>>2]&3](h)|0;k[f>>2]=1096989;k[f+4>>2]=a;k[f+8>>2]=h;Ne(1096998,f)}else{k[h>>2]=1096989;k[h+4>>2]=a;Ne(1097043,h)}}Ne(1097122,g)}function Ke(){var a=0,b=0;a=r;r=r+16|0;if(!(Ia(1097808,2)|0)){b=Ga(k[274453]|0)|0;r=a;return b|0}else Ne(1096810,a);return 0}function Le(){var a=0;a=r;r=r+16|0;if(!(Ja(1097812,9)|0)){r=a;return}else Ne(1096760,a)}function Me(a){a=a|0;var b=0;b=r;r=r+16|0;we(a);if(!(La(k[274453]|0,0)|0)){r=b;return}else Ne(1096707,b)}function Ne(a,b){a=a|0;b=b|0;var c=0;c=r;r=r+16|0;k[c>>2]=b;de(941016,a,c)|0;re();Na()}function Oe(a){a=a|0;return}function Pe(a){a=a|0;Qe(a);return}function Qe(a){a=a|0;we(a);return}function Re(a){a=a|0;return}function Se(a){a=a|0;return}function Te(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;g=r;r=r+64|0;f=g;if((a|0)!=(b|0))if((b|0)!=0?(e=Ue(b,16)|0,(e|0)!=0):0){b=f;d=b+56|0;do{k[b>>2]=0;b=b+4|0}while((b|0)<(d|0));k[f>>2]=e;k[f+8>>2]=a;k[f+12>>2]=-1;k[f+48>>2]=1;_a[k[(k[e>>2]|0)+28>>2]&3](e,f,k[c>>2]|0,1);if((k[f+24>>2]|0)==1){k[c>>2]=k[f+16>>2];b=1}else b=0}else b=0;else b=1;r=g;return b|0}function Ue(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0,g=0,h=0,l=0,m=0,n=0,o=0,p=0,q=0,s=0;s=r;r=r+64|0;q=s;p=k[a>>2]|0;o=a+(k[p+-8>>2]|0)|0;p=k[p+-4>>2]|0;k[q>>2]=b;k[q+4>>2]=a;k[q+8>>2]=48;h=q+12|0;l=q+16|0;a=q+20|0;c=q+24|0;d=q+28|0;e=q+32|0;f=q+40|0;g=(p|0)==(b|0);m=h;n=m+40|0;do{k[m>>2]=0;m=m+4|0}while((m|0)<(n|0));j[h+40>>1]=0;i[h+42>>0]=0;a:do if(g){k[q+48>>2]=1;Za[k[(k[b>>2]|0)+20>>2]&3](b,q,o,o,1,0);a=(k[c>>2]|0)==1?o:0}else{Va[k[(k[p>>2]|0)+24>>2]&3](p,q,o,1,0);switch(k[q+36>>2]|0){case 0:{a=(k[f>>2]|0)==1&(k[d>>2]|0)==1&(k[e>>2]|0)==1?k[a>>2]|0:0;break a}case 1:break;default:{a=0;break a}}if((k[c>>2]|0)!=1?!((k[f>>2]|0)==0&(k[d>>2]|0)==1&(k[e>>2]|0)==1):0){a=0;break}a=k[l>>2]|0}while(0);r=s;return a|0}function Ve(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;if((a|0)==(k[b+8>>2]|0))We(b,c,d,e);else{a=k[a+8>>2]|0;Za[k[(k[a>>2]|0)+20>>2]&3](a,b,c,d,e,f)}return}function We(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0;i[a+53>>0]=1;do if((k[a+4>>2]|0)==(c|0)){i[a+52>>0]=1;c=a+16|0;e=k[c>>2]|0;if(!e){k[c>>2]=b;k[a+24>>2]=d;k[a+36>>2]=1;if(!((d|0)==1?(k[a+48>>2]|0)==1:0))break;i[a+54>>0]=1;break}if((e|0)!=(b|0)){d=a+36|0;k[d>>2]=(k[d>>2]|0)+1;i[a+54>>0]=1;break}e=a+24|0;c=k[e>>2]|0;if((c|0)==2){k[e>>2]=d;c=d}if((c|0)==1?(k[a+48>>2]|0)==1:0)i[a+54>>0]=1}while(0);return}function Xe(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0;do if((a|0)==(k[b+8>>2]|0)){if((k[b+4>>2]|0)==(c|0)?(f=b+28|0,(k[f>>2]|0)!=1):0)k[f>>2]=d}else{if((a|0)!=(k[b>>2]|0)){h=k[a+8>>2]|0;Va[k[(k[h>>2]|0)+24>>2]&3](h,b,c,d,e);break}if((k[b+16>>2]|0)!=(c|0)?(h=b+20|0,(k[h>>2]|0)!=(c|0)):0){k[b+32>>2]=d;g=b+44|0;if((k[g>>2]|0)==4)break;f=b+52|0;i[f>>0]=0;d=b+53|0;i[d>>0]=0;a=k[a+8>>2]|0;Za[k[(k[a>>2]|0)+20>>2]&3](a,b,c,c,1,e);if(i[d>>0]|0)if(!(i[f>>0]|0)){f=1;d=13}else d=17;else{f=0;d=13}do if((d|0)==13){k[h>>2]=c;c=b+40|0;k[c>>2]=(k[c>>2]|0)+1;if((k[b+36>>2]|0)==1?(k[b+24>>2]|0)==2:0){i[b+54>>0]=1;if(f){d=17;break}else{f=4;break}}if(f)d=17;else f=4}while(0);if((d|0)==17)f=3;k[g>>2]=f;break}if((d|0)==1)k[b+32>>2]=1}while(0);return}function Ye(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;if((a|0)==(k[b+8>>2]|0))Ze(b,c,d);else{a=k[a+8>>2]|0;_a[k[(k[a>>2]|0)+28>>2]&3](a,b,c,d)}return}function Ze(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;d=a+16|0;e=k[d>>2]|0;do if(e){if((e|0)!=(b|0)){c=a+36|0;k[c>>2]=(k[c>>2]|0)+1;k[a+24>>2]=2;i[a+54>>0]=1;break}d=a+24|0;if((k[d>>2]|0)==2)k[d>>2]=c}else{k[d>>2]=b;k[a+24>>2]=c;k[a+36>>2]=1}while(0);return}function _e(a){a=a|0;Qe(a);return}function $e(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;if((a|0)==(k[b+8>>2]|0))We(b,c,d,e);return}function af(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0;do if((a|0)==(k[b+8>>2]|0)){if((k[b+4>>2]|0)==(c|0)?(g=b+28|0,(k[g>>2]|0)!=1):0)k[g>>2]=d}else if((a|0)==(k[b>>2]|0)){if((k[b+16>>2]|0)!=(c|0)?(f=b+20|0,(k[f>>2]|0)!=(c|0)):0){k[b+32>>2]=d;k[f>>2]=c;e=b+40|0;k[e>>2]=(k[e>>2]|0)+1;if((k[b+36>>2]|0)==1?(k[b+24>>2]|0)==2:0)i[b+54>>0]=1;k[b+44>>2]=4;break}if((d|0)==1)k[b+32>>2]=1}while(0);return}function bf(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;if((a|0)==(k[b+8>>2]|0))Ze(b,c,d);return}function cf(){var a=0,b=0,c=0,d=0;c=r;r=r+16|0;d=c+8|0;a=Ke()|0;if((a|0?(b=k[a>>2]|0,b|0):0)?(a=b+48|0,(k[a>>2]&-256|0)==1126902528?(k[a+4>>2]|0)==1129074247:0):0){Ya[k[b+12>>2]&3]();Ne(1097134,c)}c=k[235310]|0;k[235310]=c+0;Ya[c&3]();Ne(1097134,d)}function df(a){a=a|0;return}function ef(a){a=a|0;Qe(a);return}function ff(a){a=a|0;return 1097187}function gf(a){a=a|0;var b=0,c=0;b=(a|0)==0?1:a;while(1){c=ve(b)|0;if(c|0){a=6;break}a=hf()|0;if(!a){a=5;break}Ya[a&3]()}if((a|0)==5){c=xa(4)|0;k[c>>2]=941332;Ma(c|0,72,6)}else if((a|0)==6)return c|0;return 0}function hf(){var a=0;a=k[274454]|0;k[274454]=a+0;return a|0}function jf(a){a=a|0;return gf(a)|0}function kf(a){a=a|0;Qe(a);return}function lf(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0;e=r;r=r+16|0;d=e;k[d>>2]=k[c>>2];a=Ua[k[(k[a>>2]|0)+16>>2]&7](a,b,d)|0;if(a)k[c>>2]=k[d>>2];r=e;return a&1|0}function mf(a){a=a|0;if(!a)a=0;else a=(Ue(a,104)|0)!=0;return a&1|0}function nf(){}function of(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;c=a+c>>>0;return (L=b+d+(c>>>0<a>>>0|0)>>>0,c|0)|0}function pf(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;d=b-d-(c>>>0>a>>>0|0)>>>0;return (L=d,a-c>>>0|0)|0}function qf(a,b,c){a=a|0;b=b|0;c=c|0;var d=0,e=0,f=0,g=0;d=a+c|0;if((c|0)>=20){b=b&255;f=a&3;g=b|b<<8|b<<16|b<<24;e=d&~3;if(f){f=a+4-f|0;while((a|0)<(f|0)){i[a>>0]=b;a=a+1|0}}while((a|0)<(e|0)){k[a>>2]=g;a=a+4|0}}while((a|0)<(d|0)){i[a>>0]=b;a=a+1|0}return a-c|0}function rf(a,b,c){a=a|0;b=b|0;c=c|0;if((c|0)<32){L=b>>>c;return a>>>c|(b&(1<<c)-1)<<32-c}L=0;return b>>>c-32|0}function sf(a,b,c){a=a|0;b=b|0;c=c|0;if((c|0)<32){L=b<<c|(a&(1<<c)-1<<32-c)>>>32-c;return a<<c}L=a<<c-32;return 0}function tf(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;if((c|0)>=4096)return Ca(a|0,b|0,c|0)|0;d=a|0;if((a&3)==(b&3)){while(a&3){if(!c)return d|0;i[a>>0]=i[b>>0]|0;a=a+1|0;b=b+1|0;c=c-1|0}while((c|0)>=4){k[a>>2]=k[b>>2];a=a+4|0;b=b+4|0;c=c-4|0}}while((c|0)>0){i[a>>0]=i[b>>0]|0;a=a+1|0;b=b+1|0;c=c-1|0}return d|0}function uf(a,b,c){a=a|0;b=b|0;c=c|0;var d=0;if((b|0)<(a|0)&(a|0)<(b+c|0)){d=a;b=b+c|0;a=a+c|0;while((c|0)>0){a=a-1|0;b=b-1|0;c=c-1|0;i[a>>0]=i[b>>0]|0}a=d}else tf(a,b,c)|0;return a|0}function vf(a,b,c){a=a|0;b=b|0;c=c|0;if((c|0)<32){L=b>>c;return a>>>c|(b&(1<<c)-1)<<32-c}L=(b|0)<0?-1:0;return b>>c-32|0}function wf(a){a=a|0;var b=0;b=i[v+(a&255)>>0]|0;if((b|0)<8)return b|0;b=i[v+(a>>8&255)>>0]|0;if((b|0)<8)return b+8|0;b=i[v+(a>>16&255)>>0]|0;if((b|0)<8)return b+16|0;return (i[v+(a>>>24)>>0]|0)+24|0}function xf(a,b){a=a|0;b=b|0;var c=0,d=0,e=0,f=0;f=a&65535;e=b&65535;c=ha(e,f)|0;d=a>>>16;a=(c>>>16)+(ha(e,d)|0)|0;e=b>>>16;b=ha(e,f)|0;return (L=(a>>>16)+(ha(e,d)|0)+(((a&65535)+b|0)>>>16)|0,a+b<<16|c&65535|0)|0}function yf(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0;j=b>>31|((b|0)<0?-1:0)<<1;i=((b|0)<0?-1:0)>>31|((b|0)<0?-1:0)<<1;f=d>>31|((d|0)<0?-1:0)<<1;e=((d|0)<0?-1:0)>>31|((d|0)<0?-1:0)<<1;h=pf(j^a|0,i^b|0,j|0,i|0)|0;g=L;a=f^j;b=e^i;return pf((Df(h,g,pf(f^c|0,e^d|0,f|0,e|0)|0,L,0)|0)^a|0,L^b|0,a|0,b|0)|0}function zf(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0,g=0,h=0,i=0,j=0;e=r;r=r+16|0;h=e|0;g=b>>31|((b|0)<0?-1:0)<<1;f=((b|0)<0?-1:0)>>31|((b|0)<0?-1:0)<<1;j=d>>31|((d|0)<0?-1:0)<<1;i=((d|0)<0?-1:0)>>31|((d|0)<0?-1:0)<<1;a=pf(g^a|0,f^b|0,g|0,f|0)|0;b=L;Df(a,b,pf(j^c|0,i^d|0,j|0,i|0)|0,L,h)|0;d=pf(k[h>>2]^g|0,k[h+4>>2]^f|0,g|0,f|0)|0;c=L;r=e;return (L=c,d)|0}function Af(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0;e=a;f=c;c=xf(e,f)|0;a=L;return (L=(ha(b,f)|0)+(ha(d,e)|0)+a|a&0,c|0|0)|0}function Bf(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;return Df(a,b,c,d,0)|0}function Cf(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;var e=0,f=0;f=r;r=r+16|0;e=f|0;Df(a,b,c,d,e)|0;r=f;return (L=k[e+4>>2]|0,k[e>>2]|0)|0}function Df(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;var f=0,g=0,h=0,i=0,j=0,l=0,m=0,n=0,o=0,p=0;l=a;i=b;j=i;g=c;n=d;h=n;if(!j){f=(e|0)!=0;if(!h){if(f){k[e>>2]=(l>>>0)%(g>>>0);k[e+4>>2]=0}n=0;e=(l>>>0)/(g>>>0)>>>0;return (L=n,e)|0}else{if(!f){n=0;e=0;return (L=n,e)|0}k[e>>2]=a|0;k[e+4>>2]=b&0;n=0;e=0;return (L=n,e)|0}}f=(h|0)==0;do if(g){if(!f){f=(ja(h|0)|0)-(ja(j|0)|0)|0;if(f>>>0<=31){m=f+1|0;h=31-f|0;b=f-31>>31;g=m;a=l>>>(m>>>0)&b|j<<h;b=j>>>(m>>>0)&b;f=0;h=l<<h;break}if(!e){n=0;e=0;return (L=n,e)|0}k[e>>2]=a|0;k[e+4>>2]=i|b&0;n=0;e=0;return (L=n,e)|0}f=g-1|0;if(f&g|0){h=(ja(g|0)|0)+33-(ja(j|0)|0)|0;p=64-h|0;m=32-h|0;i=m>>31;o=h-32|0;b=o>>31;g=h;a=m-1>>31&j>>>(o>>>0)|(j<<m|l>>>(h>>>0))&b;b=b&j>>>(h>>>0);f=l<<p&i;h=(j<<p|l>>>(o>>>0))&i|l<<m&h-33>>31;break}if(e|0){k[e>>2]=f&l;k[e+4>>2]=0}if((g|0)==1){o=i|b&0;p=a|0|0;return (L=o,p)|0}else{p=wf(g|0)|0;o=j>>>(p>>>0)|0;p=j<<32-p|l>>>(p>>>0)|0;return (L=o,p)|0}}else{if(f){if(e|0){k[e>>2]=(j>>>0)%(g>>>0);k[e+4>>2]=0}o=0;p=(j>>>0)/(g>>>0)>>>0;return (L=o,p)|0}if(!l){if(e|0){k[e>>2]=0;k[e+4>>2]=(j>>>0)%(h>>>0)}o=0;p=(j>>>0)/(h>>>0)>>>0;return (L=o,p)|0}f=h-1|0;if(!(f&h)){if(e|0){k[e>>2]=a|0;k[e+4>>2]=f&j|b&0}o=0;p=j>>>((wf(h|0)|0)>>>0);return (L=o,p)|0}f=(ja(h|0)|0)-(ja(j|0)|0)|0;if(f>>>0<=30){b=f+1|0;h=31-f|0;g=b;a=j<<h|l>>>(b>>>0);b=j>>>(b>>>0);f=0;h=l<<h;break}if(!e){o=0;p=0;return (L=o,p)|0}k[e>>2]=a|0;k[e+4>>2]=i|b&0;o=0;p=0;return (L=o,p)|0}while(0);if(!g){j=h;i=0;h=0}else{m=c|0|0;l=n|d&0;j=of(m|0,l|0,-1,-1)|0;c=L;i=h;h=0;do{d=i;i=f>>>31|i<<1;f=h|f<<1;d=a<<1|d>>>31|0;n=a>>>31|b<<1|0;pf(j|0,c|0,d|0,n|0)|0;p=L;o=p>>31|((p|0)<0?-1:0)<<1;h=o&1;a=pf(d|0,n|0,o&m|0,(((p|0)<0?-1:0)>>31|((p|0)<0?-1:0)<<1)&l|0)|0;b=L;g=g-1|0}while((g|0)!=0);j=i;i=0}g=0;if(e|0){k[e>>2]=a;k[e+4>>2]=b}o=(f|0)>>>31|(j|g)<<1|(g<<1|f>>>31)&0|i;p=(f<<1|0>>>31)&-2|h;return (L=o,p)|0}function Ef(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;return Ua[a&7](b|0,c|0,d|0)|0}function Ff(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;Va[a&3](b|0,c|0,d|0,e|0,f|0)}function Gf(a,b){a=a|0;b=b|0;Wa[a&15](b|0)}function Hf(a,b){a=a|0;b=b|0;return Xa[a&3](b|0)|0}function If(a){a=a|0;Ya[a&3]()}function Jf(a,b,c,d,e,f,g){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;g=g|0;Za[a&3](b|0,c|0,d|0,e|0,f|0,g|0)}function Kf(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;_a[a&3](b|0,c|0,d|0,e|0)}function Lf(a,b,c){a=a|0;b=b|0;c=c|0;ka(0);return 0}function Mf(a,b,c,d,e){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;ka(1)}function Nf(a){a=a|0;ka(2)}function Of(a){a=a|0;ka(3);return 0}function Pf(){ka(4)}function Qf(a,b,c,d,e,f){a=a|0;b=b|0;c=c|0;d=d|0;e=e|0;f=f|0;ka(5)}function Rf(a,b,c,d){a=a|0;b=b|0;c=c|0;d=d|0;ka(6)}

// EMSCRIPTEN_END_FUNCS
var Ua=[Lf,Rd,Td,ce,Te,Lf,Lf,Lf];var Va=[Mf,af,Xe,Mf];var Wa=[Nf,Oe,_e,Re,Se,Pe,df,ef,Sd,Me,Nf,Nf,Nf,Nf,Nf,Nf];var Xa=[Of,Od,ff,Of];var Ya=[Pf,Je,Le,Pf];var Za=[Qf,$e,Ve,Qf];var _a=[Rf,bf,Ye,Rf];return{_emscripten_bind_LanguageInfo_getLanguageCode_0:Jd,_bitshift64Lshr:rf,_bitshift64Shl:sf,_malloc:ve,___cxa_is_pointer_type:mf,_emscripten_bind_LanguageGuess_getPercent_0:Ad,_emscripten_bind_VoidPtr___destroy___0:Nd,_memset:qf,_memcpy:tf,_emscripten_bind_LanguageInfo_getIsReliable_0:Id,_i64Subtract:pf,_emscripten_bind_LanguageInfo___destroy___0:Bd,_i64Add:of,_emscripten_bind_LanguageInfo_get_languages_1:Kd,_emscripten_bind_Language_getLanguageCode_0:Md,_emscripten_bind_LanguageGuess___destroy___0:yd,_emscripten_bind_Language___destroy___0:Ld,___cxa_can_catch:lf,_free:we,_emscripten_bind_LanguageInfo_detectLanguage_5:Gd,_memmove:uf,_emscripten_bind_LanguageInfo_detectLanguage_2:Dd,_emscripten_bind_LanguageGuess_getLanguageCode_0:zd,runPostSets:nf,_emscripten_replace_memory:Ta,stackAlloc:$a,stackSave:ab,stackRestore:bb,establishStackSpace:cb,setThrew:db,setTempRet0:gb,getTempRet0:hb,dynCall_iiii:Ef,dynCall_viiiii:Ff,dynCall_vi:Gf,dynCall_ii:Hf,dynCall_v:If,dynCall_viiiiii:Jf,dynCall_viiii:Kf}})


// EMSCRIPTEN_END_ASM
(c.L,c.M,buffer),zb=c._emscripten_bind_LanguageInfo_getLanguageCode_0=M._emscripten_bind_LanguageInfo_getLanguageCode_0,pb=c._bitshift64Lshr=M._bitshift64Lshr,qb=c._bitshift64Shl=M._bitshift64Shl,Ab=c._emscripten_bind_LanguageGuess_getLanguageCode_0=M._emscripten_bind_LanguageGuess_getLanguageCode_0;c.___cxa_is_pointer_type=M.___cxa_is_pointer_type;
var Bb=c._emscripten_bind_LanguageGuess_getPercent_0=M._emscripten_bind_LanguageGuess_getPercent_0,Cb=c._emscripten_bind_VoidPtr___destroy___0=M._emscripten_bind_VoidPtr___destroy___0,nb=c._memset=M._memset,sb=c._memcpy=M._memcpy,Db=c._emscripten_bind_LanguageInfo_getIsReliable_0=M._emscripten_bind_LanguageInfo_getIsReliable_0,gb=c._i64Subtract=M._i64Subtract,Eb=c._emscripten_bind_LanguageInfo___destroy___0=M._emscripten_bind_LanguageInfo___destroy___0,fb=c._i64Add=M._i64Add,Fb=c._emscripten_bind_LanguageInfo_get_languages_1=
M._emscripten_bind_LanguageInfo_get_languages_1,Gb=c._emscripten_bind_Language_getLanguageCode_0=M._emscripten_bind_Language_getLanguageCode_0,Hb=c._emscripten_bind_LanguageGuess___destroy___0=M._emscripten_bind_LanguageGuess___destroy___0,Ib=c._emscripten_bind_Language___destroy___0=M._emscripten_bind_Language___destroy___0;c.___cxa_can_catch=M.___cxa_can_catch;var Ga=c._free=M._free;c.runPostSets=M.runPostSets;
var Jb=c._emscripten_bind_LanguageInfo_detectLanguage_5=M._emscripten_bind_LanguageInfo_detectLanguage_5,wb=c._memmove=M._memmove,Kb=c._emscripten_bind_LanguageInfo_detectLanguage_2=M._emscripten_bind_LanguageInfo_detectLanguage_2,D=c._malloc=M._malloc,Oa=c._emscripten_replace_memory=M._emscripten_replace_memory;c.dynCall_iiii=M.dynCall_iiii;c.dynCall_viiiii=M.dynCall_viiiii;c.dynCall_vi=M.dynCall_vi;c.dynCall_ii=M.dynCall_ii;c.dynCall_v=M.dynCall_v;c.dynCall_viiiiii=M.dynCall_viiiiii;
c.dynCall_viiii=M.dynCall_viiii;t.n=M.stackAlloc;t.w=M.stackSave;t.o=M.stackRestore;t.ba=M.establishStackSpace;t.V=M.setTempRet0;t.R=M.getTempRet0;
if(K)if("function"===typeof c.locateFile?K=c.locateFile(K):c.memoryInitializerPrefixURL&&(K=c.memoryInitializerPrefixURL+K),m||ca){var Lb=c.readBinary(K);E.set(Lb,t.C)}else{var Nb=function(){c.readAsync(K,Mb,function(){throw"could not load memory initializer "+K;})};bb();var Mb=function(a){a.byteLength&&(a=new Uint8Array(a));E.set(a,t.C);c.memoryInitializerRequest&&delete c.memoryInitializerRequest.response;cb()};if(c.memoryInitializerRequest){var Ob=function(){var a=c.memoryInitializerRequest;200!==
a.status&&0!==a.status?(console.warn("a problem seems to have happened with Module.memoryInitializerRequest, status: "+a.status+", retrying "+K),Nb()):Mb(a.response)};c.memoryInitializerRequest.response?setTimeout(Ob,0):c.memoryInitializerRequest.addEventListener("load",Ob)}else Nb()}function n(a){this.name="ExitStatus";this.message="Program terminated with exit("+a+")";this.status=a}n.prototype=Error();n.prototype.constructor=n;var Pb=null,ab=function Qb(){c.calledRun||Rb();c.calledRun||(ab=Qb)};
c.callMain=c.Z=function(a){function b(){for(var a=0;3>a;a++)e.push(0)}a=a||[];za||(za=!0,H(Ta));var d=a.length+1,e=[xa(Za(c.thisProgram),"i8",0)];b();for(var f=0;f<d-1;f+=1)e.push(xa(Za(a[f]),"i8",0)),b();e.push(0);e=xa(e,"i32",0);try{var l=c._main(d,e,0);Sb(l,!0)}catch(h){if(!(h instanceof n))if("SimulateInfiniteLoop"==h)c.noExitRuntime=!0;else throw h&&"object"===typeof h&&h.stack&&c.u("exception thrown: "+[h,h.stack]),h;}finally{}};
function Rb(a){function b(){if(!c.calledRun&&(c.calledRun=!0,!ia)){za||(za=!0,H(Ta));H(Ua);if(c.onRuntimeInitialized)c.onRuntimeInitialized();c._main&&Tb&&c.callMain(a);if(c.postRun)for("function"==typeof c.postRun&&(c.postRun=[c.postRun]);c.postRun.length;)Ya(c.postRun.shift());H(Va)}}a=a||c.arguments;null===Pb&&(Pb=Date.now());if(!(0<J)){if(c.preRun)for("function"==typeof c.preRun&&(c.preRun=[c.preRun]);c.preRun.length;)Wa(c.preRun.shift());H(Sa);0<J||c.calledRun||(c.setStatus?(c.setStatus("Running..."),
setTimeout(function(){setTimeout(function(){c.setStatus("")},1);b()},1)):b())}}c.run=c.run=Rb;function Sb(a,b){if(!b||!c.noExitRuntime){if(!c.noExitRuntime&&(ia=!0,p=void 0,H(I),c.onExit))c.onExit(a);m?process.exit(a):ca&&"function"===typeof quit&&quit(a);throw new n(a);}}c.exit=c.exit=Sb;var Ub=[];
function y(a){void 0!==a?(c.print(a),c.u(a),a=JSON.stringify(a)):a="";ia=!0;var b="abort("+a+") at "+Ea()+"\nIf this abort() is unexpected, build with -s ASSERTIONS=1 which can give more information.";Ub&&Ub.forEach(function(d){b=d(b,a)});throw b;}c.abort=c.abort=y;if(c.preInit)for("function"==typeof c.preInit&&(c.preInit=[c.preInit]);0<c.preInit.length;)c.preInit.pop()();var Tb=!1;c.noInitialRun&&(Tb=!1);c.noExitRuntime=!0;Rb();function R(){}R.prototype=Object.create(R.prototype);
R.prototype.constructor=R;R.prototype.c=R;R.e={};c.WrapperObject=R;function Vb(a){return(a||R).e}c.getCache=Vb;function S(a,b){var d=Vb(b),e=d[a];if(e)return e;e=Object.create((b||R).prototype);e.a=a;return d[a]=e}c.wrapPointer=S;c.castObject=function(a,b){return S(a.a,b)};c.NULL=S(0);c.destroy=function(a){if(!a.__destroy__)throw"Error: Cannot destroy object. (Did you create it yourself?)";a.__destroy__();delete Vb(a.c)[a.a]};c.compare=function(a,b){return a.a===b.a};c.getPointer=function(a){return a.a};
c.getClass=function(a){return a.c};
var T={buffer:0,size:0,j:0,p:[],i:0,t:function(){if(this.i){for(var a=0;a<this.p.length;a++)c._free(this.p[a]);this.p.length=0;c._free(this.buffer);this.buffer=0;this.size+=this.i;this.i=0}this.buffer||(this.size+=128,this.buffer=c._malloc(this.size),assert(this.buffer));this.j=0},f:function(a,b){assert(this.buffer);var d=b.BYTES_PER_ELEMENT,e=a.length*d,e=e+7&-8,f;this.j+e>=this.size?(assert(0<e),this.i+=e,f=c._malloc(e),this.p.push(f)):(f=this.buffer+this.j,this.j+=e);e=f;switch(d){case 2:e>>=1;
break;case 4:e>>=2;break;case 8:e>>=3}for(d=0;d<a.length;d++)b[e+d]=a[d];return f}};function Wb(a){return"string"===typeof a?T.f(Za(a),B):a}function U(){throw"cannot construct a Language, no constructor in IDL";}U.prototype=Object.create(R.prototype);U.prototype.constructor=U;U.prototype.c=U;U.e={};c.Language=U;U.prototype.getLanguageCode=U.prototype.m=function(){return z(Gb(this.a))};U.prototype.__destroy__=function(){Ib(this.a)};
function V(){throw"cannot construct a LanguageGuess, no constructor in IDL";}V.prototype=Object.create(U.prototype);V.prototype.constructor=V;V.prototype.c=V;V.e={};c.LanguageGuess=V;V.prototype.getPercent=V.prototype.P=function(){return Bb(this.a)};V.prototype.getLanguageCode=V.prototype.m=function(){return z(Ab(this.a))};V.prototype.__destroy__=function(){Hb(this.a)};function W(){throw"cannot construct a LanguageInfo, no constructor in IDL";}W.prototype=Object.create(U.prototype);
W.prototype.constructor=W;W.prototype.c=W;W.e={};c.LanguageInfo=W;
W.prototype.detectLanguage=W.prototype.b=function(a,b,d,e,f){var l=this.a;T.t();a&&"object"===typeof a?a=a.a:a=Wb(a);b&&"object"===typeof b&&(b=b.a);d&&"object"===typeof d?d=d.a:d=Wb(d);e&&"object"===typeof e&&(e=e.a);f&&"object"===typeof f?f=f.a:f=Wb(f);return void 0===d?S(Kb(l,a,b),W):void 0===e?S(_emscripten_bind_LanguageInfo_detectLanguage_3(l,a,b,d),W):void 0===f?S(_emscripten_bind_LanguageInfo_detectLanguage_4(l,a,b,d,e),W):S(Jb(l,a,b,d,e,f),W)};W.prototype.getIsReliable=W.prototype.N=function(){return!!Db(this.a)};
W.prototype.getLanguageCode=W.prototype.m=function(){return z(zb(this.a))};W.prototype.get_languages=W.prototype.S=function(a){var b=this.a;a&&"object"===typeof a&&(a=a.a);return S(Fb(b,a),V)};W.prototype.__destroy__=function(){Eb(this.a)};function Z(){throw"cannot construct a VoidPtr, no constructor in IDL";}Z.prototype=Object.create(R.prototype);Z.prototype.constructor=Z;Z.prototype.c=Z;Z.e={};c.VoidPtr=Z;Z.prototype.__destroy__=function(){Cb(this.a)};(function(){function a(){}c.calledRun||Xa(a)})();
W.g=W.prototype.b;T.f=T.f.bind(T);T.t=T.t.bind(T);
for(var Xb={ISO_8859_1:0,ISO_8859_2:1,ISO_8859_3:2,ISO_8859_4:3,ISO_8859_5:4,ISO_8859_6:5,ISO_8859_7:6,ISO_8859_8:7,ISO_8859_9:8,ISO_8859_10:9,JAPANESE_EUC_JP:10,EUC_JP:10,JAPANESE_SHIFT_JIS:11,SHIFT_JIS:11,JAPANESE_JIS:12,JIS:12,CHINESE_BIG5:13,BIG5:13,CHINESE_GB:14,CHINESE_EUC_CN:15,EUC_CN:15,KOREAN_EUC_KR:16,EUC_KR:16,UNICODE_UNUSED:17,CHINESE_EUC_DEC:18,EUC_DEC:18,CHINESE_CNS:19,CNS:19,CHINESE_BIG5_CP950:20,BIG5_CP950:20,JAPANESE_CP932:21,CP932:21,UTF8:22,UNKNOWN_ENCODING:23,ASCII_7BIT:24,RUSSIAN_KOI8_R:25,
KOI8_R:25,RUSSIAN_CP1251:26,CP1251:26,MSFT_CP1252:27,CP1252:27,RUSSIAN_KOI8_RU:28,KOI8_RU:28,MSFT_CP1250:29,CP1250:29,ISO_8859_15:30,MSFT_CP1254:31,CP1254:31,MSFT_CP1257:32,CP1257:32,ISO_8859_11:33,MSFT_CP874:34,CP874:34,MSFT_CP1256:35,CP1256:35,MSFT_CP1255:36,CP1255:36,ISO_8859_8_I:37,HEBREW_VISUAL:38,CZECH_CP852:39,CP852:39,CZECH_CSN_369103:40,CSN_369103:40,MSFT_CP1253:41,CP1253:41,RUSSIAN_CP866:42,CP866:42,ISO_8859_13:43,ISO_2022_KR:44,GBK:45,GB18030:46,BIG5_HKSCS:47,ISO_2022_CN:48,TSCII:49,TAMIL_MONO:50,
TAMIL_BI:51,JAGRAN:52,MACINTOSH_ROMAN:53,UTF7:54,BHASKAR:55,HTCHANAKYA:56,UTF16BE:57,UTF16LE:58,UTF32BE:59,UTF32LE:60,BINARYENC:61,HZ_GB_2312:62,UTF8UTF8:63,TAM_ELANGO:64,TAM_LTTMBARANI:65,TAM_SHREE:66,TAM_TBOOMIS:67,TAM_TMNEWS:68,TAM_WEBTAMIL:69,KDDI_SHIFT_JIS:70,DOCOMO_SHIFT_JIS:71,SOFTBANK_SHIFT_JIS:72,KDDI_ISO_2022_JP:73,ISO_2022_JP:73,SOFTBANK_ISO_2022_JP:74},Yb=function(a){if(a.J)return a.J();if(!(a instanceof Array)&&"string"!=typeof a)throw Error();var b=0;return{next:function(){return b==
a.length?{done:!0}:{done:!1,value:a[b++]}}}}(Object.keys(Xb)),Zb=Yb.next();!Zb.done;Zb=Yb.next()){var $b=Zb.value;$b.includes("_")&&(Xb[$b.replace(/_/g,"")]=Xb[$b])}
Xa(function(){onmessage=function(a){a=a.data;var b=void 0;if(void 0==a.tld&&void 0==a.encoding&&void 0==a.language)b=W.g(a.text,!a.isHTML);else var d=String(a.encoding).toUpperCase().replace(/[_-]/g,""),e=void 0,e=Xb.hasOwnProperty(d)?Xb[d]:Xb.UNKNOWN_ENCODING,b=W.g(a.text,!a.isHTML,a.tld||null,e,a.language||null);postMessage({language:b.m(),confident:b.N(),languages:Array(3).fill(0).map(function(a,d){var e=b.S(d);return{languageCode:e.m(),percent:e.P()}}).filter(function(a){return"un"!=a.languageCode||
0<a.percent})});c.destroy(b)};postMessage("ready")});

PK
!<`ɖ/�/�%modules/translation/cld-worker.js.mem�\��\�0�\���\��@�\��\ֽ�\�0�\�X������������/"�(��2"�(���!e(���"�(��@!-(���!a(���!I(�[!6(�� (��!a(�"o(�5"�(��!J(��(!%(�8"�(�d!9(�F!/(�(!%(%�R!3(#�,�;"�(4��!N(<�� (B��!Y(H��!Y(O�
!(Y��!g(a�^!7(i��!k(r��"�({�� (����E(����E(��"p(��J"�(��
!(��� (D�� (
�� (��!j(�� (�>"�(i��� (�� �a!8(��!(�� (	�� 	(��!F(��A"�(�� ('�� (,�� (5�� (<��!C(D�!(J�!(S��"�(Y��!K(b�� (i�� (o�D"�({��!U(��U!4(��J"�(��G"�(���"�(��� (��"!#(@�"!#(@�� (�� (F���"�(�+!&((��g!:(�"q(���(�(	�(�(!(!(#� (+� (2� (9� (BI!0(Kj!;(SS"�(\"r(c}"�(i�!h(oV"�({�"�)�� 	(��!_(��!X(��!b(�.!'(�� (��m(�"s(�� (��!f(�%!$(�Y"�(�1!((&�4!)(�|!A(�"�()"�(y!@(#��"�(+5(8�!`(BN�(T�!g(\\"�(b�"�)j:!+(q� 
(P�x� 
(P�� 
(P�� 
(P��!P(
��!P(
�� 
(P��!P(
��"�(�L!1(�"n(�_"�(��!M(�"u(��"�(��!M(�� (�� (�� (� (	� (m!<("v($�!d(3� (<b"�(B� 
(Je"�(Qh"�(Wk"�(`�"�(�fp!=(s!({�"�(��!V(�� (�� (�"w(��!O(�v!?(�v!?(��!c(��!O(�n"�(��m(��!D(��v!?(��!](�� (NN�(��!G(� (s!>(� (�!l(! (%"x(+C!.(1 "y(7�!i(��=��E(F=!,(MX!5(R�!i(��Z�!L(c#"z(iq"�(pt"�(w "y(}!!(�!!(��!W(��!^(�!"(�O!2(��!H(��"�(�!B(�!B(�w"�(�7!*(�,"�(��!S(T���![(�&"{(���E(�� (� (
��E(��E(��E(&��E(,��E(2� (9z"�(@�!T(S�2"2"�(/"/"�(�!�!e(�"�"�(E@!-(�!HI(a([![!6(N[!6(R�!a(V[!6(""o(Z� (]� (5"5"�(�!�!J(`�!J(8"d�(N(F!F!/(jF!/(
!
!(R!R!3(#�;";"�(n��E(r1!((&�(!(!%(�!�!i(���!uY( �!�!N(^!^!7({^!7(�((�� (�!�!k(��!_(��E""p(�
!(�"�"�(� � (D�^!7(7!7!*(�7!*(�� (D� � (
�� (
�� � (�� (��!j(�� (
�� (�!�!j(>">"�(i�"!(�[!6(� � (� � �� a!a!8(�O!2(� � (�� (�!(!!(d!d!9(�!�!M(��!M(� � (�! (A"A"�(�!�!F(�� � (�� (�� (�!�!C(!�((��=((�!(�� (�� (p!p!=(u�!K(��!K(�� (!!(�!�!U(� (U!U!4(�"�"�(G"G"�(J"�(�"�"�(� (� � (
� ("!"!#(@���E(
!
!(J"J"�(� � (� (�!�!a(g!g!:(� (F�+!+!&((�+!&((�""q(�"�"�(M"�(�(+!&((� +!&((�$�(�(� � (F�� � ((� (P"�(�(,� (� � (/� (2� (6I!0(I!I!0(�!�!K(9"v(<�!X(?�!h(}"}"�(""r(D"D"�(�!�!h(j!j!;(� � 	(B� 	(F� 	(S"S"�(I� 	(�!�!_(�!�!X(M"r(.!.!'(P�!b(�!�!f(�"�"�(T� (""s(�!�!b(� � (X� (� � (�"�"�(Y"Y"�()")"�(%!%!$(4!4!)(�!�!`(55(\�!`(y!y!@(#�1!1!((&�|!|!A(`� (�!cg(((\"\"�(i� 
(P�:!:!+(� � (�!�!P(
�� � 
(P��"�"�)�"�"�(�"�"�(L!L!1(_"_"�(""n(m!l<(u(r�!M(v! (yO!2(� � (|m!<(�� (�� (""u(� � (�� (�� (""v(�!�!d(b"b"�(� � (�!(� � 
(�� 
(V"V"�(k"k"�(�"�"�(��!�!c(�� (h"h"�(�!�O(?(�!�!D(�v!v!?(�v!?(e"e"�(""w(�!�!](�� (@!@!-(!!(�!(�!(�!(n"n"�(�!�!V(�!�!G(� � (�� (s!s!>(�� (��!l(�!�!l(C!C!.(=!=!,(""x(X!X!5(�!�L(i(�"x(�!�!W(!! (�"�"�)t"t"�(#"#"z(!!!(q"q"�( " "y(�!��(E(��"�(�!"(�!�!^(!!"(O!O!2(�!�!H(�^!7(�^!7(�"�"�(!!B(�!B(�!B(w"w"�(,","�(�!�!S(T���!S(T��!�![(&"&"{(z"z"�(� � (���E(�!�!T(S���7�6�!uM�E-�!a��[!Z]���!J8"N �%F!
!R!6;"�r(!(&��!��!�(i��/^!{7 #&�"),/7!!�D�� 2�
�58>"6;�"�6��� !I�� A"��!F�� !p!uK>!��!xEA
! N�J"�� +!&(�"DG�J6MM� F�� P6/<X?h�!S	F	V6Mr.!b�!6Y\O_V� bf� e6hknq� �Y"%!$4!tg�!`5Ewy!6|!Azj`�!(&�}\"�:!�U� � 
P�+�"�n��_"6m!�v�v y2� �"6� �U�6�� � ��� 
V"�k"6���!6��!?�!D�e""n"6�!
� �6�"X!5�x�!t"6�!!�!E�>��"�!�!���!H�'�"�B��6z"e�
e[PDF
=J
@(&M#M#!2
TS�?�i��@V<ClCK�����L
����
��         	
 
                   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ �   � &   ! �0 `9 R }      "   �"!a: S ~x���������������������������������������������������������������������������������������������������&����������������������! ����������	���>�#�*�0�7�<�A�G<J�Q�T�[�^Rd�k�q�x�~������������3 ���"�X���`����������������������
x
�
�
�
�
�%
�,
5!4
�:
&>
'"B
 "F
'K
�Q
H"W
�^
�c
 i
�n
�u
" z
)"~

�
��
��
��
��
��
c&�
E"�
��
�!�
*"�
��
�!�
  �
�!�
��
��
f&�
��
��
�
��
� "   "�*a"0�4�8�=� B"H�M"T�[�b�iD o�ue"x>{�!��!�e&�& ���������!�"�+"�����"������!���)#����!�#� d"
#"�"�% 9  � <#�( .�4�;"A�D"J�O :!`"U"X�\	"b�"g�n�q�x�~S���> ������""("����������"�����"�0 ��"�����������2 �""�"�!"*# �&�!+Y2	#8 >!C�G#N�R V: ] c iap�"u�z�~����<"�`&��"��"�"��"��������"�����4"�����	 �������"!��!���!����"�*�/!6�9�@�D�I�N
 R W_flt{����������������")1;DLR[cku{����������������!(4<DQYcks~���������������#*15=@GNV`iry��������������� &,29=AEIOU_dks{���������������")/5;@LQVgpx|��������������	
!%)-159=AEIMQUY]aeimquy}��������������������������������	
!%)-159=AEIMQUY]aeimquy}��������������������������������	
!%)-159=AEIMQUY]aeimquy}��������������������������������	
!%)-159=AEIMQUY]aeimquy}��������������������������������	
!%)-159=AEIMQUY]aeimquy}��������������������������������	
.:CMZcks~�������������	 *4?HS^t|�������������(3>HR\fqz�����������%2=FOXahs~��������� , 9 B J S ] f y � � � � � � � � � � � � � � � � � � � � � � � � � � � � !!!
!
!!!!!!!"!%!(!+!.!1!4!7!:!=!@!C!F!I!L!O!R!U!X![!^!a!d!g!j!m!p!s!v!y!|!!�!�!�!�!�!�!�!�!�!�!�!�!�!�!�����!�!�!�!�!�!�!�!�!���!�!�!�!�!�!�!�!�!�!�!�!�!�!�!�!�!""""""��"""" "#"&"��������)","/"2"5"8";">"A"D"G"J"M"P"S"V"Y"\"_"b"e"h"k"n"q"t"w"z"}"�"�"�"�"�"�"�"�"�"�"�"�"�"�"�"�"�"�"�"�"�"�"�"�"�"�"�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������"�"�"�"�"�"�"#
###"#*#2#:#B#J#R#Z#b#j#r#z#�#�#�#�#�#�#�#�#�#�#�#�#�#�#�#�#$
$$$"$*$2$:$B$J$R$Z$b$j$r$z$�$�$�$�$�$�$�$�$�$�$�$�$�$�$�$�$%
%%%"%*%2%:%B%J%R%Z%b%j%r%z%�%�%�%�%�%�%�%�%�%�%�%�%�%�%�%�%&
&&&"&>(C(H(M(R(W(\(a(f(k(p(u(z((�(�(�(�(�(*(�(�(�(�(/(�(�(�(�(�(�(�(�����(���(�(�(�(�(�(�(�()))))))$))).)3)8)=)B)G)L)Q)V)[)`)e)j)o)t)y)~)�)�)�)�)�)�)4(�)�)�)�)�)9(�)�)�)�)�)�)�)�)�)�)�)�)�)�)**
***
a6lj#%<4n.,;)O5bigKIk�h`"$%&'( *+,m./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcde@%����/�\���8��C���P)3/�BaB-BBBB-�aa�a����---�--�---�------�a�aa�a�a�a�a�aaa�a�aa��a�a�aa�a���a�a��aa�a��a�a���a�a�a�aa��a���-�-�-�-�-U-U-U-�-�-U-U-	-��UaUUa	aUU��U		a��aUa�a	a	aUa�a�a��aUaUaU	a	a	Ua�a���a	UUa�	U	-	-		-		-	-----K�"�6+3`��E�;��)�s���@5����&��\���L�@M�����w�`%�|�@@�	�K���
�#��D�����r�	�7�
��@A�
�,� =��(�
0��p"�
p~�
�<��X��3���P���-�
�)��@�pO���P�0n��'�
P�����p��;��7�
 f����po��~� 8�9���`E�`*�`X�A�
��@L��<��8��n��v�@�`R����\����p��9��]�P �@G��3��-��I�@q�p����
�?� C� /�
���
�f��L��^������	@��
�x��b���P:��^�@��0f��� -�����p#�
�a�0����H�0Z��
`y���F�
 �� ���
�K�0j�
�9�����B����p\�
�%� k�~�	�G��`��&����G����
`��@��B�-��|� �@G�0������`%�P#��E��d�
��M�
����;�`��,�	�]��v����g�@v��g�`n�
�� y�	0��	���W�`8��\��B���P��f�
�?�D���C�`'�
��@�X��=�
 �@��(�И�Њ�@:� &�	�P���E�
���
�� 8�@!�
��?�к�PJ�P
�PE�pD������ ����j�@[��T� �`C� q��D��?�
��`�	P�r��09�
P��S������pG�
P�� ,�
p}�������`;��
��`3�e��w����pn����pI��J� �
b�$� H�І��Q�
�$�@z�pd���p\�������f�?�
�^��E��$�
 .�`��@!��D��g��8��c��D�@o��>�
 }������� 9� ��H� 8�@6� �pE�	��@�p2��A���=�`f���0b�p���5�@�0��e�0��B�G�PG��b��9�	�y������C�@�`�
���@0���`��l�@?��%���
��
��|����5�`j��E�
@��
`n��U�@`�PQ��7��R��
pm�
�M�_��"�P@��<�
�[�0e�P]��+��k�2�
�+����@��v��p�Px�p� ��
0�`~��C�����
��B�k��.��l�p2�}��h�pp�0�����X� '���`I��)��7� _����
P=��A� �
�@�� �*��t��#� F��D��`7�@����)��k��A��c�г�p�������x����	�t��h�
���B�0^�0f�
�B�P��0H�p,�p�02�`��t��^� V���@�����E���@�n����ж�
P��c�x�����W�`�Ј��^�Pf��5�`������C��<��W��W��W����Y����@l�Pt�G����_�@��P����P�����0!�	@�� �� {�
C�P��	�)�
P�@s�0_� ?��
���p;��(���PA�
P�-���`R��#�pS�Ј� ��q� ��`���02��S��A��>�	P�o������P����#�@U��;� D�
� ���F��:�`W�m��E��8��R�P���j�	�8�	��P�X��f� ���7� ~�
�E����:��\�
 Z��*�@��
>�@�0q�P8�p���!��#�
u�
��p���p}��
�PF�/�p����	�[��*�@#�0/��[� �
00�`u��D�
`A�����@���7��u�L���Pw�P��@�0:��R��0�
0&��!���
�~����p(�{�P�pI���	0x��:��6��V��;���`i� ^����
���`5����`2��	���`q����$�
p������ 9��{�@E��:�p=�p ��!�P�����
��� D��?��^�0�	@�@?�0��
@��������6�05�@��
���`|�P���f��4����`�P*�� #���p%��$��:�����l�P��
�!�\����0��`e��1����%�@]��\�;��b���
����P��
pe�
����8�`;����
0�`��v�@%� ��6�`�	p,����P��<� ��@��`���`��p��P��
?�Ђ�`<�
P.�`�0d��O�H�p����`�PT�$��z�P_�	�z����?��"�
05�����6��L��M�P`�@���}�	����3��`+�
��
�@�@E�@^�0M���	��P�`O���@5�~��&�
�%��������	������J�PF����#�c��"�6�p����!�Pd�@����P���'��~��W��}�
�I��S�0&�9�	�T����\�
p'��� 7�
�%�@����%�
@��pn��Q���@s��/��'����pY��7��h�@S��B�|�@��^�L���p%�pR�P�p6� $�����`D���`��p�p��
�_��&�`�
�<�pn��b�p#��{���p��6�J�`R�$����C�
����n�0(��?�0��0v��)�.�
(��U�	�� H�P6�
�G�К�
@.�pv�����~�@��|�@F�+����b��:�0��D��^��Y���� 9�����Z���p��m��G�p���������	�������G���p��w� A�`d�
pE�	q�01����
G��q�
p�0%��Q� ��0��m� g�����?�
0B�@3��a�
`W��}�`����P��P��N�`��
�(��*��f��n� I����w���������_����v�p��0D��`��p$�F��c�0Z�`>�
�D��$���@\�@��`��L�
�1���*��Z����@�&������
�0�
07��3��C�ps�Pg�01��������`b��>���`�`��
�#�����I�
�	P@����p"�`f��f��)�pG�
�$�`Z�
F�
�����p����	���p��s�
 H�0t�0h� .�&�0�@��y��;��&� ��D��e�Pi�p[��<��#��K�� !���v� ���V�`���0�@��D����
���	�f� )�p+�`��:�0B�o����Є�0�
�U��c���
0���l�@���@��	�pj����d�
`�pA�
�c��W�'�@��U��`�X�
�_��5��5��f� +��e�`]���
��$�0M��,�6�P�����4�	`h�������`8�`U��6�p%��T������p�����a�p)�`��`����V�0����X��R��^��D��n����:����o�p:�`���Q�
���	K�
#�@�	���������
@�6�	�C�@��r��G��$�������*��}�п�`e�@a����
P"� p�@n��%�@|�PA�PD�
�`��P2��O��G�	`��p,���@�P��v�
��X��/�`G�0�pF����W�
@A��,�@�@�`o��A��O�@M��F��'�pQ�0�p� z�Pf�$��+�0%�
p��S�`C����`]��E��\�`��PK��8��� e�@}�@0�@�p���x�P9�
� ����$�p�PJ�01�`3�PJ�	���f��<�`��`#�@0�
�s�@T��C���Pd�������7�
`��!��� �������p���6�2�0v�0<�
��p�� t���0@��F��^�`,�����@�O�2�0<�p��@��`,� �
���c����P=�P��p(�@����n�P\��S���Ps�
��pW��
�\�v��V�	�l�P��0l�@(��d��r��/�0�pI��?��6�
PK�@>�����y��e��c�PO��G�����:�Py�
�
�P���J������
�$�������P�p���w��w��v� =��i��&���	*��b���f��h�Ps�P�@%��
� G���E�PD��7��<�f�	�=�
 ��
�]�`:�`�	 ]�����F�p;�@����8� X�P����PT��� &����X�
`��P��P��v��o��"��}�
P=� Y�
P!�0$���
������>��{�p�����0��0����0��C�
P��
H���p<��B�
�`��K����@M�
�}���`���5���#�����`�����
p��u�`3�L�&��/��/��x��f��Q�
�!�0�C��*�@t�p~��V�`<�p��`���H�`s��,�px�4�
0%� Q�0t� K�P�.����0)�0G���Y��E�
�`�����H�`(�������B�p� *�
 �@f������� ���C��y��.� �����+��)�`�PM�� � 
�Б��E���0Q�
�v��0���pP�
`�
���\�`�P!���01�@�� p�0y�pH�p��p,��7��6�
`F�
P���)�0�����|����� g�?��������pM�`�� c�Њ�8��X��-�`u�0P��08�u��K�p�:�
W�L�
������� V�� �
0)�
 ��0f��+��F���`%�p$��c�`"� d��6����H��c�P3�`���� $�	�X��/� �P���^��<� ������j�ps�0/��� D��,�@O�+��7��7�6�
�]�`Y��'�P[�pv��_�`*�PF��r����<� Z����� \��]����0_��8�
�D�]� 2�@�0������s��,�
P_�`-�P���x�P��H������� ��Z�p}� w�
�>�
P%�0D��v�pp�B�
�$��N��X�@)�
��P	��R��-�p!�@:��1�@P��A���  � M��%�@p�PF�
��)���P�^����=�V��f�`&�`�� �P��p6��|�0\��E�
P��0|��^�����9�0��
�<�
�f�`A��0"�
�� ��
����E�
�K� _�pk��j�0��2�@O��9� 
��/�p��
��p���W���� D�p��"�`���e�
�o��������`O��I�
0��0Y����$�`K�������:��[�	�B�����m���	@v��C��$��H�
 =������
� �
p������
����"�pi�5��_���0��
?���S�����B� !��"���N�����+�
`�`f��H��&��������m�@U�Px��`���P�������P� ���p2����0��@g��^����������c��>�0$� ����p��`=�`q�
�'���%�`3� K�`S��%�
�^�������	���Z��� 
��V��`n�
`�p��E�
�2��#�pA����+��q���9�=��`�����c�
9���p��@<��v����
��)�
�p.�P�0�`3�
@ �
08� T�@~�	�R��|��p����C�
�	�Р��g��{��j������л�P�� L��E�?�@g�� F�0b�P�����U������ <�`f��P��� F�`9�	�P���<��}��a�
 A��S��B�0!��_�	0#����
���$�B�P��0$�&�
�
@���Y����d�P�@����@k��p�r��Z�Pv�@��n�0\�@h��q�V��^� S�0��6��G�����v�@O��d�
`����&��5���
�4�P"�
�M�P-��E��
�� ��s�����P��1���� ��4��}�
�b������[��.���� D� ��� �
@�� �08�C�@�<��_�pc��(�
������`��{�@F��,���0?��4�
F��	���S��?�`����	V� f��r�
�6�
P`�`���
`��
�<�
P�p��
 ���0/����?�`���z�
�R����	�-�p���D�0F�Z�0W�����
�
�$����P#� g�
�Z�)��	�@F�0D�`e� ��
�%� m�#����d�@��
��@v�	�Y��'���0
�����
@��PD�
��	@	��)�`5��;��m�ph�<�@j�0��k�	@}��*���X� ���@\�
P/�P�P0�
�g�
P�
�q�Ph�P��`"�`=���-�p �� ��'�	�	�0B�p��`&�
���@E��V��n�p������.��j�
P��/�
 >� ����&��a�	���
`�������
�\������`�p)�@��U��Q��)�
���(�
�D�@�����e�pC����g�@N��Y����+�p� `�p~����`o�����`��o�
p<��b�pF������.��$������p]�@#�
P��g��k�`�8����Y����p!�����Z�
p��P{�pU�
P6��`�	 �
Ѐ�p`�p%��+�`�g�`F�
@��`��@�����	p�	�p	��B�1���<�`#������;�@%�
Po�PY��p�
�^� �z��d�@A��'��!��m���P��P���E�@E��3��F��E�
pd�0���%����P`��d��E�@Q�������n�-�
�|�0���*�`8� .�P����@���P�@#�	 ��t��}�0|�5��"�Pv�
`��	�A�	�%����@����n�p
�Ph�
�7�
�X�PA��o�P~��:�PU�!P�
�*�
�t�i�@j�`��p;�P��	�4��u��E��� D��k�G�`��0X���0��'�	�q�
����:�����3��c��f��Y�
�!�Ph��>����?�����0i�	�-����P�� i�
p�p���Z��*�@.�������
n�@
��\�@O��B����0��� C��I����0x�pV�0K�
��pd����0� 8�PA�
�]���
�\�����6��E��T�pz��b�
p����U��E�@+�p��A����
@�;�
0��0������`��J� p5�
�������-��(��H��?�P*�@>��e�P��5�@�
�!��a�	 W�	����)�
�\�P ��$�`��@u����u�p&�N�
Pc�@%���0�����p �@d��U�`&���`���X�^�0k��
��F�
 -�`R��H��<����0��T�0���(�
����%��B��j�2��7�0e��5�����	 m��8���@y����0m� 8�	 F��/��	�
0/��E��a� �P������a��f�@A��M���f��'�0��H�І�0}�0r��(�	@�pS������w�P��
@��*��e��y�@`�
�b�P%�0Y� ��@���{�\�����PF�t��������j�p)�@1�	 }�@��d�
`I�p���`F�r�pw���@n��J��$�P���1��U����
�
�&�P[��p�@��`J������0.����h���
�_�@S����@$���
�d�pw�!�>�
���*��;��X�	�-��d�`X��f���`4�*��_��W��
�
0N��Y�p��
�@�pQ���p������������P���P��o��y� ��0��� N��m��V��0�
�q���`8�0h�
�J�`^����`(� }��u��
�0���*�P?��
��h��G�BB$$B7-
$-7-H�X�����5)3��܋܋7p����cP3������.|� � �Cd۽	�#
���ce��R
���CdI��c� � ��>��'��c�ce�Cd�o��Cd��>�Q$� ��c���c �o!�g6"�ce#��$��/%��/&�Cd	^d'� (�+)�(u*��c+�j,��c-�N.��c/R"0�i1^d2�í3��k4$n5�(u6��+7� 8�(u9�7�$���,!:��/;�$<�,!=��;��k%��/>��z?��<�,!.��c@�CdA� B��kC�iD�ceE��'F�,!G�iH�I���1D5�(uJ��cK�+gL��'M�CdNt�O��&��cP�ce$��/Q��'R��/S�ceT��U� V���W��cX�CdY�)<�,!Z��c[��c\� ]��c^ף_f`�t�a1Db�f*�o2�C�c�fd���em~f�)gg��/h�,!i�)gj�k�l�dm�nm~o�i>?��
&p�)g:��/q�
*�?:r��(s��t��F7q1u�
�:��/vت)�(uw�{~���C�����%��/x�f	��ey�)z�ij�?:{�|W�\��/}�`8y�f~�D��RY�D怀�y���S�@"��,!��*���o���8��j���	 51D<�,!�@"��)g�
ی�,!��f�*!���v�J��,!��X��1D1D�@"y�f��Jm���ic�y(��薀�i���z��g�@"{
ۚ�y1D���ݜm~���ic/g���i�Ad����C���8�Ad�Gd�
 ���5/g���i��f��c��{~��c���i���9X֬�f�Udc/gR
 ���6���밀�i��{~��f������{~���i���I�����Gd��;��w����c�����i���������Y���c���i������Ā��ŀ��ƀ�{��c��	��c�����;��c̀�{̀�/�:|�����'�
 �)|�@"Ԁ"��O+=X+����j��؀�{ـ�h��c��ۀ�i�ף��'ހ�ia��ic/g�
 ��c�Ad…�Jm��z� ��c��{����&�逊)��c�������iO��z�b*��'� � ۚ/g�)��c��i���'�� c/g��P���i���{�� ���'��c��c;��'��c��&� b*UdR�&�������c	��{����
Gd-g
f
�c��{9�c���&��{���{�|��f.���4Zd���{����I"� ��'�����{j�6�Bv���I"��h�f�c� =�D� �c!��{"
f#��{$r�%AmI"&�)n'�ʣ(���))�&w��*�zB$�"g+)|,�c-��'.��i/y�0��i1�	o2���3�L�J�4�냀�i5�)n6� 7�cQ��*{�8�c9j:�;�f<�=��i>I"9�-?��i�"g��{@���ANmB�&C\mD�BvE��iF�)nG��'H� I�i�d~��eJ�Bv(�íK��{=�D�L�F�M��{N��i�NmO�iP��{Q-g5
)���R�7S���T�o��
fU��iV\mW�iX���Y��rZ��iw�D�[����Շ���+)|\��I"]
f^
)_Nm`��a���b���cJd�\d�JdI"�x�)�>e���f�fI"g��iz�Cdhwe2
)i��ij�{�8�ݢ�{�k��l��m�XdA� hwen�Cdo��	p�{q�"�dr��i\ms�ft�N�u�{v��#wAmx��y��iz�UT{��|F�}��i~�N�:|�����e���\m���i��Ő��zt�e��i��\m��{��=����2��Kv�Ad�Վ�/�$|��2�C�����2��e����ij��&�������F� ���ݘ�e��o�5���i��Xd�:D�
)��Jd�
٠��:D�8D�ڢ�(��Xd����� ���k	o��j��"n�����8D��g���&��ګ����e�����g��6s���3��磰�y��{�� ���X)��۴�f��{y�fD�X���9Ѷ��&���b��go��S��{����ݺ�y��Q����f*�1Ѿ5u�	 �����:D�5u��e�-nJ�5�ā�YŁ�b�-n��y�5u�eD�X��C��Yvǁ�bȁ�>��d�
oʁX�ˁ�j����5u���ρ��g���ɀ�b��k:|�����0�O �X9�-n� ��i��$�7�؁�b���ځ�kہ���5u�"|ށm#��w����i��k���どc	 ��k�ck�����y݀�k��a�5u�m#ꁩc끩c��y��k��� 큩c�Nd��a��a<��c�������k�����c)���g��l1� ���a��f*��g�8|�ne�\d�Nd����Am���k�Nd�۽�-n�У�5u��-���Zd��c����ze�ze��-��a����aw�/�<�h��-	���5u
�k�Ԥ�ze-n
��>��%�Z$��-5uv1!�ze�
���-�>�Nd�i\d!D �a�i��-����>9�� ��{!���"��-�Hd#��a$�ze%���&�ի'��-(�w�x�)�ze*��a��<�+�ݩ�����d�,�m#���{w
�-\d��݀�k.��-/��k0I"1��a.��-2��a3!D4�ze5\d6��7�Ea8Nd�$�9!Dm�I":����;OvЁ|,L�2�:�Am<&=Nd"�>��?�$�?��@�zeA��-B�i~C��zD�Am8���E��-FI"G��-T��H��iI���Z$��/!DJ:|K��mL��j��bM��-.��-N�b]�zeO�����7�P�e�Q�g�R��-��-��ze�`S�b971TYUI"a��V��W��i�
�X���Y���Z¢��b[���\]v���ێOv]��^��€֣�؁+�b��Jda}_�ל`�'���b��<Da�{w�I"b��i�Zdေ#�)c�b��d�Le�cf��ig"uhI"i��ij	%Ё�?kĭ��$lY+mUm=���n�'o�ap��i��%q�cr��s�	۝��s�>Dj�eO�i~��t�'u��iW�cv�AmX��w�U�x��yAdz�c{�a|��i}�&���i~��ᘀ
 ��݀�'��c��}#���{b����F���_"��c���{Q�R���c��b��_"����c쁻j��c,�}#$n>|���܏�|��Vp���i��c��:}#g���m�>Db��V���m��>D����=(��Jd�~e����f�2ݛ~e���{hD���i���i�����}#�$nw���4~e���'�;���c��&���ݦ��k���'��{��z�Ad��븩����x4~e���'�-t��c�y*���c�g��c���'��Kv��%���c��g��Zmt�e���'�8��c���c�&��+���D���'��c������c�۽���'��av��ٺ��l��k4~e���ܼ���kB��):���c�)oB����'���D��E�a�_"}�&���c‚�{�Em�m~;������{ł{~k���m~ǂ��Ȃ�cɂ_"�Em����~e���c�%|��i��%D���+ς�c��D�)т�'҂�c=�tA��]�����&��e4~eԂ{~��?��j���ׂ%D؂�'�`*�F���b.�{~܂:�݂�'��gނ*�5<u߂���˶��'Ⴂc�Ad�r�����'�@"���y�%D炢c�@"�{~��D����{�g�<u~�Ad� �z�� ؂�c��e� �c�<uv���� Q�w�@"��e�˕���S�Em�%D��Xm�
�og�<u� <��Nj�%D��i����b~�5GB�b~ )�/�-|��ܠ =gEm ��Mg�{~���%D	Ud>��a
Ud�{.D�t�-�i�-��{
�Q �s�=gUd�M"��{~�{k2 ���j��'�M"Ud�?��-:�
`
H�{Ud��%�<u�M"�i� �i!m��%!�J��-"��cb�/#�&A$��'%��'�J�&�-'��{�? (��9W)F�* +�\d, -Ud.@"�l/��'0Ud]��c1DD2��'3��4�{5��c6DD7 ��8�k9 :��';��?<�b~Ȁ�1=��bwDD>��ck�D�?�&:@�-ADDA�g�����0�BDD9�6C�/D$g=�
��=gl�-E��cF��c�:u9��G��aHDD�=gI�k��-J�:uKUd<�LDDM�{N�cO�-P2QDDR�:u�UdS��T�Hm��{U6n��g��:u)�>!V��kWDD:��X��cY�j���'R��%Z��d[�Hm\��k]���^��%_���5�a,DD`�za��+b��kcDDd�e]��c쁥%��a,e�HmJ��'�b%`f�g��%2}h��ci��bjmwJ��>kR�l�km�n�;go��b����d�fp�Cdq�Ear�cs��it�E���gu��vAmw�c�����m�x�CdyDD)�gz�k�?3(�_�{�e|��yЁ��}��~DD�k������)��܂��yh�e��\m��:u�۽v��%�d>�@+�Ad(�8'��"g��ka��lj�:皀6&20h��)n���f���ލ�����y���y�m8��Z�t���<�Hm�\mO��?6��#���?��>!�\m���Z�ˢ���?�?3��.D��ޙ�Xd���"���y���i�\m�)�����[���ੁ!`3(;\m���?���3���)���v�+� ���ip�Cd��Xd���z��e��)n������?XzԤ�����,��?3��z���?9�S��Cd�� �ۅ��X� �?3��!��5����i��Cd���y�9:�Cd�\m���y�?3�Cd���?t�e���i�����-|�\m���y������-h��*� ���-�ˢ��ʁ�����-ڹ\m���-�����dh�ԂF"�&B�R"���"�ae��'�Fn����\mÃ�?�@+�f����-����Fv����b�U ���Ƀ�?ʃ�n����Fv̓�?��΃Xd��c{ۦЃ�-ȀЀ��i҃�?b���Ӄ�i��cՃXdf�փ�-׃ 2�}��k�Fvڃ�i�'6}��iۃ&܃�i�����	�(`��x߃�-��i�:DK��Fv�zkB�k�}�,DoB���k�}��b��'��c���ރw5�>�����kƁ,D�-n��μ��냩�<�샅-�?g5�����i�Ud-~�o%e�F��Hd�,D�o%Q��Ud�����h�-~�	��b���-���-���-n�^9��p����-�i*�-n������<��'�[���Cm��q�\���-n��e��i*��{��ۦ:Db���c�yFv�e(<'%���-nFv	��i
�Cm��k�o
��L��Y����c�c�Cm4��i�c�c�Cm|e�b���{\d���|e���?�Cm��ݫ�L��y�i�|e��,Db��c\d�,D��i��Md��c��� �Cm!��k"|e#\d$���%�R&\d��,D'��iD|eQW9� n(��y)��c*� nŁ�b⁚��&���+��,� nD��&X������-\d.��i/�g0� n����1�Cm2i*�,�"|3|e4� n5-n6�e7\d8�Cm9]v�I+���i*:�Cm;]v<�CmBi*ɓ���Q6�=�e>� n?�Cm@��cA��)�M�B!DC�Cm�!DDI"��)gE�Cm��eF]v,��&G�CmH�\dI��kJ� nK\dL�Cmb�M�M��kN|eO����AmP�$gQ��R�AmSI"T|eU�AmV|e�i>W��X�AmY���Z���[��\�Am]���^f_�Amv�Ԣ`\da]v���bI"cB�d�Ame\d����!D�� n��e5��ݒ�!fˢg��h�F�i�f�����E9���5���j��孂Amk�{���l�Ame��m����&����AmT Qn�����o�Amp��f�jq��de�ʵr�E9���z�W�s�?{�t�!|B����Amu]vv����B�'}w!Dx�Amy�E9z�fK�{!D̀~1|�b}�����{~�{�{�I"����e��f�K���>D�Q҂�f�!D��د�e��b�Gm(	 �I"\�Am�Gm�	 ��{��f��k����i�Gm��)��܌�>D��c�[���Ѐ��Jd(�����iSGm��Am��Am��E9(����c�Gm(��'(��y������E9�Gm��/�����E9(���	 ��k��c��kQ������L��ΚGm��{)�ce�����ݛ��k�>D���c���c��k��v���k�GmlQ9�#K%Gm���c|[������c)��ܥ��c��f���kwSpO$n��{���c��bd�f��>D�����i���Ԯ�c����k������b�,���c���cr��	��a��Q�	 ���k
���z�foҴ�	 ���-�Gm���c�8Ѹ��-�~1���k���c�Gm���-�$n���ɾ��-9�w��դ��c�GmR�c������k�w_„�-y��kO	 Ä�-�Gm[�cń��k���c��hd���'D��>�S���iɄ�-�3D����Yv�Em˄�k���N̄�k�EmQ��΄�{�4�Є�c��z҄�kӄ�{Ԅ�-Մ����zׄ�{���?؄�c
��{ل��ڄ�c!���G��-Z��_���-�F���z݄gs�ބ:ĉ��z��%D߄%D�Em��z��i�ň�%D��+g��{�M"]��y�%D��y���z��-�%D]��y�zo���%DDEm�Em��z��?y>�-����������-Em�M"�-]��y�;�z��{�%D��Վ�{~��%D��&[��-���{�����%D��?���S���-h��ĂM"a�!�Z��!`��%D��y��)������%D��yEm�JmNd{ۦEm�%D\d��y�%D���Jm�ۦEm	�%D
���>���	�z
ϣ��n$�JmtEm�\d��{[�zZd.���a��c�z��-��׀����1Zd�-�z�%D�Jm�Ԣ�����%D�-N����Jm�c~��٢��y�c��%D��������i�k�Jmv�F+ �-!�%D"�'���i#:�$�-%�c��f&�]��y'��(�c)�c����*�++��,�?-��i.Nd��{.���/\d��0F�9�R���{1��2�c3�Jm4f5Nd6��G97��2���8�����9�d:�c;��<��i�Jm�Dv=�n>��?���@��#A�JmBDD��G9f�C�kȀ��')�ۦD��EDDF��iG�Jm���DD ��'H�G9I�-�DDJ�kK�cL�sIM��bNDvO�G9��XP��i؂G9Q��'�HmRDDS��c��HmT��%�HmUDDV�{W�G9X�HmY�-Z�[�c%�(|\�Hm]DDU��b^�(|_)���yqc�	o`G9*�G9a�L9bDDc�cd��c��ke��'� �����cf�(|��igDDh�Y�i��cUDDj��'k
_lDDm�Hm%ۦ=�tn�G9o�ipi#q��r���s��$,��0�R�kt�Hm{ۦu��cv���iw�(|��}xDDz�G9��}yWmz�4{�I|�i���}DDd�)n~�!n���(�DD�k��ۀ�����i�DD�i���i��Ղ)n0���DD����i��k�DD��?�Nm�)�Nm��{8�x���h�!n�����@+A�?�Nm��_"��?�����+��Hmo���L9��i�Nm��j���Hm�Nm��HmY�}��L9��i�Nm���i�΀-g���d��?kB��"D�٪��i��_"��i�Zd�J9��?�Nm���m"D���0�Zd���a��i��Z���i�"D���a����Nm�"DR�i��"|]Nm�"|�
o���a""D��/�f�Am����)n��<"D��{��{���av�!n��{��b�����i����F"�"D��"|��#��{�Nm8����!n�����"|.�r>����w���?…�阀"|��/���
o!�"|Ņ��NmDž�-�Nm��{o����{�"D̅�-*ۿ���Nm��{��b��bW�{���х�-��?҅�-����Nm���Յ�'���'�f��Nm�"D_��-��b��i�����{o���J9݅"|ޅo�߅�-�a�iᅇ-�:D�"|��i�"|�o.�P��x����h��'D��兇-8�����i��/��ah� ��{��	��l�/�(w�%�9�2����{\Zd��,D��iՀ����c���r�:D� ��{���ib�,D��{�5Z�y�9:��i��{��,D��{��-W�{��,D��{������-,-n������ ��Zm�b~׀�-�-n��p���'_��-�:|�,D���-n�����-H8�́�'��}��[��,D��-��i�����-	�Hd4�y��,D
��l�iˁ�+�Cm��,D�i*b-n
��i{���Iv�b�������{��bL����ic�y\Zd��a���d��Iv��a�)g��<�i��a�:D7'���7�>Y:DAm�j�r�b~�;n��U��.�,D �-!�-"�b~#��a$���ȀƂi*1��{%�-&��'��a(�F�Ӄ )��b
�ƴ*�}+�-�Y�,��a-��c.��a��3`��/��0�,D1��aj�Zm/��2��3�-4�,DG�a5��a6��y7@"8��q9�ad��:��+;��aeUd<�&D=�->�&Dv�>o�H����'�j�>?�-@-nA�&D��aB&|C�-D�-��aE�&DF��G&|H�i��%ii*I��aJոK�&D��⚀�o9��L��cM����&�=��N��kO���%�&DP��kQ��+��R��/R��_S�&DT�?y��kU�&DV���	�`��a��&���c=�UUW��a	�}��&DX��Y��aZ�&D[��\�fm�}���=��*�o]�&^&|_��a��-jv>��-s�;�o`�>�a��b�ac�+�;d&|e��<�����b��/f&|g�a���h��//��i�-g��/j�
O��&D��=H�jۉ��/�&D�?��}����k��Rl��mm�i��'n��ϖ�x�o)p��q�&Dr�)j��s�':>|t�Bv��)u�&D��l�?���e�>Dv��rw�b�)X�DX�i����?x�4y�>Dz�'f��}�����b��x�f�}{�i�����ݜ�}c�o|Rm��}��k~�>D��/��Xdr&Z��<��>D�Gm�Mv�Mv��i��Xd�}��>D����o�:s���k�Um��cGMv:��&��Xd���j{�c�Um�-g����a��>D����)���j����-g��(n��c���Mvk��j�UmQ��d$��k��c�Cdw��
����]Um��4��{� �Um+��k���B���۝�c���k���z��c��'�Um������{� %
ۣ D�{�;gW�}k���sq���{��-��H�`j�,��-��I���{=������c��Xd�[�� � ��-������ް��Xd���k񂱌�Y��Um�`��Xd� ���'�}��-�k��W��Mv���_΀�ݸUm���k���'&��kX��ϻ��&�Um��c�}�ɻ��+�Mv�Ume��&��c��_����ZmĆ�'`ņ�'��k�Um�uI�`������c��k��Sɇ�oˆ	o��a͆�']�a	�`(�YvΆ��φ8��}~�V�ρ��+D��{҆���Zd��'Ԇ�Ն�kֆ�'*��%׆Cd�3D��}ن�{�3Dfi*ۆCd{��?���܆�{݆f�ã�+D� �+Dv����r�4�f�3D� 	�?�}~憅�+D�}��'������`�'־��'솏?�+D�f�+D�3Dw�:�.�C�ۃ&����+|��'���j��[�'�+|9��-J���+D���'<�����Ad� ���'�Cه+D�Ca������'���-��	� D��{������Ad��{�a�`t��<��y�f����/�y�{	 D���L
�Xm}~ D K�e����)
 D���Xm�+Do� D��a�/���\d�Xm��ܚݣ�g��y�Xm	 �I+%�Xm D�y����-��H�8<=�F��-�f D�k�:Zd<�5e�F�(�yZã�Xm��{>ue�f5P���"�k>�>|�f �{!��z�f"�/#�k$�-% DA	 �8<&��a'�+|( Dd�{@���)j6j��*�uR��{�
o+ D,]v-��a.��c/��@��-0\dH�1 D2�k3
o4��c5�{6 D�kk�2���7�Xm`XZ�Ad���ݠ�{8�Xm���c�*9�k:DD;��a<´=�y>�Xm?DD@ãA�y�'B����}����C�XmDDD��a�Z9%��}E�-���5	oFDDÄ^m
´���
GDD�{�c]v�dwH	 I��(J��c��&o�	K�-L� M]vLDDN��a���O�-�}>��Ȁ�P�'#Q�-R�kS�k��?�T�-gUDDV�-�&WDDXU�Y�{Zk�[��-\�{�DD]�k�k��>d��^��c�Ѵ_�>�f`Eda��-$b)�D�QX�cI"d��m.��-e���f��-g��#h���������?���-iI"���Yj��㄀�ר�.:k/|��o4Ed��}↕|9��lfmI"Q���/|r�!-nEd!��o��-��o��>���{Ё�����p´�>���q��i��o���-���r�/DsDDtEdu�`vDDw�/Dx�һy��zDD{;g|��i�ٷ��)���}�Am$�S~$|��i�z��뀇M+�\m���i��/D��/D�`o��o�+�����/D9��������/D��m*�\m�<<�\mQ�{N6Cd��/D;f���i�´�71�Wmv��A��>���
��ޒ\m���ii$|�\m��/D/|��>i�/DV��Wm)�o�$|��>�5����i�\m����i�U�%����x���o�X9�8�I"�Ed��C�X9��F"��o{�`�AmkB�`������/D���)�o��b��'����H�F"���i���i,�/D��'��m*���i���J��6��M+���ސ�'��%9��iv�.��/D�����i�\mQ�ͱ`��i�\m���i9��Sy��{���i�'���2�`��b&Fk����%���{���Y+��}�@+����B6	����\m�`*l��-�`���X9��b�o����%‡ ���ć���$Ň�ib� �:D�b{�k.���{LJF"	�b*��)ȇ
�g:D��k;���z��c��b��{��{��'��J��;���}҇R����=�f���b��i��{m�ZmՇZm��c*]�Ѵ�'/��)؇:|���ه��ڇZmۇ^9���i܇^9݇Zm��'�:|.��c��:|� �;��:|��+�^9��cw��Ξ5S�:D��b��a�;�=����^9��b�:|�ZmZ��%�51�Zm��{��ܛ�{���b�^9��:|��i��i�:|�Zm:�i����HdR R�}Ä�)�^9m�}��)��i��c�^d�Em���i��Zm" ��^9��Zm���o���}��^9��{��c��{���c��d�}Հh��cÄ�)t�}�c������}��M"��{�c��>�i�i�b�{����-	�bX�Q
���	�����N��c
`�-�:|���>��b~�^9e������?�Դ�-����f2���Zm�^9�-��-g����O��c�-�^9���c����^9��������-�- �^9�� �-b��b��a!��i�Q�w�"�i�Z�#"D$�i%��&���'�-(�i)��*�"|+�-"D,���-�?v���.�-o�i/�Iv0��>1�2��i��L9��o�22�"|
'3��'�e$Y���|��4�"|"��k*�<5)D��-]�?6��j7�"|8��'���9���5���5�-q�"�9�-:)D�"|;�M".�-<�"|=)D*�/>�-?)D����@��%A)Dk�"|8�}��}B�-C�)|s"DD��'E�-F)DGFJH�)|I�-JAmK���`#L)D�;�M�)|N��^mO�)|�;mP�vc���Q�)|)���&)R^mS��������T^mU�c9�"|V��%���%W�"|X�c��2Y^mZ�{[�)|����\�c]�2^��%6�_��d% �� g`���a)D@�b�)|yfc�"|d)De�-f�>D�^m��
跆
��>|g^mh�>D����K"i)D.>|����j��k)Dl�>D��}��c�5Zm�>Dn`b�iL�>D�^mo�)|p�-(�)|����)D6�O���q�Hm���`r�p�s�>D�m뀼�L�>Dt`u�P�v��kw�c"��%����x�py�}�>Dz`{��%|� }�Id~��6�>D�)|��>D_��9���i��ce��{o�݃Mv��}��vc��^��ce�^m���{�>|���y��}���c���Q��cv��1f���{�Mv�z��;g�^m��ce:��{��}�^m���bʁ;g���{�k���c>��ٓ�[v��Y���c��>D�� �>Dm�}=�eי�`��}���c��>D聂k��}Ex*��*`���i�dמ��}���{�)���{���=����>D9�z��}�`��-��}!�W�t�>D���-A��j�`��@v��&D���b��}o���-���}��@v��{��&D�� ��+�4߁�{���j��+��C$�֣>�ce5�N�Mv���-��&D���kt�����}�۴��-���c�۶�&Dk�ݹ�ce% �-|��ced�}��&DQ9D�)耨�9D���{F�&D��ce��&D�-|�9D�}~�9D���f��ce�i*�9DĈ�?�`ƈ�bLj�b�-|Ɉ�{h��r�F9D�Ad��aeˈ-D̈Ձ͈Ad�<!ΈAd�&|9D��?�ae�9DȀ�T���y�-DшAd�-|��b�%)�9DԈ�&Bֈ-D�9D�-|��9DڈAdۈ-D��a݈-Dd�}��?=S���-߈&D�-D^$�9D�Ad�-D��'�Ad�-ل����'�Ad�&Di9D�ie�ae��i�9|h��-Q�&D��'���-D�Add���_��3��'�}~��y-|�-D�9D���Ad�գ�9D�z�Ad��'�9D�}~	����Adi9Dd�~�u��i�9D������I�9D���i���tY���f��i3����'�9D�m~b�:n�-����ɀ�/��:nL��/��i�Ad�� �}�-D�ae��0�Ad�{�Ad�B"��i	�b_�	�B��ae�{�AdÇ5�		�c
	�	�c�-D��=����y
�:n8��x	�c	�i��!��?e��z+m~��i��v���	�i	c���+	�c��{��c��o	�i	�{Ԁ����z�
���c�{�	�c	
�Z�=���	
	DD 	�i!��cl�x"	DD����dw#�7�$	DDdw��$%��z&	DD���c'�	o���b(	DDZ�=���)��c*	DD��$��a��z+	DD����1�z�:n,	DD-	`{q.��i/��cv�����~s��0	DD1	dw��c��P�2	DD3��c&DvƁ��4	DD5�֣6	dw�ze��$�`7	�k��c8	DD9	�c:��'�b;	DD`��<	`��b�DD���=��b>	Ք?���@	eeA��bB��cC	DDD��c뀀b
`_�8�E��z��T�F��cG	DDH��ctEd5 I	EdJ��'K��z,�?;��zL��cԀ�#M	dw��yN��zO	dwP��{Q�UTR��cS�Am��zT	DDU��܎��cP����EdV	7�W	�>t��bX	DDY	Edl b=|�DDZ	�[	dw�ee\	DD���]	������^�Am_	DD�r��&`	DDZ�Ama��c)���b	DD)���c��'�Edd�m*e�Am�UKf���g	 Dh	\m�;�i	2Dj	2Dk	Ca���9��l���m	 D��n��c{ Do��p	2Dq	>nr�
s�����$U D��*t��cu��bՀ�C�>hv��b9��:'���?w	EdhY�ㄲ�x	EdhY�+��y	 Dz	�/52D{	Ed|	 D�Y����}���~	 D��a뀼�ۘ\m��܀	�>���	 Db\m���ahY��	 D�գ����j�Zô�	 D;g�Y��	ه�e��������u?"��}��=D���Ȁ�^�	<F�z��J$�	�{��E"�F"�	\m�	�z�	�c�����EJ:�Amw�uC����	 DQ��	 D���#Ԁ�S���"L�b�	\m�����"�	\m���a�	 D���-�	 D�	 =���	 D�	�d:�b�	\m��5�ۭF��/�	 D��孤	2D�	f��	 D�	���cЁ�h�	��	�	�����h�	 D�	�{�J$�	d~�	�c����	Fa���?����Xz]��)g�	�c���S���˶�)g�Y�e�۷	�c��z�Y���u��:�oR�c��	Y��2>�	�z�	Y���g1���aJ�"���a��hek�f���a��Hd�	���	�4ÉHdĉheʼn�$�-u�	�c�	5@9�W1�	ôȀ�ɉHd�	�,�	�z�nd�{��M��^�	 �	�{�	�"́��	�c(��o�f�	EmWd��c�	���t_
�o��+���׀heY�u�	 P��Ӊ�{�	 Չ�-�����	Em׉t�=�En���?�g1.�he�	�i�	 ډl1�	Em���>w�he�	�i�3'�Wd�	d~ �	Em�	�J�������Wd	Em5�X���	Y��c�Hd�Av���Hd�	 剉ko�f�iw���}�	�c����	�ia���	�����	�E��>�he쉭>�/D
`�Hd�	neD�Iv
��k;�Hd�k	��i�Avه�-��Hd5 �Y��	'���	\d�/D���i>�	new�hec��o�u��l1�`��Z�s�Hd�AvD��i��Hd9\d�	 ne���i@̣�aĂ/D�	 �	�-��$D����$Hm�������k�/�	�i	$|�	\dt�h�	�a����
�i
�'���
\d�$D
-g�	۴�iwX<F
$|
�i
���u�Av��	��F+݀$D
5b�#�	
�('s�/D��'

2
�'y�u�Bd
��+����B�ọ���_�:�
C"��k*�Bd��k
\d��u����'

`���
�>
�ف�Bdm0zj�
�J������Q
\d9�6�
�'�-g4ne�`#j��Ё�.
�'
ne
\dp�N�
��`# 
�>X�6S!
N$9S�"
\d�s2#
$|�C"5�N�3�N�tۘ0zރ��$
�{%���&�X-�����ݡIb'�����x��(�ʜ��7���>)�
�W�h*�G9��}+�bw,�N�-
f.���/
f0��d��h܈bw �'��1
�{9S�2�Zd3�1h4�bw�$g9�s�5
f��ә(�c���g��-6
f3�N�ه��7
K"�C"�k�8��i9
�'	�>3�N�:�;
-g<��i=
�*>��i?�Zd�f@
�{9��i��X�A
��B
��C��+D�Hm�C"�YE�Zdv���j�h�d�Ev�0z*���MvF
�iX�E���^~���G�s�	��nH
0z���I
����iJ�Zd5	̣K��i5�i~L�����$M��i�Um��IlN
fO
Mv5� P��>Q��iR��cЁ��t���5�N�S��cT�i~��_"U��iW��܅���D�V�Q$W
�iX��i�fWfY
MvZ�[v3�i~/��i]��[��cs#B\�Q$���]��c^
�b_
 ��{�&`��ca��b��c�c
Fd����d��h�����Q$�Xd��w$p�i~y��c�ue�Q$��Ӧ�Fdf
 g�g6h�4Zp�1hi
Fd<��h��bj
 Ȁ߀=�F�k��cl
Fd?h�����{m�M�m�"|n��'o��i�Fd*�{Jp
�b.XUq
Fd�U,r
 5Ums
Fd)?hg��i<"D����d��^t��,�/��u�� �bv
Fde��w�"|��x
Fdy��>z�2{
)Do�Q$⁧NK�|
)D}
 ~�"|y��c
�X�
)D���c����u�Q$���i�
)D�
0z�)D_��>���'���c�
)Dd�뀼���"|�
 ��f���*
�/?�����?�
)D�K��
)Dy�)|�
 ���b��f�
Fd�
�d�
 ���'�
Fd�
��
C�ه)|��ۗ
�&�-��A�	옊f�ۙ
Fd���fi��'�
)D�
Fd��)|��f�
�b�
)D����)�����'��)|��G"��f���'�̣�
 D��{��)|�ܧ�����'��
�i��)|�
 D���ɭ
�4���Ԁ�j�
)D�^d��)|��
�i�)D �)|��G"��c��Hʳ
�i��"|��C��
)D��{�?����iK�������b�
)D�۹�Hv��f�
 De)D�
ۼ�f���c��ܾ
5Z�
�i�
)Dه)|h���f�
�i�
 D���'ĊZm�
�Ŋf�)D�
�i�f�
�cȊi6�
�cm��iʊY�huey��y�)|t���
�{̊f�
�c?���
M��iy�)|�=ϊ��
�cъ)|mۚ���)|s���
q1��h��
 D�
%n��`Պ�4֊�{�
�c؊�c�Gmي�c���ڊ�@hue�
����
 D݊2ފ�cX��ć�{��H�;�i��b(��
 D(�����
 D9�
�i�
�c. D����
�c��H�"�Hv���b�
�i���?5������{���b~"��b�j?�~��
�{���{�Ģ����Hv ��c��ꊹc܄ |k�2��{�� |슱�� ��h트+�c�.�c9�ל��� ���&D��-��	�
Ģ�����i�
�{�
�c��q�[��
˴�ue*6hf���&D�
�c/
Ib~�u����
�c���fۯ��"�
�c��x��u�Ђ�݆f��&D���
-|�
9D�i#��+!�&D��m���-|�����4�
'�2-|	����b����
f��c	�&D-|c=
-|
�&Dw�y}-|��b��#�R�
��{�#nW-|�-�?D-|�-D�&�Kdj����/�>�-D�Kd-|9D�-�-D-|�?D�-D�?D���#n�Kdw���-D �?D!-|��?D"�'#�-D$�?D%�'&�
O'�-D(�-D)�?D*�#n����+�-D��&D,$|-�?D.�i��Kd�9D���/�hd�'0���1�Kd2�Kd��3��-@��x4�-D{-|5�Kd��6�-D7�&D8-|9��x�&D9�-D:�Kd�f;̣/$|<�i��J=�$DX�w�>�Kd?9DJ-|
f�Kd}	�+@�iA�����B-|��k�T;��@dC�$DD��/E-|F�-DG�-D�-|_�!6h�-|H�$DI۸J��k��-DK�#nL�bҀ�M�8hN��k��h�iO�-DP�?D	�Q�Kd:�$DR�-DS�E_T��bU�-DV�cW�Kd���X�-DY�?DZ��b[���\�-Dw�wr�'��?D]�$DI����i^-g_����$|��'`}~��ha�{�\db۾$|�yc��zd��e�cf\mgIbƁ��h��ziuCĉ�j$|k�{h�:<6Kvl$|m���w�/n��:<>�x%��'o��ap�cquJr�$D�:<s�*~t�c��iụ>�$Dv�������'�ϣw
�l�@dw� �:<U��h)���h�:<x�@dy�tIЁ8hh�:<z�X${d~|�$D(��}H~�$D��h��Y��c�{k�c���^�Ib���c�}~k6h��cdځ�6h�5@���Y�̣�u�6h���+� �uI���݉��݊�c���z�����Zm;'� �����i�&l+Dk��6:|����)��{�Od�wH���z� jۏOd��U,���z[�&ڊ�s���� �Wd��"Ԁ�_�Od�%ᔋ�z�Y����z� w�~8��c�Od�:<��Cv� ��g��c2��7�g2�:<��E�)Od�+Dg+Di�=D��Cv���a:��>� ���J��a��c��&��V9���i��a�Wd�{f���a���|e��&`<θ�f��=DT����/o�ݨ�F�af��2D��- ��/݀=D���5Ģ� ����+��%9���x1�2D�xI�À�a׉,�2D���a�Z�o\$���a97:hwe��/��*v��6h� �g��/�Wd�Od����e��+�+D
��j���R�Od����bk	U;�&Ɓ�м����|e�QҫĢ�����+�8hOdS��>�2D�Od,�/�+D�Ģ���@�b�)D�=ho�o�=z�)D��zډCv����Y"(?�ọ��Cv�)DNj�_ȋ�$ɋ*ʋ���?�ˋ�b�\d�)D� �	 ϋ;g�)D��zы�iS��b|��?��)D)���/Ӌ	��)D���Ջ�a�?���z׋�i��@?�,�/�)D)D��<(?�j�Q2D��Ӛ��h	dwQ�2�
\$�2D�VQ�2D��e��U,�)DY�e���#�)|�Y"�x15�;g��z��fw֙��!��c��b(�	�ւ2��b���Dዣ�⋣�㋀b䋆b��j�b�	 kCm�f)D���`�	 �)|� ��")D��z��j9�7��71=�6s׀ze�����cꋛ2��dw����)Dw���(?��ze�Zd�ze�~1�	 �)D�Zd��b������Zd�)D��z냨�B���+)���x1���-��~1��ze��5GQ�S��ze��)|��j��Zd+��%���c��<��i.�Zd�̌�)|(���i6��{��ze���{o�o��ze	��
�i� X����ze���F�i�Zd	0z0z� �{w��&����?h�D��i	 �~1���y�c�U,
��{�i��h��iR�iX�{
�b��������i�U,��i���i�b튶�	��ze؅�i��io���ze��&��&�Zd�{w��{�b�6|��c��{��c�_������4z�ze��i*��& �b!��i���/"�i8��J	#�Zd$�J�ބ�>%��id�Zd��a=�TB&�b'��/�~1(��+)��>*��
��/z����+���,�b-��+.� /{�0�Bd(�'���	1�$�?hO��kD��2��+3��44�'Ԁ�J/��i%�x��'��5�i���&���6�i~�Av��i7�'v��v�+8� 9-|9�{:-|���&;����<0z/�Bd=��i��j<0z=��i>��W?�Bd@���A�BdB��cs��>C
�D��cE-|,�?	��t��i�Bd-|F�״G��iH���I�BdJ-|K�'L�IdM�$N�Id�U,A
GWc�c$��.���yO�35�Όe�b�IdP-|Q�Z��+R�D��bS�&�?T��y^��+��
 U�b݃�#L����� V�?W�a�~e�0zo�_��xX��Y�-DZ^d[�{A�Bd\�4�]�{^�Bd_��>�
Ģ�7)X�{`~e*��a�{b�?�
�À�d��{c~e��ꢊX9d�Bde�?��Zmf�_g��h�?9��-|i���-|j��yk��cl��m��	��a��{n-|o�{��i닡ip^dq�{o
,r��isUJ-|t�{uU2v-|
�8hw��ix���y�{z�-{-|D��a��-ʇ�'|�-�6h}�o~�cɀ�iIb��-��{��c�6h��{T�״�Xd��-D���놌�i���+��c��-D��i��Id���y���c��Id�x*�-D��E9��?����a�^d�:�~ew�i���{���c��c��-|懽+��i��-��?��d��{�W�k��b���{�ɪ�C�$g��i���j���{%)|��{��i��&�j*�״@v3��{���coh<�ᣦ��iH�Am��k^�k��i���{���c%��+9ت�{~���{%�i��k��-�Cټ�{~��'ϋ���-u���'��kY_v���$o۲�-��i�"D��`~��h�����c��k���h	9ط�"|��}�-|���i��`~��-��`~��i{�c��-(��ꉂ�%��i��c��`~��y���L(�����/Ì"|��c�km��a��i��c��i�fl9���kɌ`~ʌ״���Md͌"|��/�+z�����'���cn�����ܣ��j�?���-ւ�uЌ��ь�{��NҌ"|;��ӌ����Ռ�c;�������֌Am׌"|��/ٌ��a�`H�y��/��-����e�j$9�m��>��'܌�b�����k+)|ތ�'Ҁ�����
�;|���Yd�X"b�I����k�Md�'�Md9{}�>^m匦N�	Md挦��Yd� D؁�'�2D�
�aތ�'�2D������� D��i�C�Ca� D��+�2D�`~��z����Yd��a������2D� D� D�+z�2DM	�a��-�^m�2D��;|���k�-��i�2D� D��.m��kJ��% D�ؤ��Zv
f
�i
 D9�;I
2D�a�5Z
i#��'
 D��'��k	
�a

 D
2D
�a���iB DW�i
��i����S���j[�p����wt��D)�����i��k{���'
�D
X����
X�G�%��$�:|�;|S�Yd
 D��bw�Yd���3>��
�{���4�;|�[��kх�-��
Em	�Hv
�%b��-
 D����{5��i<n�2Dv��Y��{���-�k 
 D!
f>"
�a#��-t��ke��$��{���"^m��z%��-	d~>2D&�����'��{
�Y(
d~��Հ�w)�Zv!��*
�c+��-��a��8h,��{j-��iT�.
�c/
�%����0��i1�4Z����g2��{3��-4
=h׀��Ɓ2��8h���b�5��a6��?7��{��c�X�8
�D9�&Dz��i�����{(�1'+�����ψ&D��\m:� ;��{<��-c��{=
�jȉ�+>
 ?��-@
�6F��A
 B
�D}�&{�C�&DD��{E
d!F
d�G��-?���H
 ���?�zKQ�SI��-���jJ�&�K�&D����L
 M��-N
 O
]d��+P��{Q
]do��+R��?S
 T��-ւ&DU���V
9D����W�&D5nX��{� Y
�c>�=zZ
]d[��-ރ�b�\�?D=�N�]
�P�c^�?D_
�d`��-#��a��ib�?Dc��-��V^d��ie��-�f�
��f
]dg
��h�?Di
Z�j��ik
]dl
9Dm��?n
]do�?Dp
�4i�'q��ir
�+��-s
]dt��i��ףu�?Dv
`n��-w��i+����$|x��?y��iz��{{��-|
�i}
=h~
�-3
�h
�i�� ��k���i���i���{�
�'�i�
�i��?D��݀�{QW1R��-��&D$$|��T�����&�
]d�
 �
�-�
]d�
�i�
9D��]��
��9���i�
u3��k�
R�
�e�
�?�
�-�iju��
T;��?D�
�i��-��?D�
9D���i���-=t��
�?���-���f���ޟ��i���+���i�
�i�� ���-���b��q~�
�{�
�iv�֧��b�
�?���i���-��?DoI���|��?DZ��+��Nҭ�?D_�Eٮ�Ea���i�
 ��ܱ
�{���x�����$���	ރ���D�0z�
7f!�
�-�
�{��D�
�i8��<���D�
�-���1�
���
�i����%����i�
��
�-���D�
�a��D�
f�
�i�
�{����
f�����=hb�{J��A��N�()5'���)Ǎ�mj���{Wf��>�Hm���]�ȍ��ɍ�ml�����Db`8b��|ʍf��
v~��j̍�݇Y�T�ף2���r���͍��Q��Ƕ�iYf��?�#'�
f�
�{��+Ѝ�@�����T��
�{$�<��Sҍ�A�-g�
g��
�{�y	X$�
ft�=�*�
�{�
�{=�5���a���D��Ӛb�{���N��꣚����
�k�
Zd_�8��D�
�k�����
�io���
�p����
�k�
�i��S���D��3`�}b��eA��Dߍ�Ä�z��&�
�?�
f�
�kk��b�
�i�
f�CvO�i~�
�i	�a~�
fƪ荩c�����`�?��a�
��덩c�a퍩c�
Od��x�
�k�8�
f�c�c����
�il�����=D�ףm\9��Am ��&�
�k	 ����b�a~��<��
�i��$��D3�=D�
Dd��Am���8���cJ���k)|c�h�Am���c\�����DJ����\d�
`��١��s�����?�����,j�&⁦�::�����B9��]����>f�?'�����kk�D��a�k"�D��i�2�iĂ�'by��=z��i	�kh��
�2G�D��2�K	
� �k�iR�i��a~9��e�)D��8�i~;D���	 O� �������cҀ���_
�e��f"��k�)��a~�)#���>;���)DN��c��8��) )Ds�=Dk�2Ԇ���>(�մ!��,"�;|#��,	 $��8(�8'%�2&�D��j8�
Z�')D(�)|{��))D�6h�������o��6��܄�,*��k+)D,��k����-�D.)Dc���d�c��;|Ӄ;|.F��2/��-0 D^�1�;|Ё�b����2 D3�+{�4��kه�%@�5��y6 DЂ�%7�;|8��- ��$ဇ����k5�j9 D:)DG��;��,<)Dv���y D�`= D>)D*���l�)|b	 D?)D�dw@<n��kA��-B)D`^�C��i��HD DE DF��iG)DH�z�)D+;D9�Ik�Ҁ-|J��i���X�{RK DL)DM�-�6h+�-N DO��iR���P��kQ�cR��-S��?T��iU�cV��,���ףW�;|X��iY��-Z�cT���[��%\��,]��,^�-_��-W��k D;��`��-a��kbEm� Dc���B�D�� Dd�i��ݒ<n��ce Df�cj۷���g�-〻cy Dh Dw�f�i�j�c?���kc�l<nm�an Dom~��/����p��q Dr�-���%s D#�8t��-u�{~vۥ���m���5�i����@�fw��-x9�y�iz�k�
;|{��-�ۧ�|��cۊ��m�}��if�2~�b��i���-��ᣧ
�mނ���o��j�
m~��c5ȴ��j��Z��bj�L!��j��x�m~F��ۢ���z_�j�mފ�c(mދ�i9���j��k+�|���-��j�˕��.�8���8mސ� *Ȱ$�{~��k�)z�|~��q,�)��j��i,۽�8�-|�2`���mm�k�q,�8\��$݀/D��'s�V�-|�۫�m��g���c�-|���{����'��>�Bd�������c��{-|�
 �.��������m�Ԉ�S-|��?D���⣎Id��
 �-|��'!�-D�����>�C���?D�-|]�Id��|���-D���(m�)�Id��j��?D(�+��k��?DX�-D����Id��-D�ۢ��y��?Dց-D��?D��:�h� �ۢN�Id)�#nd���?Dń�t>����-Dg�?D<�h��Id��at>���?�Ӛ��-D�$|Ɓ�N,�Id��?D�����-D���%��?D���?Ž�b�2`Ď@d��a��+Ŏ�[Ǝ@d��a����2`I
��?D�$|��3b��'�0�ˎ�i���i腶�̎�i��amwΎ?Dώ-DЎ�iю@dҎ-D��@dӎ�i�$|%�$DՎ�'W��iH�?D֎-D׎?Dʇ@���aL��{c�-D��a���|ڎ?D֎-D`ێ$D�-D��
 ܎�a��aݎ-DW�Idގ�cߎ�i��?D�-D���ᎂ��
&��&`�i8��i�@d��z掶���a��z��{�$|��i��i�ף����6�-��{��
����i	:��{���$|����`���|�$D���$|y;|��$|��$D��a��$DQ�ap������{�����`��@d��Hm��$D|��h��z��|⁦�ۊf�/|T�|���Hm����������$D:�����@d��i�
i8)�@d��8��z]���D��
)��d��zu,X��h5����{��z	��8�i8v��a
�׌"D.Fd;�$�{
��{�����Y��`�!��k��ko��+���{�{��5�+�!���|��_�"|��k	��a��bI�� %�bMd��j.
�y5b$�|�G��*tPb�4ښ���Md��{Md�������i��&��{�iϋ�hÎ�p���^Md��/�"|\�|��CvMd��{ �|��Zdv��'!�i"���#��'$���%���&"D'Z�(�"|L
���y)"D���*��'+Md\�z,��i-��k.��/:� '�0�Cv1Md2��il��k�W3�Cv4Mdv��'5�b6�-7Md���bJ��j8�Cv92Dd�7j*:��j	2D;�-<�Cv�a=2D>�i����?2D@�YdA�-B�iC�iD�-q��QE�CvF�iG�-H2Do��+I�iv��%��-G��'��'J2DK�-†�>L2D�	�4��-M��cN�iO�����-P��b@m;�� Q�eR��S�CvT2DU4�VMdW�-XMdȀՁY�CvZ�-c2D5��[�i\�ic&H�$z�|�]��c^�?=����_)D`P��p���a�ib��c��rbc�{d�{T��kk��'���%e)D�-f�ig��jh�{i�ij�Cv�ak)Dl�m8m��j�an)Do�-c2Dp��iq�{r)DT�s��jt�f>2D\�zu����2DG�y>��a���;�-�av�?w�ix)Dy�iz�-ه�D{�y|�-j	2D��y>}�)|~)D2D�:a��i��m8��a��)|d�:��Zv�ܸG�y>��i���9�S��m8��8�Fa��?��m8��)|��m8�����{��)|�y>��y�a��f{
�z��R7�7�)D��������f��)|�&|p�A��Y��)|���m8��f��&D��{�@m�˕�)DŁ&D��{�1'�)D��{��?�{1Ŵ���y�)D�`��Y�ۯ��{��{�)D��)|_�m8��{�&D�{q��ǩ)D���}�� ��y��.��k�m8��)|9�S��kc�)|�ao�iN�f��)|�9D����y>��f��a��E"��)|o�+�������y��)|+�� ���^tŴ��co��+��>j�	۵�&D������j(�.��kc�V���y��AA��������j���b��;��F��.9D+��>w���k�
ۯ�gԵ&������֣��z��'�&�棐�'�kk
�Ï�/�&|i��/9{������/��'u��r�+��'���zj�z��D⁗�����ʏ&Dˏ��̏����'�ɣ�|��
���
�A��|o�Ł�/hx8Ђ	���'��D�-Џ 
��я�-;|+�b��'S��y����ӏ,ԏ�-Տ��ɣ��j���i�9D€�8��a׏ ��j��%�����kڏ|�����/�����jp�ژ���� ���/b)܏�-.�jݏ�.�bi��/ޏ �-|�Zm2ĭ;�|�����Ꮬ-��AŁ�/� ㏛k��
a{����-98���:<�ɣ�Zm叛k�?⁛k��(u��'-|=�N��6|샘y�ɣ�`��5�d�'����-|� �-D������&v�Dc�&��'�y��`�-D�-�-|����Y��8�c�6|�D�.�-D���S����� ��-D?����F����8��-D��-��h�.n��-D���-��>̀|����k�.nЂ�8���'���-��ܾwU���l�-D��8��-�Zm���m�f8T�4��--|�-D�`8Ђ�8�-D�X�	f_��+�-D��(	`89W9
-|�)�y7.n�
ۊ.n�L考�8
-|��-�T�A`8��8S��ik�-b`8-|e����
��	X$�-D�o-|[�����8[�BR��Y<h�U��|���(�Id�-DY��8ň�ܫ���-D\��z�����8�-D�����Y�*�i��|��R��>���v�(��-Y���+D�-DŅ�8	�Ɉy`8�-D�����aЁ�������Z���Y��8�r���8����Ɓڷ��c�_��z �>9���!�3��":|#��c$�>���&%��i&��a�|�'���܁(��X��c�F�)_�*�&`h�|�+��z�+|8
�>�|�,��a-��.��f8^�ت.N�Am���l@���m��z��/Ð��֣0��{1�iɀ�a��/2��c3����������4��aa��z5��ac�2g��z&�y6��a��c7�.��2<�h8��'9�i��a��摻��:��&���	w�D�;��c�2��a�t�p5ɀ"��'���'�����g����<�c�<�y�̣��i=�>>��?��c@��cB'!A�it�>��zB�iC�i(�`��k:���D�yE�i��L�9z�y�f5�rbЂ�z�	v�2��F��'=���G��at�>��H��cI��a����b��z��.J�f=�>K�%�L��zM��a���=���@��N��cG��"=�7TO�P��aQ��R7�SɣT�2��`>U��YVZ�W��'=({��8
�yX�;|����Y�kc��<Z�y�e�{��W�j[�k\�	v�]�y*�k��^�b���_�y`�ya2Db�yf�fc��'	��&����d����'��9y��&(��bJɣe��f D���
2
��'g Do�+L�g�o�Y���t�>���}펽ih D�i��>i D�>��)�2Dj Dk��-l�k(�y�Ιm��-�	+j�k��-z��at�>7��$n Do��ip�>qt\r�>su:t��xu
2y D���̣5�ܻ��awv�y�
2w Dx��i��y��Ř���y��iz��z��-R��-{��i��{���-����� ��]�>?	��a�a|��}��-��a~�z���;��ۀ��i� D��z���z��a� D� D��z��a��2D� D�Nv��z��a z��Y��� D�2Dy�/D�
2m z���h9�r� D���� D��/D��.� D���h`���|�f����-� D��%���i�@�{@m;���a��ȣ��c���Ǚ��?|��? z��L��-���i���(u���"���i���-��k���-���i��tB�W���a����c6��0ѣ�c��)���i(��o�z��c���z��c���֩�k���x����z���iS
�.g�a���ۡ��.�co��i[�a��� �����ar��+��.�Em��'��۲�"��c{�z��X�1Ѵ C��<��'��c��aN�5��'� ox��'j�h��Bd��%cۯ���|���>��'t��-s:|{���c1�����c$��	 ���VR5n��'d��	S�
 Q�͚�����Yh�@+���c��'��:���c
�Ð[dރ��Đ?D��"Ọ̋_
�Ɛ?D��'W��kW��}Ȑ�4��kk�z��Aː�i��k�ɀ�����-��cϐ���i���=�?D��zd��ѐ?D́yȅ��|����Ґ?DӐ�iԐ�jJ�ȣ��iՐ?DqC2o�_j��Y֐�f�{ko��%א�ܿ���3�>��'��'ِ�iڐ?D�П�$|=�?D��'f�X9ݐ[d��i9�5D�-d�'��i���|d����̣��'N��I�c�Adᐢc��i��b�����$|䐢c��a���񎀢c���|��a搢c�ݻ�?D��i�?D�$|d��Gc��$|��a�?Dn:�?D����$DT�;��'c����i��Y�+�?D5�Zm�z~�[d�..� �{�i8}��k�c�[d��V��?D�[d���j��N���i��E�j��%�:���h���i3���it&~��}��i(��?����b���a�W����$|���c�C���ci8��`s;�e��$|��$$|�
��o�h�)�i�;��{�!nl�{����zY"Dl��c������>c�Zm��	F�	uJ
��cɣ��>����a�{����
�Zm.#|�Y��ޗ�z�����
a�z~D��c����oN��>ʈ�QS�@��(5���zt���t���"|��a���	�n"D��pc�rb�i8�{����ј�{5��*�AdX$�۷��C��A�"| �"Dw����>Ȁ���"|��@âUv�zx�z�̣9�S�:<��"|�i�Q�k�j����"|d́ 
� ��0��/ �i!�+"2��-��9
(�-#� $�"|6;�%���_���i����0&��'��Cv����'��%(��')��ct���*��'9��*+�"|,��c��'Ȁ�
��-�Cv.�k/��a0���1��c2f�3��݀Cvj&h4�U���%
���zx5�z���a��
aA 6��a7�k8��9��':��aj���;Vd<~~=��i�~~��>��a?��a@��%A�bB�cC�aD�b�	��g�CvE�aF��a�"|ɣ��bG��a��aH��ݾ��[I�a	Dd
��cJVd	�K�aL�&�W�6;�����s���M�'N��a�~~Oâ9���D��iP�bQ�c�&hR�be��`.V75ZY���ZvS�CvT�a߁�'��2U�kVգ��W�iX�jY��aZ�Zv[�c��rb\��i]�a���x�b^)D_�p�`�Yd���aa�b	��k\� b��a�)Dc�bQ��%d��ae��a6�b��9��%s��c��`f��jg�&h��]�Vd����i)Dj��ak�uC��&{��`l)Dm��`9��n���o�)|p��Qq�&����rZ�	�dv��s�&t)D:�t	 \�bu~~vVd��rb�^m:�b;��Am9��w)D9�Uz�ax�)|y)D��m8z�b����{ D|��f��b}�m8b�a��@��m8@�|�~�&����)C�Y� ��)|G��j��;|�x����4Z�
ۃ Db�$zی8vx$� D�<n4�)|������)|
;��)D� D���"��&DȀ�	� D� D+)Di�&Dȃ۪�)D��&Dښ�rb9�{���	�[9�{q�)D�f��s�)��2�)D� D��&DJ��i�x�jXO� Dvo<Σ���&ё�&D��c\���o�9D��cs��mƁ)|����Σz�&D��)|�)��c��iC��&D����;|��)|G�&DX9D��&D� D��)|��c��c��&D(�����`��&D��u\�����{ D��cރ��J�(c D�����}���Q� D��4�_��c��co��a��>���{� D��鲑�z�gԴ]d���z��'�
ۧ���c��:Z� D���z��cg���m)���bp�Ӛ��a��&DY�`�BЅ��|d]d��&�B�����)^����jxÀ|���Y���	pj��iه��;��@m��#��i��c+�&Dv�i��c‘&D���C������i����#��)��cj��z���D=�A,
�C�jx5�-o�%��&h��%Ƒ̣��S�g{�;���cs���c��|���Ӛb�i��co&h�k��9D��i�g|y�c��-|@Σ�
�`�|(���-|��i�51Б	�q�	$jۯ�f[�z�-|`9�������{b�� ��zґE9}	����ԑ�zՑi��-|בi!��{(��諉�=}��ّ� �-|h�-D�AmƁ��ۑ?D�;�s�-Dܑ�b�����'��zߑ�b�?D�-|�Ids�-D�	���c�Id�{�䑫c@�-D�Id��b��z�-|�-D�?DI
��v0ꑃb&h�-D�?D(��D�w������t$|�����%� z�)�?DI��	�-D;��?D�c�-À�b5�c�f8:��b�)���Q����?D�iV$|��?D���c��)���b����Q��LJ�f8���b�\v�-|���b�@d-|���a�$|�n�4��i��b��g�f8-|��ܧ���b-|��a�@d�?D�@d-|N����$D(��	�?D
�@d*��%��c�f
�?D��c���c��a��b�z�-D�?D�@d��b�a�@d�?¢�?D%��c��b��-D�-D�?D�$|H�a�-D��c�a�@d �@dV�z.��!��b"\v#��b��$*����&hΣ$�z���5�2%��z&$|Z�_'\v�
��A�{($|)Fnp�o*�z$|QW+�@d"��/,�@dY��.����z-+D$D.�z.��a��a/�@d0�f8݃�����1�c�$D��v�2���3�=D4��a��z5��`6��a���27�c8�a9�jd���':�����?����_�����d<�f9���c�z��5;��a<�2;��zs�=����	+Dd�fƁ{K=��ӏ���-z�`� ���a>�c?�zQ��+D@T;�ޣA�{΁�5��$)���{�f�|B��O('!6;�C�&D������.��5*�ޣ���E����f#j�&)�#/��=DF+�GMdHMd5��<'�ۀ�;���b������7�D�I�f;����.JMd�Mdt��zK�&L	 M�Cv��=��N�c:���O�CvP�YQ
���bR z��Ы��	��׀&�S�bad>\!��T�cU z��'V�bWMdh`X�CvX�zKY�bZg�[Md\�Cv��bg!]�Cv��b!^Md���_��(��	2�
;�b4�Cv�{���b`���a�Yb�bE�c��8 z�g�dg!��������b�F�m ze�Cv����f zg�Cv�!h	 v��o��$���-ed>
��iڊFS�@+it����+��cj2Dk��h�&���lNvlm#i�cm�bnMdo�Cvp�bq��ar	 s��t�Cvu��aH�%��avMd���w��a���x��d4�Cvy�bz2D_',�!��b{)D���(|�Cv=���})D�p�k�2vxʉCv~)Dvx>�$z�)D�
ף��Cvh!�)DD�ޣ��Cv���ao�������J�>J¢���a�Fh�)D�j���D��Ɓ���vxw��¨�/Do���$�'��)D�vx��-�R��ۣ)D��)|��)Do��k��D�.<��)|���(�)ڋ�D�)D8��CJĆ$z�)D[�)|��L���>�)D��)|�o5�v&h��v��_�����ܫۀ���y�c_��=�ף��/Dv�o���ay�c��)|g��D��4S�Σ�f�)D��-��c��c)D�ܟo�5@���/��)|�)D��c���aHmy�ck��D�)Dmo�vx���Q���d�ϥ�-J�/Ѧ)D�o��ַ�>�)D���x�ף����vxP�Qj�"�)|Ԇ�xv��*����c�Hd���>��i�Gv�	��j���y�t��µ���b*�/D��)|.�/D=e�ґ	���_��8���bm��b���V���>��ٕ��)|Y����2�Bd]���T���/D���-�o��c���k��#����o�F����S2��b:��γ��c�L,D�$��5�8�̍�xj����*`��QX��!���crgԩ�xx��cH�;8
ף<�"�ܶ�}���c������(��d��cO��&y�cƁR���co��xkxc��b�q���cЁxx5�����b�Fٽ۾�a��a�����-z������a䣚��&��ꗣ&h萺x��a��aĒ�>Œ�������ΣȒ�#�ף�Zmɒo��8��&h˒�bω�x̒o�
դ8 gQ�w��Σa2�8�iovxk�1<%TTΒ���-��a�u3d���	�5��a#��c=K9��a��۰vx���'e����9�a���(�Zm�vx�-|��xx��t�Ւ�&��xx֒�{ג��ؒi�ْc~5��&�
 }vxڒxx���9����-|Z�d�#�V
vxܒ��ݒ�c�i8�-|�+��xxN�m����5�	,k
��呼��(���	�-D�-D����i8ރv��-D�6|h�!n��	�-D�-|�C�C��-D�����$�6|���������;�-D��>��a����-D�-|́�j��a��!n��a9���-D<�-D��!n����"D���j�"D^����-D�"D���%��{��-D׉ʦ��b�4Z�ףp�բԀ�+�������"D;�)����b�Jv_"D��wU�	Σ��	��������"|vx��f8��	"D
-|vx��	"D��&�b%"D
�"|�b[�-|�-D�b�"|Zd"D�Jv�-D�"|ބ���!n�r�!n��D�Id�'I
�	I�����}q����D�-D"Dܣ��%�c�-D�kj+�) ��x!�c"�"|#�-D��D$�EJ%�'
	�i��"|Ȁ#������{�۫���D����r�݀)G���&"D'��c��:�(�c)�b*�Ba��9�ډ+�c,��c-�	o.�b
���f8mAm.��/�c���c�zЁxx0������A����e�51�	o
��.��	o���'Z��2�Jvv�i3�"|4�	o�}I��5�Jv6�"|7�f88Vd9�	oc��'o��i:�"|րw��'�����"|p ;��'<�4Z�ףV
vx=�c�����D6�{>a�x>�c?��c�� 2�c @ ��DS䣑G�ރ{:G�A�'=Up��
)B�c�{�I��c��C�9�D���cD&;�	o���'E�c��h�ZvF��Rx~���G
)܂��9�HMdI��c���	o���'�	o(��DJ��c�	oKMd9X��b뇘�L��cM�m8��	o_��l���b_���N���DףȀג�&
^�O�fP��D�����ǪQ�i���RVdS��D�iT�m	�f�{U��D��d�-��+V�;|W�iXMdY��b;
)�ߣ��c�
��s��Z�&��磀	�[��DX��1o���}x��8\��Dd�:�]Md��&>2D^�;|_�/�2D`��ca�@����fb��D���c2D(��Dd��be�iL�Z�&f�fg�f2Z��A�����f��������D��h��o2Dm��i D%nV
2Dj DX�{D��e|k�zl D���c��fm���nMdo2Dp��Y� Dp��&|q�&DrMd��/Ł&De��D��&D� D+�m8sMd>2Dtx�o
ou}8
 Dv2Dw
ox��D�9D�f���N����DT�&�r��J��+c��c���w�p�y�fz��D{��Z�m8|�f}��Do���ִ���'~�;|>2D D��/.�>��U��
o\&�jd����D���i�2D^��(��D� D�9D��z��[À�d�2D�&DR��'��<�կpj��[���{� D>2D�&B�;�9DV
2D� D���j��z��>2Dp�Kd��ovx5	�#��zj��i�[�D�ǪN-b�&D��磒��'��ԓ�&D�/gL���g��<��-z��&D>�h��c��&D8�i��tjȀҁ��'_� ���&�� ��cX{J�{™
o�� ��cl�(n��a���{���xJ����c�� ���a��ݖ��l�c��e�
o{
2�
o�� ���{�
o���D́�B��*���{�� ��)v�ۇ	�1���{��&�� h�5����{�� d��ު��{��)�cv�F�ۯ��f8���ܫfp� �X�pj�f��b��9D���i�I�c�v<�(n��b펫+��bw�76��c��i!��bh�`���{xףl�$h�&���{�f8 ��kvx��?Dc����b�=���i����`9���]��i��b�Ǫ�
�J��c�c�ck�b���i��b���{��c$�Cm��?D�� Ҁ�{��8�2���y��{Q�“�i��{��b(��Ó�i4� ��bœ�iƓ�{Ǔ�.r�	��$|��boѰL��ɓ�{ʓ�ia�c�l*˓�i̓�{9�;��b�{��?D���dՊ��͓�Ċ	W1�f��{}��c�����n4GГ�i%��8*��ӏP��dwѓW���&ovx��&��1��b��]�������$݀�`��`���J֓�a������lJ�c6}x38|ؓ�i��`e�b=�p�ٓ&ړ�a��ۓ�i��p����a�+Dkvxݓ?D9´��&��`QR��+D��{h�!��i�+D��+D�?D䓡8�ߣ�?D-�Ϣr��d�=�iQ��"+D �@d[ףG��۫�	4��i�����z�)��}j]�=D$�+D⁃��&�!n����z"�+|�)����+|�)��z4oX6�+D���R�&�����>�a�Y��aj�����z݃�A8
�v��(耚-Oo����aXW^
!�I�{o��	>+D�݃���ߣ��h��>h�@d������/����a�
"��o߂=D��h���a9����>[�6[��i�2���'��>���kv�2��:h���+�+D���$�?z��>�i��R��f��}��b6�?z�	 	��$v���z�ӗ�ߣa��Ϣ��zX5�Hd�f��.��k	 ��bJͣ	�+|	 ��f�Gm	���
�i�������$��X��X�
���b��=D��z�f�i�>��z��	%�a�)%2zo�xxk�`#݀Cv��i�	 Uv��d���I
�	/?���cY��uW�{2z�i�	�>�����*��R����(�e��?�W��uܣ�F��i�ߣ.��x�&>;���o�A��c �g�!�7�"Vd#�il�,$Vd��io� %��c&��bi��$'��d�n�Cv(VdA�c=����{��Gm(��93�)��c�	 ���*Uv��]�+��c��f#,���-Ӏ��ל�DdVd.g�J�a/�a�
2�Fa	 !�o��_�i0�a�	�`R��UvbDd1��t
�i�u:���/2��ogd=�3�a42z���e5Vd]2z
��D6�i��Y��7
2݀Cv8�a9�8��z�	 :�ze�	�%�)D��x;���Cv��z<Xm=�d�>��?��$f2z@)DA��cB�z�jC)DDj�h�zEjF)Dه�-�	/|G��{H���I)DJ�)|?����v��)|K�;|ڋ/DL�M�zN)DO)DP�/DQ��i���ӃFa,�/D��z���R��i�DdS)DTCJU)DV�;|z�/D��-8
)D
��DW�/DH���X�zY�e���]�J��:��	Ҁ�d��D����iw=v�5���Z��@Ł/Ds��[�b\�)|]�b�����ze���?^��i_�b`)D�iea��ib�zc)Dd�/Du�
2�Xv9s���7�e)Df��i{������/�/Dg/|h�bioj�/Dk��il�Ը��mn�zZ=o�bp�bq�z�[T�4r)Ds�z���iZ�t�zu)D���v�b)D)�bw�zx��iy�/D�)|�!Dz�c{�bd�BdXm���>��i�)||�/D}�Bd~�c��i��/D��	���i��)|��b��/D���i��%B��Bd��	o<�Ϣ߂�VJ��䀉}j���i��Bdp�˸Ё�_���cπ裊��i���i�آ
�񌔻c���b�����)���{�o݁	o�<n���c��b'��S� 	�b��c���	p�%g)=��6Zd��c���i/�a��b���h���ib��,-�Ϣ�$[RҘ��i5�Bd��裚��i��{��	o ���c���h��Xv��U8_��n瀨8 ��{<��,���ݡ��'��c>!袔�,���nA��'���i��{B��dw�٥�c��˸ƁW�
� �*��S�)
��%���h*ۥ�{��c D�c��\��)
��(O��cU2�
 ��ab��c���o��x���ߨ��d��c���c��{k����do
&�)Ԃ�a߂�e���c��� ��Y�98΅�{)��Q� ���h"�z���=���c��(������E³�i�i8����c��i��i���i!�-|��z��i�:�V �~8�݁�-|� ��{��?D��Id�i)�zmw�-D{-|”IdÔf��Dj�e�i�Zd�{ +�h�#�d`Ŕ-D��{ǔ?D�-D��i��Id]�;�?D�Idɔ-Dʔ�'˔-D̔Id�Fd)�?D$�i�"Dϔf��D��z@��IdєIdҔ-DӔ�b�?DԔf���֔f2�\vؔ-Dٔ?D�-|�"D���ܔ?DS�-D�IdݔId8�-Dޔf8߇?D�"|�"��-|�˕��zȀ���Fd���0��ir�)��D'
������i�i8�f��z�-|���をI�?D�����-D�-D��i��Id��>��>��z�f8�f�f�?D(��D�-DX2��>a�V���/�-D��>��Id��X��آ�-D��f��?D��Id��T壞�f��-D��Id��آ5����
�/��-D�
o��g���>�@dh�-DÎ�f��D�?h��t�ߌ�Z�Nm���"|���P:�%o���f8�z���G"�h'�'#��5�D��x��>	F
��x��(���z �آ�,�a�2���
�f8@����x�n��oA f��Yd[2ѝ�o�H�̣�c��o�蓉���%{�@d��f7�/���t�@d�?h���f ��][�SJ��(����
o,=g)'!6�8<�2ރ{l�m��g�w�%�25@��d�
 �& .��ko��d5Z5���;
o��im���k��' ��i� k��i�f!��e�"��i�:uJ�#Mdɀ�i���־H�p�$Md닳[%�0	f�&�2��V��k'��i��b(��k��i鑦R%�2)��i�)*���+ Ă�k,_��m8s�i-Md.��idۢ�Cv��Yd�/Md��{k�2��Cv0�Cv1��i���ރ��2��ip�-��{�
�m#Md|��3�b�v���݀Cv�BЄKv4��{���5��i���%#Md��`�����j6Md7����n*��8&|9�/5�cĉm8�Dd<�m8:��j��x;��i��:偓�Ă�i<Dd=���>��i?��i@|~��fA�f��>!��zB�Cv�_d���{C&|D7n�8:EMd냺iF��j�MdG��{��ޫ�	H�&DyDdI�&D)�?J&|K�FLMdMfN��{O�4>��P��h��&D�܌Q��R��hSMd	f�S&|��	9�eT�8�	�z8:U��hЁ���&e�&D��gV_dW��{X]dY��h׀�Z�&D9D[��h\��,]9D^��{_�Cv��j`9D\�8��Cv�&Da9D�O�b�e�"��'c9Dd�&D?�U~.��he��,f��{g��>h��i.��D��p�����i�	}i�Kd.��D���/j��/k9Dl��h��b��Dm��z�&Dn�x&D���/o9Dp�[q9D;�x���yDd���rDd�9|A��'Z!����s�z��꣚��t9DWۯ����u�{v��/�����9D��Kd��&Dw*AZ��9D���D��i)��x9D��hy�i��iz��z4�z{��f
��d��i|��xԀ�%}�i��D<�(n��z~�&D�i���hʃ_%�i�9D���ha��z�	����i���Z�fb�钅9Dt�zo��o��i��z�^v�i�9D��i��D�����9D�	Ym� ��i�9D�G9��z���D�]d���J,��/t��>�$3��/�f��i��D��b�:n��{o��x�f��9|��D��> m�.��Do��Jo���{����E9���-���D��$��i��zی�zp"���%��{*����Cm��U�����y������	�!ۗ����{kr������i���.��l�c~��x$�dl�x��{��z��$?����۟���{��-=*�X�61��$��}��i��	�F9��7��ib��e���ܒ�iZ��׀�-�5iFێ2
�%���d��At����ŧ��%��Pҩ�&֪�Cm��/��Cm�Ș��2
���*���}����آ�����׭�{% �`8���>��{��8��$7Um��2������q���(��{��Cmj��/z
2�2��$��'
��o�ã���$[�i��?��a���/b�dYV�(�۲�裬�2�Q$��z��F�Zd��z������ɸ�c~
���@۹������+D��c~���.=��%"+D�)�*�J��z���
f��	�?��z��fr�Ě���Z���z��+|��IdQ�=D•�x����+D/��ĕ�b
���&.�zƕ�bǕ�x�=|�;[��er���,���9�=DՊӘ�{��&ȕIdm�aɕ+|�$gQ�=D����	�z˕�x����{��z��%Ε+|ϕ�b��{@�ѕ�)H=|R�Idҕ��ӕ�x��{�����z���hՕ�b��'��{��Fi���ی�zؕ2ψ�D�$gl�f���B��b���D�$gd�f�$g��'+Dψ�Dݕf��%=|S�Xm�'�$g��z����$g�D╪j)��x�x��D���j���)��xg�z��D�Od���d��z�D畁bg�f	۰��D�=D镁bTԙw�O�ꕁbc�����de���땁b���z��f8�d��Ide���h�f� �z�Id��i� �z��j�$g€ã�$gV
 ńң8�d��b�j�>nz���$g���$���c����k���z��{��Q�R�]�'5�5��{����d�g�C��{*��z�k���	�f97�<�ã��$�Fi��bJ���h��D��$�����i�f8ψ�D ��z��h��b��ׁ�f8��z� ψ�D��ji��C�)ψ�DY�f�Gm���d)���v��^���>��`�"g	��$���db���
���z���K���d[�6s>����z3�9��R� ȃ��
� R�O�O����>� "��>p�b�Wd��=u�bu:��-�rf:�"g��h���z��x� v�ң��f��-��%�i{�i�/|� ��d���������kdMd)����b�X��0��i�b9r�e�i��$T�	o������zX�z? �i!��c��	Md"�iۃ�+?	��#�iX��z���l�/D$���%�xƁ:d&�i��'/|�Md��<�-(Z�j�ۆ�;|)Md*�/D+�x,�i���(�3��,xVd
�/D��b-�,.�/D/�i0��O1���d�/D����Md�2 D3�b4���5�/D6�i��� 7�"g�/�/D��� ��x8�&3f9��&���De�Av: D;�/D�x�Av<��xMd= D>���?����-=�t`�{M@ D3��x������A���B D(���C D��
��+Q�BdD��iU�/DdMdS��xE��i΀�xF DG��Md؂BdH����HMd?�bI DJ�}'!K�i� DL9�BdM�iN��i��b��{>���-O��P� Q�zR�%�J�N���S��i���T�Av;�� Հ�n_�	U�{V�;|T�(��iW�Bd;��$X�zډ'!m�Y D
�d[�݋��'Z�/DSjϓ� [�)g\��i� Dl�z���l9��i���5��脢x]���� D��m^ D+;_�)g`�	+a D��["�bfbv�+c�	lC"��d��,�)e Df��xg�)gh��ii D���d��+Q�bo��xj��iψ)g��k��il)m��i��%�
ۯ
2n��iT�)go�Jmp��$q�)gr��i����s���t��8u�ov�cw�T2֣x
o�)g����y��i��d��ãq�zz��i�����	���z�@��-{�i{g��%r�z�$|�z�	 }�o~��i�{�
o��d���i��b���
�5�)����"��ii8�f���i��)g���b��zX5S�)����t��Hތd�������i.�����z���b��i�偫H���댖)g�U��(ݍ��iS�)g
D���b'i8,��/���i���i������f
V �V���=��!n��!n���bo�A��?D؂�if��̶��[dc�=���{ȃ�Ƅ�?D��[d���i���x)���W��{�C���:�
�8�=��dt�?D�?��$
�I5�=��[d���{��=<�!n��o��?D����[d�����"D�$|��?D�Fd��o�	"D�Fd���{"D�$|�Fd�����"D[��5�!nFdL3���i��!n���n��?D��=��x���"D��?D��[d��i>Ev���b���i��i��=���z�"|��ܭ�i
��d�s�f�i8�$|Ё�`,��/��"|�Fd��'�"D��?D�t{p!��\v�$|�����-g9R��"DR�"|\�d��!n��?D��i��9�5�=��c�=��!n2ü�?D�"D��Y�2�c�=��!n��{��"|��?Dc�=–!n��[d�����'�e�Ė?DŖ�i��&Ɩ?DǖE��-gg��`��i���"D��{����Fd�"Dϖ���-g����"DZ����Fd��i��i�"|�7$|����$| ����"DS�G"�$|l�I��i��m�Fd�F��$|�H���{�������a\$ݖ�N��⌀"|�*A��z��$����
��z��{�S����n��z��i��o��z�G"��Q$b ��$D��z\�i�G"��&��{��i�-g��z҇��Hv2S��Ggo ဉ$��{B�Hv�-g��{5ȣ3�+�Hv�-gd�ց�iQ� g��i� ��z��{� �f��i��{_�i��{	��5�
}�Zv� ��x��z��,{��'��i��{��i��{��Q��,����Y�{��i߁��i�{��E�|}v�+�{�i���v�i�{�i��	�i T,����}�X$��{�V1	 
�Q$Ё�d�)��i�y�
%n�i,�hd��an �m8�{�z ��a���$�ij�{�{o�-��{#���'�x�z�Hv���{�i��a�{�z��am�8����i��i{�x��a�	�{ ��  �{!&|�2j�f���{2í8�f"��a� g# $ %��a&��z'��( ���`)%nz�i*�x+&|��z�����+,��a- �{���{.�&D/��a�Ҁ:�&D�� ��{0 �&|��������%nI����&D1�i���2�&D%��'3�i4�m8��i5�{�)���6�i7�&D��Y�8��a9)D:9D;�i<��a=9D�&|'��_>��a�)D(�&D��t:|?)D�&D@��a�8�[��aA��'Ђ	�)�� B)D�������%C�~D9DE)D��&F��aG��aH)Dw�n���/�)DI��aJ���뀽�K9DL)D.�&D�	�m.�&D���z&|��'M)D[��&���^��&D�N�KdO��P�)|Q9DR&DS]dO&|T)DUfR�'V&|W�)|X��z�)DY��>R�'(�&D��/���Z�&D[��'\�)|�'��ڹ��.���]ˣ^��_$g`�Kd��':�����`a��zb�&D.�&D\��$��"���dc�&DQ�	d)D��ԣ)D���e��z�&��'Ts��&��(n�9Dffg)Dh��c�)Di�'�9D��oj��o{�h�zk)D��:no��9��?�l)Dm�(n�'ntvo�)|���pfj
�q)Dr9D%\$s�zy�(n�"aUmt�£���嚀�·�>`u��me��cv�aЂ:���,w�%"��'x��iy�az�)|{�'��|f}�'E�)|~��c�E9�f���z�B���Kd?�V�fe�0�5�(n�f>D��':4�a����d���,���zv�@�a��y����z��F���?����{���z�B�hE'�`8���z���,oB����f��z�`�P��(n��Ύ��'� [���(�t��,��a���z���z>�À|�r�S�z�(n���z������zЁ�%�+��i��(n��wň�?���z���'��#ۘ`8��>�`8儤������g��$���'��a���%�$��d���r�<Q�;����	�?���㾀��"�2���'��3���ԚUm��2��a 7)d��	�����z��$�g��Y�Տ���ߢ�as�R�$���)ϋ�ݣ���o�2�-|���X�-|���z�+D�Od�Od
��d��d��1���$���z��c���Od��B=B����,�t����z��$�-|��2�	+D�-|d��	+D�-|���z5��&�-|�-DX���o��?��5i#rԣa����
?��+D��+|Od
-|�+D��Ĺ�+|��9S�ˆ�b��$d�-D��+|���`+DI��'��-D�-|��-D�`8ʎ��a~��-D����� D×�'� Dŗ-D��)�\v�-D�Ca��<V���jȗ�@ɗ�x}+D<�h���&����y��f8ʗ+|� D�-|� DΗ�&��ݫ��^Q����f85�h׀+|� D�XmԀ	&��2З+|8��.��'ї�az
Od�$җ�a
5@��
�i�-|� D�R՗f8�OdJmח-D���â���xl	p�� D'!�]mٗ�aȀ�� ��$� Dۗ-D����ܗ�a���ݗ-D	�j��&�`jޗ+|o�	&ߗ�a�-D-��b��a�)��&�-D��`��z�fx�-D.���sfx�)g��a�+|��&�)��Ds��� D3�o� D{fx��D��{$�)� Dw��8��D�\v��|܄�z�\vfx���)>?���z�g�j�d� D��Dw���k��D��b� D���$�F����z��D��j��	��zg�x~[���,�)��Xmd��f8���,���a��D��zā	&��{��&>��h�u0��a!��A��G)�9t��p�]��=�	&u��it�,��z?���z��>X�;T��z��a���&Ɓ��5�Im�`j��>s��Jm�	��ތ�>	�$
�z��,u:�x�u������z�~Ȁ�^
�{�zw�uf����)_',�z�,h����E��&Y�b��D��	v��{��>QX�=�X��z��������z��D$�o��	�d��)g�b)��|��z~/|j��a���j����Ȁ%�)��| �x!���"�,#��z$�}%�/D
�a�	�>&�#n'�/D�(Ƽ�]�(�}��/D�
c�5���)�&o���b��*�#n���+�/Dm�~ӏ��,��?<�ai�}-&�.�/D���>/�},�`�$|0�a1�#n2��-3$|4���5�#n+/|�G96��>������k$|7�/DG��z82D�{��'9�#n����5�ʀ_}5��:��-Q7:>2D;�/D�u�<�/D�$|=��id>$|?�$D��z@�b��âA�/Dk��x�2D$|B�Xk��xJդC�$D0a��xD$|E��xF�$DGMd5��H�$DI�bJˣK�#nL�$D<�aM$|N�bO$|���`P�'����Q�$DR�#nS�/DqG+�BdT�/DU�}O-gV�/Dg��x{��`��5@W�$DX�}��|�>!Y�/DZ�#n[�}�:u\2D]�/D���`w�}c�m#R�/D^�$D)_�$DO$|��/D�\$H$|`�tCAm(\m��a\mb��c��id$|e��x����f�'g�'h�)�	�?i�j�)k$|�}8Ё�dl$|��	�7��
 m}�Z�n���'o�$D��;gp�%:�q�$Dr��xs�)"�{t��x�*Au�)uԣv��`�	&"�{�Úw�)ЁM��Ymwۯd�7�x�4Sy�;gz��f{�$D8
=�o|�)}�)~��ns�$D97:j��lg�d�^�Sɀ�����'>�&���@�)���c�ۯ(���)��[����c�(	����ի��l?!
U2O-g���b��	&���b��ۀ���}���i8���cG��bd�	���bՀp��'n���'_����)��$���a���{���"i8��;g�
�i8_�i��$���V��c��Y�4�)��$��)�����`��)���bĆ�c�i8j&`�&ؖ��c�i8��)�i8�:�݀�'��)e��c{�5b3��ޛ�)���a���b��$G
�݁z��g,�f��`��i��i��$�g�����)���Oۯ��x��%���O��8���|���Ǘ�ܤ�iO��br�	��A�Fd$�<��bd�r_�3G$��kr�ܣ���bt&fx5��h�i�6|���b���j;��<���bbi8c�k>^�$ĀZ�y�$���z���$��+���b�Fd��F�~���c��o���b��`f��bd����^l�bX"�Fd��ױ�d��`k�`kXi8NKѳ�`��o��$��?���'
X"��
�Fd*��J�3�����G��>��~�󸾀S�7�o��.�)D��9绘o��'��)Dw��'d�O��ʈ�0��I"�	;��/�)D��o �/5��h���N����X"���(�)D���'���)D��E���`���.��&9����)Dy�`�)D��`Ƙ��ǘ�'��ބV�Ș�'��:�Q��Κ���)D��Dʘ)|���&�)D̘o��.
 f�->��&͘�b���&Θ)|t��i;f#Ϙ�'И)|��5���i�X"�)D����Ԙ�b`����X"֘)|��k
���À�&T��ט)|ؘ�b٘�&ژYdۘ�'ܘ)|��bg$g��Ydޘ�&��a���)D☁bd��%ĉ�&V�)|�Hv�)D��a����b�)|��'�Ӹ%`j��b�ZvQ���阁b��al� �)D�Zv� ��b
ԣ����O���)D8
��Z&�X$��2�Zv"�a��
}�)D���	�0"��&w	,�)|d��^�K��)|��a��,�g��,����Yd��)|8�Yd��Zv��)|���'�f8��a��2����)|�����b(��2��)|������2�,n��b�,�ݦ�)|�Am� ������((��2�,k�yb��^��$�|�<u�d���	��$��m
��$��b�N腤�D�dg�<��>�D���|/���m8
�be���,� (�� �a=��{��$@��$�Am�b�f8�f8�,@����{�m8�K�X��q�j�#��8�
}l��`ʍC��Am��'����&��� �x��'[����$����K���� (��2���'$��}�� �"|�ʀ�,�) �, ,Vd>��~!�e�k�yb�"D�� ��7n"�)��,Տ�#�)�{�)���$��$����yb%�"|o�yb&�8���|+���'��_t�_�"D(�%�S��$)-|m��8*9D+���+-|�]d)9DS��$耢��y,]d--|.�b��8,9D/��$����Cm09D�-|#�x1�82�)3�Am4��`�9D��`���z�mj)�� 5��ⴅ�`6�-D7-|8�-D�fx9��`:"D;9Dm�o]d���'<9D=�-D>��`?-|@9DA]d�9|݀KdB�-D���'��j���C�-D.�&D9DE-|p]dF��`G-|fxH^vI�-D�8:J DK�KdL��'M��'5�-DN9DO�z��iP���Q�-Di9D
�;|R�zS�9|Ё�݆ DT��`U�)�	 DV�Kd����3�id��W���X�)Y�zZ�"|[�-D�i\]d��&!��D�b$@+a.nd��]9Dd��^ D_-|`9Da�i��i�<��b-|c��`��|d9De��`f-|g�-Dh9Di��`j�-Dd]d�9Dk�b��i��l�f�moظn��o�-D�Jm�op��`q��`r�-D�:nl��x���~� Ă:ns�����+_��6Q�-DK&to��E9u�-D��v�-Dw�&x Dw�U5w��y�Kd�.n*��`o���z.n���iƁzT��'Q��,�Em{����|�z�.n��mx�do��}�&~D D�c�}�Em���,��,Ă�c��zv�+��<n�ꣅ�z���ކ��f��$���z��D(��,ց�bĉ:n��$+��,��ҷЁ� ��Dv��f��:n��$�`8ŋ�cfG9X��c���z��Ʈ����m��c��D�`8}���֎�&9��,`8���,5`8o���o��&D����
�$`8�o�o<u,]㷒��&��|��+�o9H����X�`8� ��&D�	�Ȝ���b��z��zwg���ߘ�&D��`��)gu,��X$��&D��`��`���5��&o���&D�=|��{�����be&9·�n��I9��`��$߁�&���@��)������1|���b�`8��&D���z���z�<��xv�`��`���&D���|�`8��a~��`��$��b��?D��=D��&��=D��?D����`��`�8�D�=|j�K�=D�&B�;�G��x9�=D��D�	R��8���&���&%�`8H��xk�?D��?D���&�2D��a~Q7���b��a~��?D���a�`*�&���=D��i�=D�2D�$|��bę=Dř�&*�C���`��Yݐa~Q3��$|�{�	�)��?DQ�7�)��|H�`ș�b�2D�=|��$��$DX�;���$ိ~���5��$i�`��a�$|͙�aΙHmy2Dϙ@d��$���k�P�s�)��|љ�a��ѻҙ?D�G9ԙ?D�=|�a~��&���a�2D�OdؙHmٙ9�ڙ?Dۙ�a�	���e���&ܙ�aݙY�5�=Dޙ�aߙa~�?D�t>���ӏ��<����?D�?���)㙧��a��$��+�2D噧>�=D����d�y2D�?D5�����a�٣�)Ꙥ�X�zK�2D��z� y2D���Dc���ܣ�[�ϋ;g����&�2D�$|��z�>o��'���{����Ј�Ir 3t&���F��F��	���'���a+�Hm�C�*)���⣈�)���a��$D���Լ�&݀�a@�)��g���I�I�x���g�,�<��f���|��	��"g���a>���W��Hm�"g���|k�x,���a�"g��b������a?��"gQ������b�(h��u,��"gj�g�t��J�u:���h�"g)�Cm8t>���
�+9������aU X�W1�� 'h�"g�E����o��� '-�,>��x�:�o�v����>+D��(j��΀�zO)�J)��|��>��J��z
�?���8�x���z	��a
��z$jj�9��3����Av)A��$i�	;
�Av=�r��x뀍$L��a�[�+D����ի�����$�"gm[�Jm����WЁ��xo�o��$�AvQ7���$&K+��dA��$�����o)��$�P�Av�Ĵ�"g�����(�"g�Av&�J9��$VDd6|h�"g�������|�y>a�e��e�93�=�e �x��!�o*���Î�$X��"��$�Avd�#�a$�o%Dd��_go&�Bd�
+D�
)'�搋6|���(�a��8o���Dd��b)�Bd��^�)���*�a+M,��`])ho�	 -+D�[�)@m
����.+D��</��$����Av����e������`5��$)��${@�0�Av1�Cvڂ	2)D/�Bd��&<�Bde�
)/��$3�z)��$�	O	 脼8S���h�Bd�Ddo���4�uC5�)I�Bd���)D���6���]�)|�p>���k�z��&X�z�a@�����7t��&�oi
��8�&f�z{6|9)D��!�J��c!�A�!�:$'뀗d;��Ȁ�^�)|��a�)D�:�<�6����p�)|��Bd���=�o��)|>�)|?$'��|��a@�a=�x,��aՂ�`A�`�?zB$'C�aD�`�	�a�jd��^E)D�7:4�BdF֌G�*���&�)|+$g׉پ��������})4��b��%���cH�h~I)DJ�EK�`L��վ�q�M)DN�aO)DPZm�u:Q)D���Nane��h@��R�`si8�)DS�zz�aT�`���c6�fU�aVEv��W�a��`X�dv�y>X�Ư�)|X�`���Y��b�2(�a��e�Z�)|J�a_��_Σ5�a��`�2��bĄ�'Հ����"���h~�� 	$'���bi8���Ȁ�&�1[$'l�O+Evw�]���IL��\��]�a}�/D^�a݀Ami8U�a_ �g�`�`a �Amb�`k�q,c����ad�)e��e ��8@��f�dg�a�h�Ami�`�/Dj �)���{V��k�bA c��lFd( ���,9�9� z�Amm�knFdoFdpj*d[�q��'?�+(��w��$m��rFdsEv8�/D5�ct�`
�k��bh�$u)Ёq,vFd�"DwFdx jGv���b�EvyFd��0zFd�?�N{EvS)�Am|�x&�`}�"|)=	%j"D~�bR�aʇi8����-|b��d���,��i6��/D�;D� ��Y�4�� �)��݇�-|x��;D��
 � 3������݌�`(��N ���ݍ ��f������< �`���'W�}��� t���Id�FdR�-D�馓�;|ہ�Δ Xm�j*��Id��k��-D�-|��f�Fdk?����d�Fd��;|��$]��b��f�Fd��;|�	o�)�}��G"6�;|��|���:��	o+��� D��b���&��f��-DH�i��;|����:�<n���'��;|� D��G"t�z��Fϯ�f��Id��G"8�f8��;|��G"��Zv��}�ش���f8��i���?�<n��Hv����b�d
���=���Hv�}��Hû DQ&��f��i��>��f�;D��C���'<�Hv�h��H�*o�)3��0��HEmšfÚZv��=�������ĚHv��H�(�h�b�Yd��Hvه�|Ś�{��;|��?ǑE�7�E�H�ƚ�aǚ-D(�H�[�	oȚ;|��)'�HvɚE�y�f����	�#�|��Em�m8�<n���7o˚f�EmY Do�4},�|��m8�ش��8Κf��z�W�;��i%��$�<n�C�Em��HË��$���EmԚ8� D���X�8֚Hvךm8��|g��
�Hvؚ_w��ٚ)'I��e�6aښfV�f���|*��?ۚ���H��D��,{��${��$��TT��&��m[�ޚf9����Hvߚ��ʋ�|,��D8��-�[��D��+(�H��D
��oh���&D�[�����t�m�f�o��*��zl�.XU��&��z��ܜo8[��Em߀)���dl�a�m8L���Em�d	��z݁)�	;�Em=���&D
o	�fO�&�TT��z�E�	�f�)�f�)'�Md�f�X��2�)�f
o�)�t>���z{��$�2�[�)Md92Ä��zH��`�[��]dO��`�t>��2��f��&��&��?D��n��ˀ��m��?D��)��Kd`���.�)��Kd�
o��?D�D���J"��Kd�D��k�?D]d�Kd)Md�{�5����D��|�߂�h�Kd��`{��c[��?D��h�	��`�
�{���z$|
�)�KdȀޒȀ�Ł��`�$|���8�)��?D
��z�&D[���G9��z��)�':��h� �?D��h�8]��z�)�?D� c��k�4��)Ђ@d�@d��'p�)w�Tٸt>�f�Kd��{�@d�t>�� $|�N�t�MdT5Z��`�?D ��!�@d"��`���`#9�{��+>�?D" 3$]d%�Kd&�?D'� O��`(�Kd\�|�����k� )�Kd*�?D+��`,��x-�@dX��hZ�i��-3.�?D��,/��h��0�?D1��h2�Kd3� 4�Kde[�_���5��kw�E�ܒU����hr-g9�֜���6��h7Wm8��h9��xȃ�S��\m�|����x���d:��t[��!!��ݗ-gwt� m[�;��x���<�@d=$|l��>�@d��zz�	$|���w���{����@d?�
׌�@��x��+�|�	�R�	A�}Î�iه@d�
ۯl� g*:���dk��x)��:�[�
Xm	���)��:�;TB�z��Ɔ��"gC�@dS��'D�@d��<���d������d�ցJ�E��D�"ge���F��&����G�ԁ_�H[�I��bJ�z��DK�lL��D\�zM��&
u���D�8:H��&�*:Ӄ�D���&�YN�zO�`P�|������!!X��Q��&4�=�e�R��bS�ލ�$J�X�L��DTOdU��bV�$���&W X��D�oYOd%�4�Z�$[��D; ���&\�|�]�k^Od��h�`���e_�Cv�f�`9DOda9Db�`c+Dg�`#d�`�$�k1��e9D���xe�Cv5 �f+Dg�|h�#ui��π=j9DkOdl�$9DmOdn�Cv��+D��ᴊ#u��a�+���&bOd�7����&o�`��bp��b���xq��&r��Ds��bt�#u��bu�Cv�"gvXmw��b��2/g�!�6|O��&�Odx���y��bz�Cv{��&L�2|�`e[��3�vأ���-]�`Y��h}��bA�fƁ��r��_��~��+�>q�OOd�o(�Zm���`>�ꣻ$��`� \�Zm��`1�2�=�Odm�"���$o�ʣ��Zm��x���xh��b���x������'�Od��Cv�2���`�+D�)D�Zm��L��x�#u��R݀Zm9�{\�Zm��Cv�)D���$�)D��Cv��h���x��)��Zm��#u��\m��t�l�Zm��#ua�G%��E�!洔�>�
Я�>.���b	 r�����.�!)D��B��)� �)Db	 ���$���艂)��E"+~-�)D���'�[�{�#۞����>���z��$���4[��j��=��)|z�f9��ډ�z�f�S�)|��
]��`��h8[���F��D(e���Zm������:��'m[��z]��)|��b��Zm��f�Dw���=�EĬ
2��f���2'9w3��9�)D��2'�Zm{�D��2*)D�|�6��b��b��2�|�N�3�)D���b��$���V5@�x��b��Sz�)Dw3Κ�ͩ���)D�f�
2�u:���ft�.��{[��ͷ
2�|��)|�P���ze߂��	�z��,�
2���b��E����8�À|�o� ���{��dÎE"
����,g�`#>�)|z�-�[�>�� �� ���zm�)|��x�Fh�[��F�›�bfÛAmt� 
��aL�a��?L�L�	;�� �/|���5�?�

���dǛ�zț VÐ�|��fʛ�$� ˛�z�=��*�͛�zΛAmϛ|���f��bi�Am��=D֑�z��b��i��/D�ˢӛ�z��&ԛ�$՛�z[�v⁤cS��ݼ��֛ כ=D��/D��iٛ\m��hۛ�zĀj
���ܛj�9��Y��z��S��4҇�ݛ�z+�|���h�B+��h(�F�ߛ�z�$�����h��b"��e�d�}��z�^m����-���f�)�����z���'��z��-��}4S�^mv���d蛟$�
�N�u��ܼ��7��,�����'�
ˢ�f� ��it�d:�m���/D9�{� ��z�/D.� �=�%�"g��'5��iЂ=����i��h���$���Z�'�$-|��h�cb��d�-|��=�r
�.��b����z���⩀�D=^m�hO�-D��'���D��;|���D��,����h�-|��;|J��v+������D��-D�� ��,�|��-|�C9�� ���D0���m�f9{���"gl�|���y�쁡j���	��'9�-D��D$��D��b�b�"g�C���D�;|�
Ca��D�-Dn�Ѐ�a��=��v���j8��|����D	�-D
-|��D��-|�	o��*�=��t�d"�;|��+�����|��M��h
���.���e)9�;����h~
-|T]��8т���-|b��d耏v�i8Ȁ�	-|�����l��4����8��b��D��|�倢xЁ�ۮ)��-����D��D�8���a[��-D�->i8����D����-D��x��D��j�;|k��&��DEm
fi8o��o�i8�Em���?	�ƫ%��$g�v�Em��,��8��>��,����Ɓˀ�ݬ
���z �f
��s�i&�h!��,*��x"�`#�z����4Em���,$�{[�%�hX��h��{�c�J�&�i*��z&�+gЁ��:�z~'��x	�vk;(�8�)&��
�k��n���a�%$�h��`*��,+X"��b�=�k8[�,�b&�*u��o-�`.i88�T2/�)���9X"�o0�8��`��-1��:B�8�t�'���d2�`\�+�-��|����z3�zZ�h4Xm5�byWv����-�O?k�b��~
	�>��݄�-�
o��/4Em6X"7f�k�i8��y��=�8
o�Y��z9�$a?�%�U�ɃBd:�Y�	E��¢;��~�� ���?<�D����=�$���>��'�
�D?��,9��,@��,AMdB���Dc��h:�i�p������ւ�"
ot��+���C��hHf�
oX�9RD�?DE��hf���I��hFF�YdG�DH��h��c�`�Am�YdIX"2?���DJ�x���'Kdw��DL��h��姆�5~~��YdM�x+���"~~�Xޘ�T�X"2 ��'K�?DN~~�рO�Rt�d.~~/dwP~~����Q2DR�YdU�����aSNv|��-��ht��`k T���$�o��G9T�x��Yd��Zv�����'��DU�ZvfVdw��D��6c�Yd���W�����fX~~Adw��9v��?Y�ZvZ��h���`��Cdw"�Hm[�Zv\��h{�f]��h�=g���z"G9� o��&^���~~_�Yd�#`��ha�Zvb��hc�Yd��]�d��zd��he�Yd9X�f�?D
�Q-g�$gj��'h�Ei�[d����j�?D��o�Yd��	k�Zvl�?DI�Ɂ# ������>Ӄ8v
~m��>�d_�Ԥ�р��vn��-s��o��>p��`q�xU��ir�-sNvt\vu�)��	vNvw\v�~~X��c<u{�t�dx��ݺ�m8y��w��B�m8z��>{�������.�8|�Zv�Zv�d�i}�8~��>�o���d��`q�����d/����ƀ"Do�̴(��i��Vp��:�"D
�=��� �c���>�)|�"D:�8݊Hm%��c��4�8� ⁤c�m8��&���z��"|j�f���DgX9��\m��o��DY"DlX9o=g���D�;'geedр$��D� ����&� �U~b�h���D��8o�f�h���>t�d�
f���"���`֜�� ���>���D�]d���,� ��Dw������>;�f#���D� �,�]dӐf�9D��i��D. �9D>�"|. ��D�����h�]d� ��h�� ه�{g�E2��hU�,0Z�����$��)��h�,��h� �j��h�,��d���k�f��]d,��/��$��h��`���`�]d�"D���`�[����`���D�]d�[����D�uCw��`�=�$��D�J�]d큍$ 	]d� O^v}o��D���`�^vw�66� Z�
�� ���D� h���� �o��h���㄀c2��Zm���h���`�o<���Z�´<��,��h� ��hهc%�]d�]d��h�o� ���i�X$�(u�]d�]d��,��hœ)��`�]dĜ�`v�)�]dƜZm�UAd��?�)�^v5��<f�i?	5�ȜheɜZm�@+˜)��z�����h��$����)���,��%�)=��7���͜)��Zm�he0���Μ�ꢊ�x���z���d>�ϜE����ќ�&���%nҜ�&��)����Ӝ)Ԝ���^v՜��Zme��&9��S�[���&r�_���h��ݘ�w֜�bO^vop
��ל�&$�#��&�@+��bDd�@+倃b�:�X{��@+>�i��FP�eܜ�&��������D$�Dޜ�b�f���h��D
{��J����Df�2�`8=�I�o�¢$���2㜃b��&�f_�;��D�Zm蜃b�&|�)�`8���f�&D�f��D�`8��D�f��B?��'(�B?�&D�ֈ�����D�m1�0�����&��`t��i�X$�g��f���	;��f��&Dc����&Äiw��̣��&D��5@���&�5u�2S�`���Q�8�UvI
�	��1������i`����&D7�p��k��&fו �`�&DȀ`8o{�A��i=��f�ne�D�� ��b	�DJ��-
�Cm<ۯ��b[�� ��c~
�a~�f� ��|f<`8� �D�g���� &��D&��5ur�+I�ɁQ2�B?5um��=D4�&�B� �Vd��Cmt��dd�\�$�a~��� ��B��,��%�iVd�&D���)�iml�&D��W�&�`^m��2��<��,�&D�
$gl� X�wU�&D{�f�b:$g݁Qm �)�$�$g�i���$�5u遆$��Y!�2H�ݻ5uه=�"�`#�k$^m%�&T���i^	�c&�f'� h��$g�
�+(� 5o���.���$)�(nq��O���:d� ����#��>Ҁf*�Cmy�=D+�a~��x�}m� ���bڋ��,�Cm��"g:�b-�Cm.�a~�N%/�%���$��	o.��N$gU��?$�g-i��$t��d��m�b2�}W�f0�2�mw1�-2�i!3�Id4�i�>?���d5�&�����$6�;|7��$8�&,�/$g9�b:mwq�R;�b���d<Vd=�>�f�	o�?�b��P@�f� ��"gA DĄf9��$)���gc*B��$�k�\mĀ��C��$D�f��iE D������JsF�-gG�h���a���d���H�hI�f8J�;|8�� K Dm��i_�Z䂂�$L DM�hNmwg
)O DM��i�a)g櫀ɁU$O�Xv5��P DQh���bb�R D/mw��bS� |�b�=���5�{T�=�U��zdV�hW DX�	oY� |='�����d��=�Z�hv�o״�D�[�b�́\�z���)�g$X��]GmՀW����d�zd�^+Dܒ�_�Id`�;|����a�xb�Idc�;|d D�́e D��hfOde���� |g D��)� D��zo�ɴ�r�h D���Y�����
 i D`�|���d�+D��+���$j�zt��k�zl Dv��Ym�zn D� |K��+o�z�
�h�+D���p��+q�zr� O�hs�it�zJ�h_&g+�-g�UBu�hv�c�6Tw�zx�c���d��2m�,�}?�F����/�zy����W�k��}z�c{�t:��_���v�̣*5@|�譫�A�
)}�hb�{~� �h�+D��c��=���c��zg+D�Gm�����T��qo�x״%_�Gm��h�Gm���b��"Ɖ磊
oD������b������xj+D�� �+D�ݬ�i��,�Md��E�J��_��z���b�� *�Bd�MdXm#p��$p�}���$�Md���d�e��^�]�v�
o�ɀ��2��	��h�H�� ��c�f��#n�����Bd�}��o=���Md���㘝�b��d�v���z9������,�MdJ��v5,��?D��cʁ)g�� �ƞ�#n�}j�m#��$��,_��kL�dԊ?D�ց��Š\v�<u5�8�Md+$|�� �
I+��)g�$|�Bd��?D�X$���&{�o=����$|���b*dw���+�蜦�#n���)��d�$|�\v��d��7�7:�$|��`=s諝�xO�o��[d���+�$|�����#��b��$DT״_��v!��Y�dw��ˢ�� i��<��$D��@d^$|D�����Md��i�\v��$D�i8���(�^���$D�)g��?D��Y���:i8�$|�ee�i8��#nd�v��ીɁ��Ea���Jm�����$D5�ݽ�[d�i8u��x��j��V
���a��N%p���?D�j�m#ց����e�t�j�m#�����!n���<����|�i8��/D
L��$|o��xt��,��D��|�$|���i�����ƣi8o��
��#�\v�$|�\v�6���&�$|D��+jf�CJȝ$D��/Dɝoރ���ۯ	�v��8ʝ$D�t��,˝�xEv��}����"D��`.���耦�m��x��!n#�Jmj��<ۅ/D��ʴ����@d��7���b�3��$D��bН!nV
���i8o��z��<�	�&�"D~��ݣi8����aӝ�z��`�i8>����"|gee>��(��"|�ۯQ���"Dם�b��"|��b.�>!
�|*ʒ/|5���"Dڝ�b��b��b6��dd��*�+R�!n��ʴ�|�4���!nޝ}��Oߝi6��b�Y��"D�/Dp�jt��,LX��!n��$��b坚b�!n�!n�"|��.&�IЅ��=)�{�8�`������"|�)ȀÒ��al�.�"|O���l-g�&ƈ�eړ�Cv�"D�����f����j*"D���'Wۯ��"|��o�X9흩c�{�����"D��h큩cJۯ��'�"Db��h�)���h�h=��w�{����d����큩c���da�-�c��"|���'���n���2�Fh���d��"|��a���h���h��zm�iՏ�i��z5��=�⻣�z���`��[��-g���]��-�o���`��>!�Zm8��cA�z�����Y�o���|��tq߂�?���\o�i�����z��>�-g��/���d��${�F�VeG"�Zm��h)v�!����-����iȀ���`�z)DQ:��Zm	��h*�p�
)D��d�J��h
� ߁	 �{�)D��c� }���ݙ�u{3��������c)D�EEe��)D��cS�&5����	:�)D� ��=�)D̸�)|9o��-Z�%o��f�m8��	���)D��m�8�)D�z{���-d�	��8�z�)|���d f!� X�m8")D� �)|#�z$�m8ʎ�%��O��&�m8'�)|(�z��z�� )��?��x*���e�*��d�&|+���m8,�)|r�޴d��@���d-&|��&��TA.�x.�Y/�b0�x�)DĂ)|%nb	)D:&|3�&��1)D⁚���ੁ��2)D3� 4m8��&D5)D6�&D	�x�N%��&D���d��&7� 8)D9&|:� ���;)D<�&D��'%��|=)D>�h���db�dF�&D�&|<g
���l�f���?�h��,Հ�=I�j�cL
��@�&DA�c��*�A��aB�hC�'D�&DE�cF�&D����)|����8��`���O�h�	� 
�xG�'ʁ&DH�c~B�-g8��I�&DJ�CmȀ��i�c�$m����c9�:�ރ��K�CmL��`��h�� Q��`��M�hN�&Drg�';A�CmO��P�)Q�<m��`���$R�CmS�)���$T�YdU��>V�)W�&��V1X�)����ch��`Y�&D�v����`Z�Yd��CmÃ)=�x}[�*n\�&e�f�]��`^�,_�&D��d`�)����a�)xb^m�':c�&d�&De�c��'��?�f��z"�Kdg��kh^m��da��'i�&j�}�eЎ)W�)k�(nl�ho�<Ȁm�cl�}�|n�&o�Cmp�,q�xr�&ە�`s�)�CmY��"�/���t��$e�Cmu�xv��h���e�w-|x�c~y��zz-|{�Cm|�}t�"9\6���,/��`z��$}��X~-|��`��&Ã)��Cm��s����$��Cm���$�-|t)Li���&��	o�o��&�-|��Yd��)��-Dʁ	o�-|�)��-D��IdD�	o|��nj�&�mwQ��񍞃b�-|��&��Id�����	��,�-D�����-De����,'���-|�{���E����l�.n��{wy-|� D���>:�}��-D�	)͚.n��	o��bY�}�Ca���d�@+���b��[�.n� DX���>!k��x�ze�
)���x��٥.n���ԣ-|�
)��-D� Dk}.��b"��,�-| D��+_��4-D�)�-|��L��:�-|���l� D��&���ԭ-|ao��x�.n�-|h�}6���P��
)���z�	o� D�
)�Gm
k�?�� |�Gm�Um�9D��-D�+Dq)�9D�Um+D�
)zGm�.n����߇�xUm��-D�
)��}�+D���+D�Gm��'�������9DF�}?��%�9D�+D�Gm<��,���x�.n��`�x�o��x5��.n���x_�?��&� Dj��,�
)Ȟ�x�
)ʞ+|�	�>� D	]dOd��x"��x̞f8�x-��ݴ
)�@z�7��xQ����}f��a͞}�
){��	{Ϟ+|���xО�'ў��+�a�+DӞ�,�x)��|���

2���Hb#՞}�)��`U��a{�מ)�)��+|�{�؞)����ٞ�$��)��)�ց���&�Gm�+D�o����+D�����aS�)���b�[Umy+D$��b\��$�c
��xk�)��)���xR��$�)�ى��&�q���)�9D�Od䞅��"|����+|w7���|���51��b�
ol��x鞭,�Md�|���x�)
o<��,���dp��a	���Q��,잜d힘bb�i��i!DI+*	 4
o��˒�)g>��a��)��~��-�Cm�
oj���}�Y��������֥�����d��b��j�v_���|�	ckb��|*�iAm�Mdb��ϋCm����`8�O��f���d��b��>�/�2D�$|���f��Cm��B�2D� 3k���)��b���I�2D[��&$|���d��b}# 3'�Cm2D��,}��`v�3��냰�������H}���$���u$|2Dz��b(\v��O�	$|��
�)�2D��[��Jm���
2D���|<״$|�-}.�����V
�x�)�JmMd2D$|�����|�2D�&*8��Jm!d;耯~�����x��CmS�&��b��`�$gW-��)9R���`�ۊ�)g���W-�߁=�U�2D��W-�&߁�2D�&�xc��2D�״�x�a2D }z�&!2D���`"�x#}$2D����%�w}&�/D���'�<��a(�/DQW�)$|*2D+2D�
�&$|B},2D[j�-}���/Dy2D냭S./|Ă�>/�i_��z��z�R��/D0�Xm1��`2�/D�ۯ�V 	�=D��ci�/D3�)z�&4�c5�/D6�b��7�h8�b�X9�;)�"gZ��m9�bS�&��b�é:�?5�<��z�&5�;/|<�b�/D�	״=�)>��S�&�o?��&��@��k�U�z�&A�b���|"|z�&B�'b��ݾ-gC�h
aD�b/|Ex*F��k�,��'G�8H�/Dc�cI�b�׸ތ/DQV�J�b��݁�&.��ݨ� K-gL��b������ĂL9m�'�hM��kN�/Dt�/D�����)���.O�/DP��k{ۦ�c*Q��k���-'��>���Ȁ�P���{Q��S�/
�'fۯ��bЁ��3��2-gg�hR�bS�c��q��T�'A-g��ؚ�cU)	����n���y��ܾW��f�g�`m�,V��zWNmX��zY�bZNm[[�\��&���|g|~���i�f#)����]��z^�_\m��"gG9�	)`Nm����a��hbNm��"�L9��?����\m�&��y���97(��cNmގ�zd�����(	��e��zy��hf\m��8g\m���h�Eh	5n��ޅ�z~h)���h\)i\mj�,\�z~�HdkNml�,m6|n��o��?� g���y��h׀�a��zp)D��LJ���6dq)D���W��{r����is)DЂy�t��hw���u)D1��L����
�v)D�ޣwNmxNmy)Dz\m�M+{��&U��a|�`)D})D~��ze���\m�����-����?�E��\m�)D���&�W���ay��h"�}�)D�Nm��}���&���&�Nm��)|(�}8�#��)D���h��)|�)D��
�ޣ�Kd��d�z~�)D5%��ߓ��h��)|���ݖ��&��hea{�j��<Q�θ��<��b�{��)|犣�g�}哾����&���-f�n������&93Ü)D��)|��)|�)D��-�5@��0y)D��F���j#�-��X�)D��Bd���A���.�`�)D�XU�	{��&)��[��b��b�)Du���	'�����Jm�)D���~ݬHm���&m�Ы�䁮�)|�;g���&���щ�xt�>��f�}ÄBd#�}���M��)|���&�}���<�?��~�s�t{�d�����&m��h�聴�	g�����<���<�gkk�U$R{����'����$���_}��=�z���L�|*v��'ʈ�a�����z���c��'��'�yȅ�X	{��`D��'���,���c���c��/���'w��k{��
)���h��q�5z;g��mv�'���ם�%���h���~~L����8n�~~���$��h�~~=	{���u!�����$��&�Vd�ޣş�c��&.���~~܂4Z=�e�ȟ&�ڏ�ݑ$gm��#����ze�~~9��,ʟ�c��\��
�~~��$��{K�~~ȁ���)�~~���8�a�)a���Vde�8ϟ�ad�6�5ZҀ����&��&G�/��̫��h,�ze�i8F�e��z��q��~~����gk�-|�;D��iF�*�;Dl�):�gk�e}�X^0�5@؟�h…Eٟ�c�;Dl��h��?ڟ�,۟�`�-|��xNݟ�,ޟ�Rߟ�c�c�$g�-|�~~�A%����~~�Ev�-D�~~�����&�;|�-D�	{��kꋲ�.�?Ł�<B<�&�-|�-D��?�-D��?��$�#�`#�;|��k�bp<nr��j�-|�;|�;D�-D��$�<n�}�-D���o��|� D�}<�k���݅��@r	Ca��V8C� D�-D�8V����}�%D��=�)�)$��b��.FAm�<n��-D��  D�f8 )y-|9�w� )�"|t} �Nc D҆X� <n } )	�"|
 }_�x }D"D -|Q5�
 -| ;D �h
��� -|�-D	} ) )Dآ�"|  D  D��i8��| Um��<Q) 9Dh�Am�"| 9D ).��| 9D8�  �c )�y��-D���<҆Y�"| UmO��`��i 9D �;|!�-D��M+�"|9D" �c#��b$ 9D% �c&��>��c��8� � D' <n( Z��c)��$* Um��>}d�xV<n+ 9D��%,  D��V�- 9D.  DLu~��d/  Ds�?0 9D1 )Q��2 9D�)=ŏ�ؒ3 9D؇9|eK�8�f84 �c��ʀ���|5�"|6��`�)w)�����B)�}#Ȁ��)+��<i9D��߄���O��7 Ume�a%��h=�f�p)���8 )�Hv���|()��c��"|��9 )�}m��3�): 9D;�}�� < Um�9D	;= )h��|> 9D? �c@�}�UmA 9D���0�Hv{B��-C �cD 9D�� E 9DF �cG�}[ؗ�Hv�� ȃ[Q��C)��E���c9��-e�a%��k��}u
֣��>H �bI��,��YJ�%�e�a%K��`L�%M��,l	{{��$�)N �b�)_��O )���P�}_��Կ�+Q��,�)h��?Ł}#Q�?D��t��+E�CmR�?DS�[dT�)gU��kV )W �-��bX�m8Y�?Du�b��?Z�[dE
�B��Z[��k\��bJ�m8]�?DY�,���|^�?D_ �ܮ�d`�)ga�jb�?Dc�j�d�}e�?Df��'g @ah��?i 2D�)a����'H��hj 2D�?D��)g���k $|�?D���\l��?d�z�m��b��}p��b���xn�}l�[do�?D���p �iy�`���xq�Xmr 2Dp�&Ds�@dt 2D�Xmu��b���<T��+A���v У�	�����S�@dw 2Dx ����j9�����bb��|���y�Xmz�){�@d|�Xm�$D?	��[�@d
�)g%���z]���?} �&ܚXm~�@d�?D�)�d{�&D��Cm��?D��?D� �`��[d��}��@dd��b��Cm���?U�?D��Cm� =|��8� �`��?D���x���b� 2D���b��?D��}���b��)S�$D��}��	}e��i���b>2D��	}���x� �&����o2D� ޘ �m�=D%���	}	&��a� 2D��&� ��Am� 2D�
�	� {�� $|��	}��@dD��x�|L�=D�/џ�@d��`��@d��䀿,��	}��Xm�'�� F���3��}8�xڥ�@d��V ��Xm��=D���x�ۉ��@dd�7穠Xmܚ)L3ê�@d���y�Xm��}���.9�=D�Up��=D�����8�@d.�&��f�-)�������.(�{���k��ݰ �&��=D��5���j� �&� �`�� ���jx�+J�	У[	{� -g���jS�Hm��-���j��	}� �-���,Um���b����$l	{���b�����=D�k� �$S-gc�����x_��X� i#�_d� -g{�����
�� i#?�p���	}
������!��% 	}àCvĠ�kk��A� -gƠ�bBUm� �bȠCv��$}oUm,���ɠCvH�bʠ�j�
�$� -g���	�$�i#̠Cv����8��{͠Cvn�R�
gˬ�$� �bϠCv�W1�cРCv� |~Ҡ;g��^� \m{��j��b��;gԠCv�\mU��bՠ�� Z9נCv�<� g� \m� ��۠�j������.ܠ�jH��.� |~��Cv���~��`�z�;��<w�PҠ�Cvޠ�`��(��b� Dd� |~� \m�i#� g���㠪j�oD?�� �`\m� |~���b�
�h� �b]��`�~���Cv� Gm� �����?� �$�Cv��-�
�-� \m� i#���D{�� i#�Cv� \m� �k�Cv� �+� �����Cv� 
}��� �b� 
}� )D� �ݮ��i��Cv뀄?�)D� 
}��Cv���`O�����Cv�|� 
}׀U7_��>d�!\mk���~�.�.�����`Od�AvX��
鴟��~!)Dp��諀�B!\mǑ���	!\mУ�Av!)DJ�ӣ�����
)D!+D!
}v�x3�hЂZ$	!VB��k
!\m!)D}�H[�]��`&'@+DF�!)D���pt�~
!�{�z� ���at�|j��`!&'�+|�
����!�b!&'���za�!u��h>�)|����!�c�E"i!)D(����Avk�h�|$m�!)D>{��U��)|ЁL%�����[���h3�h�)D7�o���m�o!
}!)D�-!!)D.�����!�b!)DY
}.����bZУ�{� !�h��Jm��^9!�Av"!+D���#�Avt��_��Bd$��b����)&'r��+%!�~&�!u� o�ie�S�������Bd'�Jm�^9(����먁Bd���<o{���&�����$�	))!�h��bn�~"�^9*��b(&'����-{�+�Bd�	)�[�0��j$
�y:w>{���^9��y��b,!C���&� �	��-��$o{�&'3	)��Fk{�'
,i�m#	�c��<��c�Hme�&�$g�W��fɚ�y��&���8��JmZm|��r���5��#��&���$��Jmk�Xm.!�鋀�b��q�|�|��&j�f���bm,\� 9���&����/�^9��$���i{O�0�Bd1!�$��2!�&3!5�4���(��h��5l�&,�&��h5!�&9��6!�k�Hz����,�� ׀�QS� .��-ʃ֣>��h��7!�&8!u:��&l�?9!4Z:!�x���h��h���8��{;!�a��x���h��k����
�x���Ё��}�-|�	4b��c~<��h=!��>�E�[�?!-|�}k���@!}A��$H[�B!�&s��$��{C�m#���'�)���qFPjD!-|����E!�&��%4���w��F!�&G!-|H�-DI�;|J!}���5��dK��b�&1}L�-DM!�wN��bO!}��hP�;|Q!�`s��Q�2�R��b�{��!
�-D��-S!}�%T!�dU!��V!-|W!}X�-DY��bZ�;|[!Nm\��V]��b�|^!\m_��b`!Fd�"D��b5�;|a�/Db!Fdc!�bu�(d!�b<��e��bc�tIf��bg�-D��&h!�`�-Di!�b�Amj�-Dk�f8�|l!"D����p�	�m�"|n!�x8
-D}s�bo!"D�Fd��`p!-|p5@q!�`r!Fd�"D��?s!}t!\mu!�bv!Nm�}w�-Dl�b��x!}�{�y!�bz�-D{!��|�"|}��bh�8I�-D��~�����i6>�"|ۄU�!�b��i8��b��-D��V�!�I+�bod��5G��-Db��b��-D$�0無Id��-D��;|�!�`��"|���b����ѐ-D3���I��b��cej�+'�!�{�!Fd����h��h�!Fd��A)��S�"|���h�!�b�̸���c�!����b%�ܒ��'���c���c߁�&�!�b�y�Fd���h�F�!Fd�
"Ds��c���'��|߁
)���&�!�b�!�b�|΂�h��4���h�����"|��b/�ce�㑶�	�� ���&o�_����!
2�����c4��c.�)��s��~e�!)�ٞ�G"���&h򭠡�d ��&���'��_R��t��i�'݀(u�����Hv��G"y)I����բ�(uk g�(u_[��!�{t��%c�(u5�+,�S��-���h���h��i��ע�+'���$����*��c�q���*
��������+�Md���-�(u��ce�!Md�{��� ���h�����Wx��(u%��h��ce��G"�!�`��B3�H���{7�oÄ�c΂�,��ce�!�b���c@�m8��f��ȕ�
:�*z)��!g9z9V��갡?D����"Em��?D��b���k�EmȀ���
���-<�b��p��?D��!g
&�����k��x��?D	�?DT�bB�-����!~e#�x��I��HveNv+�?D��Hv��(u�!}��(ut��_�UK�!}t
�f
����X���&��!g�!�-���l�!}Q2D�!�`O贡�`¡�&���G��`�!}_�f���9ġ?D�����iš�c�!`#�!}�f�!�&ɡ&D%���o{��}�}К��X�`���9;Zmʡ�`�{�ˡ�z���̡�`�����!}<�-p��`�!Nv�!Md�!}ѡ?D���z�!U2�!}ԡ&Dա?D��j֡&D���`�*!o{�סfӤ�)go�?Dء�z��$١�`ҍ��b�<ڡ�zۡ!g�!�hĂ&Dݡ�k��9ޡ�`ߡŠ�?D�{D�&D
��k,�&D8�f�?D�{����!}쁉k�{��!�.|Rɼ��}�F��!Nv�!�hh���2D�zO�Ea�!}��`�Kd�}�!Nv�!�xNv��P[�J/'�!}>{�	��fښ2D�<���`�!}��YdQ��`
>{���zԀ�o�S���9��+�����`���o{���`�!�f��+m[��z��`�!'Aŋ�`{���Kde��z���*��v���Kd�|��Ydt�<��0'���z���Kdt
�j��&D���-h��HZmt�i��<�$�) (�0'
���+��0'���b;��ȫ��.��bt��p"�c"�h"�`{�h�h)�0'��b"�c�$!���-o?�h-g�{����YO��0'���b��&��-y�Hm	�E�'��#
�0'�KdQ��b�-g��b
�kh-g+��-
��`��`��b�)"�b"�k��Kdh��|�����>"�b��b�Kd"�bP[��i#��k"�b8[�"�h"�$���o�ӣ��b	Zm�,垈b�-g>�-�mq��݀Cv��Q��_r����,"�b�-g��;g_�Q�G	f"�?j�b�0'��i����9�>�bw�j� ��z!"�k"��z'?�#"Dd��ce���b�d�$"�b�@+��(%��k�UQ{̰B��-&�$!��b'�0'��?�("�b��f(���)��ze�ȶ*"�z9�;;Zm��-+"�h,"�kp��-��h.��,5�]�/��z�����-g0��b�b���h-g1"Um2"�$�%g�����>3"Um4"i#5"�ko�eUm6"�b7��h*���8"�k9"�`5���b���x�݀;geDda�}<���L��:��.�x���bb^c�Odd;"�`<�}
��%="�zgOdT�P�k��h��`.��h��.��8���>"Od?��z@���D]dA��xt�i��fC[�B��xC��zt9DD"��_���E��z
��F��x{�m�fG�	}H��xI"�`��	}J"OdQ��=Od	�f���K��x��z*��hL"[�M�	}�:�[��9DL$N�	}b�;'dd4'�-��cO"Od���X���x�	}��`샻xւ�-׀���q�4'P��hR����9Dm4'Q�	}r�
=u�&R��x��{�j�r���f��iS��-)�`T"�`U�C�V�}|��(�9|��
�1[�W�ԢX�	}���+�I"�x�X���Y"OdQ�XmZ"Od�2["9D\��c]��c^�(g_�Xm���'�``��b��n.��ɸ�fa��b"[w��c[QҖ	gc��x�jd��xZ��%e�}X9D!��bf��c!���	}g"�jʐ����譡Zm�j���+a�1z�M����h��cȒ��i�	}.�;'�Q��)!(��jj�	}���e�!	 ��%��ꇉE9��٪�}k��b�)!l"�im�}@���n��br�Cmo��cY)!���p�Cml��$m4'Հ��)���W~��q��,��$���$������&�h`8r")s"�j����i��$t")u"�hv"�������(g��J�w"�jj�%&��&��Xm���fx"ney"z"�j{"Vd|�Xm�)}"Am~�(ge)�����$��b��Xm�"Vd��(g�")���b�����}]Vd4ne�����/���	 �")�)!B��cb`8�")5�<o�	������b��$�`8���c��a���>�")��7:���⎀�>���>�"Vdt�㭘[��"u:e�}΁��Y�f��i���c���>�z%�"Vd4!��	��"�z$�q����,��$S
�i�"
}���>Ё�W�"�#�")8�f��a���%Zm�z%�"���>�"
} ��/b��kw��>��f��?�����5;
}���>�"�x���$�")�"j���=<
}��H��o����i#�)������+��Id��o�������Id�"Vd�")�?����2��Id��=D��;|"�o�"Vd�"���i+��b�"VdCJ��i��Am�"Vd���>V�F����>���p>-����o�"}���>�"}�����ZmR�/DZ�\m�"�h��{�b�"}�I"�"\m�"�b��g˖�&O�>a���`\�b�"�b���. �bw���d}���Ԝ�b���=�w�"\m���$�"}0������*�+u�Xv���`�"�h;�/_�֨��"
}���`��x�"i#J��J�"�b��2{�"
}���i�"
}t��t���"�b�"
}	}Ào�oŢ�`�"�� 
}���H��oՀ5�Ǣ�`k�&�"�b���`��&F��iQ�γ�&�u�r��`ɢ�`�	)�F9߂;|ʢId�;|ˢ�W���'��<�"�h���"	)΢�bp�٪���YϢAm���b�F��� ���oТekZ�-{�{��yKѢ�$&���Ł�$���z�"��	}遝$p�i%��$�"�h�ƗY�-!:g�Ԣ�`բ#���eگ��$�Zm9���"�bע�`آ�,�"}���$�����"�b!�#��b$D5��i��iۢ˸t�,	
;�zNܢ�`��W��,��ݢ�z$����"�,i��$`�v�ߢF��"�c��f���iR��io��c	_��$�"�&9�Ҡ��|e�9������$��ie��i
�c������?�����<w�֙ބ�S�D	�Xi��$j��)��� ���+�	)V
�&A�^9�"�c� UJ�[��f��<�9'wFm�f�"Zm	�5��"Em5�#�^9\�"Md,��$�ô�[�)�^9�"Md��݂��$��d���1��[���$Ȁ�Md��`8��^9W��$"Em}c/u�Md��#�"Em遝$,Em9v*���%���h��c5/uЁ�%�"Em�t��"�&�?D��c} ��h.�S-�7FP�"���"F=��EmJ�G�"Emo��ht�,�"�U6��p��ց�<�"\v^	�-�"Mdm��x�'�"�d)��9���O��?D€��"��o�?�"\v��f]��e��m#���j��iEm�)*�9	��^ne�"\v��C��"\v�X"�	���x��yȃ{�ҴQ7:�\v#X"$od�`���^9�i��w�I9#�`0��#���(,[��x~#�?	��z�������
��x���#�`#MdAne
#\v聢x�n���Md#��]Em�� ��(���<�ǣ��b}�'�='��?D,����-#�`v��J���*�k��m##�-��X��?D��[d9��<��f���o�� h�`��h�?D=����?D�m##�t���<��f#��#�`��9S�m#�)���:i	�`⁥�o��f#�? ��x��)�7��`�Yd��h��?�����&�[�uҴ/��c\v��-#X"��h�YdQ�� ��xm��h�"'!#���c�X""�)�Yd#��,_��8��&$�	}%�ꖵuR���5��À� &�F+�Cd'#�k��Yd���c5����(�����h�P��Yd&
�j�c)#�c*#�k�Jm+��-& ,#�k-�	}B��-.�Yd/#�k��.��߅�ah�Zv��"'�ށ��٫�3���
��-0�Zv��kʁi
�-1��.杈b��:u2#�k3#�-Ai84#�>5��h��6#)7�Zv�8��9R�:u9#�k:��&�h~i�)���9��fJ�ZvI��-���&���5u�2;�Yd�Zv<��&=�)�]�>#�b?�E��_d��@��-��d����A��B#�)��9��C#i8D#��)E�	}��c��z)�yi��c[����h׀�7��cy�;gF#i8G��c�H��cu�bI#Z���mJ��c�<���j€���i�
�f���jK#�k��xL��c�i8���j
�(��x�<�M�ZvN��jv�D�x��xO�Zv�cy�kP��cQ#�x{�]d��j5R�� ��G�S#g����Y�fk��j��J��/T�I9�������۩��U��c�i8��j��	�z3��H��?���c���z�)7�xV��?W#�N.��c��5`�
��X#8Y��?m�ft�x�)�{	�XZ#�b����$i8[��j;��(v�=�_d{��~��#l�${�)��*�bX9��%�f#\#�y]#�n^#�b.
},
�h_#]d�w�`#]d���m�
}h�`�F�a#]d샣j��h�
}������.��fb#]dc��?d��j瀩ce#�hf#]dg#9Dh�!g܄�`���N���f��>x!i#]d 9Dj�"u��vk#�x��V�l��>m#9Dn�)uo��`p#�hq��`D�,r#]ds#�x�]d�Ft��` �}�-u#Dd�����`v��-]dfio}w��`�-x��`ٖC�y#]dz#
}��X7�{#^v*��?|�Em}#]d
�~��ސ��-�f#��?]]d���>:��`.)���?T��:aJ��>&oĉ�f���>?��#�h���m��n��݄����t�7i����f#��	�#]d�ͣ�9Dp���#]d���c�#��e)�	xV�#
}v^v���_y�h��z�����	ٍ#]d.
}������`�#MS��c]]d�#]d���c�#�hy��`�#�<���c���b�#�h�Jm��ܖ��c�Zm���b��e�#�,�#�c���>������c@��`���-��`�#�c�fg�)��)u�#�c���-m��ȟE9�+D���M�fN��m���c��)4�c�@9Ă�,�#�,�i�$'���#�cD�,���k���bJ�m8+�)>o��k�h�m8ń�.D�������j'��Y9����m8.��[����cv� �*�����z�������ͣ��a,|e`8��j�ی���b��m8�3G�#`8��bQ��v� 諣)�|e�#�$��>~�܈��b��
٭��b���������˶��2{t`8o��h���b��e���k��f�ޣ��c�Iu`8���'���c�`8Y�����|�>�#�c_4��:��'ò�)�#�ce��m��&�#"D&�,Ą�h�)�����&�'�W�c���#�c��k��)��c~��c��i���/�c~���&��j����#-ue-u3��&��)�������&��E���h��=L��hЂ�'�"-u�/��c~i��$;���#`8��"|��_�#`8a��£�k(���p���ã"|:��:��&뀸�ģa~?�,��"�ţAm	-uV��hƣ}ǣ�xȣn�ɣAm�#`8�	}�#"ḌAm�#�d�
����vV����h$$:�����Σ�bϣ}��a~У}Q[�e�ٸ�Am���`8ѣ�&ң=DӣAmm�ԣ�&�}գAm֣�'ף�nj�Kd���h���@�#�S��&�#�bڣAm����|ݐ�&pĢۣAmܣ)4�"|ݣAm�`m�ޣAm5�"|J��\ߣ}��s�	fm��} �"|k^m�Am����#�bm�f�fr�:gH�ݎ���#�e�#Ģ-u�#�W���j裃Q�#-u꣪jf/�q�룪j�#�b�P�*��x�6(�}�~1,�f��n�Am����Am珥�~1t�|��Ame������g~Y��J����k�}���b��$
�:g�}�a~e��jb�k�Am�j��}e�g���}��tm�AmA
���c��#�kp��b9�V�W�f�#�bƁ֜����#�k��e���j�#7�#;!�c���X�<���#����j��,$C�����k$g�$����$�2��$�b���ȃ�Ģ2��:g׀
������,�:gt�,>��Q��	$�,9��c
��j]�c�_や�� D��,���a�Q��F+�F�$�c
$�,$)!�
�-j�c$�`���$�cʋ���j$�c$�k��6ͦ��j�� D��,5Cd$)��&D$G-$�k$�c��ik$�k�	 *�,Ɓ7����z�I9$�c��:a$�k(s$o@z��$�k$�,��z���$�q�p�&D.��b�,9zt��x $Em�!$Em"$=u#$o$��>%�&DEm=u�
�)&$Md����
	�`b�(�=���'��,)���($Md;��c��)$�b�/*$�cu��]+�&D�Ģ���*�xy)#�c,�[dzD��`w׀��-$Em� ��ՙb���.$�t�,/$�c���⫀�00$Em���x1$�h2$�,3$Em4$&5$Ģ�,N�c���j6$�?.�O9�Emm�-*{}�Ut����
`7$}�Ģ8$Em9$�?.���:$\v�f�fq4�4�
};$�?<$�h0�t3�}@�&D=��y>$}�
�j�??$w�����x@$}A$�hQ}�{R�"\v�oB$�'y��3C$Em��+p�&D9EmD$�?E$�`F$Em��hG$}��c�YH$=u��xI��zJ��z	�?K$UTL$Eme=uM$Md����� �N$MdO��z�
2tzee=u�}���eN'�P��z��j� �&!�>��b�-����Q$��u����zKւ�`u&�	�-R��z��$�-p�	O	)B��g�
}��-��մS���T�[d��:U�BdV��z��?W$Aȃ��X$}��8��G<Y$�?���j耕���I9}}Z�}[$F�:�?\��i�&��z]$}^$�?��m_$\v`$�?������&��|��&b}�H#��G9a$�-�
VB�G9��eɚfP�{�9{~9��b�������
Ģc��z��'/�G9d$$D��-!e��xF�Bd	�f���H�g5g�G9ȕ�z���h�=i$C�p��c��ew�vfj��zo�ִ��F+�����k��i:�Jl��z��G9$�&m�on��z>�-�I9ÄG9w�T���e��7����ao�op�Hm�����<����h��i��)'h�>q�Hmr$�>�zes$��i����>*�&Y�?!xĢt$)u$�h���ݨ��o�o��ˀr�Àv$�hՠմWm�(�i8w��-�Z9kHmx�Hm���i8Q��y$)z�HmFĢ��z{�Hm|$�$������}�Hm~$@9��e$@9Ev�����ʀ�Hm�$j��Wm�$j9��̓$i8�$)杈b�$��t@9�Wmh���%�+|���ch�Hm����]��c�$@9�$i8J�����9(��$�kK�մ��9�$�`��⌤�c��3i�&� >�{���Qi8���c�׾�ִ�\m���c��Hm��HmQ5A���"Fdo��<���9�$$u��oG\m`�o��$)��oe��9�"DR�T;���.���:���YFd�i���Hm[�tI)i8���,�b�"D���������#"��z���<�$\m%�z�$$u��6l�$�_������9�$�`l��ݜ$)�� \m���c���$"D���O�|e�X�b�b�$�$�s-�$$u�$s-��?�$"D�$$u	�f�<���'�%�i8~Ǜ�?�e��9��"|��I�X9��z�[�V�U���9祤BmX�[�P�Y��c��Bm���|���h[�cŋ�i���h��c��c;�`���9Ȁԁ
��h>���h]��c#�UT���-g��>z\m���>e�"|	�$}��+|m[�"D�$\m���h��}���>j�7���	}*�
)�$X9#Z䳤�>�j*���>���$$u�x*�$\m���>;�cj��h�$�z롧>LF����-���OΎ�?�������-߁}i	}����j�����z���>���h���h�C���H�w�����͢��Ծ�}��׀Pҿ��>�D95�礁
�P�&!���>�����	}���-¤X9=��nä}Ĥ}����ŤZmʃ�nbw�$od��^��"|(�P?$op�1ak��c�
�Յ�>�?��K+Ǥ�>
�}�$���c�G� ɤ�>Q�Cʤ	}��Zmˤ�hB�c���̤�>ͤ}5��Τ�>Ƈ�c5Uv��D9ϤZm��	}�Ƙ�T�U�cX:dФ�-�$�,Ҥ}��>�$�cԤ�cդ�>֤�h�$�,ؤ�-S�S�٤}ڤ)�Zm�$�,��c)��ܤ	}�$�cޤm8ߤ}ढc�$�cH��
y�m8Y�u#�m8�����$�cb��|�m8�$�c��2'���$���o�(��)��	�,��zjoz�m8�?��)�$�c�$�-�$)@���xm�kv{�Ԁ�[��9��n�)�$�c��/��>Q�L)���$�-d��k�$)�$Fm����$�k�$)�$�-�$Fm�)��Uଋ���n�Fm�$)�$Fm�$�kj�J�$Fm�m8���a���$�$)�$�c��kd�ZmL
X�c�c�/��k)?a��i8"۴��2'��&D�$Fm�	£@����&X��$Fm�)�$Fme�&D:���-�-�FmQ|���m8x�c"��n>�|��)��h�$�c%Fm�)��q���9����h<��n�&D�a؁m8��m�)�!FmJ�p�m�h�&D$��%
}M�&D%)�a��$��&D	%���a;�!g
%�h%�|
}%)��hG��`��o
%�h��al
}_�%)U��a%Fm%�%�-%�h%)�)u����%C�%)
1v2�%�-{
}��`%�&%��%���&D�Fm%�k&|
}AFm�o�Kd��%�k��a�`#�����WO���` �o��)u��Kd!%}S�"u�)u���	�z"%�#��@$�)um�)��&D%%�b&�f8��o*ۡ��{�h:^m��G+y}'�^m"�h(%}J�|Ȁ�)�&Do�|��c��h�}*%^m^��Y���+%�h,%�?��i-%-D(�H����.%
}/��90�o1%}��0zg�2��9��(n3��`4%
}�J���?5%�56�o7%}8��99%Ģ�}b�����OD6aΎ�?��|:%I9瀘9�I9����6XmS�k;�o<��{=��`>���?%4b@��`iI9A��9oĢB��{k6a
�)ux£��)u�I9�ҪC%I9D��9J�G+��)�)̄�{>��E%I9F%I9G��{��G+�!}e��o6ay��,H��9��I%�d⇘9J%}V
�|A��-K%)���,L%}M%6aN��k�����}�,O��,�I9�I9�|P���Q%}�£w�SpR��?/�e��9S��-T%�x�w<U�)瀘9��{d�|ݝ��V��,���+!���y��,��Fab��|��(���W%I9X��iY��9ޤ�{���Z%�xG��
[%I98[�H�����9�£ʋd�\��{2��9]%I9��Ϝ�^��9ҕC�O��&J5��I9_%�>�b`%u~a%�k��dցo�|��`.�>b��&l�}��x��xc��&d%�k���,�[���)e�}4���x�T��y�e��'��xO�`f��&g�}(���h%-u3��&i��0�����4b��8j��ka��|���	d�ȁk��$l��,����m��in��ko%�`p��x}#-uq��?+�#gr��kD�`s�#gb�`ѣԂ�im�:瀻x����t%+D��)u�Kmv%�`w�#gx��?x�Km*��'y%-u�Kmz��?��{��||�Km}��i~%&�+D�KmoJ9���?*��k��⁥�&��#g�%-u���&�k⁆��
g�Z������&9W��%$u ��k��#g9�W+�{���'��Km�%���{"�9�%�j���kւ9u>��&��Km���k��j��1a\��x�%�`���x!â�%�9�%�j���h��Km�%�{ʁ�|�������9��{���S��Km�%�=J��(�%�e_X^�%�{��)���	���Q�%�`h��ɜ%�9	�@m�%�j����fXm��)���x(���%6a�%�j�3΄G"׀fӠ%���d����?�?�T�f�H��p�%�j��Km:�Km�%�j�x~��S�6a�%�9���ܦ�Km���ko�&Ȥ���i��Km��#gρ%d�쪥�k�����a��[d���<�M9��Km��+|g��k���i%��4�-��	}�M9����}$u߁)'�%�i ���"�$X�9RƁ6���&B�-��	}�%�9��M9@����BZ��J���|l�-������c�ԣ���,�%�j*��'��t�|�"�9,��,��j���P�ܸ��M9���޷�	})��-$��Հ�Y?�M9��	}_����i��2ѹ�)�?���	}��b��N/�)'�P�q����|m��a���6a��	}W$9���n�%�(��-.�=�耾>��	}c��x��꾥�>�%��.�=�ݝ�C�6a��%6a.�=��Xmȃ[J��i�b��Ɓ�����u:��u��}6a(���׆	}d��>%`8Q�3�oɢO��(�)��|�؀��/�M9���	}�6a�i#¥	}ށ�,)۴å	}�����E�Om��+oɢyWm �|К�n���>;��<��/�Omĥ	}���|K�UBe)e�|��k�|(����a���o�Fಒg�x6�6aW��im?��}��|�%�/��zXOm�%�d��-�%�ko��o�͢U�%Om����6a�VBc�̌�h*Wm�{��Om�6a��/D۠�>ɥ�z>��$�>僾>+��>O���ʥi8�Jh˥�z�|.)���ߎ�/D*��nt�|�	Om/)��z��>��bF�+>6ḁ�`耾>o����`�%X9�h���`�%6aϥ�z�w~׉V�g����hХ�`UOmC
�@�|��h(����%Om�%�k$Wm�
�忎�[虶�%[�%��/ԥ�`�u�t��#�Vr�ˢa��|�/��%�k���`���%Om�%X9�%Om.��`�%۴:��kJ��`ڥ�`�%X9�
}!��|��f#���`���%X9ݥ/D5��`ޥ�z߁�k		)�ZdH
}a6a� ����ߥ�z��i۴���WȀ��%
}6a���k�%�h{|~��z�`�_���z��&�>g	�h�%|~l�ekL��i�z��`�	�a�%=a��$����%ۢ!Fat��jo=*Y+�|~�%DmQ��`e��|o����`R�?�%Dmw�-!��|�%
}
�y�@93��`0���2'�+�%�?($D��L������%�c�<�����{£壟k�`m�h���<g�&%�?��$��z�s�:�\�?'�J�]?t��'�%�cH�P���{��hQ7:�%�cB��k��;F�Zm��{�F9x���Zm6a��9��(?�{��Zm,��<lY+��Zm��&���;�(�%�c���<�%�&���b�%
}g�v�F9��|r����Zmob���58�%+!?��b۴��Zm�he�%�c�}�Zm��93��|&+!%�x��Zm&�-�+!��?�Zm��-�+!&+!{����)�)uj�)~���mۢ�x&F��Zm�H�i�z&�c:�?	�)=�����?�	:���|?�뻣�[
&�?�)��|:@9w����{
�)&�cg�?�jL&+!�)&�ct�n
&�c��-���&�x>��|
��[&�`���Zmp��c&�c����F9&�`�Zm���F9�Zm&�$&�`&�-�)&�k���|��=D��> �\m!�)"&�>#&�k����r��$�Zm6a%&�-�+!;ۢ	�*�&��|'&�>�`{=a6a��i�;�(&�`B�k���|)&�k��)
	�-��>Ȏ)�Bm*&�z+&�-��$�X$��z,�)c �|-&�i.�)��>6�
�-��(/&�z�����k0&5m1��Rʃ�y�k*�|2�)3&�?4&�iy�k5�)��Bm6��|ȆY��?.�)��+b�Bme��|����k6a��{7&�-m��-	��>���H�6a8&�`��{9&=a:&�>�"|;&�`l��{<&�>��>���|=&�kZ��&>��≙�h?&�>��@&�-n��&A&�`]��&B��bC&�`���WD&�>jf�E�}_�>R��|^�$�"�-A�Im��-F&�kG��&��ImH��h��hL��D�/��|��|I&~~	��{~~J��&���h��hK&^m��b��m‰��L&CCM��bN&C���iO��hP&~~���|Q�}R&^mS��&�6a�f8T��b$���U��&V�BmW���vm8S.gc��?H��d��&X&�?Y���Z&^ml����~~��?[&۴���<��b`^mc�(o���\&�?�~~<��.�Zd]�)U"T]U��h� ������?�h�|4�y���{^��h‹��_��h����&���`&6aa&~~��+���h�~~���7�����+g�����ho��hD�|v��{b�2'l��ݘ�V��iЂ�h��Imb��&튡�c����S�d&i8��<+�Im�8e��{f��z��|g��bh&^m|�� �i&^mj�8���8��iӃ8���<��+b��nk��cl&�d9��|7�%��$m&V n&�|f��nB۴�����o&~~p�8a��ư���-q��c����r��c>����-��|s��c���id��t�8����"��nu�f8Їf8{6a�j�v��k.��c�����9����zw�8	!gx&�,y��c���z6��|�)��z��x�i6��|b۴Ԃa�(a����m�az��cb۴���l{&�b|��i}��c-��|����~�&D]ң&�/�Fm�4��;�b۴h��>�&X+�b��&ܖ8a��|�X+�|���-n�{i�(g1$��c�8���-�h�&�xE��c��q���	�&MmX+ ]d>�h��F҅&Mm�&�h6a�&�b��8a���߂&DLX։&�b�Mm6a��48���`l�"u���$��b(��㌦�|l�Bd�&9D2��&D���|L�������|
�hc�:�h�&�b
�YmFm��ae�&)��Yms�ae��Ym��8a�k8��ae�&Mm�&�kX�Ym&D���|
(a����`#�(�Ym9W�o�;u�&)���^W(a��YmyMmj��`�Fm����:�&�b�w���=��幄Ym�
�����
C���+��Ym��-��Ym���`��W���S�
�s��k{���*�h����)ʃ�#��ň��4}9�����x�&}���kv��f�W�&}�&�b�>Y9Db�Ym=���{�&Mm"]dD)�&}������#9�;��&Mm�6a���`#UT���-�&Mms��|���`��{�I9�9D�&}�&}�&�b�9D��8��Ym��ael��-�|��c��Ym�����|��P���`ki�ae�&X����|�&�i���k�Ym��?Ђ�-��hԱ�kF��۲�Ym	ң���k�&)�&�c�&I9$�io�۷&I9��ݸ&�c�����R�)u�i:��x���,5�i�&}�&�-��m8��(��,o��b>�z2ܽ�&�cLңĉ�b,�z�&}c ���b��m8Y��b�&}�&�c��|�&�c{���5�i��o����&�{����&�iŦ�b��գ!�3GƦ�|�$�o�x���.��'8��|��ʀ�O?��w��bw������������&`8��|����R$iȦ�&����cz�cj�ɦ)ʦ�&˦�&�&iͦ}�8�Φ�bn���&����C�Ц�bѦ�&o��[�3���&�&$i~#�����&)�&�cʉ�c����iզ�b�&)�&�cئ}v��٦�|��)�&�cۦ�c�&�`�&�`��˸.��&���|ަ�bc�i;�֣�&�$ݐ�&�&)�&�|��i<��#Q�}�)��$�k���b�&)�|e]mY(a��&����&�`ဃb�$)N��h]m���|e)���v��<�dwF۴b$��禈b�
��]m\�}A]m�)�&�`Ҁ�x�)�&]meeϋo�(aW&�]m[�����3��x�&ee���<�&]mI�-��`J����
=a�&]m������|R�o�b�&���&$u����bj)������(a.}l�Kmџ�|�b�Ԁ�����b?�)�	�`_)��$��y�(�=D�&)��c�&)i��X"�(a�&�k���k{�`���b�&)�&�`�&�h�(aD]m4)�&�`�&�`���*���&�i�(a���i��/�&�k�&]m'[��@m'�(T]m�գ��+'�kN�k']m'�&���'�&X��`���(��'�oТ�寙)!'�&�ңl�&�Kk
�net��
DZB��k�g-	��k:�-@�b
��iw�p�'�b{�(�
�&��k�)!G��
'�b;��i��M9'�&;��i:��k���i'�bʋT�g)!)���<e��|w�����<�b׌�-�,ԂQ-7��!��%���'&��	}�r�'W�'�&'��}��|�$u�g1�}Y�_9����
�A'=a~!�S���<7o�	}��<>�_9n��g���'�b'�c��|'�c�}�:�.� DG��k����}7�_9'o���|�܋�@m��|Ё�-�	o��/��c�}l��<����	} �}!'�c�Ŵ��W1�C�D��|"�he#'�c�he7�g1$'�Fc�&%'�b&'�&���Q''�&(�he��?���|�c�X-)'�&��b9<��-nQ�8g�|��u۴*�8g��he+��|<�֣��%�b۴��c>�_95��,��<,�he9�8gI����գ-�M9.'��;��h9!/'�,��(0'���Q��)���<1�}2�	}��) ��v�p�=�s��3�	}�we4�heO�)�9!d	������j�۾��d֟5	���he5�)6�Kd7��z��)8��|�PkOf�9��|m�,�)��	�(a��h�)[�c���|)۴��,:'�co�(���O�)ߌ���(a;�)*
۴<�he	)��1���Dž(a='Dm���M���R�i8Dm>�8g?�)픘S��t���i��z��-΂8g�he��/Db#Dm@'�xA'�hd�7����w���Dm����C�c�-��}	�(DmB�)C�գD'�i����zoڌE�)F'�-�Dm"$�-&�be�:�	)G'�iH'�6��z4!g2��-I'g�J'Dm�)�(ab}t�	��	�
(aa��bK'�lL'�i���bM�)����e
}N�)[�t蘆�$O����ο�(u�(W(a��/��j���bP'�hv����	�(ai.�%BP�hdƐQ'
}��`DmQ��R'�{�(aS'�i?�G<.�)u�PkT'
}��+��&�֣���bU��b.�{c����.�h�g�V'
}W��by��.�C�X'�$J��)h'�ȩ���#��Y'<g߂fPZ'
}��&p
}�۾��1���|�	Σ�_v���[�)u	}��&ȗ��k��\'��]'�i���磔Le�a^��>_'}�@9��`'�cj�/a�F9�F9(��&O	)�@9b���c'�&d'}ۯ�F9e'�&���a�ze�dȨ�	��*�f��bŁ�<+��b@9g�-|h'
}Q�{��F9R��|�� ��&��F9i'��j'
}j�Ok�F9�
�k'
}�ze���& �F9Q��
$iSi�}mPkl�F9m'}��UH�zen��&��`���f<��b�Od�F9���+��&Z���F9o��{R�F9e6az<g�Pk	Evp��b(R9q��&�i8�	��;ur��|�;u�}���{z<g\��/��i��c��s'<gJ�״r�J���i�No�?9��{��zem8t'}6�q�d{�q%	�cu'<g8��a�*��/v'}��w'}x'�'?�ܣy'�`z'�c�$F�{'�`�u,"��/|��c}'�ch�z~�F9�����	��.���i'�&�z��)�'�b���{�q%��F9�'9�9�q%���c�'�c���c��b%��|c����'�c�'�bȃ{��'�c{Fdk�&�'�'���c�)�'�'�'�`�;�
��c��_��|��b�'�''�6���'�':�-	�)v �Gʎ'�c�b����'�cc�գ	X$�'�b�'�&�b��b&�Bm�'2�'fe�Fd�5���%
��n�*���_6����q%6a2]�b��)k�Bm�
g�
�i���05 D��`���X�	�b��c��&��+���/���h��X؂�h���c{�&|��h���h�׀��+�'�`n�'g�&� �'�c�"!���cn�[m4��띧Im�#g�)]feoݞ'�c�Od��'�b�'�'��Im�'-u��Imk	f�ń�Y9Sɣ�
)�'�c��6�����h�'�c+��h�j�,�[m��b��ǂ����'�b���h�)�6L����)�)���h�'cbf��h��zoݪ��%�'�z���h�譧#g��Ch5�Nd��*�R�#g��h�����W䰧Im9��6���z#�j�2۴���w���ւ@m��z�'�-�@m�zÀ�ܼ�u��֣ʁ۩�{o�Ud��@m�
gPPk��f�'-us
ۇ�zU;��@m���h��)�
۶�@m���h�'�z�����-b��o��繧58%�#g;�i�hmu%�'�c�'�z��3���h��#g��c��轧@m�u%��#g����~��g��Im��[m�hw��m8	�§Im�˻�zo�ImçHv}ħ@mŧ@m��	�n����uv�գ�'i1ף����	}W�c\�-#�	}��iǧ�c�/�'�-
Z��'�z����r��i5��\��c�'�c,�d��'�T�(�'�zY��c�ͧ�c��zΧ@mϧ	}.�&�Hv�'�zѧ�a�'���FmD�c�'�z�'�kէ@m֧m8䀌-�'�`曚-���ا�c���c���u9�ŧ���٧@mڧ�c	���6�
�n-<�Ok�'
��'�&�'Fm�ۧ
�ާʕߧ@m���i�
����vٸ
�䣯���_�ST�'Fm�'�&��@��
�ȕ�'B9k�&A)�'�c�'X+�B9S���'�i���m�kd
�`��/eFmu%���槚y$��cg'g�#�`X�&!��/�'�-;�ɸ�'�c��i��88Mm|��m�9Dl'g%4So����'�hn�	}ꧻc��h<�m8j�`	��c�	}���i��e=a�	}g��c�'�h�c�'Mm.�?,�-���`��&��'�&�'�-9����$�&m/O��`��Ji�N�������c�keƁ��'MmO�&���c��ke�&9�ke�f��'�-��ke=a��h�'Mmv�+���c�KdU��`���cD����ke�')��-|�Ch����w������</�ke�C���cƒ�(�+��Ym�')��`(Dm�f�o��c�ke���k��Ym�h��.)��{�-|��cl'g�&(�蚀�y	�&�
+il'g��`Dm�����ke&�h�)
(~m���(�(Mmo���l'g��i��h(UTU�(Mm	��`<Mmi������"
(Mm(�&��`��c
(�&(���0&��Q�keX�9%���i�{�O��`(�&��g���`R����(
}(�&��k�I9(�&�E��c��id�����c�ke�x%(
}�ʢ2���&��i8다i��Ud��aX�����S(
}���i(�bi!�&�)u}e������d(���;
}(�|~�)u��
(�b��n (Dm	
}c�!��"(Dm#(�b�OҘ��������ې���J��%�1�ң$�m1%(۫�j&(�b'�o�F��W�%�]d��o9��!���v�2�((m8O�xی)u��(�[)�o��*�F9+(�x,�x%����#ƪ�ހw�P�-(
}�ێ��?�����&��<���.��&��W�}9��߂�&���/(�b?�{��}0(�&���1��b����2��&�b3�&�$3�F9J+!4(�'5(
}�����6(���'�K�7��b�+!2d28�o9��&Q��J����5��|:(oe;��f�Įc+!<��&��o=(oeu+!s�>(�c?(�'@(oe)+!���&��ٞA(oeN��&	�	8�%B(�cC�)uo��i9�C	oeD(+!��g���u���&��c���$��^,�}E(oeF��k��'G(�cW&�H(�'loeI(�c�+!J��i��}K(oeL(�'�#��M(&N(]m�oeO(��P��`��c�������`j{�Q��b��+R��&S(�T��&֘}�{�j�	�U(oe���i2í��=� oeV(��H�����}w��b���&m�=W��D��&,�/X��b5��iY��h��d�[Z(x1[�)|%��\(��]��h�'^�)��Fd_(+!�/`(oeH;�a(oe�+!z�ib(�i.oe��-��5
��i/��)�
u\*?�c(�cn)!d(oe��hc���oe��Ye(�c
oe��c�Od���f�)g(�cY�y���hh��'���hi(-uf�]kj��bȀ́Q�{
-uk��cc���q�k�+i�8�l�)m(-u��%���bn���e��}��0o��i�p�p��ke���%;|��_9q��kr��b΁ң��=D{��bP�y�犎���Cs(-ut�}ޤ�bo���u�}v��hQ���=��c��{=�e���1Ca'
�+w��bx�)y��h�W��	�z({�{�)|��b�M9���1���XV�{Ä	�}(-u~�_9���u�a׉����h��}����;�)�( Dj�Iv�%��c 
�i%Amґ���(�b��}���u��jg D�}����'
�w�����(�b�(-u���b��`q�ʜ	��|��������b����c\�q%�(�b��n���b�( D��ze��;T4��ʈ���( DJ��P�(-u�()!��
z'��/SX-�(�`�-u��ze�(�b��?�K9��$#��_9�( Dĉze��cЂDv���a���{��+Q�zek۴�(�k9�P#�~��1?�E�(�k(��}�(�i���u>K9���1���1��?p�}�ze�<i�(�`��}�( D��?�({���ze`��_�n��i��)���1��������/3�k�/!����(�ik�<iH'j6{��xa�	۝��z Dx-�����z��b�f@}��xŁ�$�(�{5 De D�(�{�(�h�(��(�k�(�&y�{w�)�(�,��ze�]yK��'�(�&��L��z�(�c���')�b���յ��zF�h��)����z#ʢ�(�b��ݬ(�&���׀=
ۭ��z�(�&�(�b�(�'��\dt�,=�e|�(�hD�ִ�(�&X[������ף��bM�&e�Q��
�Դ(�k��'�(�h�(�h���z��%�(�c���(�c�(�&���z��h�����(�k������/!3�&]�)L�ʢ���(�,�(�i�(�b=��^�/!���/�)�(�b�(�i��/!è�z�nĨDm��a;��zŨ�'�(�b_��Ԥ�&.�b�(�&Ȩ�'�(�b�(�h,�&�(�b�
ۤ�&�(�h�(�c�(�&ϛ-|߁D��(�c�(�&�,�(�h�(�b���z�(�bo�%N���(�&�(�b�
ۯ"�h�(�c�(�b�(�&�洘��g�:�a#�(�c�(�,mPk���j�(�b��,'�c���(�c����(�b��(�(�b:��Jk�b�be]~eߨ�E�bਁ9��mۇ$|�(����H�ִ�(}��C��($|4�����Ca�\dH&�ܠ��	��@��y�X�be�u���W-���i��b�dP��ۅ��u��j��9�($|>��j?����x�f�W��D�L�;�}jE�Ɓ;*�(�bgQ+���<ao�-!�P��b�$D?	���(�bX����i�(�b�(���$|'�ִ�
��d16����_��(�>�
�@dl�d1
�	ww�
�9���(�z
�x�=�je�wz�z�ie�O9���9�ie����&F��(�z�(}��\dL��(�z�(���9���������׀��	���i6���(���R�
�����ct��,h)a�}��S-8�<���{�(}���cB��ϣscD��<��Ϫ�
��<�(�z{
���{�<ց<i��/��������ڇ�Dmy��c�Ϫ�Fd��}��`l����is
�%�z��c,����}��z�Ʈ��<is�`��Mv��c��Y)5�Y)�d�ց��&��})�'6�‚�{B��x�����i;��)
���&f��Q����z	)�>�}
)�z�ʢ���)�>))	X$)�z#�`
)�` ��&�����y�)�z��&�Q	u�^��%)�`��a%)�c��$y��c���)�zrme�+��/��b)�`)�j��bb@���{)me��c)�>�41��/meÀBm��))me��&��{���h�	me�����hLϪ��J��{lme);|�^9��:e��j�[m�Fd��c�	����΀��co��D�$
��.
�cq��<i��c�[maKk )f�R�>!��c"��h>�)�%�d�ګ�́#��&I
’���$��c��O[�%��&���&�[mۗ�cv�
)��X�'��6��QW�:�xOCC1��l��bg��&�
��F�L�Yd��&���`U !��Қ��
z���<�X"(�[m�5g���u)��`��(���55g*)�`����+)�-�Yd����[�k	�,)�`��$�S�@m-�W�.��z/�@mȀ́���0)�$1��z��2�@mt���3)me4)�$��	ۘ��k9�3Æ�$3��5)$u6��z�R���A+�q%���^ȉ=�#g)D7�@m�7�ca}����ցf��zw8��c9))Dh�#g��
zg�[mˉzw+��%���:))D�Zv���zیzwU��z;��><��c��{��>!��c��a)&'ٓ��	}=))D��>��>?�	}@))D.��A)}B))Du�Ҁ�$Z�C���ۃ=C�	}��uW�&'}D�<iE��>�$u뀼�)DF��>G�}H�	}��n�&'I))DJ){�B&'c)D���
�+K��>��Ϛ��&'n� ��Nd>��z��܄�zL�	}Ɏ}��ϪM��>N��j�@mǗ�O��>P��Q�n-=�d�L3�هiQ))DԪ��xR��?S))D��{A���qd�%��?5�)|T��{H�삄?"�v�_��{K.��?��{�U))D�$�{V��>�ƪdX-W))DX��>[������k�'��=�Y)�{Z��>[))D\)�h���]��{�)Dm�շ�9!^��{_)�h�	}`)�,a)�hy�,���9g���b��>c)}ed�Y��Ae��yf��>�a�&'g)}eh�	}g��`��G&}ei)�cj)�xk)]dl)�h�)|X��������>m)�c��=�n)}e�o��{p�Ymq)�hr��-s)�ct��$��9�u)�'��뀻cv��zv�(ކϪ��h��9�w��iȀ��x)�c���/y��z�){���ց��+R��cz��i(��"]d{)}ep�Ym�)ď�i��9�|��z})�c0Dm~�Ym��i�Vm���c���%�
}e�){����i���{�)Dm��9�)�hd��ce�
م)Dm�)�h���{�))�)Vm���i�))w��݋��{��hiDm��,�)�cQ�ל�)}e�	�h�)�)�i���i��-�Dm���i/�<i�)�U��)}e�)Vm��4�)kc��,��'��+D@���a8�9�"�]k���c�)Dm�)]d��z�)�i��tAh��W�+5]d�)�i8�9����i���-�)
}Ȁ�Ŝ��-'����9���(
�+�)-|�)ۯ��>��y"食)-|)��ܠ��z��9����i\�����c{��-Dm�����9��ʢU��i���c��a���z�����S�)Dm��Dm���i�r�9i8�)Dm���(���z��-D�)�z������C
}�)�i�)�-�)Dmx毣�zl�j��a%��-D��	�?�IVml
}Ӄm11�-D�ۊ�)�)�z��-D��u�=���k��-Dk?�B
��d���	2��i6a�;|���b8��)u�<g��F9(�訁�n��=�b��}5f���y �	ۊ�-D�-|.�)u��F9(��c��Dm8�-D��}���c���o��
¢ȃ�{k�í���9���&�췩�(�)-|��o���F9.�)�)Y9�&�{���g�)
}��F9kC�À���-D��n�)-|���y��F9�_�׀F��]d{&a΀�]m��<i���f��F9���3��ދ�zJ����h-��?��-D7�iL��z�iv=�R��-D���`�-DS�}©�x�){�[�-D���]]�)uz<g�)&a�h-����ʁ	�z�Y�߂�$<�=�)]mQ��S�&aƩ�$�	��pc�
'�=dᔐ�j�+��L9���ִ��yz<g�bc����'��j�]m/pc��Ć�'ǩY�@آ�)ۂ��x�)
+�F9j��
�3�)�{�)�&�}�)de �F9�=����)�-�{��)�'w�&q=���	��wpcz�}<ˣآ�)�-m���bck�9{_���j���'q���#�Y#آ$�}�pc��Ӣ�����)Y9�)�&w�tC��J�-
���	ˣ�G�����-R��>�pcA�-ҩ�j�)�-ԩ�x����	�����)����-Ɓ�v��<,��x�	������)�iԀ�i���j�\h�q�ש	����bc�آ������-ة�j�)!X���٩�x���Xک�`mpc9<�w��쁊y�_9۩�j�pc	ۇbc����I�$�izܩ�j�)pc��b�pcbc}oީIm�)�-���੮�|���Ԁ��2���⩃k�_9�ImJ:�L�Imh��k뀵9䩋kwde�)oh�Im)��9橃k穋k��Im�r�pcA��9詃k(���驃k�)-u/��i��f�/pcl�M96���i멃k����)��9�M9A��9�)pc D�m8��Imk뀵9mpc�) D�k\��%���s��7)!�z��
ˣ뀵9}Ϫ�X�S�(M9F��k(���k*�{�� Dl)!�k}�2D���9���])!]-u뀵9�)&a��@m���k�)!'
�J+��-���k D&a��@m��-t���k��@m���i� |4-u�@m�	ۢ� |� Dc2D��@m=���-���-�)�x��h��>y�@m���iN��k��Im��k$�_9뀵9*&a��kj��>\@�)��9���I��'I�Im�Md��k���*2D��io�p����뀵9��?���9���?^\��-�}��i��kI
���7�\��x,�i�ڀ��?	*2D��x
*$uԀ�*;�z!2D�@m
*B9��?�0'*��c2D*�h߁�?N��*B9*�h�@m��h$ˣ��x��z��-��0'���(*��@m�	�}���SN��ۇ��i��,(�0'��{J��zo�cc�Y*̪�@m�
�y�����@my�ht�x�o��hÀ�)�0'�[�t��(�0'��i�0'��{D�	���i��h�xt�{sդ7�{d��^��h�t��#Tl)���QX����1��y*�`D�o�����y@�Ϣc�i���?d߁*�h$����{p�}�MmH��o�����)7����%��}*MmL��� *Mml��'��r���G!��?ڋ�ho�n�"*	)�ـ��0'#*�h��$*B9$��%����E%��h����
kc&*B9��i6��{(�0'y�h'*Mm_�ie��-!��Jϋ�pc(*Mm��0't���y|g�!C��;��k�
�c�-!���Sv׬�-!o�.�r�)*�c�
���{�W-r�S��i*�o�I��Oع�-!w�����-Ԃ�t�fk��π+���m�kz�ܼ��?煔c�,*$|X���r���%&a�@9�Ck���J��ie-*�(�Ϣ(����-!��O�.�&���o��{�`ՠ�(�?����9��$D��oc��.�$D��V/*Mm$��0��z�����1�$D=�N2*Mm���z�ۨ�`e3��/j��-	��i�-!5��if��)d��iԀ�<�$|��轇�v'�4��h�pc�$D}5��z���}��b���Ԃ-!�C^�b\��/d}���6*}��}��-@�=Ԁ�����?��z7�ie�j����pc�i8}=�n�z�ܟ�$D*�'���$D���׀}���)u���y_��8��{w��o�=Fa���y9*} ��d�^:*}��ߣ�{;*�h��},��<�#VBc�+'`}(�<�?����i<��{/��/��Y5�hc�F�6ԇ?�=��iS�G��$D��Wc�$D>*&a?*�h@*�c�&a�}A*�hB*��C�F94'D*�c������`e��F9E*�hF�u0�3�
YD��+G��H�F9I��i���>�tcJ*}��b��{��bF�h�fe���ЁL%K*}�F9L�}~������Y��IJ�b��F9����
�؂�ih(
ۀ��yN��o�آM� ���N�F9O*�hP�F95��i5�bQ��yz�,Q��i�	�d�-��,��i6D{�e�)u)�U�l�}[�Y�ݐF9���{R*�hS*�,T*��U�)uɀ�iV*�-;�-W��{X*�>��i��{�߁Y��h��'�Fa��.���"�tc���iomeZ* [��h��-��'��\�F9Zԣ��c]*�-[$Y���+.�)^��hÄF9��<�{�_��c��F9`*�c�o9�_����F9a*�c���hA�-b*�c��>)��y���c�F9d��h�)e*�-�F9f��cW����-\�F9!�A�h�
�zg*�z�)h��hF�>h(�)i��c��j*�>k��h�z�cw5�����'�)ɀ�i:|/����z��W��dv��|���l*�)�����ϸ��cm*�>����Ӏ��)dٸ��)o��s��n*�zo*�>�>u\Wme�)p*�zā��3�iq*���)%�-���r��c4��Y|�zZ��#$�۸���?s*�iB��ct*�z�)u*)D�:T�)v��c5��tv �w*�zx��{��-u��U���{������y��c���z�m8{��{|��c}*)D~*�>��c���*)D.��{��y��墧��Y��!gx)D���<��}��Imރ:�����آ��)���<�*�z�*δ�)3����)|��;|�*)D�>Q�W���<�}��zq�z��m8�r�C����)�*�z�*)Di��<�*�z�Fm��)B�e#�آ
ی*�)�x]��c��z΀!g���=�����*�`i��<oրΟ)|���<	�!gk������|���*�`�e#�����n��^UAx')D���<-�)|g�!g���c�*-u���ifm8����9��{���}O)D4�+)Dȃ�� ��j�*)D���y���-u��"uv)D*ے*K9�
[ғ*)D�*K9-uo2���	X��;|)	 ʃ���*K95�;|(���2�e����<�<H�����DK9S�e#���<��{���a	 ��)|��;|����e�u@ˣ���6�*K9�S���g���)|�*K9����냹�*#�{�pc�)"K9�*�{qv�ĉ9'��e#���羚��d����*�{�*	 �V)���c�י�!�(����'���cւ�-���c�)�*�{�)���-���`�*)���hԀآ��*�����ݪ�*�{��棧��;*�d��ޚ��y��v���Њ�-7�Щ*)���kc�yVm���-(��*Vm'꣬*)����$�a���c&��-,
)Ԁ��y)k
2���+�*Vm�*)l�"uj�8k��ݰ*)���_c[�Q�;屪 �)�
�(�*)�*K9�	 �*�hw�hJ��<�*)�*�{(	 )x�%I9����5��<��D4'�*Vm���`�����䁼ˣ_ݢ��c�)6�9'��}c'�$!������-���'�$!���-�I9M��{,��-�€���,�*�xj��-Z&:
�(��h'��X�*-|Ϝ4@߂�⿪�,_��}�*I9��*)ªآ(���	)�*-|�pc���<ց�m����)���l,��-|�I9Ī-D��o�PmŪ-D(��ƪoY)�Mm�*���*)���ɪ-De-|ʪ?D�z�dݢ	:��$|r��_����w�����h�-D�)-|
Mm˪-D̪�i��آ�	%W��)��-D=}�?Dͪ-D��)�����	ݢ@ԣ����*}Q��G�-D��{Ϫ-D�*}k��xѪo+��	}k��x������iR�Ҫ-D�*�x>��xנ-D��W��;u�*}d%g�gȱgP�*Zm�*�{B
��ƁW�����*Mm=�؀=w��*�-S�ܧI9�*}
!��$�*Mm(ԣ�*Mm��p�*-|ݪ}�-|��}c�-D� �L�0'���&ު-D��{W)�h����Tg���e�?z�6�ߪ-DeP�w�p��*�h�o�xG�-D�?D~�Y���0'�*�c��`Z��J�*���?Dv�š�*�h�-D�-D�W-��cF_�	��*�c��i�*�>���A"�}cԀ�<��Km��i꪿,���*�]m(�0'I}瀻x{�m못xS�/��
�
��k��̟�}cA�ck��x
��`ؠ�
�{p �Km�*���cT�H�}x
��*
�
 �x�
 ��`�`8��`��
 �}�xZ�ܣ%IdlM?m��'�
 >�tc��{��@dI
���*�{��t+�@d��)��y"�}c��Km��)�聫���jÃ)��	(��)��@d�*�j���,pc��)�}c�*�jH��&|
�V�蟀}c�"��*�\u������'��h<�jmpc�*�c���*�ǚq�*�ck	��8
�j���x�)Ppc+�z'�)+�a�@d��)Ȁ�.�	��%.�
 ����iv�9']��'%�(g�)���	�).�
 $�.+�k��ۃ�'�b��NÃ)˧EJ+�kx�kFx��i�+�i�)	�#g���}D)!
�Km����  �d$+�i��im�_9r&l)R�k+�i
+@k���}�����[m��+r���)��B+�.���+�yb$�{"�tc$��+ݢ+�jZ�Jg�(g=g#�i���'(�(g:�)�)� �jĆ_9Cpc�)+}�)+}��'n�(g+}����'+$u+}+�i}�eDd��<9�;��¾" Dc�Cv�$u��X��&����O��`��>�2D +?�dݢ��/��	�y��=�})��- D!��z��$"��-#��>$��>a��i
ɴL״�i%+�`&��i�	�-'��z��i���++�M9z�&(�@m)+�j��z*+�i+��zz�&,+�-��J���z.+�i�۴/��i0��-1+�&R��z�i2��i�wA3��z���-4��i5+�`�<�܁�z���6�8�[m7+�`8��z��o�
�&6��-S�&9��i>�dw��@mm4'�C`:�~~A}� Dm@k;��z<��zm��=+Omz�&>+}?+�-��-@�	} D� ��2D���+ �&��}}�s	�	}��_a=uO��`A+}_��X����>j��-X��>B�	}5��<{��-C��iD��z󌣏E��i����uCÇe�F��z��bL�	}��G��z����H�	}��II��z��gJ��>K+�cL��`v�Hݝr�M�@mN��z���`O�	}S�&Ȁ��P��i+��`�
K�Q+�R��zS+�bT��`U��iV��`�ָ��܅��W+�&��-���X��`��5�]�Y+�ץ���Z��c�9!��&!c2[+�h��	�b�b��x�F[�	�	}>�آ�be\��`b):�z]��`���ou�&��ݪȀܬ�m^��`���X�=_+�-`��`a��ib+�(��	}c+�-��Gd�	}�Zm��%B��&e+�-���T��x��J	����9Df+�-g+�hV	j�j�h��-��&����i+fw�W�>�:aj+�-�ԣT�p?�:�k+�-:��bl+�h��m}�-��Jm+�-Ȁ��n��{o��`�@9��?���X�6S��ڎ��`����p+��=�vY5��{q+Vmr��{�c=�P�s+Vm;�bt+�臀�u��{.c�Q��v��`���w+Vmx+�`y+Dm�����	�`z+�i���{+Vm(�|+@9s�?}+Vm�Dm~��M+�h��f��+Vm�������h�+VmT�3T�����wU�+�-�ƹv
���R�-���c,
��+Vm���c�+Vm�Dm�ܴ�,���c�+�&�`eg����c��_}+:�+Vm���c�g�Ɓ�ʋ+Dm�ݢ�VmG�}c��|Z��c��ދ
}g���¾�+
}��h<���{y�*�dw���c�+
}m4'���<��-4'@k���c�+Vm���{���c"�xa j!�j�Fْ��{���&�z�+���+VmL���X����+
}Z������&�)u� �A����+VmDm {��+�e(�蛫�&�+
}�*ܴE��&��}.�z��Id�Dm�+VmW�`�&|Xٶ�`���}��>��҄}�+�z���y��
����&w��y���/�jo��J=����e�-���c뀀$��}*�=�Id��kv�Dk��}������F�+�k;��s�F9���c
}���+o��S�
}��}��F9w��ݦ��c�!���F�*��*s��	���i��3S�+�kƁ��c/u_դ��~�+�/oʹ�+�`Ԁ�����L
�edX$�+
}���A��&�
}��|���y���&�+�k'��.Y挀�i���&��w�}���&Ҁ}��i8��íi8�+�k9<���}%��x�}�����������F9Z����ŗ���뵫�&���i��
)R�Id#��J�+�`�;u��}��R���+��Id=��� ��&�+{���:.���io�ߴ��́�%Ё��\��&�	 ���&�+�`�	��h�	�-9��,�����a���aQS�I�{E
��ѡ}�+)
I���h���a֔������$�+):��i	�ʟ�+�&)�kī�&�+~~�+�-��aT��&�+�&�;d% !ȫ�&�F9g�cI
"��)ɫ�a.�4�ʫfx˫�&��&̫}ͫ�%z��io�=Ϋ�&ϫ�a�+�c�w�+�&���&�?a��}U)ҫ�?��6T��cӫ�&�+�&��?酔���&��])ի�at��?��-A	 �+�{�+)�+	 
�\.$�6/U;�+���+�{"�>�Zm�+�{�+�y�
�-���}��&�0��ǘݫ�%�q���&���%�	г )뀞?ޫ�a����R@k���?��Y�߫�&QXֆ�ߴ�?aP��a���v�ܣ��E���&��a.)w��?�+)D�+)g&i�B|�de='��yǑu\��`�+)�+�yЊIm�+�&��a�)�+�c�Md='�+�&�+�c��?1
V �+�c��&���YB�o7�;|D)�x��)|�o����<����m�o9�n���-���Z�^��?hۂ�Im='�+}���+}�&9��i�Y���?Ԁ�o��]d��j���Im���	}�+X+�?�o����+�yv}��yQ��y��<���Im�}@k6�y��Im,�y���{a�V���X��"u�)D�2�+Fm��@mÀ<��X�:�x���yU��i����Q����+}�Md�+)D�+�����+��k�i�+Fm[�x�+)DÄo;�?B�8�+}����b�Im9��@m�>&�x���k�@��?�F�2��8�"uQ�@mL�Im�o?�Y�
��`�+g#�)|]�a�o='���h��<�,�c{��j�Im��`�<�,}���	��`��Iml�"u��<��⣁�3�Ӄ)Dr�t�
,}��t���c���,v�9�}��h,}�
�x���
��`�Ɛ5��h�X+ 
�?��cs&D��,)}=O�,F�,)��`!�:����,Q�Ɯ)��cb��h�����:���k���YmĠ�h��c,)���`�
 ��΂��,)%��c�F���`��c�@m��a��),���h,)�@m�):�?�$|�����)���`�����s�)>�}c>��'��?€�d��.a���t,,)��+��D,)��R��x ��'(O��H���&��? ,�ckI9!��`"��'#,I9$,�V.��'%,)&,�a8��,'��c(,�9��),�cb�j����*,�p.��`jI9�O�+��`���',,I9-��,:��c��+.��h/,Mm0,)���o��i���,�)O�1,Mm2��c3��c�)��|4,I9��xx�Ym5,I9 �$|6,Mm�)�M"݄)#7,I98,�m9�ie��<�:��c��;,Z�<,)���c=�ie>,Mm�Cm?��c@�?D��-D
w��oA��c΁&��$|��d��?Dm��cB,�-C��cD,MmE�?D��F��b�
�Ղ�c���[�T���N��PcFy��F�˧Fa_��b�DmG��cH,}�ޤ�cR��I�?Dǂ���cj_mJ��cQ�T�Ä-DK,I9��b�Dm%��c��bJ��Ă�,��cO���j���aL��c��b������M��&N,_mNMmifO,�P,I9Հ��Q��c%s�R,MmH_mS,�bT,Mmg��&��Λ��+��`D��l�����/e��ݹ_mU,�&V��`���Հ�W,]mu�+X,�&5�`Y��&4��aZ��c�2�9�iey��c�
�`j�&ˉ-D[��c\�ie���)�9;���)�9],��Ä-D^,�m_��c`,Dme�h��;��Z�ma��c��`b�Km��`Sf����&��	}c,]md��a�#Fa��e4F�e��`f,�`g�	}9�:*��h,�&i,Dm�)(�/@�	�	}��x����j�Kmf#k�	}l,�z	%�Î��ɗ�9m��&&�- 
��������&�mn��`��Km���:��xo��&��@dp�	}q�	}���?r�@d���[���os,�{t,�`�+�&�)u,�`=�$���`é�b93�t�`��{�F9
���&m�{v��aȃW1w,�`x,�&i�׀�<���-���y��PQ���z,�&t���X-{@k�����//�&�(g}	ð{@kg)ۃ���9)���9<���5��baG"��(g����F9{,]m���R��$(��)|,�m���}�	}��ہ��i~�	}��Y�,)�:g�,me}�c���%����Z��iZ��+��M9�,)��V��鄬	}��EJ��	}��cゐwЁۧ�i���m@k-�.��V}f�ۆ��i�
me��M9{�eb��cw�,�c��������M9*#me����c���5���.�y
�c,��$��c.�M9W�{v�	ۊ�_9��ی��i<Rkyme�F9*�_9�� �me��F9��c{@k�cwg�M9�����F9��ې,"D��S��M9��	����i��c7�-ʋ
����eDd9��%�,�yq�4Z5��%5��b�@k�,)�me�)!D�Fo��x��y�G-�׸.Cm�,)o�Y�)��+�c���^���ٔ��'�,)��=v�壻D���c�,me��c���D�Do�,me�"DD��(�棤��'�9���!�D���c>��&��"|v�Dk�,
}�'����x���^��s�o���}�,
}3"=u���'�cwʈw���Cv�,
}�,����"|�y��Cva��������,
}�@k�9���iS��&	
}��"|.���9�6���i5���*�"|�,�-���i)�۹Om?�:��}{@k�e#�@Е
}��e�}�	�O�y&�}�
&<Rkw��I���b۴U
"D2��'���>۴[s-�,
}���'e�a3��Y���Q��,>g��}���'�,�{X��q�$|���'D��9��r8�fk$�xc���偗�'�i��v0��}R��k� D�D��, D��c�<��, D�,�p�/�}: De�9D��?ao�S���Ăbe��e#��}� D�-|���o5=u�,�>�,
}���io�|�+��<�����i*
۴� D��o��}���i�	 AdЧ�i�, D�,
}���i�,�?���a��kǒ	���}�,�i��tA"�beˆ�-ӆ��l��ܽ��k�����ھ��i��&�d�5~8� |���i�ÁƋIm��)�&���i¬)וbe@��+�#�/o�(�)��)ì)�Á[$jY9���=H��-Ĭ�aŬ)_�Ƭ�-��?��)��^�&(��bǬ)�, D���7�%g�X����)�m�-!	��{b@9H�{��&�[�ɬ�-��ʬ)��&��o*���Ȁ��@9]�f1z�-!�,�mϒ�i
����&�#g�og�i�uR��)9�on�⧉�8q���j�u�2���̬�it��ih@9!�_�,�&�@9.�7�ά�i�,�&���i��&h�-|����,@9V��iu���,dP�,�&Ӭ&Di�<�Ԭ�b�@9�,�?p����&֬�-׬&D聭c�%nج)�,�&��'*�&>R��&�a���-�\mڬ)��۬�-ܬ&D	5儀�-d�Ϧ�,��ެ&D߬)����e�)�Mm�� ��w��{ᬄ-��Ҁ���

�
Mm�,}㬄-)}�&D�,@9����,}��{@�ʖ$���&D�,@9�,MmhF��,}(�&D9��-�,Mm���&D�,�&�,@9��<�����ie��Y�,}
!��,�&�
��,}))e�9�,Mm�.=v_�,�%À�i�Vm��F�����$|�,}���)�Mm��
ۋ�@9�c�,�y�,�mo���,�aw��yՀ�wB$|������y���<��_T��)���ٓ&D�,�i7�t5�h�,$|�,�i��j��A��*�����߁��
Yd5ᱦ��-�iv��%�&D��i��cS�&D�h�Fdv�'-}��+-�i�)|-}e�ف
Mm���;��b-��#�i+}�MmȀ��:��z͟�i-}-Mm��iep�(n-}���^	-�i��c�����壐�<�Y��Z�m��(nb�i3
��XmԀ��י4�yȀ۸�U�
�ie��T���Eu��)��ܮ�B��܄ie��(n��{���-�`
�ieH�uJ��)���^�)���ۑ�p�A
&`]�ôO�)�ma�$/�����-��۝	)-}�vY)��"�m�$|��	,��'-	)W&���+f�{}Ȁ�^��h3*�i� !ńô?�����9�	�����(۴?��O�Cmk�b-�b-�cゔw4��'��$|-�i-�-,�{�㑡��%h�.g�A��'R�-|���9��4������`Ҁ<������T;�D9���5k1^�諀�D�)|S�):�{��&�+��g��Ҵ��z-��d�'�� ��&��%��ބ�9��SҀ�b��D9--:�gH���-�y��
��?k	���{��&��he����&o���y�D9{H������Y5�me��,'�ƀ�H�-me -oz�m�{�jRk!�)�E���y!�ô߁��"-me�i� !z��&�+D@�#-�y��&$-+D�����9l+Do%�!g��R�
H�&��{'-me���L(�!g��)- ��i��!g*�[mہ��(Z�{�+- ��[m��&g�zƁ„�d�ve,�;|����<�D9-�+|m[�.�!gÄ&�+DH�[m���&?��6�?Հق�[m����y}�۫��^X��Ȁ��Fm/-}0-Fm�+|��`1�D9���/2-$u3-Fmw��oO$u4-Fm���>h�!g5-Fm��iʎ�9����`�a�`\��'m?�6�[m��������X7-�`�X�8-�?D�&������w<9-�&�o�me�9���"G�@my}��Jj��mw�i��:-me�me�%{�D�Mk;�[m���9Md���}e�9(�[m\�m��"uŘ!gv�&Ҁ@m����-|<-)D��"�/:�!g�i]�R�#g=��`����>-)D�X�͓��?-�{@�!g���'A-)DB�[mိ�C��`�����'D)D݀�`:)D��V���[m�}D-Fm
�+E���S�&F-FmG�)|s��������"o�?�FaH-}���ѧ)|�F�8[���I-}��V���{J��hh$u��K�)|�)|L-)D�"蚀��3��`M�)|��b=���N-ִO�)|7��P��h/��Q��`���㚀��ތ��
	�:�bd���B)|R-)S�)|��`T�)|�[���T���f��ִ��v?"ܣ��J�ӃG+��U-�y�,A���<����"��&!V� ���"3�2��i�&�m��)�$!�;|oִ��PҮ��W�2�{���]9�n� X-)��LY-����Z-)[��`�o��{_�9\-)D�{�h���h��]��I��(��%t%gI9Ʌ�^�)|=��lI9_-�%e��`� 
�)|�o1��%a-)b-%g��c-)����)|�~ed-�c��%e-%g��	}f-)HI9g-�%D)/I9h-)i�)|B�j��B���cH�)D�B�k-�m�+��/Dw��l��z�c�ִ(��)m��'G
�������U
#�b�/D5�
�ckB����ĉ�n��z���-��ئo-�cw�Ң�zb���p��)J�-�{�)q� ��z��/D��or-)�Dmj�
�
��|ʉ/D�Y�s-)���
�ct��'s�/Du-"Dv��z؂/Dk3�w-)�Vm��;Fm��&ǔ�zS��hx��z��&���$y-`cӃ�'z�"|w�p�{��z|-)�9��c��,}��&d�����]���?~-�h��h2�����'����
���%�-Vm#�+��`����$���h�-�coB���"|ꃭ�&*���-
}>B��--|��A��&���z���&<�ߣj
�h�
}.�����'B�Z������z�$����Y`c�-�h���Y��|��'��"|]m�p���z��/D��)*۹�/D���z���닭}�ߌ�Km�-D���a[�(���ێ-
}�-|��)u��Km.��x����_�m`c�--|,��/��Kmi����ߣ���c��&�-&u�Ȕ�}:�-D�-`c���&2���w�З-"D�����-D���'���` D)�}��᙭-Doe���'�-o�`c)�Km��}��㌒�&�� D��m�)���h���a��Km�'}��-D�}s�}��"|��-Dl��h��*�)s�.�"|� D�`c�-o�- D���a�(g+({�/$
}Ђ�}�-��/�Km���-�h���
}�-o�{��-oω(g{��f8��-D���/�-�?��+Z���/��c�{��
��l�9�e�ݑ-D��Km��%��Km��-D�{���p�>�\��-�z��谭-D>oП%��۱�Km��-D�)��}.�)|�EJ����b�- D��c��-D�Km"�/oR���)>�%���%��M9��D����z� D��)���cD#�iv��Y��)��R�H��{��͢$�����$c����)�+��_9(�%�D DfRk�-�&>���- D��(g�-)�F��9S�(g��M9)�-�-*�(gc�-�i* g­_9/��í�V��c��_9ĭ)߁�$Ā4�R�
�!���|��c[���-�-�4Z�;|�-�-m��۸}�-�(�{���?���%��-&|
��c�-T���{��"8�ɭ&D:�a��-�{������$����Y�-�-�de9���˭&D��&D��&��zÀ�>�Zm	��5�-7��cʋq�̤_9[$٤��-d���&D���`��-^�&Dv�鸺��d�z��M9Om$�)!f��̭&D�)�-OmjRk�--u���i[�tk�w����Qւ�o�	_����H$�-��y�Rk��ϭ&D@�ʖ	��6��hA��'m�)��J�m`���ՀN�Э%�&��>����{J�>�[m�-Omc�-q�ҭ&D��z��+ӭ[m�-�&QS��xխ�i�-�-׭�z���i'"�'�-deOmS�m����٭�zڭ�i�-�al��?ܭ�i�F�ݭ�\&|�-�{��h�-}��z��i�Q��'��+u�-}�h��9�-Omc��z�&�c�$|��'��i��':z�&Da���-�h��i��z��n�&D�$|��z'!�b��zL�X��h ��`��z��c��b5�R�-���x��h�f1��z���y�-}oY���c�-Tl�--u �b/��z�-�b�-}��h�?��be��'��x�lOm�$|�B�%�x���z��b�B���be;�x�{`c��I�-`c>B�;�x�<U-V
�<U-H�f,�$D���z��[m���iQ�?��x�����im�);�?X2���-�h���i�	)=�tkB�$*�hx�x�����ߣ�-2D��h��x�-�-�-�h�9���+5y1�Ԗ�-�c�-$|
�-��mo�	�+�?��b�-$|�-2D��	&�ש��. .$|�@d�bx�x��jx���.�c. ����5�b��(����f1��;�xӔck�{J���.�{}�?Z���x�:�x�L��.�{��D�.Ԗ. � �{.@9:�x�	.�{���
.�?��.(���oB�w�����$�?.�{��ioB��y1(�(�`c��Xcy15��+�?.�{��?oB�
. c�{=�?�u3d�̀.�{�B�r�.3]��J���&�b۴ց��o4�+�'9��..�58�{�
;�EP�`c.MmԀհ�)�+…����{�V����.+D.Mm(����R��{��e���.��i�k�.�i��܄�h�"j!^�#g:j������iS�#g�4��+D�f(���@�%���.�{��f. �#gȀ�.�{� V��y��{�#gm�Fd.@9�#gÄId�i���h.9Dj���f�% .9D!.�zR�i�#g".�{���%�#.9D�Jj$�)%.�3��`8�/u&�#g�%l��x&��i���%��-|6Y��?�!���'.�z(��i+D)�#g*.�+.�J;�#g,��i"�9|e�i�Y-��i����..gP�����^,/��iϸ�����
���h0��i��?��	1.fe2.�z5/u���
��I
ā3��iҀ#g�+D4.�i5.Mm�a���<�46��i7.9D��
�8�#g�_9��ib�Bm�������:.9DD�*���8�z;.9Dn�#gx%)D��/ĂBm��f��#g�&)D��/߁�[�#g<�f���=.��>.)D��B�hǬ#g��i?�ie@.)D5��A.)Dw�5�b�5�B��hC.)D���hx�h�D.�?��84��E��h��/��Y�)D��PF��hG.)DN�)|��ۇ�yȧ��9��hH.)DI.�?J.)DK�)|L��ik*$uM��i����
)��	}LWϊ��v�
�)D)D:��N�)|o8&��{O��i��	}m`cf��iP.F�Q�)|&��-R��i���S��h
cky��hT�)|����fxu�f�U�)|���)�hh�<n�&V�	}W.)D��k���h����X.)D�)|��/o�4��/�����ʑ)Di�/���Y��i(���.��z�)D��{KZ.)D[.�/J�F�k\��h]�].��^.)D_.)D:��z`��i����a.)D�� �	}��j�b.)Dr��ic��?d.)D΁&���躘/|e�9煀�z���Ea�	}R�mc��)|���B���s�f�)|�Y�g�	}b��i�)|Տ�%�)|h�	}i.�mk�2:������-��s�&	�$j�)|��s�&k.me{�l��k9�����x�
۱��{m.�>n�=Do��{��&A��{E��{�me��-p��{q��{5�d�r.�d��ɪ��3�=D�`c��/D��&s��{6�-΀��s�&�	F�����J't��.u.Fmv�=D�	"Dw��{r�)�&D��{x��.��&���TT��{y��{�"Dz��h{�g�:�����{��{�۴h-���{|.Fm�x}��{��&{��{��o����?@d+'g~��i��`%��?5Fm6�"|��{���i�."D��meB��?Ri1�."D�.mec*{���&��x*�mc�.me�.�'����.Fmg�h�
��s�&��'���h�+�'i�ڢ��a����E��"DJ�Ӹ�&��߇.
}h&-|��"|D��Y���{w�����&�.
}���{��/D��&v�棌��{�.-|5��Y��&k�1��m����.-|�.�'���{.-|=���?���h���{���w�t��I
��-D�.-|�.}e��Ym��-D�
�h}��ܕ��`�
}�ۖ��?��-D�.-|XX+.�YmQ;��)�.
}�."D��&������-D�[���-D��Ym�?���h���x�-D�Fh���hv���9PY !���}��,��-D��Ym[��?��k��i��x}��-D�.�iQ�h��YmE�i�.�h
���
��'q�R��ϣ�-D�.-|��-D=�"|���y��i��"|�.�i��FC
��k	�Ǩ�Yma"��.�i����h� !��i�.��+&-|>�z�
}�.���.
}� �i�X-�.+!�.�i���=�.-|�.�i��-D� �.�5�.?����k�.+!�.�i��p���Ym�.  ��Ym�.�{���.�%k�p��£�.�i�-Dנ-D{�a���yD�{��-D���kB +!���ᾮ-D�&�{�.�{�.�?��-D���)�{3�d�7D����Y��{� %�?���kD�d��{jrc�.������{d���. î�-�:��?�.�{��!g%I9��	jrcĆm1��{"rc�9�.�kզ<n�.�z3�4�J�
���;����{��b]+!�.�&���=�]m�#�i`c�. *�ɮ�-.�&؀�dc�Vm������.+!zeZ�pӥ n��nt�
�3�\�?+!�.�%�#��jrc`c5���[�?�.+!�.`c�8�ή&DϮ�-m`c������)+!�	 `c�I9s&Ю�-�.�{��6�&�`�B�2��%Ү&D��m1Ӯ B�Ԯ�-�.�{�dc	�J�I9֮&Dۂ�&D.-u��mc�.�`.-u�.����-���yψ&D(��iٮ�m�i8�`c�.�`.-uۮ�-�.]mݮBd�֢��&D�Bd� �`ޮ&D���&D�€��mc߮�-�Bd�.]mV�u\�-u�Kmϖ��(���n����#��'@���<�?Do]m��i*�`�?D�.]m�;u�W�R��.]m�?Di��/Q����`�.�%�'�.�y�Km�]m��	�?D쮄-:�?D�a~�.�yq�4@��iﮡc�-�Km)���&D��`��ڊ�哖?D��'>B������5)Y�Kmjrc:�p�.�yn������-c�s\.-u��'G��~*���a�j*��2��BdB��&�g"�f8�-u��&D���c�]m2Df*��iW&���y�.-u�Gd�.�`�%��r�)j4�-u���{k��.]m��:g.-u���`O��{]mp-u)��i���a���e�����'�	۴��£x�9�����)��%݀Km��/�y݀Km�Y��c(�a���钚��c������!��㯂ie�EJ�?D� �ie��?� ��j҇�w	��?��Kml�/!t��i���c
�t�/�yt����%p)�y=������%V��y�z��j����
�_9��{52D*���=s���?��aL2Dt� ���ª�1���?ބ2Ë�t��zȀ��'�9�ZJ� �_9u�M9��&/�-�M9{��?�����-�(n`��{��j>�+/�-��{`1)��$��?��--����GҀ�?�Fd��-u�=���'9�j�$�n�-���/Fd
��%��y�}��-��'�*���)�u%��'��y/Fd��mcƁ��_�3�Fd�9���i�ȕ/�
��Dd��[�_9���q%@Fd;��?/�)/Fd��}�}lFd7��i � ��_9!��?mu%�Om=u���������_9"/9D�)=u#� :�I"��M9	��?$�����
��Q%/�ie���.��%k��%&/9D'/�i�u�9D�
����8
=u�9D(/�i�"�x)/�i
ê�	�-�	�c*/Om��w@��ܑ����£+/�cYme,/9Dg�[mu%meo�&-/+D./9Db&//��qF�0/Om��h1/+D��vc2�Fa˟FپG��-3/9D*�-4/>g���Z�h�aw��[m�Fd�'í��T!��o=u5��'���	����+|6/ D��{�9Dh�[m7/ DZ�£U�{��i8/ D.�?>g��i�
 Dʋ�݊	{k��vcv�F�9/�{��}���:/ D;/�h���ib </9D?�{D�
�{��x=/ Dd��z���\�)>/ D?/�{�"�a��c@/me�+D�
 Da�{�9D��YA/�{���4B/5ZC/ D�z�9DD��zT>gE/ D)���c��hF/ Dv�X|�)�Gm�f�ۄE���{G��z�<���{��Àbe��?��{�[m�c#��{��(H��zᎠ%9)!��%BI���J/ D�IK��EL/ Dg�?!��hs�i���z.�?��g�)݄f˔�����h���z.�?��{����$�! DM/�{N/�?��q%�;� D�c��1C DB��@91�aO� v��JP/@9ɀ�iT���Q/�aj�mcR/@9�@9��{#��iS/�aT/@9��vcU��i���o����V��yc��֠�T]W/@9�Cd���X��z�@m���h'
��R9��f�be�f{Y��z�9�Z/@9�Id��z[/@9΀-g\/@9]��z^/dȇ�vcQ���r��zb��>
�׾@95��_��cց��Q�[���i)!�
�+��T`/�h���z܈�i_�BT;a��i��(���H�?!�iR9�)b��cz��ic/�hQZ�ρ��d��i��EP���iX:�X:ܾ:T��y��id�6X�e/�{�������@9O��y6��i� �s�f��c���Ng/@9h/@9�mcyj�kF��@9�Ł�$z�mc=��ci/@9ڋ)���<�+ڊ4S�/D*ww�@9j��i�
��&����/D�	��k/{���i&��{D��I���@&��FO��y�Fnz /��i��q%�I��/DΠۨ���l�A=��{S�hf��/�ADm{�ӑ�ym�Kd=��Ah��e���-��%���c������{en/$|o��i	��i�fe� �eO�fep/�h$]���/D�!5�	%�h��?��Z�/D��zUDm;��cq/�zr/�h�A�`��?s/Dm���A��t/fe��Z�f��u/�Vk���$|m�� �e`cf��'H�%PDmv/$|�QҨ5z��w��hl+$|�
�yx/�z�'��$D8���y/�z��-z��{���o���/D׀�{|�Bm}�	$~����hp�/D���u��B�Aj�Y�/�h�{e�[dÀKd�˚ ����݀�h: ���hi�=DՀ��*�/D�=Do���ht�}u�t��h�|%
?�y�܁/�'��-!� �/$|��)ui�)�/F��/f���a*f3*Dmmu%N׷���<����T����h�?�5Cd�g�'��h���h��h���?��6g��h��}�/�i#�;|�/��k�݌��?b�z���?��}��z׀vq�/�z��f��3�;|�/�iXW��$D��F9���ē/�y�����F9�/�i��F9�,�zv !�����F9��}���/�i�%�8g��}�����YU,�]d۩�D9��}�/�z
�z�/�i�/�'���/�i��z�/ ��8g� ��z��(�/�iR�?�Y�ϋ}�/�i���f�/ o�ʣ�/�z�F9�9!�/�i9|�/�{Ȓp��/�i�/�{�/ �/�z��;|�/ �/�{��}�/�i�/�{��Id�/ {�q%?	˳�+��Id�z��)q��3�����
۳/ �/�{���ܫ��^�d�t�mc��{����GZ�ۿ�/'��z�/ <rc�/�{�/�z�/)�/�i��{�/)\���/'���/�z��}*�{ǂ��zo �z�
���i��z�/�-Ҁ-gz��%�/�i�/Fm�/)�/�-�/Fm�/)Y�/'�/���/Fm�	)�/�-�~FFm�/�{k�fԀȴ�%Fm}�Ch<�/��/ �/)�/�-�9!�/�i�/Fm�/ �Jdh�i�/�i�/�-��ih9!�/ mFm!2���/)	���/�{Od�/�-�Fm�/ ��-��/Fm(�~�\J���9|���z�#'��/�{e2�/Fm���h�/�-*�&D�sc��zɀ�%�	ٴ���ٯ&D�/)D�������/Md�/Fm�/)�2��9��/)D9X֫�^&t�ň����-{�/'�"Md)Du*)�/�i���)�/)�/)D��/'�/�b�)DY�/'�r���,�/)�/Fme�2�	;�/Fm�/)D�o�Im�/Fm�/)�/�-�ɢ�ImH��`)D�/W��/Fm�/)D�/�-<�Im�/Fm�/�-+�)|1�Im�/Fm�/)D�)|�<g]�'�=�/)D.�?D�)D��'�/}����/}��Ym��o�/<g��)|���'�ã��ke�/}��)|�x%O�;ug<g�)|�ke�G+��ov}��-�G+0}d�ke)���ޣ5���k�`m0f0}0)D��,))�'���0�'���	0<gA���٤�g%�y
0}�ke&��)$�b0<g
0)D�o��{'-f0-u��1�ã�)|0}0Md��'0)D�o3���Ev0I9�ket
�0)D��ke��ܒRp)��Հ��Im���E�oc��y@�)|�E�)|_��'�H�y��=�Im��a҆��f�-0<g��{$��h�I9�Ym��ߒ 0���	�'!0<gۣ_`��'����Ă &��)��{�r�"0}#�o�Z��'���a�<g瀟-$0}�<g(�H�>��%0}]�`&0����>�*��2�9�N��r�J�z0��X�oՏ�5&Ղ ʋ�8'��=��߸�`�H����%�S(�o)��{*0�`��f�+�T]/�`.��,0I9d�灎Fd6��$�%��S3�b�`-��{��%�H����L��J��jT�	����ބS�a�q%p�ã��`S��%.0I9/0I9�`00Fd��j10����%�|%c�>��.{�h��wۯ����20 ��9��30Fd���4�2���t�2�~�h���'5��-6�o]m&FdӃ�{-|Roe�I9s�T��rc��a70Mm��}�o7�I80oe� .�}ͬo�oe90-|B�$5 g�������jB+-|��i:0oe;�}�0�7��-�Mmh-|5�͌d<�Km�$=0]m>�ie?�Km@0oeA0-|B0 C0oeD�Km4��E09D��`��KmF0�%G0-|H0]m��a<���;oI�-Dz��\�?�MmJ�Km�?K0oeyMm���iL0-|����"��iM0 D؎-DN�}ۛKmO0 DP�Km��-Do�&Q0$uR0�{S�Kmu�{eT�KmU�-D��aV0 Dψ�-��g���a� g D����W�-D��c�X�-DY�KmZ0-|�U���-D[0oe\0 DO�)]�@m^�Km_0 D�oe�9D_�5��o�ӁKm`0-|+�-g��@m�a0 DqUTb0Mmc0�{��	��-�@mRoed�-De0�{Ł�if�@m�-|A]mg�@mh0 Di�Kmj�-Dk0�{>��i��}o�-Dl0�{���m�Kmn��i=����jp��|�&o0�{�o��{p�-D8�������q�Km�	 �)!r�Km���5��ies�@m��t0)d�Վd���M9�h�-D�d|`��tX��dS��<k��a����u0 v0�?���%�	}R D��S��<�$u+�?t&��Dd�w0B9x�)y0 D?���p�@mz0 D���u\;g�B9{0 D>�@m|0 D�@m��O�)�oV�}0}tB9~�@m0}�b�vc��&x	�jt�W����݀�@m�0B9w��ѡ�Md��@m���m�0�ye�$���Nb��t�Ϊ׀����ob��%���B9@����(����ꇀvc���d\�ã�����0}��{[kC�*
��o�y���k�,}��3���$�0)�}h�i��_9��>{�ۉ�M9J�֊0)X�M*G5J�51z��<���ց����i�Lj���>�0�i�0)�0Om��9x'���%)��_9�-�0Om��<����
*�'��Om���0���`B9b�i��I5�w��$�i+�-g�0)��}{�Nv[�ek{}v��eB9	�Yd,��$�0)��-��0}�0Om�0)�iv��}�Om������?U��{w�Kd)Om�	VBX}���Om�0=|�0Om�>n3�`1��625��`�0Dm7��i��/Ȯ�$��-���`�0Om������t�����-o�)��f�:)�02D=����f{�02D���a��=D�0�h��-:����?�02D�	2D	��`���?���{��{�0DmЁ��ۥ0Om(�覰�a�0$|$)�0Omဥ��#�)��i���&�02D��$D>�beg�{6��i]�y���%�0Om9����`�Om��$D�0�'��$D�0�-]�y���yc�Zv��$D��'���{����$D���-�0
}���o��mc�0�'4�y���o�0
}%�$D"��$��$D���`��㹰�`�t��t_3)
}���a�0
}!��"��$��&f�'�0j�02D�0
}��$D�
}�02De�RU%'��0�'�)°)u[�����Qð���Id�0
}Ű)u�0$|�0�c��ɀ�igԯ�Id�2D�-�-!�	&O�02DȰId�$|�0
}ʰ$D
�)u�02D&o�0$|Ͱ��谏%:� ��0 D�Id4�y��R�H�ϰ$D�0 D�0 DҰ�? �)u����0 D��Id԰F9d��հ$D˗f��0 D��
}�0 D=��5��}�0�'���yʥF9��$D�0�'҇3T9R9ΜC��y�0
}BX>&o�0 D�0
}"��c�z�0�'S��c3�����0 D �߰ۻ�S�0
}΂��X>�0[��&��u��F9z��'i�A�����Y���W�i��i�ⰺcTգD��	�6��f΂��v��<����0�%�)u�0�%_��L�Id�F9�)��k��y�)u�)�Id�+]d�0R9�R9d����Id�0�`�)�0 D&t��ۗwws�i�)����%�0 DT�`�f���^�%{��0]d���)�0 Dt�ã�-J���0 De�}��)����0 D"�Av��+ԀТc��y�0Od�0 D����0 DF_/Z�Ϝ�+�F9���y�0�a���y���y��ߺ�F9���y{��c�F9�)ף,
�a�0Vm�0�%�))���.��aL
�`�6|;�%b�a���c*�����cA
Y�f2��c*�
�`���oZ�z��c�I�>���)z�`*�����)$��3i{�#ۇ��H1�?���n�%1�`��*[�B]db��i�Y� 
���)��`)�%	 ���+y�%��@`�)	1Od��}��Fd�')D
�)�Cӧ�c1)Dg�Bd�k����-
1-uՀ�皀�<�Avg��ߝ�א�B	��h3)�ay��-�ܡ�}�)DX|Kk�Avn�a1)D���$j�Od1)D���-��'ǖ���)D�)|y�a-u@"c��)|��c��h�-u��h���O)D1)D��'`�)|:g������c��?! ��h�"6|c�$�)D��$v��1)D���'.�
)!�ˣ^$}(����c
��&��'����)|��c���h�ckt�����)|����1}��)|�1}���Ȁ�^W@9d��Р�c���'1)D���a>�Y�܄)|�cD��B��c��)|�
dȘ�4S��aD)$|1)D�f8B��{��{9�;C�zw1)D1$|"} 1tU�	�	Y��?!1$|"1-uL
T;#1)D��y^�$D$1)D$���?!%1$|��?���&1)D��}'��'�ۘ)D(1��y��zw���?��=Ȭ�c��ap�)|)1�y�)|΀�5��c�=*��c�2*@96��i+1�y,�)|��y����l�)|S��c���ϋ���)|m�~~.������--1�y.��c>�=����B�g�a/1$|��7㯨�a}���J01�aR�1��%o�21}�"��z�c��oւ�%$|�8���}��)�ց�h(�=�$|31�=&�5�
/������{{��{���o����av�E�41��X+51�i�a����4�����-�)3�)�� ��~6��71�y(�i��81X+���Ȁ���yo�2O���9���l��hϋ�<��*X�o
�Ot:1�;1X+���G��=��R<�E�	�i
�<"�=�W,�X�c3}=1�=2շĆE����m>1�if��*
ۧ��?1}�����@12A��}�\d��ho��hB1X+� *�`C1}eD1-|)���9DE1-|ތ9�	�+F1}eG1�h��cH1}e��mI1}eJ�48K1-|�Y�L1�i���M��a��cN��aoӢ���`��%O1}eP1-|��ϣ���`Q��a��R�ke���A��aS1-|T�;|h�U�YmV1}eW�-D��cX�[mY��aZ��{����[1X+��Ym\1-|��j]�Ym^�)������-_��af��'`1�>y�&�a��ab�Ymc1}em{�!���P��O݈�a�}d1}e�ke
��f�-Dg�Ymh1}e���9��ai1}j�Ym	��a����p�-D��;|��<k��`o�	l���m��a���R�
 ��s�}n1-|o�Ym���r�g����;|p1�iq��{y}.��{r1}es�Ymn ��t�Ymo���	}ey}�"|u��k�b�	�cv��av��w1Mm]'-|x��ay1.nf{�z��ay}�-D���ce�!h��a���'{��a|�Ym}1-|��)~��a��Ym����-D�1)D���a��Ym��@m܁;|�1)Dڊe�����1)D��2䆱�{��[mk)D��Ym��-D��O9I
�	��Ym���{��m1�1)D���{�ۏ�ke��-Dy)�ߎ�1)D�1)D��p{}��)|�1)D��ew�m3}��	}�6��1.n����1)D:���}@�(Z#.n�1}�1�J�֌�1)D��)|΂��L�	}��)�}��~ct��o��	}�1kFma�G�Ȁ�^��)|5�����	}��j��)|���	}��	}��l߂)|t��ow�E���)|��Q� w�f���3à1)D���y��	}��)|�1m1S�o )D3�"u2շ���m*��1)D9�{T�15��1Fm�ʸ�)Do��+J�+o��J�{�1e~��ܨ1۩1)}�o����:��� �N_U���ܪ1)D�o��"u�1�a�1)D�1)+;g�1�`>�£���]m���o�)��"u��$���-�1]m`��m�1)����1�a��{��)|:�Y�1]m�1�`Ҁoy���)|�1]m!�+
�I�1�a���o�1�`�%�1]m�1)m۴a�`�&�a��)D��E���94k�t�@oe���c��?D�1]m��ʢ�)�1�a!�[�1�ai��i�W�?D�]m���+�1�iñ?D�1�i���ű�i�1]mDZ?D� ��5�0�%�1]m
Vf�Kd]���ɱ?D�ʢ3���]mj��1�i4+|�1]m�1)�븾;�΀��]m�#�iͱo�a�� m��cαo��@�1�-�1�%�1�a��a�1)��`ӱ?DԱ�bz��O�{���1)ֱ�b�1�a&2D�/]m�/)�`��o�1]m����1Dm'!�i`XU�1]m�	X-��$�1[�1]m�1�{��{�1]m�1�%d�{�1�$�1]m�?D��|;Q�A]m��=�1����X6e��9��?D����1�a
��ۧ���Y���i��ad���?D��%O��
�1-|�?D�����-|�1�i�_9ѬEJ�?D��%.ˢ.�i�_9��/{I��1-|bI9��/�1�iU��z�-���}Ł_9҄�/o�-D��fI
�����i��|
��/�1
}�-D��/�-|+�m�1Dm��-D���k���/����,o���k��-D���/��F��-D��}�1o���a��_9.-|��-D�-D���/�1 D�y��Ղ�_9i'���Gm�-D� D2o?��k���W��-D��k��/2 D���F!�����/��k�-D�@d2o	�-D
2 DZ�F�Q�}+o���2 D|��k2o-|ȃ���k
�"|��{�Aa2`1. D2�D�9ބ{R҄�/2
}_����k2-|2o�_9냿��_9{ D$�+a�-D�-D>��/2 D"��2 D<��/�vU��_9�-D2]d�}�����/�"��ɦ�"|��k9�V��/ 2ob�-D!��k�"��/%�-D#�)�
`1��"|o�ʢ�-D$2o{�]�-D%�)&2 D2
�'2o9�Z�� D�`1��)��k��k��Cv(�)� ]dS�"|;(�-�$ Dm�m)2o�J+e�{*2 D+�)΂�$7/ D,��Ԁݣ��a-2]d.��%�/��`0�)g�f8ѐ�_�����S12 D���%22Vmg��`=��8���@��?؂�%[�)���?3�"|42de52�-ȗCل���X��62DdW�Cv#��a�[\��i���?�)4����d���%Vm�"|7��%Dd8��{�9�أY��?��-��
9�)��l�,:2�V�C��)��;2Vm�Dd<2�it��m��_n)!��M�^v���=�)>�o?��%��)@2Vm=�֨�r��jO�)�U�A2DdB�)�p����mC��%D�)Y�Km���%&���m)[���84bH��f�	 ��}E2c#F�Cv���G��%H��`��撕�%B�)!�{�I��%�рJ��%���	K2VmL��m�Dd�u�5��]_�M2Vmb��,deN��i�)!d��t�Im]��Yw�w���-�ѸdDdy�;uO2
���)�֒P2Vm�gԂ$�;�0�2Q��A2Dd݀?!w���R2Dd�����i�oS��iT2Dd}w�V�z����)�`82D4$|U2}�����ݙ�AV��i6��ʁ�i��Y�W2}�02DX2$|j۫�è��mw��wY2$|H2DՈj�z�F�Z2	 k2D[���2D$|�3�*���֒�ʢ�L�$|΀&D���i�X�S��	��i\�&D���e	�aZ)!]2$|\2D6(�+Ĺ@��o���x^2Y9>��	e��i쁺c_2Y9o��`22D��F���i%�&D��a2Y9J#}X�ib2ˢ_������1}9�#c��i�����a9���أ
�ʢ����憅L���@<gN}?���y���)Հ�����)ɀ�y�
}:�&D��y����'}fGv׀����9����Kvӏ��,�ۧ���d22DȁY���m��p�}��)�ˢe2��f2Y9g2$|h2}i2}j2`8����+&}k22D	�&���l2��Q�NQ�m2$|n2�a?	��v���}�ۺ��)A�a\�Y���&���o2����)~���ʇ&D�fep2}��9�*q��-��&D��%�::�	"!	��ӏD��O�]feo�����Y9�
�%A�a5���r2�as2fe��&DƁ{�t2}���u�&Dv2Y9��mw2}x2Y9�fe�}]fe�����)y2�a���>�۩���no	Tl��)k�i#}��i���������)z2}�����{2o��%,�it��|�̣��"�Mm�x#��-]fe}2Mm���Λfed��{�k~2;DcMm瀜m݀�y2�-׀��&���8��?Fۯ�2�a*�-��ia�}�2�R�2�am�~~Y�Y탲#g�2�-�2�i�ۆ2feC'�������ʋ5���#g����Q�a�2�a+�-�2MmB�%��#g,�)��ie���h��Id��#g�2�-I#ƿ虸a�2feΆ�ٹ�-!S�g#ڛIdq�5�2fe����60�-�2::��Id �&d�ܗ��Oȗ���	:"�1.�Bm��ie�L�D<n��N�`��E��vq�N��Id�h��C��)�2�i�D9:�#g�����{T�2$u�2�i��97ӎ����r8��;|l$u;D9�����?�-������aw���[���+���?T����E�8�g#
������{�6i>�-o��B
u3��#g������T��"�)�,<n��Ԫ���7�-?��Y'%������#g%�#g�;Dk��?�Id��q���ݣq��f��(��#g��’��I����Id���@m��-!�)Du��g#P���2)D��Id�-y�;|
F�(�N�9�)D$��?����Ȇ�V��Id]�ϪT)D�.��%ݕf5��裲	}�2)D-)Dd�f��)|o��fk�����7��m��)|�2)D��dx�����=�!gȀ�2�h�2)D�5�5�	}Ҁ	}�2)D���񫲌?��2)Do��!g���m��)|S�!g��}��m���a
����!gu�ʢ��}���{��fXFm��)|!
��ڷ��S�j��m�����i>���)|h��}�7S�2)DҀ!g����	}X�wU>��У��f*�{�o��j)D���\o���g��h���k�2)D�����Ԃ�	X$;�A�2+D����im�#Dj#€�2)D�Ԣ��>:7)�2)D���ɸ��Q��)|��4�2}e��f�2)D�f�_m��)|8{��2Md��!g����)|� _m��)|�2'g�2MdIJ!g�2Md�S�M����fY��%�	}�('g�2MdDz)|��-��4T����2Mdɲ�`ʲ�aH_ml�,�me�E��ke�2Md��-t�i̲?D�Md"Fm��E�=��Ͳ�-���2meϲ�`��keв?Dj���NѲke.}e��"��#/Md��j0�>Ҳ?DӲ�aԲke��Z����Ȁ����C� ��a�����2Dmj�K$y�?D�2Nvv�Ydb��af���2Dmز�aٲ�-ڲ�a��۲keܲ?D��L�"#Dm'��t��_��g�"u�2�F޲keL
�y� �р��ap�ke�C�4��2me{�oˢ�ke�@d� �@d(������m� �Dm���dMd@��ᔜm�@dYmeMd�2Md�_m�a��.�@d��a�2Md��`�me�@d�� ��љ��?D)�@d��|�ke�@d˃��2
}� �kei�g���a��(Q�NH�?D,I9+���@doأ��ߋ�`{�?D��K$Ȓ��b
}��1i8�ke;��-��*�p��@d�?D��}:��\�@d8-|��-D�-|��wj�I9��-D(Z�e����i8ȃ���-D)a��i��-DX�i�wp*�;|��-D���m����/D��@d��-D9�w�� ��-D�Z��
}����<��d�-D9�%��i���8�-D��ҷ�@d���z�{ゔI��m1�@d���m��b�}��&[j�&��-D�����@d�f��F9���	�-D)-|�
ۯh��}
�F9��h��}m��.�;|5�}9����-D�nqf�2���ݸ
3"D�-|�I9h��}˃�c3-|n"D��'�i8�
}���i���&3+!�F9 ��c샀y -|O�a���D�m13+!�}�&-|�-D)�I9|�i�i�[�a�+!3oe3�%�6�3oe�+!WO$��"|�-DB��b5+!��+3+!�)3oe��-D3oe����p�}�a3�a
�&���J���3oe�-D 3+!�/�?!�Cv��a����%"��b#3oeȏ��@��c5	��]d$3+!�F9��}%3�a� =��%&3oe'�Cv;F��F��}�
oe(�Cv�d��2)3oe��4ʉCv*3oeT�%�F9����F9��}=��&�-foe)����F9+�YmčCv&�����Vy�Vm�]d,3�%-��cc���m�/��cwΥ�.3�a)�i8��&/3+!�3Á�Cv	��z%�}��"|��)ڋ��03+!��)1�(g���3+!23oe33�-��Odf�a4�(gDoe��)}�(gO�a�'oe&���53�/0oee	�%"��a63oe��(g�^�f�%73oe��h� &	 )oe�Cv��)���,€�@�آV�9���h)�Zx8�Cv���)w���p����)U�Y�*�cw���ʉCv93-u��/��m�Cv��h������Cv+�Y� ��)�EJ�����):3�k�uՠ�Ym�	 l�(g٫�5�N��	 ��/ރ��;�?D�	 <�Im9���x=3�f>��?��k}�Im�)��M9+��/�Im���i
�y@3	 A3�%B�?D��h]deÄ�)C�?D��I��(g�]��Fa�	Sp[d�D�V l�ke�㈀(gE�?D���)g�e#����2DF�?DDR����
��)g�?D�����G��H32DI�?D��)J3
2�Im3��);�y�٣b��ݼ��	 K�?DL3M9h��}Ղ�%�?DM3	 ����k�j��}��&D�2D&��3�^��	�=���O��iҋ�{=1��	 ���v��n��}l��щ���jg�h7�&D���N��io�<O��{��݄�Y�&DP�Y���{��&DQ3��R��i��{�܌.�\dS3������5T�&D����{@��M�&D����U�M9h�ImV�?DQ�m8��&D�?DW�Im)�/!��V
]m�hX�f��Y�?D7�f�e#m��v٣Z�&D�e#��b[3]m�Om���%考kmR�\3
�.�h�*��2D���.��%�`8���$�/!�)]�+u^32D��"!b��%��a�Z���=��z�f������_���x�.��%O��{���{���%��&D`��-���ؚ���	�Y�T$f����x��,a��%��-��܍��/���b��{'�䣼��&D5�h��z��Ɔ���l���c��Q�&D.��%�)x�&D^��{=��%�=�tC�'k�h{���>��z�٣�Y�ĉ2��&�Q�Gv��_�d3�h���me3�i���	�qӝ�5zp��n�ۼ�!d$����ݎ�k���$���g"	)9��ۺMmI
Á��-9�l��"7&�-f3�ig��%��g�Mm����m��h3	)�e�Á.��%������[�	)�����&i��ij��$���%W�˕.� k3�,h�i#jl��i5��S�[mm3Mm���Ɓ���	���-��-n� ���YY�Cv?��o� p3Mm��-�o�m�j��Cv�Mmq�ie͉_9r�ˣr3�-X�G\�����s��{���mT��ݫ�ڀJ�/�Z���t3$uu��{�&v� �	۟�Id�٣۲ (�&w3 D�#�{x����f��w�Idy� ���mz3 Dd���`1���m{3f����F���m�Dd|��3�{���m��ӃId}��{~�ۘ�{��Y"��贁 t��m�&Mm��j����efp����J�3f٣�!�S� D��%.t3�c*��{��5�ۀ3 DC
�����i�Dd�ہ��{.� ��
 ���<+ww���i=#ۃ��f��޷��{ŏ0�39D��[m��i�� >�_9��Idı�{��85/u�)�/u��&���yt���#F�n��{E
Fa D��^���y���{�3 D�3f.��{���m}�Id�f��i�ww���y�3�f{f
�z냱���i+��=�Dd�3�g�{[v_$��o٣�39D�Dd�%f�r��Á�3f86|��yq�d�3Ddl)��m��i��y�m*߂ۑ3uC�z�xӒ��y�+|�ۣv��yL0�k�t\�e�3�k�3�a���i��kJ뾋�&!�a��f�X�}���z����=����3�kA�ke�e���a�x#��k����be������k��,�3�a��o��iN����=���В۩��œ	���<�3�kҀ�ܵ��ւ�y���ip9D���k�beJۯ7�ia���w+D�5gO�4��k��-�b~
��y���i���MdX#5g�2Md��J��J����3�kZ��ȝ3�{�ܞ�9D�3Md��=�
)
+�aĉ�=�$�i�3�{e�%p���}�-�Mdk�G�5�Y"Ȓ�mÀ}�	2{�ƥ�j�3�{�3�-�3Md��ۤ��-��[d��Y$Ҡ�-�	z>�eI�{e	2�-�g�kCk۴׀�h�3�i��-\�?!�j��k����F����h�3�i���-��h�[dHMd�Nv�3�k�	 *�
)�3�i�	 ���b�� �
g�'�ڃ��Vʫ��(���k�ƭ��i���b�3$|���-��a���a�\v�ۯy!@9;��b�3�i%��������$�\v��i�3 ���?���Md���?���3@9���"z$|���a�3�i��aep��3Md���?������+.�<��ܺ3�yB2�3�%�{e/Md�3i8�%Md�U29�3�b����z�3�'�r$�3�{��z��zw��ye)���P�q��3�i���3�i³uó�a�����o�?�ij�a��h�3�i�g*Ƴzwdz{e>�	<�[d�3�y��^�(�iɳyeʳ[d˳�/����g��b*���3\vͳ)u����$|�3�i�3�i݀;|гId��FdD#�fȀ,
!��:�ѳ�/��}�fބ����/~&���� �<�ҳ�/ӳIdԳ;|N�ճ�/%�$ֳId׳�aسId��>��/D;�lȀ"����a��/DٳV ڳIdm{�۳�/ܳ�aݳ<���7޳�a߳f�Id���tU��/D��/��Id�m��zX�/D��/:.�i�>ni8
Y5��8�#�Id���g�<�O� ���X�,"n���S��&h�i8�"|���3�N��� �"|�3�yЁ ���/Q��/��j��ihC��!�i��h:����d�^���/f�&;�uY��g��{�"|�����/s� ��{���$�3}e>�?���&,�/D�3}e�"|�	��`��{}e�;|,�/D��$��/�3}e��a��)u�Id�3}e��;|�3�i�"|�Id��۴�hL��$�/D+�Id�Fdw�?���{[�E���-�9��}e4��{���%�3}e�
"D��i���a���%�3}e��i�9��3}e{��	�hd�́nUs�F�	^����a4}e��%	���'�>��a4}e��>kG"T����9�g"D&"ۯ��-5��ahC�:� 4}e��8gl�ad�^�4Vm���{���ʈJ���a�	4�-�C�
4)�����-��{H��Y�hhC����.
��$��-��%�}e��{49!���s�}e5f*Ɓ�.)4}eD�D9��׀e��*Vm4�a4}e��G�4}e��4?����)��.��a��9�4}e*+}e�u:�� 	Y�4}eb%nr��WHA>�9�4-u}��ՙ�ԣT
)D��Y"���a�i?��9�t��%����t���E�Md4)D�����)D��iMd��9���i��9����[4)D
Md��'G���a��%ŋ�- ��i3)D!4f�%n���b�'��Md��)|����"�?DV���
7�o��	F��c#��c$4Md�?�%4�$&��cs�?D��-'4Mdr�?D/��ܺ��ء�c(��i�<g��<��=)�)|�,<g���c����=f�Md*&���*4<g+�)|,��c-4f.��i��V)€�.�
ۯ:�x�/�?D�㑥&c�&04<g1��ca&24<g
����c���yV
Fm3��y44<g5��c;&|64)D���c7��y84f9��c��c�v��:�&D;��c�MdyFm&|9��ú�@db�z���<�&D=��c>4Md삣c��a?��i�Md@4MdA4)DB�&D��iC4)DD��i��y��m8E�&DF�?D���G�@dH�&DȀ"p9��O�a�G�I�@dJ��b��K�&D��?L�BdJ��(A��M��c��)|��<�N��i:1��G��'O��cP�?DQ�)|d���R4�aS�&DT�?DU4<g���c�&V�?D.��c�a>W4<gR��b\�<�X�?z&X��c͍&#O�&��&f���K<gk��e;�<�Y4<g���H��Z4�m[4f�?D�&D�ʣH(&\��iD�n*Ä��L����ւ�yݝ:�
V�&���fFm0��]��y^4`�[Q_�Y�o��	���2��%�&D��-_��'��e��@d��`4�%��ʣQ�-m�w���[�&D\����a�z������w�����%Ɓ��%�A�->��a4Vdb�&D�
ۯ	�:gL�\��{��4bk�-T��Cc��%��:gd4�a���{΂:g���+�{e4�ag�?>�!�d��-�&��:g���i&�fu�!�������;�x�y��%�L�jVdf��=(�<���:gk�!V
�!p*-|.�-}��g4�-� h4�i�����%i��iI�%�Ɓ�%1oj4-|k�G�s��W��EJl4I9m��a+�xӮ�_9H'��n4�c��io��aM.�iDI9b�zv��	.�z� R��dp4�-ϜD��c�7�q4�-r�-Ds�CvĂ2�
�(t�[mm`u�iev��kdÐ)��G�-D���k��w�-D�zx��k��mA�yQ�-��j�y�-Dz��{��aB�c
�y��}�ʣ|��k}4�i��fǁ�%����Q�����؁�k��!~4�y���a�Dd݀CvՂ-D4�i���kw�Sp��f�4Dd���%=�t��-D��y>�!�Dd��jԄ��k��@m��f8*��݇��k��@m�4�i���ȴ����4�i�4 V	����@m���k׀G���c���k:��a��i�c�4-|��@m���k��’�@m@z�		%��eF�ʣXz��Y�����@m9�����a9�����E>�6�*���49Dy�51[�b��i��@mc=ucy1��f���k��-D��f8P�[m��i=u��[m���u�oe��ie�49D��ݪ�@m��-D���k	��{���kj9D���?�4+D��	fp��a?�ye=u�4�y�49D���Ϗ��?�y1o��tB9A$u\�%��@myDd�4��4 D�4+D=uҀ���ۧ�f8+�b���%���X��@mj�
٩��?�4 D��Km��f8��f\��f�@m�4�{�+|�Y��̀�4��9ۚlf��@m$���f�Km��@m����Od���?��!Y�{�y!��������%��@m������*.� D��	&)� :����������$�4B9ۊ��!.�@9��"p9DL
��e=u,����k�����j�+Dd�9De=u��aG"�)�i�4�{��j���?�4�ip��s��?�4�-#3meo�4�����������!��	f{�c���?�4�-h��u�+�b��i)��ҀM9��	fR����c�Uv���㾴ۿ4����=B9)<�Km���Dڊ����CY|�Ҁ�݃ʣЁ�!�4�-B9��?(��c��M9���o��̓�$���ĉM9,�h3�pg�%��)�܉�Fd��<���k�4�-��M9:��w�8e��J�?!ô m���Ĵ��\vc'@9���{�4�kp�M9Y@9�4Dm	�M9Ǵ��e�
٘UZ�����۟��ws�ۼ3Dmϋ<�Ă�b��j�5���m��;\v�j
ȴ�{DmgDmҀ<�׀�ob�*Er����Q"H�$ɴ�i�4�i�@9%d��4Dm�Uv̴�{>�`ʹ�Ԁ�Ԁ��Dm+!@9)���4Dm��<�a�i�4me���ۃ&�	���-�\v���r��	m�w�3�4DmЁ�$�����4��>�G��|9v������h�M9�!
f�4
f�����Ӵ�e׉q���C���{�F��4
fG"մM9������o�$�m`��y�p�@9k���ʣX��e��ִ��m��%��<%���)J����*|w��!n�DmDm״)ul�)��˴�g$��شId�4@9�4
fÀ<�ѧ!n�i8۴Id�4Dm�Vk�4\v	%+|�DmH�&*�Idw��� Dm޴�i�=DR�d~�fP|��|�Fਆ�i��$2�*�* ��aߴIdӂ����iQ�S�4"D�ݣ��KdՀ�8�J��uz���i"D���+�c8�ʣ.�aʉ/DU��z
Y������.��鯂IdוF9���,��<"D�4�a���� ��ŃOmz�FdƁ��i�4"D�4
f#��	�"|h�a�F9紊k�4
f�"D��j�F9�i�"D��"|�k�����4
fS�)>բ
�L>���(l��$͓"|�4��*���AI
�-�4��	��i���<�"D�4�-8`�4�%��-��/D�Id��"|��B��-.�Bm�4�a�Idx���l�b	�	���	25
���"|�/D��bm oJ���Fd��p��-"D�C+��a�C��4"D�4 A�-���l�-�4�c�4 ��-f�3�bџ)$"D��-5�5 ��hQW�XW�4�-�����%�-f�k,�� 
Cd.�-��F9G�`��Bm�4�-J�-d�"|�4����-���ǡ�Ţ�4de�W^�-]��h��)��BmÎ�}	�"|� !p��-�
	2��-N������������a	�"|��ٶi��$W�-|�4�-��?�)�a?$˸A�-���}f��k��(	2 ��?5�-R�i��h����	2!u35�%f$����a��5��l�$A�-��-5�y��h��-��a��A��?���c�̀�
偄Q��?��ۅ�i%����
�p�g���2)D	5f
5f5Md5-u&��>v����z���
5Md��?Z�	��iݎk,�
F�lju՗Fawj����|y�y5Md3������B�;u5�y��?��i����R/f̄�k5��5f{�>���a���O��i�%�?��Ұ�[d5�i�)|���^�>��5�i_f�Md=��P56|ܡ�?)��$�$ ����{5Md��i�Fm)�����a5Fmkf�4Fmv�&5�io���TT�[dl�'&�
��٢��<���{ ��k:���I
�zFmw���!5�i��E���yH�w�"5Fm:�[dO�F�K�F#�[d$� ���t;��%5�i����&��i'5Fm.�&D(5��d��{)5Fm�&D*5&|+5�i�(f=U$,��b	X$-5Md�)D.5f���/5Md05f15Fm25)D��YD��{35����>R� ��&Dۊ��4��{55Fm>��e6� ��b��toګ�
�Fm7�&D��� �\"d��bR�&D�4�	��ʈs�85�i9��{\�!g:5�i���b_��a�	)���b;�[d<��k=�X"%Fm<�i᫊b�bFm>��{���?��{@��a��iA5&|B��-V
�񬁵e9���C��a	�^�o��or���À�D��-E��a�u����FmT��a�����&�F� ��aG��%�Z���aH� V�%}�x�A��Հ
��$|p�&D��&D�{KI��{c�Bd�-\��a���J�&DK��-L� ��g�Ϥ�{]��b=��M5��b� v���w���S���A��-��&DN��{m��(A��m�(n����ۨ��u>��O��-�����$P5K9��ia��{��í������Q��-��3���i����щ(n �
`R��ai�=��`��i; ���n����S5�i���b3��۹��->��-Q���
&T5�i^I9��}�
IU��-L�
2V5�iW5-|	��Y]��a{�@9�����{�i���&��c]�(nI9���%� :X��-Y5�i)��}Z5�c	�cS�x�[5�i��-\5�'�I9]5�{��5�^�;|��x�_5�i��6�XZ���0�mh��u��$`�ie��xӨ�㚀�a����x���{a��{���e��x��b5�c����۩���>*�b���'�hX�%�(nL��(�
�c5f
/J�d5f����3�ye5�{�)�i9��l��݆�{8�cf5f���i��㿃��fg5�i��{…T2����h��{h5�ii5fK��վ����]d=�{���j��ik5�{l5fI9 �
`m5�i���{�W�n5�b��o5�{���9��{���p5f����}\ �UT���a'�k�{�#�a������Z�cq�8Ɓ3���{8�����I9r�"ns5�b	-|t5�b��'u59DB�{��H�5��� �yv5�aw�ie��?�#D�Y�C0����}���@��x�"ny�iez5�y�ӣ{5�a|5#D�p�#D}5�y�oe~�;|i#D�"n�Cd�5�a/�y�59Dl��i,#D&:�َ�E��5�a���i��Km�5f�5�a�f�>�Q"���#D�5f)�{$�xӇ5fQ��?��Km���i�5f�5�{���?"9D�5�{��d.�	9S��5#Dj�"n��x���i���i��x��Y�����`X��݁x�
�b#D;�x��Km��T]��Km,#D��#|e�Ym��Ym[��?+�x��ar�Km�3�B�x�	��i*�#|l�"n��xd�����\CdބSɌ��))+�x�������#D��+|����#D"+D�+�{)��}�#D�
�a��g~�5#D���i	3zC����i)#D_�9D#D�5�a����+D�5#D����9D���i\�Km�)9���G����?�X�ȥKm��Y�;�xӘ5mee��H)!��Km��T 	��G���?g�x���c��Kml��񫀽���{�N��k:����G"$�x�Y �ܣ���d<�M9+�xӛ�M9�?D?�uC\���?D{`m�aރ2ã����%��M9o�i�k胨.v�?D���Ӄ��8�`��$:S���To��M9��Ǟ5�-'�j���i�5�?�Dm��M9)�Chk��Dm�5�?�&n4Vd�$:>��cZ�	��F٢�M9��oj�$:�5�?��i�5VdՂ?Dd�Hr�4Zs�fo��y���a��9�;��yΟ�`,�M9���a��%��ܧ��`y���P�a���+.���Ԁ�o�5�?���a��@d���5�ݫ��aI
㸬5Dm5Uv��i�W��M9	�i'�۰�n�ף
�`5Uv�5Dm��f�M9��k�5����a��M9��'��M9�X1��`����߄M9��fp��m���Om���@�i�5�bS���5'|��Y��`8=�T��X"��y�	o�5Omb
�i��?Db�W>n/|?+���4���i��?D�5�i�5Om�戹�f=��Ę�i��	f��iA�fЁ�w}�	r��4Omw��+��/D��'D�5Om{a~��	f�+Dm�����/D�'D��Vd}��/D�\�/|�&����'D��	f��\���f>�\�}���k�]d��=Dµ'Dõ	fx����5��զ��\����4�������ŵ'D��'|�fƵ'D@�/Do�\�8�>
�c˃\��F9��أ�5��o�\�=��/�5�iɵ'D��\����<��F� Omʵ�)��=D*��gOm˵'D�C�����5�ջ/|�۹�\�͵'D�۾�)���)�5�cx�'D�5Om��b�/(�'D��`o�\���=�f8�5�c����j]m�n�	)΀��Q:��cѵ�y�	fy�-��9���׀�ҵ'D�5�-݁�'Ե/Dյ'D����́�ܼ��ֵ	f��f́��o�\�����5�c�5�-@���b		)ٵ'D���+ڵ	f�/Do�\�G����G�۵'D�
u0@�.��D>�\�)�'D���j���%T*��a"�
����F9>�\��5�-�-!��Z�ãU�=>�\���}+����5�a?��_9�ܣ��cՠ�f�5�?U�a��bރ��5�c�����5Vm_�'� �-�5�->���5Dd�ުI�?h��5�aQ���5Vm3*D����5*D�5�as�g��u��5Vm�):l*DC�F��Vm�5�xJ�W���,�5*D�4Vm�5�xh�z>>�}�5Vm�z>���5*D$���J����5�x�-W):�,�-�z>�Vm��S2s�F�p�xXg��*D�z>o��5*D�z>�rfr�*|�*D����5Vm�z>�5 �5�y�5Vm��}�5
f��$��y��}�5
f7�a����5*D�
�����4h�z>���݂*D���L�5
f���L��a,*D@�ȋ�*|���v�Y�]
f �5Vm��*|{��'j�ʋ��$
f���'^5�?ߝ��5
f��e�5�a�
 6+Vm��F��ww�5Vm��F���֣]d�*D"
fF��'���y�	���*D�5Vm{��'�5Vmi*Dcܣ6ۀ6Vm�*D��yN��`*DXW^�+n�Vm�*D]���?�r��Im��'�z>��+n�����al���6������ψz>6�k�*|��+=�����̴6�k��x�	6�a
��i������'�Imʋ��:���ã�3�k6�
�i8�+n'۶���-@�x�6�a���[���L��;�'���6
f_�\�x�닦+�<��gY�
f���6�b	X$��k��x�6Y9?�6�k��yp��iցbe��$���щ�����'���i�*?�T�i�6�k��x�j��b6����'��iv��+���}��'��,$�����i�Imh�G��?��k��i�Im΀����$�
�9H�)!D�&B>N��j��}���η������oҀx�=���-:6�f��-��-�C��fƁV�R�x���-���Y�g���[���6�k ��i+�x���<��^�h���i!6�kR��c?	��"6�k!D#��-�/�-@�Tz�-:���ׯ�ok��x2��-$��i%�Bd&6�$'�̴����/�-o\"��֣'6�%%���(6�$��F��.|)6.|��2��$i��R�?�F�*6�-���?+6�$74.|�Q�,��-.|}�?6��?-6�$l@9.��?��}/6Y9;��b������-�Y9����.|뀞?p��́.D�(U;�!D>.|�G�0��?16.|��-:���G�%����������}2�G�΀x�g��?9#Mm݁x�C�G�)��36.|̑q��Ev���Ek��?+�x�)�-:�MmЁ��4�.DA�eD5�����a�Y�6��?���-��.D���76�-Ÿ�b�Mm�x�a`k$�x�8�G�9��x:��a>�\�� ��[m�2Mm;� +�x�<�[mm���Z�>�\�*6�-=�G�%��{Χ ��`8ᬩx>��{?� e�i�/�-@� ��i�\�A6�-�\�"��)��?B6�$���?��Q"&�j���[mC��xD6.|��`8k�����x�\�`6&E��aF�.D)�٘�v���?G��iv����iL��?��č {<.o�\����H� ���}���I6��J��{���;�x��Fd��[m��<��V��K�+L6�}(�x�׀G����b���G#�in�yM6Mm��x��i��ͳ�G�J��MmXJ�N� # Mm�����io�<�Fd4��i�Mm?�=����}k�\�*�i�����-O��aP���Q��a�\����x(��j�9DR� �[m�S��{T��xU69DV� W��{X��a���Y69Dj9D��Z��{��]9�9D[�Id\61D�!�->9D2��Ł�<]�Idi9D3��a{���.��Sֳ�{r�Ym���a���$^69D��_�Ym>��}���%�1D���`69D<1Da�Ymb69DX+c69Dd��a��Ym=���e69Ds1Dm�o��Gf6&g6
 ϋ�)�����Ö��Ȁ�<h��a�uCl��)i69D��Ym���Ԃ�/�9D��Ym��o3��}�Ym�9Dk��)��os�X�j61Dv��$��	f��Q�2
 ���k�Ym���{���`8l�:nm۴�{���c�	�����cd�?b��m69D�[�t�HP�F�1Dn69D>�
ۂ�]9���)�q�2i9D�	f��o
 o69D=�o�1DP5�=9Dj9D"�}�o[��?�}9Dp6_m�˜
MdʉYm��o"�:n<��/V�z��Ymq6Md��עr��a�E	��Ys6Md��G"t�	f��`8w����ڊeҰ�	fu6�bb�Ym�'�v6�xm�ow6�{���$(�bm
 M9b�bk�����4x�)#�x����y6
 z6�b{�?D3�a|��cR�ie��}v��}��c�bH�~6�{��c�6��>�&�c���c��? ��p6_m���c�c��À�)I
������)��2�{���cPDm�6f�6�?�ۆ6�b�
�k��*-DmR��&�B9��c���c��I"�6Dm�6�?<��i��?h��c��}�6�a���h�i
)�6�x��Q���c�6Dm���c��}E3�?o�ã��Σ�6�?��`e��}c4�����6f�_m>��}�6�x퐣cG�@$���c����6�x�6�?�6_m1Mdۘ�W�6Md��z�an5ZG�b�Dm����6���]m5�a�6�b�	
f�6]m����k��}���c�6]mD6=|۞��c�6�?�*]m�����6]m���c�մ��"nӃ�}�6�a��?D�(]m���c��?D(����?���c�6]m<ۨ6�?�6=|Y
f���c��)n������+֟�i���cg]m�=|B#��3���6f�&F�5|@��)�6]m�ۭ6�?�=|�6Dmd��i���d
f��i9���#����6]m���5��"j#Q7�m�-�)n��մi��V���n�����@$�&ϱ�"nG��i��`s���ʐ�!Y=|��~�a�(���ܳ�`eT��F��`e9�a	����"nց\�׀���F9=��&�}p&�R�F9��}��Ě^5]mw�W�X�7ܵ6���6]m:=|�J��6
f�4�a�#D9��B닫�6I9�6
f���{��{��?/�n��F9���ݳ�ܪ�6i8�X�5�̑1ѽ6�a�i8��#>������������$+�%�ٴO��{��=De�$�G���Fd���VA'�:4S�6�%A��%�6�$���{��}���,�cQ�2o���6I9�6�%k��ݙ��>�_9+�cwA��%Q�!gk��>��}¶_9{����6�c_�$��}�$���7�i8t���Ķ 9�˧�`��&;M"⁦9�cw��⁦9Ŷ!g�
ۯ�6�x�*瀅_9)���}?�b�_9���,�Ƕ_9c�_��c��g���%�6�c�3�ܙ�,(���m���i�$�aS�jt�68Dʶ_9˶ ̶�a��
oJ�����o[�v_�!3��a��}�i8�68D9w3ބ�Ǎ3�_��a�68D���ݸ�}ʋ��9�,�8D	8D��}϶�aж"u�8]��a���6�c���6�-�68D�$�68D<��a��_9�ٴ�_9ڏ�?ն�}o�8��68D�6�$&��>�_9ض8|�6 �68D�f]g{:�c��i8"�_99|_8Dl�8|���HÄcw۶_9��&u��ܶ�a��,Dr�������  ��8�j`�6`1��}9��,���?k��X!oe�NҜ �}5��o�}h�f�Od,�i�Avk��}	de�&�6 �f߶Imn��`��'D��f����68D��}����Av��N�x �6de?����$���?���f�Wt&8D��}���'D�68D䶅?����j*�8D_��aĆ�嶜?�f�Av�,Dց��%�o��}趖i鶜?�f�)g��?R��i��	�ڴ�6�{��ۇ�{����Àe#o��>�8|ΟAv�6�{�,D춂y>\"
�6a4�{# �e#=���&�y8
ɸ�Od��,M.�y� ��>z����}/�n���c�6 /�4le#�6�}�6�{m��>):%��c����BdM�{96����Ђ�)����be���{X���z9���Av���i�f��iz��b{��c��?��e#��b�_,�G�X�ξ��}�6�{k�\�g�f]�e#R��o�\���Im[�e����
�m8o�\��
{��&&뀘��6):|
j�e#�'Dk�\��e#k��Z�����\��)o�ʹN���k�\����$���Q�m8o�\����6�c�g���$�X��"���6�}P���$S��-��m8�6�ft�ʹ���}>������$@���c��諀��6�{$�����$��be!�\����o9�m8�$��<�v�_�&u�%�*DQ�ŏ�Bd]���):82�<|�
f7�%�!�驂�
5Z��7���<��
@9}��7m8o�j�$k�\�7�$7<|����׫ˈm��@�<������S2o�\���%b<|c����<D7<|(`8>�\�7<|"��o�*D	�<D� k��}��$����S�<D�&݁<�
��}o�\�U���T���<Dʁ�}�=n�\�7=n���N����Ev>��}e��$\��&���
7=n��41�{���%�}� ��$m{��}\�����ܒ�d7=n&�N+Ă�ag�[m�<D��%>��o<�<D�)�l*�u��f7�%7=n7��r	�d��<D��}7<|Y)h�ף7�%7޴���}�?���%7�$��
��	Ev7<|ރ�����|�W��{v�ʹ7f`�ٹ<|7f��b���c������c	�im�۽fe��<D7�t\7f�=n��iŁ<D<��
`�z��Fdc�Od7�a?��e0f �<D!7Fdd�iE�F	��5�$ Ł<D�Fd9�V\"��bۊ4S#7}�<$��b%7��&7Fd�5�R �U]P}�f`{ӫ=n���=U�'��i�Fd(7Mm3!k��
f��ۂ�c�<�)�
��Bm�+�_*7�b���y��`��`�Fd*�b�=+��i��I$�[mgFdD��XS�
fhX�,��x�A�,��c-�[m[���.��x/��b��07f�fD��O��۹)!�	;1��c�fԀ�ه�`
�}2��c肁b�=cf�/Fdo�}���ck1{.�	�feD۬��}�&�
)��]f����+�Ҁ��hf+Fd��b h37ffe��F�_{�47 57Fd�H���-���O��bwEg67Q�
�77 �H���c�gF��}�e���ȟ�� ����3��}{���@dD�G"8�_(�H�� \	f
��`(۴97 �����ʦ��}:��Ժ۞��҇�;�Hv��T$��)��=
���#z����ۂ5b�4۲_��+<���,x]<�g# �">!D��@���}��?:=7 =�"��$�����)D)�-:���)"2�O�~�����}���ܷ�k]$��q�>7me�����HvY�?:��)!��H�?7�a(�H�@7�ai�yA�Odg�.D��bB7 t@"ނ�S��cC7me��}:j��j!�x�D7mes��yme{�.D6��o����Ё�cE��yƁ�����}9��D�㴎�HvF��yG7 
)gb������:�H��}*�Ģ�۽��d��{H���۽ݑ�I7���hr�e{�N��T�iJ7�ib6�e���	�>��i���{K7ۚ�iZ$n��L7�bM7�+N��"z�>D��x��}O��{P�TT��a��>�ag������:��aQ�t���$�O����8�aR7�$�DS7�iT7�U��{V��a��j����5O�	��ց-h��pme���dѸW��a\�{G�
۫�X7$n��k�G�Y7�xZ7�c�me��{��b����[7�b	%�v��������g�{���\� ]��xv��a��{�ێ�>�|����	f��U~a��"�ܷ��ۀ����}���d娑"�y�)��^7� �_��a`��a7�i��i���"b7�w���p�)����}�[dȀ�^c7��@���'d7�ie�E�nw���"���[�>*��af��aV��a���%ۆCg7��h�	fx�Kd	�ii�kee{�W$n��KdX�e�j�Kdk��a$�%DVF��f5O���&���fl7�$��	fm��a�Bvo�\��
��5��a�	f���-n�Cd�Kd����ߡo��ke_C�o�Kdp�Cdq�%Dr�Cds�ke��ᣲKdӃ�}�Kdt�Cd����u�f���a��>ω v�Kd�Cddつ��+����h�CdA������w�Cdx7 :Q��=J�#y�&:�
k5z��c)zX�Kd{�Cd���b	�4a�}���%����|��af�`�5�:� ��b���z߂�}}��{��i8��f~�%D������ =�}��c+� ���KdQ���Kd��f��keD�+�%��cH�Kd��Cd$� �7�$5�2��ke��Cd��%5�2��m1���{��c�i8��c��KdE��=5�2�ۂ�Cdc�7�%�i8���b��\��g*�񘆢�R��b���$� ##�{�7�%���Γ	�ܚ�2��$}�d��P�f8�2R� �i8����!
�<o2���c�F�3�����?	�%ԗ��7�c��
ۢ!�c[t��Ͽ�&:�.Fv=�O��ێFv���;��{;� w�f��ی��b�G����}[����c9�[��5ak2��j��bl��5���´�h���{s�/�#z���?�7��a����8
/u����A�%��R$;�c����|de�j�7�a�7�<��g/X�����7�%	%g���hY����c�%i�$�?zX��b��"oe��$��2�z�ո�7oes�{
���7oe���X����`�Od�7
f� �7Gd����7Gd��W�ܜ7oe>+�a�7 �7Gdo2Gd"����&uoe?�G�]dӃ�i��	%�
f�]d���i���v
���i��܎
]d�7oe�7X����i�7
f�7Gdoe��
�
 �Od�7oe�7Gd�7OdĞE��X��de�Ym���oe�7GdT�Km��g�Od���y�7ft�#z=��֩7Gd�f��Kmm[����h�Od�7oe��R$�#zY��y�{��Ȅ���7�{������X���a������Km����� :��ݻ��㡁���R$m�{Y�f|�X��7oe��`�d�
f'X��7oef��g���ܲ7oe�ң>�u{��� �� n�7Gd�7X��7oe'
fH�%��i�7�{eq��oe�OdI�{�� n�7Gd���iv�{)�z>�ۇۏ�i���i�7]d��)n@��c5�{"���v�H����&���ֽ7!Dx� n�!D��"�{�7�{'�)n!�۹)!���vң��5r�+%��cW)!o۰� n���ae��{u�KmC
�p�!D	��b�9�f�v̡���7)!8�W!D_�7f4�m�
�{���%���"I"�7�{�0ft����vX�����%�{�7f:����m8v��+������`N���\�rSb��#�X!Dz��%"�E�)�z>�7!D�
ve'���R8D��cŷ n��b�z>f��ca�2�7�-)�z>Z��$�I"�,e��"Q�{���2���7�`��f8Dȷw�=����7�`k�� �������	!D��`���h���,38DR�>D�7�`�&n(�%�>|�,D��`a��˷Jd
�`O�i�S��76zo2Gh7��b�7����,D�78DƁ��7!D��>DзJd\"�%<�Jd�!D+�Y�E��7!DҷJd�fQ���-nӷ�%���7-n����+3���ČQ-nʁ�b��Jd%���7�i]-n��$ZңR7�$5��7�`�
��,D|�$b���7�$^�Jdط�$��!|N� ��ieٷ8z�Jd�7*�������4-n�� �7-n��ܷie�� I
��7�%(�$��޷ ��d��+:�9�'D*Y��7����$��[m�ie��Xpң� �>D+��� R8D�ie���	`�Jd��գ�)�c�'D�Jd��j�Sh��a�Aʉ'Do�"�f8Q�,Do��+�7�c�7�c��>D��"e�Kv�$���aЁ�2����c��k��,DƁ�����'D�i��aZ��+���|�7\G��.�$��Qj��ak�"�7-n� �7�c,��{y1�7�$��x��7-n���O�"�7$nဩx�x�P�2�������x�@3��6�9<Ё�a�9�oߴ@3���� A�Y�c�xӤQ�� :��&.�腀�めݏ� e����Q���8z�,):k%|� I���V
B�M"���):3��Q��n��u�G"�'Dk�"�	fׅ	f���%��M"����%D)�����$NW�A�	f%�"w���%�������a@�c�u�U�%D�7�"���a�!�i���7�c9�`��Q��%D�ң�7
 5�x��%D8�cЁz&��%�
´#�,�%D�f8�.�,��%D�f8Ȁ�	8�xӿ
 m[�kң��}8Nd8
 �%Dɀ&:�%D�
 �!Nd��f�d�4Sg��ŭ	f���X�򅀸㚀x��%D�y�=ރ{}	�%D���
8Nd��V��&:�o�=�8���+�'a´s*D�A����8
 ��
8Nd82�	f��8Nd��_�5�52�%D����i�me�%D��(�,�����w�8me��Q�8�M"�	fZҳ96�8mec�����$�%D�%D��%�v싀� ����	f��$��`��$���ҡ��4j!�%D�[����$���ϫ�i�ww�z[�������$O��`�%D��} �[d�F� 8
 ���!8Nd���_U��6z��G�Jң���$X{�"��{_�,�{#8�,~��6z��V��`��z��fPm[���'u�F�ADvt��$8Dv%8�$�6z&8�'8DD�Dv(��`,(=zƊ�`D����f�)8�a
��+��8zl�j*8DD+8DD���i�۹��b$DD:�x���x�{K��a�&����,��$脢x���-��i�´.8DD�$´/��.�C���i)�a'��Q��08DDI����U]��ң1��xl�x6۽�c�1me�	�o��8z���`��a�[d���i28��38DDf�����o�i�(|�6z[���]d�$ң*���o�u
�{e4��i5�8z68
f7��$8��i�����2���9�(|��`t!�a:��i;��$
f<�[d=8�aj6�>��i}6zs�[$|j�að?8
f��a�)n9�V�@8�z��v=z��(|��
g���"_䨁�R>��xjDDA�YdB8
fه�"o��x�zӑb�DDϒ�iC8DvD8�$"��b	})��E8�$F8DDy��迚�iG�ye��i�TH�YdI������iJ��x�*���bK��xdž�i���v���Z��L��x��"
�YdҀ�b{�a�8zĉYdM8
fՂ�,&��'N�Yd����O���m��bX�;O8@"��F"� =��%�@"�A�a��şZv���{���&.�褃գ+.̣ȃ{k�{K���.�Zv��Ё�u���ĕ
fP8��[��-:��`�AQ8
fR8��۽S8
f3�i1T8�`瀞?���۟F"�� U��&��zw��R��&V�ZvW��b��&X�)nY8�{g��	�9��	}��
�#�%o�cri8n����գv��yw�{ه�"�����RZ8�`������$��([�Yd��u\8�`�6zo6z	%m1�6����b]��b�i�^��b�z�&.��{_8�`��e`8�`��{��fa��y���b8�ic��{F
�d�id8�8e8�{f��yΥ�uD��{Z�+v��գg��{�ih8�i��y9�-�8zi��>j�8z��bk��{��I���)�8l8�i��X��9%�+܈.D��im8��գo�b�6z(���G�i���`��oP�i�i8����n8�8o��ap8�`�6zm�8����=zst.�iq��{k�E��$�c"������am�r�,DSX-*�3�k��(����)���۔�is�,D(���y��{t8 �"�G�`,u8�{%��$݃�[�& v�,Dw�,D�b~n�i?k���d}e*v�x8 ��,D^}e(�L��34S]d?']dy8}e���{e�v�z��{Ud�,D{8}e6z|�,D��{�B}8�i~8Ud��{�8]d�8�8-��ar�=��4'�]d�,D���{��Z�Iv�8Ud�8=z�
����a�8}e�]dH$)�
ه8]d[��E�f�8Ud�8}e%�
ۊ8۫F����k6�-6z:���8Ud�8�b�8z.�Ym��,Ḍ���a<��a�,D=R퐸�k�8�b(����	&��YmV	C�%��$C�����Q��(�,D�Ud���8��,D�8}�)���y�m~=����-n݀Bv�Hdh!@9�8 R��$��,Dk�8z��a�8}e���k?+gP�8 �8]d���8}e �)]d-ࣜ8}e�£���c�8Ud�8]d�դ���b�}e��'��a��b�UdjE�9�x�8Ud%�?ݐ�bo��+ŋ�/�8�b�8]d�UdȤ�Hd]dB��c���a���c���bk6z�,�8�,z��a���kW��/���b��?���a[�f;�?���k�8�,�Iv%�?	�c���y�z>��?��{���bo=zn�{�8�,���{����ȣ���~£�<Ҁ�c���*���-(���j��g�ѣ��y��
:�_
VQl	`%�?�@��j��8�,%�?�8�`�8}"۱8۸��	�*�{���r+�5��3���l,۲�8z�z0�$���{�`�8�{�`8�86zπȣ�8۶��%���c}۸8�`��?�8�%ʋ��".�{�8�`�7�գ��tB�8ے[�%�?t�Yr��{Ղ�b���b��?
�Q��'6`8%�?��"n��7��b<��g���-�Y%�?�8�`�8�{8�R�?��:|��{M�8�,¸XdR�?ø��{�8�,md,Ÿ�-o�Y?��Z��oƸ/:
�,ǸXdw&���-�{�8�{�X� ]m��%ZMv�8�$���2����%w��I"�E����LXϣ	��ʸ�-�Mv3��-΂Y��8�$̸�%d��>M��i6�"n���[z����a5�=�Mv�8�a��i���o��%���z�a~���i>�E�θ8z�md,�;nk)n����{��+&��8�`�#D�â���H���8�yI�$�,�`�a_��&Ѹ�-�zҸ;ns����X��+(����8�y�_*�I���-��H�k���'�8��5��)����)o�u�2�Mո�-= �-ָXd��cX;C��׸�-�8�a�8��8�y���޸��a۸۹�Xd��]d�8�b�Y%�-��,
O�3����8£�f�Y��a<��+�'�b޸�6�/�y�Mv�Y�lG��۽�����{\��{c�����v�8Mv�
U]���އ�{a���΀�.��{:�$�Mv8[��8fḩ8������{�������i€IJ��(���{�9�#�:g}�訁�8~��)�yr=]��4S���Y��Y�;n��{���>��u�8�y�;n���{>����8�y9�I"{��渇?����8�-+�y����T�ce����5��=�8�-�83D�8�-�8f[�U��k���%�����C��,�83D��{kY�_9�8fX�8�f���k�8 �?Y�_9�8�b�8 ��<���c�8�b���%X��,(��~!F�
��?(�������ʰ�,�8�6�\d�8 �>|���%�8 ���k�88Do��iZ�i�8\d�8 �-8D���k�  <���*��%:3��8z�8\d?��{���kQ��,�8 J��{���k��.����>��fQ��Fm6=z�>D9\d$��?���9\dB "�sN��kt33����F���%�>D�f��?�	}��>D9\d�
����?��4i;��?���4�	9\d��f[���/
��ke�8zD��k��+���k9 ^5mey�E����k��b�*]vu�7�)�f
��`�\d��,�f��,��k�	}��k�7 ��?�>D?��%9 ;�{ey��kX����^��`�]v9 ��{e���{��?9\d9 ��`�̣
�'& 9]v��$�h ��Fm9\d ��`���� �=z!9\d"9�?#��$$9\d�{%��?&98D'��?���gf=z\d(��`)��{���?*�fl�'D+9�{(�?ယb†�݀�/+�Y�8zN��{� ,�>D-9�{�1�{�7�l�Y�d΁.9�{:��_.�fr���?��{聢x�]v�i/9�{0������j	21�����9��x�d�29�{��+3��x.�?�&�*�{ŋ�io�Y���&���`T]c=u-���z��cg��?j�beo&4��{���?��ԣ��z�Y":�͸������£.��{�i,��i	��?59�{6� ��k7��{I
΁X��8��{
����>9��y���⁇^�� f��$� ����{�j�)��{�I�cI9:9�?�
}t�+��+�f��h���;9�{9��S<9�?#:�=9�{O�?k��u����ܠg�M�{�Z�%L�>9�{?9�O	�%*��@�AdH�?!A�f3�{�1�d��B� ���������_*ۘ��z��M"�&�����Ĩ)��B"%�	�Ad����e���C�毘�j�v��u���Q�4���P�f�i�������]�	f|�X��X�O�	fS�Ad�����S3��	��z(���{ k�L�׀3��9TE��y=���	h�D9*D�(z*��À<D�g�I9E9*D� F9�`�*D�΁h�M"�����2},��Hi8G�ע>Ndw��~�ǘ���B*D���H9i8I9�%O�`�h~�i8J�	f���"����K9�%����[� o����i8�����uL9��M�	fN� T���O9�`�"|��Zޣ��`h�{P�Ad�!�{��N���Q��yR�Km���C��5S��yT9�`���>����$[�%U��`V9i8OfM�(�a���{=z�W9�i��%���X9�{�,��
��`(z��Y	�a��Y9=z��J�Z� <�-[������z��`\9Dv�*=z��i�,��]9�i�� j:D^9DD_��`v£�t\e�i�ی`��`�j*a9�ap�z�b9DD:D�DDc9Dvd9�aD��e9�iЭ�d�DD��f9:D�A$�c=DD
n>g9DD���=n���h9�ii9Dv�գj9DDk9�`2�:|l9�i�	2o��om9�{���?n9g�o9DD�g{p�(|q9Dvt�,Ȓ��zg#.��ݫ̀(z8��DD�[�r9�{���?s9�ig�:|��ct��$�Ay�-��~	2v���=F�u9Edv�:|��w��`�ix9	2y9�iz9�a{��$|9�i}��/o��_��&��-~��/"��>Ԁ��$�;n�:D�)=
f9�i���/��x�)jDD��ہ9�i�Dve=���/�9�-�
f��-Dv���/�.DD�9DDw��/Ԃ���Aq�B�9:D���/�-c�գ5���9
f���b~X�(|o���f��+��b~���/��9�
�I�'#_
���o�7e�z����6��c�գ̈́�?9S��y@"v��>��F"&��`�*	2y���J�:�k,f��:|�`����F"��:|҇��9�-�
XҎ� n)E9�";ؾ�ִ��(��F"
fl��x�� n��F"�ۋ��R�-�9
fi1���xx
f�� n�9ۍ�i�9�?E� nS��]
f\�?��ܕ��/�d*���/o��xjۇ��q���D�ڣi��{���/$!D�F"��c���>���/%���$�Y�Ä�bm��/˝)n��.D������>
�9ܛ��{�9�{���� n}0f!D9���5۫���9!D����q>%���!D��Y�:����{��&����=��4��w��y�q>��m8Ɓ����q>�9f�9�{&�m8�9f�{ͳ n��i��?
F�4�8�� n)r#���?�i��{�!D�)�i��84� n�9�-Z!D���%��q>ތY����{��q>�o�q>���x���%K�i�� n	�{D�ִ[�Ӂ n�0�y�9�-�Fm�9�i`�9�`��&�9�i�ޣQ�8e[�c*�����{�9�-��\	��=�s�l>|�ST%�Zd���i���B
��/�>D�!D�9�-�f~�8B:����Y�*� ��>D���,D���i�9�-�9!D.�{�A��!D냤�� �9f��>D��s�m8�7��f�)�>D�!D�Bv��>D�9-n!)W���-n#�Hd'�,D���ρ��b{�,D��>D��5L��>Dة�i�9�iDz�i���a�ׁ{۶�i¹�iv��c)	��a� �9UQ-n������Ĺ>D���a��i}>|gC�	C�9�-�x#�9-nǹ�i��$�9>|�9�-ʹ�i�%�`9��-˹�a�9�-���͹�a2�Iv��
2ւ�b	��a��i�kι>DϹ>D{�,D��Hdй�a7�/��9$n�ke�x#Ԇ>D{�ҹ�aӹ�-Q��:�����>Թ,Dչ>D@
}���dֹ�-Q�,D���aT�Y��Z�8�>DT��׹,Dd�'ع>D���-@� 	}�����������$nl�6�۩���ٹ�-�Հڹ�i�?hs��9�3�Y��۹@vߋ�{���aܹ�iݹ�{UT޹�i߹�a�a,i*��i�aii*�@v��{{ۚ�@v���*��a��i�}$��a�i*���x��{���a�Iv_��i� Ȁո��a,i*��n�9u>�����{,I9�Iv.��b@�7���mu>�Iv��{4���,i*m��z5���y$n?��+��{+�Y�vi*��{˝	f"I95�=n��{�$n7i*��d�d}�Ad�:�̚�{)�%Dk�ܠ��{�d8
}<��%D�9�hZ����8�9��@�h��%Dբ����9�h·Ad�I9�9�iA���%De��*���Ad�Ad�9�im�T磆��n���{l�hԀ��Ad�����Q%�9�i��Ad�i*Y`�9�h����i*��{��93�^�%D���{��"n�9�h�=�[���%D��Ad�� ��n4��4�h�i*�Ā�9�h����J�E��D���b�b:�aS�hy�i�!�i�`���:�aj��At�:�i�Ad�����b:�b���r�%D�AdH'Uc�%D�%D:�,;��b	��%
:�a�%Dc�b�%D$��x
:Mv���b��o�#Dt�;g#:�b��i��b偹�Mv@�h��b:�h�%Dt
Ģ����%D:Mv�f:B"3��`��hX�:�a�i:g���bJ����Xd:Mv�3���,u�݉f�U���a��{�Km��b����?��i�#D�<���i����:�,��i�g�b����=�e���#DC��%�e�c��H��i���YЂ=�:DD:�{����:�?
�&��? :�{!��b":DD��b�	�{�{#:DD�"n"�`�܁$:DD%:�{&:�?"�k~':DD(�x>):�i���*:DDe�x>���b+:�i,:DD-:�_ʦ(|J���"DD\��<��{.:�a�DD���h/:�{YMv��{�`0��i1:�,2:Mv����x>�t�Q�3�)�h�[�`3:DD4:��5:�{i��<�`�7�{���Ђ=�۱g�ی��8��z�
4SfEdo�{m`����)6:�{7:
f$�k�
I"��{Ԁ��8:�?9��{::
f=��ݹEd=�?��y������<��Xb  Ed�(|;:DD8�q>X�V1<�)nV
ۀ�m=:�?�x>>:DD?:�{�
f��+#DDZ�	}����
Z�	�x� #DD��)n��{��摫ׁt��,@:DDA:�{N�G�"��,Q��B:�{o���C��i
���o���:DDؚg�v�_�XbD�(|�\d���<���������(|
��'E�(|F: �r$��/��
a�3
fA��c�����#�+�G:�bʈ�TH:��8%��:�^������I:\dJ:��?�+�ݾ��&t�K:��*�{k��n�����f����L:�b����EdM:
f�\dj4����kN�fO:\d�h�P:EdQ:�b�������f3�` &����iĂ�&5\dR���/�`/\dS:`T��yU�f��b��q�V�)n�7��Xb<�zW�f>��X�)n��k���Y��`˩�hQ��,Z�)n9�k����`[��,Vf񂶪�����E����)���\��-<��k�&n��f8��D��im�f�*ַ
�xp�
�<�#7�]��$���^:�b*\d��8_�F"��i�[Q���`���`��h��x���h���m[�Y��$��h�fa�F"	`�θd梸��,ɰf=3��k��h�����+���h"�-��(|>b��h���k����$�v�_���c��`��f�!�-"��k9w3ĉ�ad��a��+΁�je��,��aەq>��cf��kd��a��'D���k��-g�Hdh:��_
��i��j�,D��$5���k:�`l�,D���?��a��Jm�Hdn�,D���?x�+o:��p��%��aq��h�� �Hd��-o�ir�,De��g�s��ht:-n��9�>�,Du��$i9
}l��av:�-X��al��$J��h��Xb8
��w:�-x:
}��9�:�Hd06-ny��$z:�-I���{��$Ȁځ9�|:�-}�,D?�(� ������~� Y�'D���:
}l��kj�'D���h� �:�-�ݨ��?�-t�,�ہ:�-��*�:�-���ap��a����%���c
���,��*�`*���`�:-n5�=u'�Hd�� ��,D۫�ځ]��`bf��,D2���f�:*D���`�� ��k��,D	�?���Ջ���[��8o����� ��g*D���d
f��,D��Hd`���ʕe�,D.�f%f�`*<���*D�:�y���$�:�-�:
}�:�?�磔:�-��{�@��Y�:f�*D�:
}�������4�b�:�-�:*DS�&�:K"l-n�:�-�:
}�:
}��h��Xb�� ��	�UA�:f���:��F���	���&���*��*����h�Xb	[��:f��f��f*?	��:�h݄�Ģ:�{��+ ��&���`*���Z��f���*�`*:�h\�x	[�����`*���*�����ݸ`xk�+n
�z~P[��:[����y�:f�:�h~�ި:*D�:�fi`*�:�h��x��g@9wf�SCd�:�?���$�"Vk�:f�.�a�t���a�YuF���h�����Xbb��?��ܭ:�%������-߷�>��J����X٬��v��t�d�	7��&%��x�:�%w�Fa�:�k�����Xbt�kz�z��%��>�:�k#�j�L�as���:�%�:�h��h�:�a� �+�[�%+�6��f&�a��˴:�>�:�k*�*|��	�ݶ:�%�O�u�>�:�a�%��{�:�k8�/�h_[������:�k��:|�a��#�%ףk��:�k��%��:|��k���i�.�k9��G]m��:�:�k3��ј�f_�������b~���:|�k����ֿ��i�Cm&t�R�����i!�%Kl�q>�k�做 �[�摸[�b,6z��n�:Fa��%#�a�:�a��i~=��|��`��bú״�:�a�����N��%��,<1۽���iź;n�:�%Ǻ�i���-�:�>���-��ܢ�%��Xbo�k9{�w��Q9��N��iɺ�cb���	2g�kȀ�=������8�b~�:��[&��f����9��.|���i˺���D���-�:f���+��c��S(~~d[����"���Ё�+Ȁ߁�4`1_��=���g���N�%*���$u�%��:[�����?!
6zA�6��zκ�cϺ�?к�{g��ݭ��bѺ�{u��?�f���Һ�i[�5��*
`eӺ�{���i�Upr�x���.D
��4�����k6z��{䆡c�*��Ժ����7��߁�~~9�����xպ�i��-���cֺ�{	[���i�`�:3D���&5��cS��&“i~�^�8`x.��-غ�{`��d~��8���VF�d��ٺ�c��?ӏ�܊��~~(��c.��?:Cd{��"z�8�:~~��+Z�Yy�B��:3D5�Aܺ�%>3D�
+$u���/��n�d�8�f�:�h�!D]6~~!��+���x��xh�E��4!D+�x�d偸�8Ӑx�O��{h�E���޺8���;��R��+ߺ�{��xӶ�j;�x:�<�.��?���%��{(��㮄�GP`�:�i��c�Tޣ���%Q��xӘY"
�8:�x���{I�.D���� ���ޣD�� �be.3D��� h�EB�����v� ��Y�Zd���:�i�:3D�>D�X-�>D�:�i� �gȤ�>D�:3D(��t�8��>D� !D�:2��b� ��x6z��
t�Q�d6�Bv�:�c�:�i��z9�*�>D`��g�c�&�)�+o�E����a��,$�x��:�c�:�i�>D���ag�i��a�
�+�>D�:�i�� R�<�.��a�U��	�c�������c���a�	�c9���:�i��x�:�i�: ���a��E�
��.+�x����:�c���aه�o�:�c���{;�?L�#D�+|��mUdL��+���'���>D;�c�Ym���k;�id[�:�4�΀�{eC��$�iP��֥�[v<��a��+�ң�>Dc�Y;�i�)��+�
2;�i9���	�>D
;�i�����`Dz>DX��[v( :v�+�
�c;$n��	;�{	[�m`
;�i��lz8��!��c;�i."$n�cg6z���	�����{��'m~m~5�+�0�,D��QX�;�c;�,��a;$n�
�c�����y����\�&;`�Z��{X5�+��ՀW�Ȁ��8�[v�a#�Ym��;`;f*�b́�o5��vZ���DZ�i*�$f׀�$�$n$�)#��)��{���o쌕ej�z����bi*;���&�i*a��y��,���n;����;$n�1��c��h��{�f�Ad��b;�{;�i ��b��x�!�Ad���I
�Ԁ& ��cd[�"��cB��b�{3����㫀�#��i��͒��	$��c�Ad	[��)Ҁ<�%;�i�����x�\��+�Ad��fI��%�x�Z�p�$&��c�����+'��b���c��b8�Dm��s(;�h���ɟ�b,i*_[�)��c�
����*��c+;f	���iY�"n	��c�i*���b$��x9�w3��6�bZ��+,;��Ё�삣c-;�a"�"n.;�ݎ��'���/;�af���y�a0��c9�3�1;^d2;�a3;�%��I9��	f;�x�*�ң2�<���� ��c��<�2ĭ�&z4;�a	$�%�ۿϣo��i5;}~6�f�b�:�{7��b8�	f(�%9��c_���:;�a������a���c�h;;�a<;�%���j&z=��b��h��x���6'g$8�a��[����+X��ƣ�%8��>;f냷����d��0.��i��0�8e�ac��<���%�a?;�%��i��a�[�z��c�-R�Z�@�E�Jϣؕ"n���7�&:�;��9DD���c���h[#�A;:D/DD� �����DDy�aQ�+t�=B;DDj3:DC;�aD��`E;DD��%�&F;�a�3���n��G��c�	}~H;:D
���I;�aJ��CK;DD�:D��(�%�aL�:|�_���^Ȓ�����nM�~8N��a�%)}~�&z�:|����:D��%q*�E�[���%뀼x�:|#�<�A>��	O;DDЁG���&׀E�.-
f�A>P;��z��%Q��R�:|S����&��Y��Wd^��%92ò�%T�~8U��hs���V����,XI"n
f��I���m*
��i�uե�&�i�" .���DDW;�b:D�(|X�:|�c�j�@DD����t�(��:�Y;DD0���OdZ;Ed��b[;:D�	}���\;DD*Wd�&����];DD��c9�g�_9J[����e�}�,�.ā&^��a�.DD�	��:D_�V ���%��dZ� !~��`+�<��)nc�۠�o�������cM9��ڇ�F�`�(|����~a��a��b�-ub��b�8#=��kÄ���݄�:|��l$����| ��b��o��%�;��yĵ�o`��z�cc�f*�{K�#�0���bW�\mm[�o�E��mxd��be�)nf��bg;�b�F"ဂ-9��i��+3�H�y�b��m*�\d*Wdh��b��&���r���	i;&z�f����H�*���ցi1Ȁ͸���m��bʥ�&H�)nH���&zń�+Μ�@w��D���hj;!D����+hk;�h�ۅ��n>�����l�&m�)	V�=���]v^��,��<׀�¾����f *�����\d>����
�!Dn;�a���o;8Dp��b���i��'Dq;�a���Jr�'D�?�@;Omˋ�%���h�ˀs;�a��Yғ�%զ�b��%���t;\du;�%v�G�w;8D^��bY���a��	�a
d~��%k��	�&��'Dx;�a���������߂F�a�$�y�۾[\z;&z��+s����]o�&�{[9���{;!DP�f|;��Ŏ��};��2�!�y�y�>D��o9�P��;T~�Zd(��;�{�;�a�h���+g�<��):�;'D��Zd��<�����Hd):������h��>D(��	��;d~�>D��Zd+6z��be]��b��>D%��c��Zd���>D��Zd��be;6z���Hd۬�h��'D���%��Hd��d��>D��������c��i�U��c���'�ۗ��i���'��`�Hd���㒻Zd�;�aC'����,Dc�a��Zd���'c�ң���&��M"�;�a뀵"��ݙ��'��Iv\��>뀵"�d~�Ӂ�#�%|��'�
��	��Ć�'�-nNO�뀵"�;-n��>Dw���.��;�>��Hd��Zd���(��>D�dl�<�݀M"%�ZdY�ܠ��&��ۂ�ܡ�>DЁ�o
�b��>D%��b��Zd�ۘ�j(�����xӔ��Gy�>D��+뀑~��ه�-��>D4�Z�x�;�>DQ�{ڳ�^����*���@v뀵"h�>D&O�V���`ŋ�i��+>��`\�x�ւ�':�x�.���;-nϋ�`٧�`*-n���&v,�YΨ&��Iv���&��L���ˀ���	&�	Œ뀵"��"�*D����Ө&���<�M"��[�x�M��/�	��a>�;�D���'��Iv���䥆�����"��&���#ݣ���g��D�+A��"�m,@��&�^����Hfrxւ`���"}�xӚ��<�&Ёy���AdV
�D��<��̑V;&���:���l��:�<����R&��xӬ;�D�;)�;c��;$n�;�a\�x�3��ba��9��z���:�dЁy���Ad� R�xӲ;�&��xӝ�%DO�Ad�[���Ad܅��x��%D���`+h$�&�;)��Ad�۷;�b�!��#6�b�;�&*�I9!�/hӃOd�;�a���F�(�[���z0;^d��b�;�bTg!��+f6)�;���;�bi^d3����bo���bQ&[��;)Ԁ⭩�E>}�I��
�b�	2e��9��1�b�;�D��&f�/h���A��&�bO�/h:�b<�/h
�&:��ic����;�D��Ҁx�U%D��#��G��6zD���	2��Ad_�/hU�Ad�D�o�ۿ;Mv[=��Ё(�4�7�a�;	2サ�o�	��a�[����-:\�Ado)%t%ەx�»Ad8�G�݁x�^d)�E>��Ū��&��i~�0h���+*�&��%")�;Mv�;�b��{B	�b;�b�8@"��+=��L���Dm�Mv�;^d>�G��+�c\�i~����;�c��c���_��+���$<��ǻb~8�b��\'�+:/DDȻ�aw�b�;�cʻ�&<��$��d˻i~/���$u��T����;DD�����G���x�B����;DD�Mvk���:���λb~2��a��!�G�ϻ`~{�i3�H�c��a�`~�oeĉ`��_;���.
)o��+�;DD
�A
�\"	2 :DD(��x	u\��x�R���>��xoL�o:�����d�G��;DDm0h	%�һ}
/�]΀�����S�yS��ao
	f��H�df�����3Edӻi~Ի}�;Ed�y	f�;f�i~y�������Ő��ZĢ׻}�;Ed�}�ۨ��ٻi~�;DD%����{��;Edܻۈ�f_���;Ed���&�z��)n��̕CDD(�������.�7DD��&\����;X�b%�a�fY�f�z�+DD��y�;Dv�M9?	�Oۃ��X�`�fB�����<��	5�jsDD��G:(�a�;EdԚf���:��%�;DD%�a}��x׀`~j�
�À�x���o	}r��_��TT����	��iU�f9�̡����}� t�o�ۊY�?	�Z�}�r�m� ŋ.D�;f�EdL��dF�a����a���4f<��٣� *�'2��� ^�x�;Ed�Nj���8[�Ԫ ���i�a���;Ed��@���aۅ-�a��a7� ���w�fӧ�%�;m~�;��w�f��;m~*��9�(pTl|V��;T���զ�$��`� J��Yy�
2�G�"J���
�
�G��;��o�)n��a0h�a�����o�f���� �ac3�%X�:Fv)Ɓ���a�xj�;�i��%��������;�a�Cd�a��G�$����m~>�G�J�
�xj�;1D`�����d�Z�v��+���a����k�2� 0���[�d����� ���a߁
�l���_������k���a�C�M� �"C���
�G���Cd���a.��5�)X�a.� k�I�(���� ���J�+����;�_����a���'X��)��a�`o�E������xj_�7.
�G�(�6��Ԁ�i�;�%\���,D��ij�-���a5�CdJT��%:��>��#
�G�8��a�m~�;�%L�W���a���h��a�G�t�Hdo6n݀�a<����<-n<��f����b<-n�����a�Hd�ˁ�i6:���$��Ǿ�ƹ�Cd������z�&z��K�HdH�v�<�bb�b��+<-n)���L�����݁��	<�b�6�
�Hd�	}"#�bT����	C�L��<&z�)ud������{�>g�f;-n�ˁ�!g�)u�b
�;u�&z��c˜f4�&�j�xFn��I<*�J��a�Hd8-�x[���z���(����Hdk��<}��t�ܸ��<�bk��}�ף���)���9���Hd�	�i��/�ˁ�}<�<�i����f*���bs�x���;�V/+����̢ϓ���Hdl�f*D���>�G��Hd<��>�G����b<�il��������"n#�	���c����3�i���R�"n�������<�hg��<-n<K"�ˁ<}��⫀�	�����"nm�8դ<+!T��!�G���< ��c <�C=�,	�&A!�"no�Y|��3*�,"�"n�	�+!�#Dj#D)�h场c��:#D��<#<+!��Ġ�$#D$<�h%�"n&�E��#D��"n�&֢9����v�L��aW��cm!g��̀�}:����#D'��a�G���o��ߣ�#|(<}��<vjc+!F)<F�oe��b*��a���i�#D+<{�,��h-��%9��+:�'���	"��,(�����c.��bo�G�!�o/��aR�"n	��>G#D;��c�+!�X��{�%��'Z
,ʉ�c*��/���cj���o��A��'0�#|1<�a����<8
+!?��%��d����2<�,q��:���
+!3<K"4��c"���K�#<+!
oe��&����#D ��_X�����#D�6z�	�,����#D�5������coeډ�a..�()#D<��:�Y�@#D�oe�Y�6��b��c�,7<#D��<c
 8<���X�耸c�݁9< ���&@��b�-u���:�#|^����X�g��&�݁���i��h���ތ�ƭ ��:|�vj��&��*�����e�;��i�����ˁ����I =�����fo�<�:|YI"݁�����`7�=<Z�k�"��,��*>���[�z{��
z�{���쁘b��όB�~�?�G�d�	������
�@<[����	��m[�����?���D�ף��i[�z���\!�d_�3A<f���%������,߀�hx�I�θB<fi�`����c���b��#�80h���C<I"�8DD��`ݝ�n����܄�`��}Q���݀�:|�3�w�cE��`��e�O,ʣ��I��`��%j��	�cF�G����l���t�f�+I"�* �Y��_
 G<�a�8DH��{#��`{��D���g��b{�3��1�\dX��b,�*B�fI���;'�av�J%�{Ą���Ʋ��������	�n�9�	�5b��;nV�9F$�7�����z��{o��%g�;n��i~z���$���J<�aK�;n��i~���r:�`��.�a�'|���<�̢]�;nh'|�{���Od�N�	�G���9nP���L<�a���M�	}R���������N�'D:�'��xO<�P<fQ�'DR<�cS<&���ij�'D���*����'DT�
)M�@m��U�
)�'D=��V��`q���@�
)W��X�� X�'D���KvH)!*���qY<�&l�����'D�0�'Z��%�
)[�'D\<�b_���G�']�'D^<�b%��D_<�&��b��+Q�'D��`<�&a<�bb<�d��cc�'D��2d<�'���'DL�S:�'�{��5���Kv�be<�&:�&f�'Dg<�a΀��h�'DJ�-?8�c��#���H��i�Zdj<�ck<�cr{�l<�&m�	}����.�
)_��n<�b6���6������o<�c��b.�
)��{+���p<�bq�>DցG�ʇ��r�'Dŋ/h�>Ds<�c�'*��{h�_"L�>D�)t<�i�'DC�'D=�"���c��u� /��v�Zd�?�Y� w�'Dx<�'���Ջ>Dy<�&Z���'����z<7���{
�cg��{׀ ��b��&��cW)���{<�'|��{x�&}�M"~� ����� �b9��w�c��b�o�h�vr�<�c�� ��Zd�<f�� ���{��g�� �ԁD�Y�����Z"�<*D��d~�<�b�Dm�<*D���{՝/h�� ��Zd��{�<�d�*D�<�b��F�bψ>D鑆�<*D�<�b�<�b��X^��{��^�cDm;�'Q�Sɏ<*D<�bU	m~���<�<fQ�w3����bӢI�}j.m~���i*D.�b��_"�<�x�<�c'f�<�b��c�<�镼 �NdR;�,��<����<D�<
}�vj���f�<f;�}���{�� .f#� ���-�*D�<�x���{"��y�*D���Л<�b�*|A�bi*D��7�[����{o�o���{R���o
��^��Nힼ�{���4��G�@�<f��	f�<���	�b	:��f�<*DDm�Ndt����<*D�<f5��$n�<Dm�<f4�Ad��	f�	}�<f�<�+�����}�H���բ��΂*D�ˁe�	f� ���v8Ndz��`�X�#�rӂ�Ad����`��*|�ԁ��$k�+no�������i���iW�/h�)T�a���i@�h!)�ۮ��i���xW�/hҀ�Ǔ��i��F9[���oe��Adۅ��ir�Y*s�Av=�w�g2%��'���io���o�ڢv�=�<�a����i�<	2�<�NЂ	f:��`���i��)�<�<X5��<�a��j���i�֭"�)v&	2Xu���������f����i��{��	f{�� [��i�<�{�<{�"	2��Adk�ۻ�)��'���iA�{��ɨ�a���i��+n8��5�!�X���a�xj��4h�<�{.��-���x�<o9������0h]�{~���?:�<�a�<�{����a���{~`���Ə�i� �za�k�*�i�<�{��4�{~���i'���^d�5g�F��<�i��Qo�J�Vvx�ϣ�<�{�{��<�i�VvǼ�&ȼF9YDD�ˁ��iX��Vvh�?:ɼ{~�<�i�W��<�ie��@�<.|BDD�[�o�G��@"w��?[��<@"����ae�����d�Ӣ�<Vv�<Dv�<DD��a�(DD��,�<.|��y�'DDҀ��{��<�x�<DD�.D
�բԼ��<�{`1u\�<�%!�G�w���<DD�<�%`��a��<�{���&{���{�<DD���aۼ.D#DD�<fg��&�<�{�<DD�ˁ�<{��<�i;"����^
o�G��<
f�.|�Wd�;�`�{~�����&@"Vv(�.D�<�i��/e���<�i��
���/�DD>��>��8�<�i�	��Vvg��/ :Vv�<�i��Em16Wd��V��<Dv"�f����ү�/m{�,
sI�<DD� W��<.|҄f�<fU��/��{{��\��{X��/��<	H��<DD��f��b��aV��/�G��<�i��/�*�iƥ�{�{�� D��-�f��8�%�'�b��i��.D|��/���h��{_�����.D��/��{Ђ�́ �`���ja��� �<}��5��Z�}j��{�� �� ���j�Wd��x��E���{� �<�i�m�� a��{���{�����/dX-U��b�!�� �Bv���b�<Wd�����/��b��/pY"Xf瀞bĚ�/Q�M� ���{{�f�<̢t��{��BvU��/V�f���{�� c2�»�/�Im�� ��{���/�"Y"ʉ)n%�����{��i� ��x��{pY"Հ5�pY"��Bv{����h��{[�p���{HU�(���*��l�
�f�&�����a�"Y"�(n��>��%Am%�
�j3 :=�<1D=}���a>�f�}e%� v�Y��z%�����f �a���/3'Ud	��z�1D�X���:�d���*�Se��>(��o飼���X�
���td~Қ4�[�g� ��{����
=Ԁ�l��� ��apY"Z������d~X�Hه�J���pY"s��{���>pY"�������	۽�ZdȀ܁pY"���zp��
�>D�,D�	)xUTv"�@飽U���瘀�a�{���Zd�&1D�>D= �Y�= s�\�'�Zdm�%[�b������>�>D�Zd� �>|�?��Zd����/Q��=-nݗ>D��܁�Zd= ��{�EΌ��i=�{*ە�g?:�i˺��83�{)�b��if�5�{�Zd�
�bgX�r�p��>D��i��z@3X� =f!��i���8�b��b�fo��z"=X�P[���z���d �iw�}&�b@3X�D�{�[�#=X�$=f"���X��f8��8$�Y�;��Zd%�@9&�>D�!f'�Zdg��&��<�罣��i���8��8�&(��iv�}"��8c�Ca��Y�)�Zd���b��b�-�i�X�*�>Dc��=eMm��>D+�Zd���bl��.S�Zd�X��[�F��hٮ�,��i�[���i�
�bX�-��i.��Dg�{}����[����f!
�X�{Ƣ�D�
Mm/��i���80��i1=��Ѡ�b��8Y���ۀ5����b�
����$��ԁ2�"n3��D4=�{�ۢ�
��b�Gd���A���8"�=h웠j"�}%��d����y^�Iv���05�Iv6=���ad��x�{��X��#D7��x��f��a;�Xd���x֑�%8=f9��a���x"��%:=}~��;=f;Ӣ3�	}((��ه�%�
)�Y�Gd
��x<=f5����5|=��a���3����b>=�:��%9���	}���]�	}?={���zk��Z�@�Ad3��x�xA��x\GdA��aB��C��bD=^dm�Ad���܁�D���z�o耴D"���ۨ
�z�hE��D�aF=^d����Am3��D9{T3�?z(�7��H��mG=^d	�{�H�AdI=�4�i���5b!5|*kF�
X�ʇ�{��L[Gd��h3�i85|=�|���{�i��MJ��jK=^d���(�騃�xL��%v���{�P�8dM��aҀ�{��x���x����
���O��%I
ԁ����:�ۀN=�i��9�O=MvP�	}�#�hv�4�5��xQ��{�7�����R=I"S�	}D�&T� U�AdV=�W=�i�� ���AdX�	}R�{~s�t���բF��{]� Y=I"�i~Z=���+�%��C	�8[={�\� ]�i~^=^d_� m�>�Z�`=�b&�*&��€��v��T�m�A�{~a=MvQ^d��~8���>��o輁���9^d����b� 	����d�Dه�'c=DD��d=8De�4��摯�d�D���f� ���g=DD���T���i�DD�8DU��k��(I�܁����8D�{��8D�`~h=DDd�D�DDi��j	�Dj�}z�ik=Mv��kl�8�X�,��`~m=DDf�8�8D�����%���U]n=8DZ��dQ'Mv՚�h�"8D�$��o�@m"��z�� �DDX�3�p�8|:8D�,q� ���hr=8D�@mI���6��s�9�+� t=
}(�)D�ܰ�� T�(u=8DR���v=
}l����� 뀼�w=Dv��(��8|��Yx=
}y�}z=
}$������`Y�`~�o��i׀g�)�'�k	�D���`��j֔f�{�'DWF�����]�D�'|�o3{�|=Dv
}���(+�9n}=DD�Dv�)��i~=8D�&���@mt�}oDD�d����\d=8D��'D=���)],\d
��h��i���i��}o��@��{��'DI
⁃�8|B�@mH�8|c�{:���ʐ�H��`~����՚@m��_�=)ވ`~��`՚@mW�'D�=�{��x��<����T�}z�8|Ɓ7.�=�{��������j�3TƁ������=
}������k��'�Y�=�{Y):��ܖ���⁊=
 ��	f+)u�EdD�=b():o����ۿ�ܣ���%���Q��=m~h.�{���=�tA��	fV�	f(���=�{�
):���{�%m~��{
�_e���	f
����h��	f5��^��E��	m~^����>� !(��٨{�)):DX��=m~�
�D��>��h�����k��ϋ!gI�偓=���=�{'�M"g�{��,��z+�!gt����{�=�b��+T�B�_"@�!g����8��(� �e���,�=�{����,k�ۚ=�{l����{���Od:�{��M"
��Y�F��8��M"�{��=Nd�� D��md~�5*D�8�<|Dm��8آ
�O�b���,�!go�<���!gU��`>�^���8���,5��`���,J�����Nd�8�=<|�=�b��"u����_�$���o�"uNd��<D�d~����<D���w�f_��`�=<|W���oj�_"߈<D�M"�=<|��`�=�`��<DNd�=*D9;<|��"=n�!gR�!gկf�=<|i!�`8�I9S��~���j�`J��,��C����ϋ"u��"u3�`���,���׭=
f�1�j�<D�=� ���,"�<DB!�`f�<��#D��+?�����.N�"uv��+n���jZ��$�&��:e���D*��X�f�:粽f#{�S�bi�<DV(�/&��`�fQ�x�<D�W9�{~�=<|�Dmϋ�����j�
��B�z�f��+o飵=*D���D���jם$u����k�+n���O���<D_��	���y��"u��<D�={���fŁ<D��n����T��	v����/�`3�a ��<Dz�ݡ��ޫϻ�����IvB�<ܽ�"u����<D΁&�F9�= 2I�*{�����0�E����=
fƁ�m��ݝ�c4�`�0A��w51�{�R���z�ݎ��y���c:�����(+���
��^��D�	)щ8؝&�Y�t�= ��+�k�&��	)v�b~��C쁩j�8���y���D��L�,�hZ��J�������`r=���½�D��8�a�&)�{X ܒ���?:��&� ý�D#j�Y-j!�$!{��d�X�7��D��f��V�?:�[��8��?�����yȀҀ.�� BK" �{��
�(�=�����
�>���@"\���ւ?:Îz>��?:�� �={������o{D$v�ݙ����6ue۫���=�>\��ǐ����=.|��l� V	��DX���^���^Ƚۓ�uR�=��o��X�|�[Q���5�=R"À<�@�(�{��=�6�����́j{�.|�᥂8�φSTX���m8c�D��>�X�e2~��[�=.|B�����5�h��u\�=�z�=����{*TT�bԀ�O�j9�>�= �_�S�<�1�yϽ�&������*�j���fD$�=Mv{��'.���������v����'�Xv�.D'���y��d*ѽ.D�fҽ�i��>�v��=�{�=�j݀�%�X���L���.��i��)���y2��=Mv�=�D�i~�$ *�Ȁ���{���-J�i�=fؽ�ie�;n����=�aH�>�=�b�X��=�b���	��=�>��#'�;n�=f��Б޽�$�f	�b߽�i�7�ܛj	X����=�i�=�b�=�{H�[m���T��i3��=�b�f��/�`X#�y��TT. ���=�`��Lm��D��=�`Y�y罔C�Y��� �) ҇R��=�b� �=f�= �a
�a�=f�� y�Bv��{�=f\� �= $� F�Bv�i����= �=Mv��Ё��� �i�=�D�=fg�{i��i6=�D������{�:h)!(}���{�� �=��(g������F���(gڂ9� ���{��bʊ������{���g����{~�6�=�i������;n� �=�b;!D�'!D�)!>�b����� r��<��Bvb��,>�b��{>1D������{{X���%��yl�����>�bT�E���%���>Ud�Cd�Cd� f	>f�h#)!
>$u��>?W�ۇ�a�
 :v�Cdg1D� ;� L!Do�Cd��&�|� %�x�E��{�(g%ې��~�Cd���j
��{%�ل���
)>?����{�v~��4���{ʋ٤���V���(�oeZ�Bv�	2�CdȀ��Q��	o�����{���<��x�>g���{����Cd΀x�R�'��Cd��X���΁؀���	h:����'����'�T��>D���>�i��Ǭ�me>�i��W����i*���g�xө����!D��>�i���������Jc���y�Zd���>�ie�4�e��z{���>�i�
Mm�� ��Cd���>�i�� �z
Mm�Cdc������%��%��x���y>�b��׬ : �Cd!>m~">I��Cd#>�ixVB$�x�$>ף%�/o�+&�Zd#)��{.��
 :l�x��F�����'��{(�[v��i�Ī�$��݁x�)>�mЂ�:��x�"{�d�ճ1�<����<�h�
Mm���������Ȁ��5�����{*��*���Ђ�:�$n�ݙFv+��{,>�i->�{.>$nm��]d9��/>}�Fv��i���{;�{
ִ���@����0>�i1>}��h耘S�Zd}�zj�c�2>�{w�mCdyMm�����Jc3>�i��h_s���o4>�i�Fv!
�ܚ���^}�$n5>Fv�
�h��&$��h�m~���6>�{yMm�Mm*{��*$n������	:��7� $ɣ�b���Xd8>}������:2���=��x��|�����תD{�5�pӃ[$9>#D��"n:�/;>#D%Gd��b@�<��b��!�	=���`�h=���d�dɚ	;>>#D?>Gd��{����-�{
� �k��"87��@>#D�X�]�"n�	f��⥛�aA>��S���R��i��{=��a�'$n[Gdu%�{�B��b�YvC>#DD��aE>Gdw$�"�#|O(���:�y c��z� !��zkF>#D��dÄ�iG>�{�"nH> I��a.)�����	fJ��h��i�#DK>�{L> {�����{M�#|�x��#D:  ���J4i#Dg�{Њ"n	�dj��a)������6۽�)uN�)��d҆4����O> P>�{�#|���4Gd��jQ>kF��R>#D�Gd	�dS>�{T>�{U> V>#D��b:���W> X>�{�����#D��b��`Y>�{�"�az���{����x%#Dd�>Z>�{[>�aN�{]�>z�����D|�{������iA�j|�9�ʇ��ѐ��\��a�{
=�h��X� e�)u�'�>]>�{Y��^�#|��>�&�i�y���!g_> `> �
�>+�x��#|g�����@9Ԃ*y �dZ��+��{a> X�uC\�{3 ��>���b>�{�:��c> �6fd>��g#���ie>�{��+�����hd�d�{��=^d�?���{*���&:׀��'�{h���߲*
&n��df�)���Q�cg>DDՀf�h�4�i>�d�DD ���z?�j>&nk>DDl>�>�����j�DD�8Dѐ�����m>�j9�;n> o��a:����DDp��>���R�C������ ��{������p>&n����q>DDr��z�
�j��_s�Jdt��z@2�a��bu>�`w�[��(|�b=��zv>�{�Jd��f�DDw>��w�{x>�'�T�`������ڪ�z��p�y��z����z�JdV�����i@����b{>'||>�aڡ�z�*}�Jd���h=�j>Q0�`\�a~�Jd�3�j���>�`o�!g��(*�b��`5��-���h�-u�>
f�����>�ae�Kv��)n߀�z�>'|,
�-u��(Y�b�4�`|�'D�DD��f'����'D�&'|��Jd	�DD��'D�>��n
f��'D�>&n��	}�=�ዾ'D����	}����	)�'D���㍾�a��'D�\d���ߡ��h��c��a�����`޺'D�(�'O�f���z��'D:�b���l���L�'D���zm��a��>��'D�KvY��h��a���Ǔ��`܁�z��'D��'D+�'[�蕾�`��'D���`��ߣ�>�b��Kv��'r�+���{�>�a��'DO��aԀ���'D���`��`�
�n:� --u��i��L8
�����hQ��q��դ�):�	}j��>���'|�����̍��G����X���x��'DW�>�>�&Б�.͍%mK��{��-�>�&��hŐ�o�>�>	�y�m���>�>�� ��M"��'Di
���	���ۥ�'D.��{���`|2���>��)���γ'D���>+�'D�>�&b�>�>�>>��J:7��Ђ�f��	�>���橁z>�>�a:�3k��ۆ��>�>�a���`���>(�ߣ<�M"Ȁ�	5������`J�,�>��F�g�M"e[d���>�� Ђ`w��� ����$��ւ*D�>�*�� �|�4��>*D���`������cNd��a�>*D�M"��TT����m��,T/*Dh��>�F�4d~����{V$�� _�[���+�>�Q�	d~�>�&i*D_�>��F���Jc�����a�_)?D���x�a�;):9��C�9*D�>�>�w��,D�+*D��Zds�F����*Dг,D��<D�-�*|m��ۤ*D��`�=*D�=n���&���ONd��ȁ���>*D�>
}���d�۾>-n��F�U�,D���>���>�>*DȀ���<D��	¾�>i*D��a��o�"�a�>Ov��`ľ�'�&�>
}���y�N�s�F��>�ay-n��'<
}m�8���!Nd�>*Ds�F�^�fk�*D� �>NdS��'��@Я*D��+n�Nd�*DW�a�Nd��+n(�,�>*DQ;۽ɾ�'ʾ�j��+n�>�z�`�*De�,DY���̾�'���;�'�>*D\Nd|��ՏɸϾ*|W���=����>f�>�a�,Dg�x�Ҿ�'���G�֣���x��&��'�>�k<��oU�,DH�h0
} �x�X�yoԾ�b�>
}�>�a�
�`�׾ nΟ n@"�Xؾʢh�]dپb~�>�a�&9��۾ n �` 
}�>�a	�mD9ݾ}���>
}@ݻ�� ng�a߾�'���*�F9��}� n��'���b�Sb�}e� n��'�o�+n��#����b
 ��+n��'Y!D����!D��')��u����'
5�a뀹�"�?:�>!D��`��-�a�+n{���`6+)!�5!D�5��}����>�a�]d\!D��)����!D��&�>�a�>�at�㑹)!À������&�>$utxq��	����B@"� n�>)�>!D�
$u��}/� n��x��}����>�&���,�>)a��bw!D{�c��<�{��>�>ه�f�� n���&+*�)�>.|
��
��&�>)˛ n�Y�Jc�>.|��Jc��~�z>|�ٳ� n��}B.|�>�>�>.|���>)!��$��nl���.!D��{fl@"3����6��Fmm���_9m�.D��+�>�>	'!D��.D6��c�<�)!��� :!D�.D�]"�� <�5Z�+�G�?!D��i?!D���`���'�Hd?.|?!D+�x������F�.Do��?��&m�q�Ǎi6o��{()�9� .|���!|��?meC�����!�k�	��&%�U\X�x�
�G��)�j�h'��aҳo?�j?)
�G��o>������?)�.D���a� #�o!��.'�f�>)?@"%��X��zO)Ł[m?.|{�>��i�����o?�>��+>�b�G����[m���md,@=�bϋo?.|���{�}���[m�=}_�s����d,?}��S5�ʢ��a�	ˢh��b0�+��aC��}��[m����?�b��.D�G�=��(���o}w�G�Q3�?}�i~�.DV�G�Ϛ.Do
}�a�bxN�w?}|�%Z� �G�
�����ymd,��!?�_z}�Mv"�G���x�"�G��ee�#��a
�G��[�me��	$?}����l���H���c�<� À{~S�oa��[m��&��5ڜ��a���݀{~%?1D��~�Im&�o~�6�f1DT�*�~�
1Dd�e<d�e�삙b'?Ud��a(�[m�� :Ud8����bx)��`*��a9�R� ��aŒH���e��6u+?Ud
�%D,�Cd-��b���.�	f��E��}ރ���b/?1D�)uU$nsUd0?1Dk�"v�����a<Ud	�)5���1�%Dy��a�  k�"2�%D��`<��a3?1D)�Y҈F�Հ
�4��8��5?�&6��`���%DW��a���bl�	f7�%D�
Ud�1D!�f�����8��a9?�&Ɓ�裂�aՀ�"۴��C��
g2:�Cd� !�iF;�E�<�%D��� =�%DA�&���'�5.�!1D��'p�&���>?Ud??�&꺗��5UT�1D��&Ђ���i*@��a�1D}�atUd���e�P+!A�%D=��`B?�a��F��b���`u91D���S�-!���`���C��bD�%D��+�kx<�и��%D9|�M�ĉe�E��`F�%D�aڵ	f��	f�i*+!�)u��-G�%D�i*S5m~H�%De=u�����G�j���I?�&�m~(+!{�Cd��E�J?�&�i*����� <��r���i*5���������c���z��G�K��c$�!g�#�g>T��I���k��2�L?m~��M��c�֣J��+۴A���ց��6n��&:�
)�P�m�
�B�3�6nl�cl"g�����Κ����YҮ�
�o�1
�		����cii*U��cN?�a�� O��a���+!k��c��ʢP��&���'�a�݀h��&+!��aU��cQ��az<4�R?)���&׀��ٳXd� �`��G�<��aS�Xd�3m~��Xd�-�W�aT?m~U�XdV?��Ȁ%�W?m~B�� X�(|X�XdN��a���(�� ���o�G�(�� ��a9�UV��&���a���&���Y?-uZ?
f[?)��&��i\�(|o@�]��c���T�(|��&W��`���h��a��b^?5|_�Xd`?-u��?!����
�aa�(|���-u��� .��`b�	}��:ec?5|(�b;��ii��d?�aWGd��a�Xd28��v��+�����`h�	}e?d~f?@9��i�>g9��mKm9��g��`h?@9i?Gd?���Yv�5|�?��;F�������&l��a�� j�(|ԟ"u������� �݀�@9耣�k�Xd�*�@9��h���&M$}j�D�l��be�ag�`/�悀(|m?}*�I���%�2�F���	}�����n?�bo?}~�ad��<�
�p��%��`(��b�?Dq?-u�}�5|v���I"r?
f�;���bs?)�%۴���HǪ�}t?)u?-uv?}��}���hw�)n����o�G�x?)y?�>X��2��#���ۃ�[�����Yv��w��۴�	}z�\d.��`h�cɑ)n{?)�I"�	}|�m*�px}��ne�*��>�	}�"@9 �	}��8{}��	}a�>Ђ�f���I
݀~?I"…�cm��>
�ո���{�����8l$���X�(��2w�����8�&d���G�,梘7�>�۴i����z>���A!
���}���>�G�� ?��t��bL�����b۽vȣ�?8D���=�G��?\d�?8D�?\dŐã̈4:\d-%8D���d��G�H)\d���bq����?�j�?\d�zӶ8D��G�?�Ϫ�j�f)��2�?�>�ʢ����>|\d�,��?8D
���X�'�,D��%�8؊�,D��8|�?�>���`�7�g�,D�8DЂ8|���b1���?\d�>�?8D�?\d��m*��8|��#��,D�?�h8��%`�?\d���&�Dm>�q>��,D�?8D
}L�,D�=
}���یBm:8DwH�o�E�\dk\d���ݘ?8D�
�8�Q�?]vW�j�?\d��8|�8�h��Bmr
�	�
}�JdЁۜ?�he�Kvo#D��Bm�n>��`�+��,D/����#|��}]v�?�h��8|+��(��~�?8D�?8Dπ�?�b�?\d��b���i	�}�\d�?8D���?���?\d�����?�b��,D8Dٖg�ʐg!�\d�C��?�b�����)�?�bQ�,D �9n�?�{:�8|X[���Bm��,DL�,D�?�b�?�����-ɀ�j����C���Kv��,D�?�b�z3�h4�,D��y�?�b���+9�,Dm��>-�{��{:]v�Y�=�t杢j����4S�b�
ɴ?�h����?�{�
}#-n�����J��������{�?-n$��hk��"�	��� 
}5�{a�kc�)!���"_Z�7
}d�Ɂ�?�b$�8d��ѧ�z"� ��b����۹?�bY�#|¢}�
)!~�6�X#�D��&��i~��eD$�+���X��)�"��?�b�}Q����~��_"�? ;���?�b���>��d�D�)��>�&O�)���>c�D��&����?�&�?�{���z@�&��)Q���Ŀ	}7�&ſ�>�?۴ǿ�>�i	�M"ȿ�>t0�&�	}���xe�)�DmI��Q�����Z�ɿ�jʿ)˿�x�$�z�ã̿	}���
<|�?Nd���ο�>I��Ͽ	}Ӂ)gFm�?<|}�	}5�)���<|�?�bҿ_"�Fm�3<|;����Q���><|��	}�?Fm��f�=�S�JԢ3m1��QH�X��&֌��*�3T�&��<D�Y�Կ>D׽<D�<�?Nd�݀Ł<D� �')!ֿ>D
6<|x�)׿<D�?<|,�<Dٿq>z�_"ڿ<D�
<|ۿf����?<|ݿ�>����޿<DA�<D�<|߿)�>D�	�(�Fm�)�?=n�>D���>�?m~s�M"��>�?=n[������>i�<D俭>�q>���dŁ<DŏFP$m~�?�'U�	}X�z?�?=n���X�62���52�>D�?�@“<D����$*�|���A<|=��a�&m~�?<|��'D���j�?�&�
&Ł<DK/���<D��O��'<|��ox����?<|��L��=n뿥,	Fm���������?<|sCd̀'D���j\9�a�'D��q>��`�?DmhX�>(��Jv����z�<D���W�t\�<D�Dm�Ca���)���>��<D�>Dv�_�hmm~� �:g��f)����<D�Dm�,s�Tl��<D9�;m�G��?�������´��h��!�X���H�Nk�""۴����
&+4=nmpx�?m~�:g=n�=no�矀�d�?��?I9�=n��´�ae��a	�:g��������f!���3
}�ԣԀ�o���!“�,�X��.I9��e؅b~���,%��z�´�)
}�X�Ҁ�>W�%K̛�����LR�h���?I9�?
}I
݀�+��(��b�?I9���?X�� �ampx0�fԸ0I9��bQ����� &I9E��b	�b~�����c���&@
}��)|I9����/���&o�DX����+��*�
}��o�L����b��AdJ�m��D.
}�.@"��)@ ���&��7��4@��b��&�)�X�����@"��Ad,��&զ�&���ե��af��c����f����(�´g@"mu>�F9�	R"Ȁ������I9����pxj�6͹@"*�F9y���ǃ4S���'@��X��u03DЁ�@@"I9ݎ�'�-�r,��ݹ�a5��@m5���Î �a)��@3Dg��b�	�80'�	@
}!Y���T�}��I9,��<��&��
��&��b�´������´�ao�&@X���9��	 ��/۴��&
@me+����b�}@�j��q>���Ad{��i�[�@X�?	i��!'@o�3Zb��!'��	��i,)���\d۴�
me��7/�!'聉b�@"j�@*Dy�a�Ъ�!'�*D�,�a@W��!'���bS���5���@)�!'6��v&meX�!'�kx���$����XNjj��δme@��!'�Dvy)�Km�F9��!'@DD��";��>@Dv���2$7$u@�-�DD@�&r���
�e��) �!'!�Kmv�!'���;� ��Dm"@DD�����mw�s*�a��A�#@Dv8
�-�i��A�DDe DDQ��������$@DD��_jme����!'o�G�
DD[$X-%@DD��܀yme{�߁!'u�Ez�����W&@DD΀�z�me���aX�����DDԂ����!'7���O�&��b'@Ud�	2�4�b4�{~��!'y)�Ed�b
��d��!'(@��O�y�)@Ed�!'*��a?�a�	 ��bDvU�h+)��u���`+@�b�DD�px,����&-��`��&j�8�px3��a���.�ImJDD/@Ud0@$u�	:�1��a2�CdV�G���ãDv3�x>��b$����<DD>g4@$u��ah�?!5@DD�$u6@Ud7�Cd���8��a9�M9���aߑCd:��a���=�
�&�M9v�%���������;@�b<��a)@9=@}��CdT�&ӏ
�>�Cd?@}@�F"�j�߇�݈�b�@9�\�A@}�w���
B@!�C@�bg�&!��f	M9�%�bD�Cdy�f��H'�6 �b���E��DF��D��G@.|�f׉ٸ}#˕��bH��D���a��`�fH��D��h��q>I@Ed>�E�ʉf�
.|۴J@i*ҥ�a��DK�f�����hj�8�|>��q���Cd��,L��aL�M9M��D%���N@)O�CdP��D���y��,���Dz�b3�bǃ��Y��$�q>�"�����Cd���Q��D�@9L��D��.R�Cd
��?<��h�&�{���B�#��8���o�\�j@9S@){n>T@}@3)k�\�U@W���DV@}C�=I��ݙW�E���+k۴@��z�������X@��k2��/�Om>X��۴$��D{�85��y��UY��D����"X�v/FvZ��D[@:Dk2�d~j)�ɣ\@FvC��
�b�!:DJ��z�"U�Y�Q���D� ���D]@:D"X�.Fv�7:D^�.D�[�5�A_@)�:D9�;��FvĄ����Xd(��:�OmQ�������/)���&dX-`��&z��� :Fv=:Da�Hd"X�>��b@ "X��Hd3��aZ�.�Omo�cc�:|��i��X���:|bOmd��&�X�e@:Df@ n�`*�Bmr�ɦ�#D\X�NGdg@ �,DGd�Mmh@ m��ak�:|i@ j@Gd�
�k@Gdd	);�l@�hm�Xdn@Gdj���o@X���UFvT��c*X��Gd���"X�p@ v��Jq@�h��ɣ�)u>
X�v#Dr�Ivs@ �7:Dt@ I�)u�u��ib}~�>X�v@:Dw@Fv�Ʈ�X��
X�*):D4���o�ݙe�Hd�0�B8fx�Ivy@f/�)����Gds�)z@�{����{�:|. �f�{fd�V��^�|@Gd�:o���-�^�}@X�
�x>��&��*��+f~�Hdr�ɣ�o�}bO <���:|. c*X���ݵX��oe=�{X��@Gd���&�* ��N�.�{�Gd�@f�X������hGdQV15�H�h7Mmc*X�gf�&X�s������ʴ��^�7�~8
	Mm�I"Qƻ��P���@�&,�}�	C+k�^�(�^��z��8�\����&��H�_�	�}(�H��:� �*Э���)u�*��j��)#!Dr�}@�ɣ9���ܻS�&�R9��%�H�����
f�����fDш�m*k���?��Ǽ0�jŋ	}��+Ԡ)�\��3h$��{�@�&�@f��닯��@fզWd	�	}�I"g�݌�^��@f��<���	��	}:f#�!Da��bv�5����\��@Fm�
;���b��( �d�@\dh�>���&��>��)�U���]��b�I"/7�>=����H��I"3U����c��i((�z����I��@FmȀ�	��	}��H�
I"�����c6=�&�@�i[��>ĉ�c5���>D�@Fm�&�&��`��Jd ��)�gb��Jd�&�>|o�Ӄ�cQ!D"�}��d�Fm�ER�£���A��>Doͣ��Jd��)��>D�]v
�y���Eٛ�>D%�I"��£��)�>D���-u��)�@Mv���m�Jdt�	}y%-u��>D��Jd�)3�>D��`3*�j������ w�y_ه<������ �-u�>|��	}��>D�Mv��Jdk۴�� e�Kv�i~��b��Kv��fG)�'纶Jd�Mvh�{~��i~�Kv�� �@>|�@�>���zy�Jd�@�>�q�
)���ze6>|��h�Kv���z��>D�h��>D�� ���t>Fm�@>D=�h=���fp�>D��Jd���z��Kv�@$n��>D����{~;�zy�>D� _��H���z���`}ף��>D��>DX����zNj��t�>D�h	X�G�Jd��h��>D��Jd��a�@���v�T��>D�ֹ@Mv�)&��kZ�4�@-u9���#$n�� N�h�
�QI��@-u��Ȁ�@�c�� ��z�@-u��h�� �y�o�=�re�� �@v5��fd�w�����@
}���f� ��a�u>�'D��zHu>e����%���z�Kv��{~�x�!�h�):��Kv�@I9�@�h����#���,�hA� ����@
}� ��xb��dk�q���zd��>� ��b�@I9'��x�ɣ�� �Ad��M"���D4� ��D�磸
}@�b�Ad��Do�b��@v���D�M"�@�%<�	f������Adh3
 ��`��D� ��x��@
};�z�Ad�/
 ����Ad����� ���	��D�Nd��&��&Xf�Ad���Ρ@v�Ad�@f
��'�b�@Nd����8D�`*�NdR� �b�/fN��D�֣�@8D�^�_����Ad@
 �M"X�{D�@f=�}��!�&�.8D�):��b/Nd���@ff8D�@
}�����"o2�@Nd5���	�b@� �Adf:�5b(8D�^��%D� �Ad���D����
%Dtm~��D� z����}��Ad�<y� �_"Q��ĢNd�o��&�����z�>;�}dfگfb�&�@
 ��DR��_���ȶ#g�1'6�AdY�#g"7
 =���h�Ym�@f$�t�g�#g�@
 ݃ô���@Nd.�b�@
 �~�Nd9���@�a�)�%�&8Nd_cD.�#g��&tfzNd�"��X�6S������#g%�Ym�@Nd�oeO�F��j�=&��*Dl8D;Ov�Km�Ym@�<�a��+%��f�$�a��}�@2����DDĂ{ed��7g��z��"�s��@DD��Ym�@ �ADD���
��H�}���d���>gd��ȃ;e�@�a���i3�$'T�b~j���'�5���@DD��^��!�Ɓ�3�@�b*$uG?DDy m~M�9���@�b���j���z���i�@g�b~�@DDQ�7K�#g��i�@DD�
f�1'�:F��#g�@
f��l	�m�@�b^%��OEd脁?w����~F=Ed�@
f^%��%�x>��������L�t��ô=���-:~�y_��	�ۘDD���h��b��YmADD��Ymք�h��)n;:DD��y΋�h��)n�y4�b*��D7����A$uD;nDz)nb��j���zi�
)��dA�zADD��z��D9w��H2��z����j6���io�!,�;���hj$u7�)nA����ô!��iADDA�b�CÄM9e��h����8���@9|��D	��h�@9�S�m��{��h
��b�f*��D^��-A�b��D��(|��b���%
�Im�b~G�}x8��hA��e
fA�b9� y��h	���bބ7\̍<D�Ed�k�R��b���a�+��%�⭞�bA
f���%����hz%
fՏϣA
fAEd�{���
��h!ɪ����z��h��z��h��jѕ�F������h$֣�����h��)n��>�>	�4b€��W��h�)n���z&�1ы��]m#�)n2�jA�>%��A�>A@9.��7AOm%��A]mX�.D/�	���>��>��.DU��b��@9z@9(�p���b5@9A]m��A�>n]m��j~+2�\@9e�f��<�'��n�>@֣3�!��f0�z	]m��d*�Om�OmF����
�zd�	������b�ׂ A�>�
�z!��b"A�>����� �����<D]��d�z��4X�6�����#A�x@�-�.D��o�o�}��j�����{$A�z%A�i(�����	�.Dd������>&A�z��x{��*��P7�Bm'��z>�?_X�h� #�Hd(�Hd��>���)�,D*A�>��{�,D5�ʘ+�Hd�,D�]m5�(,A -�,Dj��,�>���.A�h�	]m�jH�h�Om/A]m0A�Y%�Hd���� �� ��{� 1�Hd2AMm3A-n4A 5AUdm� m? 	�z3��>��ad�z�,D6A "��h7AUd�	)s�_9��i8AMm9AUd�,Da�_9:A�z�h��Iv;�Hd<AUd��Hd2AMm�; �-na�*#j6��z��a΂�a=A����h�
Ud�=�h�Mm>A ?AUdQ71��j���>���ZdX�_99{0:�%�� �:`1@�=@AUdAAfB� y�Iv��a߁HdCAUdDAf�CC�� �}-��he� ʋ
ن$ 4UdهHd����_9EA}��#gO��j5o|�Ɇ�h�,DѪHdFA G�HdHA IA$u�[�j/ J�Hd,�z��h
�!���k۴׀��UdKA �����j
���LAUd�Mm��hMAUdNA-n�
gȜ˕�!Mmii*OA-n6�}x���dUd�bPAMm
(UdQA$u��zy�Ivd�ۘ$uYMm�4UdI
��a�&%�IvRAK"��9瘁�#�Iv�
�z��aT�_9���l�de��_9s�E���Ivk�\�SAf,��;��[mk+!s��z��x��&5�by�IvTA`14�b����)ud�b���>=���5��>�b����f��5��#�߁h��>U��>
��>VA$uWA+!2f.�mW$z�XA��Y��>$u>��Q��>���샿>�$u�Ó�f�e��ie��>=����<�ߣb��.Z��>D����2f�}x����[��>;���b��D5��>��>5�	>|\�25֣�	��@��ݞ�t3��&!��f�	��n���>9���]��>5�d���^�2z����F�����_��h0K"�
�X�wU`AfaA+!��=�Fm�+!ofĂ2b�>Dc��hU�Xd���fw��d�Xdj��-΀Xd�����\�&�f���>e�>Df�XdG�Xd<f耘�k��id)4�i��������g��h�>Dd)��h���x��beh��>iA�a�>D��hǔXdo�>Dd���j��#��>���k�XdlA�a����m�Xdd)���z��S��`�}~��-��oen��z�����O��ao��zpA�a��
-ud��+a�˴
���&q��zH�Xd
�9���}~r�;us��i��ٽ���j��Xd��&t��zX�8��&�Ăk~)��h��zuA}~z��,	��&�
)�G��-u��Y�k~�
f.��a��O��zv�Xd��ܱXdل������fw�Xd��
����z�)Q��H��O�7%k۴^֣x�>D��Xd��a�:'t�����-y�Xdz��,�:'wQҾ��{A}~v������� :����:'�كgnv�A|A-u}A�am�í���:'���~��z��z�:'��
-u���z�:'}~Ă�iQ�g�l8��xh�:'���z!����:'��ʐ�������&k�����z�:'m�k�I9���L��,q�X���x˃�-���d�I9&����˴�3D낅L&�D��AI9�-} :	'�j���'��`�݄A���1��X�v���	fރ�~�A��.�:�E�j��&V7�}l2ʸk�?%}��a�!������D3�ށ�{��fyf��<����W}�A ��\���k�}�A �}�:'�o��B�*��{)/�ψ:'uߣՀ
��C�3!�d|
�ܣ�.�)u�:'�A /�k��	f�A\dI�Ad�:'f�}�ߣ�1 ˆ)	�>3���)u۽<��_�A\dJ��&=��/�3�Ȁ䁎��b5�"n��)u�A\d��N*5����b9�ל�A�x���l6�����b�A\do������&�A\d�A �A�5�A8D�A\d��}��&�AU��\dƔ�&Q��b��z�>��"n	�z*��`�A\d���be�z}���\d�A�>]v‰�.����Aߣ=�����}��)u�A\d�}�A A��u> ]v��Y7\dd�	��)u5�"n���&d�>j��b��AdG]v��&�A�z›Ad�z�A�>�E���n�>�Ym�\�{~[��ݧA �\d�z�A]v�A\d�A�z���k��I
���f�!f�Y]vݐ�b�A\d��9;�Ym(&�A�>O��&�&K������>b�Km���ܮA\di�g���Xk���8D���)��A6n��zc�}��Km� �% !�;�5DD+!�A�z���&e�6�������i��&k+!��
��ADD�A�z�Km��i�"n������d�X��'�z{��A�>�2DD��=����8|��ֵA]vm��ze�@m:��{�6n��a�X^�
DD��@mT��5�����>{�x�A��9]v�A�>�����uk=rDD���)���z�9
f�I"�DD�	��_
f���>��_h�`�����-u��zk=8
i1
f����
-u(�+!�A�`5�m�a��>)��f=�5���j% !��-u���+V�	
�?�R摻A-u��Ym։�o��@m��f-u���z��_"���D-uz�	}�����D=�$��i1���u�#-u�A��j裂�M9�ADD�`*��f��D�ADD)��f��D�A
f�-uQ���F��f��(��f-u�DD%��܂`*:�~��bt���7rӨ�� �A�S����A`*쁙b���D�<�����@9k�f��D!gY��u���v����b�i1ĵ�W� g�8��fb�b�f�`*$��h�`��N����A�b�A��i`*��b뀘b��`�A�5ه<�`*�i1�� �˕(-u}��D�'-u%Up���z�Ag�� ��j9V���jҀ"u�`*��5b��ٙD%��2���3���z�a8�Ȉk�f�A]m����߄�D�峮��T��A�����:� �f���jD�<D)�f�`*��D�"u�	}��	�f�`*�	}��i`*Ɓ�����w�����>��"u��>���:2:�)�@9*���H`*�A]mڳ�>뀘bZ� l��j��kH�'DŢ���9{�O��j�<�냫��A]m9��i� �F��>���72Ђ<�=�Cr��>"Cd��'DB��.3��j���n��ADm��X�o	�Y�ݝ ����Ad~�ADm�'D�%��^ ��´d���ADm��>�ADm6����,�	�Y\Dm�>�'D����Ad~�A���DmD�<D5�27��!  Ăb~"�%6��+t���a8S�:|��Hd��{,Dm�	�2���:|���>'
�qӣ��i����5�2����b~J�"�j��+�$���+j��� .�i�O	)��8��>7
}p���AWd�AWdԊ'D���j�b~�yc �A
}���Wd�>Ȥ%K_\�Dm��g?_+�����h�A �
}g�m1@"�9a�j
	
}��M"�h��h
�d��b����G�k�d|C�ɪ��۾;n�ADm�3vRb�m1 �
}��݇��d��?:k�v���a��M"�ADmo��+�A
}�᷍m1� k�{�ADm�����tD�o�����b~��*�w4��G��$�A'�v�Y�rNd�A�{�	f��&��Aiel����7$u��~��"5 �A�z�A@"��`	��b�f*D�`1����b�����F9�6$u��F9�� n�Nd���nN� n2�F9���j�=���X7
}�����ANd3
}�F99�3�/Wd�zO� n0�� nlNd�
��|T���h��zk��&�d*�A
}��hz���� n�f��i�G9!X�i!����A�z������&!D�f�A�z�f*�!D� n����9!�Af��F9��_9���-Y!DZ�#��j��zB!D�� nJ��-��B�B!DBf#� n�9!!DJ�X�Ё�B��$�� z���!�ܫ	E�!D(�fBf�F9*��f�"$u*D�!DB�zQ;���*D�!D��z�� n��!|˛ n6����z��b�f6��d��7�N%B!Dz��"� n�bT"!D��
� n���	شE� n!�)�z�F9���+耦=�=f~� n�裛Af~B���/�zϋ�av�zD�2� nF�z����5b	B�zB!Dȕ Oش^5!D��[
ӣ����z� -!耴�X��
B!D��O�f!D���Ε>D� �!D�f~m���fu�Zd�(g�>D�oe�!D���%fB�jw�f�o��������ݴ7!DAoe� ��"��h�>DU!DD��
�Zd8� B���be�>D��"���-��h���)��bD�!|�-:B�z�>Dl�s{� �����׀����`ʋ���>Ds�⨁�d�Zd���d��E��)|9�z�ZdBf~Ԃ�")�Im�aB�zBf~(���aѧi~7&�z��>�o��t�u$�b�a�ش\���>��9{0� �Zd�<g� A���>D���H��Zd$��;��-:��Im� �
)x��6v�2�����B$n��v �Zde��=�F���c�q�-��+!�>D@��a"�>D)��֜$nl���[�Zd� :�$n��be�(.|̑�eީIm*��䕯�	)��ii��+H�c����k۽��γ����@v�����$n#B)=J���
}�-i*������f8�av��_qme��5Z�Im$B$ng�8(��jv���i~����=��I3���+}�N��
i*���
��V��F%B%|��9�w�Im�i*���j�a���&�i~H�6�'™-t�Imj��x1�.D(�ImyjzF��+b�"��)�Ime�Q�%|�۽⁕4*�.D-��+�’�oP�.D�%|+B$nV :��a���{pش,�	fd��>���R%|+!$n�W��i-�Ad�.B$n���/B S���7$n�i*0�%Dt
�.膙��1�%DW5}������&��l2¢jj.|���.D��Y[���耦�R��>3�o4B^d��a5�%Dj���=��6B�&i*7�%DX��8B^d�i*_�49B�ad��:��n*�%D;B^d�
ӣ<B�&W�%D���J�*gf�%D^d�-|=B�&>B^dt�~�!��?B�a���?��ބ�{�%|%�f�΢�X-~�}!��jx�-�
�l��Ƕ+!r�+�}@B�&A�%DÎ��)���}/+!��b���hB�oCBm~_�%D�����+��#DBMm���F�m~EB#D+1D�MmFBMmR�Ad!�"n�[��#D{���ʐ�w�
�G�%DHB#D���(IB��JB#Dc^d.���Ad:Ud��Ӂa��z��$gKB#DLB}~�%D�����&�"n��&J����#|'Ud�1D�	��"*Mm��k~����;�&���z���MB�&�}~�#D���{Mm�^dT���l��z�z�k~&/Mm��z��|��"n�#D��r�S�-!���d�}��}���kĆYmNBDvZ�OBDDP�zL��QBDD�Ϣ��d�}���hR�̴�e�A��ޤ DD����~8s�)��{	�}l��X���4�S�)T�l8
DDUB+!V�)��%��@m���˸	�)WBDDX�(|�`~	MmY�@m�%��m~i#DDDW*MmZ�`~��z�Mm[�@m$�P"#D\BDD��@m�Mm]�`~����^B#Db�(|_�@m����(|`�@m��kV�k~b+��XI"$ۿ
f��@m��#|a�@m���M*-uԀ�A#Edn��4��*�I"/�#|w�u�b�k~Ed�i�c�@mĉ`~� ��f���d�#|���{(�
��e�(|9�����-o�?���&ܸ�DDݎ�kf�f���+%���gBDDi�M9O�j�DD7�`~hBf�i�)?���,�`~�*ֶ��FDD��@m�J	��l8���x=v_{��8��m&Kj�l8����.�<�j���֮B9<�$:��4H�4�۟fm�kk�`~$��b��"l�mel�@m!�`~��c�mB�jY�.n�@m�f��o�F"��(|p�@m7�;u��b_[��;uq�(|@�om*rB�j��)��b���sB}:��tB}��
}J������uB
fvB�zo����&nw€bxB
f--u"�h>9�zEdyB-uD���fzB�|>�������i!X�i���z��?�9��|!��a�>���{B�z�"uP���*�zM�;u8�U��6}�fo~��X1�Gxy�;u,����f��������a���#��z��C��;utB9=��%|B�z�
&n��C9���O�B��'|}B}F��~~B}�]m_ *֫$��'Dv�ꣀB�>��I
�ˁ�'D&���
�'Dn���}u��%9�'Dcӣ��B&n�'D�B�>�B}ރV[j'|]mA�X��'D#�����a�B�d��݇��aH��%�DmЁ������B�>��'D:��⛌���;�ǁ��t�.�z:�'D���z�W�'DXy��B�z��f��'D)����ًB�kcӣx�'D2��7��z#�'D���,�zt磈�e|�B�z��,D�����'DX����B'|��,Dw�'D�B�>�x�����,D�'D�B�>�����aj'|"��>��Hd��,De�ݔB�>*��B����)�zwbDm��tAe�,D����-n��,De�'D$����)��'D8
ӣ�-nի�>�
�ݗ���,D�����,D��>h�'D���!���MȀ&�<��>��'D):�-n�;|��,D�������Ɓ'D��}��,D������|��+>)a��1��xބ�S��Iv�&B�&���_9�)�>�Bd~�B�b\-�>k��8��a��_9j�-%-n��b[�,D�� ��cy-n"�,D�&Dm��Z���b���z�6��z�S��(ݬ,D
��z�B8D�BԴ�B*D��,D�� ��f��,D�B*D��}�<|\���5M��*D���&��,D���B8D�����,D�B*Dy-n���&\d��X�����ځ�<*D�BM"�-ni�,Dw�IҀ��z%-n�۹�b�#G9�Y�b���8�*D98�g�8�R�F9<��8��̣%-n�B*D.�_9����b\d�B*D��*|耫8%���S�ݰ�F9�+>�m�|[�@m2��%�@m���|��&e!D�*D�oF!DT��j��֢�
!DՀh��Ivߝ4@�!D,*D��ie�B�a�4f�/!D��@m��oyr*�2�f��8;�����| ��>̑�}��.�����*|@��|"��8��}�Boe��|�B*D{澥�>��}j*D��}�+n���}��+nm��ji*D��}���m.�|	j�r<*D�©j���/!D�*D�Cd�­>�8D������}m�|�f*?	��)�}������e���}M��>W�`v�}��arVma�`��};�&4������Y�}��}"$�a����5�o��}��+���>b��?�}+�-g�&
�a��}Տ���|Hs�]��	o��ကx�����}n����|b�i\�a�Vm��}+*�_�­>�B{���ޫ�ǒ�
!D;������o�����<:oe��i�B!D��ޯ�$uoe�$���<9���}߁�n��be�Bme���$�­>(	2�BMv��}���j�­>��}�>�Bme���,�­>�	2��+n�!< �}��az�`����}me�Aj���))�����}��I@�#����Mv�	2dy�4����)��,�`Ɓ�S1��kރr���`�����
�҄)D�j�{�j���`�8�B�$9�0�Vm@�)$���闁-:��e$n�B�$�#U�)J$n�̣�"�|I��6[��89W���)�{��$n�Dm�B.|��l��ImX�)ϖCaW�)�BDm����оW��BDmЁ����|P�)5���R/Dmo��9�{���
�kDm��i?���,���5 �BDmj'qЁ{����<D;�܅�V%�.D�$n,.|���BMv��\�B.|���Bmee���Bme 
�ܹ.|�����)��8��.DW?A�)Dm&��cW?t.|o-�jS��&��.DZٴ��BW?qԣ�B3D͟<D�B.|��1��)Ҁ���.D��|�B�jͷiL�$n��<D�bS�<Dn��`HF2���`
}�%|�����>�|+�Imm��>��'�)�����A��`�f.|(��y3Dt�&�����&�.}�8�B.|�W���`'�*o�3D(Dm�B���	۽�|�}��|�P}�BDm�����|�y��&l{e�3D<���	��*����`�
��6�<DԂ8���=��.D<�k"��b �<D��c+���-g�B�z�B�jĄ|�@��`��~�.D�|���r�4�
�zԀ��l�������&t�%Ԃ�k�F9�B�z�����8�S�|�Ԁ���$�}���&l�-Q����!3D�3D��/ު�`l"g+.ٴo�?��z۲�&"��8�B3D��&$�F9��&'���-o��3�F9�-������B�|=�ԙ�	g{���Ṅ�`������x��[�F9��9��q��B�>���&�1D�Y�?:������z�������&�B�>��¹	;��|�BMmP���Fa$�E����bm~~�x������&܈{~l2ٴZ����~��F"�\d�B�>o�~��)���ϡ��Հ�a��)-�44�#81D��.��&�	{�31D��)���a�F9��[m(��K
����&R���s1D��9���F9�-g" �B���g��))�B�-��%m�8h�[mv&6n7�-���&ǔ�z�7�-��[m;@"�z8ɰ�z����
8�y-��&p���R��z�)��s���yI��dX���T�X$v)�y��&���km�.
��H(Cd.�y�m~�B1D�Mm�c7��<Z�%�b�1D��ܮ�&�B�>/�Mm�1Da6n	�Y����a��a�B1D��>���B1DC�b�Jd8�8[�q�˺E�h����[m��jb����y~׀������b�[mr�ބ���|��{~�p+���C�bb��'r�˴P�[m��b9�[ !@��h�[m�A)Q���Y�)n"�\dƁ:j8�|r�����.��dCf�毹)g� ĉ�~��f��hGC�C)��|��Im�z�l{eL�ImQ71D�գ�7)	�Imƪt�Jm�8s'#ϋ�ܕ�g
d��ː(��z1Cu��Im
��h9���o���H���̽�{��}*�{T\�|���Im�۷C�c�����n� D��	%�����&!N��h��8ج5�
�ImS�|�m�|:����B��.%���aQ�Im����)������)�"uh���)v�޴.�Ϙ4@�/���h.�b\���������h�&���j��C�aO�_��h��;��j�Imɀ�h)>d{��nC�a��h��8Е)<���`�Im��)�>�aցG�%74S*���)�����h��|=�p��y�Ӄ�h����C�a�)nկ)���h�|����8���zC�a��"u聆-C}~S�|�h�)C��V�=�8�|����x�|����>�����C%{���z�+ C�ao�|�!C}~t�~���z��a��
"C}~���#C�|��z'ڋ�8
@9ᓭ>���z$�)����ϒ)%�z_�庇��a�)���J�3Z��&��z+��>�}���i��ye�}	�
;'C:D������֝�������E2���)(C:D��n�&[�x)C@9��a����b���֜�>�}ց�~ۗ�c�����a}*�"u"��kQ�W��:Di��&�:D9��oݸ�3�i+C�Nb},�:|�{�g}~.��.:D���&-C:D%{�}~.C�%q�%$q��z�:|�?�/áx��0�:|1C-n�!ױ�x2�:|�:D\�:|���3C-n4áx8�|�9��5�zl�8�5����Eu�o^�|��6Mmm�8Wo|��>o�~̑ԙ��*��8����,�*�@96Co�#MmS:�|Ё����,�*�i��Y�7�;n�Mm��*H�)u��t��:D�#oyMmc�ݸ-n8Co�z�:D9Co8Mm��_9���|����F�5�%�(:D���y:Co;C-nj3:D��c,�*Mm<�@92<o��y"�*�j)o���y��=���>CC�]�:|҇
�?C8D��޴)o�i8�
8D����^�@Àb:8DAÀb���bŁ*:o�8DB�:|CÀb��⏀b�<8D� DC8D��ݨo�o�8D�=�Mmo�o�8ث�׀�EÀb��^�&MmFC8D�-8DG�@m���b�
-n��8|HCMmIC8D�\dJC8D��ݯ�*�5Mm��n�Mm����\dKC8D���o��~��8�LC8DM�"n��-��|Ł*N�BmO�Fm���/L!D8DPÀb=�wA�8DS�|��@mz�8|QC�zRC!Db�j̛�gSC!DT�8|6(ף���b9���bA�)u��U�8|��iԙ��k5��ɀ8|��|VC�z.��L��+�x>��~WC�-XC\dYC8D�!Gd���bZC8D���[Àb�ˣ
��ȃ��8�|�#D�8Dҫ�b�Q�8�R8D.����Y�\C!D]C8Db��b^Àb\;ge��.�>����@�R-�8�_Cdee��� `�@m��8|���b�Ƈ���a�@m�de�� bC�zS�|�o��8[�f�J��3��$Q�cCVm��8dCVm�&>|����.!D�
����8|�)de�|eC�)�)��Vm��>D������� �8e !DQҢ��8fC�z�6!DT�i�x�j��8���?`�ZdgC!DhC�yiC!D��>D���d���j�>D�!Dep��-k�>D�>|l�g�3]m�>DҀ�?o��<In�>D3�"��)�#��ʈ��I"o�|�o�Zd�ԙ�)!i�y��-p�>D_���� J��|qC�y�I"��|����	�rC�y�:deY�>Dȗ�奂-g��E���>D��$��sC�yK����k�?tC>|u�[v���,�����>|ه�'vC�ywC)xC�b��3y��'��b�=�b#��,>��~�dezC>|A�>De��~��b{C>|k�b��>D/��襙��>D5�_��b��9|�>D�6Dmp�b��I�}C<|�ף�)�"��|~�>Dd<|��|�V�'A����>D�C�b҆T;�C<|�Dm���|�C�b�C�y�-gj�k�C<|��>Do����=Q҅�>D瀼~��8���|��	I"z�|�
Fms��C�y�B�Vo��~"��|�C<|��<D:����~�C<|#*�-��D�}S�|�o�~�Y9��<D?	z�Xz�|�z��C<|?	��+�k��$��<D�<|��c��?�ç,��5���x	�9�(}<|��@v�(%|�6�C�b��|]}�<D�C}�j�C<|�}��,7�b4�oDm	�<D���,b�o*�63��4��<D��^�L>�o~���>���Ad�C}��ߘ�Ǟ<|�C})}Dm�C<|�CDm�C=n�$n�ûj���|H6$nl�<D\�8�=n�Dm�ÿ>J�|���Adm��
���<�Ad�<|O
)��&��Ad��i�C�&��̒E��j9��ՠˣz���c�Um��<DՔ�iY��>��Ad,�<D�@v�%��Ad:�&>^d9��1i�<D��a���joާZ� n�Ady�&��Ad�C�&��<D�C=n⁣d�
���
Y9�C=n�C}�=n�C�O?�-*��"�&���a�'g�=À8����C%|���C'D9����?vy#Ȁ�	}��-
$}�C%|��Ad�C�&��Ad�oJ�V�4i��b~��Ad2&������#��fm�����jt�+jI9m~��'D��&��M"*�	o��%D]�&�8��?:o���
;t��h�I9��a�1��@Ѐ	j!O�)��h_�R��'D��{~X�V1l�8ج�AdF��j�&���~����)	�a&�-!��a��M"���C�&c�Fm��ie?	W1G��$�C�&H�|�G��$�@"��-!S������ !T�ԣ��8�z9��-b�)�3�i��b~�&:�$@"� !��c�F9�$���	 !�CDDp���ā	�/��fxDD샇$��Om�CDDӃ�~b�	9DD��y�3@��e��CDD@<�c6+ !
�[m@DDG��$��[���׉�ri)O���f��Š��(�/v�.DD�i8�C*D&Dv�CDDI
�	�&I9�6�ۃ��������Yց|����p���Ş�U2�C*D��}�CDD�|Rbo^�*|�V^�
o�*D�C
o���瀼~k����/Ed��f瀼~�C�|��*|���Ř�)��)݁}$�ԣF�u��C@",Ed��iezB
o��41�����ie�C
o9��(Ed������j[f�N�?�:FfEdt�ԣ�DDe�ۘ��s��1���Im�CEd���@"{Cdi��5�-.�!g��Im��!g	��~ DvЊIm�C���CDDҀ�&�$|\��&�����
Fn�CDD���o{�;|�CEdo{�Հ���eڔ���CDD���Imo{��fo��~�)��!g΀�&�C}p�o��Im��㑨�0,�f)J��@�zՂF"���o{����1�
o�{�b�Im��4��&�l�!g1CEd)'�Im��~5	�&p��*�+!]6Ed�C
o�C)۽�z=2Ed��|Ɓ��u>Edg;|ց����"u�C�z!
{�5

oee��������Kv$���CEdy)���Yk��d�!g�;Ed�%�A����#��C�>9֝;�z�Cme��&���䛾��~T�$��C�>����`�ν�9��)n���&��Im+��&��z�	�z�����Img�>��?�)!
{���>]_m:�FmX���� �3me�{����%%�z�� ��F��~��5��X���o{��2�� ��Cd���ao{�"*me��Cdk{����,�f��M9��a	%�~�?!��`Ӄ�~��Cd5��yu������~�� k�>���yk�Y���Cd��&!���aQ��~����?!���-�Cd~ل��k��{��Cd���z��-a��aj�����B�Cd���z�C}9�4��CdO����zX��O��A�M9�M9�V�m�z҇���C�y��a�C�&��Hd��%B
����.D��Cd�}=4��C.|u��zJ�Cd�C�>3����hme��Cd(S>����,mem�8|�5Z����C�b��z��+Q}Q�.D���i�����Hdc4|���ܘ��z�=��Hd��̅����C-n�i*��>��|��Hd�MmY�5@�o��Hd���`�oh�CdXjz��Cdw��|l�hc�yR�Cd�%Mm��h0�Cd�\d��Hdĉ,Do��Hd�CMm��Cd��C��,}��nӢ��c�%)�
;|MmꋲΩW�w}P6�����Co�"����}��������z�[m�-n
�����K��z�C}��-�Hd���z�����,{�HdܱHd�ל)���"�,D��Hd��.D�;n��y_���[m��,Dv۽�����&u��t3ӏ	:��Hd禵~ȒZOd��_��Hd��)yMml0Fv��)�o��Hd�ԣ�Co@��)��HdN�)�Fv	4@$�?5�')���)�*MmOX-N���*')��DMm3-n���|=5DMmܒQ��4o
���"n�o�)9��8�i�]�iq�Ehg�E�U/L�*'�-k��~F-o%�)큂b�Bm�Hʮ1D�[m��-ƴ�z�)	DGd�¢]�[m
Gd3�)�
DGd��\d��c��zg�-�Gd��`z��
�����zQ�&��z���zV(��x��jĭ>�z�޸�)7�-�	�]���ծ�>���F
F�
Do%�J��z$��ĭ>��8�y�����-�c�)o�+Ŀ>Ć�x��'���z΂�>
.�?cUdD�`��j��z�)r�<�de��z���z��%��?�>�)o�+��J�Ħ�G��ԇ�)I3�'��&P�`o%7�&�+��ĺc�Ám���J��VD�`� j�4�y����&D�`o%	;|Ȁ�0�'#��'�DGdo%��$:J֢�a�GdD�`T���D#Dz��z��zo%D�`��>D�^�g��hDGd�)!����B)Ё�f��z��>�GdDMv`���Udk{�DMvψ�z�>s���)!�z%MvI��z���yv�n ĭ>!Ŀ>� 3"ĭ>yMv�	!!�&)�y���3��/Mv�)!k{�j����y��Y�#DMv$D�`�>D׉5l��-���%�zv�5[i
��K/��&D�`RF�,�m*h�^���+O�m�)���7�`��0=��z8��'Ě�(ěy���) �;g�ܴMv)ěy�(g!���*Do���+Doˆ(gk{�,D�b���h���5���8H6o<��_4�bv�+�os�\d�,�b-Dol)|e�(g�oY)!�j.Doϋe#/DMv0�>DN�b�:)!�!!����1Do�)�[���z�Mv9��2Do��f3Do�	�bz%Mv�)!1Do4D�y��6�))���G&�y�;Mv�Mvb��u�f�-|�����f>Mv5ěy��&V����
MvZ�Xd\�f6�%��%`!�YG�#���7D�b	�9�8�f��i~�����.'�Xd9�f��b:D�b�Jdw�i~ဃ�;D�z<�|��(�=�;u�b;��>Do���dD�"g?ą$2�Jd�Om��n�*�Jd/*Ah�z8
��@�Jd�
jꏶRRoA�Ad���h�����z�:�9��#u�Coe�Kv#j]B�Ade�KvW�'D���AdCDo��h��aXF�m�����D�#u�$��z�"g��h݇|�E�'D�t%FD�h��KvG�'D:�|�8�Kv��"g��f���5�+u����9�h�,veN���ɚ���c�
��~H�#up�,��'Dg�|�ID�zJ�,I
��R�#uȀ���
܁Ad���mt%���-"�XdYC�,��+ÀJdJD�֨
�z�g��5�
�'D8t%�	}~KD�,��8o�d�LD�z��h���?�'DA��MD�-p������o&��%a�n��>�AdU��?ND�,��Kv_���‰�9�U
�+uOD�>ǃ"`��~��?PDm~d���#uv�AdQ�>��h�U�Ύ�~��3�䅼~�G+Q�AdX�AdRD�z����hSD�zT�)U�|��-!݀'D�&V�M"��#ug�B�*��emt%��1'�W?�WD�>�1'?���XD�z_%m~8��c�6fYD�h��1'��8��b�m~Ԃ|�}�1'ZD�>[D�b����{��/9�,���D�-!��O�\���\DDv��:g�1'�
�z� �1�b���֣Dv�1'��]mRDD
;|S�x�*D]DNd^D�z�DD_DVm`��zaDDD$�=��DDb�1'��bVm�Nd���cDVm��b1��d>�bO���9��X�vdDNdeD*D냾�Q�>S��bfDDD	��xgDDD��b�*D�de"$ۡ=m~���&g�
f�#5���f�)�!Dv@��a�b5l1jDDh�1'o�f�	\diDDD��1'jD*D�|�]Nd9�M"k�1'k�����~���*�
i�Ed���>k��zlD*D�xmD�bT�'x�'�nċbo�1'���b7�bpębv�ʹqD�b�Dv
	'urċb[����P�c�	o�
�:�N�s�ot��z���bu�	oOfvDݣwD�b�f��bx��hT/DD\�|�a��byDDv
'u�OvzDNd�DD{DVm��	op��j:�|�ͱ	oOv�Vm|Df2۴���b"Vm}��h ��x~��zDNd���h��z��	o��o���b������,�}*g��b��&��jցme���h��F"�D�$���?Ԉڢ���hL��x+�|��	o�}+�|��$�-|�$��F"Q�@m��+���ĵGk۽:�|��f��v{�+!ݣJ��(Ov��F"���hD
)5���@�ʻ��⣇��h	>�
�F"F��j���h.��b�?�\���AEd��	o5�<:
'u���h��
�o$�|��ębd�ʻ9�ʌD�&���<�{t�]m���h�me�Ļj��q����h�?��5'�|����h&�	op5'���:�|�:�B�X���?!"���5'�	2튬(���U�m��c�ܑ�|��棼�P�	�?!΀|���{e�?!Q�֜�{eo���v�M9Q��{��f<�_"�"��{e�;|G�y%��)�D�zc�_"n�3�������s�?!p�$�D�i��?!��&|�{e$�|�腬($�|���'AVvݐ�&Հ
�����y%��V$�|�3%�5���	���5@9P������,@9�������?���{!�b�� �D`*�� ͓��:�|��@9AVv���6��~5'�(m5'�Vvk�f���$p�.`*�}��bZ	��?��@9��C
!�V(���*u�ܫ���j3��."Dm�D�b�5G�|��&�)���7&Vv��&`���y%,{�-)�|�Y�{�/
oԀ��G�y%�D),�|��	��|���j��b5
o��y%ˆ_"�D
of�.DĄ|��?!�D)l�z(4
o݀{e�D�j���{e�Vv�D�zY���cO-g=nl�Hd�
o��}���iнIvc�<DH�zC)�.DV	Ca'�f�R"Dm��Ԋ;n�D
o�d$j��D�zW�R��Vv�+g)
o�ȣ�W1�D
o�ĉy��[mh�8��Fa�Vv��A�
Dm׉��[m��z��Hd�DCA#C�1�ie�|��f��l����� �'��%��
	�h��ie�Ӏ�D�bh�+g��F9k���D�z�Âb�)[����Ԟ�F9�)���~��h'��R
o�D)��h���L�D�z��F9���[���Ӑ�O��ǩD)��hO�jރ���D)h�,!�
oJ��+��"��$��$�&�D�t���DWd�!
o��8'N�F��i��D]m)�,!�,!~�����F9O�xګ�㒮�,!���i��8'���8'.��f�f���
�i8�=Ud��[m��;n�8'��%-��(r�;nň,{*oD�|�֣��в�[m��8'�DUd�Fٵ�ie_j!Ř�`���>����}�DUd��8'�f9�Z��V�}e�
�f�D�-��f�a�B�;�z�DUd�Do��%"�=����w��nt����-Z����-3�Ud�ȣĂF9�D1D��-e������W���c�DUd�z0�&���b~�D�&Z����F9���>��>��8':��%4Ud�D�-�����~�D�&��`�D�&�����|�I�|���#�5ٌ����T���9��W�&�<ܥ�|�ֱ�{���O�Bm��:�`1J���S3@"�DUd��&\�|�S�<��DUd��&�<�<ݣk�&#U��DUd?�>�|�儸+��>D��5Zd�Y��D�-g`1$�|�4Ud=���,��`�D�-�D�b��P�DUda�ô��ZdՀW�n��>7%Doݣ���`+�ZdȀ
��|�mf��e��Zd)�Ac��vi�yfy��;͓�����z�f��)�DMvp6�b��)�ή�>Dk�p�mf�me�D�&���̇Zdތ�ʟ)��Im��|�$�8�<���&�����)�ěy+Mv�D�>���z�Dme��Im�D�- �|���)��ô����T�&(�-Ȁ�=%�|�&me��Zd�D�-��&i3��棹$g"<';�|��)��Zd��Zd���J��|�ޝZd%<'�8���)���e8*��%'�)<'g$gԂ<���Zd'Mv�ĵP�ִ��Zd:Fm�7$g	�b�[�c��yo�,��Zd��Zd�m8K6֣f�m8*�%*%Fm)�Xd�Dme���y��-�Xde	Mv��)]�y�G��@�����Xdy$gg�Xd��)���Dme�fSg#��)��i���z��}/��j�i��Xdo�f���zk�Bv��)���j5��y���zd� ��)���f_�w���z��Xd�8'�ěyA��+�Ġj��f$�%S��zd�����L���]m�z���j��Imk�EEP��d5��z��h��zj����XdT��jV��f�	�.�3{�ۖg��>�|���z;�Xd/�
$g��������z���z$g�Ġj7��zރ8����/U�Yv��<�|��D
)����뀡x�Fm���y���zvգc��y+�&��Xd�D^d9��Ăy��Xd�D^d+�&��&���|���z��Xd��}8��xh^d
)�����~8�����$I���:��?w^d{�Cd�Yv/'^dg
)� a��p��d�8'e
)l}~q�6�:^d��:g��8'
�����+�8'���Ec�`E�bh�:gG^d�+�zE
)�iG�CE�>�5�5���x��z��H�*�	od��x�%^d[�hЁ��)fՀ�����Mm
�����.���xE�%��z;��z�9�E
)��z���.šx��%��e3��.� i*�ogI9e�,{�f?����	����I��AdE
)���m�>�����&���nЁ�����n�
 a	E�>��|
�{~���E
)O^d_f�nu%Mm��ή�jC'գ�!ۭ4{E�� �>
E�9%�j��fX
^dH�J	ȗJ��:g�^d���v)�9��j+����n���_���
����DDg�:���EDvEDD��uQ�c�\d
���DDDEDDE\d߁I9�:gEDDE8D���{� ���Ede�+�j���u��f��`~%��d)��nE\d��|���|b�i&D�|�j�4@����`~>��fg���E\dE\dE�%EdeEDDY\d�5�%� ���+8Mm�nE8D"�kEDDS���	MmADvY�')f E�z!E�x"E\dm8D[����G]v#EEd�"g[C�$EEd%E\d�nM�`~Y\d&E]v*̀5uV��f>��	5u'E�j��|(EEd)��n+�xӫ������n�zz��X�o������)�)g�z�5u��Y���*E�j+E=u,��a+��?=<:�vЪ�;�8D:���-E8Dޣv]vWde.EDDlEd[��?�CDD/E5u/
o�3\dL�׉�xKDDD$�ܐ��`B\dl��0�`~�Ed��ܝ�m��1�`~z����a�Kmp�Ӹ,�o��5�5u��`~2�j���j��"`%�z^�`~3�oe��ִ����x��`~g7{�|�f�������X���v���m�|2��+g#@�=_D]v���b����x��|	�����YQ�����|4E�z���T�e�Ed� �x��l��䀦>��5��b��z�5u5EEd6E=u]�z7EEd�l8[��b8E�z9EEd5u{�f�0�z����������|a$�}z���$r���[��?�@�&�)�|:Ň?g)Б��1�;E�z<E�&��)gܒp�=Ei1*�շ��QiȎ)��%z����>�Km��|�_",�z�� �v۽�%�{�j��,l�M9!��J���i�M9~#	>ŧ,?E�.��>v�b���f
$@9<��,i
&O��f@ŧ,y�&������-A�Ca�	�X��@9+���BE@9��8�:+`*(�8�k��k�C�M9���,#d~ی_",�j5�%���,b�� ��f�|��,�)e��8DE�|!>d~���)z�:������d~���eC�Q2��)��,.��E�"nFE@9�GE�&HE�&$���+�����>,���큧,�V��bޤ�xIE�>��8�ĉ�zm��.���x�ۭe��h�u:�B�&��M9m�|JE)���z��bK�Jd�-g��|L����h�Z�N)���fJa8��|R��,�_"���L8�L�JdM��z�))��큧,��o%g#NE�bOŭ%PE�|�@9Qŧ,RŻjգ�@9`+�b�Jd�f_�դ�@9Sʼn9l-gm�bTE)�������0�b靉9UEOm�d��9�b�@9$�o��b�d~��)���VE�%�DmW�o�<�X��z��8�YEd~��!'ƒ�_��e�&�ی�i���A�oZ��z[E���j������z�Cd�6z
Cd���z��b,)�� ��A��#b���Q�VJČͽ�9$��{"�b�-go��\���)m�o]E�bRa8$���e��bt����������5)��%��JdL��R���	;�(��^E�b_E-g`E a�
���b� a��BmaE-g��h����a8���杻j���jbE-g%�������P�Q����[�b~;�������8�cE aX�oaOm5��^dE�-��&e�A���fz��9y�b~R��f����R�&Z�ief�4Sĉ�f���V�	b�b~5�x��o�Č�ie����4
;��-1�ie��8ؙ�c\����b~9�w�0�xNg�&��|���>��B�gE@m�ᣳ
�hE�-i��&ս�b��>X�r����&��b~��&��?��>��f/���{����>|{1��m1C�d9w���-m��h<��>g�)Ԃ�&Cd���bU��>ĉ�f��M"9S�m�|jE�>�b~m�|k��&=t�聾�
�l<u*
�>>��fW�Fٸ�€{��&lE�>�X�e�p�@"��>����mE<u��&{ߴn��&T��P����8�D9e�!��}~�m18�D9Oe��K��b��	)�8�*���#`Ä�&���oE�|����!�4�pE<uqE��€�	��b���y��j:���$)��>k��fn�-��jr�
��{Z�rE�jlNd�zf%�f9܉[E���|e�|u��& a�G���sE)f�ctE�j���>�me��yuE=g��>t�^d��fv��h���U�W��-wE�je��y�j���yv�Zd���i��[vȒ�mof��>C��)S�{e�$�>��f
���+�ޫ���kU~xE�-�����+yE)zE�z{E�>�f�"�)��?m�|��O+�me�<u�.�b{�Uy�j�me�H����l�'DFE�z?o�H��{�j8$���o��$|E�z}Eo~EFm�)'�ɣ٫���'D�,oEFm�0�z�Eo�EMv���=)�E�zZFmp)�H���R�Q o(�H���Րb��|�EfH6o�E�j;��(�E�z|���f�o�EFmme�
)��ܜMv�E)�
Fm��Od�EFm� MvWme�Ef�EFm�����V��n$��f�|�����5�<�>!������	��j��&(�\�fӗ�?AFm�?��k��kFm���-s�E�rj8���`���=��Q��?�ˁ����e�:�?��x_�%��(��R�?	�N΀�cFًE�b��Aa��x�,Fmcow����*A�+�F(�H���?$vR	����L���)���G7���|{.f�::��oFm�ů>��%R�?9{0l���>!�E)X���*�>!
�h��-3�€Ҁ�&�E�|$�ܐţxt��j�z0������V���J��~:�>!�E)��h����%�?�+)~�h�{�
��c$7)m�(��{d�&��h���f=��$������g�?�Eg2	۽�������}��?��&�E�hm :�������C�.�5S:�/3�;d	�-��?�E�z�E)R�?�ń-�E)��|$�?��V
{��"�i��|,I9�{�EI9��-B����ń-5��xʫ��l��&���>iI99X1�7*�z�EI9���:�xH�E�z��xg���l���,�zI9H	�%�ń-(��Pw/m~;���R
�z�)��|�Em~vף�	I9|I�"��.{�#I9*��-��h����:Cd�[m�EI9��%�E�z�E)b��,Ȁ�^���p��H�=c�
��{�+�����p��w������E7�Q;I{��>k
�"�|Y�[m��)�ť,�ť,\����-$gQ����E�z��|���=j�}�I"*D��4���y��ΦE�z�)�ń-�E$g9��쁥,��[m�EI9�E�z-��-t��,�裫EI9i*���I9S$g��yB�-,I9�$g��oe�Cm~�E�z�I9�	�iA�zCd��}��"n�EI9��|�s?�窉�����)�	�|=��z��qu\�G�����E$g�E�-���(`$m~.�-��)��c����ݲ�"n{�����}v�״{��{����n�꾥,���|�`��w����,i�y�h��ه�z��z�ť,L����[m���@$g�	���,���z����	
)��b��۹��?r��z��|>�}
��?��n��Ym���zhGd���zv�ݬ�H{��z�E$g�Cm���	ox�Km��Cm$g��Km��΂$go�Km7�-��Cm��Km)�Cm�$g���i����ܚ�Pҿ�Cm�Cm�!��o��Km���z��Cm����-:B�Ym��Km��z҇
�X�Km�E.|��Cm�Gd��Km��)��Cm���䧄Km��Cm��&3����R�Ym��N.��Km��}��.W�Cm�.|6�oݝI���x�Km��N.�Cm�E��Ă$:�.|��)#kF��Km(�#|��)�Cmp�ɣ�Kme a:�����xK4R���ŋ�zf2aA�oL��%�����I"[H��I"���&5��yg�l8�o����z��y��Km�E�>�:F�����Cm��q�耢�k�!'��Cm���x��z]I"��	ow�Cmg�)���;ߣgI"��o��)��Cm:�o��Odi�m*/�&��Km��Cm�!!�Cm��m*��Ym�<�Ӑ�ܾ�y�،m*���
�̣�����E�&�|��Q?i�m*����q�
��m/��%��!'�#�	�jQ��_��E9<2a���^4�h�n�m*	�>��?5��y3�j��}�%�E�?�M9��
�{���W��?i�m*c..|�����)9��m}���y�)��E9@�o��g!�E�?����v��a�!'��|���aŁm*|����m*���i���f��k���a��}�����&�E�&9�W}9�;�<�����s}s�&*�!'���Q���ap������dP��E�z��&o�n�E�>,�m*6�M9��m*�E�>bt�€���M9��`]�b،m*�!'N9
o��Q?��M9��̣
�����M93�YG��m*�����}���%gGm<�Xd��ι1D.Om�EGm�	Ѣw�}�E�zy�j�#�|yOm�EOm�|׍�-����E�zD]m߁!'��}\�`#v蚀������z�EGm����)grC�L]m(Ca�E�z�EOm�E
o���ÿ�B)Q�U�'D�E�?l>�E�z�EOm3
o:��EGmq'}�Om3�'D��;g���`�E}v��o�EGm��}�'D�o��.�KvւYҤ�)gx�ָ�b��'D��ֈOmz�)g��?F����|�EE��Yd�
oe�Kv�)��,�'D<�n��|+�,�'DM-9)�<�?���+�z���΋ۦ��'D7&OmF�n:Gm(��=�z��'D;Om�EGm��,�Ŭ,��,��΍	���gP�E
o&�<�-�,��,���FGm� g5�;g#C�|�R��,�7GmQZ�X�i��i!|Z�Ƌy񂾣F�,�2�>���F�>t�)gk���F�|ه<�"۴)���F�|!�g�<�|.��`��g��NdF���
y*�9	���|�)gY��zh����*ƭ>5���ބ2�	�'Dy���ڪ�>j�|d�������n���
F�>d��>>�nՂM"Ҡ�&��|��_9��bFo����bo��&
F�>P�fn��>���F�iF�>F�`���>F�,&gm�|\���Fde�
�|j�n3�)��|���%���	<�|Q����a9�S���&A�U?��Ҁ�?ϋ��?	�U�M��ۮ�v~Q����,�Nd������,{ۦFR9����4I+�.�>�
\d�51>�!uS
�>k��,:��b$���`��x�,�>F\dQ�iFNd�Jm�Jm�de%}	Nd	�ZdF)Ǝ?�Jm{ۦO�Jm�1�>��|O*�jXy���*||z�\d��+D# �j(�*|F-g��>�Jm���刭>⁧����o�a:��xW�|.�Zd;B-gg�`#�JmF�|���x��-�4�ƺxl��{U�*|_���R��j�Jm F�i!�)�)"F��@�Jm���9k��-���$�i���j�-�Jm�*|s�a9���ׁ#�)$F)����8-g%�)&F�`'�{e��zȀׁ���|��%�	de:�)(F-g��Od5���f�-g��V�-b��x�Yd)�)m�|*F�(A���+F\d,F������}��j��&����(�գ=��-Ǝ?���9U�)s��9��?��?��y���o��j5N��Jm=Q�L��i���z������|��Jm���i��i��j.�L9��zX�wU���z�-}�H���y{ۦ��̣*��|{	fj�L9/F�-;���0F-g����)u���)*�-l�}�|,�գ�Q�}��n������z1�)��}�֪�J��G����2Ʀ>(�+n:�4i���3��j��X?�be~�?3�)Q8�G��"�X?���{f2ad���M+�6�4�L9O�)��|e.��t� ���a��DŽ�ho�I"���h�be5��h��	o6��&����	ol��hg���5)7��h��$D�ۦ�?��	o�n�+%ۦ=�{8FѢ$���5��i9��z:F	iO��Y?	�8*��Lآ%��;F
)���i���|r�<w(_�Z���+<��h���i=��&b�)��>��z>�]mƩ}�Am?FNm���-g	f@��=3Nm�آ��}=��X����_�v�AFNmB��hC�	o��}DF
)EFNmFFNm��<���ʢG��hH�}Y��h�&���<�%�����]��hh�O�d��IF��.|Nm����?��`cBNmq�ݴ�-�	8Dq:�-a
)JFNm5<u�Nm���h�U2H�U�KF��#6�-Nm�e<��hLF�%��M+M�.DNFNm��	o���h{ۦc��h)�	oȀ��O�F	
)	����DNm���v��h��v��YPƿ>�	o���Nmց�ܽ��)�	ot�Am�^��ɶQ��i�	iƁZRF
)���i��g�6Wg�{~S�E�݁$g���,{��aTƩjUF�7Î/(����	ބ�S�D�&W�V�[mI6E����i>	i��zL:�%�{�WF�%,�{~_����.|����	�z�����zc&D���A�.|/�z�
o�գ������'�Ơ��Ё��XFoYF�zZ�{~7��i[F�-\��ik�J��PՑ�D�z\?CE�-��z���]F\?“8D|���h�z^Ʀm��-�
o��-k�i#ȃ�Nb��ۦ+�j��z���$�8|�+���!'��zآ����w�|��Sc��-Ёʢ���b�g��7��j�o��6a��ֹ��-_��i%ۦ��(u��N��
آ�8��d�h`F�|ɀ�iaF
oo�崘�h@�o
����h��{~bF
o���-^Em98)�^kj��nJN���N֜!o�EmcF�zj�[m	id�[m@9�z$�iee�`3�N����H��efF�zg�`́?3	�Ⳛ�`_�fԚo�������h�`>��ni�YmI��	�Ym��N��-����7���
����-j�Ym���kFV\lF�-��j���]�Ym�
omFUd?�-���:�&�@+n�Ym��J�
�97�k�noF�&�`��n��hN�YmpF�&q�Ym���o���&���-��Ym(���r�Ym_��-���`sF
o���c@�|��(uh�z��|tF�&��;��Ym���yyM-��b��&�Xo�բ���.��&��<nu�Ym��&3�xȃ{��)���Ё��!��(,�&�		i��&8
oiEm�܉S��.vFEm�M-�4
owF�&9��SAEm���d�������H��.�j�ox�YmyFme���`zF�&��`.�&{�Ym�<�Ck�e��b��Fb�
}|�`��JLآ)me���;}F�jA��[��$g�(sme��<D	�i~�Ym���J^�c��ie�׫�&�L�a��nF�&�/g�M-��i=�G�7�>a�|�F�iR�z��jaY�d%�`#�F�iB�&�m.�&���b�:w�آo��n�n//��k�f�F�?���I�[�5��y���X�?��c��)Q�Ś�r�<�|	��n��)�2�J����ݪ+��x$��'.����)���&/:�?�'�|����8
���F�?e��i{��&�Fd~l��<+���;*�?�F�i_�Ƀ�n���z=�e���Z�o��n.���4A�?Ä�n$�<DS
�ij7�?ʵ<DՂ)����i�(�i�F�֫��7�آ��-���a&i1�F�?�Ƅ-�me=��@�ͣ��i�#)�Fme�����b�c뀄-A}~���hԆ�-���F)9�����h��|�! ��iO]m���h���-�C]m�F}~�k�F�?�9)�/Um����F]m%��zG}~S)�)ʔF]m�Ƅ-ڪ�z��p�$]mj�v�`%��z�F)�|��}<�|�F�?�F)�FUm>��zH��y�F]m����if=3�ޛF�?�f�FUm'	}~�@)�F�`�F�?��ݟF]m��zo��Fo��z��h�z�}~�ƚ���h�F]m�F]mh-Um��c	��z�B 
�{Um�}~��zR]m�Х+�|B�$gy�
Um���Ą�i�Ff��<y]m���z��Cm��ac��}��!����`%����#s���})[��ݥ�`#���|�/)C]m�����t��{�.)�]m�FUm(�:DY]m�頹p)V����}~�%Um'U]��z�E���z�-g��z���̼��F}~ZUmŐͣ���]m�����z����F]m�F�%=p���D�'�����z� �%ĉ�z���|�&���$�%��b�F�%��|}z��~8��e�vi*ւ�|��b*��f6�n��E�����_9��RЂx�́�v}��d���j����_����À��$�|������-l_"fD�n
	�%+���߄_9��(��6�̌?��Ffؕ�n�F�i���e��x��ih-`1�F�xԢQ�|o�{��z�~8�`1���`1�/�F�i��?+ck�F�dl��>:�x�8�>�C�	��u�F8D����/�F�x ��b���n.�C��	i��xՀ�8�Ъ���|���`�F8D��Z���%j��e��^�F\d*�sU+�-g+\d
ޣm�%�4@�F	i�����Xm�F�%��>)��՟��|ɀ�`�F�|c4��B8D삜?�F�zX�=杀b��"g)���8D��Xm�"8D���y����z��i�`1��}Ă�b�F�z_$8D���`ϊ�nlGm�M-��nz��b��Xmńͣ��`l��y��Xm�F�-a�j��b�ƭ>��Xm�ekw��|�)��x���nz��N�Xm��N.�F�x	Gm�Xm֙t��+�#�����d���n��Xm2}e���oe��d������Cdw���F�z��b�>D�ƀb�F8D���b�F�bo��.�/W��b���n+��`��d��w�("U#Z�?c���bĉ�`�FZ9G�&���O���F�b׀f��C��ƀb>D���2�����Xm������i�Xm�Cd��|b�zB��ի恫�����*�n�z��Xmr�բ��jȀ��o�x���j�F�b�o��Xm�����:�-<Z9��ͣ�$j�|n�z�7�bxآl���)y2o9�;dބ;ܖ��6�e�=���U-'�k��5⣩�/	�-:)������n΁����Fo���Yl��?AI+�ۡ�-�F�b(�9nQ%���Z9�F�b<�|ĉ�n���=��%�|��%�I+!��竀ۧ)�F�&�^kh�;g��b��U٠���F�b�
ݪ�+��>�Cd��F�FI+�F�&��N�95�b'�(�ƅ$�F�bx��|����N���o*�r:竢�%쁿>��|s��{��	fh��F�{Ghڏ$g�o6�������F)����&�F�n��iV)o���"��v�J%����F�b����F))�	fȀ��L��iI����|҇�8m���F\m�F)(��y(�i� )ow����(�F\mR�-gR)�FCd�F)�ƿ>|���,�iH)�F\m��be@
 Q�3��e���%��i�F)|����&�0�X��oz�&[�3T�%
\m��n��i�F)��/{�i#�F�i�.)��o~A�z�\m>آj1\m���+hM-�F�z�&�F\m۽�F\m*D�����{o\m%<|�ڥ��o�o=n<|�F��	f�ƭ%�F)gm~�JmM)a��%y\m��	f�.���%�)�ƭ%�	f�m~&�)	]��o�+)����]��[���m~�@�bX�6R�o	ԢX��F)�F]�&`�F\m�F�������n���F)�
���i%�F]���&\m��&�F7�G\m�X��)�ie+\m�m~b��|ރ6\G\ma�%�{Bǧ�:$g
������֣I����o(!��́Gm�Jm�$g]feb��b���_���J�&�O�<D���8�Afe�)l$g����^i�o��L%$g�W�A���/�]�X������D������������^9K�m~�}�}Yx�=��o�+:���m~�-����}Gfe>�^9���?G$g���G���R$g�b~	�}{��-�ݷ����|s��z��i�b~��^9V?�lp��
�}�^9h����~�uI���i��-��{�$
fw��	Njb
�}���_M��-G{`�t؎�E�)�

;y��`Njb�j!�i%�����n%$g��{���C��&�69G
f%$g-�r��b��&��	o��&��,k��n��)+$g��ͣ��m%$g>´RB���Am#��$�Am7�l��8�)���b�}ǚ��^9�o��}‹�(մ�?����j�ډ���&R�θR${���&NjbK����)�Am9W���&_���}G��~�b��`��b�	��}ŀ���&G
f �Am!�)"�}#��&��{$��&2��|�W�
´%)%�)���|�´ʈ�����&j�;�'
f��/�%Nm���`��y�I"&�)X!�a'G
f���n���
f(��`t�&�5�����2n���)�Am*��`+��&��ǩӫ�b:i17�)�(|џ�&<�&,Njbo�آ�>(�4S���>���
d�� -�)��2�0�ǘӃ۾ڥ��jD�	oW�c��+.��&�=gӐ�&�i��.D��Am/Gۼ��j��aւ�$9�v��=g�g��D�ͣ����i���j��)����0G�>�<u9{������*
��1��&2ǻj��zOʈ���g�c���&#�%����%��iց��	�zR���/�F�ǝFalj�W�c��&p��j�)��)��z��&���m�֣f����芈�i3��&��$4��i5�)H6�j6��&�}�����
�%��&7�ۧ�ͣ8G)�ib�zx�J9G}:ǟ-D���-�z;G�%�-\'�F�(9��$��Bvj#���i��>�����Bv�C�{���$��ݺ�D�g�g� �c>�:u)��i�DPK�<GZd���i_����cϛ-g
o�	�cW@���{C�(P�q���c�
o=�bw��Bv�!�b���7>GEm���-W��ia��j?G)@�)֠��ݱ�Bv��&AG�z�:u1�۔���%5���B�Bv��{a��kCG�b;�%�$
oj�:M-k2���nD�)g'�o2E�keCټ�ek�9�zp
oS�keF�)gG�ce�(�$��7 :C�%H�ce�Cd�
��I�ce�%}�DJ�ke����ceKG
oL�)g�h*�bwEmؕ(u�!�b[$
	��g�M"����M"?��=�(�NdM�ke��c����N�ceQ��b(�Cd��<�֣>�F+��2O�keE�ce�Cd_�)gPG�bQ�ce��)g�Cd�:uz
�bl}R�:uS�ce%NdĚce�$�bTǂb[
oUǂb� VG�`:-Dπ���1D�F)� .Emc�Nd��)g�(|G��bW�cez��c杂b,����)Xǂb�)gYG���}��)gZǨcJ�(�[�[�ke�|������iZ��\�)g���o=���
�kev��c�F+]�Cde�)g��ce��d��)g��^�ke�
@+_�ce�ob�bTF��Cd`�
}y�i���U��%�cec�Cdaǭ>,��cbG�%Ɓ�Mho���<cǂbh�����>}�b�
���o��m1Q����ydGo�Âb�
`���9S�邉yeG�__��Ȁ��H�%���yA��⬊F�fǭ>gǂb\#hG;giG�-��ij��{ݢ�b�F+��d�j�,��>3��=�
�i�&�������4���T]Ƀm1_���+��cd�H��
��5S6��nf��h
��ck�h��a�	Fv)������d��5	ߣ���w�À�a�
�ǟ
�OB��ބ�l�HmmG�%���5Cd�4����i���~���A
�M*�d��)���h�(������>�0�%��n�)�o��ԣ����"-g�%G�Ё��E�W�������|>�ո����oGoe�
�`pGoe@�Zmߝy�9����Q̿�>+��aq��z oeS�i���zk�#/rGoesG�iG��z�	��t�Hm.o	��zuGoe�}e��`��hJ��J�f���D��£��zOg��
g�vGoew�z8�i��z#$oe€�+�	C���`z
oe��z/�-5������z��Cm��b&��X��+b��x�zA�
Ny�zeB����<{����`z��z������{�z���* 3����|�Cmթ�z}��z��W�<7f[�hȉ�����4�-gG��z���a��~�z��+��`€�H~����c8=���~���>��nz�-gG���{�jՀ��oeЂ�㘁�aGoe�����bA
��!���ۣk�c�ge��֣27-g��z�GU]��z�Ĉ��/Xۃ�zp��V�Goe�I"�Goe5��ֹ�?�%d��{z��z���/�i#{������φ��K��z�G�&��E�D���i#�&���z�G�e��-�G�&���-'��+d���'ۊG�&�&�����8�y�I"v�(�i#5�&x%�y����W��d	۶���<�&��3�oe�GI"�G�&�G$g5��y3u>��Amo��h��Ёy��
��4�m���������Am
�����	��}�&�6f�f��%��Amz��y$g����O�AmS�a�G�h���m�Qks�Am̐�ck�m*��Am⁦�@���b��|��a��-n������ܤ��%ĬAm{���g^m��Am�����k
�y���f
��9I"���� �G�aõ�`�Gە�Am�Gi#%�i9�3�ܵAm:£��H�6�.�&�i#{�a4�������p��,�a.�"g�G�zs��䳅�o�+���Ǩcd�ցV�ۗ>�&{�z���k��Am��f���5����G�z#�����6��%�M���8D���`��Amw�9熁Jd����ǭ%���ܝGUd��Am��Am�	o���`�	 �G�z�'z��%
�%Ҁ�c�Jd��%	�Xm��Amu�t�%��h��Am�ǭ%Z���c�G}~��AmO�����ݜg!xg��Kv�;g�G�a_��i3�.|��d�.�a���i��k�Gf:�z #v%����G��(ߣm}m�Qk��
���`y���O�*X���۾w�J�Β1�Xk���,�"g{�����ւ�-:�Xm�,ߣ�����cɚF2b���8��`}�'!��j���:��D���a(�����a�G��;���.�"g��&���{~?�����a���iƁ��;�Xm��%|���ǔ4(���Ms5@��G�zȃ۷�GGmc�(u�7'��-xd�JU~	�X���ai��+��(u���iN��ݩ�
z}�'!�G�%+�<{�Qk�o���ݱG���(�A���G
o��&r���2�ír�q��Em�GUd�G))EmQ+£��۵G
o݀(uw��b��\d��{�Ǚb�(|��Xkrsc	�&
o���d��^��z�3��&�Ǚbk�/J�N�ӣ�GEm�)g�GEm��>�)gg�(uȀ�7	*����&�Em�)g��m#���&���&��%C�	f)���d7�7�bB�ǣ��yb��%@<�ϋ-no�)g8{�'
�����&(�䣼G
o�GEm��&񟯿%5���a]�&���4Em��=���aCߣ�G\m�G�&���a磊u5�(u�����&G�����4���m�j��&G��Y����8��ab��%�����vf��	f�G).��%�,\m�EmX����j�G�`Ɓ7����&�Em���y	X��Xd���+Ane�GVk�G
o�GEm�)^\mc��\m��)g�G)�b�Ǚb"NdȤu:\���y�o�)Emne��`��b/\m���A\m���&����Gne�q�/�ۮ�͸���bЁ���	f�f���G�b��z��)g����.��%�G�b��f,ne��%c�f���^A��&��%��G9(f�m{�*�ւ�߀Qѻ����G9��&	�fိ%��o��}�G_���O�p��G\m���a��}\�x�
&��V��G�d�G�&t����G93��a⁚ⶐ}t�F�������	�f��}cNd$\m��G9j�
����39\m�ne��%Q����d��߽
�5��a�C������>������a֗���G9���ܼ��zܛ��d́�%/5ߣڵ�in��z�D�%�������}���i���Î:'(��)���zr-7�}e�=u��Xd	��i�G�a��Hm	�%£��}�G�b�G9��z�G�{�D�����G9��0���a����G99�ל����bȀ�:��+�)���G�$-g��G9�G�b���aB
f�����G9��Hm����*
f
�uI��}�8��(PD
f�
ߣ��}�G�b:�Hm�{�
f���z���E����
f��)裟!�-���Z����Hm���h��{��}S�a��h��ʌ<��	�)o����)�b����]k�
fl�`&�$|f��z��.�G�%���z�)]kR��z��t�J@�����Is��_+���hb�)����Y��i��C�Ђ����z �Hm~��i��)�"�b�7���i��Hm�
o��Xk���h��Hm���b���hԀ�(?�b��(�Hm!7�b5ʌ:�X$���+��#�`#�
f�b��)$�Hm��c�-g��;F�b��G�Ȫ�G�b���J�_SNm�=
fa]kƁ7�"@\dp�虆f#�n${��h��G�a�G
fi�)Ԁʌ�
f�o>��a%��U2�G��߁)<�(:7�|��י�@d7��h��i�	�i!���h{���)�����h…�
�G�{�zw���h :�{�v�\dK����ljy���G�bH�aH�{�zw��@9�aȒ��e�P�!D[�xNĽ�dȗ��b�a�gՇ�i�B����{���j��jH�aH�aS��j�-|d�Ё�%�aH�z.��
!DH�zJ�'k��j�}H�z��z�&��o�z�th��Ђ�H�ze�i��jȂbcF�z	H�>
W�
Ȃb��@]k-:�n���H�zH�>,�8�f���{��<��J{�Oʰ��a҇�8�!�9�z
H�z%�⫀����w�e�n$H�a��zH�z�&�>{��*�A(�{��♂�%;�*u�ɻS}���o�ۢr�aH�z�������"i���k�H�a�����zH�{���Z�zH�>����}�"�j�!DX)a5}�#	�h�	�{��:;g�)iB�hdג嚕�H�h����}eH}e=����:u��<{�Fd��8��܀H�zH�z���Xt nH�h8�m�8�/�zh��&ue�Ƹ�5�b"��/Ӑ8�H�>�1!��+��]dJ��e��>��H�z*�/�!DB@�>{)}e˜;gD�iw��
K�>����HC�H�hH}eH�>���Vk�!<H�>��9{� H}e��!ȁ+耾c?	��"H}e�;g����x�(oUdЁ�-8?��C�ڻ)��+'��H���9;T�g�}e{�CC�,$n�
}e�C����G)��+8�Qk�$n�����+>�ir��J#H�=��`��O��j��j$Ho��%�G������a
�u�g
=��+�B��%H�:?D���c&�
}+�hF���������jvg5���'H�yi��헆��#(H}e�Vkh��|#o��k��c� g����W��c	�������)H���T h���b݀�c����b��j�y*ȁ+����+�iwB�6�=l���h��_�e<T-<7�yh��P�'W���y)��+,H�=$'=�Āh��b���h��c(��+"��>h�۹��c���.�y$'ܣ-ȿ>s���
,{�"
�g�8�$�������.H�a/��<�osfoI��
)���煛y0H�`we|v��1H�����k��5�ۧ�+�����&}b���׉�8Ҁ8�.��y�%D2H�`�"�a=���tib�`���u��<�3H)�]kc�/Ԋ�c���]�4H�a��̚�y5��h\�)��yY��c��`w�'�%��c6��h��/�B��y��f�ۻ��h7H�a8H;����c!�&9�Zm��ʌ(�:H�yd�Xd�Ǝ��h݀)m)e��h�Zm�F�=�
u�ؾ�v�`ۃ���h��b�)Ȏ�h;�)�7�?����A؈)t�?��8���8��x��'�<H�%=�Zme��x�h닱Y>�z?�)u������@�)[��hk��z���z��`���N�?ʁ�)AȡxF�Zm��aBH�`��C��h���)DH�`���z�)��?ρE����x�Fg��i툄|EH�޾�V�FH�`A�-|�!�&������i���z��yރz}��X�8G��h���ik��jHHUmI��z�"aʈg���{���z�Zm���h�$g�����,������o�J���x6��hw�f�J��z�Yv�)ՀJ���xp��,�%<KH�-"��>m{�LH}�0���q�L
������`k�XdMH 3��?쁥,�;n�D���c����	}�9A�g���� 3�XkW�cNH�-��aOH�%g�-l�%5���#U]Ԁ�.y�-�}P��zQH�%�i#R��z�x=��x
$g��)�%��ݘdf��_B/����ii�xЁ��Ё��S��i��(|T��z��c��q��f%@��&2ĭ��uIg��b��v�ԙ<�u�U�/Ђ�a7��&g��%�E�j�쀃��Yv�oBi#j��a����j��C:�-$��o��	o��d���-(�Q7��o
�-VHoA
"fWH$gX�m�o{�Xȃk�$g��cQ��Y�AmZȃk��h&)|ձ�k[HAd�co�|���	o�����ƒk��a�o�!�j��<{M!�P��j\ȃk]H޴^H^m�/�,C��/���#�T]���>���k(��m�f���>��|�_ȃk�]k`�	o��k�
�aa����y�obH\dcH�ja��}dHo�y�/��a�s�x�(eȃk�i#�
�^���b�'> m|eA�y��z�"\dfH�z�"g��k(����o{'�gH�z�k&�$|fW?���'�'���zhH�>��	o�E�i�\d���iH�z<����	o	���l�۸O=d]vjȃkkH�zە�lH�jڋ���ʌ������km��a΂	o�)ne��e�zn�"g���oH�z���m�az�Xkp�|� ��?5��kz�>qȃk��%rȥd��ߒ_���%sH�>g��?t�{D���u��a���?v�{wH U]vxH\d�޴*�1�$�z���X�6�y�|�z�{���i{ȭ%|H�>�9\d@]k}�{��ә�|e]vL��{�\da��?���~�{�	G�=�
���a���?��{ޟ�Ɯ��`���%�5Ӛ�����z&1�z���ր��{��{�H�zԀ���ȅ?��v��#��z�~1$�z����…?S
�>���{���z�Xm�H�z�H�z��{*(�>��:u�:u���=i�	]v�H�>�H�{�ۤ��z�:u]�>���׈�b�]v֏�a�z�Fg����:u��>"D:����{�H
o�:u��:
o����a�*z�:u���6]kb��{'=Wm�.
o��CӍȭ%	�׎H-n5��+a��{��{�ȭ%�:u�Em��0v�#���
�̢���h��{�
o�D)��{Q����
o��:uX�v����h�HEm��c�+�壥��ho	�����h	?'�	�ú�o��f\��)��c��3��&��ha����h@�����_"�ȧ,�:u�(�
o�Hx�+�*'8�,?'�+X9���h�Z�5��$���C�9���>D�|�z\ml�8��X9��`��:u�HX9�`*�X9y�8ؚCdcX9��:u=
-n5���:u����ڋ:���He��x���^\m�HX9;��x��)�*'*��o�*'s�NЁת��J���h�*'�H\m*�{l���h���*)�Œ�H)�H�a����H-n���h�HEmXWm��m#���ho��(���Y+����r�a��;����o�H�a����A�9�ȧ,�c�?����a�H�{9��,{��e+a@��h��{S�|����jȀ���H��9��
�H�a�ȧ,�HX9Y�f��o��av��y�A,e�a��8��{$�|����j��|���{��H�
�|���y����}�H�����9��*'>�|��zoʣb�/���%�*'���#]�i�"�i�ȩjn��%��a��a*
��雨x]kΔ|���(�H�>�z���%�(k�|�V\m���߇�����d���j��j��mꏫ���}�ƾ;�+��Ϛ�y+�����s���ܷH�i�?�zЁ�b��>Ă�>��	��?�����ȿ%Q�3�b��%���W�٢���2�?g��L�;
�	}dw���Pp���a�z��?�g���H�a���.�{��Hm��%���%)����Hm�����a��Zm�H�{�ȁy��Zm��8؏�{��y!d�-g�h��B
�e�H�z5&���Zm��������HmM��
�|�R��k��-{�Hm������Zm��|���E���ž��_Ą|��qc�H�-��Hmw��/��i��-��Zm���/i�-�]k:�-�����?ބ�����/Z��<�H)3l�?:�I���]-Km��("�h������/���T��/Y��a* �-?�8�x����/���H�-�H-g4���|����Zm����%�Hml�M+�o�2�k����/v�-�������Zmր*���h�H$g=�4K��Zm���9s���Zm=��0a��Hm��8�h�f#��Zm�H$g��Hm����T�Q3��x��ZmH)$g��Zm���?��L�m���Ȍ?y�Zm�����-v��(���i�qcP)lNm��}o�+�H�-�H-g<��{��/5�̀.-g��-(�����x�Ā��>��-�Q���}ם��k���l$gɀ�i���/���{h�M+���/q��?ʵ�m�}m�qc�����/À����/���{Ҡ�?�����/�k�-r=���/��6�����țyA��?S�y���{�F	�	x��&ؾ�_�/aR�Ұ8�mՔӢ���nQ�z�w�p�o��o�~����H$ǵ�{��Y���Am��/a%��h<u�H$g�|�F��[�Cd��Am��a�ǻjscMXqY�����=���(�AmS�m�����a�H)ޝAm0a�3�]D�%+a	)���'�	HĠ���u	=.�������ꖨ��i0a��}r�J�Ȍ?��o[>�j���{���?脼c��{��m�H�ar�/����Ё|�b�,!5�a�c9�3���T�	���k+^mÀ��o%*^m(�H���oƪ���������yz�d>���xh�^��2Q��;����|�j�/a�H'D �%���y�"��C9
��5�������Am�H^m����!D��Am��]�C90a��h�>!� n}�9���f���(o��o��Am��1'v��?J΀J�	�z��z���id�
2�a�)�H�zo����z�G)����䅋-��Amd���i���iň�a@�/a��8�Am>�����3gƁ�-���1'9��t����6��-��mq�
�ȯ>=�ȫ��(=vA�H^m�0aK6�_��|����i����+��FJ���Xvco��<�H^mˆ-3������D�{��9o�|�b�=摏-(���vc���-���-��-�$n� �w�|��ȇ-S�|�\��-�@+���-�H�i�������C��-c=�}�ڀS��-(�1'�}m�f�}��>��f���-�HC9������i����|�vc���ȇ-X���-:2���7}F��+�����������jvcA�-|��U2�vc�'�f�}���+���z�����iI}Z��p��i�Em�f�=��i�Em)�q�ɇ-�i�i*��xL���r��yIoJ��n@�(IEmɇ-�F���kt�i��e_�f���ݨvc�N�ɉk	ɇ-
IEmɉk�*A��+L��.D#�޺vc�f�
I�����)gIoɉk:�$��m
�z�Emy}o��vc���R}?�� Io݀�y���(?���ɉkyD�"}���k%vc��k�fJ��A�%D)����P� ���-�D�z>�&��� �=��(9'��j�-f~e�%DI�z5�煀]dÃ)����a��c�)l�����Ϻ�)�����Em�$���ae)1�-��z�)v�������)�o09vcg�bxEm�xc:�)?Em�5'j��k �z�vcI�zɉk]Ę�BEmɉk��cR�)YEm���k)��I�i�z�)뀉kz�i�掀)�����k���+ه�b_��cI=�)(���	���W�)gI�i��� �z;��i\�?!I��5'�z"�z��za��z#�G9⁧���=$�z�ia�/߂ae<�f%I�=&�z��G9$g��i�g'�xc;�Y�V�ƾ���(I�ie:�=)I�-:7�3$�?���a*�G9+I��TG~e���i,�z�%��-I�>��)�mw���zD���.�)��F+���Z4���d2��ĉ�z�/C �zw�)�z/I�z0�z:����G9g�)��������z^�v!������i��G9I�z��<� �	 3��i1ɺc��e���F	�z���a�0aԂ|���i�5'a�Uܴ�{R�Hm��^��z2I�i��Hm��>vc3�G9ԜҰ>"�>4�zy�?�HmݢG95�z�8vca��a@��z6Ivcd����7�Hm�����8�G9�֢��k9I��a���	�u0:�z*�;�z<�G9ȏE�:��=�G9��k��=���F�Hmʎ琎!!#�����6�
[>Ivcv��O�k?I�k@I-g7� ��y%AI�k*�-g���:��+�ѪI6^��HmR�o�����9��tBə9�=���i�=��m��i��k�����)n���ƫ��5���p���5�������܀��Y�j�o5���
�,C�Hmd:�>���Xׂ�{�D�YE�Hm���9{�j�fѠ�zl����Hmʋ
�^f��˸Fə9����"�,1��?f�ku�4�݃�F4=�k	 i#-��+i�V �Hm��@����G�Hm�׀���+���'e�:|H�Hme�r�酹�$����fnvc"�h�b������	��"gy�"g�0a�	}c� ����k�0a��yII-gJI-gQ|b0ap�E)&�L�'[��S�KɎ-�xc��at���י���Km��zZ��,S�4o��3}c������`p�����%��Xmz}c��%ϯ	&��Y�ل��=3������a��g��E��xc��vLI�a��p�4��9���`�fkm�;']��z/0a����~6�{�0a���ʉ�%i��d�j�	׀gj���z����fo�}�)vcw�}.��%vcf��?h5f��>�{M��Ŗ��<X�6�NIft��%�@}cOI�x%��y��kP�"gQI�c2�#@;}ct�"g��xg�g1n���R�a&��?r�SI�>m0a#�'�b�4a��Ά�f��?v��=@���Ԃ��T��aUI+aVI�aȀ�ˎ�>g�m*�a�'S��a��=W��ad0a������-X�#uy�z'ۃ�<�}c���������D�#un0aY�he��?m�\�,DZI}cI<�xn�%[I�x5������\�>DTI8�f�oeo�1'vc�ia�%Lz��)���h��>��c]I)��G��
�><6)��98��-�>Ȁ�o�xc%Y���^I-n�>s��ar���U�,D_I�h.��%`�,D��#��<aI�-���䔿%�vcr������:�xbI)c�he�}d�:u����)vc)eI�>�C�f�m#���a}cr���(�xd0a��;�-b)�>gI�>��-�
)�	vAv��hI�>��iiI�,ڡ�a�iF�,��#��-,0aa>|�0a�oj�{�%��8��k��a�.>D
���ko6)k�xc���?lI0aU�,D�+a���J�>D��,D��i��mI)$�h�5뀺x=)n�,D�>DH��jܒvfֱ>D��eOoI�i�>E-�io�p��-W#4@p��<��qI)rI�-sI-ntI�-uI\mt)vI�>��iwI�zxɺx��4�o9�>=�n/�iY-nyI�>�o��-���)�Q҂�JmzI�z���L�� �|R{I+a^��{����|I�z<�	��ir��ʇ�>ÃJm�8�=l��x}�;g
� u�ˣ
��(��z�0a@�	�oA�iY�U����d��	L��~�{+�?
�(���+/��{ɿ>���J�{��I�?�
�z���{<'���-��	f�梂�}j6}cbo��z��}�ɟ-_&���i<��+p�?))ޞ�-��}��
2ĉ8'5��>��}���x��%�*�
2��N9���t=܈}���i�����}��&���eo%='
�֢޺}�g��Ad��}����&��Q+�+�#�zz� �I�k��}쁿>r��f��o��kj���	�;D��-��Z�0&{��*}c���z��Jm��}J���Ԁ�A�1'>�(�i�/Q�;L��zh�/�1'��}�I�Ԏ�Zmo�=��Zm!��%��c����_�����Zm�I�kdݒ�%�ݒI�k��e�I QXm�����5��}~��Zm�I�?��^�vc��k�Tɕ�}���j�vc�
0a���I�{��}〿>�(r��0�׀��Ad���i�Ivc�}��ę��i��-��-��Zm��)Z�Ad��)��}& ��{��)@�}�1'���)]�)5%���i�I�&y�)gf���i��{��&�Ӛ�c�
c��I�d���i��&�I�i�I�iX�8���������#�)Ёxc���)߄�-�vc�f�I�k�
o���)x�m�INm�ʴ�)��`e|�摨I$g��)��μܰ�	� �vc��)��Zm�=a�I�i��/a�IDD'��<L���*�8��IDD5�kt��f����kkNm���n7������+�ݒ��ft�%�dȡ�cd'�mw� NmDD���{���?�{�IDD(�?a�iM�{��+�f��kc�%��^��b�p�3vc�HNm�f���i��)R��?DD���{�Ivc�Nm���?��)�DD%!
f���{r�d��)_���9�H3D 
�m �)=��o�ߣ��kK�״N��{%�i��ԣH}k}ct%���`�}��	������n広/aϋ�?�
�%[
�i���{W�	o�I=g��Am��k�
o�%㯳�+g�	o��b�I�i��	;��c�IoN���
DD��xc]�Amt%X��	�h�IF��qDD.)�hc�<��n�IDD2�F��Am�hc���?�INm�hc
�״J���0a++�Am�hc���{��m�I�hd���I�-�I�h���`L�Am���{�ɞ?t%�I�h�I�-��8D�;
f�I�-݁D|�):�+%y���{�hcI�Am��Am���{$y����&�ˣ�hcx�O%<'���&#�-i��`k�9:y�7�hoܣ�����I�i6
f�I�-o<'�I�-�����I�ih50a���&�I��k�Am�����&�I�i����Ivcm"Ѿ��I�>���"�i���`�	o�I�>S5�i��k�z�I�z;��)�?)�hD�iǃ����q����j���&l�<��%�?
��`X��&�C�h��`�i��Q��� n~�1�{��hc��?��`6�-e�(�^m����8�h�
hc�i*���3�=
hc�	���Y���&vIX�6���-�y��I�>XW �If�&DR��-�� S�\cS�yX��?�I�z��ō�&1��&�F"�?3,��?/�zt��`7�b	.u���&��C��ۈ,$n��ˣ��`���z�I�z�@+��b��{��ze�i���z��	�����I�z�
�{�7��y�I�z�I�i_�Ԥ�I�{p�b�I���I�i���ɚy@�&��
}�I�i�I�>a7�?/��
}w���~C�>a�!�G�4�?�I�`��	�]�և��z��`��$[�ɚyr<om�9������
}���i�&g��<��˃�`5�H
���'���C�͢xȍd{	����`��Hd8�)��+-D
o���cj��IoM��iҍѨ��i��l��<Phc�I�����y��.	�]�I��#�	�o�ۚ����B���i��<��o���)g�ۇ���܋��chD���c��Z���I�{��|X�@�Dw���ɉk�ɚy��g�;g�I�{�Ȁ�oܒe�`�sI�?�{�Em��'k�z��k�z�,�}#���k~e��`���y����\y��f��%��<��y�z��y��V ��)��c�}ck����)(a��ɣc�����
}\yӎ��c���m�B/g�ɣce�����)�ɣc��)(a�z�xc�"����}E
o�i*�I
o_�§�ih�)��)�
����z���cO25@�)	��i�hc��A��)�oW��-�)j�Y�I}c��f���i���ė�`��aYUm�I�`����ly�Z�4S��a�܀����) ��c%}c�)gA�)�^���`���Ɉ�o�BJ�a�xc=�T�ʣc85'�G?;�<�ܒp��)���{����	�9��-ʣcJy�3�}�
g7�Cm:)>�zJfŋ<|�)�`�}co	�U� 3�5'wf�	�dd������o�H	J^�;�)۷���J�}��)��}
J�y2FkV0�d{�)��}f}c��-F��}�)��)>9}	�4G׀����%�f�J�&l�
f�}ƫ)�xc@�T]�^�m��$	:D5���
�Cm�$��w�)���`���-\�}��aܒ���}�<���a<'A��?A�Hm<�Zm��a�}��k���=U]G�=a��ʀ��n؅Zmy}��z�<'b��ag�Zmr�����c�Zmm{���R�}�Zm�}���6�Ő� ��&��Zm�)p�}�{�
�G���1玀�%�ĭ�}o{��Zm�}Jo)��?�Zm��HmJoy}p�}J�kN�i�i��}@�k����e-g�p�� -g��ke���Zm��Hm\y�F$)S�c!���y�J}Jo��km<' J}#7�{K��*�{�?��}=�!J:D�o�JlB�*��b(�U"�Zm#�E9Z���}$J}c�
��Zm%�ZmRo�o*�ϴZ�Zm�$W�ĉ*����&�Zm����͓�a��E9'�ZmU�k}�f#��H%�f#R�i(J�k�i#<'�Zm偵�)J)*ʂk;��c+�Zmr�i<+o�F),J�ik��Q��Ջ��[-Jo�!�i�o��k��Xm[��`Ҁf#=�c.J)���%�f#/J-g�!!�{�tAd�o7Mv0ʠc1Jo2J�i��ko{�l�f#}c�棑{��hc��b�Xm��c����+3ʀb4ʭ*���3�+�f#Ղ�`5u��f#���k�}5�z{��m�IGm��c�o6�"g"�E97J�i�5u#���Gm�"g߁�c�ɣ��z8ʧ=���>W�����t�X�zN+�f#9J5u=�"g}c:�z�o���;J�i�Gm�kkj��'Jo%�f#<�	f��=�AmȀ�L�}c�5u:�f#�hc���c5ݥ9��>�	f ��`�hc?	���B�&%�f#?ʂk;�f#@�zAJ5uBʸ��6lCJ^mDʠc��Am�^m��b}7�a�;T��a�b�j��G?���>EJ�aۃ���Ӛd�����0o�G����X��FJ^m�
�+,�<�GJ^m��nHJ'D����I�AmX�{JJ^mク�KJ^m����5uL�zeMJ�<�Gm��~N�zeX�;Gm}���|5��zeY��>��b
ǘO�>D��&=��8)�)��X���šAm�AmPJ5u�I+��zew�{��+GmA�Y��Am����,f���mx��z
��Y)��&��6�Qʿ>�&�a$y��f��&����R�G�I+���p���$nSJ�aoɣT�Am�)���"�>DU�)
�1&�VJ��WJ�a�#ZGXJ:�
fW�iwY�)�G�Z�)��ʀ�)[ʅ-�ft�c���^m�2�c{<'r��-"@^mQ�dDM�1\J�c]J&g^���ze	�Ca��c5��+��ڪ�洇������V� D_J�c֔4�\�<��U�۹�b3��`Jvcr���a�>D.I+b��֥���cJI+��腺t����3�>Dd�ze����y�K@�cw����&e�>D<�f���c�c�ִ9�W�f�}b	)
�<g�)�f%y�Y,)h�)i��:y��)��}j� ��a(�@vs��J5���"�}h-\mG�̣��ʀRyӲ� �iwj��ݲ���+�� @�}>�)��%m�m#4EmȀ��k��'�W?��l�)Ԋ +�iw>��-�
�c���-mJEm��=a/��-���n�ە�f߁�-��t�o��i4
fp�}qJ�c�CJ��i{��%���2�@���r�ae-#`C9#�5=X�:̎
�sʩjte8r7�\m2������\yӇ�ae����avcYW1I���Adc�}tJ
)^�m� nuJ�&&Em��	o��� ��a%��vJ�&
\mw�ae
�(��ք}xJ�&T��ayJ~e�dh�㑪�8uw�����=E
)��t,ؚ�sz�}d�@v�&��i�"~eބ�ǩKmt�L9�z��c1�fщ��Jm`3�?3��s���?��S}Ɓ�4{J~e|J)��5&�&�4�"ê�#�JT��"#Em"$�����E
fD
)��&&
f.3Em�<�����}JEmg����������4~evcL���?�~eЁ�+��ae7����?a��{��9�
)W��i�2�P����o���y�~��aʿ%]�&��z�J�c.��w�ae��&�~e9�o�<�)+y�����i~׀��o��Y�J�&�� JB"�1�&�J
)�J)v���`c��&���{@�cv�Ѡ�k$	�&w :]�&{�>X��a.��:�J~eoɣ��{:��&8�'���r�L9Iu���aЁ�+��k\���5�����{u��Q�C����&€���Y�0DD…�*�H��c<u���j_�����>9�WJ���i�C�E�kfDD���i�<���ʋ�[�l&:À�&uDD�Q��3þ�{RV
DD��"n�<u��kȀӀk�"nc�'
�	�+h�_GX�$p�yDD R�Ё���0�}��Hm�0DD�JDD�ɣ�y�	�6Q�0�JNm�G��5�J<u���&m�}�JDDLDDd
o��aԀ�Aŋ�b|DDB��a�=gL
oӍ9��"n
��j����h���j�"ni��Y1�J�)�

o8��&́�c���b�hcV�|���J�y�ׁL
o-��c���i�;ܐʩc�ʉb��o���{c�"n�	��
o�JFkV
DD�ʩcrG
o[R�8
DD� +�JX��i����<�`ւ�����i�"ەʩc~�ݖJDD�DD���c.)j��)��cj	DDr�����\DD{���ʩcs�R�-#��i���cS��i�J)��G��ۙ�}@�<��b�ʂkЁ�R�J)��k��c��+g_��ԝ�Hm�F"��o�"n��o���c��)�)�}��"n�F"@ᣡJ)�)&��k=g�

o*������hm��-���k���{#
o�
5b���ʩcL
o��b��N�#D�ʩcQ�ל�A���-���{���iT����ۧʩc%��e�F"Q�>!��Ψ��{T��J�&�ʩc��9�ه�i������-.��a��{��F"���������Ϋ��{e}c����{�Fk�%X����{��g1J�2)����ʌ-�J�aq?)�� ����Ρ
 nM��?;��8�Gm,!yHރz]�a�긬�'�棗諀�����J)�����Ed���c����C��ʂk�
�fR�>!����
F�5������L��A���ma}c6�?3�ʂk@�{���{�����ܿ���95������-����)��δJ}׽�{��hc��i�o����M��{���������-2����������{���}(()}�X�����
�p{��m�a�J}���{��M"���9�7�i'�4��he��>��?3��&����
h�J}��>��+}c�J}�J)��l��,u��+ $)}�
�ԼJ}�J�cU���Hd�J)�J}Q�եƘ׎�+Q���Ba���?���J�cm�)D0)�)k��+��i�ʛk�Jo)���<�ʀ
��(��c��c�J�i���?�Jo9����J�c�ʄ?��c%��m��.��he0)5�����fJ�N���J�c�ʅ-�J�f�5�c5���-�ʀ'U/L}�J)��zބ�β.����'D�}��k)or�i7��-o����J}4��-}�c���k��;g犊)��Fa�J}�{�z�����kɅ-
	�,�G��ʅ-�J�z�� �ۮ.�z^)׎�+�pȀӀ�J�c"(�z�ʅ-�U��{�&��he�)>��+�")��)T�����Ժ�)�J)>��+e�Zd�ʅ-y���n\(��)��)�J�i�J���/�
�{��)���g$)D�ۡ��Y��c�k��)⁢c�o���9t��Jo�):w��Qґ��):��)���j�ɣc���)���c��i�d��k���-��)��U/6��[Fk�ʅ-5�����)=���'D��A�e��)���։��=�(5fs���J):�褀)�4r��%L�N���)j��&��'D��d��'D5��h��͑Cm(����'D݀�{T�$n[��>�Cm
��p�JfFk��Cm�oj$n�Jo�J�{o�T��J�`����:�)�J�`�J�{��
��p���2.�c��ʢc�J�&{�`��?���l�޸����7� 
�&Ã)ЊCm������)��Cm�J�`�J�&��Cm�T]�J�`k�+�J�`9�4� 3�J�i&�+�
h�Cm
=�J�`��Cm���L����Cm���mK"k�+$���o�+2f�oe-%-D��Cm�s��C����a�i#�J*D��`
�ɢ�J�`���z:D��ܫْ��
2�����&�*D�!!�C��J*D
�ZmЂ�>S�kJ���o�܀�+5�J%D�*D��i?�J�&���
!!{�%|���z���J�`�݀�JX�ߓ�>�F�k��Cm�x������@��Cm�J�`K�`�4!!���c�Cm���^��*|����Zmk2�W,�C�K�&o2�!!��W,�E9��S����.�Cd$*D�+sC�i#K�&;�aa�6��8
�ao�+���⅀H�K:�+����C��7$g!!k�+d�����E9�o��d���<I�򣝁�b��=�=���*�&@������f
f���ρ�׀�P������r$g&�E9�i-/�a	f�C��Zml$g���u��c�&+��N���ރ�S�ْ�>|�#`J�i�-�&w���J���&�"g���D���"�kKMv�p��fb�����Q�+���aKMv� ���Q����͇�a��E9m�"f��YI��Le�Xm�W,ڊEa��`�-n�W,o�+��E9T������d��^	�Xm
KGmm���)� �\�i�ʀ��`���k0�i嫀Ӏ���`)ʘ�aKGm
KfKGm��"g|�Xm��k��L���t�K$g��*A:��
:��(�.ǖS�#ud��Kfx��z��#up�a[Gm|:��#u��� :�Ð5={��#u��&!)KF�K)�#u��`	�������-:gGmŒj��zP��amېGmK)�	oH�:���`K)ѐ|��#u���K)�#u��c�-:�#uZ�p��j�x���K�c/�#u� :� ��c��%����|�
��Ă2@�.��z�B)��#uX���5=|�ּ��
� �6��'.�#u ˌ-!KGm"K)#K.|Ã#u�����������-�׉�[$�|��)=��LP�-:H��-:7��Gm@��-_:S��k$ˋk�^m\'����c%K�c�4״��k�	���н�hc&K)'�#u���(Ԋm#(K))K�c��|��@)P�	o*K)+�#u��z���̾�z1,K)��-K)'��(.�#uR�Х.D����9�'��zS�#u��.D/K)� �V/���v��&�c0K)�X�1K�c��.+�|����&�!�ch� ��b��$3��ޣ��?׀R҇�aR��-	���2K��k��8�a?�c��`z��ac�����3K��4�2;��-Ȁ�U"�d���H�4�l�.DId}�c��ޣ9�;��݀��y���|��a5K�c߂.D6�|���c�����7K�c8K)O��z������c���a��`׫"nH�c߂.D
+Jmw�=���9�}��.D�
�c�ٕ:K\mrޢy"�YȀ���z;�Jm(��4״oɴ<K)���;�Ɓל=�۾�}��'h����c>�}9�S?K
o@�"n��}��o�&gʇ�)%�z�����(R
o98����>9� � AK�c��a��jBK�z��}�ɢC�JmD��hEK�c���FK�i���h��aGK��ց
۸�}��'9����z�%2<�c�JmA״Q�cH�	f�&gr�zsE{~I��aJ�)g€ ����݀K�	fх�hL��h�1D�#D��M��h<@&g"
oZ��a:�|�N�)gb����e<ޣO��h(ۈ��aP��hb"�m�o���zg�|�������1D;�|��
��w�JmQK�a��(ۭ�	fo�|����z�~e��z�{R�)S�}T�i��͓:�g��iǔ).�}X���U�i.�Jm���^V�iW�)��ۅ��X�i#�|�2��J�Y��zo״�#Dk��Z��h��i�ۈ���)Z����e:-n'u[�)����	f���h��i\�^9�=]��h*�ۘ����U����
�%+:|���#��zi���۹#D��i
�I�*X�*���@��h���a^�i׿)g_�	f`�)��{aK�ab�	fV��6X���|� ��hc�	f̘�o���sE{~ɿ�h�"c��a�|��ۯ��h�㭕�|�z�z��h
����(s��" Ҁ)��a:�|�*���dK�zJ�,4�j7�&e��zf�i��f?��g�)o�|�J������*�ᚤ)h��hz�ێ�^9��k��o5��a��hiK�d���jK�z �)��i+DDkKNm��Yl�)��|�mKDDn��ho��h ��Y���pKNm�(�z�?Nm�`#qKDD/%DD�,�a$�_r��h���ބ3���5�?�}W�+sKNm5�ktK}j�j���Ġ��_uK�z��|+DDe,Nm�DDd�ည��+v�ZmwKDD)�kxK�ao�|�*���_'���
��+�h��hW)	�X"�X���ᩁ�h�Z�)yKNmHH�z(��W��hV	
fJ�zcNm9�9�zK-g(��%��'���
Ո�3{��hh�t�>�|����AW�zO�ʀ� �aO��c�|�h�|���,�,��5��b��-��cfc(��c/'�zW)|Kۗ=g��f~%��}KNm��˃�+f
f(�����~KNm#
f�+g��{���cKDD��{ �z�DDR�a�K
fǒ!'Z��{�KDD@��c�K}�K�z�j����c�˩c�K�z��i~Ё�+�'D�C��*����{l�-�˻c�Ko@��Y���Qs����o�|���&i���{�˩c,��k���sJ�z�Zm,�h���⁀?��zo����{��>�zl�>|���+��<W�f
��((��k�K�z���{��O�,!v��-o�=<��a��-�fc\��k�K�a���c[:|�˩cR�z~��cЊ�-��k�A��+J
f/�z��N(��쁞-��cY�.��`�u!Äbw���@�.B��?���e	�h��*�,!	�h���{܀��,!�Äۓ��{��y�K�z��bw���{�K�.RH�z���{
;a�+gQ� gO���vc׀�%�zl�h���U?�i�6�z���{ɍ�j�v�ߴ�z�'DfcQ��SԀ�;nԞ�&yF"�Kg6+��kJ�
�F�	$nF��fc
��
��@��k�,!ā�@�%��ۛ�>|�2�{[�'����-΂�k*���!uO�41��� c����'W� gl*DV�T�������T@��(3��l���V���v��ϔ!u�K��ĵ��;�|��X�m��,\��㣨x�=�K*D	5�D�Z�{��*D5��b;�|��K8Dȃ��X*D���ze^�he�K*D��ze�
��*|I�|���h�K�c�*D�uU��|�Svc�g��ze#�,o�|��K�c&.u�F��  �K*De��K�c�ǁ�*D��ze�ze��|��K�c��Zdg��[�K�c��ze�X1(�c�{)#�a(�ze��|��KL��K�{@��>�{��ߴ�U���c!j���d(���{�6�c�Ef����K�{�K�c���m�K�{��ze��)��ze�ck2�"θ�
f(��V��kC3�c�K@+ݥ�z�˛k9�S���z�����{�#�c3Z��K�{�K.u+�|�cִ%&���]�ze>X��^mNJ�a��-!X�4�zeo�|��K*D�a��� ��;�|��_k���h)8Dy��z��,9�5�8ѹ�|��ct�zeݝ�p��|�w�����{b��!�zeƷ�{�8D倢c���h��ze��|�Ad��Zd��{qx�+��'��z4��c�K�czm~N��h���c�K�cc��m�(�{Q�P�|���h�z����ˢc��hH�{ ��ke��hl��<efc,��k���{�\%�|�=�A�Ģcm���Y�K�{�{X�]���{��c����jfck��c��z��
hz��{������CU������k��h&�)��9��b�{�eM��-:�Um��>|���i�\�k���X?7��i{Am�)�K�`U����i�fc5��?���+��D��z�Um��Cmw�t�$ ���ۦ	of��i����K���%:�i��)ny��k��{�f�\�!�ޣ_��h���c��տ�-:�z8��i3������h���z��|�Um�ˢc����KUmT�����i���{#��i�,4~eL�CmF��i�K�-���z�����,Um��h�W�p��?]~e��h��׉
�Ȓ�����U�Cm���i�K��M~ee���Y�Z^N�g���66��i)���K�����{�KUm���۾�[*��+9%�(i?D��.D��)��g���zvD����<DK��{���z��<Dk�n;Um��iP��z3��md%���K�z@��{���+�	Um�K.|��z���z�k�K~e=
��R8���i���P�q�U�?��'���#����K�zX�.Dŋ\d�K�kd��+�y刃.D�! :��<DJ��
J����� ��D�
���=�� ���4~e�o�K�j�R�> �� O)���{�K�hp�?�ޣ'ͣJ� %��'��� <7�w� �Ko�� �K^d��<D�� �K~el�'!���z�����ڌ	:�p>E�J	c7�$�'�J�ӑ���f�� ��zB
�d���ܸ o��;���{��{b�)��x�y\��'Ȋ��f.� �
�|{�� �?�Ki#5�j�:k��Kf
�j�<D�ӁHd�K�y�i#�K�z���{*�>| �i(��լ�(uNNҀ�<D���+�'�/�i_�W�E� �f���(�?��Xm�� �g���}��Xm�z�i%��I�����Xm7��z�� ��i�EaV�E��K�z��Xm�� :�)A����/��w���KGmo��+����K
o[�CӨ�"nc�2�*�k�Gm_�p��z�#D���aЁ�Ύ�E5uƥXm�#D���ad�i��z�Kf�K�y�#D��w9�ìXm�5u{'�K��amd%Q�zw%�ig1D4Gm=�o���'�z�K5u	�i߁�wa��&9;��5uYf��-.���0f���i��݁J@
Gm���>�i���z�K 
���K
 ���e%5uiGm�f���a�8 2�Xm�:�	�ؕ�5uҨXm�ˆj��?:�{U��a�K�z� �{�˂k���{8'Hd)�(u{fc)���_
�C)���Lf뀸�L@"���'ŸXmL�{l*!
f��	ێ��5'
�L�z�5u��6�<�{�'ԧ�-b��k��&03
o�5u�Gm�@"̞-�#DO��/j�ۢ��ǐ-�
 �%�{���+��#L�{�K5u
���G5u9Vϫ�	큌-���-󞥘o�����'E,�{A��-̌-R�f#׋\m�4SI
�	�6�ň\�a̞-�訁d�$��b��/.��y���a}��㳇�-d�		L�Ȳ��y�ʈp�9��+k��-����
̞-1 *
ۛ�L�m#LI+L��
Lf�)�@"L�{L�cYB�{:�')���n�YLf���	�]��f��$:�f#���ܻI̧,.�aS��-��m#h�f#��c���-�{;�f#��k�:|���L�{׿�-Bce��4���Ɩ/\mO��-�2d~̞-5�L�c��ϣ���"@\m��{��޴�����i�cL\m��iL����i"�c��a���̄?L�cL\m��i)�XZ׸��i�Jm��i�8uJ�e��]�2\m���i�磣�>D.��? ��ib۽���ڋm#!̄?"�8u��h��&ۨ\mS��a#L\m���i��*���$L\m�)�a��8um�"g��i���a����Bv%�Jm&��i��'�Bvm��i���,�c��Bv(��i��hՀ��=\m�	��)L�a$���¢��{*�Jm�#���ܾ��{J����K+�Bv,��{��� a�h-̅-�].�az��a���{!��.̅-/L\m0L\m\�fT��1L�a<��2̅-�\mƋfv��j3L\m.����˪4��i5̗�]��-��;g6L\m���-7��i��W��.��a8��a��a9̅-�}:L>Dў��;̦�<�Bv�>|���5B��?�R�J��i=̅->̄?ɑ�c��-��'DR�Cd���L�+��l�hϛ"g݀'D삢ca�CdW��-)�'D*fc��f*?̅-@̢cA�JmO�JIB��:�'��^9;鸯�%���cBL�a۽>��{���`��cb��ܟ� C��{��;g��Q��c�a�f�'DD̅-E̢cԂ v���#��{���a����F�^9�y�?/G̅-;�ݴ� �˕'_8[�fjfc���h� [���H̅-�G)I�M+�eMJ�fw�4���EK̅-ċf���=L̅-*�Vz�Cd�����꾀z?h;K��������M�CdN�'D�<�$����cO�Cdv�N �W�# ;�$|PL�k(��0&c����c��mv��[���Q̢cm�n
_��ǀ���^9���g׀�&��^9R̢cSL�/{d%TL�U̢cV�	f�{�"�^9��cWLNmX�	f���YL*D�1<u��e�^9�<uZ�	f�8D+@�k)��������&�<u﫲j�	f��u!u08D)�#�	f��e�QȀǀ�-��Z�[L�k�<u�8Dk��	����8�\L��.����j]LNm��$�
fc+��'X��h�:|���;�	�5i)��d��X�����{���j^LNm� o��	݀%!JE �B�?_L<uւ8�`L�?R�8|a��b�oP�۸T�i! jv%�9��?�<u���ۺ=g���	c̩cd�	f��oy$�{jv%̑��Tx��{��c�
���{���6_=g��{Ȇf��.�?� �۽�GdJ���_L e̩c��j�
�?+��cfL7���c<Cd�?<uϠi~g
 +� G�ag̩c�I?	q^��chL�?�<ui̻c�{hL�?⁚⌢�%���j̻cJ<ukL�d���c�=glL�zx ��c��bB�c���h����|mL�alL�z��ˢ�DD���SƗ��DDcӣ9��q�{nL�av�J�)|� �joLDD���{3�����cQ�3Æ��8��%D�i���{�?��Qb�av1!��bw�ӣpLDD�2�c� �%�{qL�a�DD(��rL�?v"��s̻ct̩cuL�?v�>!A�=������?�<I"Ԁ�%CXm�DD���wL�᧫��I"x̆?��>!N��0��%��>!��Rp��9���c��9��c��eo�"
fyL!D��z�>!�Ew��k(�'u��yR.�a�5GYI"��zL!Df/I"=���
f3��{̥%,��'I�zF¢g�	�����i|��i|��}L
fm�{,74!D�@h��i�I"e !D�DD-¢P�}�#w幁]���y~LDDL�hiDD�*37I"��wY�h�LDD؇�iȀ����-���i���L)6�?3��>!K�g���:�:�?3�g!�L�h5�����5�]��L�h�)�;�'@�[�h��ce��f]-#��,���"�<耦ŋ�-�����L�h����R=�66�L�htI���L!D�<.|��h8F�+o�Ҵ��zeKh��̌-�g��)}(�6Q%�z��{��ވL�c�L�z���{��܊̌-��e*5�e�c�#ȕ�z�-!DX�W�_���9V�̋k	��{ލ�-; �z��<DX:F��]��f�L�c�?3�Dm~s�ce���	�#�z_��i�L�z�L^��̉k�̌-…U�p��-�L�c��ze	F���d��Am�6j�X�z$�=�V��e1��Jd��-���5��k��-��c�̍?5�zeF@+=���=Ԃf�Z�ށe1�L�h�	�C��	�L�zl2���n�̛kR�.D#=2<��k��X�؅&�j�S��������yЁ��|#2/�j���k��e5��k�-3|���k�Kvs�zLʌ-~�fZ�.D�̌-5��{�	۽��zt��-0�ze#�c�L{���z��eq��.DL��ޙ̌-� g�$ncC$n+��-��L>��?�̌-����'=�o%���{��z�3�z��<D��}v耉.D6�8��-�
�c���D�m~e2�a���Dm$n�g��}t��i���k���D�5�G�ˢ�
D��)���������+�'3@�8�-���+��n���<���6۽�LUm���)Ă,!����DV�yQ�	�̉k;��c��-3酹�� �EmR��z7��co2��a���z���i6۽�L%|�a�\mv�M">2��yރ:���z���z�2+�)g��.(Em��z�L�y�㯥�Cm��o�L$n��F�#f�)Fa�� ��i��Cm�L$nn��l�?��M"�?:�F��z�*���Cm;�g��L �L Dz�z�%|�Em��Cm��i��D�۽~e�L ���)	��a��Cm�i��Dl�`8g) �%�{�Lfh�M"�L�{�L c����%�CmXۜ�L�{�7@"Ȁ�ŵL �L�{9�;���CmX�{yEmY��N��Cm�L%|5'�L �s��L �����iQ�i5 2��߂&:�L�k��Cm��R=f��{��Cm�L ��	 �LEm���z�̫c�L�{)�#�(%|��M"�LNdl��cv��i��e���c�I�{��Nҍ8%|��Cm������c���aĂ?:"2���Cm�L �L�{���.���
�K�kd�Iv���.z�Cm�=Ɓr��L l�Cm���L ����Cm�A �����4�L �����L�kΈCm�����E9n�\d����?f0? �f��2
NdQ��L �@"B���L @�k*�{�L�{��e,�{��k�6�{�&����{
��b��}�L�{g;|�F�{"�{|�4@��#��}� M!�d��&:���Ȁ�^��'#щ����L�����ol��,��E9�L�kՀ��%�C��{�:�{����#��0�{9�wt�}h��)�}c��*��*`Q�W^�X�w�߁7|��hm��,��ۚ�Xm�c9�W��L�k�:uL�c�U�*���/�L�a�����e�L�/�L)5
�kk4۽hc�L���)��k�=�E�e��-S���*�Ea	�g��۽�����¢��}�:u��E9 �a�L):�iG�Xm��)�
Gmѣ��E95�f<LGm�L)�L�{D����LGmK�aZ�o~%���XmGGm"�i���a�	��:u�L�i�3��;Gm���()��i�	�+��i���G�f?#��)�#��CdT��a3Gm��)n�)�FFu���z%)�LGm���_�S�GmZ9o��j��f������Cd��)n�o�Hmć�	�	�bX��;�Cd�Gms��z��a���z��)nB>Do�6Gm���b���:u��)n�Gmzf+�f#�!�b�o���(�)�o��Hm�:uR�:uR�f#.��	�i�)��a�L�iA��\
f[�Xm���a��-�LGm��i�۫�Ѐ�LGm��l1�L�67��izGmTI�a�L-g�iֳ�-e b�$3���LGm�̞-T�[<�y�Gm�$�y�Lo���-A��4�	Gm���o-£��4��%`����Zd(!�������}�!�%魞-l�)nV�f��/q�6�r���ݝQ�Q���f��l1k�II)��)n��_�
�>���I+	��b�/R"%�YU�J)J��Y�I+)�#�0�y.�	f����Y&V/F
��H$�b�j��fjR"���́3aFoQ�S�L�c�L�Fo�F��3a��	f2��kAo�	f���a.�#9S��c$�%.�c2}���k
J�in����*`���&��������L�i���-L�4�������/M�c�
�y��
���/�5���r8D{!�c��7��-����v۽��,8D���i�8D����)���i�H) ��3a�o���ek��8��iH8��>�y�Jm�*`��c�;\mh�Jm)���M8D�Jm�8D��iAne.�JmM}��bMFvdg?��3a�Jm8D��i
�3a��E���>��i�Jm�=�3aX��i��酧[d���;�p	��iSM��	f_��
�oŅ3a��i�	f�zE$�c}�Jm�)��d��3a���
�)r	����i�d��i�JmW�3a�CJ�ze�Jm��)At��' ���(�zɗ)
�)�Jm�!m�}�)�}�UdM8D�):Ud��}ͅ-�}��)^��i�*`��i��y��{
�Y<�!m��a����iA�OdL�*M�a��%��i��&������{V��xM$g����i�} �)�����	���i.�zi�Jm�DD�5[�o��ze)���!͢c"�Jm�DD�$�a���6�a{�Jm�}?����}eF�a�}s;���L9'��o)���C�}#M�z�a��=v�n$�)�� n���H�}%�)'�˻N� n���a��A���)� nZ�=o�&��)y� n9G����2Gd�
f&� n����'�)(�}98)��{��)d�T/� n$��>�!Dٶ)�� n
�\d*�eM��a��L9��4��!D�}�ǣ�)��)+M
ff�}耤OI",M�a���-��-�!Dٶ)-M
f��a.�Am%ۦ/�)W!D0�Am�
�a�!Dw��J$gP6��(
f!>�k��)1M����[�
Dv�DD!D2M
)�
fv����$
)���	9P3�Am4͢c�%�i�/��!D5�)�f6�)g�Am�!D7M
)8MNm<�磑�/9W�9MNm:�Am�!
)��};M�k��PNm&<MNm=M
)�� n=�E�6Nm�}>�f��/�Am|�/��k2�))3�k:�i�I"X�i���L�"��Zd�/�o��k%�_09�l�Xd=!D���
)�#Nm�!D�.51��j?MNm�)��{�!D@�Amh�Xd�G���)�!D��{4�AmAMNm��-ΎAmBM!D�f���C�)@��DMNm��9��)EM
)��AmFMNmG�)H�XdIMNmJM
)o��KM�k̈�0LM�i_��c��{��.���{M�Am5��Nͩc#�/OM�{3�{,7o
}��{@��cPͻcQM�{���RM�/SMNmTͩco
)��cx�z��/UM
)�F�8Nm���c�������cVM�-NmWM}XM�/V���Nm΂�cG�->��zg}YM�-��TB<��c��-����y��dq�fPqg��YvZͩcvJ�z��y�C9��,�{�y����[M�{?E�{�Yvhc������p�.ր�@,�{���cԂXd'��9�3ß�{\�bw,�y��z�yp�?��{]M}��{U�?��{((m8�ϸƁ������k�?h���6�y��c>�k���{���xHUph��c���hc��-7)�kH6$nÀ�ܾ��cy
o���N�:P�]��bw���?��k8��c��?^��{x�´6+Em_��{���c`ͩc��yj�-3��?a���tQ��tA(�'�EEm��a
; ��a�����a��Ѐ=��?
�J ��=�h�a��a��y#Zdb��ac��a�X�8��c|��܎��a���dd�%D���a���B�yn�%Dn�
e͹c���]Em2۽f�%Dg�%D����'�v�M"h��a�
on����a�%DӞ�a��a	� ���a�%D���ay�Iv�li��a�3�%D��񚀹cyNd<?\dj��a=��5��´�;|k��a	�%D��alNd��l�%Dw@"��a��){��m�)���`n��a�k���oMEmP�%D\dy��a�NdV
ue�%D�0
ogNdp͞-Q�S��'q�)`2\dwuUrMEm�
o������-�M�sMEm���a��X���m��at͞-�Em��S���-u�%Dc��+��Iv���a���a��-��%DS9v�%D4��}��ix�洓4Sw�%D�'�cx��ax��-��iŜ%Dy�%D���-����[p!�a��zM��n�%Do�1<��G9|i�{�%DSĞ-{M�cRB�9Ék|�%D��G9��Ѐ�]v삛k���}͉k���B�)3��k�������o��z�@"~͉kkgNd��&�G99!�f���-��);�G96��*�Î���)L��(�G9�_��G9;<u���� ��$�\d�ڋ��͞-��F������)�����}���!Ё�܃�G9	fJ�co��+�䣄M�c{�
}��)�͍?脭x�
)V�F�Y��l�
}9���=u��)��'J�k����Hm��߮��#��i�I��)��(|�!D���?�G9��ZdV	��R�c��Hm�͉k		2��G9���k�)*�Ր�a�)�B�it�Hm��)��HmW�a��^d%�Hm�߈�)�����Bv��)�G94Mvx��k�MUm��G9�0)�MUm�M)�G9	����a��i����hB��MUm���'	2)A�a�	2�)J�	��ag�Y��il�i�MUm�ad�)n�()	�a)	2���z�����z���zؠ�	�a��is��o�M�a�)�M`*�MUmhcl�i��9��a��Cd�MUm<�&e�)�_"eMv�9oO�T��Hm��
;<�_"��Cd^ Um{���MC���Hmw�
ّ�r���i�MUm)��(|�DUm��)��Hm,�xe�͢c��aĉ);�ʋo��7�ao��[��͢c�0)�3Mv�cK*�a�M�c�Hm#Ăb��c�"۽v`*���&�M)�MUm�͢cv��'�#�i<��MMv$s����c�MUm�M)�-gUmg}j����&��k$�^d��€��a��i�,Umj_�}$n�M$n{ Um���&��/�٫M�/>�k�E�%i#���cBZd�M�k��_"�d~��k5��i����5o
�\d �;g�M�k�o��)n��/hc	��'	Ja�=u���b�kJ���{|�)nԂ�z���$n�͢c>5o�;���'G�P�Mo0��%㑜��c� �Mo�)�Mo��&`*�*�{�i#�Mo���cO��b�ck���_�M�/{D$nk2}��'�}��A������%��o��/�or�kHm����M��<ãp�����8[���i"�%D[�z�������b�k�D�k��f�S�{{�#�M�kJ�F�)Ç�0jk
P������#Fv��r�|��a��Z��k�o���}5���{+��a�Mo��jeÎ>��b~��Xm:�k��,D��,D��Xmv�J��/�/=Hʼ�Zm��6��,D�M�/O�Xma�8<�
��b��ab�a�)_|e:Zd���aB#����,D݇Xm�}�D���^�,D��a��b~�M�z_Ԥ��,D@����Xmu��j-ne�Ӫ��Xm�M�z�}wJ����Xmb�%D��,Dz�)�M�i�͎-�Gd��Sxv����Xm��b~4�kw��,D��Xm[ Ԃ%D�	����%�Dg�g�L�Xmg�?��F�O�{������;n�	�c��Xm�	�c�ͥ%')	=�}��&�|e��9���a�����ao�c2�;ndco{��%��q���"�d������,Dƒڰ��,D�M�c5����tZ9k����Xm��Xm���a���偔Vx�,D���@ބr�!�b���F��o3���M�c��,D����M����c��Xm��b~^2��,D?���$������,D��?���9�[�j���
�M�z�M-n�~1��(f�F�A
��:I"�M���L�?aUd�o:�����&SZd:ã��!����(4 �����;n�%��/��Ea{�%��c��}�#ã��b�ͥ%�M)��ݝq�s�E�D��m΁
�\�c��%��U��;n�)�e�QK�b\�c<�;n� �MI"\�b�MZ9�.�@f�	����N��c�ͥ%_*AH��/�2�c��}o�	o�f=R�D!!���/j�
��fb�����i ;�c\:�ɸrf_��i����Am�Mf���ik���t��i��	��ǔf���i�M�'b����/�M)�����/b��9�ң.�m�\mFne��'���6偃X��f�4\m�1)#�)n���i�M�c�M)���iJ�����Y�'�B\m�M>|�M\m/)�I"֟)n�M�'�M)��o��"g�M)j
�c��f���}��(瀫%�)������i�\mp���c�M)_Zd��o���ic)��c�	f�\{\mZ���
��n] B�f���i���\m�\m���i�!��J�+J\mܞ)n�����f���/=�
	>|q�)n/:)h���M\m�M)��/�M]��)n�\m��c�M�'�,)Q�AmB��Q5��D)�8���]��M)�Gm]��i�E�ρV�ƁR�׀�!O���M)�M��G��)>|�M)��+�MGma�c[�{���)ft���M\mX��E<<v��{N\mc��XW1*]ψ�iN\m�&D4�{N\m���y��{�ei�����N\mW�>DN�{�kN�?�>Dd�?�� ��𘁃-�����6	����6���9W�΁yN; :z�Ԕd~}���	�n��Զ	�?e	�}�f�]��^9$��2��Ɓ[6���	ΰj
N]a}�C�}%��)Ё���^9���#����aN�?؁,D
�}S��{��{��2���"]���}���Ԁ��{Mi*�
o�}�����yλ}� �{���׀��@�{z��y�}M
o��-����N3D9
o%�-����,D��9�}N
o��_+s�E��Ew�t_��i���fbN�-N-n�I+�	�?ԁ}�,D�o�o$�i���
��H����N
o��.�{�*ã��:̋�Ad+3D:�-�3D�Ad.�$���D�i.��O҂�AdN�?�M"A
o�}Y�%!�^9����^99'3D�`#�Ad�Ad,`#�3D����}M�M"{I�}aۘ��`#�}t�}���� �}�U���iV�Ad����j��q+
o��}�,D	ܖ)!�)�}�`#��o"N�kdB�
��H��i�	f#N-n�c�����k�%��?�i���b�-n0��$�)r
ң�
�i%Ϋc=\d�{&N-n1
o�3Da���M"'Ϋc�k� (N-n=��,`#03DG��lNd����텫cdB�Q��'Ђ��@�}��|�o��l��{���[9�(�hc)�o���"Ё��t��*�)+N�{�	f,�oҢ�0-N�k�� 5�4�i��Ad���<!D���ce���f���c.λc/N#D0Ωc��1Ωc)���!D��n�)�������N����@"��ץ�c�. Ї)�{b=�)��&�۽.�
Ȁ�� ����2Ωcx	)3λc���d���4Ωc��U�o��9���"n=�J�f�_DD	�C��)��?<�գO=Dv=�j��{�8��c5N�{��{j9DDt
��
�.���ik��vr<DD9���5��i6N}7NDD8N��_"}��*9NDD��9N!D:��i�,DDcD)��
o��%���=��9����;��iv�̣�1�c����Y��ݣ<NDD%�cRʩc�?DD
I������}�cg�-���)EDDX:���́�`��i��)=NDD>��i>NDD������c���c#Wm���`��ckɩc:��{Ed�)?N
f@��{A���_"�WmBNEd	��y�d~�"	28
�<�aCNEdl�k~t��a����6EdD�c�0EdY�_"<DDR*
fQ��a��ct��{DNWm�;gDD�&ueE�cef
fF��{,�m*GNDDHNDDINDvj٫�ceJNDDUGX9KNDv�Z��X9L�)�ԣ���o�}�`*o��V
���X9��*��cM��i2��{�DD���ϑ}ɀce�&9X9NN}s�ce���ag�cc��--��Ho��+X9Q�,��c'X9ON}>��)����f#�o���y���P·-%%}?JX9��ce��c,�b��cS��4�$�7���l� ga�F"�'
f�Ed�}QN�c.Y+C��YRN
f!?}�����yٖuCSNWm}T�a�$}UNEdVNWm.Y+�}WN�'�EdXN�cYN�'mݣZN}9��[N�c~�Wx�Yv*��\N�']NX9r�m*���a�(^N��R��- �cem?�c=��ݞ�'_No>��d�ce;�'� ��'`No�ۿX9aN�'D�̣�5X9�a8bΞ-cN�cdN��9��dD&o����F"�Y+eNX9�7�'w��<��/p*o��YS9$nx��A�}kS�cfN�'�}Ȁ
o�<oã6o�I��}gN��\}�f#j�����y!
��w6G�o��i���kR�4�-��8
�<��hN��iN�i����8�X�jΞ-kN:D;�cd�O���'J��TA�';�i�Y+5��ψ�-��i�)���.Y+��'l�)�'�b~<��#.Y+�)�)^���
�̣
��m�)e�Zm�:D
�c��֬�L��Eo�3�'��ZmB�'�"�i��{�'D��'�:D�}n�ZmR7�'^$�0��Zm
g�+"n���/*�R�?�:DoN�-ޤ�k��ZmT��p�)qN�iК	��8���� �)�b~A�-%�)��գ\L���'D��Sge��i��om~rΜ-[�?��as�Zm��e��	�aK�^�t�ZmuN:DvNUdwN�i~g�	����?xN�$�i�㯩 ơ��`��iFUd97�o��=W"�7�3�Fy�)W#��5z�Zm{N:D|�)+�>��)}�Zm~N�-k�P�$�Zm��z��;n�$g�)��Zm@�٣�N�3�)��Zm> 37�&:�N�l�D+.�F��΢cr�d*���`��ܹ� ��ZmQ7u��S�(�{��iJ��n��Hd���g��Zm��Hd��{��aB�:|*N�{��l$g�Cm���cބW��N���H��J��'��m~�[��M=��c,ge���'����Ҫ���T�,ge���'��(|o���c���*�D+�ֈ΢c�N�kЁ��)�N���
}�棎�D+l��+=�Eh��'� �*D��Њ�z@�e�k�+��̣o�ָo������!!醅Lʍ8�b��'?	r�	�?" 3�;n[�q��N�'��׀'ό��'��2�f���Q��4�)n  '��t��ݍ���!!ւ^���'��	o�*D�!!�)K/:��)nU��܏�AmR��'�6n����i#j���΢c��:':�$9��k���N�i���d$Ef�)ne��'�΢cA��'ݠ�'z�)n�΢c����z��ܔN�/�N^maA�i��%�>>|�N�/i!�z��g�N^m��Am.�{S�{e������2�����'  a)��g ��'w���N��NC J�(|T^m�g�ۚN>|�	�����ۺ��a]�>DX�{��>|t&��>D��f�Nۼ9�>D�
^m	�fm���>D����	�f9�je��{���
���)n��>DA�i!֔i���a��>D+�i#�
8؟N^m�����>D�Umƍ�'-7�?m�9�Nmw��Am�*Mvˆ	o*�Xd�N�>\�)n����Ԗ�0Ղ�a��>D����)n������9��Amo�	Ҁm~�բh�>�^m@����Jx�9��g���>Dr�"gt��?g��d��>��'!sg��NMv:��?X^m��9-�#u����/���8ݫ؁"�/�I���� ��?�#u��x:�>Dy0>|��TBD�"gU|ed�f���D|�F���?�=ģ��a��>D���-�a��o��>D�����?vY�d�f���ab��{k���N�>��g5�m~��׫�>D�
l�l1�#u��>D˺�����?j�jeЁ�{��#u탏?!5��Ώ�.�Y.�@v�)�>�+۸�NMv��#uv���.|qU]Ё�{&!�>Պ&��kC8Mvx@Mv���5)�9��keQ�D�L� �&���k����N
o)�(u"Jd����vʭ�#u�e
�,D^(-ng�#uEm�N�i�N�-�����N�i��-x��JEm�ιc~��E�@-n��-�Hx���� ��#u0�.�.)��)���iX��S��o�� c�|�B�E��j4� d��is�ae���*۳N
o�� ��o����N�c;-n�� �N�ifQo�c7��{�2f�\�AdH�&׀ ��ܔ�*����i�.|�= ��w�ul�%!��Ad�N�'t�AdV� d���3�Ad�N�cf�N�'�fc�(u…4��g�c��+(�
oN$ˆ(u�H�c����4�ae	%�uN$�<f��(u�� )neR��$�iz&g}	��kN$�inf�N�c��Ad�N\dX���b
o|�Ad�N�iX�{��i*��-��}q?�ģ�N�-2\dAi*�
8��N-n�neUž-�N-n��-��-�NEmp��?g���a��j� ��<�����Ad�
ne�� 犖���o健������ae���ܒ������k�;n3�u��#De�oV�Ad���z� )���,#D�N�c�ae��a��z9q�������Ad�N]vw��i#D��ae�c�Ad�-X����:F�&f��գ��/ne�N$�N�c�Nne�N v�(� �N�'���	{coN$�����{XW1��i#D�Nf������N$
=�<<neO��-����'u(��,#D�;�Dv.������X��=����NDD�dwG�"n�B��NDD����Σ�m���NDDi#Dr�=���>��T��-�ģ�c1�̣�NDD����g��i"dw���o�y~�
z8j\k���k�����x�֣ރ5d�����i"dw�#D9��49��<�NDD���i�N�cT>|A�Hm�8DD���{��>D{DDl1UT��i,#DL�>D0��C�cS�i�NDDR�>D���M()'u���v��jX	29��i98�}��&"�HmD�c��h������>D���zR�cҀ�>���ͥ�iۊ�QV�iLZ�i�?Ed�-gf*#w����N�c=��R2-g��>D{�Y��NDD*���NDD9�i"	2�Ed8
�<	dw��c�hȀ́Ȁ���B�o�Q$�NDD�dwk�Q$�6��۪���g��i��k�Ed��zp�i���g����i~Ղf2�]�2GDD3�HmՂ)s�V���>D l�c}�Q$�w$$��i��e ��Q$P�����iꢦ�4��i@�գ,`*���ݣ�k<��ho��z����N�ck�>D���h�N\m�΢c�@��N ��ix�c(	2��i[9�Q$�k� �5�c��Q$�ȢcW#���h�N�c�N\me�ڪ� i�i�\m�g
ۭ�z�;Ed3)-g��	�-g��w$b\m�_���c�N\m�NEd�g=����Q$ʎ��.Ed���	B��&Ed��g٣��1��)�΢c�9��Θ-
\m��Q$o�⣨���Ωc�������g�)o�Q$��41�Ωc�z���c����?������X���S�@"�N "�yl���Ωc8
x��Ωc⁥d����c���!��c�N ��c�k�!բ��	�F���+gz�U�'D��f� ۸+g�f�λc��fQ�
����Ωc��f/�Ad�N�i��e�΢c���Ad�'D�΢c��f9�'D�\m��+g^��bXq�񓪿�N�-���N�i+�>x�i=%�-��z��Adc�����i���ݙ�N�-Z3�z�f��Q�f��-A���,!�f8%ۄ�Ad���~���� �����f� ��heo�����i��Hd��g1�f�he/:�id�O�{��Y��ٹa82���5�-O�-γ�c����35��׀he����+g�)}~V�-)�he��-�he%�+g��i��b~�fD#Tl:�?�iϻc��-	�fʋ��
O�ia�S�heތ[���?[��A��he(���ue�fo۰i�f�B�{��
2K�?�f�1!
��{�HdF� ����c�iwa��g|�O�iχ-o����?9�:L�9�:�sE�i�m~5٣`{�*{��g{�?�-��99����1� ��{��a�
2�� �<�-���b5��,(���{O$g����Hd�b~ՠ�,>�{�*D��'��i�� \�{T/*Dg��5�he-� ��Hdj9DD�Cd���۰h�heo���\CDDχ-�Hd�h=C�Q�DD�Hd��?O�?3Dv��ݡ��b���ȃwU�DD��v�_�s��1!πbO�c�8D��{O*D��c�WdODD�m~O�cue]%*D9�� ψy�	%�ց2��a��'ҡ[�N�!O�cP�'"O�cc��#ODD�*Dk�2$��{%O�'x�搃@"��'e��yf'Jm����&O�''O�c%$g(O�'z8د*D)O�'v��i�=g
f��?�*O�c9zTV��d��iH�_݀�k+O�c)��,ODv5��k��cb,f~!��$g�Cm��-��i.ODv/��i�7DD0O$g�*D,'�	�'�
DD��n�DD1O�'=j��*D2ODD�Dv��'P5)@��{���k���y�"�'�*D=��{�Cd���kdޣ3Of@�̴=��f�	^m��'4��i5��p�j�����j�F�'��hk>�
	�i���i5O�',�'�����6�i=��6��a����a+�'�/g��^��c_�Ԥ7O!DF�c8Of$�.e�'߁��f9��i�+�c	��/:Of;�X$q�5x��i<Ϥ>���-h��k5�܀f��'d�Zd��aD�̴=Ϝ-�f �)��ZdZ� dfg���
�ZdU��h�X$b��ie��{>=��9�td�� R��i��h���o�����V�Zd� 
����h��/>��ij�ۖ��d��xe?�ir���Z����@O�a$�A�Zd��'ˁ�r�<���B� COCm���,�i��X$��{%��-� �������z�Mv��{�$�
�/�{��'k��?߁�-��(XW1_�Fe��.��zDO�{��h�F"���AE�X$ۗ�h�S8�F��zG�Zd�	�9�*#�0�{:�*����zHϜ-�%�b�X$��F"�{z��-�[v��hI�X$5���f��,�
@+JO�{KO�bLO :��w$�b;�X$MO�{N�Zd��bO��'��Zd��ioD"v���J�xe���PO�b��(4�{��/QO�b�w������RO�by�k���x�HdS�:|> ����h�i��F��zmw�i��zo�Sƨ:|Q6���(����<�%�iT�:|3��'��HdU�:|*���zv۽�:|%|a��d$�. VO�{�i*�+�ic��
WO�k� T��ih#�i3�:|��5@�Hd s�XO�b���az
�{Y�'��Hd���*��'�) �j��i>��@3 X�) �;nZO�b�o[�%D.�j:�i� \��'A�bR��':�i��u97�<��'݀�']��av�%D^O�a_��'>�=<��`�:|���c;,C�a��'b��'��a��:|�SA���.ˆ
f5���c��'\�Hd5��'��)gD��e���'�;ndO|ek\$%�k5�:|]~eS�HdV
\$��ieϠck����գw���A��$�:�ʋr�
�c�z�s�Ϡcf�:|��{8�-n:�i���bo\$မ�o\$<|e����c�)Q�i��b��a��)g��h2��ЁB�T |egϠc,��aQ��b�'h�"n��8zĂIv쑠c�
|e?�E�w��'(
��yHk\$a�B
�dž�ae��'\$��a
쩀��i�)"�)g��z�#�>j��'k�;nl��'D�o��Iv�5um�;n�>�z���:	�:��U�>�zy�ܮ�)��z�
)���[�U�nO�>�֙oOgpOCd���RJg�\$"�\d��k�����n|eqO�>|���\$��c��>���:�Ȁ��������c���e>JdrO\$���u\$#Dn
|e酔�<��b���4��?x�����b=���s�"nt�Cm� >|uO�?�5u���3*>|*��$:��6vϏ?�zrfw����z��רCm�b��>a�5�,�/��~1��e��ܹ�$:"��/��zxO���>|��p�g�����ȃ5yO�>z�>D�>|�:u{OCd�5uy
&|��i����:u}O#D~�>D�CdX�>D��i
�Y�>�>D��:u�����c�:u�O-g��-�l8Jd��i!��>D�%`��>D%��:u��ϥ���Ӟ>Dq�6�OMvS����^���'���{(�3'��>D��>DN*�NMv��#+�����{��մm��-��-��#�����*Mv>��{I��i�# 
Cdo��R3���dw�;g���iЁ�xb�?�%�?�O@">��w#>|�F�8dw*�Nd�ωb�O�c=����]>|�OMv�:u�O�c��c�ωb�O�c	�O�>D�*�c*�w� >|�d�ù_��{�OC�R�I"��#��4��O�'Ӏ:u���s�3b(�>D��͓���>DǑU��:u⁗ݔ�>D�� �<�c��>D��:u�� "�>D�:uY�vƖO�cX�;F&+��W����c�cb��9�c	Cd+!\m��WD��k��,�ۗ�#����uq�OMvh��-.�,��,��#��oV;�w@–�\m�	Mv�πk�	�Y�;g~�`���m��,U�8u�O�'w��{�;g,�#���x�O�'��b��,�c�cz�������;g�O�c��,��z�O�'>�c�c�'
�?+��7�w_*��>�\dGm&
�c/��y�'9���&Gm���o��/	�cD���>z��ݟO�'G��y��'DL�AdړC$B��>��'D�	���πk��ԧ�{�O�'��C$��Ad�����d�O�i(
�����'D��yb��C$Îe����	G�Ad�C$֟Cd%�仁�X�A�?d~��C$S��'9��AdU��,�O�?G�գ�O�&��?�'D�'��y��"n��S܄Ad>�iu�v_�O�?:"�a��,��y��Ad;�CdO<˝��e߂�=��	�'�O�?�O�'Œ&D? |��Y��}~v�]�c�
hn�?����d~��ZmtN�{�i�O۽�'D���L��j�O���O�{�-�ρy=d�~���{�C${��i���<>�͢�O�{��C$��?z-n7��ᒁ�/.�b~��Zm�O�i#�-�I���O�iD-n�φb��Ad�-��C$�'DS��c):O�i��b��C$�O ��-|r����)��b�C$��Ad뀸cQ�Ad�bZ.�-�O):;�{h?g�O��<��v��%�O�i{ۦ*�r��O�b�O�-z��t�knWd����&V��d~��k�
�dm�,*�w�jˋCm�~/�k��-�O�{	�m#�υ-l�)���b۽ODD��v��C<u�O�k�ODv�	�{�O
�k��У�c��x��?�*DG�k�):�O<uu�=�ODv�ˢc�O�k�O�b�D�{�OŪ�O�k�I��ODDR�)��{"�,D�HDD�
�ke=���O�-��b��	��bh-�-�ODD@<u8D{ۦ�ODDL
[�J$g%�ۅ
DD�<u��bé�Xz Xz�L�-�z0�ODD�
�y���*Dv���ODD�O	 Ć8��ODD+��b@f9��;״f���c�Of̏�.Ȼc҇K��Of�k��k�O�{��ct�k�%u���	 Ą�-�OEd���c�Dv�N�Z�y���c#Ed���c)|�ϻc�Dv��Am�ϻc�O�k��c��Am�ODv�DD�?	 �ODDf	7�N7*D<�Am�ϻc�ODv�H8D�ODDyf�O<uDv����Am<��f�ODD�O<uL��'��c�Ϣcc�Am:G�a���c��Am�-�ODDk����'�����zt^m@�h�z����'�a�O^m)�o��a�����zĉ8D��=���{�y2�]�Ɓ�xo�����AmZ����J��Y�� �)�OEdCL���Of�9�z3�>!Bf��ze�� �>!��w�Zd�%�OEdk�����4�ze4Ed��w)����Zd��c�ϻc��Am]�ze�Of�O�{��Am�>!�φbϛ ���c�OCds�Zdg�)���'�Am9� z��c��Am��Zd�� �	I"��Am���'<�g8��?������'��Zd��ze�Zd	�z�K�t�ۗ�AmӃ?3Հ���`�Am�O�a�?3��Am����D�2������ZdA�i~O:�f>=F���z�}�χ-�%��͢U�?Z���'p�b�χ-.��ˆ-:��۾�?3�f>�&�7� Ӄ?3��zel�%�>!'� Fˇ-�*� :���*��*#6�?3�Zd$�o���ze"��S��"n�χ-
�����{�χ-��Zdց�e����	�b�?3��ze��Zd���{	%�4�ze%Nmބ3��}��jB��y���-�}�۷��y�)���{� @��Ї-�����{
��y
�(��<D��c�w��cŁ<D-��{��?3h�`����-���y��c�7�-��i��c�`��?3���P�i{@�5���P��xCEmy��y��DZ�o���PEm���{ᾚy  Ї-��ʞ�>��w����Em/��w���iڋ��o���PEm�ι��-����EmP�c������-	P ��<D�i��־
PEm��}B
�  $�)gϋf��
 b��PEm�IvO�bD�`ô�k�5�i,�<D�	cM*�
��{��<D��i��������y�;uEm<��{� D��� Em�(H��{��iP�'�!�ŇXda�o��P�-P�'C1^d�{�^d��f^��i��i(���P~e��b�a8
��%ۦ�ց�O�-)���ЂbǃU $Em/&�'���bG ���'�% ���z3�`��{�/�PEm�~e/ɵwPEm #�'P^dP�- ���(Jр��; �xk��PEm�-^�"n)����b��5bĂ)��B߄�bPEmA��i����-���O���Pm~.%Dl��iP�?�����?��-���Ԁ�Pm~aH1D��
 O�&�#D���P� �Iv���ir�'!P#D��G9"�G9#P#D�}ǿ�-X��#D8�}k�G.$�G9��>��k��e�g�[C��#D<��-%�CmH��?�?��Cm&P�'>2'�G9�����Cm��(�G9��Cm"�G9)P#D��zb�#D*P~e�#|��G9���Ҫd�M"7��b���?>��zj~e�d%C�+Ю%P7C���ij�
���w�-�b,�Cm-Pf$����G9.�Cm.�h/Pf*�wO�Cm��#0�$:g�M"Q���	�b��$:R��i$����#|����]d1�Cm��#|�r�	fM�`~��?Y�/�����2P#D��Zd֋G9G�G9܄�i�
z8��,Q<m~�G93P�k��Cm-�G9
m~"*�km��zh��5�ͣ�	m~S�G94�Hm5�G9>����`~���i�5#D���k�G9�	�ji#6P�-7�Cm֋G9�!f�i#�M"��ᫍ?��Hm��}Z���5�ݾ���i$�ݧ��e�kk��N8�Cm"��5�b^�Cm4�.�=�Hm����{���'�-�������9P�k��tF�P��� ��i��kfp��Mv���iP%�Y!���.�k��ܾf:��'�$:��)Rf]�O�Hm��j��Hm;��z�;g�;�>)|��{5��lNd��E9\ۢ
&n3i�<��i��`=�Hm���W>�Hmrڴ�ku��+?P�{����k@P�kA�`~A�k�&nBP�{�MvC�`~*��b��ZdD�Hm���Y����{EP�kǯHmz�ZdĂ�k̑���kQ��M����i�Mv����9���ᬎ-Z꣰͂k9Og�Eg!e�Bv�۸�Ӏ.�-���b#���"gv�2�E94��E9֔i!|�i�O���F�2�R���U���U�c���$�Bv��E9G�[v����e���)�je�Gm�;gV��ޑ���Gm� ��{�}~�HЃy�"���{IPGmJP�i�<}~��y?��e��u����
	�b!����Gmy�Y�KЏ?��z"&n�}~@��Yg��Um��'D7�/�'DLPGmR��y��-M�'D� D?	�9�̏?NPGml��kOP D`�&P�'DQP�i���?k DRP D��i������y�T�5��ySP�iQ�'D���%Dz'DĂ�?e DTP�i<:D����U�'D��iY��?j�z1џ�w��'DVP Dm�i�'D��/� D>��wW�'D�:爀"g���'=�iX�'D�X�
��/YP�i/
^d9�'D"�/F�'D D���.ǍW��GmT��?��iZP D�GmQ�[P�i���a.��?��'D\P�{:
''Du� D]Є?� D..$���^� |t��?aGm���?g��y+���%6I+Ղ�/��a��/_P�{���):)��ܹ� |��9�'D+��&%�-d�� Dg�'DOP D*�i���i��-5��À�?���i)�'D�
�{`P�i�g�D D2�'D�):aP�ib�'D/��wcPj�2 Dŷ�-o���L�idP DeЅ-�Fm��,��m#�&��M.�-X;�fP�c8��-��-��?J����+�c��-���$��FgЅ-��Ǵ-n����hЅ-d~�Ҫz��aiЅ-j��/����kD"kЅ-l�a���?�а;�ct�4�})�.�c�
�{9��B��a�{��*DmP�cN��Ă�{J��&?�7)ʋo��	):B��/����{
�Y�)nP*DoЅ-��]
��X���pЅ-�m#o���,*Dj��qP8D����X�;T	�{��bk�)rP*Ds�Jm��-�{�o�*|t�Jm�d��ʷ������9�*|���buP*DvЅ-w��i=����…-��|��1�'l���|z�Ё��5�|���8|xЅ-���=����'yP�'���`��-R�XmzЅ-��'{�Jm�*||�Jm��;n	����y}�Jm~�;nЅ-��{�Ѕ-��\d��Xm��	��l��{'	�PCJ\����E�.��8��Jm��Am�[�΀8�5)�P�c�=*D(�'o)|X��>��*|�Am�8D�*Dr$|߁JmOG�'�	$|�*D��#n��{o
-�b�a�Z9��ǫ$|L�Am[
�'�*DX���J�P�a��g����{��a9�Jm�Am��+n�P�'̔�{Z�*|�P�a�Pv~��$D��'����ѧAm՜��8�'��i�Cy$|:����Jm��#nCt>�g��$D��Am�P$|��$D��Xmw��5��:�8|ʇ�a�v~�P�'6�$D[��ǗZ9d��ٴ!�'-�>D�Pv~d܀��z��{�P�')+�i���{�r�Ё��/�'Ł$D9�p9�`9�x��$Dc�Ӏ�����6���+nQ�vOI"�?��$D��i�g�>D���{��?����>D�`��\d�a�r	�$D`����5$|�����$|�$D�PI"�Am��Ami�$D��+n�Z9��t�P�?��a*�)]t>��[�sŹ�d�'��U�P�a߁�w�Pt>�нJI��	m"v~��J�P�'��))�v���$DH-�'?t>��o�磣�$D��*��*v~��.|��?�P�{�
�'�$D�\m��V���>D
�)[��g�b��}�P\m���5�ېNm����L9�"g�Nm��_���>D�P�i�\d��ݨP.|{��`��>D�6gI"aS�Nm��i�Pd~.|�P�?d��_����r���:�uNm��ݫ�>DZ�<��?�)�����i\m�>Nm�
Nm��<D0�?��}� ��?$.|3NmG�{�.|�P�?(\m#.|����pu,c�i�Pd~�
ۗA��F�<DTNm�P	 $�����Xd�8Em�.|{ۦ��<Dq:\mG�9�FEmKNmC�.D��Xd����kc������񯒋k�k%6�b5
�{G��c���a��.�p���}G��c��n巍�c(�8'G��'��<D��{���k��'��'��|�ǩc����\Nm���'ߤ�c3L�{��'q"���a��(L?�������'�Pd~���a��Ad��\m(���Q��c��'
�y�	����Nm���v��'À�c���'{��a����P.|)�ܽлco��׷���g����.D;��'n�<D�P�-{�}����H�.DY�}.�C�����P�-I
�P�{�D�-ʇ�a�P�-��<DϖƮ�	ob��P��-	�����$�f|��{��큋k�Ќ-�'n�i���&�Ќ-�P�-_�i��=��݀�c��-���}�)D�Ќ-=��9�-��}�P)D�Em
�_�g�͟�-��&{����P)Do����Ћk��-�P)D�P�-�-{��@G��c�P)D�P�?�Ќ-0�Ad�P)D���'���s
�?p��.U��'��'a�)|��8����Ќ-�P)D��)|�y����P)D
�Ad�P�?|
��9)D�1D�Ќ-��Ad?�����-G�y>�^9_����?���y>)D�P�-��)|��5��<�}G�y>�P�k�P)D@"����_+p�-��M""�?:��)|��)|���P�-��{~�P�-:�������)|�����˹�?�PDv�2�>����?��)|�����&���
��-���_���p�f��-�P)D����k�?�К��)D�Ќ-G@"�PDDv�ڇ�Ќ-[����f�Q�+�?�Ќ-��j����#�k{��}�P@"�P�?�LDD�P)D��Zd��_�P)Do�&��-\d��Zd<�c��?��)|	���y�)|����/�?:�m~����P�k�)|���)|��+'�)�Zd�����)|���P1D��)|z�f��Zdi���PEdw�#�w�Zd��y��Zd�1`S��?w�cG�y>��{~耛k���k��B
jˑ@"]G�j�ܣ�P�y5�|��/��Ȓ�n��<�ۍ���A�c}�
 �'��PDD���7�-n>���l��>���o�
�c<�x�DD���5�о�d���P�F�i�6DD�Лk�@���
 �X9@�����y�@�w�������k샚y#JX9�DD��Zd��i~�\d��U���8�}�(f�,�P�'��o��i~�&���{K��Zd��F"�Zdo�� ���{v��_�ɁZ��=���ʐF�v
S9���{	��h�� l�kb��}��7�X9���<%P�c�� ~%�=٘Ęs��М-��c�� �&腶����-QWm���c�]�⁛k���-Q-|O-|Q�e�Wm���k�܀ќ-�-|�{� Q-|~�)ц?ќ-74�'ћk��9縄�	�Bv-|
�i~��̾:|��ۈ�4Z��
 �Umǂ�Q�-D���o����'�/�'U�)QUm�-|�'�-D|F-|
Q�aQ�'��-D��o�f�Um�aќ-s��i��iQ�'����	f�-D8
ܸ�/�'�-|�-Um��B�ף��{%���Q-|�+QUmQUm��'��V�<� w��-�>|ō�,��-D�i�8�o��-DQUm��iЂ�8�i�-D�{"�y��	>���-Dќ->�o�-|�A.n!�"|�i�-DQ-|�� ��o��-DQ^d��- Q.n!�i������r-|��Cd���"��'%��i����#Q�'9���<��i:�$$Q-|���%Q^d�i��i�/�'�U]:���3�-D&�-D'�Zm�4�'	��>����(Q�'=��b)�Zmq��*�i����۱�⋱Zm+�i,�-D�-D-��'.n.�"|˕-D/�i{�}�m~9�-…e��(�_"��Zm=m~���x�Od���
�����-䀛b��ozOm~��g+�k0ѩj9v�ՂZm1��}��Zm2щkh�{v�.�Zm%��d��jە��Y���3�"|)Ð�i��>Q�2
��n聩j�
�M+11m~X�2\����-����덉k���j+�8؀��>)��Zm(����q��Z#�23m~~2.n4Q^d�)� ��j9��:7�d;ƣ5�Zm
�6��aU�Zm���a������{7�Zm8D8�2���9Q8D���c���aS��b���Կ�Zm:Ѡc���b;Q8D<�Zm������c�Zm�8D���c�t:x���L8D=�Zmm~J8DX�Xm~)>Qm~��c?Ѡcp��%e8D��Q�=�t�Xm@Qm~(�'��cA�2<8D���k�2�"m~h�Xms�-��%[���c��|Z���BQ�i��fY�β�8|@�y�Ɂ8Dc�2���k�oCѩj،�b���ŢcJ8D� �y{��}��	��XهXmS�f�+�iD�XmQ)��EѠc(���iw��ւ8�ǑN�FQ�iū�'���
�Xm��ir���y�Y�<�}Pj?��'��ۇ���j��GQ8D��8؍8D. D�
��FuH��'U��c���|i��bB De�}��I��'	��'�az

2Z�f���'���c,��c���JQ8D��aKѢc��'�8D��XmLQ^m���bP^me��' D�2D��R�XmŶ8|���|MQ^mNQ DOQ�a��'c�]���6P��'
2QQ D͐�'^mR�&D�^mC%2DS�XmTQ D��jUQ�a3 D9�i�)�Xw�F�g^m��/( DZ�&D�
�6���/)��V��/A�ioΣWQ�ia^m���'=uq���+Si#X5����m���Am���di��/XQ�kY��'{!^m�y~Z�&D[QMvZ>_�\J/��'���d�
�\Q�>i#�I"m z���_]��'O���ل6�R��/s
 D^��'('��k�OI"_�Am( D`��'/*�� D�*#��j�6�i���u>^m<��3Q�j<�" z�&D Da�&DbQ
2�ud7��#^m��k� D[FncQ�c��as��dQ�cSi#f�C`eQ�cfQ^m	�>��������cm���gQ�-hQ^miQ�cu��	-�bř&DG�jQ�cc�})\ms{�e��	g�\m���<ִ��b�\mkQd~c�]����o&<|��k���t\lQ�c�\mގ)��fw�
�cӃ�{\m��imQ�c<|���onQHd���o���o��i�۷����pQ�b&�hqQ\mrQ�k3�o(�c+\m�<|�DI"Ł<DsQ�cj�<DtQ<|u��i�\mQ<|��q�'���c���p��i�<|	_��<Dv��i�w}5��)jp�9x���۾�w��i��<D��x�8
�b�Em��ax��iy��i,�<DzQ]?Em�5\m{QEm���o|��i:	�<�ȋ@+}Q�c)
4Em�����"����~Q�cP����}��}Q�c5n_�}6�"�Q�c�=n�}�\mA�c�d~���\m/����i�+$|,�<D���}���Ԁ��QSɼ/\mXW1�Qd~8��dEm���i���{�Em	��{�Q�'Ё�of�'2O<|�Y��$|��ae՚Kv�$|��_��߄Q�'G��y�Q�-d��id�'��$D���/$|���iD)�'i�<D
�^9�<D5Y���=��^9���}���b��݉�ae�QEmy$|{8�'�Q�-���ae��9�Q]��i��^9��'�-9��4H6$|�
�'!Em{@�񂶣5��i�QEm	
��$D���2Em��A$��'gEm	��� z��9��Y
��Q=n�Jd.�-���큌-��^9̓aes���7�-n��ae۟f*���-��}�>ɩ�ae"�^9z�m#���}�Q$|g���h�'
c1��ae~��9h�`��o�aen�x0��-���e�����ae��e�u(���+�A$���/�3�'�3$|��/��m#���o�Q+Dm�Da��&ִ��$D�Q�'��/�Q�'�'��$D��{�:�=�-��$D�Q�y�oәQ�y�Q�-���������v���y93Ò�F���jS�k��>)���3P@"=��k?�jFD��>>dw��ή.�y7�F�4�Ȁ�}ل��%��k��9w�Q�uI+j0�i�@"k���DD��[9�Q�e�5<u􍫣����c� m���4�jd���7��L���_=+ܜQ�io�oANd(?�DD�Q�y\d��k���i�'G9�ydw��a�{�Qdw���6Ad�Q�kp�۳=�DD���ņ����o�����'��_�!DD�@+J&�8/����yϤJm��c���b�ݠQ�{�Hd�6�-�ЩcS��>���cW�y��p�[�S���Jm������c�Q�y�ѩc���E���+t��c�$�{D��%��+��y��ۥѻc
��<�}�'�
��Q�y
IקQ�?�'�����}��]��4�)�����o{���d�^���+��o���c�Cee�QDDŐ��yt��+̩c�����EdJ�Y3��{�f�Nd;�˹�c-��>D ���i��ag�%���Ֆh�Q�}���|<�}�ѩc{��q��������g���cJ�y�тk��]�a�+��Q�k����p���%�Jm�
۩��Մ�ݮў-la1m��ˁ�ں�-
�a����ee�Q)D(���ȸ5�o\�&�ѩcT9�d�(����o�ѩcH6�-L��ў-�)D ��o�����')D�c����=)D�ѩc�8K���$��-�)D�ѩc-��bӃ�ot+)Dt�i~�ѻc��o�ў-�)|ȏ�޷Q)D��i~f��ob�;|�)D� 뀦>S�)|�Q)D�::�;D�?�Q)D�ў-&�Bv���>��-���b�;|���/��)|���z��p���Cdp)D� W�cev:գ4�Cd�g6{zd�g_��/��)|�;|�Cd��o?+����)|t۾��o��cee�Cd�i���oꯞ-��ceғ)|�����-��Cdf�,�����u0�;|��'���y�)|:�����ce��}��CdL)D��Cd��;|���/��Q)D9{}?<n��Cde��@��ce*�=ɣ�Ѐ$)D(�j�Q)D3��9�3��۔;)D�he��Cd��b=�he���?p��o��׿<n�!�j��)|��CdKF���)|s?0�i�<n�&�c+��atۚ�-��c��he<n��;|=���Ё�� Q�)|�Q�cW}\��oL�)|��=��.o&��ce�Q�c�y���Cd.�he��9��Qm~��Cd[�heG�$z@+�o�Q�c��ceoߣm��f9��y�cJ�[�Q�b��ce�Q�cy�he*��� r�Cd��o�Q�{j����H�'��c�Q�c���a�D�i���m~�z���b��%=X��х-����%�s;�����;�c������Ё�����r��D���a��Zmam~��{~�&�R�t<n	 D��a���:|m�al��݋I���he�{~v�#��{*
�b���|�Q�c��!	��5��cX{MT�heݝf|X5S�� �
�c֔F�׀he"�-H�c�Fv5�c���|/�4��V�a�c��o��oJ��-�����ieFv��1-|��5o��o���k��--|oo�t��Q-|�Qm~���|��C�Q�-�Q-|�Ѣc)�'6L-|����u�)��'���z����-|z�X"2�b~�Q�'�� ���$H�>��-D��x�6��o����J��Gd)��g��?D�	-|��?D��i�	��2-|Gd*�?D4�\m��N�m(����<-|������:��/s�-D�;�� zI-|=���Q-|A�'$��/�)�f	f��/ge�c�u��|����?D��>�FQ{�cv��-D�Q�'����at�-D��?D���c���1��'�Ѣcei�
IW�?D�g���J�e��Q�'�
���-D��?D���(Y�aӂ�A�Q�'��"|�Q-|7�CَGd�
���Q�'[���e	�aY�u)��gǡ@d�
�@dg�'�Q�a~�	)�=����'gf�'!H���l�-|��@d�Q-|�B�'��Zd�!2��?D�;������oQ�-D�Q�'eE�a�����?D3���Ȁ(��L$Gd,�-D��?D��o@�������QX��QGdYI"���'��-DÄ�x_�l)I"�a12��@d�Q�?s����a��Y���q�2�S?�'.�@d��w?�>o2=�@d�׀
3�>)I"a�d��@d���x�Q�a=��I"��Cm��oו[v����	�'�Q�am��%���k�ѥ%��>�	�a�z�Q�'e�b
�{�2�c��%��@d��k�"�b��@d���Q�b��{���%���(�ѻj��>�"|r�@dR�b{�����'��z��k�j�J��%/'�b��"|B��'QW�R�b��aI�@d(���ҥ%D��i(��j�ť%R�k���xa��-	��Կ�{�
�%����I"k�bI7�{A
$ҥ%�;�l���\�У	�W-ҥ%9�w�9�7��c��"g�?|��I���c�"g�p��(�>�j����4��Jdc���Jd�g�;Ƣ'!)�I�b�g�je�f*�i���|��Jd'�Jd�*�Q�f��:
����\	��Q>��%ҥ%�Jd�$�b�f	 a��k��I�s�2@	�Xd��%����I�Y�{+�f=G�i�	�c
ҥ%�	 ��fؕ����%R�b�{R�i�����%
R�c�+ڂ�Gm�'l�Jdh
Ca\P�b�C٩���o��>m��'��{�iR DZ�Cv�&����)z��o�Cv�&|&�Jd�CvX�z�)�u$���`�Cv+�f݀�'s����'��'���7�,�Cv�u$H��'j��	A�u$R D����o���R D��'�u$Q�Jd�Σ���	\�&DJ�Cv�&De D�,2D�I���o��Cvo���`�&DC��'L�&D.��'�I�o D�&D�f6��b�&Dw�e��4 D�u$_�Xd5��)���
��b���5��&D��{lf1�&D���R	 ���-$51���'�P���&D%��/R9Do�2C zރ7���bq�X���9V��`���b�i*��f�#�&D$�f!�:�&D���b�Kv��'�Cv���u�9D ҂b!�Cv"��'mĭh��'Ã�'���')�u$9Dh D��m#�-zi*��58�I+�u$��p�CvŁm#{�g��u$���ί�m#92D�V�#�&DV���v1� D�m#$҂b{C���:�&DQ��>�7�a,��c����@�fւ8���cS���ۉ�$�a���b�W "�&D��M"~�&D�P�c��fB�M"��4�&D�ʂb���bx��%�f#��b&R9D.Nd�Ƃb�f7�j'RNd��aؚ4�ĉ�coNd׉���f&g(��i��m#�ܾ��o<��Nd��a+f)R9D(���*RNd+R��,R9Dd��i��fE":��-RNd.R�Y9���xΣ(���uD�a��":��i/R�5����H'ٕ0��i�Nd��m#�q1�Nd��%h��܎��c,�m#t\d�E",�>C��iZΣ1��i2��z=�F�9��i=���:Nd
I�|3�f��i��}~��Xm�V=��e0Nd4�fGv5Бd��L��c5RNd���b��ܾHd6ҋb���jY��7�f���h�
Σ剙bQ�V|��b���h8R�cf�"Rʢ�r���Y&C2�Σ肋b5��������Z
OvރD]�	��Nd 
m89R\d���:RNd��z��avv~�Gv.$|�;Rne<ҫc
م�܄�X=Rne��S$N�f�&Nd��zo�o�Ƙ<��h>R$|��c?R\d���@ҋbo��o�_A��i$Cm��%7?|?��i0$|���o��{B�Hd��$DCR�b���5�$D�I���¢Ԃ��<� D�$DER�kv�=F� ���V$|F�j.G�&r����Gvv���-G�� �f#�'����&j��Hҫc�� ���n݋۽�¢I� d�Ɓr��{JY�Jҋbk� ���쁙bg�'ŋ�c��؁�E�)�'��c��{KҋbU!TT���c��'LR�ea6�'��h� Xf�@�'܄�h&Σ�
 \�'��6�(�NmM�+|NR�'Ԁ�dh� O��Q)�f(��5�]�`���PR�'€��d�����t�g��+|v;f�W�'����fi�!n�'� $|�5�b���QR"DRR�cr���f�SR"D� �'�AΣT� ��'݀$D��IdH�_"�����'�"D
	��	�۬�#[U� ǂ#�f"D�D�i�`*v��V� �NmWR�&��cXR�i��C��>&�?Y� ZR"D��"|�bw����E�i�� [R�c��foZ�Jh��-.Nm\R"D��"|]��+����I�������\m��+<�"|�G�i2<��\�'^R"D_�"|z�i`"D`RNm��bڋI+\�'�S�Yf�d~��'aRK�b�Zm>�{��'��_"X�wU�"|M.\m�f��"|z�ic�IddR�b\m�#f�;�{"D��z��腽p���[��"|�����c�Nm)�+D��"||�9�<�bc��"Y�_"�Zd�"D�G�beһcfR"D��c{�l�{���c�2gR�{D��hR"DM��c{���!iR"D�2��&jһckR"D\�d��!��D�ڸ�c�
�c
\mQ�SX�r�<�"|k�2�"|�cMlR7�o�2��2����m�"|�&��{5x1�{~�'��"|nһcu�2���~�"|8�{~�Σ����9S�J���"|7��b��a8��g���cj�΀NmoR�{ғ{~s�2pҢc���cf6�{m��0/�{qR�c��̴�Kb�`���Շ�b������m�a^	�cj�%h��b�cj2z��܋B�{*�a��g���ΣrR�{*�닶|)DcJ���,./|��,k��� >�2��۟�c���s�b~+�d��v)D��So�2]3	 ���&�
	 ,�Nm��tRMd/�b~Ռ�cu�2J䣎
	 vR)Dw�{~���&(J��)|:ף�2xR)Dʈ�����y$UdO��Z�1�e;ue��{~hΣh)D�/Dhue���a��m�����yR)D	��a���'�	(J��3Md-Udz�)|{��'P	 �)||R�-Q	:�}R&|~��'�'Ud���aUdk���;|���'��9	ue��g��'�
ף��$]3	 ˁ�X�Ɓ�;|_��	�-N��a��'5�)|Ȁ	�RUdII&|V��a����=�R�.�kۄR�>���a�Σ{�̴)DZ�W���a�R�k�
�.��f�=�#�J�f�NNv���'�R�>�b��ף���a	 �R��:�/$�Nv��4�zej���&DƁ[�=ݸ��&D��zeWUd��&D�	 (J���3%��x�	 \�;|��)|��8##�.o���J����a��&D��ze� ��ze�/�c<��a��>��4Udn� ��-��&D7��a���'��ze.�;|4��?Dz�a-��'���'e��玧ze�ҩc��ze������a��gt�!*ۇ�6�7)�/��גr�]����d��	=��c]�ze	�c���Y��ze���닡ok��c�]��Fah-&|�*A��&D��/k	f�
�>�&�/( Z�R�>�&D�R�>���c?	{–	ף��>��;O��{�-z�����4�ze�{���&D���lNv��ze��&D�ǩc����fZd��D��!��&D	%ۗ��OD�gG�ze�P�z*���i~��zen���
��5��&��{���{Ȭ�x2��ז{}�Ңj��{
�l�i8��ze<�{���j��ze��-zŜ�{w�-z�zev��D��d�z<����ףc��{�t��%J�Xd�R-|e��炂xe��Xd���{݀�cÀ
o�	Ӂ�{t����	�XdD�%��d����{�R�a)J����xe(J�s�Xd
դȀג�R-|�a��{�9�	��C��{p�U2��-D��?D�һj�`�R-|2�(n���{��i~(�?D.�Xd���{,�����i(J���Xd�飥�-D�R�a���-|<��i{�
 Ł}#��iH�-D5�?D���|��/��-D�
 ��i���d�V�
���8��-D���{��i5�?D�W1|��*�Xd��b�RUm��?D)�z���jJ���x!���{�
 ӑ-D�ҢjG�'�W�EҢj���io����{e�и*�tI)�']~ed�@d��i��Xd�:ף��iw���9��"���bZ�����Vpy��b�����i �'�R�a��
 ��a��xe	���b$�Xdk��b�����'�	�J�"�7D�h�'�R-|���'�R�aV
HmuR+D�R�c�
�6��?Dw��Gж�-D��+����-DG�?D��bj��i�����i�R�c(����҂bEm~���i��x�Ԁ����?D.�
 V��i�k�e�-D��c&��W����+|��iû�'�D�c{�
 ��i�����HʎF�R[���')��8�2m~?��T��+ތ.�Мk�2��|6�^d�R�c��'���
�Hd��'i*׀e���ߵ8�b���k��մ��c~ף�m~\�+|:z埯@d,�{8�
a�gp��.�������@F�l�j���+��o����M���.�{���'K6�+ϡ�b�҉kԀ�Ȁ�����'��#��/.�N%�R|e����{͓��+Dy6\d�����R\dk��P���'~�F�;�&���'�ף���R\d�?zr�]�����+�R+Do�#m|e��gd�؁�\d��{��+|�=�5�|�j_����R�c��%h��`>�|e�R\dd���O��nG�+|��ܾ��5�+�`>�!�ij,m~�$5@9�ks���R\d�R�i腼��|eӂi!�R�i�R�i�R\d'
�����aB$)��u�R\d�R�i�R�i��u����]vM:\db+�A%�	ւ`>�R\dI�-�]vȃ2��R\d9)�6�^dYr�,�i��u���Z
�i�u�]v����Y���z'|�g!�
g���i���1�r��Ҽ�O�i�
�\d���&�ߣ��5�R\d,
 D%�b��a,Ņ�1�2<]|e>��׆��oQs���a,��c���&5_}�bB��aU|e D �R�&�R�bj���R D\dl�bi}w�R D�f��R ݀Cv%�b���N����);�bՀr�R D΁ڣr	�C��a,h���RVd	Ddi�"g�҂k�Vd	�b�Dd�RVd . D��Y"�oV2De�iZ����&;��Q�7u������-Vd*�i�Ree�R�k�۠9D���-����~1��Wd�:Uv�����'<2zjӣ
��D� �R9D��ߣ[����-�9D��u�:�>���ce��Ղ�/Ё�D��/�	��R�k�R9D�R�>��ڣ���p9D�RDd��6�O �RVdo�҃y&�@9h!Vd\�/D+�b
�/�R�>��.���>p���Ԁ�o�UB�/D�(}R�:�/D��٫�b�RVd;�b�&�/�&� �d7��/DȀ�5��y�Ro� �R ���)���.��l��b$�ϣ%�/D����]�ue|��>������&f��-o���҃yW��ʻ�/D�R��`�m�R��׀/D,�*#�[���*���+p��yi��I�+9D��g����C��U��/|��5��kl��is�_"b�c�>>�ߣȀ��Q�s�x��e���?���iՀ�w\mjXL4�(�����/D��Xv�R�>�҄?>��i;�/D�/���i�5�c�R�>���i+.ӣ�R�b�>b��?���ba�b��i�R�>	�R�`y��?$Dd>�Kd�J���/DJТJ�9�⽃U m��(���i��`#��������c��9D�����{��c~����iS�b>Zd���k���?�|���}J�K���i���l�b��Xv���iƁ�SރPòߝ�mJ�&���L&`���	�c�
&�:�CmS\mS�cӅ--$|r��+�+�U8{��-�7�ӄ?��+S\mt��!'��G�&��iӅ-����i9��>;��;\m(J����-2��Ӆ-���i���?sC	S$|B�?y��if_�o$�c
ӄ?*�$��o�J��i��H?��?S�?�)c�)S�cY��?
Ӣc:�?S�b�h��dA�cj�B�cӅ-S�cp$|�}\�?��}m��,S�ch$|U�b~9�;e��<�ڣb�nӢc&Od���cJ�M� =|f�}h�q�eٷ��A'��Ӣc��,Ӆ-���0Ӆ--��
��i��{������?$�ߣ���@�S�?�������&����Ӆ-Ӂy�)p�U2S�-偂�HmS6|z�-��8��ߣS�-y��Z���NS�-	�����\x<���f'�'�B�?��Id��#��F�D�'���-j�CmS��b���A
ZP+��/S�'���s)�_+ ӭ>��-"�O���?��kd`���>Ԁ�%L�Id��&!S�?�k"�Id#ӭ>����kd�Id���羂�gz�`#$S�k�3���g��h�Id��%S�>��[�Id��>	�F��\v(�ߣ��>'ܣ�4�$��%r�'Պڣh� �6�k=���9��9�e{�&�"|���מG\v2�Q���V��k'�����>��"|�4Z
\v�F�'�X���D�����o�#���(�s�o�Id;�'(S�>h�58�Id��k�@��?�:�ߣ�
)o�+
�#)�"|�?�>" ���>���b�>h�-\x�}�ڶ��'\�Id��։J���b���>9%���+�D&�y\�d*{��u����?�摭>���A*S�k:�{+ӭ>��X�	�k{�%T!��l��>��L�ze��k
��,ӫ�~���n���^X��:��d���c?��k�i>-S�>
\v�?�����f|��.S�/r�]�����/S�>D�����cp	�>H�d*��ߣ�޴�f��\v}ڣ��(0S�Y�ˋ"|�i>����݁��u��>fe�
[��gӣoд͚ߣ��i>q���*��-}�i>�&�
�2���f�t�oȀ�:�i#��{l����	U�À��Ɓ�{�֣j�׀�݂�7�Q�3å��k?����j��::���r/	d��ٖ�{�Ţ���ݑ)D����3'J�b(�!'���1SMd��b� '
���z����)D���hMdW�f2S�b3S)DZ=������@�i>�:)D�֣�@�i4�f5S)D3��{O�9��f��
o=b��O�5�$�1)Dŋ)|�X��Y�
Md?�ߨE�L(�bϟ�i%��?6Ӌk\CMd7�)|�KMd}(���;|�*AI)D��i��!`dMd��&�)|聇-8�)|�޸9��iXMd:��i޴;Ӈ-��F<�;| ���Aw壷)|+��-�#���9=�)|���wŋky��'���a>��i���g��'�裣 eӣ?�;|���x��@�)|9���fY�D� @��aZ�)|���Q��SAӇ-�ϣ1��-d�Bd(JMd�9�?	�-9 MdX��B�&DC�&DĀ�()	 ��ؒ{�&D� ��WTD�f�ES�bF�&D����9 Md��Gӌ-HS	 Z�&DIӌ-�
o��i?�&D��`JS�cN�Kӌ-:��	 ���iLӋk�i*y > MS9DN�&DO��i5��-�)|Pӌ-�����9DQS9D�Ќ-�i*_���ᇌ-i9Dψ)|R��i�)9D��&DS�;|��)|��9�'���c��ǩ���-�-���'o��yTS9DUӇ-t��k���'����h��'/:�&2���i*���kVӻc� �?�:d�I�i*���]WS9DX�&D@��cYS 9D0��k2<n9���� .�{p�9|��b�&�{��&D���=������wX�(���R�x��&D.��Dt�{���-���k8��g��,,i*ZS9D�٢V
2<3�8
�z��[S9D�"�d�+9D	��Ox+fO���\ӌ-��{~�-���c7'�����zp*9Dn��D���3�-�z]ӌ-O�{��Σ������^ӌ-,i*�H�z��-�n�+_SY��a��{�f�i*�����>`S�{%`t��k���y�$-|rHg���k	�aS�-�b8{-|bS�iHH�>F�i	�l)����>a�gxցNmcӉkC!�{�
`dS�i���v��+����	�g��c������,n��De�?Df�-Dg�?�����-|gS�i�$�i5
-|w��S�-D��R�?.��Do�+P�{~KA�{�i�0-|��yS5�{Q���ڣhS�ii�?Dj�-Df"�{{-|+�-D�2�c���>�,�c3�?e���Ɯ�?Dk�-D�����8��l�c~��e$�t��f2�����mS�>��-n�-D����=s�o�-Dp�?D{�Cm����C�o�#���A�A腣����;Z�-D���߬���B��k	�ԙCm3�(n��c~�٢�Cm�Cm	�!�tdqS�i�I�r�
 r�i���~�G�sS�i����d��r �c~tS�iuS)��Cm���cX-|�5S��vS�i%�V�
�me}��pnk5��@d��A
�+��kX�k'�-��'�"�9=|���k>Xd€�*v:�kԂi#wS+DW�>�k~kXmkR$k��&
�>k�c~���G����n�?D�����k'�㴑�c~:�裒��	�6�x�=D������ ����FkR$5��c~��>��Cm���8y�.皀Cm����k��=DoR$��:����k�cQ�kb��8z�=D���k_�'9��Lk��������ƒ�o™=D�:'m��k�ie�I�FP=���k��}g�>{Ӣjj�h��'|S�{�Ȃk�?��&��?}S"D~S�{���-��'̣���S"D�Ӣj�S"D�����=D�O�k�S"D;̣�M�!nه�ݞ�?m�{J��D���a;"D�.�{��k�S"D[�����a�S�{��?���1��i���i�d�%��R$L�k��a�+|��=D�S"D�,���>[8�{|
4Gb�E{���f8��=D�	壊ӎ-kR$���iW�"|9�����z��"|D�J���a��2"�>m����գ	*�P5"Dv��=D���i�S"D���	*�"|E�i��"|��ݑS�-��{.�r>�"D��!nh3�{��-w��I���{��{���i�f8݀�?��{��!n=�%��"D:��$���-"D��"|��{�j��"D�Cل�i�}~�
����{a{�	ەS D4��i1�i̜�i߁�-G��-�S D
��i���-� D��a4�j�g����{��i�ӏ?q�"|R[�D D�-��"|��ň��-k>'� D���i��f8���i��iu���m D9��<_�k�ۢ��?� |�S D�ӂb�*�<7DdȀ��T��Ԁң2�����-�����i���io��'�S Dzm#�?��}~�S Dd*�ٳ�4V) D��ޣ�g~g��{Z��#g)z3��&�*�s�-���{	*��g~���{€ݢ�SGm��݊��{n�-4�y��
}��-�Gm뀄bG&�y�4���bw��šo���{����J��,�Mb[��%��'
�Ȧ�f��	 *
���ՂCv8�5ZDY��e�0�yB>'g���,'Ɏ/D��/D�S�-�ӏ?́me<ۯ{�/DL�������*���/D���� �S]d�R��/D���/���>a�Av��/�	`~Q���S D*���Av���	7炂*�����Y��z���Fn��-co�������S�i����2�i�/Dw/�+�Av�����/��/D��i��i|�J2��b�,�iБ�Ă/D�S�z�S�i����(�ie��a�&Ϻ#�i��'��&D��裵�Av�;�L�/��zm"�ijUT���85���J��,�3�z�S�iZ
�y5
�i��&D�S�z�S�i���iҫ&Du�a�5�z�S�/
��i9�i#��g�	 Ѫ&D���'(�zT�#��i�_��/D�	 :�-��&D)�*��p���$�&Dd�z��'��i��?Y�/D���}��iX�ƽS�>ˆ/D�uka��b^��i�'��&D|����?>�>���i$7�{��4SG#�i�z��/���h��8U�Jm&|�բ��Avk>�>��� �a�*���i�S�i
��{�ӄ���&DY=����`,&|B�iQ�zo)�H�i��in�&D(����S�aX����S$|
ff$7��&DaY�#�b�S�>��&Dg$|���i�S���S�><v>o��iG��i���.$|�S`8��8��&DТX��`8Q����ib��{b5��Ǔ��i9��m}g������i��	��fb�S$|��8o��_��'`�&v�����CQ��z���v��ٛ�j��$D�Y�kXmb�{�$|V$|G8�{m$�>��j�Ѵ���+�$D�/��A�>u�=��(n[9U��S�>�����z�S�>����Ng���(L	�b�S�>�q�Ղ�{�S�>��-�@v�!�b��Y������&�l*щ(nĂ�/(�ga�td���@�9X1��b%(��wێ�T/�`����z�)$|(���o2�/�b���%��ۈ�b�S`8%�bG�&�A���nZ��+)�[d9���e��;��/�Y���$�����Q�R�����/��㎀�'"��/��bE�b�6���	o�S�cD�`�SFd��[d���{��S�k[�[do�`��&#�[d�����S�iЊ[d�N$R��i��� �S�'O�i��䣳��S�i�$W��SFd�S�'��̸�S�iy�F��&�Fdډ�'��`�S�c�)���S�-��`X����(n5�=�	����Fd#�b�(n�S�'w���4��-�!�b���b���/r�i�S Ł�/ރ{}���l*�`Ps�r���X��S �S+D�ˣ�S+D�Id��[d��`�\��ܜ �o�+d�B	�i��{o��S�&%�����h���/3+D��{D) &0�|��[dv ��lŁ�/H�{'+D���/�(��S+D)���������+|:ҿ>���z>�N$��i*Fd'	+D�,�b�S�i�
F�{F�+DH�{�S t��cu2L�2��S ���>��N$�S�i��-k�\$"�+|�S�c��iN��l�Nm�S�'�g������-gd�f-�P�&�������z�S�-�S ���Nm%��zm}�Y$`j��>���`@��z:�[�\$��>W�d^͌���zo�v�C���-�+D���z�S�-]�f��{���>��Y���r�j�['�-�S�-�+Dɀfȃ{�A�-�S�>k�\$�ӭ>"��>��f�y`�S+D�S�-��?�S�y�S+D�Xm��>�Hv�4]�fT�-�� ��-;�y]�f�+|��z�3'.�-�ݸ��zT�iT�-�\$T�ia"�%U��>���7,
)D<��MC�-�>;�-(��+���@o�\$�2)D.����
�?�K�8T�?T�-5)DT�iT�)|�`���zT)DT�-ۚ�	TMd�<�-�$��
��a�fDz�a�!�?]�f��k��a��)|��yԅ�
T)DMd���'g�fT�-��)|��T�y���aT�-C�>*�2T�-T�i�
:�T�kw6�3�T�>�J@��	�U)D�-T�>T�-'ˣ����T�>�;|T�-�`j���܁�-��a�)|&�'34�?m��@��aȗZ��6l��ah�;|��h��b;ˣ�iq��}_dLMd�ˣԘ-�Hv.����i���y9�i��a*���%�3�iT)D&��a�\m�ܘ�����a��kc��� T�i9��]���-!T)D��"T�i��� #T9D��id��$T9Dؕ)|��|��y��-�69Dd"n�y�%T9D6�-���a*/|�ˣȀ�Q��y&T9D'Ԟ-S��a�N�z��� w���Z��a�Ǵ샚y�ˣ!�)|���k��� (��a��Q�/D�ݣ)�/D������k@�*T�z%��c�Fm�ˣ+T�z<�/D!��׎��k�[�:7F�),Ԟ--Ԟ-�z����k���G�/Dݥ�cp��_.ԩc�k��z��`/�/D;(*V�Kdk{�lf!V~�#��0�/D�#@1ԛk2Ԟ-3�/D�[��âjǞ-�� �j�c<��z��k4Ԣj�f:
ˣ��/D���k5Ԟ-���c6�Kdg�/D��`���-�`*�Aj9D��p+9Do��c�� 9{}�����z��i7Ԟ-�J9D����Y$�ux��iY�6G�9D�/D8�Kd��l��>�����i9�/D{`���il��.:�/Dc��-oˣ;T�zZ
-|<Tf���i=�/D>T�c=�/�/D*��i�-|OG�b?Ԝ-%`��>z�`��/D�i8]�a@T-|���i��/Dl��-����/D������AT-|BTi8<��cۃӸ�ٗC�-D5�?DDTi8倜-ET-|>�a��F��i���-Ă-D��?DGTu~L)ʈi8�c���H�-DIT}~4�a��^"�h~��3`��a�����p�J�-D��?DKT}~�}~:7�LTu~F��}~=C�|DO��iݬ�M�-Db�?DB�-|���N�-D;�OT�aPT;�9��i7�R�Ȁ��O� �����+,�-D!�dQ�?D;����i���-RT$gM��{*�����i��Ќ`���ƫ�^���c�=�x�	[�j`SԢc�i8ˣT�-D�G�L��c��%UT�a�����as��-V�`Jɨ��'O&���cWT}~	&XԢc��y��"=YT�aZT-|[Ԣc��%�Od����\T�c�&�8�-D����d�?DS�b�P���?D���G=|]TOds�h�q�^Ԣc_T�'��?D����K��-�'� �ݣ�
Ԕ�# l��$��'2�c`�-D9��\+�&aT �����ybS
�cF�'#ˣ��t{��F�$g[T�b� ��?���b�=D
B���n�.�bT�'<8�l$g��b*�cԢcdT <8;�>��k�#�'c�o�Odh��R yŢceT;���m��k��i]�z��c���ˣ���c�Fa�"�i� k�B�L�i���'f��4�Ƴ��x(��+�� 3��kO�zm��cj�Cd�/�i���'gT�id�ָhT�/iT�zjT�i��oo�`ȗ�kT�i��"|��
f��`ʋ�Q�E���'lT�'���amԀbo��c-�"|*���nԀb:ˣT�a5"�ih: w2Od���c7H�zoԀb��'v��a<�pT�c��"|qT�irT�'
?�0 �?����asT�i(���&p�i�)tT�'uT?���'vT�h �%�2wԠc���bc����
����&.�ixT�>���y��azT�i޼f8�?��a�?�{T�i��"z���l��ip�ij��Z&
��%|T�i�`}T�id���`��̽�m8~�`d�⁀�Xm!��d��m8T�>�"D
?��ԥ%�ˣ�>.�i+S�i��{t�z�T�i�T�cGB�"�>��c<��'-�c��Q�9��D��>��m8��g��T�c��Ă�b����遾wJۯ��"|�3ÄT�{�c�, D>�2_C�(��k����	�c=��2�?��c���%ڕF�L����%Gd���~��C�?�� D`��vc|T�i��ck�Xm�ԥ%
?��T;�S	�i�T DX�?����z
G�����?�<8;�! D��`��c�/�6x�+��i�TdP�@��T�i+ D69]d{��cI��	���T�>d Dԣ��i���>��L�+�]d��Y��h�۾�����c��%�Bm���.��`V�u�Î(uu�u��m8�ԥ%oY$�T�c(>'�ԣ2�k��m8G D��c�T�%��E�O��fkY$���=7 D���ㄅ�'�ԥ%Y��7��'��c������i�A�c�-����>z��'Y: D�Hxr����'i7���&���>]?�c��Cd	��'�� �T�c-`h���ۯ��'�	�z�Ҵr��i�
ԣG��'��cɃ�{���&3�'N9�cȀۀ�}Ȇ4@����z�T�c���k���'*�`�|~냮Ȩ
\m�)�&D]��|��UK.�`�T�c�T�'o�����&DG�a	�Bd�T�zP�c��Y��'q�4oY$္>&� .�{�&D�T�c�!X�=)��&D���Q�c�T�a�T9Dv
�b��&D��&ه�'���/��&DkY$���'�9D�O��|~�T9DX��ġ�&D&\m���
�YQ��i� �T9D%�xA�i!p�i��>9D��U �&���i��&D�T9D�����'���'��i<)�c�ۯ	�cܜ�&�'ۯ���:�c�����&�T`���-�=ۨ���Q��*N�b�T�'��+�T�c&9Dh)h&|P9D�(�c���>�T).��z��%�Xo���:)D&�T$|g|~	��>	�&D��Î>h��&DLˣQ��z��3'��{]��>
\m�i��&D�\m[r���Bdt�&D�T9D�ԣR`8���z�:9D�&D%�`O��OQ����y"��kw�t�$Dg횭>�T9D�$D���hv6|��	�9W֙�Ɨۊ��>�`�Z"UN$|ޓf�9Dˣ��+J����T9D�̢2{,Ɓs�w�U�jY���׀qf��_�K�f�:n8�Fm~�Պ�\$�Y��T5�<�$DJۯ�i#�ݣ�`8��$D~Y�g�*�$D�i��z	�-�$D����B�d��φ�Z�@̣!��z�T�i��+h�$D��:nJۯ<�͸��>��ik���G�5S�` ��x|�E����c���>���?�$|������>���a�,$|�p�F��a���`0�ڀ��+W��`�� 9�P�!-g�+z~����a���(��:Fy���Fm:��c�ף!>$|���a���cʉo���a��k��[d�����cĂ�c(�� ���a����� ����ˋ�c)FdA��`�"ˣ	������-g���al�k��� ��[d耦��$D�ԫc���H�����h��c��$D5���,`#����a(�_�� �TX"�T�k	�b�$��b��:n΁����	o��`8��j��nȀ�����'�N �ˣ[
Fd�T�k{��2�i���&݀�'ه�a��0��Ɓ{]�s���aˋ�cyٕ���`��[d��G vԣ��"%��c���I8ˣ�`�� �	�z���a��:����Hx�ԫc@��`���&D�ۀ	&�ԫc���a�`���T�z
��`��{l'nˣ���&���'��Yd��=Dj��-=��`��0���cXN�zy���;��c�Yd�ԁbIFe�z3Щc{��r��ԩc&��br�z��n���z5�YdW#f�T\v�ԁb�Ś�ԩc��Yd����Tf#g���b���{3���5ԣ�ԩc��`��:���z�Կ>���by��c5��ԁbh��z���c!�\m�Z�Cm=�����YȀ���b��z5�Yd��zd��i�����ԩc���z���b���߂��ԁb.�{���&�ԩc����R�ϣ��O���%�� 
�&��i���b��վ�;̳=|>�"|�T[���jC(�&��Yd��;F��"|�	f����A{,�T�zp�&�::�T)D�T�z�"|I5Z�)D���cګJd��	
�&��Ydm�=D���zR#��z����k{�c�Yd���i��=�$��@���c��!�V8�Yd�T)DȺ�b��Ӝ9�m�"|�)D*h��;|h��z��)|�ԁb�T)D���� Md=���T)D뇕_V����)|���{�;D9	�j�t_8
)Dg�iցyb�)|��̢�6)DȀ�^�Zv��;|��)|�"D�vq�6�x!
?��T�Yj�`��2�����x8�>��%��&�)DÃ;|�� Ʃ"|��)|���L����&
�f�*
o��<���|���€����d�"|��)|����)|�ԏb��ۖ�*h��߅���"�������i���c��c����;���
)D�T���i�T)Ḍ�cee�F�ք�-����oK��djo?���{D�T)D�+z|)D�4�>`�?�)D�ce�Ԣc��i��w�46�T)D�ܣd�6�T]dz�-�G���T]d@�`�:���2�Y�c��i��ډ�T]d9ZQ���c��ۘ�����)|�A�'9�c�T�'�T�b]d��ex�	̣�"]d��b{��$u)�'v���;|)�bϊ��9�Իc=���T�bT�	�F�'��f�T�ct
���Y���L�c��Kd���V)�'�'5���I�c@�+.��p���z���u���Av�b�лc@0�c�T �T�'�T�co�+\����oU ��bA��cբc����c��'՛b	 ��U�iU�'5 �B����b��A�Av��$
�Hx*�iه�i�>��K�d�M]d-�-v��%���n_�}�
ۯ �Z��+)���_ۯ�&D5�c�'�J��j�;]d�$�c,
�-v�e�x@�m��i�'�B�i��	��&D�^vJ�&D<�ܣ�0z�̣�	�iU�z�&Dw+ܑ�-v�-��n�&D%K�b	U-|Oۯ
�&D��'āϣo*h�-|h5@�5�ԣ�C"T�Av��Kd��{9i8����X$+�d�&DP��ݟ-|_ۯ�
-|D�	��#�i8Q���&D�����-DC".�~�6�?D�i8�-Dn0z~� 
Ui8�8�z@�ɢ���i8x��J�)���Ui8�۫	)-|�-D��&D$�h7�Bd�-Dṿ�0zV�?D�����?D�i8�;��:�z�C�	�Bd,�-D�?D����-|�&DUi8ތ-Dù���
�����&Dp1h���%_���_�-D��-D
�� e�?D7`8�&��5_ۯ��e��Я�h�-D<}����Ufg��x���_{i8�� �-D-|�8i8�D�-D`�J��a�C"9��iUi8Ոb�i8�
�f�� j1h}b�Sx�
i8
C"Ոbr�@d.-|-�o��lՈbUi8
�xU�k*h��@dm�?D��	i�$v�$��-D Ui8�r,i8��ݾ|D
��ܔԣܒ��!Ոb�f�=�$"Uf�5�#�@d{@�$ՈbȀB�I"��?D��$%U�k���b̈%�%@�N��>&Ui#��'U�k�Od��x:�	�T�j2�շW(j���b���΂�$e�j��.��`8Д�'(U�$)��'� ބ2�A
uJ�毗�P����B*��'+Uf��$��a~,UFd��{�+z6`8#1h-��'4�ze��kY�a����.��'���A{��b�Fd���Qf<�/��'V��Yo��GLFdԀ��0U�a�*�a1UFd���ܷ�̢ٶ�bv���)�>h	Q�'f��'RFd)��ł�$Տ&“u,��uCʁa~@�ap̣�Fd2�oy��'3�o�Fd��y��'��}4�a��ܶ�$��a�a~b��kr,<��+��$5�ao
�8
	���cH�$6UFd=�+�����̢����7U�'�����k�����'�h��#hd�����	8��'@+D��>h���a-��'Ё2�Y�9UFd����2>�D�:��'�U &;UFdI&b nQ&0zFd"��'��\-Fd<�a=��'W�f8S��&��&>UFd�<�?�a~80z�F�m�a@�a��aA�m8��&'�	�D�XmBUFd��Ģ��f��z<�d��m8#j�Og�,هm8��p	��zrg�?��ǾgCU�`��m8V��DU Dg&�E�m8���F�a^�8. D*�g�X����5�GU Dʓm8Ցc�HU D^���m8�uxLDd&�I�m8OCnJU D�m8k���C�Cv��	���a�̣$�x9�Ψ D��5#"Dd��m8�2D�����)�DdKU D�Cm�����@��')� D� D�V,m�շ5� |� Dg2D�EmLU)��Υ�&K D�⣷��AȀ�	6"zr�����W�m8��8�4M��z�m8��-1=��<�Ģ�ۨЮ0z�y>�+|N�m80z��m8��'��<�)�����	O�m8P�f!�y>ՠ۸��G"i��Q�m8RU DS�m8�U;��Cv<��iT�m8UU D� |c)�v�շ� Dn�m8��"V��A+��$Q���WU D	����'q��Uh�Y��b���'� D�1hXU D_�}����Ȁ��dr��Dd���i/�)gY�'���cf}Q3��y>��$���ϋ |�<�c� ��iʇ D�&�bĆ�"��#�aZՍ$��'���"��&�;g��.[U�c���B�Hvy \� |v�:��&��a���v�$�.��4�M�Hvb7Y�d����9���5��o����*�c?�j���ㄩ�o��bN����� Ȁ��b=�&��+�bs5i�K�,�a)���]�&vD⣹�b6�y>�KgP�
�c<�b{�=��$��&7�z1/�a.��-��cX�.
��Z��p�z������&��> ��&^U�.���cဍ$��>y0�e���$���뀍$_U9D"	;`U�>ҕŮI�z䅭c��+%�c�#٣e�&�a�Kdb�Kd�����	}�g�Y���j"@�zD�Y�4��cU�id�Kdq�c��a�8h��cf��b��͢x�Kdy�cy�'�$j� &`<�ie�Kdc21#9D��=Ԁ�f�#ngU�jhU]d�����?7���	���5iU�.��Kd?$|����7]٣��Kd�zjU�.$|��i��z��SkU$|�Ƙی#n�4zl�Kd�4zm�z��&H��X�$|e�θJ���p���ʫ�����n�$Do�$D(�&#$|d��p�$DqU�z�$|9�
���r�$DsU$|��ft��z/$|3��&€Σu�$DZ��i'����vU$|ӁKdw�$Dk�5hx�$DyU�>$|�E_��<���zU�>{U�z|U�c�051�>}U�i'�$D�����&�x���>��>8h�΢~�$DȀ��Ȁ�	��8h�Kd*�D�m��D%�c��Kd=�$DN��p�+������&���zR�c��`j���5hhM$|
��&�6�:nv��/i�$D�$|B��&L)ʋ��>���׉�`G�#n�$|0�Y�2�:nd��<�ڢ��	�
$|�	�c�Id&�'%��^"��d���Ɓ���$D>���q0zҒo��$D_�Id
��n�U�&��$D���R�&|�i�����it���z=����$D�	٣�<�����x���z. _�Ŝ 6�c	����i�Wm� 聏bD�c���6*�;���A9P� ��c��c�1j*��S�U�cϚ�i*�ۆU�$�բc9�I�U o�h~"�cP�e�䀦>���cl�)�U�c��4�7 ��(t��%b,��ϋ)�U ���"��%ԟ�i���c�U Ss���)80z���o .�_J�t��r��Y��i+��iw��Q��i�~8��9�>�+[�)��)�'�U �UOd�٣�Od� f�	�+���cȀ�	��'�9���D��%D�)�U�cM�{{⦐U�$:	ˑU�$W��c�UaӃ�c�Od!�Y�Yd�G�♒�!��g��A>��	��U .��c�U)d�U�$�)�'��f<�g��Y8�f�&~l��co>�/�H�f��cN �U�io�5hp��c�U ��i��f�
�Q{q��	o��8ؙբc��f��b}��$��<hc�UOd
��cW�U�j;� !ٳ�>ϡf��c������i1'�"|�+���i#�f�Odz� ��f��_��}OdZ�	��"|׀���
f��fN�Vg}�&���-Od~$�i8�U�ic&?z���K�k��&��`�U�-D8h���Z�'"K���"|)D���a���&�ջc�թj)D� �Md�U�{�U)D��"|(Q)�U)D\�x*��b�
1h">�ΦU)D�V,7o聏b��f�Md��c�U)D���Yv�fm�	o��f���~@�i��O�9�_Md��<h��>k)D��)|��fQ�;|�)DJ��>[�f��)|��fQ)D�U�i��R��<h+�8ا
�=��$��;|O��(�>\���m0z݁8���UMd����}�:�$�/��;|�U�/�����>�)|*�9����qj�ĉm8{������֯�)|�"|��L"��2d��v���͢Y��8�1h\'I��at�`Nv+��a�����F���֢�ػ�΢���&�=z�)�_��-9��{�{v��%�?�@�m8���%��/��{�kIb	Md���(3)D��z�Nv��&��o�΢���}���U)D
�3!a�=Md\�͢K��GNvQ�)|6~8��{��9X�)Ă�$��j��;|>��׳�)|{�ʇ)D���b�8h�U]d<�)|�⣺��%ךm8	 o
���b���$��o%�<�)|%�.�	 ˁ�w�0z����h���.t0z	���{����U0z�U�b� ;fs��iB
o�0z���i\f
��oU	 *�4zl�&���&���'�(f	 P0z�թc�&�每�
o�r��	Em��b�՛k�0z���i��,9�U ���im��cf?	b��a�ۯ�)�%��a� �b�U⣛��i���$Ib�#�Y#��i�U�'���'*B
2d��'v��&c,:��c1 �'�,f��ao���Bd#�a�`%�i8m�&Do8hi�a�U�a��{.�C��N�j��{o�5h	�Bdì�j���{��G�U�a�F]d��{+�`{۫��Ÿ0z�ۗ�qg�m.0zu$�'��{�
o��aK�&Dm)�j��a��,��]��U�`���iY����թc��{g9D���i�{o��	���i�����{Z磾���Q8��<h� ע��ه�'mt>9��.��Bd
9D,
����{��?D���'���kK�؁-D�U9D�{c�����-D��?D�U�z�-|��i��)��8ؼ�o��+��-Db-|3��?��i�-D9�V��U�`-|U��i_��=��?D��i���ތ-D�U9D�)�{�5h�U�'��\��{��{��-��i���{z�-Do�?D��Bd���o��	��&Dqj���h~�&Dʉ?Dj�w��?D�=��աx�՝?^=�{��{C�?D��Bd�;`8��h냤�5�kY�@de��i�U�{��	��i�.nҀ?D]	-|�?.n���x�-|{`8J�a��{��u��m�*��b�U9DV�*#9D9��".n�՚bۃᣡR�k�8�{�,{`8��N�՚b8!���ch�Cd��c�=|���bD�?D3.n��@dw.n��S�?�i��-D�
�iw��s��b)����b
(�c���	����U�iY�
f�{��@d��͢��m?�{�U�ct�� 	��b��i�`�՚b��i���@�{�U�c��
f0z�U�idF�=�{���
R ���%t��*p��?�.�`��v`8����*��Q�2��U�{.����U��(�{�U �	΢��c��'�
 �U %`8��|���U R�i�8h� ��O���'X�i� �՚b�՚bk��'߁�i[�w�U.n�e|�
 6�jE�3SK��o�	 =v%�}��Xd��Ø�͚b���'�����Hmv����՚bK���\�&~�uC��c_��bb�
 �Q�$�U�i���Ւ�cv��;�_r��oa�&D��A�
f��F�s@�
�}�d��U r�]���i�U�$�U r�=��n�U�i��c���cȀ́��	B��D�"�U=|�R�z��=z��AB��D��<�4r�U X�����+z��N�W�iR�5b;�A>�XdB��D�/&|e�U8h�U�i���D¤�'�դc��	�U�i����D��
 ��'����)��ʫ��7��z���A�U�iv�%���n���DJ�f8L	����v�����+J�i�U�/聏b{8Fd�8\}�J�o�>!��i���DqCٯo=U+�}���i� D�&3��Yd�Vd�2D��i=C�I}��2���z�5 D�!�c��g���Fo�2n�����Ȁ�L)�`���ע�U�5������--�f��zD�b�2�Cf���{�8k DV�f��25��D5�{b�U DB��D��Y���D57�i D�8V�i;�ۢ���i�2DV D�f2D��DV D��i����(��D �bЂ�aa�i�Zv��Zvo΢��z3�W���V�i����/��Y�Zv��z��=D�Ydm�-�f�hƚ�f	�ZvDd��bo��4	�>��Yd�f��PV����Ydo�5h����Zv*
���c�
�DՂ�>����b��8:��z����k��{̑�A�9�V2Dd�6���{/�g�0�D	V�b@��$"��*
��hV�b���{D���h�2D�f���{��D
� ���
�D��h�?V  DV
�DB
�DV]d���{d7���{f��,��h��h� >�D�Ƨ,$}���Dt��h9ۉV
�Dψ�,V��
!�f��i���c���k
.)��h��zo�ȫ�	\� ��h]��{\��>�.�cOע��c��d}�RǦ>�����	�c�}�_�h�8� B$��i��c�Zv��id�Zv� �)��i����w�8��x��i� ��c{E"�]d���l���"���{���i}�� ��D��D����֨x���iV]d%�i80�Dj��h��{��k � Ł�$!V]dB
�D"��h����#��{�dw$��hy�8�%V�'&��{[��xA�i!�"]dg� '֍$Yf��'Wf���,���(� Ɗ�,)V�'*րk���i+��hb�'���,�]d,� ���cރzڳ��JR��j-��i��i��t��fЂ�iQ��{\}����岃�{�<h���d�Ca��j�
C�s)�'�c}]d��{�A$|.��D>�+����'/V�z6|��h���D��D�z���iG�i!,��8ر��io��D���&�g��}�0V]d�f1��i*$|2� 
أ���/��D�\m.��i{N�i��iM� ���j3� 4V$|��BdHZ"5V]d����X{f���׍����=}�6V�.���7V]d��:fۯ8V$|9V�i�ۢo��D(�'B]d�	5n8X�B��ct�%����.:V�i���4@;V5n3�i� �{h�k<V�'�5@�Y���$D�#f�&Oˡjh��=V�{��yi8�U��D��l�5nw�����A��D>�Cd��ۢ���{V��D?V�iWi8"�z_��j@Vi8偔���	&��D���뀃?o��.�Id�}�L*i8A�2���xBV�i"�2�zCV�iզ<h_�B��&��Id��i8*�/_��SDV�iE�Id�k5��<FV�i�-i8�	�)��D��ۢ�C�i��Dh-�$y�$D��	��[d<_�)��DG�Id�o���!
���L�$�Id�$Dd�Bd��$DԂ�8�?]�ۢ)��D��Id��$A��DW�Id��F�{_�x�	H�Id��D��$����{�2D�jF��m8��<h��$d�Id67�{6�<h�	i8w��{k}�Z���kQ����%+�z~��aa��$�{��o��$���ע�c\v���$3�o��8�IVi8q��_��iဆ$v�Y�)���z����bJ�IdKVi8n��ԥ��$��i���<��'De��DT��z{�)��IdLV�jS
o+�]�(�Id<�+�)l��?��v@�2!=���z��DM֩c�vN��z�}�)��De��D�
�$L�[�$�{OVfz��i뀹>��D����{PVfj����DO��c��}.��'q�⏗�>�c����
fQ֩c�� *��'��8ڮ�c���c���b�Am+��>���'��-y��{	Fd�`83&`Rֻjo!,y
o��'	Fd������c���>Y�z��::��{SV�&���c��'��8i#�o�+���j�4@V��'TV~~�)���8D�5�]���{���hw�%���)�<~~UV�{J��_�O��]�9��AVV~~��D�WV 3X�f���}�x�a~��bƁ������z'���9�����&SfY�Amx�o�ش0h�=�ZV)D�
�+�	�[VMd�.
owUB�MdG��)Md�;D	�XZ}�����R)D��&�	��c?f����\V)D���X��8~~D;D(Md�}�]VFdo�%��'p)D^֩ct_��'
��'"��`�a~��a�-)D%+Md;��baֿ>G�;|�Md�
��
$� 3�	�1	�;|�4�&G�;|dFdb�Am��xcV)Ds���d�m8&2��m8��)|o�#2eVMd����")D}ע�!:��::�~~.�-:)D��/f�)|��m8gV~~\�x)DG*�%	�;|��8��&�!~~H�;|h�m8=�/��)8��-��C����?8�m8���e�ն�;|b��/h���B�F�Ǣm8��;|?<n:��>@�c��ih)Dզ�>sOMd��:}�iV<n�Md���$j�m8z!!YÊbkV<n���>
��̧���}�lV<n���jt�),j�/Ũj�����?��0mV)Dn�D��b%�%<n���De6�c���6�>f�;|E�)|����o�D)��ܡJ�i��;|��)Dv�%��u3t��d@<n
��D��8. �% ��{p�m8q��zrV s�8��m8��I+%�"gtV�cu֧>m�84�i��vֻc��8� ��8��cwV�i_�8��cx֧>z�f���I �>�,��c��cyV�iz�"g{�D|V�c@�i=. c}�Dz��R' 6T}V<n��bh�,� ~։kȃR���8V搢�c�	ـV<n��8��F2��a��'�V :7�-����;�����i�����D=�f����c��%�2�։kd�&��'���D��iq:�c����U�����d�f�V�%��DvԢ�,C
�(H��ރw���J��M���DԀ���-��:�9��D��c��o�8���D�V ���c��� ��i��gE�cM�8G'��a�#������V m9D�	;������a �Yp�ֻc<�z�V ��zЁ���;�/��})?�S-|R�>ʋ�݋V�/_����}��V-|��e���c�-|l"�z��/A�I9��-Do�!ŽV�z�?D��J�G�?DZ�+�V�.��z��?D��-DU�zh����?D*B�zo�-D���?DR��b,��o��&��-D�3�z3��8�?|��?Dl�-D>�/�����V�.X�������Y��-D���b���k���?D!��d&�/	�yw!�F�	���=|�?D�-D��oӗ�?D�`X�r�g���&d��Ę�?D�j�&c���0љVf�V�D��ݛ�?D�Vf�V�D���b���-|�փb�?Dv���f��@d:�?D�!�D]����i{?D_�zʇ�87�:���D�d{��@d+�zI�z�!�a
�D\�,��z{R�zʇ�8�V�D���i��.�V�/�f$��c��?D��?D�V}�'��b��kh�?D����-D�A�D��=��փb.�k�f�Vf�?D����@d�q���?DF
i8d����?D�V�DY��d��-Df�@d����Hm���b�����i�=�k{�g����{���i-��is���kՏ��@d����kd�ЀP�=������D���f�Zm�
�j��A��dd��	3�b��P���	���Vfw� ��DI��iϷ )��[�}�V
�Dx��+��@d�7S�Bfq*�-���,���iW �D�b�Vf��<�Vfʇ�8�V�D�D � �f��D���:�@d�	��	�	��dÄہ�@d����Vd�����ɨ��a�o���ji8x�Zq�2�$��_�V ��ݛu\��Hm偗�Ě ����1i8��ZmQ�n�N��݀)���$g����$gk{���d``��d��k��=+��,b��Xi8���Vi8k�c��н� �{��֭,@��{�/��7Od�=G��V�i�� ���{9ڷ=���M��{B� �b�ֆ$�U �ţ��iT��g}���5�妽V�be�k�as������`��t{��۸J��
����Hg)gaj��a��� 8�i8�������0.�� ���5����+M���.�� �V)�V�i�FhA$g$�zl�x���Cv�!�b2D)3�V D�$g�2DDd�V D�CvT$g���$Μ&��iȀ�	�V D�V D(��	�+eG�Y�V D�V�iJ�^��.�V2D�&��脀��L�Yd�V�8ú�d��?.�������Cv.�� Q2D;�"|�8֟Cv� D��i�� |�V�i� D
��̀�zD&�V D��i(,�bl�bo��^�&� |r;�V D=��i�V�b�V DS� |JT�
�i"�{�"|�!�b��W�V�i� D�}	�,�V D��%��&��`�V D~�hb�=����L�i/� |Ł�&4�ґ����'l"D��F=�{9X*��i��V�i��Cv���..�� �V DQ�>H�Cv�V�i�V D�V2D�V�>�V]d��� Do f�gB���V�ik8�{]d�V D:�m8��E"��$�m8� D|Fa�
a�V D�V�i�V2DFM�{[�め�D��m8s�iJ��D_
��11�izX����� |��8��m8Y��D(�����%�Vo��I��$y� |$'أ�"|�M�>“m8���1Ȁ�	�6�M��D��"|��m8b+أ�c�3��k���D�Ҍ
c���zZ��+偣d�5o��d��Yb�{����,����%)��i��{��m8���{Io���i��m8��i��z�	�zu���)U7�{��i���z�E�{W��D7o�X���i�o�o�V�,l��x��W%�ٕՂ�,=�)���i�u:�$
"���bڋ
}�o<�qӾ��<
�%���i��i��m8�)�oه�DՂ)�W%�
a.�m8�E"U��iX)g�!�ݝ�֭�Ů���{r�)��m8���i%@�]��iY��D����m8Z����Ņ�	�ʐ	Զ�D�#�i���iS��j��D��iބ�ރ�������Y7��&�أ[F������'&�D��&x�z�Vo�V]d��'���#n�{�	�v��)g]7�R���{B�#n��i��A=�#n�!24��i���{J2[�Q6$|��C����{���,�V�'��Ƙ�V$|=�i��zy��i7��a
��A�#n_��i����b���i��#n����h]dw�$D�'��xf�Bd��$D��#n�V�'�E$|k�&D?O$|��+��x�6�$D�&D�E�'�5$|��$D	�Bd��.��&D�$|��&D5�UkQk�$D�$|��&D��'����&D���V2Ԁ�i`�U�V$|���/��$D���|$�����j'�'}�!!� ?���,��v�:_:�=����vd��f��#n��&D5��v���/A��|��$D�	|9��#n�q���#n{��V$|��$D�)g	$|�'��$Do?�W�'��Idi�$D�
��Y��#n;=$|<�Id<��Y�_��+$|W�'�&D�V �$|�X�&D)$|����&DƮW�'$�&DW2�Sf�y�$DW�'=d��$DW�'	W�b�Id�[d
�&DLH�i�x]�Bd��io�'4`8b�[d������&h�i�$Dt
��W�iF��e�¤Id��fPo�	����.�b��	~�)�z
W�i���5�dg`8��bb���d�5	�jq�g59��r�	��iX`8�!!=�نA�iW�b߁��$�=�iHX������P��f���Ȁ��s���Q�Id��[d�i���fF`8�[d-�Id�[���iW`8��i�\v��{��
��ϗ�j��{�� W-gˋ)z;-gJ�f��"g׀��W�{��
a'3�b�)x�b�)@�&j�d��fš�z@�i�4�b���aW�b=���W�{\�iP��a!��z�{W�i��}	�%ۃ=�6-gR�f�Jv��aW`8׻c�'W�▹�'V�b׻ce����	o���aE�i@C�b���cW`8*�	o$�ⵉ�zX)gt��� ��aF�JW�!W�hJ��j���z�g����d�㵭��c���}"��a=�#�f�v$7�
 �������Jv� � U?�{9����A��)��?���d�Fd$W�{�{�
�N��8�[��h�������::܂f�ͷ*֫�����fA�l��o�Ɓ�����8�����h�i%ס8�'n� 
��'��i���aO<�Y��
a��>��^��z���'&W)Dq��zI
�'W)D(��aX�Q%)D݀�a��-)D�:�z
�'��-)W)D*W�x���jMd���at�zW���	oJ��a+W)D;�&,׻c-��a	�;|o�.�)|/�	o0W�z���迎	o1W)DՀ�9���+|9	��Yd�=2��a���`4�;|$Md3�`4��a����9��fX��aX���ȃ��5W)Dp)D{�8����%!] ��?6W������!��a7W�c8W)D��x9W)D:�)|���_ۉ�)|���^+�>��d�)|��a…���d��-��a��`S�c���a���.�&���x�&�'f��
;�)|;W)D��zѪ�a2��9�")D��>��b��a<W)Dŋ�-=WNv�S�&��z{���3��a
A�c��&�
+D>��a�)b	)DQ%�z��c�Nv
)D��z?W�c@׺j���u�)D��&h_d�.�xg�&��t�d�;|{��8����|KA��a��dBW�zC�)|�
aD��a��EW�z�`��)Df�Yd3�+|F�`��)D�Nvh��a4
o��D��&���4�a�/�&��)
ov�ȸG��a���|H�8��,�b�_�J!Ѿ�Y�e�c��6B+��iO�U��V��������z�#ƁV��,IW�&l�,t�dc������&{�@���	遝$� ��&�NNv��k��c��+d{x��z�%c��?B
j�$�z
o��6�JW�&�	
o�ƛkR�5���
o���khB��������W$g�$g��e�ɗ�k�`>QS�K׏bf���T��dw���ʐ&�����$go)i�zt��+���j�ZvX5NLW�z��aރy�MW$gu��+ރy�)��NW�aq��ƁW8�7�C'�%
	$gY�����OW�a���ze� PW
o��a{��v5S��%�i�z�7$gj&
o
-|��,:�݁���{k�_�h�o���:-|QW]dRW
o��o[
�'��
al�oÎ�|��ݍ'-|ob,�,:Q��1&�$]dĉ>:SW�'Q�̉�I9TW-|��?D�bЁ.���UW�'{�-D����e�R�VW�'WW-|X�i<�/YW�'�-DZW�x�
-|e�-DK@�'�-|I
Ձj*A!:/|[�-D��y�\�?DqUɿ�ʌy-|]W�'^W-|��y�_W$g�:�`W"D���ia�?D$gT��i&�����i�5�'ݴ-DbW"DcW�'d�-Dj�䟇-D<�hmCm����e�?D�
�iڊT����fW"D.�!n	]d��}gW"D��ʌ�}��bXW �gԨ��}h�@d�-|i�!nԑ�
�'jW�'���kW-|�b��'"D�-|	]d�&-|�T�'�x���-|��'..n4��E�/�ԡ�'lW-|z�@dy"D�� � �Op6�a���bm�"|�.n�Zmn�-D�+m_�G�@dD=�'�.n����{y�'o�"|Ԃ�bpW�zZ#.n���#�z~�i8�i8�&qW"Dw�-D�)Q?"Dr�-DsW)p�?DtWi8��]��|��y�d��u�!n�®�6�vשcwW)�"D�"D_�[����mq��5d>�ox�!n5��vyW"Dz�!n�i�i8�"D��&��2{�Zm|W"D	�ܹ�i��}W.n9��~Wi8��|w
�&���'�������}r��+�	�EW)�
i8�&�����	����zX���� t=�.n�`~��{��"|g�"|޸�c���5�Xd���a�o�+g�שc����@d�W�d��_^�z~���D���v��cZ�z��Zmr�{D��'
ȒJ���$	_��b�Zm��$��)�� o=�z���>��Zm��a
��H[${�#��a<i8C�+��)��|)�`8[݇�b�_9�[��Wo�i8p��a+Ode�שc)�����f8��$��o�b�׆$Od�(�&g�db��j����f8�W	 ���^X���%��\���$b 	 ��j���l,��Lk��b�שc=�I�5�i�b˂e!�i�Uv%�b)�Ȓ5���z)8ݧ��u�Ia��D{��8׀�/���'�d��$���'��+c�Cv�j�")��d�Wи�2��ib��|�<��$�)`8�Dd�W2D�Oo�`8,�"@2D���[�W�a�	 r��6Vd:G�a耙�<�ɣL2D���b�W�'2VdX:R 3��d5�b���a��dnW�w�t��Cv�52Df��Ǝ�f3l}5�b��N%��&Do�xD�a��;��W�x0���WVdt%2D��&D��Cv���|��b�K2D��bk�xi�����z�a���`��i.�&D�W2D���|��/��5�b��&Df0�&D�5b�Uv�b��c��Q2D�3�Ȁ灹�'�W�x��&D�����$�W2D>���꣫�=J��~��/g�)&|��c�"�ܛ�&D^mb��|ٳ�'��&
�aR�c���`y��/���`���'&�me@��$ˁʌ�W2D�&�W�&�W�c���|��$��)����l�':���֚^m}�a�W2D��m8Q2D����&D�W�a��x2DP�)���׍$�&D��&;�&D�g�1
����)u�)+.��P�D���x��&D��&��)�?�5F���)|�b*�&�#�&��m8�
��ک�&D@�)�-,o�)u���`���ݪW�bm��>�W�cl�&�2�c�W�b�����)�צ,�W�bd赧��vl�j����`�"�cH$�c~�m8�o��c��,��	�@�b
�c��w
��Y��23�KĊb2�X��(n�׊b
!���"g� 
�Cf�bB��2�W�b��Xv�)���(n,�&���$v�}��i��_o�i���Ȁ灘�)��aX<L=�'��ץ>)��2΂(n�����2�U�L�m8��N��4�2�"g��b���br��%D���)��m8Z
�b3���g���zg�m8�W5nQ	�C�b� �܇�dB��z�b��i-g�)�b�Rp)�� �����p�p����z/�
C��H�b���m�>!>jx�W?��� 9�b�W D�W�b�� ��`>�6|���88pjooco��&�5nʃ&=��!�j�e��z��c�W D��	�6|�f��W D3��bMoI�"g�� . D�(nh��d�W�iy�'l@+U�>!����o4
)}F
)�W Dy�'^o6|:�(n(�� �W D;� +��`j�}Ԃ(n�"?�ނ���W��)����W DƁ��6|v&
)��H���7oh)�6|-g�5n�W��!���4̨�>�7+DX-g�39D��{���zg
)�5n��-����O9D�W+D�W6|�W9D�o�e�Y�l��x�W+DʐB���DG+D<���|�eЁ&=�w��@9Dϋ;g���z��R�
)��
 @�z�9D�4 D�� �+|w���W?�	�y��)�	+D��[d�Wo)�Q��� ��	o��%���xZ

))|�2��W�`+D�=�Id�Wo�* D�שx���&��j���&h�����&�׃b���&�2o���&�W�'�׃b/��&��[dxWo���&��+|	�2�!�P���xf�[d�˓&��-��g��+D<��x��8�ex�+|{���D9D���Ĩ)R?\vkb,��&��	�#�a�&�ׂ�bb��|a�T]ĉ�&���&�שx���zb`~{��'�W\vs@֣*?��W9D���&b/+D'�ʌ���&��a�X�L��e���,Xm(��jas�qٞe��&�׸��$���i��
�vָ��{z��b��Id��*B\v~����=�*�ۧ�%��
��p�?��i��+|(��p��'Ai8��8n1i8w�ܜ?�z�׃b�.
o�ʌ2��&���i���&�i81 �{-
o�שx΁&*��'q�g����W�{.��c�(�z���x��!
o.�묁i>e��c�6�z�>�Wi8�#/���c��t�X�yB�
oS��zK/��!�bp
�Hn&�>jxi8�שcւ�ݳҩc�$|�W
o���wv���ہբw��{dB���z�$|�qx%���i8�f�qxg��z�
of�)@K$|Y��z���R�::��w���W$|��Ƙ�B����a���zH�c��)�-�&�שc
��u*$|���౦�;eۖ���o?�=�o�'�&�B��|o��$
N�����Cm8�~V$|Ă$D�W�&r
�+y�䇀r>{��A�)T�.@K�z��&����WMd�/|�i8�W�&�Wi8ɯ)$|��&��$D�;Dj�b����W�b�=|��b��$D����W�'&�+
o�W�x��b��&O��c՜�@���$cQMdI'�Ve�'�`~��of=|
�/D��+ˌs�[X�/D��;|#Mde���e�':&�>�xŜ;|�/D�;|�ݎ2��;|��z��9�Rd>;R��b��Cm��ބ‹�x���x!�:�/D�'��;|��>
�/D�W�b:��E���bl�Am��4��`�W�'o�_�;|��&�
A%/���	���;|��;|�W�&�)3,��	o�{[�	�Ṁ$DB���W<n��$D��;|R�/D��;|�b�Wce��)�W<n������`XMd����;|�����j�.�	o����a�'��	o��ʎ��3�p	�	oX�b��c����c�+�����d>X�b�/De�'�m#(
�bX�'I<nl��aq��U�/D 
�,OF�r�xÃ;|X�c�'�;<n�"|��;|��Vѧ"|X?�A%<nX<n��/D)��N�/D';�c&�;|�9�z�;|L)	�"|9D�c>�� Ɍ	o�ف����b
X�ch���X)Z=p�z�#)��	oX�cx�	o�ف���`�
g	�Am
X)
F"��c�״!�z��t
�ՀEE^)�qx��iS(FmU�c�*)���aX<n��8p*�i|RGm��m8��"|g
)Z&�)A�c
�z�m8M�FX<n��ux����<n�m8���>X<nR�"|*�^
�w�"|V�:�Q�m8k?��&o����"|}����զdc�cX�z��a��D�js�����p�z��l`��,�7X�zX�cJ�+؊bئ, �m8ۚ���)�;?�[�/�zeaAm��,X�cX�z^)p�zX�c��,�K�)؊b	�z����&�� �I)D��5J����2j��b��|?��·��|ˆ�</
�i�q�ئ,�k�#.)D��)�|D�
?|�E��a�Gmd��X)D��`�>:9������
o��$����U2�)|�?Dw����?D�m8�F)D*��bt�?D�D�zX)D
o;�?D��d�j�=>�ܳ�?D��)| ��'m��&!�?D"��'��z�N@)D#�)|����.2�{%�(�?)D��?D�<
o$�c~R��'���sxx��)|�@a.�7�`e��')
o�̦,��c~`�I2:�a	�F%�?D&�?Dm�{��,B�)|'X�a��?DR��'h4�'�0�(؊bW��?D��')�?D[�t��T A<�a�@d6�'*�&D+X�a�����?DJ�)|,��'-�?D.X�a
���&D{�@d��?D�kt�*d{X�q쳥@a� �a��(�D?D
oo}��`+?|@<u/X
oi�@d��Jm0X
o�+)Dj�?D1X��2X�a��3�?DÄ)|�"�am_��?D^�@dF
o���"
oX6)o�o��?D4�@d5�?D%ۦ���6�?D� �Bd��j�?De��'���%�e7�@d8X�&9X�a:��';��'������c~<�Jm��@d=X�$o�ux��`v��A�&7���>X�&)�E%(���/g�&?�Jm{�@�(��?�g�z~O��c��(�	�"�ao�ux
kj���@X�&:�aAX���'��DBX�`."�{T�+� �aȃ��{v>��'C�@d�ߣ��c����b���[I�&���x����(h��8"@�&��fL��c�����,DX$gE�@d!�	�f��.d���`8m_�vT�b3�b���B��?:���eb��D����F���$gX����bF$g�}����j���GX�b�$��+0�<�f83�&���_�k��T�Y�[S�&R��eA>�CHX�&IX$g�v�����bJ�f82ĪjЁ��7��,o�uxo��2W$��3t86��m�nK�f8Ղe*���b��&4�DI1-|��k�b���`Lتj�-|������2k�ux��yH��f8��	��f8��	O���aM�f8@�-D���c�b�!-|ĉ�jN��zOXEv��b���zb-|t�-��f8.�i�u��o���(PX�b����-Dk�ux��6�{>���''�b��Cv`KC�Q�-DR�CvSX$g��OTX�bU�Cv� D��b�fV�-D�{>,Vd��CvH)$gW�Cv>�uxJ�!�v�+�)X�aCag }ϔ�j	))��a,�-D��Cv��a%q��io�)ۅ|x<ִ��-D�Dd�a�f8Y�Cv< �#)�R)�*VdY��`@�5Z�Cv�/�Y�[X)��J�b�CvvY���Cv�ux���9���Ӄ|x\�Cv[X)V"Vd]��`^X)9�{�v�����F)�>-|�ux�����/_XNm�Cv��{>`X)�KE��i�C)a��z�g~8�'b�M+���oÎ����	�NmQU9Do�G9D��i��'c��`}Nm9|³��'��ddX9D��`U�Cv�.����`R�Cv��z��…�E]�/e�uxh�|x��f�CvRFg�'DH9D�)"�Cvh�E"ʉCv9�����9�ڷ��Ђ�`d��j���f�"��t�\m��k9De���� DZ
)U��'�'iXNmW�`�)Ro��f��Dj��D�Mo�ۉ~�`���D���`�Nm���8$��D
�fkX�alX9D́ D	�c��`m��DnX9D/�f��E"!�CK�C��qxoX�a	9Dg�dp�f��&o��D�Fa�f���q��D��p�۰��'r��D�+9D����sX�ot�f�$�aV�vd�����DuX�a�d�	+D#(�a�9DZ�y�ץ	�v��}����59D�ؾv�}k�z4�t�a������z҇u��}���b��8���i�L%��a_�oj=����})��w�E"x�}�*�z��f��`y�f
��>�}���9Dr��D���i9X1z��D{��`���{D��|��D�8��d�,�
��}X�a���~X�z�i��f�X�a���z˺E��f$��D��a$��D��}"� ��h�X�z���-��)g��b�� �c.��{��%���a��)g瀂%�	��S�x���7|x���j�m�w���o�ux��:n��豺�i�Hv5��c�A�3���;$@���{c�(�p��z�X�&����{����}[��?��&S����@�bQ2D��}(�$Dy�&�X�z#�	��ū���v�}���z]�)����$Dm�����)���zV2D:��x�X$|��}��z�2D��L|�7�πaxx)���d�R�&Y=|S��zO�z�� t���)5�/u��`�%��&���� ��$D��&5o(� �X�`���J����
�&Z�`�
�e��$D�f��$D5�c��͓�@�f�X�'g�)�)��[d�Zm(��X�=D���m2DC�)g3� ��)gڊgP��)�� ��=Dm=|��)g���hfщE9Ԁ�.��ݖ��h�����	f�V 2�&-7f���Fo��`�X�&m�$�	o�摘X�&��=D�� v��h
��W�3zo� �� L�$��~,�X�'��Id:
"D����C�&�fl��&�� �� �)f���hУo�ux�� H�F�Id�X 8�)8�D+�� �X@�П��}<��h��bdT�'�X�$��'L��h�Am��"|Yf��bb�i��I�j)Yf>G=|���V7#�{��=D��d�w�{v>�X�`��dG&�b�f�& �X�`h4�`��"|�У�f3�'j��Id�$�s�gf�ӣ���Wf��"|���j��%��b��)���h#�E��f�X-gZMi8ی"|iB-g��=D��	o�����e�i8�	o�|x�X-g{v>k?�a�fܸ�h�ػx�ػc�X-gy��b4���䐻c@�2N����clUm�Xi8X��Č�(ۛ�Nmv�<�Um���c���	(��+���� 
9Y��cd�f���.�C<鸎��bGm�ػc��2�Um��f���/��c��c
�f��2%Umd�t�2�z��f��O�bh��8�X}�X�zQ�dz�f��NI��	������0䅩c���b��Н����q#���_��_�梶�f%��x��f	�a��xx��f��o�w�o���z~��z�b�؆$�}��f�X�d�X�zÀ^m��zt�&�bF�f�Xi8���fb�z��pd-g��f��f�X)Dv=�/i8e��%���K�y�f�Mdk�2=���C��)Dc�2Z�&�_�f�Md@�2?��	�0)D���&:�nX��b	 ��V�2��`��f�X)D��`o�f���$�X�`��f�����2*��b)D��zh�f�X)DB�)|��f�X�zȀ�	
)D+
o�f�9�φf5����f����+�z!��ȃ�����co��2�d���'�D�zB��&�=�`3�^m�F��XУ��`r�)|��b��`�
���؆$΂ gA
o�V)�X&|��Av�%���f#��bf����X�h�؆$v���U��)|��o��Av��dԂ�x��f�/Nv�ؘbؚ?z�!u�XNv��&DP�'k����&D��Avʉ^"��BdN�do��y�f�XMd�&D���iR�o���'�X�$��f��&D$�o;Nv�`f&�$��hn��d�()D
���`�o��y�Jm �Xm�/Nv�Bd��`��&D����X
o�)|�L�`�v>�X�b�)�&D�Bdb
o5�o������)D��v��&D��)D>�ux�ze��Av�x�߂ �"/'��o>��i��i�Av
�Xm�/'R����X�b��o�������X�xg�Xm	�,d�탺8��/u(S�b\2=g�o�Xm�У�m8��v#�m8�&D��G�f�����b�=�F٘��̳m8�f9�V}8
<u�/'��8���H��)�Nv�Z�{�8��Bd��m8���X�6��X�b��m8NX�Bd��m8���XNv��&D�)��Bd�W��(����&��{��d�D�bo��iʁO�l��d/�f���b�ze�wh�Xm&�b�)87����ד�b*��iL��i��v:�bX�{�9���Zm��h,�b�	Z9��Xm���h�W�b����Xm�$�3?�����
/'���h�c�	=g�X-|��h�f��5���'�b�Ɩ�8��,��Ђb�aZm����񍽾��٥����{�Ǵ��dk��y�)����{���񍽾���h��<ŝ,::�bǡ�hk�<n�����8��{;�;|��}j}����t��-|�X�b�*�b���ho�ӣ�C;D���z
���d�X�b���z�X�b��;|ҳ�'�(Zm�*A�|xЁ�&��;|	��z�������hi�-D�b��'>�}j�
:���D��z�XFd�XFd��Y�j]�%���{��ڣd�	'��٭&�.HFd� Dj���z�}�W D��z�X�`F3�;| ��]���h��z���z�A\m�X�`�XNm��i�����i��f�)-|��0'���zH
�`ʋ/�X��	_$9Zm�2�il�`Ёd�p-|���'s��zƁ��
�d���ڣ5-D�X\m�	u~=FdtI�+��)�����`�
�d�Ǵe)(\m!0�n�n�9D)p���{5)�9DDu~���z#�$���z��'��z����X\mt��z@�ӣ�KNm�XFd���'��d��	���h��-D����b��Z��x�;|o��.o��iQ��z��h��$|� fQ�$ԗ6���'�;'@+)��hY�$À
fc��-<�+|9����Zm���<Fd���-Y�$T��a=Fd��x���8�{��$]Y -��x��SS�G"�
�`Y ��᫡�&d_$���&�&�J��f�
�7Y�`��&���x���b��"���g�'�jQ��,��ib���j�Y4'� Ё�����'\m�4'l�
fYNmx�t��{�[����&�
�耪�ڋ
fs�{	كbu��֮���1!�i]4'
Y�$X���J�څ�fӃ�x��㬐��% {��	4'�9Dd4'�Y��ʪ�;�z�Hv��1��|�?��Hv��$� �zk�xڬ J��fL
�AЁʪ�Hv���,��xk�í��.���Q�z��ģコ�
Y�zF��j��^9ĚHv���)��I
�	ʋ���z?���z����2���b�zY�z���b���,�?D>29��x��`>Uvm��zq��xց�n��i���Ȩ�d*��&�E�z�5M?��d��?��zY�?DY�jj��iY4'����5Uv���z�?DEj�2�j��f8J�O	6g6L�z@"��j2D���?D5Č��zm%4'���z�*Vd�4'��?ډ?D��f)���_+�*Vd��d�4 co�?DVd��Cm?�IC�z�
C�[�
�h4 j�?D�?��Vd�2S�x~Y$g�	�bن$)f�F9��$gg�z�8VdΟ�k>�h.�f8��bq�z��z��|x�Hv���k$g��ߡ�����49�;��?�
#)�	2Y�b�z$��$��>Y$g���+���9���L�YVd��� �?D�zYVd��-���6$go?���� �~1*>�-�aŁ�/���>��`�_+��i�hA�xZ��پ>�U�x�
Zmx�?D Y�x�$!�_+"YVd��f�f��d9�Kdm��`#�Yd��� ����$g���$Ç��Yd$�Kd����ʍ.�?#��%�)g��8��`��	��$g
`#@-&YVd������d�v�U �)'��`����7爉�$(� �R$g)� b��$[ *��`+٣j��x,Y$ge��>>?�(��D-�Kd���`.ن$/��Dӏ�K0��`k?�5�2Dn
�ԊKd��g�� 1��D���`_�d� !`�T�e��>m[�[� {���h�f�2��`)��DF$�x3پ>[����o��� �%@�Հ���`S�x4��i��>"��/���D5�`���	�؁ŋ�z5�/D��v
X�#�X��`ރW1m"�b6��i��KdÀ^m�ӳwH#z�Kd��<7ٍ$8�KdQ��`(��D�|~��j�|~=�u�*�bw�� 9٣j�
|~�Ydߜ �@��Kd:�Xv���`�� ʐ�4U£jd�E@�ވ�$��<��`;��D��<�Xvx�9��q�{�̶�㵐 ւXv�@���x��<̤�`*�bwP�&��E�,�<o�&j���=�9'+)���	ރz?��x�CaA
F:C�>�٩Zd�'�,u/�>Y D$�<?�;g��Cv;�x��x�׷���>?	��"�e�iDY��;g@��i��<�^m�|~u��>m�&}�ڣR����}A�9'Bم-	%o� DCY@�D�`z�bwE��zFY D��Xv�Xv�ce��Cv���`G��iH�`��<
��i;)�;��<9����.����Dd�X�fd�j6���>��CvYg�х-IY Dr������bQ���@��<������<�
-g��}�Ι��im�f{Gm5�k��i:Od�;��6 D�&��	�} Dx�}��;gJY]d.�+ҡ���]d��i�:	 KY�`y�E�L٢xz�}a�}�Odn���5f��
�EȀ�B	�;gW�DMY�`�IOd��(u���?Lj�>�-��`2ĊbN�9'��;gOY D�����?s�d����)pf�PY�`�D)Q٥>_��ރ2ñ��h9�˘�f��d��x��o[�Ave��h�M D&�[d���h����R��h$}�oQ�`���Av]
��S�)gTY�`�o���x��jƇ�hXo�02n���5�$���,b�z�cOd�`UY2#fVY��}j�Avk��۾W}h&\v���Q����WY\v$�)gĉ�hXYOd=��:l�(u��`_�7�m[�YYOd��`�U$���O&�RҀ�x�52�$ZY]d[�}o��\Y\vƁ��]�z�351�0�`�a5��c��ٶ,5�**��5����Jm\Zm&\v	 =<��.�`wQ��U���y�\v�"�`,�)gv���{"�`��^�o|�LԂ)ҡ$���[�Xm@�ӻg�)gj�JmȀˁ���>��_�o`YfI�)g9t��ňf�AvaY\v��JmR�o�)g�؂��dk2�Av����46��bb�)g��o�*�cY2dفb���<耤O���be٩xY���ff��&Q%�`gفbhY)iY2jفb���c���&$|��kY\v��d��&���b�� �ǣ�>\v)<����`o��Z	V ���4��&�{1&`8���&\vl�Bd���b�3�mY`8ޓ;nفbo�o��&���,&`8oX��$|5e��bۃˣ�`8p��&QW��qY$|���P`8��$D��{���d
��*::��#�brY`8��&�isفb?������&$|���.R�Xm�f/(�tYf;Giu�f"Z9쁪j�ô���C��+���_d�%��9���v�f;LU]�΁b=��`8��DW�f��:wفb��f���	��[ ���
ô�谁�Do��+x�ft��D��u�ȩxyY_d��u_���.���czفb{��&|Y-g}٩x��Рfz
 
`8~��&o-g��^m�`8��&��Y{�i#g�f�Id��&��w_�*A;��	o��d��;|[�Y�b�`8r�(.$|3��'��{�Y`8�-gG:�"壁�Id��$DՏ�+�
F����dQ{� �AmMHZ9ǑF�J
��_�Fy_d��fz	}���9#�h��oӂ�Am��h��;|o�[���d��j��f���k`�F�Z\m��I"ψ�D���օ ��f%�&�Y�<���z;D��D��f� {��$
ô�f��i���fa�Am���"\m��Dބ�i#t���+�
�0�Ҭ>����\�f#	���hY���\m��f�����HUB-g_9D�	oD9D��+Q%-g�Y9D�Y-gz�f�V1��i�ek����Y�b�Y�b� �x`��k. �嵍Y�b���
ôQ ���l�Y�bπ���Y 
ô��'Y�	oh��'] �Y�x��t�y)�����ݒ�Am�9D��d������Am���d���&����d9���+ރz��b� q�@����8���`��k�*�g�� ြd����Y�h���€�7 ,�<��d��bu�2?����)��b�U�x�8k�2a��i���d�n�\m5�mT*��Z�_��2��ˁ����(�8u��.���*����ߕYD���� g������-l��i�{�o�bC��i���>���"g��<��>�{���b�9DWf� �Y ��݇��e4
��	f�Y�x	�f� I�����Ya*���<��b�8 O��Md���j5l�f��C� D))�D�Y{�UB��>t{�#�D/��X:��Md��Wfh�
f���d�)D<Md�)D'��`�Y
o�,��������z{�B?��fm�)|ƒմ��`�Y)D�YMd��rGEm��Ƣ�YEm	��zg�?DEm�Y)Dl��zO�u��Y)DkJ�\C�bQ��z���z�Y)D��dY� I�ˁ\�?D�F��՗)|���,�?D
	
o�:�$�gf��c~��)|�?Dk�c~3EmZ)Dgf�X"�Y��c~6��I�i�:�Em���	f�)|��)|{��YEm[�m$gP[���MEm�TfȀ����c~*'��]"$g�(f��o���)D%�B?�٢cl��	<�)|⁼d�ǀ��bS	
oǸΫ�J��٢c�$gK����?D��bl$g(Em�?D�وb<��-ډ�hR�H���b�)D耹�ĉ�&�f��c���d���h�Y
o)��b��{���co��h���z�Em��ݩ�m#�GEm�մ�z?	y���d�f	�?D6�$��N��`�Yf���&���bG�c~���h���h�%�`��d��cc��c��c~�� 
��b���&��Yd��5߁iw�?X"�'���h�jԡX"��b��d=|T�.�c�c~_��h�YX"������hm��,��hT�@M��c ��~��[x&�[���!n#$���C"D�
f��YdLX"�"D�$gT��h��ݱY$gWӈb����Y�`�Y"Dtf��Yd�وb�f��h����!nU"D�٢cy��b��h�Y"Dֱ�cc 	�f��%��c:�Z���U�"D��b>��h� f�!n2�"|���{t��Y��{��&����h�Y"D��-��"|9��ѹ�!nj��-�!���ɠ{��#����.�"D-Y"D��1ĩ��"|�Y"Dh��d��2r��P�	��Y-|�,�`Ԍ)*
��ۗ�,��e��ZvR�:u��Ydz��d��Hm��ZvUBf�5�����:u&Di8��2�
y8e0"D9���i8m�fYB"DL-|�mw���h�Yi8�Y-|���,�����d��4��g~ǝ4S�Y"Df"-|k��#�2�Y"D��-D��Hm�:u�-D	 g~"D�{��"D~@i8-|�Zv�	g~�Y�~�{��Y-|��-D�ԣr{��g������-D�{����%��I�g~� D���d��g�-|V	 D��-D�"|�����h��b� D�i��2Qң�"|��b��-D��&�Y D��-Dh�f8H�"|��
��/��Hm� D� D�-D���o��Zv=��F�R��$
�-D磖i�:u�Y D��o�8��-{/ Dh�Zv��-DԀ�o� D�������?� D�6}�܀}	x^����: DU��՝He��ݫ�ˁ�
f�0 D�Yi8�Y]d��zϋ�$�v��i
 D�{��i8�g~k����(u�Y]d�k�+�*b���Y�htQ��b�g~v�i��)�G-�i!� |�Y]d���%n��-D�1)�Y]d�"�h�Y Dv ��Y�h�)(5 Dd�d9� |ʐ�E�F�
)�Y�h��9��׀'���d�2j���.��Y D(������ٟ$���`J�d���hb���Y D���+b����]d�)��hך�$��)g4&|�鬥��$�m���=&|��$���`����ٟ$� i��d������f�a)���)��|G��㘁���
}�
%nQ]d��f��Y]d�U@��k̦d���`�ˣ���b�T&|>��Z�Y0�̴����r�&DA=]d���{��.�!	}�Y]dZ��_��6
��zJ�&D���/�Bd��w�=�x�<)�%n���z��{�a��T��<��z�6�h���|%�&D��z�&|���{;�&D��=jc4�;�����z��?��$ʋ�ꊉ��i�<�	 %�&Dv��+,�x1��|��z���i[�i`�o�Ƙ��z���i��XmK�&D��z�
}�2M��&D%��z�	ã��$�$�Y��`��oMه�zʐ�����`��7����S��M�)g�d��I"��}�z��<;�&�������~o�m8Ȁ&ЄAd���B���%�&m���&D�*Ă':��#n3��;�&D
��[�G�#n�[���x?�&D(�m8�}2D݀#n��-��zR�m8�}���;�&D�$|G�C�U�C�%�&D(��Ŋm8�`8��i;�&D��z��oތ�Ŋ�z����_��Q����z����b�}ǘ�z��}�����`�Y$|Q�΢�#n��$DȀ�Q��yծ$DW��5KD$|��$D��}V����$Dt��(j�#n��xȀԁl$|J��[���$D��̴����
2D��s���d?��裆�M��M��㩀$Dp
ĩ��m8�YM��$D�����k�$|+�$D�#��J�m8to��܂�S�Y-g�����'8-g���y���de��,��#n�m8�٢x�m8I
-gL�h�Y$|�[�p�$D$|2Ev��9��$D�i#�Y$|B�$D��%:���5�#n��)h�b�}����$�)�+s-ד}�h:�)�2$|� �b��ޝAm�F��),�bg�)�7����h�$D��Id�f�y�a~i�$D�AmHv!���Fd1);��d�-g��$D,�)��Am�Am���x�Am�)��X��$D��$}�)T�ԣ��4Gv
Nm��IdZ�`��|�i�)�(n��$��n�)�Am^�)��Am���z�$ڢx=����i#�ãZ�`�a~�	�h�Am�\m��z	Z�$Si#qX
Zo��5Z�n�i#Z�`[0y�
Z\m�Nm��hrã�U2��IdЁ���+U�Z�h�1-gZ�`��"ggȘgHt���+����AmĬ)b�iˈ����)����y!+D�Am_�
(�	���uY+D���8��'h��d�Am��X�r�%:\ms�Am
���	��͜�)����8��b���<�	�z�R+D8�Am�4��y+Dg�z��Amz�`b������'n�+|)�	���~�
�	�`��{5oa��b��Ђf.
f��g��\mZ�`G��blo��ak�-8)Q$fo��ib����:	}]���
\m��V��<倻xr��b�x+D8�~�(=wr8c�"g�؁HNm=�H�c�Z���C���n��"g;+D�	f��n$�q���}/X+D*��bd���Z+D�}o�ic�"gv��ƚ�xH�^9���o �6|����+|�y���d�@da�DE����awQ�Q�����{�}d�()�	�Ԃ
}�
Md��})D.��s�O���<?����|b$�ނ��<��}��9݊�<��+|.)D�ݣ����L)DW>5n��i�)D���<Ԁ�ib�����ZEm.���o�&�Em-��x�m�&��)|ZMd���xYBEm�)|r8c��x)D�.Ĵ;�)|�>DZ)DQ�)|#��aZEmd��a��)|ZLEm�)D��)Dj��a"^9.Em~�)|(���>
�O�2�W���m"r8�[d����m{�GT)Dó)|ZEm��i��_+��F�^9��0�[dM���D��at�)|kD� Em.���Z)k�}(��)��a�Em�){�)|��&OĴ Z)DYU)!ZMd*�*~��a)O�Mdt)Dܩ�a%�)|���y�)|���������"Z)DK�&��9���_#Z)M#Em$Z)De��
��Q�	)1�0�2(M�m�%�+���>x��Yp)��|�����PEm%�)|<��*����b.���K)�{����&Z)'ھ>5�'�)|j��nj��*�{��)|(��a��xz�m#c���Ңc�)�)|"�xs�Y(�ԣD�	`�������
}*�/DI���J��>+��a)�	���,�����������Kd,�/Dq�@+-ڙ"��.�.Z)O�&��|`�����`H��m�X)U!�@/Z)>�/DƁ�}�0��`펶1�o2�G9��`�b2Z)�)�Lf�o��A�~))57}�0�x��`�w#u�x3��`��&��ԣ���`y}�&e�o��Kda}�/|o͢Z�G9x��ds�4@#��|�)3��`{}4�oՀ$�r�f�tIw�"|�}��G9��o��>�{�5�ok�)>�
}/�"|����k�./|=�o�6�Am{-�,�/|냼�7ڣ��,8�)��/D�(��Kd��&�)ՇHm����XY�9�Hm*�b9�"|��ݙ�`��/D�b:�/D;�Hm]-|8�֔�"|��մy}��`9�/D��߹�Hmm��|i8���~<�Hm=�/D�݁>�Hm��/D?�o�Ei8_�)@�o��G9A��`f�;|��}Ii8d��BZ}���`C�-D��)q}DZ-|-|̈́-DE�Am�Z�F�-D-|ʋ��=}�)j��b-|Ɓ-D�y�Q�+�dd:p<-|��-D�i8#�մs	:#��xG�"|��;|{kHZ�x�`Q���C-|9��Z�jI�-Dv�ִJZi8�/�-D�)K�"|o��m��<L�HmM�;|��?���<�X9�3�N�`�.n�[�O�-D�%����P�`qD�@}QZ-|R�Hmd��`Ҁ)�
-|SڢcT�-D᳈b���ճ��|_�Hm��HmКbUZ}Vڭ~���'W.nWZDXښb	�0i8���&�
u~>�aR�j��%tÚbQ<L(�-D���b@ٴo�-D�i8��j��cYZi8-.n��-D���'���bZZD[��&Ł-D�.n\��&m[�꒙�.�-Dڊ�Lٚb�?�ǔ-D���'��]ښb,<�`^��&Ȁ��覢c_��&`ژ-aZ�`a����$���b��hoyF�F��o3���oĶ�b���k��8m�~܄�hc��&dښb���eb��h���eZ�$�恅(�9�	�5ej*,4�`
� [,o�5ogie�X9�c:�$:�)
��h�=
СW��&�TL&`fZ.n<��h���.��`��A(��h�����[����,�CmJ%�`,}��cަ�bvށo\��h�����t���|�&D���b.��&o����`g�&D���~��`h��h���&iZ�`jZ�`½&Dٴ���&�{�O��&��&��x�%��r��E���&;��h)
�12o�&DkZ�`�X-lZ�$u��a�1�`m�&D��X-�,<£�nZ�`b�&Dိ~ӃX-�5@�	�zo��hd�Zmp�&D��?���(�)6�'6�X-��n��a��ó&D�£��&Dq��h�y8r�Zm�X-��*5���	�i��h:�?D�h<��A����s�	}��~-��Zmԑ	}���5�X-��l�|��-|�����?Dt�	}�Uv;�Zmg�m8ɀm8CЩx�%�|$�&DǿZm�X-s���62�`��u�	}�z±�x��`���F+�Vd>��ݔ�m8vZ�`��?D�m8�52D���x��mp�kG��&g�?o���w�	}x�m8����F��2DQ3�y�&D��Zm��	}Ԁ�+�Fb�	�m8��X-o£z�	}���&���?��h^��h͓����a��X-{Z�b����m8j/|Z�kӃX-��a0�Zm��ӃX-c���}��`~Z�b���Z�bք�-6�X-I�⁀Z�b�@d�Z�k;�k
�ڴ���ak�F.
�	}�0�k�s��d���?D��zk���-u��	}�y8_��`ϔ:uD�k'�|a��8���	}����U�h��m8z�m8��	}�)�i#�	}Z��&z�m8�Z�h��?D�����ևZ)a�m8�Z)�Z�h
2D;)��m8������8��h*��&���v��$�bO��&���|��h���7�bŋ�-��Hm9�2���a���|�2DZ=V �Z�h�)��lkU ���z=��3)�Z�b�Z)`&����Z�h��?	��$�`8�� �Z)"��z�$gH)$g��h��;g4����zo�+��ےZ�h���z�ڍ$	)�7�)��g���*
)�Z|~߁�|�Z�hr��d�	�lj�d;����ܳ�B2<��z��hC8�h�Hm�Z�h2��*��$*|~��hЁ�|X���p
X�倂k��I��h�)o���Z)~�y��
��4b�ױ8�Ud@��k�
o�Z9D�Z�h��W)��m8�hm£6+
})�Z�h
��.
}|~)9D���s+D�x�Z9Dc
}����q��zPB9D���~��T�x�9D�&�����-*%9D���z�Z5nC|~
�b�妞��z���	��c��Ǽ��=����d	�<�:�x<�U`�Z9D�Z
}o޴N��8*���ǝE���8k��`%�8�:���`��f8�Z9Dj��f=9�9D��8 
}�\-�5n9���fT DlL6|��Cv�}8bVB�
���5n(��`,
}�Z^m'��j&6|�}8q|~M���l�8 D�Z9D_�8�ڂk� D��"g��L��8_+ D
޴��g�m��"��?������-l
}��ݧZ9D4J D�K
}���?�&�;��-�}8�����`ԗ�թچ?a[�*-9D�ڇ-+���6|ʈ����xC��<��=��
}>�@+|��ƫZ�|��|��:n�9D��}5�(
�zad恟QҭZ�x��|��:�z�
�-[5nk�Ȁ́�a�8>��Rɽ�`�
�-�V�o�|e��ݿ����8��%i��ktٻ�`t��Y���퀡j��R����7�EϮZ D���`l�:nm�8ȍeM���`m[�Z6|=��k�b����ٱ��`7�X8�6|��kԂ�- �:nZ)|��Cv��)����j�Cv%'�Ă).��(6|d�����a�)/ݶ����<+.������|'�[d�f�c��'8��c��|,�$�ډk�)�,Ĵ풉kH�*#��$�w��ډk��$�F���xŁ�<��݂�$��|��k��)a�:n�[d�z��|ݎ�c6�)��-|��|lie���JW�)��'d�o�|,(�|W�)<�$��)XwU�|tI��$9���'���0'ѻ3�)6�|&��aރW��z���jo�M��)��|�-)��)U?�i��}�ϸk�zџ�&��k�=|�	Mz�h��d�ܪ$ۯ�@�=�,�$Ё��7��&�Z�|��)�%!y@�`��)���&)
���&E�)�#'*�|(�`��k��I9o&�ډk��=D�)���=D(��e�|���~�)��`i��<��$��Yd{��b�Z�|H�=D���&��`�Z�$���h��&��=D���&��h���b��=D�Z�`�Z�$��|(
�ځb��=D�Z$|��-X��z�ݸ�8$ǁbU��h�������`�{���Am���&H�=D+'�|�ځb�'D��h&"Dv�|/��&��Am=��$g��bA�=D ��&��b/$|�����Am��i#���h��&E)�ځb��Amu�Ame�|BS�`�{���&�)||��|�;|]��b}�"|���z,�@+�����&@3�`e�8n�D+�ځb/|�$|;�=D�Z�`�3)�$|)���|�dw�Z�&�`��{�?�<��*�Zv?�q���Z�`��=D%$|�;D��<?��Z�k���܀㴍����ځbz��h.�� ?�Е=D�Zi8��"|�"D��=D,��<��V���"|�i8�恋;|��Id�Ёb��?���;|׉4�|�9��Am��k)�|��;|���m�)�Q�k�Z"D�?�,�;|��9��Amm��-�)Ӄ;|��k��Am�Y��kZi8`K�d��;|���߁"|=N�k�:�ˆ�>���.�� �Id�)��|*dw��;|�Z>n�ZdwBϧ>A�a��;|�Z�/�-g��"|��|��;|R�$u��"|���|�Wm�Z�h�$D�;|�9�|��z�<n>�F�o������!���d��b���$ʒ�~��>��f#�&�|i��>?���i ��>�Z<n���$��ڒ�-g���>?�Z�+�X9��k?������Z<n��)�چ$�?���+#�|j�B� '���<n�Zi8�紏�;|d�;|�� 'ه'a<n��b�*<n��;|��o�]d;}���$~��j��b�;|9���{��Z}�+<nm�c�	)X���"�bH�;|蘧>ㆧ><n.Em��8� '�Em�ڧ>�Z<n
�bՀ���Z�|�&�ZEm�1�|�ڧ>�þ�D����|]-�|	�|�Z�b�h� '�ZX92���'Em��+�b�� '��Av���c*o�Z<n[>Em�Z�|�6<n�Z}�oDo_�o�Z�|�e"L{¼/<n��h�D�<n���b�Zo\<n�چ$h��jf?}�}��?m�f�F�k8�oE��`NU�:�x���9�Mh�f#��&D9�,��(�	�|��&D.}�}5��V�bc��ih���&D�Z}�&Da�b��Bd�&�hR�xw���+}j�JvA�Bd�Zo�[�o��)�?�hH�b��)9�۝����|�Z)D��)��� ao4Em��|�	)��@o�-|�?�%nՀ���	�|���{_�ZEmm�-ǔZm��hs�|���(��&D)�e�Zm������Av�Z�|��&D,�|��)��Zm�Z�>^�Nm�����i[)DN}��?D[Md�[��Zmp�?D�
�Ӄ?D��-T�tA��?�?D���G��.�� »Zm[)D��&D"%�fz�|BY}H��?[)DL�?D������&D�*�����'C�����0�k$�-́�a�~%9�29��&D� ��)r�&D��-�m81�?D{;|��Bd�);�)3�)|A�&Do��+��?D��|�-�)|:�k��R�T`8m$'���O7�)$'��iwg�@d��Zm�����)D��b[�km$'��?D�q�3�z~ڋ�?���@e�Zm��@dۈb�Nv	�@d����
ۈb[��b[�k(�)7)D����@d���ϋI+G_�
�?D�Hm��)|��V�?DW$'���b똸��?DޤHmmZmh�@dۚb�@dm��b�iw��b��f	�?D��ۈb��Hm)��$ۈb�?D[)5���
$�$���bK�@d��&�m8,i#%Јb��u�d���@d�m8��|e[���iw��@d[���')B/go���@dy�[���,Ђ�h��+�2Ñ����
)4���/)�R�m$'�=��|m[�L2���o)tFd[`8��'�DFd�)�!!D��,��@dH��bl��,“�h��F�R��[Fd[)ۈb��|�i۸� [FdjNv�Evk�X�i��b![Fd"�@dGK�fۘ�,���b�Nm#[~~$[^mv���q���%[Fd��$��Hm��Y��&���R��h!��a����Q��h��|k{�5�'�)^�`&[�^�~~�)ȃ������b�t�m����Fd�)���g^m���YEvA�`'[)���?���(�����y	��m[��!!)[�|*[Ev+[){����+w��)	-|,�hӏEƒ��S��,�^m-[Fdw���S:[�]FdĆ�?@^m��z>����+m�)����nmw�-D�Fd�[m-|.[Fd/�hρ�0[Fd�}7�I8��>�-|��4�L�G"��`p�-Da��x�	}��81�82[Fd_�H���>3�-De:9D4[Fd5[�`Ą�x�-D�=�6�87�Cv�$�/� D� �`8[�`!>^m���9�-DX�Cv��M+���x:[�b�^mT�CvA~~�#uo��+X�}�C^m���;�-D<[�b�	 D=�f8�����	�"g�-D��}��-D�a�M+O�Cv(�������>[o?[�|m�8@[-|k{�A�Cv�|*��Ȁ��k�mw9��׀e�B[9D���`��?wKz1܁#u�CvC[�k��&%;|�#�4���`�)|j�k�-|��Cv��b��}?�Cv�	}��G"D[mw�p�2o��HvE[mwF��`�8G[ob�=�d����7��8H[�|!��B-�8��}j�����Xm��i:x�ҥ8Q�	}“�`���oI[�|�#u������CvJ�Zm˩xK�8.����'a0�8Lۉk�FT��x2{�6�8E�Cv)O?���$�|8�$ ���c�\v��Zm�|���`�|�Dd\�Zm�)'\��-��E"�k��)��)'l�&��)'�O?M[H�o��m�����bN�)'���kH�-��Z�O�)'P[�|�J�o�_|�j�H�8��h�[�j�-��`�U)󑔒dƈ��)'Qۉk�����g���|Ȁ䒐�Zm�C�hR�)=�
��h&���)'�&g��tU	�E"Y�Hv��aeO)_��$�)''�HvI�Zm
�|v���ҀZm��8[�]�$��fO�)��Y$�z��)�hJ4b�)*UTg58�S�=�S[
}���T[�-�0�h�V)������$rM.3
}JW)Uۉkc��c V[)���e�-������fW�)'S�)�	
}"=�&a��)'�y8o��cZ�x���$�E"s��kX�Kd�gj)�)o�	Y�Kd�I���l�?Z[
}��d�0�x{�h�b�)'m[���ae�T
}J�.�!�E���x�Kd[��`\ۃbރ�][F��f�&g^�Kd��a�֒	��`_ۃb`�Kd�2Dx��b�=$|�7�(�L�����`΀�b��&9[JV"�&a�Kd�:�|�&gb[�&C��`��� �f��gԵ��bm{�n�Tل��'c�Kd��Um*�F�d[�iSS$|�Ŵ�-|���ɍ����Neۃb�'u�
}9+$|���l� u��z9�a�|�/د�}ۃ�����v��f���ff��Й�J��I�ϣ}g[�`��Kd>dw�����Kdh[
}i�}j[�&�}~�|k[
}l[�xmۃb!
Xm����k�M�*��b"�Kd��ce� ��bo�Kd"dwn[?�[��˃b5�Kdoۃb�z0�Gv���xp�KdA
��q[�k]��`r�Kdo�i�$DL*i8sۃb�����k(�L�t��`n��`��hs�eMu��`������xv[�kw��`7��`J���Y����G�����"%�fh��f��-��x'j!�
j�x�\md[�w�5�qV y[�k%�q%�i8���5$|�
�ǫ���;T����|��E9^m��Rg�}��q%zۛk�θኛk��*������V�{�}��C��+2{�|ۛk;�&�}9'�{�|!�Y�3��k�?��$Dj��k�Id��4G���}�`��w0~ۛk�	�f��=����ϋ�$�-|Y�O�	�E9׀o�z��x[�|D�{À�$�iv�f_����8�`w߁Nm[��=���z��&@ ϋ#i6��U�Jvo�i��Xk�2�p��))Xm�Yi8��k���i8�n�7"!�[�b3!�h��w���\��k����7��&�[�`׀���Xv��i���[�b�[�`���k���&�
�J��2[[�`a�q%�|��Π��k��(��b���&Р;g� �Q�l�Fm�+�44�`�ɀ�->5Od+�`#�)|���&���&*�[��;�`ܜ�&��́���h[�p��-� �i]Ԃi-Od
f3��`��#g����#gh��{JFv�+؂�h�����*{�v��h��-{R�[�`u�#gk��ͱ�-lUm��&�*�`�$�Od�[�z�[�`�[$u��/|��?�[OdJW�[�`�JvoROd"�>
�>���&��#gm[�R�#g�Od��z��ݹOdw$�>��#g��`��&��Bd5�b��&>Xm���&k�#g�	$ue�G%�#g�[�z��r�+�`�Od�6|�[�&��b=��4�[5G�>�[)D�&�`�[�z�%�`�[�>�>��@���aA�>��h]�`HMd�	)D3��k�����L"5)DΈhe������9�w��{�5����he?	���|ʘ�#g��	 �[)D�u%	�#g�ˣ��#gZ���Av��4�[)D�G%��l$u�&�|��m�)D	[�e�G%1#�z�
ۖ[)D�[�z��#gp�Ң����H)D8�m8��u���Bd/e}	�`)D��m8?�����)|�ݙ[�`m{�d�m8�$�>��)|��[d��d'�`�we_�:*
����z:�'��>��6���m8)�Bd*�����,!�m8�2��he,we��F�	�[�$��-|��m8�ۧ>)�����MdO��6{�	{���O��*i�?���hb��>Q�μ��Y�[�����(�ۺj�ۘb���ɻ�he���{��f5��F��X��\vc<u�[)Nvo�%A
�Q)�2'Ɓ{�c)D���k{��dw�s���"Nv�K�v� �2'*
����jNv��m8��2'.9Z��\v��_fm8B�h���9��6��z��bwNvϋ�c���&�h��(��`#.�բ[�hN�2'��`#
��_��Xm��2'��m8<�h�2'��u�N�h�[�,��z�,�h��k�m8���z������(��i�
�
~���z�2'�}�'g�2�[���-Nv�ɰ�ہb�&۩��z�[dw݁�b�Nv���z�Ьہb�<u�F������z�[�h���z��Am�~~���j��-|7��i����Ȁ����zgm��b���zQ�Am�[�h�:��2'���b5<uւ�{G؁b��z_��bЁ*i�[)|��*i���b�[��/}�2'�,�*��Am%��'n�b[��[}
�*i�2'�[�f��h=�������hV}��b�Am��v�Um*�Ah��uE�h���zo	dP���*�|���z�-|���<��e� �[�hy��zւ�fI������z�x%=��㷆��{��b"%�f�'9D@�Am���z��`�[9D�%� ��il9Dk�+�Am5Fd8[��[�xx��z�[-|�/����*iH�;|���i��Am�[-|o��&=})�)u	
�
�ND}�����U���&y�Am��)u�R�1=�����x��`��Am��8GT-|,�-D��;|����,�&c�)u��-D���`U���W&��;n�G�c������;|�-|���`{�c��)u!([�3�bW�co�f#ȒR��&��)u��j��c�[�b��-D��c[[�J�
��n$T�b�;|�[�b�[�c9��ʞx%�����}�[���g��}�[-|\m��b�[�cs@+�Nm�դ#�b�ۚb����a����ۚb;9D�X�݁�ݿ��i�b�9D`��V�3�c�%:�ۚb	�0<��bҀ�i��Zm�[-|�%�cv�3�.�)�*���ۚbm�fr�;|��)u���h_4�cejlb�u��i���i��bx���Ԃ�`��)u�[\m{�呀�Zm��h���b��}��o�"+!	��h<�-D4Em.��b�+!R+!��`��Zm��8#�Zm�,�,��
����c�ۿ,�?�b��c�{�[+!a�����Zm3��be�eu"�b�=c���*
+!2��f('�B�b$'#��Zm��	 �&&���f6'�[+!�;n��R���
)U+!eɸhD���i�CX"+��k�+!��
)�\m��	�Σ�j��ۚb(�Zmo<io��+��Zm+!���P%Cn�
�fl��k�Em�a.��b#��,���i�
)��{l��Zm�$��i"�F+O�)�p��+_�����Zm�ۢc9W��i#���2��Ӛ뀘?�[+!�)$ !C	+!�Zm��%��'���z�5�1+�Zm9��`��c����|%:F�h��&�FEm�ۀ+!�|%g� Ё�,�
)��)Z���k�WvȀ����[�`�_��)��S��*iA��c�����[�?��)磙I��	�E=��I��y��[�`A�)��(�Œ��#!�/��Yd��	}
��Y�[�`��Yd*-u{��\�}��`�
)��u�Q)��~~λ)�H5uQ�`�5uu[X"s���[����h��1�� ���YdH�����(���?D;�`��Yd"��h+�	}7��-(�9�Q�S��h�W}�}X{�Uv�[Vd"�b�1|%�}I�b��Yd�2�b(���G��Y�:�Q-:�!g3�	}1��c��Q-������)
J�b�b<�Yd.���j^m��)�Cm*-u�����)����".g�[�`���h�-uH��X�2D��h��y��`�W�b�Vd:Y-u��i8��>�	2Dc�]�C@�b�&^m��Zv�"�H�����`�[Vdi�-|�}��Zv4��b���z#k�bo���`ML)|[S�kW�y��Yd⁞��
�=�^m�[?�3�kVd׀�c�Yd��?
��� 8��h��b���z�Ydn��h��}?����1ҀQ-(�����k��3��h�ƪ�Vd���`Y��c���k{�fŁ�$���h]��$	}R��`�-\�bT����Q-��k�u�G�Zv���$n���Q�/!��۳��c.V \Vd"�Cmm��r2�ܟ$΀`#�mw�G�bT"�&���l$7��
^m��,�+�?�&ܩc�;'[=�?a��$Zͩci��$b��Ё*i�;'ܟ$���z���c��`ܩc�f�ߔ�����z(���݀)�/!(�;'$�i#�hܩc��iw�
��Zv��C�遟$l��$���*�/!c��k���Zv�� v�Zv,��$Ќ�W��Zv=��")��f8�聨?�	\�-
�HmU�h�"��X���&
\mw�;'�4@�+�p]d�
}�<i�*i��+Ł�$ :�,9�S\�b���${�f׀�c\]d
�i	�Xv
\�b>P�h�&�{��W
}PD]d\�h�Xv�!�b]d�� ʋ�I�
}\�h�[�b\]d��k�g�\��c�����f8b
}\�,�hl�b��&��h��ܫ��	�;'��jÄۀ��9U�h�[��,Άף9��5
�b�Id�]d\]dܡjc��"j�!�ף�hs��`������\�b�Gm�	��*if۩��h�_��-�
���]��`w��9+(�hv�'״�k��Z���ң�=�\�i\]d��x���>��9\]dM‚k��`�o��x\�'���܂k�&�.�؞����b�]d 
<i\�hƢ�9\]dҡO�	�b���i��h޿Bd��`�b�-]d�h�#�x�)�h�����'6|�h
�*i$�36����c�|��l1]dJ��2\�h\�b5
GmS��x \�c�H�h�и���'�h�5@��i!ܡj"\�c���9����Iv<�-$���c%�z�z�i#���9��c��c%4��`雡j�դk��\�c;I+��9��i	 �>��j�I+o������`��f��eM��{]��`]��?#\�'PFI+���
������>هc%���k�x��`� �cȒq�b�[d���x$\�ci;|L�	��e�%\�c��&y��&\�'#��>�F/RƮ=��&_ۯ'\�'��&��2o�co���f(\^vo��a����[d����<iv��?���~v!`8cDN�Bd��)`F�f��Y�-�[d�� cH`8t��݄�Ga�����'��b�� dw`8)\<u*��&{�f�W�c���_���OGI+?�c+ܦ,�`8�_�*`8���&�5a����j�f'���=���$�*i���&�$�>(�c,\�c-\�>W`8�\v�㸿��PL�c.\\vh��bc�|�q�݅M�ǀ	 �>�D`8/\\v�=� G`8jQ�")��[d�	�jTA\v0\��Q:<��b�����)!ĉ[d��j�<u���|!��&���&��1\=g��sq��&`��b�<u3Ev<�f2ܘb��QҎG)!�`8x���dY�=���ne	`85Ad�I�J��i�*i�_�>��&?�h��f�frG`8���Ɓ�v��3\\v{��h����$4\`85\\v������,6\?�_Z DA`87�a~:�ۀ���W*���r�ף�=��Fd;��b)�a~��,��b=�:�`̏��9<ub��b%�x8ܘb��b�i%�`,
 D���d��4@�� Zјb9\ DJ�ףD�a~�g�5�� |:\ D��>9��;\�xb�����H��`�*$u��`��k��fu�5a�a~k��'�m#�$u=g<\ D@�x$���9���h�`��ze�����j��>!��zeD5gU>)!��h�����3?�(Ev��&��J����f�״f=\eeܛ��4�ze6o�ze*{������O��&��e���a}%i#���&b�>!��1��?>�a~$u��;|d��h����	���{�"F?��
_dA�>!�i#[ʴ��?݁g--�a~n��Ղa~��xl�g-΁���$�4�ze��`�K�&�*i^�;|��x?�8@�;|\<io�M+r�Х�>��&�-�`�4�c�)��;|��Id:�x��	ۈ<i�&�G�&�۰��`A\�bj������x�?�C�AEZ�s�Id�c��x����IdB��`���xM.�cC�8�X+��ze/�����`?�������kT�b)� 
�*i���fQw�y��xH��X�<n��ze	�c3?�p��jD\�be�c耧>�X�Eܧ>�́D��c��=�p#�c9�{���#+�i#6����R�%�+�)Nm�\m��jb�&!���@m)�;|�UJ5�b� �?��-;�,eШ�� �X$&*�g樁� �����v�9!F\�,��f�A9��=�e~{�ʈ��Gܧ>Q8:���j�	e~")�b~��xЁ*i�9!�ʴ$.�b[>9!3�b�����c<�ף�8gH\�bQ��>g�cI\�bJܧ>���6K\�b��,w���9!z<i����	���*hHe~7�c+_�L�8g����%�bQ�cM\9!��腳�8,��<j�i�����iЂ=�k��/�� 60)W(:���cQ;�])P��	�N\�h�@�h�&!G��y���|e�nO\�<S
�S$|��>�{�%)�R�(�,3$|)(�P\�,Q\}}��c<�<�� �)R\�hS\�bj����&��&c)T\9!�)Uܢc��)c)�~eq$%�%a�/b8
��Yk)�ˢc�)�)���-��b=Qҡ�$D�L�b�)}})1)V\�h�e~큢cW\<ivۚ�h�I+	���	��o8ʫ\T���G)�x���l��,�*i'�	�{�z<i�8�x׀r@<i��'J"�'��?D�����Yd3!}�	ێ�?D���
|
���<��iw\�i#�FC�Iv96�X\�hY�.�������&��xه=��,�h���b���?�h�))Z�?D[�?D���^��`��c~�����Cm.�-\�Cm*{ӏ�h�{[��`^�ۜ��d]��z̈́)u^ܢcg��`d�Cm�����1#�G�c~<�f_ܢc3�	�A�Cm�<iq�c�`ܢcB;|�c��$D؆)u�<g��ha�Cmb���}�Cm�fl@d��c~i�Cm9{��DmA�Cmc�}^�c��|ه�iz?}���Vi�x/
���gd\�`���h��ie��hj{�=�}��|�'�� )�cf\�xS�Cm
ߴ�?g�Cm��Idj�?D4<iۃ�(��`h\<gI�zeWf{�%a��}h��߬��l)�i8�i8��'�.�=�{���P��i��}���hl�$��?Dw�F�߇}<i8/mw��Cm\��`i��`��Cm��q�j܍?���i8��&���h�6�-��h�i8�c��$k�ze�����Ki8l�Cm/IdZ�}�CC{�	g�e*��}m�CmK��R�L�D<g�U ��}i��c�f���}��>�f�[k�i87̩c���k��¢��������y�\��b�L%a��c�tfQfЁac��hd�7(��c�}��E9�e����X
��j
��.���߇��l���x�$9��S����Fa����n܂kyi8p����	۫����ze��Vp�i8��h~��-��zi8�Ć�2z�1o\i8���1D��r��>WU�r��j���ki8�́��&�f8`7k�ʗf8jE�i8�����i8"@-uk'qp\�-ϋ�c
��v��<�=�`����e�o�2�E��%aqܙ���`���Ԫ�n����߈,)DT��P"��,��}�'Gmrܩc)���s\Gm��gYt�)|�=����(u\)D����28�lʇi8v\Gmw\-u��%a�	}v�4�x��h*�%a��`g~Δ	}��h�}�j��N)D�%ao��hZ>-uj*����fw��'�'��
��y\ Dz�}h-Gm�)|*{‚��j{ۦ��z��n� D&�����'c�)|��hD����f{\�x4�}|ܣj���ܺ�f8}�)|���d��hQS�5 D���h{! D��%aGm~\�i�}��`�"uDd��@m+�%a�\ D�"u�@m���h2G-u=��h���T����>T��`�܄?P��n=��5�	���] DF��?"�`��Zmm�@m�\Gm� D�,�`�.Gm<G�z�	}:4�c	Gm9�S��Ɇ�)|��	}
�_�@m^��?ތ�_�E��@mA��?��@me� |���V�Eم��`�܄?ߝi�΂	}��)D��@m�W���']�}>�'�@m…��U� |�܄?��hm��()��9��+w��')&D�\�c�ٙ� D�܄?��zvA+5�}��|���p"�z*I+
F�i��c��m8��Y�\�cX���ه�i%�z�|���c��&��܌�@m(&D0ͅ-5�z��@m���>�)�i�W|�\��\��c��@m��"u�� {#�
�2�P����>�܅-9��
&���-p�)�B9o�0ёܻc���>��g��܄?�ֻc�\
���Ε�cB9_��>i�{�܄?�ܥ>	�ƶ��Sg.�cn`8�ۅ-��n*�Zm<B9��c�\�N(`8���>g��ݸ��((��-���bҡX޼O����w��-����	�1�\�c�ۘ\�?��|��i�
}aܲJm
�Jm�%�<�\-|o�&|��|�GB9�Oع`8�Jm��og�j�$�?��?b�{Ӛ�}�I�h;�Xm�\�h.�N���� \�xڕ�o���(݊�c�\�h՜���o��9D��Xm�ť>�ܻc�ۅ-�Jm*�|�
}�
�<��yp��>�\-|e��>9���Jml�<�飚-|+(�И�-�Gv�-|��{b�o����-D-|��Jm[�h��-D�&��
}�-D5��n��Jm8/'��%a���c����ܰ�[�h�B9��-DJ*A��h�&PF�?�Iv�=��r�5�-DH�x�Dm5�����i��p�-Dj��J��	^m�\�&9S�h�Jm\�Xm_+�'Z
$|�N��*#(�I���C���֣	)�5�<�߁�)-|�\�&�I�����cIDm��c#�|��Jm���%�\�&N�JmA�l�L9��h_[��XmDm����
}[�r��jg�hj#�&]/}ws��\�h�*}:�թ\�&5�&�H%RG۪\�hI*}Y��㎍��5�$D^Z�&�	���k���{�m{ۦ�.�&C
��}������&��3�&��C֗�k����m�}�-D%�%am[���L9�h�Dm튳�$|��L9=�e��,i9��I�1�'��b�ݦ��DZ9F$Dm���e��x��$�4'�2�&��o9�2�{���c���-�&*N}��%a�\�&{ۯ\�h�)�\�c�h�|‰�A;�b��25�'��$DXW1dF����I�<�Ǫr�%ao������,�bC鴘7�3��$O����L9�I��|��$D���&��I�$�S@�NmܙE+���<�Nm���<��}o@+�cQ�+x-]}|%Nm���<�z��}k�G��\Nm�\�b��Z\mQ�{����V�����"%:B�F9�x-�#�X��%a��j��<��}_��<!��J�F9���\Nm(�����*-Nm(�R?��{!�Ք��.����������)!�\mo���{ۦ�)!��9{}�I���ǪyU\mELNm.�o�{g)!m%�����84'��F9(�R?�\NmY)!�`W)!���<,��<F�F9�)!H\m�4Ȁ�z�o�\��=��Y9��L��or�¢a�	���}@�fՀq��Zm|CC
����)!g	{�\NmÄ}t��Rn)!�;NmŸ�<b�0'eOҡ7��)!ehl���a-��<“�<�Um4'?��%�\n~L��-s�S-E�i�v_���k냮����<���ETx-���j�R?�
f�[�>��W�܋k����Q�iX۷ډF9�>���o��j@$u뀼���>9��l !a�|���nt���h�>���8
2D%�)�C�
��-��)���e$uu�(�9g)!5�����&�	)!����%a�\�>-7)!�p��܌-��&�\)DW)!��)���&;��-��a)!���<%�)�	Q�V)D�����)̀�{��܌-���&W��A9��x��e/�`�)D�܌-b�	ێ)D.�
)�܌-�Md�\)D���aI�
)�v�…eM\CMd��&�\$u�\)D�):�)|�Md%�)�)D儽	���x)D�MdC{��9�x>��-��#g���h����((���!g��`���ho���΂!g�ϣ��)|	�>	���r���)�8}��!g���x_�)|�>���h���<�aI�x��)|�={�;�)酔�O�&��܌��`��O�D�	��!gȀ��!gI7痁_+w�r�9���>g�\�{e�)|�\}�Zm��&Ĥ�-i!�y�)D.�"u�\)D�\�{�\`#|7}�9�܌-T��+5���fU�$�����9�)D�5|e=�����&��9���(%
�`�
�Qc�����aԀ�(�{�߁�9�aj)D��9a��h��)|�{�c����Im���9Xy0h��9�y�b�i�i8Ȁ�5��[���3��j�����\}���x�9�"u���l���h�\i8����bЁa8��h(}�����ӻx�܃?���h�\i8ʈ�
��9�}�h��9�{=�E�C������`g�!g"��?��.=�ގ�-!�n-��H)[�k�*A�{�&M�Uǩc�\�?8�k�\�/���b:|e	��
CaV�"udE�R�/��� ��c�i8��F������9�z|���(��b�)�����\@9�����9�)�@9�\�h�P}�0)�ܩc��J�#)��9R8�R7��c���
���˧C�W)�)�\}h��9�1�)��{d���\i8n)��n��k��zeg)���{�\i8J��,۟�/�;)�'�k)DZK9�i8��=��}�)���/9���vТ�	58�\i8�f8(J�)z��-�{�W)��i뷩c/'`&�|)6�9'".-|�	�ί��/偄�6�9'��h9������o��
�xX�����n����o�a/'�Mm�\-|�\�'J�A-|��c!"Um��-D��/3���_�-Dð�])�\-|�
�x�\Um�-|
�'�����
�Um��.�_�-D�
�)�\�'���z�)�J���-D��-Dm�a*�|"۴�)�	u~��-D�\�'�)d�M[PUmc&���)��-DX�J����-D��h��;u��-���z~�y�X���-D���-�x��hl��k7�B�ja/'���q�F�Ć-D��>��}���.kUmV�-D5���Mm�N�\�h�-|�-|"Mm=����Zd��'/'�\�?�9'J��\�x.��+�Ҟ-%�x)�����|5���Q�3�l,�|�\�x�u~Um���h�\�'f�9'�\�ha��h���b-|:�?���\�h%
 (J�G�UT�\�'��-Dɀ�h�	�G�-D��|m��iϜ��Zm���zy$�c*6Um��-D3�N���z���hj����,	��?�-Dl��������h���-4�-Dr��`��,w�}f��c��&p٢���h]�c	��?��{:�}��hIF���a݌-��_۠�,w�p�Pɿ,��m8�|���]�c>�}7��h�Uɚ%fx�O9�[?޸�m8ĂO9]���O95���~e�;T�m8�	�ݻc���I��d�����{������{��,��F���h��{"��?���h?���z~������h[�	��{��t��A`8��-
��{��_��o��h���,��؞J��(��D�*����m8t��h97f�j�j���o���?O��c8�xګ�ؒ=��ٻ�b��ǁv�9߂�c�W�e�˶��X!�|�[?ݢc.�֣l��&rV�c=��{���w9-u�Xm��(i��,��-���
�m8��,��}�|�Xmݢc#$�z��(g(�Ӹ��d�z��{)J�*�-]-u9�Ύ�m8�XmŜ�c��{
��nݍ?n��cD�	}��n�O9[��?�Xm�(g�@-u��iw��i��-��XmJ���i����Xmk�Rp��(g9�V�ހ	}�}�;|��:T�$cb��'��]ބ2���cJ��}3�;|��d���'�;|`8��'�	}ݷ}�#V~V��;|m4'>�?�:F:#g�͢cÄ�n]Zm��,��'��a�h�	}���'�Ad]�`L�Z�����W�(g��;|]�`���܀��dݢce��'��Xm�xڝ�b�~��ƥ�o��k��?$�(g]�k��n2��gG��'���U�(g9�W��~~���k)J�v��eVdR�k��k �Xm��k�-u����?~~ -u��N>b�a5�y+�i��;����#�!�@m"�	}�{��-g�@m���݅�N>3�^,���`�*�{#]�-ު�'���ڋ��$]~~z�	}��(o~~�Z9�mwj��R�jT�����e%�	}���'��'�@z����{���{�i#�
&��'������E'��'�{(]�&9�ƴ�{�Bz	�Ё�-��({������{)]�{��i6��N>*]�a��{e���+]i#��-m�&�`�)i D��&��klie����$
�W�ih�@mw�j�(�Z��ݢt��W�&�b�`ߝj�)��F�=����~~��c,]�b�N>Jr8j�/��iςk˪b�-]\m��7�ۉv�8?��(�\m����cl���.� S��`� �,B|~�$���`�[�_��N>��0��|���$�zj��8Q\m
�-m��n^\m	�b"��zk��}�u��X�7�&h�c�
�{�|~�|.]\m���o�D\m |~�
��9�S/]\m�|~��+0�{��|��튼׼�b�1]I95
}��:2]���\mx;I9��f8��I93]\m4݊-5]
}o�i6]I9���(rKk/�9D�]m�-�I9�
}7]\m8�8p�?D�	\m��=����،�����C�bԂ�&
}g�?D6�b���ރv���GL
}lie�M%�b����Ɓz����Zm�.]9�?D��bc\m����mVm��b��|C3\mwF�7Vm�|~��f�:]\mIJa%k�o�\m= Vm;]I9�|~�5Vm�\mADm<])=]\m�CVm�n��?5�?Db�ܘ�'>])m\Vm�)?]�h_�`��i�$|��}8��ּDmf)@�@dA]VmI9B])��/C]DmrFVmf�)D]Vm7)E])F]Dm`���_��f���(��`�|�Vm�)�DmG])H]I9�DmI]
}9%���`�ա�*�^9�BVmJ]I9�ɻ����):�@d
8VmK]}L])�)�}D]����o���}}�'�D�>စ-6�^9���x��ĩ6�-=�W����n�a%��l��^9Z��(A�?D��a%M]Vm��)N]VmȀ�	O]DmP])a"�W
:Vm�Dm]��i׉В5)�=Vm�f�耚-k�V���ց�nQ݃bʞa%�=�R]VmjDmR��$)Vm9�2�>�����b���x���&S]).��bt��&�m#T])�$�$U])V]�hW��&X݃bY]�`�3)Y��`��-Z]�j�yg��be��`��iЁR���j���|���'"�)�
 ��h뀼���e�C�ޣ�ȴJ��+[��h{�j��&��}��b��}�/u�yR�$9����u�o���\�}"�)F�.�|]��&��'5/u(J����&�F�/(J���F9�/u]�}��j^]�i�m~5W�67�x>��&��}�ۖ������_݃b�K�k:��k���bm4'��}��&\s��+�i)J���O)J��?��j~�i8�z�݀)"�F9o�}`]�k*�F9�'Y9f:i8a��&(��ǡ��b���bb��&(��ǂ�F9�3�iS�>����c]Y93�>�Zm���+��&
�Y��d�:gJ�d�M9c��e݁b��&��M9��|��b�V�y�Y9��}"�M9fݦ(���
g݁b�?Y9��yh]�i����ji]�y(�����i�
Dd7)�/j�}�Y9�M9)J��ަ��bo���i��(}؁b����G���ځb��}�����1k݁b���l�}m�F96�^m���<B�M9)�ަ=�o���r�t�}n]�&ӻ�b���	�������c�29���ݢ,�{o]�&���p]�kqݧ(e�/�l	{Ё�%b��(�&H:Oت$u*��@kĂ}*��iЁ�1rݾ>s]�&tݞ-Ɓ;�[p�uݞ-w�a�&�&��im�aY9 !�%�?�7�i��Jv݁b��Ԥ��\w]�bR��b��`ž>)���x]�`t��-��`X�i8���>�`³�	y�M9zݞ-��`�&�&1Ɓb{�M9�q�|ݾ>]�^��M9}]�`~]�bݞ-�b�����.��;|)ì�Im�)��;|ЊIm�]�`΂�h*��&��h��;�`Q٢�C�&��^��@k�]�&7X�`l��`F	 �#�b4��`�r��]�&$ !���`{�뀼�<�Im?��s�ce����e��>���`׼Ima�&`���d�Im
NՑ��t��i֗C�j��m"�x�ce��)ЊImk�g��Imh��h"��-�ݾ>e��>%��`���ʃ��́�$������-��"u��ce���h�����i%���ceh����ݾ>v�_��x��ce�]�b�ݞ-��h�X���R��>J���
�0J��[�]�b�])Dg��k���`��"u�	پ*�i��X�ce�]	 �9ߑ�`��;|��Im;�b��`��`6�^mk�vh�&!*�(�)D΂�h3X��]�b�����)Dd�f��`�1�bん<Y�ݕ]�b�ZmP�EP�)|���i=��{���h�9!�]�&���h��b��ce��7�c�����`���*��ʈ���ŗ�	�i�]�b
��_����
c���c����*��i9�����,뀼���m8��,���ݻc�Cٚ]}g�k�ݍ?a�b�]�x{Kf�Cm���c��m8��<k���6*ȣ5��cwZ�(�b���^�&F$@9�}Ä�i��&d}r�v�/d�^��)s{��Cv��W텔�`���}	���i�}w�OҞ�`���}H@9�]�bt���9���m8�@9���,.�i��>��&�b�g}�]}e���ܤ�b���}���c�]}��]�&��o�ݢc���&�]�b�]�&�?v&Mm�8@9�]Mm�!}�ݢcx��c��i�����]�&z)�
�&�U@9���Y��j�
_m�ݢc:�h����]_m���-���o���}t~��h�ݢc���}���m8}�`m/��;u�Mm���Q~��ɧ�m8�]}�

�

����&`���]Mm��?D��i�Mm�&	)�����_m�Mm������9+�ܤ?�磥��_m@�;u�)um�a�����}���]@9J�������;u�]@9����]_m�O@9��?D���}4F�P)��p	)l�ݠ�k
��_c������}�+�`�])Z
�kb�a��c�Ey��IdĂ�,�|��d�`%�B?��?DԀ�(�)ף����`yMm��Bm����	)�ٶ��>���b�$�`��i��Z!�k|ɈbÀoD�h-D�|AÁ���b�]�k����3�`�(�ky�H-|��Bm�݈b��|�]�h�XDX�s��]}���i��;u�	)@��(l���B-i#�?D�]}ii#	)��&�s��i���b�����i#ji#��hjw��}&7}�i#a��݁�i��;u�����&�]}��?D�ݞ-�Ȉb���iM��by��);��]}9�&W�$$��-X��b��{%�UA�V�&��8�ݞ-�i#�ȴ�� �5i#�]�&�)��&��Bm<$}�{]��&�i#�$}��kti�])o�<�]�`���]��&��<;������� �]�b���%塈b�j�4��&3�M�]��&ti{��i#���&�|�]�b풥%c�@d���bt�~~.��{I�t!�b��x�t�B�b|
}��}{@k�]}�i#�cF�����^� ���i�]}��}9�Ů�j�,i#���&�]}ii#��Vp�]}���&�7�b�ݥ%"��%��$*�&��}�i#�]�{
��"�:g�����&Y-u��b�}��/]/�b��8��|+�/8�m~��}���	�04��&��}�	�&~�eb��z�-u�f8V��I��i���;��c�]����x��|cȴ;
�b����蝢�&��x��}k�	}�Ҁ��ܨ��x�)!8����	}<Rk���Y�cA����])!Ⱥ�%�ҀĄ�&�"J�sJ7P�c��'��	}h��%�]�b]�cV�}��
F�#g�\��o�Eyi%���b,��	iob��i��	}���'�]Fm�]o{����'<�Ԣ$�g�o�F�Y�ݟ�Dk�Fm�`��i��	}�`��i{�}���Z���J[�
�w���$���@�����`�
�9S���d��^�{�])!}��(%%Fm�:g@k�{�Cv��
)����5��]�z(��龀[��F�*�cw�{�]Fm��j�ЂC���Y�2�ݢc�)!������J��i�-u�)!_[�=��c�p�)!��cw��{?禞b�-�-���c)���]�.@��c.��b��h�>a���c��5��iЁ����Ey��x܈@m-(�e��
)�ݢc��	}�
Fm��l��cw�]�xک}fRk����b�[��cw	del��ܼ��k��	��&�{���$`1F�5��=u���k��e#d����'��uC	deŁ�$�c����=���`��!g���/��e#.Z��$o��`�o,�x��|'
��p��k�]�`6��c��`� !���_����k���$��b����<�a���$���ݍ$y������i�!gv�)n�����\�!gŁ�$3��ki��Q̝�c��f�9�����$�!g��hS�W퉙�/��"u��܂��$O��
"��z遍$B�i����Ł�$2��v��)��"u%�!g�]=u�]ne-��f	��zk{�o�Jsd{��1s�-���zȃ��腭���G�jRk��z�(48��x��{"��$>
}���z���ki�<�	�(9��,��$z	�xҀ"uAne4�i���Յ��z~\I9*
ne߂�$w�I뀸��]I9���㪙�zi��$�]I9Q�"u�:|���,���z���z��`�!g����m�-h9�i���/�]I9ɰ�`��ܪ5�d�I9.��/��i@��cϔ�zd��i'�+���ik{�΁�+�����]I95���*Fah�|�ݫcd��^�]I9�]
}��ifi�|�]�i/fk|eROI9D������+�
}��z�]I9;�ݓ=j�]�&܁�z(�
����i<�"u����_��15��(�]@9��"uO���bne��h-�c���z��.@9,Dm"��	�b5�ָ�]�&	��z���5�]�&j��z�ͣ�]
}�Θ-��&��e�뀸՜�&"Vmb�m~)���)"Vm�I9�|��T䳔eDm��i���&�@9����
�n@+�&���D�i�EI9�� ���+��$���OI9*��&	TU&�&4�i}��`�@9�֣�ݫc�]�k���9�dP�]�&�d�i��/.�b<	)^�`���`��/��Ea���`��k�ݫc��`&�&�	)`�5��b�]�&��}�Km^�`Vm^Dmd	)�L�b[�.��3�k�(�(ӑ�`P���)���ʇB?d�ie�.�	
;���`꫆$�h����
���h��Ao��`^�&<�a�ie5���#�����&���`�)^�bm�{�@9���k����^�`��r开���Id����^�&���	�ie���$뀚97��h_�Id`�)�� ^�''�f����ܻ�g����`9�%�������&	��`���9��$!����ie�!�,��zo���_U�kX��&��{��&h�|���,&��`^�b	g��h����	^�'��ܮ�Bm�{		),��`b4b'��(�X�k
^�b�)/�b�&	)��k6����ie	I�`��ׁy�KmR��k��Id^�c�5�`d�H,�F9�	��Jv�
�ƕ�N偤�o��Tp��5�+�ie�����4�o�i�ieQ�����'�%S��`�� ~�����`D��iԀ踈��U���߂F9�|���`J���m�)J��m/���h
^�zR�c^�z��G-��M9�M9������_9o2.g�
	�c(�Jvv��%���-�ݚ����M9�Y9�z�$u�26��:�_9��<����;��cm�����
&�ަd�5�m�#�cti{/5�ݩ)H�M9߁�i���3�F9��?�G-b�{.��cQ��l�_9���<�1��f�o���j��$%�F�M9^$u�}6�'脘b��M99��;��֋�M9���b���O�&`)��=�1i����}R�cs�E�ޘb�;$up��9�WW��(g�Vd��&�D9$u:74i-��&��G-*۸d���)���OmW?�V�?Pt���{�D9��G-q�X�M9~me��D9?	��)!�����J�)!ޘb��G-ti۲�'Ȁ��^�`	�_9ҩ�'^)!�����G-�	 ^�`��G-��'���&n)!^�`hue�Q�B/fM������[m��c�Md2�D9��'�)!�#ue����F���?�����[m.��h��V��Im6+)!<vq^�`)��$Ome��݇��b ^Fm�I�F�'T��!^FmxC�k�_m��[mЊ[m\7i�	 Y)!�[m�{�7Fm�"ޘb(� D9�[m�&[#^Fmׂ�c�f�r���V��b$^)��(g%^Fm���&])9��A�[m����/�5&ޘb'^)��/n)!��[m�hMdAd9��(��i��.)��'��/��`�!����&Q�P���*^�`�)!�NFm����)!�Nvw�/���-h!Fmr�p+^�/�)!�) ��i,�[mg{��`���-yO�`r)!K�u\-ޞ-��9.�y����
�9�X�+��i8�8.^�?���-�|kTIFmF�9/�[m9��NTFm��-��b�1ѫ��0ޞ-���賀��
)��9�b��&�́1^Fm���-����,�y�9�����G+�����)2ޞ-�ې9Fm3^)��i���&4^Fm|�@5^)�
���P�/��8�nw�cȀ�|7i��������9%�6^�/��<m:�/�)�[�.��{	}O��bPf2Ϳ7�be��<&��'�(l���8^}�2Σ��o��=<�5� �塁o19^}/�Z�e��;:ށb;^�bI����9L��-��b�����{��i��{��o<^}�Y}��9i�<6��{Ӄo܃�-�}=��{��n�
�9X���3�b=G}w}DK9���9���	Cu�^}��9��%b1Mm�cq*�$7�b��xe�9��_�	)���ߣ��>^�&�������xe<��+������=5�ҡ$�偖��}	� =�uv��?�ӥ��%�bz�o�
��{��)uۅ�{Ŏ⣝�o�0Mm�}�}�C�b?^�b��<�'}��]9�	�@��zA^�b����"}���s��zj��f�cDi��$��y���ó�zcF}tP�b��2{�Zd��b�{���?D�v�Pvff�,R9�_F�B�)u���i�I9��[d���i�Fa�����c33�iI9����h�|C��z��
þ��{5�)ue	�`���z��z��{��J�����zD^�c��0�j�a���a�
 �������E^�i�Mm_����۷F^�cGޢc��)��$#s-I9탚b���zHޚb��C���I^�h�����b �cX�7�{�cd�ۀƁ3�J��h�b�!U�h)�+�h�ۚ/u�I9j{�)C��b�/u��cw��
��n=��K^�cc/u�́��UTp��j���bL��z.���.�����iMޚb��S�z1N��zO^�{��V�ˢcۢ��w3���O�;u����Km���i��`���b���kP^�,�K�X�΂)�/u�{&�Km�/u[�Qޚb��.�`c��'�Y-o�,��c���R�Km��{���nc#{��'㑦�c΁�b��c咉k��hS�Km�7�%��[�d��b�`eo���F9T^�i߉Kms�z�8
Σ(��h��c/��b׀�kÀX"ۄF����`c��GU޻���7(�eXʚbh��c.��kA��cY�O�|�F��"DVޚb�����J=��Pd���&�6��5/u{fف��Ȑ��Cm��nǬ}W^|e~�'������w��f�j�{������x��*gy�1�
�{��9n|e��.��Km+�.ؕ�<-u�Km��܀�}��'��6�"|�(�>��WX�Kmm|e��:g)���ף�{���oo6�Km�z
H�i&�OȀ�7A?i1�:g|eY^�i��^g��4��z�z
i1뀥9��:g:�c��i+��`6ljk����ϖ�Z^-u��i[�}�����i�{�+��Q�:gf�i\�	}'�+w=�z��i
���L<-u]�M9�,i>�}��{�"|׀p��F,-u6ze5CdjCh�C���v݀}�-u^�M9Ђ=�_�	}���d���� �c���C�}R�-�"|�����b���,�c��}`�"|E
��	|ea�"|}�M9fRk^	�be��X��$�&���{�@m�"D���a�%�baކ$b^de&�h㆘-(� '���x�cކ$��M9��hX�)��a��9�Eded��`��&���5dee^�b�&�b��e��h'
_�.g�P�i9�;�f^-u��z�?�i�[���	}GOm(� 't�b�i1�	}8,i�o�=u�}��)]�n��b��	;���+`cW#�뀭c�Om��	}��`��	}g^�/����)�,Σk��-�}�)ۃո��cUB�/h^�k�9�4)	���dew�&�P��ji^OmGm��`j^�b{��c��k
�ĢV��v��$�W�b��cM��h摣j����R�k�!�hk^�`�
����h���$���c�&>�/�������l��'(� 'I�Ym=66��m^�h��nކ$W��jo^)��$f��h���k��'���$=���p��h���`��Ym5��)�ĩc�́��'�̆$(��ά�c�,B�xq^�'��^>�\-!��d�"�&Drޤcd��'�
5h�k�&D���`4fĆ)���ks�&D
�z�,)k�v� '�))������;)o��T��	>�XԀբH�/���Ȁ�?}/4@�)M�`��c�-S��zt�&D>u����c����hu^�a5�����zv^
}<����w��z3F
}����f1b���8�����R��$������Ym
}Ҁ�'[��?�x�q�Tl8��aQ��h� ���`��&x^�,8�)Z�:�Ģy��{�'���j|���䣀�!gP;�-��co���{Ң��(����^A�m1!�&DG��>>�Ij!�	�czޥ>Ђm1.��yᎭcHu�ȀՁquY(�����o{^�'��z�N6�-�-��y��[٥>,�<��&Dr��]@9oɀ���>|ޡj��&DQ�=�}ށyašj.�?���~^Vm��?c�W,m����'@9��F���Fo�c��ܹ�?S3Dm�Gm>��,�@9<�o�t���;a��j�$'DR9u�Xvه=�t�բ�ͥ>�
}g�c�P@9t#a�VmW�c��?���MF@9��?9�[69�[`c�ݴ�	�??�?1�; ^]m���&9<��ۯ@9�����Հޡjo�j��&8�ȀՁ=��&��I�ہ��&S��<k�u��{��>���j��-��{�F�hoۇ�c쁥>���>�����ޡj��V@9��ieF�h��{e��c��hx�飬4���>�-�58���x�Y�h���&�ޥ>"��hL=�h���&Ě�h�oe'�c�^�?��Ģ���p�?�Dma�h����Ě�h!.�>+��/����T@9��?�vfѧ�h]"�cÄId��-
�U-
�?f��h��)>�kI���5�A�d���^�i��ie�^�`��k*��&����A�k���6��o/�`��#���&}��鲼�h���&����OG�/?����ch�$����O*�c�uUӏ��8�h�ݴ���i�kE
�C��&w�/�t����ɪ��-)�k+��Y���h�~��Ҫ"ǘ5���ty;���^�`߁)�%�h�i}�ꭰ;�/��Ģ>�cA�ie�^�h��F9��kr��<
	�iQ��6Y-�^�/rףh����c�Y-�	�{e���!�V<��h|��Y-��h���v�f8|���ݴʈ։7�k����{��n����<�k"�f8u�	�%�)���I%�d���hπ�%/"�k�ܟ����ܒ^@���"�i�x�
�y���oo2�Y-Ɓ2�*�_9.�iA�k�^�/d��F�^�`UB�io2����&��>a��:ik6u�&�b�|w\$u6Y-�_�ϗ�b�)�[�X-����b��Bm5��w���[�,�|��_9�_�7��[���A�&ѕ9Ҁ��k���`��5�@������h�=�b��4G���h�4$uk�f8����W��b���{v��\��b��_99w�h�f8��Ё�eY�&�^$u���j�Y-�<�����x��4����w��j�`1�Y-.�Q�D5G�Y-3��jme�)���6Y-m�8��_9��?��a&meH�f�t{��^�&����A��j��c[��Zd�)!���A���j��I�[E/��t�_9@�բ�+�cף���|�}�hˁ͢�iv��.�
�g��b�)!��.��R���`G$u�(F���z��i��gi`1X���mew)!���(L�fǣB�
)����c
�͢�ImDC��_���z#�b��[m���z�ҧ>���i-އ-Z�[m���i�mer9��-�Gm8K)!#6�c��i"�dc���G���z�^�h�)�*����zz�hQ�t�c�� D�����iq��ףFm]?�h��.��F�h�:g|ԧ>8�/r�h�^me:�c.��-���i�:g��m�^�h��:g��c(��j�{�j��>U�i�͢���פާ>��c���<�Ҁ��h�)'��j��c�Md�����t��۟�z�[��Hd��i$��z��[m9�w0�)!�^{����z�"�hZ�ɣM��zR !:g=u���zQ�7�-@�s��i�%�Պ̌��,��ҍSFmD��4�����i�ާ>U7�x���nj�i��YmA��-	�'�އ-I��i�)��Ym���3�,�)�'i�ye=uc�Ym�^��[�,Ȁ�k)�h��f��[��`���n6]�'���kc�Ym���k�
 !j,�h���n���b��'7�Ymc�Ym�ɣ��dc�	{�����R�{=��՘�k�^�{o�$��Ym�}��Ym�[����.��]9y��k��`�^}k��#Ȁ�j�G�ߝ��D�\-�YmҀ�$�}�@�{�>�-�{ ��k�
��9�,�ެ>~�&�^�{e�"u��,]�-�ށk��͢�@��[/��'���K�"u?'�{�n�{�>gf�Cm=u�^�/��Ym�^}�[���-�7�x��Ym&�'��{D)Gm #�a�}�4gPd�x�g��mc	.�x��'ʁ�kMO�-9�Ym�n���爃�k��.�^�z�[�`S�-c�Ym��i�^I9��-c�Ym:���o*a:�n��i������^��{�8I9}�?z
�{�i���`�ށko�ik�z���`Ԁ�<�<5��,����^�?�^I9h	�i?Z�^�{�+}���c��ۼ^��m�x�S�)u5��Y��A���펫���ijrce�;u��+��?��)u3�id��	e��x�	�?v�-�	�/�!�iā��JZI9�O�/e�"uk{��^I9�)u>�dc��Z��ܒ[���dcŏ�L��������)u @�A2@9��a���>�dcD@����@@9��)u��W1�H��C��s������x<�ߣ�MmC�^I9��z���{�-t#a_��
�iނ�o��$��I9��@9��zk�WOه/|>�dc�����;��^�z{%:�^�?y�i�ޞ-w+�t��-�8I9��zq�v�X�i�^]m���-$��&�?��Ģ��)u�)'^�=����-��u\�o�^@9������{�����<:�o�?�k�^�`��ތ]m�6){�)'"�k�0�`a�;ufrc5�<�=�w��7]mor%�2�$����ޛk��mc���[���5˘Ă�c�Ωc/)�^��h�`��cW�=��;#F��h��c!��y��h���iX����	@9j��c�Km�ޑ9�)f�`Q��Uc���)�^@9�hy��h���{"�dcr�KmV��99���	%)�"D���{U��c��kjrc5��h�=���c�"D��Km����ޞ-���k`���L��-��͢s��d^"D���3�y���h�]m	ͩc�Ը���iꋛS��y˘Bm�����]mo��9��"|��`�[���k�BmӪ�z�"|�Zd�=�=�o��]m��kb�y������/)"��iƁ�֣1a�^�y���>���z�)�^�ybT �^-u��M�Ί�i.��3��G��'�ީc����ޛk��9��>���9{�Bm/�md��h��)$ۅ��>���ij��zk��9#�)�"D�6��ӷ�hu��9.�Bm��+!d����'���hm��i�����'Q�}�^-u�)
-u"��{9�2���}�*)û�}���?�^"D��M9|��{��Ù}��}��Bm�^"DQ�_9 �M9������{3!"Dq	-u��M9	�}y��?��g���M9�����{�����?�
���-��}��?ό�?Ĥ}��S��_9����v�Bm���{��}šM9&�M9��?�	}��Bm���z���Ԋ"|��{���i��M9q��-���iM�@m3o���?���iR-u��?�M9z�	}�o���|�_9�Zd���,�M9{��?��
�+�$���h[$g{(�2'p1a��m�G��.��@m�z� �ވk�&�):-u��vc�D9�"�Qe	o����ɚ�M9�^o�k�Y�@m�}/�m.��h/o�@�cm��bg f��{��hW���.),�_L�c��hȀ�&_��h��}�����M9��)D�^)!���?��h��M9_�M9P�g����׹)!��'&�h��_9t�M9��c )!��D9��_9�c�ޏ?�^�h��g�'xo�ik��?��e#�2'/$�c��:d����G�c���Y)!��)"��h�o��h.�vc�^�co�)b�h���c��$��2'��9��-?	�5
�c݃ɪ�����5�h��i��mB,�i�^o�iʫ���$Fmt��c1adE�^�i�4Fm��D9i�-�Fm��@m״�c�^�?m)Fm4�iT�i��D9�F�c��E��&D�?٪&Ds�E������U�cI
�:Fm��z�&D��&D�H�{�9��'3Er%+��z3��z�iHI�h��Ԁ�JT�1�&D��i�	)!.�{�)!\�-u<�c���z�^�h��&D�)!X�����i��i�)!=�g��������{�F�{��'��&Dh��z�!FmΆѻ�^�{��h�&Dy�b��z�����&D{�%��	`���c��z��&D<��i���Ct8
}/��z
Fme���L
�dY&Z�e��nĉGa�,=�N҇�vc�����1~�z��x	�5ad��^�i���]�m1�^�?��r	��ރ��8�i���x�7�?��X���2G�{��T��&D��&Dt#a9����i���z3����be���{Ӂ&D?��zK�̴�^�/|���kCة��z%u0��&D+��z�%�F�vc���z�&Dt��<{C���z
�&D��^��vc����yk��+��vc�1a�
}�ע���!�� u2{��
}a��-��ħ��wJ�d�V%������j�]��`�� u̒�-�{
�p;-D<6'�#�_�{���9_�/j� uO
}�Ҫ��_�jS��`cDm_oeJ��&�,>���`r�+f��9ƁS�����l��`k�5a���y���8%��{�5a�d��vc)��A�i���i!�Ea�h��vck������lo�5a�끩xK�hoe�:I9m��`M��x�=�c��E��
�� D�ߪ2{�&�bk�5a���f�)���%I9��:�tգoe��ݨ�ۢ���lI9o�i I9�hd���	�mvд&�'�{}v��/_�c����fe�{e_I9�����5a_�k+@I9�{�=����]�h���wv��&s{���z�ɁdF���@��nW4�'Q��(����Ap:)����g� Z�=5�c������4G���k���Jj��9��"	_�c��I)���
_I9Z*�%I��*�'I9ÄF9���
�ۢv�ɣ��F9�kЁ5a��-p�c�_��_�'�)���a��I9.�z?��>_I9oܪa`c�<r+I9
_�h��F9����g`�k����t�h��z�0�x���aI�nv֣��&"�)w�����+�{e�k1��yX�%�&���d�^�1aY�Km^��a��U/��d���<3�EW2{�����d�Q����o��b,��<������Km�)4�.�
W킂�<��<�裤��-�<�Km9�2�_����Km�ע�	)��F�_�z�Km�#g*�zg�c�1a_�'�ۈ�`��
�H�$�֟#gX�L�����#g
�´���c���,���<�8a
�i��10�ƩԀ�Z�OҺ�Kmj�
�À��?�=$u���2��2�#g<�����ir
��#gO����u%�)�-e�>;�#g��j��/=�}�}_�-���/�j_�i�#g�	�i��KmE�}��6]m>�5a���H׫Kmރ{�$R-$uh�#g�}�T�>_�-,��<hT�i�R�>�5a_�-,��<�з��+Ł�<˶}����} _�-Ł�<����S�Km?�!_�>F�Km"_�>R-s�=�-#�Kmh�},��<��#g����o�}c���(���NJIm�-̈́[m�?�mc$��/�r%��>��7n�#g�$uL)�;�iv�<���%�M9{�jO�?@�)G���:g��S�z��+�C���i� �%:��9&_�>ҍ��N��F'_=9�-~�Im��?l�`e&�i�����M9֣(_�>:�a�>)�M9w٥ D��n}�M9N���)_����h�}*�}+�M9�)'4�5a� !����5�h|),_)>=e�Tta�z��5ae���ʑ�-e�M9S�F�*��%�������:g()��-��-9�{}��eq{�	۲��y2)��c��<a-_)��?�ۢ��-g�i�֣�y�)._�a�Zd��v�cQ8��>��M9/�Im���,��?o�ۣj)��>X�u\� �=V����2���>|�M90_)�����U��M91�M9k)���z*=��T]2�Im����'g9�M9>� Ă2����3�M9s��c״ke+��m�Ym�;�-^��y�c)�)m�q�ke4ߛk���5߾>[��o��J�)	
;O��c7��>���~8)	�h�6��`7�oD��c{8a8�ox�z !9_)j�U):��i�!g;_)�	�乀�`2�j��{y��kOj>۴<_}��k�%��i������y�Om��`=ߩc��{>��h�<a?_}���+�/D��&�o��:��)kr%�)@_Om�o�5aOmA_}B߾>�<a�'}���>�%)�oC_Om��5aD�`M�Bd��ЁIЁ�HB�<avU}w�5�ke%8a`V}hעA�o
�n?	r�E_Mm��o��c�}�
1a{�f��iF��iGߜ-y��k7�/D%��/��/DD�5a��>X5H�ke��5a���`s�c���x̜-�|%b-MmI��iB�o���r%Q��iJ_�&�5a��oKߩc���y��+L_}�1aM_Mm��o��<a@褙i6���Z�N�oO�`�5�xP�oQ_}ބR�_.�&e�<a� ��R_MmS_}3
�x���aRҥ��%�_A}h��?T_�x*��N������2{���xg��X�Q�{��Jt�n�vcߌ��{
����� �&e�5aU_�{*�����y*��i?��5a%��/���`��
�� ��0ѧ�c�1aI�{Q��`Ԃ�zܚ�-�V%V�`��c��fk�Mm�,:�$��c"MmR��/�{¾�{J ��l�{��x����À�݅�;Wߜ-��f������vc��ꁴ�&1a�EMm4@9
�&J�ͣJ�coe�ٴk{����X_�ceNҫ��	�)ux�& Y_]mF]mZ_oe��܎@9;���.)SR-���i	��h�c�ٴ�+![_oe�����i�)u�V\_�{	)�c�`�#�c��i݀�y]_�'(4+!��93���^_�c��5z5�i�)J+!+��b__�'`_�i��h�/�'a�O9$=�Xo )����������poeb_@9-?��oe����c_�'*
)R+!w��>�'
���@9�<ao=���bg�'�F��#֣	��h6�iY-�z���&�'d_�'o��e_�`!�Y�9oeD>�z7��h����l8oe>�bf߻c�V%�?�=Q�`*��J����]m�B�i�o�<�<a�=�`�FdF�`�h*8a���ٴ%�<ao��+�Ǡcg_�z� R-h_oe���i�O�c�i��z��i_�ij_oe�Cxړ�`@�ik_�?@;8a���?l_�h��'�+!m_�'c�i(��+!n_�{��{�O�zo_�'A)Ё5a��'p_�{	�����'�������c����&�y%���_�դ���s���3�zd�����{q�Bmr_�z��i����h��
 s_�{{�5at_�`'�z���	}���[�zP�z�1a=����n-uu_�z��<v_�i��9�V�.��f�>�`�-�z�vcw�}�%�zx�_9y_�{��&�z��5a��iT��(��f8z_�`���%��cȃ6�D�`{_�?<���v��&>@���_9|��&��	}��?U�5a�1�}��&
I���(��5�<k�5a�w�l�}��_9�R-�[�	��b�@m-uN�j�]�4G�de聩x����QW�	}��ֺ1a]���o�<r����z~��&X�W)h�ۊ��km�۲6�{��o����@mR��k�+h
	���_�{Q����y�gF�ң���i,����@m�5a�֣���M��x��	}�r1�	`1��`��5az�b��/���&.��i�@��.g+R-�i��@m�)!�)Y�i����x��vc�)���&=�h�_)!���3�_)��_9=��ˏ9���ݺ�ۢ��x��
ܒԙ+��i��{�����&�_)!j�Rp�)!������v�՘��_uC�
)J��'g)���ĉ�i�
)߁�`ƀ�'8��{��'n)�.gyV`1b��'o��_B��$5��o�_�&@��2)���'���'�_�c�ߚk��m��i���'���^�)�_�c&��`�#�'��)� DH��-��'�ah��'���i��i*N)���`x��S�S�Ȱ�'
��kj��w=��wM��iӃۍ_�'���'>儙5a�'�+!�c�_�'�7���'��&D�E�c}�&D�c+()�	Fm9��O�g��5a�+��co��z�bR_�hd4@��'�
)!���g,
}�_9D�̦R8�BZ
}�+9D;ߣ��'I��'��be��'�R-�
}�>9D���'�_�&�C
}���z܈�{MW�y���+�y�_8a���{HC
}���	,�<dF�x?�
)!�Ym��˸��'b�Ym�_9Dǭ����|%�
}	�j�5a�ߜ?)�YmV�c��5.Fm!�c�)JV�$ 9D�Fm��Ym����a��Yml�5a��şC2��'�_�c�+�

}���VmDm�_)���a�<a��`�4�'T��+F
}�)�^Dm��Ym���y
}{�5a�&DX9�iBO�'�@�	�&D�o��F'_��Ym�ߜ?^Dmby&8
=�_�i	��?��O���;���݂[9D�;'�WDm����ip��LDm�!��_9D����9D^Dm뀈b��<e�5a��y�
}Q�i�_9D�H�y�0
}�Ym4_
})?De�<a�_9D�]Dm��xk��b������+i�}�_�x[�x��$��Ym���b���x��Ym9�S������<a�18a߂c��Ym�I���,�{sI9��Ym�Ģx���/w��ǒ�+��-P�%ikI9��9|X���J֣I���$&�{eG��?�Q��_Dm���-�ww�_DmU�g2�fM<��#8,Z�]/ l��/��i��^,�ie�-!�I9��)�D�`�����<Y��a��)��y~��c�]m����>�i�I9v�io7�)��iϒ)���ܢ��a�i���&S�`%�i8�I9�_I95��-��)b��-��}���Y���"|���&΀`8�hd�ƌ�)����8�<a>�����-��?!k{��_@9�֊s�)d��h�C�_�ao�fS��&L�ધ�)���bh-�_33:��x���&��z�_�%�9�a�"�kr%�<a��)��F9��)j��ݐ�&fI9�ǘ�_�{��&��<�{��_@9]mX����<a�I��_]m��&��<aB Z=#�)���+l�ie��&��<a��)�_@9��&��)S�)���(�`۱��&9��ݐ�&Z�<���E��a��ieKkj��#Ї)4� �_]m�%���&�����_�`g���-!��c�0]m�)�I]m.��c��`�	 ?�L�d�.��`��tQ���Հ����&y�%P]m��%d�^}2@9�C]m�߁b
��y�`�$u	}�_@95g��+�����"F2	�W�a?�Z�7)�'���c��&�_}���� ��c��&F�{�Fk��g���c���&��'ݐ�&�
]m��)���h��+'=���c�}��&Ą)�'��)@�`w��:o�&(��&�_]m|{�v��+��&�_�'Ä۬�&��@����@�`w�:p��&�A]m�_�'�&�`�_]m:�il�~#��o���r�&}6���_�`��X���,��U7�`Z+�KkÀY���c3�&�Kmhm���c���c�_�'=�����c��%H��c=���>��A!�=��z���뎊�c���h��g�&=���ʩc���b��^迆�w��ho����q�}$u���ho����+N�'������m��+Q)�o��h��/�<a��_9�	me2�f��_9�_}��M9�}��,��%��J��i�_}'��+�:�'�_�&o����!
����`�y�_�'�Im��%�Z+����_9t{�탍$ӶImk�ffņLo���2'�C�M9A�Im��&�Bml��$9�	�<�@�-����Imi
��I�Im��M9��@h	�����ۣ��`����me	���'�5���-�i��+��Ν`1Հ��M9��_9��n�����B�i����զဥ��+f��#~K��3���<a�meb�hr����.
X�Zme!
���9}e��,d'�_}e�
�>���4�Im��Imo���?E�c��c"�_9�	����io����_9H�X�	�'k��$�9!	�_9��'Z��+.�)D�+5�[m5&v���_9��'��h�9}e��8g�_9�_�,�ic�'�ke0*�b&���cף�_�'��ke�i �c�֟�'��1�-�'�_�c{�&���-�PkU�bo��<4}eZ��%>Z�hX��U�`80/�c�ش��
 0'��_ D���ܣ}	��X}�߻c�b�Av:���	�'�[Fm��蒾7)	Fm��D�m{���`�ke��{���c<����4}e��o�_ D����M�c�8g. De��D�o�1�i��c'DQW��_ D�_}e�_�cn1�'�_�'z3 DoC��c� �c�_}eN D�@����)�i�_ D	FmD$ D�	�D�+T����_�'>�b
X�_�i�Mm�i�_ D����_�'���+�_�h΂����� ��c�&MmX1�'_%�'����be���yw����F����&J��?��mPk��	�k{��	��`A�`w���f�Ok�����::���4�<g��$5��cF���=}�_Mm<��<�Ok���[�)uMm����j� DZش@�Mm�Pk	Fm�;u�Kk9�����;u�܉y�����Mm&��&�����m1D���Mma�a�_ D����_�`&�}��)u ��lMm��`��;u��+Pk$,��c��%����/�D�;u���"�2o=�@�k��b�m1O��y�@�3��U�ۧ+���0��m1y<g���"�Ok�<g|��6b��a�Mm��h���<�&K	$MmS<g��y��j��f����i�����M��c���i�Mm���{���&)�<g��iy��cń�-DK/;Ѱ��i�MMm��^NٸPkv���+��;u��)u��;u���o=B��
�;u�}�_�%��+v��#m.Pk�oe.��{�}�_oeb)�����c���.)�):�i#.� 98K�I9=�V�)�x-�_)��-,�$����_)soe���k��'��$8��+�"f�՚�b�����c��-��*���:"�f�I9�,�+!9�:�c���K)�)�۫��ی�'�5ak��lm8�������&�\�%���']�����_fe�8�-�)�_Fd⃨c�5I9�_I9<%:�_)9�A7�`f�1}�ל{��i;/�`ی�'���)9}���ߨc�1)%�z���c,��i���c���'�Y�%S3�-̈��ѽ$D}=�0�Y�$ӣ�W$|5�M�
	�)���Ԃ�܈$D�)5�M���F������)�}a�a+�y_��_�-	��$|�����$D����F�e=��'ٸ�I9�z�_I9�)�}*{�])�	�&h�?��'�#}4�Y��$DNh�w}S-uY�t�F��S�M�yՑB�]fes�Bp��{S�iB
�V�Pk���'R*}�����-u�[�-�_�'z���A�'��}e��'/-u��'��k��}Z<feD�%
Fd��}��d��_�-���'��}��y�_�-t�}���k�_�'� �'�y��۱
�-:}�߃k4&�_�'g��GgsCh���k���k:����y0�P�}8Ѓk���_�'����$DJ��i����}���?��iD��m��k����Ӆ'#�O���i.g뀼�:���.�}N��`�_oЊ@mH��k�����%�}����#!deg��k�����i!դ�ж����'�g|o��@m� ���7�6�R@��6��@m5�+��%���:�'��@m��@m�_�D�Pk��k>o�E�Ӂ�k��yd[`�'G�}@�"|��>+�g#v��kJ�@m�@ma��h�kH)�i`o�����+��Ok3o���h`�{	�@mZӣ�ħ=B�@m���j�@m�k`oi1
���k�g#��TkQ�9����@ml���v�هB��
F�r�{�
�iD�mH�������-\�����߁�'{�)�����i�)�) 7�{=��� ��T�)|�)_�M9��@m	�'=�)Ł�$
�@m<�Ok�)����@ƭ%H��l� W�M9]�"|�(o瀭%ˆ@mĀ��������?�%�+#�"|.)�@m���+
�A'b,B9
�?e�ێG !������%` ����L�)h-$��ƭ%f��?`�&d���s{��!5	@dQ�{����)�A+����{�o���gQ��wj�Ok�耗�_�1���t�)��C� ��iԂi8�R�`�i�[������i1)����
z��+�%Y2��i�%X���`�i� `)D� �}��)w��%.�&��i�)�٥,����ۭ%=�w�`)DÀx-`)D*��m�'k�	`d��?��ke��M9��L�ic=u��M9�)|�")D
�Kdy�'f,�{G)D?���!n���?ӏp��	}����Z�i`)Dޭ�`����V�ke��)_��řH)Di��&)D�֒A
Ɛ�Fp�ke}e@)`)D` .�	}�C��'R��'` <�&�
g�
)D��&�{�o���J�{�	R�tKk `�'!��'j�	}J��ieVmv��'��'ӑ)|	�"�Ym���&�#`�'����$��'����2�'aVm��&�H)D��:�'j��'�%`)Dd�u���ʢ���iw�C���i��&D�Pk&�ke�)DN�&DH��h������ܹ}Gg�'`)D:�&D�CDmv;�]s��(`�`�#�&�P�a��&����+g�x-)�Ym*`�i�&Drӣvˢ��'�Dm+`�',`�&��)D���/8Q�'	�g��Ym�/�&T��D�i!5�i�%A�%��?	�Ʈ��'-�	}���ie��' ��`.`�&���'�&{� u33��"��{�����VV	�'��'�'V���ylD
�p�ie�C�fo��+��a9�m1�
Ex�&��&D��/`��0`�'1`�&�12`�&4Ca�'A$&DV�'	�L��y��ie�7�'�G�&=�����֎&Do�۾�������ln*�u0��m�� u9����-���mJڣ�m1� 6�_�t�m�{eZ�y,أ�@9R�%o�d3``8����%�ۈ��ՀV��[����V
G�4�m1�`8�eӣՀ�X5`�%��y�F9j�f�`8��Ԗ�定�&6�F9��m7��&�%�
z%�F9i�F98`oe��&� 
}9`oe���&��-:`�&���m;`�&&"!`-��<`oe����4�-=��z��&>`
}?`�����oe��N�ީ(uI��9
}@`oe�F9A`-|B�F9�Xoe��W�_>��w���C�F9�(
}ӑ-DD��&�	�Ȁ��E`oe��En,
��%
�w׻cF`oe�����&���f3��t��c���&��cxt
}�I�/]m� Z��)�-D��h*oe���<��#gG�-D
��*��H` D��#g�$ueX�M�#g:�-Db'���g�#g	�-DS�b	�I�F9,�-D���%LT$u߄F9�-DJ`oe[�`K�-D���&�F9_�x-L` Dϊ�l�-D[��&M�#g�洷�-D�oe�oe�h��#g97N��&O`oe=�#g���P��&+�`r���Q�BmR`oeS��&���m�ުT`oeU��hh-|��mV` DN($uik�W�?I�p8�=>-|��L;me�-|X�#g�,�'��Ng��hY�-D$u���c���{��-D�%gZ�-D�Č?Ą�&��mv@���������mG�-D"me��zw��ge�}*�V=mem�&|��-D*|bh�`����k�`[�-DT�#gʃ[m���meX�`�[m\�#g]` DI��&��[m^�}�g27�`�&_�#g�$u\�`z���
 D�̌�me���>��d{�i�`�=�m� DI<�b�g!�g��ش D����F�r�w�`���e��\�`9�zm�_9�ʜ/�[mo��L�`���`�_9��[m	�Bm�)����g�[mL���I��?�<��ǫ��Ό�[m�ۚ��?��Im���h��ʴ���6a�?ŋ�>����8�#��-t��瘁�֚�
zb�_9����8�#���nc`�`��5���v�-o� �g�/m����-|�`���n*ۇ4b�G�9�{��me�ڣ�ո�`����+0�W<�3}eˁ�dI�[mi�[m�Zm3�����Ok>��ց��u\���id'EJ��+8Pk)5��:���	 <��oނ��Ԭ_9�T"�)!�K9i'gϜ��	 ��_9��{�
?�Ą�-_?��-��_9�4�{���zd�_9e`'g�	 uˢ��a*��&f`)!��&b�TkÀ
f)��H)!���^^�-��m~�����3�g��z��)o�-j��ah`�b�Ii`'g�й��zj`$|Fm$�x->2D�'g�W[�QK9�2D3�#nT�+D�ʴ9�hr��z���|��lk`�h����$|�j�}l`�a��o��{�,��()��h���-m��z�������sL�hw�V����n`�a�{o`$|9�61�{4��{~���&2Dp`�a��{�hoڣ��C�%�o�Q��(���{��$D�2D�P$||�hM��{q��zr`�h���)s`}t��z_[��2Du`	 R+}��b����$ 	 Q����v`�h[�{��.'goӣw`'gx�$D׀u�w��{�����W�'s)�'�Aj{�y`}Q�ҷ��4d�$D}�'m\�'�}���{(�ʢ7U�'mPk	�	�z�Ymu�{a2}���m���{`�'���ze&j��R��z��d�MmA�Ym��a�Tk�.�h��-9�h���i|�Ym� }:��`����@d�h���i��'A�Ym}`�h~�Ym�]md����{Q۪�=�'��$D�G�y�'���{�����=����s{�\'��i��be�`}ょ�p��z��)u��iV���{[�ك�is"D���zf6}�$DY�$|�}��"|���z�`}\�&�����m��i��}:��c��'�
z�
��(寂�i�/�'���y$[���@oV�`&}�1��h��9(�"|�<g&���w��c�/DmPkh��9��}�=�'�Nʵ�-�VН�z��ΨH�
�Ym���zÄ/D�
z @�:��c�Ym�VЊ�"|1��b���`�z��i���z�4�@;i8�����z�4@�*���n_(+!�`i8X�"|�z��i�%���c��zw�9S�ʢI����yi+! �`�%Àx-��
8'n��Odĉ�cL�����z��i8$�zh��+!	��'��
 E@+!h��9��%*��hw���
 EHF��5�zX���8D�6+!9�{�'+!3{�ji8ˋ�c#+!:��cg�"|	��'o�ݣ���e��a|�����g$@����a���bϖ�+!��c����#�	�����T-��b����s����m��)���c�}��[҆3GI���'i8�Wȃ{kl���Z�{��m*�M�H�z�`+!��{���'a�i�`+!�Z�{Vx�2{���1�T-�`i8̞?�Gfed��6+!�I9h	�zb�l�`)D���Ii8�p���:�:s�`+!ÀY�`]mc��mDi8Ɓ{���	�R�Y�,-u�zd�	B��iJ�d��=������iS�)��=)DS�zAA�i��y��	}��i��ze��ܗ�)|+�Y��
 ��z�`)D]�
 �O9�
�Kb%�c�\�
 3���=�S���.���`���c/uЎ�'���`�`�i)D��'��!g��]k��i��fG]m\�����'�vC:g�����	}��'��Yd�F2?�v'��)|��gĂ�ht��'�
F�B���	}��^�)|�{M뀼�v�4S%�!g���'���k��!g��)|8
=pf����h�`�c:��m-��πˣ��h�涄�@m�
�fb%�"u�de��I��>��fl���)�i���=��3��']m�������ɫ�&Dd�i��`)D���=	�`��Yd��i����)DZ&���z(��)�`)Dz�`�`�z�!@���&D4ڣl�*n^@�i���z#�W������S���`|ډ��)|o�YdȀ���E��Xp)�Yd�@m��x��`���g)������'x�&Dl�	}��)|k����g�
����V��'*�9��'��fʈq�z��'l��>��,h�!g腩�P��'D��Q�����!g��f�'Eo����M9�`�h崛��_�	@ڣ�����-o�b�_9��M93*�ac�b��m8N�?F�_9U�b���o�m8v�௩M9t�_9��m*&vB��c�&Dd�Z���a��M9
���O��m8��&D��`��,��_9gfׯ�z���%t�F�@mg�4�M9�W�W�瀝� یzwa��98�� ʁM9��VÀY��zwD{�5��`*ۀ۫��i���Ȁ݀�Էi��i��i� ����'��i=� ���z�	+���i���2C�Ʃ�z�`}e�㴯`�-a� �`�-���iC1-|@(}e�� 9]dރ�ϙ����-�� 4}e��p�A� �&�h�=����N6}e�&�huɵ�� �8}eʕM9�
}ј�ճ�m8�B�h�
}�`	) b%�-�}e�M9��M9q�_9�`
}���ie�m8�`-|�`�h��M9�}eX�zz�`]d	 ��}��{��h��i����fGKm��z��8}e�{@�}	��i�-D��;| f�`�h�i��-D�	�mDm�Dm���{�Y�@���-D�Vm9��f���i{�۽`Vm������i�Vm��i�@�Ykc� f�}e�` D�}eh! D��z��ތ&��" D�`Vm��-�������ف��9}e�� X�;Te��{�`}e����	 Dd��ig��`�i�-"�]kI1}e-WVm[�C��`
}�`}e�`�h�`]d*�i D}e�i9�{��-|�}eB DtkcѸ�&�h�`-|y�zwf��-D�`�h�ـA�ke��bek��c��-D�-Dj�&�U�9�@��w���Q��{��j" D-�-D&��1���}$��'�ˁ�kc�N>i�-Dک}���0�'Q�-D��;|��}��i�BVm�-��i4�p���`�i)���{��'@ـz�i�&��i�Vm�33J�5�=3uC2�����yی�i�ˣ��ieEVm���< D����z�Dm�`�i��F=���������Y�$|��߁�y.� �m1�Ad=��Ȁ��ږ:��h��͓���be��y���˜�G�be��i���l����ኃ).�j�g���8&!�k���m�`�k�7`8��%b%Zkc&�����%��莃�y�:���
����i6�m1��0ј_�������`�kd�c���L��yD�F9������-���L4���~�4R�f������&��kt״�oe�	�Ʊ���(g��i�k��&���Z��yծie_�t���_�S���{���ikc���+�(gk�F9o�(g�<:�)�:��W�f��{�z=�{e
�k�`�?��2��9D�����iڏ���<��?D��y�m1�)pco�?D1�m��k���-g�?��i��{�`�k	��j7�{�
�BpcH��i$�&1�-�)pcW�{��-$��
F9�G�^�?D��)�`�-���-��?D�$	 �`�-��`�{gO$|��?De��{n`8�f8i��<�Y���	 c�)���`�h���<��'Äۚ����hʇ?D/����-l�Y�pckc�`�h�?Dpc�?�iT�`�h4��)�`�i՘@d���]�iQ�۪��-��(gɄF9�pcc� ,^�@d��Ҙ8pcR��Z+YU�i���=���Y9�Y9Tg��2�$|�?�	 l�$D��&T�����iL0^]�?i��<���?��)�"�i؁�{�	 ��@d@<pcr�?D��@d�E�&脞?��/؝-�V��	$w��|�`me���-��B��me��<���5��-ŋoc��?DR��`�yѵ?DR�-dpc��Im�me�'�y=����e��/D�me���� 52D6�[m���r�<�O��
�.�#�-
�憀$|�`�i֔i6����3�iȀ�o�-��z;����k��
mpc�%8h(��26���)n��i��k�#�h$mej��k���/�pc�$D����aR�i�<� �'w�O�mpc��k߁�i��k���/����-|:��?B��.{�C\۱48��C펀�/G\
�$D+��&��„����k��ˆ�/	�i|���ڴ�/�֞?	�V���{����t����z���{�@�{�n�:�-+����/À���V����=�������{��k�i��/D@�y��F�p�,�/D�
�ۤImm�:g���LJbcd����k�2�?�*���
�F��k�դv�Х�����{�\,�8g(�/D��Î��n�����{L�N�_9!���/���kӃ&o����{х�k+�Y�o
���kc��ԣ���{�����\��/>�z�� |���F"@�����/��9�A��-ΆǢb��/��z��)���B��?���s���A΂8g������?�����\�`$ui�ˣ�zq�-!��n��i1*���0��{��i��i��Dd���a��{�g��9�;/�h�	
ٟ@�(���;/�h�6|�@����{F����?��8g=��d������Y]��g��{@���&>A$u@�Y�j��{�6|�Dd~���$u+���?�����`�h��?y�h�i����kc�Yۯ�;$u��e_��@�=S����z�X� Dd�`�h�)jWMm	Dd&i1�� �`)D)���2��D�i@ۯh���h9!�Mme=uҀ�?�����-�`)D7����̪����o��Ёl�l�]9���d��-��<Mm�8S��
 eMmo��o/u�W)D��&��tcJ�գ�`Mm6�)|o� ��;u�)Dv�9I�����	 �)D9�V�`Dd���h��
 ��.�S�;u��c��)|s�Cv�`)Dx��iX�ҫVȃ˭��cҀ<��DdFU��cH�/��i��A��i�c
�hJۯ6�)|�܁�c�	 ��i�;u�#�Y���|�Y��;u׀����h5��c�/ug�<����-��;u��i��)���=<g�;u0��h��ٽ�%�� <gB�aq��4��i;�;u���c�X)D�/u��ij��>��i���yz��i�))�Bd[$����a�XĆ��{ǥ/��iքocQ��_a�ܫ��^��k蘣ca	 y��i+*�	�c��
a�i��%��hpco�e	 <pc]��
<gp�O#�`e��c��[�pc�pc�ch�`e��i_��i	�
�-,�Y��c���iai8�&(���L٣c�>�V	����h��`��i�Wi8Z&��%�����h�T/Ȓ99����%�Z헥�Q����0J�kϊOd:�<��	�܋ǥ����=���=��P*A�ۯ-���pc+�<�&Q%<g�?!ei8���)���/�`eV�`eO�oc	%&���a<g��F�����iX�Bd�&�3i8��a<g�-ߤBd�@9�Bda�{a@9j&�ԣx��`e���z�����d��n����C+�"�tc_�%�G}a�e97D�J&�{��?!��?��d���P�{�pct��i+@9P-}8� E��%X�%W�{KF��%�5�pc��+�?5!��e]	�.@��@B�{{��a-|���&�?!ai8c�m���,ni8��cQZJ��v
�{��cL�x{��=G�hN��y�'=����c��)���� a�-���y����!a-|�%ؚ���>|��-"a�c#�#&�-D/��y��;|h�cǭ {�Id�4�-h�c�"�m�
��w�ې�{��}��hD�}�4�i#�-D��c �'fۯ:�)u��<��$���5�$�-��h��{Ch
�i�-D��c���|���@9%a�h&�-D'a�z����;|(�IdQ��/�cwxFa)�Id*a�i8��%L�h]�Id+�
 ,a�i-a�-��
 .�-D�tc/�Id�Id$�z��y�
 ��Id0a�zo���]�-Yh�1�@m/�cw2a�-3�@m���-4a�zmpcϷ@m �z ��h5�Bm!�i!�@m8�i=�-6�@m��7a�zEE�i��.�cWR�i��C8�@m
�)�cm�id�ޟ/�c�"|;B�'�F�z9a�h��}�%�i$� ��@m)���`�':�Id#�';�)��NjE��ْ<a9De�t�)�
 �:����:���"|��=�@m>��'>�@m?a�zY�y@�'}�"|��hA�z�������1�@m*��E�
 (�@m����A�'��ݸ����)y�@mB�
 Ca�zo
_a�eDKd�=���?2��*�)o�z����/�	��ԣ��)!���Da�i��i��E�m8���u&�zl*�)t�_9Fa�iG�@m�pc^g�m8(�"|1�@m熿%r6���e#�j��X$<���W�@m��j��Ɏm8H�@mY��?B9I��'eCz�����a�pc�ޒ��a8pc��+�e����܌� S�tJ�?���h���*�tc���zV�ơK�?���_�9D�9�A{����L��iq��)��zaCzM�z(���yS��}�ْB��iҀ���	�?�ԣ���i�Y��s�yތ�zMd�	�y"�-u/�X��yN��i��OaMd�B9^.�?f?�-���i9��zP�e#��h�zƓ�i��f���|��z��pc������+!���iQaMd���?p��P�	}�gw�}e끏-��?D��i�)-uZ��iVMd���>�-u�pcg��-��z\��)��i´�z��ĉ}c��--u|��(��zRaMd���'�	}]C�jMd��?DS�	}�-u��-�f�)Vms�B#�	}��iVm��G���zā!T�?D��CL�zU�{eCzDmV��iDmW�z+EkcX��i_ۯ~��	�W)c��'�#~Y�?Dz�&���zZa)��	}��-[�C\�z������]aVm2�&D��i���#�&D^aMd�}e�	Vm`�&D�KMdq�be_aDm`�&D��F�)b�� �&D�Md޹�-�#}e��&D�)!(Mdl#a�&DMdDmba)����G���-8�&DA�&DL��-��'�I����-�i�kcq�˕X:���܇���{"��-Nv��&D��aɈ�-���g��%���cc�&D&�?D�#�d�@d��?De�	}y�&Dܛ��&Df�-B��i�����'DmpcE\�h_�T/R��)�Vmܷ�'9������
��Ɓ�z���i�Dm�
�杁�-� 輎�i2���Dm�˪��}c�&DD�ԣVm��ਜ਼&DW��i�h�&Dw�8ga�hg�oha�h
����i��&D=7)	�{e��i��&D�Z�b�!m�tc��&D�=�i��o��zi�E+S�oja�i���X{���Ԃoka�i�&D��z��
�h�{em��ml�om�}�cw��C��%ց�<���ii��i���bna�ir��Q����<o�}@��U>�h������i�
��:�F9��oc�X�'pa��hA�i����
ۢ�:g��-���1��̣�
4@׀F9���R��ie,���	�ܹ���{��
���#�h@�o�f�z��=�`��	�:gN��8�fb�!Q�Y�fk�e��o�f
}qaI9��`
}�}�pc��b�0����i���{T
}耉7j�:g���i��mS�z8Ѥc7��:	�(�{�4Z�҇4��*I9�{��:g��c
}raI9W�f*�{Z�f��{w�oc'#s�)�	�{f�octa�{��<>ۈ��J�o:����+uaDd��oca���}��oc>���k��mۦY9tݣy�{y��v��<׀F9���<�Y�h��Cv&�3Sw�F9pcz�:g��P����pcA=U> !Q���ݣ�:g��tc�}mpc��fЁL>�pc5�F9��CvjFDdx�f��}bZ+x�:g��)��2ya�hz�)n�f���}(�H��pc 	Dd�p�w !��{b�/d��>I9����${�)��H�=u|aI9��)�H��pc�h��)p�>�v*ރ��.�H�9ɷ���|���Y2{��)D��bo�+D�mb�H�뀞?��}��)[��O�)D%��e����Ӆe����`��Ā[�Km}a6|�̣9��0A��:�)���Q)Èmet��a�+|��ocJ�~��a�i	��h҆����a�����i�o��a���XCa����7}΋�a�'�hϔ}�D}��)��[m�pc��)���aW	 m{�!����*���iH2�I���a(�Im,���4S}�=u��Hء�H�W�-��`�⁆��a9W�5���P}@6�b���d���i=u��a0�)e�"u$�bm]��a���>@�s���%)?�"u]��{���h!��{=u��a�y��-b��iA��{7@���a���{)�m�)Wf���9����a}��ib4���=���{vs�]3	 ��mm?�,�=�����fထm�a�yk��{��u\��+|���aߚE��E�!��{��{S�m�^}�����{����j����aБU Qഉ��a���'�I���{r�q�8!}7��aY��{�Uv��me}�˥>&������#H���='��{���8��A}�a)��M9���ȍ�M9�a)l�'�6}�(�yh�{a��i�
2ńӪ�p�<��o���M9	�۳ �y&I}���K)��o�a)L
}��?!���k��M9J�
ۤ}>�=��{�
�.�M9Q��/@9`6}Q�f���{���y5Uv�ˋ�)��M9��S2���{��5$��_����C���x�"�M9�@9֖o�}w�f�*%�m��o��Q���}��Fa� }�@9��mJ�&AK9��o��xө��}:���d�4�z;�-}��y�3}m�=À�ܖ�oe�-�a_m�!U���8
Uv�a}��-�M�	%@9�a�-�m�$�5[�- @9�y���m'�բ�M9�
�h���[X�-�|D]�a�a�i��i��o{�	�a��o��M9�'�a��tʽ�o2I}�a�-��?s�i�a}g�xӹ_mw�q�7�i�1�-`7}K�i���x���cЁ�}�Q@9�a}b�/D���
㭯}f?�i���h�a}�a�i��偢a�-ۅId�a�-�D�i&��q���!n9@9C�
 �"D��i��}�a�h��;|��{�@9�{K�i	����
���n3"D@I�{@��hȻ�k��f��l�;|�'�-�"D"۴"�/D��tc
kc��{�
"D�f�a"D�N�s���`e�a�-�"D�?�a�{��"|����a�-�a�c;DP�a�-"D��{�Y�-�a�i3�{5��h�'�-I"�i��	s�{
���{����}c�;D������Y�.�a�i�a"D��Bm ���o�x�a�{��R	N�&��P�O5�����+�ܝ��#Gq���'��)�7�"||�)#:��y���+�?����3"D��/DD�%,��Eg	"D���'t�
 ���I��w|d�Idw��h��}��l�%V�YGj�9�f�]6�{R�
 �"DR�`8f�}b�
 V# �!nr&��f���h"D�G�j{�}�"D7������ї"D�x��O9����+��Q�U�[�{��}��o��"|�N	��w�ݥ��od���?�6<�+&�a�?��TlU�F��Կ�j�\�xӶʦ�W�*��[�ۀ��m�荸}	�&�LR9�m1kyc��f���O9A��,�"|97����xӈ�%�,��,����bP�j���c���	��h����p��(�}������,��yJ�̣���as�E���܁x�щ��)��}݇�c3`�?���aᎸc!eA�k�6�&�}��aMd��^����}��ߊo=E��%��ٶރ�>��������}�$�x�z��c��.-u]��y�}���y���)!†�x��e��5��-��bS��yV��c�t#�-�t����Y�	}T
�O9��[d�?Dr�	}����Θ	}��+��9�;u���	}��?DƁ�����[����%N��-�!�i�	}o�-�7)!΁���g��de��?D��;u&K�aFm�F�>deE�	}3�@���̔[d��y�	}o��)ԟ�c��㱋�a��[d�aFm=����?���<V��c��	}�?D%Fm��	}s�E������[d+��cj��a�S�R�i��E��-uɎ�a�	}m<�ܖX�il�a��?DDve=�
)@�x�;�&D�a�iK�'3&|��-��xӘ�	�i=Fm��?��`������?��i�aMd��@d�Md�a}���a��&DF�&DQޞ?��	}52?J�i�)5��-�@�n(�֣}T�&D	�e��`��&Do��x���-ZFm�	}\'���-�О?��-�)o�5	�7�&Dک;u��yg������ah�&D�R⎞?)�izIFm:����'�+Fm��sJ�y��'$��i��y�+/:Fm�ǘ�Ş?
��$&|>�G+2��?	)5�&D�a}�)�Fm���z��:)%���a)�8�m8(�ȴ�)�a�i�)��j����
�i)j�k)��&D��:.��c�*0$�<���'���k� ��&D�a�co���Z���ch�<�/��m��?��'��c*�D�>��&DҀ<�׀
���?|�4G��`���=����",�c��?��<����݁<���}k�ۼ��˜�a)r
���f'�c���=b�y����xӨ�<�g[�'�'"Dm���"D��?��p��=�c��'H��^)%�x�|@�y��<�:�+���x��(n2��m���R��=�Ѹ��'������m��fd_I9�C��Ό\C
}{�i�xӖ�}������c�ۥ���"�m8�=׀}cϋ<�g�}�I9B�c3��g�(n:"������h�m�}c;�<��a
}��$|������F
}�aI9g�{w��'҇Q�o�ۉ�<�:�}�a�c	!gJ��i݁<�Zf1k��I
}������Ԛ�'��:Vmh���m�
���}�a
}�4:�Շ�'=��݃$I9�'���c耙��aVm�a))���ÀxӇVm�a)	�}����a>�۝���m�+)
��}ĉ}c8��{��a9
}�����I�PD)$���r
��1Vm:�{S3DdCG��)yVm�Q�ae�׷\9Vm�o�)��(n��}�C����z#�
}��@mh 4I9�a)((�%��^s�1�aI9h��x�a�a���{QI9$T9D�+D�YVm�a)OSVm�9D�I9�F)��}Ԃ!�=Vm99D�a+D	��ch}e�9Do�i��B��a9D*�mZU+D8���)9D��Y��'��e�lj�4g��'W�Km�+D�a)Շ�'�a+D�Ymٳ�c�a)�%|T9D��c��Km$�$|��dVm�a�an�.�+|Q��iāͣ��Kmā��<��j3)�CVm��(��c��Km��i���i>�Q9D��Km��c�a)��x��cԚie���'��c��i��'���'��)�>��Km�o���cǴ�ja��nj&�t�)G��i�o��Km�WҜ����9R�)Հ
�m۝����)	�J'Ԏ�c��'���'\�a�=�g�/�/���i)���x5���&D%��'�i8�$9D$ ��'�t\��ak�&D�a9D׀&�����a9D����(�&D��
}�x�k����c|�����bh|w����8.��iK�{e���-e�Kmc��i~ԣcR�&DD�KmH��c��&D5��i���i��)�|w��i��)g�UB!�Km�9|Eңc�&DY�Km�)���'5�<S�ie@�<���i� .������c\ ��)���i�Y9��)���'������1ѧ��i>
����Sj�M9�x���'���
�3��封M9���9~�m�f,C2���?�M9���y�'��&DJY9?���pf�����s�&D9;���i���}�Y9��i��aC�&D�Y9Q���i��&D#��i���}����i����h�G�<��M9�3Å��`�Yҫ@9��pZ+:���X���9����Nؒ��i@Ӧ%�FDm�fԆ�i�Vd��M9lDmL��>�M9>��%L������o(�aY9��o�Sj��%�i��-t�}'�
�;��bh��L
���Z+�-me��%�Om��+��}%�icOm,]m���Zv[p��</|<�!�
]m�OOm�.�c8
Uv�k���/�����a�qA�c��M9悦%���iU��A�Om��c�	)��%����>Oma���.��e(�M�bme[�xӤ��f�G�k�c�[m��o%����{3�۹�<���[mX��9�i[���[m�x���{�\�c�2��/D��}4Om�`8�%:��Id�4���an�	���m8�`8������ۮ��z���#�㌣�[���6�-���I��{��%*�Id�[m#�ܘ�ѧf���{��^	��a����>�/D
b���i7��/�OmҀ�aʋ4�6��kme�؁��x�b�-
�t2\bOm���a҇�
�fb�c��a
� Δ�m���=�v_V�����8����-t�$z4/|�/DE�c��{[��mbme�������=Y��{��qX�/�U;��$b\Z�������Id����Ё���{���m�/D?��;�$|w��b?�f�/D��a�����/DH����a���
�۹��/k��a��f8Ё���a��{�J�H(���->�hЁۤo��f8����aR��<�W�pˁk���xL�q���R�-�E���s���_9��9瀋�m��]9��k5+D�iG�_9"�y5�}oNm�&\��'��tf87��y}�7��̸�	�8�o&�y��+|��{y\����Pv��_9�\�}��k�$|xWR9=�&��k瀜-z�&r�=��+�G`1�]9����k�]9
ģr�6[����9W�B�<�>$u�J������c?��5J�f8����Ҁ`8�Fmv���s'[����^b$u DՀ��b?�&?�
����o�  D�X�}�oUB�ob�y��-Oe.���ol�<�7Md"�}̸�{����zNdW��kb�{�)����d�_m��X�]9�	 �]9;��j*%�{ۘ�k׽]9ɀ�/�uR��3�����]9倅�+�?���]`�_9 b	 �og D�ہ�T % D!b_m"b�{�(�?nU�aS�b����h}e�Im�
)*	},R9(���ў�f��o�A�a�_m}�bMdj?�!2ђ�-J&`�vfe�}�? K�{Ԁ݀*�*�Im.�;u�<�_�Z�:_m�4:�#�Av+�<�b�Im�ak��9$b�a%�c�Im��?�����ִ�}7�a�G+�}��P�	�?D&b�a݀�ca�}2��9o�<����}�)��aE��-���c'b_m��`e<�㣬)U��-(b�a+�`e��aB�-|�be�Bd$*�{+��-r.���9)[������-m�$KF�:������y)�-m�݀��=��<�Q%}��,�o*b�{�[3�o�Q���h��&!�o+�-@�<��)̈́-d'}|V�>�Q��}�`e����+,b}ߤ
�׀&!�������������8��}��x��;uw���-�Bd�`7L}e�}U )Y�%��os� a�}.b}�����/b)0b}1�/D9�ZQ�ǥ�)|�}��9&��y�K9���i�I��2b)���(�i28C��}(��}�<����6)�)��L3�`eQ{�Q��-�)p"D9���"���#.Q[J&۫�c�	)
"Dw�g�4b�bЁl�5�Bd���<�a}:��-�Bd6b}7���_�Z��}oA�`e�]}k�bԂo�	�V�Q�8b}��}��-|m�;�Y��ca�}L�$D9�}:b�b�yG)���o��<4}$|h)���}D;)�髀��r};�aX��&#�"|3�y�$D������U2�����o@�"|��U#����ЁG�o��p�Ϝ�<E�Ӵ/D܄"|�:����ao��a5b~	���	B��s���[[i8q��e��݀
���<b"D=�}[�.>�)u�i8�"|+��mMmϔ}�ƀ%$|����Ҁ�a����<�"D��$ރ��?b$D�Ev(�b��}@b�i"D�b2���N�y�I9lo�}\�$|Ab��Y�c`M}Bb�E��?MRi8���ۯ[�-Cbde�)ud���
X+��$Dm�.o�ۻ�$D��-�6�]��2>de�VFdD�}E�"|F�}��)u�deR'�*��{�Fd���ood���؁���nҍ��ڣ���Fn��hY�}w��9���?��o�Fa�)de��Nbder����um84de�g{I��n���a`��Ev>�cw��H�:��mބ��h�G��a��`��eރ��+Hb���)oL�}��aeo�	E���]�}Ib9D��u�i82�>Jb=uEv���ܐh���mo��
=*�}���N��-K��aO�)>��o6�o;��i	��h�N��cwh��m����U9D���L�% deMb9DH�9�7��a���%
�Y���Nb9D7X+O�YmP��aցQ�a��ddeΕ�a��H�Q��a[D$O�)���8
Fd;�!gʋp��9D���a=3��oރ��o`�YmP !�g��%�Z&|�R��a��Ym
ٹ !S��aL�}Tb�{�b~`�Ym�&|�������U�kV��aԊ�%��4S��{��!g	�G�W��aX�Yms�)7�Ym��F���H��}ځ)��
�l��d�_9D3*&|����Yb9DZ��a5��?�$|��?�͢k�K�E$&|��!gk���i[� %���&D�ô��\b9D�����z �a]�&D'�
��K&|	͢(�)���-u
)D9�L��{t��)Do�?����&D���Ҁ!g)D�?�^�):��_�:n��&DB�YmX�)���mV)D}-u���&D`��a%)D
��ʥ}k��o���$|<?�a�&Dх�a����b�)|�&|�-)Dc�)|��!g��R�!gY�6a	�G���'&�N�ao�o��4���L8���o��(��(���7C�k��}�m8J�d�)|��&D���\�?\�y/f?)D�Iիk�	}����a���_��)|W�m8e�&D�z倉�o��)|��&D��=�A��Z��)|����r�	}���h�&DQ�)|��M9>��o,Dm��S2gm8J���
f��)|ြm&��y���m��}5��8�9��)|l�i8>��or�ˣ[��V�:n�Dm��J<�.�
��x��P)�$!�z#	Dm���i����ց����$!e� u�,)D;���Σ�1]m��ݴ)D>�۔���9�)|��)|z�c����fb]mg��ho�۾�_�YU�a�'���}k��hb�a�Q]m����ێ(]m҇���ib�a/]mY�jb�a��-Ӄm8��)|֢B]mkbDm-� u��-l�ie$�}����m�m8dցI����}g�ie��9	�e�����a�=|j��}h��inb����f��]m���VJv!���oob]m��a
`^�=D:�&pb@9RJ�?qb�c�c	��-Ă{e�=Dl� u�=D݊�-���=�&�w�]��a�}� ��iea�}rbS�X���f�����i���Q�fhX�:֢�sb v���I$Z��&
��N���tb]mv�ǣۆ�'u� 뀟�m��=�Tpvb�aq'�c�Bm*�z"�)w��il �B�-Omރ���&�b��'J���-	%ۿ��|T�sfxb ބ�&O��?�O �	%� 
}|�i�J�}��) � �-|I��yb-|D�Ov9D��F9X-|����zb+D� �E
}€넦-|'���{��|�=D�Bm{�Kd|�
f��ie{�
ۀ��9D���
�Kd��)5+D}b9Da�}c���Q��~b
}�CE9D� -|s�)v�mժ��{'���-e{��+|�b?��
�_`�3��_9��Jj�b
}<�%(�7&-|C�%�-Df�m1���}�R �bVm�=-|�m1a��k D��ܿ�#g��f\�#g� D�b$u��_9��f�X D���V�#g��_9���}��φ�Bm�-D��F9���mkC���f�_9r�&TZVm���}^($u뀜-��0����T��O_+ DW��ip&�)��f�������b-|�֣��݇�e��l#��f��%�9D�9D��Ow�}.�f8R�� DQ�	VVm��u��o7�f��}2�-|�b-|�9Z

}:C?��b�ju&�f$�S-g�(g��fg��c:�-D��ߐ�+|��֑��}��(gȃS���+|�bme)!�_9s�@J��k��c�W)!���b�a��#gΑ_9��RBz��E��#gs� ��%�m1�b$u��#g��fŴ(g��f�b)!��_9�me� D��_9��#g������-k�9:��5��i��ymeH)!�б���{Q�a��fs��Q�2y��3 D�/��
)�Av���Z�ڢ|�W�`1��Jjn�ImM�a���f$f3�&�)��i[�-��%XFmk��m���i+��i ��cc�-����b�i��Av�vC�j
���c�!����%�[dh�48���{�ImǍ4�3��9����܀Ă2.��i��W8�bme�b��g�(gE$�i����A�-9�Ρb�i��(g����i�b�{Ȓ�Xo���b\v�Fm"�if���b�a�)!sżm��b�iJ��/9�Ӏ��e���!�`HMd���?�ô�Im�A���io��m��[d�gb���{�aY��m�S�m�%�k�ť��z"��o���o�7��iv֣�	��۪����� �y��	�V��4�� =�����
)��x5�i)����/D��Av���m׹���޹'g���XW��i��u(gf��i ��G�-��ic�iD��m�+��x�i<֢%*A��i�/|��y�^�-���b�i�}��o1f$g!)�i������a��:�c�'g���c.�iX�V��$|v��k��,�X�}ʁO��bĀZҪb�c$|���9�� ʈg��'���g�c�� Z�ڢ3/|Z澾� �b�c�*�b�� ڊ4Gl��{�b}Ł$D�+�x낆�4�&!������ (�	�i�$D�ڢ�ô�lD��Z�Z�	��i�/|�$D9��9��y��}���{9��N>�xD�O��9�c\�y$|"@dR�x*\�cÀY�kX���A���'��ۨ�ބ����{���=��<{�e��f��$D�=���?�9؜�ZI9��uR%Pi8ߝk5��d��bI9,�$Di�cҀ�&�i8"��y5�b*䢡Mm���l�-|�?���G�h��ܖ�c��9	˂�$D�Mm��b�)�$D>?��b�c�i8��b��*�<��J�h���g�a	��l1j��bI9ꞙף���$I��O�iFd݀[m	f�"Fd�&�◁�='�`��$D�����?a�}(�bFd�O�a����b�ai�$D�l*(�)?�n�2���{p�{Հ�SZ�a�=FdٮH��.Fdp�{���{�bFd�b�a��"�5�{��m��l !O���i9��3�{�?����l !'
�.EFda�}Ɓ�X��7��h|���W�	�sI9��a�bFd5�{I9o�ڢ�/uj��#��)u�i8m{�݀[m�I9�b�a�+i8l !�X�c/u=�+�&0���i=�8gp+!��'o��z�܀���+!�=fe�RFd8��h�$fe��SR (��	�h��G��
f�۪E�4S�>�%� ��Jm�. ���h�Q���BUFd��ւt �?���G"�	�, ��'���)��0Fd�����/u���i�b�a +!R��i6��b��57FdW"+!�  ��� 9 !��i�/ �ʦ��G"� g !��Z��ڢ���im��<�{�����E�y�f����D�{��	��8ۘ�������T�ڢ�F���&��é���i�܀B�"uL�ڢ��@m���im�x�5/u 
�?�b��zi��i��k?�3�y}�	�w�o�Wf	��ik{�)���+!����\ � ��Km�
x����-0���w D�}����e	�?a�G"J��j:
֢�)D�b�{�[�
-u��i}�ڢ[��i�b)D�gֹ�i��H��Bdi��i�)D�?�G)D9���G"��?�i�\)D��?����k7)D�b�{�΢a�G"y�{1�� 3)D���i��)|��3����i�!g��{��0z���i�)|/)D����{dfdZ)D����s)D�q���M97⣬:�;uo֢��mT)Dwv}��	}G)D����,�)|�nyf3�(�S�	}XW�(�	���i����%�?�m8��$��	���)|��i�^ve…��T�?ӧM9
��Bd�)|k���S�i5�)|��'��؉քM9��	���m8�\�h-uP�)|3�C�{��)D;�Hv���^,�E�)D<��k�����)D)��k��z�b�$���a��>�$3�Q�;)�u4���)D��
�,
����O����	s�H.)D���aH�$o�ܣ�b)D�f�b�$���ʃ���)|+��a��)|��ۨ46� p����%�bOme��a�I#��iaCv����b�`e�	����ak��b���M9��m8	��a����Z�ic�`#��M9�	}݃���M9�m8���P��9�o�f����y���{9�Kd|F�X3�-թE��ke�m8��9�e��a��i8�Yd��a���a���{S
�h(��J��	���%���a���{� ��Kd�� ��*�	D�ԁb���� �h[$V~���{a� p���Kd(�s�&��Ydv��J)�>���a��bs��a�� ��j6����&�fi�y��Kd�� ���0�� ���aS� a
���Z���� ����<��k���a�� ��"|��&�4���a�� ��&�O��� ��"|"��a=��� o�	{��3�R�i3ۮx�k�N��&�	%�� \��{�� ��ke�b
}���{�"|�ș�i.�ke���+3�ke���ao�� �b-|9��?9D��Kd��"|>�b"
}�b9D:��? � 5-|Vg�(�ȑ�f8��ag��,�� �$
}���+�9D�&cy1�� '��aN�-D�b
}0��a	��a�� ��	fX��?�
}�{�s�&ւ ��-D䕧��� G�-D=�V^(�	f�-|s�&�-D��f6��{�8��-D��	f�-D$��?��hVg��b-|Gg�de�b D[\��}���F-|� D€�4��	f��"|�-D��	f=���c����	fj�-D�-Dm�7�"|t
֣��-D�f8����i�b D��0�G�ޜ/9DC D�ࣂ�-D��@m�	f�`1�
 �-|�b DD -|n��������{=�sqm����6�iƁ�I$�f8�b9Dr8	�a@��?:�@m�b D=����Ɂ�.
}�-|�;D�
 �boe�}�b-|r8��	f	����@m
 �	f��'�{�b]d�b�{�b}e��oe�)!�b]dL��j�b�ay
 �E�a�b�{���d��V"aOd$��	ft]d�A���a�Od��-DBOd�5]d�9D��a�ׁ�{e=����boe<',�b�)!-��-��{e�]d�ׁ]�a9�:�<'挢��E�h�-��{ez]d�K�a��9D�b�b�ac
 �)oe�b D-Ods&�< DSZ-
<'� �a��r8� ��_)!s�%�}r�)O(�q[��
 
 ��ws��-�coe��a8��i��[d5Od�{�a�}�
�(���9w_��hcoe�B9c]dף�m�۪���+Odcoe�5���>|w��&D$�i#m������Fhcoec����&Dvt\�&Dc]dcoe��Voec�OvP)!nOdQ����{5�&Dۃ�a	�{e�{6��-�<'CvH)!�,c
f	_�{��ۡ)!��y��9���a�	}.
f
�&D��y��Du�<'À�`}
�r���{ej��ha�u	�&D>�u���y8���w(
���v�	`
c�{�|w�����?Do��S�����mJ
f��Kd�{�
fa��c�
f��=Dc�a���������1d���ߣ�i�F�(
���l��c%�?D
� 
f�1��B/��yf3�uP$|�|w3Vk��dD�ê�JM�����Z+8��5b��cy8)
�
c|wc�i��&DO��Lg�fU2D*���ޏ��cH�'��?D��Kd��&Dm��$|�Dm���c$|�f�f��$D�CDm�	
f&�fxL$|��)�&�k2Dd)�E`8��{�����$D��=�s�(Dm
�2D6�ir�F�$|��	_�O5bZ�2D�`8	�uT�	��{�z*� 
I��히b��{�R�?D�
��
�H`8���cJ�$�:g�c���b��"9:IB��b��B�?Do��{���曡cQX[�Ꚁ�c���b� �Hg���bi�$��i���i��h�w�?�e�Lr�Ȁ,Ä�uX��{c�$N�3i2D��$D�� =��Ѩ��u�����q�{���c�$��$�ie%۶�8`8W�$���H&1H�t��p�-7�����[mk�2�r�c`8݀�/ˆf��c`8=%���ud̸c�i�"'z)n��fȀ$�[m�ׁ�l*_��-��{,
�-耖�;�i#�����i]��{�$D�:g�vs�b��A[�-N�F���i��-<��$e�$��:g���X2� ���/)����ʣH�-�&:v�-�R�-�DX�΀���j�1����6��:��$��N�9>�֣Տ��=Y@��==u�/�9��-�u�
�?��F9�I�>��$���*�a���̸&�2��X-��b�8g߁�u�F9
�(����h9!�]=u;�I" �i��cz��.>�xY�Nj�
Ȁ�?{��c �))�����f3 "��k��Ym�ubD��y��k�i?�i��ۧǰt��km��"��Ё"'�k��-b�-�9!
 �����PH�i�W ��i !D+D돁k��a���o�i*`{k ��{/f�$ c K~&�]9���a� ���X�悁k G����-鑆�$uo��}7��uO
�{��a(3�y����;ɣd��<��1
�B�/%ցk���{�	�ck����⇉��#��	�Bd��A`��ǰ�]=uH�le}S	+D��i�X�Y�;u =u��`58R�cg���j��#��'�i��U�ix')D���xG��a;a"𸅀'����o��JO !c d[�zI��@�g� ���a�����'�b7�"u"�k��a�n�7X���'#c)D�)�{7��k$c)D����E��1�{��)|)�z>�@me� %c)D�;uo�٫�֣��)|�?��s
�&c�{��E�K�Av!��Ќ���Cv���$@�i'c)D����i1D� g�)|��<`�;u(3�{�ʢg�?(�)|@� ]��?��F�$��u<��i)�)|'��k���?��?!)Dщ)|�
�x�
$ɍ�܅�'�'��"um��۽�Bd�$�c�P��?:�$�	@9@��$=��ݜ<gz��$��(m`9 @9*cf+�-l��8#@9��$,�--c<g���ce�i�'�
@9�'�)D	�)|BP@9?<g���c����m��c�;�	�$��.�-/cM"��-� me�<g0c)D�fa��+rs���Y��`e�+�sEfڋ�$]<g>τ-X�)|Q��~�	@9�zB�d�dP�o�֣1c	2�о|D���$S��a�f��k��c>�u	29��-2�$	�$�˜�g��fy]m3�-�&@9ܛŮ$���%k�ێ��?4�b���i��V
�$7@9R\�k�`+�Yd�K��+d,Ղ 5c@9�`Ɓ����:��$�<g�� >	26c<gH�$��a��Uں�-7@9��y	27cFd8c<gm����S��c�'�	2`U<g�S�t
Ԣ�Yd9cFd���`4�`
�y^��cn��%�۾W�Q��Ԃ�a5��c�'�`:c�y���^d��%;cfk�x�C`���7	2�fl�y<cf� �y��վ���
ۯ���=cFd]f>c�`8��A)�?c�a@c�yl�L+�x�(R�e�zw��Zvv�(Ac�y�=	25WMm�-|j��<	2�C�aG�O�p�3�-uae�P���Yd{��%Bc�y��i��͌���C��Dci879�y��Zv���9r�Ec�y���F���,:+��iFcFd�-|G��i�}@���͢-Df�GFd<8i8ᨁb�$-|�Fd��:Fdw�E釀��?4i8#H���Fd*�m� Hc�y��9��_9Ici8�Fd�i8�u�Jc-|�FdUi8K�_9L�};�`g��i82g��}�W�!�y�3�i��-DM�?Nc DOc�y�, D%6�2��?��-D��u�y6 D�z�	�(� DP�)uQ�-DW= D��-D�e�R�i�`1� D���?��u���S�@mߌ0y�-n. T�@m�i���X�@m.2 ���9WU�@mV�%�֣Wc \�@m� D���D9���cɣ��@ms�E���E���󥀲���x�i#�i8Xc D�D92� |Yc s�E��+�_�A]dZci8� ���[�Id�  %�U]#�@m\c�b]ci8$�?��D9��@m{�D9�]dt��.�{�7}e��E�!:.]d��8�c)�íN�@m�}e�R �+]d���s�E�=}eP (e����?��uP�`�M D�.��r�!"�b. D*2^�@m��D9_c D��E�:��`c]d����
��a D���f%�a�
]dg�⟁p�T
 �>]dY�scac Dg1Fm��b�@m*�O9wy*}{�sc]��a��E��@mz �Fa�
�� .�)��íz��%
�sc��E�bc B��cc�i�Fm�íd�@mhFm�D9S}eFm��E����{	��۹Fmec}e��a��D9�E�1��{]dm�=fc}e_���`?Fm9�7Ӻ6Fm��̣��E�>�beC8�iO��z�j�sc���/`���}gc}ehc]d�
x�+}e}](}e�6�i9���:F�a��?���z�)!���%��b��
2�-u`��z��i4�b�B9�i�sc�/�i�>�%��sc�gȢ�}icFmp��z����4���O�bڅ��hj�a�>�y%=�$����Y�sc��k�a�ѣ)FmR�i	�f����sc�FmL4Md�Z��sc:�i#y�i��?DYFm��Ȱ?�y{�o��i9�yl�m8�fFmt�?�CC��`mc�?��ya�uu�Kd?�m8d��{nc�i���52Do�m8pc�i���y�m8qc��Rc�i�Mdr�m8T�	f���z���y�#gsc�?��f9�m8��̣u£.`8(��`۱��^�?D"`8���y��m8�Œ��"�f��m8m�j�1`88F£H��a	��z
��,����2D�����Md�$D.�#n���y�$|�m8h$|��$DDm�$D�E`8t����(E��$DX��!$%��i.� ��*�1$|t�$D��q��{�� ��	f����iu��W��}	£�D��v��``8j�m�Kdd�m8n�$Dw�m8��3���s�$D�I9�í��`��i6�$Dx�	f��d�o�m8;�$�����`Ӄm8�$D�#nvEDm߄�y��9�y�{���`J�$D�9�$
����+�aX���	�q�d2�㮜 $|<�%:�I9z�$DZ�Rp{�E�ۊf��?�?�����$��ieҀ �#fyI9�$||c����}
`j22D'
ܸ��$DQ5}c�-d�$D~��?�cI9.�-Yfe3��l�}\��{SD�a[�a~�2�'�a�c�-i�����i���i�c�-�� E��y�c�a���H�-���{<��i�$Dt�a���v:��S���v�c�a��w0[�t� �� Ą���k�a6�%�ܘ��߆� h���t\yI9�&I94�-9��Se�a~z����$�c�a���Q6g��?����$>���69D�`�cI9+#g�£�7749D��]d��F9�'�)Տ���d%������a~|�UB"9D4Od��-:���Ŏ�}�í���'�F9�����vl]d��KmX�y��F9.��?��Km�c�-�V
fh	�a�c�-j2
f�c�a[]d��Km���k.
f3��X�.��-�c
f��a~��k�Km�`��-@�Km���+�9�-�]d��$�{��k��a���k5$uy�y*��xۃ��k�V��_�{��v��Kmq�&�m�Km%�Z�	Dd��%y��ih�#ga�F9�c�i��k�c�{�
�5��x�Km.�޹z#'n��KmO��?]$u���i��۔�k�r8k��-�]dT�a!"$uԁ�k.� n�6|�c�{���7�� n��f��iƁ۪c�����Cv�� n�c
flJ$u��Km]�f�Km���0�'�l� n����c9D�� nU��k��k��f�
�{*��5�Ɲc�`��KmL����Km8
���� n���k�V)DŃk���me��Km�c)D|� n���$�c)D��Km�!D����Km��g
Q|[V���o��kԈ�+�� nj�{)D��k��?R��i���i���$۱
meQ6|���)|�c)D)��?�c)D�c!D�!D�U)D���$��?!�c)D��)|�q�!D>��{e�6)D�c!Dm��vZ�{��)|��W)DȀ�� nh�q>��*[!DJ�{e�#f�R�?�r8S7�-�`:#,)DJѢe�)|+�?��)|w�؉�f��)|���?�T�-� n��ie�q>3��?��� �)|�c�-x�487��/���S�C����/D��%�c)D��/|����-�)DQ�7� :`8�q>�V)DQ��Z!Dm�o)D��?^��v��)D��q>5��ۃ���c!D��i���5�?�c)DX�;F��-�Q`8���{:!D��<Om�1Om�/5��j�?)R�o��)|΂��6�-�cOm��)|���{���)�)|����Im���{����S��$}Om�=����/Om�0�j��-e�q>OmԞ)|����cOm�*n�M�-��!|׉���q>�)�c�-���k��Y��(wꅰ�q>:Om���i.х?c
}N�q>����݀�/v����`8&�9�^1�-w�ݹ��a�>
}BOK9^Om��U>�K9(�=x�� �j:���U>R��a���b�+���	0�)�U>遁b��a��a£`��Ԁ���~~��iy��a}��a5��dʐZһc�i�b��U>YOmȀ����o���a�c�i���az����8n�c}ҀI"9¡��b.��a�	ݾ{���i�2�i�c}�cOm��Bmb$n��ak�}�
$nL��a5�i
}���`
��a��iY9��ke��J.�c�i��}:��a����^"�/<�
}�e�E�i�'�%��o�!����a��{=-|
��a�MmЁ�h�c�i�{����`���b�-|X5��i�
;D�Gvhi8�c-|�ci8�c-|T����mu>��vz���a��a����)u�u>68�i��;|؆ ٻ�a��b��aR�����ii8��-Dm��>r�%D%=-|d�-D�i8q����iډ%D�'-|hi8d�-D�I��o���k��́%D��;|��)uE
#���i�-|��-�%|/�iY��aԊ[m��}��Bm��;|9�;
��a�%D2i8�`7����%D�
�{�j*���a���i��-D��>�Bm\�%D{��$��{́�a �-D�؉F�%D���U�Ido�-Du�%D�}��i��0ه�a픪�i8-|��Id���/%|���և��c#�,8�%Dlæ�,�b� -|X����d��%D�HS��c�%|�b?�&-|�i8�-|5��cg_"�)�c�-�{8`��-D�i8��-�)B+i8z�;|̑Y��x���-dg!�c�ic*�-��a��S�&i8ـ�c��-D�x��<	2u>_�%D�M�&.n��؁%D�c�a���<��c��%D��-�7���;|��`��%D�E�b�-��u�,�b�	2H�uY���,7�E���{���$����c	2���۬f9R9�0�^"2����b��c'��G�a�P	2��,e���pf�c�b��b@�Y�	.nv�w\M��:{~@	2��=A����z��cN&0���HFmz��c�S��c�a��!۫��vM�a-��=��4�o�������-_�é���h�a�ۘ�-3�`o�?d�����C��cf�	0���Bdy�a�c�a��Bd����x���r���r��++(�-�J}e��ah�a�
����-�#f�ـ�4S���?�c�a{�{�x>��ێf�)h��?	2��*�T-u:�Y�m˴{��c	2BZ�a�H�?J�b���y䅩%.�a�cMd�ۯ|�(|��?k�Y��O9ӆ�?��(|��O9��y� ��x>��yJ��E���� -u�Y�m`Z���%`��Y��ۮ�?Dg�(|9-u���o��+��	}�x>�	��A Nj�X
�?DMd?�V1�-��up��?DR�)n
��i���y�#�a�m8Ё�x�	},�?D�c�?��)n�&ii�?D.�aMd՝	}�L�ac�`9{�-u��?D(����.�a���Bd��?��)n�c����L���c�? 2D���˭	}��?D�Bd?�g�p�?Dݷ(|%�}���ia�x>"��y�m8��c��?D��?�x>q���Ё�b���3ʌ?�cMd��y���i��x92D����7�x>c˴o��a�x>��&��{2D��oj��ɻ��B2DV�f���(�@d���kƁ��f�Zv�)Md��)ni��Ӄ/|��)n��i���b���aS��Dd�?D�˼�$s79%�A��b��$*���Ϝ�����cI9���Zv�@d��k�m8���Zvf�m8Y-�V���y���i��$��?Db��%s��9�摏-х�i�F�2D��$�~~�c2D�"i8B���΅m8(2D3�
2XI9�i8qI9l��7�_��-��bd��c.��%(2D���g�x���c�c�r1���-D"�F��%��{�cFdI9��z�샿%쁚kQ���N�F���`I�F��=t�v��%r�,i
C���Zv�ǭ%bX+S�7샿%��f���kx��^X+΀ ��
2��&���.��g���&],�x���&�Zvj����Zv��,D@�Fa�%4�%"�2�}��Zv�Y�3�,D���.��NVVi8���o��,D���
|>���Yf�,D<8i8�ϣ��2:�x��?H*I9��!�ci8ϊ�o��%�,D��%��-��i�2�N-n�ci8*{��?C�,D	]dO�P��-y�,D��-�c�y��i/�69�,D�yE
}�,��,DW�`a-n��D��
2��,D��Ym��Ym5�
2��,D*��9��zU�S]dg
}��YmfF��-n�c�y�{X�y@���ad����a6�Ym��íh�87�5ŜYm7���� ���YmC��a튤��Ym�g�ʉ�`�}',f� N��{��a�	Vm��a=�%���m�,Dl�f�YmS� ����{�8�+Vm!毚�ޣ�	fp��{�,Dr��a�í]6f)��?deā�����a��,DL�����d-n�� X{���������	��aVm
�8��aS���^��`hI]d�{@��t��ۘ� N�Ymn]d�a�kf�y��E��Ym9�7�p���f���)�&VmR5n����U�ʃδ]�� ��a߆f
�b�أ��Ym3�y�{*��$�"n�8؉ ���{0�Ym�-j� \�Ym����a5n� ЕYml)��fݧ8̈��O�{���`��"�(��a�{�� �8��a�í{�%�8Ņ+a� ��a�Ģ����9������)�#K8|˟ Y^f1�M9_�����b�xF9���5�Cv�VVm�F�H�?!�m8����k�k3����E:�dDdw6���{e+�%k�<��m8��Z�d�%�$O�����m8���2��b�%f�m8�[�a���-�f�/`8W�m8��v�<-�O<N�@9��m8�3�a��b�ùs��d`8������N�`8����=΁�5�m8��,_7�m8��<���))�%d�%��<��F"f�m8"�%�����^(@9�X��^�t��)3�%�`8v��ܦ�<���Y�JE
f'�zd�,��5z�Y�ks�8��j�
�S	��$���o�0�5):#�$]m����m8L��d�����{��a4�6�m8=w���x�
]m;�5z;�������:�{�í9��Q0�`d]mݖ9�|�M9�m8�3
fd�a	`.�6�')Z��a�]m:�x��ce�`ЁY�d�a�c��"��{�6CC��``$
f߂�i�2â��:�ݲۑ��Y�A*��G�a��:F�9Y9��l��d@9�`8䀡c��f��nd`8o��1�aЂc>:�b�%��Z�a��"w�w���x^�Y���i4��%�W]m�bw�� d]m!d�i��ߝ�m�	0���f�2�i���&>�أ(,]m�ik}c"d�aR�b�
f{��"?�f#�^��y��o��7�(�+���i��{Y]m��c��b:�5z$d]m1Y9���i����2�a����5�i��&� �5z�7�iz�`���%d�bo��j�5&d�{'d�a��g�`�֎
5�G$�a��>=6D�nh;D(d3DC���i_�׾y�����	0�e��"Y�c>��x;�i�ze>�A<����n��bm�Kd~��I���< ��b��q�)�c���i��e��3D�e���`�[m�;|���F"�=�����*�;||;3DŘ;|+�_9�3D����_9[
�ig�;|5�����3DO��/2��/;Do�[m,�_9y�{���/$��R�W��b���/a�_9��ie�;n-��/.�Id��5���ib�;|���/~�_9“�,.��,h�ۄ�_9a�}K3D9�ൂ;|�	�fY�����)�E��!|�_9��w��F"+�<���ߣ�;|/�,<n><n��/���׊��q������/cD�,'!��_9>Z<n0d3D1��{��{2��/r�or��	%?���/��a냳�8�f8�i�� ���o���%b��/$�D9U��%��-���/���3d<nx*}��{��/��;|]�Id������,q��,4d<n�8���/��c� �_9����.��{z�;|R��{5��/6d<n7d�c遟$��;|9��/i��$8d�-9��/o���,��״Ġ ���@�xE�-�r����/��_9���/OS�-!��3���*�b.��a:d�c��ĹF�����y�k��>�c�A�-�yȉĦ%Ɓ�t��a��ۙ��݈�L��\������_�]9��+��F��{a{�<nXFaW��a@�]9���+� 1D�c;��{Qs�X7)��xJ>�i�M<n.�-�&;� �р��)	�]9(��m��?��-�aJ#�-|� �)B�h�񨑦�v&$n<d�-<�=d�-݃�DR�c���&m�gf�c��i>d�-�$n�рPb�i?d�-��?Ӵ�@�x#*):Ad�-��ʃ�$nR4Z����{��b�L�c��z~��z0f�30����+��aBd):��H�>:C�aTp����x����״���
��a~��+��磍f����{����al.fFm"�^+�me��aP`� ��a�MdW��+5�;uDd$n��y��(&Edme.�?D�
�it��	}F�?D��G�8��>v�[d:�?DG�%D;`�g�����?D���i�Mdp�
H��iId�i=����Y�'
�J�;u}���i��k��>z ��$���iKd{��iLd�km�g��i���)��iy�?D3�H��������$�C����۵�;u���:�M��i��]d���F{����xN��iOd�kցۙ�5z�[d�?��`��Dm_���)�z'MdP�@d��x�meQ�@d��{%��i�,�&:(?D~�|
���^�@d ��cg��iRdf��@dS��i+0�J��'�Ұ�.met��Td�,�����@d���7|�p���%�?DH�kЁ״\.�%��%DU�?DJ�@d� �iV�?D�%D=�@ds��Wd�%y��cR��zl�@dX��iZi8y�?D��Y������Y�@d�)5�?Dk�I9����Z��iYCi8si8[���[�-�I9��%�@do�L�큊b?��c_W��-�T�X�,R���g��xZ�%߁���{��X�r��	�kƁڷ�����Y���\d�%�#g]d�k��4]fe5fo��i��@d3$u��%�5fe萨c(F$IB:�o���}@ۯ��@d��^d:D��Ԁ!��a_�b^�&:`d:D������Z�I��-�بc�!Nv��@d�c�a>�����Z#~~���b�
fe*�+�:D�I9�fad�%�#$u�~~���Ybd.|��:|�@m@�x��a�陡f��h*���s��C3:Dpf���(�%���?S���c�:|dd�aedi8:��I9e��Į� fdi8��:|X%f+@I9;p��YCi8���-��agdi8�:|�9:!�����d���-j�ۙ��?R�i4fe䋙��:|��Ӛ��?j��iq��e���MmP�:|m$<���7�YmwEMm��i��:|����fe[�t��� D\":D�}=��ւ��h�:|�fe����+�`����&fe���.i�k���]�a)��N;�aρ��F$��W�;nj��i�g�kd�i��}G���Cv_ۯld�aT�?��:|��a��&��	}A�)nE�Cv݁<��?m��_���Շ;n�<�%�ndde��85��W\��?&�bD�	}��<�o�;n��:|
1�y��{?��d;��?p�:|��0������:|Ҁg#����x��R�<�	 �bg�Cv$�<�Ԟ?������mf�$�b��Cv��� q�fr�ۅ��Vs�Cv��@mT����)�fĂ@mƁ7��i�4b� ��Cv˸��	��@���s�t�g#݀@m�	�bG ����}ee�ۄz��b��%z��u��a�5z̏��!���M�g#⁄�v��	�8��������l�g#w�g#<��i���f�CvJ�8��hg�Cv��<�� m����&��բ�&x�ۀ�-tv�y�E"[�<������e#�����M9�dez�m8���p�m8;�<��{���ᘐM9"�`� ��\�<���b�aʦ#&��H���%y�<�������5$˸��z塁<�?	{�U��%քm8��Ӛ<�m8{d>|��H�v�e#<�m8|���H�}�M9o��.���)~�m8w���U��%	�����
��.J�iߝ�����t��������3l���1 :�M9��FaƁ>D)�H��H�� :@�>D����3�4��>|_����%;�>D�>|Vx��-���	�?��zv��*>|�8�,�+-nF��+�>D�>|5�
��f1�d-n�!���]m�Om��>Dq�m8��e���
��)d�Ԋ,D��ek����z��z�J��Ǔ>D��g�4���<w�m8��%�>DJ��@�����h���~�P��m8��w��,{��IBZ��ʣc�d>|닸���b��c�>D#-n�	f`���`-n肃b�	fZ��Ў�a5�ke�f�j�RGv��o��c�
-nd�e���a!��c7��`��b �<�d�eI'V/��c���bQ{�Q{[몣c���a ��b-�b��Ă�{�����c��a���a��	f��>��)u�h>��>D��b{��o���<������	fH�>D~��Ǩ��O���:�>D�d-n�ODm�$�b`��	.��b�-n���a{��Q���a=R-n�bDm��b�GS���>~���@v]�"nDm���&-n��%�Gg���^��+�h��w���1����c��|,gw��ke��=θېd�h�zp�ke��p�	�˕_�6��c �&���Y�ܒ�keQ�	f뀵"��ke�&��J��+���a‘ke���cC�	f��c���bX��]�f;�<���b���aҡ� ���al����Y�&	:��}���a=��8۟Ad���=�.��)hF�Ad���I���`���Q����[��܏�Adڋ<�*�Id5�Hث�*�Ad/�����h��y��Ad���$"۴����Ad´@v���/��Dm�"n*��5�<��&��Ad�y�a�߮�Ad�}΂<��6�C�Ad�b��ta��iB��Ă@v��ݝ�����Ad\�b��O�T�������Ad�����
�Ad
�iek�H���j�H�9P�C� .�Ad���(��9�8
���j�Adg��$�F9��Ad��}	%��a�O�Ȁ��ň����5zW B"���*ƁWҗ�<��doeЁz�եs�O�
oe�-uz�Id���X<T��F9�d�{_B"�(oe�d�c�B"���$��a�Id\�5z��}��Ad�L�a#oe�����܋�Ad��<�a��yX�a�Ad��e�d	2�doek��yQ��,��_9�[�t
f.��y�<����F]�a��c�+-u�oeQ���<���H�Q:�aH>m0h�d
f���޲0`1b��f����doe�Z�T�Dǁ��%�d�a�
f�;	2�&���o�0h ցDmO�d�b�	2��c�dDD��a�g~>&Fa�dDDl��$���[�8DD�S�i%����dDDݑ�,�:�a��H��&�[�0h�oe�dDD�.g��~���ʉbe[���ah�c� DDᨸǪdoe9�H[���P$���\Coe���id�/�$u&Dv�r�k��$u��#gdoe\�kT"�bo�۫dDDB�P$�doe�d�a�dDDyO$�/�c��b�dDDg.�a�d�/�d	2�dDD9N�i�d�a�c	2�d�aXme4Md��/
�)�DDZEd"�x'DD#��i����dEd�DD�d��me9:IȀ��Ȁ�LGEd�MdD$�y�[�
�/@�_LMd��H�a��Jۯ����DvҀ����}f+DD� �a������)Md�d ݝ��=���DDhMd���@Ed�Md9��i2���Z"�DDWEdN;�a9*��=DD<Md����)�dDD�d�aX��ʒ%z�Ed�dDD��)��������v�)�����)���i[��,b�[d�Ed�  2��d"�{e�`8j�f�^�3�{e��o���ioͣi�F��^�3��-�":���iG�^���<v&Nv���io�ش�f3meIMd��4G��*/MdL����{s��i{��c��<����sMd���{!D�;IGNv��i{��c���{�� ���bof�Md+'���4��^���y�',�dEd�>@m'D���{b� o�G��d ��[d���c���{�Nv�% ���,_�8�
�L>���i��bt��i��������z��^���i� �(u$��$�����`� =����k���Ă[d����� 2[�;��i�d):!���V����i���w��-� �����Dm."):���Qs7��3���g��q��fC=C�5�
2���Ӄ^�n=Nv���;�x�Z�עO\v�$n������W�f��^�Ɔ�`8D�Od^NӃ^�X�*E��{���:�>�����hD�ش̈́f��-ĀZ��
2%*���{�d\v�$C����bZ��-xטb5�����b���{�d�h�Ċb�$n8��{Ҁ<�ǥ(�H�m$<��ݯ-�hX!6n�Dm��*|lf#� o��?�Hd�d�V$nL�
ߨ��ׇ�z��Hd2��`����p��-�݇�`\�<��ƌ:�<��`��zo��E):忆$;�_���D�L��d{�d����Hd���`0h���{+�Hdҁ���z`�iԮ;�ƴ�Hd�d�ik��(�HأMm�d�i�~8�d�i���`h3Mm����9�Hd�d�i%�%z������`z%Mm��Hd�d$nH��f���)*ֵ��`:F�i�� �xӣ%|��Iv���a
�Œ��艂<��幀IvQ=n�� ?�����Y����a�7�i{�>�/�iJ��W*���f�{�냷_D�aR��i���a�d�i���
f�d�c[D�b�2�zt� �d�b�d�{��Hdi��i��� ��$�Hd�df|�b���i�C�	�{�d�b9�~��Hd`������ʈ�l���iه ��"n]�b�!�f��b/��a�d�b��bƁ3�k� ���7{�%zx��nY�i�[���cD��\����ft��۫�	�����<[�Ԁ�Yy�"n��Ivj�^�J��A�Ġn%|$p�k��%#D;�cm[��W�4�v�)u�z�+��%D�<�(���{�*g:#Dn��e��֪�c���aj���4�=���� �d#D3��
�c:#D���a�Dm#HĀ:#DR��a���czf�� XfN���c��=.�zc����+fP��c��z��b�dfa�bS��=�
'##'����=�6����b
+!��b��#|Z#D�	fI3�{��%�[�%���d�bh۸K"%��c_ȵ�"n�K"h��c�.|Ё��a��ca�o!+�@";�&:�
@"Ё�܊۔��W��c��z�Qflj���߻�4��5#D��˩�c���ܑ�Fef0�Ђ��d#D.�z��,k�í���sog�&:��$ϔz~�\���o�������@
f�:D�d#DX�M�^���Ҫ%�Y�^�f�#D��<�m$#Da]m���^�.K"��<�	�%��h��(+!�!p��
+!_�X�[���:|
�i� �a]ۥ��[���Yb�z�3	2L�$M�^�����O�6
��=�d�ax�x����_�#|��z4�;n�d�a��u7��y����f݃��oMv�����d�aN
-u�Uv�Y$y�a��$:ϑ��Mve'�a5�-��^��d�a��-B��r�ye����۩��>f�a$OMvj��i3�)ng��h�[��d@"��>�7ܸs��{�g��;n��߁}��Hب����:|h�a�dVd��Z�Vd����;na�cg��ca��m��iaVd?�^��d�`U"7琡�%�$��;n׀v��Y$���h�^����&��;F�vR��&
�	��Y�Z�^��%;��&v
�)�dVd��^���
�	O$s��Q7�;U����a�%Tl���3�������_�a�ۙ�8gӃۧ���zw��E�;n�J�W�G�d�a��;nڰ�a���>�ye����`�X[�o�cF��aیzwH�p��j�����ye���f
�2x�ebg�?��h���a�i��;nk�%V��	��Y$���a�^�o��a���H�T �f��%�����Y��f%�$׉�V��a[�U2��l�fi��x1��a��'D��z�6� ��'Dʒ���튡i�
*���%���
*#��'D%�`;��$y��a������#B��f�'D��Cd��8�d�`��%;�ߚ%�q���i��m���.����8�ˣ��b���a���$��'D���a�����b�	�f��������f��t����%���0˜�$��'D���b�%�� `�'D���W������{��� ���a!���3���(�����,D�>Dރ�iӏ������&À,D���z€���
��a� t�'D�3��>D�*�{��'D�d}e�-n�р=M}eOg���f8��(`�'DS�c��fooe@�cv��Y.� �%}en���̀>D1�C���,D�Z}e� �f���#6}e��h�>De���'D���}ey�he�haY}e�	f�'D;�;uey1�-ny�'Dx}e0�8���y�{ת�{>|Äۯ9}e5�	fN�f� ���a���.��{W� e�焦�ne}eޝ�{Z��+vĀ���{��f�bKCaz�c��z��bȀ��{�	fr	V/�4�e*D�a�W[Δxӹ�e*D� }ך��{��a�by�h	�	f���{)}e:�xӂ�f8�� �Z}e/����*D
�a`�ce)��xl�,D��b�<-n�@}e�?*Db��{M}e_�8_� ��En���{���a��a��`}e��iʋ�v!�-%�c�}e�Vm�[���o�4�
e�c=���]$�/-n뀹�J}e>fk�|�����io�Շ�f���
���ve)�;�h��AV
f*��{w�F���e�x��{j�ۑ�&ј��1Df�$�|T���{]��a��{�۞�{����O�	fkc��*�*Dof�{L�+nk��a��ނ	`��9�>f��5b[�>f#��XЁE���b�e*D��ԃb�[�w�m8�&�aց��Ё@+��`�@f�,��Ɓ��xeVm)���2��e�iG��';
`(,�i��%�)��d�e����B��'��[d��^��m8���<�Ad���%�.FP�fo�be��f2�Ad�?������+I�Ad:�x��\����H�t���')���of��Av�J'����ִ^[\vV
f%�x�[B��'T\v���'���=���V
fw���B��'�Ѧ,>f,��=�cŀy<��c�=of72:�x� ��,����8[�\���G��'����b�z�c=��y�\v��,�I��e�o̢e�{R��:�bA�Ad��m8��{��L��?
f����eI"*��&�	Y9t���/�{"�ۡ��e
f����Ќ�@09�e�?%�cl��b��[��D
f�3�{�?+�m8�
��m8�KZ�_)��1���-�I"��m8l�?��{��� ��'e��o�� e
f8�3� �{ؠ�49�z��k��	ʵ�ݨ��c?��,�-�\v�?�����-�?��`�{�:DDZ��A������Y!e?�<��"e�{j����O�{���c�ִ�񎊘b#e�i�X�-�8DSĀ�JDD_�i��-$e�i8���RDDw�d���Vk��i�1.|A�@m�
fv��o��
��t�{�i��e%eDD>ہ@�{4�ª&eDD�X�{.|Vv�DDR.|*��A'��`�iFDD���Q�;�(eDDB�i��$�����{7R�?\:DDo��
���ԣ�۸��`)e�i���/[���6f>�?(���Wd�DD܍�d�#�bf*eDD
�i+e�-o�=(�>�ۚ��$�x�ԣ�&.|,��#fH��4-�-�G���-:.�-_�b�Dv��Ԁ�J.e�-�*�i�=�
f�Wdx�����`/��i0e.|O�-Q%�i���k�3�i1e�-�f2e�i3e�-ͽ�$,��ü�k4��i�_d.|쮩x砃k���/۬�a:�&[��kݷ[m�遆�/���k5��i6�x"��a[����)�c<��/�.D�-��	���k7��/�)�c���$)��i�^�c����e��a;�c��.D���a����ƅ�k8�@m�c���w�c,��i9e�b:�k;��{)��a�������r��a�b��/<��{���y�f��cԊ�{���?��/=eWdo��bo����棣Wd[����@��k���/>e�c��b���(-�`?�[m��`���� *�b@e1DA�kB��{2��{Ce1D鯃k���{݀�{���a���aW��{B�F"De1D���ke���Q|k���e1Ds�E��/�� Ɏ�k���i|��a҄�/������{�1DF�x�h?��a��,��Y�`��k%��{��o�*��Qʃ�kG��/��{o��/�M"\�bHe��Ie�ct�-Ё�?	��g��?���c�M":��cJe�c��Ke�b[��?��1Dކ��Y"9�S�f)�I�8�9�L��{��ʜĆ\�M�kN�?�� g�>Ҁ�?���k��f�I�����O�?3��ko�G�t�=Pe1DQeۣP�h���Z����7���۽���{Re$n���S� �ˣw1DoߣԌ)R�c:�Zdk���l�Zd�ԅ?Te1D�W$n8
Dm�$nT�ZdUe��V�ZdS�Y�l�/Ɓ�v���WeNd���(�"u%��mAh9!X�ZdӐ�c@=n΀��^��Y"��a�^�h����k����m�.��z)�i��Zdʃ=큘b̑tYe۫��?��Zd��Zd���Ze�x��k��?��ᨁZd���A�M"R�x���k[�Zdw�U$��?>�Zd �[vT�i@��$n�xDm�7�a�xm&\�Zd	�Hd��a2�h�xKD�i]��aTrӫ��i���i� :���fk�G��a��a�%#���>%�Y��"��>݃�YR��iȀ�	ٳ�a��@��
Zߣ��a.��$�Zd��բ�����c�a��a�Zd?��ܕ����Q�܄�b�����yQ7��^�ZdW��iW$�f_��ag����b7��a9��i��"n��j��8����-`�a��^���%ε�i\��ameQˣa��i��a�
�`���i��b�-ԗ��c�i*$�d��i� ͓��(��虶^���i]�x%��c���9�We���\�Q����W˸����W<ge�IvX���D�֣�p�,5|R�i���~��i��&:����io�����jf��ige۾�0��� �P��i�F9h��a��iv���m[�"�=hi��aQ�U �
�(j��ik�a�,�I��뀉$l��i��a�i2�g�iȃ��m� ЂI�I
́D��-ߣ2����@��?:�{����`K'���>��`n�m1�ԣk�2����.f<F�^�֣o�iɖ�-�H9:� ��Ư�i�^dd́j��iwv�R��x���.^dpe@"��b̤�iҀ�f5@"[:D�V�Wfz:D��Z���i�5@"�] A[�y��zw�@"G���5�yƁ�-1�i��x� @"3�yQ7qe@"���i��񘾰�r�%� �b9Wse@"\^d��%�btef�:D^@"`�^dX��?ue@",fv�
�@�owe@"a��RH�߀:0 �-xe^d��Fdye�-�
۾p�ze@"{eMv�
�ȃ7�����y�6�$�6�&p��������c
'�iQ��/ �(�a|e�-�?�iလm��_}e�a����R?�i��̛ŗ���86��Mv�,f�@"c���8
f�*޴A�E�.��	%�i~e^d��{�e�-�;ny	2+�y�J�Y�8t��{oe�e�y�;nG��h^d�g~B
&i�>�8�e�i>0�b�!��5@"xde����@"G�`y	2h���0�i�f)F2�e8D�F�a�	2��
���%���
ٌ�i�-���{@�k,R� D���?��k�ۥ^8D����v���i�8D�
��W� �-��ެB�aWMv��`~����-��O��`~�<����`~5��8��z��`~E��a �-�Ca�18D�e�i�飈�8|�e�i��8�LE�-m�����.��? ���?��֣�ef
�飆��?�	2��)n
�8��;n(������S���񂼣�f� �Ʒ�e�֧�=ȀՁS���z�8�*�	_��8|z%�y���#�Vۄ�������;n��
)D���٣-��{�?��e8D	��>���%��cҀ��e8D��6��?"7�yd��D�f��Cd$����e8DC����I8D$����'D��-����v���2C�i�'D��	�$8D�e�L�2�%�c"fW��>88D
��l���e�i��Si~���%��i�#�j��>֑`~=��n��m���"���`%�8|g�xk���2{�ӥ�>�efo�@m 	�`~��@m%��w�P��'D>��0��t���t&�8ؔef�
��?;���:���f�O"bCfY'D���P�(���z�8�,�բ�	o�efĚ�,������f��?����>D>��=� ��ۖ�f
� R�&a��h�>z��>D\�?�.���'D�3g�t�&�?��g?�!�?l-n�;{�>?�L"�zwǩYA�{+�,D��i�� Oƛ���΀ �>�?�����Y�́�'D���}���@v�2��H�	f� ��f��{���)�C9摏-���,��eՠ֣o��$۽g��&:Z�o���6�eۚ�	fr�7�ף�g�� "�耏-6۽�	f��4C�_c�
�p�	fa):�8�
	�+��-�́j����f�B9���ރ���v��� �	f+� �?��C<|�T�a��f��$���?����b�`(�fUB�8��8c�=(��襆��>D=� �<|���-:� �Dm�e<|��8V
�{�-�,-n��J�<D�e<|�8��ۣ	���<D�� �xj�]6�{h�ۡe<|��<D;�a�e�<|���-@+�{΂��J�����8=�Τe
 �X�{Ÿ<D�<|+���g����
����a8�<D��f��-*��J�<D��	fv�F���8
�$��&ѧ�- �
`�N�'��y��-y$�`n'Gd���'ւ],ɗ�`o=n��f$��+����<|	d~��<D��NҖGd��<D.��y��l*�]�b�򸅁9�-7�$8�&����۽�`Y^�b�e<|X�y�o�_5���ô8�z��
`SGd��c����	�a��<Dv
?�	�x��$ ��x�&{~�e<|��
�i�<D�́��ԣ(��_�<D�)��xt���� �+����}Ÿ<DC�噁���c�y���Ŀ%$���c����<D���`[�XdS
�x��<D|��0z���	Gd���ao�<D+���[�>��{��n��ܷ��wπ��oh�J���a��"nr�j5�`���a,n~�G5D��xa=n��xo�+ĂF9a�b�X�#�z5z��`m�zAG�<zGd�e�`�
4i�bH4�V
f�{�<p�	%��`9%�+{Am�ۘ�Y(��b�x�e#D��Ad!�x�_
f���[� t��ҁ���#D
kΒۀ:
f������F9i#Dg��,�{���^���ρ� ׀�ܳe
fI"��h����
���m`V
f)��`l��?ź8d�&��c�I"k>#D!��+�[��#
f�#DBX�����iC���?�����x���Z��<k�2���\|~�A#Do��+�
#D����`O�}�DD���Rj!�=I";�x�����棵�{R�飶eDDȀ�	?�"n/?��eDD�}�ުj�e{��+@|~�Qg�c4DDꛪj,#D���?%����ݪjMņ-}��eDD�!I"�GDv���(���T�/�����=|�eDD�?�l7�/y����e#D��z3?��	I"/I����?��h3?��eDD,#D�?��'t&DDl���y�{�壯�D���?��eI"�?�	%{����h�����#+��)"�`���h��R��x��bZ���/��	�����eDD����[�/:�)D��#|�B�ت+��h	����mex� ���a��x�!D,Ed����T�a�meQ	&�e��h����?�3DD�Ed/��)�aʺ����x��{e���h�i~���i�?����VBc*�����i��H���皀i~⁻x]��i���x��
��me	}��i��{e9DD���'�9Β贯�{���i�jX	2B�{ɀi~��/S7�-j��.�{
&n뀚"��b��h���i/Ҧ�o�c�� ��W��e�{N��b�e�{�
`i�$ゲ��%͡�ay�Bv9��L��4�|��S����	!D���{����h���i»�{���h�e�a�e{�W��{j�%��{���i���{ʋ���4Sb��i���{�ʀ?�E�y]�i~�"��y	2�9���`�	2�.��!D��i~�O.�-�e�i����xa��{�G�Y��{B�?k�b����m��{�ݙw��i6���ʋq�
�bO�i~��Bv����G���'|��{��i���{��)���xΌ��"��{���x���i��'D6�Cd��xy����Cdd}�'D���a�Cd_W�Њ'D)��Q��'D��Cd�����e{�N�{S���.����x��{��Cd�{�+�z��'D?���''A��Cd�Cd��'D�Vm�{���xy�Bv�{�€Ъ�?�H�_"���nѧ�x��'Dt�����Bv�w�Sb�x���׀H�c��<��M"��Z�J?�.��{h�'DX3�y�Bv��'D��߄�:���Cds�'DQ����M"��+!ȣ�Zd� h�M"F��xj?�(��球xө��x��f9����-���1�X�o��<���Ԝ�+�#�{�:-DV�\��4Z΀����Cd��'D�e�-�UU�mX���'D6'D�eMm��+��'D�Mm�gb�`*��Cd��'D�*.Mm?�+y�'DU?�ag��b€���e�-��D��'D��'D�e�i��Cd��cv�'D��Zdϖ����e�i{�'D�M"q3MmS5�i��'Dd�}��M"�MmBO�a(�[vm�c��i�Am%|�{��Am����Z�����X�%D&�a�{��MmLJ�i����Ă�/6Sb��Q�����a�1m~T�W�FvfSb�Mm
g2����a��Iv�o,ʐ�Uj�,�	�{B�[v�e*D�e�i�e�{�P�{�&���{��Ӏ�e*D�e�i��-�F�2@�{��ho2�g�i%J�-��i%O�{�e*D,M�h�e*D��H=��gm~�*D�����s�tG�-
�cZ�o�+*D$�������{��?�e�-Z��P�.��i��h�e�{*�-S���g):V�-W�{ބ�h-�iC'�Yh���5��+�e*D�G�i����$)����`?�i�):��{񂮣F$�i.�v���	���{��<����eGd�D�yX�%D�8V���z�Gd��<�����)��[v|��=��b*�
�*DGd����+!% r*D��<�eGd�e*D�{��Gdނ	`'�9�WGd]6�{,�b��%� m�+n��`��{�2�e�b,GFvD)�x��ג�XGdڋx�e*D~������{�Z*D�f�K"k�ݙ� �i#(�<R�,��գ���l�h�eGd��I
ˁV�k��#m��y�e�?�q8��h��݁��,��xu�Y��f�u���ɚ��:���*|��(.$��\�k��?Ԁ�J犆���kOGd��g�d��.�`��bf&�?QWx���{Am�{��
�<���G{Am����eGd5��%�k>f�K"���x�G�k���x�)���@F�o�ݙ�e�x�R� $�<v�kg�y�Gd�:D\�k��ږ��>k����?�_�e�b�X�:���m�}v�&���	�&j�Àx�I"o�x�{�k��y��_�\��	�`X�Î>zA���*�(|���I"l�)n�
`o��+g�{`��C�+Mv����(��-�I"e��df슶.Q	�^d=�k�����0�f�:D5�)n����������-r�?���-f�k�I"Z�?B����kI"�	VQ� f�k�?���<�)n��-:�?�0N�?ly8*�[m����E>.|ۊ��X���&[�8����i��;n%��R��i�if�a�;nJ��d��,[�oڌ�O�af�i37������[�َ.|���yȤ4Gh_vI"����f�`��`�JdP�.D9W���c�k��`f�a	f�{���J���^�=��Nx��?
���`�k�	4S��?h�`�Jd
f�`i/SbI"��a�?H�$��aI"��z;��.D_+�i<��?ȃ��{�j��?��?e4̹�Jd��x
��+�����(�Jd��aX8�ִ��?%�� Z�)nA��aP���%�� u��p�Jd��$)����%�"՞?�Kv5��aȀ�	h:�҇�N������a��i��<^����V��?�
���{��?N��i%ۈTj��i�����`ef� �
�逎 j�!��f�i�V/0�����z�Kv�� ��ib��%I� f�`p��<�۞?$�<�J��d^�����h$�<���p{��{��Jd��ax��i��k1��0��\��?���k�?e���O�Jd?���ʇ� ٿ $��ڋ<��?Ȁ�	*{����?@1|~{�g�<�9��?�˜��<��6���I""�h*��<��{�,D�
�y9��_��Kvh� �%+�<�*��m�zm�@dv�,D%�j��G� Ё�{Y1D���{����{�o��?k{�,��<&�'�>D���7���ɴ�61D��{^��h���i��N⁕e~�`�|~ʱ ����1D�{�f1D.��i��2{�V�%��
-nM.�yɀ	f�jlσff1Dǘ	f�7�M�	f�i*J�>�z1$�<��-1Do�ߣ� 
1D��	f�;-n�fck��ܚ�ͣ��<��x��f%FR�<���y�"|~�?���ͣ����Ύ
`�Vm�	f��M"+�<���,DȲ��H�%*Nd f{�e ����!
�i�M"!f|~����at�f�`�e�a#�f�))��ݬ��-"f1D�Nd���e�Cv#f�a�S1D��	f�%+Nd���M"�#NdVm"v~;�ͣ?��Q�{�9���$f1Dw�ũ
��,��c��)��h�d~Y#G�nM1D��%�ۨ��&�1D�]-n@�aރ;:�a%f
 �1D2[��M"&�	f'��(N
 4{��
�,e�>37
 b9 :��a�5]J�Q�bmۨ��:
 ��a�ͣ�a�Cv<�	f�ao�b'��h�`B���=n,� ��hm h(�M")�%�1D3=n*�M"�U��c�Nd݃ͣ�e%�.�Q��52v��m�Y�+��a7��c�
δp��a,f�a���3��`JH�i��aY��cʇ�'R��--fNd.��J��'��cє�a�
`���`�{�.�c	��a/�cR��Yv��'0��agk���`oȣ"��ac��<1f
 2�c��{���T��at�޴(�Y���a{�~*��%(��3f�{�b1/֣c4�c5��'��0����;�I"��c#C h��{J��'U�{ՀtȀ��:�x�m��� 0J�%���`��í�A{� ��'� =�/�0�6��'���j�E��၂�f*7�cw��ixW�%@��h��{>?�Ȁ�0���H>?�=��a��{�8�8��a�B�hbu�5S��z>?�[��cD��a9�c��,� hA��$���Z�ek�
f� ����:��'o?��{9�c���`,�f*V��'�δu?�a��)%��`;f�$����<f�{v�iه��S��'��$Q���'(���OS�{ψG����'=f�{R�<�ψG�R�%�ہb_�3���x�?�>f�%?f�{8{�e��b���y`7���z#F9YAm9;n�D�W@�x.de��{�
�(4�"nAfDD큽,��%m�Hy*oR{�Bf@"뀸�9{�k��7@"x�"n"��bk?��#�+���b��"n��c֔�
��bt��c���X�?��Z�������׹Dv04@"&��bb�g8>?��H���)�e�3DDC2�{g�F9r�_?`��)��DDo?��#z@de3!�o?�{V$��<��Ł�DD�&��Ɓ[�JX�C� ��\�
�&J��>�
	�.�{g)�fB����zS@"��$�c+D�bJ�8�֔��ǁbEf�a���c����JҁbX6)T� �I��VP3D�!D_�`��x5	2���cm"ہ���C�.�u\��`F�b�@"��xC�`e�c�DD���D�aGf�`N!D��aH�G�If�a�!Dg�x��ۆ�2c@"�c�)!��)��F�g3D�C���aI��aa�ۥDDJ��a�[m��`�#�Kf@"�&��+M"q�6�	@"=e=5��oW8D|��a
�!|m h��`8D\�{~k�G�Lf8D�ڧ�:�ib���A����X�M��a�4b=���&,��a����]��Nf8DO��aȀ�H���Pf�a��%V8DQf8DI�ׁ��a���a��8|�!D=��a�� q���ڋ�go�۬G�V
G�W8D��a������+$�8���}	�#G/�`Rf�i[8D��cSf8D���`����Tf�a8D�����+�f+�`UfUd=qӫ��.�`+�`4�iMf��V�`9��������a��i�w��W�`҄�a�{~+��{���$Xf8DYf�b������>8D<`'3Ud(��Zf8D��cL�۞��a���E��a41�5x�m[f8D6�ۨ���\�`�08D偄Qg�x��8�]fUd-$�b���� ^f8D��Cd��$��N8DZ���)�i��5'$nĆ\��9Ud�,�x_��Ԛ_"5
�i*��$`�_"af ��	��� Cd��V��R��>zb�Bvx��d�X��$n[Q�&cf`*d捙e�8ث�ff$n����a�CdρR��z^��a�X�;Fl��h?$n���`ʣej'��a���`��<�g�Zd5�8ب�߂������$n�	�b3*fd��{�8�vP$n�br���
��b%?DބP=p��oe��<T�<�hf�xifUd���Udj�Cdkf Ѩ lf�b���'m�_"��*N�x�{���<�z��Hdބ���Q������+Ҁ_"Mm~��{{��'���=,�Cd�ۘ�I���Id�D3����8K��a7�nf�ax��-3��@�E�o���5m~	��f����C�����9{���N�$m~�3�ej4������gY=m~b�����{���h_mރvU�����[vo�%D>�&��Ě��ι�/b�ak����က-����%Dt#zk��2|,���
9�;�a��iL�%D˃\�
):��+pf�a��$���
۶�&r	��<|g�ie��ieC݀%D���5��z��%DG��xk[�	�a;�<�Ă�-k�G�k��*qf�aȀՒ�� �<�	�a�*D���{��	i�<D��b~}����������@rf�aH�`݁Xds�b)����״���ϓ�c���a�(m~t�<D��cu�b��Xd�,�`��<DԼ�{G��=n�Xd�b~���;Gd��$����s?�$G�F�۬����Ђ�z�`ev�cw�b���@��i�$��$`� ����v�آ���	4�[vz��[�Xd��Yv��$�+�Xd����C�%DGd���b�i�b�&Ȁ��j��X���{�xf�a��$\7�$�ׯ�ah�$�Gd&��h��S�Ȁꁾ��Nyf�a�C�ҀXd���iz��&���{f�aG�b~|S��<Ds6�a�_�`��	��ȨcҀXd��|�<D}�c�*E�����`��'����b~��<�`��&9לy��i��h��<D�򋷍�c~ff������L�<D�P_�;�Xd�� ����S��c�bi�$/��'Efo2��P_�f�$�ffi�$sY�������u$=nҒ�'SGd=�Ӹ�ff���&i�$'!}8
/u\��&x�ͣf*�b��$}GdI���Dn�0�c��:|̏�EIme�,���r�+\�ۚ�Yv'{��kO�����{>"J���hY�	�Q��c��YvaI"I"��<���'��o*`��Amۄf�+Am��(|eI"��<���i���$I"���&�)n�MvB�]"���heI"�)n�fmen�X�`:D�f���c���[)I"9�cl��.o��J��&�ݪYme��-%�c�j6�;n�me:D{��Y�?:I"
>@d[��{��{�)n��b��i�f:D��F��vQ�����f�����"\d=���|��B�"n(��n�G�>G���)n��b��|�ܣ$Ǵ
�`��ۭ &\dvˢr�<����5��K�`��I"~�Є�`e�4G6\d�f �I"٫�� ��F��bde(�a
deT� ���f 
��t#z3�4�I"�$���Jdg�<���ox�@"���*��ݍf �]v#�i�I"���bI"S��l��h����f &�,��e���iM]v&�,�͚b���n��d �C�ݑ�or>Ă2z��h�Y�h�f]v�?:��;n�f�i�
)5��h�ܣ�J\d���b�f %(��f\da{����)�������%[r�_�f]v�f�h]��+�\d�����%����Z�_or>Y�����  m]v�\d�f\dZ��f ){>(\ Q��.ւ��a@"�\d�����a�f�`��`LJ����a�7��m hꙏݴ��%�%���aT���Q'�h�f�{)��Z����{���h4�f�0�b���a�� _�é��Č�˭%�f�b3��`o����$�`{� ��,��fk㕣�b��a+�>D���f�`v��a�#>|���(��ۧ\Ї���<��h	�b�?�`���~Z���F�b�`�f�b��}�&�?k{�U�b�����f�`9�,D�X�D.��םf�h�N{�9-nզej�f�b�E���f�b5�?�t׽��%���$�,D~��a���a�:i*2[��oJ���f-n���a��%Ȁ�	��p��>̐,D
I���aH�k��ո%�������c8
-n
]i* ��fI���Cd	�fn�'DiB�b8��`������Cd;�z��Cd�f�b�M"‰ߣq��f�b߃	f��{�i*�M"����Cd�C�.�+�Ca��Cd��	f&��+�f�`��Cde�M"q3i*�f�bȀ�.O���<����z	�
ۿ�����a�M"�Cd�
�
���݀'D�۽5
Nd�d~�e��L���&�����}��Cd)���'D����Ғ�;�b��?�)��>�-nb��ר��\�&	���fNdMR)���&�ۅ�1�Ђ����G�"۽�1za��i�91z����/)o���{�Cd�)����V>P�Cd��&X��a�>�'����f�M"�f�'��Cd��fĄ�aD� ��c�)�
�+��Cd��Cd�����)Ӌۉ� ��6m h��@vݵCd�M"n�'D	�f��Cd�'D��	fց��>�cv����Cd��%$d~��a��c��Cd�F��۽�j6g� �Z��nO�`)���Z�aW��ah	�c��-Z�	���a?��`f2hv€��&�摷��`���a�
):��+�Z�R�c�I�`���+j2h;�%UFv��+<4l���{��`%}���+�f}��ң��2�
)�	�ùf�'��a���&k��O}Ђ��7��ao�+7� 7�x'�ߣ�f}����8F�(5�����)���a͇�`>)�ɁH��&.����b~���(��a,�o��,+��&��aÀQm���
t#zoZd	�ck�N��Ԫ�}5����r>U7}I�c����,(�&�@֣�'}�f�`�f
f9\�������a�f):m h�*�`Y��-֣ Gd8���fGd���G�>z�
f�V>��`<2ha
f/GdY��2��>�f%
fc��
��+��aԀ�E��`>�$h��ae�+!��4���a���`��*|(Gd��Jx��4o�,)Fv��G�'Ҁ��0���X��X��"���f�%#7��f
fĂ�b�Gd�)�+i��b��������f)��b���2�:��{X���I)Gd������%��b�X�S()�LDDe���۽
��o�۸��xa�۾�G��b~#z�۽�C�Z2���bY&���-hF���b��w~DD���`����	�@�⣓aDD����b����� nW
f�����DvGd�a1zj��`�DD��)� nȒ��ňݨQ�px�܌#�	ۋb�fDva��"�NGd��ܰ� nRDD�ADD�� n	��	�y�fDD@�ҳ��ң��}S��cҀ}�%GS���,;��cm��X�UB��d�j���)�fDDh-!D�� n�3D;��`@��'B!DI"Z��v� n���)!ٙ���I"%K�a�T!D���P)!�Rme���`�� nl��`��ʢI��DD�	mep��I"o�a�\�d��ܚ
�kDD|��7�����5z���w������
DD�f!D�6�a	�`�I"}��c��I"$!Dv��n� n�3Dm���fDDi$:��f!D��蠁�
���I"�� n]�Z�w��6�{~�}�f!D���(���{~��i~��l�}j� nܶ�xȀ��ґ4So�‪H�)�S��f�i(�48o�&>�!|ú�%m_�I"����Ă!|�����3�!D���)!�5z�,�C!D�D!Dk����x��4c崼F�ah�\��������D�aZ�(
��V4S���!DÀ�"���{�I"�h�!D3�i~Z��%���SI"S���)!��́�?I"��a5��݀�!|}���c�����!|Y)!N�Jd��be��	�`��Jd����t�`����`Ł[m��&�&(�U ���&�֣�f= ��Jd=��㧄KvZ��+�f�`��+���i�	}��́����'�{i�`��L����������<�i�'�bҀ�&��O�[�a�)���L��`��Jd���ބ2�>���f�b�G�����_�hu�JdoG�����f�bXJ摾�6�	�	֣O ٳf�'O��%ϤJdl��z��	}� �c�����&o�|>��KvԀ�����=
�>�b8��7�b:"�M֣��Ц��d��f��ca��i���D�)�cډZdr��D���D��`��f��f�J$n��b��@c�bo��D��cc��ݿ�x��ϋ�x5���'6n���!€O�
5���g�����_�c�QrX��&�`��G��	}ѿ�DZ�wp���	}L�5z���`��L��<z��f(�����D�`/�f��&l��"����	}=%�����ܣo*z����t�a{�k�|>�	�c��Iv��	}��D?m~��fJ��`��+���x>�|>$no��o�9�c�%D�۫����%D�-h����Q�M"��S��M"� ��fm~�f�c��	� C��=%|��M"��E���%D�*zs����w�O�.z%�f��&�π���Z���X���ZNd��DK��
o��D����m��%D(����o�)�%D��h_�o~!��R��`��%D��hȀ����D�fNdo�_6�Md�4Nde�TU\�����m~�ң�Ndk2�Nd[���h���w��Ҁ�`t�h�0��%D_�*A.��~��`Ԡ�b<�&:=����@9��&:���#��Nz����ٺcb���5z�fNd2z���k��b
Nd��b��M"�f
}�
�+�%Do����><�%���{�X�=�Q흗%D��h��%�M"L9X�}��c��bH�,�M"s���f�߂&:#�%D�( �f
}�fNd��M"�f L�&:�X�Y�M"��%D �D
}. ��bNd�
}U�Yv��ht�)S����ań�+
�8K
}o��i��W��a�fNd
}+�&-��Nd˿�at��i�f�h�X���5z8zЁ%`oX�AI �� b@9]f�۽ŋ�c2����۽����a�[��ffc*X��;=f���bX�H�f�{c/u 
�?�f:D�<ze��a����)f���(3�`/#�{��2kr>>�2���{o��Y��Q{�v���4f(,
}8
/uc����o��<j��f��{�e
�<�۾�<z�
}�(|X:D遂b�f (5
}%��o��<3����:|���*�{��`��i)�(|���b��8����i�f�b��:|w�i�"n�V>g �~%��m�(|��C���b�(|JӖ�����h�(|��ik�\����i��"n���<9��b?���֟�iy��bo��aّ�awfՇ�h�6Mv�fC�i(M�w�#��(|� [�b��b�*f�۽S���9���[�h�
�E�r�+C�D�fV�{�۽����{o?�Z�`Ԁ��`(��fۮo>r>Ȁ��6�;n֣,E�`R�(|ψ��X�I�i�(|6��
�j��`k��u�"ng�brF)��C��&	��h����Ł�b{�xo��<N��u�@"���<�[�ݣ��bg�&�V>_�4G����g\d���i�iŁ�b�)���_9W��{k�)i�����)=�ox�crݣ��b��&��d{67�bp�-��cgMv�= ރ;�	\d�m#�2r֣ o�πÀ���c�)nZ���k��H
�&I
Ӹ�@"����)��ݱ������J���	��aH>��
g�a��܅�<u�)nÀ���_�M9��a��;nҡ�oݣ�Ԥ�b��$�5�܀��fv,��{�p���v�q�� {���
��{��&�R	2�5z�)g�a(�$�I�a�]vP7��[��p���Y&�l��Z���{���%� 	%۲� ��h��x�'۟8z�<zy	2Y�� XD��I��`ه�h�,	%ۣ	�%�=n\��{e��hg�,�� ��{,ͩ,9_�]�����6�8^}o��+	�� ���{��3�>�%d�5zӃ<z� ~�C�m�8g	2��ao�,D3�y	2���:�,D�
�,p1z��h� ��,y	2��b�,Dg}�9�a(���,D��8D6>|��� ��&�Bv׉澂�%�?�a���GdUdv��$@��,��,g�8o�πy	2���{��8U�,Dǔ�{�-nc�xӃ��c.�����?��+�,D��{(�+�� ՝�h�Ud�>DU�,D�Ud�۷�f���D��h��,D��Ƅ�h gUd�8�� s� n�+̏��Ђ���{!� "gUd#�,D��bm�8\ې"UdcFmc
�ߧ�{��`;�{'}e&�݀�bc�ig��$��D%gUdIUd&�f���`d�{j�Cd�	f$����g�5z'g�(�,D��`���{=�P�6�bf6�������ȉ��(�f^��{Ȁ選�%)�,DԀ�oA��b���V5Udj
 0��
*�{����{o��c*-nS��dʎ֊n��`�WÀ��+g
 ���|[/,�{-gUdY��j�����o�.g
 Bd~E�ۮ��Ud8[���8d>]fUdݣo��&l��-���&��ƒ�]6�b�}~QҬ��D���g��/��&0g�{$�ܸ�ۦ�{�
�{β�`!��x���{��A�Cdm\
 ��.1�b��D�h��&���DS�����&2��`��f3�Cd%I9��d���{�ۈ�9�"��`��h��{4��`%�{��E�5g�h�:Z�Y�ho�{e6g�7g�|���5��8��&9��&X�y���y0r�	�
 DI9�
��{!���
 :g�hz۩;h�ie��Adm��;g�h:C8z�b��-<�%%8zm)�{e 'I9`�:���uՀs�:��=g
 ��h	���� ��a���s\Y�}>�xw�5zT�偖��I�a���&��{��"R��-�b~�8z\��d���&�ۡ��~7�E�%�{?�磔�+�H�	��9s�_$}��G�F�o��E2��&�5z@�b~��&^�Xd}O�{Agۼ��+��Bg�h���.�{��*ۧ��h]�Xd�):>!�I9���A=nCg
f(@��I5S�{e!��	����,k㮾�&�Dg
f��Xkډ�(
fʞ5z"�f*���i�=�b�8zz�Y�Eg�{Fg (
f{�5zj#�b�]�bλ�h���=ƒ�L�Yv	�{H8zGg�{=���e
fr∛5zV4�`Hg}�� ���h��{��a��	}.7�ԭ�`Ig�b�}��`[���U-g�h�{/R�Ap}��XdV
�b��.Dvf�`\�b~Jg
fk�ۨ�{�bKgDDL�	}M�Xd�5z㕦d	�j]�[��b�DDI�避�{a�5zNgDD����i����Og�i��DvrգP�Xdx�4Z���ODDQgDDm[�*��R,
fRgDDSgDv�
f{��h�Dv,�*������{Tg�i�{m�ې��{�TDD!��`Ug�b�9<���{�&
fVgDv�^�Wg�i�.�bl�C���XgDD]'�Y� ��xe��/DD^��{Zg�i[gDD��`?��b�� d�i:F�b����t�i� \g�`���1�	�	�8[�]��{�i���{�\�� ���Wdv��$�)e��X���J�n���<z���h}��{v������)��`1� ���܂i?3��i%� !DF��z�Ed���`^gDDC�i0�d93D��i~_g�i����F
F��'�i�{~(VB`��zV
?��
C�l
DvA� agDDb"��b��/w��aDD��>��z
�i>�����i@K\d���z~^\de�2Δi~j�B��{k�υ3D6�^��\dw��/�!g�M98��zT��(�hm[�8z.K\de��\� @K\d������hcg\dd��z��<z���$eg\df��/y�g���-ه<������`d��g��{?\d�3D�&�Z\d?�9���g��/�I"o����F"{�1�\dϗ�{��>kr>����)\d:]vWI"o�{~�/hg]v[X\d�&߁�z��F"��ށc�2G]v^C�h&
}v���i��{N�83�hj��z˂���F"9�Z�%����j���,k��z��{~�>`��/n��>�2t e��&l�>m��{ԊF"t�>e�M9n�F"og :���l8	)��{��}9�,Yc\d�t�V���ו�/��a����[�I���=3� j

}�%]v���(��aS\d�&�hp� qg\d�C rg$n���.fJZ�h@"�#�zs�}k��`8�at��{ug�{��؀v�<z]��{�� ��Jd���a΂ tF�{*]vr>wg$n�=ux��aB
�fz&9��aX�`�{��{cD]vY��{y�}���k�� ]�{)�'d��C*{�G]v���(��۹�,�����?����`]vǂ�*�]vS��&z�^�{� #Q]v��УWf��L���b��*O	)����Q�58}F�{ރb��j�}.��d����;�B�}k���X�'� �Hd�$ ����]� ;�z^@m~l��aP��B�y����&�� r��a�����J�Μ)�����Ȁ	��� 9�Hd|��a3��-�"�{k��	 �z���a6nef�Ȁށ���a��Cd1�a�'=�%D��Cdo�z%��B�{@fi����E���Cd��-�'f�磳L�{W��i
�I�_$n% �{Ԁ̢���i���i-$�{+�<�}g�b��a+��i��!gނ���-�b����~��iX��^���
M��[Q{�݀Bvo,��<���IvF�+<�ی�%DS
�h-�Iv�){�Iv�[����&��%D�Z���i
�+{�f�Cd
��7�)v�)Q��h��>~�Ab�����i�e� #�牣x�����j�҇W"�e�
#�z"�a���i������&:�
[�a����i�g�z���BD��
��x��7t�%�B�z���o�za�<z+�<�D��{��Iv���	�d�?��{X�9���脙-}۳��i/�Q��%D.��ʉ�i�g�a�g}~À�x��Cd�΁ܒs���M"��[v:�Cd���_}~%�<z�_"��:ܢ���i*�%]�%���i	��,�M"�� ��>��c���5�)�g�a���x��g�ϋY�g�%��ᾀyԎ��x����h�'#�Ղk~?��"7�a|���9�VW�~+���������e�a��o��>#�hgK^d�?:DXW�@�����<z99:a��-��i���,�<:D�ۊg�i���.}���:D��jq#`�����g}��h�3�i):Dc�c2��偆X��h�]d��b~��h3�c��g�h�<z,F������"^d���dL:�h�i��Bm“�z�g�iܛ�Q��,��b~[>�iX�>�������a~���Ȁށ�:|�g�as4b�g�a�g�h�(|���>��:|7�7
�a.0�$�i�+D��h)�+!��(|m��()�:|��򣇱*�`�&���"��b��:|`�׀�t��i����!Mvj��,�(|�b~y�iv��/�at��,Ӵ<z�:|D�	�gMv������$�iO}A�;n�g:D,�io���O+JX:D�U J��h��;n倛bX�iS}��;n��b/��c}�
�d\��e�z�HЙg�iw�m*�
:D:}
�x�e�MJ��\��ݚg�iD=^d}�;n��b]]�iͷ�<�У�g�iY�(|���m��y�r�<����埇�b����b~%��:|����bj�.g ��7!��A��<�<����y��?���b�3 ˋ(|�4G�ۚ'Mv��+f��&;�F��g ��g�)!�۽�:�
܀b׀���%���b j��J�(�� �;�i���:7��Mv4���
 i ��i��;n��<�� 
�;n��&� ���b�T�>�ָ�[��5�j�i�
�+2΀b� ���<���E �� w�>�g��o�'+C���;nxd�h�����>��b� x 
�u\	�^���b���R�k��	�'��b����g�"�g�{r�R�Ă�$:77�9��au� {��2�շ�&�h�� 5o��&�8��<�£�G �����2�Ҁ�,����7��b
 m
f���&�.�h\FmQ���f~�g E
 �Fm��<��$u���<�:� �#g�	>|��h��b�g����(�>|i�`���+<��o�h�̀b����W҅�߂+�i��Ƙ��h��fP��fȷ�����b!fҀ#g�g�>�,Dй �g�i��b���: !M� 9�>D-�>DT� ��8�\1"�bX�W1����$>|��>D{&>|�C�{���7�il��M�>Dh���`U�,D>f��lL�>Dv�(�gfeJf�����h�g�i��,D�"fof�g�8ތX�g�h��,D{�8�g"��,Dlf~8Efϋ��5o���+�>D9�,D1��h7��xNf����>DNfb�,D�g-n��f��h��	}���R�f3��{۶g>|��T/��#g�f%��i��
v�>D=��{�f��1�y��i8�i_�9��'�8�۝��զ�&��f���i�g�bp6�ij���p/�z>|����F�g�i@�8��	}��h��>D���ݼ�>D�g�8��ݸ��`կf�>D�������h���9[�k���	}]�>DR���,D���hܩ�h9��i&������gf<��oG��m���,D�-n$!gN�Tp�f���@v���{����&“�h���)\f�,���=����{��a��&Ђ�`���׉��d~oԣ��5lm,a� ����Q���{�� A�f΀ �f{��Y�"u��	}��a;�fX�����`G� ���{ʱ�a��2�fÀ"u9�@v���h���a�)"��{���`�4�ڊB��f���`��&!v�@v��޺�i;� I9L��p���?���i*� Z��h+��`߂ ĉ&!X)7�Ad-QI9��&!+��>0��(�Xb���IȀ�f�Ad��>m����d)�	�1)r�<���>UB�Ad��{9�׫�{���-e���Ad��i�� &��%�)�����@v�M" ���&�Ad�ۍ�Ad'�fʐF�v���M"�a@)�gNd���{�	=")�疖��>�wH�Ad����')���j(����u
��>�])���d�����)�Y�� �B",i*���Nd���`���v����� ��f*N�Ad��Xd>)��V���J�z��-��8Q�z)�����&���Rg�?
X�խ>b1
}�i*>��&ʹ�>��Ad���>�g)x�+��Ad�	�Q�
}���.)��>sXb���&ȀÀ��h,!g]�Ad�{�g�h���&��E�ZM�{��iA�h�4�h��Ad���6��&��}��k~̛���ou�h��qӤ*
}j���h��1��)u�Yvm$�hރ:F���R�d������)[�݁)u
�k~�����	f�g۱�k~
�ѻG/)�g�hg-!r(�hF�})��显��`���i�g)�ۘDD��}�3�{��)uk�)u���{9����<RRDD��je�h
�ѻy��&b�$��<=
��gDD�>�f����)u�gDD;��<�Yv�I�{%eDD�g�{��)uz�z,DDV�	f�����{�
}��{P�hY�a�DD�� n��o����gDD�� n��}?D�{Ŝ n9DD�g�{��|LDD9�;�����'��o�� n�FDD���	ƁWBDDL>�{�g
}��iDD�)u���ׇ�{偧N�&��� nm�h�gDD�@�h�"n}� n�gDD�� n�����V DDt!DR!�>�)u4=!D�gEd������i_� n�!D����9x���i~~�����iZ
�{o!D��i�gۊ���gDD�������A� ndDD�[����`� �{�g!D5�i~��i!NEd&�"n�!D�{~ĉ�i�gDD�!De�}�(�{�gDD6�{���9�2��gDDP� nj�M9�>���i�/�O�i~�:�M� neh�Kj�	��S�}G�)ܐ"n�+!�%���"no!D��{~wVp(�}�o�
Fh��i٣�}���o��i��<��W�������P���i
��oj��+.��ױ��>D�>!���c� N��:b�>]��i,!D�Ed��)���{��g��[��g!D��z##\d�":e�G����{7�e���F"7�x�g!D���{�x���+�g!D�gEd#41�Xbw\d±�{�F"��&�\b������8�-ur>�-uD�^���_9���{��E�OS
f���"���{�-u���i9j8���{c��Dh�Y�9�F"����-uO�i~h�i��+!���{4[�`��m*���{�(
ff6�`���ip���+�g-u'���"�ň�h^�J�YI�À73\d/��7o��+���+��a��ȃ��h�iM�>�g-u�\dͳ�a��<5
f��Ih-uh\d
�=f�`l�xӴ	2��r}��+0�$n���{[��{�Q������{����{"$nǐF"�)H�h�:$n���o����Z���{� �h
�F":�x��aa$n��ih�h���{��ݩ��"�F"
��1�)��aQ8h$n�٣���h-u��Hd4$nr��oW��a$n<�Hd'�����HdՀI���-��G�r�Hd�*�hk����9�[��˕S)!d�Ph��h�ih�ho�Hd�+�g���$��	䇫������9�P��i�Zd�Hd	h�`�.gZ&�%|���(R%|��Hd�Ud��+�i%�Hdw������Xb
�j��Hd��a�+U>�i?�$n:�Hd�%|N*$n�dm~$����i�����%D��Cd�%D�>�i�
�;�xӣ%|�%Dȃ�J
hۂ����B���x|�%D�7�i�[v�VB]���xh�i��iR%|h�i΂�ݡ.g�ش
�h�M�i_y9$�hh�i+��a9�;ǬHdw5�i�%|��%D\�{:X�'�_"���a	�%D��{�j.�Hd�%D�[v��{*]�h	 h�{��"X�%�٨�a(�%D��Hd�%D��ZdO��az����٫��	)'!��j)�%Dg%|���a�_"h�iȀ�	�%D�%|��i��<� �%Dh�{��i��x	�b��%|ʵ������m~h�i?$�k��fF$Ad�	�i��{�/ĉ&:����ȃS��"�i�%D�`*h}~o�ϣw��;�Q���<J����_��%D�K"h ��%D�	)3*K"8�Cdʺ�VB):�����
	)u�%D�'Db��|ǖŮk[��`*��o		)h�{v�<��U���{�5`*e��t��,�Xb�5�{��%h�-e`xt��,R��h,@9>��AՏ���,;�ţ���	��3�{Ɓ�X�{ �h���T � `*	�B9�x�{٣`x�ێ
Ŷ!h Ɛ'�, �6�z'�a�֒�	:D"h�i����=#h�a{�h�`�脀]dtXb��aݏ�0�
����i��+�`�#g���,$h�z��ax�Kf%h�a��:|��`o��:D&h�zϒ(|E�b~��fP��Fh�K"���h�(|��q�'�*|*�Bm�O�z�L�a���(|]	)��a`x�� ��=�ϣif(�:|Tt\
	)���م`�z]	)�
tQ֋X5����()�:|��a*h�h��(|�Nd��j���+�(|������x�=f~�`Àx��(|�:|�b~(�ف�e:�x���Y���Y9Xb�aw��� ��9�e�����hv����Df��)n���?,h:D-h�ap
7����À��F�z@�����b���D�a�����h��=��i<�+.h�h/h�a�	����j=��h���0h�z�a@��+��1��i�����:�(|�����i��m�b~�)nNi�j����h
��iǑC�R�(|�����ɐXd�3�O��T�4Z��
�&�(|��Հ��tf���…4G;�j�a�f��)nI�߁��2hf��E��f(���{�g�x��n�3hf€ �+��x�V���'f��&!��aO��d��`���k-��4� 5��`��fց���,���`����#��b6� ��a7�;n.U;�7�>� ���`��v٣�	�\�u�8� ��`�4Z9���7+:;I"΂ ��>��)n6����v�&!:��+��_�>:��h���a��i7��`b� ��N�g��h��t���`���iw�q�
�&���h�� \�&��'�5�>��<?���;� d�&!�&��^<� ��d�h@
�o
H'�	J��9�2�߁�agm,Ё�����9�
)��'����
�S:[�\���)Z��$�>|=h\d>� 56uB�<����'��a� j�-?��a�Y\d� O)]��`�� D�
)�EC���<�6u	�x���@� w���m��b'��0���'҇%���=L�,Dk�í�����>N�,D���`{{��>|2�,DA� ���H�L�>��ȃIB�,D:�>*�z� �w���<�,D�Y>|56u���C�>D70��-#����>D����X�D�.D�-n��+�
)�.D��,D��>D�]v�)�E-nDz,D3����v��,DȀ߁�>D΁&����D����h�����OL\d{���xEh�h�,D��j�b>v�
)F�,D<�����v�
)-n��0�)l�j��
)z�����h���	G��iH�Ym5W-ny�h���i��,D���hR�	f�P\d.�9�Ih.|\d �x�-n�	��$�j��hI]d~�c8���{J��a��� �)�7d~���V;{��h��y�h@��{�{�%)K�,DL�h���$��{�F�a����3'M�,D�&	�(,ǡ,D�T2z�a9��aN�,D)�a$���s ���ޠId~�-n�}ۢ˸Y�`��.-ny-n��n{��iO�h��"�U��b��$�-n4d~�b>s'[�aQ��0j�o�W-n<J�`�"�h� �}Ph�aOu�1�{� �a�h��	ld~,:f,Qh�h�����Ym�d~ZG#D��ao�<����&�
�h
�X&��h�!#DXy�ҡ��B��i3X�5�Ym��h�Q�`#D���,���hc?#DR��aҀ}�I9�!#D��zShI9(��逥�)j6#DTh�b�Z��� �1D��$��C��	��Ɓ��Uh�az�M",�o���8��F=��`x(���*�{�v+[d~����#D�voo�b���V��&f#D���'���'�vWh�a��#|��Z���'��Ad���>τ4弁�>e��&�����#|Q�W-�vX�}�ˀ$��f���Y��&�X#DG��h	�W����&���H�)�#|� i*�������2Zh#DR��'���P�M"8
#D�o�5i*�Xb�#D"�f*�2�$��v�)Łf*2{���%!��dX�o#D�)��(?#D9�����z��#D�}~��>o�i*��n���@�,��)H��ҡ��[�)/:}~9��Xb\�a)�)��M"�-�QoH�a���\�)]h}~�)!��~8�#|��'��⁔V^�#|��ak�)_h���}~��f�#D��;ue���`h�ax��+�-�aj�4T)!J��'-7)!k����zk:�x��;�a��:h�G��	2�ӣ�(�b�G�a�f(�x�3}~�"Y�b	�a��Ӑ;uƁVW%�x��/�F9�	��d})tXbb�fchDDwE�a��)��Ӂ)dhDD�����i�>DD(���[6��}(�)أ'3}~r�f�{�,�f*���e�;u���`!�)fh)5��:DD�Z�3�&W�vUDDgh}���.W�v��Y�j��a���hhDD��)k��]m�4}~�ϣ�蹀�$��&�Ԓ����^أ9)!Ɓ{�)�����++
�jih}�(��r�a��b~RcDDf6)J%�ajhDD@�iւG���a]])��7.�����a/F)�;ukhDD�/��Áj.g�G�ˁ�je=�Xk��3Wd3��t
�ց��_���;�x�<�fC�=�&D3D�;ul�{~��+w���mh�j����Ȁ��i~m&����]}��<�J�e�/nhDD��'D73D���fg�9�}�WdDDc��{��`)Vvo�'Dph)q�b�����aڋ�`�d�&���)��a�3D"�M9�j�l��%�������)�	FaP�'D���`��'D��b~�
����{�'D�&�f
�|
��r��a��&s�%��b~Њ�`!��AW�k,v����'D���aj��?j�k,�'DӐ�`o�i~��}z�8���`���{۠}���`���>���bth3D\�xӎ�>u�b���`���5C�}�'v�E�S�8�wh'|�"!Ă�>��'D۲�&���Go&x�'D�������*#�>y�'Dz�b�'D �i~�'D2��&���ż���w�'D��ݣr������a���Ă�&��{�'D|��a��F"Ԃ�%\��a		)��`}�}�\m`x~��`"�M9��'D�
�x�����E�φ���'Dh�>3��bf��8��mx�E�䅀b4��`�`1���d�ހd��	b�<����Q�r�:1�j{b>΂����		Y"�EC����B��2y�I�E�7�>�z��d���€�皀�D攀b���&�6nه��6n$Ё�-!�)*Do���a%�C����d�h�a$>�€b�h8D�"*DX��>��&զ�bͦ>���&́X��5*Do����F�$uX��b���a�]*DΎ@mЮ�b�*D�Zd���J��=6n*D_�*|X�Zd́ a���]�Zd�8Du(�a�/��'9���k�e��]*D�$u=W�ˆHd���@m��Zd(��a�a���d�̣|�Hd8D��Zd��a�h��8/��aȀ���@m��*|��Hd݀@mb�Zd��+%��lj�Zd/��b��ߊ�*|k��jp������Zd"�be������w�w�!D�*D�Zde/!D~����
2em~������h��`�/u�͢�$uw�a�6n�Zd�h
	�`�h*D"� �,m~��8���j
N��`߂Zd$T�aWV*Dg�d�6n�h*D��L��`"�}�	�a?��1�+n���UB�a�&�hv�Iv������i��[v���h�8���

�G���Zd0����������(�j���i���i�@{#��j��Zd��[vɎbeԂ8���2��PҶ�C�T�*|��@m�Zd��*D�@d��Hd�;m~�#U��S�8�a��d��[vv�J����!D��*�tA�ց}����c��&!p��&���_��Հ�� ΁=�&b�����j�!D�Gm~k�������a!Dt��Ђ��t!D+0�7Ć[v�h�a'��j������E�#��%��{�^d"�_��+n�i*���Փh�&��}��&Tӣ׀u�G�[v+��$���Ђ�����Ci*�9:�&߁�&D�G��9:kJ�4�&'��j�iЁ�n� ti*��%� �D�h�&z�8�{b>#�uIg�djrx�
�����7�ᅊ-:��{)B7�ੁ��(�&1�ી�	�=''A)�ՠ$n�&�hDm�#�����D�&4��-�-:k��)���������h:D��+��4�h:D`x	�-:�h:D�h�a�h^d� =�@99:D�M�&�^dO����h:D�h^d�hDm�h@9�Dm��<�L�]:D�h.|�^dՏ_Dm�h�a����偔��.Dه�cz�ݡh@9e�/�Dm��:|�@9�A:DN.|���b�4^d*�;"#:D��+&�C��:|�hDm[�.Dl�&�@9�&Mv^aDmA#��:|Ti*�<.|u(=n�h�ah��-:��.D�֢�#=n��x�:D��.D"@:DF��d�=n��:|�h�j��bO�&���Mv(�:|����E�}���`��%1m�8��:|��-:�����)r
Mv��:|��j؍;n��%�h&Mv8Dmd^dJC}m���aG:D�h^d��t�!*��*#C�i��h��c(���a9���/`:D���i����h@9�h:D�h^d{Dm��i�:D�h��a��a�����J�ւ@9P�:|^��h��L�V�;n���"�mx���F֢�8���hx��ag�%D�.D:�:|h&ɗ%D��.D���a��c2O�4Zp�(0�:|��`~߁L����ނ�`~���<>�L��ˁ&���b�u�����
�d���	\�������@9�=n��}��ه�݈�`~Q��g)��� 
�$���G����Mv��=7 �`~���+[Y��	�>s[����a��;n�8�/g���/�h1D��)u�h�~�1D����f��8��F9'B�~o���,�F9�h1D�8g&�����O.�Bm��;n�F9;���B@1D�Am���a��%D
B1D���i�F9ه�aU�F9|��F����h1Dl��h���n��jR�����F96��8���θ�F9�U1D�����>��j�h1D���&6��8�
�Zt���L����Fmh! �"�dt���s�)�hF9����&�R�d��`~�)�h1D?	D��7\����)���e#����N>|J��[����)�G�l 耣�D�d�$>|��o��)k�~8
1D)
>|PV@"�hf*��_�é�1D��d�fC�>��ܸ�¦>�1D�,�أ�>Dg�>Dt�F9���8�h1Dc�>DD"\z������Gf��>D��>�>D��G>|=Ռ�1Dc�>D���ݧ5Zz1D��>DY�(|g� 瀹8���7-uv��%�5G�)�hck��h>|΁ʣ	�O�cr?n���+�>D�>|��ekoأ� Ɓ>D��>D�T�a�O"τ\�)S-u݃��frxل�輁��Ղ)n��)
��&�>D��)�d�)�d�a�?>|��=:m���h>|jrx$_�(�a�h>|��a���8ȃz1>�>Dy�f{�E��$ck�xj���h�c���a�h�a�!>|���,��,�h֢��->|��)n����h� !wE�a���>��Im��׼Im��Fu=���>Dt�Im�Y7�b�#��Im.�)n�����>D���+�W���>��>D��Im��b�;́>Dx����Im����h�&	)ɧ,��adž@v���
֢e�"up���d)��aQ�&�!)"��a����D�&H�&��~h�8����)��s��z�8ؘ��)����x�,Ɓw'9�޿S���h�'9����T��bd�D��Q6�<�b��,��Im�d�&R�����Im��,$��h)��,ւ@v%���.�Im����'Z��أ���,�h�&9���Im�@vu�
)��aM5��瀧,ѧImk�U /�
)���m�����a{�����=�E��Cd�)�Z����h���=�E�5��?5�{b�(�G��Cd��Ad�K9�4����mx�hd~�_"��Ad,%)�� �h)��&�����@v��&��Ad��&9T�bm�Cd�>���'�����CdX���mx`x�
�d���>�E��"u��Ad�h�&D�uj��Ad�&�w3�&��n,5}����%���fأ�	Z�w����e|_�}�AdY�
)r4}��&�}b�&�����&_��b��I
Ւ�	&*����Ad,�f*�Ad����E��h�%Łf*��Vʓ_��CdQ�}�4Z�ڏ����mx�R-n��f*�a'D��f*��>���������Ad�hoe��:'׽f*Mmg�,D�ˁ�Ad��ˣb�Y�(oe��a�h�h֪,D��AdD�Ad��f*��Cd�	���Ad6���Łf*�hMm€�+��	f��h���ด��d-n�>I9�&��f*�1	}k�	f�h}�Mm��f*P��c$���h��X�:vW�k,����Mm��}����j-n���T!C��RҰJMm5�	fzMm�h-n@}�BMm�%A�h�y*�������hJ����n�6DD��!�ˁ�B"G?DD��aq�*�ރ����f*�hDv��n�	f�hDD�h8D3�	f����hDD�h8D��}�C�yI8D:�a9�	���?8D��ڢ�hDD����@m;�b~�h8D�7���۷.}~ADv�:g�DD�Mm��6I9\ZDD='8Dl
 ń��hDD�8D�Mm�hoe&Dv�A��Mm��8��hDD��@mY�8��#Mm�SDD��!��a݇��8WDDR�8|�9�h�aWA!Ds�@mj���NU#�Qm�h�GA�l#�*DD�Ed���s�	f�hDDu�a��8�EdVEd��@m ��)?b8D��`�!Dk{�g!D8DZ���ێ��&�h8D�hEd�O!D�8D�hDDv�!�:�{~,DD#!D�(8D�A�!Ed�? J�W= v��<�h8D< �hEd�DDg"8De*�a�����hDD���a�
��hDD+Ed��a�_�ajDD�����V�{~��%����5�Y
�}8c�a�Ed	��
�{>B[���H�*Ed̝8|݀�%9��E�b~{�@����^S�8ؑ�:�{~��`�T�7��&�@m]�y��&��-ʇ8D���Dܒ�l���/�IEd��	P�8� ��{�7Ed���֒�!�h!D<+A��GEd�h�a�h c=u��j���&�)iEdo=
�$Q�!��` ��`�cEd��i�$ w���AEdeP�� g��&
�a*#�>��Dv��M�a�{~�%Y�O
 ���
YϜE�T�E�Fm*{���{~a�~iI"�����i~�^��4�o�������i
f����{~I���(��)!��}� 
f��}�*8����[�������b��h��{�
)!#I"�8����D�:�E�W�	����i�}*�t��zK�	n")!��M9��AQ�2��d�	}�3)![�ه�cŁ�<���j#Dm��c������ܠ	}������~Ӵ^�<| # Ё��(j8�ˣ\�dժ^�ւ���M9s<|8�8�*Dm�bo�d~C��^�ER<|�bDm<��1)|�<|��&ؠڢ����|jh��b2<|~��ciDm�8��	}�ۘ�Hd��<D�.D�x{�Hd"�Zd	i<|��c�Hd���
�.Dt�<D_Ԥ	�F�b9�7����+���Z�2i
fi<|
�<D���~��Zd{>�<DS}�X<|g�	}M)!Q%}�H����X2��jHK=n�Hd
��� m~��������݀Zd�<D�%����Hd����f$<|2�E�.m~�}X!%|�����HdA��Hd(�H��b}z�<D�O�<݃ˣ�����M9		)i)5�2wS%|��z�{�6[�i}������a�Hd����� ~VB�,<|��%D��a���z�Dm�	<|��<Dif�7V?9�� �`f�@92��Q����f�r���z�Hd�� ]f��Zd-�<D�� v�J�Hdi�bq� ܟ�z�D�<D� �
�܍HdQ�C�<Di�b�"f�Hdkf����o&� ����(�b�2�̴�\�b�f��a�%|��"n�#}�	)�D � �;�b!i3D)� �f*����z�[f"i},
�9���א���O}�찄�f��8~��hx6�Q�2.%|	��h�X��
}@�b�	)#i�&)�D$� �)�b��F9�^���!�� ��*'��D�Iv��>/#DDK"c�ˣ�Iv�{��f%� =�b����6�|jiBf� z��؅�a��~���a���h�a�b� ���'�F9&i�&�����1Y�D.� �9����b��(�Dj� �#DX�$6K"��b$4�b'i
}(�M"b�D\������)i�b�X��)*��Ȁܒ*i�b4f��b�M9+�?:\	}{xj�|j	\Nd��k B�"n��<��c,ifWf��6ONd��&:�7����a�`8��������`�Vmjxj-i�`��$��c3��d��hЁ��.i�cR
}�%/iVm��&�^dso�������4@���:�F9ބ��遂b=�T��f��T�;�����i(�&�Vm���(-D�{�,3^d遂b����|j��X�#}~T�ɢ�Mv�K"u%Mv���b�i���b|zK*
�`k�Y�ތX�AMv+(X�	�z0i�`s]Mv
��fz��b�zgX�z�M"�ǂb;�i#m� B�z��fL/f��?:{�� ��)n����Im���y9��x���wEMvƁ�b5��yo��oHMv����ރ��m��a�%f�{�@"��a��;u1i}~�z�x8w����f���0���{��}~2�!g;=�`���,@"���h�a�3@".MvtJ����:��a3�Im�M��4i�aD�ʕVj���߇�j�������cȀ����b�㯩��t������b�!g;�Imd�!gT���aρ9 
��x����� ���b��:�����4G�&Mve��a7Mvj_���/��{	�摈�a��E��z҇��Om��%�Mv���g��z��y,Q	��{��[�
���
_�b�jJ�_��H��a2����>�
���a�g����h'��a��/@�;u2������ab�U]��\c� �jV��a�|M���m��q�iC��\�d�UB}�Iml��?��=�<�҇'�	�Imr�8ի	�y��]��%-�
)�f��T 
����Cd	��[Z�Cd5�!g�,��fo���l3��6�Cd��b�'D%�!g[9��ȃ��r��k��Cdo�c��b'�o����Cd� �<g]�Cd)��� �`~7�Cd�����,���b8i}�{���m*8�,m�CdԂ|���B�N�t}9� ��ׇ�,���-�|���8�:i����{;�W��*��ݓ��0<�Cd=� �fP>� =���Ă=!|�,�Oml����� +}Ё8�)�,D�>*aw{xj�{��� ?i:DȀ˸ݴ ���h�C:D@� H:D���f��&A� !�CdB� Td2� � �,����:|j� BiMmC�|j��>R}D�Cd,�<�Y����G� ��<��c̘Cd���� \�ԣEi�>.Mm9� ��0F�Cd��&��b��>�� �{>h�>�D):���b��;nq'�>��<�tg�-|�t���?��Mm�	�%�� �:D�I9�|�Ɓ���� G�	fi�<_� ;�8�*�V�H�	f�G*�ᒢ�9�R������;nY�O):�1'�	fI�1'Jid~��IŁ[m���Ã;n�Fvd���K�	f�\d~8�1'�N壟��j�<d�bZ�!��1'��[m8� ,�<5�;n��L+"FvE��b�3�� md~��P� L�1'���W�>M� f)N�[m~�����zg������ ��︍�1'��<� i�<��*|���w�3�ם��i�<v�@m�}e9���1'�� Oi}e)��~�1'c��PIMm+��x�����F�fT����	f�):Pif���z|�{)��h_+
 �!Ud*�@m�I9t�F�GdH%gԂ�ݽ�&�Q�1'��bl�d��1'�bUd�#�R�	fo��xo�ԣ[�	f����1':�[m��SC��zg��>w�+����oS�1'̈yH�Ud[��x)��*���9�@F�T�1'UiX��f��zV�1'>�-�D�í>��1'��zvA+�Fvj��h�X�Df�~��cW��h�d~� 	 !�2j��h�⁎ ���>��(X��h߂O9���h�/摧�<ց #��h1�b�C
 �|j'��zl��>Y�]95���Z��h��|� F��>y� D솺xo�Ƣ5��z[��hȀ��� D��b_�C�����H磦\�f.x8�8D6�|j��D\��z$�!g�x8]��h�UdՀPҷ�ɣ��a���]��h�%ݣ^��Dw��B�>DI��hB��D�Ud�BGd�	�b_��h`�>D��(gUI�D��Dai�b��(gbi�b�
fx8ci�b��'�	
f��(gk��D}[��D��f��|�8�s���>)�c��D��"uI
��Qz1d��DI"D�݀�> ��h�)!���D�_m|�!gI"���j���D�5'�R �+�!ge�jh��h�b�_mf��hgi�{|�b�	}���hk?�t�dI"����hI
f)��D�|��c�b[�!g�	}�=�Y��(g�VB9{��dބ��I"j�	}�C���f_ g!�	g���>H��D�������󸾄�Dˆ>DhiVvg)!b��D�����iix8�>D{��x�	}q��DX��b
�>�X)��D���:�0'_��fj��Do?��>D���`�N�z���Dm���b�#g�����P"
f
f�Vv��fo=ki�z�)l�XdI"��(g*���Vvl��xQ�۹�da�Jdmi�z��;I"��>ni�z����	)!�Jd�Vv+�iI"��&o��.i�Jd�)!�i�� �
)!/�?=]m���5'�$�b��?�R9R�?À|�)��:piWd��׼b�q�Jd��.����6�|j����,8�Ń��ri�j���	}���X3D�Vv��.�7Vv	�Yvs�JdZ�t�{~�DmB� �i~X)YVv����Q��ui�zk�-�>+��a(�B�(��+vi�z�Vv�5�Wd�أ5�Ad�Kvk�XdJg�w�Jd��z�>��h�Yv�%��
�&�Ŝ0�z�
׀A�z����x�)x�Ad̤{~�{�O�j��db�Ad%�Jd[�t	�D��<�ody��>��k� �����h��4Z��.@�y*{�P�D$e�>��|��}�{~ݙ}���<���J�F�t�Űk��Wd��,�,��}́ 3D�T�D��}��.=���
�h��,*Wd��}/�8'���Wda2y�}�{~��j��^�o�е�{~.����{~ΟKv��8'j�/%$
}X)��51D�hЕ}=����y�d�
}N�8'
�+un�O��{~�8'�����N���� �8'l 2J���Sc���G ��>+�Add��߄F9zi f�=>L
}��(ش��] %�� ��_���>یM"3�8'H�z�M"u2 z�8'b'g�(�}�9!Y�}�^�{i�D��ףV�U
}�&�Dv�Y��AVmTX�&a�bjDD)�|�}6���DD:�}��`��I��dY�背8'����%#g��)���}i�&~i�&)����}kDDiNd�de.��f�i�&�iDDI�&�iNd:�Z���8'pNd<1Dv~����<Vm�i�V����i�|��iDD��8'��Zd.���ԠZdg���ЁQ"�<Y"J��`��X���Zd�Scg�{�Zdc$[��e�γZdde)X��f
}�im~l�<ܚ�R+a�~��Zd�ىiNd�Ve�i �m~v��Jj��E=X�j]v�Sz�^�[S	2�����߾v��Zd)�}ךM"�i)!!"�&�`��M"����^�l�}΀$u�iNde��n$��L�M"Nd�im~ւM"�	)!+!DZ��`�Nd�	2O�ʹ�ide��Zd�REd�()!*��P)�����&U)!�i�a2<DD���dl�|��ƪ�"\d>�&"��z�DDe��zx��,�iNdr��a���,�	)!��ŔiDD��R9���z�&��@�ZdI�:��Im:�&9���h�~x�Zd��>F��襧+�X����|���S��Zd��� ����z���il�(g9���Zd��Zdg<'���z��,��+=ۧ,&��,۳<'�	2�)!��|��i!Ds����)2�otӢj�W8�^��m~��:3<'��|�����!ݣ"�^��<'DEd%�f1�)!�<'�|�=�F"��*���^���fr!D�}��[v��,#���)!��:#g��,�)!{X����z��EE`��,�����ʹ���j�K<'���z�)D��,X|Dj��i
�)D+�f1��,��d"��z����τJ��-	�	���i��U ��D�8|��D����	����D�����i�<'5��%J�ރV^���jH��Da��.��l���D����`^#<'��_"�<'��b�%���'A#��.��yW�?!�<'��E�}�ҩjXW� ^d&��=�
��o�D�d<'�P^d��_")�c9�Bv��_"k�8؍��j½�DR��^d�i^d�i@9� ⁔C#��D�}f �6$n�i@9��Ʒ�]"��6̺_^dQW1$n9W9�_}=�f��@9d�)@9r�A57^d��h o������6:D2^d�$nބ71��fg >^do}�?����hX�5�g�:DI�k�Hd�@9�uI%��$��D� JX^dQ��D9�P9�#
�h�h^dQ��D��Hdʵ8ا����6=�ܛY�		)� ���c�����D��jt�?9��Q��D倅ʷ�%�7�%1Ȁ��%f3��j�L+(��i��(�iĂieW}�iMm��U2��9�?^dX�zk�����x��-��O�Ivӏ��	���G*����*���A�c���Ǥ�Iv� �i@9��%D^d�(`� [}뀺*��z�i@9*�;n�&`=O�i@99{T�F2�A� ���b@9_�f��h����v'�zd�	�ie��z+��ǩ��*g���}( j�H#�f���i�z{�d����6�ie`�zd��^
�: ���+�i�z��`~8���QPW �Mm��-� !8D�Fa�`~뀺*:�⣹�&:�-D$�8��Jc�I D#����KI�z������`~��z�	���8ثif�Y ��Q�>�۩�Bm�zՂ`~B+ ���m�|݀[m�F�oD�D֎ievcUd��`~�%DP5 �a�DL�D�SUdW �Df�i�D����p�`~͓4�[m��`~݁��������Dp�Y �7 ]��-7�>����ǒ㴽��ӽƮ����*
�fH�`~+���t��$�iUd�)A5��ݯi�Do�ܰiUd��8J�f��Jch��i�ze�f"@ {�#'�Z��`~�D��邁#'g !�������`~�#'bf ʉ*g��Q3�����{���&�g�c��{E մx����@� ړ#'&1�b<�p�⣓�BmJ�#'�iUd�i�b��C��-fH�bO1Udm<�b�Tf��D�{��D�ifo��f��>DfUd.�f}H�|�4Ud+���/���`;#g�Dgf������ �$Ud�i�b���z�� ��>��Dv��z�	Ud��z��>DS:�|�/�y�i�&R]me��偔��#� 3��z��yQG�>�i �me?p�K� ��#'X:*X:���#'Z�����Ϥ�zӜ��o��݅�#'�3�bȀ�ϻ�zhme�ic8�>D�������3�)n�� ��>)�}��#'8
���ime�f,E$u�#'Ome}���%8�b.���{�#'��|�� ��b�me��#']�b���)n����;����i��@�)nȨfJ��><Z�Ca�C��)nm�|�id~���i�bli1U
d~�f�6���z1�{����;d~棧�f�Jc	�)nnFm�id~Ҁ��&�	}�	�F3PFm��݀�	^� 2e :�7d~�>D�Dm7�&-,Fm��z�Ĵ���Cv~Z�׉���X�<�	��z�	me ��ǥ�����VP� ���������o����Xdg����)gFm����B��ǿ��z�8me�� �iMv8��>g���A��zA�Xd�meq�Xd��|����"x���	}S��>b�`er�+UB=nM� +���ϋ��L��>�
&�!�Ao=��Xd���>΀�����z�f��h.�d���z��zg� 9�;�Jd>
ۆ�	}ѧXd���ՀN�	�yHW�Xd����g� �id~0�J���za��zS���D��+8,Z�T����񩁐#TTFm���+��>=�fk��z��h��g :�id~����h5Ԁ@��z
�h�Xd9��gf�/���>�ۿ>_��z�h���z���_��W������z��߈��z��h�O$Z� M��d�zv�ִl������z�(�/B� ��Xd�� �� ���z�΁.�zL��>�B":�&��+)"g���-��zJ!�z�,D�5%� ���z��?g��x�� h�>�]mD�,D"����&��:�i�h��g��,D��z{��f���b�hƁ��h�bS��܄�QҀ���]�h	���.[
}�Э>r�ִ]�d�#Dd�YX���Q�,D��P0�z����F9)�����b�#Di*���&I9:�&I9:-n��Ad�#D�,Di*�
}�-#D�iI9���z���j���&�i-n�#D,
����%�"��Q�W�O$�&I9��#|+�����&�ࣿ� 	��+�z��d��,Dm�|��b��Ά�86�m1H������b���Ԋ#|ܐ?:���b.��&�OI9��%#D��	f��4dem)\d����KI9��&�i\d���b���&a:���'I9��dA�"n���#|��"n䃋b��b��)u���i\dJ#D'��b�����cه���b�#|�i#D�\de�f�� n<YDvT�)u� n�8#D(+
}e DDc����b�� n�� n>#D݃�A}���ʉ n����z�$:o#DZUDD����l�����Ye:DD%ʋb�I9�
#D>����#|J���i\d��{=�p��"�{�Edv��q�$:h�� nh�܉6I9����H�#|t�d�A!D�� n��;B!D��d9�o��b�i!D�i]v+�|m�!D�Eœ� n�Ed��m1X��b��{~�Ed 
��s!D�i\d��z�S]vy\dh���TEd?����i\d�e��e=u�i�zbX!D~DD2DDڴ7EdF&�DDo!DZ��@���Ed�i!D���(�iDD�� n�'����YJ_&n�� nqO&n�i�z��{~� n%�}��ol���t�d�)!Ă)��׳��iւ!|�
�o�i��Ȥf�]vӑ{~���4�� ne��a����i~�i)ÄI-��}����[�Jc�	&n3�$:�i)��i=�>���:��Y	'!Dk)!�����iEd�ov=!D����i]v�a�z�be���w�J�=u���/$�z�]v�����z��u�)�)!�)=u�$uU@�z�i!D��`c� �-)��.h��&�)�	2���*��v�|�ÄI-�i�j���8�Km뀺*R�E��ij8r
f96{��"V��d��4���{ÄI-B���	��d�(��p�Km�������P�|N�3��DԂ�����[���:����i
f�-uҀ��
&n��	����Y9�+���Cd��E�pc`�Ea�Y9��Jc�m*��M9-u�-)b�M9ƴ'Dr&n��b�ݜ�>��M9��'D�M9�'A)��*\�o�i�j��8($n`�d�}��M9n$n��o}�M9= )��'D
}���{�Bv1}�[$n��'D�Cd��-�oR
}���ae�h��M9��'DƘ���'DU�M9-5@9L�:ؘ�'D��8���{=�����8	�o��F�u�+m���V�o��vR	%$n�M9/�Ƙd]}��o���	��b�}����i�b�i}��o뀺*��'D�4Z~}^�he�>@;}uI�i@9�>��'D����o�&��'D�2m~��j����[������ۉ/m~��M9�'D�m~�"�Q�Om�����'D	��jF%|��_"j�>����1�'D�M9⁔V^��6��o`��n�M9"۴�o�=�'D9���M9�[-�~p�it�>�%D�M9"۽�%D�^}Q����F�i:�%D^a�ze�>��9VB�%|9�R�5�aj}�ҳ	j�h
�%D�0�z�Om��>i�[m!�%DDh���;�z>���:�$�z��j�@9j�z�*D��_����h|;@9~d�i
�>�%Dj*D��zm�%Dj��9��q\���Z*D���"۴9�ٵ�%Dd��nj*D���$.<6*Dnm~a�txܒF2����j*D�m~��*|��Y�b~t�����z�����+!(���j*Dj�-�*|Ԃ:gj�>��{D�Wm~ރ�p�����x��z�a8�0*DsŹ�L���)�zV���z��x�*D?��j�zY�i�):�$�;�����z�g��J�I
���%D��������_Gdz��x*�����Dz�%DS��x9�ԛ=�fO�%D���։�����ܒG�m�8a*D��[m��+n��8�%D�%�
*D�Ʈ��)���_�L��*D
�+n@d**D�+����Ԃ�x>��k��I��a���e���&:�+n�fj*D�L�k�{3c�*D�+nS:�|��y���Y��>F��j*D��	����6n�J�������.�$u7���%J�������*|oE�|�/$u�8��l�g���8���x��%���4f9�t���R9_���q��L�x)�����ȒX�����;��ܷ��x6�o�yx���ܜ��|��&��oz�d*���bIɺx����(|\����f,��b�Gd �(|��L��4S:���s�E�m���l,���!�Zd�����b�	�+�9�&\���oр"�+ne��bd���)�L�)�D9��cv ��"�Zd��(|A�D9�xl����włb�Zd�8�o��E���&u����+n#���K" �&o�o+(�&�D9��(|Ԁ�_]�(|��g��j$�b،)nO�	}c
me��&��}��	}��t�����#��-���%�[v�����	}
����E��h��:���{�8&j�&��}rI"�	}�#�/F�& �	}�Ad�F����|Fm�}x�[v�LR9p*�i��)nmKmPR9�B.|$���R'�?!�	}��}(jFm��zV�{e��z���)j.|Fm��&�o�>�Fm����k*�D9�4Fm�}$�ݍ�z��"Fm�(�z�@9܀��� �.|+�.D\��t1Fm,�)nd���=�����-jFmK�.D.j@9�.|/j�zXq8�?D�I�&�V0jFm�!��=�.D�me��.Dh�ߣo�)n��Q
���.Dr<�z�	�1j�z2�	}3j@9��	}�@9;��k�{e��	})�}��$�)n��.w���X�)n��	}4��ai�)nĉ&!��>�)�	)�Fmک[v�	��}t�壠�{e��>�3��=���	��)�I�>�G)B�>��Kv�)��'�)(�	�[�w��j�)�'.�w?���?���Fm5j�zv� 5�o{M@9u$.|��.DA')A)OK�z�.D�'	>�)?z��'�ִ6j�zh)2�í7j�jX�Z/\��"��|�7�b�� 8j)Ԃ :����T@��
�j9j:Db���kI�#�Jd�4Z:j:D�.Dh� �!�����"n��fP�����a��>��'o�J����6s8{���Bm� :D2�í��	�8�,D;j:D�	��bo<j�b���,D��g��,D�o��Bm�'�)�|��>=�Bm��� ��܁�,D��Bm�3���b>j�j�%1D���܏-n{�j�
}����o��A��Y(�'Ȓg���%�3)L-nX5�?j1D�H��	I9 ��|A�"n�����&�61D����@�,D��w�1D��w�
}Z�Pc��f��&�,D�4�+v�%e�قI9:D��)utO
}YI9:D"@1D�f��zw�ִ�)u9�q�1D-n�Y�|4�)u�����	f*
}��r�+X���]�&'I9���ƅ�b�
�j�c�
��`Q�ܸ���O�}�f�A�,DB�b�<�&��Bm����miv~{��́,Dz�	f�6�&�Bm�,Dj7�&s��&	�}X��`��b	��	�m�%��
�Cj�&��>䕄�eGִ<�:|���>W
}�de��&9Wg
}p��D�b�	�vU
}E�`�\ds�-��-���o\dFj1Dm< h�&D
}RI9t�o��|&�f1D��-��%l�;nf���|��)u�	���&G��zW�uH�>�I9I�)uJ�`��P��	�P��`K��&Lj�&l��,�{���z���b��0��/�C�a�#�&M�;n�)u�'\d.�)u[�}ſ>������KmN�b��`��Km�	������-O�`�K�k�)%g�&P�Ym��zO�Km��zl���"��>��z%�Km��&	��>{�)Q�)�Km�\dj��z��)R�)l�hO�.�)S�Km(�%� !��)���G��>�ʴ9�����Ym��Km�
=ȉ�#���T�>o��z�Fm���z��c���>U�)���z����>ߤ�zҲ)�����)(>|�j(l��.Vj��"�&��c���z��JcW�)(����)���X�)(��9FmY��zZ�)v��ӑ���䋃�g��)��ܕ�>D�	Fm�����ƀKm��KmЁ}Fm���z�x8�.Hd���U
X<��>�)��
uk[��z���z*­>2�a=-u+�Ym�ۧCd݁��́�z+!7
f��Km݁��)5�b�n� �bR���\��z��Km�.
f����Ղy��`��+X��z]�z��+!^j
f/�&��X?_��z:���m?-uD�'D��$�b\�!g�)��y`�be�V���
9��y�	o��N������X?s�>|s�b���h�뜡	o���l�&�2ÔCab"���Cَ�'Do�|�a��h(�+i�m*QWT���b|W�^�f�"�}8��h�b������bjd~��c�	ol��b�;e��f'��4�	oj�}k�*ֺ6�b���y3d~D��a
)w�}5��hT����?
)���b��h���ߣ��-����	i�=-u�
ve��K#
),�m*��zd�%���8e��h�n��b�
)	Sd~�-u@�b��V���"���b�
)fj-ug�-�$	i��b
)\-u98}~��	o��nq[��\X
)���h�	o���n��m*l��&��Rb]mn):�]}~p��ij�zP3�\�ܚ�	ov�s���Q�]mjj3D��~kj�z<}~ROm%��j����'DgI9l�{~mjOmZB�z��{~e�n�Ad"�^d��8S��hnjI9���bZ�M9o�{~}�$g>?�ޑ{~��nl���ojI9Y&��n�n�:C£I9��b�{~~&�pjI9i�[mqjOm�e���1��7�^3Drj8D%��v��c�cr+I9sj�z���耩v]�z�%
)���6�{~9�U	�zd���J�ST��>
�����8�8D�w��?�tj�zZ������&����5�cuj8D���	�zz�{~����3-�z97����{�:g��8|�53Dd�z :8D��>t�+{�:gv�d��4@�I9'jOm	�X�o	i��%�9�dI9ROm+�8�
�(
�,�z�~�`x��.
���{~l���Q�d g�ܹ&�z���`��z���N*�����<v��ܒ&ѝ�8�*�j��|8�
o���&��,ׂ{~�4����i����vj�j*��na�nwj8D)�/m�8�Q8D��>�n�C�z'������aÄ�ns
8D)�����ރ9�fF�>xj�zyj
o����b,�}*Ҁ8�o#8D,J�>��}*�
oׂ_9�-8Dcg]"	i�
o�����YC8D�
�8I
�H��b��G,�}*zjDD�	���Km�_9\�8�G�_9Ł}*DD`W���d{����]9�裑�zӺ�8b�jm�8j�8�}*"�9n.Dv��fX����8|��*�Z�
o�}*ɀ�.ú�+�DD_^�.!�f*8+
o;�8�$��?{jDD�΀��Œ��wDD�
o,	i���ޤ�j�Dv���{�w�e��g2�n|�j}jDD�$nɶ��}*�
58Ҁ�?�K�����~j�&ւ �=��j�I�?�@mjEd2����&�o�DD��_9��d���}*�)!�i�?�o��&ڂ���}*���j�Ed�?�X�8��E��������%$ /���Ѩ_9:���"�eD�_9�$u��}*`1��&�Edy6n��fo�Y��nY����%�-<�|@��z"!D�j	i�jDD��M9���&��F��>g�M9�&:sDv/�M9D�=#;�?!s�M9�jDD�'��Y҄j<|k�:�X�{e�j�i��
<|�G���3���j��.=��ÎYү�M9�<|�@9���xȀM9d\�����5C��&u)<|p�f��j@9�<|)�&��b�j@9���x�j<|@����g@9��>d%<|�8X	i��M9����?�aG@9�j�&��M9<|:�)�<|3�Xd��<D��- �&Z�̣����	@9Ł<D��-G�`%��h%�&:�<D���o�<|�ג=e+0�7��<DB�aa�>i�S�"�@9��M9*��n���_�<D{��+	��>��M9j�|"۴��n�?���|��Y��V)��M9�C<|�k�Y):�<D��M9�j)><|��M9�j)����j�&�j)�j)�2����:�M9>=n�n&@9x��<|���m)�j�n��x+)���ܹ<|�Om�)"�$T<|�3)G�`%�n:��<|}�ɣ������+<|Ă�,��:����j@9G�`%$�<D�/��
7�j:D��:�q�<D)
:D�)�	)�R:Di4)��be�b)����$g��beK�>T#:D��h�be��z��:����Z�.���Dz�,@�&��ɣց����Hd��h|�8���o�"n������j��hU�bh�:|9:?��:��ܗj:D���hk�f�:D̑&��j�j)�)
����$'�:D!Y�
Yo':D��Hdی�z��b�����9:	�e�k?a�g����b=��5��Hd��#)+�:|���d��8g]�Yv�	���a��́���,���z��V��h�%���Hd7�"nf�&ɎIv���&H	i���z%��zk�:|���&��%�Dm�����z�9碡:|٢}=��&�i*e���S:Dv��!��;nB�}6l���&�-!��%,�%D�'��Md4]@mH:m~z��&�;n���&��}��}*��9�Iv��%�?:p��jԖ涐:|���-ҍ��J㣝�Bm��}��}��:| ��-�"n��v���Bm�E\d��"n�&���BmT��,�:|��ʴ����9@"��j��%Ds�}��i
+d%��&�jo(�zd�ʒ��;n>�ִ�b@"@\d�j�-d_o��}��j!_�j��)��*��-�d%T\d��&'���^o��(���rH����zcHm�  *�-ց��J�;n%��&� ���U���ʢ$8o�����>O��&��x(�/Q@"JoXzK����̓�&��Ζ;ع�y|}�j�z�=��a��&S��zg)98\�	iq�Eº�c	;��{	���?:��g����զWd��Ymv�%D���w�q�����Ym��)Ղy~�Y����$��Ym��$�j\d;���
���.Ɓ¡��́)`�Ym���#��)Z̢��۬:�9[>�o� �jo�ʴ� Fm��Ym9ov�oFm)>|J��!'�zj��A�Ym�Fm�j>|
�۹�Ym>�ִF��j�b�jFm��,�j�b݀�$��i]�bU�Ym�DFm=1�j�T�b�J�iH��X�j�ܬjFm�
�b����)�!'�A��>D)o��=�>D\�b�ͣU?o>��w��P
�>Dw���@�i�o��+!��!'�>DÃ)YFmA�b��>D��Ymȃ�υ�A���?]Mv�	;�ͣ]>|Dz>D������&��Ym�j�b��i���YmQ�(|��$�	i��>DNE�3��$��>D��)��YmO-u��>D��Ym� �byMvɀ>D�Fm+��{3Mv�!'��>D5���ji1��>De�ٲjMv�
o��I�f��Cd< -u��Cd�>D<>|�/Fm������b\�@+z��b�jFmLo��Cd��b>|wFm�bG-u.��&Fm��b��i��Cd*���$gń� ;�zb�iL>�b_�Z�Cd �b��>D��Im�A�R�>DX���h�-��Cd����m��ݧ"܎�b�N>��8�:"�>D=�����
���>A) �_"0�Z�Y)��>D2�Wd_��
٣)*�@�4G=����W9W��W ��V)��>�}
��-���n����Î/�)d~���H'�6t����,ӏ�8(���o��4�	_��@v
}~�4'�+zT-u�*]m~'DDbi1��Cd�Cd�)$	i����Cd���ς}~�j�kx�Cd.]m�j)C�n�j)e������jI9^�ɣ]�Cd[S]m��,��Yq9):�)>E}~@}~������,J�n�"��	)�jI9qQd~Xve_���Ad��
�8#I9��Ad�}~�
7�؁�>��Ad�j]m��$]��z�V1k�Ad�-g�Ad����j��J���c���)�J���zG]m�a).V ��j�AdB�����z]f`)��z�8D���)hX�S� u�>��Ad��>	��S�&��Ad�B"R}~Ց��Ȁ���j�&���z�)Fv�B"�z x
]m��c�W��0�fP�W���Ad�4�ֿ�)�j�x'!}~W�#b�&S�b~;]m^�}���zvB"DĢ��}�Adk�F'��&��}m�*�fw�o���~8�c]m��}i*]m��}�jI9��,D�Zdh����8�B"��}�
(���{d��[[I9B"��y�jGdj,Gd���=�F�B"&�Ad��}z��>��	�,DE�4Gdh�Ad���a�}k�-g%��>��Ad��,D1�b��ƍAd���zo�}\�@+#��z7�}y��@��<��ׁBG�b��o�jFv%�8�Ym{�<���5bn����
 �
�Fv�jFv�j�b[�_9��bC�C��b�m1���b�:��խ�}R��b�jGd���i��m1;��z��b�����#g�$un�#g��}J��o��	o$��jl�-g��}��}��o��Km��dĂ�b�h$u����E��jDD[ċb��B�#g�jDD���+f��������b�+DD�Z�
�}��l��zb����D��j&��/�jGd��#g��b�jDD���&:DD�jGd��m1��b�jDD+��b݀b�$u�WGd�ͣi�V��b\DDὀb�jGd����jDD���]�& $u�jDD
�&l�	o偀b�ˀb=��-�ǥX�s���&��)M�Km�jDDkBEd�I"��Y�%4@DDa��b��_9��bg�X��	o��܅�Q4��jEd�m1e���ʟ)h�&k�a�m1�b
��iQDD��bA�u�ݐ�b�:Z�ݐ�b��σ!D�jDD$ujEd���DD��#gv�����)�eDDe����M9S��zLDDG�&�M9�f�jDD�j�>�M9�jEd�jDD��i~�Ed�:��3�>Ղ{ebI"���@�����M9Ä�bH��b�j�z��>�}SD�za���4S*i���b>�'>��	o�M9�
�z.}…��
�<���<���&�� �g�zrh�z�I"�� �j�>��z�Ed��Jd�)gEd��/�oes�Fa�!D�%�j�&�j�z�4@���_��Jd�joe*��X��`[��w��F"w��w��:5Z@��Y�I"R�Jd�!D�� Gg.!Do�a�j�z�Wd5�Jd�� �-g��f�T���	]m܄M94���Om5	���)!��Jd�F��_�8�b�]m���`�)B]m�j
o��M9���(�^d�'g��M9�Kv9�M9��-:�
o�M9�}
o�	'gk
o�։D��k}k
oQ��l��z����Kv�´.�	fe��`�[]m,(��I
okOmk
o�2���>��j}��b��-��z��|c��.'g &��Q[\k�b#´���9�S��b5Dm<�Jdc�	f���z�Jd�5�{0�z��F"=�HĀC��	f���i����'g��Jdge����b�.$n���`2���Jd��c�]m��oj�f���z5~bJ��	k
ok��?���
�Hd뀫�Ɓ�{w$�bs���k�4�ڋX�ѐHd4)�b5OmƁ��
o�0�b�j�����Hd������o�DmT�Ql�-g�B �ג�Hd����9�M�3!i�ba�	f���^+$����b
k�f.�Hd3��z�KvV�k5���g�B��Hd��B���g�f��3��
�z�oЂ��ی	f,��:��{~K���,��o~b�Hd��&���`z�f��%i��4���zr��,{�f���&&�[k�{������)u3�{~K�Hd��Iv�f�9�{��ڥ\����{~?��x�����3D{F����Թ%|�V�JWDm�;F&��&k�zmfe"۴��l��x-�HdkNd�:�~^NdՇHd��aQ���\;g�Hdk@"<��okNdp�{~=�oZ�Hd���	o�	ۣ�o]fe�%D�Iv�-�kNd�A�)u�LR9�$m~Հ
�@�'
آ�
�-lJNd��zڇ���
I&�Ψ����Ac� ��&:׀����&	��:7�l�`1��&>�x9���i*j$�	m~4fe�{����%|��-ڢd�ϣ2Ndh
f5OvJU�ʒ�{5��܈)u�@"R?OvƁ�����?��_9Ř�`1!�%���a%�Iv�f��`RK"'��>���ԚOv9�%DkNd���g�{��k�%D�Nd=����d~��q�Q+!I����1D{Nd��	Ѝ�����-Nd
#Ov�_9À�?6<Nd5��z��a�
���z�
f5��.k�fC	+!�%���6�>
f�z�&`1�+!�BNd���kNd���z�@+`1kfz
+!�z�+!�)�zb�ה��(i��o��.���o��z˧������zN����z��ekOv��g�0�g� �z0\�!�)���zl�8gR�b�j�Z�Ov���h:��?��
��&�{�e
f��z��z������	�)�<��"�BvOv9�S��$g��+���z��a�����o���W#k+!�
4S$kK"�iz�%���yЁ��*�i%k
f�Zd}-��0�ϝ��z��z�Mvl}_��z�79�{���yH�Zd-���ߣ!�)h��ܣ?�9�����ir������X|Dk��yO��z@���d���=����緃�߅�ۣ�����9!&�)'��z��Im��Ɉ�e����s���f��z_����-uJ��j�
�?����Pk��~o�>��?!���@9�:����i(��h�[��&�)nk�ݙ�#�)�)n*k�a>�(���+k�y+�;u��,�[v��)n
f����hq�;u����-uh��f5��h�iG��e�y9��h|�
��
���&@9z�z��--�bev�o5��ya�����h8L���x��f.�bel.g�Cd
��z/�-��0��&l��z=��o�费�%I�&�g}+�k�%�v����h�ԑ
�h���ȃ7�?�i1k�,9�{�9�{�[�B����Ym#=a���i1��D����@9��T}~��y��fb�)n�/��f2�;uЁ�A�4�������e��h��z?�p�o�	=��f�/��+V�9�Y93�Cd{�� ���ݨ�P� �zI��V\��$��Im\��a���'�tߣ���b"�>Y	;(�z{��h��>��ke�'@9���24��h�@9U
7���h���{��fa��z�ߣ:���5k�z]��+v�(��6k��6��&� �z\@9�'!��f�'!��	@��3.g;���O
�zDߣ9�>(�'!7k:D��h8�/!(�'!�g��3'hf@99k�z���G}V =�z�'!o��kY�$ {���o?9#��kk��:3�xb�'!�^dn�x�'!:k�i:�z;k�z}7:D/:o��� �'!��,D<k�z�ơS�%̀(u��f�	�z=�b�� �x��f׀o���ai��5	ߣ�	;��%:�Y���|^��+��>>kd2?k�z��+p��@ߣ(�'!�7'�(u"��b+?Ud�}��x�Mm
���PAMm��:|��(u_�x�
$�@�,D"-n�IUdYܙ�z��b��
x'�ʣ)�'!.Mm�'!�-nM-o)����6Ud��h>/�jo�M-���-n�jUdjFMm�'!��	fm��b��za��a��c�)g(�'!�YmG����Mm⁽��dol��+3��b��j#��7:D|N�xA�b���a��)g|��Bk�d@�ܘUd�Ud�Y���b����
Mm��(uk�f�4�	fS��b�A��� ��H������,D;�����e��$��;nv�	fB��	��z*�,C�b��(u��(u�6Ud��P�͇�bS��bDkUd��L��Ym^£��������cߣ�Ud�MmE�,�Ud��V���b���FkUd���z+)eo>��zBM-���bo��ܡ'Ud��b��7��{S��ܢgUd�)G�)�IUdc��Hk)Ђ�i���b����(�)gIk}eQ6�χ	fۈ@m�J�Ym9��)�&ȸd�\���)g]}eJoe����_��b��YmD��K�Ymo��	AٙbL�@mG&oeဦ�����M�)⁛b	��6N�>�oe�ѣ>oe���>z�%j6,�Kd���>���Okoe8,Z���ިr���m1P�>5��񙂿>��}|��q(��zQki1Z��ゲ�R�>w@m�2òRoe[*!}e�oe�;���J����}���>wC�'�}'��>��@mIߣ���!i1��S�>:�)|�}샿>�o��}�&���z0\CP�ۿ>�裢~nhoeee�?��s�tI%��>��w
��@m?$ߣ�x�P��0�Rߔ������Xd9WTkoe՝>DЮXd��F����Ukoe�� �>D��r��(��Xd�EoeVk}eރW1��hRoeWkFmXk}epoe�'�c�x�Y��h�#4ZB��>�JI"��}H�Xd8
f��}��>Z�}���>S-g���2�B��Շ�$�@
fmĭ�'�b�<����<[k]mQz�\k]m]�Xd�b^�>s�}oH�b��ht:�bȀށ&�}Y7-g_�}#6
f�$�b3��5��.{�bȎ�h�&�QҖa
f���(��&L�2�t��(��ke
f-g�e��zw��h{��z9�ZQa
fԂ�j5!�b
�����)���)`��z'���
fa�Xd�?��mb��z�x��1���ic��zdk�b�Q?/G�bek��f�)T�>Dg�)���z���if۽�X�b�/��bD6ue��:�䣚����/�be��ׂXdd~���z)�Q?�Dm`��m�b<d~o���{酀Xd ��z�¾��"��h��hw��z�]m��P;-gh�)ik]md�b˖)O
f��b��"g����ߣ�Dm��b=��c�?]mo�k%����UH k(���c:���4�b��z�	�_jk�b�5�Ђ+z6��=��߃)k��i���z��e�)ȒxN���d���z*i/�E�R������z�:'`VI9I����+n<Wd��i2�)������P���i����:'j��i�1���zg!Dm��%Ҁ�cT��((�)���-a$�%s�M"�� ��f7�%X���9���:1� l�)W&VB�
��k��_9��+z][F��%FJ/�?:�}����֋�`�8����?��{��a~I9d�ˁ���mk�>��J��_9�ʪ�����?:n�"g��M"���
�����`��f�+�>H�m1f���:�"`1�4\d�	���@"S:�/U�
�W`1��fyըc\:\d���`O��(�����Wd�@"A��&97�%:��(~`1�>���%�#��b�m1o��`#\d}���&ւ�`��>Z��Ԁ��}�5Zs�fk����\dcD]v%�z`K�|���S�D~]v�(I9�>���&�'D.'��kF���&�*]v\�a�I9�E�_��m�_9��Zо�m1���|���"�Ad�]vp�F9qk
oUIY"Q�zrk\d9!t�_91U\dY� ��?:����H�w�m1�M
o�C\d��_9u��fi\d��>]v���sk`15��+�
}��z:�i����=�-:��.t��`��"t��Juk�-��z��y�B\dvk�zX�:wk`1�]U#��ʃ�"�F9
`1(Q\d��z���&LDDD����
�z�����vf�z(����;g]��,��c�
9!r�<��S�G]v
��>Vm¤�%�]vk���u����&<$�joej�F9�gDD!�Z��:]v��f�j�zわ�oez�zF ]v#���H��`CDD��Zde���h��`:]v�Zd��z��O�	���q9]v9�{�5�z�%]va/+J�z���xkDD{�Zd<��a��O��jm�y��^>'v�Jyk�z��<�P���c�f�L�z��-�7�b����Zd����t��je�Zdt�++�k5��z��p�I[��$zk��/���k�	ۙ�������J�Km��fه�i���༁i~{�f\��|�Zd��e�Im�
��iАѸ�;u��`׀4��*ւ�Zd<�i���;ul�z|�M9��;u׀q��Z�B�b<1��2DDҀ
}��%��<�щ_"t
��[�b���2DD}�x:�&~��h�M9x�:�+j�y)�f�+�`��&g�Zdm�/��x��Zd���e�e"�H�)�4S��Im5�i~h�be��9@9ӏ���Zd
��x��$l�
}�%��Zd���x�<gt,Se#i`*�'<g2��FJ��f*�)�%߂��Ŧ>m�W�o��a�W���-���Ɓ3Ê�ژ��>�/Ѿ�q�b���H'[�ǒ��f�����v�)q9zu���uI��`���x�ݍ}~�	�7���P�Y`*��[v���Q�Im���Aw}~T��Y��SJ����)	�d��<����/���<g�"Az
`]�)�k<g9XTL�ǁ�x9����Im�M9T��{L�����Y9��ސ���€ �	E�������x��&������M9݇�aÃ)j��۽�����l��z�a8�)o�A�F_�pF9cF�k��2,�zj��>��%�a8�{�D#��“�>���z�<g���z�^db���k:DQ�)�����Y�^d>��zv��O֦>����^d���-��ƒ�_;�)S��-��g�m":D�0^d:��z��)钣��k�a<��,O*}t� �I
��a7^d������.�ش	�c?��"�:||�ֆ�)O�b~@��z{�)큂b�}~f�i��7)h�$�k^d���#(^d�X��b~\U�j�8���`"�h:O:DJ΢m~���<"��i�8Mmo�}[^d��jj�۝�J���o�����*�b~������do�d���OMm3���q��zw�zԀ��a�ix���,����"a�km~;Υ,ȃ��Ԁ�� �;n��A?�$g�of۽��ie�k^d�=Mm>��z��a=^d�
:Dy�g?N�x��i'�i���<�.�;nH�x�7�S��y�	m~v��E��7}�o�<{�kfv�*����<���y8�j��+���?�Ivϋ��k�j��<�@�(
��j���	*�^�a�m~ʋ�݀��j�������v�شж;nJ�3���b~׉g�[�wĂb~��G���j�D)w�$��jA9˝�[m���&~�<��;n��A�;��{�3Mm�`~o��h�8��@mU�`~��ո���%\�`~l�@m��f���@m;�`~oեzw<�=|�@m
ɻ��x��<�d�`~��@m�>��@m��&���̍���	}eL�>��`~� ���[}e��`~Pi@m�D���@m��`~��P�����@m�gP��>�}e # 
$�>��@m<���j���N6}e5�֒�@m]}e��`~	2�=�>ջ@m%�z���j��}��`~G}e	��G��bʊU2��@m��y��灿�@mlI"
9}ed���y�@m}e���X�g]��-3��&Ɓ�	����1�i�8��*#��>�k}e�<����/�2ù�`~�
F�������`~��@m�kf~�W}��@m;�`~">|�}��@m���J�(�4hս@m1�zd���h�f%��['I����������/��@mm�k}e����@mYf~=���L��f�k�>��W�>��ca����>D��:'"�`��z�:u��	]6}e�$nw�s龀��k�zn���?.}e%%�>l$n��.��>.�%$n9����A�=�:u�O}esI"T5Z�
$na0��+)O9�v.o����z�$nk��*�>D�:uG?�z��r�>Du��mP���E�:u:x�2���#�>D�>tã���:uj�>D�B9��>Dc�4�>�:u����I)�y*�>D�`#vãD��$k��*�Yv��f��)g>��)ܒ��:u|�y4a(�>�j�����	�����j�f.Mvo�-��z���9�_w��i�o�0�⍤�a�&D�z�����{}/:�zh������U�z9w�>�C�ݯ�:us?�ySDmރ�ϸ���X�;g��:u��a`d~e
�H�^+7�b��	f��>D���a$����k��g��xh����:u�C��mw����ƫj�:u��)g�>Dh����C_
5G߁C_��I��,��C��6s�d~l)���e��Dm���o=��@�)���y���^�����b����b��)jc8|`�y#�o�	�j�S4S��)�k�y;'ĉf���z�k�y��f\�u���a*p�!�����Ϭ� �%D�դ������k5���tۢ��jɩ����>��zg�����a��������o�aЁ��E�?Ԃ8�0Jd~n��a$���ؕV����z��%��Ad��;��0�)���a��aF���{�?=�.��8�Ԃ8�׀rq�|2���	�;��u���(\d~�UB<����?+a����y&���m1=�Ad�M�>�k�&D�cT,i*2����\d�k�&ã�k\d�Ad��&����&X��>�k�-3!CP��?)J��x���"��-�\d���z=��y9�_ѕ7��AdX���9���:��-A�>��&e��d`�-��x�!;��k\d3�&�ɯ�%|���z�����Q�+a��xh���vãg������k�&v�8+a�$�DU2��ݠUJ�+�-Z#\dM����F9�#�P�x�Ԁ
'dIoe��
I'���oe� 3�5��t�}O��5�k�?�koe��(|p
�)��|��.���:Xoe��a�����oe��%ܢi\d�^oeh�E *�ah����C�a����)�j/���*=oe��q��Adg����m15-g��&pN-g\d��k~t�-���k��>H��zڋ��
�-`��z.W�
�(���k
fS5�-�G��z�i�-�\d�oeZ��kg���fx��N����k�"V ,VmhGGDD��b⁃k]��z퉃k�Z��N��de�	o���z��|���i���k��	ol�)nT�����k�EDDv�F9���zM��z�`+a���S�(|�DD���L�Z��E+a�kDDY��k����C�k�}���k�I�a(��z
DD9`oe��zP��z�-DD%�T�+�i#ބ��6oe9DD	�
�>��9D&oe�kDD����"g$DDX�Q�;�ay��k�������	o@�&�	o^��k��z�k;�m��+��k~�kEdsEDD��|h��z���k\DD���k��	og�����X5���/m�}���z���I�k~������4�o[����i��`?��z��zk����΃kEd����4��zP��i
�Aml}�:Z���Am�g���bM�Am�KmX�_"��$9�6:��{	%��5��%�Am	��b�_+��ŋ/a�kDD��Amqf�qQDDe��?���یAm�kDD��Am�津���#�k�i~@�}�����Amw�/a����J��+ޝAm��i~о�?{��|?�Am�(����Am��}Ё��q��%��}<��Ȁ��>�h̟Am���D��&'�}�Am���9�k)��?׉����?=5�0aR����Am�[�>��9�o��1H!D��/'D=HdƒԢ8�Am��+�	�`��/ؒ/�V�Y�~�|w�n�`mæ>�k�`ϔi~��(C)��i~�G��k'D��Am��z�p��Am�k�|��&��`����?E�z+���X�zսAmǒ�#t�M�Am�\�.
o�Ū��Amo̢�	;���%��Amg�Y�Y�_"�y*4����>��.k�_"��z�
��
ʦ]�%�}E��Kڧ,�r����zg
ol�?N�b��?S-n:��d�CyC������k-n#!�z�����,	�
����|��%
Ч,냶jS����b|K9�h}_*#_*��F2���偤�m��h}_{��kZ+�6�Dz�,�z�_}R�zЧ�,��Ū�0aR��ه�$�zه�$� $np�j�3�g�i#y��m�9�zh���z}5 e-n"�Hd�F"��z��y��F�ĂF"�$n�}�'�
$nƁ�j[}9���`��߂Y9�k�z.�>������ch�8�d�%����z�k�z\o����-n�3�z�����}J:����k�z��,�m~o�kEm���,D����|o��c��|ݎ�|#]Em6#��#m~Ι���EmH������,��B5.Em�km~�k)M��,"����}���ED
ˣ\���o��kEm��z�zi}k��c�
m~�YoJ}���%�|�kEmLW�c�����x���Em�Mm�Eme}����
:��4}5�Ӛ�C��o�Emf>�z��[ma+a�X�zY	Em0�{~9���[m��m�-(��	����Faŋ�|:�7�$�8��k)���8�
�%D��[mԂ8�"+a�6ܫ.�L�8�2���|��|h�[m��PҮ�%���kow_�_��`B�>�&�o��%&EmB�8�g����kEm݁o�Mm�|�	Emo����+a�)��._�/ъ�{17�o�)���P�	Em5�)V
%|��b����k�-ք�|a$��i����)<��|���<9�>��[m7&)%�-d�^��)h���<�>��o%����m�|.�>�٢>ʩjЂ�i��������)"�&:&�)]��"�)m0a��>��jJ�n�n8��|\x� $�&�ݚ�-�5'�|i
��D�>���|��8�뀢���DI��%�-���m�t����X��|��)z��C�.C��j��|��ё�k�-��}�㙤�} F9�6Ejɰ%�&��G9��O9	�I��|�o��	"�&��|���o&���c�G9��o��)��}i�]9ŋ
��}Ӑo��ؕ)t+a5�D�>W�'���|d[�|	�)�k�`���|�}�T-�Q�ՠB�-h�be��G9���ܔ�Vd�ZdʇG9�)�Hm9��	�G9N�Hm*��I�>D�(w1�-.$g��Hm*�Hḿ����|J�(�	+aʉHm��}��i�:�`
���$�����}���$'��y�Hm�Σ��� �`:��?k���W�}��Hm<me=�%�:�����Hm������|k�Hm��O9�k�`Y��?���o�um�5'�MvʇZd�kMv���i��e�y1��}��i5��ȟ������Hm\$g���x��G9���?q����G9��h�����Ӛo��iL�2�kF�ց���nIƙ9���z��?��9	��k���me���z$���k�`k���9"�`�)n냽���)�"#�}	�&Ԁ��8�`H��z�/ј�HmϒHm��)n9{k���z�C�`9�Hm��)��I:x���������9��Hm5i�ڊԣ���=���i$�o�(|g���R�HmH�*����n�9<��iQ�����"g��)<��(偣�=���՝/a��?���i<��z��9��)̈���9\��?c��?,�"g�i�����o�G���̀��5me����|2�G'w�����I��R����z��%�Om:}���-�0ah��l}�����9��{���8�|&�-|�v� 
}ri*��)�s���z׀t�'���aj�c�9��Qc���y��|��z��&�0aQǯ>�ř9P0a+��z;��-��l�%��{O��Ɣ#u�ә9b��|��):����״0a�ڦJ��I�$|8�|
���D*#h�%�*���j�|>�4ab[��:#ug}z�|�}[��|��������	���#ui*���-)��-��i��O"Z����W��kz�|�.��&�y��#ul\d[��ioԂ|�7��y|��f2'9Pj�/.��f��	��x̛fPeob��HJ��0��#u	0al}3�nd��:�|�l�x�o�}�0aU�,Dw���\}�|G4�x��>lo��+�K+����Ɓ7Gd{u��Y	0aTd2��x��,Dh�i#D\dg�{�|���>�0-n�-�i"��|1�|��m�|o�z�������<�xń��2'�i�-	l�z�,D΁������9!�g
�#u��'!l�z��i0�,D{��$��zN���_Z-n��bѐ|���oc��k��c6nl�z?9���
ׯ�%|� -n��c>��|҆���x���z�'�����|�ЁC�
l-n΀|�5a�-n�:�\d��|��z\-�xP��a�
uJ�
�o���CJ��z��Eց��Q�t{v~�@�z�X�x
��Vm��zP�vf��x�g��{®u��z��/�*AOde?�g��|�itT��m��|�Vm���Afd:�ie��ac��3�,D��B��,DH��ݎ��/Z���|��z�-n�
�|��i���c�"Vm�&�-aVm��|��{zm0al�i��IVml�z��f�6del�jm�|���@��{o��l�zlVm��G�m�|l�z��{Ҧ����)g�ݸ�)|�G����ap�<���D)��o��{�z���{
�.v���ab��{Z�n���+T�>Dz�zւ�ag��"��a7����i��a���z�Ո�>b�U ��>&�U���=)��>�7�iDVm�Z�-�y[$F�KVm;�i#��>o���+�Y�*�n��N9l���Ђ1�0F���1�FAVm���h)�iπ����h	
�1��&10��o�̢��&�9'��h�
�[k�i�>���F���>����&g�&w6��B�!v(���k��h���g�Ӄ�>��|o����z��������"x8�|���h:���&�|�\c��|�
�&���z��)��h��|��z䝺cm�|׀o��Om�x8�oZ���Y�:De��hń�+�)l�&�&���́ؾ�aE�)��h��|��>:�����LY��>��CmE�)Py��&��y"��%��>�	���(�c�y��)��}~T�Q瀿>���(�:Db�|�,����a���Cզ)���h��	;��f1���hT��Vl}~T����aT�	o��)m�K"RY9��)��ף0-g+�|����l�&��h��|��
}~�=aR�)��V�S�|���|զ)���i�Y�g��h��o��6�ף� F�v��j4�|�lY9��|	�|aF�5�;n)��I'V �ףy�|�>�-m�|�]��z�W��y*��)���n!�C��)���
�ؾ���)���&��)��Y�5�	o��i;�)o�G���;nY���u��؇)�"2'r�)�b}~ϋY�hX�|:�-s}~md~��<]��3���S��i����Z��z��)�f�z���<�몁բw�6�À�i��)5�bQ�y*k�|�����<�-g]��{M�aOS
)'!Y9Z
-g�|��o�-��a��|lo΀Y� l�a-Ī�������Y��|�\c7o���|��À|�|\�|ȀՒJ����%2'7o��|�Z��	�z]��z<�-�!D�b~[2�|A���c�)DY��z:Cot
̣�+g~������z����4�3�AmԊ{~�,�-	�|ґT/D�[m���GZ���-�|�i���-��d��*[�����i4�Am���(��-Z�j��̣��Am2��6�����`���Ζ�`ŭ>��*X�m.�|^��	G��`G��`��Amm�'!l�|�������`S:0a4�Am"�Am��f�b~v��z���҆�C��Fb�����`m�|��ie4�Am����x���p��`��,h�Am���*��-�Ѐ6�8D#��`��&���w�t$��&
	�,%�|��`$��&ئ%�b��J`-o�L�,�����&l$o�0a8���b��bc�G��`&��&�/�h��>��g��yv��Jx�>��?�-R�ie'�bHtt�-_$�>����(�b��-�>D����	�e���i�ED��|B�|�)��:��*)��`t�>*l�-t
̣��>*�d0ae�>D���%�ܱ��`+l
o�h�-�E���L���,�_9?	�𺂀b�|��iꆀb�,9��-��`t�-y�
�
ok��"��{ƒϣ���������@����&�|ރz�w�/a9�2�
����&����{d�I�y�|�~��Jx���敀b��i;��b�2'	�:�Լ�b:#�2'��i�����z�Y\Q��L���?ݐ�b:��&�
#`e�f�,�zV����z��%I�?w��|ĉ�|����3
ot�-J��|׀���>.�b�o/l��-�D�2'��e�� �>��z0�Jm��`e�>��i*�-��He�>�$nI"�-����e��x��z�,
o��-�-D�/gj��|a2'��Q���k�|�Z
me1lo��*2'l��	l�iv.o2�ZdQ'_moRo�B�|�8�N�I>9_m3�?9�k��:P
Wmlf#=M_m��hvۀ�&�`�.u]�bQ�E��/me��[v��e"��$Ԃ'4lD�o;��{t�|Z
me5�&�`5	̣�)|5�Ad���|�<DbEm���+��`|>�2'�L�z;me�D���Ђ��6�[vlB_m4]2'>Wm��z	 �`�2'��%D�a�zŶAd7l�z8l�?��t�Y=�n�>�`�|� �:�S��za��x��b㄰Ս�Ad9l�ka6�?)��|:�Jm���x��+��|�������|� ��`�2�|���zz�|��B"��~�-|�k8�|�;lme��xz�|���8��|��`e�K�y��k���=��wf�`e8�|����i�|�X#/g/�y\4|�Р�k,��zg�`��ϣ���'Ϲ�j�me*�`e>�)�g{�$'�̣����q�)<�Ek
$'��o#�{m�|j��|
�zV�`e�$'��`�x��ã=l}����|�����ٙ�v��Jb��\�oʭF+9X���%DR�����l}2)����.���$' �Ek�:��y��Ad�>�)t{�$'�	�`�|�����Z�{��ۚΰ��%��.t����|
��3�b܀&t{�$'���'�X9H��y��@��FD�Eky�|����P�k���h�|���|>�o��&�����<�ۣ�)9�|�?lX9���-#\�i��gc@�o�����oDD€�RH:$g
	��Q��r��n(�&揷�gZd(f%�.Dv��o�բ�X9����}�ۢ��|KDv�ʀ�$'�����t���AlDD�Y+�:|a:DY$'(��$g�Y+�!DDB�ZmJ$'ہ�:|9�;��ۗ4S8DD�ʀ�U2�X��{��Zm��hW!=g��?��-�ip�<�Zm���>�ϣ5��-"�Zm���d=��?���C�ZmV��?\��&�����Zm���b��΁&v�������o���Zm��-튢�v��yD�Zm���y��.є��+��?	��y�	�-���RE�Zm@;�z�h��;n(��F��zGlDD�	EdH��zN��?bXDD[����
W�ݬ�(�Nm3�-��8u��z�+�aĂ�/(����z��}`7cZIl:DJlDD4�)NC�a�ADD�Ȑ�z���$K�Zm,�}L�I���z%�Zmȁ�z�
o�)fEF��Fh��3�degǟ	���i���M�ZmH�zM�j���c�2D��Zm5�:|H�7�<�ϣ.�Zm}�|���zX��Nl9'��iQ<�-���?�=���}��"g$=�ǂ��deO�?�=�P�?�cw5�-Q��ם���n8��|5��ƁW��}��,�� 
=�ے	�Ee�����=h�Ik9'Q��Շ;n�5u���$$�l.5u��\9	�{|��=T��z���8�4�=b5u��Uk�ϣns�!̣�F"i�>R��z��$J���|׀f���z�|����S�z7��z��?�cw(�ov�<T��zo�ϴ�de��)(�oR��$b��|!���Ul)��j哷Y��?3���ɘ���5u�de(�y��jB�>\���F�i�e#i1��	����j-5u�+�&H9'nh�iOVQ�o��jD��(	��Z�+k���&�=��̣rE^m���c�*od��`�o$��Àe#v��`��b^m��}Vl^m�&���|Wl)���q��X��`�&���W�.^mXl�bY�`o��Zlo�����B9R�bx�&.)^m[�`��t{\l)}4o�oe]l^m�<'�^m�$5u�b�&�d{��A��>D�	�b��'�o��TJZ�@��oA^m^�>D+��p���n]�b��/�z��(_���_�>D�`SY�zMk�z��&�>D:)�+���`7�&����A��&���ȕ�`���T��bW�1x�բo��h���Ek`l�&?^m��(a�`�\^m�JMi�z��c�M K^mw���b�cY�b�C�&���=�N3��cl^m���c}�գ@+o>|�)g������a���aQ2�b���c��գD#���c���dl�b��Л�c�f��}�U��w����U ��p{��b��c���ʀ'&e�cD�)gs������if�c���R����)gt{�Y�a�=��8(�>D�M��ʋ���cҀHd���i�P5Ggl}Ё����<'��8��A�;=�9���h��z!/}rհ�Z}׀�"�12'����il�z.
h�!Z)�	��c���jl�z}�o*��klo3}l��{�)|]7�z�9���8�g���mloQ6�n�c�&���Ё�����cƣ�cݲ�+9��z����)g.��6�BЁ����+_��ce��{	���oloЁ���ʀ�}	�	��`p��zq�c��h{Ro���"d~�CmȀƁ�ow�
��-4���_U/݃ϣ��~�$��J��z�os�ae�U}Q��h����Adr�j}���ʃ�'���sl} ��J�d�&�&w:4�洨*2'��&L	�c��Ad{�t{=��N��i�Y�&�1�&h���t�aeJd+�Ad72ѿ<�&z�h%=�|��S�2�Adul�&�Adv��h��tIs�ae\���{����2�ڢ:=���oe���ݦCo>�2��a��|۷�J�&��ie3�I-ǂ��ݩ�z���|z��
�w�o�mloʃ�<�hwlo��$ȃ��Տ�n��&���k��	�)G�Pցo*�����)xl)���a�&VK�l-ga�&:�hy�)��f*����}҄)������aey�)]#-g��!���i=����=��ae���Q��i��l�o�=�I�)L�Ad��ae〩j��izl�&���쁩j�{���i��&=�g:� 0�i�)�i[�)�7����Ԃ +=���b)�)@c�&��&�F�B��b�]�i;�(#*���{z�e��b�;����o%���bڢ�?�
�飈.�i߁I-i��{{l�i][��DD��c���bt&DD�oX��{(��|lDD}�	o�)}�ot&DD
�&X��b���DD�î��E9̛���)c<u�‹b���a;-g����!EDD�Ad�DDՇ)��������)�Ջbi'ڢ
Dv~lDDl<u��)=���lDD��b�K"�8xDDȃ{�s�Dv�lDDF
)|��S��cwEDD���R5?���]v��DD�	oo�+چ	oZGm�DDpӸ�����i{Gm�+���b\#���{��ыbm�Ё��5���ؕEk³��meoI�"{�\<u��j��oGm�+g,=g��p%����j2ee���B\c�Am�l<u�Ed�l�`�\DDXme�<u��)�lDD��呆l<uk�{o�-�{eƁ��_�z���ރz�dz�z�c/Rck<��c�
c�i~-%Gm<u���[��n�lDD�V >��>�i~�lDD{�x�&!O��6c��`��Am���H���$�bO�&$��J�Ԥ�ִ�6�`ڋ)@=gz��i�����z�Ed��Am���y��|>�b�m{�`���Mۣ���"g��&���Ղ)��f�"5��l=gN��᛭�`���O����"gZ��v�բ��fZM=g���.m�)_���89��XiGm9���=g%Gm�lme,�[�fk��4�5�+�l�&3�ִ�J�&�+��L �l
o��b�`�Am��&+I��!! ǭ���\�v0�?_�8�Q
o����&J
o�bk�Am�	�&cAd�	��6�.��Y�)��۶�Ame9'ތHc���
o{
osl�b�&�%���
o��b�#ugI+�
o��>!��{
:%؎�-3��b.�	�SCW$C��l
o9�b�l
o%�&o��%��#u���G�b�g1�l�b܀���fU�bւ|��jz��b	�Ek���巃�����b�"ga9'��?3���zӞ�"g�_���z<��i��F"Z6�h����l�h݀�c�d@+�*?�hk_�&������#u�l�hES
o����\m=�hI
oo��?��e|�Jm�he��&R��ی�b��h=>�&"�F"o�hN�b��he����l
oR��>��b[$���he�=���Hd�J�5�$�hg=�[�he��Hd�=�iI+��{��he�p�'D�F�ho˴���a��hef�b��%9;�h:=�Έhe�zB8�z�qx�he�g�-��#u���T�����a�:m~���%'�z�#u�l�h������aO�#u�l�z��h�Em�lm~��׸��z�&����-h:�z��m��iw�lEm��heO�{~��zr����t%��he�l�z9�������<r��]�z��z]Em�l�h�ۖuJ���X�h�<�
�FN�8�
)��O�ٌ�l�z��Iv4f�l�z��h��[$j!"o��c���E��+�!�z�&�bÄ�%Y���f�쫣&�,�یg���n.�bܒ�Ё���heF�7�:��	�>�̒��aj�{�?�J�b��-�4@���a������o�{�l�z��b��3èl�-8�z��O�9�>��cB�c���������ᣩl�b�X9��h:�z���Ja_��
�0k�<'�7d���z{�(j,�z	��V�l�bMB�z�l�b��p��j1���N��*ۊ��������4|d��z�ܹ�iw��&?�
��-o��+S�{~�δ��=������d~/��&4�	`ێ�>�
:��-t{��=c��&���ބ���&����9�2�k�<'=�������)����U�b/�bt{�f3&gоCm.��-�̅?�a)�Cmh=�nj�&�l�b�Cm�l�b���.�K"����>�bK"��?lcNmF
��*:D���c�Z����Cm�i�����Cm�&��Uk���%l�a4�`5�Y���&���a��a�Nm��Cm~�f��o�Ek��Cm�a5���r��p��e�Q�5������<)f�`
'u��o��:|��o$�oj�Ek
	'u���&o��%�#$�oF�+!�'u��&��W�	Nm�ۯ��&�Hm������am����/���&%$g�+�Mvc=���&�l�`�l�a��Hm�+��Cm��y��Cm;��&���ݷ������t{���&�G?�l�_��oԂyQ����������y+�Cm��CmZ
Mv�l�`s�z�$gy	2y�Cm��)�%ب��&̛%`T���磼���
{Ă)�@d��$��a����i.��z5���b��X��Nmy	2g�a��z�$g�(�U���l�`���;8���pi�;�I��a$)�`�������=���
��i�Zm��:�D�[�}Ӑ�z>�y%���}�E93�y%�l�j5i����i�l�a;�;n��8�)(�jB�y%l�}ߌ4b��7���9��A��Vdl=����Z��5G��^��X���{���!���l�a/mwN��?5��+�ބ�a:b�a���i����=W�a9�{���i��+���-ڌ�ir�����i��?�����it��{{	2n���=�y%��}3��LGm��}���-�מ?���՛�}%ۯ��l�Q���?�Gm�)��c
�
����I�}>�E9�)��}k�= ��i%)��z���ݢ�+g�۹�}ǔ�zK&)�Gm�l�*�{�)N&F�|�E9�f��K7�DGm���u��iD��+>��+���.�8|���3�J*�d�^�+gJ�%��b�K�jL;���d(g�C&��������
)h�o#�|�h)Î��䃛b?��Q�S�<uR^4�a�8�������%5oi�8ؚ��)*�Ԥmod�<�(�W�����+U��1��8��{};�W�Q+�(w��݉�|�9��iߝf��lGm�lo]��{Ϥ�i�(���?�lGm�(o�Gmh-�.�oo�8؄Gm8��z��MdJ���lGm1Y��l)��l)S�8�o� Wu:��|�]6Gm��,!Ɓ����l��xw�>|�'�8؇ �I9�)�gcl}k[�*��5�Zd���P;|��(u9�i���bl=���)��>D2�>���n98\�>D5��+�8�v��<̈́)gӐ|���,!{�)�ɣ�o�4ɢ�oO�1�b�f5� �^mh�,!��iS2'���߇8ؘ�x��}X�y0��-3��|�_���o�8�w2�iS�)g��b��;gG��b�C��RH�Ą8�Aۙb;��a���{p�;gy�
2O�)g�HdK*�ii�8�9�V�>�آ:�{�����ߍ��c+(۠�
���
	�)g�l�z�ia�)gB��bt��bߌ��y�
2(��a=���7��RԂ@+���dȀ܀샙b��$!��l�i�����l�zq��2Њ,!��-3��Jmt��b�D�i��f���a��_A�z�l�{����܀���_�����ct��z	��3�'�'�iǒ���8���(�>�{���z���&�CЎ�)g�/g{��)o��{�f� �i	�z:�� �i�Z�kocȣf��F��D�iܒp�=���*!��b��%&�C���z��8�`�S�k�Y�	�h�	�8�W��>���e>��zB�(���fǗ�w3��$��a�i/g���a��z5��z��y����{ex�᣷�iՑ���J�g�|�"�>�f?��z�d��aR�|�X��I��h�o���)(�	���hZ	���)����#��h0�)�=�跸8�h��h�)��(\�{�l�i�ɣ�)�l^dA�Jm�*!h��.������he�b^��zvD�(��&��aɗ�z�5��������h��|���:���)�8إ�boʀ���h%��)��)�&���h��)t��z�[ofi�8�5�8фʀ�|����hՀ6�^d(�2�bmfA�)��O�<�}+�8�k���M��h��8���jւ�9���h�)S
���)�4�b���h�l�&����C����<���aQ�{e�l�bŋ}]��cs�|���p�b4-g��)�i#$�8ؼ��y�}�մ͌F���ܒ�~Y�b���S2'��b��a~Kۥ�8�o#�b�))��+ս�hq�}g�|�+�8�ߔo�|ww��aÃ�hO(:炀�<�F�9���:�8���)�^d�)����ȴ��o�l�b��8ؙ����&
�&.���0�b��&��(:��a���%���6-g'2�b[��x>ۀ	�b7�o�J��`�	o�DD��b�ܡx��?p�o�}ՠ�_���P.	�>nj�D��x^�
۫���	�}���=������{ a��f�;��(Nm�ʇ�z���c�-g^�|����x�
)�lNmSe�&Ղ�%��A��?�z{�)�� �0�b�lNm���|�lNm��`� �&�l�b�l�b�G�z�a�&��)
����z��;h��`n�z�G�&/Nm��bT
)79�b��`�i#�l�D�ۊ�(�Y��%o��a���e�os���ec��	���>Ԃ8�rwe�\�z�lDD�|�L �g���aZk�h��{'3Nm���c���S��x a[�E�o�����ۧ�6����&F��T�>Ԁ�+=�ĩ a�"'��)���	:Nm�l�z��>�lDD��h���z�T�z�l�a$7Nm*
)("'��z�Nm���z9�R�������ix�&W�z9�2Ó�z("'��>��h���(�l�zd��i��<���z���i�+?:�[�Ȁ��D�h_�ڥ���z��{�>Ȁ�	S2'���%j��c��-����i`Fۅ��l�c���iS�"g��j�l�c�ۈ�&ه<��� ������`/�l�c9}y��iه<���i�=g4��aOV�a��&�l�co�c:�c�Ƣ,�>�i��a����a5���44b�Ȁ�l�h"'R�>��z�EdC�?R�a�@5u��iP��	5u�P��#
oϏ�%g�.��h:�5u�	�.o�f#��{��Ǒ�O��xb�uCd��ii�f#�5uS�"gk��z��(k���G��&R�>��z���&�l�j�5u���i��uc�l
oD��i�(a a�P���JW�>ț�k�
���>�B+�����k��j�Ɂk��
h_���9��C���.��beC���m�+�jKk5u5u�l�c�#d~���@��€���(��t;��(���?�&#�sS:�����Z��{:�az��Sm۫��	�
C�Ȁ��Ca�Y
 
�|��@+���(�d~����	I@+r��+z#@+�|�o�o_��k�8D_U
o+5ui�f#��<��x�)���	Z#5u��־�7��ze��ec\�@+k�Zd8Y"�@5uÄ�kj��g�
�g��|�Kk5uj��nu:d{A�)��
h�zeo�ߴЁ��J��R��{m�a�	�C��xʈp�L�ze�	͌>��;cdv�ZdI�ze�k�
�z	'�k=�ze�ze�ێ�z���ׁkG���ʹ=�ze��ze���a�Zd؈�bh�؂y�_�N0�?>�?=����zj��,耕�4�ze��[v3��-=��SD@+-�)����)�z���,�����ۮ'�d���k	��	��ŋ�x��+��z�,���z�����!���z�#1b��,��z�?f�i�ze��?l��%f��z�|��
�(��ze��iw6T�g��ze}F��,mFaS�a�%��h�|�:7��CW�{k���Q�ze�Zd"�J��x��7�9�8خ�f�t
��8��:؅�M��a�����-m�a�������xD�8؇�e����a��ޣ9�)`�{�+��xO��-�;�	�i
�-�8ؗ��Fv�(R8��<��>��8��|�.��-��`%�z;��{K>�z�����ۆ�z�v�;��d�i����8���c�|�˫�-l��<��{�8��|���E����z
�i��z߁�-	41瀣x��{,��-Uml�8���h��,{�x ai��z��	`�ဘ�A+
)\�T��)�>����N��Q��i/�a'�Cm��i��-5�8�}�|��"'݁8�m�&є�,��h���9�|����(�����;�g};�8�"$gRF��fb(�(+��ܲ��h���-�|�m$g1��i�F�3��h�Cm��iÀ�x$g;�8��66�����8�Ӥ�iڋ|�Ԃ��U�z��8�Y+g�T��z͑Cm��x��[^.�y�y��j3Um���]��,`��i�W$g3��h$g��Zm���jB�8�TI�h�èx�8��|���b�$gxWo
�^9�!$g��}~��Ϋ��%Um�@)ƒ꣝��:�)Q��W�R�y�b��I)���w�Zm��Hm��h�)�t�"~e�)�r
Тg)=eQ�ŚZm��W�ʇ��K�f�8��ec�ٵ[y�h�	2'߻�bz��,��h��9E a����+|��,��h��}��c)�z	����|��$gՀy}��l3�> ��h
�P��{���99��S.��%��t{��{ymwn�����Y
�x���ҡDJ�V�b�h6#&�ym�z���r�_�h��{��g���	5^dȶ�?'�|���hh �ѦN�+�V�xg��?���{Ά�6,z{���ǝU��&�h��!&I�z��i~�ec��{ؚ�fo#{�&�B�$�m�z��?��o�|��#�i���{��ꬂ��?;�|��{�-�zm�z߁bw���a�)^�z��Ɓ;�٥$a�"�{$�8�_Ոk�e�O,€ a�C�-��+|�S�zȀ���zU���Xm���kl�i�z �iB�Xm+�8؆
=g��I+��Xm~��{��>)+�z:Gm�i~��>�k�H�zo��+���	�j��i�Xm�`~�
�>�ec[��{q��=��ց�b8d%�
�-���{	�h�&�j]�[�XmD��{ɚ�e���=@��{��Xm��?��z�Gm���l=g����z��-ރ:d���?m�z9�ˑ��y�j>�-���@�'�zg�\9� a�Z�z��a{*�����m�i%%�jnZ�m�z���a\Gmd�Ձm�z$Gmx�-�
�%O�z��a�條^m��a&�x��ڌ�)	�iS�|��!a8d%)�@��i%o���mo\��c�fp^m��Xm�GmI �XmA�J^�#�(� D��?--o�"g5�Od8
��Gm����္���
p^m�˕y�j=���J=g4��a�
۫Œ=�90�=g��?����	�"gO�j��4@�Gm��[��i������?��jQ	��@mA��iE�>!Ԁ���4��av�$a�� 8�����?
X�{�as�����{]6�{
'�� �$��磕��a�ٙ�
�X%*�>!� �*}	�e���k m�{���a�����a!m�{�o�i������i"��a����k�I+À��w�g���}J�� ��Sl��k��~��+Z��H��y���)g���⯂�y�+�?!f�e��j��c��y��?׷)g��j䐃y��
�O!�a�\mv��݃�f����,�í��"g��)g����k�*A���z3�����kf[��#�)gD\m���(�>!���yh�{�:�im����e�E>\m� [�����܎��(�(��>!9��� �쁃y&��?��{9���$m\m��=F\m�%�{�E)���a%��h��&m%DƁ�8�i�{'��h[�a�{y�<�U��,(m).�����%�G\m	��hygȆ�)�)gAI)T��*��aw�f�r�p����F�1	)��}�2��Т�ec�)lů>+m�a��+���aʹec�	o�}~�x��\mu�a�ecbQ�h6�{�!�he��h���>51�,v��)\m�%*$�a�U�a�\m���h��#��h,�-t��z{�ec�R�a���>��zj�ec9���-m\m��i���$J\m�ecq�&�.�-�\mx�&2e)���a��?5��&�W�
)И�z���/m)J��,0�-k��h�ecl�{�>1��z�ժ�ec(��+��h��Jm��OdY��aB��+.��?1�Jm/��-����ˡ)_���-��-{�h��v2��hm}�7:9���[�E�"�}3m�iV��zh��4�-R;�i5��h�)9�3�-�}��h6��aΌ�_�o��:�i���e�}g�c(��+�/�a��ii`�a�
�k�ecOV�i뀕#7m�k8�)
�ec��{9m�aq���hY)�&�a��i���-$��{�ec��+��f*Z�ϣe�"n�}
��>_��:��z���.��-��z���$��ۡ��i[�s�9�L9��N�����L�}	� ��+.Q-gЊ�-��z% a;m-g��o��i����������&����oL7� ai�y��EhH[�iC�o�)���<s��Ti#<m�if�Cmri#g�i��ao��c��y��f*����S��i+J�iB-g��N�F�o=mi#d�B�(��it�}o��o�]���};����e�����Cm>��{�Rݰ�	o�����}?�^98ENm��w@��{��f;�A�Cm:��{B�>*�c�����(��>{ aj�}�j�^'-g��^9����CmNm�2aܛ&:Dm�a���zf2a�-gdfsG�H�N�aۓ�Sc�a��Ӽ�N�䀦>"��h�L�G
)��w��z�uI�)l�ec׵CmZ���=E�o�֣k�o=���F�Cmh��hJ�A˔)G�Cm(��ߨ��o|F�z_��
z��h���z"2aH�>M�o|��Im�-��=& J�	o��Cm��Т a4'x�c<2a�)z�c��)[�?��Am�1�z�ێee����o�z{ۦ�4'׀(��z|�bf2a���NmWd��i��uˆ�>��ec��)5	�#��o����bdo��b��%�lV ��>k����|��	��8���'���?� Kmfu��>Lm}B����i��zƁ�==fMm�-Nm�&Om�{�&�?b��>.�&�)Î�hPmf>�2g�{vk�?Q�E9Rm�&�c�{��Z������
�9���Yj7f&�-���Ŷx�m��8���ۃ�Zӣ��E9��i�f�)S�E9����Ȁ܁f�ec�4@Tmf5�	+&\��+���	ǘ�?�$�XUm�{�H�z�
)3o
ot�؀VmGmȃ��9<�i�f#g�Amb�����w�p�Wm
o��~��iT�E���Q9����|�E9���[��c�
oZI5Ad��2S]��Q�����e)�)D��E9ďAm�f
o�7��z���i}}���E9}��X�'DY��i��5i���I9���Zm}'�m;�`#��{@���<�b~�'D[�k�}�:�{+��k�&�	�b\mGmng�_��R�{���kcYd�}��'D��i5׌f�,����'D����W��奦�ic��uRA
u�v���	�,.��i�'Dd�e=C�UCa(�'D�ˋkS�m#	��Q���K������}:�c���%��U�Gc�h�=
on��%�'D��8DUK�h%��c�ȋk(���hi�h�
Gm#��i��ע���b��f���-]�-�ʋk��Ca��̥a�i%^�-d� ���-��h�_Gm�ne_m�h_;u:=�>�'�'�D��'D��=܄f(Q�h`mEmo	�Q��bam�>Ӄ�-b�h<f�'D뛌-z�-d� g]�'D�$�h���k��z����bm�z���-'��k��'D���i��>瀘+m.�>� ):r(�z94��hb��iT��-��b�ދk��{w��^cm�,�Em-��i�!uL*�>gM-#����Ԏm~e�zHՍ?@�x�n$�f<Y�h��>(3m~@3�h��z��[v6�,}'�hӃ^�|j�z܀��:��c7�h�����'�zdm�h���;��.�x@�_��%9��-�?���-e�?��z!���C]�zf�%|�!u�FaȀ����ܦ%�!uc��<*�z��gC�����dZ�_8)�>ܛ��À �7‹ۦ��������|g� W�>��Jm�{	�m��ygm�>�#�z��
۫̈h�Jm����
&�����KZ�>��<-���Jm���X��b�����zL�JmimI+b����I+O�Jm���)��?�W ꫭc��Jm�):�/g��!u��zr�Т?	����݀Jm��!uU�i0�g�Jm$��x���?���b���)[�	���<�
�y�Jm�eg� ^�Jm
�y[�`#��E9jm)�	`�:�Ǒ����ô�.k�	f����]�=bUm�$�?j)��c+�mcYdN�i����쁻j�.)=������`j�=�ėUm�Jmk]��^�c���F�\��s��9������xl�j~�
��(쁻jm�	f��fI��ޯ�.|W�	f<�kЁ�"j��>V�Jmn�	f��kom݀�����Jm	�ma�$�p�	fv�k]�Jm��<1��$�?����Z�	f��?��kh�~c���qm�?�Ҵf�Hmr�o>��	b�o��摨�Ȓ�Q�o3��+��)9�����i���XT�=sm�?ރ{�ރ{9v����i�L9g���k�D^d��Hmt�ih��cum�?�.���ii�?�g�ivm)@�c׿	f[�op�Hm�i�f��d���ޟ��i<��!�8�ۢ�ô(�í��}#ے��j�	fP�Hm�-a�X�쁻j�i��y��_����#���'��o�-a(��l�I@i�}#��"��}����
:�Zڣ���Rv��o�M+4�����wm�{F�L9��ax�})��Ԙ� ��M+p�?o����+;
 <2aym�?�;e�3�?
�ôA2�k�bNm�ϋo������:�z�}��{�}�灩��#��?"�Ҵ@@NmP<�?���i�i|�}��J�7��O��
Nm��7�{�zq�*���L9}�i��i�.Nmtӣ���i�7��|���?�L9��^�����a&
=����?�e�⁆Q�g��~c�#�a~m�{
\Nm
	J9�ӣ"��k��H��z���À|8m�{���8^Nm��{k�%f
fo{�@�+)�'w�*���
��d�a�m
f4�i�m�a�ôv�_��,��8�}I
É��x��M+5�Xm;�}����e���=�����8 $)�\NmǘXm��}݀�y�Nma�-a��8w�W�i��Oߖ�G�)mm
f��8WD�%k.|S;�a��{�mNm9�2���_�%�>m�?h��8�)U��zD�,��i�mNm�m
f����� 
f���0E�a
�Iz;)��-��z��^k��8��)d)m{��
fִa@"��i�Úk�Hd6�i�����8z;)�mob���H�i#�.Df2a��?��z'�i��Xmvo:� D��.D#�xґ�1ل����n1?E)�m�xl�.b��kCE�i��8�Ѐ��Z���%{ �m�i��(uY�"g���{�(uI�Bv	ؕ���{�6�i�i#)�:�m
f�)(�i�o���O�)���샠1��(u�m�x�m
f�!M� ܢ���{��H�x�c�(ut)L��(�	�_m�s��)�݀h�"g�:u΁���_L��d�)�x��k���d5�(un3}S�|�5��ko��m}>��(���k�
��ւ�1`��䚓�zT�JZoc�(u�ˢȺ�z��^k|��>���k΂�cR�i��=Ą){v%G���gf}3am��k��y�2ִw���l� ��k��y�}�o��`�g?�F�y���"�i*�j
W�xU�&e�#�I���m�&�i�m�j�$�xc�(u�݀B�yK�&Y���k|���#A�x�(ue��`���N9\m��ke�:���{���;g��`;��=�
��2�m�&<֣�m�&�í=�v%����`Ā��\mc�Cd�}[��k����A}��{l��K�&3a"}�B�j)4'���4'��[�m�&���d~j�y{��Ɓ۷�m1DR_}>�&Ԁ�i�Gо�;C�m}��;g��)g8��_�Ut
!g��/���8"�;g7�p�m��ʫu+�m���(��x�)�/C<�{o�%�m�&z��z��؀���転��`e�&$�z�	;�����*|z��&��&�z��k@�&��A�J�%���>y�&���e�p�B���N҅�`� �O��m�&?��`���h�zg��%���a�m�&���>�m�&F*!��z��d��)(�z�ݰ��%Ɍ�a��)��-�m �mf��)�`#
O�7�)Q5X%����)� ��%�Hg�{s�&��z��%��)m�
�Of\��z��{���aЁ�ilj8�ɑ)5��i��c�����)�1�{`�v���ab�>�ۇ��
*��-%�))1f��}�
�{�āVT�{����%.��a�ۍ�%9��������&�{�
��ݨm�{�XfeY���)5�%�?�{7�8ѩ�)�}p�z��
�m�{B�8ѷ�}��zӃ�>�%��^9���a����}���aZ��i�FҾ��>�m�k��1{x8�m�k��������)K��6��`	��zl�)V��aO�}��^9��)���a\Um��ڨ����`�}�{���aЊCm��)O�޳��a��)Q�^9�m�{��)�7�R���&��{�	�`�]Um��%��1n�`��kЁ�bZ�(�O�{�8f�"'�/�-�mf�D�d�4��?�{%UmԀیW��|��Cm��61��}r=d��i�����xdρ9�j�	ofv%Ђ�|��k���^�Cm5ԣy��vc	��i܈�i@
)i�-���dUmѬf=�PҩCUm�0�@�Cm���=
)��fk����8�ʈ�k�`#�m
)B�8���2�-���Ck'�3�^�Fh݁`#� 
)�m�-r
,���5��)&0�y_UmUZ`#*��k��%|�

)X�km��FhR�>�oy�)�*`I	�V �?-���m
)"��i5˲��|4�	h�	�	��ݘ�"�#�a�
)��?�8�����<����	o����(�?S�i#Ҁo���3{��|	��m
)���i���+jJ�a�c>�Am9!��F�K�a��a���6�-��{~Q�6���?��i�m�a5�����偢}�:Ja
)G�a�m�=$
)m�۾m�a)����-��2�m���`��<�
)���m#_���K�@��`t
)��`ף�
)��»1
)��)I�����&��b��ft�z9���Am�z��{~bл�Am=��Ձ��{74�z�������5,���8D��>&�y&��,��Z���`��?���A���&��
)�m8D���`�� ��>����e�&�`
ݴF� ��!�a|���V �?�M-���`���m�a����&I"fE�CV/�y
���f��a%�Xm�Xw��`wtf�b���&���aR��&:�f#�b�a�=g�m
o,��a��y_�"��Xm7�kÀf#�m
oG��`�)KvD��`:�)D�Y)B��`��k+�Xm�<�k(�i���`%�Xmj�Am=�E���X���%^�h}��n�ekw�����)����Q��U8D���`��:�)�m8D�m<�c�bTCxC��m>D%�Xm�f��>7�k��2��8D��ۀ����Xmd�Ո7��&�m�z.�<K�Xm(�	f5�����j;vR���i'��Yj�:�z��&��{��bT������'k��XmS��&��{�6��ffiZ9�J G�4Slj�
�
o��f�ڛXmא�a�D�R
o�m�{�{�~c��X�����,D���S��.��{��f[M-t�<�m�k����y#5=�m '
�)�Hzo��'��-���-ʇ)eC��zm#��)n��x����&���j�&��)a�Em�����m)rWm�h;'��-�)��lZ
=D)���9�ի����z�	f�����-��-��	f�;�߂?a}I+sEm2����)�E�������i텞-ւ)y�i�h�g��)�i:�}Em�G�a��u:�Em�)��}�����}w�4������`���X�y0��y Y2��`�mEm�
ף�
 V��Z9�;�����{���;��{w��}�����-�CZ9�摄_\mr��-O�(��}3�N�6��?#�?ax�}��`aKvl@+�͞-Ȁ���X�K���m\mQ���)�`�m\mk�}��-�Օ\m�}���-�@ףw��
�a��(�θgNd�\m������)�m�&��M"�Em����}e�$\mؚ���&����<D�����&:-D��}�m�`m��O�\m)?D�j�aD�<D�F+E�}�c
f�i#��}��-h�i��貄}��fӒB�-J�۸�%�&�`��a��ވ��Jm��%o�Ѵ`�i�z�F+h����X�'	2N
f��i8��	یM"�J\mJ)��}�
f���]��E9��fw�JmO:�"�M"�m)�'��;\m7	2�	�%�&�����%�t����	�a�<|�X�k	2�m\m.�)eC�3	2�)+��>nƮ�Z�E)_C	2��-�-a��)F
���)��{&�>��a:	2&)��f�o4�)ƀ!#�a��k��)T��c��)N)@�(����m�h�%&`���z�f��kH�f*����z����<r�)��Q7:��)��c�Xy;�-��l�[�k�]�^�kG�?��	��9p&�%g�)�]�kE�)�

f�ހ���c��c,�{ބ;��&)�
f�k��-O��c�Hm��c&
f�Zm�HG9���c�!`2	2u���	2��k�
�L��Hm.�)������<$G�Ł�b�)o�)Ȁ��ŏp����@}��)]�^9M��z����ހA��錀�c��f��c���ɠ�f��II��$g��&��^9�ˣ��)�
_�����)n��&>*|����#�kT���a�x#4�X�:�i#g�zB�^9|��~��k��d��&_��,��c�i#s������k���&Ł�boˣ��z��Cmv��K'	c�}%�kw��Y�����4~�橀����N��c׀��o����G+k����mNmv�����&*�F�7��&@"Q�^93	}Ђ�<"|ew�4Z.��cW�^9��oR\Nmg�?�����2j���?Ϣc�Nm�	8!]���b�_+$� *�ߣ��&3��W��
|eȃ��i#oմ7@"ی�&�m}k��&b���͆?'�gؕ�'���&�K����&ki#5}��4�Nm��D��7��&@Ӹ�	8!��/���J�XGJ�X��ˣ��{���&����+�Y�X�z����@�.�	����&/�CmHK+��&PD�z"��?����?Dˣ��U]b���_+�>�.���������]�H�m|e;��2�j?Ɓ�#4�-�|e��~��z���m�z���a{�&��{LF���f���f����́�%��#O6�z�e��>���z�ek����z�F�L
�����a�R"gf1$����a��%)�"D��-�ST�����a������5��%O1f���a8��i��� �&`w���氇-���%V	���m^mCd"���Q;۳��%��c�z�^m�Z��mf+����Bv�
^m�m�i�o=��a���a��Bvh�_�b/.�aщ���:ub�z�3�mf@�����zt�<��>�b
B�z�{����VE�c:$�z��H���i������D�`�a���.��s�������%mĭ���%���aՂBv�M���a9�z�a2��k%ԥ%���:u���;d��%	�%���(u	M���%?GGm��p�!D��k-��Hی:u�
����s�cem�Nk����ce9P�	^m���a��m#!���|��舙m#���kP�a��ce�
�m$��bU�净��W��k(���ce��m#����ce�oY^m�^m��i!
�b��cec�bs�ce�
����k:�a�ሀ���Cd��m#�}��k��`���kq�ce*���Cd׹�k���i8Cd*!
�'��R��A�W��k�m)%Gm;�ce���&�m)��$�'��ce,)N̉k��m#:GM�m).���e�ط���k.��׊��b)��VbZ)�����`w��k8����"�[�mGmf��!D��ce+�)��k%B)��`5�CdT�c)F�)o!D;�ce�m)��ce���$����)k�ۄ���m)s�ce��m#�"�O)�)�a��CdR�;g��ceHˉk��m#�5)w�ceL�8mQk��m#�m)�ʉk��ceU��k������)����k*-�a���4&D&�"��;��a��)m�>���	��%3�)���|�a��7)��aoˣt���ƭ>-��a'�£�m)���a��?�o�W^�m�?�)���f&)���>&��%�$n�){�`9��%e�:��yg�ar����Y�m�iJ��>(�?�)��P�����fB��1��-
�Qk\�)R��an�i�tI�0�.�/o9�	��̴�a%�_��$n�}�
�>�����?�}ȆFar�)��F��ȃ��=��6�-Ã}��.$n쁅ȅ;����n�&���x�=n$nc��n-g-o��}�o€£A�a�#p��	�-��i��Xds�-�6�[o�}�T�k�t_�Xds�-�}��k��ۻ�}���.��變�L	'�k�a;��oe�s��^�-�}�����-�������a܎Jmc�i����o�o�"f�΁y�@�k���a
x	n�k�ԣ
�}��LM+�?n�-��iUB�k*���gk�.o8�	f��kƇoX����.��L��i>-g?	�%�c�Ջ�i��o6�}�����}|���Q���i��ka�}��a9�Z����%D���aЪ�c;�	fvo�׫c�j?��}�o��o]�[
�}no��C�.���W��c5�5z�k,�}-on�-���a�c��k����T-a��i�os�-8��a���z���Eds�!���-� �k$�{d����)���-g�.�-xuIV�k��)n �=�k�o�B�)�)+5l��k	��z�0�)�{���a�)���¬����%D[��� ��c��zN��*��
H����D�&���c3����a�������?�I"M�c@�)��(l��aВ)��?��zV��i
��c�&:��)�i��a���b.�&
��	��	�U7�)��AmY�`��?n�{Qk�_|���n�&��&�.|��Am��_�4T�`@ԣ��{�Qk�Am�)m��.�HТ�@��F.�`3��)n�'�Am�Am��4#���)��<ˣ�����z�?<�J)�\����iI����}�Am!���e: 37�Am���:�j1��&=�Am
}
Nm>��<�´Q71�7�ւ)Ӄ&:ه�`��Am��K�&����k�)�n$�{��t�Xmf_�Άn�&����'��ͨ�`v���p"���5Z#(i#��ۚ%h��(|)
f�i#À�ܣ	ˣ;ˌj�ۢ��93
f���5�JdӧAm���(|u����8
f��	���Am݀�`���ꑓ�����`���6��E���	
fی{~+�D_�
fNm��c�cʈ��)k�XmQk
f �-� �c:�)n�j?�j�{��CX�:H��
!�-���i����
f��	o����#�d����@�)nl-:۵�-���X"]���R-��2�粇-**|)���8"�-ǔf��F��U6�ax�)n ���H�f�ʇ-��)n|T�c����-�����€´���lIg�j��۠�`��Xm#�Jdpˣ	���d�c���-��Jd�����f�/�cS� g��tq	�fP��`$n�c&�6뫁ʌ)���%�"g&n�a���`'�f��ܹ�f�Xke	�aÀ g;��a���z�UkQk�C9΂ g;�c(�-m�c�ˣ�
f$��-*)�
�a-@�c6ǘl��`/)%�aM	�cO1�{��)n}��a)���ӻ�-Em�S7�p�����ZEm��)n����k�*j;Em��)�E��-*�)n?���+�f˟�-,�-Y�l1-��&4�f�� g�Em	��i���Ȁ��z��i)��܃iV/�I���	�cX!Em���.�!u��z�F�%��/nEm���k>��at�c�M"�!uwVg��m#3������k���)����M"4��&J��{I������c�m#�
\m� g����I��4ne�f�Em�Em�ԣqu�=BNd��!u���
z0�f�c���f�1��a����;c�T@+
��`���)��ܗ�!u�M"�>�-�I���&�Nd�
)�����a�#2n�-�M"3�,D���/Ё)��#��,D�"n��]��g�\m
����&��`��db���F��O:?D�����c�,D�
\m:� ���?���\mc�m#“,Dq-n�\'D���c�-n"nV��ݙ�!u,`*7��<��,D؂�k%#'�l��cqE�gI�4�,D���cV�Xz;Xk5ۀ�?�_"k\m��x�߁�mc��$�ne,�=���Q0
o��	`�Ov'����m#V�,D��=+J-n5-n��^�
��$���\m�
 �ne��%5n-nB
Nd���/�Y
o�-n�J�#neH��6�zT�Bc�Uk��
F���xȄF9��@����O$g�
o����{�D��<�<D�<=�%*���#�$g��,D�4噆�c7�,D
�a��Uk8n�k�\k�� ��9��#��ʌ9n�a�$g^�zƠG9�*�`�Xd<?�a��`97:�Uk:�,D	�Zm��aB�G9��_���-n�$g;n�a<�Hm��G9	�g$g=n�`�j�������ꮢc(��)>n\?n�k%
o?�ܣ@��zy4S+�`�mwf��c'�aA�Hm�`���zńʌw�`=�?��c���QD
���!DL�\kBn�`J�Hm=h��_C�Hm�e���`*��]�Hm���c��zD�cEn!D����ٳE�瑢c������&�ȕQ�^9��`>��z���ƨ��c��z}>��h!$g�*|�Hm �Y��k��	�� Ɓ�Sk��r��iF�^9�\kG�cg��h9�:����c7�b~e��h�
C�t��c��x?H�c��ހЁ�������I��h��d[٩cJ�cV�`2��&�$�HmӒ�c6�-Kn�a��/��hL�c���c#�cҥ�c�c��ո��cM�}���zЁZmȀހ�{���4Z�)<�ܣ�Hm���zd��h�ũc���z?�Zmލ�h ��ź�i�����ހ��8��)Ł�bN�c�}��h��£(��Ca܅�hX�W^)���Um��CJ��*Xkބ{���iЁ��c�$o�ܣ�%u�k�͒��������0]ٴ���� �p�裯�`�����h'��=�⹁�{�c��k��b��68!� �Q�� Щc���ht�ףٴ��cb��_;�[	2��h��?���N���Wj��Ç��w�O��hP�c��k�U$ލ�h��g���":�Q��htٴ��ϙ�%����dR�aSn�c��?���]�Ѫ��|�Q�����	r��:�/�����y	2�K$nT�\k��#Q�f�L}��\kg�|����	2���a��+�=�)c�|�Un�z�\k���d�z�|����_������?������S_�z�K�=�|����������%�'!�W$n_3�z�'!m�K"6�\kVn�z(�'!%�&�9�ל��(ul�.@	2��Wn�zr�f�Xn}d��	�>ٴ���À�܋�Xm�Q�zYn$nl�c�٣�����y"CdL�z(�����:ukF��(u��Q�o�	2<�n9֜y	2Zn�z[no�%|���:u9��c�c�#}��Q����d����\#+�h�����7h)�'!TA�c��:u�aҀY�M�-�
�cǴʌH�(u���a)\nٴ��|���a]n�c��Do8�m�o��)g��#Ȁހ
�z4})�?^��a�ɛk7�{_n�z�d�z���a���{=�F�`�{��c��(����aan�z���{*���bn�zeh�c	��{۪�a�
��Ȁ�^c�{d�(u���F����a��(u�:�e�%DR��aÎ(uf�{��aւ�k��O(��y��{�ћkg�Xm���{
��gg�Xm4�g6���abXo��0�hn�c��e���{d���tin�c�ܣj��a��oٴk�otcc���((o�o�-q$��Qk.o� ��co�N9ܖ���Uk�#*�Ɓ�@��- ��`a��e�.��#��ln�ctT-6�\kDF�5�)���'W��aؖ)���{�X^߂�LZJQ�)$I+��)g>�ܣ���f��l�|���aRI+3��b��zd���m�)��n�ko�{�ۇ�gѧ�ip�k�ʌkr���%Dǔ�aw�x2��*7�i	�)���LJ�����X�V^ �)S������P6����iϋ|����h��-3*��z�|���h�
��Ё��=Qk�V �=9��h��~/���&�`8k�镼)��Y�o�|�" �)" 3y�-3�f!��h����t��z> 3;�)�偶���(�
.�)� 3q��h��)�|��&J�^��h����).���n��hQW9	_�)�F����h��
2b2�(,���Xd� 3��&:k��0��h��/ыWբ��o���h����0�O�� 3r��hs��h�I��\k��hhL6n,#D�\mɑ�ha�{�Xd$�-�����Ɓ���F)h�����t��h~�F��	��au��h��{��	�	-g1Xk������h�L>�{��X�
2��{ǒ���h$S�cvn�a��{��hR�-,�ce��hy��hg �
��2�|�i��h�5!!��F����<��h�4�>��z�`���zy��hw��hxn�{yn\m��c� 3��I-�)nU��z�bzn�{;�a� 3������ӱ�	o�a���)��۫��^{n�a��{p�-�ay-g္m뀅#<��U��z����h�3 |n���	��::����P����(�}��)?�i�c�}�->�:����~��h:�uՀ0�j�&�*dw��0b��h��i��,�������p%��hn�{�
�bo_��n�i����=�{�dw���`-g�iD)�-�=�z�Odw�e�a��-4�i�f%uI��"g�"�c\�-V
�b�a�W�i0�!�c�b�_�c����5�08��<�X�a��|���Ukݲ=��	� 5�v-��)n/��)���'�|�/��)	�g�b��W�l�|�����0i#�I�x��g���zT��4�)nƀ�z�
ۀ�)���PY�����h��)�
g��)R�#upL�ht��ִE�m�N�-��a���h �#u��>
�i{��b�itcc��>��#u��nc��8P�Am�<�k�ہn�z5��/�{���z��i�C�-܁#uY&�Q����Amw��/	�i�f=B\d�:DЂ��2�8�#u��f+`:D�)���/���h�ʦ�f{����6_�5ƪt�Am�#u�n�-���%k��?.�����/�\k���?(��/�(�-,�\k97(��/���?�i#������{Q�-���/�"|x�-�n:D��f#�;\d��{��:�ĉ�/��#uH:D���{Ɓ����?�����x��#uv�崄n�-�+-n���/��#u�y�(�/�'Dr�p�%�?��#u�{��#uR2�z�,D�6�+��{�n�z" �Zd�#uy��{9��k��?S�z���_���{��#u��#uU-n��:|d��^a�Am���v��/H�;n#��{o�;n_.-n�X1d2�ۜ�2���/?�-ه��|e�:DQ��Ήn\d��\kQ��a�n�-)��-�t�\d�n�-�� ŋk%J%�zo��?�áO� 7D�|��:f��oJ��A%� ?�z�R
f��z�n�z�,D`��{��-?	��"�cwH����{��,D�x������	=j��H�;nK/�cތ&�`$f=��	��Q��;n'-f���{�~c��P��{�R�c'���Q�c��1�� {�c�g���AИYf���c�cV+�zu�6z���� ��zg�|����� ����Ɓ{1r�ܣ�n�c��{�c��~1�+-n[�	=U�+�|�S�|���z�'�,�Y�/�|�hI�cgEm7L�z/��-w��δ�z��ze�� =���!D�ٚy�����~ciԁ˺�#�iL���z�\k�!D��o����a�o4�ze��;no���L�)*�iw��>k� R��iVȀ�iQ�����J��`��
�)+Em�n�c���?�n�c���w�	��f���k��:�cz
f���h�2�cm�\kV
!DZ
�ԫ�i9���e��z�{���i���A��i)��z���?"�ۚ��i���>tz�|�t�:�#��@��ii�Y틎�k*����iv�!|�毛��{K�iw�;F��~1l��i���Ӟiw�X9��iQ��i�݉k���i��J��n��nX9S�Uh�\kڋ|�]�ze���i@��k��j�z�\kC��i;��-�X9ۅ�k��>D<�>��i�K"\}�X9�!D�!i']{ӯ��iҀ|�&��d�V��i��>D�":�|���`�nX9G
�O��>D%�����{e�����-�!DQeX93���e�a�\k��f]�񷍉k���{���z��'f�⣴��i�|�5��k@��k���i۫́���i��?b�f��i��،��4霉kۆ>D*��i���)��k�:����
��Շ_"���i�ш8�K"��uR{��it����|�b���<�¤�.ބVҋ��_�������p"Y+$��-�nX9Ԁ��":�n�?���S�|��������J��Y�_����-��ܽ#شF�d��f���屋�yցX�L6X9���	�4�6gI�f��{���	�#���ޗ��fmB�{?�?��f8����j���>D����f����n�z�n�{1�sI���ܲ�>D#�?'���Ӛ6�\k��)��Zm���i��c�Fa\�ȃ��d�Zm	�{����)��Zm���{#Y�qc>�:�&s�{X�*�qc5�Hm.��c
�Zm�8��cc�o�.�-�n�{��{�Ĺ��HmY	�{׀fәOm��Zm�3Db�k�ŀ��d���c��ZmV��ܑ�Zmه�&"|�@v�n�k݄E°n�-�����-��i�1�i:G�kpqc��?��-���ܥ�-�qc��i��i�i�����i:��'Hqc��i�n�{�n�-��i�n�{�����?��ܫ�dk*�i��c4)�i�n�k�+g?�-Q�ip:�z�n�{�i�ҩc��i]~e�n�i�����c<D�۶뭸�Zm��i��+��Ad��%D��c�;�k�b~�Ad���=��cg� ւb~
��4Z��ctZmj�AdN�%D� r�n��Hm$�{�8!���X;�-��c��|�*mwv�m脢c��I"������-�e�� ��z8
qcI�z|���p�`�i����X�q�pqc��-�i�� �4�i�˜d��c��zo��.�E���cD�i��c�i;� T���/]��� �
�(�߰�i�� N�s�d�i9�8�z�f�#�/h�,!��c�iCߖ���-ҀAdi�/�y�UB��c����5��.�����ʎy}��crϣ��>+0:��c��/�3:��f����|���+�n�/	������ccԂ {�-3����nƁ���j?o��ȀÒ�	��kjc�\dt�k����$g4)����A���,�/Dv,�/�ߪ'��cb���bDD	�	2�{�h07^m����4YI$g<ODD�%�a�o%7��&$g_D^m��i��cv��1Q�w�9DD8t%e���ۨ�Km�n@":DDQ{��� ဠc�͠cX_DvD�̴���|��Ø s=�Nb����nDD�:ut�=ٶ �>DDЁ�#'!@"�������w����=�4S�Cd���
X��DD�Dvf�a���i��(|3	i#b�����|�_DD^��ktcc:+f��ݿ���]f�=g��:u�D�c3f*�(|e DD#�c��i�Ģր��5	2�n�c��i��?��߂E�f�+�c��ۥfB��'��c�;gjM�$gݬ�-��f�EdH'��&!�c�7DDf:^m�Dv�#DD<��-���z��i]�DD���zs�;g���I%�d �ay�h��;g;$g��-�	2�?^m���{�Ed{	2�nDD�?�c��-��z}GDD�-�n�a���a6�j�*�c���{��	�KY��+�cX�uc���z'�c���{h��-o�̴���io����)�� D"����ܘ�cY��{X��aӏ�u�n�c��;g�qc��tq�n�c��uc	�cj�{.��z���{	�uc��c����P�_H�uc�=g;f���a�n�c1��X�:�c*f(��_��cBLjy��uc�M$׀i��-D)��]�_��*����$�1	� ��z�wd� �c���a݇-��p����{��p���cU��, ��(��z�y���-��z����a��-��,��a��F""��zÇ-e�h�����,ۯ�a@�hV��{>��{3��ap��a��7� j�N���w��_Q�aer��yxj�cmqc��k�X92�[4*#:*��n�c��aeQ�����i�։k)Y����d�c��ho%�,�c]����kKvc��k����`w����n�-j������kU��,��ae�o*ۧ�ae�f����� �, :���yJ�P�
��@�n�i ��?��F"b�iS�tIs�ae��t�$o��.kێ>ucģ�n�i��k��}@أB�:|��m#�� �T��a�˸����oQ�{ڋ�,DO��n:D��hƁۀ�n>u������hv��ց!�8�i>���
����P�xc�-:�?����i!?:D��
=5��(�?���hh��>t=�:�i�2xc��h��i9�zu��kl�Cd{jc��?g��kL�ae��,D:�?�z��Cd<�:|g�?�Hd�n�{��z��m#�;�{�jc�-nO�ݸ��uc}��k�Iv�n:D�����?%�m���h�37�a�\k���ad��z(��{��>�-���i,M�i�nxc7��z�,o:�i�1�z���i��X�{,�i�`��zԼo��)g��zV N�z��Xd%�Iv9������p(��zI?�i��z�@�(�IvC�,D+�?E�Iv�{.��z������;nl�m�� ��Iv��z��aɐIv�ucv2m`~��zN��z�HdԂ �:|��h��z��Hdg�a8Dc��i�	dw�����:�?���i��{��?�ap�)�aမ�8DW	�{9�w���<���S;�@��{�n�z�8�{uI{�uc�f�z��X��n�/���{��i[�F��n5u=Dn�g5u��Iv5��h{�uc���c'�k|V����
)UE5u�8��z� �i��(ree�K�k�5u3 ��������uc�eed�߁���i��k���a��
2!��i������Am�g ��Am���a�{�O �Amo��335u� :�Am��Cmʏ�c�=v�݅��io��)���Am���ilftڵ��i���z��=Cd�c9[����i�=:��ܟ�cԀ����i:>|��Amz�i5
fXG���c*��a�����'��z��ҕ�Am��"n���o��*���_)�Amܕ�h���H��%��/Ç�ĉ|c��AmZ
�أ���i�ߣj�,���c��iC�>D���c��(�ee�5u��Am�	�Jㄶ
O��{��Aml��-��f#�#5u��{.��z_ %��t%@�f#��{
$>|��k335u,�f#g��{��f#��cR�>D�i"�Am���{�aAI �xc�
�ĉ|c�-gI�|c� a�x���{0j�a��Amh��i�Am�Cm��{���m�Am��f#���i����>D��Am��cu���n-g���-��?������%�DMv"��{ ���i5����|c�MMv<�י2Mv�*�a�n���Mv�F���),�f#�=�5C��	o[�/:��?����
ϋf>��:��'��2�f�ao�	o���a�_������z�T�m��D_�;�x����{�-�h"C9
���Łf#�	o���{��_�c�	o��V�ZG��+�f���fo�h߁ucH6�z�c'��s����|co�۰��{�z5�	o�|cŁf#o�an�>Dt%�����heo�h�3Mv�he��a3�|cD����h��he�
�L
���G�>��{��he�xc<�oMvY�heh	 � �h��|c�uI��)o�h���܎�y�K�o�-��h]�5Go�a��hI+�a�Lo`-�z�.Em/�|c��he)�zo�h	o�-�XGm��z�Em�)�#�z��i
oEm]�zo)ݐۂ�h]5�i3Ӛ���:Em>��a=�he�0)�����io�i��i
o�zk�ۀ�c��-�A)��(��{�'Em��-#�iS�|co�z�)��i�EmY�z�A�c�WmT
�io�z�dEm�2�z��-�,�z7�z@�+�Ad��z�Y�iĉhei�<D6�h"(EmF�h�(�i��hoEmK�i��AdQ��{Q�z��it%��{w�M�{�XdĂ�{��z���c�C�|���b�t%Q3�B�{��g�F�'Dqcu�v�Q��{o�-O�coEm�b�����)o)
=no�-@��͢[X�io�{y)��-���oGmo�c��-(,�i�$�{���zn2Em�͢��uc�Xd='��'�z.�)o)������E^�b?4�io�x���'l�o@㣚����	a�i����F)��V��6�)4@� (����B�	�)oI+�w����W):��{��+5�H�'D뀼���	`�

o��}\���Ǚ�Ad�I"��4T�Q�{"ݎ�S
��f��.���M#�{��v��ݷ�����n���.3�*��:�xӇ�G9���.��G9g�o��'�}o o�b1�uc���}��G9g�ohƃ:DD��G9�Ӛ� :�m�}g�	f��oU�}��G9o<u9%㣙$g����2�uc�xc;Dv o<ug�xӾ{[ADD���EA<u*

o!oDD�}Àx�b�nc��G9
�ZmD&<uNDv��Hm	� ����B~e:DD�
����<u��Jm��G9"�c�qc>�E(������Hm#�Hm�8Xdc(DD�}$oDDN�}l��.v��i�x��*|��.��H�:��c��i_-f��}��i%�}X��WEd�HmXyB��偕�&�Hm.	2�-�{�
��}�1�&�ի��id�G9~�Zm'�}%Of(o	2<�})�Hm�H�*�Hm+oEd����,of�-��g{�Ed-o=gˡ}�=g����(�£d�i�<u
nEd.oDD/��iֶ�c�{0oDDWDD�d���h�)�)ŋx8�C��<uUZDD�2�Ed���c1�)ϣ�cy��'hEd�DD�m��ih�ǵ��ޥ�Hm� �p��f��Hm2�c�}r�a��)3�Hm�
Зأq��g��c4�Hm���i��Hm}�f2P��i��4Z�o����5�}���{6�)�i�
�e�}���i7�cщ�&8�Sf.G	2Ю"gaȹ�g��i��-ϔ}y�kv���	fȃ��o��d��a�\d���{7��A�c���a8of��-Ԫ �ӚY��9�O�Ed=;
f�<	2>��
1Edv�J#�9�)ŋ}i
f�)��i~��a�)Z� g� _����i#�)�����F"ӏ�����c:
f�)���h� !��c���z9�}h�)〻c���z�9::� ��)o}���� �>!�i#�&ƫ'�ܤ>!Si#�����;� ��)�!
f6�?3\*�6� �9O��&6�?3h���o#cP��/�!��W$C�4�k!��C��c)<o)Q�-=oxcT$^m( �>o�-d
Nm�|c���%t%?�"gh)@o�-Aoo%�uc���a�#֢�P��Bo.|d��ah&�-C� ����AA�i^�-�/Do:D:D���a��i`�E�%
fq�c�E�>!�FS1^m	��a�F�?'=�-�-�i�o6��k�T�f"�i�F�-���)Goo˂4@j��k�)cQ�i,3^m�g�-v��(76�i[P�-�B�iǓ:|�łka>�-�)�羄�kB:Dv�J�&�?�p���Ho�{�?3�!��"��>k��d}���Io�i���k?�{���xc7�z�%�iJo�zb�-�m*�����a�{�5�-��?3�;�{�GoK�kF:	;�����L�k
�-��-��{�uc���MooNoWd���zOo�zL���X����?���JPo�-
0oh���{t �5�;nQ�z8t%Ro�i��{S�
2�I�^ma�zz��-��z%T:D��?��Iv��z*����w������ �c��?��iTo�cr�����Uo�����V�k��Iv���Wo�c|�RXwh�����蜅�?�uc�*�����D�,Da��z8��-��c�W�yO�z	_�;��k<��c����#�c��{��?��|�I
	*���[5�{��c�F�{�Xd;��z{�c4�cv���Ӛ�{Xo�z}q1��{k����zYo�{{�Zo���$�{	�1[o�cl�zR��d��zl��
	;�F���1D���-Ǖ�z݀N9�'n��?\o1D���g��y��zؕ�cƒ�i��a1D�֣���z�և-?���]o#D>�uc	f��z)G�co���Yf!��-� ��Cm7��>O�5b^o�cm�9��Y�1D:�Cm���2��9�1Dc#D���6f�m�a�Cm�)��k�Cm_�)�-�c��mo�c,:���_���a���i�)�?�c%xc��-`�)v�&�R�a܀�+��
}��=:����W^5>{a�)�f����"��:7��g�]�'ㆅ-;�o;�x��Cm΁���)�f��Cm��c�>|b�'B���А�+�)@��<X��'S��z����>Db>|"*�'��)(ov�繀>D�"#D5ob�)c�}	�	ۨ��%��)d�Cmf#D�'eo#D4�)P�Cm*�|cʥZd��|cfoxc��}o�>{Տȣ*xcg�)�Cm�|c���0"�}Y��̑p��:�����h�-���+����A�)Q�)Q�Zdi�)W�>D,-g�Ij�)��Cm�Zd���i�t�P�t��'�	o��i�Y�f��m'�︫��L4�)>�ib���).�)5Ӛ� Y��a�^"k�	o8��'m������e�;g��|c��v쒜-��|c��x�^����a�I"�f}�;g�j�x��E9��c�>|b��o2xc�)*�>D�;��j�d�)W��-a�E9�>D������-����0���-l�E9�&nD�Zd��}Ҫ)O�ya�)����m�E9� |�f!ؚf���0�ˆ>D��*Ç�ZdD$�-no
)� :��R�)9���1d)�=�[ :)ȃ�ƕ�)��I&��?�TۢB/���Mv�	��4�r&�
)>��}� :eoi�?�	o8Cooo
)�G
)�]����{9�&���;�xӖXGm�wed�p�"��}y��m��aO�Y�poUm%��98Ι��T��%2��o��Т�[v)�i4�}%+Gm�.o=�%i�Xd����Gmq�a��-m٣+o|�E9W�?r�'Dsoot�E9"�?M��cu�E9��p��?�#Gmv�cw�cf�|c��'D�0���c."&n�cWo�&�k�4��`�'DxoGmy��azo�{Y�'D���a��Ad�&`���a{��a��%��:�|o�kc��݅�'D���cဩc�
)ĉ|c��ڢ}�'DC���f|%��a\�'D
�i���ۅ�cW��c,�/T�`8��o9�~oo�B"�B"|��
��/cL�'Dooe�Jl��/y̻c���a���aǍ�X�׋ކ֢�Gm�	�JӠc��<So��&��+g�oGm�n��o�-h��c`Gm������'��'D!�l��Q�
Gm��"n��'Dꫩc�o�k4��c��}\�f#of��'D>�c��/H�OQ��ݴ'D߁�c��c��'D�
o�����a���a�ͧ���a3P���'DF
oS�Ad���a%��&����	��oځ'D��aဩc*�|c���a�|c=�Y|�}m�	f�':瑉d{�
o)
o�"7�Ԃw��'��j8��	f�����^d���)���߁|c/
o{��m���o
o�f���p�	f	�k�o�k.�o��c�^d"��}9{uk����	f,����t*DN$��DDɀ�/8D�o\dÀ����o�o��"n�oDD�@8D*D��	f�\d9DDg� ւ	fb���PH*DE
o���k��?fDDL
o
��XV*D��he�DD���ݑo*D��?�*D��Jm�|c#DDt
��w�ze��T��Jm���ʉ8D�ze��i�<
o(!*D���i��iT�ze:�Jm���$�&��)g �I+��)�):L�ze�*D���{{
�Y):��5G��=�he��-��)w�FV�c.���X��Њze.�)�oDD߁�3�Wmv�_�J�c����o�c������?���i@�JmWm*�c��w�yWm[Ed{�ۗo*D�Am����.�a��c>DD���i��ucb�k�+Wm��u������+Ӄ�{_*D=�����{Y)����a��Xm�o*DQ\db~�o8D�&*DQ�2���a
�&�oDD\�鷆��
DD�ze瀍?o��?��Am.��ݜ�ze��Jmr�c��Jmk��?W��i������{͆?�"�c��{>�Xm��v�l���A�)�cN�uc��Am��ze��Jm���~�J��	�c��f��c*�*D���i����>D�c�uc�Y3��{�X9�Wm��c�0�c�X9�Wm�X9���֞�{�o�c��cl���5��i%eWm��i��c��{�oWmЁ����a�����c��1�f�o�c��1w�p��'��c<
f��o�'��'��ۂ�ad�ai!
f��+n���oX9@

f�����oĂ+n�X���{j��
)���{�ɵc*����&[�Am�1)����,<�,��cB^)�5�'�Y+ ��c��������%��)�D�'���=e�x)L�uc��'D5����T ����C��'��i4�'D?��v���
X)�
�cSkX9o�cP��i�o�c�)"]�d�aX9�QNm��{�i�/\m�Td~����i(����d�{�������oNm�Nd~Jm)��	Fhڊ�S�B.|�oNm��ݳ���l)�����'Dڋ��k��oO��|9Nm��i�5�'%�{�У����L9��Zmsd~�����`���۲�ck��<B~e��ii��c�ZmuE�{��Zm��<g��� :�'����£L�[M.)�o)�0
f���	��cO�.D"��c��{Àf��c:).|�'D�Zm��i�it�!�Y7�{b��i`�Y�	;���d���ܒ�ZmӐ����Zmu��i{�{�?�T��i��E³o�{Q�'D��i�����GEm!\m��Zm��ky����c8=�{#�<D����B��i>d~mEm���m�9x�Zm���i{���E\m�o�z�:Dl���>�J��oNm��d\m�3=n�	�{�� ���z�9�	o��jX�˘��
�L5��<)�<D��z�� xW�{�8Ȁ��o�{&�{��4U�ZmY�.D��c&;u�
�_�Zm��c��<�Zm���~�q���a�Zm���|��.D(�4ZޝZmn�.D�*�4�<DS�i��7μ�<D9��݀.Dʈ%��}ܢb~�3D�B��}�������as˕�����y�}�=n���8�9��{=��I�o):r��oR���_�i�cJX):��{o�%i�}>�[p"�{o��k��_f��ɢҀ}��\9�9D)b~�1D���V��-�>�-��f*�fЂ�z�ƪ��˻�i���<1D=����-���b��i��/����i8���[����}����ij�z��ipf���'1:�́�'���_o��<��f*�
1D���'��9��c����F�Ea�o�iH��'��i��+�ʉb�k�1Dƒ�A@����#�	��"�kD����c�o<�9��b"�c)Gz�L1D?Ef���'���c3or�k��c]^m+����VK�o^mCީc���'=��W�FL�i��k��cR�i�o^mԞ�c�$g�o^m@��':ۢl�oc@"9�a�g8GT1D�o^m�)��n���'�f[��ꐁc��$gP�ZdU��c��i� f�^m䐉bO(�N�\93��k��/v��1D�^mDefo�o�o)�[1Dܦ�k�X�^�aӃ�'�i��i��Hm���k_�Zd߄�'�W�i<'�aA�i聛b��i4�Hm߁�c|zT��c�^m���'�)8� c��c�	2�c�a(��'��kuĩc�Gz��_���;�_!D\� �����	2_��IL��c{�����i~������|c	��o@"�f�^m��i~�
�ޥ��oa)��-e���L��΁�����	2d^m$�ag)8,���k^m)�i~�o^m%W����%)	��o@"L;F��j��)o����i~s���|c\)��*~���ö�]9�i�MvDB��i~_
��뀺#:�&�d� ��i~k�	����o}�)'��z�� �o :�o)�i~���æd�Q_(k}(��y��?�)��Zd���� "Q)�	2�'_�o}��m�۩��# )|�㑨ۘ���W�Q��_�w_���	�[��ݢƁ{��m8ZMv� �Xoj)oP��i�8�Ԃ�i�7UmB	2.��o�q�k�i~�|c� Um�ooXz�c��m>���o�c~I)��c�w��HUmI
�I
����@Ԗ�Z�c냱a3}�o}"o"MUm��|��)�o�c��8��opף�cB��-�oo�o}]2�cv�����ae��-V.)���i�*�cI�ݸ3��i奚yBUm��cMÅ-�D*#�*��o)�}��s��c�0&��o)�oUm���?�}�Cd���YX���W)ݲΪ�#��Cd�
)2��-�E)�BvL��Y.�ae������Cd��#�Cd	)߂ae���-��?s�aeq��i[��o�UmĚ�k-��	��ae9��'7m�kZUm�oo��)�o�c�^d^	Um���i����+��k���iBUUm��}�oo��A���m���cl����'L���Q�c��)=�}���)���'p
R(3Um�"�cUm�o)5�(u5h�c��Cd�om~v���)?�c�f́����)�R)!�Ё��x��(����ܾ:���Cd��	�ǔ������)c����}Њ�/Ĉ�-n�aelc1�o����)Ƀ�{…-�)��	f��k���wd$n�Ёɢo�3:��	m~��5l�%D�)���Dm~�o۽�m~}�)�۽��o����:�)gЁ�_���F"m~o�Ϊ
��	f�%DU-��m~�&\�?2�)g�$۽�U�^d��}��%Db�o�8D��)��}��"n��)x�8Da�}�oDD���'���y> �o8D_�o_U\d��}:��$�DD��)�}h-8D��)e�}w�PҐh8D#m~�DD���Y
	&C�)dw$�Xmdw�)�om~��o��)�aDD��5�8D�o8D�Xm�5˕g�
�o8D���}dw3%D��+w-D��i��Á��ܳ<����)�Xmc8D
�ݢ��5*��ϙXm��)e�ʣ��+�oee��)���t��	_���
��Gd��+֘Xmy�Y�98��o�.DD�Gd��a��+�)�!!|&�a/�	fXY�`B�Xm�^8D�)e�E9��o��Xm��oiEd�dw��Am�o8D����Cee5��m�o���ow8D"dw�8D(��+�oDD�5���a�DD��]>5Yd�o�8D)�a9�P�o8DGDD���c~�a�o8D���c�������8D�Xm2R�9�����z�Am,ף���c|n�o��Xm���3è�����/{�a�Jm���cĄXmȀÁ�!�	�)9�Xo�H�Xm����Xmմ)=������c=�
�i}j�P�����h��))�8؜�-��HO�"g�۽��)*���}��4��
}��%l�j�o���X�T�(׀�8Z����a�Gd��a��oq4��-g���o�
f�a��{��oX�������J�!!�o��)��c��cԸ�t��(|'
�Y�o
f5����i#$+I"Ȁ!��o*2��f�^
fpi#瀘+����pI"〻c9\
f�&�o�[-g̐�/Yq8,S��#�#u,m
f߁�����}�&��#u3�f���̈Ea��A��Y�#u>��a��c��*���K
f�3�C�V:�Z���#u���k��#u�Om��k��h�kpd~+f#@��t��o��fw�#u9{u�G<|��k��f׀ƞ/	o�#u�����Z^V�#u���mFL@+€�a��>y��k�C�p\m�l@+���%g\mک#u�?<|*�o��#u*{�<|��071���	�
	pd~j�hee<|
�k샆?���JfX\mx��j�heB<\mR�g1<�f<��(�he��4d~�-m��i$+I"� gO�#u�*\m�ze�E���\m�I"��p���<D��#upI"��i�֞-S����i�#uZ�#uߌ-�he��	d��{۵�-���k���m�#u.Em��o״�k]��ac\m�"�#u
�helJ<|ʈ5>"�#u_��-pEm�hep\m;��a�$\m[��"�z��o8'EmW\mo��+��oG=n����,�Ea>��{a�!uf��op\m����p]�:Emp\mZഖJEm���hA���!a�ރZ�=��p<|U� gЁ!u��?�he����К����ޣ;&g�:�ik��-w�hel�i��Ϊp�c����JdK�he������-�o��ϰI�he��<Dp�c������Jdd����he3�

����Sz�� ��p�c�7��S�o2zEm���葭*|���{�P���Q�c��*|�G]��݄����{��41}��oX���^�^9=�3#�^9c��o�3|D):d�'��o�8D�EmpEm�^9���8�,D���o��p):p�cE�cЁf*S�*|�ۛ�^9?�,D>�X���mӃ^9" ���ݙ�,Dp�^9��*|�X�'��f�!�X���w�'�!u���5� '�$���^9��o����.
o��^9��x:�%��ݘ���	�'݀%!h�cD��o���_�'(�YƩ^9��Cmo2&��^9�&�cv8D���9�Cm<��%p�M" �k�o�M"�	*Da��GF���@!p�c����c	�i���	�ܨ~e��M"3��f��"�M"m�,Ds�4�o
$g�Cm����#��ݍ�*|��^9U�Ƌ�O |f ��o�>�h$�k�,D�''`:�f��^9��'n�sI���'��oQ!$g��^9���k�
�i�f*�-f%�i��^9�(���S
�^9'�
ov��l�{f��aO$g�K�'�ԛG;�� g#!g�-n	���Y�a��h������<�Zd
�aX 3�o���95��M"�y�=�%�]�M"�'@"���^����ʈp�s��&y�"Ņ��&�c
�Cm'p�a��Cm Ђ�&(�k:��$�V�M"�'��ᗁ�c)�m#*p$g+p$g���c,�M"Y!D-�M".�k��Eh���i�fj�)a�M"��o5��{!Dv�t��
�+ު5f�F�9!D�!$gb�d')��)~eNd<�x8[E�9/p�aP�-:L6Nd���w�$f��@";�e.��<�i~˭[v>����o5��{Ё/�0p�i�.|wAm���R���a�i~��{��a�	��,�o=��w݇�-1�i~)�+��4Z���i��o�磇�&�o��92�cȃ[�+��ymwp��{
�?݀�c��a���m��<.|�
�I�ߩ
ۍ�"{��a��)�mw��+Ɓ��(�+�G{Ӵ!.|9�������ܨ��LR�{;�bw���R��o�^�Ţ���ւ��5]_D)�ݎ�[vރW1r��/�-�۽~��0
��i6'��
�ݙ�*u)�Y�hGm@"�Q�-z�o9���+��i�k*�i�Ġa��i��il�$|��`8~��Il#�iĔ�I3�+gD�kX�zݧ
���'��ce9��l�-np��{Ё/�P�Cd9��S4p�-���cH.|�����d5�ce2*��9X�Cd� *#��V��c6�c���	$
�k5R�n#)-?�
	
(	(�R!' !��d(%
+Rd!$!
	22h5�!+Rd	�
?	Z-	�JSU!	
-22#'!
-�!�5dR�2�k%+S$'2� *$!!(�2
5	�1;%	-1�k(5R25	
�!�%!	"nJ�2"'n�!5%
n
!�%;)
$�21
*'1$1�1nd'2'J(*1��)5�dRS;*	
22S#!
�
�2!UR�	
5%�'
�*$%�
�

(?� �
UU-!(5�
	"5�
	?$��!('%	
�
%;-?�
�k?2�2"	J S(2"% 		!!�?�(S(% �	�(dS*-5	
%
R�15 )%k$((d52�kR,52+	%
$+	
�
		U(5�kU!
*(�dU$J"�
�
2$d)%1hU%2�1�Udk?�!5d�		
+$	215'�5!5)h�
hJ5�2

dhU5%	k%52k*-+1`'hdS ;-
U(	%
!%*U$-$5!
	"hJ?J"�-

5( �)-Z?5% 51
-
;?�
(kU
	S
$#1�

 !2!+1hkJ�
	




d	
UU

-*

*!(�-h(d!!	

1(d
!	-
�	?#�
#;	�	�?�*'
h�!*d!n"*"*5d
	n;�)2� 5�
	1$*	

�'?�	"dUU5 +
-5+?�!+$#*�2n;
k�%?�'	#$n�?
Ud�  !?d
� 5 (5
!
!	%dU�#S	*)-1�!$S�;
#�*	?�'5Ud�	
 �
*5'� 
'#
-��5S kU?�'�
	 �

'�*
n�*
(�!?!� �#�'5�
*�
(d�U		"U(
#'S
S
k1S(U!SS5-)#

	SU
-!
Z2�
-


-)	�2$	
$J#	%`
-�)2! %!J2�RJ
-�5d5�
Rh#5! 
�)5
+
-	J�d	n+
?-!	2�!�
k d
-

1�

2
)5?
-
1$5�?	�-U�;?�
# ;�
	2
*
�k;�
�	�?k�+R)-�;`	
?;�;k"1�?d-1�
$;"�;
-J5U`JJh?U?*'(�5+"2dn#�;d�

)S!?�1
?h
�;;+R2!k;#k	k!d71d;		�U*!�;-US$	%�1�?S2U�
R2 *(;!	
*d$d(U�%#2 �; %�!��?	%( U !5d
`

�

?�
	R
-J�S
)R	
!%�U!'-3'�!; R!�U*-)
�
Jkh#�)$2n?J5(+�6;R$%?+�( #+))	$'hknRd(	#2!)	'%#d

�5U12
5 $�?�
	
)�
*	
	�2�-%?UR!
-�#?1#
*U##S!!'
d
#

?hk�2
2#'�)
�!�5*R'5�)Rd`	
(R�
Z

k1R(!
-?�
-�2;dS
		J#$

�	?1J!
-U?$Rk#�-$!
-�-?
�
�	�U5!�5!�
 	�?�;	�	
$%5�!(((�
! 	
!U(
		
S
 d��)2 
--
�
S?d-(J5
d$!	�S
-?
#Ud" 
-	S
	5	?-	S�`5�k!	%�!5�
n!*�
-?R;kU "!�?�5d(
)�5U
�'
#
k�;�?
U 	U '(!U(!U(�
-�!d�R�*!U(�U*R1;�U�Uk�'�k`	
)�Uk?!dkh%�)U�;15�?
-2$�;-?�!�1
!#1!!!,
!�U1k�?k�!�`	
�


;!	)
J

#?#�k
)5#*
!%(
k
%?�!�*2
%kS!*�	-1?!!5+�#	
-! # )$#+(`2)d?!S
!2�RU"k�k !�*!)k2R
-

'�
Ud
 �?
?;-)*R�R5'"
J!!!�!�)�
	�%#
;h(�S�Z�+k?d
�(")*	%�$$?%�!)	
-2-* �?		%5-2

!�J)2?#'�
+
-!;-)�U(�!�;22�5?R�(U2 
;	
)7	�1(5
-
	�
	
J�(5R)5U
	
55*	�U'U5U �*k!S
�
-U!

-dR2*�
--5�%;�

#!�d(5)2��$�* )	 %!$	
	�(d
dU'dU(�?U	Ud*R
J h*)�
R(
	U��knS%dU(R#U
##�(R�!-)
-J�1"d�* d?�*n�	
-k	n�;*2S
	
!?�5d�	�$U;�
	;)n*;#Z2�nU($!U5
dk	'#�k5�?!
!	
�
(h!�dU)	?J(!�
�!
%!'!2R��
2Z*!1�
		U;*?
'2Uk?	1	-#n)
-n#
-'�	'�#U%-
�'	�
S	h1U!1�d55
dkU#�!!S1dR�1U
#' -5U
k!�;51�?# 
�# *! ?Z!
	5)?5	
�#U)?d#n#�
;	2
;(�2?(nZ	
�		(!�!*�+n(�%5*
�?!d5h!-	
��!5)-'
!1�kUd
	#'#5J1%	1?!!
-�
`? 1hU
�k+ �)��
h5;k'USd5k2)�	
'(5JJ n+�!�SU
5�1;�%1		�	
d?
d
�
U2	%2
-J+�k1;!!
�S$!;k!�2�2

	
�*%
	`
	��;1R�
	
?�RU12?
(dU?+*+R5S1-kRR�*R5�!5�5
-	'�nkd5%U
-)	
�25dU�
2kR(-�5Ud�
%%J kS		?kSR%�-)S
	�dU5�d%�)S(!�S	
d(5)	�h(d(U	-1#�
	SR%)ZS!'%�
'!		
!�+ 2

k1 	*?	
?%
(�U%	
	-�!$)	�
'+� %S

2#-�#�	
�#%�*1
'!�!
�US
*SJ+U-
�	
Z?
R;�';!+�-)�1� )-�
-)S�*5
�1�U	;2�d	;�)(5hU-1�R*(�%U	d5�n�#'�5(d 1	k5�;
	-
-)
*�*	
�?
- '-5#k�dU(�
'-
-5	�RU;�-*Sn	R
�U	-	k#*(d?
-UU�Ud�'?�2+#U
�d! ?)-5'
U5d(!!(�
�	
?%U2
U5 hk	
�(!S	;5("
-+?n

,�)
-#�;*h$J5%-' 1n
,)R�!�#"$#U(�
%�Ukn)�
#�k)RU#5(!�
?)2
�*R�d(
? �S
)-�)
� n'k �	
-%U,�#
�#R�
-�;�!
!?d1+(!k	2	
k1$?k�5R�!	#
�1!
-?1�
	*	##
)*
-2#
,
#;�	#
%+�
��#�1
	
-
#

�#�-	)?;;?�S%#d5#

2(
1?%
R�2
)
d
"1!#'!#h 
!$#
h�	#%
#!hS
)#	
d) U))
1#Uh
2!'2�)$hU?Z `
-	�U
'1S	)2�'(S)
?'(
�h)**d�U�2d%hk
?�hS#1U' ? 
1 4�	
!U'+�'
2!�d+
?k	!

	�	�#

-U�1;k�R1�	
�d��	 !
-)h�?!
	-

�+U	)
� 
dU(�5�!	#
? R�5! 5�+!�%
J5RJ
	�-)!+R�#
g?	
#� %J�)U- d5UJ	Ud*n+!U2U
	U( 2!) ?+h
+*Uh#n�#)�
h4+'�"S%�
kS*ZnS*J	1�
d!
(!$ �*J�dk?n
	
 ?
)*!- �!n�ndh�d-;;+U
-!�
#-�d-2
 *'n(#R1#	�k�1;	�!�) 2-�	
�'�%	? 
�2?	
�'	
)2)
;	
�h
�kJ2))#(�	;(�


� +%	k)n*
;dU2 J#`dU'%J�S
�"(#
1h)	7�	�#
�
#�' h�-k*
$$�nR		)-)
#2+n�k*
R(;S%##
�?
n;k	1J�
-
�+kU�
�#
+

)JR�(�
-;*11)�+J�	
-7!)S+�

	)%
�UR%)J	*�) 1	�)2#�d
S)�'#-")�S$'+
)�*;'

�
	12
	1	
'?�	?1�1)!
;	*1*�
�?
1		
-
2!�	

R1;*�? 
U5�
	
�-)	5d
 ** � (�*
)

	"R5!'�	�	%#512�hk55�S5(�*("!d!h�h*- �k
'h�
	!�11	d(J?

-%
�!k ('5�?k;	
�!!5!J��
	�?
-"2?
U#2
+#�	-+,1)
�?
�

?	�!�1�''5���2-
�*�#2�U5*�2	
' -h)
��5?�1*��%U("k	
U�2
�#
!*"d5

k1*
�!$ d

�-kk1
Z?
?h(#
#
-S-%2�S
U(;-
U%;�Sd		!)�;(	�;%?U�+
-1(
R((*	;d%�(dU UJRRU(-R
�
1h-dkU?
	
-	�(�R(%U%J!�dh?1-!?�5
-JU*)
	*5
-#(U#$�!
!J(#�!(	
��R'(?R(1
-��# R�%R- % ;# 
	
	-�21�#%)
	
%U5	
�1)-
-�;5� ;5RU(5
�

- +�J;�?
(�'h��5�dh'5 �
"'�%�*! %	%U5'k+(#%�
 +� � %$�'�')	#!
!5!			5d#	
	
*`5U(
�hR(	

	�5 �'�	
)+''(�
�UJh+1kn-
��1
	

,1+�%R%Z%J)�5R
S%*1�%S`U(d�1!- ZJ%kRd�S%�k
5'!1!
-*(�)�
;5�*�%�?d-*%?$R�
k� !'�2)�k1�#k"	
?	-U(%d(Z
1%-*)1%%2�-�
(d�SJ	
J!�!�d(�!

#
%
U%�
�-Ud
!
�kd�'2U#
��%

�%"
-�
	`!�R�k?�-+�J�-)�*�2��

		!
	);�!�S(R;U%
#US dS `#'
2�

+2R	R��5
J#
��*n'�	
-�1k1
n;�
		#

	!?�
2)
	%(%%�
�)-+!�!+JR2�%(�%(-U%(S�%(
�?;%(U;U�5	�
�?�;?!1R;1!$	(%	d%U5'
%�d�k!�%(�R;k 	�#%?5�#
-U�;?S	!?�U5!U%U�#;!R#�kU5
)
�;?U�? 5!�
SU2?�SU�?
	�
5�;? �2-)U2
)	;� 1k��!`!'		d	J?�)


	; �2�%#*U% %�!!�-	�'�'�?%�%
	R5(!'%%+?
%
�

(5�$d#U?S
(	(Uk!d'
�5+?	(kU25*'�2 R�
!�	5d

$
Ud52	R!�?�-R!%U?
	-2�S(U%S
	R5(S	52
-�d!U�(?��
RJ5!U'd�#" �!(	�5(
RUS�!
�%�
(*2
(52�
'k�Rhh5?!'(R
���S)*'	U5!dUSU�!�;-S�S�!)SUR#!�!�
)-U
%	#'
	�
-#J#	
�;!	�?�!2�
!?n;
)-?
%*)'$
-!('
!�
n
�
U	)kZ !
�R�k!
	�!k� 1)#�?�*	��dS%S*�d!(;?�S'�#�)*+#k�S?
h;�2	
)	1?S�!
		*U#UR!k�d+
#' *%;�	;2%k(*S�%?5	U#�(!U2�2*
�R5	�
-�
-�d
$	J!!+Ud5	R*(!nU
	(U	J!U�


	5R5(!R*!* J(!�-2�5d(d�U5*h!(RR!	UJ�	?J(?*U	
Z*JS�5hS5(J!*Z
�%J

5'�!�%
*5�
	Uk	5(5*
#�	�()d�

;!-2;!
-�R��5�R JUd(*
	h-(!5R!�5d-�	

;�!(5U12h%
	�+5	�JS!�
J�

`5
	
�%;?
2! R
-
-2��J�!��*
JU��
-)�U5#�;RU5d		
 
?;5dU(U�"

�

--Z
��;2)!!	�
#�;U!5k�SU;J�(
+k;
%dk; n;U;?'
UdU
5	-;d-�?*	!
;�%?�;?	�??
Z	
d(
-	"d;
�
-�#!�;d	
�-
�!(kd( �	
`�-*1
?
�'!;-*�#�-
-
S+*2�!d;�
S)�
);RS�(
�(	�	
`S	(�k);�
	)�;	�h(`;*�
)�	*;	
�;	�R%#
�k1(�
)"UdZ-;"?	U*Z2;S5J5�	��52%!?�n�
`)
;
!
	#	
	�;2�	
?�hJ(^S
2 ;)*�!�Sk�J	`	$;Z!Sk
	�?	)Ud"�S*�	
;	
`
-�
�(5%�
�'#� %?#kRU-S	'%!��
-Sd
-�'

*
?-�
-S�- 	-S
-S
	
�S-R	
	
	;�;�?
-		)-
	-
S?
R
	�R5d;
k���
�-	2�5�;?-�5
d;J�%-)"-
	�%		S
�
 SS�5#-2)%U)?�*
�	�S?�d


	�!dR	
)!;d�	1 �d�1nn# d5Uk	-
`2 �% �
S
	? 2U
-�	
dRU�
	
	�

�J�'	'dU!k�?;�
	5*�Uk�dR	
-)�
	#-S�!-)	%
'�?�%2#	'#
!S'�'5�	
-	��5	
%�	*	U;	R%U!ZUUd(!;J�
-
!(;-	
�#-
-	R#	)2#";h�
	S	�
�'U'�hd(��;?R! !�2R�(%!$	%R#!5�R U�R�	-(�	
-
�k�U;R(n(�-?�*#(!k2"(�'S*�;)-�2R��%#	%*�S5S�%		?�

?�	%(U?


R	�?	*` 
Ud�	5	�
%�
�%!Z	�
		�!Jk(�;
�	Jh'!�-( �h
-S	dd' 	d-!	R5d;	!U?#�??��	Z%+-+Ud(�
#
+

"J
%k�k5!�!+
�
(�
	

'	
�R )'! -	*`h*�	1�2�R
� (��SJ	%
%S�	
�	
'�	
�%#-?2-'
5-		) �%	J	
?hd*-? )2)
 2)�
? ) -%-�?�	h-+-	
'	k?%�
	�%	"1	)�-+	!1
-�
�1)?	-
	?#	!	%5�

k)J�
+JS	
�*d��1�R;+
R1� ;�;`�	R%
�d
US�?J	
k��)-#�	
�J+	�	
U
-		"kU
�!�	?)
-(';?*S?�

"kR�5;!2 �(!;! J�
 S*h�)
1S
�
-	�h(	%(h
 )#Sh
#�? Rh;(1n S121dU	-
75��R1�)(R5Z
"S	�#*2�#�
	�1?d�S?hSJ(
	�'%%�-J R?5
'%J
 1	-n	?;�!S��('�!;* n?;;�%�
;?
	"kR(��J1
	
�JR?J
	?
S�#%�
	
�*;
2
)1��;�;k
!+!#%h+(

U(
;'h
S
!?�'"?

( (R�*$( U"#

)�
Uk�
-�?!U	JR�R�#
#�-)
S	
U*!%	2
*�!!�' 

-	2%#!%+()�?�d5#
(

*
�5*5"	U 
�!�;d(J()�2k(5#�(dU2)	
�?Ud�			
��R	S
?)�k�
)*!h	*	2�k�#
5U(*!�5�	

(! �R#

%%U�k%(�('
*�JS5'*5%
h(dk�	1`';�
�
h

)��� n5	(�Jh;	)	*5'*�*!�(Ud	J	;;d
;�	�knZd;?�'+U# Jh
%	)�(U;?d�JS!?;d
)	+! dJ+;d(�	Rh5
�;d	)(5d;	#5-)-�	
-Z
	 ;dhk#)`;d(1�
	
	;d
d;�k�h!'
?�
�

k
hJ?	
�!�#�##
5�	-*h('�'U''!-5�5k	! '

!�- J'h�#;
#�
�-		 	*!��kSk;k�(�!'�5�(; � U-# 1�5#U 15#2U dS5 #h�S�k2�
 +
 

RU5-#5�?
 !Uk	k	�k+�**�k�R)U*U? 5(d155	 �1	

-)5'd
?		
�
)!%�kJ�?*	
	


�
'	
-��%R5;	;5
		
U?��2�'U*S�!'U(�n'
		

		
UdU5;	(��
#�"5h)U
-%hS5�2�'	
� )	;U"
;d

-�;
-5UdSU
-
2�d


	d

"d
�!hd
�
!
S-
2�?
%	U
��%;*�

d�;R*�
)-�

-
�J ?2(*%!1;S!�*

�'%�
?�!	�	
!#�
 ?#		
;#�-
�1(;US�`�S�

-�R�!�)��	
�
()	)?%d%�5�k!

d?  �	
�?	��;%)-'S+?"!�2)�kR!!d(
-5?k
S;	
?;�-
+
	?�1-
JR�?;Ud
�%-)�
J
SS?�	)�
 -)!dZ	�R+Sd
-�;�;�S
R!)%�d?	2�
�kJ(k-5U�dk�h�SZ?#US?�?#d	,?SU
k;�J+5 h($k�		�?*��J�R5(U%R�%�;�J2nJ
R�k�d5;)2�J!Z
 (�* �
#+-
�d!Z	2�;!�R*d*2�!
-h�!
-	��
�

-S�#J	##U 	*;U
?,
-U*)#*-
d;(Z)*1�
-#
	
�%?n�#
'
��	
-*5U5kR
	
#	5((5U�U'
�kU5�U5�d�5JUR#�kU!*)	k(;#�2`Ud(
-)�dk�dhU(Ud!5)�5�dU?�%��)%%R
Ud?U;U%
!5"�)kUk�
�1Z%	5-
 15dh�+h�5 #5+�
-	�;(5�-		5+5(?�+'!?
*`2�%#�
�S?1	
)-
S#-5(�)�#?
�)h5Sk�%
	!	??2)�
#5;�'+	
�
 S	;(U	?
%-
	S	
4$�;U 
	U;		�)
-	
!;U;U 1;d	*�;1
!S�

U
2��2Ud;

R
U?# ��	#
 
� �
4d*?
*)
2%
�% )!S( 
�*)1�?�#!�U(U! %d�	)�J;S	* %�%		;�)**�'�k!J7n;#�	�*JdR�* 	%�� *�"n#;#%1n'�'5!#n?#2k2!	
'�#;#�JUn5�)2�
'�)S
!	;!#;,	#h�5??(�% J�k�;U#d5 +�
	2!5(�?�
#�5
?1
!#!;�
-�J
�*? *)�d5U5�
;	�?
	
?� d(5
Sd2 �2)'?�1S
2?�? �d(
��dU �d*;
-
�n	
*!�!R
	)�!�%5�#?	 )� 	S`!d5

-)
'� +�?'�
�	5(	?	 ?U	 )�h"
)k	
	


1U5-�UU(
	
		
	J
	
-	
��

�1dk
		
�	,'�n�-
?SdU	
�R1
�RS�#)'��R�*?US'
*?�
*S#;	
�2 *?�?)S!+"!?Uh%�n!�*
	,'�-U��d	�d*�d�15�	*��
�d�?n
!�2 n?``n"d);d *�n?#
d5%Z
;	#J!�?n
n1�;5#�2�h1#`k!R'd	�?n
?%d!�#�!	�	 Z)�n�J%*�?!+	
1

5
!	#)�#
%#('�#+#d-�	
 !!�S	�#2?�(	�kS�(1�
�J�
Jn'	
Z!�
��?5#	!� 
%5�%S?# -dk5��*
dk

k!d!R%!-2*	
2`12	%S� 	
2�R`+!2kS;�125J	S��%?%Z%�-)dJR2
*`!k	
`

2
�#%d(%R	)2��S#�#%	%	�?%;

U%	�
-,h%5U
-;#�%
#
2Z#
�
�5 ��51
'!k
+%!*k+	
�
�
S�U 6k �
2( �+-
;1
)
-+�1;�;*�!�;	Z;"

5�	U-
`!!

�*�?'1	+� 
*Ud%	%	

1

1*�%U
*;1dU(�#;*

-�!(?�?!�%7 R

�	%!��*k
-R���%�)- Z
-)!*S`)�*	?)�
!�
-*U?? 
J
?	*2
'1%	R�?%S!�;`*)$�'�%U-	
#?�%!%	R�k�k'�SJh*S�;�!�%*)%S�
*;)�hR �'�*)�*RS;	*)#*)�
�!)%�!)�#

�		!S
U!*
 
!��(?�
R!1%)	
"%"

#�
`�
S*25S�5!!�R; 	k�- 
%	h
5 )�#!?dknRUJ?k
�;#�'(#?%	#Ud�;
�+'�!5!

-	
'�	�2 *� d%  R
U��#	?h	R ?+

	RJ�!d)5��5�h!Un
-	#�U!(+;#�('+(R�
-)
+`
(+%
1�1
#;dU1URh �;*4?k1�R1#�* �"1-;kk	%�#?)#	*()(!dk)+

)�?	
		S�
d+
*+;1�;?dU�	;*�5U%�',��'1k-�?�1;*
#
	�	�5�
51��k!2!�d�?%�%)2!U� !2U!5)Ud�	kU1 +�k!5)�5 �
	�%;�'-�R	-�-)	*	#��'�� R�k5�+
k`	�SR(
-�
-;d
'?
	?�? 
 '�
�)�?�(
	S5'% U?
k;-�
�-
k	k1
-1S
Uk
�?J�J*��'kZ
��d;R+k�R?U? kU*
k;���k��
	 `*)kJ
-1*k;�J;!
-�#��

-)�)22U##*��
�kU�'!�	
#�JdS
*
�2(5*�%


2Sk'%�nk�1kR
n;

knR� 
'	U�n(;�
-?���n

)	)	n 

�5
-#
 nk�
%
)
`
'
�S�
�%�;n�?Rd5k�
!RJSnU?Rd�
	-`?
#

-
� 	�UZ
�
-1

5RdZ
d
`#
�?h�
)

U5�
)	(d�*k	
 �
)5hd�d%5!
-�$	#�(%?�	S	�d%	R
-�
-
-)
U
�5d!�d5	)R1d5'�hd%#'d5d*+"
�
�
�	1U5�-
d	
�*;	 -
�
	Jhk;	�

�d5S(%�#U(%+�%�	�Sh�%(%�%
#��?�?%��(%
-"J;?�;?d �hk*?�d�)%J�; �	k+?�d;!	�!�!
%(
5	U�hS2-	�S5`%(	4%d	*#;
-?
S#)�
�	�d?	
-%?+Z;�%�d�)-
-d�*%Z
)
�#

�hJ	�?2)
;)(%-!
d$%!U)JRU;!U*)�
 -J"
	'%�#n�%#2%!k*)#�nJ�#R�(;�%'h(*2�2�n#?	?%'�
-Unh;h(Sn'�'%		k;h5	
JR�n1�	�
!?;S!*;�	
�5�!!(;? �%'�n;*%?*�'�(n*!;�k5!
�h?
	k�	;?#�h�J�5Rd�
)
k*�R	kR1
�(?
	-	?		k�'
	JRS�k?	�k'hJhJ�(*JR!(Sh	Ud-)
�n(R-	#hJ	�
%)	�
�%;n;
'k+-
)S;�

d;;JR�?#;*S	;
n
�2�52�
-n-*�1

�1
	�J;1!�;5;%Z

*-�1)�hk�*?

-�1*
��*	�n**;	

!S;�*#�
	;S�S%J�S*
#!

�#
� 1%#� !2 S+�
	%J�U5d"S*%(;S�k'R*$�!	�+d#		d� 

�
		�1	�?);d'!�-+'�!�1R2('-1��J
S%k�2)�!
d;�	
!;*??2R!�
		
S*
)
S)

*�**�1)�!�%�;-!
-�R(�*
k7$*R5�"U1?!(RU5#	%�dU�k�(1kZkdR;; 	)(�RUZ%(R!;	%R;
-
#�d
5	R�

'%	
+R!�Ud	 S) �R(hJR(�;J
5R)!�?�!	'
#S 5�(R;�;*�	 !�)!?S�2�5'�;?dk�+J?�;2�?	
!U)
S	S!Z
-!S-�dS	#�* �
d	R		SkS�R?#
U(nS�U52�	
?*��%k
U5!Sk?J�	1(
!
UU#((U5#
(dR-	J-

-"?U*
-"(d?�n
-�
S(S-�#
�#
U#k�!U#U#
d"U��#
#!�(dk�UU(�
-!k+�1
# �'!�Uk
	 !Ud	5
d�
�'% (R%U5(
�1

#S�kR	#2 -
�#�
�#
�!(�U5!�
-�;	U1;� R))
-	5!� d�

-)Z�(�5!J'+!%�kR�J+�kk�;R�
-JUR(d�(
!R
'+n#2�
5!+J	
-U#�Ud
�-�(�d;( h�*Zd!;�(U 1? dSkJ�U5!(Ud5`-2�
)	
�*�h5
('�	
�;
�h�( n*`5(U1
1hdU�k#SU;?�dU#U5U%ZU*+;";-)
"!�?�;"$�#�J% 
?)�(k2Z)
-;	�;-	
	(�)��kU�`;U
R!
S
-�(;(�% �?�'5�J-
U
5;k;�
�d!U�!*R�-2(2(%�k%*!%�?);
#!h;?
	 ;!�?+�%

5%	�1 	R%#!!1+
;kJ%��

�
;*�J
';�J
-Jd�5!� !+�*ZJ-
�J�*%"J�+�%%%?JJ


+�-
�*�;
5#R1
"�U
 �
-S *

-%
	
�-U�% )!-(U--
J!	+d%k�	%1�+	%R5U�-�

	,�)-�?(S

	U	*(�?�!,	�)?2%Z	)*?Z	
-?	UU�SdS-�*?	*- 		?SJ�!	-dU"	*!;�*1!	*2��?*dU*#�?�#�(d�J-U�?*�U?
#	
	!R	#
d;R
!�
'	�# -
%U+J�h-�##�J+#�%�!J
*J�5�?!�1#�
	*'(h
k+
"h�J!(-U5SRk!'(!J)dJ�	-2
#J
�(JUU
*;�5 
!(!	5 �)*;
1!2
(;R* R!*!S" (�!'�k2h�#
�
�!	!"*
�
d!(�)#
k#	+Jd5�J
ZJ'
5;5J	2%	)	

�RhR;
�1�J+%	
)�J+)
�U52Jh-(h
�d!�S		UdkR;1*%h`
1�?!�J#h�
-�(kUS�hkZ!	+
7J�)k*n'�k
h)d 
�d(U*%'
h�	
-U(�k�#JU�2�(U(!!��
-
Z-	

?�
�!)J
Un
U!R	!
-

�51�U*S
	R�n?;	n? 
R; ?�d�,�
-�R(�R(`(
#1�R�1;�d2U*)
�
U5U
�k�R�(�	-)n%�R�%"%�R

R?U	#  
k1�5(�
�
	�	�'(�(';Ud
1	-�?*	
**'�RJ�	R(	
#�S
�	�Uh?�R;(2-�S�
(`
-�(5�%J(;�k?UR	';	J)�'R�JR;'(SR�%!	?S�'65��+1;
-'#6?�1�
-!-S
-	#!-
�?SU�'
�n1
;
-�
+1? !*3$�*;!2
-nRJ
�!2!��	�k	2
�h'�
)2)	?	?2R?'�?�!
5�$-�k� �	-kU�R)-!�d'5S'�)
�#�R5#!�	#'
(�##;�
	�*+�
�;k%�(
	!(;�	(��
	d!(J
-;R
-
�
;hJ	
%;!!*
S!�(
�!?*!(h;J


;(+";%S �	h;(+"J�;R�'('+*+*�1 �(U5�1Sdk�d5*R(+�%�(�*�R2d	+�RU! 6'U�?"Uh ��hkRk�1?�hJ�d
'U�h(!�2R �'*(# hUJ!%1k;(1?U;( ?�R���?5(U%)!-;	�hJ!U?;(
;?J'J(*;hU(
�+?�;?!d

5Ud(!*d*	�%
#�R*;S+Ud5-
S�*R�
!J	?��h�#�;?`*Rd�;�d!�!(1 )

Rd;d�?R�U	#�5(`
-�S�(%(�*d�!*�#�*Rd
�U2
-k�;)Shk'	dU�?%
#+	'?UR(R)k( 
U�-
�(!-)+


`
`5 *	'R

)-5d	'�
�
k	#-!d�k;	dU�?#�#�;k�%2�
-2*	5;U(�*?1";d'�R(
S;kSd(�5(d!'%;k�d;*�)dU#J S	)U)
#-k1S�;k	
5)2,%;#;�kS*�?S�!(2R(	Ud�;�;�R(!(2d R	(�)�
%�JR�'�	R�*�J#
Z?	%�(d5!'�?n�%)#
R�5 U�?Ud!**RdZZ


+k5;	d(5R1%Z
# kU�';Ud5Sk%�
R�d(
;*��'Jd;?�;JS;kU%U!*;�1+S*R�)	�**d�
�
�JhZU�k�*kUdkd
d;�;*d�
�
;#;?)
kJ�)(%�)
d
?	;�
	�
-
)%�S%
S�
)	


)k	U;k)�)
(5(+	5*dJ!�-'+!**�
"-) 1�kd�k*dU	;(�?
d�
�
 �	)-�(U#*1;+"%	k%?!Rk�k�2U?�**?d5(�?)-k%�2
J?"S;h�#
1
�	1RR�2*?)R
2R'%R(*
�;�
	)U�'J*R�Jk�R1 ??
*1*k?)1J�2
R	�R�d5U#�#1�n�JdkU%'�U`#%!)#!U?
kd
*U4n�R	
#-	�
!#n?�U
*	�h5U5%�R;h�n'?%�U%dRJ�n 1dS1U�)-?US?!n�Sk�R?�U5*;!(�?;�*!?d(RUR�R�#S(*
n+�-J��#�U�(RJ
�RU-1 	J#�
�knJ)#� d5U1)2dU-(1
-�	
?Ud
(R`5()d
dJh?!�R5)'kU
'#?�kh5U1
	-�?
�%�!(?�hk`�(Rd�?-	
#�;5(�)-�
'�*2�R1�d� k*1UJUdk�5(5?�J(U*�*?�?	R5	!?�S*2d�
S�*�dh5�(Ud�
#US;�dd�*�*S;5Ud-�U	!J�U!-(	
�n*#Ud�
	kU�
�*S�%(5�*?�%	 Ud5*	�	
�hk�U�1 dU�

h	U
	�
UU-
1)2�U`1Sd	(#kU�-	)�!R"
U!) �Shkk�
�!;-�*U+�?�*
+#+S�)?;	11	
'd1�)1;S�-#RS�S1	'�k;S*��SU'��
�
#'	
dU%
	d)U��?�
-
�
�
�##
d( 	�	2;Ud�S�
)	%� 5
R
�J%�RUhR
(�
-*
�
�'k
-	?!%�?	Z2'?-)
!2;U�dU!5?5�-!(UdR*?�%�?5�?�)'%U5'�
)�*;2	J!�U %	�
;% *	!�
##?5?�h(%n-S�Sd5R!*
-1J!5U5k	d�J!5U()(	h'-	�;	-��)
-+2		%2
k�?J;-	S!
		25(*
%ZdU�*Rh�R(!	(�222 S�#%-)�5!*dR
-2R;�k%�%k�*'Ud?;
-
hJ-
	U5!��d*��dU
-!�*
-�		
Ud!*;d�!�Ud;1Uhd?
?�

	�(#J+!h''+JU;�)'%1#(5##1!+'
	%�
'+%U!U2(d;U�U�%)S?5d;	(
'�
�5- kJ+�	
�%R
U(dSR;d�	S�?+J�kU5�*1?�U(	�	k%�#)'!	n �Ud�U?5
	�J-(k�
?S��SSk�S
5R+SJ��%*"SS!)dJ
	�*� J�2�*�?�?�	
S
#S	?J%Z5Uk�* k?
?k*;�Ud�-!�1� k5�		d5�Z2*	RS�S!U(2
-�
*(5�%d(` *Ud*d5R��d5U)*J�U%S`�#(Un?*(d�	*
+%�dU�(%
SUR�`�5Rd�5US%S	
J5�k*R��?;'?U5d?)S
�?
�SJ#�;
�*#�Uh�kZ2
�?	n�
)��#U((U!7	kn "#�(US?
+S-5�'R!5'(�U)	�
5k?!
�S	R'�
U-)
1k�-)
U�!S1��?		�-
)'(-#
�
-���;U	!
�	
	)�)-
S-* �	U5?#
#	#+�!k
���
-J�
??"S1?k)*��
	#;dZ!!S+ �#
�-�

R%	?


�U!SJ�S	��-�)
SU*	
*�S 2-�%

-

d	5U

52)-5h��?# 5Jd? ��?;)-�
!?!?�!-
5
�
-#-
U!!55Uh�%�
�!
*%J
�#5d "dh- 5U+�d5)U
-?
-)5hd
��(	�	

U(dU%k�'Ud!R 

-d%Z5(*!Ud
?22��?�2?*d�JR


�'R1%h%�kR?!(	d!�
�!?+5��J�hUdU
!�d�R%`�kk%#R(R'�d�?�%R2
4	


*%��*(�!kn!�!�k�1!%�)
- �d	
�1d�

'�%)�*�#)?*
*#?�R-	
!d�5?U;	
��?	�5?*;%�
�?
(
�()k	+
5RU;
5	dh�?(kU
*� Uk2('?
�J�2%�-(!k5Ud�R(5(
(d *	�U1d?�		R*+'�
�*
* 
?%�% �;!U-
�
		"5dd	)
-*+�	�
*�	
-d-RU(;* #%#kk#(Ud�R'2%�	'#+#R;
'
#(5'�'�?k*5	5�5(*'U�RJ�d�-)U2n)
-
5
d�#
	�
	2�?�%2#�
�% �h� !�2	�(U2?%�**1!
!hJ5
S#??
*�#	�
1 	2	�(�h	*! �5'�S#5d
J(�U1�'%!
R	�'�!#?SS�d5hd(Z?�5S;	Rd	R!dU )�!*(�dU(�#k(k';�(R�S�(Udd	�(*�(1'5R*5(5	;U#R
U%�RR	�5!�;	R(dn�R	Uk!��%
-
�J"('�(U!U
#!�(%� 	
(*S�S�
%�
-)�
2�!�Rd;�U ?�
`
-J�;dd!�	Z%$)�!#5
;"�1S�n	�
	h;	n1SR�5�?#n
!
S#d;�5(;d�kh�;JR

?
�-	�U d�d�;�-�)2,Un
n
�%) %
%J�!%'*S%k�
d� *�?;Z
*�(h5n(2�*2S k5� Ud(*%d;�Ud�?
%1*��!S!2%(2dSk
U%d�
�
?')

-2#�#
*5�
	UUh
%	*U(5%�#�+R1�R(J!%URR*U
#�(S	
#� 
�#
%�
!�?�-	?�*2�	?-#J "#��?h?#(R??		��
-
-		?�?�	
S%U!5k	�-
� ;?*5�d*	#%�!U!#Z1
#'�?;	#)#'?
!	
U
 RdU�
-��
	��-�(-2 2U
((U�RdU?kn
R�kS(U*�
-?+�kUd-�	-�;h

!U1Z;U�-?2;U�U(-	;d
`
- RU
Rk!%�'h
'n�'U;h
�;�		(�U 
?�(1'; U;S�**
-�`	�;d%
d;U�
-U!!;�
��(�	"R"��;?5(�2
-�5+
(Z
#?�d
;5;knJ�1
%-'+�d!1S?!
�;`;R5
5SU!�!
'#�
�
-#
?U-? #k;'?`?)
-
%%�'�%;4!;?+5U;	
-2;��kU-
	?�!�5k�*U; 1�k;(d�

#�?	�
"J(k5;R;2 �#�!(;�k;U?�k	?�;?!!'5��-kRRJd5�+!2;%J!�#
n'*'S2 kJ!Zk"5U
U #�5'k;R
-� �


!5%+R S2
�J	�?
�5;hZ25
d�k	
5R�J#
-S!5 ;�;(d�
!(;5dS
#
�+�2;5�
�R!;�dJ+�
d;;*�(kU%;	
#5!�hR*5;�d�;� dd�?�(d;�?;d-%	��)UJU
)
!�!*?))J �
�%!
J)k�
 (!�	?�'('R�	�
%1� (;'#�	
	2�)!
)�!J))2
�!;�5 �k�	�	5U!;(5)R2"'	
!�'
(d!	!;�2)�(5d5(d
%?UdR5SJ�%
�
�%�(d(�
'5;-)�!;!'*'hk�? )�*)'5�ShR	
% �*%	
n�;*Z' Z
	�5U(�n�!�)2S?
!`*) U�) S)*RJ�k	�; %
�%?�*

�J�'
2;�;J%�)�;J�5�5
�
-2R 
1�S%Sk1�
4'k?
)-%�'%%
%-
dS
�5d�
�)
-�;%#?S	d2��'	)*%�2U
SdR)S
`(% 	
�S *R%'� )�%'!U?"#�!�JS�%#�# (
*; !5(?�?�	
(�5U12dd'k�
1�*(!*	1*!�k��k�* S'5Z
+#*�1(	� 
2-
�k(;?5 �Sk;'k#
)-?d#S#�%�h%��?�1*�
?;�?!U
"(5JS�
(R5(U�R!�?
%!�1;h-5
�-�
	5�% �%S��%� U

2`?	
�dU
S�Ud`	?�R1(�S�
	�k%�SU (h
	!(S;'
((�
-)�'	�'(dd	-JS#�*	�#U
1�
UJd�!
J(
�5(� �2d�
-R5 '��R5?(�%'�'#S!`%# ?RU�-%`%�%�%(5SJ-	(UR�#!'�!(1�k(�
�
Z(% k(Z?*-	�dh�-%
!J5U5�
d(Z!!5dU55U*d

'	! ?�;�#?�#�`k�d%"'�'!	d�#(Sd�1�%;Ud�k!'�dU	�!S('"'SR%*Z'kJURdJ�!'S?k?	%?�5?S-�+#JRU%S�';'�1
h?�Ud)S�n
-ZdU
	UR(d�1?'?!�	!�
25S �Ud(� 
-)-�S
U)2�1	
-	-!#�	
	* *U)� 	-�kn !�)�;d1�;d-?�';'d;�)�!�
�)
* � n�
n 
)�%**!?kd( ;
-�%�?d�5�	�k'n �5U�#?!)5`!�S
;R�?�;
1;R?�d(d%�#�� -	#�	-'d ()##*R*�
##� SU#�
#

�dd�1+%#S�d(h�S###	hJkd"(`;(;�#? (R
J+#U( �;�
�	*(;�( RU1h5�;J5(;�)	
"%'*5;�UU R�;Rh�d5JdUU;�%;�1�k	(Uh5kJ1�d1�;U R�)�5d#* R�1(dUn	-?	
hd%RJ!Z� ?%d�%�!�
-!	�
;�)*#�n; 	-2?�U(d�2SU!(5
!%#*k*d	`%#�d�U!Jn?
!
%�J*�*�(R%J�+�
?h �?kSU �
?�n!U5R �
-k5
)2d�+#*R(dR�*�*!5�k5'%
dUJ	d!��%d-
�*;5(�5dU
;SdJZ-
�*!��5d�!J%�(?�-!2;
-*J�%-)
�5U2?!;�k�(?!
�
1�Sk"
(;5%�	;+;;d!Jk;hk�,	
�?5�;��?2U	
	dU�-�?n�	-Z;??2�)*#?1�?�#kUh?
-hh*�)-�%k�1R'��
Z#�
�*


-�
	%#;?;+U�
-(R	
�U2!R�

+%�?�k�%?#%k�*%	+�'?#'
�!S* 1	;ZUd(kd%�R%#?*RS#?ZU!Sd�d2?�)'"%!
�h�U	�kd!�	!dUR
;ZdU5%(;�	U
�d!#�RU##kd�%
�#�#�U%-?(!%�#�nd5 
U#%'Ud%!�%
hd#k*
�
	�d#-k�k�U;(��'�
	
USUd�Sk�)1-�k55hU*RU�dS?�'�Sk5Sd'�;J(
 *	!k�J	k;??RU;�
R1SU�d5S-!�S!
h+�*	d5U�RSd"S �h!k�
-

?
-d!h2�
5+�(Rh�(%�+ kS�!)$-�R5 1?R*
�RJk?R
� (5!�)2�5� 1 
U !#!

	�?�+%5(�?k*�)� 
� k*U
2J	)S�J��!R ;Z!�R d
h 
J)�* �%UU!�!	1R!U%Ud"�J! %d�k1��?*
k�15	* ;�*
-�%		
	*	)2�1 h?)J;*
�1 R1RJ*�*JS?#��
�
'���'J !�nk; %J�Uk;�*Z;1R
!;R!+h)2*�R1-
k!#�R;�!R!S�
;R5dJ;R1J�+(�R;11!R-(+R;J!+�#�S�1R5?+k �;1�R	U	Z	1SU;�1;Sh*
U(h+UR+�+(
�;?Z1)�!�	(d�(

 #�R*'7

�U	R(�k�R�#
-%�J
�;
5ZU!�5�
)�k!k5!)�1R�(+�
 )�U#;!(
kh*Ud#nU5�%!R
n�#
 R�#
�U%)U";!dUk" 2);")d	�2
 )�	�
	�J5
n�2%�R�
-#%)#!�
*%!#*;)U5%#5;�h(2�d	5(��(;�!�d(�? !d(U?J5!hUd
-
J;�RJSR
;*1kS?
�RJ-�?	1R�	)�	5U2Rn;� �'�'�;(
-�k�RhJ��hk�dJ!UdUJ*R
#?�'dh!d�
k�+U�Sn�d1U
1�
Zd?
* ;(5	kJ�*5(
kZ2-
#?��kd'�Rk(U-55'?*U(%dkS5J)?d�J;?	�k'k
k�(�	�(U�5�
#d"d!S�;J!dhdU151JSS%�2�
- (d�#
2�'	
�kU%J�'
J5'
k�#U
�k??�
-d
! *
dRk�' �nk�
1S!	S%1	'�J%??�-)#?)(�?�(5U;)*)2?U)�#
*;�;U!
�URhkJ��	)�%")U;)UJ-hn)USS �*U	�'?J
�dRd*;4
SU-?	d�R;U#k?*2-R�5	�
1?1��'�R�*
*?;	*?*-�1?*2�khJ#"	n*?�
�%
#R) �	
#
)UJ(�)-'�!(� '�k(d(�;�S�)		)*;R%J�Z*5"%�kJR2(R�2dU2S� R*#U5��dn�*?� n'�)�-;�;J!�d��	
�)*�#n
#?�?�
 %(dUk)d'��*+2 �;�#2�!�R(	(
�! �?-##)2�? + �Skd)S�1R%� #�%�1UJh!�h()-�(
k
dn;%J�
RU5�RSh%�(!;1�5d 
��
U
-?�1�J!�2%#��2
-	�?U1;�1 �k!S*�k?!�5(
-�#Z -`h-%;2d)-)S5U	� 2�R(!��(!RR(d!R�!
k(*!!�	�!�R	?�?	�)	!�	SJ5�!5%*h�!
�	%��%	
� ;U
�dU�*k1
k(��5 !
R
5U�1#d?�;?d	
	%5
�( dJ
k�   *�h�S�!*1(!�
-k�!d�R(')*
!k�
(d("	kd2�U dU�5d)U ��2(?d*#
5(R
35*RU(5%5)R�5 ?�;Rd �*
2dR(�U%) U* h;�d5#"�2�(U%(SR(;S	S(�
Z'2*)�Ud �	�US5U( �(R?�5UZ;Jh
	h�J;d
*	dSU�
�Udh�hS5dU�(#�
?�%�?k?!5(U�	5USU*n?�d(UdR�5(	5((S	5�
�?*�?�?*1k?!;R(Uk# #k*�Ud�*�5;J ��URdhRJ	J?k!�R	k!!�+�2?k
U(;�d5%;#� *5+!k�
h!k�kU*R( �5?d+5!U2�h5U*R	Z'dS�5US5	?dUh;
Z(;	!'�d;(		*dU-)U	d
d�S#	�
dUR#;+5UZ++�1%�
*#
!RR%?!%U!;*�U?d2R
�;
?+?2	;�
5?�
	!J(  !(S	+  )#J!
(+k
J5!U k?#'�J*� -Z#
5UR�*Z?(U
�
d#�(dk!5+�2+
 5(	?	
 �5k?	d(R� �'(;%U;(d#�* �	
2d(5h�
*�!U�-�	R�k!�hUd*#U(5d�2#!S5�2 UdS�5(	�
hU�%Sk��S?!-*�%
J2�UJ
2;�5U
)�%
Z1!-�SU';�

#�!
 #!d�*�d% )-;�h* J(Rk
-�;*#
*�d*��%"5*(d)
nR?kn�%?	Ud*n�RZ*'1"n�n�1 5�-)
'5
d'	

nU

�;1�%?�)J�k?�'1�-
5
dk�!#-
	!�%*+�
�
-- *�%SJ)-�dZ(US-"(
�(dR�dk�!�U(
U�U2(5�R U(2 �%*�R %;`(R%;5(k5%�?!J�2 ��RJ#�5	�U(*�%!(!5�d
%"hR�k?U;�U�RUU(;U"R�Rd��!RUJ;15(R!U!U5k �;R(U�dU!Zh!!R%Z(;h(�n5!R;R(`Ud5UdS5�(dU
R*(!�R!Z(�U(�US�%
�;�U;(d	U5ZR		�;*R*'�h(R	 )d?%�'?�h�#��;�(
1�'%?)?�';*�RR�U(d*
	
%'�)�!�R!n�* ;+R2?%)�
#S*U 	'+
)#!55d)
	?;�(;?�k;
n!�S

	-  �?�
�%
'�1kU;S?
Uk�'�%;!;!2�#
- �d 'SZk!S	'��
SUU5d�%2;!�;�?#�R!1	(�R	�S(�#�;?�Ud�%%	
)#R1d

n
Ud%SUd(
5?	
-khnU*(	U#
�?SSh�	k5(d		!212
!;2 5�;k�;!;
5U;U;�!!nU(5hnJ`;U(�(#Z�2

-	
�kn�JR
d(
�()

%!;*�#!�
-;!5�+�	 ?�2 	h;dJ� +-;d*
�(
-5�;dU
	d;1U
- '
�;dZ(5�2d;�k1�
-U- *	kS�
hS
(; ++#;dJ	*d;+U*Z
#;d�)
#k		) �d�k�?U5R	U�-1-�S-� !��U;�+(U%#(;�-)	*(
J	(Sd�U;-)	)�U5%Ud;*5U�UdU!dS�U
%nU(5�(%#(%)*�hJd!5#(%�)h(J%)*S(�% ((n
#%%())R�%( %!%+-	J5)*�JR�
	n�)�)�1�kk �	
	-
-?!	)#	
k(USJ*(��
�RU% ��?R `k! � 5h (U�
��?	�	(5  %�	��% � R(� ?U��% -	5R#�#%	k!JR-	
�-k(�R;;!�k?��(+ ;;!d
	 %ZkR�'?k�h5`	(R"Rd
-#??Rd	+R�
 )	�
*-!S�%
1% (5 #R
-!�
 *
�%d*d�
�;kU� h�	?�#d�??5S�d�k
%?
�R2) -�1�h-((dR
-
*Rk�d!%'�U!�%%	�d5U
Rk�dUU!*	Udk
-'kd�%%dJ%R;)" RUd*2U �UdR!
�J5�%R5�%d?�("	�*;	
�J (�R;	1�R�
!`
-%2�#
 �J%	-"n
%�nk1k%JkJ;k?�k!R�-�UR�2 k!�!��'(!UdJ!+!+�'�	�-#!� 
-�2�%1� 1�UR%R��!�n!� ;n1;+�
S!*�J#
R)�'(�k5R�#!R�
;	�R(	d(R(;�2)d*hRh�(R*;1�(;S
-�kR?;	5?�	R(�!5;%('(!+�	J5d
;R	!�-R((R�1�#�5U!�-?U	-�;d5S�5�dS1-;5U 12d�d1	2(R**Sk*;
*	
�(�?
SS�k�Uk#	5kh�	*
;Z	
d(	k'5kh�#
5�*�#��'*�2	)?1*U(;R	
�	�))!�
'5!�d;)5 !5d?)5
*5;
;
	%d);%`dJU�)�;
d	#	�-'U1'k5)*1�	!'?) *2 �k;k-	RU'�
?U;R?*11'�)�R*�'"'"�-d+	(U	�+;)k`(U!;J'h#(�Skh�?�'
R*-k(;
'2h*d�J!d5 �'
!�5k�	5�'5(�';UR (d
'*kh5(
�?S�		2'!#�?S 	?%�?(d�(R�k�'? !#J	 *�R%�'	S'	"h5?;�

*hS�U�U(d�SS	'*�
-� ?'	#5�-
('	*''55d�Sh;S%?�;�	'�'	!h*�*2Z('�Jhd4*�' !#�R%*	
�*�*Jd ?	+�d	U�*h�#+h	(%#J	?JJ!,Jd-	J'2�**SdS;�
�	
kU2�15�(d5kd�(?	Rd�';?!*�*);?nS�k%�(5d�! �))*�R*S�R-R-!��5

!;(�#�?S5
�?S
�*Z�5�;kU*5S���#d!	 k(�
( �(UR�
�?'�)

-5S�*�%5%h	)52h	! %
h?d;5
#RJ52d U5U�
�%1	�(J*%% S(*(% 5	SU�;Jd
�5%#;;�(d�k*(%d5(S�!�R�'�!?"d??d�2�d
+�!#UJ�US;--!(S)�k!%�U ()R(#
UkU!�(U;�J*	
R ;�-	�	%U UR�%d?�;k
 
%R;�?U'�;UR$-S�U5()k�
�(d�d�R%�'+
2 ;k� h!; ?h ��(U
 ((5U�
�J'�
(S�-�R'�2�S*'k�'h?!*)?	�U;*)d"* d(U�) *�	k�S-d-	 "d
'%	'1(
)�	�;�n5U�1 h?�Jh(�R�dR�Jh*)!* U)�;(�	;?2�S5dh+
(;�+,(RR%(%S!S!!n-2�)R?�S(	
��J4!5`
!h#�!55	k�2 ;k+'
�'�U�! ��U5!	
�5R(5(d5 U(k#
�R�;)�n	5S(S#�n	(d!� �Rd+�RnU5�1 ;��
-�%��%1* k� (U5J�'U
-
+1;5d5!��'S'�?�?(`?
�
J�2d�!�%`	
�'n
�'�%

'-%;�%
!�-J!!�2
�'?!%!�'*�2R1?�R#	�R2�*)	
+�!+(�)	*
!R+�
5kR2�
*)	
-#kd(;!?S*
�'5 	�(;d��;(R!;k'Z(% *	*-
'1(�?��
�	U*'�)U�(dU(#(	5!2� d2'?	S
)� 1*�J
-�d%�h2h*�%U-?5U-
�#�'+�d5-!R
! Sd5 �**S�h!RJ!`	
dd)�
-
5U5+S�5d(1*?'-	
5Z#*	!�?*�2	U!�1?�	?*%!!U55	%!#J?k?JU#S�%!*
?!'U?%`h(d("S�h(d*�h� *!k' hd�? ?1?�*dJ�*?S�!J�!hn!�2;dn �?�n?�)+ n	5Z#�d!5?n�S!�!Jd�n?*?;�(h(U'%k%'�5J7hn�2;
n�5Rd��	J!U%h n?*'
;1(2
$�'	�%#
Sh'�+�R *hU+JS�?;�'%!;)5�S?+�Z1h �#
	1�?	d;�n1k;�SU
Sn�)�k;nJk�n1;1S�?k;!(�R*)�!
;d�'k*!(�;R	R�	
k!'�'��;�S2"%1R
#kh
)�U
)��d�'R;
	�'#'*�%
�'�JUU �
? �;U5(S'� #�*# ;RUS !?� n!n	;� !�#?1' 	RU�!
-�kR( �!+! (7*�SU2?�5J
�1

?U5(?�
� (1� 
-U d�)-)!1
�S*2*n�!RJ�'n?S�%
#�5%h�n	!?2 �nn
5;'�-5(%%!+�U#J�
�;(Un**
R�
-J�*;
-S'*d
*'+d;1�)�
�#�

)-	RR'�Sd)U	-5*5;1�'�*1(�' 	R;2)
 �'!�d5�*1�R?	S�dU	2(�d��S*�!?S+'1'+�)2%	R5�!
2J!�
#	-	S 2�5(�
%*� ;55-�* *!'5%5�*?'51
-#�'�
(�'#
�
-�5U�'�
d!*Sk
-(?dkd(�51?kU(!	5dU 
-�5d)2+n
-�k?�5d�+�	

#�
"!`	
d))-�'?��h?*'n1� *�;n?�n1*?	
J�1;�n#'#!�'	'?5)	)	25d
-	��(15!Snd!d��
�;!�S
-	5k�)	�)-	
-)2	J(

 k
(d�;h(�)S2�
-SJ�; 	;Zh!d!d	�RU�
-�!	S5*Z	



�1RS

d
5*dd�k	n(�)*k *
�J5!!�#�SZ	%7#?"h"U%dJ )*	5UUJR'�#U
h
R�S
?#
�kRS )?
55- U�!;kSS
 h? 1*2Ud�'
�'2)�
-)(1R+��k(�	�'hUkS	'��h1#dR*(�	(Uh(;h�5#
d?%J(S
UhSd�J!S	d!k5S+�
�d
�SZS5U!

	 (U( SS�U�1+J
!�d

d�+!5dU;U%`�Udh`(R

-U	!
5J�!�*5(�;?�J�(h�!2 �#
��k�%
#()`�S1(	U U5;
-�;!�5)!;
1d5��'R
�)n+2	
Z1(	511 5+;--*�	�!-
5;)-R1�kS	�#hkd(	JU�-
kJU	*#�U(1�R1�	%	�-S;	?�k�S*#-?h�RU

�5�;1*�(J#�5�5	;n�!S*�%d*Un!S�#)
(� *
#n*�2 �n� 
�%	�
+!�-)
 
#'(Uh	1k�;(�%;1 #*((U?�	
-
�2- 
%h!;S	
!%
	
 !%�d	�
-'UU5"U!	
!?
-�%dh*hUk�kJ�%##Ukh(�� *!R�k`k�(1 '
)k1U( �k! ;�k"�S(d?�#d51k5dUZd5�5U d
%1d
 
-�%?S1
%�hdk
k*5(�
1�UR �%�U%15  h�)
-15
 S� k(d2)-( ��*
5�*%kk�d�Z5; �JJd�
-?)�hd
dU5�-
U� 5-�5���(	��U-�)�	;!U+
##nR�h?d5-��5(h-S�;Uh1#U5 72R1h(��(Rh5(�h(d�5))2�kdS!�()5!�%� !
(U �h1�J�*	h k��(R '!	h(	5!�;hSZ(dh� d!(1�U'�1U'Jhd'!	-)�1d�(h15*(; *)(!*	+U*(�h(d!	dRhdR�5-)�k�#	
-S�U�d(�	?�;'J�	h5)�U�!';%*kd

)-;JZ
-�5%-	R '5	k(*R
�(UR!1;
�'(�225UU
	 S;�5'!#5U '�h# �?Jd5
J'�5(d��
*
�1;5*'" SS1!1S�d??*�*�5?S1�?R5!U?�
'Sk�U(�5*�UU%�(d�*�U?S!?�!5d�
-�(U!R2!5Z'h�?�!(h�!��-	�2(d�?
;U(
	�h2U!!%-`?#?(	U�)2"+� J+% dU?�dU5�k
-�JRdU?+
)**?�Ud
U;5�R5�d
Sn�
-�(;U�UR;k'�U(J�)!)
��hR#S�5?#�USRdUZSJU;RSSk� S�S)�d(;S
R*	 (�(Udk5Udd�?
S`dU(S`dU5
S5�Ud5U;12"d�R(5�-)�*;'�#! *!�-	
	!5�J(U5
S'#
5
�(��5S�
*�
2S5(2*2*�51S)�;*�!1n�?-2)
S�S);5�*5'S�k;�*�;

1�5d�(UR�k�(	
�U
-%
%
-�%Ud+1�)�)5-

(5	(U��1
-5U#!�R�U(R�d-d�
;S
Rd5J!
5d	!R!U?#�*;�hJ- �#-
%R!?'�J%�('!�
	*
kk-kU�?*U(#*k+J�?)�k�5*�#!*#
	��!2'	?�-k

%!-)�)?!%�!�J1(�S 
�R)ZS!5S�	-!*��-;1�;	*1� d?	 S'�-2RU!5�JSS�!Jd?S�S2+k U?SUS 
		dd!5!1%(5U?`?*R
'
d' Z'!?*'!'�*�*
'�'�' 2S!�?(?(�
�U	
%1'5?#�)-;��
(	)"!�( k?�2
5Z%�-#�(�);*`!5#`?;� 	 �U( *J
(d�*1?#`()��#	k����k!	J5)k S
?(S�	 �kk*���d	�	
�hkn�5d((; !�U%�!
�#�h'5�#
(5'!5d? k�!U5U;�Z?Zd1!�-	UdS��#2;;
-
-�;(;�?d�SR k�2	�dJ;;h�;?`kd
�;�#�'S;d(�'�?�
	-
k?�
�-*U1
�12
#�
-�21(5
)�#!'!S�`

k�(!	 	2kJ#!R�;(

	5(U!�)hS�	1�5() !(;##� 2!�-S%U#U 
*�
h%k5�(*?�#Z�SS5%7'kS�kS;�h
 (S�# �d�!S S�% �'
-RS*;(2�

%
(R1
�(	h%� -d5�k;� 54	
)

-dR;k;?k!;U 	�-
�*)
5(#255?%?�?+?!'#;k-1�R;
((;�J
�(#;d�;U 1;1U  );R;�
`�!�
-#

�(%
��	
1hk*5
#");% U	-2� #
?(�#�(;5SJ�;�#%�5;�5�5(U�R�'
	(d55U
5-(51Uh2*S�15��#h3
	 ;nJk+��dn*	-;n�	-J!#�k+�
-�)h5%�#
-
d12�?;h5�#k1;k�*()-2Un�#!*1�5
U2#�1%�Ud	�5(	S#�k5k+)
�dh5UU5;5%d+	�!?k�?�%�-�SU�!S#kRU �J)JUU-
S'#*JSU5(7S'!�
U�) �%�R ' J�(RU;1?*�	
�(��%!�dU�!d;R�!k�n'5USn�d�h
-)�Skn'k�n)�d?	�U(U5# 	nRd�?
�#5S%k�R?�%

)#n�??d�#	
�
-
%?*�-n;
�U�
)
# �


?#- U`U;hJk5;�%�UkU(5!%UUk�!5�
k2�-
%!U�%?�1RJ;!(;5
)2*n'�

k�#�'UJ
;??%�k'�-%�%�(!!%!�5�'�-	%Sh!� '�'U
k�!� �(k�(*!�()#R;%�'d1%k

�UU	R-J�-
##
-!	�k
�
%
R�J(h
�(U1�S( ��d5;UJ2�J
-	
!

�(U!�U�
�U5-�
-��;
SUU
UdJ#dZU�J5J#(�5;(J h!S+�Jd5n*�h5d-n?"#k�(U!1d�
;(�R
;�k;�dU�5`k?	�5)?
��k%�#
 5(k5J(�
5S(U))k;%
"5J�?
**U55()2�?%�
#)!()?k% !���%hd5`1R?+)	)�'S�)�'k�R!
-*�!5
5d(�-�
	?d5dS)

#(
#�hd dRh!
�*1;"#�%�nn#
�#%(5R	nS?�%
!23
)	"SJ�U%5?�d?%n#n+

-��%	(JSZJS

� ?�-
S5()1;2�#!%h�hJR;1Jn!(	J15k�d5 R;!5;�
�!�5
% �(UUh�k!�
5%	(U!%�k*�S�U%dh;SJ!?k
%RU%SS! 
%RS1
*�d	J)U	S	�#�-h#k�-?	
*�*!�5�Z%(%
%�)-�)-25%5((52	2#(dS�(4$�5	�-��?+�)-�5(5d#�!!k�5�
+k!;!(n2	)n�	d%
"nSS
)n-d(
n�n Ud-��*RU(Z'%d!
# )�),Sn�U-J*�-)R+2�dh;
U)�U� #J%)`�)S � 1*�(R(%,n	d�R(*��(UJ*d*�5�2  !dJ#�d(n-!d-)
%J�SJ �!S%� '+�S'R�+RR*�*%'R��Rd

	;k-%�R%S�RJ	�-�JR)-U )'`?SR� �k�J?U 1�-)2�2

;nUk
n)
UU)
2�	k*�R%5d�	# n�? h`'	*�n`	#�nS'�n�S!#�S�5�S'(R�?d	')
	
�)?�'�5k%'�S�d!S�2�S�kJ�S'S!!)�kJ5?5!kR!JU	
�U5!!;
U-R 5�;UdUS� �Rd(k�*S!)�)�
)!U*;!k(Ukd�!
-�(!		Rd
-!!RUU
 �!�!R5!�2!-
!�?2?-�U�
SU�*!-
!-�!h%n;#;5'	d	�5?(�S	1Z!!!
S`-�J(�
1	*�(d�J
-*-;%
2R�?1�'#dJ	�
#;�?
h��RS!Uk h(��#%S)!;	)�(5-51�)5*d
!5d�;1d �-2!5(?Z -'1'JdJUS�d?
-(5d(�d�1 S*UnR�d5�)d%R #�
2-n)!)�-
'�%��%!?
-�(d1R#�J
-)*'1)��
?*�

(?�) 1	h*R-J	*2�;'Z;RJnk;#�%�SdU��!dJ*%;6%	-2�(`

?�-%	%hd%khkS�*% �
-d	%	 Z%Ud*S
hk��J�) �%�U%
*%-!;	%** 
1*	�##+*S?5��*%�'

%*�JSk�S-"#;Jnh d�*;�1?�dd;!��*
-
*%R;� `#'#�n2nS
��n%n+
?�
�S?)
	�! k� !
;S!	)
�U;�Ud#U 2�*k	�
�5k	#!;R(�d(5�%�2
�#	)�#
%;'?1U-(�)J;S�;R1�;?�';U1Z
-!;*
-)(;�d�*d#`k(
J�*%U'd5!�%5 )
#�*#U%?
R+'4JS�JS+�; !Sh�;�(#`SJ+d5�k'+"JS�	 �5UU
k 	#! JS)J	d!?(�!
?()� !�d#(-RJ(((5)R(S)�d#
S#
(RJSd5U�	�hU(�
R!!d-d;
(Uk�-R(`
d5
Rn)hn(�
*Rd-!`% Sk
? �);*;?J!�	+	%;(R �Rd� � RUd�5k� �U5d�(R�!RR	5!U�d�(R5�
*U�
-
#�dU# +�RJ5*	!d5	!?'!�� 1*dR!*%'	2�%*�(k5	U(	k�R�)!
;"!!�
"'?�!�;�J5!J�5)JZ�2)J+

�
?	�R(�
	-�?R�%;�5�5(�-J)1�JZ);
*�5k5�('U�?%
	%5
?1�%�dh#�
?'!�h5d!dd!h�%U%)Z?�k# 51�5dU
�!
;
�;?d-U
#5 *��hk�k 2(� ��
-�	?�;#k�?!��J'!�;�#*S�5 d� (%�'�	�*?d;?Z5U%�h(U	(hd5 
(dhd(�1%�U%521�1
�Ud(S
(� �	(	)#5S�* SJ%U 15R%�?dU1�	

S%
�-		2�* '+k�1k+�d1
dR;�-�?d1;�5!h 1d'S`*�-S5	%*	!nh?�)ZS	�(5�#* %;�?-*
+-k
5d
!RU#�*%�(-(JS!
!Z!�51�5!J�U%;(R�
U!h!�R;-)
#	)�-RUR%�?�'	 #	#%%!�k+�;*1�1
�' U%?�-#kS�Sn##�?k� 
	%*k(ZU#�;�R�?+?Rd�	
�n!
+) Jd#�

R2d(*

+�#�*�*?�R#d
�*Uk�
�k��#%*	� �2*J�+5��'+��!;�	%-'%
 
2		�d;�!(�*;�;�;%�n�Jkn!n�-%�?#!�5?Ud)'J"S'?))J
% � U(��!S�
!?�d5� 5�?�! dd#�J1%()(( '( %1�	`#
'�	J
-�%
'�'5k�'(*��!kU?	)�!k 5-!�	2�U
�5h!d)�%Z?2
?�S�n?
n-n12,RU�'
k'�nk?	R(d
12	RUd�n?�5k5'n�-n	��5k�
k�k�dS�!R��
*d�;(dJd�!
5
�(#�5S �kU
'%�d*%�!((-�? �*�?n2 �
-��
5%#�+5
' ;)* ?;?�k
�;k�?R	;kk%;!hU5k;!�)
�
	5k	?�n�;U-�n#Z
*	nnSUd�%1�';�n��kURZ(*RSJ	 !�*UkR?`kR5d "
'
JkR(! 	5kd�U`U! ??-��;�U!(
k� S1;-�	*S�JSR1!#
�)
-�-�(Jd+d%
k;�;*� J)*�d
(J5 %)-��U)�n +�
�	!1	('�%( %*?U5?�R#S�'+Sd)2��;#515dRJ�
	 dR-d�Jk%?5!�)25Jd�)-?-)
-
)�J 5 d51?%
-'hJ�*�U(!kS*� )	US �k;
	5+!'*R`##Rd


�)k	5)U�?"k
��S	?;+#"J
�1?d(%Sh�'	ZS1SJR
## #%'�)
`
�k(�'S�%kd5�U(%'!J��
� 
?
;	#SJU	!* �Ud+�*	#
U1� 

�%�#*�#�d %*	%1*	*4%?n	
?
dR#*1
n;k#� !%
�J R;d(�nd�k�k	RhUn%
�*?�J'#(R�2Sd5�
*51;�;UdU(;
5d%%(d�;
k5%
(Rd�*�;%51?	'U;'
�(
d5
U1k;*5�2#'�	1%�*)�k� *#�
#�?� 5�
!!5
%�	�!2!*??;J!k2%?*(�;R?�;
�;�(;;
?;�?�;�	J5 +-1
-kU#hRd
+'?d% � 
-R
#�J�Jn� 4d512  (-!2 �(�J2
�-	*R1S�1)� 2k;d;dJ� )  �d(;`1*'�1JR�?!5n'�%S
');d��
-�"nR*
	%�J5�dSU�) �!S#%;#(�?)	#�R1d%�

* 6
-
�5S
)�*�)
'S%�
#**Ud;UR!�-)	kS(%�S?'	J+
Sd;?)'n*J+*�-�'�1U�(!J+*�!�%!)%		d�U(1R;k1�% d!;	 51*5�!51UR1d1�?(?�1 
1U�S

15(!)2�?%'��S!#��U(J5U'+ZU5'#J(
�k�S d5- )�;12;�S+!%n�J5  -�5d5'S	�UJ!�+��U)5JS "5�-
"51d2-!2

?#!#)R�5k �J	5 ?;`!%+�-�)*
)-U5d
%�(1 ;5SU
*
�!?k2-
?Z	#%5	()�?	5R�?�h�#�(%��5d;�h#(�	?�k-#5�*!� 
-Z)(5
#R(� �d!*�J2!�R;�#SR'
'k�2	��S
*�-;h)��'(5U% 1S�R�'*"(*�
	�;
**
�
�
-�5(�n	�+S*-'?�
2U(R`Z?
?)5'(�;
!`?5
(#�(?!5k#�(�) ?�	)!�2�#1
�
!S	R'2;)�12�%S%*();d*#d;?�2�(5�'�d2'	
(5dU'U%??;(� ?�R)2R; -
S%!U-�#1��U5#1�Ud!�n!2
S
�Udk�
n�%	;�U%;	*�� S�
#d(n
�*S	
'�
U�n
�	;*?�!UU�hUJ
?�?)*U#%)2k(�#
(	k(k;	*
(�)!#'�1'%UZ? %55h	U'�'*�*!�!R�
	
�

-(+�!U	(!�1�
-	 2�#S�
�RJd#d);Jh!;'k+#"n)`(S
!)1

R*�d			*�
-;2 #?J!J
()�"'�()�hU�) 1#�)Z?? ;'5J�?1*#	?�?(RhJ(1d�'�(d2R;(�(Ud1�?
;�-�d-Z� (%kUU� R
	5	
(J�� 	5k!	kZ?%
Uk%�	
�5?;)-Z;dR%U�-�k;#	*;R	!	�d?;�US(�;5�%(S#�;-�#?!k)�#(
%	5�#
	-
(?hk�+J�
�
?-	-S�' #�("R��(;d;(
?S RU�R(2�
	dh!;R	-(	�kUR?RJ�R(!!5�(5S!	k'5dU!(R	%#((2Rd53U(!�%hJ#"n2!�UU#
�!*h(URdUU'd5	�2*�hU(R�1R%�Uh(U ;!!h%�U'R�d5*;R
-Ud �dU!�U1U?!�*;##
	%-*( +#*;�)2�2�*�+�J�h1�-!�)U2d�#!�1#+�d2�;�-�%!�5U	
�5�SUd 52U Ud#-%!+�-;U?1RZUd�
#

UdJ�RUR;R	*%d2! �%dUd%	dU)S�?
RJ+Ud	
?,dUkhJUJS�U)-�U
 ;
	U%U�;!*2!5 �5U R,	USU5�2�R%5
�2)�RJS
!;U%5
�dh#�;UU d!'�)5d5�d)5�-� d�	�S1hR�
-�)�;k

-?�U25)U )U)(##d(k
%-�*d5�;	U�-)
�(�-*k+�*2�
+('?�(	�*)-hk+*k'�-k!%U�?�) -+*�J?)
-	hd
)-
 d�
+�`
�;+�	
�k?;�
)�?	?+'�1#%!(�(
-5�'�S'h�

5d	S?�)�')+�
-�U	
�S

h�(	`kR�%5-
	RU

5JkS%�k%;)�5	!U!*;�
-;
nS( ��%5'(#1)*d	�%1 ((h!%Z*#?�

"(5*�?�S�-
�;	d	-k5d#
5kk5�5UJ�-	
�
!(5Ud1U�+2U#�)2R;	k
	kR�5!J'5�S!;�#	+��)'S*�
k
-#2)�?-))d	5�'�*?#�#� �#�5?!U%#
�1 �k%5((S	5S
#��
"%�	-%RdU�-#�?	5+
JU!+?%
"�S(�%�;k	���kU'�%R�S)"%%+% ;(5 �'�h*� 
�
2;UU; 1!k! R+	*U `	�)2	U?�(!k�R( Z+#*# 5%U��)-*(!5,;�R;d5#�' 3!(2�
J�Rd1
 �S �
RU22k
U �
!"*�

�#),5Rk)!d	�k+�' !�#?�''kU!R;?%�h�"�R�d�5JU;d)2Zk#
�;Zd�J5
�R�
)k�!?
5k�!�5!!k(%dd�J�!d(;d�dh%1?k�51nU%�	 1
R%(%�
 Rd*%#2`%
�Ud-J�%*� �kd?h(*� kU
-!Rd� �#?�U��
'
-dJ�  �!?(5%Rh(U?5�%U	k1h%)k%k(�k%!!k!+� J�-S	(%2*�k*SS�k%
* �%�S(#!R�1�S2��)�!!!5�!	U�5;�S�;R�*�J%!
	�Jk�-
-+5;�2
-�;	!+�1hkSZ�%1
�(J�
-J�%kR?U(�)	J%��knk#*�U!�*J�#	#
S�SU�h�%k%R��*%#	 %*		n
%#
k(�*�
R
*?+�R�*#)$?���
(R�(R#J�R()h
R SJ�S5
�U51JS	
)-)�%*	
��n�d5� (5* !�
� U(
2)S�1U!;�)#�?-5*�(Z?	�*Z+�1dS;JU*��hd!�1!d
-!��!1�R
S�
-
 �S`5Rd3;'�';	!;(R#RS(5`d5(�-*!%;*;!!S�*%(d
!?-))�R�U*�*Ud
'h;)-*%�UJdU	(U�
-�15*�1�5'-)�2�
+	5(�?�dSR%�J?�%? ;! 	�;)�U�R`S5)�;)
�
#1R# �U2*#*#�	U))	�	�5%( �h�
-`%d�*	dR# S�S2U	d�(S1%J� 
-
h�S*�J(R�J�%�+�(2'R?'� 1R'�R # ;5d	R�2`?UJ(SJ
-?1�!!(Uk�)�R?	�#U�
'�%R�!'#Z#;R;� Z% #!;�!	U#%R'�
#J%?(*�
(�)'�*R�	*-Jkh5	)*	?
�d)d)?*	h	1?d??�h5J�*�
��!S��k(J+�(!
5)	5?"d
*%(
�
�%h(�%;*�#*k?5�	?5�-	
S;
%�5R1#�'
%�+;S?dZ?`(*

5�;
-5;5
*
`5;(�5� �1;�5Rd U;?5%%2�#�1?
-� %R	*;k	2�5;�?
	(ZS??�d;! �1�Sh	d2
UU+�	U�%�*?-?�%?��1
d�;?	�5
�R� ('?5'2;�U5';'	U()�;�k	�kU 	
'�2-'dJd�		;!U'
(�S)'d'()(�U(�RU?�#�	R! # 
SJd"R�
�(2�JR+-!*R*#��
�#!	%�;�-
�
�R�#'SR  1*
- �R!�-
�R!d
 %�R( ;!U�;U(U�(-(%1�
?	) *�5*RU 

%d
h%�ZU%(%dURUd� R1)
J�R?U%d#d?*�
%U�*S�h;(�)%#h
(�dJU�(UR
Ud�	*

	
;-U;k�2
h('U(R5�h	 !�%5dU��*�	'	55'(
�5#
�5?�R
)�dU�
-�5)Uk�-!*�*(U !U1d�)�#�dU�;!R%�%��)�!#
5%Z
� %)*	%�S-*�?5
! 	*5�;R`(�#'
-�*#%!�*�SZ !
'2!(!k�5)#

1�Rk(�!;2	
5� R*�(U`k� ?;�dk#�
#-k
'%55Z5(�*2UU5+�'(�)k
�kd1�2J(!d(;(!d
!U!5�5#�dUhU!dn*�'1?�)
)!!5J-"+k5!'*�(#�h5�'J;�	%
 '?d#!#;#	� kh�hd*k#
d?�5(#	ZJd*((!2�(1�Ud�2  *5�k�Sn	?Udh(�(Sd�k!�'�( 5*5	Uk?�d
()��h	5
�#!*�%(�?S	d�#�S?R�?�hU�	
�

�
	%S!RU�U1�%*"�)'?J%SJ
�%(#d("d%%h*;�;#
1n 1d(1)	
%2JR'%�1�?	(d�*!U5�5�%�-
	ZdR5`;,'(	�52	
�!(�d5(5�dSU+�5S�;d#5�R%�5J2
S(
)%U`;	`
;k��%*;%d*!�5kd �	�S1�%+�2 ��#
UJ%"Sh
�U5Z+'� ';1��J1�n* R'� ?!�
J#S
 k,#('%d;!R�%R%�#(5UJUk
�%d;5J2
 Jh(�J�UJ('1+ 1U*�hU	;�%Ud*Rk�U5�(%5 2*U�!;�d*
5R ;1!�!	%!�U *)�*5
U(UR �*RU;!S;	;�UR(�R 	
�;1(U;! !1) *!R
k�(U�h%%�*k`*5
d# "1;�!hU�))2?#�5� ?k	`S#)?#!)d#
�)#�' R(S#%;�d`J
U;R12dh5dd
-	;�!�	*R#�'d�J!��)!2hS�'�h!5+�
U�;-
;
Ud�'�# %?
5#dhU%�R)'R�UR(U
(U?
)( 5

?
h�!'	 (UR�U	*UUkdR(U5�U�!;R�;1�5
U�?U�!�#
�5!�*)5!�5%�R-�)�%)d(Ud�5d((k
;5��R;%5	R;U!-
�%5)�U	Ud5-
;;

�*�52(
n!
5d
1�(;5
Jk�)2?kJ 5
!
�
?�#%(51(�*;�;*�	5�'#1 'RR�5k(
hS�R5�khhU�%;? �*�

S!'#%
U?*

U	!5(#*2
R�?S
*!�;� U
25UR()R�'#�2( �R5-)�US5U-
1!�
(		'	!;(%`%(SU(2(	)
�Ud%�!%�Jd7�*�)�
n	+� 1�1;�k�* 4kk
k!5d;�U)51
Ud	1#�5(! k�(d�Rd�1U#Uk5k� h(
kk�U�JS*
!#5Z
�		��*k�!�R*!
RJ#	�#�Rk�*#!(RRJ�U!%	
Jh(�U�J(1URUJ�## 1h!�U%R�-�h!R*!+;1	�;U!RU'( �5J?1k(27 ##*
; �U�%�R5U*1�5' S�RR�*(2

UU% RU5�%)#'
U!R%;�!##
(h�**h)%	 �*h��2!�-�1k�'�%�	J�!+�';�+�J%
*�1;�
Zk�5�!?�
?�U(Rd; '�R!hd�S1�!d*S�
�#)�
-kSh'%5?#';
5U' � JRUS)	h%R)ShUS-S%!k
-��US
�% 	!*� d(J2 #
	!hd�(+�?1! 2
J??k
U)�
�!4k'�5'�5(5+`?�'�!�k+ �*5
#�

"! #	�1
*# k��
*)�';�**U�d+5�(U
U
U �d5	�

-5dU1S5d �d
5	�5 ;�d%5�R�d�5�
-S��#*�'�d)
S
�)-�2%5)�
�S�
(5�'5 ?d(5d�-

+#�-)�
-?k��
5h	R!5	U� -�)1� !�S5!U�-)2
5S5*(U*5�!
-�Rk!
!(�#�S)" #!d* �% �	S
!";--UR�RJ��5
-�d !U 
" J!U5h�U	U��*R%+�U(�	(?(S�R2)-! !(%1�(R�)
k�?n�2S(5!�#�1k+%�%!R�k�';�%-'15!S1�%S!%*�; '��J�d%	


�+5 �'�;`*# �

-kS�S

 J�%5Ud�R �
'
�-)�(*h;�'�'#
)�!Z	)�-R	5?5
�S!	*�#��2�dS�
�J
-�-'�
!5!�J	JR� �( U)
�J1�kk #52�J*% !h1�S-
?#S��%n;Rk%!+k%%
h%�%!k"!%)*k??#SU1dR;(R51!%k+kS�
-�R`�5U%5U-!�S	S)51�'#�UR)5U�khh'�1 
� 11#	2	
��)2S�?#nR�#RS(;!#1d5�'�(UR1+n�
51n�
-
**�	�*�k!	dk5� 5(; `k�5`dU;k-2�1%	('�1d�)-J
d�
�#%?�(RS	 Uh1�5(RUU�%U
+?R��)��
n !+
Jh;(R�SU�?' ;)%RS!�5kd+��!(�5d�U!�k5(U!�n#S#S�;
 SR*RS?(�Shk2n5k�?+?�R�);
*?!nkRS(�!5J;�)�
�;R#( d?#'�
k�;?d+R�+�U�S�R
�R2�?5Z)#;J�'RdJ�U%�'U�;'
J'+d%	';k�Ud�k`d)-;
(d�UJJR
;Jh5(5h�55?	2`)154n	55n�	Rn
-5%5S�R'
� S"1?�5���!#�5!*%1�1�?	J;�2k!
n`?;!n�J;k!k�n?'�R
S;!?k1!�
;�n
+��h15%*!?�+Z#�R (
*�17,�2
!�S*%�J1S%�n!S�RSU?!J`n!J h)?JU�+ (Ud?�JnR)%R�JU(n�%
U�Jh+"	*nR2�h?;%	?!Un	*	�?R*R�1kn�h#?)S�%1�)
+n
���	�	 		�)�n5h?k�d	
*��?;�!*�S;#(�S	%h�(JS�'?hS2�'5%�%(�%S�
�)
	
�d)
-d*!�k!!h!�*�SJ�	k�hk��	h�( ;�k�#
	)-kSJ�
#`-(U; �1;`� )
5'*h;1 *!�(*%'
� U�
S�n5(�U
 
1	%+`%*�(!�;�n�*�+�JS;*'n!%n;d*(�#-kU#�%�?1%�;�-	
'%�%(�kk��kS1+%R�
-	�(U�	% 
��?
#
�'�; 
�(JR�'�?S�
;��)	*-
"
k	2J!
k'
S�#
R;
#�R1+R k
-UddJ�1�#%
Ud
#%!U"(
#1h�	?�-

1*�	)(RUJ�%�)
�kJ�!%��S(?�(? �hU;`('+!' %�'*#hZhS�5
!
5R-%�+nR('((( (;�;(*hk�U(2d;�+;%?�+12�?(!��!;R(*#S*�(k+	+!�(+S
h(-�'S�J2,R
h++�%�# �;R�R�1Z
	�
�(%U�#' �;R)(!B�k�11#R-k�d%#�#U�( 5S"*##k� �!U�R
J !;J	nU`

�)�5
-k
-�;�Sk� �k�?5�h#J
�k; ;�)*;�1	U;#?	d)
U`�
�
k�))1U
�;h)-#�5
-n5�hd)h5U"#�!
�d5 -� )	�kd�Uh;�R#
-;�-kUJ-� )�)  % U	-!!RJ%!	
;,hJ+
�%?�
%(-�

)-�R�d#�?#
(�-	
#hU�hRkJd%J	S	 1S!Uh� ;'%d	5
 #'�;;h %Jh�;? ;hk	U5!5n�n;
�# *?;(5�	kR!5'5U%h�(5R#%5h2Rd)h5�
5
Uh(h5;S(5!	*R5		
5�S	dh! 
�
	%55!(U�+�U5 �! Uk;?'+'5
15%�
	�hJ%?�UU�Sk�kR5�� 	U �k5! ;�;R!(U;)�U1�%;
	�)h hJRS!RdU;(S1nJJ�hUd*
#*;h�5SdU� Sd��J1Ud�
�
S
�kn*d(�
?#*�dU
#*5�k#?R;'RU�'525
Ud!;�5hdZdJ(�%	)-(5d
-)�!�%)S5;R!hd5;?�	*2JJ ) S!* � �R)'
 	'�?��)-#?�-%	
"�'#`?�	n�#(�2d �d
2 ) !%�U%�! U	� �hJ!#U?�	�;)��);��J+n
-k5?�d�-);!d5?
n%#�!U(� (!-;;�
k
�-U-5Rh)(UUd?�2*!JhJUURUhd�J�JU�!S 2hJ�Jh5
�))	(
5;5	;�kdR!R!
5Sh
%+5%�+;�!n;	�R�#;	1S	�%
%�k%	� �Sk#�!#!�%�%	�%*U?�J�U!Jh
#dU#J�
+�U

'k`�
?n�5!J)�5h+k
�5#�* 5�%� 
 �Rk%dh,%�1)Uh ��� 
 	*��d!R#
#  #
S#1
hU;U! 1 (%(%#�R	 S�%R5S�';�R�	)R�#�')*�'�#k!)RRJ	5(�R%S�%S%�;%�
	hJ,h *S�#+�%RU�+S5
"h�
(J5""5;h�(SR
�R�J2�Jh#URUShJ	#h)5JU ?�+h� �h hJ-�R% 
;1�d%	5
5�U
�+�)�#R'U-'2�2�`hU �?�-%�
-� �%h)
nnU	n?S

*h()h(�R(�h)�
�!Uh`%S�Uh�h%%�S5((5�#
h�)�k�;5!J�?'�(S5�R5(J�JSRU2%1k�h%
2-;(
1�
	U%��5S
U�2�1�5J�d%5	(�
dU�1#k1�
J1
#	1
UUh�
�
-'%#)
!2%	#�
�S(#*�!��#�- ��'?	#%S�;!!�hknk(
�
U
#J� (2�5+nR�h!5!�n !�1� !'	k�2d
d%��5;�U1�%	?		%;�;
?5(J5)�%-�;d�UU!;�hd�
?Jk	�;(dR5Z#
*Rk	R(k"5�	
1'�JUJ�h%!*?S 
d!�%�
	 ?

#?S?�?��U2! ?+'%R
J5�k%5	'

k�
-�%!?�#%k

(�'kSk	)%d�S5%hd;-%d�n5
	1h
J�hdJ�-1�d'Z;-	
�
1(S�;(!


1#h;51�hS%d*!h�!5hnS!h;n�;�;%�%	(;	%?";%21(U	�-
	��U15J;;%dR
;�USd� *;S(%�
-�
(d2k+(�#+++�+#*
?+�1+�
�+�+R+#;�
�SS#�;�;2�!#?Uk%S%k�U
S�S
U;d5'+?nn?
 `'��n'�'�?#n*k
�(5k)� ;!		-dUh+*"��k"n+!�R 5R)�R%�1R�;(�	*+n�RSJ�
n5ZR5	)%?�UU5�	
U*;
	S#�!�?+�!�h+�Sh%k-S+JU#'n�
�(+!*�S'5kn	 �!U5? *d�*)�	
%-!2	5U�(�J*h�R�?!?-	;���hJ�h!*
R
+h�hRdUkU%
?
**(�-
R�
n�(	1�! R5(hR	�kU
�R �R *S!�**J)R
�-*k?!R#
)`%	�
�	
�h5;?

h
-�(*	)-�J)1S	�RJ?�k(# SU
d1�#1!�
�%
U�U%("+h�5 S	�d ��
�?U*5Ud�d5�U�d5;Un #!1U�	)2
J!15-� �( �#dk�S?�?S%55Uh	-'k�U	 U%

-�5(dU
5%
�U5�'�#
�(	U
*%d)hd(�'
*�
-	�R(kR-(
UU5�k	)�?'�,
��	2?)�%#!

!-�k?"( !5kUJ5);#
%!J
-5d'%!h"!J�Jd
�
-! %)
Un(kJ %U-'�!5?S`�	2�S?#!S+S�?#S
S#S!R
-?SR
-'�SU�)�J	
-�k5U?S4S?	
dS+R?S!
4 �5Jd�5R;#�;R	�
�JR 	Z	�!%*		U!	�?	#?�%#5+,R?%�R�+J�%�h
-
�(U�*1%�Rn�-
%+
nR(�n#+
%R U�Rn�?
-� ?
�)+�%R�h�;�-k%
�*#	?#�5��
-S�'!J'�R+S� n�;%%�
)
;d�-R+�U(�R;Sh�'+RnS
R'%�?
-'!nS�2d

nh!	 !U2�	�??�
2%d�))*)
#�!?1'Jd�!5R;(1�1 2�
-;J;h1�h; +Jk�;(!�
k;Jh
*; �;J�	
45UhR;dJ;�k�k�5h%*##�#
�!�J(

U)2 ?1�	
!;;	k!;h!U)2?
h1�;?�;hJ�!*
h2�%
;�J5h�2�;	Z(;J!�5	?
%�+�) 
**%5(�
?)�Rd'*�U)�%�5dU �#!##-%U5
U!�
+;�UR *�
�d
(2
-5(*d�S'(2
-�+n�+�'!'+;U1( U�+1�)!h)!R;%�-UdJ5U(�� U��-hU��%1h*	)(�d*(�JUh)(! UJ�	(�S2d�%hJd!S	?
)-;1?)*d�1`
�1;�dd	!d)(;(�
-�dn!2�S2�*(U)�#%1	5d#�5Jd)d%UJ�(J�R?�)R! ##%R�R*#R#R#!�?#%R!'�*�*�k;�kJ;�*�
�-	�n)�!U�U5RJU�5
�%k�R*Jh�
S��;?S?(!�;+'+dJhJ%%nR*�d'() �%n+�h#+S%+#�1U?1%R#"Rn1	;��1+n;n#!k"(1kRh
)SU	hU5� 			*35S 2?!?k�hJU%�-hR�*R5%5U�5JU
RS?�h	R#UR�nhk�!
�;%�UR2-U �
-S2+J�!-Z
2,+S�);7+J
	�;%�)!�
*�%5�)
J�J
?�U�)�!5%�%�#R	#*k
�(5 �-?+5U	)

(�d�R'%�)-
�!JS*! �*S1	�S5�Rk��	
-J!(
	#�
-�1�	*S
1-�!;*Z(�15(*R!U�k�5*
�5 d`
*+�2	#'�#*;	�52;	2S2��nU�
S�	!R	`#*!2*2?1�
�hU)%�??+)�	)k	;'	*k

%"*	
�)
-)�ddk	�d?;dRUU1
);
�%U	�S%! U�hd	R�(55
	�%2� *�k;*
	%%'*?%�%
5	S�'h�
	
	
S2*1R(*	;!k�k
n	�S	(R�h�?�h	d�R#)h2 Zhd��-?#d	�U55�'R5d Ud*�dU+#�;SR�U#
�dR �#!?1�2!%�?)	
+�	-*
+
d�	�?�%#�-(
+U!S!Z1Jh�	-
!S5#�?�+;�hdU�?� h?15!1#!S#S-5�#	*-1#�kn	!R 
)'�U	!
�	2!%
;U)-
(		k-	*�
-�k'(!�?	�)1	)JJhR1�h5%	)	�!Z)	1	%5�J-!)�?1
(�1 	
B ?!-�
 	
2�S	1nS	1	11#� #Rd+�'4�
'!+�%"dJ #%S�h%n5�(RU	'* )�k%S�Jh	?�?�#;*(%�)?d	%	%! %(%(+!
-�1J(	�?
��) !U�!%�%�hUU1�Jd�
`JSh�1'(��!5;R�!h 2#

;;4*#� #� 5�k 
�?J
2�
�
)-(;�2*'�+Snn S?
;*n
(	S�-
n�n1+')R!)
�d5�h
,
)�;SR�5;�2�;))�	�;kS;);�1%(�;)k1�1(S(5ZU(�'�?�k
J�U!�k%�2'�"R'd?kU

 ?d?�k'*!S�R! )�S�)!R�1�'1
+h�h?�d#�kZ5R?!!�	?�;h*��+h�'�dh%)!?h��'	
1dnn	�-#?1(!'�;#�
R-�RJd�%�Uh`�
(*h)kSU��?�%; �;(�	U+1?);5Jh�?
%�-	 	-d4??;�);�kU;RJ!
�
)-�d#�--	*';h!U+#��
�;	�5 k�-
�
�';#
-?5	)`)!
�%�%�kd�1#
5! �R%(	n�-) !�2		k!�?S%

-�
)�R(-`?�?	�Uk
#�U%U%
dU;�-�RU	*k�S*5-
nkn;�#-5�JU�;?�
-#%(-�(d�h	*k?�
#*k'�
-?!
%�kn*'�*�!�!%k?S+';(R�#k	�
-U;RU;5��Sd
k !�1�
-�
�?-	
;�(�1#h�J51�
��;S�
?	U �dh(�
#5dk?+
!Ud!!*5*	�5h5!#%S#
;#!
##SdR �5-J1�#(%UZ!S	R(U	Rk�1�#�%
�-)(k�21 1%U�(UJ��(R�?S�`hJ	`k%5 k�(;�U (S5J�%S*h5?S-hS���;k
J�
-	J;?�*J

+J	+�-
�5?�hk�U% 
*d�#J+�d�'*
-�Uk�
�;(JUd
�
5-!! '�)
�hkU(-U)5J-��('
2�-�;U5kJ-�5%JU�?2�(!
5

k�dUZ(h	�15	d�R15
U *%�	 ;h�%	#;(!�kd ,h�U%S
!d %U)'	
�?d+;�*+k;�;
JS� !	5�*1�-)
�;R�';
;S�R;%�;
�k
-15�';
1#? �!U Uh
�(-�?�k5h5!dURd`% �hJ�S
�Ud)�';%U(�d!�(U�%U2
k	�%J-�;-!?�R(�?S		
-�'!+%�
	;SdU�
-�()�*
5h �k!	S())5U-	
�?"	n�1;	S*	!�
-;hn� !�d	d1
 Un4;�
�%(1(?)5#'#k( �!+JUd�SU5�
-1U15d�
?*%-
? (5-
	ZJ1U)�*�%J�S#	#% 	
-�+S2;
Sk�'#
S	J*?hUU5'S)hd'%*�!( ��2! 	;#S�hU	d?�#*�	 %2
 k
(?hS?k�S!(;
S �!S'5
)�21�
dh�5+d�5(Sd	?!R�	
-# �	!1S�#
k�(R
;�?;!U#5h(	5
h%U�SUh
R;�5(Uh5%d��%� �R5d*d!� 2#!(d4%(dUS(-	%5-;Sd�S5�dJ-(
#�)�#�R!#�%SU	�
*
J
'�U!%1UU-;�S	--�
�51U�R'h#!
h)+;5�!5 5R)
-
�!J%1J%+(-)
	
d?1-U��R
n)�)	�%�Ukn)-�dk�2(JRk�U2�R5
)!(
 JUR�
�h!�2�
�S��n  hU? '	)2�'#�*
#5�;#�k*)?R-�%RS�RUUdR!S#"dSk%Zk%	�d(��!1	;k%�%?�S1kS?�
�?;!
(	 ?
!5�
*��#'�'!'�#' kU�Ud 	S`(#'
#d�S"SR%
!;h#� 
� *d�J!R;(5SRS�)-%
#	S�?	'R'�%�+'%;R
5S
h�!kh1�1; �;RU�R'U�R1J;2R;!Rd5�;!(�!'�1d5'�(5�(+�;1!�1;R�')	Ud1�
-!U�RUk�15(hd
�k�-2�n!�
?2
-1%;U


'-U
	�%!5URU5)"5�'5U;5;k
�J;�S5U�# %�# �*'�)( `dR;R5)kk?h*!�)- 

� )h

	#n%�R(�
h%-U-k?(kS�#�n�J*�?-R5� 

-Ukh*�(%-U-n2%+�Uk15;� hUS%;�h(�� h5?2
%(�%
kd�
**2�?1
*
%?�5U(%�'�%5�Ud(*�5
)	5-�' U**nR!
JU)
# �U� #�J5�(';�	)!?	k�51�5(�;
 *
(%	5�'�
�
k�5hk�*�!*%(�!;R
;-
-�k�dh5;k�R;J� �J2�?
R5) �U- �
5
'
#�
S?U?#`'??�n�J�!-�)12�S	!U5*
-�kU� R!��)�'+�!k#?' R?	'n(?�(%�!(hJ? '5
U	�
)-�?	
U(%��)
%�##�!)�;*(!-)1�R
�?' Sk(%
-�� R#)	 �RS	
-�dUU%�?

5?�% #-�hU�-2+5!U)5'�U(h�#S*�- 2�
J*)2
5(	�12h	!�k'h?R�2�d	d�'!?
*'n*
!5�n
)�?5R)
� k?'#?'�k�#�?k� �%
-?kd%5S�	%�
�5)�5	
�%#�(R	#	?#5!#
 `
�! �	d�	R5�k'(	5�?�(J;S!" 	(	5 2�
)�`!�d(k��n*(2� 
#?�%d�S);�;

hkU�
-�;h�?%-RU?h %k?S'�d(!�
?	�#�
?UR�S	�%(d�;!

�))S?S!2
dR

R;��?
'	Skdkk%�*;�R�*?2�k!�Uk�?dn5n1�21J-`h�k%!�n
1R�
	'�(
n;%U2�+(	k2 %!S	#
�? 
-(R�(?'k!?(kU?�
)#?n�#?"hZRS#d�R(S�h%(#?�;(`�(5�R�
!-�R	
(%���U!	(�*R�;hd�
U �'�*�d;hR!5;�#
-�d;�'RdUUJkU;5�	(	; 
USdR�21�'n
;�'R�hd!*'#�h
-
R;%';�!�k*((1�d�%�U�5d�!�
5)�5)2k(1)*!!d�*� (�# �''��%�?!
�S?
�?'�#
d'?*??�� #!(J
'-�n*1J-�'(�*;'5dR
!
n
R)2	
J;R�;k+k1k*1 1
2	)�d;� R1�?! `k!�J
d R�d�R
�; (!2"'%	'+'SRhURZJd-#JU%(kh�!�k�U (��%	 
%'J;	U 2SS�
S
S�'R�h�%#?5?�R2
JU)-Un`J'��%)?�#�kJ)#5�2!';S2�'5R'#�'#dh�#d�R	(RU	�JR!�;R(- �k5�1;�(
k�h!*2�?-?�5'
!(kU!RUU !�?	 S;	�*#;	R*�?	�S#+
 h�+�-�5�5 *h-5�kRS+�R!k�R�	
n?;U(5�(#'�S� Ud�
(	h� nSn?�);�(�k'nJ�hUn?5�%JhJhS�!2k
�';*�%#'(
�('k1�hJ�h%JUJdJ 5-Z?)h?2�d'�J
�!
*�kn�!5!*d�R�;�!d*d*h
'�1�5kd%SR1S 	'�!US��'(*d+��R!R*#('
?h�%d
;�!d�;
�U+�%?d*5�	h�)-�d1�hkR�S'#
'S+�+!#%�R!SS�*U#�J-�U(4
) �h�
)?�Sk`)!%
-U;!'�)*U�(U �55?#d�?h�(k!5'�RJk;�S! �;hdSJ5	'�5h�1S� 
k?-))� (5;(�	�52(;�;d+	?S(;�#*�h�R k5#�;(	Jd"h �?S#Uk`hRdh?%*?�
%� 2� (;�+ �Rd �U�(d�5	U?!5! !(�U;(�#?d�U*�J!kR!�U25
(;
R-kS;�J(�d(?�)d
#�d��SJ�SRJ(�;?��'-)2dS�
d�U�	�k1�#�#(�*#S;Z#
dU�`'

-
?;J%R;R)%	' -)�h*(�#;d'�JS#R�'#*?!*�
)�%SU%J%!�%R	�R�%%kR�5k';�)(�n#%hRZk�%R�RR�?!%*	#�-
k	
R*;R*�(d�h	)�%'+%� !(!h	R5�%d2J�%5!h�'
#�-
-k�%-�;d1h
#1
k�()-�#�!�%�(
'	(#;h;d
-#(�5(R(nU�R*	5(�#(UJ+"!�!( !	( ? !U�?�!�!)� -�(?2#5*5�U�# �
�'?,	 dU5 �;''#�!!!(k-� 
)*�!%-"%k�k!�)2Jk�kS�
-% �5h5;?J#'*-' 5	�*	%Rn
;�J?(�!%
;+�hk-(h�
-�%h(%!''�k�U-
kh
Rh;�(;#S�2	R;'!(!�U�h1U1R	d��*k�!
J*!%;?Udh�J
-3*�
-`'�)#�?#d!�#��d�J
))*'
';-�5U;
�S	U(�!�`%�1 k�
- %R�
Sd	�!?�(k�;#n!J#	 �k")
n;%*
#�'S!R%(�%
5;J?�1SJkS*5;U1


(?RU*R	d� 	'	U�(RSh+�(U�)
�	1h

	�J!��R(�%
�?
�5!�`�%5�)RS
�?'!U
?*�?�5-;�	2��'�R5?�?R!5!�#
-!JR��+�ZUkS5  51�(d1	*+?JS#��-	$UJ�
�#-
�	1
R��#�
�U
��#US"Un
2
#
%2R�
#
-R�JUR
-
�#
-�%�
#2;�n)�
-��JR�n;##�	�d1k	;(!*	h	k1?	�-
+d�	)�Ud
-�);'?Uk(��1 U5515U-1�??+J� %7(�
-nh U
�dU #
	J
�RhU� k?+n5?
#�kUk"k�Rh(�d�%*�JR+�(1
?�S�%ShRS+Ukd5R?S!�S Ud�S�*�dS5-		h	Uh#d%hJ?Jh4JU(J�(UR	�%( 
%n�5(2�*	h�%#�SJ5�'1�h�%!J�%5�J((�?5��%
�*;*� � R�?	1S�-5�#
2�n(U�#%!
-	U	S#�
S!;(1Jd�Uhn
 h�k(%7%U*k%UURU R �) !

)-h(;S�
!))R�*;�	*�	))?;Rh(hhS?	
-
	5UR�h
hUdU#
-`R U5d	;%!5�%-� !U%�R#R(S5#�?hk)�* dh
	1d
U
 !#+�#�%+�;h�;1�	 +5 U5 (15� �;��*	;UU;�!�)( n
�2d�� %5'1n1+*

5)�
?;S5�5JU��)1�
+!-);�!(?�%)2%d;!�!%5
(!�J(�;'U�5d!%2�(*(!(U%d�
-�5d �	dRU%( *+ �kR!�!(;k?(�+
n
')�%�d5(
n
2�S
n%
1!hk%?1
`R?�S
�	%		?!U*S51!+%?!1	�+� �?%U�((S�hnk
*)
h 5��';k
5-�(d��1	`;R5�k*!�RdSR*))�?�	SJ�S(Uk�
)(�	;*;*�+(S�U
�#
�%UR�d(((dR�S*(?d(#k	�)*k�(	�S*nk�S'*
-�h?*		1d�1
�	**)�d-�U	�	(�k�!�')-U'!�n�-%#�*!RS%'dR�#
*J�?
+*);��"+� 2)�h)	#�;5;?!+R�1 �?�	-2?Ud 

h�5�52J?�J !

-h;;UJ(;�-�!?*(Uh
�??�U�%�#*k�	�dU'�;h;??� (?5�;S2�?S�?	k'kRR�#
+!;k
 5;�`%k	
-�hd5)S�Rk-
�
1
?#2�;('� *�d*�k �	5�h S�%;(Rd+�1d?�(h-�k
�5(�?(�'k�RS;hk'h�*�	) 1 
�;Sk�'�S*!	(h�'	;J�#*�hR*nS(��h	�5J**?;�hk5U5
J�(!�JS2+!?
?
 h;2
"R k(�k?�#?d! h;Z;h5(�2d�#%(?d�?�kJ�	S�
-�5%�)�d?k�J
d�'*(	�n (2`�1 n-�- 
R#!SZh
	d�-
d�5Ud
*�!
�d5B�
#%1#
%)*�kS �k 
�5RUS�
	1#�(h'�(hJ�UR;n
�R)h"(k(-�(*	h	)2d
�S*��#�!�
�
�)
)25*%) 2R%5�U
-U�J7;'2+UJ
�R5�n +�% �
�?�!("h	�!?�!)	%d?h?)�!+ 1!UU;	kR�%'�% )d(�R! *S
% 
+! �+R�J  !R�d(�d	*%?�J(R(�% 
�1(d�('
 #!�#U R(U2� (� �d%�dS
dU
(;1�!� 22#	?�;�12� 
U??1�!15%�?1-?�kU�
S �;!
R
5��
)U5*	
U�!k!� h!	?�
�1 
!	�
�?)%-�%�Rd�2�(dh�)	�UZ%*�#%Z
	-)11�)-1
-� U*(-1)�-n1%�-)1�
-)1�*Ud	)n	RJ�*)�?�
�(�+
U	
4S-� )*(�!+)1�*+	d	5�U5�U�(U	J#d(5�(*�	*�
 
R #1RR %-J�5!�R%
R�RJ�5S!%	
-�R1J;#1�J;Z1
S	'�!d(!1U!�%)Zd5�1*;�U 1SZ)5
1n1S�*R1(Sn1;�51!"
�2(	
-5
1��(21UUS�2R�*1U �
#?(�k�%	�SJJ+
�%dR
US;� �#�hh(d%(U*%Rd%? %S�%	dJ�-#� �%R!?�S�2?!J5
%??%�5!�
-�R�R�J��U�R'�S'�'hk�d?	RS'
?nh(U�U
U)	�R(h��?#	!#d;�k�-#�	)�1�?*�1�%�2R1
2U(�1;�;!�%5�1##J�!h(? R
-)-
dS�%5�h(�k';#`'?�J5()Sn;
�
)-
	-�Z5J;%S�
	J5!�S1J5�5d
�UR
-��

- %*%J� JSdJ+%;�*5k�			�J	�kJ(d� 1!�R d;!J*!Ukh;?ZdJ;�?;
S�1
5�Sk5�-! �U *R2*S2+
S-�#1�S
-�	!�1�%5�SdUd*J;*(�(?�*%(�
-)5�(�R!�U+	!d5�5�	)��)�)S
!�(
!(U!'d+�2(##-!U*
!�
-�(�(%�
-)U
�-?52�)	�-!')�#�dU	?�U%�;dd� kUd�!RUZS(d51*�hk!*�J2-�; (,#�?(J#%*dJR5�!#�;%�US
�Uk� R!R!; �;kh
*5�nS7�
�U�SU�% J;% n1!-J%Udh5S-�*) 
3k�%52d �Uk5�J+U ;*�dRUU;S�J2
)�*dJ)!5�?	J'd	*	�5�J�5U	1**)(R)�U*�51S�?`##U;��R�	?��+ !�Jh(�R5�1*�R��S�?5�UR�;%#*??Z* US�5)h�
(U�!*kd�J
? 
#�-2��*�1R	;U
-�hS1;�-!;S�*�-	k;d�-	�;(U�k'
;�'
")Jd�J(d�')�?)�1UR�*!%*�S�n)�)
-%5�)�*;
-�

--�(;)h�U1;7`#
�
	US	hJ(J �*)2�	2�#U5R�h S1 �d�
-7�U5U�? S�5 �#U
�!5�+-�(�'
5�
S��- UJ�S*JRUk%�)UJ�(R!UR�(R�-R3SU�;R k	h(5
J'��-+S�JUd�J�# h)`!!*
�h
+-�R S-?�%U
R%1)*�5k 	*S)d�

�S5�;�;SdJJ!� U;� *;5(S
� )�?*S�'�2�U k�)! d S�% %�?!
S	-'�? �1%*%�%)(�h
�5�J 
2�5(1� 2��+*!�;S' (�	d(�)?2)`*		S )�
#( %�#
�	�*d!%)R
!-R5(('�#�
-h�d525Jh�+-nd�
?,�'nB5J		 %-���;�5#J	2 �SR#)k1#%�#J�J;�5?5(�1
� ;��)��-
#J 
�*n*?!
R
#Rn�!%
 +S*� k;�)?S1SJ?%#�J�k;?Sh��k;�dZR�S-5-S
S1+
?
�J�*U-5�%)�
%�#`%�5!U 
 dZ%%k�	)�
?�! �;R�kJ		
S?�J�')2�-�d�;	k
��'5S�	
R
Rn�n�5!�
�;d?;
)hJ'	
 n;�R+`�h*R?�(�1; ��+�
	�% 2�
n#k%R
+kJ?1(JU6*R�'U!1k17�%
!#
U!�!5!
��%�k�%!U%hn
)
%;R%!?�d;-;5
`R*�1RU (U�-k%R�
U*�(2dR�+!S52)�d	*(+n�#�*;!�(5k�%1�U
kR1� ?�+R�'� --)`*U5
 -�
+�)- S)?!1�S)d;��)�2
-hJh��d�

S#(� '�kn -
�
?	�-JJ4
*Jn�!U%�
'�'
��+J#U5U1*- �(k'JR�nR'�J'+-*R%k�%;�d"
S� �� US�kd�n-2 
S��5�5U	JU%h �J%n)(�

dS�(�J�(#S`
52U
�!�?%	�(h�-)2�-%2�2!n#?� *U(�?;�
+`?!??�(k	'�	JS5�SS�!	�J+5*J+�d5
5	k%J�	k(�!-d�	!
��*?�2* �U!U
-
(h+�2J
(U5*%h'J�!)�5S�?21�h
�hR(�	#)

?�S1nJ�;R !��h!%?d?h(	�#��!5R(ZR��Sk	�)�5J ?�( ;� �-?�!'*�'#';1%ZJ%J)�-)
�##S?';#SZ?�%+�#)1S

-S;S�	�?�?Sk+*)�' 
kJ1;�J�;n);2#�;kd)d(U�-���?�;nU1;d!�S�*#�d5U)!�k�(*�2
�2S�1h(�%S;?R; 	5�%(J�JR	!�-%	�J5Zk!S(S1Z	;%
-�5;�(n15*U�( k2

#!�#U
-�R#)��Uhd�
�kU5�h -#!�1�SUd��	 �*1�(�#S#
'!
R;�#�*RR"R���*!

k;%�;)
(';�n?2+*'	% #�J
�%)�SJ�U(dR(�)S
-n�d  k1*
�%5�-)?%�
�?�?5%;d#
	

	2�dR�##+!UR;))d%�)2�?�
)�*J*'�%-	1�;J		-%�?�-R	�'�R5	�S'�S � !�1%;1�#Rk#
�;�U'�k'5�*k
;)%�;�R?1
	�(�k1RUR)�U�;h*
(d*d�Uk!U#Uh5�RS n�J�S+!�*(
SR�(SJ�dJ�ddJShk%J1	5J��# S?�k�*J2�*#)� k�;U�-
�k�h5U+��;�S(+;'h��(#R�Sd�+�R1?
7�d5)��5�!US�(U%)U�*�J5
 �S
-�S	U�?	#1!S(US�5U# �)5!�-
dU � 
� �;��
-U)	�1!-
�5*'�d�#(d	1+*+	dS!�51d'S�%SU'U-5n	?(5d*1	*�+�h(?%
-	S!
h5(��n
 d; )5S
-d15	%�S	�
5)�d%*-d	Rdd!51� �J*S!
�h2%R1�)-�!�%1�2�-JSU15!S	
� RS#(
	J	d5#�)U)hUS!%S�d?�(?R5�!'�;!'S�5-�%R
5U 
�	!�1
; !	S%�%
-	Rk�*#k);!R	-%�U�
	5�?k
�;
-d	! �!4d#�
2#�k'* 2�5!��'	;5!7kJ
-�dRh�5h%�
�1
-?�+J�J*��R!hk%d5+5 )5
 Jk�
*UU%2*U)2)25 -2�U*h-J�R
R�
'�;�;S5�k�'+5Rk%`#%U(
-�S#�%!#Skd+*1
-#S!?#S!-2�?S#!!%�?��h�5kd�J�%�dU	5*�RS-Z�%�n!�%%1'�1 �?hd2�#�R'	kS1'
-�(S#!k5k�
SUUS#J�nU
�#(?
?;�
- ;�)+5�
-(� ?
-�J
1��'41S(

�-kUU( 2�
��-
�(2U�1d�d�	!U�
-(%hZ� 2*2)�(dSd��(2+�%U-d�() �)?JdR	1�5U#	)��%-d	�)!5!�5�!
	U5(S'-�U!�S5� +d �	26;�*(�
d* *�6?�	#
+ �+kS5 5(
+�'%;	##S
'-
?(;*`('?�U
�(�'n�U�5!'�*J5;�
)	
�
��kR5Uk�;�Ud�kU	5�	U`	Ud	!��!dSdS	!)U#
��1'�)U*##	?�� �#�	!Z2�# �Rh%��)�U!�5R?)%!d#5d	�R
J#'
�U1�*
�5-d)
# 	? �
);	�
;	U�2US`-
%#�%2� -k
?�*;-#��n2d	Rd
�! �'5S*%k�� �#55%���-�5�*��S!�
-?�-
�5;�?	
��h- 
U(5(�5 *�d�
-d�(J*J	�d;R(SU%(J#�1%;�d -�(Sd(?5;	�d'�*'n
d(�#�(US(;�?+�?�
S;!1#'�5US dJ1dJ J#� 5�2(#S�?� %?�
d�!�hS(�	 �*U;k	+U�(U
*S(�-#*! 
UJ%� 2Z	*h*!5((*1 #�h%J ; 1 #SJ2
;	�)�%dU�U(%	��U!#%"%?�
-!U�(�S�%d	(Ud�-dk%d*	* �%dU�
+%d%d%)
R(�R�kR;R+5(
-�#?�UR?�5U%Uk,Rh%U
 %%�
�	(�
�
?
(U(-

d;5 �;5UR5�;?U1�#R*�h;;S�+�- �?%	) � �R
� 2
�hUU

)	

-)
R�)d�('%�5�+(
-S;d�(J
(	-�d?!%S� k �h%	�
`-	*�Rh;�*
2-	nJ!h5�*�JhR	�
R+RJ;�;

�5 *�?)*�R�R%;(Jd*�R	!)UR5U) R

�S�d�
*!-#�h%5�
%�
+k	#� U�S!	� R(�
	�-Rd�*
 �
-h)�'�U1�*k#
-�h�)�)R�21�U�k!(5;S' kSR�%%'1�!2'�2�1
-%�dk%
(d;�
-RRR
k 5%(
�!?R(	d1%h1�!U	Sdk1�	1�(+5��h*2?!�#�(S�;5%;*�dR;
�(UR? � U;d�-+�d;(1U+�
�U Sk� �5d*
( 
 �!  U�d!�R;�;�U	U
	�k�%'%12 �1�!d�k�U
1 *
?	�*	)2�** ?%*
-�
k-
�J 	�k	dU2U �U) (5k(�2	)�#*
-�
)1�
2	�#?�')�	�#)�?d�-
� !+
-!	�2%�1J�h(*�	�d
( �?5-?*(�*;U!�	*�1*U*5
��5R�%R� �dSk�;��d(#
)%;!�d1(%�%dJ;
d(!-�?�%	%! US#�
dS,;�;(2d5?�
�%
-�
*;U
S�;#;U�SUh*!�(�;(
!�- ZSRSR	2
nU)
-S�#�R	hZ
;2S�k'R'*�;�5J?%	+�!%51;1�d�!1
���U( (�S#-Zd �U�#�(�)�5'
?;kd��
#
#1�kS	U�+!�!Sk�	
S	!UZ
�
-%S	 Z!�5 #;
)S%
n	)�;(
5)*U;
R+�##?J
#n�21R�??�-)�
(�J5 ;�1(hJ�R�U-�RJ�2
JRk�Z;%�5��%��h?J;!)#J*-d(	d1!;1!�
	+n��5?�15	5��5
;!!R?�!2`-;;�Uh�!dk(	#Rk !J�
(
U%�)%d5?U�*�#5�#)	� 2U+�(S�k��)�%k�)	2��+�dk;)�!��h?)R'�SUR�
-%2
R(�
	�R1�k�5	�R!		;�S
-�-�(�2�5*S#h�-	
	(���S	�JU "-�UR!#�)dU�1+ `1?
dR!� !*�#n 
�*�-�
# !	 �)k S?S�*
5(��2�%!5(�?�%S!!;Z;(hn%+#

Ud!%
%
 (k
*���'�5�2(5�#�5')dU(*	�-) �?�d�+)	-	%�
% *�1Rd*5hS 
 -
�5�k(�L
�L� �cP3���ce�Cd��o �-��c��i�	 Ov
�z�6�@�%
�Ym��}$n`���2 ��@����c��w�Q$@dw ����&���`�|
��,�f|}�t@
���� � !@ף"`#D#��'$`2%�$D&�л'�c& �n(��)��Y*� +/|,�-$.�(u/��0@��1��"2�?�3��S4��{5}@�%6+!7@�c8� 9�ce:��z;�)<`��=@�k>`�i?�Cd�	�@��A��gB��iC�Q
D��}E��{F ��G �H`#DI���J��gK`��L`'gM�Y�"�<I���N@-gO�I�7	 P`>Q�,!R��?S X10�ZmT��iU��iV���W@�bX@�{T��?Y`EdZ��i[ 1D\?�A�u$N��?]�.^@�Y_�$D` ��W�Kma@��b�c��id 1De��hf�|�g��h� ]`q1i@�S�v�j�>DA ��k�e#l@:�m`Edn�m*o@��p �cM@�Fq	2r��is@detfeu@��vwDw�
�x�)uq�m8y �{z`:�{��)|��W@�m}�?:W`�~�V~}�	fA��� z�I ����`�%�`C��`>D��kd�| ����h@ ������@�^��j�@��A��g� �i� �݊`
 R�X"�`8�ˍ@�M�Fd��>DIQ�1`��A���>`���@��.��'�Ţ@�g� a�7��`
 ��=�@�[��q� �i� �-;���@ !�`�����c�@P�`���`
 �@4Zg�	�I�@d��
f��)gM௘�`��R@G���?D��k������Fҥ��z�`q1��%D�`��|�7�`�cf@�e�@��|�ڀ��?D��)J@飬�ۭ@�/H�m*f`
2o��g�	���Km�` �@�a]`\m�Fd� Gd�`8�@Om��&Do@+!��Q$�`X���g����i�`�J@�9���%�fe� ����c�������c� j���f7��/I��v�R���yW����i���(���%ƀ�c��'DX�f� Gd� �{	�}�` �@}~�`X�̀z����{a��-Ϡ��� ��� ��@ � z�` ����O+ր�mנ9�ؠzـ����hۀ���  �~�+g���@�>€�T���/gM`}��※c�@�j��Ad���{��u����'���{�z�@+!�֢.��젼��'� �{e��g�Bd�@+!I�8�� ��y�7�b~�@�c�Bv��zd ��f��?�� �� |��``��he�@+!�� �g#�@ ��Bd�@�E�`X����'��Hd��$�@�9�]��iad{AMm����9��g1���%!��C@n��iH`WdA�?	��i
f�Bd3 ۣ���v�'
��z�)n�
2�Ad���Hd��{�f�Ad��i��h���&aۣ!�7
f�)|f�m8!����� �i!a�?"�M9X�Cv#!�{$)D�	�%�B�&���'��{(�i)!Ad*ᥘ+A�,0)D,�)|-���@!� F_�)nq@@�7�-!�ˣ.��� /!�F@�-0��'1� 2A�-3���4a��5A6|��iI��m6��kA$u7�Cv8AfA���*�{9�á%��i�:�3l;a*D<�{=��i��yN �e��he>A$u?ADdA���@A$gAADvW�cN �eBaDDW�wC�$DAfE��iF�iAi1GaDDH��+I�cJA�K���v<L�I@�M�CdNAMmOA�zK��	P��{QaDDRA$ge`8DS�`e��^�T��iU�m8V!�x࠮�v@��W�8�;`��H`*D�x�X���Ya�ԉ��Z� [��bf 8�P���\�)|f`V����]� ^��b_�ڣ �"`*D`�Kda�-b!��	�c��y��m8g� dADvea�i� �!f�Kd��N�ga�i�്ha��iaDDJ�,j�?k!�elA�@�h��Kvm`#nA�'o��/ �p��{;@6nT@DvqafrADvs� t��ag��jua�{vA
�@۴
ADdw��kx� yAfzA�)�&{"D|��i�@f}��i~��a�`8D��i%A�������ァg��!ۄ� o�@"��Kd7�/!���b�aۈ��{�&(&���{���a�ax1�!�oa�y���a���a���z�!�-�� C�x�	`�iO�lv��y������J��ݓ�ƥ7K"���{7@�'��c]`�`��F"��fR �z!ww���yR@X$��Zd2�Kd��xӚa���!u\&�X$�A5S��kg�ȝA��f`�h�a!D�A���aՀX��A%& ����ۣ�be���b�A��-n���§�Kd�
}VA�a�ΪFd�!۬�q�H�<���{P��
o� 5u��&�!۰�j��ݲ-|�e:����v�d@y1�!۴��|`ـ�!:Di@�#�
}�a�'w@�<�a�{�A�y�!�c]"D�A�'�A��fd�A���b2Vm��[v�@+�`���
)�!��`��`�{��F�I`�u�A��� �e&�ڀ&�ڢv P��[v"|zA�aH�<�a���y��{� DR�Zv�@r8ǁ*A�!\v� D����ʁ�{g�{��&,�!������Ar8΁����b�X�v@�z��a�a�a�!����c��-n:�i��!\d̴֡P��8�!5u�!��!�ea�!!\dI�ف�aI�|�!\d]��{ۡzwO��-��:nݡ��!���m�A$|�!�=�Ame�ayq�a �-D&�u���]v��:n�A�"�� n�a�'C�
f�`�� :��A����v@�S졞?��}��id�	fl�f�qa�i�!��a5�W�d���-Da�h�oe��<D��Hd�A)!�A�a��:|��8��L]2!���*i8��{e�!�{Ġ�,W�!�y�!ne��:|��)g*�n��{e��:|��{W@oBoBZ���{�Id���Ϛ \vC�&`<'i��"�{b<n�i~�Id��a	���
B$|��z��H�/D@�-]��'�ze|��	
B�$����a�@���zwC�;|i�ۮ� (@�5�IvbX+�i�Id�M�d@��
��$B"DR@�a�;|]@"D�`��������"8!��'e��z��+N@�a�VBB�a�]di�IvJ��gJ��u�Idt��b� �4�b�o��a& Bfe� �/!"4S"]d C�VA�#�/DW`�bV!�w�a|e:Ap���$B �
f%� SaX9&"�L.@X9'��(�>D)"�{*��+��/P�E� C�T`|wR�{e,�"|V!��`�-��.��zH@�//�"n0��n1†$2B?�3�P�s�耠c4��'5�;|6�h7�o8†$9�}�� :���$�BmX�'P�U�;�;|��fC��ޖ���<�b=�<�>b�a�A�+?B�cJ`\$@��ig�&A�-(�+N@�aBBFa��ڷC��uD��`|w7@":%���E©jF�zGbf
�H�>|7�'DI{�W`��6SJ"��o�9��6�KB��L� M�6�N��{O��aP��Q���&@ˢy��aR��cS��{T��D� S�Ub��V��a�`��WB�- C�X� Y�8gZ��'[�V�\�f]�5^B�c��JC�#_�'`Bˢa��9!€�d zq��iN_�VA�.B�0 Z9��b1��D%@VmbB�c9!dB�ie�:uf"q1<$ng�f�`5uh"�e@���)g
�i�>Djb#Dkb>|A��`lm~m�$n�:go"�VpB{�<�vd��cqb�h�A�brb�av�W��>Di#s?���<t"�aW�$:���/!�i���ۀk�u��{`�?� �v�E�R �b	��1�H�;�Fdw��x��{y�eD#�#z�:u7��b{�$|*#}B�(~��i���
�1�崀�A�g�:u��8'w���i��;uI �߃�>D���iR@�m������{�A&���[mV@&����{���i�Bf��)|�B%�J�Ad��)|��v;��m8��c�BMdA����*�b��~1�b��;���	�{~�"ʢ�`<.J�����f��<����/���x�B�i����b��A��b@�w���/��)|R����?D���i� ��Y�]�#���k��?D��xӠ�c��c��M"�?y`����1�b�܉`�ݥb\m�B�#!M9��Bd�"&���{�B��"��	@{��Bf���yC�UB�B�կB[�B-u�=ui Am���{;���"&���i�Bˢ�BMd��)|�Bf`�-��g���y"�#&�Hi���b �B�'���"�-�"g�A�i���Au8v ����i��)|Ѡ�'�"<g@9!��ie��@d��b�)|��;u�@����&DN`�g���a�� ���+]�-��,�=g@9!�W�	=u�B�{��Odɂ��A���B¢�"�d���¢��B��΂��")��h�B@"W`�i�"�-�@(=f�g�"Nd�B�'�B����'Ԣ�%�"Fy �n`�+;@�{R`J
�`���‹bׂb~:�:��@��`oآʕق�'ڢ�%�B�?܂�i�b\mP��8:���o��|��7ޢ�k&��|�θ�b�W��w@��"�{�" �"�)�C�@{����%|����Dd��*����†Qт@"qA�k��%V!�x�BdJ��w��o ���B�kW`���"�{�=g��
��o��u�b��B\��/b �!���me�@d��i�b9��B�'�`��]�'Dg@7'�a5��b �B�x�B?z�)D���i�B{���b���!��z��Ymz�E��B-n
 �}�a>��f���'a�y��f�‚kJ�����!c�c�i��a��kc�-��
#�i\�E�A��$$u��/���c��1`���N_�}*	�b���eb�?3
�CdF�f�E>�@�%�)�C-|
C�?��k{;!C-|�kC*E��V�bC-|��aV`�
���y:�$|3��c�Cj?aµeC�?C���:g�B�-�E>I ���@-|�f�[m�a@z��{�&��y˂9�-D����i øDŽ���!c*D"��#�jF`�-$��co��?%c<u&�!f`�b� �e<�''�yA�E>(#ʢ�me)cf*��?+��-,�Xd-cDD`�(;�`_9� Z����{1�e.�f8/�{Oa*Dz�-n0cf1C�_V!�-��e2C D3Ci1|���AUTI��4�k}�fi��{_��$n`�-5CDdI����6cf���$ zC@�7c�b�@��8���7�CvvX�9#�y:�x�;�f<��b=�w� �b>�w�#B9D?�9�@��{y�A�CvB�)nC�Q�DC=gX �y:�1�W��w��i���EC D���F�T@DdFd,@�bH@@9�yBGCfH��{]�/:HcfIcDDJ�kK�{LCfѢ�� @oe{ ��MC&��ǚ@fM`*DNc<uOC��PC=gQC@+RB9ScfT��`EdU
�V��cW��'XC@9Y#��*A	 ZCDv[�̀��{W��b\��iC!�)]#f^�@mR�h_��`ef`��?��8ac�b
B9b���_@9D`��#B9D��ocC+DFB9��lq��c ���:'d#�a;@	 e��' �x�>`�{v����`�b���NfC��gC=uh#\)� iC=g.���]�1�x�j�zekcEd1�BhJ�,�@�`l�F"mC	2n��-oC	 pC9Dq�Zvr� s��'&��ct��c?gw`״�`%uu�{. �'v�51@+DwcEdH@9Dx�Zdy�zezcEd{C�?o�G�d�2��b|� N�9�}�>!��?������/*!��~C�?�����?
B9�c�k��9D
��i��2� ��*�%\��<�Ci8��o%�Cme��;|%A�W��a#z� ��ze���]�M+�ÉkqA�c��zeI-nJ �,f� ��c���C�`�Éy��-D��?�C���c�A��-D�c-:��ۓ�-�A�?��{;��ᕃId�;|�����,D�Ci8�C��"@i*	 �ᚣ,DI��ߛ� �`�k�`�{�C2D��ܝ���J�J.�c#���)u�#��:�0Ѡ�ba ��-|b�ۢ�ze<�i~��Zv��}S@�����]`Nme�zw��y� £g�í.���� |R��H�{!f�zw���O� D�� n�c � UdB��a\�۩� |7@)�C9��'f)bNm��>N���N@�(�Ci8A`.|�}e�#ʢ���i"`�c��D9�#\d|�䁳۴}e
���zA�)��@m�c�y��8�H�<D�COm
�@m�c ͡�y�����v�@m���{*���#ɽ�-�`�ξ�Hd��_9�]d�C2�u�£�kU �i�C�	:�`e�� |ă@mŃ�%��c�#\d�c�.���`�� �b!��a��)z���#uCN�	�) ���C���c ͣsc����}e�}e��>҃�%����`V9�#!D.`�<&@� ���c��K@�y@���CFپ��%փ |ף�yO�Fh:�@9y@��؃jB�t��a�C�i�#�bۣ�ܣ�{ݣ���E��CB9��ߣsc�c.|��a�C�i�C"D]�/D����.D�F9o��A@
f� ۯ�C������c;�Hd�#�{�c^d�cEmT@�i=����c�?��cw@��J����F.�sc���|`�^��<D�#�{�f����C�i��<�b�]d�B��-gdc�c����Om�C
f��a7@�b�CI"y�.;	2���ρ�%|��?��f��5�
B�$eB"D�C�kW�o1���k���y�C�%��!n�@[�I����>D�&DDm~D"D� ��n`�c!�]�$�)u]��$G�t�ʠ�i��"|��?DT	2�!n$��N�	������f	2�@_dO��>	�G+
��L��A�i
�G+�!�#I���$��m8��D�i$C�&�!:��D�'��`Dm~��id#DċyD�'d^d��#|�рM";� ��F�!���-|ĩc��$ ���YvĻc�beDm $|f�	fq2D!d��"����)#�&�@�h$$Gm%ċkO!�&ċy'$Gm(�f�b#D%��?_`#D��#|>�Wdg	 "`#DUA�k��-)$Gm*ĩc��D9+d�h�Xg2D���F��J��{��o�B�aq��,��--��.���_�@v/�Cm0Dm~��<�`f��/1�L�;�4	 �H`#D��imA�&� ��2D�c3df4�`~�A�g���hˠ�ca۽��'5Dy86DI97��j���b8��-9�	:df���-;��-<Ļc*a��=��?>DI9?d�-@�?D�`�?A��iB�G9C�`~D��{
��k��E$EmT@I9Fj!G��+� HDI9Ir�V!`F��� ��Y�`~�`�<�k�a�-J�#nW��-�Ea`��K��i:����LDvCMf�o ��< �}���N�{���R��'��7�ie�!�rB`8O��iP��iQD�z=��I�[dd���>��?Rd��<�[dS��a�@`8T��U���V�[d*ᾘW���X�4tY$�Z��{[D�a`�f\�T����]DFm^$�ܱ��y_d�y��Ad`�n8a�tBb�k��e�c��?dD�ae�Jm	 ��R��f$�g�f1h��{i$Nvq`�jD&|K��o%�) ��T�ykD�zl�4S;��m� V�{n$�io�İp��q��:�+:��@��rD-usd�t$g�
"fu�Jm ��M �?{`��v�&DJ�z:�7�w��	x$��yDFmzD)D{��i%���|d��}��{��F�~��id�{��y���k��%D��ko��ii@ۃ$�-��yi�	ۅ$����n:a)��[d���a�@�$W�F+��-��F9���Ռ�[dy`��]`'n��M"����I��g��ay���C��aK@�	�ď-S!�i��#g���(	=u"éx��	��iW�F9�d�y�a�	�dF�ď?�d���d��d'|y����$����'D$"nv w��v���{��{���b"�}*���i�@�$���{���<�d
���
ۡ��ߢ� ��xӤ��a| ���D�k|��3�ӣW`��")�$�'{��-����Y"�G9S��o�
f�Ćwt
f���0�D�`���ޭ����x�d�Bd��_�$�-��摱D�k�Vm��i]��'��{|���|`��dGm�۳�Km�
f��'e�L9A�� �&B$��'���aP�qf�D�i`���d�>:!�@i�f\`)|H��i�@�x��f. ^"�I9"`G��!(�+��bf��'b3hJ �ܻ��a�)D\����iN��I�)D�Ą-��a��{e��'D��_f��R�x����P�c~I��hO�(n�@��d�?��-��Q*I9�):�Ą?I��4�$�{`����i��;n��#g] )�p�[�5��D���A�T�'D�$�z7 D
�\d	�q>"�*��;nz!!D�D��:!�]�$�����d^d�`e~���i��o΄�$�����yϤ(n�d:Di �k��q>��-���䀹���q>�d'#��i� 3���c�$�����`W��`�i�D"D��ie�d�%�D"D܄��;��b�@���Ą-�Dfߤ*����)|N�����i�d*D]�-��c���i��{�t�d�+�D�+��c}`8DT@M��l�Df�d�c@�}��wca�o�D-|} !Dfī��$���m!�x��=�D"D�OI`�+��-�$��㠾+��>��{}\`\"�^m:a�|����Xm���z|`ڀ��o��-����{ �k�ۢ���)|�d*D��"|�$�-�Ĺ�D�{:�f�$Nm���'y�������i�O9��/�$Nm�����	��iׁ�'j!Ťx]�keC����-n���<���aE}e��{��'|ۀ$O��	�?�")
�Jmj�x�Ef�O9.A��BjT�@`8���i�@@d
�}y����x�}�#|e�0e�E�{� :DI`�z`)A����o`�$P`�{5��'E�'��iM��zF࿣�`*:�g!�`1A�U>� �#|�aJ��?�a���j���"|�
���iE�'��$D%:D����a���e�a�,Dz�:| ��N@t>!E�'�A�k*a��!�i"�}#E+D$�#nT$|%E�'€�I �&E�{q@+:l��'E�'W�o!Am(��-&�ƴ�>D{aUd>a3h)�k*��?:a��+E�{|�,I"W��a-�>D�[��@6�.���"��b/���c��a0�a�` �1�+|o �o2)!ߣ�3��x@�-4�#n5��'6�}*a��7�Am
���W��%���I8e����oB �a�*�T@�'5�#nc�^�9-|�`�V�,:���|���;���<����@��i��x7-n=�c>$|?%�@E�iI��AEmeB-|)��[y���C�%D��-DR��bD}E��i`}�@�i*�1F��$G%�{H��{ �,�IvI�M9K��K�_"`�{J����`K�
�L�$Dg��+@��[�Y��i ��K`�+M��$*�8XJ�S2�@6gN�����a4Mm�!�{O��メ�Pe.nw�`A ��Q�:|R%Zd#�M9M9�kSŏݢ@i8Tţd�@i81�fU�Ve\m@i8J`�dI����{�v�{�W�{K�@:�X�,� �ݐ�bY�`� UdZEd�[ʣ�A�f[�{�M+Ca���@��]��$\�%DJ`�&�0�]E�(^�%D@��_eu>`��aq��}(��aE	2�@�'�$�ib�{
��*Jad����Jad��!8)c�-D�@	2=�-d��
�-De��f�'��@4ÏbEm�{I��g�֢|��O�7���.D;@	 h��k��XdieEmN��gj�k��'N�{=B�hl�&D�`�am��a�B��N@}��in�۞��o�AdpEd~�a\m!�bb'n}`Nmqe)C /�r��cR`Fs�+��	ftEd~�^"�#�n�@�ue��R��� dEmv��k#�/D��fw�'Dx�ͣy�z��c�&:I�x}}�=D&`�d.aF���'0��c� �{e�-&|�<D)��$�?��}�{&��~��'o��c�/р��cv`�c�@	 
A�}S����@
۰�F9�!\S�b�%���@�� @�?|���e�}�%�Qh0�����E�}�%�'�%�{#�/D�E{�]�f*��ۉE�ai`X��eH�	)���c�eEm�	fJ��kn��ƣ�E	 ��(|q Úf8�E۴���aJ��ݑE�k��-�B�;��&��>D��x>���a���k	`hk�Em~��a: X�:A?�}��$&`Y�%�>@��z�Nd�)D�@�Ɨ�	f�f���_�>D�e1D��x,�e���'Dz���7�Cv��)|�d�-+�Yd�E�a����Mv���i��Av�A?�*��>�E�->��ܤE:�<��遣����{�A���m8W@�-�%���1f�\�I�۾{D2D`�?��fW��o�y>�@ۦ��-�%=
�=\��ݫ�Yd7�M"��)|�Z$`�@�c�Ţx�2D;`)h�
�A �?�@�$yO�i�I9�eDD��>�řy
 �y��y �?�eDD��)|WaDDA�>D�%Um�a���jH)D��m8z�)|:���E�,���yNX�wB�,< �oo  �E�-�`۹��'�eۘ��w"�>�-u�E�e���U�)|�E�?�E�$f`2e �<�V�Sa I�`e��Zv��@d���e�e��Å	}ąYdť����Zv�।�eDDI@I����y��2ɅYd{ ���řy�%����.v 4�J�����y>;���e`�}��k�% o~����Ń�ҥ�y#b*D)�(<`0��j!��x��;����%�-‏?/�Yօ�-�%�?؅�yf2W��-��j�eDD���q@���%���?�E
f�}~=����%3D�"�1�;gx��P-!|~I ��E�>
����*|�%J�@6|5�f�:��@ۍ��kn`��ׂF������'ㅛy��a�Av�eC�
�f! �&���
A�}{@?�ad�v`�S&���C�v�g��?�@�� ��
��e|>� �`祣q�E��酂y€�y�+�Xm�e�kf��x�+|�Ei8o����F�v�׋��k�%�A�% f �-��}w�`�e�y�% ����}%��xӥ��{��-�����f��,Dn�$�AF9�Y9:!�h:E۸�
}�� ��
2
��H��2w У�!��w����O!��e�k��&� ����i��+|&�-f�y���P`���+|���f��af�kƦ>���|��	�����}�B�y	�}���}
fUm��+���-D�[d��
���� j�
}�z��Y�Ʀ>��G�&��F�yu �椬��?��{3��)|@�	� ��oXb�yd����dFg&X9f�;��i��{�@�'}�Cv�V���2��@�}�-D�*��x�d����%X� � !���"ff�$�y#f��x���`F@�%� ��@��$�j?Uet%�Ym�Dd�ࢢ&��+:�0�\���'��FO���(F�')f.n�@��*�T]+�@
}���M �$,�"|��=:�.-��a.F��"`.n/F�k0�-�B�jv��&�
o1
}2�}3F�7T�Km��W�ݠG�4�.Do���]@�$�dUm5f.n6f)\�?�7�Ymt!�a8��jz�<�f�,�`�9��zA�%A@�x€ߣi@_��"|:&�IF����-;��;���<FMm=�.D>�E��@�.�իlCI"�%Wm?�	f@��``.n뀕�A��b�VmH�,}�?n�Y��E��Bf.|��}CDd*�,D��`���lE�.Df�\�qAMm���bF�4�|`��G�M9i Ev��Iv�!F�H�0��i]�̣o �-o`��I�/DJFMmOCF���'q@?�K&�L�\�<�MF7�´N�Y�OF�$P��yQ� YC?�R�[mS��x1�/DT��7@�zU�i|�́;� �-Ń <@$|V�f
!�}1������0�@���:A�}WƊ)���X�.DY��j�[�Z�'�����V��fR��[�iS�{\fX9]��N�7�^ƀb& ´R���{O�}_F/| WmV�XJk�`I��}����`�i�!Ѵa&��b&��cFFdd�2�@�'����e��bJ��af&\P��*f��� �ˮ�
�� g�XmV!Evv��ah&�yW��xi&�ij��ck&�yn�fg
fM���I�E���o|��� ��� �%J@�olF�`C�$m��bn��aof��C���!��&�\�p� \�\��@��< �o6��ad@�<�iq�[mr��{s��|�ց:�}�'t&��uF�bvF�w�i����b@�`x�E��#�y#�]9y��%�fU��z��a{Fj!|&�o}F`8~F*w�ʣF`*["��/ \T��ba :ʀ
2��	f�)�ƿ%�F`*�&�a� �1��	fO��S€]�."��K��F9D�F�܇f1D���a�d��M�8�c2DW@Y9�ʣ� D�c�&Z9�&�y��Cm��eO�����i&��c����0�]D�@�8�@�j
@�xi DF �i�2D��b~݀M�X �i�F�c��&�$�y�F��F"��h���/��{��f^m����@�{7�'`)z��&D!�%�&:D�`�&��_#
 ��yࠁ"�`A9_��/�&�a� � ��;`��R��i ��A�g��c���'�&Z9W@�}��i&��.f���f��}�^9�Fã �ܟ��&�Y����g� ;�%`�-D_m��x�F_��Zm�������{�Ƣcw"�o|@���Ym��dh��'] :��dA�b)��I���)�e$�M+�ۧ&:D�f���M"��)�W
�F����&�a�F:��x�#*A��{e�Ff̈́[�F_m]��aC�<�'�?DW@&��汆�!�}�F�b�_n�x�`��Zm��b~��5��A;��`*A�>za�%��۶��/��+y��-���iP@�¸��/��0�&�-%Ģc�@�j��Zm��f�P�
��@Aa�{�A�ż��/��׽�j"@`#@�8h �;D`\��j��b��jK����AMv�7��I9&��+�5h�FFm/�
�CA����>|7@`1��2q�`���B����`e�����I+:AY*��i'b\I��"�Xv�f\�� _ )
�5z�f<n4�"|�I���<DɆ;|ʆ�$��-��`e��-��&ͦE���-7c<ng��+���Ϧ���-�L���a�f5nӆ�>�f\m�ę���_9���$֦�
�f8@]��c!ɣ�F=n�`W@;���	`)h�@$>����b|��7!᫘ �>} �/I�I��<�q�`	�"1ę��fEm�f\m�㥘�&)� �-� ���F���' �>I@�u%@=nf@�������{
��<�A��O%��f\�fִ��{�f\m�C���`v��$P@�P�t�bAd>��'o�)|�&>|�?.�\��F]m�f�a�)|� W}�F�i���ۀ��C�t(`��W@�&J?��&^m���n�F�c�&���M+���8R�S���}���q��=΀be�cg�f�a7�^9�F]{����f���}7 �>]�f8�F=nz��`�.{��f�iS���!^m���i�� ���F]m�@���yW��iVa�g���{���W�F�'	@Hu�~f��}q�$:(��%�F�{�������%����v@�v���i���� GmM��x}��'�¢jF@�x� �V0�c��F9J`���a�o���P������W��}z�'q@�}�‚k���f�_mw"�}P��8�i~� MvG�$��Kd�
۰��9�	q��{qa�bǛkW`Md{_m�;uG�%�_9���R ���f8G�c	�ceI@?�
�״��o��jg��I`��zM`�?��<�G�'��{f�>��y%�`
�%D�C�
�G�$B`ۯS�%Dg@���cw�m1�"X��r���cG�-q��}��i�&J�4Z�ce>+D솪�T`8D����
oǩcB`ۮ�&gDD��i��?Oaۯ�Q+��c��{��a���C��>�"�y��O��i�@"'4@�x%AL;y@L�`�c�i8Sanez!)� ��"`�cG�% �yU��o!G�$"G�x#ǩc�Dde�B���E�xy ��� X�$G�$%��a&��c�A��'G-n(GD$)g8D�!�aq��bR�fl��j%���o F�*��a�`)ߥ�oI��n+�ce �{~,��--�&D`)@K+.��{/��-0G-|1��`J��2��i3K�4��z5'�y6��7�@d��,���|8G�J@���G�g�' �dF �i�!Hd9'�`��:'�;�Ad<��c=G�>>��aA-n��'?g��@�AdA�-D�A�����/ =|��&B� �8e@{�"@I9C�-DG9DEfe"@i*e�i~FGDv��/G�ae���-¤��K� �Y�Z�Hg�pd��i��'A ��I��-7�i�f;�&�$u@�`	�2���T�Ad����g��J��%ᦑK�&DL���MG�a����% N���fez�:n�K�I�JmO�.D�@�zg@��P�:|QG-|h@i8� ��@�)�fe��%zf�q�{R��}S��c0�8�@��1f�$��T':DU)D<��|��oP���Vg�%&��4W�ۜA�ߟᬘ���,���oX���ހ�Y;DZ��% �� :D�����Y�i`�/b�fe[�9�\�'D" gwg):]�Imf���{A�$^�Bd5�Av���W�.D_�	}`�'N���a�Ad0��bj�cg�d�g1��@9o@ۘ` `A>W��e'�?�@�}��c�N�;@:p%�/Dՠ�f'c�*A��q!�-I�`��g�Cvh�g#iGC�!A�aW@�i�C�ajg~~;@hkG�cN��i�}ߥ�}$�>|I��'l�Cdm'�in��k���iog�%p�)|qG�%�@޴1��vל��ۉ�ۉ�\�r�c2\ ^"��i�c	 �=�g#s�@mt�F�u�cwv��'& ѻ���}wG�cx��%�`{�d���&��'����y)Dq��o| �`���@?�zG�ig@��{'�i� \|�j}ǀbOA�cf�_�~'�-/DK��_�a ��-|���o�f*D@�$��c�g�-��)ng@��gT;���z��)|�`���aͥ�}\�\�C@e=�݆�	;۠���he�G��g<n���?�a��'���ɋ�;|��ceb{�"�<D� Ev�ǭ%€���ǭ%de��`I�*|���G���i��ze��-�dw`�i���h��U2z�Ě�Gi#ޡ`3��{f�����'�i��CdR�if�媌�ۖ�Cv(��g*D:����>�G�ܚG���)bC��g<|"�<DWAۜG�k ��y�G�< )a# ���i���܇A���Ej�b F_��y�>z�G�>�G�$��b��I$��ܤ`���'�Afe����I@���a�cR`�o����GFd�# w"���t����"`�c��	���@':��}zগ��<D�'�-Y`{��GFv�dž���b�G2D�(u���}��i��F@޴|`�����q@+:��a��SIB}:��}� ee�'\mn��@U;J���_fƢ�;��`C� ��)!�G2D�@��Y���fd_;@�C�!�+b�g���c�GFd#����bz����Am] �yS@zԳ�+v@z���NҵG�h�G�"A�z
�m*�`�}g�J$�@�{F�M9�	f��m8��f��a�g����!�>D�Y��gge��	�Cx��Y��ke�'>|]�-:�F����ag�I�1��-|��a�G�?F ����bW�?D
A�g�'g��H��G D�GFvo`�xćke�gf��o�gUd���`LJf�'��	f>@�h�G�'ʇf{�'�a̧�iU�?Dg��a��g#:!�a���/1 ��� ��·-D�'�a���Ч�b:@�h!A��J�E�%]m:���b~ѧ�i�'�a⠣c�<��g�'P�v�ԧ�b�"X�Շ@d�-���ׇ@v�G'�`��B�z��W�b~�g���Y��' P�he�d�+A��g�'2�d~���o݇"|ާheA��g @�'%�>�A���`f�fߧ�-d`��:��<`��+aGd�g���	fᧃb`�:!4�J�h`�@d�G�'�"|�he�I"qb�|�`Gd姺j��a���xg�j<@Mv�G�>���?aUd���q��'ЀhN�g#��o|`�^��*��a��Hd�G�c�Hd���{́�'���`��j*��a
�#z��>���a� ��k�}��0��Hؾ�<��}���+g�������2�'�f�U��'��"��-	�"��_9��@d�'�a�&2�#�����F' �'���ۻ ne�ge�P@6�� \vJAZ��=�Y�����ljy1@��)��%�&ne�'�-�g�A|@р&@̣\�f�Av�g��D$n��k��i�ljy�g�{�@Fm�[v�xU���Hv��D�R��1��i���a%AO�|`��VzA)|H�$Ḥ����aHdh��"HFm��I��a*!X��i�C�`Z�q@��_�j	��
��a��
�$D��a�Bvd1�bE���a5@$n
�AdK��+�@�a�
fe �>H�F �`1@��!!�I@��(�b!�b;@z

fH	2=�<�oeȉk!����I�h�oeq���_9�H�H�`�Ad;�Xv��9�f�Q"H�a!۞
�ŀb�i�
�#�"���JdI�46f��|����p��n�W �i�Y�e��{�E���	f�Kd�ke-u�Cd�6T����ʦ� :�%Dy�N�I��/ � i`�!�Cd<�b"HF�*A��G`�^���#��d%��a��'&`�+@��g�ަ�@�wK ��$��$uJ�'��N�*�毉�\�%�3S��Fm&Ț����`Gm��Xd'�f��>��G�
�<�9��3��o�Kv��CvA�u$(H�i%@	2 ��R�@+	  z�@�)��)�b���/���G*�G�:�P�UC۽+(�6,�Cv-(�c�"�c�Ak�} :DF�a.�b���{/(4So@�hW �i0-uf�21�-��'h2�?�!Ed%�H�3�-� UmI�ۆG�a���m��H�_��>4���5�Y��$6�b��ke� Ed�B�7�Cd&���^�8�Cv�ù��q�9(EdA�u${@�ȃ��a�-!����:2D;��(<h ;��螧�=h�iJ�'#B�$]�M"���L�-���>��qa�{?h��@�YdA(W�B(DDCH� �,D(*A�`�u�A �^�E��`Fh�/GH�ye��gH(EdA`�c�¹�f2I�{e���i���JJH�bK�&DLH�%	�MȨd�bN@Fa�{NHIb����Ede7�%V��!��O�3��`�ifY��^�Ph�Q(�J���h@Fdd Um���/R�&d UmS��>WmwC�HmfNvT�-F�%_�m#UfV�^��->mw7 EvW��X�c�A��Y(EdZ��i%�cM`��V X��{0	@�-bb\v]��{[��i\��i٤�b]�&F�x���V�ۋ@Fv^h�s�i8_h�c`�J7 !D$ X����`( �R$d�2H�m#a�����"@`*��,b��{c� �@"��E{�dȻjeHFdǂ�'*��>f���_�m#@۩�Gm:�Ź7`#DgHOv�@۴h��iiHoe�[v|@�RH��i�R$�Q$
a�hjhNdk(�l�a�í>>H4'm(X�n���N���o(��FFdpḤ�D�2�3K�q�an�
fA༁�irH)D&CPs�f!�ڻ
�)f��tH��uh�x`��Ē�v�)|wḤ���^xh7�yH]dX�/�!X�W@OdVA_�ڧ�	]��'�@s����w[�zH D{�Ym��e#|hj�}��'� �~��-|���O��z��i���}��ۀ�)n��Qa�>� �C�<�d�/u��b~�f��
 �H D�Hʄ�a!A
f@ D �x���W�bbۅ�G"@���ۇh�iJ�ۈ�Hd��3'Z9��ۉ�aA�z>S��$f`�b��c�H
f C�&��Zm�D�+}`Gd��b~Az���i�)!0��i!�)|�aSb�HIb7@
f�H D��?]$|I�1Ѥ�p���Y�Ḅd@�zi������(0�ܔ��� nK��+�Ȧ,�`�ݖ�C����ioH��h�i&D�{�E™�Hv�a!D��ec��o2<�H D�h����J��}�)g�H�h������`;����H D�ȭ%7 ue��d�9�z��c��� �ܠH�id`����C(=�`�%����C��{z�$g����iƻcw��Ģ�.]��+�h!D�`f�!f�H�h���ܟ�<�i`�{��Ym͡*|�H�iI�oӦ�c�hf��<��A���f|F[����$W�۪�IdA@"gf D"�`���f2��Hd�H�i��c��У7��iP�j��H�&��L�����1�-��!n& �c�ȹ��@�}=)!�f��"I��.2<\�2�țb�D�{�!�b
�c�+D�hNmW��q��&���
 m8Va0h] �iTMm�Hs�|@ϙ��"|�!�i�@��A�,W�Id!�i%�P��h�o��b�(�b�鴽H�{� z�f�c]@�'�@gk3a8D�h�ig����{�D����<� \���C��%)���}��iI����Jd�ȍ$���c��,D��%�`�u&`�ܟf�Ē";��b:A��`we��>�(#Dƈ�5'¹c�(�i|��� �c�HK"{�O����7��c�Kd�H-n�%h�H-|�H]m: �c*�z̈8h1#W������`��Kd��[mz`��Ј8zE�H�$)`���H[�
`�d�H-n��=�H��] :D�A�$��Kd�hg��ȣ	@�z��,D���� \m��&�'�Ɖb7��/��Kd7$����Kv�h�{��&D�H ����bNJ��H��8z��JdJ�� x1��?Dߨ7��'���	��,D�ȯ�����a�ȉb�Ȇj���7�>g���H-|W�8h��H�V�'@��X$���(X-:A$|�hIb��<����A�a�Hd�g�i�!Y&�>�澘��i8�(�5g`'D`�գ㾘�H$|���h��?D툢c1@��h�i㾘�� 7�+�x�;a�J@�P@h��-D�@��):gy1��ϣ�h�X=b�d�.D��(ף�TJ��I
&��u}`ue��Kd��'�Ȃk�H	 ;@	 ��8z����;|,�O9�-uʂ�(���{�hUdma�-��;|V�5S�5�0eue��]���xӕ�S� ��;|��$���%D
a�cw��%����%W6zGu�� �I�a�)|7��'�GC� �P(�f`Ud���aɟ$i�c>C���;|���J��.�\@�BI�/�!�a᡺���/��a<�	f�]d]�M"	i�+
	�yV�f8*�T]I�,@�%����d�x�:��f��!��`:�j��-u��:���`��
�&D
A�,&@�u�Ad)�Q��kI��WMdwb��-h�iI��0��Y�`�?I���^�I������H�/}`*:�huUF`��G��U�&:�M"� Q�o`<n��'��;|�a�DW��UA=��b�@�VJA��	Σ)�
�Y�����6z:�ňd�	f��%Uew��%Dz!ww��(�<�'iNdU���J��j��Y��4��<D���EAzX@mea��x�@Ov#�>�'D>��| ��%!	F�")�c3��?	`�#)
 $�N�%��`�&�Ea\d~'���()Wd)�Cd*��c+�&D,�'D-Ime.�if6z� �/��a�`+zCA+D;`��{0o�"'��{e0�^d߂�x1��`P`w#�c��Sp���>���� �- �
f2��>3��d4	ף5)|eN@Dd}��>�=zq�[da���`5Ӻ`�6ɦlR@f7�<D��fx8���O��i9�8zA&���S	`ۋ�[$:iX"���?;����`7�<	6z
aң=��i>I�$?��i@IOdf��A	�aW`�=hB	6hCɂbDɀbE�(|�ȶF)'n� \$���x���G��i���i@�g�#� ��;��bHig�&@��Ii��aDD
��@/uJ��%�@E�KIi8L��iM	�-<��f�=zx��NI
f	��b"�S�('"�aO	h�f8-nP�Cd� �b|�Á�&���!�-���Q��a� �cq�m1W��R�  dEdP`�d�zF�{SI
fTiۜd�Ɣ&��U	jV�Y�d�)N`0z�Xd:Vp���&c�@ۘ� x���:���	f
 �b�?Dͥ�}�Q��b�%�b�(@��;`�[|�́!���d ��*AG�WiDD� W�X�Yd|��tYi�&�{р�*Z��x��f=�x�%@�cJ�8hN��>���| ߒ[��{\��{ta~e� ���`3�g���{{Ɣ_�@B�]�b@^��{�>D_	}eP���b�6l`I�(a	de�B�x���b)*DR�l_C�h��*|cI�b>@�d� e��iga
 n�(nf��g��g��b@@�ih��{iiңj	}ekIѴlI�i>��	�`�{c�D:A[J �a���ym	+Dnɹ�o)��<`	`J��+}@�j@?�p�ZdK`��q�tC��r��8� P����?��b"�}#�ze�+I�q�%���أBad���)D�`�zs	�`_�)t��Ou�
 ��+�+Hi*o@�iN��v�R���,]��>g���P��8+D�`��A༁w	JV��it�ze����Ydx�Zdg@��"�m*w@�A��P�GӉ6z��G�Ã'yɛk�#�%zi0z{I�j2�{����=�Nm7i*Ca�a� &`<@�c|�ۼ!F�J �x�`�VV!��_}I�b~� �B�g<�N%`�zezA`J�fci
 ���y�	�i) ���	�Y�� �	�+ �]�/D��c@�bff7ĦY����u���&�	f�@3�| ��i�'�	+D)�–!a|e& �A� �Ň	�i���i&	2*�zC��8{��c���a����	 ���b��>���F�b����a�i�`,`Xm%AO��	�`d�͌���{��x[bp�f��< �J �b|�􌉵5i �aA��p�+�y%A���a�j�i\d��i��`>���a�@]d�f#	 �{I`):��n��8��,DT`\v�II"eB-| %���&���am$ ��i~��Bv�������)�i���`���aX��/�) AI"��/D�p��)2z���{�	6z��ZE�) ��Ym�G��ɹ�!g��Od�i۠	�i�@�-�E�c�I���	���k!�IvP�G�:A�����!�i; Fai ��)�ܤɀy����	�bVd:a�.�`1D�I�b��y�I�(f\dX��`g�a>�	Vd���>d`\vN6g��o�I[����&��c�ɀywB[�����c�`>���ܰ	�-�Ba�a��g�P���@������.�Vv�ɹ��Hd���a¹�۪0Dv��.D�i�-�)�iq�j��&��;@�������,D�) N �(P ٹ��{�	
f㠹���.D�)�{�xӽII"�).|�-
��a{A�{�	m~�"�&��@mUc�!AI"�i����c�I�{|��	Va�z���%$`Udw��:a���I�/�	_d� Ga� p����`�)�b�G��	�-f�C���	�b��y.Ĺ���`�i�{�dZ��i�̉m8W`��cy�(�.R%.|��k@�|@��U�.��k�	9D��*RO"Wdd�݃!?��)?����i7 !D�ɻc���/���-&(���)��w"D$)@��Bo���a,$DD\�ە���I�i�I�i�I�a�c��׉8h�)�c;@��٩��m8�)�b��x�`�?��_"�)Wd���i
���
��j9)Wd@��݉[m��j

@":ofީ�K ߣ7�c��iR������IĀ���/�)�b�ɩxT��i}@"DW�8hbBd>���/䩽��)Wd������@���c�)�-X��k��Ǫ)�����`�@"D�/D��iW���\9I@�k�)4S��f� �b:��i�{g���@���f�B��	m~�I�/�Ʉ?���������Ʉ-��Y���b�i\m �����c�	�/�i�i Am�����lz�E�J�E�	[���/D���{�o�IMd���i�ɩx+a�k��f����)�cA���)1D�驉I@Mdy �-	``e"f��"|���&���/��`� 
�c*�c"@�$�5�a@۽
�aA��k*1D
 I�����6�Yd߃�'X@"D}��{��+
�c*�b*ˣW �{a�kn�M"i&�-
��Y]G�s‰b ���E�$f�E����i��{	�&�	6zb[�A[�`�y�@U��A�$
ʄ?O��	
�cf@Y""ӌjC����ʇ�
j�b
����H�J�
��&:�CmB�-�Yv�B����c�$|����
�+�c���;|��i�b���V[�q��ba�{��o� �ݮ�����]������/I@��D�`�_���9����Zv�`�a� ۾��{�a#DJ�LJ�-JMvJ�$%DXd��b�"�%CA�?�@i#��:���Zd���b*EmJ�{ �Zv�i!J�'"j�aS�Hd�@�j� tR��M�!�M"i �c#��b1���$�a~o�
f%j�?&J�'H�$�\�Ʉ['�Zv ��@�{(�+�)��{F��*���F�?��2�+�V!D$,��b-��{{�k�R�輻-.J�{Q��
�	}/ʫ*0�� NvK��1
$n2��?3j�a7@fw"[�!��%4jZ���^�5*[�6�kC�-T�IvB��87�Zd:A�a���aC�������ق[d8�m~9��-@�x;�x��"D:�k;JFm��y�%�{��@99&ۘ��'<J�`=�Zd��[v€��q«8�Ø?>J�ce��>���)��nL�Zd?���@����ƥ>��/@ʭ,A�`�@��� �i���&f`�A*ߣd�	}B��?:a\d8���C�f�a3DDj\dfH�ޡ�o �6I����`E��-F�kGJ51H
�`I
d~��i8Jj\vK��-L�{��[d́"|M�i4�Se��	��N�{t"5nma�`O�&D�H]d�C�XP��a�@�Q��{m����yRJ]dS���I��T�aU�Y�B��8Vj\dWʭ,��	}:�JdX
=zYj\dZ��?| �?[��a\J]vq«8]�8z�r8^J�>n@)|I�}K�_�$uf�$g,�b7 �>z�]�`��⢅?a�)�d �obJ]dc*�idJ�/ej\d]�'D��?Df
 g
�ah
�`5d8Dij\djJ�ck�"nl��-m�?Dnj
�0�9��Zo
�>�ַC� �,Ǡx�6S�#��p��-��>D|΁{���qJ]d�f��a0zr
 �_�aU)s�?DtJ$|7 ::F���uʦW]�2e`�I��-C��`+��x:��2*���v��-��yw*�-y �� ���x�?DK��%y*:Dzj�i]��`�!^v{��{
�a��P��/�@d|*^d�%�b}*?�� s.ۯ~���ʂb�F]d��D�J���Jme�d£��&�
�+�
)D�a����=z_d$oe��G��J]vv���`�q`�ʍ$��f�_v�J�a��?DU�=h�J	2n��$
����9�| �=<@�*B�8�J	2�g%!�������?���`d�b���J�{Bf��')��A�J@"�*^d�
Mv�
��:��
"$@�&e�k,�J	 ��'�"F���	f�@�i��F��_"��
ۗJ�i��z�J�x���'�
fӣ�
�-�*:D�j�
�iѣ�J���j���{��;n��'��'��g�j A��ܣ�?����J�ܦ�f}�f�ۧ�)|��M"�*8D́�%�`�%@�i5��k��*�
ȣ�J�,��'����J�i�`â�
Md!aie3��o��?� �jh�-�*8D�@���
}��-��f<"8:��ƴ��f���yHMd��?�$8D�G�$�bd��<=B�`�
�-��f��n�"�@�綊&D��� ۷�-��s�d�G���kobg!�Sɹ�&D�j�-<@ m��:a�J�i݅@m��`~Hi*d�-2��Va[����k���`"i*W��&���k�� :����8��ۈ��'�j��Hi*�4�*ەB�n��-Ū�i��-��-Ȋ��2���a��`ʪ�7�c`�ݻ�	f]��z��U2��?a���}��,W`V!8:�J�k�!���ΪAm�
�Y���~�J�i�@��*!��}�Cv�Cfmaf�ʟ$q��`q��g�D�V!��ʟ$�J�P@X�%@ �*�IՊ�k�#�i� �	n�-|"i*|@́f�?֪Cd���Am��8� �Dd��A%���j���ʩcيi#ڊBd�b����-��t�wb::ݪ 1�{�jDD��)q  ��o�J��*��|�J�,g ��
�{���ו�ۉ��-���?�*a8�i**��D]@�,����D
���*�{z�%"i*����
fY#?��>D�jDDꪎ?$��a�a%�L����&
A��D�Cdݡ��J�k�
}~�J۽�jg!@�>��(|t!�a��N����� �����:n}�a~( �5o�x�_G;D�$�i�jDD�j�-���
�{|��
�+��Bm��+�
-|![�P-n\�Cd���*��|��>D
�>@\�
-n�� ��%�
�
�F�㴕`���  d�)��8n�)K):���;	��;� o@?�q@�� �-:�fk�?��-I�݀D�?a壮�
 ;� kf@۽eFDd�J�>�-|K�>���+��-�!?��A
f	��hfE�?:���>�C�-� �W�]�f�>D`��*A�-q�O�`���	f�`�	�2
��>��K�i��?J���
��/K)f@^"�c~��/q <|F@��-|�9��;|,�2NK�
2�&�kJ ���-��-���?�Z�W���VA�8"$�c��`����-X�?+�;���?zf�l8}~C���7`
 �@):VA�8��/��5��-��xk{�|΁ �-�!��7�yK{�q�a K)D�b�!�<D"��?@v�[#�+�¹�:��(#���BOm$˙�J��kB�)|��%��&�)|'�C���*�;|��k(�)|���)�{~��;|*k�iI��+Dm�:�&��@��U�.�N@�xM��񕠺���Aj�����#��fT��a3éx,K�c�A��&�-+�f�BOm.�Y�|ీ/�h0kf����@[�%�$�'�-�Ad1kCJ2K�cQbF3��%%��&4K��@��f��b��8 �h�$�5�=Dc�6K�-�� 7K�k8��k���!n3��j]�d*9�F"��X:K�F�ܣ��ˀm8;K�-<�N�=�����d���:�v�>�i?�"n c�k�`�&g��{<��f@�ף���'��.A[�Bk�#C�!|ѠE�D�iE��io ��� C��I��F+�?W�;�B��G��iH�81@�����Ik�iVB�/J�'���@G9���{���+ɂ��O��KK�c��Lk�{�Av�e�8%@�k��q>ଣi@��|`��$`>|MkkFJ�q>V!��cb!��N+�i��}��	��m8 t�q�c�`�O��aP˭%Q��{2��V��a\�ݙR�N�S+#DTKI"h@�-U��ae�HdV�,DB�8R`�z�Id|�Ё% �iW��aXK�-YC��Y+ P�5m��%Cai@-|Z�{[˱�:��\�#n2&�j�'٣]Dv
`�`f@)|^K�b�IdI�-|
�}_K�b7�Cv|@��
 �@�b|��`ˏ-���' ��a+�{!�/�`GmN �"`n~�6uK���bK}c�"|�c��aC�dk�aW@��e�Ʒ��{f��{���ag�{>�Eoo��h
f?��DS@W1i�Idjk�a3���K`��
��m���	�!.|U�ik�Ή�cl��b�ju%]��i� �{@��m
fn� 
@M9oK-|z��DF `p Dq��{r��J �s�)t�	�@��uK-|`��� ��!�IvI�-|b�K��;��{v�@dw��bx+DD��=9ɏ?_��&d`Gm�@2�W�?D[C˴V!�)��:���y�Id����z�@dW��$N���{+!D���h��G9U�5_��c|K$|}��z~��iV�$D�$���8��Id͡Hm��$D��dV"!D:�u،�����b���i8>!DD��i��|��#u>�˻x�+.|�*!D���hY#?�
 6|���{�K�a�ⶣ���{�`��C�|��˿%�k��&�A���h�»x�k�/���a��*d�i~>��ݎ˻x_i*���?���{-@��q��|`ϳ�@�� �b��zC�IN�4S�$���d�9�kC٩a�����i���i��Hm�A"D���{���>|��q�E�>���ASb��@d���iI�Xb�BFdJ{���h�+�ifd磛�-�+�{�@����{b۞R$� ��f��x:@����aH����K���+€�+�i���nN@4@:At{������i�A�/���"��/Fʹ�k�?`	��g���A����8`!-%�Bv�`%|�F����i���8|`���KMv��"nc&|��/��A��{C�|��=���i�G�-��Ad�B�,���N�˿%Kଣ>��&`�+�KMvN�	��h��-;�z��$f@�,���b�k�b���{}��{���{�+�{;����+�{���O(�3�}��%D��JmkHv>��x�K�>�K�?Ĉ�{��i�+�{�k��$n�K�>�%;��΁�KMv�+�� �KMv�K"D�k��a�գq��y��'D{��a���K�>=�ŃBd5`Um��"|��b���jla�cv s�ŋ�A�k�b.�i���4�J����Yv�ˉkɋXb�K2D˫��*�� j�aV�z�$:̋�{m�&�@[���eA�Hd�&i�'���z�@��A ���'�%��b�4@�� @�{W��þ��x�W :3q�G����&�"��}H�,M`���+�?ݠ^�j�&�KU]���%7`�a��(n�KMd�=�FK2D��-	 hkԫCvo@�,&f˸�k�iU��Ά‚b�Yv֫��<���@ ��b�� X^��?&��nkۯ����k�����ڋ�'a���R�5�> ?��E����I2D�ˢc�+�-�������ت��M"t�-J�f]�?`��N`[��˥R�Zd�m8�+�ݮ@	)�`f ?��@���K�����K2D��)nw�壦���LI�i�˿,�k�k�K�{��}SJ̸R [�F��(���)n��	��{l��A�+�+� ��͡%D��~~w�ӌq���cC�i�x>�Fd��k��Id> ?�H��/��-�K�z��(n\�N$��T@�{I��k_m~B�E"�i�i�@{�@�2 
)��@R@�'�MmXM�K)Dg��镠��w�Դ�ˢcC��n������c���i�k�?�K�iR�vձ�?��D�`��>���|��y��k��^���b��z@w�n��i*f�7F  }`8Dd �o��`�+*Dрy>�`<�a�K�z�a�֟$�{@)D��f: �-��f<�aJ@�>c�m8:�XbX��=|�)�Հ��=R@B����L�kH�;g]��y��%D�aNmFd��9�R��z�C�'I@���o�}���/:�1�L�i��G"f�N$f�>�]m�Y�@�hf��>V!}���/̛k���6��A�iJ�^�eǟ$�A��`��@���@�;��,�{,�bi�'l&,*Do�壘����`ѠG"	,�y�@��
̭>���ܻ�f�ĭ>L��|Āf�\$@������h �y+D˫�0آ�:��*a�����ܰ���
��W&�`L�?`��(,�y�Ym��,|>�'D,�F��������{9h �y�F�L�x�D�,��k��θ�@�-с�ab"`�~xc��bo����?��$:ᄻ,Gd7 :D�S�f����J��,GdC�����-;�ux`�ϱ�y�ڀ�'H)D3c�,v <�LR",�{�E�x|��A�??��$� �Hv L�,��2:�Ĺ!�+|q�`"��zK#��h���`$lx*�`�-�`�-%�f� 8)&�,Dh �y'L
o!a�-(�f)L-|P���W`�-o�\$=­," �&@�Y&�\$h �yg@��*��-%@�,+��ܩ��{:�T9:��R,�'-��a*���.� |́S�c����/��a<�8�f���0�;|�@
}J6n)�+<��||��1�>v�h�Aʀͣ��2L-|�`8�dZ����-3,8Dq@
o>A�?i�I�A ��f��z�*~@�`��)n�A[��A{�A�ܕ`�����|4̆$5,�-	��R�[&�f�$������@��6)D7��a3`�;@A>�`�K�Aɣ8�Zm���
9쩪:�f��!kxV!`x�A�-%`*C�zJ��;�.D�K�a�Y9	ve��y<�3S`A�=L-n>�-.AU�DgP*���f{�?,.nR`�S@Lfߦ��A�hBl�-��kCL�aDLf�a�?EL���L蠨c0K"�2�@۽w`�+��*|F��-G̶�;�<�HL�b� �-C�(:���o@
`
fI��}�_"J�	K��aLl�M�'"`fN��ߣ����a�
�k��%�{��kO,`x��&�t��� P�{~Q�KdѠ�yR,8Dw��AFG�`�륩
�-S�/DCaf	 `xwb}��kT�z1@��RGd���(�9DI@��U�JdV�2	��hWL�b|f[�� �A���׀I��b(����`
�ǴX��kY��FZ,^m�a�v[l*D�@Av|������@�,V�7�\��x]��c^l\m3�Kd_9D`��jal�+*���bl�ycL�k�t�
ɴ|�ρ0`�dl|~e� f�j�h�a{�W�%f��a$��zgl͢+>|0� �a�6sW�.���z���.hl�`i/|I���R��'<`��j�Kdq�,:��>D
�z�@۽|��	� �{kl{����a�B�*5@�`Q�� �>lL�b�e�Q|��Lml�yn�Kd) �Y���iĀ\$�`5`�{�`�%A`�|C�|8F�>oLi8aE�y���[�i[����C���[p�,D| ���z��hq��.K��+�Fmr��{Հ-:$Ҁ�`��{s��ht��h�D�Y��E�a��.`�E;�	��zuL�yv�>AMd�#ۺV��B?�� j�aE�%)-:��wL�yv��`CdI@v�� 7�xL��y�&�AC�T@�kv�U&��+z�M"{�-]E��-]@�$|LAd
Aɣ�A��}-|���iW@i8~��{.A���	fC�;nS�}� ��|��?��i�D�x��i���+ {����'��&��a;��,ۃ�����C���&o Nvi��cq`Q�C�B|��?�,1D�L�a �a�l�o� 0��-q@��i``�x|@��%^�!?��L���L$g�,Nd%��A��LMd΀�i�LMvn`Cd���-�C��f�|��!Nd�,�a�l�%Ń	f��f���i���,> ?��=nρ�iU%��L D�@}~�����zFݣ�`��?VAf���i<����A�i���{���n���c���'�`Ԥ���zق�ca�aV!d>��?DwE�'K���t�S���Aș��$���d~" Em)�ȣ�j��eM���a��-�E2DS`����{���>��N�����{R _���`��x��RB�l
 �,Nv���$�Dm�,��ۣ�c>@��@W�b z� gk��c0��'R@c�n��,F��:�B�t(F�{!f���-�,�{C�Ad�A�
�%������?���ie`�.�>o��ܸ��ii�ᦺ �{� )�����W�Va hc�E�S��'��.D��?��"|���֬L�'4�c�:�`A�X9�l zoH�z���|�(&�� %�/D�L�'��2��c�L�'�����i۱���f׸�E���%]�f8@ ���i�@���ˣ!`��E� ��<��a�'g��"|�L�'�ˣ�ex��{�6z��Zd�۸l�a�`��I`۹�8G�"|�Lm~���a��z�a>�� G�fӽLm~|Ӂ��CAy8i�{��i&?�;6zwb�>�����L�>��<���=f �{���c���c͡ ��{q��b	��ig���A����7
f�,�{�,�i�B����x�Ŭ�bi���, N�})`ϣR ǬE��V~A��cV!`f?��L�	�@�A�:<*AuR�@G�:%��g# D�%�m8N ��Ɍ�b�A�k��"|S�2 �Z�̩x�K`�oˬ�a��m8��%DI�i&?�e�}o�m8��%�(���$�cό�c�lCP& 
�"�f*��a| ԁѬ�bҬ�c��iB�Fg@��Ń�i�)[�� :լ�b�ݣ��{DI�i��x�l�c،�i"�{&`?�4��b��S D	�%�ٌ}~���6zՠ�b��{J��"�de� :�`1D8'��L�,&��%���`Ud����]v�l�N�,{��L�>_i*� !D:�����ji�_��`�C���i8����f�L�a!��}�L)!@6��ң�L�aI��fY$���c4i8"�{���|0������̉y�G�
 �c7@)!R@�a]@)!��i$��k��E���}�A�`��8&�,3D����FG@"��t#�@��G��L�`�i8�D�a��{>�>W`�<@�� `IAf�A����&� 72D�{~�L$n��c@��M`�%W@�T��a�L�a���a�l�{<��n��b�,�af)��Ad&��"*�{~/j�%�L�'d�rb��b����'�̢c�D�z�,)�`�dA�>z��-���<�{~���lq� ��c�� �	�bf`�b��ۓh���I�L�''B)3_i*�`�!ȣ�S����F���̿>���<} �&&`��F"���#��h��>�>z��%D�&XUbC���{�,A�灀k��8�L�'W���E#!D7@	2f�\$€I9U�D��c��.�H�amC���{~�#�-{�[m����BdM�(�&:�Nm��aM}~�8�%��T@�%-Ud�%a� �L�xP��I��&Di��c��&� |1���2��x�:��4N�}!�[����f��'�c�#���"|M]mB�%	�"n
M}~%A���&͛k#��
�Cv�cA^9P��4�Y��`OB]m3`�py�.���(�
�a���im�i:�n��y��z�&%A��f�&M�zM}~y@����hm���`	}; x�P`�$J�&D�_"��J�L>
�&��i��%��Yd�`�A�b��&�"|���ag!-^mQh:::A���"D#�FW|`�<�
aU,Ud�Yd&���
�&* `W�`W2V![��$|�̦d m:D!mDD"��a]��h#M�%���DDd$�C�%
�a�@}~@A>���&m:D����@A>� �@�C'��`(
�&)mDD;@A>d Uvg��k*M�%Gt�+-5G,M�j-M}~C���Z��`g��`�_.͂y/��a���0M)D��%<@;�1�`<�$D2M;D3͂k���H4��%&�@)D5ͩc:���6
�&��bƅ
�G�I��7��z8M-g9�_"��926|��9����,:�%Dg`^�o�B�;M)D�Yd<m�cC�%C���ޱL�z)��Y=�8�@-u>-�m ��\��b?M�c@M;DAm�a2 ^mI"g�&��jB
�fCm�c|`��Ci�Dm�a7�m8R@?<z�>@`8�E;D�`�x����#�E��I���FmDDG
Fd��9�p�	��$DH��aIM��;��`q�%DJ�~8�a{� �h�c۷P�
�i��c�eޣK-[�Lm8D��
9)֣M�F� �`n��>@A>��$;� o`{�N
m~O�<D�X�*�;|7��+R�Zۭ��>|`��[�J:��^`� F&|!<|�@�(PM DDB�+��_QM;D�@�`R-�'�I"g@��:���̣�s�S
=n�۔��zJ��>T�`栈be���@-g�A��P ����@)DU�b~�����;|V��h��n�`�P���WA���=||��J�G�G9D(� =O��W��&���Xm�a7@]d-!Gd€YdY��4&#L�Z�f�iB��i[M D<��|�@��f��h��iS�$€Yv'�z����u0P���\-<|	��i]��'�ke^��z_-'���hW�%€YvN@�`"�$`��h��%��<�a�ad�Yv��(�f
;�@�=aM :b�N���h��`K�� :D��F�
@��#�$
A#zc��d��"�$em\vf�b~g��bh����o� ^dڈb~�ZҤ¿>P����"Gdi��&j
�%:���<�'��N�7 :D*�`5@�c�#�k-�b
�%v��� Gdd �-l
��m
�q�'DS-�bn�C���>a��o
&z�)�b���ip���Od�;�]":q
�arMd~_�h�aQ���DsmR|hH`�Mv7`�zt�HduM�vM���
)D'�+|0���b�H���X`�`7`�zw��c�@�n�!�ܨ�G�JE��€Yv@��J�L,q�k�"Dq��i;���xM-n:aH���hy��z���''I��B�oxz�)n��B�{�Zvz��,>�[���g\?�I���|M-n}��&~�>D��'f?����`Q���&�
Mv�M�{�I"x��JT�'�
j���d��F�K��%��bJf��͋b���̅M�%Ѡ�')`�x��׆M�&7�ܣ�d�UJ�b�-\v���"`�x�DjԉM-|�� _��&�m>|���݌��'��Iv�Âb���'C�m#���'W@ۏ�L�I��i-���G����)�-\vN�b�͋bW��'K �o�@ !R`�b��Hd���hS�>D�A-nC�T���-DGd�Y��X?��
/�Ǖ�B&b���} �$|ඁ�
�Q7�$D�e*DO![��M�ݦ�	�M-n�<���S	���m*D���f@��< +h�-�S*&�-����k�-D`*D�mg�A��@`����%���� Z�D�z �h���a���-�bW��|�`y~͡�&
�f�M�z�M ��bIL�D�
�ce�)|����M�{N��q�<D����.De��| ္��ca��b�
�>�
�,��?D6�e�\�-|��>�@���
�ޥ-�b���a��KdJ�(�*���ej��z1|��^:Aj�����M ��	���aN�>N@�`��i�M$n���j)@âf�L��M&�F��S�N%` � �ͮ�B���`�����ŌKd�-�&OB"�
�a:A&�`*D�A�������
�z|����0��@d��f��	��b�?��٣�
�a� �(|�遶�&D`�Ӹ
j"�f�m^vb�v{��$��Bd�
)�-�i���iYۮ�ۻ-�b���0)�-�b�
��| ȁ��AvǠ,D�B�'S�Ģ�.ϿM�'�
�c�$�x�_o@*zfr>��4��ej�
	 Cgו��w"0h�
�&I�~y��I HmB�zN@ �F�`�M�h��/z�!R�>ct�8it�M�`��>DW�
���Ad��b~�B�b��bȭ>Dɍ&D��E�g��˥��ʍBdo�G�&`�ma�c��Av��aՠ^�� �i���a7�f��%KR 0h͍-D��Av [�ύ?D`b�+\�G�����
j��-+h=b!� 0h{@�B�/hD&�&N 
;{�'D�-�i�&D�Ji8i��ԭCd;�'DJ�	-nxHg!{�'D,d~�M�b�H�>�ݰ�Mi8�M��c�f�!�&A�>��i~ٍ-D�Mi8[‡�o���M@��
̣"`�cʀf�`�,���hݍ	f���yZ���&D�`�b*a���mf�M)�M`8���;�ck2A$g�cH��&gd~f��]�>Df ۺ` n�lf�&�����tx;��d�!�'V��`���	� �c�8�O!g�R�}.�n>0@���Li83@��G�I���E>DB�NI�����f���:�G��@�X�-0z
�?D���Bi8ak5g 'D���&OEQҦ����`�'r��`zipxW�-h:A���-D��'D�a�bW�k歈bV�f筙�J@B��$�i�!Ei]�$��w�	��-Dat>]��$����})����M"�o��n���iN��F��N��M�`@��뭸��Gf�Õ4� i���.��M)D�'���}W&h���&W����aT`Ed來��&��XF���€�b{��a�� ��/D�vxf�o�i� /�w���
�k���^�@������b�,����+ �J����a~۠�D�j�T@�cw��g����!����)|a��x��X��a~~��j�mW�
̣�
Fd����P�
ل��`���&S�.hC��M�a���&1��,
��D�
u:��$�`7:�
�$����mEd������ {`ۨƦo|��cK�.D�B���
Fdn����i�C��vx���&@˻N�c�e�f@�o@B�'��d.�aC!��OhX��a�b&h�&5S�!����hnEdv ��0&h�dۈ@f�F 0h��ad`�i	�Bv
N����)w�ң�ҳ:�B�C`��@m~�`��~e�a|��	��>N��`��<�w�+|J�`
�Ad�`����&�e:����`o�G�
B�&�@�%�GFd�!���b�������b۔�b~��`�%��.Y#�a|���N����a�C�'�D�&�c~�'@����`ރ�ݻ��i�G"W�	�(�g��!�	���i�m8�$�a�(����aW��z���B��Oa�m{ ?���a��Ģ
A#z|���!�a��ܾņQg��%!���K�� |�O����m�m8I��Nm~��a
!֣VA1hV�u,�۷$GdP�C��o�R�c��>�bX�J�� .�C���!�aq��c".0h`X�w�̌���F��5#
f$.Gdq�'q��cd@
=F���A���%n1D&��'��&��&���} D��8�5h��� �&(�bq��a���������'JI"o�G�7 3D��bL��ΡG�2�G�|�����<�շ)� n:����$�:���_��&�)*�i~+�8��o,.!D�LՖŀ�5-nX�.΢x��b&	���\ �b��#�H)!/�!n0N-|]�ke& �D�me<�@ЧH)!e�'1.W�&��i��iW��2.DD ù�3'3D�E1zy���	��%?�q@d~��)3�-D��b4���e�F�ң5���o �e�h�@��}
��	��ARx�|b��_m��,6��aM���y���+.|��7�f�C���vx���i	&h8N�'9�;:�)󧦏%��y��|>ȟ�;N-|<N"D�E�=�&+me:��}�ǹ��IdDI}�f8>Κ��Űٯ�t?�)@N-|A��$��f*A.�b��z 4S�(�o�G�"��b�%+hB.�&C�Jd\��DN�a���i:�iE�c�@Fd�	)���6z�H�B��X�hq@}f�G�T��bC�a�@��F���b{�|����"�ۘ���G�"|W�Id\vx&�!nH�	`*�-�E�'�$I��a&�9�&� J�'B�f��EN�	:��KN�c��xx�	�x\�G�`{�L�b�Kv_&UdM��NN�O��Pέ%Q�]dR�c@8hSN�E�`w"�z_�}*> �bTn y�����KdU�bVΤjW R`��f�|>XNoem��bYN�<��#|Zλc�&[N"Do��A��ct�	\��]N}���F^�f8_λc`�����U�
�a�	}��ib�"nf��c��&��'�F��d�He�Kd_`#Df�#n�@�&{a�$؂��g�f���f+!��hN�{g��|����J��bi��&z���Ir>�'�cd���A����E��j�Kd���+'�ck���&�l�]d����m�ag��D �Dˀ�n�fom~��pN�b�E�`�`�q�$Dr�Kdp�%�^�g@��	 �|���`#Ds�fV!(gf��� �|gm~]f��tN�2�|>g��DW��,g@��]`�z{A�$m��b$@�bu. �'_�&XϤHMvvέ%Ѡ^�wN-gx�Id���0_�$DyNMda�[dF��iz�c�r,􇻣�e�b:���f��+� %n{NMd]��+���Sp(�S|.�a��dQ�
���
ᕟ�* V![���.z;g�{}N�zy q��DSF�%�A�� c�i�@�ѩ%D���D�� �`�%_;AMv~�{e�$:g��D�n{�od� ���
A¢���'C���W��DOe6>Oe6l���D�ۃ�f��c��(|���M"�n�>��;n��%DŨ%D�@I�o�L�F +z�%:��&���Sjʣ��
)W@��[�݉.+z���`�B�<X`�c�N�z:�eb���b�Nf�`�dP�H#��+Q�Xd.�h_��c:AwH�N�h��%7��b�`8D�y1c�&:-�xx-I�z�b��{@��n8Dy���=m�c���D&���{@�|B���N D��&D��f��u3�Od�.}S��κ@)���%@������M"o��$��;|�N9D���Ιb��ib&=����퇀R���;|%
ot�C|���D�&d/u�A[��n'n��&� �5�U�%�W�
}_��{tb {;a�c�+�AV>P �wb[�>��f 
ۦaʀ�B¢��>�.NvZN w@m�N7�|B�A���`�B�z���&N`J�{f&��&r�SJ�@���N�z�F D�
oW`X���Z��Dހ�*��Am���b��'D���c̈́�c;��`�n b۽�N�c@�:�nX���8�
�4I��֪��i��:]�Am
�iN��%)�Ā��	}��
)Tf�c�.�
B9�.�'�Qүn�c��h`�o�� |��	7)W�2W@��&`�_���f��x:`�&d/u�.�{��D�.�C�)�.�+��
�h tA�c`�oBۯP K�
}�n�b���z�DK��.�&�.�i
�����3a�bN`�V��"|��b�+I� �8ǧ�N�$Ĉ �@������E�5���K`�+������ay@�#�@��F��%`�`7�
;�vx߁ ��辆Y���)d@�����b!0z{��i��)|۽W�ۻ� (i�b|�j� I��έ,�L�c��)|'�����iV�>D�`�`��E��m8���X�)Î;n�'�c�N�c��&1C#�	 K��.�'�`�`i@��
@��*FF�b��υ2?�b�ۅ�;ntB����i|��N�c��&�;|n�(n��b�bC�_"o �&C���R��z� \d��{��i|�	&��ˎY�z�?DΔC��Zd�n�bU�E�����(�#��|���	�`*�n*D��-o���)D�]d����NDm��Y�`W9�nNd� h!{�H`*D*[���}y����@���!Z��@�b�N�ce{��Nf��b��̀��a:Aw��Kf�N�c�ߣ��ԪvX{@��3�*|�&���£>cs��۽��{�N���NfMb��:�@d����]d���+d�)|mA����*|K��i{@
o� �N
}�lj ��{�M	2R�<DY��nf�CmN��@	 �"�*�[dVa�hF��d;��`�B
}�nEm��b���~�n�x�`)�&�Nf��i� �1�n��N����*|i��{W@8hP@@"|@�7�O"�n�뎛yW�� �8خ��/��'��
��/�$�+;@6|�?z�}&,�2���z � OB	2|@��n�&�� �F)=|s����c�N�i�}�� �I�8AV>�a;��&!`�6� ��<h��a�2A�\�(����]d����d����ۇ`׻f����oڈJ�aa#>n�, �.�`�)��6	�� ��>D��&D���j��V 
&�Y�]*��>D��% �b�b�$�N۽�� 1 ��) �q�/h3����.�b߃��VA�8ՠ�i�N	2o�dO
}g��Z-|�bO+D�W1W!Uv�bf�-|L�>DV�5��!�탲%w��o-n�a��<z���E3����d,�+i��'wbt>� ���f �aq@�$��Q;��&|�Y�:���%�,��Cd@���xo�{P���I�X9� �f)�:������{��>	O�i	 >D
ϙbI@�^@��/�/�a�!#�B@ۯ
O�`OC�\����Z�Of�O@���j�kV!0z�60�f�&D���-��vf ��P���}�>:���&�)g@���o�{W�-D߂Bde`�{dl1I�>DP]d>fc��d,S`{?�-D�Cd��Ɵ�Y��,D�}�C��<@Vv�Ý?���xŠ�*���O�>oDD�ہ����`�io�o�{�"�>0d~:���d,I@?�o�&��D /h���Fa�9De�|��W �kf�G��){�V!0h!��aT�-:"og��6z#�-D�Ƣx$o
 %��{R��x&�?De�)�F�>�5zgl	�|H�Q'�	��E۽W��(O۽��t�)O
f*/�{���/|��H+�?D��-D,�?D_c
 d)|`�EJ ����e���`M�T]�F4��KVd�A/|��U-j���kk�B��/]��b.�ѻF��`|`�	�@i~zA$g΀�?:`�-$D)D:�=z&�G��u!C�Ea//��0��1�;n���2�)n
 M$\�G����d?g�3OVd4ODv5oDDy��?�� ٥W�iw,�i �MY#�6}N�Z���x��j��8س�ebW@	}d@��(`���-��\�7�G��Df8O}G`vq�&\��9�;|��o��J�,,DS���W:�f;O
f<�xj=�x��/�)|A}x>Ϗ�h�I9?o_�%@/�{`�bA�i�E��B�.DC�xg)��D}v���E��{f��� S�CAգ	�8gFO�aG��iH��FH/4S6Y"�aWd�J
fC����i�j��I��{;� Jox��EZdm��&W`{�@ZdW@�K��{%`Wd${�Lo��5zM�y�`�i3� N��iO�/DP�2 {�@�Q��{R�Bd���n{�F"N	)���S�;n���&ca1DTo�bU�'V�)n7� ��f�	)WO}�A����8���/�@}��Yv�VQX�iz����{Y��{���%]@y*ZO�`�N�b�&C�C!Cهʢ���[O}wb�`Bgx*\/�i]O�'́Yv�Dd�*+-���b��^o�_����(d�ư�a��DF�`��{ �P� Dɤz�a/�bb/�i@�{!�i�R�*�Bd%��c�Xvc��{��d��`�A����D�F��g!|�$O�f�w"_�eDd���df��zj}�g�-h��hSDdi/�bj DC�qk��D��ab�DF�;��zk/�b
�%]@y*lO�b��3�!{�9!mo1Df`�DN@�6n/�iWLd~;��z~��ioO��p� q/�bw"[�]��=0� |a۱��a˥�+�bˊ9r��'so1DR*�/!�t�ZvJ�����z����N��&@
�`Ud������%��Ļc"�xjU��џc��u��a`�{vo�D���`�w�GS��(W
۪@ݢ�@��x&`eۯ��Ջp&��>DP�V�_&X�yV\z�-D��Zv{/~eac�d���|�f D�
ע��y���y];��ze��*z�<��� }OCh~OC�?C�r
�c��#n�c���(�j�A灍$=��/��/e ۀo	;��O���z���hYc� ��$�o�i|��n�ݘ�m~d�-Dd`,B��8�oF����{e���� @�k���`���!׌9��Q�`�>|�5zN�8#��,� �z}�ۢ�+�ig@��n�8�f��	��<'�¢c��:�Qb��Hdh� _ EdM����F"�A"D`i�_�O�h} )7�b�o�{�Of4a� {�糒�O)J�\����/ Nl��/\v�� 
��{A�<h��t�=u<�AЧDY$�o�����O"D�� �ۢ�@���d���Ad;"��.�]dvo�Dߨi��E���ԗ��{���՘�f8�
}�`��T��i�]d�/\d�]vǠ�{;����{>��EJ���y���`g &D��?f�2��}�/\dJ� f@8h�"�:�t���Ԫ:�t��Y�m!)��q��ۮ&���a���b�}�V�nP����!{��`#D��o@ �!蜤f�O�a��	\d*#82�'���� �/�i�b��/�{�� {@Y�_�,��&�f�!|��f� A��o ��}2G�b���b�]d�a�`�d���| �	���b�/�i`;��؀�A�.�f�O�b@�'�]d<`�=}���':���6n��Zd��x�Add@�>_����,�aQب�^�=/�{%��{MVaD$��vLkΩ�+`KdR�Bd*�=��$n� �8�A۽V@�ݔ�ZdW {��@�.�/Z��@Y�y@�C�j���aT�jf��JA5�a�����J�^���{���I����)��f�z���{d@1ݷ��i��oI���We¹�)� �z��{���&| �������j[�3�/�ig����f���yI�H����%�);@a$tAMv��jN��(���a�o^d���(�OMv��B�e�ۮ!@d)@����{~���D���i��Cv3��)��+*����$|����i*�:A�u���DC�8�
�Bdŏm8Ə8>��i�A�b�d�%b�+�����i���+ȯ%D����yH�k�I��{~�� �ODv��i���i�`���J��Ϣx:�|M��`ߣ�'w¢>��m8�Of�o8D&�27`\d�eDDĦ2R`�Џ�iW {�_CDvR@DdS-|~��k��i��d��{~���/�I"���`�+fVbN��� Z��)Rf�7�Jd-�@mρ�iqa�,ԏ�hV�8|Տ�zΡ2��8|��&D�`��`�����!{�O(U2�OMd7�{W��@)د2;�@m�o\d<��iʇ5Z����aDDP`�-��%:�&�g�&�O
}ۯ���$�?���(��xߦ�I�}<@��*�Id�`�';`fM�����k���{�b� �m���y��j�O9D{@�����hf��|���`�`���@m|`�	ʠ�>��jw@ݢ��Di�%":�Va�:�F�}��>	��8�/)��j g!|`�	��<g"����/�b:��8T@`8|`�^�\d��bB�:�O9D�@���<z� ;�Q%::(��I��>*��D�f���i@�
![���{菆$ׂ|�Ѐ��`~��zW��q�fJϸTI��z�/
 ��h�)����/:Db���5��h��.�`�{_��/7�}"��/Po�Dv��U�O�`�/^v%@�a�9|��:n����B�<�́��i �v������:|F(:D
A�h������]@�j�b!g�OX����5�o�ע�=zd���i@@�!a�v�����^����ݪ��`���3��<v ֜q��,�����>��	f@�iq`�w �o����Ń�/ͬ�>�<zo ��a/��<;��'��z�����g��՟$}���,��;n�O�i��;|��z�GIWMv���ǟ�}S��z�(��}��Md��m8�ȸ�>�dK���W�:|�O�hA��O�z��P�iP�i0�c�;|W@��:!{�� (��`�C ��e`�i͡Cm�a��b�j��z_��*���>7�)|�@�h�m*���^S�>I��OA�U�-�A�*�a�-�8�F",̦>��!g�@"!g ���p�+�}�i��i'�9�!g%�,����`�_��a<|���+�`~������m8��;|���	���;nz�<�I {�
�cTh<n��bR �X��+���6�g�.}!#�i��lCm~p-D
��x� �����<q ۘ@)g]F�ޅh�a|`ʁ��d*� �̀bZ��<C@��b�j �iqa�x=���V!�"0�{�VbB�m8P+D����P��W��W�;nR`�dp*D�;|�c��>�"g�D"�,�*|4g�eJ��d��%�!�bR����,�!g��b
!��A�Df@ie9��y�c��`��Yvʦ8�b�8P��eB
f�a���k�c��D�fR�Idf�Q$⎻c��8�h�C=n�IdW��dM v~P=nP�z��h ���D���J���b0�i�@2 P ]��$|��	F��h!�ۨB+DR��`"��.#P�i$p A r#�� %��/ $gW {���f0 ���`K �&�''$ut�f(��d����S�{&��W��f�'�{܎�c)P�i���H�<Du�#g�n�*0�{qB�8+�,D`�ã͈f8a% ,�f*$u3@�J��;�f9I=|�`��f8�,�{-�+|.P�i/���00�i1P�{V�
ۦa�C���2-n�|t�|d �+=���3�f�a$[�-|�a$���m��ݙ"�"��K@�<�(4�w"Hd�L͢	f�	���x�#�h ��ݠ�D�a��*AƓ�@գ;���4-n顣����o��iI��5�f�@�2��#g:��d6�JdW��7	)�a��7�D8P�i9�b:j?;�>D:��d<��{=П$|v7��`��A�-��#gR �>0ck/�go >|�J�i?f@��bA�=za�>�KC�AP�{d��#B0>nC0�dS�?Do���h�J�{Cw�<@�d���i>�jD��H�^Ep�����FP�>G��iF@�,I�طa�vJ���v[�˥��͡ �ف�.菅-DM�C������8H�f��D�jV�z���"I�%���D�!�c6�X9P�E�J�f��*KP�.;@��L��<M��`Nf�@vC��`F`�_i�dOp�F@�,b#��PP�&:�����@��@�<&�̻Q�@dA�>ma��R��b
�$gM�}}��SP�.��>ckF��jT� Oh���GQ�W`�bC"gZa.|U�m| ց��V��i��b��iWpfW� X��z�LI9Y�@dQ��b��|�ҀZPMd^�-:���[��P���<�'�# ��ãf`�b��{\Ѝ$�j�P�y{��]Ѝ$^Pf\@��,c�b�e�_Pi8@۱�k@o�!X��MI9`��{a��,:/|<�
ab� *A
�|`�=*�=�&��p���b���i��[mc0{�@W�dpfbZ�e��f��g��>.F��*F�gp�{X� ���iwˢh��b �j���;nN�i~
A��i0Nv>cR�
��&�s��"gjP DB��>�
�`kp�il�am����`�+��T2%��x��j(@ݢn��->@�o�Hmp0)�ᔛ��i$�xx씡�`3�|��j�/Dq��$\`�bA %ݮ��
)$ X�f��<	�F�<�'zAf�%�� դa��rp�{�!X�rG$e�I$`�bW@�a�i�����i+�6e�}�d{�#5S�b�bd�Q�Ճ�W�
oJ`\�
A��sP֣t]m3���A)CA$gu�i�CA�j˫	�D� ��i~bT��;nJ`Aze�[mvP�z�)!���>A�nie!D��i~z��m	2H`�j %�*����i��u���eB)q�BdP��8j���k~X� H,�bg ��Fb;���]@f~��i��mi X�A %�:@�zj �ݑ�$g��iw0�xp�byP�z7`�$A %�z0�-C}{�Cd|P D�ۚ}P�zW�o;�������~��{b1D/�b] :`)�)u��Bdp�`�����Bv]�)W �{ ��e��|��I`)��4Z��Km�h��
�i�%q�Gb<�
a��>�梐Dd�p�> rb�hף��Cv*a�/�0�b�� |P��&����Ć�&2D_��'�� |I�d��{��$���z�P�h��}~w�<nK��R [�el
 M�x�S�{�C�"n|�E�b)�P�z �&;�����Cd<�'�x`�>� �I@Xb��YdC�x�3��zb"T$1@�#ˢ�%EdI�d�i87�b��{A (�e"fA ���`Œ0)��i6�0�{��)u�P�>(`&��{#��K��}��i�"Ƙc��'���k�a�*}2D��L���$" )�?z��"_�}#�� �V�[d��h���h	�~~]���0�e.�m*�0����i�M9�� �0Ed��{@����iX2D@���E��3`�}��i.�h�0�i�(��w��@�.��z�)a�>��zy�?D��P��"���D@�:Ar��`!�|��	<��
�"n�� n��o��bd�=%�!n��zd�b-��$�	�hf �%&\d�P`#⊣�R [�������?D��Jc�P}�J���F�P$|�!E����b�`{�} Ev�PMm�P$|�0Ede"f���h@ˣ���D��{��z�&�Pm~��/�`�~W�>@@"!��j���bA (ݮ }	 fg@���m8�0u\S�"|€�D|G"DL�K�k�ݗ�K��FFd�p�R�"n���P@Up��h9�9�.�m*�P"D�M�{q@��"��/���${��{0�"|*�Y���	!��%#�#��'���i@��|��	�"|��[dd�=f ݳP$|���i�b,��E�6	�d�@�}�@}}��`a���{�P�(R��=B�`3`ڶ�'��9骊�i�PFv�0���/�@�{���1A`1�P}���| ��;@Fd��x�T�h� !�p�-�$|�b��!!ݨ�)7��{|��	�&|<�Jc.A����#n:��ʾp�ܿ��i���i�p*����)uX�F"M�"|�Yv|�ρ&�&�$|�P�'�g&��#|�&D��tIŐ-!���(`���#n����a&�h9 !�Yv	��ǐBd�@�i���Ȱf<����������P�h<�'w@�	���a$n�E����^�҅�b9A��0�<�.٣��'&`Gd���1`�o����i_ �?I`�L�{�dGd�  ̐�i�0�{�� ��p�i|ف��ib �ϵ�p��a܀�@��"`#D& �����b'"%n� IB�x�Єb2��xA�~xT�&D��#|�еA� ��a�0 3!���P�,>`�{�E�W&�i!���`�z
������b�{!���7��j
�#n>���02:AXbU�[d��ݪ��J ����΀@���p�iiF�jm������%���Q$g)�p�j�p�xx��g�y�g���0���){��+�i(`� �{�``���it�p�{]� u<@ ����` ��zN`�d��)h��I`Z|�p���P�i<@�d|�ۀ2!�bߐ$DT�&DH��x���a(��]di�m~� ����� |��렍�;�E��fO"��kf��ƪ�v��v+
�z�2�����~]#���@��`�"u �{F��>�P�L��8|��jAH�^F��av�Uv���P�,�A��)�(�`��@_d%d8D�ШB��aU4�K�,����`�z��/�p\dT��{�B��@]d]`�h�����`�zg�`m�
f�P-u��z��a��%:��`������N���&`����-ꐻc��JvK`�o�%D&�&D���+O��>X�'��U5J��dC���0�b�`��� �wb�a&C����|��P&nN��>��`@��g��:�����_�Kd�`8D��aJB���C�" �iNH-gd@�<<B&ntD��a�$g�$u��' W���)|���W���E��e �d��Kd%@	)<@�`�	}zAw>�����!ݮ�':r>��Kd5��iJe��?eÐ���	]fw�p����)n��	}������0�z7� E'I'fI�ye��&��'���=��y�)!|��� ��a��F"��	}�g��X��Jd��z�z>aC-u���� ��Q��E'�$uP`'| d�,��ʆ�<"�ס��%!��A���A	;�a��1:D��,� ��B�a|��=):
�(|*�>`g�
 m,):�a'n��a| �	тbѡ8R��hR��zS��>�bi�+	�U 
�)|� �������	oP��+g��ѡ8
�'D��	`"�;n}�)n�h����|@�%@ qB�-q"�h���)D'&)D<�
 E'F��۪�)|<``x�@�a�(|�
���;|�d��I��
Aˣ|��B�VA������e�`F_�$uߣ`R@�ۣ���v�Ϯ�F�� �w� �&g� q�b��h��z�aL�M"��%E>�;nF�޴����gԮ�F����q�5�zg� ��%��`��Yd�� �@�����`q�z� >��������z���`ѭ><�z��`�c��7��,���Q�iR%�&�a*D���@&u q�az��a7@�$	  !q�z"(�)J!�"Q�&
���#q*D$�*|v�7K�Dˣ:�YK�g
�޴� �?����M"B�>!��>�a�z�fNvJ�*|F�)%�uCI�dEg�aS�AH��� �a�a���	|�Ӏ���ǘ�<D&Q-n?g��'Q+D���'PB�(�<D����)�z�B̈́��Q���N�ט��"*�  ��d+Q+D�z>D
}g� ��F�����+�<D"@-n��,D'�*|��`,�<Dw��-�~S�V1.1�/�a�<D\$��&㯏�,V!�8
@��/kd>eBDm0��a�f�rM
}|@Ȁ	
F���'�?icD�gz1��	m���2�a�ef}`�hP���Y�CAOd3��B�Zd��j���.4�a����!��5Qf6q��`*a��7��x��� \d�[v���ji@I�8�~��,��	�=D9��a:��a���`)`,q�-D;�+n�${��!{��@$u�� n�*D{�
):!w�I�e�	�F9���`+Q+DŌ�&džF9<Q�]d�e V!�%9� |�Ӏ}�&�N
`�x=�`>Q)?Q$u@��j{f{�`�dA)!T�-D�N�Bq�'kٞCQ+D�ǴA�@��w��_ۀ��:@?��F�]Y99A��T-n�K�hyŹ���K��D��b�G�A�z>E�%{A�$;��z��[d��|8����\b@�hF�[d��u/�a�-DG�Ym菹���<J@<�%-n3�5�f�E���y�2��'|`�H��aIQ�zA�?�@?���b~��p��#j��@$g�a�J�-DK�]`�>)��L1�a!�-o�z�,VB{!)�'�+]��h
-nMQ$g�"���
-nN1��7�?D�$��%@�zy&{k���� Wv~OQ�bE��C�r�u� n=d!D8�븁�x��>DdB�`Pq.n]cAmQ�TTR1�b��{S-|�@W�<�'�L�`�B	�|\1�?T"D��6] �a�ȟ$Uq.nt0�`��!V���:��|)`���}�`)W�"|K��%�i��@�iX1�b<�Jc€Ym^a!DYQ��`b�+Z��z��)��Jc��.D��[1�i\q)]�?D��^Q�>]e&��-D�yȦ��d�Ym;��D��5G��oN )e")_q)w�Eaj,���_P�G�w��+� X2��V�Eh<�'`q�+a)bQMv��>��DU��'��%��d�;��i?m{�c��ad�G�NF�����3
��⥩E/|�ͧTeq�a:A��d�v�����E��@;g���]��><�'&����fQ�`���g�z;�{~h�#ni�jz!�?j��a`����e")��j%`.|
B�`Va_�k�#n_f.n
 |~l1F�_F��m�}_��an�h8I��`�/��Jce��a��s\|`��o�zq���{�"|/�f2Wer�@�+;�v�<@�
���A\$`��O��2%�}�>��8gA$n1@��pQ2DV��V�z~q�zr�}�e}z��"�[m�zd�m#\��xM����x�X�z�`=%@i8s��`N���$,���M� ��lNt��`e`�au�#nv�ʢw��aA�ax���oy}~�``~䣩8&`az1�{M�#n{�z	���w@=�� I`D�N@4Sڥ�#|3�&�i˕o��~J��	��|Q\$ĥ#n}���I�d~�E�-I	2U�bH�/D�_"�`o;�9�ͩ#|y�M"@$|�]m��f*�q1D�h���'�1�$]  �b�v�Q�a��`��)�`%|�	�b�!�v��)u�q1D:A�ʇQ�aF�&l!g�'�h���a!�,�醂)� �b<@���磉��b.B`*�Q2D�`%|����Q)��'� �b���a����.�����;uN�Cd�Vd�@7�d�$DT��`��ؤ�1
 �@�����'� ۷b���Q�xo���Q�'���a��;u��Fm��
�\��ޔDv��b~�`�&
@�a��)]@)!��Cپ`���2D@۽Z	g�g��Q�a�B�(1�D�v�7.�Q)Dv�Q�adoe�&|v�{D�E�� :ݜQ)C@�8�]�
��c����1�a#dU��;F���bR`�m1@�gA	 &��:�יV`���VdW�b~e�k,7����k>���a��%D�+!I@Xb�@۴�Q�&�x�v5L�	f�Ѣc���a;�����` Wd�ff��b~�@��}��+ rbW`}I�TD�x���` �cN��	&n�2D�����bѣk,� Ev�q�H@�&| �	 t�z��˕�ǶI ��A��P��Vd� �b��c�@�pq��`�b�{�Qm~�Q�'UD�{@�&��,]��$���`�q^m$+�ĮQ�&S��d�ֻ@m~mڥ���btIF٦� b�(|`�|���)���cq��$���`��_9n�5ti�� � OaZ�i��'*� e`ce`�{��b�o�6����`�����>���a���`���ba�>�13D
�������a{�>��$��c��]Z�F"f��n&I[��b��� �A�ϵ�z��(|D�&�Qm~%�Xd��D�-3D.��c��F������c��ޣ��m*�a���@�f�Q$;�a���&��`R�)��`��2���w@�owBKm��'q��`
a=���ժ��+���a!�����ĩ%I@����(|�௘�`۽�{~�`�(������ay���0`X���M9<kJ���=S��a����q@��3G۽.@����by���C��q��$��aT)Ñ�bq�Xd��c>�%�]� | �	�a����	}���_��a��bA�Fd�d�Ǒ�i�1)���z��c3G۴bb`j;&��ɱ�&i��/
/�Q)D�� �Q}��?!�	}��*:���~)
)��4!@�T@�'\�?!̑�a�bGd���b�1
)�b��r���٫�N�j?l DV!pxf�E�i@����݁�b��x��,)�Q�aĠ� T@}o )�1
)C$g�!��@��A)DБf8;��q�	ol�4SJ�4��GddB)DJ��̃�8|�&�i��&)�nёf8@9� �z�Hdf��Ld5ZNf�:A��m~C� ���'��)|�1
)yबI`�Q��)|�	}|��7n��b�����ϪH� VA�>@V�JK��q�a
���߅D�e�b��b&K��I"�@���2�Q�aڱHv���i��&{��a�A�,�A���Q�a���ݦ�eM����*8�$gޱk5' D7�m*�)f�yb| �߱Hd���{�q�a�����`�i?�f8��O��PZ��1�OV1��%@�,�1
)�@+t�o�Q�,���2�Q���$g�q�`(�f΀be���&�~��:�q��iz��h
��ݤ�HdA`�&|��{I"g�� �&�C����Ū���X�Am$� �Q"D�)�b�	�+Ӄ[d�}\�4��E�ʪox��&b�ZvT@de0��A��Q��i�I"�Am�
��Am�%�q\v�!�b  2���>��`�@ۦ��z��D1u,�)o C�P s�>051�@��'��b�m~3)q��&V!�8�1�b�q8Dy���&@"Dac�`�m8Dn@�������� �%C��Q����"n��zo C���f�"��,D���q8D6�)��8|XA��o C��1 �)�q�d��+De��m��h��Id�Q9DP�1���,D&f�+f@�����y�q�b�F2��"|N��`?�����'Ci8]`#DVAi8��1�"n��"|�
'�,D�i~R�>�"|; j�q�i~]@
}�&%���<@�&��8
�R]dr\dR�>R]v	R
}�a�-N��`
R9Dr�b(@̣}@"Dk�Kdo C�A��r8D"�9n�f�a=�;n.�8؈`��
��`/F���)v%.n} R�_�R D��&�$nr�i
!��R�a_��bEJ 2^d����	���U�P�eB-|$n�;|��Cm`��e}�%<``x�!:D��&�Xd��c��J��+W��~d��i�$n�:|� �d`
�Y�8�:n�"�<!��r�{�D�b�f�>r�; �v�����i7)!^M]d)@��@�o��cR-|]��>qo�`{�%@�at�-D��`o�:n��|'f�τ$D }��t�M �iMo��i8���{1e�$�&�o!�;n7G�&"K����X#�o$R�x��Cm% �#�b��cH Em7)!�@R�&�}IMdb�r��:|'�;n�!Q�z �'*A(�;|€�$	`d>�'��)R Dw"t,*o��a+2.|��.D,2.n.$}��|HckV��.  �0f���	.�L�-R�hؤ:|{`8.Ҩ�/���N�x�/DT;DD��j�	o.�	}0�;nA�����]�(gF�/D1�/DVaNv�@Fmf��"��<��)��Am2r<|�%D3�	o$�.D4�))��%n 
)��4G5�;n�@��| �� �'�d��{k��o�& �Y�C�&��B��8�X�����@u>
�&�B��6�B�Md7RFmo@K���+7`*�@Fm8MdI`Am�BFmA��)g<|9R����/D��:R�+�agխ���;�/D��[<�z��i=�c>rNd3����8��� ɓq�ux�����b?Rm~&B���hۀ�>@�)0O�Y�z�b<n]@=|ArGm��8%a�����|��-A�Y҈�f�h��b�ַBROd@�$3�p�0ߣ ��C�	oDRm��i*��|�����F+E�c�F�� ��F�"|( �GR�|N�)�8mA�&H�ߣ0�c��yjI21D7@=nJ�&KFaf�L�`�f�`��dc�:a�]��F�F�\d��G2�LR�$C�zM�b
��,N�<�b��!�b��)OR2DJ�q,�`r�P��h���%3G۽�o��Q���:a�o9�m8
�h�t���`m�t:��.b�'T@2DR��`S�+
A�b�$�+T��4�)��>�������SF)U�j���)f Q�F kx��`�B����8V�@dWR=|�=D�A�܇��Μb�`X�=DY�@vZR=n͊�,� �nmMU;y�[�Hm\R�$O`x�l!ٞ]�m8� f��b���^��`{��$_R)f$& �d`�a~����f�a �Hmހf�W� v�=Da�>�	�b҉b������`C��4C�������V�c�Hmo�+3�[dd�Ime�;u)��fd��f-g��>N�5��Fa�ަ�j�_�9gg2�zh2�a������I+�a{�'D$@Dm�#���&�)DiR
o���;�Dm>@��|��^�m��A?�q��&jR@"R��k�a��8��
)l�Adm��`n2�ao��2�A��"X�S���ap��>נ�l�$C�g���qR�`r�>Da��,�g�Ams�
)	�����n�8�M���	��'tR)���s���au���@�iS�>DF`�+v-uwR)*�
) ����~=�BvJŦO��x��&��zW�{�f�ە��Ny��b
�$zR
}I�7�q@?�W�:{�"g��8*��|�Q�c3�o8)��(@����fI4�|�@d��'3b�OaT~}�&D��'~R�d�"g{`�b@U~���b����Ad��(`�`R�ـ��a��ār�& �x�R�&wf���I��am����B�/�ux���,�2X��2�a3!���B۴:�o��'�R�&��a|`��v���`�&�r{�I2���Ad�`v>��8�����Y��R���_"D��|%I����'7�Cva�oD��|�Km��~��(����R��M 5n�R�'	�i>�c�۽�éj�gV/<A)�2j��2�b3g�*a
�)��Ad.�8�^Q)�_"i��>� ����&D�2�bd��;�k�@d��d�&DI�C_��a&��&�o�v��Rf~A���o��0���J��a��j��Km`{R��j�J��IP��՘)� kj�R$u��z�!���}_�G�)@��Km��C���b@�?� �&�2�&��a��a`�&v#�֞)	ODv��$��hI}~�AXk�Cd�a�Q��Bd��%@DvI`��`� �a�b.�k,�r)��Bd�@}�a��J�-n'b)<@�a���`[�q�k,@�c}�,D�RI9�_"���li@{���a� �b7�Cv5��a*���H�<��Av9�&B�h�D�oe���ieEdd)�Mw@�+"0�& cC"�R)DC��ަrUdV��a��'o�G���oS��G�����.��@f~�2C�V1:�N��z�-|H�f*�}~O(�Mff��D�Θ`Ud�R�?�-|�`î�R�-�-|�rA�����&���эjK��F  �J`��\�\�����U�M+�Dd�-:�s�=H�f*A��f}��j���a���ְ�Cd��f*����j�Bo��M+`),*����-|�@W�rNmI�o��oe��f*a@����f8�ƒ�Dv�oe�#8Dc��'���k�@+S���|j`I���ĩ>��!�aCd@9�R}�F"F���-n�GI+I@��9!+��<�@o!�_�z�)I`3�k�Cd��f�)��Ɨ@�%�2EdC�����)%D�a��&�P`�@�av@Z��*Gdţ8|�y,3���d��h
����Fm:a�a߂�`�-D�@Om��M+!A�af�d"�@OmVb�a��|j�)A��f�r ���f��^���9�Dd�R�`7LoT(8:q`{�|��^�)] Ev���i�í,���`C��R`�8�2EdŒ{~�` �9D��fO�S���&�QOm9A���@�:��`_�Am�R�a�R�`�r�`�r o@�xy��{� ����G�W�{~i�\m��~�P��}��`*��8ͲE"I�A��f����|@Ձ��)��^�ڥg��r:D��f��8�`���9D�`w
*���R�v�R"D��H9D���{g��:��f*��8��,7��,
���g�f	���A���8�f��^��r�a�i8!A�aY�Am������^�M`�h1��D�+���)*��8��-S�%�Q���i~͡ �R��
`:D���`�Hp�ڒi~*��8�i{��r�a��oS�i~e�Bm`{��`sj�Ҁb�`�ca:D��i�w`��X�f�"n�RFv�e�th4m�AMmV�ax|&pjM��$��£��Xv��!�W� ��E" X���>���Mߒ	}I�q�A�8��d��f��}��Hd�)%��hq`C9��i]��)��;|�`��� ��}��ĮqA-n��`@|,�RFd��}
a�9��G��>@�k��}2D�%X�`Gv]�
 �r)��a&:�%a��c�G"�R�$1l::�PZ���}U/o{@���`��9���q��DN��dC�>��i��}��i�@ DVA[���|��8&`Gd�i��R�D�}*�Ao%�)�E�h�rGv� 4Ef�$|��Hd��\m�A-|R��R�D�C�� X�;�����>�Rf*�BmC�G"��Cm�r�i�R DS����:D��H�� oK�ʴW��RMm�R9�2 �!<ny�H�3a1D�) C�|�i�q>VA�DM��xZd��<D����wb�8��	o�`{��@ D��.D���`sGdW�$:����H��K�(Ԁf���c���8O���Hv��҇ye��b3@���Zd�)Em�[v��	}|�ׁ�ʣ:���;|�Уs��)I�)'y࣬F�m8� %n�f=n.brjJ��x��a�N�m8	3�b
�	}.��8��$��*�Cf�).�2q� �fS DXD�ux�M��g�e�
}e�-�)S�'�ii��$����:�W�l8��*S}}�D9J �D!A�b�B�b!��X�Zd/ʢx�Hd�	oS�>f��z�.D|�] �{�HdF�f7}�	}(  �e�?���� �ZdΠHdS }h��z{@����&"f����5E�bS�{�g��i���z
 �dǪ�1�^��g>|b[�����O!4�<I"P �w��b�w�S�>&`�K`�w�a�O��A RH�x�!& 
o���N��z>�&D!�=Dj�N:���|��"S�>Rf�<�m8�N]d
A�d#�)�b�`,�9�v@��m8C�f<�$���a�)( �٩)��9�N�A/a�����ݖ�	}& �� P-u$s %ӻj&f'� (
}�F-g)S-g�3h��c| �&@-gf�\�*S <�d�@]v��a+�Jd��~)*
�J%+&�V��cT#�+WKqj�DMd|�L�f"H�,sUmah�+J �D��Kv�|�d@Fm-�{~.S �`3@/s}��8E�9��b:� 0��'n �1��`�av� ��2��`q�) �J �S�?D|��73�'D��h4�}� �$�@��5�'D&�d6Sxxz��$3�'D�d���`D�&`bn��b7��x� �D-�F��`VAu>8���A��9�ݲ��h��he��z:�8�:ӂb������5 #D �D�o�jwBu>g�aE[��@��;_d]@ !�FMd<��`� ��=��h�h�`U`� vʼnk���{�L"e�΁�x[����h%��`%�@rV!t>$E	2>S�a?��	��n f@s��y���b�5� ^��{~
I9�(Q�A� �C�:�U�������k�fB��
{�-!C�?DD� !��||��E
��z��D�2TMv��a���'��2m��<��3�)|����%MdT㫣��F�$G�aH�Im�MFa3�;uC`��O$n|����D��F� I�%D�-FJ�Md5�a*�AdJ�E�I`v�%a��$��Ѐ��I@z΁)|��bG�Av�dKS�&zB"L� ���$M�@v��& ��N�b-��&C��u��[O�AdP3
 ) ��Q��D]�������R��D�X�SsNd|��	KI�zo��Du�uRT�}UI9.'�:�BdVS�`i����F����" W�bI�Bdf���ay�%A)�pZm��{�XS�V�@�W7�Xmq!f���]�
fTH	 Y�`Z  ��R �Q��)��*|(�f��<M C"� [�ɣ\s��.��a`�z%��<�"g�@oN���@�kyF6�]sNv^S+D_���|�I@��� �A�zW��&m��x��\�b�`�h�L�`��&a��DbsNd�`����q�3G+D��=�ˣc3
 �Omd�}]�!u�DH�!�ɣ�Af%�n��aW �d�΢��|���j��xH`�z( θe�Cv7�2f g�}N N��`�b� ��hSf��|�{u|���iSOvj�Cvk�2mpF��61olS�z�gNd*��df����Tޡ m�Y�%a���}ڥ�W�1' ������n�#gW Z9�og�b�����\c��f�U�
�7��%7�Cv6%�>���`o pS�k�I$u`W�q �{���ze���<1�"�R�$��j"�-r2a@b8s��z���a�C�C�t��`W����"Gm��ˣ7@K9A�kg`�u�Cdv��`|�ɀ�}w�Cd\�8�]�,Dx3�,q��Dysg��gg�d#�&ͩCd{�~��O;��Ń�io �'�cf��bg� 7�)zs��{ӆ$31ף�f����ʙ�`2Ò��t��j;��z|� }�Cv́�ig�ˣ�8~��b��a� � ��E�C����_b�P�Q
f�j�&����
��
��-D��;n_�<���iW��D�� ( ������m��|~�#;|��>D�⃳�i�S6|��z��)|eKg!�3&B)@!~�B��|��
��b��,Dv wTF�[mU� B �k��[m�_bpEd"��z�@����9�Fd�Im�����h( ��y�	f��1'7@9o�L���;|�@+� 3��W�Om�`
 �z3����,D��|�W��{�>��2�`�:�v~%@
f����Fd �z�S)DR����z����@M��S
f@�h��*|�@9�S
fgl����fC�XM����@m���z:a�_� 
b�b�3������E�z&��3��g!Gd���hf`�{���f�
�Ji�♳�hw�i!�S�>w�i�}@ ���{���&�3Gd�SOmy �蚡q��Fd�᳷X Ud�`��
�D��;n�3Gd���be��z���h��){��'3��,qv~�h�3��hR��ݡS�,�[�m��z�3Udqa�.��x�өx�»x�����$�өx�Ӎ$��b)f&���1�}��z���z�R������P��<@��o I�I`��f�E"'g{@���������O�!��]��$�� \`
2��|�%�h�� ���d%��i���!}��hS�/D%�Wk-!^m)�ԣVs�b��F��s�����z��/D*Ud�S�>���>n�xI��׮������z��Fa�@�����&�3�j ���ϣ��$��'��W] >n+��ii��:�2W@ BC�,Vax8f?�`�i��!gP"�Oz	[��S�b\?�W�ٰ�F�ϣ ^m���hf?���'���z%	)za�n��|�0�x��dy���� ���� T@�,y	)��Hd��c; �$/!f?�AI"?
?�S�ii�]m��(gV�hzAMq�B� ���8�. ���>:�Amzi�f��Hd�ӻxGNMm��ϣ�� ��f�yȽ
f�@��{�1�LMm��jK�W�Hdf��i����⣬d oX�'�F-|�A?�T�Iv7@)3
"�z4'cI����	}��?D�S4'|�ш�SMm�3��%X�"�_9y	)|�!�A��f�@6�N`Ðؤ � {�� {��sUdl� P`��l�{�Vv�3X� @�&�3\m��,D��"u;e�Dm�?�&\m5�j�lC��afk�Sf�S$u��Hd�@2�3 ́	}�@�hC�Ԯ�i���Ӌb�S�zf?�@�/�tͳJd��>o��f��+%@-|�O��Vd\?����`J 2DI}|�j���F@�9�Vvd(u<@��;T�S Va5'�a۴�@Ę]`1ѓ�$}�?D;iy�`�=na����VvO�eR�Y�C�	} Vv6Y":a�A��f���`��==BM-w��i|߁m�d�G���KfmA�>ӓ�$I�� ��?F���]��Sʷ�%��a�=|����{�D9ճJd<��k��-Dz!��f?�o }�Vv�S �>���ד{~�� qa�hr:��ӿ>M������`��>�!Wdܓ{~椢i��>�	��$W��� �Vv �b�Sgf@����?ߓKd\?��Hָ/R�"`�zg��ya�8��S}�ӭ>]�<DF �d�Jd?��
UE�r������A�{e�� I������a͡�k`{�҅�k]@B"�KvF �d_��f?����`�ţ0�
��2S@{�I�� �c^m��i~��tI������a��`�{~n
[�CQҧ̪�g��D��hw@g�oe$@�&�������h��S;� �%s�g��Dqa�zcC�xI�33��+qi*��Zd�A�]@B"a�{�`3�I@��i�>D��h3��+^
}H�<��i!��`R��x|р'b)qa�h,��j��`�ŭ>�A��]�)Ы��ӿ>.�Bv�A6�����&��z��F9���`遟���&gCa}$@-uR`�d�3f�
o���`��`�bef�����i!�8'ȱ �@=AB[����"�c�
}��\m�'�h[��+�k~*� T�Yd��b@�$�dN"��'d�oA�{)�+�[vS�r��a	��S��g��{g�?D��"q�M"g� e@��@�yf@�{�G9����{'Nv������&�3\db��L���E@�J����Dv�3Nd��Yda �`�b'N}��?DJ����|�|@�	\���N@}��M"�S}z��h��r����aJ��#DD��?D�>DU�n��"��&� 	}�3\d����g���%�D�LJ�H�<_��i�S��4Nd:���|ŁTo0A}~�Zvdw�&$��&!gca ] �z�D��:�d�H}����@�[d4DDI� � �`o�lZm; �&�"!D_F@���%4DDq!�j��Hm=gI� (@�o��j%a���j�&i�dm~J �����`�K	2	�9'O�դym~W`Zm
4�&���d۠�e"Nd�_97�G9�[d���i
�&T)!h�[v�'~~�"!D��,�Od4Nd�ZdD	2�a<�Zd_�[v�
�jʠ�`�=gĠ\m4���kN�|�4!Dh�[v
@ieUI��tߣ��`ݠ:'A^"|@�BJ�m*q!�j�H)!*a�P@&�A���ZdT)!��
S�zk!`����Ϥ�`� Xdj���9�jF�b_�i`X��8�Im?g��N@�>��|�T���t8D�Đ��F��|����i&���0�z�:'I��I� 5��-T�`��z��é:%�.��=�4 $��� T��t<'��&��f�m/g =g�"�j!4�&"4�Ի��x#��a��`&�8�
 �8laٞ�b^d.��a$T	2%t<'< �<�Jc�a��N �v:� �c�i�D�+����Jc&�"|K�̴P ��'T�>_�f#I���( ���+�Į��z��zM@9D)ԥ��c�A��zd�y� ]@9Dl\d*�	f]�}]�\+�	f���/��}�࣬!�=�7�i,T='-��`VA_�
��'��h�@��&�^m蠟��E�n��"��"n���D.T]dM�|��'ܸ/T�>�C"D�@K+|@�H��x�@꣮!}A�}0ԛy1��D&���2f<����@]d3ԍ$���&*A��I`�b�ۯA��f` {44�b�L�_�Fࣺ����{�)�b
A`*�@�(0�o8f����+€f%$n`o��'�L]dO!�k:�hi[�9��D�5��&���6�hza�h�)��aS`wTV�c�%o�a���ʚ�� 7T9D�$'�J!K��f#8T�,9��b�����AieGDd� ���z:2;�)<tDDg�Zm�[�@9D��{�@�%=�:|o�i�A�,{!:D>Mm�D`*N`� 9k�`RŦ>� z)�A����Y�|@Ӏ�D6��-^d�D��^"c��Do�meR �d�Іĕ@J��&e�$D?4�z�%�z�Ņ��ޣ@ԅLA�fHi#:��l
����C-g�D�˩x�͍$B�)]��CtA9D�E�V"%|E��dFT@90�|�G�d��I��%_d�ڣ�@��HT-gf X�f X�{�L+��*"u�I��.JT
f���F-uKTDd�S�OT��h�^m�A6nF �du�Ivv`8� {�J��&L_dMT-uf`�{wb�k>�f����7�b���vN�D����O�)P`8�k�iqۯPӻ¬%D|@ؒw� �fQT ��ZmQ��/�(�D]@}���&��AmC�8�RT�h�����bSԏ�,d��ݠheT�)�!�*��ڢ����f�2U�b�̲�>��V�BmF@�h.bNm�D-uW_d�A��XT����<D:���Y��&|р �`Z4-DB#\k�H�z1`J�$�b~*�ףg�)�a�D[t 1 �D\4Ud]�`~�&��d{�&���a�.R@	)�#�cA��y�,�!)��AmI��/A���`7�^�f�dh\�`_��&����� �`�)� �EaT�y��[mb4Udc�
 ^D�zd4UdX�)Bۯe�`~��f�C�7d��f�g4f�!5uA��|рh�a~7�=D����fĹ�b��xiԭ,�<u�j�i�F�d�`��o`$�D�duJ��e�j4Ud�D2|`�	<�JcJ!Udo@{���~kԭ>I �Dq��ilԭ>&��A��b~@�� ۴����#'�D;v�f���Ȳje��F�´mTVdnt\m4f&`�>ȇ����C�KҴs�Bm���h:��dR������f��f+f|�	4�>@[AA��{o$up4�&��
}<�=�RM�$�b��]@D*(�&R�'qô	�frf���#'e 7�fI@���M��ܻ Ud�y}s�"gt�fuT�xI@��oy ���=�v4�&w45uK�k�@�b�$u`)x�����(|`)v�>D�\�lMvN��z�Ǣ*A���`y�izԭ>b`�+��(d�å <T���d{)DX@I"
!�d|tEm��.I��d�B?I�����.�$�d4�	}�Mv%_m���z})D"�<"�]9q��,�`�%�Md�%۴C!>|I�>�x�%۴@����<~��+_�<z�$g�"Z��
) ��Uf��&f�	}�4�b�`Em��z��,�)D�TD�T�b`�n�hZ��2�|�tEm{� � ���TD �-|��>D��?D���z�$R���nH$gd�/u��#g�j���2�t��I�0�Ca'AiFm]@"��d��;nN`�f�)D<"X"G�m#� ��W`{��T$umeAD��2%d~P���d~|�ʒ��Xv� \m��|!�i���(��@d(��&����vKখw"�|��4��nAI"�ĐH`Emv�S -!:A�Vbne� �""��$�tfN ��߂�z�tEm;f��tf��ݾ��,
�
fy�8g('�|%��z� }�PDS3QƔ� nR����"O���[�&
�x��{�T�z�`���M��x�w�"�_9}�M"JP+D�d�h����g���5� �a�hN����d�
!�d��_�j�����Yd�e*q@�������+z�?�������B����d(��+�t	}�T��K��	��fI �՘��h_`#A`�z���o�9��Ҝdy�8g� �N`�f�s�����O$A�ze"DW��|'��>���c!�k��bU�Yve"DO�f8zA$gzA�{�tNd���>`�he��h|�ԁT�Yd:�e�qa�z|���T۽�`��Ŧ[W@�?S@{�k�"|��=�B�'d`����z��_]������z��h�t�YW@��;���I��	���@f�Y"��z��a��|�a��)����be;�'�4fGդ� �}{ ��O��vOAd2��z|�è	 a�zq�>D	Ţ�@�>�Tf�4�����z����6!�z]-n�I�n�ݙ��5I� ��&ш�ga@۽O���z�e*J�fz!�z"��b��f��!n�����"| ��;@I9�-n����&`�n
!�da`X�V!�|Wh��A�)I�d@�>���h#�mK���!n��}"@i*&�I.�me������X��4#D�T���&g�4�z����� w"�|�TI9�!g�N�C�X@�,��#n��I�JI9ND��4Ʈ!fb&K�Im�@�>e�7�P���aCe�
)� ڴ�Tɣ)�)�Ŏd��i~&�y���i~�����T$n�@3�<P$n���a��Zd��[d���&��f��	f�`㯸�?DF �d���.ʠ,D����&`�+I�Im��G���?DS@{�D� |�`7���}�ԋbN��;@I9��}��Imgo�k�.\dN �d�b	}7 z*��}��||'\v���b'��+�g]@y*J!��� n��.�"��m���<�z~�TI9�
}C����h~�SMdHn4Z�TI+�-���&�dĴ#n֋58�]v&�����aƔ
�Ǵh~|@�a�K�j<2݋� n&����E	2�]vt���f�Q�ȴ#n���ɴ#n� ����#|�+�N��4\dP@)!˔)u*���|�
�m1�T�h�ҋb��i~C��$�A��dwg�;u��.D�Z��}��i& 
�Q�I-��6��b'���i*�$���ҜdCA�d[�t����:��d���Ad��Ad�e�@7�J�m���iє}*��`<�Av� |� 2d@ � )�T�d"i*JE���bq`�i�ۯ�Կ>�T�&Ք n�>֔�xW{�
�,��/D"��<��+g�T"D_F�&��%D��Ad"�zqi8߈�d �"GԜdP�(!aa��o-�Ւ�ԣr��+`7���$D�ۯ�=gP��w�
i8�T�jH@	 0 �<�]vh�`m�~q��i	@���e�AC)�0�&�T�h]dO��v1��i�GXEC)<�JcC�%�4Ed&f�n�i~��`�&D�!؁�@�h%��pD�L�T"D!��z&@¢I��d��%D��/D�T���T�$�''n+!�!nN`G��"nA�<u)��o�T�b�a	o� |9� |�| ׁ�m#D{@������A�&]�.�����)uN@Mm2�Ad�&|���`J�o!a	f���{g@���'Df��%�)�s^d
@�&�4Z���h�-g�Cd�H�b��7�"|
 �8�m*c_�Am~��o
`1D�t#D��m8a�k~�`;|`��z�T"DX�#|��&{�m8� ��1��xT@Mm��
}�jN@���}��iVA[�ZL�a���i��fP��e�4�-�T@9(�<���?�#�>�E�b���ܜ���@����k~����"`#D��Ǭ)�|X�.!`���)���T@9�)��M9U����}��&���t���+Nm�+!�C�j��*R ��Vd�$���HdA��$�(;�#n
�ʣ�t#D�TM�)��z��Cd́�h;��dd����z�m~�`~��z�&�_$�C}���(|�S�C�b�M9CaDD�T��U�)B`�i�8���xf�W�$:�CdM���C���U�i	�o
U�zi&%|$n��#|L�:gP���  � %|$nC��'
}w"�|_ )V��-Om�u#<�Jc53D��:g�(nW��|�m8���Uve��f>@���@�d5�-.W�fa�dfUg|��)|��x&{��@Dv7�(fZ�'f�U�hba�@#g�a�u�z� �& ۴n;g� �I� @�aOA�<Sׂ(�&��oU�`z�j�0��j"��$C�Am<��d�(n����){!�i��):��^��&�M@&n���8U)DJ�F�
�b~��T;d���!3D�oگ���b�)|R���U�`�>�Ӣ�f���`�>�#�N��x�Am| ݁�#�i�"�禁�� 5�&� %|!U���)"5%|&��e �- �&�� & �#U�zPT&ac��I`�R�x/��|& ��]�[mӾ� ۴��h����Xv�z��fC�&����Õd$5֢���"g,�a~W�Id%�;g&�) c5nB�-<]�H gw'�
 j��R�@Gԕd��&(�hM�j���`)�-3]��<f�}W��dVA_�! �o*U�>��+�hI��d,՟$�\-�c~|%���Lf`��i�z�F]m��d]`5|.U�,�s&�/5�z0Ĵ�G�;%!�&�Ɵ$0`���`"!A&|D��[P@�15�&2�Cmz!^m3$giE�,A�ܥc~7Q_� =/!�54��`�c~e���Ȧ�ڥ��fP�+Dah-$��xg �5��b6՛k�A�,7���8��z9��h:5�&�f�a$r;զd!��+�$g†X�$u0�'���"&�&��o��㑉@veA�D9<��||@#���)�M��w"|xLłbq@?�Va�8g�&�6ne��'Ja5Z���f�� ۴=U�c��(n<�}N`x�>U�c7	)?$gq@?�f����~@��z��C��+nO�8�d �+!�fo��+|�&�д��;�Aթj3�=�(n��s߆MdB��`�Å
�C���Cu�b�ze��$*���@����k4E�>Du�bEU-n��|��}��kF�&�MdR@R9��)nI@��� �jG�}7`e~Hu�bI�o0�9؅�o�#�	E0��$���-D:Md$gq@��%�~J5�&��	o����mPDm=/)�g>gkDr8���V!�|]):�`ȯ��|Va�kI`%�K�}�b#����V/e�-|��M"V� z0%�,DL�}I`��MU DN�)|3�	}��fPOU�z&�_P5.|��ʕQ�	}��{eR�D9�4GSU DF�����{�X�b� .n&`��]��TU@9�d�|��� c8DU�j�ʔC�*.nY��A2.n:��`R�FN[�o`�bVU�z�A�'h@)V�[D�=WU�z�Fm��&2�`�J-n@�/������X��>:�}����A��Y��xI�Q� k��|	`�ڋ	o�F
}�%۴Va�|��2ZU}eq�&R�9�f��x�o[u51 �Y�������*�w?�̙�\�*|*�w?-�_:�����.D �]�Id^U D�f��Ȩ�d��a_5.n`�G9e �b���ka�}b5.|
@�z(&cտ>@��/!�_�#��7�a/��|dU}e#feU+D;a8Df�/D���jg5�b!!�~e"Um:�}hU)@�%i��x&��`�B)H�aeju*D�dj��F��w}n����LK�#:D1����o�@)����:��d(��%����Nok�*n���d�}:�e�T�/Ds�4��@"��Am�+|���&�֣�0aT�}Q�@m�/:��.D���G9l5�`R}T}��,DW��`��2����@�gK9D���x� �`������jm�/D���<nU�xoU)p�"|q�)}r�o�
�`��)su)A�^%NA�xt5�Qi'��u-|�#���B'�v�).���@�x�
o��o �&��bw-|{!:D@�
]`!Dx-|&��yUMd}B�%��=�bwz��`�)"`Wm1i�&u-|��x{@I9��p�P�w_q"�|��ޫ��/�)m���:�H���be>
�f{51Do���Jj!|�)� Ad��o��;|�Li8sp�	U��|�9R�(,D}��:� �~�;|:Ai8����;|Ā-|&�Ǵv�:m} :D>21D�
}g�	�{M��	fg�	�L�i~��,D��"|K�)uP@�5���&
@	i;��i�)\d���`��,D���.%���A$|q"�|�uu~�)��`��)u�”Vq�h~�51D�df�u�`ebu~�#1D�-nd��b��F�B��|A{%A�w�Ui8%A����� \v���b:��e�&��h~�51D�` ���>��h���{`��a�|%%�Z��c� ~��a�$OI} ��B�-�51D
a�M�Ui8:A�u.n��x�mC2D5@$|�	�|��&f�&5@$n*���OdebWm%`.n���ݑ�-D�#�iM�x�U�`;�Gd�A�h��b�U�`0�$7`}���c
�x&��I`%|���|�͒���`��i~f���8g���z���&�v~C!"|�G�w��cA�<�[vc�|�UDmc�.DM@�ݢ@o�%۴��)8�.硪
��.�&%�̘u3D��x
��,�`.n�@�a�u.n%����-�UD�5�&�Uf��zb}�F�`| ݁�۽}�`~���h��,%��6F !W��c��ݪ�j�U�<�/�J�Ū8�j.|bB�-��Jm(��+y��y���`Em���cyeo�dx8��Km�A�?�ux8��Xm|@�R� S�ڴ��)���&��8%�$]Fm�dx8���$D�Fmt��OVa�~� ����|����'$��&�I�`�@�j�U/|c�|��h����eۯb'�Ā-| �)�5W?��F9*k����n�����Zm���+��zx(u����&��|��Km�A�`|Ò]@fe�~qa}fH���b��}_�f#"��k[�k��b���������h�f��)�Ղyn��%٢}�`�c���s��b��&O�[�
���K)/��)��&�%X�?:�8�7��aV/�u�~Va�|� >n�u1D3 ��P�$�)�����g�fO����zS�z1�[m>�n�˴q�-%�&D<�[m��|�|�ځA )��zC@$���`�n�Ug!څF9�۹��[mI?�d@�c]�g#�S�-e����&�U�>ߦxȣD�-�[m�~�@��h)�F+��X-C��w���<�bae
)R �b�j*#!��
�F��ng�F+��>D7�`�u)��k7�k�@{�B��&	2D*����2DR�Zm�`���5\m���|�Ղy!�K)/f@�~1@��]�d*s�+%2D|Ò��k����2D�`ie�S`8a�' � g>�nǕ�h{fUT;�#�:���'=���Y�Om�!o��m8e�[m�]m�U�b��'D���~c#�`�.>�bv@6��@��&IMɱ�	˕�嘠|*g@��b�:g��h�U�z����Om��M9��:g�b
)��Hm#i#�թx��$��M9)`�%�թx=�tl��h9�y��Hm��M9���|�"3D�]m�TI+��&��h�U�hmOm�UI+�5Nm��Im���hzKVB�/�	������y:J�3`���5\m] !�uCm�@����k:!��ߵ�&@��<`�Ƙ�`����h�Z�q�{~�D���Ad��1���P@H�1{�a�z~�U)��-顰*�$ug���)|��z�58D��)n�)>@}7���;�Tܺ��-M@m�g�i�:gVa�|
�"]m�5bf?�7@�&�5��]m� 8D�`ɱ��}~I�I��]m{��n1]m߅}�+�z3�f8�]m7��&�5�W�U�hU2�uI�U�z`j\?]��z�{~�U)� �c�58:��n�8��5yq9ұ8����{w"�%�C����zP��C!"|�U�bwB�8�<p|~����o�B]mW�c �(���.W��?��~�U�x�Ն?|��H�|��i`hd�U}~A@��W�|��S�
B�--�8�?ʦ� 8:]� ��=B�x�u5nVa�|�a���J�&� ]�8��E�5)e`�8����H�9n� �7�Cv�u^m˥ɣ���`
�*f$&�̭>�u|~��_9�U�zP@�8N�_9�58DR@�z&�ݴv|~�6|��|(�6~~��'d��6�&�C6|ՃC����b���ށA�$"@`#� ����j*a�5yDvM�
C� D)ݣ:����`0�8��ov�h	�_9B�c>Dd
A�~��|
�Zd��}*$u
}{�o
�n���j��`c�e��ᆚ1��â(�n]���$DD%a����ˣ� D`�-651�)vCm@�0h�`Y�R@�ݐ`�z�jK4��h��|�|��	�me6��A��D�@�xJ�ã�a��1@/��Km%a��]�D"]`�{@:vwb�k��dA"�8���&��zĀ-|f�|u؞�)֢j�_9���X��:|;��"��Fm�)������e��J-|&��:�K�����?@;Dd@-|�@���_9]@;D�޸U)G;DF�ɣV!�8�d棓AKd.��n�%7�,��O��|V-|�J&K@д6�&�@�6~~.$�ݛb@z@,I�j	�� =WM9V`#N0�-�M9VB};�ʕ�@�	�}|��8V"D�_� ��ֱ*�} ��k!�-D���y:���$`\d"V@9#�M9��<�H��<��)$�
�W��d%V D&�|��Է�-D��즒�'V"D�`	{(�M9A@��)�j�Co�B@+V�b*V D+�be,��b�x-�@mʠ8ش!�l��?�E;D�!��>�|o�1J�5Z� ���beaCo��*.v)݀beAJ���*/�<D0V D���h1V;DL�8|��-Du��b��82�@m<b8D_�*��z!A�{I��|! �J3V�c4�<D�}o�<�d��'"��<C`"j5�z��|��I+ROmT��b�;|�d!Dg���6v)5��&g�)g}`)*�"|7V}g �"��<o� 
�M�/D"�$������`�b�2A�c[�j8��b9��j7@Fd�Y�/V D���|:v8DjD�`�-g�'<n1� �lC�c6j)�bj�›k���`vc7��`;"D<��aW@��:!�]� :D-
)|��n`}�@{�3)U%�/�#�k�8���H=V9D=��a>���?VVm@v>|AV9D:�����`�
�>��l�|@��*=|kQ�/G=nB)�@{u�*)�ᦽ�~��-CV�cDv�,:aCm�O�,�OmC���E��b{=|A�aa�|�Fv��	fO�4��զ,	��3G��=�&W*��|G��&OH��@�>N@4@(��o�A��6��>H�k*��|����@{�A�z%I��b|��	���|qЦ,�UVm���|�P�cJ6)��������@{�K�����|��i&��.L��|!�S \\��Am���`M�}H��/��-&����hzɻxՀm8��;nNֻxjm~�L-gs�i!���,O)& ��] )`�$xk>|���߂}��$n�a>|i���a��€����i(��j��Id�
)�|P�y0�>7`>nI?�I�Id@��]@�>Q�)g��|��ǴR�b0E�>�༓w[�;�L>�br�
�ɣS��,0�b�TZ�n�8�¦,TV2D����f��W�-N@�zn@�z1�BU��&V֦>%��-� ����;|< W�8حҊ����X��>����@$|3a<nY6X9Z�;|��zM��9�k���>��W )C����)!aw~��b��f#�����[�bq��i\��$"@-gI�3͜@�]6X9� 'R@�L��@v��b �8ج�f#w��J�RDm�Ae^V-g_�8�2�@vd@����ڤɣ�j@a߂}@y� z1��F9`�}a��$��'bV	)c�@v�X�w�=�@r�q�@d��oC����i�@ 3�Jd��#d�oi��>Too� e6�&��-e�b��~z���1D2D~�@d��-!g��P��� �bfvEm!x*v��g6�-�A$|����b�`)u����O�h�	oi��j
��x�@��S�j�Ad�!\di@{�k�o�ˮB��C�l�<DY�=D{ �&m�	}�	on��ji�!'o}7��$U��|э��`<|pV}�`p�0��$�AoI��3|��$>|qV�-�@v��@vr�oi;|J�) ��::�fb�og���A�)���ނ�|���)@_�<D����B��|��@v�Ï-��'DHa�."�<D��)`þs�)To���?W�!'<�A�^!>|t}u� �@���Ymd ��<@	;v6�b��p}]`)wV	)xvEmQ�Bd<� y��b���_ �-?-u�ay�%@�& ozv%|��3�{V�b�`%nb����Âk�|V�+렔N֠��`	iM � W��|SMdq o}�	}�a�
B��|ߢ��w@��f-u߃�'~��j��`�v�b�V?�����Zm�}�c���)�V�i�b��/�Kd��R+Рt���Av�Fm<�AЕ�>D�6>|�։b��9�_���?D��iw�V�&�_�%�Cd�_"̈́-|;`��{�-n�J��:AeڊVCa�6>|��M"�b�?�V�&��GRs���Mm�љbq)D	���;uAz��H��""N���.�VDv�iwn�{
�Zm��m8(�m8q�$|.«j�`#�wB[�Р�8
�Zm��&���j��Cd��?y��d ��o�~@��� ��…�+�`W2�� �c{�X@DvI��"��� ���y3��c� !C� I�����c��� f�|4Gm;@�H�'a���/�-�Vj�)DK&��Hm�6Gm�p�����`��=��D"�v)���b� i⠊b�)|��$Ai1�v)w�ɣK@�oVa�f� X"��$:�I9�D)1��hI���|���b��Nm0�@v�&� Az�\�i5dEv��iwg��ט��-��Jm
�΀ u���b�]mV����เ�ֵ~_�oz���A
}�%�.o���bW��~I��U��J����� ��!gN u0��ImG�)|�!�9	 !� �a#"g��k�A����֭��Av\�|�g��{a*D��@d^Fv�V
o�lc��t��o@)Dq�]9�6\mH�$���,�A�S�\P��D���z
$װ�6)�����`Ed~jNd�V)�~��z�v���| �iC�)
�AdM��hC�o���b��$��o���*�=���o�|���k��n��࠺I�V
}�����2�Hm�Fdg&'�b�b$���B��'�\dJ����Bd(
f����f��'�e^m��լFd@�h��&��Xm�@H|��	f�&�o�e���&>��?of���a���� �V)D �-��XmJ�L%|��H1�Jm�Ť����y���	_c�c�t�f�b���F@�,y&��K�ٸ,�Im���	��Im5��Y)��#i�6Gd���)@ɰ&{���}tF"���a@�`��zR�	`�6Gd�@�bDFme���g@�� ��KmW��b����V�A�m^m��|�6Gd�Km@��Fv@��;Uj���Cv��Rɺ��&D�b�$|�Ʋ�V�#u"OFa&Y�!�<���c���jTT�8��G"�V�`��8�6�b��)�֍$��8g����v~~m!� J�L%�T�$�cb<��Ⱦ�Ym:a�m�����$u�ĩxd�k� D�$g��Jm;�f��}!�<�%�����1�&��#u�8�voX D�Dd"��i��8a �b���#gs��Y��0��CvW`X�� D��`cc$g�V�>�Hv�.%��@۽G�Hv�,D�Ar8Lme<ϼ~�B�b8	ٷVa�f��S�F��Ɩ	},�8�6�hq}D�.D%a����|�@�b%�H��ǥ�֋b����O�8�$u��Km!౴�`e�W��Cv� ����x@��f@�a3�#g'΅jR�_9� �z�|�m1<�=����`<`��Y�#u;�tR��_9�$u��m1<�H��.D�COm��fW�)YG����A`�~�$g�)�
�k8�}0@)��)y��ζ�z��?X	 D�`
)�֧�6�&�� MF����+�Vd~�րbCݕ;��)���h�:4��y�|w�Fh��-D@�+��jo�ݙg�`
�;nW��`�B"DuSFd��M+e��<�)]��<I���U�h�b�&�@"D|��7d o�րb!\m��|�e��v�b́f8�&NmV[�3�&�� ��	���'a�n�b�;||@�+��)]@&u� � �.D%�̆�4b��&��� �Y{ ����&�(Ed>��|,�Jd�v�b�V�z���YR`�`W`�?]FX�BX�w"�%X@)!�V-|��;|W%q�OmR�`
o�VFd
a�i� �OVK"߶)<�J�V
oߡJd���n��kJ�U����`UdJa��w�ԣ	��0t.�Rq��$L�&�*'A`�~��� N� �No%�'A�hv�[�ae��`O��h|�;|�8R�ٕ�[m�����vUd��>{GFvf�<-/Z�#Nm�e�Y�b��B�+'�6\m�A�?1@��	�`�
}��G"��R�'oFˆ@��6Nm�Jd�֩c�V�h�h%`�b<�Jd%�5i�}l�&~�:���'�ֻc_@�{&��ݘ�f8�����
)b�
�O��`f`Cة �?�'���'��
).��h�ֿ>�@�~sB��7�
)�Ơ%�"�<q@��}�3b�vY��s�?$n%!S�!�.~�c!Ҥ`mW ��8ѣ��`��������vݣu�Hvo� �ք$�`{�
}S�{K�7��V�xcaGv�o@�0	�|���b�"��.�/ �(gf{��Kd�A�-*�� a#�{�:!���B��U��a'!q��xv ����� 	�|݀�hP`�����W�$:��(g(�մ��(u�C�6�ݕ X֯�-|�G"t��hVA[�ӡ�-�v��ĩ(l�^9���~�@�?��&7 !M�(u�ֿ,V�,M`��	��_��(g��`9DŽ|��� ��Hd[�p���^9�v�bP�n��'#��Ӣ7�?:� �i�57@�hP�q�O�d>`��7`eey�{~`�`��j�@|��?�༓ ��&f�I ���Ci8�Id ������|�C��n����*#�~`�٣B��
�e#�lJ��`w��X�m8�`�a5�ɤ�[�d@������{�6�i~W}<�9�@��q�C�7�`W ����>D�{��{��W�e���`�`\c1�HS`�1{��!K�: Zm���hw�f�|	�)
W}J@�8�6��W}�w�bA)!
�f
�k�b�vٞ)@ѸN�c7`|~D��Y�B���#������7`ee- 4m�@e���9��z�"�F��!g�-g�hW@��w�b���h��&��ŐOdCa��<�9f$ߣ	��>w�_�R+!�K�0��f��k�*uq�-�[�4�"uy�%D@���
���&��Ѡ�$�?@��C!N"R�;g��i�XdM�e#
*#N@[�\�4��A}~���~/'��|�$΁���`�C���Kv�#�xf�Xd�Kd�)�@&�#Cj��#g�)I`e|���z<�9��`{��%f��)�zW`17�'D�Kv���.`�?.B�-€�ݑ��`��$��˿%})֒�`J{�I��h���hh�hN�g�Ҥ2i� 
a�,@�-R�v07�hW�`�b;| �Wd��������Vm	�Kv�Kd��� ��z<�'D)|���(+�h ����p���%Ăk�$g/%�C8G�(!7��CaXm"�he�A�b���h��#g#7�b���k$W�<%�#g,�z�I9��|�[�be@�?SA�� ��<	�-`�{9��{&-gq�j
ˣ&C?�IMd���(eV�k"@i#��>I�>`��V!|8�+g"@i#'�f�
Md"`we(W�$)�f}�'D���f�Kv���h
��i*�Kd��h�
I9|��B�>+�Av(Md���!遱�#g�-gŅ�Yi "�Cv��|�,):q�M"<� �AZP� Umq$��H`we��UĠ1'&����N�>T@�.W�bfHf����ܐ���% �z��&�
��� ��g�龂M"��;g-�be(W�$qB��.�j�  g�>�[va;nza�>��k���w@؀/�z._�t����;u�
�b0�1'1��?< �f2��zW@��3�ԣ���h�-g4��h8�4G�C��bNd/��jk��x�Ef5��z�a8D@&g�G#䀧�=�@+:ũ(��C��)DS��b`\d���b��{lV)���  fd�M�-g]@f�@ؖWH�+%A��6W�$7w\d8�`9wNd�T)U���[�f�):w�b���;w\d"�.���z�Ek�{AVm<W����h��~�qNd@2ÏeDDX�.��W%�m8R[�=��zJad�>�)|�Y?�m8���hRH�z€�P�'�R�/@��xe@_���t\3k'g,U�z� <i�bEdA� �b/
�v����t'gB��i��>�s/��iC�b�!s89N]dM&��FU"7�
�`�AVmDw8DEWde
A�iF��b�
}a��{�a8Dd`�<'	�m����GW@9aNd��{�k��bn�-n$�ke�JOd :D~���X@Vmm��bHwNvq���A)���d���bC��j�D�0�Ym��(���xI78DK��#�`#I����@]"]� &��iy%>gJ�F"ZA)O�z�KW�zLW�h4E)A�o��ƿM7�bH�Xmg�}@;��5�A9D;�x��$�|W ���`�<N�f1OW+D!�E�"�.O!�gC�f#b"�fPW�h��f���bQ��b�@�	��
�(g��)R�Xmq@+DSWOdT�	}UW�hn��x9	 7)!e@]d]}V�	}])!W�}�M-X�keUeW��AmH��-�XmD������ f �f#pĵY7Ud@�&>�p��	2ˠ�{�Mm_�bf�Z�	o[��h\WmeS�YmF ߣ����3��5MmS`��K��_!!�a��+�'�+F`)���+� Uv]�)uC!Bmn�o^}V�-;F�8_�Ym- #�7�?)��`�	f+hF��D�z�R�ha�-Db��`�@�&�fcaR"J���|�{� ̴��fJ��ݷ�	f��� 6��cWVdǁ�m�o�x�AVvd�z��{e�`# ���W�-D]�)u�o��SB�@+����z�QDmC²��he�o}�,Df-n����R�����(< ���bW�{<� �#���c*!!������hgWVdq�+!���/i�d�e��b�l*!�
�z��}	/@�%Mmf�%|��.D�e*!hw\mN�|i��I"�-W�-:��b]@me�AVviw�-S�S�jw)�eif�)��M9�ŰY�"�hkWI"l�Ym{��,]�@mm�)uJ�b�n7�h,FI"���+o)Ja���<�
�depWI"z��%U�q��bP�����br׍$�Bi#�y%:���s�cѥ���j��u:P����Am���z��b� �btW
}z!�-�*�bu�
گ�ڣ]@i# ���܇A)�鿴�#����/�<u��Ivv�}���C!�zw�h~RҴ�Bx7)F@
}yWDm"��/VEDmU��,��aEmn@�%ԈF��I������k:���F£�rEm���%g`���a}&���_��/	 �R��Zi�£z�ZmR`�i�v#C��W7��	f ��{��b4�F�q��>@&|W
}d����U
}�
)}W
o~�
)>�iwq��a���w�	�b�%)����qoe'�����Y����#$d��W�<]&ĩ��"�I�je|��H�7����o|���:� �A\�Xv�Ui1��h�KI"I`En���i���`Pd�nR`'DW�&���@�cq`���I�����b���m�AmC`s�р�>�
f���>�Bm��'���恅�>DP���1_ḿ�,"�F9=s�bg�Xv?��݆W�`F࿴w�j0��%�7����Bm�wUd�&KC&�Fa��`W��%�c5g��� �W}р9'���d 2��YdI��b�GZ�@�c� ck�s�b�
fa{�7\m���j�e�b��(�xHg!��i���i�w�bH�`�B�<���T�
2y`1D��Xd(�
�>:�}m�X"A`ݑ�Xv��-!��}��)�Vd��?D�w5u�w�b�w5u��*�S��z���`��4Z�W�&W��(��4���vfH��$݂����FP�@3�n�z�8'v@��T��i�
fN��3/�.��9'|�>a.g{A�,4��0 �-�5��7�b€Yd"��$n�Q--4S:G��"-g'����Fm@�`�a5u�M�/�2D?cU��
f�`}%d5u;�<ܙ�}��h��%�`������!��%
���'"�܁�%�@�=�H��7�be��`�����i�i�K�i!�Wme��k�)wb�/��o�����?D��}�'3D��)ac�&���`SI9f��ݟ�Zd�D�.�@meР�<�k~m�-��Q-�Vd���z�# �̱ՠWmeq�-���c�7�b�`lP�&℟$�w^m��i���Im`.g��h��z���� �b�[v'"3D�@�b4��O7�*F'A���b���.C �㤷e8>@���e۴���y�WmeC@X��bg�< �I����D�$�fhC��c�N��m1�w^m{�?���c	`�Ր��$�=�N@�zIBۜ��$��)��$ ���A�h͡Adf��۩��%K �+n��cH�`��jw֣;�� @�z�����m#���!Em7��j)�����b�ף�F�fp�\m7�bt�|�#E9��
o�|��7<'aEMv�F9���$;�5aR�Xv�M�< \ЭwG��@=.O!�����z|	����f�@ �a�?���$< \�n�%g��&"�F9�AdZ���7�a��'���X���_��$|�p ����Xq]]v�m1|��� 4�|�䁯]v{�F9d@�ܕ@�q��>N�����Im�7�-��/!��Xv�7\v
��]�,0��2�W��|1<'����7
}�.�+�׿,��Yd�	}�@���@�h�ۘ���z7��c�	=�Q*ͺ7<'€�y��f #<i�7\d�!�+ <'��Ad<]�M��.�W�h ��wX"��� ��)�a��'�:ib�c�� {x���`��h�7
)�a�l߅�9< \��"'�Yv	���i �`�C�> �V��z��`�W�y1@��f �{�C�>n��z@��Dd��Bv���F+
)O��R � W���o�.f <iǚ���F97�Cv�W�b�A�j	6n÷:g]��.��O�l�D�`�3�g�
}�Vmg��xw��+��D/�7
)&���Ʒ:uc!�y���g�8'J��@��H�)Ƿ�&�l���i����&��Ƣx1�H���`@zlw����`/ȷ�&q@M�W�>8�ɟˢ��� �� i8.��<�����f�,f`�cI�� ţ�x.���"I9���?lKMm���O���A�e�7��-�`۴�Ad�Y�Z	���a�kw�
� 
)�`{i`Xd��ݐ�b�I+�� �W�`�aweV �i�$��-*�g�8'$�͗�%Η[d��i~�`��&'�`ʤc�W�x�U�y�f���qdjY?�зZdїIm�!�g��A&��`����;u)�ʴzA�|�W`8w ���%�-i�]v��j��W}�שcA;��a�bOi\d�Ƹ�!�����2��9�Wde[��)d��fP�
�ח�`J�t�O!�$��&e��-�A����Hmٗ�`�w\dI9۷�b&���)��jHA��G��H����WFd��j]`<u�7�Q�@Dv3�$�wDDW��cC�Fm@����&��I9>�<��`�b�WMm�`�c��[d� �f���ى@�f  �'F��<��!g�թc�D5���$@���@9D�a���y1�9�F�=�a�"gPh˕��x�	�9�aX9�wd{1@ĭf�մ�W]dCA9:��x�)����a��,�-G�zA��n>U=u�W=u�w5uC�����f�@K+a@۽�rEd"@`*3G`8�z	�j	`�}{A]v
����lEd��9�a8D	�g$##`�wDD�h棤EF�.�''BY+�ʴ�w�|��Am�p5g������I@�oi`�|5D]v��z*�+v ;����b/wEd��`~�w\d�F=g�`<g���z�`<u���Y�aX+m!y�����`��Ym<`�S��b
̣�G�`)€hӁ5�<� I�f�W�oo@{����t��:�ɠP�����"g��z���ŀ ��f�ܒ������fJ`���@
o���oA]va :�F=u<� V�cq5u:A7�F"f�|���)�G=uw ��W�~*L��`dM?�;`5GV�h��`��*if��-�)Oa�l�{����1�z�bcd�@�`q�z!c87MmO�D�)��n��>!�9|w@"�� �We�����b�ՐU��`�W�,C����_��c�ܭ[��D�,�B�&q��>�c�|0@۽�]����e�q��a~�_ :D|���V!�f-�zHA������6���� �a��Y�i`�fg���שx�GF��Ī�W�c�$g�s�nZ�_"��;n�w�&�7�Y� D@�`�A�-_dm�f#@	}��k�W�&���A�)q�b��>�A�9���?࠯�)����$�C� |w�;|��	��>��zeqb�i����w>�;|���f�ωk�`ۦc�>�
��W@�c'�c~�׉k�%���z�bW 
�࣠$oV`8�k�W�%M�����z{�m8��C���&7 �>l�C@?�X�,��)	��:�� |�E���[�`8o���I"���ff@"D��+W{�	�>��&!T�?3:a�J?�ˀId�U��+��eg��v�PՀM9DP�?��`��`~�t&i`Xd��<DX-gI�q��a\m@9!��z�"n�{&`��]@]m7�?!@}���X��3���@9��<��!ߣ���F��>�`��%�,��>���	�Am@?��`���`A)R[�m�@����d�o> |w(��
�`,�bS���
`)�`�cCA-g��!�6h�a&�C�	W@?��$n��۬#�b�$|���h��&
�=D<�@J��h@"DDŽ���'��H�������{]��x�@�c;@a���gC)�"n
�Am��d��jF`�aC��q�}��v蕀���A���`~eS���a�bN�j&�aR�j�8�h�$|�� W���(@��IdK@�@�zI��Mn <i]�A;�A$|T�,X9!7�$D��:g��`f �b/H'�"�| D�hX�z� �bX{�Rt�	Q˕�$|�d�XB9V�>DL��c&��&�`�f���|���rG�Q��b�S��"���F��x�:gJ���X�yTI�ki�b�o��Am�:g�!��؆$��a~W�ۦ�ԁ�Am�AmЀ�G�)g��&J����g�X)�+�YI��"�A�'��%X)|�!�*j�M <iV_�|�"@�y��`c�'3w�e`�f�ן$d@)X)q@�����E�Md��b���Cm�A�{A�hX)�ō?'fF��I��:42R F9
@��:A��>@����:u߁f�c~�?D�@Z8��dc�cЅg� )���R`�>7 �?o��܇���R�Bm;� D2�ã)Ⰸ�:gF��+I@����`F � !�oz�:gI�|"ثx� р܅�.I{�#)$ثj%me��a�a{(�o�)ą�.<P��7�d*��?&�}"�o1��f��x'X�-(�}@�`R ��&��0�|�v�7���ib"$'o��,�m<g)�c(=I`��П$��x�	�`�)g*X�x>�C+ح>C�m1d���B�c;�`��{�$,x<g-��&���`�x�5aJ���v�\�?���{�?D�#4�.�Cm�d�>/�c~\��<0�c~K�렇m11Xf2��`&�Y�U���8j�&��UA7@Md3Xmey���v�ZE�����(�Ç�a�-�c~4X���A�S�z����hW�|GKmw� �i`�|58�%�j6��c#D�m���%���<"��-*!�k�Ad7Xme@Md����'��>v[�8X�,9X�cJ�hy ):�{e���)F � I@��T�j�fĀ%�Zd��y܅�.>A 3��{;x)�#)�@g#<Xf=�-eGۦ>x�&��
��z?8j8@��x���zޡ�c ��xR̀kVA[����D Co\@me�Y�w�8D��?�	�(��F`N	}&�Bd��&A�-����%@i*B��kC�zJ�>CFm�@�cDx�&EX};��F�&�H����GXmeC Upa+a8Em��
� �Ao����
��c�l�|Hx�b*���� �?�����Bd��>!zAI9g�F+J���x��iC���蠹��  a-gc+a��[d��?�-uaUmf��co�Dm���qoeg�F+(�y�F+��zI�k~J-u+
��ޫ¢K�{e��Ym�oei�&aI�0�R���@�akoeR���X��H'_Od6%�bL��j/A�%MXf~n���N�>O�zf��fe��>
��&?Sj]�`�_�oeaDdp��'!�c�mV~:a:�PXo� {z���h��}&����A�E��QG�.Q��j&��@�a�	}] �$%�-!���b�)|�i�/��') 	_�@�kR8��S�	}����
@�a�A@d%�@m��ce�l%ő"g�$�k��a+a���h��g8�f8Va�|��oT��i�F9"��i�b�7 �yU�@moe
AڣV؄-r���`=����d�Od\5�
��<P�S��"�zW �|] �y.��:��&�*in��bW8�zX�}y�	}:aEEi@ۦN�Y��gs�7��.Y8
�eGm�@�xZ��`*�=�҄}[��j�sg~g�c{�%a�ƭ�\�������]��x^�hb`F�_�	}I`J��=�`�c��h 
���cJ��a��h���b�@m�%aJjz��'��@m�j�o �|��5GJ���mc؄?B�m8d�)C!Ed:{���G+�v}e�z�$�A��U��*�䀍$w`ڢ��I�b"��k�K��P@��fX�j�E�<P`��W^�"�hR`�| �Aa��f@��`���訅�O�@mHI9� �|�)�I9<@�Μ�d�=!!�=�ZmϏ@m�g!ב"ugx�bO$g��<�ڤɣB��chؤ>iةcj�oq@�-i@ۦ:�b���,kX�-&�%Z5S��9�`�<�ĩcl��h瓡i�-ad�Odq�,J�L%��Am`�z�>m��?@an��Q�%����E$goX�%(��%pX$g��}��F��,��kK`�	{�"nq�}r�	��<��s�iI@��;`��g`�/s�}{F�7��zn���d�o����zw`�(��@aİy�}
{��`#���(��<bV	)��tةc�>�����c���-nt�?uةcX�#���QW�"|�Y�JA�bC���v�U2P�4��)w��-x�BmW�Jm`J?��	}܂�z,fC9���y
oz��-<@������z���??�>|m�+|{X�-|X�?�6}��-��N.~��?�Ȧ.��?���-/F$uϡ�,]`�h �8��}�X�-�`Gd��Ym�b���@��R�X^0�	o���>�(ovJ9���Ŀ����-���"|F@[�j���T@�'� �j�$|i@{��X�hA�z�Jm��k���c�a���Dm�`S�P��ֈ$|��#|���p��}R@�9J`F�T��&ޡ{��C�����ۊ�||��kq@�b��2'�a
ۋ��M��,f`�?i]m% 
)��$DT@	)lQ�=�۲�}�m[��}o�
}9	MmW,Em� �	@���d�(��&ѾbC9�tɪOE�I)��_)EmI���P �������g�H�B��i���h���z�-aB�-�Km���?$�4Sߣ�?����@�&
����-G�m8�)V�1ay@��f@}�����f��zC!z8�E�|c�)��KmT�N.��z!3!
!�|C@���y��ةj��{~W  �+�m~*�2'&�+&������V@�;�U�吘M9���R`#����C�/�@�|KM�i�T�cˠ%|&�p��%aDFّ8Em}�_9�4�&u,�-t�&!�@ۦ;��|��f�V_�m�c�8�-1��a�,Zi��P?�)�x�t�8�-�g?��/]���I�S�`
�B�1a]@&|�Hvѣ�|�xNm�ƦP�j�fN�%�m8& �n`�&�L%n`x-A3a|��7"ؖ�!�&�`}��}�&D&���L�k� Uv�)�P�c��/H`�y��W�F�,�iٞ�F+�`�9���m8�eĢ��"u{�%a��}��_��&D�GFmA�z�m)!�?�f�9A[�@�keB]m:�FҼ�h�z.�!g��"u�)�a��*�o�XOm ��ܠ�{~���{5��xg�+D�+�b�D�Ρ)�R?�X
o�I�" �=l��a������c�ܣX
ob0�&\{�d�z�B]m�XFm�%�-�i��r���h<@��-h-C�i�G9��>� �-��
)=mGm�Xz��c�$uiF+n��'D�nGm�2D H)!4E]m�@&|�p����P�Kd��G+�g8N}ެX$ac�%��)g��Yd�&D��F9��}e 6'R)W �iZ��<� ���x�,р�`�X�|���q�,���i|�����ef�6T:���:�6d��g����)uq�5'��-Va�|��c�]@)!
A�|1�&D�@�zUa�`��?��-a)��{T@}� �ݠ$��+�J��{%!��F�z�5@z�?]�8ثcXmg���9	I9���`�!�
��y ��S��`��\0��K`̀@{�<@��.B}~�C]m��`����zi0a�(|W��jN`Ę�T�j�<����3Ǧ%��(|�o������?��Hm��;ge�)g ����x��� �eX��i��'D���&�@��
�-J����V�P��!�#��O9P��
 	imᆙ�B�k��4a]�_+C��ݏE�`�!�mqb�>i)DI�A ^9���iߥ�iF��x�me��'���h��y�#f<���ح>q@�z:a�&����)���W�۶�)HI9|@ʀ�`��P�c�CA�>_)DP�$�*��?�H�>��ݰí>� g$�x���W��,+�C�|������x��>�ō?���-;��&2'���?R���MdC��if6a�@g#T�>_��&HI9���>� )���H���i�����Ά�$�H�/�j5�x�@�>*�͌�	�i��h����xo�c�Md-�aр�'M��{�x+ahmeH�j"�yC��>K�.��zeJa�_S�`e`{ ���Jm�`�bW��{�c�i�X-u��X�k��9Ь���&!J�����`�c[��?ȅ�'T�*�`��f�R��x�|�ح>��{�@ۦ���?��,��a1ȭ>n�o��J ����K��-Tr*&@��S`���}h'}!!�|"@�?J���}g��c�؉kI�
o�B�|W��|�����+��"�?b�Jm"�y&���{F'A�}�X�?��9;����gc���գ�O7��-!��� {�x��|��9'w@�gg!e�)Co^��?	���e�)ǘ�'zA"|%�}@@9��f�)S͉k�a���d���ˠ�-ׁ)1�Fݠ�9t��x��#g�P-u���n��Km��Km� Zm	���4E$u���>��[dv`��W��Kf`Xm]��c@K9� :!|`�H�`3!�{��; �5��Km�QX,�0o�8}�Ad�zaW?��KmJ�'�]�a��Ca)�X-u�|�x�jI`�9�6a��xZ�=���X-�`�$�@dP��>}�n�6aϘ�x��и#gј�?�L��*�=�-�X-͡,D�X�'J����x�&2'	��M��&G�����-��q��`�j'�,DP�Km����H)`!�-�-n��f͡,D�tUm�-D�D)<`��X�h�
6a-n��z�`�|����
��,%@�h�-|9�)uCA�h�}��9'�·����ؿ>-���-|N��?/T@+�x�i[)�-wB�|��{��	}N��?"
��v� �@�bUmX@)"Q�'7��a���fi�X?P�{۸,D�Vm�X�igš��)g�D�-ݸ�heGۦ��iP�H�)��("��<�vq��|@�-
a0aA�?��-eB�z*�=�}�&!�4@>��n���Y�`#7��(�ئ,��a��&!�#�X��b�x.| �j:ᗬ�dNm�8
)!�?��Q�M;Ѿ�W~
��wU	6apH£��=����z�8
)gI�e��>��c���h�X�'�-D�+YM5S �b� (f�`��V@�C�D��I�-��yȨ�
�R`%g"��<��[�7��iqB�ko����>���%&آI`)�W`�-��zC��υ��C���.�bv��z�����6ag��褢�>��,q�m0J�����	}�X�k{�%3>�	�b"�|-�g�{��>� �|o���sС�@�-� @zM�O9�8*�> Wm�W�kO��|^3
)��_"�bNm@�-��|����|�J�y{W{��b�@�{��{~�J�bNmC�cR�z���V�f{wt�&�¶+��F���i*i@ۦ����!|~�XDm��he�Bmi�
}��&J��:�x+aGʦ��h�&č?��z:��߸�z~�@��xx8�"մq�_��͢�`x8.�'>�&�ϸ�iF[�����d@o�.D3�z~��.D�ǁ��K�(|~��/��C�A"�|xŶ���`SaX9��h�x�b� �$ȣ�`�/�}~���c�lx8�UkJ���cP@���}~M�U$<���;Fe����7��c�|�a�(g�aX+^��c�@:��Xmg-ú�a��)�}{�]m:a�}��(g$��`���`�f0a�`x8��+%���S����&]`x8��Z�^B��|��|�d@Dm�ADmW��`f�!'V!�||@��
@�a��i 2a�!�xW��n�8�&��	o��}*�-a��a �o��z�F�
�h\@��X
}swx*R@�KU��@�x�g����W��f��#:A����ۨ`��S�6�%�b�z�v W*�bEm�����/MF�S|`�	�6a��?!��`��` dEm�@����b�|`恕 ��J���Y�c��_��(un�:||�ؒy%�i*A �N�`����E�+`aF@�:.�	}�oڨ�)`��}~�;��b~��S@9Y
odb
)	Y�1�bQ�Fm| �ւ
XUv���X"�&Y
}Y
oI@)�€�y�<DI��|�a�: �z���`
`�-���ng�)u+!�fxN7�
;��ʀ	٤%?E
}�떮�$
��A{?�A�aJ���
)<�N>��?"�}*��N>[��pS|K:`Xm� �jA��{:�y��e���eo�Ym�`L�"`�z����<
�;|N�	�:�P�N�	|��:a�`�@9H�k�<'<�+`�z�z&@�+I�cm8��Zm�6a��[m7 �c��i)z ��H��<ni�oa0a9�+g�:F~o����Nme�K��ٻc؀���`�R ݀ie���%9�>y�h9�-�|�)� ;L� DA�@9S�8R����|Y�k9�>�@�iw~��� 
A���ͣ��N>����M�{~�� �ih�Zm�#�b��&;����榗����` ��}�n�o.�?`���F0Oʣ9�>oA�� +!{�"|!+!C�� �n��A"9�-&��1�g���l��%M�>D.��z#
}���1$Y�k��+`1���h�%�bG`p�T�[�{~�N�l%�?&�Am>�`'y�{o�gc(�Am
�-y
o��5�|��)
}V�aX]m��?���W��J@�|N��f�&�|P���R�e����ݕ@4�:�����ie*��i��H��=W@{�+9�,L��`,�-&؀w�i�=D-9�- ����`.6a�+�,	��|�&g
��z/Y�-�-g���2'0��h� �>�A�{��&�=a19�-2Y�)@�3Y]m��/49�?
!�i5��n@I9�A�#Dm6Vm�ý��!٢҂�z���Oڇ�7��hn�'�ʀb�&� >D8��z9��&�3~~gAf:��%P�o�C�|�;�`%�`6<9�as!^mJ�|��+)��V =y���a�|6�&�;���:��VmW�Zd��ʵ�Bm�C��>�Im���c?�f8�"|@��3�:pF ��
@�h��?>�Ume,~~@y�-�H�69iU��a%A�~8��SByUmI �q��|W@��e��n�!	i���zv��W2'%$~~�`[Ҳ��z�`�?�@p��me`�yC9��P)��Q�:���I�o�nD9^mJ�Q-E6ae��y�E��F�ImG6a���&Hy�z}Ѣ�I9Wm6a�AJm�/uJDmz_q��%g�yP�H�0@����U��+e��I��W2'K9EmHDmi�?3'Wm��������Z!Wm�ɼ��@�`bb0aWoLY�xMY)�&�m� ��e$|1�|�.��|v)W�*a�>�bƮ�t�FN���)�oO9^mPY�h�J��1��k�|����J`�1���k�b�-Qy�>]me��J	�@�,R�J�S��$�עx@��達"T��?U`1)��(`bۢ(ȩc����P��nG'�x�� ���al�T]V�_9�G�|P�t3D�2'W�^9X�`ev`���a��Y�o�oZ9�y[9Wm��8a�@[�O蔢VA�|\9�j���]9�x^�� �yv�WW�/g�A��(�֢ %f�%X�b"�|�`8D_�XmW��i�`�?��>
aɀ��>`�)��`eg���a�kD0�kS��be�z�`X+!o��8�/,�80�8�bYY9c��h�"ƘI���f)S�F+��x;���d��ke�kB`�aL�:g��xJ����8afYY9_�x<`�m���%@Mm��h�X۴W�$!�&��<E�j��"g4�j<BMm2'�jSA۴��y�8a)���gy�bSMMmd���(amA)uhy�bt	�iy\dq"�j�󾣡
�("M�,��+j�Ym�AMm���I�b�t�bkY�b�c�I�jX9lYY+m��`��'D �nn��+*A��R�;u.b�i�S�f�aeo��-���fpY}�sCqY�|W@?�o{�r9DD9	�=� }v �Ͼ��R�&C��i�Z�s$gt�)�����:u�e�zP�O�46a�)uy�bg���T@MmvY�{@w�aex�|y�xzA?D}��+z�oe���Dvg�'DAIz�W�iW��i��{YMm�8`�|YY9P@�m~w"�|�`�>A༾��>�v'�`Тk&�{W��``��.������. Z9�!)�&��܊����顢}y�&I�-~�-��-�(�� )|��HmS �򘀢x��n�Y�&0%�b�Y	 �y�&����9�b�w�I9"��@�܅��-��֌VI+@�`��w�#����Âb��z��be%I+f`�x|@�'��.��Zm7W�i�)��k�9Z9�������.&�ᣴA��$u��ce����۴���܊Y�`T`�&v���`�y!D
�{F
W`�
��h��ce���zV!�agde���d;�[m�y�&���-B`�||��	��&�b&`��q@�&�g�sW��|��L�'�f�:|���>*A}!)|M=�R������;�"g���?]��`T	�k�U]ׁ�$�B.��&���-&��`y��hZ�bW��i��{��o��)P��f�oSA�_I9z+X�f@�z��8��d��Rg!���x�Y7�V�|ieJ+W��{�9�b���&=a<@Cd�Y�`�(a��`L��|�����F��(	��|]i+(a�Y]md`�i`�%�Y]mw��W��|W�}y��h��`R`�OA༾���'�y\m�z���b���&��&i�B?��|jh\m�@m~���c��b���
����|TF=n�Z���`���c)"a8:`�&��?3B@�x��Hz �m�!5g:��3:�i%M#'֞9�bg�v���遺@���`��i༾�9j���&���DF�k>#6̡ٛk�a摦��\�G�y̸>9'�S� ��C���&"@�yI�/��@����"u�G��l��c��)�=a�Y]m3��<h��&���i:����=D�ٛk:@{�́o<@�����(�țk��
�<@�g��cq�?��`�i8ߥ�n&��Y`�&�}�=DW��i��3H�������dKk|`�Ƅ?�z�'���YaKk�o��)S�_FuBg@���Au�FG]�Q�$"�=D�Gq�W5�o�Y$u'H�y(�)��.aG�%Mm<��}�9^m�`� t�� t��呮�}`ek��^+O%��y)����Km�|�u�J�;<�j%�@�/��W@�n}�^9��)���bg��cF`[�`�c@X�&��(.���׳�`��E����(�o9��3Ejˇ��(�Y�h�Y�z���c<_��&��V��M9J�g�P��
�;g��aQ��&A@$g��;u�f�@&�ϡ�`d >|�H�!f{dF�9��s�)g�;g�g���9oM")�`Y��`)���i>@�����&"`�`J༈II��@7����`v�������j� !>����|��}o�Hd� ��-@)Ca��f{K`ۢ(}��]�>DzA�z�2'l�B`�0��o�`�`"��<�8gD���g#��o�i��� ɣG�J!gP��@d��`eC��&���b��w��Y�h��||`��b�b[�s_��`0 �,�_9S�he�y�v¹8g��W�V�`ev�W�&��_;I51�y��� Up���h�������ە�W�N`��ę)u��|3�$�o����$���b�䩳&`�n#c�CŹ�`�۫�oe��Ϫ�]�b�AmX`�cy��j�y�&��	}X�he�@�b��<qA�c�y�z�9}����#8|���z@�n%��y�9}w"�aC�Ǐ�@�8�٩x ��(��>�{w̙�xA@}�m#��?Ff�93�Y�c�@f<;������i�n�&�YOm�+�b�&��j�c&Hi#{�8�(�����iHi#@�/q��x��+�YDm�éx鈣�W�Ek7@b1��/f�@v���h��h�٩j1@��v`���`ef��{레b�D�y�YDmg���J�����&K�%����֙}�9�bn`�i��&��M��Φ(ʃ��-C��z����M���А@��}b�X�w��!`���&�	��}b"�z��cW,�bg%٢�zJ���]�c��{�B۴�y�i�@�ݙ�%.�/ޙ�%�@oy`��a,�T�%'+��y�&��<&&�+��ie�Y
}��Q�I`
�S@��&�O��&J��>�/D�0?�>�I-����ae�Y
o��&d�-��]�|����%䨔4VAۡ
�<�5�b*��&��ld�2@�{v`�Ǣ�&�z�`c-uc#�z��;u��ɴ�ADm��z��''��%Ca<��9���&�YDm�-!@�iJ༈3��I��/�o.�}�Cv@{��-u�F
o�Z_��i�|�)f����Cv$ �i�����)	��|�Cv&��nU�ce�ًb�`������eb�{�	o��ke�Ð <gD�	}�Dv��Ӏ�'��b��&S��b� ���ce��}`���Y9�:u�Dd�bn�aw ����ke�Dd��>1�h���@���b@�z�jۯ��+;�CvC!�i�M�6Ju3�@�>��F+�F�{%��b�@�?q`���baC�i�Ey�A}J �&�0?��me%�x����'g=u �ͳ!ze�Y�iI@o���b��p%a���S|����kۮ��b
A{~�ke�����%�C��`!!€�@�%��	�W�;u�E����)�
)렸����aC�h��|q@��C�-|P�#�V�'�+g�@�{3�m1J �i��`q@������	�x�d©cI���B�ިB����B�J@^�I@�Y��٭,��}|�΀�9DDk��/�@m-@�c��z€� �{1@���	%��c�������I�ne�����kF��%�F+ތsI�@��Yfe6N�c�b	6a��T��`���hw���ŭ,��g�)"��>����YfeH�z7`j*WiZfe�ane*���2@{��{
 �{���e�b�@fe=���Amy�	�]��`�
�`oi夿��)��"u3�xӕ�v�A�Z�xK@ң��	%$�&��|��v;Afe��Bm�"u���|��
��cm0uBZ�xA�ՑFU����?�ene��Ի �{�ӣ�h ��Am�OtI�F�)]m�`�{F�i|��M&U}e���`

}f2'�F"|�H�T-u��״���kf��<)���z�-�)½�3��̈́��P�sf�d��bʦF���{��hI���q��cq�g��!��:�&Z-gD�cR�#�	�[m
Z-uJଘ��ۋ@�$��hZ�b�2�&a
�
��h��zgeM`L"K����h5�#g�[m���h� 6��Y�%�#uD)\m�?3���Od�f)���7��/����c��Hm�����@�&�(�ݣ�FmuXoe	@f��
;�f� �9W��c"�[m��Zţ�1�nd���;���%A�{��#uV�)L���&V�
�b�Hm%@I+zߣ.�����B?��r%;�h'<@� 3M )Q�
�J��I�$! �#��e�f9',��%�|�w ��g�	 zeQ�$!d ?D�#un��kz�&��k�I"o��%�Im� EmG���@I+[C��}��`�2����y�d�%�#u����Z�`	�1=B�&7�,s��z��k��	 �g#W��hڰB���a;�Hd́�hy�d���4�k�?3J�5�@�y��-] !�"δy&�&iCA)|K��dZ�cy��-��p�
���@�`��&��`���%�d�ڻc�a�n���k���0�<��u��+AdZ�`�Dm�̴�J�O�k?e�
���9ɿ, �m8���$!z�hd �o ))���$$@j�"Vk�ĭ%6�he#�`$:�&DŽ/����M`Fa%��zVA{�F���&��%��>'�heU��/��`v`��(��aC��>-��R��\�D�'��Iv�@۴"]m��kv@�):�b*ڻcr�$+�iS��&�AFmM@)|qm8�a��N�&,��`k����f��'g��`���&澴3��5-Z�&e�y�bߣ�I"�"�-�h�l&����r1�`����cbV	)e Rk`��
!ţF���{��-���'.�#/ڻcI@��S�Jd	�`0Z�$� �&1�h�"g��	}�t�V!/R`4��Dk�`4�2�z� 'g�h��z3ZMmI��a€�&*ʽ�@o4�"u���J �-5ZMm��a�K�b6ڭ%�`ڣ)f���7�Jd�E�N�}� 'g���`%@Mm8�keHf6Ωc9��`&ћk��Jd|G��	����M9d��&� �c3G9DdNf�aUd�A�&::�hVA{�;Z�k�ϩc����ke`��SMm1��i�U�kA��܎���ށG-�ؽ1<��`�aUd=:Nm.�X�i��W`�b=�Ym�@�'b�hԠ�&��<'0@`1M��<�!��7�ؕW`̛ۖyq`�1ĐFPI��3�Y���K��~�L"�_9v�keW�`	)J�&�$uW`����D9G
4��)%᤬>�c����-�bm0�n�D��Cx$@�a|H4G���
?��bK��+�� x-@z�i��q%}�_9�(�YZ�CmV!�a�Cm:��ct!�-o��x>)A��&f@}VAK"B�}�.)�DV�@?D �����i��x^�-|m��;��-�keCڂk��<i
��>D�M9Ġۉ��b��G9E�_9W@{�FڛkGZDmH�M9I){A)!(��+�`��9N`1Jz�bK�o�)�$*�tJgɥ�̥mf )c(���["V���&����	fD��9I�>��E9���%�L�`� @��˷S�`�-�m��e`/��}�����}�@c��&� �����aC�/R�
)O���ͅy%z���Lz�`�x%
����z�劗����&V�ۘ�f*Aoi�[m�S�/&�+���MFmWA�`���`�b���FNZ)q�x%�e��&��Y��J���}�����k��
)��O�(uPڢx:�J�>��A�I-;�DmJ`�1�`Nd�Q�&���Y�`�=V���+'QZ
}QE)��{b��{��q%1�L�R�}�u�X )��-��[m���+iE��
�{���v�W���i_�[mJ�C��bne#�[mI���{A)!}J��t��9���%Z�)u�+!#��d�]������S��bj�A>@���i�&���+�@oe4�;|T:Gm#�{U�E9|ހR�	��-�c۔�&%��BC{����|��jf���}�F�pFa�Vԕ�֜��/Vz�J we��bՠ�&�[�ZE��_�]9:�o���x�D����G9:tNd�lne�D�a&�WZg!�t)!!�K�} Gm0�o��+�C��	 �`XZ�&@��;`sIo�%���?A ��F�i
�{Y��bi�-_�<� �Z�A ���l�m�(`mp��2��{0��%[��a�.�-Gmgeƪ�b��@��\�{����]��i^��hP����`W��h_+!`�ZdaZ��O���%��k"�<C�T� �bM�|���-�����@�(���F{�b��zWVkF�hdiyJ |%<�,!R�bxFqӏE���s�i��\dA@�g����(����ȉ)"�<�q%>`�&{��z�
]k��}�`�"@�&�Hm�Vk��JcZ-gg��)(�I�d�P)!ll�QR`��R�;��z� 8(ذG9I ze3�ң&+i��(u�������b�ɢcH��$�F9�@-gd:�`�}���h��yE�i}��zٮ)e)3:��]�Vk	�/f�HmK �pK��+&�)uw�Ŵ"��$���1�II9�`��g�;u`ۇA����c ��."V?�F/1{�h��h�E��i�}jZ�%"`#;IF�P ��Ǚb<@�%�g@í"��z'��i�`�$%�;uI@���CaCp_�A�<C`��`Nm�A�+kZ D �j��DvB�i| ߀l�bm�)#���nZ}@?�o��z�@opZ�i�>Dr��q�}r�Hms�Jm<�oH!�tZ-g*a����2uZ-u���?Di )I s�G*��W!�`vZo�)3F��z���$eG���-nDX�Oa��Je��o
Σ���iϤze�`)K�ʀg�_+ 5�-nu�-!`���`��w�Xk񃹣���hq��&|`�^x��by�zw"��$���'	�#ۊ�ze��og�_+���nz�)u{�)gJ�����a�cz�J|z�i
a�[Mp�inG�k&���
`1�b}"`#i��i��h:�/!��	���'��g z1'ZP{�	�}z�b��D�a�<i~ڍ?���aYL��"`#]@o�Km���'�ڻj@�JmE�D��Jm^K�k�Z�`n�8�b�'I��kS��>@�zʉ�k���z�DZ�;��(��9���w�
&�`ʢ�eO-�໣��?�]k��h�D���/!�Z�cX�ze��fq�	ێ)�:�&d`�J�N�,��)�(�&���'�n���a��v@��U��8I�&��Z�>3�	�*�?�R�'@�hii�t�!�%Drʌ���h���/\��c]��`(�%<@���I9d`��Z�c̒Km�e�i&+i:A�����/���@;|�b�aJA���:g�:�&�c�a|�́�ڟ$|���Z�k�:u���*�o|�^]�Ad^��'P�4���	�H`}&�ٻ��-��˜��-��:@ې:�{��٘@7i�:�&�+!��be"��/IB{��ӟ$I���q�o�-gѭ�D�v�)��)1�&H �{�����9�Z�bf@�f ��7�R�!@}e �)&��_�Z�>�F�c]�be]�U`�bW��j�&I�cNz�I?��h�b?C;"�ide�����?!��-!_J;|Lg��!G��AVm�h�Ad|wۇ	}V�
��}���Y�-�jۖ�o&��j��C@9	{�F@���	�"nF`�R�a.gN���;����C�z<��9�C;�&�Ģ���$.@�:�Z�Hˠj�क़��9�Y?*�9���H���z
)�`���XmP����xG�Xm�z a���x*���Uwp�������aW@{��"�z9��$�(
=�C}ev���`Uv�dw� M"��<{�a
�.��$��k���a�	}��;g`ۡ��.�aٙ)"~eK����c
�]��2Ѕ1 a*��X��`7������yI��qVkF@�<��x #�-��ң-��9f`�'���$�XmqVk�M�a:��Т�{~�˙-A�/;���i�$|���9q	j���j�F���k|��R����0'��a��xa��zՀYmTɿ,Dr�(e@�/I�kg����
��>:�X��i�Om��{~<�
h1�
�>a�h���zC�d1�ecN��z�hr��:Nm�pZ�
`�h#$Fn����&F����C�Am@�a�Z�z���`:a�f�ڗw
�,���z&�~1�Om���(!{&ڭm�.�<i���z�cʌ���hb��'�k�ڻc��ց��a�@��d��O!�`V��x"=�`�45��i��)<��{�a��|ྒ�˻c"��j��)��a���z���h�8�`ڀ�j��b~i��[bF��.Aդ��=�݀�s�)��x�í�'g�V@�X|���1�k�Z�/�:�`H��i������|�Z�/���zq �c�&��ՁR��i�a�87�<�Y���]�a�G�%&�v�A."��x�#+�?�:�-���j��"u@&DR��ߓ�c���`f�2��>��9��`���r�%Do��h��xw��Y�K��z�ƻc������`���Y�%a��V!ۋ��y�t⮁y�\-%D�#(�\�XvS�9R����K૴rMme<@�Y�f���kBsf�@�x�Au7S�r����>߅\-�L�c��
z���if ۩!�BC�q%|ހ9�)
o������x��<U@7iO�U-�Z@+��"g
�>�ڋb���&���h����zC��Z�- )���yW`�C�ȓ9�F��z�ź�hA@9�F��Vm�bo��n�Z�?�
}v`7����:�-%@�%��9_��'�a}0DmJ���VdnM;a���'���<@�Ⱥze��W9��W�@���Z@9g��k�&a&��>�Zd���E�Wfk�A@�?I��<�
zH�)a�
)ws�a��P@+P��H��&�",e�zw�@�z��k"�[mN��||�ց� 8�UH	)<��ڢx�K���)��,ţ�xo`�cq`�kBq�>F�ݴ��&��m1J���Z�z� �a�,��>?�)�7�[v�ݴJ��@�,33&@�� �j���a��)�Y��Tk(`�+�࣬g &'�Z�>�`�?�Y@9 &'��<iGJC�3A?:"��z�@���Z@+�Am�}�@�>�Am4�}ԇie�Z�hO���[vf`2��M����
A#a�:�3a�-��)|v���
` 3����x�)|׺|�q�غڸ�cw�
�cc�����|����j�%��z	{�H���t�f���<�ze*��,(��:�{_i#g��?g��Uu3ں��Z�a��$aqA�z��Ԯ!&'��kv�����i�A�zU�3���j徴�	VkR-�xF�:D�cN@�iB�cݺ�,l�?��&=9!�EX���>A@�A�i�E�1��)|�S�{-!Um &'X��<ĥ�,5��?� I��@��I"n�Xm��J{�"g�C�>	�i �i�Oœ����j;�9o�)D�N�>�����)nầ,���>��y��x�hʢX��/��	}�Z�>�9!��	}�Z]d��<i���z:A��T�h�D�)�ӢT@�,0�ߴ'�jQ[�d��/��"g@����z-�|�V!/C��`�U�c�A�aXȍ$̈́�i�7�{W@}w��ʢd
&a��|�?�hR��?�I�a_)T@�,��Ym�d��%@�c���o�9�����ZDmꈶ5��ܸ�){)@?��S}e2ѻc��z�i) �n�pgA �")뚄-�a
�$e�z<�
zP��wˠ�,=N;|�ZDm��z<�
h:�9��ZVm� Um')�-����|`�ɦm�
9!�ZDm���W?�H`�z���A}eT@�,�S]d<@�κ!���Z}e�ӢcJA\�Z�q$g�@�yf�)�b��CmmC�,o@]d�ZDm$��`�Z}e�ZVm]`�`R@�z�z a�ڭ%� ��!���/a�bkc���?�A�k�YmI�M����N��{���![��zkcq�M��!!�: �c���-ŬZm;��b:�v�빥>`)���ZDmB@�{A�ie����i*�_"y$g_�m#��$F��D�Ym� ~e���h|t|����ϥ����&l,j�A��P�5��)<�
zU��hT��9�$g����Z)!H�>G�-D��	��m��@��{�i"@)!@D�`�H�zɄ�����J �-�`D|N���(Ҫ�ȧ���jg� D���&����i�[m���`ʦ�)��b���`�-��<i��:uq�}��j�dC !��Ug�*'y��*�4�<"�iFm݀be3C�3��%P����θ�"mF +a`�J�i��?:����aEm�>��W�3�?�:gw`����=1'��ZE���y a"(��{�0��x����{�"�&����y]@)!�,n�I9(�dŀ�y �bo�-�}�cV�u\�c�(`��h��E�ʀ�y	`�g�	��GN�j	;Gm�@]m>`ۺ�{�v�>o��jf$�`b��
�e�7d�J`\m���ef�=gU��+2�)g
�o�><@���j
��aZk�{B�|�;�b(����G�'7 <gC�=u�8X9���
[�';fC!Gm
apc��a�	�a��}n�8���`;X9`bc[����j
�F9��?g���p+X9B�XmVA�/d�J�Y� 3�XB�'8Q:��`~'�J���´��\-����5�i]�a�A�����y���`�!�&�������W`�>�!�{��y���%����d�Ym�`�| ����k7`�>څ��O=g� ^m��F��ؕ ��
�,��>g���'�⼁�`{�:��l;Y�}��^{�?S����÷��@h-�f�a߁�z��;e]�a[�/�@���D�&y����iД�j�{�$�w"�i� 1��s�?�'+a<P��3@�+[de7$΢�@�'C@�o����Ϣ��$��ĢC����E۽_��k�@�?( ��1@�����;.�{�D��Ym��=F-g��a*��%J�L%���'{�-q­>0@��T��>�o7p�-�i��8���i6& �zyeAd*��'Ca�?��.��x�̢�	o@�m�hMm��Zm�KI97)! [�>��i²)![�-"[I+�o.AfMAR-u`��i@�?j��%#�'ma�?�eF�$�;u| �^�g��d��NLj?y�y��W�6\mĠ�'�@Amg�Zm�3�< }f�9{�-\m'b�?	�)g��'!/'���vW�HmW \3A?:%[�z`�bA$u�����x&d~%ċk��R@�z���7w`Σ@��*��Ž9��/��ImR�`�iC!�xࠬ,'�Jm� ��@m�d~��y5��?([�>I�mcV!�M`�?!�i^`�(P`��B��-){)�D�M5Z�:\m��o���a.ag��ggn*[-u+[I9�k��p��=���̀I�mc���htB�-�@�����h,{؀gA�`H�a-��{���+���`(�蠌-a�i8��[mG�@mS��i�黣���w�c~�kU�UB�)'�A�.�ëxA$u���-|��	/��i���?C�C�W�{���?�U D
!0a��~�Y��>���N 
;�����	�]tI��q��-�CЎ�dc��,0��c���_�ec���c|��1{�h2[ D3�hA$u4[@9�'hO&D5��k1�@m��Km�Z)6��?WL�i7{)5��h���'�A�a:��9��g�8��?�+geO�yqB�c6
]m�0'H��hb�{y=�� ��[F�y�v_I�mcK���d߁�f�T@�,I��9d@�a��j������ecc�%�b�yFB99[�c���:�k%�h_c\���X
!�- �ecF@#an`�z;]m`�yv���:����}V�he�C��PUp�r�hv��<�͌@�}7�f*�Rc�dF�W�Km={@�q`�x"��>�}e rc?�Km�b�|`Ӹ@[�a�-uA-gq��{���h0/���|�]�f*&�FF��,B-g�ʆ-'BM	)f��c��{.��䈠N�Ԍ�'��aףCfD��iE��i��iF��hy�N����&`�(t��\`�c�,�hga^ma�{F;)%�	���W �z���YÏ?J!*��`t%%O���J������iʎ�{eKg��(�G[C�=b�aC�
T�%�aK�����
�	�}kH�%���iP��b`�-`�?H��H[M9(�Ұ˅���C��O��i��CmI{�(a��b�f8��G�BmJۏ?|����-_Ï?�!��K�}䡭ci�ec��-�	deA,i�ԷL�iw@$|M��%@Mmg���N�&~"C9���k1@���-ue��c�vc��?��cN��'��?e�a*
 	��mWvcM@m8��ϣ�fkcg��F�=�x��E�N��{f�}O�}wB1aP�cQr%if�.�r%�OmR��z��oc�+���i��$D��wB1aS��c.�	}T��{�E��ao�M9�4ע
��{U�cVۏ?|`ע)��+ͤmc�Om{�{eW Wd/���W��?"F)!�����9���]j!{�%Q(4'U�	Ю�E���{���2�B��X��iY�O9Ir%Z�=�Om�F)��h�G�…,�c���[�%D9�xc\��cA �?6�i~];+aV!0ag��^�E9V�qcR��z�dC�6E1a_��c;A$|`��k)���i�5aD��g����|�J��(;���N��J���{��C����+{ �i��)a[�'S z��E�^�hޤAmA��1�j�z���7�beo�-a.����D91�i �.=
i8��f�D-|����h�Z�hg�cy%�i�f�Amd�b[o���#+aJ�%D���S�����z��%Di@.'tb�-c�cb�?��?d{�m�a�m?
}uU�h �i��
!+az!�ie[of�2�
}�fYf�b}�'�&ag�?{�)h�{AV%	�zQ�ii�i���.���`e����F�{�Ұj[�>��zk�zVat%w�n27Gm,�z\����il�%Do�i�C5'�
���)ocf[1�{�!�&{�?���hm�l#"�)�9�-n�E9J��o;Gm;���(
op[�b")I ,D��vcq;+a8��W��&DUVmeRE�-�@��͡'Dr;os�%���!A�jW �-tۻcC@Q�d ׀�*��X3V *�%u;�,v Wv{�mB���Cδ>C��|`�{{�w�
)x�Bmb�f#�aF��b�c(_�ԋyZ�W�=ͻc��{���'�(]���M�%����ceF�ϣ���H�{Yc�[me�x�io��h
�ZdV��{]��? �-l��{���� �>J�O	��E&@�-T@me;�Vv 3��Ӣ��zqB5'�V)@
�y
oI��i���%z[�,���h����{[me<P�,#+a�(G~Y&a���h]��i�� g
!��*�5Z� �`T�Y��!!!�ɋkw�ԣ|��hF�>g���T��i}�Hm�>f �f��b�m~Hi#�E-gy@��*�����UT�'D��%�!��Z�m#L�vc)@ԣC���;�y~J9���\g�������4K�(D��S�y 3W�5a���Q�"�-��-�m~S�:�%���ab�b���%@)DN@\�[)D�@?���y��k��m8��\c�dz�Yc�)����A��|��N� gwbpc�K)D�£�������a�T�'q�m8�לU�}�󾤈{e;N)���9�[�h| �d�cL��i�p���oeߥ�9eSBq�cCcSp����>��Pf *D���kR��Y�D�B��ZI9������k��V~��y���`P��@�� �#ע����A;�d�m}c$��^D	f������y%����[I9 �z���CP�ֈ����@-gQhpcF +a�`�0��*|%��?��a<�hc��9<��o{�m8m���f �b����ܐB܀>�h>�z�`��g ����if@f���-����l�V��^�Ja򑂞8.*媸� )�;nfD''��4S�ʰ�������`	`}��	f���'��j��:���:��C��5aA�a�&�z���9P�3i���ǧ=��8'�ۭ><��v��V��	f�ۭ>��F{!�cq��`&�ڀ��"g(�	f��j�{)1f�@�z�6G{�>�aF�۩c<@;�d�	��7��Jy@����#u~��aO�愗[�'�!�S�[�i�@Vm6��iq��c%Ԛi �j���k��#g�aZ�)'�G��۩cS��`
��-0��I��`���n@��@۞;�j)�׌: ���4GH��<���wbt%BC5'NƏ-�K9�G5_��<� 0��{�?�K9�)�xJ��M �%�)I���i�-0�x�K9�Eԣ��{���{u�EVm 亂Km��I��o���g��鉎�.�~c��	f�`@���L9� ��akcI���h��P�&���{�?�;�{���{�-�@���a@�Ӣ7`5u�~c���iF-|���{i	�(��YF`̑�;)o�x��,D�)� �hd���N�פ�������&��{�ۭ>)�ۏ-@�>W��Y�5a�[ �@�/����#u@�-I��0>}c�4�d��6a�{�-��	���%X@Vm��}cVA5'�y`O?	@۰;�j�[Vm��i��(�{�,��-�[�/I`r��� ��{�{�,|���[�/�b�>����oR�I���}�[m��(�`6a�>L�o�s���iA��ܷ[�{mA��%�-D�;�-��in�`#)�1�i<Po�;�?��xcDe�-ʀ�{��#`�`o�ŀ�Ȫ�-:�����heb��?aB��1&�i*��c��M9;��x|����i��Q� �h��-�ܔM�?W �a'(�?��n��5a-��xi	ۻ��>w@�[)XB�{�[M&@)��&!��@d�� ��;'"��,�a�m�/|< ���-��?D�ۦ���5�}c��]�H�����<�?D���i���JW����}��&�@۴��ۀ� � +a<�oa�-D<��}��i�B�ܗ��;<�{U�}c1Wo����B+=We���M+�j�Uj��-D�@5'�$�iW�(�J����i��i0@oe��hx��xN@�zܬ�{7@&gdN_���z;�&(�-i@�@���u`&}��%�L�a��}��z�[�io�`�j�����ܮ@�c&��n4}���>~��j�Y�&����7��cN`t%Û
2���Ÿ-�N9Q��x��>ěg$@�&*�c���`�`�jO�>��TTƛ
 ��"u�}c��*�T�h����(�R��{�xU�}cg�0'�`�y�[�����+��i-�>b�3��F�>�����`�<���m�c^��i��f���o���}�,:!VapcV!(n��'!@�n&`=�,�/Nn=�)3�{�j��tcF@�{Uu�z.���{�i̛�'S�:gvc]@�j�[�-�B��S
=Λ/Dϻ�ivX�	r%�d�C���WL�k�tc|�#�<@Adv`��'�B�`"ܪN�8|�c�Pԫ(��E�q@�xW
I�t,� [�]@�?
�}:���e?�<��oH�ZmЛ�i�A�&ޡc	 �-�{�i�;�/w���K�״��J@8a��a(��۝-�@��I@�n�Hc��{�y�p�ka�y =�۝-׻)4�;g�,4'�{�i�`���`&���*�;g& ܪqa�x�;M-�{�k�[�kR�"g��cU�����&��`P�z}VA�"�Zm'�_9g%
�	`���ib"4'��i5DoCDvaDD��Ӹ'I
 ��}cg�)u�&i��(J��7�)govNmf�&�`W�v`{�޻)�;�Q��?�[$g3E�x���ov��fd=��W����[ I�J���[�%�k�?⻞-HT}&@ԣ]��%㻾>���b� �j���#�vcC@ez����Q�7�c��5a�{�{��{0jϣK��>滞-J "�[�'�;�i�oeg��hw�[}(@״��`�B<'W�M+I����� 뻾>P�eon �&H=w��>d��+r��i���h| ��0ٴ���a�)�n��i�oe� �i��-t��-��`%@	)ݦ�i��vc7�c���!�y;N�'�d�&�8#Е���Nv~at%I��i�F��s}| �^E��e N��z1��j���aAFa� � �t�<�R��?K��p"�]9��ݰw�}|�ÀX`Nm�!�h�{}��`0��- W17��/(��iH�]9�@@d���^ao �&d����y�M�c�����-����� �z���aw�
��o�MR?��f�y��,�A�'ׁ��SF+!�G�`6E1a;n�-�;�hN!�iC�����\ʪ}�As���,!�;�& ���K@,��&�R���;�{g�-��xc�ؚ���d���{#�i2!�{x��{>,�ieb�?A��`�A<'� 0��a���	�Yvc�@���z�`2"�]9���]b��!A�� #�&^�	}
��,�����`���&<�-}�be=�Am��	o��9D��	}�|9I�y��%�"��G
�f�@�-hco�G��;�heH�V%w�
�w�;Umi����%���ۂ��� �&b�
ۡ����,�/�i`��:�zV�
�;Um_5q1wIc�X )UA�-�I�c]��-�!�-�s���Y#�ݪŀ�Y�{�+���W�1'}q�(�<o �bR-�!�&:�#����Y ���Fzdckg
@9<��} �?!��{e�vYJ�&��C�&V��l]��`�@�\�&�ib�
���a��i��-��&� 1@����f*<��}�B�ܛCDmq��`%��h�@۴6E?�|�i%A��
A�?�
�{���y�G�y&h�(�(�B�k��<av�{J�vc�a.u%�)\�&���`�e:@�x`�	}�`�(}"ݴoR-C��iH��<@��J�$�<�`�A[`
)I�Cm'F��Dh�_O����B����z\�a�2.b2a���i:]���i|
)��_f��cS��`:�v_	ܩx| ��~��€��R@
}�
@9��?lFa
|�&|�&����] �?I�K��`)YVmO��iK`�l��i<�`���'i�}��}�@D%��
�P\@<'��f
�)uA$n HDm��Fz�f�\�a0e�i��&���\
}�6�A@
 NN���;g|�{����c|�i��c͉�iT��k��)gB��':�/�K���5a���_�c��/!�A�'9��&|��(@�5\8a��W��A�'�5a���%h`�k�@��v���|ુ�Xm|@��	@�a��j"�?f�$)�)\�a�`$���C)���g���<8D؆�&1i�y<�`X�)o@�x@�NO�Xm)�)���R�z&F�+�Y9r��&;y�q$�OB 8D���{�-�P(�&DA[�|�&q�c�����i�Am\o<��}�a9d��C`P�&��F9�a�y����R�b���kx���Am��	$ ��&!|�k1�`��ى��"�;gC�d#�B$�xc�`+%�	f�b�z&\�'V��`���(T
}�e!n���`	@�
 �`&�G��C�ޔ�cQC�k��>���K �<m� '
}K@�	vY9��-(ܭ>b!X9)\f*<�a+ !g��,�Ym]��{-\}~W��a��}:�}cL�kV!%:J !���+z�&D�ĝ-��j��be�@de�.��v@�S.|��Sa�k�)/\�-*�Bm��̸�)���aN`@�T �&c��7 z#8
}�f�ס�ii�Fk��7�
}:���/�5a
}��?�*�����`Ѧdapc�Ʀ���?P�C��660Y+RT�
Vm�
oS�-�'�c�"�i�#ue
�\d�A5S��ә1<Gm3g;���{Z6�i&��+JA��ʢ�f@}~.Dm3G۴2\�cI@y��&w@���	۾��x��h����& I�BI9W��Ӂ%�AR-uc:)P-gI�iv@�{����U})�����eU��%��Ȍ��o3��/tA)D��S-�?a4<)&��`P���T��{5�4Z6�$"��-7�
�{)��W`����o��+�@-u���{F �|`Ѐ��Im� j��6�-8�)P���y��yB�	�$gDm9�ie�&���&�L�	�Lo���C&I`��a�=a�@ :<{�� ۪�!�2(�5;|�`�N@�	<�i|g:D��?�k��B�_y�-!&��*��SA� �#�S���|�$�(�l&��+�2)g��-F)�
2T�梶��ׁ���A�'C-uz���_Ʀ%N`pc=�c4A@9P���>|ɣ���`?�I@�i� �Hg��k�OoC�fF�EdA��cf�"gv�y��jf��z�L��3�)�& ��em~3`ɣ&���B|ɣ_g�-����c��q��)Idp�W�F9+JYU&�@mx�_@@9���+]c�c�F��I��YC\�`�@Y9��X� ��>c�}D�F9�
�f��������z����$�nt�{e�]mE�)�`o���&F��&W�<a*�y�݆�a��ȣI����8'X`�a_�;|�
f�A۴&��Ye� ���&<��o<�)::�tA:�t_:�t#ta�a!`�YM@Y9I`@�pջc|`�j���� C�����e�fɱBP}chc>֙G|5u<PoH\@9I\�a ���X`�aKI}�Ym�k���!+��y�˂&W�)�d�G�)���J�a�]m4}��b�r%�f#V�\9%���q�ۖ��&�d;F
`�&KܴW9k�&t)��c��g]m����"$g�$u���:��R��)`{��cW Z+�G����/d ˣ
"�-o �ic@�yz��?U"|b ��@<'C�oL|5g�Od0 �$g�$��7�%G+2!�aM|5u<`}�b^mC�<��@�f0�$!�U	 N@5�{I+��?R@�`N|�%C��v�Py��b����o�&4�zw�AMmB nv@zR�I+3�<�O<)łb���>�C��P�HmQ�)��E����i��)}N��:@��9	I9b�^mR�=.ċb�Ƣc�E�k�)}�C���[��M@�xY��i]mS��S�t_��9Ƃb;�Bmw�棋��c���OvA�N_mFe�&P�3���n�m#C�<�T�_9g@��S�'��<��bU��b���ȇ�#/e�ޘ�m1/a�H:aj��U�ܼ���֋�%@�kQ��4�Bm����.�ieOA�4�"íV\Dm��[m���'�:���I�ieW��V�D��݀m#z8 �c@O�w�壖C@"W��aX�Zm.��J���Y��`Z<}�{w	�1'<@��]�-Mæ�R�-C���B�ze����[\�&��eOC�cc��a��ze���&	vc��bw�(� �x�y��c��f\|\m��@v�b�%CPU2]��a<�[m�[M����c:!��� �&@ &��a�Bf��)^�fVۈ�躄�oo�ie&��+��&!_}e`�c�B
f:��금s����a � a}e�E۴b��c
A�+��-Dc<�&���i�=aeBDmJ�{e%��&��M9�%�&d�?D]�b4�ce}eL��cUj�f�Jmg<�c-��|���[c�J<�c �&�@�΀�{� �ih\9!��c�cX@ ��ʕ�c�a;��֣�_�C�$���iNFmf`�bi\]m�(�&v ;�5��i�&�� �cj D���h���+�a�%1@۴k}Ha&�y�T}��iU}eUɘ�|��	`�5��iL�}� �����,�#l\�iD���g#�im|�c�E�h���n�be!:uo�	}z�(gx�  :�:uJ৘0@����d �i���ܠǂk�:u}eA裏� �%%$�c�o.!��:u�~1T}M}e�:up\4Sۡ1
)C�4�q)!�"�iq�z��%h��L9!0�fW��i��	}�)}��+���VA�1@Ch���z0`�c�`�mr)aH�+<�@��@������$������$n� ��)��T/s�ae�a�c��_9��	o��t}a�:g�B�c}��Kmu�;uW`�id���"�4b�cg@��,}B�UQÉk*����$R�/��&�y��!�50Pk���m1��7v\�a���w<
)�y�'Z�i�$x\�a�"ˣS%�I΀BdY�Cd:a��c�;g!@�%C�<���m#W��`[�v�F�ceπ��S@��@۴U)yܦ%%��VSW�
B�c���{�A��	dkӹ�a���k������aO���z��<Nf ��S�;u%��&"�y����W��%��W��f<u{�am�):R@��Է���ܾƯ%d�$�@"Db<uH��p
�y�@C�3G۴qa�c~H���*g;VQkq�}�`)�!���"nI �R@����F�xd���|�)g���i�f��i/O�X`�c:��'�A5���iR�"nJ�{�@Z��P
o���͡Cd}|<g@UI9N�Cv�&��d��;�)|h�W��2g�n~�Cd� �<f`�?�}��-�܉y��`N��c��j��+{��&���F@I9��%(����o7��bV�<|&��H����}���{�L
}dFy��!T�}q"�>
!ظ�A�'�@��8Q*�vB7�F �o��oW(�A2}w"Pk�\)��-�$|N@�|`��a[���
P�ٟBC�X@Dv��%�M��D
o�a��@Ц��b3G۴�\"DD���$ngA D܎�'���{���nX��'|��?kV)���{����R�M�A��P���]�=!���i�
�%�%�a,ǀy���&�a��v`W9`W1��Imw�)P��!��}*��a�$n�� �`a`����k��)�fy�@s��F Dca�&�- �i�`�&;@I9U��i!a�{}��#M`#D���d �*?E���\۴:1�y:!�/�-�C��i<@�Δ��k.*�o�`@�D3)��t#B��b �3`̣�S��i���c��p����{]�$:���k�o탹> �-Ӂf#��}�<�?I�$Wv��iF�k��|�-gV���g�-1H�k�<�-cY�yd��<}��b��kfe�7�>;� ��_fed�8і�%Dw�M���Mv<1y��Fd3�x-�$n��}�ۗo2��b��{-K��N`�A��D�d���d�Oa��U�	fW��M"|��o-��f�\�k)�̀0��o͡�y%��y��b2`�i%��H`�i$o:��-n�)��9<p�i�@9;��*�*�&�=$�g]`ۜ<�&
boy��(@�+��@m��(�+W �&_�}Q�a���'�@��`�{��۟\Vm�<�& Ä-��{€���Ţ�܄?��a"����a��+e�&:0��z�ޣ<�-����\�'<�}ba�-��he��-��cfR���o�|o�l�`i *|�\�'1���z�ʄ-��k-!�i@6��he��i\��+I��uN`�E�o���-Ӂ��<��.g���fR�e��i:�1�y��yI�`8W@} #)��&D�ʌc��%ɚ�%d��b�Am��9�\���`��⣬	��3��+T@MmN�ieg��y8���[d��)%�L9�6��\�m�`�c��i�|oO%�9�&{<�i3��[b��:A�n��Iv���y߁�yeP븰�y� �<~wo� ��g�c��)��W�V��a��	�M�j�)��#��Fd��ֳ\}��y��A���-��h��"|��&_�@��&:��)䄦�q�[d��W���`�ca�Hd���Z���HdR@�I
u3��i��L�uI)`ɣ�Ivo�ie��he���-��c�� �\HK$|:��漜�cW�/|9Bo��'D��c��m�a�cZ�����iDH��S}�f�i| �^�����+A2�i�):߁�y&)D|@�jN\B9CaH� �{f �'����@��|�ߟ�M�`*A ڄϣC`Q���%��i�\�`� T�B��yӡ5����? }�`�S�%g ۾)Dcg�w�g�� ��f7 �`�<a��)?Z�Kv7�M9�j���9�Zm7�Ym:���se���`R@�@B9|�׳�Y,D��:�m�):Ā�dc�c���`�|Um ۖb�&�):���+�df�\�J
Dm;�J%f�O f;@B9N@�3�٢d�/�&�Dm��)|tAfe������'żXm3DmI���AΒU�c%����� �����ǜke��-nP�t�I`S$Ā��)n����m�f'���O�&ډ`*Dȼ�-�A{���Cm]`fV!Pksy�&<�
z�HմBH��u��Cv��{����|�&Vm�b*D�\�'>�<aU��(@����8�|���	 �#.��a��`P�CmA '�f�+z3�.�sۨ����b���-!}+��`́Kd�b)y@���| �F�c�@�z� gP` -@��&�tk���`��k
��'7��=f�+�Zm.@C�� x�9F��:�t�~8C��?��E�R�I�q�C7 !!�!��͜�&�J�c:��^&��+:��|�f�ct��M�x-������io@C��yB€ۍ�.�bOMd��yw��A?%j��<EmV!�,`��mw��%�ie�@
��\�c��?!/H�����x	�}����!�Q+D	�93�[m9F�&����?�*#�`��C5�-O�'y����+|��d
{��<�&R`@�A����<�-��_" Emo@C�0��`�\�&f�+5,�&�\}W�UkDy�b]��-��`g�z�E�7��kC�%�@P�\fJ��
yq�<Em�\FmK@Ǵ�m#�b�w@
�N�gԩI}�\$u#k(� �a��c����`Q��m&�|�,�\f�$!!�l=�� �@Fm]`�b] fJ@�I�< �W@�7`�`5��&�����a�W�&���P�|�&��&R�Fm��f� ��E9]��b��&q`ۇA�i-��'@�bAFm
@)|��g��-�X�<�"I��X���G{D)D��/��m1(��%$�	���{�E9�j�-|ƿ�_�F9����I]m|�L��-C������-|| #�<�-\��<�
zق�`Va�+�\)D�-|Kண@&���G9�-n�
o�
}w`�j|�^7`�`W��`�!�|Gm@�#�AXk,�2�\Fm�"�F� Di`I���
���
$��&& DUe��d`�bo �m��#g�)|��Ym��	}D�	}��)|"hf:���&`I]��?�$uX�F9 D�m~�-��F9&��YV�::���	���W� i�����x-e$u��é;�x�3!�m��`k	�\kA@���&�~c�A�`W���SpJ�-|�݀���I`@�H�zw<�Hm��#g&o���iF�>�Hm�`�ܱ-n��?v�Ϊ�}?%�D�tGm���i'tI@�UJ�Y��Hm� m�)|@:�me�`.|'�$3��9�A n�<%gS�$!w�ϣ�#u�\�cA�`D���'H�cv`�ή��ct�a��6���.D��	o$)	�\k���?*J���w"4E�kv`��g�c`V��#u����nvc"�[m��_9���eWI9:��J༈���C���z��N��)A�c���'��/Dg�����1wU��c�@�&�\�k���m
A�`��b��[m@���?�R���``c���he�`|�7�!B�g ��3Bv�F���� ���hR��z@�aw���h���|�`.|'B"D���zX@�c�!���f7��& I&g*����bJ9�|)��h�a�|���*��޲͢cG��'�zG�̌��-�``c��b�
�ǕΣF��}�Y�SJ��t !D���'z!q1|܀�@"D/��|�Jm�ynev�{lI�%e��?] %g��z����-��i��{�)U`�cݩc}�`5��zAk�� �
���'z�?D7q`c��'nm{>�6�_C&uC(ԣ��a��帱�"n{A&g}`c���9���b��
=�z}'g]c'u{AK+�`�9��a}'gX@)!A༾��aJ ��]-|��"n �`eB�U2OW��):Kָ���Jm�b�<	�h
]]m�}y�z'��+��Ok�=q�M�
�}�c@X�g�'!�D�U��%$�
|��R`�f�\�I`fک�	fg�'!�f#DC!�c
��a�FmC�����g�'!Uef�u��?|��^z�����a$��%�D���.��`M DS wU�	fߥTk���A�����d�ϰ �Jm�BE����(]@)3ᆙ|��LI����h�|��$nV!Pkt�|�w�Km=^m>�F9��}])!o�_�`g�|���EOЈ�Ok���Ӈ$D�EK9i)ˢI ��#�Y�}'g�(g�`�%�t'g��{|`�^�J)!�!}��'!�H	%]
}]�h��a�@K9��XmAn۪�+N���-D| ���'ʠ^9��w��i�ʂk���y}#D����a�i�aV��>�4i���� �V}�h�)u�ʖ�`�:{�<�Uk�o�(g��	B�₹����fR�҇�{=}Mm� o )%| ��{7 ueVA�'!]�'"��i�
2D��8хoA@�i�y���9#] D$$|�/�2D.\��<�)�h�$��k�;g@�#��x�r1���h��k�B�)!��]��Y�%]�c��&��zy��i�be%@�'��C�<�
h0�|�@wwb�{�nܴ']�-(��zi��k%�%DF��h+͔V��
�)��i�2���*}DD
"�?f�a+��iF���H�ce�$q���m��,��z&�bd@�-/�+P�G��Wde� �i
Acc��H9	)!N ��J X"�J�=��-��an`�-��az!)�B�-{��.]�'-@$��Z�/��hW�/|׷�cf�|�R@���@�-�@�&C�xZ��E0��i ��c1=�zh�)f�|�R��z�f��2]
f�Ym�LI9
A-gᡘd3��z�a���&:4�|�}@ 5=�zzaww���~ ���#"#DoM����٢�`#�k6]
f�`17��aM�Y�	`d%pm�j΀�c���'�BI+W@Dd1�w�^�&D8]i*9]�-	)-�a;N
f}��>&�	II����{�f���âQ+!P��:F�8�':!o�
`�.��9]�'D2$��W��b�A�o���c��z;�i@�R@' ��h'bDDf��+���c��9�����Uk<]Omc!ad�ǁ_� {1=��cw��mF T-O�Z�d��$>��h� \m(�����4\mA��#Jo�û+�&:�y!C����������cR�W�C��i�i~*�"k�'g�
W���?�?[c�HB��@��i� ��A��B}�.�්H�$��X�f���c��i_��{A):�fcC�,!| ݀Oc�5�`wwz���#�$D�
�J�@p�D�f���E����$�@ F-g3��h��	$a�|�S�,!6��hN�cG�[m.��H=�-����%���I"I]��J��hK�fʠ(|:��i��[m��9T�
2L=�{����!��}�$B����M��z=�5�N��g ��A}J�9��$D�@��D n��-�d ���O�ߣb���ێ��hS�g�
*�y�<�
zי)|�Hg�P@9a���Q=�zR �c�S��)"-g� �� �i�SFaT]�`3�)|;f�/�
U=�{V�!g|`�;�Xd%��y�Y����
�>ͺ՞'�� �be��zN�	=: ��B�TA]�!gq�g!W]�z�ߣw��oX]�i,O�%N�`_dw�`�
Y)D�'�-�5@e�)of+��H��x�:���P�W��Y�c��fC�!g@Xk����Z]�{W@Xk[]�c\�f��Lj����"�-/f)�b��):���`t8D�n U�|�|�]=�{��
z'�]�*j+��y�^�ze���yO�5����	_�?�@���	�@�i)���7@i#�8�`.u��z( ��e" � X�cj�o`�'ac.g��)q��z%��o�ù>D��h`��z=̈́?���&  ��ca]9Db}�zv�c��zĈ�hzA;|*��'��}��Z�b�&�Ѵ�!�޺��`�A���@����z�*��! ��d=�c������u{R�i3g�e�#u>#v�=�/!�o*D;X�{���y�D�f}�i�BXk�`&�]B�m�Ʈ�.�-���;���F`&d�Zvr��ꁃ"ug]9Dn`8D��ߴh=�?� ��K�Ѵi�M9KmG"P�H�@$u:�tj�´( �`�i{!f_��a��A�\�k�`	`�(`�����?Q�je��+|��a�ZdUAf��m0�a:`G�l��id@/u!`&BMm����܌�a��|�P �׾f�&]�,D�+|X f9G��E�c��Y�ac�h5�#g�f�f#B$I�	�O!:DP��{��/�A�C v-W�m�AX�. �'J��3Ĉ�h��h��zm]fn}�i��Y�>a�}���ho=��`�a͡�had%\�Y�p=�-_� d�$�� �gfa�}q�f�m8T@�hT��{�W�z�}eEcf�h��"�i��-D�M9< \�q ::r])Ds�	}���-g����	}o���3��it�)|u]�z%��{儹��&v�џ�Y�: ���vc���{���&WfcI`@���}w D	�\k&u���ii�)|���\��?x�;n� �y=�{!ŏz��?Oe�h�)|{� � 
)���cy��?R�|�A ��#�f#��8`{|�-D[ݹ��O}�	}NP�'���{C�|�%�-D[#=o}e~��i�-B ��.�i"���-T@�h��be�r�g�W���f�Z"F@�h�=)��-�{���\�K��%��ܺ@ �]�hy@�,:aI�_-nI�m& +7�.Dq$51&_�VqU]&c���~1�{$�o�A/��Y��.D�A�a=mfCaow �%@�h|`�Ri1�������Ev�ի���=!DǕ�h��z��zeA�I�{&��� �;|b��i��<D���i�]`���'\��V��i*��u�&i�&�G�)|C�)X��ta~e���{�`�+�~1�� ��壦&'u�\k���zq�k%� % �z���{<�N>I��k�݉y(�贁#Cd�
�܌=!D$mz�wbd%wbCd���P �TF�'-P�c���OI neg���}�-�� ta~e�݉kJ�\�UC۽ac)n�@F"`�-n�4be���8kc��)�����|�����i�
ٕ��z���?R@�C��k;@f���i���{��J'f�>�i�)&���&�}X9�=Wm��iza�)�Y�>DX !D��m# Wm��y���i�fg�����4S��>D�b�Y�0˕�)4�!4�%d�cYc�\`2=��{
  �Q-W�k� �y��h]@"D��b�`��>@\-o��i%�ؚ��ش1��)ޡ&��]�3G\-�D�⢜-�H��P t���h�])!C��{7@	 �݉yF��{�@;n$��z��F+���{z�]�C�?���-,ěk���noe��-���-7��{��"|;�jC�"|BՉkf��1}��PG|5u���{��'��{��(g�"ne��Y�:��"��<2)�{��)u�����':Aۅ=C2�3�k��
)�Dʈ"��<q�{�@F�%��a�}X9@�YmP��`�mc���zlNY+��&Ǐ�	�(�m�}1DzaHd����&�1&@�'����]6uF`�g$|��ke��)灋k�`,D@�kt��{� 2��.g����G9���a*�Xm� ��]�'�]f� !���y���/�����oe+2D��z�]�i5�$D���W�{a�e7�	f��ܖ�4bI�����1�%€�-�0
 ��:D�=�iqDk5�L�zg���"����:Aى
�Zm;���qA-|	��e� �me� �#�#kc�]-|���-@����-DI`�i"�f#�ˢc<�?:�K�E��	�`�i:a%��]�i�B�{� ��=&�Ym�a\q`�iЀ��P��a��u_�w_g !�}�i���W
۹ !�g�]�zv����\kr�YmVa۷�[m
A�i��k��	@" ��w"pc���\G�{N�%De�B"�i��i��=�m��Ǻ]�{���7�ɴ�4
 | �= 3`��P�y@Ӛ"`�z�]�zg@�:W�%Dg�b~kDqc��[m� �zZ�cV)C�j�>�]����ڡܩcwBqc)`�o"��/��c>�g�]i�&���-&`�!���`�{I۫ܩc'�%D( ��+�C��?Š���d��<���-�}\��-���/��Ad���y���	��i˕�ݩcF���J�Ȝ���� kc�)��,W
�%��a�+!
`M9�mw�@�>��%Dme�b�{��&D�݄?z� �}�{�������,@��]{�I_��ݩc�ݻc*��-Рc/J�z"�/&ɩc9i�i�`t��݄-�}�{�'"|V�\mFE
f�Ei#��%Df B���`#����ݻc̽�{e"�z���=f��iP��~]�c�ʟ-��a���i���q�>{fc�A
f|�|�%���Bd��]@�%�aͽ�{���`�&�mр�'�@-u&�q%��`�9u€q�:!��р�'@��oO�}�Km��}�B]��iey�����j����ф?��һ�'D��ϝ�ag�&�@��q��i�Dv7��iP���g��C���F��{1h{�{fc�@���B�[%A��%A�)J�4S�)��&D�]]m5fcԽ�i���[�ţm�
 ՝�c`í�A2D\ B��9^m��� �� f�f@�i���f����^9WDv��k�`�CA@"�Dv��8a�ea�'D��3`'Dv7���/�Dv�&�@�z�Dv4�:u`b�[��j��N"�f*�g������W���/u����&�?D���{р�'ac�a�/u.b�>0%^m7��if@@"q ^mS�r���z�ۚ΂k������{�:uJ৘W ��؝�cg��`w@�nU�$g /g�����5����{˂�g�'D*�ܮ )��B�
��?f�;uB
�J!�-��)n��)|{
f���aN_�]@�-_��S	2A@�a0ӂk��a���{p$��F�%�
f�]�c&`���Dv���z���?�ScCa�`�Dv�1f�����]@"�D����	�"�ZW@�`��_9�]	2��aۀ��:g&�i�}�ۢ�L9�T����i�)|>��w���{J�<<�����`�/�4G��`� �.�� �b*D���-| 
������{�
Mm;� ʆY�!�.*�vc�Fqcg��{-��f�`�A��܋f�H�c�Bv���iW`&���a|���L	 �f �y�C��|`��F"�a&A���gMmWL�/O[�� %@	2��ۯa&o`f�ds�� ����_9(��Ɂ����v�wĈ���%D	 ��>��=���CAFd��}��9F�{��&!�
�+W������{$D=g�`�"2BFdM��P�����S��c���{gC=u�k!��z���-n �'�'��P�h��i�����j�{�@i*yf���)��N�K`��� �.A����iӉyb����]�-:���{3 �F&@��d��y>fc]`*D��ǘ��Ixc�`&��}�{! �}�i`��c+�F�R� %�h+�
OP���`<u��+|o���.����i��{�!��}Nmf�8�
ۮ�&3ifc&F�oP�&��a5*�	}�]$u��k�d�^:��fȔ4K :�"�m�}�{g�Րn �a|��7NUۢ�i��{q@xcFi8�c��}n�,Df�Hd�,D��zW�Nd
`�hb�����[�f���c�A�?{fcg�vcw@=�@۴�C=g�����?3`ڣ� �=BSU2���=��p��Hd���f�cC���]Ddޡae�-|��<���ncW@�FV+D)!$�����z
B�i� :D́�h�]�h
Y�'aC�iWa��We�S�]�h��
)R� % �{>R�'��m1�։y�u0��%a�gV�%�(@�+�D���?�`!D��iU	jA@Cd��-�Fh-����f��cR�zX�?!����c�)�?gR�?�Afk�:|��?�]�h��Id�@zGW@9bFC�S;CW �iS����/��z�'�� H�'�]�h��h�]�i�(�z]@@9]��a@�{��h%@ D��Ɨ8���|��ŸE�i��ce��\d� ל7` ���a��Iv �c��)�į,(�(�ceW�cw��sI# DO	%T@ ^�i�`�eK�>-|��hP`]��
 �-|q )J�e>�i>�z� �c{�-D�(�i�"|�-|������	�"|�@+ �)C�?�fM"�@9�'��~H�v�Q�>�I"�H��Y��
�h�% ��z�H5S�Dxc"@9q`�.���{��-�@9
a=�<DfH��"|��?��cS@:*
��kW��w
��
�cea�eH9D��
 ��;|�@Ӛ�Iv>�h^m~A�<D�Jd������'��tc]�.D�"�m8x�i H�y��.D��
2g@��	�)��ucV�zeq�uc�ڜU��cd@�a*��[m~A�_9Z&H��t_f�>0@/n}"D��e^�k���{�45uf_�b?�τ"|��y��e��G<5ua�.�2�`��D����q��a]�?Th�.��%Ρ�ǡ�����;�@9^$|R`Xd���h]`�c��FaW �cVs�z��|ce©c�@feDŽ��3 &^Dms�شH�g2�z�m)2�#>|{�¤�d�Kd��tAfe��c�Q��A�$�J�k��{��Kd� Cz����~�C�|�Ca��|cH�f#�>Dv�s��!X�� ^$|`�mc��4:}e���/ͬ}�?����h��€�)^�kǠ>:I@
�g��cK ���i0���E���}c��?�-€�)��tc��͡�c࠯�"��<��i7@B9݆�kJaH.����	`!kc"��$d��,յ�z �9D!Md[���<��hA �����z
�DdA$||��o	 �}N�:��?D��<� �i�Dfe
@��"~�z�|c� t�)�;|4a�eI`�X@)!�C� Cz�)�	�F"y���#~Um���a�ef���a%��cI@
�ED�& $��'�"ުad:�/YB9y���%��-�tEm�M"��z&��-��'o��OH�ԣsR){�-"�>DR�	}z��,���K�z�H�f#�������ʆ�?'~Nd���i(�CmW@�h���i�$.
�@d��;T��)���oDm@{���<�{�|c��f�<��D��*��@Q��t�z*�y�cv�FC*A��+~�z�)�x�<��D�!H�L���zC9�VmeG
� ]�A�c�MdAAd�C|��!�C�dK4�F9]`1D7�?:Va�we��'��@dv :�X��{� :��`��8�,~�h����ق	ۢi1���-�ޣB�9�RV�c9���f�kd���`�S��{�I�i͢��:����)u~����k�{�'�o[�w�c��wT`�{;���a�)S��7��j. �#�a�e� ����.�W`Hd] 8!
�F**2�&|�xӐ�ie�y0�A�cQ%�>/^�i�
���@Od��| �i@
��P��`o�i�[)��\d�@
�0>Em1^�ieb�{���2�}�`�3^�{4k�)@�W@�\�he4~)r
�i@�{��£%��->�mcz!EmϤE95~NdH��b����i`ە`�~&��6� T@�if`2Q�he�a�)��i-�}�Q�cf�ې�i��nc�ҭ%P�*��*�R��'�{oD�o|��+g���m�{! =��\d9!kK�b7 �*�N�9!�#����8�yAc N���t)9�o] E�q�z:^�cga ;�:g(@&<^�-�s�z=~�i�]�cN�Bd>^�{�]�-���E9aBOd(����/'��"g �f@���:gLŭ%��BvV�z?^Md@�a
��LL��w�ޣ��iA^I9�ԛyB�
)C>�iZ�	�P���]�)n�o�@��T�}�B��W�F9y���D�o0	2b����i�΀�`E�E�:a�}Y�-%@}e| �5��`F>�|@��|[�-�}	 �i_�x��	f=Mde��keN��a& ո�;ukDvA�|cG�ke|[�-� ʦH�cX�?D�)f���I><g�"*D��
� (f)����Xo4)�8'���u����� .��<	@�itDd3`0���4DDJ�o���@۴��Т�-f��aW`HdV�)�	fmЯ����K^)DL��a��M�'	@�-N^o��cV�k� ���P����X�O��`�):Aw�P^k5� �m���iY�o� ~e7��cQ^��@Md�Č�	�'V!�}R^Md�b�S�`T~۵L�I9�ImO�Hm��
��Uޢc��)|��1���i<@ VI9R��cʠ}c���{f��<�h��P�W����/��	�E–"Ed�Hm���i��F��{W>fC��$g�Ϣc&I�>m��z��m#��fI�ke3A �r�p����X=u�W}J�HmY)���Z�}��?[^	2!��f; �m���� i��Fo��uc�ϻc�f��iA(�f%H��W�uc\f>��iy i��n}Bf7ѻcV!a~�]}��)>fc]�	f| �	g�I�^�	f��3aG@֢J �&o��)_�)���{.���i`�>�}5��ifD�L�*|`޻c( 㣢��{����X�Zma�)�A
�g�I�3���Bv�x��@�y0�-w�!g�af�`�8���z�H�b���`�Yc>�m�1�y��6z��F"��Bv�Aĭ��c]��'� ��+J�@�_��>!d� ���\J	2e��iy�&�ɪf^�{o� C 	���{& I]`o]`!!�bne���g^�>��i`Zd�`ne��mρKm�@T���i��;�JMm%F�$2a8Dh��i�Aĭ� X�f���7�F"f_�!t%�|c{A9Di�Jm�u)3ݣ��ij~Zd@?gk��i������F"��xo oeUd�W�Z-nC��Ʊ)b���?3%�=���f�@���,�dl~)1@��&`Ӣ1'fm^K9�ie�KMm���i�o��Jm&D-n)��{����Km:Aoen^�%:a֊��a���� C��|���o^-|���p�a��?3���{q��<r�)7`f���
�x��Uoe	��s��a��-t>:D�ĔCu��bv>�?w^oeE��y���i�F�ϳ��I!��u��Kmx��?��0�!��uy~�{��}�`���?�`��z~�iS-Nm@Dv�W-|���n }H �?dž)2�-�@U�1� n%����{>}��n�
f :D+J[�`DD���|^oe}��iGeDD�(���)@�+/f�{Aoe�@�$AZd�xH�Lg!��'~�-D���cW�|c@5�M DI@�az��>d`Uv�B�z��:|�`���-3��|���0��?�ނk7@�{g���C��w��Km���i�o����{e������a���-TMʧ����}�� nc��S����~!D)�&%�N��z�\>�co� oy�x�d�i{I"
���rv�ie�X"�>�?vVd�%�JO��#�-D;sg�y��@mVAu%DDd�!F��f%!%��~�{]�!|�� n ��CpU U�{+��ܬ�A��uc	��}U�k��@m`� n��cR`:��-�֢�@�{^�z�!������ W^n��kb\�c%���@;��k�>
)7�=:�7d ��E��-|aE�ae�)V!�}�R
f7I"�F�n�`!DJ�_�>�QP3
)��
}�Am��e�C[�a�P�=�@�z��k��Jd��L��f�~Nm1@{�];D%@	;��hi�zew��&'�(`�%�Y
f7`!D) ��s٢f��z���h��"|%�
2����,#DYfNm��fA��n�`�?��Xd�fn��'� ��J��d��.D��Id� �hPi}�@�?�ådqĥ�\��'}�<D���' �f�ˤxkVi8� #D�>
)z��cI��ׁ�cW�{�>�i��kY���w�Ȅ�y��i���c0`۽�Cxc!��"@xci �>�P�a�Z�+g�y& �YGljk�F�N��;a�?�ȣ��i��KI�/��i�+�yH@�?���h|�灓^�?{ ����/���ʺ ����y+���Oa�3�a	��Bm�^Dm4�"|��
o�c�ܕ^2D�~�F�@��i�@xc����*a�h��
}�>#D�|ć�i�x�O!>n�� ߃�a��z��{ȱ�-…%Фc��ZdP�w<�*`��}c�TR9%�zwb�)4�N
A���%��;�t��<DP7J��q�}�>1D���-�� {��U��R$n<-u��Cm��)'Mv��)e�aP�ئFmeo����'}��?�@���`�|c7�o��E�I@��.č$7�>:��=��E��-gS��i��Cm��C���?���a)9�ke�#nĠ\d:�N�/a��$:��?D:�t���#:�t}�Md���,���ܣ~3D֐�c��$D�~Em���-��'}�?D���;] >nR��`�@�?��IvW�>��}��ce *�y�;u�^2Dׁ�)'���2��{�'���a���`�;�^$|f`fV��`I`�߫>>nW�۾�S��[d ;T*�%D��x�*��}�����磬{��A�i5d%|�~Em`&J��{�~3D�����[d:a��i@�������-��[d]`
):��n�>}m� �F9X�>D��ۖ�پ`��@�{Fm8��zN�beT@
}j�$:���9b�}e�(2�3���E9) �i���c���$�<��{�1 yc΀&DS�{]F�`��5|ϸ���8N
}€�y��cP ��~�?�K�a��`F�W`ۨ"���@d�)��c����a�^Y���a��)@f\dd��c�H�ƨ��&��o�`Nd7�cP`u�>��)��	}7��a�� ���	aC)��aK ����)g��E9���a�����@�:�-�^ Dd@=u���aV��vJ6Um�'�-q!�-@�+g�ީc��m�^�yA�i���?|`�W��P�G��^Od:2�!��%+&�޼~
)���a]`)��C*�	�o�d
�&I��7 �?w�a	�}c_�Ad��c;@�{d@�)y�����}Z��c(`����|�ψ ^d<���nE��R9��)���-��
}7`!D�a�f{AB"( ���^�k������j1�3�}`�31����<�Ρ�>�c���a�Q�k��?�/Gmžo��|cF@ɣ���i��۷W S9þ)<��%��&� @d��}�|c_oI��}g����	��a�^]d�@]v�
�c�@�ᨓ)9��aq`�h�a�g���cB�$���%��m=

}�ީc��Y��G9&C�����W�|c�@I9�
}ǾG+�L]d�^-u(��5�^Mv*��}�Hm�H)::��cǤ`:A��P��˞5b� �iq��a�\Mv࠵`�`�y�.�{�8)A�k��;�8
o#˩c�`���^Md� f
��(f����ߪ(�����aC�;g�>) 3)�HmN&�i} �''�'��	f����@j���c! ���+|��3��<�_5)�^Mv�g�
AB�k"�xw}��cOH�:a�j�^�$� {�� {�/*a~��)|W��}�`}��}$DD'��q ��X��c7��ii@
�Ҟ�iӾ)t��a]�I�oCb�D���i���h�HmY$���ޠc@��u�X�΁�?���5�g�A�k�Sg�Jm�I���F$n���o�gӣN �`|U$nߥD�MGMv1&)x�a@$n�>�]�Y�Ġ�b���'�J���Vm	7)cg��c�")��a���%�{| ��Σ���kf`�a��oR �`����lUm`U7�@mڞ�c*��m�E��4��'& I���ݰ���&!DD�A�����{ܾJmA�a��%���9��&��$Dƫze!��-o`�'�`C�ݞ}�Vm3e�&�9Dg�	f�9D��[d���b'慬o@�<!�)�D�=�	J9�Ym��y�j�v��-���^�c�9D�!Ed�]�a�VmQ��c�I)�"�i@�cT`)7�CvUV�/��'�@oR��{?G���^�aM@�aa�+|�^�c��)��iw�e�eJl�QPe����{C�Y�`>���Z�^i~�ze��{�@����ivリ9i x�X�7�@md��cd��m ��WeW�`"
�!7|���
�x�q�8|+��m	 ��7����|@ۀP�he�&D�hX+Sƿ%�~X9��D�W�i:�n�Z�c�T�j @m�)�!
�:�)�@�c�_ʦ�y@��I �.��@{�U&| ���@��g ���i�Ao��z�j�Ob�{; �d�
f}�F"o�ie�
f����#}�!Ed�+n�E���+|]S�cg�iw�i��~X9��z�h|wD�+|��U �p�����$i��'�>�S�jei��}��E�J�g�mA����ۺ��%�o�칖�ޭ%g�v��ÿ%���F"w`�(7@�-�
f�'g�?���'�L-|��E�@9D|Ȁ�����v�n��&��`�i�^�c%���@Y+@Y+��i� �{�E������H}e��-DX�keX`�c<`|w��`8:a�o��f�~)@}eq
f������-DV�Z�(��^; �d��vc"f�c =*�)
�d~�^���ޏ-<�N�?\m
���F��<��,D�;|́�	灠c��%X�c%�,D��'�ke)�%T@}U��8؁f���;���'F�'@����g��Xm��/7`�b���
!�_aB����W�g�&���Cg� $��ke?o�
fd~%�Ym���A��$�`'�a�G��ί7@Y9� �{�a��t!EmD��`���C��&�-��C�l#n��y��ܕ@��gA	)g��`�����@����ϣ�eԔR`��õ�����`b�~~�I"��Zm{�ZmA��$?Nm߉y�Y�{$Dm�྘��'=b�;|* ��-D	�M9���T@}o��}��;|����vU�/D����ޢc%�Zm��a
�b��q��J�v���b��i8I��Df��d~o*\mL�+�!�_@9���/Dq!�cCb���G��£0�1��M�8ңa��J ��nMf�Aպ���c
�c?)S�J�j��Hd cf�q���c���3�΢�i8Omq!�c`
=�Am��f �x���i<�����_�'*��ibA�����Ρ�]�c*HdK	=|RŤxR���ŀ�+|@؁�)nC�j�i>|H���`��yw@�(ې�-���B�}A�?�{q��3�k\:��@{�F@�J!U��c�a��?Emm��&�ɀs�&Ch�<i���(`��>D��"uA��zA)D�Koe|��� +D
��(e�Jvр#7)	@�$���y���'��7�m#}����`�)[����i��i��"u��/N�	`�
�7��&?1DM���:�%[I���A�$�)|€o�f�E���'��EaG]m�� �)b֍-���ie�X"J ����b@]��oN�]���ƛb�&�}�?:`�iѢ�ܗ�i�b��oB�m}��b�m&I��ix���Xf��'�@V/�d�i7o{�Cmi�ʀ�N�Y}g �#k�ܖL2D���V�?D���Li�
}R�<*��}�S@"��/�t�{�@���	�it��/��m~:���~ͩc��c���c���Xt�<���}?^m( �F9P�9ga�{�?D�i�e����7�W`�a���A��?�"�]9�@�_�]9�i $gCPX�A �$���_C�-I�`e �i!ߩc"?^m{��i�H�$}@�-<@�,Vb�{A@Gz�� 
��.�7()��f�i\��@5��b�i#��''��a�b�{$_	2��o� 8�!A�g�%�|c �%�P�F�%?�iP�G�:���A �{�@۴i� 㡛���{&�{'ߕe�ã#�]9(߂k�0�Va�o��
��	oN� )�_9*?^m+_�-�Zv�J-n�`��o�գ,�£ >D��k-�{.�_9<�}zi8C�|c�e�a"�?DnW`#/_�-0��1`1� Zd2�}�a��3_
}zkW�wb�ՒFa4�_9��k|@��}��V 5`1��k�����k( ���	f��k�ٮ.r��e`16��W�E�,Q"D&�L�Z�gˇ����O�H :0j�#�)��1�J�р@$f[
oF� ��_9����m����F7_"Dd��|@�7��w`�#�`eT@)�#�8_ D�B�i9_ Dh/�{,�Ad:�?ͥ}c��`e��݌;_-|;@��<�_9q o��<�N�f8=}*�;u΀�?�@�,F
}�}�	�C P�{A"D"�_9�!ZdN��i)v)( ���	�
�<�q��i��_9���}�`�膂_9>��ioFף�o���A��#��}?�)_#o`1�(}c�k@��i��AaU���&�kb���q@]d\��hTN]d��Vs�k���J�ae}�Cvf �iU ����}�#J!�)�ae����*���HoE)^�ɣ������-JOg��O�aP ��:a�ʲ�o���-9I :<@�h�)]��yq@Od�@)A��{�}b��8B?۽C)
a&;e���W`�G�KmD_VmI`�����`�}V�/De�Cd:A��E_dwn$|��
٘��?:���F��S@wUF��-��%; �by���7�&�	odž�o`�S`��ѣ�I��i�A�y��"|�G_Vm�"nH��a!��V���I��J�of��z��
�Ko� )L�HmJE��'��}���R��^Q)w��c�bM�:u7`#DF ��N_Dd.��HA�T��9� �i� UU)�>3��ae����*ADd�Ϫ��awY�	$�`��`۽|����șI����:���)���!ۿxEd�����ae�{�O�CvPߛkQ�}l�o< �m7�Cv�}�aEv�A���i�}���R_
f%`Wm`��w�Т*�o"DSDDT�o~�
 ��}~	Mm:��m&�M+���]�M9;$|�Mm�"|����U��-��ceJ��V�]�M9W`8L��[�WVmS�}��{`�	fdw��ce��}3�o�Bj�3 �<�ou��{R��|V`�d۫�ۦ)T��a�	f3X99�I{ ��B���dwS`{ Ude���(X9����A	f�i�}�)!U�ce��)gX�}`۷��F"`�䶔�k& ���R�\�7��8|��ʀy���;X9Y�)Z_$|q�$D�!\dg��+R�_��v��)edw4�Xmq��}� �%[Wm\��i�$�Q�c�o��x" �&]det�݀�
�]?��N`�}{ ��G2D$@o]�$D
`Nm��^۲�F"��8�@N�kR`�WW��)��&D_��-�A
������ce���a}��cRoN�d9[+͢�;u���@�~������-X9Y�_��.y`Ӛ`�X���n�`I���H��.�a�c�cI��Kd��ܖ࣬b?X9�de_�Ym�9���B�	��%�
�cc_2D7&:J���1�)|�ш�8X9���v�!ud?DDR`�}o�o��2ÊL�'e_}�@o�"�i!!}��:�c �ڻc9)eef�)��_����g�a�Y90��xZe��92H��D��I��h?}o��W��@X�A�����'D˕��A ��2���V!�+A�!g���aW��5�� gi?�{jۍM�'B��aޡ�k_�%��Bml�am�'D
 �i*�!u3ݣ����37͢�f	fn?�a:��"�������� �ݣ���A��o}J��ݨ���>!��V��#��p_oq�-W��yg
f�����)r�AmS��i��k�|�T��kBI����Hd� ��-J�%DV}J�l$��b~Ń�y '�cI��-gH�ЪN����#�i�+�{[�q���Zm%@Mm� S�UA��\��E��@i#��6�k�)��"@i#W�{����n�i��ʣf��sߋk
��i�=)NI"I@��q	)��Hd��	}=�?!�&|����&��t���AfeI��€�pu�#u���`v��zw�m8���(��;|(��YѠ�C��iaC�?x?��.����Z��m8T�iwi`��)f@��	 �-y�;|z���`:D��)ڸ�a�Ao������zF�B9��&q��}!e��'<n!A;�o��o� <ncC$g|�ր�͋k��zev`7k�f� �@6nK��͂�|`$à��X� gi ��g���W��{��-aC�-I�K����cΣI+f�ې�
o|`�^���|�'H�<D��#u}�he�]e"<|���ܺ�5G~=n-����x�q� u��'Dk�m8?�c|����=n���B�	���>�N�����{�o|�7�ť�heS��-�$֢��he���6��0ߣ���-���iS��{���Ā�}�_�a��? Fag�{��@/u C/g��!gj�3��m�@`8;@$u����!\��fC�!g��e�ʢd�E�9��{��}�@��3��4a�i��o_#<| �Am@�]W}�@×g �<`�e�@}t�z:A3lf?���G�1Z`8o�JdG�hed��mu�ט��c��}��?�_�cK@��&��!`ˢV�Jv��� �:ʠfU]m�a㢘`>|&`I�_�.��7&gM�a;�懿:��$!���@�)W�@m��66��h��z�N)Dwb�#�=|V�-��N�<@)D	�)|���a�A�R�Tl��ށ��4S.�f��zv^mR��ށ=Dn�f] !!��a�u�o`��f�E� �-�?)T@�cv���+�(�jǘ�`� �U�h�Ǚc���E+DϜmߥ�;��i	ģS�!g��E���:'� � \��af�g����f��f���y`���,D �4(��I��a*��%� /��WI���:`���F9=���	����"'u�$�"��AMv�� C(�(�"g
�$|V!ۺy���:'��}
@�,��m1$)��M"I+Dg��y �ai}��%��ke�m#>H�/���9HMvT�f�i&$g$u:a�pv ��v ����aJa��PMdP�ә�
)���a�_MdA���Tp��a`��w"�9%��aJ��5�iW}R`9z��3�¾�t��g�?D
��ࠢ��fO
o;WDm�`�a���)@䣸

o��$|��o�`�a#�f{��'�rX�`��ߏ-ح�cb-D`X���}>��}R ��e)�E�a��#g���@��$u	��u+'g�a���9D0����K��*aU;��#uW@�-�i8 Zv%��ߢc8me&�ȣ���a�#���C�)��-�_}e���m� Q�i��}0�l#Ii�Tme�+|�&�A
}I��m��o��)k��a��@d��-DN��ߚ�9DX�(u�`�?#Md��m]@�`g ��fh� �f��}�jtb�-0�p�;�+�
b�?�#ua�(g�#gJ����*�
)b#j�F���~�_�a%F)!A(�Z� �q�w�@�a�a�i]`�?dN� D����I@
}W�%����(vR�|�?gme�@
o�_
o��Bq"ۯUO�(����p�(`��fe&����{��c��ae��}�  �
�R��cN&�iG۔_%� ��}l��Hi)�(�_�i5@f
�~1��	&]`\d��Av���iA���=�}��c�`�ܟ\d�(u�_�'��%Y�k���?mN$:AI�h� �ynenw�-N ��J�G�n��݇�{¢_�i��-f��-��{C#ǩ�_�a�ne{Ef �a�A���/|��-�_�'n��ݦ�Jm!Dg��'� dne��'q=�c)�|`$� �;�b2��i;�F9�S]d��aC�-DW@T94�"n��-Ѐ��AdO��ig� Va�#3٣J�z�I�'��c*�;n�����E��V�;n�z�0��(�_]d��/DʮCv8g���	�o�ݕ������3Gmt?�Z�'�_f��Cv���z$F�"`#DC��j}�J�+!�Nf�U 
k($2$'d U!
!)2+
#

!#5!
!
R	n�R(;

-				�( ;��
-
S
J
-	�1
-d�	'Z5-1�%(
	#S!1!
n
d*!)'$;2
(-5
-
n)2S!	5	
-+R�2�)2�
U)-�!�5-
5	d
h
)�
+J
!	
$h	
-�5dU		d('d�
h
2�!
dU521%R

�!(!2?S		
2*	�'k(J1U*JS
-�!#
	
5

-
$*d5

5	�?	��U(*US


(
!S! RJ%#	�
5 
�	
	2�%	
R%!�
	�%*J'�	k!�-U-J*� kJ
	
�(R1�5
;	
5%?�!5	k?�%R+k!?	
+
`-
�dU(d
?
d�#J"dhh
-)�5U(J
-Z!!dJ)
	)�JU?	�U � 
*U)	2!;5
d�#		
�k+�k
�*'%?
?

S
	
% Ud*�)	�Ud(S(S1;(%(
-�;?�
2�k
;
�;R!U5�
d (*��-d+%?�	
1
R %Ud
%!��d(;?!�'	
d
-
;�2!
$1)$-kU5'5d�Ud!%#d$;#!

		

!�d5(R?
�%'�

+
		
-�RSJ%k(

R;

-?�5?�
-*	*
-	�%+#

Rkk�2�USU-2�J*%!	;�%?�k(�?*�k5d( -�

*�UJU-nJ*�
	
	5'2	1U5S;(-?(R!	�2%)1
#�?%R
n)#J�*�+?U?
hJU
d!�k)�!?�
!(�'+2?�2�5
�;)�#�
!"
d�dS�)
��;R�!�%5;
�-
#hJ�
'�?!�)2?k�!R

%) 	
-
 (%�!*�1�
�?JkS
#?
�#5
J�2+
�5)�%#�S1#
	�?�
;#Ud%Udn�5d(�? �%S?S'	h%?
(�
�? 
2�;ZJ;	2'�?�*'�U(�Ud5�
�Ud
%	
?U�	
'�%Uk	%%!	!)2*5%	5)
*Jk
�2�
	�
)-�%k2?

*�

' ;hdUdU�k2�d
?	5d
	) %+
!J R?

!)R�U� �+'�'+
-	2?�UR*	��	
�'�?�	(�k
k?�)1(�
�-R�12?!�2
#��5 
-SJ�	
!R
�) 	Ud?�

	
#%hd;R;?;!2	�%U#
!!
?
�! !!;�)-%;
(!%
� 2�
%'�	��	#�?�%�#�1�;�5d;R!2$)�%	�	?� 5;1;�-)	n!dd %?�?�k U5!RRU(�'#
	�#! $h5
-
%;2?(%JhR	�dnU2#'
 �5+
!*(R#	(!	
-

		�25dS(�
?#�dU%�!"

-
 R� h

		?�
(Uh!*(#
�Jd
- $�R?-	�k
	5;d
?d�U$hU
hUJ1�2�U5(;k�
!	-
U;kk+�
-n2115-

-
�	�) J�UR%��S
1�2 *!;1Z?dk
��	
�'k)�(
;!�?
+S� 
	�2)-
�
#1U;U
Rk�#n5
)(
�*R#?;'	
2
	k5(*�(5	2��
)�;%h
-
)2�);?J5
;�
	�;RRS?�!;�(!2�S�5kUd)5
�	
�;?`!RJ�(d5(#


�-#-
'?

U
R2-
(US��!
)�;5U
�%(	h5
�(Ud� 	

�
(�!R�
Z
U?d�*;1Ud) (�5(J52�1;d�?
U 5�	
�'?RS*	!��2�*(5k
�(
1�2 U
$ %U()! (2�
R
!*d$2�SU�' !%; ?!'�	
2�
�U5R�
U
-	2�k�
�kS(��
-2-!dS?!( R51-
�
-��J?%
	!S?	� ?(
5�

�*2
nkd		
�5
�?SR
-	
�* 1�R5
)-
5�k	);�'��Ud5!*J�5;)
�?
�	
;#!Rh5d�?%2�!h?%;'S�
	
�)		
d
�;?#k;�JS
;Sk��#US#
�

-�2!!(

%R;`�?
	�	
	U2�;	k!�'k5U$!U�	n?2)!d;�2!!�1$1-"
n!
	�
Z1# �J#�#J`!	
kn'hkJ�
%1k!S�*%	dd5U	!
2
%R		25!dR-�%!�S	�
 **� �1�
;�'�#!'�1'(��#1J%�)�?
	R!+-Z2��

�
�	�k?
k+?	R*�dU5?� %
�*1�d5%k-%5
2-?	�
� k1?�
	Z	?
�?R*	�
	S
-
!Ud�
��5(dk(
S�?;#
#!R�(d-	
U	R�
��
U)J�52	�J5U2d	1R* h!;1S
R��#S%!U?
�	
#Ud)�?'d;�dkR5�
#5 h?*1�
)*�	
�?�
;% �*;
�
1#J
 U(S*R2Rd�#!!?;#U(%�
 k*!U# 
k�?JU
U
-
) #dJ5- !S#S

?SdS�
Sd`	
�;
*�U(5k(5 d�d;	
)*(% -
-
#�#�
!$J5
'�
	n?dR*S	#�%?
'�
*�	
%h*5U	5!
' k1� 
?$�(!�)!�?kJ�1	*#%2	
	�-*1� )*?'2	�	
!	�1�
kU!5;!U	
!�#n?5
n?Z`!hd+
��;R
	
-	5	
#k2�2�k'�
J!�J!d�R1(�# S�%S*
-!RJ
-!R#1
S*;d��(5Ud**
!�*
�1'�?	)�
�(k?�S' 1(

5 )#d*;
?#k5
	#'+�

�( �
+)--;1*�;�(
d�*d�?�
5?!
�
-?**
55
U(!'JU�2 k�#

�d?�U

�
#)(*S� �
d ')!k�?k51?	 �-+!n5;��#?�*S	!#'?#S 1)-�!+U'+5);
-'
k2)�!�#*�	�; !�'S
2;k��5 k1(n5�
!?�dk "'�U5;*k;J
1�(!;
-J�R##	!�
�%
#	;R!dU(?� 
 %;	�k?2#
k#+! hR(d2	�
�d�	
-'
	-
	�	
dS�%d-	
!�112 )Uk'US5!5(�k 
�%#$k%Z5hd�'k1)	S�%U1�+15;(1�	

	!S�!% !�k;k�-
�?�)%	�
Rd5( 5(�Udh!

	�S�
�?kJ	51?d( 1	

-�
-dU11
1	-
#d	� �
)�;(+S?�($J	#S�5R��!�
k5%�k	'U!!!R�

!�?�
S#d-)
(U5�R?�	5 UkU;k�1(	
�R;)2)�
)-�U	5 n
�
?Rk	R�U1--'S�	��
%R?�
-	
)�
Sd5;
	R2
	� %52 �(UU	�;?hS;�*

2�2	5(*�%�
	2	1�+ ?;
?
?5 	�	k?J�SJ;5k�	U?2#+�
	`	
�
-k!
SS) %
-R�?;k�	
	!!%(h!�

�*!1�	
#
	k2d!�#hJ�!
;Z
�!
	d�
�R�
# �? 2	%#��#%?�R*h S+d
U�	1'!d
-	Ud+hdJ;kU�5n'1+J2'�	�S%;	S		
�

U	!�%h!
�;d%'(
	�
;*kZdJU��'?k!k
�d(U;
#U;k�?

�
�	
	?
%�	


-#
	%U1k!
21J�
�2;5)�;n(%k�?�S1 U'�	)��k+�!!���-
�?% �hU� �'
-
�?!-
�*2�)!U��?�-%
J5J� %J?$!2�U5d�k hJ� (		
J5�;
-�5k*`5d(U�
S
	�( �	
?#U#2	�
;
%
%�U(*1
-�kd�!h�#�
	1
	�#'#�15$	
�(U)�
%*S�+h�5()�;UU?(5
(d!	
�U2h5-#�	
-
)�5(U
 1?5
;-�

dU(!
�?d�k!?	
�*;dU �?U5(?(U%	
(
1%
dUk
�?	d;dJ`*	R(�	d!U�*?�!#(R
J-)-
Uk1�;dRU%5�		
�+
 k�?�+nk	
;(UU5(�-?��! 
! (2(J-U�?S')
?	
*J	'(5Uk�%S
+?n?�?!%%( �)
-�% k	
�d5!	#%�

?�)�
	dS�Rd5

!;�?'%�)1�#!Ud)!;�)�!�1k�kdU!�R(	

'RJ�dS	
(UUJ�k-(!�2-5�5!%)*5!d?
21�	2J5U*kR�S5%Ud1�;�U5�d!)h 2�!%(�)UdR�U;d S�#�S �
%'?#UJ�1�	#�
-
51�
�1(5d�S5��?JR%1�*h%��-U2
hd	J
-	!	
)

-J! (*1k;k+;�!-
5(��?1�!5�

!*S#�5UR�2(�5���25dU	
�d)!J5(


)-� 
�	
?
#?$!��'2J
�!R�!)2*);
�dU�;1-d!(#-	J�1)%#�1	�)#�(5S;?

�?%�-��	�;d�	�
�d2�!)�*?Z
-�'2 �
2!
�
1
�(5�d?
S
?�1)S%'

�(5dJd!�
	�(5R�Ud�	
�
�-$dS�d) �S15�	�
-#�dkJU!k(#+	!?
?�J!
JS2`?

-!k`-)�
-
*;1!-�)-%d�	
�
h-;2`!�;
))

�';dR;	;	'(RS;!k�h!#!15R#�
J;�(!
`	�
;S�	
-�
;1�(U5
�;�5�(

�)2+�??RSU�J	!k
�k%U1kS+?)J%?�(#'(�	�-
)? U)#?	
	
-�%�	?%)'�;h25?-2�
?'�d;'Rd5JZ �S
?-
d;(!

S	�d!R�	-#�kS1	?�J�	
� 
#U	2'?�1R;2;S5�5;* k�S+�U2ddU?%Uh?;1?- J�Uk�?�#5U#*;d
�;1( R�(	
+	�J	
#	?'
"!Rh�
�d5%
?R	�!�%5 �
	?!
�-?)2�
	-
�	�)	�R!;2
!
	;U5�
S!
(5k(U5!S�(UdU#S`Jk1;)�	1�
!� 	?k5;�1;R!*�

	
�-RU++	
2 �) )%(?�(5

d	U5�)-!?	#�5?;U#S%(d5"1�(*RS
!%�?)�!h%
	�1
��d�1%S;*?

�(J	
)R;�
	�U
	�(h!;�%;5(�dU(k*#

)�#
�h?�
	U?)
�	
5�!5U

?�R?��-d5�?Sd�
	S?!#!�
�*nS#�kk�	

� S	+�2(-2
?�5'�
-%h�?� k�Jh5
%Z2�
�(	- �U
-$n#�	
-
S!d(�	�#2'12)(%;112
 !'1		�'n
J�!## 2;�
-(�dkJ�J
!�
�!-�)� �)
-kn�U#!
� (%S?

�5!�;R;(
;'�k	�'1�%
k(�%?�5*U' ?-)d-JS?�?k
'�)
-�'R�J
-
#�%5�R�U	
�
)�*	�!dUd�?5U�*?�	
?U512
-�5%k�;#�k*h5%UZk�5�'�?;d(!(5d�?�*!5(�?'*1�1 RJ#�(h(U�5
-�(*	?'*2* 	
-
1*�)
 *
*1;�#S?��21
5+)
	R1'2 ?��?�1) (d
55d)�	1�*
!(�2UZ?
#
#-
�	
-)#�
JU�
	�
	�k�
S	
-**dh5	5�k?�)S�kJ�(dU%%k
hk5�;	!SU2d�#
�'55� 
`	


(SR*)J *�5kR-�%*�(('�

	�( )?1%

	)-
�1; 	
;�()-
(%�hd(5!k-�S+%'"'RU�(d?�15J1+%ZkS��SUkS�

-
�(�(2�'#'
)�'S�1## 
	�%d5;
-)!�-
�k$Sk(h�	n�2 !;S!)	2�
�*;�?�
(5%	�)

n�U5(� +dJUU-)�;!!?�U1�

#;!J
�
5kd-
-JU-(kS%��
	1*
2
5�(5*J(
�#�R*(�R-h51	�J2;�	
!)�	!;1�%�?*
�k	!U)(�!(?�J�;?`�h5%R��#+�-$�!!#
*�kU hkR-�* )U
?;�R
)-�)n-#;�%S%�
	)�5Ud	#�*5(+�k(�'*�U	�k�	�k;)U(
(��%
ShR�?	-�1JUR()S)%k'1(dk(2*	
-*'J�*);h�;2
#
 '!J?	J)-�U	SU�2�)
-U;%R
Z
J#S
�  ��J	

�U(�2�U;'U	R	
-)�5-
��d5J� ?5?�5k?SU!U�5dU)5U(
�
-!�)U*�-)�2'1
�
	Rd2)d5S)*�d5�2-RS)2�	
U*
2� #!�!5U5hJ U) Z
�2%	
!1�
1Z!(	�UJ!#!R �*;�;(%
#+�(�	-?�Uk)kSRd�
	R5hU)#?�-
��-;
n#!U�(UR���5JR��-�
S%�(?21$�
��	�dU;�!dd�5;R��	

-	5dk?k�1
U��	(5�#%�%U!`% %J�hk
U#;S-�%J'�11R�	5#�;?�*�?Z
n+U%(2�(d��J(#�5JdS	;�R1�kU�!R
S
d�5
-	��
 U5
��?��%1� 

U
J1!
?'
��
2�J
-JdU	� (
	� ()(5!% %(;dU1Sk-) 2U*dhk;?�1dJ5d55!5USU�U22�?�5dU '!�1;#?
122;�U5d?kJ�!k%(5;!
%
J	 !!%S�1;�U
�	5
	;�?
d5U�	�?)�	�	(�(n�
- U(d
*��#?nd��J'*U#S'1�!-2 2 
?	�!	

� #S##�	
-d?�

%' *?
Sk�k?2?
-


1�
-+2
)?�
)�)1-�5%S�S'J%
�1)%�
�
5
(�
!!5;!k!*�;5

51R2
d�!k(U	dh%SJ�R5;d5�� S'
U5	�-k5�RU�
;	5R(�)��2dk#*R1 	
#�R;-SS��	

?-)
�!�#�5!k�?!J
 ;
�%+(;%5Z(!U	?
�%	?5	

U
� 1;%U);k�
-)!!5
�*�5� *
�U�
-%U�5�U%hnR


!?)�5k�1 *5U�-)�'(
	1�)?d;d) %�5-�+
�S5n' J?Z1)�d?�
!%�
-��%S�#�)U�
�;JR'k)	+;
��#!%#
%!J?$�5h�
Ud	�!#k
!U	��5d'n1�;#*��5�%d5J( �SJd)�S-k
#�1�
	-5(! ?)2U
k� %5�#�1R	�-	
S!�1	d	
	�k`;;R�;h
;`	
� S��

	
�!1R�5dU�-5 
kR%
;�%�k!
U
�
	U
%�
5
J
#
#U(	� UR! kd;n�
?!	�51;�;�(�
*�
	5(�;R*
5	h;?�d
� �)2	*?*
k*�	 k�RdUUR!'?�!
-2*!�?	%�(*�
	5U	(U�
�J!Ud*?�%k�	
#�?
kh!J	
*R?�*	?;(�U1Ud(!%1;d?�
-�!
�J+*
-?	5	2)�
R�	
+;k#d;(	
�d5n?�S
(U�d5J?h
�;
	��dJ!�5 
khR1*�JU'hJ�Z
-!*;?)5
�1UZ (n(;U
 #+!'�%k? 	(5d) 	
�%(��JU;�d5?�%U*kJk�;d2d)Ud;J?)
%;Sd2S)
k*%�

J�
#	d-?'2�;	%S�%!
�?;;?�
h 	5R�
-?R+�	

Z5	d�#��(k
2J?�-)�	
#�*' S! '�kU5;�!!U;h%k1��UJd%

� R�-	*�!'%-#%�
?'
�
-;k�*1 ��5d�

!)% �
k2dn�#;�(%J	��dRS%%	S*
5	
Sk#�+-
%*`	
-


;S�
	
R	
!n;?
;*%1?*�5dk�-)�
S%'U?1�J�'k�-	1(''1	?� �1 %(
* 5hd  �;��		JR
;
�2#%#1!���R-	
'+�	R#��5�(;
;�5?5U	)	?-�%�Jk�?
-)	( ;;d-	1�*-25d��!- 
-S

#�d;
n
U�#� �5U(�R(5dU(!#	
!R%R($Jd5�;;5kUS12!k?)S
�(R!#?�
-�5d(J�?!
n-
-	�5;)%�U�;#*d;5'�k�5U�(S�55+dd;�Un�U+!	R%! *;%+
-?�(!�1)�(!(S�
d���15?5RR�'-
?	1;*!
Rd�#�;J5	-�	1?!%�)�-S*

�UR
 *�d-
� �	)�)	U+%�*�!!) !�1#R#�#*)

-?1;*RU#�(�#
J%S1�?!
;)dUZ%R5�+
J-;%�)S!S�U5�
R�	
;�dU(UJ�%;R5J�
5k5d?;�;-
-dJ)
R�R�
	55!(1k�%#

?SJ�(#�5?d�?(#;U#�'�5
��+(U)�d?S
!d#� %��#(;
U5�
-% �	)�
�U�;	
�%''��k
;�)�S+�5Rd�k)-) �S	!;R�%Sh�?;�SRJ�?(!%	
51%(�(k�Sd;�U*5�R5d-)dU**-
	-
5UR!	S
S
�*
�*#
?�
�
��	*�*`S*�-%
?�dUS
%�#�;�d*%!��(d?	
-%k-S-%(U !SJ��'?%
?�(1S �		S-RS!
Uk�1�-1

J�5?(�
;(�JUh!�#�5R��+J5R;#RU�+J1*
?�dJ		'
	1;

-	?	kJ%
d�
!�k(Jk;;5%?+Z	;	

-�	
�%#�%5;�5)(
dU5h%�!%5!R5�!5�d(UZ	
		�*5%�+	?1J�S;-)
�;� U-
�5

*?�Ud5nd
	�%5�5
)-
;�(	5n�#U#)

kdU;	d�'
(
	�)!��5h;k
)�
k#%*#J�!
1
* ';U�
-+
?!
#�1	(
 S	�
;-+��	'	d5%kdU
d;d;�(2
(R5�+?�!5d
(U#�+5
?�1;%�S; 52US?'�-��5(d 5(!*'�?1*��(;!�S5(5S��(`!?�SkU	
)21 S�*�!-)'� �?;k '�$*`?'�dU#�	Jh)�*'�'�h!'U�J#)�
-
*J�#
*
;-
J
'�);)1k�(S!
 )�%�5d2)
�1R(*U	-	
5 �'k�
 ;!�S*�1
	�
(�%k�Ud�5d?U
%
�k�UhJ�

#		
	5
)	5)�-dk
15�+5Uh'�'�;R;!1 �*(��* Sk�
-�R!��'1*
#! �R 2�#?##
12�*(	� ?�2�#5
	 ��'�
-' '�%'?)�%	�(%-J	�JU�#�!	�1n?�%	�dk
S�
-R
%�%
-
'�d*-?1�*
U*;(U#1�)(1�)2�
-((d
dkU�'�Rdd
�;-
2k!�d%1�h)
;)'khJ+%)
(%S	
%
 5	h'!�;1!�S;�U;!5dJ��	;5 #�
-� �% �5-)2kU�'
-U�);;)Ud�

�R�RS
�
%�ndn	
-
2!SR�*#?S# 1�'�k'`1* �?Sk5!�)??
#*�n1;SU	�U-�
!2-� *J(#*d?dk*S%	J-��h
`#��
�
`
-) U�R
-)�- 
1!?�dU1�dR%�;S?��;!�
n��k55nJR
S115d�#
-%US
	
�
-�RUJ(!dh*
(�SJ�
	k?)
�?%�h#5k�#2
h�#;U(	(d�(;R�RJ�
dJ5	5	#
n1� 	S5�1R�U'R	'-U(�;�;kh?��U%d
#�*?�5hk(d�!
�!*
�%�S
�;U
�
 �(h1R*��(
�*Rn!�R;URZ
*k��';�!(S
U�*h�?	U%��U!2��	-)%'�(!k	;2�	Rk	k�5+?U-)
?U5d	�?S�221�#*!k 5)R;J5 ;%?!d�'�?(�dUJ�	�
�-
	�
)2U)RJ(;J#�	)2�?d�U!J'�1RS*
�S	)'%;�('d5(#'�5(
d!�k
!
	�*�#-
�!�	2�2?�*U
%5��	
�SJ
%S�S��'�
�2	* 	�1!R�')(1-� 1*hd	�Rd�S�� �;?*��!;�U5J
( �	'd5�? 
� 
-
(+J�)U	(	��	�;5�� �U5#hknURd�� )-�%d;(U*%(d�1
-
!(';�k
1�J1S�		?5h�
�1 5
k?#
k?h!(1S5!�-'5RS1
?
Uk!k	�d5U%
 R�? *�		�5(�5U �	-
?	;
	�

#5	��d�
hS-(?�?�d;(1;)�	(U'!;U)-!k5d%JUd�(U 1(!Uh(
�dh	)% �
?%USR!;?J
1;5Jn�1S�'% S�S+�
��?S�h
-�1+(;�Rd�S;S55*U�%?�
	S�)-�1�S
)�S
%'���(*
�!*J1�;�	+-(�	�
�	J2�
n	*d) �;
R n�!*'Z	
��?�-k�S*5!U)!S�	
�
�	d�?

�
+-�55U%'S	d!J�%�;'2! �#
;?R(?k? +!RR*�	 	�US1d�
�;	'	�?-;	`
-
?�)U
#R
�;JRU'�S%SJ-
+'
�5d)'?5 -	U5S51S+U%(%55S?�Rd
k�?%hU�* �(k
) 	+(h-
�
 #
h*�'$5dR5 U5(�U1h(;'5
�*5d-�k1h�SkS%
 1�U5U5*�	�+
�
#!�51*1!%
	d�%!d#
 U1�5-�#
# ;
;?#U5%�
k�(5%	

+;!��UdS�#!dJ�dUJ52�U2(U`'	5�?��U(U
'?�%

	

��1�k�d(55�J�� %#��5;(5�k
-#J'SJ'�U;5	2);*;*(;1��JU5	S!R��+1�
	)UdkJ�!;1U??	?$'k�k�(hU
(S?2�khU 2d
�;dUUk�n��U)
--S-%-('!�)2Z(R�
-'�%J!(
	-�U �JU 1��'5
2%�-'-
�U5`;kd1�'2`'1�?�U?
S?�J5dU 
(JR(d�#5�+-
#S''� 
--)(�1
5S	h+)�
-
�;
n�* *1d!2�S(;1�S�
R
+�;*!*(�
+RU5k;'�k1!)
k%(�*;;kU'!(d�%#�2
	U
�JU;U)�2�R!�?
;
U;

(U!
J?!U5R�5)%U�;1*kSk';
�
R)J%1%J!
�)�?�d;;(kU�2�Ud%!R� ?

5(%% 
)�
�U5d#�
	!�	)�% �(%U�#	�
R-�*!�
d'%d	�?!�
�n5�%U#�*2�
2
�dUS�5 U%-1U;1?d
-
%'?
?';'�%
(-dkUU5S �#'?(5U'%�+
S
'kk !dU'
�hd5-�dU;?U#	)
-�
!R5��U	?dU%5!5%#�	
�U!S	U?�-)
� %�-5#!?*��
(;

	S);R(�hd?(�(!�'(
Rd	%!?J�hUd) +S? (
2d'S)2�R(*�U#;�%*
;
 �'�1�?%�?
--)
J�;?
%��5%S;
	!�)�?�#	
	d(dU�
	)*R�R1-'�)1#%(+�	d�S�U*#dRh�#� '%*5�)()RZ-�#
#%(U(2	�(#
�)	% �(S
'(5(Sk�k�?
?k�-n
	

-#)
�
2'U
5�;k	'-	!
�5J��(Z-)UJ�)
-	hk
�!5�%5;
-		5� k
(d)RJR*5�1�	)#(
�	��)
 U;?��R5`	
d*U(5�
?UR
�#� )1�';1�2`	
�* 	hJ�*U�k**)!%� �?�?;k;(	5U5(�SJd!	-
� (�kn(k*kJR-�1 	%!1	(2�(U
�*%k�
�5d% !?-�	*U�d�h'
*�(
#�;d!?Uk;!	(SU	 5?!R��dU�	!J�?�'�5U(	�5%(�
	#�5dS�U	;�;;
dhUR!U
	k%#�	(5�)!()J5#
d��


?#�
SU�dU�kUSd5
(*J%��#?	k�5d1#*�d5�	*?�
Ud(�2
�+k!U�!%S�?	khJ2Z?;1-�
;�nS�% '(�'1(R�(;�
�
Z
	�(S
�k$�!+J'5(�%J �1� !� !�* 
(%%(�
#+ 
�!! h-	
R#�h	�'#;-	#�'#* #!�(Jh1d;(�*15%(5U2�!�?#?)�#�!�!�
�R ;U 

-�
*	+�	
�dk
-�d�;� *�?? ?�?##
�5!(US+d(;5
22-R?%!�?�J5
�*2�%RS �*� ;!d-
�kR(-?)!d�n)�!S�-�Rk)�*UdRJ)U�	 
'!	S2�U5
	JR5)JU(�	� SR5U� )J
J S)5 �)
'1;�(d)'d'
)	��S2
)#�+	)R #�5U 	;�!2
-��2�(;�	k?	#� n�-
(U	  h�*
)	* U	

�5!�;d�S�dU�!)*�k

	-21�;	U15h	!#�?
-%
5��-
;h�15!	�5U(nJ5 �5(;#U
2 1	5)SJ;Sk
;(?d�?k
�
*k1
S�-�%d�)'�	)	S(�?�#hd'
�R!dJ�S


�
!'#	�-d?�	�(dh �k�!Sk�
; )2�;!5;
5�

-'SR'(!('�-kS��-�
�#SUh5?(Rd	?�;#S	h!d�-# +�%
Unh�U5k�
S?
%2�)2h	��S'Z; '!d5d�%
-�)�d)�d5;���?J;dd*)2'Uk;%UJ�)U�U+-*
%U�- �dR
d�?%
n; 2�RU(�
�%2SUJ��
�
-2�%!S(5
)-�	k'?1�d2dk�%1�	`
	U	%U(d
k�2�*Rd�)S!U�%';!)
1�S2�	? 5`';5SU*R��%S	! 1;
�%�;''(�(-)(R'%	
-U-nU(�2	�
?(5'�;�%!1�#*	�'�5�	(;�2)
	;�51 �	5?#		'!�5+��%dU �1Udd�R)�!
-`(R;5�?�k�*2 ?�+2�5�d!U
2	 	
 Sd�(U�?�)d( ! �	(S5?�U?dd		�(
!U !
-
�U5*�dJ5*�d;(!R%
�d!(�;*'?�2�(*	)dRR?�-	
�k�S5+��k	?Uk()(�#U2
�Jk%��2(	'5*+� nR5
-�!?	U?5UU�
#%R#�S�-
2!%	*�;**R%
	*�'
�*d�*!2R;	�1� -�
?5U?�5
S!� 2�d!5	d?�	d�;d?SR)�5(�k!?�;�� J
�*
S?#�%#!k�(U
�SU  �;(SUS
+'*�;%S;�)
�Jh;�%;�R�*?�R
 
�#� -R1'	? ?;�k1R1�UR	%J(;*�!
-�(R �)J;%?�!*UJ!Sn'�%d%
*�%;
U�?		!(UkU-k52Z;U(
%�S 1k(	52(?UJS�d(�Sd%*)�	
*
�) 	R�%�(
'!'R5*
+
�
'�5 2?;%	�R�#
�+;�5(RR�	
�)2	'

!	U5R
�
-R))�R;(?	
--5�S)
-R%'
?dd� 	-(
n?�?	5 
*S'!�dR*%	k!(d?!R
(
dS�;d2
�U S(
+�#
�#	-�*%%�2*?k�%
hn �'(J*%+!5U5 �2)
-!khn�k�?USJ-J"
	JR(�'k�-1	'5!;�hk-*;U�;%�
kS�RS-);	'�'#)
%SdS
5�)*�R5!(?-!--Rh�hk	S-(�U	;R?�5U
-*d�?!)� 2;* �U5J!51)2#)S	S;�(	%
*U	S(%#'�)?
*	#')� 5 d�R#�
-	
�;!d*%J�!;;dU-5'�*d2� -+-�-�!*
--5�'	%�k�'
)�(

!JR	�'n#5J !%+�Rn#%J?�;	 �JR#�n5?)#R�)##!S 15;*%;!;k-)
R-�?R;-�1
-*;; 2U�	`-
� '#1�?#
	+�#�k1�	-
-h�
-�!'�;'!+ !�
-�-h)?;
-*dU#J
? 2�(
-k�	�%k!��(2
'
�%5(�5dh
*R�%�*!'�'+R5� U
RU##�URJ; %�dS�k)5�;	

-d%15�	?�(�??k�	����
��P3����`WS���RP�gp�c�K:	p��
��)���~b
P�RpI"P�#��r�}�@ʲP��`q~���0����u�P�R�� б�P>�@�#�ق �]-!�
�" �r# �S$���%��&Pa�'�d(`�S)�a0*�Zb+P�G, g�-P��. �k/�H�00�1���2�93@H�4 ��5�n6���77=8�~b9��:��S; i
<�~�=�I�>��?p}�@��2A�bBP:�C��D���E`�F{C%�sH>��G��H@�_I�r�J�S�K��2L�YM�B.N`�#`Z�O�
PP�Q���RPI�S��]T�cU�V$RW�	\M`�>��CI�[�XP$2YTpd�,���Z�R[��b\ �T]���^0�_`Z�`�v�a�G�b�Ec`�sA�%cd�!�e H�T�Ĩf�T]-��2g<UhP��:�bi ���]j���.��k��lpN9m�R �Bn@��;P�op
�p��p�[�q �xr��sPH@l�V|�t���u��"v֟w�~�x�
���y�z&z��{�"�|�S�}Ч�~ �`�O5p�� 5΁�Se�0�l@Ђ����"��҄��b�냅0�<0��e �{����lj��Z2��U.��"��l������Í�H��c�pK����r3�Z"��ZB�p+�p����} ��.�P����"�p��0�M�����1��؋Џ$`@v�pB�pY�a+�𧢝��M �tP�����2`��� !#��Z�ua�pg^���+������ӧ��=pʂ�p�R��$�0U��������� �r;�M�霮؂���,�ְ0&#��z00%7�`4��J�@]>��ٲ���X�pZ�I`�G�0��e���0�u+-&���º0+ݻ��@��VP�r���Bv���PܾP��P�, �W��Z�,�Σ���]7����#��C�nRp�Z�� !���v�L��~��穉p���X`�s��ƠjL�A"�"�pbp�C�`�Ȁ��ɰ]{P�Rʐ����{��W��Fn1@.]�0�)��#H������0r�Vl��#� ��А��9�`�R��~����0��p[B�0�9�0|�>���0k8�"��~2��H��'��p��)������^� ��,�Bx��
��t�0vL��U��ᠪi0���pՂ��` _�� U{���j�G] Ѕ]�0��f�~pA�IkV���^ � ��蠣S�p�2���M�zjꀋ����p��^3���p����#7�Cx��`��G �8�P`,�P��0
;ʠb�0�_�� ���°��@���`l{� b�p[W`�p�O B7P��E�"��~rM`-���c� o� ��`��U0lp� ���`tv�j�$R�P�C���1�ۨ$� в{P����op��y����Y��O#{�~�V`�J�������i��FG@B��m<��=��Դ@�ˠБB�ϒV���3��X�n4�xs��7B�P���x3!�t��bAI"=��N?PRg0R��<��q ���p�5�b	av
!y��ω����A�
W��OR�0ؒ1�&�PY�q�vw�6"!�̛���aP����Ҫ���0_7����PB��GC��(_���Bqx��]<`������ʲ��ҽ�"��]��B�`@�h �����񑯪Py3���ɉ7���@}S��!��áG@�Y;p�S8@��F@�rA" �:��a�de@{�����N3�bM�>mE�a��Q�c�p��>��cV�80#��)���0�V Qw�!�٢^`��&ݤP�"!�"�p&b��>�#A�k�p>��S$q�#{P3�%��H�]_&��Ӣ�$"��~���]�Ҍ��B'q�(!>U��)�_
*q�Y����$�]�H�����`�̠D�a�C�P�B+�C�,!��-��wO@JO.�D`�c/5
����pN0!�q1QlR2і ?0oo;P�3�ؑ��M`�4�1���5!b4��Z"%�a��]���6�re��7�oU89�$b:�%;��eO;Q�;�tp #<0w��@U<�S�=a���n>AB�Á����v�B�?��P��3������@}����@1��AQ��B�9�CA��^P�8����^4��T�i!�Yʀ�HD��{@�/Fp��@�P�T)t�RO0_�E6"M�z�F�f�G�4����ZBH�ʂIAlRJ�Z�KA�pb��P` L�'���?~;�ejr��J����� ]�M�nN~�q�"԰�ja���3r�0�rOq8�(���P���Q��RQ�SѺ�1��Tq��n�[��{���p� ՈUu8V�@4A�W�cX�u�;`H��:��O��Bqb�YܳZ_G�p�{�[��,QBy\a������_�]0� U�C���t�$2$��^Q�[aZ"�t+_Q��`��ZP�r�p�{P�
=#L�#�h`<a1�Sb�Bc���q�����R ��d��X�ZB{pG?j �e�c"*�Qlfq�6g�8oM!�Rh�zq�r��i�
dl�b���4��Z`�����[�s-8�jaT�kAg��X1l�Gt��Bv`�b�j���I� qLma�
� SG��n1m�� �oq����<`s�pA~3;�`���qA��P~|`ZB{`\lr��Rs!��"1�77��C���tq�b���@��mu�h^v�t,w!UÏ�]���xq�byQDO��V��;�;�z�tT`�� ����j��zu�/A�be0��?@(,uq2����{�H�]!�$��&`�T|1h��rP��s1Ww4A�"}�~�b���z�AZr���5�F���u|�1167P�B!Ъ�1�փ_(�$R������;�׊���]��|Sq�R���rDP��;!s�DP�V��Y<���L��B���@�����Q��P��rP��5��1��?���PvˊyӋ�M��MPtA�[q�Cb�ؒ7����P��1L��PHR QBP0�^�Zr1E_{�+�Q��A�	e��ܐH"��a�B/�e��+��t���Rn`Zb. �� U[�q�����t`Z�s��m*�a ��)�����J��!�6{05���6Ԡ����%����/1���Hآ��ِ��,��2��S��ת ��E�ecb�q�әQ�����b.`ڲ@k���Cq�Z��S��M p-������8nM ��ep�pL#��~�i�|0pR�:�Z›�0�<@x��"X�����B]����q�b�Q��1gXa��N!��3��WЌ�0g�Q��`�$sp P���s�Q���P��p���Q:����{��ӏ����u*��H"��Y*p���ؒ��<�`����qw�p�r`�{[ؐ�c�����1$,���i�a]�qʒ1P�e�!~9����?ǫ�~r� l��䰬���=`�3��41!����󮑔��q�_ lr�1�L�Axs�҄ ��oฉ��؂���{��;@,;@"w`�C̰'|�J�p+���3�a�;�o+�1�b`�')�q��p�v$�!0�qHι�?�q�sQ�s�!��]�A�Ѫ���@��q��5�@5�pHͰ�;�O�dṲ ��.��?C0��"���?30ز@�H^ �����Ҿ�~rN�BF�����j,0Q�qʢ<��
UP
iT������Ѱ�:$@u����+0*�ʲ�q[�������b�0H����@Ҷd��As��q��i`~�q���e�$����AqN��ZP�i�P��9��eЪ"�`��đʢ����q�bv�~�!��T��V2с�7p�� (���8�a~�P_O�1��+0mns��!wqu!�e�a~�IQl��1�c���̑�9Po' D�/a�:pP�����M�x5ƷnA�bxІ#6��b��6�TpʂΡ)a�!�>�`Z�P��p�|Ё�#�A�s�n3�!���@l���ӑ܋F�֠<�+ �b)�q�cA!���q$�i\y�I� 0C��� &��:�ց���@�q!�K�a��p����[{�p`h�ٲp �luA
שq�� �J�m�p��Q�w@��
0���aa�M�ua�#l`����@����c��ΙP&��`a<4V��ʲt�ْ���E`Zb����u��p�,,`ڛg�b ���r�V�1����|�q�cGQ�R�q�B��y1��{�A������&��`A4�؂Őg�D�";P��݁ق;�����`�fQQ��L���v-Kl@�m��
@�:A�R�Q��� ��p�= �P�R�ф���YA%�`,��ق�q;�sQ�1�Ղ���7 Q�.�A��!ҙ�1��Nw$���$�R�@؂�@�R�5� �M~:�Zc!����Ή`���Qt���RGQ�P�B������5 :��q�s�J��\s�a$b���s�>������60�u�Q�r���Q@�B�P�T��r5�~�kAvNГJ^�Bq� ŧ���i��ϻM�I�T������s� �**�"�Z�7��bO��c6�@����"�8��p��0�5A*U�!)tW�B�2�%�cR"��v�&qU���0V0p�����"�q�"/�����c��ꓵ@���ق�Al���3�5ϵpl�����@������c�!(�ѷ-���u��3����M��g�;��A+"q�}VP�R"є��QW��[TP[�{Ђ� <t�qw�a�����e��V��1p��!oSr����
�-�
b�v�T��`�B-a��a�2a0�7����1�b% fCpl��0��Q, 	jmQ�]TPd�90�r]���s1�)��{s�����B�p�r3�� �h�������!�V�뎀
p�1����Iq�2�1��!0�&��S�����G@�b��b! �I�r��1��p�r��]��1D������s{���/aZm���I�����|ì�<U���Šw�1�l�G����.h�a�!�b���piܞ���U���`��	�Z��0��p�žᴢx��RڀSg�1Fg�x�ʰC�<B"Z��q�A���saQ�e� p����u�Z��"; ����#�P���Ħ��R���Z���na�2����0��Q�#����q�b���{������-��-p�2p|���Qw�*�;.� ���c$������@ơs1*m��� ������eP��^���"Qp�B���r�c�ʢɐS�Qp�R�RW=0�OQp����DC	��B
��2�"D�L.��ьCA�6�{�����2;a}����b[1�^�[�T����p�[�p&R�����ƪ�钞it��B{����C��
r�"����D��s6�'e(Q�ҧqn�-$��@k�a�֢,�D�Ց����360h���BH��⮠�ðh�B�V@���`XG��� ���������p�"y��R������B ����	���NqzÐ�ʒ��CTq����~첑��Rp$���b�p�rqqY��J��/>l`ڲ���B:І�E�z�2Ap��p����2���ҭ�s�=Az��1�p�W(��Q10n\���о�x�?����Bx�l2R�r�`xB%P�֧��TR�B����"a6�w��u!-Q����G��JP����m�F��ps�"����M�
�1}k�`�b`��`���ŐS��ai�.��QR^CA`jT 6p��<Ѐ��ZB����%�R���09$4��ā���I�V���u��B` ����i��[�e1�W�Rj$r�y#�Q��ޘ�����S,�-��m"籐�
���nд`�rp�*�"�2p �15�r�b )�:PZ�x���6h�ѓ
�?*�Mp�`z��rG��J`��!�� �+#�\<���2"�:.���1;���"b���A��Dͬ ���R���RͰ���NIQ���6�	'�D�]Z���"`�)����Hpx
Ԡ~�O@�S�y��%R� |u��2SA�B�cM`s�@wU���\㑦� ��B��u�@������I$�����Al�T�Hb1��6�[�����az�1����6qQiى�ޝ@ʊ�!
3�<!r�b�l�����;���hЫ`�C`�pP��
p��^a�#BqcI�A�{0�9���b<�xa��"-K���%��@4� �֋q�Ep���Y5�˒Q)�^�uZ��9�a��"�"�2#R�C3���{�*Z$r��{C%Rσ�������4�q�2��jA�"���;!�6>�%�s�@���@E1��,��v H�u�*���7���"qb|�^,�� ��geA�B�s��=f1�sad&�RaP�R�",���5���J� `��!r��?��V`9D�C�'�S��ё��V��{1���0f�`D��p�qL(�_]���şp���#��k3a`����$��!q�C�:W���?��!�³Qlr�����r)�a�2�sDplb�P�˙P&�Sq�r�h��>��ŀ5+��R��s0�ʂ9���̢A��/ZŒ�Pb�qlR��¹pT7��7��r*"���B T� ��+2��eq[,rے����$*�ѐ����!u�`�9��B���-"{�.�R�1��TpxY�ܼA�E8p`[Bqc/�P0���0�>/�~b/�� ��®AI�g!� �@�C���30��0=����l‘a�[o�	f���pQV#��z��p.^ �W�i�;`~��0�b��3;�Ƒ��"��:b�W��Q��y�s0RzS%�C�O�2��~�ܰuٓ@�h@�ɐ�C�QiBQ�ҫ���l���YT1��W�ހ��C�l����"Q[,�1r��"2��r9�sPDS�"{�����"3�R��[��pMu�p�#*�7Б�!h�42��;���5�[�T0׹W���"S��Ea�@��r6b��6�o���%�H�:qe6@b搢㲡��Py�7�D�PG��%�;����p�������as4�g��Rހ��B��-828/@��$��Pd��0X%�@�2.�r�Py���7�̀�y�7,{���Ҡh�4���,Q@W�@��9���b�p@��ZR�9��?:�]G���r��2B��;©��t`�r
�ܳ���_<2L%�'	t�2��M�z>��Yce�Z�1O~�����PFvpآ&q!s���퀑r=d�v��Ƞ�E�֟ⴒ!��*P�NuA�Gt��>,���?"�y��̀٤��Ӥ �2��=��c/$��� h���@2-K�Pk��PZ��p��a�J��pÃ�B��C����QZ��I�z��!�����A����rB�.��"�P��T�B+���p�ԍ/>D
�~��q��EP[";|���=^P�󇡑�	�p%"��
�3R��p��C�SL�uQ������Dr����ׁ
E���5��s���C��2�nb$@fܘp��EB#F�btp�R��v~;T�}>`��+�C]P2!��Ő��C�6�MHE �=G�r)�E��	AZ�@!F�!�V��_z�RH��"A�2a��p�@����N�Oa�b<0f���R�13�z����`��P���1�B��%p"^s���6��q�c�p�#
�[�D��B����r�f�%�G����7�� ���I�6� �����e��$A٢P4�,�2�‚J�a��a#�r!�k�J7K��-���J���ᒔ����Lr��v��s/aꉭ��B���!n��l|]��3�s�Dr o�P��!��!z�a�R�����bM��B~!b�P�5p�ONҔ��*�G!Z2A.1�]NP��NP�8OBW�A�N�`n����4�Y�P�hp�# ��qr��_�iA�Pʲ���4��Pn(A&fTPZ��P�31�P��A�2�����8�0m��aq���Qr�2P�bAqy�RkM�G"�\�R���Ѣ]�����"�P���abʐ����"˰�i���
H�W���8P�葴���u�J"Q�vPK!S�?�p�VT~2Lp1BM�U��@��`z�OPZ�A�d<w@I"�ц�%�T�pl[�a��+SL�֔ �t�p��:�=�;�
��M�U��#ܠ�N��ʂVRڂ�$�p0�W����A�70t�;�uX�F͑+r�x�����#�d5�"p�Y��-!́��G!ro�Z2v<�"�I��WZ�`�Cy�vDP��<���u�>��ø�k������Y��xc�b\[�r���u��C\~2�Ш�S�nwV@Z���g��+�����]�n|]1��^r��%�eF8�v8�d�:$���.��M_B��j�R���{@Af��ղM��mx����"�y������#r"`Ҳ�Q�kqʢ0ж�"��"n���ސ™��Q�i֞���C~����+��Qʂa���U������F�B逢��p�F��"R�~"V��:PB/q[��E
�x���T�`�7��b�r�c򹴉�A�qR{p0,���fQ��ġh���6�'�d�G#ߑ��rP�=Xp��+!\�e�mBi����P�RM@�H;@k��`��/���~�x�ZR.p�R4PZb��mn+��"H�fra���iD@�%b1HH��sM�f�V���@�r^�"���"w��A�f{@�|�^�=�M������!�B�`6�� lRwQ��7��¨`���b�re��2v�8Д��2"�i���tQ�2 h� /� ��K���7�8w��p*�%Bwp��e���t�q�T 9]Z���"��S����6�ސ�cg~���x��$�+Q��x�A��� p�Y���M1�h������@s����0�g~¢�8[!r�b���Rc�AXc���{�!´�I�4��"W�~`��*iB�2���"�1]/>��4 !?�0^��wc���p�"瑢�z��r^pʢ1�~�j�mdoяk�Zbz`��j�<@E�w �V��꓍�wӑw.��.@9�7𠶜@�Sl
�r�Y���$���m҆cՐBs͠S�!�gh�Z�R��>m�����H_��n���H�o�r�p�q�c�0Fs�@@�;�!T�a��S;Q��
 2Ԁb&>q�N��]ްK�����[2����^�����1�nC '"�Y�pr�JS�q"{C�~�.��B€���A��6�����mB� �
�`�!�axcC����C�`l"����"+҆Sz�
��@�ƌq����c��o�X�[ʀ٢r�BJr���Rv�{�uї�+r~�"1�k&A��ц3$`��""ϣ��I��3HBu�!����2�aMqs��`t҆S��D�@�cB"˾Qxb"*�Q��;�u��,�/G��������>p��{�1*\2s;q1��#e ;V��"�j<4��R^�[x���vRcX����c�$������?�["��$�0�FM0ye�ŭ���P�cx��N��g�{�<4a���w�w�^e�0'��pJ��q�M`�Ϙ��Nx�c�òѬ�ж�H Po6p�bD�[�����t`";�Iz9@�2q�Y5���\�=�!�%0#IjAX70��{Pߙ�P�y���
%�U�=�n�y��bGax��Б�ב��aZ�zr#� i,�RL��;���r8АB{��<`�<a�{2}��a�g1��1�j�B0��|B�#�aJ��A���l��C�@��4�6�m�wY��R�ڢ}b��ΡUp0��nW��r���°%��`/P�HBI2�S
��x��Ѩ���� ��k@ܶi��Ur�ݍx��*�P�v�j�~2W�	Q��#~21!�3���8�� �?�����w�C�R�M�<k�0n�� �����IGq���1��O0d\-�B5��"6!�A"�ub�R1��a�6��b6�S&����`��Q(!�>��p�"}b�^%���`n�U����q���8��ӏ�ʒ{0�绀cx���������@�e5��D�� /1�b�ýr�"��S������8�R^�Z�V�ʂ�H6�ʂE���q�r"��pH���C�,���@�R5p ��p~� 
����� �Sa�s��[�Os�A���^jp~��`�r�Y�qO�0���QoߠL�OpJb����M���Дs�R�Sqȣ��8rx�z�j2�^�Z">Z�s`�”a�R���1�X��<Ҭ��2<�3ap�ME�Fь#3 �Q�b(a�o�����s2�5�l�e0NL�Q��P���"�� I6�@��F��ReP���]��i��Nu��Pz��)�\@�,0!fʢA�b�Γ�p�)5�$>��~2�цӤ@C��@Z�0�ß����b1�wl�ᑂ��q.�W�Ǡ�y�`�{�5�ZQ�⧀�r9^��b6�Y+���_a��p����E��#V��TA���`L��K�$@� �mr�����R��6�����P�2uІ�<�������61�vA2Z��c��c����輔Ѥ������ΘFc�
`��M�	{� ����1�`�"Xa|j �b�P�B  
>��&l@�R �t���F�C�@��VP$�]A��b�Cq��/���q�Ê�I�@2��;�� �25�6�8��rD`���c�m��?��곽a�"%�v\�A��{�GU4qJ�;��V��3�^�-�]o%�U��Z�E�,�`�"�����@�N�"1�X�h����2����p~]��sR�]UM��n��Cl!R�p�r���MP[@��:"kl$S��b�����C��ñ/��VІ�`r���"CA�&0v, `Q�1 ;�4��
��.���[@��\���~R5��VK҆�GAl��0g�G�ZBVp~�v��P�7����!�"b���ˎ%��4~�N����p~�uaC3�IcV�m��`n�>p&���M���q"���]B>l��b�F�1l2ıf�;+�oH� l���֬qJ�1alo�R|�`��=�c�ǀ�v`�y<P�B��@��p�$@w\�BӖ>Q٢ean2���6��çP�Q���͠q�p��R�o27z���@S����5�! 7���!"ap$�w@w��p�S��E³@����@PA�2���A[RHf؀볪��/a�R �[bpp���5��5��p�"єS�0}ˍP��9p~B�a�;�[²��C̀���r.d���M�P��gu9p~"�?I{��9����2�`�r��Buq
`J����룓�'RP�Ҝ�R���SHA2�"T�E�e������ן;�A#�q{=09t겒��k�
E�Z�d�q0��0ܓ2q(qЕ�]����a�UBRp�l��
�AIO`��FY�}<x@�0�v�hn������G��+�r������
�a�H,���ހ�e0������0��A���2�t��A�� �G�N� |��B\�ٲr�C�@�l–r�b���P����S7P�2��`<�PRu�
���l��azYBI�mr"aZNCΊ!Sn�`��0�{z <��p��x ��*�(��%_�!jJJ�ΓȐ��6��ɡ���u�S����T���|�e�+�����
~™C2і��ʂ̰M�T�"��6R�҆��m�U0�"U1$�N������2B���?�0�*t��a���\���^q���`��3��S
P����Пp�Vy���uܲA��2��kG0_�A聆�Oq���a3����"$����T @S�p��%r&™�mB������:�0Q�A\��q~B;��GBˢ�����!c�`^|�r|\+a�����GH�G��������$��q
�t��1P�/~�Q�{e��8������"߂�W~7�sM ֖O���H������[�l҄�Is�C�@a$5a�2�2,���+~�N�V���01p�81p�k1p�I�ת4���ᐄ�QB�6@���Я-P%���Z��ц��aqnC��9Za��% ���j �B��R�0mepn{a[2���Aa��`���m�h�qR��[�3��p$c��p���x�!"`������0�z

�P�"Gp���P�¹�I�g����0����:am2u�B��a�S��~�yZ@w�A��e �]%0D0}����pl����[ \���;��MA%T>�~B�pb4`e!;>q�ԑ�S,�i6���G�!3/�Y�����M�p-=�2��~�������)����R��Oa����H��S0n���働a�s� S���
�5�U���j1YJP�-�����I�=�mbv@7�G�`�JE��ДC�m�v�ʂ�a�M T�TP�ҍ@�:�ӆ�	*�xCA�S�b��;�^є�V`�BCQ�0�[\׿���0JZ���� ��>�BrM������`�����z�H�~`q����c��,AIBq�I���[�>�uq����˲�Z"�Q�"�@u���X$[�*�́C��Q��Tq*�ݒ�"���>21=�6��礀�*��`�yᔃ]p��԰:��R|�[�no�P��cqf���rp�y�����vTP��C"!�B��� Я��@�S�q����
.bf1�L-`�C��{�f��ABlµ��bIPhy���2� ����ߤ��3u�3��p�Ss�����c�@[�D��C6�ŒX��R��%=�3�&q~�q�병Ѵ��$���b,vri��X��`������jb8@��G�X��RJ�ꃒ@n�r��a��	��
�@�|�p�"�:u�&P���~2dp�#7�B���f ^�G���z�����"��`���Pc��{�����"��sq�⦒�30 ���B�C�زV���
���̀�
L�1�$p�Cp�-��C���m��B�9G�s�PC�P{{b����S�� $���{Q�bpТ�"��j�v}5 o�ɰЏhP0�ʀ~�82��"Q�"�p��E�>WN�����ŀm"���/�#1�!���F���e0o���s���M�G!zsOq�;���3e`
��� `�1u�
H�����Q�(��2@ٓ"0rpt��Ю�0J,b`c�M��3��Ga�_p�r�s�b�yc��Ǜ��繄`�.O\|��r%�	2Z��C��!;J:@�3Wp�QЏz��a>�c�1��
`~C��q������P���˲����s����S'���s1������„qR�2�)��+�Z��p��ߌ��;��q�?���Pr��R��.��pcArS<��B�%�PI�b_��mR�b�b/�{�a�f�ϖ����Z�5Cs �R^���Ҿp��д�6Qq��Q�Ak^���q��e0L1��6!���r�Д�A�L,Q l��p�21��2�`nBސ�4���bE(� ֥ܰ$R��[R��bm1w��"M�pz��~rv`�&x��V)p0@�ʐ2��2�
�)��T+�Er&16-1�c��S�Y� �Fa�w��rm(Q�p�.��b�@Lsa��@D�e �� l�ޠ׹a�r�oqn~2a�#]p�Bs�dd���"��/����!�������}.���UB��DAq��$��1�G��̸~Q��u�`��,�P�,���;!X$  L�oA
�E�˯�,�{��@�D:��`C�7�A��T`�s���¿���[�!S�a|�1pƍR���@�HQ`��C��RE�
��̐����A%"xp�_��~��`x��@Ny�0�J��vq�ֹB�T�H2r��D8l�_�P��  Ӽ;Q��`�ˑVC"Q6һ҉������Gpr�AN.�a��1{0�q3�;R�g�rb**G ��R�R� l2�;��2���#����Zݣa�r�Pi�`~B+�S�4�<㘑s�"l"��4ۈP־"J���L6�2�	��rǑ~42U\a�����sSa�� �g&�p��pq��4��������mr���юe�q��ZR]p��W�3�p냩�6mr��yP�Ş�P剩������⏈a���`���gm�k\����Pa��aR�lb�@}C�$�AV�H�mvb��0�]��?q�r�m@a���M�4���>�-��+ll����cj`�s?!|�a������Y�ZVp�"l@��g��1P�_��s�;�M��z�Ї��lrpPT3��EA?��2YQ��G�w<�*K^�Q���b���Rv��C���;Pl�ʐ�t�!+�`�9P�b��s�/v`V��#�лb��]3!%Gp�<���)�&[��16��Q�17�V���!IP�%@]fab��6�2�q�a�2���2eP��%W�[ϞPycq�b"��<�&���/�~B����� ��;0�Ģo���r���:����PlrNax�i��#˰��3�Ţ�Cy!W���d�`��1�bR@O����
p���r$bp�LQl‹q�2M^xT�-0R
�@�m G�$�A��)�mR=�������i/�@�p���ib�C�PES,СSǒ}� ��Qp�¢����w���= w&��R����pΠ(�0~�,�;�tPy��!E�Ca��P�1��`~�$"]`x��h@���]�����ДC�q��!]z����i|o�H9Tp���s뫁���]��6R��~B�BBnɒ�I��Z�Jr���PZl��Oma~"��rZRMC`*t�@b��;%g�m�Na~b�q{^p��7`�r� �r���e�&��xA�Kl?Д�7��9P������m�$b,w�Ӣ$���8��"�dPR�`~�V1Z"n�!MP%�~¿0,��ZM ��]�~Bʒ�2a"H`~�e�`;�sA�R1���B�޹`��,�w�\Xu�Y���#�Ѳ�=�Eu���A+�S6�r˒~�6Q62[Q̢���aa�X�
R�$?�����2TP�Zc��B���bBqy�r!�cFa�s(����@/$0d[�RJ����P�u���R��μ���)hrp�2zѳA�q�S�P����~�ςW@p�3�p��Ђ}!(1�Z�`},I�A�,0�^�e��1a��$�+�
�;��^q�2�~��@B$�p~�1����\g�1�a0��h20D�62 �ª�V�h�w6��0���ƑM �=iR��ĐmM�|��r��q�2`�YB��p�R+�F��q��w`}�Lq`���9��G�d��L��~��0��1�q;�\��0<��Ae<�=�.�ob����Mg~ lJ��U0�kr��
p"�1�%���p
Ep~��"mMP���
/���rxb�1L!�P���r�m����u�fc��ŽP�R?AhK���hDQ��{p���tQ��
�c����
��`s����2À�Ʋqr�ւj<GP�!2y�=/�C�a�q�#�ԑ�x�R���j;�s^ZB�6ZrZ�G�{#��q25@H��q��ꠠ��~��p����ܐ��1&~<�w"!2��e��Ρp��``��>0ى? �C��F,�V�pr�<дRON_������I�ϐs{��]p���~��2�}-v��lR`\�$���[��3 e�*��s�`�i#R,6�n;`^��s�]p�F �r�`�p%�
�U��3Ј%�q��� �Šw�6A߶�%2@�����U�0��� ��7 �"> B�@ɢꠠb �@0!EN� �� "�2�
�p�=PV�v@ 2����3@�٢�D���b1F_6a��Sa�{�g���B�I.pOy��w��W-�1Ts�a��u!�J@�M�`�l���à'�pA��YPE4�i��B�pyc`�{A�룀P�;a���t���U@�R{�VB�1�A�������Q��. L��u�'�;��N�Qv�1@�f�uRb�RRb�zRb�IRb�R�p��v��A��"��� �qϣ�"LC�0����C�8.4Q�0?�7P��;/ݔ�{�t26{��V�jE�|�62I<�I�*@�����L�L���#�s�Ǒ�;�Z")24����S+���^P��� �ڒ�]�Q�r�і�7 �ұRF�؂$����D�]��"v<�aR^�~B=��9݂��"2�R'�NH��K2TA��B���q{�T]r��\�v|�}�"s��⇓��$B;1|��l���q���%�$QB�@�2����Np��M�7ؠސW�C s�[�]r��6���uPrQ��6`�1�K{B�Za~rR
�����<��0!�7��b��D��b@D��01��P���A%�9Bu����!�grr��R;�뉂�aOnro٢p���Pb,q���lSQC��PC/��H�QH����wO0^��0��p���4�T|�a~r1�j�p��{{��b� {��qSQ�5��Z�ؒ�q�CQj��`h(�r�҅7��`U�#K�ѴB �wb ׆e0�2�a6x`3% ����#��~1�@��0瑂�_Qp$B��i���i�݃�pڂu�Gi�p���@�"������f�a~B�p��"��Q�V�A��o16�v�bQh"s{���2.Kq$����S�S���_T�r��2V���V�'τ0�r��e��Hc�w�=#��Z���֚@��r&O��b̜qu�� ��A�Ap���"��q���~�n�2��O�a���{��/a_tHr�A�@�~�qr|��ӹ`��^�����3����@2(4��#{���Rs>{���� ��~ҕ���=0L�G�w��q1�H�:O�j�<$���)b0=����)r&���1`��p0�x0Ir.1Wn�`}P�j��O�!�sw��#�˞�@��`�C�@s�#���g��B��~��0�u��H�]�~~�>WAI"i��$�����~�\p�Yڐ~�0�>���4��h�E�����8�H2���e��A��ay�}B�����SG����pl��b\̞�"*Q����o<v Q�Pvy�RP|��n�߁����ʢ7�I���e ]E�H2���Dop�>I2��jЭ����%0#����`��a�;`�Ga��K��a�p�uA$�c/!��/�*2R����2�r���ۤ��+�f�E`�2��S�@�Rx�'���T�m|p���g�s��Hr����{ ?��A���2sbR�B*�ÑJ�r������c!%���#p�36�!��u�m���c�
�ac���mG�;b�1n��p��݁��T��3
���C���ea�3�wA��t���+H�mb�p~b�>L�p�>���h����`#O}���(K=���0�9բ��r}Llr=f�!���Qѩ��T��m�*��dJbA�p�Tq C]P������.("�a`~{`�e�,����9�!č6psӌ�$f2xoJ�-���։0�r su!��12�q c;��P5@x�;�`�P
���١�"u��񁵎��B�PB��\�B�r@1X>R�7`~2�"�s@��P�]�r� �Py���}�+1oE���@���p�┐�l(��jq�"� ���ȣ��>�)��‹�z0%��M�,�~������߸���cs�a2A{�!Ղ���#u!G	���3���RG�T,
�g�E0�"� ���a�v&�%������w�DP�5PI��l�r�mZ�~B��B~��;�!2�!�"n!�h���L��5�͑~��0����J�22��!N���`�b��*=��b�����2U
�1b�+�|KO��� �4�2_R6�/Q��5P� "� L��R�TL�#�9PA��@C���Ҥ!ң��mVqW�Q|C�Ҕ#��ZrEp~��p�R��?2�P�JP���ՉMPgH,e�‘�GayC�H2JE���3��7� ��\\Yr~R�'&6`��*�_���i�l�� ��Z���mR� C=�@���@%2����������f)pwl|�Z��W���pEd@qA�p���~�1 �LDp���2������`(���A�~r�0�BC�ҹwP���@�b��✐ �K!�R���~,S�P�ƀ�K�`��e0�I�@�Ҡ�Kݑ�>�[2 ��I.��`�
{��a�bnp~�p ���@T�ؒS!E��e���ƿ�Z���B��’q�R�C���~���be���Ta���0h#���Sj�؂�`�s(�Q� ��3��G=��"a�R���`-T_���q�Q�݉@SH�0�c�цS����˒����󡁖�A]�6@c\�B���ꀘ~���. P.T�M�]���(�\�]�|j�"�U�@�b	ҿ�~�U���u��v<M��� �R�цàP���pA����ڲ���~��-/��5k P�}6�m�r[�J���MPh^���Ⲕ{~1�9w6p�7�a�r̠��6���@�G�Q����m�2��cp�S�m�;�.���'!�����n���Ӑ���pX#�@~���3o��jq�b�@}����r�;�S�`�]`��%�Dp���,`7&����1X'���!=�R��apb��1pv�a�*`*€�@����cސ���gy>��M�˜40�7��.N�H�[���P����q��&M�<e��8O�P\���.)�9��� +r~Ҕ�]X�R/�mB��=yp#ޓ��� �����i�"O����&u��z��s�Qn�T�Hr�0Œ`�v��B+BDtao|����pr�Qp��`��6{p-;�ilב���pR� '����,�[Ip��f��B�p�铁&��Vp�#�����H�ϐ2�p-{���1�����l����Sa���x�7�r��G����А�2�7@�Ray/�o
� �B�q��e�l�_��Q�bK񇲀0�r5��4����@W��mR4�x���eC�=/@�z	C�1,=��Ca1g�	@����Sk�cqwQ�����	�}t��S~2�Ӣ0��C~2Pp��@���5t��H�E@�tBQ����~S���! %wp{��~Z~l��TN��P[��x�m2��G��b�e"�<P/�68qv�u��F����q��S&�V���D �S�A��ܐI�{�P|�	㣑�A���E@H�j��������0S�����Q�2� ��4?��0g�C�R<_����H��R!b��7`�ӧP��pp�I��&��ύ�����M��H<��dP%P��1���p29��y��9��^��SC���`��� 0?�4є�h2FN�n��A:2�1V�1m�PH���0�&D� qR��2°�%����G����i�)è Zr��CH 0����~���G��'+!�Ԑԫ!��<�����?v�0��Q�����A���alq�T���X�Ӊ��Y)��e ��a"CRr�o��eۖ�ZA �}�EP���º7YZ�{�\���ê���$_(��>�)��&f27�u.�i"e�?Cd�u���`\����AP������ 0D�ІcR���A��~u�3P����s�!��� �3�-�T��C���?���r�P�r�p2�0w��RG� ���Zr��"G��3����]op0v0�o={�ԙ���B]qW��#yq�rj;��˿�����[�ʲ� �C��e1��G�@�S�al��@Q)̢?_�"��� �b�eE�xh�`�l�@1���y�Q��@�A��HB���q��-`�>��^��r�1�1���v;qN�1W���2Saw,�@��ѽ�z�����/!_!�Q>j��b���M�c�!�R{�)�EP�&�@��g��7�m�71ml�!�bQp[�4q��+�wx{������ >�1"3qJR���m"��S�b��u:BN�����p���п�M��o`],� �p)�%�p�+��ѩo!}�a�C�Z���,D��7 a��@y����p����Ix;�Z�x�b���}�Iuq�ã��� ��A;0�*{ A���j"��r�[R2�bj�F�pȣt��Ҧ���t~2d��.�yVꀪ�p���A�3S1�j��2$_sq�o�r���Z"����C�2_�q�RVP�#��lc��~�@�ڲ�b��CZP��M�c��Z�<1�lBH�M���eP*��m�<@��:��†j�<�jqV��0+0^8C�2�b ������ y�`����ҞA��IP��r���==!PS�APLoL����0�B�p�DcC��P��`a�6@�cD@w�@g+� �����p
Ts�~����6N_:�E��Ģ��#}C��3a������{�7R��؊\RI����`p]
ipC!�2�`Բ�����OG0��ma~�LP��$p�s!<�q�����Z ���І�Q�o�X�B��C��p�RH �pV窐�TCS둶�����2B���!��p�#
����Z��P���ʒ+P�@��Q���K����Rn1�IP��9�FPؒgr�#��S��~�ձ�Z����Z`����C	�rJ2wp��� �ㅁ�"@P̹p~R�gE�
��~�CZB)RrC�<@�� �C��OL���IP�w�p���.�@��5P�������pS��`��a�
J��RC�᧐s��� G�6p,�ܒ�D��%R̐�Pu�,�`�N�7W$�4��0a�EP�rw0��P��
u��rxє~�I�04R�ˠ��M�x3� �A+&TZR�qJ����"w��s �|�E�67�Z��e#��╢��(I@�to�crs��p[
�P��H����q[�1��T���Bސ��M�	��z���#��ц��`�#݊�=p�> �"��2��1�(q�W@�b��sM�Ų���R�`B���Bt�uw ����k��kA�2�s�T1_SÐ�X�sA���`���p�c`x�.Йߑ�R��"@��2B�Rz��70���p&��&�P�"�$����p"��]z.��Д��@2�@��Z���#uq훷���0Cg��9s!-�����ѿ!�r��"D��D�p��m�q���Q���q��Vp�=PD��!��zq���I��u�A��vl_��V�1��0[�*@_��$	S�¢��A�r�a�-�FEp�*J�fs�Q�����"t�� ׀!�)� ��Z@�v��%D��Cc۲z�i�޼�q����m" ��=>p�+���@0^������^����PorP����/K�[ӁQ�v���<@My:�j�gL���i�[\�q��q�bN�W*���D`-�{���ӌP�3�`7"��_���!��0�?0�;S�Q@M����bw�q%Biru\3 Y.��~�u����њ]e�C'��4Y��{P�-���� p!p�[�l�ɱ��ϑʒ�A����8/��E~|�Q���Q��dБ�uP���~���-�����W�d���|��zm�~R
s��9 �y+#�f������P�3怹V��r��]{�P�s~��ё����^B‘ҁ��J��-�����)(q�o�}6�#,�~"�`[u��2��s�`�R�� ׼�ІS��3�Zc���i��(��!�S:���`ʂ/1m�p���$��&�~�@ؒ���b.O}�ٲG�����Q�t~R#!_P�%��@m�r���Š�l2�p�Q�b-�m�S�l!Q�sAB�>r�����0�X^��y���]�_�~,`���P$���CF��3pJ�5�HS����c�����1���CS��B;a1�ܺ��B!I��c�#��"/�61�@W����b�����0j��!"�@�7�Q�B�!*�E�C-���⍀CA@�AP0� w1�`��60!|1�C
�@a�P�s�@�y���2rd�d���������"`�t�AP��Zr��E�M��H��-��?0�/-0��;;�/���ŀ-�PL���B�R��
�mBD��#AZ=�0�a�RR_�C���z�e`��ŀ��+�*�.�(p���� ��k���rCQZBPP�S`�C6���ᔣ�P�	����%b�A�Z��TSS��`�������u�`6�����3�ZB�b��%��P��~�ŀ���T�)e;ѫq� ����G��Yg�҇�s�쨯��{@WR?@����2n�$`�29`i�aѫ�*6��s� T<	��R3bi���T���(�S ���ѣ�w����w�[_�O�B+@:5��
Pnb��A�����@�قCѦ��q #G�Z��Л}lI�7@�7b�B��_�`�6����0S���ZR��W��MPr;��C+�0xLy�ç1�[�����Tp�9��u��~t]a�q!R}�#Fa��0љ!�p�2�Q�2"!�:M��7�q�uQ�K���S���]�Zr�a�,��U}����oT1_#G��C�p���X��p������a~"�|+��"�p�¬@g���RF^��t ������T�5��ulTA~�m����_�Cq�b���>�P<�"h|�0�$kR�A�����s���A~�r�C��҄~��M���2_��pлSׁIB�0�ͻ`ق��6����B��W�dr���c0��Dshp�ty����-�ѭ:���a��+�
74���*Aa<�؂/�NAd��Be��1�p~���PA�ʷ�����#
As���b*�
|�s1�J���`�#����[���R�Qh�Z��óp�B����li`l�V��)M��Ē-=�x���S*`�6��N4�{�d|��PE1��A�q��D%�����N�l2�
`�RE0F�{P1T �؂M _{���r*��t�Q&h�ز���sD1�r�0�ƙ��52P"���p����#0��6��߂��@{��@�D`7ZD`פn�[�Z}��i��r����p0�h��Rl0
�;���wicC�Ԑi�2�3�K�6�&Y�b��ڠ1�L�aH������H�����O��qe��0��N�1���ɫ1`[G~�ġT�I���2�՚"�JZ��1`�1`�+1`�;�[, �L(�@���0�%]����$��R����� Gp�@"&��!\\Y�/��DP���3(8���7">vb|�R�p~�`�>+70�:9p�Mܐ��
ļMA��T1'�E�쥙Q���7p&rc�ѐ��7���4�W i�
`�ax�� �Sb�4 uev��q!؆WpBwpؒ��}V������bjqF�Ia����cm���Ba~�v�%�����'%
�Ru1��P����ۻ1`��S듀��AsI��B}#a�"����s�p�����D
�dl�8pα5��!-G��ax2�i;���0G�3A�-"����A�/A㿹`��e@��p����!��xA��ٰ�B-�;2���HjK���1���,`�c�1e��:'{���]P��v��hC�2pXr�B���$A��3�
��^���+Ѵ�$�C���-�4��[B�0��3`��qp�W�p6Rj �b2��O�C��?�2Gz5�U�Rr �EFI�{"o��@���~� ���aB40�@u1�߂��vSc�QD�7���`"` &�4!�� �&t�����~�Z ��p)<�0����ma~��a��Q����~B���{1�b2�puȹ�ZR5��,�p��؂]a�"X��ق
‘r��Zr�u>�!t:A�$���GѨ��� �A}4q6��ȼ4�O�A���ڂ�@��O��Pd�T@$|�{��2Tby�8��A���b�A3�1B�T����Z�h��n�p8\+Bܸ=��g��{�,2��_p�l;�pF$ j��qRP�c3���q 3C�b+1ѫ?agao2� q���Z2�Ѽ�����s`���=�q��F�3���� �ZR�0�>
@Cn��6,ᔣ�0��e���q +"�@r�wP����tܺ��iMpՖE���qAv��@�Z�E0"��i`2�0�O!㥗p��r"��E��5r�@Q�B�R@�#�"d0
s�1��#�l��D\]��R��\��0�b@}S���%�A��G0��p�d�p�R�1���;���0���ո�`?";`���|v��~�4`u��q$�B�B��@�(���(���f]r�9�٢G�~�G �B�pU=PPvePB��3�`=��q�r/�+�e�3��0Ds�A�����Ы� �E��y��H�q��A_Uц�{���Gp`{�f܂����&�"!�j� �*q�G��at�|v��1}�/!���!n4(��C0�� "-� ��n2
�#�`�+�s���h`�����5�{l;��M�]a��b�����������Y��h���r��-����#6骞��
�u��b�1�TA�2Vp�\RƊ 2tp' ��P�28��ۦR����3%#ْ�0�����S՚x=:p14&s��� آ"b��$0)�
�%��D4�]��ؑ[��a~� ��M"��AH���]���٠�y��9.w�~X`ڲZ��8�E�Ջ����a�C�+��6* b	wQv/�a�wQVY��Q�����L M �p0\���b1��"1�f�@��rE St����x����"���2p�r��Hb{݌$Щ�s�]ϐsxV��� #uʀ�s5 e�ar*�S+���G0Q���q{���x�$R*��CpKZ�'�g�f3.b�'��g�1?%�7���	��������B���������(ӐT@g�j���6Q�r{��rqJ��!���0�S@�4@�#̠oE�H� �
j?��������M�9����(��"�QZBoP�cZj1�p��S0�P�
h
��£�2� �Аrz1�����`�®RAS"���P���@]uA��E�)3sx*c����@���� ���s�Q�+S`8��R�`I�UцC��r���60ҏP�r��ү� � ���2�g��6֡�@eb�2�s��ys,Sw��pآ��
;1Q�-ӂ�H"h��"F��6�8��P�R�@��G@N����*�`�g�p���`2p�U���G�����V���d�A!#vѥ�.� �/��B�R��B°p�PI��`H� 6}B��㐢�lv·ޤ�@CX�U����pt���@�٢��FԐ�r�b���.�@W"%�ب�A�0vCÐ�BS�B#4��-����plC��s��[|q����@�j�e3ѐRE᣷M�y���E�ibZ��BS����T��rl�9����-`��t[R;�P1�����#�0�O"!�bCa"i���/3e ���c(�aPH�l �q�C��g<wp��8�����ق���2=���Д���z��R$�`lp\7�������b+�+o�2���MH���VPZR]�`���bG�����b Q�Y�%�ނD�'hC����.��
 �����P�l�}�jЂ����׀�R�̸h��Bp�0�C�Zr+_Lr�Ά��Z����-������@�z�A6Ue���V����3��K�V�K��2�]&�P�-!���Q�Ҽ�s�rPKp�$"�`{�2�+P�2B7�pآ��$�`�$3�������`�!VCճ �s9��R<�f�p�\��(��23o0Ź�A���� ‘b����|��~�r�2�@1We ��ˠ��TP6b0�I�-`�aZr"�*p���d���`Lb���0��Bpv/a�~��2A�Z�;Qš(�v�����)82Q�.@�gbʢs�=Z�����"3ns;�s���2?��C1�u������S�A�������2�b9��¥pd�ep�CJ+P3SI_ ��r� �q��{�8���u���v��*�_/1i�0H�p��an2��;1��p�>�&�0@,
�}p K�1��n���Zґ��M�PѪp�s��3�vla
4�Bֲ�`�``+����0 @�.$����곽�؂����������S2�}
�1��a�b6��*��Q��*C���A��A�mr�!�"Q�4r�2-�rk\y���5s��"߻!�6�훧������pQ�n�2]��Z�����з�����{���`�=+��.B�3Ҁ��pl>��ؒb@M d��a�*pu�f A��q��� ��N@���ٲ��r�da��6���G���M��~"7ÿwe0����l��1l8�+Ѩ��2U�0�N�rf�A����H"3���A����pnB��Iru��B�`��9���Bfa��RR� �93�h2o���Y�P�D�����#E<�A��!��o q�q�.$�A���`��P����ã�q7p�G!�M���V�p$_6Bl��X1�u�1mVj�‚=��9��"� �j��"D��s� T�Cz��A�Aؒ�q౱�:s����l9��M�9���vsa`,3�@���R�BT��;!Q%;��c,�i˛1As�{���Щ]�07b '�PN`�a9���;��$BZ�=����������z�@I;�T�a C߰�dM���u��� �7pl�a��p ��F?Z��s7�ꓝ�d��p�<��$��2uQO��P�YK^��TNM��@��b��̠@[b��{���.4�K֑��i`lb
�������M �o���R=n�n����0��lbw9V �eDa�z�0u\�}� ����Rޣ!���о����`}�n���
��\���0m=��B�� �+��张��r�a���:�9�c���A�qʢ=��r= ���0��P Ǿ>c���АP�V8@"���r�آ��Z��`�����p�s��Q<*�F��®2*�(A���@�o`d�@�����
n�2�������"?�s�P��634��Rf��"r�RB]��B�@�r�P�@�a���.`вހjL�q�sj v�)�s�w�D@�4��"^ ��^ ��6P���b��vQs�U�2�HBS��R�q��b��s���ˢx��Cߒ��s��6�3_�:V�߲��Ű���P��b~�e@q3���7�p�E@�W�~"o����(O�`�#�[ׁ��@�����R���N7����~b#�<��b��r% <!��"=@c�{����'����"z�I��І``�M@�z���"��ҲQ�4��a�r<��QPrN������Z�@��a3~| ��@�Α�r� ��;��y%Y'�H�E��B�1
(�B�pr����ᐭ1{>Q�2��v�!��qbi�{�'�%��+o���������(��G��B��*"��cM���� o�@�����Ձ�)AS4P�`����Nb��!ԇ������> p��B�y���PbO�v�a����(A��6P�<��w�U����}x��r���Bap�Q�r�q��``��E (I"���a��A
���b��?G@��{��ңJ�QHz��.��{�Ѱ�!o�@���"d�q�6���3R�R0p�Yh�M	�@��!�!�У�`�
�T���r�xOʂt��RH�a�`��`�p���3`CaZ�W��`�4!C̠{ ���a`�,��i�2�"Hr��ݺ�@���Y��ٹ9��s���I�1��rRZ�SP*�X���ꡇ���eR����p�� ac�ў_����A�ₐj{Mp�b{�]$ �1�����s(drc<e0ō��lrm@���z��N���E��A��"������ R0|��ʒ�����ž����Bc�0����OB�Bw�|�Wq<�40
�jA+�A� @�4N!\���IE��{1�3{��@�-@�9,��Rp���1���آC�v)p�t �B��C3��C#�Z'M��.{��je AX�q�s4���A�C�A�3�`��(���Q��Xцâ��_b�b/�M���p�� l�7��s�P��P� #D���`��XA��>p��Ecb�-�g�) �?t �R��G�t҆b��-�1Q��Z��}�R�r��}��Rѐ5���h��θ�=�p��,���j�Z�(���x��C����w����”�]/��[\v2��M��8j�c���Q����K�r�Q%�E�w�uE�QY��ZbuZPD`��]@��*Фj2ˉ��֢9H�^2�buq�CF�ӹ�R�@�2l�S�*Q��,A���
A���.�G���0P֤L�[?`�gbW�q��Q�1�2�+��=P�Gc�1`Ys;;�TA�B��G� `�6p�Bj!#���?��a�@�r����c����)1݋(�R"��p��<��,��\���R���q�$���`��NA���B�8���*�����" DJ{��N�P�޽�""�PuQ�He�#�.���H�����ddR���`���ҏ
�࿓�B�¿B'0��f����pز%�g̠� ���-�+B�B�qAM�1�+�P�����jAF���d�<����!�tb07beQ����׹ՠw�*�3���B�����6B2��,��47P��!7�
��7(P�r���Ӈ��)�3�P2 �4�U�~`�C�Pb|50IJa��IӆX�p���p��q2�1%p�p��퐟5o���p0��Ѷ(FRr�p�s����^Q�
��ަ���s(ѐ�C�����D�<�A21`�Ym�� I�Б�a��A2�20BO�S�P�2B�^b���Z�Qb<a�V� ����%j\PRg!�_�<�jp�{pW�$P�bp�=�Pl�O ����~"�Pl�2���G��|J�p!��>�#Dq�C:�<�S��1�Q�+svܒ�fs S�@�|�V�5�Oހ�R�!U�]�,�i ���2��H���=�`Z�1#:�����b�aD�^" �r�
��pyC �@K�P"��2�cBA�S$H����P��.ހ�4���Q2��ZZ�����apl�,�pm�q �Tq�B�������"��"�#O�Rs���B�d�bĐ�@"�}:P"d�ё�	"�����#�p�� w�i2��Q�(p���Ks �3��! �� ��/1��e��Рv?�B��؂^p���Zt�g����Ѻ	c��ӥ�_zA�IS�ؒ3q�"ѐ��@�znp%�2PiD/�&31�B�b=`;�`�*�2�G�HQ�w<	=�A`2�eQr C��ْMr�Sa�v‡�p0�E�GR�B�
���sy1H�^pI"+ч�ِ�SD�nP0b4��@�LO��3ra"�l9�ҫ1�MQL��S�2�5�@P�R1ǡ�0H��I��+$��� ��� �%�YW!�M�Pز�QV�Xцs���r��R{�"��7�2���l!9J�`�qa�}�\[r�ʒ`�b!����4QZR�!�p�I���&���cN`�Bjb60;�0V	���.݁�Bp sW�y4n�R�p!���S��Ut�bLӐBl`_90�[�q�"f1����S�Zʑ ��P�#�$����@�!�3A]_=�3�Pt��r2Pj_]���p�R&aq<]� r@آ$���M���p�B�q��U+`���)�^��B�P��¬~��l"M���Q�RT�H2rزJ��vj/�0�����]ІcT����:~p��qU�a��M�:�0$)6����q��c�N�DAˢ���¹��N�Ǵ!��G�����;A;��0T�)���!��"�˂�p��:�I�0]/xa��2p�jmR؂�!aK�@�]�A�v��F1���S���I�0O4-`r{x4��>����3�ZR]𳄠!;¢��x��îasL	b�rs��5r�r:@cW0�p�3�1���l��d�!�F!,,�19�C>���Bu1�(k���rA"�ؒ&��?��+����آ*���p�Mp�� _�"a!qG0)���~���1jP���7������b�ct��"�L�p"<�|�Ԑ�~� ^`��$���,��.†RZ`���s���r�2S �H����Tp��!2�A��X�K�>�H�a����p�#�p��ҡ�X��b� ��D�����R���[a^�*����CO�g�!<=�p�eoQ��3�rr0w<�p��� U�e@h��#D��9�U@�5��@�bʂ��wQ��B0]_R��*�~2���P�������Ց��)b\��|w�0�fc{�e	�!E���/!|6���![V�M�!L�� ��
�~�t�2Q#�R���bB��nV�:"��z����6 -����D��Ba&�p0[�[�Rn`���`��w{b�nɰl�Y��Xe C�Y2:�saM�f��T�F�TᔠV�ћm��apĭ��b�0�b����D�|��`{�ĵ�P�	���a��;�]��C�q�!q�����;�~6T�m��J��Bw��ް��q S*��_�Rdp ��
 �p�~�Jp �ɂ�"��~R_���p #*!�˕�ON�q��K��7=S�P��OТS,��2�����{�q����"C���� g���	�r�po��ң�d�>$�Ց�!T��a���ݱ��z�1 �0��q7���w`�7D�?O]�R��p@M�I�%�@�!�O�G�;�7��8�;�MS��٢�/s2(�����-�C"�Q�IR���[B܀�q��!�kK$���hP����C�A��h���Y񇃰w�QQ��Q���M���A��t%���.p�c�1B�`��c`�s0�2��RA2 q��p.��r�HbS�"���M�(��r��%�G�b5�����Ir�Bq̡A��g�2�a�r�i�w��PZ�C#1؂.Po�SSZrM�6
�~9�Z��V>62����"��t��Z"l���:����2_�� b� ߋ50���n��#b2 �ѡ����;�[�
�A��=�@~����8C����y_�@[+`����[B�pzY2~շ��6/!|`̐}*�M`��#7���:��Sq��{ x`�$�;��@���P��1{�� -�P�����ْ� g�Ȑ{3nq�b�`�HF���n�("����"�p�vR�3@`<o`H3��{��,��?��B�T���  ��1򈣒����
���.�_c�ѐ�s�F�Bᐢ6�ZaqRܑ�6vPV���T�l"4A~����5A�@t<MR�Cs��7� �W��B9�����������ӟO��2���"���.�Bc��B�Іs领�9��2
��U�R�$�hБVcb��b��ؒ��빐��"a��a���"�yE��2�A���1�B�"e0}��і���[�,)b�נ	@nR���=��W��+09bdb-�h@�+1�3W��"z�s���4@2t�x��@�zg�"s�J�;!}-��P��Q ��q�.��C���ް�Qq�w(�����%�[�r�@��D���C�SR��Be0@�̐}a��R{��c 
�XCv�"�H���bR�G����	�һ _8�a�'}q��Ա����X�
�"�RFq��p� ��BjpC������p��Ā�b���,��3�+�R���1r�@�d���K4"=�ѭ�@B��D�M��3e@Q� hs�U~AB''�ܡza�8��b���	L��1�q 7�ݡ��T���a�'�?�����@����#���RH@��Y��6 I��@�M��p��� l0`�&���B���"�2��Z��aC��@ �6 T~�@�A�٢1�<���1������u�Z#[�7�Rn*�竑�(�����=��j�| �x+��?����_=,0h4r`t7%������Վ��"UP�A���0�aQ��(S�
�@�*�@q�v��7�p�P�/	;M0�0ز�q�c֡
u��G[�7rj�FO֑BS�(v%�Ry�kī�X�`�lsŁ�2�A{����~RK!���@Fh���?�'�a$�%P���;0}AG@����2v�-��pݐj@���6�%iF #��3�8���Ly/�8N ��A�Et��x�E@j��Ǯ"s�P$���Q4���91��jP�s@�����>����D�U����S�C��ep �x��~�Q69�Fގ�B̰]M`��ѹ���`?0 �m�P�� �AVH5 �05�����1��P�N{���
���wP�)�pyc���\��⼐8�����1`��PZ�$q��j�[r����c~��B��j�PluA��;�n6�A
�(������%`��R�G�C!"�4�PX�����2{��T1�]�XÝ!����2^��B�c�ph�{p�.����!9M���R��1�� Q9�p[����u�s���p����_T�$}��1��S\p�Z��!�~̐C
E������r�N!�37��C_��Û�r���b�Ő����ð���3j��{��l���j`K�g�Z���`�lr��L�&ࣜՠ��TQ���|��Ž�ɢM�c!��"@�~��A�t��B�|�UCR �b��1��%�ْM��u�Q�R�bM�2�����qB���7���G8"!M���q$��Ch`~"��`dr��;AR�O`��3���!��v.'s!MX�ѭ�G��� �l��Ӑ��ʠ�FCaP�#G����cDPZ�9�`�qb��` p�
M�V̀Í�p�c��]�c0�j�q��;`I�Z"A�SZ���@�Ѱ���� �b1V�׀��1y3e�G�Mc��U�+N�}H��r*X{@�?�`�l�� ��s��~B���P�e�@���&q$u�Ģ��Ё�[�"��s�Kr�a�6 0�Ԣg@P�a�6� 懚`?��@U��a*A�Q�*1u�le�O0�G�aSQ�x�A����B��WܳV���E���P��(B��j�v~@��B�H�LRZ�a���L�q��`��{`vNÐ�3(Q�r� �����ô��2������3QZ"� ��!���4lt�BEPS���Un���8��2V�s��ز�P��{��;��n�Pʢ��M�)���Ci�20�ҁ�$�ڒM�1u�����Yb��R��r~!ҹ	b�"�PZ�P؂S��r"+��~��Ҁ�MҐ�t��� ���% J,WRn�R�Y���0��ŝ�B�%��m>��b�1S;T���\q��p���H�� ]���T��!��h0�c_���r����A���q�#W�����N�"���Ar����@S�Q���QZ�9P�q�lq���p%KԐ�r�r�Z�+���/��HU�5���`@U<��� R�I������{�~���&QlBU@5���
b�x���N��r�"l���"����Sp$���r���Q���B�����o
;q)�C��N�qʲ	1H2`cV���]dH2�b�zq�����ҥ&;1�	̠����@IѴ�1;A�(�����W��r���Ep�s��"a�*+�]��G#�Ѩ홀[<����[q�
�?A[���t-$�R�����F.�А�8���1L�]@���s5<0�B60B60,}j��p���� wNp�p �]�p�C�pAOT��v���3�`;�.&1 �_��Ȓ���c� i�0l��q�$s ��P�	��� ��Z L����́�n�a�#c3e���3�Ag^@�b7�<n"q؂���ׂ�SY���Z��G��"p"�VPZ2R���o�D�4��d�e�B�ሻx���ؒ��B�PZ�-������~2�0Ԣc��{�P�������@9���P��h����O�H�AИk<���u�9D	`sS�Ҹ��\&�������0�xД� i�Q�5��w�AaC꡹�fSJN��K"I���$�����B�D����ѐB��bq���PZ�P��V�~2qA2���%������$���mQ��3�~rp��zP�w��QUӏ�s��@��!l2݁��5q��a��`�rN���wl��`��h��4g���+0�e�7QA�C��{R���~"CA�pOc�PA�5PŘ1�l�e�
�w�۰EHV�v�����PX�ePN�`` �ە��"^P)~*��2S/X��bͲ����'�b�2C�]X~!-1T�i�Q���Б��S�P�Ҫ���nߘ�6-%�P�2��B7�ҍqzp�#/�g��yQ��a�ý7���*�����$�2����?��C1��Jp�s��3��$�|���y���i�9.�a�#t��pp=����P�Rcxµ.r4�m�����6�~�$�l2�>p�b�`�ɾp��=`�:P��cം�P�{ >
��R�Qڂ��R��S@pJ��� ���`�2d�?�������`5��6��Pp�������2��sSg��T��y�or-a2�p��j��|.��UR,�N��QԆp�`�뒏1Ҭ@%���/q'/"�m���7�R�f�ˠ��� �y�iuql�
j{7��AD�3���(�ۘ8pBJ�
�t�r�0��?�Z�h`���X'p�Zpʂ�n�Д#huQ�1�b�V��B7r��1�PV�0آ�0j�{`�sq��]P����u��G��A��23�����M`TW׀�
�2sRt�0֢�a,`�3��.
�f���v�1�dᔓ�P'��Pʲ{0na6����@�cІ�E�t l��pu��ss�=!�Ah�Nё�10��r��b���۸�\���svr�†�BÒ͙����8p��� ����;A�Z�C1Ñc@�cpb���Z�����"Ѵ.���r$!$Ka��= ��2ta�!7f��N�x�$��0er��u���Z���s�� �ӟ��2���֛�=u��B��b����Z�����F�ز+0������86��(S~�!�ʑD�����*Q�i�fj�M�r��@Yo$p�0!k?4 �N���4P�����p��M��=�{�
�I7�?�(����2�v`��l⾱b%�U�����G �gG��r���p��d���RaC�Б!��������kӦ�`Q�2�`x��B��/�v<Q����<�@�duQ�rd�Z�q�H#a��e0�/�����Zr70���~B0 ��p��Ʊ��݊��ҷ���|��"���C�r��E��a4�ll�QC��J�$o�U�gr�!`������1Q6va�="�6����Bs�ݡ��a�َLA;����}��B�@W��lRxnr;@r�@��A��|���ʀ볈��K�ԣ��C���3EC���=��A�R<u}����@�{jA�'\3u�P���~"7@ǂ^���9�W2!���a�C��K'�DS���%�X'pA���B&��z��BSlv�H�Pit�1��, G�+2�@�e!5BA�lB����
�l�[�> ?����ʐ��w�!S��#p�_��B9�@���|3����0��J�P�VeaZ�Qe<�5θL�m�C$��aІ��P7�`�:r������bo�Ng�a����#���R��e � p�[�
",^�.`*�a��P�r����YҐR6 9(_�%"�?3Pw-�c
�R���B
 ��D�ZB]plb���c��>����]R6bA)g�|~P����2�!����=��B���`~"D�������//�r��ހ�b���ŰNA�5@l�����x�n3q~w��T7݁k<oCU�A�l�(�ULG�B-�r�T@7�D�s�� =>�[h <, L2�!%�QC���K����`���->/��q�8���ʲ�g�Bb�����e��̈́�BԐ�)�p��F�%���u�ZmX��w��J%��,� �����c����O��b�pY����B�C���� M�;��qdq�QP�"���!�5Ma��ea~4�r�R�bt+��xCK@��"A�I1C�{X� �R���9 �%I�z`Cl���!�< ������yI�Pm�b������%�e�I�Is�Y� .p9 ��N��C6p�16p�KX�b@��yA
s�������"�p�r�Т��O30ZGpӇ��1g�*�Zo ���}L�K<4��!�9R0���&Z�QlbN��+z�I-��V6�k��}�*�މŰ�p0:F��$����ctQl�{qʒ|@@�8����S��j���qLc�Pw�҉2S@ࣥ0j���R���q��MxE0��7��4ql"����b,�Rl�.`�"$�c7�7�;1�xtQ��`�I�Д�*@(�s�W��a�Vh���ް��0ũ��)��P=Ÿ�)fin�~�!�%R �N���T#��قD��GP����;q��Tq�"�`x
5�x�ӑ��y�ĝ ���va؃�@E��꾑�S.Қ7�s�� ��!g��=���~���p� `�
��"R���!WG(��Au��Psn��me��m?@t�0`�>?B����߀����7 ��GC�A����
��%�t��Ӿ���F�ʢG0��e��o� �!,�1v� BmO��72�aP�P�t���*q��aR��c�
A��3������`J"E���j��0��[�>�3%�35�#���p��b�W���\ �d�"�V�n�a�r�bb0E�r/o��S���%0�\� !y�~b�@��Ppz��"�s��(���UPЁ�ْ3`~"Mp������#���Iڀ�T{������uLpp�r��{��B%�R;�G�g�0�+p�:E`
u!�0?ڐ~��J�r�*�A�3G�����"x��"��q�.� ��`����n���	Cq�^q��7�b4%; �#�l���28��?���^��/�ٜ��
�]�� ��Š����q~bM����P�re0�5�΃�a4�����B��~�T�2/�Z��ycVPZ���w,�Z2*�$�A���$BD@b�Hr�R7`��pb�3�~��P%bQ�R�`x2	s~�����՟M���9�g|a`�2��˧bR�A����_yMP�e;��r�`�}��~r �#����H�հ�)t`��zS3�
 r�bEp~"����6�!�s�:R(B�7208ΈC�>єӆ@ľ���j�Ah���E����2���Ӕc��j��B+��Қk?q!cxџ�M����D`��cѵ����]�Z"9pK�t�~2%��RPrr�5-p��hp��`�?�����Ѵ� 2���"I`�?�Ѕ�0�2�
W�P��Pr.�qB��0>�Q�5@"��0�C�ʢ�1ǡ4Nq<!�E�GqĐ�20!a
�~�6pܯ����R���Ҿr����I�!"�Y{�:�̐I��`o3s�U�V�I�M/���3ss���r�p₰ep1�6��Hv��B/Q;40�=�	hŁ�#�A��qd2_B6r�0�ʞ�����o�(����Z�_�p�7�E�.I��s/q{b��;����հ�w�qIM�p�r��"z�pI"��8L�� ��ő�x�$R0��#� -ѝ��;QR3�qbu��_�Ѓ�q��.�6]`L!
���r"O�Ei��L�� ��%��N t�A�M�1�`&��pY� W
j��g���e�F\7�����S�qʲW���l�/q�gG���.Ӕ��`S��qb"��J2	P6r���]׾�Z2+#�"�;7 ��]�멷 ��E@s!����Q6�(��r��O ��i����?���3��U�q��|b)\�
JS�ْ!�l2$'�P�"`!a�@�%\�ax��s�p|bV�
rƸ6б™��(��� �����j����n�jp;�H�؂=
��Pn�q�C,�5�Ba��;�.��PZ�4�����˞&��S��`a���y?�c�����F��ݑ��q�Ql�D���|������N�ZByPF���<0V��6�  ?� �b�������A�;�t`����b< ��{@�pT�@�7p�r5���p`Z�P�2ɖQ���s�A�RQ mB9 ��.�(���&�b
u��|R냨0�6@��p(E�p�%�2��E�̈́��P�2Àl��@W���F�sGB�ꓦa�OыR�q�ǡq��"�X2��3���o ��;�A��%�f��pⴢ.c�ҹ~�o�n���V1��& ����2pP�[<w�+q�Z�!�C���sr�Z��S=P��E0GAIR�+M��a����2�`��d@�Y�t�B⹅���M𘆳���{��3.s?P�"@PǘN2F��0��lї����0g|�3o�0��xp�C�U�
��c���p���3qr;Q�bڐ%���~�߰�7��!�ל�Zb�@s[�����BCu�e�R����[r�=�kY����H"��/{"x*�l�����C+�,9�B�� ��Sa�
r"��Z2�bÐ�3�0�r%"�^u�3�����z�0�� ���N�:�ũU��Rh��b�#�".E��[j`i�@�bn�~�5SP�;�㜉����ZR"�Z�s�O@�@H�1�4�K��V��rq�Y��s��ªB{��E���Jr��b�3q��I��7v#��.0���a<�Uui�;P� �z�q��Qgaz�$B ��R�a"6���l��?�����l�,Á6�v�RV�m2��f�>Z�`~|R��u��͓"}�� ��G��^:>,���x��io1�;[`�a��a��M@��!�=���36i���*�(b�L�{�����lr�q��r�B��!��5��/1=i7�� f; �����B�P�7���adR�p��h���(+��L�b�r�!I0p 9��t���cQ10��2�#�����)��p�4���=�7��r�a��]@2A��L5P�b���yNp�}qpVs+q2-*0����w�B���!�j1÷E��C*�7���s]�+o1
���#�0��ur[�H�$��"��lW`�B����wc���p�s�'�>PQ��!���?&�n��p�� p� p���q����s�\��X�p~A�H������ð�q#-h	#.m"��#{P�T1�k��6��xp�{1�k ]�x#�?���:BS�;�z!�a�<jA��7�br��NaôG�f�w��W�rػ�q8x*�~��pySn�~a2��OQr;҆s�АbBa����x�9�Ҥй{�����pϓ[d�1�0s���mrQ�Z�7�~�E�h�P�tPxCp�c�`�~QZbR�R{��u�n
jQ���[�A�mb�`q	ʁ�C^0�
s��,PZ���e�7��c�Ҍ�����X��Ңp�R��X��#����W�~""��2V��S��&���;z)�$��rn0�hPz��iaF�:�W`���r^`����!�_�1F�sAVse2G_�[rײK~0�"�?�re<Pm����^P)�N��BV@��2"��m3�U�ІS�!� ��~2s�ڈg3�@�"v�lrZ�;a��p��6��"���Lu�+�;r�G����:s��E�%4�m��Ћ������ԸJ0Rq��S�AxT�0U�rA#�p��A�w�K]�F.iR��(q��9p$���uL��E`�pS�4 ��ys�r�Dt8B
h +2z�Jdo���v���γO+����P4R:a�R���`�Í���*�ep0t�W��C� F�a�r��A�/x`$2�	������H0m�{��A�B�r9�����G{M�M�B���&�,��`7��p�"dR��q�c�q�R�q��q����R53�� �q�r�`����^�2p�b]І�T�6`}ܿ�$"APxU5Vb2�9�����`�ZB	B��!�+p�2A����Iu���0Q {#�-e!{�U��!�I�q�n���@�ˍ���+eբ*P%�_q�c�ц����ca% �7l,q~���3^⑲swo�2DA�U :�����|�rW��Rq���p���Qi?q�o����D�janb
P��J�,��B�}A�aܳx`~
�1,1s��`�]�Zq����s1ۑTq�b\P��|��X�Z������H�HpyJp�rb� [}Æ�;��������fRa��ː�+�q~"g �p���7|��-�0
��m�[�#s�qڢA6[�Ci`lR6@[�Gp�9�l2]���Q@�B )�"	\�ݭ��u�~��Rsa�#�`x=v��'<�~G�76� d�������b��Se���p<��d��T`k[Xц�fьV�mB3Q�
%���TB��,pQnq��A�Ca~r�m�IB��G��P��GP�?��ÀU�qG�����𣓎nR *�(�[2�p��~�󓫏0ɒ,��	`��A2��0�vm����l��1G�9p?�pp�hCW�i�Fˢ<�	I9�3(C�s��s6���Ѵ�qJ
,ql�"B-�h���r�6�B�zb$�	1���&��A�r29�ꓪ�td�q��RA��ma�3p�,!ES��,q��N�1�RT �*���Bzqʲy���Q�Rr����0���A��1𾧍��K9p;��¸ �p����QAgr~Ґq�
A�ig4.0�q�g�PZ�z�f�s�f\6p~�a	󝡐r��r/�j�A��{�� �r̀Vri�C��?5��yߑR��~����eM�~21��Ű�����W���Ӡ��@���E"�%%!�d-`�R����P0�N^�|��q��:G��K3��,0�(Q�%�a�
p��a��10F$U���q�bװ.p^P)�`��Ba��9�l��RPQ��V0^*��M�a$�ma~�r��w 8���M����P:3�h�]�Gɡ��x���?�[�О's1Q7�`���vm� JRxU�����`8���|n�m򭒙ġ䚔�\��U- mae�F�e}�7P�����7�^a��A�`��0��aa�G0�xpZ���UP2�Z��3����&эE���O���L�^3� <,��c+`�u�
Y�}5@I"#��:�=p@�S1T(M��R���cOp����B�Â�څ�`q�x#�^�@@����� +�ua����?���ٲ��H��B�#�B_n5*��M �ɘP�/@]� �ߥ�����8/DB�Q��B�D�P�JB�G`ʂ2b�iP@7>���x��?p��jЩ���
�u��z��r��)&v�,-gBR��07P�6��k4�#�NY�¢0$"��2\K"��Q&�,��5b�����2à�qKrs7@AѾ��Q�b�`���;��2|��~b�"I#~���$r��s �c�ik���M^a$�����Б��7�ra`�b�����#5*�;g��pl�`2�"�AG3��n�"�����@7�д2��	�c\S���s+0	��C��_�F-�\�D�uL\�ZҴRu���b����������@!u�K"�+�BCG0���`���`ҧ1�\"A� �3/��e@CQ�`7z�p���@� ���Σ{��)Tp}���2?��s}�|���34�~"�A�r v,=�Z�H�D�q	s)�.~��Źd���4c��ê]p�� �´r�sb�w<��y��"��v7�Sn��m.
�rF��p�#E�Z� ��Cl�!{���8�o��Ѓ(f���eu��E`-�D�~{P�@����eP7� Ӧ
 ]����qxt�mb�Q�
D��R@�r�Q�Ri�Ң��M ���@�t$2�9�����;qs�sASf%�Q��K 0&�0�ٜ��lRCq��|���d\��b�֐�N1�l"M@L�+�g.�l"������i�vd�R�RPPd�Q�Be 
d�c��ѝ��r��>�7�@�r��ДC6GX~�A��!Q�b%�VB�N!aJh9���wzA��`�#o��3qdwM ��1��9@tJ`�(�GCaղ"[w`��V��R�q�R6Э5,��9r��w�V���շ�~��P��`�s+ }�<�ٵGp�A��4�m].2�ځ)�6��#����g<����!$o �X��G�K\���"���`�dh��m�آ�#�]���yV;a��p�3~��?Œ�aa��`[��4Rp�;���\Y�Q~�[�=b����p'��`�j~m��PE�PYN�PP��~��#��R"���N,���'�3=Q�R� �bDQ`�,b��සӦi@����?2��3�PxU���B;\���SC!�c�`~Bs�)
�p$B*ZG+"$���70��P��<A�RE�3���b5�l�<�z����8��`x�T`�>M�Iv�s�@�.������ ,1`-(a
�.�N��AIrU���%�1E��o�@��1�fQ1��RTQ�R^�u����`b����+��3���/�~A>p����c��0���pp+ԠM��a�쵠�իp��DѦ"p��q����"[��Gp�czp$B�~1���@i�m��S�� $�b0�����[$[�*8B��pl�T��\�`ip ��a�{	qڂ!����H"��#��T^� �y5q�("���cн<�e�������� ������eЌbP�bm�Sa�b*�~‘@��S�e��х}���;Pn�A2a�A2e��?1�N� J(k����]����c��{P��a�T���tpz#%�,�k�ˁ�eP1k/�# �����rDa�R��3>`"j �q
@�� �7���l�� L�A�rE�6�\���l�?���T�u1n�Mp�)m�C��c�`�"j1�@� �����@���Q��u����F��b6q�wZc���<�P���!"<��pB@�;���%�)4���, $�0!C�a0j�p\� �R9�߉Aq�K@e�q�C��3s:cI�6��D`��V�e�b0�oA��ڱգ�B{PCi���06����mrT��eP��`�n�RsR��{���D0�_�$�D0��v�vHt2_��qB�3�
�3\�AP��Bq�Y��s%�Zb�� Ø��n!��0�[5�Q\Rc)笑��(��3�R$ DR��3Na~��3�p@W�pҜd1p&��m��~�8�Z"/!��{!}��e�vP��7P27p���qY�Rv�PDkl�(Q�2��m����+1�#���*�R�Σ1�o��.@27��Zb.�X�ay#V҆����<�є��"�;Ѽ�%�7�%�~�p[r���*@AS�?B����2HB����l�l3���RQ�S?���+��`�⡁��?��0�Н�7"��D1��p��1НH 	S��?��E/!,B�¶"$Ru���P��t�(���4��61$r;A�4� <k��ْ⁇B���C+��BJ�!� ��l�<�7&��C�1��!�`s��mm6����" D1�70H�1�+g,�˒N���DBWC��
ګ!�1@��;�a@�`��vrM��*�����H"�b�2� ]Bl��� C�����;`.���6@��#9�b�p��P
H^�P�I�J��CY���p��D�N+3u�M������$�������q�R*�;�EP�Aٱ5`����A�# P	��Д�2R��1/�>PQV�����]ː�za��`�9@w����؀C^̐3�Bqt%`��1��0�k2��XM�W\  ����P������͑������4�P�@ذ��q�{ۚ��ʒԁS��r҆�H2�$�2w��"#����sp�DÜ���M�iu�L8. ㇂"&�v�`|�X��� ����MgЂ�j�i��`��-���j�n�ao�e0o�� LӟІ#Q��CP�B;�6��}����h�~b�A���Z�P��z!m�aVgP�2��?��r�����Q�����b���#�p ��aIK,a���:%��>C��B%@�ϧ��� 0p�Cs��py���kѪ62�0Hy�Pa~��b���`���Q�e0Ae��\�6;�wѴ`�9� �J��6�av�A2�"��sx ��4Ѵ"%Pgɂ�vT�Q)��f!vЩ]��zPڒ����
2�Po�6���ā|�)—�7 
��҆#�pc�g�	Ga�s�@��ˑ�]Ц���c���1�:�r�1�b(��Pڂ;�,�6҂�Xa+HB����I�G�x|��FBNx��r{��3,�%r!�)݁mp�
0�Y��$v#yepAe��.@�=�q"W����V:҇&��S�4�7�4�u�S0���Ð�qP���M��ee��/��Cq��;������p܁��Lb�߷���0�O����s��m�!z���AkS��wd� l"S �b�����[�a��r��G���+��ʠ��)T�`�q�s�����5֑�#V���������+1��+��,�X�Q���g�Q��+~����s�f��0�
b�@f�;�vosePK� �L_ �2/S�;�? ���R[�g9Э��Z�j�;1�WC!̨� ܹDaZ"s˲���;!r`ZB�y�fq�`�r\L�Bq���"�����g<[W�%p,�;���d)!�o,� �X4��b p%!�H�'�E�dO3�H�$ �M�Ěa�T�l@�c��X@�B�����٢ڠ��p�sc�IA�G�(�;�A�⹐v�3`C����#��bй�E��#C؂�po����"��%���CB�!�hpS�Yb�Í��Y�gP�2�@�i$a`�@P����?d�0��0|$�����C��I�P�[{��>��J҆5p����V��~ғǺHqB6�k��C����~rJ"%��Q/C�0�`��+�i`�-�цSPI�$��)[Rآ��7p��!؂�P�B�`\R����c��r�c>�C�0�]G`�p"�'��T�pZ[��Iӄ����pCC�p��1�$��s��LF��� �`� ٬�ʒR-l�H�0�
gm�c��m�9A�!l�[���>������m�D�Ѣ21hj����H�-Q1*�1h|3H�P�r���+�?��r��~r
ªiu����bH®��Nq�s�f�&�b�m��q�=���Pud
��re#2v����6�����,@���@���@�b
"�ӂ�&�wR��@Ѥp�G���T�C���2T�c�{b-Dq^�5%"`��q��0�x��֍]��=;��zVP�S�n
s��C�����	e@�'�d�!l�1`�bRP��6�ax�,�̀x�j!��3��Ӱc\�H�Uqo\�@	'�KTAGZ���@��TQr�=���m�+a�BK���L����h�ZB�A�ߛap��-��1*�=�7q-u2�rI��G�p&bD�lbE@�.8p��C߲���*A 3�p�R��W4��
��"b~R�p�J! 9��9/�ІC���
�IS�%B�`V#��꓏�n��q�P i��i`~b����S�L���{cž��,�
(�;Y4a���Zr.ࣉuP�2���E�B�����꓆p�0��6��b(�H"~퐆�^��j��G�u���`�`��{o1�
��3!D^��Sє��*r��sn]p�2Ґ"e���,Pnrv�Ҷ�1�`q��q��� ��']@"T�@Q��� �`V�p����5���Z��u����!"�>")T�`68`��h�L�h@ki�A�1l�a�㾑�r��z�m��/��u��oA�6�R�����R��V�Q��4�~�p�R<�~�"��a`����_9v�s�3"'�%�>$��B�bVw! 6�G`�$��R���b�P3�@��r�2��)�mr����X6���Ҫb���PtG���
r�bu�i�U�Z���{��1��9T�"5PSW ๋C1+�p0	��2���V�lr�A�.]�~�2����5���!�mTql�\G`І��lra@`�QX|�q~"ѧ�����0�)a62Tq��p1TQ�2�a��;`�?���@H�Tp~Bp��t��RCX�;10
[Ai@��c��}��Ԃ!Њ�؋<����KRM@_4�V�;���`�G�b����P\$q��A�V�AfS�Q	��RԡTi��3U�$�7�6l��3�qC�q�BM��$�&�DЦ2��Ƥ`�CV�Σ2{��Di�����D�4PAc�s�c���`���F�ʐ�f��ٹ`�RI���p+k߁m2�hp�R�
@�A���mr�|ߑH�����@���pq�a�s�G6U��vF��ࢷd����@dfn ���[b�0l����g,��@�G�c��;���n�@뿿��b��m����Tq�`R�GM� !�Gc��
cԒް|^��sI�؂�c�1�w�sp �-aZ��P1:�A�B#�z�����r<�r�hp[�`�R`���*�4P*�p@H!��`�5 �Q���$���A�-@��1`�^�J�e@a˗SG���ZR�Q^pl��Q2��`���
�آ�}!��<V��ZB/P20P����S�r�`� �"�P���@s{�A/5�`�On���3�R�`��.w���.�2`�sV�!0�_ᆓAr�R��b?p��,Q�#�A��3R����p�7v�Z �����۩U�s$@`<0rl�r�������K�r��~b�@yRDq�����li*0��Pl���ع<��@A��p��ˠ�=Oaa\
"L+^ l�"��l�q�b �&C�YW�0��G�B�7`�����!����"1�l�,��G���B�z�$92C�ư*B�q�s�p�SGsn�jP��,�ST�v4�B�� ���J���7aP�R����90l%@�7 ��iߛa�p�"��"�����]S��_��m��m�r��p�>H2���]p�r�C�P��>�p�b��"��,a��ðl�Cg�����c�b�RAq"�pS|�P�P����P�l�,��©ʵ�laD���i�~��B��~�CPг_p�k<<p��KBӱk�5p�<"r�Gq"!��N�t�S������s�4L���q~�E�ؒ~��s3�@)3�0:��p��+aγ��s���r�¸�q~���Z���2q�T�۱���z؁�2I`ݫ��~�tp��s�!��c�^��0�gv�"�^`�2�\���]�~�%`d�p�A�z�k���ц�ב��#,�<���zA[�q��R���G��U��b����m2���Ӓ 3!�ц��
Q��LD@��r�{AW�KQ�R�a��=�UL���>$�:u�p���vB�>�A��
�I��@�
b�b��ph�؂~y��	7�q�"�ؒeP>�tz���], 8±1�"���#q�2��1�jV)PR�P��גUyB��=�K����hP�#�cRu��͐H�� V2q�F����vP�",PP��n���ʢ�a�Tp�'0d^�@�s�g�>P�7�p�C�_7o�Q��؂��=`ǐU�~ҹ��NQl�wP�q{c]Cl�p��;���*p����A���@�o�o���~�s��sqa�·Ah+�0����bpP�R`�4rAA���`��X`�s4A#S�����2��x0��j1����@Z�	�2B�h��2BPlrS0~��p �.��&��^^�� 3�Q!p�ʛ�(Z� ��Eb�s61Y�61y����f�z���`�R>0l*X�2�� �Yv�P�d�2v��w��C�P��0l�t|v@��t�M
��+AL.?����p�\����lB���+jp�K.q�����
;�t|�&}�G��0�b�禸����p��`Re�H�q�@q��������n{ ��� �06�ٟD�b£؎��D`|��x��w`�Ԣ1wqRnP��2����c��$�f�p#�Cwz����ߩ{`7*e�
�"qI���E/���~"�ă��M��5NŢ`6� @<�^��{�57�M�,�cᐲ;!�m��b�Q��4a�B��p�k��R��1v��B�?��"��b^��%2U�~bu���0��1p�5Ӡ��3!�BQ�x�O`�:pڂ�P��O�0p��T+/q�|1��iq0�b�l��q~����P�LK�i�["�rZ0�������~�Q�
QHR/!]�n�0}����л�/n,�AV� E��r��1��'��IJ��,t��?��bDq��RQ㸑X�=0�qd_;`�C��HR\ ����a,p���7�f�4�"�/�q1���-��"S��A�,�a�R�@?��Q�jP4�5С�K¨Ţ0�9��*ð�G�#{@�R�T�⑆��`��!j��1�v2�?�~2�P%P���Q�'n@���0�0��t`n�~�H�nߗp�1@�b������ʲ�{�?�0�
�"x�%�L�=pt����a��]s��{�VUP��0ֈ7̼nq�B|Q��ސ%�:�lG�@q��p�m�a ό�%��B7��M4�9�blw�u��vP�2A#���S�� �A9N��6�n ��<���
�5���m�a�/!du�aA��V�]Ɛc�4Pdh `����p��s�m"�~r"aU~���Tp~B������m�����q�e9G���ISe�Ձ�i?�M�q2��|^�-���~��@G�%҆S���p�* �MIœ��{��bxR`E��b��۠m�`�-�0`+�YJ�s�`��PG�D���P�p~�G��yB�A�
 
�C!����AP�z@�2�p>�||����lbu��Ԁl� �!Z�ʒ��6�7p�U@��3`��vB3n���E�3��t@���p2&��@��% �c,q�6 �b��`6"� yD�UҜ�c�І�c��c�љ�{P\�6�wPi1۠��A���~ఎV�d(/QxS�|�a�^��rMp��~r�Pr��m�9Bb�G��# �H"��ß10�+"��$#G��򌩰R�0P�F3�{p�[|�,q��)���b��yx �rI��9�`�_�~W��G1M04:x�ZґI~@�a�l�� e4 ��g :�ځI�?�آ4�c�/��ҟ���l�S�E�04���I���H�_L�R�R����>���'p���>@���t1��@����p��U��rM���lRQ@����i�'["ц#=K(?���ц�.p��;��T0p!��A�u{E�Up��b�%��E��9�7@��c�}j��l� �ʀ�R�bƑ���4!��,�6�~��@�9R%���@ؒ�0�������sbٮ�~�����H�~r��N�<�W��A��mb�QK����pn����}r+Nr�+e���G x���MҐ��A�e�����ْ%09�D���*@�}QPl�����/1֟��)�����FVZ�DmE�|� ��Ó�r%aSH5�x���ԀI/���.a�3w�ؒm��bs���~�����1o
C0�ҵM`��T��r�0�☘=cVҦ��b�S6��z6�sR�QQ��e0����D�q�w<���qf�쐁4��ώ��SP0�B�Qeq��a�m�À/��'e0�l�~p6���V"u��Z��mR؂�H�x�Վ����Z�Dө��c��3�`�}�y�x{��[/�(\ġw�Er3���2���"�pJr+�+�|�ۂ`A��#X�p��eQ����>@��pׄ��?�m�p��r^Qi�4 �ː��uK��
Q ��ց��?���1��1c��4z��슡mbu�"}0�29`��͠_'!Bk
p $�F��@�^�f��V�6�1 n���"��Gc�`�Gr9ojp?`�#@�~"qd20��b�ҞH|�E���b��������@E�� ��r5��E��B��1��~�1�����lgr^=�BM���1@��p�-`�BІ��O����!�w����pb�uq��ݡ��c ᔣE�Il֑��pЙ�@��6Q"@�آP໧;�3�B!e4S!�	7`��a��B{+R�r>P��~rS�A3�>p�mb<0�P��6���ax�,�D0�p��p��o�pJy�kź,�`�3� M4Q�ס�r8��+q�a �]1]2� ��D�r��)u�� c�A�2�)s2@{�3A��i�r�`��.���`�F&��6�1>�b �!
c��*0��1	-�"�(�/�أ!��[���p�rH�Uҋ�)��D�����B,��!�^��y�&�0����.�?2U��LQHrؒ��gCQ�o�Q����,pꂬ0UKS㮚a��#�[R�`&��xS^����H"t��~�1����9޳�I�?�3Dm��0H�A)��~�<{����9wZ�3�Q����~�� �^a�@i<�A�¾a��E�EX�HP��ߡ�K�µP3��@2����k|ް�*���a~�c�	� z�Q�ӝ=ж��s[��Q������1�{��Ŝ3ݫ�p�R��sSp0��������������=JCq~��@{�G��s�1š-@v�>�~b��|p~�Gq��s� �/t��F�mri��9����@�����Y;��P��1�����H���#s��W�lp�P��_�`���Z� �~��s�2(���l"�@�F��uRbم0�&�HbW��܀��y�N�D�CGP-�ڰw�qIBnAOR,`�J{`��s�/n�1�{F.Ї`�򛡆�%0!&����cr.�ӛ����B6�bsbC��~�p`���a�t�Z2oPqb1��)��@.�a�p�Lgh���4BO�٢6P��p���
p�m���ٲ^�r% �:�Д�`�����H�~�Ѹ��s�1tb_�~�ua},����2/��)j�T���@�����q��G�U���b!�U�̲�k!0�5��EN`?]���7��2"�2�`y��[�C�~4q�S���!sB��6�=47���r�����tp#���+r�B��G m��І��p�Ғ�~BW������xBLJ.A�9�E)E��H���Т<�Z��a�l���/�Ζ�!Dn��#qbF����TQNM3��Dn��~�U���k�[���d�r���an6�J�Gqc�17a[��I�u|;8�T[Ba@�ʲ��5�I"p���1��R1���x��a+`�#1��D`���آ��H@�i�Ѓ�Tp���p�C�0�
`�2]�lR��rMp�f%Д�0 ����S�Mp�v��]R8`+��d�G�M��@(�{��V�!��ƒw0?�w0�w0��j0߲&�E�@��V@��*@ǖw~7@o��;`�����������}�0l��k/�R�pq�P�*�`'f�`gI��Ra`����a
caO� Y B�5��s4�~Bs��><��Ҳ��<�ܩ�����Ӥ@6	��"�P��!���"�����ZlB���r���b~�;������Ґ�mce@Mڋ��A�Q�>4����"@���Zsp���p��[�s,�0�e�ڷe �v�9���"�b�Z�PP*!g�����,0lR��d�F�x�좐r%p$R�
tn@�������_@&���@S�p�rC�g{c!�B���x���
��=����b���U�A)�%���Q�)U����ZҒQXq˲��Qb،�H�@�I ���Z��a�Q�G@v;�ѐr;�2�٢���ס�ru��z��m��a�2��
S��juP�r<p"՜�7p�Si`l�9b��czCu�)�!+D\"�Ne��.�1��>2�(�Z|k�r҆#e@CQ�!���!�,�w��!6�p��b��â��a��P< /Zrl����m2(qW
9P�Á��C-���a���'S˜���a�S�QB4�B�Iw�M�=�?�Q��v\|`�nMЬ���Z"����m��0�� �@`P���¹`��+�-�fc�B ^CŰ�<3q����B����"epD��!�u�������	z#��b��̠'��s�P��%��j�mR� ْ"!:{��%P_����S"`!��V@TS�P��?�ҦS��1��4QZ�s1
w{p�z�QŲ��ޡE`mp����������
�~�m�3]��R��R���qJ����/�Cɛ�W�qJ��A��ҏqZ[���Gpx����)t��~Pb��Γ��A�]�`9�NrE���"��~��]`�#�/��P���@�3�A�@����)����L
pC�,ġ1�C�`<���2���*P��4a�^�5�>��5���fi��k"��M�<� ���Q}nB�7fkt�S�g3���bG���uJ#e�~L&��;�o�o6/�P|d0��PZ◢m����b|r��|4�KCa��>ZbFs�B�Bn~R$@��Đ��N��`9p\Ѩ�@g�,!�2�p\!U�nl�ؒ�S�/�m��[V0آe��I������0�^4q*�o�ȲP��Y�l�.��"�!��l�fa��G���y�1�|l"�6�&cHB�c�4�CA�/1�؂U��Ss��cCq�z���!EH�,��Æs"��
�p��`��p��uqʢ���	1ؒ 8n3�"0Ӏ�3�FH؂nj�T�[D���R�w`�p��pa�B�(���w�L�!�0A�J%@
!��lB������ �"���pe��p��p�<U�l����������pIa�«C�T!��O,��I3�~���"A�@��uP���sh|��a�S�1�/��V�Hr�`��$��>�~"�q��y�z�mR�s�&BX�a�h>����r��#j��_;�	�[I�PA{8p���j���M�j,�~�T��S"62�a�zh��b�q�#<����QZ�# �70��M��h@����3an �O�ҕ�z!�� �a��6�hn�b�a�MZ`��xr��f0�G��~��Q��4�e"끈�M�$�2�.���P2����3�k+����Q�V������<z��b��i�����2c0��q�BSaH�
Q�WQ#�&� �3�^C3 [��p)4M ;�@q�=A�Â0Ъ��U��0�������QZ�=����`��=����A�ɐ��c��lP�@R��jž` ��>�c�Q������p$b��� ^"�Ǘ�m���~����nja��",����; ��`x��`i� ��n ��q�f���<^ʲp��SYr�`{����n&;y"<��r�O�ْ=sԒ"��,p@v���eb<�T \��<Q:���Zⷀ����뇰�	�p˪�ѱ��>�����G�������c4�Z�e0��t��Ӯ�P���#�������н.p`�S� 
0!ҹ� �h� a A�>�3o�{@�
�H!��
rʒ�0��>`xr��#���b'q�COp�#9�/~r���~V�m�I�^Ǚ�b�!'�c��C1�R�p�P؟M��@*���!�Iz0�~�@���Z���2FB�PZ{�2$�p���S{�6*=�z����Zb[��3���81�R��L�<��`�3y�np��2]�~�A0��=��"��@�� ���"I+B���x�b�$@��)R,�����[�rR��n��Qʂ
R�"���#,0r���!���R���R��&�nb�zBd"s��"ʠ�KQZ�n�m�\����@��S�I�(��u��5��%��[C�����ւ;p�
���`��]�w9�~��R"p������~b��27P�M��!�"�`���p��3���d��n�Z2D�C���яs{0
=������u�<-b�9��٢��V��MO�Qn�q�ٲ;�A�E���Sᑢ- �#���2M�I�#
=2iv����#)!�dG�Ơ*���~2�u�ucuSXG��2����>p�������dߑ�W5P�rw��� *;Pb{�Zp��P�F��p��^�;��B�y��p1�ߑ���pN��p�cҚy�Ӊ@9��>�C�B��C������I!��""��������!S�}4v�e�TQd��pdb�q��W������7h��%�i0�s�p�r�a�^�@�7��5�Ѐ��W��,��y}��#�¢s��f1��0��҂0S(03.q�ʂ�p�0{�A��2�����~2+�g�q�����';��E ���u�-��푌F��oڑއB;Q;��C���bŰi��e��0���.�P�ӳ�KV�1U.����9Oh��0�`�R����S�b�����#� �\s��Gpb��٢���qI2}rA(2������R%�R,��6�P�<�7���d����`��"$��̰�^��~ү���A�-��3E����7����B�p�rCѢS/�#rx�P�g��NӐ�S��I(�h�����Z�����$0����0Z^����B[���bˠVם��� آL��G��
�ә����1J�S��w�Ɠ�H4�� ��9Pn����b��0��H���{a�2v1��J��wQ�̠��pc�r�rAr[���L�0��Sa�_qB��P�iRv�>q���q���v�H"+a�R�bQ`�“`�!wA&��,�b�C�������d@ZB]@X����B�!xRwq�Lcڒ
�vX����lx�@�}=p������&�u��$z�t�����~� ����Hr�qR*1	�"���"٢M�ǘ%�G
���p�2�2N	^]���RM��ȏqZ�yB�N�1�bO�j�w���Sq�bi�q��� p4 �@o�q�oa��ˆ���,��<��Yl���ҍ�r�;�Z��"�"ْ���̢��C�#&]����xanB��╲~
�r��(qA���DA���q��w��-p�sA�2{�R���7@np��TA S��蒲��b���[a�n6H2�ғMy�3B-��5�S������{�3v��A0=]\���0}rh�sṡԀ����C��a:�"bJ"���
`~"���ь�E�\5�q���"�b/����0ɑ���6;Q&àp~��@�\�>^2e�淣�
`~R�0�7'S�9.f������U�#V��sY�֑�C�@v�a��q�"TQ�2! WMP�Q�@�:bHV�,��"�Zr۰�2;��=B�b��sz�C�����Z�b�5M7@��]����٢
`�b��s|rn�7�  �O����lT��?����Zr�p&b����Z��@���?�㱐���Z�}r9�yPu,8�Z7J���,�pb,L�a~�6���@������HpSZ��cڂ�a
e0�D �r�)��a~B���b��03d�){ �r�➑����Rj����Aw�`
ʛQ��5�<@�Q��4^�j`�S�a��`�"�P�.7;otQ�ٔAD�)@�t����2$Ǖ��;	Sι�j;�p c�@]�����Qa<!���t��D�E�90Z�!=�8�Z2n�Æ��َ`/p2��%o�$�@�70)t/�~b�R��Q��BT��>2d�0��N�~{P1�apx�ScXH0�/{�|0�в�<���@�B���R��s*9�
�~� ����#�P���hn#� �!c������G�r{@�4��H�f1	�Zp0<Qݑ蚙p
�+�r��O�>�R70��D0��0�B����r�ё"M@� $��J��@��g��BT�_Z�w��й��+��R<���Ab!6��rs���s2�4Z2T1�"Ѱgs��
�=�C�X�b���S��S��7��0�;0�U6ZG�~⼡ǐ���/0�p�r������s�B�~Rs!�{z�_-�2��1X�^�{����a��M`�pʂD�s/kG��C�rD�b!�{B���v�3����j���*�+7 �D��w�s���`��<��K�����RW ��r�v�pBIF#����W.��1�=����;�s�6���(A
�c!�ID�>��>�iӽQ��81�����!r�R� ���Aw̑ cV�p��P}���:���h+��pْM0:~�ǒ��D`7�D`��q[d�r]#*�l}2��uUd���Z��hN ��4�j5��bM����"wQ�r݁���Y�p�2C��0�*������FR���$B>��øу�a]�`��C��3���/!��?��҃��b	P�"-��TqxSb����ٲ��j4Ġ!cq�A�s@آ:@�
��C���/�-3a:"Rz�0�Cc���10��v�R���u�!,��p�%��%�9��TR��$�������O�`�b2S�"�2}p�!����Z*1�B�P�B+H�@�Y^N��;�ѱ�@%"*��'�wQ�@d���cp�⡀�3�`3��2���"S�ْ���BІ�)�	����6!�b�ra�1�[��;�1:��`�8`�
RB��$����~���n��[�Ss�`�p�����B�`�����������=R�"/1�
r8.;b�A�~�qr�@���!૑˄H!آŰ��1����7��S�@p|8����@Ѐ�5��U�6����Dh�|!���+LY>��Y����%��bp�gY�@g�+�0��B*O�㴲Pax]���;��
����1�y��s�)��3a��(�R�B3*�A3q��~|Z��C22`�Q�H"{�6A��֖��x6Aֲ��c]�`n"���o��@q��>7OP9nײ
���~�!��b7��B������xS�c����E@L��1ZrYцC��A���yM�%��Eb���a�s%�&����2����f㐢$Pa|�P�Bp��  'bi�="<Az�a��y�HrN҉�~�ޢ��?���"��>4�Q��Q"�@.��`���a�=��"�@]	P202C��R���PI�MP�e@1
p�[g�;�{"�r��]��%A]o��>z��Be@D�3qB��R�0��A�g0�B������P�����z��_p�C�0�^��A���NY!�Sl1h|��Iq2���EP�PQ��V@Hb��Mq�Rc��p��~7@��=pJ� �Cb��0��Bn�Zb0��#c�g����rq���@�B�Q42P�3���p�* ��p�(����0�|�1�&!l>7 �ⶱjP-߷�_W���j��������8�G  ��Q��@��J��B�1�K̒5�/��B��36�:�GAn���CPR���u,,bm�`"ԢK�v��$��r�?��[/q�\����|nf�c|Ap��$`�}1�[a���S.@v��:��<*}BW��p ��s���rސZRpL�a�������A�O���b���ARH�@
�[�(a\O����eP^B��֐;�7�x��@%2N���@��V�\�P�Bobul����*�� �<��𝝷0�(�Z��Q�M�q"sA��hr��pxB��P�z�^�g�Rۗ"q�栌Z;Q҆��}���t逭-s���Αa���2��s>��@���`�c�C�"
�[��Z��p�yހ6Tq ۠X���(X�`�R���t@�S�A�O �2��$��cP�
��p�W��_p��d7 2��xV`ق��B�h���c���R�p����s䛷0f��0HR��І��ay���$$$	-J$!-*BBU-d--$5B$n6
7 B$B-R
B1				76#
-		7
BB!(B- B	BB$

k-nBBB+-	,k7$
R$5;B-SkU%	BdB$?$J7-h	
n	
J
BJ-67	BR$	$7J	"
S$-
$",
U$B#--R5) --?
$7
"
6d	U?dB27B$)#171$)-+
+-#
-
B?
7n$kBSJ

-$B-

*

B
$	
1
 ?B*	$$#-!7)#
B +	B	
n	7((#B 7*#5-
UB+B%$%Sd,$%-
B$R#,	
-51
7-B'6	*4
d
nB	
;B;$S	!B)-
#7%);		$#	-k
-B	
#-
$'B7-#-'(577+6$*B-2-77
(7	-(5kB(hnJJ??	$+7	,*	,
	472
	
	-
6
5$BB

	7#77U
	?(		$
6
$U	-%7!-S#7kR-B-#	-
"!B17?!	#2+$
7,2d#
#
	nBn
!-!7
7

Bh$ Bk
#5
	
!
$
k7--,
J#R	#
h7h-'?7*#
%
B5-%57'
77S62 )
h66#
6
	$-	5		$d626	,"
7$-17
--$,$(

$'5-
$	-#2-;'h-h
-S75
S#2B*-6
-+
	,,
	
52$-
n
,"2

	-;7#5
			
#
		
	
4U7R$h4k5''1- $57;
',
-%
-	
5
7*
+4		
	
-7d6
"
*#	

	
 
	
-
7!)
%5 
-*"*
-!-
	

U5$4

	
h6	?	

,24
	745


1
6#,2$
;-(kU
'---
	,4
--U27-
(5$R		
$2*
	
!
-62
2!
h
62	2-$4,	
-+2!
	
!+-

-
	!,+
,		!
6-
	
	2+2,

	-
	,-
	
-#	
n	
-)52

�)
	6$1*

)
#			SS1*
!
!
5d%"
4

?"-
$;+
			
5
5	
$$
	
	
-
-
		-
+4-

*2d

-
-+4	!	#	
-52
	4
6	


*4
	

�
R,

+
B
R
	#
2"	#	
	
!22		
*+$-
2
52+++

*!-

*2

$+4	#



	
5

!J#
2)
"
	,	
	;
5
-
-

*

4			#
-
	;
	$


+
-
	2-
��
�ZL����P3���	S�J�d�x�RxB4@����r	p��
p"GBxBT
X��0NJ�u$ ���~ �p�JP�R�b�xB�hKR�ʲ��mb��s��[�����b����~�<�xB�9hB�Xz����nB葂H>�
���6���s0m�l�$b0|�X����P�

xB��ǒX�#xn��Q���	U��
�6�H�6h��tr�KZ ��=��7�
�L��d��[X��!�T�ؒ%2�Ւ`M�"���X�lp������ZrXv�ȕ`ط� �E�ʒ�~�#X�2�B� �������P�.���[�xX�$�|���@A� ��0�	x�B%0��xBp>����rXo�%���������;hBD%�3����Hˢ��b&�Q�
��p�5�3X=j0T�8i
�����n �R�V5(=J�!�
P�����%`���+�j���؛���#h�rp�-�2j��B X|D%�
�
h�d�102p&��$B��"��|��B$O8>�X��HXxr�B�(�hBt`�$����mq��h��B���4�}��f OL0
��Bd��(�+��0��
 [��"��@��P��Ȑ���r%`���ֲX>�H����
��r�B�x[�)�#�l��kH[������fc��{�ix� m"%p��о^{� ���R�l2'h�X��9�@B�s	`��(x�rh���(3p���Nl�b# �Bp$���`(&��x  � p$/$`�`����4%P��X�b
�])��4����H��b��"�D��j��h9R
x�b,
0�@�*�r(��#ة�*���`��x��j��2
�BD'� �l�0���B$X�.0=z�)�H��x��B�p� )��u�
x�X��B�x�"���B�$�8a��B
��r`B"3�x�P�B�+8�'��`�R
xqD��J"�=J`��HM
)��b�Z�X��)PbT!8��x��x�
�x�
�ikLJ&�����P�t��}�l20=�xxq�
�}dxx��B�#���)����0=
pޭ
`Z2 � �8a,��H �B
������{�{30м�dD�f���J�I�r~@s0��`V� �������
�u�C$Bt�l"��� ٲ P"p���Ǫ�`�F�N<�2�~"��"���B0�S��
��k-@��pg���RHp��_�h&c�b`��B���r��P��.�R�;�SP�B��4��/8�,��(��X�"�"����+
5���`%H"�
����R0��Ȑb
�?C
���`3�ȉCx�"��� �r
�wD�9�xx�b*�����h^h�S��
����pH;��"���0ؒ����V`)Rx2)��BX�
B�@��؃�`s��.����s�����Y<�T(���	#0��;�.8��@D���,�F���x�r�"X��
��
�2�
0�(��t(��BTx��%x������@V�Љ�(�l�؟D@D����
����DNl #��K%��
.Xnl@F(�.'������@D����X�N؆�px�b��D��� Bt��b���`�x]�
���
x�
��> �4���hk�BP�P1B��u,���D%��H#�@����q�x[�%x��x����������t���
xhd��3 p�~"0v�x��=����4`�$ ��@|\#���1�Q� �2�&0�@Rx�P����PNT@R�@Dt����� Bx���G�X��b�n
x$�Z���O<%�f���"h���\x�����ux$RزZ ������ق�0��C��7��\��
�Bx$��$����{x$r �е�#H&\،�+	�wdhB�(;�l �B�HB�0m}����s��Hl�<�$XB$&-HB(|���i\
i6����B�3&��hB$�Cd�%���
>H��8
xo�'h6b�Ct2�q���������Y���rN~hǾ��xl�ؓ���2	[�x\� � ��Xʢ,@Ǐ ������H���4'h6R )N
�"XB���H��`�������%�	�^�E	���(��. �(���(g��2()@D�P���a���8��
�;.X��0�@0.X�ʲ
��2
pf�
p�#�2U�
�C����������r��~�x�[%���p��
`���2 � �+؆����B�
�B�h2�O�()t�3h��
`�T� -psN ��%@��0�0����p�S[%�=j �ݗ  ��c�k����
xB�Ȃ�`B#�����9xrP2d �P��Xs(���3�{s.0�H�8��ȑ� "Z�S
 q���
�pz�����`�C �<UX�мt%�Yb�ٲH�
�B��e��~���~* ���3�
�g����(t.�x�X�X��p�$���'�6���"!P�dHB*P��0ؒ ���J��sX2X��؟2H��)����Hd�!8�u�>U�0;��X���+��K0`��`��O��S��b�B�x$����s�����sxBH�b�|-�G-x2��(L��b)x�b�BT0�.�rМ��Pd"O�覒�R�r��	8:����p���3�Bx$�h��	6�or)�B�x����"�� 
~��x���C�PLd�dXR�"T��rIb@o�l���.xx4�x�C$x�9�F�xZ=x*"��R�2x�2x�"x�B`� OH�2�y3�z�0l"
��2�Bd
X����=J�m������d���s��B�p��4`N�Skp�����x����1PGB?��c��sH�!�Cp�Kx=�##Ph���2Ȑ��Bh.M
�����y��<x�.0hg����T�T���Bt%P��>�*بT	H�r��.)n�5x�� )`z�(k?`:2��"#[��"�U�0g�0w��r�R`� B�
����b# ��#�g��^$Ȑrx�P�r��gXb�l2h��*�:����+�p>���2���=H��6���2���B�)��� T���%�nrH�Vp��r~0I�ؾ�4خ�ػD��� ��DS�٢��А��rp�
#7O�C
���x��h����H's��"�ʂ�B�(>/�Zr�B�@D�7�iv��RH�h!�"�=:H��q�0���������R�ph�#��B��^�(N��#�$P���@I�8�B8�C%�� �kHp�8�b@D�0�� �pʒ �(>�
8B� �$��<Hl��i�)���2%� ��N�(.�&��� h��pX@D�x�"�}!�"#��$ C�@�
 b�l"�P}HǪ�3hh��g�
Ld`B���2%Bz@D���Ȕ�`�4%X��Px������ S�Kx����X��)���Z�����e b���0���I��� ���B�b@D�@�nx0��r@�3 �l��قXD�)XlB �0.8�8�`�-��
�Tؔ9X���2Xl�% �+��%
p�� Cd.�3���:�� ��r>X��(�>,
Xl�X���R�~��ؒ
N�D�xl"���
��+%8�8`�RP��x�_@Dd
���@v���R`B>�H@D�@��
�����
0������ʂ��D
xBD ����H��
(~�@D�
���:��" ��`��.���*�%B�/�`�d�Usxc@D$x�h6R �:�"���(ћpa���$p��!��a�SP�cA2"�k�tL���� l�E����B@D� ���&�H�Z��O]����S�sb��&(z�H���4�
�s��ْ�Z@D� s� �HD�{p����I9Hl2�t:���)0�Dp�x�N�� �B`l�
�;�� 4H�����
x�2 BT)�B��� قB��l��W������Zb�l��"�%8���ɲ����x1BІ3 B�`��
H��h�� B
膳h�%;P<j'���x�R��~Pd	%`�r
�x�j%8N�l��l�)x��P��`z8��x�304T
2�8���".`zS(x~"���08����
��ؔc���8$�
���+
�txn����%Ѓ
�����:��؆�8g(�R�?� ��h��K���.�����B�
p{�����S�E5��� ��
�HL.��#���&���-a�C�ػ�)HBX8s`zS��l 8#q�<�N�"�ޢ���B���PG��
�C$�1p*%��l���T�Bd)HB��1��n���o��S�k(�{
�|K��3ؔ�'h�X��#А�X 3
�W��C��C�B$���4"#�"`�.�B4
�'��ϳ��B�#h��!���w�hB4!����0�%��(X�(p�rș��  �T=H��hB�(p�"ج-�� x��5`��%0�
���P��0n(>������jA��!���*.�6bp�S^�P���KSh���%�AJH@�%��P�r.��B�`�D�V. �B�D�XnkB$����=:@����B(������Pd�x��H��)hB�X�
xB�h����r�n�j�xs�XF$	X��
�'��l2$�xB��r�s@�c'`�� B����"
8 pxȺRЭ������kTX�'�bP��p'�����%���dx��P��#h�� �����.����� �mp'8�i�4���ȵ� p��C�����Pc*`B
���( �^hƽ0�T���@��o@=�p��p&��B4��"�l���S��
(�D�+�W,
��D���x����� �#`O� C4#�#xC3>ZX���2x�
X��5sh�x\�8P2�B$萢�h��#��2.N�
���8P��C�8�x�P�c���.2x��^�=���h��x��� s�$`�s
���B��B�%�f�B$!�#�b���rG�ʢ�B4p���p�������#(� �P@�M���y`���G��B�<���
���x[��/���3��� p~�B�����	����5�����/%h���S�(
��p�����������
P�?.��(�葫%����r��T�CD.hV�&��>�=��Ct�����$2@��-�	���(8�����
(��'�����hP�(�@S@�
8>��BDؑ���2���`_�% u�#�����
�m��2hS�"��%��� | l2pr�lRhBx�=h_Hho�m��I
�;��6�px�X�d�i����(�.أd��c$pS��{D���P����`L�`T��i���z0�\
��R
H��Yd`�������b�r��r
��'�B��c���
X����.��jx�R�����`��B���h����.����
��(,h�����!8���VJ�BD ��
B��Us
`N�@D���� l�xS�@D����@D$�>�V�Y�d��@D4
�rx��B��Q~#�C�9�m����z8�)�B����C�o2@D������@S��"*��!h#� 
C
B��L�
8���p4PeQ���@D#0����(c�@D9�C�`ld�B�(�HP����@�k �U@�s��‚��4x��B$��.@�`~�`n�s�ןx{pb���*`t�@D�`�T��.@D�XlR	�(�PA�p�[��l%`��xa�@�CT
�gc	�2�8��Xl�B�@��nR��n$ B���~��/ B� �m"�$��	��$��*��� B$ص���L
`xd�*��@DD
 
� B48� Sn�m���r���( Bt��	�&�����C5�q�`��
�� B��Ct��c�P`�P�
��hծ�Z�x�`
�&S#H;���^(
)@����"0B��r0B$&h�����٢�l"�?��� BT�����#X9�#����L�U ��Hڒ���X��Bd�~�H˲�4���`7�۹�c)�Wd��!0C� LC�-Gȯs
������@$ؼs0�(�qP��x��(���2� 8�h��h��!�\����4�K x�PFf�EB*��r���j��B���B��@��?t,X����/��#���

8
@%T��;
�<Z��R�+.XB�
��s��o +�*H�#'XB4*H_#���rC��l`�#(�4
�
�
���q�&�>"`ܶ�mb��h�J����XB����p��h6���S@�k%x���g��Z���c�#3XB����(a��ob ��h6���$l�Ȇ�%BT��k6�I�`��`�D
@<:�c�ld*����4@G��*	�d���hBT(��h #P2�0��~�]
�>��y#�lX�T.@:_bl��Rx��Y��P>
�L��Z����*pS����ߒ���x��P��������lr���0#
�~h-�H���.xB�����)P�R�l����*^x�
8�xB��C4 ���|�lB#8���;�Y��T�4�d\P��<�������}T@�� ��(��CD	���hB�/D
�Ғ����~BX���#�Ql�C���&�@�"X�C��x�b�!�p��p&�hVs'������f�ئ��G
�F$��%X�r���9��o�.���0�T��"
P�xR�0�4
��
�� lb
h����S���������B4X��������Γ��;�ئ���X`�!hB����% ��Hx���Џ�O�
�R�)�[R �<���P}T Ic���x����s��&xal�Z���)�Z#��Pln
`������x}��BT���Bt`�M�Ԯ��
���H��$�3Hq��l����C�P���<���"��&����"�E�X�N@DT86|'@DD���@Dt���b�?�,�x�$��r@D�������2P��B� Tr(<Љ�(,#hB� д����زh���S8;��_���B� `���aH��X�rHJ���Ц�X�t
��8H�
諴8��XlrP��C0p�(�k\�m��0�Mx��0gd8�� *B���KC���HH|� �؊<H‚��+@�4�������c�4(<�؊|
���C���������s��!`md:8&�3how���h�
��"h��
���
���&�C$�)����ad�'T�� oT�3���#���,C��*�{
���x�8�*��k8�P0� �Sx7���*�k�X.#�RX���SB�P���P�x~"�{��{�����
�`$0bl�
���h� ��(���FU I� ���]\	(���i|�6R�k%����
c���XZl"�2��S�E`@D�pAc*�V�hSU�_+H��`�$@D��<�H��Z��rD�{���i���CT���p�4���x~���#	�2૒9R@+�h�"���h"+0�����(�}�m�O CD��B�%R
��� 
PJ����B��{���@D$`C�C4#�Z"*P��	�	�U�`���B�Q��T��
Ȫ��T#�W�8��l"`E�7 �)���`u>�!�*�U�x�.��1)R���m�ثdP��C��md	h�[�I��)���� $�h�c�g(��� $�8<��jtxBA�����x�Ah�2��b0��C�P��%��Z�ؑ�8mm�k�S0�r��b
���B� IB$w"�&@>b9�D�@.���*0l�������x���tJ@D�`�$@D�@D4h�t@D$ ��&�`�4+�x��9�t�.��0�()�B����#P���X��4`��؞���sp{4@D���k%@� �(@D�#����
3��h8>�R���\���%(�b#Ș
�g�
0Ts���h���dO��P�
B�
�m� �r�i��S؆�
x�����^8T���Z*0��B�0��%`��PmTP�
Hl�ЗD�������{�����ഏXBD�<�
�H��V��l����xI�������-� �%P�
P~���hB�`�����)���� B��$����pg4�y�)���`����Nؔ��$B�)�@03���
8�.p{����H�H�B�DhBD���ό�C�8�Kh��hBdx��X��%�=�)#�)d1���P�"�2Z��>�Rh�mX�P2 Фha�xl��ͫ�F�،��P��lRx�YX�~h�D�GH�Dh8�(�`�~�<>%��
P������z�m��`������<Z`���k!x��p;��M��?�~#؞s�S*�%b%pG�P���BT���{���z
 ��0�r��*xp�xc ����S ��
Paj _3�%(LU�Bm�j��B���y$��3���%`���S��B��y�y� Wb�C���Zh��Ȕ�`l�m�8�9��� m��TCP�b�C�hH,8j�
@sЉ~��x%�#0AkhxI)h����K%���?�B� ��(/���(��>��+%�*PY1��2�S@D�
ؔ;�N�
8�Q�~�hVwXvy
��[#`�d�K�hS�
؜�P=a _��C�
�9T����DhB�Y�0��h�2�w�p�y����x~"�nz
�J�Jsh��8y�9@D�h��%�i�Ȝd�
��������g���P��
p�
��#*(a�o� ��O�P�3
�4��m�	x�cX�b
3�XҒ���S%���hB@i�)��C0�4"3*�T(ԕX�3X����(KS6�=�x��m����8v0+�
�6
���`W�PB
8;�X�����ؘ�.8�3hB�Ȑ�P��xv�A0�{ȝr ��H���j�*r��o`�&Ћc�Bd#H7���h�Rx�
��#��P��B�Bh؂��ed#xB%`,XG�*p�mp)^(`z��CT���p�����H�B�pl#�|R؆� �$ �2
`��Lj@��(��p���������t~xl�0=:H�BP|C@J����
����CTp� W7@�R(#�,eXgHXg�(�Ct� ��XH2�B�p�bpx��$���H�Bx��!�S��%x����$�H��΂�*B�������@�0=Jh\��B4�B$����Ct��mS�|��
�%P�
%@�u@HY��T��sX�'���`*�l��2��Ql���B`i��2�~����&\���`�N�-�@IL`2/����~H&���0s蕒% iG8IR�[D���=p����!�JD0�j
Zc(m�`���u�9
pԢ�Bd#`��(�y%`9
��>0[Jx�s5���
�+���%8>zhZk-�mR�l�!���y
H���.�h�r�)�~����)
���X��PR
(^Z.�����2�%B P)��B��Er �#p�� V2�[�5f�8d^h��`�< l��R�$	�ź�\�(3a�����*h�4)��s�o�%��Q��?0�Vкl�xR��������"�̓H1�`��x�i��$`�3�j4xH�wNl�!(#Ch�s0!X�I
h� \L�9|h;R��h�����B�Ch3�~�x�R�P��C��R�
���Ȼ*��+Ш�h��S8�88݋x��0��BD(V*���rC $B�b@D�����&X�#p��Xt B�k�ؔ� ��pE��M(I��Γ@D$�� `��@D4 ��
�2#�"H���(*$.`3�#@��B�dr�N@`n�8�7�@D�
�+(�~��CH
@D� ����~�(�����o"�B��C�@s�"��6p��3@D4Xl��(N�/�nXl�@DdXm4@Dt��B00"�m�0	��X���"Hh��I
�qDh�#
8�
(x��Zr(j
���b(�k���#@Dd ݲ�R���X�3%����,`��M7�?_�e�C��0p@��0E:	�f�L��Tr`��0�4@%�`��%�n
uzX{������^"P�kx2��=�
��(�"s�	�d
9P���X2����
��)�:@��1�	�~#%��k �Ȧ�xl�MT`��p�k�y�0/�x�2x)NX�~@�1.�r��ٛ�mB0�(Z-�����HR���pFX�0� ��HBD�� �����Ht$ Y���#h��#���m�`���c�����J
��#PD����B�0�b8<�����I�$�,�P~�hK�
p��8�H��~�0w0�(����)�lR����H�(+/x��x�b){�~2Xl�@�9�В'`tt����Tpg�X���~��S�*@� 00(չ	z��ג�5�P#��%b�ʒh0�%����l���hB(��l�5�@D
@	��g�д�x6B�l� ��Rx��(C�D�3:Pmt8��������ft.�<�-� ��0�B�lr�>E0�&�lb�6�
>�^��hd�����j�x	�sC.��"����Q�x6"���l��`�� � �x��.8/�Fps.0�Bxk3��~XO�s����Pc����1�Ȑ
�����`��~hئ��7�"q~�����T*���(Ilػ��~�0�2@��	`����0�b�ˢ%`f� �
8��H{��$0��X������[
��2�
M��sH{� �3�p�X��ɒ�V2�\x�r���+� X����@%dX��`7/���xؒJ��m�5�s�x�L�)(���{��`Ԣ(�`���Rx�����X��(a�h� ؑ��o/�A�0IM���CD��`l�Bh��x��X�x���c�ZR�׶�g����-�N
�:�	�B4(`��~�$��Bd�բ���N$Ȇ0�S3��%�h~2
X��X�(��x�
x�o�������=���%h�KX��x�RXb`r.C(=�x�bxr0xB7��
x�K�~}Pg����(�����H�hE)`^�`k4��(]�$"h\x���sx��h�;`�d�J���V���D!p�dX��H�辔~���p��
��s�C���q�#�M���2K~�Z
���x�������
 P;�mr�!d�cd������"�������� �{菋�&���T)`��	P� mR�M4hi��k��I" �������s�
�3�"h̫(�p�X�j*�*�0S����*x%�XS�h�U�m&�C�2�=z@ǖ%��� m"��
��plbGX>
�F�
����j�S�
���
x��x̻
Xzn��k*`*�.�k�=sPV��`N@����`�"�����&�BT��зt���� ��S�?�
H�� )�H��Z$@K���@DT����
��Wth*+h�SȂ���`3����8*�y���zX�9��5��2h1�� �
0���#xD�]c �7�
���@D�0�2�:Z�
��B
�����9hzC#���	�8�rȐ����p�� ��(X�Y�^�~v��pb���H�����D@D��()O�O�	 ��X]l	P|$@D$�
��!X� ��O�P$��B՚(�	M�8#�
8T�����~�X�.(h��s�Xl	��(:��@aF`�TW� s9Ȑ@Dt(�#"���hmpF��!h�X���K_ȑ�!��b�Q|P��
P�dx�'H�Q��0�
�~�)؆���2(o�
0�k�"5��C��C:@�k(�"��S�@D�%�L�0Z�8��`�.PO�"Pߒ��#���b��`�`|�
H�����/�
(=�r"`��`B�NX� �4XD����
@�ZB@D4B�@>e`��`��] �@Ddx���Y�0ؒ@D��mn�3 �Rx\�(`��f!!`�?H t3���
�#X��p��`��
��}@�BH�T�~��m"X`��nDȓ�h��(POrxR'@D�bPB�(n踎@���b�T.Y�"��F'`�4$��
�~��HPOtp�N�s5�ZR'`t4X�~X�Ȑ�X}T#@D�H���d(���t�" ��B�
�`�x%�襗! �bx%�"�
��]����m�zT* �&`�T�_}h��Pf���
����I@0�����"
�J�؆s
`��"`�
��;����mR�]@DD.�(��"��+��^. Bp&�<Q(�2�{�P
�
�����z��
�`�0���3�`�S ҹ*h�S h$�pex)
x�C%��  }،D(4�{��޹��2��k��R`�
0��(t"H��r�53�������)`l��)`l��]ȹ��mr8����"@�!�|T�`���`�#�T3`F:мT�f3
�o��!��s8?�V��nj �0B�غ��0�0��hM��[@��H��~B�
'��O�@�d��b`$��(���-�<j�{`��h���~�6Rp�"؆#p]R�r��&�0G�I8�&(=z�C`�
����b
��Ox��P؆c�p��H"0������m�x��X��Pn�H��Ѝ�
�pd
Ў;� �)0���BX�m��)�`[$�@C0� ��(�ؔ� �rx�bاT�~�7���(�r
��(��8t��s�����H��2��^�`ZPD#�^=h	���6=p�� �����m������2H�g��"% ���lTxD���pYx`�t(�5������"h�EhBDXi�؊�)��NH�B`V0l��g��3؏+�ْEvpq&0m�H���վP��(�����p5>��3H�����
�d�6��Bt�"���6��c�X���C
��2h���(>�^@IR`�k��H*\��rx%�H��
������H�����s`�,x��.�X���٭X�7���2%�����> +3(�#���B� ���-�����kC%x��ج�,3���Y��k������4�B�$@��`����3���`��0W��i�
X�b�BJ���$.��������!x�������`hd����Қ��3�b����)0�p�c��v��
�2J��
�I�x�"�ؒ�&cNl"�2J�utx�x�2�6B��B�� *B���ȫ2zD0�x��5`b�0�S$@}9#�BT��X�S���<��x'��I���!��2Z�T��TsHu�~x�
�i;��;`���TF%0���0�\
���#�U�
�e2
x����
@>��nz�ٲ�a\�Cڒ ��l"
�S�C�X�й�P��P���Z�`��X_�`�B��D����x2����n�5�94�Q��s`�. RC
��������B�`�
��O������r`d��� ��B���B����B4��	fd�E�5P~�	8!�9"���٢r�ȑ��")ķx�b��.'�B���8�1����B�H��誒`��%����^
��sh��pYpR��B$�d��p m"��B�X�c�r��h�����"��n Csp�$ ��tRX=�8�=0؂���8`� �B�
���BD`_[ "XPb)�AC`�~�B���� B��1� c%n
�����B`�d�& 8��ǒZr�[d���Z"P������n�"�<��N�x�~x��
�����"
臓. o�X
�%�H�
X�B������
�A# �\;��xH���>x~��m���%�.�&X�B�
Ј�X�rB���28�f@Dtb��2J%P����D������5��3��.�S49��R?(� �B��h,��~ӛ R �2�n�0�h�4����!3 �B���B� 7'@Dd؅��#x��X���64��(�T0����S��H�c)T������P�]�c`h� ^>%�]hx @�*p~@�B�����-�%�p�0B�90Bx��2����0r
0�dK�3J$І�踎0�Z'h]�'0BT �x c��c
0�s�� 
�`�`R�9$ C�xR��6�� #��~��	0Z��B8�d
�PpsJX"����
�����YثD�H\x�1��B�4�N� �r�q3(w�x�hO�#��|h��h�b)HBdpV�r�HB�*��0B����x�R ��H�� �H�
n�
�k�[#�Q�0��Pv�X�0n�	�#�)XHj�-�rN%O�������_W�=��O�X�R`o�8�e��$���XBT���X�bX�R�~PN<��4HB����P�� Ԣ�uQ%H���x��	��	��	�r�.�HB�x �N,���	�s��h&��b�H��.��
HO�0�~�td
 mLH�rH,c'�)�HB�h�"�=j
0�R#`�D�w/ �DX����
X=�!@�
hB��Z�`f�8��o�萒ȫ� �Xl�.�d" �9�N �&�"X͋�`�2.xB��.

2-2U5
	
	
	2-

	
	


	
	
	
	
		
	-
				
	�
			


�
		
U
	
������������ H
 !&'(*-01789:=>?ABCDFGHJNPSTUVWYZ]^_defgpqrsvwyz{�����������������������������������������������������	
"#$%)+,./2356; <@EIKLMOX[`abchijklmnHJNoux��W���^_���gry���������������������������������������������������������������������	

>o~������������������������������������������������������������������������((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((66666666666666((((((((((((((AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�������������������������������� !"#$%&'()*+,-./0123456789:;<=QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ^^^^^^^^^^^^^^^^^^^^^^^^^^^^																																																																																	((																															
















































































































































?@ABCCCCDEFGHIJKLMMMMMMMMNOPQRSTUVWXYZ[\]^_`abcdefghijklmn))))))))))))))))))))*********************++++++++++++++++++++,,,,,,,,,,,,,,,,,,-----------------------------------------------------...................................77777777777777777777777777777777777777777777777777777777777777777777775555555555555555555555555555MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM=====================================================================================BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((pqrstuvwxyz{|}(((((((((((((((((((((((((((((((((8888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888888666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666669999999999999999999999999999999999999999999999999999999999����C��CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC((((((""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC�CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC����������������������������������������������CCCCCCCCCCCCCCCC$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRREEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEESSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS::::::::::::::::::::::::::::::::::::::::@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF																									GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC��CCCCC�C����������������(((((((((((((((((((((((�������������������������������������������������������������///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////(IIIIIIIIIIIIIIIIIIIIIIIIIIIIIJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&000000000000000000000000000000;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''1111111111111111111111111111111111111111111111112222222222222222222222222222223333333333333333333333333333333333333333333333333333333VVVVVVVVVVVVVVVVVVVVVV??????????????????????KKKKKKKKKKKKKKKKKKKKKKKKKKaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa``````````````````````````<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<WWWWWWWWWWWWWWWWWWWWWWWWWWWWWOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOXXXXXXXXXXXXXXXXXXXXXXYYYYYYYYYYYYYYYYYYYZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ��������]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]][[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ddddddddddddddddddddddddd_____________________________________________________ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccceeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee��������������>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>�����������������PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP������������SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb����((((((((((((((((((((((���CCCCCCCCCCCCCCCCCCCCCCCCCCC�CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC�CCC�CCCCCCCC���((((((((((((((((((((((((((((((((((((((((((((((((	!$'*-0369<?BEHKNQTWZ]`cfilorux{~�"�m	�U�!%�������34��xe#�VX���!R[	K&�/t�H��"t��T����ABFi�M
$-7B																												






	

	

	

		
	

		
	


		

	







				

		
		

	


	
	


	



				


	

		

																													






	
	
	
	
	
	
		
	

		




		

	







								


	

	


	


		


		






	
	

	
		

																											






	

	

	

		


		
	


		
		







				

	
	

		

	
	



	



		

		
	
	

abkhazianafarafrikaansakanalbanianam-amamharicarabicargentinaarmenianassameseaymaraazerbaijanibanglabashkirbasquebelarusianbengalibiharibislamabosnianbr-brbr-frbretonbulgarianburmesecatalancherokeechichewachinesechinese-tzhTchinesetcorsicancpf-hatcroatianczechdanishdeutschdhivehidutchdzongkhaell-grenglishesperantoestonianeuc-jpeuc-krfaroesefijianfinnishfranfrancaisfrenchfrisianga-esgaliciangandageorgiangermangreekgreenlandicguaranigujaratihaitian_creolehausahawaiianhebrewhindihn-inhungarianicelandicigboindonesianinterlinguainterlingueinuktitutiu,ikinupiakik,iuir-ieirishitalianja-eucjan-jpjapanesejavanesekannadakashmirikazakhkhasikhmerkinyarwandaklingonkoreankurdishkyrgyzlaothianlatinlatvianlimbusitlingalalithuanianluxembourgishmacedonianmalagasymalaymalayalammaltesemanxmaorimarathimauritian_creolemoldavianmomongolianmontenegrinsr-memyanmarnaurundebelenepalino-bokno-bokmaalno-nbno-nono-nynno-nynorsknorwegiannorwegian_nnyanjaoccitanoriyaoromoparsipashtopedipersianpolishpolskapolskiportuguportuguesepunjabiquechuarhaeto_romanceromanianrundirussiansamoansangosanskritscotsscots_gaelicserbianseselwasesothoshift-jisshift-jsshonasi-lksi-sisi-slsindhisinhalesesiswantsit-npslovaksloveniansomalispanishsundanesesuomiswahiliswedishsyriactagalogtajiktamiltatartb-tbtchineseteluguthaitibetantigrinyatongatsongatswanatt-rutur-trturkishturkmenuighurukrainianurduuzbekvendavietnamvietnamesevolapukwelshwolofxhosayiddishyorubazh-classicalzh-cnzh-hanszh-hantzh-hkzh-min-nanzh-sgzh-twzh-yuezhuangzulualam,hyaraarmarzatauazeba,bsbelbigbmbr,ptcatchde,frchnckbcnzh,zhTcroctcymczdandeudivdkdutegengerespestfarfilfrafrega,glgaegd,gagalgbgbkgeogergrhathbhebhkhuniceidsinindinuitaiwjpjpnjvkckgkhkorkrksckzlaolitltumonmxmy,msnbpa,psperphpkpnbpolporptgqcrsrussesi,slslospsrbsrlsrpsveswesyti,botjak,zhTtwiuavavalvievnxhozht���������M����������������������M�����������M�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������,,,-abcdefghijklmnopqrstuvwxyz-abcdefghijklmnopqrstuvwxyzacadaeaganaoawaxbdbfbjbtbwbycccdcfcgciclcmcrcucvdjdmdoecgfhnilimiqirjokpkwlilklslulymamcmdmemmmqmvmzncnfnpnzpepfprpyqaresctdtmtptzusuywfye%s.%d !--font script link img a meta  http-equivcontent-language  namedc.language language  content lang:lang{Unreli %s.%dR,%dB => %s} {Unreli %s.%dR,%dB} {CloseLangPair: %s.%dR,%dB => %s}<br>
*<br>&nbsp;&nbsp;Initial_Languages %s%s(%d%%)  %s(%d%%)  %d bytes 
{Unreli %s %d%% percent too small} {Unreli %s no languages left} <br>lang_tags '%s'<br>
DumpCLDLangPriors %s<br>
CLD2[%d] '%s'<br>
CLD2[%d] '%s'
<br>---text_bytes[%d] Recursive(Squeeze)---<br><br>
%d bytes %s.%dR(%d%%) = %s%c <br><br>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;%s %d%% &nbsp;&nbsp;---text_bytes[%d] Recursive(Top40/Rep/Short/Words)---<br><br>
&nbsp;&nbsp;---text_bytes[%d] Recursive(Top40/Rep)---<br><br>
AEligAMPAacuteAcircAgraveAlphaAringAtildeAumlBetaCcaronCcedilChiDaggerDeltaETHEacuteEcaronEcircEgraveEpsilonEtaEumlGTGammaIacuteIcircIgraveIotaIumlKappaLTLambdaMuNtildeNuOEligOacuteOcircOgraveOmegaOmicronOslashOtildeOumlPhiPiPrimePsiQUOTRcaronRhoScaronSigmaTHORNTauThetaUacuteUcircUgraveUpsilonUumlXiYacuteYumlZetaaacuteacircacuteaeligagravealefsymalphaampandangaposaringasympatildeaumlbdquobetabrvbarbullcapccaronccedilcedilcentchicircclubscongcopycrarrcupcurrendArrdaggerdarrdegdeltadiamsdivideeacuteecaronecircegraveemdashemptyemspendashenspepsilonequivetaetheumleuroexistfnofforallfrac12frac14frac34fraslgammagegthArrharrheartshellipiacuteicirciexcligraveimageinfinintiotaiquestisiniumlkappalArrlambdalanglaquolarrlceilldquolelfloorlowastlrmlsaquolsquomacrmdashmicromiddotminusmunablanbspndashninotnotinnsubntildenuoacuteocircoeligograveolineomegaomicronoplusordfordmoslashotildeotimesoumlparapartpermilperpphipipivplusmnpoundprimeprodproppsiquotrArrradicrangraquorarrrcaronrceilrdquorealregrfloorrhorlmrsaquorsquosbquoscaronsdotsectshysigmasigmafsimspadessubsubesumsupsup1sup2sup3supeszligtauthere4thetathetasymthinspthorntildetimestradeuArruacuteuarrucircugraveumlupsihupsilonuumlweierpxiyacuteyenyumlzetazwjzwnjENGLISHDANISHDUTCHFINNISHFRENCHGERMANHEBREWITALIANJapaneseKoreanNORWEGIANPOLISHPORTUGUESERUSSIANSPANISHSWEDISHChineseCZECHGREEKICELANDICLATVIANLITHUANIANROMANIANHUNGARIANESTONIANIgnoreUnknownBULGARIANCROATIANSERBIANIRISHGALICIANTAGALOGTURKISHUKRAINIANHINDIMACEDONIANBENGALIINDONESIANLATINMALAYMALAYALAMWELSHNEPALITELUGUALBANIANTAMILBELARUSIANJAVANESEOCCITANURDUBIHARIGUJARATITHAIARABICCATALANESPERANTOBASQUEINTERLINGUAKANNADAPUNJABISCOTS_GAELICSWAHILISLOVENIANMARATHIMALTESEVIETNAMESEFRISIANSLOVAKChineseTFAROESESUNDANESEUZBEKAMHARICAZERBAIJANIGEORGIANTIGRINYAPERSIANBOSNIANSINHALESENORWEGIAN_N8182XHOSAZULUGUARANISESOTHOTURKMENKYRGYZBRETONTWIYIDDISH92SOMALIUIGHURKURDISHMONGOLIANARMENIANLAOTHIANSINDHIRHAETO_ROMANCEAFRIKAANSLUXEMBOURGISHBURMESEKHMERTIBETANDHIVEHICHEROKEESYRIACLIMBUORIYAASSAMESECORSICANINTERLINGUEKAZAKHLINGALA116PASHTOQUECHUASHONATAJIKTATARTONGAYORUBA124125126127MAORIWOLOFABKHAZIANAFARAYMARABASHKIRBISLAMADZONGKHAFIJIANGREENLANDICHAUSAHAITIAN_CREOLEINUPIAKINUKTITUTKASHMIRIKINYARWANDAMALAGASYNAURUOROMORUNDISAMOANSANGOSANSKRITSISWANTTSONGATSWANAVOLAPUKZHUANGKHASISCOTSGANDAMANXMONTENEGRINAKANIGBOMAURITIAN_CREOLEHAWAIIANCEBUANOEWEGAHMONGKRIOLOZILUBA_LULUALUO_KENYA_AND_TANZANIANEWARINYANJAOSSETIANPAMPANGAPEDIRAJASTHANISESELWATUMBUKAVENDAWARAY_PHILIPPINES183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505NDEBELEX_BORK_BORK_BORKX_PIG_LATINX_HACKERX_KLINGONX_ELMER_FUDDX_CommonX_LatinX_GreekX_CyrillicX_ArmenianX_HebrewX_ArabicX_SyriacX_ThaanaX_DevanagariX_BengaliX_GurmukhiX_GujaratiX_OriyaX_TamilX_TeluguX_KannadaX_MalayalamX_SinhalaX_ThaiX_LaoX_TibetanX_MyanmarX_GeorgianX_HangulX_EthiopicX_CherokeeX_Canadian_AboriginalX_OghamX_RunicX_KhmerX_MongolianX_HiraganaX_KatakanaX_BopomofoX_HanX_YiX_Old_ItalicX_GothicX_DeseretX_InheritedX_TagalogX_HanunooX_BuhidX_TagbanwaX_LimbuX_Tai_LeX_Linear_BX_UgariticX_ShavianX_OsmanyaX_CypriotX_BrailleX_BugineseX_CopticX_New_Tai_LueX_GlagoliticX_TifinaghX_Syloti_NagriX_Old_PersianX_KharoshthiX_BalineseX_CuneiformX_PhoenicianX_Phags_PaX_NkoX_SundaneseX_LepchaX_Ol_ChikiX_VaiX_SaurashtraX_Kayah_LiX_RejangX_LycianX_CarianX_LydianX_ChamX_Tai_ThamX_Tai_VietX_AvestanX_Egyptian_HieroglyphsX_SamaritanX_LisuX_BamumX_JavaneseX_Meetei_MayekX_Imperial_AramaicX_Old_South_ArabianX_Inscriptional_ParthianX_Inscriptional_PahlaviX_Old_TurkicX_KaithiX_BatakX_BrahmiX_MandaicX_ChakmaX_Meroitic_CursiveX_Meroitic_HieroglyphsX_MiaoX_SharadaX_Sora_SompengX_Takriendanlfifrdeheitjakonoplptruessvzhcselislvltrohuetxxxunbghrsrgagltltrukhimkbnidlamsmlcynetesqtabejwocurbhgutharcaeoeuiaknpagdswslmrmtvifyskzh-Hantfosuuzamazkatifabssinnxhzugnsttkkybrtwyisougkumnhylosdrmaflbmykmbodvchrsyrliforascoiekklnpsqusntgtttoyomiwoabaaaybabidzfjklhahtikiuksrwmgnaomrnsmsgsasststnvozakhascolggvsr-MEakigmfehawcebeegaahmnkrilozlualuonewnyospamnsorajcrstumvewarnrzzbzzpzzhtlhzzexx-Zyyyxx-Latnxx-Grekxx-Cyrlxx-Armnxx-Hebrxx-Arabxx-Syrcxx-Thaaxx-Devaxx-Bengxx-Guruxx-Gujrxx-Oryaxx-Tamlxx-Teluxx-Kndaxx-Mlymxx-Sinhxx-Thaixx-Laooxx-Tibtxx-Mymrxx-Georxx-Hangxx-Ethixx-Cherxx-Cansxx-Ogamxx-Runrxx-Khmrxx-Mongxx-Hiraxx-Kanaxx-Bopoxx-Hanixx-Yiiixx-Italxx-Gothxx-Dsrtxx-Qaaixx-Tglgxx-Hanoxx-Buhdxx-Tagbxx-Limbxx-Talexx-Linbxx-Ugarxx-Shawxx-Osmaxx-Cprtxx-Braixx-Bugixx-Coptxx-Taluxx-Glagxx-Tfngxx-Syloxx-Xpeoxx-Kharxx-Balixx-Xsuxxx-Phnxxx-Phagxx-Nkooxx-Sundxx-Lepcxx-Olckxx-Vaiixx-Saurxx-Kalixx-Rjngxx-Lycixx-Carixx-Lydixx-Chamxx-Lanaxx-Tavtxx-Avstxx-Egypxx-Samrxx-Lisuxx-Bamuxx-Javaxx-Mteixx-Armixx-Sarbxx-Prtixx-Phlixx-Orkhxx-Kthixx-Batkxx-Brahxx-Mandxx-Cakmxx-Mercxx-Meroxx-Plrdxx-Shrdxx-Soraxx-Takr	

	

 !"#$%&'()*+,-./01 !2"345678#9:$;<=%&'(>?@A)*+,-./3BCDE4FG5HIJKL6MN7O9PQRST:;UVWXYZ[\]^_`abcdefghijklmnopqr>s?tu@vwxy������ThaiHaniChamLisuZyyyLatnGrekCyrlArmnHebrArabSyrcThaaDevaBengGuruGujrOryaTamlTeluKndaMlymSinhLaooTibtMymrGeorEthiCherCansOgamRunrKhmrMongBopoYiiiItalGothDsrtZinhTglgHanoBuhdTagbLimbTaleLinbUgarShawOsmaCprtBraiBugiCoptTaluGlagTfngSyloXpeoKharBaliXsuxPhnxPhagNkooSundLepcOlckVaiiSaurKaliRjngLyciCariLydiLanaTavtAvstEgypSamrBamuJavaMteiArmiSarbPrtiPhliOrkhKthiBatkBrahMandCakmMercMeroPlrdShrdSoraTakr&amp;&apos;&quot;2147483647�������������������������������������������������������������������������������������������������������������������������	

9_nuvvvvwyvv��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������� !"#$%&'()*+,-./012345678������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWKXYZ[\]^����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������`abcdefghijklm����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������oCpqrst��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Mx�������������xz{|}~���W����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������𠴸�����vvvvvvvvvv��������������������M�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������B�K�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������B����������������������������������������������������	
	
												
														
															
													



	












			
													
														
														
														
														
													
														
														
												 		
										  	!                        "                   #        ""             $                   %                   &                   '                     
	

	
��������������������������������������������������������������������������������������������
!$'*,.036JXdh			
�����������abcdefghijklmnopqrstuvwxyz���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������i����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������79;;=;@BDG��������������������������������	

 !��������"#$%&'()���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������KNPS;V�����k�����������������������������������������������������������������������������������������������������������������������������������������������������*��������������������������������������������������������������Y[]_a��������������������������������������������������������������������������������������������������������������������������������������������������������+�������������������������������e����������������������������������������������������������������������������������������������ij������������������������������������������������������������������������������������������������ⱥⱦⴀⴁⴂⴃⴄⴅⴆⴇⴈⴉⴊⴋⴌⴍⴎⴏⴐⴑⴒⴓⴔⴕⴖⴗⴘⴙⴚⴛⴜⴝⴞⴟⴠⴡⴢⴣⴤⴥⴧⴭᵽᵹ<br>ScoreOneChunk[%d..%d) <br>DumpHitBuffer[%s, next_base/delta/distinct %d, %d, %d)<br>
Q[%d]%d,%d,%s DL[%d]%d,%d,%s D[%d]%d,%d,%s <br>
<br>DumpLinearBuffer[%d)<br>
DumpChunkStart[%d]<br>
UQLD[%d]%d,%c=%08x,%s<br>
[%d]%d
%d lin[%d] %s.%d %s.%d %dB %d# %s %dRd %dRs<br>
<br>DumpSummaryBuffer[%d]<br>
[i] offset linear[chunk_start] lang.score1 lang.score2 bytesB ngrams# script rel_delta rel_score<br>
[%d] Hitbuffer[) Linear[)  ScoreCJKScriptSpan[%d,%d)<br>
<br>ScoreOneScriptSpan(%s,%d) '%s'DocTote::Dump
  %d chunks scored<br>
[%2d] %3s %6dB %5dp %4dR,
������������������������������������������������������������������	
 ���������������������������������������������������������������������������!#'+/37;?CGI,������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,�-./01234��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Ь�������������������¬���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Ь������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������X犾��g����r肆�vD���DHD�uH�HH�
v�D�
��/�5��t�/��ˆ�X��Ho�sU��vxHˊU����D~����������A���������a�,�K���gAˉ��bc���5��5o
v\HNs�DϊD�D��~#�~翿H�Ͽ忿�鿿HvD���xD����t�?�H�j�~XX�����y翿�����Ͽ�
����Xݿ܌������v�������Ή��s5�DX����uH���?�b��D����oX�a�D���X~W�ӌ�XiDA����#x~���Hv�v���U���yv�o�W�υ�H�oḦ��s�y�s�X���W����yv���lHHb���ˇD�W����蒆s�����D��vv����s����voK��,����HH�Ҁ����t�/�D�\���y����y��Ή�ǿ����Xς�~�鿿��|�/Ŋ��UvW�/������e�����}���ǿɬH�vw���оΆ���H�a���}H��H���鿿��묿��������i�[����ƿƒ3����D������X������̿WWHHH�H���H�������d��~K�k�Wv���������������L���t����X���������D����������v����D���W�w�����2����ǿD��y翁�e���W3����k���πwH���y���2�H�D犉��w���N�tb�v���r���v��K׈��~����v�X?�b�C����HC�����m����X��
�����Ԋ���\HvƉ�Hbv�H,J����܅�e�c��H�o���H��dŠ�H���t�t��H��Lxƌ�X�
�a�H�v�5�牉iX����
����
gg��C�K�vsӾ��X5X���vX���dwo�y���~����D΂����u���τ~��e�d�̏����x��̿WoLοHM��z��ƿ�������H��������������bu�������o�����������	

 !"#$%&'()*+,-./012345�w�t�gH��HϊW�̉W�mzj�W���Կπ��π��,L��K��DaH�u��v�C��������~�og�o���~�H�����#GA�vg���N�vav��W�3i�k��C�뇐b�?~�����D���~AX�X����D�D
H���̿?��s҉��y���H�DH�Ag���w���wϿ����܊ԅ��|�z�Ͽ����A�H�Dω�A�D��HW��DD���g~�s�v��g��鎑s?��Ͽ�x�v���܉��y�XCD���H��HXoUXg����~��X�����K?vw�g���X�v��ψ݊,���yHu���H���u���z�e�π�H���X�v�y�ɉ���s����g��5�XH��ς������������t΂�w��~�xv,��C�����D���s���X�v��H���Ӏ�����Hk�v��Dyv~s�D�v������m#�v��DH�t���H���yӐ�ψ�y��������X����X����v����k��,������`܄�vv���܂Ͽ�Կ������H��oy�s�xs��y�vi������������WԈ~���g�tWt[uǁ�y�vǔ��X��������ǂ��π�HaHD��y�3���������y����~���W��~ǁv���k�L\ܐ��v������w̑��v����v�v�yu��i�����y��,H�������a�~΂�����W�yy\Ͽ��y�����w������������y�����e�܏�݁��y��K��D�iye��v�y��y�������̈��ux��x�����臀������G�����v��K�z�k��������H���D�����������,���̍vH~o����bHHHU~��|�����x�������������H���������H�������H�����X5�����b��vԿ���Ѐ�π�Ͽ���b�~�����X�����Yy�����i�ωsϿ����������i���s�����m݈�珌y���������A��Ͽ�H�H�v�܌�������3J�璆���u���tu�������v��s���mstt��y���u~ԁ���������i���v�������b���u�?�W�}D�����v��z�`���m������������X�y�[�e�u������#�������y�U���y�D�AN�,?�Hty��3�������HH�ωH��g��g��X�����犈X�D�����Dx���g��jD���CX�X��Dvo����v��GN�̡��5}s}������~DDHd�v�����̈��v���v��ψ��?t�H�czϿ��Ԉ�gϿDԿ����v��,���z��~D�~��݀H�C�D�����ǀXW�~�j,U�����H�eH�����g�DvH��H�H�����C�v�����v�d��x�v�����u�?݀�~���#���3�HH��H~H�������������C��k����������bb��HX����������g�����������������v��������v�����X��X����x������������c��v��Ͽ�H�������y��������j�̃����������,�����CHA���L��������������������Ί~錈C�CDD��D���3���������H�}�X�X��D�t݌������D�X�X�A�����C�����X��s��X���WD�D�������s�����te~����sL3sW�KK�a�v���LL�X���u��D����܈碀�~����H�����~�H����v�翿~�H��CDot~A������g��Xy3ovΈu������sH2�k����D����s濿H�Q|?H�H���������iC��v���HQ�����m�����X��/�s���ш�����������y����������D����u5�Կ��c��j���m���K��H���nj�ƿ�iHs��Db�DHL�vԀ����Ͽ��y�g�ԿH��b��ς�H�υ����������H�~���/����������������/����������ǿy��ѿ�ut�����������������������������3�L3�����������ts����z�3������5���v狈��bD爿�v5~�o�g�z�H���������b�ݐc�H��D�3�������wH���[�
���Ks�������w��Hlj�l��Ԁi��ϿϿ�Ίb�H�����wis����������c��瀡XW���D[���������b����X�D鋿H�HHH�Ҍ��m�Ͽ���55�D����yu���[s�������~�݂����L�KL������D���M��D��,���D,���v�g����t��D3��DDX���
���\XD�Db�οl���bWHW�HbD�,�迿�ܡ��ܢ3�y�����X����c�,��vy5��smDU�犊����W�X�5��W�CſG��H�Ή�W��DX����`��z�d��gW������,
R����x�����X�Կ�Xܿ��΀H�Όg�Ԁ��D��ǿ��҆����v������x�Dܤ�
�����v���X�ѐX����#g�X����LjD����X����������������L��5o����y������UoDm�DcłDs~�H,���ܿ�H�����vK��s���~������H�����̿X�����ԑH�e�b����X��Έ�X��ΉѿϿ��Կg�g��H�������v��i��DUL�u���A�b�Ͽ�H��̌���X���D���炈��������g���DW��X����H�sǿ��HH��H��m�ǁ����|��[�D�W�̌��D������[��k��o����[�����[�������W��������g��������������H�Wd�W�������3��ݏ��������v���Ws�ɂ��Կe�HH�Կ�����ҿ���������X���~K��[N��3[�����U�DD�X�s��ΐ�
njǿHHɀ��H�e��vܿX,M�y�,�҆�D�����5��s|�Hv�x�v����}�����b��~v�v�D�c�sݐ�����������	

 !"#$%&'()*+,-./0123456�����o������~|�v��C�������v��������݈��g���s�Ǒ}s�v�y~o�‡���U�vg�Xys�,�eH�H��Xgv�v���s���ϐLjv牀�܉�v��y����v���翿\HH�Hb�dv���g�ҿ�����2~��o������v���X�ϊv���e��
H�HH�������H�wтg5��v��k��nj��~��vр~HϿH����k���tskX�݌��v��������e���Ht���]���X���g��Wk�݌������x�vW���X����,�H��b�H����k�v���vy�����}��������р�X���H���\�m��e�?��b������v�����ρ������v̊���[������v��������u��L�����[�v����m�~��������[�Di�D�3���b��\�c~����s�yv�����v���yM������g��爡�����́w����L�s�������v��y��D��K̒������Կ���}�o����s��HԌ��}Ͽ�2�������sW���y~猈�D�����D��Ԇ�m�i���������ow�D�,HC,X�����Xщ�o�g��U��H��3�U����K牿�y��HH�x����H��nj���ц��t̉�爈��XgX��s��H��o��y�����CD݈��5C�����g����X�����5�~�`�/Ho����e�
��kX����2�5��D�Ӈ�HH�鐿�o�D�ӽ�~�g�v,Ͽ���v��ga���D��v�,���go3����yX���܌XN����Կ�܁���5���ݏ��i�������y�ӡ�����2�m�L�������Կ���
�H�~υ��v����DXX����D��~,~X�������~s�����D����5���mg���mw�D��3�����H������x�X��D����/����Ds�H�u��A�����c�C���g݉������X�
����v�y�������Xό��d�����ueAb�ܾ��?H��������~�s��������iϿ���d��H爆�`�����#�D�H̽�HH̀�}Ͽ���v�U��D�3�����Ɖ��tuH��H��X#X�����t����ܿ�oi��H�������~��艉H��i�a��v��������D����,t�t�ܿ	܅���H�������������w�D����������݈�����w�[���Dg���v�~�
�s�Du�Hb����ԳbD���v爿~����X��v�~��#Ͽ�ϒ~�x��y̿�翿g��C���v�ʼn����t����a�H��#�X��������������b������������C�5��xH�H�L�������ot��~���v�,tv���L����i��W����b���HxH�e�t���ov��gv����ݽv�vL��D��������X��e���WX[��c����tb������������������Կ�e�Xܤ�yK���s,��M�������y�܊��\t���H�o�yWa��j�πv�H��������Wv���KH�������t3����ix��[����ҳ��~�L�w܏�����Ͽ�������d����oj��������y��������W��������|�HD��y������g�
~�A�X�����[~�Nv��X�wz�H�����v�DvX��L�X������g�X�\����ϳ�Կ��K�����H������y�w��j�\��D��,��X�?�ψy��`����U��C3�������v���������5��v
����v�Hy��ѐv��|�v�v��nj�,D���W�DX�����mW��HXyx�Dv�������s����U�kvCsxD�v��vX��od���D�DHu�HHb�b��H2x���D��5cU�kJ�Xd���ό�C������Ί�gD�#l�����D��Xx���?Hb��������X�����5��|/D��D�����#���WD��Hܿv����yݽ�҈H�������H��H��HHb���HDe�HJ��#܉D��y�X�ωX�H��a��RcX�Cǂ�π5�b���b�H��HHHH��X����v�g[?A�H�ov�����D��Hs�K���ψv��yy���x���D3艡~w���tuH,�H��tuH�#Wx�CX�sCXW��s���v�x�o��3���v��3�����#��������ܽΒ���z����AAu�H���v��dž���k�X������UK�v��x�������H��DD�C��爁����sx���,��
�bH�by��灏��yK[���d��tC�5����3���т�mxDyC���L�vw�u�H���������U�K�Ht�ϐW΀�ϿU����������x�ye��L�v����������v�3��H��i��U�Hc��[�W��?��H���ϿH����yH1c#3���2?i�W����������2�w3�o�ݏ�~Uom]���/Nu��y����������]�����ܿ�C���y����W���H�z����Ͽ���AD����?v�ԉWH�����AgԌ���H�X�H��2���Rc�vD�HD�|�ΆGw��H���Dy�3��oH���H��H�v���va�C����v���tH�y�aay��k����v�Ԍ����u�����ܿ�����w�܆���ρ����3�D܂�����x3`��[�ԪX|��ԉ�v��Xx�~��D�����o�v�����Ԁ�DHπ[2ωK�L���i�K��Du����X[���2y���������y��[����X�K�w3D�H�vWɿ�N��������	

 !"#$%&'()*+,-./01234567����y����v�������ݿ���D��/�D����ό�����yb翈H���Xv�������������y��g�ωݿH��\��Hԅ�����D���ܪ���Dit��Ӎo��i�\H����DL���������H�����v�t���Xo�\�ӎ��v�]o��5��W���������������������L���?��L�M�K����D̈�D�����X����y���y��o���������̿v��X���D�~�����������������������/~y����������k����C�yD����H�vӉ��XU�̐�y�y���mDDDD�݃?�y�`������o�s�D~�~U/�L]Կ��yy���wv��l����Cv�D���v�L�D�v���v�2�����#`���k�������Hw���������������s���Xv�ϊ������b�����v����M����������[��s~���g]ӈv�DH��v5���ӈv,\���������������D�,����̌D��e��#��i��蒿�uH�y�s����i������Ca�1iuU�����
��o��cv��s�H#X��oX�~c����݈��H����A��~��X�y��o����uH���������|k��т����y����y����������vя�̆�v����H�������܀��k��̃�����m��d�����`��������ԏ�ý��o���D���WDyC�t��Hӂ������������爿yH��������wg�w�Ό�U���vH���v�݉K��L�W�o�3L��vnj����݋�J���Ɇ�u�������/�������X����ǿ����os�j҈�AH�����΀������v����HJ�̉i�~�������v���AD�v��wz�v�jH��wν��vH�XoX5�����UX������b�������翨���y�����5~�����H�~��HH���y��g��sv����ubH�������gds�����W��vv\����yc������x���������������Գ��s��X�W�~HvDW��v�H�����D����?�����s�v���ss��g����������̈����������HvH���Ͽ���������������������������҉DCy���3Xi����j�t�D3��2xljXgD�i݈�݊��Xܿ���\H�o���2��5����D3����������wD�2Lmy��DH
sD[�XH��Uu�
��X������v�罊�Dv�J���H�A�����HԿ���댿����܊v��܀��gܽ�X�W�ws�����,����Cd��g3,�D�L�L��D����3t��z�W��X�H�oX��s�J�o���Hg�yx}��HH�X��HH��[m[�����[���[�Lt݁�������?�}�,�U����a��,������W��D����a������oϾ?���iX����o�X�/����is�v��Ό��������u�HƏ�t��g�XXJX��Hl���������v���v��
�?���
�����Ϭb�/���翀�̿�dž�����e��~ϿԊ��o�jϿ���珽��WDsӒ������������tvm���tb���v��v���������������v������������y����Waǿ�yKXˏ�������k���L����vm����H��e�����Lj�χ��H�eD����b�m�܀����v�H2��v܀�o�zt���D�����琌��v�璊#������s����t�ˆw������́ckW[�Hvk��W3�[�����W������ew����W���mt����[Wƒ�����k������t�3�k������y�
,W��ܬ���y�翿���b����N����,�������H��Τ��Ư3�L�w�mkwm���������u,�����k��om�z���WW�wW��k���y������������m�m����Kk��������t��yW��H��݂οu\�ϫWk����Ʌ�y��K��tDLzW���ܿ�o�Hzy�b��D����l�H�|�W���������?���H��Hb�HH���H��H
Hπ�
H�HH
��HH
�H�π?H��b��Hϳ�HbH�H�πH�
Hb�ϿHH��H����b�Hb?��H���������Hܳ�܅,ݐ�������������~���v?H����t�H��琌e���ݏ�]�a�����3��wφ�c�����x����o��X�H�̌��WW���o�2����Xgy�D��~��2�j��k��Hx���y������D���w�D���D���3��vv�X�H��s�����������D�H�D�D��H���v���3vv��k�u����H�y[K��yk�yWN�y1o��������DXv���b~�H��������s��#����Hbb��A��ϊ�v���|ʈ���5�����ѐX�g�v̌�W�uWH�Կ�bHπ�v��ϑ������Ъ��ԿԤ�K���Ǒ�u���HW�܆������vXHk�XɌX���������v���ς�X���Wg�H�b������������WшXwt��������X�u����`��L’~~������܏����N������3D�W܌��X���~���vj���������	

 !"#$%&'()*+,-./012345678ξ��sz�K҈R�i�U�g��iD�������ˉ�X�HH����g��z������z���������sK��������W�����gbw�?�m�
��v�������y���b���b��������G����΂a`����݂j���������u����b���oX����X�ov���a��d�v����?m�����D|s���D�ӑ������̈������Hd��DH���ow�v�版���ӂ�b��w��d
�v�b�H��b�HH�H�w�X��jX���HC�oKv���2v����c��������D�l?܅���H����΂iR��X���H��
��g���/v���D����iXv����������~���K�gv������b��灧�b/��LD��o��v������������X��X�����s�[���v����AyXҁ�d�������H��/�D��w���y����D����w���c�y����~�3��d���������������s��������g�������g�y�vLD����UK[ύW����,��Ԅnj����D�Ό~�
�����o��������X]��L��?������������y�y�tmU������[~,�esM������5�[ݐ��x��y���iz��55��oX������L3w��x��?��������������������������熿X��D����K/�D��#���s������ibH���a�x��ӎ���|���o������������y����������Ɉʿs�,�����~��c���o��dAt����iXӁ�D�5�������肂�H������?�o����d�Xo��bԎu�H����Ӄ���o������o�����L�����ӂaӃ����uo����������yc�|���������������y����������2��������W�o���w������w������s���s����3�K��ο,��D���Wb�ӌD��W��H����ӐHӿ����������/�C���iX~���X�����H��e�̊b��c�t�������A���m�D��v�kjy�H����Xs��v�ki�������ſϿ��Xy�����������g���X�����o������D���m�����X�������������xo,��m�k��y��u�����k�����K
H�
������D���������\��ݯ�X�m��k������L��/��W����k�kݏu���kK
�����������3k��WW�/k��W
�w�
k��������������m������݉Ӓ��[WL�[�3́�k��L�W���������,[����3���¡��k[j��3�L��[�[�WW�]����y��Ӓ�m�����kӒ’̏�L,����3�3��m�[��������K�¯���k�3�[�����o�KW��ky��u�����t�����L��������H�H�
�bH|�����܀�HH������
b�HHϿ
��H�?��H�H��HH��
�H�b��HHH�Ͽ������ԿH��H?��H�b����H����H�5��~���w�,cy�K�g�����U�������D���o��ѡ�����y������3W��W�m��W��m,�LWw��菏�w��wm���33���m�[����Wݒ��33[��y����K�����y���tm�Lt����t�3���?HυHHbbH��?�H�bb�b?����bb�HH������?�bbbdD��D�Hӌ���H���v�܈�����3�v�XL��̉y�����a���b���Dv�������������v�s����G���|������\���K�o����~�vl����g������Hv�g��������������L���v�����sd���������d�i�k�����������̈D���v�vu�����k[W3������݃�������ܒ�ݤ�uL�Ϗ����e2̏K��y�W�y�ǽܒk����y������珿��W����L�z�3y������W��t��HbH��
H��ܿ��H���HH�H�b煅Ͽ��~\Cs�C�K�L3�k���t�t�gX��
���X��d�
�
�b��o窂3�̬v��s������X�Hy�X��g
��og�H�D�s�t���a������ň�����X�k�����D�HD�oX��珏s�vo��W���[�3�KWt�5#�k犈��L�s�[k�3W�v[ov���[�tL���DbC���H��CD��D�
C�`�H�C�XH���D������5��������v��/��ԅ��H�D��H�uH����H����2̡���D�H��H��3���2�ǯ����������D�DaX�����v��?�i��~ň�vvHH���Xv����HƁ�sUv�j��j~����exvWK��zu�Kyyz�d�u����������	

 !"#$%&'()*+,-./01234567892���xX��g�w�����灏���gk���m��
�����y���������������̏w�L��H���������k�c�HU�����L�,��3Ӣ��������k��,w������݁t�kmWHHHb�����KƁk�t�y����������u���y�����z�����m�[����������w��u��tt����W���t���k���́���ݒy���[���3,ƒ��������vXHw�H���HcH�HH�x����HH���x|HυH�Hmt���m3�zzt�2k������ݒ��/�o�Cπ�H�H��b��b�|�HHHυHH�H݀|�tǁ���́��L����������HH��HH��HD�����πH�H�����Ͽ��π��́L�w����y����kL����������y������ο���owyw�������H�ܿ���̿H���H�HϿH�H���HHH??��H�H�H����bH�HԿH�H��Կ����Ͽ�����������?�������HH�ܿ�?�b�?�b����HH��b�C��H�b��b���H�H�|π?H�H�HH�Կ����bHH�����H��Hܿ��������������ǿ�?k���W�[����k̤3�3�w������[W��LK�ѫ��i���bHbcCǽH�b�ǿ�yiH�o����́w��u�z�wy��HHϿ��
H�Hb�?�Hb������HH�b�����5����t�2�v�H���DD�C~�H�sv�����΅��g[�w�v�W�u��s�W�L�D�UW�3����5Di�WD�k��am�sXk����t��,��~yK����?,y��g�
���a��XoL�D�b��y�m�WW݉Ήm�Y
[�~��c�b�s�~�s�y�,vɂ�sD����������a��o���w���K�������H�D�5yx�v����̌�ttxgv�����t���ӂ�J�2���X���D����������������L��[������HH��Ͽve�XHv�3�ݏ�w�mW�ݏ�[��mw[wWu�����y�w����tky����mWy�,����珒XyH�HC5\b̒��wݏ݀��x�����sǀ�y��b
�H܅H�H���b�H��H�ϳ��H��H��W���my�������L���
���b��W����3�������kkm������������mv��W��Ly��w��������聁����������L]y�������������?�b���H�܀b�Hb�H��b�ܿ���H��~����DDW�3�Wm��Lu,����m�L���w�ݯ���N��w����[�����W��,����t���W�ݡ��������K������[W3��/�
��Hb?HH�����b�H�?H�H����H�HH���?�Կ�b�ԉ���������v����,�������ź�t�������o�,�/�������������������������¾[���Έ�X�~g������D�X�k��������L�����������t��w������������t�m�[�������������Wy�������
��������L�W��������������������t����������������́�m����m����m���3�������������y���̀�������H��������܅H�H�H�������Ԁ�����?��?����ܿ�H�ԅ������m�����3��������[�,���w�������w�3������[��y�������o���m��j����[�yL����������������������������������[��m���������������L�����������Wk����������3H���b�H�����b�����H��H�?����ܿb��������b����H�H�LuyLKX��y������犉�3��oD�KG��m����ybuu�`�tKA���~Y�RuHUuo��H��M������������������D��������~����������X��L3��K�����zy��������H������3K��y����H�2��x��������������������������������������������������������������������������������t�������������������������������������������������������������������������Ь����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������	

���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,-./0�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,-./01�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,-./012�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,-./0123����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,-./01234�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,-./012345����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,-./0123456�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,-./01234567����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,-./012345678����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������	

�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������zh-Hans zh-Hant ja-Hani ko-Hani vi-Hani za-Hani ja-Hani ko-Hani zh-Hani zh-Hant af-Latn ar-Arab az-Latn be-Cyrl bg-Cyrl bh-Deva bn-Beng bs-Latn ca-Latn ceb-Latn cs-Latn cy-Latn da-Latn de-Latn en-Latn es-Latn et-Latn eu-Latn fa-Arab fi-Latn fr-Latn ga-Latn gd-Latn gl-Latn ha-Latn hi-Deva hmn-Latn hr-Latn ht-Latn hu-Latn id-Latn ig-Latn is-Latn it-Latn iw-Hebr jw-Latn lg-Latn lt-Latn lv-Latn mk-Cyrl mn-Latn mr-Deva ms-Latn mt-Latn ne-Deva nl-Latn no-Latn pl-Latn pt-Latn ro-Cyrl ro-Latn ru-Cyrl rw-Latn sk-Latn sl-Latn so-Latn sq-Latn sr-Cyrl sr-Latn sv-Latn sw-Latn tl-Latn tr-Latn uk-Cyrl ur-Arab vi-Latn yi-Hebr yo-Latn zu-Latn af-Latn ar-Arab az-Latn be-Cyrl bg-Cyrl bh-Deva bn-Beng bs-Latn ca-Latn ceb-Latn cs-Latn cy-Latn da-Latn de-Latn en-Latn es-Latn et-Latn eu-Latn fa-Arab fi-Latn fr-Latn ga-Latn gd-Latn gl-Latn ha-Latn hi-Deva hmn-Latn hr-Latn ht-Latn hu-Latn id-Latn ig-Latn is-Latn it-Latn iw-Hebr ja-Hani jw-Latn ko-Hani lg-Latn lt-Latn lv-Latn mk-Cyrl mn-Latn mr-Deva ms-Latn mt-Latn ne-Deva nl-Latn no-Latn pl-Latn pt-Latn ro-Cyrl ro-Latn ru-Cyrl rw-Latn sk-Latn sl-Latn so-Latn sq-Latn sr-Cyrl sr-Latn sv-Latn sw-Latn tl-Latn tr-Latn uk-Cyrl un-Latn ur-Arab vi-Latn yi-Hebr yo-Latn zh-Hani zu-Latn bh-Deva bs-Latn cs-Latn da-Latn es-Latn gl-Latn hi-Deva hr-Latn id-Latn mr-Deva ms-Latn ne-Deva no-Latn pt-Latn rw-Latn sk-Latn sr-Cyrl sr-Latn zu-Latn { throw 'Array index ' + $0 + ' out of bounds: [0,' + $1 + ')'; }T!"
K'hnopqb ($	
%#��}&*+<=>?CGJMXYZ[\]^_`acdefgijklrstyz{|Illegal byte sequenceDomain errorResult not representableNot a ttyPermission deniedOperation not permittedNo such file or directoryNo such processFile existsValue too large for data typeNo space left on deviceOut of memoryResource busyInterrupted system callResource temporarily unavailableInvalid seekCross-device linkRead-only file systemDirectory not emptyConnection reset by peerOperation timed outConnection refusedHost is downHost is unreachableAddress in useBroken pipeI/O errorNo such device or addressBlock device requiredNo such deviceNot a directoryIs a directoryText file busyExec format errorInvalid argumentArgument list too longSymbolic link loopFilename too longToo many open files in systemNo file descriptors availableBad file descriptorNo child processBad addressFile too largeToo many linksNo locks availableResource deadlock would occurState not recoverablePrevious owner diedOperation canceledFunction not implementedNo message of desired typeIdentifier removedDevice not a streamNo data availableDevice timeoutOut of streams resourcesLink has been severedProtocol errorBad messageFile descriptor in bad stateNot a socketDestination address requiredMessage too largeProtocol wrong type for socketProtocol not availableProtocol not supportedSocket type not supportedNot supportedProtocol family not supportedAddress family not supported by protocolAddress not availableNetwork is downNetwork unreachableConnection reset by networkConnection abortedNo buffer space availableSocket is connectedSocket not connectedCannot send after socket shutdownOperation already in progressOperation in progressStale file handleRemote I/O errorQuota exceededNo medium foundWrong medium typeNo error information
	

		


			

			

		0123456789ABCDEF-+   0X0x(null)-0X+0X 0X-0x+0x 0xinfINFnanNAN.!"basic_string length_error"/home/eitan/Projects/emsdk_portable/emscripten/master/system/include/libcxx/string__throw_length_error!"basic_string out_of_range"__throw_out_of_rangecannot zero out thread value for __cxa_get_globals()cannot create pthread key for __cxa_get_globals()pthread_once failure in __cxa_get_globals_fast()N10__cxxabiv120__si_class_type_infoEN10__cxxabiv116__shim_type_infoESt9type_infoN10__cxxabiv117__class_type_infoESt9exceptionuncaughtterminating with %s exception of type %s: %sterminating with %s exception of type %sterminating with %s foreign exceptionterminatingterminate_handler unexpectedly returnedSt9bad_allocstd::bad_allocN10__cxxabiv119__pointer_type_infoEN10__cxxabiv117__pbase_type_infoEPK
!<b���%modules/txEXSLTRegExFunctions.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export function match(str, regex, flags, doc) {
  var docFrag = doc.createDocumentFragment();
  var re = new RegExp(regex, flags);
  var matches = str.match(re);
  if (matches != null) {
    for (var i = 0; i < matches.length; ++i) {
      var match = matches[i];
      var elem = doc.createElementNS(null, "match");
      var text = doc.createTextNode(match ? match : "");
      elem.appendChild(text);
      docFrag.appendChild(elem);
    }
  }
  return docFrag;
}

export function replace(str, regex, flags, replace) {
  var re = new RegExp(regex, flags);

  return str.replace(re, replace);
}

export function test(str, regex, flags) {
  var re = new RegExp(regex, flags);

  return re.test(str);
}
PK
!<wܕ|3�3�modules/vtt.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Code below is vtt.js the JS WebVTT implementation.
 * Current source code can be found at http://github.com/mozilla/vtt.js
 *
 * Code taken from commit b89bfd06cd788a68c67e03f44561afe833db0849
 */
/**
 * Copyright 2013 vtt.js Contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyPreferenceGetter(lazy, "DEBUG_LOG",
                                      "media.webvtt.debug.logging", false);

function LOG(message) {
  if (lazy.DEBUG_LOG) {
    dump("[vtt] " + message + "\n");
  }
}

var _objCreate = Object.create || (function() {
  function F() {}
  return function(o) {
    if (arguments.length !== 1) {
      throw new Error('Object.create shim only accepts one parameter.');
    }
    F.prototype = o;
    return new F();
  };
})();

// Creates a new ParserError object from an errorData object. The errorData
// object should have default code and message properties. The default message
// property can be overriden by passing in a message parameter.
// See ParsingError.Errors below for acceptable errors.
function ParsingError(errorData, message) {
  this.name = "ParsingError";
  this.code = errorData.code;
  this.message = message || errorData.message;
}
ParsingError.prototype = _objCreate(Error.prototype);
ParsingError.prototype.constructor = ParsingError;

// ParsingError metadata for acceptable ParsingErrors.
ParsingError.Errors = {
  BadSignature: {
    code: 0,
    message: "Malformed WebVTT signature."
  },
  BadTimeStamp: {
    code: 1,
    message: "Malformed time stamp."
  }
};

// See spec, https://w3c.github.io/webvtt/#collect-a-webvtt-timestamp.
function collectTimeStamp(input) {
  function computeSeconds(h, m, s, f) {
    if (m > 59 || s > 59) {
      return null;
    }
    // The attribute of the milli-seconds can only be three digits.
    if (f.length !== 3) {
      return null;
    }
    return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
  }

  let timestamp = input.match(/^(\d+:)?(\d{2}):(\d{2})\.(\d+)/);
  if (!timestamp || timestamp.length !== 5) {
    return null;
  }

  let hours = timestamp[1]? timestamp[1].replace(":", "") : 0;
  let minutes = timestamp[2];
  let seconds = timestamp[3];
  let milliSeconds = timestamp[4];

  return computeSeconds(hours, minutes, seconds, milliSeconds);
}

// A settings object holds key/value pairs and will ignore anything but the first
// assignment to a specific key.
function Settings() {
  this.values = _objCreate(null);
}

Settings.prototype = {
  set: function(k, v) {
    if (v !== "") {
      this.values[k] = v;
    }
  },
  // Return the value for a key, or a default value.
  // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
  // a number of possible default values as properties where 'defaultKey' is
  // the key of the property that will be chosen; otherwise it's assumed to be
  // a single value.
  get: function(k, dflt, defaultKey) {
    if (defaultKey) {
      return this.has(k) ? this.values[k] : dflt[defaultKey];
    }
    return this.has(k) ? this.values[k] : dflt;
  },
  // Check whether we have a value for a key.
  has: function(k) {
    return k in this.values;
  },
  // Accept a setting if its one of the given alternatives.
  alt: function(k, v, a) {
    for (let n = 0; n < a.length; ++n) {
      if (v === a[n]) {
        this.set(k, v);
        return true;
      }
    }
    return false;
  },
  // Accept a setting if its a valid digits value (int or float)
  digitsValue: function(k, v) {
    if (/^-0+(\.[0]*)?$/.test(v)) { // special case for -0.0
      this.set(k, 0.0);
    } else if (/^-?\d+(\.[\d]*)?$/.test(v)) {
      this.set(k, parseFloat(v));
    }
  },
  // Accept a setting if its a valid percentage.
  percent: function(k, v) {
    let m;
    if ((m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/))) {
      v = parseFloat(v);
      if (v >= 0 && v <= 100) {
        this.set(k, v);
        return true;
      }
    }
    return false;
  },
  // Delete a setting
  del: function (k) {
    if (this.has(k)) {
      delete this.values[k];
    }
  },
};

// Helper function to parse input into groups separated by 'groupDelim', and
// interprete each group as a key/value pair separated by 'keyValueDelim'.
function parseOptions(input, callback, keyValueDelim, groupDelim) {
  let groups = groupDelim ? input.split(groupDelim) : [input];
  for (let i in groups) {
    if (typeof groups[i] !== "string") {
      continue;
    }
    let kv = groups[i].split(keyValueDelim);
    if (kv.length !== 2) {
      continue;
    }
    let k = kv[0];
    let v = kv[1];
    callback(k, v);
  }
}

function parseCue(input, cue, regionList) {
  // Remember the original input if we need to throw an error.
  let oInput = input;
  // 4.1 WebVTT timestamp
  function consumeTimeStamp() {
    let ts = collectTimeStamp(input);
    if (ts === null) {
      throw new ParsingError(ParsingError.Errors.BadTimeStamp,
                            "Malformed timestamp: " + oInput);
    }
    // Remove time stamp from input.
    input = input.replace(/^[^\s\uFFFDa-zA-Z-]+/, "");
    return ts;
  }

  // 4.4.2 WebVTT cue settings
  function consumeCueSettings(input, cue) {
    let settings = new Settings();
    parseOptions(input, function (k, v) {
      switch (k) {
      case "region":
        // Find the last region we parsed with the same region id.
        for (let i = regionList.length - 1; i >= 0; i--) {
          if (regionList[i].id === v) {
            settings.set(k, regionList[i].region);
            break;
          }
        }
        break;
      case "vertical":
        settings.alt(k, v, ["rl", "lr"]);
        break;
      case "line": {
        let vals = v.split(",");
        let vals0 = vals[0];
        settings.digitsValue(k, vals0);
        settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
        settings.alt(k, vals0, ["auto"]);
        if (vals.length === 2) {
          settings.alt("lineAlign", vals[1], ["start", "center", "end"]);
        }
        break;
      }
      case "position": {
        let vals = v.split(",");
        if (settings.percent(k, vals[0])) {
          if (vals.length === 2) {
            if (!settings.alt("positionAlign", vals[1], ["line-left", "center", "line-right"])) {
              // Remove the "position" value because the "positionAlign" is not expected value.
              // It will be set to default value below.
              settings.del(k);
            }
          }
        }
        break;
      }
      case "size":
        settings.percent(k, v);
        break;
      case "align":
        settings.alt(k, v, ["start", "center", "end", "left", "right"]);
        break;
      }
    }, /:/, /\t|\n|\f|\r| /); // groupDelim is ASCII whitespace

    // Apply default values for any missing fields.
    // https://w3c.github.io/webvtt/#collect-a-webvtt-block step 11.4.1.3
    cue.region = settings.get("region", null);
    cue.vertical = settings.get("vertical", "");
    cue.line = settings.get("line", "auto");
    cue.lineAlign = settings.get("lineAlign", "start");
    cue.snapToLines = settings.get("snapToLines", true);
    cue.size = settings.get("size", 100);
    cue.align = settings.get("align", "center");
    cue.position = settings.get("position", "auto");
    cue.positionAlign = settings.get("positionAlign", "auto");
  }

  function skipWhitespace() {
    input = input.replace(/^[ \f\n\r\t]+/, "");
  }

  // 4.1 WebVTT cue timings.
  skipWhitespace();
  cue.startTime = consumeTimeStamp();   // (1) collect cue start time
  skipWhitespace();
  if (input.substr(0, 3) !== "-->") {     // (3) next characters must match "-->"
    throw new ParsingError(ParsingError.Errors.BadTimeStamp,
                            "Malformed time stamp (time stamps must be separated by '-->'): " +
                            oInput);
  }
  input = input.substr(3);
  skipWhitespace();
  cue.endTime = consumeTimeStamp();     // (5) collect cue end time

  // 4.1 WebVTT cue settings list.
  skipWhitespace();
  consumeCueSettings(input, cue);
}

function emptyOrOnlyContainsWhiteSpaces(input) {
  return input == "" || /^[ \f\n\r\t]+$/.test(input);
}

function containsTimeDirectionSymbol(input) {
  return input.includes("-->");
}

function maybeIsTimeStampFormat(input) {
  return /^\s*(\d+:)?(\d{2}):(\d{2})\.(\d+)\s*-->\s*(\d+:)?(\d{2}):(\d{2})\.(\d+)\s*/.test(input);
}

var ESCAPE = {
  "&amp;": "&",
  "&lt;": "<",
  "&gt;": ">",
  "&lrm;": "\u200e",
  "&rlm;": "\u200f",
  "&nbsp;": "\u00a0"
};

var TAG_NAME = {
  c: "span",
  i: "i",
  b: "b",
  u: "u",
  ruby: "ruby",
  rt: "rt",
  v: "span",
  lang: "span"
};

var TAG_ANNOTATION = {
  v: "title",
  lang: "lang"
};

var NEEDS_PARENT = {
  rt: "ruby"
};

const PARSE_CONTENT_MODE = {
  NORMAL_CUE: "normal_cue",
  DOCUMENT_FRAGMENT: "document_fragment",
  REGION_CUE: "region_cue",
}
// Parse content into a document fragment.
function parseContent(window, input, mode) {
  function nextToken() {
    // Check for end-of-string.
    if (!input) {
      return null;
    }

    // Consume 'n' characters from the input.
    function consume(result) {
      input = input.substr(result.length);
      return result;
    }

    let m = input.match(/^([^<]*)(<[^>]+>?)?/);
    // The input doesn't contain a complete tag.
    if (!m[0]) {
      return null;
    }
    // If there is some text before the next tag, return it, otherwise return
    // the tag.
    return consume(m[1] ? m[1] : m[2]);
  }

  const unescapeHelper = window.document.createElement("div");
  function unescapeEntities(s) {
    let match;

    // Decimal numeric character reference
    s = s.replace(/&#(\d+);?/g, (candidate, number) => {
      try {
        const codepoint = parseInt(number);
        return String.fromCodePoint(codepoint);
      } catch (_) {
        return candidate;
      }
    });

    // Hexadecimal numeric character reference
    s = s.replace(/&#x([\dA-Fa-f]+);?/g, (candidate, number) => {
      try {
        const codepoint = parseInt(number, 16);
        return String.fromCodePoint(codepoint);
      } catch (_) {
        return candidate;
      }
    });

    // Named character references
    s = s.replace(/&\w[\w\d]*;?/g, candidate => {
      // The list of entities is huge, so we use innerHTML instead.
      // We should probably use setHTML instead once that is available (bug 1650370).
      // Ideally we would be able to use a faster/simpler variant of setHTML (bug 1731215).
      unescapeHelper.innerHTML = candidate;
      const unescaped = unescapeHelper.innerText;
      if (unescaped == candidate) { // not a valid entity
        return candidate;
      }
      return unescaped;
    });
    unescapeHelper.innerHTML = "";

    return s;
  }

  function shouldAdd(current, element) {
    return !NEEDS_PARENT[element.localName] ||
            NEEDS_PARENT[element.localName] === current.localName;
  }

  // Create an element for this tag.
  function createElement(type, annotation) {
    let tagName = TAG_NAME[type];
    if (!tagName) {
      return null;
    }
    let element = window.document.createElement(tagName);
    let name = TAG_ANNOTATION[type];
    if (name) {
      element[name] = annotation ? annotation.trim() : "";
    }
    return element;
  }

  // https://w3c.github.io/webvtt/#webvtt-timestamp-object
  // Return hhhhh:mm:ss.fff
  function normalizedTimeStamp(secondsWithFrag) {
    let totalsec = parseInt(secondsWithFrag, 10);
    let hours = Math.floor(totalsec / 3600);
    let minutes = Math.floor(totalsec % 3600 / 60);
    let seconds = Math.floor(totalsec % 60);
    if (hours < 10) {
      hours = "0" + hours;
    }
    if (minutes < 10) {
      minutes = "0" + minutes;
    }
    if (seconds < 10) {
      seconds = "0" + seconds;
    }
    let f = secondsWithFrag.toString().split(".");
    if (f[1]) {
      f = f[1].slice(0, 3).padEnd(3, "0");
    } else {
      f = "000";
    }
    return hours + ':' + minutes + ':' + seconds + '.' + f;
  }

  let root;
  switch (mode) {
    case PARSE_CONTENT_MODE.NORMAL_CUE:
      root = window.document.createElement("span", {pseudo: "::cue"});
      break;
    case PARSE_CONTENT_MODE.REGION_CUE:
      root = window.document.createElement("span");
      break;
    case PARSE_CONTENT_MODE.DOCUMENT_FRAGMENT:
      root = window.document.createDocumentFragment();
      break;
  }

  if (!input) {
    root.appendChild(window.document.createTextNode(""));
    return root;
  }

  let current = root,
      t,
      tagStack = [];

  while ((t = nextToken()) !== null) {
    if (t[0] === '<') {
      if (t[1] === "/") {
        const endTag = t.slice(2, -1);
        const stackEnd = tagStack.at(-1);

        // If the closing tag matches, move back up to the parent node.
        if (stackEnd == endTag) {
          tagStack.pop();
          current = current.parentNode;

        // If the closing tag is <ruby> and we're at an <rt>, move back up to
        // the <ruby>'s parent node.
        } else if (endTag == "ruby" && current.nodeName == "RT") {
          tagStack.pop();
          current = current.parentNode.parentNode;
        }

        // Otherwise just ignore the end tag.
        continue;
      }
      let ts = collectTimeStamp(t.substr(1, t.length - 1));
      let node;
      if (ts) {
        // Timestamps are lead nodes as well.
        node = window.document.createProcessingInstruction("timestamp", normalizedTimeStamp(ts));
        current.appendChild(node);
        continue;
      }
      let m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/);
      // If we can't parse the tag, skip to the next tag.
      if (!m) {
        continue;
      }
      // Try to construct an element, and ignore the tag if we couldn't.
      node = createElement(m[1], m[3]);
      if (!node) {
        continue;
      }
      // Determine if the tag should be added based on the context of where it
      // is placed in the cuetext.
      if (!shouldAdd(current, node)) {
        continue;
      }
      // Set the class list (as a list of classes, separated by space).
      if (m[2]) {
        node.className = m[2].substr(1).replace('.', ' ');
      }
      // Append the node to the current node, and enter the scope of the new
      // node.
      tagStack.push(m[1]);
      current.appendChild(node);
      current = node;
      continue;
    }

    // Text nodes are leaf nodes.
    current.appendChild(window.document.createTextNode(unescapeEntities(t)));
  }

  return root;
}

function StyleBox() {
}

// Apply styles to a div. If there is no div passed then it defaults to the
// div on 'this'.
StyleBox.prototype.applyStyles = function(styles, div) {
  div = div || this.div;
  for (let prop in styles) {
    if (styles.hasOwnProperty(prop)) {
      div.style[prop] = styles[prop];
    }
  }
};

StyleBox.prototype.formatStyle = function(val, unit) {
  return val === 0 ? 0 : val + unit;
};

// TODO(alwu): remove StyleBox and change other style box to class-based.
class StyleBoxBase {
  applyStyles(styles, div) {
    div = div || this.div;
    Object.assign(div.style, styles);
  }

  formatStyle(val, unit) {
    return val === 0 ? 0 : val + unit;
  }
}

// Constructs the computed display state of the cue (a div). Places the div
// into the overlay which should be a block level element (usually a div).
class CueStyleBox extends StyleBoxBase {
  constructor(window, cue, containerBox) {
    super();
    this.cue = cue;
    this.div = window.document.createElement("div");
    this.cueDiv = parseContent(window, cue.text, PARSE_CONTENT_MODE.NORMAL_CUE);
    this.div.appendChild(this.cueDiv);

    this.containerHeight = containerBox.height;
    this.containerWidth = containerBox.width;
    this.fontSize = this._getFontSize(containerBox);
    this.isCueStyleBox = true;

    // As pseudo element won't inherit the parent div's style, so we have to
    // set the font size explicitly.
    this._applyDefaultStylesOnBackgroundNode();
    this._applyDefaultStylesOnRootNode();
  }

  getCueBoxPositionAndSize() {
    // As `top`, `left`, `width` and `height` are all represented by the
    // percentage of the container, we need to convert them to the actual
    // number according to the container's size.
    const isWritingDirectionHorizontal = this.cue.vertical == "";
    let top =
          this.containerHeight * this._tranferPercentageToFloat(this.div.style.top),
        left =
          this.containerWidth * this._tranferPercentageToFloat(this.div.style.left),
        width = isWritingDirectionHorizontal ?
          this.containerWidth * this._tranferPercentageToFloat(this.div.style.width) :
          this.div.clientWidthDouble,
        height = isWritingDirectionHorizontal ?
          this.div.clientHeightDouble :
          this.containerHeight * this._tranferPercentageToFloat(this.div.style.height);
    return { top, left, width, height };
  }

  getFirstLineBoxSize() {
    // This size would be automatically adjusted by writing direction. When
    // direction is horizontal, it represents box's height. When direction is
    // vertical, it represents box's width.
    return this.div.firstLineBoxBSize;
  }

  setBidiRule() {
    // This function is a workaround which is used to force the reflow in order
    // to use the correct alignment for bidi text. Now this function would be
    // called after calculating the final position of the cue box to ensure the
    // rendering result is correct. See bug1557882 comment3 for more details.
    // TODO : remove this function and set `unicode-bidi` when initiailizing
    // the CueStyleBox, after fixing bug1558431.
    this.applyStyles({ "unicode-bidi": "plaintext" });
  }

  /**
   * Following methods are private functions, should not use them outside this
   * class.
   */
  _tranferPercentageToFloat(input) {
    return input.replace("%", "") / 100.0;
  }

  _getFontSize(containerBox) {
    // In https://www.w3.org/TR/webvtt1/#applying-css-properties, the spec
    // said the font size is '5vh', which means 5% of the viewport height.
    // However, if we use 'vh' as a basic unit, it would eventually become
    // 5% of screen height, instead of video's viewport height. Therefore, we
    // have to use 'px' here to make sure we have the correct font size.
    return containerBox.height * 0.05 + "px";
  }

  _applyDefaultStylesOnBackgroundNode() {
    // most of the properties have been defined in `::cue` in `html.css`, but
    // there are some css properties we have to set them dynamically.
    // FIXME(emilio): These are observable by content. Ideally the style
    // attribute will work like for ::part() and we wouldn't need this.
    this.cueDiv.style.setProperty("--cue-font-size", this.fontSize, "important");
    this.cueDiv.style.setProperty("--cue-writing-mode", this._getCueWritingMode(), "important");
  }

  // spec https://www.w3.org/TR/webvtt1/#applying-css-properties
  _applyDefaultStylesOnRootNode() {
    // The variables writing-mode, top, left, width, and height are calculated
    // in the spec 7.2, https://www.w3.org/TR/webvtt1/#processing-cue-settings
    // spec 7.2.1, calculate 'writing-mode'.
    const writingMode = this._getCueWritingMode();

    // spec 7.2.2 ~ 7.2.7, calculate 'width', 'height', 'left' and 'top'.
    const {width, height, left, top} = this._getCueSizeAndPosition();

    this.applyStyles({
      "position": "absolute",
      // "unicode-bidi": "plaintext", (uncomment this line after fixing bug1558431)
      "writing-mode": writingMode,
      "top": top,
      "left": left,
      "width": width,
      "height": height,
      "overflow-wrap": "break-word",
      // "text-wrap": "balance", (we haven't supported this CSS attribute yet)
      "white-space": "pre-line",
      "font": this.fontSize + " sans-serif",
      "color": "rgba(255, 255, 255, 1)",
      "white-space": "pre-line",
      "text-align": this.cue.align,
    });
  }

  _getCueWritingMode() {
    const cue = this.cue;
    if (cue.vertical == "") {
      return "horizontal-tb";
    }
    return cue.vertical == "lr" ? "vertical-lr" : "vertical-rl";
  }

  _getCueSizeAndPosition() {
    const cue = this.cue;
    // spec 7.2.2, determine the value of maximum size for cue as per the
    // appropriate rules from the following list.
    let maximumSize;
    let computedPosition = cue.computedPosition;
    switch (cue.computedPositionAlign) {
      case "line-left":
        maximumSize = 100 - computedPosition;
        break;
      case "line-right":
        maximumSize = computedPosition;
        break;
      case "center":
        maximumSize = computedPosition <= 50 ?
          computedPosition * 2 : (100 - computedPosition) * 2;
        break;
    }
    const size = Math.min(cue.size, maximumSize);

    // spec 7.2.5, determine the value of x-position or y-position for cue as
    // per the appropriate rules from the following list.
    let xPosition = 0.0, yPosition = 0.0;
    const isWritingDirectionHorizontal = cue.vertical == "";
    switch (cue.computedPositionAlign) {
      case "line-left":
        if (isWritingDirectionHorizontal) {
          xPosition = cue.computedPosition;
        } else {
          yPosition = cue.computedPosition;
        }
        break;
      case "center":
        if (isWritingDirectionHorizontal) {
          xPosition = cue.computedPosition - (size / 2);
        } else {
          yPosition = cue.computedPosition - (size / 2);
        }
        break;
      case "line-right":
        if (isWritingDirectionHorizontal) {
          xPosition = cue.computedPosition - size;
        } else {
          yPosition = cue.computedPosition - size;
        }
        break;
    }

    // spec 7.2.6, determine the value of whichever of x-position or
    // y-position is not yet calculated for cue as per the appropriate rules
    // from the following list.
    if (!cue.snapToLines) {
      if (isWritingDirectionHorizontal) {
        yPosition = cue.computedLine;
      } else {
        xPosition = cue.computedLine;
      }
    } else {
      if (isWritingDirectionHorizontal) {
        yPosition = 0;
      } else {
        xPosition = 0;
      }
    }
    return {
      left: xPosition + "%",
      top: yPosition + "%",
      width: isWritingDirectionHorizontal ? size + "%" : "auto",
      height: isWritingDirectionHorizontal ? "auto" : size + "%",
    };
  }
}

function RegionNodeBox(window, region, container) {
  StyleBox.call(this);

  let boxLineHeight = container.height * 0.0533 // 0.0533vh ? 5.33vh
  let boxHeight = boxLineHeight * region.lines;
  let boxWidth = container.width * region.width / 100; // convert percentage to px

  let regionNodeStyles = {
    position: "absolute",
    height: boxHeight + "px",
    width: boxWidth + "px",
    top: (region.viewportAnchorY * container.height / 100) - (region.regionAnchorY * boxHeight / 100) + "px",
    left: (region.viewportAnchorX * container.width / 100) - (region.regionAnchorX * boxWidth / 100) + "px",
    lineHeight: boxLineHeight + "px",
    writingMode: "horizontal-tb",
    backgroundColor: "rgba(0, 0, 0, 0.8)",
    wordWrap: "break-word",
    overflowWrap: "break-word",
    font: (boxLineHeight/1.3) + "px sans-serif",
    color: "rgba(255, 255, 255, 1)",
    overflow: "hidden",
    minHeight: "0px",
    maxHeight: boxHeight + "px",
    display: "inline-flex",
    flexFlow: "column",
    justifyContent: "flex-end",
  };

  this.div = window.document.createElement("div");
  this.div.id = region.id; // useless?
  this.applyStyles(regionNodeStyles);
}
RegionNodeBox.prototype = _objCreate(StyleBox.prototype);
RegionNodeBox.prototype.constructor = RegionNodeBox;

function RegionCueStyleBox(window, cue) {
  StyleBox.call(this);
  this.cueDiv = parseContent(window, cue.text, PARSE_CONTENT_MODE.REGION_CUE);

  let regionCueStyles = {
    position: "relative",
    writingMode: "horizontal-tb",
    unicodeBidi: "plaintext",
    width: "auto",
    height: "auto",
    textAlign: cue.align,
  };
  // TODO: fix me, LTR and RTL ? using margin replace the "left/right"
  // 6.1.14.3.3
  let offset = cue.computedPosition * cue.region.width / 100;
  // 6.1.14.3.4
  switch (cue.align) {
    case "start":
    case "left":
      regionCueStyles.left = offset + "%";
      regionCueStyles.right = "auto";
      break;
    case "end":
    case "right":
      regionCueStyles.left = "auto";
      regionCueStyles.right = offset + "%";
      break;
    case "middle":
      break;
  }

  this.div = window.document.createElement("div");
  this.applyStyles(regionCueStyles);
  this.div.appendChild(this.cueDiv);
}
RegionCueStyleBox.prototype = _objCreate(StyleBox.prototype);
RegionCueStyleBox.prototype.constructor = RegionCueStyleBox;

// Represents the co-ordinates of an Element in a way that we can easily
// compute things with such as if it overlaps or intersects with other boxes.
class BoxPosition {
  constructor(obj) {
    // Get dimensions by calling getCueBoxPositionAndSize on a CueStyleBox, by
    // getting offset properties from an HTMLElement (from the object or its
    // `div` property), otherwise look at the regular box properties on the
    // object.
    const isHTMLElement = !obj.isCueStyleBox && (obj.div || obj.tagName);
    obj = obj.isCueStyleBox ? obj.getCueBoxPositionAndSize() : obj.div || obj;
    this.top = isHTMLElement ? obj.offsetTop : obj.top;
    this.left = isHTMLElement ? obj.offsetLeft : obj.left;
    this.width = isHTMLElement ? obj.offsetWidth : obj.width;
    this.height = isHTMLElement ? obj.offsetHeight : obj.height;
    // This value is smaller than 1 app unit (~= 0.0166 px).
    this.fuzz = 0.01;
  }

  get bottom() {
    return this.top + this.height;
  }

  get right() {
    return this.left + this.width;
  }

  // This function is used for debugging, it will return the box's information.
  getBoxInfoInChars() {
    return `top=${this.top}, bottom=${this.bottom}, left=${this.left}, ` +
            `right=${this.right}, width=${this.width}, height=${this.height}`;
  }

  // Move the box along a particular axis. Optionally pass in an amount to move
  // the box. If no amount is passed then the default is the line height of the
  // box.
  move(axis, toMove) {
    switch (axis) {
    case "+x":
      LOG(`box's left moved from ${this.left} to ${this.left + toMove}`);
      this.left += toMove;
      break;
    case "-x":
      LOG(`box's left moved from ${this.left} to ${this.left - toMove}`);
      this.left -= toMove;
      break;
    case "+y":
      LOG(`box's top moved from ${this.top} to ${this.top + toMove}`);
      this.top += toMove;
      break;
    case "-y":
      LOG(`box's top moved from ${this.top} to ${this.top - toMove}`);
      this.top -= toMove;
      break;
    }
  }

  // Check if this box overlaps another box, b2.
  overlaps(b2) {
    return (this.left < b2.right - this.fuzz) &&
            (this.right > b2.left + this.fuzz) &&
            (this.top < b2.bottom - this.fuzz) &&
            (this.bottom > b2.top + this.fuzz);
  }

  // Check if this box overlaps any other boxes in boxes.
  overlapsAny(boxes) {
    for (let i = 0; i < boxes.length; i++) {
      if (this.overlaps(boxes[i])) {
        return true;
      }
    }
    return false;
  }

  // Check if this box is within another box.
  within(container) {
    return (this.top >= container.top - this.fuzz) &&
            (this.bottom <= container.bottom + this.fuzz) &&
            (this.left >= container.left - this.fuzz) &&
            (this.right <= container.right + this.fuzz);
  }

  // Check whether this box is passed over the specfic axis boundary. The axis
  // is based on the canvas coordinates, the `+x` is rightward and `+y` is
  // downward.
  isOutsideTheAxisBoundary(container, axis) {
    switch (axis) {
    case "+x":
      return this.right > container.right + this.fuzz;
    case "-x":
      return this.left < container.left - this.fuzz;
    case "+y":
      return this.bottom > container.bottom + this.fuzz;
    case "-y":
      return this.top < container.top - this.fuzz;
    }
  }

  // Find the percentage of the area that this box is overlapping with another
  // box.
  intersectPercentage(b2) {
    let x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
        y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
        intersectArea = x * y;
    return intersectArea / (this.height * this.width);
  }
}

BoxPosition.prototype.clone = function(){
  return new BoxPosition(this);
};

function adjustBoxPosition(styleBox, containerBox, controlBarBox, outputBoxes) {
  const cue = styleBox.cue;
  const isWritingDirectionHorizontal = cue.vertical == "";
  let box = new BoxPosition(styleBox);
  if (!box.width || !box.height) {
    LOG(`No way to adjust a box with zero width or height.`);
    return;
  }

  // Spec 7.2.10, adjust the positions of boxes according to the appropriate
  // steps from the following list. Also, we use offsetHeight/offsetWidth here
  // in order to prevent the incorrect positioning caused by CSS transform
  // scale.
  const fullDimension = isWritingDirectionHorizontal ?
    containerBox.height : containerBox.width;
  if (cue.snapToLines) {
    LOG(`Adjust position when 'snap-to-lines' is true.`);
    // The step is the height or width of the line box. We should use font
    // size directly, instead of using text box's width or height, because the
    // width or height of the box would be changed when the text is wrapped to
    // different line. Ex. if text is wrapped to two line, the height or width
    // of the box would become 2 times of font size.
    let step = styleBox.getFirstLineBoxSize();
    if (step == 0) {
      return;
    }

    // spec 7.2.10.4 ~ 7.2.10.6
    let line = Math.floor(cue.computedLine + 0.5);
    if (cue.vertical == "rl") {
      line = -1 * (line + 1);
    }

    // spec 7.2.10.7 ~ 7.2.10.8
    let position = step * line;
    if (cue.vertical == "rl") {
      position = position - box.width + step;
    }

    // spec 7.2.10.9
    if (line < 0) {
      position += fullDimension;
      step = -1 * step;
    }

    // spec 7.2.10.10, move the box to the specific position along the direction.
    const movingDirection = isWritingDirectionHorizontal ? "+y" : "+x";
    box.move(movingDirection, position);

    // spec 7.2.10.11, remember the position as specified position.
    let specifiedPosition = box.clone();

    // spec 7.2.10.12, let title area be a box that covers all of the video’s
    // rendering area.
    const titleAreaBox = containerBox.clone();
    if (controlBarBox) {
      titleAreaBox.height -= controlBarBox.height;
    }

    function isBoxOutsideTheRenderingArea() {
      if (isWritingDirectionHorizontal) {
        // the top side of the box is above the rendering area, or the bottom
        // side of the box is below the rendering area.
        return step < 0 && box.top < 0 ||
                step > 0 && box.bottom > fullDimension;
      }
      // the left side of the box is outside the left side of the rendering
      // area, or the right side of the box is outside the right side of the
      // rendering area.
      return step < 0 && box.left < 0 ||
              step > 0 && box.right > fullDimension;
    }

    // spec 7.2.10.13, if none of the boxes in boxes would overlap any of the
    // boxes in output, and all of the boxes in boxes are entirely within the
    // title area box.
    let switched = false;
    while (!box.within(titleAreaBox) || box.overlapsAny(outputBoxes)) {
      // spec 7.2.10.14, check if we need to switch the direction.
      if (isBoxOutsideTheRenderingArea()) {
        // spec 7.2.10.17, if `switched` is true, remove all the boxes in
        // `boxes`, which means we shouldn't apply any CSS boxes for this cue.
        // Therefore, returns null box.
        if (switched) {
          return null;
        }
        // spec 7.2.10.18 ~ 7.2.10.20
        switched = true;
        box = specifiedPosition.clone();
        step = -1 * step;
      }
      // spec 7.2.10.15, moving box along the specific direction.
      box.move(movingDirection, step);
    }

    if (isWritingDirectionHorizontal) {
      styleBox.applyStyles({
        top: getPercentagePosition(box.top, fullDimension),
      });
    } else {
      styleBox.applyStyles({
        left: getPercentagePosition(box.left, fullDimension),
      });
    }
  } else {
    LOG(`Adjust position when 'snap-to-lines' is false.`);
    // (snap-to-lines if false) spec 7.2.10.1 ~ 7.2.10.2
    if (cue.lineAlign != "start") {
      const isCenterAlign = cue.lineAlign == "center";
      const movingDirection = isWritingDirectionHorizontal ? "-y" : "-x";
      if (isWritingDirectionHorizontal) {
        box.move(movingDirection, isCenterAlign ? box.height : box.height / 2);
      } else {
        box.move(movingDirection, isCenterAlign ? box.width : box.width / 2);
      }
    }

    // spec 7.2.10.3
    let bestPosition = {},
        specifiedPosition = box.clone(),
        outsideAreaPercentage = 1; // Highest possible so the first thing we get is better.
    let hasFoundBestPosition = false;

    // For the different writing directions, we should have different priority
    // for the moving direction. For example, if the writing direction is
    // horizontal, which means the cues will grow from the top to the bottom,
    // then moving cues along the `y` axis should be more important than moving
    // cues along the `x` axis, and vice versa for those cues growing from the
    // left to right, or from the right to the left. We don't follow the exact
    // way which the spec requires, see the reason in bug1575460.
    function getAxis(writingDirection) {
      if (writingDirection == "") {
        return ["+y", "-y", "+x", "-x"];
      }
      // Growing from left to right.
      if (writingDirection == "lr") {
        return ["+x", "-x", "+y", "-y"];
      }
      // Growing from right to left.
      return ["-x", "+x", "+y", "-y"];
    }
    const axis = getAxis(cue.vertical);

    // This factor effects the granularity of the moving unit, when using the
    // factor=1 often moves too much and results in too many redudant spaces
    // between boxes. So we can increase the factor to slightly reduce the
    // move we do every time, but still can preverse the reasonable spaces
    // between boxes.
    const factor = 4;
    const toMove = styleBox.getFirstLineBoxSize() / factor;
    for (let i = 0; i < axis.length && !hasFoundBestPosition; i++) {
      while (!box.isOutsideTheAxisBoundary(containerBox, axis[i]) &&
              (!box.within(containerBox) || box.overlapsAny(outputBoxes))) {
        box.move(axis[i], toMove);
      }
      // We found a spot where we aren't overlapping anything. This is our
      // best position.
      if (box.within(containerBox)) {
        bestPosition = box.clone();
        hasFoundBestPosition = true;
        break;
      }
      let p = box.intersectPercentage(containerBox);
      // If we're outside the container box less then we were on our last try
      // then remember this position as the best position.
      if (outsideAreaPercentage > p) {
        bestPosition = box.clone();
        outsideAreaPercentage = p;
      }
      // Reset the box position to the specified position.
      box = specifiedPosition.clone();
    }

    // Can not find a place to place this box inside the rendering area.
    if (!box.within(containerBox)) {
      return null;
    }

    styleBox.applyStyles({
      top: getPercentagePosition(box.top, containerBox.height),
      left: getPercentagePosition(box.left, containerBox.width),
    });
  }

  // In order to not be affected by CSS scale, so we use '%' to make sure the
  // cue can stick in the right position.
  function getPercentagePosition(position, fullDimension) {
    return (position / fullDimension) * 100 + "%";
  }

  return box;
}

export function WebVTT() {
  this.isProcessingCues = false;
  // Nothing
}

// Helper to allow strings to be decoded instead of the default binary utf8 data.
WebVTT.StringDecoder = function() {
  return {
    decode: function(data) {
      if (!data) {
        return "";
      }
      if (typeof data !== "string") {
        throw new Error("Error - expected string data.");
      }
      return decodeURIComponent(encodeURIComponent(data));
    }
  };
};

WebVTT.convertCueToDOMTree = function(window, cuetext) {
  if (!window) {
    return null;
  }
  return parseContent(window, cuetext, PARSE_CONTENT_MODE.DOCUMENT_FRAGMENT);
};

function clearAllCuesDiv(overlay) {
  while (overlay.firstChild) {
    overlay.firstChild.remove();
  }
}

// It's used to record how many cues we process in the last `processCues` run.
var lastDisplayedCueNums = 0;

const DIV_COMPUTING_STATE = {
  REUSE : 0,
  REUSE_AND_CLEAR : 1,
  COMPUTE_AND_CLEAR : 2
};

// Runs the processing model over the cues and regions passed to it.
// Spec https://www.w3.org/TR/webvtt1/#processing-model
// @parem window : JS window
// @param cues : the VTT cues are going to be displayed.
// @param overlay : A block level element (usually a div) that the computed cues
//                and regions will be placed into.
// @param controls : A Control bar element. Cues' position will be
//                 affected and repositioned according to it.
function processCuesInternal(window, cues, overlay, controls) {
  LOG(`=== processCues ===`);
  if (!cues) {
    LOG(`clear display and abort processing because of no cue.`);
    clearAllCuesDiv(overlay);
    lastDisplayedCueNums = 0;
    return;
  }

  let controlBar, controlBarShown;
  if (controls) {
    // controls is a <div> that is the children of the UA Widget Shadow Root.
    controlBar = controls.parentNode.getElementById("controlBar");
    controlBarShown = controlBar ? !controlBar.hidden : false;
  } else {
    // There is no controls element. This only happen to UA Widget because
    // it is created lazily.
    controlBarShown = false;
  }

  /**
   * This function is used to tell us if we have to recompute or reuse current
   * cue's display state. Display state is a DIV element with corresponding
   * CSS style to display cue on the screen. When the cue is being displayed
   * first time, we will compute its display state. After that, we could reuse
   * its state until following conditions happen.
   * (1) control changes : it means the rendering area changes so we should
   * recompute cues' position.
   * (2) cue's `hasBeenReset` flag is true : it means cues' line or position
   * property has been modified, we also need to recompute cues' position.
   * (3) the amount of showing cues changes : it means some cue would disappear
   * but other cues should stay at the same place without recomputing, so we
   * can resume their display state.
   */
  function getDIVComputingState(cues) {
    if (overlay.lastControlBarShownStatus != controlBarShown) {
      return DIV_COMPUTING_STATE.COMPUTE_AND_CLEAR;
    }

    for (let i = 0; i < cues.length; i++) {
      if (cues[i].hasBeenReset || !cues[i].displayState) {
        return DIV_COMPUTING_STATE.COMPUTE_AND_CLEAR;
      }
    }

    if (lastDisplayedCueNums != cues.length) {
      return DIV_COMPUTING_STATE.REUSE_AND_CLEAR;
    }
    return DIV_COMPUTING_STATE.REUSE;
  }

  const divState = getDIVComputingState(cues);
  overlay.lastControlBarShownStatus = controlBarShown;

  if (divState == DIV_COMPUTING_STATE.REUSE) {
    LOG(`reuse current cue's display state and abort processing`);
    return;
  }

  clearAllCuesDiv(overlay);
  let rootOfCues = window.document.createElement("div");
  rootOfCues.style.position = "absolute";
  rootOfCues.style.left = "0";
  rootOfCues.style.right = "0";
  rootOfCues.style.top = "0";
  rootOfCues.style.bottom = "0";
  overlay.appendChild(rootOfCues);

  if (divState == DIV_COMPUTING_STATE.REUSE_AND_CLEAR) {
    LOG(`clear display but reuse cues' display state.`);
    for (let cue of cues) {
      rootOfCues.appendChild(cue.displayState);
    }
  } else if (divState == DIV_COMPUTING_STATE.COMPUTE_AND_CLEAR) {
    LOG(`clear display and recompute cues' display state.`);
    let boxPositions = [],
      containerBox = new BoxPosition(rootOfCues);

    let styleBox, cue, controlBarBox;
    if (controlBarShown) {
      controlBarBox = new BoxPosition(controlBar);
      // Add an empty output box that cover the same region as video control bar.
      boxPositions.push(controlBarBox);
    }

    // https://w3c.github.io/webvtt/#processing-model 6.1.12.1
    // Create regionNode
    let regionNodeBoxes = {};
    let regionNodeBox;

    LOG(`lastDisplayedCueNums=${lastDisplayedCueNums}, currentCueNums=${cues.length}`);
    lastDisplayedCueNums = cues.length;
    for (let i = 0; i < cues.length; i++) {
      cue = cues[i];
      if (cue.region != null) {
        // 6.1.14.1
        styleBox = new RegionCueStyleBox(window, cue);

        if (!regionNodeBoxes[cue.region.id]) {
          // create regionNode
          // Adjust the container hieght to exclude the controlBar
          let adjustContainerBox = new BoxPosition(rootOfCues);
          if (controlBarShown) {
            adjustContainerBox.height -= controlBarBox.height;
            adjustContainerBox.bottom += controlBarBox.height;
          }
          regionNodeBox = new RegionNodeBox(window, cue.region, adjustContainerBox);
          regionNodeBoxes[cue.region.id] = regionNodeBox;
        }
        // 6.1.14.3
        let currentRegionBox = regionNodeBoxes[cue.region.id];
        let currentRegionNodeDiv = currentRegionBox.div;
        // 6.1.14.3.2
        // TODO: fix me, it looks like the we need to set/change "top" attribute at the styleBox.div
        // to do the "scroll up", however, we do not implement it yet?
        if (cue.region.scroll == "up" && currentRegionNodeDiv.childElementCount > 0) {
          styleBox.div.style.transitionProperty = "top";
          styleBox.div.style.transitionDuration = "0.433s";
        }

        currentRegionNodeDiv.appendChild(styleBox.div);
        rootOfCues.appendChild(currentRegionNodeDiv);
        cue.displayState = styleBox.div;
        boxPositions.push(new BoxPosition(currentRegionBox));
      } else {
        // Compute the intial position and styles of the cue div.
        styleBox = new CueStyleBox(window, cue, containerBox);
        rootOfCues.appendChild(styleBox.div);

        // Move the cue to correct position, we might get the null box if the
        // result of algorithm doesn't want us to show the cue when we don't
        // have any room for this cue.
        let cueBox = adjustBoxPosition(styleBox, containerBox, controlBarBox, boxPositions);
        if (cueBox) {
          styleBox.setBidiRule();
          // Remember the computed div so that we don't have to recompute it later
          // if we don't have too.
          cue.displayState = styleBox.div;
          boxPositions.push(cueBox);
          LOG(`cue ${i}, ` + cueBox.getBoxInfoInChars());
        } else {
          LOG(`can not find a proper position to place cue ${i}`);
          // Clear the display state and clear the reset flag in the cue as well,
          // which controls whether the task for updating the cue display is
          // dispatched.
          cue.displayState = null;
          rootOfCues.removeChild(styleBox.div);
        }
      }
    }
  } else {
    LOG(`[ERROR] unknown div computing state`);
  }
};

WebVTT.processCues = function(window, cues, overlay, controls) {
  // When accessing `offsetXXX` attributes of element, it would trigger reflow
  // and might result in a re-entry of this function. In order to avoid doing
  // redundant computation, we would only do one processing at a time.
  if (this.isProcessingCues) {
    return;
  }
  this.isProcessingCues = true;
  processCuesInternal(window, cues, overlay, controls);
  this.isProcessingCues = false;
};

WebVTT.Parser = function(window, decoder) {
  this.window = window;
  this.state = "INITIAL";
  this.substate = "";
  this.substatebuffer = "";
  this.buffer = "";
  this.decoder = decoder || new TextDecoder("utf8");
  this.regionList = [];
  this.isPrevLineBlank = false;
};

WebVTT.Parser.prototype = {
  // If the error is a ParsingError then report it to the consumer if
  // possible. If it's not a ParsingError then throw it like normal.
  reportOrThrowError: function(e) {
    if (e instanceof ParsingError) {
      this.onparsingerror && this.onparsingerror(e);
    } else {
      throw e;
    }
  },
  parse: function (data) {
    // If there is no data then we won't decode it, but will just try to parse
    // whatever is in buffer already. This may occur in circumstances, for
    // example when flush() is called.
    if (data) {
      // Try to decode the data that we received.
      this.buffer += this.decoder.decode(data, {stream: true});
    }

    // This parser is line-based. Let's see if we have a line to parse.
    while (/\r\n|\n|\r/.test(this.buffer)) {
      let buffer = this.buffer;
      let pos = 0;
      while (buffer[pos] !== '\r' && buffer[pos] !== '\n') {
        ++pos;
      }
      let line = buffer.substr(0, pos);
      // Advance the buffer early in case we fail below.
      if (buffer[pos] === '\r') {
        ++pos;
      }
      if (buffer[pos] === '\n') {
        ++pos;
      }
      this.buffer = buffer.substr(pos);

      // Spec defined replacement.
      line = line.replace(/[\u0000]/g, "\uFFFD");

      // Detect the comment. We parse line on the fly, so we only check if the
      // comment block is preceded by a blank line and won't check if it's
      // followed by another blank line.
      // https://www.w3.org/TR/webvtt1/#introduction-comments
      // TODO (1703895): according to the spec, the comment represents as a
      // comment block, so we need to refactor the parser in order to better
      // handle the comment block.
      if (this.isPrevLineBlank && /^NOTE($|[ \t])/.test(line)) {
        LOG("Ignore comment that starts with 'NOTE'");
      } else {
        this.parseLine(line);
      }
      this.isPrevLineBlank = emptyOrOnlyContainsWhiteSpaces(line);
    }

    return this;
  },
  parseLine: function(line) {
    let self = this;

    function createCueIfNeeded() {
      if (!self.cue) {
        self.cue = new self.window.VTTCue(0, 0, "");
      }
    }

    // Parsing cue identifier and the identifier should be unique.
    // Return true if the input is a cue identifier.
    function parseCueIdentifier(input) {
      if (maybeIsTimeStampFormat(input)) {
        self.state = "CUE";
        return false;
      }

      createCueIfNeeded();
      // TODO : ensure the cue identifier is unique among all cue identifiers.
      self.cue.id = containsTimeDirectionSymbol(input) ? "" : input;
      self.state = "CUE";
      return true;
    }

    // Parsing the timestamp and cue settings.
    // See spec, https://w3c.github.io/webvtt/#collect-webvtt-cue-timings-and-settings
    function parseCueMayThrow(input) {
      try {
        createCueIfNeeded();
        parseCue(input, self.cue, self.regionList);
        self.state = "CUETEXT";
      } catch (e) {
        self.reportOrThrowError(e);
        // In case of an error ignore rest of the cue.
        self.cue = null;
        self.state = "BADCUE";
      }
    }

    // 3.4 WebVTT region and WebVTT region settings syntax
    function parseRegion(input) {
      let settings = new Settings();
      parseOptions(input, function (k, v) {
        switch (k) {
        case "id":
          settings.set(k, v);
          break;
        case "width":
          settings.percent(k, v);
          break;
        case "lines":
          settings.digitsValue(k, v);
          break;
        case "regionanchor":
        case "viewportanchor": {
          let xy = v.split(',');
          if (xy.length !== 2) {
            break;
          }
          // We have to make sure both x and y parse, so use a temporary
          // settings object here.
          let anchor = new Settings();
          anchor.percent("x", xy[0]);
          anchor.percent("y", xy[1]);
          if (!anchor.has("x") || !anchor.has("y")) {
            break;
          }
          settings.set(k + "X", anchor.get("x"));
          settings.set(k + "Y", anchor.get("y"));
          break;
        }
        case "scroll":
          settings.alt(k, v, ["up"]);
          break;
        }
      }, /:/, /\t|\n|\f|\r| /); // groupDelim is ASCII whitespace
      // https://infra.spec.whatwg.org/#ascii-whitespace, U+0009 TAB, U+000A LF, U+000C FF, U+000D CR, U+0020 SPACE

      // Create the region, using default values for any values that were not
      // specified.
      if (settings.has("id")) {
        try {
          let region = new self.window.VTTRegion();
          region.id = settings.get("id", "");
          region.width = settings.get("width", 100);
          region.lines = settings.get("lines", 3);
          region.regionAnchorX = settings.get("regionanchorX", 0);
          region.regionAnchorY = settings.get("regionanchorY", 100);
          region.viewportAnchorX = settings.get("viewportanchorX", 0);
          region.viewportAnchorY = settings.get("viewportanchorY", 100);
          region.scroll = settings.get("scroll", "");
          // Register the region.
          self.onregion && self.onregion(region);
          // Remember the VTTRegion for later in case we parse any VTTCues that
          // reference it.
          self.regionList.push({
            id: settings.get("id"),
            region: region
          });
        } catch(e) {
          dump("VTTRegion Error " + e + "\n");
        }
      }
    }

    // Parsing the WebVTT signature, it contains parsing algo step1 to step9.
    // See spec, https://w3c.github.io/webvtt/#file-parsing
    function parseSignatureMayThrow(signature) {
      if (!/^WEBVTT([ \t].*)?$/.test(signature)) {
        throw new ParsingError(ParsingError.Errors.BadSignature);
      } else {
        self.state = "HEADER";
      }
    }

    function parseRegionOrStyle(input) {
      switch (self.substate) {
        case "REGION":
          parseRegion(input);
        break;
        case "STYLE":
          // TODO : not supported yet.
        break;
      }
    }
    // Parsing the region and style information.
    // See spec, https://w3c.github.io/webvtt/#collect-a-webvtt-block
    //
    // There are sereval things would appear in header,
    //   1. Region or Style setting
    //   2. Garbage (meaningless string)
    //   3. Empty line
    //   4. Cue's timestamp
    // The case 4 happens when there is no line interval between the header
    // and the cue blocks. In this case, we should preserve the line for the
    // next phase parsing, returning "true".
    function parseHeader(line) {
      if (!self.substate && /^REGION|^STYLE/.test(line)) {
        self.substate = /^REGION/.test(line) ? "REGION" : "STYLE";
        return false;
      }

      if (self.substate === "REGION" || self.substate === "STYLE") {
        if (maybeIsTimeStampFormat(line) ||
            emptyOrOnlyContainsWhiteSpaces(line) ||
            containsTimeDirectionSymbol(line)) {
          parseRegionOrStyle(self.substatebuffer);
          self.substatebuffer = "";
          self.substate = null;

          // This is the end of the region or style state.
          return parseHeader(line);
        }

        if (/^REGION|^STYLE/.test(line)) {
          // The line is another REGION/STYLE, parse and reset substatebuffer.
          // Don't break the while loop to parse the next REGION/STYLE.
          parseRegionOrStyle(self.substatebuffer);
          self.substatebuffer = "";
          self.substate = /^REGION/.test(line) ? "REGION" : "STYLE";
          return false;
        }

        // We weren't able to parse the line as a header. Accumulate and
        // return.
        self.substatebuffer += " " + line;
        return false;
      }

      if (emptyOrOnlyContainsWhiteSpaces(line)) {
        // empty line, whitespaces, nothing to do.
        return false;
      }

      if (maybeIsTimeStampFormat(line)) {
        self.state = "CUE";
        // We want to process the same line again.
        return true;
      }

      // string contains "-->" or an ID
      self.state = "ID";
      return true;
    }

    try {
      LOG(`state=${self.state}, line=${line}`)
      // 5.1 WebVTT file parsing.
      if (self.state === "INITIAL") {
        parseSignatureMayThrow(line);
        return;
      }

      if (self.state === "HEADER") {
        // parseHeader returns false if the same line doesn't need to be
        // parsed again.
        if (!parseHeader(line)) {
          return;
        }
      }

      if (self.state === "ID") {
        // If there is no cue identifier, read the next line.
        if (line == "") {
          return;
        }

        // If there is no cue identifier, parse the line again.
        if (!parseCueIdentifier(line)) {
          return self.parseLine(line);
        }
        return;
      }

      if (self.state === "CUE") {
        parseCueMayThrow(line);
        return;
      }

      if (self.state === "CUETEXT") {
        // Report the cue when (1) get an empty line (2) get the "-->""
        if (emptyOrOnlyContainsWhiteSpaces(line) ||
            containsTimeDirectionSymbol(line)) {
          // We are done parsing self cue.
          self.oncue && self.oncue(self.cue);
          self.cue = null;
          self.state = "ID";

          if (emptyOrOnlyContainsWhiteSpaces(line)) {
            return;
          }

          // Reuse the same line.
          return self.parseLine(line);
        }
        if (self.cue.text) {
          self.cue.text += "\n";
        }
        self.cue.text += line;
        return;
      }

      if (self.state === "BADCUE") {
        // 54-62 - Collect and discard the remaining cue.
        self.state = "ID";
        return self.parseLine(line);
      }
    } catch (e) {
      self.reportOrThrowError(e);

      // If we are currently parsing a cue, report what we have.
      if (self.state === "CUETEXT" && self.cue && self.oncue) {
        self.oncue(self.cue);
      }
      self.cue = null;
      // Enter BADWEBVTT state if header was not parsed correctly otherwise
      // another exception occurred so enter BADCUE state.
      self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
    }
    return this;
  },
  flush: function () {
    let self = this;
    try {
      // Finish decoding the stream.
      self.buffer += self.decoder.decode();
      self.buffer += "\n\n";
      self.parse();
    } catch(e) {
      self.reportOrThrowError(e);
    }
    self.isPrevLineBlank = false;
    self.onflush && self.onflush();
    return this;
  }
};
PK
!< �+��!modules/workers/PromiseWorker.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env commonjs */

/**
 * A wrapper around `self` with extended capabilities designed
 * to simplify main thread-to-worker thread asynchronous function calls.
 *
 * This wrapper:
 * - groups requests and responses as a method `post` that returns a `Promise`;
 * - ensures that exceptions thrown on the worker thread are correctly serialized;
 * - provides some utilities for benchmarking various operations.
 *
 * Generally, you should use PromiseWorker.js or PromiseWorker.mjs along with
 * its main thread-side counterpart PromiseWorker.sys.mjs.
 */





if (typeof Components != "undefined") {
  throw new Error("This module is meant to be used from the worker thread");
}











/**
 * Built-in JavaScript exceptions that may be serialized without
 * loss of information.
 */
const EXCEPTION_NAMES = {
  EvalError: "EvalError",
  InternalError: "InternalError",
  RangeError: "RangeError",
  ReferenceError: "ReferenceError",
  SyntaxError: "SyntaxError",
  TypeError: "TypeError",
  URIError: "URIError",
};

/**
 * A constructor used to return data to the caller thread while
 * also executing some specific treatment (e.g. shutting down
 * the current thread, transmitting data instead of copying it).
 *
 * @param {object=} data The data to return to the caller thread.
 * @param {object=} meta Additional instructions, as an object
 * that may contain the following fields:
 * - {bool} shutdown If |true|, shut down the current thread after
 *   having sent the result.
 * - {Array} transfers An array of objects that should be transferred
 *   instead of being copied.
 *
 * @constructor
 */
function Meta(data, meta) {
  this.data = data;
  this.meta = meta;
}


/**
 * Base class for a worker.
 *
 * Derived classes are expected to provide the following methods:
 * {
 *   dispatch: function(method, args) {
 *     // Dispatch a call to method `method` with args `args`
 *   },
 *   log: function(...msg) {
 *     // Log (or discard) messages (optional)
 *   },
 *   postMessage: function(message, ...transfers) {
 *     // Post a message to the main thread
 *   },
 *   close: function() {
 *     // Close the worker
 *   }
 * }
 *
 * By default, the AbstractWorker is not connected to a message port,
 * hence will not receive anything.
 *
 * To connect it, use `onmessage`, as follows:
 *   self.addEventListener("message", msg => myWorkerInstance.handleMessage(msg));
 * To handle rejected promises we receive from handleMessage, we must connect it to
 * the onError handler as follows:
 *   self.addEventListener("unhandledrejection", function(error) {
 *    throw error.reason;
 *   });
 */
function AbstractWorker(agent) {
  this._agent = agent;
  this._deferredJobs = new Map();
  this._deferredJobId = 0;
}

AbstractWorker.prototype = {
  // Default logger: discard all messages
  log() {},

  _generateDeferredJobId() {
    this._deferredJobId += 1;
    return "WorkerToThread-" + this._deferredJobId;
  },

  /**
   * Post and wait for an answer from the thread.
   */
  callMainThread(funcName, args) {
    const messageId = this._generateDeferredJobId();

    const message = {
      id: messageId,
      fun: funcName,
      args,
    };

    return new Promise((resolve, reject) => {
      this._deferredJobs.set(messageId, { resolve, reject });
      this.postMessage(message);
    });
  },

  /**
   * Handle a message.
   */
  async handleMessage(msg) {
    let data = msg.data;
    let id = data.id;

    // if the id is found in _deferredJobs, we proceed with the message
    if (this._deferredJobs.has(id)) {
      const { resolve, reject } = this._deferredJobs.get(id);

      if ("ok" in data) {
        resolve(data);
      } else if ("fail" in data) {
        reject(data);
      }
      this._deferredJobs.delete(id);
      return;
    }

    let start;
    let options;
    if (data.args) {
      options = data.args[data.args.length - 1];
    }
    // If |outExecutionDuration| option was supplied, start measuring the
    // duration of the operation.
    if (
      options &&
      typeof options === "object" &&
      "outExecutionDuration" in options
    ) {
      start = Date.now();
    }

    let result;
    let exn;
    let durationMs;
    let method = data.fun;
    try {
      this.log("Calling method", method);
      result = await this.dispatch(method, data.args);
      this.log("Method", method, "succeeded");
    } catch (ex) {
      exn = ex;
      this.log(
        "Error while calling agent method",
        method,
        exn,
        exn.moduleStack || exn.stack || ""
      );
    }

    if (start) {
      // Record duration
      durationMs = Date.now() - start;
      this.log("Method took", durationMs, "ms");
    }

    // Now, post a reply, possibly as an uncaught error.
    // We post this message from outside the |try ... catch| block
    // to avoid capturing errors that take place during |postMessage| and
    // built-in serialization.
    if (!exn) {
      this.log("Sending positive reply", result, "id is", id);
      if (result instanceof Meta) {
        if ("transfers" in result.meta) {
          // Take advantage of zero-copy transfers
          this.postMessage(
            { ok: result.data, id, durationMs },
            result.meta.transfers
          );
        } else {
          this.postMessage({ ok: result.data, id, durationMs });
        }
        if (result.meta.shutdown || false) {
          // Time to close the worker
          this.close();
        }
      } else {
        this.postMessage({ ok: result, id, durationMs });
      }
    } else if (exn.constructor.name == "DOMException") {
      // We can receive instances of DOMExceptions with file I/O.
      // DOMExceptions are not yet serializable (Bug 1561357) and must be
      // handled differently, as they only have a name and message
      this.log("Sending back DOM exception", exn.constructor.name);
      let error = {
        exn: exn.constructor.name,
        message: exn.message,
      };
      this.postMessage({ fail: error, id, durationMs });
    } else if (exn.constructor.name in EXCEPTION_NAMES) {
      // Rather than letting the DOM mechanism [de]serialize built-in
      // JS errors, which loses lots of information (in particular,
      // the constructor name, the moduleName and the moduleStack),
      // we [de]serialize them manually with a little more care.
      this.log("Sending back exception", exn.constructor.name, "id is", id);
      let error = {
        exn: exn.constructor.name,
        message: exn.message,
        fileName: exn.moduleName || exn.fileName,
        lineNumber: exn.lineNumber,
        stack: exn.moduleStack,
      };
      this.postMessage({ fail: error, id, durationMs });
    } else if ("toMsg" in exn) {
      // Extension mechanism for exception [de]serialization. We
      // assume that any exception with a method `toMsg()` knows how
      // to serialize itself. The other side is expected to have
      // registered a deserializer using the `ExceptionHandlers`
      // object.
      this.log(
        "Sending back an error that knows how to serialize itself",
        exn,
        "id is",
        id
      );
      let msg = exn.toMsg();
      this.postMessage({ fail: msg, id, durationMs });
    } else {
      // If we encounter an exception for which we have no
      // serialization mechanism in place, we have no choice but to
      // let the DOM handle said [de]serialization. We can just
      // attempt to mitigate the data loss by injecting `moduleName` and
      // `moduleStack`.
      this.log(
        "Sending back regular error",
        exn,
        exn.moduleStack || exn.stack,
        "id is",
        id
      );

      try {
        // Attempt to introduce human-readable filename and stack
        exn.filename = exn.moduleName;
        exn.stack = exn.moduleStack;
      } catch (_) {
        // Nothing we can do
      }
      throw exn;
    }
  },
};

export const PromiseWorker = { Meta, AbstractWorker };
PK
!<~�!|�	�	'actors/AboutHttpsOnlyErrorChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { RemotePageChild } from "resource://gre/actors/RemotePageChild.sys.mjs";

export class AboutHttpsOnlyErrorChild extends RemotePageChild {
  actorCreated() {
    super.actorCreated();

    // If you add a new function, remember to add it to RemotePageAccessManager.sys.mjs
    // to allow content-privileged about:httpsonlyerror to use it.
    const exportableFunctions = [
      "RPMTryPingSecureWWWLink",
      "RPMOpenSecureWWWLink",
    ];
    this.exportFunctions(exportableFunctions);
  }

  RPMTryPingSecureWWWLink() {
    // try if the page can be reached with www prefix
    // if so send message to the parent to send message to the error page to display
    // suggestion button for www

    const httpsOnlySuggestionPref = Services.prefs.getBoolPref(
      "dom.security.https_only_mode_error_page_user_suggestions"
    );

    // only check if pref is true otherwise return
    if (!httpsOnlySuggestionPref) {
      return;
    }

    // get the host url without the path with www in front
    const wwwURL = "https://www." + this.contentWindow.location.host;
    fetch(wwwURL, {
      credentials: "omit",
      cache: "no-store",
    })
      .then(data => {
        if (data.status === 200) {
          this.contentWindow.dispatchEvent(
            new this.contentWindow.CustomEvent("pingSecureWWWLinkSuccess")
          );
        }
      })
      .catch(() => {
        dump("No secure www suggestion possible for " + wwwURL);
      });
  }

  RPMOpenSecureWWWLink() {
    // if user wants to visit suggested secure www page: visit page with www prefix and delete errorpage from history
    const context = this.manager.browsingContext;
    const docShell = context.docShell;
    const httpChannel = docShell.failedChannel.QueryInterface(
      Ci.nsIHttpChannel
    );
    const webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
    const triggeringPrincipal =
      docShell.failedChannel.loadInfo.triggeringPrincipal;
    const oldURI = httpChannel.URI;
    const newWWWURI = oldURI
      .mutate()
      .setHost("www." + oldURI.host)
      .finalize();

    webNav.loadURI(newWWWURI, {
      triggeringPrincipal,
      loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY,
    });
  }
}
PK
!<��X'��(actors/AboutHttpsOnlyErrorParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { HomePage } from "resource:///modules/HomePage.sys.mjs";
import { PrivateBrowsingUtils } from "resource://gre/modules/PrivateBrowsingUtils.sys.mjs";

export class AboutHttpsOnlyErrorParent extends JSWindowActorParent {
  get browser() {
    return this.browsingContext.top.embedderElement;
  }

  receiveMessage(aMessage) {
    switch (aMessage.name) {
      case "goBack":
        this.goBackFromErrorPage(this.browser);
        break;
    }
  }

  goBackFromErrorPage(aBrowser) {
    if (!aBrowser.canGoBack) {
      // If the unsafe page is the first or the only one in history, go to the
      // start page.
      aBrowser.fixupAndLoadURIString(
        this.getDefaultHomePage(aBrowser.ownerGlobal),
        {
          triggeringPrincipal:
            Services.scriptSecurityManager.getSystemPrincipal(),
        }
      );
    } else {
      aBrowser.goBack();
    }
  }

  getDefaultHomePage(win) {
    if (PrivateBrowsingUtils.isWindowPrivate(win)) {
      return win.BROWSER_NEW_TAB_URL || "about:blank";
    }
    let url = HomePage.getDefault();
    // If url is a pipe-delimited set of pages, just take the first one.
    if (url.includes("|")) {
      url = url.split("|")[0];
    }
    return url;
  }
}
PK
!<��O��%actors/AboutTranslationsChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  return console.createInstance({
    maxLogLevelPref: "browser.translations.logLevel",
    prefix: "Translations",
  });
});

ChromeUtils.defineESModuleGetters(lazy, {
  LanguageDetector:
    "resource://gre/modules/translation/LanguageDetector.sys.mjs",
});

/**
 * @typedef {import("./TranslationsChild.sys.mjs").TranslationsEngine} TranslationsEngine
 * @typedef {import("./TranslationsChild.sys.mjs").SupportedLanguages} SupportedLanguages
 */

/**
 * The AboutTranslationsChild is responsible for coordinating what privileged APIs
 * are exposed to the un-privileged scope of the about:translations page.
 */
export class AboutTranslationsChild extends JSWindowActorChild {
  /**
   * The translations engine uses text translations by default in about:translations,
   * but it can be changed to translate HTML by setting this pref to true. This is
   * useful for manually testing HTML translation behavior, but is not useful to surface
   * as a user-facing feature.
   *
   * @type {bool}
   */
  #isHtmlTranslation = Services.prefs.getBoolPref(
    "browser.translations.useHTML"
  );

  handleEvent(event) {
    if (event.type === "DOMDocElementInserted") {
      this.#exportFunctions();
    }

    if (
      event.type === "DOMContentLoaded" &&
      Services.prefs.getBoolPref("browser.translations.enable")
    ) {
      this.#sendEventToContent({ type: "enable" });
    }
  }

  receiveMessage({ name, data }) {
    switch (name) {
      case "AboutTranslations:SendTranslationsPort": {
        const { fromLanguage, toLanguage, port } = data;
        const transferables = [port];
        this.contentWindow.postMessage(
          {
            type: "GetTranslationsPort",
            fromLanguage,
            toLanguage,
            port,
          },
          "*",
          transferables
        );
        break;
      }
      default:
        throw new Error("Unknown AboutTranslations message: " + name);
    }
  }

  /**
   * @param {object} detail
   */
  #sendEventToContent(detail) {
    this.contentWindow.dispatchEvent(
      new this.contentWindow.CustomEvent("AboutTranslationsChromeToContent", {
        detail: Cu.cloneInto(detail, this.contentWindow),
      })
    );
  }

  /**
   * A privileged promise can't be used in the content page, so convert a privileged
   * promise into a content one.
   *
   * @param {Promise<any>} promise
   * @returns {Promise<any>}
   */
  #convertToContentPromise(promise) {
    return new this.contentWindow.Promise((resolve, reject) =>
      promise.then(resolve, error => {
        let contentWindow;
        try {
          contentWindow = this.contentWindow;
        } catch (error) {
          // The content window is no longer available.
          reject();
          return;
        }
        // Create an error in the content window, if the content window is still around.
        let message = "An error occured in the AboutTranslations actor.";
        if (typeof error === "string") {
          message = error;
        }
        if (typeof error?.message === "string") {
          message = error.message;
        }
        if (typeof error?.stack === "string") {
          message += `\n\nOriginal stack:\n\n${error.stack}\n`;
        }

        reject(new contentWindow.Error(message));
      })
    );
  }

  /**
   * Export any of the child functions that start with "AT_" to the unprivileged content
   * page. This restricts the security capabilities of the the content page.
   */
  #exportFunctions() {
    const window = this.contentWindow;

    const fns = [
      "AT_log",
      "AT_logError",
      "AT_getAppLocale",
      "AT_getSupportedLanguages",
      "AT_isTranslationEngineSupported",
      "AT_isHtmlTranslation",
      "AT_createTranslationsPort",
      "AT_identifyLanguage",
      "AT_getScriptDirection",
    ];
    for (const name of fns) {
      Cu.exportFunction(this[name].bind(this), window, { defineAs: name });
    }
  }

  /**
   * Log messages if "browser.translations.logLevel" is set to "All".
   *
   * @param {...any} args
   */
  AT_log(...args) {
    lazy.console.log(...args);
  }

  /**
   * Report an error to the console.
   *
   * @param {...any} args
   */
  AT_logError(...args) {
    lazy.console.error(...args);
  }

  /**
   * Returns the app's locale.
   *
   * @returns {Intl.Locale}
   */
  AT_getAppLocale() {
    return Services.locale.appLocaleAsBCP47;
  }

  /**
   * Wire this function to the TranslationsChild.
   *
   * @returns {Promise<SupportedLanguages>}
   */
  AT_getSupportedLanguages() {
    return this.#convertToContentPromise(
      this.sendQuery("AboutTranslations:GetSupportedLanguages").then(data =>
        Cu.cloneInto(data, this.contentWindow)
      )
    );
  }

  /**
   * Does this device support the translation engine?
   *
   * @returns {Promise<boolean>}
   */
  AT_isTranslationEngineSupported() {
    return this.#convertToContentPromise(
      this.sendQuery("AboutTranslations:IsTranslationsEngineSupported")
    );
  }

  /**
   * Expose the #isHtmlTranslation property.
   *
   * @returns {bool}
   */
  AT_isHtmlTranslation() {
    return this.#isHtmlTranslation;
  }

  /**
   * Requests a port to the TranslationsEngine process. An engine will be created on
   * the fly for translation requests through this port. This port is unique to its
   * language pair. In order to translate a different language pair, a new port must be
   * created for that pair. The lifecycle of the engine is managed by the
   * TranslationsEngine.
   *
   * @param {string} fromLanguage
   * @param {string} toLanguage
   * @returns {void}
   */
  AT_createTranslationsPort(fromLanguage, toLanguage) {
    this.sendAsyncMessage("AboutTranslations:GetTranslationsPort", {
      fromLanguage,
      toLanguage,
    });
  }

  /**
   * Attempts to identify the human language in which the message is written.
   *
   * @param {string} message
   * @returns {Promise<{ langTag: string, confidence: number }>}
   */
  AT_identifyLanguage(message) {
    return this.#convertToContentPromise(
      lazy.LanguageDetector.detectLanguage(message).then(data =>
        Cu.cloneInto(
          // This language detector reports confidence as a boolean instead of
          // a percentage, so we need to map the confidence to 0.0 or 1.0.
          { langTag: data.language, confidence: data.confident ? 1.0 : 0.0 },
          this.contentWindow
        )
      )
    );
  }

  /**
   * TODO - Remove this when Intl.Locale.prototype.textInfo is available to
   * content scripts.
   *
   * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/textInfo
   * https://bugzilla.mozilla.org/show_bug.cgi?id=1693576
   *
   * @param {string} locale
   * @returns {string}
   */
  AT_getScriptDirection(locale) {
    return Services.intl.getScriptDirection(locale);
  }
}
PK
!<��#

&actors/AboutTranslationsParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  TranslationsParent: "resource://gre/actors/TranslationsParent.sys.mjs",
});

/**
 * This parent is blank because the Translations actor handles most of the features
 * needed in AboutTranslations.
 */
export class AboutTranslationsParent extends JSWindowActorParent {
  #isDestroyed = false;

  didDestroy() {
    this.#isDestroyed = true;
  }

  async receiveMessage({ name, data }) {
    switch (name) {
      case "AboutTranslations:GetTranslationsPort": {
        if (this.#isDestroyed) {
          return undefined;
        }

        const { fromLanguage, toLanguage } = data;
        try {
          const port = await lazy.TranslationsParent.requestTranslationsPort(
            fromLanguage,
            toLanguage
          );

          // At the time of writing, you can't return a port via the `sendQuery` API,
          // so results can't just be returned. The `sendAsyncMessage` method must be
          // invoked. Additionally, in the AboutTranslationsChild, the port must
          // be transferred to the content page with `postMessage`.
          this.sendAsyncMessage(
            "AboutTranslations:SendTranslationsPort",
            {
              fromLanguage,
              toLanguage,
              port,
            },
            [port] // Mark the port as transferable.
          );
        } catch (error) {
          console.error(error);
        }

        return undefined;
      }
      case "AboutTranslations:GetSupportedLanguages": {
        return lazy.TranslationsParent.getSupportedLanguages();
      }
      case "AboutTranslations:IsTranslationsEngineSupported": {
        return lazy.TranslationsParent.getIsTranslationsEngineSupported();
      }
      default:
        throw new Error("Unknown AboutTranslations message: " + name);
    }
  }
}
PK
!<�Ov,��!actors/AudioPlaybackChild.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class AudioPlaybackChild extends JSWindowActorChild {
  observe(subject, topic, data) {
    if (topic === "audio-playback") {
      let name = "AudioPlayback:";
      if (data === "activeMediaBlockStart") {
        name += "ActiveMediaBlockStart";
      } else if (data === "activeMediaBlockStop") {
        name += "ActiveMediaBlockStop";
      } else {
        name += data === "active" ? "Start" : "Stop";
      }
      this.sendAsyncMessage(name);
    }
  }
}
PK
!<��ȄGG"actors/AudioPlaybackParent.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class AudioPlaybackParent extends JSWindowActorParent {
  constructor() {
    super();
    this._hasAudioPlayback = false;
    this._hasBlockMedia = false;
  }
  receiveMessage(aMessage) {
    const browser = this.browsingContext.top.embedderElement;
    switch (aMessage.name) {
      case "AudioPlayback:Start":
        this._hasAudioPlayback = true;
        browser.audioPlaybackStarted();
        break;
      case "AudioPlayback:Stop":
        this._hasAudioPlayback = false;
        browser.audioPlaybackStopped();
        break;
      case "AudioPlayback:ActiveMediaBlockStart":
        this._hasBlockMedia = true;
        browser.activeMediaBlockStarted();
        break;
      case "AudioPlayback:ActiveMediaBlockStop":
        this._hasBlockMedia = false;
        browser.activeMediaBlockStopped();
        break;
    }
  }
  didDestroy() {
    const browser = this.browsingContext.top.embedderElement;
    if (browser && this._hasAudioPlayback) {
      browser.audioPlaybackStopped();
    }
    if (browser && this._hasBlockMedia) {
      browser.activeMediaBlockStopped();
    }
  }
}
PK
!<��/:l)l) actors/AutoCompleteChild.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint no-unused-vars: ["error", {args: "none"}] */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ContentDOMReference: "resource://gre/modules/ContentDOMReference.sys.mjs",
  LayoutUtils: "resource://gre/modules/LayoutUtils.sys.mjs",
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
});

const gFormFillController = Cc[
  "@mozilla.org/satchel/form-fill-controller;1"
].getService(Ci.nsIFormFillController);

export class AutoCompleteChild extends JSWindowActorChild {
  constructor() {
    super();

    this._input = null;
    this._popupOpen = false;
  }

  receiveMessage(message) {
    switch (message.name) {
      case "AutoComplete:HandleEnter": {
        this.selectedIndex = message.data.selectedIndex;

        let controller = Cc[
          "@mozilla.org/autocomplete/controller;1"
        ].getService(Ci.nsIAutoCompleteController);
        controller.handleEnter(message.data.isPopupSelection);
        break;
      }

      case "AutoComplete:PopupClosed": {
        this._popupOpen = false;
        break;
      }

      case "AutoComplete:PopupOpened": {
        this._popupOpen = true;
        break;
      }

      case "AutoComplete:Focus": {
        // XXX See bug 1582722
        // Before bug 1573836, the messages here didn't match
        // ("AutoComplete:Focus" versus "AutoComplete:RequestFocus")
        // so this was never called. However this._input is actually a
        // nsIAutoCompleteInput, which doesn't have a focus() method, so it
        // wouldn't have worked anyway. So for now, I have just disabled this.
        /*
        if (this._input) {
          this._input.focus();
        }
        */
        break;
      }
    }
  }

  get input() {
    return this._input;
  }

  set selectedIndex(index) {
    this.sendAsyncMessage("AutoComplete:SetSelectedIndex", { index });
  }

  get selectedIndex() {
    // selectedIndex getter must be synchronous because we need the
    // correct value when the controller is in controller::HandleEnter.
    // We can't easily just let the parent inform us the new value every
    // time it changes because not every action that can change the
    // selectedIndex is trivial to catch (e.g. moving the mouse over the
    // list).
    let selectedIndexResult = Services.cpmm.sendSyncMessage(
      "AutoComplete:GetSelectedIndex",
      {
        browsingContext: this.browsingContext,
      }
    );

    if (
      selectedIndexResult.length != 1 ||
      !Number.isInteger(selectedIndexResult[0])
    ) {
      throw new Error("Invalid autocomplete selectedIndex");
    }
    return selectedIndexResult[0];
  }

  get popupOpen() {
    return this._popupOpen;
  }

  openAutocompletePopup(input, element) {
    if (this._popupOpen || !input || !element?.isConnected) {
      return;
    }

    let rect = lazy.LayoutUtils.getElementBoundingScreenRect(element);
    let window = element.ownerGlobal;
    let dir = window.getComputedStyle(element).direction;
    let results = this.getResultsFromController(input);
    let formOrigin = lazy.LoginHelper.getLoginOrigin(
      element.ownerDocument.documentURI
    );
    let inputElementIdentifier = lazy.ContentDOMReference.get(element);

    this.sendAsyncMessage("AutoComplete:MaybeOpenPopup", {
      results,
      rect,
      dir,
      inputElementIdentifier,
      formOrigin,
    });

    this._input = input;
  }

  closePopup() {
    // We set this here instead of just waiting for the
    // PopupClosed message to do it so that we don't end
    // up in a state where the content thinks that a popup
    // is open when it isn't (or soon won't be).
    this._popupOpen = false;
    this.sendAsyncMessage("AutoComplete:ClosePopup", {});
  }

  invalidate() {
    if (this._popupOpen) {
      let results = this.getResultsFromController(this._input);
      this.sendAsyncMessage("AutoComplete:Invalidate", { results });
    }
  }

  selectBy(reverse, page) {
    Services.cpmm.sendSyncMessage("AutoComplete:SelectBy", {
      browsingContext: this.browsingContext,
      reverse,
      page,
    });
  }

  getResultsFromController(inputField) {
    let results = [];

    if (!inputField) {
      return results;
    }

    let controller = inputField.controller;
    if (!(controller instanceof Ci.nsIAutoCompleteController)) {
      return results;
    }

    for (let i = 0; i < controller.matchCount; ++i) {
      let result = {};
      result.value = controller.getValueAt(i);
      result.label = controller.getLabelAt(i);
      result.comment = controller.getCommentAt(i);
      result.style = controller.getStyleAt(i);
      result.image = controller.getImageAt(i);
      results.push(result);
    }

    return results;
  }

  getNoRollupOnEmptySearch(input) {
    const providers = this.providersByInput(input);
    return Array.from(providers).find(p => p.actorName == "LoginManager");
  }

  // Store the input to interested autocomplete providers mapping
  #providersByInput = new WeakMap();

  // This functions returns the interested providers that have called
  // `markAsAutoCompletableField` for the given input and also the hard-coded
  // autocomplete providers based on input type.
  providersByInput(input) {
    const providers = new Set(this.#providersByInput.get(input));

    if (input.hasBeenTypePassword) {
      providers.add(
        input.ownerGlobal.windowGlobalChild.getActor("LoginManager")
      );
    } else {
      // The current design is that FormHistory doesn't call `markAsAutoCompletable`
      // for every eligilbe input. Instead, when FormFillController receives a focus event,
      // it would control the <input> if the <input> is eligible to show form history.
      // Because of the design, we need to ask FormHistory whether to search for autocomplete entries
      // for every startSearch call
      providers.add(
        input.ownerGlobal.windowGlobalChild.getActor("FormHistory")
      );
    }
    return providers;
  }

  /**
   * This API should be used by an autocomplete entry provider to mark an input field
   * as eligible for autocomplete for its type.
   * When users click on an autocompletable input, we will search autocomplete entries
   * from all the providers that have called this API for the given <input>.
   *
   * An autocomplete provider should be a JSWindowActor and implements the following
   * functions:
   * - string actorName()
   * - bool shouldSearchForAutoComplete(element);
   * - jsval getAutoCompleteSearchOption(element);
   * - jsval searchResultToAutoCompleteResult(searchString, element, record);
   * See `FormAutofillChild` for example
   *
   * @param input - The HTML <input> element that is considered autocompletable by the
   *                given provider
   * @param provider - A module that provides autocomplete entries for a <input>, for example,
   *                   FormAutofill provides address or credit card autocomplete entries,
   *                   LoginManager provides logins entreis.
   */
  markAsAutoCompletableField(input, provider) {
    gFormFillController.markAsAutoCompletableField(input);

    let providers = this.#providersByInput.get(input);
    if (!providers) {
      providers = new Set();
      this.#providersByInput.set(input, providers);
    }
    providers.add(provider);
  }

  // Record the current ongoing search request. This is used by stopSearch
  // to prevent notifying the autocomplete controller after receiving search request
  // results that were issued prior to the call to stop the search.
  #ongoingSearches = new Set();

  async startSearch(searchString, input, listener) {
    // For all the autocomplete entry providers that previsouly marked
    // this <input> as autocompletable, ask the provider whether we should
    // search for autocomplete entries in the parent. This is because the current
    // design doesn't rely on the provider constantly monitor the <input> and
    // then mark/unmark an input. The provider generally calls the
    // `markAsAutoCompletbleField` when it sees an <input> is eliglbe for autocomplete.
    // Here we ask the provider to exam the <input> more detailedly to see
    // whether we need to search for autocomplete entries at the time users
    // click on the <input>
    const providers = this.providersByInput(input);
    const data = Array.from(providers)
      .filter(p => p.shouldSearchForAutoComplete(input, searchString))
      .map(p => ({
        actorName: p.actorName,
        options: p.getAutoCompleteSearchOption(input, searchString),
      }));

    let result = [];

    // We don't return empty result when no provider requests seaching entries in the
    // parent because for some special cases, the autocomplete entries are coming
    // from the content. For example, <datalist>.
    if (data.length) {
      const promise = this.sendQuery("AutoComplete:StartSearch", {
        searchString,
        data,
      });
      this.#ongoingSearches.add(promise);
      result = await promise.catch(e => {
        this.#ongoingSearches.delete(promise);
      });
      result ||= [];

      // If the search is stopped, don't report back.
      if (!this.#ongoingSearches.delete(promise)) {
        return;
      }
    }

    for (const provider of providers) {
      // Search result could be empty. However, an autocomplete provider might
      // want to show an autocomplete popup when there is no search result. For example,
      // <datalist> for FormHistory, insecure warning for LoginManager.
      const searchResult = result.find(r => r.actorName == provider.actorName);
      const acResult = provider.searchResultToAutoCompleteResult(
        searchString,
        input,
        searchResult
      );

      // We have not yet supported showing autocomplete entries from multiple providers,
      // Note: The prioty is defined in AutoCompleteParent.
      if (acResult) {
        listener.onSearchCompletion(acResult);
        return;
      }
    }
  }

  stopSearch() {
    this.#ongoingSearches.clear();
  }

  selectEntry() {
    // we don't need to pass the selected index to the parent process because
    // the selected index is maintained in the parent.
    this.sendAsyncMessage("AutoComplete:SelectEntry");
  }
}

AutoCompleteChild.prototype.QueryInterface = ChromeUtils.generateQI([
  "nsIAutoCompletePopup",
]);
PK
!<�%bNbN!actors/AutoCompleteParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "DELEGATE_AUTOCOMPLETE",
  "toolkit.autocomplete.delegate",
  false
);

ChromeUtils.defineESModuleGetters(lazy, {
  GeckoViewAutocomplete: "resource://gre/modules/GeckoViewAutocomplete.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

const PREF_SECURITY_DELAY = "security.notification_enable_delay";

// Stores the actor that has the active popup, used by formfill
let currentActor = null;

let autoCompleteListeners = new Set();

function compareContext(message) {
  if (
    !currentActor ||
    (currentActor.browsingContext != message.data.browsingContext &&
      currentActor.browsingContext.top != message.data.browsingContext)
  ) {
    return false;
  }

  return true;
}

// These are two synchronous messages sent by the child.
// The browsingContext within the message data is either the one that has
// the active autocomplete popup or the top-level of the one that has
// the active autocomplete popup.
Services.ppmm.addMessageListener("AutoComplete:GetSelectedIndex", message => {
  if (compareContext(message)) {
    let actor = currentActor;
    if (actor && actor.openedPopup) {
      return actor.openedPopup.selectedIndex;
    }
  }

  return -1;
});

Services.ppmm.addMessageListener("AutoComplete:SelectBy", message => {
  if (compareContext(message)) {
    let actor = currentActor;
    if (actor && actor.openedPopup) {
      actor.openedPopup.selectBy(message.data.reverse, message.data.page);
    }
  }
});

// AutoCompleteResultView is an abstraction around a list of results.
// It implements enough of nsIAutoCompleteController and
// nsIAutoCompleteInput to make the richlistbox popup work. Since only
// one autocomplete popup should be open at a time, this is a singleton.
var AutoCompleteResultView = {
  // nsISupports
  QueryInterface: ChromeUtils.generateQI([
    "nsIAutoCompleteController",
    "nsIAutoCompleteInput",
  ]),

  // Private variables
  results: [],

  // The AutoCompleteParent currently showing results or null otherwise.
  currentActor: null,

  // nsIAutoCompleteController
  get matchCount() {
    return this.results.length;
  },

  getValueAt(index) {
    return this.results[index].value;
  },

  getFinalCompleteValueAt(index) {
    return this.results[index].value;
  },

  getLabelAt(index) {
    return this.results[index].label;
  },

  getCommentAt(index) {
    return this.results[index].comment;
  },

  getStyleAt(index) {
    return this.results[index].style;
  },

  getImageAt(index) {
    return this.results[index].image;
  },

  handleEnter(aIsPopupSelection) {
    if (this.currentActor) {
      this.currentActor.handleEnter(aIsPopupSelection);
    }
  },

  stopSearch() {},

  searchString: "",

  // nsIAutoCompleteInput
  get controller() {
    return this;
  },

  get popup() {
    return null;
  },

  _focus() {
    if (this.currentActor) {
      this.currentActor.requestFocus();
    }
  },

  // Internal JS-only API
  clearResults() {
    this.currentActor = null;
    this.results = [];
  },

  setResults(actor, results) {
    this.currentActor = actor;
    this.results = results;
  },
};

export class AutoCompleteParent extends JSWindowActorParent {
  didDestroy() {
    if (this.openedPopup) {
      this.openedPopup.closePopup();
    }
  }

  static getCurrentActor() {
    return currentActor;
  }

  static addPopupStateListener(listener) {
    autoCompleteListeners.add(listener);
  }

  static removePopupStateListener(listener) {
    autoCompleteListeners.delete(listener);
  }

  handleEvent(evt) {
    switch (evt.type) {
      case "popupshowing": {
        this.sendAsyncMessage("AutoComplete:PopupOpened", {});
        break;
      }

      case "popuphidden": {
        let selectedIndex = this.openedPopup.selectedIndex;
        let selectedRowComment =
          selectedIndex != -1
            ? AutoCompleteResultView.getCommentAt(selectedIndex)
            : "";
        let selectedRowStyle =
          selectedIndex != -1
            ? AutoCompleteResultView.getStyleAt(selectedIndex)
            : "";

        // Normally preview is cleared after selecting/hovering on a different
        // entry. However, we also need to clear the preview when a pop is closed.
        this.clearAutoCompletePreview();

        this.sendAsyncMessage("AutoComplete:PopupClosed", {
          selectedRowComment,
          selectedRowStyle,
        });
        AutoCompleteResultView.clearResults();
        // adjustHeight clears the height from the popup so that
        // we don't have a big shrink effect if we closed with a
        // large list, and then open on a small one.
        this.openedPopup.adjustHeight();
        this.openedPopup = null;
        currentActor = null;
        evt.target.removeEventListener("popuphidden", this);
        evt.target.removeEventListener("popupshowing", this);
        break;
      }
    }
  }

  showPopupWithResults({ rect, dir, results }) {
    if (!results.length || this.openedPopup) {
      // We shouldn't ever be showing an empty popup, and if we
      // already have a popup open, the old one needs to close before
      // we consider opening a new one.
      return;
    }

    let browser = this.browsingContext.top.embedderElement;
    let window = browser.ownerGlobal;
    // Also check window top in case this is a sidebar.
    if (
      Services.focus.activeWindow !== window.top &&
      Services.focus.focusedWindow.top !== window.top
    ) {
      // We were sent a message from a window or tab that went into the
      // background, so we'll ignore it for now.
      return;
    }

    // Non-empty result styles
    let resultStyles = new Set(results.map(r => r.style).filter(r => !!r));
    currentActor = this;
    this.openedPopup = browser.autoCompletePopup;
    // the layout varies according to different result type
    this.openedPopup.setAttribute("resultstyles", [...resultStyles].join(" "));
    this.openedPopup.hidden = false;
    // don't allow the popup to become overly narrow
    this.openedPopup.style.setProperty(
      "--panel-width",
      Math.max(100, rect.width) + "px"
    );
    this.openedPopup.style.direction = dir;

    AutoCompleteResultView.setResults(this, results);

    this.openedPopup.view = AutoCompleteResultView;
    this.openedPopup.selectedIndex = -1;

    // Reset fields that were set from the last time the search popup was open
    this.openedPopup.mInput = AutoCompleteResultView;
    // Temporarily increase the maxRows as we don't want to show
    // the scrollbar in login or form autofill popups.
    if (
      resultStyles.size &&
      (resultStyles.has("autofill") || resultStyles.has("loginsFooter"))
    ) {
      this.openedPopup._normalMaxRows = this.openedPopup.maxRows;
      this.openedPopup.mInput.maxRows = 10;
    }
    browser.constrainPopup(this.openedPopup);
    this.openedPopup.addEventListener("popuphidden", this);
    this.openedPopup.addEventListener("popupshowing", this);
    this.openedPopup.openPopupAtScreenRect(
      "after_start",
      rect.left,
      rect.top,
      rect.width,
      rect.height,
      false,
      false
    );
    this.openedPopup.invalidate();
    this._maybeRecordTelemetryEvents(results);

    // This is a temporary solution. We should replace it with
    // proper meta information about the popup once such field
    // becomes available.
    let isCreditCard = results.some(result =>
      result?.comment?.includes("cc-number")
    );

    if (isCreditCard) {
      this.delayPopupInput();
    }
  }

  /**
   * @param {object[]} results - Non-empty array of autocomplete results.
   */
  _maybeRecordTelemetryEvents(results) {
    let actor =
      this.browsingContext.currentWindowGlobal.getActor("LoginManager");
    actor.maybeRecordPasswordGenerationShownTelemetryEvent(results);

    // Assume the result with the start time (loginsFooter) is last.
    let lastResult = results[results.length - 1];
    if (lastResult.style != "loginsFooter") {
      return;
    }

    // The comment field of `loginsFooter` results have many additional pieces of
    // information for telemetry purposes. After bug 1555209, this information
    // can be passed to the parent process outside of nsIAutoCompleteResult APIs
    // so we won't need this hack.
    let rawExtraData = JSON.parse(lastResult.comment).telemetryEventData;
    if (!rawExtraData.searchStartTimeMS) {
      throw new Error("Invalid autocomplete search start time");
    }

    if (rawExtraData.stringLength > 1) {
      // To reduce event volume, only record for lengths 0 and 1.
      return;
    }

    let duration =
      Services.telemetry.msSystemNow() - rawExtraData.searchStartTimeMS;
    delete rawExtraData.searchStartTimeMS;

    // Add counts by result style to rawExtraData.
    results.reduce((accumulated, r) => {
      // Ignore learn more as it is only added after importable logins.
      // Do not track generic items in the telemetry.
      if (r.style === "importableLearnMore" || r.style === "generic") {
        return accumulated;
      }

      // Keys can be a maximum of 15 characters and values must be strings.
      // Also treat both "loginWithOrigin" and "login" as "login" as extra_keys
      // is limited to 10.
      let truncatedStyle = r.style.substring(
        0,
        r.style === "loginWithOrigin" ? 5 : 15
      );
      accumulated[truncatedStyle] = (accumulated[truncatedStyle] || 0) + 1;
      return accumulated;
    }, rawExtraData);

    // Convert extra values to strings since recordEvent requires that.
    let extraStrings = Object.fromEntries(
      Object.entries(rawExtraData).map(([key, val]) => {
        let stringVal = "";
        if (typeof val == "boolean") {
          stringVal += val ? "1" : "0";
        } else {
          stringVal += val;
        }
        return [key, stringVal];
      })
    );

    Services.telemetry.recordEvent(
      "form_autocomplete",
      "show",
      "logins",
      // Convert to a string
      duration + "",
      extraStrings
    );
  }

  invalidate(results) {
    if (!this.openedPopup) {
      return;
    }

    if (!results.length) {
      this.closePopup();
    } else {
      AutoCompleteResultView.setResults(this, results);
      this.openedPopup.invalidate();
      this._maybeRecordTelemetryEvents(results);
    }
  }

  closePopup() {
    if (this.openedPopup) {
      // Note that hidePopup() closes the popup immediately,
      // so popuphiding or popuphidden events will be fired
      // and handled during this call.
      this.openedPopup.hidePopup();
    }
  }

  async receiveMessage(message) {
    let browser = this.browsingContext.top.embedderElement;

    if (
      !browser ||
      (!lazy.DELEGATE_AUTOCOMPLETE && !browser.autoCompletePopup)
    ) {
      // If there is no browser or popup, just make sure that the popup has been closed.
      if (this.openedPopup) {
        this.openedPopup.closePopup();
      }

      // Returning false to pacify ESLint, but this return value is
      // ignored by the messaging infrastructure.
      return false;
    }

    switch (message.name) {
      // This is called when an autocomplete entry is selected by the users.
      // In the current design, when a selection is triggered from the parent
      // process (ex, select by mouse click), we still first send the "HandleEnter"
      // message to the child and then child send the "SelectEntry" message back
      // to the parent to indicate that an autocomplete entry is selected.
      case "AutoComplete:SelectEntry": {
        if (this.openedPopup) {
          this.selectAutoCompleteEntry(this.openedPopup.selectedIndex);
        }
        break;
      }

      case "AutoComplete:SetSelectedIndex": {
        let { index } = message.data;
        if (this.openedPopup) {
          this.openedPopup.selectedIndex = index;
        }
        break;
      }

      case "AutoComplete:MaybeOpenPopup": {
        let { results, rect, dir, inputElementIdentifier, formOrigin } =
          message.data;
        if (lazy.DELEGATE_AUTOCOMPLETE) {
          lazy.GeckoViewAutocomplete.delegateSelection({
            browsingContext: this.browsingContext,
            options: results,
            inputElementIdentifier,
            formOrigin,
          });
        } else {
          this.showPopupWithResults({ results, rect, dir });
          this.notifyListeners();

          this.notifyAutoCompletePopupOpened(
            JSON.stringify(inputElementIdentifier)
          );
        }
        break;
      }

      case "AutoComplete:Invalidate": {
        let { results } = message.data;
        this.invalidate(results);
        break;
      }

      case "AutoComplete:ClosePopup": {
        if (lazy.DELEGATE_AUTOCOMPLETE) {
          lazy.GeckoViewAutocomplete.delegateDismiss();
          break;
        }
        this.closePopup();
        break;
      }

      case "AutoComplete:StartSearch": {
        const { searchString, data } = message.data;
        const result = await this.#startSearch(searchString, data);
        return Promise.resolve(result);
      }
    }
    // Returning false to pacify ESLint, but this return value is
    // ignored by the messaging infrastructure.
    return false;
  }

  // Imposes a brief period during which the popup will not respond to
  // a click, so as to reduce the chances of a successful clickjacking
  // attempt
  delayPopupInput() {
    if (!this.openedPopup) {
      return;
    }
    const popupDelay = Services.prefs.getIntPref(PREF_SECURITY_DELAY);

    // Mochitests set this to 0, and many will fail on integration
    // if we make the popup items inactive, even briefly.
    if (!popupDelay) {
      return;
    }

    const items = Array.from(
      this.openedPopup.getElementsByTagName("richlistitem")
    );
    items.forEach(item => (item.disabled = true));

    lazy.setTimeout(
      () => items.forEach(item => (item.disabled = false)),
      popupDelay
    );
  }

  notifyListeners() {
    let window = this.browsingContext.top.embedderElement.ownerGlobal;
    for (let listener of autoCompleteListeners) {
      try {
        listener(window);
      } catch (ex) {
        console.error(ex);
      }
    }
  }

  /**
   * Despite its name, this handleEnter is only called when the user clicks on
   * one of the items in the popup since the popup is rendered in the parent process.
   * The real controller's handleEnter is called directly in the content process
   * for other methods of completing a selection (e.g. using the tab or enter
   * keys) since the field with focus is in that process.
   * @param {boolean} aIsPopupSelection
   */
  handleEnter(aIsPopupSelection) {
    if (this.openedPopup) {
      this.sendAsyncMessage("AutoComplete:HandleEnter", {
        selectedIndex: this.openedPopup.selectedIndex,
        isPopupSelection: aIsPopupSelection,
      });
    }
  }

  // This defines the supported autocomplete providers and the prioity to show the autocomplete
  // entry.
  #AUTOCOMPLETE_PROVIDERS = ["FormAutofill", "LoginManager", "FormHistory"];

  /**
   * Search across multiple module to gather autocomplete entries for a given search string.
   *
   * @param {string} searchString
   *                 The input string used to query autocomplete entries across different
   *                 autocomplete providers.
   * @param {Array<Object>} providers
   *                        An array of objects where each object has a `name` used to identify the actor
   *                        name of the provider and `options` that are passed to the `searchAutoCompleteEntries`
   *                        method of the actor.
   * @returns {Array<Object>} An array of results objects with `name` of the provider and `entries`
   *          that are returned from the provider module's `searchAutoCompleteEntries` method.
   */
  async #startSearch(searchString, providers) {
    for (const name of this.#AUTOCOMPLETE_PROVIDERS) {
      const provider = providers.find(p => p.actorName == name);
      if (!provider) {
        continue;
      }
      const { actorName, options } = provider;
      const actor =
        this.browsingContext.currentWindowGlobal.getActor(actorName);
      const entries = await actor?.searchAutoCompleteEntries(
        searchString,
        options
      );

      // We have not yet supported showing autocomplete entries from multiple providers,
      if (entries) {
        return [{ actorName, ...entries }];
      }
    }
    return [];
  }

  stopSearch() {}

  // Hard-coded the mapping by using the message prefix to find the actor
  // to process a given message.
  #getActorByMessagePrefix(message) {
    const prefixToActor = [
      { prefix: "PasswordManager", actor: "LoginManager" },
      { prefix: "FormAutofill", actor: "FormAutofill" },
    ];

    const name = prefixToActor.find(x => message.startsWith(x.prefix))?.actor;
    return this.browsingContext.currentWindowGlobal.getActor(name);
  }

  /**
   * When an autocomplete popup is opened, we notify all the autocomplete
   * entry providers that have an entry displayed in this popup.
   *
   * @param {ElementIdentifier} elementId The element with which the autocomplete popup is associated
   */
  notifyAutoCompletePopupOpened(elementId) {
    const actors = new Set();
    for (const result of AutoCompleteResultView.results) {
      try {
        const { fillMessageName } = JSON.parse(result.comment);
        if (!fillMessageName) {
          continue;
        }

        actors.add(this.#getActorByMessagePrefix(fillMessageName));
      } catch {}
    }

    for (const actor of actors) {
      actor.onAutoCompletePopupOpened?.(elementId);
    }
  }

  /**
   * Clear the autocomplete preview
   */
  clearAutoCompletePreview() {
    const selectedIndex = this.openedPopup?.selectedIndex;
    const result = AutoCompleteResultView.results[selectedIndex];
    if (!result) {
      return;
    }

    const { fillMessageName, fillMessageData } = JSON.parse(
      result.comment || "{}"
    );
    if (!fillMessageName) {
      return;
    }

    const actor = this.#getActorByMessagePrefix(fillMessageName);
    actor?.onAutoCompleteEntryClearPreview?.(fillMessageName, fillMessageData);
  }

  /**
   * Show the autocomplete preview for the current selected entry.
   */
  previewAutoCompleteEntry() {
    const selectedIndex = this.openedPopup?.selectedIndex;
    const result = AutoCompleteResultView.results[selectedIndex];
    if (!result) {
      return;
    }

    const { fillMessageName, fillMessageData } = JSON.parse(
      result.comment || "{}"
    );
    if (!fillMessageName) {
      return;
    }

    const actor = this.#getActorByMessagePrefix(fillMessageName);
    actor?.onAutoCompleteEntryHovered?.(fillMessageName, fillMessageData);
  }

  /**
   * When an autocomplete entry is selected, notify the actor that provides the entry
   */
  selectAutoCompleteEntry() {
    const selectedIndex = this.openedPopup?.selectedIndex;
    const result = AutoCompleteResultView.results[selectedIndex];
    if (!result) {
      return;
    }

    const { fillMessageName, fillMessageData } = JSON.parse(
      result.comment || "{}"
    );
    if (!fillMessageName) {
      return;
    }

    const actor = this.#getActorByMessagePrefix(fillMessageName);
    actor?.onAutoCompleteEntrySelected?.(fillMessageName, fillMessageData);
  }

  /**
   * Sends a message to the browser that is requesting the input
   * that the open popup should be focused.
   */
  requestFocus() {
    // Bug 1582722 - See the response in AutoCompleteChild.sys.mjs for why this
    // disabled.
    /*
    if (this.openedPopup) {
      this.sendAsyncMessage("AutoComplete:Focus");
    }
    */
  }
}
PK
!<p+��w2w2actors/AutoScrollChild.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
});

export class AutoScrollChild extends JSWindowActorChild {
  constructor() {
    super();

    this._scrollable = null;
    this._scrolldir = "";
    this._startX = null;
    this._startY = null;
    this._screenX = null;
    this._screenY = null;
    this._lastFrame = null;
    this._autoscrollHandledByApz = false;
    this._scrollId = null;

    this.observer = new AutoScrollObserver(this);
    this.autoscrollLoop = this.autoscrollLoop.bind(this);
  }

  isAutoscrollBlocker(event) {
    let mmPaste = Services.prefs.getBoolPref("middlemouse.paste");
    let mmScrollbarPosition = Services.prefs.getBoolPref(
      "middlemouse.scrollbarPosition"
    );
    let node = event.originalTarget;
    let content = node.ownerGlobal;

    // If the node is in editable document or content, we don't want to start
    // autoscroll.
    if (mmPaste) {
      if (node.ownerDocument?.designMode == "on") {
        return true;
      }
      const element =
        node.nodeType === content.Node.ELEMENT_NODE ? node : node.parentElement;
      if (element.isContentEditable) {
        return true;
      }
    }

    // Don't start if we're on a link.
    let [href] = lazy.BrowserUtils.hrefAndLinkNodeForClickEvent(event);
    if (href) {
      return true;
    }

    // Or if we're pasting into an input field of sorts.
    let closestInput = mmPaste && node.closest("input,textarea");
    if (
      content.HTMLInputElement.isInstance(closestInput) ||
      content.HTMLTextAreaElement.isInstance(closestInput)
    ) {
      return true;
    }

    // Or if we're on a scrollbar or XUL <tree>
    if (
      (mmScrollbarPosition &&
        content.XULElement.isInstance(
          node.closest("scrollbar,scrollcorner")
        )) ||
      content.XULElement.isInstance(node.closest("treechildren"))
    ) {
      return true;
    }
    return false;
  }

  isScrollableElement(aNode) {
    let content = aNode.ownerGlobal;
    if (content.HTMLElement.isInstance(aNode)) {
      return !content.HTMLSelectElement.isInstance(aNode) || aNode.multiple;
    }

    return content.XULElement.isInstance(aNode);
  }

  computeWindowScrollDirection(global) {
    if (!global.scrollbars.visible) {
      return null;
    }
    if (global.scrollMaxX != global.scrollMinX) {
      return global.scrollMaxY != global.scrollMinY ? "NSEW" : "EW";
    }
    if (global.scrollMaxY != global.scrollMinY) {
      return "NS";
    }
    return null;
  }

  computeNodeScrollDirection(node) {
    if (!this.isScrollableElement(node)) {
      return null;
    }

    let global = node.ownerGlobal;

    // this is a list of overflow property values that allow scrolling
    const scrollingAllowed = ["scroll", "auto"];

    let cs = global.getComputedStyle(node);
    let overflowx = cs.getPropertyValue("overflow-x");
    let overflowy = cs.getPropertyValue("overflow-y");
    // we already discarded non-multiline selects so allow vertical
    // scroll for multiline ones directly without checking for a
    // overflow property
    let scrollVert =
      node.scrollTopMax &&
      (global.HTMLSelectElement.isInstance(node) ||
        scrollingAllowed.includes(overflowy));

    // do not allow horizontal scrolling for select elements, it leads
    // to visual artifacts and is not the expected behavior anyway
    if (
      !global.HTMLSelectElement.isInstance(node) &&
      node.scrollLeftMin != node.scrollLeftMax &&
      scrollingAllowed.includes(overflowx)
    ) {
      return scrollVert ? "NSEW" : "EW";
    }

    if (scrollVert) {
      return "NS";
    }

    return null;
  }

  findNearestScrollableElement(aNode) {
    // go upward in the DOM and find any parent element that has a overflow
    // area and can therefore be scrolled
    this._scrollable = null;
    for (let node = aNode; node; node = node.flattenedTreeParentNode) {
      // do not use overflow based autoscroll for <html> and <body>
      // Elements or non-html/non-xul elements such as svg or Document nodes
      // also make sure to skip select elements that are not multiline
      let direction = this.computeNodeScrollDirection(node);
      if (direction) {
        this._scrolldir = direction;
        this._scrollable = node;
        break;
      }
    }

    if (!this._scrollable) {
      let direction = this.computeWindowScrollDirection(aNode.ownerGlobal);
      if (direction) {
        this._scrolldir = direction;
        this._scrollable = aNode.ownerGlobal;
      } else if (aNode.ownerGlobal.frameElement) {
        // Note, in case of out of process iframes frameElement is null, and
        // a caller is supposed to communicate to iframe's parent on its own to
        // support cross process scrolling.
        this.findNearestScrollableElement(aNode.ownerGlobal.frameElement);
      }
    }
  }

  async startScroll(event) {
    this.findNearestScrollableElement(event.originalTarget);
    if (!this._scrollable) {
      this.sendAsyncMessage("Autoscroll:MaybeStartInParent", {
        browsingContextId: this.browsingContext.id,
        screenX: event.screenX,
        screenY: event.screenY,
      });
      return;
    }

    let content = event.originalTarget.ownerGlobal;

    // In some configurations like Print Preview, content.performance
    // (which we use below) is null. Autoscrolling is broken in Print
    // Preview anyways (see bug 1393494), so just don't start it at all.
    if (!content.performance) {
      return;
    }

    let domUtils = content.windowUtils;
    let scrollable = this._scrollable;
    if (scrollable instanceof Ci.nsIDOMWindow) {
      // getViewId() needs an element to operate on.
      scrollable = scrollable.document.documentElement;
    }
    this._scrollId = null;
    try {
      this._scrollId = domUtils.getViewId(scrollable);
    } catch (e) {
      // No view ID - leave this._scrollId as null. Receiving side will check.
    }
    let presShellId = domUtils.getPresShellId();
    let { autoscrollEnabled, usingApz } = await this.sendQuery(
      "Autoscroll:Start",
      {
        scrolldir: this._scrolldir,
        screenXDevPx: event.screenX * content.devicePixelRatio,
        screenYDevPx: event.screenY * content.devicePixelRatio,
        scrollId: this._scrollId,
        presShellId,
        browsingContext: this.browsingContext,
      }
    );
    if (!autoscrollEnabled) {
      this._scrollable = null;
      return;
    }

    this.document.addEventListener("mousemove", this, {
      capture: true,
      mozSystemGroup: true,
    });
    this.document.addEventListener("mouseup", this, {
      capture: true,
      mozSystemGroup: true,
    });
    this.document.addEventListener("pagehide", this, true);

    this._startX = event.screenX;
    this._startY = event.screenY;
    this._screenX = event.screenX;
    this._screenY = event.screenY;
    this._scrollErrorX = 0;
    this._scrollErrorY = 0;
    this._autoscrollHandledByApz = usingApz;

    if (!usingApz) {
      // If the browser didn't hand the autoscroll off to APZ,
      // scroll here in the main thread.
      this.startMainThreadScroll();
    } else {
      // Even if the browser did hand the autoscroll to APZ,
      // APZ might reject it in which case it will notify us
      // and we need to take over.
      Services.obs.addObserver(this.observer, "autoscroll-rejected-by-apz");
    }

    if (Cu.isInAutomation) {
      Services.obs.notifyObservers(content, "autoscroll-start");
    }
  }

  startMainThreadScroll() {
    let content = this.document.defaultView;
    this._lastFrame = content.performance.now();
    content.requestAnimationFrame(this.autoscrollLoop);
  }

  stopScroll() {
    if (this._scrollable) {
      this._scrollable.mozScrollSnap();
      this._scrollable = null;

      this.document.removeEventListener("mousemove", this, {
        capture: true,
        mozSystemGroup: true,
      });
      this.document.removeEventListener("mouseup", this, {
        capture: true,
        mozSystemGroup: true,
      });
      this.document.removeEventListener("pagehide", this, true);
      if (this._autoscrollHandledByApz) {
        Services.obs.removeObserver(
          this.observer,
          "autoscroll-rejected-by-apz"
        );
      }
    }
  }

  accelerate(curr, start) {
    const speed = 12;
    var val = (curr - start) / speed;

    if (val > 1) {
      return val * Math.sqrt(val) - 1;
    }
    if (val < -1) {
      return val * Math.sqrt(-val) + 1;
    }
    return 0;
  }

  roundToZero(num) {
    if (num > 0) {
      return Math.floor(num);
    }
    return Math.ceil(num);
  }

  autoscrollLoop(timestamp) {
    if (!this._scrollable) {
      // Scrolling has been canceled
      return;
    }

    // avoid long jumps when the browser hangs for more than
    // |maxTimeDelta| ms
    const maxTimeDelta = 100;
    var timeDelta = Math.min(maxTimeDelta, timestamp - this._lastFrame);
    // we used to scroll |accelerate()| pixels every 20ms (50fps)
    var timeCompensation = timeDelta / 20;
    this._lastFrame = timestamp;

    var actualScrollX = 0;
    var actualScrollY = 0;
    // don't bother scrolling vertically when the scrolldir is only horizontal
    // and the other way around
    if (this._scrolldir != "EW") {
      var y = this.accelerate(this._screenY, this._startY) * timeCompensation;
      var desiredScrollY = this._scrollErrorY + y;
      actualScrollY = this.roundToZero(desiredScrollY);
      this._scrollErrorY = desiredScrollY - actualScrollY;
    }
    if (this._scrolldir != "NS") {
      var x = this.accelerate(this._screenX, this._startX) * timeCompensation;
      var desiredScrollX = this._scrollErrorX + x;
      actualScrollX = this.roundToZero(desiredScrollX);
      this._scrollErrorX = desiredScrollX - actualScrollX;
    }

    this._scrollable.scrollBy({
      left: actualScrollX,
      top: actualScrollY,
      behavior: "instant",
    });

    this._scrollable.ownerGlobal.requestAnimationFrame(this.autoscrollLoop);
  }

  canStartAutoScrollWith(event) {
    if (
      !event.isTrusted ||
      event.defaultPrevented ||
      event.button !== 1 ||
      event.clickEventPrevented()
    ) {
      return false;
    }

    for (const modifier of ["shift", "alt", "ctrl", "meta"]) {
      if (
        event[modifier + "Key"] &&
        Services.prefs.getBoolPref(
          `general.autoscroll.prevent_to_start.${modifier}Key`,
          false
        )
      ) {
        return false;
      }
    }
    return true;
  }

  handleEvent(event) {
    switch (event.type) {
      case "mousemove":
        this._screenX = event.screenX;
        this._screenY = event.screenY;
        break;
      case "mousedown":
        if (
          this.canStartAutoScrollWith(event) &&
          !this._scrollable &&
          !this.isAutoscrollBlocker(event)
        ) {
          this.startScroll(event);
        }
      // fallthrough
      case "mouseup":
        if (
          this._scrollable &&
          Services.prefs.getBoolPref("general.autoscroll", false)
        ) {
          // Middle mouse click event shouldn't be fired in web content for
          // compatibility with Chrome.
          event.preventClickEvent();
        }
        break;
      case "pagehide":
        if (this._scrollable) {
          var doc = this._scrollable.ownerDocument || this._scrollable.document;
          if (doc == event.target) {
            this.sendAsyncMessage("Autoscroll:Cancel");
            this.stopScroll();
          }
        }
        break;
    }
  }

  receiveMessage(msg) {
    let data = msg.data;
    switch (msg.name) {
      case "Autoscroll:MaybeStart":
        for (let child of this.browsingContext.children) {
          if (data.browsingContextId == child.id) {
            this.startScroll({
              screenX: data.screenX,
              screenY: data.screenY,
              originalTarget: child.embedderElement,
            });
            break;
          }
        }
        break;
      case "Autoscroll:Stop": {
        this.stopScroll();
        break;
      }
    }
  }

  rejectedByApz(data) {
    // The caller passes in the scroll id via 'data'.
    if (data == this._scrollId) {
      this._autoscrollHandledByApz = false;
      this.startMainThreadScroll();
      Services.obs.removeObserver(this.observer, "autoscroll-rejected-by-apz");
    }
  }
}

class AutoScrollObserver {
  constructor(actor) {
    this.actor = actor;
  }

  observe(subject, topic, data) {
    if (topic === "autoscroll-rejected-by-apz") {
      this.actor.rejectedByApz(data);
    }
  }
}
PK
!<9����actors/AutoScrollParent.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class AutoScrollParent extends JSWindowActorParent {
  receiveMessage(msg) {
    let browser = this.manager.browsingContext.top.embedderElement;
    if (!browser) {
      return null;
    }

    // If another tab is activated, we shouldn't start autoscroll requested
    // for the previous active window if and only if the browser is a remote
    // browser.  This is required for web apps which don't prevent default of
    // middle click after opening a new window.  If the active tab is our
    // documents like about:*, we don't need this check since our documents
    // should do it correctly.
    const requestedInForegroundTab = browser.isRemoteBrowser
      ? Services.focus.focusedElement == browser
      : true;

    let data = msg.data;
    switch (msg.name) {
      case "Autoscroll:Start":
        // Don't start autoscroll if the tab has already been a background tab.
        if (!requestedInForegroundTab) {
          return Promise.resolve({ autoscrollEnabled: false, usingAPZ: false });
        }
        return Promise.resolve(browser.startScroll(data));
      case "Autoscroll:MaybeStartInParent":
        // Don't start autoscroll if the tab has already been a background tab.
        if (!requestedInForegroundTab) {
          return Promise.resolve({ autoscrollEnabled: false, usingAPZ: false });
        }
        let parent = this.browsingContext.parent;
        if (parent) {
          let actor = parent.currentWindowGlobal.getActor("AutoScroll");
          actor.sendAsyncMessage("Autoscroll:MaybeStart", data);
        }
        break;
      case "Autoscroll:Cancel":
        browser.cancelScroll();
        break;
    }
    return null;
  }
}
PK
!<[~=��actors/AutoplayChild.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class AutoplayChild extends JSWindowActorChild {
  handleEvent() {
    this.sendAsyncMessage("GloballyAutoplayBlocked", {});
  }
}
PK
!<�IA��actors/AutoplayParent.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class AutoplayParent extends JSWindowActorParent {
  receiveMessage() {
    let topBrowsingContext = this.manager.browsingContext.top;
    let browser = topBrowsingContext.embedderElement;
    let document = browser.ownerDocument;
    let event = document.createEvent("CustomEvent");
    event.initCustomEvent("GloballyAutoplayBlocked", true, false, {
      url: this.documentURI,
    });
    browser.dispatchEvent(event);
  }
}
PK
!<A^�
�
(actors/BackgroundThumbnailsChild.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PageThumbUtils: "resource://gre/modules/PageThumbUtils.sys.mjs",
});

// NOTE: Copied from nsSandboxFlags.h
/**
 * This flag prevents content from creating new auxiliary browsing contexts,
 * e.g. using the target attribute, or the window.open() method.
 */
const SANDBOXED_AUXILIARY_NAVIGATION = 0x2;

export class BackgroundThumbnailsChild extends JSWindowActorChild {
  receiveMessage(message) {
    switch (message.name) {
      case "Browser:Thumbnail:ContentInfo": {
        if (
          message.data.isImage ||
          this.contentWindow.ImageDocument.isInstance(this.document)
        ) {
          // To avoid sending additional messages between processes, we return
          // the image data directly with the size info.
          return lazy.PageThumbUtils.createImageThumbnailCanvas(
            this.contentWindow,
            this.document.location,
            message.data.targetWidth,
            message.data.backgroundColor
          );
        }

        let [width, height] = lazy.PageThumbUtils.getContentSize(
          this.contentWindow
        );
        return { width, height };
      }

      case "Browser:Thumbnail:LoadURL": {
        let docShell = this.docShell.QueryInterface(Ci.nsIWebNavigation);

        // We want a low network priority for this service - lower than b/g tabs
        // etc - so set it to the lowest priority available.
        docShell
          .QueryInterface(Ci.nsIDocumentLoader)
          .loadGroup.QueryInterface(Ci.nsISupportsPriority).priority =
          Ci.nsISupportsPriority.PRIORITY_LOWEST;

        docShell.allowMedia = false;
        docShell.allowContentRetargeting = false;
        let defaultFlags =
          Ci.nsIRequest.LOAD_ANONYMOUS |
          Ci.nsIRequest.LOAD_BYPASS_CACHE |
          Ci.nsIRequest.INHIBIT_CACHING |
          Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY;
        docShell.defaultLoadFlags = defaultFlags;
        this.browsingContext.sandboxFlags |= SANDBOXED_AUXILIARY_NAVIGATION;
        docShell.useTrackingProtection = true;

        // Get the document to force a content viewer to be created, otherwise
        // the first load can fail.
        if (!this.document) {
          return false;
        }

        let loadURIOptions = {
          // Bug 1498603 verify usages of systemPrincipal here
          triggeringPrincipal:
            Services.scriptSecurityManager.getSystemPrincipal(),
          loadFlags: Ci.nsIWebNavigation.LOAD_FLAGS_STOP_CONTENT,
        };
        try {
          docShell.loadURI(
            Services.io.newURI(message.data.url),
            loadURIOptions
          );
        } catch (ex) {
          return false;
        }

        return true;
      }
    }

    return undefined;
  }

  handleEvent(event) {
    if (event.type == "DOMDocElementInserted") {
      // Arrange to prevent (most) popup dialogs for this window - popups done
      // in the parent (eg, auth) aren't prevented, but alert() etc are.
      // disableDialogs only works on the current inner window, so it has
      // to be called every page load, but before scripts run.
      this.contentWindow.windowUtils.disableDialogs();
    }
  }
}
PK
!<����"actors/BrowserElementChild.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class BrowserElementChild extends JSWindowActorChild {
  handleEvent(event) {
    if (
      event.type == "DOMWindowClose" &&
      !this.manager.browsingContext.parent
    ) {
      this.sendAsyncMessage("DOMWindowClose", {});
    }
  }

  receiveMessage(message) {
    switch (message.name) {
      case "EnterModalState": {
        this.contentWindow.windowUtils.enterModalState();
        break;
      }

      case "LeaveModalState": {
        if (
          !message.data.forceLeave &&
          !this.contentWindow.windowUtils.isInModalState()
        ) {
          break;
        }
        this.contentWindow.windowUtils.leaveModalState();
        break;
      }
    }
  }
}
PK
!<�j<[}}actors/ContentMetaChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Debounce time in milliseconds - this should be long enough to account for
// sync script tags that could appear between desired meta tags
const TIMEOUT_DELAY = 1000;

const ACCEPTED_PROTOCOLS = ["http:", "https:"];

// Possible description tags, listed in order from least favourable to most favourable
const DESCRIPTION_RULES = [
  "twitter:description",
  "description",
  "og:description",
];

// Possible image tags, listed in order from least favourable to most favourable
const PREVIEW_IMAGE_RULES = [
  "thumbnail",
  "twitter:image",
  "og:image",
  "og:image:url",
  "og:image:secure_url",
];

/*
 * Checks if the incoming meta tag has a greater score than the current best
 * score by checking the index of the meta tag in the list of rules provided.
 *
 * @param {Array} aRules
 *          The list of rules for a given type of meta tag
 * @param {String} aTag
 *          The name or property of the incoming meta tag
 * @param {String} aEntry
 *          The current best entry for the given meta tag
 *
 * @returns {Boolean} true if the incoming meta tag is better than the current
 *                    best meta tag of that same kind, false otherwise
 */
function shouldExtractMetadata(aRules, aTag, aEntry) {
  return aRules.indexOf(aTag) > aEntry.currMaxScore;
}

/*
 * Ensure that the preview image URL is safe and valid before storing
 *
 * @param {URL} aURL
 *          A URL object that needs to be checked for valid principal and protocol
 *
 * @returns {Boolean} true if the preview URL is safe and can be stored, false otherwise
 */
function checkLoadURIStr(aURL) {
  if (!ACCEPTED_PROTOCOLS.includes(aURL.protocol)) {
    return false;
  }
  try {
    let ssm = Services.scriptSecurityManager;
    let principal = ssm.createNullPrincipal({});
    ssm.checkLoadURIStrWithPrincipal(
      principal,
      aURL.href,
      ssm.DISALLOW_INHERIT_PRINCIPAL
    );
  } catch (e) {
    return false;
  }
  return true;
}

/*
 * This listens to DOMMetaAdded events and collects relevant metadata about the
 * meta tag received. Then, it sends the metadata gathered from the meta tags
 * and the url of the page as it's payload to be inserted into moz_places.
 */
export class ContentMetaChild extends JSWindowActorChild {
  constructor() {
    super();

    // Store a mapping of the best description and preview
    // image collected so far for a given URL.
    this.metaTags = new Map();
  }

  didDestroy() {
    for (let entry of this.metaTags.values()) {
      entry.timeout.cancel();
    }
  }

  handleEvent(event) {
    switch (event.type) {
      case "DOMContentLoaded":
        const metaTags = this.contentWindow.document.querySelectorAll("meta");
        for (let metaTag of metaTags) {
          this.onMetaTag(metaTag);
        }
        break;
      case "DOMMetaAdded":
        this.onMetaTag(event.originalTarget);
        break;
      default:
    }
  }

  onMetaTag(metaTag) {
    const window = metaTag.ownerGlobal;

    // If there's no meta tag, ignore this. Also verify that the window
    // matches just to be safe.
    if (!metaTag || !metaTag.ownerDocument || window != this.contentWindow) {
      return;
    }

    const url = metaTag.ownerDocument.documentURI;

    let name = metaTag.name;
    let prop = metaTag.getAttributeNS(null, "property");
    if (!name && !prop) {
      return;
    }

    let tag = name || prop;

    const entry = this.metaTags.get(url) || {
      description: { value: null, currMaxScore: -1 },
      image: { value: null, currMaxScore: -1 },
      timeout: null,
    };

    // Malformed meta tag - do not store it
    const content = metaTag.getAttributeNS(null, "content");
    if (!content) {
      return;
    }

    if (shouldExtractMetadata(DESCRIPTION_RULES, tag, entry.description)) {
      // Extract the description
      entry.description.value = content;
      entry.description.currMaxScore = DESCRIPTION_RULES.indexOf(tag);
    } else if (shouldExtractMetadata(PREVIEW_IMAGE_RULES, tag, entry.image)) {
      // Extract the preview image
      let value;
      try {
        value = new URL(content, url);
      } catch (e) {
        return;
      }
      if (value && checkLoadURIStr(value)) {
        entry.image.value = value.href;
        entry.image.currMaxScore = PREVIEW_IMAGE_RULES.indexOf(tag);
      }
    } else {
      // We don't care about other meta tags
      return;
    }

    if (!this.metaTags.has(url)) {
      this.metaTags.set(url, entry);
    }

    if (entry.timeout) {
      entry.timeout.delay = TIMEOUT_DELAY;
    } else {
      // We want to debounce incoming meta tags until we're certain we have the
      // best one for description and preview image, and only store that one
      entry.timeout = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
      entry.timeout.initWithCallback(
        () => {
          entry.timeout = null;
          this.metaTags.delete(url);
          // We try to cancel the timers when we get destroyed, but if
          // there's a race, catch it:
          if (!this.manager || this.manager.isClosed) {
            return;
          }

          // Save description and preview image to moz_places
          this.sendAsyncMessage("Meta:SetPageInfo", {
            url,
            description: entry.description.value,
            previewImageURL: entry.image.value,
          });

          // Telemetry for recording the size of page metadata
          let metadataSize = entry.description.value
            ? entry.description.value.length
            : 0;
          metadataSize += entry.image.value ? entry.image.value.length : 0;
          Services.telemetry
            .getHistogramById("PAGE_METADATA_SIZE")
            .add(metadataSize);
        },
        TIMEOUT_DELAY,
        Ci.nsITimer.TYPE_ONE_SHOT
      );
    }
  }
}
PK
!<��*LLactors/ControllersChild.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class ControllersChild extends JSWindowActorChild {
  receiveMessage(message) {
    switch (message.name) {
      case "ControllerCommands:Do":
        if (this.docShell && this.docShell.isCommandEnabled(message.data)) {
          this.docShell.doCommand(message.data);
        }
        break;

      case "ControllerCommands:DoWithParams":
        var data = message.data;
        if (this.docShell && this.docShell.isCommandEnabled(data.cmd)) {
          var params = Cu.createCommandParams();
          let substituteXY = false;
          let x = 0;
          let y = 0;
          if (
            data.cmd == "cmd_lookUpDictionary" &&
            "x" in data.params &&
            "y" in data.params &&
            data.params.x.type == "long" &&
            data.params.y.type == "long"
          ) {
            substituteXY = true;
            x = parseInt(data.params.x.value);
            y = parseInt(data.params.y.value);

            let rect =
              this.contentWindow.windowUtils.convertFromParentProcessWidgetToLocal(
                x,
                y,
                1,
                1
              );
            x = Math.round(rect.x);
            y = Math.round(rect.y);
          }

          for (var name in data.params) {
            var value = data.params[name];
            if (value.type == "long") {
              if (substituteXY && name === "x") {
                params.setLongValue(name, x);
              } else if (substituteXY && name === "y") {
                params.setLongValue(name, y);
              } else {
                params.setLongValue(name, parseInt(value.value));
              }
            } else {
              throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
            }
          }
          this.docShell.doCommandWithParams(data.cmd, params);
        }
        break;
    }
  }
}
PK
!<�s(�*	*	 actors/ControllersParent.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class ControllersParent extends JSWindowActorParent {
  constructor() {
    super();

    // A map of commands that have had their enabled/disabled state assigned. The
    // value of each key will be true if enabled, and false if disabled.
    this.supportedCommands = {};
  }

  get browser() {
    return this.browsingContext.top.embedderElement;
  }

  // Update the set of enabled and disabled commands.
  enableDisableCommands(aAction, aEnabledCommands, aDisabledCommands) {
    // Clear the list first
    this.supportedCommands = {};

    for (let command of aEnabledCommands) {
      this.supportedCommands[command] = true;
    }

    for (let command of aDisabledCommands) {
      this.supportedCommands[command] = false;
    }

    let browser = this.browser;
    if (browser) {
      browser.ownerGlobal.updateCommands(aAction);
    }
  }

  isCommandEnabled(aCommand) {
    return this.supportedCommands[aCommand] || false;
  }

  supportsCommand(aCommand) {
    return aCommand in this.supportedCommands;
  }

  doCommand(aCommand) {
    this.sendAsyncMessage("ControllerCommands:Do", aCommand);
  }

  getCommandStateWithParams() {
    throw Components.Exception("Not implemented", Cr.NS_ERROR_NOT_IMPLEMENTED);
  }

  doCommandWithParams(aCommand, aCommandParams) {
    let cmd = {
      cmd: aCommand,
      params: null,
    };
    if (aCommand == "cmd_lookUpDictionary") {
      cmd.params = {
        x: {
          type: "long",
          value: aCommandParams.getLongValue("x"),
        },
        y: {
          type: "long",
          value: aCommandParams.getLongValue("y"),
        },
      };
    } else {
      throw Components.Exception(
        "Not implemented",
        Cr.NS_ERROR_NOT_IMPLEMENTED
      );
    }
    this.sendAsyncMessage("ControllerCommands:DoWithParams", cmd);
  }

  getSupportedCommands() {
    throw Components.Exception("Not implemented", Cr.NS_ERROR_NOT_IMPLEMENTED);
  }

  onEvent() {}
}

ControllersParent.prototype.QueryInterface = ChromeUtils.generateQI([
  "nsIBrowserController",
  "nsIController",
  "nsICommandController",
]);
PK
!<P;�CdCd actors/CookieBannerChild.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
  setInterval: "resource://gre/modules/Timer.sys.mjs",
  clearInterval: "resource://gre/modules/Timer.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "serviceMode",
  "cookiebanners.service.mode",
  Ci.nsICookieBannerService.MODE_DISABLED
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "serviceModePBM",
  "cookiebanners.service.mode.privateBrowsing",
  Ci.nsICookieBannerService.MODE_DISABLED
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "prefDetectOnly",
  "cookiebanners.service.detectOnly",
  false
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "bannerClickingEnabled",
  "cookiebanners.bannerClicking.enabled",
  false
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "cleanupTimeoutAfterLoad",
  "cookiebanners.bannerClicking.timeoutAfterLoad"
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "cleanupTimeoutAfterDOMContentLoaded",
  "cookiebanners.bannerClicking.timeoutAfterDOMContentLoaded"
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "pollingInterval",
  "cookiebanners.bannerClicking.pollingInterval",
  500
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "testing",
  "cookiebanners.bannerClicking.testing",
  false
);

ChromeUtils.defineLazyGetter(lazy, "logConsole", () => {
  return console.createInstance({
    prefix: "CookieBannerChild",
    maxLogLevelPref: "cookiebanners.bannerClicking.logLevel",
  });
});

export class CookieBannerChild extends JSWindowActorChild {
  // Caches the enabled state to ensure we only compute it once for the lifetime
  // of the actor. Particularly the private browsing check can be expensive.
  #isEnabledCached = null;
  #isTopLevel;
  #clickRules;
  #observerCleanUp;
  #observerCleanUpTimer;
  // Indicates whether the page "load" event occurred.
  #didLoad = false;
  // Indicates whether we are using global rules to handle the banner.
  #isUsingGlobalRules = false;

  // Used to keep track of click telemetry for the current window.
  #telemetryStatus = {
    currentStage: null,
    success: false,
    successStage: null,
    failReason: null,
    bannerVisibilityFail: false,
    querySelectorCount: 0,
    querySelectorTimeMS: 0,
    bannerDetectedAfterCookieInjection: false,
    detectedCMP: [],
  };
  // Indicates whether we should stop running the cookie banner handling
  // mechanism because it has been previously executed for the site. So, we can
  // cool down the cookie banner handing to improve performance.
  #isCooledDownInSession = false;

  handleEvent(event) {
    if (!this.#isEnabled) {
      // Automated tests may still expect the test message to be sent.
      this.#maybeSendTestMessage();
      return;
    }

    switch (event.type) {
      case "DOMContentLoaded":
        this.#onDOMContentLoaded();
        break;
      case "load":
        this.#onLoad();
        break;
      default:
        lazy.logConsole.warn(`Unexpected event ${event.type}.`, event);
    }
  }

  get #isPrivateBrowsing() {
    return lazy.PrivateBrowsingUtils.isContentWindowPrivate(this.contentWindow);
  }

  /**
   * Whether the feature is enabled based on pref state.
   * @type {boolean} true if feature is enabled, false otherwise.
   */
  get #isEnabled() {
    if (this.#isEnabledCached != null) {
      return this.#isEnabledCached;
    }

    let checkIsEnabled = () => {
      if (!lazy.bannerClickingEnabled) {
        return false;
      }
      if (this.#isPrivateBrowsing) {
        return lazy.serviceModePBM != Ci.nsICookieBannerService.MODE_DISABLED;
      }
      return lazy.serviceMode != Ci.nsICookieBannerService.MODE_DISABLED;
    };

    this.#isEnabledCached = checkIsEnabled();
    return this.#isEnabledCached;
  }

  /**
   * Whether the feature is enabled in detect-only-mode where cookie banner
   * detection events are dispatched, but banners aren't handled.
   * @type {boolean} true if feature mode is enabled, false otherwise.
   */
  get #isDetectOnly() {
    // We can't be in detect-only-mode if fully disabled.
    if (!this.#isEnabled) {
      return false;
    }
    return lazy.prefDetectOnly;
  }

  /**
   * @returns {boolean} Whether we handled a banner for the current load by
   * injecting cookies.
   */
  get #hasInjectedCookieForCookieBannerHandling() {
    return this.docShell?.currentDocumentChannel?.loadInfo
      ?.hasInjectedCookieForCookieBannerHandling;
  }

  /**
   * Checks whether we handled a banner for this site by injecting cookies and
   * dispatches events.
   * @returns {boolean} Whether we handled the banner and dispatched events.
   */
  #dispatchEventsForBannerHandledByInjection() {
    if (
      !this.#hasInjectedCookieForCookieBannerHandling ||
      this.#isCooledDownInSession
    ) {
      return false;
    }
    // Strictly speaking we don't actively detect a banner when we handle it by
    // cookie injection. We still dispatch "cookiebannerdetected" in this case
    // for consistency.
    this.sendAsyncMessage("CookieBanner::DetectedBanner");
    this.sendAsyncMessage("CookieBanner::HandledBanner");
    return true;
  }

  /**
   * Handler for DOMContentLoaded events which is the entry point for cookie
   * banner handling.
   */
  async #onDOMContentLoaded() {
    lazy.logConsole.debug("onDOMContentLoaded", { didLoad: this.#didLoad });
    this.#isTopLevel = this.browsingContext == this.browsingContext?.top;
    this.#didLoad = false;
    this.#telemetryStatus.currentStage = "dom_content_loaded";

    let principal = this.document?.nodePrincipal;

    // We only apply banner auto-clicking if the document has a content
    // principal.
    if (!principal?.isContentPrincipal) {
      return;
    }

    // We don't need to do auto-clicking if it's not a http/https page.
    if (!principal.schemeIs("http") && !principal.schemeIs("https")) {
      return;
    }

    lazy.logConsole.debug("Send message to get rule", {
      baseDomain: principal.baseDomain,
      isTopLevel: this.#isTopLevel,
    });
    let rules;

    try {
      let data = await this.sendQuery("CookieBanner::GetClickRules", {});

      rules = data.rules;
      // Set we are cooling down for this session if the cookie banner handling
      // has been executed previously.
      this.#isCooledDownInSession = data.hasExecuted;
    } catch (e) {
      lazy.logConsole.warn("Failed to get click rule from parent.", e);
      return;
    }

    lazy.logConsole.debug("Got rules:", rules);
    // We can stop here if we don't have a rule.
    if (!rules.length) {
      // If the cookie injector has handled the banner and there are no click
      // rules we still need to dispatch a "cookiebannerhandled" event.
      let dispatchedEvents = this.#dispatchEventsForBannerHandledByInjection();
      // Record telemetry about handling the banner via cookie injection.
      // Note: The success state recorded here may be invalid if the given
      // cookie fails to handle the banner. Since we don't have a presence
      // detector for this rule we can't determine whether the banner is still
      // showing or not.
      if (dispatchedEvents) {
        this.#telemetryStatus.failReason = null;
        this.#telemetryStatus.success = true;
        this.#telemetryStatus.successStage = "cookie_injected";
      }

      this.#maybeSendTestMessage();
      return;
    }

    this.#clickRules = rules;

    // Check if we are using global rules. If we are using a site rule, there
    // will be one rule has its isGlobalRule property set to false. Otherwise,
    // we are using global rules if every rule has this property set to true.
    this.#isUsingGlobalRules = rules.every(rule => rule.isGlobalRule);

    let { bannerHandled, bannerDetected, matchedRules } =
      await this.handleCookieBanner();

    // Send a message to mark that the cookie banner handling has been executed.
    this.sendAsyncMessage("CookieBanner::MarkSiteExecuted");

    let dispatchedEventsForCookieInjection =
      this.#dispatchEventsForBannerHandledByInjection();
    if (dispatchedEventsForCookieInjection) {
      if (bannerDetected) {
        // Record the failure that the banner is still present with cookies
        // injected.
        this.#telemetryStatus.bannerDetectedAfterCookieInjection = true;
      } else {
        // A cookie injection followed by not detecting the banner via querySelector
        // is a success state. Record that in telemetry.
        // Note: The success state reported may be invalid in edge cases where both
        // the cookie injection and the banner detection via query selector fails.
        this.#telemetryStatus.success = true;
        this.#telemetryStatus.successStage = "cookie_injected";
      }
    }

    // 1. Detected event.
    if (bannerDetected) {
      lazy.logConsole.info("Detected cookie banner.", {
        url: this.document?.location.href,
      });
      // Avoid dispatching a duplicate "cookiebannerdetected" event.
      if (!dispatchedEventsForCookieInjection) {
        this.sendAsyncMessage("CookieBanner::DetectedBanner");
      }
    }

    // 2. Handled event.
    if (bannerHandled) {
      lazy.logConsole.info("Handled cookie banner.", {
        url: this.document?.location.href,
        matchedRules,
      });

      // Avoid dispatching a duplicate "cookiebannerhandled" event.
      if (!dispatchedEventsForCookieInjection) {
        this.sendAsyncMessage("CookieBanner::HandledBanner");
      }
    }

    this.#maybeSendTestMessage();
  }

  /**
   * Handler for "load" events. Used as a signal to stop observing the DOM for
   * cookie banners after a timeout.
   */
  #onLoad() {
    this.#didLoad = true;

    // Exit early if we are not handling banners for this site.
    if (!this.#clickRules?.length) {
      return;
    }

    lazy.logConsole.debug("Observed 'load' event", {
      href: this.document?.location.href,
      hasActiveObserver: !!this.#observerCleanUp,
      observerCleanupTimer: this.#observerCleanUpTimer,
    });

    // Update stage for click telemetry.
    if (!this.#telemetryStatus.success) {
      this.#telemetryStatus.currentStage = "mutation_post_load";
    }

    // On load reset the timer for cleanup.
    this.#startOrResetCleanupTimer();
  }

  /**
   * We limit how long we observe cookie banner mutations for performance
   * reasons. If not present initially on DOMContentLoaded, cookie banners are
   * expected to show up during or shortly after page load.
   * This method starts a cleanup timeout which duration depends on the current
   * load stage (DOMContentLoaded, or load). When called, if a timeout is
   * already running, it is cancelled and a new timeout is scheduled.
   */
  #startOrResetCleanupTimer() {
    // Cancel any already running timeout so we can schedule a new one.
    if (this.#observerCleanUpTimer) {
      lazy.logConsole.debug(
        "#startOrResetCleanupTimer: Cancelling existing cleanup timeout",
        {
          didLoad: this.#didLoad,
        }
      );
      lazy.clearTimeout(this.#observerCleanUpTimer);
    }

    let durationMS = this.#didLoad
      ? lazy.cleanupTimeoutAfterLoad
      : lazy.cleanupTimeoutAfterDOMContentLoaded;
    lazy.logConsole.debug(
      "#startOrResetCleanupTimer: Starting cleanup timeout",
      {
        durationMS,
        didLoad: this.#didLoad,
        hasObserverCleanup: !!this.#observerCleanUp,
      }
    );

    this.#observerCleanUpTimer = this.contentWindow?.setTimeout(() => {
      lazy.logConsole.debug(
        "#startOrResetCleanupTimer: Cleanup timeout triggered",
        {
          durationMS,
          didLoad: this.#didLoad,
          hasObserverCleanup: !!this.#observerCleanUp,
        }
      );
      this.#observerCleanUpTimer = null;
      this.#observerCleanUp?.();
    }, durationMS);
  }

  didDestroy() {
    this.#reportTelemetry();

    // Clean up the observer and timer if needed.
    this.#observerCleanUp?.();
  }

  #reportTelemetry() {
    // Nothing to report, banner handling didn't run.
    if (
      this.#telemetryStatus.successStage == null &&
      this.#telemetryStatus.failReason == null
    ) {
      lazy.logConsole.debug(
        "Skip telemetry",
        this.#telemetryStatus,
        this.#clickRules
      );
      return;
    }

    let {
      success,
      successStage,
      currentStage,
      failReason,
      bannerDetectedAfterCookieInjection,
      detectedCMP,
    } = this.#telemetryStatus;

    // Check if we got interrupted during an observe.
    if (this.#observerCleanUp && !success) {
      failReason = "actor_destroyed";
    }

    let status, reason;
    if (success) {
      status = "success";
      reason = successStage;
    } else {
      status = "fail";
      reason = failReason;
    }

    // Select the target result telemetry.
    let resultTelemetry = this.#isUsingGlobalRules
      ? Glean.cookieBannersCmp.result
      : Glean.cookieBannersClick.result;

    // Increment general success or failure counter.
    resultTelemetry[status].add(1);
    // Increment reason counters.
    if (reason) {
      resultTelemetry[`${status}_${reason}`].add(1);
    } else {
      lazy.logConsole.debug(
        "Could not determine success / fail reason for telemetry."
      );
    }

    lazy.logConsole.debug("Submitted clickResult telemetry", status, reason, {
      success,
      successStage,
      currentStage,
      failReason,
    });

    let { querySelectorCount, querySelectorTimeMS } = this.#telemetryStatus;

    // Glean needs an integer.
    let querySelectorTimeUS = Math.round(querySelectorTimeMS * 1000);

    if (this.#isTopLevel) {
      Glean.cookieBannersClick.querySelectorRunCountPerWindowTopLevel.accumulateSingleSample(
        querySelectorCount
      );
      Glean.cookieBannersClick.querySelectorRunDurationPerWindowTopLevel.accumulateSingleSample(
        querySelectorTimeUS
      );
    } else {
      Glean.cookieBannersClick.querySelectorRunCountPerWindowFrame.accumulateSingleSample(
        querySelectorCount
      );
      Glean.cookieBannersClick.querySelectorRunDurationPerWindowFrame.accumulateSingleSample(
        querySelectorTimeUS
      );
    }

    lazy.logConsole.debug("Submitted querySelector telemetry", {
      isTopLevel: this.#isTopLevel,
      querySelectorCount,
      querySelectorTimeUS,
      querySelectorTimeMS,
    });

    if (bannerDetectedAfterCookieInjection) {
      Glean.cookieBanners.cookieInjectionFail.add(1);
    }

    lazy.logConsole.debug("Submitted cookieInjectionFail telemetry", {
      bannerDetectedAfterCookieInjection,
    });

    if (detectedCMP.length) {
      detectedCMP.forEach(id => {
        Glean.cookieBannersCmp.detectedCmp[id].add(1);
      });
    }

    lazy.logConsole.debug("Submitted detectedCMP telemetry", {
      detectedCMP,
    });

    // Record whether the banner was handled by a global rule or a site rule.
    if (success && reason != "cookie_injected") {
      Glean.cookieBannersCmp.ratioHandledByCmpRule.addToDenominator(1);
      if (this.#isUsingGlobalRules) {
        Glean.cookieBannersCmp.ratioHandledByCmpRule.addToNumerator(1);
      }

      lazy.logConsole.debug("Submitted handled ratio telemetry", {
        isUsingGlobalRules: this.#isUsingGlobalRules,
      });
    }
  }

  /**
   * The function to perform the core logic of handing the cookie banner. It
   * will detect the banner and click the banner button whenever possible
   * according to the given click rules.
   * If the service mode pref is set to detect only mode we will only attempt to
   * find the cookie banner element and return early.
   *
   * @returns A promise which resolves when it finishes auto clicking.
   */
  async handleCookieBanner() {
    lazy.logConsole.debug("handleCookieBanner", this.document?.location.href);

    // Start timer to clean up detection code (polling and mutation observers).
    this.#startOrResetCleanupTimer();

    // First, we detect if the banner is shown on the page
    let rules = await this.#detectBanner();

    if (!rules.length) {
      // The banner was never shown.
      this.#telemetryStatus.success = false;
      if (this.#telemetryStatus.bannerVisibilityFail) {
        this.#telemetryStatus.failReason = "banner_not_visible";
      } else {
        this.#telemetryStatus.failReason = "banner_not_found";
      }

      return { bannerHandled: false, bannerDetected: false };
    }

    // Record every detected CMP. Note that our detection mechanism return every
    // rule if the presence detector matches. So, we could have multiple CMPs
    // if the page contains elements match presence detector of them.
    if (this.#isUsingGlobalRules) {
      rules.forEach(rule => {
        this.#telemetryStatus.detectedCMP.push(rule.id);
      });
    }

    // No rule with valid button to click. This can happen if we're in
    // MODE_REJECT and there are only opt-in buttons available.
    // This also applies when detect-only mode is enabled. We only want to
    // dispatch events matching the current service mode.
    if (rules.every(rule => rule.target == null)) {
      this.#telemetryStatus.success = false;
      this.#telemetryStatus.failReason = "no_rule_for_mode";
      return { bannerHandled: false, bannerDetected: false };
    }

    // If the cookie banner prefs only enable detection but not handling we're done here.
    if (this.#isDetectOnly) {
      return { bannerHandled: false, bannerDetected: true };
    }

    let successClick = false;
    successClick = await this.#clickTarget(rules);

    if (successClick) {
      // For telemetry, Keep track of in which stage we successfully handled the banner.
      this.#telemetryStatus.successStage = this.#telemetryStatus.currentStage;
    } else {
      this.#telemetryStatus.failReason = "button_not_found";
      this.#telemetryStatus.successStage = null;
    }
    this.#telemetryStatus.success = successClick;

    return {
      bannerHandled: successClick,
      bannerDetected: true,
      matchedRules: rules,
    };
  }

  /**
   * The helper function to observe the changes on the document with a timeout.
   * It will call the check function when it observes mutations on the document
   * body. Once the check function returns a truthy value, it will resolve with
   * that value. Otherwise, it will resolve with null on timeout.
   *
   * @param {function} [checkFn] - The check function.
   * @returns {Promise} - A promise which resolves with the return value of the
   * check function or null if the function times out.
   */
  #promiseObserve(checkFn) {
    if (this.#observerCleanUp) {
      throw new Error(
        "The promiseObserve is called before previous one resolves."
      );
    }
    lazy.logConsole.debug("#promiseObserve", { didLoad: this.#didLoad });

    return new Promise(resolve => {
      let win = this.contentWindow;
      // Marks whether a mutation on the site has been observed since we last
      // ran checkFn.
      let sawMutation = false;

      // IDs for interval for checkFn polling.
      let pollIntervalId = null;

      // Keep track of DOM changes via MutationObserver. We only run query
      // selectors again if the DOM updated since our last check.
      let observer = new win.MutationObserver(() => {
        sawMutation = true;
      });
      observer.observe(win.document.body, {
        attributes: true,
        subtree: true,
        childList: true,
      });

      // Start polling checkFn.
      let intervalFn = () => {
        // Nothing changed since last run, skip running checkFn.
        if (!sawMutation) {
          return;
        }
        // Reset mutation flag.
        sawMutation = false;

        // A truthy result means we have a hit so we can stop observing.
        let result = checkFn?.();
        if (result) {
          cleanup(result);
        }
      };
      pollIntervalId = lazy.setInterval(intervalFn, lazy.pollingInterval);

      let cleanup = result => {
        lazy.logConsole.debug("#promiseObserve cleanup", {
          result,
          observer,
          cleanupTimeoutId: this.#observerCleanUpTimer,
          pollIntervalId,
        });

        // Unregister the observer.
        if (observer) {
          observer.disconnect();
          observer = null;
        }

        // Stop the polling checks.
        if (pollIntervalId) {
          lazy.clearInterval(pollIntervalId);
          pollIntervalId = null;
        }

        // Clear the cleanup timeout. This can happen when the actor gets
        // destroyed before the cleanup timeout itself fires.
        if (this.#observerCleanUpTimer) {
          lazy.clearTimeout(this.#observerCleanUpTimer);
        }

        this.#observerCleanUp = null;
        resolve(result);
      };

      // The clean up function to clean unfinished observer and timer on timeout
      // or when the actor destroys.
      this.#observerCleanUp = () => {
        cleanup(null);
      };
    });
  }

  // Detecting if the banner is shown on the page.
  async #detectBanner() {
    if (!this.#clickRules?.length) {
      return [];
    }
    lazy.logConsole.debug("Starting to detect the banner");

    // Returns an array of rules for which a cookie banner exists for the
    // current site.
    let presenceDetector = () => {
      lazy.logConsole.debug("presenceDetector start");
      let matchingRules = this.#clickRules.filter(rule => {
        let { presence, skipPresenceVisibilityCheck } = rule;

        let banner = this.#querySelector(presence);
        lazy.logConsole.debug("Testing banner el presence", {
          result: banner,
          rule,
          presence,
        });

        if (!banner) {
          return false;
        }
        if (skipPresenceVisibilityCheck) {
          return true;
        }

        let isVisible = this.#isVisible(banner);
        // Store visibility of banner element to keep track of why detection
        // failed.
        this.#telemetryStatus.bannerVisibilityFail = !isVisible;

        return isVisible;
      });

      // For no rules matched return null explicitly so #promiseObserve knows we
      // want to keep observing.
      if (!matchingRules.length) {
        return null;
      }
      return matchingRules;
    };

    lazy.logConsole.debug("Initial call to presenceDetector");
    let rules = presenceDetector();

    // If we couldn't detect the banner at the beginning, we register an
    // observer with the timeout to observe if the banner was shown within the
    // timeout.
    if (!rules?.length) {
      lazy.logConsole.debug(
        "Initial presenceDetector failed, registering MutationObserver",
        rules
      );
      this.#telemetryStatus.currentStage = "mutation_pre_load";
      rules = await this.#promiseObserve(presenceDetector);
    }

    if (!rules?.length) {
      lazy.logConsole.debug("Couldn't detect the banner", rules);
      return [];
    }

    lazy.logConsole.debug("Detected the banner for rules", rules);

    return rules;
  }

  // Clicking the target button.
  async #clickTarget(rules) {
    lazy.logConsole.debug("Starting to detect the target button");

    let targetEl;
    for (let rule of rules) {
      targetEl = this.#querySelector(rule.target);
      if (targetEl) {
        break;
      }
    }

    // The target button is not available. We register an observer to wait until
    // it's ready.
    if (!targetEl) {
      targetEl = await this.#promiseObserve(() => {
        for (let rule of rules) {
          let el = this.#querySelector(rule.target);

          lazy.logConsole.debug("Testing button el presence", {
            result: el,
            rule,
            target: rule.target,
          });

          if (el) {
            lazy.logConsole.debug(
              "Found button from rule",
              rule,
              rule.target,
              el
            );
            return el;
          }
        }
        return null;
      });

      if (!targetEl) {
        lazy.logConsole.debug("Cannot find the target button.");
        return false;
      }
    }

    lazy.logConsole.debug("Found the target button, click it.", targetEl);
    targetEl.click();
    return true;
  }

  // The helper function to check if the given element if visible.
  #isVisible(element) {
    return element.checkVisibility({
      checkOpacity: true,
      checkVisibilityCSS: true,
    });
  }

  /**
   * Wrapper around document.querySelector calls which collects perf telemetry.
   * @param {string} selectors - Selector list passed into document.querySelector.
   * @returns document.querySelector result.
   */
  #querySelector(selectors) {
    let start = Cu.now();

    let result = this.document.querySelector(selectors);

    this.#telemetryStatus.querySelectorTimeMS += Cu.now() - start;
    this.#telemetryStatus.querySelectorCount += 1;

    return result;
  }

  #maybeSendTestMessage() {
    if (lazy.testing) {
      let win = this.contentWindow;

      // Report the clicking is finished after the style has been flushed.
      win.requestAnimationFrame(() => {
        win.setTimeout(() => {
          this.sendAsyncMessage("CookieBanner::Test-FinishClicking");
        }, 0);
      });
    }
  }
}
PK
!<_����!actors/CookieBannerParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "serviceMode",
  "cookiebanners.service.mode",
  Ci.nsICookieBannerService.MODE_DISABLED
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "serviceModePBM",
  "cookiebanners.service.mode.privateBrowsing",
  Ci.nsICookieBannerService.MODE_DISABLED
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "maxTriesPerSiteAndSession",
  "cookiebanners.bannerClicking.maxTriesPerSiteAndSession",
  3
);

ChromeUtils.defineLazyGetter(lazy, "CookieBannerL10n", () => {
  return new Localization([
    "branding/brand.ftl",
    "toolkit/global/cookieBannerHandling.ftl",
  ]);
});

export class CookieBannerParent extends JSWindowActorParent {
  /**
   * Get the browser associated with this window which is the top level embedder
   * element. Returns null if the top embedder isn't a browser.
   */
  get #browserElement() {
    let topBC = this.browsingContext.top;

    // Not all embedders are browsers.
    if (topBC.embedderElementType != "browser") {
      return null;
    }

    return topBC.embedderElement;
  }

  get #isTopLevel() {
    return !this.manager.browsingContext.parent;
  }

  #isPrivateBrowsingCached;
  get #isPrivateBrowsing() {
    if (this.#isPrivateBrowsingCached !== undefined) {
      return this.#isPrivateBrowsingCached;
    }
    let browser = this.#browserElement;
    if (!browser) {
      return false;
    }

    this.#isPrivateBrowsingCached =
      lazy.PrivateBrowsingUtils.isBrowserPrivate(browser);

    return this.#isPrivateBrowsingCached;
  }

  /**
   * Dispatches a custom "cookiebannerhandled" event on the chrome window.
   */
  #notifyCookieBannerState(eventType) {
    let chromeWin = this.browsingContext.topChromeWindow;
    if (!chromeWin) {
      return;
    }
    let windowUtils = chromeWin.windowUtils;
    if (!windowUtils) {
      return;
    }
    let event = new CustomEvent(eventType, {
      bubbles: true,
      cancelable: false,
      detail: {
        windowContext: this.manager,
      },
    });
    windowUtils.dispatchEventToChromeOnly(chromeWin, event);
  }

  /**
   * Logs a warning to the web console that a cookie banner has been handled.
   */
  async #logCookieBannerHandledToWebConsole() {
    let consoleMsg = Cc["@mozilla.org/scripterror;1"].createInstance(
      Ci.nsIScriptError
    );
    let [message] = await lazy.CookieBannerL10n.formatMessages([
      { id: "cookie-banner-handled-webconsole" },
    ]);

    if (!this.manager?.innerWindowId) {
      return;
    }
    consoleMsg.initWithWindowID(
      message.value,
      this.manager.documentURI?.spec,
      0,
      0,
      Ci.nsIScriptError.warningFlag,
      "cookiebannerhandling",
      this.manager?.innerWindowId
    );
    Services.console.logMessage(consoleMsg);
  }

  async receiveMessage(message) {
    if (message.name == "CookieBanner::Test-FinishClicking") {
      Services.obs.notifyObservers(
        null,
        "cookie-banner-test-clicking-finish",
        this.manager.documentPrincipal?.baseDomain
      );
      return undefined;
    }

    // Forwards cookie banner detected signals to frontend consumers.
    if (message.name == "CookieBanner::DetectedBanner") {
      this.#notifyCookieBannerState("cookiebannerdetected");
      return undefined;
    }

    // Forwards cookie banner handled signals to frontend consumers.
    if (message.name == "CookieBanner::HandledBanner") {
      this.#notifyCookieBannerState("cookiebannerhandled");
      this.#logCookieBannerHandledToWebConsole();
      return undefined;
    }

    let domain = this.manager.documentPrincipal?.baseDomain;

    if (message.name == "CookieBanner::MarkSiteExecuted") {
      if (!domain) {
        return undefined;
      }

      Services.cookieBanners.markSiteExecuted(
        domain,
        this.#isTopLevel,
        this.#isPrivateBrowsing
      );
      return undefined;
    }

    if (message.name != "CookieBanner::GetClickRules") {
      return undefined;
    }

    // TODO: Bug 1790688: consider moving this logic to the cookie banner service.
    let mode;
    if (this.#isPrivateBrowsing) {
      mode = lazy.serviceModePBM;
    } else {
      mode = lazy.serviceMode;
    }

    // Check if we have a site preference of the top-level URI. If so, it
    // takes precedence over the pref setting.
    let topBrowsingContext = this.manager.browsingContext.top;
    let topURI = topBrowsingContext.currentWindowGlobal?.documentURI;

    // We don't need to check the domain preference if the cookie banner
    // handling was disabled by pref.
    if (mode != Ci.nsICookieBannerService.MODE_DISABLED && topURI) {
      try {
        let perDomainMode = Services.cookieBanners.getDomainPref(
          topURI,
          this.#isPrivateBrowsing
        );

        if (perDomainMode != Ci.nsICookieBannerService.MODE_UNSET) {
          mode = perDomainMode;
        }
      } catch (e) {
        // getPerSitePref could throw with NS_ERROR_NOT_AVAILABLE if the service
        // is disabled. We will fallback to global pref setting if any errors
        // occur.
        if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
          console.error("The cookie banner handling service is not available");
        } else {
          console.error("Fail on getting domain pref:", e);
        }
      }
    }

    // Check if we previously executed banner clicking for the site. If the pref
    // instructs to always execute banner clicking, we will set it to
    // false.
    let hasExecuted = false;
    if (lazy.maxTriesPerSiteAndSession > 0) {
      hasExecuted = Services.cookieBanners.shouldStopBannerClickingForSite(
        domain,
        this.#isTopLevel,
        this.#isPrivateBrowsing
      );
    }

    // If we have previously executed banner clicking or the service is disabled
    // for current context (normal or private browsing), return empty array.
    if (hasExecuted || mode == Ci.nsICookieBannerService.MODE_DISABLED) {
      return { rules: [], hasExecuted };
    }

    if (!domain) {
      return { rules: [], hasExecuted };
    }

    let rules = Services.cookieBanners.getClickRulesForDomain(
      domain,
      this.#isTopLevel
    );

    if (!rules.length) {
      return { rules: [], hasExecuted };
    }

    // Determine whether we can fall back to opt-in rules. This includes the
    // detect-only mode where don't interact with the banner.
    let modeAllowsOptIn =
      mode == Ci.nsICookieBannerService.MODE_REJECT_OR_ACCEPT;

    rules = rules.map(rule => {
      let target = rule.optOut;

      if (modeAllowsOptIn && !target) {
        target = rule.optIn;
      }
      return {
        id: rule.id,
        hide: rule.hide ?? rule.presence,
        presence: rule.presence,
        skipPresenceVisibilityCheck: rule.skipPresenceVisibilityCheck,
        target,
        isGlobalRule: rule.isGlobalRule,
      };
    });

    return { rules, hasExecuted };
  }
}
PK
!<��S��"actors/DateTimePickerChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  LayoutUtils: "resource://gre/modules/LayoutUtils.sys.mjs",
});

/**
 * DateTimePickerChild is the communication channel between the input box
 * (content) for date/time input types and its picker (chrome).
 */
export class DateTimePickerChild extends JSWindowActorChild {
  /**
   * On init, just listen for the event to open the picker, once the picker is
   * opened, we'll listen for update and close events.
   */
  constructor() {
    super();

    this._inputElement = null;
  }

  /**
   * Cleanup function called when picker is closed.
   */
  close() {
    this.removeListeners(this._inputElement);
    let dateTimeBoxElement = this._inputElement.dateTimeBoxElement;
    if (!dateTimeBoxElement) {
      this._inputElement = null;
      return;
    }

    // dateTimeBoxElement is within UA Widget Shadow DOM.
    // An event dispatch to it can't be accessed by document.
    let win = this._inputElement.ownerGlobal;
    dateTimeBoxElement.dispatchEvent(
      new win.CustomEvent("MozSetDateTimePickerState", { detail: false })
    );

    this._inputElement = null;
  }

  /**
   * Called after picker is opened to start listening for input box update
   * events.
   */
  addListeners(aElement) {
    aElement.ownerGlobal.addEventListener("pagehide", this);
  }

  /**
   * Stop listeneing for events when picker is closed.
   */
  removeListeners(aElement) {
    aElement.ownerGlobal.removeEventListener("pagehide", this);
  }

  /**
   * Helper function that returns the CSS direction property of the element.
   */
  getComputedDirection(aElement) {
    return aElement.ownerGlobal
      .getComputedStyle(aElement)
      .getPropertyValue("direction");
  }

  /**
   * Helper function that returns the rect of the element, which is the position
   * relative to the left/top of the content area.
   */
  getBoundingContentRect(aElement) {
    return lazy.LayoutUtils.getElementBoundingScreenRect(aElement);
  }

  getTimePickerPref() {
    return Services.prefs.getBoolPref("dom.forms.datetime.timepicker");
  }

  /**
   * nsIMessageListener.
   */
  receiveMessage(aMessage) {
    switch (aMessage.name) {
      case "FormDateTime:PickerClosed": {
        if (!this._inputElement) {
          return;
        }

        this.close();
        break;
      }
      case "FormDateTime:PickerValueChanged": {
        if (!this._inputElement) {
          return;
        }

        let dateTimeBoxElement = this._inputElement.dateTimeBoxElement;
        if (!dateTimeBoxElement) {
          return;
        }

        let win = this._inputElement.ownerGlobal;

        // dateTimeBoxElement is within UA Widget Shadow DOM.
        // An event dispatch to it can't be accessed by document.
        dateTimeBoxElement.dispatchEvent(
          new win.CustomEvent("MozPickerValueChanged", {
            detail: Cu.cloneInto(aMessage.data, win),
          })
        );
        break;
      }
      default:
        break;
    }
  }

  /**
   * nsIDOMEventListener, for chrome events sent by the input element and other
   * DOM events.
   */
  handleEvent(aEvent) {
    switch (aEvent.type) {
      case "MozOpenDateTimePicker": {
        // Time picker is disabled when preffed off
        if (
          !aEvent.originalTarget.ownerGlobal.HTMLInputElement.isInstance(
            aEvent.originalTarget
          ) ||
          (aEvent.originalTarget.type == "time" && !this.getTimePickerPref())
        ) {
          return;
        }

        if (this._inputElement) {
          // This happens when we're trying to open a picker when another picker
          // is still open. We ignore this request to let the first picker
          // close gracefully.
          return;
        }

        this._inputElement = aEvent.originalTarget;

        let dateTimeBoxElement = this._inputElement.dateTimeBoxElement;
        if (!dateTimeBoxElement) {
          throw new Error("How do we get this event without a UA Widget?");
        }

        // dateTimeBoxElement is within UA Widget Shadow DOM.
        // An event dispatch to it can't be accessed by document, because
        // the event is not composed.
        let win = this._inputElement.ownerGlobal;
        dateTimeBoxElement.dispatchEvent(
          new win.CustomEvent("MozSetDateTimePickerState", { detail: true })
        );

        this.addListeners(this._inputElement);

        let value = this._inputElement.getDateTimeInputBoxValue();
        this.sendAsyncMessage("FormDateTime:OpenPicker", {
          rect: this.getBoundingContentRect(this._inputElement),
          dir: this.getComputedDirection(this._inputElement),
          type: this._inputElement.type,
          detail: {
            // Pass partial value if it's available, otherwise pass input
            // element's value.
            value: Object.keys(value).length ? value : this._inputElement.value,
            min: this._inputElement.getMinimum(),
            max: this._inputElement.getMaximum(),
            step: this._inputElement.getStep(),
            stepBase: this._inputElement.getStepBase(),
          },
        });
        break;
      }
      case "MozUpdateDateTimePicker": {
        let value = this._inputElement.getDateTimeInputBoxValue();
        value.type = this._inputElement.type;
        this.sendAsyncMessage("FormDateTime:UpdatePicker", { value });
        break;
      }
      case "MozCloseDateTimePicker": {
        this.sendAsyncMessage("FormDateTime:ClosePicker", {});
        this.close();
        break;
      }
      case "pagehide": {
        if (
          this._inputElement &&
          this._inputElement.ownerDocument == aEvent.target
        ) {
          this.sendAsyncMessage("FormDateTime:ClosePicker", {});
          this.close();
        }
        break;
      }
      default:
        break;
    }
  }
}
PK
!<���

#actors/DateTimePickerParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const DEBUG = false;
function debug(aStr) {
  if (DEBUG) {
    dump("-*- DateTimePickerParent: " + aStr + "\n");
  }
}

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  DateTimePickerPanel: "resource://gre/modules/DateTimePickerPanel.sys.mjs",
});

/*
 * DateTimePickerParent receives message from content side (input box) and
 * is reposible for opening, closing and updating the picker. Similarly,
 * DateTimePickerParent listens for picker's events and notifies the content
 * side (input box) about them.
 */
export class DateTimePickerParent extends JSWindowActorParent {
  receiveMessage(aMessage) {
    debug("receiveMessage: " + aMessage.name);
    switch (aMessage.name) {
      case "FormDateTime:OpenPicker": {
        this.showPicker(aMessage.data);
        break;
      }
      case "FormDateTime:ClosePicker": {
        if (!this._picker) {
          return;
        }
        this.close();
        break;
      }
      case "FormDateTime:UpdatePicker": {
        if (!this._picker) {
          return;
        }
        this._picker.setPopupValue(aMessage.data);
        break;
      }
      default:
        break;
    }
  }

  handleEvent(aEvent) {
    debug("handleEvent: " + aEvent.type);
    switch (aEvent.type) {
      case "DateTimePickerValueCleared": {
        this.sendAsyncMessage("FormDateTime:PickerValueChanged", null);
        break;
      }
      case "DateTimePickerValueChanged": {
        this.sendAsyncMessage("FormDateTime:PickerValueChanged", aEvent.detail);
        break;
      }
      case "popuphidden": {
        this.sendAsyncMessage("FormDateTime:PickerClosed", {});
        this.close();
        break;
      }
      default:
        break;
    }
  }

  // Get picker from browser and show it anchored to the input box.
  showPicker(aData) {
    let rect = aData.rect;
    let type = aData.type;
    let detail = aData.detail;

    debug("Opening picker with details: " + JSON.stringify(detail));
    let topBC = this.browsingContext.top;
    let window = topBC.topChromeWindow;
    if (Services.focus.activeWindow != window) {
      debug("Not in the active window");
      return;
    }

    {
      let browser = topBC.embedderElement;
      if (
        browser &&
        browser.ownerGlobal.gBrowser &&
        browser.ownerGlobal.gBrowser.selectedBrowser != browser
      ) {
        debug("In background tab");
        return;
      }
    }

    let doc = window.document;
    let panel = doc.getElementById("DateTimePickerPanel");
    if (!panel) {
      panel = doc.createXULElement("panel");
      panel.id = "DateTimePickerPanel";
      panel.setAttribute("type", "arrow");
      panel.setAttribute("orient", "vertical");
      panel.setAttribute("ignorekeys", "true");
      panel.setAttribute("noautofocus", "true");
      // This ensures that clicks on the anchored input box are never consumed.
      panel.setAttribute("consumeoutsideclicks", "never");
      panel.setAttribute("level", "parent");
      panel.setAttribute("tabspecific", "true");
      let container =
        doc.getElementById("mainPopupSet") ||
        doc.querySelector("popupset") ||
        doc.documentElement.appendChild(doc.createXULElement("popupset"));
      container.appendChild(panel);
    }
    this._oldFocus = doc.activeElement;
    this._picker = new lazy.DateTimePickerPanel(panel);
    this._picker.openPicker(type, rect, detail);
    this.addPickerListeners();
  }

  // Close the picker and do some cleanup.
  close() {
    this._picker.closePicker();
    // Restore focus to where it was before the picker opened.
    this._oldFocus?.focus();
    this._oldFocus = null;
    this.removePickerListeners();
    this._picker = null;
  }

  // Listen to picker's event.
  addPickerListeners() {
    if (!this._picker) {
      return;
    }
    this._picker.element.addEventListener("popuphidden", this);
    this._picker.element.addEventListener("DateTimePickerValueChanged", this);
    this._picker.element.addEventListener("DateTimePickerValueCleared", this);
  }

  // Stop listening to picker's event.
  removePickerListeners() {
    if (!this._picker) {
      return;
    }
    this._picker.element.removeEventListener("popuphidden", this);
    this._picker.element.removeEventListener(
      "DateTimePickerValueChanged",
      this
    );
    this._picker.element.removeEventListener(
      "DateTimePickerValueCleared",
      this
    );
  }
}
PK
!<�[i��actors/ExtFindChild.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  FindContent: "resource://gre/modules/FindContent.sys.mjs",
});

export class ExtFindChild extends JSWindowActorChild {
  receiveMessage(message) {
    if (!this._findContent) {
      this._findContent = new lazy.FindContent(this.docShell);
    }

    switch (message.name) {
      case "ext-Finder:CollectResults":
        this.finderInited = true;
        return this._findContent.findRanges(message.data);
      case "ext-Finder:HighlightResults":
        return this._findContent.highlightResults(message.data);
      case "ext-Finder:ClearHighlighting":
        this._findContent.highlighter.highlight(false);
        break;
    }

    return null;
  }
}
PK
!<�/Z��actors/FindBarChild.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
});

export class FindBarChild extends JSWindowActorChild {
  constructor() {
    super();

    this._findKey = null;

    this.inQuickFind = false;
    this.inPassThrough = false;

    ChromeUtils.defineLazyGetter(this, "FindBarContent", () => {
      const { FindBarContent } = ChromeUtils.importESModule(
        "resource://gre/modules/FindBarContent.sys.mjs"
      );

      let findBarContent = new FindBarContent(this);

      Object.defineProperties(this, {
        inQuickFind: {
          get() {
            return findBarContent.inQuickFind;
          },
        },
        inPassThrough: {
          get() {
            return findBarContent.inPassThrough;
          },
        },
      });

      return findBarContent;
    });
  }

  receiveMessage(msg) {
    if (msg.name == "Findbar:UpdateState") {
      this.FindBarContent.updateState(msg.data);
    }
  }

  /**
   * Check whether this key event will start the findbar in the parent,
   * in which case we should pass any further key events to the parent to avoid
   * them being lost.
   * @param aEvent the key event to check.
   */
  eventMatchesFindShortcut(aEvent) {
    if (!this._findKey) {
      this._findKey = Services.cpmm.sharedData.get("Findbar:Shortcut");
      if (!this._findKey) {
        return false;
      }
    }
    for (let k in this._findKey) {
      if (this._findKey[k] != aEvent[k]) {
        return false;
      }
    }
    return true;
  }

  handleEvent(event) {
    if (event.type == "keypress") {
      this.onKeypress(event);
    }
  }

  onKeypress(event) {
    if (!this.inPassThrough && this.eventMatchesFindShortcut(event)) {
      return this.FindBarContent.start(event);
    }

    // disable FAYT in about:blank to prevent FAYT opening unexpectedly.
    let location = this.document.location.href;
    if (location == "about:blank") {
      return null;
    }

    if (
      event.ctrlKey ||
      event.altKey ||
      event.metaKey ||
      event.defaultPrevented ||
      !lazy.BrowserUtils.mimeTypeIsTextBased(this.document.contentType) ||
      !lazy.BrowserUtils.canFindInPage(location)
    ) {
      return null;
    }

    if (this.inPassThrough || this.inQuickFind) {
      return this.FindBarContent.onKeypress(event);
    }

    if (event.charCode && this.shouldFastFind(event.target)) {
      let key = String.fromCharCode(event.charCode);
      if ((key == "/" || key == "'") && FindBarChild.manualFAYT) {
        return this.FindBarContent.startQuickFind(event);
      }
      if (key != " " && FindBarChild.findAsYouType) {
        return this.FindBarContent.startQuickFind(event, true);
      }
    }
    return null;
  }

  /**
   * Return true if we should FAYT for this node:
   *
   * @param elt
   *        The element that is focused
   */
  shouldFastFind(elt) {
    if (elt) {
      let win = elt.ownerGlobal;
      if (win.HTMLInputElement.isInstance(elt) && elt.mozIsTextField(false)) {
        return false;
      }

      if (elt.isContentEditable || win.document.designMode == "on") {
        return false;
      }

      if (
        win.HTMLTextAreaElement.isInstance(elt) ||
        win.HTMLSelectElement.isInstance(elt) ||
        win.HTMLObjectElement.isInstance(elt) ||
        win.HTMLEmbedElement.isInstance(elt)
      ) {
        return false;
      }

      if (win.XULFrameElement.isInstance(elt)) {
        // If we're targeting an embedded XULFrameElement
        // (e.g. about:addons extensions inline options page), do not activate
        // fast find.
        return false;
      }
    }

    return true;
  }
}

XPCOMUtils.defineLazyPreferenceGetter(
  FindBarChild,
  "findAsYouType",
  "accessibility.typeaheadfind"
);
XPCOMUtils.defineLazyPreferenceGetter(
  FindBarChild,
  "manualFAYT",
  "accessibility.typeaheadfind.manual"
);
PK
!<�{���actors/FindBarParent.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Map of browser elements to findbars.
let findbars = new WeakMap();

export class FindBarParent extends JSWindowActorParent {
  setFindbar(browser, findbar) {
    if (findbar) {
      findbars.set(browser, findbar);
    } else {
      findbars.delete(browser, findbar);
    }
  }

  receiveMessage(message) {
    let browser = this.manager.browsingContext.top.embedderElement;
    if (!browser) {
      return;
    }

    let respondToMessage = () => {
      let findBar = findbars.get(browser);
      if (!findBar) {
        return;
      }

      switch (message.name) {
        case "Findbar:Keypress":
          findBar._onBrowserKeypress(message.data);
          break;
        case "Findbar:Mouseup":
          findBar.onMouseUp();
          break;
      }
    };

    let findPromise = browser.ownerGlobal.gFindBarPromise;
    if (findPromise) {
      findPromise.then(respondToMessage);
    } else {
      respondToMessage();
    }
  }
}
PK
!<R<�n�
�
actors/FinderChild.sys.mjs// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
// vim: set ts=2 sw=2 sts=2 et tw=80: */
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Finder: "resource://gre/modules/Finder.sys.mjs",
});

export class FinderChild extends JSWindowActorChild {
  get finder() {
    if (!this._finder) {
      this._finder = new lazy.Finder(this.docShell);
    }
    return this._finder;
  }

  receiveMessage(aMessage) {
    let data = aMessage.data;

    switch (aMessage.name) {
      case "Finder:CaseSensitive":
        this.finder.caseSensitive = data.caseSensitive;
        break;

      case "Finder:MatchDiacritics":
        this.finder.matchDiacritics = data.matchDiacritics;
        break;

      case "Finder:EntireWord":
        this.finder.entireWord = data.entireWord;
        break;

      case "Finder:SetSearchStringToSelection": {
        return new Promise(resolve => {
          resolve(this.finder.setSearchStringToSelection());
        });
      }

      case "Finder:GetInitialSelection": {
        return new Promise(resolve => {
          resolve(this.finder.getActiveSelectionText());
        });
      }

      case "Finder:Find":
        return this.finder.find(data);

      case "Finder:Highlight":
        return this.finder
          .highlight(
            data.highlight,
            data.searchString,
            data.linksOnly,
            data.useSubFrames
          )
          .then(result => {
            if (result) {
              result.browsingContextId = this.browsingContext.id;
            }
            return result;
          });

      case "Finder:UpdateHighlightAndMatchCount":
        return this.finder.updateHighlightAndMatchCount(data).then(result => {
          if (result) {
            result.browsingContextId = this.browsingContext.id;
          }
          return result;
        });

      case "Finder:HighlightAllChange":
        this.finder.onHighlightAllChange(data.highlightAll);
        break;

      case "Finder:EnableSelection":
        this.finder.enableSelection();
        break;

      case "Finder:RemoveSelection":
        this.finder.removeSelection(data.keepHighlight);
        break;

      case "Finder:FocusContent":
        this.finder.focusContent();
        break;

      case "Finder:FindbarClose":
        this.finder.onFindbarClose();
        break;

      case "Finder:FindbarOpen":
        this.finder.onFindbarOpen();
        break;

      case "Finder:KeyPress":
        var KeyboardEvent = this.finder._getWindow().KeyboardEvent;
        this.finder.keyPress(new KeyboardEvent("keypress", data));
        break;

      case "Finder:MatchesCount":
        return this.finder
          .requestMatchesCount(
            data.searchString,
            data.linksOnly,
            data.useSubFrames
          )
          .then(result => {
            if (result) {
              result.browsingContextId = this.browsingContext.id;
            }
            return result;
          });

      case "Finder:ModalHighlightChange":
        this.finder.onModalHighlightChange(data.useModalHighlight);
        break;

      case "Finder:EnableMarkTesting":
        this.finder.highlighter.enableTesting(data.enable);
        break;
    }

    return null;
  }
}
PK
!<��<D�9�9actors/FormHandlerChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * The FormHandler actor pair implements the logic of detecting
 * form submissions and notifies of a form submission by
 * dispatching the event "form-submission-detected"
 */

export const FORM_SUBMISSION_REASON = {
  FORM_SUBMIT_EVENT: "form-submit-event",
  FORM_REMOVAL_AFTER_FETCH: "form-removal-after-fetch",
  IFRAME_PAGEHIDE: "iframe-pagehide",
  PAGE_NAVIGATION: "page-navigation",
  PASSWORD_REMOVAL_AFTER_FETCH: "password-removal-after-fetch",
};

export class FormHandlerChild extends JSWindowActorChild {
  actorCreated() {
    // Whenever a FormHandlerChild is created it's because somebody has registered
    // their interest in form submissions. This step might create FormHandler actors
    // across multiple window contexts. Whenever a FormHandlerChild is created in a
    // process root, we want to make sure that it registers the progress listener
    // in order to listen for form submissions in that process.
    if (this.manager.isProcessRoot) {
      this.registerProgressListener();
    }
  }
  /**
   * Tracks whether an interest in form submissions was registered in this window
   */
  #hasRegisteredFormSubmissionInterest = false;

  /**
   * Tracks the actors that are interested in form or password field removals from DOM
   * If this set is empty, FormHandlerChild can unregister the form removal event listeners
   */
  #actorsListeningForFormRemoval = new Set();

  handleEvent(event) {
    if (!event.isTrusted) {
      return;
    }

    if (!this.#hasRegisteredFormSubmissionInterest) {
      return;
    }

    switch (event.type) {
      case "DOMDocFetchSuccess":
        this.processDOMDocFetchSuccessEvent();
        break;
      case "DOMFormBeforeSubmit":
        this.processDOMFormBeforeSubmitEvent(event);
        break;
      case "DOMFormRemoved":
        this.processDOMFormRemovedEvent(event);
        break;
      case "DOMInputPasswordRemoved": {
        this.processDOMInputPasswordRemovedEvent(event);
        break;
      }
      default:
        throw new Error("Unexpected event type");
    }
  }

  receiveMessage(message) {
    switch (message.name) {
      case "FormHandler:FormSubmissionByNavigation": {
        this.processPageNavigation();
        break;
      }
      case "FormHandler:EnsureChildExists": {
        // This is just a dummy message to make sure that the
        // FormHandlerChild is created because then the actor
        // starts listening to page navigations
        break;
      }
    }
  }

  /**
   * Process the DOMFormBeforeSubmit event that is dispatched
   * after a form submit event.
   *
   * @param {Event} event DOMFormBeforeSubmit
   */
  processDOMFormBeforeSubmitEvent(event) {
    const form = event.target;
    const formSubmissionReason = FORM_SUBMISSION_REASON.FORM_SUBMIT_EVENT;

    this.#dispatchFormSubmissionEvent(form, formSubmissionReason);
  }

  /**
   * Process the DOMDocFetchSuccess event that is dispatched
   * after a successfull xhr/fetch request and start listening for
   * the events DOMFormRemoved and DOMInputPasswordRemoved
   */
  processDOMDocFetchSuccessEvent() {
    this.document.setNotifyFormOrPasswordRemoved(true);
    this.docShell.chromeEventHandler.addEventListener(
      "DOMFormRemoved",
      this,
      true
    );
    this.docShell.chromeEventHandler.addEventListener(
      "DOMInputPasswordRemoved",
      this,
      true
    );

    this.document.setNotifyFetchSuccess(false);
    this.docShell.chromeEventHandler.removeEventListener(
      "DOMDocFetchSuccess",
      this
    );

    this.#dispatchPrepareFormSubmissionEvent();
  }

  /**
   * Process the DOMFormRemoved event that is dispatched
   * after a form was removed from the DOM.
   *
   * @param {Event} event DOMFormRemoved
   */
  processDOMFormRemovedEvent(event) {
    const form = event.target;
    const formSubmissionReason =
      FORM_SUBMISSION_REASON.FORM_REMOVAL_AFTER_FETCH;

    this.#dispatchFormSubmissionEvent(form, formSubmissionReason);
  }

  /**
   * Process the DOMInputPasswordRemoved event that is dispatched
   * after a password input was removed from the DOM.
   *
   * @param {Event} event DOMInputPasswordRemoved
   */
  processDOMInputPasswordRemovedEvent(event) {
    const form = event.target;
    const formSubmissionReason =
      FORM_SUBMISSION_REASON.PASSWORD_REMOVAL_AFTER_FETCH;

    this.#dispatchFormSubmissionEvent(form, formSubmissionReason);
  }

  /**
   * This or the page of a parent browsing context was navigated,
   * so process the page navigation, only when somebody in the current has
   * registered interest for it
   */
  processPageNavigation() {
    if (!this.#hasRegisteredFormSubmissionInterest) {
      // Nobody is interested in the current window
      // so don't bother notifying anyone
      return;
    }
    const formSubmissionReason = FORM_SUBMISSION_REASON.PAGE_NAVIGATION;
    this.#dispatchFormSubmissionEvent(null, formSubmissionReason);
  }

  /**
   * Dispatch the CustomEvent form-submission-detected and transfer
   * the following information:
   *            detail.form   - the form that is being submitted
   *            detail.reason - the heuristic that detected the form submission
   *                            (see FORM_SUBMISSION_REASON)
   *
   * @param {HTMLFormElement} form
   * @param {string} reason
   */
  #dispatchFormSubmissionEvent(form, reason) {
    const formSubmissionEvent = new CustomEvent("form-submission-detected", {
      detail: { form, reason },
      bubbles: true,
    });
    this.document.dispatchEvent(formSubmissionEvent);
  }

  /**
   * Dispatch the before-form-submission event after receiving
   * a DOMDocFetchSuccess event. This gives the listening actors a chance to
   * save observed fields before they are removed from the DOM.
   */
  #dispatchPrepareFormSubmissionEvent() {
    const parepareFormSubmissionEvent = new CustomEvent(
      "before-form-submission",
      {
        bubbles: true,
      }
    );
    this.document.dispatchEvent(parepareFormSubmissionEvent);
  }

  /**
   * A page navigation was observed in this window or in the subtree.
   * If somebody in this window is interested in form submissions, process it here.
   * Additionally, inform the parent of the navigation so that all FormHandler
   * children in the subtree of the navigated browsing context are notified as well.
   *
   * @param {BrowsingContext} navigatedBrowingContext
   */
  onNavigationObserved(navigatedBrowingContext) {
    if (
      this.#hasRegisteredFormSubmissionInterest &&
      this.browsingContext == navigatedBrowingContext
    ) {
      // This is the most probable case, that an interest in form submissions was registered
      // in the navigated browing context, so we call processPageNavigation directly
      // instead of letting the parent notify this actor again to process it.
      this.processPageNavigation();
    }
    this.sendAsyncMessage(
      "FormHandler:NotifyNavigatedSubtree",
      navigatedBrowingContext
    );
  }

  /**
   * Set up needed listeners in order to detect form submissions after an actor indicated their interest
   *
   * 1. Register listeners relevant to form / password input removal heuristic
   *    - Set up 'DOMDocFetchSuccess' event listener (by calling setNotifyFetchSuccess)
   *
   * 2. Set up listeners relevant to page navigation heuristic
   *    - Create the corresponding parent of the current child, because the existence
   *      of the FormHandlerParent is the condition for being notified of a page navigation.
   *      If the current process is not the process root, we create the FormHandlerChild in
   *      the process root. The progress listener is registered after creating the child.
   *      If the current process is in a cross-origin frame, we notify the parent
   *      to register the progress listener also with the top level's process root.
   *
   * @param {JSWindowActorChild} interestedActor
   * @param {boolean} includesFormRemoval
   */
  registerFormSubmissionInterest(
    interestedActor,
    { includesFormRemoval = true, includesPageNavigation = true } = {}
  ) {
    if (includesFormRemoval) {
      if (!this.#actorsListeningForFormRemoval.size) {
        // The list of actors interest in form removals is empty when this is the
        // first time an actor registered to be notified of form removals or when all actors
        // processed their forms previously and unregistered their interest again. In both
        // cases we need to set up the listener for the event 'DOMDocFetchSuccess' here.
        this.document.setNotifyFetchSuccess(true);
        this.docShell.chromeEventHandler.addEventListener(
          "DOMDocFetchSuccess",
          this,
          true
        );
      }
      this.#actorsListeningForFormRemoval.add(interestedActor);
    }

    if (this.#hasRegisteredFormSubmissionInterest) {
      // If an actor in this window has already registered their interest
      // in form submissions, then the page navigation listeners are already set up
      return;
    }

    if (includesPageNavigation) {
      // We use the existence of the FormHandlerParent on the parent side
      // to determine whether to notify the corresponding FormHandleChild
      // when a page is navigated. So we explicitly create the parent actor
      // by sending a dummy message here
      this.sendAsyncMessage("FormHandler:EnsureParentExists");

      if (!this.manager.isProcessRoot) {
        // The progress listener is registered after the
        // FormHandlerChild is created in the process root
        this.document.ownerGlobal.windowRoot.ownerGlobal.windowGlobalChild.getActor(
          "FormHandler"
        );
      }

      if (!this.manager.sameOriginWithTop) {
        // If the top level is navigated, that also effects the current cross-origin frame.
        // So we notify the parent to set up the progress listeners at the top as well.
        this.sendAsyncMessage("FormHandler:RegisterProgressListenerAtTopLevel");
      }
      this.#hasRegisteredFormSubmissionInterest = true;
    }
  }

  /**
   * The actors that are interested in form submissions explicitly unregister their interest
   * in form removals here. This way we can keep track if there is any interested actor left
   * so that we don't remove the form removal event listeners too early, but we also don't
   * listen to the form removal events for too long unnecessarily.
   *
   * @param {JSWindowActorChild} interestedActor
   */
  unregisterFormRemovalInterest(interestedActor) {
    this.#actorsListeningForFormRemoval.delete(interestedActor);

    if (this.#actorsListeningForFormRemoval.size) {
      // Other actors are still interested in form removals
      return;
    }
    this.document.setNotifyFormOrPasswordRemoved(false);
    this.docShell.chromeEventHandler.removeEventListener(
      "DOMFormRemoved",
      this
    );
    this.docShell.chromeEventHandler.removeEventListener(
      "DOMInputPasswordRemoved",
      this
    );
  }

  /**
   * Set up a nsIWebProgressListener that notifies of certain request state
   * changes such as changes of the location and the history stack for this docShell
   * and for the children's same-orign docShells.
   *
   * Note: Registering the listener only in the process root (instead of for
   *       every window) is enough to receive notifications for the whole process,
   *       because the notifications bubble up
   */
  registerProgressListener() {
    const webProgress = this.docShell
      .QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIWebProgress);

    const flags =
      Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT |
      Ci.nsIWebProgress.NOTIFY_LOCATION;
    try {
      webProgress.addProgressListener(observer, flags);
    } catch (ex) {
      // Ignore NS_ERROR_FAILURE if the progress listener was already added
    }
  }
}

const observer = {
  QueryInterface: ChromeUtils.generateQI([
    "nsIWebProgressListener",
    "nsISupportsWeakReference",
  ]),

  /**
   * Handle history stack changes (history.replaceState(), history.pushState())
   * on the same document as page navigation
   */
  onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
    if (
      !(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) ||
      !(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE)
    ) {
      return;
    }
    const navigatedWindow = aWebProgress.DOMWindow;

    this.notifyProcessRootOfNavigation(navigatedWindow);
  },

  /*
   * Handle certain state changes of requests as page navigation
   * such as location changes (location.assign(), location.replace())
   * See further comments for more details
   */
  onStateChange(aWebProgress, aRequest, aStateFlags, _aStatus) {
    if (
      aStateFlags & Ci.nsIWebProgressListener.STATE_RESTORING &&
      aStateFlags & Ci.nsIWebProgressListener.STATE_STOP
    ) {
      // a document is restored from bfcache
      return;
    }

    if (!(aStateFlags & Ci.nsIWebProgressListener.STATE_START)) {
      return;
    }

    // We only care about when a page triggered a load, not the user. For example:
    // clicking refresh/back/forward, typing a URL and hitting enter, and loading a bookmark aren't
    // likely to be when a user wants to save formautofill data.
    let channel = aRequest.QueryInterface(Ci.nsIChannel);
    let triggeringPrincipal = channel.loadInfo.triggeringPrincipal;
    if (
      triggeringPrincipal.isNullPrincipal ||
      triggeringPrincipal.equals(
        Services.scriptSecurityManager.getSystemPrincipal()
      )
    ) {
      return;
    }

    // We don't handle history navigation, reloads (e.g. history.go(-1), history.back(), location.reload())
    // Note: History state changes (e.g. history.replaceState(), history.pushState()) are handled in onLocationChange
    if (!(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_NORMAL)) {
      return;
    }

    const navigatedWindow = aWebProgress.DOMWindow;
    this.notifyProcessRootOfNavigation(navigatedWindow);
  },

  /**
   * Notify the current process root parent of the page navigation
   * and pass on the navigated browsing context
   *
   * @param {Window} navigatedWindow
   */
  notifyProcessRootOfNavigation(navigatedWindow) {
    const processRootWindow = navigatedWindow.windowRoot.ownerGlobal;
    const formHandlerChild =
      processRootWindow.windowGlobalChild.getExistingActor("FormHandler");
    const navigatedBrowsingContext = navigatedWindow.browsingContext;

    formHandlerChild?.onNavigationObserved(navigatedBrowsingContext);
  },
};
PK
!<Q�ψBBactors/FormHistoryChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  CreditCard: "resource://gre/modules/CreditCard.sys.mjs",
  FormHistoryAutoCompleteResult:
    "resource://gre/modules/FormHistoryAutoComplete.sys.mjs",
  FormScenarios: "resource://gre/modules/FormScenarios.sys.mjs",
  GenericAutocompleteItem: "resource://gre/modules/FillHelpers.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(lazy, "gDebug", "browser.formfill.debug");
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gEnabled",
  "browser.formfill.enable"
);

function log(message) {
  if (!lazy.gDebug) {
    return;
  }
  Services.console.logStringMessage("satchelFormListener: " + message);
}

export class FormHistoryChild extends JSWindowActorChild {
  handleEvent(event) {
    switch (event.type) {
      case "DOMFormBeforeSubmit":
        this.#onDOMFormBeforeSubmit(event.target);
        break;
      default:
        throw new Error("Unexpected event");
    }
  }

  static getInputName(input) {
    return input.name || input.id;
  }

  #onDOMFormBeforeSubmit(form) {
    if (
      !lazy.gEnabled ||
      lazy.PrivateBrowsingUtils.isContentWindowPrivate(form.ownerGlobal)
    ) {
      return;
    }

    log("Form submit observer notified.");

    if (form.getAttribute("autocomplete")?.toLowerCase() == "off") {
      return;
    }

    const entries = [];
    for (const input of form.elements) {
      if (!HTMLInputElement.isInstance(input)) {
        continue;
      }

      // Only use inputs that hold text values (not including type="password")
      if (!input.mozIsTextField(true)) {
        continue;
      }

      // Don't save fields that were previously type=password such as on sites
      // that allow the user to toggle password visibility.
      if (input.hasBeenTypePassword) {
        continue;
      }

      // Bug 1780571, Bug 394612: If Login Manager marked this input, don't save it.
      // The login manager will deal with remembering it.
      if (this.manager.getActor("LoginManager")?.isLoginManagerField(input)) {
        continue;
      }

      // Don't save values when @autocomplete is "off" or has a sensitive field name.
      const autocompleteInfo = input.getAutocompleteInfo();
      if (autocompleteInfo?.canAutomaticallyPersist === false) {
        continue;
      }

      const value = input.lastInteractiveValue?.trim();

      // Only save user entered values even if they match the default value.
      // Any script input is ignored.
      // See Bug 1642570 for details.
      if (!value) {
        continue;
      }

      // Save only when user input was last.
      if (value != input.value.trim()) {
        continue;
      }

      // Don't save credit card numbers.
      if (lazy.CreditCard.isValidNumber(value)) {
        log("skipping saving a credit card number");
        continue;
      }

      const name = FormHistoryChild.getInputName(input);
      if (!name) {
        continue;
      }

      if (name == "searchbar-history") {
        log('addEntry for input name "' + name + '" is denied');
        continue;
      }

      // Limit stored data to 200 characters.
      if (name.length > 200 || value.length > 200) {
        log("skipping input that has a name/value too large");
        continue;
      }

      entries.push({ name, value });

      // Limit number of fields stored per form.
      if (entries.length >= 100) {
        log("not saving any more entries for this form.");
        break;
      }
    }

    if (entries.length) {
      log("sending entries to parent process for form " + form.id);
      this.sendAsyncMessage("FormHistory:FormSubmitEntries", entries);
    }
  }

  get actorName() {
    return "FormHistory";
  }

  /**
   * Get the search options when searching for autocomplete entries in the parent
   *
   * @param {HTMLInputElement} input - The input element to search for autocomplete entries
   * @returns {object} the search options for the input
   */
  getAutoCompleteSearchOption(input) {
    const inputName = FormHistoryChild.getInputName(input);
    const scenarioName = lazy.FormScenarios.detect({ input }).signUpForm
      ? "SignUpFormScenario"
      : "";

    return { inputName, scenarioName };
  }

  /**
   * Ask the provider whether it might have autocomplete entry to show
   * for the given input.
   *
   * @param {HTMLInputElement} input - The input element to search for autocomplete entries
   * @returns {boolean} true if we shold search for autocomplete entries
   */
  shouldSearchForAutoComplete(input) {
    if (!lazy.gEnabled) {
      return false;
    }

    const inputName = FormHistoryChild.getInputName(input);
    // Don't allow form inputs (aField != null) to get results from
    // search bar history.
    if (inputName == "searchbar-history") {
      log(`autoCompleteSearch for input name "${inputName}" is denied`);
      return false;
    }

    if (input.autocomplete == "off" || input.form?.autocomplete == "off") {
      log("autoCompleteSearch not allowed due to autcomplete=off");
      return false;
    }

    return true;
  }

  /**
   * Convert the search result to autocomplete results
   *
   * @param {string} searchString - The string to search for
   * @param {HTMLInputElement} input - The input element to search for autocomplete entries
   * @param {Array<object>} records - autocomplete records
   * @returns {AutocompleteResult}
   */
  searchResultToAutoCompleteResult(searchString, input, records) {
    const inputName = FormHistoryChild.getInputName(input);
    const acResult = new lazy.FormHistoryAutoCompleteResult(
      input,
      [],
      inputName,
      searchString
    );

    acResult.fixedEntries = this.getDataListSuggestions(input);
    if (!records) {
      return acResult;
    }

    const entries = records.formHistoryEntries;
    const externalEntries = records.externalEntries;

    if (input?.maxLength > -1) {
      acResult.entries = entries.filter(
        el => el.text.length <= input.maxLength
      );
    } else {
      acResult.entries = entries;
    }

    acResult.externalEntries.push(
      ...externalEntries.map(
        entry =>
          new lazy.GenericAutocompleteItem(
            entry.image,
            entry.label,
            entry.secondary,
            entry.fillMessageName,
            entry.fillMessageData
          )
      )
    );

    acResult.removeDuplicateHistoryEntries();
    return acResult;
  }

  #isTextControl(input) {
    return [
      "text",
      "email",
      "search",
      "tel",
      "url",
      "number",
      "month",
      "week",
      "password",
    ].includes(input.type);
  }

  getDataListSuggestions(input) {
    const items = [];

    if (!this.#isTextControl(input) || !input.list) {
      return items;
    }

    const upperFieldValue = input.value.toUpperCase();

    for (const option of input.list.options) {
      const label = option.label || option.text || option.value || "";

      if (!label.toUpperCase().includes(upperFieldValue)) {
        continue;
      }

      items.push({
        label,
        value: option.value,
      });
    }

    return items;
  }
}
PK
!<ī~� actors/FormHistoryParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  FirefoxRelay: "resource://gre/modules/FirefoxRelay.sys.mjs",
  FormHistory: "resource://gre/modules/FormHistory.sys.mjs",
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "PREFERENCE_PREFIX_WEIGHT",
  "browser.formfill.prefixWeight"
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "PREFERENCE_BOUNDARY_WEIGHT",
  "browser.formfill.boundaryWeight"
);

export class FormHistoryParent extends JSWindowActorParent {
  receiveMessage({ name, data }) {
    switch (name) {
      case "FormHistory:FormSubmitEntries":
        this.#onFormSubmitEntries(data);
        break;

      case "FormHistory:AutoCompleteSearchAsync":
        return this.#onAutoCompleteSearch(data);

      case "FormHistory:RemoveEntry":
        this.#onRemoveEntry(data);
        break;
    }

    return undefined;
  }

  #onFormSubmitEntries(entries) {
    const changes = entries.map(entry => ({
      op: "bump",
      fieldname: entry.name,
      value: entry.value,
    }));

    lazy.FormHistory.update(changes);
  }

  get formOrigin() {
    return lazy.LoginHelper.getLoginOrigin(
      this.manager.documentPrincipal?.originNoSuffix
    );
  }

  async #onAutoCompleteSearch({ searchString, params, scenarioName }) {
    searchString = searchString.trim().toLowerCase();

    let formHistoryPromise;
    if (
      FormHistoryParent.canSearchIncrementally(
        searchString,
        this.previousSearchString
      )
    ) {
      formHistoryPromise = Promise.resolve(
        FormHistoryParent.incrementalSearch(
          searchString,
          this.previousSearchString,
          this.previousSearchResult
        )
      );
    } else {
      formHistoryPromise = lazy.FormHistory.getAutoCompleteResults(
        searchString,
        params
      );
    }

    const relayPromise = lazy.FirefoxRelay.autocompleteItemsAsync({
      formOrigin: this.formOrigin,
      scenarioName,
      hasInput: !!searchString.length,
    });
    const [formHistoryEntries, externalEntries] = await Promise.all([
      formHistoryPromise,
      relayPromise,
    ]);

    this.previousSearchString = searchString;
    this.previousSearchResult = formHistoryEntries;

    return { formHistoryEntries, externalEntries };
  }

  #onRemoveEntry({ inputName, value, guid }) {
    lazy.FormHistory.update({
      op: "remove",
      fieldname: inputName,
      value,
      guid,
    });
  }

  async searchAutoCompleteEntries(searchString, data) {
    const { inputName, scenarioName } = data;
    const params = {
      fieldname: inputName,
    };
    return this.#onAutoCompleteSearch({ searchString, params, scenarioName });
  }

  static canSearchIncrementally(searchString, previousSearchString) {
    previousSearchString ||= "";
    return (
      previousSearchString.length > 1 &&
      searchString.includes(previousSearchString)
    );
  }

  static incrementalSearch(
    searchString,
    previousSearchString,
    previousSearchResult
  ) {
    const searchTokens = searchString.split(/\s+/);
    // We have a list of results for a shorter search string, so just
    // filter them further based on the new search string and add to a new array.
    let filteredEntries = [];
    for (const entry of previousSearchResult) {
      // Remove results that do not contain the token
      // XXX bug 394604 -- .toLowerCase can be wrong for some intl chars
      if (searchTokens.some(tok => !entry.textLowerCase.includes(tok))) {
        continue;
      }
      FormHistoryParent.calculateScore(entry, searchString, searchTokens);
      filteredEntries.push(entry);
    }
    filteredEntries.sort((a, b) => b.totalScore - a.totalScore);
    return filteredEntries;
  }

  /*
   * calculateScore
   *
   * entry    -- an nsIAutoCompleteResult entry
   * searchString -- current value of the input (lowercase)
   * searchTokens -- array of tokens of the search string
   *
   * Returns: an int
   */
  static calculateScore(entry, searchString, searchTokens) {
    let boundaryCalc = 0;
    // for each word, calculate word boundary weights
    for (const token of searchTokens) {
      if (entry.textLowerCase.startsWith(token)) {
        boundaryCalc++;
      }
      if (entry.textLowerCase.includes(" " + token)) {
        boundaryCalc++;
      }
    }
    boundaryCalc = boundaryCalc * lazy.PREFERENCE_BOUNDARY_WEIGHT;
    // now add more weight if we have a traditional prefix match and
    // multiply boundary bonuses by boundary weight
    if (entry.textLowerCase.startsWith(searchString)) {
      boundaryCalc += lazy.PREFERENCE_PREFIX_WEIGHT;
    }
    entry.totalScore = Math.round(entry.frecency * Math.max(1, boundaryCalc));
  }

  previewFields(_result) {
    // Not implemented
  }

  autofillFields(_result) {
    // Not implemented
  }
}
PK
!<�p����&actors/InlineSpellCheckerChild.sys.mjs/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  InlineSpellCheckerContent:
    "resource://gre/modules/InlineSpellCheckerContent.sys.mjs",
});

export class InlineSpellCheckerChild extends JSWindowActorChild {
  receiveMessage(msg) {
    switch (msg.name) {
      case "InlineSpellChecker:selectDictionaries":
        lazy.InlineSpellCheckerContent.selectDictionaries(msg.data.localeCodes);
        break;

      case "InlineSpellChecker:replaceMisspelling":
        lazy.InlineSpellCheckerContent.replaceMisspelling(msg.data.suggestion);
        break;

      case "InlineSpellChecker:toggleEnabled":
        lazy.InlineSpellCheckerContent.toggleEnabled();
        break;

      case "InlineSpellChecker:recheck":
        lazy.InlineSpellCheckerContent.recheck();
        break;

      case "InlineSpellChecker:uninit":
        lazy.InlineSpellCheckerContent.uninitContextMenu();
        break;
    }
  }
}
PK
!<n�����'actors/InlineSpellCheckerParent.sys.mjs/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class InlineSpellCheckerParent extends JSWindowActorParent {
  selectDictionaries({ localeCodes }) {
    this.sendAsyncMessage("InlineSpellChecker:selectDictionaries", {
      localeCodes,
    });
  }

  replaceMisspelling({ suggestion }) {
    this.sendAsyncMessage("InlineSpellChecker:replaceMisspelling", {
      suggestion,
    });
  }

  toggleEnabled() {
    this.sendAsyncMessage("InlineSpellChecker:toggleEnabled", {});
  }

  recheckSpelling() {
    this.sendAsyncMessage("InlineSpellChecker:recheck", {});
  }

  uninit() {
    // This method gets called by InlineSpellChecker when the context menu
    // goes away and the InlineSpellChecker instance is still alive.
    // Stop referencing it and tidy the child end of us.
    this.sendAsyncMessage("InlineSpellChecker:uninit", {});
  }

  _destructionObservers = new Set();
  registerDestructionObserver(obj) {
    this._destructionObservers.add(obj);
  }

  unregisterDestructionObserver(obj) {
    this._destructionObservers.delete(obj);
  }

  didDestroy() {
    for (let obs of this._destructionObservers) {
      obs.actorDestroyed(this);
    }
    this._destructionObservers = null;
  }
}
PK
!<���Vkk-actors/KeyPressEventModelCheckerChild.sys.mjs/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

export class KeyPressEventModelCheckerChild extends JSWindowActorChild {
  // Currently, the event is dispatched only when the document becomes editable
  // because of contenteditable.  If you need to add new editor which is in
  // designMode, you need to change MaybeDispatchCheckKeyPressEventModelEvent()
  // of Document.
  handleEvent(aEvent) {
    if (!AppConstants.DEBUG) {
      // Stop propagation in opt build to save the propagation cost.
      // However, the event is necessary for running test_bug1514940.html.
      // Therefore, we need to keep propagating it at least on debug build.
      aEvent.stopImmediatePropagation();
    }

    // Currently, even if we set Document.KEYPRESS_EVENT_MODEL_CONFLATED
    // here, conflated model isn't used forcibly.  If you need it, you need
    // to change WidgetKeyboardEvent, dom::KeyboardEvent and PresShell.
    let model = Document.KEYPRESS_EVENT_MODEL_DEFAULT;
    if (
      this._isOldOfficeOnlineServer(aEvent.target) ||
      this._isOldConfluence(aEvent.target.ownerGlobal)
    ) {
      model = Document.KEYPRESS_EVENT_MODEL_SPLIT;
    }
    aEvent.target.setKeyPressEventModel(model);
  }

  _isOldOfficeOnlineServer(aDocument) {
    let editingElement = aDocument.getElementById(
      "WACViewPanel_EditingElement"
    );
    // If it's not Office Online Server, don't include it into the telemetry
    // because we just need to collect percentage of old version in all loaded
    // Office Online Server instances.
    if (!editingElement) {
      return false;
    }
    let isOldVersion = !editingElement.classList.contains(
      "WACViewPanel_DisableLegacyKeyCodeAndCharCode"
    );
    return isOldVersion;
  }

  _isOldConfluence(aWindow) {
    if (!aWindow) {
      return false;
    }
    // aWindow should be an editor window in <iframe>.  However, we don't know
    // whether it can be without <iframe>.  Anyway, there should be tinyMCE
    // object in the parent window or in the window.
    let tinyMCEObject;
    // First, try to retrieve tinyMCE object from parent window.
    try {
      tinyMCEObject = ChromeUtils.waiveXrays(aWindow.parent).tinyMCE;
    } catch (e) {
      // Ignore the exception for now.
    }
    // Next, if there is no tinyMCE object in the parent window, let's check
    // the window.
    if (!tinyMCEObject) {
      try {
        tinyMCEObject = ChromeUtils.waiveXrays(aWindow).tinyMCE;
      } catch (e) {
        // Fallthrough to return false below.
      }
      // If we couldn't find tinyMCE object, let's assume that it's not
      // Confluence instance.
      if (!tinyMCEObject) {
        return false;
      }
    }
    // If there is tinyMCE object, we can assume that we loaded Confluence
    // instance.  So, let's check the version whether it allows conflated
    // keypress event model.
    try {
      let { author, version } =
        new tinyMCEObject.plugins.CursorTargetPlugin().getInfo();
      // If it's not Confluence, don't include it into the telemetry because
      // we just need to collect percentage of old version in all loaded
      // Confluence instances.
      if (author !== "Atlassian") {
        return false;
      }
      let isOldVersion = version === "1.0";
      return isOldVersion;
    } catch (e) {
      return false;
    }
  }
}
PK
!<�(2wDwDactors/MLEngineChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

/**
 * @typedef {import("../../promiseworker/PromiseWorker.sys.mjs").BasePromiseWorker} BasePromiseWorker
 */

/**
 * @typedef {object} Lazy
 * @typedef {import("../content/Utils.sys.mjs").ProgressAndStatusCallbackParams} ProgressAndStatusCallbackParams
 * @property {typeof import("../../promiseworker/PromiseWorker.sys.mjs").BasePromiseWorker} BasePromiseWorker
 * @property {typeof setTimeout} setTimeout
 * @property {typeof clearTimeout} clearTimeout
 */

/** @type {Lazy} */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  BasePromiseWorker: "resource://gre/modules/PromiseWorker.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
  PipelineOptions: "chrome://global/content/ml/EngineProcess.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  return console.createInstance({
    maxLogLevelPref: "browser.ml.logLevel",
    prefix: "ML:EngineChild",
  });
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "CACHE_TIMEOUT_MS",
  "browser.ml.modelCacheTimeout"
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "MODEL_HUB_ROOT_URL",
  "browser.ml.modelHubRootUrl"
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "MODEL_HUB_URL_TEMPLATE",
  "browser.ml.modelHubUrlTemplate"
);
XPCOMUtils.defineLazyPreferenceGetter(lazy, "LOG_LEVEL", "browser.ml.logLevel");

/**
 * The engine child is responsible for the life cycle and instantiation of the local
 * machine learning inference engine.
 */
export class MLEngineChild extends JSWindowActorChild {
  /**
   * The cached engines.
   *
   * @type {Map<string, EngineDispatcher>}
   */
  #engineDispatchers = new Map();

  // eslint-disable-next-line consistent-return
  async receiveMessage({ name, data }) {
    switch (name) {
      case "MLEngine:NewPort": {
        await this.#onNewPortCreated(data);
        break;
      }
      case "MLEngine:ForceShutdown": {
        for (const engineDispatcher of this.#engineDispatchers.values()) {
          await engineDispatcher.terminate(
            /* shutDownIfEmpty */ true,
            /* replacement */ false
          );
        }
        this.#engineDispatchers = null;
        break;
      }
    }
  }

  /**
   * Handles the actions to be performed after a new port has been created.
   * Specifically, it ensures that the engine dispatcher is created if not already present,
   * and notifies the parent through the port once the engine dispatcher is ready.
   *
   * @param {object} config - Configuration object.
   * @param {MessagePort} config.port - The port of the channel.
   * @param {PipelineOptions} config.pipelineOptions - The options for the pipeline.
   * @returns {Promise<void>} - A promise that resolves once the necessary actions are complete.
   */
  async #onNewPortCreated({ port, pipelineOptions }) {
    try {
      // We get some default options from the prefs
      let options = new lazy.PipelineOptions({
        modelHubRootUrl: lazy.MODEL_HUB_ROOT_URL,
        modelHubUrlTemplate: lazy.MODEL_HUB_URL_TEMPLATE,
        timeoutMS: lazy.CACHE_TIMEOUT_MS,
        logLevel: lazy.LOG_LEVEL,
      });

      // And then overwrite with the ones passed in the message
      options.updateOptions(pipelineOptions);

      // Check if we already have an engine under this id.
      if (this.#engineDispatchers.has(options.engineId)) {
        let currentEngineDispatcher = this.#engineDispatchers.get(
          options.engineId
        );

        // The option matches, let's reuse the engine
        if (currentEngineDispatcher.pipelineOptions.equals(options)) {
          port.postMessage({
            type: "EnginePort:EngineReady",
            error: null,
          });
          return;
        }

        // The options do not match, terminate the old one so we have a single engine per id.
        await currentEngineDispatcher.terminate(
          /* shutDownIfEmpty */ false,
          /* replacement */ true
        );
        this.#engineDispatchers.delete(options.engineId);
      }

      this.#engineDispatchers.set(
        options.engineId,
        await EngineDispatcher.initialize(this, port, options)
      );
      port.postMessage({
        type: "EnginePort:EngineReady",
        error: null,
      });
    } catch (error) {
      port.postMessage({
        type: "EnginePort:EngineReady",
        error,
      });
    }
  }

  handleEvent(event) {
    switch (event.type) {
      case "DOMContentLoaded":
        this.sendAsyncMessage("MLEngine:Ready");
        break;
    }
  }

  /**
   * Gets the wasm array buffer from RemoteSettings.
   *
   * @returns {Promise<ArrayBuffer>}
   */
  getWasmArrayBuffer() {
    return this.sendQuery("MLEngine:GetWasmArrayBuffer");
  }

  /**
   * Gets the inference options from RemoteSettings.
   *
   * @returns {Promise<object>}
   */
  getInferenceOptions(taskName) {
    return this.sendQuery("MLEngine:GetInferenceOptions", {
      taskName,
    });
  }

  /**
   * Retrieves a model file as an ArrayBuffer and headers by communicating with the parent actor.
   *
   * @param {object} config - The configuration accepted by the parent function.
   * @returns {Promise<[ArrayBuffer, object]>} The file content and headers
   */
  getModelFile(config) {
    return this.sendQuery("MLEngine:GetModelFile", config);
  }

  /**
   * Removes an engine by its ID. Optionally shuts down if no engines remain.
   *
   * @param {string} engineId - The ID of the engine to remove.
   * @param {boolean} [shutDownIfEmpty] - If true, shuts down the engine process if no engines remain.
   * @param {boolean} replacement - Flag indicating whether the engine is being replaced.
   */
  removeEngine(engineId, shutDownIfEmpty, replacement) {
    if (!this.#engineDispatchers) {
      return;
    }
    this.#engineDispatchers.delete(engineId);

    this.sendAsyncMessage("MLEngine:Removed", {
      engineId,
      shutdown: shutDownIfEmpty,
      replacement,
    });

    if (this.#engineDispatchers.size === 0 && shutDownIfEmpty) {
      this.sendAsyncMessage("MLEngine:DestroyEngineProcess");
    }
  }
}

/**
 * This classes manages the lifecycle of an ML Engine, and handles dispatching messages
 * to it.
 */
class EngineDispatcher {
  /** @type {MessagePort | null} */
  #port = null;

  /** @type {TimeoutID | null} */
  #keepAliveTimeout = null;

  /** @type {PromiseWithResolvers} */
  #modelRequest;

  /** @type {Promise<Engine> | null} */
  #engine = null;

  /** @type {string} */
  #taskName;

  /** @type {string} */
  #engineId;

  /** @type {PipelineOptions | null} */
  pipelineOptions = null;

  /**
   * Creates the inference engine given the wasm runtime and the run options.
   *
   * The initialization is done in three steps:
   * 1. The wasm runtime is fetched from RS
   * 2. The inference options are fetched from RS and augmented with the pipeline options.
   * 3. The inference engine is created with the wasm runtime and the options.
   *
   * Any exception here will be bubbled up for the constructor to log.
   *
   * @param {PipelineOptions} pipelineOptions
   * @param {?function(ProgressAndStatusCallbackParams):void} notificationsCallback The callback to call for updating about notifications such as dowload progress status.
   * @returns {Promise<Engine>}
   */
  async initializeInferenceEngine(pipelineOptions, notificationsCallback) {
    // Create the inference engine given the wasm runtime and the options.
    const wasm = await this.mlEngineChild.getWasmArrayBuffer();
    let remoteSettingsOptions = await this.mlEngineChild.getInferenceOptions(
      this.#taskName
    );

    // Merge the RemoteSettings inference options with the pipeline options provided.
    let mergedOptions = new lazy.PipelineOptions(remoteSettingsOptions);
    mergedOptions.updateOptions(pipelineOptions);
    lazy.console.debug("Inference engine options:", mergedOptions);

    this.pipelineOptions = mergedOptions;

    return InferenceEngine.create({
      wasm,
      pipelineOptions: mergedOptions,
      notificationsCallback,
      getModelFileFn: this.mlEngineChild.getModelFile.bind(this.mlEngineChild),
    });
  }

  /**
   * Private Constructor for an Engine Dispatcher.
   *
   * @param {MLEngineChild} mlEngineChild
   * @param {MessagePort} port
   * @param {PipelineOptions} pipelineOptions
   */
  constructor(mlEngineChild, port, pipelineOptions) {
    this.mlEngineChild = mlEngineChild;
    this.#taskName = pipelineOptions.taskName;
    this.timeoutMS = pipelineOptions.timeoutMS;
    this.#engineId = pipelineOptions.engineId;

    this.#engine = this.initializeInferenceEngine(
      pipelineOptions,
      notificationsData => {
        this.handleInitProgressStatus(port, notificationsData);
      }
    );

    // Trigger the keep alive timer.
    this.#engine
      .then(() => void this.keepAlive())
      .catch(error => {
        if (
          // Ignore errors from tests intentionally causing errors.
          !error?.message?.startsWith("Intentionally")
        ) {
          lazy.console.error("Could not initalize the engine", error);
        }
      });

    this.#setupMessageHandler(port);
  }

  /**
   * Resolves the engine to fully initialize it.
   */
  async ensureInferenceEngineIsReady() {
    this.#engine = await this.#engine;
  }

  /**
   * Initialize an Engine Dispatcher
   *
   * @param {MLEngineChild} mlEngineChild
   * @param {MessagePort} port
   * @param {PipelineOptions} pipelineOptions
   */
  static async initialize(mlEngineChild, port, pipelineOptions) {
    const dispatcher = new EngineDispatcher(
      mlEngineChild,
      port,
      pipelineOptions
    );

    // In unit tests, maintain the current behavior of resolving during execution instead of initialization.
    if (!Cu.isInAutomation) {
      await dispatcher.ensureInferenceEngineIsReady();
    }

    return dispatcher;
  }

  handleInitProgressStatus(port, notificationsData) {
    port.postMessage({
      type: "EnginePort:InitProgress",
      statusResponse: notificationsData,
    });
  }

  /**
   * The worker needs to be shutdown after some amount of time of not being used.
   */
  keepAlive() {
    if (this.#keepAliveTimeout) {
      // Clear any previous timeout.
      lazy.clearTimeout(this.#keepAliveTimeout);
    }
    // In automated tests, the engine is manually destroyed.
    if (!Cu.isInAutomation) {
      this.#keepAliveTimeout = lazy.setTimeout(
        this.terminate.bind(this),
        this.timeoutMS
      );
    }
  }

  /**
   * @param {MessagePort} port
   */
  getModel(port) {
    if (this.#modelRequest) {
      // There could be a race to get a model, use the first request.
      return this.#modelRequest.promise;
    }
    this.#modelRequest = Promise.withResolvers();
    port.postMessage({ type: "EnginePort:ModelRequest" });
    return this.#modelRequest.promise;
  }

  /**
   * @param {MessagePort} port
   */
  #setupMessageHandler(port) {
    this.#port = port;
    port.onmessage = async ({ data }) => {
      switch (data.type) {
        case "EnginePort:Discard": {
          port.close();
          this.#port = null;
          break;
        }
        case "EnginePort:Terminate": {
          await this.terminate(data.shutdown, data.replacement);
          break;
        }
        case "EnginePort:ModelResponse": {
          if (this.#modelRequest) {
            const { model, error } = data;
            if (model) {
              this.#modelRequest.resolve(model);
            } else {
              this.#modelRequest.reject(error);
            }
            this.#modelRequest = null;
          } else {
            lazy.console.error(
              "Got a EnginePort:ModelResponse but no model resolvers"
            );
          }
          break;
        }
        case "EnginePort:Run": {
          const { requestId, request } = data;
          try {
            await this.ensureInferenceEngineIsReady();
          } catch (error) {
            port.postMessage({
              type: "EnginePort:RunResponse",
              requestId,
              response: null,
              error,
            });
            // The engine failed to load. Terminate the entire dispatcher.
            await this.terminate(
              /* shutDownIfEmpty */ true,
              /* replacement */ false
            );
            return;
          }

          // Do not run the keepAlive timer until we are certain that the engine loaded,
          // as the engine shouldn't be killed while it is initializing.
          this.keepAlive();

          try {
            port.postMessage({
              type: "EnginePort:RunResponse",
              requestId,
              response: await this.#engine.run(request),
              error: null,
            });
          } catch (error) {
            port.postMessage({
              type: "EnginePort:RunResponse",
              requestId,
              response: null,
              error,
            });
          }
          break;
        }
        default:
          lazy.console.error("Unknown port message to engine: ", data);
          break;
      }
    };
  }

  /**
   * Terminates the engine and its worker after a timeout.
   *
   * @param {boolean} shutDownIfEmpty - If true, shuts down the engine process if no engines remain.
   * @param {boolean} replacement - Flag indicating whether the engine is being replaced.
   */
  async terminate(shutDownIfEmpty, replacement) {
    if (this.#keepAliveTimeout) {
      lazy.clearTimeout(this.#keepAliveTimeout);
      this.#keepAliveTimeout = null;
    }
    if (this.#port) {
      // This call will trigger back an EnginePort:Discard that will close the port
      this.#port.postMessage({ type: "EnginePort:EngineTerminated" });
    }
    try {
      const engine = await this.#engine;
      engine.terminate();
    } catch (error) {
      lazy.console.error("Failed to get the engine", error);
    }

    this.mlEngineChild.removeEngine(
      this.#engineId,
      shutDownIfEmpty,
      replacement
    );
  }
}

/**
 * Wrapper for a function that fetches a model file as an ArrayBuffer from a specified URL and task name.
 *
 * @param {object} config
 * @param {string} config.taskName - name of the inference task.
 * @param {string} config.url - The URL of the model file to fetch. Can be a path relative to
 * the model hub root or an absolute URL.
 * @param {string} config.modelHubRootUrl - root url of the model hub. When not provided, uses the default from prefs.
 * @param {string} config.modefHubUrlTemplate - url template of the model hub. When not provided, uses the default from prefs.
 * @param {?function(object):Promise<[ArrayBuffer, object]>} config.getModelFileFn - A function that actually retrieves the model data and headers.
 * @returns {Promise} A promise that resolves to a Meta object containing the URL, response headers,
 * and data as an ArrayBuffer. The data is marked for transfer to avoid cloning.
 */
async function getModelFile({
  taskName,
  url,
  getModelFileFn,
  modelHubRootUrl,
  modefHubUrlTemplate,
}) {
  const [data, headers] = await getModelFileFn({
    taskName,
    url,
    rootUrl: modelHubRootUrl || lazy.MODEL_HUB_ROOT_URL,
    urlTemplate: modefHubUrlTemplate || lazy.MODEL_HUB_URL_TEMPLATE,
  });
  return new lazy.BasePromiseWorker.Meta([url, headers, data], {
    transfers: [data],
  });
}

/**
 * Wrapper around the ChromeWorker that runs the inference.
 */
class InferenceEngine {
  /** @type {BasePromiseWorker} */
  #worker;

  /**
   * Initialize the worker.
   *
   * @param {object} config
   * @param {ArrayBuffer} config.wasm
   * @param {PipelineOptions} config.pipelineOptions
   * @param {?function(ProgressAndStatusCallbackParams):void} config.notificationsCallback The callback to call for updating about notifications such as dowload progress status.
   * @param {?function(object):Promise<[ArrayBuffer, object]>} config.getModelFileFn - A function that actually retrieves the model data and headers.
   * @returns {InferenceEngine}
   */
  static async create({
    wasm,
    pipelineOptions,
    notificationsCallback, // eslint-disable-line no-unused-vars
    getModelFileFn,
  }) {
    /** @type {BasePromiseWorker} */
    const worker = new lazy.BasePromiseWorker(
      "chrome://global/content/ml/MLEngine.worker.mjs",
      { type: "module" },
      {
        getModelFile: async url =>
          getModelFile({
            url,
            taskName: pipelineOptions.taskName,
            getModelFileFn,
            modelHubRootUrl: pipelineOptions.modelHubRootUrl,
            modelHubUrlTemplate: pipelineOptions.modelHubUrlTemplate,
          }),
      }
    );

    const args = [wasm, pipelineOptions];
    const closure = {};
    const transferables = [wasm];
    await worker.post("initializeEngine", args, closure, transferables);
    return new InferenceEngine(worker);
  }

  /**
   * @param {BasePromiseWorker} worker
   */
  constructor(worker) {
    this.#worker = worker;
  }

  /**
   * @param {string} request
   * @returns {Promise<string>}
   */
  run(request) {
    return this.#worker.post("run", [request]);
  }

  terminate() {
    if (this.#worker) {
      this.#worker.terminate();
      this.#worker = null;
    }
  }
}
PK
!<�	��ggactors/MLEngineParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * @typedef {object} Lazy
 * @typedef {import("../content/Utils.sys.mjs").ProgressAndStatusCallbackParams} ProgressAndStatusCallbackParams
 * @property {typeof console} console
 * @property {typeof import("../content/Utils.sys.mjs").getRuntimeWasmFilename} getRuntimeWasmFilename
 * @property {typeof import("../content/EngineProcess.sys.mjs").EngineProcess} EngineProcess
 * @property {typeof import("../../../../services/settings/remote-settings.sys.mjs").RemoteSettings} RemoteSettings
 * @property {typeof import("../../translations/actors/TranslationsParent.sys.mjs").TranslationsParent} TranslationsParent
 */

/** @type {Lazy} */
const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  return console.createInstance({
    maxLogLevelPref: "browser.ml.logLevel",
    prefix: "ML:EngineParent",
  });
});

ChromeUtils.defineESModuleGetters(lazy, {
  getRuntimeWasmFilename: "chrome://global/content/ml/Utils.sys.mjs",
  EngineProcess: "chrome://global/content/ml/EngineProcess.sys.mjs",
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
  TranslationsParent: "resource://gre/actors/TranslationsParent.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
  ModelHub: "chrome://global/content/ml/ModelHub.sys.mjs",
});

const RS_RUNTIME_COLLECTION = "ml-onnx-runtime";
const RS_INFERENCE_OPTIONS_COLLECTION = "ml-inference-options";
const TERMINATE_TIMEOUT = 5000;

/**
 * The ML engine is in its own content process. This actor handles the
 * marshalling of the data such as the engine payload.
 */
export class MLEngineParent extends JSWindowActorParent {
  /**
   * The RemoteSettingsClient that downloads the wasm binaries.
   *
   * @type {Record<string, RemoteSettingsClient>}
   */
  static #remoteClients = {};

  /** @type {Promise<WasmRecord> | null} */
  static #wasmRecord = null;

  /**
   * Locks to prevent race conditions when creating engines.
   *
   * @type {Map<string, Promise>}
   */
  static engineLocks = new Map();

  /**
   * The following constant controls the major version for wasm downloaded from
   * Remote Settings. When a breaking change is introduced, Nightly will have these
   * numbers incremented by one, but Beta and Release will still be on the previous
   * version. Remote Settings will ship both versions of the records, and the latest
   * asset released in that version will be used. For instance, with a major version
   * of "1", assets can be downloaded for "1.0", "1.2", "1.3beta", but assets marked
   * as "2.0", "2.1", etc will not be downloaded.
   */
  static WASM_MAJOR_VERSION = 1;

  /**
   * The modelhub used to retrieve files.
   *
   * @type {ModelHub}
   */
  modelHub = null;

  /**
   * Tracks the most recent revision for each task and model pair that are marked for deletion.
   * Keys are task names and model names. Values contain their respective revisions.
   *
   * @type {Map<string, object>}
   */
  #modelFilesInUse = new Map();

  /**
   * The callback to call for updating about notifications such as dowload progress status.
   *
   * @type {?function(ProgressAndStatusCallbackParams):void}
   */
  notificationsCallback = null;

  /**
   * Remote settings isn't available in tests, so provide mocked responses.
   *
   * @param {RemoteSettingsClient} remoteClients
   */
  static mockRemoteSettings(remoteClients) {
    lazy.console.log("Mocking remote settings in MLEngineParent.");
    MLEngineParent.#remoteClients = remoteClients;
    MLEngineParent.#wasmRecord = null;
  }

  /**
   * Remove anything that could have been mocked.
   */
  static removeMocks() {
    lazy.console.log("Removing mocked remote client in MLEngineParent.");
    MLEngineParent.#remoteClients = {};
    MLEngineParent.#wasmRecord = null;
  }

  /**
   * Creates a new MLEngine.
   *
   * If there's an existing engine with the same pipelineOptions, it will be reused.
   *
   * @param {PipelineOptions} pipelineOptions
   * @param {?function(ProgressAndStatusCallbackParams):void} notificationsCallback A function to call to indicate progress status.
   * @returns {Promise<MLEngine>}
   */
  async getEngine(pipelineOptions, notificationsCallback = null) {
    const engineId = pipelineOptions.engineId;

    // Allow notifications callback changes even when reusing engine.
    this.notificationsCallback = notificationsCallback;

    if (MLEngineParent.engineLocks.has(engineId)) {
      // Wait for the existing lock to resolve
      await MLEngineParent.engineLocks.get(engineId);
    }
    let resolveLock;
    const lockPromise = new Promise(resolve => {
      resolveLock = resolve;
    });
    MLEngineParent.engineLocks.set(engineId, lockPromise);
    try {
      const currentEngine = MLEngine.getInstance(engineId);

      if (currentEngine) {
        if (currentEngine.pipelineOptions.equals(pipelineOptions)) {
          lazy.console.debug("Returning existing engine", engineId);
          return currentEngine;
        }
        await MLEngine.removeInstance(
          engineId,
          /* shutdown */ false,
          /* replacement*/ true
        );
      }

      lazy.console.debug("Creating a new engine");
      const engine = await MLEngine.initialize({
        mlEngineParent: this,
        pipelineOptions,
        notificationsCallback,
      });

      // TODO - What happens if the engine is already killed here?
      return engine;
    } finally {
      MLEngineParent.engineLocks.delete(engineId);
      resolveLock();
    }
  }

  /**
   * Validates a taskName
   *
   * Throws an exception if the task name is invalid.
   *
   * @param {string} taskName
   */
  checkTaskName(taskName) {
    // Define a regular expression to verify taskName pattern (alphanumeric and underscores/dashes)
    const validTaskNamePattern = /^[a-zA-Z0-9_\-]+$/;

    // Check if taskName matches the pattern
    if (!validTaskNamePattern.test(taskName)) {
      // Handle invalid taskName, e.g., throw an error or return null
      throw new Error(
        "Invalid task name. Task name should contain only alphanumeric characters and underscores/dashes."
      );
    }
  }

  // eslint-disable-next-line consistent-return
  async receiveMessage(message) {
    switch (message.name) {
      case "MLEngine:Ready":
        if (lazy.EngineProcess.resolveMLEngineParent) {
          lazy.EngineProcess.resolveMLEngineParent(this);
        } else {
          lazy.console.error(
            "Expected #resolveMLEngineParent to exist when then ML Engine is ready."
          );
        }
        break;
      case "MLEngine:GetWasmArrayBuffer":
        return MLEngineParent.getWasmArrayBuffer();

      case "MLEngine:GetModelFile":
        return this.getModelFile(message.data);

      case "MLEngine:DestroyEngineProcess":
        lazy.EngineProcess.destroyMLEngine().catch(error =>
          console.error(error)
        );
        break;
      case "MLEngine:GetInferenceOptions":
        this.checkTaskName(message.json.taskName);
        return MLEngineParent.getInferenceOptions(message.json.taskName);
      case "MLEngine:Removed":
        if (!message.json.replacement) {
          // when receiving this message from the child, we know it's not a replacement.
          await MLEngine.removeInstance(
            message.json.engineId,
            message.json.shutdown,
            /* replacement */ false
          );
        }
        break;
    }
  }

  /**
   * Deletes all previous revisions for the current task and model used by the engine.
   *
   * @returns {Promise<void>}
   */
  async deletePreviousModelRevisions() {
    if (!this.modelHub) {
      lazy.console.debug(
        "Ignored attempt to delete previous models when the engine is not fully initialized."
      );
    }

    const deletePromises = [];

    for (const [
      key,
      { taskName, model, revision },
    ] of this.#modelFilesInUse.entries()) {
      lazy.console.debug("Deleting previous version for ", {
        taskName,
        model,
        revision,
      });
      deletePromises.push(
        this.modelHub
          .deleteNonMatchingModelRevisions({
            taskName,
            model,
            targetRevision: revision,
          })
          .then(() => this.#modelFilesInUse.delete(key))
      );
    }

    await Promise.all(deletePromises);
  }

  /**
   * Retrieves a model file as an ArrayBuffer from the specified URL.
   * This function normalizes the URL, extracts the organization, model name, and file path,
   * then fetches the model file using the ModelHub API. The `modelHub` instance is created
   * only once and reused for subsequent calls to optimize performance.
   *
   * @param {object} config
   * @param {string} config.taskName - name of the inference task.
   * @param {string} config.url - The URL of the model file to fetch. Can be a path relative to
   * the model hub root or an absolute URL.
   * @param {string} config.rootUrl - The URL of the model file to fetch. Can be a path relative to
   * the model hub root or an absolute URL.
   * @param {string} config.urlTemplate - The URL of the model file to fetch. Can be a path relative to
   * the model hub root or an absolute URL.
   * @returns {Promise<[ArrayBuffer, object]>} The file content and headers
   */
  async getModelFile({ taskName, url, rootUrl, urlTemplate }) {
    // Create the model hub instance if needed
    if (!this.modelHub) {
      lazy.console.debug("Creating model hub instance");
      this.modelHub = new lazy.ModelHub({
        rootUrl,
        urlTemplate,
      });
    }

    if (url.startsWith(rootUrl)) {
      url = url.slice(rootUrl.length);
      // Make sure we get a front slash
      if (!url.startsWith("/")) {
        url = `/${url}`;
      }
    }

    // Parsing url to get model name, and file path.
    // if this errors out, it will be caught in the worker
    const parsedUrl = this.modelHub.parseUrl(url);

    const [data, headers] = await this.modelHub.getModelFileAsArrayBuffer({
      taskName,
      ...parsedUrl,
      modelHubRootUrl: rootUrl,
      modelHubUrlTemplate: urlTemplate,
      progressCallback: this.notificationsCallback?.bind(this),
    });

    // Keep the latest revision for each task, model
    this.#modelFilesInUse.set(`${taskName}-${parsedUrl.model}`, {
      taskName,
      ...parsedUrl,
    });

    return [data, headers];
  }

  /** Gets the wasm file from remote settings.
   *
   * @param {RemoteSettingsClient} client
   */
  static async #getWasmArrayRecord(client) {
    const wasmFilename = lazy.getRuntimeWasmFilename(this.browsingContext);

    /** @type {WasmRecord[]} */
    const wasmRecords = await lazy.TranslationsParent.getMaxVersionRecords(
      client,
      {
        filters: { name: wasmFilename },
        majorVersion: MLEngineParent.WASM_MAJOR_VERSION,
      }
    );

    if (wasmRecords.length === 0) {
      // The remote settings client provides an empty list of records when there is
      // an error.
      throw new Error("Unable to get the ML engine from Remote Settings.");
    }

    if (wasmRecords.length > 1) {
      MLEngineParent.reportError(
        new Error("Expected the ml engine to only have 1 record."),
        wasmRecords
      );
    }
    const [record] = wasmRecords;
    lazy.console.debug(
      `Using runtime ${record.name}@${record.version}`,
      record
    );
    return record;
  }

  /** Gets the inference options from remote settings given a task name.
   *
   * @param {string} taskName - name of the inference :wtask
   * @returns {Promise<ModelRevisionRecord>}
   */
  static async getInferenceOptions(taskName) {
    const client = MLEngineParent.#getRemoteClient(
      RS_INFERENCE_OPTIONS_COLLECTION
    );
    const records = await client.get({
      filters: {
        taskName,
      },
    });

    // if the task name is not in our settings, we just set the onnx runtime filename.
    if (records.length === 0) {
      return {
        runtimeFilename: lazy.getRuntimeWasmFilename(this.browsingContext),
      };
    }
    const options = records[0];
    return {
      modelRevision: options.modelRevision,
      modelId: options.modelId,
      tokenizerRevision: options.tokenizerRevision,
      tokenizerId: options.tokenizerId,
      processorRevision: options.processorRevision,
      processorId: options.processorId,
      runtimeFilename: lazy.getRuntimeWasmFilename(this.browsingContext),
    };
  }

  /**
   * Download the wasm for the ML inference engine.
   *
   * @returns {Promise<ArrayBuffer>}
   */
  static async getWasmArrayBuffer() {
    const client = MLEngineParent.#getRemoteClient(RS_RUNTIME_COLLECTION);

    if (!MLEngineParent.#wasmRecord) {
      // Place the records into a promise to prevent any races.
      MLEngineParent.#wasmRecord = MLEngineParent.#getWasmArrayRecord(client);
    }

    let wasmRecord;
    try {
      wasmRecord = await MLEngineParent.#wasmRecord;
      if (!wasmRecord) {
        return Promise.reject(
          "Error: Unable to get the ML engine from Remote Settings."
        );
      }
    } catch (error) {
      MLEngineParent.#wasmRecord = null;
      throw error;
    }

    /** @type {{buffer: ArrayBuffer}} */
    const { buffer } = await client.attachments.download(wasmRecord);

    return buffer;
  }

  /**
   * Lazily initializes the RemoteSettingsClient for the downloaded wasm binary data.
   *
   * @param {string} collectionName - The name of the collection to use.
   * @returns {RemoteSettingsClient}
   */
  static #getRemoteClient(collectionName) {
    if (MLEngineParent.#remoteClients[collectionName]) {
      return MLEngineParent.#remoteClients[collectionName];
    }

    /** @type {RemoteSettingsClient} */
    const client = lazy.RemoteSettings(collectionName, {
      bucketName: "main",
    });

    MLEngineParent.#remoteClients[collectionName] = client;

    client.on("sync", async ({ data: { created, updated, deleted } }) => {
      lazy.console.debug(`"sync" event for ${collectionName}`, {
        created,
        updated,
        deleted,
      });

      // Remove all the deleted records.
      for (const record of deleted) {
        await client.attachments.deleteDownloaded(record);
      }

      // Remove any updated records, and download the new ones.
      for (const { old: oldRecord } of updated) {
        await client.attachments.deleteDownloaded(oldRecord);
      }

      // Do nothing for the created records.
    });

    return client;
  }

  /**
   * Send a message to gracefully shutdown all of the ML engines in the engine process.
   * This mostly exists for testing the shutdown paths of the code.
   */
  forceShutdown() {
    return this.sendQuery("MLEngine:ForceShutdown");
  }
}

/**
 * The interface to communicate to an MLEngine in the parent process. The engine manages
 * its own lifetime, and is kept alive with a timeout. A reference to this engine can
 * be retained, but once idle, the engine will be destroyed. If a new request to run
 * is sent, the engine will be recreated on demand. This balances the cost of retaining
 * potentially large amounts of memory to run models, with the speed and ease of running
 * the engine.
 *
 * @template Request
 * @template Response
 */
class MLEngine {
  /**
   * The cached engines.
   *
   * @type {Map<string, MLEngine>}
   */
  static #instances = new Map();

  /**
   * @type {MessagePort | null}
   */
  #port = null;

  #nextRequestId = 0;

  /**
   * Tie together a message id to a resolved response.
   *
   * @type {Map<number, PromiseWithResolvers<Request>>}
   */
  #requests = new Map();

  /**
   * @type {"uninitialized" | "ready" | "error" | "closed"}
   */
  engineStatus = "uninitialized";

  /**
   * Unique identifier for the engine.
   *
   * @type {string}
   */
  engineId;

  /**
   * Callback to call when receiving an initializing progress status.
   *
   * @type {?function(ProgressAndStatusCallbackParams):void}
   */
  notificationsCallback = null;

  /**
   * Removes an instance of the MLEngine with the given engineId.
   *
   * @param {string} engineId - The ID of the engine instance to be removed.
   * @param {boolean} shutdown - Flag indicating whether to shutdown the engine.
   * @param {boolean} replacement - Flag indicating whether the engine is being replaced.
   * @returns {Promise<void>} A promise that resolves once the engine is removed.
   */
  static async removeInstance(engineId, shutdown, replacement) {
    for (const [id, engine] of MLEngine.#instances.entries()) {
      if (engine.engineId == engineId) {
        await engine.terminate(shutdown, replacement);
        MLEngine.#instances.delete(id);
      }
    }
  }

  /**
   * Retrieves an instance of the MLEngine with the given engineId.
   *
   * @param {string} engineId - The ID of the engine instance to retrieve.
   * @returns {MLEngine|null} The engine instance with the given ID, or null if not found.
   */
  static getInstance(engineId) {
    return MLEngine.#instances.get(engineId) || null;
  }

  /**
   * Private constructor for an ML Engine.
   *
   * @param {object} config - The configuration object for the instance.
   * @param {object} config.mlEngineParent - The parent machine learning engine associated with this instance.
   * @param {object} config.pipelineOptions - The options for configuring the pipeline associated with this instance.
   * @param {?function(ProgressAndStatusCallbackParams):void} config.notificationsCallback - The initialization progress callback function to call.
   */
  constructor({ mlEngineParent, pipelineOptions, notificationsCallback }) {
    const engineId = pipelineOptions.engineId;
    this.events = {};
    this.engineId = engineId;
    lazy.console.log("MLEngine constructor, adding engine", engineId);
    MLEngine.#instances.set(engineId, this);
    lazy.console.log("Instances", MLEngine.#instances);
    this.mlEngineParent = mlEngineParent;
    this.pipelineOptions = pipelineOptions;
    this.notificationsCallback = notificationsCallback;
  }

  /**
   * Initialize the MLEngine.
   *
   * @param {object} config - The configuration object for the instance.
   * @param {object} config.mlEngineParent - The parent machine learning engine associated with this instance.
   * @param {object} config.pipelineOptions - The options for configuring the pipeline associated with this instance.
   * @param {?function(ProgressAndStatusCallbackParams):void} config.notificationsCallback - The initialization progress callback function to call.
   */
  static async initialize({
    mlEngineParent,
    pipelineOptions,
    notificationsCallback,
  }) {
    const mlEngine = new MLEngine({
      mlEngineParent,
      pipelineOptions,
      notificationsCallback,
    });

    await mlEngine.setupPortCommunication();

    // Delete previous model revisions.
    await mlEngine.mlEngineParent.deletePreviousModelRevisions();

    return mlEngine;
  }

  /**
   * Registers an event listener for the specified event.
   *
   * @param {string} event - The name of the event.
   * @param {Function} listener - The callback function to execute when the event is triggered.
   */
  on(event, listener) {
    if (!this.events[event]) {
      this.events[event] = [];
    }
    this.events[event].push(listener);
  }

  /**
   * Removes an event listener for the specified event.
   *
   * @param {string} event - The name of the event.
   * @param {Function} listenerToRemove - The callback function to remove.
   */
  off(event, listenerToRemove) {
    if (!this.events[event]) {
      return;
    }

    this.events[event] = this.events[event].filter(
      listener => listener !== listenerToRemove
    );
  }

  /**
   * Emits the specified event, invoking all registered listeners with the provided data.
   *
   * @param {string} event - The name of the event.
   * @param {*} data - The data to pass to the event listeners.
   */
  emit(event, data) {
    if (!this.events[event]) {
      return;
    }
    this.events[event].forEach(listener => listener(data));
  }

  /**
   * Sets the engine status and emits a statusChanged event.
   *
   * @param {"uninitialized" | "ready" | "error" | "closed"} status - The new status of the engine.
   */
  setEngineStatus(status) {
    this.engineStatus = status;
    this.emit("statusChanged", status);
  }

  /**
   * Create a MessageChannel to communicate with the engine directly.
   * And ensure the engine is fully initialized with all required files for the current model version downloaded.
   */
  async setupPortCommunication() {
    const { port1: childPort, port2: parentPort } = new MessageChannel();
    const transferables = [childPort];
    this.#port = parentPort;
    const newPortResolvers = Promise.withResolvers();
    this.#port.onmessage = message =>
      this.handlePortMessage(message, newPortResolvers);
    this.mlEngineParent.sendAsyncMessage(
      "MLEngine:NewPort",
      {
        port: childPort,
        pipelineOptions: this.pipelineOptions.getOptions(),
      },
      transferables
    );
    await newPortResolvers.promise;

    this.setEngineStatus("ready");
  }

  /**
   * Handles messages received from the port.
   *
   * @param {object} event - The message event.
   * @param {object} event.data - The data of the message event.
   * @param {object} newPortResolvers - An object containing a promise for mlEngine new port setup, along with two functions to resolve or reject it.
   */
  handlePortMessage = ({ data }, newPortResolvers) => {
    switch (data.type) {
      case "EnginePort:EngineReady": {
        if (data.error) {
          newPortResolvers.reject(data.error);
        } else {
          newPortResolvers.resolve();
        }

        break;
      }
      case "EnginePort:ModelRequest": {
        if (this.#port) {
          this.getModel().then(
            model => {
              this.#port.postMessage({
                type: "EnginePort:ModelResponse",
                model,
                error: null,
              });
            },
            error => {
              this.#port.postMessage({
                type: "EnginePort:ModelResponse",
                model: null,
                error,
              });
              if (
                // Ignore intentional errors in tests.
                !error?.message.startsWith("Intentionally")
              ) {
                lazy.console.error("Failed to get the model", error);
              }
            }
          );
        } else {
          lazy.console.error(
            "Expected a port to exist during the EnginePort:GetModel event"
          );
        }
        break;
      }
      case "EnginePort:RunResponse": {
        const { response, error, requestId } = data;
        const request = this.#requests.get(requestId);
        if (request) {
          if (response) {
            request.resolve(response);
          } else {
            request.reject(error);
          }
        } else {
          lazy.console.error(
            "Could not resolve response in the MLEngineParent",
            data
          );
        }
        this.#requests.delete(requestId);
        break;
      }
      case "EnginePort:EngineTerminated": {
        // The engine was terminated, and if a new run is needed a new port
        // will need to be requested.
        this.setEngineStatus("closed");
        this.discardPort();
        break;
      }
      case "EnginePort:InitProgress": {
        this.notificationsCallback?.(data.statusResponse);
        break;
      }
      default:
        lazy.console.error("Unknown port message from engine", data);
        break;
    }
  };

  /**
   * Discards the current port and closes the connection.
   */
  discardPort() {
    if (this.#port) {
      this.#port.postMessage({ type: "EnginePort:Discard" });
      this.#port.close();
      this.#port = null;
    }
  }

  /**
   * Terminates the engine.
   *
   * @param {boolean} shutdown - Flag indicating whether to shutdown the engine.
   * @param {boolean} replacement - Flag indicating whether the engine is being replaced.
   * @returns {Promise<void>} A promise that resolves once the engine is terminated.
   */
  async terminate(shutdown, replacement) {
    if (this.#port) {
      this.#port.postMessage({
        type: "EnginePort:Terminate",
        shutdown,
        replacement,
      });
    }
    await this.#waitForStatus("closed");
  }

  /**
   * Waits for the engine to reach the desired status.
   *
   * @param {string} desiredStatus - The desired engine status.
   * @returns {Promise<string>} - A promise that resolves when the engine reaches the desired status.
   */

  #waitForStatus(desiredStatus) {
    return new Promise((resolve, reject) => {
      // Initial check in case the status is already the desired one
      if (this.engineStatus === desiredStatus) {
        resolve(`Engine status is now ${desiredStatus}`);
      }

      let onStatusChanged;

      // Set a timeout to reject the promise if the status doesn't change in time
      const timeoutId = lazy.setTimeout(() => {
        this.off("statusChanged", onStatusChanged);
        reject(
          `Timeout after ${TERMINATE_TIMEOUT}ms: Engine status did not reach ${desiredStatus}`
        );
      }, TERMINATE_TIMEOUT);

      onStatusChanged = status => {
        if (status === desiredStatus) {
          this.off("statusChanged", onStatusChanged);
          lazy.clearTimeout(timeoutId);
          resolve(`Engine status is now ${desiredStatus}`);
        }
      };

      this.on("statusChanged", onStatusChanged);
    });
  }

  /**
   * Run the inference request
   *
   * @param {Request} request
   * @returns {Promise<Response>}
   */
  run(request) {
    const resolvers = Promise.withResolvers();
    const requestId = this.#nextRequestId++;
    this.#requests.set(requestId, resolvers);

    let transferables = [];
    if (request.data instanceof ArrayBuffer) {
      transferables.push(request.data);
    }

    this.#port.postMessage(
      {
        type: "EnginePort:Run",
        requestId,
        request,
      },
      transferables
    );
    return resolvers.promise;
  }
}
PK
!<��-�AAactors/MegalistChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class MegalistChild extends JSWindowActorChild {
  receiveMessage(message) {
    // Forward message to the View
    const win = this.document.defaultView;
    const ev = new win.CustomEvent("MessageFromViewModel", {
      detail: message,
    });
    win.dispatchEvent(ev);
  }

  // Prevent TypeError: Property 'handleEvent' is not callable.
  handleEvent() {}
}
PK
!<�u
���actors/MegalistParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { MegalistViewModel } from "resource://gre/modules/megalist/MegalistViewModel.sys.mjs";

/**
 * MegalistParent integrates MegalistViewModel into Parent/Child model.
 */
export class MegalistParent extends JSWindowActorParent {
  #viewModel;

  actorCreated() {
    this.#viewModel = new MegalistViewModel((...args) =>
      this.sendAsyncMessage(...args)
    );
  }

  didDestroy() {
    this.#viewModel.willDestroy();
    this.#viewModel = null;
  }

  receiveMessage(message) {
    return this.#viewModel?.handleViewMessage(message);
  }
}
PK
!<�3���actors/NetErrorChild.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AppInfo: "chrome://remote/content/shared/AppInfo.sys.mjs",
});

import { RemotePageChild } from "resource://gre/actors/RemotePageChild.sys.mjs";

export class NetErrorChild extends RemotePageChild {
  actorCreated() {
    super.actorCreated();

    // If you add a new function, remember to add it to RemotePageAccessManager.sys.mjs
    // to allow content-privileged about:neterror or about:certerror to use it.
    const exportableFunctions = [
      "RPMGetAppBuildID",
      "RPMGetInnerMostURI",
      "RPMAddToHistogram",
      "RPMRecordTelemetryEvent",
      "RPMCheckAlternateHostAvailable",
      "RPMGetHttpResponseHeader",
      "RPMIsTRROnlyFailure",
      "RPMIsFirefox",
      "RPMIsNativeFallbackFailure",
      "RPMOpenPreferences",
      "RPMGetTRRSkipReason",
      "RPMGetTRRDomain",
      "RPMIsSiteSpecificTRRError",
      "RPMSetTRRDisabledLoadFlags",
      "RPMGetCurrentTRRMode",
    ];
    this.exportFunctions(exportableFunctions);
  }

  getFailedCertChain(docShell) {
    let securityInfo =
      docShell.failedChannel && docShell.failedChannel.securityInfo;
    if (!securityInfo) {
      return [];
    }
    return securityInfo.failedCertChain.map(cert => cert.getBase64DERString());
  }

  handleEvent(aEvent) {
    // Documents have a null ownerDocument.
    let doc = aEvent.originalTarget.ownerDocument || aEvent.originalTarget;

    switch (aEvent.type) {
      case "click":
        let elem = aEvent.originalTarget;
        if (elem.id == "viewCertificate") {
          // Call through the superclass to avoid the security check.
          this.sendAsyncMessage("Browser:CertExceptionError", {
            location: doc.location.href,
            elementId: elem.id,
            failedCertChain: this.getFailedCertChain(doc.defaultView.docShell),
          });
        }
        break;
    }
  }

  RPMGetInnerMostURI(uriString) {
    let uri = Services.io.newURI(uriString);
    if (uri instanceof Ci.nsINestedURI) {
      uri = uri.QueryInterface(Ci.nsINestedURI).innermostURI;
    }

    return uri.spec;
  }

  RPMGetAppBuildID() {
    return Services.appinfo.appBuildID;
  }

  RPMAddToHistogram(histID, bin) {
    Services.telemetry.getHistogramById(histID).add(bin);
  }

  RPMRecordTelemetryEvent(category, event, object, value, extra) {
    Services.telemetry.recordEvent(category, event, object, value, extra);
  }

  RPMCheckAlternateHostAvailable() {
    const host = this.contentWindow.location.host.trim();

    // Adapted from UrlbarUtils::looksLikeSingleWordHost
    // https://searchfox.org/mozilla-central/rev/a26af613a476fafe6c3eba05a81bef63dff3c9f1/browser/components/urlbar/UrlbarUtils.sys.mjs#893
    const REGEXP_SINGLE_WORD = /^[^\s@:/?#]+(:\d+)?$/;
    if (!REGEXP_SINGLE_WORD.test(host)) {
      return;
    }

    let info = Services.uriFixup.forceHttpFixup(
      this.contentWindow.location.href
    );

    if (!info.fixupCreatedAlternateURI && !info.fixupChangedProtocol) {
      return;
    }

    let { displayHost, displaySpec, pathQueryRef } = info.fixedURI;

    if (pathQueryRef.endsWith("/")) {
      pathQueryRef = pathQueryRef.slice(0, pathQueryRef.length - 1);
    }

    let weakDoc = Cu.getWeakReference(this.contentWindow.document);
    let onLookupCompleteListener = {
      onLookupComplete(request, record, status) {
        let doc = weakDoc.get();
        if (!doc || !Components.isSuccessCode(status)) {
          return;
        }

        let link = doc.createElement("a");
        link.href = displaySpec;
        link.setAttribute("data-l10n-name", "website");

        let span = doc.createElement("span");
        span.appendChild(link);
        doc.l10n.setAttributes(span, "neterror-dns-not-found-with-suggestion", {
          hostAndPath: displayHost + pathQueryRef,
        });

        const shortDesc = doc.getElementById("errorShortDesc");
        shortDesc.textContent += " ";
        shortDesc.appendChild(span);
      },
    };

    Services.uriFixup.checkHost(
      info.fixedURI,
      onLookupCompleteListener,
      this.document.nodePrincipal.originAttributes
    );
  }

  // Get the header from the http response of the failed channel. This function
  // is used in the 'about:neterror' page.
  RPMGetHttpResponseHeader(responseHeader) {
    let channel = this.contentWindow.docShell.failedChannel;
    if (!channel) {
      return "";
    }

    let httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
    if (!httpChannel) {
      return "";
    }

    try {
      return httpChannel.getResponseHeader(responseHeader);
    } catch (e) {}

    return "";
  }

  RPMIsTRROnlyFailure() {
    // We will only show this in Firefox because the options may direct users to settings only available on Firefox Desktop
    let channel = this.contentWindow?.docShell?.failedChannel?.QueryInterface(
      Ci.nsIHttpChannelInternal
    );
    if (!channel) {
      return false;
    }
    return channel.effectiveTRRMode == Ci.nsIRequest.TRR_ONLY_MODE;
  }

  RPMIsFirefox() {
    return lazy.AppInfo.isFirefox;
  }

  _getTRRSkipReason() {
    let channel = this.contentWindow?.docShell?.failedChannel?.QueryInterface(
      Ci.nsIHttpChannelInternal
    );
    return channel?.trrSkipReason ?? Ci.nsITRRSkipReason.TRR_UNSET;
  }

  RPMIsNativeFallbackFailure() {
    if (!this.contentWindow?.navigator.onLine) {
      return false;
    }

    let skipReason = this._getTRRSkipReason();

    if (
      Services.dns.currentTrrMode === Ci.nsIDNSService.MODE_TRRFIRST &&
      skipReason === Ci.nsITRRSkipReason.TRR_NOT_CONFIRMED
    ) {
      return true;
    }

    const warningReasons = new Set([
      Ci.nsITRRSkipReason.TRR_HEURISTIC_TRIPPED_GOOGLE_SAFESEARCH,
      Ci.nsITRRSkipReason.TRR_HEURISTIC_TRIPPED_YOUTUBE_SAFESEARCH,
      Ci.nsITRRSkipReason.TRR_HEURISTIC_TRIPPED_ZSCALER_CANARY,
      Ci.nsITRRSkipReason.TRR_HEURISTIC_TRIPPED_CANARY,
      Ci.nsITRRSkipReason.TRR_HEURISTIC_TRIPPED_MODIFIED_ROOTS,
      Ci.nsITRRSkipReason.TRR_HEURISTIC_TRIPPED_PARENTAL_CONTROLS,
      Ci.nsITRRSkipReason.TRR_HEURISTIC_TRIPPED_THIRD_PARTY_ROOTS,
      Ci.nsITRRSkipReason.TRR_HEURISTIC_TRIPPED_ENTERPRISE_POLICY,
      Ci.nsITRRSkipReason.TRR_HEURISTIC_TRIPPED_VPN,
      Ci.nsITRRSkipReason.TRR_HEURISTIC_TRIPPED_PROXY,
      Ci.nsITRRSkipReason.TRR_HEURISTIC_TRIPPED_NRPT,
    ]);

    return (
      Services.dns.currentTrrMode === Ci.nsIDNSService.MODE_NATIVEONLY &&
      warningReasons.has(skipReason)
    );
  }

  RPMGetTRRSkipReason() {
    let skipReason = this._getTRRSkipReason();
    return Services.dns.getTRRSkipReasonName(skipReason);
  }

  RPMGetTRRDomain() {
    return Services.dns.trrDomain;
  }

  RPMIsSiteSpecificTRRError() {
    let skipReason = this._getTRRSkipReason();
    switch (skipReason) {
      case Ci.nsITRRSkipReason.TRR_NXDOMAIN:
      case Ci.nsITRRSkipReason.TRR_RCODE_FAIL:
      case Ci.nsITRRSkipReason.TRR_NO_ANSWERS:
        return true;
    }
    return false;
  }

  RPMSetTRRDisabledLoadFlags() {
    this.contentWindow.docShell.browsingContext.defaultLoadFlags |=
      Ci.nsIRequest.LOAD_TRR_DISABLED_MODE;
  }
}
PK
!<(�MQ-Q-actors/NetErrorParent.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

import { PrivateBrowsingUtils } from "resource://gre/modules/PrivateBrowsingUtils.sys.mjs";
import { TelemetryController } from "resource://gre/modules/TelemetryController.sys.mjs";

const PREF_SSL_IMPACT_ROOTS = [
  "security.tls.version.",
  "security.ssl3.",
  "security.tls13.",
];

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
  HomePage: "resource:///modules/HomePage.sys.mjs",
});

class CaptivePortalObserver {
  constructor(actor) {
    this.actor = actor;
    Services.obs.addObserver(this, "captive-portal-login-abort");
    Services.obs.addObserver(this, "captive-portal-login-success");
  }

  stop() {
    Services.obs.removeObserver(this, "captive-portal-login-abort");
    Services.obs.removeObserver(this, "captive-portal-login-success");
  }

  observe(aSubject, aTopic) {
    switch (aTopic) {
      case "captive-portal-login-abort":
      case "captive-portal-login-success":
        // Send a message to the content when a captive portal is freed
        // so that error pages can refresh themselves.
        this.actor.sendAsyncMessage("AboutNetErrorCaptivePortalFreed");
        break;
    }
  }
}

export class NetErrorParent extends JSWindowActorParent {
  constructor() {
    super();
    this.captivePortalObserver = new CaptivePortalObserver(this);
  }

  didDestroy() {
    if (this.captivePortalObserver) {
      this.captivePortalObserver.stop();
    }
  }

  get browser() {
    return this.browsingContext.top.embedderElement;
  }

  hasChangedCertPrefs() {
    let prefSSLImpact = PREF_SSL_IMPACT_ROOTS.reduce((prefs, root) => {
      return prefs.concat(Services.prefs.getChildList(root));
    }, []);
    for (let prefName of prefSSLImpact) {
      if (Services.prefs.prefHasUserValue(prefName)) {
        return true;
      }
    }

    return false;
  }

  async ReportBlockingError(bcID, scheme, host, port, path, xfoAndCspInfo) {
    // For reporting X-Frame-Options error and CSP: frame-ancestors errors, We
    // are collecting 4 pieces of information.
    // 1. The X-Frame-Options in the response header.
    // 2. The CSP: frame-ancestors in the response header.
    // 3. The URI of the frame who triggers this error.
    // 4. The top-level URI which loads the frame.
    //
    // We will exclude the query strings from the reporting URIs.
    //
    // More details about the data we send can be found in
    // https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/data/xfocsp-error-report-ping.html
    //

    let topBC = BrowsingContext.get(bcID).top;
    let topURI = topBC.currentWindowGlobal.documentURI;

    // Get the URLs without query strings.
    let frame_uri = `${scheme}://${host}${port == -1 ? "" : ":" + port}${path}`;
    let top_uri = `${topURI.scheme}://${topURI.hostPort}${topURI.filePath}`;

    TelemetryController.submitExternalPing(
      "xfocsp-error-report",
      {
        ...xfoAndCspInfo,
        frame_hostname: host,
        top_hostname: topURI.host,
        frame_uri,
        top_uri,
      },
      { addClientId: false, addEnvironment: false }
    );
  }

  /**
   * Return the default start page for the cases when the user's own homepage is
   * infected, so we can get them somewhere safe.
   */
  getDefaultHomePage(win) {
    let url;
    if (
      !PrivateBrowsingUtils.isWindowPrivate(win) &&
      AppConstants.MOZ_BUILD_APP == "browser"
    ) {
      url = lazy.HomePage.getDefault();
    }
    url ||= win.BROWSER_NEW_TAB_URL || "about:blank";

    // If url is a pipe-delimited set of pages, just take the first one.
    if (url.includes("|")) {
      url = url.split("|")[0];
    }
    return url;
  }

  /**
   * Re-direct the browser to the previous page or a known-safe page if no
   * previous page is found in history.  This function is used when the user
   * browses to a secure page with certificate issues and is presented with
   * about:certerror.  The "Go Back" button should take the user to the previous
   * or a default start page so that even when their own homepage is on a server
   * that has certificate errors, we can get them somewhere safe.
   */
  goBackFromErrorPage(browser) {
    if (!browser.canGoBack) {
      // If the unsafe page is the first or the only one in history, go to the
      // start page.
      browser.fixupAndLoadURIString(
        this.getDefaultHomePage(browser.ownerGlobal),
        {
          triggeringPrincipal:
            Services.scriptSecurityManager.getSystemPrincipal(),
        }
      );
    } else {
      browser.goBack();
    }
  }

  /**
   * This function does a canary request to a reliable, maintained endpoint, in
   * order to help network code detect a system-wide man-in-the-middle.
   */
  primeMitm(browser) {
    // If we already have a mitm canary issuer stored, then don't bother with the
    // extra request. This will be cleared on every update ping.
    if (Services.prefs.getStringPref("security.pki.mitm_canary_issuer", null)) {
      return;
    }

    let url = Services.prefs.getStringPref(
      "security.certerrors.mitm.priming.endpoint"
    );
    let request = new XMLHttpRequest({ mozAnon: true });
    request.open("HEAD", url);
    request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
    request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;

    request.addEventListener("error", () => {
      // Make sure the user is still on the cert error page.
      if (!browser.documentURI.spec.startsWith("about:certerror")) {
        return;
      }

      let secInfo = request.channel.securityInfo;
      if (secInfo.errorCodeString != "SEC_ERROR_UNKNOWN_ISSUER") {
        return;
      }

      // When we get to this point there's already something deeply wrong, it's very likely
      // that there is indeed a system-wide MitM.
      if (secInfo.serverCert && secInfo.serverCert.issuerName) {
        // Grab the issuer of the certificate used in the exchange and store it so that our
        // network-level MitM detection code has a comparison baseline.
        Services.prefs.setStringPref(
          "security.pki.mitm_canary_issuer",
          secInfo.serverCert.issuerName
        );

        // MitM issues are sometimes caused by software not registering their root certs in the
        // Firefox root store. We might opt for using third party roots from the system root store.
        if (
          Services.prefs.getBoolPref(
            "security.certerrors.mitm.auto_enable_enterprise_roots"
          )
        ) {
          if (
            !Services.prefs.getBoolPref("security.enterprise_roots.enabled")
          ) {
            // Loading enterprise roots happens on a background thread, so wait for import to finish.
            lazy.BrowserUtils.promiseObserved(
              "psm:enterprise-certs-imported"
            ).then(() => {
              if (browser.documentURI.spec.startsWith("about:certerror")) {
                browser.reload();
              }
            });

            Services.prefs.setBoolPref(
              "security.enterprise_roots.enabled",
              true
            );
            // Record that this pref was automatically set.
            Services.prefs.setBoolPref(
              "security.enterprise_roots.auto-enabled",
              true
            );
          }
        } else {
          // Need to reload the page to make sure network code picks up the canary issuer pref.
          browser.reload();
        }
      }
    });

    request.send(null);
  }

  displayOfflineSupportPage(supportPageSlug) {
    const AVAILABLE_PAGES = ["connection-not-secure", "time-errors"];
    if (!AVAILABLE_PAGES.includes(supportPageSlug)) {
      console.log(
        `[Not supported] Offline support is not yet available for ${supportPageSlug} errors.`
      );
      return;
    }

    let offlinePagePath = `chrome://global/content/neterror/supportpages/${supportPageSlug}.html`;
    let triggeringPrincipal =
      Services.scriptSecurityManager.getSystemPrincipal();
    this.browser.loadURI(Services.io.newURI(offlinePagePath), {
      triggeringPrincipal,
    });
  }

  receiveMessage(message) {
    switch (message.name) {
      case "Browser:EnableOnlineMode":
        // Reset network state and refresh the page.
        Services.io.offline = false;
        this.browser.reload();
        break;
      case "Browser:OpenCaptivePortalPage":
        this.browser.ownerGlobal.CaptivePortalWatcher.ensureCaptivePortalTab();
        break;
      case "Browser:PrimeMitm":
        this.primeMitm(this.browser);
        break;
      case "Browser:ResetEnterpriseRootsPref":
        Services.prefs.clearUserPref("security.enterprise_roots.enabled");
        Services.prefs.clearUserPref("security.enterprise_roots.auto-enabled");
        break;
      case "Browser:ResetSSLPreferences":
        let prefSSLImpact = PREF_SSL_IMPACT_ROOTS.reduce((prefs, root) => {
          return prefs.concat(Services.prefs.getChildList(root));
        }, []);
        for (let prefName of prefSSLImpact) {
          Services.prefs.clearUserPref(prefName);
        }
        this.browser.reload();
        break;
      case "Browser:SSLErrorGoBack":
        this.goBackFromErrorPage(this.browser);
        break;
      case "GetChangedCertPrefs":
        let hasChangedCertPrefs = this.hasChangedCertPrefs();
        this.sendAsyncMessage("HasChangedCertPrefs", {
          hasChangedCertPrefs,
        });
        break;
      case "ReportBlockingError":
        this.ReportBlockingError(
          this.browsingContext.id,
          message.data.scheme,
          message.data.host,
          message.data.port,
          message.data.path,
          message.data.xfoAndCspInfo
        );
        break;
      case "DisplayOfflineSupportPage":
        this.displayOfflineSupportPage(message.data.supportPageSlug);
        break;
      case "Browser:CertExceptionError":
        switch (message.data.elementId) {
          case "viewCertificate": {
            let certs = message.data.failedCertChain.map(certBase64 =>
              encodeURIComponent(certBase64)
            );
            let certsStringURL = certs.map(elem => `cert=${elem}`);
            certsStringURL = certsStringURL.join("&");
            let url = `about:certificate?${certsStringURL}`;

            let window = this.browser.ownerGlobal;
            if (AppConstants.MOZ_BUILD_APP === "browser") {
              window.switchToTabHavingURI(url, true, {});
            } else {
              window.open(url, "_blank");
            }
            break;
          }
        }
        break;
      case "Browser:AddTRRExcludedDomain":
        let domain = message.data.hostname;
        let excludedDomains = Services.prefs.getStringPref(
          "network.trr.excluded-domains"
        );
        excludedDomains += `, ${domain}`;
        Services.prefs.setStringPref(
          "network.trr.excluded-domains",
          excludedDomains
        );
        break;
      case "OpenTRRPreferences":
        let browser = this.browsingContext.top.embedderElement;
        if (!browser) {
          break;
        }

        let win = browser.ownerGlobal;
        win.openPreferences("privacy-doh");
        break;
    }
  }
}
PK
!<
6����$actors/PictureInPictureChild.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ContentDOMReference: "resource://gre/modules/ContentDOMReference.sys.mjs",
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  KEYBOARD_CONTROLS: "resource://gre/modules/PictureInPictureControls.sys.mjs",
  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
  Rect: "resource://gre/modules/Geometry.sys.mjs",
  TOGGLE_POLICIES: "resource://gre/modules/PictureInPictureControls.sys.mjs",
  TOGGLE_POLICY_STRINGS:
    "resource://gre/modules/PictureInPictureControls.sys.mjs",
});

import { WebVTT } from "resource://gre/modules/vtt.sys.mjs";
import { setTimeout, clearTimeout } from "resource://gre/modules/Timer.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "DISPLAY_TEXT_TRACKS_PREF",
  "media.videocontrols.picture-in-picture.display-text-tracks.enabled",
  false
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "IMPROVED_CONTROLS_ENABLED_PREF",
  "media.videocontrols.picture-in-picture.improved-video-controls.enabled",
  false
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "MIN_VIDEO_LENGTH",
  "media.videocontrols.picture-in-picture.video-toggle.min-video-secs",
  45
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "PIP_TOGGLE_ALWAYS_SHOW",
  "media.videocontrols.picture-in-picture.video-toggle.always-show",
  false
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "PIP_URLBAR_BUTTON",
  "media.videocontrols.picture-in-picture.urlbar-button.enabled",
  false
);

const PIP_ENABLED_PREF = "media.videocontrols.picture-in-picture.enabled";
const TOGGLE_ENABLED_PREF =
  "media.videocontrols.picture-in-picture.video-toggle.enabled";
const TOGGLE_FIRST_SEEN_PREF =
  "media.videocontrols.picture-in-picture.video-toggle.first-seen-secs";
const TOGGLE_FIRST_TIME_DURATION_DAYS = 28;
const TOGGLE_HAS_USED_PREF =
  "media.videocontrols.picture-in-picture.video-toggle.has-used";
const TOGGLE_TESTING_PREF =
  "media.videocontrols.picture-in-picture.video-toggle.testing";
const TOGGLE_VISIBILITY_THRESHOLD_PREF =
  "media.videocontrols.picture-in-picture.video-toggle.visibility-threshold";
const TEXT_TRACK_FONT_SIZE =
  "media.videocontrols.picture-in-picture.display-text-tracks.size";

const MOUSEMOVE_PROCESSING_DELAY_MS = 50;
const TOGGLE_HIDING_TIMEOUT_MS = 3000;
// If you change this, also change VideoControlsWidget.SEEK_TIME_SECS:
const SEEK_TIME_SECS = 5;
const EMPTIED_TIMEOUT_MS = 1000;

// The ToggleChild does not want to capture events from the PiP
// windows themselves. This set contains all currently open PiP
// players' content windows
var gPlayerContents = new WeakSet();

// To make it easier to write tests, we have a process-global
// WeakSet of all <video> elements that are being tracked for
// mouseover
var gWeakIntersectingVideosForTesting = new WeakSet();

// Overrides are expected to stay constant for the lifetime of a
// content process, so we set this as a lazy process global.
// See PictureInPictureToggleChild.getSiteOverrides for a
// sense of what the return types are.
ChromeUtils.defineLazyGetter(lazy, "gSiteOverrides", () => {
  return PictureInPictureToggleChild.getSiteOverrides();
});

ChromeUtils.defineLazyGetter(lazy, "logConsole", () => {
  return console.createInstance({
    prefix: "PictureInPictureChild",
    maxLogLevel: Services.prefs.getBoolPref(
      "media.videocontrols.picture-in-picture.log",
      false
    )
      ? "Debug"
      : "Error",
  });
});

/**
 * Creates and returns an instance of the PictureInPictureChildVideoWrapper class responsible
 * for applying site-specific wrapper methods around the original video.
 *
 * The Picture-In-Picture add-on can use this to provide site-specific wrappers for
 * sites that require special massaging to control.
 * @param {Object} pipChild reference to PictureInPictureChild class calling this function
 * @param {Element} originatingVideo
 *   The <video> element to wrap.
 * @returns {PictureInPictureChildVideoWrapper} instance of PictureInPictureChildVideoWrapper
 */
function applyWrapper(pipChild, originatingVideo) {
  let originatingDoc = originatingVideo.ownerDocument;
  let originatingDocumentURI = originatingDoc.documentURI;

  let overrides = lazy.gSiteOverrides.find(([matcher]) => {
    return matcher.matches(originatingDocumentURI);
  });

  // gSiteOverrides is a list of tuples where the first element is the MatchPattern
  // for a supported site and the second is the actual overrides object for it.
  let wrapperPath = overrides ? overrides[1].videoWrapperScriptPath : null;
  return new PictureInPictureChildVideoWrapper(
    wrapperPath,
    originatingVideo,
    pipChild
  );
}

export class PictureInPictureLauncherChild extends JSWindowActorChild {
  handleEvent(event) {
    switch (event.type) {
      case "MozTogglePictureInPicture": {
        if (event.isTrusted) {
          this.togglePictureInPicture({
            video: event.target,
            reason: event.detail?.reason,
            eventExtraKeys: event.detail?.eventExtraKeys,
          });
        }
        break;
      }
    }
  }

  receiveMessage(message) {
    switch (message.name) {
      case "PictureInPicture:KeyToggle": {
        this.keyToggle();
        break;
      }
      case "PictureInPicture:AutoToggle": {
        this.autoToggle();
        break;
      }
    }
  }

  /**
   * Tells the parent to open a Picture-in-Picture window hosting
   * a clone of the passed video. If we know about a pre-existing
   * Picture-in-Picture window existing, this tells the parent to
   * close it before opening the new one.
   *
   * @param {Object} pipObject
   * @param {HTMLVideoElement} pipObject.video
   * @param {String} pipObject.reason What toggled PiP, e.g. "shortcut"
   * @param {Object} pipObject.eventExtraKeys Extra telemetry keys to record
   * @param {boolean} autoFocus Autofocus the PiP window (default: true)
   *
   * @return {Promise}
   * @resolves {undefined} Once the new Picture-in-Picture window
   * has been requested.
   */
  async togglePictureInPicture(pipObject, autoFocus = true) {
    let { video, reason, eventExtraKeys = {} } = pipObject;
    if (video.isCloningElementVisually) {
      // The only way we could have entered here for the same video is if
      // we are toggling via the context menu or via the urlbar button,
      // since we hide the inline Picture-in-Picture toggle when a video
      // is being displayed in Picture-in-Picture. Turn off PiP in this case
      const stopPipEvent = new this.contentWindow.CustomEvent(
        "MozStopPictureInPicture",
        {
          bubbles: true,
          detail: { reason },
        }
      );
      video.dispatchEvent(stopPipEvent);
      return;
    }

    if (!PictureInPictureChild.videoWrapper) {
      PictureInPictureChild.videoWrapper = applyWrapper(
        PictureInPictureChild,
        video
      );
    }

    let timestamp = undefined;
    let scrubberPosition = undefined;

    if (lazy.IMPROVED_CONTROLS_ENABLED_PREF) {
      timestamp = PictureInPictureChild.videoWrapper.formatTimestamp(
        PictureInPictureChild.videoWrapper.getCurrentTime(video),
        PictureInPictureChild.videoWrapper.getDuration(video)
      );

      // Scrubber is hidden if undefined, so only set it to something else
      // if the timestamp is not undefined.
      scrubberPosition =
        timestamp === undefined
          ? undefined
          : PictureInPictureChild.videoWrapper.getCurrentTime(video) /
            PictureInPictureChild.videoWrapper.getDuration(video);
    }

    // All other requests to toggle PiP should open a new PiP
    // window
    const videoRef = lazy.ContentDOMReference.get(video);
    this.sendAsyncMessage("PictureInPicture:Request", {
      isMuted: PictureInPictureChild.videoIsMuted(video),
      playing: PictureInPictureChild.videoIsPlaying(video),
      videoHeight: video.videoHeight,
      videoWidth: video.videoWidth,
      videoRef,
      ccEnabled: lazy.DISPLAY_TEXT_TRACKS_PREF,
      webVTTSubtitles: !!video.textTracks?.length,
      scrubberPosition,
      timestamp,
      volume: PictureInPictureChild.videoWrapper.getVolume(video),
      autoFocus,
    });

    Services.telemetry.recordEvent(
      "pictureinpicture",
      "opened_method",
      reason,
      null,
      {
        firstTimeToggle: (!Services.prefs.getBoolPref(
          TOGGLE_HAS_USED_PREF
        )).toString(),
        ...eventExtraKeys,
      }
    );
  }

  /**
   * The keyboard was used to attempt to open Picture-in-Picture.
   * Note that we assume that this method will only be called for the focused
   * document.
   */
  keyToggle() {
    let doc = this.document;
    if (doc) {
      let video = this.findVideoToPiP(doc);
      if (video) {
        this.togglePictureInPicture({ video, reason: "shortcut" });
      }
    }
  }

  /**
   * If a video is focused, select that video. Otherwise find the first playing
   * video, or if none, the largest dimension video. We suspect this heuristic
   * will handle most cases, though we might refine this later on.
   *
   * @param {HTMLDocument} doc The HTML document to search for a video element in.
   * @returns {HTMLVideoElement} The selected HTML video element to enter PiP mode.
   */
  findVideoToPiP(doc) {
    let video = doc.activeElement;
    if (!HTMLVideoElement.isInstance(video)) {
      let listOfVideos = [...doc.querySelectorAll("video")].filter(
        video => !isNaN(video.duration)
      );
      // Get the first non-paused video, otherwise the longest video. This
      // fallback is designed to skip over "preview"-style videos on sidebars.
      video =
        listOfVideos.filter(v => !v.paused)[0] ||
        listOfVideos.sort((a, b) => b.duration - a.duration)[0];
    }
    return video;
  }

  /**
   * Automatically toggle Picture-in-Picture if a video tab has been
   * backgrounded.
   */
  autoToggle() {
    let doc = this.document;
    if (doc) {
      let video = this.findVideoToPiP(doc);
      if (video && PictureInPictureChild.videoIsPlaying(video)) {
        this.togglePictureInPicture({ video, reason: "autoPip" }, false);
      }
    }
  }
}

/**
 * The PictureInPictureToggleChild is responsible for displaying the overlaid
 * Picture-in-Picture toggle over top of <video> elements that the mouse is
 * hovering.
 */
export class PictureInPictureToggleChild extends JSWindowActorChild {
  constructor() {
    super();
    // We need to maintain some state about various things related to the
    // Picture-in-Picture toggles - however, for now, the same
    // PictureInPictureToggleChild might be re-used for different documents.
    // We keep the state stashed inside of this WeakMap, keyed on the document
    // itself.
    this.weakDocStates = new WeakMap();
    this.toggleEnabled =
      Services.prefs.getBoolPref(TOGGLE_ENABLED_PREF) &&
      Services.prefs.getBoolPref(PIP_ENABLED_PREF);
    this.toggleTesting = Services.prefs.getBoolPref(TOGGLE_TESTING_PREF, false);

    // Bug 1570744 - JSWindowActorChild's cannot be used as nsIObserver's
    // directly, so we create a new function here instead to act as our
    // nsIObserver, which forwards the notification to the observe method.
    this.observerFunction = (subject, topic, data) => {
      this.observe(subject, topic, data);
    };
    Services.prefs.addObserver(TOGGLE_ENABLED_PREF, this.observerFunction);
    Services.prefs.addObserver(PIP_ENABLED_PREF, this.observerFunction);
    Services.prefs.addObserver(TOGGLE_FIRST_SEEN_PREF, this.observerFunction);
    Services.cpmm.sharedData.addEventListener("change", this);

    this.eligiblePipVideos = new WeakSet();
    this.trackingVideos = new WeakSet();
  }

  receiveMessage(message) {
    switch (message.name) {
      case "PictureInPicture:UrlbarToggle": {
        this.urlbarToggle(message.data);
        break;
      }
    }
    return null;
  }

  didDestroy() {
    this.stopTrackingMouseOverVideos();
    Services.prefs.removeObserver(TOGGLE_ENABLED_PREF, this.observerFunction);
    Services.prefs.removeObserver(PIP_ENABLED_PREF, this.observerFunction);
    Services.prefs.removeObserver(
      TOGGLE_FIRST_SEEN_PREF,
      this.observerFunction
    );
    Services.cpmm.sharedData.removeEventListener("change", this);

    // remove the observer on the <video> element
    let state = this.docState;
    if (state?.intersectionObserver) {
      state.intersectionObserver.disconnect();
    }

    // ensure the sandbox created by the video is destroyed
    this.videoWrapper?.destroy();
    this.videoWrapper = null;

    for (let video of ChromeUtils.nondeterministicGetWeakSetKeys(
      this.eligiblePipVideos
    )) {
      video.removeEventListener("emptied", this);
      video.removeEventListener("loadedmetadata", this);
      video.removeEventListener("durationchange", this);
    }

    for (let video of ChromeUtils.nondeterministicGetWeakSetKeys(
      this.trackingVideos
    )) {
      video.removeEventListener("emptied", this);
      video.removeEventListener("loadedmetadata", this);
      video.removeEventListener("durationchange", this);
    }

    // ensure we don't access the state
    this.isDestroyed = true;
  }

  observe(subject, topic, data) {
    if (topic != "nsPref:changed") {
      return;
    }

    this.toggleEnabled =
      Services.prefs.getBoolPref(TOGGLE_ENABLED_PREF) &&
      Services.prefs.getBoolPref(PIP_ENABLED_PREF);

    if (this.toggleEnabled) {
      // We have enabled the Picture-in-Picture toggle, so we need to make
      // sure we register all of the videos that might already be on the page.
      this.contentWindow.requestIdleCallback(() => {
        let videos = this.document.querySelectorAll("video");
        for (let video of videos) {
          this.registerVideo(video);
        }
      });
    }

    switch (data) {
      case TOGGLE_FIRST_SEEN_PREF:
        const firstSeenSeconds = Services.prefs.getIntPref(
          TOGGLE_FIRST_SEEN_PREF
        );
        if (!firstSeenSeconds || firstSeenSeconds < 0) {
          return;
        }
        this.changeToIconIfDurationEnd(firstSeenSeconds);
        break;
    }
  }

  /**
   * Returns the state for the current document referred to via
   * this.document. If no such state exists, creates it, stores it
   * and returns it.
   */
  get docState() {
    if (this.isDestroyed || !this.document) {
      return false;
    }

    let state = this.weakDocStates.get(this.document);

    let visibilityThresholdPref = Services.prefs.getFloatPref(
      TOGGLE_VISIBILITY_THRESHOLD_PREF,
      "1.0"
    );

    if (!state) {
      state = {
        // A reference to the IntersectionObserver that's monitoring for videos
        // to become visible.
        intersectionObserver: null,
        // A WeakSet of videos that are supposedly visible, according to the
        // IntersectionObserver.
        weakVisibleVideos: new WeakSet(),
        // The number of videos that are supposedly visible, according to the
        // IntersectionObserver
        visibleVideosCount: 0,
        // The DeferredTask that we'll arm every time a mousemove event occurs
        // on a page where we have one or more visible videos.
        mousemoveDeferredTask: null,
        // A weak reference to the last video we displayed the toggle over.
        weakOverVideo: null,
        // True if the user is in the midst of clicking the toggle.
        isClickingToggle: false,
        // Set to the original target element on pointerdown if the user is clicking
        // the toggle - this way, we can determine if a "click" event will need to be
        // suppressed ("click" events don't fire if a "mouseup" occurs on a different
        // element from the "pointerdown" / "mousedown" event).
        clickedElement: null,
        // This is a DeferredTask to hide the toggle after a period of mouse
        // inactivity.
        hideToggleDeferredTask: null,
        // If we reach a point where we're tracking videos for mouse movements,
        // then this will be true. If there are no videos worth tracking, then
        // this is false.
        isTrackingVideos: false,
        togglePolicy: lazy.TOGGLE_POLICIES.DEFAULT,
        toggleVisibilityThreshold: visibilityThresholdPref,
        // The documentURI that has been checked with toggle policies and
        // visibility thresholds for this document. Note that the documentURI
        // might change for a document via the history API, so we remember
        // the last checked documentURI to determine if we need to check again.
        checkedPolicyDocumentURI: null,
        isUnloaded: false,
      };
      this.weakDocStates.set(this.document, state);
    }

    return state;
  }

  /**
   * Returns the video that the user was last hovering with the mouse if it
   * still exists.
   *
   * @return {Element} the <video> element that the user was last hovering,
   * or null if there was no such <video>, or the <video> no longer exists.
   */
  getWeakOverVideo() {
    let { weakOverVideo } = this.docState;
    if (weakOverVideo) {
      // Bug 800957 - Accessing weakrefs at the wrong time can cause us to
      // throw NS_ERROR_XPC_BAD_CONVERT_NATIVE
      try {
        return weakOverVideo.get();
      } catch (e) {
        return null;
      }
    }
    return null;
  }

  handleEvent(event) {
    if (!event.isTrusted) {
      // We don't care about synthesized events that might be coming from
      // content JS.
      return;
    }

    // Don't capture events from Picture-in-Picture content windows
    if (gPlayerContents.has(this.contentWindow)) {
      return;
    }

    switch (event.type) {
      case "touchstart": {
        // Even if this is a touch event, there may be subsequent click events.
        // Suppress those events after selecting the toggle to prevent playback changes
        // when opening the Picture-in-Picture window.
        if (this.docState.isClickingToggle) {
          event.stopImmediatePropagation();
          event.preventDefault();
        }
        break;
      }
      case "change": {
        const { changedKeys } = event;
        if (changedKeys.includes("PictureInPicture:SiteOverrides")) {
          // For now we only update our cache if the site overrides change.
          // the user will need to refresh the page for changes to apply.
          try {
            lazy.gSiteOverrides =
              PictureInPictureToggleChild.getSiteOverrides();
          } catch (e) {
            // Ignore resulting TypeError if gSiteOverrides is still unloaded
            if (!(e instanceof TypeError)) {
              throw e;
            }
          }
        }
        break;
      }
      case "UAWidgetSetupOrChange": {
        if (
          this.toggleEnabled &&
          this.contentWindow.HTMLVideoElement.isInstance(event.target) &&
          event.target.ownerDocument == this.document
        ) {
          this.registerVideo(event.target);
        }
        break;
      }
      case "contextmenu": {
        if (this.toggleEnabled) {
          this.checkContextMenu(event);
        }
        break;
      }
      case "mouseout": {
        this.onMouseOut(event);
        break;
      }
      case "click":
        if (event.detail == 0) {
          let shadowRoot = event.originalTarget.containingShadowRoot;
          let toggle = this.getToggleElement(shadowRoot);
          if (event.originalTarget == toggle) {
            this.startPictureInPicture(event, shadowRoot.host, toggle);
            return;
          }
        }
      // fall through
      case "mousedown":
      case "pointerup":
      case "mouseup": {
        this.onMouseButtonEvent(event);
        break;
      }
      case "pointerdown": {
        this.onPointerDown(event);
        break;
      }
      case "mousemove": {
        this.onMouseMove(event);
        break;
      }
      case "pageshow": {
        this.onPageShow(event);
        break;
      }
      case "pagehide": {
        this.onPageHide(event);
        break;
      }
      case "visibilitychange": {
        this.onVisibilityChange(event);
        break;
      }
      case "durationchange":
      // Intentional fall-through
      case "emptied":
      // Intentional fall-through
      case "loadedmetadata": {
        this.updatePipVideoEligibility(event.target);
        break;
      }
    }
  }

  /**
   * Adds a <video> to the IntersectionObserver so that we know when it becomes
   * visible.
   *
   * @param {Element} video The <video> element to register.
   */
  registerVideo(video) {
    let state = this.docState;
    if (!state.intersectionObserver) {
      let fn = this.onIntersection.bind(this);
      state.intersectionObserver = new this.contentWindow.IntersectionObserver(
        fn,
        {
          threshold: [0.0, 0.5],
        }
      );
    }

    state.intersectionObserver.observe(video);

    if (!lazy.PIP_URLBAR_BUTTON) {
      return;
    }

    video.addEventListener("emptied", this);
    video.addEventListener("loadedmetadata", this);
    video.addEventListener("durationchange", this);

    this.trackingVideos.add(video);

    this.updatePipVideoEligibility(video);
  }

  updatePipVideoEligibility(video) {
    let isEligible = this.isVideoPiPEligible(video);
    if (isEligible) {
      if (!this.eligiblePipVideos.has(video)) {
        this.eligiblePipVideos.add(video);

        let mutationObserver = new this.contentWindow.MutationObserver(
          mutationList => {
            this.handleEligiblePipVideoMutation(mutationList);
          }
        );
        mutationObserver.observe(video.parentElement, { childList: true });
      }
    } else if (this.eligiblePipVideos.has(video)) {
      this.eligiblePipVideos.delete(video);
    }

    let videos = ChromeUtils.nondeterministicGetWeakSetKeys(
      this.eligiblePipVideos
    );

    this.sendAsyncMessage("PictureInPicture:UpdateEligiblePipVideoCount", {
      pipCount: videos.length,
      pipDisabledCount: videos.reduce(
        (accumulator, currentVal) =>
          accumulator + (currentVal.disablePictureInPicture ? 1 : 0),
        0
      ),
    });
  }

  handleEligiblePipVideoMutation(mutationList) {
    for (let mutationRecord of mutationList) {
      let video = mutationRecord.removedNodes[0];
      this.eligiblePipVideos.delete(video);
    }

    let videos = ChromeUtils.nondeterministicGetWeakSetKeys(
      this.eligiblePipVideos
    );

    this.sendAsyncMessage("PictureInPicture:UpdateEligiblePipVideoCount", {
      pipCount: videos.length,
      pipDisabledCount: videos.reduce(
        (accumulator, currentVal) =>
          accumulator + (currentVal.disablePictureInPicture ? 1 : 0),
        0
      ),
    });
  }

  urlbarToggle(eventExtraKeys) {
    let video = ChromeUtils.nondeterministicGetWeakSetKeys(
      this.eligiblePipVideos
    )[0];
    if (video) {
      let pipEvent = new this.contentWindow.CustomEvent(
        "MozTogglePictureInPicture",
        {
          bubbles: true,
          detail: { reason: "urlBar", eventExtraKeys },
        }
      );
      video.dispatchEvent(pipEvent);
    }
  }

  isVideoPiPEligible(video) {
    if (lazy.PIP_TOGGLE_ALWAYS_SHOW) {
      return true;
    }

    if (isNaN(video.duration) || video.duration < lazy.MIN_VIDEO_LENGTH) {
      return false;
    }

    const MIN_VIDEO_DIMENSION = 140; // pixels
    if (
      video.clientWidth < MIN_VIDEO_DIMENSION ||
      video.clientHeight < MIN_VIDEO_DIMENSION
    ) {
      return false;
    }

    return true;
  }

  /**
   * Changes from the first-time toggle to the icon toggle if the Nimbus variable `displayDuration`'s
   * end date is reached when hovering over a video. The end date is calculated according to the timestamp
   * indicating when the PiP toggle was first seen.
   * @param {Number} firstSeenStartSeconds the timestamp in seconds indicating when the PiP toggle was first seen
   */
  changeToIconIfDurationEnd(firstSeenStartSeconds) {
    const { displayDuration } =
      lazy.NimbusFeatures.pictureinpicture.getAllVariables({
        defaultValues: {
          displayDuration: TOGGLE_FIRST_TIME_DURATION_DAYS,
        },
      });
    if (!displayDuration || displayDuration < 0) {
      return;
    }

    let daysInSeconds = displayDuration * 24 * 60 * 60;
    let firstSeenEndSeconds = daysInSeconds + firstSeenStartSeconds;
    let currentDateSeconds = Math.round(Date.now() / 1000);

    lazy.logConsole.debug(
      "Toggle duration experiment - first time toggle seen on:",
      new Date(firstSeenStartSeconds * 1000).toLocaleDateString()
    );
    lazy.logConsole.debug(
      "Toggle duration experiment - first time toggle will change on:",
      new Date(firstSeenEndSeconds * 1000).toLocaleDateString()
    );
    lazy.logConsole.debug(
      "Toggle duration experiment - current date:",
      new Date(currentDateSeconds * 1000).toLocaleDateString()
    );

    if (currentDateSeconds >= firstSeenEndSeconds) {
      this.sendAsyncMessage("PictureInPicture:SetHasUsed", {
        hasUsed: true,
      });
    }
  }

  /**
   * Called by the IntersectionObserver callback once a video becomes visible.
   * This adds some fine-grained checking to ensure that a sufficient amount of
   * the video is visible before we consider showing the toggles on it. For now,
   * that means that the entirety of the video must be in the viewport.
   *
   * @param {IntersectionEntry} intersectionEntry An IntersectionEntry passed to
   * the IntersectionObserver callback.
   * @return bool Whether or not we should start tracking mousemove events for
   * this registered video.
   */
  worthTracking(intersectionEntry) {
    return intersectionEntry.isIntersecting;
  }

  /**
   * Called by the IntersectionObserver once a video crosses one of the
   * thresholds dictated by the IntersectionObserver configuration.
   *
   * @param {Array<IntersectionEntry>} A collection of one or more
   * IntersectionEntry's for <video> elements that might have entered or exited
   * the viewport.
   */
  onIntersection(entries) {
    // The IntersectionObserver will also fire when a previously intersecting
    // element is removed from the DOM. We know, however, that the node is
    // still alive and referrable from the WeakSet because the
    // IntersectionObserverEntry holds a strong reference to the video.
    let state = this.docState;
    if (!state) {
      return;
    }
    let oldVisibleVideosCount = state.visibleVideosCount;
    for (let entry of entries) {
      let video = entry.target;
      if (this.worthTracking(entry)) {
        if (!state.weakVisibleVideos.has(video)) {
          state.weakVisibleVideos.add(video);
          state.visibleVideosCount++;
          if (this.toggleTesting) {
            gWeakIntersectingVideosForTesting.add(video);
          }
        }
      } else if (state.weakVisibleVideos.has(video)) {
        state.weakVisibleVideos.delete(video);
        state.visibleVideosCount--;
        if (this.toggleTesting) {
          gWeakIntersectingVideosForTesting.delete(video);
        }
      }
    }

    // For testing, especially in debug or asan builds, we might not
    // run this idle callback within an acceptable time. While we're
    // testing, we'll bypass the idle callback performance optimization
    // and run our callbacks as soon as possible during the next idle
    // period.
    if (!oldVisibleVideosCount && state.visibleVideosCount) {
      if (this.toggleTesting || !this.contentWindow) {
        this.beginTrackingMouseOverVideos();
      } else {
        this.contentWindow.requestIdleCallback(() => {
          this.beginTrackingMouseOverVideos();
        });
      }
    } else if (oldVisibleVideosCount && !state.visibleVideosCount) {
      if (this.toggleTesting || !this.contentWindow) {
        this.stopTrackingMouseOverVideos();
      } else {
        this.contentWindow.requestIdleCallback(() => {
          this.stopTrackingMouseOverVideos();
        });
      }
    }
  }

  addMouseButtonListeners() {
    // We want to try to cancel the mouse events from continuing
    // on into content if the user has clicked on the toggle, so
    // we don't use the mozSystemGroup here, and add the listener
    // to the parent target of the window, which in this case,
    // is the windowRoot. Since this event listener is attached to
    // part of the outer window, we need to also remove it in a
    // pagehide event listener in the event that the page unloads
    // before stopTrackingMouseOverVideos fires.
    this.contentWindow.windowRoot.addEventListener("pointerdown", this, {
      capture: true,
    });
    this.contentWindow.windowRoot.addEventListener("mousedown", this, {
      capture: true,
    });
    this.contentWindow.windowRoot.addEventListener("mouseup", this, {
      capture: true,
    });
    this.contentWindow.windowRoot.addEventListener("pointerup", this, {
      capture: true,
    });
    this.contentWindow.windowRoot.addEventListener("click", this, {
      capture: true,
    });
    this.contentWindow.windowRoot.addEventListener("mouseout", this, {
      capture: true,
    });
    this.contentWindow.windowRoot.addEventListener("touchstart", this, {
      capture: true,
    });
  }

  removeMouseButtonListeners() {
    // This can be null when closing the tab, but the event
    // listeners should be removed in that case already.
    if (!this.contentWindow || !this.contentWindow.windowRoot) {
      return;
    }

    this.contentWindow.windowRoot.removeEventListener("pointerdown", this, {
      capture: true,
    });
    this.contentWindow.windowRoot.removeEventListener("mousedown", this, {
      capture: true,
    });
    this.contentWindow.windowRoot.removeEventListener("mouseup", this, {
      capture: true,
    });
    this.contentWindow.windowRoot.removeEventListener("pointerup", this, {
      capture: true,
    });
    this.contentWindow.windowRoot.removeEventListener("click", this, {
      capture: true,
    });
    this.contentWindow.windowRoot.removeEventListener("mouseout", this, {
      capture: true,
    });
    this.contentWindow.windowRoot.removeEventListener("touchstart", this, {
      capture: true,
    });
  }

  /**
   * One of the challenges of displaying this toggle is that many sites put
   * things over top of <video> elements, like custom controls, or images, or
   * all manner of things that might intercept mouseevents that would normally
   * fire directly on the <video>. In order to properly detect when the mouse
   * is over top of one of the <video> elements in this situation, we currently
   * add a mousemove event handler to the entire document, and stash the most
   * recent mousemove that fires. At periodic intervals, that stashed mousemove
   * event is checked to see if it's hovering over one of our registered
   * <video> elements.
   *
   * This sort of thing will not be necessary once bug 1539652 is fixed.
   */
  beginTrackingMouseOverVideos() {
    let state = this.docState;
    if (!state.mousemoveDeferredTask) {
      state.mousemoveDeferredTask = new lazy.DeferredTask(() => {
        this.checkLastMouseMove();
      }, MOUSEMOVE_PROCESSING_DELAY_MS);
    }
    this.document.addEventListener("mousemove", this, {
      mozSystemGroup: true,
      capture: true,
    });
    this.contentWindow.addEventListener("pageshow", this, {
      mozSystemGroup: true,
    });
    this.contentWindow.addEventListener("pagehide", this, {
      mozSystemGroup: true,
    });
    lazy.logConsole.debug("Adding visibilitychange event handler");
    this.contentWindow.addEventListener("visibilitychange", this, {
      mozSystemGroup: true,
    });
    this.addMouseButtonListeners();
    state.isTrackingVideos = true;
  }

  /**
   * If we no longer have any interesting videos in the viewport, we deregister
   * the mousemove and click listeners, and also remove any toggles that might
   * be on the page still.
   */
  stopTrackingMouseOverVideos() {
    let state = this.docState;
    // We initialize `mousemoveDeferredTask` in `beginTrackingMouseOverVideos`.
    // If it doesn't exist, that can't have happened. Nothing else ever sets
    // this value (though we arm/disarm in various places). So we don't need
    // to do anything else here and can return early.
    if (!state.mousemoveDeferredTask) {
      return;
    }
    state.mousemoveDeferredTask.disarm();
    this.document.removeEventListener("mousemove", this, {
      mozSystemGroup: true,
      capture: true,
    });
    if (this.contentWindow) {
      this.contentWindow.removeEventListener("pageshow", this, {
        mozSystemGroup: true,
      });
      this.contentWindow.removeEventListener("pagehide", this, {
        mozSystemGroup: true,
      });
      lazy.logConsole.debug("Removing visibilitychange event handler");
      this.contentWindow.removeEventListener("visibilitychange", this, {
        mozSystemGroup: true,
      });
    }
    this.removeMouseButtonListeners();
    let oldOverVideo = this.getWeakOverVideo();
    if (oldOverVideo) {
      this.onMouseLeaveVideo(oldOverVideo);
    }
    state.isTrackingVideos = false;
  }

  /**
   * This pageshow event handler will get called if and when we complete a tab
   * tear out or in. If we happened to be tracking videos before the tear
   * occurred, we re-add the mouse event listeners so that they're attached to
   * the right WindowRoot.
   */
  onPageShow() {
    let state = this.docState;
    state.isUnloaded = false;
    if (state.isTrackingVideos) {
      this.addMouseButtonListeners();
    }
  }

  /**
   * This pagehide event handler will get called if and when we start a tab
   * tear out or in. If we happened to be tracking videos before the tear
   * occurred, we remove the mouse event listeners. We'll re-add them when the
   * pageshow event fires.
   */
  onPageHide() {
    let state = this.docState;
    state.isUnloaded = true;
    if (state.isTrackingVideos) {
      this.removeMouseButtonListeners();
    }
  }

  onVisibilityChange() {
    // Ignore if the document was unloaded or unloading
    let state = this.docState;
    if (state.isUnloaded) {
      return;
    }

    if (this.document.visibilityState == "hidden") {
      this.sendAsyncMessage("PictureInPicture:VideoTabHidden");
    } else if (this.document.visibilityState == "visible") {
      this.sendAsyncMessage("PictureInPicture:VideoTabShown");
    }
  }

  /**
   * If we're tracking <video> elements, this pointerdown event handler is run anytime
   * a pointerdown occurs on the document. This function is responsible for checking
   * if the user clicked on the Picture-in-Picture toggle. It does this by first
   * checking if the video is visible beneath the point that was clicked. Then
   * it tests whether or not the pointerdown occurred within the rectangle of the
   * toggle. If so, the event's propagation is stopped, and Picture-in-Picture is
   * triggered.
   *
   * @param {Event} event The mousemove event.
   */
  onPointerDown(event) {
    // The toggle ignores non-primary mouse clicks.
    if (event.button != 0) {
      return;
    }

    let video = this.getWeakOverVideo();
    if (!video) {
      return;
    }

    let shadowRoot = video.openOrClosedShadowRoot;
    if (!shadowRoot) {
      return;
    }

    let state = this.docState;

    let overVideo = (() => {
      let { clientX, clientY } = event;
      let winUtils = this.contentWindow.windowUtils;
      // We use winUtils.nodesFromRect instead of document.elementsFromPoint,
      // since document.elementsFromPoint always flushes layout. The 1's in that
      // function call are for the size of the rect that we want, which is 1x1.
      //
      // We pass the aOnlyVisible boolean argument to check that the video isn't
      // occluded by anything visible at the point of mousedown. If it is, we'll
      // ignore the mousedown.
      let elements = winUtils.nodesFromRect(
        clientX,
        clientY,
        1,
        1,
        1,
        1,
        true,
        false,
        /* aOnlyVisible = */ true,
        state.toggleVisibilityThreshold
      );

      for (let element of elements) {
        if (element == video || element.containingShadowRoot == shadowRoot) {
          return true;
        }
      }

      return false;
    })();

    if (!overVideo) {
      return;
    }

    let toggle = this.getToggleElement(shadowRoot);
    if (this.isMouseOverToggle(toggle, event)) {
      state.isClickingToggle = true;
      state.clickedElement = Cu.getWeakReference(event.originalTarget);
      event.stopImmediatePropagation();

      this.startPictureInPicture(event, video, toggle);
    }
  }

  startPictureInPicture(event, video) {
    Services.telemetry.keyedScalarAdd(
      "pictureinpicture.opened_method",
      "toggle",
      1
    );

    let pipEvent = new this.contentWindow.CustomEvent(
      "MozTogglePictureInPicture",
      {
        bubbles: true,
        detail: { reason: "toggle" },
      }
    );
    video.dispatchEvent(pipEvent);

    // Since we've initiated Picture-in-Picture, we can go ahead and
    // hide the toggle now.
    this.onMouseLeaveVideo(video);
  }

  /**
   * Called for mousedown, pointerup, mouseup and click events. If we
   * detected that the user is clicking on the Picture-in-Picture toggle,
   * these events are cancelled in the capture-phase before they reach
   * content. The state for suppressing these events is cleared on the
   * click event (unless the mouseup occurs on a different element from
   * the mousedown, in which case, the state is cleared on mouseup).
   *
   * @param {Event} event A mousedown, pointerup, mouseup or click event.
   */
  onMouseButtonEvent(event) {
    // The toggle ignores non-primary mouse clicks.
    if (event.button != 0) {
      return;
    }

    let state = this.docState;
    if (state.isClickingToggle) {
      event.stopImmediatePropagation();

      // If this is a mouseup event, check to see if we have a record of what
      // the original target was on pointerdown. If so, and if it doesn't match
      // the mouseup original target, that means we won't get a click event, and
      // we can clear the "clicking the toggle" state right away.
      //
      // Otherwise, we wait for the click event to do that.
      let isMouseUpOnOtherElement =
        event.type == "mouseup" &&
        (!state.clickedElement ||
          state.clickedElement.get() != event.originalTarget);

      if (
        isMouseUpOnOtherElement ||
        event.type == "click" ||
        // pointerup event still triggers after a touchstart event. We just need to detect
        // the pointer type and determine if we got to this part of the code through a touch event.
        event.pointerType == "touch"
      ) {
        // The click is complete, so now we reset the state so that
        // we stop suppressing these events.
        state.isClickingToggle = false;
        state.clickedElement = null;
      }
    }
  }

  /**
   * Called on mouseout events to determine whether or not the mouse has
   * exited the window.
   *
   * @param {Event} event The mouseout event.
   */
  onMouseOut(event) {
    if (!event.relatedTarget) {
      // For mouseout events, if there's no relatedTarget (which normally
      // maps to the element that the mouse entered into) then this means that
      // we left the window.
      let video = this.getWeakOverVideo();
      if (!video) {
        return;
      }

      this.onMouseLeaveVideo(video);
    }
  }

  /**
   * Called for each mousemove event when we're tracking those events to
   * determine if the cursor is hovering over a <video>.
   *
   * @param {Event} event The mousemove event.
   */
  onMouseMove(event) {
    let state = this.docState;

    if (state.hideToggleDeferredTask) {
      state.hideToggleDeferredTask.disarm();
      state.hideToggleDeferredTask.arm();
    }

    state.lastMouseMoveEvent = event;
    state.mousemoveDeferredTask.arm();
  }

  /**
   * Called by the DeferredTask after MOUSEMOVE_PROCESSING_DELAY_MS
   * milliseconds. Checked to see if that mousemove happens to be overtop of
   * any interesting <video> elements that we want to display the toggle
   * on. If so, puts the toggle on that video.
   */
  checkLastMouseMove() {
    let state = this.docState;
    let event = state.lastMouseMoveEvent;
    let { clientX, clientY } = event;
    lazy.logConsole.debug("Visible videos count:", state.visibleVideosCount);
    lazy.logConsole.debug("Tracking videos:", state.isTrackingVideos);
    let winUtils = this.contentWindow.windowUtils;
    // We use winUtils.nodesFromRect instead of document.elementsFromPoint,
    // since document.elementsFromPoint always flushes layout. The 1's in that
    // function call are for the size of the rect that we want, which is 1x1.
    let elements = winUtils.nodesFromRect(
      clientX,
      clientY,
      1,
      1,
      1,
      1,
      true,
      false,
      /* aOnlyVisible = */ true
    );

    for (let element of elements) {
      lazy.logConsole.debug("Element id under cursor:", element.id);
      lazy.logConsole.debug(
        "Node name of an element under cursor:",
        element.nodeName
      );
      lazy.logConsole.debug(
        "Supported <video> element:",
        state.weakVisibleVideos.has(element)
      );
      lazy.logConsole.debug(
        "PiP window is open:",
        element.isCloningElementVisually
      );

      // Check for hovering over the video controls or so too, not only
      // directly over the video.
      for (let el = element; el; el = el.containingShadowRoot?.host) {
        if (state.weakVisibleVideos.has(el) && !el.isCloningElementVisually) {
          lazy.logConsole.debug("Found supported element");
          this.onMouseOverVideo(el, event);
          return;
        }
      }
    }

    let oldOverVideo = this.getWeakOverVideo();
    if (oldOverVideo) {
      this.onMouseLeaveVideo(oldOverVideo);
    }
  }

  /**
   * Called once it has been determined that the mouse is overtop of a video
   * that is in the viewport.
   *
   * @param {Element} video The video the mouse is over.
   */
  onMouseOverVideo(video, event) {
    let oldOverVideo = this.getWeakOverVideo();
    let shadowRoot = video.openOrClosedShadowRoot;

    if (shadowRoot.firstChild && video != oldOverVideo) {
      // TODO: Maybe this should move to videocontrols.js somehow.
      shadowRoot.firstChild.toggleAttribute(
        "flipped",
        video.getTransformToViewport().a == -1
      );
    }

    // It seems from automated testing that if it's still very early on in the
    // lifecycle of a <video> element, it might not yet have a shadowRoot,
    // in which case, we can bail out here early.
    if (!shadowRoot) {
      if (oldOverVideo) {
        // We also clear the hover state on the old video we were hovering,
        // if there was one.
        this.onMouseLeaveVideo(oldOverVideo);
      }

      return;
    }

    let state = this.docState;
    let toggle = this.getToggleElement(shadowRoot);
    let controlsOverlay = shadowRoot.querySelector(".controlsOverlay");

    if (state.checkedPolicyDocumentURI != this.document.documentURI) {
      state.togglePolicy = lazy.TOGGLE_POLICIES.DEFAULT;
      // We cache the matchers process-wide. We'll skip this while running tests to make that
      // easier.
      let siteOverrides = this.toggleTesting
        ? PictureInPictureToggleChild.getSiteOverrides()
        : lazy.gSiteOverrides;

      let visibilityThresholdPref = Services.prefs.getFloatPref(
        TOGGLE_VISIBILITY_THRESHOLD_PREF,
        "1.0"
      );

      if (!this.videoWrapper) {
        this.videoWrapper = applyWrapper(this, video);
      }

      // Do we have any toggle overrides? If so, try to apply them.
      for (let [override, { policy, visibilityThreshold }] of siteOverrides) {
        if (
          (policy || visibilityThreshold) &&
          override.matches(this.document.documentURI)
        ) {
          state.togglePolicy = this.videoWrapper?.shouldHideToggle(video)
            ? lazy.TOGGLE_POLICIES.HIDDEN
            : policy || lazy.TOGGLE_POLICIES.DEFAULT;
          state.toggleVisibilityThreshold =
            visibilityThreshold || visibilityThresholdPref;
          break;
        }
      }

      state.checkedPolicyDocumentURI = this.document.documentURI;
    }

    // The built-in <video> controls are along the bottom, which would overlap the
    // toggle if the override is set to BOTTOM, so we ignore overrides that set
    // a policy of BOTTOM for <video> elements with controls.
    if (
      state.togglePolicy != lazy.TOGGLE_POLICIES.DEFAULT &&
      !(state.togglePolicy == lazy.TOGGLE_POLICIES.BOTTOM && video.controls)
    ) {
      toggle.setAttribute(
        "policy",
        lazy.TOGGLE_POLICY_STRINGS[state.togglePolicy]
      );
    } else {
      toggle.removeAttribute("policy");
    }

    // nimbusExperimentVariables will be defaultValues when the experiment is disabled
    const nimbusExperimentVariables =
      lazy.NimbusFeatures.pictureinpicture.getAllVariables({
        defaultValues: {
          oldToggle: true,
          title: null,
          message: false,
          showIconOnly: false,
          displayDuration: TOGGLE_FIRST_TIME_DURATION_DAYS,
        },
      });

    /**
     * If a Nimbus variable exists for the first-time PiP toggle design,
     * override the old design via a classname "experiment".
     */
    if (!nimbusExperimentVariables.oldToggle) {
      let controlsContainer = shadowRoot.querySelector(".controlsContainer");
      let pipWrapper = shadowRoot.querySelector(".pip-wrapper");

      controlsContainer.classList.add("experiment");
      pipWrapper.classList.add("experiment");
    } else {
      let controlsContainer = shadowRoot.querySelector(".controlsContainer");
      let pipWrapper = shadowRoot.querySelector(".pip-wrapper");

      controlsContainer.classList.remove("experiment");
      pipWrapper.classList.remove("experiment");
    }

    if (nimbusExperimentVariables.title) {
      let pipExplainer = shadowRoot.querySelector(".pip-explainer");
      let pipLabel = shadowRoot.querySelector(".pip-label");

      if (pipExplainer && nimbusExperimentVariables.message) {
        pipExplainer.innerText = nimbusExperimentVariables.message;
      }
      pipLabel.innerText = nimbusExperimentVariables.title;
    } else if (nimbusExperimentVariables.showIconOnly) {
      // We only want to show the PiP icon in this experiment scenario
      let pipExpanded = shadowRoot.querySelector(".pip-expanded");
      pipExpanded.style.display = "none";
      let pipSmall = shadowRoot.querySelector(".pip-small");
      pipSmall.style.opacity = "1";

      let pipIcon = shadowRoot.querySelectorAll(".pip-icon")[1];
      pipIcon.style.display = "block";
    }

    controlsOverlay.removeAttribute("hidetoggle");

    // The hideToggleDeferredTask we create here is for automatically hiding
    // the toggle after a period of no mousemove activity for
    // TOGGLE_HIDING_TIMEOUT_MS. If the mouse moves, then the DeferredTask
    // timer is reset.
    //
    // We disable the toggle hiding timeout during testing to reduce
    // non-determinism from timers when testing the toggle.
    if (!state.hideToggleDeferredTask && !this.toggleTesting) {
      state.hideToggleDeferredTask = new lazy.DeferredTask(() => {
        controlsOverlay.setAttribute("hidetoggle", true);
      }, TOGGLE_HIDING_TIMEOUT_MS);
    }

    if (oldOverVideo) {
      if (oldOverVideo == video) {
        // If we're still hovering the old video, we might have entered or
        // exited the toggle region.
        this.checkHoverToggle(toggle, event);
        return;
      }

      // We had an old video that we were hovering, and we're not hovering
      // it anymore. Let's leave it.
      this.onMouseLeaveVideo(oldOverVideo);
    }

    state.weakOverVideo = Cu.getWeakReference(video);
    controlsOverlay.classList.add("hovering");

    if (
      state.togglePolicy != lazy.TOGGLE_POLICIES.HIDDEN &&
      !toggle.hasAttribute("hidden")
    ) {
      const hasUsedPiP = Services.prefs.getBoolPref(TOGGLE_HAS_USED_PREF);
      let args = {
        firstTime: (!hasUsedPiP).toString(),
      };
      Services.telemetry.recordEvent(
        "pictureinpicture",
        "saw_toggle",
        "toggle",
        null,
        args
      );
      // only record if this is the first time seeing the toggle
      if (!hasUsedPiP) {
        lazy.NimbusFeatures.pictureinpicture.recordExposureEvent();

        const firstSeenSeconds = Services.prefs.getIntPref(
          TOGGLE_FIRST_SEEN_PREF,
          0
        );

        if (!firstSeenSeconds || firstSeenSeconds < 0) {
          let firstTimePiPStartDate = Math.round(Date.now() / 1000);
          this.sendAsyncMessage("PictureInPicture:SetFirstSeen", {
            dateSeconds: firstTimePiPStartDate,
          });
        } else if (nimbusExperimentVariables.displayDuration) {
          this.changeToIconIfDurationEnd(firstSeenSeconds);
        }
      }
    }

    // Now that we're hovering the video, we'll check to see if we're
    // hovering the toggle too.
    this.checkHoverToggle(toggle, event);
  }

  /**
   * Checks if a mouse event is happening over a toggle element. If it is,
   * sets the hovering class on it. Otherwise, it clears the hovering
   * class.
   *
   * @param {Element} toggle The Picture-in-Picture toggle to check.
   * @param {MouseEvent} event A MouseEvent to test.
   */
  checkHoverToggle(toggle, event) {
    toggle.classList.toggle("hovering", this.isMouseOverToggle(toggle, event));
  }

  /**
   * Called once it has been determined that the mouse is no longer overlapping
   * a video that we'd previously called onMouseOverVideo with.
   *
   * @param {Element} video The video that the mouse left.
   */
  onMouseLeaveVideo(video) {
    let state = this.docState;
    let shadowRoot = video.openOrClosedShadowRoot;

    if (shadowRoot) {
      let controlsOverlay = shadowRoot.querySelector(".controlsOverlay");
      let toggle = this.getToggleElement(shadowRoot);
      controlsOverlay.classList.remove("hovering");
      toggle.classList.remove("hovering");
    }

    state.weakOverVideo = null;

    if (!this.toggleTesting) {
      state.hideToggleDeferredTask.disarm();
      state.mousemoveDeferredTask.disarm();
    }

    state.hideToggleDeferredTask = null;
  }

  /**
   * Given a reference to a Picture-in-Picture toggle element, determines
   * if a MouseEvent event is occurring within its bounds.
   *
   * @param {Element} toggle The Picture-in-Picture toggle.
   * @param {MouseEvent} event A MouseEvent to test.
   *
   * @return {Boolean}
   */
  isMouseOverToggle(toggle, event) {
    let toggleRect =
      toggle.ownerGlobal.windowUtils.getBoundsWithoutFlushing(toggle);

    // The way the toggle is currently implemented with
    // absolute positioning, the root toggle element bounds don't actually
    // contain all of the toggle child element bounds. Until we find a way to
    // sort that out, we workaround the issue by having each clickable child
    // elements of the toggle have a clicklable class, and then compute the
    // smallest rect that contains all of their bounding rects and use that
    // as the hitbox.
    toggleRect = lazy.Rect.fromRect(toggleRect);
    let clickableChildren = toggle.querySelectorAll(".clickable");
    for (let child of clickableChildren) {
      let childRect = lazy.Rect.fromRect(
        child.ownerGlobal.windowUtils.getBoundsWithoutFlushing(child)
      );
      toggleRect.expandToContain(childRect);
    }

    // If the toggle has no dimensions, we're definitely not over it.
    if (!toggleRect.width || !toggleRect.height) {
      return false;
    }

    let { clientX, clientY } = event;

    return (
      clientX >= toggleRect.left &&
      clientX <= toggleRect.right &&
      clientY >= toggleRect.top &&
      clientY <= toggleRect.bottom
    );
  }

  /**
   * Checks a contextmenu event to see if the mouse is currently over the
   * Picture-in-Picture toggle. If so, sends a message to the parent process
   * to open up the Picture-in-Picture toggle context menu.
   *
   * @param {MouseEvent} event A contextmenu event.
   */
  checkContextMenu(event) {
    let video = this.getWeakOverVideo();
    if (!video) {
      return;
    }

    let shadowRoot = video.openOrClosedShadowRoot;
    if (!shadowRoot) {
      return;
    }

    let toggle = this.getToggleElement(shadowRoot);
    if (this.isMouseOverToggle(toggle, event)) {
      let devicePixelRatio = toggle.ownerGlobal.devicePixelRatio;
      this.sendAsyncMessage("PictureInPicture:OpenToggleContextMenu", {
        screenXDevPx: event.screenX * devicePixelRatio,
        screenYDevPx: event.screenY * devicePixelRatio,
        inputSource: event.inputSource,
      });
      event.stopImmediatePropagation();
      event.preventDefault();
    }
  }

  /**
   * Returns the appropriate root element for the Picture-in-Picture toggle,
   * depending on whether or not we're using the experimental toggle preference.
   *
   * @param {Element} shadowRoot The shadowRoot of the video element.
   * @returns {Element} The toggle element.
   */
  getToggleElement(shadowRoot) {
    return shadowRoot.getElementById("pictureInPictureToggle");
  }

  /**
   * This is a test-only function that returns true if a video is being tracked
   * for mouseover events after having intersected the viewport.
   */
  static isTracking(video) {
    return gWeakIntersectingVideosForTesting.has(video);
  }

  /**
   * Gets any Picture-in-Picture site-specific overrides stored in the
   * sharedData struct, and returns them as an Array of two-element Arrays,
   * where the first element is a MatchPattern and the second element is an
   * object of the form { policy, disabledKeyboardControls } (where each property
   * may be missing or undefined).
   *
   * @returns {Array<Array<2>>} Array of 2-element Arrays where the first element
   * is a MatchPattern and the second element is an object with optional policy
   * and/or disabledKeyboardControls properties.
   */
  static getSiteOverrides() {
    let result = [];
    let patterns = Services.cpmm.sharedData.get(
      "PictureInPicture:SiteOverrides"
    );
    for (let pattern in patterns) {
      let matcher = new MatchPattern(pattern);
      result.push([matcher, patterns[pattern]]);
    }
    return result;
  }
}

export class PictureInPictureChild extends JSWindowActorChild {
  #subtitlesEnabled = false;
  // A weak reference to this PiP window's video element
  weakVideo = null;

  // A weak reference to this PiP window's content window
  weakPlayerContent = null;

  // A reference to current WebVTT track currently displayed on the content window
  _currentWebVTTTrack = null;

  observerFunction = null;

  observe(subject, topic, data) {
    if (topic != "nsPref:changed") {
      return;
    }

    switch (data) {
      case "media.videocontrols.picture-in-picture.display-text-tracks.enabled": {
        const originatingVideo = this.getWeakVideo();
        let isTextTrackPrefEnabled = Services.prefs.getBoolPref(
          "media.videocontrols.picture-in-picture.display-text-tracks.enabled"
        );

        // Enable or disable text track support
        if (isTextTrackPrefEnabled) {
          this.setupTextTracks(originatingVideo);
        } else {
          this.removeTextTracks(originatingVideo);
        }
        break;
      }
    }
  }

  /**
   * Creates a link element with a reference to the css stylesheet needed
   * for text tracks responsive styling.
   * @returns {Element} the link element containing text tracks stylesheet.
   */
  createTextTracksStyleSheet() {
    let headStyleElement = this.document.createElement("link");
    headStyleElement.setAttribute("rel", "stylesheet");
    headStyleElement.setAttribute(
      "href",
      "chrome://global/skin/pictureinpicture/texttracks.css"
    );
    headStyleElement.setAttribute("type", "text/css");
    return headStyleElement;
  }

  /**
   * Sets up Picture-in-Picture to support displaying text tracks from WebVTT
   * or if WebVTT isn't supported we will register the caption change mutation observer if
   * the site wrapper exists.
   *
   * If the originating video supports WebVTT, try to read the
   * active track and cues. Display any active cues on the pip window
   * right away if applicable.
   *
   * @param originatingVideo {Element|null}
   *  The <video> being displayed in Picture-in-Picture mode, or null if that <video> no longer exists.
   */
  setupTextTracks(originatingVideo) {
    const isWebVTTSupported = !!originatingVideo.textTracks?.length;

    if (!isWebVTTSupported) {
      this.setUpCaptionChangeListener(originatingVideo);
      return;
    }

    // Verify active track for originating video
    this.setActiveTextTrack(originatingVideo.textTracks);

    if (!this._currentWebVTTTrack) {
      // If WebVTT track is invalid, try using a video wrapper
      this.setUpCaptionChangeListener(originatingVideo);
      return;
    }

    // Listen for changes in tracks and active cues
    originatingVideo.textTracks.addEventListener("change", this);
    this._currentWebVTTTrack.addEventListener("cuechange", this.onCueChange);

    const cues = this._currentWebVTTTrack.activeCues;
    this.updateWebVTTTextTracksDisplay(cues);
  }

  /**
   * Toggle the visibility of the subtitles in the PiP window
   */
  toggleTextTracks() {
    let textTracks = this.document.getElementById("texttracks");
    textTracks.style.display =
      textTracks.style.display === "none" ? "" : "none";
  }

  /**
   * Removes existing text tracks on the Picture in Picture window.
   *
   * If the originating video supports WebVTT, clear references to active
   * tracks and cues. No longer listen for any track or cue changes.
   *
   * @param originatingVideo {Element|null}
   *  The <video> being displayed in Picture-in-Picture mode, or null if that <video> no longer exists.
   */
  removeTextTracks(originatingVideo) {
    const isWebVTTSupported = !!originatingVideo.textTracks;

    this.removeCaptionChangeListener(originatingVideo);

    if (!isWebVTTSupported) {
      return;
    }

    // No longer listen for changes to tracks and active cues
    originatingVideo.textTracks.removeEventListener("change", this);
    this._currentWebVTTTrack?.removeEventListener(
      "cuechange",
      this.onCueChange
    );
    this._currentWebVTTTrack = null;
    this.updateWebVTTTextTracksDisplay(null);
  }

  /**
   * Moves the text tracks container position above the pip window's video controls
   * if their positions visually overlap. Since pip controls are within the parent
   * process, we determine if pip video controls and text tracks visually overlap by
   * comparing their relative positions with DOMRect.
   *
   * If overlap is found, set attribute "overlap-video-controls" to move text tracks
   * and define a new relative bottom position according to pip window size and the
   * position of video controls.
   *  @param {Object} data args needed to determine if text tracks must be moved
   */
  moveTextTracks(data) {
    const {
      isFullscreen,
      isVideoControlsShowing,
      playerBottomControlsDOMRect,
      isScrubberShowing,
    } = data;
    let textTracks = this.document.getElementById("texttracks");
    const originatingWindow = this.getWeakVideo().ownerGlobal;
    const isReducedMotionEnabled = originatingWindow.matchMedia(
      "(prefers-reduced-motion: reduce)"
    ).matches;
    const textTracksFontScale = this.document
      .querySelector(":root")
      .style.getPropertyValue("--font-scale");

    if (isFullscreen || isReducedMotionEnabled) {
      textTracks.removeAttribute("overlap-video-controls");
      return;
    }

    if (isVideoControlsShowing) {
      let playerVideoRect = textTracks.parentElement.getBoundingClientRect();
      let isOverlap =
        playerVideoRect.bottom - textTracksFontScale * playerVideoRect.height >
        playerBottomControlsDOMRect.top;

      if (isOverlap) {
        const root = this.document.querySelector(":root");
        if (isScrubberShowing) {
          root.style.setProperty("--player-controls-scrubber-height", "30px");
        } else {
          root.style.setProperty("--player-controls-scrubber-height", "0px");
        }
        textTracks.setAttribute("overlap-video-controls", true);
      } else {
        textTracks.removeAttribute("overlap-video-controls");
      }
    } else {
      textTracks.removeAttribute("overlap-video-controls");
    }
  }

  /**
   * Updates the text content for the container that holds and displays text tracks
   * on the pip window.
   * @param textTrackCues {TextTrackCueList|null}
   *  Collection of TextTrackCue objects containing text displayed, or null if there is no cue to display.
   */
  updateWebVTTTextTracksDisplay(textTrackCues) {
    let pipWindowTracksContainer = this.document.getElementById("texttracks");
    let playerVideo = this.document.getElementById("playervideo");
    let playerVideoWindow = playerVideo.ownerGlobal;

    // To prevent overlap with previous cues, clear all text from the pip window
    pipWindowTracksContainer.replaceChildren();

    if (!textTrackCues) {
      return;
    }

    if (!this.isSubtitlesEnabled) {
      this.isSubtitlesEnabled = true;
      this.sendAsyncMessage("PictureInPicture:EnableSubtitlesButton");
    }

    let allCuesArray = [...textTrackCues];
    // Re-order cues
    this.getOrderedWebVTTCues(allCuesArray);
    // Parse through WebVTT cue using vtt.js to ensure
    // semantic markup like <b> and <i> tags are rendered.
    allCuesArray.forEach(cue => {
      let text = cue.text;
      // Trim extra newlines and whitespaces
      const re = /(\s*\n{2,}\s*)/g;
      text = text.trim();
      text = text.replace(re, "\n");
      let cueTextNode = WebVTT.convertCueToDOMTree(playerVideoWindow, text);
      let cueDiv = this.document.createElement("div");
      cueDiv.appendChild(cueTextNode);
      pipWindowTracksContainer.appendChild(cueDiv);
    });
  }

  /**
   * Re-orders list of multiple active cues to ensure cues are rendered in the correct order.
   * How cues are ordered depends on the VTTCue.line value of the cue.
   *
   * If line is string "auto", we want to reverse the order of cues.
   * Cues are read from top to bottom in a vtt file, but are inserted into a video from bottom to top.
   * Ensure this order is followed.
   *
   * If line is an integer or percentage, we want to order cues according to numeric value.
   * Assumptions:
   *  1) all active cues are numeric
   *  2) all active cues are in range 0..100
   *  3) all actives cue are horizontal (no VTTCue.vertical)
   *  4) all active cues with VTTCue.line integer have VTTCue.snapToLines = true
   *  5) all active cues with VTTCue.line percentage have VTTCue.snapToLines = false
   *
   * vtt.sys.mjs currently sets snapToLines to false if line is a percentage value, but
   * cues are still ordered by line. In most cases, snapToLines is set to true by default,
   * unless intentionally overridden.
   * @param allCuesArray {Array<VTTCue>} array of active cues
   */
  getOrderedWebVTTCues(allCuesArray) {
    if (!allCuesArray || allCuesArray.length <= 1) {
      return;
    }

    let allCuesHaveNumericLines = allCuesArray.find(cue => cue.line !== "auto");

    if (allCuesHaveNumericLines) {
      allCuesArray.sort((cue1, cue2) => cue1.line - cue2.line);
    } else if (allCuesArray.length >= 2) {
      allCuesArray.reverse();
    }
  }

  /**
   * Returns a reference to the PiP's <video> element being displayed in Picture-in-Picture
   * mode.
   *
   * @return {Element} The <video> being displayed in Picture-in-Picture mode, or null
   * if that <video> no longer exists.
   */
  getWeakVideo() {
    if (this.weakVideo) {
      // Bug 800957 - Accessing weakrefs at the wrong time can cause us to
      // throw NS_ERROR_XPC_BAD_CONVERT_NATIVE
      try {
        return this.weakVideo.get();
      } catch (e) {
        return null;
      }
    }
    return null;
  }

  /**
   * Returns a reference to the inner window of the about:blank document that is
   * cloning the originating <video> in the always-on-top player <xul:browser>.
   *
   * @return {Window} The inner window of the about:blank player <xul:browser>, or
   * null if that window has been closed.
   */
  getWeakPlayerContent() {
    if (this.weakPlayerContent) {
      // Bug 800957 - Accessing weakrefs at the wrong time can cause us to
      // throw NS_ERROR_XPC_BAD_CONVERT_NATIVE
      try {
        return this.weakPlayerContent.get();
      } catch (e) {
        return null;
      }
    }
    return null;
  }

  /**
   * Returns true if the passed video happens to be the one that this
   * content process is running in a Picture-in-Picture window.
   *
   * @param {Element} video The <video> element to check.
   *
   * @return {Boolean}
   */
  inPictureInPicture(video) {
    return this.getWeakVideo() === video;
  }

  static videoIsPlaying(video) {
    return !!(!video.paused && !video.ended && video.readyState > 2);
  }

  static videoIsMuted(video) {
    return this.videoWrapper.isMuted(video);
  }

  handleEvent(event) {
    switch (event.type) {
      case "MozStopPictureInPicture": {
        if (event.isTrusted && event.target === this.getWeakVideo()) {
          const reason = event.detail?.reason || "videoElRemove";
          this.closePictureInPicture({ reason });
        }
        break;
      }
      case "pagehide": {
        // The originating video's content document has unloaded,
        // so close Picture-in-Picture.
        this.closePictureInPicture({ reason: "pagehide" });
        break;
      }
      case "MozDOMFullscreen:Request": {
        this.closePictureInPicture({ reason: "fullscreen" });
        break;
      }
      case "play": {
        this.sendAsyncMessage("PictureInPicture:Playing");
        break;
      }
      case "pause": {
        this.sendAsyncMessage("PictureInPicture:Paused");
        break;
      }
      case "volumechange": {
        let video = this.getWeakVideo();

        // Just double-checking that we received the event for the right
        // video element.
        if (video !== event.target) {
          lazy.logConsole.error(
            "PictureInPictureChild received volumechange for " +
              "the wrong video!"
          );
          return;
        }

        if (this.constructor.videoIsMuted(video)) {
          this.sendAsyncMessage("PictureInPicture:Muting");
        } else {
          this.sendAsyncMessage("PictureInPicture:Unmuting");
        }
        this.sendAsyncMessage("PictureInPicture:VolumeChange", {
          volume: this.videoWrapper.getVolume(video),
        });
        break;
      }
      case "resize": {
        let video = event.target;
        if (this.inPictureInPicture(video)) {
          this.sendAsyncMessage("PictureInPicture:Resize", {
            videoHeight: video.videoHeight,
            videoWidth: video.videoWidth,
          });
        }
        this.setupTextTracks(video);
        break;
      }
      case "emptied": {
        this.isSubtitlesEnabled = false;
        if (this.emptiedTimeout) {
          clearTimeout(this.emptiedTimeout);
          this.emptiedTimeout = null;
        }
        let video = this.getWeakVideo();
        // We may want to keep the pip window open if the video
        // is still in DOM. But if video src is no longer defined,
        // close Picture-in-Picture.
        this.emptiedTimeout = setTimeout(() => {
          if (!video || !video.src) {
            this.closePictureInPicture({ reason: "videoElEmptied" });
          }
        }, EMPTIED_TIMEOUT_MS);
        break;
      }
      case "change": {
        // Clear currently stored track data (webvtt support) before reading
        // a new track.
        if (this._currentWebVTTTrack) {
          this._currentWebVTTTrack.removeEventListener(
            "cuechange",
            this.onCueChange
          );
          this._currentWebVTTTrack = null;
        }

        const tracks = event.target;
        this.setActiveTextTrack(tracks);
        const isCurrentTrackAvailable = this._currentWebVTTTrack;

        // If tracks are disabled or invalid while change occurs,
        // remove text tracks from the pip window and stop here.
        if (!isCurrentTrackAvailable || !tracks.length) {
          this.updateWebVTTTextTracksDisplay(null);
          return;
        }

        this._currentWebVTTTrack.addEventListener(
          "cuechange",
          this.onCueChange
        );
        const cues = this._currentWebVTTTrack.activeCues;
        this.updateWebVTTTextTracksDisplay(cues);
        break;
      }
      case "timeupdate":
      case "durationchange": {
        let video = this.getWeakVideo();
        let currentTime = this.videoWrapper.getCurrentTime(video);
        let duration = this.videoWrapper.getDuration(video);
        let scrubberPosition = currentTime === 0 ? 0 : currentTime / duration;
        let timestamp = this.videoWrapper.formatTimestamp(
          currentTime,
          duration
        );
        // There's no point in sending this message unless we have a
        // reasonable timestamp.
        if (timestamp !== undefined && lazy.IMPROVED_CONTROLS_ENABLED_PREF) {
          this.sendAsyncMessage(
            "PictureInPicture:SetTimestampAndScrubberPosition",
            {
              scrubberPosition,
              timestamp,
            }
          );
        }
        break;
      }
    }
  }

  /**
   * Tells the parent to close a pre-existing Picture-in-Picture
   * window.
   *
   * @return {Promise}
   *
   * @resolves {undefined} Once the pre-existing Picture-in-Picture
   * window has unloaded.
   */
  async closePictureInPicture({ reason }) {
    let video = this.getWeakVideo();
    if (video) {
      this.untrackOriginatingVideo(video);
    }
    this.sendAsyncMessage("PictureInPicture:Close", {
      reason,
    });

    let playerContent = this.getWeakPlayerContent();
    if (playerContent) {
      if (!playerContent.closed) {
        await new Promise(resolve => {
          playerContent.addEventListener("unload", resolve, {
            once: true,
          });
        });
      }
      // Nothing should be holding a reference to the Picture-in-Picture
      // player window content at this point, but just in case, we'll
      // clear the weak reference directly so nothing else can get a hold
      // of it from this angle.
      this.weakPlayerContent = null;
    }
  }

  receiveMessage(message) {
    switch (message.name) {
      case "PictureInPicture:SetupPlayer": {
        const { videoRef } = message.data;
        this.setupPlayer(videoRef);
        break;
      }
      case "PictureInPicture:Play": {
        this.play();
        break;
      }
      case "PictureInPicture:Pause": {
        if (message.data && message.data.reason == "pip-closed") {
          let video = this.getWeakVideo();

          // Currently in Firefox srcObjects are MediaStreams. However, by spec a srcObject
          // can be either a MediaStream, MediaSource or Blob. In case of future changes
          // we do not want to pause MediaStream srcObjects and we want to maintain current
          // behavior for non-MediaStream srcObjects.
          if (video && MediaStream.isInstance(video.srcObject)) {
            break;
          }
        }
        this.pause();
        break;
      }
      case "PictureInPicture:Mute": {
        this.mute();
        break;
      }
      case "PictureInPicture:Unmute": {
        this.unmute();
        break;
      }
      case "PictureInPicture:SeekForward":
      case "PictureInPicture:SeekBackward": {
        let selectedTime;
        let video = this.getWeakVideo();
        let currentTime = this.videoWrapper.getCurrentTime(video);
        if (message.name == "PictureInPicture:SeekBackward") {
          selectedTime = currentTime - SEEK_TIME_SECS;
          selectedTime = selectedTime >= 0 ? selectedTime : 0;
        } else {
          const maxtime = this.videoWrapper.getDuration(video);
          selectedTime = currentTime + SEEK_TIME_SECS;
          selectedTime = selectedTime <= maxtime ? selectedTime : maxtime;
        }
        this.videoWrapper.setCurrentTime(video, selectedTime);
        break;
      }
      case "PictureInPicture:KeyDown": {
        this.keyDown(message.data);
        break;
      }
      case "PictureInPicture:EnterFullscreen":
      case "PictureInPicture:ExitFullscreen": {
        let textTracks = this.document.getElementById("texttracks");
        if (textTracks) {
          this.moveTextTracks(message.data);
        }
        break;
      }
      case "PictureInPicture:ShowVideoControls":
      case "PictureInPicture:HideVideoControls": {
        let textTracks = this.document.getElementById("texttracks");
        if (textTracks) {
          this.moveTextTracks(message.data);
        }
        break;
      }
      case "PictureInPicture:ToggleTextTracks": {
        this.toggleTextTracks();
        break;
      }
      case "PictureInPicture:ChangeFontSizeTextTracks": {
        this.setTextTrackFontSize();
        break;
      }
      case "PictureInPicture:SetVideoTime": {
        const { scrubberPosition, wasPlaying } = message.data;
        this.setVideoTime(scrubberPosition, wasPlaying);
        break;
      }
      case "PictureInPicture:SetVolume": {
        const { volume } = message.data;
        let video = this.getWeakVideo();
        this.videoWrapper.setVolume(video, volume);
        break;
      }
    }
  }

  /**
   * Set the current time of the video based of the position of the scrubber
   * @param {Number} scrubberPosition A number between 0 and 1 representing the position of the scrubber
   */
  setVideoTime(scrubberPosition, wasPlaying) {
    const video = this.getWeakVideo();
    let duration = this.videoWrapper.getDuration(video);
    let currentTime = scrubberPosition * duration;
    this.videoWrapper.setCurrentTime(video, currentTime, wasPlaying);
  }

  /**
   * @returns {boolean} true if a textTrack with mode "hidden" should be treated as "showing"
   */
  shouldShowHiddenTextTracks() {
    const video = this.getWeakVideo();
    if (!video) {
      return false;
    }
    const { documentURI } = video.ownerDocument;
    if (!documentURI) {
      return false;
    }
    for (let [override, { showHiddenTextTracks }] of lazy.gSiteOverrides) {
      if (override.matches(documentURI) && showHiddenTextTracks) {
        return true;
      }
    }
    return false;
  }

  /**
   * Updates this._currentWebVTTTrack if an active track is found
   * for the originating video.
   * @param {TextTrackList} textTrackList list of text tracks
   */
  setActiveTextTrack(textTrackList) {
    this._currentWebVTTTrack = null;

    for (let i = 0; i < textTrackList.length; i++) {
      let track = textTrackList[i];
      let isCCText = track.kind === "subtitles" || track.kind === "captions";
      let shouldShowTrack =
        track.mode === "showing" ||
        (track.mode === "hidden" && this.shouldShowHiddenTextTracks());
      if (isCCText && shouldShowTrack && track.cues) {
        this._currentWebVTTTrack = track;
        break;
      }
    }
  }

  /**
   * Set the font size on the PiP window using the current font size value from
   * the "media.videocontrols.picture-in-picture.display-text-tracks.size" pref
   */
  setTextTrackFontSize() {
    const fontSize = Services.prefs.getStringPref(
      TEXT_TRACK_FONT_SIZE,
      "medium"
    );
    const root = this.document.querySelector(":root");
    if (fontSize === "small") {
      root.style.setProperty("--font-scale", "0.03");
    } else if (fontSize === "large") {
      root.style.setProperty("--font-scale", "0.09");
    } else {
      root.style.setProperty("--font-scale", "0.06");
    }
  }

  /**
   * Keeps an eye on the originating video's document. If it ever
   * goes away, this will cause the Picture-in-Picture window for any
   * of its content to go away as well.
   */
  trackOriginatingVideo(originatingVideo) {
    this.observerFunction = (subject, topic, data) => {
      this.observe(subject, topic, data);
    };
    Services.prefs.addObserver(
      "media.videocontrols.picture-in-picture.display-text-tracks.enabled",
      this.observerFunction
    );

    let originatingWindow = originatingVideo.ownerGlobal;
    if (originatingWindow) {
      originatingWindow.addEventListener("pagehide", this);
      originatingVideo.addEventListener("play", this);
      originatingVideo.addEventListener("pause", this);
      originatingVideo.addEventListener("volumechange", this);
      originatingVideo.addEventListener("resize", this);
      originatingVideo.addEventListener("emptied", this);
      originatingVideo.addEventListener("timeupdate", this);

      if (lazy.DISPLAY_TEXT_TRACKS_PREF) {
        this.setupTextTracks(originatingVideo);
      }

      let chromeEventHandler = originatingWindow.docShell.chromeEventHandler;
      chromeEventHandler.addEventListener(
        "MozDOMFullscreen:Request",
        this,
        true
      );
      chromeEventHandler.addEventListener(
        "MozStopPictureInPicture",
        this,
        true
      );
    }
  }

  setUpCaptionChangeListener(originatingVideo) {
    if (this.videoWrapper) {
      this.videoWrapper.setCaptionContainerObserver(originatingVideo, this);
    }
  }

  removeCaptionChangeListener(originatingVideo) {
    if (this.videoWrapper) {
      this.videoWrapper.removeCaptionContainerObserver(originatingVideo, this);
    }
  }

  /**
   * Stops tracking the originating video's document. This should
   * happen once the Picture-in-Picture window goes away (or is about
   * to go away), and we no longer care about hearing when the originating
   * window's document unloads.
   */
  untrackOriginatingVideo(originatingVideo) {
    Services.prefs.removeObserver(
      "media.videocontrols.picture-in-picture.display-text-tracks.enabled",
      this.observerFunction
    );

    let originatingWindow = originatingVideo.ownerGlobal;
    if (originatingWindow) {
      originatingWindow.removeEventListener("pagehide", this);
      originatingVideo.removeEventListener("play", this);
      originatingVideo.removeEventListener("pause", this);
      originatingVideo.removeEventListener("volumechange", this);
      originatingVideo.removeEventListener("resize", this);
      originatingVideo.removeEventListener("emptied", this);
      originatingVideo.removeEventListener("timeupdate", this);

      if (lazy.DISPLAY_TEXT_TRACKS_PREF) {
        this.removeTextTracks(originatingVideo);
      }

      let chromeEventHandler = originatingWindow.docShell.chromeEventHandler;
      chromeEventHandler.removeEventListener(
        "MozDOMFullscreen:Request",
        this,
        true
      );
      chromeEventHandler.removeEventListener(
        "MozStopPictureInPicture",
        this,
        true
      );
    }
  }

  /**
   * Runs in an instance of PictureInPictureChild for the
   * player window's content, and not the originating video
   * content. Sets up the player so that it clones the originating
   * video. If anything goes wrong during set up, a message is
   * sent to the parent to close the Picture-in-Picture window.
   *
   * @param videoRef {ContentDOMReference}
   *    A reference to the video element that a Picture-in-Picture window
   *    is being created for
   * @return {Promise}
   * @resolves {undefined} Once the player window has been set up
   * properly, or a pre-existing Picture-in-Picture window has gone
   * away due to an unexpected error.
   */
  async setupPlayer(videoRef) {
    const video = await lazy.ContentDOMReference.resolve(videoRef);

    this.weakVideo = Cu.getWeakReference(video);
    let originatingVideo = this.getWeakVideo();
    if (!originatingVideo) {
      // If the video element has gone away before we've had a chance to set up
      // Picture-in-Picture for it, tell the parent to close the Picture-in-Picture
      // window.
      await this.closePictureInPicture({ reason: "setupFailure" });
      return;
    }

    this.videoWrapper = applyWrapper(this, originatingVideo);

    let loadPromise = new Promise(resolve => {
      this.contentWindow.addEventListener("load", resolve, {
        once: true,
        mozSystemGroup: true,
        capture: true,
      });
    });
    this.contentWindow.location.reload();
    await loadPromise;

    // We're committed to adding the video to this window now. Ensure we track
    // the content window before we do so, so that the toggle actor can
    // distinguish this new video we're creating from web-controlled ones.
    this.weakPlayerContent = Cu.getWeakReference(this.contentWindow);
    gPlayerContents.add(this.contentWindow);

    let doc = this.document;
    let playerVideo = doc.createElement("video");
    playerVideo.id = "playervideo";
    let textTracks = doc.createElement("div");

    doc.body.style.overflow = "hidden";
    doc.body.style.margin = "0";

    // Force the player video to assume maximum height and width of the
    // containing window
    playerVideo.style.height = "100vh";
    playerVideo.style.width = "100vw";
    playerVideo.style.backgroundColor = "#000";

    // Load text tracks container in the content process so that
    // we can load text tracks without having to constantly
    // access the parent process.
    textTracks.id = "texttracks";
    // When starting pip, player controls are expected to appear.
    textTracks.setAttribute("overlap-video-controls", true);
    doc.body.appendChild(playerVideo);
    doc.body.appendChild(textTracks);
    // Load text tracks stylesheet
    let textTracksStyleSheet = this.createTextTracksStyleSheet();
    doc.head.appendChild(textTracksStyleSheet);

    this.setTextTrackFontSize();

    originatingVideo.cloneElementVisually(playerVideo);

    let shadowRoot = originatingVideo.openOrClosedShadowRoot;
    if (originatingVideo.getTransformToViewport().a == -1) {
      shadowRoot.firstChild.setAttribute("flipped", true);
      playerVideo.style.transform = "scaleX(-1)";
    }

    this.onCueChange = this.onCueChange.bind(this);
    this.trackOriginatingVideo(originatingVideo);

    // A request to open PIP implies that the user intends to be interacting
    // with the page, even if they open PIP by some means outside of the page
    // itself (e.g., the keyboard shortcut or the page action button). So we
    // manually record that the document has been activated via user gesture
    // to make sure the video can be played regardless of autoplay permissions.
    originatingVideo.ownerDocument.notifyUserGestureActivation();

    this.contentWindow.addEventListener(
      "unload",
      () => {
        let video = this.getWeakVideo();
        if (video) {
          this.untrackOriginatingVideo(video);
          video.stopCloningElementVisually();
        }
        this.weakVideo = null;
      },
      { once: true }
    );
  }

  play() {
    let video = this.getWeakVideo();
    if (video && this.videoWrapper) {
      this.videoWrapper.play(video);
    }
  }

  pause() {
    let video = this.getWeakVideo();
    if (video && this.videoWrapper) {
      this.videoWrapper.pause(video);
    }
  }

  mute() {
    let video = this.getWeakVideo();
    if (video && this.videoWrapper) {
      this.videoWrapper.setMuted(video, true);
    }
  }

  unmute() {
    let video = this.getWeakVideo();
    if (video && this.videoWrapper) {
      this.videoWrapper.setMuted(video, false);
    }
  }

  onCueChange() {
    if (!lazy.DISPLAY_TEXT_TRACKS_PREF) {
      this.updateWebVTTTextTracksDisplay(null);
    } else {
      const cues = this._currentWebVTTTrack.activeCues;
      this.updateWebVTTTextTracksDisplay(cues);
    }
  }

  /**
   * This checks if a given keybinding has been disabled for the specific site
   * currently being viewed.
   */
  isKeyDisabled(key) {
    const video = this.getWeakVideo();
    if (!video) {
      return false;
    }
    const { documentURI } = video.ownerDocument;
    if (!documentURI) {
      return true;
    }
    for (let [override, { disabledKeyboardControls }] of lazy.gSiteOverrides) {
      if (
        disabledKeyboardControls !== undefined &&
        override.matches(documentURI)
      ) {
        if (disabledKeyboardControls === lazy.KEYBOARD_CONTROLS.ALL) {
          return true;
        }
        return !!(disabledKeyboardControls & key);
      }
    }
    return false;
  }

  /**
   * This reuses the keyHandler logic in the VideoControlsWidget
   * https://searchfox.org/mozilla-central/rev/cfd1cc461f1efe0d66c2fdc17c024a203d5a2fd8/toolkit/content/widgets/videocontrols.js#1687-1810.
   * There are future plans to eventually combine the two implementations.
   */
  /* eslint-disable complexity */
  keyDown({ altKey, shiftKey, metaKey, ctrlKey, keyCode }) {
    let video = this.getWeakVideo();
    if (!video) {
      return;
    }

    var keystroke = "";
    if (altKey) {
      keystroke += "alt-";
    }
    if (shiftKey) {
      keystroke += "shift-";
    }
    if (this.contentWindow.navigator.platform.startsWith("Mac")) {
      if (metaKey) {
        keystroke += "accel-";
      }
      if (ctrlKey) {
        keystroke += "control-";
      }
    } else {
      if (metaKey) {
        keystroke += "meta-";
      }
      if (ctrlKey) {
        keystroke += "accel-";
      }
    }

    switch (keyCode) {
      case this.contentWindow.KeyEvent.DOM_VK_UP:
        keystroke += "upArrow";
        break;
      case this.contentWindow.KeyEvent.DOM_VK_DOWN:
        keystroke += "downArrow";
        break;
      case this.contentWindow.KeyEvent.DOM_VK_LEFT:
        keystroke += "leftArrow";
        break;
      case this.contentWindow.KeyEvent.DOM_VK_RIGHT:
        keystroke += "rightArrow";
        break;
      case this.contentWindow.KeyEvent.DOM_VK_HOME:
        keystroke += "home";
        break;
      case this.contentWindow.KeyEvent.DOM_VK_END:
        keystroke += "end";
        break;
      case this.contentWindow.KeyEvent.DOM_VK_SPACE:
        keystroke += "space";
        break;
      case this.contentWindow.KeyEvent.DOM_VK_W:
        keystroke += "w";
        break;
    }

    const isVideoStreaming = this.videoWrapper.isLive(video);
    var oldval, newval;

    try {
      switch (keystroke) {
        case "space" /* Toggle Play / Pause */:
          if (this.isKeyDisabled(lazy.KEYBOARD_CONTROLS.PLAY_PAUSE)) {
            return;
          }

          if (
            this.videoWrapper.getPaused(video) ||
            this.videoWrapper.getEnded(video)
          ) {
            this.videoWrapper.play(video);
          } else {
            this.videoWrapper.pause(video);
          }

          break;
        case "accel-w" /* Close video */:
          if (this.isKeyDisabled(lazy.KEYBOARD_CONTROLS.CLOSE)) {
            return;
          }
          this.pause();
          this.closePictureInPicture({ reason: "closePlayerShortcut" });
          break;
        case "downArrow" /* Volume decrease */:
          if (
            this.isKeyDisabled(lazy.KEYBOARD_CONTROLS.VOLUME) ||
            this.videoWrapper.isMuted(video)
          ) {
            return;
          }
          oldval = this.videoWrapper.getVolume(video);
          newval = oldval < 0.1 ? 0 : oldval - 0.1;
          this.videoWrapper.setVolume(video, newval);
          this.videoWrapper.setMuted(video, newval === 0);
          break;
        case "upArrow" /* Volume increase */:
          if (this.isKeyDisabled(lazy.KEYBOARD_CONTROLS.VOLUME)) {
            return;
          }
          oldval = this.videoWrapper.getVolume(video);
          this.videoWrapper.setVolume(video, oldval > 0.9 ? 1 : oldval + 0.1);
          this.videoWrapper.setMuted(video, false);
          break;
        case "accel-downArrow" /* Mute */:
          if (this.isKeyDisabled(lazy.KEYBOARD_CONTROLS.MUTE_UNMUTE)) {
            return;
          }
          this.videoWrapper.setMuted(video, true);
          break;
        case "accel-upArrow" /* Unmute */:
          if (this.isKeyDisabled(lazy.KEYBOARD_CONTROLS.MUTE_UNMUTE)) {
            return;
          }
          this.videoWrapper.setMuted(video, false);
          break;
        case "leftArrow": /* Seek back 5 seconds */
        case "accel-leftArrow" /* Seek back 10% */:
          if (
            this.isKeyDisabled(lazy.KEYBOARD_CONTROLS.SEEK) ||
            (isVideoStreaming &&
              this.isKeyDisabled(lazy.KEYBOARD_CONTROLS.LIVE_SEEK))
          ) {
            return;
          }

          oldval = this.videoWrapper.getCurrentTime(video);
          if (keystroke == "leftArrow") {
            newval = oldval - SEEK_TIME_SECS;
          } else {
            newval = oldval - this.videoWrapper.getDuration(video) / 10;
          }
          this.videoWrapper.setCurrentTime(video, newval >= 0 ? newval : 0);
          break;
        case "rightArrow": /* Seek forward 5 seconds */
        case "accel-rightArrow" /* Seek forward 10% */:
          if (
            this.isKeyDisabled(lazy.KEYBOARD_CONTROLS.SEEK) ||
            (isVideoStreaming &&
              this.isKeyDisabled(lazy.KEYBOARD_CONTROLS.LIVE_SEEK))
          ) {
            return;
          }

          oldval = this.videoWrapper.getCurrentTime(video);
          var maxtime = this.videoWrapper.getDuration(video);
          if (keystroke == "rightArrow") {
            newval = oldval + SEEK_TIME_SECS;
          } else {
            newval = oldval + maxtime / 10;
          }
          let selectedTime = newval <= maxtime ? newval : maxtime;
          this.videoWrapper.setCurrentTime(video, selectedTime);
          break;
        case "home" /* Seek to beginning */:
          if (this.isKeyDisabled(lazy.KEYBOARD_CONTROLS.SEEK)) {
            return;
          }
          if (!isVideoStreaming) {
            this.videoWrapper.setCurrentTime(video, 0);
          }
          break;
        case "end" /* Seek to end */:
          if (this.isKeyDisabled(lazy.KEYBOARD_CONTROLS.SEEK)) {
            return;
          }

          let duration = this.videoWrapper.getDuration(video);
          if (
            !isVideoStreaming &&
            this.videoWrapper.getCurrentTime(video) != duration
          ) {
            this.videoWrapper.setCurrentTime(video, duration);
          }
          break;
        default:
      }
    } catch (e) {
      /* ignore any exception from setting video.currentTime */
    }
  }

  get isSubtitlesEnabled() {
    return this.#subtitlesEnabled;
  }

  set isSubtitlesEnabled(val) {
    if (val) {
      Services.telemetry.recordEvent(
        "pictureinpicture",
        "subtitles_shown",
        "subtitles",
        null,
        {
          webVTTSubtitles: (!!this.getWeakVideo().textTracks
            ?.length).toString(),
        }
      );
    } else {
      this.sendAsyncMessage("PictureInPicture:DisableSubtitlesButton");
    }
    this.#subtitlesEnabled = val;
  }
}

/**
 * The PictureInPictureChildVideoWrapper class handles providing a path to a script that
 * defines a "site wrapper" for the original <video> (or other controls API provided
 * by the site) to command it.
 *
 * This "site wrapper" provided to PictureInPictureChildVideoWrapper is a script file that
 * defines a class called `PictureInPictureVideoWrapper` and exports it. These scripts can
 * be found under "browser/extensions/pictureinpicture/video-wrappers" as part of the
 * Picture-In-Picture addon.
 *
 * Site wrappers need to adhere to a specific interface to work properly with
 * PictureInPictureChildVideoWrapper:
 *
 * - The "site wrapper" script must export a class called "PictureInPictureVideoWrapper"
 * - Method names on a site wrapper class should match its caller's name
 *   (i.e: PictureInPictureChildVideoWrapper.play will only call `play` on a site-wrapper, if available)
 */
class PictureInPictureChildVideoWrapper {
  #sandbox;
  #siteWrapper;
  #PictureInPictureChild;

  /**
   * Create a wrapper for the original <video>
   *
   * @param {String|null} videoWrapperScriptPath
   *        Path to a wrapper script from the Picture-in-Picture addon. If a wrapper isn't
   *        provided to the class, then we fallback on a default implementation for
   *        commanding the original <video>.
   * @param {HTMLVideoElement} video
   *        The original <video> we want to create a wrapper class for.
   * @param {Object} pipChild
   *        Reference to PictureInPictureChild class calling this function.
   */
  constructor(videoWrapperScriptPath, video, pipChild) {
    this.#sandbox = videoWrapperScriptPath
      ? this.#createSandbox(videoWrapperScriptPath, video)
      : null;
    this.#PictureInPictureChild = pipChild;
  }

  /**
   * Handles calling methods defined on the site wrapper class to perform video
   * controls operations on the source video. If the method doesn't exist,
   * or if an error is thrown while calling it, use a fallback implementation.
   *
   * @param {String} methodInfo.name
   *        The method name to call.
   * @param {Array} methodInfo.args
   *        Arguments to pass to the site wrapper method being called.
   * @param {Function} methodInfo.fallback
   *        A fallback function that's invoked when a method doesn't exist on the site
   *        wrapper class or an error is thrown while calling a method
   * @param {Function} methodInfo.validateReturnVal
   *        Validates whether or not the return value of the wrapper method is correct.
   *        If this isn't provided or if it evaluates false for a return value, then
   *        return null.
   *
   * @returns The expected output of the wrapper function.
   */
  #callWrapperMethod({ name, args = [], fallback = () => {}, validateRetVal }) {
    try {
      const wrappedMethod = this.#siteWrapper?.[name];
      if (typeof wrappedMethod === "function") {
        let retVal = wrappedMethod.call(this.#siteWrapper, ...args);

        if (!validateRetVal) {
          lazy.logConsole.error(
            `No return value validator was provided for method ${name}(). Returning null.`
          );
          return null;
        }

        if (!validateRetVal(retVal)) {
          lazy.logConsole.error(
            `Calling method ${name}() returned an unexpected value: ${retVal}. Returning null.`
          );
          return null;
        }

        return retVal;
      }
    } catch (e) {
      lazy.logConsole.error(
        `There was an error while calling ${name}(): `,
        e.message
      );
    }

    return fallback();
  }

  /**
   * Creates a sandbox with Xray vision to execute content code in an unprivileged
   * context. This way, privileged code (PictureInPictureChild) can call into the
   * sandbox to perform video controls operations on the originating video
   * (content code) and still be protected from direct access by it.
   *
   * @param {String} videoWrapperScriptPath
   *        Path to a wrapper script from the Picture-in-Picture addon.
   * @param {HTMLVideoElement} video
   *        The source video element whose window to create a sandbox for.
   */
  #createSandbox(videoWrapperScriptPath, video) {
    const addonPolicy = WebExtensionPolicy.getByID(
      "pictureinpicture@mozilla.org"
    );
    let wrapperScriptUrl = addonPolicy.getURL(videoWrapperScriptPath);
    let originatingWin = video.ownerGlobal;
    let originatingDoc = video.ownerDocument;

    let sandbox = Cu.Sandbox([originatingDoc.nodePrincipal], {
      sandboxName: "Picture-in-Picture video wrapper sandbox",
      sandboxPrototype: originatingWin,
      sameZoneAs: originatingWin,
      wantXrays: false,
    });

    try {
      Services.scriptloader.loadSubScript(wrapperScriptUrl, sandbox);
    } catch (e) {
      Cu.nukeSandbox(sandbox);
      lazy.logConsole.error(
        "Error loading wrapper script for Picture-in-Picture",
        e
      );
      return null;
    }

    // The prototype of the wrapper class instantiated from the sandbox with Xray
    // vision is `Object` and not actually `PictureInPictureVideoWrapper`. But we
    // need to be able to access methods defined on this class to perform site-specific
    // video control operations otherwise we fallback to a default implementation.
    // Because of this, we need to "waive Xray vision" by adding `.wrappedObject` to the
    // end.
    this.#siteWrapper = new sandbox.PictureInPictureVideoWrapper(
      video
    ).wrappedJSObject;

    return sandbox;
  }

  #isBoolean(val) {
    return typeof val === "boolean";
  }

  #isNumber(val) {
    return typeof val === "number";
  }

  /**
   * Destroys the sandbox for the site wrapper class
   */
  destroy() {
    if (this.#sandbox) {
      Cu.nukeSandbox(this.#sandbox);
    }
  }

  /**
   * Function to display the captions on the PiP window
   * @param text The captions to be shown on the PiP window
   */
  updatePiPTextTracks(text) {
    if (!this.#PictureInPictureChild.isSubtitlesEnabled && text) {
      this.#PictureInPictureChild.isSubtitlesEnabled = true;
      this.#PictureInPictureChild.sendAsyncMessage(
        "PictureInPicture:EnableSubtitlesButton"
      );
    }
    let pipWindowTracksContainer =
      this.#PictureInPictureChild.document.getElementById("texttracks");
    pipWindowTracksContainer.textContent = text;
  }

  /* Video methods to be used for video controls from the PiP window. */

  /**
   * OVERRIDABLE - calls the play() method defined in the site wrapper script. Runs a fallback implementation
   * if the method does not exist or if an error is thrown while calling it. This method is meant to handle video
   * behaviour when a video is played.
   * @param {HTMLVideoElement} video
   *  The originating video source element
   */
  play(video) {
    return this.#callWrapperMethod({
      name: "play",
      args: [video],
      fallback: () => video.play(),
      validateRetVal: retVal => retVal == null,
    });
  }

  /**
   * OVERRIDABLE - calls the pause() method defined in the site wrapper script. Runs a fallback implementation
   * if the method does not exist or if an error is thrown while calling it. This method is meant to handle video
   * behaviour when a video is paused.
   * @param {HTMLVideoElement} video
   *  The originating video source element
   */
  pause(video) {
    return this.#callWrapperMethod({
      name: "pause",
      args: [video],
      fallback: () => video.pause(),
      validateRetVal: retVal => retVal == null,
    });
  }

  /**
   * OVERRIDABLE - calls the getPaused() method defined in the site wrapper script. Runs a fallback implementation
   * if the method does not exist or if an error is thrown while calling it. This method is meant to determine if
   * a video is paused or not.
   * @param {HTMLVideoElement} video
   *  The originating video source element
   * @returns {Boolean} Boolean value true if paused, or false if video is still playing
   */
  getPaused(video) {
    return this.#callWrapperMethod({
      name: "getPaused",
      args: [video],
      fallback: () => video.paused,
      validateRetVal: retVal => this.#isBoolean(retVal),
    });
  }

  /**
   * OVERRIDABLE - calls the getEnded() method defined in the site wrapper script. Runs a fallback implementation
   * if the method does not exist or if an error is thrown while calling it. This method is meant to determine if
   * video playback or streaming has stopped.
   * @param {HTMLVideoElement} video
   *  The originating video source element
   * @returns {Boolean} Boolean value true if the video has ended, or false if still playing
   */
  getEnded(video) {
    return this.#callWrapperMethod({
      name: "getEnded",
      args: [video],
      fallback: () => video.ended,
      validateRetVal: retVal => this.#isBoolean(retVal),
    });
  }

  /**
   * OVERRIDABLE - calls the getDuration() method defined in the site wrapper script. Runs a fallback implementation
   * if the method does not exist or if an error is thrown while calling it. This method is meant to get the current
   * duration of a video in seconds.
   * @param {HTMLVideoElement} video
   *  The originating video source element
   * @returns {Number} Duration of the video in seconds
   */
  getDuration(video) {
    return this.#callWrapperMethod({
      name: "getDuration",
      args: [video],
      fallback: () => video.duration,
      validateRetVal: retVal => this.#isNumber(retVal),
    });
  }

  /**
   * OVERRIDABLE - calls the getCurrentTime() method defined in the site wrapper script. Runs a fallback implementation
   * if the method does not exist or if an error is thrown while calling it. This method is meant to get the current
   * time of a video in seconds.
   * @param {HTMLVideoElement} video
   *  The originating video source element
   * @returns {Number} Current time of the video in seconds
   */
  getCurrentTime(video) {
    return this.#callWrapperMethod({
      name: "getCurrentTime",
      args: [video],
      fallback: () => video.currentTime,
      validateRetVal: retVal => this.#isNumber(retVal),
    });
  }

  /**
   * OVERRIDABLE - calls the setCurrentTime() method defined in the site wrapper script. Runs a fallback implementation
   * if the method does not exist or if an error is thrown while calling it. This method is meant to set the current
   * time of a video.
   * @param {HTMLVideoElement} video
   *  The originating video source element
   * @param {Number} position
   *  The current playback time of the video
   * @param {Boolean} wasPlaying
   *  True if the video was playing before seeking else false
   */
  setCurrentTime(video, position, wasPlaying) {
    return this.#callWrapperMethod({
      name: "setCurrentTime",
      args: [video, position, wasPlaying],
      fallback: () => {
        video.currentTime = position;
      },
      validateRetVal: retVal => retVal == null,
    });
  }

  /**
   * Return hours, minutes, and seconds from seconds
   * @param {Number} aSeconds
   *  The time in seconds
   * @returns {String} Timestamp string
   **/
  timeFromSeconds(aSeconds) {
    aSeconds = isNaN(aSeconds) ? 0 : Math.round(aSeconds);
    let seconds = Math.floor(aSeconds % 60),
      minutes = Math.floor((aSeconds / 60) % 60),
      hours = Math.floor(aSeconds / 3600);
    seconds = seconds < 10 ? "0" + seconds : seconds;
    minutes = hours > 0 && minutes < 10 ? "0" + minutes : minutes;
    return aSeconds < 3600
      ? `${minutes}:${seconds}`
      : `${hours}:${minutes}:${seconds}`;
  }

  /**
   * Format a timestamp from current time and total duration,
   * output as a string in the form '0:00 / 0:00'
   * @param {Number} aCurrentTime
   *  The current time in seconds
   * @param {Number} aDuration
   *  The total duration in seconds
   * @returns {String} Formatted timestamp
   **/
  formatTimestamp(aCurrentTime, aDuration) {
    // We can't format numbers that can't be represented as decimal digits.
    if (!Number.isFinite(aCurrentTime) || !Number.isFinite(aDuration)) {
      return undefined;
    }

    return `${this.timeFromSeconds(aCurrentTime)} / ${this.timeFromSeconds(
      aDuration
    )}`;
  }

  /**
   * OVERRIDABLE - calls the getVolume() method defined in the site wrapper script. Runs a fallback implementation
   * if the method does not exist or if an error is thrown while calling it. This method is meant to get the volume
   * value of a video.
   * @param {HTMLVideoElement} video
   *  The originating video source element
   * @returns {Number} Volume of the video between 0 (muted) and 1 (loudest)
   */
  getVolume(video) {
    return this.#callWrapperMethod({
      name: "getVolume",
      args: [video],
      fallback: () => video.volume,
      validateRetVal: retVal => this.#isNumber(retVal),
    });
  }

  /**
   * OVERRIDABLE - calls the setVolume() method defined in the site wrapper script. Runs a fallback implementation
   * if the method does not exist or if an error is thrown while calling it. This method is meant to set the volume
   * value of a video.
   * @param {HTMLVideoElement} video
   *  The originating video source element
   * @param {Number} volume
   *  Value between 0 (muted) and 1 (loudest)
   */
  setVolume(video, volume) {
    return this.#callWrapperMethod({
      name: "setVolume",
      args: [video, volume],
      fallback: () => {
        video.volume = volume;
      },
      validateRetVal: retVal => retVal == null,
    });
  }

  /**
   * OVERRIDABLE - calls the isMuted() method defined in the site wrapper script. Runs a fallback implementation
   * if the method does not exist or if an error is thrown while calling it. This method is meant to get the mute
   * state a video.
   * @param {HTMLVideoElement} video
   *  The originating video source element
   * @param {Boolean} shouldMute
   *  Boolean value true to mute the video, or false to unmute the video
   */
  isMuted(video) {
    return this.#callWrapperMethod({
      name: "isMuted",
      args: [video],
      fallback: () => video.muted,
      validateRetVal: retVal => this.#isBoolean(retVal),
    });
  }

  /**
   * OVERRIDABLE - calls the setMuted() method defined in the site wrapper script. Runs a fallback implementation
   * if the method does not exist or if an error is thrown while calling it. This method is meant to mute or unmute
   * a video.
   * @param {HTMLVideoElement} video
   *  The originating video source element
   * @param {Boolean} shouldMute
   *  Boolean value true to mute the video, or false to unmute the video
   */
  setMuted(video, shouldMute) {
    return this.#callWrapperMethod({
      name: "setMuted",
      args: [video, shouldMute],
      fallback: () => {
        video.muted = shouldMute;
      },
      validateRetVal: retVal => retVal == null,
    });
  }

  /**
   * OVERRIDABLE - calls the setCaptionContainerObserver() method defined in the site wrapper script. Runs a fallback implementation
   * if the method does not exist or if an error is thrown while calling it. This method is meant to listen for any cue changes in a
   * video's caption container and execute a callback function responsible for updating the pip window's text tracks container whenever
   * a cue change is triggered {@see updatePiPTextTracks()}.
   * @param {HTMLVideoElement} video
   *  The originating video source element
   * @param {Function} _callback
   *  The callback function to be executed when cue changes are detected
   */
  setCaptionContainerObserver(video, _callback) {
    return this.#callWrapperMethod({
      name: "setCaptionContainerObserver",
      args: [
        video,
        text => {
          this.updatePiPTextTracks(text);
        },
      ],
      fallback: () => {},
      validateRetVal: retVal => retVal == null,
    });
  }

  /**
   * OVERRIDABLE - calls the removeCaptionContainerObserver() method defined in the site wrapper script. Runs a fallback implementation
   * if the method does not exist or if an error is thrown while calling it. This method is meant to remove any caption observers that
   * may have been set in setCaptionContainerObserver().
   * @param {HTMLVideoElement} video
   *  The originating video source element
   * @param {Function} _callback
   *  The callback function to be executed when cue changes are detected
   */
  removeCaptionContainerObserver(video, _callback) {
    return this.#callWrapperMethod({
      name: "removeCaptionContainerObserver",
      args: [video],
      fallback: () => {},
      validateRetVal: retVal => retVal == null,
    });
  }

  /**
   * OVERRIDABLE - calls the shouldHideToggle() method defined in the site wrapper script. Runs a fallback implementation
   * if the method does not exist or if an error is thrown while calling it. This method is meant to determine if the pip toggle
   * for a video should be hidden by the site wrapper.
   * @param {HTMLVideoElement} video
   *  The originating video source element
   * @returns {Boolean} Boolean value true if the pip toggle should be hidden by the site wrapper, or false if it should not
   */
  shouldHideToggle(video) {
    return this.#callWrapperMethod({
      name: "shouldHideToggle",
      args: [video],
      fallback: () => false,
      validateRetVal: retVal => this.#isBoolean(retVal),
    });
  }

  /**
   * OVERRIDABLE - calls the isLive() method defined in the site wrapper script. Runs a fallback implementation
   * if the method does not exist or if an error is thrown while calling it. This method is meant to get if the
   * video is a live stream.
   * @param {HTMLVideoElement} video
   *  The originating video source element
   */
  isLive(video) {
    return this.#callWrapperMethod({
      name: "isLive",
      args: [video],
      fallback: () => video.duration === Infinity,
      validateRetVal: retVal => this.#isBoolean(retVal),
    });
  }
}
PK
!<�FY

!actors/PopupBlockingChild.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint no-unused-vars: ["error", {args: "none"}] */

// The maximum number of popup information we'll send to the parent.
const MAX_SENT_POPUPS = 15;

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

export class PopupBlockingChild extends JSWindowActorChild {
  constructor() {
    super();
    this.weakDocStates = new WeakMap();
  }

  /**
   * Returns the state for the current document referred to via
   * this.document. If no such state exists, creates it, stores it
   * and returns it.
   */
  get docState() {
    let state = this.weakDocStates.get(this.document);
    if (!state) {
      state = {
        popupData: [],
      };
      this.weakDocStates.set(this.document, state);
    }

    return state;
  }

  receiveMessage(msg) {
    switch (msg.name) {
      case "UnblockPopup": {
        let i = msg.data.index;
        let state = this.docState;
        let popupData = state.popupData[i];
        if (popupData) {
          let dwi = popupData.requestingWindow;

          // If we have a requesting window and the requesting document is
          // still the current document, open the popup.
          if (dwi && dwi.document == popupData.requestingDocument) {
            dwi.open(
              popupData.popupWindowURISpec,
              popupData.popupWindowName,
              popupData.popupWindowFeatures
            );
          }
        }
        break;
      }

      case "GetBlockedPopupList": {
        let state = this.docState;
        let length = Math.min(state.popupData.length, MAX_SENT_POPUPS);

        let result = [];

        for (let i = 0; i < length; ++i) {
          let popup = state.popupData[i];

          let popupWindowURISpec = popup.popupWindowURISpec;

          if (this.contentWindow.location.href == popupWindowURISpec) {
            popupWindowURISpec = "<self>";
          } else {
            // Limit 500 chars to be sent because the URI will be cropped
            // by the UI anyway, and data: URIs can be significantly larger.
            popupWindowURISpec = popupWindowURISpec.substring(0, 500);
          }

          result.push({
            popupWindowURISpec,
          });
        }

        return result;
      }
    }

    return null;
  }

  handleEvent(event) {
    switch (event.type) {
      case "DOMPopupBlocked":
        this.onPopupBlocked(event);
        break;
      case "pageshow": {
        this.onPageShow(event);
        break;
      }
    }
  }

  onPopupBlocked(event) {
    if (event.target != this.document) {
      return;
    }

    let state = this.docState;

    // Avoid spamming the parent process with too many blocked popups.
    if (state.popupData.length >= PopupBlockingChild.maxReportedPopups) {
      return;
    }

    let popup = {
      popupWindowURISpec: event.popupWindowURI
        ? event.popupWindowURI.spec
        : "about:blank",
      popupWindowFeatures: event.popupWindowFeatures,
      popupWindowName: event.popupWindowName,
      requestingWindow: event.requestingWindow,
      requestingDocument: event.requestingWindow.document,
    };

    state.popupData.push(popup);
    this.updateBlockedPopups(true);
  }

  onPageShow(event) {
    if (event.target != this.document) {
      return;
    }

    this.updateBlockedPopups(false);
  }

  updateBlockedPopups(shouldNotify) {
    this.sendAsyncMessage("UpdateBlockedPopups", {
      shouldNotify,
      count: this.docState.popupData.length,
    });
  }
}

XPCOMUtils.defineLazyPreferenceGetter(
  PopupBlockingChild,
  "maxReportedPopups",
  "privacy.popups.maxReported"
);
PK
!<�=�&�!�!"actors/PopupBlockingParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This class manages all popup blocking operations on a <xul:browser>, including
 * notifying the UI about updates to the blocked popups, and allowing popups to
 * be unblocked.
 */
export class PopupBlocker {
  constructor(browser) {
    this._browser = browser;
    this._allBlockedPopupCounts = new WeakMap();
    this._shouldShowNotification = false;
  }

  /**
   * Returns whether or not there are new blocked popups for the associated
   * <xul:browser> that the user might need to be notified about.
   */
  get shouldShowNotification() {
    return this._shouldShowNotification;
  }

  /**
   * Should be called by the UI when the user has been notified about blocked
   * popups for the associated <xul:browser>.
   */
  didShowNotification() {
    this._shouldShowNotification = false;
  }

  /**
   * Synchronously returns the most recent count of blocked popups for
   * the associated <xul:browser>.
   *
   * @return {Number}
   *   The total number of blocked popups for this <xul:browser>.
   */
  getBlockedPopupCount() {
    let totalBlockedPopups = 0;

    let contextsToVisit = [this._browser.browsingContext];
    while (contextsToVisit.length) {
      let currentBC = contextsToVisit.pop();
      let windowGlobal = currentBC.currentWindowGlobal;

      if (!windowGlobal) {
        continue;
      }

      let popupCountForGlobal =
        this._allBlockedPopupCounts.get(windowGlobal) || 0;
      totalBlockedPopups += popupCountForGlobal;
      contextsToVisit.push(...currentBC.children);
    }

    return totalBlockedPopups;
  }

  /**
   * Asynchronously retrieve information about the popups that have
   * been blocked for the associated <xul:browser>. This information
   * can be used to unblock those popups.
   *
   * @return {Promise}
   * @resolves {Array}
   *   When the blocked popup information has been gathered,
   *   resolves with an Array of Objects with the following properties:
   *
   *   browsingContext {BrowsingContext}
   *     The BrowsingContext that the popup was blocked for.
   *
   *   innerWindowId {Number}
   *     The inner window ID for the blocked popup. This is used to differentiate
   *     popups that were blocked from one page load to the next.
   *
   *   popupWindowURISpec {String}
   *     A string representing part or all of the URI that tried to be opened in a
   *     popup.
   */
  async getBlockedPopups() {
    let contextsToVisit = [this._browser.browsingContext];
    let result = [];
    while (contextsToVisit.length) {
      let currentBC = contextsToVisit.pop();
      let windowGlobal = currentBC.currentWindowGlobal;

      if (!windowGlobal) {
        continue;
      }

      let popupCountForGlobal =
        this._allBlockedPopupCounts.get(windowGlobal) || 0;
      if (popupCountForGlobal) {
        let actor = windowGlobal.getActor("PopupBlocking");
        let popups = await actor.sendQuery("GetBlockedPopupList");

        for (let popup of popups) {
          if (!popup.popupWindowURISpec) {
            continue;
          }

          result.push({
            browsingContext: currentBC,
            innerWindowId: windowGlobal.innerWindowId,
            popupWindowURISpec: popup.popupWindowURISpec,
          });
        }
      }

      contextsToVisit.push(...currentBC.children);
    }

    return result;
  }

  /**
   * Unblocks a popup that had been blocked. The information passed should
   * come from the list of blocked popups returned via getBlockedPopups().
   *
   * Unblocking a popup causes that popup to open.
   *
   * @param browsingContext {BrowsingContext}
   *   The BrowsingContext that the popup was blocked for.
   *
   * @param innerWindowId {Number}
   *   The inner window ID for the blocked popup. This is used to differentiate popups
   *   that were blocked from one page load to the next.
   *
   * @param popupIndex {Number}
   *   The index of the entry in the Array returned by getBlockedPopups().
   */
  unblockPopup(browsingContext, innerWindowId, popupIndex) {
    let popupFrame = browsingContext.top.embedderElement;
    let popupBrowser = popupFrame.outerBrowser
      ? popupFrame.outerBrowser
      : popupFrame;

    if (this._browser != popupBrowser) {
      throw new Error(
        "Attempting to unblock popup in a BrowsingContext no longer hosted in this browser."
      );
    }

    let windowGlobal = browsingContext.currentWindowGlobal;

    if (!windowGlobal || windowGlobal.innerWindowId != innerWindowId) {
      // The inner window has moved on since the user clicked on
      // the blocked popups dropdown, so we'll just exit silently.
      return;
    }

    let actor = browsingContext.currentWindowGlobal.getActor("PopupBlocking");
    actor.sendAsyncMessage("UnblockPopup", { index: popupIndex });
  }

  /**
   * Goes through the most recent list of blocked popups for the associated
   * <xul:browser> and unblocks all of them. Unblocking a popup causes the popup
   * to open.
   */
  async unblockAllPopups() {
    let popups = await this.getBlockedPopups();
    for (let i = 0; i < popups.length; ++i) {
      let popup = popups[i];
      this.unblockPopup(popup.browsingContext, popup.innerWindowId, i);
    }
  }

  /**
   * Fires a DOMUpdateBlockedPopups chrome-only event so that the UI can
   * update itself to represent the current state of popup blocking for
   * the associated <xul:browser>.
   */
  updateBlockedPopupsUI() {
    let event = this._browser.ownerDocument.createEvent("Events");
    event.initEvent("DOMUpdateBlockedPopups", true, true);
    this._browser.dispatchEvent(event);
  }

  /** Private methods **/

  /**
   * Updates the current popup count for a particular BrowsingContext based
   * on messages from the underlying process.
   *
   * This should only be called by a PopupBlockingParent instance.
   *
   * @param browsingContext {BrowsingContext}
   *   The BrowsingContext to update the internal blocked popup count for.
   *
   * @param blockedPopupData {Object}
   *   An Object representing information about how many popups are blocked
   *   for the BrowsingContext. The Object has the following properties:
   *
   *   count {Number}
   *     The total number of blocked popups for the BrowsingContext.
   *
   *   shouldNotify {Boolean}
   *     Whether or not the list of blocked popups has changed in such a way that
   *     the UI should be updated about it.
   */
  _updateBlockedPopupEntries(browsingContext, blockedPopupData) {
    let windowGlobal = browsingContext.currentWindowGlobal;
    let { count, shouldNotify } = blockedPopupData;

    if (!this.shouldShowNotification && shouldNotify) {
      this._shouldShowNotification = true;
    }

    if (windowGlobal) {
      this._allBlockedPopupCounts.set(windowGlobal, count);
    }

    this.updateBlockedPopupsUI();
  }
}

/**
 * To keep things properly encapsulated, these should only be instantiated via
 * the PopupBlocker class for a particular <xul:browser>.
 *
 * Instantiated for a WindowGlobalParent for a BrowsingContext in one of two cases:
 *
 *   1. One or more popups have been blocked for the underlying frame represented
 *      by the WindowGlobalParent.
 *
 *   2. Something in the parent process is querying a frame for information about
 *      any popups that may have been blocked inside of it.
 */
export class PopupBlockingParent extends JSWindowActorParent {
  didDestroy() {
    this.updatePopupCountForBrowser({ count: 0, shouldNotify: false });
  }

  receiveMessage(message) {
    if (message.name == "UpdateBlockedPopups") {
      this.updatePopupCountForBrowser({
        count: message.data.count,
        shouldNotify: message.data.shouldNotify,
      });
    }
  }

  /**
   * Updates the PopupBlocker for the <xul:browser> associated with this
   * PopupBlockingParent with the most recent count of blocked popups.
   *
   * @param data {Object}
   *   An Object with the following properties:
   *
   *     count {Number}:
   *       The number of blocked popups for the underlying document.
   *
   *     shouldNotify {Boolean}:
   *       Whether or not the list of blocked popups has changed in such a way that
   *       the UI should be updated about it.
   */
  updatePopupCountForBrowser(data) {
    let browser = this.browsingContext.top.embedderElement;
    if (!browser) {
      return;
    }

    browser.popupBlocker._updateBlockedPopupEntries(this.browsingContext, data);
  }
}
PK
!<�V,"%"%actors/PrintingChild.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  ReaderMode: "resource://gre/modules/ReaderMode.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

let gPendingPreviewsMap = new Map();

export class PrintingChild extends JSWindowActorChild {
  actorCreated() {
    // When the print preview page is loaded, the actor will change, so update
    // the state/progress listener to the new actor.
    let listener = gPendingPreviewsMap.get(this.browsingContext.id);
    if (listener) {
      listener.actor = this;
    }
    this.contentWindow.addEventListener("scroll", this);
  }

  didDestroy() {
    this._scrollTask?.disarm();
    this.contentWindow?.removeEventListener("scroll", this);
  }

  handleEvent(event) {
    switch (event.type) {
      case "PrintingError": {
        let win = event.target.defaultView;
        let wbp = win.getInterface(Ci.nsIWebBrowserPrint);
        let nsresult = event.detail;
        this.sendAsyncMessage("Printing:Error", {
          isPrinting: wbp.doingPrint,
          nsresult,
        });
        break;
      }

      case "scroll":
        if (!this._scrollTask) {
          this._scrollTask = new lazy.DeferredTask(
            () => this.updateCurrentPage(),
            16,
            16
          );
        }
        this._scrollTask.arm();
        break;
    }
  }

  receiveMessage(message) {
    let data = message.data;
    switch (message.name) {
      case "Printing:Preview:Navigate": {
        this.navigate(data.navType, data.pageNum);
        break;
      }

      case "Printing:Preview:ParseDocument": {
        return this.parseDocument(
          data.URL,
          Services.wm.getOuterWindowWithId(data.windowID)
        );
      }
    }

    return undefined;
  }

  async parseDocument(URL, contentWindow) {
    // The document in 'contentWindow' will be simplified and the resulting nodes
    // will be inserted into this.contentWindow.
    let thisWindow = this.contentWindow;

    // By using ReaderMode primitives, we parse given document and place the
    // resulting JS object into the DOM of current browser.
    let article;
    try {
      article = await lazy.ReaderMode.parseDocument(contentWindow.document);
    } catch (ex) {
      console.error(ex);
    }

    await new Promise(resolve => {
      // We make use of a web progress listener in order to know when the content we inject
      // into the DOM has finished rendering. If our layout engine is still painting, we
      // will wait for MozAfterPaint event to be fired.
      let actor = thisWindow.windowGlobalChild.getActor("Printing");
      let webProgressListener = {
        onStateChange(webProgress, req, flags) {
          if (flags & Ci.nsIWebProgressListener.STATE_STOP) {
            webProgress.removeProgressListener(webProgressListener);
            let domUtils = contentWindow.windowUtils;
            // Here we tell the parent that we have parsed the document successfully
            // using ReaderMode primitives and we are able to enter on preview mode.
            if (domUtils.isMozAfterPaintPending) {
              let onPaint = function () {
                contentWindow.removeEventListener("MozAfterPaint", onPaint);
                actor.sendAsyncMessage("Printing:Preview:ReaderModeReady");
                resolve();
              };
              contentWindow.addEventListener("MozAfterPaint", onPaint);
              // This timer is needed for when display list invalidation doesn't invalidate.
              lazy.setTimeout(() => {
                contentWindow.removeEventListener("MozAfterPaint", onPaint);
                actor.sendAsyncMessage("Printing:Preview:ReaderModeReady");
                resolve();
              }, 100);
            } else {
              actor.sendAsyncMessage("Printing:Preview:ReaderModeReady");
              resolve();
            }
          }
        },

        QueryInterface: ChromeUtils.generateQI([
          "nsIWebProgressListener",
          "nsISupportsWeakReference",
          "nsIObserver",
        ]),
      };

      // Here we QI the docShell into a nsIWebProgress passing our web progress listener in.
      let webProgress = thisWindow.docShell
        .QueryInterface(Ci.nsIInterfaceRequestor)
        .getInterface(Ci.nsIWebProgress);
      webProgress.addProgressListener(
        webProgressListener,
        Ci.nsIWebProgress.NOTIFY_STATE_REQUEST
      );

      let document = thisWindow.document;
      document.head.innerHTML = "";

      // Set base URI of document. Print preview code will read this value to
      // populate the URL field in print settings so that it doesn't show
      // "about:blank" as its URI.
      let headBaseElement = document.createElement("base");
      headBaseElement.setAttribute("href", URL);
      document.head.appendChild(headBaseElement);

      // Create link element referencing aboutReader.css and append it to head
      let headStyleElement = document.createElement("link");
      headStyleElement.setAttribute("rel", "stylesheet");
      headStyleElement.setAttribute(
        "href",
        "chrome://global/skin/aboutReader.css"
      );
      headStyleElement.setAttribute("type", "text/css");
      document.head.appendChild(headStyleElement);

      // Create link element referencing simplifyMode.css and append it to head
      headStyleElement = document.createElement("link");
      headStyleElement.setAttribute("rel", "stylesheet");
      headStyleElement.setAttribute(
        "href",
        "chrome://global/content/simplifyMode.css"
      );
      headStyleElement.setAttribute("type", "text/css");
      document.head.appendChild(headStyleElement);

      document.body.innerHTML = "";

      // Create container div (main element) and append it to body
      let containerElement = document.createElement("div");
      containerElement.setAttribute("class", "container");
      document.body.appendChild(containerElement);

      // Reader Mode might return null if there's a failure when parsing the document.
      // We'll render the error message for the Simplify Page document when that happens.
      if (article) {
        // Set title of document
        document.title = article.title;

        // Create header div and append it to container
        let headerElement = document.createElement("div");
        headerElement.setAttribute("class", "reader-header");
        headerElement.setAttribute("class", "header");
        containerElement.appendChild(headerElement);

        // Jam the article's title and byline into header div
        let titleElement = document.createElement("h1");
        titleElement.setAttribute("class", "reader-title");
        titleElement.textContent = article.title;
        headerElement.appendChild(titleElement);

        let bylineElement = document.createElement("div");
        bylineElement.setAttribute("class", "reader-credits credits");
        bylineElement.textContent = article.byline;
        headerElement.appendChild(bylineElement);

        // Display header element
        headerElement.style.display = "block";

        // Create content div and append it to container
        let contentElement = document.createElement("div");
        contentElement.setAttribute("class", "content");
        containerElement.appendChild(contentElement);

        // Jam the article's content into content div
        let readerContent = document.createElement("div");
        readerContent.setAttribute("class", "moz-reader-content");
        contentElement.appendChild(readerContent);

        let articleUri = Services.io.newURI(article.url);
        let parserUtils = Cc["@mozilla.org/parserutils;1"].getService(
          Ci.nsIParserUtils
        );
        let contentFragment = parserUtils.parseFragment(
          article.content,
          Ci.nsIParserUtils.SanitizerDropForms |
            Ci.nsIParserUtils.SanitizerAllowStyle,
          false,
          articleUri,
          readerContent
        );

        readerContent.appendChild(contentFragment);

        // Display reader content element
        readerContent.style.display = "block";
      } else {
        const l10n = new Localization(["toolkit/about/aboutReader.ftl"], true);
        const errorMessage = l10n.formatValueSync("about-reader-load-error");

        document.title = errorMessage;

        // Create reader message div and append it to body
        let readerMessageElement = document.createElement("div");
        readerMessageElement.setAttribute("class", "reader-message");
        readerMessageElement.textContent = errorMessage;
        containerElement.appendChild(readerMessageElement);

        // Display reader message element
        readerMessageElement.style.display = "block";
      }
    });
  }

  updateCurrentPage() {
    let cv = this.docShell.docViewer;
    cv.QueryInterface(Ci.nsIWebBrowserPrint);
    this.sendAsyncMessage("Printing:Preview:CurrentPage", {
      currentPage: cv.printPreviewCurrentPageNumber,
    });
  }

  navigate(navType, pageNum) {
    let cv = this.docShell.docViewer;
    cv.QueryInterface(Ci.nsIWebBrowserPrint);
    cv.printPreviewScrollToPage(navType, pageNum);
  }
}
PK
!<�ˑ��actors/PrintingParent.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class PrintingParent extends JSWindowActorParent {
  receiveMessage(message) {
    let browser = this.browsingContext.top.embedderElement;

    if (message.name == "Printing:Error") {
      browser.ownerGlobal.PrintUtils._displayPrintingError(
        message.data.nsresult,
        message.data.isPrinting,
        browser
      );
    } else if (message.name == "Printing:Preview:CurrentPage") {
      browser.setAttribute("current-page", message.data.currentPage);
    }

    return undefined;
  }
}
PK
!<yg
�zz%actors/PrintingSelectionChild.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class PrintingSelectionChild extends JSWindowActorChild {
  receiveMessage(message) {
    switch (message.name) {
      case "PrintingSelection:HasSelection":
        return this.hasSelection();
    }

    return undefined;
  }

  hasSelection() {
    let selection = this.browsingContext.associatedWindow?.getSelection();
    return selection && selection.type == "Range";
  }
}
PK
!<��5��'actors/PurgeSessionHistoryChild.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class PurgeSessionHistoryChild extends JSWindowActorChild {
  receiveMessage(message) {
    if (message.name != "Browser:PurgeSessionHistory") {
      return;
    }
    let sessionHistory = this.docShell.QueryInterface(
      Ci.nsIWebNavigation
    ).sessionHistory;
    if (!sessionHistory) {
      return;
    }

    // place the entry at current index at the end of the history list, so it won't get removed
    if (sessionHistory.index < sessionHistory.count - 1) {
      let legacy = sessionHistory.legacySHistory;
      let indexEntry = legacy.getEntryAtIndex(sessionHistory.index);
      indexEntry.QueryInterface(Ci.nsISHEntry);
      legacy.addEntry(indexEntry, true);
    }

    let purge = sessionHistory.count;
    if (this.document.location.href != "about:blank") {
      --purge; // Don't remove the page the user's staring at from shistory
    }

    if (purge > 0) {
      sessionHistory.legacySHistory.purgeHistory(purge);
    }
  }
}
PK
!</O9���actors/RemotePageChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * RemotePageChild is a base class for an unprivileged internal page, typically
 * an about: page. A specific implementation should subclass the RemotePageChild
 * actor with a more specific actor for that page. Typically, the child is not
 * needed, but the parent actor will respond to messages and provide results
 * directly to the page.
 */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  AsyncPrefs: "resource://gre/modules/AsyncPrefs.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
  RemotePageAccessManager:
    "resource://gre/modules/RemotePageAccessManager.sys.mjs",
});

export class RemotePageChild extends JSWindowActorChild {
  actorCreated() {
    this.listeners = new Map();
    this.exportBaseFunctions();
  }

  exportBaseFunctions() {
    const exportableFunctions = [
      "RPMSendAsyncMessage",
      "RPMSendQuery",
      "RPMAddMessageListener",
      "RPMRemoveMessageListener",
      "RPMGetIntPref",
      "RPMGetStringPref",
      "RPMGetBoolPref",
      "RPMSetPref",
      "RPMGetFormatURLPref",
      "RPMIsWindowPrivate",
    ];

    this.exportFunctions(exportableFunctions);
  }

  /**
   * Exports a list of functions to be accessible by the privileged page.
   * Subclasses may call this function to add functions that are specific
   * to a page. When the page calls a function, a function with the same
   * name is called within the child actor.
   *
   * Only functions that appear in the whitelist in the
   * RemotePageAccessManager for that page will be exported.
   *
   * @param array of function names.
   */
  exportFunctions(functions) {
    let document = this.document;
    let principal = document.nodePrincipal;

    // If there is no content principal, don't export any functions.
    if (!principal) {
      return;
    }

    let window = this.contentWindow;

    for (let fnname of functions) {
      let allowAccess = lazy.RemotePageAccessManager.checkAllowAccessToFeature(
        principal,
        fnname,
        document
      );

      if (allowAccess) {
        // Wrap each function in an access checking function.
        function accessCheckedFn(...args) {
          this.checkAllowAccess(fnname, args[0]);
          return this[fnname](...args);
        }

        Cu.exportFunction(accessCheckedFn.bind(this), window, {
          defineAs: fnname,
        });
      }
    }
  }

  handleEvent() {
    // Do nothing. The DOMDocElementInserted event is just used to create
    // the actor.
  }

  receiveMessage(messagedata) {
    let message = {
      name: messagedata.name,
      data: messagedata.data,
    };

    let listeners = this.listeners.get(message.name);
    if (!listeners) {
      return;
    }

    let clonedMessage = Cu.cloneInto(message, this.contentWindow);
    for (let listener of listeners.values()) {
      try {
        listener(clonedMessage);
      } catch (e) {
        console.error(e);
      }
    }
  }

  wrapPromise(promise) {
    return new this.contentWindow.Promise((resolve, reject) =>
      promise.then(resolve, reject)
    );
  }

  /**
   * Returns true if a feature cannot be accessed by the current page.
   * Throws an exception if the feature may not be accessed.

   * @param aDocument child process document to call from
   * @param aFeature to feature to check access to
   * @param aValue value that must be included with that feature's whitelist
   * @returns true if access is allowed or throws an exception otherwise
   */
  checkAllowAccess(aFeature, aValue) {
    let doc = this.document;
    if (!lazy.RemotePageAccessManager.checkAllowAccess(doc, aFeature, aValue)) {
      throw new Error(
        "RemotePageAccessManager does not allow access to " + aFeature
      );
    }

    return true;
  }

  addPage(aUrl, aFunctionMap) {
    lazy.RemotePageAccessManager.addPage(aUrl, aFunctionMap);
  }

  // Implementation of functions that are exported into the page.

  RPMSendAsyncMessage(aName, aData = null) {
    this.sendAsyncMessage(aName, aData);
  }

  RPMSendQuery(aName, aData = null) {
    return this.wrapPromise(
      new Promise(resolve => {
        this.sendQuery(aName, aData).then(result => {
          resolve(Cu.cloneInto(result, this.contentWindow));
        });
      })
    );
  }

  /**
   * Adds a listener for messages. Many callbacks can be registered for the
   * same message if necessary. An attempt to register the same callback for the
   * same message twice will be ignored. When called the callback is passed an
   * object with these properties:
   *   name:   The message name
   *   data:   Any data sent with the message
   */
  RPMAddMessageListener(aName, aCallback) {
    if (!this.listeners.has(aName)) {
      this.listeners.set(aName, new Set([aCallback]));
    } else {
      this.listeners.get(aName).add(aCallback);
    }
  }

  /**
   * Removes a listener for messages.
   */
  RPMRemoveMessageListener(aName, aCallback) {
    if (!this.listeners.has(aName)) {
      return;
    }

    this.listeners.get(aName).delete(aCallback);
  }

  RPMGetIntPref(aPref, defaultValue) {
    // Only call with a default value if it's defined, to be able to throw
    // errors for non-existent prefs.
    if (defaultValue !== undefined) {
      return Services.prefs.getIntPref(aPref, defaultValue);
    }
    return Services.prefs.getIntPref(aPref);
  }

  RPMGetStringPref(aPref) {
    return Services.prefs.getStringPref(aPref);
  }

  RPMGetBoolPref(aPref, defaultValue) {
    // Only call with a default value if it's defined, to be able to throw
    // errors for non-existent prefs.
    if (defaultValue !== undefined) {
      return Services.prefs.getBoolPref(aPref, defaultValue);
    }
    return Services.prefs.getBoolPref(aPref);
  }

  RPMSetPref(aPref, aVal) {
    return this.wrapPromise(lazy.AsyncPrefs.set(aPref, aVal));
  }

  RPMGetFormatURLPref(aFormatURL) {
    return Services.urlFormatter.formatURLPref(aFormatURL);
  }

  RPMIsWindowPrivate() {
    return lazy.PrivateBrowsingUtils.isContentWindowPrivate(this.contentWindow);
  }
}
PK
!<�͵�.�.$actors/ReportBrokenSiteChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const SCREENSHOT_FORMAT = { format: "jpeg", quality: 75 };

function RunScriptInFrame(win, script) {
  const contentPrincipal = win.document.nodePrincipal;
  const sandbox = Cu.Sandbox([contentPrincipal], {
    sandboxName: "Report Broken Site webcompat.com helper",
    sandboxPrototype: win,
    sameZoneAs: win,
    originAttributes: contentPrincipal.originAttributes,
  });
  return Cu.evalInSandbox(script, sandbox, null, "sandbox eval code", 1);
}

class ConsoleLogHelper {
  static PREVIEW_MAX_ITEMS = 10;
  static LOG_LEVELS = ["debug", "info", "warn", "error"];

  #windowId = undefined;

  constructor(windowId) {
    this.#windowId = windowId;
  }

  getLoggedMessages(alsoIncludePrivate = true) {
    return this.getConsoleAPIMessages().concat(
      this.getScriptErrors(alsoIncludePrivate)
    );
  }

  getConsoleAPIMessages() {
    const ConsoleAPIStorage = Cc[
      "@mozilla.org/consoleAPI-storage;1"
    ].getService(Ci.nsIConsoleAPIStorage);
    let messages = ConsoleAPIStorage.getEvents(this.#windowId);
    return messages.map(evt => {
      const { columnNumber, filename, level, lineNumber, timeStamp } = evt;

      const args = [];
      for (const arg of evt.arguments) {
        args.push(this.#getArgs(arg));
      }

      const message = {
        level,
        log: args,
        uri: filename,
        pos: `${lineNumber}:${columnNumber}`,
      };

      return { timeStamp, message };
    });
  }

  getScriptErrors(alsoIncludePrivate) {
    const messages = Services.console.getMessageArray();
    return messages
      .filter(message => {
        if (message instanceof Ci.nsIScriptError) {
          if (!alsoIncludePrivate && message.isFromPrivateWindow) {
            return false;
          }
          if (this.#windowId && this.#windowId !== message.innerWindowID) {
            return false;
          }
          return true;
        }

        // If this is not an nsIScriptError and we need to do window-based
        // filtering we skip this message.
        return false;
      })
      .map(error => {
        const {
          timeStamp,
          errorMessage,
          sourceName,
          lineNumber,
          columnNumber,
          logLevel,
        } = error;
        const message = {
          level: ConsoleLogHelper.LOG_LEVELS[logLevel],
          log: [errorMessage],
          uri: sourceName,
          pos: `${lineNumber}:${columnNumber}`,
        };
        return { timeStamp, message };
      });
  }

  #getPreview(value) {
    switch (typeof value) {
      case "symbol":
        return value.toString();

      case "function":
        return "function ()";

      case "object":
        if (value === null) {
          return null;
        }
        if (Array.isArray(value)) {
          return `(${value.length})[...]`;
        }
        return "{...}";

      case "undefined":
        return "undefined";

      default:
        try {
          structuredClone(value);
        } catch (_) {
          return `${value}` || "?";
        }
        return value;
    }
  }

  #getArrayPreview(arr) {
    const preview = [];
    let count = 0;
    for (const value of arr) {
      if (++count > ConsoleLogHelper.PREVIEW_MAX_ITEMS) {
        break;
      }
      preview.push(this.#getPreview(value));
    }

    return preview;
  }

  #getObjectPreview(obj) {
    const preview = {};
    let count = 0;
    for (const key of Object.keys(obj)) {
      if (++count > ConsoleLogHelper.PREVIEW_MAX_ITEMS) {
        break;
      }
      preview[key] = this.#getPreview(obj[key]);
    }

    return preview;
  }

  #getArgs(value) {
    if (typeof value === "object" && value !== null) {
      if (Array.isArray(value)) {
        return this.#getArrayPreview(value);
      }
      return this.#getObjectPreview(value);
    }

    return this.#getPreview(value);
  }
}

const FrameworkDetector = {
  hasFastClickPageScript(window) {
    if (window.FastClick) {
      return true;
    }

    for (const property in window) {
      try {
        const proto = window[property].prototype;
        if (proto && proto.needsClick) {
          return true;
        }
      } catch (_) {}
    }

    return false;
  },

  hasMobifyPageScript(window) {
    return !!window.Mobify?.Tag;
  },

  hasMarfeelPageScript(window) {
    return !!window.marfeel;
  },

  checkWindow(window) {
    const script = `
      (function() {
        function ${FrameworkDetector.hasFastClickPageScript};
        function ${FrameworkDetector.hasMobifyPageScript};
        function ${FrameworkDetector.hasMarfeelPageScript};
        const win = window.wrappedJSObject || window;
        return {
          fastclick: hasFastClickPageScript(win),
          mobify: hasMobifyPageScript(win),
          marfeel: hasMarfeelPageScript(win),
        }
      })();
    `;
    return RunScriptInFrame(window, script);
  },
};

export class ReportBrokenSiteChild extends JSWindowActorChild {
  #getWebCompatInfo(docShell) {
    return Promise.all([
      this.#getConsoleLogs(docShell),
      this.sendQuery("GetWebcompatInfoFromParentProcess", SCREENSHOT_FORMAT),
    ]).then(([consoleLog, infoFromParent]) => {
      const { antitracking, browser, screenshot } = infoFromParent;

      const win = docShell.domWindow;

      const devicePixelRatio = win.devicePixelRatio;
      const frameworks = FrameworkDetector.checkWindow(win);
      const { languages, userAgent } = win.navigator;

      if (browser.platform.name !== "linux") {
        delete browser.prefs["layers.acceleration.force-enabled"];
      }

      return {
        antitracking,
        browser,
        consoleLog,
        devicePixelRatio,
        frameworks,
        languages,
        screenshot,
        url: win.location.href,
        userAgent,
      };
    });
  }

  async #getConsoleLogs() {
    return this.#getLoggedMessages()
      .flat()
      .sort((a, b) => a.timeStamp - b.timeStamp)
      .map(m => m.message);
  }

  #getLoggedMessages(alsoIncludePrivate = false) {
    const windowId = this.contentWindow.windowGlobalChild.innerWindowId;
    const helper = new ConsoleLogHelper(windowId, alsoIncludePrivate);
    return helper.getLoggedMessages();
  }

  #formatReportDataForWebcompatCom({
    reason,
    description,
    reportUrl,
    reporterConfig,
    webcompatInfo,
  }) {
    const extra_labels = reporterConfig?.extra_labels || [];

    const message = Object.assign({}, reporterConfig, {
      url: reportUrl,
      category: reason,
      description,
      details: {},
      extra_labels,
    });

    const payload = {
      message,
    };

    if (webcompatInfo) {
      const {
        antitracking,
        browser,
        devicePixelRatio,
        consoleLog,
        frameworks,
        languages,
        screenshot,
        url,
        userAgent,
      } = webcompatInfo;

      const {
        blockList,
        isPrivateBrowsing,
        hasMixedActiveContentBlocked,
        hasMixedDisplayContentBlocked,
        hasTrackingContentBlocked,
      } = antitracking;

      message.blockList = blockList;

      const { app, graphics, locales, prefs, platform, security } = browser;

      const {
        applicationName,
        buildId,
        defaultUserAgent,
        updateChannel,
        version,
      } = app;

      const {
        fissionEnabled,
        memoryMB,
        osArchitecture,
        osName,
        osVersion,
        device,
        isTablet,
      } = platform;

      const additionalData = {
        applicationName,
        blockList,
        buildId,
        devicePixelRatio,
        finalUserAgent: userAgent,
        fissionEnabled,
        gfxData: graphics,
        hasMixedActiveContentBlocked,
        hasMixedDisplayContentBlocked,
        hasTrackingContentBlocked,
        isPB: isPrivateBrowsing,
        languages,
        locales,
        memoryMB,
        osArchitecture,
        osName,
        osVersion,
        prefs,
        version,
      };
      if (security !== undefined && Object.keys(security).length) {
        additionalData.sec = security;
      }
      if (device !== undefined) {
        additionalData.device = device;
      }
      if (isTablet !== undefined) {
        additionalData.isTablet = isTablet;
      }

      const specialPrefs = {};
      for (const pref of [
        "layers.acceleration.force-enabled",
        "gfx.webrender.software",
      ]) {
        specialPrefs[pref] = prefs[pref];
      }

      const details = Object.assign(message.details, specialPrefs, {
        additionalData,
        blockList,
        channel: updateChannel,
        defaultUserAgent,
        hasTouchScreen: browser.graphics.hasTouchScreen,
      });

      // If the user enters a URL unrelated to the current tab,
      // don't bother sending a screnshot or logs/etc
      let sendRecordedPageSpecificDetails = false;
      try {
        const givenUri = new URL(reportUrl);
        const recordedUri = new URL(url);
        sendRecordedPageSpecificDetails =
          givenUri.origin == recordedUri.origin &&
          givenUri.pathname == recordedUri.pathname;
      } catch (_) {}

      if (sendRecordedPageSpecificDetails) {
        payload.screenshot = screenshot;

        details.consoleLog = consoleLog;
        details.frameworks = frameworks;
        details["mixed active content blocked"] =
          antitracking.hasMixedActiveContentBlocked;
        details["mixed passive content blocked"] =
          antitracking.hasMixedDisplayContentBlocked;
        details["tracking content blocked"] =
          antitracking.hasTrackingContentBlocked
            ? `true (${antitracking.blockList})`
            : "false";

        if (antitracking.hasTrackingContentBlocked) {
          extra_labels.push(
            `type-tracking-protection-${antitracking.blockList}`
          );
        }

        for (const [framework, active] of Object.entries(frameworks)) {
          if (!active) {
            continue;
          }
          details[framework] = true;
          extra_labels.push(`type-${framework}`);
        }

        extra_labels.sort();
      }
    }

    return payload;
  }

  #stripNonASCIIChars(str) {
    // eslint-disable-next-line no-control-regex
    return str.replace(/[^\x00-\x7F]/g, "");
  }

  async receiveMessage(msg) {
    const { docShell } = this;
    switch (msg.name) {
      case "SendDataToWebcompatCom": {
        const win = docShell.domWindow;
        const expectedEndpoint = msg.data.endpointUrl;
        if (win.location.href == expectedEndpoint) {
          // Ensure that the tab has fully loaded and is waiting for messages
          const onLoad = () => {
            const payload = this.#formatReportDataForWebcompatCom(msg.data);
            const json = this.#stripNonASCIIChars(JSON.stringify(payload));
            const expectedOrigin = JSON.stringify(
              new URL(expectedEndpoint).origin
            );
            // webcompat.com checks that the message comes from its own origin
            const script = `
            const wrtReady = window.wrappedJSObject?.wrtReady;
            if (wrtReady) {
              console.info("Report Broken Site is waiting");
            }
            Promise.resolve(wrtReady).then(() => {
              console.debug(${json});
              postMessage(${json}, ${expectedOrigin})
            });`;
            RunScriptInFrame(win, script);
          };
          if (win.document.readyState == "complete") {
            onLoad();
          } else {
            win.addEventListener("load", onLoad, { once: true });
          }
        }
        return null;
      }
      case "GetWebCompatInfo": {
        return this.#getWebCompatInfo(docShell);
      }
      case "GetConsoleLog": {
        return this.#getLoggedMessages();
      }
    }
    return null;
  }
}
PK
!<�z6�P"P"%actors/ReportBrokenSiteParent.sys.mjs/* vim: set ts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Troubleshoot } from "resource://gre/modules/Troubleshoot.sys.mjs";

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

export class ReportBrokenSiteParent extends JSWindowActorParent {
  #getAntitrackingBlockList() {
    // If content-track-digest256 is in the tracking table,
    // the user has enabled the strict list.
    const trackingTable = Services.prefs.getCharPref(
      "urlclassifier.trackingTable"
    );
    return trackingTable.includes("content") ? "strict" : "basic";
  }

  #getAntitrackingInfo(browsingContext) {
    return {
      blockList: this.#getAntitrackingBlockList(),
      isPrivateBrowsing: browsingContext.usePrivateBrowsing,
      hasTrackingContentBlocked: !!(
        browsingContext.currentWindowGlobal.contentBlockingEvents &
        Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT
      ),
      hasMixedActiveContentBlocked: !!(
        browsingContext.secureBrowserUI.state &
        Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT
      ),
      hasMixedDisplayContentBlocked: !!(
        browsingContext.secureBrowserUI.state &
        Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT
      ),
    };
  }

  #parseGfxInfo(info) {
    const get = name => {
      try {
        return info[name];
      } catch (e) {}
      return undefined;
    };

    const clean = rawObj => {
      const obj = JSON.parse(JSON.stringify(rawObj));
      if (!Object.keys(obj).length) {
        return undefined;
      }
      return obj;
    };

    const cleanDevice = (vendorID, deviceID, subsysID) => {
      return clean({ vendorID, deviceID, subsysID });
    };

    const d1 = cleanDevice(
      get("adapterVendorID"),
      get("adapterDeviceID"),
      get("adapterSubsysID")
    );
    const d2 = cleanDevice(
      get("adapterVendorID2"),
      get("adapterDeviceID2"),
      get("adapterSubsysID2")
    );
    const devices = (get("isGPU2Active") ? [d2, d1] : [d1, d2]).filter(
      v => v !== undefined
    );

    return clean({
      direct2DEnabled: get("direct2DEnabled"),
      directWriteEnabled: get("directWriteEnabled"),
      directWriteVersion: get("directWriteVersion"),
      hasTouchScreen: info.ApzTouchInput == 1,
      clearTypeParameters: get("clearTypeParameters"),
      targetFrameRate: get("targetFrameRate"),
      devices,
    });
  }

  #parseCodecSupportInfo(codecSupportInfo) {
    if (!codecSupportInfo) {
      return undefined;
    }

    const codecs = {};
    for (const item of codecSupportInfo.split("\n")) {
      const [codec, ...types] = item.split(" ");
      if (!codecs[codec]) {
        codecs[codec] = { hardware: false, software: false };
      }
      codecs[codec].software ||= types.includes("SW");
      codecs[codec].hardware ||= types.includes("HW");
    }
    return codecs;
  }

  #parseFeatureLog(featureLog = {}) {
    const { features } = featureLog;
    if (!features) {
      return undefined;
    }

    const parsedFeatures = {};
    for (let { name, log, status } of features) {
      for (const item of log.reverse()) {
        if (!item.failureId || item.status != status) {
          continue;
        }
        status = `${status} (${item.message || item.failureId})`;
      }
      parsedFeatures[name] = status;
    }
    return parsedFeatures;
  }

  #getGraphicsInfo(troubleshoot) {
    const { graphics, media } = troubleshoot;
    const { featureLog } = graphics;
    const data = this.#parseGfxInfo(graphics);
    data.drivers = [
      {
        renderer: graphics.webgl1Renderer,
        version: graphics.webgl1Version,
      },
      {
        renderer: graphics.webgl2Renderer,
        version: graphics.webgl2Version,
      },
    ].filter(({ version }) => version && version != "-");

    data.codecSupport = this.#parseCodecSupportInfo(media.codecSupportInfo);
    data.features = this.#parseFeatureLog(featureLog);

    const gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo);
    data.monitors = gfxInfo.getMonitors();

    return data;
  }

  #getAppInfo(troubleshootingInfo) {
    const { application } = troubleshootingInfo;
    return {
      applicationName: application.name,
      buildId: application.buildID,
      defaultUserAgent: application.userAgent,
      updateChannel: application.updateChannel,
      version: application.version,
    };
  }

  #getSysinfoProperty(propertyName, defaultValue) {
    try {
      return Services.sysinfo.getProperty(propertyName);
    } catch (e) {}
    return defaultValue;
  }

  #getPrefs() {
    const prefs = {};
    for (const name of [
      "layers.acceleration.force-enabled",
      "gfx.webrender.software",
      "browser.opaqueResponseBlocking",
      "extensions.InstallTrigger.enabled",
      "privacy.resistFingerprinting",
      "privacy.globalprivacycontrol.enabled",
    ]) {
      prefs[name] = Services.prefs.getBoolPref(name, undefined);
    }
    const cookieBehavior = "network.cookie.cookieBehavior";
    prefs[cookieBehavior] = Services.prefs.getIntPref(cookieBehavior, -1);
    return prefs;
  }

  async #getPlatformInfo(troubleshootingInfo) {
    const { application } = troubleshootingInfo;
    const { memorySizeBytes, fissionAutoStart } = application;

    let memoryMB = memorySizeBytes;
    if (memoryMB) {
      memoryMB = Math.round(memoryMB / 1024 / 1024);
    }

    const info = {
      fissionEnabled: fissionAutoStart,
      memoryMB,
      osArchitecture: this.#getSysinfoProperty("arch", null),
      osName: this.#getSysinfoProperty("name", null),
      osVersion: this.#getSysinfoProperty("version", null),
      name: AppConstants.platform,
    };
    if (info.os === "android") {
      info.device = this.#getSysinfoProperty("device", null);
      info.isTablet = this.#getSysinfoProperty("tablet", false);
    }
    if (
      info.osName == "Windows_NT" &&
      (await Services.sysinfo.processInfo).isWindowsSMode
    ) {
      info.osVersion += " S";
    }
    return info;
  }

  #getSecurityInfo(troubleshootingInfo) {
    const result = {};
    for (const [k, v] of Object.entries(troubleshootingInfo.securitySoftware)) {
      result[k.replace("registered", "").toLowerCase()] = v
        ? v.split(";")
        : null;
    }

    // Right now, security data is only available for Windows builds, and
    // we might as well not return anything at all if no data is available.
    if (!Object.values(result).filter(e => e).length) {
      return undefined;
    }

    return result;
  }

  async #getBrowserInfo() {
    const troubleshootingInfo = await Troubleshoot.snapshot();
    return {
      app: this.#getAppInfo(troubleshootingInfo),
      graphics: this.#getGraphicsInfo(troubleshootingInfo),
      locales: troubleshootingInfo.intl.localeService.available,
      prefs: this.#getPrefs(),
      platform: await this.#getPlatformInfo(troubleshootingInfo),
      security: this.#getSecurityInfo(troubleshootingInfo),
    };
  }

  async #getScreenshot(browsingContext, format, quality) {
    const zoom = browsingContext.fullZoom;
    const scale = browsingContext.topChromeWindow?.devicePixelRatio || 1;
    const wgp = browsingContext.currentWindowGlobal;

    const image = await wgp.drawSnapshot(
      undefined, // rect
      scale * zoom,
      "white",
      undefined // resetScrollPosition
    );

    const canvas = new OffscreenCanvas(image.width, image.height);

    const ctx = canvas.getContext("bitmaprenderer", { alpha: false });
    ctx.transferFromImageBitmap(image);

    const blob = await canvas.convertToBlob({
      type: `image/${format}`,
      quality: quality / 100,
    });

    const dataURL = await new Promise((resolve, reject) => {
      let reader = new FileReader();
      reader.onload = () => resolve(reader.result);
      reader.onerror = () => reject(reader.error);
      reader.readAsDataURL(blob);
    });

    return dataURL;
  }

  async receiveMessage(msg) {
    switch (msg.name) {
      case "GetWebcompatInfoFromParentProcess": {
        const { format, quality } = msg.data;
        const screenshot = await this.#getScreenshot(
          msg.target.browsingContext,
          format,
          quality
        ).catch(e => {
          console.error("Report Broken Site: getting a screenshot failed", e);
          return Promise.resolve(undefined);
        });

        return {
          antitracking: this.#getAntitrackingInfo(msg.target.browsingContext),
          browser: await this.#getBrowserInfo(),
          screenshot,
        };
      }
    }
    return null;
  }
}
PK
!<�Tb<b<actors/SelectChild.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  LayoutUtils: "resource://gre/modules/LayoutUtils.sys.mjs",
});

const kStateActive = 0x00000001; // ElementState::ACTIVE
const kStateHover = 0x00000004; // ElementState::HOVER

// Duplicated in SelectParent.sys.mjs
// Please keep these lists in sync.
const SUPPORTED_OPTION_OPTGROUP_PROPERTIES = [
  "direction",
  "color",
  "background-color",
  "text-shadow",
  "text-transform",
  "font-family",
  "font-weight",
  "font-size",
  "font-style",
];

const SUPPORTED_SELECT_PROPERTIES = [
  ...SUPPORTED_OPTION_OPTGROUP_PROPERTIES,
  "scrollbar-width",
  "scrollbar-color",
];

// A process global state for whether or not content thinks
// that a <select> dropdown is open or not. This is managed
// entirely within this module, and is read-only accessible
// via SelectContentHelper.open.
var gOpen = false;

export var SelectContentHelper = function (aElement, aOptions, aActor) {
  this.element = aElement;
  this.initialSelection = aElement[aElement.selectedIndex] || null;
  this.actor = aActor;
  this.closedWithClickOn = false;
  this.isOpenedViaTouch = aOptions.isOpenedViaTouch;
  this._closeAfterBlur = true;
  this._pseudoStylesSetup = false;
  this._lockedDescendants = null;
  this.init();
  this.showDropDown();
  this._updateTimer = new lazy.DeferredTask(this._update.bind(this), 0);
};

Object.defineProperty(SelectContentHelper, "open", {
  get() {
    return gOpen;
  },
});

SelectContentHelper.prototype = {
  init() {
    let win = this.element.ownerGlobal;
    win.addEventListener("pagehide", this, { mozSystemGroup: true });
    this.element.addEventListener("blur", this, { mozSystemGroup: true });
    this.element.addEventListener("transitionend", this, {
      mozSystemGroup: true,
    });
    let MutationObserver = this.element.ownerGlobal.MutationObserver;
    this.mut = new MutationObserver(() => {
      // Something changed the <select> while it was open, so
      // we'll poke a DeferredTask to update the parent sometime
      // in the very near future.
      this._updateTimer.arm();
    });
    this.mut.observe(this.element, {
      childList: true,
      subtree: true,
      attributes: true,
    });

    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "disablePopupAutohide",
      "ui.popup.disable_autohide",
      false
    );
  },

  uninit() {
    this.element.openInParentProcess = false;
    let win = this.element.ownerGlobal;
    win.removeEventListener("pagehide", this, { mozSystemGroup: true });
    this.element.removeEventListener("blur", this, { mozSystemGroup: true });
    this.element.removeEventListener("transitionend", this, {
      mozSystemGroup: true,
    });
    this.element = null;
    this.actor = null;
    this.mut.disconnect();
    this._updateTimer.disarm();
    this._updateTimer = null;
    gOpen = false;
  },

  showDropDown() {
    this.element.openInParentProcess = true;
    this._setupPseudoClassStyles();
    let rect = this._getBoundingContentRect();
    let computedStyles = getComputedStyles(this.element);
    let options = this._buildOptionList();
    let defaultStyles = this.element.ownerGlobal.getDefaultComputedStyle(
      this.element
    );
    this.actor.sendAsyncMessage("Forms:ShowDropDown", {
      isOpenedViaTouch: this.isOpenedViaTouch,
      options,
      rect,
      custom: !this.element.nodePrincipal.isSystemPrincipal,
      selectedIndex: this.element.selectedIndex,
      isDarkBackground: ChromeUtils.isDarkBackground(this.element),
      style: supportedStyles(computedStyles, SUPPORTED_SELECT_PROPERTIES),
      defaultStyle: supportedStyles(defaultStyles, SUPPORTED_SELECT_PROPERTIES),
    });
    this._clearPseudoClassStyles();
    gOpen = true;
  },

  _setupPseudoClassStyles() {
    if (this._pseudoStylesSetup) {
      throw new Error("pseudo styles must not be set up yet");
    }
    // Do all of the things that change style at once, before we read
    // any styles.
    this._pseudoStylesSetup = true;
    InspectorUtils.addPseudoClassLock(this.element, ":focus");
    let lockedDescendants = (this._lockedDescendants =
      this.element.querySelectorAll(":checked"));
    for (let child of lockedDescendants) {
      // Selected options have the :checked pseudo-class, which
      // we want to disable before calculating the computed
      // styles since the user agent styles alter the styling
      // based on :checked.
      InspectorUtils.addPseudoClassLock(child, ":checked", false);
    }
  },

  _clearPseudoClassStyles() {
    if (!this._pseudoStylesSetup) {
      throw new Error("pseudo styles must be set up already");
    }
    // Undo all of the things that change style at once, after we're
    // done reading styles.
    InspectorUtils.clearPseudoClassLocks(this.element);
    let lockedDescendants = this._lockedDescendants;
    for (let child of lockedDescendants) {
      InspectorUtils.clearPseudoClassLocks(child);
    }
    this._lockedDescendants = null;
    this._pseudoStylesSetup = false;
  },

  _getBoundingContentRect() {
    return lazy.LayoutUtils.getElementBoundingScreenRect(this.element);
  },

  _buildOptionList() {
    if (!this._pseudoStylesSetup) {
      throw new Error("pseudo styles must be set up");
    }
    let uniqueStyles = [];
    let options = buildOptionListForChildren(this.element, uniqueStyles);
    return { options, uniqueStyles };
  },

  _update() {
    // The <select> was updated while the dropdown was open.
    // Let's send up a new list of options.
    // Technically we might not need to set this pseudo-class
    // during _update() since the element should organically
    // have :focus, though it is here for belt-and-suspenders.
    this._setupPseudoClassStyles();
    let computedStyles = getComputedStyles(this.element);
    let defaultStyles = this.element.ownerGlobal.getDefaultComputedStyle(
      this.element
    );
    this.actor.sendAsyncMessage("Forms:UpdateDropDown", {
      options: this._buildOptionList(),
      custom: !this.element.nodePrincipal.isSystemPrincipal,
      selectedIndex: this.element.selectedIndex,
      isDarkBackground: ChromeUtils.isDarkBackground(this.element),
      style: supportedStyles(computedStyles, SUPPORTED_SELECT_PROPERTIES),
      defaultStyle: supportedStyles(defaultStyles, SUPPORTED_SELECT_PROPERTIES),
    });
    this._clearPseudoClassStyles();
  },

  dispatchMouseEvent(win, target, eventName) {
    let dict = {
      view: win,
      bubbles: true,
      cancelable: true,
      composed: true,
    };
    let mouseEvent =
      eventName == "click"
        ? new win.PointerEvent(eventName, dict)
        : new win.MouseEvent(eventName, dict);
    target.dispatchEvent(mouseEvent);
  },

  receiveMessage(message) {
    switch (message.name) {
      case "Forms:SelectDropDownItem":
        this.element.selectedIndex = message.data.value;
        this.closedWithClickOn = !message.data.closedWithEnter;
        break;

      case "Forms:DismissedDropDown": {
        if (!this.element) {
          return;
        }

        let win = this.element.ownerGlobal;

        // Running arbitrary script below (dispatching events for example) can
        // close us, but we should still send events consistently.
        let element = this.element;

        let selectedOption = element.item(element.selectedIndex);

        // For ordering of events, we're using non-e10s as our guide here,
        // since the spec isn't exactly clear. In non-e10s:
        // - If the user clicks on an element in the dropdown, we fire
        //   mousedown, mouseup, input, change, and click events.
        // - If the user uses the keyboard to select an element in the
        //   dropdown, we only fire input and change events.
        // - If the user pressed ESC key or clicks outside the dropdown,
        //   we fire nothing as the selected option is unchanged.
        if (this.closedWithClickOn) {
          this.dispatchMouseEvent(win, selectedOption, "mousedown");
          this.dispatchMouseEvent(win, selectedOption, "mouseup");
        }

        // Clear active document no matter user selects via keyboard or mouse
        InspectorUtils.removeContentState(
          element,
          kStateActive,
          /* aClearActiveDocument */ true
        );

        // Fire input and change events when selected option changes
        {
          let changed = this.initialSelection !== selectedOption;
          let handlingUserInput = win.windowUtils.setHandlingUserInput(changed);
          try {
            element.userFinishedInteracting(changed);
          } finally {
            handlingUserInput.destruct();
          }
        }

        // Fire click event
        if (this.closedWithClickOn) {
          this.dispatchMouseEvent(win, selectedOption, "click");
        }

        this.uninit();
        break;
      }

      case "Forms:MouseOver":
        InspectorUtils.setContentState(this.element, kStateHover);
        break;

      case "Forms:MouseOut":
        InspectorUtils.removeContentState(this.element, kStateHover);
        break;

      case "Forms:MouseUp": {
        let win = this.element.ownerGlobal;
        if (message.data.onAnchor) {
          this.dispatchMouseEvent(win, this.element, "mouseup");
        }
        InspectorUtils.removeContentState(this.element, kStateActive);
        if (message.data.onAnchor) {
          this.dispatchMouseEvent(win, this.element, "click");
        }
        break;
      }

      case "Forms:SearchFocused":
        this._closeAfterBlur = false;
        break;

      case "Forms:BlurDropDown-Pong":
        if (!this._closeAfterBlur || !gOpen) {
          return;
        }
        this.actor.sendAsyncMessage("Forms:HideDropDown", {});
        this.uninit();
        break;
    }
  },

  handleEvent(event) {
    switch (event.type) {
      case "pagehide":
        if (this.element.ownerDocument === event.target) {
          this.actor.sendAsyncMessage("Forms:HideDropDown", {});
          this.uninit();
        }
        break;
      case "blur": {
        if (this.element !== event.target || this.disablePopupAutohide) {
          break;
        }
        this._closeAfterBlur = true;
        // Send a ping-pong message to make sure that we wait for
        // enough cycles to pass from the potential focusing of the
        // search box to disable closing-after-blur.
        this.actor.sendAsyncMessage("Forms:BlurDropDown-Ping", {});
        break;
      }
      case "mozhidedropdown":
        if (this.element === event.target) {
          this.actor.sendAsyncMessage("Forms:HideDropDown", {});
          this.uninit();
        }
        break;
      case "transitionend":
        if (
          this.element === event.target &&
          SUPPORTED_SELECT_PROPERTIES.includes(event.propertyName)
        ) {
          this._updateTimer.arm();
        }
        break;
    }
  },
};

function getComputedStyles(element) {
  return element.ownerGlobal.getComputedStyle(element);
}

function supportedStyles(cs, supportedProps) {
  let styles = {};
  for (let property of supportedProps) {
    if (property == "font-size") {
      let usedSize = cs.usedFontSize;
      if (usedSize >= 0.0) {
        styles[property] = usedSize + "px";
        continue;
      }
    }
    styles[property] = cs.getPropertyValue(property);
  }
  return styles;
}

function supportedStylesEqual(styles, otherStyles) {
  for (let property in styles) {
    if (styles[property] !== otherStyles[property]) {
      return false;
    }
  }
  return true;
}

function uniqueStylesIndex(cs, uniqueStyles) {
  let styles = supportedStyles(cs, SUPPORTED_OPTION_OPTGROUP_PROPERTIES);
  for (let i = uniqueStyles.length; i--; ) {
    if (supportedStylesEqual(uniqueStyles[i], styles)) {
      return i;
    }
  }
  uniqueStyles.push(styles);
  return uniqueStyles.length - 1;
}

function buildOptionListForChildren(node, uniqueStyles) {
  let result = [];

  let lastWasHR = false;
  for (let child of node.children) {
    let className = ChromeUtils.getClassName(child);
    let isOption = className == "HTMLOptionElement";
    let isOptGroup = className == "HTMLOptGroupElement";
    let isHR = className == "HTMLHRElement";
    if (!isOption && !isOptGroup && !isHR) {
      continue;
    }
    if (child.hidden) {
      continue;
    }

    let cs = getComputedStyles(child);

    if (isHR) {
      // https://html.spec.whatwg.org/#the-select-element-2
      // "Each sequence of one or more child hr element siblings may be rendered as a single separator."
      if (lastWasHR) {
        continue;
      }

      let info = {
        index: child.index,
        display: cs.display,
        isHR,
      };

      const defaultHRStyle = node.ownerGlobal.getDefaultComputedStyle(child);
      if (cs.color != defaultHRStyle.color) {
        info.color = cs.color;
      }

      result.push(info);

      lastWasHR = true;
      continue;
    }
    lastWasHR = false;

    // The option code-path should match HTMLOptionElement::GetRenderedLabel.
    let textContent = isOptGroup
      ? child.getAttribute("label")
      : child.label || child.text;
    if (textContent == null) {
      textContent = "";
    }

    let info = {
      index: child.index,
      isOptGroup,
      textContent,
      disabled: child.disabled,
      display: cs.display,
      tooltip: child.title,
      children: isOptGroup
        ? buildOptionListForChildren(child, uniqueStyles)
        : [],
      // Most options have the same style. In order to reduce the size of the
      // IPC message, coalesce them in uniqueStyles.
      styleIndex: uniqueStylesIndex(cs, uniqueStyles),
    };
    result.push(info);
  }
  return result;
}

// Hold the instance of SelectContentHelper created
// when the dropdown list is opened. This variable helps
// re-route the received message from SelectChild to SelectContentHelper object.
let currentSelectContentHelper = new WeakMap();

export class SelectChild extends JSWindowActorChild {
  handleEvent(event) {
    if (SelectContentHelper.open) {
      // The SelectContentHelper object handles captured
      // events when the <select> popup is open.
      let contentHelper = currentSelectContentHelper.get(this);
      if (contentHelper) {
        contentHelper.handleEvent(event);
      }
      return;
    }

    switch (event.type) {
      case "mozshowdropdown": {
        let contentHelper = new SelectContentHelper(
          event.target,
          { isOpenedViaTouch: false },
          this
        );
        currentSelectContentHelper.set(this, contentHelper);
        break;
      }

      case "mozshowdropdown-sourcetouch": {
        let contentHelper = new SelectContentHelper(
          event.target,
          { isOpenedViaTouch: true },
          this
        );
        currentSelectContentHelper.set(this, contentHelper);
        break;
      }
    }
  }

  receiveMessage(message) {
    let contentHelper = currentSelectContentHelper.get(this);
    if (contentHelper) {
      contentHelper.receiveMessage(message);
    }
  }
}
PK
!<�=�Veeactors/SelectParent.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "DOM_FORMS_SELECTSEARCH",
  "dom.forms.selectSearch",
  false
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "CUSTOM_STYLING_ENABLED",
  "dom.forms.select.customstyling",
  false
);

// Minimum elements required to show select search
const SEARCH_MINIMUM_ELEMENTS = 40;

// The properties that we should respect only when the item is not active.
const PROPERTIES_RESET_WHEN_ACTIVE = [
  "color",
  "background-color",
  "text-shadow",
];

// Duplicated in SelectChild.sys.mjs
// Please keep these lists in sync.
const SUPPORTED_OPTION_OPTGROUP_PROPERTIES = [
  "direction",
  "color",
  "background-color",
  "text-shadow",
  "text-transform",
  "font-family",
  "font-weight",
  "font-size",
  "font-style",
];

const SUPPORTED_SELECT_PROPERTIES = [
  ...SUPPORTED_OPTION_OPTGROUP_PROPERTIES,
  "scrollbar-width",
  "scrollbar-color",
];

export var SelectParentHelper = {
  /**
   * `populate` takes the `menulist` element and a list of `items` and generates
   * a popup list of options.
   *
   * If `CUSTOM_STYLING_ENABLED` is set to `true`, the function will also
   * style the select and its popup trying to prevent the text
   * and background to end up in the same color.
   *
   * All `ua*` variables represent the color values for the default colors
   * for their respective form elements used by the user agent.
   * The `select*` variables represent the color values defined for the
   * particular <select> element.
   *
   * The `customoptionstyling` attribute controls the application of
   * `-moz-appearance` on the elements and is disabled if the element is
   * defining its own background-color.
   *
   * @param {Element}        menulist
   * @param {Array<Element>} items
   * @param {Array<Object>}  uniqueItemStyles
   * @param {Number}         selectedIndex
   * @param {Number}         zoom
   * @param {Boolean}        custom
   * @param {Boolean}        isDarkBackground
   * @param {Object}         uaStyle
   * @param {Object}         selectStyle
   */
  populate(
    menulist,
    items,
    uniqueItemStyles,
    selectedIndex,
    zoom,
    custom,
    isDarkBackground,
    uaStyle,
    selectStyle
  ) {
    let doc = menulist.ownerDocument;

    // Clear the current contents of the popup
    let menupopup = menulist.menupopup;
    menupopup.textContent = "";

    let stylesheet = menulist.querySelector("#ContentSelectDropdownStylesheet");
    if (stylesheet) {
      stylesheet.remove();
    }

    menupopup.setAttribute("style", "");
    menupopup.style.colorScheme = isDarkBackground ? "dark" : "light";
    menupopup.style.direction = selectStyle.direction;

    stylesheet = doc.createElementNS("http://www.w3.org/1999/xhtml", "style");
    stylesheet.setAttribute("id", "ContentSelectDropdownStylesheet");
    stylesheet.hidden = true;
    stylesheet = menulist.appendChild(stylesheet);

    let sheet = stylesheet.sheet;

    if (!custom) {
      selectStyle = uaStyle;
    }

    if (selectStyle["background-color"] == "rgba(0, 0, 0, 0)") {
      selectStyle["background-color"] = uaStyle["background-color"];
    }

    if (selectStyle.color == selectStyle["background-color"]) {
      selectStyle.color = uaStyle.color;
    }

    // We ensure that we set the content background if the color changes as
    // well, to prevent contrast issues.
    let selectBackgroundSet =
      selectStyle["background-color"] != uaStyle["background-color"] ||
      selectStyle.color != uaStyle.color;

    if (custom) {
      if (selectStyle["text-shadow"] != "none") {
        sheet.insertRule(
          `#ContentSelectDropdown > menupopup > :is(menuitem, menucaption)[_moz-menuactive="true"] {
          text-shadow: none;
        }`,
          0
        );
      }

      for (let property of SUPPORTED_SELECT_PROPERTIES) {
        let shouldSkip = (function () {
          if (property == "direction") {
            // Handled elsewhere.
            return true;
          }
          if (!selectStyle[property]) {
            return true;
          }
          if (property == "background-color") {
            // This also depends on whether "color" is set.
            return !selectBackgroundSet;
          }
          return selectStyle[property] == uaStyle[property];
        })();

        if (shouldSkip) {
          continue;
        }
        let value = selectStyle[property];
        if (property == "scrollbar-width") {
          // This needs to actually apply to the relevant scrollbox, because
          // scrollbar-width doesn't inherit.
          property = "--content-select-scrollbar-width";
        }
        if (property == "color") {
          property = "--panel-color";
        }
        menupopup.style.setProperty(property, value);
      }
      // Some webpages set the <select> backgroundColor to transparent,
      // but they don't intend to change the popup to transparent.
      // So we remove the backgroundColor and turn it into an image instead.
      if (selectBackgroundSet) {
        // We intentionally use the parsed color to prevent color
        // values like `url(..)` being injected into the
        // `background-image` property.
        let parsedColor = menupopup.style.backgroundColor;
        menupopup.style.setProperty(
          "--content-select-background-image",
          `linear-gradient(${parsedColor}, ${parsedColor})`
        );
        // Always drop the background color to avoid messing with the custom
        // shadow on Windows 10 styling.
        menupopup.style.backgroundColor = "";
        // If the background is set, we also make sure we set the color, to
        // prevent contrast issues.
        menupopup.style.setProperty("--panel-color", selectStyle.color);

        sheet.insertRule(
          `#ContentSelectDropdown > menupopup > :is(menuitem, menucaption):not([_moz-menuactive="true"]) {
            color: inherit;
        }`,
          0
        );
      }
    }

    for (let i = 0, len = uniqueItemStyles.length; i < len; ++i) {
      sheet.insertRule(
        `#ContentSelectDropdown .ContentSelectDropdown-item-${i} {}`,
        0
      );
      let style = uniqueItemStyles[i];
      let rule = sheet.cssRules[0].style;
      rule.direction = style.direction;
      rule.fontSize = zoom * parseFloat(style["font-size"], 10) + "px";

      if (!custom) {
        continue;
      }
      let optionBackgroundIsTransparent =
        style["background-color"] == "rgba(0, 0, 0, 0)";
      let optionBackgroundSet =
        !optionBackgroundIsTransparent || style.color != selectStyle.color;

      if (optionBackgroundIsTransparent && style.color != selectStyle.color) {
        style["background-color"] = selectStyle["background-color"];
      }

      if (style.color == style["background-color"]) {
        style.color = selectStyle.color;
      }

      let inactiveRule = null;
      for (const property of SUPPORTED_OPTION_OPTGROUP_PROPERTIES) {
        let shouldSkip = (function () {
          if (property == "direction" || property == "font-size") {
            // Handled elsewhere.
            return true;
          }
          if (!style[property]) {
            return true;
          }
          if (property == "background-color" || property == "color") {
            // This also depends on whether "color" is set.
            return !optionBackgroundSet;
          }
          return style[property] == selectStyle[property];
        })();
        if (shouldSkip) {
          continue;
        }
        if (PROPERTIES_RESET_WHEN_ACTIVE.includes(property)) {
          if (!inactiveRule) {
            sheet.insertRule(
              `#ContentSelectDropdown .ContentSelectDropdown-item-${i}:not([_moz-menuactive="true"]) {}`,
              0
            );
            inactiveRule = sheet.cssRules[0].style;
          }
          inactiveRule[property] = style[property];
        } else {
          rule[property] = style[property];
        }
      }
      style.customStyling = selectBackgroundSet || optionBackgroundSet;
    }

    // We only set the `customoptionstyling` if the background has been
    // manually set. This prevents the overlap between moz-appearance and
    // background-color. `color` and `text-shadow` do not interfere with it.
    if (custom && selectBackgroundSet) {
      menulist.menupopup.setAttribute("customoptionstyling", "true");
    } else {
      menulist.menupopup.removeAttribute("customoptionstyling");
    }

    this._currentZoom = zoom;
    this._currentMenulist = menulist;
    this.populateChildren(
      menulist,
      custom,
      items,
      uniqueItemStyles,
      selectedIndex
    );
  },

  open(browser, menulist, rect, isOpenedViaTouch, selectParentActor) {
    this._actor = selectParentActor;
    menulist.hidden = false;
    this._currentBrowser = browser;
    this._closedWithEnter = false;
    this._selectRect = rect;
    this._registerListeners(menulist.menupopup);

    let menupopup = menulist.menupopup;
    menupopup.classList.toggle("isOpenedViaTouch", isOpenedViaTouch);

    let win = menulist.ownerGlobal;
    if (browser) {
      browser.constrainPopup(menupopup);
    } else {
      menupopup.setConstraintRect(new win.DOMRect(0, 0, 0, 0));
    }
    menupopup.openPopupAtScreenRect(
      AppConstants.platform == "macosx" ? "selection" : "after_start",
      rect.left,
      rect.top,
      rect.width,
      rect.height,
      false,
      false
    );
  },

  hide(menulist, browser) {
    if (this._currentBrowser == browser) {
      menulist.menupopup.hidePopup();
    }
  },

  handleEvent(event) {
    switch (event.type) {
      case "mouseup":
        function inRect(rect, x, y) {
          return (
            x >= rect.left &&
            x <= rect.left + rect.width &&
            y >= rect.top &&
            y <= rect.top + rect.height
          );
        }

        let x = event.screenX,
          y = event.screenY;
        let onAnchor =
          !inRect(this._currentMenulist.menupopup.getOuterScreenRect(), x, y) &&
          inRect(this._selectRect, x, y) &&
          this._currentMenulist.menupopup.state == "open";
        this._actor.sendAsyncMessage("Forms:MouseUp", { onAnchor });
        break;

      case "mouseover":
        if (
          !event.relatedTarget ||
          !this._currentMenulist.contains(event.relatedTarget)
        ) {
          this._actor.sendAsyncMessage("Forms:MouseOver", {});
        }
        break;

      case "mouseout":
        if (
          !event.relatedTarget ||
          !this._currentMenulist.contains(event.relatedTarget)
        ) {
          this._actor.sendAsyncMessage("Forms:MouseOut", {});
        }
        break;

      case "keydown":
        if (event.keyCode == event.DOM_VK_RETURN) {
          this._closedWithEnter = true;
        }
        break;

      case "command":
        if (event.target.hasAttribute("value")) {
          this._actor.sendAsyncMessage("Forms:SelectDropDownItem", {
            value: event.target.value,
            closedWithEnter: this._closedWithEnter,
          });
        }
        break;

      case "fullscreen":
      case "FullscreenWarningOnScreen":
        if (this._currentMenulist) {
          this._currentMenulist.menupopup.hidePopup();
        }
        break;

      case "popuphidden":
        this._actor.sendAsyncMessage("Forms:DismissedDropDown", {});
        let popup = event.target;
        this._unregisterListeners(popup);
        popup.parentNode.hidden = true;
        this._currentBrowser = null;
        this._currentMenulist = null;
        this._selectRect = null;
        this._currentZoom = 1;
        this._actor = null;
        break;
    }
  },

  receiveMessage(browser, msg) {
    // Sanity check - we'd better know what the currently opened menulist is,
    // and what browser it belongs to...
    if (!this._currentMenulist || this._currentBrowser != browser) {
      return;
    }

    if (msg.name == "Forms:UpdateDropDown") {
      let scrollBox = this._currentMenulist.menupopup.scrollBox.scrollbox;
      let scrollTop = scrollBox.scrollTop;

      let options = msg.data.options;
      let selectedIndex = msg.data.selectedIndex;
      this.populate(
        this._currentMenulist,
        options.options,
        options.uniqueStyles,
        selectedIndex,
        this._currentZoom,
        msg.data.custom && lazy.CUSTOM_STYLING_ENABLED,
        msg.data.isDarkBackground,
        msg.data.defaultStyle,
        msg.data.style
      );

      // Restore scroll position to what it was prior to the update.
      scrollBox.scrollTop = scrollTop;
    } else if (msg.name == "Forms:BlurDropDown-Ping") {
      this._actor.sendAsyncMessage("Forms:BlurDropDown-Pong", {});
    }
  },

  _registerListeners(popup) {
    popup.addEventListener("command", this);
    popup.addEventListener("popuphidden", this);
    popup.addEventListener("mouseover", this);
    popup.addEventListener("mouseout", this);
    popup.ownerGlobal.addEventListener("mouseup", this, true);
    popup.ownerGlobal.addEventListener("keydown", this, true);
    popup.ownerGlobal.addEventListener("fullscreen", this, true);
    popup.ownerGlobal.addEventListener("FullscreenWarningOnScreen", this, true);
  },

  _unregisterListeners(popup) {
    popup.removeEventListener("command", this);
    popup.removeEventListener("popuphidden", this);
    popup.removeEventListener("mouseover", this);
    popup.removeEventListener("mouseout", this);
    popup.ownerGlobal.removeEventListener("mouseup", this, true);
    popup.ownerGlobal.removeEventListener("keydown", this, true);
    popup.ownerGlobal.removeEventListener("fullscreen", this, true);
    popup.ownerGlobal.removeEventListener(
      "FullscreenWarningOnScreen",
      this,
      true
    );
  },

  /**
   * `populateChildren` creates all <menuitem> elements for the popup menu
   * based on the list of <option> elements from the <select> element.
   *
   * It attempts to intelligently add per-item CSS rules if the single
   * item values differ from the parent menu values and attempting to avoid
   * ending up with the same color of text and background.
   *
   * @param {Element}        menulist
   * @param {Array<Element>} options
   * @param {Array<Object>}  uniqueOptionStyles
   * @param {Number}         selectedIndex
   * @param {Element}        parentElement
   * @param {Boolean}        isGroupDisabled
   * @param {Boolean}        addSearch
   * @param {Number}         nthChildIndex
   * @returns {Number}
   */
  populateChildren(
    menulist,
    custom,
    options,
    uniqueOptionStyles,
    selectedIndex,
    parentElement = null,
    isGroupDisabled = false,
    addSearch = true,
    nthChildIndex = 1
  ) {
    let element = menulist.menupopup;

    let ariaOwns = "";
    for (let option of options) {
      let isOptGroup = option.isOptGroup;
      let isHR = option.isHR;

      let xulElement = "menuitem";
      if (isOptGroup) {
        xulElement = "menucaption";
      }
      if (isHR) {
        xulElement = "menuseparator";
      }

      let item = element.ownerDocument.createXULElement(xulElement);
      item.hidden =
        option.display == "none" || (parentElement && parentElement.hidden);

      if (parentElement) {
        // In the menupopup, the optgroup is a sibling of its contained options.
        // For accessibility, we want to preserve the hierarchy such that the
        // options are inside the optgroup. We do this using aria-owns on the
        // parent.
        item.id = "ContentSelectDropdownOption" + nthChildIndex;
        item.setAttribute("aria-level", "2");
        ariaOwns += item.id + " ";
      }

      element.appendChild(item);
      nthChildIndex++;

      if (isHR) {
        item.style.color = (custom && option.color) || "";

        // Continue early as HRs do not have other attributes.
        continue;
      }

      item.className = `ContentSelectDropdown-item-${option.styleIndex}`;

      if (isOptGroup) {
        item.setAttribute("role", "group");
      }
      item.setAttribute("label", option.textContent);
      // Keep track of which options are hidden by page content, so we can avoid
      // showing them on search input.
      item.hiddenByContent = item.hidden;
      item.setAttribute("tooltiptext", option.tooltip);

      if (uniqueOptionStyles[option.styleIndex].customStyling) {
        item.setAttribute("customoptionstyling", "true");
      } else {
        item.removeAttribute("customoptionstyling");
      }

      // A disabled optgroup disables all of its child options.
      let isDisabled = isGroupDisabled || option.disabled;
      if (isDisabled) {
        item.setAttribute("disabled", "true");
      }

      if (isOptGroup) {
        nthChildIndex = this.populateChildren(
          menulist,
          custom,
          option.children,
          uniqueOptionStyles,
          selectedIndex,
          item,
          isDisabled,
          false,
          nthChildIndex
        );
      } else {
        if (option.index == selectedIndex) {
          // We expect the parent element of the popup to be a <xul:menulist> that
          // has the popuponly attribute set to "true". This is necessary in order
          // for a <xul:menupopup> to act like a proper <html:select> dropdown, as
          // the <xul:menulist> does things like remember state and set the
          // _moz-menuactive attribute on the selected <xul:menuitem>.
          menulist.selectedItem = item;

          // It's hack time. In the event that we've re-populated the menulist due
          // to a mutation in the <select> in content, that means that the -moz_activemenu
          // may have been removed from the selected item. Since that's normally only
          // set for the initially selected on popupshowing for the menulist, and we
          // don't want to close and re-open the popup, we manually set it here.
          menulist.activeChild = item;
        }

        item.setAttribute("value", option.index);

        if (parentElement) {
          item.classList.add("contentSelectDropdown-ingroup");
        }
      }
    }

    if (parentElement && ariaOwns) {
      parentElement.setAttribute("aria-owns", ariaOwns);
    }

    // Check if search pref is enabled, if this is the first time iterating through
    // the dropdown, and if the list is long enough for a search element to be added.
    if (
      lazy.DOM_FORMS_SELECTSEARCH &&
      addSearch &&
      element.childElementCount > SEARCH_MINIMUM_ELEMENTS
    ) {
      // Add a search text field as the first element of the dropdown
      let searchbox = element.ownerDocument.createXULElement("search-textbox");
      searchbox.className = "contentSelectDropdown-searchbox";
      searchbox.addEventListener("input", this.onSearchInput);
      searchbox.addEventListener("focus", this.onSearchFocus.bind(this));
      searchbox.addEventListener("blur", this.onSearchBlur);
      searchbox.addEventListener("command", this.onSearchInput);

      // Handle special keys for exiting search
      searchbox.addEventListener(
        "keydown",
        event => {
          this.onSearchKeydown(event, menulist);
        },
        true
      );

      element.insertBefore(searchbox, element.children[0]);
    }

    return nthChildIndex;
  },

  onSearchKeydown(event, menulist) {
    if (event.defaultPrevented) {
      return;
    }

    let searchbox = event.currentTarget;
    switch (event.key) {
      case "Escape":
        searchbox.parentElement.hidePopup();
        break;
      case "ArrowDown":
      case "Enter":
      case "Tab":
        searchbox.blur();
        if (
          searchbox.nextElementSibling.localName == "menuitem" &&
          !searchbox.nextElementSibling.hidden
        ) {
          menulist.activeChild = searchbox.nextElementSibling;
        } else {
          let currentOption = searchbox.nextElementSibling;
          while (
            currentOption &&
            (currentOption.localName != "menuitem" || currentOption.hidden)
          ) {
            currentOption = currentOption.nextElementSibling;
          }
          if (currentOption) {
            menulist.activeChild = currentOption;
          } else {
            searchbox.focus();
          }
        }
        break;
      default:
        return;
    }
    event.preventDefault();
  },

  onSearchInput(event) {
    let searchObj = event.currentTarget;

    // Get input from search field, set to all lower case for comparison
    let input = searchObj.value.toLowerCase();
    // Get all items in dropdown (could be options or optgroups)
    let menupopup = searchObj.parentElement;
    let menuItems = menupopup.querySelectorAll("menuitem, menucaption");

    // Flag used to detect any group headers with no visible options.
    // These group headers should be hidden.
    let allHidden = true;
    // Keep a reference to the previous group header (menucaption) to go back
    // and set to hidden if all options within are hidden.
    let prevCaption = null;

    for (let currentItem of menuItems) {
      // Make sure we don't show any options that were hidden by page content
      if (!currentItem.hiddenByContent) {
        // Get label and tooltip (title) from option and change to
        // lower case for comparison
        let itemLabel = currentItem.getAttribute("label")?.toLowerCase() || "";
        let itemTooltip =
          currentItem.getAttribute("title")?.toLowerCase() || "";

        // If search input is empty, all options should be shown
        if (!input) {
          currentItem.hidden = false;
        } else if (currentItem.localName == "menucaption") {
          if (prevCaption != null) {
            prevCaption.hidden = allHidden;
          }
          prevCaption = currentItem;
          allHidden = true;
        } else {
          if (
            !currentItem.classList.contains("contentSelectDropdown-ingroup") &&
            currentItem.previousElementSibling.classList.contains(
              "contentSelectDropdown-ingroup"
            )
          ) {
            if (prevCaption != null) {
              prevCaption.hidden = allHidden;
            }
            prevCaption = null;
            allHidden = true;
          }
          if (itemLabel.includes(input) || itemTooltip.includes(input)) {
            currentItem.hidden = false;
            allHidden = false;
          } else {
            currentItem.hidden = true;
          }
        }
        if (prevCaption != null) {
          prevCaption.hidden = allHidden;
        }
      }
    }
  },

  onSearchFocus(event) {
    let menupopup = event.target.closest("menupopup");
    menupopup.parentElement.activeChild = null;
    menupopup.setAttribute("ignorekeys", "true");
    this._actor.sendAsyncMessage("Forms:SearchFocused", {});
  },

  onSearchBlur(event) {
    let menupopup = event.target.closest("menupopup");
    menupopup.setAttribute(
      "ignorekeys",
      AppConstants.platform == "win" ? "shortcuts" : "false"
    );
  },
};

export class SelectParent extends JSWindowActorParent {
  get relevantBrowser() {
    return this.browsingContext.top.embedderElement;
  }

  get _document() {
    return this.browsingContext.topChromeWindow.document;
  }

  get _menulist() {
    return this._document.getElementById("ContentSelectDropdown");
  }

  _createMenulist() {
    let document = this._document;
    let menulist = document.createXULElement("menulist");
    menulist.setAttribute("id", "ContentSelectDropdown");
    menulist.setAttribute("popuponly", "true");
    menulist.setAttribute("hidden", "true");

    let popup = menulist.appendChild(document.createXULElement("menupopup"));
    popup.setAttribute("id", "ContentSelectDropdownPopup");
    popup.setAttribute("activateontab", "true");
    popup.setAttribute("position", "after_start");
    popup.setAttribute("tabspecific", "true");
    popup.setAttribute("level", "parent");
    if (AppConstants.platform == "win") {
      popup.setAttribute("consumeoutsideclicks", "false");
      popup.setAttribute("ignorekeys", "shortcuts");
    }

    let container =
      document.getElementById("mainPopupSet") ||
      document.querySelector("popupset") ||
      document.documentElement.appendChild(
        document.createXULElement("popupset")
      );

    container.appendChild(menulist);
    return menulist;
  }

  receiveMessage(message) {
    switch (message.name) {
      case "Forms:ShowDropDown": {
        let menulist = this._menulist || this._createMenulist();

        let data = message.data;

        SelectParentHelper.populate(
          menulist,
          data.options.options,
          data.options.uniqueStyles,
          data.selectedIndex,
          // We only want to apply the full zoom. The text zoom is already
          // applied in the font-size.
          this.browsingContext.fullZoom,
          data.custom && lazy.CUSTOM_STYLING_ENABLED,
          data.isDarkBackground,
          data.defaultStyle,
          data.style
        );
        SelectParentHelper.open(
          this.relevantBrowser,
          menulist,
          data.rect,
          data.isOpenedViaTouch,
          this
        );
        break;
      }

      case "Forms:HideDropDown": {
        SelectParentHelper.hide(this._menulist, this.relevantBrowser);
        break;
      }

      default:
        SelectParentHelper.receiveMessage(this.relevantBrowser, message);
    }
  }
}
PK
!<��
p

actors/ThumbnailsChild.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  PageThumbUtils: "resource://gre/modules/PageThumbUtils.sys.mjs",
});

export class ThumbnailsChild extends JSWindowActorChild {
  receiveMessage(message) {
    switch (message.name) {
      case "Browser:Thumbnail:ContentInfo": {
        let [width, height] = lazy.PageThumbUtils.getContentSize(
          this.contentWindow
        );
        let documentElement = this.document.documentElement;
        let body = this.document.body;
        return {
          width,
          height,
          scrollY: this.contentWindow.scrollY,
          documentHeight: Math.max(
            documentElement.clientHeight,
            documentElement.scrollHeight,
            documentElement.offsetHeight,
            body.clientHeight,
            body.scrollHeight,
            body.offsetHeight
          ),
        };
      }
      case "Browser:Thumbnail:CheckState": {
        /**
         * Remote isSafeForCapture request handler for PageThumbs.
         */
        return new Promise(resolve =>
          Services.tm.idleDispatchToMainThread(() => {
            if (!this.manager) {
              // If we have no manager, our actor has been destroyed, which
              // means we can't respond, and trying to touch
              // `this.contentWindow` or `this.browsingContext` will throw.
              // The `sendQuery` call in the parent will already have been
              // rejected when the actor was destroyed, so there's no need to
              // reject our promise or log an additional error.
              return;
            }

            let result = lazy.PageThumbUtils.shouldStoreContentThumbnail(
              this.contentWindow,
              this.browsingContext.docShell
            );
            resolve(result);
          })
        );
      }
      case "Browser:Thumbnail:GetOriginalURL": {
        /**
         * Remote GetOriginalURL request handler for PageThumbs.
         */
        let channel = this.browsingContext.docShell.currentDocumentChannel;
        let channelError = lazy.PageThumbUtils.isChannelErrorResponse(channel);
        let originalURL;
        try {
          originalURL = channel.originalURI.spec;
        } catch (ex) {}
        return { channelError, originalURL };
      }
    }
    return undefined;
  }
}
PK
!< g�4WW actors/TranslationsChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  TranslationsDocument:
    "chrome://global/content/translations/translations-document.sys.mjs",
  LRUCache:
    "chrome://global/content/translations/translations-document.sys.mjs",
  LanguageDetector:
    "resource://gre/modules/translation/LanguageDetector.sys.mjs",
});

/**
 * This file is extremely sensitive to memory size and performance!
 */
export class TranslationsChild extends JSWindowActorChild {
  /**
   * @type {TranslationsDocument | null}
   */
  #translatedDoc = null;

  /**
   * This cache is shared across TranslationsChild instances. This means
   * that it will be shared across multiple page loads in the same origin.
   *
   * @type {LRUCache | null}
   */
  static #translationsCache = null;

  handleEvent(event) {
    switch (event.type) {
      case "DOMContentLoaded":
        this.sendAsyncMessage("Translations:ReportLangTags", {
          documentElementLang: this.document.documentElement.lang,
        });
        break;
    }
  }

  addProfilerMarker(message) {
    ChromeUtils.addProfilerMarker(
      "TranslationsChild",
      { innerWindowId: this.contentWindow.windowGlobalChild.innerWindowId },
      message
    );
  }

  async receiveMessage({ name, data }) {
    switch (name) {
      case "Translations:TranslatePage": {
        if (this.#translatedDoc?.translator.engineStatus === "error") {
          this.#translatedDoc.destroy();
          this.#translatedDoc = null;
        }

        if (this.#translatedDoc) {
          console.error("This page was already translated.");
          return undefined;
        }

        const { fromLanguage, toLanguage, port, translationsStart } = data;
        if (
          !TranslationsChild.#translationsCache ||
          !TranslationsChild.#translationsCache.matches(
            fromLanguage,
            toLanguage
          )
        ) {
          TranslationsChild.#translationsCache = new lazy.LRUCache(
            fromLanguage,
            toLanguage
          );
        }

        this.#translatedDoc = new lazy.TranslationsDocument(
          this.document,
          fromLanguage,
          toLanguage,
          this.contentWindow.windowGlobalChild.innerWindowId,
          port,
          () => this.sendAsyncMessage("Translations:RequestPort"),
          () => this.sendAsyncMessage("Translations:ReportFirstVisibleChange"),
          translationsStart,
          () => this.docShell.now(),
          TranslationsChild.#translationsCache
        );

        return undefined;
      }
      case "Translations:GetDocumentElementLang":
        return this.document.documentElement.lang;
      case "Translations:IdentifyLanguage": {
        // Wait for idle callback as the page will be more settled if it has
        // dynamic content, like on a React app.
        if (this.contentWindow) {
          await new Promise(resolve => {
            this.contentWindow.requestIdleCallback(resolve);
          });
        }

        try {
          return lazy.LanguageDetector.detectLanguageFromDocument(
            this.document
          );
        } catch (error) {
          return null;
        }
      }
      case "Translations:AcquirePort": {
        this.addProfilerMarker("Acquired a port, resuming translations");
        this.#translatedDoc.translator.acquirePort(data.port);
        return undefined;
      }
      default:
        throw new Error("Unknown message.", name);
    }
  }
}
PK
!<���iII&actors/TranslationsEngineChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  return console.createInstance({
    maxLogLevelPref: "browser.translations.logLevel",
    prefix: "Translations",
  });
});

/**
 * The engine child is responsible for exposing privileged code to the un-privileged
 * space the engine runs in.
 */
export class TranslationsEngineChild extends JSWindowActorChild {
  /**
   * The resolve function for the Promise returned by the
   * "TranslationsEngine:ForceShutdown" message.
   *
   * @type {null | () => {}}
   */
  #resolveForceShutdown = null;

  actorCreated() {
    this.#exportFunctions();
  }

  handleEvent(event) {
    switch (event.type) {
      case "DOMContentLoaded":
        this.sendAsyncMessage("TranslationsEngine:Ready");
        break;
    }
  }

  // eslint-disable-next-line consistent-return
  async receiveMessage({ name, data }) {
    switch (name) {
      case "TranslationsEngine:StartTranslation": {
        const { fromLanguage, toLanguage, innerWindowId, port } = data;
        const transferables = [port];
        const message = {
          type: "StartTranslation",
          fromLanguage,
          toLanguage,
          innerWindowId,
          port,
        };
        this.contentWindow.postMessage(message, "*", transferables);
        break;
      }
      case "TranslationsEngine:DiscardTranslations": {
        const { innerWindowId } = data;
        this.contentWindow.postMessage({
          type: "DiscardTranslations",
          innerWindowId,
        });
        break;
      }
      case "TranslationsEngine:ForceShutdown": {
        this.contentWindow.postMessage({
          type: "ForceShutdown",
        });
        return new Promise(resolve => {
          this.#resolveForceShutdown = resolve;
        });
      }
      default:
        console.error("Unknown message received", name);
    }
  }

  /**
   * Export any of the child functions that start with "TE_" to the unprivileged content
   * page. This restricts the security capabilities of the content page.
   */
  #exportFunctions() {
    const fns = [
      "TE_addProfilerMarker",
      "TE_getLogLevel",
      "TE_log",
      "TE_logError",
      "TE_requestEnginePayload",
      "TE_reportEngineStatus",
      "TE_resolveForceShutdown",
      "TE_destroyEngineProcess",
    ];
    for (const defineAs of fns) {
      Cu.exportFunction(this[defineAs].bind(this), this.contentWindow, {
        defineAs,
      });
    }
  }

  /**
   * A privileged promise can't be used in the content page, so convert a privileged
   * promise into a content one.
   *
   * @param {Promise<any>} promise
   * @returns {Promise<any>}
   */
  #convertToContentPromise(promise) {
    return new this.contentWindow.Promise((resolve, reject) =>
      promise.then(resolve, error => {
        let contentWindow;
        try {
          contentWindow = this.contentWindow;
        } catch (error) {
          // The content window is no longer available.
          reject();
          return;
        }
        // Create an error in the content window, if the content window is still around.
        let message = "An error occured in the TranslationsEngine actor.";
        if (typeof error === "string") {
          message = error;
        }
        if (typeof error?.message === "string") {
          message = error.message;
        }
        if (typeof error?.stack === "string") {
          message += `\n\nOriginal stack:\n\n${error.stack}\n`;
        }

        reject(new contentWindow.Error(message));
      })
    );
  }

  /**
   * @param {object} options
   * @param {number?} options.startTime
   * @param {string} options.message
   * @param {number} options.innerWindowId
   */
  TE_addProfilerMarker({ startTime, message, innerWindowId }) {
    ChromeUtils.addProfilerMarker(
      "TranslationsEngine",
      { startTime, innerWindowId },
      message
    );
  }

  /**
   * Pass the message from content that the engines were shut down.
   */
  TE_resolveForceShutdown() {
    this.#resolveForceShutdown();
  }

  /**
   * @returns {string}
   */
  TE_getLogLevel() {
    return Services.prefs.getCharPref("browser.translations.logLevel");
  }

  /**
   * Log messages if "browser.translations.logLevel" is set to "All".
   *
   * @param {...any} args
   */
  TE_log(...args) {
    lazy.console.log(...args);
  }

  /**
   * Report an error to the console.
   *
   * @param {...any} args
   */
  TE_logError(...args) {
    lazy.console.error(...args);
  }

  /**
   * @param {string} fromLanguage
   * @param {string} toLanguage
   */
  TE_requestEnginePayload(fromLanguage, toLanguage) {
    return this.#convertToContentPromise(
      this.sendQuery("TranslationsEngine:RequestEnginePayload", {
        fromLanguage,
        toLanguage,
      })
    );
  }

  /**
   * @param {number} innerWindowId
   * @param {"ready" | "error"} status
   */
  TE_reportEngineStatus(innerWindowId, status) {
    this.sendAsyncMessage("TranslationsEngine:ReportEngineStatus", {
      innerWindowId,
      status,
    });
  }

  /**
   * No engines are still alive, signal that the process can be destroyed.
   */
  TE_destroyEngineProcess() {
    this.sendAsyncMessage("TranslationsEngine:DestroyEngineProcess");
  }
}
PK
!<��I���'actors/TranslationsEngineParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  TranslationsParent: "resource://gre/actors/TranslationsParent.sys.mjs",
  EngineProcess: "chrome://global/content/ml/EngineProcess.sys.mjs",
});

/**
 * The translations engine is in its own content process. This actor handles the
 * marshalling of the data such as the engine payload and port passing.
 */
export class TranslationsEngineParent extends JSWindowActorParent {
  /**
   * Keep track of the live actors by InnerWindowID.
   *
   * @type {Map<InnerWindowID, TranslationsParent | AboutTranslationsParent>}
   */
  #translationsParents = new Map();

  async receiveMessage({ name, data }) {
    switch (name) {
      case "TranslationsEngine:Ready":
        if (!lazy.EngineProcess.resolveTranslationsEngineParent) {
          throw new Error(
            "Unable to find the resolve function for when the translations engine is ready."
          );
        }
        lazy.EngineProcess.resolveTranslationsEngineParent(this);
        return undefined;
      case "TranslationsEngine:RequestEnginePayload": {
        const { fromLanguage, toLanguage } = data;
        const payloadPromise =
          lazy.TranslationsParent.getTranslationsEnginePayload(
            fromLanguage,
            toLanguage
          );
        payloadPromise.catch(error => {
          lazy.TranslationsParent.telemetry().onError(String(error));
        });
        return payloadPromise;
      }
      case "TranslationsEngine:ReportEngineStatus": {
        const { innerWindowId, status } = data;
        const translationsParent = this.#translationsParents.get(innerWindowId);

        // about:translations will not have a TranslationsParent associated with
        // this call.
        if (translationsParent) {
          switch (status) {
            case "ready":
              translationsParent.languageState.isEngineReady = true;
              break;
            case "error":
              translationsParent.languageState.error = "engine-load-failure";
              break;
            default:
              throw new Error("Unknown engine status: " + status);
          }
        }
        return undefined;
      }
      case "TranslationsEngine:DestroyEngineProcess":
        lazy.EngineProcess.destroyTranslationsEngine().catch(error =>
          console.error(error)
        );
        return undefined;
      default:
        return undefined;
    }
  }

  /**
   * @param {string} fromLanguage
   * @param {string} toLanguage
   * @param {MessagePort} port
   * @param {TranslationsParent} [translationsParent]
   */
  startTranslation(fromLanguage, toLanguage, port, translationsParent) {
    const innerWindowId = translationsParent?.innerWindowId;
    if (translationsParent) {
      this.#translationsParents.set(innerWindowId, translationsParent);
    }
    if (this.#isDestroyed) {
      throw new Error("The translation engine process was already destroyed.");
    }
    const transferables = [port];
    this.sendAsyncMessage(
      "TranslationsEngine:StartTranslation",
      {
        fromLanguage,
        toLanguage,
        innerWindowId,
        port,
      },
      transferables
    );
  }

  /**
   * Remove all the translations that are currently queued, and remove
   * the communication port.
   *
   * @param {number} innerWindowId
   */
  discardTranslations(innerWindowId) {
    this.#translationsParents.delete(innerWindowId);
    this.sendAsyncMessage("TranslationsEngine:DiscardTranslations", {
      innerWindowId,
    });
  }

  /**
   * Manually shut down the engines, typically for testing purposes.
   */
  forceShutdown() {
    return this.sendQuery("TranslationsEngine:ForceShutdown");
  }

  #isDestroyed = false;

  didDestroy() {
    this.#isDestroyed = true;
  }
}
PK
!<`������!actors/TranslationsParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * The pivot language is used to pivot between two different language translations
 * when there is not a model available to translate directly between the two. In this
 * case "en" is common between the various supported models.
 *
 * For instance given the following two models:
 *   "fr" -> "en"
 *   "en" -> "it"
 *
 * You can accomplish:
 *   "fr" -> "it"
 *
 * By doing:
 *   "fr" -> "en" -> "it"
 */
const PIVOT_LANGUAGE = "en";

const TRANSLATIONS_PERMISSION = "translations";
const ALWAYS_TRANSLATE_LANGS_PREF =
  "browser.translations.alwaysTranslateLanguages";
const NEVER_TRANSLATE_LANGS_PREF =
  "browser.translations.neverTranslateLanguages";

const lazy = {};

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

if (AppConstants.ENABLE_WEBDRIVER) {
  XPCOMUtils.defineLazyServiceGetter(
    lazy,
    "Marionette",
    "@mozilla.org/remote/marionette;1",
    "nsIMarionette"
  );

  XPCOMUtils.defineLazyServiceGetter(
    lazy,
    "RemoteAgent",
    "@mozilla.org/remote/agent;1",
    "nsIRemoteAgent"
  );
} else {
  lazy.Marionette = { running: false };
  lazy.RemoteAgent = { running: false };
}

XPCOMUtils.defineLazyServiceGetters(lazy, {
  BrowserHandler: ["@mozilla.org/browser/clh;1", "nsIBrowserHandler"],
});

ChromeUtils.defineESModuleGetters(lazy, {
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
  TranslationsTelemetry:
    "chrome://global/content/translations/TranslationsTelemetry.sys.mjs",
  EngineProcess: "chrome://global/content/ml/EngineProcess.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  return console.createInstance({
    maxLogLevelPref: "browser.translations.logLevel",
    prefix: "Translations",
  });
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "translationsEnabledPref",
  "browser.translations.enable"
);

/**
 * Retrieves the most recent target languages that have been requested for translation by the user.
 * Inserting into this pref should be managed by the static TranslationsParent class.
 *
 * @see {TranslationsParent.storeMostRecentTargetLanguage}
 *
 * There is a linear chain of synchronously dependent observers related to this pref.
 *
 * When this pref's value is updated, it sends "translations:most-recent-target-language-changed"
 * which is observed by the static global TranslationsParent object to know when to clear its cache.
 *
 * Once the cache has been cleared, the static global TranslationsParent object then sends
 * "translations:maybe-update-user-lang-tag" which is observed by every instantiated TranslationsParent
 * actor object to consider updating their cached userLangTag.
 *
 * @see {TranslationsParent} for further descriptions and diagrams.
 */
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "mostRecentTargetLanguages",
  "browser.translations.mostRecentTargetLanguages",
  /* aDefaultValue */ "",
  /* aOnUpdate */ () => {
    Services.obs.notifyObservers(
      null,
      "translations:most-recent-target-language-changed"
    );
  },
  /* aTransform */ rawLangTags =>
    rawLangTags ? new Set(rawLangTags.split(",")) : new Set()
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "chaosErrorsPref",
  "browser.translations.chaos.errors"
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "chaosTimeoutMSPref",
  "browser.translations.chaos.timeoutMS"
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "automaticallyPopupPref",
  "browser.translations.automaticallyPopup"
);

/**
 * Returns the always-translate language tags as an array.
 */
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "alwaysTranslateLangTags",
  ALWAYS_TRANSLATE_LANGS_PREF,
  /* aDefaultPrefValue */ "",
  /* onUpdate */ () =>
    Services.obs.notifyObservers(
      null,
      "translations:always-translate-languages-changed"
    ),
  /* aTransform */ rawLangTags =>
    rawLangTags ? new Set(rawLangTags.split(",")) : new Set()
);

/**
 * Returns the never-translate language tags as an array.
 */
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "neverTranslateLangTags",
  NEVER_TRANSLATE_LANGS_PREF,
  /* aDefaultPrefValue */ "",
  /* onUpdate */ () =>
    Services.obs.notifyObservers(
      null,
      "translations:never-translate-languages-changed"
    ),
  /* aTransform */ rawLangTags =>
    rawLangTags ? new Set(rawLangTags.split(",")) : new Set()
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "simulateUnsupportedEnginePref",
  "browser.translations.simulateUnsupportedEngine"
);

// At this time the signatures of the files are not being checked when they are being
// loaded from disk. This signature check involves hitting the network, and translations
// are explicitly an offline-capable feature. See Bug 1827265 for re-enabling this
// check.
const VERIFY_SIGNATURES_FROM_FS = false;

/**
 * @typedef {import("../translations").TranslationModelRecord} TranslationModelRecord
 * @typedef {import("../translations").RemoteSettingsClient} RemoteSettingsClient
 * @typedef {import("../translations").LanguageTranslationModelFiles} LanguageTranslationModelFiles
 * @typedef {import("../translations").WasmRecord} WasmRecord
 * @typedef {import("../translations").LangTags} LangTags
 * @typedef {import("../translations").LanguagePair} LanguagePair
 * @typedef {import("../translations").SupportedLanguages} SupportedLanguages
 * @typedef {import("../translations").TranslationErrors} TranslationErrors
 */

/**
 * @typedef {object} TranslationPair
 * @property {string} fromLanguage
 * @property {string} toLanguage
 * @property {string} [fromDisplayLanguage]
 * @property {string} [toDisplayLanguage]
 */

/**
 * The state that is stored per a "top" ChromeWindow. This "top" ChromeWindow is the JS
 * global associated with a browser window. Some state is unique to a browser window, and
 * using the top ChromeWindow is a unique key that ensures the state will be unique to
 * that browser window.
 *
 * See BrowsingContext.webidl for information on the "top"
 * See the TranslationsParent JSDoc for more information on the state management.
 */
class StatePerTopChromeWindow {
  /**
   * The storage backing for the states.
   *
   * @type {WeakMap<ChromeWindow, StatePerTopChromeWindow>}
   */
  static #states = new WeakMap();

  /**
   * When reloading the page, store the translation pair that needs translating.
   *
   * @type {null | TranslationPair}
   */
  translateOnPageReload = null;

  /**
   * The page may auto-translate due to user settings. On a page restore, always
   * skip the page restore logic.
   *
   * @type {boolean}
   */
  isPageRestored = false;

  /**
   * Remember the detected languages on a page reload. This will keep the translations
   * button from disappearing and reappearing, which causes the button to lose focus.
   *
   * @type {LangTags | null} previousDetectedLanguages
   */
  previousDetectedLanguages = null;

  static #id = 0;
  /**
   * @param {ChromeWindow} topChromeWindow
   */
  constructor(topChromeWindow) {
    this.id = StatePerTopChromeWindow.#id++;
    StatePerTopChromeWindow.#states.set(topChromeWindow, this);
  }

  /**
   * @param {ChromeWindow} topChromeWindow
   * @returns {StatePerTopChromeWindow}
   */
  static getOrCreate(topChromeWindow) {
    let state = StatePerTopChromeWindow.#states.get(topChromeWindow);
    if (state) {
      return state;
    }
    state = new StatePerTopChromeWindow(topChromeWindow);
    StatePerTopChromeWindow.#states.set(topChromeWindow, state);
    return state;
  }
}

/**
 * The TranslationsParent is used to orchestrate translations in Firefox. It can
 * download the Wasm translation engine, and the language models. It manages the life
 * cycle for offering and performing translations.
 *
 * Care must be taken for the life cycle of the state management and data caching. The
 * following examples use a fictitious `myState` property to show how state can be stored.
 *
 * There is only 1 TranslationsParent static class in the parent process. At this
 * layer it is safe to store things like translation models and general browser
 * configuration as these don't change across browser windows. This is accessed like
 * `TranslationsParent.myState`
 *
 * The next layer down are the top ChromeWindows. These map to the UI and user's conception
 * of a browser window, such as what you would get by hitting cmd+n or ctrl+n to get a new
 * browser window. State such as whether a page is reloaded or general navigation events
 * must be unique per ChromeWindow. State here is stored in the `StatePerTopChromeWindow`
 * abstraction, like `this.getWindowState().myState`. This layer also consists of a
 * `FullPageTranslationsPanel` instance per top ChromeWindow (at least on Desktop).
 *
 * The final layer consists of the multiple tabs and navigation history inside of a
 * ChromeWindow. Data for this layer is safe to store on the TranslationsParent instance,
 * like `this.myState`.
 *
 * Below is an ascii diagram of this relationship.
 *
 *   ┌─────────────────────────────────────────────────────────────────────────────┐
 *   │                           static TranslationsParent                         │
 *   └─────────────────────────────────────────────────────────────────────────────┘
 *                  |                                       |
 *                  v                                       v
 * ┌──────────────────────────────────────┐   ┌──────────────────────────────────────┐
 * │         top ChromeWindow             │   │        top ChromeWindow              │
 * │ (FullPageTranslationsPanel instance) │   │ (FullPageTranslationsPanel instance) │
 * └──────────────────────────────────────┘   └──────────────────────────────────────┘
 *             |               |       |                |              |       |
 *             v               v       v                v              v       v
 *   ┌────────────────────┐ ┌─────┐ ┌─────┐  ┌────────────────────┐ ┌─────┐ ┌─────┐
 *   │ TranslationsParent │ │ ... │ │ ... │  │ TranslationsParent │ │ ... │ │ ... │
 *   │  (actor instance)  │ │     │ │     │  │  (actor instance)  │ │     │ │     │
 *   └────────────────────┘ └─────┘ └─────┘  └────────────────────┘ └─────┘ └─────┘
 */
export class TranslationsParent extends JSWindowActorParent {
  /**
   * The following constants control the major version for assets downloaded from
   * Remote Settings. When a breaking change is introduced, Nightly will have these
   * numbers incremented by one, but Beta and Release will still be on the previous
   * version. Remote Settings will ship both versions of the records, and the latest
   * asset released in that version will be used. For instance, with a major version
   * of "1", assets can be downloaded for "1.0", "1.2", "1.3beta", but assets marked
   * as "2.0", "2.1", etc will not be downloaded.
   *
   * Release docs:
   * https://firefox-source-docs.mozilla.org/toolkit/components/translations/resources/03_bergamot.html
   */
  static BERGAMOT_MAJOR_VERSION = 1;
  static LANGUAGE_MODEL_MAJOR_VERSION = 1;

  /**
   * Contains the state that would affect UI. Anytime this state is changed, a dispatch
   * event is sent so that UI can react to it. The actor is inside of /toolkit and
   * needs a way of notifying /browser code (or other users) of when the state changes.
   *
   * @type {TranslationsLanguageState}
   */
  languageState;

  /**
   * Allows the TranslationsEngineParent to resolve an engine once it is ready.
   *
   * @type {null | () => TranslationsEngineParent}
   */
  resolveEngine = null;

  /**
   * The cached URI spec where the panel was first ever shown, as determined by the
   * browser.translations.panelShown pref.
   *
   * Holding on to this URI value allows us to show the introductory message in the panel
   * when the panel opens, as long as the active panel is open on that particular URI.
   *
   * @type {string | null}
   */
  firstShowUriSpec = null;

  /**
   * Do not send queries or do work when the actor is already destroyed. This flag needs
   * to be checked after calls to `await`.
   */
  #isDestroyed = false;

  /**
   * There is only one static TranslationsParent for all of the top ChromeWindows.
   * The top ChromeWindow maps to the user's conception of a window such as when you hit
   * cmd+n or ctrl+n.
   *
   * @returns {StatePerTopChromeWindow}
   */
  getWindowState() {
    const state = StatePerTopChromeWindow.getOrCreate(
      this.browsingContext.top.embedderWindowGlobal
    );
    return state;
  }

  actorCreated() {
    this.innerWindowId = this.browsingContext.top.embedderElement.innerWindowID;
    const windowState = this.getWindowState();
    this.languageState = new TranslationsLanguageState(
      this,
      windowState.previousDetectedLanguages
    );
    windowState.previousDetectedLanguages = null;

    // Attach a closure to this so that we can remove the observer when didDestroy() is called.
    this.maybeUpdateUserLangTag = () => {
      const langTag = TranslationsParent.getPreferredLanguages({
        excludeLangTags: [this.languageState.detectedLanguages?.docLangTag],
      })[0];
      this.languageState.maybeUpdateUserLangTag(langTag);
    };
    Services.obs.addObserver(
      this.maybeUpdateUserLangTag,
      "translations:maybe-update-user-lang-tag"
    );

    if (windowState.translateOnPageReload) {
      // The actor was recreated after a page reload, start the translation.
      const { fromLanguage, toLanguage } = windowState.translateOnPageReload;
      windowState.translateOnPageReload = null;

      lazy.console.log(
        `Translating on a page reload from "${fromLanguage}" to "${toLanguage}".`
      );

      this.translate(
        fromLanguage,
        toLanguage,
        false // reportAsAutoTranslate
      );
    }
  }

  /**
   * A map of the TranslationModelRecord["id"] to the record of the model in Remote Settings.
   * Used to coordinate the downloads.
   *
   * @type {null | Promise<Map<string, TranslationModelRecord>>}
   */
  static #translationModelRecords = null;

  /**
   * The RemoteSettingsClient that downloads the translation models.
   *
   * @type {RemoteSettingsClient | null}
   */
  static #translationModelsRemoteClient = null;

  /**
   * The RemoteSettingsClient that downloads the wasm binaries.
   *
   * @type {RemoteSettingsClient | null}
   */
  static #translationsWasmRemoteClient = null;

  /**
   * Allows the actor's behavior to be changed when the translations engine is mocked via
   * a dummy RemoteSettingsClient.
   *
   * @type {bool}
   */
  static #isTranslationsEngineMocked = false;

  /**
   * @type {null | Promise<boolean>}
   */
  static #isTranslationsEngineSupported = null;

  /**
   * An ordered list of preferred languages based on:
   *
   *   1. Most recent target languages
   *   2. Web requested languages
   *   3. App languages
   *   4. OS language
   *
   * This is the composition of #mostRecentTargetLanguages and #userSettingsLanguages
   *
   * @type {null | string[]}
   */
  static #preferredLanguages = null;

  /**
   * An ordered list of the most recently translated-into target languages.
   *
   * @type {null | string[]}
   */
  static #mostRecentTargetLanguages = null;

  /**
   * An ordered list of languages specified in the user's settings based on:
   *
   *   1. Web requested languages
   *   2. App languages
   *   3. OS languages
   *
   * @type {null | string[]}
   */
  static #userSettingsLanguages = null;

  /**
   * The value of navigator.languages.
   *
   * @type {null | Set<string>}
   */
  static #webContentLanguages = null;

  static #observingLanguages = false;

  // On a fast connection, 10 concurrent downloads were measured to be the fastest when
  // downloading all of the language files.
  static MAX_CONCURRENT_DOWNLOADS = 10;
  static MAX_DOWNLOAD_RETRIES = 3;

  // The set of hosts that have already been offered for translations.
  static #hostsOffered = new Set();

  // Enable the translations popup offer in tests.
  static testAutomaticPopup = false;

  /**
   * Gecko preference for always translating a language.
   *
   * @type {string}
   */
  static ALWAYS_TRANSLATE_LANGS_PREF = ALWAYS_TRANSLATE_LANGS_PREF;

  /**
   * Gecko preference for never translating a language.
   *
   * @type {string}
   */
  static NEVER_TRANSLATE_LANGS_PREF = NEVER_TRANSLATE_LANGS_PREF;

  /**
   * Telemetry functions for Translations
   *
   * @returns {TranslationsTelemetry}
   */
  static telemetry() {
    return lazy.TranslationsTelemetry;
  }

  /**
   * TODO(Bug 1834306) - Cu.isInAutomation doesn't recognize Marionette and RemoteAgent
   * tests.
   */
  static isInAutomation() {
    return (
      Cu.isInAutomation || lazy.Marionette.running || lazy.RemoteAgent.running
    );
  }

  /**
   * Returns whether the Translations Engine is mocked for testing.
   *
   * @returns {boolean}
   */
  static isTranslationsEngineMocked() {
    return TranslationsParent.#isTranslationsEngineMocked;
  }

  /**
   * Offer translations (for instance by automatically opening the popup panel) whenever
   * languages are detected, but only do it once per host per session.
   *
   * @param {LangTags} detectedLanguages
   */
  maybeOfferTranslations(detectedLanguages) {
    if (!this.browsingContext.currentWindowGlobal) {
      return;
    }
    if (!lazy.automaticallyPopupPref) {
      return;
    }

    // On Android the BrowserHandler is intermittently not available (for unknown reasons).
    // Check that the component is available before de-lazifying lazy.BrowserHandler.
    if (Cc["@mozilla.org/browser/clh;1"] && lazy.BrowserHandler?.kiosk) {
      // Pop-ups should not be shown in kiosk mode.
      return;
    }
    const { documentURI } = this.browsingContext.currentWindowGlobal;

    if (
      TranslationsParent.isInAutomation() &&
      !TranslationsParent.testAutomaticPopup
    ) {
      // Do not offer translations in automation, as many tests do not expect this
      // behavior.
      lazy.console.log(
        "maybeOfferTranslations - Do not offer translations in automation.",
        documentURI.spec
      );
      return;
    }

    if (
      !detectedLanguages.docLangTag ||
      !detectedLanguages.userLangTag ||
      !detectedLanguages.isDocLangTagSupported
    ) {
      lazy.console.log(
        "maybeOfferTranslations - The detected languages were not supported.",
        detectedLanguages
      );
      return;
    }

    let host;
    try {
      host = documentURI.host;
    } catch {
      // nsIURI.host can throw if the URI scheme doesn't have a host. In this case
      // do not offer a translation.
      return;
    }
    if (TranslationsParent.#hostsOffered.has(host)) {
      // This host was already offered a translation.
      lazy.console.log(
        "maybeOfferTranslations - Host already offered a translation, so skip.",
        documentURI.spec
      );
      return;
    }
    const browser = this.browsingContext.top.embedderElement;
    if (!browser) {
      return;
    }
    TranslationsParent.#hostsOffered.add(host);
    const { CustomEvent } = browser.ownerGlobal;

    if (
      TranslationsParent.shouldNeverTranslateLanguage(
        detectedLanguages.docLangTag
      )
    ) {
      lazy.console.log(
        `maybeOfferTranslations - Should never translate language. "${detectedLanguages.docLangTag}"`,
        documentURI.spec
      );
      return;
    }
    if (this.shouldNeverTranslateSite()) {
      lazy.console.log(
        "maybeOfferTranslations - Should never translate site.",
        documentURI.spec
      );
      return;
    }

    if (detectedLanguages.docLangTag === detectedLanguages.userLangTag) {
      lazy.console.error(
        "maybeOfferTranslations - The document and user lang tag are the same, not offering a translation.",
        documentURI.spec
      );
      return;
    }

    // Only offer the translation if it's still the current page.
    let isCurrentPage = false;
    if (AppConstants.platform !== "android") {
      isCurrentPage =
        documentURI.spec ===
        this.browsingContext.topChromeWindow.gBrowser.selectedBrowser
          .documentURI.spec;
    } else {
      // In Android, the active window is the active tab.
      isCurrentPage = documentURI.spec === browser.documentURI.spec;
    }
    if (isCurrentPage) {
      lazy.console.log(
        "maybeOfferTranslations - Offering a translation",
        documentURI.spec,
        detectedLanguages
      );

      browser.dispatchEvent(
        new CustomEvent("TranslationsParent:OfferTranslation", {
          bubbles: true,
        })
      );
    }
  }

  /**
   * This is for testing purposes.
   */
  static resetHostsOffered() {
    TranslationsParent.#hostsOffered = new Set();
  }

  /**
   * Returns the word count of the text for a given language.
   *
   * @param {string} langTag - A BCP-47 language tag.
   * @param {string} text - The text for which to count words.
   *
   * @returns {number} - The count of words in the text.
   * @throws If a segmenter could not be created for the given language tag.
   */
  static countWords(langTag, text) {
    const segmenter = new Intl.Segmenter(langTag, { granularity: "word" });
    const segments = Array.from(segmenter.segment(text));
    return segments.filter(segment => segment.isWordLike).length;
  }

  /**
   * Retrieves the Translations actor from the current browser context.
   *
   * @param {object} browser - The browser object from which to get the context.
   *
   * @returns {object} The Translations actor for handling translation actions.
   * @throws {Error} Throws an error if the TranslationsParent actor cannot be found.
   */
  static getTranslationsActor(browser) {
    const actor =
      browser.browsingContext.currentWindowGlobal.getActor("Translations");

    if (!actor) {
      throw new Error("Unable to get the TranslationsParent actor.");
    }
    return actor;
  }

  /**
   * Detect if Wasm SIMD is supported, and cache the value. It's better to check
   * for support before downloading large binary blobs to a user who can't even
   * use the feature. This function also respects mocks and simulating unsupported
   * engines.
   *
   * @type {boolean}
   */
  static getIsTranslationsEngineSupported() {
    if (lazy.simulateUnsupportedEnginePref) {
      // Use the non-lazy console.log so that the user is always informed as to why
      // the translations engine is not working.
      console.log(
        "Translations: The translations engine is disabled through the pref " +
          '"browser.translations.simulateUnsupportedEngine".'
      );

      // The user is manually testing unsupported engines.
      return false;
    }

    if (TranslationsParent.#isTranslationsEngineMocked) {
      // A mocked translations engine is always supported.
      return true;
    }

    if (TranslationsParent.#isTranslationsEngineSupported === null) {
      TranslationsParent.#isTranslationsEngineSupported = detectSimdSupport();
    }

    return TranslationsParent.#isTranslationsEngineSupported;
  }

  /**
   * Only translate pages that match certain protocols, that way internal pages like
   * about:* pages will not be translated. Keep this logic up to date with the "matches"
   * array in the `toolkit/modules/ActorManagerParent.sys.mjs` definition.
   *
   * @param {object} gBrowser
   * @returns {boolean}
   */
  static isFullPageTranslationsRestrictedForPage(gBrowser) {
    const contentType = gBrowser.selectedBrowser.documentContentType;
    const scheme = gBrowser.currentURI.scheme;

    if (contentType === "application/pdf") {
      return true;
    }

    // Keep this logic up to date with the "matches" array in the
    // `toolkit/modules/ActorManagerParent.sys.mjs` definition.
    switch (scheme) {
      case "https":
      case "http":
      case "file":
        return false;
    }
    return true;
  }

  /**
   * Invalidates the #mostRecentTargetLanguages portion of #preferredLanguages.
   *
   * This means that the next time getPreferredLanguages() is called, it will
   * need to re-fetch the mostRecentTargetLanguages, but it may still use a
   * cached version of userSettingsLanguages.
   *
   * @see {getPreferredLanguages}
   */
  static #invalidateMostRecentTargetLanguages() {
    TranslationsParent.#mostRecentTargetLanguages = null;
    TranslationsParent.#preferredLanguages = null;
  }

  /**
   * Invalidates the #userSettingsLanguages portion of #preferredLanguages.
   *
   * This means that the next time getPreferredLanguages() is called, it will
   * need to re-fetch the userSettingsLanguages, but it may still use a
   * cached version of mostRecentTargetLanguages.
   *
   * @see {getPreferredLanguages}
   */
  static #invalidateUserSettingsLanguages() {
    TranslationsParent.#webContentLanguages = null;
    TranslationsParent.#userSettingsLanguages = null;
    TranslationsParent.#preferredLanguages = null;
  }

  /**
   * Provide a way for tests to override the system locales.
   *
   * @type {null | string[]}
   */
  static mockedSystemLocales = null;

  /**
   * The "Accept-Language" values that the localizer or user has indicated for
   * the preferences for the web. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language
   *
   * Note that this preference always has English in the fallback chain, even if the
   * user doesn't actually speak English, and to other languages they potentially do
   * not speak. However, this preference will be used as an indication that a user may
   * prefer this language.
   *
   * https://transvision.flod.org/string/?entity=toolkit/chrome/global/intl.properties:intl.accept_languages&repo=gecko_strings
   */
  static getWebContentLanguages() {
    if (!TranslationsParent.#webContentLanguages) {
      const values = Services.prefs
        .getComplexValue("intl.accept_languages", Ci.nsIPrefLocalizedString)
        .data.split(/\s*,\s*/g);

      TranslationsParent.#webContentLanguages = new Set();

      for (const locale of values) {
        try {
          // Wrap this in a try statement since users can manually edit this pref.
          TranslationsParent.#webContentLanguages.add(
            new Intl.Locale(locale).language
          );
        } catch {
          // The locale was invalid, discard it.
        }
      }

      if (
        !Services.prefs.prefHasUserValue("intl.accept_languages") &&
        Services.locale.appLocaleAsBCP47 !== "en" &&
        !Services.locale.appLocaleAsBCP47.startsWith("en-")
      ) {
        // The user hasn't customized their accept languages, this means that English
        // is always provided as a fallback language, even if it is not available.
        TranslationsParent.#webContentLanguages.delete("en");
      }

      if (TranslationsParent.#webContentLanguages.size === 0) {
        // The user has removed all of their web content languages, default to the
        // app locale.
        TranslationsParent.#webContentLanguages.add(
          new Intl.Locale(Services.locale.appLocaleAsBCP47).language
        );
      }
    }

    return TranslationsParent.#webContentLanguages;
  }

  /**
   * Retrieves the most recently translated-into target languages.
   *
   * This will return a cached value unless #invalidateMostRecentTargetLanguages
   * has been called.
   *
   * @see {#invalidateMostRecentTargetLanguages}
   *
   * @returns {string[]} - An ordered list of the most recent target languages.
   */
  static #getMostRecentTargetLanguages() {
    if (TranslationsParent.#mostRecentTargetLanguages) {
      return TranslationsParent.#mostRecentTargetLanguages;
    }

    // Store the mostRecentTargetLanguage values in reverse order
    // so that the most recently used language is first in the array.
    TranslationsParent.#mostRecentTargetLanguages = [
      ...lazy.mostRecentTargetLanguages,
    ].reverse();

    return TranslationsParent.#mostRecentTargetLanguages;
  }

  /**
   * Retrieves the user's preferred languages from the settings based on:
   *
   *   1. Web requested languages
   *   2. App languages
   *   3. OS language
   *
   * This will return a cached value unless #invalidateUserSettingsLanguages
   * has been called.
   *
   * @see {#invalidateUserSettingsLanguages}
   *
   * @returns {string[]} - An ordered list of the user's settings languages.
   */
  static #getUserSettingsLanguages() {
    if (TranslationsParent.#userSettingsLanguages) {
      return TranslationsParent.#userSettingsLanguages;
    }

    // The system language could also be a good option for a language to offer the user.
    const osPrefs = Cc["@mozilla.org/intl/ospreferences;1"].getService(
      Ci.mozIOSPreferences
    );
    const systemLocales =
      TranslationsParent.mockedSystemLocales ?? osPrefs.systemLocales;

    // Combine the locales together.
    const userSettingsLocales = new Set([
      ...TranslationsParent.getWebContentLanguages(),
      ...Services.locale.appLocalesAsBCP47,
      ...systemLocales,
    ]);

    // Attempt to convert the locales to lang tags. Do not completely trust the
    // values coming from preferences and the OS to have been validated as correct
    // BCP 47 locale identifiers.
    const userSettingsLangTags = new Set();
    for (const locale of userSettingsLocales) {
      try {
        userSettingsLangTags.add(new Intl.Locale(locale).language);
      } catch (_) {
        // The locale was invalid, discard it.
      }
    }

    // Convert the Set to an array to indicate that it is an ordered listing of languages.
    TranslationsParent.#userSettingsLanguages = [...userSettingsLangTags];
    return TranslationsParent.#userSettingsLanguages;
  }

  /**
   * An ordered list of preferred languages based on:
   *
   *   1. Most recent target languages
   *   2. Web requested languages
   *   3. App languages
   *   4. OS language
   *
   * @param {object} options
   * @param {string[]} [options.excludeLangTags] - BCP-47 language tags to intentionally exclude.
   *
   * @returns {string[]}
   */
  static getPreferredLanguages({ excludeLangTags } = {}) {
    if (TranslationsParent.#preferredLanguages) {
      return TranslationsParent.#preferredLanguages.filter(
        langTag => !excludeLangTags?.includes(langTag)
      );
    }

    if (!TranslationsParent.#observingLanguages) {
      Services.obs.addObserver(
        TranslationsParent.#invalidateUserSettingsLanguages,
        "intl:app-locales-changed"
      );
      Services.obs.addObserver(() => {
        TranslationsParent.#invalidateMostRecentTargetLanguages();
        Services.obs.notifyObservers(
          null,
          "translations:maybe-update-user-lang-tag"
        );
      }, "translations:most-recent-target-language-changed");
      Services.prefs.addObserver(
        "intl.accept_languages",
        TranslationsParent.#invalidateUserSettingsLanguages
      );

      TranslationsParent.#observingLanguages = true;
    }

    const preferredLanguages = new Set([
      ...TranslationsParent.#getMostRecentTargetLanguages(),
      ...TranslationsParent.#getUserSettingsLanguages(),
    ]);

    // Convert the Set to an array to indicate that it is an ordered listing of languages.
    TranslationsParent.#preferredLanguages = [...preferredLanguages];

    return TranslationsParent.#preferredLanguages.filter(
      langTag => !excludeLangTags?.includes(langTag)
    );
  }

  /**
   * Requests a new translations port.
   *
   * @param {string} fromLanguage - The BCP-47 from-language tag.
   * @param {string} toLanguage - The BCP-47 to-language tag.
   * @param {TranslationsParent} [translationsParent] - A TranslationsParent actor instance.
   *   NOTE: This value should be provided only if your port is associated with Full Page Translations.
   *   This will associate this translations port with the TranslationsParent actor instance, which will mean that changes
   *   in the translation state will affect the state of the Full-Page Translations UI, e.g. the URL-bar Translations button.
   *
   * @returns {Promise<MessagePort | undefined>} The port for communication with the translation engine, or undefined on failure.
   */
  static async requestTranslationsPort(
    fromLanguage,
    toLanguage,
    translationsParent
  ) {
    let translationsEngineParent;
    try {
      translationsEngineParent =
        await lazy.EngineProcess.getTranslationsEngineParent();
    } catch (error) {
      lazy.console.error("Failed to get the translation engine process", error);
      return undefined;
    }

    // The MessageChannel will be used for communicating directly between the content
    // process and the engine's process.
    const { port1, port2 } = new MessageChannel();
    translationsEngineParent.startTranslation(
      fromLanguage,
      toLanguage,
      port1,
      translationsParent
    );

    return port2;
  }

  async receiveMessage({ name, data }) {
    switch (name) {
      case "Translations:ReportLangTags": {
        const { documentElementLang, href } = data;
        const detectedLanguages = await this.getDetectedLanguages(
          documentElementLang,
          href
        ).catch(error => {
          // Detecting the languages can fail if the page gets destroyed before it
          // can be completed. This runs on every page that doesn't have a lang tag,
          // so only report the error if you have Translations logging turned on to
          // avoid console spam.
          lazy.console.log("Failed to get the detected languages.", error);
        });

        if (!detectedLanguages) {
          // The actor was already destroyed, and the detectedLanguages weren't reported
          // in time.
          return undefined;
        }

        this.languageState.detectedLanguages = detectedLanguages;

        if (this.shouldAutoTranslate(detectedLanguages)) {
          this.translate(
            detectedLanguages.docLangTag,
            detectedLanguages.userLangTag,
            true // reportAsAutoTranslate
          );
        } else {
          this.maybeOfferTranslations(detectedLanguages);
        }
        return undefined;
      }
      case "Translations:RequestPort": {
        const { requestedTranslationPair } = this.languageState;
        if (!requestedTranslationPair) {
          lazy.console.error(
            "A port was requested but no translation pair was previously requested"
          );
          return undefined;
        }

        if (this.#isDestroyed) {
          // This actor was already destroyed.
          return undefined;
        }

        if (!this.innerWindowId) {
          throw new Error(
            "The innerWindowId for the TranslationsParent was not available."
          );
        }

        const { fromLanguage, toLanguage } = requestedTranslationPair;
        const port = await TranslationsParent.requestTranslationsPort(
          fromLanguage,
          toLanguage,
          this
        );

        if (!port) {
          lazy.console.error(
            `Failed to create a translations port for language pair: (${fromLanguage} -> ${toLanguage})`
          );
          return undefined;
        }

        this.sendAsyncMessage(
          "Translations:AcquirePort",
          { port },
          [port] // Mark the port as transferable.
        );

        return undefined;
      }
      case "Translations:ReportFirstVisibleChange": {
        this.languageState.hasVisibleChange = true;
      }
    }
    return undefined;
  }

  /**
   * @param {string} fromLanguage
   * @param {string} toLanguage
   */
  static async getTranslationsEnginePayload(fromLanguage, toLanguage) {
    const wasmStartTime = Cu.now();
    const bergamotWasmArrayBufferPromise =
      TranslationsParent.#getBergamotWasmArrayBuffer();
    bergamotWasmArrayBufferPromise
      .then(() => {
        ChromeUtils.addProfilerMarker(
          "TranslationsParent",
          { innerWindowId: this.innerWindowId, startTime: wasmStartTime },
          "Loading bergamot wasm array buffer"
        );
      })
      .catch(() => {
        // Do nothing.
      });

    const modelStartTime = Cu.now();
    let files = await TranslationsParent.getLanguageTranslationModelFiles(
      fromLanguage,
      toLanguage
    );

    let languageModelFiles;
    if (files) {
      languageModelFiles = [files];
    } else {
      // No matching model was found, try to pivot between English.
      const [files1, files2] = await Promise.all([
        TranslationsParent.getLanguageTranslationModelFiles(
          fromLanguage,
          PIVOT_LANGUAGE
        ),
        TranslationsParent.getLanguageTranslationModelFiles(
          PIVOT_LANGUAGE,
          toLanguage
        ),
      ]);
      if (!files1 || !files2) {
        throw new Error(
          `No language models were found for ${fromLanguage} to ${toLanguage}`
        );
      }
      languageModelFiles = [files1, files2];
    }

    ChromeUtils.addProfilerMarker(
      "TranslationsParent",
      { innerWindowId: this.innerWindowId, startTime: modelStartTime },
      "Loading translation model files"
    );

    const bergamotWasmArrayBuffer = await bergamotWasmArrayBufferPromise;

    return {
      bergamotWasmArrayBuffer,
      languageModelFiles,
      isMocked: TranslationsParent.#isTranslationsEngineMocked,
    };
  }

  /**
   * Returns true if translations should auto-translate from the given
   * language, otherwise returns false.
   *
   * @param {LangTags} langTags
   * @returns {boolean}
   */
  #maybeAutoTranslate(langTags) {
    const windowState = this.getWindowState();
    if (windowState.isPageRestored) {
      // The user clicked the restore button. Respect it for one page load.
      windowState.isPageRestored = false;

      // Skip this auto-translation.
      return false;
    }

    return TranslationsParent.shouldAlwaysTranslateLanguage(langTags);
  }

  /**
   * Creates a lookup key that is unique to each fromLanguage-toLanguage pair.
   *
   * @param {string} fromLanguage
   * @param {string} toLanguage
   * @returns {string}
   */
  static languagePairKey(fromLanguage, toLanguage) {
    return `${fromLanguage},${toLanguage}`;
  }

  /**
   * The cached language pairs.
   *
   * @type {Promise<Array<LanguagePair>> | null}
   */
  static #languagePairs = null;

  /**
   * Clears the cached list of language pairs, notifying observers that the
   * available language pairs have changed.
   */
  static #clearCachedLanguagePairs() {
    TranslationsParent.#languagePairs = null;
    Services.obs.notifyObservers(null, "translations:language-pairs-changed");
  }

  /**
   * Get the list of translation pairs supported by the translations engine.
   *
   * @returns {Promise<Array<LanguagePair>>}
   */
  static getLanguagePairs() {
    if (!TranslationsParent.#languagePairs) {
      TranslationsParent.#languagePairs =
        TranslationsParent.#getTranslationModelRecords().then(records => {
          const languagePairMap = new Map();

          for (const { fromLang, toLang } of records.values()) {
            const key = TranslationsParent.languagePairKey(fromLang, toLang);
            if (!languagePairMap.has(key)) {
              languagePairMap.set(key, { fromLang, toLang });
            }
          }
          return Array.from(languagePairMap.values());
        });
      TranslationsParent.#languagePairs.catch(() => {
        TranslationsParent.#clearCachedLanguagePairs();
      });
    }
    return TranslationsParent.#languagePairs;
  }

  /**
   * Get the list of languages and their display names, sorted by their display names.
   * This is more expensive of a call than getLanguagePairs since the display names
   * are looked up.
   *
   * This is all of the information needed to render dropdowns for translation
   * language selection.
   *
   * @returns {Promise<SupportedLanguages>}
   */
  static async getSupportedLanguages() {
    await chaosMode(1 / 4);
    const languagePairs = await TranslationsParent.getLanguagePairs();

    /** @type {Set<string>} */
    const fromLanguages = new Set();
    /** @type {Set<string>} */
    const toLanguages = new Set();

    for (const { fromLang, toLang } of languagePairs) {
      fromLanguages.add(fromLang);
      toLanguages.add(toLang);
    }

    // Build a map of the langTag to the display name.
    /** @type {Map<string, string>} */
    const displayNames = new Map();
    {
      const dn = new Services.intl.DisplayNames(undefined, {
        type: "language",
      });

      for (const langTagSet of [fromLanguages, toLanguages]) {
        for (const langTag of langTagSet.keys()) {
          if (displayNames.has(langTag)) {
            continue;
          }
          displayNames.set(langTag, dn.of(langTag));
        }
      }
    }

    const addDisplayName = langTag => ({
      langTag,
      displayName: displayNames.get(langTag),
    });

    const sort = (a, b) => a.displayName.localeCompare(b.displayName);

    return {
      languagePairs,
      fromLanguages: Array.from(fromLanguages.keys())
        .map(addDisplayName)
        .sort(sort),
      toLanguages: Array.from(toLanguages.keys())
        .map(addDisplayName)
        .sort(sort),
    };
  }

  /**
   * Create a unique list of languages, sorted by the display name.
   *
   * @param {object} supportedLanguages
   * @returns {Array<{ langTag: string, displayName: string}>}
   */
  static getLanguageList(supportedLanguages) {
    const displayNames = new Map();
    for (const languages of [
      supportedLanguages.fromLanguages,
      supportedLanguages.toLanguages,
    ]) {
      for (const { langTag, displayName } of languages) {
        displayNames.set(langTag, displayName);
      }
    }

    let appLangTag = new Intl.Locale(Services.locale.appLocaleAsBCP47).language;

    // Don't offer to download the app's language.
    displayNames.delete(appLangTag);

    // Sort the list of languages by the display names.
    return [...displayNames.entries()]
      .map(([langTag, displayName]) => ({
        langTag,
        displayName,
      }))
      .sort((a, b) => a.displayName.localeCompare(b.displayName));
  }

  /**
   * Get the display name for the given language Tag.
   *
   * @param {string} langTag
   * @returns {string}
   */
  static getLanguageDisplayName(langTag) {
    // Services.intl.getLanguageDisplayNames takes a list of language codes and
    // returns a list of correspoding display names. Hence the langTag is sent as a list
    let displayName = Services.intl.getLanguageDisplayNames(undefined, [
      langTag,
    ]);
    return displayName[0];
  }

  /**
   * Handles records that were deleted in a Remote Settings "sync" event by
   * attempting to delete any previously downloaded attachments that are
   * associated with the deleted records.
   *
   * @param {RemoteSettingsClient} client
   *  - The Remote Settings client for which to handle deleted records.
   * @param {TranslationModelRecord[]} deletedRecords
   *  - The list of records that were deleted from the client's database.
   */
  static async #handleDeletedRecords(client, deletedRecords) {
    // Attempt to delete any downloaded attachments that are associated with deleted records.
    const failedDeletions = [];
    await Promise.all(
      deletedRecords.map(async record => {
        try {
          if (await client.attachments.isDownloaded(record)) {
            await client.attachments.deleteDownloaded(record);
          }
        } catch (error) {
          failedDeletions.push({ record, error });
        }
      })
    );

    // Report deletion failures if any occurred.
    if (failedDeletions.length) {
      lazy.console.warn(
        'Remote Settings "sync" event failed to delete attachments for deleted records.'
      );
      for (const { record, error } of failedDeletions) {
        lazy.console.error(
          `Failed to delete attachment for deleted record ${record.name}: ${error}`
        );
      }
    }
  }

  /**
   * Handles records that were updated in a Remote Settings "sync" event by
   * attempting to delete any previously downloaded attachments that are
   * associated with the old record versions, then downloading attachments
   * that are associated with the new record versions.
   *
   * @param {RemoteSettingsClient} client
   *  - The Remote Settings client for which to handle updated records.
   * @param {{old: TranslationModelRecord, new: TranslationModelRecord}[]} updatedRecords
   *  - The list of records that were updated in the client's database.
   */
  static async #handleUpdatedRecords(client, updatedRecords) {
    // Gather any updated records whose attachments were previously downloaded.
    const recordsWithAttachmentsToReplace = [];
    for (const {
      old: recordBeforeUpdate,
      new: recordAfterUpdate,
    } of updatedRecords) {
      if (await client.attachments.isDownloaded(recordBeforeUpdate)) {
        recordsWithAttachmentsToReplace.push({
          recordBeforeUpdate,
          recordAfterUpdate,
        });
      }
    }

    // Attempt to delete all of the attachments for the old versions of the updated records.
    const failedDeletions = [];
    await Promise.all(
      recordsWithAttachmentsToReplace.map(async ({ recordBeforeUpdate }) => {
        try {
          await client.attachments.deleteDownloaded(recordBeforeUpdate);
        } catch (error) {
          failedDeletions.push({ record: recordBeforeUpdate, error });
        }
      })
    );

    // Report deletion failures if any occurred.
    if (failedDeletions.length) {
      lazy.console.warn(
        'Remote Settings "sync" event failed to delete old record attachments for updated records.'
      );
      for (const { record, error } of failedDeletions) {
        lazy.console.error(
          `Failed to delete old attachment for updated record ${record.name}: ${error.reason}`
        );
      }
    }

    // Attempt to download all of the attachments for the new versions of the updated records.
    const failedDownloads = [];
    await Promise.all(
      recordsWithAttachmentsToReplace.map(async ({ recordAfterUpdate }) => {
        try {
          await client.attachments.download(recordAfterUpdate);
        } catch (error) {
          failedDownloads.push({ record: recordAfterUpdate, error });
        }
      })
    );

    // Report deletion failures if any occurred.
    if (failedDownloads.length) {
      lazy.console.warn(
        'Remote Settings "sync" event failed to download new record attachments for updated records.'
      );
      for (const { record, error } of failedDeletions) {
        lazy.console.error(
          `Failed to download new attachment for updated record ${record.name}: ${error.reason}`
        );
      }
    }
  }

  /**
   * Handles the "sync" event for the Translations Models Remote Settings collection.
   * This is called whenever models are created, updated, or deleted from the Remote Settings database.
   *
   * @param {object} event - The sync event.
   * @param {object} event.data - The data associated with the sync event.
   * @param {TranslationModelRecord[]} event.data.created
   *  - The list of Remote Settings records that were created in the sync event.
   * @param {{old: TranslationModelRecord, new: TranslationModelRecord}[]} event.data.updated
   *  - The list of Remote Settings records that were updated in the sync event.
   * @param {TranslationModelRecord[]} event.data.deleted
   *  - The list of Remote Settings records that were deleted in the sync event.
   */
  static async #handleTranslationsModelsSync({
    data: { created, updated, deleted },
  }) {
    const client = TranslationsParent.#translationModelsRemoteClient;
    if (!client) {
      lazy.console.error(
        "Translations models client was not present when receiving a sync event."
      );
      return;
    }

    // Invalidate cached data.
    TranslationsParent.#clearCachedLanguagePairs();
    TranslationsParent.#translationModelRecords = null;

    // Language model attachments will only be downloaded when they are used.
    lazy.console.log(
      `Remote Settings "sync" event for language-model records`,
      {
        created,
        updated,
        deleted,
      }
    );

    if (deleted.length) {
      await TranslationsParent.#handleDeletedRecords(client, deleted);
    }

    if (updated.length) {
      await TranslationsParent.#handleUpdatedRecords(client, updated);
    }

    // There is nothing to do for created records, since they will not have any previously downloaded attachments.
  }

  /**
   * Handles the "sync" event for the Translations WASM Remote Settings collection.
   * This is called whenever models are created, updated, or deleted from the Remote Settings database.
   *
   * @param {object} event - The sync event.
   * @param {object} event.data - The data associated with the sync event.
   * @param {TranslationModelRecord[]} event.data.created
   *  - The list of Remote Settings records that were created in the sync event.
   * @param {{old: TranslationModelRecord, new: TranslationModelRecord}[]} event.data.updated
   *  - The list of Remote Settings records that were updated in the sync event.
   * @param {TranslationModelRecord[]} event.data.deleted
   *  - The list of Remote Settings records that were deleted in the sync event.
   */
  static async #handleTranslationsWasmSync({
    data: { created, updated, deleted },
  }) {
    const client = TranslationsParent.#translationsWasmRemoteClient;
    if (!client) {
      lazy.console.error(
        "Translations WASM client was not present when receiving a sync event."
      );
      return;
    }

    lazy.console.log(`Remote Settings "sync" event for WASM records`, {
      created,
      updated,
      deleted,
    });

    // Invalidate cached data.
    TranslationsParent.#bergamotWasmRecord = null;

    if (deleted.length) {
      await TranslationsParent.#handleDeletedRecords(client, deleted);
    }

    if (updated.length) {
      await TranslationsParent.#handleUpdatedRecords(client, updated);
    }

    // There is nothing to do for created records, since they will not have any previously downloaded attachments.
  }

  /**
   * Lazily initializes the RemoteSettingsClient for the language models.
   *
   * @returns {RemoteSettingsClient}
   */
  static #getTranslationModelsRemoteClient() {
    if (TranslationsParent.#translationModelsRemoteClient) {
      return TranslationsParent.#translationModelsRemoteClient;
    }

    /** @type {RemoteSettingsClient} */
    const client = lazy.RemoteSettings("translations-models");
    TranslationsParent.#translationModelsRemoteClient = client;
    client.on("sync", TranslationsParent.#handleTranslationsModelsSync);

    return client;
  }

  /**
   * Retrieves the maximum major version of each record in the RemoteSettingsClient.
   *
   * If the client contains two different-version copies of the same record (e.g. 1.0 and 1.1)
   * then only the 1.1-version record will be returned in the resulting collection.
   *
   * @param {RemoteSettingsClient} remoteSettingsClient
   * @param {object} [options]
   *   @param {object} [options.filters={}]
   *     The filters to apply when retrieving the records from RemoteSettings.
   *     Filters should correspond to properties on the RemoteSettings records themselves.
   *     For example, A filter to retrieve only records with a `fromLang` value of "en" and a `toLang` value of "es":
   *     { filters: { fromLang: "en", toLang: "es" } }
   *   @param {number} options.majorVersion
   *   @param {Function} [options.lookupKey=(record => record.name)]
   *     The function to use to extract a lookup key from each record.
   *     This function should take a record as input and return a string that represents the lookup key for the record.
   *     For most record types, the name (default) is sufficient, however if a collection contains records with
   *     non-unique name values, it may be necessary to provide an alternative function here.
   * @returns {Array<TranslationModelRecord | WasmRecord>}
   */
  static async getMaxVersionRecords(
    remoteSettingsClient,
    { filters = {}, majorVersion, lookupKey = record => record.name } = {}
  ) {
    if (!majorVersion) {
      throw new Error("Expected the records to have a major version.");
    }
    try {
      await chaosMode(1 / 4);
    } catch (_error) {
      // Simulate an error by providing empty records.
      return [];
    }
    const retrievedRecords = await remoteSettingsClient.get({
      // Pull the records from the network if empty.
      syncIfEmpty: true,
      // Do not load the JSON dump if it is newer.
      //
      // The JSON dump comes from the Prod RemoteSettings channel
      // so we shouldn't ever have an issue with the Prod server
      // being older than the JSON dump itself (this is good).
      //
      // However, setting this to true will prevent us from
      // testing RemoteSettings on the Dev and Stage
      // environments if they happen to be older than the
      // most recent JSON dump from Prod.
      loadDumpIfNewer: false,
      // Don't verify the signature if the client is mocked.
      verifySignature: VERIFY_SIGNATURES_FROM_FS,
      // Apply any filters for retrieving the records.
      filters,
    });

    // Create a mapping to only the max version of each record discriminated by
    // the result of the lookupKey() function.
    const keyToRecord = new Map();

    for (const record of retrievedRecords) {
      const key = lookupKey(record);
      const existing = keyToRecord.get(key);

      if (!record.version) {
        lazy.console.error(record);
        throw new Error("Expected the record to have a version.");
      }
      if (
        TranslationsParent.isBetterRecordVersion(
          majorVersion,
          record.version,
          existing?.version
        )
      ) {
        keyToRecord.set(key, record);
      }
    }

    return Array.from(keyToRecord.values());
  }

  /**
   * Applies the constraint of matching for the best matching major version.
   *
   * @param {number} majorVersion
   * @param {string} nextVersion
   * @param {string} [existingVersion]
   */
  static isBetterRecordVersion(majorVersion, nextVersion, existingVersion) {
    return (
      // Check that this is a major version record we can support.
      Services.vc.compare(`${majorVersion}.0a`, nextVersion) <= 0 &&
      Services.vc.compare(`${majorVersion + 1}.0a`, nextVersion) > 0 &&
      // Check that the new record is bigger version number
      (!existingVersion ||
        Services.vc.compare(existingVersion, nextVersion) < 0)
    );
  }

  /**
   * Lazily initializes the model records, and returns the cached ones if they
   * were already retrieved. The key of the returned `Map` is the record id.
   *
   * @returns {Promise<Map<string, TranslationModelRecord>>}
   */
  static async #getTranslationModelRecords() {
    if (!TranslationsParent.#translationModelRecords) {
      // Place the records into a promise to prevent any races.
      TranslationsParent.#translationModelRecords = (async () => {
        const records = new Map();
        const now = Date.now();
        const client = TranslationsParent.#getTranslationModelsRemoteClient();

        // Load the models. If no data is present, then there will be an initial sync.
        // Rely on Remote Settings for the syncing strategy for receiving updates.
        lazy.console.log(`Getting remote language models.`);

        /** @type {TranslationModelRecord[]} */
        const translationModelRecords =
          await TranslationsParent.getMaxVersionRecords(client, {
            majorVersion: TranslationsParent.LANGUAGE_MODEL_MAJOR_VERSION,
            // Names in this collection are not unique, so we are appending the languagePairKey
            // to guarantee uniqueness.
            lookupKey: record =>
              `${record.name}${TranslationsParent.languagePairKey(
                record.fromLang,
                record.toLang
              )}`,
          });

        if (translationModelRecords.length === 0) {
          throw new Error("Unable to retrieve the translation models.");
        }

        for (const record of TranslationsParent.ensureLanguagePairsHavePivots(
          translationModelRecords
        )) {
          records.set(record.id, record);
        }

        const duration = (Date.now() - now) / 1000;
        lazy.console.log(
          `Remote language models loaded in ${duration} seconds.`,
          records
        );

        return records;
      })();

      TranslationsParent.#translationModelRecords.catch(() => {
        TranslationsParent.#translationModelRecords = null;
      });
    }

    return TranslationsParent.#translationModelRecords;
  }

  /**
   * This implementation assumes that every language pair has access to the
   * pivot language. If any languages are added without a pivot language, or the
   * pivot language is changed, then this implementation will need a more complicated
   * language solver. This means that any UI pickers would need to be updated, and
   * the pivot language selection would need a solver.
   *
   * @param {TranslationModelRecord[] | LanguagePair[]} records
   */
  static ensureLanguagePairsHavePivots(records) {
    if (!AppConstants.DEBUG) {
      // Only run this check on debug builds as it's in the performance critical first
      // page load path.
      return records;
    }
    // lang -> pivot
    const hasToPivot = new Set();
    // pivot -> en
    const hasFromPivot = new Set();

    const fromLangs = new Set();
    const toLangs = new Set();

    for (const { fromLang, toLang } of records) {
      fromLangs.add(fromLang);
      toLangs.add(toLang);

      if (toLang === PIVOT_LANGUAGE) {
        // lang -> pivot
        hasToPivot.add(fromLang);
      }
      if (fromLang === PIVOT_LANGUAGE) {
        // pivot -> en
        hasFromPivot.add(toLang);
      }
    }

    const fromLangsToRemove = new Set();
    const toLangsToRemove = new Set();

    for (const lang of fromLangs) {
      if (lang === PIVOT_LANGUAGE) {
        continue;
      }
      // Check for "lang -> pivot"
      if (!hasToPivot.has(lang)) {
        TranslationsParent.reportError(
          new Error(
            `The "from" language model "${lang}" is being discarded as it doesn't have a pivot language.`
          )
        );
        fromLangsToRemove.add(lang);
      }
    }

    for (const lang of toLangs) {
      if (lang === PIVOT_LANGUAGE) {
        continue;
      }
      // Check for "pivot -> lang"
      if (!hasFromPivot.has(lang)) {
        TranslationsParent.reportError(
          new Error(
            `The "to" language model "${lang}" is being discarded as it doesn't have a pivot language.`
          )
        );
        toLangsToRemove.add(lang);
      }
    }

    const after = records.filter(record => {
      if (fromLangsToRemove.has(record.fromLang)) {
        return false;
      }
      if (toLangsToRemove.has(record.toLang)) {
        return false;
      }
      return true;
    });
    return after;
  }

  /**
   * Lazily initializes the RemoteSettingsClient for the downloaded wasm binary data.
   *
   * @returns {RemoteSettingsClient}
   */
  static #getTranslationsWasmRemoteClient() {
    if (TranslationsParent.#translationsWasmRemoteClient) {
      return TranslationsParent.#translationsWasmRemoteClient;
    }

    /** @type {RemoteSettingsClient} */
    const client = lazy.RemoteSettings("translations-wasm");
    TranslationsParent.#translationsWasmRemoteClient = client;
    client.on("sync", TranslationsParent.#handleTranslationsWasmSync);

    return client;
  }

  /** @type {Promise<WasmRecord> | null} */
  static #bergamotWasmRecord = null;

  /** @type {boolean} */
  static #lookForLocalWasmBuild = true;

  /**
   * This is used to load a local copy of the Bergamot translations engine, if it exists.
   * From a local build of Firefox:
   *
   * 1. Run the python script:
   *   ./toolkit/components/translations/bergamot-translator/build-bergamot.py --debug
   *
   * 2. Uncomment the .wasm file in: toolkit/components/translations/jar.mn
   * 3. Run: ./mach build
   * 4. Run: ./mach run
   */
  static async #maybeFetchLocalBergamotWasmArrayBuffer() {
    if (TranslationsParent.#lookForLocalWasmBuild) {
      // Attempt to get a local copy of the translator. Most likely this will be a 404.
      try {
        const response = await fetch(
          "chrome://global/content/translations/bergamot-translator-worker.wasm"
        );
        const arrayBuffer = response.arrayBuffer();
        lazy.console.log(`Using a local copy of Bergamot.`);
        return arrayBuffer;
      } catch {
        // Only attempt to fetch once, if it fails don't try again.
        TranslationsParent.#lookForLocalWasmBuild = false;
      }
    }
    return null;
  }

  /**
   * Bergamot is the translation engine that has been compiled to wasm. It is shipped
   * to the user via Remote Settings.
   *
   * https://github.com/mozilla/bergamot-translator/
   */
  /**
   * @returns {Promise<ArrayBuffer>}
   */
  static async #getBergamotWasmArrayBuffer() {
    const start = Date.now();
    const client = TranslationsParent.#getTranslationsWasmRemoteClient();

    const localCopy =
      await TranslationsParent.#maybeFetchLocalBergamotWasmArrayBuffer();
    if (localCopy) {
      return localCopy;
    }

    if (!TranslationsParent.#bergamotWasmRecord) {
      // Place the records into a promise to prevent any races.
      TranslationsParent.#bergamotWasmRecord = (async () => {
        // Load the wasm binary from remote settings, if it hasn't been already.
        lazy.console.log(`Getting remote bergamot-translator wasm records.`);

        /** @type {WasmRecord[]} */
        const wasmRecords = await TranslationsParent.getMaxVersionRecords(
          client,
          {
            filters: { name: "bergamot-translator" },
            majorVersion: TranslationsParent.BERGAMOT_MAJOR_VERSION,
          }
        );

        if (wasmRecords.length === 0) {
          // The remote settings client provides an empty list of records when there is
          // an error.
          throw new Error(
            "Unable to get the bergamot translator from Remote Settings."
          );
        }

        if (wasmRecords.length > 1) {
          TranslationsParent.reportError(
            new Error(
              "Expected the bergamot-translator to only have 1 record."
            ),
            wasmRecords
          );
        }
        const [record] = wasmRecords;
        lazy.console.log(
          `Using ${record.name}@${record.release} release version ${record.version} first released on Fx${record.fx_release}`,
          record
        );
        return record;
      })();
    }
    // Unlike the models, greedily download the wasm. It will pull it from a locale
    // cache on disk if it's already been downloaded. Do not retain a copy, as
    // this will be running in the parent process. It's not worth holding onto
    // this much memory, so reload it every time it is needed.

    try {
      await chaosModeError(1 / 3);

      /** @type {{buffer: ArrayBuffer}} */
      const { buffer } = await client.attachments.download(
        await TranslationsParent.#bergamotWasmRecord
      );

      const duration = Date.now() - start;
      lazy.console.log(
        `"bergamot-translator" wasm binary loaded in ${duration / 1000} seconds`
      );

      return buffer;
    } catch (error) {
      TranslationsParent.#bergamotWasmRecord = null;
      throw error;
    }
  }

  /**
   * Deletes language files that match a language.
   * Note, this call doesn't have directionality because it is checking and deleting files
   * for both sides of the pair that are not involved in a pivot.
   *
   * @param {string} languageA The BCP 47 language tag.
   * @param {string} languageB The BCP 47 language tag.
   * @param {boolean} deletePivots When true, the request may delete files that could be used for another language's pivot to complete a translation.
   *                               When false, the request will not delete files that could be used in another language's pivot.
   */
  static async deleteLanguageFilesToAndFromPair(
    languageA,
    languageB,
    deletePivots
  ) {
    const client = TranslationsParent.#getTranslationModelsRemoteClient();
    return Promise.all(
      Array.from(
        await TranslationsParent.getRecordsForTranslatingToAndFromPair(
          languageA,
          languageB,
          deletePivots
        )
      ).map(record => {
        lazy.console.log("Deleting record", record);
        return client.attachments.deleteDownloaded(record);
      })
    );
  }

  /**
   * Deletes language files that match a language.
   * This function operates based on the current app language.
   *
   * @param {string} language The BCP 47 language tag.
   */
  static async deleteLanguageFiles(language) {
    const appLanguage = new Intl.Locale(Services.locale.appLocaleAsBCP47)
      .language;
    return TranslationsParent.deleteLanguageFilesToAndFromPair(
      language,
      appLanguage,
      /* deletePivots */ false
    );
  }

  /**
   * Download language files that match a language.
   *
   * @param {string} language The BCP 47 language tag.
   */
  static async downloadLanguageFiles(language) {
    const client = TranslationsParent.#getTranslationModelsRemoteClient();

    const queue = [];

    for (const record of await TranslationsParent.getRecordsForTranslatingToAndFromAppLanguage(
      language,
      /* includePivotRecords */ true
    )) {
      const download = () => {
        lazy.console.log("Downloading record", record.name, record.id);
        return client.attachments.download(record);
      };
      queue.push({ download });
    }

    return downloadManager(queue);
  }

  /**
   * Download all files used for translations.
   */
  static async downloadAllFiles() {
    const client = TranslationsParent.#getTranslationModelsRemoteClient();

    const queue = [];

    for (const record of (
      await TranslationsParent.#getTranslationModelRecords()
    ).values()) {
      queue.push({
        // The download may be attempted multiple times.
        onFailure: () => {
          console.error("Failed to download", record.name);
        },
        download: () => client.attachments.download(record),
      });
    }

    queue.push({
      download: () => TranslationsParent.#getBergamotWasmArrayBuffer(),
    });

    return downloadManager(queue);
  }

  /**
   * Delete all language model files.
   *
   * @returns {Promise<string[]>} A list of record IDs.
   */
  static async deleteAllLanguageFiles() {
    const client = TranslationsParent.#getTranslationModelsRemoteClient();
    await chaosMode();
    await client.attachments.deleteAll();
    return [...(await TranslationsParent.#getTranslationModelRecords()).keys()];
  }

  /**
   * Delete all language model files not a part of a complete language package. Also known as
   * the language model "cache" in the UI.
   *
   * Usage is to clean up language models that may be lingering in the file system and are not
   * a part of a downloaded language model package.
   *
   * For example, this deletes models that were acquired via a translation on-the-fly, not
   * the complete package of language models for a language that has both directions.
   *
   * A complete language package for this function is considered both directions, when available,
   * for example, en->es (downloaded) and es->en (downloaded) is complete and nothing will be deleted.
   *
   * When the language is not symmetric, for example nn->en (downloaded), then this is also considered a
   * complete package and not subject to deletion. (Note, in this example, en->nn is not available.)
   *
   * This will delete a downloaded model set when it is incomplete, for example en->es (downloaded) and es->en
   * (not-downloaded) will delete en->es to clear the lingering one-sided package.
   *
   * @returns {Set<string>}  Directional language pairs in the form of "fromLang,toLang" that indicates translation pairs that were deleted.
   */
  static async deleteCachedLanguageFiles() {
    const languagePairs = await TranslationsParent.getLanguagePairs();

    const deletionRequest = [];
    let deletedPairs = new Set();

    for (const { fromLang, toLang } of languagePairs) {
      const { downloadedPairs, nonDownloadedPairs } =
        await TranslationsParent.getDownloadedFileStatusToAndFromPair(
          fromLang,
          toLang
        );

      if (downloadedPairs.size && nonDownloadedPairs.size) {
        // It is possible that additional pairs are listed, but in general,
        // this should be parallel with deletion requests.
        downloadedPairs.forEach(langPair => deletedPairs.add(langPair));
        deletionRequest.push(
          TranslationsParent.deleteLanguageFilesToAndFromPair(
            fromLang,
            toLang,
            /* deletePivots */ false
          )
        );
      }
    }
    await Promise.all(deletionRequest);

    return deletedPairs;
  }

  /**
   * Contains information about what files are downloaded between a language pair.
   * Note, this call doesn't have directionality because it is checking both sides of the pair.
   *
   * @param {string} languageA The BCP 47 language tag.
   * @param {string} languageB The BCP 47 language tag.
   *
   * @returns {object} status The status between the pairs.
   * @returns {Set<string>} status.downloadedPairs A set of strings that has directionality about what side
   *                                                is downloaded, in the format "fromLang,toLang".
   * @returns {Set<string>} status.nonDownloadedPairs A set of strings that has directionality about what side
   *                                                   is not downloaded, in the format "fromLang,toLang". It is possible to have files both in nonDownloadedFiles
   *                                                   and downloadedFiles in the case of incomplete downloads.
   */

  static async getDownloadedFileStatusToAndFromPair(languageA, languageB) {
    const client = TranslationsParent.#getTranslationModelsRemoteClient();
    let downloadedPairs = new Set();
    let nonDownloadedPairs = new Set();

    for (const record of await TranslationsParent.getRecordsForTranslatingToAndFromPair(
      languageA,
      languageB,
      /* includePivotRecords */ true
    )) {
      let isDownloaded = false;
      if (TranslationsParent.isInAutomation()) {
        isDownloaded = record.attachment.isDownloaded;
      } else {
        isDownloaded = await client.attachments.isDownloaded(record);
      }

      if (isDownloaded) {
        downloadedPairs.add(
          TranslationsParent.languagePairKey(record.fromLang, record.toLang)
        );
      } else {
        nonDownloadedPairs.add(
          TranslationsParent.languagePairKey(record.fromLang, record.toLang)
        );
      }
    }

    return { downloadedPairs, nonDownloadedPairs };
  }

  /**
   * Only returns true if all language files are present for a requested language.
   * It's possible only half the files exist for a pivot translation into another
   * language, or there was a download error, and we're still missing some files.
   *
   * @param {string} requestedLanguage The BCP 47 language tag.
   */
  static async hasAllFilesForLanguage(requestedLanguage) {
    const client = TranslationsParent.#getTranslationModelsRemoteClient();
    for (const record of await TranslationsParent.getRecordsForTranslatingToAndFromAppLanguage(
      requestedLanguage,
      /* includePivotRecords */ true
    )) {
      if (!(await client.attachments.isDownloaded(record))) {
        return false;
      }
    }

    return true;
  }

  /**
   * Get the necessary files for translating between two given languages.
   * This may require the files for a pivot language translation
   * if there is no language model for a direct translation.
   * Note, this call doesn't have directionality because it is checking both sides of the pair.
   *
   * @param {string} languageA The BCP 47 language tag.
   * @param {string} languageB The BCP 47 language tag.
   * @param {boolean} includePivotRecords - When true, this will include a list of records with any required pivots.
   *                                        An example using true would be to determine which files to download to complete a translation.
   *                                        When false, this will not include the list of pivot records to achieve a translations.
   *                                        An example using false would be to determine which  records to delete, but wanting to be
   *                                        cautions to avoid deleting model files used by another language.
   * @returns {Set<TranslationModelRecord>}
   */
  static async getRecordsForTranslatingToAndFromPair(
    languageA,
    languageB,
    includePivotRecords
  ) {
    const records = await TranslationsParent.#getTranslationModelRecords();

    let matchedRecords = new Set();

    if (languageA === languageB) {
      // There are no records if the requested language and app language are the same.
      return matchedRecords;
    }

    const addLanguagePair = (fromLang, toLang) => {
      let matchFound = false;
      for (const record of records.values()) {
        if (record.fromLang === fromLang && record.toLang === toLang) {
          matchedRecords.add(record);
          matchFound = true;
        }
      }
      return matchFound;
    };

    if (
      // Is there a direct translation?
      !addLanguagePair(languageA, languageB)
    ) {
      // This is no direct translation, get the pivot files.
      addLanguagePair(languageA, PIVOT_LANGUAGE);
      // These files may be required for other pivot translations, so don't list
      // them if we are deleting records.
      if (includePivotRecords) {
        addLanguagePair(PIVOT_LANGUAGE, languageB);
      }
    }

    if (
      // Is there a direct translation?
      !addLanguagePair(languageB, languageA)
    ) {
      // This is no direct translation, get the pivot files.
      addLanguagePair(PIVOT_LANGUAGE, languageA);
      // These files may be required for other pivot translations, so don't list
      // them if we are deleting records.
      if (includePivotRecords) {
        addLanguagePair(languageB, PIVOT_LANGUAGE);
      }
    }

    return matchedRecords;
  }

  /**
   * Get the necessary files for translating to and from the app language and a
   * requested language. This may require the files for a pivot language translation
   * if there is no language model for a direct translation.
   *
   * @param {string} requestedLanguage The BCP 47 language tag.
   * @param {boolean} includePivotRecords - When true, this will include a list of records with any required pivots.
   *                                        An example using true would be to determine which files to download to complete a translation.
   *                                        When false, this will not include the list of pivot records to achieve a translations.
   *                                        An example using false would be to determine which  records to delete, but wanting to be
   *                                        cautions to avoid deleting model files used by another language.
   * @returns {Set<TranslationModelRecord>}
   */
  static async getRecordsForTranslatingToAndFromAppLanguage(
    requestedLanguage,
    includePivotRecords
  ) {
    const appLanguage = new Intl.Locale(Services.locale.appLocaleAsBCP47)
      .language;

    return TranslationsParent.getRecordsForTranslatingToAndFromPair(
      requestedLanguage,
      appLanguage,
      includePivotRecords
    );
  }

  /**
   * Gets the language model files in an array buffer by downloading attachments from
   * Remote Settings, or retrieving them from the local cache. Each translation
   * requires multiple files.
   *
   * Results are only returned if the model is found.
   *
   * @param {string} fromLanguage
   * @param {string} toLanguage
   * @param {boolean} withQualityEstimation
   * @returns {null | LanguageTranslationModelFiles}
   */
  static async getLanguageTranslationModelFiles(
    fromLanguage,
    toLanguage,
    withQualityEstimation = false
  ) {
    const client = TranslationsParent.#getTranslationModelsRemoteClient();

    lazy.console.log(
      `Beginning model downloads: "${fromLanguage}" to "${toLanguage}"`
    );

    const records = [
      ...(await TranslationsParent.#getTranslationModelRecords()).values(),
    ];

    /** @type {LanguageTranslationModelFiles} */
    let results;

    // Use Promise.all to download (or retrieve from cache) the model files in parallel.
    await Promise.all(
      records.map(async record => {
        if (record.fileType === "qualityModel" && !withQualityEstimation) {
          // Do not include the quality models if they aren't needed.
          return;
        }

        if (record.fromLang !== fromLanguage || record.toLang !== toLanguage) {
          // Only use models that match.
          return;
        }

        if (!results) {
          results = {};
        }

        const start = Date.now();

        // Download or retrieve from the local cache:

        await chaosMode(1 / 3);

        /** @type {{buffer: ArrayBuffer }} */
        const { buffer } = await client.attachments.download(record);

        results[record.fileType] = {
          buffer,
          record,
        };

        const duration = Date.now() - start;
        lazy.console.log(
          `Translation model fetched in ${duration / 1000} seconds:`,
          record.fromLang,
          record.toLang,
          record.fileType,
          record.version
        );
      })
    );

    if (!results) {
      // No model files were found, pivoting will be required.
      return null;
    }

    // Validate that all of the files we expected were actually available and
    // downloaded.

    if (!results.model) {
      throw new Error(
        `No model file was found for "${fromLanguage}" to "${toLanguage}."`
      );
    }

    if (!results.lex) {
      throw new Error(
        `No lex file was found for "${fromLanguage}" to "${toLanguage}."`
      );
    }

    if (withQualityEstimation && !results.qualityModel) {
      throw new Error(
        `No quality file was found for "${fromLanguage}" to "${toLanguage}."`
      );
    }

    if (results.vocab) {
      if (results.srcvocab) {
        throw new Error(
          `A srcvocab and vocab file were both included for "${fromLanguage}" to "${toLanguage}." Only one is needed.`
        );
      }
      if (results.trgvocab) {
        throw new Error(
          `A trgvocab and vocab file were both included for "${fromLanguage}" to "${toLanguage}." Only one is needed.`
        );
      }
    } else if (!results.srcvocab || !results.srcvocab) {
      throw new Error(
        `No vocab files were provided for "${fromLanguage}" to "${toLanguage}."`
      );
    }

    return results;
  }

  static async getLanguageSize(language) {
    const records = [
      ...(await TranslationsParent.#getTranslationModelRecords()).values(),
    ];

    let downloadSize = 0;
    await Promise.all(
      records.map(async record => {
        if (record.fromLang !== language && record.toLang !== language) {
          return;
        }
        downloadSize += parseInt(record.attachment.size);
      })
    );
    return downloadSize;
  }

  /**
   * Gets the expected download size that will occur (if any) if translate is called on two given languages for display purposes.
   *
   * @param {string} fromLanguage
   * @param {string} toLanguage
   * @param {boolean} withQualityEstimation
   * @returns {Promise<long>} Size in bytes of the expected download. A result of 0 indicates no download is expected for the request.
   */
  static async getExpectedTranslationDownloadSize(
    fromLanguage,
    toLanguage,
    withQualityEstimation = false
  ) {
    const directSize = await this.#getModelDownloadSize(
      fromLanguage,
      toLanguage,
      withQualityEstimation
    );

    // If a direct model is not found, then check pivots.
    if (directSize.downloadSize == 0 && !directSize.modelFound) {
      const indirectFrom = await TranslationsParent.#getModelDownloadSize(
        fromLanguage,
        PIVOT_LANGUAGE,
        withQualityEstimation
      );

      const indirectTo = await TranslationsParent.#getModelDownloadSize(
        PIVOT_LANGUAGE,
        toLanguage,
        withQualityEstimation
      );

      // Note, will also return 0 due to the models not being available as well.
      return (
        parseInt(indirectFrom.downloadSize) + parseInt(indirectTo.downloadSize)
      );
    }
    return directSize.downloadSize;
  }

  /**
   * Determines the language model download size for a specified translation for display purposes.
   *
   * @param {string} fromLanguage
   * @param {string} toLanguage
   * @param {boolean} withQualityEstimation
   * @returns {Promise<{downloadSize: long, modelFound: boolean}>} Download size is the
   *   size in bytes of the estimated download for display purposes. Model found indicates
   *   a model was found. e.g., a result of {size: 0, modelFound: false} indicates no
   *   bytes to download, because a model wasn't located.
   */
  static async #getModelDownloadSize(
    fromLanguage,
    toLanguage,
    withQualityEstimation = false
  ) {
    const client = TranslationsParent.#getTranslationModelsRemoteClient();
    const records = [
      ...(await TranslationsParent.#getTranslationModelRecords()).values(),
    ];

    let downloadSize = 0;
    let modelFound = false;

    await Promise.all(
      records.map(async record => {
        if (record.fileType === "qualityModel" && !withQualityEstimation) {
          return;
        }

        if (record.fromLang !== fromLanguage || record.toLang !== toLanguage) {
          return;
        }

        modelFound = true;
        const isDownloaded = await client.attachments.isDownloaded(record);
        if (!isDownloaded) {
          downloadSize += parseInt(record.attachment.size);
        }
      })
    );
    return { downloadSize, modelFound };
  }

  /**
   * For testing purposes, allow the Translations Engine to be mocked. If called
   * with `null` the mock is removed.
   *
   * @param {null | RemoteSettingsClient} [translationModelsRemoteClient]
   * @param {null | RemoteSettingsClient} [translationsWasmRemoteClient]
   */
  static mockTranslationsEngine(
    translationModelsRemoteClient,
    translationsWasmRemoteClient
  ) {
    lazy.console.log("Mocking RemoteSettings for the translations engine.");
    TranslationsParent.#translationModelsRemoteClient =
      translationModelsRemoteClient;
    TranslationsParent.#translationsWasmRemoteClient =
      translationsWasmRemoteClient;
    TranslationsParent.#isTranslationsEngineMocked = true;

    translationModelsRemoteClient.on(
      "sync",
      TranslationsParent.#handleTranslationsModelsSync
    );
    translationsWasmRemoteClient.on(
      "sync",
      TranslationsParent.#handleTranslationsWasmSync
    );
  }

  /**
   * Most values are cached for performance, in tests we want to be able to clear them.
   */
  static clearCache() {
    // Records.
    TranslationsParent.#bergamotWasmRecord = null;
    TranslationsParent.#translationModelRecords = null;

    // Clients.
    TranslationsParent.#translationModelsRemoteClient = null;
    TranslationsParent.#translationsWasmRemoteClient = null;

    // Derived data.
    TranslationsParent.#clearCachedLanguagePairs();
    TranslationsParent.#mostRecentTargetLanguages = null;
    TranslationsParent.#userSettingsLanguages = null;
    TranslationsParent.#preferredLanguages = null;
    TranslationsParent.#isTranslationsEngineSupported = null;
  }

  /**
   * Remove the mocks for the translations engine, make sure and call clearCache after
   * to remove the cached values.
   */
  static unmockTranslationsEngine() {
    lazy.console.log(
      "Removing RemoteSettings mock for the translations engine."
    );
    TranslationsParent.#translationModelsRemoteClient.off(
      "sync",
      TranslationsParent.#handleTranslationsModelsSync
    );
    TranslationsParent.#translationsWasmRemoteClient.off(
      "sync",
      TranslationsParent.#handleTranslationsWasmSync
    );

    TranslationsParent.#isTranslationsEngineMocked = false;
  }

  /**
   * Report an error. Having this as a method allows tests to check that an error
   * was properly reported.
   *
   * @param {Error} error - Providing an Error object makes sure the stack is properly
   *                        reported.
   * @param {any[]} args - Any args to pass on to console.error.
   */
  static reportError(error, ...args) {
    lazy.console.log(error, ...args);
  }

  /**
   * @param {string} fromLanguage
   * @param {string} toLanguage
   * @param {boolean} reportAsAutoTranslate - In telemetry, report this as
   *   an auto-translate.
   */
  async translate(fromLanguage, toLanguage, reportAsAutoTranslate) {
    if (fromLanguage === toLanguage) {
      lazy.console.error(
        "A translation was requested where the from and to language match.",
        { fromLanguage, toLanguage, reportAsAutoTranslate }
      );
      return;
    }
    if (!fromLanguage || !toLanguage) {
      lazy.console.error(
        "A translation was requested but the fromLanguage or toLanguage was not set.",
        { fromLanguage, toLanguage, reportAsAutoTranslate }
      );
      return;
    }
    if (this.languageState.requestedTranslationPair) {
      // This page has already been translated, restore it and translate it
      // again once the actor has been recreated.
      const windowState = this.getWindowState();
      windowState.translateOnPageReload = { fromLanguage, toLanguage };
      this.restorePage(fromLanguage);
    } else {
      const { docLangTag } = this.languageState.detectedLanguages;

      if (!this.innerWindowId) {
        throw new Error(
          "The innerWindowId for the TranslationsParent was not available."
        );
      }

      // The MessageChannel will be used for communicating directly between the content
      // process and the engine's process.
      const port = await TranslationsParent.requestTranslationsPort(
        fromLanguage,
        toLanguage,
        this
      );

      if (!port) {
        lazy.console.error(
          `Failed to create a translations port for language pair: (${fromLanguage} -> ${toLanguage})`
        );
        return;
      }

      this.languageState.requestedTranslationPair = {
        fromLanguage,
        toLanguage,
      };

      const preferredLanguages = TranslationsParent.getPreferredLanguages();
      const topPreferredLanguage =
        preferredLanguages && preferredLanguages.length
          ? preferredLanguages[0]
          : null;

      TranslationsParent.telemetry().onTranslate({
        docLangTag,
        fromLanguage,
        toLanguage,
        topPreferredLanguage,
        autoTranslate: reportAsAutoTranslate,
        requestTarget: "full_page",
      });

      TranslationsParent.storeMostRecentTargetLanguage(toLanguage);

      this.sendAsyncMessage(
        "Translations:TranslatePage",
        {
          fromLanguage,
          toLanguage,
          port,
        },
        // https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects
        // Mark the MessageChannel port as transferable.
        [port]
      );
    }
  }

  /**
   * Restore the page to the original language by doing a hard reload.
   */
  restorePage() {
    TranslationsParent.telemetry().onRestorePage();
    // Skip auto-translate for one page load.
    const windowState = this.getWindowState();
    windowState.isPageRestored = true;
    this.languageState.hasVisibleChange = false;
    this.languageState.requestedTranslationPair = null;
    windowState.previousDetectedLanguages =
      this.languageState.detectedLanguages;

    const browser = this.browsingContext.embedderElement;
    browser.reload();
  }

  static onLocationChange(browser) {
    if (!lazy.translationsEnabledPref) {
      // The pref isn't enabled, so don't attempt to get the actor.
      return;
    }
    let actor;
    try {
      actor =
        browser.browsingContext.currentWindowGlobal.getActor("Translations");
    } catch {
      // The actor may not be supported on this page, which throws an error.
    }
    actor?.languageState.locationChanged();
  }

  async queryIdentifyLanguage() {
    if (
      TranslationsParent.isInAutomation() &&
      !TranslationsParent.#isTranslationsEngineMocked
    ) {
      return null;
    }
    return this.sendQuery("Translations:IdentifyLanguage").catch(error => {
      if (this.#isDestroyed) {
        // The actor was destroyed while this message was still being resolved.
        return null;
      }
      return Promise.reject(error);
    });
  }

  /**
   * Returns the language from the document element.
   *
   * @returns {Promise<string>}
   */
  queryDocumentElementLang() {
    return this.sendQuery("Translations:GetDocumentElementLang");
  }

  /**
   * @param {LangTags} langTags
   */
  shouldAutoTranslate(langTags) {
    if (
      langTags.docLangTag &&
      langTags.userLangTag &&
      langTags.isDocLangTagSupported &&
      this.#maybeAutoTranslate(langTags) &&
      !TranslationsParent.shouldNeverTranslateLanguage(langTags.docLangTag) &&
      !this.shouldNeverTranslateSite()
    ) {
      return true;
    }

    return false;
  }

  /**
   * Checks if a given language tag is supported for translation
   * when translating from this language into other languages.
   *
   * @param {string} langTag - A BCP-47 language tag.
   * @returns {Promise<boolean>}
   */
  static async isSupportedAsFromLang(langTag) {
    if (!langTag) {
      return false;
    }
    let languagePairs = await TranslationsParent.getLanguagePairs();
    return Boolean(languagePairs.find(({ fromLang }) => fromLang === langTag));
  }

  /**
   * Checks if a given language tag is supported for translation
   * when translating from other languages into this language.
   *
   * @param {string} langTag - A BCP-47 language tag.
   * @returns {Promise<boolean>}
   */
  static async isSupportedAsToLang(langTag) {
    if (!langTag) {
      return false;
    }
    let languagePairs = await TranslationsParent.getLanguagePairs();
    return Boolean(languagePairs.find(({ toLang }) => toLang === langTag));
  }

  /**
   * Retrieves the top preferred user language for which translation
   * is supported when translating to that language.
   *
   * @param {object} options
   * @param {string[]} [options.excludeLangTags] - BCP-47 language tags to intentionally exclude.
   */
  static async getTopPreferredSupportedToLang({ excludeLangTags } = {}) {
    const preferredLanguages = TranslationsParent.getPreferredLanguages({
      excludeLangTags,
    });

    for (const langTag of preferredLanguages) {
      if (await TranslationsParent.isSupportedAsToLang(langTag)) {
        return langTag;
      }
    }

    return PIVOT_LANGUAGE;
  }

  /**
   * Returns the lang tags that should be offered for translation. This is in the parent
   * rather than the child to remove the per-content process memory allocation amount.
   *
   * @param {string} [documentElementLang]
   * @param {string} [href]
   * @returns {Promise<LangTags | null>} - Returns null if the actor was destroyed before
   *   the result could be resolved.
   */
  async getDetectedLanguages(documentElementLang, href) {
    if (this.languageState.detectedLanguages) {
      return this.languageState.detectedLanguages;
    }
    const langTags = {
      docLangTag: null,
      userLangTag: null,
      isDocLangTagSupported: false,
    };
    if (!TranslationsParent.getIsTranslationsEngineSupported()) {
      return null;
    }

    if (documentElementLang === undefined) {
      documentElementLang = await this.queryDocumentElementLang();
      if (this.#isDestroyed) {
        return null;
      }
    }

    let languagePairs = await TranslationsParent.getLanguagePairs();
    if (this.#isDestroyed) {
      return null;
    }

    const determineIsDocLangTagSupported = () =>
      Boolean(
        languagePairs.find(({ fromLang }) => fromLang === langTags.docLangTag)
      );

    // First try to get the langTag from the document's markup.
    try {
      const docLocale = new Intl.Locale(documentElementLang);
      langTags.docLangTag = docLocale.language;
      langTags.isDocLangTagSupported = determineIsDocLangTagSupported();
    } catch (error) {}

    if (langTags.docLangTag) {
      // If it's not supported, try it again with a canonicalized version.
      if (!langTags.isDocLangTagSupported) {
        langTags.docLangTag = Intl.getCanonicalLocales(langTags.docLangTag)[0];
        langTags.isDocLangTagSupported = determineIsDocLangTagSupported();
      }

      // If it's still not supported, map macro language codes to specific ones.
      //   https://en.wikipedia.org/wiki/ISO_639_macrolanguage
      if (!langTags.isDocLangTagSupported) {
        // If more macro language codes are needed, this logic can be expanded.
        if (langTags.docLangTag === "no") {
          // Choose "Norwegian Bokmål" over "Norwegian Nynorsk" as it is more widely used.
          //
          // https://en.wikipedia.org/wiki/Norwegian_language#Bokm%C3%A5l_and_Nynorsk
          //
          //   > A 2005 poll indicates that 86.3% use primarily Bokmål as their daily
          //   > written language, 5.5% use both Bokmål and Nynorsk, and 7.5% use
          //   > primarily Nynorsk.
          langTags.docLangTag = "nb";
          langTags.isDocLangTagSupported = determineIsDocLangTagSupported();
        }
      }
    } else {
      // If the document's markup had no specified langTag, attempt to identify the page's language.
      langTags.docLangTag = await this.queryIdentifyLanguage();
      if (this.#isDestroyed) {
        return null;
      }
      langTags.isDocLangTagSupported = determineIsDocLangTagSupported();
    }

    if (!langTags.docLangTag) {
      const message = "No valid language detected.";
      ChromeUtils.addProfilerMarker(
        "TranslationsParent",
        { innerWindowId: this.innerWindowId },
        message
      );
      lazy.console.log(message, href);

      const langTag = await TranslationsParent.getTopPreferredSupportedToLang();
      if (this.#isDestroyed) {
        return null;
      }

      if (langTag) {
        langTags.userLangTag = langTag;
      }

      return langTags;
    }

    if (TranslationsParent.getWebContentLanguages().has(langTags.docLangTag)) {
      // The doc language has been marked as a known language by the user, do not
      // offer a translation.
      const message =
        "The app and document languages match, so not translating.";
      ChromeUtils.addProfilerMarker(
        "TranslationsParent",
        { innerWindowId: this.innerWindowId },
        message
      );
      lazy.console.log(message, href);
      // The docLangTag will be set, while the userLangTag will be null.
      return langTags;
    }

    const langTag = await TranslationsParent.getTopPreferredSupportedToLang({
      excludeLangTags: [langTags.docLangTag],
    });
    if (this.#isDestroyed) {
      return null;
    }

    if (langTag) {
      langTags.userLangTag = langTag;
    }

    if (!langTags.userLangTag) {
      // No language pairs match.
      const message = `No matching translation pairs were found for translating from "${langTags.docLangTag}".`;
      ChromeUtils.addProfilerMarker(
        "TranslationsParent",
        { innerWindowId: this.innerWindowId },
        message
      );
      lazy.console.log(message, languagePairs);
    }

    return langTags;
  }

  /**
   * The pref for if we can always offer a translation when it's available.
   */
  static shouldAlwaysOfferTranslations() {
    return lazy.automaticallyPopupPref;
  }

  /**
   * Returns true if the given language tag is present in the always-translate
   * languages preference, otherwise false.
   *
   * @param {LangTags} langTags
   * @returns {boolean}
   */
  static shouldAlwaysTranslateLanguage(langTags) {
    const { docLangTag, userLangTag } = langTags;
    if (docLangTag === userLangTag || !userLangTag) {
      // Do not auto-translate when the docLangTag matches the userLangTag, or when
      // the userLangTag is not set. The "always translate" is exposed via about:confg.
      // In case of users putting in non-sensical things here, we don't want to break
      // the experience. This behavior can lead to a "language degradation machine"
      // where we go from a source language -> pivot language -> source language.
      return false;
    }
    return lazy.alwaysTranslateLangTags.has(docLangTag);
  }

  /**
   * Returns true if the given language tag is present in the never-translate
   * languages preference, otherwise false.
   *
   * @param {string} langTag - A BCP-47 language tag
   * @returns {boolean}
   */
  static shouldNeverTranslateLanguage(langTag) {
    return lazy.neverTranslateLangTags.has(langTag);
  }

  /**
   * Returns true if the current site is denied permissions to translate,
   * otherwise returns false.
   *
   * @returns {Promise<boolean>}
   */
  shouldNeverTranslateSite() {
    const perms = Services.perms;
    const permission = perms.getPermissionObject(
      this.browsingContext.currentWindowGlobal.documentPrincipal,
      TRANSLATIONS_PERMISSION,
      /* exactHost */ false
    );
    return permission?.capability === perms.DENY_ACTION;
  }

  /**
   * Removes the given language tag from the given preference.
   *
   * @param {string} langTag - A BCP-47 language tag
   * @param {string} prefName - The pref name
   */
  static removeLangTagFromPref(langTag, prefName) {
    const langTags =
      prefName === ALWAYS_TRANSLATE_LANGS_PREF
        ? lazy.alwaysTranslateLangTags
        : lazy.neverTranslateLangTags;
    const newLangTags = [...langTags].filter(tag => tag !== langTag);
    Services.prefs.setCharPref(prefName, [...newLangTags].join(","));
  }

  /**
   * Adds the given language tag to the given preference.
   *
   * @param {string} langTag - A BCP-47 language tag
   * @param {string} prefName - The pref name
   */
  static addLangTagToPref(langTag, prefName) {
    const langTags =
      prefName === ALWAYS_TRANSLATE_LANGS_PREF
        ? lazy.alwaysTranslateLangTags
        : lazy.neverTranslateLangTags;
    if (!langTags.has(langTag)) {
      langTags.add(langTag);
    }
    Services.prefs.setCharPref(prefName, [...langTags].join(","));
  }

  /**
   * Stores the given langTag as the most recent target language in the
   * browser.translations.mostRecentTargetLanguage pref.
   *
   * @param {string} langTag - A BCP-47 language tag.
   */
  static storeMostRecentTargetLanguage(langTag) {
    // The pref's language tags are managed by this function as a unique-item
    // sliding window with a max size.
    //
    // Examples with MAX_SIZE = 3:
    //
    //  Add a new item to an empty window:
    //  [ ] + a => [a]
    //
    //  Add a new item to a non-full window:
    //  [a] + b => [a, b]
    //
    //  [a, b] + c => [a, b, c]
    //
    //  Add a new item to a full window:
    //  [a, b, c] + z => [b, c, z]
    //
    //  Add an item that is already within a window:
    //  [b, c, z] + z => [b, c, z]
    //
    //  [b, c, z] + c => [b, z, c]
    //
    //  [b, z, c] + b => [z, c, b]
    const MAX_SIZE = 3;
    const mostRecentTargetLanguages = lazy.mostRecentTargetLanguages;

    if (mostRecentTargetLanguages.has(langTag)) {
      // The language tag is already present, so delete it to ensure that its order is updated when it gets re-added.
      mostRecentTargetLanguages.delete(langTag);
    } else if (mostRecentTargetLanguages.size === MAX_SIZE) {
      // We only store MAX_SIZE lang tags, so remove the oldest language tag to make room for the new language tag.
      const oldestLangTag = mostRecentTargetLanguages.keys().next().value;
      mostRecentTargetLanguages.delete(oldestLangTag);
    }

    mostRecentTargetLanguages.add(langTag);

    Services.prefs.setCharPref(
      "browser.translations.mostRecentTargetLanguages",
      [...mostRecentTargetLanguages].join(",")
    );
  }

  /**
   * Toggles the always-translate language preference by adding the language
   * to the pref list if it is not present, or removing it if it is present.
   *
   * @param {LangTags} langTags
   * @returns {boolean}
   *  True if always-translate was enabled for this language.
   *  False if always-translate was disabled for this language.
   */
  static toggleAlwaysTranslateLanguagePref(langTags) {
    const { docLangTag, appLangTag } = langTags;

    if (appLangTag === docLangTag) {
      // In case somehow the user attempts to toggle this when the app and doc language
      // are the same, just remove the lang tag.
      this.removeLangTagFromPref(appLangTag, ALWAYS_TRANSLATE_LANGS_PREF);
      return false;
    }

    if (TranslationsParent.shouldAlwaysTranslateLanguage(langTags)) {
      // The pref was toggled off for this langTag
      this.removeLangTagFromPref(docLangTag, ALWAYS_TRANSLATE_LANGS_PREF);
      return false;
    }

    // The pref was toggled on for this langTag
    this.addLangTagToPref(docLangTag, ALWAYS_TRANSLATE_LANGS_PREF);
    this.removeLangTagFromPref(docLangTag, NEVER_TRANSLATE_LANGS_PREF);
    return true;
  }

  static getAlwaysTranslateLanguages() {
    return lazy.alwaysTranslateLangTags;
  }

  static getNeverTranslateLanguages() {
    return lazy.neverTranslateLangTags;
  }

  /**
   * Toggle the automatically popup pref, which will either
   * enable or disable translations being offered to the user.
   *
   * @returns {boolean}
   *  True if offering translations was enabled by this call.
   *  False if offering translations was disabled by this call.
   */
  static toggleAutomaticallyPopupPref() {
    const prefValueBeforeToggle = lazy.automaticallyPopupPref;
    Services.prefs.setBoolPref(
      "browser.translations.automaticallyPopup",
      !prefValueBeforeToggle
    );
    return !prefValueBeforeToggle;
  }

  /**
   * Toggles the never-translate language preference by adding the language
   * to the pref list if it is not present, or removing it if it is present.
   *
   * @param {string} langTag - A BCP-47 language tag
   * @returns {boolean} Whether the pref was toggled on or off for this langTag.
   *  True if never-translate was enabled for this language.
   *  False if never-translate was disabled for this language.
   */
  static toggleNeverTranslateLanguagePref(langTag) {
    if (TranslationsParent.shouldNeverTranslateLanguage(langTag)) {
      // The pref was toggled off for this langTag
      this.removeLangTagFromPref(langTag, NEVER_TRANSLATE_LANGS_PREF);
      return false;
    }

    // The pref was toggled on for this langTag
    this.addLangTagToPref(langTag, NEVER_TRANSLATE_LANGS_PREF);
    this.removeLangTagFromPref(langTag, ALWAYS_TRANSLATE_LANGS_PREF);
    return true;
  }

  /**
   * Toggles the never-translate site permissions by adding DENY_ACTION to
   * the site principal if it is not present, or removing it if it is present.
   *
   * @returns {boolean}
   *  True if never-translate was enabled for this site.
   *  False if never-translate was disabled for this site.
   */
  toggleNeverTranslateSitePermissions() {
    if (this.shouldNeverTranslateSite()) {
      return this.setNeverTranslateSitePermissions(false);
    }

    return this.setNeverTranslateSitePermissions(true);
  }

  /**
   * Sets the never-translate site permissions by adding DENY_ACTION to
   * the site principal.
   *
   * @param {string} neverTranslate - The never translate setting.
   * @returns {boolean}
   *  True if never-translate was enabled for this site.
   *  False if never-translate was disabled for this site.
   */
  setNeverTranslateSitePermissions(neverTranslate) {
    const { documentPrincipal } = this.browsingContext.currentWindowGlobal;
    return TranslationsParent.#setNeverTranslateSiteByPrincipal(
      neverTranslate,
      documentPrincipal
    );
  }

  /**
   * Sets the never-translate site permissions by creating a principal from the URL origin
   * and setting or unsetting the DENY_ACTION on the permission.
   *
   * @param {string} neverTranslate - The never translate setting to use.
   * @param {string} urlOrigin - The url origin to set the permission for.
   * @returns {boolean}
   *  True if never-translate was enabled for this origin.
   *  False if never-translate was disabled for this origin.
   */
  static setNeverTranslateSiteByOrigin(neverTranslate, urlOrigin) {
    const principal =
      Services.scriptSecurityManager.createContentPrincipalFromOrigin(
        urlOrigin
      );
    return TranslationsParent.#setNeverTranslateSiteByPrincipal(
      neverTranslate,
      principal
    );
  }

  /**
   * Sets the never-translate site permissions by adding DENY_ACTION to
   * the specified site principal.
   *
   * @param {string} neverTranslate - The never translate setting.
   * @param {string} principal - The principal that should have the permission attached.
   * @returns {boolean}
   *  True if never-translate was enabled for this principal.
   *  False if never-translate was disabled for this principal.
   */
  static #setNeverTranslateSiteByPrincipal(neverTranslate, principal) {
    const perms = Services.perms;

    if (!neverTranslate) {
      perms.removeFromPrincipal(principal, TRANSLATIONS_PERMISSION);
      return false;
    }

    perms.addFromPrincipal(
      principal,
      TRANSLATIONS_PERMISSION,
      perms.DENY_ACTION
    );
    return true;
  }

  /**
   * Creates a list of URLs that have a translations permission set on the resource.
   * These are the sites to never translate.
   *
   * @returns {Array<string>} String array with the URL of the sites that have the never translate permission.
   */
  static listNeverTranslateSites() {
    const neverTranslateSites = [];
    for (const perm of Services.perms.getAllByTypes([
      TRANSLATIONS_PERMISSION,
    ])) {
      if (perm.capability === Services.perms.DENY_ACTION) {
        neverTranslateSites.push(perm.principal.origin);
      }
    }
    let stripProtocol = s => s?.replace(/^\w+:/, "") || "";
    return neverTranslateSites.sort((a, b) => {
      return stripProtocol(a).localeCompare(stripProtocol(b));
    });
  }

  /**
   * Ensure that the translations are always destroyed, even if the content translations
   * are misbehaving.
   */
  #ensureTranslationsDiscarded() {
    if (!lazy.EngineProcess.translationsEngineParent) {
      return;
    }
    lazy.EngineProcess.translationsEngineParent
      // If the engine fails to load, ignore it since we are ending translations.
      .catch(() => null)
      .then(actor => {
        if (actor && this.languageState.requestedTranslationPair) {
          actor.discardTranslations(this.innerWindowId);
        }
      })
      // This error will be one from the endTranslation code, which we need to
      // surface.
      .catch(error => lazy.console.error(error));
  }

  didDestroy() {
    if (!this.innerWindowId) {
      throw new Error(
        "The innerWindowId for the TranslationsParent was not available."
      );
    }

    Services.obs.removeObserver(
      this.maybeUpdateUserLangTag,
      "translations:maybe-update-user-lang-tag"
    );

    this.#ensureTranslationsDiscarded();

    this.#isDestroyed = true;
  }
}

/**
 * Validate some simple Wasm that uses a SIMD operation.
 */
function detectSimdSupport() {
  try {
    return WebAssembly.validate(
      new Uint8Array(
        // ```
        // ;; Detect SIMD support.
        // ;; Compile by running: wat2wasm --enable-all simd-detect.wat
        //
        // (module
        //   (func (result v128)
        //     i32.const 0
        //     i8x16.splat
        //     i8x16.popcnt
        //   )
        // )
        // ```

        // prettier-ignore
        [
        0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x05, 0x01, 0x60, 0x00,
        0x01, 0x7b, 0x03, 0x02, 0x01, 0x00, 0x0a, 0x0a, 0x01, 0x08, 0x00, 0x41, 0x00,
        0xfd, 0x0f, 0xfd, 0x62, 0x0b
      ]
      )
    );
  } catch {
    return false;
  }
}

/**
 * State that affects the UI. Any of the state that gets set triggers a dispatch to update
 * the UI.
 */
class TranslationsLanguageState {
  /**
   * @param {TranslationsParent} actor
   * @param {LangTags | null} previousDetectedLanguages
   */
  constructor(actor, previousDetectedLanguages = null) {
    this.#actor = actor;
    this.#detectedLanguages = previousDetectedLanguages;
  }

  /**
   * The data members for TranslationsLanguageState, see the getters for their
   * documentation.
   */

  /** @type {TranslationsParent} */
  #actor;

  /** @type {TranslationPair | null} */
  #requestedTranslationPair = null;

  /** @type {LangTags | null} */
  #detectedLanguages = null;

  /** @type {boolean} */
  #hasVisibleChange = false;

  /** @type {null | TranslationErrors} */
  #error = null;

  #isEngineReady = false;

  /**
   * Dispatch anytime the language details change, so that any UI can react to it.
   */
  dispatch({ reason } = {}) {
    const browser = this.#actor.browsingContext.top.embedderElement;
    if (!browser) {
      return;
    }
    const { CustomEvent } = browser.ownerGlobal;
    browser.dispatchEvent(
      new CustomEvent("TranslationsParent:LanguageState", {
        bubbles: true,
        detail: {
          actor: this.#actor,
          reason,
        },
      })
    );
  }

  /**
   * When a translation is requested, this contains the translation pair. This means
   * that the TranslationsChild should be creating a TranslationsDocument and keep
   * the page updated with the target language.
   *
   * @returns {TranslationPair | null}
   */
  get requestedTranslationPair() {
    return this.#requestedTranslationPair;
  }

  set requestedTranslationPair(requestedTranslationPair) {
    if (this.#requestedTranslationPair === requestedTranslationPair) {
      return;
    }

    this.#error = null;
    this.#isEngineReady = false;
    this.#requestedTranslationPair = requestedTranslationPair;
    this.dispatch({ reason: "requestedTranslationPair" });
  }

  /**
   * The stored results for the detected languages.
   *
   * @returns {LangTags | null}
   */
  get detectedLanguages() {
    return this.#detectedLanguages;
  }

  set detectedLanguages(detectedLanguages) {
    if (this.#detectedLanguages === detectedLanguages) {
      return;
    }

    this.#detectedLanguages = detectedLanguages;
    this.dispatch({ reason: "detectedLanguages" });
  }

  /**
   * A visual translation change occurred on the DOM.
   *
   * @returns {boolean}
   */
  get hasVisibleChange() {
    return this.#hasVisibleChange;
  }

  set hasVisibleChange(hasVisibleChange) {
    if (this.#hasVisibleChange === hasVisibleChange) {
      return;
    }

    this.#hasVisibleChange = hasVisibleChange;
    this.dispatch({ reason: "hasVisibleChange" });
  }

  /**
   * When the location changes remove the previous error and dispatch a change event
   * so that any browser chrome UI that needs to be updated can get the latest state.
   */
  locationChanged() {
    this.#error = null;
    this.dispatch({ reason: "locationChanged" });
  }

  /**
   * Makes a determination about whether to update the cached userLangTag with the given langTag.
   */
  maybeUpdateUserLangTag(langTag) {
    const currentUserLangTag = this.#detectedLanguages?.userLangTag;

    if (!currentUserLangTag) {
      // The userLangTag is not present in the detectedLanguages cache.
      // This is intentional and we should not update it in this case,
      // otherwise we may end up showing the Translations URL-bar button
      // on a page where it is currently hidden.
      return;
    }

    this.#detectedLanguages.userLangTag = langTag;
    // There is no need to call this.dispatch() in this function.
    //
    // Updating the userLangTag will affect which language is offered the next time
    // a panel is opened, or which language is auto-translated into when a page loads,
    // but this information should not eagerly affect the visual states of Translations
    // content across the browser. Relevant consumers will fetch the updated langTag from
    // the cache when they need it.
    //
    // In theory, calling this.dispatch() should be fine to do since the LanguageState event
    // guards itself against irrelevant changes, but that would ultimately cause unneeded noise.
  }

  /**
   * The last error that occurred during translation.
   */
  get error() {
    return this.#error;
  }

  set error(error) {
    if (this.#error === error) {
      return;
    }
    this.#error = error;
    // Setting an error invalidates the requested translation pair.
    this.#requestedTranslationPair = null;
    this.#isEngineReady = false;
    this.dispatch({ reason: "error" });
  }

  /**
   * Stores when the translations engine is ready. The wasm and language files must
   * be downloaded, which can take some time.
   */
  get isEngineReady() {
    return this.#isEngineReady;
  }

  set isEngineReady(isEngineReady) {
    if (this.#isEngineReady === isEngineReady) {
      return;
    }
    this.#isEngineReady = isEngineReady;
    this.dispatch({ reason: "isEngineReady" });
  }
}

/**
 * @typedef {object} QueueItem
 * @property {Function} download
 * @property {Function} [onSuccess]
 * @property {Function} [onFailure]
 * @property {number} [retriesLeft]
 */

/**
 * Manage the download of the files by providing a maximum number of concurrent files
 * and the ability to retry a file download in case of an error.
 *
 * @param {QueueItem[]} queue
 */
async function downloadManager(queue) {
  const NOOP = () => {};

  const pendingDownloadAttempts = new Set();
  let failCount = 0;
  let index = 0;
  const start = Date.now();
  const originalQueueLength = queue.length;

  while (index < queue.length || pendingDownloadAttempts.size > 0) {
    // Start new downloads up to the maximum limit
    while (
      index < queue.length &&
      pendingDownloadAttempts.size < TranslationsParent.MAX_CONCURRENT_DOWNLOADS
    ) {
      lazy.console.log(`Starting download ${index + 1} of ${queue.length}`);

      const {
        download,
        onSuccess = NOOP,
        onFailure = NOOP,
        retriesLeft = TranslationsParent.MAX_DOWNLOAD_RETRIES,
      } = queue[index];

      const handleFailedDownload = error => {
        // The download failed. Either retry it, or report the failure.
        TranslationsParent.reportError(
          new Error("Failed to download file."),
          error
        );

        const newRetriesLeft = retriesLeft - 1;

        if (retriesLeft > 0) {
          lazy.console.log(
            `Queueing another attempt. ${newRetriesLeft} attempts left.`
          );
          queue.push({
            download,
            retriesLeft: newRetriesLeft,
            onSuccess,
            onFailure,
          });
        } else {
          // Give up on this download.
          failCount++;
          onFailure();
        }
      };

      const afterDownloadAttempt = () => {
        pendingDownloadAttempts.delete(downloadAttempt);
      };

      // Kick off the download. If it fails, retry it a certain number of attempts.
      // This is done asynchronously from the rest of the for loop.
      const downloadAttempt = download()
        .then(onSuccess, handleFailedDownload)
        .then(afterDownloadAttempt);

      pendingDownloadAttempts.add(downloadAttempt);
      index++;
    }

    // Wait for any active downloads to complete.
    await Promise.race(pendingDownloadAttempts);
  }

  const duration = ((Date.now() - start) / 1000).toFixed(3);

  if (failCount > 0) {
    const message = `Finished downloads in ${duration} seconds, but ${failCount} download(s) failed.`;
    lazy.console.log(
      `Finished downloads in ${duration} seconds, but ${failCount} download(s) failed.`
    );
    throw new Error(message);
  }

  lazy.console.log(
    `Finished ${originalQueueLength} downloads in ${duration} seconds.`
  );
}

/**
 * The translations code has lots of async code and fallible network requests. To test
 * this manually while using the feature, enable chaos mode by setting "errors" to true
 * and "timeoutMS" to a positive number of milliseconds.
 * prefs to true:
 *
 *  - browser.translations.chaos.timeoutMS
 *  - browser.translations.chaos.errors
 */
async function chaosMode(probability = 0.5) {
  await chaosModeTimer();
  await chaosModeError(probability);
}

/**
 * The translations code has lots of async code that relies on the network. To test
 * this manually while using the feature, enable chaos mode by setting the following pref
 * to a positive number of milliseconds.
 *
 *  - browser.translations.chaos.timeoutMS
 */
async function chaosModeTimer() {
  if (lazy.chaosTimeoutMSPref) {
    const timeout = Math.random() * lazy.chaosTimeoutMSPref;
    lazy.console.log(
      `Chaos mode timer started for ${(timeout / 1000).toFixed(1)} seconds.`
    );
    await new Promise(resolve => lazy.setTimeout(resolve, timeout));
  }
}

/**
 * The translations code has lots of async code that is fallible. To test this manually
 * while using the feature, enable chaos mode by setting the following pref to true.
 *
 *  - browser.translations.chaos.errors
 */
async function chaosModeError(probability = 0.5) {
  if (lazy.chaosErrorsPref && Math.random() < probability) {
    lazy.console.trace(`Chaos mode error generated.`);
    throw new Error(
      `Chaos Mode error from the pref "browser.translations.chaos.errors".`
    );
  }
}
PK
!<'�Urractors/UAWidgetsChild.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class UAWidgetsChild extends JSWindowActorChild {
  constructor() {
    super();

    this.widgets = new WeakMap();
    this.prefsCache = new Map();
    this.observedPrefs = [];

    // Bug 1570744 - JSWindowActorChild's cannot be used as nsIObserver's
    // directly, so we create a new function here instead to act as our
    // nsIObserver, which forwards the notification to the observe method.
    this.observerFunction = (subject, topic, data) => {
      this.observe(subject, topic, data);
    };
  }

  didDestroy() {
    for (let pref in this.observedPrefs) {
      Services.prefs.removeObserver(pref, this.observerFunction);
    }
  }

  unwrap(obj) {
    return Cu.isXrayWrapper(obj) ? obj.wrappedJSObject : obj;
  }

  handleEvent(aEvent) {
    switch (aEvent.type) {
      case "UAWidgetSetupOrChange":
        this.setupOrNotifyWidget(aEvent.target);
        break;
      case "UAWidgetTeardown":
        this.teardownWidget(aEvent.target);
        break;
    }
  }

  setupOrNotifyWidget(aElement) {
    if (!this.widgets.has(aElement)) {
      this.setupWidget(aElement);
      return;
    }

    let { widget } = this.widgets.get(aElement);

    if (typeof widget.onchange == "function") {
      if (
        this.unwrap(aElement.openOrClosedShadowRoot) !=
        this.unwrap(widget.shadowRoot)
      ) {
        console.error(
          "Getting a UAWidgetSetupOrChange event without the ShadowRoot. " +
            "Torn down already?"
        );
        return;
      }
      try {
        widget.onchange();
      } catch (ex) {
        console.error(ex);
      }
    }
  }

  setupWidget(aElement) {
    let uri;
    let widgetName;
    // Use prefKeys to optionally send a list of preferences to forward to
    // the UAWidget. The UAWidget will receive those preferences as key-value
    // pairs as the second argument to its constructor. Updates to those prefs
    // can be observed by implementing an optional onPrefChange method for the
    // UAWidget that receives the changed pref name as the first argument, and
    // the updated value as the second.
    let prefKeys = [];
    switch (aElement.localName) {
      case "video":
      case "audio":
        uri = "chrome://global/content/elements/videocontrols.js";
        widgetName = "VideoControlsWidget";
        prefKeys = [
          "media.videocontrols.picture-in-picture.enabled",
          "media.videocontrols.picture-in-picture.video-toggle.enabled",
          "media.videocontrols.picture-in-picture.video-toggle.always-show",
          "media.videocontrols.picture-in-picture.video-toggle.min-video-secs",
          "media.videocontrols.picture-in-picture.video-toggle.position",
          "media.videocontrols.picture-in-picture.video-toggle.has-used",
          "media.videocontrols.keyboard-tab-to-all-controls",
          "media.videocontrols.picture-in-picture.respect-disablePictureInPicture",
        ];
        break;
      case "input":
        uri = "chrome://global/content/elements/datetimebox.js";
        widgetName = "DateTimeBoxWidget";
        prefKeys = ["privacy.resistFingerprinting"];
        break;
      case "marquee":
        uri = "chrome://global/content/elements/marquee.js";
        widgetName = "MarqueeWidget";
        break;
      case "img":
        uri = "chrome://global/content/elements/textrecognition.js";
        widgetName = "TextRecognitionWidget";
    }

    if (!uri || !widgetName) {
      console.error(
        "Getting a UAWidgetSetupOrChange event on undefined element."
      );
      return;
    }

    let shadowRoot = aElement.openOrClosedShadowRoot;
    if (!shadowRoot) {
      console.error(
        "Getting a UAWidgetSetupOrChange event without the Shadow Root. " +
          "Torn down already?"
      );
      return;
    }

    let isSystemPrincipal = aElement.nodePrincipal.isSystemPrincipal;
    let sandbox = isSystemPrincipal
      ? Object.create(null)
      : Cu.getUAWidgetScope(aElement.nodePrincipal);

    if (!sandbox[widgetName]) {
      Services.scriptloader.loadSubScript(uri, sandbox);
    }

    let prefs = Cu.cloneInto(
      this.getPrefsForUAWidget(widgetName, prefKeys),
      sandbox
    );

    let widget = new sandbox[widgetName](shadowRoot, prefs);
    if (!isSystemPrincipal) {
      widget = widget.wrappedJSObject;
    }
    if (this.unwrap(widget.shadowRoot) != this.unwrap(shadowRoot)) {
      console.error("Widgets should expose their shadow root.");
    }
    this.widgets.set(aElement, { widget, widgetName });
    try {
      widget.onsetup();
    } catch (ex) {
      console.error(ex);
    }
  }

  teardownWidget(aElement) {
    if (!this.widgets.has(aElement)) {
      return;
    }
    let { widget } = this.widgets.get(aElement);
    if (typeof widget.teardown == "function") {
      try {
        widget.teardown();
      } catch (ex) {
        console.error(ex);
      }
    }
    this.widgets.delete(aElement);
  }

  getPrefsForUAWidget(aWidgetName, aPrefKeys) {
    let result = this.prefsCache.get(aWidgetName);
    if (result) {
      return result;
    }

    result = {};
    for (let key of aPrefKeys) {
      result[key] = this.getPref(key);
      this.observePref(key);
    }

    this.prefsCache.set(aWidgetName, result);
    return result;
  }

  observePref(prefKey) {
    Services.prefs.addObserver(prefKey, this.observerFunction);
    this.observedPrefs.push(prefKey);
  }

  getPref(prefKey) {
    switch (Services.prefs.getPrefType(prefKey)) {
      case Ci.nsIPrefBranch.PREF_BOOL: {
        return Services.prefs.getBoolPref(prefKey);
      }
      case Ci.nsIPrefBranch.PREF_INT: {
        return Services.prefs.getIntPref(prefKey);
      }
      case Ci.nsIPrefBranch.PREF_STRING: {
        return Services.prefs.getStringPref(prefKey);
      }
    }

    return undefined;
  }

  observe(subject, topic, data) {
    if (topic == "nsPref:changed") {
      for (let [widgetName, prefCache] of this.prefsCache) {
        if (prefCache.hasOwnProperty(data)) {
          let newValue = this.getPref(data);
          prefCache[data] = newValue;

          this.notifyWidgetsOnPrefChange(widgetName, data, newValue);
        }
      }
    }
  }

  notifyWidgetsOnPrefChange(nameOfWidgetToNotify, prefKey, newValue) {
    let elements = ChromeUtils.nondeterministicGetWeakMapKeys(this.widgets);
    for (let element of elements) {
      if (!Cu.isDeadWrapper(element) && element.isConnected) {
        let { widgetName, widget } = this.widgets.get(element);
        if (widgetName == nameOfWidgetToNotify) {
          if (typeof widget.onPrefChange == "function") {
            try {
              widget.onPrefChange(prefKey, newValue);
            } catch (ex) {
              console.error(ex);
            }
          }
        }
      }
    }
  }
}
PK
!<'^�-mm&actors/UnselectedTabHoverChild.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class UnselectedTabHoverChild extends JSWindowActorChild {
  receiveMessage(message) {
    Services.obs.notifyObservers(
      this.contentWindow,
      "unselected-tab-hover",
      message.data.hovered
    );
  }

  handleEvent(event) {
    this.sendAsyncMessage("UnselectedTabHover:Toggle", {
      enable: event.type == "UnselectedTabHover:Enable",
    });
  }
}
PK
!<'QLG@@'actors/UnselectedTabHoverParent.sys.mjs/* vim: set ts=2 sw=2 sts=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class UnselectedTabHoverParent extends JSWindowActorParent {
  receiveMessage(message) {
    const topBrowsingContext = this.manager.browsingContext.top;
    const browser = topBrowsingContext.embedderElement;
    if (!browser) {
      return;
    }
    browser.shouldHandleUnselectedTabHover = message.data.enable;
  }
}
PK
!<��
�
'actors/UserCharacteristicsChild.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  return console.createInstance({
    prefix: "UserCharacteristicsPage",
    maxLogLevelPref: "toolkit.telemetry.user_characteristics_ping.logLevel",
  });
});

export class UserCharacteristicsChild extends JSWindowActorChild {
  /**
   * A placeholder for the collected data.
   *
   * @typedef {Object} userDataDetails
   *   @property {string} debug - The debug messages.
   *   @property {Object} output - The user characteristics data.
   */
  userDataDetails;

  collectingDelay = 1000; // Collecting delay for 1000 ms.

  // Please add data collection here if the collection requires privilege
  // access, such as accessing ChromeOnly functions. The function is called
  // right after the content page finishes.
  async collectUserCharacteristicsData() {
    lazy.console.debug("Calling collectUserCharacteristicsData()");
  }

  // This function is similar to the above function, but we call this function
  // with a delay after the content page finished. This function is for data
  // that requires loading time.
  async collectUserCharacteristicsDataWithDelay() {
    lazy.console.debug("Calling collectUserCharacteristicsDataWithDelay()");

    await this.populateGamepadsInfo();
  }

  async populateGamepadsInfo() {
    lazy.console.debug("Calling populateGamepadsInfo()");
    let gamepads = await this.contentWindow.navigator.requestAllGamepads();

    lazy.console.debug(`Found ${gamepads.length} gamepads`);

    let gamepadsInfo = [];

    for (const gamepad of gamepads) {
      // We use an array to represent a gamepad device because it uses less size
      // then an object when convert to a JSON string. So, we can fit the string
      // into a Glean string which has a 100 size limitation.
      let data = [];
      data.push(gamepad.id);
      data.push(gamepad?.hand ?? "");
      data.push(gamepad.buttons.length);
      data.push(gamepad.axes?.length ?? 0);
      data.push(gamepad.hapticActuators?.length ?? 0);
      data.push(gamepad.lightIndicators?.length ?? 0);
      data.push(gamepad.touchEvents?.length ?? 0);

      gamepadsInfo.push(JSON.stringify(data));
    }

    lazy.console.debug(`Reporting gamepad: ${gamepadsInfo}`);
    this.userDataDetails.output.gamepads = gamepadsInfo;
  }

  async handleEvent(event) {
    lazy.console.debug("Got ", event.type);
    switch (event.type) {
      case "UserCharacteristicsDataDone":
        // Clone the data so we can modify it. Otherwise, we cannot change it
        // because it's behind Xray wrapper.
        this.userDataDetails = structuredClone(event.detail);

        await this.collectUserCharacteristicsData();

        await new Promise(resolve => {
          lazy.setTimeout(resolve, this.collectingDelay);
        });

        await this.collectUserCharacteristicsDataWithDelay();

        lazy.console.debug("creating IdleDispatch");
        ChromeUtils.idleDispatch(() => {
          lazy.console.debug("sending PageReady");
          this.sendAsyncMessage(
            "UserCharacteristics::PageReady",
            this.userDataDetails
          );
        });
        break;
    }
  }
}
PK
!<>�>�YY(actors/UserCharacteristicsParent.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};
ChromeUtils.defineLazyGetter(lazy, "console", () => {
  return console.createInstance({
    prefix: "UserCharacteristicsPage",
    maxLogLevelPref: "toolkit.telemetry.user_characteristics_ping.logLevel",
  });
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "UserCharacteristicsPageService",
  "@mozilla.org/user-characteristics-page;1",
  "nsIUserCharacteristicsPageService"
);

class UserCharacteristicsParent extends JSWindowActorParent {
  receiveMessage(aMessage) {
    lazy.console.debug("Actor Parent: Got ", aMessage.name);
    switch (aMessage.name) {
      case "UserCharacteristics::PageReady":
        lazy.console.debug("Actor Parent: Got pageReady");
        lazy.UserCharacteristicsPageService.pageLoaded(
          this.browsingContext,
          aMessage.data
        );
        break;
      case "ScreenInfo:Populated":
        Services.obs.notifyObservers(
          null,
          "user-characteristics-screen-info-done",
          JSON.stringify(aMessage.data)
        );
        break;
      case "PointerInfo:Populated":
        Services.obs.notifyObservers(
          null,
          "user-characteristics-pointer-info-done",
          JSON.stringify(aMessage.data)
        );
        break;
      case "WindowInfo::Done":
        Services.obs.notifyObservers(
          null,
          "user-characteristics-window-info-done",
          aMessage.data
        );
        break;
    }
  }
}

export {
  UserCharacteristicsParent,
  UserCharacteristicsParent as UserCharacteristicsWindowInfoParent,
};
PK
!<�m?�AA1actors/UserCharacteristicsWindowInfoChild.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  return console.createInstance({
    prefix: "UserCharacteristicsPage",
    maxLogLevelPref: "toolkit.telemetry.user_characteristics_ping.logLevel",
  });
});

ChromeUtils.defineESModuleGetters(lazy, {
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

export class UserCharacteristicsWindowInfoChild extends JSWindowActorChild {
  constructor() {
    super();

    // Expected properties to collect before
    // sending the "WindowInfo::Done" message
    this.targetProperties = new Set(["ScreenInfo", "PointerInfo"]);
    this.collectedProperties = new Set();
    // Event handlers to remove when the actor is destroyed
    this.handlers = {};
    this.destroyed = false;
  }

  /*
   * This function populates the screen info from the tab it is running in,
   * and sends the properties to the parent actor.
   */
  populateScreenInfo() {
    // I'm not sure if this is really necessary, but it's here to prevent
    // the actor from running in the about:fingerprintingprotection page
    // which is a hidden browser with no toolbars etc.
    if (this.document.location.href === "about:fingerprintingprotection") {
      return;
    }

    const result = {
      outerHeight: this.contentWindow.outerHeight,
      innerHeight: this.contentWindow.innerHeight,
      outerWidth: this.contentWindow.outerWidth,
      innerWidth: this.contentWindow.innerWidth,
      availHeight: this.contentWindow.screen.availHeight,
      availWidth: this.contentWindow.screen.availWidth,
    };

    if (Object.values(result).some(v => v <= 0)) {
      return;
    }

    this.sendMessage("ScreenInfo:Populated", result);
    this.propertyCollected("ScreenInfo");
  }

  /*
   * This function listens for touchstart and pointerdown events
   * and sends the properties of the event to the parent actor.
   * The reason that we listen for both events is because rotationAngle
   * is only available in touch events and the rest of the properties
   * are only available in pointer events.
   */
  populatePointerInfo() {
    const { promise, resolve } = Promise.withResolvers();

    const touchStartHandler = e => mergeEvents(e);
    this.contentWindow.windowRoot.addEventListener(
      "touchstart",
      touchStartHandler,
      { once: true }
    );

    // Allow some time for the touchstart event to be recorded
    const pointerDownHandler = e => lazy.setTimeout(() => mergeEvents(e), 500);
    this.contentWindow.windowRoot.addEventListener(
      "pointerdown",
      pointerDownHandler,
      { once: true }
    );

    const mergedEvents = {};
    const mergeEvents = event => {
      if (event.type === "touchstart") {
        mergedEvents.touch = event;
      } else {
        mergedEvents.pointer = event;
      }

      // Resolve the promise if we got pointerdown
      if (mergedEvents.pointer) {
        resolve(mergedEvents);
      }
    };

    promise.then(e => {
      this.sendMessage("PointerInfo:Populated", {
        pointerPressure: e.pointer.pressure,
        pointerTangentinalPressure: e.pointer.tangentialPressure,
        pointerTiltx: e.pointer.tiltX,
        pointerTilty: e.pointer.tiltY,
        pointerTwist: e.pointer.twist,
        pointerWidth: e.pointer.width,
        pointerHeight: e.pointer.height,
        touchRotationAngle: e.touch?.rotationAngle || 0,
      });
      this.propertyCollected("PointerInfo");
    });

    this.handlers.touchstart = touchStartHandler;
    this.handlers.pointerdown = pointerDownHandler;
  }

  sendMessage(name, obj, transferables) {
    if (this.destroyed) {
      return;
    }

    this.sendAsyncMessage(name, obj, transferables);
  }

  propertyCollected(name) {
    this.collectedProperties.add(name);
    if (this.targetProperties.difference(this.collectedProperties).size === 0) {
      this.sendMessage("WindowInfo::Done");
    }
  }

  didDestroy() {
    this.destroyed = true;
    for (const [type, handler] of Object.entries(this.handlers)) {
      this.contentWindow.windowRoot.removeEventListener(type, handler);
    }
  }

  async receiveMessage(msg) {
    lazy.console.debug("Actor Child: Got ", msg.name);
    switch (msg.name) {
      case "WindowInfo:PopulateFromDocument":
        if (this.document.readyState == "complete") {
          this.populateScreenInfo();
          this.populatePointerInfo();
        }
        break;
    }

    return null;
  }

  async handleEvent(event) {
    lazy.console.debug("Actor Child: Got ", event.type);
    switch (event.type) {
      case "DOMContentLoaded":
        this.populateScreenInfo();
        this.populatePointerInfo();
        break;
    }
  }
}
PK
!<��h�7070actors/ViewSourceChild.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ViewSourcePageChild: "resource://gre/actors/ViewSourcePageChild.sys.mjs",
});

export class ViewSourceChild extends JSWindowActorChild {
  receiveMessage(message) {
    let data = message.data;
    switch (message.name) {
      case "ViewSource:LoadSource":
        this.viewSource(data.URL, data.outerWindowID, data.lineNumber);
        break;
      case "ViewSource:LoadSourceWithSelection":
        this.viewSourceWithSelection(
          data.URL,
          data.drawSelection,
          data.baseURI
        );
        break;
      case "ViewSource:GetSelection":
        let selectionDetails;
        try {
          selectionDetails = this.getSelection(this.document.ownerGlobal);
        } catch (e) {}
        return selectionDetails;
    }

    return undefined;
  }

  /**
   * Called when the parent sends a message to view some source code.
   *
   * @param URL (required)
   *        The URL string of the source to be shown.
   * @param outerWindowID (optional)
   *        The outerWindowID of the content window that has hosted
   *        the document, in case we want to retrieve it from the network
   *        cache.
   * @param lineNumber (optional)
   *        The line number to focus as soon as the source has finished
   *        loading.
   */
  viewSource(URL, outerWindowID, lineNumber) {
    let otherDocShell;
    let forceEncodingDetection = false;

    if (outerWindowID) {
      let contentWindow = Services.wm.getOuterWindowWithId(outerWindowID);
      if (contentWindow) {
        otherDocShell = contentWindow.docShell;

        forceEncodingDetection = contentWindow.windowUtils.docCharsetIsForced;
      }
    }

    this.loadSource(URL, otherDocShell, lineNumber, forceEncodingDetection);
  }

  /**
   * Loads a view source selection showing the given view-source url and
   * highlight the selection.
   *
   * @param uri view-source uri to show
   * @param drawSelection true to highlight the selection
   * @param baseURI base URI of the original document
   */
  viewSourceWithSelection(uri, drawSelection, baseURI) {
    // This isn't ideal, but set a global in the view source page actor
    // that indicates that a selection should be drawn. It will be read
    // when by the page's pageshow listener. This should work as the
    // view source page is always loaded in the same process.
    lazy.ViewSourcePageChild.setNeedsDrawSelection(drawSelection);

    // all our content is held by the data:URI and URIs are internally stored as utf-8 (see nsIURI.idl)
    let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
    let webNav = this.docShell.QueryInterface(Ci.nsIWebNavigation);
    let loadURIOptions = {
      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
      loadFlags,
      baseURI: Services.io.newURI(baseURI),
    };
    webNav.fixupAndLoadURIString(uri, loadURIOptions);
  }

  /**
   * Common utility function used by both the current and deprecated APIs
   * for loading source.
   *
   * @param URL (required)
   *        The URL string of the source to be shown.
   * @param otherDocShell (optional)
   *        The docshell of the content window that is hosting the document.
   * @param lineNumber (optional)
   *        The line number to focus as soon as the source has finished
   *        loading.
   * @param forceEncodingDetection (optional)
   *        Force autodetection of the character encoding.
   */
  loadSource(URL, otherDocShell, lineNumber, forceEncodingDetection) {
    const viewSrcURL = "view-source:" + URL;

    if (forceEncodingDetection) {
      this.docShell.forceEncodingDetection();
    }

    if (lineNumber) {
      lazy.ViewSourcePageChild.setInitialLineNumber(lineNumber);
    }

    if (!otherDocShell) {
      this.loadSourceFromURL(viewSrcURL);
      return;
    }

    try {
      let pageLoader = this.docShell.QueryInterface(Ci.nsIWebPageDescriptor);
      pageLoader.loadPageAsViewSource(otherDocShell, viewSrcURL);
    } catch (e) {
      // We were not able to load the source from the network cache.
      this.loadSourceFromURL(viewSrcURL);
    }
  }

  /**
   * Load some URL in the browser.
   *
   * @param URL
   *        The URL string to load.
   */
  loadSourceFromURL(URL) {
    let loadFlags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
    let webNav = this.docShell.QueryInterface(Ci.nsIWebNavigation);
    let loadURIOptions = {
      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
      loadFlags,
    };
    webNav.fixupAndLoadURIString(URL, loadURIOptions);
  }

  /**
   * A helper to get a path like FIXptr, but with an array instead of the
   * "tumbler" notation.
   * See FIXptr: http://lists.w3.org/Archives/Public/www-xml-linking-comments/2001AprJun/att-0074/01-NOTE-FIXptr-20010425.htm
   */
  getPath(ancestor, node) {
    var n = node;
    var p = n.parentNode;
    if (n == ancestor || !p) {
      return null;
    }
    var path = [];
    if (!path) {
      return null;
    }
    do {
      for (var i = 0; i < p.childNodes.length; i++) {
        if (p.childNodes.item(i) == n) {
          path.push(i);
          break;
        }
      }
      n = p;
      p = n.parentNode;
    } while (n != ancestor && p);
    return path;
  }

  getSelection(global) {
    const { content } = global;

    // These are markers used to delimit the selection during processing. They
    // are removed from the final rendering.
    // We use noncharacter Unicode codepoints to minimize the risk of clashing
    // with anything that might legitimately be present in the document.
    // U+FDD0..FDEF <noncharacters>
    const MARK_SELECTION_START = "\uFDD0";
    const MARK_SELECTION_END = "\uFDEF";

    var focusedWindow = Services.focus.focusedWindow || content;
    var selection = focusedWindow.getSelection();

    var range = selection.getRangeAt(0);
    var ancestorContainer = range.commonAncestorContainer;
    var doc = ancestorContainer.ownerDocument;

    var startContainer = range.startContainer;
    var endContainer = range.endContainer;
    var startOffset = range.startOffset;
    var endOffset = range.endOffset;

    // let the ancestor be an element
    var Node = doc.defaultView.Node;
    if (
      ancestorContainer.nodeType == Node.TEXT_NODE ||
      ancestorContainer.nodeType == Node.CDATA_SECTION_NODE
    ) {
      ancestorContainer = ancestorContainer.parentNode;
    }

    // for selectAll, let's use the entire document, including <html>...</html>
    // @see nsDocumentViewer::SelectAll() for how selectAll is implemented
    try {
      if (ancestorContainer == doc.body) {
        ancestorContainer = doc.documentElement;
      }
    } catch (e) {}

    // each path is a "child sequence" (a.k.a. "tumbler") that
    // descends from the ancestor down to the boundary point
    var startPath = this.getPath(ancestorContainer, startContainer);
    var endPath = this.getPath(ancestorContainer, endContainer);

    // clone the fragment of interest and reset everything to be relative to it
    // note: it is with the clone that we operate/munge from now on.  Also note
    // that we clone into a data document to prevent images in the fragment from
    // loading and the like.  The use of importNode here, as opposed to adoptNode,
    // is _very_ important.
    // XXXbz wish there were a less hacky way to create an untrusted document here
    var isHTML = doc.createElement("div").tagName == "DIV";
    var dataDoc = isHTML
      ? ancestorContainer.ownerDocument.implementation.createHTMLDocument("")
      : ancestorContainer.ownerDocument.implementation.createDocument(
          "",
          "",
          null
        );
    ancestorContainer = dataDoc.importNode(ancestorContainer, true);
    startContainer = ancestorContainer;
    endContainer = ancestorContainer;

    // Only bother with the selection if it can be remapped. Don't mess with
    // leaf elements (such as <isindex>) that secretly use anynomous content
    // for their display appearance.
    var canDrawSelection = ancestorContainer.hasChildNodes();
    var tmpNode;
    if (canDrawSelection) {
      var i;
      for (i = startPath ? startPath.length - 1 : -1; i >= 0; i--) {
        startContainer = startContainer.childNodes.item(startPath[i]);
      }
      for (i = endPath ? endPath.length - 1 : -1; i >= 0; i--) {
        endContainer = endContainer.childNodes.item(endPath[i]);
      }

      // add special markers to record the extent of the selection
      // note: |startOffset| and |endOffset| are interpreted either as
      // offsets in the text data or as child indices (see the Range spec)
      // (here, munging the end point first to keep the start point safe...)
      if (
        endContainer.nodeType == Node.TEXT_NODE ||
        endContainer.nodeType == Node.CDATA_SECTION_NODE
      ) {
        // do some extra tweaks to try to avoid the view-source output to look like
        // ...<tag>]... or ...]</tag>... (where ']' marks the end of the selection).
        // To get a neat output, the idea here is to remap the end point from:
        // 1. ...<tag>]...   to   ...]<tag>...
        // 2. ...]</tag>...  to   ...</tag>]...
        if (
          (endOffset > 0 && endOffset < endContainer.data.length) ||
          !endContainer.parentNode ||
          !endContainer.parentNode.parentNode
        ) {
          endContainer.insertData(endOffset, MARK_SELECTION_END);
        } else {
          tmpNode = dataDoc.createTextNode(MARK_SELECTION_END);
          endContainer = endContainer.parentNode;
          if (endOffset === 0) {
            endContainer.parentNode.insertBefore(tmpNode, endContainer);
          } else {
            endContainer.parentNode.insertBefore(
              tmpNode,
              endContainer.nextSibling
            );
          }
        }
      } else {
        tmpNode = dataDoc.createTextNode(MARK_SELECTION_END);
        endContainer.insertBefore(
          tmpNode,
          endContainer.childNodes.item(endOffset)
        );
      }

      if (
        startContainer.nodeType == Node.TEXT_NODE ||
        startContainer.nodeType == Node.CDATA_SECTION_NODE
      ) {
        // do some extra tweaks to try to avoid the view-source output to look like
        // ...<tag>[... or ...[</tag>... (where '[' marks the start of the selection).
        // To get a neat output, the idea here is to remap the start point from:
        // 1. ...<tag>[...   to   ...[<tag>...
        // 2. ...[</tag>...  to   ...</tag>[...
        if (
          (startOffset > 0 && startOffset < startContainer.data.length) ||
          !startContainer.parentNode ||
          !startContainer.parentNode.parentNode ||
          startContainer != startContainer.parentNode.lastChild
        ) {
          startContainer.insertData(startOffset, MARK_SELECTION_START);
        } else {
          tmpNode = dataDoc.createTextNode(MARK_SELECTION_START);
          startContainer = startContainer.parentNode;
          if (startOffset === 0) {
            startContainer.parentNode.insertBefore(tmpNode, startContainer);
          } else {
            startContainer.parentNode.insertBefore(
              tmpNode,
              startContainer.nextSibling
            );
          }
        }
      } else {
        tmpNode = dataDoc.createTextNode(MARK_SELECTION_START);
        startContainer.insertBefore(
          tmpNode,
          startContainer.childNodes.item(startOffset)
        );
      }
    }

    // now extract and display the syntax highlighted source
    tmpNode = dataDoc.createElementNS("http://www.w3.org/1999/xhtml", "div");
    tmpNode.appendChild(ancestorContainer);

    return {
      URL:
        (isHTML
          ? "view-source:data:text/html;charset=utf-8,"
          : "view-source:data:application/xml;charset=utf-8,") +
        encodeURIComponent(tmpNode.innerHTML),
      drawSelection: canDrawSelection,
      baseURI: doc.baseURI,
    };
  }

  get wrapLongLines() {
    return Services.prefs.getBoolPref("view_source.wrap_long_lines");
  }
}
PK
!<J�"�;�;"actors/ViewSourcePageChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const BUNDLE_URL = "chrome://global/locale/viewSource.properties";

// These are markers used to delimit the selection during processing. They
// are removed from the final rendering.
// We use noncharacter Unicode codepoints to minimize the risk of clashing
// with anything that might legitimately be present in the document.
// U+FDD0..FDEF <noncharacters>
const MARK_SELECTION_START = "\uFDD0";
const MARK_SELECTION_END = "\uFDEF";

/**
 * When showing selection source, chrome will construct a page fragment to
 * show, and then instruct content to draw a selection after load.  This is
 * set true when there is a pending request to draw selection.
 */
let gNeedsDrawSelection = false;

/**
 * Start at a specific line number.
 */
let gInitialLineNumber = -1;

export class ViewSourcePageChild extends JSWindowActorChild {
  constructor() {
    super();

    ChromeUtils.defineLazyGetter(this, "bundle", function () {
      return Services.strings.createBundle(BUNDLE_URL);
    });
  }

  static setNeedsDrawSelection(value) {
    gNeedsDrawSelection = value;
  }

  static setInitialLineNumber(value) {
    gInitialLineNumber = value;
  }

  receiveMessage(msg) {
    switch (msg.name) {
      case "ViewSource:GoToLine":
        this.goToLine(msg.data.lineNumber);
        break;
      case "ViewSource:IsWrapping":
        return this.isWrapping;
      case "ViewSource:IsSyntaxHighlighting":
        return this.isSyntaxHighlighting;
      case "ViewSource:ToggleWrapping":
        this.toggleWrapping();
        break;
      case "ViewSource:ToggleSyntaxHighlighting":
        this.toggleSyntaxHighlighting();
        break;
    }
    return undefined;
  }

  /**
   * Any events should get handled here, and should get dispatched to
   * a specific function for the event type.
   */
  handleEvent(event) {
    switch (event.type) {
      case "pageshow":
        this.onPageShow(event);
        break;
      case "click":
        this.onClick(event);
        break;
    }
  }

  /**
   * A shortcut to the nsISelectionController for the content.
   */
  get selectionController() {
    return this.docShell
      .QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsISelectionDisplay)
      .QueryInterface(Ci.nsISelectionController);
  }

  /**
   * A shortcut to the nsIWebBrowserFind for the content.
   */
  get webBrowserFind() {
    return this.docShell
      .QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIWebBrowserFind);
  }

  /**
   * This handler is for click events from:
   *   * error page content, which can show up if the user attempts to view the
   *     source of an attack page.
   */
  onClick(event) {
    let target = event.originalTarget;

    // Don't trust synthetic events
    if (!event.isTrusted || event.target.localName != "button") {
      return;
    }

    let errorDoc = target.ownerDocument;

    if (/^about:blocked/.test(errorDoc.documentURI)) {
      // The event came from a button on a malware/phishing block page

      if (target == errorDoc.getElementById("goBackButton")) {
        // Instead of loading some safe page, just close the window
        this.sendAsyncMessage("ViewSource:Close");
      }
    }
  }

  /**
   * Handler for the pageshow event.
   *
   * @param event
   *        The pageshow event being handled.
   */
  onPageShow() {
    // If we need to draw the selection, wait until an actual view source page
    // has loaded, instead of about:blank.
    if (
      gNeedsDrawSelection &&
      this.document.documentURI.startsWith("view-source:")
    ) {
      gNeedsDrawSelection = false;
      this.drawSelection();
    }

    if (gInitialLineNumber >= 0) {
      this.goToLine(gInitialLineNumber);
      gInitialLineNumber = -1;
    }
  }

  /**
   * Attempts to go to a particular line in the source code being
   * shown. If it succeeds in finding the line, it will fire a
   * "ViewSource:GoToLine:Success" message, passing up an object
   * with the lineNumber we just went to. If it cannot find the line,
   * it will fire a "ViewSource:GoToLine:Failed" message.
   *
   * @param lineNumber
   *        The line number to attempt to go to.
   */
  goToLine(lineNumber) {
    let body = this.document.body;

    // The source document is made up of a number of pre elements with
    // id attributes in the format <pre id="line123">, meaning that
    // the first line in the pre element is number 123.
    // Do binary search to find the pre element containing the line.
    // However, in the plain text case, we have only one pre without an
    // attribute, so assume it begins on line 1.
    let pre;
    for (let lbound = 0, ubound = body.childNodes.length; ; ) {
      let middle = (lbound + ubound) >> 1;
      pre = body.childNodes[middle];

      let firstLine = pre.id ? parseInt(pre.id.substring(4)) : 1;

      if (lbound == ubound - 1) {
        break;
      }

      if (lineNumber >= firstLine) {
        lbound = middle;
      } else {
        ubound = middle;
      }
    }

    let result = {};
    let found = this.findLocation(pre, lineNumber, null, -1, false, result);

    if (!found) {
      this.sendAsyncMessage("ViewSource:GoToLine:Failed");
      return;
    }

    let selection = this.document.defaultView.getSelection();
    selection.removeAllRanges();

    // In our case, the range's startOffset is after "\n" on the previous line.
    // Tune the selection at the beginning of the next line and do some tweaking
    // to position the focusNode and the caret at the beginning of the line.
    selection.interlinePosition = true;

    selection.addRange(result.range);

    if (!selection.isCollapsed) {
      selection.collapseToEnd();

      let offset = result.range.startOffset;
      let node = result.range.startContainer;
      if (offset < node.data.length) {
        // The same text node spans across the "\n", just focus where we were.
        selection.extend(node, offset);
      } else {
        // There is another tag just after the "\n", hook there. We need
        // to focus a safe point because there are edgy cases such as
        // <span>...\n</span><span>...</span> vs.
        // <span>...\n<span>...</span></span><span>...</span>
        node = node.nextSibling
          ? node.nextSibling
          : node.parentNode.nextSibling;
        selection.extend(node, 0);
      }
    }

    let selCon = this.selectionController;
    selCon.setDisplaySelection(Ci.nsISelectionController.SELECTION_ON);
    selCon.setCaretVisibilityDuringSelection(true);

    // Scroll the beginning of the line into view.
    selCon.scrollSelectionIntoView(
      Ci.nsISelectionController.SELECTION_NORMAL,
      Ci.nsISelectionController.SELECTION_FOCUS_REGION,
      true
    );

    this.sendAsyncMessage("ViewSource:GoToLine:Success", { lineNumber });
  }

  /**
   * Some old code from the original view source implementation. Original
   * documentation follows:
   *
   * "Loops through the text lines in the pre element. The arguments are either
   *  (pre, line) or (node, offset, interlinePosition). result is an out
   *  argument. If (pre, line) are specified (and node == null), result.range is
   *  a range spanning the specified line. If the (node, offset,
   *  interlinePosition) are specified, result.line and result.col are the line
   *  and column number of the specified offset in the specified node relative to
   *  the whole file."
   */
  findLocation(pre, lineNumber, node, offset, interlinePosition, result) {
    if (node && !pre) {
      // Look upwards to find the current pre element.
      // eslint-disable-next-line no-empty
      for (pre = node; pre.nodeName != "PRE"; pre = pre.parentNode) {}
    }

    // The source document is made up of a number of pre elements with
    // id attributes in the format <pre id="line123">, meaning that
    // the first line in the pre element is number 123.
    // However, in the plain text case, there is only one <pre> without an id,
    // so assume line 1.
    let curLine = pre.id ? parseInt(pre.id.substring(4)) : 1;

    // Walk through each of the text nodes and count newlines.
    let treewalker = this.document.createTreeWalker(
      pre,
      NodeFilter.SHOW_TEXT,
      null
    );

    // The column number of the first character in the current text node.
    let firstCol = 1;

    let found = false;
    for (
      let textNode = treewalker.firstChild();
      textNode && !found;
      textNode = treewalker.nextNode()
    ) {
      // \r is not a valid character in the DOM, so we only check for \n.
      let lineArray = textNode.data.split(/\n/);
      let lastLineInNode = curLine + lineArray.length - 1;

      // Check if we can skip the text node without further inspection.
      if (node ? textNode != node : lastLineInNode < lineNumber) {
        if (lineArray.length > 1) {
          firstCol = 1;
        }
        firstCol += lineArray[lineArray.length - 1].length;
        curLine = lastLineInNode;
        continue;
      }

      // curPos is the offset within the current text node of the first
      // character in the current line.
      for (
        var i = 0, curPos = 0;
        i < lineArray.length;
        curPos += lineArray[i++].length + 1
      ) {
        if (i > 0) {
          curLine++;
        }

        if (node) {
          if (offset >= curPos && offset <= curPos + lineArray[i].length) {
            // If we are right after the \n of a line and interlinePosition is
            // false, the caret looks as if it were at the end of the previous
            // line, so we display that line and column instead.

            if (i > 0 && offset == curPos && !interlinePosition) {
              result.line = curLine - 1;
              var prevPos = curPos - lineArray[i - 1].length;
              result.col = (i == 1 ? firstCol : 1) + offset - prevPos;
            } else {
              result.line = curLine;
              result.col = (i == 0 ? firstCol : 1) + offset - curPos;
            }
            found = true;

            break;
          }
        } else if (curLine == lineNumber && !("range" in result)) {
          result.range = this.document.createRange();
          result.range.setStart(textNode, curPos);

          // This will always be overridden later, except when we look for
          // the very last line in the file (this is the only line that does
          // not end with \n).
          result.range.setEndAfter(pre.lastChild);
        } else if (curLine == lineNumber + 1) {
          result.range.setEnd(textNode, curPos - 1);
          found = true;
          break;
        }
      }
    }

    return found || "range" in result;
  }

  /**
   * @return {boolean} whether the "wrap" class exists on the document body.
   */
  get isWrapping() {
    return this.document.body.classList.contains("wrap");
  }

  /**
   * @return {boolean} whether the "highlight" class exists on the document body.
   */
  get isSyntaxHighlighting() {
    return this.document.body.classList.contains("highlight");
  }

  /**
   * Toggles the "wrap" class on the document body, which sets whether
   * or not long lines are wrapped.  Notifies parent to update the pref.
   */
  toggleWrapping() {
    let body = this.document.body;
    let state = body.classList.toggle("wrap");
    this.sendAsyncMessage("ViewSource:StoreWrapping", { state });
  }

  /**
   * Toggles the "highlight" class on the document body, which sets whether
   * or not syntax highlighting is displayed.  Notifies parent to update the
   * pref.
   */
  toggleSyntaxHighlighting() {
    let body = this.document.body;
    let state = body.classList.toggle("highlight");
    this.sendAsyncMessage("ViewSource:StoreSyntaxHighlighting", { state });
  }

  /**
   * Using special markers left in the serialized source, this helper makes the
   * underlying markup of the selected fragment to automatically appear as
   * selected on the inflated view-source DOM.
   */
  drawSelection() {
    this.document.title = this.bundle.GetStringFromName(
      "viewSelectionSourceTitle"
    );

    // find the special selection markers that we added earlier, and
    // draw the selection between the two...
    var findService = null;
    try {
      // get the find service which stores the global find state
      findService = Cc["@mozilla.org/find/find_service;1"].getService(
        Ci.nsIFindService
      );
    } catch (e) {}
    if (!findService) {
      return;
    }

    // cache the current global find state
    var matchCase = findService.matchCase;
    var entireWord = findService.entireWord;
    var wrapFind = findService.wrapFind;
    var findBackwards = findService.findBackwards;
    var searchString = findService.searchString;
    var replaceString = findService.replaceString;

    // setup our find instance
    var findInst = this.webBrowserFind;
    findInst.matchCase = true;
    findInst.entireWord = false;
    findInst.wrapFind = true;
    findInst.findBackwards = false;

    // ...lookup the start mark
    findInst.searchString = MARK_SELECTION_START;
    var startLength = MARK_SELECTION_START.length;
    findInst.findNext();

    var selection = this.document.defaultView.getSelection();
    if (!selection.rangeCount) {
      return;
    }

    var range = selection.getRangeAt(0);

    var startContainer = range.startContainer;
    var startOffset = range.startOffset;

    // ...lookup the end mark
    findInst.searchString = MARK_SELECTION_END;
    var endLength = MARK_SELECTION_END.length;
    findInst.findNext();

    var endContainer = selection.anchorNode;
    var endOffset = selection.anchorOffset;

    // reset the selection that find has left
    selection.removeAllRanges();

    // delete the special markers now...
    endContainer.deleteData(endOffset, endLength);
    startContainer.deleteData(startOffset, startLength);
    if (startContainer == endContainer) {
      endOffset -= startLength;
    } // has shrunk if on same text node...
    range.setEnd(endContainer, endOffset);

    // show the selection and scroll it into view
    selection.addRange(range);
    // the default behavior of the selection is to scroll at the end of
    // the selection, whereas in this situation, it is more user-friendly
    // to scroll at the beginning. So we override the default behavior here
    try {
      this.selectionController.scrollSelectionIntoView(
        Ci.nsISelectionController.SELECTION_NORMAL,
        Ci.nsISelectionController.SELECTION_ANCHOR_REGION,
        true
      );
    } catch (e) {}

    // restore the current find state
    findService.matchCase = matchCase;
    findService.entireWord = entireWord;
    findService.wrapFind = wrapFind;
    findService.findBackwards = findBackwards;
    findService.searchString = searchString;
    findService.replaceString = replaceString;

    findInst.matchCase = matchCase;
    findInst.entireWord = entireWord;
    findInst.wrapFind = wrapFind;
    findInst.findBackwards = findBackwards;
    findInst.searchString = searchString;
  }
}
PK
!<��ݐ��#actors/ViewSourcePageParent.sys.mjs// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const BUNDLE_URL = "chrome://global/locale/viewSource.properties";

/**
 * ViewSourcePageParent manages the view source <browser> from the chrome side.
 */
export class ViewSourcePageParent extends JSWindowActorParent {
  constructor() {
    super();

    /**
     * Holds the value of the last line found via the "Go to line"
     * command, to pre-populate the prompt the next time it is
     * opened.
     */
    this.lastLineFound = null;
  }

  /**
   * Anything added to the messages array will get handled here, and should
   * get dispatched to a specific function for the message name.
   */
  receiveMessage(message) {
    let data = message.data;

    switch (message.name) {
      case "ViewSource:PromptAndGoToLine":
        this.promptAndGoToLine();
        break;
      case "ViewSource:GoToLine:Success":
        this.onGoToLineSuccess(data.lineNumber);
        break;
      case "ViewSource:GoToLine:Failed":
        this.onGoToLineFailed();
        break;
      case "ViewSource:StoreWrapping":
        this.storeWrapping(data.state);
        break;
      case "ViewSource:StoreSyntaxHighlighting":
        this.storeSyntaxHighlighting(data.state);
        break;
    }
  }

  /**
   * A getter for the view source string bundle.
   */
  get bundle() {
    if (this._bundle) {
      return this._bundle;
    }
    return (this._bundle = Services.strings.createBundle(BUNDLE_URL));
  }

  /**
   * Opens the "Go to line" prompt for a user to hop to a particular line
   * of the source code they're viewing. This will keep prompting until the
   * user either cancels out of the prompt, or enters a valid line number.
   */
  promptAndGoToLine() {
    let input = { value: this.lastLineFound };
    let window = Services.wm.getMostRecentWindow(null);

    let ok = Services.prompt.prompt(
      window,
      this.bundle.GetStringFromName("goToLineTitle"),
      this.bundle.GetStringFromName("goToLineText"),
      input,
      null,
      { value: 0 }
    );

    if (!ok) {
      return;
    }

    let line = parseInt(input.value, 10);

    if (!(line > 0)) {
      Services.prompt.alert(
        window,
        this.bundle.GetStringFromName("invalidInputTitle"),
        this.bundle.GetStringFromName("invalidInputText")
      );
      this.promptAndGoToLine();
    } else {
      this.goToLine(line);
    }
  }

  /**
   * Go to a particular line of the source code. This act is asynchronous.
   *
   * @param lineNumber
   *        The line number to try to go to to.
   */
  goToLine(lineNumber) {
    this.sendAsyncMessage("ViewSource:GoToLine", { lineNumber });
  }

  /**
   * Called when the frame script reports that a line was successfully gotten
   * to.
   *
   * @param lineNumber
   *        The line number that we successfully got to.
   */
  onGoToLineSuccess(lineNumber) {
    // We'll pre-populate the "Go to line" prompt with this value the next
    // time it comes up.
    this.lastLineFound = lineNumber;
  }

  /**
   * Called when the child reports that we failed to go to a particular
   * line. This informs the user that their selection was likely out of range,
   * and then reprompts the user to try again.
   */
  onGoToLineFailed() {
    let window = Services.wm.getMostRecentWindow(null);
    Services.prompt.alert(
      window,
      this.bundle.GetStringFromName("outOfRangeTitle"),
      this.bundle.GetStringFromName("outOfRangeText")
    );
    this.promptAndGoToLine();
  }

  /**
   * @return {boolean} the wrapping state
   */
  queryIsWrapping() {
    return this.sendQuery("ViewSource:IsWrapping");
  }

  /**
   * @return {boolean} the syntax highlighting state
   */
  queryIsSyntaxHighlighting() {
    return this.sendQuery("ViewSource:IsSyntaxHighlighting");
  }

  /**
   * Update the wrapping pref based on the child's current state.
   * @param state
   *        Whether wrapping is currently enabled in the child.
   */
  storeWrapping(state) {
    Services.prefs.setBoolPref("view_source.wrap_long_lines", state);
  }

  /**
   * Update the syntax highlighting pref based on the child's current state.
   * @param state
   *        Whether syntax highlighting is currently enabled in the child.
   */
  storeSyntaxHighlighting(state) {
    Services.prefs.setBoolPref("view_source.syntax_highlight", state);
  }
}
PK
!<�nx==actors/WebChannelChild.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

import { ContentDOMReference } from "resource://gre/modules/ContentDOMReference.sys.mjs";

// Preference containing the list (space separated) of origins that are
// allowed to send non-string values through a WebChannel, mainly for
// backwards compatability. See bug 1238128 for more information.
const URL_WHITELIST_PREF = "webchannel.allowObject.urlWhitelist";

let _cachedWhitelist = null;

const CACHED_PREFS = {};
XPCOMUtils.defineLazyPreferenceGetter(
  CACHED_PREFS,
  "URL_WHITELIST",
  URL_WHITELIST_PREF,
  "",
  // Null this out so we update it.
  () => (_cachedWhitelist = null)
);

export class WebChannelChild extends JSWindowActorChild {
  handleEvent(event) {
    if (event.type === "WebChannelMessageToChrome") {
      return this._onMessageToChrome(event);
    }
    return undefined;
  }

  receiveMessage(msg) {
    if (msg.name === "WebChannelMessageToContent") {
      return this._onMessageToContent(msg);
    }
    return undefined;
  }

  _getWhitelistedPrincipals() {
    if (!_cachedWhitelist) {
      let urls = CACHED_PREFS.URL_WHITELIST.split(/\s+/);
      _cachedWhitelist = urls.map(origin =>
        Services.scriptSecurityManager.createContentPrincipalFromOrigin(origin)
      );
    }
    return _cachedWhitelist;
  }

  _onMessageToChrome(e) {
    // If target is window then we want the document principal, otherwise fallback to target itself.
    let principal = e.target.nodePrincipal
      ? e.target.nodePrincipal
      : e.target.document.nodePrincipal;

    if (e.detail) {
      if (typeof e.detail != "string") {
        // Check if the principal is one of the ones that's allowed to send
        // non-string values for e.detail.  They're whitelisted by site origin,
        // so we compare on originNoSuffix in order to avoid other origin attributes
        // that are not relevant here, such as containers or private browsing.
        let objectsAllowed = this._getWhitelistedPrincipals().some(
          whitelisted => principal.originNoSuffix == whitelisted.originNoSuffix
        );
        if (!objectsAllowed) {
          console.error(
            "WebChannelMessageToChrome sent with an object from a non-whitelisted principal"
          );
          return;
        }
      }

      let eventTarget =
        e.target instanceof Ci.nsIDOMWindow
          ? null
          : ContentDOMReference.get(e.target);
      this.sendAsyncMessage("WebChannelMessageToChrome", {
        contentData: e.detail,
        eventTarget,
        principal,
      });
    } else {
      console.error("WebChannel message failed. No message detail.");
    }
  }

  _onMessageToContent(msg) {
    if (msg.data && this.contentWindow) {
      // msg.objects.eventTarget will be defined if sending a response to
      // a WebChannelMessageToChrome event. An unsolicited send
      // may not have an eventTarget defined, in this case send to the
      // main content window.
      let { eventTarget, principal } = msg.data;
      if (!eventTarget) {
        eventTarget = this.contentWindow;
      } else {
        eventTarget = ContentDOMReference.resolve(eventTarget);
      }
      if (!eventTarget) {
        console.error("WebChannel message failed. No target.");
        return;
      }

      // Use nodePrincipal if available, otherwise fallback to document principal.
      let targetPrincipal =
        eventTarget instanceof Ci.nsIDOMWindow
          ? eventTarget.document.nodePrincipal
          : eventTarget.nodePrincipal;

      if (principal.subsumes(targetPrincipal)) {
        let targetWindow = this.contentWindow;
        eventTarget.dispatchEvent(
          new targetWindow.CustomEvent("WebChannelMessageToContent", {
            detail: Cu.cloneInto(
              {
                id: msg.data.id,
                message: msg.data.message,
              },
              targetWindow
            ),
          })
        );
      } else {
        console.error("WebChannel message failed. Principal mismatch.");
      }
    } else {
      console.error("WebChannel message failed. No message data.");
    }
  }
}
PK
!<��(�
�
actors/WebChannelParent.sys.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { WebChannelBroker } from "resource://gre/modules/WebChannel.sys.mjs";

const ERRNO_MISSING_PRINCIPAL = 1;
const ERRNO_NO_SUCH_CHANNEL = 2;

export class WebChannelParent extends JSWindowActorParent {
  receiveMessage(msg) {
    let data = msg.data.contentData;
    let sendingContext = {
      browsingContext: this.browsingContext,
      browser: this.browsingContext.top.embedderElement,
      eventTarget: msg.data.eventTarget,
      principal: msg.data.principal,
    };
    // data must be a string except for a few legacy origins allowed by browser-content.js.
    if (typeof data == "string") {
      try {
        data = JSON.parse(data);
      } catch (e) {
        console.error("Failed to parse WebChannel data as a JSON object");
        return;
      }
    }

    if (data && data.id) {
      if (!msg.data.principal) {
        this._sendErrorEventToContent(
          data.id,
          sendingContext,
          ERRNO_MISSING_PRINCIPAL,
          "Message principal missing"
        );
      } else {
        let validChannelFound = WebChannelBroker.tryToDeliver(
          data,
          sendingContext
        );

        // if no valid origins send an event that there is no such valid channel
        if (!validChannelFound) {
          this._sendErrorEventToContent(
            data.id,
            sendingContext,
            ERRNO_NO_SUCH_CHANNEL,
            "No Such Channel"
          );
        }
      }
    } else {
      console.error("WebChannel channel id missing");
    }
  }

  /**
   *
   * @param id {String}
   *        The WebChannel id to include in the message
   * @param sendingContext {Object}
   *        Message sending context
   * @param [errorMsg] {String}
   *        Error message
   * @private
   */
  _sendErrorEventToContent(id, sendingContext, errorNo, errorMsg) {
    let { eventTarget, principal } = sendingContext;

    errorMsg = errorMsg || "Web Channel Parent error";

    let { currentWindowGlobal = null } = this.browsingContext;
    if (currentWindowGlobal) {
      currentWindowGlobal
        .getActor("WebChannel")
        .sendAsyncMessage("WebChannelMessageToContent", {
          id,
          message: {
            errno: errorNo,
            error: errorMsg,
          },
          eventTarget,
          principal,
        });
    } else {
      console.error("Failed to send a WebChannel error. Target invalid.");
    }
    console.error(id.toString() + " error message. ", errorMsg);
  }
}
PK
!<�h	���=chrome/toolkit/content/extensions/child/ext-backgroundPage.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.backgroundPage = class extends ExtensionAPI {
  getAPI(context) {
    function getBackgroundPage() {
      for (let view of context.extension.views) {
        if (
          // To find the (top-level) background context, this logic relies on
          // the order of views, implied by the fact that the top-level context
          // is created before child contexts. If this assumption ever becomes
          // invalid, add a check for view.isBackgroundContext.
          view.viewType == "background" &&
          context.principal.subsumes(view.principal)
        ) {
          return view.contentWindow;
        }
      }
      return null;
    }
    return {
      extension: {
        getBackgroundPage,
      },

      runtime: {
        getBackgroundPage() {
          return context.childManager
            .callParentAsyncFunction("runtime.internalWakeupBackground", [])
            .then(() => {
              return context.cloneScope.Promise.resolve(getBackgroundPage());
            });
        },
      },
    };
  }
};
PK
!<s��Uzz=chrome/toolkit/content/extensions/child/ext-contentScripts.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var { ExtensionError } = ExtensionUtils;

/**
 * Represents (in the child extension process) a content script registered
 * programmatically (instead of being included in the addon manifest).
 *
 * @param {ExtensionPageContextChild} context
 *        The extension context which has registered the content script.
 * @param {string} scriptId
 *        An unique id that represents the registered content script
 *        (generated and used internally to identify it across the different processes).
 */
class ContentScriptChild {
  constructor(context, scriptId) {
    this.context = context;
    this.scriptId = scriptId;
    this.unregistered = false;
  }

  async unregister() {
    if (this.unregistered) {
      throw new ExtensionError("Content script already unregistered");
    }

    this.unregistered = true;

    await this.context.childManager.callParentAsyncFunction(
      "contentScripts.unregister",
      [this.scriptId]
    );

    this.context = null;
  }

  api() {
    const { context } = this;

    // TODO(rpl): allow to read the options related to the registered content script?
    return {
      unregister: () => {
        return context.wrapPromise(this.unregister());
      },
    };
  }
}

this.contentScripts = class extends ExtensionAPI {
  getAPI(context) {
    return {
      contentScripts: {
        register(options) {
          return context.cloneScope.Promise.resolve().then(async () => {
            const scriptId = await context.childManager.callParentAsyncFunction(
              "contentScripts.register",
              [options]
            );

            const registeredScript = new ContentScriptChild(context, scriptId);

            return Cu.cloneInto(registeredScript.api(), context.cloneScope, {
              cloneFunctions: true,
            });
          });
        },
      },
    };
  }
};
PK
!<�!JJDchrome/toolkit/content/extensions/child/ext-declarativeNetRequest.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

ChromeUtils.defineESModuleGetters(this, {
  ExtensionDNRLimits: "resource://gre/modules/ExtensionDNRLimits.sys.mjs",
});

this.declarativeNetRequest = class extends ExtensionAPI {
  getAPI() {
    return {
      declarativeNetRequest: {
        get GUARANTEED_MINIMUM_STATIC_RULES() {
          return ExtensionDNRLimits.GUARANTEED_MINIMUM_STATIC_RULES;
        },
        get MAX_NUMBER_OF_STATIC_RULESETS() {
          return ExtensionDNRLimits.MAX_NUMBER_OF_STATIC_RULESETS;
        },
        get MAX_NUMBER_OF_ENABLED_STATIC_RULESETS() {
          return ExtensionDNRLimits.MAX_NUMBER_OF_ENABLED_STATIC_RULESETS;
        },
        get MAX_NUMBER_OF_DISABLED_STATIC_RULES() {
          return ExtensionDNRLimits.MAX_NUMBER_OF_DISABLED_STATIC_RULES;
        },
        get MAX_NUMBER_OF_DYNAMIC_RULES() {
          return ExtensionDNRLimits.MAX_NUMBER_OF_DYNAMIC_RULES;
        },
        get MAX_NUMBER_OF_SESSION_RULES() {
          return ExtensionDNRLimits.MAX_NUMBER_OF_SESSION_RULES;
        },
        get MAX_NUMBER_OF_REGEX_RULES() {
          return ExtensionDNRLimits.MAX_NUMBER_OF_REGEX_RULES;
        },
        // NOTE:this property is deprecated and it has been replaced by
        // MAX_NUMBER_OF_DYNAMIC_RULES/MAX_NUMBER_OF_SESSION_RULES, but kept
        // in the short term for backward compatibility.
        //
        // This getter returns the minimum value set on both the other two
        // new properties, in case MAX_NUMBER_OF_DYNAMIC_RULES or
        // MAX_NUMBER_OF_SESSION_RULES have been customized through prefs.
        get MAX_NUMBER_OF_DYNAMIC_AND_SESSION_RULES() {
          return Math.min(
            ExtensionDNRLimits.MAX_NUMBER_OF_DYNAMIC_RULES,
            ExtensionDNRLimits.MAX_NUMBER_OF_SESSION_RULES
          );
        },
      },
    };
  }
};
PK
!<y�6))8chrome/toolkit/content/extensions/child/ext-extension.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.extension = class extends ExtensionAPI {
  getAPI(context) {
    let api = {
      getURL(url) {
        return context.extension.getURL(url);
      },

      get lastError() {
        return context.lastError;
      },

      get inIncognitoContext() {
        return context.incognito;
      },
    };

    if (context.envType === "addon_child") {
      api.getViews = function (fetchProperties) {
        let result = Cu.cloneInto([], context.cloneScope);

        for (let view of context.extension.views) {
          if (!view.active) {
            continue;
          }
          if (!context.principal.subsumes(view.principal)) {
            continue;
          }

          if (fetchProperties !== null) {
            if (
              fetchProperties.type !== null &&
              view.viewType != fetchProperties.type
            ) {
              continue;
            }

            if (fetchProperties.windowId !== null) {
              let bc = view.contentWindow?.docShell?.browserChild;
              let windowId =
                view.viewType !== "background"
                  ? bc?.chromeOuterWindowID ?? -1
                  : -1;
              if (windowId !== fetchProperties.windowId) {
                continue;
              }
            }

            if (
              fetchProperties.tabId !== null &&
              view.tabId != fetchProperties.tabId
            ) {
              continue;
            }
          }

          // Do not include extension popups contexts while their document
          // is blocked on parsing during its preloading state
          // (See Bug 1748808).
          if (context.extension.hasContextBlockedParsingDocument(view)) {
            continue;
          }

          result.push(view.contentWindow);
        }

        return result;
      };
    }

    return { extension: api };
  }
};
PK
!<?�C�
�
7chrome/toolkit/content/extensions/child/ext-identity.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var { Constructor: CC } = Components;

ChromeUtils.defineESModuleGetters(this, {
  CommonUtils: "resource://services-common/utils.sys.mjs",
});
XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "redirectDomain",
  "extensions.webextensions.identity.redirectDomain"
);

let CryptoHash = CC(
  "@mozilla.org/security/hash;1",
  "nsICryptoHash",
  "initWithString"
);

XPCOMUtils.defineLazyGlobalGetters(this, ["URL", "TextEncoder"]);

const computeHash = str => {
  let byteArr = new TextEncoder().encode(str);
  let hash = new CryptoHash("sha1");
  hash.update(byteArr, byteArr.length);
  return CommonUtils.bytesAsHex(hash.finish(false));
};

this.identity = class extends ExtensionAPI {
  getAPI(context) {
    let { extension } = context;
    return {
      identity: {
        getRedirectURL: function (path = "") {
          let hash = computeHash(extension.id);
          let url = new URL(`https://${hash}.${redirectDomain}/`);
          url.pathname = path;
          return url.href;
        },
        launchWebAuthFlow: function (details) {
          // Validate the url and retreive redirect_uri if it was provided.
          let url, redirectURI;
          let baseRedirectURL = this.getRedirectURL();

          // Allow using loopback address for native OAuth flows as some
          //  providers do not accept the URL provided by getRedirectURL.
          // For more context, see bug 1635344.
          let loopbackURL = `http://127.0.0.1/mozoauth2/${computeHash(
            extension.id
          )}`;
          try {
            url = new URL(details.url);
          } catch (e) {
            return Promise.reject({ message: "details.url is invalid" });
          }
          try {
            redirectURI = new URL(
              url.searchParams.get("redirect_uri") || baseRedirectURL
            );
            if (
              !redirectURI.href.startsWith(baseRedirectURL) &&
              !redirectURI.href.startsWith(loopbackURL)
            ) {
              return Promise.reject({ message: "redirect_uri not allowed" });
            }
          } catch (e) {
            return Promise.reject({ message: "redirect_uri is invalid" });
          }

          return context.childManager.callParentAsyncFunction(
            "identity.launchWebAuthFlowInParent",
            [details, redirectURI.href]
          );
        },
      },
    };
  }
};
PK
!<�f���6chrome/toolkit/content/extensions/child/ext-runtime.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

ChromeUtils.defineESModuleGetters(this, {
  WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.sys.mjs",
});

/* eslint-disable jsdoc/check-param-names */
/**
 * With optional arguments on both ends, this case is ambiguous:
 *     runtime.sendMessage("string", {} or nullish)
 *
 * Sending a message within the extension is more common than sending
 * an empty object to another extension, so we prefer that conclusion.
 *
 * @param {string?}  [extensionId]
 * @param {any}      message
 * @param {object?}  [options]
 * @param {Function} [callback]
 * @returns {{extensionId: string|null, message: any, callback: Function|null}}
 */
/* eslint-enable jsdoc/check-param-names */
function parseBonkersArgs(...args) {
  let Error = ExtensionUtils.ExtensionError;
  let callback = typeof args[args.length - 1] === "function" && args.pop();

  // We don't support any options anymore, so only an empty object is valid.
  function validOptions(v) {
    return v == null || (typeof v === "object" && !Object.keys(v).length);
  }

  if (args.length === 1 || (args.length === 2 && validOptions(args[1]))) {
    // Interpret as passing null for extensionId (message within extension).
    args.unshift(null);
  }
  let [extensionId, message, options] = args;

  if (!args.length) {
    throw new Error("runtime.sendMessage's message argument is missing");
  } else if (!validOptions(options)) {
    throw new Error("runtime.sendMessage's options argument is invalid");
  } else if (args.length === 4 && args[3] && !callback) {
    throw new Error("runtime.sendMessage's last argument is not a function");
  } else if (args[3] != null || args.length > 4) {
    throw new Error("runtime.sendMessage received too many arguments");
  } else if (extensionId && typeof extensionId !== "string") {
    throw new Error("runtime.sendMessage's extensionId argument is invalid");
  }
  return { extensionId, message, callback };
}

this.runtime = class extends ExtensionAPI {
  getAPI(context) {
    let { extension } = context;

    return {
      runtime: {
        onConnect: context.messenger.onConnect.api(),
        onMessage: context.messenger.onMessage.api(),

        onConnectExternal: context.messenger.onConnectEx.api(),
        onMessageExternal: context.messenger.onMessageEx.api(),

        connect(extensionId, options) {
          let name = options?.name ?? "";
          return context.messenger.connect({ name, extensionId });
        },

        sendMessage(...args) {
          let arg = parseBonkersArgs(...args);
          return context.messenger.sendRuntimeMessage(arg);
        },

        connectNative(name) {
          return context.messenger.connect({ name, native: true });
        },

        sendNativeMessage(nativeApp, message) {
          return context.messenger.sendNativeMessage(nativeApp, message);
        },

        get lastError() {
          return context.lastError;
        },

        getManifest() {
          return Cu.cloneInto(extension.manifest, context.cloneScope);
        },

        id: extension.id,

        getURL(url) {
          return extension.getURL(url);
        },

        getFrameId(target) {
          let frameId = WebNavigationFrames.getFromWindow(target);
          if (frameId >= 0) {
            return frameId;
          }
          // Not a WindowProxy, perhaps an embedder element?

          let type;
          try {
            type = Cu.getClassName(target, true);
          } catch (e) {
            // Not a valid object, will throw below.
          }

          const embedderTypes = [
            "HTMLIFrameElement",
            "HTMLFrameElement",
            "HTMLEmbedElement",
            "HTMLObjectElement",
          ];

          if (embedderTypes.includes(type)) {
            if (!target.browsingContext) {
              return -1;
            }
            return WebNavigationFrames.getFrameId(target.browsingContext);
          }

          throw new ExtensionUtils.ExtensionError("Invalid argument");
        },
      },
    };
  }

  getAPIObjectForRequest(context, request) {
    if (request.apiObjectType === "Port") {
      const port = context.messenger.getPortById(request.apiObjectId);
      if (!port) {
        throw new Error(`Port API object not found: ${request}`);
      }
      return port.api;
    }

    throw new Error(`Unexpected apiObjectType: ${request}`);
  }
};
PK
!<$L8chrome/toolkit/content/extensions/child/ext-scripting.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var { ExtensionError } = ExtensionUtils;

this.scripting = class extends ExtensionAPI {
  getAPI(context) {
    return {
      scripting: {
        executeScript: async details => {
          let { func, args, ...parentDetails } = details;

          if (details.files) {
            if (details.args) {
              throw new ExtensionError(
                "'args' may not be used with file injections."
              );
            }
          }
          // `files` and `func` are mutually exclusive but that is checked in
          // the parent (in `execute()`).
          if (func) {
            try {
              const serializedArgs = args
                ? JSON.stringify(args).slice(1, -1)
                : "";
              // This is a prop that we compute here and pass to the parent.
              parentDetails.func = `(${func.toString()})(${serializedArgs});`;
            } catch (e) {
              throw new ExtensionError("Unserializable arguments.");
            }
          } else {
            parentDetails.func = null;
          }

          return context.childManager.callParentAsyncFunction(
            "scripting.executeScriptInternal",
            [parentDetails]
          );
        },
      },
    };
  }
};
PK
!<�8 Յ.�.6chrome/toolkit/content/extensions/child/ext-storage.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

ChromeUtils.defineESModuleGetters(this, {
  ExtensionStorage: "resource://gre/modules/ExtensionStorage.sys.mjs",
  ExtensionStorageIDB: "resource://gre/modules/ExtensionStorageIDB.sys.mjs",
  ExtensionTelemetry: "resource://gre/modules/ExtensionTelemetry.sys.mjs",
});

// Wrap a storage operation in a TelemetryStopWatch.
async function measureOp(telemetryMetric, extension, fn) {
  const stopwatchKey = {};
  telemetryMetric.stopwatchStart(extension, stopwatchKey);
  try {
    let result = await fn();
    telemetryMetric.stopwatchFinish(extension, stopwatchKey);
    return result;
  } catch (err) {
    telemetryMetric.stopwatchCancel(extension, stopwatchKey);
    throw err;
  }
}

this.storage = class extends ExtensionAPI {
  getLocalFileBackend(context, { deserialize, serialize }) {
    return {
      get(keys) {
        return context.childManager
          .callParentAsyncFunction("storage.local.JSONFileBackend.get", [
            serialize(keys),
          ])
          .then(deserialize);
      },
      set(items) {
        return context.childManager.callParentAsyncFunction(
          "storage.local.JSONFileBackend.set",
          [serialize(items)]
        );
      },
      remove(keys) {
        return context.childManager.callParentAsyncFunction(
          "storage.local.JSONFileBackend.remove",
          [serialize(keys)]
        );
      },
      clear() {
        return context.childManager.callParentAsyncFunction(
          "storage.local.JSONFileBackend.clear",
          []
        );
      },
    };
  }

  getLocalIDBBackend(context, { fireOnChanged, storagePrincipal }) {
    let dbPromise;
    async function getDB() {
      if (dbPromise) {
        return dbPromise;
      }

      const persisted = context.extension.hasPermission("unlimitedStorage");
      dbPromise = ExtensionStorageIDB.open(storagePrincipal, persisted).catch(
        err => {
          // Reset the cached promise if it has been rejected, so that the next
          // API call is going to retry to open the DB.
          dbPromise = null;
          throw err;
        }
      );

      return dbPromise;
    }

    return {
      get(keys) {
        return measureOp(
          ExtensionTelemetry.storageLocalGetIdb,
          context.extension,
          async () => {
            const db = await getDB();
            return db.get(keys);
          }
        );
      },
      set(items) {
        function serialize(name, anonymizedName, value) {
          return ExtensionStorage.serialize(
            `set/${context.extension.id}/${name}`,
            `set/${context.extension.id}/${anonymizedName}`,
            value
          );
        }

        return measureOp(
          ExtensionTelemetry.storageLocalSetIdb,
          context.extension,
          async () => {
            const db = await getDB();
            const changes = await db.set(items, {
              serialize,
            });

            if (changes) {
              fireOnChanged(changes);
            }
          }
        );
      },
      async remove(keys) {
        const db = await getDB();
        const changes = await db.remove(keys);

        if (changes) {
          fireOnChanged(changes);
        }
      },
      async clear() {
        const db = await getDB();
        const changes = await db.clear(context.extension);

        if (changes) {
          fireOnChanged(changes);
        }
      },
    };
  }

  getAPI(context) {
    const { extension } = context;
    const serialize = ExtensionStorage.serializeForContext.bind(null, context);
    const deserialize = ExtensionStorage.deserializeForContext.bind(
      null,
      context
    );

    // onChangedName is "storage.onChanged", "storage.sync.onChanged", etc.
    function makeOnChangedEventTarget(onChangedName) {
      return new EventManager({
        context,
        name: onChangedName,
        // Parent event already resets idle if needed, no need to do it here.
        resetIdleOnEvent: false,
        register: fire => {
          let onChanged = (data, area) => {
            let changes = new context.cloneScope.Object();
            for (let [key, value] of Object.entries(data)) {
              changes[key] = deserialize(value);
            }
            if (area) {
              // storage.onChanged includes the area.
              fire.raw(changes, area);
            } else {
              // StorageArea.onChanged doesn't include the area.
              fire.raw(changes);
            }
          };

          let parent = context.childManager.getParentEvent(onChangedName);
          parent.addListener(onChanged);
          return () => {
            parent.removeListener(onChanged);
          };
        },
      }).api();
    }

    function sanitize(items) {
      // The schema validator already takes care of arrays (which are only allowed
      // to contain strings). Strings and null are safe values.
      if (typeof items != "object" || items === null || Array.isArray(items)) {
        return items;
      }
      // If we got here, then `items` is an object generated by `ObjectType`'s
      // `normalize` method from Schemas.sys.mjs. The object returned by `normalize`
      // lives in this compartment, while the values live in compartment of
      // `context.contentWindow`. The `sanitize` method runs with the principal
      // of `context`, so we cannot just use `ExtensionStorage.sanitize` because
      // it is not allowed to access properties of `items`.
      // So we enumerate all properties and sanitize each value individually.
      let sanitized = {};
      for (let [key, value] of Object.entries(items)) {
        sanitized[key] = ExtensionStorage.sanitize(value, context);
      }
      return sanitized;
    }

    function fireOnChanged(changes) {
      // This call is used (by the storage.local API methods for the IndexedDB backend) to fire a storage.onChanged event,
      // it uses the underlying message manager since the child context (or its ProxyContentParent counterpart
      // running in the main process) may be gone by the time we call this, and so we can't use the childManager
      // abstractions (e.g. callParentAsyncFunction or callParentFunctionNoReturn).
      Services.cpmm.sendAsyncMessage(
        `Extension:StorageLocalOnChanged:${extension.uuid}`,
        changes
      );
    }

    // If the selected backend for the extension is not known yet, we have to lazily detect it
    // by asking to the main process (as soon as the storage.local API has been accessed for
    // the first time).
    const getStorageLocalBackend = async () => {
      const { backendEnabled, storagePrincipal } =
        await ExtensionStorageIDB.selectBackend(context);

      if (!backendEnabled) {
        return this.getLocalFileBackend(context, { deserialize, serialize });
      }

      return this.getLocalIDBBackend(context, {
        storagePrincipal,
        fireOnChanged,
        serialize,
      });
    };

    // Synchronously select the backend if it is already known.
    let selectedBackend;

    const useStorageIDBBackend = extension.getSharedData("storageIDBBackend");
    if (useStorageIDBBackend === false) {
      selectedBackend = this.getLocalFileBackend(context, {
        deserialize,
        serialize,
      });
    } else if (useStorageIDBBackend === true) {
      selectedBackend = this.getLocalIDBBackend(context, {
        storagePrincipal: extension.getSharedData("storageIDBPrincipal"),
        fireOnChanged,
        serialize,
      });
    }

    let promiseStorageLocalBackend;

    // Generate the backend-agnostic local API wrapped methods.
    const local = {
      onChanged: makeOnChangedEventTarget("storage.local.onChanged"),
    };
    for (let method of ["get", "set", "remove", "clear"]) {
      local[method] = async function (...args) {
        try {
          // Discover the selected backend if it is not known yet.
          if (!selectedBackend) {
            if (!promiseStorageLocalBackend) {
              promiseStorageLocalBackend = getStorageLocalBackend().catch(
                err => {
                  // Clear the cached promise if it has been rejected.
                  promiseStorageLocalBackend = null;
                  throw err;
                }
              );
            }

            // If the storage.local method is not 'get' (which doesn't change any of the stored data),
            // fall back to call the method in the parent process, so that it can be completed even
            // if this context has been destroyed in the meantime.
            if (method !== "get") {
              // Let the outer try to catch rejections returned by the backend methods.
              try {
                const result =
                  await context.childManager.callParentAsyncFunction(
                    "storage.local.callMethodInParentProcess",
                    [method, args]
                  );
                return result;
              } catch (err) {
                // Just return the rejection as is, the error has been normalized in the
                // parent process by callMethodInParentProcess and the original error
                // logged in the browser console.
                return Promise.reject(err);
              }
            }

            // Get the selected backend and cache it for the next API calls from this context.
            selectedBackend = await promiseStorageLocalBackend;
          }

          // Let the outer try to catch rejections returned by the backend methods.
          const result = await selectedBackend[method](...args);
          return result;
        } catch (err) {
          throw ExtensionStorageIDB.normalizeStorageError({
            error: err,
            extensionId: extension.id,
            storageMethod: method,
          });
        }
      };
    }

    return {
      storage: {
        local,

        session: {
          async get(keys) {
            return deserialize(
              await context.childManager.callParentAsyncFunction(
                "storage.session.get",
                [serialize(keys)]
              )
            );
          },
          set(items) {
            return context.childManager.callParentAsyncFunction(
              "storage.session.set",
              [serialize(items)]
            );
          },
          onChanged: makeOnChangedEventTarget("storage.session.onChanged"),
        },

        sync: {
          get(keys) {
            keys = sanitize(keys);
            return context.childManager.callParentAsyncFunction(
              "storage.sync.get",
              [keys]
            );
          },
          set(items) {
            items = sanitize(items);
            return context.childManager.callParentAsyncFunction(
              "storage.sync.set",
              [items]
            );
          },
          onChanged: makeOnChangedEventTarget("storage.sync.onChanged"),
        },

        managed: {
          get(keys) {
            return context.childManager
              .callParentAsyncFunction("storage.managed.get", [serialize(keys)])
              .then(deserialize);
          },
          set() {
            return Promise.reject({ message: "storage.managed is read-only" });
          },
          remove() {
            return Promise.reject({ message: "storage.managed is read-only" });
          },
          clear() {
            return Promise.reject({ message: "storage.managed is read-only" });
          },

          onChanged: makeOnChangedEventTarget("storage.managed.onChanged"),
        },

        onChanged: makeOnChangedEventTarget("storage.onChanged"),
      },
    };
  }
};
PK
!<a�ν�+�+3chrome/toolkit/content/extensions/child/ext-test.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

ChromeUtils.defineLazyGetter(this, "isXpcshell", function () {
  return Services.env.exists("XPCSHELL_TEST_PROFILE_DIR");
});

/**
 * Checks whether the given error matches the given expectations.
 *
 * @param {*} error
 *        The error to check.
 * @param {string | RegExp | Function | null} expectedError
 *        The expectation to check against. If this parameter is:
 *
 *        - a string, the error message must exactly equal the string.
 *        - a regular expression, it must match the error message.
 *        - a function, it is called with the error object and its
 *          return value is returned.
 * @param {BaseContext} context
 *
 * @returns {boolean}
 *        True if the error matches the expected error.
 */
const errorMatches = (error, expectedError, context) => {
  if (
    typeof error === "object" &&
    error !== null &&
    !context.principal.subsumes(Cu.getObjectPrincipal(error))
  ) {
    Cu.reportError("Error object belongs to the wrong scope.");
    return false;
  }

  if (typeof expectedError === "function") {
    return context.runSafeWithoutClone(expectedError, error);
  }

  if (
    typeof error !== "object" ||
    error == null ||
    typeof error.message !== "string"
  ) {
    return false;
  }

  if (typeof expectedError === "string") {
    return error.message === expectedError;
  }

  try {
    return expectedError.test(error.message);
  } catch (e) {
    Cu.reportError(e);
  }

  return false;
};

// Checks whether |v| should use string serialization instead of JSON.
function useStringInsteadOfJSON(v) {
  return (
    // undefined to string, or else it is omitted from object after stringify.
    v === undefined ||
    // Values that would have become null.
    (typeof v === "number" && (isNaN(v) || !isFinite(v)))
  );
}

// A very strict deep equality comparator that throws for unsupported values.
// For context, see https://bugzilla.mozilla.org/show_bug.cgi?id=1782816#c2
function deepEquals(a, b) {
  // Some values don't have a JSON representation. To disambiguate from null or
  // regular strings, we prepend this prefix instead.
  const NON_JSON_PREFIX = "#NOT_JSON_SERIALIZABLE#";

  function replacer(key, value) {
    if (typeof value == "object" && value !== null && !Array.isArray(value)) {
      const cls = ChromeUtils.getClassName(value);
      if (cls === "Object") {
        // Return plain object with keys sorted in a predictable order.
        return Object.fromEntries(
          Object.keys(value)
            .sort()
            .map(k => [k, value[k]])
        );
      }
      // Just throw to avoid potentially inaccurate serializations (e.g. {}).
      throw new ExtensionUtils.ExtensionError(`Unsupported obj type: ${cls}`);
    }

    if (useStringInsteadOfJSON(value)) {
      return `${NON_JSON_PREFIX}${value}`;
    }
    return value;
  }
  return JSON.stringify(a, replacer) === JSON.stringify(b, replacer);
}

/**
 * Serializes the given value for use in informative assertion messages.
 *
 * @param {*} value
 * @returns {string}
 */
const toSource = value => {
  function cannotJSONserialize(v) {
    return (
      useStringInsteadOfJSON(v) ||
      // Not a plain object. E.g. [object X], /regexp/, etc.
      (typeof v == "object" &&
        v !== null &&
        !Array.isArray(v) &&
        ChromeUtils.getClassName(v) !== "Object")
    );
  }
  try {
    if (cannotJSONserialize(value)) {
      return String(value);
    }

    const replacer = (k, v) => (cannotJSONserialize(v) ? String(v) : v);
    return JSON.stringify(value, replacer);
  } catch (e) {
    return "<unknown>";
  }
};

this.test = class extends ExtensionAPI {
  getAPI(context) {
    const { extension } = context;

    function getStack(savedFrame = null) {
      if (savedFrame) {
        return ChromeUtils.createError("", savedFrame).stack.replace(
          /^/gm,
          "    "
        );
      }
      return new context.Error().stack.replace(/^/gm, "    ");
    }

    function assertTrue(value, msg) {
      extension.emit(
        "test-result",
        Boolean(value),
        String(msg),
        getStack(context.getCaller())
      );
    }

    class TestEventManager extends EventManager {
      constructor(...args) {
        super(...args);

        // A map to keep track of the listeners wrappers being added in
        // addListener (the wrapper will be needed to be able to remove
        // the listener from this EventManager instance if the extension
        // does call test.onMessage.removeListener).
        this._listenerWrappers = new Map();
        context.callOnClose({
          close: () => this._listenerWrappers.clear(),
        });
      }

      addListener(callback, ...args) {
        const listenerWrapper = function (...args) {
          try {
            callback.call(this, ...args);
          } catch (e) {
            assertTrue(false, `${e}\n${e.stack}`);
          }
        };
        super.addListener(listenerWrapper, ...args);
        this._listenerWrappers.set(callback, listenerWrapper);
      }

      removeListener(callback) {
        if (!this._listenerWrappers.has(callback)) {
          return;
        }

        super.removeListener(this._listenerWrappers.get(callback));
        this._listenerWrappers.delete(callback);
      }
    }

    if (!Cu.isInAutomation && !isXpcshell) {
      return { test: {} };
    }

    return {
      test: {
        withHandlingUserInput(callback) {
          // TODO(Bug 1598804): remove this once we don't expose anymore the
          // entire test API namespace based on an environment variable.
          if (!Cu.isInAutomation) {
            // This dangerous method should only be available if the
            // automation pref is set, which is the case in browser tests.
            throw new ExtensionUtils.ExtensionError(
              "withHandlingUserInput can only be called in automation"
            );
          }
          ExtensionCommon.withHandlingUserInput(
            context.contentWindow,
            callback
          );
        },

        sendMessage(...args) {
          extension.emit("test-message", ...args);
        },

        notifyPass(msg) {
          extension.emit("test-done", true, msg, getStack(context.getCaller()));
        },

        notifyFail(msg) {
          extension.emit(
            "test-done",
            false,
            msg,
            getStack(context.getCaller())
          );
        },

        log(msg) {
          extension.emit("test-log", true, msg, getStack(context.getCaller()));
        },

        fail(msg) {
          assertTrue(false, msg);
        },

        succeed(msg) {
          assertTrue(true, msg);
        },

        assertTrue(value, msg) {
          assertTrue(value, msg);
        },

        assertFalse(value, msg) {
          assertTrue(!value, msg);
        },

        assertDeepEq(expected, actual, msg) {
          // The bindings generated by Schemas.sys.mjs accepts any input, but the
          // WebIDL-generated binding expects a structurally cloneable input.
          // To ensure consistent behavior regardless of which mechanism was
          // used, verify that the inputs are structurally cloneable.
          // These will throw if the values cannot be cloned.
          function ensureStructurallyCloneable(v) {
            if (typeof v == "object" && v !== null) {
              // Waive xrays to unhide callable members, so that cloneInto will
              // throw if needed.
              v = ChromeUtils.waiveXrays(v);
            }
            new StructuredCloneHolder("test.assertEq", null, v, globalThis);
          }
          // When WebIDL bindings are used, the objects are already cloned
          // structurally, so we don't need to check again.
          if (!context.useWebIDLBindings) {
            ensureStructurallyCloneable(expected);
            ensureStructurallyCloneable(actual);
          }

          extension.emit(
            "test-eq",
            deepEquals(actual, expected),
            String(msg),
            toSource(expected),
            toSource(actual),
            getStack(context.getCaller())
          );
        },

        assertEq(expected, actual, msg) {
          let equal = expected === actual;

          expected = String(expected);
          actual = String(actual);

          if (!equal && expected === actual) {
            actual += " (different)";
          }
          extension.emit(
            "test-eq",
            equal,
            String(msg),
            expected,
            actual,
            getStack(context.getCaller())
          );
        },

        assertRejects(promise, expectedError, msg) {
          // Wrap in a native promise for consistency.
          promise = Promise.resolve(promise);

          return promise.then(
            () => {
              let message = `Promise resolved, expected rejection '${toSource(
                expectedError
              )}'`;
              if (msg) {
                message += `: ${msg}`;
              }
              assertTrue(false, message);
            },
            error => {
              let expected = toSource(expectedError);
              let message = `got '${toSource(error)}'`;
              if (msg) {
                message += `: ${msg}`;
              }

              assertTrue(
                errorMatches(error, expectedError, context),
                `Promise rejected, expecting rejection to match '${expected}', ${message}`
              );
            }
          );
        },

        assertThrows(func, expectedError, msg) {
          try {
            func();

            let message = `Function did not throw, expected error '${toSource(
              expectedError
            )}'`;
            if (msg) {
              message += `: ${msg}`;
            }
            assertTrue(false, message);
          } catch (error) {
            let expected = toSource(expectedError);
            let message = `got '${toSource(error)}'`;
            if (msg) {
              message += `: ${msg}`;
            }

            assertTrue(
              errorMatches(error, expectedError, context),
              `Function threw, expecting error to match '${expected}', ${message}`
            );
          }
        },

        onMessage: new TestEventManager({
          context,
          name: "test.onMessage",
          // TODO bug 1901294: Set resetIdleOnEvent=false. Tests should not be
          // relying on test.onMessage for its side effect of resetting the test
          // but set extensions.background.idle.timeout instead.
          resetIdleOnEvent: true,
          register: fire => {
            let handler = (event, ...args) => {
              fire.async(...args);
            };

            extension.on("test-harness-message", handler);
            return () => {
              extension.off("test-harness-message", handler);
            };
          },
        }).api(),
      },
    };
  }
};
PK
!<��f

6chrome/toolkit/content/extensions/child/ext-toolkit.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

global.EventManager = ExtensionCommon.EventManager;

extensions.registerModules({
  backgroundPage: {
    url: "chrome://extensions/content/child/ext-backgroundPage.js",
    scopes: ["addon_child"],
    manifest: ["background"],
    paths: [
      ["extension", "getBackgroundPage"],
      ["runtime", "getBackgroundPage"],
    ],
  },
  contentScripts: {
    url: "chrome://extensions/content/child/ext-contentScripts.js",
    scopes: ["addon_child"],
    paths: [["contentScripts"]],
  },
  declarativeNetRequest: {
    url: "chrome://extensions/content/child/ext-declarativeNetRequest.js",
    scopes: ["addon_child"],
    paths: [["declarativeNetRequest"]],
  },
  extension: {
    url: "chrome://extensions/content/child/ext-extension.js",
    scopes: ["addon_child", "content_child", "devtools_child"],
    paths: [["extension"]],
  },
  i18n: {
    url: "chrome://extensions/content/parent/ext-i18n.js",
    scopes: ["addon_child", "content_child", "devtools_child"],
    paths: [["i18n"]],
  },
  runtime: {
    url: "chrome://extensions/content/child/ext-runtime.js",
    scopes: ["addon_child", "content_child", "devtools_child"],
    paths: [["runtime"]],
  },
  scripting: {
    url: "chrome://extensions/content/child/ext-scripting.js",
    scopes: ["addon_child"],
    paths: [["scripting"]],
  },
  storage: {
    url: "chrome://extensions/content/child/ext-storage.js",
    scopes: ["addon_child", "content_child", "devtools_child"],
    paths: [["storage"]],
  },
  test: {
    url: "chrome://extensions/content/child/ext-test.js",
    scopes: ["addon_child", "content_child", "devtools_child"],
    paths: [["test"]],
  },
  userScripts: {
    url: "chrome://extensions/content/child/ext-userScripts.js",
    scopes: ["addon_child"],
    paths: [["userScripts"]],
  },
  userScriptsContent: {
    url: "chrome://extensions/content/child/ext-userScripts-content.js",
    scopes: ["content_child"],
    paths: [["userScripts", "onBeforeScript"]],
  },
  webRequest: {
    url: "chrome://extensions/content/child/ext-webRequest.js",
    scopes: ["addon_child"],
    paths: [["webRequest"]],
  },
});

if (AppConstants.MOZ_BUILD_APP === "browser") {
  extensions.registerModules({
    identity: {
      url: "chrome://extensions/content/child/ext-identity.js",
      scopes: ["addon_child"],
      paths: [["identity"]],
    },
  });
}
PK
!<i��O3O3Bchrome/toolkit/content/extensions/child/ext-userScripts-content.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var USERSCRIPT_PREFNAME = "extensions.webextensions.userScripts.enabled";
var USERSCRIPT_DISABLED_ERRORMSG = `userScripts APIs are currently experimental and must be enabled with the ${USERSCRIPT_PREFNAME} preference.`;

ChromeUtils.defineESModuleGetters(this, {
  Schemas: "resource://gre/modules/Schemas.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "userScriptsEnabled",
  USERSCRIPT_PREFNAME,
  false
);

var { ExtensionError } = ExtensionUtils;

const TYPEOF_PRIMITIVES = ["bigint", "boolean", "number", "string", "symbol"];

/**
 * Represents a user script in the child content process.
 *
 * This class implements the API object that is passed as a parameter to the
 * browser.userScripts.onBeforeScript API Event.
 *
 * @param {object} params
 * @param {ContentScriptContextChild} params.context
 *        The context which has registered the userScripts.onBeforeScript listener.
 * @param {PlainJSONValue}            params.metadata
 *        An opaque user script metadata value (as set in userScripts.register).
 * @param {Sandbox}                   params.scriptSandbox
 *        The Sandbox object of the userScript.
 */
class UserScript {
  constructor({ context, metadata, scriptSandbox }) {
    this.context = context;
    this.extension = context.extension;
    this.apiSandbox = context.cloneScope;
    this.metadata = metadata;
    this.scriptSandbox = scriptSandbox;

    this.ScriptError = scriptSandbox.Error;
    this.ScriptPromise = scriptSandbox.Promise;
  }

  /**
   * Returns the API object provided to the userScripts.onBeforeScript listeners.
   *
   * @returns {object}
   *          The API object with the properties and methods to export
   *          to the extension code.
   */
  api() {
    return {
      metadata: this.metadata,
      defineGlobals: sourceObject => this.defineGlobals(sourceObject),
      export: value => this.export(value),
    };
  }

  /**
   * Define all the properties of a given plain object as lazy getters of the
   * userScript global object.
   *
   * @param {object} sourceObject
   *        A set of objects and methods to export into the userScript scope as globals.
   *
   * @throws {context.Error}
   *         Throws an apiScript error when sourceObject is not a plain object.
   */
  defineGlobals(sourceObject) {
    let className;
    try {
      className = ChromeUtils.getClassName(sourceObject, true);
    } catch (e) {
      // sourceObject is not an object;
    }

    if (className !== "Object") {
      throw new this.context.Error(
        "Invalid sourceObject type, plain object expected."
      );
    }

    this.exportLazyGetters(sourceObject, this.scriptSandbox);
  }

  /**
   * Convert a given value to make it accessible to the userScript code.
   *
   * - any property value that is already accessible to the userScript code is returned unmodified by
   *   the lazy getter
   * - any apiScript's Function is wrapped using the `wrapFunction` method
   * - any apiScript's Object is lazily exported (and the same wrappers are lazily applied to its
   *   properties).
   *
   * @param {any} valueToExport
   *        A value to convert into an object accessible to the userScript.
   *
   * @param {object} privateOptions
   *        A set of options used when this method is called internally (not exposed in the
   *        api object exported to the onBeforeScript listeners).
   * @param {Error}  privateOptions.Error
   *        The Error constructor to use to report errors (defaults to the apiScript context's Error
   *        when missing).
   * @param {Error}  privateOptions.errorMessage
   *        A custom error message to report exporting error on values not allowed.
   *
   * @returns {any}
   *        The resulting userScript object.
   *
   * @throws {context.Error | privateOptions.Error}
   *         Throws an error when the value is not allowed and it can't be exported into an allowed one.
   */
  export(valueToExport, privateOptions = {}) {
    const ExportError = privateOptions.Error || this.context.Error;

    if (this.canAccess(valueToExport, this.scriptSandbox)) {
      // Return the value unmodified if the userScript principal is already allowed
      // to access it.
      return valueToExport;
    }

    let className;

    try {
      className = ChromeUtils.getClassName(valueToExport, true);
    } catch (e) {
      // sourceObject is not an object;
    }

    if (className === "Function") {
      return this.wrapFunction(valueToExport);
    }

    if (className === "Object") {
      return this.exportLazyGetters(valueToExport);
    }

    if (className === "Array") {
      return this.exportArray(valueToExport);
    }

    let valueType = className || typeof valueToExport;
    throw new ExportError(
      privateOptions.errorMessage ||
        `${valueType} cannot be exported to the userScript`
    );
  }

  /**
   * Export all the elements of the `srcArray` into a newly created userScript array.
   *
   * @param {Array} srcArray
   *        The apiScript array to export to the userScript code.
   *
   * @returns {Array}
   *          The resulting userScript array.
   *
   * @throws {UserScriptError}
   *         Throws an error when the array can't be exported successfully.
   */
  exportArray(srcArray) {
    const destArray = Cu.cloneInto([], this.scriptSandbox);

    for (let [idx, value] of this.shallowCloneEntries(srcArray)) {
      destArray[idx] = this.export(value, {
        errorMessage: `Error accessing disallowed element at index "${idx}"`,
        Error: this.UserScriptError,
      });
    }

    return destArray;
  }

  /**
   * Export all the properties of the `src` plain object as lazy getters on the `dest` object,
   * or in a newly created userScript object if `dest` is `undefined`.
   *
   * @param {object} src
   *        A set of properties to define on a `dest` object as lazy getters.
   * @param {object} [dest]
   *        An optional `dest` object (a new userScript object is created by default when not specified).
   *
   * @returns {object}
   *          The resulting userScript object.
   */
  exportLazyGetters(src, dest = undefined) {
    dest = dest || Cu.createObjectIn(this.scriptSandbox);

    for (let [key, value] of this.shallowCloneEntries(src)) {
      Schemas.exportLazyGetter(dest, key, () => {
        return this.export(value, {
          // Lazy properties will raise an error for properties with not allowed
          // values to the userScript scope, and so we have to raise an userScript
          // Error here.
          Error: this.ScriptError,
          errorMessage: `Error accessing disallowed property "${key}"`,
        });
      });
    }

    return dest;
  }

  /**
   * Export and wrap an apiScript function to provide the following behaviors:
   *   - errors throws from an exported function are checked by `handleAPIScriptError`
   *   - returned apiScript's Promises (not accessible to the userScript) are converted into a
   *     userScript's Promise
   *   - check if the returned or resolved value is accessible to the userScript code
   *     (and raise a userScript error if it is not)
   *
   * @param {Function} fn
   *        The apiScript function to wrap
   *
   * @returns {object}
   *          The resulting userScript function.
   */
  wrapFunction(fn) {
    return Cu.exportFunction((...args) => {
      let res;
      try {
        // Checks that all the elements in the `...args` array are allowed to be
        // received from the apiScript.
        for (let arg of args) {
          if (!this.canAccess(arg, this.apiSandbox)) {
            throw new this.ScriptError(
              `Parameter not accessible to the userScript API`
            );
          }
        }

        res = fn(...args);
      } catch (err) {
        this.handleAPIScriptError(err);
      }

      // Prevent execution of proxy traps while checking if the return value is a Promise.
      if (!Cu.isProxy(res) && res instanceof this.context.Promise) {
        return this.ScriptPromise.resolve().then(async () => {
          let value;

          try {
            value = await res;
          } catch (err) {
            this.handleAPIScriptError(err);
          }

          return this.ensureAccessible(value);
        });
      }

      return this.ensureAccessible(res);
    }, this.scriptSandbox);
  }

  /**
   * Shallow clone the source object and iterate over its Object properties (or Array elements),
   * which allow us to safely iterate over all its properties (including callable objects that
   * would be hidden by the xrays vision, but excluding any property that could be tricky, e.g.
   * getters).
   *
   * @param {object | Array} obj
   *        The Object or Array object to shallow clone and iterate over.
   */
  *shallowCloneEntries(obj) {
    const clonedObj = ChromeUtils.shallowClone(obj);

    for (let entry of Object.entries(clonedObj)) {
      yield entry;
    }
  }

  /**
   * Check if the given value is accessible to the targetScope.
   *
   * @param {any}     val
   *        The value to check.
   * @param {Sandbox} targetScope
   *        The targetScope that should be able to access the value.
   *
   * @returns {boolean}
   */
  canAccess(val, targetScope) {
    if (val == null || TYPEOF_PRIMITIVES.includes(typeof val)) {
      return true;
    }

    // Disallow objects that are coming from principals that are not
    // subsumed by the targetScope's principal.
    try {
      const targetPrincipal = Cu.getObjectPrincipal(targetScope);
      if (!targetPrincipal.subsumes(Cu.getObjectPrincipal(val))) {
        return false;
      }
    } catch (err) {
      Cu.reportError(err);
      return false;
    }

    return true;
  }

  /**
   * Check if the value returned (or resolved) from an apiScript method is accessible
   * to the userScript code, and throw a userScript Error if it is not allowed.
   *
   * @param {any} res
   *        The value to return/resolve.
   *
   * @returns {any}
   *          The exported value.
   *
   * @throws {Error}
   *         Throws a userScript error when the value is not accessible to the userScript scope.
   */
  ensureAccessible(res) {
    if (this.canAccess(res, this.scriptSandbox)) {
      return res;
    }

    throw new this.ScriptError("Return value not accessible to the userScript");
  }

  /**
   * Handle the error raised (and rejected promise returned) from apiScript functions exported to the
   * userScript.
   *
   * @param {any} err
   *        The value to return/resolve.
   *
   * @throws {any}
   *         This method is expected to throw:
   *         - any value that is already accessible to the userScript code is forwarded unmodified
   *         - any value that is not accessible to the userScript code is logged in the console
   *           (to make it easier to investigate the underlying issue) and converted into a
   *           userScript Error (with the generic "An unexpected apiScript error occurred" error
   *           message accessible to the userScript)
   */
  handleAPIScriptError(err) {
    if (this.canAccess(err, this.scriptSandbox)) {
      throw err;
    }

    // Log the actual error on the console and raise a generic userScript Error
    // on error objects that can't be accessed by the UserScript principal.
    try {
      const debugName = this.extension.policy.debugName;
      Cu.reportError(
        `An unexpected apiScript error occurred for '${debugName}': ${err} :: ${err.stack}`
      );
    } catch (e) {}

    throw new this.ScriptError(`An unexpected apiScript error occurred`);
  }
}

this.userScriptsContent = class extends ExtensionAPI {
  getAPI(context) {
    return {
      userScripts: {
        onBeforeScript: new EventManager({
          context,
          name: "userScripts.onBeforeScript",
          register: fire => {
            if (!userScriptsEnabled) {
              throw new ExtensionError(USERSCRIPT_DISABLED_ERRORMSG);
            }

            let handler = (event, metadata, scriptSandbox) => {
              const us = new UserScript({
                context,
                metadata,
                scriptSandbox,
              });

              const apiObj = Cu.cloneInto(us.api(), context.cloneScope, {
                cloneFunctions: true,
              });

              Object.defineProperty(apiObj, "global", {
                value: scriptSandbox,
                enumerable: true,
                configurable: true,
                writable: true,
              });

              fire.raw(apiObj);
            };

            context.userScriptsEvents.on("on-before-script", handler);
            return () => {
              context.userScriptsEvents.off("on-before-script", handler);
            };
          },
        }).api(),
      },
    };
  }
};
PK
!<
4H��:chrome/toolkit/content/extensions/child/ext-userScripts.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var USERSCRIPT_PREFNAME = "extensions.webextensions.userScripts.enabled";
var USERSCRIPT_DISABLED_ERRORMSG = `userScripts APIs are currently experimental and must be enabled with the ${USERSCRIPT_PREFNAME} preference.`;

XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "userScriptsEnabled",
  USERSCRIPT_PREFNAME,
  false
);

// eslint-disable-next-line mozilla/reject-importGlobalProperties
Cu.importGlobalProperties(["crypto", "TextEncoder"]);

var { DefaultMap, ExtensionError, getUniqueId } = ExtensionUtils;

/**
 * Represents a registered userScript in the child extension process.
 *
 * @param {ExtensionPageContextChild} context
 *        The extension context which has registered the user script.
 * @param {string} scriptId
 *        An unique id that represents the registered user script
 *        (generated and used internally to identify it across the different processes).
 */
class UserScriptChild {
  constructor({ context, scriptId, onScriptUnregister }) {
    this.context = context;
    this.scriptId = scriptId;
    this.onScriptUnregister = onScriptUnregister;
    this.unregistered = false;
  }

  async unregister() {
    if (this.unregistered) {
      throw new ExtensionError("User script already unregistered");
    }

    this.unregistered = true;

    await this.context.childManager.callParentAsyncFunction(
      "userScripts.unregister",
      [this.scriptId]
    );

    this.context = null;

    this.onScriptUnregister();
  }

  api() {
    const { context } = this;

    // Returns the RegisteredUserScript API object.
    return {
      unregister: () => {
        return context.wrapPromise(this.unregister());
      },
    };
  }
}

this.userScripts = class extends ExtensionAPI {
  getAPI(context) {
    // Cache of the script code already converted into blob urls:
    //   Map<textHash, blobURLs>
    const blobURLsByHash = new Map();

    // Keep track of the userScript that are sharing the same blob urls,
    // so that we can revoke any blob url that is not used by a registered
    // userScripts:
    //   Map<blobURL, Set<scriptId>>
    const userScriptsByBlobURL = new DefaultMap(() => new Set());

    function revokeBlobURLs(scriptId, options) {
      let revokedUrls = new Set();

      for (let url of options.js) {
        if (userScriptsByBlobURL.has(url)) {
          let scriptIds = userScriptsByBlobURL.get(url);
          scriptIds.delete(scriptId);

          if (scriptIds.size === 0) {
            revokedUrls.add(url);
            userScriptsByBlobURL.delete(url);
            context.cloneScope.URL.revokeObjectURL(url);
          }
        }
      }

      // Remove all the removed urls from the map of known computed hashes.
      for (let [hash, url] of blobURLsByHash) {
        if (revokedUrls.has(url)) {
          blobURLsByHash.delete(hash);
        }
      }
    }

    // Convert a script code string into a blob URL (and use a cached one
    // if the script hash is already associated to a blob URL).
    const getBlobURL = async (text, scriptId) => {
      // Compute the hash of the js code string and reuse the blob url if we already have
      // for the same hash.
      const buffer = await crypto.subtle.digest(
        "SHA-1",
        new TextEncoder().encode(text)
      );
      const hash = String.fromCharCode(...new Uint16Array(buffer));

      let blobURL = blobURLsByHash.get(hash);

      if (blobURL) {
        userScriptsByBlobURL.get(blobURL).add(scriptId);
        return blobURL;
      }

      const blob = new context.cloneScope.Blob([text], {
        type: "text/javascript",
      });
      blobURL = context.cloneScope.URL.createObjectURL(blob);

      // Start to track this blob URL.
      userScriptsByBlobURL.get(blobURL).add(scriptId);

      blobURLsByHash.set(hash, blobURL);

      return blobURL;
    };

    function convertToAPIObject(scriptId, options) {
      const registeredScript = new UserScriptChild({
        context,
        scriptId,
        onScriptUnregister: () => revokeBlobURLs(scriptId, options),
      });

      const scriptAPI = Cu.cloneInto(
        registeredScript.api(),
        context.cloneScope,
        { cloneFunctions: true }
      );
      return scriptAPI;
    }

    // Revoke all the created blob urls once the context is destroyed.
    context.callOnClose({
      close() {
        if (!context.cloneScope) {
          return;
        }

        for (let blobURL of blobURLsByHash.values()) {
          context.cloneScope.URL.revokeObjectURL(blobURL);
        }
      },
    });

    return {
      userScripts: {
        register(options) {
          if (!userScriptsEnabled) {
            throw new ExtensionError(USERSCRIPT_DISABLED_ERRORMSG);
          }

          let scriptId = getUniqueId();
          return context.cloneScope.Promise.resolve().then(async () => {
            options.scriptId = scriptId;
            options.js = await Promise.all(
              options.js.map(js => {
                return js.file || getBlobURL(js.code, scriptId);
              })
            );

            await context.childManager.callParentAsyncFunction(
              "userScripts.register",
              [options]
            );

            return convertToAPIObject(scriptId, options);
          });
        },
      },
    };
  }
};
PK
!<��S���9chrome/toolkit/content/extensions/child/ext-webRequest.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var { ExtensionError } = ExtensionUtils;

this.webRequest = class extends ExtensionAPI {
  STREAM_FILTER_INACTIVE_STATUSES = ["closed", "disconnected", "failed"];

  hasActiveStreamFilter(filtersWeakSet) {
    const iter = ChromeUtils.nondeterministicGetWeakSetKeys(filtersWeakSet);
    for (let filter of iter) {
      if (!this.STREAM_FILTER_INACTIVE_STATUSES.includes(filter.status)) {
        return true;
      }
    }
    return false;
  }

  watchStreamFilterSuspendCancel({
    context,
    filters,
    onSuspend,
    onSuspendCanceled,
  }) {
    if (
      !context.isBackgroundContext ||
      context.extension.persistentBackground !== false
    ) {
      return;
    }

    const { extension } = context;
    const cancelSuspendOnActiveStreamFilter = () =>
      this.hasActiveStreamFilter(filters);
    context.callOnClose({
      close() {
        extension.off(
          "internal:stream-filter-suspend-cancel",
          cancelSuspendOnActiveStreamFilter
        );
        extension.off("background-script-suspend", onSuspend);
        extension.off("background-script-suspend-canceled", onSuspend);
      },
    });
    extension.on(
      "internal:stream-filter-suspend-cancel",
      cancelSuspendOnActiveStreamFilter
    );
    extension.on("background-script-suspend", onSuspend);
    extension.on("background-script-suspend-canceled", onSuspendCanceled);
  }

  getAPI(context) {
    let filters = new WeakSet();

    context.callOnClose({
      close() {
        for (let filter of ChromeUtils.nondeterministicGetWeakSetKeys(
          filters
        )) {
          try {
            filter.disconnect();
          } catch (e) {
            // Ignore.
          }
        }
      },
    });

    let isSuspending = false;
    this.watchStreamFilterSuspendCancel({
      context,
      filters,
      onSuspend: () => (isSuspending = true),
      onSuspendCanceled: () => (isSuspending = false),
    });

    function filterResponseData(requestId) {
      if (isSuspending) {
        throw new ExtensionError(
          "filterResponseData method calls forbidden while background extension global is suspending"
        );
      }
      requestId = parseInt(requestId, 10);

      let streamFilter = context.cloneScope.StreamFilter.create(
        requestId,
        context.extension.id
      );

      filters.add(streamFilter);
      return streamFilter;
    }

    const webRequest = {
      onAuthRequired: new EventManager({
        context,
        name: "webRequest.onAuthRequired",
        // Parent event already resets idle if needed, no need to do it here.
        resetIdleOnEvent: false,
        register: (fire, filter, extra) => {
          const listener = details => {
            // NOTE: asyncBlocking and blocking are mutually exclusive
            // (and an error raised synchronously from schema based
            // validation).
            if (!extra.includes("asyncBlocking")) {
              // NOTE: may return the result or a Promise.
              return fire.raw(details);
            }

            // Wrap the call into a promise resolved with what the extension
            // passed to the additional asyncCallback parameter.
            let asyncCallback;
            const promise = new Promise(resolve => {
              asyncCallback = Cu.exportFunction(value => {
                resolve(value);
              }, context.cloneScope);
            });
            // Return value is ignored on asyncBlocking listeners
            // (chrome compatible behavior).
            fire.raw(details, asyncCallback);
            return promise;
          };
          const parentEvent = context.childManager.getParentEvent(
            "webRequest.onAuthRequired"
          );
          parentEvent.addListener(listener, filter, extra);
          return () => {
            parentEvent.removeListener(listener);
          };
        },
      }).api(),
    };

    // For extensions with manifest_version >= 3, an additional webRequestFilterResponse permission
    // is required to get access to the webRequest.filterResponseData API method.
    if (
      context.extension.manifestVersion < 3 ||
      context.extension.hasPermission("webRequestFilterResponse")
    ) {
      webRequest.filterResponseData = filterResponseData;
    } else {
      webRequest.filterResponseData = () => {
        throw new ExtensionError(
          'Missing required "webRequestFilterResponse" permission'
        );
      };
    }

    return { webRequest };
  }
};
PK
!<�G��!�!8chrome/toolkit/content/extensions/ext-browser-content.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/frame-script */

"use strict";

ChromeUtils.defineESModuleGetters(this, {
  ExtensionCommon: "resource://gre/modules/ExtensionCommon.sys.mjs",
  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

// Minimum time between two resizes.
const RESIZE_TIMEOUT = 100;

const BrowserListener = {
  init({
    allowScriptsToClose,
    blockParser,
    fixedWidth,
    maxHeight,
    maxWidth,
    stylesheets,
    isInline,
  }) {
    this.fixedWidth = fixedWidth;
    this.stylesheets = stylesheets || [];

    this.isInline = isInline;
    this.maxWidth = maxWidth;
    this.maxHeight = maxHeight;

    this.blockParser = blockParser;
    this.needsResize = fixedWidth || maxHeight || maxWidth;

    this.oldBackground = null;

    if (allowScriptsToClose) {
      content.windowUtils.allowScriptsToClose();
    }

    if (this.blockParser) {
      this.blockingPromise = new Promise(resolve => {
        this.unblockParser = resolve;
      });
      addEventListener("DOMDocElementInserted", this, true);
    }

    addEventListener("load", this, true);
    addEventListener("DOMWindowCreated", this, true);
    addEventListener("DOMContentLoaded", this, true);
    addEventListener("MozScrolledAreaChanged", this, true);
  },

  destroy() {
    if (this.blockParser) {
      removeEventListener("DOMDocElementInserted", this, true);
    }

    removeEventListener("load", this, true);
    removeEventListener("DOMWindowCreated", this, true);
    removeEventListener("DOMContentLoaded", this, true);
    removeEventListener("MozScrolledAreaChanged", this, true);
  },

  receiveMessage({ name, data }) {
    if (name === "Extension:InitBrowser") {
      this.init(data);
    } else if (name === "Extension:UnblockParser") {
      if (this.unblockParser) {
        this.unblockParser();
        this.blockingPromise = null;
      }
    } else if (name === "Extension:GrabFocus") {
      content.window.requestAnimationFrame(() => {
        Services.focus.focusedWindow = content.window;
      });
    }
  },

  loadStylesheets() {
    let { windowUtils } = content;

    for (let url of this.stylesheets) {
      windowUtils.addSheet(
        ExtensionCommon.stylesheetMap.get(url),
        windowUtils.AUTHOR_SHEET
      );
    }
  },

  handleEvent(event) {
    switch (event.type) {
      case "DOMDocElementInserted":
        if (this.blockingPromise) {
          const doc = event.target;
          const policy = doc?.nodePrincipal?.addonPolicy;
          event.target.blockParsing(this.blockingPromise).then(() => {
            policy?.weakExtension?.get()?.untrackBlockedParsingDocument(doc);
          });
          policy?.weakExtension?.get()?.trackBlockedParsingDocument(doc);
        }
        break;

      case "DOMWindowCreated":
        if (event.target === content.document) {
          this.loadStylesheets();
        }
        break;

      case "DOMContentLoaded":
        if (event.target === content.document) {
          sendAsyncMessage("Extension:BrowserContentLoaded", {
            url: content.location.href,
          });

          if (this.needsResize) {
            this.handleDOMChange(true);
          }
        }
        break;

      case "load":
        if (event.target.contentWindow === content) {
          // For about:addons inline <browser>s, we currently receive a load
          // event on the <browser> element, but no load or DOMContentLoaded
          // events from the content window.

          // Inline browsers don't receive the "DOMWindowCreated" event, so this
          // is a workaround to load the stylesheets.
          if (this.isInline) {
            this.loadStylesheets();
          }
          sendAsyncMessage("Extension:BrowserContentLoaded", {
            url: content.location.href,
          });
        } else if (event.target !== content.document) {
          break;
        }

        if (!this.needsResize) {
          break;
        }

        // We use a capturing listener, so we get this event earlier than any
        // load listeners in the content page. Resizing after a timeout ensures
        // that we calculate the size after the entire event cycle has completed
        // (unless someone spins the event loop, anyway), and hopefully after
        // the content has made any modifications.
        Promise.resolve().then(() => {
          this.handleDOMChange(true);
        });

        // Mutation observer to make sure the panel shrinks when the content does.
        new content.MutationObserver(this.handleDOMChange.bind(this)).observe(
          content.document.documentElement,
          {
            attributes: true,
            characterData: true,
            childList: true,
            subtree: true,
          }
        );
        break;

      case "MozScrolledAreaChanged":
        if (this.needsResize) {
          this.handleDOMChange();
        }
        break;
    }
  },

  // Resizes the browser to match the preferred size of the content (debounced).
  handleDOMChange(ignoreThrottling = false) {
    if (ignoreThrottling && this.resizeTimeout) {
      clearTimeout(this.resizeTimeout);
      this.resizeTimeout = null;
    }

    if (this.resizeTimeout == null) {
      this.resizeTimeout = setTimeout(() => {
        try {
          if (content) {
            this._handleDOMChange("delayed");
          }
        } finally {
          this.resizeTimeout = null;
        }
      }, RESIZE_TIMEOUT);

      this._handleDOMChange();
    }
  },

  _handleDOMChange(detail) {
    let doc = content.document;

    let body = doc.body;
    if (!body || doc.compatMode === "BackCompat") {
      // In quirks mode, the root element is used as the scroll frame, and the
      // body lies about its scroll geometry, and returns the values for the
      // root instead.
      body = doc.documentElement;
    }

    let result;
    const zoom = content.browsingContext.fullZoom;
    if (this.fixedWidth) {
      // If we're in a fixed-width area (namely a slide-in subview of the main
      // menu panel), we need to calculate the view height based on the
      // preferred height of the content document's root scrollable element at the
      // current width, rather than the complete preferred dimensions of the
      // content window.

      // Compensate for any offsets (margin, padding, ...) between the scroll
      // area of the body and the outer height of the document.
      // This calculation is hard to get right for all cases, so take the lower
      // number of the combination of all padding and margins of the document
      // and body elements, or the difference between their heights.
      let getHeight = elem => elem.getBoundingClientRect(elem).height;
      let bodyPadding = getHeight(doc.documentElement) - getHeight(body);

      if (body !== doc.documentElement) {
        let bs = content.getComputedStyle(body);
        let ds = content.getComputedStyle(doc.documentElement);

        let p =
          parseFloat(bs.marginTop) +
          parseFloat(bs.marginBottom) +
          parseFloat(ds.marginTop) +
          parseFloat(ds.marginBottom) +
          parseFloat(ds.paddingTop) +
          parseFloat(ds.paddingBottom);
        bodyPadding = Math.min(p, bodyPadding);
      }

      let height = Math.ceil((body.scrollHeight + bodyPadding) * zoom);

      result = { height, detail };
    } else {
      let background = content.windowUtils.canvasBackgroundColor;
      if (background !== this.oldBackground) {
        sendAsyncMessage("Extension:BrowserBackgroundChanged", { background });
      }
      this.oldBackground = background;

      // Adjust the size of the browser based on its content's preferred size.
      let w = {},
        h = {};
      docShell.docViewer.getContentSize(
        this.maxWidth,
        this.maxHeight,
        /* prefWidth = */ 0,
        w,
        h
      );

      let width = Math.ceil(w.value * zoom);
      let height = Math.ceil(h.value * zoom);
      result = { width, height, detail };
    }

    sendAsyncMessage("Extension:BrowserResized", result);
  },
};

addMessageListener("Extension:InitBrowser", BrowserListener);
addMessageListener("Extension:UnblockParser", BrowserListener);
addMessageListener("Extension:GrabFocus", BrowserListener);

// This is a temporary hack to prevent regressions (bug 1471327).
void content;
PK
!<ye>2chrome/toolkit/content/extensions/ext-toolkit.json{
  "manifest": {
    "schema": "chrome://extensions/content/schemas/extension_types.json",
    "scopes": []
  },
  "alarms": {
    "url": "chrome://extensions/content/parent/ext-alarms.js",
    "schema": "chrome://extensions/content/schemas/alarms.json",
    "scopes": ["addon_parent"],
    "paths": [["alarms"]]
  },
  "backgroundPage": {
    "url": "chrome://extensions/content/parent/ext-backgroundPage.js",
    "scopes": ["addon_parent"],
    "manifest": ["background"]
  },
  "browserSettings": {
    "url": "chrome://extensions/content/parent/ext-browserSettings.js",
    "schema": "chrome://extensions/content/schemas/browser_settings.json",
    "scopes": ["addon_parent"],
    "settings": true,
    "paths": [["browserSettings"]]
  },
  "clipboard": {
    "url": "chrome://extensions/content/parent/ext-clipboard.js",
    "schema": "chrome://extensions/content/schemas/clipboard.json",
    "scopes": ["addon_parent"],
    "paths": [["clipboard"]]
  },
  "contentScripts": {
    "url": "chrome://extensions/content/parent/ext-contentScripts.js",
    "schema": "chrome://extensions/content/schemas/content_scripts.json",
    "scopes": ["addon_parent"],
    "paths": [["contentScripts"]]
  },
  "contextualIdentities": {
    "url": "chrome://extensions/content/parent/ext-contextualIdentities.js",
    "schema": "chrome://extensions/content/schemas/contextual_identities.json",
    "scopes": ["addon_parent"],
    "settings": true,
    "events": ["startup"],
    "permissions": ["contextualIdentities"],
    "paths": [["contextualIdentities"]]
  },
  "cookies": {
    "url": "chrome://extensions/content/parent/ext-cookies.js",
    "schema": "chrome://extensions/content/schemas/cookies.json",
    "scopes": ["addon_parent"],
    "paths": [["cookies"]]
  },
  "declarativeNetRequest": {
    "url": "chrome://extensions/content/parent/ext-declarativeNetRequest.js",
    "schema": "chrome://extensions/content/schemas/declarative_net_request.json",
    "scopes": ["addon_parent"],
    "manifest": ["declarative_net_request"],
    "paths": [["declarativeNetRequest"]]
  },
  "dns": {
    "url": "chrome://extensions/content/parent/ext-dns.js",
    "schema": "chrome://extensions/content/schemas/dns.json",
    "scopes": ["addon_parent"],
    "paths": [["dns"]]
  },
  "downloads": {
    "url": "chrome://extensions/content/parent/ext-downloads.js",
    "schema": "chrome://extensions/content/schemas/downloads.json",
    "scopes": ["addon_parent"],
    "paths": [["downloads"]]
  },
  "extension": {
    "url": "chrome://extensions/content/parent/ext-extension.js",
    "schema": "chrome://extensions/content/schemas/extension.json",
    "scopes": ["addon_parent", "content_child"],
    "paths": [["extension"]]
  },
  "activityLog": {
    "url": "chrome://extensions/content/parent/ext-activityLog.js",
    "schema": "chrome://extensions/content/schemas/activity_log.json",
    "scopes": ["addon_parent"],
    "paths": [["activityLog"]]
  },
  "i18n": {
    "url": "chrome://extensions/content/parent/ext-i18n.js",
    "schema": "chrome://extensions/content/schemas/i18n.json",
    "scopes": ["addon_parent", "content_child", "devtools_child"],
    "paths": [["i18n"]]
  },
  "idle": {
    "url": "chrome://extensions/content/parent/ext-idle.js",
    "schema": "chrome://extensions/content/schemas/idle.json",
    "scopes": ["addon_parent"],
    "paths": [["idle"]]
  },
  "management": {
    "url": "chrome://extensions/content/parent/ext-management.js",
    "schema": "chrome://extensions/content/schemas/management.json",
    "scopes": ["addon_parent"],
    "paths": [["management"]]
  },
  "networkStatus": {
    "url": "chrome://extensions/content/parent/ext-networkStatus.js",
    "schema": "chrome://extensions/content/schemas/network_status.json",
    "scopes": ["addon_parent"],
    "paths": [["networkStatus"]]
  },
  "notifications": {
    "url": "chrome://extensions/content/parent/ext-notifications.js",
    "schema": "chrome://extensions/content/schemas/notifications.json",
    "scopes": ["addon_parent"],
    "paths": [["notifications"]]
  },
  "permissions": {
    "url": "chrome://extensions/content/parent/ext-permissions.js",
    "schema": "chrome://extensions/content/schemas/permissions.json",
    "scopes": ["addon_parent"],
    "paths": [["permissions"]]
  },
  "privacy": {
    "url": "chrome://extensions/content/parent/ext-privacy.js",
    "schema": "chrome://extensions/content/schemas/privacy.json",
    "scopes": ["addon_parent"],
    "settings": true,
    "paths": [["privacy"]]
  },
  "protocolHandlers": {
    "url": "chrome://extensions/content/parent/ext-protocolHandlers.js",
    "schema": "chrome://extensions/content/schemas/extension_protocol_handlers.json",
    "scopes": ["addon_parent"],
    "manifest": ["protocol_handlers"]
  },
  "proxy": {
    "url": "chrome://extensions/content/parent/ext-proxy.js",
    "schema": "chrome://extensions/content/schemas/proxy.json",
    "scopes": ["addon_parent"],
    "settings": true,
    "paths": [["proxy"]],
    "startupBlocking": true
  },
  "runtime": {
    "url": "chrome://extensions/content/parent/ext-runtime.js",
    "schema": "chrome://extensions/content/schemas/runtime.json",
    "scopes": ["addon_parent", "content_parent", "devtools_parent"],
    "paths": [["runtime"]]
  },
  "scripting": {
    "url": "chrome://extensions/content/parent/ext-scripting.js",
    "schema": "chrome://extensions/content/schemas/scripting.json",
    "scopes": ["addon_parent"],
    "paths": [["scripting"]]
  },
  "storage": {
    "url": "chrome://extensions/content/parent/ext-storage.js",
    "schema": "chrome://extensions/content/schemas/storage.json",
    "scopes": ["addon_parent", "content_parent", "devtools_parent"],
    "paths": [["storage"]]
  },
  "telemetry": {
    "url": "chrome://extensions/content/parent/ext-telemetry.js",
    "schema": "chrome://extensions/content/schemas/telemetry.json",
    "scopes": ["addon_parent"],
    "paths": [["telemetry"]]
  },
  "test": {
    "schema": "chrome://extensions/content/schemas/test.json",
    "scopes": ["content_child"]
  },
  "theme": {
    "url": "chrome://extensions/content/parent/ext-theme.js",
    "schema": "chrome://extensions/content/schemas/theme.json",
    "scopes": ["addon_parent"],
    "manifest": ["theme"],
    "paths": [["theme"]]
  },
  "userScripts": {
    "url": "chrome://extensions/content/parent/ext-userScripts.js",
    "schema": "chrome://extensions/content/schemas/user_scripts.json",
    "scopes": ["addon_parent"],
    "paths": [["userScripts"]]
  },
  "userScriptsContent": {
    "schema": "chrome://extensions/content/schemas/user_scripts_content.json",
    "scopes": ["content_child"],
    "paths": [["userScripts", "onBeforeScript"]]
  },
  "webNavigation": {
    "url": "chrome://extensions/content/parent/ext-webNavigation.js",
    "schema": "chrome://extensions/content/schemas/web_navigation.json",
    "scopes": ["addon_parent"],
    "paths": [["webNavigation"]]
  },
  "webRequest": {
    "url": "chrome://extensions/content/parent/ext-webRequest.js",
    "schema": "chrome://extensions/content/schemas/web_request.json",
    "scopes": ["addon_parent"],
    "paths": [["webRequest"]],
    "startupBlocking": true
  }
}
PK
!<N����;chrome/toolkit/content/extensions/parent/ext-activityLog.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

ChromeUtils.defineESModuleGetters(this, {
  ExtensionActivityLog: "resource://gre/modules/ExtensionActivityLog.sys.mjs",
  ExtensionCommon: "resource://gre/modules/ExtensionCommon.sys.mjs",
});

this.activityLog = class extends ExtensionAPI {
  getAPI(context) {
    return {
      activityLog: {
        onExtensionActivity: new ExtensionCommon.EventManager({
          context,
          name: "activityLog.onExtensionActivity",
          register: (fire, id) => {
            // A logger cannot log itself.
            if (id === context.extension.id) {
              throw new ExtensionUtils.ExtensionError(
                "Extension cannot monitor itself."
              );
            }
            function handler(details) {
              fire.async(details);
            }

            ExtensionActivityLog.addListener(id, handler);
            return () => {
              ExtensionActivityLog.removeListener(id, handler);
            };
          },
        }).api(),
      },
    };
  }
};
PK
!<Q�����6chrome/toolkit/content/extensions/parent/ext-alarms.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// The ext-* files are imported into the same scopes.
/* import-globals-from ext-toolkit.js */

// Manages an alarm created by the extension (alarms API).
class Alarm {
  constructor(api, name, alarmInfo) {
    this.api = api;
    this.name = name;
    this.when = alarmInfo.when;
    this.delayInMinutes = alarmInfo.delayInMinutes;
    this.periodInMinutes = alarmInfo.periodInMinutes;
    this.canceled = false;

    let delay, scheduledTime;
    if (this.when) {
      scheduledTime = this.when;
      delay = this.when - Date.now();
    } else {
      if (!this.delayInMinutes) {
        this.delayInMinutes = this.periodInMinutes;
      }
      delay = this.delayInMinutes * 60 * 1000;
      scheduledTime = Date.now() + delay;
    }

    this.scheduledTime = scheduledTime;

    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    delay = delay > 0 ? delay : 0;
    timer.init(this, delay, Ci.nsITimer.TYPE_ONE_SHOT);
    this.timer = timer;
  }

  clear() {
    this.timer.cancel();
    this.api.alarms.delete(this.name);
    this.canceled = true;
  }

  observe() {
    if (this.canceled) {
      return;
    }

    for (let callback of this.api.callbacks) {
      callback(this);
    }

    if (!this.periodInMinutes) {
      this.clear();
      return;
    }

    let delay = this.periodInMinutes * 60 * 1000;
    this.scheduledTime = Date.now() + delay;
    this.timer.init(this, delay, Ci.nsITimer.TYPE_ONE_SHOT);
  }

  get data() {
    return {
      name: this.name,
      scheduledTime: this.scheduledTime,
      periodInMinutes: this.periodInMinutes,
    };
  }
}

this.alarms = class extends ExtensionAPIPersistent {
  constructor(extension) {
    super(extension);

    this.alarms = new Map();
    this.callbacks = new Set();
  }

  onShutdown() {
    for (let alarm of this.alarms.values()) {
      alarm.clear();
    }
  }

  PERSISTENT_EVENTS = {
    onAlarm({ fire }) {
      let callback = alarm => {
        fire.sync(alarm.data);
      };

      this.callbacks.add(callback);

      return {
        unregister: () => {
          this.callbacks.delete(callback);
        },
        convert(_fire) {
          fire = _fire;
        },
      };
    },
  };

  getAPI(context) {
    const self = this;

    return {
      alarms: {
        create: function (name, alarmInfo) {
          name = name || "";
          if (self.alarms.has(name)) {
            self.alarms.get(name).clear();
          }
          let alarm = new Alarm(self, name, alarmInfo);
          self.alarms.set(alarm.name, alarm);
        },

        get: function (name) {
          name = name || "";
          if (self.alarms.has(name)) {
            return Promise.resolve(self.alarms.get(name).data);
          }
          return Promise.resolve();
        },

        getAll: function () {
          let result = Array.from(self.alarms.values(), alarm => alarm.data);
          return Promise.resolve(result);
        },

        clear: function (name) {
          name = name || "";
          if (self.alarms.has(name)) {
            self.alarms.get(name).clear();
            return Promise.resolve(true);
          }
          return Promise.resolve(false);
        },

        clearAll: function () {
          let cleared = false;
          for (let alarm of self.alarms.values()) {
            alarm.clear();
            cleared = true;
          }
          return Promise.resolve(cleared);
        },

        onAlarm: new EventManager({
          context,
          module: "alarms",
          event: "onAlarm",
          extensionApi: self,
        }).api(),
      },
    };
  }
};
PK
!<REW�EBEB?chrome/toolkit/content/extensions/parent/ext-browserSettings.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

ChromeUtils.defineESModuleGetters(this, {
  AboutNewTab: "resource:///modules/AboutNewTab.sys.mjs",
});

var { ExtensionPreferencesManager } = ChromeUtils.importESModule(
  "resource://gre/modules/ExtensionPreferencesManager.sys.mjs"
);

var { ExtensionError } = ExtensionUtils;
var { getSettingsAPI, getPrimedSettingsListener } = ExtensionPreferencesManager;

const HOMEPAGE_URL_PREF = "browser.startup.homepage";

const PERM_DENY_ACTION = Services.perms.DENY_ACTION;

// Add settings objects for supported APIs to the preferences manager.
ExtensionPreferencesManager.addSetting("allowPopupsForUserEvents", {
  permission: "browserSettings",
  prefNames: ["dom.popup_allowed_events"],

  setCallback(value) {
    let returnObj = {};
    // If the value is true, then reset the pref, otherwise set it to "".
    returnObj[this.prefNames[0]] = value ? undefined : "";
    return returnObj;
  },

  getCallback() {
    return Services.prefs.getCharPref("dom.popup_allowed_events") != "";
  },
});

ExtensionPreferencesManager.addSetting("cacheEnabled", {
  permission: "browserSettings",
  prefNames: ["browser.cache.disk.enable", "browser.cache.memory.enable"],

  setCallback(value) {
    let returnObj = {};
    for (let pref of this.prefNames) {
      returnObj[pref] = value;
    }
    return returnObj;
  },

  getCallback() {
    return (
      Services.prefs.getBoolPref("browser.cache.disk.enable") &&
      Services.prefs.getBoolPref("browser.cache.memory.enable")
    );
  },
});

ExtensionPreferencesManager.addSetting("closeTabsByDoubleClick", {
  permission: "browserSettings",
  prefNames: ["browser.tabs.closeTabByDblclick"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return Services.prefs.getBoolPref("browser.tabs.closeTabByDblclick");
  },

  validate() {
    if (AppConstants.platform == "android") {
      throw new ExtensionError(
        `android is not a supported platform for the closeTabsByDoubleClick setting.`
      );
    }
  },
});

ExtensionPreferencesManager.addSetting("colorManagement.mode", {
  permission: "browserSettings",
  prefNames: ["gfx.color_management.mode"],

  setCallback(value) {
    switch (value) {
      case "off":
        return { [this.prefNames[0]]: 0 };
      case "full":
        return { [this.prefNames[0]]: 1 };
      case "tagged_only":
        return { [this.prefNames[0]]: 2 };
    }
  },

  getCallback() {
    switch (Services.prefs.getIntPref("gfx.color_management.mode")) {
      case 0:
        return "off";
      case 1:
        return "full";
      case 2:
        return "tagged_only";
    }
  },
});

ExtensionPreferencesManager.addSetting("colorManagement.useNativeSRGB", {
  permission: "browserSettings",
  prefNames: ["gfx.color_management.native_srgb"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return Services.prefs.getBoolPref("gfx.color_management.native_srgb");
  },
});

ExtensionPreferencesManager.addSetting(
  "colorManagement.useWebRenderCompositor",
  {
    permission: "browserSettings",
    prefNames: ["gfx.webrender.compositor"],

    setCallback(value) {
      return { [this.prefNames[0]]: value };
    },

    getCallback() {
      return Services.prefs.getBoolPref("gfx.webrender.compositor");
    },
  }
);

ExtensionPreferencesManager.addSetting("contextMenuShowEvent", {
  permission: "browserSettings",
  prefNames: ["ui.context_menus.after_mouseup"],

  setCallback(value) {
    return { [this.prefNames[0]]: value === "mouseup" };
  },

  getCallback() {
    if (AppConstants.platform === "win") {
      return "mouseup";
    }
    let prefValue = Services.prefs.getBoolPref(
      "ui.context_menus.after_mouseup",
      null
    );
    return prefValue ? "mouseup" : "mousedown";
  },
});

ExtensionPreferencesManager.addSetting("imageAnimationBehavior", {
  permission: "browserSettings",
  prefNames: ["image.animation_mode"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return Services.prefs.getCharPref("image.animation_mode");
  },
});

ExtensionPreferencesManager.addSetting("newTabPosition", {
  permission: "browserSettings",
  prefNames: [
    "browser.tabs.insertRelatedAfterCurrent",
    "browser.tabs.insertAfterCurrent",
  ],

  setCallback(value) {
    return {
      "browser.tabs.insertAfterCurrent": value === "afterCurrent",
      "browser.tabs.insertRelatedAfterCurrent": value === "relatedAfterCurrent",
    };
  },

  getCallback() {
    if (Services.prefs.getBoolPref("browser.tabs.insertAfterCurrent")) {
      return "afterCurrent";
    }
    if (Services.prefs.getBoolPref("browser.tabs.insertRelatedAfterCurrent")) {
      return "relatedAfterCurrent";
    }
    return "atEnd";
  },
});

ExtensionPreferencesManager.addSetting("openBookmarksInNewTabs", {
  permission: "browserSettings",
  prefNames: ["browser.tabs.loadBookmarksInTabs"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return Services.prefs.getBoolPref("browser.tabs.loadBookmarksInTabs");
  },
});

ExtensionPreferencesManager.addSetting("openSearchResultsInNewTabs", {
  permission: "browserSettings",
  prefNames: ["browser.search.openintab"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return Services.prefs.getBoolPref("browser.search.openintab");
  },
});

ExtensionPreferencesManager.addSetting("openUrlbarResultsInNewTabs", {
  permission: "browserSettings",
  prefNames: ["browser.urlbar.openintab"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return Services.prefs.getBoolPref("browser.urlbar.openintab");
  },
});

ExtensionPreferencesManager.addSetting("webNotificationsDisabled", {
  permission: "browserSettings",
  prefNames: ["permissions.default.desktop-notification"],

  setCallback(value) {
    return { [this.prefNames[0]]: value ? PERM_DENY_ACTION : undefined };
  },

  getCallback() {
    let prefValue = Services.prefs.getIntPref(
      "permissions.default.desktop-notification",
      null
    );
    return prefValue === PERM_DENY_ACTION;
  },
});

ExtensionPreferencesManager.addSetting("overrideDocumentColors", {
  permission: "browserSettings",
  prefNames: ["browser.display.document_color_use"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    let prefValue = Services.prefs.getIntPref(
      "browser.display.document_color_use"
    );
    if (prefValue === 1) {
      return "never";
    } else if (prefValue === 2) {
      return "always";
    }
    return "high-contrast-only";
  },
});

ExtensionPreferencesManager.addSetting("overrideContentColorScheme", {
  permission: "browserSettings",
  prefNames: ["layout.css.prefers-color-scheme.content-override"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    let prefValue = Services.prefs.getIntPref(
      "layout.css.prefers-color-scheme.content-override"
    );
    switch (prefValue) {
      case 0:
        return "dark";
      case 1:
        return "light";
      default:
        return "auto";
    }
  },
});

ExtensionPreferencesManager.addSetting("useDocumentFonts", {
  permission: "browserSettings",
  prefNames: ["browser.display.use_document_fonts"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return (
      Services.prefs.getIntPref("browser.display.use_document_fonts") !== 0
    );
  },
});

ExtensionPreferencesManager.addSetting("zoomFullPage", {
  permission: "browserSettings",
  prefNames: ["browser.zoom.full"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return Services.prefs.getBoolPref("browser.zoom.full");
  },
});

ExtensionPreferencesManager.addSetting("zoomSiteSpecific", {
  permission: "browserSettings",
  prefNames: ["browser.zoom.siteSpecific"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return Services.prefs.getBoolPref("browser.zoom.siteSpecific");
  },
});

this.browserSettings = class extends ExtensionAPI {
  homePageOverrideListener(fire) {
    let listener = () => {
      fire.async({
        levelOfControl: "not_controllable",
        value: Services.prefs.getStringPref(HOMEPAGE_URL_PREF),
      });
    };
    Services.prefs.addObserver(HOMEPAGE_URL_PREF, listener);
    return {
      unregister: () => {
        Services.prefs.removeObserver(HOMEPAGE_URL_PREF, listener);
      },
      convert(_fire) {
        fire = _fire;
      },
    };
  }

  newTabOverrideListener(fire) {
    let listener = () => {
      fire.async({
        levelOfControl: "not_controllable",
        value: AboutNewTab.newTabURL,
      });
    };
    Services.obs.addObserver(listener, "newtab-url-changed");
    return {
      unregister: () => {
        Services.obs.removeObserver(listener, "newtab-url-changed");
      },
      convert(_fire) {
        fire = _fire;
      },
    };
  }

  primeListener(event, fire) {
    let { extension } = this;
    if (event == "homepageOverride") {
      return this.homePageOverrideListener(fire);
    }
    if (event == "newTabPageOverride") {
      return this.newTabOverrideListener(fire);
    }
    let listener = getPrimedSettingsListener({
      extension,
      name: event,
    });
    return listener(fire);
  }

  getAPI(context) {
    let self = this;
    let { extension } = context;

    function makeSettingsAPI(name) {
      return getSettingsAPI({
        context,
        module: "browserSettings",
        name,
      });
    }

    return {
      browserSettings: {
        allowPopupsForUserEvents: makeSettingsAPI("allowPopupsForUserEvents"),
        cacheEnabled: makeSettingsAPI("cacheEnabled"),
        closeTabsByDoubleClick: makeSettingsAPI("closeTabsByDoubleClick"),
        contextMenuShowEvent: Object.assign(
          makeSettingsAPI("contextMenuShowEvent"),
          {
            set: details => {
              if (!["mouseup", "mousedown"].includes(details.value)) {
                throw new ExtensionError(
                  `${details.value} is not a valid value for contextMenuShowEvent.`
                );
              }
              if (
                AppConstants.platform === "android" ||
                (AppConstants.platform === "win" &&
                  details.value === "mousedown")
              ) {
                return false;
              }
              return ExtensionPreferencesManager.setSetting(
                extension.id,
                "contextMenuShowEvent",
                details.value
              );
            },
          }
        ),
        ftpProtocolEnabled: getSettingsAPI({
          context,
          name: "ftpProtocolEnabled",
          readOnly: true,
          callback() {
            return false;
          },
        }),
        homepageOverride: getSettingsAPI({
          context,
          // Name differs here to preserve this setting properly
          name: "homepage_override",
          callback() {
            return Services.prefs.getStringPref(HOMEPAGE_URL_PREF);
          },
          readOnly: true,
          onChange: new ExtensionCommon.EventManager({
            context,
            module: "browserSettings",
            event: "homepageOverride",
            name: "homepageOverride.onChange",
            register: fire => {
              return self.homePageOverrideListener(fire).unregister;
            },
          }).api(),
        }),
        imageAnimationBehavior: makeSettingsAPI("imageAnimationBehavior"),
        newTabPosition: makeSettingsAPI("newTabPosition"),
        newTabPageOverride: getSettingsAPI({
          context,
          // Name differs here to preserve this setting properly
          name: "newTabURL",
          callback() {
            return AboutNewTab.newTabURL;
          },
          storeType: "url_overrides",
          readOnly: true,
          onChange: new ExtensionCommon.EventManager({
            context,
            module: "browserSettings",
            event: "newTabPageOverride",
            name: "newTabPageOverride.onChange",
            register: fire => {
              return self.newTabOverrideListener(fire).unregister;
            },
          }).api(),
        }),
        openBookmarksInNewTabs: makeSettingsAPI("openBookmarksInNewTabs"),
        openSearchResultsInNewTabs: makeSettingsAPI(
          "openSearchResultsInNewTabs"
        ),
        openUrlbarResultsInNewTabs: makeSettingsAPI(
          "openUrlbarResultsInNewTabs"
        ),
        webNotificationsDisabled: makeSettingsAPI("webNotificationsDisabled"),
        overrideDocumentColors: Object.assign(
          makeSettingsAPI("overrideDocumentColors"),
          {
            set: details => {
              if (
                !["never", "always", "high-contrast-only"].includes(
                  details.value
                )
              ) {
                throw new ExtensionError(
                  `${details.value} is not a valid value for overrideDocumentColors.`
                );
              }
              let prefValue = 0; // initialize to 0 - auto/high-contrast-only
              if (details.value === "never") {
                prefValue = 1;
              } else if (details.value === "always") {
                prefValue = 2;
              }
              return ExtensionPreferencesManager.setSetting(
                extension.id,
                "overrideDocumentColors",
                prefValue
              );
            },
          }
        ),
        overrideContentColorScheme: Object.assign(
          makeSettingsAPI("overrideContentColorScheme"),
          {
            set: details => {
              let value = details.value;
              if (value == "system" || value == "browser") {
                // Map previous values that used to be different but were
                // unified under the "auto" setting. In practice this should
                // almost always behave like the extension author expects.
                extension.logger.warn(
                  `The "${value}" value for overrideContentColorScheme has been deprecated. Use "auto" instead`
                );
                value = "auto";
              }
              let prefValue = ["dark", "light", "auto"].indexOf(value);
              if (prefValue === -1) {
                throw new ExtensionError(
                  `${value} is not a valid value for overrideContentColorScheme.`
                );
              }
              return ExtensionPreferencesManager.setSetting(
                extension.id,
                "overrideContentColorScheme",
                prefValue
              );
            },
          }
        ),
        useDocumentFonts: Object.assign(makeSettingsAPI("useDocumentFonts"), {
          set: details => {
            if (typeof details.value !== "boolean") {
              throw new ExtensionError(
                `${details.value} is not a valid value for useDocumentFonts.`
              );
            }
            return ExtensionPreferencesManager.setSetting(
              extension.id,
              "useDocumentFonts",
              Number(details.value)
            );
          },
        }),
        zoomFullPage: Object.assign(makeSettingsAPI("zoomFullPage"), {
          set: details => {
            if (typeof details.value !== "boolean") {
              throw new ExtensionError(
                `${details.value} is not a valid value for zoomFullPage.`
              );
            }
            return ExtensionPreferencesManager.setSetting(
              extension.id,
              "zoomFullPage",
              details.value
            );
          },
        }),
        zoomSiteSpecific: Object.assign(makeSettingsAPI("zoomSiteSpecific"), {
          set: details => {
            if (typeof details.value !== "boolean") {
              throw new ExtensionError(
                `${details.value} is not a valid value for zoomSiteSpecific.`
              );
            }
            return ExtensionPreferencesManager.setSetting(
              extension.id,
              "zoomSiteSpecific",
              details.value
            );
          },
        }),
        colorManagement: {
          mode: makeSettingsAPI("colorManagement.mode"),
          useNativeSRGB: makeSettingsAPI("colorManagement.useNativeSRGB"),
          useWebRenderCompositor: makeSettingsAPI(
            "colorManagement.useWebRenderCompositor"
          ),
        },
      },
    };
  }
};
PK
!<��$�1�1<chrome/toolkit/content/extensions/parent/ext-browsingData.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

ChromeUtils.defineESModuleGetters(this, {
  // This helper contains the platform-specific bits of browsingData.
  BrowsingDataDelegate: "resource:///modules/ExtensionBrowsingData.sys.mjs",
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
  ServiceWorkerCleanUp: "resource://gre/modules/ServiceWorkerCleanUp.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

/**
 * A number of iterations after which to yield time back
 * to the system.
 */
const YIELD_PERIOD = 10;

/**
 * Convert a Date object to a PRTime (microseconds).
 *
 * @param {Date} date
 *        the Date object to convert.
 * @returns {integer} microseconds from the epoch.
 */
const toPRTime = date => {
  if (typeof date != "number" && date.constructor.name != "Date") {
    throw new Error("Invalid value passed to toPRTime");
  }
  return date * 1000;
};

const makeRange = options => {
  return options.since == null
    ? null
    : [toPRTime(options.since), toPRTime(Date.now())];
};
global.makeRange = makeRange;

// General implementation for clearing data using Services.clearData.
// Currently Sanitizer.items uses this under the hood.
async function clearData(options, flags) {
  if (options.hostnames) {
    await Promise.all(
      options.hostnames.map(
        host =>
          new Promise(resolve => {
            // Set aIsUserRequest to true. This means when the ClearDataService
            // "Cleaner" implementation doesn't support clearing by host
            // it will delete all data instead.
            // This is appropriate for cases like |cache|, which doesn't
            // support clearing by a time range.
            // In future when we use this for other data types, we have to
            // evaluate if that behavior is still acceptable.
            Services.clearData.deleteDataFromHost(host, true, flags, resolve);
          })
      )
    );
    return;
  }

  if (options.since) {
    const range = makeRange(options);
    await new Promise(resolve => {
      Services.clearData.deleteDataInTimeRange(...range, true, flags, resolve);
    });
    return;
  }

  // Don't return the promise here and above to prevent leaking the resolved
  // value.
  await new Promise(resolve => Services.clearData.deleteData(flags, resolve));
}

const clearCache = options => {
  return clearData(options, Ci.nsIClearDataService.CLEAR_ALL_CACHES);
};

const clearCookies = async function (options) {
  let cookieMgr = Services.cookies;
  // This code has been borrowed from Sanitizer.sys.mjs.
  let yieldCounter = 0;

  if (options.since || options.hostnames || options.cookieStoreId) {
    // Iterate through the cookies and delete any created after our cutoff.
    let cookies = cookieMgr.cookies;
    if (
      !options.cookieStoreId ||
      isPrivateCookieStoreId(options.cookieStoreId)
    ) {
      // By default nsICookieManager.cookies doesn't contain private cookies.
      const privateCookies = cookieMgr.getCookiesWithOriginAttributes(
        JSON.stringify({
          privateBrowsingId: 1,
        })
      );
      cookies = cookies.concat(privateCookies);
    }
    for (const cookie of cookies) {
      if (
        (!options.since || cookie.creationTime >= toPRTime(options.since)) &&
        (!options.hostnames ||
          options.hostnames.includes(cookie.host.replace(/^\./, ""))) &&
        (!options.cookieStoreId ||
          getCookieStoreIdForOriginAttributes(cookie.originAttributes) ===
            options.cookieStoreId)
      ) {
        // This cookie was created after our cutoff, clear it.
        cookieMgr.remove(
          cookie.host,
          cookie.name,
          cookie.path,
          cookie.originAttributes
        );

        if (++yieldCounter % YIELD_PERIOD == 0) {
          await new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long.
        }
      }
    }
  } else {
    // Remove everything.
    cookieMgr.removeAll();
  }
};

// Ideally we could reuse the logic in Sanitizer.sys.mjs or nsIClearDataService,
// but this API exposes an ability to wipe data at a much finger granularity
// than those APIs. (See also Bug 1531276)
async function clearQuotaManager(options, dataType) {
  // Can not clear localStorage/indexedDB in private browsing mode,
  // just ignore.
  if (options.cookieStoreId == PRIVATE_STORE) {
    return;
  }

  let promises = [];
  await new Promise((resolve, reject) => {
    Services.qms.getUsage(request => {
      if (request.resultCode != Cr.NS_OK) {
        reject({ message: `Clear ${dataType} failed` });
        return;
      }

      for (let item of request.result) {
        let principal =
          Services.scriptSecurityManager.createContentPrincipalFromOrigin(
            item.origin
          );

        // Consistently to removeIndexedDB and the API documentation for
        // removeLocalStorage, we should only clear the data stored by
        // regular websites, on the contrary we shouldn't clear data stored
        // by browser components (like about:newtab) or other extensions.
        if (!["http", "https", "file"].includes(principal.scheme)) {
          continue;
        }

        let host = principal.hostPort;
        if (
          (!options.hostnames || options.hostnames.includes(host)) &&
          (!options.cookieStoreId ||
            getCookieStoreIdForOriginAttributes(principal.originAttributes) ===
              options.cookieStoreId)
        ) {
          promises.push(
            new Promise((resolve, reject) => {
              let clearRequest;
              if (dataType === "indexedDB") {
                clearRequest = Services.qms.clearStoragesForPrincipal(
                  principal,
                  null,
                  "idb"
                );
              } else {
                clearRequest = Services.qms.clearStoragesForPrincipal(
                  principal,
                  "default",
                  "ls"
                );
              }

              clearRequest.callback = () => {
                if (clearRequest.resultCode == Cr.NS_OK) {
                  resolve();
                } else {
                  reject({ message: `Clear ${dataType} failed` });
                }
              };
            })
          );
        }
      }

      resolve();
    });
  });

  return Promise.all(promises);
}

const clearIndexedDB = async function (options) {
  return clearQuotaManager(options, "indexedDB");
};

const clearLocalStorage = async function (options) {
  if (options.since) {
    return Promise.reject({
      message: "Firefox does not support clearing localStorage with 'since'.",
    });
  }

  // The legacy LocalStorage implementation that will eventually be removed
  // depends on this observer notification.  Some other subsystems like
  // Reporting headers depend on this too.
  // When NextGenLocalStorage is enabled these notifications are ignored.
  if (options.hostnames) {
    for (let hostname of options.hostnames) {
      Services.obs.notifyObservers(
        null,
        "extension:purge-localStorage",
        hostname
      );
    }
  } else {
    Services.obs.notifyObservers(null, "extension:purge-localStorage");
  }

  if (Services.domStorageManager.nextGenLocalStorageEnabled) {
    return clearQuotaManager(options, "localStorage");
  }
};

const clearPasswords = async function (options) {
  let yieldCounter = 0;

  // Iterate through the logins and delete any updated after our cutoff.
  for (let login of await LoginHelper.getAllUserFacingLogins()) {
    login.QueryInterface(Ci.nsILoginMetaInfo);
    if (!options.since || login.timePasswordChanged >= options.since) {
      Services.logins.removeLogin(login);
      if (++yieldCounter % YIELD_PERIOD == 0) {
        await new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long.
      }
    }
  }
};

const clearServiceWorkers = options => {
  if (!options.hostnames) {
    return ServiceWorkerCleanUp.removeAll();
  }

  return Promise.all(
    options.hostnames.map(host => {
      return ServiceWorkerCleanUp.removeFromHost(host);
    })
  );
};

class BrowsingDataImpl {
  constructor(extension) {
    this.extension = extension;
    // Some APIs cannot implement in a platform-independent way and they are
    // delegated to a platform-specific delegate.
    this.platformDelegate = new BrowsingDataDelegate(extension);
  }

  handleRemoval(dataType, options) {
    // First, let's see if the platform implements this
    let result = this.platformDelegate.handleRemoval(dataType, options);
    if (result !== undefined) {
      return result;
    }

    // ... if not, run the default behavior.
    switch (dataType) {
      case "cache":
        return clearCache(options);
      case "cookies":
        return clearCookies(options);
      case "indexedDB":
        return clearIndexedDB(options);
      case "localStorage":
        return clearLocalStorage(options);
      case "passwords":
        return clearPasswords(options);
      case "pluginData":
        this.extension?.logger.warn(
          "pluginData has been deprecated (along with Flash plugin support)"
        );
        return Promise.resolve();
      case "serviceWorkers":
        return clearServiceWorkers(options);
      default:
        return undefined;
    }
  }

  doRemoval(options, dataToRemove) {
    if (
      options.originTypes &&
      (options.originTypes.protectedWeb || options.originTypes.extension)
    ) {
      return Promise.reject({
        message:
          "Firefox does not support protectedWeb or extension as originTypes.",
      });
    }

    if (options.cookieStoreId) {
      const SUPPORTED_TYPES = ["cookies", "indexedDB"];
      if (Services.domStorageManager.nextGenLocalStorageEnabled) {
        // Only the next-gen storage supports removal by cookieStoreId.
        SUPPORTED_TYPES.push("localStorage");
      }

      for (let dataType in dataToRemove) {
        if (dataToRemove[dataType] && !SUPPORTED_TYPES.includes(dataType)) {
          return Promise.reject({
            message: `Firefox does not support clearing ${dataType} with 'cookieStoreId'.`,
          });
        }
      }

      if (
        !isPrivateCookieStoreId(options.cookieStoreId) &&
        !isDefaultCookieStoreId(options.cookieStoreId) &&
        !getContainerForCookieStoreId(options.cookieStoreId)
      ) {
        return Promise.reject({
          message: `Invalid cookieStoreId: ${options.cookieStoreId}`,
        });
      }
    }

    let removalPromises = [];
    let invalidDataTypes = [];
    for (let dataType in dataToRemove) {
      if (dataToRemove[dataType]) {
        let result = this.handleRemoval(dataType, options);
        if (result === undefined) {
          invalidDataTypes.push(dataType);
        } else {
          removalPromises.push(result);
        }
      }
    }
    if (invalidDataTypes.length) {
      this.extension.logger.warn(
        `Firefox does not support dataTypes: ${invalidDataTypes.toString()}.`
      );
    }
    return Promise.all(removalPromises);
  }

  settings() {
    return this.platformDelegate.settings();
  }
}

this.browsingData = class extends ExtensionAPI {
  getAPI(context) {
    const impl = new BrowsingDataImpl(context.extension);
    return {
      browsingData: {
        settings() {
          return impl.settings();
        },
        remove(options, dataToRemove) {
          return impl.doRemoval(options, dataToRemove);
        },
        removeCache(options) {
          return impl.doRemoval(options, { cache: true });
        },
        removeCookies(options) {
          return impl.doRemoval(options, { cookies: true });
        },
        removeDownloads(options) {
          return impl.doRemoval(options, { downloads: true });
        },
        removeFormData(options) {
          return impl.doRemoval(options, { formData: true });
        },
        removeHistory(options) {
          return impl.doRemoval(options, { history: true });
        },
        removeIndexedDB(options) {
          return impl.doRemoval(options, { indexedDB: true });
        },
        removeLocalStorage(options) {
          return impl.doRemoval(options, { localStorage: true });
        },
        removePasswords(options) {
          return impl.doRemoval(options, { passwords: true });
        },
        removePluginData(options) {
          return impl.doRemoval(options, { pluginData: true });
        },
      },
    };
  }
};
PK
!<���33=chrome/toolkit/content/extensions/parent/ext-captivePortal.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

XPCOMUtils.defineLazyServiceGetter(
  this,
  "gCPS",
  "@mozilla.org/network/captive-portal-service;1",
  "nsICaptivePortalService"
);

XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "gCaptivePortalEnabled",
  "network.captive-portal-service.enabled",
  false
);

var { ExtensionPreferencesManager } = ChromeUtils.importESModule(
  "resource://gre/modules/ExtensionPreferencesManager.sys.mjs"
);

var { getSettingsAPI } = ExtensionPreferencesManager;

const CAPTIVE_URL_PREF = "captivedetect.canonicalURL";

var { ExtensionError } = ExtensionUtils;

this.captivePortal = class extends ExtensionAPIPersistent {
  checkCaptivePortalEnabled() {
    if (!gCaptivePortalEnabled) {
      throw new ExtensionError("Captive Portal detection is not enabled");
    }
  }

  nameForCPSState(state) {
    switch (state) {
      case gCPS.UNKNOWN:
        return "unknown";
      case gCPS.NOT_CAPTIVE:
        return "not_captive";
      case gCPS.UNLOCKED_PORTAL:
        return "unlocked_portal";
      case gCPS.LOCKED_PORTAL:
        return "locked_portal";
      default:
        return "unknown";
    }
  }

  PERSISTENT_EVENTS = {
    onStateChanged({ fire }) {
      this.checkCaptivePortalEnabled();

      let observer = () => {
        fire.async({ state: this.nameForCPSState(gCPS.state) });
      };

      Services.obs.addObserver(
        observer,
        "ipc:network:captive-portal-set-state"
      );
      return {
        unregister: () => {
          Services.obs.removeObserver(
            observer,
            "ipc:network:captive-portal-set-state"
          );
        },
        convert(_fire) {
          fire = _fire;
        },
      };
    },
    onConnectivityAvailable({ fire }) {
      this.checkCaptivePortalEnabled();

      let observer = (subject, topic, data) => {
        fire.async({ status: data });
      };

      Services.obs.addObserver(observer, "network:captive-portal-connectivity");
      return {
        unregister: () => {
          Services.obs.removeObserver(
            observer,
            "network:captive-portal-connectivity"
          );
        },
        convert(_fire) {
          fire = _fire;
        },
      };
    },
    "captiveURL.onChange": ({ fire }) => {
      let listener = () => {
        fire.async({
          levelOfControl: "not_controllable",
          value: Services.prefs.getStringPref(CAPTIVE_URL_PREF),
        });
      };
      Services.prefs.addObserver(CAPTIVE_URL_PREF, listener);
      return {
        unregister: () => {
          Services.prefs.removeObserver(CAPTIVE_URL_PREF, listener);
        },
        convert(_fire) {
          fire = _fire;
        },
      };
    },
  };

  getAPI(context) {
    let self = this;
    return {
      captivePortal: {
        getState() {
          self.checkCaptivePortalEnabled();
          return self.nameForCPSState(gCPS.state);
        },
        getLastChecked() {
          self.checkCaptivePortalEnabled();
          return gCPS.lastChecked;
        },
        onStateChanged: new EventManager({
          context,
          module: "captivePortal",
          event: "onStateChanged",
          extensionApi: self,
        }).api(),
        onConnectivityAvailable: new EventManager({
          context,
          module: "captivePortal",
          event: "onConnectivityAvailable",
          extensionApi: self,
        }).api(),
        canonicalURL: getSettingsAPI({
          context,
          name: "captiveURL",
          callback() {
            return Services.prefs.getStringPref(CAPTIVE_URL_PREF);
          },
          readOnly: true,
          onChange: new ExtensionCommon.EventManager({
            context,
            module: "captivePortal",
            event: "captiveURL.onChange",
            extensionApi: self,
          }).api(),
        }),
      },
    };
  }
};
PK
!<;3��s
s
9chrome/toolkit/content/extensions/parent/ext-clipboard.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

XPCOMUtils.defineLazyServiceGetter(
  this,
  "imgTools",
  "@mozilla.org/image/tools;1",
  "imgITools"
);

const Transferable = Components.Constructor(
  "@mozilla.org/widget/transferable;1",
  "nsITransferable"
);

this.clipboard = class extends ExtensionAPI {
  getAPI() {
    return {
      clipboard: {
        async setImageData(imageData, imageType) {
          if (AppConstants.platform == "android") {
            return Promise.reject({
              message:
                "Writing images to the clipboard is not supported on Android",
            });
          }
          let img;
          try {
            img = imgTools.decodeImageFromArrayBuffer(
              imageData,
              `image/${imageType}`
            );
          } catch (e) {
            return Promise.reject({
              message: `Data is not a valid ${imageType} image`,
            });
          }

          // Other applications can only access the copied image once the data
          // is exported via the platform-specific clipboard APIs:
          // nsClipboard::SelectionGetEvent (widget/gtk/nsClipboard.cpp)
          // nsClipboard::PasteDictFromTransferable (widget/cocoa/nsClipboard.mm)
          // nsDataObj::GetDib (widget/windows/nsDataObj.cpp)
          //
          // The common protocol for exporting a nsITransferable as an image is:
          // - Use nsITransferable::GetTransferData to fetch the stored data.
          // - QI imgIContainer on the pointer.
          // - Convert the image to the native clipboard format.
          //
          // Below we create a nsITransferable in the above format.
          let transferable = new Transferable();
          transferable.init(null);
          const kNativeImageMime = "application/x-moz-nativeimage";
          transferable.addDataFlavor(kNativeImageMime);

          // Internal consumers expect the image data to be stored as a
          // nsIInputStream. On Linux and Windows, pasted data is directly
          // retrieved from the system's native clipboard, and made available
          // as a nsIInputStream.
          //
          // On macOS, nsClipboard::GetNativeClipboardData (nsClipboard.mm) uses
          // a cached copy of nsITransferable if available, e.g. when the copy
          // was initiated by the same browser instance. To make sure that a
          // nsIInputStream is returned instead of the cached imgIContainer,
          // the image is exported as as `kNativeImageMime`. Data associated
          // with this type is converted to a platform-specific image format
          // when written to the clipboard. The type is not used when images
          // are read from the clipboard (on all platforms, not just macOS).
          // This forces nsClipboard::GetNativeClipboardData to fall back to
          // the native clipboard, and return the image as a nsITransferable.
          transferable.setTransferData(kNativeImageMime, img);

          Services.clipboard.setData(
            transferable,
            null,
            Services.clipboard.kGlobalClipboard
          );
        },
      },
    };
  }
};
PK
!<���i��>chrome/toolkit/content/extensions/parent/ext-contentScripts.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var { ExtensionUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/ExtensionUtils.sys.mjs"
);

var { ExtensionError, getUniqueId } = ExtensionUtils;

function getOriginAttributesPatternForCookieStoreId(cookieStoreId) {
  if (isDefaultCookieStoreId(cookieStoreId)) {
    return {
      userContextId: Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID,
      privateBrowsingId:
        Ci.nsIScriptSecurityManager.DEFAULT_PRIVATE_BROWSING_ID,
    };
  }
  if (isPrivateCookieStoreId(cookieStoreId)) {
    return {
      userContextId: Ci.nsIScriptSecurityManager.DEFAULT_USER_CONTEXT_ID,
      privateBrowsingId: 1,
    };
  }
  if (isContainerCookieStoreId(cookieStoreId)) {
    let userContextId = getContainerForCookieStoreId(cookieStoreId);
    if (userContextId !== null) {
      return { userContextId };
    }
  }

  throw new ExtensionError("Invalid cookieStoreId");
}

/**
 * Represents (in the main browser process) a content script registered
 * programmatically (instead of being included in the addon manifest).
 *
 * @param {ProxyContextParent} context
 *        The parent proxy context related to the extension context which
 *        has registered the content script.
 * @param {RegisteredContentScriptOptions} details
 *        The options object related to the registered content script
 *        (which has the properties described in the content_scripts.json
 *        JSON API schema file).
 */
class ContentScriptParent {
  constructor({ context, details }) {
    this.context = context;
    this.scriptId = getUniqueId();
    this.blobURLs = new Set();

    this.options = this._convertOptions(details);

    context.callOnClose(this);
  }

  close() {
    this.destroy();
  }

  destroy() {
    if (this.destroyed) {
      throw new Error("Unable to destroy ContentScriptParent twice");
    }

    this.destroyed = true;

    this.context.forgetOnClose(this);

    for (const blobURL of this.blobURLs) {
      this.context.cloneScope.URL.revokeObjectURL(blobURL);
    }

    this.blobURLs.clear();

    this.context = null;
    this.options = null;
  }

  _convertOptions(details) {
    const { context } = this;

    const options = {
      matches: details.matches,
      excludeMatches: details.excludeMatches,
      includeGlobs: details.includeGlobs,
      excludeGlobs: details.excludeGlobs,
      allFrames: details.allFrames,
      matchAboutBlank: details.matchAboutBlank,
      matchOriginAsFallback: details.matchOriginAsFallback,
      runAt: details.runAt || "document_idle",
      world: details.world || "ISOLATED",
      jsPaths: [],
      cssPaths: [],
      originAttributesPatterns: null,
    };

    if (details.cookieStoreId != null) {
      const cookieStoreIds = Array.isArray(details.cookieStoreId)
        ? details.cookieStoreId
        : [details.cookieStoreId];
      options.originAttributesPatterns = cookieStoreIds.map(cookieStoreId =>
        getOriginAttributesPatternForCookieStoreId(cookieStoreId)
      );
    }

    const convertCodeToURL = (data, mime) => {
      const blob = new context.cloneScope.Blob(data, { type: mime });
      const blobURL = context.cloneScope.URL.createObjectURL(blob);

      this.blobURLs.add(blobURL);

      return blobURL;
    };

    if (details.js && details.js.length) {
      options.jsPaths = details.js.map(data => {
        if (data.file) {
          return data.file;
        }

        return convertCodeToURL([data.code], "text/javascript");
      });
    }

    if (details.css && details.css.length) {
      options.cssPaths = details.css.map(data => {
        if (data.file) {
          return data.file;
        }

        return convertCodeToURL([data.code], "text/css");
      });
    }

    return options;
  }

  serialize() {
    return this.options;
  }
}

this.contentScripts = class extends ExtensionAPI {
  getAPI(context) {
    const { extension } = context;

    // Map of the content script registered from the extension context.
    //
    // Map<scriptId -> ContentScriptParent>
    const parentScriptsMap = new Map();

    // Unregister all the scriptId related to a context when it is closed.
    context.callOnClose({
      close() {
        if (parentScriptsMap.size === 0) {
          return;
        }

        const scriptIds = Array.from(parentScriptsMap.keys());

        for (let scriptId of scriptIds) {
          extension.registeredContentScripts.delete(scriptId);
        }
        extension.updateContentScripts();

        extension.broadcast("Extension:UnregisterContentScripts", {
          id: extension.id,
          scriptIds,
        });
      },
    });

    return {
      contentScripts: {
        async register(details) {
          for (let origin of details.matches) {
            if (!extension.allowedOrigins.subsumes(new MatchPattern(origin))) {
              throw new ExtensionError(
                `Permission denied to register a content script for ${origin}`
              );
            }
          }

          const contentScript = new ContentScriptParent({ context, details });
          const { scriptId } = contentScript;

          parentScriptsMap.set(scriptId, contentScript);

          const scriptOptions = contentScript.serialize();

          extension.registeredContentScripts.set(scriptId, scriptOptions);
          extension.updateContentScripts();

          await extension.broadcast("Extension:RegisterContentScripts", {
            id: extension.id,
            scripts: [{ scriptId, options: scriptOptions }],
          });

          return scriptId;
        },

        // This method is not available to the extension code, the extension code
        // doesn't have access to the internally used scriptId, on the contrary
        // the extension code will call script.unregister on the script API object
        // that is resolved from the register API method returned promise.
        async unregister(scriptId) {
          const contentScript = parentScriptsMap.get(scriptId);
          if (!contentScript) {
            Cu.reportError(new Error(`No such content script ID: ${scriptId}`));

            return;
          }

          parentScriptsMap.delete(scriptId);
          extension.registeredContentScripts.delete(scriptId);
          extension.updateContentScripts();

          contentScript.destroy();

          await extension.broadcast("Extension:UnregisterContentScripts", {
            id: extension.id,
            scriptIds: [scriptId],
          });
        },
      },
    };
  }
};
PK
!</��W7'7'Dchrome/toolkit/content/extensions/parent/ext-contextualIdentities.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

ChromeUtils.defineESModuleGetters(this, {
  ContextualIdentityService:
    "resource://gre/modules/ContextualIdentityService.sys.mjs",
});
XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "containersEnabled",
  "privacy.userContext.enabled"
);

var { ExtensionPreferencesManager } = ChromeUtils.importESModule(
  "resource://gre/modules/ExtensionPreferencesManager.sys.mjs"
);

var { ExtensionError } = ExtensionUtils;

const CONTAINER_PREF_INSTALL_DEFAULTS = {
  "privacy.userContext.extension": undefined,
};

const CONTAINERS_ENABLED_SETTING_NAME = "privacy.containers";

const CONTAINER_COLORS = new Map([
  ["blue", "#37adff"],
  ["turquoise", "#00c79a"],
  ["green", "#51cd00"],
  ["yellow", "#ffcb00"],
  ["orange", "#ff9f00"],
  ["red", "#ff613d"],
  ["pink", "#ff4bda"],
  ["purple", "#af51f5"],
  ["toolbar", "#7c7c7d"],
]);

const CONTAINER_ICONS = new Set([
  "briefcase",
  "cart",
  "circle",
  "dollar",
  "fence",
  "fingerprint",
  "gift",
  "vacation",
  "food",
  "fruit",
  "pet",
  "tree",
  "chill",
]);

function getContainerIcon(iconName) {
  if (!CONTAINER_ICONS.has(iconName)) {
    throw new ExtensionError(`Invalid icon ${iconName} for container`);
  }
  return `resource://usercontext-content/${iconName}.svg`;
}

function getContainerColor(colorName) {
  if (!CONTAINER_COLORS.has(colorName)) {
    throw new ExtensionError(`Invalid color name ${colorName} for container`);
  }
  return CONTAINER_COLORS.get(colorName);
}

const convertIdentity = identity => {
  let result = {
    name: ContextualIdentityService.getUserContextLabel(identity.userContextId),
    icon: identity.icon,
    iconUrl: getContainerIcon(identity.icon),
    color: identity.color,
    colorCode: getContainerColor(identity.color),
    cookieStoreId: getCookieStoreIdForContainer(identity.userContextId),
  };

  return result;
};

const checkAPIEnabled = () => {
  if (!containersEnabled) {
    throw new ExtensionError("Contextual identities are currently disabled");
  }
};

const convertIdentityFromObserver = wrappedIdentity => {
  let identity = wrappedIdentity.wrappedJSObject;
  let iconUrl, colorCode;
  try {
    iconUrl = getContainerIcon(identity.icon);
    colorCode = getContainerColor(identity.color);
  } catch (e) {
    return null;
  }

  let result = {
    name: identity.name,
    icon: identity.icon,
    iconUrl,
    color: identity.color,
    colorCode,
    cookieStoreId: getCookieStoreIdForContainer(identity.userContextId),
  };

  return result;
};

ExtensionPreferencesManager.addSetting(CONTAINERS_ENABLED_SETTING_NAME, {
  prefNames: Object.keys(CONTAINER_PREF_INSTALL_DEFAULTS),

  setCallback(value) {
    if (value !== true) {
      return {
        ...CONTAINER_PREF_INSTALL_DEFAULTS,
        "privacy.userContext.extension": value,
      };
    }
    return {};
  },
});

this.contextualIdentities = class extends ExtensionAPIPersistent {
  eventRegistrar(eventName) {
    return ({ fire }) => {
      let observer = subject => {
        let convertedIdentity = convertIdentityFromObserver(subject);
        if (convertedIdentity) {
          fire.async({ contextualIdentity: convertedIdentity });
        }
      };

      Services.obs.addObserver(observer, eventName);
      return {
        unregister() {
          Services.obs.removeObserver(observer, eventName);
        },
        convert(_fire) {
          fire = _fire;
        },
      };
    };
  }

  PERSISTENT_EVENTS = {
    onCreated: this.eventRegistrar("contextual-identity-created"),
    onUpdated: this.eventRegistrar("contextual-identity-updated"),
    onRemoved: this.eventRegistrar("contextual-identity-deleted"),
  };

  onStartup() {
    let { extension } = this;

    if (extension.hasPermission("contextualIdentities")) {
      // Turn on contextual identities, and never turn it off.  We handle
      // this here to ensure prefs are set when an addon is enabled.
      Services.prefs.setBoolPref("privacy.userContext.enabled", true);
      Services.prefs.setBoolPref("privacy.userContext.ui.enabled", true);

      ExtensionPreferencesManager.setSetting(
        extension.id,
        CONTAINERS_ENABLED_SETTING_NAME,
        extension.id
      );
    }
  }

  getAPI(context) {
    let self = {
      contextualIdentities: {
        async get(cookieStoreId) {
          checkAPIEnabled();
          let containerId = getContainerForCookieStoreId(cookieStoreId);
          if (!containerId) {
            throw new ExtensionError(
              `Invalid contextual identity: ${cookieStoreId}`
            );
          }

          let identity =
            ContextualIdentityService.getPublicIdentityFromId(containerId);
          return convertIdentity(identity);
        },

        async query(details) {
          checkAPIEnabled();
          let identities = [];
          ContextualIdentityService.getPublicIdentities().forEach(identity => {
            if (
              details.name &&
              ContextualIdentityService.getUserContextLabel(
                identity.userContextId
              ) != details.name
            ) {
              return;
            }

            identities.push(convertIdentity(identity));
          });

          return identities;
        },

        async create(details) {
          // Lets prevent making containers that are not valid
          getContainerIcon(details.icon);
          getContainerColor(details.color);

          let identity = ContextualIdentityService.create(
            details.name,
            details.icon,
            details.color
          );
          return convertIdentity(identity);
        },

        async update(cookieStoreId, details) {
          checkAPIEnabled();
          let containerId = getContainerForCookieStoreId(cookieStoreId);
          if (!containerId) {
            throw new ExtensionError(
              `Invalid contextual identity: ${cookieStoreId}`
            );
          }

          let identity =
            ContextualIdentityService.getPublicIdentityFromId(containerId);
          if (!identity) {
            throw new ExtensionError(
              `Invalid contextual identity: ${cookieStoreId}`
            );
          }

          if (details.name !== null) {
            identity.name = details.name;
          }

          if (details.color !== null) {
            getContainerColor(details.color);
            identity.color = details.color;
          }

          if (details.icon !== null) {
            getContainerIcon(details.icon);
            identity.icon = details.icon;
          }

          if (
            !ContextualIdentityService.update(
              identity.userContextId,
              identity.name,
              identity.icon,
              identity.color
            )
          ) {
            throw new ExtensionError(
              `Contextual identity failed to update: ${cookieStoreId}`
            );
          }

          return convertIdentity(identity);
        },

        async move(cookieStoreIds, position) {
          checkAPIEnabled();
          if (!Array.isArray(cookieStoreIds)) {
            cookieStoreIds = [cookieStoreIds];
          }

          if (!cookieStoreIds.length) {
            return;
          }

          const totalIds =
            ContextualIdentityService.getPublicIdentities().length;
          if (position < -1 || position > totalIds - cookieStoreIds.length) {
            throw new ExtensionError(`Moving to invalid position ${position}`);
          }

          let userContextIds = [];
          cookieStoreIds.forEach((cookieStoreId, index) => {
            if (cookieStoreIds.indexOf(cookieStoreId) !== index) {
              throw new ExtensionError(
                `Duplicate contextual identity: ${cookieStoreId}`
              );
            }

            let containerId = getContainerForCookieStoreId(cookieStoreId);
            if (!containerId) {
              throw new ExtensionError(
                `Invalid contextual identity: ${cookieStoreId}`
              );
            }

            userContextIds.push(containerId);
          });

          if (!ContextualIdentityService.move(userContextIds, position)) {
            throw new ExtensionError(
              `Contextual identities failed to move: ${cookieStoreIds}`
            );
          }
        },

        async remove(cookieStoreId) {
          checkAPIEnabled();
          let containerId = getContainerForCookieStoreId(cookieStoreId);
          if (!containerId) {
            throw new ExtensionError(
              `Invalid contextual identity: ${cookieStoreId}`
            );
          }

          let identity =
            ContextualIdentityService.getPublicIdentityFromId(containerId);
          if (!identity) {
            throw new ExtensionError(
              `Invalid contextual identity: ${cookieStoreId}`
            );
          }

          // We have to create the identity object before removing it.
          let convertedIdentity = convertIdentity(identity);

          if (!ContextualIdentityService.remove(identity.userContextId)) {
            throw new ExtensionError(
              `Contextual identity failed to remove: ${cookieStoreId}`
            );
          }

          return convertedIdentity;
        },

        onCreated: new EventManager({
          context,
          module: "contextualIdentities",
          event: "onCreated",
          extensionApi: this,
        }).api(),

        onUpdated: new EventManager({
          context,
          module: "contextualIdentities",
          event: "onUpdated",
          extensionApi: this,
        }).api(),

        onRemoved: new EventManager({
          context,
          module: "contextualIdentities",
          event: "onRemoved",
          extensionApi: this,
        }).api(),
      },
    };

    return self;
  }
};
PK
!<nZ�wZwZ7chrome/toolkit/content/extensions/parent/ext-cookies.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/* globals DEFAULT_STORE, PRIVATE_STORE */

var { ExtensionError } = ExtensionUtils;

const SAME_SITE_STATUSES = [
  "no_restriction", // Index 0 = Ci.nsICookie.SAMESITE_NONE
  "lax", // Index 1 = Ci.nsICookie.SAMESITE_LAX
  "strict", // Index 2 = Ci.nsICookie.SAMESITE_STRICT
];

const isIPv4 = host => {
  let match = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/.exec(host);

  if (match) {
    return match[1] < 256 && match[2] < 256 && match[3] < 256 && match[4] < 256;
  }
  return false;
};
const isIPv6 = host => host.includes(":");
const addBracketIfIPv6 = host =>
  isIPv6(host) && !host.startsWith("[") ? `[${host}]` : host;
const dropBracketIfIPv6 = host =>
  isIPv6(host) && host.startsWith("[") && host.endsWith("]")
    ? host.slice(1, -1)
    : host;

// Converts the partitionKey format of the extension API (i.e. PartitionKey) to
// a valid format for the "partitionKey" member of OriginAttributes.
function fromExtPartitionKey(extPartitionKey) {
  if (!extPartitionKey) {
    // Unpartitioned by default.
    return "";
  }
  const { topLevelSite } = extPartitionKey;
  // TODO: Expand API to force the generation of a partitionKey that differs
  // from the default that's specified by privacy.dynamic_firstparty.use_site.
  if (topLevelSite) {
    // If topLevelSite is set and a non-empty string (a site in a URL format).
    try {
      return ChromeUtils.getPartitionKeyFromURL(topLevelSite);
    } catch (e) {
      throw new ExtensionError("Invalid value for 'partitionKey' attribute");
    }
  }
  // Unpartitioned.
  return "";
}
// Converts an internal partitionKey (format used by OriginAttributes) to the
// string value as exposed through the extension API.
function toExtPartitionKey(partitionKey) {
  if (!partitionKey) {
    // Canonical representation of an empty partitionKey is null.
    // In theory {topLevelSite: ""} also works, but alas.
    return null;
  }
  // Parse partitionKey in order to generate the desired return type (URL).
  // OriginAttributes::ParsePartitionKey cannot be used because it assumes that
  // the input matches the format of the privacy.dynamic_firstparty.use_site
  // pref, which is not necessarily the case for cookies before the pref flip.
  if (!partitionKey.startsWith("(")) {
    // A partitionKey generated with privacy.dynamic_firstparty.use_site=false.
    return { topLevelSite: `https://${partitionKey}` };
  }
  // partitionKey starts with "(" and ends with ")".
  let [scheme, domain, port] = partitionKey.slice(1, -1).split(",");
  let topLevelSite = `${scheme}://${domain}`;
  if (port) {
    topLevelSite += `:${port}`;
  }
  return { topLevelSite };
}

const convertCookie = ({ cookie, isPrivate }) => {
  let result = {
    name: cookie.name,
    value: cookie.value,
    domain: addBracketIfIPv6(cookie.host),
    hostOnly: !cookie.isDomain,
    path: cookie.path,
    secure: cookie.isSecure,
    httpOnly: cookie.isHttpOnly,
    sameSite: SAME_SITE_STATUSES[cookie.sameSite],
    session: cookie.isSession,
    firstPartyDomain: cookie.originAttributes.firstPartyDomain || "",
    partitionKey: toExtPartitionKey(cookie.originAttributes.partitionKey),
  };

  if (!cookie.isSession) {
    result.expirationDate = cookie.expiry;
  }

  if (cookie.originAttributes.userContextId) {
    result.storeId = getCookieStoreIdForContainer(
      cookie.originAttributes.userContextId
    );
  } else if (cookie.originAttributes.privateBrowsingId || isPrivate) {
    result.storeId = PRIVATE_STORE;
  } else {
    result.storeId = DEFAULT_STORE;
  }

  return result;
};

const isSubdomain = (otherDomain, baseDomain) => {
  return otherDomain == baseDomain || otherDomain.endsWith("." + baseDomain);
};

// Checks that the given extension has permission to set the given cookie for
// the given URI.
const checkSetCookiePermissions = (extension, uri, cookie) => {
  // Permission checks:
  //
  //  - If the extension does not have permissions for the specified
  //    URL, it cannot set cookies for it.
  //
  //  - If the specified URL could not set the given cookie, neither can
  //    the extension.
  //
  // Ideally, we would just have the cookie service make the latter
  // determination, but that turns out to be quite complicated. At the
  // moment, it requires constructing a cookie string and creating a
  // dummy channel, both of which can be problematic. It also triggers
  // a whole set of additional permission and preference checks, which
  // may or may not be desirable.
  //
  // So instead, we do a similar set of checks here. Exactly what
  // cookies a given URL should be able to set is not well-documented,
  // and is not standardized in any standard that anyone actually
  // follows. So instead, we follow the rules used by the cookie
  // service.
  //
  // See source/netwerk/cookie/CookieService.cpp, in particular
  // CheckDomain() and SetCookieInternal().

  if (uri.scheme != "http" && uri.scheme != "https") {
    return false;
  }

  if (!extension.allowedOrigins.matches(uri)) {
    return false;
  }

  if (!cookie.host) {
    // If no explicit host is specified, this becomes a host-only cookie.
    cookie.host = uri.host;
    return true;
  }

  // A leading "." is not expected, but is tolerated if it's not the only
  // character in the host. If there is one, start by stripping it off. We'll
  // add a new one on success.
  if (cookie.host.length > 1) {
    cookie.host = cookie.host.replace(/^\./, "");
  }
  cookie.host = cookie.host.toLowerCase();
  cookie.host = dropBracketIfIPv6(cookie.host);

  if (cookie.host != uri.host) {
    // Not an exact match, so check for a valid subdomain.
    let baseDomain;
    try {
      baseDomain = Services.eTLD.getBaseDomain(uri);
    } catch (e) {
      if (
        e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS ||
        e.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
      ) {
        // The cookie service uses these to determine whether the domain
        // requires an exact match. We already know we don't have an exact
        // match, so return false. In all other cases, re-raise the error.
        return false;
      }
      throw e;
    }

    // The cookie domain must be a subdomain of the base domain. This prevents
    // us from setting cookies for domains like ".co.uk".
    // The domain of the requesting URL must likewise be a subdomain of the
    // cookie domain. This prevents us from setting cookies for entirely
    // unrelated domains.
    if (
      !isSubdomain(cookie.host, baseDomain) ||
      !isSubdomain(uri.host, cookie.host)
    ) {
      return false;
    }

    // RFC2109 suggests that we may only add cookies for sub-domains 1-level
    // below us, but enforcing that would break the web, so we don't.
  }

  // If the host is an IP address, avoid adding a leading ".".
  // An IP address is not a domain name, and only supports host-only cookies.
  if (isIPv6(cookie.host) || isIPv4(cookie.host)) {
    return true;
  }

  // An explicit domain was passed, so add a leading "." to make this a
  // domain cookie.
  cookie.host = "." + cookie.host;

  // We don't do any significant checking of path permissions. RFC2109
  // suggests we only allow sites to add cookies for sub-paths, similar to
  // same origin policy enforcement, but no-one implements this.

  return true;
};

/**
 * Converts the details received from the cookies API to the OriginAttributes
 * format, using default values when needed (firstPartyDomain/partitionKey).
 *
 * If allowPattern is true, an OriginAttributesPattern may be returned instead.
 *
 * @param {object} details
 *        The details received from the extension.
 * @param {BaseContext} context
 * @param {boolean} allowPattern
 *        Whether to potentially return an OriginAttributesPattern instead of
 *        OriginAttributes. The get/set/remove cookie methods operate on exact
 *        OriginAttributes, the getAll method allows a partial pattern and may
 *        potentially match cookies with distinct origin attributes.
 * @returns {object} An object with the following properties:
 *  - originAttributes {OriginAttributes|OriginAttributesPattern}
 *  - isPattern {boolean} Whether originAttributes is a pattern.
 *  - isPrivate {boolean} Whether the cookie belongs to private browsing mode.
 *  - storeId {string} The storeId of the cookie.
 */
const oaFromDetails = (details, context, allowPattern) => {
  // Default values, may be filled in based on details.
  let originAttributes = {
    userContextId: 0,
    privateBrowsingId: 0,
    // The following two keys may be deleted if allowPattern=true
    firstPartyDomain: details.firstPartyDomain ?? "",
    partitionKey: fromExtPartitionKey(details.partitionKey),
  };

  let isPrivate = context.incognito;
  let storeId = isPrivate ? PRIVATE_STORE : DEFAULT_STORE;
  if (details.storeId) {
    storeId = details.storeId;
    if (isDefaultCookieStoreId(storeId)) {
      isPrivate = false;
    } else if (isPrivateCookieStoreId(storeId)) {
      isPrivate = true;
    } else {
      isPrivate = false;
      let userContextId = getContainerForCookieStoreId(storeId);
      if (!userContextId) {
        throw new ExtensionError(`Invalid cookie store id: "${storeId}"`);
      }
      originAttributes.userContextId = userContextId;
    }
  }

  if (isPrivate) {
    originAttributes.privateBrowsingId = 1;
    if (!context.privateBrowsingAllowed) {
      throw new ExtensionError(
        "Extension disallowed access to the private cookies storeId."
      );
    }
  }

  // If any of the originAttributes's keys are deleted, this becomes true.
  let isPattern = false;
  if (allowPattern) {
    // firstPartyDomain is unset / void / string.
    // If unset, then we default to non-FPI cookies (or if FPI is enabled,
    // an error is thrown by validateFirstPartyDomain). We are able to detect
    // whether the property is set due to "omit-key-if-missing" in cookies.json.
    // If set to a string, we keep the filter.
    // If set to void (undefined / null), we drop the FPI filter:
    if ("firstPartyDomain" in details && details.firstPartyDomain == null) {
      delete originAttributes.firstPartyDomain;
      isPattern = true;
    }

    // partitionKey is an object or null.
    // null implies the default (unpartitioned cookies).
    // An object is a filter for partitionKey; currently we require topLevelSite
    // to be set to determine the exact partitionKey. Without it, we drop the
    // dFPI filter:
    if (details.partitionKey && details.partitionKey.topLevelSite == null) {
      delete originAttributes.partitionKey;
      isPattern = true;
    }
  }
  return { originAttributes, isPattern, isPrivate, storeId };
};

/**
 * Query the cookie store for matching cookies.
 *
 * @param {object} detailsIn
 * @param {Array} props          Properties the extension is interested in matching against.
 *                               The firstPartyDomain / partitionKey / storeId
 *                               props are always accounted for.
 * @param {BaseContext} context  The context making the query.
 * @param {boolean} allowPattern Whether to allow the query to match distinct
 *                               origin attributes instead of falling back to
 *                               default values. See the oaFromDetails method.
 */
const query = function* (detailsIn, props, context, allowPattern) {
  let details = {};
  props.forEach(property => {
    if (detailsIn[property] !== null) {
      details[property] = detailsIn[property];
    }
  });

  let parsedOA;
  try {
    parsedOA = oaFromDetails(detailsIn, context, allowPattern);
  } catch (e) {
    if (e.message.startsWith("Invalid cookie store id")) {
      // For backwards-compatibility with previous versions of Firefox, fail
      // silently (by not returning any results) instead of throwing an error.
      return;
    }
    throw e;
  }
  let { originAttributes, isPattern, isPrivate, storeId } = parsedOA;

  if ("domain" in details) {
    details.domain = details.domain.toLowerCase().replace(/^\./, "");
    details.domain = dropBracketIfIPv6(details.domain);
  }

  // We can use getCookiesFromHost for faster searching.
  let cookies;
  let host;
  let url;
  if ("url" in details) {
    try {
      url = new URL(details.url);
      host = dropBracketIfIPv6(url.hostname);
    } catch (ex) {
      // This often happens for about: URLs
      return;
    }
  } else if ("domain" in details) {
    host = details.domain;
  }

  if (host && !isPattern) {
    // getCookiesFromHost is more efficient than getCookiesWithOriginAttributes
    // if the host and all origin attributes are known.
    cookies = Services.cookies.getCookiesFromHost(host, originAttributes);
  } else {
    cookies = Services.cookies.getCookiesWithOriginAttributes(
      JSON.stringify(originAttributes),
      host
    );
  }

  // Based on CookieService::GetCookieStringFromHttp
  function matches(cookie) {
    function domainMatches(host) {
      return (
        cookie.rawHost == host ||
        (cookie.isDomain && host.endsWith(cookie.host))
      );
    }

    function pathMatches(path) {
      let cookiePath = cookie.path.replace(/\/$/, "");

      if (!path.startsWith(cookiePath)) {
        return false;
      }

      // path == cookiePath, but without the redundant string compare.
      if (path.length == cookiePath.length) {
        return true;
      }

      // URL path is a substring of the cookie path, so it matches if, and
      // only if, the next character is a path delimiter.
      return path[cookiePath.length] === "/";
    }

    // "Restricts the retrieved cookies to those that would match the given URL."
    if (url) {
      if (!domainMatches(host)) {
        return false;
      }

      if (cookie.isSecure && url.protocol != "https:") {
        return false;
      }

      if (!pathMatches(url.pathname)) {
        return false;
      }
    }

    if ("name" in details && details.name != cookie.name) {
      return false;
    }

    // "Restricts the retrieved cookies to those whose domains match or are subdomains of this one."
    if ("domain" in details && !isSubdomain(cookie.rawHost, details.domain)) {
      return false;
    }

    // "Restricts the retrieved cookies to those whose path exactly matches this string.""
    if ("path" in details && details.path != cookie.path) {
      return false;
    }

    if ("secure" in details && details.secure != cookie.isSecure) {
      return false;
    }

    if ("session" in details && details.session != cookie.isSession) {
      return false;
    }

    // Check that the extension has permissions for this host.
    if (!context.extension.allowedOrigins.matchesCookie(cookie)) {
      return false;
    }

    return true;
  }

  for (const cookie of cookies) {
    if (matches(cookie)) {
      yield { cookie, isPrivate, storeId };
    }
  }
};

const validateFirstPartyDomain = details => {
  if (details.firstPartyDomain != null) {
    return;
  }
  if (Services.prefs.getBoolPref("privacy.firstparty.isolate")) {
    throw new ExtensionError(
      "First-Party Isolation is enabled, but the required 'firstPartyDomain' attribute was not set."
    );
  }
};

this.cookies = class extends ExtensionAPIPersistent {
  PERSISTENT_EVENTS = {
    onChanged({ fire }) {
      let observer = (subject, topic) => {
        let notify = (removed, cookie, cause) => {
          cookie.QueryInterface(Ci.nsICookie);

          if (this.extension.allowedOrigins.matchesCookie(cookie)) {
            fire.async({
              removed,
              cookie: convertCookie({
                cookie,
                isPrivate: topic == "private-cookie-changed",
              }),
              cause,
            });
          }
        };

        let notification = subject.QueryInterface(Ci.nsICookieNotification);
        let { cookie } = notification;

        let {
          COOKIE_DELETED,
          COOKIE_ADDED,
          COOKIE_CHANGED,
          COOKIES_BATCH_DELETED,
        } = Ci.nsICookieNotification;

        // We do our best effort here to map the incompatible states.
        switch (notification.action) {
          case COOKIE_DELETED:
            notify(true, cookie, "explicit");
            break;
          case COOKIE_ADDED:
            notify(false, cookie, "explicit");
            break;
          case COOKIE_CHANGED:
            notify(true, cookie, "overwrite");
            notify(false, cookie, "explicit");
            break;
          case COOKIES_BATCH_DELETED:
            let cookieArray = notification.batchDeletedCookies.QueryInterface(
              Ci.nsIArray
            );
            for (let i = 0; i < cookieArray.length; i++) {
              let cookie = cookieArray.queryElementAt(i, Ci.nsICookie);
              if (!cookie.isSession && cookie.expiry * 1000 <= Date.now()) {
                notify(true, cookie, "expired");
              } else {
                notify(true, cookie, "evicted");
              }
            }
            break;
        }
      };

      const { privateBrowsingAllowed } = this.extension;
      Services.obs.addObserver(observer, "cookie-changed");
      if (privateBrowsingAllowed) {
        Services.obs.addObserver(observer, "private-cookie-changed");
      }
      return {
        unregister() {
          Services.obs.removeObserver(observer, "cookie-changed");
          if (privateBrowsingAllowed) {
            Services.obs.removeObserver(observer, "private-cookie-changed");
          }
        },
        convert(_fire) {
          fire = _fire;
        },
      };
    },
  };
  getAPI(context) {
    let { extension } = context;
    let self = {
      cookies: {
        get: function (details) {
          validateFirstPartyDomain(details);

          // TODO bug 1818968: We don't sort by length of path and creation time.
          let allowed = ["url", "name"];
          for (let cookie of query(details, allowed, context)) {
            return Promise.resolve(convertCookie(cookie));
          }

          // Found no match.
          return Promise.resolve(null);
        },

        getAll: function (details) {
          if (!("firstPartyDomain" in details)) {
            // Check and throw an error if firstPartyDomain is required.
            validateFirstPartyDomain(details);
          }

          let allowed = ["url", "name", "domain", "path", "secure", "session"];
          let result = Array.from(
            query(details, allowed, context, /* allowPattern = */ true),
            convertCookie
          );

          return Promise.resolve(result);
        },

        set: function (details) {
          validateFirstPartyDomain(details);
          if (details.firstPartyDomain && details.partitionKey) {
            // FPI and dFPI are mutually exclusive, so it does not make sense
            // to accept non-empty (i.e. non-default) values for both.
            throw new ExtensionError(
              "Partitioned cookies cannot have a 'firstPartyDomain' attribute."
            );
          }

          let uri = Services.io.newURI(details.url);

          let path;
          if (details.path !== null) {
            path = details.path;
          } else {
            // This interface essentially emulates the behavior of the
            // Set-Cookie header. In the case of an omitted path, the cookie
            // service uses the directory path of the requesting URL, ignoring
            // any filename or query parameters.
            path = uri.QueryInterface(Ci.nsIURL).directory;
          }

          let name = details.name !== null ? details.name : "";
          let value = details.value !== null ? details.value : "";
          let secure = details.secure !== null ? details.secure : false;
          let httpOnly = details.httpOnly !== null ? details.httpOnly : false;
          let isSession = details.expirationDate === null;
          let expiry = isSession
            ? Number.MAX_SAFE_INTEGER
            : details.expirationDate;

          let { originAttributes } = oaFromDetails(details, context);

          let cookieAttrs = {
            host: details.domain,
            path: path,
            isSecure: secure,
          };
          if (!checkSetCookiePermissions(extension, uri, cookieAttrs)) {
            return Promise.reject({
              message: `Permission denied to set cookie ${JSON.stringify(
                details
              )}`,
            });
          }

          let sameSite = SAME_SITE_STATUSES.indexOf(details.sameSite);

          let schemeType = Ci.nsICookie.SCHEME_UNSET;
          if (uri.scheme === "https") {
            schemeType = Ci.nsICookie.SCHEME_HTTPS;
          } else if (uri.scheme === "http") {
            schemeType = Ci.nsICookie.SCHEME_HTTP;
          } else if (uri.scheme === "file") {
            schemeType = Ci.nsICookie.SCHEME_FILE;
          }

          // The permission check may have modified the domain, so use
          // the new value instead.
          Services.cookies.add(
            cookieAttrs.host,
            path,
            name,
            value,
            secure,
            httpOnly,
            isSession,
            expiry,
            originAttributes,
            sameSite,
            schemeType
          );

          return self.cookies.get(details);
        },

        remove: function (details) {
          validateFirstPartyDomain(details);

          let allowed = ["url", "name"];
          for (let { cookie, storeId } of query(details, allowed, context)) {
            Services.cookies.remove(
              cookie.host,
              cookie.name,
              cookie.path,
              cookie.originAttributes
            );

            // TODO Bug 1387957: could there be multiple per subdomain?
            return Promise.resolve({
              url: details.url,
              name: details.name,
              storeId,
              firstPartyDomain: cookie.originAttributes.firstPartyDomain,
              partitionKey: toExtPartitionKey(
                cookie.originAttributes.partitionKey
              ),
            });
          }

          return Promise.resolve(null);
        },

        getAllCookieStores: function () {
          let data = {};
          for (let tab of extension.tabManager.query()) {
            if (!(tab.cookieStoreId in data)) {
              data[tab.cookieStoreId] = [];
            }
            data[tab.cookieStoreId].push(tab.id);
          }

          let result = [];
          for (let key in data) {
            result.push({
              id: key,
              tabIds: data[key],
              incognito: key == PRIVATE_STORE,
            });
          }
          return Promise.resolve(result);
        },

        onChanged: new EventManager({
          context,
          module: "cookies",
          event: "onChanged",
          extensionApi: this,
        }).api(),
      },
    };

    return self;
  }
};
PK
!<�njEchrome/toolkit/content/extensions/parent/ext-declarativeNetRequest.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

ChromeUtils.defineESModuleGetters(this, {
  ExtensionDNR: "resource://gre/modules/ExtensionDNR.sys.mjs",
});

var { ExtensionError } = ExtensionUtils;

const PREF_DNR_FEEDBACK = "extensions.dnr.feedback";
XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "dnrFeedbackEnabled",
  PREF_DNR_FEEDBACK,
  false
);

function ensureDNRFeedbackEnabled(apiName) {
  if (!dnrFeedbackEnabled) {
    throw new ExtensionError(
      `${apiName} is only available when the "${PREF_DNR_FEEDBACK}" preference is set to true.`
    );
  }
}

this.declarativeNetRequest = class extends ExtensionAPI {
  onManifestEntry(entryName) {
    if (entryName === "declarative_net_request") {
      ExtensionDNR.validateManifestEntry(this.extension);
    }
  }

  onShutdown() {
    ExtensionDNR.clearRuleManager(this.extension);
  }

  getAPI() {
    const { extension } = this;

    return {
      declarativeNetRequest: {
        updateDynamicRules({ removeRuleIds, addRules }) {
          return ExtensionDNR.updateDynamicRules(extension, {
            removeRuleIds,
            addRules,
          });
        },

        updateSessionRules({ removeRuleIds, addRules }) {
          const ruleManager = ExtensionDNR.getRuleManager(extension);
          let ruleValidator = new ExtensionDNR.RuleValidator(
            ruleManager.getSessionRules(),
            { isSessionRuleset: true }
          );
          if (removeRuleIds) {
            ruleValidator.removeRuleIds(removeRuleIds);
          }
          if (addRules) {
            ruleValidator.addRules(addRules);
          }
          let failures = ruleValidator.getFailures();
          if (failures.length) {
            throw new ExtensionError(failures[0].message);
          }
          let validatedRules = ruleValidator.getValidatedRules();
          let ruleQuotaCounter = new ExtensionDNR.RuleQuotaCounter(
            "MAX_NUMBER_OF_SESSION_RULES"
          );
          ruleQuotaCounter.tryAddRules("_session", validatedRules);
          ruleManager.setSessionRules(validatedRules);
        },

        async getEnabledRulesets() {
          await ExtensionDNR.ensureInitialized(extension);
          const ruleManager = ExtensionDNR.getRuleManager(extension);
          return ruleManager.enabledStaticRulesetIds;
        },

        async getAvailableStaticRuleCount() {
          await ExtensionDNR.ensureInitialized(extension);
          const ruleManager = ExtensionDNR.getRuleManager(extension);
          return ruleManager.availableStaticRuleCount;
        },

        updateEnabledRulesets({ disableRulesetIds, enableRulesetIds }) {
          return ExtensionDNR.updateEnabledStaticRulesets(extension, {
            disableRulesetIds,
            enableRulesetIds,
          });
        },

        updateStaticRules({ rulesetId, disableRuleIds, enableRuleIds }) {
          return ExtensionDNR.updateStaticRules(extension, {
            rulesetId,
            disableRuleIds,
            enableRuleIds,
          });
        },

        async getDisabledRuleIds({ rulesetId }) {
          return ExtensionDNR.getDisabledRuleIds(extension, rulesetId);
        },

        async getDynamicRules(details) {
          await ExtensionDNR.ensureInitialized(extension);
          return ExtensionDNR.getRuleManager(extension).getDynamicRules(
            details?.ruleIds
          );
        },

        getSessionRules(details) {
          // ruleManager.getSessionRules() returns an array of Rule instances.
          // When these are structurally cloned (to send them to the child),
          // the enumerable public fields of the class instances are copied to
          // plain objects, as desired.
          return ExtensionDNR.getRuleManager(extension).getSessionRules(
            details?.ruleIds
          );
        },

        isRegexSupported(regexOptions) {
          const {
            regex: regexFilter,
            isCaseSensitive: isUrlFilterCaseSensitive,
            // requireCapturing: is ignored, as it does not affect validation.
          } = regexOptions;

          let ruleValidator = new ExtensionDNR.RuleValidator([]);
          ruleValidator.addRules([
            {
              id: 1,
              condition: { regexFilter, isUrlFilterCaseSensitive },
              action: { type: "allow" },
            },
          ]);
          let failures = ruleValidator.getFailures();
          if (failures.length) {
            // While the UnsupportedRegexReason enum has more entries than just
            // "syntaxError" (e.g. also "memoryLimitExceeded"), our validation
            // is currently very permissive, and therefore the only
            // distinguishable error is "syntaxError".
            return { isSupported: false, reason: "syntaxError" };
          }
          return { isSupported: true };
        },

        async testMatchOutcome(request, options) {
          ensureDNRFeedbackEnabled("declarativeNetRequest.testMatchOutcome");
          let { url, initiator, ...req } = request;
          req.requestURI = Services.io.newURI(url);
          if (initiator) {
            req.initiatorURI = Services.io.newURI(initiator);
            if (req.initiatorURI.schemeIs("data")) {
              // data:-URIs are always opaque, i.e. a null principal. We should
              // therefore ignore them here.
              // ExtensionDNR's NetworkIntegration.startDNREvaluation does not
              // encounter data:-URIs because opaque principals are mapped to a
              // null initiatorURI. For consistency, we do the same here.
              req.initiatorURI = null;
            }
          }
          const matchedRules = ExtensionDNR.getMatchedRulesForRequest(
            req,
            options?.includeOtherExtensions ? null : extension
          ).map(matchedRule => {
            // Converts an internal MatchedRule instance to an object described
            // by the "MatchedRule" type in declarative_net_request.json.
            const result = {
              ruleId: matchedRule.rule.id,
              rulesetId: matchedRule.ruleset.id,
            };
            if (matchedRule.ruleManager.extension !== extension) {
              result.extensionId = matchedRule.ruleManager.extension.id;
            }
            return result;
          });
          return { matchedRules };
        },
      },
    };
  }
};
PK
!<��-�3chrome/toolkit/content/extensions/parent/ext-dns.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const dnssFlags = {
  allow_name_collisions: Ci.nsIDNSService.RESOLVE_ALLOW_NAME_COLLISION,
  bypass_cache: Ci.nsIDNSService.RESOLVE_BYPASS_CACHE,
  canonical_name: Ci.nsIDNSService.RESOLVE_CANONICAL_NAME,
  disable_ipv4: Ci.nsIDNSService.RESOLVE_DISABLE_IPV4,
  disable_ipv6: Ci.nsIDNSService.RESOLVE_DISABLE_IPV6,
  disable_trr: Ci.nsIDNSService.RESOLVE_DISABLE_TRR,
  offline: Ci.nsIDNSService.RESOLVE_OFFLINE,
  priority_low: Ci.nsIDNSService.RESOLVE_PRIORITY_LOW,
  priority_medium: Ci.nsIDNSService.RESOLVE_PRIORITY_MEDIUM,
  speculate: Ci.nsIDNSService.RESOLVE_SPECULATE,
};

function getErrorString(nsresult) {
  let e = new Components.Exception("", nsresult);
  return e.name;
}

this.dns = class extends ExtensionAPI {
  getAPI() {
    return {
      dns: {
        resolve: function (hostname, flags) {
          let dnsFlags = flags.reduce(
            (mask, flag) => mask | dnssFlags[flag],
            0
          );

          return new Promise((resolve, reject) => {
            let request;
            let response = {
              addresses: [],
            };
            let listener = {
              onLookupComplete: function (inRequest, inRecord, inStatus) {
                if (inRequest === request) {
                  if (!Components.isSuccessCode(inStatus)) {
                    return reject({ message: getErrorString(inStatus) });
                  }
                  inRecord.QueryInterface(Ci.nsIDNSAddrRecord);
                  if (dnsFlags & Ci.nsIDNSService.RESOLVE_CANONICAL_NAME) {
                    try {
                      response.canonicalName = inRecord.canonicalName;
                    } catch (e) {
                      // no canonicalName
                    }
                  }
                  response.isTRR = inRecord.IsTRR();
                  while (inRecord.hasMore()) {
                    let addr = inRecord.getNextAddrAsString();
                    // Sometimes there are duplicate records with the same ip.
                    if (!response.addresses.includes(addr)) {
                      response.addresses.push(addr);
                    }
                  }
                  return resolve(response);
                }
              },
            };
            try {
              request = Services.dns.asyncResolve(
                hostname,
                Ci.nsIDNSService.RESOLVE_TYPE_DEFAULT,
                dnsFlags,
                null, // AdditionalInfo
                listener,
                null,
                {} /* defaultOriginAttributes */
              );
            } catch (e) {
              // handle exceptions such as offline mode.
              return reject({ message: e.name });
            }
          });
        },
      },
    };
  }
};
PK
!<V�*����9chrome/toolkit/content/extensions/parent/ext-downloads.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

ChromeUtils.defineESModuleGetters(this, {
  DownloadLastDir: "resource://gre/modules/DownloadLastDir.sys.mjs",
  DownloadPaths: "resource://gre/modules/DownloadPaths.sys.mjs",
  Downloads: "resource://gre/modules/Downloads.sys.mjs",
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
});

var { EventEmitter, ignoreEvent } = ExtensionCommon;
var { ExtensionError } = ExtensionUtils;

const DOWNLOAD_ITEM_FIELDS = [
  "id",
  "url",
  "referrer",
  "filename",
  "incognito",
  "cookieStoreId",
  "danger",
  "mime",
  "startTime",
  "endTime",
  "estimatedEndTime",
  "state",
  "paused",
  "canResume",
  "error",
  "bytesReceived",
  "totalBytes",
  "fileSize",
  "exists",
  "byExtensionId",
  "byExtensionName",
];

const DOWNLOAD_DATE_FIELDS = ["startTime", "endTime", "estimatedEndTime"];

// Fields that we generate onChanged events for.
const DOWNLOAD_ITEM_CHANGE_FIELDS = [
  "endTime",
  "state",
  "paused",
  "canResume",
  "error",
  "exists",
];

// From https://fetch.spec.whatwg.org/#forbidden-header-name
// Since bug 1367626 we allow extensions to set REFERER.
const FORBIDDEN_HEADERS = [
  "ACCEPT-CHARSET",
  "ACCEPT-ENCODING",
  "ACCESS-CONTROL-REQUEST-HEADERS",
  "ACCESS-CONTROL-REQUEST-METHOD",
  "CONNECTION",
  "CONTENT-LENGTH",
  "COOKIE",
  "COOKIE2",
  "DATE",
  "DNT",
  "EXPECT",
  "HOST",
  "KEEP-ALIVE",
  "ORIGIN",
  "TE",
  "TRAILER",
  "TRANSFER-ENCODING",
  "UPGRADE",
  "VIA",
];

const FORBIDDEN_PREFIXES = /^PROXY-|^SEC-/i;

const PROMPTLESS_DOWNLOAD_PREF = "browser.download.useDownloadDir";

// Lists of file extensions for each file picker filter taken from filepicker.properties
const FILTER_HTML_EXTENSIONS = ["html", "htm", "shtml", "xhtml"];

const FILTER_TEXT_EXTENSIONS = ["txt", "text"];

const FILTER_IMAGES_EXTENSIONS = [
  "jpe",
  "jpg",
  "jpeg",
  "gif",
  "png",
  "bmp",
  "ico",
  "svg",
  "svgz",
  "tif",
  "tiff",
  "ai",
  "drw",
  "pct",
  "psp",
  "xcf",
  "psd",
  "raw",
  "webp",
  "heic",
];

const FILTER_XML_EXTENSIONS = ["xml"];

const FILTER_AUDIO_EXTENSIONS = [
  "aac",
  "aif",
  "flac",
  "iff",
  "m4a",
  "m4b",
  "mid",
  "midi",
  "mp3",
  "mpa",
  "mpc",
  "oga",
  "ogg",
  "ra",
  "ram",
  "snd",
  "wav",
  "wma",
];

const FILTER_VIDEO_EXTENSIONS = [
  "avi",
  "divx",
  "flv",
  "m4v",
  "mkv",
  "mov",
  "mp4",
  "mpeg",
  "mpg",
  "ogm",
  "ogv",
  "ogx",
  "rm",
  "rmvb",
  "smil",
  "webm",
  "wmv",
  "xvid",
];

class DownloadItem {
  constructor(id, download, extension) {
    this.id = id;
    this.download = download;
    this.extension = extension;
    this.prechange = {};
    this._error = null;
  }

  get url() {
    return this.download.source.url;
  }

  get referrer() {
    const uri = this.download.source.referrerInfo?.originalReferrer;

    return uri?.spec;
  }

  get filename() {
    return this.download.target.path;
  }

  get incognito() {
    return this.download.source.isPrivate;
  }

  get cookieStoreId() {
    if (this.download.source.isPrivate) {
      return PRIVATE_STORE;
    }
    if (this.download.source.userContextId) {
      return getCookieStoreIdForContainer(this.download.source.userContextId);
    }
    return DEFAULT_STORE;
  }

  get danger() {
    // TODO
    return "safe";
  }

  get mime() {
    return this.download.contentType;
  }

  get startTime() {
    return this.download.startTime;
  }

  get endTime() {
    // TODO bug 1256269: implement endTime.
    return null;
  }

  get estimatedEndTime() {
    // Based on the code in summarizeDownloads() in DownloadsCommon.sys.mjs
    if (this.download.hasProgress && this.download.speed > 0) {
      let sizeLeft = this.download.totalBytes - this.download.currentBytes;
      let timeLeftInSeconds = sizeLeft / this.download.speed;
      return new Date(Date.now() + timeLeftInSeconds * 1000);
    }
    return undefined;
  }

  get state() {
    if (this.download.succeeded) {
      return "complete";
    }
    if (this.download.canceled || this.error) {
      return "interrupted";
    }
    return "in_progress";
  }

  get paused() {
    return (
      this.download.canceled &&
      this.download.hasPartialData &&
      !this.download.error
    );
  }

  get canResume() {
    return (
      (this.download.stopped || this.download.canceled) &&
      this.download.hasPartialData &&
      !this.download.error
    );
  }

  get error() {
    if (this._error) {
      return this._error;
    }
    if (
      !this.download.startTime ||
      !this.download.stopped ||
      this.download.succeeded
    ) {
      return null;
    }

    // TODO store this instead of calculating it
    if (this.download.error) {
      if (this.download.error.becauseSourceFailed) {
        return "NETWORK_FAILED"; // TODO
      }
      if (this.download.error.becauseTargetFailed) {
        return "FILE_FAILED"; // TODO
      }
      return "CRASH";
    }
    return "USER_CANCELED";
  }

  set error(value) {
    this._error = value && value.toString();
  }

  get bytesReceived() {
    return this.download.currentBytes;
  }

  get totalBytes() {
    return this.download.hasProgress ? this.download.totalBytes : -1;
  }

  get fileSize() {
    // todo: this is supposed to be post-compression
    return this.download.succeeded ? this.download.target.size : -1;
  }

  get exists() {
    return this.download.target.exists;
  }

  get byExtensionId() {
    return this.extension?.id;
  }

  get byExtensionName() {
    return this.extension?.name;
  }

  /**
   * Create a cloneable version of this object by pulling all the
   * fields into simple properties (instead of getters).
   *
   * @returns {object} A DownloadItem with flat properties,
   *                   suitable for cloning.
   */
  serialize() {
    let obj = {};
    for (let field of DOWNLOAD_ITEM_FIELDS) {
      obj[field] = this[field];
    }
    for (let field of DOWNLOAD_DATE_FIELDS) {
      if (obj[field]) {
        obj[field] = obj[field].toISOString();
      }
    }
    return obj;
  }

  // When a change event fires, handlers can look at how an individual
  // field changed by comparing item.fieldname with item.prechange.fieldname.
  // After all handlers have been invoked, this gets called to store the
  // current values of all fields ahead of the next event.
  _storePrechange() {
    for (let field of DOWNLOAD_ITEM_CHANGE_FIELDS) {
      this.prechange[field] = this[field];
    }
  }
}

// DownloadMap maps back and forth between the numeric identifiers used in
// the downloads WebExtension API and a Download object from the Downloads sys.mjs.
// TODO Bug 1247794: make id and extension info persistent
const DownloadMap = new (class extends EventEmitter {
  constructor() {
    super();

    this.currentId = 0;
    this.loadPromise = null;

    // Maps numeric id -> DownloadItem
    this.byId = new Map();

    // Maps Download object -> DownloadItem
    this.byDownload = new WeakMap();
  }

  lazyInit() {
    if (!this.loadPromise) {
      this.loadPromise = (async () => {
        const list = await Downloads.getList(Downloads.ALL);

        await list.addView({
          onDownloadAdded: download => {
            const item = this.newFromDownload(download, null);
            this.emit("create", item);
            item._storePrechange();
          },
          onDownloadRemoved: download => {
            const item = this.byDownload.get(download);
            if (item) {
              this.emit("erase", item);
              this.byDownload.delete(download);
              this.byId.delete(item.id);
            }
          },
          onDownloadChanged: download => {
            const item = this.byDownload.get(download);
            if (item) {
              this.emit("change", item);
              item._storePrechange();
            } else {
              Cu.reportError(
                "Got onDownloadChanged for unknown download object"
              );
            }
          },
        });

        const downloads = await list.getAll();

        for (let download of downloads) {
          this.newFromDownload(download, null);
        }

        return list;
      })();
    }

    return this.loadPromise;
  }

  getDownloadList() {
    return this.lazyInit();
  }

  async getAll() {
    await this.lazyInit();
    return this.byId.values();
  }

  fromId(id, privateAllowed = true) {
    const download = this.byId.get(id);
    if (!download || (!privateAllowed && download.incognito)) {
      throw new ExtensionError(`Invalid download id ${id}`);
    }
    return download;
  }

  newFromDownload(download, extension) {
    if (this.byDownload.has(download)) {
      return this.byDownload.get(download);
    }

    const id = ++this.currentId;
    let item = new DownloadItem(id, download, extension);
    this.byId.set(id, item);
    this.byDownload.set(download, item);
    return item;
  }

  async erase(item) {
    // TODO Bug 1255507: for now we only work with downloads in the DownloadList
    // from getAll()
    const list = await this.getDownloadList();
    list.remove(item.download);
  }
})();

// Create a callable function that filters a DownloadItem based on a
// query object of the type passed to search() or erase().
const downloadQuery = query => {
  let queryTerms = [];
  let queryNegativeTerms = [];
  if (query.query != null) {
    for (let term of query.query) {
      if (term[0] == "-") {
        queryNegativeTerms.push(term.slice(1).toLowerCase());
      } else {
        queryTerms.push(term.toLowerCase());
      }
    }
  }

  function normalizeDownloadTime(arg, before) {
    if (arg == null) {
      return before ? Number.MAX_VALUE : 0;
    }
    return ExtensionCommon.normalizeTime(arg).getTime();
  }

  const startedBefore = normalizeDownloadTime(query.startedBefore, true);
  const startedAfter = normalizeDownloadTime(query.startedAfter, false);

  // TODO bug 1727510: Implement endedBefore/endedAfter
  // const endedBefore = normalizeDownloadTime(query.endedBefore, true);
  // const endedAfter = normalizeDownloadTime(query.endedAfter, false);

  const totalBytesGreater = query.totalBytesGreater ?? -1;
  const totalBytesLess = query.totalBytesLess ?? Number.MAX_VALUE;

  // Handle options for which we can have a regular expression and/or
  // an explicit value to match.
  function makeMatch(regex, value, field) {
    if (value == null && regex == null) {
      return () => true;
    }

    let re;
    try {
      re = new RegExp(regex || "", "i");
    } catch (err) {
      throw new ExtensionError(`Invalid ${field}Regex: ${err.message}`);
    }
    if (value == null) {
      return input => re.test(input);
    }

    value = value.toLowerCase();
    if (re.test(value)) {
      return input => value == input;
    }
    return () => false;
  }

  const matchFilename = makeMatch(
    query.filenameRegex,
    query.filename,
    "filename"
  );
  const matchUrl = makeMatch(query.urlRegex, query.url, "url");

  return function (item) {
    const url = item.url.toLowerCase();
    const filename = item.filename.toLowerCase();

    if (
      !queryTerms.every(term => url.includes(term) || filename.includes(term))
    ) {
      return false;
    }

    if (
      queryNegativeTerms.some(
        term => url.includes(term) || filename.includes(term)
      )
    ) {
      return false;
    }

    if (!matchFilename(filename) || !matchUrl(url)) {
      return false;
    }

    if (!item.startTime) {
      if (query.startedBefore != null || query.startedAfter != null) {
        return false;
      }
    } else if (
      item.startTime > startedBefore ||
      item.startTime < startedAfter
    ) {
      return false;
    }

    // todo endedBefore, endedAfter

    if (item.totalBytes == -1) {
      if (query.totalBytesGreater !== null || query.totalBytesLess !== null) {
        return false;
      }
    } else if (
      item.totalBytes <= totalBytesGreater ||
      item.totalBytes >= totalBytesLess
    ) {
      return false;
    }

    // todo: include danger
    const SIMPLE_ITEMS = [
      "id",
      "mime",
      "startTime",
      "endTime",
      "state",
      "paused",
      "error",
      "incognito",
      "cookieStoreId",
      "bytesReceived",
      "totalBytes",
      "fileSize",
      "exists",
    ];
    for (let field of SIMPLE_ITEMS) {
      if (query[field] != null && item[field] != query[field]) {
        return false;
      }
    }

    return true;
  };
};

const queryHelper = async query => {
  let matchFn = downloadQuery(query);
  let compareFn;

  if (query.orderBy) {
    const fields = query.orderBy.map(field =>
      field[0] == "-"
        ? { reverse: true, name: field.slice(1) }
        : { reverse: false, name: field }
    );

    for (let field of fields) {
      if (!DOWNLOAD_ITEM_FIELDS.includes(field.name)) {
        throw new ExtensionError(`Invalid orderBy field ${field.name}`);
      }
    }

    compareFn = (dl1, dl2) => {
      for (let field of fields) {
        const val1 = dl1[field.name];
        const val2 = dl2[field.name];

        if (val1 < val2) {
          return field.reverse ? 1 : -1;
        } else if (val1 > val2) {
          return field.reverse ? -1 : 1;
        }
      }
      return 0;
    };
  }

  let downloads = await DownloadMap.getAll();

  if (compareFn) {
    downloads = Array.from(downloads);
    downloads.sort(compareFn);
  }

  let results = [];
  for (let download of downloads) {
    if (query.limit && results.length >= query.limit) {
      break;
    }
    if (matchFn(download)) {
      results.push(download);
    }
  }
  return results;
};

this.downloads = class extends ExtensionAPIPersistent {
  downloadEventRegistrar(event, listener) {
    let { extension } = this;
    return ({ fire }) => {
      const handler = (what, item) => {
        if (extension.privateBrowsingAllowed || !item.incognito) {
          listener(fire, what, item);
        }
      };
      let registerPromise = DownloadMap.getDownloadList().then(() => {
        DownloadMap.on(event, handler);
      });
      return {
        unregister() {
          registerPromise.then(() => {
            DownloadMap.off(event, handler);
          });
        },
        convert(_fire) {
          fire = _fire;
        },
      };
    };
  }

  PERSISTENT_EVENTS = {
    onChanged: this.downloadEventRegistrar("change", (fire, what, item) => {
      let changes = {};
      const noundef = val => (val === undefined ? null : val);
      DOWNLOAD_ITEM_CHANGE_FIELDS.forEach(fld => {
        if (item[fld] != item.prechange[fld]) {
          changes[fld] = {
            previous: noundef(item.prechange[fld]),
            current: noundef(item[fld]),
          };
        }
      });
      if (Object.keys(changes).length) {
        changes.id = item.id;
        fire.async(changes);
      }
    }),

    onCreated: this.downloadEventRegistrar("create", (fire, what, item) => {
      fire.async(item.serialize());
    }),

    onErased: this.downloadEventRegistrar("erase", (fire, what, item) => {
      fire.async(item.id);
    }),
  };

  getAPI(context) {
    let { extension } = context;
    return {
      downloads: {
        async download(options) {
          const isHandlingUserInput =
            context.callContextData?.isHandlingUserInput;
          let { filename } = options;
          if (filename && AppConstants.platform === "win") {
            // cross platform javascript code uses "/"
            filename = filename.replace(/\//g, "\\");
          }

          if (filename != null) {
            if (!filename.length) {
              throw new ExtensionError("filename must not be empty");
            }

            if (PathUtils.isAbsolute(filename)) {
              throw new ExtensionError("filename must not be an absolute path");
            }

            // % is not permitted but relatively common.
            filename = filename.replaceAll("%", "_");

            const pathComponents = PathUtils.splitRelative(filename, {
              allowEmpty: true,
              allowCurrentDir: true,
              allowParentDir: true,
            });

            if (pathComponents.some(component => component == "..")) {
              throw new ExtensionError(
                "filename must not contain back-references (..)"
              );
            }

            if (
              pathComponents.some((component, i) => {
                let sanitized = DownloadPaths.sanitize(component, {
                  compressWhitespaces: false,
                  allowDirectoryNames: i < pathComponents.length - 1,
                });
                return component != sanitized;
              })
            ) {
              throw new ExtensionError(
                "filename must not contain illegal characters"
              );
            }
          }

          if (options.incognito && !context.privateBrowsingAllowed) {
            throw new ExtensionError("private browsing access not allowed");
          }

          if (options.conflictAction == "prompt") {
            // TODO
            throw new ExtensionError(
              "conflictAction prompt not yet implemented"
            );
          }

          if (options.headers) {
            for (let { name } of options.headers) {
              if (
                FORBIDDEN_HEADERS.includes(name.toUpperCase()) ||
                name.match(FORBIDDEN_PREFIXES)
              ) {
                throw new ExtensionError("Forbidden request header name");
              }
            }
          }

          let userContextId = null;
          if (options.cookieStoreId != null) {
            userContextId = getUserContextIdForCookieStoreId(
              extension,
              options.cookieStoreId,
              options.incognito
            );
          }

          // Handle method, headers and body options.
          function adjustChannel(channel) {
            if (channel instanceof Ci.nsIHttpChannel) {
              const method = options.method || "GET";
              channel.requestMethod = method;

              if (options.headers) {
                for (let { name, value } of options.headers) {
                  if (name.toLowerCase() == "referer") {
                    // The referer header and referrerInfo object should always
                    // match. So if we want to set the header from privileged
                    // context, we should set referrerInfo. The referrer header
                    // will get set internally.
                    channel.setNewReferrerInfo(
                      value,
                      Ci.nsIReferrerInfo.UNSAFE_URL,
                      true
                    );
                  } else {
                    channel.setRequestHeader(name, value, false);
                  }
                }
              }

              if (options.body != null) {
                const stream = Cc[
                  "@mozilla.org/io/string-input-stream;1"
                ].createInstance(Ci.nsIStringInputStream);
                stream.setData(options.body, options.body.length);

                channel.QueryInterface(Ci.nsIUploadChannel2);
                channel.explicitSetUploadStream(
                  stream,
                  null,
                  -1,
                  method,
                  false
                );
              }
            }
            return Promise.resolve();
          }

          function allowHttpStatus(download, status) {
            const item = DownloadMap.byDownload.get(download);
            if (item === null) {
              return true;
            }

            let error = null;
            switch (status) {
              case 204: // No Content
              case 205: // Reset Content
              case 404: // Not Found
                error = "SERVER_BAD_CONTENT";
                break;

              case 403: // Forbidden
                error = "SERVER_FORBIDDEN";
                break;

              case 402: // Unauthorized
              case 407: // Proxy authentication required
                error = "SERVER_UNAUTHORIZED";
                break;

              default:
                if (status >= 400) {
                  error = "SERVER_FAILED";
                }
                break;
            }

            if (error) {
              item.error = error;
              return false;
            }

            // No error, ergo allow the request.
            return true;
          }

          async function createTarget(downloadsDir) {
            if (!filename) {
              let uri = Services.io.newURI(options.url);
              if (uri instanceof Ci.nsIURL) {
                filename = DownloadPaths.sanitize(
                  Services.textToSubURI.unEscapeURIForUI(
                    uri.fileName,
                    /* dontEscape = */ true
                  )
                );
              }
            }

            let target = PathUtils.joinRelative(
              downloadsDir,
              filename || "download"
            );

            let saveAs;
            if (options.saveAs !== null) {
              saveAs = options.saveAs;
            } else {
              // If options.saveAs was not specified, only show the file chooser
              // if |browser.download.useDownloadDir == false|. That is to say,
              // only show the file chooser if Firefox normally shows it when
              // a file is downloaded.
              saveAs = !Services.prefs.getBoolPref(
                PROMPTLESS_DOWNLOAD_PREF,
                true
              );
            }

            // Create any needed subdirectories if required by filename.
            const dir = PathUtils.parent(target);
            await IOUtils.makeDirectory(dir);

            if (await IOUtils.exists(target)) {
              // This has a race, something else could come along and create
              // the file between this test and them time the download code
              // creates the target file.  But we can't easily fix it without
              // modifying DownloadCore so we live with it for now.
              switch (options.conflictAction) {
                case "uniquify":
                default:
                  target = DownloadPaths.createNiceUniqueFile(
                    new FileUtils.File(target)
                  ).path;
                  if (saveAs) {
                    // createNiceUniqueFile actually creates the file, which
                    // is premature if we need to show a SaveAs dialog.
                    await IOUtils.remove(target);
                  }
                  break;

                case "overwrite":
                  break;
              }
            }

            if (!saveAs || AppConstants.platform === "android") {
              return target;
            }

            if (!("windowTracker" in global)) {
              return target;
            }

            // At this point we are committed to displaying the file picker.
            const downloadLastDir = new DownloadLastDir(
              null,
              options.incognito
            );

            async function getLastDirectory() {
              return downloadLastDir.getFileAsync(extension.baseURI);
            }

            function appendFilterForFileExtension(picker, ext) {
              if (FILTER_HTML_EXTENSIONS.includes(ext)) {
                picker.appendFilters(Ci.nsIFilePicker.filterHTML);
              } else if (FILTER_TEXT_EXTENSIONS.includes(ext)) {
                picker.appendFilters(Ci.nsIFilePicker.filterText);
              } else if (FILTER_IMAGES_EXTENSIONS.includes(ext)) {
                picker.appendFilters(Ci.nsIFilePicker.filterImages);
              } else if (FILTER_XML_EXTENSIONS.includes(ext)) {
                picker.appendFilters(Ci.nsIFilePicker.filterXML);
              } else if (FILTER_AUDIO_EXTENSIONS.includes(ext)) {
                picker.appendFilters(Ci.nsIFilePicker.filterAudio);
              } else if (FILTER_VIDEO_EXTENSIONS.includes(ext)) {
                picker.appendFilters(Ci.nsIFilePicker.filterVideo);
              }
            }

            function saveLastDirectory(lastDir) {
              downloadLastDir.setFile(extension.baseURI, lastDir);
            }

            // Use windowTracker to find a window, rather than Services.wm,
            // so that this doesn't break where navigator:browser isn't the
            // main window (e.g. Thunderbird).
            const window = global.windowTracker.getTopWindow().window;
            const basename = PathUtils.filename(target);
            const ext = basename.match(/\.([^.]+)$/)?.[1];

            // If the filename passed in by the extension is a simple name
            // and not a path, we open the file picker so it displays the
            // last directory that was chosen by the user.
            const pathSep = AppConstants.platform === "win" ? "\\" : "/";
            const lastFilePickerDirectory =
              !filename || !filename.includes(pathSep)
                ? await getLastDirectory()
                : undefined;

            // Setup the file picker Save As dialog.
            const picker = Cc["@mozilla.org/filepicker;1"].createInstance(
              Ci.nsIFilePicker
            );
            picker.init(
              window.browsingContext,
              null,
              Ci.nsIFilePicker.modeSave
            );
            if (lastFilePickerDirectory) {
              picker.displayDirectory = lastFilePickerDirectory;
            } else {
              picker.displayDirectory = new FileUtils.File(dir);
            }
            picker.defaultString = basename;
            if (ext) {
              // Configure a default file extension, used as fallback on Windows.
              picker.defaultExtension = ext;
              appendFilterForFileExtension(picker, ext);
            }
            picker.appendFilters(Ci.nsIFilePicker.filterAll);

            // Open the dialog and resolve/reject with the result.
            return new Promise((resolve, reject) => {
              picker.open(result => {
                if (result === Ci.nsIFilePicker.returnCancel) {
                  reject({ message: "Download canceled by the user" });
                } else {
                  saveLastDirectory(picker.file.parent);
                  resolve(picker.file.path);
                }
              });
            });
          }

          const downloadsDir = await Downloads.getPreferredDownloadsDirectory();
          const target = await createTarget(downloadsDir);
          const uri = Services.io.newURI(options.url);
          const cookieJarSettings = Cc[
            "@mozilla.org/cookieJarSettings;1"
          ].createInstance(Ci.nsICookieJarSettings);
          cookieJarSettings.initWithURI(uri, options.incognito);

          const source = {
            url: options.url,
            isPrivate: options.incognito,
            // Use the extension's principal to allow extensions to observe
            // their own downloads via the webRequest API.
            loadingPrincipal: context.principal,
            cookieJarSettings,
          };

          if (userContextId) {
            source.userContextId = userContextId;
          }

          // blob:-URLs can only be loaded by the principal with which they
          // are associated. This principal may have origin attributes.
          // `context.principal` does sometimes not have these attributes
          // due to bug 1653681. If `context.principal` were to be passed,
          // the download request would be rejected because of mismatching
          // principals (origin attributes).
          // TODO bug 1653681: fix context.principal and remove this.
          if (options.url.startsWith("blob:")) {
            // To make sure that the blob:-URL can be loaded, fall back to
            // the default (system) principal instead.
            delete source.loadingPrincipal;
          }

          // Unless the API user explicitly wants errors ignored,
          // set the allowHttpStatus callback, which will instruct
          // DownloadCore to cancel downloads on HTTP errors.
          if (!options.allowHttpErrors) {
            source.allowHttpStatus = allowHttpStatus;
          }

          if (options.method || options.headers || options.body) {
            source.adjustChannel = adjustChannel;
          }

          const download = await Downloads.createDownload({
            // Only open the download panel if the method has been called
            // while handling user input (See Bug 1759231).
            openDownloadsListOnStart: isHandlingUserInput,
            source,
            target: {
              path: target,
              partFilePath: `${target}.part`,
            },
          });

          const list = await DownloadMap.getDownloadList();
          const item = DownloadMap.newFromDownload(download, extension);
          list.add(download);

          // This is necessary to make pause/resume work.
          download.tryToKeepPartialData = true;

          // Do not handle errors.
          // Extensions will use listeners to be informed about errors.
          // Just ignore any errors from |start()| to avoid spamming the
          // error console.
          download.start().catch(err => {
            if (err.name !== "DownloadError") {
              Cu.reportError(err);
            }
          });

          return item.id;
        },

        async removeFile(id) {
          await DownloadMap.lazyInit();

          let item = DownloadMap.fromId(id, context.privateBrowsingAllowed);

          if (item.state !== "complete") {
            throw new ExtensionError(
              `Cannot remove incomplete download id ${id}`
            );
          }

          try {
            await IOUtils.remove(item.filename, { ignoreAbsent: false });
          } catch (err) {
            if (DOMException.isInstance(err) && err.name === "NotFoundError") {
              throw new ExtensionError(
                `Could not remove download id ${item.id} because the file doesn't exist`
              );
            }

            // Unexpected other error. Throw the original error, so that it
            // can bubble up to the global browser console, but keep it
            // sanitized (i.e. not wrapped in ExtensionError) to avoid
            // inadvertent disclosure of potentially sensitive information.
            throw err;
          }
        },

        async search(query) {
          if (!context.privateBrowsingAllowed) {
            query.incognito = false;
          }

          const items = await queryHelper(query);
          return items.map(item => item.serialize());
        },

        async pause(id) {
          await DownloadMap.lazyInit();

          let item = DownloadMap.fromId(id, context.privateBrowsingAllowed);

          if (item.state !== "in_progress") {
            throw new ExtensionError(
              `Download ${id} cannot be paused since it is in state ${item.state}`
            );
          }

          return item.download.cancel();
        },

        async resume(id) {
          await DownloadMap.lazyInit();

          let item = DownloadMap.fromId(id, context.privateBrowsingAllowed);

          if (!item.canResume) {
            throw new ExtensionError(`Download ${id} cannot be resumed`);
          }

          item.error = null;
          return item.download.start();
        },

        async cancel(id) {
          await DownloadMap.lazyInit();

          let item = DownloadMap.fromId(id, context.privateBrowsingAllowed);

          if (item.download.succeeded) {
            throw new ExtensionError(`Download ${id} is already complete`);
          }

          return item.download.finalize(true);
        },

        showDefaultFolder() {
          Downloads.getPreferredDownloadsDirectory()
            .then(dir => {
              let dirobj = new FileUtils.File(dir);
              if (dirobj.isDirectory()) {
                dirobj.launch();
              } else {
                throw new Error(
                  `Download directory ${dirobj.path} is not actually a directory`
                );
              }
            })
            .catch(Cu.reportError);
        },

        async erase(query) {
          if (!context.privateBrowsingAllowed) {
            query.incognito = false;
          }

          const items = await queryHelper(query);
          let results = [];
          let promises = [];

          for (let item of items) {
            promises.push(DownloadMap.erase(item));
            results.push(item.id);
          }

          await Promise.all(promises);
          return results;
        },

        async open(downloadId) {
          await DownloadMap.lazyInit();

          let { download } = DownloadMap.fromId(
            downloadId,
            context.privateBrowsingAllowed
          );

          if (!download.succeeded) {
            throw new ExtensionError("Download has not completed.");
          }

          return download.launch();
        },

        async show(downloadId) {
          await DownloadMap.lazyInit();

          const { download } = DownloadMap.fromId(
            downloadId,
            context.privateBrowsingAllowed
          );

          await download.showContainingDirectory();

          return true;
        },

        async getFileIcon(downloadId, options) {
          await DownloadMap.lazyInit();

          const size = options?.size || 32;
          const { download } = DownloadMap.fromId(
            downloadId,
            context.privateBrowsingAllowed
          );

          let pathPrefix = "";
          let path;

          if (download.succeeded) {
            let file = FileUtils.File(download.target.path);
            path = Services.io.newFileURI(file).spec;
          } else {
            path = PathUtils.filename(download.target.path);
            pathPrefix = "//";
          }

          let windowlessBrowser =
            Services.appShell.createWindowlessBrowser(true);
          let systemPrincipal =
            Services.scriptSecurityManager.getSystemPrincipal();
          windowlessBrowser.docShell.createAboutBlankDocumentViewer(
            systemPrincipal,
            systemPrincipal
          );

          let canvas = windowlessBrowser.document.createElement("canvas");
          let img = new windowlessBrowser.docShell.domWindow.Image(size, size);

          canvas.width = size;
          canvas.height = size;

          img.src = `moz-icon:${pathPrefix}${path}?size=${size}`;

          try {
            await img.decode();

            canvas.getContext("2d").drawImage(img, 0, 0, size, size);

            let dataURL = canvas.toDataURL("image/png");

            return dataURL;
          } finally {
            windowlessBrowser.close();
          }
        },

        onChanged: new EventManager({
          context,
          module: "downloads",
          event: "onChanged",
          extensionApi: this,
        }).api(),

        onCreated: new EventManager({
          context,
          module: "downloads",
          event: "onCreated",
          extensionApi: this,
        }).api(),

        onErased: new EventManager({
          context,
          module: "downloads",
          event: "onErased",
          extensionApi: this,
        }).api(),

        onDeterminingFilename: ignoreEvent(
          context,
          "downloads.onDeterminingFilename"
        ),
      },
    };
  }
};
PK
!<�,qyKK9chrome/toolkit/content/extensions/parent/ext-extension.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.extension = class extends ExtensionAPI {
  getAPI(context) {
    return {
      extension: {
        get lastError() {
          return context.lastError;
        },

        isAllowedIncognitoAccess() {
          return context.privateBrowsingAllowed;
        },

        isAllowedFileSchemeAccess() {
          return false;
        },
      },
    };
  }
};
PK
!<�UFJ=chrome/toolkit/content/extensions/parent/ext-geckoProfiler.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const PREF_ASYNC_STACK = "javascript.options.asyncstack";

const ASYNC_STACKS_ENABLED = Services.prefs.getBoolPref(
  PREF_ASYNC_STACK,
  false
);

var { ExtensionError } = ExtensionUtils;

ChromeUtils.defineLazyGetter(this, "symbolicationService", () => {
  let { createLocalSymbolicationService } = ChromeUtils.importESModule(
    "resource://devtools/client/performance-new/shared/symbolication.sys.mjs"
  );
  return createLocalSymbolicationService(Services.profiler.sharedLibraries, []);
});

const isRunningObserver = {
  _observers: new Set(),

  observe(subject, topic) {
    switch (topic) {
      case "profiler-started":
      case "profiler-stopped":
        // Call observer(false) or observer(true), but do it through a promise
        // so that it's asynchronous.
        // We don't want it to be synchronous because of the observer call in
        // addObserver, which is asynchronous, and we want to get the ordering
        // right.
        const isRunningPromise = Promise.resolve(topic === "profiler-started");
        for (let observer of this._observers) {
          isRunningPromise.then(observer);
        }
        break;
    }
  },

  _startListening() {
    Services.obs.addObserver(this, "profiler-started");
    Services.obs.addObserver(this, "profiler-stopped");
  },

  _stopListening() {
    Services.obs.removeObserver(this, "profiler-started");
    Services.obs.removeObserver(this, "profiler-stopped");
  },

  addObserver(observer) {
    if (this._observers.size === 0) {
      this._startListening();
    }

    this._observers.add(observer);
    observer(Services.profiler.IsActive());
  },

  removeObserver(observer) {
    if (this._observers.delete(observer) && this._observers.size === 0) {
      this._stopListening();
    }
  },
};

this.geckoProfiler = class extends ExtensionAPI {
  getAPI(context) {
    return {
      geckoProfiler: {
        async start(options) {
          const { bufferSize, windowLength, interval, features, threads } =
            options;

          Services.prefs.setBoolPref(PREF_ASYNC_STACK, false);
          if (threads) {
            Services.profiler.StartProfiler(
              bufferSize,
              interval,
              features,
              threads,
              0,
              windowLength
            );
          } else {
            Services.profiler.StartProfiler(
              bufferSize,
              interval,
              features,
              [],
              0,
              windowLength
            );
          }
        },

        async stop() {
          if (ASYNC_STACKS_ENABLED !== null) {
            Services.prefs.setBoolPref(PREF_ASYNC_STACK, ASYNC_STACKS_ENABLED);
          }

          Services.profiler.StopProfiler();
        },

        async pause() {
          Services.profiler.Pause();
        },

        async resume() {
          Services.profiler.Resume();
        },

        async dumpProfileToFile(fileName) {
          if (!Services.profiler.IsActive()) {
            throw new ExtensionError(
              "The profiler is stopped. " +
                "You need to start the profiler before you can capture a profile."
            );
          }

          if (fileName.includes("\\") || fileName.includes("/")) {
            throw new ExtensionError("Path cannot contain a subdirectory.");
          }

          let dirPath = PathUtils.join(PathUtils.profileDir, "profiler");
          let filePath = PathUtils.join(dirPath, fileName);

          try {
            await IOUtils.makeDirectory(dirPath);
            await Services.profiler.dumpProfileToFileAsync(filePath);
          } catch (e) {
            Cu.reportError(e);
            throw new ExtensionError(`Dumping profile to ${filePath} failed.`);
          }
        },

        async getProfile() {
          if (!Services.profiler.IsActive()) {
            throw new ExtensionError(
              "The profiler is stopped. " +
                "You need to start the profiler before you can capture a profile."
            );
          }

          return Services.profiler.getProfileDataAsync();
        },

        async getProfileAsArrayBuffer() {
          if (!Services.profiler.IsActive()) {
            throw new ExtensionError(
              "The profiler is stopped. " +
                "You need to start the profiler before you can capture a profile."
            );
          }

          return Services.profiler.getProfileDataAsArrayBuffer();
        },

        async getProfileAsGzippedArrayBuffer() {
          if (!Services.profiler.IsActive()) {
            throw new ExtensionError(
              "The profiler is stopped. " +
                "You need to start the profiler before you can capture a profile."
            );
          }

          return Services.profiler.getProfileDataAsGzippedArrayBuffer();
        },

        async getSymbols(debugName, breakpadId) {
          return symbolicationService.getSymbolTable(debugName, breakpadId);
        },

        onRunning: new EventManager({
          context,
          name: "geckoProfiler.onRunning",
          register: fire => {
            isRunningObserver.addObserver(fire.async);
            return () => {
              isRunningObserver.removeObserver(fire.async);
            };
          },
        }).api(),
      },
    };
  }
};
PK
!<�2j�884chrome/toolkit/content/extensions/parent/ext-i18n.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

ChromeUtils.defineESModuleGetters(this, {
  LanguageDetector:
    "resource://gre/modules/translation/LanguageDetector.sys.mjs",
});

this.i18n = class extends ExtensionAPI {
  getAPI(context) {
    let { extension } = context;
    return {
      i18n: {
        getMessage: function (messageName, substitutions) {
          return extension.localizeMessage(messageName, substitutions, {
            cloneScope: context.cloneScope,
          });
        },

        getAcceptLanguages: function () {
          let result = extension.localeData.acceptLanguages;
          return Promise.resolve(result);
        },

        getUILanguage: function () {
          return extension.localeData.uiLocale;
        },

        detectLanguage: function (text) {
          return LanguageDetector.detectLanguage(text).then(result => ({
            isReliable: result.confident,
            languages: result.languages.map(lang => {
              return {
                language: lang.languageCode,
                percentage: lang.percent,
              };
            }),
          }));
        },
      },
    };
  }
};
PK
!<�h�8chrome/toolkit/content/extensions/parent/ext-identity.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

XPCOMUtils.defineLazyGlobalGetters(this, ["XMLHttpRequest", "ChannelWrapper"]);

var { promiseDocumentLoaded } = ExtensionUtils;

const checkRedirected = (url, redirectURI) => {
  return new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest({ mozAnon: false });
    xhr.open("GET", url);
    // We expect this if the user has not authenticated.
    xhr.onload = () => {
      reject(0);
    };
    // An unexpected error happened, log for extension authors.
    xhr.onerror = () => {
      reject(xhr.status);
    };
    // Catch redirect to our redirect_uri before a new request is made.
    xhr.channel.notificationCallbacks = {
      QueryInterface: ChromeUtils.generateQI([
        "nsIInterfaceRequestor",
        "nsIChannelEventSync",
      ]),

      getInterface: ChromeUtils.generateQI(["nsIChannelEventSink"]),

      asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
        let responseURL = newChannel.URI.spec;
        if (responseURL.startsWith(redirectURI)) {
          resolve(responseURL);
          // Cancel the redirect.
          callback.onRedirectVerifyCallback(Cr.NS_BINDING_ABORTED);
          return;
        }
        callback.onRedirectVerifyCallback(Cr.NS_OK);
      },
    };
    xhr.send();
  });
};

const openOAuthWindow = (details, redirectURI) => {
  let args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
  let supportsStringPrefURL = Cc[
    "@mozilla.org/supports-string;1"
  ].createInstance(Ci.nsISupportsString);
  supportsStringPrefURL.data = details.url;
  args.appendElement(supportsStringPrefURL);

  let window = Services.ww.openWindow(
    null,
    AppConstants.BROWSER_CHROME_URL,
    "launchWebAuthFlow_dialog",
    "chrome,location=yes,centerscreen,dialog=no,resizable=yes,scrollbars=yes",
    args
  );

  return new Promise((resolve, reject) => {
    let httpActivityDistributor = Cc[
      "@mozilla.org/network/http-activity-distributor;1"
    ].getService(Ci.nsIHttpActivityDistributor);

    let unloadListener;
    let httpObserver;

    const resolveIfRedirectURI = channel => {
      const url = channel.URI && channel.URI.spec;
      if (!url || !url.startsWith(redirectURI)) {
        return;
      }

      // Early exit if channel isn't related to the oauth dialog.
      let wrapper = ChannelWrapper.get(channel);
      if (
        !wrapper.browserElement &&
        wrapper.browserElement !== window.gBrowser.selectedBrowser
      ) {
        return;
      }

      wrapper.cancel(Cr.NS_ERROR_ABORT, Ci.nsILoadInfo.BLOCKING_REASON_NONE);
      window.gBrowser.webNavigation.stop(Ci.nsIWebNavigation.STOP_ALL);
      window.removeEventListener("unload", unloadListener);
      httpActivityDistributor.removeObserver(httpObserver);
      window.close();
      resolve(url);
    };

    httpObserver = {
      observeActivity(channel) {
        try {
          channel.QueryInterface(Ci.nsIChannel);
        } catch {
          // Ignore activities for channels that doesn't implement nsIChannel
          // (e.g. a NullHttpChannel).
          return;
        }

        resolveIfRedirectURI(channel);
      },
    };

    httpActivityDistributor.addObserver(httpObserver);

    // If the user just closes the window we need to reject
    unloadListener = () => {
      window.removeEventListener("unload", unloadListener);
      httpActivityDistributor.removeObserver(httpObserver);
      reject({ message: "User cancelled or denied access." });
    };

    promiseDocumentLoaded(window.document).then(() => {
      window.addEventListener("unload", unloadListener);
    });
  });
};

this.identity = class extends ExtensionAPI {
  getAPI(context) {
    return {
      identity: {
        launchWebAuthFlowInParent: function (details, redirectURI) {
          // If the request is automatically redirected the user has already
          // authorized and we do not want to show the window.
          let promise = checkRedirected(details.url, redirectURI).catch(
            requestError => {
              // requestError is zero or xhr.status
              if (requestError !== 0) {
                Cu.reportError(
                  `browser.identity auth check failed with ${requestError}`
                );
                return Promise.reject({ message: "Invalid request" });
              }
              if (!details.interactive) {
                return Promise.reject({ message: `Requires user interaction` });
              }

              return openOAuthWindow(details, redirectURI);
            }
          );
          if (context.isBackgroundContext) {
            context.extension.emit("background-script-idle-waituntil", {
              promise,
              reason: "launchWebAuthFlow",
            });
          }
          return promise;
        },
      },
    };
  }
};
PK
!<טI�8
8
4chrome/toolkit/content/extensions/parent/ext-idle.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

XPCOMUtils.defineLazyServiceGetter(
  this,
  "idleService",
  "@mozilla.org/widget/useridleservice;1",
  "nsIUserIdleService"
);

var { DefaultWeakMap } = ExtensionUtils;

// WeakMap[Extension -> Object]
const idleObserversMap = new DefaultWeakMap(() => {
  return {
    observer: null,
    detectionInterval: 60,
  };
});

const getIdleObserver = extension => {
  let observerInfo = idleObserversMap.get(extension);
  let { observer, detectionInterval } = observerInfo;
  let interval =
    extension.startupData?.idleDetectionInterval || detectionInterval;

  if (!observer) {
    observer = new (class extends ExtensionCommon.EventEmitter {
      observe(subject, topic) {
        if (topic == "idle" || topic == "active") {
          this.emit("stateChanged", topic);
        }
      }
    })();
    idleService.addIdleObserver(observer, interval);
    observerInfo.observer = observer;
    observerInfo.detectionInterval = interval;
  }
  return observer;
};

this.idle = class extends ExtensionAPIPersistent {
  PERSISTENT_EVENTS = {
    onStateChanged({ fire }) {
      let { extension } = this;
      let listener = (event, data) => {
        fire.sync(data);
      };

      getIdleObserver(extension).on("stateChanged", listener);
      return {
        async unregister() {
          let observerInfo = idleObserversMap.get(extension);
          let { observer, detectionInterval } = observerInfo;
          if (observer) {
            observer.off("stateChanged", listener);
            if (!observer.has("stateChanged")) {
              idleService.removeIdleObserver(observer, detectionInterval);
              observerInfo.observer = null;
            }
          }
        },
        convert(_fire) {
          fire = _fire;
        },
      };
    },
  };

  getAPI(context) {
    let { extension } = context;
    let self = this;

    return {
      idle: {
        queryState(detectionIntervalInSeconds) {
          if (idleService.idleTime < detectionIntervalInSeconds * 1000) {
            return "active";
          }
          return "idle";
        },
        setDetectionInterval(detectionIntervalInSeconds) {
          let observerInfo = idleObserversMap.get(extension);
          let { observer, detectionInterval } = observerInfo;
          if (detectionInterval == detectionIntervalInSeconds) {
            return;
          }
          if (observer) {
            idleService.removeIdleObserver(observer, detectionInterval);
            idleService.addIdleObserver(observer, detectionIntervalInSeconds);
          }
          observerInfo.detectionInterval = detectionIntervalInSeconds;
          // There is no great way to modify a persistent listener param, but we
          // need to keep this for the startup listener.
          if (!extension.persistentBackground) {
            extension.startupData.idleDetectionInterval =
              detectionIntervalInSeconds;
            extension.saveStartupData();
          }
        },
        onStateChanged: new EventManager({
          context,
          module: "idle",
          event: "onStateChanged",
          extensionApi: self,
        }).api(),
      },
    };
  }
};
PK
!<K���f'f':chrome/toolkit/content/extensions/parent/ext-management.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

ChromeUtils.defineLazyGetter(this, "strBundle", function () {
  return Services.strings.createBundle(
    "chrome://global/locale/extensions.properties"
  );
});
ChromeUtils.defineESModuleGetters(this, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
});

// We can't use Services.prompt here at the moment, as tests need to mock
// the prompt service. We could use sinon, but that didn't seem to work
// with Android builds.
// eslint-disable-next-line mozilla/use-services
XPCOMUtils.defineLazyServiceGetter(
  this,
  "promptService",
  "@mozilla.org/prompter;1",
  "nsIPromptService"
);

var { ExtensionError } = ExtensionUtils;

const _ = (key, ...args) => {
  if (args.length) {
    return strBundle.formatStringFromName(key, args);
  }
  return strBundle.GetStringFromName(key);
};

const installType = addon => {
  if (addon.temporarilyInstalled) {
    return "development";
  } else if (addon.foreignInstall) {
    return "sideload";
  } else if (addon.isSystem) {
    return "other";
  } else if (addon.isInstalledByEnterprisePolicy) {
    return "admin";
  }
  return "normal";
};

const getExtensionInfoForAddon = (extension, addon) => {
  let extInfo = {
    id: addon.id,
    name: addon.name,
    description: addon.description || "",
    version: addon.version,
    mayDisable: !!(addon.permissions & AddonManager.PERM_CAN_DISABLE),
    enabled: addon.isActive,
    optionsUrl: addon.optionsURL || "",
    installType: installType(addon),
    type: addon.type,
  };

  if (extension) {
    let m = extension.manifest;

    let hostPerms = extension.allowedOrigins.patterns.map(
      matcher => matcher.pattern
    );

    extInfo.permissions = Array.from(extension.permissions).filter(perm => {
      return !hostPerms.includes(perm);
    });
    extInfo.hostPermissions = hostPerms;

    extInfo.shortName = m.short_name || "";
    if (m.icons) {
      extInfo.icons = Object.keys(m.icons).map(key => {
        return { size: Number(key), url: m.icons[key] };
      });
    }
  }

  if (!addon.isActive) {
    extInfo.disabledReason = "unknown";
  }
  if (addon.homepageURL) {
    extInfo.homepageUrl = addon.homepageURL;
  }
  if (addon.updateURL) {
    extInfo.updateUrl = addon.updateURL;
  }
  return extInfo;
};

// Some management APIs are intentionally limited.
const allowedTypes = ["theme", "extension"];

function checkAllowedAddon(addon) {
  if (addon.isSystem || addon.isAPIExtension) {
    return false;
  }
  if (addon.type == "extension" && !addon.isWebExtension) {
    return false;
  }
  return allowedTypes.includes(addon.type);
}

class ManagementAddonListener extends ExtensionCommon.EventEmitter {
  eventNames = ["onEnabled", "onDisabled", "onInstalled", "onUninstalled"];

  hasAnyListeners() {
    for (let event of this.eventNames) {
      if (this.has(event)) {
        return true;
      }
    }
    return false;
  }

  on(event, listener) {
    if (!this.eventNames.includes(event)) {
      throw new Error("unsupported event");
    }
    if (!this.hasAnyListeners()) {
      AddonManager.addAddonListener(this);
    }
    super.on(event, listener);
  }

  off(event, listener) {
    if (!this.eventNames.includes(event)) {
      throw new Error("unsupported event");
    }
    super.off(event, listener);
    if (!this.hasAnyListeners()) {
      AddonManager.removeAddonListener(this);
    }
  }

  getExtensionInfo(addon) {
    let ext = WebExtensionPolicy.getByID(addon.id)?.extension;
    return getExtensionInfoForAddon(ext, addon);
  }

  onEnabled(addon) {
    if (!checkAllowedAddon(addon)) {
      return;
    }
    this.emit("onEnabled", this.getExtensionInfo(addon));
  }

  onDisabled(addon) {
    if (!checkAllowedAddon(addon)) {
      return;
    }
    this.emit("onDisabled", this.getExtensionInfo(addon));
  }

  onInstalled(addon) {
    if (!checkAllowedAddon(addon)) {
      return;
    }
    this.emit("onInstalled", this.getExtensionInfo(addon));
  }

  onUninstalled(addon) {
    if (!checkAllowedAddon(addon)) {
      return;
    }
    this.emit("onUninstalled", this.getExtensionInfo(addon));
  }
}

this.management = class extends ExtensionAPIPersistent {
  addonListener = new ManagementAddonListener();

  onShutdown() {
    AddonManager.removeAddonListener(this.addonListener);
  }

  eventRegistrar(eventName) {
    return ({ fire }) => {
      let listener = (event, data) => {
        fire.async(data);
      };

      this.addonListener.on(eventName, listener);
      return {
        unregister: () => {
          this.addonListener.off(eventName, listener);
        },
        convert(_fire) {
          fire = _fire;
        },
      };
    };
  }

  PERSISTENT_EVENTS = {
    onDisabled: this.eventRegistrar("onDisabled"),
    onEnabled: this.eventRegistrar("onEnabled"),
    onInstalled: this.eventRegistrar("onInstalled"),
    onUninstalled: this.eventRegistrar("onUninstalled"),
  };

  getAPI(context) {
    let { extension } = context;

    return {
      management: {
        async get(id) {
          let addon = await AddonManager.getAddonByID(id);
          if (!addon) {
            throw new ExtensionError(`No such addon ${id}`);
          }
          if (!checkAllowedAddon(addon)) {
            throw new ExtensionError("get not allowed for this addon");
          }
          // If the extension is enabled get it and use it for more data.
          let ext = WebExtensionPolicy.getByID(addon.id)?.extension;
          return getExtensionInfoForAddon(ext, addon);
        },

        async getAll() {
          let addons = await AddonManager.getAddonsByTypes(allowedTypes);
          return addons.filter(checkAllowedAddon).map(addon => {
            // If the extension is enabled get it and use it for more data.
            let ext = WebExtensionPolicy.getByID(addon.id)?.extension;
            return getExtensionInfoForAddon(ext, addon);
          });
        },

        async install({ url, hash }) {
          let listener = {
            onDownloadEnded(install) {
              if (install.addon.appDisabled || install.addon.type !== "theme") {
                install.cancel();
                return false;
              }
            },
          };

          let telemetryInfo = {
            source: "extension",
            method: "management-webext-api",
          };
          let install = await AddonManager.getInstallForURL(url, {
            hash,
            telemetryInfo,
            triggeringPrincipal: extension.principal,
          });
          install.addListener(listener);
          try {
            await install.install();
          } catch (e) {
            Cu.reportError(e);
            throw new ExtensionError("Incompatible addon");
          }
          await install.addon.enable();
          return { id: install.addon.id };
        },

        async getSelf() {
          let addon = await AddonManager.getAddonByID(extension.id);
          return getExtensionInfoForAddon(extension, addon);
        },

        async uninstallSelf(options) {
          if (options && options.showConfirmDialog) {
            let message = _("uninstall.confirmation.message", extension.name);
            if (options.dialogMessage) {
              message = `${options.dialogMessage}\n${message}`;
            }
            let title = _("uninstall.confirmation.title", extension.name);
            let buttonFlags =
              Ci.nsIPrompt.BUTTON_POS_0 * Ci.nsIPrompt.BUTTON_TITLE_IS_STRING +
              Ci.nsIPrompt.BUTTON_POS_1 * Ci.nsIPrompt.BUTTON_TITLE_IS_STRING;
            let button0Title = _("uninstall.confirmation.button-0.label");
            let button1Title = _("uninstall.confirmation.button-1.label");
            let response = promptService.confirmEx(
              null,
              title,
              message,
              buttonFlags,
              button0Title,
              button1Title,
              null,
              null,
              { value: 0 }
            );
            if (response == 1) {
              throw new ExtensionError("User cancelled uninstall of extension");
            }
          }
          let addon = await AddonManager.getAddonByID(extension.id);
          let canUninstall = Boolean(
            addon.permissions & AddonManager.PERM_CAN_UNINSTALL
          );
          if (!canUninstall) {
            throw new ExtensionError("The add-on cannot be uninstalled");
          }
          addon.uninstall();
        },

        async setEnabled(id, enabled) {
          let addon = await AddonManager.getAddonByID(id);
          if (!addon) {
            throw new ExtensionError(`No such addon ${id}`);
          }
          if (addon.type !== "theme") {
            throw new ExtensionError("setEnabled applies only to theme addons");
          }
          if (addon.isSystem) {
            throw new ExtensionError(
              "setEnabled cannot be used with a system addon"
            );
          }
          if (enabled) {
            await addon.enable();
          } else {
            await addon.disable();
          }
        },

        onDisabled: new EventManager({
          context,
          module: "management",
          event: "onDisabled",
          extensionApi: this,
        }).api(),

        onEnabled: new EventManager({
          context,
          module: "management",
          event: "onEnabled",
          extensionApi: this,
        }).api(),

        onInstalled: new EventManager({
          context,
          module: "management",
          event: "onInstalled",
          extensionApi: this,
        }).api(),

        onUninstalled: new EventManager({
          context,
          module: "management",
          event: "onUninstalled",
          extensionApi: this,
        }).api(),
      },
    };
  }
};
PK
!<S�Y.��=chrome/toolkit/content/extensions/parent/ext-networkStatus.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

XPCOMUtils.defineLazyServiceGetter(
  this,
  "gNetworkLinkService",
  "@mozilla.org/network/network-link-service;1",
  "nsINetworkLinkService"
);

function getLinkType() {
  switch (gNetworkLinkService.linkType) {
    case gNetworkLinkService.LINK_TYPE_UNKNOWN:
      return "unknown";
    case gNetworkLinkService.LINK_TYPE_ETHERNET:
      return "ethernet";
    case gNetworkLinkService.LINK_TYPE_USB:
      return "usb";
    case gNetworkLinkService.LINK_TYPE_WIFI:
      return "wifi";
    case gNetworkLinkService.LINK_TYPE_WIMAX:
      return "wimax";
    case gNetworkLinkService.LINK_TYPE_MOBILE:
      return "mobile";
    default:
      return "unknown";
  }
}

function getLinkStatus() {
  if (!gNetworkLinkService.linkStatusKnown) {
    return "unknown";
  }
  return gNetworkLinkService.isLinkUp ? "up" : "down";
}

function getLinkInfo() {
  return {
    id: gNetworkLinkService.networkID || undefined,
    status: getLinkStatus(),
    type: getLinkType(),
  };
}

this.networkStatus = class extends ExtensionAPI {
  getAPI(context) {
    return {
      networkStatus: {
        getLinkInfo,
        onConnectionChanged: new EventManager({
          context,
          name: "networkStatus.onConnectionChanged",
          register: fire => {
            let observerStatus = () => {
              fire.async(getLinkInfo());
            };

            Services.obs.addObserver(
              observerStatus,
              "network:link-status-changed"
            );
            Services.obs.addObserver(
              observerStatus,
              "network:link-type-changed"
            );
            return () => {
              Services.obs.removeObserver(
                observerStatus,
                "network:link-status-changed"
              );
              Services.obs.removeObserver(
                observerStatus,
                "network:link-type-changed"
              );
            };
          },
        }).api(),
      },
    };
  }
};
PK
!<D�kBB=chrome/toolkit/content/extensions/parent/ext-notifications.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const ToolkitModules = {};

ChromeUtils.defineESModuleGetters(ToolkitModules, {
  EventEmitter: "resource://gre/modules/EventEmitter.sys.mjs",
});

var { ignoreEvent } = ExtensionCommon;

// Manages a notification popup (notifications API) created by the extension.
function Notification(context, notificationsMap, id, options) {
  this.notificationsMap = notificationsMap;
  this.id = id;
  this.options = options;

  let imageURL;
  if (options.iconUrl) {
    imageURL = context.extension.baseURI.resolve(options.iconUrl);
  }

  // Set before calling into nsIAlertsService, because the notification may be
  // closed during the call.
  notificationsMap.set(id, this);

  try {
    let svc = Cc["@mozilla.org/alerts-service;1"].getService(
      Ci.nsIAlertsService
    );
    svc.showAlertNotification(
      imageURL,
      options.title,
      options.message,
      true, // textClickable
      this.id,
      this,
      this.id,
      undefined,
      undefined,
      undefined,
      // Principal is not set because doing so reveals buttons to control
      // notification preferences, which are currently not implemented for
      // notifications triggered via this extension API (bug 1589693).
      undefined,
      context.incognito
    );
  } catch (e) {
    // This will fail if alerts aren't available on the system.

    this.observe(null, "alertfinished", id);
  }
}

Notification.prototype = {
  clear() {
    try {
      let svc = Cc["@mozilla.org/alerts-service;1"].getService(
        Ci.nsIAlertsService
      );
      svc.closeAlert(this.id);
    } catch (e) {
      // This will fail if the OS doesn't support this function.
    }
    this.notificationsMap.delete(this.id);
  },

  observe(subject, topic, data) {
    switch (topic) {
      case "alertclickcallback":
        this.notificationsMap.emit("clicked", data);
        break;
      case "alertfinished":
        this.notificationsMap.emit("closed", data);
        this.notificationsMap.delete(this.id);
        break;
      case "alertshow":
        this.notificationsMap.emit("shown", data);
        break;
    }
  },
};

this.notifications = class extends ExtensionAPI {
  constructor(extension) {
    super(extension);

    this.nextId = 0;
    this.notificationsMap = new Map();
    ToolkitModules.EventEmitter.decorate(this.notificationsMap);
  }

  onShutdown() {
    for (let notification of this.notificationsMap.values()) {
      notification.clear();
    }
  }

  getAPI(context) {
    let notificationsMap = this.notificationsMap;

    return {
      notifications: {
        create: (notificationId, options) => {
          if (!notificationId) {
            notificationId = String(this.nextId++);
          }

          if (notificationsMap.has(notificationId)) {
            notificationsMap.get(notificationId).clear();
          }

          new Notification(context, notificationsMap, notificationId, options);

          return Promise.resolve(notificationId);
        },

        clear: function (notificationId) {
          if (notificationsMap.has(notificationId)) {
            notificationsMap.get(notificationId).clear();
            return Promise.resolve(true);
          }
          return Promise.resolve(false);
        },

        getAll: function () {
          let result = {};
          notificationsMap.forEach((value, key) => {
            result[key] = value.options;
          });
          return Promise.resolve(result);
        },

        onClosed: new EventManager({
          context,
          name: "notifications.onClosed",
          register: fire => {
            let listener = (event, notificationId) => {
              // TODO Bug 1413188, Support the byUser argument.
              fire.async(notificationId, true);
            };

            notificationsMap.on("closed", listener);
            return () => {
              notificationsMap.off("closed", listener);
            };
          },
        }).api(),

        onClicked: new EventManager({
          context,
          name: "notifications.onClicked",
          register: fire => {
            let listener = (event, notificationId) => {
              fire.async(notificationId);
            };

            notificationsMap.on("clicked", listener);
            return () => {
              notificationsMap.off("clicked", listener);
            };
          },
        }).api(),

        onShown: new EventManager({
          context,
          name: "notifications.onShown",
          register: fire => {
            let listener = (event, notificationId) => {
              fire.async(notificationId);
            };

            notificationsMap.on("shown", listener);
            return () => {
              notificationsMap.off("shown", listener);
            };
          },
        }).api(),

        // TODO Bug 1190681, implement button support.
        onButtonClicked: ignoreEvent(context, "notifications.onButtonClicked"),
      },
    };
  }
};
PK
!<RDmL��;chrome/toolkit/content/extensions/parent/ext-permissions.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

ChromeUtils.defineESModuleGetters(this, {
  ExtensionPermissions: "resource://gre/modules/ExtensionPermissions.sys.mjs",
});

var { ExtensionError } = ExtensionUtils;

XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "promptsEnabled",
  "extensions.webextOptionalPermissionPrompts"
);

function normalizePermissions(perms) {
  perms = { ...perms };
  perms.permissions = perms.permissions.filter(
    perm => !perm.startsWith("internal:") && perm !== "<all_urls>"
  );
  return perms;
}

this.permissions = class extends ExtensionAPIPersistent {
  PERSISTENT_EVENTS = {
    onAdded({ fire }) {
      let { extension } = this;
      let callback = (event, change) => {
        if (change.extensionId == extension.id && change.added) {
          let perms = normalizePermissions(change.added);
          if (perms.permissions.length || perms.origins.length) {
            fire.async(perms);
          }
        }
      };

      extensions.on("change-permissions", callback);
      return {
        unregister() {
          extensions.off("change-permissions", callback);
        },
        convert(_fire) {
          fire = _fire;
        },
      };
    },
    onRemoved({ fire }) {
      let { extension } = this;
      let callback = (event, change) => {
        if (change.extensionId == extension.id && change.removed) {
          let perms = normalizePermissions(change.removed);
          if (perms.permissions.length || perms.origins.length) {
            fire.async(perms);
          }
        }
      };

      extensions.on("change-permissions", callback);
      return {
        unregister() {
          extensions.off("change-permissions", callback);
        },
        convert(_fire) {
          fire = _fire;
        },
      };
    },
  };

  getAPI(context) {
    let { extension } = context;

    return {
      permissions: {
        async request(perms) {
          let { permissions, origins } = perms;

          let { optionalPermissions } = context.extension;
          for (let perm of permissions) {
            if (!optionalPermissions.includes(perm)) {
              throw new ExtensionError(
                `Cannot request permission ${perm} since it was not declared in optional_permissions`
              );
            }
          }

          let optionalOrigins = context.extension.optionalOrigins;
          for (let origin of origins) {
            if (!optionalOrigins.subsumes(new MatchPattern(origin))) {
              throw new ExtensionError(
                `Cannot request origin permission for ${origin} since it was not declared in the manifest`
              );
            }
          }

          if (promptsEnabled) {
            permissions = permissions.filter(
              perm => !context.extension.hasPermission(perm)
            );
            origins = origins.filter(
              origin =>
                !context.extension.allowedOrigins.subsumes(
                  new MatchPattern(origin)
                )
            );

            if (!permissions.length && !origins.length) {
              return true;
            }

            let browser = context.pendingEventBrowser || context.xulBrowser;
            let allowPromise = new Promise(resolve => {
              let subject = {
                wrappedJSObject: {
                  browser,
                  name: context.extension.name,
                  id: context.extension.id,
                  icon: context.extension.getPreferredIcon(32),
                  permissions: { permissions, origins },
                  resolve,
                },
              };
              Services.obs.notifyObservers(
                subject,
                "webextension-optional-permission-prompt"
              );
            });
            if (context.isBackgroundContext) {
              extension.emit("background-script-idle-waituntil", {
                promise: allowPromise,
                reason: "permissions_request",
              });
            }
            if (!(await allowPromise)) {
              return false;
            }
          }

          await ExtensionPermissions.add(extension.id, perms, extension);
          return true;
        },

        async getAll() {
          let perms = normalizePermissions(context.extension.activePermissions);
          delete perms.apis;
          return perms;
        },

        async contains(permissions) {
          for (let perm of permissions.permissions) {
            if (!context.extension.hasPermission(perm)) {
              return false;
            }
          }

          for (let origin of permissions.origins) {
            if (
              !context.extension.allowedOrigins.subsumes(
                new MatchPattern(origin)
              )
            ) {
              return false;
            }
          }

          return true;
        },

        async remove(permissions) {
          await ExtensionPermissions.remove(
            extension.id,
            permissions,
            extension
          );
          return true;
        },

        onAdded: new EventManager({
          context,
          module: "permissions",
          event: "onAdded",
          extensionApi: this,
        }).api(),

        onRemoved: new EventManager({
          context,
          module: "permissions",
          event: "onRemoved",
          extensionApi: this,
        }).api(),
      },
    };
  }
};
PK
!<�-��7�77chrome/toolkit/content/extensions/parent/ext-privacy.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var { ExtensionPreferencesManager } = ChromeUtils.importESModule(
  "resource://gre/modules/ExtensionPreferencesManager.sys.mjs"
);

var { ExtensionError } = ExtensionUtils;
var { getSettingsAPI, getPrimedSettingsListener } = ExtensionPreferencesManager;

const cookieSvc = Ci.nsICookieService;

const getIntPref = p => Services.prefs.getIntPref(p, undefined);
const getBoolPref = p => Services.prefs.getBoolPref(p, undefined);

const TLS_MIN_PREF = "security.tls.version.min";
const TLS_MAX_PREF = "security.tls.version.max";

const cookieBehaviorValues = new Map([
  ["allow_all", cookieSvc.BEHAVIOR_ACCEPT],
  ["reject_third_party", cookieSvc.BEHAVIOR_REJECT_FOREIGN],
  ["reject_all", cookieSvc.BEHAVIOR_REJECT],
  ["allow_visited", cookieSvc.BEHAVIOR_LIMIT_FOREIGN],
  ["reject_trackers", cookieSvc.BEHAVIOR_REJECT_TRACKER],
  [
    "reject_trackers_and_partition_foreign",
    cookieSvc.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN,
  ],
]);

function isTLSMinVersionLowerOrEQThan(version) {
  return (
    Services.prefs.getDefaultBranch("").getIntPref(TLS_MIN_PREF) <= version
  );
}

const TLS_VERSIONS = [
  { version: 1, name: "TLSv1", settable: isTLSMinVersionLowerOrEQThan(1) },
  { version: 2, name: "TLSv1.1", settable: isTLSMinVersionLowerOrEQThan(2) },
  { version: 3, name: "TLSv1.2", settable: true },
  { version: 4, name: "TLSv1.3", settable: true },
];

// Add settings objects for supported APIs to the preferences manager.
ExtensionPreferencesManager.addSetting("network.networkPredictionEnabled", {
  permission: "privacy",
  prefNames: [
    "network.predictor.enabled",
    "network.prefetch-next",
    "network.http.speculative-parallel-limit",
    "network.dns.disablePrefetch",
  ],

  setCallback(value) {
    return {
      "network.http.speculative-parallel-limit": value ? undefined : 0,
      "network.dns.disablePrefetch": !value,
      "network.predictor.enabled": value,
      "network.prefetch-next": value,
    };
  },

  getCallback() {
    return (
      getBoolPref("network.predictor.enabled") &&
      getBoolPref("network.prefetch-next") &&
      getIntPref("network.http.speculative-parallel-limit") > 0 &&
      !getBoolPref("network.dns.disablePrefetch")
    );
  },
});

ExtensionPreferencesManager.addSetting("network.globalPrivacyControl", {
  permission: "privacy",
  prefNames: ["privacy.globalprivacycontrol.enabled"],
  readOnly: true,

  setCallback(value) {
    return {
      "privacy.globalprivacycontrol.enabled": value,
    };
  },

  getCallback() {
    return getBoolPref("privacy.globalprivacycontrol.enabled");
  },
});

ExtensionPreferencesManager.addSetting("network.httpsOnlyMode", {
  permission: "privacy",
  prefNames: [
    "dom.security.https_only_mode",
    "dom.security.https_only_mode_pbm",
  ],
  readOnly: true,

  setCallback(value) {
    let prefs = {
      "dom.security.https_only_mode": false,
      "dom.security.https_only_mode_pbm": false,
    };

    switch (value) {
      case "always":
        prefs["dom.security.https_only_mode"] = true;
        break;

      case "private_browsing":
        prefs["dom.security.https_only_mode_pbm"] = true;
        break;

      case "never":
        break;
    }

    return prefs;
  },

  getCallback() {
    if (getBoolPref("dom.security.https_only_mode")) {
      return "always";
    }
    if (getBoolPref("dom.security.https_only_mode_pbm")) {
      return "private_browsing";
    }
    return "never";
  },
});

ExtensionPreferencesManager.addSetting("network.peerConnectionEnabled", {
  permission: "privacy",
  prefNames: ["media.peerconnection.enabled"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return getBoolPref("media.peerconnection.enabled");
  },
});

ExtensionPreferencesManager.addSetting("network.webRTCIPHandlingPolicy", {
  permission: "privacy",
  prefNames: [
    "media.peerconnection.ice.default_address_only",
    "media.peerconnection.ice.no_host",
    "media.peerconnection.ice.proxy_only_if_behind_proxy",
    "media.peerconnection.ice.proxy_only",
  ],

  setCallback(value) {
    let prefs = {};
    switch (value) {
      case "default":
        // All prefs are already set to be reset.
        break;

      case "default_public_and_private_interfaces":
        prefs["media.peerconnection.ice.default_address_only"] = true;
        break;

      case "default_public_interface_only":
        prefs["media.peerconnection.ice.default_address_only"] = true;
        prefs["media.peerconnection.ice.no_host"] = true;
        break;

      case "disable_non_proxied_udp":
        prefs["media.peerconnection.ice.default_address_only"] = true;
        prefs["media.peerconnection.ice.no_host"] = true;
        prefs["media.peerconnection.ice.proxy_only_if_behind_proxy"] = true;
        break;

      case "proxy_only":
        prefs["media.peerconnection.ice.proxy_only"] = true;
        break;
    }
    return prefs;
  },

  getCallback() {
    if (getBoolPref("media.peerconnection.ice.proxy_only")) {
      return "proxy_only";
    }

    let default_address_only = getBoolPref(
      "media.peerconnection.ice.default_address_only"
    );
    if (default_address_only) {
      let no_host = getBoolPref("media.peerconnection.ice.no_host");
      if (no_host) {
        if (
          getBoolPref("media.peerconnection.ice.proxy_only_if_behind_proxy")
        ) {
          return "disable_non_proxied_udp";
        }
        return "default_public_interface_only";
      }
      return "default_public_and_private_interfaces";
    }

    return "default";
  },
});

ExtensionPreferencesManager.addSetting("services.passwordSavingEnabled", {
  permission: "privacy",
  prefNames: ["signon.rememberSignons"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return getBoolPref("signon.rememberSignons");
  },
});

ExtensionPreferencesManager.addSetting("websites.cookieConfig", {
  permission: "privacy",
  prefNames: ["network.cookie.cookieBehavior"],

  setCallback(value) {
    const cookieBehavior = cookieBehaviorValues.get(value.behavior);

    // Intentionally use Preferences.get("network.cookie.cookieBehavior") here
    // to read the "real" preference value.
    const needUpdate =
      cookieBehavior !== getIntPref("network.cookie.cookieBehavior");
    if (
      needUpdate &&
      getBoolPref("privacy.firstparty.isolate") &&
      cookieBehavior === cookieSvc.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN
    ) {
      throw new ExtensionError(
        `Invalid cookieConfig '${value.behavior}' when firstPartyIsolate is enabled`
      );
    }

    if (typeof value.nonPersistentCookies === "boolean") {
      Cu.reportError(
        "'nonPersistentCookies' has been deprecated and it has no effect anymore."
      );
    }

    return {
      "network.cookie.cookieBehavior": cookieBehavior,
    };
  },

  getCallback() {
    let prefValue = getIntPref("network.cookie.cookieBehavior");
    return {
      behavior: Array.from(cookieBehaviorValues.entries()).find(
        entry => entry[1] === prefValue
      )[0],
      // Bug 1754924 - this property is now deprecated.
      nonPersistentCookies: false,
    };
  },
});

ExtensionPreferencesManager.addSetting("websites.firstPartyIsolate", {
  permission: "privacy",
  prefNames: ["privacy.firstparty.isolate"],

  setCallback(value) {
    // Intentionally use Preferences.get("network.cookie.cookieBehavior") here
    // to read the "real" preference value.
    const cookieBehavior = getIntPref("network.cookie.cookieBehavior");

    const needUpdate = value !== getBoolPref("privacy.firstparty.isolate");
    if (
      needUpdate &&
      value &&
      cookieBehavior === cookieSvc.BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN
    ) {
      const behavior = Array.from(cookieBehaviorValues.entries()).find(
        entry => entry[1] === cookieBehavior
      )[0];
      throw new ExtensionError(
        `Can't enable firstPartyIsolate when cookieBehavior is '${behavior}'`
      );
    }

    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return getBoolPref("privacy.firstparty.isolate");
  },
});

ExtensionPreferencesManager.addSetting("websites.hyperlinkAuditingEnabled", {
  permission: "privacy",
  prefNames: ["browser.send_pings"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return getBoolPref("browser.send_pings");
  },
});

ExtensionPreferencesManager.addSetting("websites.referrersEnabled", {
  permission: "privacy",
  prefNames: ["network.http.sendRefererHeader"],

  // Values for network.http.sendRefererHeader:
  // 0=don't send any, 1=send only on clicks, 2=send on image requests as well
  // http://searchfox.org/mozilla-central/rev/61054508641ee76f9c49bcf7303ef3cfb6b410d2/modules/libpref/init/all.js#1585
  setCallback(value) {
    return { [this.prefNames[0]]: value ? 2 : 0 };
  },

  getCallback() {
    return getIntPref("network.http.sendRefererHeader") !== 0;
  },
});

ExtensionPreferencesManager.addSetting("websites.resistFingerprinting", {
  permission: "privacy",
  prefNames: ["privacy.resistFingerprinting"],

  setCallback(value) {
    return { [this.prefNames[0]]: value };
  },

  getCallback() {
    return getBoolPref("privacy.resistFingerprinting");
  },
});

ExtensionPreferencesManager.addSetting("websites.trackingProtectionMode", {
  permission: "privacy",
  prefNames: [
    "privacy.trackingprotection.enabled",
    "privacy.trackingprotection.pbmode.enabled",
  ],

  setCallback(value) {
    // Default to private browsing.
    let prefs = {
      "privacy.trackingprotection.enabled": false,
      "privacy.trackingprotection.pbmode.enabled": true,
    };

    switch (value) {
      case "private_browsing":
        break;

      case "always":
        prefs["privacy.trackingprotection.enabled"] = true;
        break;

      case "never":
        prefs["privacy.trackingprotection.pbmode.enabled"] = false;
        break;
    }

    return prefs;
  },

  getCallback() {
    if (getBoolPref("privacy.trackingprotection.enabled")) {
      return "always";
    } else if (getBoolPref("privacy.trackingprotection.pbmode.enabled")) {
      return "private_browsing";
    }
    return "never";
  },
});

ExtensionPreferencesManager.addSetting("network.tlsVersionRestriction", {
  permission: "privacy",
  prefNames: [TLS_MIN_PREF, TLS_MAX_PREF],

  setCallback(value) {
    function tlsStringToVersion(string) {
      const version = TLS_VERSIONS.find(a => a.name === string);
      if (version && version.settable) {
        return version.version;
      }

      throw new ExtensionError(
        `Setting TLS version ${string} is not allowed for security reasons.`
      );
    }

    const prefs = {};

    if (value.minimum) {
      prefs[TLS_MIN_PREF] = tlsStringToVersion(value.minimum);
    }

    if (value.maximum) {
      prefs[TLS_MAX_PREF] = tlsStringToVersion(value.maximum);
    }

    // If minimum has passed and it's greater than the max value.
    if (prefs[TLS_MIN_PREF]) {
      const max = prefs[TLS_MAX_PREF] || getIntPref(TLS_MAX_PREF);
      if (max < prefs[TLS_MIN_PREF]) {
        throw new ExtensionError(
          `Setting TLS min version grater than the max version is not allowed.`
        );
      }
    }

    // If maximum has passed and it's lower than the min value.
    else if (prefs[TLS_MAX_PREF]) {
      const min = getIntPref(TLS_MIN_PREF);
      if (min > prefs[TLS_MAX_PREF]) {
        throw new ExtensionError(
          `Setting TLS max version lower than the min version is not allowed.`
        );
      }
    }

    return prefs;
  },

  getCallback() {
    function tlsVersionToString(pref) {
      const value = getIntPref(pref);
      const version = TLS_VERSIONS.find(a => a.version === value);
      if (version) {
        return version.name;
      }
      return "unknown";
    }

    return {
      minimum: tlsVersionToString(TLS_MIN_PREF),
      maximum: tlsVersionToString(TLS_MAX_PREF),
    };
  },

  validate(extension) {
    if (!extension.isPrivileged) {
      throw new ExtensionError(
        "tlsVersionRestriction can be set by privileged extensions only."
      );
    }
  },
});

this.privacy = class extends ExtensionAPI {
  primeListener(event, fire) {
    let { extension } = this;
    let listener = getPrimedSettingsListener({
      extension,
      name: event,
    });
    return listener(fire);
  }

  getAPI(context) {
    function makeSettingsAPI(name) {
      return getSettingsAPI({
        context,
        module: "privacy",
        name,
      });
    }

    return {
      privacy: {
        network: {
          networkPredictionEnabled: makeSettingsAPI(
            "network.networkPredictionEnabled"
          ),
          globalPrivacyControl: makeSettingsAPI("network.globalPrivacyControl"),
          httpsOnlyMode: makeSettingsAPI("network.httpsOnlyMode"),
          peerConnectionEnabled: makeSettingsAPI(
            "network.peerConnectionEnabled"
          ),
          webRTCIPHandlingPolicy: makeSettingsAPI(
            "network.webRTCIPHandlingPolicy"
          ),
          tlsVersionRestriction: makeSettingsAPI(
            "network.tlsVersionRestriction"
          ),
        },

        services: {
          passwordSavingEnabled: makeSettingsAPI(
            "services.passwordSavingEnabled"
          ),
        },

        websites: {
          cookieConfig: makeSettingsAPI("websites.cookieConfig"),
          firstPartyIsolate: makeSettingsAPI("websites.firstPartyIsolate"),
          hyperlinkAuditingEnabled: makeSettingsAPI(
            "websites.hyperlinkAuditingEnabled"
          ),
          referrersEnabled: makeSettingsAPI("websites.referrersEnabled"),
          resistFingerprinting: makeSettingsAPI(
            "websites.resistFingerprinting"
          ),
          trackingProtectionMode: makeSettingsAPI(
            "websites.trackingProtectionMode"
          ),
        },
      },
    };
  }
};
PK
!<̔�b��@chrome/toolkit/content/extensions/parent/ext-protocolHandlers.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

XPCOMUtils.defineLazyServiceGetter(
  this,
  "handlerService",
  "@mozilla.org/uriloader/handler-service;1",
  "nsIHandlerService"
);
XPCOMUtils.defineLazyServiceGetter(
  this,
  "protocolService",
  "@mozilla.org/uriloader/external-protocol-service;1",
  "nsIExternalProtocolService"
);

const hasHandlerApp = handlerConfig => {
  let protoInfo = protocolService.getProtocolHandlerInfo(
    handlerConfig.protocol
  );
  let appHandlers = protoInfo.possibleApplicationHandlers;
  for (let i = 0; i < appHandlers.length; i++) {
    let handler = appHandlers.queryElementAt(i, Ci.nsISupports);
    if (
      handler instanceof Ci.nsIWebHandlerApp &&
      handler.uriTemplate === handlerConfig.uriTemplate
    ) {
      return true;
    }
  }
  return false;
};

this.protocolHandlers = class extends ExtensionAPI {
  onManifestEntry() {
    let { extension } = this;
    let { manifest } = extension;

    for (let handlerConfig of manifest.protocol_handlers) {
      if (hasHandlerApp(handlerConfig)) {
        continue;
      }

      let handler = Cc[
        "@mozilla.org/uriloader/web-handler-app;1"
      ].createInstance(Ci.nsIWebHandlerApp);
      handler.name = handlerConfig.name;
      handler.uriTemplate = handlerConfig.uriTemplate;

      let protoInfo = protocolService.getProtocolHandlerInfo(
        handlerConfig.protocol
      );
      let handlers = protoInfo.possibleApplicationHandlers;
      if (protoInfo.preferredApplicationHandler || handlers.length) {
        protoInfo.alwaysAskBeforeHandling = true;
      } else {
        protoInfo.preferredApplicationHandler = handler;
        protoInfo.alwaysAskBeforeHandling = false;
      }
      handlers.appendElement(handler);
      handlerService.store(protoInfo);
    }
  }

  onShutdown(isAppShutdown) {
    let { extension } = this;
    let { manifest } = extension;

    if (isAppShutdown) {
      return;
    }

    for (let handlerConfig of manifest.protocol_handlers) {
      let protoInfo = protocolService.getProtocolHandlerInfo(
        handlerConfig.protocol
      );
      let appHandlers = protoInfo.possibleApplicationHandlers;
      for (let i = 0; i < appHandlers.length; i++) {
        let handler = appHandlers.queryElementAt(i, Ci.nsISupports);
        if (
          handler instanceof Ci.nsIWebHandlerApp &&
          handler.uriTemplate === handlerConfig.uriTemplate
        ) {
          appHandlers.removeElementAt(i);
          if (protoInfo.preferredApplicationHandler === handler) {
            protoInfo.preferredApplicationHandler = null;
            protoInfo.alwaysAskBeforeHandling = true;
          }
          handlerService.store(protoInfo);
          break;
        }
      }
    }
  }
};
PK
!<=���+�+5chrome/toolkit/content/extensions/parent/ext-proxy.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

ChromeUtils.defineESModuleGetters(this, {
  ProxyChannelFilter: "resource://gre/modules/ProxyChannelFilter.sys.mjs",
});

// Delayed wakeup is tied to ExtensionParent.browserPaintedPromise, which is
// when the first browser window has been painted. On Android, parts of the
// browser can trigger requests without browser "window" (geckoview.xhtml).
// Therefore we allow such proxy events to trigger wakeup.
// On desktop, we do not wake up early, to minimize the amount of work before
// a browser window is painted.
XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "isEarlyWakeupOnRequestEnabled",
  "extensions.webextensions.early_background_wakeup_on_request",
  false
);
var { ExtensionPreferencesManager } = ChromeUtils.importESModule(
  "resource://gre/modules/ExtensionPreferencesManager.sys.mjs"
);

var { ExtensionError } = ExtensionUtils;
var { getSettingsAPI } = ExtensionPreferencesManager;

const proxySvc = Ci.nsIProtocolProxyService;

const PROXY_TYPES_MAP = new Map([
  ["none", proxySvc.PROXYCONFIG_DIRECT],
  ["autoDetect", proxySvc.PROXYCONFIG_WPAD],
  ["system", proxySvc.PROXYCONFIG_SYSTEM],
  ["manual", proxySvc.PROXYCONFIG_MANUAL],
  ["autoConfig", proxySvc.PROXYCONFIG_PAC],
]);

const DEFAULT_PORTS = new Map([
  ["http", 80],
  ["ssl", 443],
  ["socks", 1080],
]);

ExtensionPreferencesManager.addSetting("proxy.settings", {
  permission: "proxy",
  prefNames: [
    "network.proxy.type",
    "network.proxy.http",
    "network.proxy.http_port",
    "network.proxy.share_proxy_settings",
    "network.proxy.ssl",
    "network.proxy.ssl_port",
    "network.proxy.socks",
    "network.proxy.socks_port",
    "network.proxy.socks_version",
    "network.proxy.socks_remote_dns",
    "network.proxy.socks5_remote_dns",
    "network.proxy.no_proxies_on",
    "network.proxy.autoconfig_url",
    "signon.autologin.proxy",
    "network.http.proxy.respect-be-conservative",
  ],

  setCallback(value) {
    let prefs = {
      "network.proxy.type": PROXY_TYPES_MAP.get(value.proxyType),
      "signon.autologin.proxy": value.autoLogin,
      "network.proxy.socks_remote_dns": value.proxyDNS,
      "network.proxy.socks5_remote_dns": value.proxyDNS,
      "network.proxy.autoconfig_url": value.autoConfigUrl,
      "network.proxy.share_proxy_settings": value.httpProxyAll,
      "network.proxy.socks_version": value.socksVersion,
      "network.proxy.no_proxies_on": value.passthrough,
      "network.http.proxy.respect-be-conservative": value.respectBeConservative,
    };

    for (let prop of ["http", "ssl", "socks"]) {
      if (value[prop]) {
        let url = new URL(`http://${value[prop]}`);
        prefs[`network.proxy.${prop}`] = url.hostname;
        // Only fall back to defaults if no port provided.
        let [, rawPort] = value[prop].split(":");
        let port = parseInt(rawPort, 10) || DEFAULT_PORTS.get(prop);
        prefs[`network.proxy.${prop}_port`] = port;
      }
    }

    return prefs;
  },
});

function registerProxyFilterEvent(
  context,
  extension,
  fire,
  filterProps,
  extraInfoSpec = []
) {
  let listener = data => {
    if (isEarlyWakeupOnRequestEnabled && fire.wakeup) {
      // Starts the background script if it has not started, no-op otherwise.
      extension.emit("start-background-script");
    }
    return fire.sync(data);
  };

  let filter = { ...filterProps };
  if (filter.urls) {
    let perms = new MatchPatternSet([
      ...extension.allowedOrigins.patterns,
      ...extension.optionalOrigins.patterns,
    ]);
    filter.urls = new MatchPatternSet(filter.urls);

    if (!perms.overlapsAll(filter.urls)) {
      Cu.reportError(
        "The proxy.onRequest filter doesn't overlap with host permissions."
      );
    }
  }

  let proxyFilter = new ProxyChannelFilter(
    context,
    extension,
    listener,
    filter,
    extraInfoSpec
  );
  return {
    unregister: () => {
      proxyFilter.destroy();
    },
    convert(_fire, _context) {
      fire = _fire;
      proxyFilter.context = _context;
    },
  };
}

this.proxy = class extends ExtensionAPIPersistent {
  PERSISTENT_EVENTS = {
    onRequest({ fire, context }, params) {
      return registerProxyFilterEvent(context, this.extension, fire, ...params);
    },
  };

  getAPI(context) {
    let { extension } = context;
    let self = this;

    return {
      proxy: {
        onRequest: new EventManager({
          context,
          module: "proxy",
          event: "onRequest",
          extensionApi: self,
        }).api(),

        // Leaving as non-persistent.  By itself it's not useful since proxy-error
        // is emitted from the proxy filter.
        onError: new EventManager({
          context,
          name: "proxy.onError",
          register: fire => {
            let listener = (name, error) => {
              fire.async(error);
            };
            extension.on("proxy-error", listener);
            return () => {
              extension.off("proxy-error", listener);
            };
          },
        }).api(),

        settings: Object.assign(
          getSettingsAPI({
            context,
            name: "proxy.settings",
            callback() {
              let prefValue = Services.prefs.getIntPref("network.proxy.type");
              let socksVersion = Services.prefs.getIntPref(
                "network.proxy.socks_version"
              );
              let proxyDNS;
              if (socksVersion == 4) {
                proxyDNS = Services.prefs.getBoolPref(
                  "network.proxy.socks_remote_dns"
                );
              } else {
                proxyDNS = Services.prefs.getBoolPref(
                  "network.proxy.socks5_remote_dns"
                );
              }

              let proxyConfig = {
                proxyType: Array.from(PROXY_TYPES_MAP.entries()).find(
                  entry => entry[1] === prefValue
                )[0],
                autoConfigUrl: Services.prefs.getCharPref(
                  "network.proxy.autoconfig_url"
                ),
                autoLogin: Services.prefs.getBoolPref("signon.autologin.proxy"),
                proxyDNS,
                httpProxyAll: Services.prefs.getBoolPref(
                  "network.proxy.share_proxy_settings"
                ),
                socksVersion,
                passthrough: Services.prefs.getCharPref(
                  "network.proxy.no_proxies_on"
                ),
              };

              if (extension.isPrivileged) {
                proxyConfig.respectBeConservative = Services.prefs.getBoolPref(
                  "network.http.proxy.respect-be-conservative"
                );
              }

              for (let prop of ["http", "ssl", "socks"]) {
                let host = Services.prefs.getCharPref(`network.proxy.${prop}`);
                let port = Services.prefs.getIntPref(
                  `network.proxy.${prop}_port`
                );
                proxyConfig[prop] = port ? `${host}:${port}` : host;
              }

              return proxyConfig;
            },
            // proxy.settings is unsupported on android.
            validate() {
              if (AppConstants.platform == "android") {
                throw new ExtensionError(
                  `proxy.settings is not supported on android.`
                );
              }
            },
          }),
          {
            set: details => {
              if (AppConstants.platform === "android") {
                throw new ExtensionError(
                  "proxy.settings is not supported on android."
                );
              }

              if (!extension.privateBrowsingAllowed) {
                throw new ExtensionError(
                  "proxy.settings requires private browsing permission."
                );
              }

              if (!Services.policies.isAllowed("changeProxySettings")) {
                throw new ExtensionError(
                  "Proxy settings are being managed by the Policies manager."
                );
              }

              let value = details.value;

              // proxyType is optional and it should default to "system" when missing.
              if (value.proxyType == null) {
                value.proxyType = "system";
              }

              if (!PROXY_TYPES_MAP.has(value.proxyType)) {
                throw new ExtensionError(
                  `${value.proxyType} is not a valid value for proxyType.`
                );
              }

              if (value.httpProxyAll) {
                // Match what about:preferences does with proxy settings
                // since the proxy service does not check the value
                // of share_proxy_settings.
                value.ssl = value.http;
              }

              for (let prop of ["http", "ssl", "socks"]) {
                let host = value[prop];
                if (host) {
                  try {
                    // Fixup in case a full url is passed.
                    if (host.includes("://")) {
                      value[prop] = new URL(host).host;
                    } else {
                      // Validate the host value.
                      new URL(`http://${host}`);
                    }
                  } catch (e) {
                    throw new ExtensionError(
                      `${value[prop]} is not a valid value for ${prop}.`
                    );
                  }
                }
              }

              if (value.proxyType === "autoConfig" || value.autoConfigUrl) {
                try {
                  new URL(value.autoConfigUrl);
                } catch (e) {
                  throw new ExtensionError(
                    `${value.autoConfigUrl} is not a valid value for autoConfigUrl.`
                  );
                }
              }

              if (value.socksVersion !== undefined) {
                if (
                  !Number.isInteger(value.socksVersion) ||
                  value.socksVersion < 4 ||
                  value.socksVersion > 5
                ) {
                  throw new ExtensionError(
                    `${value.socksVersion} is not a valid value for socksVersion.`
                  );
                }
              }

              if (
                value.respectBeConservative !== undefined &&
                !extension.isPrivileged &&
                Services.prefs.getBoolPref(
                  "network.http.proxy.respect-be-conservative"
                ) != value.respectBeConservative
              ) {
                throw new ExtensionError(
                  `respectBeConservative can be set by privileged extensions only.`
                );
              }

              return ExtensionPreferencesManager.setSetting(
                extension.id,
                "proxy.settings",
                value
              );
            },
          }
        ),
      },
    };
  }
};
PK
!<�m�U..7chrome/toolkit/content/extensions/parent/ext-storage.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

ChromeUtils.defineESModuleGetters(this, {
  AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs",
  ExtensionStorage: "resource://gre/modules/ExtensionStorage.sys.mjs",
  ExtensionStorageIDB: "resource://gre/modules/ExtensionStorageIDB.sys.mjs",
  NativeManifests: "resource://gre/modules/NativeManifests.sys.mjs",
  extensionStorageSession: "resource://gre/modules/ExtensionStorage.sys.mjs",
});

var { ExtensionError } = ExtensionUtils;
var { ignoreEvent } = ExtensionCommon;

ChromeUtils.defineLazyGetter(this, "extensionStorageSync", () => {
  // TODO bug 1637465: Remove Kinto-based implementation.
  if (Services.prefs.getBoolPref("webextensions.storage.sync.kinto")) {
    const { extensionStorageSyncKinto } = ChromeUtils.importESModule(
      "resource://gre/modules/ExtensionStorageSyncKinto.sys.mjs"
    );
    return extensionStorageSyncKinto;
  }

  const { extensionStorageSync } = ChromeUtils.importESModule(
    "resource://gre/modules/ExtensionStorageSync.sys.mjs"
  );
  return extensionStorageSync;
});

const enforceNoTemporaryAddon = extensionId => {
  const EXCEPTION_MESSAGE =
    "The storage API will not work with a temporary addon ID. " +
    "Please add an explicit addon ID to your manifest. " +
    "For more information see https://mzl.la/3lPk1aE.";
  if (AddonManagerPrivate.isTemporaryInstallID(extensionId)) {
    throw new ExtensionError(EXCEPTION_MESSAGE);
  }
};

// WeakMap[extension -> Promise<SerializableMap?>]
const managedStorage = new WeakMap();

const lookupManagedStorage = async (extensionId, context) => {
  if (Services.policies) {
    let extensionPolicy = Services.policies.getExtensionPolicy(extensionId);
    if (extensionPolicy) {
      return ExtensionStorage._serializableMap(extensionPolicy);
    }
  }
  let info = await NativeManifests.lookupManifest(
    "storage",
    extensionId,
    context
  );
  if (info) {
    return ExtensionStorage._serializableMap(info.manifest.data);
  }
  return null;
};

this.storage = class extends ExtensionAPIPersistent {
  constructor(extension) {
    super(extension);

    const messageName = `Extension:StorageLocalOnChanged:${extension.uuid}`;
    Services.ppmm.addMessageListener(messageName, this);
    this.clearStorageChangedListener = () => {
      Services.ppmm.removeMessageListener(messageName, this);
    };
  }

  PERSISTENT_EVENTS = {
    onChanged({ context, fire }) {
      let unregisterLocal = this.registerLocalChangedListener(changes => {
        // |changes| is already serialized. Send the raw value, so that it can
        // be deserialized by the onChanged handler in child/ext-storage.js.
        fire.raw(changes, "local");
      });

      // Session storage is not exposed to content scripts, and `context` does
      // not exist while setting up persistent listeners for an event page.
      let unregisterSession;
      if (
        !context ||
        context.envType === "addon_parent" ||
        context.envType === "devtools_parent"
      ) {
        unregisterSession = extensionStorageSession.registerListener(
          this.extension,
          changes => fire.async(changes, "session")
        );
      }

      let unregisterSync = this.registerSyncChangedListener(changes => {
        fire.async(changes, "sync");
      });

      return {
        unregister() {
          unregisterLocal();
          unregisterSession?.();
          unregisterSync();
        },
        convert(_fire) {
          fire = _fire;
        },
      };
    },
    "local.onChanged"({ fire }) {
      let unregister = this.registerLocalChangedListener(changes => {
        // |changes| is already serialized. Send the raw value, so that it can
        // be deserialized by the onChanged handler in child/ext-storage.js.
        fire.raw(changes);
      });
      return {
        unregister,
        convert(_fire) {
          fire = _fire;
        },
      };
    },
    "session.onChanged"({ fire }) {
      let unregister = extensionStorageSession.registerListener(
        this.extension,
        changes => fire.async(changes)
      );

      return {
        unregister,
        convert(_fire) {
          fire = _fire;
        },
      };
    },
    "sync.onChanged"({ fire }) {
      let unregister = this.registerSyncChangedListener(changes => {
        fire.async(changes);
      });
      return {
        unregister,
        convert(_fire) {
          fire = _fire;
        },
      };
    },
  };

  registerLocalChangedListener(onStorageLocalChanged) {
    const extensionId = this.extension.id;
    ExtensionStorage.addOnChangedListener(extensionId, onStorageLocalChanged);
    ExtensionStorageIDB.addOnChangedListener(
      extensionId,
      onStorageLocalChanged
    );
    return () => {
      ExtensionStorage.removeOnChangedListener(
        extensionId,
        onStorageLocalChanged
      );
      ExtensionStorageIDB.removeOnChangedListener(
        extensionId,
        onStorageLocalChanged
      );
    };
  }

  registerSyncChangedListener(onStorageSyncChanged) {
    const { extension } = this;
    let closeCallback;
    // The ExtensionStorageSyncKinto implementation of addOnChangedListener
    // relies on context.callOnClose (via ExtensionStorageSync.registerInUse)
    // to keep track of active users of the storage. We don't need to pass a
    // real BaseContext instance, a dummy object with the callOnClose method
    // works too. This enables us to register a primed listener before any
    // context is available.
    // TODO bug 1637465: Remove this when the Kinto backend is dropped.
    let dummyContextForKinto = {
      callOnClose({ close }) {
        closeCallback = close;
      },
    };
    extensionStorageSync.addOnChangedListener(
      extension,
      onStorageSyncChanged,
      dummyContextForKinto
    );
    return () => {
      extensionStorageSync.removeOnChangedListener(
        extension,
        onStorageSyncChanged
      );
      // May be void if ExtensionStorageSyncKinto.sys.mjs was not used.
      // ExtensionStorageSync.sys.mjs does not use the context.
      closeCallback?.();
    };
  }

  onShutdown() {
    const { clearStorageChangedListener } = this;
    this.clearStorageChangedListener = null;

    if (clearStorageChangedListener) {
      clearStorageChangedListener();
    }
  }

  receiveMessage({ name, data }) {
    if (name !== `Extension:StorageLocalOnChanged:${this.extension.uuid}`) {
      return;
    }

    ExtensionStorageIDB.notifyListeners(this.extension.id, data);
  }

  getAPI(context) {
    let { extension } = context;

    return {
      storage: {
        local: {
          async callMethodInParentProcess(method, args) {
            const res = await ExtensionStorageIDB.selectBackend({ extension });
            if (!res.backendEnabled) {
              return ExtensionStorage[method](extension.id, ...args);
            }

            const persisted = extension.hasPermission("unlimitedStorage");
            const db = await ExtensionStorageIDB.open(
              res.storagePrincipal.deserialize(this, true),
              persisted
            );
            try {
              const changes = await db[method](...args);
              if (changes) {
                ExtensionStorageIDB.notifyListeners(extension.id, changes);
              }
              return changes;
            } catch (err) {
              const normalizedError = ExtensionStorageIDB.normalizeStorageError(
                {
                  error: err,
                  extensionId: extension.id,
                  storageMethod: method,
                }
              ).message;
              return Promise.reject({
                message: String(normalizedError),
              });
            }
          },
          // Private storage.local JSONFile backend methods (used internally by the child
          // ext-storage.js module).
          JSONFileBackend: {
            get(spec) {
              return ExtensionStorage.get(extension.id, spec);
            },
            set(items) {
              return ExtensionStorage.set(extension.id, items);
            },
            remove(keys) {
              return ExtensionStorage.remove(extension.id, keys);
            },
            clear() {
              return ExtensionStorage.clear(extension.id);
            },
          },
          // Private storage.local IDB backend methods (used internally by the child ext-storage.js
          // module).
          IDBBackend: {
            selectBackend() {
              return ExtensionStorageIDB.selectBackend(context);
            },
          },
          onChanged: new EventManager({
            context,
            module: "storage",
            event: "local.onChanged",
            extensionApi: this,
          }).api(),
        },

        session: {
          get QUOTA_BYTES() {
            return extensionStorageSession.QUOTA_BYTES;
          },
          get(items) {
            return extensionStorageSession.get(extension, items);
          },
          set(items) {
            extensionStorageSession.set(extension, items);
          },
          remove(keys) {
            extensionStorageSession.remove(extension, keys);
          },
          clear() {
            extensionStorageSession.clear(extension);
          },
          getBytesInUse(keys) {
            return extensionStorageSession.getBytesInUse(extension, keys);
          },
          onChanged: new EventManager({
            context,
            module: "storage",
            event: "session.onChanged",
            extensionApi: this,
          }).api(),
        },

        sync: {
          get(spec) {
            enforceNoTemporaryAddon(extension.id);
            return extensionStorageSync.get(extension, spec, context);
          },
          set(items) {
            enforceNoTemporaryAddon(extension.id);
            return extensionStorageSync.set(extension, items, context);
          },
          remove(keys) {
            enforceNoTemporaryAddon(extension.id);
            return extensionStorageSync.remove(extension, keys, context);
          },
          clear() {
            enforceNoTemporaryAddon(extension.id);
            return extensionStorageSync.clear(extension, context);
          },
          getBytesInUse(keys) {
            enforceNoTemporaryAddon(extension.id);
            return extensionStorageSync.getBytesInUse(extension, keys, context);
          },
          onChanged: new EventManager({
            context,
            module: "storage",
            event: "sync.onChanged",
            extensionApi: this,
          }).api(),
        },

        managed: {
          async get(keys) {
            enforceNoTemporaryAddon(extension.id);
            let lookup = managedStorage.get(extension);

            if (!lookup) {
              lookup = lookupManagedStorage(extension.id, context);
              managedStorage.set(extension, lookup);
            }

            let data = await lookup;
            if (!data) {
              return Promise.reject({
                message: "Managed storage manifest not found",
              });
            }
            return ExtensionStorage._filterProperties(extension.id, data, keys);
          },
          // managed storage is currently initialized once.
          onChanged: ignoreEvent(context, "storage.managed.onChanged"),
        },

        onChanged: new EventManager({
          context,
          module: "storage",
          event: "onChanged",
          extensionApi: this,
        }).api(),
      },
    };
  }
};
PK
!<�Fzl9chrome/toolkit/content/extensions/parent/ext-telemetry.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

ChromeUtils.defineESModuleGetters(this, {
  TelemetryController: "resource://gre/modules/TelemetryController.sys.mjs",
  TelemetryUtils: "resource://gre/modules/TelemetryUtils.sys.mjs",
});

const SCALAR_TYPES = {
  count: Ci.nsITelemetry.SCALAR_TYPE_COUNT,
  string: Ci.nsITelemetry.SCALAR_TYPE_STRING,
  boolean: Ci.nsITelemetry.SCALAR_TYPE_BOOLEAN,
};

// Currently unsupported on Android: blocked on 1220177.
// See 1280234 c67 for discussion.
function desktopCheck() {
  if (AppConstants.MOZ_BUILD_APP !== "browser") {
    throw new ExtensionUtils.ExtensionError(
      "This API is only supported on desktop"
    );
  }
}

this.telemetry = class extends ExtensionAPI {
  getAPI(context) {
    let { extension } = context;
    return {
      telemetry: {
        submitPing(type, payload, options) {
          desktopCheck();
          const manifest = extension.manifest;
          if (manifest.telemetry) {
            throw new ExtensionUtils.ExtensionError(
              "Encryption settings are defined, use submitEncryptedPing instead."
            );
          }

          try {
            TelemetryController.submitExternalPing(type, payload, options);
          } catch (ex) {
            throw new ExtensionUtils.ExtensionError(ex);
          }
        },
        submitEncryptedPing(payload, options) {
          desktopCheck();

          const manifest = extension.manifest;
          if (!manifest.telemetry) {
            throw new ExtensionUtils.ExtensionError(
              "Encrypted telemetry pings require ping_type and public_key to be set in manifest."
            );
          }

          if (!(options.schemaName && options.schemaVersion)) {
            throw new ExtensionUtils.ExtensionError(
              "Encrypted telemetry pings require schema name and version to be set in options object."
            );
          }

          try {
            const type = manifest.telemetry.ping_type;

            // Optional manifest entries.
            if (manifest.telemetry.study_name) {
              options.studyName = manifest.telemetry.study_name;
            }
            options.addPioneerId = manifest.telemetry.pioneer_id === true;

            // Required manifest entries.
            options.useEncryption = true;
            options.publicKey = manifest.telemetry.public_key.key;
            options.encryptionKeyId = manifest.telemetry.public_key.id;
            options.schemaNamespace = manifest.telemetry.schemaNamespace;

            TelemetryController.submitExternalPing(type, payload, options);
          } catch (ex) {
            throw new ExtensionUtils.ExtensionError(ex);
          }
        },
        canUpload() {
          desktopCheck();
          // Note: remove the ternary and direct pref check when
          // TelemetryController.canUpload() is implemented (bug 1440089).
          try {
            const result =
              "canUpload" in TelemetryController
                ? TelemetryController.canUpload()
                : Services.prefs.getBoolPref(
                    TelemetryUtils.Preferences.FhrUploadEnabled,
                    false
                  );
            return result;
          } catch (ex) {
            throw new ExtensionUtils.ExtensionError(ex);
          }
        },
        scalarAdd(name, value) {
          desktopCheck();
          try {
            Services.telemetry.scalarAdd(name, value);
          } catch (ex) {
            throw new ExtensionUtils.ExtensionError(ex);
          }
        },
        scalarSet(name, value) {
          desktopCheck();
          try {
            Services.telemetry.scalarSet(name, value);
          } catch (ex) {
            throw new ExtensionUtils.ExtensionError(ex);
          }
        },
        scalarSetMaximum(name, value) {
          desktopCheck();
          try {
            Services.telemetry.scalarSetMaximum(name, value);
          } catch (ex) {
            throw new ExtensionUtils.ExtensionError(ex);
          }
        },
        keyedScalarAdd(name, key, value) {
          desktopCheck();
          try {
            Services.telemetry.keyedScalarAdd(name, key, value);
          } catch (ex) {
            throw new ExtensionUtils.ExtensionError(ex);
          }
        },
        keyedScalarSet(name, key, value) {
          desktopCheck();
          try {
            Services.telemetry.keyedScalarSet(name, key, value);
          } catch (ex) {
            throw new ExtensionUtils.ExtensionError(ex);
          }
        },
        keyedScalarSetMaximum(name, key, value) {
          desktopCheck();
          try {
            Services.telemetry.keyedScalarSetMaximum(name, key, value);
          } catch (ex) {
            throw new ExtensionUtils.ExtensionError(ex);
          }
        },
        recordEvent(category, method, object, value, extra) {
          desktopCheck();
          try {
            Services.telemetry.recordEvent(
              category,
              method,
              object,
              value,
              extra
            );
          } catch (ex) {
            throw new ExtensionUtils.ExtensionError(ex);
          }
        },
        registerScalars(category, data) {
          desktopCheck();
          try {
            // For each scalar in `data`, replace scalar.kind with
            // the appropriate nsITelemetry constant.
            Object.keys(data).forEach(scalar => {
              data[scalar].kind = SCALAR_TYPES[data[scalar].kind];
            });
            Services.telemetry.registerScalars(category, data);
          } catch (ex) {
            throw new ExtensionUtils.ExtensionError(ex);
          }
        },
        setEventRecordingEnabled(category, enabled) {
          desktopCheck();
          try {
            Services.telemetry.setEventRecordingEnabled(category, enabled);
          } catch (ex) {
            throw new ExtensionUtils.ExtensionError(ex);
          }
        },
        registerEvents(category, data) {
          desktopCheck();
          try {
            Services.telemetry.registerEvents(category, data);
          } catch (ex) {
            throw new ExtensionUtils.ExtensionError(ex);
          }
        },
      },
    };
  }
};
PK
!<�>�
��;chrome/toolkit/content/extensions/parent/ext-userScripts.js/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var { ExtensionUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/ExtensionUtils.sys.mjs"
);

var { ExtensionError } = ExtensionUtils;

/**
 * Represents (in the main browser process) a user script.
 *
 * @param {UserScriptOptions} details
 *        The options object related to the user script
 *        (which has the properties described in the user_scripts.json
 *        JSON API schema file).
 */
class UserScriptParent {
  constructor(details) {
    this.scriptId = details.scriptId;
    this.options = this._convertOptions(details);
  }

  destroy() {
    if (this.destroyed) {
      throw new Error("Unable to destroy UserScriptParent twice");
    }

    this.destroyed = true;
    this.options = null;
  }

  _convertOptions(details) {
    const options = {
      matches: details.matches,
      excludeMatches: details.excludeMatches,
      includeGlobs: details.includeGlobs,
      excludeGlobs: details.excludeGlobs,
      allFrames: details.allFrames,
      matchAboutBlank: details.matchAboutBlank,
      // New matchOriginAsFallback option not supported in userScripts API
      // because the current MV2-only userScripts API is deprecated and will be
      // superseded by the new one in bug 1875475.
      matchOriginAsFallback: false,
      runAt: details.runAt || "document_idle",
      // "world" option is unsupported in the old userScripts API. The new one
      // (bug 1875475) will support "USER_SCRIPT" (default) and "MAIN".
      jsPaths: details.js,
      userScriptOptions: {
        scriptMetadata: details.scriptMetadata,
      },
      originAttributesPatterns: null,
    };

    if (details.cookieStoreId != null) {
      const cookieStoreIds = Array.isArray(details.cookieStoreId)
        ? details.cookieStoreId
        : [details.cookieStoreId];
      options.originAttributesPatterns = cookieStoreIds.map(cookieStoreId =>
        getOriginAttributesPatternForCookieStoreId(cookieStoreId)
      );
    }

    return options;
  }

  serialize() {
    return this.options;
  }
}

this.userScripts = class extends ExtensionAPI {
  constructor(...args) {
    super(...args);

    // Map<scriptId -> UserScriptParent>
    this.userScriptsMap = new Map();
  }

  getAPI(context) {
    const { extension } = context;

    // Set of the scriptIds registered from this context.
    const registeredScriptIds = new Set();

    const unregisterContentScripts = scriptIds => {
      if (scriptIds.length === 0) {
        return Promise.resolve();
      }

      for (let scriptId of scriptIds) {
        registeredScriptIds.delete(scriptId);
        extension.registeredContentScripts.delete(scriptId);
        this.userScriptsMap.delete(scriptId);
      }
      extension.updateContentScripts();

      return context.extension.broadcast("Extension:UnregisterContentScripts", {
        id: context.extension.id,
        scriptIds,
      });
    };

    // Unregister all the scriptId related to a context when it is closed,
    // and revoke all the created blob urls once the context is destroyed.
    context.callOnClose({
      close() {
        unregisterContentScripts(Array.from(registeredScriptIds));
      },
    });

    return {
      userScripts: {
        register: async details => {
          for (let origin of details.matches) {
            if (!extension.allowedOrigins.subsumes(new MatchPattern(origin))) {
              throw new ExtensionError(
                `Permission denied to register a user script for ${origin}`
              );
            }
          }

          const userScript = new UserScriptParent(details);
          const { scriptId } = userScript;

          this.userScriptsMap.set(scriptId, userScript);
          registeredScriptIds.add(scriptId);

          const scriptOptions = userScript.serialize();

          extension.registeredContentScripts.set(scriptId, scriptOptions);
          extension.updateContentScripts();

          await extension.broadcast("Extension:RegisterContentScripts", {
            id: extension.id,
            scripts: [{ scriptId, options: scriptOptions }],
          });

          return scriptId;
        },

        // This method is not available to the extension code, the extension code
        // doesn't have access to the internally used scriptId, on the contrary
        // the extension code will call script.unregister on the script API object
        // that is resolved from the register API method returned promise.
        unregister: async scriptId => {
          const userScript = this.userScriptsMap.get(scriptId);
          if (!userScript) {
            throw new Error(`No such user script ID: ${scriptId}`);
          }

          userScript.destroy();

          await unregisterContentScripts([scriptId]);
        },
      },
    };
  }
};
PK
!<�lt�Q Q =chrome/toolkit/content/extensions/parent/ext-webNavigation.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This file expects tabTracker to be defined in the global scope (e.g.
// by ext-browser.js or ext-android.js).
/* global tabTracker */

ChromeUtils.defineESModuleGetters(this, {
  MatchURLFilters: "resource://gre/modules/MatchURLFilters.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
  WebNavigation: "resource://gre/modules/WebNavigation.sys.mjs",
  WebNavigationFrames: "resource://gre/modules/WebNavigationFrames.sys.mjs",
});

var { ExtensionError } = ExtensionUtils;

const defaultTransitionTypes = {
  topFrame: "link",
  subFrame: "auto_subframe",
};

const frameTransitions = {
  anyFrame: {
    qualifiers: ["server_redirect", "client_redirect", "forward_back"],
  },
  topFrame: {
    types: ["reload", "form_submit"],
  },
};

const tabTransitions = {
  topFrame: {
    qualifiers: ["from_address_bar"],
    types: ["auto_bookmark", "typed", "keyword", "generated", "link"],
  },
  subFrame: {
    types: ["manual_subframe"],
  },
};

const isTopLevelFrame = ({ frameId, parentFrameId }) => {
  return frameId == 0 && parentFrameId == -1;
};

const fillTransitionProperties = (eventName, src, dst) => {
  if (
    eventName == "onCommitted" ||
    eventName == "onHistoryStateUpdated" ||
    eventName == "onReferenceFragmentUpdated"
  ) {
    let frameTransitionData = src.frameTransitionData || {};
    let tabTransitionData = src.tabTransitionData || {};

    let transitionType,
      transitionQualifiers = [];

    // Fill transition properties for any frame.
    for (let qualifier of frameTransitions.anyFrame.qualifiers) {
      if (frameTransitionData[qualifier]) {
        transitionQualifiers.push(qualifier);
      }
    }

    if (isTopLevelFrame(dst)) {
      for (let type of frameTransitions.topFrame.types) {
        if (frameTransitionData[type]) {
          transitionType = type;
        }
      }

      for (let qualifier of tabTransitions.topFrame.qualifiers) {
        if (tabTransitionData[qualifier]) {
          transitionQualifiers.push(qualifier);
        }
      }

      for (let type of tabTransitions.topFrame.types) {
        if (tabTransitionData[type]) {
          transitionType = type;
        }
      }

      // If transitionType is not defined, defaults it to "link".
      if (!transitionType) {
        transitionType = defaultTransitionTypes.topFrame;
      }
    } else {
      // If it is sub-frame, transitionType defaults it to "auto_subframe",
      // "manual_subframe" is set only in case of a recent user interaction.
      transitionType = tabTransitionData.link
        ? "manual_subframe"
        : defaultTransitionTypes.subFrame;
    }

    // Fill the transition properties in the webNavigation event object.
    dst.transitionType = transitionType;
    dst.transitionQualifiers = transitionQualifiers;
  }
};

this.webNavigation = class extends ExtensionAPIPersistent {
  makeEventHandler(event) {
    let { extension } = this;
    let { tabManager } = extension;
    return ({ fire }, params) => {
      // Don't create a MatchURLFilters instance if the listener does not include any filter.
      let [urlFilters] = params;
      let filters = urlFilters ? new MatchURLFilters(urlFilters.url) : null;

      let listener = data => {
        if (!data.browser) {
          return;
        }
        if (
          !extension.privateBrowsingAllowed &&
          PrivateBrowsingUtils.isBrowserPrivate(data.browser)
        ) {
          return;
        }
        if (filters && !filters.matches(data.url)) {
          return;
        }

        let data2 = {
          url: data.url,
          timeStamp: Date.now(),
        };

        if (event == "onErrorOccurred") {
          data2.error = data.error;
        }

        if (data.frameId != undefined) {
          data2.frameId = data.frameId;
          data2.parentFrameId = data.parentFrameId;
        }

        if (data.sourceFrameId != undefined) {
          data2.sourceFrameId = data.sourceFrameId;
        }

        // Do not send a webNavigation event when the data.browser is related to a tab from a
        // new window opened to adopt an existent tab (See Bug 1443221 for a rationale).
        const chromeWin = data.browser.ownerGlobal;

        if (
          chromeWin &&
          chromeWin.gBrowser &&
          chromeWin.gBrowserInit &&
          chromeWin.gBrowserInit.isAdoptingTab() &&
          chromeWin.gBrowser.selectedBrowser === data.browser
        ) {
          return;
        }

        // Fills in tabId typically.
        Object.assign(data2, tabTracker.getBrowserData(data.browser));
        if (data2.tabId < 0) {
          return;
        }
        let tab = tabTracker.getTab(data2.tabId);
        if (!tabManager.canAccessTab(tab)) {
          return;
        }

        if (data.sourceTabBrowser) {
          data2.sourceTabId = tabTracker.getBrowserData(
            data.sourceTabBrowser
          ).tabId;
        }

        fillTransitionProperties(event, data, data2);

        fire.async(data2);
      };

      WebNavigation[event].addListener(listener);
      return {
        unregister() {
          WebNavigation[event].removeListener(listener);
        },
        convert(_fire) {
          fire = _fire;
        },
      };
    };
  }

  makeEventManagerAPI(event, context) {
    let self = this;
    return new EventManager({
      context,
      module: "webNavigation",
      event,
      register(fire, ...params) {
        let fn = self.makeEventHandler(event);
        return fn({ fire }, params).unregister;
      },
    }).api();
  }

  PERSISTENT_EVENTS = {
    onBeforeNavigate: this.makeEventHandler("onBeforeNavigate"),
    onCommitted: this.makeEventHandler("onCommitted"),
    onDOMContentLoaded: this.makeEventHandler("onDOMContentLoaded"),
    onCompleted: this.makeEventHandler("onCompleted"),
    onErrorOccurred: this.makeEventHandler("onErrorOccurred"),
    onReferenceFragmentUpdated: this.makeEventHandler(
      "onReferenceFragmentUpdated"
    ),
    onHistoryStateUpdated: this.makeEventHandler("onHistoryStateUpdated"),
    onCreatedNavigationTarget: this.makeEventHandler(
      "onCreatedNavigationTarget"
    ),
  };

  getAPI(context) {
    let { extension } = context;
    let { tabManager } = extension;

    return {
      webNavigation: {
        // onTabReplaced does nothing, it exists for compat.
        onTabReplaced: new EventManager({
          context,
          name: "webNavigation.onTabReplaced",
          register: () => {
            return () => {};
          },
        }).api(),
        onBeforeNavigate: this.makeEventManagerAPI("onBeforeNavigate", context),
        onCommitted: this.makeEventManagerAPI("onCommitted", context),
        onDOMContentLoaded: this.makeEventManagerAPI(
          "onDOMContentLoaded",
          context
        ),
        onCompleted: this.makeEventManagerAPI("onCompleted", context),
        onErrorOccurred: this.makeEventManagerAPI("onErrorOccurred", context),
        onReferenceFragmentUpdated: this.makeEventManagerAPI(
          "onReferenceFragmentUpdated",
          context
        ),
        onHistoryStateUpdated: this.makeEventManagerAPI(
          "onHistoryStateUpdated",
          context
        ),
        onCreatedNavigationTarget: this.makeEventManagerAPI(
          "onCreatedNavigationTarget",
          context
        ),
        getAllFrames({ tabId }) {
          let tab = tabManager.get(tabId);
          if (tab.discarded) {
            return null;
          }
          let frames = WebNavigationFrames.getAllFrames(tab.browsingContext);
          return frames.map(fd => ({ tabId, ...fd }));
        },
        getFrame({ tabId, frameId }) {
          let tab = tabManager.get(tabId);
          if (tab.discarded) {
            return null;
          }
          let fd = WebNavigationFrames.getFrame(tab.browsingContext, frameId);
          if (!fd) {
            throw new ExtensionError(`No frame found with frameId: ${frameId}`);
          }
          return { tabId, ...fd };
        },
      },
    };
  }
};
PK
!<ɔ���;chrome/toolkit/content/extensions/schemas/activity_log.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "PermissionPrivileged",
        "choices": [
          {
            "type": "string",
            "enum": ["activityLog"]
          }
        ]
      }
    ]
  },
  {
    "namespace": "activityLog",
    "description": "Monitor extension activity",
    "permissions": ["activityLog"],
    "events": [
      {
        "name": "onExtensionActivity",
        "description": "Receives an activityItem for each logging event.",
        "type": "function",
        "parameters": [
          {
            "name": "details",
            "type": "object",
            "properties": {
              "timeStamp": {
                "$ref": "extensionTypes.Date",
                "description": "The date string when this call is triggered."
              },
              "type": {
                "type": "string",
                "enum": [
                  "api_call",
                  "api_event",
                  "content_script",
                  "user_script"
                ],
                "description": "The type of log entry.  api_call is a function call made by the extension and api_event is an event callback to the extension.  content_script is logged when a content script is injected."
              },
              "viewType": {
                "type": "string",
                "optional": true,
                "enum": [
                  "background",
                  "popup",
                  "sidebar",
                  "tab",
                  "devtools_page",
                  "devtools_panel"
                ],
                "description": "The type of view where the activity occurred.  Content scripts will not have a viewType."
              },
              "name": {
                "type": "string",
                "description": "The name of the api call or event, or the script url if this is a content or user script event."
              },
              "data": {
                "type": "object",
                "properties": {
                  "args": {
                    "type": "array",
                    "optional": true,
                    "items": {
                      "type": "any"
                    },
                    "description": "A list of arguments passed to the call."
                  },
                  "result": {
                    "type": "object",
                    "optional": true,
                    "description": "The result of the call."
                  },
                  "tabId": {
                    "type": "integer",
                    "optional": true,
                    "description": "The tab associated with this event if it is a tab or content script."
                  },
                  "url": {
                    "type": "string",
                    "optional": true,
                    "description": "If the type is content_script, this is the url of the script that was injected."
                  }
                }
              }
            }
          }
        ],
        "extraParameters": [
          {
            "name": "id",
            "type": "string"
          }
        ]
      }
    ]
  }
]
PK
!<▎�5chrome/toolkit/content/extensions/schemas/alarms.json[
  {
    "namespace": "alarms",
    "permissions": ["alarms"],
    "types": [
      {
        "id": "Alarm",
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Name of this alarm."
          },
          "scheduledTime": {
            "type": "number",
            "description": "Time when the alarm is scheduled to fire, in milliseconds past the epoch."
          },
          "periodInMinutes": {
            "type": "number",
            "optional": true,
            "description": "When present, signals that the alarm triggers periodically after so many minutes."
          }
        }
      }
    ],
    "functions": [
      {
        "name": "create",
        "type": "function",
        "description": "Creates an alarm. After the delay is expired, the onAlarm event is fired. If there is another alarm with the same name (or no name if none is specified), it will be cancelled and replaced by this alarm.",
        "parameters": [
          {
            "type": "string",
            "name": "name",
            "optional": true,
            "description": "Optional name to identify this alarm. Defaults to the empty string."
          },
          {
            "type": "object",
            "name": "alarmInfo",
            "description": "Details about the alarm. The alarm first fires either at 'when' milliseconds past the epoch (if 'when' is provided), after 'delayInMinutes' minutes from the current time (if 'delayInMinutes' is provided instead), or after 'periodInMinutes' minutes from the current time (if only 'periodInMinutes' is provided). Users should never provide both 'when' and 'delayInMinutes'. If 'periodInMinutes' is provided, then the alarm recurs repeatedly after that many minutes.",
            "properties": {
              "when": {
                "type": "number",
                "optional": true,
                "description": "Time when the alarm is scheduled to first fire, in milliseconds past the epoch."
              },
              "delayInMinutes": {
                "type": "number",
                "optional": true,
                "description": "Number of minutes from the current time after which the alarm should first fire."
              },
              "periodInMinutes": {
                "type": "number",
                "optional": true,
                "description": "Number of minutes after which the alarm should recur repeatedly."
              }
            }
          }
        ]
      },
      {
        "name": "get",
        "type": "function",
        "description": "Retrieves details about the specified alarm.",
        "async": "callback",
        "parameters": [
          {
            "type": "string",
            "name": "name",
            "optional": true,
            "description": "The name of the alarm to get. Defaults to the empty string."
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "alarm",
                "$ref": "Alarm",
                "optional": true
              }
            ]
          }
        ]
      },
      {
        "name": "getAll",
        "type": "function",
        "description": "Gets an array of all the alarms.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "alarms",
                "type": "array",
                "items": { "$ref": "Alarm" }
              }
            ]
          }
        ]
      },
      {
        "name": "clear",
        "type": "function",
        "description": "Clears the alarm with the given name.",
        "async": "callback",
        "parameters": [
          {
            "type": "string",
            "name": "name",
            "optional": true,
            "description": "The name of the alarm to clear. Defaults to the empty string."
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "wasCleared",
                "type": "boolean",
                "description": "Whether an alarm of the given name was found to clear."
              }
            ]
          }
        ]
      },
      {
        "name": "clearAll",
        "type": "function",
        "description": "Clears all alarms.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "wasCleared",
                "type": "boolean",
                "description": "Whether any alarm was found to clear."
              }
            ]
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onAlarm",
        "type": "function",
        "description": "Fired when an alarm has expired. Useful for transient background pages.",
        "parameters": [
          {
            "name": "name",
            "$ref": "Alarm",
            "description": "The alarm that has expired."
          }
        ]
      }
    ]
  }
]
PK
!<+�'CC=chrome/toolkit/content/extensions/schemas/browser_action.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "id": "ActionManifest",
        "type": "object",
        "additionalProperties": { "$ref": "UnrecognizedProperty" },
        "properties": {
          "default_title": {
            "type": "string",
            "optional": true,
            "preprocess": "localize"
          },
          "default_icon": {
            "$ref": "IconPath",
            "optional": true
          },
          "theme_icons": {
            "type": "array",
            "optional": true,
            "minItems": 1,
            "items": { "$ref": "ThemeIcons" },
            "description": "Specifies icons to use for dark and light themes"
          },
          "default_popup": {
            "type": "string",
            "format": "relativeUrl",
            "optional": true,
            "preprocess": "localize"
          },
          "browser_style": {
            "type": "boolean",
            "optional": true,
            "description": "Deprecated in Manifest V3."
          },
          "default_area": {
            "description": "Defines the location the browserAction will appear by default.  The default location is navbar.",
            "type": "string",
            "enum": ["navbar", "menupanel", "tabstrip", "personaltoolbar"],
            "optional": true
          }
        }
      },
      {
        "$extend": "WebExtensionManifest",
        "properties": {
          "action": {
            "min_manifest_version": 3,
            "$ref": "ActionManifest",
            "optional": true
          }
        }
      },
      {
        "$extend": "WebExtensionManifest",
        "properties": {
          "browser_action": {
            "max_manifest_version": 2,
            "$ref": "ActionManifest",
            "optional": true
          }
        }
      }
    ]
  },
  {
    "namespace": "action",
    "description": "Use browser actions to put icons in the main browser toolbar, to the right of the address bar. In addition to its icon, a browser action can also have a tooltip, a badge, and a popup.",
    "permissions": ["manifest:action", "manifest:browser_action"],
    "min_manifest_version": 3,
    "types": [
      {
        "id": "Details",
        "type": "object",
        "description": "Specifies to which tab or window the value should be set, or from which one it should be retrieved. If no tab nor window is specified, the global value is set or retrieved.",
        "properties": {
          "tabId": {
            "type": "integer",
            "optional": true,
            "minimum": 0,
            "description": "When setting a value, it will be specific to the specified tab, and will automatically reset when the tab navigates. When getting, specifies the tab to get the value from; if there is no tab-specific value, the window one will be inherited."
          },
          "windowId": {
            "type": "integer",
            "optional": true,
            "minimum": -2,
            "description": "When setting a value, it will be specific to the specified window. When getting, specifies the window to get the value from; if there is no window-specific value, the global one will be inherited."
          }
        }
      },
      {
        "id": "ColorArray",
        "type": "array",
        "items": {
          "type": "integer",
          "minimum": 0,
          "maximum": 255
        },
        "minItems": 4,
        "maxItems": 4
      },
      {
        "id": "ImageDataType",
        "type": "object",
        "isInstanceOf": "ImageData",
        "additionalProperties": { "type": "any" },
        "postprocess": "convertImageDataToURL",
        "description": "Pixel data for an image. Must be an ImageData object (for example, from a <code>canvas</code> element)."
      },
      {
        "id": "ColorValue",
        "description": "An array of four integers in the range [0,255] that make up the RGBA color of the badge. For example, opaque red is <code>[255, 0, 0, 255]</code>. Can also be a string with a CSS value, with opaque red being <code>#FF0000</code> or <code>#F00</code>.",
        "choices": [
          { "type": "string" },
          { "$ref": "ColorArray" },
          { "type": "null" }
        ]
      },
      {
        "id": "OnClickData",
        "type": "object",
        "description": "Information sent when a browser action is clicked.",
        "properties": {
          "modifiers": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": ["Shift", "Alt", "Command", "Ctrl", "MacCtrl"]
            },
            "description": "An array of keyboard modifiers that were held while the menu item was clicked."
          },
          "button": {
            "type": "integer",
            "optional": true,
            "description": "An integer value of button by which menu item was clicked."
          }
        }
      }
    ],
    "functions": [
      {
        "name": "setTitle",
        "type": "function",
        "description": "Sets the title of the browser action. This shows up in the tooltip.",
        "async": "callback",
        "parameters": [
          {
            "name": "details",
            "type": "object",
            "$import": "Details",
            "properties": {
              "title": {
                "choices": [{ "type": "string" }, { "type": "null" }],
                "description": "The string the browser action should display when moused over."
              }
            }
          },
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "getTitle",
        "type": "function",
        "description": "Gets the title of the browser action.",
        "async": "callback",
        "parameters": [
          {
            "name": "details",
            "$ref": "Details"
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "result",
                "type": "string"
              }
            ]
          }
        ]
      },
      {
        "name": "getUserSettings",
        "type": "function",
        "description": "Returns the user-specified settings relating to an extension's action.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "userSettings",
                "type": "object",
                "properties": {
                  "isOnToolbar": {
                    "type": "boolean",
                    "optional": true,
                    "description": "Whether the extension's action icon is visible on browser windows' top-level toolbar (i.e., whether the extension has been 'pinned' by the user)."
                  }
                },
                "description": "The collection of user-specified settings relating to an extension's action."
              }
            ]
          }
        ]
      },
      {
        "name": "setIcon",
        "type": "function",
        "description": "Sets the icon for the browser action. The icon can be specified either as the path to an image file or as the pixel data from a canvas element, or as dictionary of either one of those. Either the <b>path</b> or the <b>imageData</b> property must be specified.",
        "async": "callback",
        "parameters": [
          {
            "name": "details",
            "type": "object",
            "$import": "Details",
            "properties": {
              "imageData": {
                "choices": [
                  { "$ref": "ImageDataType" },
                  {
                    "type": "object",
                    "patternProperties": {
                      "^[1-9]\\d*$": { "$ref": "ImageDataType" }
                    }
                  }
                ],
                "optional": true,
                "description": "Either an ImageData object or a dictionary {size -> ImageData} representing icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * 19 will be selected. Initially only scales 1 and 2 will be supported. At least one image must be specified. Note that 'details.imageData = foo' is equivalent to 'details.imageData = {'19': foo}'"
              },
              "path": {
                "choices": [
                  { "type": "string" },
                  {
                    "type": "object",
                    "patternProperties": {
                      "^[1-9]\\d*$": { "type": "string" }
                    }
                  }
                ],
                "optional": true,
                "description": "Either a relative image path or a dictionary {size -> relative image path} pointing to icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * 19 will be selected. Initially only scales 1 and 2 will be supported. At least one image must be specified. Note that 'details.path = foo' is equivalent to 'details.imageData = {'19': foo}'"
              }
            }
          },
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "setPopup",
        "type": "function",
        "description": "Sets the html document to be opened as a popup when the user clicks on the browser action's icon.",
        "async": "callback",
        "parameters": [
          {
            "name": "details",
            "type": "object",
            "$import": "Details",
            "properties": {
              "popup": {
                "choices": [{ "type": "string" }, { "type": "null" }],
                "description": "The html file to show in a popup.  If set to the empty string (''), no popup is shown."
              }
            }
          },
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "getPopup",
        "type": "function",
        "description": "Gets the html document set as the popup for this browser action.",
        "async": "callback",
        "parameters": [
          {
            "name": "details",
            "$ref": "Details"
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "result",
                "type": "string"
              }
            ]
          }
        ]
      },
      {
        "name": "setBadgeText",
        "type": "function",
        "description": "Sets the badge text for the browser action. The badge is displayed on top of the icon.",
        "async": "callback",
        "parameters": [
          {
            "name": "details",
            "type": "object",
            "$import": "Details",
            "properties": {
              "text": {
                "choices": [{ "type": "string" }, { "type": "null" }],
                "description": "Any number of characters can be passed, but only about four can fit in the space."
              }
            }
          },
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "getBadgeText",
        "type": "function",
        "description": "Gets the badge text of the browser action. If no tab nor window is specified is specified, the global badge text is returned.",
        "async": "callback",
        "parameters": [
          {
            "name": "details",
            "$ref": "Details"
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "result",
                "type": "string"
              }
            ]
          }
        ]
      },
      {
        "name": "setBadgeBackgroundColor",
        "type": "function",
        "description": "Sets the background color for the badge.",
        "async": "callback",
        "parameters": [
          {
            "name": "details",
            "type": "object",
            "$import": "Details",
            "properties": {
              "color": { "$ref": "ColorValue" }
            }
          },
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "getBadgeBackgroundColor",
        "type": "function",
        "description": "Gets the background color of the browser action badge.",
        "async": "callback",
        "parameters": [
          {
            "name": "details",
            "$ref": "Details"
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "result",
                "$ref": "ColorArray"
              }
            ]
          }
        ]
      },
      {
        "name": "setBadgeTextColor",
        "type": "function",
        "description": "Sets the text color for the badge.",
        "async": true,
        "parameters": [
          {
            "name": "details",
            "type": "object",
            "$import": "Details",
            "properties": {
              "color": { "$ref": "ColorValue" }
            }
          }
        ]
      },
      {
        "name": "getBadgeTextColor",
        "type": "function",
        "description": "Gets the text color of the browser action badge.",
        "async": true,
        "parameters": [
          {
            "name": "details",
            "$ref": "Details"
          }
        ]
      },
      {
        "name": "enable",
        "type": "function",
        "description": "Enables the browser action for a tab. By default, browser actions are enabled.",
        "async": "callback",
        "parameters": [
          {
            "type": "integer",
            "optional": true,
            "name": "tabId",
            "minimum": 0,
            "description": "The id of the tab for which you want to modify the browser action."
          },
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "disable",
        "type": "function",
        "description": "Disables the browser action for a tab.",
        "async": "callback",
        "parameters": [
          {
            "type": "integer",
            "optional": true,
            "name": "tabId",
            "minimum": 0,
            "description": "The id of the tab for which you want to modify the browser action."
          },
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "isEnabled",
        "type": "function",
        "description": "Checks whether the browser action is enabled.",
        "async": true,
        "parameters": [
          {
            "name": "details",
            "$ref": "Details"
          }
        ]
      },
      {
        "name": "openPopup",
        "type": "function",
        "description": "Opens the extension popup window in the specified window.",
        "async": true,
        "parameters": [
          {
            "name": "options",
            "optional": true,
            "type": "object",
            "description": "An object with information about the popup to open.",
            "properties": {
              "windowId": {
                "type": "integer",
                "minimum": -2,
                "optional": true,
                "description": "Defaults to the $(topic:current-window)[current window]."
              }
            }
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onClicked",
        "type": "function",
        "description": "Fired when a browser action icon is clicked.  This event will not fire if the browser action has a popup.",
        "parameters": [
          {
            "name": "tab",
            "$ref": "tabs.Tab"
          },
          {
            "name": "info",
            "$ref": "OnClickData",
            "optional": true
          }
        ]
      }
    ]
  },
  {
    "namespace": "browserAction",
    "permissions": ["manifest:action", "manifest:browser_action"],
    "max_manifest_version": 2,
    "$import": "action"
  }
]
PK
!<���5��?chrome/toolkit/content/extensions/schemas/browser_settings.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "OptionalPermission",
        "choices": [
          {
            "type": "string",
            "enum": ["browserSettings"]
          }
        ]
      }
    ]
  },
  {
    "namespace": "browserSettings",
    "description": "Use the <code>browser.browserSettings</code> API to control global settings of the browser.",
    "permissions": ["browserSettings"],
    "types": [
      {
        "id": "ImageAnimationBehavior",
        "type": "string",
        "enum": ["normal", "none", "once"],
        "description": "How images should be animated in the browser."
      },
      {
        "id": "ContextMenuMouseEvent",
        "type": "string",
        "enum": ["mouseup", "mousedown"],
        "description": "After which mouse event context menus should popup."
      },
      {
        "id": "ColorManagementMode",
        "type": "string",
        "enum": ["off", "full", "tagged_only"],
        "description": "Color management mode."
      }
    ],
    "properties": {
      "allowPopupsForUserEvents": {
        "$ref": "types.Setting",
        "description": "Allows or disallows pop-up windows from opening in response to user events."
      },
      "cacheEnabled": {
        "$ref": "types.Setting",
        "description": "Enables or disables the browser cache."
      },
      "closeTabsByDoubleClick": {
        "$ref": "types.Setting",
        "description": "This boolean setting controls whether the selected tab can be closed with a double click."
      },
      "contextMenuShowEvent": {
        "$ref": "types.Setting",
        "description": "Controls after which mouse event context menus popup. This setting's value is of type ContextMenuMouseEvent, which has possible values of <code>mouseup</code> and <code>mousedown</code>."
      },
      "ftpProtocolEnabled": {
        "$ref": "types.Setting",
        "description": "Returns whether the FTP protocol is enabled. Read-only.",
        "deprecated": "FTP support was removed from Firefox in bug 1574475"
      },
      "homepageOverride": {
        "$ref": "types.Setting",
        "description": "Returns the value of the overridden home page. Read-only."
      },
      "imageAnimationBehavior": {
        "$ref": "types.Setting",
        "description": "Controls the behaviour of image animation in the browser. This setting's value is of type ImageAnimationBehavior, defaulting to <code>normal</code>."
      },
      "newTabPageOverride": {
        "$ref": "types.Setting",
        "description": "Returns the value of the overridden new tab page. Read-only."
      },
      "newTabPosition": {
        "$ref": "types.Setting",
        "description": "Controls where new tabs are opened. `afterCurrent` will open all new tabs next to the current tab, `relatedAfterCurrent` will open only related tabs next to the current tab, and `atEnd` will open all tabs at the end of the tab strip. The default is `relatedAfterCurrent`."
      },
      "openBookmarksInNewTabs": {
        "$ref": "types.Setting",
        "description": "This boolean setting controls whether bookmarks are opened in the current tab or in a new tab."
      },
      "openSearchResultsInNewTabs": {
        "$ref": "types.Setting",
        "description": "This boolean setting controls whether search results are opened in the current tab or in a new tab."
      },
      "openUrlbarResultsInNewTabs": {
        "$ref": "types.Setting",
        "description": "This boolean setting controls whether urlbar results are opened in the current tab or in a new tab."
      },
      "webNotificationsDisabled": {
        "$ref": "types.Setting",
        "description": "Disables webAPI notifications."
      },
      "overrideDocumentColors": {
        "$ref": "types.Setting",
        "description": "This setting controls whether the user-chosen colors override the page's colors."
      },
      "overrideContentColorScheme": {
        "$ref": "types.Setting",
        "description": "This setting controls whether a light or dark color scheme overrides the page's preferred color scheme."
      },
      "useDocumentFonts": {
        "$ref": "types.Setting",
        "description": "This setting controls whether the document's fonts are used."
      },
      "zoomFullPage": {
        "$ref": "types.Setting",
        "description": "This boolean setting controls whether zoom is applied to the full page or to text only."
      },
      "zoomSiteSpecific": {
        "$ref": "types.Setting",
        "description": "This boolean setting controls whether zoom is applied on a per-site basis or to the current tab only. If privacy.resistFingerprinting is true, this setting has no effect and zoom is applied to the current tab only."
      }
    }
  },
  {
    "namespace": "browserSettings.colorManagement",
    "description": "Use the <code>browserSettings.colorManagement</code> API to query and set items related to color management.",
    "permissions": ["browserSettings"],
    "properties": {
      "mode": {
        "$ref": "types.Setting",
        "description": "This setting controls the mode used for color management and must be a string from $(ref:browserSettings.ColorManagementMode)"
      },
      "useNativeSRGB": {
        "$ref": "types.Setting",
        "description": "This boolean setting controls whether or not native sRGB color management is used."
      },
      "useWebRenderCompositor": {
        "$ref": "types.Setting",
        "description": "This boolean setting controls whether or not the WebRender compositor is used."
      }
    }
  }
]
PK
!<��~�3�3<chrome/toolkit/content/extensions/schemas/browsing_data.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "OptionalPermission",
        "choices": [
          {
            "type": "string",
            "enum": ["browsingData"]
          }
        ]
      }
    ]
  },
  {
    "namespace": "browsingData",
    "description": "Use the <code>chrome.browsingData</code> API to remove browsing data from a user's local profile.",
    "permissions": ["browsingData"],
    "types": [
      {
        "id": "RemovalOptions",
        "type": "object",
        "description": "Options that determine exactly what data will be removed.",
        "properties": {
          "since": {
            "$ref": "extensionTypes.Date",
            "optional": true,
            "description": "Remove data accumulated on or after this date, represented in milliseconds since the epoch (accessible via the <code>getTime</code> method of the JavaScript <code>Date</code> object). If absent, defaults to 0 (which would remove all browsing data)."
          },
          "hostnames": {
            "type": "array",
            "items": { "type": "string", "format": "hostname" },
            "optional": true,
            "description": "Only remove data associated with these hostnames (only applies to cookies and localStorage)."
          },
          "cookieStoreId": {
            "type": "string",
            "description": "Only remove data associated with this specific cookieStoreId.",
            "optional": true
          },
          "originTypes": {
            "type": "object",
            "optional": true,
            "description": "An object whose properties specify which origin types ought to be cleared. If this object isn't specified, it defaults to clearing only \"unprotected\" origins. Please ensure that you <em>really</em> want to remove application data before adding 'protectedWeb' or 'extensions'.",
            "properties": {
              "unprotectedWeb": {
                "type": "boolean",
                "optional": true,
                "description": "Normal websites."
              },
              "protectedWeb": {
                "type": "boolean",
                "optional": true,
                "description": "Websites that have been installed as hosted applications (be careful!)."
              },
              "extension": {
                "type": "boolean",
                "optional": true,
                "description": "Extensions and packaged applications a user has installed (be _really_ careful!)."
              }
            }
          }
        }
      },
      {
        "id": "DataTypeSet",
        "type": "object",
        "description": "A set of data types. Missing data types are interpreted as <code>false</code>.",
        "properties": {
          "cache": {
            "type": "boolean",
            "optional": true,
            "description": "The browser's cache. Note: when removing data, this clears the <em>entire</em> cache: it is not limited to the range you specify."
          },
          "cookies": {
            "type": "boolean",
            "optional": true,
            "description": "The browser's cookies."
          },
          "downloads": {
            "type": "boolean",
            "optional": true,
            "description": "The browser's download list."
          },
          "formData": {
            "type": "boolean",
            "optional": true,
            "description": "The browser's stored form data."
          },
          "history": {
            "type": "boolean",
            "optional": true,
            "description": "The browser's history."
          },
          "indexedDB": {
            "type": "boolean",
            "optional": true,
            "description": "Websites' IndexedDB data."
          },
          "localStorage": {
            "type": "boolean",
            "optional": true,
            "description": "Websites' local storage data."
          },
          "serverBoundCertificates": {
            "type": "boolean",
            "optional": true,
            "description": "Server-bound certificates."
          },
          "passwords": {
            "type": "boolean",
            "optional": true,
            "description": "Stored passwords."
          },
          "pluginData": {
            "type": "boolean",
            "optional": true,
            "description": "Plugins' data."
          },
          "serviceWorkers": {
            "type": "boolean",
            "optional": true,
            "description": "Service Workers."
          }
        }
      }
    ],
    "functions": [
      {
        "name": "settings",
        "description": "Reports which types of data are currently selected in the 'Clear browsing data' settings UI.  Note: some of the data types included in this API are not available in the settings UI, and some UI settings control more than one data type listed here.",
        "type": "function",
        "async": "callback",
        "parameters": [
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": "result",
                "type": "object",
                "properties": {
                  "options": {
                    "$ref": "RemovalOptions"
                  },
                  "dataToRemove": {
                    "$ref": "DataTypeSet",
                    "description": "All of the types will be present in the result, with values of <code>true</code> if they are both selected to be removed and permitted to be removed, otherwise <code>false</code>."
                  },
                  "dataRemovalPermitted": {
                    "$ref": "DataTypeSet",
                    "description": "All of the types will be present in the result, with values of <code>true</code> if they are permitted to be removed (e.g., by enterprise policy) and <code>false</code> if not."
                  }
                }
              }
            ]
          }
        ]
      },
      {
        "name": "remove",
        "description": "Clears various types of browsing data stored in a user's profile.",
        "type": "function",
        "async": "callback",
        "parameters": [
          {
            "$ref": "RemovalOptions",
            "name": "options"
          },
          {
            "name": "dataToRemove",
            "$ref": "DataTypeSet",
            "description": "The set of data types to remove."
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Called when deletion has completed.",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "removeAppcache",
        "description": "Clears websites' appcache data.",
        "type": "function",
        "async": "callback",
        "unsupported": true,
        "parameters": [
          {
            "$ref": "RemovalOptions",
            "name": "options"
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Called when websites' appcache data has been cleared.",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "removeCache",
        "description": "Clears the browser's cache.",
        "type": "function",
        "async": "callback",
        "parameters": [
          {
            "$ref": "RemovalOptions",
            "name": "options"
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Called when the browser's cache has been cleared.",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "removeCookies",
        "description": "Clears the browser's cookies and server-bound certificates modified within a particular timeframe.",
        "type": "function",
        "async": "callback",
        "parameters": [
          {
            "$ref": "RemovalOptions",
            "name": "options"
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Called when the browser's cookies and server-bound certificates have been cleared.",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "removeDownloads",
        "description": "Clears the browser's list of downloaded files (<em>not</em> the downloaded files themselves).",
        "type": "function",
        "async": "callback",
        "parameters": [
          {
            "$ref": "RemovalOptions",
            "name": "options"
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Called when the browser's list of downloaded files has been cleared.",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "removeFileSystems",
        "description": "Clears websites' file system data.",
        "type": "function",
        "async": "callback",
        "unsupported": true,
        "parameters": [
          {
            "$ref": "RemovalOptions",
            "name": "options"
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Called when websites' file systems have been cleared.",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "removeFormData",
        "description": "Clears the browser's stored form data (autofill).",
        "type": "function",
        "async": "callback",
        "parameters": [
          {
            "$ref": "RemovalOptions",
            "name": "options"
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Called when the browser's form data has been cleared.",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "removeHistory",
        "description": "Clears the browser's history.",
        "type": "function",
        "async": "callback",
        "parameters": [
          {
            "$ref": "RemovalOptions",
            "name": "options"
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Called when the browser's history has cleared.",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "removeIndexedDB",
        "description": "Clears websites' IndexedDB data.",
        "type": "function",
        "async": "callback",
        "unsupported": true,
        "parameters": [
          {
            "$ref": "RemovalOptions",
            "name": "options"
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Called when websites' IndexedDB data has been cleared.",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "removeLocalStorage",
        "description": "Clears websites' local storage data.",
        "type": "function",
        "async": "callback",
        "parameters": [
          {
            "$ref": "RemovalOptions",
            "name": "options"
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Called when websites' local storage has been cleared.",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "removePluginData",
        "description": "Clears plugins' data.",
        "type": "function",
        "async": "callback",
        "parameters": [
          {
            "$ref": "RemovalOptions",
            "name": "options"
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Called when plugins' data has been cleared.",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "removePasswords",
        "description": "Clears the browser's stored passwords.",
        "type": "function",
        "async": "callback",
        "parameters": [
          {
            "$ref": "RemovalOptions",
            "name": "options"
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Called when the browser's passwords have been cleared.",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "removeWebSQL",
        "description": "Clears websites' WebSQL data.",
        "type": "function",
        "async": "callback",
        "unsupported": true,
        "parameters": [
          {
            "$ref": "RemovalOptions",
            "name": "options"
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Called when websites' WebSQL databases have been cleared.",
            "optional": true,
            "parameters": []
          }
        ]
      }
    ]
  }
]
PK
!<�	�A
	
	=chrome/toolkit/content/extensions/schemas/captive_portal.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "PermissionNoPrompt",
        "choices": [
          {
            "type": "string",
            "enum": ["captivePortal"]
          }
        ]
      }
    ]
  },
  {
    "namespace": "captivePortal",
    "description": "This API provides the ability detect the captive portal state of the users connection.",
    "permissions": ["captivePortal"],
    "properties": {
      "canonicalURL": {
        "$ref": "types.Setting",
        "description": "Return the canonical captive-portal detection URL. Read-only."
      }
    },
    "functions": [
      {
        "name": "getState",
        "type": "function",
        "description": "Returns the current portal state, one of `unknown`, `not_captive`, `unlocked_portal`, `locked_portal`.",
        "async": true,
        "parameters": []
      },
      {
        "name": "getLastChecked",
        "type": "function",
        "description": "Returns the time difference between NOW and the last time a request was completed in milliseconds.",
        "async": true,
        "parameters": []
      }
    ],
    "events": [
      {
        "name": "onStateChanged",
        "type": "function",
        "description": "Fired when the captive portal state changes.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "state": {
                "type": "string",
                "enum": [
                  "unknown",
                  "not_captive",
                  "unlocked_portal",
                  "locked_portal"
                ],
                "description": "The current captive portal state."
              }
            }
          }
        ]
      },
      {
        "name": "onConnectivityAvailable",
        "type": "function",
        "description": "This notification will be emitted when the captive portal service has determined that we can connect to the internet. The service will pass either `captive` if there is an unlocked captive portal present, or `clear` if no captive portal was detected.",
        "parameters": [
          {
            "name": "status",
            "enum": ["captive", "clear"],
            "type": "string"
          }
        ]
      }
    ]
  }
]
PK
!<����8chrome/toolkit/content/extensions/schemas/clipboard.json[
  {
    "namespace": "clipboard",
    "description": "Offers the ability to write to the clipboard. Reading is not supported because the clipboard can already be read through the standard web platform APIs.",
    "permissions": ["clipboardWrite"],
    "functions": [
      {
        "name": "setImageData",
        "type": "function",
        "description": "Copy an image to the clipboard. The image is re-encoded before it is written to the clipboard. If the image is invalid, the clipboard is not modified.",
        "async": true,
        "parameters": [
          {
            "type": "object",
            "isInstanceOf": "ArrayBuffer",
            "additionalProperties": true,
            "name": "imageData",
            "description": "The image data to be copied."
          },
          {
            "type": "string",
            "name": "imageType",
            "enum": ["jpeg", "png"],
            "description": "The type of imageData."
          }
        ]
      }
    ]
  }
]
PK
!<Y�d!��>chrome/toolkit/content/extensions/schemas/content_scripts.json[
  {
    "namespace": "contentScripts",
    "max_manifest_version": 2,
    "types": [
      {
        "id": "RegisteredContentScriptOptions",
        "type": "object",
        "description": "Details of a content script registered programmatically",
        "properties": {
          "matches": {
            "type": "array",
            "optional": false,
            "minItems": 1,
            "items": { "$ref": "manifest.MatchPattern" }
          },
          "excludeMatches": {
            "type": "array",
            "optional": true,
            "minItems": 1,
            "items": { "$ref": "manifest.MatchPattern" }
          },
          "includeGlobs": {
            "type": "array",
            "optional": true,
            "items": { "type": "string" }
          },
          "excludeGlobs": {
            "type": "array",
            "optional": true,
            "items": { "type": "string" }
          },
          "css": {
            "type": "array",
            "optional": true,
            "description": "The list of CSS files to inject",
            "items": { "$ref": "extensionTypes.ExtensionFileOrCode" }
          },
          "js": {
            "type": "array",
            "optional": true,
            "description": "The list of JS files to inject",
            "items": { "$ref": "extensionTypes.ExtensionFileOrCode" }
          },
          "allFrames": {
            "type": "boolean",
            "optional": true,
            "description": "If allFrames is <code>true</code>, implies that the JavaScript or CSS should be injected into all frames of current page. By default, it's <code>false</code> and is only injected into the top frame."
          },
          "matchAboutBlank": {
            "type": "boolean",
            "optional": true,
            "description": "If matchAboutBlank is true, then the code is also injected in about:blank and about:srcdoc frames if your extension has access to its parent document. Ignored if matchOriginAsFallback is specified. By default it is <code>false</code>."
          },
          "matchOriginAsFallback": {
            "type": "boolean",
            "optional": true,
            "description": "If matchOriginAsFallback is true, then the code is also injected in about:, data:, blob: when their origin matches the pattern in 'matches', even if the actual document origin is opaque (due to the use of CSP sandbox or iframe sandbox). Match patterns in 'matches' must specify a wildcard path glob. By default it is <code>false</code>."
          },
          "runAt": {
            "$ref": "extensionTypes.RunAt",
            "optional": true,
            "description": "The soonest that the JavaScript or CSS will be injected into the tab. Defaults to \"document_idle\"."
          },
          "world": {
            "$ref": "extensionTypes.ExecutionWorld",
            "optional": true,
            "description": "The JavaScript world for a script to execute within. Defaults to \"ISOLATED\"."
          },
          "cookieStoreId": {
            "choices": [
              {
                "type": "array",
                "minItems": 1,
                "items": { "type": "string" }
              },
              {
                "type": "string"
              }
            ],
            "optional": true,
            "description": "limit the set of matched tabs to those that belong to the given cookie store id"
          }
        }
      },
      {
        "id": "RegisteredContentScript",
        "type": "object",
        "description": "An object that represents a content script registered programmatically",
        "functions": [
          {
            "name": "unregister",
            "type": "function",
            "description": "Unregister a content script registered programmatically",
            "async": true,
            "parameters": []
          }
        ]
      }
    ],
    "functions": [
      {
        "name": "register",
        "type": "function",
        "description": "Register a content script programmatically",
        "async": true,
        "parameters": [
          {
            "name": "contentScriptOptions",
            "$ref": "RegisteredContentScriptOptions"
          }
        ]
      }
    ]
  }
]
PK
!<��I���Dchrome/toolkit/content/extensions/schemas/contextual_identities.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "PermissionNoPrompt",
        "choices": [
          {
            "type": "string",
            "enum": ["contextualIdentities"]
          }
        ]
      }
    ]
  },
  {
    "namespace": "contextualIdentities",
    "description": "Use the <code>browser.contextualIdentities</code> API to query and modify contextual identity, also called as containers.",
    "permissions": ["contextualIdentities"],
    "types": [
      {
        "id": "ContextualIdentity",
        "type": "object",
        "description": "Represents information about a contextual identity.",
        "properties": {
          "name": {
            "type": "string",
            "description": "The name of the contextual identity."
          },
          "icon": {
            "type": "string",
            "description": "The icon name of the contextual identity."
          },
          "iconUrl": {
            "type": "string",
            "description": "The icon url of the contextual identity."
          },
          "color": {
            "type": "string",
            "description": "The color name of the contextual identity."
          },
          "colorCode": {
            "type": "string",
            "description": "The color hash of the contextual identity."
          },
          "cookieStoreId": {
            "type": "string",
            "description": "The cookie store ID of the contextual identity."
          }
        }
      }
    ],
    "functions": [
      {
        "name": "get",
        "type": "function",
        "description": "Retrieves information about a single contextual identity.",
        "async": true,
        "parameters": [
          {
            "type": "string",
            "name": "cookieStoreId",
            "description": "The ID of the contextual identity cookie store. "
          }
        ]
      },
      {
        "name": "query",
        "type": "function",
        "description": "Retrieves all contextual identities",
        "async": true,
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "description": "Information to filter the contextual identities being retrieved.",
            "properties": {
              "name": {
                "type": "string",
                "optional": true,
                "description": "Filters the contextual identity by name."
              }
            }
          }
        ]
      },
      {
        "name": "create",
        "type": "function",
        "description": "Creates a contextual identity with the given data.",
        "async": true,
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "description": "Details about the contextual identity being created.",
            "properties": {
              "name": {
                "type": "string",
                "optional": false,
                "description": "The name of the contextual identity."
              },
              "color": {
                "type": "string",
                "optional": false,
                "description": "The color of the contextual identity."
              },
              "icon": {
                "type": "string",
                "optional": false,
                "description": "The icon of the contextual identity."
              }
            }
          }
        ]
      },
      {
        "name": "update",
        "type": "function",
        "description": "Updates a contextual identity with the given data.",
        "async": true,
        "parameters": [
          {
            "type": "string",
            "name": "cookieStoreId",
            "description": "The ID of the contextual identity cookie store. "
          },
          {
            "type": "object",
            "name": "details",
            "description": "Details about the contextual identity being created.",
            "properties": {
              "name": {
                "type": "string",
                "optional": true,
                "description": "The name of the contextual identity."
              },
              "color": {
                "type": "string",
                "optional": true,
                "description": "The color of the contextual identity."
              },
              "icon": {
                "type": "string",
                "optional": true,
                "description": "The icon of the contextual identity."
              }
            }
          }
        ]
      },
      {
        "name": "move",
        "type": "function",
        "description": "Reorder one or more contextual identities by their cookieStoreIDs to a given position.",
        "async": true,
        "parameters": [
          {
            "name": "cookieStoreIds",
            "description": "The ID or list of IDs of the contextual identity cookie stores. ",
            "choices": [
              { "type": "string" },
              { "type": "array", "items": { "type": "string" } }
            ]
          },
          {
            "type": "integer",
            "name": "position",
            "description": "The position the contextual identity should move to."
          }
        ]
      },
      {
        "name": "remove",
        "type": "function",
        "description": "Deletes a contextual identity by its cookie Store ID.",
        "async": true,
        "parameters": [
          {
            "type": "string",
            "name": "cookieStoreId",
            "description": "The ID of the contextual identity cookie store. "
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onUpdated",
        "type": "function",
        "description": "Fired when a container is updated.",
        "parameters": [
          {
            "type": "object",
            "name": "changeInfo",
            "properties": {
              "contextualIdentity": {
                "$ref": "ContextualIdentity",
                "description": "Contextual identity that has been updated"
              }
            }
          }
        ]
      },
      {
        "name": "onCreated",
        "type": "function",
        "description": "Fired when a new container is created.",
        "parameters": [
          {
            "type": "object",
            "name": "changeInfo",
            "properties": {
              "contextualIdentity": {
                "$ref": "ContextualIdentity",
                "description": "Contextual identity that has been created"
              }
            }
          }
        ]
      },
      {
        "name": "onRemoved",
        "type": "function",
        "description": "Fired when a container is removed.",
        "parameters": [
          {
            "type": "object",
            "name": "changeInfo",
            "properties": {
              "contextualIdentity": {
                "$ref": "ContextualIdentity",
                "description": "Contextual identity that has been removed"
              }
            }
          }
        ]
      }
    ]
  }
]
PK
!<q�gNN6chrome/toolkit/content/extensions/schemas/cookies.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "OptionalPermissionNoPrompt",
        "choices": [
          {
            "type": "string",
            "enum": ["cookies"]
          }
        ]
      }
    ]
  },
  {
    "namespace": "cookies",
    "description": "Use the <code>browser.cookies</code> API to query and modify cookies, and to be notified when they change.",
    "permissions": ["cookies"],
    "types": [
      {
        "id": "SameSiteStatus",
        "type": "string",
        "enum": ["no_restriction", "lax", "strict"],
        "description": "A cookie's 'SameSite' state (https://tools.ietf.org/html/draft-west-first-party-cookies). 'no_restriction' corresponds to a cookie set without a 'SameSite' attribute, 'lax' to 'SameSite=Lax', and 'strict' to 'SameSite=Strict'."
      },
      {
        "id": "PartitionKey",
        "type": "object",
        "description": "The description of the storage partition of a cookie. This object may be omitted (null) if a cookie is not partitioned.",
        "properties": {
          "topLevelSite": {
            "type": "string",
            "optional": true,
            "description": "The first-party URL of the cookie, if the cookie is in storage partitioned by the top-level site."
          }
        }
      },
      {
        "id": "Cookie",
        "type": "object",
        "description": "Represents information about an HTTP cookie.",
        "properties": {
          "name": {
            "type": "string",
            "description": "The name of the cookie."
          },
          "value": {
            "type": "string",
            "description": "The value of the cookie."
          },
          "domain": {
            "type": "string",
            "description": "The domain of the cookie (e.g. \"www.google.com\", \"example.com\")."
          },
          "hostOnly": {
            "type": "boolean",
            "description": "True if the cookie is a host-only cookie (i.e. a request's host must exactly match the domain of the cookie)."
          },
          "path": {
            "type": "string",
            "description": "The path of the cookie."
          },
          "secure": {
            "type": "boolean",
            "description": "True if the cookie is marked as Secure (i.e. its scope is limited to secure channels, typically HTTPS)."
          },
          "httpOnly": {
            "type": "boolean",
            "description": "True if the cookie is marked as HttpOnly (i.e. the cookie is inaccessible to client-side scripts)."
          },
          "sameSite": {
            "$ref": "SameSiteStatus",
            "description": "The cookie's same-site status (i.e. whether the cookie is sent with cross-site requests)."
          },
          "session": {
            "type": "boolean",
            "description": "True if the cookie is a session cookie, as opposed to a persistent cookie with an expiration date."
          },
          "expirationDate": {
            "type": "number",
            "optional": true,
            "description": "The expiration date of the cookie as the number of seconds since the UNIX epoch. Not provided for session cookies."
          },
          "storeId": {
            "type": "string",
            "description": "The ID of the cookie store containing this cookie, as provided in getAllCookieStores()."
          },
          "firstPartyDomain": {
            "type": "string",
            "description": "The first-party domain of the cookie."
          },
          "partitionKey": {
            "$ref": "PartitionKey",
            "optional": true,
            "description": "The cookie's storage partition, if any. null if not partitioned."
          }
        }
      },
      {
        "id": "CookieStore",
        "type": "object",
        "description": "Represents a cookie store in the browser. An incognito mode window, for instance, uses a separate cookie store from a non-incognito window.",
        "properties": {
          "id": {
            "type": "string",
            "description": "The unique identifier for the cookie store."
          },
          "tabIds": {
            "type": "array",
            "items": { "type": "integer" },
            "description": "Identifiers of all the browser tabs that share this cookie store."
          },
          "incognito": {
            "type": "boolean",
            "description": "Indicates if this is an incognito cookie store"
          }
        }
      },
      {
        "id": "OnChangedCause",
        "type": "string",
        "enum": [
          "evicted",
          "expired",
          "explicit",
          "expired_overwrite",
          "overwrite"
        ],
        "description": "The underlying reason behind the cookie's change. If a cookie was inserted, or removed via an explicit call to $(ref:cookies.remove), \"cause\" will be \"explicit\". If a cookie was automatically removed due to expiry, \"cause\" will be \"expired\". If a cookie was removed due to being overwritten with an already-expired expiration date, \"cause\" will be set to \"expired_overwrite\".  If a cookie was automatically removed due to garbage collection, \"cause\" will be \"evicted\".  If a cookie was automatically removed due to a \"set\" call that overwrote it, \"cause\" will be \"overwrite\". Plan your response accordingly."
      }
    ],
    "functions": [
      {
        "name": "get",
        "type": "function",
        "description": "Retrieves information about a single cookie. If more than one cookie of the same name exists for the given URL, the one with the longest path will be returned. For cookies with the same path length, the cookie with the earliest creation time will be returned.",
        "async": "callback",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "description": "Details to identify the cookie being retrieved.",
            "properties": {
              "url": {
                "type": "string",
                "description": "The URL with which the cookie to retrieve is associated. This argument may be a full URL, in which case any data following the URL path (e.g. the query string) is simply ignored. If host permissions for this URL are not specified in the manifest file, the API call will fail."
              },
              "name": {
                "type": "string",
                "description": "The name of the cookie to retrieve."
              },
              "storeId": {
                "type": "string",
                "optional": true,
                "description": "The ID of the cookie store in which to look for the cookie. By default, the current execution context's cookie store will be used."
              },
              "firstPartyDomain": {
                "type": "string",
                "optional": true,
                "description": "The first-party domain which the cookie to retrieve is associated. This attribute is required if First-Party Isolation is enabled."
              },
              "partitionKey": {
                "$ref": "PartitionKey",
                "optional": true,
                "description": "The storage partition, if the cookie is part of partitioned storage. By default, only non-partitioned cookies are returned."
              }
            }
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "cookie",
                "$ref": "Cookie",
                "optional": true,
                "description": "Contains details about the cookie. This parameter is null if no such cookie was found."
              }
            ]
          }
        ]
      },
      {
        "name": "getAll",
        "type": "function",
        "description": "Retrieves all cookies from a single cookie store that match the given information.  The cookies returned will be sorted, with those with the longest path first.  If multiple cookies have the same path length, those with the earliest creation time will be first.",
        "async": "callback",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "description": "Information to filter the cookies being retrieved.",
            "properties": {
              "url": {
                "type": "string",
                "optional": true,
                "description": "Restricts the retrieved cookies to those that would match the given URL."
              },
              "name": {
                "type": "string",
                "optional": true,
                "description": "Filters the cookies by name."
              },
              "domain": {
                "type": "string",
                "optional": true,
                "description": "Restricts the retrieved cookies to those whose domains match or are subdomains of this one."
              },
              "path": {
                "type": "string",
                "optional": true,
                "description": "Restricts the retrieved cookies to those whose path exactly matches this string."
              },
              "secure": {
                "type": "boolean",
                "optional": true,
                "description": "Filters the cookies by their Secure property."
              },
              "session": {
                "type": "boolean",
                "optional": true,
                "description": "Filters out session vs. persistent cookies."
              },
              "storeId": {
                "type": "string",
                "optional": true,
                "description": "The cookie store to retrieve cookies from. If omitted, the current execution context's cookie store will be used."
              },
              "firstPartyDomain": {
                "type": "string",
                "optional": "omit-key-if-missing",
                "description": "Restricts the retrieved cookies to those whose first-party domains match this one. This attribute is required if First-Party Isolation is enabled. To not filter by a specific first-party domain, use `null` or `undefined`."
              },
              "partitionKey": {
                "$ref": "PartitionKey",
                "optional": true,
                "description": "Selects a specific storage partition to look up cookies. Defaults to null, in which case only non-partitioned cookies are retrieved. If an object iis passed, partitioned cookies are also included, and filtered based on the keys present in the given PartitionKey description. An empty object ({}) returns all cookies (partitioned + unpartitioned), a non-empty object (e.g. {topLevelSite: '...'}) only returns cookies whose partition match all given attributes."
              }
            }
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "cookies",
                "type": "array",
                "items": { "$ref": "Cookie" },
                "description": "All the existing, unexpired cookies that match the given cookie info."
              }
            ]
          }
        ]
      },
      {
        "name": "set",
        "type": "function",
        "description": "Sets a cookie with the given cookie data; may overwrite equivalent cookies if they exist.",
        "async": "callback",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "description": "Details about the cookie being set.",
            "properties": {
              "url": {
                "type": "string",
                "description": "The request-URI to associate with the setting of the cookie. This value can affect the default domain and path values of the created cookie. If host permissions for this URL are not specified in the manifest file, the API call will fail."
              },
              "name": {
                "type": "string",
                "optional": true,
                "description": "The name of the cookie. Empty by default if omitted."
              },
              "value": {
                "type": "string",
                "optional": true,
                "description": "The value of the cookie. Empty by default if omitted."
              },
              "domain": {
                "type": "string",
                "optional": true,
                "description": "The domain of the cookie. If omitted, the cookie becomes a host-only cookie."
              },
              "path": {
                "type": "string",
                "optional": true,
                "description": "The path of the cookie. Defaults to the path portion of the url parameter."
              },
              "secure": {
                "type": "boolean",
                "optional": true,
                "description": "Whether the cookie should be marked as Secure. Defaults to false."
              },
              "httpOnly": {
                "type": "boolean",
                "optional": true,
                "description": "Whether the cookie should be marked as HttpOnly. Defaults to false."
              },
              "sameSite": {
                "$ref": "SameSiteStatus",
                "optional": true,
                "description": "The cookie's same-site status.",
                "default": "no_restriction"
              },
              "expirationDate": {
                "type": "number",
                "optional": true,
                "description": "The expiration date of the cookie as the number of seconds since the UNIX epoch. If omitted, the cookie becomes a session cookie."
              },
              "storeId": {
                "type": "string",
                "optional": true,
                "description": "The ID of the cookie store in which to set the cookie. By default, the cookie is set in the current execution context's cookie store."
              },
              "firstPartyDomain": {
                "type": "string",
                "optional": true,
                "description": "The first-party domain of the cookie. This attribute is required if First-Party Isolation is enabled."
              },
              "partitionKey": {
                "$ref": "PartitionKey",
                "optional": true,
                "description": "The storage partition, if the cookie is part of partitioned storage. By default, non-partitioned storage is used."
              }
            }
          },
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "parameters": [
              {
                "name": "cookie",
                "$ref": "Cookie",
                "optional": true,
                "description": "Contains details about the cookie that's been set.  If setting failed for any reason, this will be \"null\", and $(ref:runtime.lastError) will be set."
              }
            ]
          }
        ]
      },
      {
        "name": "remove",
        "type": "function",
        "description": "Deletes a cookie by name.",
        "async": "callback",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "description": "Information to identify the cookie to remove.",
            "properties": {
              "url": {
                "type": "string",
                "description": "The URL associated with the cookie. If host permissions for this URL are not specified in the manifest file, the API call will fail."
              },
              "name": {
                "type": "string",
                "description": "The name of the cookie to remove."
              },
              "storeId": {
                "type": "string",
                "optional": true,
                "description": "The ID of the cookie store to look in for the cookie. If unspecified, the cookie is looked for by default in the current execution context's cookie store."
              },
              "firstPartyDomain": {
                "type": "string",
                "optional": true,
                "description": "The first-party domain associated with the cookie. This attribute is required if First-Party Isolation is enabled."
              },
              "partitionKey": {
                "$ref": "PartitionKey",
                "optional": true,
                "description": "The storage partition, if the cookie is part of partitioned storage. By default, non-partitioned storage is used."
              }
            }
          },
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "parameters": [
              {
                "name": "details",
                "type": "object",
                "description": "Contains details about the cookie that's been removed.  If removal failed for any reason, this will be \"null\", and $(ref:runtime.lastError) will be set.",
                "optional": true,
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "The URL associated with the cookie that's been removed."
                  },
                  "name": {
                    "type": "string",
                    "description": "The name of the cookie that's been removed."
                  },
                  "storeId": {
                    "type": "string",
                    "description": "The ID of the cookie store from which the cookie was removed."
                  },
                  "firstPartyDomain": {
                    "type": "string",
                    "description": "The first-party domain associated with the cookie that's been removed."
                  },
                  "partitionKey": {
                    "$ref": "PartitionKey",
                    "optional": true,
                    "description": "The storage partition, if the cookie is part of partitioned storage. null if not partitioned."
                  }
                }
              }
            ]
          }
        ]
      },
      {
        "name": "getAllCookieStores",
        "type": "function",
        "description": "Lists all existing cookie stores.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "cookieStores",
                "type": "array",
                "items": { "$ref": "CookieStore" },
                "description": "All the existing cookie stores."
              }
            ]
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onChanged",
        "type": "function",
        "description": "Fired when a cookie is set or removed. As a special case, note that updating a cookie's properties is implemented as a two step process: the cookie to be updated is first removed entirely, generating a notification with \"cause\" of \"overwrite\" .  Afterwards, a new cookie is written with the updated values, generating a second notification with \"cause\" \"explicit\".",
        "parameters": [
          {
            "type": "object",
            "name": "changeInfo",
            "properties": {
              "removed": {
                "type": "boolean",
                "description": "True if a cookie was removed."
              },
              "cookie": {
                "$ref": "Cookie",
                "description": "Information about the cookie that was set or removed."
              },
              "cause": {
                "$ref": "OnChangedCause",
                "description": "The underlying reason behind the cookie's change."
              }
            }
          }
        ]
      }
    ]
  }
]
PK
!<~p�7>~>~Fchrome/toolkit/content/extensions/schemas/declarative_net_request.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "Permission",
        "choices": [
          {
            "type": "string",
            "enum": ["declarativeNetRequest"]
          }
        ]
      },
      {
        "$extend": "OptionalPermission",
        "choices": [
          {
            "type": "string",
            "enum": ["declarativeNetRequestFeedback"]
          }
        ]
      },
      {
        "$extend": "PermissionNoPrompt",
        "choices": [
          {
            "type": "string",
            "enum": ["declarativeNetRequestWithHostAccess"]
          }
        ]
      },
      {
        "$extend": "WebExtensionManifest",
        "properties": {
          "declarative_net_request": {
            "type": "object",
            "optional": true,
            "additionalProperties": { "$ref": "UnrecognizedProperty" },
            "properties": {
              "rule_resources": {
                "type": "array",
                "minItems": 1,
                "items": {
                  "type": "object",
                  "additionalProperties": { "$ref": "UnrecognizedProperty" },
                  "properties": {
                    "id": {
                      "type": "string",
                      "pattern": "^[^_]",
                      "description": "A non-empty string uniquely identifying the ruleset. IDs beginning with '_' are reserved for internal use."
                    },
                    "enabled": {
                      "type": "boolean",
                      "description": "Whether the ruleset is enabled by default."
                    },
                    "path": {
                      "$ref": "manifest.ExtensionURL",
                      "description": "The path of the JSON ruleset relative to the extension directory."
                    }
                  }
                }
              }
            }
          }
        }
      }
    ]
  },
  {
    "namespace": "declarativeNetRequest",
    "description": "Use the declarativeNetRequest API to block or modify network requests by specifying declarative rules.",
    "permissions": [
      "declarativeNetRequest",
      "declarativeNetRequestWithHostAccess"
    ],
    "types": [
      {
        "id": "ResourceType",
        "type": "string",
        "description": "How the requested resource will be used. Comparable to the webRequest.ResourceType type.",
        "enum": [
          "main_frame",
          "sub_frame",
          "stylesheet",
          "script",
          "image",
          "object",
          "object_subrequest",
          "xmlhttprequest",
          "xslt",
          "ping",
          "beacon",
          "xml_dtd",
          "font",
          "media",
          "websocket",
          "csp_report",
          "imageset",
          "web_manifest",
          "speculative",
          "other"
        ]
      },
      {
        "id": "UnsupportedRegexReason",
        "type": "string",
        "description": "Describes the reason why a given regular expression isn't supported.",
        "enum": ["syntaxError", "memoryLimitExceeded"]
      },
      {
        "id": "MatchedRule",
        "type": "object",
        "properties": {
          "ruleId": {
            "type": "integer",
            "description": "A matching rule's ID."
          },
          "rulesetId": {
            "type": "string",
            "description": "ID of the Ruleset this rule belongs to."
          },
          "extensionId": {
            "type": "string",
            "description": "ID of the extension, if this rule belongs to a different extension.",
            "optional": true
          }
        }
      },
      {
        "id": "URLTransform",
        "type": "object",
        "description": "Describes the type of the Rule.action.redirect.transform property.",
        "properties": {
          "scheme": {
            "type": "string",
            "optional": true,
            "description": "The new scheme for the request.",
            "enum": ["http", "https", "moz-extension"]
          },
          "username": {
            "type": "string",
            "optional": true,
            "description": "The new username for the request."
          },
          "password": {
            "type": "string",
            "optional": true,
            "description": "The new password for the request."
          },
          "host": {
            "type": "string",
            "optional": true,
            "description": "The new host name for the request."
          },
          "port": {
            "type": "string",
            "optional": true,
            "description": "The new port for the request. If empty, the existing port is cleared."
          },
          "path": {
            "type": "string",
            "optional": true,
            "description": "The new path for the request. If empty, the existing path is cleared."
          },
          "query": {
            "type": "string",
            "optional": true,
            "description": "The new query for the request. Should be either empty, in which case the existing query is cleared; or should begin with '?'. Cannot be specified if 'queryTransform' is specified."
          },
          "queryTransform": {
            "type": "object",
            "optional": true,
            "description": "Add, remove or replace query key-value pairs. Cannot be specified if 'query' is specified.",
            "properties": {
              "removeParams": {
                "type": "array",
                "optional": true,
                "description": "The list of query keys to be removed.",
                "items": {
                  "type": "string"
                }
              },
              "addOrReplaceParams": {
                "type": "array",
                "optional": true,
                "description": "The list of query key-value pairs to be added or replaced.",
                "items": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string"
                    },
                    "value": {
                      "type": "string"
                    },
                    "replaceOnly": {
                      "type": "boolean",
                      "optional": true,
                      "description": "If true, the query key is replaced only if it's already present. Otherwise, the key is also added if it's missing.",
                      "default": false
                    }
                  }
                }
              }
            }
          },
          "fragment": {
            "type": "string",
            "optional": true,
            "description": "The new fragment for the request. Should be either empty, in which case the existing fragment is cleared; or should begin with '#'."
          }
        }
      },
      {
        "id": "Rule",
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "An id which uniquely identifies a rule. Mandatory and should be >= 1.",
            "minimum": 1
          },
          "priority": {
            "type": "integer",
            "optional": true,
            "description": "Rule priority. Defaults to 1. When specified, should be >= 1",
            "minimum": 1,
            "default": 1
          },
          "condition": {
            "type": "object",
            "description": "The condition under which this rule is triggered.",
            "properties": {
              "urlFilter": {
                "type": "string",
                "optional": true,
                "description": "TODO: link to doc explaining supported pattern. The pattern which is matched against the network request url. Only one of 'urlFilter' or 'regexFilter' can be specified."
              },
              "regexFilter": {
                "type": "string",
                "optional": true,
                "description": "Regular expression to match against the network request url. Only one of 'urlFilter' or 'regexFilter' can be specified."
              },
              "isUrlFilterCaseSensitive": {
                "type": "boolean",
                "optional": true,
                "description": "Whether 'urlFilter' or 'regexFilter' is case-sensitive."
              },
              "initiatorDomains": {
                "type": "array",
                "optional": true,
                "description": "The rule will only match network requests originating from the list of 'initiatorDomains'. If the list is omitted, the rule is applied to requests from all domains.",
                "minItems": 1,
                "items": {
                  "type": "string",
                  "format": "canonicalDomain"
                }
              },
              "excludedInitiatorDomains": {
                "type": "array",
                "optional": true,
                "description": "The rule will not match network requests originating from the list of 'initiatorDomains'. If the list is empty or omitted, no domains are excluded. This takes precedence over 'initiatorDomains'.",
                "items": {
                  "type": "string",
                  "format": "canonicalDomain"
                }
              },
              "requestDomains": {
                "type": "array",
                "optional": true,
                "description": "The rule will only match network requests when the domain matches one from the list of 'requestDomains'. If the list is omitted, the rule is applied to requests from all domains.",
                "minItems": 1,
                "items": {
                  "type": "string",
                  "format": "canonicalDomain"
                }
              },
              "excludedRequestDomains": {
                "type": "array",
                "optional": true,
                "description": "The rule will not match network requests when the domains matches one from the list of 'excludedRequestDomains'. If the list is empty or omitted, no domains are excluded. This takes precedence over 'requestDomains'.",
                "items": {
                  "type": "string",
                  "format": "canonicalDomain"
                }
              },
              "resourceTypes": {
                "type": "array",
                "optional": true,
                "description": "List of resource types which the rule can match. When the rule action is 'allowAllRequests', this must be specified and may only contain 'main_frame' or 'sub_frame'. Cannot be specified if 'excludedResourceTypes' is specified. If neither of them is specified, all resource types except 'main_frame' are matched.",
                "minItems": 1,
                "items": {
                  "$ref": "ResourceType"
                }
              },
              "excludedResourceTypes": {
                "type": "array",
                "optional": true,
                "description": "List of resource types which the rule won't match. Cannot be specified if 'resourceTypes' is specified. If neither of them is specified, all resource types except 'main_frame' are matched.",
                "items": {
                  "$ref": "ResourceType"
                }
              },
              "requestMethods": {
                "type": "array",
                "optional": true,
                "description": "List of HTTP request methods which the rule can match. Should be a lower-case method such as 'connect', 'delete', 'get', 'head', 'options', 'patch', 'post', 'put'.'",
                "minItems": 1,
                "items": {
                  "type": "string"
                }
              },
              "excludedRequestMethods": {
                "type": "array",
                "optional": true,
                "description": "List of request methods which the rule won't match. Cannot be specified if 'requestMethods' is specified. If neither of them is specified, all request methods are matched.",
                "items": {
                  "type": "string"
                }
              },
              "domainType": {
                "type": "string",
                "optional": true,
                "description": "Specifies whether the network request is first-party or third-party to the domain from which it originated. If omitted, all requests are matched.",
                "enum": ["firstParty", "thirdParty"]
              },
              "tabIds": {
                "type": "array",
                "optional": true,
                "description": "List of tabIds which the rule should match. An ID of -1 matches requests which don't originate from a tab. Only supported for session-scoped rules.",
                "minItems": 1,
                "items": {
                  "type": "integer"
                }
              },
              "excludedTabIds": {
                "type": "array",
                "optional": true,
                "description": "List of tabIds which the rule should not match. An ID of -1 excludes requests which don't originate from a tab. Only supported for session-scoped rules.",
                "items": {
                  "type": "integer"
                }
              }
            }
          },
          "action": {
            "type": "object",
            "description": "The action to take if this rule is matched.",
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "block",
                  "redirect",
                  "allow",
                  "upgradeScheme",
                  "modifyHeaders",
                  "allowAllRequests"
                ]
              },
              "redirect": {
                "type": "object",
                "optional": true,
                "description": "Describes how the redirect should be performed. Only valid when type is 'redirect'.",
                "properties": {
                  "extensionPath": {
                    "type": "string",
                    "optional": true,
                    "description": "Path relative to the extension directory. Should start with '/'."
                  },
                  "transform": {
                    "$ref": "URLTransform",
                    "optional": true,
                    "description": "Url transformations to perform."
                  },
                  "url": {
                    "type": "string",
                    "format": "url",
                    "optional": true,
                    "description": "The redirect url. Redirects to JavaScript urls are not allowed."
                  },
                  "regexSubstitution": {
                    "type": "string",
                    "optional": true,
                    "description": "Substitution pattern for rules which specify a 'regexFilter'. The first match of regexFilter within the url will be replaced with this pattern. Within regexSubstitution, backslash-escaped digits (\\1 to \\9) can be used to insert the corresponding capture groups. \\0 refers to the entire matching text."
                  }
                }
              },
              "requestHeaders": {
                "type": "array",
                "optional": true,
                "description": "The request headers to modify for the request. Only valid when type is 'modifyHeaders'.",
                "minItems": 1,
                "items": {
                  "type": "object",
                  "properties": {
                    "header": {
                      "type": "string",
                      "description": "The name of the request header to be modified."
                    },
                    "operation": {
                      "type": "string",
                      "description": "The operation to be performed on a header.",
                      "enum": ["append", "set", "remove"]
                    },
                    "value": {
                      "type": "string",
                      "optional": true,
                      "description": "The new value for the header. Must be specified for the 'append' and 'set' operations."
                    }
                  }
                }
              },
              "responseHeaders": {
                "type": "array",
                "optional": true,
                "description": "The response headers to modify for the request. Only valid when type is 'modifyHeaders'.",
                "minItems": 1,
                "items": {
                  "type": "object",
                  "properties": {
                    "header": {
                      "type": "string",
                      "description": "The name of the response header to be modified."
                    },
                    "operation": {
                      "type": "string",
                      "description": "The operation to be performed on a header.",
                      "enum": ["append", "set", "remove"]
                    },
                    "value": {
                      "type": "string",
                      "optional": true,
                      "description": "The new value for the header. Must be specified for the 'append' and 'set' operations."
                    }
                  }
                }
              }
            }
          }
        }
      },
      {
        "id": "GetRulesFilter",
        "type": "object",
        "properties": {
          "ruleIds": {
            "type": "array",
            "optional": true,
            "description": "If specified, only rules with matching IDs are included.",
            "items": { "type": "integer" }
          }
        }
      }
    ],
    "functions": [
      {
        "name": "updateDynamicRules",
        "type": "function",
        "description": "Modifies the current set of dynamic rules for the extension. The rules with IDs listed in options.removeRuleIds are first removed, and then the rules given in options.addRules are added. These rules are persisted across browser sessions and extension updates.",
        "async": "callback",
        "parameters": [
          {
            "name": "options",
            "type": "object",
            "properties": {
              "removeRuleIds": {
                "type": "array",
                "optional": true,
                "description": "IDs of the rules to remove. Any invalid IDs will be ignored.",
                "items": {
                  "type": "integer"
                }
              },
              "addRules": {
                "type": "array",
                "optional": true,
                "description": "Rules to add.",
                "items": {
                  "$ref": "Rule"
                }
              }
            }
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Called when the dynamic rules have been updated",
            "parameters": []
          }
        ]
      },
      {
        "name": "updateSessionRules",
        "type": "function",
        "description": "Modifies the current set of session scoped rules for the extension. The rules with IDs listed in options.removeRuleIds are first removed, and then the rules given in options.addRules are added. These rules are not persisted across sessions and are backed in memory.",
        "async": "callback",
        "parameters": [
          {
            "name": "options",
            "type": "object",
            "properties": {
              "removeRuleIds": {
                "type": "array",
                "optional": true,
                "description": "IDs of the rules to remove. Any invalid IDs will be ignored.",
                "items": {
                  "type": "integer"
                }
              },
              "addRules": {
                "type": "array",
                "optional": true,
                "description": "Rules to add.",
                "items": {
                  "$ref": "Rule"
                }
              }
            }
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Called when the session rules have been updated",
            "parameters": []
          }
        ]
      },
      {
        "name": "getEnabledRulesets",
        "type": "function",
        "description": "Returns the ids for the current set of enabled static rulesets.",
        "async": "callback",
        "parameters": [
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "type": "array",
                "name": "rulesetIds",
                "items": { "type": "string" }
              }
            ]
          }
        ]
      },
      {
        "name": "updateEnabledRulesets",
        "type": "function",
        "description": "Modifies the static rulesets enabled/disabled state.",
        "async": "callback",
        "parameters": [
          {
            "name": "updateRulesetOptions",
            "type": "object",
            "properties": {
              "disableRulesetIds": {
                "type": "array",
                "items": { "type": "string" },
                "optional": true,
                "default": []
              },
              "enableRulesetIds": {
                "type": "array",
                "items": { "type": "string" },
                "optional": true,
                "default": []
              }
            }
          },
          {
            "name": "callback",
            "type": "function",
            "parameters": []
          }
        ]
      },
      {
        "name": "updateStaticRules",
        "type": "function",
        "description": "Modified individual static rules enabled/disabled state. Changes to rules belonging to a disabled ruleset will take effect when the ruleset becomes enabled.",
        "async": "callback",
        "parameters": [
          {
            "name": "options",
            "type": "object",
            "properties": {
              "rulesetId": {
                "type": "string"
              },
              "disableRuleIds": {
                "type": "array",
                "items": { "type": "integer" },
                "optional": true,
                "default": []
              },
              "enableRuleIds": {
                "type": "array",
                "items": { "type": "integer" },
                "optional": true,
                "default": []
              }
            }
          },
          {
            "name": "callback",
            "type": "function",
            "parameters": []
          }
        ]
      },
      {
        "name": "getAvailableStaticRuleCount",
        "type": "function",
        "description": "Returns the remaining number of static rules an extension can enable",
        "async": "callback",
        "parameters": [
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": "count",
                "type": "integer"
              }
            ]
          }
        ]
      },
      {
        "name": "getDisabledRuleIds",
        "type": "function",
        "description": "Returns the list of individual disabled static rules from a given static ruleset id.",
        "async": "callback",
        "parameters": [
          {
            "name": "options",
            "type": "object",
            "properties": {
              "rulesetId": {
                "type": "string"
              }
            }
          },
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": "disabledRuleIds",
                "type": "array",
                "items": {
                  "type": "integer"
                }
              }
            ]
          }
        ]
      },
      {
        "name": "getDynamicRules",
        "type": "function",
        "description": "Returns the current set of dynamic rules for the extension.",
        "async": "callback",
        "parameters": [
          {
            "name": "filter",
            "$ref": "GetRulesFilter",
            "optional": true,
            "description": "An object to filter the set of dynamic rules for the extension."
          },
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": "dynamicRules",
                "type": "array",
                "items": {
                  "$ref": "Rule"
                }
              }
            ]
          }
        ]
      },
      {
        "name": "getSessionRules",
        "type": "function",
        "description": "Returns the current set of session scoped rules for the extension.",
        "async": "callback",
        "parameters": [
          {
            "name": "filter",
            "$ref": "GetRulesFilter",
            "optional": true,
            "description": "An object to filter the set of session scoped rules for the extension."
          },
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": "sessionRules",
                "type": "array",
                "items": {
                  "$ref": "Rule"
                }
              }
            ]
          }
        ]
      },
      {
        "name": "isRegexSupported",
        "type": "function",
        "description": "Checks if the given regular expression will be supported as a 'regexFilter' rule condition.",
        "async": "callback",
        "parameters": [
          {
            "name": "regexOptions",
            "type": "object",
            "properties": {
              "regex": {
                "type": "string",
                "description": "The regular expresson to check."
              },
              "isCaseSensitive": {
                "type": "boolean",
                "optional": true,
                "description": "Whether the 'regex' specified is case sensitive.",
                "default": false
              },
              "requireCapturing": {
                "type": "boolean",
                "optional": true,
                "description": "Whether the 'regex' specified requires capturing. Capturing is only required for redirect rules which specify a 'regexSubstition' action.",
                "default": false
              }
            }
          },
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": "result",
                "type": "object",
                "properties": {
                  "isSupported": {
                    "type": "boolean",
                    "description": "Whether the given regex is supported"
                  },
                  "reason": {
                    "$ref": "UnsupportedRegexReason",
                    "optional": true,
                    "description": "Specifies the reason why the regular expression is not supported. Only provided if 'isSupported' is false."
                  }
                }
              }
            ]
          }
        ]
      },
      {
        "name": "testMatchOutcome",
        "type": "function",
        "description": "Checks if any of the extension's declarativeNetRequest rules would match a hypothetical request.",
        "permissions": ["declarativeNetRequestFeedback"],
        "async": "callback",
        "parameters": [
          {
            "name": "request",
            "type": "object",
            "description": "The details of the request to test.",
            "properties": {
              "url": {
                "type": "string",
                "description": "The URL of the hypothetical request."
              },
              "initiator": {
                "type": "string",
                "description": "The initiator URL (if any) for the hypothetical request.",
                "optional": true
              },
              "method": {
                "type": "string",
                "description": "Standard HTTP method of the hypothetical request.",
                "optional": true,
                "default": "get"
              },
              "type": {
                "$ref": "ResourceType",
                "description": "The resource type of the hypothetical request."
              },
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab in which the hypothetical request takes place. Does not need to correspond to a real tab ID. Default is -1, meaning that the request isn't related to a tab.",
                "optional": true,
                "default": -1
              }
            }
          },
          {
            "name": "options",
            "type": "object",
            "optional": true,
            "properties": {
              "includeOtherExtensions": {
                "type": "boolean",
                "description": "Whether to account for rules from other installed extensions during rule evaluation.",
                "optional": true
              }
            }
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Called with the details of matched rules.",
            "parameters": [
              {
                "name": "result",
                "type": "object",
                "properties": {
                  "matchedRules": {
                    "type": "array",
                    "description": "The rules (if any) that match the hypothetical request.",
                    "items": {
                      "$ref": "MatchedRule"
                    }
                  }
                }
              }
            ]
          }
        ]
      }
    ],
    "properties": {
      "DYNAMIC_RULESET_ID": {
        "type": "string",
        "value": "_dynamic",
        "description": "Ruleset ID for the dynamic rules added by the extension."
      },
      "GUARANTEED_MINIMUM_STATIC_RULES": {
        "type": "number",
        "description": "The minimum number of static rules guaranteed to an extension across its enabled static rulesets. Any rules above this limit will count towards the global static rule limit."
      },
      "MAX_NUMBER_OF_STATIC_RULESETS": {
        "type": "number",
        "description": "The maximum number of static Rulesets an extension can specify as part of the rule_resources manifest key."
      },
      "MAX_NUMBER_OF_DISABLED_STATIC_RULES": {
        "type": "number",
        "description": "The maximum number of static rules that can be disabled on each static ruleset."
      },
      "MAX_NUMBER_OF_ENABLED_STATIC_RULESETS": {
        "type": "number",
        "description": "The maximum number of static Rulesets an extension can enable at any one time."
      },
      "MAX_NUMBER_OF_DYNAMIC_AND_SESSION_RULES": {
        "type": "number",
        "description": "Deprecated property returning the maximum number of dynamic and session rules an extension can add, replaced by MAX_NUMBER_OF_DYNAMIC_RULES/MAX_NUMBER_OF_SESSION_RULES."
      },
      "MAX_NUMBER_OF_DYNAMIC_RULES": {
        "type": "number",
        "description": "The maximum number of dynamic session rules an extension can add."
      },
      "MAX_NUMBER_OF_SESSION_RULES": {
        "type": "number",
        "description": "The maximum number of dynamic session rules an extension can add."
      },
      "MAX_NUMBER_OF_REGEX_RULES": {
        "type": "number",
        "description": "The maximum number of regular expression rules that an extension can add. This limit is evaluated separately for the set of session rules, dynamic rules and those specified in the rule_resources file."
      },
      "SESSION_RULESET_ID": {
        "type": "string",
        "value": "_session",
        "description": "Ruleset ID for the session-scoped rules added by the extension."
      }
    }
  }
]
PK
!<����kk2chrome/toolkit/content/extensions/schemas/dns.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "PermissionNoPrompt",
        "choices": [
          {
            "type": "string",
            "enum": ["dns"]
          }
        ]
      }
    ]
  },
  {
    "namespace": "dns",
    "description": "Asynchronous DNS API",
    "permissions": ["dns"],
    "types": [
      {
        "id": "DNSRecord",
        "type": "object",
        "description": "An object encapsulating a DNS Record.",
        "properties": {
          "canonicalName": {
            "type": "string",
            "optional": true,
            "description": "The canonical hostname for this record.  this value is empty if the record was not fetched with the 'canonical_name' flag."
          },
          "isTRR": {
            "type": "string",
            "description": "Record retreived with TRR."
          },
          "addresses": {
            "type": "array",
            "items": { "type": "string" }
          }
        }
      },
      {
        "id": "ResolveFlags",
        "type": "array",
        "items": {
          "type": "string",
          "enum": [
            "allow_name_collisions",
            "bypass_cache",
            "canonical_name",
            "disable_ipv4",
            "disable_ipv6",
            "disable_trr",
            "offline",
            "priority_low",
            "priority_medium",
            "speculate"
          ]
        }
      }
    ],
    "functions": [
      {
        "name": "resolve",
        "type": "function",
        "description": "Resolves a hostname to a DNS record.",
        "async": true,
        "parameters": [
          {
            "name": "hostname",
            "type": "string"
          },
          {
            "name": "flags",
            "optional": true,
            "default": [],
            "$ref": "ResolveFlags"
          }
        ]
      }
    ]
  }
]
PK
!<�T)�r�r8chrome/toolkit/content/extensions/schemas/downloads.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "OptionalPermission",
        "choices": [
          {
            "type": "string",
            "enum": ["downloads", "downloads.open"]
          }
        ]
      }
    ]
  },
  {
    "namespace": "downloads",
    "permissions": ["downloads"],
    "types": [
      {
        "id": "FilenameConflictAction",
        "type": "string",
        "enum": ["uniquify", "overwrite", "prompt"]
      },
      {
        "id": "InterruptReason",
        "type": "string",
        "enum": [
          "FILE_FAILED",
          "FILE_ACCESS_DENIED",
          "FILE_NO_SPACE",
          "FILE_NAME_TOO_LONG",
          "FILE_TOO_LARGE",
          "FILE_VIRUS_INFECTED",
          "FILE_TRANSIENT_ERROR",
          "FILE_BLOCKED",
          "FILE_SECURITY_CHECK_FAILED",
          "FILE_TOO_SHORT",
          "NETWORK_FAILED",
          "NETWORK_TIMEOUT",
          "NETWORK_DISCONNECTED",
          "NETWORK_SERVER_DOWN",
          "NETWORK_INVALID_REQUEST",
          "SERVER_FAILED",
          "SERVER_NO_RANGE",
          "SERVER_BAD_CONTENT",
          "SERVER_UNAUTHORIZED",
          "SERVER_CERT_PROBLEM",
          "SERVER_FORBIDDEN",
          "USER_CANCELED",
          "USER_SHUTDOWN",
          "CRASH"
        ]
      },
      {
        "id": "DangerType",
        "type": "string",
        "enum": [
          "file",
          "url",
          "content",
          "uncommon",
          "host",
          "unwanted",
          "safe",
          "accepted"
        ],
        "description": "<dl><dt>file</dt><dd>The download's filename is suspicious.</dd><dt>url</dt><dd>The download's URL is known to be malicious.</dd><dt>content</dt><dd>The downloaded file is known to be malicious.</dd><dt>uncommon</dt><dd>The download's URL is not commonly downloaded and could be dangerous.</dd><dt>safe</dt><dd>The download presents no known danger to the user's computer.</dd></dl>These string constants will never change, however the set of DangerTypes may change."
      },
      {
        "id": "State",
        "type": "string",
        "enum": ["in_progress", "interrupted", "complete"],
        "description": "<dl><dt>in_progress</dt><dd>The download is currently receiving data from the server.</dd><dt>interrupted</dt><dd>An error broke the connection with the file host.</dd><dt>complete</dt><dd>The download completed successfully.</dd></dl>These string constants will never change, however the set of States may change."
      },
      {
        "id": "DownloadItem",
        "type": "object",
        "properties": {
          "id": {
            "description": "An identifier that is persistent across browser sessions.",
            "type": "integer"
          },
          "url": {
            "description": "Absolute URL.",
            "type": "string"
          },
          "referrer": {
            "type": "string",
            "optional": true
          },
          "filename": {
            "description": "Absolute local path.",
            "type": "string"
          },
          "incognito": {
            "description": "False if this download is recorded in the history, true if it is not recorded.",
            "type": "boolean"
          },
          "cookieStoreId": {
            "type": "string",
            "optional": true,
            "description": "The cookie store ID of the contextual identity."
          },
          "danger": {
            "$ref": "DangerType",
            "description": "Indication of whether this download is thought to be safe or known to be suspicious."
          },
          "mime": {
            "description": "The file's MIME type.",
            "type": "string",
            "optional": true
          },
          "startTime": {
            "description": "Number of milliseconds between the unix epoch and when this download began.",
            "type": "string"
          },
          "endTime": {
            "description": "Number of milliseconds between the unix epoch and when this download ended.",
            "optional": true,
            "type": "string"
          },
          "estimatedEndTime": {
            "type": "string",
            "optional": true
          },
          "state": {
            "$ref": "State",
            "description": "Indicates whether the download is progressing, interrupted, or complete."
          },
          "paused": {
            "description": "True if the download has stopped reading data from the host, but kept the connection open.",
            "type": "boolean"
          },
          "canResume": {
            "type": "boolean"
          },
          "error": {
            "description": "Number indicating why a download was interrupted.",
            "optional": true,
            "$ref": "InterruptReason"
          },
          "bytesReceived": {
            "description": "Number of bytes received so far from the host, without considering file compression.",
            "type": "number"
          },
          "totalBytes": {
            "description": "Number of bytes in the whole file, without considering file compression, or -1 if unknown.",
            "type": "number"
          },
          "fileSize": {
            "description": "Number of bytes in the whole file post-decompression, or -1 if unknown.",
            "type": "number"
          },
          "exists": {
            "type": "boolean"
          },
          "byExtensionId": {
            "type": "string",
            "optional": true
          },
          "byExtensionName": {
            "type": "string",
            "optional": true
          }
        }
      },
      {
        "id": "StringDelta",
        "type": "object",
        "properties": {
          "current": {
            "optional": true,
            "type": "string"
          },
          "previous": {
            "optional": true,
            "type": "string"
          }
        }
      },
      {
        "id": "DoubleDelta",
        "type": "object",
        "properties": {
          "current": {
            "optional": true,
            "type": "number"
          },
          "previous": {
            "optional": true,
            "type": "number"
          }
        }
      },
      {
        "id": "BooleanDelta",
        "type": "object",
        "properties": {
          "current": {
            "optional": true,
            "type": "boolean"
          },
          "previous": {
            "optional": true,
            "type": "boolean"
          }
        }
      },
      {
        "id": "DownloadTime",
        "description": "A time specified as a Date object, a number or string representing milliseconds since the epoch, or an ISO 8601 string",
        "choices": [
          {
            "type": "string",
            "pattern": "^[1-9]\\d*$"
          },
          {
            "$ref": "extensionTypes.Date"
          }
        ]
      },
      {
        "id": "DownloadQuery",
        "description": "Parameters that combine to specify a predicate that can be used to select a set of downloads.  Used for example in search() and erase()",
        "type": "object",
        "properties": {
          "query": {
            "description": "This array of search terms limits results to <a href='#type-DownloadItem'>DownloadItems</a> whose <code>filename</code> or <code>url</code> contain all of the search terms that do not begin with a dash '-' and none of the search terms that do begin with a dash.",
            "optional": true,
            "type": "array",
            "items": { "type": "string" }
          },
          "startedBefore": {
            "description": "Limits results to downloads that started before the given ms since the epoch.",
            "optional": true,
            "$ref": "DownloadTime"
          },
          "startedAfter": {
            "description": "Limits results to downloads that started after the given ms since the epoch.",
            "optional": true,
            "$ref": "DownloadTime"
          },
          "endedBefore": {
            "description": "Limits results to downloads that ended before the given ms since the epoch.",
            "optional": true,
            "$ref": "DownloadTime"
          },
          "endedAfter": {
            "description": "Limits results to downloads that ended after the given ms since the epoch.",
            "optional": true,
            "$ref": "DownloadTime"
          },
          "totalBytesGreater": {
            "description": "Limits results to downloads whose totalBytes is greater than the given integer.",
            "optional": true,
            "type": "number"
          },
          "totalBytesLess": {
            "description": "Limits results to downloads whose totalBytes is less than the given integer.",
            "optional": true,
            "type": "number"
          },
          "filenameRegex": {
            "description": "Limits results to <a href='#type-DownloadItem'>DownloadItems</a> whose <code>filename</code> matches the given regular expression.",
            "optional": true,
            "type": "string"
          },
          "urlRegex": {
            "description": "Limits results to <a href='#type-DownloadItem'>DownloadItems</a> whose <code>url</code> matches the given regular expression.",
            "optional": true,
            "type": "string"
          },
          "limit": {
            "description": "Setting this integer limits the number of results. Otherwise, all matching <a href='#type-DownloadItem'>DownloadItems</a> will be returned.",
            "optional": true,
            "type": "integer"
          },
          "orderBy": {
            "description": "Setting elements of this array to <a href='#type-DownloadItem'>DownloadItem</a> properties in order to sort the search results. For example, setting <code>orderBy='startTime'</code> sorts the <a href='#type-DownloadItem'>DownloadItems</a> by their start time in ascending order. To specify descending order, prefix <code>orderBy</code> with a hyphen: '-startTime'.",
            "optional": true,
            "type": "array",
            "items": { "type": "string" }
          },
          "id": {
            "type": "integer",
            "optional": true
          },
          "url": {
            "description": "Absolute URL.",
            "optional": true,
            "type": "string"
          },
          "filename": {
            "description": "Absolute local path.",
            "optional": true,
            "type": "string"
          },
          "cookieStoreId": {
            "type": "string",
            "optional": true,
            "description": "The cookie store ID of the contextual identity."
          },
          "danger": {
            "$ref": "DangerType",
            "description": "Indication of whether this download is thought to be safe or known to be suspicious.",
            "optional": true
          },
          "mime": {
            "description": "The file's MIME type.",
            "optional": true,
            "type": "string"
          },
          "startTime": {
            "optional": true,
            "type": "string"
          },
          "endTime": {
            "optional": true,
            "type": "string"
          },
          "state": {
            "$ref": "State",
            "description": "Indicates whether the download is progressing, interrupted, or complete.",
            "optional": true
          },
          "paused": {
            "description": "True if the download has stopped reading data from the host, but kept the connection open.",
            "optional": true,
            "type": "boolean"
          },
          "error": {
            "description": "Why a download was interrupted.",
            "optional": true,
            "$ref": "InterruptReason"
          },
          "bytesReceived": {
            "description": "Number of bytes received so far from the host, without considering file compression.",
            "optional": true,
            "type": "number"
          },
          "totalBytes": {
            "description": "Number of bytes in the whole file, without considering file compression, or -1 if unknown.",
            "optional": true,
            "type": "number"
          },
          "fileSize": {
            "description": "Number of bytes in the whole file post-decompression, or -1 if unknown.",
            "optional": true,
            "type": "number"
          },
          "exists": {
            "type": "boolean",
            "optional": true
          }
        }
      }
    ],
    "functions": [
      {
        "name": "download",
        "type": "function",
        "async": "callback",
        "description": "Download a URL. If the URL uses the HTTP[S] protocol, then the request will include all cookies currently set for its hostname. If both <code>filename</code> and <code>saveAs</code> are specified, then the Save As dialog will be displayed, pre-populated with the specified <code>filename</code>. If the download started successfully, <code>callback</code> will be called with the new <a href='#type-DownloadItem'>DownloadItem</a>'s <code>downloadId</code>. If there was an error starting the download, then <code>callback</code> will be called with <code>downloadId=undefined</code> and <a href='extension.html#property-lastError'>chrome.extension.lastError</a> will contain a descriptive string. The error strings are not guaranteed to remain backwards compatible between releases. You must not parse it.",
        "parameters": [
          {
            "description": "What to download and how.",
            "name": "options",
            "type": "object",
            "properties": {
              "url": {
                "description": "The URL to download.",
                "type": "string",
                "format": "url"
              },
              "filename": {
                "description": "A file path relative to the Downloads directory to contain the downloaded file.",
                "optional": true,
                "type": "string"
              },
              "incognito": {
                "description": "Whether to associate the download with a private browsing session.",
                "optional": true,
                "default": false,
                "type": "boolean"
              },
              "cookieStoreId": {
                "type": "string",
                "optional": true,
                "description": "The cookie store ID of the contextual identity; requires \"cookies\" permission."
              },
              "conflictAction": {
                "$ref": "FilenameConflictAction",
                "optional": true
              },
              "saveAs": {
                "description": "Use a file-chooser to allow the user to select a filename. If the option is not specified, the file chooser will be shown only if the Firefox \"Always ask you where to save files\" option is enabled (i.e. the pref <code>browser.download.useDownloadDir</code> is set to <code>false</code>).",
                "optional": true,
                "type": "boolean"
              },
              "method": {
                "description": "The HTTP method to use if the URL uses the HTTP[S] protocol.",
                "enum": ["GET", "POST"],
                "optional": true,
                "type": "string"
              },
              "headers": {
                "optional": true,
                "type": "array",
                "description": "Extra HTTP headers to send with the request if the URL uses the HTTP[s] protocol. Each header is represented as a dictionary containing the keys <code>name</code> and either <code>value</code> or <code>binaryValue</code>, restricted to those allowed by XMLHttpRequest.",
                "items": {
                  "type": "object",
                  "properties": {
                    "name": {
                      "description": "Name of the HTTP header.",
                      "type": "string"
                    },
                    "value": {
                      "description": "Value of the HTTP header.",
                      "type": "string"
                    }
                  }
                }
              },
              "body": {
                "description": "Post body.",
                "optional": true,
                "type": "string"
              },
              "allowHttpErrors": {
                "description": "When this flag is set to <code>true</code>, then the browser will allow downloads to proceed after encountering HTTP errors such as <code>404 Not Found</code>.",
                "optional": true,
                "default": false,
                "type": "boolean"
              }
            }
          },
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": [
              {
                "name": "downloadId",
                "type": "integer"
              }
            ]
          }
        ]
      },
      {
        "name": "search",
        "type": "function",
        "async": "callback",
        "description": "Find <a href='#type-DownloadItem'>DownloadItems</a>. Set <code>query</code> to the empty object to get all <a href='#type-DownloadItem'>DownloadItems</a>. To get a specific <a href='#type-DownloadItem'>DownloadItem</a>, set only the <code>id</code> field.",
        "parameters": [
          {
            "name": "query",
            "$ref": "DownloadQuery"
          },
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "items": {
                  "$ref": "DownloadItem"
                },
                "name": "results",
                "type": "array"
              }
            ]
          }
        ]
      },
      {
        "name": "pause",
        "type": "function",
        "async": "callback",
        "description": "Pause the download. If the request was successful the download is in a paused state. Otherwise <a href='extension.html#property-lastError'>chrome.extension.lastError</a> contains an error message. The request will fail if the download is not active.",
        "parameters": [
          {
            "description": "The id of the download to pause.",
            "name": "downloadId",
            "type": "integer"
          },
          {
            "name": "callback",
            "optional": true,
            "parameters": [],
            "type": "function"
          }
        ]
      },
      {
        "name": "resume",
        "type": "function",
        "async": "callback",
        "description": "Resume a paused download. If the request was successful the download is in progress and unpaused. Otherwise <a href='extension.html#property-lastError'>chrome.extension.lastError</a> contains an error message. The request will fail if the download is not active.",
        "parameters": [
          {
            "description": "The id of the download to resume.",
            "name": "downloadId",
            "type": "integer"
          },
          {
            "name": "callback",
            "optional": true,
            "parameters": [],
            "type": "function"
          }
        ]
      },
      {
        "name": "cancel",
        "type": "function",
        "async": "callback",
        "description": "Cancel a download. When <code>callback</code> is run, the download is cancelled, completed, interrupted or doesn't exist anymore.",
        "parameters": [
          {
            "description": "The id of the download to cancel.",
            "name": "downloadId",
            "type": "integer"
          },
          {
            "name": "callback",
            "optional": true,
            "parameters": [],
            "type": "function"
          }
        ]
      },
      {
        "name": "getFileIcon",
        "type": "function",
        "async": "callback",
        "description": "Retrieve an icon for the specified download. For new downloads, file icons are available after the <a href='#event-onCreated'>onCreated</a> event has been received. The image returned by this function while a download is in progress may be different from the image returned after the download is complete. Icon retrieval is done by querying the underlying operating system or toolkit depending on the platform. The icon that is returned will therefore depend on a number of factors including state of the download, platform, registered file types and visual theme. If a file icon cannot be determined, <a href='extension.html#property-lastError'>chrome.extension.lastError</a> will contain an error message.",
        "parameters": [
          {
            "description": "The identifier for the download.",
            "name": "downloadId",
            "type": "integer"
          },
          {
            "name": "options",
            "optional": true,
            "properties": {
              "size": {
                "description": "The size of the icon.  The returned icon will be square with dimensions size * size pixels.  The default size for the icon is 32x32 pixels.",
                "optional": true,
                "minimum": 1,
                "maximum": 127,
                "type": "integer"
              }
            },
            "type": "object"
          },
          {
            "name": "callback",
            "parameters": [
              {
                "name": "iconURL",
                "optional": true,
                "type": "string"
              }
            ],
            "type": "function"
          }
        ]
      },
      {
        "name": "open",
        "type": "function",
        "async": "callback",
        "requireUserInput": true,
        "description": "Open the downloaded file.",
        "permissions": ["downloads.open"],
        "parameters": [
          {
            "name": "downloadId",
            "type": "integer"
          },
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "show",
        "type": "function",
        "description": "Show the downloaded file in its folder in a file manager.",
        "async": "callback",
        "parameters": [
          {
            "name": "downloadId",
            "type": "integer"
          },
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": [
              {
                "name": "success",
                "type": "boolean"
              }
            ]
          }
        ]
      },
      {
        "name": "showDefaultFolder",
        "type": "function",
        "parameters": []
      },
      {
        "name": "erase",
        "type": "function",
        "async": "callback",
        "description": "Erase matching <a href='#type-DownloadItem'>DownloadItems</a> from history",
        "parameters": [
          {
            "name": "query",
            "$ref": "DownloadQuery"
          },
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": [
              {
                "items": {
                  "type": "integer"
                },
                "name": "erasedIds",
                "type": "array"
              }
            ]
          }
        ]
      },
      {
        "name": "removeFile",
        "async": "callback",
        "type": "function",
        "parameters": [
          {
            "name": "downloadId",
            "type": "integer"
          },
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "description": "Prompt the user to either accept or cancel a dangerous download. <code>acceptDanger()</code> does not automatically accept dangerous downloads.",
        "name": "acceptDanger",
        "unsupported": true,
        "parameters": [
          {
            "name": "downloadId",
            "type": "integer"
          },
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": []
          }
        ],
        "type": "function"
      },
      {
        "description": "Initiate dragging the file to another application.",
        "name": "drag",
        "unsupported": true,
        "parameters": [
          {
            "name": "downloadId",
            "type": "integer"
          }
        ],
        "type": "function"
      },
      {
        "name": "setShelfEnabled",
        "type": "function",
        "unsupported": true,
        "parameters": [
          {
            "name": "enabled",
            "type": "boolean"
          }
        ]
      }
    ],
    "events": [
      {
        "description": "This event fires with the <a href='#type-DownloadItem'>DownloadItem</a> object when a download begins.",
        "name": "onCreated",
        "parameters": [
          {
            "$ref": "DownloadItem",
            "name": "downloadItem"
          }
        ],
        "type": "function"
      },
      {
        "description": "Fires with the <code>downloadId</code> when a download is erased from history.",
        "name": "onErased",
        "parameters": [
          {
            "name": "downloadId",
            "description": "The <code>id</code> of the <a href='#type-DownloadItem'>DownloadItem</a> that was erased.",
            "type": "integer"
          }
        ],
        "type": "function"
      },
      {
        "name": "onChanged",
        "description": "When any of a <a href='#type-DownloadItem'>DownloadItem</a>'s properties except <code>bytesReceived</code> changes, this event fires with the <code>downloadId</code> and an object containing the properties that changed.",
        "parameters": [
          {
            "name": "downloadDelta",
            "type": "object",
            "properties": {
              "id": {
                "description": "The <code>id</code> of the <a href='#type-DownloadItem'>DownloadItem</a> that changed.",
                "type": "integer"
              },
              "url": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>url</code>.",
                "optional": true,
                "$ref": "StringDelta"
              },
              "filename": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>filename</code>.",
                "optional": true,
                "$ref": "StringDelta"
              },
              "danger": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>danger</code>.",
                "optional": true,
                "$ref": "StringDelta"
              },
              "mime": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>mime</code>.",
                "optional": true,
                "$ref": "StringDelta"
              },
              "startTime": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>startTime</code>.",
                "optional": true,
                "$ref": "StringDelta"
              },
              "endTime": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>endTime</code>.",
                "optional": true,
                "$ref": "StringDelta"
              },
              "state": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>state</code>.",
                "optional": true,
                "$ref": "StringDelta"
              },
              "canResume": {
                "optional": true,
                "$ref": "BooleanDelta"
              },
              "paused": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>paused</code>.",
                "optional": true,
                "$ref": "BooleanDelta"
              },
              "error": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>error</code>.",
                "optional": true,
                "$ref": "StringDelta"
              },
              "totalBytes": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>totalBytes</code>.",
                "optional": true,
                "$ref": "DoubleDelta"
              },
              "fileSize": {
                "description": "Describes a change in a <a href='#type-DownloadItem'>DownloadItem</a>'s <code>fileSize</code>.",
                "optional": true,
                "$ref": "DoubleDelta"
              },
              "exists": {
                "optional": true,
                "$ref": "BooleanDelta"
              }
            }
          }
        ],
        "type": "function"
      }
    ]
  }
]
PK
!<vb��4�45chrome/toolkit/content/extensions/schemas/events.json[
  {
    "namespace": "events",
    "description": "The <code>chrome.events</code> namespace contains common types used by APIs dispatching events to notify you when something interesting happens.",
    "types": [
      {
        "id": "Rule",
        "type": "object",
        "description": "Description of a declarative rule for handling events.",
        "properties": {
          "id": {
            "type": "string",
            "optional": true,
            "description": "Optional identifier that allows referencing this rule."
          },
          "tags": {
            "type": "array",
            "items": { "type": "string" },
            "optional": true,
            "description": "Tags can be used to annotate rules and perform operations on sets of rules."
          },
          "conditions": {
            "type": "array",
            "items": { "type": "any" },
            "description": "List of conditions that can trigger the actions."
          },
          "actions": {
            "type": "array",
            "items": { "type": "any" },
            "description": "List of actions that are triggered if one of the condtions is fulfilled."
          },
          "priority": {
            "type": "integer",
            "optional": true,
            "description": "Optional priority of this rule. Defaults to 100."
          }
        }
      },
      {
        "id": "Event",
        "type": "object",
        "description": "An object which allows the addition and removal of listeners for a Chrome event.",
        "functions": [
          {
            "name": "addListener",
            "type": "function",
            "description": "Registers an event listener <em>callback</em> to an event.",
            "parameters": [
              {
                "name": "callback",
                "type": "function",
                "description": "Called when an event occurs. The parameters of this function depend on the type of event."
              }
            ]
          },
          {
            "name": "removeListener",
            "type": "function",
            "description": "Deregisters an event listener <em>callback</em> from an event.",
            "parameters": [
              {
                "name": "callback",
                "type": "function",
                "description": "Listener that shall be unregistered."
              }
            ]
          },
          {
            "name": "hasListener",
            "type": "function",
            "parameters": [
              {
                "name": "callback",
                "type": "function",
                "description": "Listener whose registration status shall be tested."
              }
            ],
            "returns": {
              "type": "boolean",
              "description": "True if <em>callback</em> is registered to the event."
            }
          },
          {
            "name": "hasListeners",
            "type": "function",
            "parameters": [],
            "returns": {
              "type": "boolean",
              "description": "True if any event listeners are registered to the event."
            }
          },
          {
            "name": "addRules",
            "unsupported": true,
            "type": "function",
            "description": "Registers rules to handle events.",
            "parameters": [
              {
                "name": "eventName",
                "type": "string",
                "description": "Name of the event this function affects."
              },
              {
                "name": "webViewInstanceId",
                "type": "integer",
                "description": "If provided, this is an integer that uniquely identfies the <webview> associated with this function call."
              },
              {
                "name": "rules",
                "type": "array",
                "items": { "$ref": "Rule" },
                "description": "Rules to be registered. These do not replace previously registered rules."
              },
              {
                "name": "callback",
                "optional": true,
                "type": "function",
                "parameters": [
                  {
                    "name": "rules",
                    "type": "array",
                    "items": { "$ref": "Rule" },
                    "description": "Rules that were registered, the optional parameters are filled with values."
                  }
                ],
                "description": "Called with registered rules."
              }
            ]
          },
          {
            "name": "getRules",
            "unsupported": true,
            "type": "function",
            "description": "Returns currently registered rules.",
            "parameters": [
              {
                "name": "eventName",
                "type": "string",
                "description": "Name of the event this function affects."
              },
              {
                "name": "webViewInstanceId",
                "type": "integer",
                "description": "If provided, this is an integer that uniquely identfies the <webview> associated with this function call."
              },
              {
                "name": "ruleIdentifiers",
                "optional": true,
                "type": "array",
                "items": { "type": "string" },
                "description": "If an array is passed, only rules with identifiers contained in this array are returned."
              },
              {
                "name": "callback",
                "type": "function",
                "parameters": [
                  {
                    "name": "rules",
                    "type": "array",
                    "items": { "$ref": "Rule" },
                    "description": "Rules that were registered, the optional parameters are filled with values."
                  }
                ],
                "description": "Called with registered rules."
              }
            ]
          },
          {
            "name": "removeRules",
            "unsupported": true,
            "type": "function",
            "description": "Unregisters currently registered rules.",
            "parameters": [
              {
                "name": "eventName",
                "type": "string",
                "description": "Name of the event this function affects."
              },
              {
                "name": "webViewInstanceId",
                "type": "integer",
                "description": "If provided, this is an integer that uniquely identfies the <webview> associated with this function call."
              },
              {
                "name": "ruleIdentifiers",
                "optional": true,
                "type": "array",
                "items": { "type": "string" },
                "description": "If an array is passed, only rules with identifiers contained in this array are unregistered."
              },
              {
                "name": "callback",
                "optional": true,
                "type": "function",
                "parameters": [],
                "description": "Called when rules were unregistered."
              }
            ]
          }
        ]
      },
      {
        "id": "UrlFilter",
        "type": "object",
        "description": "Filters URLs for various criteria. See <a href='events#filtered'>event filtering</a>. All criteria are case sensitive.",
        "properties": {
          "hostContains": {
            "type": "string",
            "description": "Matches if the host name of the URL contains a specified string. To test whether a host name component has a prefix 'foo', use hostContains: '.foo'. This matches 'www.foobar.com' and 'foo.com', because an implicit dot is added at the beginning of the host name. Similarly, hostContains can be used to match against component suffix ('foo.') and to exactly match against components ('.foo.'). Suffix- and exact-matching for the last components need to be done separately using hostSuffix, because no implicit dot is added at the end of the host name.",
            "optional": true
          },
          "hostEquals": {
            "type": "string",
            "description": "Matches if the host name of the URL is equal to a specified string.",
            "optional": true
          },
          "hostPrefix": {
            "type": "string",
            "description": "Matches if the host name of the URL starts with a specified string.",
            "optional": true
          },
          "hostSuffix": {
            "type": "string",
            "description": "Matches if the host name of the URL ends with a specified string.",
            "optional": true
          },
          "pathContains": {
            "type": "string",
            "description": "Matches if the path segment of the URL contains a specified string.",
            "optional": true
          },
          "pathEquals": {
            "type": "string",
            "description": "Matches if the path segment of the URL is equal to a specified string.",
            "optional": true
          },
          "pathPrefix": {
            "type": "string",
            "description": "Matches if the path segment of the URL starts with a specified string.",
            "optional": true
          },
          "pathSuffix": {
            "type": "string",
            "description": "Matches if the path segment of the URL ends with a specified string.",
            "optional": true
          },
          "queryContains": {
            "type": "string",
            "description": "Matches if the query segment of the URL contains a specified string.",
            "optional": true
          },
          "queryEquals": {
            "type": "string",
            "description": "Matches if the query segment of the URL is equal to a specified string.",
            "optional": true
          },
          "queryPrefix": {
            "type": "string",
            "description": "Matches if the query segment of the URL starts with a specified string.",
            "optional": true
          },
          "querySuffix": {
            "type": "string",
            "description": "Matches if the query segment of the URL ends with a specified string.",
            "optional": true
          },
          "urlContains": {
            "type": "string",
            "description": "Matches if the URL (without fragment identifier) contains a specified string. Port numbers are stripped from the URL if they match the default port number.",
            "optional": true
          },
          "urlEquals": {
            "type": "string",
            "description": "Matches if the URL (without fragment identifier) is equal to a specified string. Port numbers are stripped from the URL if they match the default port number.",
            "optional": true
          },
          "urlMatches": {
            "type": "string",
            "description": "Matches if the URL (without fragment identifier) matches a specified regular expression. Port numbers are stripped from the URL if they match the default port number. The regular expressions use the <a href=\"https://github.com/google/re2/blob/master/doc/syntax.txt\">RE2 syntax</a>.",
            "optional": true
          },
          "originAndPathMatches": {
            "type": "string",
            "description": "Matches if the URL without query segment and fragment identifier matches a specified regular expression. Port numbers are stripped from the URL if they match the default port number. The regular expressions use the <a href=\"https://github.com/google/re2/blob/master/doc/syntax.txt\">RE2 syntax</a>.",
            "optional": true
          },
          "urlPrefix": {
            "type": "string",
            "description": "Matches if the URL (without fragment identifier) starts with a specified string. Port numbers are stripped from the URL if they match the default port number.",
            "optional": true
          },
          "urlSuffix": {
            "type": "string",
            "description": "Matches if the URL (without fragment identifier) ends with a specified string. Port numbers are stripped from the URL if they match the default port number.",
            "optional": true
          },
          "schemes": {
            "type": "array",
            "description": "Matches if the scheme of the URL is equal to any of the schemes specified in the array.",
            "optional": true,
            "items": { "type": "string" }
          },
          "ports": {
            "type": "array",
            "description": "Matches if the port of the URL is contained in any of the specified port lists. For example <code>[80, 443, [1000, 1200]]</code> matches all requests on port 80, 443 and in the range 1000-1200.",
            "optional": true,
            "items": {
              "choices": [
                { "type": "integer", "description": "A specific port." },
                {
                  "type": "array",
                  "minItems": 2,
                  "maxItems": 2,
                  "items": { "type": "integer" },
                  "description": "A pair of integers identiying the start and end (both inclusive) of a port range."
                }
              ]
            }
          }
        }
      }
    ]
  }
]
PK
!<&A��
�
:chrome/toolkit/content/extensions/schemas/experiments.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "Permission",
        "choices": [
          {
            "type": "string",
            "pattern": "^experiments(\\.\\w+)+$"
          }
        ]
      },
      {
        "$extend": "WebExtensionManifest",
        "properties": {
          "experiment_apis": {
            "type": "object",
            "additionalProperties": { "$ref": "experiments.ExperimentAPI" },
            "optional": true,
            "privileged": true
          }
        }
      }
    ]
  },
  {
    "namespace": "experiments",
    "types": [
      {
        "id": "ExperimentAPI",
        "type": "object",
        "properties": {
          "schema": { "$ref": "ExperimentURL" },

          "parent": {
            "type": "object",
            "properties": {
              "events": {
                "$ref": "APIEvents",
                "optional": true,
                "default": []
              },

              "paths": {
                "$ref": "APIPaths",
                "optional": true,
                "default": []
              },

              "script": { "$ref": "ExperimentURL" },

              "scopes": {
                "type": "array",
                "items": { "$ref": "APIParentScope", "onError": "warn" },
                "optional": true,
                "default": []
              }
            },
            "optional": true
          },

          "child": {
            "type": "object",
            "properties": {
              "paths": { "$ref": "APIPaths" },

              "script": { "$ref": "ExperimentURL" },

              "scopes": {
                "type": "array",
                "minItems": 1,
                "items": { "$ref": "APIChildScope", "onError": "warn" }
              }
            },
            "optional": true
          }
        }
      },
      {
        "id": "ExperimentURL",
        "type": "string",
        "format": "unresolvedRelativeUrl"
      },
      {
        "id": "APIPaths",
        "type": "array",
        "items": { "$ref": "APIPath" },
        "minItems": 1
      },
      {
        "id": "APIPath",
        "type": "array",
        "items": { "type": "string" },
        "minItems": 1
      },
      {
        "id": "APIEvents",
        "type": "array",
        "items": { "$ref": "APIEvent", "onError": "warn" }
      },
      {
        "id": "APIEvent",
        "type": "string",
        "enum": ["startup"]
      },
      {
        "id": "APIParentScope",
        "type": "string",
        "enum": ["addon_parent", "content_parent", "devtools_parent"]
      },
      {
        "id": "APIChildScope",
        "type": "string",
        "enum": ["addon_child", "content_child", "devtools_child"]
      }
    ]
  }
]
PK
!<(����8chrome/toolkit/content/extensions/schemas/extension.json[
  {
    "namespace": "extension",
    "allowedContexts": ["content", "devtools"],
    "description": "The <code>browser.extension</code> API has utilities that can be used by any extension page. It includes support for exchanging messages between an extension and its content scripts or between extensions, as described in detail in $(topic:messaging)[Message Passing].",
    "properties": {
      "lastError": {
        "type": "object",
        "optional": true,
        "max_manifest_version": 2,
        "deprecated": "Please use $(ref:runtime.lastError).",
        "allowedContexts": ["content", "devtools"],
        "description": "Set for the lifetime of a callback if an ansychronous extension api has resulted in an error. If no error has occured lastError will be <var>undefined</var>.",
        "properties": {
          "message": {
            "type": "string",
            "description": "Description of the error that has taken place."
          }
        },
        "additionalProperties": {
          "type": "any"
        }
      },
      "inIncognitoContext": {
        "type": "boolean",
        "optional": true,
        "allowedContexts": ["content", "devtools"],
        "description": "True for content scripts running inside incognito tabs, and for extension pages running inside an incognito process. The latter only applies to extensions with 'split' incognito_behavior."
      }
    },
    "types": [
      {
        "id": "ViewType",
        "type": "string",
        "enum": ["tab", "popup", "sidebar"],
        "description": "The type of extension view."
      }
    ],
    "functions": [
      {
        "name": "getURL",
        "type": "function",
        "deprecated": "Please use $(ref:runtime.getURL).",
        "max_manifest_version": 2,
        "allowedContexts": ["content", "devtools"],
        "description": "Converts a relative path within an extension install directory to a fully-qualified URL.",
        "parameters": [
          {
            "type": "string",
            "name": "path",
            "description": "A path to a resource within an extension expressed relative to its install directory."
          }
        ],
        "returns": {
          "type": "string",
          "description": "The fully-qualified URL to the resource."
        }
      },
      {
        "name": "getViews",
        "type": "function",
        "description": "Returns an array of the JavaScript 'window' objects for each of the pages running inside the current extension.",
        "parameters": [
          {
            "type": "object",
            "name": "fetchProperties",
            "optional": true,
            "properties": {
              "type": {
                "$ref": "ViewType",
                "optional": true,
                "description": "The type of view to get. If omitted, returns all views (including background pages and tabs). Valid values: 'tab', 'popup', 'sidebar'."
              },
              "windowId": {
                "type": "integer",
                "optional": true,
                "description": "The window to restrict the search to. If omitted, returns all views."
              },
              "tabId": {
                "type": "integer",
                "optional": true,
                "description": "Find a view according to a tab id. If this field is omitted, returns all views."
              }
            }
          }
        ],
        "returns": {
          "type": "array",
          "description": "Array of global objects",
          "items": {
            "type": "object",
            "isInstanceOf": "Window",
            "additionalProperties": { "type": "any" }
          }
        }
      },
      {
        "name": "getBackgroundPage",
        "type": "function",
        "description": "Returns the JavaScript 'window' object for the background page running inside the current extension. Returns null if the extension has no background page.",
        "parameters": [],
        "returns": {
          "type": "object",
          "optional": true,
          "isInstanceOf": "Window",
          "additionalProperties": { "type": "any" }
        }
      },
      {
        "name": "isAllowedIncognitoAccess",
        "type": "function",
        "description": "Retrieves the state of the extension's access to Incognito-mode (as determined by the user-controlled 'Allowed in Incognito' checkbox.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "isAllowedAccess",
                "type": "boolean",
                "description": "True if the extension has access to Incognito mode, false otherwise."
              }
            ]
          }
        ]
      },
      {
        "name": "isAllowedFileSchemeAccess",
        "type": "function",
        "description": "Retrieves the state of the extension's access to the 'file://' scheme (as determined by the user-controlled 'Allow access to File URLs' checkbox.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "isAllowedAccess",
                "type": "boolean",
                "description": "True if the extension can access the 'file://' scheme, false otherwise."
              }
            ]
          }
        ]
      },
      {
        "name": "setUpdateUrlData",
        "unsupported": true,
        "type": "function",
        "description": "Sets the value of the ap CGI parameter used in the extension's update URL.  This value is ignored for extensions that are hosted in the browser vendor's store.",
        "parameters": [{ "type": "string", "name": "data", "maxLength": 1024 }]
      }
    ],
    "events": [
      {
        "name": "onRequest",
        "unsupported": true,
        "deprecated": "Please use $(ref:runtime.onMessage).",
        "type": "function",
        "description": "Fired when a request is sent from either an extension process or a content script.",
        "parameters": [
          {
            "name": "request",
            "type": "any",
            "optional": true,
            "description": "The request sent by the calling script."
          },
          { "name": "sender", "$ref": "runtime.MessageSender" },
          {
            "name": "sendResponse",
            "type": "function",
            "description": "Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object, or undefined if there is no response. If you have more than one <code>onRequest</code> listener in the same document, then only one may send a response."
          }
        ]
      },
      {
        "name": "onRequestExternal",
        "unsupported": true,
        "deprecated": "Please use $(ref:runtime.onMessageExternal).",
        "type": "function",
        "description": "Fired when a request is sent from another extension.",
        "parameters": [
          {
            "name": "request",
            "type": "any",
            "optional": true,
            "description": "The request sent by the calling script."
          },
          { "name": "sender", "$ref": "runtime.MessageSender" },
          {
            "name": "sendResponse",
            "type": "function",
            "description": "Function to call when you have a response. The argument should be any JSON-ifiable object, or undefined if there is no response."
          }
        ]
      }
    ]
  }
]
PK
!<[E�}r	r	Jchrome/toolkit/content/extensions/schemas/extension_protocol_handlers.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "id": "ProtocolHandler",
        "type": "object",
        "description": "Represents a protocol handler definition.",
        "properties": {
          "name": {
            "description": "A user-readable title string for the protocol handler. This will be displayed to the user in interface objects as needed.",
            "type": "string"
          },
          "protocol": {
            "description": "The protocol the site wishes to handle, specified as a string. For example, you can register to handle SMS text message links by registering to handle the \"sms\" scheme.",
            "choices": [
              {
                "type": "string",
                "enum": [
                  "bitcoin",
                  "dat",
                  "dweb",
                  "ftp",
                  "geo",
                  "gopher",
                  "im",
                  "ipfs",
                  "ipns",
                  "irc",
                  "ircs",
                  "magnet",
                  "mailto",
                  "matrix",
                  "mms",
                  "news",
                  "nntp",
                  "sip",
                  "sms",
                  "smsto",
                  "ssb",
                  "ssh",
                  "tel",
                  "urn",
                  "webcal",
                  "wtai",
                  "xmpp"
                ]
              },
              {
                "type": "string",
                "pattern": "^(ext|web)\\+[a-z0-9.+-]+$"
              }
            ]
          },
          "uriTemplate": {
            "description": "The URL of the handler, as a string. This string should include \"%s\" as a placeholder which will be replaced with the escaped URL of the document to be handled. This URL might be a true URL, or it could be a phone number, email address, or so forth.",
            "preprocess": "localize",
            "choices": [{ "$ref": "ExtensionURL" }, { "$ref": "HttpURL" }]
          }
        }
      },
      {
        "$extend": "WebExtensionManifest",
        "properties": {
          "protocol_handlers": {
            "description": "A list of protocol handler definitions.",
            "optional": true,
            "type": "array",
            "items": { "$ref": "ProtocolHandler" }
          }
        }
      }
    ]
  }
]
PK
!<�k?��>chrome/toolkit/content/extensions/schemas/extension_types.json[
  {
    "namespace": "extensionTypes",
    "description": "The <code>browser.extensionTypes</code> API contains type declarations for WebExtensions.",
    "types": [
      {
        "id": "ImageFormat",
        "type": "string",
        "enum": ["jpeg", "png"],
        "description": "The format of an image."
      },
      {
        "id": "ImageDetails",
        "type": "object",
        "description": "Details about the format, quality, area and scale of the capture.",
        "properties": {
          "format": {
            "$ref": "ImageFormat",
            "optional": true,
            "description": "The format of the resulting image.  Default is <code>\"jpeg\"</code>."
          },
          "quality": {
            "type": "integer",
            "optional": true,
            "minimum": 0,
            "maximum": 100,
            "description": "When format is <code>\"jpeg\"</code>, controls the quality of the resulting image.  This value is ignored for PNG images.  As quality is decreased, the resulting image will have more visual artifacts, and the number of bytes needed to store it will decrease."
          },
          "rect": {
            "type": "object",
            "optional": true,
            "description": "The area of the document to capture, in CSS pixels, relative to the page.  If omitted, capture the visible viewport.",
            "properties": {
              "x": { "type": "number" },
              "y": { "type": "number" },
              "width": { "type": "number" },
              "height": { "type": "number" }
            }
          },
          "scale": {
            "type": "number",
            "optional": true,
            "description": "The scale of the resulting image.  Defaults to <code>devicePixelRatio</code>."
          },
          "resetScrollPosition": {
            "type": "boolean",
            "optional": true,
            "description": "If true, temporarily resets the scroll position of the document to 0. Only takes effect if rect is also specified."
          }
        }
      },
      {
        "id": "RunAt",
        "type": "string",
        "enum": ["document_start", "document_end", "document_idle"],
        "description": "The soonest that the JavaScript or CSS will be injected into the tab."
      },
      {
        "id": "ExecutionWorld",
        "type": "string",
        "enum": ["ISOLATED", "MAIN"],
        "description": "The JavaScript world for a script to execute within. <code>ISOLATED</code> is the default execution environment of content scripts, <code>MAIN</code> is the web page's execution environment."
      },
      {
        "id": "CSSOrigin",
        "type": "string",
        "enum": ["user", "author"],
        "description": "The origin of the CSS to inject, this affects the cascading order (priority) of the stylesheet."
      },
      {
        "id": "InjectDetails",
        "type": "object",
        "description": "Details of the script or CSS to inject. Either the code or the file property must be set, but both may not be set at the same time.",
        "properties": {
          "code": {
            "type": "string",
            "optional": true,
            "description": "JavaScript or CSS code to inject.<br><br><b>Warning:</b><br>Be careful using the <code>code</code> parameter. Incorrect use of it may open your extension to <a href=\"https://en.wikipedia.org/wiki/Cross-site_scripting\">cross site scripting</a> attacks."
          },
          "file": {
            "type": "string",
            "optional": true,
            "description": "JavaScript or CSS file to inject."
          },
          "allFrames": {
            "type": "boolean",
            "optional": true,
            "description": "If allFrames is <code>true</code>, implies that the JavaScript or CSS should be injected into all frames of current page. By default, it's <code>false</code> and is only injected into the top frame."
          },
          "matchAboutBlank": {
            "type": "boolean",
            "optional": true,
            "description": "If matchAboutBlank is true, then the code is also injected in about:blank and about:srcdoc frames if your extension has access to its parent document. Code cannot be inserted in top-level about:-frames. By default it is <code>false</code>."
          },
          "frameId": {
            "type": "integer",
            "minimum": 0,
            "optional": true,
            "description": "The ID of the frame to inject the script into. This may not be used in combination with <code>allFrames</code>."
          },
          "runAt": {
            "$ref": "RunAt",
            "optional": true,
            "default": "document_idle",
            "description": "The soonest that the JavaScript or CSS will be injected into the tab. Defaults to \"document_idle\"."
          },
          "cssOrigin": {
            "$ref": "CSSOrigin",
            "optional": true,
            "description": "The css origin of the stylesheet to inject. Defaults to \"author\"."
          }
        }
      },
      {
        "id": "Date",
        "choices": [
          {
            "type": "string",
            "format": "date"
          },
          {
            "type": "integer",
            "minimum": 0
          },
          {
            "type": "object",
            "isInstanceOf": "Date",
            "additionalProperties": { "type": "any" }
          }
        ]
      },
      {
        "id": "ExtensionFileOrCode",
        "choices": [
          {
            "type": "object",
            "properties": {
              "file": {
                "$ref": "manifest.ExtensionURL"
              }
            }
          },
          {
            "type": "object",
            "properties": {
              "code": {
                "type": "string"
              }
            }
          }
        ]
      },
      {
        "id": "PlainJSONValue",
        "description": "A plain JSON value",
        "choices": [
          { "type": "null" },
          { "type": "number" },
          { "type": "string" },
          { "type": "boolean" },
          { "type": "array", "items": { "$ref": "PlainJSONValue" } },
          {
            "type": "object",
            "additionalProperties": { "$ref": "PlainJSONValue" }
          }
        ]
      }
    ]
  }
]
PK
!<�:L00<chrome/toolkit/content/extensions/schemas/geckoProfiler.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "PermissionNoPrompt",
        "choices": [
          {
            "type": "string",
            "enum": ["geckoProfiler"]
          }
        ]
      }
    ]
  },
  {
    "namespace": "geckoProfiler",
    "description": "Exposes the browser's profiler.",

    "permissions": ["geckoProfiler"],
    "types": [
      {
        "id": "ProfilerFeature",
        "type": "string",
        "enum": [
          "java",
          "js",
          "mainthreadio",
          "fileio",
          "fileioall",
          "nomarkerstacks",
          "screenshots",
          "seqstyle",
          "stackwalk",
          "jsallocations",
          "nostacksampling",
          "nativeallocations",
          "ipcmessages",
          "audiocallbacktracing",
          "cpu",
          "notimerresolutionchange",
          "cpuallthreads",
          "samplingallthreads",
          "markersallthreads",
          "unregisteredthreads",
          "processcpu",
          "power",
          "responsiveness",
          "cpufreq",
          "bandwidth",
          "memory"
        ]
      },
      {
        "id": "supports",
        "type": "string",
        "enum": ["windowLength"]
      }
    ],
    "functions": [
      {
        "name": "start",
        "type": "function",
        "description": "Starts the profiler with the specified settings.",
        "async": true,
        "parameters": [
          {
            "name": "settings",
            "type": "object",
            "properties": {
              "bufferSize": {
                "type": "integer",
                "minimum": 0,
                "description": "The maximum size in bytes of the buffer used to store profiling data. A larger value allows capturing a profile that covers a greater amount of time."
              },
              "windowLength": {
                "type": "number",
                "optional": true,
                "description": "The length of the window of time that's kept in the buffer. Any collected samples are discarded as soon as they are older than the number of seconds specified in this setting. Zero means no duration restriction."
              },
              "interval": {
                "type": "number",
                "description": "Interval in milliseconds between samples of profiling data. A smaller value will increase the detail of the profiles captured."
              },
              "features": {
                "type": "array",
                "description": "A list of active features for the profiler.",
                "items": {
                  "$ref": "ProfilerFeature"
                }
              },
              "threads": {
                "type": "array",
                "description": "A list of thread names for which to capture profiles.",
                "optional": true,
                "items": {
                  "type": "string"
                }
              }
            }
          }
        ]
      },
      {
        "name": "stop",
        "type": "function",
        "description": "Stops the profiler and discards any captured profile data.",
        "async": true,
        "parameters": []
      },
      {
        "name": "pause",
        "type": "function",
        "description": "Pauses the profiler, keeping any profile data that is already written.",
        "async": true,
        "parameters": []
      },
      {
        "name": "resume",
        "type": "function",
        "description": "Resumes the profiler with the settings that were initially used to start it.",
        "async": true,
        "parameters": []
      },
      {
        "name": "dumpProfileToFile",
        "type": "function",
        "description": "Gathers the profile data from the current profiling session, and writes it to disk. The returned promise resolves to a path that locates the created file.",
        "async": true,
        "parameters": [
          {
            "type": "string",
            "name": "fileName",
            "description": "The name of the file inside the profile/profiler directory"
          }
        ]
      },
      {
        "name": "getProfile",
        "type": "function",
        "description": "Gathers the profile data from the current profiling session.",
        "async": true,
        "parameters": []
      },
      {
        "name": "getProfileAsArrayBuffer",
        "type": "function",
        "description": "Gathers the profile data from the current profiling session. The returned promise resolves to an array buffer that contains a JSON string.",
        "async": true,
        "parameters": []
      },
      {
        "name": "getProfileAsGzippedArrayBuffer",
        "type": "function",
        "description": "Gathers the profile data from the current profiling session. The returned promise resolves to an array buffer that contains a gzipped JSON string.",
        "async": true,
        "parameters": []
      },
      {
        "name": "getSymbols",
        "type": "function",
        "description": "Gets the debug symbols for a particular library.",
        "async": true,
        "parameters": [
          {
            "type": "string",
            "name": "debugName",
            "description": "The name of the library's debug file. For example, 'xul.pdb"
          },
          {
            "type": "string",
            "name": "breakpadId",
            "description": "The Breakpad ID of the library"
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onRunning",
        "type": "function",
        "description": "Fires when the profiler starts/stops running.",
        "parameters": [
          {
            "name": "isRunning",
            "type": "boolean",
            "description": "Whether the profiler is running or not. Pausing the profiler will not affect this value."
          }
        ]
      }
    ]
  }
]
PK
!<�ƃ��3chrome/toolkit/content/extensions/schemas/i18n.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "WebExtensionManifest",
        "properties": {
          "default_locale": {
            "type": "string",
            "optional": "true"
          },
          "l10n_resources": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "optional": true,
            "privileged": true
          }
        }
      }
    ]
  },
  {
    "namespace": "i18n",
    "allowedContexts": ["content", "devtools"],
    "defaultContexts": ["content", "devtools"],
    "description": "Use the <code>browser.i18n</code> infrastructure to implement internationalization across your whole app or extension.",
    "types": [
      {
        "id": "LanguageCode",
        "type": "string",
        "description": "An ISO language code such as <code>en</code> or <code>fr</code>. For a complete list of languages supported by this method, see <a href='http://src.chromium.org/viewvc/chrome/trunk/src/third_party/cld/languages/internal/languages.cc'>kLanguageInfoTable</a>. For an unknown language, <code>und</code> will be returned, which means that [percentage] of the text is unknown to CLD"
      }
    ],
    "functions": [
      {
        "name": "getAcceptLanguages",
        "type": "function",
        "description": "Gets the accept-languages of the browser. This is different from the locale used by the browser; to get the locale, use $(ref:i18n.getUILanguage).",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "languages",
                "type": "array",
                "items": { "$ref": "LanguageCode" },
                "description": "Array of LanguageCode"
              }
            ]
          }
        ]
      },
      {
        "name": "getMessage",
        "type": "function",
        "description": "Gets the localized string for the specified message. If the message is missing, this method returns an empty string (''). If the format of the <code>getMessage()</code> call is wrong &mdash; for example, <em>messageName</em> is not a string or the <em>substitutions</em> array has more than 9 elements &mdash; this method returns <code>undefined</code>.",
        "parameters": [
          {
            "type": "string",
            "name": "messageName",
            "description": "The name of the message, as specified in the <code>$(topic:i18n-messages)[messages.json]</code> file."
          },
          {
            "type": "any",
            "name": "substitutions",
            "optional": true,
            "description": "Substitution strings, if the message requires any."
          }
        ],
        "returns": {
          "type": "string",
          "description": "Message localized for current locale."
        }
      },
      {
        "name": "getUILanguage",
        "type": "function",
        "description": "Gets the browser UI language of the browser. This is different from $(ref:i18n.getAcceptLanguages) which returns the preferred user languages.",
        "parameters": [],
        "returns": {
          "type": "string",
          "description": "The browser UI language code such as en-US or fr-FR."
        }
      },
      {
        "name": "detectLanguage",
        "type": "function",
        "description": "Detects the language of the provided text using CLD.",
        "async": "callback",
        "parameters": [
          {
            "type": "string",
            "name": "text",
            "description": "User input string to be translated."
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "type": "object",
                "name": "result",
                "description": "LanguageDetectionResult object that holds detected langugae reliability and array of DetectedLanguage",
                "properties": {
                  "isReliable": {
                    "type": "boolean",
                    "description": "CLD detected language reliability"
                  },
                  "languages": {
                    "type": "array",
                    "description": "array of detectedLanguage",
                    "items": {
                      "type": "object",
                      "description": "DetectedLanguage object that holds detected ISO language code and its percentage in the input string",
                      "properties": {
                        "language": {
                          "$ref": "LanguageCode"
                        },
                        "percentage": {
                          "type": "integer",
                          "description": "The percentage of the detected language"
                        }
                      }
                    }
                  }
                }
              }
            ]
          }
        ]
      }
    ],
    "events": []
  }
]
PK
!<MTt��7chrome/toolkit/content/extensions/schemas/identity.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "PermissionNoPrompt",
        "choices": [
          {
            "type": "string",
            "enum": ["identity"]
          }
        ]
      }
    ]
  },
  {
    "namespace": "identity",
    "description": "Use the chrome.identity API to get OAuth2 access tokens. ",
    "permissions": ["identity"],
    "types": [
      {
        "id": "AccountInfo",
        "type": "object",
        "description": "An object encapsulating an OAuth account id.",
        "properties": {
          "id": {
            "type": "string",
            "description": "A unique identifier for the account. This ID will not change for the lifetime of the account. "
          }
        }
      }
    ],
    "functions": [
      {
        "name": "getAccounts",
        "type": "function",
        "unsupported": true,
        "description": "Retrieves a list of AccountInfo objects describing the accounts present on the profile.",
        "async": "callback",
        "parameters": [
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": "results",
                "type": "array",
                "items": {
                  "$ref": "AccountInfo"
                }
              }
            ]
          }
        ]
      },
      {
        "name": "getAuthToken",
        "type": "function",
        "unsupported": true,
        "description": "Gets an OAuth2 access token using the client ID and scopes specified in the oauth2 section of manifest.json.",
        "async": "callback",
        "parameters": [
          {
            "name": "details",
            "optional": true,
            "type": "object",
            "properties": {
              "interactive": {
                "optional": true,
                "type": "boolean"
              },
              "account": {
                "optional": true,
                "$ref": "AccountInfo"
              },
              "scopes": {
                "optional": true,
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          {
            "name": "callback",
            "optional": true,
            "type": "function",
            "parameters": [
              {
                "name": "results",
                "type": "array",
                "items": {
                  "$ref": "AccountInfo"
                }
              }
            ]
          }
        ]
      },
      {
        "name": "getProfileUserInfo",
        "type": "function",
        "unsupported": true,
        "description": "Retrieves email address and obfuscated gaia id of the user signed into a profile.",
        "async": "callback",
        "parameters": [
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": "userinfo",
                "type": "object",
                "properties": {
                  "email": { "type": "string" },
                  "id": { "type": "string" }
                }
              }
            ]
          }
        ]
      },
      {
        "name": "removeCachedAuthToken",
        "type": "function",
        "unsupported": true,
        "description": "Removes an OAuth2 access token from the Identity API's token cache.",
        "async": "callback",
        "parameters": [
          {
            "name": "details",
            "type": "object",
            "properties": {
              "token": { "type": "string" }
            }
          },
          {
            "name": "callback",
            "optional": true,
            "type": "function",
            "parameters": [
              {
                "name": "userinfo",
                "type": "object",
                "properties": {
                  "email": { "type": "string" },
                  "id": { "type": "string" }
                }
              }
            ]
          }
        ]
      },
      {
        "name": "launchWebAuthFlow",
        "type": "function",
        "description": "Starts an auth flow at the specified URL.",
        "async": "callback",
        "parameters": [
          {
            "name": "details",
            "type": "object",
            "properties": {
              "url": { "$ref": "manifest.HttpURL" },
              "interactive": { "type": "boolean", "optional": true }
            }
          },
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": " responseUrl",
                "type": "string",
                "optional": true
              }
            ]
          }
        ]
      },
      {
        "name": "getRedirectURL",
        "type": "function",
        "description": "Generates a redirect URL to be used in |launchWebAuthFlow|.",
        "parameters": [
          {
            "name": "path",
            "type": "string",
            "default": "",
            "optional": true,
            "description": "The path appended to the end of the generated URL. "
          }
        ],
        "returns": {
          "type": "string"
        }
      }
    ],
    "events": [
      {
        "name": "onSignInChanged",
        "unsupported": true,
        "type": "function",
        "description": "Fired when signin state changes for an account on the user's profile.",
        "parameters": [
          {
            "name": "account",
            "$ref": "AccountInfo"
          },
          {
            "name": "signedIn",
            "type": "boolean"
          }
        ]
      }
    ]
  }
]
PK
!<���JJ3chrome/toolkit/content/extensions/schemas/idle.json[
  {
    "namespace": "idle",
    "description": "Use the <code>browser.idle</code> API to detect when the machine's idle state changes.",
    "permissions": ["idle"],
    "types": [
      {
        "id": "IdleState",
        "type": "string",
        "enum": ["active", "idle"]
      }
    ],
    "functions": [
      {
        "name": "queryState",
        "type": "function",
        "description": "Returns \"idle\" if the user has not generated any input for a specified number of seconds, or \"active\" otherwise.",
        "async": "callback",
        "parameters": [
          {
            "name": "detectionIntervalInSeconds",
            "type": "integer",
            "minimum": 15,
            "description": "The system is considered idle if detectionIntervalInSeconds seconds have elapsed since the last user input detected."
          },
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": "newState",
                "$ref": "IdleState"
              }
            ]
          }
        ]
      },
      {
        "name": "setDetectionInterval",
        "type": "function",
        "description": "Sets the interval, in seconds, used to determine when the system is in an idle state for onStateChanged events. The default interval is 60 seconds.",
        "parameters": [
          {
            "name": "intervalInSeconds",
            "type": "integer",
            "minimum": 15,
            "description": "Threshold, in seconds, used to determine when the system is in an idle state."
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onStateChanged",
        "type": "function",
        "description": "Fired when the system changes to an active or idle state. The event fires with \"idle\" if the the user has not generated any input for a specified number of seconds, and \"active\" when the user generates input on an idle system.",
        "parameters": [
          {
            "name": "newState",
            "$ref": "IdleState"
          }
        ]
      }
    ]
  }
]
PK
!<�#�ˬ-�-9chrome/toolkit/content/extensions/schemas/management.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "OptionalPermission",
        "choices": [
          {
            "type": "string",
            "enum": ["management"]
          }
        ]
      }
    ]
  },
  {
    "namespace": "management",
    "description": "The <code>browser.management</code> API provides ways to manage the list of extensions that are installed and running.",
    "types": [
      {
        "id": "IconInfo",
        "description": "Information about an icon belonging to an extension.",
        "type": "object",
        "properties": {
          "size": {
            "type": "integer",
            "description": "A number representing the width and height of the icon. Likely values include (but are not limited to) 128, 48, 24, and 16."
          },
          "url": {
            "type": "string",
            "description": "The URL for this icon image. To display a grayscale version of the icon (to indicate that an extension is disabled, for example), append <code>?grayscale=true</code> to the URL."
          }
        }
      },
      {
        "id": "ExtensionDisabledReason",
        "description": "A reason the item is disabled.",
        "type": "string",
        "enum": ["unknown", "permissions_increase"]
      },
      {
        "id": "ExtensionType",
        "description": "The type of this extension, 'extension' or 'theme'.",
        "type": "string",
        "enum": ["extension", "theme"]
      },
      {
        "id": "ExtensionInstallType",
        "description": "How the extension was installed. One of<br><var>development</var>: The extension was loaded unpacked in developer mode,<br><var>normal</var>: The extension was installed normally via an .xpi file,<br><var>sideload</var>: The extension was installed by other software on the machine,<br><var>admin</var>: The extension was installed by policy,<br><var>other</var>: The extension was installed by other means.",
        "type": "string",
        "enum": ["development", "normal", "sideload", "admin", "other"]
      },
      {
        "id": "ExtensionInfo",
        "description": "Information about an installed extension.",
        "type": "object",
        "properties": {
          "id": {
            "description": "The extension's unique identifier.",
            "type": "string"
          },
          "name": {
            "description": "The name of this extension.",
            "type": "string"
          },
          "shortName": {
            "description": "A short version of the name of this extension.",
            "type": "string",
            "optional": true
          },
          "description": {
            "description": "The description of this extension.",
            "type": "string"
          },
          "version": {
            "description": "The <a href='manifest/version'>version</a> of this extension.",
            "type": "string"
          },
          "versionName": {
            "description": "The <a href='manifest/version#version_name'>version name</a> of this extension if the manifest specified one.",
            "type": "string",
            "optional": true
          },
          "mayDisable": {
            "description": "Whether this extension can be disabled or uninstalled by the user.",
            "type": "boolean"
          },
          "enabled": {
            "description": "Whether it is currently enabled or disabled.",
            "type": "boolean"
          },
          "disabledReason": {
            "description": "A reason the item is disabled.",
            "$ref": "ExtensionDisabledReason",
            "optional": true
          },
          "type": {
            "description": "The type of this extension, 'extension' or 'theme'.",
            "$ref": "ExtensionType"
          },
          "homepageUrl": {
            "description": "The URL of the homepage of this extension.",
            "type": "string",
            "optional": true
          },
          "updateUrl": {
            "description": "The update URL of this extension.",
            "type": "string",
            "optional": true
          },
          "optionsUrl": {
            "description": "The url for the item's options page, if it has one.",
            "type": "string"
          },
          "icons": {
            "description": "A list of icon information. Note that this just reflects what was declared in the manifest, and the actual image at that url may be larger or smaller than what was declared, so you might consider using explicit width and height attributes on img tags referencing these images. See the <a href='manifest/icons'>manifest documentation on icons</a> for more details.",
            "type": "array",
            "optional": true,
            "items": {
              "$ref": "IconInfo"
            }
          },
          "permissions": {
            "description": "Returns a list of API based permissions.",
            "type": "array",
            "optional": true,
            "items": {
              "type": "string"
            }
          },
          "hostPermissions": {
            "description": "Returns a list of host based permissions.",
            "type": "array",
            "optional": true,
            "items": {
              "type": "string"
            }
          },
          "installType": {
            "description": "How the extension was installed.",
            "$ref": "ExtensionInstallType"
          }
        }
      }
    ],
    "functions": [
      {
        "name": "getAll",
        "type": "function",
        "permissions": ["management"],
        "description": "Returns a list of information about installed extensions.",
        "async": "callback",
        "parameters": [
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": [
              {
                "type": "array",
                "name": "result",
                "items": {
                  "$ref": "ExtensionInfo"
                }
              }
            ]
          }
        ]
      },
      {
        "name": "get",
        "type": "function",
        "permissions": ["management"],
        "description": "Returns information about the installed extension that has the given ID.",
        "async": "callback",
        "parameters": [
          {
            "name": "id",
            "$ref": "manifest.ExtensionID",
            "description": "The ID from an item of $(ref:management.ExtensionInfo)."
          },
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": [
              {
                "name": "result",
                "$ref": "ExtensionInfo"
              }
            ]
          }
        ]
      },
      {
        "name": "install",
        "type": "function",
        "requireUserInput": true,
        "permissions": ["management"],
        "description": "Installs and enables a theme extension from the given url.",
        "async": "callback",
        "parameters": [
          {
            "name": "options",
            "type": "object",
            "properties": {
              "url": {
                "$ref": "manifest.HttpURL",
                "description": "URL pointing to the XPI file on addons.mozilla.org or similar."
              },
              "hash": {
                "type": "string",
                "optional": true,
                "pattern": "^(sha256|sha512):[0-9a-fA-F]{64,128}$",
                "description": "A hash of the XPI file, using sha256 or stronger."
              }
            }
          },
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": [
              {
                "name": "result",
                "type": "object",
                "properties": {
                  "id": {
                    "$ref": "manifest.ExtensionID"
                  }
                }
              }
            ]
          }
        ]
      },
      {
        "name": "getSelf",
        "type": "function",
        "description": "Returns information about the calling extension. Note: This function can be used without requesting the 'management' permission in the manifest.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "parameters": [
              {
                "name": "result",
                "$ref": "ExtensionInfo"
              }
            ]
          }
        ]
      },
      {
        "name": "uninstallSelf",
        "type": "function",
        "description": "Uninstalls the calling extension. Note: This function can be used without requesting the 'management' permission in the manifest.",
        "async": "callback",
        "parameters": [
          {
            "type": "object",
            "name": "options",
            "optional": true,
            "properties": {
              "showConfirmDialog": {
                "type": "boolean",
                "optional": true,
                "description": "Whether or not a confirm-uninstall dialog should prompt the user. Defaults to false."
              },
              "dialogMessage": {
                "type": "string",
                "optional": true,
                "description": "The message to display to a user when being asked to confirm removal of the extension."
              }
            }
          },
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "setEnabled",
        "type": "function",
        "permissions": ["management"],
        "description": "Enables or disables the given add-on.",
        "async": "callback",
        "parameters": [
          {
            "name": "id",
            "type": "string",
            "description": "ID of the add-on to enable/disable."
          },
          {
            "name": "enabled",
            "type": "boolean",
            "description": "Whether to enable or disable the add-on."
          },
          {
            "name": "callback",
            "type": "function",
            "optional": true,
            "parameters": []
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onDisabled",
        "type": "function",
        "permissions": ["management"],
        "description": "Fired when an addon has been disabled.",
        "parameters": [
          {
            "name": "info",
            "$ref": "ExtensionInfo"
          }
        ]
      },
      {
        "name": "onEnabled",
        "type": "function",
        "permissions": ["management"],
        "description": "Fired when an addon has been enabled.",
        "parameters": [
          {
            "name": "info",
            "$ref": "ExtensionInfo"
          }
        ]
      },
      {
        "name": "onInstalled",
        "type": "function",
        "permissions": ["management"],
        "description": "Fired when an addon has been installed.",
        "parameters": [
          {
            "name": "info",
            "$ref": "ExtensionInfo"
          }
        ]
      },
      {
        "name": "onUninstalled",
        "type": "function",
        "permissions": ["management"],
        "description": "Fired when an addon has been uninstalled.",
        "parameters": [
          {
            "name": "info",
            "$ref": "ExtensionInfo"
          }
        ]
      }
    ]
  }
]
PK
!<�鑦�Y�Y7chrome/toolkit/content/extensions/schemas/manifest.json[
  {
    "namespace": "manifest",
    "permissions": [],
    "types": [
      {
        "id": "ManifestBase",
        "type": "object",
        "description": "Common properties for all manifest.json files",
        "properties": {
          "manifest_version": {
            "type": "integer",
            "minimum": 2,
            "maximum": 3,
            "postprocess": "manifestVersionCheck"
          },

          "applications": {
            "$ref": "DeprecatedApplications",
            "description": "The applications property is deprecated, please use 'browser_specific_settings'",
            "optional": true,
            "max_manifest_version": 2
          },

          "browser_specific_settings": {
            "$ref": "BrowserSpecificSettings",
            "optional": true
          },

          "name": {
            "type": "string",
            "optional": false,
            "preprocess": "localize"
          },

          "short_name": {
            "type": "string",
            "optional": true,
            "preprocess": "localize"
          },

          "description": {
            "type": "string",
            "optional": true,
            "preprocess": "localize"
          },

          "author": {
            "type": "string",
            "optional": true,
            "preprocess": "localize",
            "onError": "warn"
          },

          "version": {
            "type": "string",
            "optional": false,
            "format": "versionString"
          },

          "homepage_url": {
            "type": "string",
            "format": "url",
            "optional": true,
            "preprocess": "localize"
          },

          "install_origins": {
            "type": "array",
            "optional": true,
            "items": {
              "type": "string",
              "format": "origin"
            }
          },

          "developer": {
            "type": "object",
            "optional": true,
            "properties": {
              "name": {
                "type": "string",
                "optional": true,
                "preprocess": "localize"
              },
              "url": {
                "type": "string",
                "format": "url",
                "optional": true,
                "preprocess": "localize",
                "onError": "warn"
              }
            }
          }
        }
      },
      {
        "id": "WebExtensionManifest",
        "type": "object",
        "description": "Represents a WebExtension manifest.json file",

        "$import": "ManifestBase",
        "properties": {
          "minimum_chrome_version": {
            "type": "string",
            "optional": true
          },

          "minimum_opera_version": {
            "type": "string",
            "optional": true
          },

          "icons": {
            "type": "object",
            "optional": true,
            "patternProperties": {
              "^[1-9]\\d*$": { "$ref": "ExtensionFileUrl" }
            }
          },

          "incognito": {
            "type": "string",
            "description": "The 'split' value is not supported.",
            "enum": ["not_allowed", "spanning", "split"],
            "postprocess": "incognitoSplitUnsupportedAndFallback",
            "default": "spanning",
            "optional": true
          },

          "background": {
            "choices": [
              {
                "type": "object",
                "properties": {
                  "page": { "$ref": "ExtensionURL" },
                  "persistent": {
                    "optional": true,
                    "type": "boolean",
                    "max_manifest_version": 2,
                    "default": true
                  }
                },
                "additionalProperties": { "$ref": "UnrecognizedProperty" }
              },
              {
                "type": "object",
                "properties": {
                  "scripts": {
                    "type": "array",
                    "items": { "$ref": "ExtensionURL" }
                  },
                  "type": {
                    "optional": true,
                    "type": "string",
                    "enum": ["module", "classic"]
                  },
                  "persistent": {
                    "optional": true,
                    "type": "boolean",
                    "max_manifest_version": 2,
                    "default": true
                  }
                },
                "additionalProperties": { "$ref": "UnrecognizedProperty" }
              },
              {
                "type": "object",
                "properties": {
                  "service_worker": { "$ref": "ExtensionURL" }
                },
                "postprocess": "requireBackgroundServiceWorkerEnabled"
              }
            ],
            "optional": true
          },

          "options_page": {
            "$ref": "ExtensionURL",
            "optional": true,
            "description": "Alias property for options_ui.page, ignored when options_ui.page is set. When using this property the options page is always opened in a new tab."
          },

          "options_ui": {
            "type": "object",
            "optional": true,
            "properties": {
              "page": { "$ref": "ExtensionURL" },
              "browser_style": {
                "type": "boolean",
                "optional": true,
                "description": "Defaults to true in Manifest V2; Deprecated in Manifest V3."
              },
              "chrome_style": {
                "type": "boolean",
                "optional": true,
                "max_manifest_version": 2,
                "description": "chrome_style is ignored in Firefox. Its replacement (browser_style) has been deprecated."
              },
              "open_in_tab": {
                "type": "boolean",
                "optional": true
              }
            },
            "additionalProperties": { "$ref": "UnrecognizedProperty" }
          },

          "content_scripts": {
            "type": "array",
            "optional": true,
            "items": { "$ref": "ContentScript" }
          },

          "content_security_policy": {
            "optional": true,
            "onError": "warn",
            "choices": [
              {
                "max_manifest_version": 2,
                "type": "string",
                "format": "contentSecurityPolicy"
              },
              {
                "min_manifest_version": 3,
                "type": "object",
                "additionalProperties": {
                  "$ref": "UnrecognizedProperty"
                },
                "properties": {
                  "extension_pages": {
                    "type": "string",
                    "optional": true,
                    "format": "contentSecurityPolicy",
                    "description": "The Content Security Policy used for extension pages."
                  }
                }
              }
            ]
          },

          "permissions": {
            "default": [],
            "optional": true,
            "choices": [
              {
                "max_manifest_version": 2,
                "type": "array",
                "items": {
                  "$ref": "PermissionOrOrigin",
                  "onError": "warn"
                }
              },
              {
                "min_manifest_version": 3,
                "type": "array",
                "items": {
                  "$ref": "Permission",
                  "onError": "warn"
                }
              }
            ]
          },

          "granted_host_permissions": {
            "type": "boolean",
            "optional": true,
            "default": false
          },

          "host_permissions": {
            "min_manifest_version": 3,
            "type": "array",
            "items": {
              "$ref": "MatchPattern",
              "onError": "warn"
            },
            "optional": true,
            "default": []
          },

          "optional_host_permissions": {
            "min_manifest_version": 3,
            "type": "array",
            "items": {
              "$ref": "MatchPattern",
              "onError": "warn"
            },
            "optional": true,
            "default": []
          },

          "optional_permissions": {
            "type": "array",
            "items": {
              "$ref": "OptionalPermissionOrOrigin",
              "onError": "warn"
            },
            "optional": true,
            "default": []
          },

          "web_accessible_resources": {
            "optional": true,
            "choices": [
              {
                "max_manifest_version": 2,
                "type": "array",
                "items": { "type": "string" }
              },
              {
                "min_manifest_version": 3,
                "type": "array",
                "postprocess": "webAccessibleMatching",
                "items": {
                  "type": "object",
                  "properties": {
                    "resources": {
                      "type": "array",
                      "items": { "type": "string" }
                    },
                    "matches": {
                      "optional": true,
                      "type": "array",
                      "items": { "$ref": "MatchPattern" }
                    },
                    "extension_ids": {
                      "optional": true,
                      "type": "array",
                      "items": {
                        "choices": [
                          { "$ref": "ExtensionID" },
                          { "type": "string", "enum": ["*"] }
                        ]
                      }
                    }
                  },
                  "additionalProperties": { "$ref": "UnrecognizedProperty" }
                }
              }
            ]
          },

          "hidden": {
            "type": "boolean",
            "optional": true,
            "default": false
          }
        },

        "additionalProperties": { "$ref": "UnrecognizedProperty" }
      },
      {
        "id": "WebExtensionLangpackManifest",
        "type": "object",
        "description": "Represents a WebExtension language pack manifest.json file",

        "$import": "ManifestBase",
        "properties": {
          "langpack_id": {
            "type": "string",
            "pattern": "^[a-zA-Z][a-zA-Z-]+$"
          },

          "languages": {
            "type": "object",
            "patternProperties": {
              "^[a-z]{2}[a-zA-Z-]*$": {
                "type": "object",
                "properties": {
                  "chrome_resources": {
                    "type": "object",
                    "patternProperties": {
                      "^[a-zA-Z-.]+$": {
                        "choices": [
                          {
                            "$ref": "ExtensionURL"
                          },
                          {
                            "type": "object",
                            "patternProperties": {
                              "^[a-z]+$": {
                                "$ref": "ExtensionURL"
                              }
                            }
                          }
                        ]
                      }
                    }
                  },
                  "version": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "sources": {
            "type": "object",
            "optional": true,
            "patternProperties": {
              "^[a-z]+$": {
                "type": "object",
                "properties": {
                  "base_path": {
                    "$ref": "ExtensionURL"
                  },
                  "paths": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "strictRelativeUrl"
                    },
                    "optional": true
                  }
                }
              }
            }
          }
        }
      },
      {
        "id": "WebExtensionDictionaryManifest",
        "type": "object",
        "description": "Represents a WebExtension dictionary manifest.json file",

        "$import": "ManifestBase",
        "properties": {
          "dictionaries": {
            "type": "object",
            "patternProperties": {
              "^[a-z]{2}[a-zA-Z-]*$": {
                "type": "string",
                "format": "strictRelativeUrl",
                "pattern": "\\.dic$"
              }
            }
          }
        }
      },
      {
        "id": "ThemeIcons",
        "type": "object",
        "properties": {
          "light": {
            "$ref": "ExtensionURL",
            "description": "A light icon to use for dark themes"
          },
          "dark": {
            "$ref": "ExtensionURL",
            "description": "The dark icon to use for light themes"
          },
          "size": {
            "type": "integer",
            "description": "The size of the icons"
          }
        },
        "additionalProperties": { "$ref": "UnrecognizedProperty" }
      },
      {
        "id": "OptionalPermissionNoPrompt",
        "choices": [
          {
            "type": "string",
            "enum": ["idle"]
          }
        ]
      },
      {
        "id": "OptionalPermission",
        "choices": [
          { "$ref": "OptionalPermissionNoPrompt" },
          {
            "type": "string",
            "enum": [
              "clipboardRead",
              "clipboardWrite",
              "geolocation",
              "notifications"
            ]
          }
        ]
      },
      {
        "id": "OptionalPermissionOrOrigin",
        "choices": [
          { "$ref": "OptionalPermission" },
          { "$ref": "MatchPattern" }
        ]
      },
      {
        "id": "PermissionPrivileged",
        "choices": [
          {
            "type": "string",
            "enum": ["mozillaAddons"]
          }
        ]
      },
      {
        "id": "PermissionNoPrompt",
        "choices": [
          { "$ref": "OptionalPermissionNoPrompt" },
          { "$ref": "PermissionPrivileged" },
          {
            "type": "string",
            "enum": ["alarms", "storage", "unlimitedStorage"]
          }
        ]
      },
      {
        "id": "Permission",
        "choices": [
          { "$ref": "PermissionNoPrompt" },
          { "$ref": "OptionalPermission" }
        ]
      },
      {
        "id": "PermissionOrOrigin",
        "choices": [{ "$ref": "Permission" }, { "$ref": "MatchPattern" }]
      },
      {
        "id": "HttpURL",
        "type": "string",
        "format": "url",
        "pattern": "^https?://.*$"
      },
      {
        "id": "ExtensionURL",
        "type": "string",
        "format": "strictRelativeUrl"
      },
      {
        "id": "ExtensionFileUrl",
        "type": "string",
        "format": "strictRelativeUrl",
        "pattern": "\\S",
        "preprocess": "localize"
      },
      {
        "id": "ImageDataOrExtensionURL",
        "type": "string",
        "format": "imageDataOrStrictRelativeUrl"
      },
      {
        "id": "ExtensionID",
        "choices": [
          {
            "type": "string",
            "pattern": "(?i)^\\{[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\\}$"
          },
          {
            "type": "string",
            "pattern": "(?i)^[a-z0-9-._]*@[a-z0-9-._]+$"
          }
        ]
      },
      {
        "id": "FirefoxSpecificProperties",
        "type": "object",
        "properties": {
          "id": {
            "$ref": "ExtensionID",
            "optional": true
          },

          "update_url": {
            "type": "string",
            "format": "url",
            "optional": true
          },

          "strict_min_version": {
            "type": "string",
            "optional": true
          },

          "strict_max_version": {
            "type": "string",
            "optional": true
          },

          "admin_install_only": {
            "type": "boolean",
            "optional": true
          }
        },
        "additionalProperties": { "$ref": "UnrecognizedProperty" }
      },
      {
        "id": "GeckoAndroidSpecificProperties",
        "type": "object",
        "properties": {
          "strict_min_version": {
            "type": "string",
            "optional": true
          },
          "strict_max_version": {
            "type": "string",
            "optional": true
          }
        },
        "additionalProperties": { "$ref": "UnrecognizedProperty" }
      },
      {
        "id": "DeprecatedApplications",
        "type": "object",
        "properties": {
          "gecko": {
            "$ref": "FirefoxSpecificProperties",
            "optional": true
          },
          "gecko_android": {
            "$ref": "GeckoAndroidSpecificProperties",
            "optional": true,
            "unsupported": true
          }
        },
        "additionalProperties": { "type": "any" }
      },
      {
        "id": "BrowserSpecificSettings",
        "type": "object",
        "properties": {
          "gecko": {
            "$ref": "FirefoxSpecificProperties",
            "optional": true
          },
          "gecko_android": {
            "$ref": "GeckoAndroidSpecificProperties",
            "optional": true
          }
        },
        "additionalProperties": { "type": "any" }
      },
      {
        "id": "MatchPattern",
        "choices": [
          {
            "type": "string",
            "enum": ["<all_urls>"]
          },
          {
            "$ref": "MatchPatternRestricted"
          },
          {
            "$ref": "MatchPatternUnestricted"
          }
        ]
      },
      {
        "id": "MatchPatternRestricted",
        "description": "Same as MatchPattern above, but excludes <all_urls>",
        "choices": [
          {
            "type": "string",
            "pattern": "^(https?|wss?|file|ftp|\\*)://(\\*|\\*\\.[^*/]+|[^*/]+)/.*$"
          },
          {
            "type": "string",
            "pattern": "^file:///.*$"
          }
        ]
      },
      {
        "id": "MatchPatternUnestricted",
        "description": "Mostly unrestricted match patterns for privileged add-ons. This should technically be rejected for unprivileged add-ons, but, reasons. The MatchPattern class will still refuse privileged schemes for those extensions.",
        "choices": [
          {
            "type": "string",
            "pattern": "^resource://(\\*|\\*\\.[^*/]+|[^*/]+)/.*$|^about:"
          }
        ]
      },
      {
        "id": "ContentScript",
        "type": "object",
        "description": "Details of the script or CSS to inject. Either the code or the file property must be set, but both may not be set at the same time. Based on InjectDetails, but using underscore rather than camel case naming conventions.",
        "additionalProperties": { "$ref": "UnrecognizedProperty" },
        "properties": {
          "matches": {
            "type": "array",
            "optional": false,
            "minItems": 1,
            "items": { "$ref": "MatchPattern" }
          },
          "exclude_matches": {
            "type": "array",
            "optional": true,
            "minItems": 1,
            "items": { "$ref": "MatchPattern" }
          },
          "include_globs": {
            "type": "array",
            "optional": true,
            "items": { "type": "string" }
          },
          "exclude_globs": {
            "type": "array",
            "optional": true,
            "items": { "type": "string" }
          },
          "css": {
            "type": "array",
            "optional": true,
            "description": "The list of CSS files to inject",
            "items": { "$ref": "ExtensionURL" }
          },
          "js": {
            "type": "array",
            "optional": true,
            "description": "The list of JS files to inject",
            "items": { "$ref": "ExtensionURL" }
          },
          "all_frames": {
            "type": "boolean",
            "optional": true,
            "description": "If allFrames is <code>true</code>, implies that the JavaScript or CSS should be injected into all frames of current page. By default, it's <code>false</code> and is only injected into the top frame."
          },
          "match_about_blank": {
            "type": "boolean",
            "optional": true,
            "description": "If match_about_blank is true, then the code is also injected in about:blank and about:srcdoc frames if your extension has access to its parent document. Ignored if match_origin_as_fallback is specified. By default it is <code>false</code>."
          },
          "match_origin_as_fallback": {
            "type": "boolean",
            "optional": true,
            "description": "If match_origin_as_fallback is true, then the code is also injected in about:, data:, blob: when their origin matches the pattern in 'matches', even if the actual document origin is opaque (due to the use of CSP sandbox or iframe sandbox). Match patterns in 'matches' must specify a wildcard path glob. By default it is <code>false</code>."
          },
          "run_at": {
            "$ref": "extensionTypes.RunAt",
            "optional": true,
            "default": "document_idle",
            "description": "The soonest that the JavaScript or CSS will be injected into the tab. Defaults to \"document_idle\"."
          },
          "world": {
            "$ref": "extensionTypes.ExecutionWorld",
            "optional": true,
            "default": "ISOLATED",
            "description": "The JavaScript world for a script to execute within. Defaults to \"ISOLATED\"."
          }
        }
      },
      {
        "id": "IconPath",
        "choices": [
          {
            "type": "object",
            "patternProperties": {
              "^[1-9]\\d*$": { "$ref": "ExtensionFileUrl" }
            },
            "additionalProperties": false
          },
          { "$ref": "ExtensionFileUrl" }
        ]
      },
      {
        "id": "IconImageData",
        "choices": [
          {
            "type": "object",
            "patternProperties": {
              "^[1-9]\\d*$": { "$ref": "ImageData" }
            },
            "additionalProperties": false
          },
          { "$ref": "ImageData" }
        ]
      },
      {
        "id": "ImageData",
        "type": "object",
        "isInstanceOf": "ImageData",
        "postprocess": "convertImageDataToURL"
      },
      {
        "id": "UnrecognizedProperty",
        "type": "any",
        "deprecated": "An unexpected property was found in the WebExtension manifest."
      }
    ]
  }
]
PK
!<];����>chrome/toolkit/content/extensions/schemas/native_manifest.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "id": "NativeManifest",
        "description": "Represents a native manifest file",
        "choices": [
          {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "pattern": "^\\w+(\\.\\w+)*$"
              },
              "description": {
                "type": "string"
              },
              "path": {
                "type": "string"
              },
              "type": {
                "type": "string",
                "enum": ["pkcs11", "stdio"]
              },
              "allowed_extensions": {
                "type": "array",
                "minItems": 1,
                "items": {
                  "$ref": "manifest.ExtensionID"
                }
              }
            }
          },
          {
            "type": "object",
            "properties": {
              "name": {
                "$ref": "manifest.ExtensionID"
              },
              "description": {
                "type": "string"
              },
              "data": {
                "type": "object",
                "additionalProperties": {
                  "type": "any"
                }
              },
              "type": {
                "type": "string",
                "enum": ["storage"]
              }
            }
          }
        ]
      }
    ]
  }
]
PK
!<���=chrome/toolkit/content/extensions/schemas/network_status.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "PermissionPrivileged",
        "choices": [
          {
            "type": "string",
            "enum": ["networkStatus"]
          }
        ]
      }
    ]
  },
  {
    "namespace": "networkStatus",
    "description": "This API provides the ability to determine the status of and detect changes in the network connection. This API can only be used in privileged extensions.",
    "permissions": ["networkStatus"],
    "types": [
      {
        "id": "NetworkLinkInfo",
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": ["unknown", "up", "down"],
            "description": "Status of the network link, if \"unknown\" then link is usually assumed to be \"up\""
          },
          "type": {
            "type": "string",
            "enum": ["unknown", "ethernet", "usb", "wifi", "wimax", "mobile"],
            "description": "If known, the type of network connection that is avialable."
          },
          "id": {
            "type": "string",
            "optional": true,
            "description": "If known, the network id or name."
          }
        }
      }
    ],
    "functions": [
      {
        "name": "getLinkInfo",
        "type": "function",
        "description": "Returns the $(ref:NetworkLinkInfo} of the current network connection.",
        "async": true,
        "parameters": []
      }
    ],
    "events": [
      {
        "name": "onConnectionChanged",
        "type": "function",
        "description": "Fired when the network connection state changes.",
        "parameters": [
          {
            "name": "details",
            "$ref": "NetworkLinkInfo"
          }
        ]
      }
    ]
  }
]
PK
!<�s���2�2<chrome/toolkit/content/extensions/schemas/notifications.json[
  {
    "namespace": "notifications",
    "permissions": ["notifications"],
    "types": [
      {
        "id": "TemplateType",
        "type": "string",
        "enum": ["basic", "image", "list", "progress"]
      },
      {
        "id": "PermissionLevel",
        "type": "string",
        "enum": ["granted", "denied"]
      },
      {
        "id": "NotificationItem",
        "type": "object",
        "properties": {
          "title": {
            "description": "Title of one item of a list notification.",
            "type": "string"
          },
          "message": {
            "description": "Additional details about this item.",
            "type": "string"
          }
        }
      },
      {
        "id": "CreateNotificationOptions",
        "type": "object",
        "properties": {
          "type": {
            "description": "Which type of notification to display.",
            "$ref": "TemplateType"
          },
          "iconUrl": {
            "optional": true,
            "description": "A URL to the sender's avatar, app icon, or a thumbnail for image notifications.",
            "type": "string"
          },
          "appIconMaskUrl": {
            "optional": true,
            "description": "A URL to the app icon mask.",
            "type": "string"
          },
          "title": {
            "description": "Title of the notification (e.g. sender name for email).",
            "type": "string"
          },
          "message": {
            "description": "Main notification content.",
            "type": "string"
          },
          "contextMessage": {
            "optional": true,
            "description": "Alternate notification content with a lower-weight font.",
            "type": "string"
          },
          "priority": {
            "optional": true,
            "description": "Priority ranges from -2 to 2. -2 is lowest priority. 2 is highest. Zero is default.",
            "type": "integer",
            "minimum": -2,
            "maximum": 2
          },
          "eventTime": {
            "optional": true,
            "description": "A timestamp associated with the notification, in milliseconds past the epoch.",
            "type": "number"
          },
          "buttons": {
            "unsupported": true,
            "optional": true,
            "description": "Text and icons for up to two notification action buttons.",
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "title": {
                  "type": "string"
                },
                "iconUrl": {
                  "optional": true,
                  "type": "string"
                }
              }
            }
          },
          "imageUrl": {
            "optional": true,
            "description": "A URL to the image thumbnail for image-type notifications.",
            "type": "string"
          },
          "items": {
            "optional": true,
            "description": "Items for multi-item notifications.",
            "type": "array",
            "items": { "$ref": "NotificationItem" }
          },
          "progress": {
            "optional": true,
            "description": "Current progress ranges from 0 to 100.",
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "isClickable": {
            "optional": true,
            "description": "Whether to show UI indicating that the app will visibly respond to clicks on the body of a notification.",
            "type": "boolean"
          }
        }
      },
      {
        "id": "UpdateNotificationOptions",
        "type": "object",
        "properties": {
          "type": {
            "optional": true,
            "description": "Which type of notification to display.",
            "$ref": "TemplateType"
          },
          "iconUrl": {
            "optional": true,
            "description": "A URL to the sender's avatar, app icon, or a thumbnail for image notifications.",
            "type": "string"
          },
          "appIconMaskUrl": {
            "optional": true,
            "description": "A URL to the app icon mask.",
            "type": "string"
          },
          "title": {
            "optional": true,
            "description": "Title of the notification (e.g. sender name for email).",
            "type": "string"
          },
          "message": {
            "optional": true,
            "description": "Main notification content.",
            "type": "string"
          },
          "contextMessage": {
            "optional": true,
            "description": "Alternate notification content with a lower-weight font.",
            "type": "string"
          },
          "priority": {
            "optional": true,
            "description": "Priority ranges from -2 to 2. -2 is lowest priority. 2 is highest. Zero is default.",
            "type": "integer",
            "minimum": -2,
            "maximum": 2
          },
          "eventTime": {
            "optional": true,
            "description": "A timestamp associated with the notification, in milliseconds past the epoch.",
            "type": "number"
          },
          "buttons": {
            "unsupported": true,
            "optional": true,
            "description": "Text and icons for up to two notification action buttons.",
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "title": {
                  "type": "string"
                },
                "iconUrl": {
                  "optional": true,
                  "type": "string"
                }
              }
            }
          },
          "imageUrl": {
            "optional": true,
            "description": "A URL to the image thumbnail for image-type notifications.",
            "type": "string"
          },
          "items": {
            "optional": true,
            "description": "Items for multi-item notifications.",
            "type": "array",
            "items": { "$ref": "NotificationItem" }
          },
          "progress": {
            "optional": true,
            "description": "Current progress ranges from 0 to 100.",
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "isClickable": {
            "optional": true,
            "description": "Whether to show UI indicating that the app will visibly respond to clicks on the body of a notification.",
            "type": "boolean"
          }
        }
      }
    ],
    "functions": [
      {
        "name": "create",
        "type": "function",
        "description": "Creates and displays a notification.",
        "async": "callback",
        "parameters": [
          {
            "optional": true,
            "type": "string",
            "name": "notificationId",
            "description": "Identifier of the notification. If it is empty, this method generates an id. If it matches an existing notification, this method first clears that notification before proceeding with the create operation."
          },
          {
            "$ref": "CreateNotificationOptions",
            "name": "options",
            "description": "Contents of the notification."
          },
          {
            "optional": true,
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "notificationId",
                "type": "string",
                "description": "The notification id (either supplied or generated) that represents the created notification."
              }
            ]
          }
        ]
      },
      {
        "name": "update",
        "unsupported": true,
        "type": "function",
        "description": "Updates an existing notification.",
        "async": "callback",
        "parameters": [
          {
            "type": "string",
            "name": "notificationId",
            "description": "The id of the notification to be updated."
          },
          {
            "$ref": "UpdateNotificationOptions",
            "name": "options",
            "description": "Contents of the notification to update to."
          },
          {
            "optional": true,
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "wasUpdated",
                "type": "boolean",
                "description": "Indicates whether a matching notification existed."
              }
            ]
          }
        ]
      },
      {
        "name": "clear",
        "type": "function",
        "description": "Clears an existing notification.",
        "async": "callback",
        "parameters": [
          {
            "type": "string",
            "name": "notificationId",
            "description": "The id of the notification to be updated."
          },
          {
            "optional": true,
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "wasCleared",
                "type": "boolean",
                "description": "Indicates whether a matching notification existed."
              }
            ]
          }
        ]
      },
      {
        "name": "getAll",
        "type": "function",
        "description": "Retrieves all the notifications.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "notifications",
                "type": "object",
                "additionalProperties": { "$ref": "CreateNotificationOptions" },
                "description": "The set of notifications currently in the system."
              }
            ]
          }
        ]
      },
      {
        "name": "getPermissionLevel",
        "unsupported": true,
        "type": "function",
        "description": "Retrieves whether the user has enabled notifications from this app or extension.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "level",
                "$ref": "PermissionLevel",
                "description": "The current permission level."
              }
            ]
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onClosed",
        "type": "function",
        "description": "Fired when the notification closed, either by the system or by user action.",
        "parameters": [
          {
            "type": "string",
            "name": "notificationId",
            "description": "The notificationId of the closed notification."
          },
          {
            "type": "boolean",
            "name": "byUser",
            "description": "True if the notification was closed by the user."
          }
        ]
      },
      {
        "name": "onClicked",
        "type": "function",
        "description": "Fired when the user clicked in a non-button area of the notification.",
        "parameters": [
          {
            "type": "string",
            "name": "notificationId",
            "description": "The notificationId of the clicked notification."
          }
        ]
      },
      {
        "name": "onButtonClicked",
        "type": "function",
        "description": "Fired when the  user pressed a button in the notification.",
        "parameters": [
          {
            "type": "string",
            "name": "notificationId",
            "description": "The notificationId of the clicked notification."
          },
          {
            "type": "number",
            "name": "buttonIndex",
            "description": "The index of the button clicked by the user."
          }
        ]
      },
      {
        "name": "onPermissionLevelChanged",
        "unsupported": true,
        "type": "function",
        "description": "Fired when the user changes the permission level.",
        "parameters": [
          {
            "$ref": "PermissionLevel",
            "name": "level",
            "description": "The new permission level."
          }
        ]
      },
      {
        "name": "onShowSettings",
        "unsupported": true,
        "type": "function",
        "description": "Fired when the user clicked on a link for the app's notification settings.",
        "parameters": []
      },
      {
        "name": "onShown",
        "type": "function",
        "description": "Fired when the notification is shown.",
        "parameters": [
          {
            "type": "string",
            "name": "notificationId",
            "description": "The notificationId of the shown notification."
          }
        ]
      }
    ]
  }
]
PK
!<�ǰ��*�*:chrome/toolkit/content/extensions/schemas/page_action.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "WebExtensionManifest",
        "properties": {
          "page_action": {
            "type": "object",
            "additionalProperties": { "$ref": "UnrecognizedProperty" },
            "properties": {
              "default_title": {
                "type": "string",
                "optional": true,
                "preprocess": "localize"
              },
              "default_icon": {
                "$ref": "IconPath",
                "optional": true
              },
              "default_popup": {
                "type": "string",
                "format": "relativeUrl",
                "optional": true,
                "preprocess": "localize"
              },
              "browser_style": {
                "type": "boolean",
                "optional": true,
                "description": "Deprecated in Manifest V3."
              },
              "show_matches": {
                "type": "array",
                "optional": true,
                "minItems": 1,
                "items": { "$ref": "MatchPattern" }
              },
              "hide_matches": {
                "type": "array",
                "optional": true,
                "minItems": 1,
                "items": { "$ref": "MatchPatternRestricted" }
              },
              "pinned": {
                "type": "boolean",
                "optional": true,
                "default": true
              }
            },
            "optional": true
          }
        }
      }
    ]
  },
  {
    "namespace": "pageAction",
    "description": "Use the <code>browser.pageAction</code> API to put icons inside the address bar. Page actions represent actions that can be taken on the current page, but that aren't applicable to all pages.",
    "permissions": ["manifest:page_action"],
    "types": [
      {
        "id": "ImageDataType",
        "type": "object",
        "isInstanceOf": "ImageData",
        "additionalProperties": { "type": "any" },
        "postprocess": "convertImageDataToURL",
        "description": "Pixel data for an image. Must be an ImageData object (for example, from a <code>canvas</code> element)."
      },
      {
        "id": "OnClickData",
        "type": "object",
        "description": "Information sent when a page action is clicked.",
        "properties": {
          "modifiers": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": ["Shift", "Alt", "Command", "Ctrl", "MacCtrl"]
            },
            "description": "An array of keyboard modifiers that were held while the menu item was clicked."
          },
          "button": {
            "type": "integer",
            "optional": true,
            "description": "An integer value of button by which menu item was clicked."
          }
        }
      }
    ],
    "functions": [
      {
        "name": "show",
        "type": "function",
        "async": "callback",
        "description": "Shows the page action. The page action is shown whenever the tab is selected.",
        "parameters": [
          {
            "type": "integer",
            "name": "tabId",
            "minimum": 0,
            "description": "The id of the tab for which you want to modify the page action."
          },
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "hide",
        "type": "function",
        "async": "callback",
        "description": "Hides the page action.",
        "parameters": [
          {
            "type": "integer",
            "name": "tabId",
            "minimum": 0,
            "description": "The id of the tab for which you want to modify the page action."
          },
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "isShown",
        "type": "function",
        "description": "Checks whether the page action is shown.",
        "async": true,
        "parameters": [
          {
            "name": "details",
            "type": "object",
            "properties": {
              "tabId": {
                "type": "integer",
                "description": "Specify the tab to get the shownness from."
              }
            }
          }
        ]
      },
      {
        "name": "setTitle",
        "type": "function",
        "description": "Sets the title of the page action. This is displayed in a tooltip over the page action.",
        "parameters": [
          {
            "name": "details",
            "type": "object",
            "properties": {
              "tabId": {
                "type": "integer",
                "minimum": 0,
                "description": "The id of the tab for which you want to modify the page action."
              },
              "title": {
                "choices": [{ "type": "string" }, { "type": "null" }],
                "description": "The tooltip string."
              }
            }
          }
        ]
      },
      {
        "name": "getTitle",
        "type": "function",
        "description": "Gets the title of the page action.",
        "async": "callback",
        "parameters": [
          {
            "name": "details",
            "type": "object",
            "properties": {
              "tabId": {
                "type": "integer",
                "description": "Specify the tab to get the title from."
              }
            }
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "result",
                "type": "string"
              }
            ]
          }
        ]
      },
      {
        "name": "setIcon",
        "type": "function",
        "description": "Sets the icon for the page action. The icon can be specified either as the path to an image file or as the pixel data from a canvas element, or as dictionary of either one of those. Either the <b>path</b> or the <b>imageData</b> property must be specified.",
        "async": "callback",
        "parameters": [
          {
            "name": "details",
            "type": "object",
            "properties": {
              "tabId": {
                "type": "integer",
                "minimum": 0,
                "description": "The id of the tab for which you want to modify the page action."
              },
              "imageData": {
                "choices": [
                  { "$ref": "ImageDataType" },
                  {
                    "type": "object",
                    "patternProperties": {
                      "^[1-9]\\d*$": { "$ref": "ImageDataType" }
                    }
                  }
                ],
                "optional": true,
                "description": "Either an ImageData object or a dictionary {size -> ImageData} representing icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * 19 will be selected. Initially only scales 1 and 2 will be supported. At least one image must be specified. Note that 'details.imageData = foo' is equivalent to 'details.imageData = {'19': foo}'"
              },
              "path": {
                "choices": [
                  { "type": "string" },
                  {
                    "type": "object",
                    "patternProperties": {
                      "^[1-9]\\d*$": { "type": "string" }
                    }
                  }
                ],
                "optional": true,
                "description": "Either a relative image path or a dictionary {size -> relative image path} pointing to icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * 19 will be selected. Initially only scales 1 and 2 will be supported. At least one image must be specified. Note that 'details.path = foo' is equivalent to 'details.imageData = {'19': foo}'"
              }
            }
          },
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "setPopup",
        "type": "function",
        "async": true,
        "description": "Sets the html document to be opened as a popup when the user clicks on the page action's icon.",
        "parameters": [
          {
            "name": "details",
            "type": "object",
            "properties": {
              "tabId": {
                "type": "integer",
                "minimum": 0,
                "description": "The id of the tab for which you want to modify the page action."
              },
              "popup": {
                "choices": [{ "type": "string" }, { "type": "null" }],
                "description": "The html file to show in a popup.  If set to the empty string (''), no popup is shown."
              }
            }
          }
        ]
      },
      {
        "name": "getPopup",
        "type": "function",
        "description": "Gets the html document set as the popup for this page action.",
        "async": "callback",
        "parameters": [
          {
            "name": "details",
            "type": "object",
            "properties": {
              "tabId": {
                "type": "integer",
                "description": "Specify the tab to get the popup from."
              }
            }
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "result",
                "type": "string"
              }
            ]
          }
        ]
      },
      {
        "name": "openPopup",
        "type": "function",
        "requireUserInput": true,
        "description": "Opens the extension page action in the active window.",
        "async": true,
        "parameters": []
      }
    ],
    "events": [
      {
        "name": "onClicked",
        "type": "function",
        "description": "Fired when a page action icon is clicked.  This event will not fire if the page action has a popup.",
        "parameters": [
          {
            "name": "tab",
            "$ref": "tabs.Tab"
          },
          {
            "name": "info",
            "$ref": "OnClickData",
            "optional": true
          }
        ]
      }
    ]
  }
]
PK
!<,�j

:chrome/toolkit/content/extensions/schemas/permissions.json[
  {
    "namespace": "permissions",
    "types": [
      {
        "id": "Permissions",
        "type": "object",
        "properties": {
          "permissions": {
            "type": "array",
            "items": { "$ref": "manifest.OptionalPermission" },
            "optional": true,
            "default": []
          },
          "origins": {
            "type": "array",
            "items": { "$ref": "manifest.MatchPattern" },
            "optional": true,
            "default": []
          }
        }
      },
      {
        "id": "AnyPermissions",
        "type": "object",
        "properties": {
          "permissions": {
            "type": "array",
            "items": { "$ref": "manifest.Permission" },
            "optional": true,
            "default": []
          },
          "origins": {
            "type": "array",
            "items": { "$ref": "manifest.MatchPattern" },
            "optional": true,
            "default": []
          }
        }
      }
    ],
    "functions": [
      {
        "name": "getAll",
        "type": "function",
        "async": "callback",
        "description": "Get a list of all the extension's permissions.",
        "parameters": [
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": "permissions",
                "$ref": "AnyPermissions"
              }
            ]
          }
        ]
      },
      {
        "name": "contains",
        "type": "function",
        "async": "callback",
        "description": "Check if the extension has the given permissions.",
        "parameters": [
          {
            "name": "permissions",
            "$ref": "AnyPermissions"
          },
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": "result",
                "type": "boolean"
              }
            ]
          }
        ]
      },
      {
        "name": "request",
        "type": "function",
        "allowedContexts": ["content"],
        "async": "callback",
        "requireUserInput": true,
        "description": "Request the given permissions.",
        "parameters": [
          {
            "name": "permissions",
            "$ref": "Permissions"
          },
          {
            "name": "callback",
            "type": "function",
            "parameters": [
              {
                "name": "granted",
                "type": "boolean"
              }
            ]
          }
        ]
      },
      {
        "name": "remove",
        "type": "function",
        "async": "callback",
        "description": "Relinquish the given permissions.",
        "parameters": [
          {
            "name": "permissions",
            "$ref": "Permissions"
          },
          {
            "name": "callback",
            "type": "function",
            "parameters": []
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onAdded",
        "type": "function",
        "description": "Fired when the extension acquires new permissions.",
        "parameters": [
          {
            "name": "permissions",
            "$ref": "Permissions"
          }
        ]
      },
      {
        "name": "onRemoved",
        "type": "function",
        "description": "Fired when permissions are removed from the extension.",
        "parameters": [
          {
            "name": "permissions",
            "$ref": "Permissions"
          }
        ]
      }
    ]
  }
]
PK
!<�6_N{ { 6chrome/toolkit/content/extensions/schemas/privacy.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "OptionalPermission",
        "choices": [
          {
            "type": "string",
            "enum": ["privacy"]
          }
        ]
      }
    ]
  },
  {
    "namespace": "privacy",
    "permissions": ["privacy"]
  },
  {
    "namespace": "privacy.network",
    "description": "Use the <code>browser.privacy</code> API to control usage of the features in the browser that can affect a user's privacy.",
    "permissions": ["privacy"],
    "types": [
      {
        "id": "IPHandlingPolicy",
        "type": "string",
        "enum": [
          "default",
          "default_public_and_private_interfaces",
          "default_public_interface_only",
          "disable_non_proxied_udp",
          "proxy_only"
        ],
        "description": "The IP handling policy of WebRTC."
      },
      {
        "id": "tlsVersionRestrictionConfig",
        "type": "object",
        "description": "An object which describes TLS minimum and maximum versions.",
        "properties": {
          "minimum": {
            "type": "string",
            "enum": ["TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3", "unknown"],
            "optional": true,
            "description": "The minimum TLS version supported."
          },
          "maximum": {
            "type": "string",
            "enum": ["TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3", "unknown"],
            "optional": true,
            "description": "The maximum TLS version supported."
          }
        }
      },
      {
        "id": "HTTPSOnlyModeOption",
        "type": "string",
        "enum": ["always", "private_browsing", "never"],
        "description": "The mode for https-only mode."
      }
    ],
    "properties": {
      "networkPredictionEnabled": {
        "$ref": "types.Setting",
        "description": "If enabled, the browser attempts to speed up your web browsing experience by pre-resolving DNS entries, prerendering sites (<code>&lt;link rel='prefetch' ...&gt;</code>), and preemptively opening TCP and SSL connections to servers.  This preference's value is a boolean, defaulting to <code>true</code>."
      },
      "peerConnectionEnabled": {
        "$ref": "types.Setting",
        "description": "Allow users to enable and disable RTCPeerConnections (aka WebRTC)."
      },
      "webRTCIPHandlingPolicy": {
        "$ref": "types.Setting",
        "description": "Allow users to specify the media performance/privacy tradeoffs which impacts how WebRTC traffic will be routed and how much local address information is exposed. This preference's value is of type IPHandlingPolicy, defaulting to <code>default</code>."
      },
      "tlsVersionRestriction": {
        "$ref": "types.Setting",
        "description": "This property controls the minimum and maximum TLS versions. This setting's value is an object of $(ref:tlsVersionRestrictionConfig)."
      },
      "httpsOnlyMode": {
        "$ref": "types.Setting",
        "description": "Allow users to query the mode for 'HTTPS-Only Mode'. This setting's value is of type HTTPSOnlyModeOption, defaulting to <code>never</code>."
      },
      "globalPrivacyControl": {
        "$ref": "types.Setting",
        "description": "Allow users to query the status of 'Global Privacy Control'. This setting's value is of type boolean, defaulting to <code>false</code>."
      }
    }
  },
  {
    "namespace": "privacy.services",
    "description": "Use the <code>browser.privacy</code> API to control usage of the features in the browser that can affect a user's privacy.",
    "permissions": ["privacy"],
    "properties": {
      "passwordSavingEnabled": {
        "$ref": "types.Setting",
        "description": "If enabled, the password manager will ask if you want to save passwords. This preference's value is a boolean, defaulting to <code>true</code>."
      }
    }
  },
  {
    "namespace": "privacy.websites",
    "description": "Use the <code>browser.privacy</code> API to control usage of the features in the browser that can affect a user's privacy.",
    "permissions": ["privacy"],
    "types": [
      {
        "id": "TrackingProtectionModeOption",
        "type": "string",
        "enum": ["always", "never", "private_browsing"],
        "description": "The mode for tracking protection."
      },
      {
        "id": "CookieConfig",
        "type": "object",
        "description": "The settings for cookies.",
        "properties": {
          "behavior": {
            "type": "string",
            "optional": true,
            "enum": [
              "allow_all",
              "reject_all",
              "reject_third_party",
              "allow_visited",
              "reject_trackers",
              "reject_trackers_and_partition_foreign"
            ],
            "description": "The type of cookies to allow."
          },
          "nonPersistentCookies": {
            "type": "boolean",
            "optional": true,
            "default": false,
            "description": "Whether to create all cookies as nonPersistent (i.e., session) cookies.",
            "deprecated": "This property has no effect anymore and its value is always <code>false<code>."
          }
        }
      }
    ],
    "properties": {
      "thirdPartyCookiesAllowed": {
        "$ref": "types.Setting",
        "description": "If disabled, the browser blocks third-party sites from setting cookies. The value of this preference is of type boolean, and the default value is <code>true</code>.",
        "unsupported": true
      },
      "hyperlinkAuditingEnabled": {
        "$ref": "types.Setting",
        "description": "If enabled, the browser sends auditing pings when requested by a website (<code>&lt;a ping&gt;</code>). The value of this preference is of type boolean, and the default value is <code>true</code>."
      },
      "referrersEnabled": {
        "$ref": "types.Setting",
        "description": "If enabled, the browser sends <code>referer</code> headers with your requests. Yes, the name of this preference doesn't match the misspelled header. No, we're not going to change it. The value of this preference is of type boolean, and the default value is <code>true</code>."
      },
      "resistFingerprinting": {
        "$ref": "types.Setting",
        "description": "If enabled, the browser attempts to appear similar to other users by reporting generic information to websites. This can prevent websites from uniquely identifying users. Examples of data that is spoofed include number of CPU cores, precision of JavaScript timers, the local timezone, and disabling features such as GamePad support, and the WebSpeech and Navigator APIs. The value of this preference is of type boolean, and the default value is <code>false</code>."
      },
      "firstPartyIsolate": {
        "$ref": "types.Setting",
        "description": "If enabled, the browser will associate all data (including cookies, HSTS data, cached images, and more) for any third party domains with the domain in the address bar. This prevents third party trackers from using directly stored information to identify you across different websites, but may break websites where you login with a third party account (such as a Facebook or Google login.) The value of this preference is of type boolean, and the default value is <code>false</code>."
      },
      "protectedContentEnabled": {
        "$ref": "types.Setting",
        "description": "<strong>Available on Windows and ChromeOS only</strong>: If enabled, the browser provides a unique ID to plugins in order to run protected content. The value of this preference is of type boolean, and the default value is <code>true</code>.",
        "unsupported": true
      },
      "trackingProtectionMode": {
        "$ref": "types.Setting",
        "description": "Allow users to specify the mode for tracking protection. This setting's value is of type TrackingProtectionModeOption, defaulting to <code>private_browsing_only</code>."
      },
      "cookieConfig": {
        "$ref": "types.Setting",
        "description": "Allow users to specify the default settings for allowing cookies, as well as whether all cookies should be created as non-persistent cookies. This setting's value is of type CookieConfig."
      }
    }
  }
]
PK
!<k:����4chrome/toolkit/content/extensions/schemas/proxy.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "OptionalPermission",
        "choices": [
          {
            "type": "string",
            "enum": ["proxy"]
          }
        ]
      }
    ]
  },
  {
    "namespace": "proxy",
    "description": "Provides access to global proxy settings for Firefox and proxy event listeners to handle dynamic proxy implementations.",
    "permissions": ["proxy"],
    "types": [
      {
        "id": "ProxyConfig",
        "type": "object",
        "description": "An object which describes proxy settings.",
        "properties": {
          "proxyType": {
            "type": "string",
            "optional": true,
            "enum": ["none", "autoDetect", "system", "manual", "autoConfig"],
            "description": "The type of proxy to use."
          },
          "http": {
            "type": "string",
            "optional": true,
            "description": "The address of the http proxy, can include a port."
          },
          "httpProxyAll": {
            "type": "boolean",
            "optional": true,
            "description": "Use the http proxy server for all protocols."
          },
          "ftp": {
            "type": "string",
            "optional": true,
            "deprecated": true,
            "description": "The address of the ftp proxy, can include a port.  Deprecated since Firefox 88."
          },
          "ssl": {
            "type": "string",
            "optional": true,
            "description": "The address of the ssl proxy, can include a port."
          },
          "socks": {
            "type": "string",
            "optional": true,
            "description": "The address of the socks proxy, can include a port."
          },
          "socksVersion": {
            "type": "integer",
            "optional": true,
            "description": "The version of the socks proxy.",
            "minimum": 4,
            "maximum": 5
          },
          "passthrough": {
            "type": "string",
            "optional": true,
            "description": "A list of hosts which should not be proxied."
          },
          "autoConfigUrl": {
            "type": "string",
            "optional": true,
            "description": "A URL to use to configure the proxy."
          },
          "autoLogin": {
            "type": "boolean",
            "optional": true,
            "description": "Do not prompt for authentication if password is saved."
          },
          "proxyDNS": {
            "type": "boolean",
            "optional": true,
            "description": "Proxy DNS when using SOCKS. DNS queries get leaked to the network when set to false. True by default for SOCKS v5. False by default for SOCKS v4."
          },
          "respectBeConservative": {
            "type": "boolean",
            "optional": true,
            "default": true,
            "description": " If true (the default value), do not use newer TLS protocol features that might have interoperability problems on the Internet. This is intended only for use with critical infrastructure like the updates, and is only available to privileged addons."
          }
        }
      }
    ],
    "properties": {
      "settings": {
        "$ref": "types.Setting",
        "description": "Configures proxy settings. This setting's value is an object of type ProxyConfig."
      }
    },
    "events": [
      {
        "name": "onRequest",
        "type": "function",
        "description": "Fired when proxy data is needed for a request.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "requestId": {
                "type": "string",
                "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."
              },
              "url": { "type": "string" },
              "method": {
                "type": "string",
                "description": "Standard HTTP method."
              },
              "frameId": {
                "type": "integer",
                "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."
              },
              "parentFrameId": {
                "type": "integer",
                "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."
              },
              "incognito": {
                "type": "boolean",
                "optional": true,
                "description": "True for private browsing requests."
              },
              "cookieStoreId": {
                "type": "string",
                "optional": true,
                "description": "The cookie store ID of the contextual identity."
              },
              "originUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the resource that triggered this request."
              },
              "documentUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the page into which the requested resource will be loaded."
              },
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."
              },
              "type": {
                "$ref": "webRequest.ResourceType",
                "description": "How the requested resource will be used."
              },
              "timeStamp": {
                "type": "number",
                "description": "The time when this signal is triggered, in milliseconds since the epoch."
              },
              "fromCache": {
                "type": "boolean",
                "description": "Indicates if this response was fetched from disk cache."
              },
              "requestHeaders": {
                "$ref": "webRequest.HttpHeaders",
                "optional": true,
                "description": "The HTTP request headers that are going to be sent out with this request."
              },
              "urlClassification": {
                "$ref": "webRequest.UrlClassification",
                "description": "Url classification if the request has been classified."
              },
              "thirdParty": {
                "type": "boolean",
                "description": "Indicates if this request and its content window hierarchy is third party."
              }
            }
          }
        ],
        "extraParameters": [
          {
            "$ref": "webRequest.RequestFilter",
            "name": "filter",
            "description": "A set of filters that restricts the events that will be sent to this listener."
          },
          {
            "type": "array",
            "optional": true,
            "name": "extraInfoSpec",
            "description": "Array of extra information that should be passed to the listener function.",
            "items": {
              "type": "string",
              "enum": ["requestHeaders"]
            }
          }
        ]
      },
      {
        "name": "onError",
        "type": "function",
        "description": "Notifies about errors caused by the invalid use of the proxy API.",
        "parameters": [
          {
            "name": "error",
            "type": "object"
          }
        ]
      }
    ]
  }
]
PK
!<�Yd����6chrome/toolkit/content/extensions/schemas/runtime.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "OptionalPermission",
        "choices": [
          {
            "type": "string",
            "enum": ["nativeMessaging"]
          }
        ]
      }
    ]
  },
  {
    "namespace": "runtime",
    "allowedContexts": ["content", "devtools"],
    "description": "Use the <code>browser.runtime</code> API to retrieve the background page, return details about the manifest, and listen for and respond to events in the app or extension lifecycle. You can also use this API to convert the relative path of URLs to fully-qualified URLs.",
    "types": [
      {
        "id": "ContextFilter",
        "min_manifest_version": 3,
        "type": "object",
        "description": "A filter to match against existing extension context. Matching contexts must match all specified filters.",
        "properties": {
          "contextIds": {
            "optional": true,
            "type": "array",
            "items": { "type": "string" }
          },
          "contextTypes": {
            "optional": true,
            "type": "array",
            "items": { "$ref": "ContextType" }
          },
          "documentIds": {
            "optional": true,
            "type": "array",
            "items": { "type": "string" }
          },
          "documentOrigins": {
            "optional": true,
            "type": "array",
            "items": { "type": "string" }
          },
          "documentUrls": {
            "optional": true,
            "type": "array",
            "items": { "type": "string" }
          },
          "frameIds": {
            "optional": true,
            "type": "array",
            "items": { "type": "integer" }
          },
          "tabIds": {
            "optional": true,
            "type": "array",
            "items": { "type": "integer" }
          },
          "windowIds": {
            "optional": true,
            "type": "array",
            "items": { "type": "integer" }
          },
          "incognito": { "optional": true, "type": "boolean" }
        }
      },
      {
        "id": "ContextType",
        "type": "string",
        "enum": ["BACKGROUND", "POPUP", "SIDE_PANEL", "TAB"],
        "description": "The type of extension view."
      },
      {
        "id": "ExtensionContext",
        "type": "object",
        "description": "A context hosting extension content",
        "properties": {
          "contextId": {
            "type": "string",
            "description": "An unique identifier associated to this context"
          },
          "contextType": {
            "$ref": "ContextType",
            "description": "The type of the context"
          },
          "documentId": {
            "type": "string",
            "unsupported": true,
            "optional": true,
            "description": "An UUID for the document associated with this context, or undefined if it is not hosted in a document"
          },
          "documentOrigin": {
            "type": "string",
            "optional": true,
            "description": "The origin of the document associated with this context, or undefined if it is not hosted in a document"
          },
          "documentUrl": {
            "type": "string",
            "optional": true,
            "description": "The URL of the document associated with this context, or undefined if it is not hosted in a document"
          },
          "incognito": {
            "type": "boolean",
            "description": "Whether the context is associated with an private browsing context."
          },
          "frameId": {
            "type": "integer",
            "description": "The frame ID for this context, or -1 if it is not hosted in a frame."
          },
          "tabId": {
            "type": "integer",
            "description": "The tab ID for this context, or -1 if it is not hosted in a tab."
          },
          "windowId": {
            "type": "integer",
            "description": "The window ID for this context, or -1 if it is not hosted in a window."
          }
        }
      },
      {
        "id": "Port",
        "type": "object",
        "allowedContexts": ["content", "devtools"],
        "description": "An object which allows two way communication with other pages.",
        "properties": {
          "name": { "type": "string" },
          "disconnect": { "type": "function" },
          "onDisconnect": { "$ref": "events.Event" },
          "onMessage": { "$ref": "events.Event" },
          "postMessage": { "type": "function" },
          "sender": {
            "$ref": "MessageSender",
            "optional": true,
            "description": "This property will <b>only</b> be present on ports passed to onConnect/onConnectExternal listeners."
          }
        },
        "additionalProperties": { "type": "any" }
      },
      {
        "id": "MessageSender",
        "type": "object",
        "allowedContexts": ["content", "devtools"],
        "description": "An object containing information about the script context that sent a message or request.",
        "properties": {
          "tab": {
            "$ref": "tabs.Tab",
            "optional": true,
            "description": "The $(ref:tabs.Tab) which opened the connection, if any. This property will <strong>only</strong> be present when the connection was opened from a tab (including content scripts), and <strong>only</strong> if the receiver is an extension, not an app."
          },
          "frameId": {
            "type": "integer",
            "optional": true,
            "description": "The $(topic:frame_ids)[frame] that opened the connection. 0 for top-level frames, positive for child frames. This will only be set when <code>tab</code> is set."
          },
          "id": {
            "type": "string",
            "optional": true,
            "description": "The ID of the extension or app that opened the connection, if any."
          },
          "url": {
            "type": "string",
            "optional": true,
            "description": "The URL of the page or frame that opened the connection. If the sender is in an iframe, it will be iframe's URL not the URL of the page which hosts it."
          },
          "tlsChannelId": {
            "unsupported": true,
            "type": "string",
            "optional": true,
            "description": "The TLS channel ID of the page or frame that opened the connection, if requested by the extension or app, and if available."
          }
        }
      },
      {
        "id": "PlatformOs",
        "type": "string",
        "allowedContexts": ["content", "devtools"],
        "description": "The operating system the browser is running on.",
        "enum": ["mac", "win", "android", "cros", "linux", "openbsd"]
      },
      {
        "id": "PlatformArch",
        "type": "string",
        "enum": [
          "aarch64",
          "arm",
          "ppc64",
          "s390x",
          "sparc64",
          "x86-32",
          "x86-64",
          "noarch"
        ],
        "allowedContexts": ["content", "devtools"],
        "description": "The machine's processor architecture."
      },
      {
        "id": "PlatformInfo",
        "type": "object",
        "allowedContexts": ["content", "devtools"],
        "description": "An object containing information about the current platform.",
        "properties": {
          "os": {
            "$ref": "PlatformOs",
            "description": "The operating system the browser is running on."
          },
          "arch": {
            "$ref": "PlatformArch",
            "description": "The machine's processor architecture."
          },
          "nacl_arch": {
            "unsupported": true,
            "description": "The native client architecture. This may be different from arch on some platforms.",
            "$ref": "PlatformNaclArch"
          }
        }
      },
      {
        "id": "BrowserInfo",
        "type": "object",
        "description": "An object containing information about the current browser.",
        "properties": {
          "name": {
            "type": "string",
            "description": "The name of the browser, for example 'Firefox'."
          },
          "vendor": {
            "type": "string",
            "description": "The name of the browser vendor, for example 'Mozilla'."
          },
          "version": {
            "type": "string",
            "description": "The browser's version, for example '42.0.0' or '0.8.1pre'."
          },
          "buildID": {
            "type": "string",
            "description": "The browser's build ID/date, for example '20160101'."
          }
        }
      },
      {
        "id": "RequestUpdateCheckStatus",
        "type": "string",
        "enum": ["throttled", "no_update", "update_available"],
        "allowedContexts": ["content", "devtools"],
        "description": "Result of the update check."
      },
      {
        "id": "OnInstalledReason",
        "type": "string",
        "enum": ["install", "update", "browser_update"],
        "allowedContexts": ["content", "devtools"],
        "description": "The reason that this event is being dispatched."
      },
      {
        "id": "OnRestartRequiredReason",
        "type": "string",
        "allowedContexts": ["content", "devtools"],
        "description": "The reason that the event is being dispatched. 'app_update' is used when the restart is needed because the application is updated to a newer version. 'os_update' is used when the restart is needed because the browser/OS is updated to a newer version. 'periodic' is used when the system runs for more than the permitted uptime set in the enterprise policy.",
        "enum": ["app_update", "os_update", "periodic"]
      },
      {
        "id": "OnPerformanceWarningCategory",
        "type": "string",
        "enum": ["content_script"],
        "description": "The performance warning event category, e.g. 'content_script'."
      },
      {
        "id": "OnPerformanceWarningSeverity",
        "type": "string",
        "enum": ["low", "medium", "high"],
        "description": "The performance warning event severity. Will be 'high' for serious and user-visible issues."
      }
    ],
    "properties": {
      "lastError": {
        "type": "object",
        "optional": true,
        "allowedContexts": ["content", "devtools"],
        "description": "This will be defined during an API method callback if there was an error",
        "properties": {
          "message": {
            "optional": true,
            "type": "string",
            "description": "Details about the error which occurred."
          }
        },
        "additionalProperties": {
          "type": "any"
        }
      },
      "id": {
        "type": "string",
        "allowedContexts": ["content", "devtools"],
        "description": "The ID of the extension/app."
      }
    },
    "functions": [
      {
        "name": "getBackgroundPage",
        "type": "function",
        "description": "Retrieves the JavaScript 'window' object for the background page running inside the current extension/app. If the background page is an event page, the system will ensure it is loaded before calling the callback. If there is no background page, an error is set.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "backgroundPage",
                "optional": true,
                "type": "object",
                "isInstanceOf": "Window",
                "additionalProperties": { "type": "any" },
                "description": "The JavaScript 'window' object for the background page."
              }
            ]
          }
        ]
      },
      {
        "name": "getContexts",
        "type": "function",
        "description": "Fetches information about active contexts associated with this extension",
        "async": "callback",
        "parameters": [
          {
            "name": "filter",
            "$ref": "ContextFilter",
            "description": "A filter to find matching context."
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "contexts",
                "type": "array",
                "items": { "$ref": "ExtensionContext" },
                "description": "The matching contexts, if any."
              }
            ]
          }
        ]
      },
      {
        "name": "openOptionsPage",
        "type": "function",
        "description": "<p>Open your Extension's options page, if possible.</p><p>The precise behavior may depend on your manifest's <code>$(topic:optionsV2)[options_ui]</code> or <code>$(topic:options)[options_page]</code> key, or what the browser happens to support at the time.</p><p>If your Extension does not declare an options page, or the browser failed to create one for some other reason, the callback will set $(ref:lastError).</p>",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [],
            "optional": true
          }
        ]
      },
      {
        "name": "getManifest",
        "allowedContexts": ["content", "devtools"],
        "description": "Returns details about the app or extension from the manifest. The object returned is a serialization of the full $(topic:manifest)[manifest file].",
        "type": "function",
        "parameters": [],
        "returns": {
          "type": "object",
          "properties": {},
          "additionalProperties": { "type": "any" },
          "description": "The manifest details."
        }
      },
      {
        "name": "getURL",
        "type": "function",
        "allowedContexts": ["content", "devtools"],
        "description": "Converts a relative path within an app/extension install directory to a fully-qualified URL.",
        "parameters": [
          {
            "type": "string",
            "name": "path",
            "description": "A path to a resource within an app/extension expressed relative to its install directory."
          }
        ],
        "returns": {
          "type": "string",
          "description": "The fully-qualified URL to the resource."
        }
      },
      {
        "name": "getFrameId",
        "type": "function",
        "allowedContexts": ["content", "devtools"],
        "description": "Get the frameId of any window global or frame element.",
        "parameters": [
          {
            "type": "any",
            "name": "target",
            "description": "A WindowProxy or a Browsing Context container element (IFrame, Frame, Embed, Object) for the target frame."
          }
        ],
        "allowCrossOriginArguments": true,
        "returns": {
          "type": "number",
          "description": "The frameId of the target frame, or -1 if it doesn't exist."
        }
      },
      {
        "name": "setUninstallURL",
        "type": "function",
        "description": "Sets the URL to be visited upon uninstallation. This may be used to clean up server-side data, do analytics, and implement surveys. Maximum 1023 characters.",
        "async": "callback",
        "parameters": [
          {
            "type": "string",
            "name": "url",
            "optional": true,
            "maxLength": 1023,
            "description": "URL to be opened after the extension is uninstalled. This URL must have an http: or https: scheme. Set an empty string to not open a new tab upon uninstallation."
          },
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "description": "Called when the uninstall URL is set. If the given URL is invalid, $(ref:runtime.lastError) will be set.",
            "parameters": []
          }
        ]
      },
      {
        "name": "reload",
        "description": "Reloads the app or extension.",
        "type": "function",
        "parameters": []
      },
      {
        "name": "requestUpdateCheck",
        "unsupported": true,
        "type": "function",
        "description": "Requests an update check for this app/extension.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "status",
                "$ref": "RequestUpdateCheckStatus",
                "description": "Result of the update check."
              },
              {
                "name": "details",
                "type": "object",
                "optional": true,
                "properties": {
                  "version": {
                    "type": "string",
                    "description": "The version of the available update."
                  }
                },
                "description": "If an update is available, this contains more information about the available update."
              }
            ]
          }
        ]
      },
      {
        "name": "restart",
        "unsupported": true,
        "description": "Restart the device when the app runs in kiosk mode. Otherwise, it's no-op.",
        "type": "function",
        "parameters": []
      },
      {
        "name": "connect",
        "type": "function",
        "allowedContexts": ["content", "devtools"],
        "description": "Attempts to connect to connect listeners within an extension/app (such as the background page), or other extensions/apps. This is useful for content scripts connecting to their extension processes, inter-app/extension communication, and $(topic:manifest/externally_connectable)[web messaging]. Note that this does not connect to any listeners in a content script. Extensions may connect to content scripts embedded in tabs via $(ref:tabs.connect).",
        "parameters": [
          {
            "type": "string",
            "name": "extensionId",
            "optional": true,
            "description": "The ID of the extension or app to connect to. If omitted, a connection will be attempted with your own extension. Required if sending messages from a web page for $(topic:manifest/externally_connectable)[web messaging]."
          },
          {
            "type": "object",
            "name": "connectInfo",
            "properties": {
              "name": {
                "type": "string",
                "optional": true,
                "description": "Will be passed into onConnect for processes that are listening for the connection event."
              },
              "includeTlsChannelId": {
                "type": "boolean",
                "optional": true,
                "description": "Whether the TLS channel ID will be passed into onConnectExternal for processes that are listening for the connection event."
              }
            },
            "optional": true
          }
        ],
        "returns": {
          "$ref": "Port",
          "description": "Port through which messages can be sent and received. The port's $(ref:runtime.Port onDisconnect) event is fired if the extension/app does not exist. "
        }
      },
      {
        "name": "connectNative",
        "type": "function",
        "description": "Connects to a native application in the host machine.",
        "allowedContexts": ["content"],
        "permissions": ["nativeMessaging"],
        "parameters": [
          {
            "type": "string",
            "pattern": "^\\w+(\\.\\w+)*$",
            "name": "application",
            "description": "The name of the registered application to connect to."
          }
        ],
        "returns": {
          "$ref": "Port",
          "description": "Port through which messages can be sent and received with the application"
        }
      },
      {
        "name": "sendMessage",
        "type": "function",
        "allowAmbiguousOptionalArguments": true,
        "allowedContexts": ["content", "devtools"],
        "description": "Sends a single message to event listeners within your extension/app or a different extension/app. Similar to $(ref:runtime.connect) but only sends a single message, with an optional response. If sending to your extension, the $(ref:runtime.onMessage) event will be fired in each page, or $(ref:runtime.onMessageExternal), if a different extension. Note that extensions cannot send messages to content scripts using this method. To send messages to content scripts, use $(ref:tabs.sendMessage).",
        "async": "responseCallback",
        "parameters": [
          {
            "type": "string",
            "name": "extensionId",
            "optional": true,
            "description": "The ID of the extension/app to send the message to. If omitted, the message will be sent to your own extension/app. Required if sending messages from a web page for $(topic:manifest/externally_connectable)[web messaging]."
          },
          { "type": "any", "name": "message" },
          {
            "type": "object",
            "name": "options",
            "properties": {
              "includeTlsChannelId": {
                "type": "boolean",
                "optional": true,
                "unsupported": true,
                "description": "Whether the TLS channel ID will be passed into onMessageExternal for processes that are listening for the connection event."
              }
            },
            "optional": true
          },
          {
            "type": "function",
            "name": "responseCallback",
            "optional": true,
            "parameters": [
              {
                "name": "response",
                "type": "any",
                "description": "The JSON response object sent by the handler of the message. If an error occurs while connecting to the extension, the callback will be called with no arguments and $(ref:runtime.lastError) will be set to the error message."
              }
            ]
          }
        ]
      },
      {
        "name": "sendNativeMessage",
        "type": "function",
        "description": "Send a single message to a native application.",
        "allowedContexts": ["content"],
        "permissions": ["nativeMessaging"],
        "async": "responseCallback",
        "parameters": [
          {
            "name": "application",
            "description": "The name of the native messaging host.",
            "type": "string",
            "pattern": "^\\w+(\\.\\w+)*$"
          },
          {
            "name": "message",
            "description": "The message that will be passed to the native messaging host.",
            "type": "any"
          },
          {
            "type": "function",
            "name": "responseCallback",
            "optional": true,
            "parameters": [
              {
                "name": "response",
                "type": "any",
                "description": "The response message sent by the native messaging host. If an error occurs while connecting to the native messaging host, the callback will be called with no arguments and $(ref:runtime.lastError) will be set to the error message."
              }
            ]
          }
        ]
      },
      {
        "name": "getBrowserInfo",
        "type": "function",
        "description": "Returns information about the current browser.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "description": "Called with results",
            "parameters": [
              {
                "name": "browserInfo",
                "$ref": "BrowserInfo"
              }
            ]
          }
        ]
      },
      {
        "name": "getPlatformInfo",
        "type": "function",
        "description": "Returns information about the current platform.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "description": "Called with results",
            "parameters": [
              {
                "name": "platformInfo",
                "$ref": "PlatformInfo"
              }
            ]
          }
        ]
      },
      {
        "name": "getPackageDirectoryEntry",
        "unsupported": true,
        "type": "function",
        "description": "Returns a DirectoryEntry for the package directory.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "directoryEntry",
                "type": "object",
                "additionalProperties": { "type": "any" },
                "isInstanceOf": "DirectoryEntry"
              }
            ]
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onStartup",
        "type": "function",
        "description": "Fired when a profile that has this extension installed first starts up. This event is not fired for incognito profiles."
      },
      {
        "name": "onInstalled",
        "type": "function",
        "description": "Fired when the extension is first installed, when the extension is updated to a new version, and when the browser is updated to a new version.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "reason": {
                "$ref": "OnInstalledReason",
                "description": "The reason that this event is being dispatched."
              },
              "previousVersion": {
                "type": "string",
                "optional": true,
                "description": "Indicates the previous version of the extension, which has just been updated. This is present only if 'reason' is 'update'."
              },
              "temporary": {
                "type": "boolean",
                "description": "Indicates whether the addon is installed as a temporary extension."
              },
              "id": {
                "type": "string",
                "optional": true,
                "unsupported": true,
                "description": "Indicates the ID of the imported shared module extension which updated. This is present only if 'reason' is 'shared_module_update'."
              }
            }
          }
        ]
      },
      {
        "name": "onSuspend",
        "type": "function",
        "description": "Sent to the event page just before it is unloaded. This gives the extension opportunity to do some clean up. Note that since the page is unloading, any asynchronous operations started while handling this event are not guaranteed to complete. If more activity for the event page occurs before it gets unloaded the onSuspendCanceled event will be sent and the page won't be unloaded. "
      },
      {
        "name": "onSuspendCanceled",
        "type": "function",
        "description": "Sent after onSuspend to indicate that the app won't be unloaded after all."
      },
      {
        "name": "onUpdateAvailable",
        "type": "function",
        "description": "Fired when an update is available, but isn't installed immediately because the app is currently running. If you do nothing, the update will be installed the next time the background page gets unloaded, if you want it to be installed sooner you can explicitly call $(ref:runtime.reload). If your extension is using a persistent background page, the background page of course never gets unloaded, so unless you call $(ref:runtime.reload) manually in response to this event the update will not get installed until the next time the browser itself restarts. If no handlers are listening for this event, and your extension has a persistent background page, it behaves as if $(ref:runtime.reload) is called in response to this event.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "version": {
                "type": "string",
                "description": "The version number of the available update."
              }
            },
            "additionalProperties": { "type": "any" },
            "description": "The manifest details of the available update."
          }
        ]
      },
      {
        "name": "onBrowserUpdateAvailable",
        "unsupported": true,
        "type": "function",
        "description": "Fired when an update for the browser is available, but isn't installed immediately because a browser restart is required.",
        "deprecated": "Please use $(ref:runtime.onRestartRequired).",
        "parameters": []
      },
      {
        "name": "onConnect",
        "type": "function",
        "allowedContexts": ["content", "devtools"],
        "description": "Fired when a connection is made from either an extension process or a content script.",
        "parameters": [{ "$ref": "Port", "name": "port" }]
      },
      {
        "name": "onConnectExternal",
        "type": "function",
        "description": "Fired when a connection is made from another extension.",
        "parameters": [{ "$ref": "Port", "name": "port" }]
      },
      {
        "name": "onMessage",
        "type": "function",
        "allowedContexts": ["content", "devtools"],
        "description": "Fired when a message is sent from either an extension process or a content script.",
        "parameters": [
          {
            "name": "message",
            "type": "any",
            "optional": true,
            "description": "The message sent by the calling script."
          },
          { "name": "sender", "$ref": "MessageSender" },
          {
            "name": "sendResponse",
            "type": "function",
            "description": "Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object. If you have more than one <code>onMessage</code> listener in the same document, then only one may send a response. This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until <code>sendResponse</code> is called)."
          }
        ],
        "returns": {
          "type": "boolean",
          "optional": true,
          "description": "Return true from the event listener if you wish to call <code>sendResponse</code> after the event listener returns."
        }
      },
      {
        "name": "onMessageExternal",
        "type": "function",
        "description": "Fired when a message is sent from another extension/app. Cannot be used in a content script.",
        "parameters": [
          {
            "name": "message",
            "type": "any",
            "optional": true,
            "description": "The message sent by the calling script."
          },
          { "name": "sender", "$ref": "MessageSender" },
          {
            "name": "sendResponse",
            "type": "function",
            "description": "Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object. If you have more than one <code>onMessage</code> listener in the same document, then only one may send a response. This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until <code>sendResponse</code> is called)."
          }
        ],
        "returns": {
          "type": "boolean",
          "optional": true,
          "description": "Return true from the event listener if you wish to call <code>sendResponse</code> after the event listener returns."
        }
      },
      {
        "name": "onRestartRequired",
        "unsupported": true,
        "type": "function",
        "description": "Fired when an app or the device that it runs on needs to be restarted. The app should close all its windows at its earliest convenient time to let the restart to happen. If the app does nothing, a restart will be enforced after a 24-hour grace period has passed. Currently, this event is only fired for Chrome OS kiosk apps.",
        "parameters": [
          {
            "$ref": "OnRestartRequiredReason",
            "name": "reason",
            "description": "The reason that the event is being dispatched."
          }
        ]
      },
      {
        "name": "onPerformanceWarning",
        "type": "function",
        "description": "Fired when a runtime performance issue is detected with the extension. Observe this event to be proactively notified of runtime performance problems with the extension.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "category": {
                "$ref": "OnPerformanceWarningCategory",
                "description": "The performance warning event category, e.g. 'content_script'."
              },
              "severity": {
                "$ref": "OnPerformanceWarningSeverity",
                "description": "The performance warning event severity, e.g. 'high'."
              },
              "tabId": {
                "type": "integer",
                "optional": true,
                "description": "The $(ref:tabs.Tab) that the performance warning relates to, if any."
              },
              "description": {
                "type": "string",
                "description": "An explanation of what the warning means, and hopefully how to address it."
              }
            }
          }
        ]
      }
    ]
  }
]
PK
!<C��Q�8�88chrome/toolkit/content/extensions/schemas/scripting.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "OptionalPermissionNoPrompt",
        "choices": [
          {
            "type": "string",
            "enum": ["scripting"]
          }
        ]
      }
    ]
  },
  {
    "namespace": "scripting",
    "description": "Use the scripting API to execute script in different contexts.",
    "permissions": ["scripting"],
    "types": [
      {
        "id": "ScriptInjection",
        "type": "object",
        "description": "Details of a script injection",
        "properties": {
          "args": {
            "type": "array",
            "optional": true,
            "description": "The arguments to curry into a provided function. This is only valid if the <code>func</code> parameter is specified. These arguments must be JSON-serializable.",
            "items": { "type": "any" }
          },
          "files": {
            "type": "array",
            "optional": true,
            "description": "The path of the JS files to inject, relative to the extension's root directory. Exactly one of <code>files</code> and <code>func</code> must be specified.",
            "minItems": 1,
            "items": { "type": "string" }
          },
          "func": {
            "type": "function",
            "optional": true,
            "description": "A JavaScript function to inject. This function will be serialized, and then deserialized for injection. This means that any bound parameters and execution context will be lost. Exactly one of <code>files</code> and <code>func</code> must be specified."
          },
          "target": {
            "$ref": "InjectionTarget",
            "description": "Details specifying the target into which to inject the script."
          },
          "world": {
            "$ref": "ExecutionWorld",
            "optional": true
          },
          "injectImmediately": {
            "type": "boolean",
            "optional": true,
            "description": "Whether the injection should be triggered in the target as soon as possible (but not necessarily prior to page load)."
          }
        }
      },
      {
        "id": "InjectionResult",
        "type": "object",
        "description": "Result of a script injection.",
        "properties": {
          "frameId": {
            "type": "integer",
            "description": "The frame ID associated with the injection."
          },
          "result": {
            "type": "any",
            "optional": true,
            "description": "The result of the script execution."
          },
          "error": {
            "type": "any",
            "optional": true,
            "description": "The error property is set when the script execution failed. The value is typically an (Error) object with a message property, but could be any value (including primitives and undefined) if the script threw or rejected with such a value."
          }
        }
      },
      {
        "id": "InjectionTarget",
        "type": "object",
        "properties": {
          "frameIds": {
            "type": "array",
            "optional": true,
            "description": "The IDs of specific frames to inject into.",
            "items": { "type": "number" }
          },
          "allFrames": {
            "type": "boolean",
            "optional": true,
            "description": "Whether the script should inject into all frames within the tab. Defaults to false. This must not be true if <code>frameIds</code> is specified."
          },
          "tabId": {
            "type": "number",
            "description": "The ID of the tab into which to inject."
          }
        }
      },
      {
        "id": "CSSInjection",
        "type": "object",
        "properties": {
          "css": {
            "type": "string",
            "optional": true,
            "description": "A string containing the CSS to inject. Exactly one of <code>files</code> and <code>css</code> must be specified."
          },
          "files": {
            "type": "array",
            "optional": true,
            "description": "The path of the CSS files to inject, relative to the extension's root directory. Exactly one of <code>files</code> and <code>css</code> must be specified.",
            "minItems": 1,
            "items": { "type": "string" }
          },
          "origin": {
            "type": "string",
            "optional": true,
            "enum": ["USER", "AUTHOR"],
            "default": "AUTHOR",
            "description": "The style origin for the injection. Defaults to <code>'AUTHOR'</code>."
          },
          "target": {
            "$ref": "InjectionTarget",
            "description": "Details specifying the target into which to inject the CSS."
          }
        }
      },
      {
        "id": "ContentScriptFilter",
        "type": "object",
        "properties": {
          "ids": {
            "type": "array",
            "optional": true,
            "description": "The IDs of specific scripts to retrieve with <code>getRegisteredContentScripts()</code> or to unregister with <code>unregisterContentScripts()</code>.",
            "items": { "type": "string" }
          }
        }
      },
      {
        "id": "ExecutionWorld",
        "type": "string",
        "enum": ["ISOLATED", "MAIN"],
        "description": "The JavaScript world for a script to execute within. <code>ISOLATED</code> is the default execution environment of content scripts, <code>MAIN</code> is the web page's execution environment."
      },
      {
        "id": "RegisteredContentScript",
        "type": "object",
        "properties": {
          "allFrames": {
            "type": "boolean",
            "optional": true,
            "description": "If specified true, it will inject into all frames, even if the frame is not the top-most frame in the tab. Each frame is checked independently for URL requirements; it will not inject into child frames if the URL requirements are not met. Defaults to false, meaning that only the top frame is matched."
          },
          "excludeMatches": {
            "type": "array",
            "optional": true,
            "description": "Excludes pages that this content script would otherwise be injected into.",
            "items": { "type": "string" }
          },
          "id": {
            "type": "string",
            "description": "The id of the content script, specified in the API call."
          },
          "js": {
            "type": "array",
            "optional": true,
            "description": "The list of JavaScript files to be injected into matching pages. These are injected in the order they appear in this array.",
            "items": { "$ref": "manifest.ExtensionURL" }
          },
          "matches": {
            "type": "array",
            "optional": true,
            "description": "Specifies which pages this content script will be injected into. Must be specified for <code>registerContentScripts()</code>.",
            "items": { "type": "string" }
          },
          "matchOriginAsFallback": {
            "type": "boolean",
            "optional": true,
            "description": "If matchOriginAsFallback is true, then the code is also injected in about:, data:, blob: when their origin matches the pattern in 'matches', even if the actual document origin is opaque (due to the use of CSP sandbox or iframe sandbox). Match patterns in 'matches' must specify a wildcard path glob. By default it is <code>false</code>."
          },
          "runAt": {
            "$ref": "extensionTypes.RunAt",
            "optional": true,
            "description": "Specifies when JavaScript files are injected into the web page. The preferred and default value is <code>document_idle</code>."
          },
          "world": {
            "$ref": "ExecutionWorld",
            "optional": true,
            "description": "The JavaScript world for a script to execute within. Defaults to \"ISOLATED\"."
          },
          "persistAcrossSessions": {
            "type": "boolean",
            "optional": true,
            "default": true,
            "description": "Specifies if this content script will persist into future sessions. Defaults to true."
          },
          "css": {
            "type": "array",
            "optional": true,
            "description": "The list of CSS files to be injected into matching pages. These are injected in the order they appear in this array.",
            "items": { "$ref": "manifest.ExtensionURL" }
          }
        }
      }
    ],
    "functions": [
      {
        "name": "executeScript",
        "type": "function",
        "description": "Injects a script into a target context. The script will be run at <code>document_idle</code>.",
        "async": "callback",
        "parameters": [
          {
            "name": "injection",
            "$ref": "ScriptInjection",
            "description": "The details of the script which to inject."
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Invoked upon completion of the injection. The resulting array contains the result of execution for each frame where the injection succeeded.",
            "parameters": [
              {
                "name": "results",
                "type": "array",
                "items": { "$ref": "InjectionResult" }
              }
            ]
          }
        ]
      },
      {
        "name": "insertCSS",
        "type": "function",
        "description": "Inserts a CSS stylesheet into a target context. If multiple frames are specified, unsuccessful injections are ignored.",
        "async": "callback",
        "parameters": [
          {
            "name": "injection",
            "$ref": "CSSInjection",
            "description": "The details of the styles to insert."
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Invoked upon completion of the injection.",
            "parameters": []
          }
        ]
      },
      {
        "name": "removeCSS",
        "type": "function",
        "description": "Removes a CSS stylesheet that was previously inserted by this extension from a target context.",
        "async": "callback",
        "parameters": [
          {
            "name": "injection",
            "$ref": "CSSInjection",
            "description": "The details of the styles to remove. Note that the <code>css</code>, <code>files</code>, and <code>origin</code> properties must exactly match the stylesheet inserted through <code>insertCSS</code>. Attempting to remove a non-existent stylesheet is a no-op."
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Invoked upon completion of the injection.",
            "parameters": []
          }
        ]
      },
      {
        "name": "registerContentScripts",
        "type": "function",
        "description": "Registers one or more content scripts for this extension.",
        "async": "callback",
        "parameters": [
          {
            "name": "scripts",
            "type": "array",
            "description": "Contains a list of scripts to be registered. If there are errors during script parsing/file validation, or if the IDs specified already exist, then no scripts are registered.",
            "items": { "$ref": "RegisteredContentScript" }
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Invoked upon completion of the registration.",
            "parameters": []
          }
        ]
      },
      {
        "name": "getRegisteredContentScripts",
        "type": "function",
        "description": "Returns all dynamically registered content scripts for this extension that match the given filter.",
        "async": "callback",
        "parameters": [
          {
            "name": "filter",
            "$ref": "ContentScriptFilter",
            "optional": true,
            "description": "An object to filter the extension's dynamically registered scripts."
          },
          {
            "name": "callback",
            "type": "function",
            "description": "The resulting array contains the registered content scripts.",
            "parameters": [
              {
                "name": "scripts",
                "type": "array",
                "items": { "$ref": "RegisteredContentScript" }
              }
            ]
          }
        ]
      },
      {
        "name": "unregisterContentScripts",
        "type": "function",
        "description": "Unregisters one or more content scripts for this extension.",
        "async": "callback",
        "parameters": [
          {
            "name": "filter",
            "$ref": "ContentScriptFilter",
            "optional": true,
            "description": "If specified, only unregisters dynamic content scripts which match the filter. Otherwise, all of the extension's dynamic content scripts are unregistered."
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Invoked upon completion of the unregistration.",
            "parameters": []
          }
        ]
      },
      {
        "name": "updateContentScripts",
        "type": "function",
        "description": "Updates one or more content scripts for this extension.",
        "async": "callback",
        "parameters": [
          {
            "name": "scripts",
            "type": "array",
            "description": "Contains a list of scripts to be updated. If there are errors during script parsing/file validation, or if the IDs specified do not already exist, then no scripts are updated.",
            "items": {
              "type": "object",
              "$import": "RegisteredContentScript",
              "properties": {
                "persistAcrossSessions": {
                  "type": "boolean",
                  "optional": true,
                  "description": "Specifies if this content script will persist into future sessions."
                }
              }
            }
          },
          {
            "name": "callback",
            "type": "function",
            "description": "Invoked when scripts have been updated.",
            "parameters": []
          }
        ]
      }
    ]
  }
]
PK
!<�L=ӸD�D6chrome/toolkit/content/extensions/schemas/storage.json[
  {
    "namespace": "storage",
    "allowedContexts": ["content", "devtools"],
    "defaultContexts": ["content", "devtools"],
    "description": "Use the <code>browser.storage</code> API to store, retrieve, and track changes to user data.",
    "permissions": ["storage"],
    "types": [
      {
        "id": "StorageChange",
        "type": "object",
        "properties": {
          "oldValue": {
            "type": "any",
            "description": "The old value of the item, if there was an old value.",
            "optional": true
          },
          "newValue": {
            "type": "any",
            "description": "The new value of the item, if there is a new value.",
            "optional": true
          }
        }
      },
      {
        "id": "StorageArea",
        "type": "object",
        "functions": [
          {
            "name": "get",
            "type": "function",
            "description": "Gets one or more items from storage.",
            "async": "callback",
            "parameters": [
              {
                "name": "keys",
                "choices": [
                  { "type": "string" },
                  { "type": "array", "items": { "type": "string" } },
                  {
                    "type": "object",
                    "description": "Storage items to return in the callback, where the values are replaced with those from storage if they exist.",
                    "additionalProperties": { "type": "any" }
                  }
                ],
                "description": "A single key to get, list of keys to get, or a dictionary specifying default values (see description of the object).  An empty list or object will return an empty result object.  Pass in <code>null</code> to get the entire contents of storage.",
                "optional": true
              },
              {
                "name": "callback",
                "type": "function",
                "description": "Callback with storage items, or on failure (in which case $(ref:runtime.lastError) will be set).",
                "parameters": [
                  {
                    "name": "items",
                    "type": "object",
                    "additionalProperties": { "type": "any" },
                    "description": "Object with items in their key-value mappings."
                  }
                ]
              }
            ]
          },
          {
            "name": "getBytesInUse",
            "unsupported": true,
            "type": "function",
            "description": "Gets the amount of space (in bytes) being used by one or more items.",
            "async": "callback",
            "parameters": [
              {
                "name": "keys",
                "choices": [
                  { "type": "string" },
                  { "type": "array", "items": { "type": "string" } }
                ],
                "description": "A single key or list of keys to get the total usage for. An empty list will return 0. Pass in <code>null</code> to get the total usage of all of storage.",
                "optional": true
              },
              {
                "name": "callback",
                "type": "function",
                "description": "Callback with the amount of space being used by storage, or on failure (in which case $(ref:runtime.lastError) will be set).",
                "parameters": [
                  {
                    "name": "bytesInUse",
                    "type": "integer",
                    "description": "Amount of space being used in storage, in bytes."
                  }
                ]
              }
            ]
          },
          {
            "name": "set",
            "type": "function",
            "description": "Sets multiple items.",
            "async": "callback",
            "parameters": [
              {
                "name": "items",
                "type": "object",
                "additionalProperties": { "type": "any" },
                "description": "<p>An object which gives each key/value pair to update storage with. Any other key/value pairs in storage will not be affected.</p><p>Primitive values such as numbers will serialize as expected. Values with a <code>typeof</code> <code>\"object\"</code> and <code>\"function\"</code> will typically serialize to <code>{}</code>, with the exception of <code>Array</code> (serializes as expected), <code>Date</code>, and <code>Regex</code> (serialize using their <code>String</code> representation).</p>"
              },
              {
                "name": "callback",
                "type": "function",
                "description": "Callback on success, or on failure (in which case $(ref:runtime.lastError) will be set).",
                "parameters": [],
                "optional": true
              }
            ]
          },
          {
            "name": "remove",
            "type": "function",
            "description": "Removes one or more items from storage.",
            "async": "callback",
            "parameters": [
              {
                "name": "keys",
                "choices": [
                  { "type": "string" },
                  { "type": "array", "items": { "type": "string" } }
                ],
                "description": "A single key or a list of keys for items to remove."
              },
              {
                "name": "callback",
                "type": "function",
                "description": "Callback on success, or on failure (in which case $(ref:runtime.lastError) will be set).",
                "parameters": [],
                "optional": true
              }
            ]
          },
          {
            "name": "clear",
            "type": "function",
            "description": "Removes all items from storage.",
            "async": "callback",
            "parameters": [
              {
                "name": "callback",
                "type": "function",
                "description": "Callback on success, or on failure (in which case $(ref:runtime.lastError) will be set).",
                "parameters": [],
                "optional": true
              }
            ]
          }
        ],
        "events": [
          {
            "name": "onChanged",
            "type": "function",
            "description": "Fired when one or more items change.",
            "parameters": [
              {
                "name": "changes",
                "type": "object",
                "additionalProperties": { "$ref": "StorageChange" },
                "description": "Object mapping each key that changed to its corresponding $(ref:storage.StorageChange) for that item."
              }
            ]
          }
        ]
      },
      {
        "id": "StorageAreaWithUsage",
        "type": "object",
        "functions": [
          {
            "name": "get",
            "type": "function",
            "description": "Gets one or more items from storage.",
            "async": "callback",
            "parameters": [
              {
                "name": "keys",
                "choices": [
                  { "type": "string" },
                  { "type": "array", "items": { "type": "string" } },
                  {
                    "type": "object",
                    "description": "Storage items to return in the callback, where the values are replaced with those from storage if they exist.",
                    "additionalProperties": { "type": "any" }
                  }
                ],
                "description": "A single key to get, list of keys to get, or a dictionary specifying default values (see description of the object).  An empty list or object will return an empty result object.  Pass in <code>null</code> to get the entire contents of storage.",
                "optional": true
              },
              {
                "name": "callback",
                "type": "function",
                "description": "Callback with storage items, or on failure (in which case $(ref:runtime.lastError) will be set).",
                "parameters": [
                  {
                    "name": "items",
                    "type": "object",
                    "additionalProperties": { "type": "any" },
                    "description": "Object with items in their key-value mappings."
                  }
                ]
              }
            ]
          },
          {
            "name": "getBytesInUse",
            "type": "function",
            "description": "Gets the amount of space (in bytes) being used by one or more items.",
            "async": "callback",
            "parameters": [
              {
                "name": "keys",
                "choices": [
                  { "type": "string" },
                  { "type": "array", "items": { "type": "string" } }
                ],
                "description": "A single key or list of keys to get the total usage for. An empty list will return 0. Pass in <code>null</code> to get the total usage of all of storage.",
                "optional": true
              },
              {
                "name": "callback",
                "type": "function",
                "description": "Callback with the amount of space being used by storage, or on failure (in which case $(ref:runtime.lastError) will be set).",
                "parameters": [
                  {
                    "name": "bytesInUse",
                    "type": "integer",
                    "description": "Amount of space being used in storage, in bytes."
                  }
                ]
              }
            ]
          },
          {
            "name": "set",
            "type": "function",
            "description": "Sets multiple items.",
            "async": "callback",
            "parameters": [
              {
                "name": "items",
                "type": "object",
                "additionalProperties": { "type": "any" },
                "description": "<p>An object which gives each key/value pair to update storage with. Any other key/value pairs in storage will not be affected.</p><p>Primitive values such as numbers will serialize as expected. Values with a <code>typeof</code> <code>\"object\"</code> and <code>\"function\"</code> will typically serialize to <code>{}</code>, with the exception of <code>Array</code> (serializes as expected), <code>Date</code>, and <code>Regex</code> (serialize using their <code>String</code> representation).</p>"
              },
              {
                "name": "callback",
                "type": "function",
                "description": "Callback on success, or on failure (in which case $(ref:runtime.lastError) will be set).",
                "parameters": [],
                "optional": true
              }
            ]
          },
          {
            "name": "remove",
            "type": "function",
            "description": "Removes one or more items from storage.",
            "async": "callback",
            "parameters": [
              {
                "name": "keys",
                "choices": [
                  { "type": "string" },
                  { "type": "array", "items": { "type": "string" } }
                ],
                "description": "A single key or a list of keys for items to remove."
              },
              {
                "name": "callback",
                "type": "function",
                "description": "Callback on success, or on failure (in which case $(ref:runtime.lastError) will be set).",
                "parameters": [],
                "optional": true
              }
            ]
          },
          {
            "name": "clear",
            "type": "function",
            "description": "Removes all items from storage.",
            "async": "callback",
            "parameters": [
              {
                "name": "callback",
                "type": "function",
                "description": "Callback on success, or on failure (in which case $(ref:runtime.lastError) will be set).",
                "parameters": [],
                "optional": true
              }
            ]
          }
        ],
        "events": [
          {
            "name": "onChanged",
            "type": "function",
            "description": "Fired when one or more items change.",
            "parameters": [
              {
                "name": "changes",
                "type": "object",
                "additionalProperties": { "$ref": "StorageChange" },
                "description": "Object mapping each key that changed to its corresponding $(ref:storage.StorageChange) for that item."
              }
            ]
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onChanged",
        "type": "function",
        "description": "Fired when one or more items change.",
        "parameters": [
          {
            "name": "changes",
            "type": "object",
            "additionalProperties": { "$ref": "StorageChange" },
            "description": "Object mapping each key that changed to its corresponding $(ref:storage.StorageChange) for that item."
          },
          {
            "name": "areaName",
            "type": "string",
            "description": "The name of the storage area (<code>\"sync\"</code>, <code>\"local\"</code> or <code>\"managed\"</code>) the changes are for."
          }
        ]
      }
    ],
    "properties": {
      "sync": {
        "$ref": "StorageAreaWithUsage",
        "description": "Items in the <code>sync</code> storage area are synced by the browser.",
        "properties": {
          "QUOTA_BYTES": {
            "value": 102400,
            "description": "The maximum total amount (in bytes) of data that can be stored in sync storage, as measured by the JSON stringification of every value plus every key's length. Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError)."
          },
          "QUOTA_BYTES_PER_ITEM": {
            "value": 8192,
            "description": "The maximum size (in bytes) of each individual item in sync storage, as measured by the JSON stringification of its value plus its key length. Updates containing items larger than this limit will fail immediately and set $(ref:runtime.lastError)."
          },
          "MAX_ITEMS": {
            "value": 512,
            "description": "The maximum number of items that can be stored in sync storage. Updates that would cause this limit to be exceeded will fail immediately and set $(ref:runtime.lastError)."
          },
          "MAX_WRITE_OPERATIONS_PER_HOUR": {
            "value": 1800,
            "description": "<p>The maximum number of <code>set</code>, <code>remove</code>, or <code>clear</code> operations that can be performed each hour. This is 1 every 2 seconds, a lower ceiling than the short term higher writes-per-minute limit.</p><p>Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError).</p>"
          },
          "MAX_WRITE_OPERATIONS_PER_MINUTE": {
            "value": 120,
            "description": "<p>The maximum number of <code>set</code>, <code>remove</code>, or <code>clear</code> operations that can be performed each minute. This is 2 per second, providing higher throughput than writes-per-hour over a shorter period of time.</p><p>Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError).</p>"
          },
          "MAX_SUSTAINED_WRITE_OPERATIONS_PER_MINUTE": {
            "value": 1000000,
            "deprecated": "The storage.sync API no longer has a sustained write operation quota.",
            "description": ""
          }
        }
      },
      "local": {
        "$ref": "StorageArea",
        "description": "Items in the <code>local</code> storage area are local to each machine.",
        "properties": {
          "QUOTA_BYTES": {
            "value": 5242880,
            "description": "The maximum amount (in bytes) of data that can be stored in local storage, as measured by the JSON stringification of every value plus every key's length. This value will be ignored if the extension has the <code>unlimitedStorage</code> permission. Updates that would cause this limit to be exceeded fail immediately and set $(ref:runtime.lastError)."
          }
        }
      },
      "managed": {
        "$ref": "StorageArea",
        "description": "Items in the <code>managed</code> storage area are set by administrators or native applications, and are read-only for the extension; trying to modify this namespace results in an error.",
        "properties": {
          "QUOTA_BYTES": {
            "value": 5242880,
            "description": "The maximum size (in bytes) of the managed storage JSON manifest file. Files larger than this limit will fail to load."
          }
        }
      },
      "session": {
        "allowedContexts": ["devtools"],
        "$ref": "StorageAreaWithUsage",
        "description": "Items in the <code>session</code> storage area are kept in memory, and only until the either browser or extension is closed or reloaded.",
        "properties": {
          "QUOTA_BYTES": {
            "description": "The maximum amount of data (in bytes, currently at 10MB) that can be stored in session storage, as measured by the StructuredCloneHolder of every value plus every key's length."
          }
        }
      }
    }
  }
]
PK
!<�Mx��:�:8chrome/toolkit/content/extensions/schemas/telemetry.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "WebExtensionManifest",
        "properties": {
          "telemetry": {
            "type": "object",
            "optional": true,
            "additionalProperties": { "$ref": "UnrecognizedProperty" },
            "properties": {
              "ping_type": {
                "type": "string"
              },
              "schemaNamespace": {
                "type": "string"
              },
              "public_key": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "key": {
                    "type": "object",
                    "properties": {
                      "crv": {
                        "type": "string",
                        "optional": "false"
                      },
                      "kty": {
                        "type": "string",
                        "optional": "false"
                      },
                      "x": {
                        "type": "string",
                        "optional": "false"
                      },
                      "y": {
                        "type": "string",
                        "optional": "false"
                      }
                    }
                  }
                }
              },
              "study_name": {
                "type": "string",
                "optional": true
              },
              "pioneer_id": {
                "type": "boolean",
                "optional": true,
                "default": false
              }
            }
          }
        }
      },
      {
        "$extend": "PermissionPrivileged",
        "choices": [
          {
            "type": "string",
            "enum": ["telemetry"]
          }
        ]
      }
    ]
  },
  {
    "namespace": "telemetry",
    "description": "Use the <code>browser.telemetry</code> API to send telemetry data to the Mozilla Telemetry service. Restricted to Mozilla privileged webextensions.",
    "types": [
      {
        "id": "ScalarType",
        "type": "string",
        "enum": ["count", "string", "boolean"],
        "description": "Type of scalar: 'count' for numeric values, 'string' for string values, 'boolean' for boolean values. Maps to <code>nsITelemetry.SCALAR_TYPE_*</code>."
      },
      {
        "id": "ScalarData",
        "type": "object",
        "description": "Represents registration data for a Telemetry scalar.",
        "properties": {
          "kind": {
            "$ref": "ScalarType"
          },
          "keyed": {
            "type": "boolean",
            "optional": true,
            "default": false,
            "description": "True if this is a keyed scalar."
          },
          "record_on_release": {
            "type": "boolean",
            "optional": true,
            "default": false,
            "description": "True if this data should be recorded on release."
          },
          "expired": {
            "type": "boolean",
            "optional": true,
            "default": false,
            "description": "True if this scalar entry is expired. This allows recording it without error, but it will be discarded."
          }
        }
      },
      {
        "id": "EventData",
        "type": "object",
        "description": "Represents registration data for a Telemetry event.",
        "properties": {
          "methods": {
            "type": "array",
            "items": { "type": "string" },
            "description": "List of methods for this event entry."
          },
          "objects": {
            "type": "array",
            "items": { "type": "string" },
            "description": "List of objects for this event entry."
          },
          "extra_keys": {
            "type": "array",
            "items": { "type": "string" },
            "description": "List of allowed extra keys for this event entry."
          },
          "record_on_release": {
            "type": "boolean",
            "optional": true,
            "default": false,
            "description": "True if this data should be recorded on release."
          },
          "expired": {
            "type": "boolean",
            "optional": true,
            "default": false,
            "description": "True if this event entry is expired. This allows recording it without error, but it will be discarded."
          }
        }
      }
    ],
    "permissions": ["telemetry"],
    "functions": [
      {
        "name": "submitPing",
        "type": "function",
        "description": "Submits a custom ping to the Telemetry back-end. See <code>submitExternalPing</code> inside TelemetryController.sys.mjs for more details.",
        "async": true,
        "parameters": [
          {
            "name": "type",
            "type": "string",
            "pattern": "^[a-z0-9][a-z0-9-]+[a-z0-9]$",
            "description": "The type of the ping."
          },
          {
            "name": "message",
            "type": "object",
            "additionalProperties": { "type": "any" },
            "description": "The data payload for the ping."
          },
          {
            "description": "Options object.",
            "name": "options",
            "type": "object",
            "properties": {
              "addClientId": {
                "type": "boolean",
                "optional": true,
                "default": false,
                "description": "True if the ping should contain the client id."
              },
              "addEnvironment": {
                "type": "boolean",
                "optional": true,
                "default": false,
                "description": "True if the ping should contain the environment data."
              },
              "overrideEnvironment": {
                "type": "object",
                "additionalProperties": { "type": "any" },
                "optional": true,
                "default": false,
                "description": "Set to override the environment data."
              },
              "usePingSender": {
                "type": "boolean",
                "optional": true,
                "default": false,
                "description": "If true, send the ping using the PingSender."
              }
            }
          }
        ]
      },
      {
        "name": "submitEncryptedPing",
        "type": "function",
        "description": "Submits a custom ping to the Telemetry back-end, with an encrypted payload. Requires a telemetry entry in the manifest to be used.",
        "parameters": [
          {
            "name": "message",
            "type": "object",
            "additionalProperties": { "type": "any" },
            "description": "The data payload for the ping, which will be encrypted."
          },
          {
            "description": "Options object.",
            "name": "options",
            "type": "object",
            "properties": {
              "schemaName": {
                "type": "string",
                "optional": false,
                "description": "Schema name used for payload."
              },
              "schemaVersion": {
                "type": "integer",
                "optional": false,
                "description": "Schema version used for payload."
              }
            }
          }
        ],
        "async": true
      },
      {
        "name": "canUpload",
        "type": "function",
        "description": "Checks if Telemetry upload is enabled.",
        "parameters": [],
        "async": true
      },
      {
        "name": "scalarAdd",
        "type": "function",
        "description": "Adds the value to the given scalar.",
        "async": true,
        "parameters": [
          {
            "name": "name",
            "type": "string",
            "description": "The scalar name."
          },
          {
            "name": "value",
            "type": "integer",
            "minimum": 1,
            "description": "The numeric value to add to the scalar. Only unsigned integers supported."
          }
        ]
      },
      {
        "name": "scalarSet",
        "type": "function",
        "description": "Sets the named scalar to the given value. Throws if the value type doesn't match the scalar type.",
        "async": true,
        "parameters": [
          {
            "name": "name",
            "type": "string",
            "description": "The scalar name"
          },
          {
            "name": "value",
            "description": "The value to set the scalar to",
            "choices": [
              { "type": "string" },
              { "type": "boolean" },
              { "type": "integer" },
              { "type": "object", "additionalProperties": { "type": "any" } }
            ]
          }
        ]
      },
      {
        "name": "scalarSetMaximum",
        "type": "function",
        "description": "Sets the scalar to the maximum of the current and the passed value",
        "async": true,
        "parameters": [
          {
            "name": "name",
            "type": "string",
            "description": "The scalar name."
          },
          {
            "name": "value",
            "type": "integer",
            "minimum": 0,
            "description": "The numeric value to set the scalar to. Only unsigned integers supported."
          }
        ]
      },
      {
        "name": "keyedScalarAdd",
        "type": "function",
        "description": "Adds the value to the given keyed scalar.",
        "async": true,
        "parameters": [
          {
            "name": "name",
            "type": "string",
            "description": "The scalar name"
          },
          {
            "name": "key",
            "type": "string",
            "description": "The key name"
          },
          {
            "name": "value",
            "type": "integer",
            "minimum": 1,
            "description": "The numeric value to add to the scalar. Only unsigned integers supported."
          }
        ]
      },
      {
        "name": "keyedScalarSet",
        "type": "function",
        "description": "Sets the keyed scalar to the given value. Throws if the value type doesn't match the scalar type.",
        "async": true,
        "parameters": [
          {
            "name": "name",
            "type": "string",
            "description": "The scalar name."
          },
          {
            "name": "key",
            "type": "string",
            "description": "The key name."
          },
          {
            "name": "value",
            "description": "The value to set the scalar to.",
            "choices": [
              { "type": "string" },
              { "type": "boolean" },
              { "type": "integer" },
              { "type": "object", "additionalProperties": { "type": "any" } }
            ]
          }
        ]
      },
      {
        "name": "keyedScalarSetMaximum",
        "type": "function",
        "description": "Sets the keyed scalar to the maximum of the current and the passed value",
        "async": true,
        "parameters": [
          {
            "name": "name",
            "type": "string",
            "description": "The scalar name."
          },
          {
            "name": "key",
            "type": "string",
            "description": "The key name."
          },
          {
            "name": "value",
            "type": "integer",
            "minimum": 0,
            "description": "The numeric value to set the scalar to. Only unsigned integers supported."
          }
        ]
      },
      {
        "name": "recordEvent",
        "type": "function",
        "description": "Record an event in Telemetry. Throws when trying to record an unknown event.",
        "async": true,
        "parameters": [
          {
            "name": "category",
            "type": "string",
            "description": "The category name."
          },
          {
            "name": "method",
            "type": "string",
            "description": "The method name."
          },
          {
            "name": "object",
            "type": "string",
            "description": "The object name."
          },
          {
            "name": "value",
            "type": "string",
            "optional": true,
            "description": "An optional string value to record."
          },
          {
            "name": "extra",
            "type": "object",
            "optional": true,
            "description": "An optional object of the form (string -> string). It should only contain registered extra keys.",
            "additionalProperties": { "type": "string" }
          }
        ]
      },

      {
        "name": "registerScalars",
        "type": "function",
        "description": "Register new scalars to record them from addons. See nsITelemetry.idl for more details.",
        "async": true,
        "parameters": [
          {
            "name": "category",
            "type": "string",
            "description": "The unique category the scalars are registered in."
          },
          {
            "name": "data",
            "type": "object",
            "additionalProperties": { "$ref": "ScalarData" },
            "description": "An object that contains registration data for multiple scalars. Each property name is the scalar name, and the corresponding property value is an object of ScalarData type."
          }
        ]
      },
      {
        "name": "registerEvents",
        "type": "function",
        "description": "Register new events to record them from addons. See nsITelemetry.idl for more details.",
        "async": true,
        "parameters": [
          {
            "name": "category",
            "type": "string",
            "description": "The unique category the events are registered in."
          },
          {
            "name": "data",
            "type": "object",
            "additionalProperties": { "$ref": "EventData" },
            "description": "An object that contains registration data for 1+ events. Each property name is the category name, and the corresponding property value is an object of EventData type."
          }
        ]
      },
      {
        "name": "setEventRecordingEnabled",
        "type": "function",
        "description": "Enable recording of events in a category. Events default to recording disabled. This allows to toggle recording for all events in the specified category.",
        "async": true,
        "parameters": [
          {
            "name": "category",
            "type": "string",
            "description": "The category name."
          },
          {
            "name": "enabled",
            "type": "boolean",
            "description": "Whether recording is enabled for events in that category."
          }
        ]
      }
    ]
  }
]
PK
!<�I���3chrome/toolkit/content/extensions/schemas/test.json[
  {
    "namespace": "test",
    "allowedContexts": ["content", "devtools"],
    "defaultContexts": ["content", "devtools"],
    "description": "none",
    "functions": [
      {
        "name": "withHandlingUserInput",
        "type": "function",
        "description": "Calls the callback function wrapped with user input set.  This is only used for internal unit testing.",
        "parameters": [{ "type": "function", "name": "callback" }]
      },
      {
        "name": "notifyFail",
        "type": "function",
        "description": "Notifies the browser process that test code running in the extension failed.  This is only used for internal unit testing.",
        "parameters": [{ "type": "string", "name": "message" }]
      },
      {
        "name": "notifyPass",
        "type": "function",
        "description": "Notifies the browser process that test code running in the extension passed.  This is only used for internal unit testing.",
        "parameters": [
          { "type": "string", "name": "message", "optional": true }
        ]
      },
      {
        "name": "log",
        "type": "function",
        "description": "Logs a message during internal unit testing.",
        "parameters": [{ "type": "string", "name": "message" }]
      },
      {
        "name": "sendMessage",
        "type": "function",
        "description": "Sends a string message to the browser process, generating a Notification that C++ test code can wait for.",
        "allowAmbiguousOptionalArguments": true,
        "parameters": [
          { "type": "any", "name": "arg1", "optional": true },
          { "type": "any", "name": "arg2", "optional": true }
        ]
      },
      {
        "name": "fail",
        "type": "function",
        "parameters": [{ "type": "any", "name": "message", "optional": true }]
      },
      {
        "name": "succeed",
        "type": "function",
        "parameters": [{ "type": "any", "name": "message", "optional": true }]
      },
      {
        "name": "assertTrue",
        "type": "function",
        "allowAmbiguousOptionalArguments": true,
        "parameters": [
          { "name": "test", "type": "any", "optional": true },
          { "type": "string", "name": "message", "optional": true }
        ]
      },
      {
        "name": "assertFalse",
        "type": "function",
        "allowAmbiguousOptionalArguments": true,
        "parameters": [
          { "name": "test", "type": "any", "optional": true },
          { "type": "string", "name": "message", "optional": true }
        ]
      },
      {
        "name": "assertBool",
        "type": "function",
        "unsupported": true,
        "parameters": [
          {
            "name": "test",
            "choices": [{ "type": "string" }, { "type": "boolean" }]
          },
          { "type": "boolean", "name": "expected" },
          { "type": "string", "name": "message", "optional": true }
        ]
      },
      {
        "name": "assertDeepEq",
        "type": "function",
        "allowAmbiguousOptionalArguments": true,
        "parameters": [
          { "type": "any", "name": "expected" },
          { "type": "any", "name": "actual" },
          { "type": "string", "name": "message", "optional": true }
        ]
      },
      {
        "name": "assertEq",
        "type": "function",
        "allowAmbiguousOptionalArguments": true,
        "parameters": [
          { "type": "any", "name": "expected", "optional": true },
          { "type": "any", "name": "actual", "optional": true },
          { "type": "string", "name": "message", "optional": true }
        ]
      },
      {
        "name": "assertNoLastError",
        "type": "function",
        "unsupported": true,
        "parameters": []
      },
      {
        "name": "assertLastError",
        "type": "function",
        "unsupported": true,
        "parameters": [{ "type": "string", "name": "expectedError" }]
      },
      {
        "name": "assertRejects",
        "type": "function",
        "async": true,
        "parameters": [
          {
            "name": "promise",
            "$ref": "Promise"
          },
          {
            "name": "expectedError",
            "$ref": "ExpectedError"
          },
          {
            "name": "message",
            "type": "string",
            "optional": true
          }
        ]
      },
      {
        "name": "assertThrows",
        "type": "function",
        "parameters": [
          {
            "name": "func",
            "type": "function"
          },
          {
            "name": "expectedError",
            "$ref": "ExpectedError"
          },
          {
            "name": "message",
            "type": "string",
            "optional": true
          }
        ]
      }
    ],
    "types": [
      {
        "id": "ExpectedError",
        "choices": [
          { "type": "string" },
          {
            "type": "object",
            "isInstanceOf": "RegExp",
            "additionalProperties": true
          },
          { "type": "function" }
        ]
      },
      {
        "id": "Promise",
        "choices": [
          {
            "type": "object",
            "properties": {
              "then": { "type": "function" }
            },
            "additionalProperties": true
          },
          {
            "type": "object",
            "isInstanceOf": "Promise",
            "additionalProperties": true
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onMessage",
        "type": "function",
        "description": "Used to test sending messages to extensions.",
        "parameters": [
          {
            "type": "string",
            "name": "message"
          },
          {
            "type": "any",
            "name": "argument"
          }
        ]
      }
    ]
  }
]
PK
!<��E��4�44chrome/toolkit/content/extensions/schemas/theme.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "PermissionNoPrompt",
        "choices": [
          {
            "type": "string",
            "enum": ["theme"]
          }
        ]
      },
      {
        "id": "ThemeColor",
        "choices": [
          {
            "type": "string"
          },
          {
            "type": "array",
            "minItems": 3,
            "maxItems": 3,
            "items": {
              "type": "integer",
              "minimum": 0,
              "maximum": 255
            }
          },
          {
            "type": "array",
            "minItems": 4,
            "maxItems": 4,
            "items": {
              "type": "number"
            }
          }
        ]
      },
      {
        "id": "ThemeExperiment",
        "type": "object",
        "properties": {
          "stylesheet": {
            "optional": true,
            "$ref": "ExtensionURL"
          },
          "images": {
            "type": "object",
            "optional": true,
            "additionalProperties": {
              "type": "string"
            }
          },
          "colors": {
            "type": "object",
            "optional": true,
            "additionalProperties": {
              "type": "string"
            }
          },
          "properties": {
            "type": "object",
            "optional": true,
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      {
        "id": "ThemeType",
        "type": "object",
        "properties": {
          "images": {
            "type": "object",
            "optional": true,
            "properties": {
              "additional_backgrounds": {
                "type": "array",
                "items": { "$ref": "ImageDataOrExtensionURL" },
                "maxItems": 15,
                "optional": true
              },
              "headerURL": {
                "$ref": "ImageDataOrExtensionURL",
                "optional": true,
                "deprecated": "Unsupported images property, use 'theme.images.theme_frame', this alias is ignored in Firefox >= 70."
              },
              "theme_frame": {
                "$ref": "ImageDataOrExtensionURL",
                "optional": true
              }
            },
            "additionalProperties": { "$ref": "ImageDataOrExtensionURL" }
          },
          "colors": {
            "type": "object",
            "optional": true,
            "properties": {
              "tab_selected": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "accentcolor": {
                "$ref": "ThemeColor",
                "optional": true,
                "deprecated": "Unsupported colors property, use 'theme.colors.frame', this alias is ignored in Firefox >= 70."
              },
              "frame": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "frame_inactive": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "textcolor": {
                "$ref": "ThemeColor",
                "optional": true,
                "deprecated": "Unsupported color property, use 'theme.colors.tab_background_text', this alias is ignored in Firefox >= 70."
              },
              "tab_background_text": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "tab_background_separator": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "tab_loading": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "tab_text": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "tab_line": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "toolbar": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "toolbar_text": {
                "$ref": "ThemeColor",
                "optional": true,
                "description": "This color property is an alias of 'bookmark_text'."
              },
              "bookmark_text": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "toolbar_field": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "toolbar_field_text": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "toolbar_field_border": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "toolbar_field_separator": {
                "$ref": "ThemeColor",
                "optional": true,
                "deprecated": "This color property is ignored in Firefox >= 89."
              },
              "toolbar_top_separator": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "toolbar_bottom_separator": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "toolbar_vertical_separator": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "icons": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "icons_attention": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "button_background_hover": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "button_background_active": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "popup": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "popup_text": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "popup_border": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "toolbar_field_focus": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "toolbar_field_text_focus": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "toolbar_field_border_focus": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "popup_highlight": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "popup_highlight_text": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "ntp_background": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "ntp_card_background": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "ntp_text": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "sidebar": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "sidebar_border": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "sidebar_text": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "sidebar_highlight": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "sidebar_highlight_text": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "toolbar_field_highlight": {
                "$ref": "ThemeColor",
                "optional": true
              },
              "toolbar_field_highlight_text": {
                "$ref": "ThemeColor",
                "optional": true
              }
            },
            "additionalProperties": { "$ref": "ThemeColor" }
          },
          "properties": {
            "type": "object",
            "optional": true,
            "properties": {
              "additional_backgrounds_alignment": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "bottom",
                    "center",
                    "left",
                    "right",
                    "top",
                    "center bottom",
                    "center center",
                    "center top",
                    "left bottom",
                    "left center",
                    "left top",
                    "right bottom",
                    "right center",
                    "right top"
                  ]
                },
                "maxItems": 15,
                "optional": true
              },
              "additional_backgrounds_tiling": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": ["no-repeat", "repeat", "repeat-x", "repeat-y"]
                },
                "maxItems": 15,
                "optional": true
              },
              "color_scheme": {
                "optional": true,
                "type": "string",
                "enum": ["auto", "light", "dark", "system"]
              },
              "content_color_scheme": {
                "optional": true,
                "type": "string",
                "enum": ["auto", "light", "dark", "system"]
              }
            },
            "additionalProperties": { "type": "string" }
          }
        },
        "additionalProperties": { "$ref": "UnrecognizedProperty" }
      },
      {
        "id": "ThemeManifest",
        "type": "object",
        "description": "Contents of manifest.json for a static theme",
        "$import": "manifest.ManifestBase",
        "properties": {
          "theme": {
            "$ref": "ThemeType"
          },
          "dark_theme": {
            "$ref": "ThemeType",
            "optional": true
          },
          "default_locale": {
            "type": "string",
            "optional": true
          },
          "theme_experiment": {
            "$ref": "ThemeExperiment",
            "optional": true
          },
          "icons": {
            "type": "object",
            "optional": true,
            "patternProperties": {
              "^[1-9]\\d*$": { "type": "string" }
            }
          }
        }
      },
      {
        "$extend": "WebExtensionManifest",
        "properties": {
          "theme_experiment": {
            "$ref": "ThemeExperiment",
            "optional": true
          }
        }
      }
    ]
  },
  {
    "namespace": "theme",
    "description": "The theme API allows customizing of visual elements of the browser.",
    "types": [
      {
        "id": "ThemeUpdateInfo",
        "type": "object",
        "description": "Info provided in the onUpdated listener.",
        "properties": {
          "theme": {
            "type": "object",
            "description": "The new theme after update"
          },
          "windowId": {
            "type": "integer",
            "description": "The id of the window the theme has been applied to",
            "optional": true
          }
        }
      }
    ],
    "events": [
      {
        "name": "onUpdated",
        "type": "function",
        "description": "Fired when a new theme has been applied",
        "parameters": [
          {
            "$ref": "ThemeUpdateInfo",
            "name": "updateInfo",
            "description": "Details of the theme update"
          }
        ]
      }
    ],
    "functions": [
      {
        "name": "getCurrent",
        "type": "function",
        "async": true,
        "description": "Returns the current theme for the specified window or the last focused window.",
        "parameters": [
          {
            "type": "integer",
            "name": "windowId",
            "optional": true,
            "description": "The window for which we want the theme."
          }
        ]
      },
      {
        "name": "update",
        "type": "function",
        "async": true,
        "description": "Make complete updates to the theme. Resolves when the update has completed.",
        "permissions": ["theme"],
        "parameters": [
          {
            "type": "integer",
            "name": "windowId",
            "optional": true,
            "description": "The id of the window to update. No id updates all windows."
          },
          {
            "name": "details",
            "$ref": "manifest.ThemeType",
            "description": "The properties of the theme to update."
          }
        ]
      },
      {
        "name": "reset",
        "type": "function",
        "async": true,
        "description": "Removes the updates made to the theme.",
        "permissions": ["theme"],
        "parameters": [
          {
            "type": "integer",
            "name": "windowId",
            "optional": true,
            "description": "The id of the window to reset. No id resets all windows."
          }
        ]
      }
    ]
  }
]
PK
!<p�$�4chrome/toolkit/content/extensions/schemas/types.json[
  {
    "namespace": "types",
    "description": "Contains types used by other schemas.",
    "types": [
      {
        "id": "SettingScope",
        "type": "string",
        "enum": [
          "regular",
          "regular_only",
          "incognito_persistent",
          "incognito_session_only"
        ],
        "description": "The scope of the Setting. One of<ul><li><var>regular</var>: setting for the regular profile (which is inherited by the incognito profile if not overridden elsewhere),</li><li><var>regular_only</var>: setting for the regular profile only (not inherited by the incognito profile),</li><li><var>incognito_persistent</var>: setting for the incognito profile that survives browser restarts (overrides regular preferences),</li><li><var>incognito_session_only</var>: setting for the incognito profile that can only be set during an incognito session and is deleted when the incognito session ends (overrides regular and incognito_persistent preferences).</li></ul> Only <var>regular</var> is supported by Firefox at this time."
      },
      {
        "id": "LevelOfControl",
        "type": "string",
        "enum": [
          "not_controllable",
          "controlled_by_other_extensions",
          "controllable_by_this_extension",
          "controlled_by_this_extension"
        ],
        "description": "One of<ul><li><var>not_controllable</var>: cannot be controlled by any extension</li><li><var>controlled_by_other_extensions</var>: controlled by extensions with higher precedence</li><li><var>controllable_by_this_extension</var>: can be controlled by this extension</li><li><var>controlled_by_this_extension</var>: controlled by this extension</li></ul>"
      },
      {
        "id": "Setting",
        "type": "object",
        "functions": [
          {
            "name": "get",
            "type": "function",
            "description": "Gets the value of a setting.",
            "async": "callback",
            "parameters": [
              {
                "name": "details",
                "type": "object",
                "description": "Which setting to consider.",
                "properties": {
                  "incognito": {
                    "type": "boolean",
                    "optional": true,
                    "description": "Whether to return the value that applies to the incognito session (default false)."
                  }
                }
              },
              {
                "name": "callback",
                "type": "function",
                "parameters": [
                  {
                    "name": "details",
                    "type": "object",
                    "description": "Details of the currently effective value.",
                    "properties": {
                      "value": {
                        "description": "The value of the setting.",
                        "type": "any"
                      },
                      "levelOfControl": {
                        "$ref": "types.LevelOfControl",
                        "description": "The level of control of the setting."
                      },
                      "incognitoSpecific": {
                        "description": "Whether the effective value is specific to the incognito session.<br/>This property will <em>only</em> be present if the <var>incognito</var> property in the <var>details</var> parameter of <code>get()</code> was true.",
                        "type": "boolean",
                        "optional": true
                      }
                    }
                  }
                ]
              }
            ]
          },
          {
            "name": "set",
            "type": "function",
            "description": "Sets the value of a setting.",
            "async": "callback",
            "parameters": [
              {
                "name": "details",
                "type": "object",
                "description": "Which setting to change.",
                "properties": {
                  "value": {
                    "description": "The value of the setting. <br/>Note that every setting has a specific value type, which is described together with the setting. An extension should <em>not</em> set a value of a different type.",
                    "type": "any"
                  },
                  "scope": {
                    "$ref": "types.SettingScope",
                    "optional": true,
                    "description": "Where to set the setting (default: regular)."
                  }
                }
              },
              {
                "name": "callback",
                "type": "function",
                "description": "Called at the completion of the set operation.",
                "optional": true,
                "parameters": []
              }
            ]
          },
          {
            "name": "clear",
            "type": "function",
            "description": "Clears the setting, restoring any default value.",
            "async": "callback",
            "parameters": [
              {
                "name": "details",
                "type": "object",
                "description": "Which setting to clear.",
                "properties": {
                  "scope": {
                    "$ref": "types.SettingScope",
                    "optional": true,
                    "description": "Where to clear the setting (default: regular)."
                  }
                }
              },
              {
                "name": "callback",
                "type": "function",
                "description": "Called at the completion of the clear operation.",
                "optional": true,
                "parameters": []
              }
            ]
          }
        ],
        "events": [
          {
            "name": "onChange",
            "type": "function",
            "description": "Fired after the setting changes.",
            "parameters": [
              {
                "type": "object",
                "name": "details",
                "properties": {
                  "value": {
                    "description": "The value of the setting after the change.",
                    "type": "any"
                  },
                  "levelOfControl": {
                    "$ref": "types.LevelOfControl",
                    "description": "The level of control of the setting."
                  },
                  "incognitoSpecific": {
                    "description": "Whether the value that has changed is specific to the incognito session.<br/>This property will <em>only</em> be present if the user has enabled the extension in incognito mode.",
                    "type": "boolean",
                    "optional": true
                  }
                }
              }
            ]
          }
        ]
      }
    ]
  }
]
PK
!<�q'���;chrome/toolkit/content/extensions/schemas/user_scripts.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "WebExtensionManifest",
        "properties": {
          "user_scripts": {
            "type": "object",
            "max_manifest_version": 2,
            "optional": true,
            "properties": {
              "api_script": {
                "optional": true,
                "$ref": "manifest.ExtensionURL"
              }
            },
            "additionalProperties": { "$ref": "UnrecognizedProperty" }
          }
        }
      }
    ]
  },
  {
    "namespace": "userScripts",
    "max_manifest_version": 2,
    "permissions": ["manifest:user_scripts"],
    "types": [
      {
        "id": "UserScriptOptions",
        "type": "object",
        "description": "Details of a user script",
        "properties": {
          "js": {
            "type": "array",
            "optional": false,
            "description": "The list of JS files to inject",
            "minItems": 1,
            "items": { "$ref": "extensionTypes.ExtensionFileOrCode" }
          },
          "scriptMetadata": {
            "description": "An opaque user script metadata value",
            "$ref": "extensionTypes.PlainJSONValue",
            "optional": true
          },
          "matches": {
            "type": "array",
            "optional": false,
            "minItems": 1,
            "items": { "$ref": "manifest.MatchPattern" }
          },
          "excludeMatches": {
            "type": "array",
            "optional": true,
            "minItems": 1,
            "items": { "$ref": "manifest.MatchPattern" }
          },
          "includeGlobs": {
            "type": "array",
            "optional": true,
            "items": { "type": "string" }
          },
          "excludeGlobs": {
            "type": "array",
            "optional": true,
            "items": { "type": "string" }
          },
          "allFrames": {
            "type": "boolean",
            "default": false,
            "optional": true,
            "description": "If allFrames is <code>true</code>, implies that the JavaScript should be injected into all frames of current page. By default, it's <code>false</code> and is only injected into the top frame."
          },
          "matchAboutBlank": {
            "type": "boolean",
            "default": false,
            "optional": true,
            "description": "If matchAboutBlank is true, then the code is also injected in about:blank and about:srcdoc frames if your extension has access to its parent document. Code cannot be inserted in top-level about:-frames. By default it is <code>false</code>."
          },
          "runAt": {
            "$ref": "extensionTypes.RunAt",
            "default": "document_idle",
            "optional": true,
            "description": "The soonest that the JavaScript will be injected into the tab. Defaults to \"document_idle\"."
          },
          "cookieStoreId": {
            "choices": [
              {
                "type": "array",
                "minItems": 1,
                "items": { "type": "string" }
              },
              {
                "type": "string"
              }
            ],
            "optional": true,
            "description": "limit the set of matched tabs to those that belong to the given cookie store id"
          }
        }
      },
      {
        "id": "RegisteredUserScript",
        "type": "object",
        "description": "An object that represents a user script registered programmatically",
        "functions": [
          {
            "name": "unregister",
            "type": "function",
            "description": "Unregister a user script registered programmatically",
            "async": true,
            "parameters": []
          }
        ]
      }
    ],
    "functions": [
      {
        "name": "register",
        "type": "function",
        "description": "Register a user script programmatically given its $(ref:userScripts.UserScriptOptions), and resolves to a $(ref:userScripts.RegisteredUserScript) instance",
        "async": true,
        "parameters": [
          {
            "name": "userScriptOptions",
            "$ref": "UserScriptOptions"
          }
        ]
      }
    ]
  }
]
PK
!<���VttCchrome/toolkit/content/extensions/schemas/user_scripts_content.json[
  {
    "namespace": "userScripts",
    "max_manifest_version": 2,
    "permissions": ["manifest:user_scripts"],
    "allowedContexts": ["content"],
    "events": [
      {
        "name": "onBeforeScript",
        "permissions": ["manifest:user_scripts.api_script"],
        "allowedContexts": ["content", "content_only"],
        "type": "function",
        "description": "Event called when a new userScript global has been created",
        "parameters": [
          {
            "type": "object",
            "name": "userScript",
            "properties": {
              "metadata": {
                "type": "any",
                "description": "The userScript metadata (as set in userScripts.register)"
              },
              "global": {
                "type": "any",
                "description": "The userScript global"
              },
              "defineGlobals": {
                "type": "function",
                "description": "Exports all the properties of a given plain object as userScript globals",
                "parameters": [
                  {
                    "type": "object",
                    "name": "sourceObject",
                    "description": "A plain object whose properties are exported as userScript globals"
                  }
                ]
              },
              "export": {
                "type": "function",
                "description": "Convert a given value to make it accessible to the userScript code",
                "parameters": [
                  {
                    "type": "any",
                    "name": "value",
                    "description": "A value to convert into an object accessible to the userScript"
                  }
                ],
                "returns": {
                  "type": "any"
                }
              }
            }
          }
        ]
      }
    ]
  }
]
PK
!<~��iViV=chrome/toolkit/content/extensions/schemas/web_navigation.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "OptionalPermission",
        "choices": [
          {
            "type": "string",
            "enum": ["webNavigation"]
          }
        ]
      }
    ]
  },
  {
    "namespace": "webNavigation",
    "description": "Use the <code>browser.webNavigation</code> API to receive notifications about the status of navigation requests in-flight.",
    "permissions": ["webNavigation"],
    "types": [
      {
        "id": "TransitionType",
        "type": "string",
        "enum": [
          "link",
          "typed",
          "auto_bookmark",
          "auto_subframe",
          "manual_subframe",
          "generated",
          "start_page",
          "form_submit",
          "reload",
          "keyword",
          "keyword_generated"
        ],
        "description": "Cause of the navigation. The same transition types as defined in the history API are used. These are the same transition types as defined in the $(topic:transition_types)[history API] except with <code>\"start_page\"</code> in place of <code>\"auto_toplevel\"</code> (for backwards compatibility)."
      },
      {
        "id": "TransitionQualifier",
        "type": "string",
        "enum": [
          "client_redirect",
          "server_redirect",
          "forward_back",
          "from_address_bar"
        ]
      },
      {
        "id": "EventUrlFilters",
        "type": "object",
        "properties": {
          "url": {
            "type": "array",
            "minItems": 1,
            "items": { "$ref": "events.UrlFilter" }
          }
        }
      }
    ],
    "functions": [
      {
        "name": "getFrame",
        "type": "function",
        "description": "Retrieves information about the given frame. A frame refers to an &lt;iframe&gt; or a &lt;frame&gt; of a web page and is identified by a tab ID and a frame ID.",
        "async": "callback",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "description": "Information about the frame to retrieve information about.",
            "properties": {
              "tabId": {
                "type": "integer",
                "minimum": 0,
                "description": "The ID of the tab in which the frame is."
              },
              "processId": {
                "optional": true,
                "type": "integer",
                "description": "The ID of the process runs the renderer for this tab."
              },
              "frameId": {
                "type": "integer",
                "minimum": 0,
                "description": "The ID of the frame in the given tab."
              }
            }
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "type": "object",
                "name": "details",
                "optional": true,
                "description": "Information about the requested frame, null if the specified frame ID and/or tab ID are invalid.",
                "properties": {
                  "errorOccurred": {
                    "optional": true,
                    "type": "boolean",
                    "description": "True if the last navigation in this frame was interrupted by an error, i.e. the onErrorOccurred event fired."
                  },
                  "url": {
                    "type": "string",
                    "description": "The URL currently associated with this frame, if the frame identified by the frameId existed at one point in the given tab. The fact that an URL is associated with a given frameId does not imply that the corresponding frame still exists."
                  },
                  "tabId": {
                    "type": "integer",
                    "description": "The ID of the tab in which the frame is."
                  },
                  "frameId": {
                    "type": "integer",
                    "description": "The ID of the frame. 0 indicates that this is the main frame; a positive value indicates the ID of a subframe."
                  },
                  "parentFrameId": {
                    "type": "integer",
                    "description": "ID of frame that wraps the frame. Set to -1 of no parent frame exists."
                  }
                }
              }
            ]
          }
        ]
      },
      {
        "name": "getAllFrames",
        "type": "function",
        "description": "Retrieves information about all frames of a given tab.",
        "async": "callback",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "description": "Information about the tab to retrieve all frames from.",
            "properties": {
              "tabId": {
                "type": "integer",
                "minimum": 0,
                "description": "The ID of the tab."
              }
            }
          },
          {
            "type": "function",
            "name": "callback",
            "parameters": [
              {
                "name": "details",
                "type": "array",
                "description": "A list of frames in the given tab, null if the specified tab ID is invalid.",
                "optional": true,
                "items": {
                  "type": "object",
                  "properties": {
                    "errorOccurred": {
                      "optional": true,
                      "type": "boolean",
                      "description": "True if the last navigation in this frame was interrupted by an error, i.e. the onErrorOccurred event fired."
                    },
                    "processId": {
                      "unsupported": true,
                      "type": "integer",
                      "description": "The ID of the process runs the renderer for this tab."
                    },
                    "tabId": {
                      "type": "integer",
                      "description": "The ID of the tab in which the frame is."
                    },
                    "frameId": {
                      "type": "integer",
                      "description": "The ID of the frame. 0 indicates that this is the main frame; a positive value indicates the ID of a subframe."
                    },
                    "parentFrameId": {
                      "type": "integer",
                      "description": "ID of frame that wraps the frame. Set to -1 of no parent frame exists."
                    },
                    "url": {
                      "type": "string",
                      "description": "The URL currently associated with this frame."
                    }
                  }
                }
              }
            ]
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onBeforeNavigate",
        "type": "function",
        "description": "Fired when a navigation is about to occur.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab in which the navigation is about to occur."
              },
              "url": { "type": "string" },
              "processId": {
                "unsupported": true,
                "type": "integer",
                "description": "The ID of the process runs the renderer for this tab."
              },
              "frameId": {
                "type": "integer",
                "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique for a given tab and process."
              },
              "parentFrameId": {
                "type": "integer",
                "description": "ID of frame that wraps the frame. Set to -1 of no parent frame exists."
              },
              "timeStamp": {
                "type": "number",
                "description": "The time when the browser was about to start the navigation, in milliseconds since the epoch."
              }
            }
          }
        ],
        "extraParameters": [
          {
            "name": "filters",
            "optional": true,
            "$ref": "EventUrlFilters",
            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
          }
        ]
      },
      {
        "name": "onCommitted",
        "type": "function",
        "description": "Fired when a navigation is committed. The document (and the resources it refers to, such as images and subframes) might still be downloading, but at least part of the document has been received from the server and the browser has decided to switch to the new document.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab in which the navigation occurs."
              },
              "url": { "type": "string" },
              "processId": {
                "unsupported": true,
                "type": "integer",
                "description": "The ID of the process runs the renderer for this tab."
              },
              "frameId": {
                "type": "integer",
                "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."
              },
              "transitionType": {
                "$ref": "TransitionType",
                "description": "Cause of the navigation."
              },
              "transitionQualifiers": {
                "type": "array",
                "description": "A list of transition qualifiers.",
                "items": { "$ref": "TransitionQualifier" }
              },
              "timeStamp": {
                "type": "number",
                "description": "The time when the navigation was committed, in milliseconds since the epoch."
              }
            }
          }
        ],
        "extraParameters": [
          {
            "name": "filters",
            "optional": true,
            "$ref": "EventUrlFilters",
            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
          }
        ]
      },
      {
        "name": "onDOMContentLoaded",
        "type": "function",
        "description": "Fired when the page's DOM is fully constructed, but the referenced resources may not finish loading.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab in which the navigation occurs."
              },
              "url": { "type": "string" },
              "processId": {
                "unsupported": true,
                "type": "integer",
                "description": "The ID of the process runs the renderer for this tab."
              },
              "frameId": {
                "type": "integer",
                "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."
              },
              "timeStamp": {
                "type": "number",
                "description": "The time when the page's DOM was fully constructed, in milliseconds since the epoch."
              }
            }
          }
        ],
        "extraParameters": [
          {
            "name": "filters",
            "optional": true,
            "$ref": "EventUrlFilters",
            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
          }
        ]
      },
      {
        "name": "onCompleted",
        "type": "function",
        "description": "Fired when a document, including the resources it refers to, is completely loaded and initialized.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab in which the navigation occurs."
              },
              "url": { "type": "string" },
              "processId": {
                "unsupported": true,
                "type": "integer",
                "description": "The ID of the process runs the renderer for this tab."
              },
              "frameId": {
                "type": "integer",
                "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."
              },
              "timeStamp": {
                "type": "number",
                "description": "The time when the document finished loading, in milliseconds since the epoch."
              }
            }
          }
        ],
        "extraParameters": [
          {
            "name": "filters",
            "optional": true,
            "$ref": "EventUrlFilters",
            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
          }
        ]
      },
      {
        "name": "onErrorOccurred",
        "type": "function",
        "description": "Fired when an error occurs and the navigation is aborted. This can happen if either a network error occurred, or the user aborted the navigation.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab in which the navigation occurs."
              },
              "url": { "type": "string" },
              "processId": {
                "unsupported": true,
                "type": "integer",
                "description": "The ID of the process runs the renderer for this tab."
              },
              "frameId": {
                "type": "integer",
                "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."
              },
              "error": {
                "unsupported": true,
                "type": "string",
                "description": "The error description."
              },
              "timeStamp": {
                "type": "number",
                "description": "The time when the error occurred, in milliseconds since the epoch."
              }
            }
          }
        ],
        "extraParameters": [
          {
            "name": "filters",
            "optional": true,
            "$ref": "EventUrlFilters",
            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
          }
        ]
      },
      {
        "name": "onCreatedNavigationTarget",
        "type": "function",
        "description": "Fired when a new window, or a new tab in an existing window, is created to host a navigation.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "sourceTabId": {
                "type": "integer",
                "description": "The ID of the tab in which the navigation is triggered."
              },
              "sourceProcessId": {
                "type": "integer",
                "description": "The ID of the process runs the renderer for the source tab."
              },
              "sourceFrameId": {
                "type": "integer",
                "description": "The ID of the frame with sourceTabId in which the navigation is triggered. 0 indicates the main frame."
              },
              "url": {
                "type": "string",
                "description": "The URL to be opened in the new window."
              },
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab in which the url is opened"
              },
              "timeStamp": {
                "type": "number",
                "description": "The time when the browser was about to create a new view, in milliseconds since the epoch."
              }
            }
          }
        ],
        "extraParameters": [
          {
            "name": "filters",
            "optional": true,
            "$ref": "EventUrlFilters",
            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
          }
        ]
      },
      {
        "name": "onReferenceFragmentUpdated",
        "type": "function",
        "description": "Fired when the reference fragment of a frame was updated. All future events for that frame will use the updated URL.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab in which the navigation occurs."
              },
              "url": { "type": "string" },
              "processId": {
                "unsupported": true,
                "type": "integer",
                "description": "The ID of the process runs the renderer for this tab."
              },
              "frameId": {
                "type": "integer",
                "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."
              },
              "transitionType": {
                "$ref": "TransitionType",
                "description": "Cause of the navigation."
              },
              "transitionQualifiers": {
                "type": "array",
                "description": "A list of transition qualifiers.",
                "items": { "$ref": "TransitionQualifier" }
              },
              "timeStamp": {
                "type": "number",
                "description": "The time when the navigation was committed, in milliseconds since the epoch."
              }
            }
          }
        ],
        "extraParameters": [
          {
            "name": "filters",
            "optional": true,
            "$ref": "EventUrlFilters",
            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
          }
        ]
      },
      {
        "name": "onTabReplaced",
        "type": "function",
        "description": "Fired when the contents of the tab is replaced by a different (usually previously pre-rendered) tab.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "replacedTabId": {
                "type": "integer",
                "description": "The ID of the tab that was replaced."
              },
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab that replaced the old tab."
              },
              "timeStamp": {
                "type": "number",
                "description": "The time when the replacement happened, in milliseconds since the epoch."
              }
            }
          }
        ]
      },
      {
        "name": "onHistoryStateUpdated",
        "type": "function",
        "description": "Fired when the frame's history was updated to a new URL. All future events for that frame will use the updated URL.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab in which the navigation occurs."
              },
              "url": { "type": "string" },
              "processId": {
                "unsupported": true,
                "type": "integer",
                "description": "The ID of the process runs the renderer for this tab."
              },
              "frameId": {
                "type": "integer",
                "description": "0 indicates the navigation happens in the tab content window; a positive value indicates navigation in a subframe. Frame IDs are unique within a tab."
              },
              "transitionType": {
                "$ref": "TransitionType",
                "description": "Cause of the navigation."
              },
              "transitionQualifiers": {
                "type": "array",
                "description": "A list of transition qualifiers.",
                "items": { "$ref": "TransitionQualifier" }
              },
              "timeStamp": {
                "type": "number",
                "description": "The time when the navigation was committed, in milliseconds since the epoch."
              }
            }
          }
        ],
        "extraParameters": [
          {
            "name": "filters",
            "optional": true,
            "$ref": "EventUrlFilters",
            "description": "Conditions that the URL being navigated to must satisfy. The 'schemes' and 'ports' fields of UrlFilter are ignored for this event."
          }
        ]
      }
    ]
  }
]
PK
!<��Id�d�:chrome/toolkit/content/extensions/schemas/web_request.json[
  {
    "namespace": "manifest",
    "types": [
      {
        "$extend": "OptionalPermissionNoPrompt",
        "choices": [
          {
            "type": "string",
            "enum": [
              "webRequest",
              "webRequestAuthProvider",
              "webRequestBlocking",
              "webRequestFilterResponse",
              "webRequestFilterResponse.serviceWorkerScript"
            ]
          }
        ]
      }
    ]
  },
  {
    "namespace": "webRequest",
    "description": "Use the <code>browser.webRequest</code> API to observe and analyze traffic and to intercept, block, or modify requests in-flight.",
    "permissions": ["webRequest"],
    "properties": {
      "MAX_HANDLER_BEHAVIOR_CHANGED_CALLS_PER_10_MINUTES": {
        "value": 20,
        "description": "The maximum number of times that <code>handlerBehaviorChanged</code> can be called per 10 minute sustained interval. <code>handlerBehaviorChanged</code> is an expensive function call that shouldn't be called often."
      }
    },
    "types": [
      {
        "id": "ResourceType",
        "type": "string",
        "enum": [
          "main_frame",
          "sub_frame",
          "stylesheet",
          "script",
          "image",
          "object",
          "object_subrequest",
          "xmlhttprequest",
          "xslt",
          "ping",
          "beacon",
          "xml_dtd",
          "font",
          "media",
          "websocket",
          "csp_report",
          "imageset",
          "web_manifest",
          "speculative",
          "other"
        ]
      },
      {
        "id": "OnBeforeRequestOptions",
        "type": "string",
        "enum": ["blocking", "requestBody"],
        "postprocess": "webRequestBlockingPermissionRequired"
      },
      {
        "id": "OnBeforeSendHeadersOptions",
        "type": "string",
        "enum": ["requestHeaders", "blocking"],
        "postprocess": "webRequestBlockingPermissionRequired"
      },
      {
        "id": "OnSendHeadersOptions",
        "type": "string",
        "enum": ["requestHeaders"]
      },
      {
        "id": "OnHeadersReceivedOptions",
        "type": "string",
        "enum": ["blocking", "responseHeaders"],
        "postprocess": "webRequestBlockingPermissionRequired"
      },
      {
        "id": "OnAuthRequiredOptions",
        "type": "string",
        "enum": ["responseHeaders", "blocking", "asyncBlocking"],
        "postprocess": "webRequestBlockingOrAuthProviderPermissionRequired"
      },
      {
        "id": "OnResponseStartedOptions",
        "type": "string",
        "enum": ["responseHeaders"]
      },
      {
        "id": "OnBeforeRedirectOptions",
        "type": "string",
        "enum": ["responseHeaders"]
      },
      {
        "id": "OnCompletedOptions",
        "type": "string",
        "enum": ["responseHeaders"]
      },
      {
        "id": "RequestFilter",
        "type": "object",
        "description": "An object describing filters to apply to webRequest events.",
        "properties": {
          "urls": {
            "type": "array",
            "description": "A list of URLs or URL patterns. Requests that cannot match any of the URLs will be filtered out.",
            "items": { "type": "string" },
            "minItems": 1
          },
          "types": {
            "type": "array",
            "optional": true,
            "description": "A list of request types. Requests that cannot match any of the types will be filtered out.",
            "items": { "$ref": "ResourceType", "onError": "warn" },
            "minItems": 1
          },
          "tabId": { "type": "integer", "optional": true },
          "windowId": { "type": "integer", "optional": true },
          "incognito": {
            "type": "boolean",
            "optional": true,
            "description": "If provided, requests that do not match the incognito state will be filtered out."
          }
        }
      },
      {
        "id": "HttpHeaders",
        "type": "array",
        "description": "An array of HTTP headers. Each header is represented as a dictionary containing the keys <code>name</code> and either <code>value</code> or <code>binaryValue</code>.",
        "items": {
          "type": "object",
          "properties": {
            "name": {
              "type": "string",
              "description": "Name of the HTTP header."
            },
            "value": {
              "type": "string",
              "optional": true,
              "description": "Value of the HTTP header if it can be represented by UTF-8."
            },
            "binaryValue": {
              "type": "array",
              "optional": true,
              "description": "Value of the HTTP header if it cannot be represented by UTF-8, stored as individual byte values (0..255).",
              "items": { "type": "integer" }
            }
          }
        }
      },
      {
        "id": "BlockingResponse",
        "type": "object",
        "description": "Returns value for event handlers that have the 'blocking' extraInfoSpec applied. Allows the event handler to modify network requests.",
        "properties": {
          "cancel": {
            "type": "boolean",
            "optional": true,
            "description": "If true, the request is cancelled. Used in onBeforeRequest, this prevents the request from being sent."
          },
          "redirectUrl": {
            "type": "string",
            "optional": true,
            "description": "Only used as a response to the onBeforeRequest and onHeadersReceived events. If set, the original request is prevented from being sent/completed and is instead redirected to the given URL. Redirections to non-HTTP schemes such as data: are allowed. Redirects initiated by a redirect action use the original request method for the redirect, with one exception: If the redirect is initiated at the onHeadersReceived stage, then the redirect will be issued using the GET method."
          },
          "upgradeToSecure": {
            "type": "boolean",
            "optional": true,
            "description": "Only used as a response to the onBeforeRequest event. If set, the original request is prevented from being sent/completed and is instead upgraded to a secure request.  If any extension returns <code>redirectUrl</code> during onBeforeRequest, <code>upgradeToSecure</code> will have no affect."
          },
          "requestHeaders": {
            "$ref": "HttpHeaders",
            "optional": true,
            "description": "Only used as a response to the onBeforeSendHeaders event. If set, the request is made with these request headers instead."
          },
          "responseHeaders": {
            "$ref": "HttpHeaders",
            "optional": true,
            "description": "Only used as a response to the onHeadersReceived event. If set, the server is assumed to have responded with these response headers instead. Only return <code>responseHeaders</code> if you really want to modify the headers in order to limit the number of conflicts (only one extension may modify <code>responseHeaders</code> for each request)."
          },
          "authCredentials": {
            "type": "object",
            "description": "Only used as a response to the onAuthRequired event. If set, the request is made using the supplied credentials.",
            "optional": true,
            "properties": {
              "username": { "type": "string" },
              "password": { "type": "string" }
            }
          }
        }
      },
      {
        "id": "CertificateInfo",
        "type": "object",
        "description": "Contains the certificate properties of the request if it is a secure request.",
        "properties": {
          "subject": {
            "type": "string"
          },
          "issuer": {
            "type": "string"
          },
          "validity": {
            "type": "object",
            "description": "Contains start and end timestamps.",
            "properties": {
              "start": { "type": "integer" },
              "end": { "type": "integer" }
            }
          },
          "fingerprint": {
            "type": "object",
            "properties": {
              "sha1": { "type": "string" },
              "sha256": { "type": "string" }
            }
          },
          "serialNumber": {
            "type": "string"
          },
          "isBuiltInRoot": {
            "type": "boolean"
          },
          "subjectPublicKeyInfoDigest": {
            "type": "object",
            "properties": {
              "sha256": { "type": "string" }
            }
          },
          "rawDER": {
            "optional": true,
            "type": "array",
            "items": {
              "type": "integer"
            }
          }
        }
      },
      {
        "id": "CertificateTransparencyStatus",
        "type": "string",
        "enum": [
          "not_applicable",
          "policy_compliant",
          "policy_not_enough_scts",
          "policy_not_diverse_scts"
        ]
      },
      {
        "id": "TransportWeaknessReasons",
        "type": "string",
        "enum": ["cipher"]
      },
      {
        "id": "SecurityInfo",
        "type": "object",
        "description": "Contains the security properties of the request (ie. SSL/TLS information).",
        "properties": {
          "state": {
            "type": "string",
            "enum": ["insecure", "weak", "broken", "secure"]
          },
          "errorMessage": {
            "type": "string",
            "description": "Error message if state is \"broken\"",
            "optional": true
          },
          "protocolVersion": {
            "type": "string",
            "description": "Protocol version if state is \"secure\"",
            "enum": ["TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3", "unknown"],
            "optional": true
          },
          "cipherSuite": {
            "type": "string",
            "description": "The cipher suite used in this request if state is \"secure\".",
            "optional": true
          },
          "keaGroupName": {
            "type": "string",
            "description": "The key exchange algorithm used in this request if state is \"secure\".",
            "optional": true
          },
          "secretKeyLength": {
            "type": "number",
            "description": "The length (in bits) of the secret key.",
            "optional": true
          },
          "signatureSchemeName": {
            "type": "string",
            "description": "The signature scheme used in this request if state is \"secure\".",
            "optional": true
          },
          "certificates": {
            "description": "Certificate data if state is \"secure\".  Will only contain one entry unless <code>certificateChain</code> is passed as an option.",
            "type": "array",
            "items": { "$ref": "CertificateInfo" }
          },
          "overridableErrorCategory": {
            "description": "The type of certificate error that was overridden for this connection, if any.",
            "type": "string",
            "enum": [
              "trust_error",
              "domain_mismatch",
              "expired_or_not_yet_valid"
            ],
            "optional": true
          },
          "isDomainMismatch": {
            "description": "The domain name does not match the certificate domain.",
            "type": "boolean",
            "optional": true,
            "deprecated": "Please use $(ref:SecurityInfo.overridableErrorCategory)."
          },
          "isNotValidAtThisTime": {
            "description": "The certificate is either expired or is not yet valid.  See <code>CertificateInfo.validity</code> for start and end dates.",
            "type": "boolean",
            "optional": true,
            "deprecated": "Please use $(ref:SecurityInfo.overridableErrorCategory)."
          },
          "isUntrusted": {
            "type": "boolean",
            "optional": true,
            "deprecated": "Please use $(ref:SecurityInfo.overridableErrorCategory)."
          },
          "isExtendedValidation": {
            "type": "boolean",
            "optional": true
          },
          "certificateTransparencyStatus": {
            "description": "Certificate transparency compliance per RFC 6962.  See <code>https://www.certificate-transparency.org/what-is-ct</code> for more information.",
            "$ref": "CertificateTransparencyStatus",
            "optional": true
          },
          "hsts": {
            "type": "boolean",
            "description": "True if host uses Strict Transport Security and state is \"secure\".",
            "optional": true
          },
          "hpkp": {
            "type": "string",
            "description": "True if host uses Public Key Pinning and state is \"secure\".",
            "optional": true
          },
          "weaknessReasons": {
            "type": "array",
            "items": { "$ref": "TransportWeaknessReasons" },
            "description": "list of reasons that cause the request to be considered weak, if state is \"weak\"",
            "optional": true
          },
          "usedEch": {
            "type": "boolean",
            "description": "True if the TLS connection used Encrypted Client Hello.",
            "optional": true
          },
          "usedDelegatedCredentials": {
            "type": "boolean",
            "description": "True if the TLS connection used Delegated Credentials.",
            "optional": true
          },
          "usedOcsp": {
            "type": "boolean",
            "description": "True if the TLS connection made OCSP requests.",
            "optional": true
          },
          "usedPrivateDns": {
            "type": "boolean",
            "description": "True if the TLS connection used a privacy-preserving DNS transport like DNS-over-HTTPS.",
            "optional": true
          }
        }
      },
      {
        "id": "UploadData",
        "type": "object",
        "properties": {
          "bytes": {
            "type": "any",
            "optional": true,
            "description": "An ArrayBuffer with a copy of the data."
          },
          "file": {
            "type": "string",
            "optional": true,
            "description": "A string with the file's path and name."
          }
        },
        "description": "Contains data uploaded in a URL request."
      },
      {
        "id": "UrlClassificationFlags",
        "type": "string",
        "enum": [
          "fingerprinting",
          "fingerprinting_content",
          "cryptomining",
          "cryptomining_content",
          "emailtracking",
          "emailtracking_content",
          "tracking",
          "tracking_ad",
          "tracking_analytics",
          "tracking_social",
          "tracking_content",
          "any_basic_tracking",
          "any_strict_tracking",
          "any_social_tracking"
        ],
        "description": "Tracking flags that match our internal tracking classification"
      },
      {
        "id": "UrlClassificationParty",
        "type": "array",
        "items": { "$ref": "UrlClassificationFlags" },
        "description": "If the request has been classified this is an array of $(ref:UrlClassificationFlags)."
      },
      {
        "id": "UrlClassification",
        "type": "object",
        "properties": {
          "firstParty": {
            "$ref": "UrlClassificationParty",
            "description": "Classification flags if the request has been classified and it is first party."
          },
          "thirdParty": {
            "$ref": "UrlClassificationParty",
            "description": "Classification flags if the request has been classified and it or its window hierarchy is third party."
          }
        }
      }
    ],
    "functions": [
      {
        "name": "handlerBehaviorChanged",
        "type": "function",
        "description": "Needs to be called when the behavior of the webRequest handlers has changed to prevent incorrect handling due to caching. This function call is expensive. Don't call it often.",
        "async": "callback",
        "parameters": [
          {
            "type": "function",
            "name": "callback",
            "optional": true,
            "parameters": []
          }
        ]
      },
      {
        "name": "filterResponseData",
        "permissions": ["webRequestBlocking"],
        "type": "function",
        "description": "...",
        "parameters": [
          {
            "name": "requestId",
            "type": "string"
          }
        ],
        "returns": {
          "type": "object",
          "additionalProperties": { "type": "any" },
          "isInstanceOf": "StreamFilter"
        }
      },
      {
        "name": "getSecurityInfo",
        "type": "function",
        "async": true,
        "description": "Retrieves the security information for the request.  Returns a promise that will resolve to a SecurityInfo object.",
        "parameters": [
          {
            "name": "requestId",
            "type": "string"
          },
          {
            "name": "options",
            "optional": true,
            "type": "object",
            "properties": {
              "certificateChain": {
                "type": "boolean",
                "description": "Include the entire certificate chain.",
                "optional": true
              },
              "rawDER": {
                "type": "boolean",
                "description": "Include raw certificate data for processing by the extension.",
                "optional": true
              }
            }
          }
        ]
      }
    ],
    "events": [
      {
        "name": "onBeforeRequest",
        "type": "function",
        "description": "Fired when a request is about to occur.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "requestId": {
                "type": "string",
                "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."
              },
              "url": { "type": "string" },
              "method": {
                "type": "string",
                "description": "Standard HTTP method."
              },
              "frameId": {
                "type": "integer",
                "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."
              },
              "parentFrameId": {
                "type": "integer",
                "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."
              },
              "incognito": {
                "type": "boolean",
                "optional": true,
                "description": "True for private browsing requests."
              },
              "cookieStoreId": {
                "type": "string",
                "optional": true,
                "description": "The cookie store ID of the contextual identity."
              },
              "originUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the resource that triggered this request."
              },
              "documentUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the page into which the requested resource will be loaded."
              },
              "requestBody": {
                "type": "object",
                "optional": true,
                "description": "Contains the HTTP request body data. Only provided if extraInfoSpec contains 'requestBody'.",
                "properties": {
                  "error": {
                    "type": "string",
                    "optional": true,
                    "description": "Errors when obtaining request body data."
                  },
                  "formData": {
                    "type": "object",
                    "optional": true,
                    "description": "If the request method is POST and the body is a sequence of key-value pairs encoded in UTF8, encoded as either multipart/form-data, or application/x-www-form-urlencoded, this dictionary is present and for each key contains the list of all values for that key. If the data is of another media type, or if it is malformed, the dictionary is not present. An example value of this dictionary is {'key': ['value1', 'value2']}.",
                    "properties": {},
                    "additionalProperties": {
                      "type": "array",
                      "items": { "type": "string" }
                    }
                  },
                  "raw": {
                    "type": "array",
                    "optional": true,
                    "items": { "$ref": "UploadData" },
                    "description": "If the request method is PUT or POST, and the body is not already parsed in formData, then the unparsed request body elements are contained in this array."
                  }
                }
              },
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."
              },
              "type": {
                "$ref": "ResourceType",
                "description": "How the requested resource will be used."
              },
              "timeStamp": {
                "type": "number",
                "description": "The time when this signal is triggered, in milliseconds since the epoch."
              },
              "urlClassification": {
                "$ref": "UrlClassification",
                "optional": true,
                "description": "Tracking classification if the request has been classified."
              },
              "thirdParty": {
                "type": "boolean",
                "description": "Indicates if this request and its content window hierarchy is third party."
              }
            }
          }
        ],
        "extraParameters": [
          {
            "$ref": "RequestFilter",
            "name": "filter",
            "description": "A set of filters that restricts the events that will be sent to this listener."
          },
          {
            "type": "array",
            "optional": true,
            "name": "extraInfoSpec",
            "description": "Array of extra information that should be passed to the listener function.",
            "items": {
              "$ref": "OnBeforeRequestOptions"
            }
          }
        ],
        "returns": {
          "$ref": "BlockingResponse",
          "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.",
          "optional": true
        }
      },
      {
        "name": "onBeforeSendHeaders",
        "type": "function",
        "description": "Fired before sending an HTTP request, once the request headers are available. This may occur after a TCP connection is made to the server, but before any HTTP data is sent. ",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "requestId": {
                "type": "string",
                "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."
              },
              "url": { "type": "string" },
              "method": {
                "type": "string",
                "description": "Standard HTTP method."
              },
              "frameId": {
                "type": "integer",
                "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."
              },
              "parentFrameId": {
                "type": "integer",
                "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."
              },
              "incognito": {
                "type": "boolean",
                "optional": true,
                "description": "True for private browsing requests."
              },
              "cookieStoreId": {
                "type": "string",
                "optional": true,
                "description": "The cookie store ID of the contextual identity."
              },
              "originUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the resource that triggered this request."
              },
              "documentUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the page into which the requested resource will be loaded."
              },
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."
              },
              "type": {
                "$ref": "ResourceType",
                "description": "How the requested resource will be used."
              },
              "timeStamp": {
                "type": "number",
                "description": "The time when this signal is triggered, in milliseconds since the epoch."
              },
              "requestHeaders": {
                "$ref": "HttpHeaders",
                "optional": true,
                "description": "The HTTP request headers that are going to be sent out with this request."
              },
              "urlClassification": {
                "$ref": "UrlClassification",
                "optional": true,
                "description": "Tracking classification if the request has been classified."
              },
              "thirdParty": {
                "type": "boolean",
                "description": "Indicates if this request and its content window hierarchy is third party."
              }
            }
          }
        ],
        "extraParameters": [
          {
            "$ref": "RequestFilter",
            "name": "filter",
            "description": "A set of filters that restricts the events that will be sent to this listener."
          },
          {
            "type": "array",
            "optional": true,
            "name": "extraInfoSpec",
            "description": "Array of extra information that should be passed to the listener function.",
            "items": {
              "$ref": "OnBeforeSendHeadersOptions"
            }
          }
        ],
        "returns": {
          "$ref": "BlockingResponse",
          "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.",
          "optional": true
        }
      },
      {
        "name": "onSendHeaders",
        "type": "function",
        "description": "Fired just before a request is going to be sent to the server (modifications of previous onBeforeSendHeaders callbacks are visible by the time onSendHeaders is fired).",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "requestId": {
                "type": "string",
                "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."
              },
              "url": { "type": "string" },
              "method": {
                "type": "string",
                "description": "Standard HTTP method."
              },
              "frameId": {
                "type": "integer",
                "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."
              },
              "parentFrameId": {
                "type": "integer",
                "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."
              },
              "incognito": {
                "type": "boolean",
                "optional": true,
                "description": "True for private browsing requests."
              },
              "cookieStoreId": {
                "type": "string",
                "optional": true,
                "description": "The cookie store ID of the contextual identity."
              },
              "originUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the resource that triggered this request."
              },
              "documentUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the page into which the requested resource will be loaded."
              },
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."
              },
              "type": {
                "$ref": "ResourceType",
                "description": "How the requested resource will be used."
              },
              "timeStamp": {
                "type": "number",
                "description": "The time when this signal is triggered, in milliseconds since the epoch."
              },
              "requestHeaders": {
                "$ref": "HttpHeaders",
                "optional": true,
                "description": "The HTTP request headers that have been sent out with this request."
              },
              "urlClassification": {
                "$ref": "UrlClassification",
                "optional": true,
                "description": "Tracking classification if the request has been classified."
              },
              "thirdParty": {
                "type": "boolean",
                "description": "Indicates if this request and its content window hierarchy is third party."
              }
            }
          }
        ],
        "extraParameters": [
          {
            "$ref": "RequestFilter",
            "name": "filter",
            "description": "A set of filters that restricts the events that will be sent to this listener."
          },
          {
            "type": "array",
            "optional": true,
            "name": "extraInfoSpec",
            "description": "Array of extra information that should be passed to the listener function.",
            "items": {
              "$ref": "OnSendHeadersOptions"
            }
          }
        ]
      },
      {
        "name": "onHeadersReceived",
        "type": "function",
        "description": "Fired when HTTP response headers of a request have been received.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "requestId": {
                "type": "string",
                "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."
              },
              "url": { "type": "string" },
              "method": {
                "type": "string",
                "description": "Standard HTTP method."
              },
              "frameId": {
                "type": "integer",
                "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."
              },
              "parentFrameId": {
                "type": "integer",
                "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."
              },
              "incognito": {
                "type": "boolean",
                "optional": true,
                "description": "True for private browsing requests."
              },
              "cookieStoreId": {
                "type": "string",
                "optional": true,
                "description": "The cookie store ID of the contextual identity."
              },
              "originUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the resource that triggered this request."
              },
              "documentUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the page into which the requested resource will be loaded."
              },
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."
              },
              "type": {
                "$ref": "ResourceType",
                "description": "How the requested resource will be used."
              },
              "timeStamp": {
                "type": "number",
                "description": "The time when this signal is triggered, in milliseconds since the epoch."
              },
              "statusLine": {
                "type": "string",
                "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line)."
              },
              "responseHeaders": {
                "$ref": "HttpHeaders",
                "optional": true,
                "description": "The HTTP response headers that have been received with this response."
              },
              "statusCode": {
                "type": "integer",
                "description": "Standard HTTP status code returned by the server."
              },
              "urlClassification": {
                "$ref": "UrlClassification",
                "optional": true,
                "description": "Tracking classification if the request has been classified."
              },
              "thirdParty": {
                "type": "boolean",
                "description": "Indicates if this request and its content window hierarchy is third party."
              }
            }
          }
        ],
        "extraParameters": [
          {
            "$ref": "RequestFilter",
            "name": "filter",
            "description": "A set of filters that restricts the events that will be sent to this listener."
          },
          {
            "type": "array",
            "optional": true,
            "name": "extraInfoSpec",
            "description": "Array of extra information that should be passed to the listener function.",
            "items": {
              "$ref": "OnHeadersReceivedOptions"
            }
          }
        ],
        "returns": {
          "$ref": "BlockingResponse",
          "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.",
          "optional": true
        }
      },
      {
        "name": "onAuthRequired",
        "type": "function",
        "description": "Fired when an authentication failure is received. The listener has three options: it can provide authentication credentials, it can cancel the request and display the error page, or it can take no action on the challenge. If bad user credentials are provided, this may be called multiple times for the same request.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "requestId": {
                "type": "string",
                "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."
              },
              "url": { "type": "string" },
              "method": {
                "type": "string",
                "description": "Standard HTTP method."
              },
              "frameId": {
                "type": "integer",
                "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."
              },
              "parentFrameId": {
                "type": "integer",
                "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."
              },
              "incognito": {
                "type": "boolean",
                "optional": true,
                "description": "True for private browsing requests."
              },
              "cookieStoreId": {
                "type": "string",
                "optional": true,
                "description": "The cookie store ID of the contextual identity."
              },
              "originUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the resource that triggered this request."
              },
              "documentUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the page into which the requested resource will be loaded."
              },
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."
              },
              "type": {
                "$ref": "ResourceType",
                "description": "How the requested resource will be used."
              },
              "timeStamp": {
                "type": "number",
                "description": "The time when this signal is triggered, in milliseconds since the epoch."
              },
              "scheme": {
                "type": "string",
                "description": "The authentication scheme, e.g. Basic or Digest."
              },
              "realm": {
                "type": "string",
                "description": "The authentication realm provided by the server, if there is one.",
                "optional": true
              },
              "challenger": {
                "type": "object",
                "description": "The server requesting authentication.",
                "properties": {
                  "host": { "type": "string" },
                  "port": { "type": "integer" }
                }
              },
              "isProxy": {
                "type": "boolean",
                "description": "True for Proxy-Authenticate, false for WWW-Authenticate."
              },
              "responseHeaders": {
                "$ref": "HttpHeaders",
                "optional": true,
                "description": "The HTTP response headers that were received along with this response."
              },
              "statusLine": {
                "type": "string",
                "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line) or an empty string if there are no headers."
              },
              "statusCode": {
                "type": "integer",
                "description": "Standard HTTP status code returned by the server."
              },
              "urlClassification": {
                "$ref": "UrlClassification",
                "optional": true,
                "description": "Tracking classification if the request has been classified."
              },
              "thirdParty": {
                "type": "boolean",
                "description": "Indicates if this request and its content window hierarchy is third party."
              }
            }
          },
          {
            "type": "function",
            "optional": true,
            "name": "asyncCallback",
            "parameters": [{ "name": "response", "$ref": "BlockingResponse" }]
          }
        ],
        "extraParameters": [
          {
            "$ref": "RequestFilter",
            "name": "filter",
            "description": "A set of filters that restricts the events that will be sent to this listener."
          },
          {
            "type": "array",
            "optional": true,
            "name": "extraInfoSpec",
            "postprocess": "mutuallyExclusiveBlockingOrAsyncBlocking",
            "description": "Array of extra information that should be passed to the listener function.",
            "items": {
              "$ref": "OnAuthRequiredOptions"
            }
          }
        ],
        "returns": {
          "$ref": "BlockingResponse",
          "description": "If \"blocking\" is specified in the \"extraInfoSpec\" parameter, the event listener should return an object of this type.",
          "optional": true
        }
      },
      {
        "name": "onResponseStarted",
        "type": "function",
        "description": "Fired when the first byte of the response body is received. For HTTP requests, this means that the status line and response headers are available.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "requestId": {
                "type": "string",
                "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."
              },
              "url": { "type": "string" },
              "method": {
                "type": "string",
                "description": "Standard HTTP method."
              },
              "frameId": {
                "type": "integer",
                "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."
              },
              "parentFrameId": {
                "type": "integer",
                "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."
              },
              "incognito": {
                "type": "boolean",
                "optional": true,
                "description": "True for private browsing requests."
              },
              "cookieStoreId": {
                "type": "string",
                "optional": true,
                "description": "The cookie store ID of the contextual identity."
              },
              "originUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the resource that triggered this request."
              },
              "documentUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the page into which the requested resource will be loaded."
              },
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."
              },
              "type": {
                "$ref": "ResourceType",
                "description": "How the requested resource will be used."
              },
              "timeStamp": {
                "type": "number",
                "description": "The time when this signal is triggered, in milliseconds since the epoch."
              },
              "ip": {
                "type": "string",
                "optional": true,
                "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."
              },
              "fromCache": {
                "type": "boolean",
                "description": "Indicates if this response was fetched from disk cache."
              },
              "statusCode": {
                "type": "integer",
                "description": "Standard HTTP status code returned by the server."
              },
              "responseHeaders": {
                "$ref": "HttpHeaders",
                "optional": true,
                "description": "The HTTP response headers that were received along with this response."
              },
              "statusLine": {
                "type": "string",
                "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line) or an empty string if there are no headers."
              },
              "urlClassification": {
                "$ref": "UrlClassification",
                "optional": true,
                "description": "Tracking classification if the request has been classified."
              },
              "thirdParty": {
                "type": "boolean",
                "description": "Indicates if this request and its content window hierarchy is third party."
              }
            }
          }
        ],
        "extraParameters": [
          {
            "$ref": "RequestFilter",
            "name": "filter",
            "description": "A set of filters that restricts the events that will be sent to this listener."
          },
          {
            "type": "array",
            "optional": true,
            "name": "extraInfoSpec",
            "description": "Array of extra information that should be passed to the listener function.",
            "items": {
              "$ref": "OnResponseStartedOptions"
            }
          }
        ]
      },
      {
        "name": "onBeforeRedirect",
        "type": "function",
        "description": "Fired when a server-initiated redirect is about to occur.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "requestId": {
                "type": "string",
                "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."
              },
              "url": { "type": "string" },
              "method": {
                "type": "string",
                "description": "Standard HTTP method."
              },
              "frameId": {
                "type": "integer",
                "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."
              },
              "parentFrameId": {
                "type": "integer",
                "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."
              },
              "incognito": {
                "type": "boolean",
                "optional": true,
                "description": "True for private browsing requests."
              },
              "cookieStoreId": {
                "type": "string",
                "optional": true,
                "description": "The cookie store ID of the contextual identity."
              },
              "originUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the resource that triggered this request."
              },
              "documentUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the page into which the requested resource will be loaded."
              },
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."
              },
              "type": {
                "$ref": "ResourceType",
                "description": "How the requested resource will be used."
              },
              "timeStamp": {
                "type": "number",
                "description": "The time when this signal is triggered, in milliseconds since the epoch."
              },
              "ip": {
                "type": "string",
                "optional": true,
                "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."
              },
              "fromCache": {
                "type": "boolean",
                "description": "Indicates if this response was fetched from disk cache."
              },
              "statusCode": {
                "type": "integer",
                "description": "Standard HTTP status code returned by the server."
              },
              "redirectUrl": {
                "type": "string",
                "description": "The new URL."
              },
              "responseHeaders": {
                "$ref": "HttpHeaders",
                "optional": true,
                "description": "The HTTP response headers that were received along with this redirect."
              },
              "statusLine": {
                "type": "string",
                "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line) or an empty string if there are no headers."
              },
              "urlClassification": {
                "$ref": "UrlClassification",
                "optional": true,
                "description": "Tracking classification if the request has been classified."
              },
              "thirdParty": {
                "type": "boolean",
                "description": "Indicates if this request and its content window hierarchy is third party."
              }
            }
          }
        ],
        "extraParameters": [
          {
            "$ref": "RequestFilter",
            "name": "filter",
            "description": "A set of filters that restricts the events that will be sent to this listener."
          },
          {
            "type": "array",
            "optional": true,
            "name": "extraInfoSpec",
            "description": "Array of extra information that should be passed to the listener function.",
            "items": {
              "$ref": "OnBeforeRedirectOptions"
            }
          }
        ]
      },
      {
        "name": "onCompleted",
        "type": "function",
        "description": "Fired when a request is completed.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "requestId": {
                "type": "string",
                "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."
              },
              "url": { "type": "string" },
              "method": {
                "type": "string",
                "description": "Standard HTTP method."
              },
              "frameId": {
                "type": "integer",
                "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."
              },
              "parentFrameId": {
                "type": "integer",
                "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."
              },
              "incognito": {
                "type": "boolean",
                "optional": true,
                "description": "True for private browsing requests."
              },
              "cookieStoreId": {
                "type": "string",
                "optional": true,
                "description": "The cookie store ID of the contextual identity."
              },
              "originUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the resource that triggered this request."
              },
              "documentUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the page into which the requested resource will be loaded."
              },
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."
              },
              "type": {
                "$ref": "ResourceType",
                "description": "How the requested resource will be used."
              },
              "timeStamp": {
                "type": "number",
                "description": "The time when this signal is triggered, in milliseconds since the epoch."
              },
              "ip": {
                "type": "string",
                "optional": true,
                "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."
              },
              "fromCache": {
                "type": "boolean",
                "description": "Indicates if this response was fetched from disk cache."
              },
              "statusCode": {
                "type": "integer",
                "description": "Standard HTTP status code returned by the server."
              },
              "responseHeaders": {
                "$ref": "HttpHeaders",
                "optional": true,
                "description": "The HTTP response headers that were received along with this response."
              },
              "statusLine": {
                "type": "string",
                "description": "HTTP status line of the response or the 'HTTP/0.9 200 OK' string for HTTP/0.9 responses (i.e., responses that lack a status line) or an empty string if there are no headers."
              },
              "urlClassification": {
                "$ref": "UrlClassification",
                "description": "Tracking classification if the request has been classified."
              },
              "thirdParty": {
                "type": "boolean",
                "description": "Indicates if this request and its content window hierarchy is third party."
              },
              "requestSize": {
                "type": "integer",
                "description": "For http requests, the bytes transferred in the request. Only available in onCompleted."
              },
              "responseSize": {
                "type": "integer",
                "description": "For http requests, the bytes received in the request. Only available in onCompleted."
              }
            }
          }
        ],
        "extraParameters": [
          {
            "$ref": "RequestFilter",
            "name": "filter",
            "description": "A set of filters that restricts the events that will be sent to this listener."
          },
          {
            "type": "array",
            "optional": true,
            "name": "extraInfoSpec",
            "description": "Array of extra information that should be passed to the listener function.",
            "items": {
              "$ref": "OnCompletedOptions"
            }
          }
        ]
      },
      {
        "name": "onErrorOccurred",
        "type": "function",
        "description": "Fired when an error occurs.",
        "parameters": [
          {
            "type": "object",
            "name": "details",
            "properties": {
              "requestId": {
                "type": "string",
                "description": "The ID of the request. Request IDs are unique within a browser session. As a result, they could be used to relate different events of the same request."
              },
              "url": { "type": "string" },
              "method": {
                "type": "string",
                "description": "Standard HTTP method."
              },
              "frameId": {
                "type": "integer",
                "description": "The value 0 indicates that the request happens in the main frame; a positive value indicates the ID of a subframe in which the request happens. If the document of a (sub-)frame is loaded (<code>type</code> is <code>main_frame</code> or <code>sub_frame</code>), <code>frameId</code> indicates the ID of this frame, not the ID of the outer frame. Frame IDs are unique within a tab."
              },
              "parentFrameId": {
                "type": "integer",
                "description": "ID of frame that wraps the frame which sent the request. Set to -1 if no parent frame exists."
              },
              "incognito": {
                "type": "boolean",
                "optional": true,
                "description": "True for private browsing requests."
              },
              "cookieStoreId": {
                "type": "string",
                "optional": true,
                "description": "The cookie store ID of the contextual identity."
              },
              "originUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the resource that triggered this request."
              },
              "documentUrl": {
                "type": "string",
                "optional": true,
                "description": "URL of the page into which the requested resource will be loaded."
              },
              "tabId": {
                "type": "integer",
                "description": "The ID of the tab in which the request takes place. Set to -1 if the request isn't related to a tab."
              },
              "type": {
                "$ref": "ResourceType",
                "description": "How the requested resource will be used."
              },
              "timeStamp": {
                "type": "number",
                "description": "The time when this signal is triggered, in milliseconds since the epoch."
              },
              "ip": {
                "type": "string",
                "optional": true,
                "description": "The server IP address that the request was actually sent to. Note that it may be a literal IPv6 address."
              },
              "fromCache": {
                "type": "boolean",
                "description": "Indicates if this response was fetched from disk cache."
              },
              "error": {
                "type": "string",
                "description": "The error description. This string is <em>not</em> guaranteed to remain backwards compatible between releases. You must not parse and act based upon its content."
              },
              "urlClassification": {
                "$ref": "UrlClassification",
                "optional": true,
                "description": "Tracking classification if the request has been classified."
              },
              "thirdParty": {
                "type": "boolean",
                "description": "Indicates if this request and its content window hierarchy is third party."
              }
            }
          }
        ],
        "extraParameters": [
          {
            "$ref": "RequestFilter",
            "name": "filter",
            "description": "A set of filters that restricts the events that will be sent to this listener."
          }
        ]
      }
    ]
  }
]
PK
!<�}��6chrome/toolkit/content/global/TopLevelVideoDocument.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// Hide our variables from the web content, even though the spec allows them
// (and the DOM) to be accessible (see bug 1474832)
{
  // <video> is used for top-level audio documents as well
  let videoElement = document.getElementsByTagName("video")[0];

  let setFocusToVideoElement = function (e) {
    // We don't want to retarget focus if it goes to the controls in
    // the video element. Because they're anonymous content, the target
    // will be the video element in that case. Avoid calling .focus()
    // for those events:
    if (e && e.target == videoElement) {
      return;
    }
    videoElement.focus();
  };

  // Redirect focus to the video element whenever the document receives
  // focus.
  document.addEventListener("focus", setFocusToVideoElement, true);

  // Focus on the video in the newly created document.
  setFocusToVideoElement();

  // Opt out of moving focus away if the DOM tree changes (from add-on or web content)
  let observer = new MutationObserver(() => {
    observer.disconnect();
    document.removeEventListener("focus", setFocusToVideoElement, true);
  });
  observer.observe(document.documentElement, {
    childList: true,
    subtree: true,
  });

  // Handle fullscreen mode
  document.addEventListener("keypress", ev => {
    // Maximize the standalone video when pressing F11,
    // but ignore audio elements
    if (
      ev.key == "F11" &&
      videoElement.videoWidth != 0 &&
      videoElement.videoHeight != 0
    ) {
      // If we're in browser fullscreen mode, it means the user pressed F11
      // while browser chrome or another tab had focus.
      // Don't break leaving that mode, so do nothing here.
      if (window.fullScreen) {
        return;
      }

      // If we're not in browser fullscreen mode, prevent entering into that,
      // so we don't end up there after pressing Esc.
      ev.preventDefault();
      ev.stopPropagation();

      if (!document.fullscreenElement) {
        videoElement.requestFullscreen();
      } else {
        document.exitFullscreen();
      }
    }
  });
}
PK
!<�U��LL-chrome/toolkit/content/global/aboutAbout.html<!DOCTYPE html>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta charset="utf-8" />
    <meta name="color-scheme" content="light dark" />
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:; object-src 'none'"
    />
    <title data-l10n-id="about-about-title"></title>
    <link
      rel="stylesheet"
      href="chrome://global/skin/in-content/info-pages.css"
    />
    <link rel="localization" href="toolkit/about/aboutAbout.ftl" />
    <link
      rel="icon"
      type="image/png"
      href="chrome://branding/content/icon32.png"
    />
    <script src="chrome://global/content/aboutAbout.js"></script>
  </head>

  <body>
    <div class="container">
      <h1 data-l10n-id="about-about-title"></h1>
      <p><em data-l10n-id="about-about-note"></em></p>
      <ul id="abouts" class="columns"></ul>
    </div>
  </body>
</html>
PK
!<R��+chrome/toolkit/content/global/aboutAbout.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { AboutPagesUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/AboutPagesUtils.sys.mjs"
);

var gContainer;
window.onload = function () {
  gContainer = document.getElementById("abouts");
  AboutPagesUtils.visibleAboutUrls.forEach(createProtocolListing);
};

function createProtocolListing(aUrl) {
  var li = document.createElement("li");
  var link = document.createElement("a");
  var text = document.createTextNode(aUrl);

  link.href = aUrl;
  link.appendChild(text);
  li.appendChild(link);
  gContainer.appendChild(li);
}
PK
!<Q���3chrome/toolkit/content/global/aboutCheckerboard.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

table.listing {
  width: 100%;
}

table th, table td {
  padding: 5px;
  border: inset 2px black;
  margin: 0;
  width: 50%;
  vertical-align: top;
}

hr {
  clear: both;
  margin: 10px;
}

iframe {
  width: 100%;
  height: 900px;
}

#player, #raw {
  width: 800px;
  margin-inline: auto;
}

#controls {
  text-align: center;
}

#canvas {
  border: solid 1px black;
}

#active {
  width: 100%;
  border: solid 1px black;
  margin-top: 0;
}

#trace {
  width: 100%;
}

#enabled {
  color: red;
}

#enabled.enabled {
  color: green;
}
PK
!<��V�m	m	4chrome/toolkit/content/global/aboutCheckerboard.html<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE html>
<html>
  <head>
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:; object-src 'none'"
    />
    <meta name="viewport" content="width=device-width" />
    <meta charset="utf-8" />
    <title>Checkerboard Analyzer</title>
    <link
      rel="stylesheet"
      href="chrome://global/content/aboutCheckerboard.css"
      type="text/css"
    />
  </head>

  <body>
    <p>
      Checkerboard recording is <span id="enabled">undetermined</span>.
      <button id="enableToggleButton">Toggle it!</button>.
    </p>
    <p>
      If there are active reports in progress, you can stop and flush them by
      clicking here:
      <button id="flushReportsButton">Flush active reports</button>
    </p>
    <table class="listing" cellspacing="0">
      <tr>
        <th>Most severe checkerboarding reports</th>
        <th>Most recent checkerboarding reports</th>
      </tr>
      <tr>
        <td><ul id="severe"></ul></td>
        <td><ul id="recent"></ul></td>
      </tr>
    </table>

    <hr />

    <div id="player">
      <div id="controls">
        <button id="rewindButton">&#171;</button
        ><!-- rewind button -->
        <button id="stepBackButton">&lt;</button
        ><!-- step back button -->
        <button id="pauseButton">|| &#9654;</button
        ><!-- pause button -->
        <button id="stopButton">&#9744;</button
        ><!-- stop button -->
        <button id="stepForwardButton">&gt;</button
        ><!-- step forward button -->
        <button id="forwardButton">&#187;</button
        ><!-- forward button -->
      </div>
      <canvas id="canvas" width="800" height="600"
        >Canvas not supported!</canvas
      >
      <pre id="active">(Details for currently visible replay frame)</pre>
    </div>

    <hr />

    <div id="raw">
      Raw log:<br />
      <textarea id="trace" rows="10"></textarea>
      <div>
        <input type="checkbox" id="excludePageFromZoom" /><label
          for="excludePageFromZoom"
          >Exclude page coordinates from zoom calculations</label
        ><br />
      </div>
    </div>
  </body>
  <script src="chrome://global/content/aboutCheckerboard.js"></script>
</html>
PK
!<O���    2chrome/toolkit/content/global/aboutCheckerboard.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var trace;
var service;
var reports;

function onLoad() {
  trace = document.getElementById("trace");
  service = new CheckerboardReportService();
  updateEnabled();
  reports = service.getReports();
  for (let i = 0; i < reports.length; i++) {
    let text =
      "Severity " +
      reports[i].severity +
      " at " +
      new Date(reports[i].timestamp).toString();
    let link = document.createElement("a");
    link.href = "#";
    link.addEventListener("click", function () {
      showReport(i);
      return false;
    });
    link.textContent = text;
    let bullet = document.createElement("li");
    bullet.appendChild(link);
    document.getElementById(reports[i].reason).appendChild(bullet);
  }
}

function updateEnabled() {
  let enabled = document.getElementById("enabled");
  let isEnabled = service.isRecordingEnabled();
  if (isEnabled) {
    enabled.textContent = "enabled";
  } else {
    enabled.textContent = "disabled";
  }
  enabled.classList.toggle("enabled", isEnabled);
}

function toggleEnabled() {
  service.setRecordingEnabled(!service.isRecordingEnabled());
  updateEnabled();
}

function flushReports() {
  service.flushActiveReports();
}

function showReport(index) {
  trace.value = reports[index].log;
  loadData();
}

// -- Code to load and render the trace --

const CANVAS_USE_RATIO = 0.75;
const FRAME_INTERVAL_MS = 50;
const VECTOR_NORMALIZED_MAGNITUDE = 30.0;

var renderData = [];
var currentFrame = 0;
var playing = false;
var timerId = 0;

var minX = undefined;
var minY = undefined;
var maxX = undefined;
var maxY = undefined;

function log(x) {
  if (console) {
    console.log(x);
  }
}

function getFlag(flag) {
  return document.getElementById(flag).checked;
}

// parses the lines in the textarea, ignoring anything that doesn't have RENDERTRACE.
// for each matching line, tokenizes on whitespace and ignores all tokens prior to
// RENDERTRACE. Additional info can be included at the end of the line, and will be
// displayed but not parsed. Allowed syntaxes:
//   <junk> RENDERTRACE <timestamp> rect <color> <x> <y> <width> <height> [extraInfo]
function loadData() {
  stopPlay();
  renderData = [];
  currentFrame = 0;
  minX = undefined;
  minY = undefined;
  maxX = undefined;
  maxY = undefined;

  var charPos = 0;
  var lastLineLength = 0;
  var lines = trace.value.split(/\r|\n/);
  for (var i = 0; i < lines.length; i++) {
    charPos += lastLineLength;
    lastLineLength = lines[i].length + 1;
    // skip lines without RENDERTRACE
    if (!/RENDERTRACE/.test(lines[i])) {
      continue;
    }

    var tokens = lines[i].split(/\s+/);
    var j = 0;
    // skip tokens until RENDERTRACE
    // eslint-disable-next-line no-empty
    while (j < tokens.length && tokens[j++] != "RENDERTRACE") {} // empty loop body
    if (j >= tokens.length - 2) {
      log("Error parsing line: " + lines[i]);
      continue;
    }

    var timestamp = tokens[j++];
    var destIndex = renderData.length;
    if (destIndex == 0) {
      // create the initial frame
      renderData.push({
        timestamp,
        rects: {},
      });
    } else if (renderData[destIndex - 1].timestamp == timestamp) {
      // timestamp hasn't changed use, so update the previous object
      destIndex--;
    } else {
      // clone a new copy of the last frame and update timestamp
      renderData.push(JSON.parse(JSON.stringify(renderData[destIndex - 1])));
      renderData[destIndex].timestamp = timestamp;
    }

    switch (tokens[j++]) {
      case "rect":
        if (j > tokens.length - 5) {
          log("Error parsing line: " + lines[i]);
          continue;
        }

        var rect = {};
        var color = tokens[j++];
        renderData[destIndex].rects[color] = rect;
        rect.x = parseFloat(tokens[j++]);
        rect.y = parseFloat(tokens[j++]);
        rect.width = parseFloat(tokens[j++]);
        rect.height = parseFloat(tokens[j++]);
        rect.dataText = trace.value.substring(
          charPos,
          charPos + lines[i].length
        );

        if (!getFlag("excludePageFromZoom") || color != "brown") {
          if (typeof minX == "undefined") {
            minX = rect.x;
            minY = rect.y;
            maxX = rect.x + rect.width;
            maxY = rect.y + rect.height;
          } else {
            minX = Math.min(minX, rect.x);
            minY = Math.min(minY, rect.y);
            maxX = Math.max(maxX, rect.x + rect.width);
            maxY = Math.max(maxY, rect.y + rect.height);
          }
        }
        break;

      default:
        log("Error parsing line " + lines[i]);
        break;
    }
  }

  if (!renderFrame()) {
    alert("No data found; nothing to render!");
  }
}

// render the current frame (i.e. renderData[currentFrame])
// returns false if currentFrame is out of bounds, true otherwise
function renderFrame() {
  var frame = currentFrame;
  if (frame < 0 || frame >= renderData.length) {
    log("Invalid frame index");
    return false;
  }

  var canvas = document.getElementById("canvas");
  if (!canvas.getContext) {
    log("No canvas context");
  }

  var context = canvas.getContext("2d");

  // midpoint of the bounding box
  var midX = (minX + maxX) / 2.0;
  var midY = (minY + maxY) / 2.0;

  // midpoint of the canvas
  var cmx = canvas.width / 2.0;
  var cmy = canvas.height / 2.0;

  // scale factor
  var scale =
    CANVAS_USE_RATIO *
    Math.min(canvas.width / (maxX - minX), canvas.height / (maxY - minY));

  function projectX(value) {
    return cmx + (value - midX) * scale;
  }

  function projectY(value) {
    return cmy + (value - midY) * scale;
  }

  function drawRect(color, rect) {
    context.strokeStyle = color;
    context.strokeRect(
      projectX(rect.x),
      projectY(rect.y),
      rect.width * scale,
      rect.height * scale
    );
  }

  // clear canvas
  context.fillStyle = "white";
  context.fillRect(0, 0, canvas.width, canvas.height);
  var activeData = "";
  // draw rects
  for (var i in renderData[frame].rects) {
    drawRect(i, renderData[frame].rects[i]);
    activeData += "\n" + renderData[frame].rects[i].dataText;
  }
  // draw timestamp and frame counter
  context.fillStyle = "black";
  context.fillText(
    frame + 1 + "/" + renderData.length + ": " + renderData[frame].timestamp,
    5,
    15
  );

  document.getElementById("active").textContent = activeData;

  return true;
}

// -- Player controls --

function reset(beginning) {
  currentFrame = beginning ? 0 : renderData.length - 1;
  renderFrame();
}

function step(backwards) {
  if (playing) {
    togglePlay();
  }
  currentFrame += backwards ? -1 : 1;
  if (!renderFrame()) {
    currentFrame -= backwards ? -1 : 1;
  }
}

function pause() {
  clearInterval(timerId);
  playing = false;
}

function togglePlay() {
  if (playing) {
    pause();
  } else {
    timerId = setInterval(function () {
      currentFrame++;
      if (!renderFrame()) {
        currentFrame--;
        togglePlay();
      }
    }, FRAME_INTERVAL_MS);
    playing = true;
  }
}

function stopPlay() {
  if (playing) {
    togglePlay();
  }
  currentFrame = 0;
  renderFrame();
}

document.getElementById("pauseButton").addEventListener("click", togglePlay);
document.getElementById("stopButton").addEventListener("click", stopPlay);
document
  .getElementById("enableToggleButton")
  .addEventListener("click", toggleEnabled);
document
  .getElementById("flushReportsButton")
  .addEventListener("click", flushReports);
document
  .getElementById("excludePageFromZoom")
  .addEventListener("click", loadData);
document
  .getElementById("stepForwardButton")
  .addEventListener("click", function () {
    step(false);
  });
document.getElementById("forwardButton").addEventListener("click", function () {
  reset(false);
});
document.getElementById("rewindButton").addEventListener("click", function () {
  reset(true);
});
document
  .getElementById("stepBackButton")
  .addEventListener("click", function () {
    step(true);
  });
window.addEventListener("load", onLoad);
PK
!<H6L��,chrome/toolkit/content/global/aboutGlean.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/in-content/common.css");

#tag-pings:invalid + label > span {
  font-weight: var(--font-weight-bold);
  text-decoration: underline;
  color: var(--text-color-error);
}
PK
!<��3 $$-chrome/toolkit/content/global/aboutGlean.html<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html>

<html>
  <head>
    <meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'"/>
    <meta charset="utf-8">
    <meta name="color-scheme" content="light dark">
    <title data-l10n-id="about-glean-page-title2"></title>
    <link rel="stylesheet" href="chrome://global/content/aboutGlean.css"/>

    <script src="chrome://global/content/aboutGlean.js"></script>
    <link rel="localization" href="branding/brand.ftl"/>
    <link rel="localization" href="toolkit/about/aboutGlean.ftl"/>
  </head>

  <body>
    <section class="main-content">
      <div id="description">
        <h2 data-l10n-id="about-glean-header"></h2>
        <p data-l10n-id="about-glean-interface-description">
          <a data-l10n-name="glean-sdk-doc-link" href="https://mozilla.github.io/glean/book/index.html"></a>
          <a data-l10n-name="fog-link" href="https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/instrumentation_tests.html"></a>
        </p>
        <p id="upload-status" data-l10n-id="about-glean-upload-enabled"></p>
        <p>
          <span id="about-glean-prefs-and-defines" data-l10n-id="about-glean-prefs-and-defines">
            <a data-l10n-name="fog-prefs-and-defines-doc-link" href="https://firefox-source-docs.mozilla.org/toolkit/components/glean/dev/preferences.html"></a>
          </span>
          <ul>
            <li data-l10n-id="about-glean-data-upload" data-l10n-args='{"data-upload-pref-value": "unknown"}'></li>
            <li data-l10n-id="about-glean-local-port" data-l10n-args='{"local-port-pref-value": "unknown"}'></li>
            <li data-l10n-id="about-glean-glean-android" data-l10n-args='{"glean-android-define-value": "unknown"}'></li>
            <li data-l10n-id="about-glean-moz-official" data-l10n-args='{"moz-official-define-value": "unknown"}'></li>
          </ul>
        </p>
        <h2 data-l10n-id="about-glean-about-testing-header"></h2>
        <p data-l10n-id="about-glean-manual-testing">
          <a data-l10n-name="fog-instrumentation-test-doc-link" href="https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/instrumentation_tests.html"></a>
          <a data-l10n-name="glean-sdk-doc-link" href="https://mozilla.github.io/glean/book/index.html"></a>
        </p>
        <ol>
          <li>
            <input type="text" name="tag-pings" id="tag-pings" pattern="[a-zA-Z0-9\-]{1,20}">
            <label for="tag-pings" data-l10n-id="about-glean-label-for-tag-pings-with-requirements"></label>
          </li>
          <li>
            <select name="ping-names" id="ping-names"></select>
            <label for="ping-names" data-l10n-id="about-glean-label-for-ping-names">
              <a data-l10n-name="custom-ping-link" href="https://mozilla.github.io/glean/book/user/pings/custom.html"></a>
            </label>
          </li>
          <li>
            <input type="checkbox" name="log-pings" id="log-pings">
            <label for="log-pings" data-l10n-id="about-glean-label-for-log-pings">
              <a data-l10n-name="enable-logging-link" href="http://firefox-source-docs.mozilla.org/toolkit/components/glean/dev/testing.html#logging"></a>
            </label>
          </li>
          <li>
            <button id="controls-submit" data-l10n-id="controls-button-label-verbose"></button>
            <label for="controls-submit" data-l10n-id="about-glean-label-for-controls-submit" data-l10n-args='{"debug-tag": "unknown"}'>
              <span id="submit-tag-span"></span>
            </label>
          </li>
          <li data-l10n-id="about-glean-li-for-visit-gdpv">
            <a data-l10n-name="gdpv-tagged-pings-link" href="https://debug-ping-preview.firebaseapp.com/pings/"></a>
          </li>
        </ol>
        <p data-l10n-id="about-glean-adhoc-explanation2"></p>
        <p data-l10n-id="about-glean-adhoc-note"></p>
        <h2 data-l10n-id="about-glean-about-data-header"></h2>
        <p data-l10n-id="about-glean-about-data-explanation">
          <a data-l10n-name="glean-dictionary-link" href="https://dictionary.telemetry.mozilla.org/"></a>
        </p>
      </div>
    </section>
  </body>

</html>
PK
!<�[�TMM+chrome/toolkit/content/global/aboutGlean.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const { AppConstants } = ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);

function updatePrefsAndDefines() {
  let upload = Services.prefs.getBoolPref(
    "datareporting.healthreport.uploadEnabled"
  );
  document.l10n.setAttributes(
    document.querySelector("[data-l10n-id='about-glean-data-upload']"),
    "about-glean-data-upload",
    {
      "data-upload-pref-value": upload,
    }
  );
  let port = Services.prefs.getIntPref("telemetry.fog.test.localhost_port");
  document.l10n.setAttributes(
    document.querySelector("[data-l10n-id='about-glean-local-port']"),
    "about-glean-local-port",
    {
      "local-port-pref-value": port,
    }
  );
  document.l10n.setAttributes(
    document.querySelector("[data-l10n-id='about-glean-glean-android']"),
    "about-glean-glean-android",
    { "glean-android-define-value": AppConstants.MOZ_GLEAN_ANDROID }
  );
  document.l10n.setAttributes(
    document.querySelector("[data-l10n-id='about-glean-moz-official']"),
    "about-glean-moz-official",
    { "moz-official-define-value": AppConstants.MOZILLA_OFFICIAL }
  );

  // Knowing what we know, and copying logic from viaduct_uploader.rs,
  // (which is documented in Preferences and Defines),
  // tell the fine user whether and why upload is disabled.
  let uploadMessageEl = document.getElementById("upload-status");
  let uploadL10nId = "about-glean-upload-enabled";
  if (!upload) {
    uploadL10nId = "about-glean-upload-disabled";
  } else if (port < 0 || (port == 0 && !AppConstants.MOZILLA_OFFICIAL)) {
    uploadL10nId = "about-glean-upload-fake-enabled";
    // This message has a link to the Glean Debug Ping Viewer in it.
    // We must add the anchor element now so that Fluent can match it.
    let a = document.createElement("a");
    a.href = "https://debug-ping-preview.firebaseapp.com/";
    a.setAttribute("data-l10n-name", "glean-debug-ping-viewer");
    uploadMessageEl.appendChild(a);
  } else if (port > 0) {
    uploadL10nId = "about-glean-upload-enabled-local";
  }
  document.l10n.setAttributes(uploadMessageEl, uploadL10nId);
}

function camelToKebab(str) {
  let out = "";
  for (let i = 0; i < str.length; i++) {
    let c = str.charAt(i);
    if (c == c.toUpperCase()) {
      out += "-";
      c = c.toLowerCase();
    }
    out += c;
  }
  return out;
}

// I'm consciously omitting "deletion-request" until someone can come up with
// a use-case for sending it via about:glean.
const GLEAN_BUILTIN_PINGS = ["metrics", "events", "baseline"];
const NO_PING = "(don't submit any ping)";
function refillPingNames() {
  let select = document.getElementById("ping-names");
  let pings = GLEAN_BUILTIN_PINGS.slice().concat(Object.keys(GleanPings));

  pings.forEach(ping => {
    let option = document.createElement("option");
    option.textContent = camelToKebab(ping);
    select.appendChild(option);
  });
  let option = document.createElement("option");
  document.l10n.setAttributes(option, "about-glean-no-ping-label");
  option.value = NO_PING;
  select.appendChild(option);
}

// If there's been a previous tag, use it.
// If not, be _slightly_ clever and derive a default one from the profile dir.
function fillDebugTag() {
  const DEBUG_TAG_PREF = "telemetry.fog.aboutGlean.debugTag";
  let debugTag;
  if (Services.prefs.prefHasUserValue(DEBUG_TAG_PREF)) {
    debugTag = Services.prefs.getStringPref(DEBUG_TAG_PREF);
  } else {
    const debugTagPrefix = "about-glean-";
    const profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile).path;
    let charSum = Array.from(profileDir).reduce(
      (prev, cur) => prev + cur.charCodeAt(0),
      0
    );

    debugTag = debugTagPrefix + (charSum % 1000);
  }

  let tagInput = document.getElementById("tag-pings");
  tagInput.value = debugTag;
  const updateDebugTagValues = () => {
    document.l10n.setAttributes(
      document.querySelector(
        "[data-l10n-id='about-glean-label-for-controls-submit']"
      ),
      "about-glean-label-for-controls-submit",
      { "debug-tag": tagInput.value }
    );
    const GDPV_ROOT = "https://debug-ping-preview.firebaseapp.com/pings/";
    let gdpvLink = document.querySelector(
      "[data-l10n-name='gdpv-tagged-pings-link']"
    );
    gdpvLink.href = GDPV_ROOT + tagInput.value;
  };
  tagInput.addEventListener("change", () => {
    Services.prefs.setStringPref(DEBUG_TAG_PREF, tagInput.value);
    updateDebugTagValues();
  });
  updateDebugTagValues();
}

function onLoad() {
  updatePrefsAndDefines();
  refillPingNames();
  fillDebugTag();
  document.getElementById("controls-submit").addEventListener("click", () => {
    let tag = document.getElementById("tag-pings").value;
    let log = document.getElementById("log-pings").checked;
    let ping = document.getElementById("ping-names").value;
    Services.fog.setLogPings(log);
    Services.fog.setTagPings(tag);
    if (ping != NO_PING) {
      Services.fog.sendPing(ping);
    }
  });
}

window.addEventListener("load", onLoad);
PK
!<�0�@@/chrome/toolkit/content/global/aboutLogging.html<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:; object-src 'none'"
    />
    <meta name="color-scheme" content="light dark" />
    <title data-l10n-id="about-logging-title"></title>
    <link rel="stylesheet" href="chrome://global/skin/aboutLogging.css" />
    <script src="chrome://global/content/aboutLogging.js"></script>
    <link rel="localization" href="toolkit/about/aboutLogging.ftl" />
    <link rel="localization" href="toolkit/branding/brandings.ftl" />
  </head>

  <body id="body">
    <main class="main-content">
      <h1 id="title" data-l10n-id="about-logging-page-title"></h1>
      <section>
        <div hidden id="error" class="page-subsection info-box">
          <div class="info-box-label" data-l10n-id="about-logging-error"></div>
          <div id="error-description"></div>
          <div data-l10n-id="about-logging-configuration-url-ignored"></div>
        </div>
        <div
          hidden
          id="some-elements-unavailable"
          class="page-subsection info-box"
        >
          <div class="info-box-label" data-l10n-id="about-logging-info"></div>
          <div data-l10n-id="about-logging-some-elements-disabled"></div>
        </div>
      </section>
      <div class="button-row">
        <button
          id="toggle-logging-button"
          data-l10n-id="about-logging-start-logging"
        ></button>
      </div>
      <section id="log-module-selection">
        <h2 data-l10n-id="about-logging-log-modules-selection"></h2>
        <div class="page-subsection">
          <label
            for="current-log-modules"
            data-l10n-id="about-logging-currently-enabled-log-modules"
          ></label>
          <div id="current-log-modules"></div>
          <div
            id="no-log-modules"
            data-l10n-id="about-logging-no-log-modules"
          ></div>
        </div>
        <form id="log-modules-form" class="page-subsection">
          <label
            for="log-modules"
            data-l10n-id="about-logging-new-log-modules"
          ></label>
          <input type="text" name="log-modules" id="log-modules" value="" />
          <div class="button-row">
            <button
              type="submit"
              id="set-log-modules-button"
              data-l10n-id="about-logging-set-log-modules"
            ></button>
          </div>
        </form>
        <div id="preset-selector-section" class="page-subsection">
          <label
            for="logging-preset-dropdown"
            data-l10n-id="about-logging-logging-preset-selector-text"
          ></label>
          <select
            name="logging-preset-dropdown"
            id="logging-preset-dropdown"
          ></select>
          <div id="logging-preset-description"></div>
        </div>
      </section>
      <section id="logging-output">
        <div>
          <span
            hidden
            id="buttons-disabled"
            data-l10n-id="about-logging-buttons-disabled"
          ></span>
        </div>
        <h2 data-l10n-id="about-logging-logging-output-selection"></h2>
        <div id="logging-output-profiler" class="form-entry">
          <input
            type="radio"
            id="radio-logging-profiler"
            name="logging-output"
            value="profiler"
            checked
          />
          <label
            for="radio-logging-profiler"
            data-l10n-id="about-logging-logging-to-profiler"
          ></label>
        </div>
        <div id="profiler-configuration" class="form-entry">
          <input type="checkbox" id="with-profiler-stacks-checkbox" /><label
            for="with-profiler-stacks-checkbox"
            data-l10n-id="about-logging-with-profiler-stacks-checkbox"
          ></label>
        </div>
        <div id="logging-output-file" class="form-entry">
          <input
            type="radio"
            id="radio-logging-file"
            name="logging-output"
            value="file"
          />
          <label
            for="radio-logging-file"
            data-l10n-id="about-logging-logging-to-file"
          ></label>
          <div>
            <span
              hidden
              id="buttons-disabled"
              data-l10n-id="about-logging-buttons-disabled"
            ></span>
          </div>
        </div>
        <form id="log-file-configuration" class="page-subsection">
          <div>
            <span data-l10n-id="about-logging-current-log-file"></span>
            <span id="current-log-file"></span>
            <span
              id="no-log-file"
              data-l10n-id="about-logging-no-log-file"
            ></span>
          </div>
          <div>
            <label
              for="log-file"
              data-l10n-id="about-logging-new-log-file"
            ></label>
            <input type="text" name="log-file" id="log-file" />
            <div class="button-row">
              <button
                type="button"
                id="open-log-file-button"
                data-l10n-id="about-logging-open-log-file-dir"
              ></button>
              <button
                type="submit"
                id="set-log-file-button"
                data-l10n-id="about-logging-set-log-file"
              ></button>
            </div>
          </div>
        </form>
        <div class="page-subsection">
          <p id="log-tutorial" data-l10n-id="about-logging-log-tutorial">
            <a
              data-l10n-name="logging"
              href="https://firefox-source-docs.mozilla.org/networking/http/logging.html"
            ></a>
          </p>
        </div>
      </section>
    </main>
  </body>
</html>
PK
!<S�SbSb-chrome/toolkit/content/global/aboutLogging.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";
const { XPCOMUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/XPCOMUtils.sys.mjs"
);
const gDashboard = Cc["@mozilla.org/network/dashboard;1"].getService(
  Ci.nsIDashboard
);
const gDirServ = Cc["@mozilla.org/file/directory_service;1"].getService(
  Ci.nsIDirectoryServiceProvider
);

const { ProfilerMenuButton } = ChromeUtils.importESModule(
  "resource://devtools/client/performance-new/popup/menu-button.sys.mjs"
);
const { CustomizableUI } = ChromeUtils.importESModule(
  "resource:///modules/CustomizableUI.sys.mjs"
);

ChromeUtils.defineLazyGetter(this, "ProfilerPopupBackground", function () {
  return ChromeUtils.importESModule(
    "resource://devtools/client/performance-new/shared/background.sys.mjs"
  );
});

const $ = document.querySelector.bind(document);
const $$ = document.querySelectorAll.bind(document);

function fileEnvVarPresent() {
  return Services.env.get("MOZ_LOG_FILE") || Services.env.get("NSPR_LOG_FILE");
}

function moduleEnvVarPresent() {
  return Services.env.get("MOZ_LOG") || Services.env.get("NSPR_LOG");
}

/**
 * All the information associated with a logging presets:
 * - `modules` is the list of log modules and option, the same that would have
 *   been set as a MOZ_LOG environment variable
 * - l10nIds.label and l10nIds.description are the Ids of the strings that
 *   appear in the dropdown selector, and a one-liner describing the purpose of
 *   a particular logging preset
 * - profilerPreset is the name of a Firefox Profiler preset [1]. In general,
 *   the profiler preset will have the correct set of threads for a particular
 *   logging preset, so that all logging statements are recorded in the profile
 *   as markers.
 *
 * [1]: The keys of the `presets` object defined in
 * https://searchfox.org/mozilla-central/source/devtools/client/performance-new/shared/background.sys.mjs
 */

const gOsSpecificLoggingPresets = (() => {
  // Microsoft Windows
  if (navigator.platform.startsWith("Win")) {
    return {
      windows: {
        modules:
          "timestamp,sync,Widget:5,BaseWidget:5,WindowsEvent:4,TaskbarConcealer:5,FileDialog:5",
        l10nIds: {
          label: "about-logging-preset-windows-label",
          description: "about-logging-preset-windows-description",
        },
      },
    };
  }

  return {};
})();

const gLoggingPresets = {
  networking: {
    modules:
      "timestamp,sync,nsHttp:5,cache2:5,nsSocketTransport:5,nsHostResolver:5,EarlyHint:5",
    l10nIds: {
      label: "about-logging-preset-networking-label",
      description: "about-logging-preset-networking-description",
    },
    profilerPreset: "networking",
  },
  cookie: {
    modules: "timestamp,sync,nsHttp:5,cache2:5,cookie:5",
    l10nIds: {
      label: "about-logging-preset-networking-cookie-label",
      description: "about-logging-preset-networking-cookie-description",
    },
  },
  websocket: {
    modules:
      "timestamp,sync,nsHttp:5,nsWebSocket:5,nsSocketTransport:5,nsHostResolver:5",
    l10nIds: {
      label: "about-logging-preset-networking-websocket-label",
      description: "about-logging-preset-networking-websocket-description",
    },
  },
  http3: {
    modules:
      "timestamp,sync,nsHttp:5,nsSocketTransport:5,nsHostResolver:5,neqo_http3::*:5,neqo_transport::*:5",
    l10nIds: {
      label: "about-logging-preset-networking-http3-label",
      description: "about-logging-preset-networking-http3-description",
    },
  },
  "http3-upload-speed": {
    modules: "timestamp,neqo_transport::*:3",
    l10nIds: {
      label: "about-logging-preset-networking-http3-upload-speed-label",
      description:
        "about-logging-preset-networking-http3-upload-speed-description",
    },
  },
  "media-playback": {
    modules:
      "HTMLMediaElement:4,HTMLMediaElementEvents:4,cubeb:5,PlatformDecoderModule:5,AudioSink:5,AudioSinkWrapper:5,MediaDecoderStateMachine:4,MediaDecoder:4,MediaFormatReader:5,GMP:5,EME:5,MediaSource:5,MediaSourceSamples:5,Autoplay:5",
    l10nIds: {
      label: "about-logging-preset-media-playback-label",
      description: "about-logging-preset-media-playback-description",
    },
    profilerPreset: "media",
  },
  webrtc: {
    modules:
      "jsep:5,sdp:5,signaling:5,mtransport:5,RTCRtpReceiver:5,RTCRtpSender:5,RTCDMTFSender:5,VideoFrameConverter:5,WebrtcTCPSocket:5,CamerasChild:5,CamerasParent:5,VideoEngine:5,ShmemPool:5,TabShare:5,MediaChild:5,MediaParent:5,MediaManager:5,MediaTrackGraph:5,cubeb:5,MediaStream:5,MediaStreamTrack:5,DriftCompensator:5,ForwardInputTrack:5,MediaRecorder:5,MediaEncoder:5,TrackEncoder:5,VP8TrackEncoder:5,Muxer:5,GetUserMedia:5,MediaPipeline:5,PeerConnectionImpl:5,WebAudioAPI:5,webrtc_trace:5,RTCRtpTransceiver:5,ForwardedInputTrack:5,HTMLMediaElement:5,HTMLMediaElementEvents:5",
    l10nIds: {
      label: "about-logging-preset-webrtc-label",
      description: "about-logging-preset-webrtc-description",
    },
    profilerPreset: "media",
  },
  webgpu: {
    modules:
      "wgpu_core::*:5,wgpu_hal::*:5,wgpu_types::*:5,naga::*:5,wgpu_bindings::*:5,WebGPU:5",
    l10nIds: {
      label: "about-logging-preset-webgpu-label",
      description: "about-logging-preset-webgpu-description",
    },
  },
  gfx: {
    modules:
      "webrender::*:5,webrender_bindings::*:5,webrender_types::*:5,gfx2d:5,WebRenderBridgeParent:5,DcompSurface:5,apz.displayport:5,layout:5,dl.content:5,dl.parent:5,nsRefreshDriver:5,fontlist:5,fontinit:5,textrun:5,textrunui:5,textperf:5",
    l10nIds: {
      label: "about-logging-preset-gfx-label",
      description: "about-logging-preset-gfx-description",
    },
    // The graphics profiler preset enables the threads we want but loses the screenshots.
    // We could add an extra preset for that if we miss it.
    profilerPreset: "graphics",
  },
  ...gOsSpecificLoggingPresets,
  custom: {
    modules: "",
    l10nIds: {
      label: "about-logging-preset-custom-label",
      description: "about-logging-preset-custom-description",
    },
  },
};

const gLoggingSettings = {
  // Possible values: "profiler" and "file".
  loggingOutputType: "profiler",
  running: false,
  // If non-null, the profiler preset to use. If null, the preset selected in
  // the dropdown is going to be used. It is also possible to use a "custom"
  // preset and an explicit list of modules.
  loggingPreset: null,
  // If non-null, the profiler preset to use. If a logging preset is being used,
  // and this is null, the profiler preset associated to the logging preset is
  // going to be used. Otherwise, a generic profiler preset is going to be used
  // ("firefox-platform").
  profilerPreset: null,
  // If non-null, the threads that will be recorded by the Firefox Profiler. If
  // null, the threads from the profiler presets are going to be used.
  profilerThreads: null,
  // If non-null, stack traces will be recorded for MOZ_LOG profiler markers.
  // This is set only when coming from the URL, not when the user changes the UI.
  profilerStacks: null,
};

// When the profiler has been started, this holds the promise the
// Services.profiler.StartProfiler returns, to ensure the profiler has
// effectively started.
let gProfilerPromise = null;

// Used in tests
function presets() {
  return gLoggingPresets;
}

// Used in tests
function settings() {
  return gLoggingSettings;
}

// Used in tests
function profilerPromise() {
  return gProfilerPromise;
}

function populatePresets() {
  let dropdown = $("#logging-preset-dropdown");
  for (let presetName in gLoggingPresets) {
    let preset = gLoggingPresets[presetName];
    let option = document.createElement("option");
    document.l10n.setAttributes(option, preset.l10nIds.label);
    option.value = presetName;
    dropdown.appendChild(option);
    if (option.value === gLoggingSettings.loggingPreset) {
      option.setAttribute("selected", true);
    }
  }

  function setPresetAndDescription(preset) {
    document.l10n.setAttributes(
      $("#logging-preset-description"),
      gLoggingPresets[preset].l10nIds.description
    );
    gLoggingSettings.loggingPreset = preset;
  }

  dropdown.onchange = function () {
    // When switching to custom, leave the existing module list, to allow
    // editing.
    if (dropdown.value != "custom") {
      $("#log-modules").value = gLoggingPresets[dropdown.value].modules;
    }
    setPresetAndDescription(dropdown.value);
    Services.prefs.setCharPref("logging.config.preset", dropdown.value);
  };

  $("#log-modules").value = gLoggingPresets[dropdown.value].modules;
  setPresetAndDescription(dropdown.value);
  // When changing the list switch to custom.
  $("#log-modules").oninput = () => {
    dropdown.value = "custom";
  };
}

function updateLoggingOutputType(profilerOutputType) {
  gLoggingSettings.loggingOutputType = profilerOutputType;
  Services.prefs.setCharPref("logging.config.output_type", profilerOutputType);
  $(`input[type=radio][value=${profilerOutputType}]`).checked = true;

  switch (profilerOutputType) {
    case "profiler":
      if (!gLoggingSettings.profilerStacks) {
        // If this value is set from the URL, do not allow to change it.
        $("#with-profiler-stacks-checkbox").disabled = false;
      }
      // hide options related to file output for clarity
      $("#log-file-configuration").hidden = true;
      break;
    case "file":
      $("#with-profiler-stacks-checkbox").disabled = true;
      $("#log-file-configuration").hidden = false;
      $("#no-log-file").hidden = !!$("#current-log-file").innerText.length;
      break;
  }
}

function displayErrorMessage(error) {
  var err = $("#error");
  err.hidden = false;

  var errorDescription = $("#error-description");
  document.l10n.setAttributes(errorDescription, error.l10nId, {
    k: error.key,
    v: error.value,
  });
}

class ParseError extends Error {
  constructor(l10nId, key, value) {
    super(name);
    this.l10nId = l10nId;
    this.key = key;
    this.value = value;
  }
  name = "ParseError";
  l10nId;
  key;
  value;
}

function parseURL() {
  let options = new URL(document.location.href).searchParams;

  if (!options) {
    return;
  }

  let modulesOverriden = null,
    outputTypeOverriden = null,
    loggingPresetOverriden = null,
    threadsOverriden = null,
    profilerPresetOverriden = null,
    profilerStacksOverriden = null;
  try {
    for (let [k, v] of options) {
      switch (k) {
        case "modules":
        case "module":
          modulesOverriden = v;
          break;
        case "output":
        case "output-type":
          if (v !== "profiler" && v !== "file") {
            throw new ParseError("about-logging-invalid-output", k, v);
          }
          outputTypeOverriden = v;
          break;
        case "preset":
        case "logging-preset":
          if (!Object.keys(gLoggingPresets).includes(v)) {
            throw new ParseError("about-logging-unknown-logging-preset", k, v);
          }
          loggingPresetOverriden = v;
          break;
        case "threads":
        case "thread":
          threadsOverriden = v;
          break;
        case "profiler-preset":
          if (!Object.keys(ProfilerPopupBackground.presets).includes(v)) {
            throw new Error(["about-logging-unknown-profiler-preset", k, v]);
          }
          profilerPresetOverriden = v;
          break;
        case "profilerstacks":
          profilerStacksOverriden = true;
          break;
        default:
          throw new ParseError("about-logging-unknown-option", k, v);
      }
    }
  } catch (e) {
    displayErrorMessage(e);
    return;
  }

  // Detect combinations that don't make sense
  if (
    (profilerPresetOverriden || threadsOverriden) &&
    outputTypeOverriden == "file"
  ) {
    displayErrorMessage(
      new ParseError("about-logging-file-and-profiler-override")
    );
    return;
  }

  // Configuration is deemed at least somewhat valid, override each setting in
  // turn
  let someElementsDisabled = false;

  if (modulesOverriden || loggingPresetOverriden) {
    // Don't allow changing those if set by the URL
    let logModules = $("#log-modules");
    var dropdown = $("#logging-preset-dropdown");
    if (loggingPresetOverriden) {
      dropdown.value = loggingPresetOverriden;
      dropdown.onchange();
    }
    if (modulesOverriden) {
      logModules.value = modulesOverriden;
      dropdown.value = "custom";
      dropdown.onchange();
      dropdown.disabled = true;
      someElementsDisabled = true;
    }
    logModules.disabled = true;
    $("#set-log-modules-button").disabled = true;
    $("#logging-preset-dropdown").disabled = true;
    someElementsDisabled = true;
    updateLogModules();
  }
  if (outputTypeOverriden) {
    $$("input[type=radio]").forEach(e => (e.disabled = true));
    someElementsDisabled = true;
    updateLoggingOutputType(outputTypeOverriden);
  }
  if (profilerStacksOverriden) {
    const checkbox = $("#with-profiler-stacks-checkbox");
    checkbox.disabled = true;
    someElementsDisabled = true;
    Services.prefs.setBoolPref("logging.config.profilerstacks", true);
    gLoggingSettings.profilerStacks = true;
  }

  if (loggingPresetOverriden) {
    gLoggingSettings.loggingPreset = loggingPresetOverriden;
  }
  if (profilerPresetOverriden) {
    gLoggingSettings.profilerPreset = profilerPresetOverriden;
  }
  if (threadsOverriden) {
    gLoggingSettings.profilerThreads = threadsOverriden;
  }

  $("#some-elements-unavailable").hidden = !someElementsDisabled;
}

let gInited = false;
function init() {
  if (gInited) {
    return;
  }
  gInited = true;
  gDashboard.enableLogging = true;

  populatePresets();
  parseURL();

  $("#log-file-configuration").addEventListener("submit", e => {
    e.preventDefault();
    setLogFile();
  });

  $("#log-modules-form").addEventListener("submit", e => {
    e.preventDefault();
    setLogModules();
  });

  let toggleLoggingButton = $("#toggle-logging-button");
  toggleLoggingButton.addEventListener("click", startStopLogging);

  $$("input[type=radio]").forEach(radio => {
    radio.onchange = e => {
      updateLoggingOutputType(e.target.value);
    };
  });

  $("#with-profiler-stacks-checkbox").addEventListener("change", e => {
    Services.prefs.setBoolPref(
      "logging.config.profilerstacks",
      e.target.checked
    );
    updateLogModules();
  });

  let loggingOutputType = Services.prefs.getCharPref(
    "logging.config.output_type",
    "profiler"
  );
  if (loggingOutputType.length) {
    updateLoggingOutputType(loggingOutputType);
  }

  $("#with-profiler-stacks-checkbox").checked = Services.prefs.getBoolPref(
    "logging.config.profilerstacks",
    false
  );

  try {
    let loggingPreset = Services.prefs.getCharPref("logging.config.preset");
    gLoggingSettings.loggingPreset = loggingPreset;
  } catch {}

  try {
    let running = Services.prefs.getBoolPref("logging.config.running");
    gLoggingSettings.running = running;
    document.l10n.setAttributes(
      $("#toggle-logging-button"),
      `about-logging-${gLoggingSettings.running ? "stop" : "start"}-logging`
    );
  } catch {}

  try {
    let file = gDirServ.getFile("TmpD", {});
    file.append("log.txt");
    $("#log-file").value = file.path;
  } catch (e) {
    console.error(e);
  }

  // Update the value of the log file.
  updateLogFile();

  // Update the active log modules
  updateLogModules();

  // If we can't set the file and the modules at runtime,
  // the start and stop buttons wouldn't really do anything.
  if (
    ($("#set-log-file-button").disabled ||
      $("#set-log-modules-button").disabled) &&
    moduleEnvVarPresent()
  ) {
    $("#buttons-disabled").hidden = false;
    toggleLoggingButton.disabled = true;
  }
}

function updateLogFile(file) {
  let logPath = "";

  // Try to get the environment variable for the log file
  logPath =
    Services.env.get("MOZ_LOG_FILE") || Services.env.get("NSPR_LOG_FILE");
  let currentLogFile = $("#current-log-file");
  let setLogFileButton = $("#set-log-file-button");

  // If the log file was set from an env var, we disable the ability to set it
  // at runtime.
  if (logPath.length) {
    currentLogFile.innerText = logPath;
    setLogFileButton.disabled = true;
  } else if (gDashboard.getLogPath() != ".moz_log") {
    // There may be a value set by a pref.
    currentLogFile.innerText = gDashboard.getLogPath();
  } else if (file !== undefined) {
    currentLogFile.innerText = file;
  } else {
    try {
      let file = gDirServ.getFile("TmpD", {});
      file.append("log.txt");
      $("#log-file").value = file.path;
    } catch (e) {
      console.error(e);
    }
    // Fall back to the temp dir
    currentLogFile.innerText = $("#log-file").value;
  }

  let openLogFileButton = $("#open-log-file-button");
  openLogFileButton.disabled = true;

  if (currentLogFile.innerText.length) {
    let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
    file.initWithPath(currentLogFile.innerText);

    if (file.exists()) {
      openLogFileButton.disabled = false;
      openLogFileButton.onclick = function () {
        file.reveal();
      };
    }
  }
  $("#no-log-file").hidden = !!currentLogFile.innerText.length;
  $("#current-log-file").hidden = !currentLogFile.innerText.length;
}

function updateLogModules() {
  // Try to get the environment variable for the log file
  let logModules =
    Services.env.get("MOZ_LOG") ||
    Services.env.get("MOZ_LOG_MODULES") ||
    Services.env.get("NSPR_LOG_MODULES");
  let currentLogModules = $("#current-log-modules");
  let setLogModulesButton = $("#set-log-modules-button");
  if (logModules.length) {
    currentLogModules.innerText = logModules;
    // If the log modules are set by an environment variable at startup, do not
    // allow changing them throught a pref. It would be difficult to figure out
    // which ones are enabled and which ones are not. The user probably knows
    // what he they are doing.
    setLogModulesButton.disabled = true;
  } else {
    let activeLogModules = [];
    let children = Services.prefs.getBranch("logging.").getChildList("");

    for (let pref of children) {
      if (pref.startsWith("config.")) {
        continue;
      }

      try {
        let value = Services.prefs.getIntPref(`logging.${pref}`);
        activeLogModules.push(`${pref}:${value}`);
      } catch (e) {
        console.error(e);
      }
    }

    if (activeLogModules.length) {
      // Add some options only if some modules are present.
      if (Services.prefs.getBoolPref("logging.config.add_timestamp", false)) {
        activeLogModules.push("timestamp");
      }
      if (Services.prefs.getBoolPref("logging.config.sync", false)) {
        activeLogModules.push("sync");
      }
      if (Services.prefs.getBoolPref("logging.config.profilerstacks", false)) {
        activeLogModules.push("profilerstacks");
      }
    }

    if (activeLogModules.length !== 0) {
      currentLogModules.innerText = activeLogModules.join(",");
      currentLogModules.hidden = false;
      $("#no-log-modules").hidden = true;
    } else {
      currentLogModules.innerText = "";
      currentLogModules.hidden = true;
      $("#no-log-modules").hidden = false;
    }
  }
}

function setLogFile() {
  let setLogButton = $("#set-log-file-button");
  if (setLogButton.disabled) {
    // There's no point trying since it wouldn't work anyway.
    return;
  }
  let logFile = $("#log-file").value.trim();
  Services.prefs.setCharPref("logging.config.LOG_FILE", logFile);
  updateLogFile(logFile);
}

function clearLogModules() {
  // Turn off all the modules.
  let children = Services.prefs.getBranch("logging.").getChildList("");
  for (let pref of children) {
    if (!pref.startsWith("config.")) {
      Services.prefs.clearUserPref(`logging.${pref}`);
    }
  }
  Services.prefs.clearUserPref("logging.config.add_timestamp");
  Services.prefs.clearUserPref("logging.config.sync");
  updateLogModules();
}

function setLogModules() {
  if (moduleEnvVarPresent()) {
    // The modules were set via env var, so we shouldn't try to change them.
    return;
  }

  let modules = $("#log-modules").value.trim();

  // Clear previously set log modules.
  clearLogModules();

  if (modules.length !== 0) {
    let logModules = modules.split(",");
    for (let module of logModules) {
      if (module == "timestamp") {
        Services.prefs.setBoolPref("logging.config.add_timestamp", true);
      } else if (module == "rotate") {
        // XXX: rotate is not yet supported.
      } else if (module == "append") {
        // XXX: append is not yet supported.
      } else if (module == "sync") {
        Services.prefs.setBoolPref("logging.config.sync", true);
      } else if (module == "profilerstacks") {
        Services.prefs.setBoolPref("logging.config.profilerstacks", true);
      } else {
        let lastColon = module.lastIndexOf(":");
        let key = module.slice(0, lastColon);
        let value = parseInt(module.slice(lastColon + 1), 10);
        Services.prefs.setIntPref(`logging.${key}`, value);
      }
    }
  }

  updateLogModules();
}

function isLogging() {
  try {
    return Services.prefs.getBoolPref("logging.config.running");
  } catch {
    return false;
  }
}

function startStopLogging() {
  if (isLogging()) {
    document.l10n.setAttributes(
      $("#toggle-logging-button"),
      "about-logging-start-logging"
    );
    stopLogging();
  } else {
    document.l10n.setAttributes(
      $("#toggle-logging-button"),
      "about-logging-stop-logging"
    );
    startLogging();
  }
}

function startLogging() {
  setLogModules();
  if (gLoggingSettings.loggingOutputType === "profiler") {
    const pageContext = "aboutlogging";
    const supportedFeatures = Services.profiler.GetFeatures();
    if (
      gLoggingSettings.loggingPreset != "custom" ||
      gLoggingSettings.profilerPreset
    ) {
      // Change the preset before starting the profiler, so that the
      // underlying profiler code picks up the right configuration.
      // If a profiler preset has been explicitely provided (via URL parameters),
      // pick it. Otherwise, pick the preset for this particular logging preset.
      const profilerPreset =
        gLoggingSettings.profilerPreset ??
        gLoggingPresets[gLoggingSettings.loggingPreset].profilerPreset;
      ProfilerPopupBackground.changePreset(
        "aboutlogging",
        profilerPreset,
        supportedFeatures
      );
    } else {
      // a baseline set of threads, and possibly others, overriden by the URL
      ProfilerPopupBackground.changePreset(
        "aboutlogging",
        "firefox-platform",
        supportedFeatures
      );
    }
    let { entries, interval, features, threads, duration } =
      ProfilerPopupBackground.getRecordingSettings(
        pageContext,
        Services.profiler.GetFeatures()
      );

    if (gLoggingSettings.profilerThreads) {
      threads.push(...gLoggingSettings.profilerThreads.split(","));
      // Small hack: if cubeb is being logged, it's almost always necessary (and
      // never harmful) to enable audio callback tracing, otherwise, no log
      // statements will be recorded from real-time threads.
      if (gLoggingSettings.profilerThreads.includes("cubeb")) {
        features.push("audiocallbacktracing");
      }
    }
    const win = Services.wm.getMostRecentWindow("navigator:browser");
    const windowid = win?.gBrowser?.selectedBrowser?.browsingContext?.browserId;

    // Force displaying the profiler button in the navbar if not preset, so
    // that there is a visual indication profiling is in progress.
    if (!ProfilerMenuButton.isInNavbar()) {
      // Ensure the widget is enabled.
      Services.prefs.setBoolPref(
        "devtools.performance.popup.feature-flag",
        true
      );
      // Enable the profiler menu button.
      ProfilerMenuButton.addToNavbar();
      // Dispatch the change event manually, so that the shortcuts will also be
      // added.
      CustomizableUI.dispatchToolboxEvent("customizationchange");
    }

    gProfilerPromise = Services.profiler.StartProfiler(
      entries,
      interval,
      features,
      threads,
      windowid,
      duration
    );
  } else {
    setLogFile();
  }
  Services.prefs.setBoolPref("logging.config.running", true);
}

async function stopLogging() {
  if (gLoggingSettings.loggingOutputType === "profiler") {
    await ProfilerPopupBackground.captureProfile("aboutlogging");
  } else {
    Services.prefs.clearUserPref("logging.config.LOG_FILE");
    updateLogFile();
  }
  Services.prefs.setBoolPref("logging.config.running", false);
  clearLogModules();
}

// We use the pageshow event instead of onload. This is needed because sometimes
// the page is loaded via session-restore/bfcache. In such cases we need to call
// init() to keep the page behaviour consistent with the ticked checkboxes.
// Mostly the issue is with the autorefresh checkbox.
window.addEventListener("pageshow", function () {
  init();
});
PK
!<�����-chrome/toolkit/content/global/aboutMemory.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * The version used for mobile is located at
 * toolkit/themes/mobile/global/aboutMemory.css.
 * Desktop-specific stuff is at the bottom of this file.
 */

html {
  font: message-box;
}

body {
  padding: 0 2em;
  min-width: 45em;
  margin: auto;
}

/* The comment at the top of aboutMemory.xhtml explains this font choice. */
pre {
  font-family: DejaVu Sans Mono, Liberation Mono, Fira Mono, monospace;
}

div.ancillary {
  margin: 0.5em 0;
  user-select: none;
}

div.section {
  padding: 2em;
  margin: 1em 0em;
  border: 1px solid var(--in-content-border-color);
  border-radius: var(--border-radius-small);
  background: var(--in-content-box-background);
}

div.outputContainer {
  display: flex;
}

div.sections {
  flex: 1;
  min-width: 0;
}

div.sidebar {
  flex: 0 0 max-content;
  margin-left: 1em;
}

div.sidebarContents {
  position: sticky;
  top: 0.5em;
}

div.sidebarItem {
  padding: 0.5em;
  margin: 1em 0em;
  border: 1px solid var(--in-content-border-color);
  border-radius: var(--border-radius-small);
  background: var(--in-content-box-background);
  user-select: none;  /* no need to include this when cutting+pasting */
}

input.filterInput {
  width: calc(100% - 1em);
}

ul.index {
  list-style-position: inside;
  margin: 0;
  padding: 0;
}

ul.index > li {
  padding-left: 0.5em;
}

div.opsRow {
  padding: 0.5em;
  margin-right: 0.5em;
  margin-top: 0.5em;
  border: 1px solid var(--in-content-border-color);
  border-radius: var(--border-radius-small);
  background: var(--in-content-box-background);
  display: inline-block;
}

div.opsRowLabel, div.sidebarLabel {
  display: block;
  margin-bottom: 0.2em;
  font-weight: bold;
}

.opsRowLabel label {
  margin-left: 1em;
  font-weight: normal;
}

div.non-verbose pre.entries {
  overflow-x: hidden;
  text-overflow: ellipsis;
}

h1 {
  padding: 0;
  margin: 0;
  background: inherit; /* When sticky give it the solid background of the parent */
  display: inline;  /* allow subsequent text to the right of the heading */
  position: sticky; /* Stay at the top of the page when scrolling */
  top: 0;
  z-index: 1; /* When sticky it should appear above the tree lines */
}

h2 {
  padding-left: .1em;
}

h3 {
  display: inline;  /* allow subsequent text to the right of the heading */
}

a.upDownArrow {
  font-size: 130%;
  text-decoration: none;
  user-select: none;  /* no need to include this when cutting+pasting */
}

.accuracyWarning,
.badInputWarning,
.invalid {
  color: var(--text-color-error);
}

.treeline {
  color: FieldText;
  opacity: 0.5;
}

.mrValue {
  font-weight: bold;
}

.hasKids {
  cursor: pointer;
}

.hasKids:hover {
  text-decoration: underline;
}

.noselect {
  user-select: none;  /* no need to include this when cutting+pasting */
}

.option {
  font-size: 80%;
  user-select: none;  /* no need to include this when cutting+pasting */
}

.legend {
  font-size: 80%;
  user-select: none;  /* no need to include this when cutting+pasting */
}

.debug {
  font-size: 80%;
}

.hidden {
  display: none;
}

/* Desktop-specific parts go here. */

.hasKids:hover {
  text-decoration: underline;
}
PK
!<.���'�',chrome/toolkit/content/global/aboutMemory.js/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-*/
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// You can direct about:memory to immediately load memory reports from a file
// by providing a file= query string.  For example,
//
//     about:memory?file=/home/username/reports.json.gz
//
// "file=" is not case-sensitive.  We'll URI-unescape the contents of the
// "file=" argument, and obviously the filename is case-sensitive iff you're on
// a case-sensitive filesystem.  If you specify more than one "file=" argument,
// only the first one is used.

"use strict";

// ---------------------------------------------------------------------------

let CC = Components.Constructor;

const KIND_NONHEAP = Ci.nsIMemoryReporter.KIND_NONHEAP;
const KIND_HEAP = Ci.nsIMemoryReporter.KIND_HEAP;
const KIND_OTHER = Ci.nsIMemoryReporter.KIND_OTHER;

const UNITS_BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
const UNITS_COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
const UNITS_COUNT_CUMULATIVE = Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE;
const UNITS_PERCENTAGE = Ci.nsIMemoryReporter.UNITS_PERCENTAGE;

const { XPCOMUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/XPCOMUtils.sys.mjs"
);
const { NetUtil } = ChromeUtils.importESModule(
  "resource://gre/modules/NetUtil.sys.mjs"
);
ChromeUtils.defineESModuleGetters(this, {
  Downloads: "resource://gre/modules/Downloads.sys.mjs",
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
});

ChromeUtils.defineLazyGetter(this, "nsBinaryStream", () =>
  CC(
    "@mozilla.org/binaryinputstream;1",
    "nsIBinaryInputStream",
    "setInputStream"
  )
);
ChromeUtils.defineLazyGetter(this, "nsFile", () =>
  CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath")
);
ChromeUtils.defineLazyGetter(this, "nsGzipConverter", () =>
  CC(
    "@mozilla.org/streamconv;1?from=gzip&to=uncompressed",
    "nsIStreamConverter"
  )
);

let gMgr = Cc["@mozilla.org/memory-reporter-manager;1"].getService(
  Ci.nsIMemoryReporterManager
);

const gPageName = "about:memory";
document.title = gPageName;

const gMainProcessPrefix = "Main Process";

const gFilterUpdateDelayMS = 300;

let gIsDiff = false;

let gCurrentReports = [];
let gCurrentHasMozMallocUsableSize = false;
let gCurrentIsDiff = false;

let gFilter = "";

// ---------------------------------------------------------------------------

// Forward slashes in URLs in paths are represented with backslashes to avoid
// being mistaken for path separators.  Paths/names where this hasn't been
// undone are prefixed with "unsafe"; the rest are prefixed with "safe".
function flipBackslashes(aUnsafeStr) {
  // Save memory by only doing the replacement if it's necessary.
  return !aUnsafeStr.includes("\\")
    ? aUnsafeStr
    : aUnsafeStr.replace(/\\/g, "/");
}

const gAssertionFailureMsgPrefix = "aboutMemory.js assertion failed: ";

// This is used for things that should never fail, and indicate a defect in
// this file if they do.
function assert(aCond, aMsg) {
  if (!aCond) {
    reportAssertionFailure(aMsg);
    throw new Error(gAssertionFailureMsgPrefix + aMsg);
  }
}

// This is used for malformed input from memory reporters.
function assertInput(aCond, aMsg) {
  if (!aCond) {
    throw new Error(`Invalid memory report(s): ${aMsg}`);
  }
}

function handleException(aEx) {
  let str = "" + aEx;
  if (str.startsWith(gAssertionFailureMsgPrefix)) {
    // Argh, assertion failure within this file!  Give up.
    throw aEx;
  } else {
    // File or memory reporter problem.  Print a message.
    updateMainAndFooter(str, NO_TIMESTAMP, HIDE_FOOTER, "badInputWarning");
  }
}

function reportAssertionFailure(aMsg) {
  let debug = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2);
  if (debug.isDebugBuild) {
    debug.assertion(aMsg, "false", "aboutMemory.js", 0);
  }
}

function debug(aVal) {
  let section = appendElement(document.body, "div", "section");
  appendElementWithText(section, "div", "debug", JSON.stringify(aVal));
}

function stringMatchesFilter(aString, aFilter) {
  assert(
    typeof aFilter == "string" || aFilter instanceof RegExp,
    "unexpected aFilter type"
  );

  return typeof aFilter == "string"
    ? aString.includes(aFilter)
    : aFilter.test(aString);
}

// ---------------------------------------------------------------------------

window.onunload = function () {};

// ---------------------------------------------------------------------------

// The <div> holding everything but the header and footer (if they're present).
// It's what is updated each time the page changes.
let gMain;

// The <div> holding the footer.
let gFooter;

// The "verbose" checkbox.
let gVerbose;

// The "anonymize" checkbox.
let gAnonymize;

// Values for the |aFooterAction| argument to updateTitleMainAndFooter.
const HIDE_FOOTER = 0;
const SHOW_FOOTER = 1;

// Values for the |aShowTimestamp| argument to updateTitleMainAndFooter.
const NO_TIMESTAMP = 0;
const SHOW_TIMESTAMP = 1;

function updateTitleMainAndFooter(
  aTitleNote,
  aMsg,
  aShowTimestamp,
  aFooterAction,
  aClassName
) {
  document.title = gPageName;
  if (aTitleNote) {
    document.title += ` (${aTitleNote})`;
  }

  // Clear gMain by replacing it with an empty node.
  let tmp = gMain.cloneNode(false);
  gMain.parentNode.replaceChild(tmp, gMain);
  gMain = tmp;

  gMain.classList.remove("hidden");
  gMain.classList.remove("verbose");
  gMain.classList.remove("non-verbose");
  if (gVerbose) {
    gMain.classList.add(gVerbose.checked ? "verbose" : "non-verbose");
  }

  let msgElement;
  if (aMsg) {
    let className = "section";
    if (aClassName) {
      className = className + " " + aClassName;
    }
    if (aShowTimestamp == SHOW_TIMESTAMP) {
      // JS has many options for pretty-printing timestamps. We use
      // toISOString() because it has sub-second granularity, which is useful
      // if you quickly and repeatedly click one of the buttons.
      aMsg += ` (${new Date().toISOString()})`;
    }
    msgElement = appendElementWithText(gMain, "div", className, aMsg);
  }

  switch (aFooterAction) {
    case HIDE_FOOTER:
      gFooter.classList.add("hidden");
      break;
    case SHOW_FOOTER:
      gFooter.classList.remove("hidden");
      break;
    default:
      assert(false, "bad footer action in updateTitleMainAndFooter");
  }
  return msgElement;
}

function updateMainAndFooter(aMsg, aShowTimestamp, aFooterAction, aClassName) {
  return updateTitleMainAndFooter(
    "",
    aMsg,
    aShowTimestamp,
    aFooterAction,
    aClassName
  );
}

function appendTextNode(aP, aText) {
  let e = document.createTextNode(aText);
  aP.appendChild(e);
  return e;
}

function appendElement(aP, aTagName, aClassName) {
  let e = newElement(aTagName, aClassName);
  aP.appendChild(e);
  return e;
}

function appendElementWithText(aP, aTagName, aClassName, aText) {
  let e = appendElement(aP, aTagName, aClassName);
  // Setting textContent clobbers existing children, but there are none.  More
  // importantly, it avoids creating a JS-land object for the node, saving
  // memory.
  e.textContent = aText;
  return e;
}

function newElement(aTagName, aClassName) {
  let e = document.createElement(aTagName);
  if (aClassName) {
    e.className = aClassName;
  }
  return e;
}

// ---------------------------------------------------------------------------

const explicitTreeDescription =
  "This tree covers explicit memory allocations by the application.  It includes \
\n\n\
* all allocations made at the heap allocation level (via functions such as malloc, \
calloc, realloc, memalign, operator new, and operator new[]) that have not been \
explicitly decommitted (i.e. evicted from memory and swap), and \
\n\n\
* some allocations (those covered by memory reporters) made at the operating \
system level (via calls to functions such as VirtualAlloc, vm_allocate, and \
mmap), \
\n\n\
* where possible, the overhead of the heap allocator itself.\
\n\n\
It excludes memory that is mapped implicitly such as code and data segments, \
and thread stacks. \
\n\n\
'explicit' is not guaranteed to cover every explicit allocation, but it does cover \
most (including the entire heap), and therefore it is the single best number to \
focus on when trying to reduce memory usage.";

// ---------------------------------------------------------------------------

function appendButton(aP, aTitle, aOnClick, aText, aId) {
  let b = appendElementWithText(aP, "button", "", aText);
  b.title = aTitle;
  b.onclick = aOnClick;
  if (aId) {
    b.id = aId;
  }
  return b;
}

function appendHiddenFileInput(aP, aId, aChangeListener) {
  let input = appendElementWithText(aP, "input", "hidden", "");
  input.type = "file";
  input.id = aId; // used in testing
  input.addEventListener("change", aChangeListener);
  return input;
}

window.onload = function () {
  // Generate the header.

  let header = appendElement(document.body, "div", "ancillary");

  // A hidden file input element that can be invoked when necessary.
  let fileInput1 = appendHiddenFileInput(header, "fileInput1", function () {
    let file = this.files[0];
    let filename = file.mozFullPath;
    updateAboutMemoryFromFile(filename);
  });

  // Ditto.
  let fileInput2 = appendHiddenFileInput(
    header,
    "fileInput2",
    function (aElem) {
      let file = this.files[0];
      // First time around, we stash a copy of the filename and reinvoke.  Second
      // time around we do the diff and display.
      if (!this.filename1) {
        this.filename1 = file.mozFullPath;

        // aElem.skipClick is only true when testing -- it allows fileInput2's
        // onchange handler to be re-called without having to go via the file
        // picker.
        if (!aElem.skipClick) {
          this.click();
        }
      } else {
        let filename1 = this.filename1;
        delete this.filename1;
        updateAboutMemoryFromTwoFiles(filename1, file.mozFullPath);
      }
    }
  );

  const CuDesc = "Measure current memory reports and show.";
  const LdDesc = "Load memory reports from file and show.";
  const DfDesc =
    "Load memory report data from two files and show the difference.";

  const SvDesc = "Save memory reports to file.";

  const GCDesc = "Do a global garbage collection.";
  const CCDesc = "Do a cycle collection.";
  const MMDesc =
    'Send three "heap-minimize" notifications in a ' +
    "row.  Each notification triggers a global garbage " +
    "collection followed by a cycle collection, and causes the " +
    "process to reduce memory usage in other ways, e.g. by " +
    "flushing various caches.";

  const GCAndCCLogDesc =
    "Save garbage collection log and concise cycle " +
    "collection log.\n" +
    "WARNING: These logs may be large (>1GB).";
  const GCAndCCAllLogDesc =
    "Save garbage collection log and verbose cycle " +
    "collection log.\n" +
    "WARNING: These logs may be large (>1GB).";

  const DMDEnabledDesc =
    "Analyze memory reports coverage and save the " +
    "output to the temp directory.\n";
  const DMDDisabledDesc =
    "DMD is not running. Please re-start with $DMD and " +
    "the other relevant environment variables set " +
    "appropriately.";

  let ops = appendElement(header, "div", "");

  let row1 = appendElement(ops, "div", "opsRow");

  let labelDiv1 = appendElementWithText(
    row1,
    "div",
    "opsRowLabel",
    "Show memory reports"
  );
  labelDiv1.setAttribute("role", "heading");
  labelDiv1.setAttribute("aria-level", "1");
  let label1 = appendElementWithText(labelDiv1, "label", "");
  gVerbose = appendElement(label1, "input", "");
  gVerbose.type = "checkbox";
  gVerbose.id = "verbose"; // used for testing
  appendTextNode(label1, "verbose");

  // The "measureButton" id is used for testing.
  appendButton(row1, CuDesc, doMeasure, "Measure", "measureButton");
  appendButton(row1, LdDesc, () => fileInput1.click(), "Load…");
  appendButton(row1, DfDesc, () => fileInput2.click(), "Load and diff…");

  let row2 = appendElement(ops, "div", "opsRow");

  let labelDiv2 = appendElementWithText(
    row2,
    "div",
    "opsRowLabel",
    "Save memory reports"
  );
  labelDiv2.setAttribute("role", "heading");
  labelDiv2.setAttribute("aria-level", "1");
  appendButton(row2, SvDesc, saveReportsToFile, "Measure and save…");

  // XXX: this isn't a great place for this checkbox, but I can't think of
  // anywhere better.
  let label2 = appendElementWithText(labelDiv2, "label", "");
  gAnonymize = appendElement(label2, "input", "");
  gAnonymize.type = "checkbox";
  appendTextNode(label2, "anonymize");

  let row3 = appendElement(ops, "div", "opsRow");

  let labelDiv3 = appendElementWithText(
    row3,
    "div",
    "opsRowLabel",
    "Free memory"
  );
  labelDiv3.setAttribute("role", "heading");
  labelDiv3.setAttribute("aria-level", "1");
  appendButton(row3, GCDesc, doGC, "GC");
  appendButton(row3, CCDesc, doCC, "CC");
  appendButton(row3, MMDesc, doMMU, "Minimize memory usage");

  let row4 = appendElement(ops, "div", "opsRow");

  let labelDiv4 = appendElementWithText(
    row4,
    "div",
    "opsRowLabel",
    "Save GC & CC logs"
  );
  labelDiv4.setAttribute("role", "heading");
  labelDiv4.setAttribute("aria-level", "1");
  appendButton(
    row4,
    GCAndCCLogDesc,
    saveGCLogAndConciseCCLog,
    "Save concise",
    "saveLogsConcise"
  );
  appendButton(
    row4,
    GCAndCCAllLogDesc,
    saveGCLogAndVerboseCCLog,
    "Save verbose",
    "saveLogsVerbose"
  );

  // Three cases here:
  // - DMD is disabled (i.e. not built): don't show the button.
  // - DMD is enabled but is not running: show the button, but disable it.
  // - DMD is enabled and is running: show the button and enable it.
  if (gMgr.isDMDEnabled) {
    let row5 = appendElement(ops, "div", "opsRow");

    let labelDiv5 = appendElementWithText(
      row5,
      "div",
      "opsRowLabel",
      "Save DMD output"
    );
    labelDiv5.setAttribute("role", "heading");
    labelDiv5.setAttribute("aria-level", "1");
    let enableButtons = gMgr.isDMDRunning;

    let dmdButton = appendButton(
      row5,
      enableButtons ? DMDEnabledDesc : DMDDisabledDesc,
      doDMD,
      "Save"
    );
    dmdButton.disabled = !enableButtons;
  }

  // Generate the main div, where content ("section" divs) will go.  It's
  // hidden at first.

  gMain = appendElement(document.body, "div", "");
  gMain.id = "mainDiv";

  // Generate the footer.  It's hidden at first.

  gFooter = appendElement(document.body, "div", "ancillary hidden");
  gFooter.setAttribute("role", "contentinfo");

  if (Services.policies.isAllowed("aboutSupport")) {
    let a = appendElementWithText(
      gFooter,
      "a",
      "option",
      "Troubleshooting information"
    );
    a.href = "about:support";
  }

  let legendText1 =
    "Click on a non-leaf node in a tree to expand ('++') " +
    "or collapse ('--') its children.";
  let legendText2 =
    "Hover the pointer over the name of a memory report " +
    "to see a description of what it measures.";

  appendElementWithText(gFooter, "div", "legend", legendText1);
  appendElementWithText(gFooter, "div", "legend hiddenOnMobile", legendText2);

  // See if we're loading from a file.  (Because about:memory is a non-standard
  // URL, location.search is undefined, so we have to use location.href
  // instead.)
  let search = location.href.split("?")[1];
  if (search) {
    let searchSplit = search.split("&");
    for (let s of searchSplit) {
      if (s.toLowerCase().startsWith("file=")) {
        let filename = s.substring("file=".length);
        updateAboutMemoryFromFile(decodeURIComponent(filename));
        return;
      }
    }
  }
};

// ---------------------------------------------------------------------------

function doGC() {
  Services.obs.notifyObservers(null, "child-gc-request");
  Cu.forceGC();
  updateMainAndFooter(
    "Garbage collection completed",
    SHOW_TIMESTAMP,
    HIDE_FOOTER
  );
}

function doCC() {
  Services.obs.notifyObservers(null, "child-cc-request");
  window.windowUtils.cycleCollect();
  updateMainAndFooter(
    "Cycle collection completed",
    SHOW_TIMESTAMP,
    HIDE_FOOTER
  );
}

function doMMU() {
  Services.obs.notifyObservers(null, "child-mmu-request");
  gMgr.minimizeMemoryUsage(() =>
    updateMainAndFooter(
      "Memory minimization completed",
      SHOW_TIMESTAMP,
      HIDE_FOOTER
    )
  );
}

function doMeasure() {
  updateAboutMemoryFromReporters();
}

function saveGCLogAndConciseCCLog() {
  dumpGCLogAndCCLog(false);
}

function saveGCLogAndVerboseCCLog() {
  dumpGCLogAndCCLog(true);
}

function doDMD() {
  updateMainAndFooter(
    "Saving memory reports and DMD output...",
    NO_TIMESTAMP,
    HIDE_FOOTER
  );
  try {
    let dumper = Cc["@mozilla.org/memory-info-dumper;1"].getService(
      Ci.nsIMemoryInfoDumper
    );

    dumper.dumpMemoryInfoToTempDir(
      /* identifier = */ "",
      gAnonymize.checked,
      /* minimize = */ false
    );
    updateMainAndFooter(
      "Saved memory reports and DMD reports analysis " +
        "to the temp directory",
      SHOW_TIMESTAMP,
      HIDE_FOOTER
    );
  } catch (ex) {
    updateMainAndFooter(ex.toString(), NO_TIMESTAMP, HIDE_FOOTER);
  }
}

function dumpGCLogAndCCLog(aVerbose) {
  let dumper = Cc["@mozilla.org/memory-info-dumper;1"].getService(
    Ci.nsIMemoryInfoDumper
  );

  let inProgress = updateMainAndFooter(
    "Saving logs...",
    NO_TIMESTAMP,
    HIDE_FOOTER
  );
  let section = appendElement(gMain, "div", "section");

  function displayInfo(aGCLog, aCCLog) {
    appendElementWithText(section, "div", "", "Saved GC log to " + aGCLog.path);

    let ccLogType = aVerbose ? "verbose" : "concise";
    appendElementWithText(
      section,
      "div",
      "",
      "Saved " + ccLogType + " CC log to " + aCCLog.path
    );
  }

  dumper.dumpGCAndCCLogsToFile("", aVerbose, /* dumpChildProcesses = */ true, {
    onDump: displayInfo,
    onFinish() {
      inProgress.remove();
    },
  });
}

/**
 * Top-level function that does the work of generating the page from the memory
 * reporters.
 */
function updateAboutMemoryFromReporters() {
  updateMainAndFooter("Measuring...", NO_TIMESTAMP, HIDE_FOOTER);

  try {
    gCurrentReports = [];
    gCurrentHasMozMallocUsableSize = gMgr.hasMozMallocUsableSize;
    gCurrentIsDiff = false;
    gFilter = "";

    // Record the reports from the live memory reporters then process them.
    let handleReport = function (
      aProcess,
      aUnsafePath,
      aKind,
      aUnits,
      aAmount,
      aDescription
    ) {
      gCurrentReports.push({
        process: aProcess,
        path: aUnsafePath,
        kind: aKind,
        units: aUnits,
        amount: aAmount,
        description: aDescription,
      });
    };

    let displayReports = function () {
      updateTitleMainAndFooter(
        "live measurement",
        "",
        NO_TIMESTAMP,
        SHOW_FOOTER
      );
      updateAboutMemoryFromCurrentData();
    };

    gMgr.getReports(
      handleReport,
      null,
      displayReports,
      null,
      gAnonymize.checked
    );
  } catch (ex) {
    handleException(ex);
  }
}

// Increment this if the JSON format changes.
//
let gCurrentFileFormatVersion = 1;

/**
 * Parse a string as JSON and extract the |memory_report| property if it has
 * one, which indicates the string is from a crash dump.
 *
 * @param aStr
 *        The string.
 * @return The extracted object.
 */
function parseAndUnwrapIfCrashDump(aStr) {
  let obj = JSON.parse(aStr);
  if (obj.memory_report !== undefined) {
    // It looks like a crash dump. The memory reports should be in the
    // |memory_report| property.
    obj = obj.memory_report;
  }
  return obj;
}

/**
 * Populate about:memory using the data stored in gCurrentReports and
 * gCurrentHasMozMallocUsableSize.
 */
function updateAboutMemoryFromCurrentData() {
  function processCurrentMemoryReports(aHandleReport, aDisplayReports) {
    for (let r of gCurrentReports) {
      aHandleReport(
        r.process,
        r.path,
        r.kind,
        r.units,
        r.amount,
        r.description,
        r._presence
      );
    }
    aDisplayReports();
  }

  gIsDiff = gCurrentIsDiff;
  appendAboutMemoryMain(
    processCurrentMemoryReports,
    gFilter,
    gCurrentHasMozMallocUsableSize
  );
  gIsDiff = false;
}

/**
 * Populate about:memory using the data in the given JSON object.
 *
 * @param aObj
 *        An object that (hopefully!) conforms to the JSON schema used by
 *        nsIMemoryInfoDumper.
 */
function updateAboutMemoryFromJSONObject(aObj) {
  try {
    assertInput(
      aObj.version === gCurrentFileFormatVersion,
      "data version number missing or doesn't match"
    );
    assertInput(
      aObj.hasMozMallocUsableSize !== undefined,
      "missing 'hasMozMallocUsableSize' property"
    );
    assertInput(
      aObj.reports && aObj.reports instanceof Array,
      "missing or non-array 'reports' property"
    );

    gCurrentReports = aObj.reports.concat();
    gCurrentHasMozMallocUsableSize = aObj.hasMozMallocUsableSize;
    gCurrentIsDiff = gIsDiff;
    gFilter = "";

    updateAboutMemoryFromCurrentData();
  } catch (ex) {
    handleException(ex);
  }
}

/**
 * Populate about:memory using the data in the given JSON string.
 *
 * @param aStr
 *        A string containing JSON data conforming to the schema used by
 *        nsIMemoryReporterManager::dumpReports.
 */
function updateAboutMemoryFromJSONString(aStr) {
  try {
    let obj = parseAndUnwrapIfCrashDump(aStr);
    updateAboutMemoryFromJSONObject(obj);
  } catch (ex) {
    handleException(ex);
  }
}

/**
 * Loads the contents of a file into a string and passes that to a callback.
 *
 * @param aFilename
 *        The name of the file being read from.
 * @param aTitleNote
 *        A description to put in the page title upon completion.
 * @param aFn
 *        The function to call and pass the read string to upon completion.
 */
function loadMemoryReportsFromFile(aFilename, aTitleNote, aFn) {
  updateMainAndFooter("Loading...", NO_TIMESTAMP, HIDE_FOOTER);

  try {
    let reader = new FileReader();
    reader.onerror = () => {
      throw new Error("FileReader.onerror");
    };
    reader.onabort = () => {
      throw new Error("FileReader.onabort");
    };
    reader.onload = aEvent => {
      // Clear "Loading..." from above.
      updateTitleMainAndFooter(aTitleNote, "", NO_TIMESTAMP, SHOW_FOOTER);
      aFn(aEvent.target.result);
    };

    // If it doesn't have a .gz suffix, read it as a (legacy) ungzipped file.
    if (!aFilename.endsWith(".gz")) {
      File.createFromFileName(aFilename).then(file => {
        reader.readAsText(file);
      });
      return;
    }

    // Read compressed gzip file.
    let converter = new nsGzipConverter();
    converter.asyncConvertData(
      "gzip",
      "uncompressed",
      {
        data: [],
        onStartRequest() {},
        onDataAvailable(aR, aStream, aO, aCount) {
          let bi = new nsBinaryStream(aStream);
          this.data.push(bi.readBytes(aCount));
        },
        onStopRequest(aR, aC, aStatusCode) {
          try {
            if (!Components.isSuccessCode(aStatusCode)) {
              throw new Components.Exception(
                "Error while reading gzip file",
                aStatusCode
              );
            }
            reader.readAsText(new Blob(this.data));
          } catch (ex) {
            handleException(ex);
          }
        },
      },
      null
    );

    let file = new nsFile(aFilename);
    let fileChan = NetUtil.newChannel({
      uri: Services.io.newFileURI(file),
      loadUsingSystemPrincipal: true,
    });
    fileChan.asyncOpen(converter);
  } catch (ex) {
    handleException(ex);
  }
}

/**
 * Like updateAboutMemoryFromReporters(), but gets its data from a file instead
 * of the memory reporters.
 *
 * @param aFilename
 *        The name of the file being read from.  The expected format of the
 *        file's contents is described in a comment in nsIMemoryInfoDumper.idl.
 */
function updateAboutMemoryFromFile(aFilename) {
  loadMemoryReportsFromFile(
    aFilename,
    /* title note */ aFilename,
    updateAboutMemoryFromJSONString
  );
}

/**
 * Like updateAboutMemoryFromFile(), but gets its data from a two files and
 * diffs them.
 *
 * @param aFilename1
 *        The name of the first file being read from.
 * @param aFilename2
 *        The name of the first file being read from.
 */
function updateAboutMemoryFromTwoFiles(aFilename1, aFilename2) {
  let titleNote = `diff of ${aFilename1} and ${aFilename2}`;
  loadMemoryReportsFromFile(aFilename1, titleNote, function (aStr1) {
    loadMemoryReportsFromFile(aFilename2, titleNote, function (aStr2) {
      try {
        let obj1 = parseAndUnwrapIfCrashDump(aStr1);
        let obj2 = parseAndUnwrapIfCrashDump(aStr2);
        gIsDiff = true;
        updateAboutMemoryFromJSONObject(diffJSONObjects(obj1, obj2));
        gIsDiff = false;
      } catch (ex) {
        handleException(ex);
      }
    });
  });
}

// ---------------------------------------------------------------------------

// Something unlikely to appear in a process name.
let kProcessPathSep = "^:^:^";

// Short for "diff report".
function DReport(aKind, aUnits, aAmount, aDescription, aNMerged, aPresence) {
  this._kind = aKind;
  this._units = aUnits;
  this._amount = aAmount;
  this._description = aDescription;
  this._nMerged = aNMerged;
  if (aPresence !== undefined) {
    this._presence = aPresence;
  }
}

DReport.prototype = {
  assertCompatible(aKind, aUnits) {
    assert(this._kind == aKind, "Mismatched kinds");
    assert(this._units == aUnits, "Mismatched units");

    // We don't check that the "description" properties match.  This is because
    // on Linux we can get cases where the paths are the same but the
    // descriptions differ, like this:
    //
    //   "path": "size/other-files/icon-theme.cache/[r--p]",
    //   "description": "/usr/share/icons/gnome/icon-theme.cache (read-only, not executable, private)"
    //
    //   "path": "size/other-files/icon-theme.cache/[r--p]"
    //   "description": "/usr/share/icons/hicolor/icon-theme.cache (read-only, not executable, private)"
    //
    // In those cases, we just use the description from the first-encountered
    // one, which is what about:memory also does.
    // (Note: reports with those paths are no longer generated, but allowing
    // the descriptions to differ seems reasonable.)
  },

  merge(aJr) {
    this.assertCompatible(aJr.kind, aJr.units);
    this._amount += aJr.amount;
    this._nMerged++;
  },

  toJSON(aProcess, aPath, aAmount) {
    return {
      process: aProcess,
      path: aPath,
      kind: this._kind,
      units: this._units,
      amount: aAmount,
      description: this._description,
      _presence: this._presence,
    };
  },
};

// Constants that indicate if a DReport was present only in one of the data
// sets, or had to be added for balance.
DReport.PRESENT_IN_FIRST_ONLY = 1;
DReport.PRESENT_IN_SECOND_ONLY = 2;
DReport.ADDED_FOR_BALANCE = 3;

/**
 * Return true if the report contains a webIsolated process,
 * which is a good indication that Fission is enabled.
 */
function hasWebIsolatedProcess(aJSONReports) {
  for (let jr of aJSONReports) {
    assert(jr.process !== undefined, "Missing process");
    if (jr.process.startsWith("webIsolated")) {
      return true;
    }
  }
  return false;
}

/**
 * Make a report map, which has combined path+process strings for keys, and
 * DReport objects for values.
 *
 * @param aJSONReports
 *        The |reports| field of a JSON object.
 * @param aForgetIsolation
          If this is true, treat webIsolated processes like web processes.
 * @return The constructed report map.
 */
function makeDReportMap(aJSONReports, aForgetIsolation) {
  let dreportMap = {};
  for (let jr of aJSONReports) {
    assert(jr.process !== undefined, "Missing process");
    assert(jr.path !== undefined, "Missing path");
    assert(jr.kind !== undefined, "Missing kind");
    assert(jr.units !== undefined, "Missing units");
    assert(jr.amount !== undefined, "Missing amount");
    assert(jr.description !== undefined, "Missing description");

    // Strip out some non-deterministic stuff that prevents clean diffs.
    // Ideally the memory reports themselves would contain information about
    // which parts of the the process and path need to be stripped -- saving us
    // from hardwiring knowledge of specific reporters here -- but we have no
    // mechanism for that. (Any future redesign of how memory reporters work
    // should include such a mechanism.)

    // Strip PIDs:
    // - pid 123
    // - pid=123
    // - pid: 123
    let pidRegex = /pid([ =]|: )\d+/g;
    let pidSubst = "pid$1NNN";
    let process = jr.process.replace(pidRegex, pidSubst);
    let path = jr.path.replace(pidRegex, pidSubst);

    if (aForgetIsolation && process.startsWith("webIsolated")) {
      process = "web (pid NNN)";
    }

    // Strip TIDs and threadpool IDs.
    path = path.replace(/\(tid=(\d+)\)/, "(tid=NNN)");
    path = path.replace(/#\d+ \(tid=NNN\)/, "#N (tid=NNN)");

    // Strip addresses:
    // - .../js-zone(0x12345678)/...
    // - .../zone(0x12345678)/...
    // - .../worker(<URL>, 0x12345678)/...
    path = path.replace(/zone\(0x[0-9A-Fa-f]+\)\//, "zone(0xNNN)/");
    path = path.replace(
      /\/worker\((.+), 0x[0-9A-Fa-f]+\)\//,
      "/worker($1, 0xNNN)/"
    );

    // Strip top window IDs:
    // - explicit/window-objects/top(<URL>, id=123)/...
    // - event-counts/window-objects/top(<URL>, id=123)/...
    path = path.replace(
      /^((?:explicit|event-counts)\/window-objects\/top\(.*, id=)\d+\)/,
      "$1NNN)"
    );

    // Strip null principal UUIDs (but not other UUIDs, because they may be
    // deterministic, such as those used by add-ons).
    path = path.replace(
      /moz-nullprincipal:{........-....-....-....-............}/g,
      "moz-nullprincipal:{NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN}"
    );

    // Strip segment counts from address-space.
    if (path.startsWith("address-space")) {
      path = path.replace(/\(segments=\d+\)/g, "(segments=NNNN)");
    }

    // Normalize omni.ja! paths.
    path = path.replace(
      /jar:file:\\\\\\(.+)\\omni.ja!/,
      "jar:file:\\\\\\...\\omni.ja!"
    );

    // Normalize script source counts.
    path = path.replace(/source\(scripts=(\d+), /, "source(scripts=NNN, ");

    let processPath = process + kProcessPathSep + path;
    let rOld = dreportMap[processPath];
    if (rOld === undefined) {
      dreportMap[processPath] = new DReport(
        jr.kind,
        jr.units,
        jr.amount,
        jr.description,
        1,
        undefined
      );
    } else {
      rOld.merge(jr);
    }
  }
  return dreportMap;
}

// Return a new dreportMap which is the diff of two dreportMaps.  Empties
// aDReportMap2 along the way.
function diffDReportMaps(aDReportMap1, aDReportMap2) {
  let result = {};

  for (let processPath in aDReportMap1) {
    let r1 = aDReportMap1[processPath];
    let r2 = aDReportMap2[processPath];
    let r2_amount, r2_nMerged;
    let presence;
    if (r2 !== undefined) {
      r1.assertCompatible(r2._kind, r2._units);
      r2_amount = r2._amount;
      r2_nMerged = r2._nMerged;
      delete aDReportMap2[processPath];
      presence = undefined; // represents that it's present in both
    } else {
      r2_amount = 0;
      r2_nMerged = 0;
      presence = DReport.PRESENT_IN_FIRST_ONLY;
    }
    result[processPath] = new DReport(
      r1._kind,
      r1._units,
      r2_amount - r1._amount,
      r1._description,
      Math.max(r1._nMerged, r2_nMerged),
      presence
    );
  }

  for (let processPath in aDReportMap2) {
    let r2 = aDReportMap2[processPath];
    result[processPath] = new DReport(
      r2._kind,
      r2._units,
      r2._amount,
      r2._description,
      r2._nMerged,
      DReport.PRESENT_IN_SECOND_ONLY
    );
  }

  return result;
}

function makeJSONReports(aDReportMap) {
  let reports = [];
  for (let processPath in aDReportMap) {
    let r = aDReportMap[processPath];
    if (r._amount !== 0) {
      // If _nMerged > 1, we give the full (aggregated) amount in the first
      // copy, and then use amount=0 in the remainder.  When viewed in
      // about:memory, this shows up as an entry with a "[2]"-style suffix
      // and the correct amount.
      let split = processPath.split(kProcessPathSep);
      assert(split.length >= 2);
      let process = split.shift();
      let path = split.join();
      reports.push(r.toJSON(process, path, r._amount));
      for (let i = 1; i < r._nMerged; i++) {
        reports.push(r.toJSON(process, path, 0));
      }
    }
  }

  return reports;
}

// Diff two JSON objects holding memory reports.
function diffJSONObjects(aJson1, aJson2) {
  function simpleProp(aProp) {
    assert(
      aJson1[aProp] !== undefined && aJson1[aProp] === aJson2[aProp],
      aProp + " properties don't match"
    );
    return aJson1[aProp];
  }

  // If one report we're diffing contains webIsolated processes, but the other
  // does not, then we're probably comparing a report with Fission enabled with
  // one where it is not enabled. In this case, we want to make all of the
  // webIsolated processes look like plain old web processes to get a better
  // diff.
  let hasIsolated1 = hasWebIsolatedProcess(aJson1.reports);
  let hasIsolated2 = hasWebIsolatedProcess(aJson2.reports);
  let eitherIsolated = hasIsolated1 || hasIsolated2;
  let forgetIsolation = hasIsolated1 != hasIsolated2 && eitherIsolated;

  return {
    version: simpleProp("version"),

    hasMozMallocUsableSize: simpleProp("hasMozMallocUsableSize"),

    reports: makeJSONReports(
      diffDReportMaps(
        makeDReportMap(aJson1.reports, forgetIsolation),
        makeDReportMap(aJson2.reports, forgetIsolation)
      )
    ),
  };
}

// ---------------------------------------------------------------------------

// |PColl| is short for "process collection".
function PColl() {
  this._trees = {};
  this._degenerates = {};
  this._heapTotal = 0;
}

/**
 * Processes reports (whether from reporters or from a file) and append the
 * main part of the page.
 *
 * @param aProcessReports
 *        Function that extracts the memory reports from the reporters or from
 *        file.
 * @param aFilter
 *        String or RegExp used to filter reports by their path.
 * @param aHasMozMallocUsableSize
 *        Boolean indicating if moz_malloc_usable_size works.
 */
function appendAboutMemoryMain(
  aProcessReports,
  aFilter,
  aHasMozMallocUsableSize
) {
  let pcollsByProcess = {};
  let infoByProcess = {};

  function handleReport(
    aProcess,
    aUnsafePath,
    aKind,
    aUnits,
    aAmount,
    aDescription,
    aPresence
  ) {
    if (aUnsafePath.startsWith("explicit/")) {
      assertInput(
        aKind === KIND_HEAP || aKind === KIND_NONHEAP,
        "bad explicit kind"
      );
      assertInput(aUnits === UNITS_BYTES, "bad explicit units");
    }

    assert(
      aPresence === undefined ||
        aPresence == DReport.PRESENT_IN_FIRST_ONLY ||
        aPresence == DReport.PRESENT_IN_SECOND_ONLY,
      "bad presence"
    );

    // If the process is empty, that means this process -- which is the main
    // process, because this is chrome JS code -- is doing the dumping.
    // Generate the process identifier: `Main Process (pid $PID)`.
    //
    // Note that `HandleReportAndFinishReportingCallbacks::Callback()` handles
    // this when saving memory reports to file. So, if we are loading memory
    // reports from file then `aProcess` will already be non-empty.
    let process = aProcess
      ? aProcess
      : gMainProcessPrefix + " (pid " + Services.appinfo.processID + ")";

    // Store the "resident" value for each process, so that if we filter it
    // out, we can still use it to correctly sort processes and generate the
    // process index.
    let info = infoByProcess[process];
    if (!info) {
      info = infoByProcess[process] = {};
    }
    if (aUnsafePath == "resident") {
      infoByProcess[process].resident = aAmount;
    }

    // Ignore reports that don't match the current filter.
    if (!stringMatchesFilter(aUnsafePath, aFilter)) {
      return;
    }

    let unsafeNames = aUnsafePath.split("/");
    let unsafeName0 = unsafeNames[0];
    let isDegenerate = unsafeNames.length === 1;

    // Get the PColl table for the process, creating it if necessary.
    let pcoll = pcollsByProcess[process];
    if (!pcollsByProcess[process]) {
      pcoll = pcollsByProcess[process] = new PColl();
    }

    // Get the root node, creating it if necessary.
    let psubcoll = isDegenerate ? pcoll._degenerates : pcoll._trees;
    let t = psubcoll[unsafeName0];
    if (!t) {
      t = psubcoll[unsafeName0] = new TreeNode(
        unsafeName0,
        aUnits,
        isDegenerate
      );
    }

    if (!isDegenerate) {
      // Add any missing nodes in the tree implied by aUnsafePath, and fill in
      // the properties that we can with a top-down traversal.
      for (let i = 1; i < unsafeNames.length; i++) {
        let unsafeName = unsafeNames[i];
        let u = t.findKid(unsafeName);
        if (!u) {
          u = new TreeNode(unsafeName, aUnits, isDegenerate);
          if (!t._kids) {
            t._kids = [];
          }
          t._kids.push(u);
        }
        t = u;
      }

      // Update the heap total if necessary.
      if (unsafeName0 === "explicit" && aKind == KIND_HEAP) {
        pcollsByProcess[process]._heapTotal += aAmount;
      }
    }

    if (t._amount) {
      // Duplicate!  Sum the values and mark it as a dup.
      t._amount += aAmount;
      t._nMerged = t._nMerged ? t._nMerged + 1 : 2;
      assert(t._presence === aPresence, "presence mismatch");
    } else {
      // New leaf node.  Fill in extra node details from the report.
      t._amount = aAmount;
      t._description = aDescription;
      if (aPresence !== undefined) {
        t._presence = aPresence;
      }
    }
  }

  function displayReports() {
    // Sort the processes.
    let processes = Object.keys(infoByProcess);
    processes.sort(function (aProcessA, aProcessB) {
      assert(
        aProcessA != aProcessB,
        `Elements of Object.keys() should be unique, but ` +
          `saw duplicate '${aProcessA}' elem.`
      );

      // Always put the main process first.
      if (aProcessA.startsWith(gMainProcessPrefix)) {
        return -1;
      }
      if (aProcessB.startsWith(gMainProcessPrefix)) {
        return 1;
      }

      // Then sort by resident size.
      let residentA = infoByProcess[aProcessA].resident || -1;
      let residentB = infoByProcess[aProcessB].resident || -1;
      if (residentA > residentB) {
        return -1;
      }
      if (residentA < residentB) {
        return 1;
      }

      // Then sort by process name.
      if (aProcessA < aProcessB) {
        return -1;
      }
      if (aProcessA > aProcessB) {
        return 1;
      }

      return 0;
    });

    // We set up this general layout inside gMain:
    //
    //   <div class="outputContainer">
    //     <div class="sections"></div>
    //     <div class="sidebar">
    //       <div class="sidebarContents">
    //         <div class="sidebarItem filterItem"></div>
    //         <div class="sidebarItem indexItem"></div>
    //       </div>
    //     </div>
    //   </div>
    //
    // If we detect that outputContainer already exists, then this is an update
    // (due to typing in a filter string) to an already-displayed memory report.
    // In this case we preserve the structure of the layout and only replace
    // div.sections and #indexItem. Preserving the filter sidebar item means we
    // preserve any editing state in its <input>.

    // Generate the main process sections.
    let sections = newElement("div", "sections");
    sections.setAttribute("role", "main");

    for (let [i, process] of processes.entries()) {
      let pcolls = pcollsByProcess[process];
      if (!pcolls) {
        continue;
      }

      let section = appendElement(sections, "div", "section");
      appendProcessAboutMemoryElements(
        section,
        i,
        process,
        pcolls._trees,
        pcolls._degenerates,
        pcolls._heapTotal,
        aHasMozMallocUsableSize,
        aFilter != ""
      );
    }

    if (!sections.firstChild) {
      appendElementWithText(sections, "div", "section", "No results found.");
    }

    // Generate the process index.
    let indexItem = newElement("div", "sidebarItem");
    indexItem.classList.add("indexItem");
    appendElementWithText(indexItem, "div", "sidebarLabel", "Process index");
    let indexList = appendElement(indexItem, "ul", "index");

    for (let [i, process] of processes.entries()) {
      let indexListItem = appendElement(indexList, "li");
      let pcolls = pcollsByProcess[process];
      if (pcolls) {
        let indexLink = appendElementWithText(indexListItem, "a", "", process);
        indexLink.href = "#start" + i;
      } else {
        // We've filtered out all reports from this process. Generate a non-link
        // entry in the process index, and skip creating a process report
        // section.
        indexListItem.textContent = process;
      }
    }

    // If we are updating, just swap in the new process output.
    let outputContainer = gMain.querySelector(".outputContainer");
    if (outputContainer) {
      outputContainer.querySelector(".sections").replaceWith(sections);
      outputContainer.querySelector(".indexItem").replaceWith(indexItem);
      return;
    }

    // Otherwise, generate the rest of the layout.
    outputContainer = appendElement(gMain, "div", "outputContainer");
    outputContainer.appendChild(sections);

    let sidebar = appendElement(outputContainer, "div", "sidebar");
    sidebar.setAttribute("role", "navigation");
    let sidebarContents = appendElement(sidebar, "div", "sidebarContents");

    // Generate the filter input and checkbox.
    let filterItem = appendElement(sidebarContents, "div", "sidebarItem");
    filterItem.classList.add("filterItem");
    appendElementWithText(filterItem, "div", "sidebarLabel", "Filter");

    let filterInput = appendElement(filterItem, "input", "filterInput");
    filterInput.placeholder = "Memory report path filter";
    filterInput.setAttribute("type", "text");

    let filterOptions = appendElement(filterItem, "div");
    let filterRegExLabel = appendElement(filterOptions, "label");
    let filterRegExCheckbox = appendElement(filterRegExLabel, "input");
    filterRegExCheckbox.type = "checkbox";
    filterRegExLabel.append(" Regular expression");

    // Set up event handlers to update the display if the filter input or
    // checkbox changes.
    let filterUpdateTimeout;
    let filterUpdate = function () {
      if (filterUpdateTimeout) {
        window.clearTimeout(filterUpdateTimeout);
      }
      filterUpdateTimeout = window.setTimeout(function () {
        try {
          gFilter =
            filterRegExCheckbox.checked && filterInput.value != ""
              ? new RegExp(filterInput.value)
              : filterInput.value;
        } catch (ex) {
          // Match nothing if the regex was invalid.
          gFilter = new RegExp("^$");
        }
        updateAboutMemoryFromCurrentData();
      }, gFilterUpdateDelayMS);
    };
    filterInput.oninput = filterUpdate;
    filterRegExCheckbox.onchange = filterUpdate;

    // Append the process list item after the filter item.
    sidebarContents.appendChild(indexItem);
  }

  aProcessReports(handleReport, displayReports);
}

// ---------------------------------------------------------------------------

// There are two kinds of TreeNode.
// - Leaf TreeNodes correspond to reports.
// - Non-leaf TreeNodes are just scaffolding nodes for the tree;  their values
//   are derived from their children.
// Some trees are "degenerate", i.e. they contain a single node, i.e. they
// correspond to a report whose path has no '/' separators.
function TreeNode(aUnsafeName, aUnits, aIsDegenerate) {
  this._units = aUnits;
  this._unsafeName = aUnsafeName;
  if (aIsDegenerate) {
    this._isDegenerate = true;
  }

  // Leaf TreeNodes have these properties added immediately after construction:
  // - _amount
  // - _description
  // - _nMerged (only defined if > 1)
  // - _presence (only defined if value is PRESENT_IN_{FIRST,SECOND}_ONLY)
  //
  // Non-leaf TreeNodes have these properties added later:
  // - _kids
  // - _amount
  // - _description
  // - _hideKids (only defined if true)
  // - _maxAbsDescendant (on-demand, only when gIsDiff is set)
}

TreeNode.prototype = {
  findKid(aUnsafeName) {
    if (this._kids) {
      for (let kid of this._kids) {
        if (kid._unsafeName === aUnsafeName) {
          return kid;
        }
      }
    }
    return undefined;
  },

  // When gIsDiff is false, tree operations -- sorting and determining if a
  // sub-tree is significant -- are straightforward. But when gIsDiff is true,
  // the combination of positive and negative values within a tree complicates
  // things. So for a non-leaf node, instead of just looking at _amount, we
  // instead look at the maximum absolute value of the node and all of its
  // descendants.
  maxAbsDescendant() {
    if (!this._kids) {
      // No kids? Just return the absolute value of the amount.
      return Math.abs(this._amount);
    }

    if ("_maxAbsDescendant" in this) {
      // We've computed this before? Return the saved value.
      return this._maxAbsDescendant;
    }

    // Compute the maximum absolute value of all descendants.
    let max = Math.abs(this._amount);
    for (let kid of this._kids) {
      max = Math.max(max, kid.maxAbsDescendant());
    }
    this._maxAbsDescendant = max;
    return max;
  },

  toString() {
    switch (this._units) {
      case UNITS_BYTES:
        return formatBytes(this._amount);
      case UNITS_COUNT:
      case UNITS_COUNT_CUMULATIVE:
        return formatNum(this._amount);
      case UNITS_PERCENTAGE:
        return formatPercentage(this._amount);
      default:
        throw new Error(
          "Invalid memory report(s): bad units in TreeNode.toString"
        );
    }
  },
};

// Sort TreeNodes first by size, then by name.  The latter is important for the
// about:memory tests, which need a predictable ordering of reporters which
// have the same amount.
TreeNode.compareAmounts = function (aA, aB) {
  let a, b;
  if (gIsDiff) {
    a = aA.maxAbsDescendant();
    b = aB.maxAbsDescendant();
  } else {
    a = aA._amount;
    b = aB._amount;
  }
  if (a > b) {
    return -1;
  }
  if (a < b) {
    return 1;
  }
  return TreeNode.compareUnsafeNames(aA, aB);
};

TreeNode.compareUnsafeNames = function (aA, aB) {
  return aA._unsafeName.localeCompare(aB._unsafeName);
};

/**
 * Fill in the remaining properties for the specified tree in a bottom-up
 * fashion.
 *
 * @param aRoot
 *        The tree root.
 */
function fillInTree(aRoot) {
  // Fill in the remaining properties bottom-up.
  function fillInNonLeafNodes(aT) {
    if (!aT._kids) {
      // Leaf node.  Has already been filled in.
    } else if (aT._kids.length === 1 && aT != aRoot) {
      // Non-root, non-leaf node with one child.  Merge the child with the node
      // to avoid redundant entries.
      let kid = aT._kids[0];
      let kidBytes = fillInNonLeafNodes(kid);
      aT._unsafeName += "/" + kid._unsafeName;
      if (kid._kids) {
        aT._kids = kid._kids;
      } else {
        delete aT._kids;
      }
      aT._amount = kidBytes;
      aT._description = kid._description;
      if (kid._nMerged !== undefined) {
        aT._nMerged = kid._nMerged;
      }
      assert(!aT._hideKids && !kid._hideKids, "_hideKids set when merging");
    } else {
      // Non-leaf node with multiple children.  Derive its _amount and
      // _description entirely from its children...
      let kidsBytes = 0;
      for (let kid of aT._kids) {
        kidsBytes += fillInNonLeafNodes(kid);
      }

      // ... except in one special case. When diffing two memory report sets,
      // if one set has a node with children and the other has the same node
      // but without children -- e.g. the first has "a/b/c" and "a/b/d", but
      // the second only has "a/b" -- we need to add a fake node "a/b/(fake)"
      // to the second to make the trees comparable. It's ugly, but it works.
      if (
        aT._amount !== undefined &&
        (aT._presence === DReport.PRESENT_IN_FIRST_ONLY ||
          aT._presence === DReport.PRESENT_IN_SECOND_ONLY)
      ) {
        aT._amount += kidsBytes;
        let fake = new TreeNode("(fake child)", aT._units);
        fake._presence = DReport.ADDED_FOR_BALANCE;
        fake._amount = aT._amount - kidsBytes;
        aT._kids.push(fake);
        delete aT._presence;
      } else {
        assert(
          aT._amount === undefined,
          "_amount already set for non-leaf node"
        );
        aT._amount = kidsBytes;
      }
      aT._description = "The sum of all entries below this one.";
    }
    return aT._amount;
  }

  // cannotMerge is set because don't want to merge into a tree's root node.
  fillInNonLeafNodes(aRoot);
}

/**
 * Compute the "heap-unclassified" value and insert it into the "explicit"
 * tree.
 *
 * @param aT
 *        The "explicit" tree.
 * @param aHeapAllocatedNode
 *        The "heap-allocated" tree node.
 * @param aHeapTotal
 *        The sum of all explicit HEAP reports for this process.
 * @return A boolean indicating if "heap-allocated" is known for the process.
 */
function addHeapUnclassifiedNode(aT, aHeapAllocatedNode, aHeapTotal) {
  if (aHeapAllocatedNode === undefined) {
    return false;
  }

  if (aT.findKid("heap-unclassified")) {
    // heap-unclassified was already calculated, there's nothing left to do.
    // This can happen when memory reports are exported from areweslimyet.com.
    return true;
  }

  assert(aHeapAllocatedNode._isDegenerate, "heap-allocated is not degenerate");
  let heapAllocatedBytes = aHeapAllocatedNode._amount;
  let heapUnclassifiedT = new TreeNode("heap-unclassified", UNITS_BYTES);
  heapUnclassifiedT._amount = heapAllocatedBytes - aHeapTotal;
  heapUnclassifiedT._description =
    "Memory not classified by a more specific report. This includes " +
    "slop bytes due to internal fragmentation in the heap allocator " +
    "(caused when the allocator rounds up request sizes).";
  aT._kids.push(heapUnclassifiedT);
  aT._amount += heapUnclassifiedT._amount;
  return true;
}

/**
 * Sort all kid nodes from largest to smallest, and insert aggregate nodes
 * where appropriate.
 *
 * @param aTotalBytes
 *        The size of the tree's root node.
 * @param aT
 *        The tree.
 */
function sortTreeAndInsertAggregateNodes(aTotalBytes, aT) {
  const kSignificanceThresholdPerc = 1;

  function isInsignificant(aT) {
    if (gVerbose.checked) {
      return false;
    }

    let perc = gIsDiff
      ? (100 * aT.maxAbsDescendant()) / Math.abs(aTotalBytes)
      : (100 * aT._amount) / aTotalBytes;
    return perc < kSignificanceThresholdPerc;
  }

  if (!aT._kids) {
    return;
  }

  aT._kids.sort(TreeNode.compareAmounts);

  // If the first child is insignificant, they all are, and there's no point
  // creating an aggregate node that lacks siblings.  Just set the parent's
  // _hideKids property and process all children.
  if (isInsignificant(aT._kids[0])) {
    aT._hideKids = true;
    for (let kid of aT._kids) {
      sortTreeAndInsertAggregateNodes(aTotalBytes, kid);
    }
    return;
  }

  // Look at all children except the last one.
  let i;
  for (i = 0; i < aT._kids.length - 1; i++) {
    if (isInsignificant(aT._kids[i])) {
      // This child is below the significance threshold.  If there are other
      // (smaller) children remaining, move them under an aggregate node.
      let i0 = i;
      let nAgg = aT._kids.length - i0;
      // Create an aggregate node.  Inherit units from the parent;  everything
      // in the tree should have the same units anyway (we test this later).
      let aggT = new TreeNode(`(${nAgg} tiny)`, aT._units);
      aggT._kids = [];
      let aggBytes = 0;
      for (; i < aT._kids.length; i++) {
        aggBytes += aT._kids[i]._amount;
        aggT._kids.push(aT._kids[i]);
      }
      aggT._hideKids = true;
      aggT._amount = aggBytes;
      aggT._description =
        nAgg +
        " sub-trees that are below the " +
        kSignificanceThresholdPerc +
        "% significance threshold.";
      aT._kids.splice(i0, nAgg, aggT);
      aT._kids.sort(TreeNode.compareAmounts);

      // Process the moved children.
      for (let kid of aggT._kids) {
        sortTreeAndInsertAggregateNodes(aTotalBytes, kid);
      }
      return;
    }

    sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
  }

  // The first n-1 children were significant.  Don't consider if the last child
  // is significant;  there's no point creating an aggregate node that only has
  // one child.  Just process it.
  sortTreeAndInsertAggregateNodes(aTotalBytes, aT._kids[i]);
}

// Global variable indicating if we've seen any invalid values for this
// process;  it holds the unsafePaths of any such reports.  It is reset for
// each new process.
let gUnsafePathsWithInvalidValuesForThisProcess = [];

function appendWarningElements(
  aP,
  aHasKnownHeapAllocated,
  aHasMozMallocUsableSize,
  aFiltered
) {
  // These warnings may not make sense if the reporters they reference have been
  // filtered out, so just skip them if we have a filter applied.
  if (!aFiltered && !aHasKnownHeapAllocated && !aHasMozMallocUsableSize) {
    appendElementWithText(
      aP,
      "p",
      "",
      "WARNING: the 'heap-allocated' memory reporter and the " +
        "moz_malloc_usable_size() function do not work for this platform " +
        "and/or configuration.  This means that 'heap-unclassified' is not " +
        "shown and the 'explicit' tree shows much less memory than it should.\n\n"
    );
  } else if (!aFiltered && !aHasKnownHeapAllocated) {
    appendElementWithText(
      aP,
      "p",
      "",
      "WARNING: the 'heap-allocated' memory reporter does not work for this " +
        "platform and/or configuration. This means that 'heap-unclassified' " +
        "is not shown and the 'explicit' tree shows less memory than it should.\n\n"
    );
  } else if (!aFiltered && !aHasMozMallocUsableSize) {
    appendElementWithText(
      aP,
      "p",
      "",
      "WARNING: the moz_malloc_usable_size() function does not work for " +
        "this platform and/or configuration.  This means that much of the " +
        "heap-allocated memory is not measured by individual memory reporters " +
        "and so will fall under 'heap-unclassified'.\n\n"
    );
  }

  if (gUnsafePathsWithInvalidValuesForThisProcess.length) {
    let div = appendElement(aP, "div");
    appendElementWithText(
      div,
      "p",
      "",
      "WARNING: the following values are negative or unreasonably large.\n"
    );

    let ul = appendElement(div, "ul");
    for (
      let i = 0;
      i < gUnsafePathsWithInvalidValuesForThisProcess.length;
      i++
    ) {
      appendTextNode(ul, " ");
      appendElementWithText(
        ul,
        "li",
        "",
        flipBackslashes(gUnsafePathsWithInvalidValuesForThisProcess[i]) + "\n"
      );
    }

    appendElementWithText(
      div,
      "p",
      "",
      "This indicates a defect in one or more memory reporters.  The " +
        "invalid values are highlighted.\n\n"
    );
    gUnsafePathsWithInvalidValuesForThisProcess = []; // reset for the next process
  }
}

/**
 * Appends the about:memory elements for a single process.
 *
 * @param aP
 *        The parent DOM node.
 * @param aN
 *        The number of the process, starting at 0.
 * @param aProcess
 *        The name of the process.
 * @param aTrees
 *        The table of non-degenerate trees for this process.
 * @param aDegenerates
 *        The table of degenerate trees for this process.
 * @param aHasMozMallocUsableSize
 *        Boolean indicating if moz_malloc_usable_size works.
 * @param aFiltered
 *        Boolean indicating whether the reports were filtered.
 * @return The generated text.
 */
function appendProcessAboutMemoryElements(
  aP,
  aN,
  aProcess,
  aTrees,
  aDegenerates,
  aHeapTotal,
  aHasMozMallocUsableSize,
  aFiltered
) {
  let appendLink = function (aHere, aThere, aArrow) {
    let link = appendElementWithText(aP, "a", "upDownArrow", aArrow);
    link.href = "#" + aThere + aN;
    link.id = aHere + aN;
    link.title = `Go to the ${aThere} of ${aProcess}`;
    link.style = "text-decoration: none";

    // This gives nice spacing when we copy and paste.
    appendElementWithText(aP, "span", "", "\n");
  };

  appendElementWithText(aP, "h1", "", aProcess);
  appendLink("start", "end", "↓");

  // We'll fill this in later.
  let warningsDiv = appendElement(aP, "div", "accuracyWarning");

  // The explicit tree.
  let hasExplicitTree;
  let hasKnownHeapAllocated;
  {
    let treeName = "explicit";
    let t = aTrees[treeName];
    if (t) {
      let pre = appendSectionHeader(aP, "Explicit Allocations");
      hasExplicitTree = true;
      fillInTree(t);
      // Using the "heap-allocated" reporter here instead of
      // nsMemoryReporterManager.heapAllocated goes against the usual pattern.
      // But the "heap-allocated" node will go in the tree like the others, so
      // we have to deal with it, and once we're dealing with it, it's easier
      // to keep doing so rather than switching to the distinguished amount.
      hasKnownHeapAllocated =
        aDegenerates &&
        addHeapUnclassifiedNode(t, aDegenerates["heap-allocated"], aHeapTotal);
      sortTreeAndInsertAggregateNodes(t._amount, t);
      t._description = explicitTreeDescription;
      appendTreeElements(pre, t, aProcess, "");
      delete aTrees[treeName];
    }
    appendTextNode(aP, "\n"); // gives nice spacing when we copy and paste
  }

  // Fill in and sort all the non-degenerate other trees.
  let otherTrees = [];
  for (let unsafeName in aTrees) {
    let t = aTrees[unsafeName];
    assert(!t._isDegenerate, "tree is degenerate");
    fillInTree(t);
    sortTreeAndInsertAggregateNodes(t._amount, t);
    otherTrees.push(t);
  }
  otherTrees.sort(TreeNode.compareUnsafeNames);

  // Get the length of the longest root value among the degenerate other trees,
  // and sort them as well.
  let otherDegenerates = [];
  let maxStringLength = 0;
  for (let unsafeName in aDegenerates) {
    let t = aDegenerates[unsafeName];
    assert(t._isDegenerate, "tree is not degenerate");
    let length = t.toString().length;
    if (length > maxStringLength) {
      maxStringLength = length;
    }
    otherDegenerates.push(t);
  }
  otherDegenerates.sort(TreeNode.compareUnsafeNames);

  // Now generate the elements, putting non-degenerate trees first.
  if (otherTrees.length || otherDegenerates.length) {
    let pre = appendSectionHeader(aP, "Other Measurements");
    for (let t of otherTrees) {
      appendTreeElements(pre, t, aProcess, "");
      appendTextNode(pre, "\n"); // blank lines after non-degenerate trees
    }
    for (let t of otherDegenerates) {
      let padText = "".padStart(maxStringLength - t.toString().length, " ");
      appendTreeElements(pre, t, aProcess, padText);
    }
    appendTextNode(aP, "\n"); // gives nice spacing when we copy and paste
  }

  // Add any warnings about inaccuracies in the "explicit" tree due to platform
  // limitations.  These must be computed after generating all the text.  The
  // newlines give nice spacing if we copy+paste into a text buffer.
  if (hasExplicitTree) {
    appendWarningElements(
      warningsDiv,
      hasKnownHeapAllocated,
      aHasMozMallocUsableSize,
      aFiltered
    );
  }

  appendElementWithText(aP, "h3", "", "End of " + aProcess);
  appendLink("end", "start", "↑");
}

// The locale used when formatting a number as a human-readable string in any
// format.
const kStyleLocale = "en-US";

// Used for UNITS_BYTES values that are printed as MiB.
const kMBFormat = new Intl.NumberFormat(kStyleLocale, {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2,
});

// Used for UNITS_PERCENTAGE values.
const kPercFormatter = new Intl.NumberFormat(kStyleLocale, {
  style: "percent",
  minimumFractionDigits: 2,
  maximumFractionDigits: 2,
});

// Used for fractions within the tree.
const kFracFormatter = new Intl.NumberFormat(kStyleLocale, {
  style: "percent",
  minimumIntegerDigits: 2,
  minimumFractionDigits: 2,
  maximumFractionDigits: 2,
});

// Used for special-casing 100% fractions within the tree.
const kFrac1Formatter = new Intl.NumberFormat(kStyleLocale, {
  style: "percent",
  minimumIntegerDigits: 3,
  minimumFractionDigits: 1,
  maximumFractionDigits: 1,
});

// Used when no custom formatting was requested.
const kDefaultNumFormatter = new Intl.NumberFormat(kStyleLocale);

/**
 * Formats an int as a human-readable string.
 *
 * @param aN
 *        The integer to format.
 * @param aFormatter
 *        Optional formatter object.
 * @return A human-readable string representing the int.
 */
function formatNum(aN, aFormatter) {
  return (aFormatter || kDefaultNumFormatter).format(aN);
}

/**
 * Converts a byte count to an appropriate string representation.
 *
 * @param aBytes
 *        The byte count.
 * @return The string representation.
 */
function formatBytes(aBytes) {
  return gVerbose.checked
    ? `${formatNum(aBytes)} B`
    : `${formatNum(aBytes / (1024 * 1024), kMBFormat)} MB`;
}

/**
 * Converts a UNITS_PERCENTAGE value to an appropriate string representation.
 *
 * @param aPerc100x
 *        The percentage, multiplied by 100 (see nsIMemoryReporter).
 * @return The string representation
 */
function formatPercentage(aPerc100x) {
  // A percentage like 12.34% will have an aPerc100x value of 1234, and we need
  // to divide that by 10,000 to get the 0.1234 that toLocaleString() wants.
  return formatNum(aPerc100x / 10000, kPercFormatter);
}

/*
 * Converts a tree fraction to an appropriate string representation.
 *
 * @param aNum
 *        The numerator.
 * @param aDenom
 *        The denominator.
 * @return The string representation
 */
function formatTreeFrac(aNum, aDenom) {
  // Two special behaviours here:
  // - We treat 0 / 0 as 100%.
  // - We want 4 digits, as much as possible, because it gives good vertical
  //   alignment. For positive numbers, 00.00%--99.99% works straighforwardly,
  //   but 100.0% needs special handling.
  let num = aDenom === 0 ? 1 : aNum / aDenom;
  return 0.99995 <= num && num <= 1
    ? formatNum(1, kFrac1Formatter)
    : formatNum(num, kFracFormatter);
}

const kNoKidsSep = " ── ",
  kHideKidsSep = " ++ ",
  kShowKidsSep = " -- ";

function appendMrNameSpan(
  aP,
  aDescription,
  aUnsafeName,
  aIsInvalid,
  aNMerged,
  aPresence
) {
  let safeName = flipBackslashes(aUnsafeName);
  if (!aIsInvalid && !aNMerged && !aPresence) {
    safeName += "\n";
  }
  let nameSpan = appendElementWithText(aP, "span", "mrName", safeName);
  nameSpan.title = aDescription;

  if (aIsInvalid) {
    let noteText = " [?!]";
    if (!aNMerged) {
      noteText += "\n";
    }
    let noteSpan = appendElementWithText(aP, "span", "mrNote", noteText);
    noteSpan.title =
      "Warning: this value is invalid and indicates a bug in one or more " +
      "memory reporters. ";
  }

  if (aNMerged) {
    let noteText = ` [${aNMerged}]`;
    if (!aPresence) {
      noteText += "\n";
    }
    let noteSpan = appendElementWithText(aP, "span", "mrNote", noteText);
    noteSpan.title =
      "This value is the sum of " +
      aNMerged +
      " memory reports that all have the same path.";
  }

  if (aPresence) {
    let c, title;
    switch (aPresence) {
      case DReport.PRESENT_IN_FIRST_ONLY:
        c = "-";
        title =
          "This value was only present in the first set of memory reports.";
        break;
      case DReport.PRESENT_IN_SECOND_ONLY:
        c = "+";
        title =
          "This value was only present in the second set of memory reports.";
        break;
      case DReport.ADDED_FOR_BALANCE:
        c = "!";
        title =
          "One of the sets of memory reports lacked children for this " +
          "node's parent. This is a fake child node added to make the " +
          "two memory sets comparable.";
        break;
      default:
        assert(false, "bad presence");
        break;
    }
    let noteSpan = appendElementWithText(aP, "span", "mrNote", ` [${c}]\n`);
    noteSpan.title = title;
  }
}

// This is used to record the (safe) IDs of which sub-trees have been manually
// expanded (marked as true) and collapsed (marked as false).  It's used to
// replicate the collapsed/expanded state when the page is updated.  It can end
// up holding IDs of nodes that no longer exist, e.g. for compartments that
// have been closed.  This doesn't seem like a big deal, because the number is
// limited by the number of entries the user has changed from their original
// state.
let gShowSubtreesBySafeTreeId = {};

function assertClassListContains(aElem, aClassName) {
  assert(aElem, "undefined " + aClassName);
  assert(aElem.classList.contains(aClassName), "classname isn't " + aClassName);
}

function toggle(aEvent) {
  // This relies on each line being a span that contains at least four spans:
  // mrValue, mrPerc, mrSep, mrName, and then zero or more mrNotes.  All
  // whitespace must be within one of these spans for this function to find the
  // right nodes.  And the span containing the children of this line must
  // immediately follow.  Assertions check this.

  // We want the outer span. |aEvent.target| will normally be one of the inner
  // spans. However, if the click was dispatched via a11y, it might be the outer
  // span because some of the inner spans are pruned from the a11y tree.
  let outerSpan = aEvent.target.classList.contains("hasKids")
    ? aEvent.target
    : aEvent.target.parentNode;
  assertClassListContains(outerSpan, "hasKids");

  // Toggle the '++'/'--' separator.
  let isExpansion;
  let sepSpan = outerSpan.childNodes[2];
  assertClassListContains(sepSpan, "mrSep");
  if (sepSpan.textContent === kHideKidsSep) {
    isExpansion = true;
    sepSpan.textContent = kShowKidsSep;
    outerSpan.setAttribute("aria-expanded", "true");
  } else if (sepSpan.textContent === kShowKidsSep) {
    isExpansion = false;
    sepSpan.textContent = kHideKidsSep;
    outerSpan.setAttribute("aria-expanded", "false");
  } else {
    assert(false, "bad sepSpan textContent");
  }

  // Toggle visibility of the span containing this node's children.
  let subTreeSpan = outerSpan.nextSibling;
  assertClassListContains(subTreeSpan, "kids");
  subTreeSpan.classList.toggle("hidden");

  // Record/unrecord that this sub-tree was toggled.
  let safeTreeId = outerSpan.id;
  if (gShowSubtreesBySafeTreeId[safeTreeId] !== undefined) {
    delete gShowSubtreesBySafeTreeId[safeTreeId];
  } else {
    gShowSubtreesBySafeTreeId[safeTreeId] = isExpansion;
  }
}

function expandPathToThisElement(aElement) {
  if (aElement.classList.contains("kids")) {
    // Unhide the kids.
    aElement.classList.remove("hidden");
    expandPathToThisElement(aElement.previousSibling); // hasKids
  } else if (aElement.classList.contains("hasKids")) {
    // Change the separator to '--'.
    let sepSpan = aElement.childNodes[2];
    assertClassListContains(sepSpan, "mrSep");
    sepSpan.textContent = kShowKidsSep;
    aElement.setAttribute("aria-expanded", "true");
    expandPathToThisElement(aElement.parentNode.parentNode); // kids or pre.entries
  } else {
    assertClassListContains(aElement, "entries");
  }
}

/**
 * Appends the elements for the tree, including its heading.
 *
 * @param aP
 *        The parent DOM node.
 * @param aRoot
 *        The tree root.
 * @param aProcess
 *        The process the tree corresponds to.
 * @param aPadText
 *        A string to pad the start of each entry.
 */
function appendTreeElements(aP, aRoot, aProcess, aPadText) {
  /**
   * Appends the elements for a particular tree, without a heading. There's a
   * subset of the Unicode "light" box-drawing chars that is widely implemented
   * in terminals, and this code sticks to that subset to maximize the chance
   * that copying and pasting about:memory output to a terminal will work
   * correctly.
   *
   * @param aP
   *        The parent DOM node.
   * @param aProcess
   *        The process the tree corresponds to.
   * @param aUnsafeNames
   *        An array of the names forming the path to aT.
   * @param aRoot
   *        The root of the tree this sub-tree belongs to.
   * @param aT
   *        The tree.
   * @param aTlThis
   *        The treeline for this entry.
   * @param aTlKids
   *        The treeline for this entry's children.
   * @param aParentStringLength
   *        The length of the formatted byte count of the top node in the tree.
   */
  function appendTreeElements2(
    aP,
    aProcess,
    aUnsafeNames,
    aRoot,
    aT,
    aTlThis,
    aTlKids,
    aParentStringLength
  ) {
    function appendN(aS, aC, aN) {
      for (let i = 0; i < aN; i++) {
        aS += aC;
      }
      return aS;
    }

    // The entire entry including children needs to be treated as a list item
    // for a11y purposes.
    let p = document.createElement("span");
    p.setAttribute("role", "listitem");
    aP.appendChild(p);

    // The tree line.  Indent more if this entry is narrower than its parent.
    let valueText = aT.toString();
    let extraTlLength = Math.max(aParentStringLength - valueText.length, 0);
    if (extraTlLength > 0) {
      aTlThis = appendN(aTlThis, "─", extraTlLength);
      aTlKids = appendN(aTlKids, " ", extraTlLength);
    }
    let treeLine = appendElementWithText(p, "span", "treeline", aTlThis);
    treeLine.setAttribute("aria-hidden", "true");

    // Detect and record invalid values.  But not if gIsDiff is true, because
    // we expect negative values in that case.
    assertInput(
      aRoot._units === aT._units,
      "units within a tree are inconsistent"
    );
    let tIsInvalid = false;
    if (!gIsDiff && !(0 <= aT._amount && aT._amount <= aRoot._amount)) {
      tIsInvalid = true;
      let unsafePath = aUnsafeNames.join("/");
      gUnsafePathsWithInvalidValuesForThisProcess.push(unsafePath);
      reportAssertionFailure(
        `Invalid value (${aT._amount} / ${aRoot._amount}) for ` +
          flipBackslashes(unsafePath)
      );
    }

    // For non-leaf nodes, the entire sub-tree is put within a span so it can
    // be collapsed if the node is clicked on.
    let d;
    let sep;
    let showSubtrees;
    if (aT._kids) {
      // Determine if we should show the sub-tree below this entry;  this
      // involves reinstating any previous toggling of the sub-tree.
      let unsafePath = aUnsafeNames.join("/");
      let safeTreeId = `${aProcess}:${flipBackslashes(unsafePath)}`;
      showSubtrees = !aT._hideKids;
      if (gShowSubtreesBySafeTreeId[safeTreeId] !== undefined) {
        showSubtrees = gShowSubtreesBySafeTreeId[safeTreeId];
      }
      d = appendElement(p, "span", "hasKids");
      d.id = safeTreeId;
      d.onclick = toggle;
      d.setAttribute("role", "button");
      sep = showSubtrees ? kShowKidsSep : kHideKidsSep;
      d.setAttribute("aria-expanded", showSubtrees ? "true" : "false");
    } else {
      assert(!aT._hideKids, "leaf node with _hideKids set");
      sep = kNoKidsSep;
      d = p;
    }

    // The value.
    appendElementWithText(
      d,
      "span",
      "mrValue" + (tIsInvalid ? " invalid" : ""),
      valueText
    );

    // The percentage (omitted for single entries).
    if (!aT._isDegenerate) {
      let percText = formatTreeFrac(aT._amount, aRoot._amount);
      appendElementWithText(d, "span", "mrPerc", ` (${percText})`);
    }

    // The separator.
    appendElementWithText(d, "span", "mrSep", sep);

    // The entry's name.
    appendMrNameSpan(
      d,
      aT._description,
      aT._unsafeName,
      tIsInvalid,
      aT._nMerged,
      aT._presence
    );

    // In non-verbose mode, invalid nodes can be hidden in collapsed sub-trees.
    // But it's good to always see them, so force this.
    if (!gVerbose.checked && tIsInvalid) {
      expandPathToThisElement(aT._kids ? d : aP);
    }

    // Recurse over children.
    if (aT._kids) {
      // The 'kids' class is just used for sanity checking in toggle().
      d = appendElement(p, "span", showSubtrees ? "kids" : "kids hidden");
      d.setAttribute("role", "list");

      let tlThisForMost, tlKidsForMost;
      if (aT._kids.length > 1) {
        tlThisForMost = aTlKids + "├──";
        tlKidsForMost = aTlKids + "│  ";
      }
      let tlThisForLast = aTlKids + "└──";
      let tlKidsForLast = aTlKids + "   ";

      for (let [i, kid] of aT._kids.entries()) {
        let isLast = i == aT._kids.length - 1;
        aUnsafeNames.push(kid._unsafeName);
        appendTreeElements2(
          d,
          aProcess,
          aUnsafeNames,
          aRoot,
          kid,
          !isLast ? tlThisForMost : tlThisForLast,
          !isLast ? tlKidsForMost : tlKidsForLast,
          valueText.length
        );
        aUnsafeNames.pop();
      }
    }
  }

  let rootStringLength = aRoot.toString().length;
  appendTreeElements2(
    aP,
    aProcess,
    [aRoot._unsafeName],
    aRoot,
    aRoot,
    aPadText,
    aPadText,
    rootStringLength
  );
}

// ---------------------------------------------------------------------------

function appendSectionHeader(aP, aText) {
  appendElementWithText(aP, "h2", "", aText + "\n");
  let entries = appendElement(aP, "pre", "entries");
  entries.setAttribute("role", "list");
  return entries;
}

// ---------------------------------------------------------------------------

function saveReportsToFile() {
  let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
  fp.appendFilter("Zipped JSON files", "*.json.gz");
  fp.appendFilters(Ci.nsIFilePicker.filterAll);
  fp.filterIndex = 0;
  fp.addToRecentDocs = true;
  fp.defaultString = "memory-report.json.gz";

  let fpFinish = function (aFile) {
    let dumper = Cc["@mozilla.org/memory-info-dumper;1"].getService(
      Ci.nsIMemoryInfoDumper
    );
    let finishDumping = () => {
      updateMainAndFooter(
        "Saved memory reports to " + aFile.path,
        SHOW_TIMESTAMP,
        HIDE_FOOTER
      );
    };
    dumper.dumpMemoryReportsToNamedFile(
      aFile.path,
      finishDumping,
      null,
      gAnonymize.checked,
      /* minimize memory usage = */ false
    );
  };

  let fpCallback = function (aResult) {
    if (
      aResult == Ci.nsIFilePicker.returnOK ||
      aResult == Ci.nsIFilePicker.returnReplace
    ) {
      fpFinish(fp.file);
    }
  };

  try {
    fp.init(
      window.browsingContext,
      "Save Memory Reports",
      Ci.nsIFilePicker.modeSave
    );
  } catch (ex) {
    // This will fail on Android, since there is no Save as file picker there.
    // Just save to the default downloads dir if it does.
    Downloads.getSystemDownloadsDirectory().then(function (aDirPath) {
      let file = FileUtils.File(aDirPath);
      file.append(fp.defaultString);
      fpFinish(file);
    });

    return;
  }
  fp.open(fpCallback);
}
PK
!<N�����/chrome/toolkit/content/global/aboutMemory.xhtml<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- We explicitly set the language to English, which more-or-less guarantees
     that the box-drawing characters used are the correct width. Without that,
     in a Japanese or Chinese locale we might end up with box-drawing
     characters that are twice the width of English characters, which messes up
     the tree layout. See bug 1561153 for details. Note that about:memory is
     not localized, so setting it explicitly to English should be fine.

     We also set the fonts in aboutMemory.css in such a way that maximizes the
     chances that the font chosen supports the box-drawing chars.
-->
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
  <head>
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:; object-src 'none'"
    />
    <meta name="viewport" content="width=device-width" />
    <meta name="color-scheme" content="light dark" />
    <title>Memory Analyzer</title>
    <link
      rel="stylesheet"
      media="screen, projection"
      type="text/css"
      href="chrome://global/skin/in-content/common.css"
    />
    <link
      rel="stylesheet"
      href="chrome://global/skin/aboutMemory.css"
      type="text/css"
    />
    <script src="chrome://global/content/aboutMemory.js" />
  </head>

  <body></body>
</html>
PK
!<�D���.chrome/toolkit/content/global/aboutMozilla.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

html {
  background: maroon radial-gradient( circle, #a01010 0%, #800000 80%) center center / cover no-repeat;
  color: white;
  font-style: italic;
  text-rendering: optimizeLegibility;
  min-height: 100%;
}

#moztext {
  margin-top: 15%;
  font-size: 1.1em;
  font-family: serif;
  text-align: center;
  line-height: 1.5;
}

#from {
  font-size: 1.95em;
  font-family: serif;
  text-align: end;
}

em {
  font-size: 1.3em;
  line-height: 0;
}

a {
  text-decoration: none;
  color: white;
}
PK
!<��}�Z Z 0chrome/toolkit/content/global/aboutNetError.html<!DOCTYPE html>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html data-l10n-sync="true">
  <head>
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:; object-src 'none'"
    />
    <meta name="color-scheme" content="light dark" />
    <title data-l10n-id="neterror-page-title"></title>
    <link
      rel="stylesheet"
      href="chrome://global/skin/aboutNetError.css"
      type="text/css"
      media="all"
    />
    <link rel="icon" id="favicon" />
    <link rel="localization" href="branding/brand.ftl" />
    <link rel="localization" href="toolkit/neterror/certError.ftl" />
    <link rel="localization" href="toolkit/neterror/netError.ftl" />
  </head>
  <body>
    <div class="container">
      <div id="text-container">
        <!-- Error Title -->
        <div class="title">
          <h1 class="title-text"></h1>
        </div>

        <!-- Short Description -->
        <p id="errorShortDesc"></p>
        <p id="errorShortDesc2"></p>

        <div id="errorWhatToDo" hidden="">
          <p
            id="errorWhatToDoTitle"
            data-l10n-id="certerror-what-can-you-do-about-it-title"
          ></p>
          <p id="badStsCertExplanation" hidden=""></p>
          <p id="errorWhatToDoText"></p>
        </div>

        <!-- Long Description -->
        <div id="errorLongDesc"></div>

        <div id="trrOnlyContainer" hidden="">
          <p id="trrOnlyMessage"></p>
          <div class="trr-message-container">
            <span id="trrOnlyDescription"></span>
            <p id="trrLearnMoreContainer" hidden="">
              <a
                id="trrOnlylearnMoreLink"
                target="_blank"
                rel="noopener noreferrer"
                data-l10n-id="neterror-learn-more-link"
              ></a>
            </p>
          </div>
          <p data-l10n-id="neterror-dns-not-found-trr-third-party-warning2"></p>
        </div>

        <div id="nativeFallbackContainer" hidden="">
          <p id="nativeFallbackMessage"></p>
          <div class="trr-message-container">
            <span id="nativeFallbackDescription"></span>
            <p id="nativeFallbackLearnMoreContainer" hidden="">
              <a
                id="nativeFallbackLearnMoreLink"
                target="_blank"
                rel="noopener noreferrer"
                data-l10n-id="neterror-learn-more-link"
              ></a>
            </p>
          </div>
          <p data-l10n-id="neterror-dns-not-found-trr-third-party-warning2"></p>
        </div>

        <p id="tlsVersionNotice" hidden=""></p>

        <p id="learnMoreContainer" hidden="">
          <a
            id="learnMoreLink"
            target="_blank"
            rel="noopener noreferrer"
            data-telemetry-id="learn_more_link"
            data-l10n-id="neterror-learn-more-link"
          ></a>
        </p>

        <div id="openInNewWindowContainer" class="button-container" hidden="">
          <p>
            <a
              id="openInNewWindowButton"
              target="_blank"
              rel="noopener noreferrer"
            >
              <button
                class="primary"
                data-l10n-id="open-in-new-window-for-csp-or-xfo-error"
              ></button
            ></a>
          </p>
        </div>

        <!-- UI for option to report certificate errors to Mozilla. Removed on
             init for other error types .-->
        <div id="prefChangeContainer" class="button-container" hidden="">
          <p data-l10n-id="neterror-pref-reset"></p>
          <button
            id="prefResetButton"
            class="primary"
            data-l10n-id="neterror-pref-reset-button"
          ></button>
        </div>

        <div
          id="certErrorAndCaptivePortalButtonContainer"
          class="button-container"
          hidden=""
        >
          <button
            id="returnButton"
            class="primary"
            data-telemetry-id="return_button_top"
            data-l10n-id="neterror-return-to-previous-page-recommended-button"
          ></button>
          <button
            id="openPortalLoginPageButton"
            class="primary"
            data-l10n-id="neterror-open-portal-login-page-button"
            hidden=""
          ></button>
          <button
            id="certErrorTryAgainButton"
            class="primary try-again"
            data-l10n-id="neterror-try-again-button"
            hidden=""
          ></button>
          <button
            id="advancedButton"
            data-telemetry-id="advanced_button"
            data-l10n-id="neterror-advanced-button"
          ></button>
        </div>
      </div>

      <div id="netErrorButtonContainer" class="button-container" hidden="">
        <button
          id="neterrorTryAgainButton"
          class="primary try-again"
          data-l10n-id="neterror-try-again-button"
          data-telemetry-id="try_again_button"
        ></button>
        <button
          id="trrExceptionButton"
          data-l10n-id="neterror-add-exception-button"
          data-telemetry-id="add_exception_button"
          hidden=""
        ></button>
        <button
          id="trrSettingsButton"
          data-l10n-id="neterror-settings-button"
          data-telemetry-id="settings_button"
          hidden=""
        ></button>
        <button
          id="nativeFallbackContinueThisTimeButton"
          data-l10n-id="neterror-trr-continue-this-time"
          data-telemetry-id="continue_button"
          hidden=""
        ></button>
        <button
          id="nativeFallbackIgnoreButton"
          data-l10n-id="neterror-disable-native-feedback-warning"
          data-telemetry-id="disable_warning"
          hidden=""
        ></button>
      </div>

      <div class="advanced-panel-container">
        <div id="badCertAdvancedPanel" class="advanced-panel" hidden="">
          <p id="badCertTechnicalInfo"></p>
          <a
            id="viewCertificate"
            href="javascript:void(0)"
            data-l10n-id="neterror-view-certificate-link"
          ></a>
          <div id="advancedPanelButtonContainer" class="button-container">
            <button
              id="advancedPanelReturnButton"
              class="primary"
              data-telemetry-id="return_button_adv"
              data-l10n-id="neterror-return-to-previous-page-recommended-button"
            ></button>
            <button
              id="advancedPanelTryAgainButton"
              class="primary try-again"
              data-l10n-id="neterror-try-again-button"
              hidden=""
            ></button>
            <button
              id="exceptionDialogButton"
              data-telemetry-id="exception_button"
              data-l10n-id="neterror-override-exception-button"
            ></button>
          </div>
        </div>

        <div id="blockingErrorReporting" class="advanced-panel" hidden="">
          <p class="toggle-container-with-text">
            <input
              type="checkbox"
              id="automaticallyReportBlockingInFuture"
              role="checkbox"
            />
            <label
              for="automaticallyReportBlockingInFuture"
              data-l10n-id="neterror-error-reporting-automatic"
            ></label>
          </p>
        </div>

        <div
          id="certificateErrorDebugInformation"
          class="advanced-panel"
          hidden=""
        >
          <button
            id="copyToClipboardTop"
            data-telemetry-id="clipboard_button_top"
            data-l10n-id="neterror-copy-to-clipboard-button"
          ></button>
          <div id="certificateErrorText"></div>
          <button
            id="copyToClipboardBottom"
            data-telemetry-id="clipboard_button_bot"
            data-l10n-id="neterror-copy-to-clipboard-button"
          ></button>
        </div>
      </div>
    </div>
    <script src="chrome://global/content/neterror/aboutNetErrorCodes.js"></script>
    <script
      type="module"
      src="chrome://global/content/aboutNetError.mjs"
    ></script>
  </body>
</html>
PK
!<��p����/chrome/toolkit/content/global/aboutNetError.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/remote-page */
/* eslint-disable import/no-unassigned-import */

import {
  parse,
  pemToDER,
} from "chrome://global/content/certviewer/certDecoder.mjs";

const formatter = new Intl.DateTimeFormat();

const HOST_NAME = getHostName();

function getHostName() {
  try {
    return new URL(RPMGetInnerMostURI(document.location.href)).hostname;
  } catch (error) {
    console.error("Could not parse URL", error);
  }
  return "";
}

// Used to check if we have a specific localized message for an error.
const KNOWN_ERROR_TITLE_IDS = new Set([
  // Error titles:
  "connectionFailure-title",
  "deniedPortAccess-title",
  "dnsNotFound-title",
  "dns-not-found-trr-only-title2",
  "fileNotFound-title",
  "fileAccessDenied-title",
  "generic-title",
  "captivePortal-title",
  "malformedURI-title",
  "netInterrupt-title",
  "notCached-title",
  "netOffline-title",
  "contentEncodingError-title",
  "unsafeContentType-title",
  "netReset-title",
  "netTimeout-title",
  "serverError-title",
  "unknownProtocolFound-title",
  "proxyConnectFailure-title",
  "proxyResolveFailure-title",
  "redirectLoop-title",
  "unknownSocketType-title",
  "nssFailure2-title",
  "csp-xfo-error-title",
  "corruptedContentError-title",
  "sslv3Used-title",
  "inadequateSecurityError-title",
  "blockedByPolicy-title",
  "clockSkewError-title",
  "networkProtocolError-title",
  "nssBadCert-title",
  "nssBadCert-sts-title",
  "certerror-mitm-title",
]);

/* The error message IDs from nsserror.ftl get processed into
 * aboutNetErrorCodes.js which is loaded before we are: */
/* global KNOWN_ERROR_MESSAGE_IDS */
const ERROR_MESSAGES_FTL = "toolkit/neterror/nsserrors.ftl";

// The following parameters are parsed from the error URL:
//   e - the error code
//   s - custom CSS class to allow alternate styling/favicons
//   d - error description
//   captive - "true" to indicate we're behind a captive portal.
//             Any other value is ignored.

// Note that this file uses document.documentURI to get
// the URL (with the format from above). This is because
// document.location.href gets the current URI off the docshell,
// which is the URL displayed in the location bar, i.e.
// the URI that the user attempted to load.

let searchParams = new URLSearchParams(document.documentURI.split("?")[1]);

let gErrorCode = searchParams.get("e");
let gIsCertError = gErrorCode == "nssBadCert";
let gHasSts = gIsCertError && getCSSClass() === "badStsCert";

// If the location of the favicon changes, FAVICON_CERTERRORPAGE_URL and/or
// FAVICON_ERRORPAGE_URL in toolkit/components/places/nsFaviconService.idl
// should also be updated.
document.getElementById("favicon").href =
  gIsCertError || gErrorCode == "nssFailure2"
    ? "chrome://global/skin/icons/warning.svg"
    : "chrome://global/skin/icons/info.svg";

function getCSSClass() {
  return searchParams.get("s");
}

function getDescription() {
  return searchParams.get("d");
}

function isCaptive() {
  return searchParams.get("captive") == "true";
}

/**
 * We don't actually know what the MitM is called (since we don't
 * maintain a list), so we'll try and display the common name of the
 * root issuer to the user. In the worst case they are as clueless as
 * before, in the best case this gives them an actionable hint.
 * This may be revised in the future.
 */
function getMitmName(failedCertInfo) {
  return failedCertInfo.issuerCommonName;
}

function retryThis(buttonEl) {
  RPMSendAsyncMessage("Browser:EnableOnlineMode");
  buttonEl.disabled = true;
}

function showPrefChangeContainer() {
  const panel = document.getElementById("prefChangeContainer");
  panel.hidden = false;
  document.getElementById("netErrorButtonContainer").hidden = true;
  document
    .getElementById("prefResetButton")
    .addEventListener("click", function resetPreferences() {
      RPMSendAsyncMessage("Browser:ResetSSLPreferences");
    });
  setFocus("#prefResetButton", "beforeend");
}

function toggleCertErrorDebugInfoVisibility(shouldShow) {
  let debugInfo = document.getElementById("certificateErrorDebugInformation");
  let copyButton = document.getElementById("copyToClipboardTop");

  if (shouldShow === undefined) {
    shouldShow = debugInfo.hidden;
  }
  debugInfo.hidden = !shouldShow;
  if (shouldShow) {
    copyButton.scrollIntoView({ block: "start", behavior: "smooth" });
    copyButton.focus();
  }
}

function setupAdvancedButton() {
  // Get the hostname and add it to the panel
  var panel = document.getElementById("badCertAdvancedPanel");

  // Register click handler for the weakCryptoAdvancedPanel
  document
    .getElementById("advancedButton")
    .addEventListener("click", togglePanelVisibility);

  function togglePanelVisibility() {
    if (panel.hidden) {
      // Reveal
      revealAdvancedPanelSlowlyAsync();
    } else {
      // Hide
      panel.hidden = true;
    }
  }

  if (getCSSClass() == "expertBadCert") {
    revealAdvancedPanelSlowlyAsync();
  }
}

async function revealAdvancedPanelSlowlyAsync() {
  const badCertAdvancedPanel = document.getElementById("badCertAdvancedPanel");
  const exceptionDialogButton = document.getElementById(
    "exceptionDialogButton"
  );

  // Toggling the advanced panel must ensure that the debugging
  // information panel is hidden as well, since it's opened by the
  // error code link in the advanced panel.
  toggleCertErrorDebugInfoVisibility(false);

  // Reveal, but disabled (and grayed-out) for 3.0s.
  badCertAdvancedPanel.hidden = false;
  exceptionDialogButton.disabled = true;

  // -

  if (exceptionDialogButton.resetReveal) {
    exceptionDialogButton.resetReveal(); // Reset if previous is pending.
  }
  let wasReset = false;
  exceptionDialogButton.resetReveal = () => {
    wasReset = true;
  };

  // Wait for 10 frames to ensure that the warning text is rendered
  // and gets all the way to the screen for the user to read it.
  // This is only ~0.160s at 60Hz, so it's not too much extra time that we're
  // taking to ensure that we're caught up with rendering, on top of the
  // (by default) whole second(s) we're going to wait based on the
  // security.dialog_enable_delay pref.
  // The catching-up to rendering is the important part, not the
  // N-frame-delay here.
  for (let i = 0; i < 10; i++) {
    await new Promise(requestAnimationFrame);
  }

  // Wait another Nms (default: 1000) for the user to be very sure. (Sorry speed readers!)
  const securityDelayMs = RPMGetIntPref("security.dialog_enable_delay", 1000);
  await new Promise(go => setTimeout(go, securityDelayMs));

  if (wasReset) {
    return;
  }

  // Enable and un-gray-out.
  exceptionDialogButton.disabled = false;
}

function disallowCertOverridesIfNeeded() {
  // Disallow overrides if this is a Strict-Transport-Security
  // host and the cert is bad (STS Spec section 7.3) or if the
  // certerror is in a frame (bug 633691).
  if (gHasSts || window != top) {
    document.getElementById("exceptionDialogButton").hidden = true;
  }
  if (gHasSts) {
    const stsExplanation = document.getElementById("badStsCertExplanation");
    document.l10n.setAttributes(
      stsExplanation,
      "certerror-what-should-i-do-bad-sts-cert-explanation",
      { hostname: HOST_NAME }
    );
    stsExplanation.hidden = false;

    document.l10n.setAttributes(
      document.getElementById("returnButton"),
      "neterror-return-to-previous-page-button"
    );
    document.l10n.setAttributes(
      document.getElementById("advancedPanelReturnButton"),
      "neterror-return-to-previous-page-button"
    );
  }
}

function recordTRREventTelemetry(
  warningPageType,
  trrMode,
  trrDomain,
  skipReason
) {
  RPMRecordTelemetryEvent(
    "security.doh.neterror",
    "load",
    "dohwarning",
    warningPageType,
    {
      mode: trrMode,
      provider_key: trrDomain,
      skip_reason: skipReason,
    }
  );

  const netErrorButtonDiv = document.getElementById("netErrorButtonContainer");
  const buttons = netErrorButtonDiv.querySelectorAll("button");
  for (let b of buttons) {
    b.addEventListener("click", function (e) {
      let target = e.originalTarget;
      let telemetryId = target.dataset.telemetryId;
      RPMRecordTelemetryEvent(
        "security.doh.neterror",
        "click",
        telemetryId,
        warningPageType,
        {
          mode: trrMode,
          provider_key: trrDomain,
          skip_reason: skipReason,
        }
      );
    });
  }
}

function initPage() {
  // We show an offline support page in case of a system-wide error,
  // when a user cannot connect to the internet and access the SUMO website.
  // For example, clock error, which causes certerrors across the web or
  // a security software conflict where the user is unable to connect
  // to the internet.
  // The URL that prompts us to show an offline support page should have the following
  // format: "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/supportPageSlug",
  // so we can extract the support page slug.
  let baseURL = RPMGetFormatURLPref("app.support.baseURL");
  if (document.location.href.startsWith(baseURL)) {
    let supportPageSlug = document.location.pathname.split("/").pop();
    RPMSendAsyncMessage("DisplayOfflineSupportPage", {
      supportPageSlug,
    });
  }

  const className = getCSSClass();
  if (className) {
    document.body.classList.add(className);
  }

  const isTRROnlyFailure = gErrorCode == "dnsNotFound" && RPMIsTRROnlyFailure();

  let isNativeFallbackWarning = false;
  if (RPMGetBoolPref("network.trr.display_fallback_warning")) {
    isNativeFallbackWarning =
      gErrorCode == "dnsNotFound" && RPMIsNativeFallbackFailure();
  }

  const docTitle = document.querySelector("title");
  const bodyTitle = document.querySelector(".title-text");
  const shortDesc = document.getElementById("errorShortDesc");

  if (gIsCertError) {
    const isStsError = window !== window.top || gHasSts;
    const errArgs = { hostname: HOST_NAME };
    if (isCaptive()) {
      document.l10n.setAttributes(
        docTitle,
        "neterror-captive-portal-page-title"
      );
      document.l10n.setAttributes(bodyTitle, "captivePortal-title");
      document.l10n.setAttributes(
        shortDesc,
        "neterror-captive-portal",
        errArgs
      );
      initPageCaptivePortal();
    } else {
      if (isStsError) {
        document.l10n.setAttributes(docTitle, "certerror-sts-page-title");
        document.l10n.setAttributes(bodyTitle, "nssBadCert-sts-title");
        document.l10n.setAttributes(shortDesc, "certerror-sts-intro", errArgs);
      } else {
        document.l10n.setAttributes(docTitle, "certerror-page-title");
        document.l10n.setAttributes(bodyTitle, "nssBadCert-title");
        document.l10n.setAttributes(shortDesc, "certerror-intro", errArgs);
      }
      initPageCertError();
    }

    initCertErrorPageActions();
    setTechnicalDetailsOnCertError();
    return;
  }

  document.body.classList.add("neterror");

  let longDesc = document.getElementById("errorLongDesc");
  const tryAgain = document.getElementById("netErrorButtonContainer");
  tryAgain.hidden = false;
  const learnMore = document.getElementById("learnMoreContainer");
  const learnMoreLink = document.getElementById("learnMoreLink");
  learnMoreLink.setAttribute("href", baseURL + "connection-not-secure");

  let pageTitleId = "neterror-page-title";
  let bodyTitleId = gErrorCode + "-title";

  switch (gErrorCode) {
    case "blockedByPolicy":
      pageTitleId = "neterror-blocked-by-policy-page-title";
      document.body.classList.add("blocked");

      // Remove the "Try again" button from pages that don't need it.
      // For pages blocked by policy, trying again won't help.
      tryAgain.hidden = true;
      break;

    case "cspBlocked":
    case "xfoBlocked": {
      bodyTitleId = "csp-xfo-error-title";

      // Remove the "Try again" button for XFO and CSP violations,
      // since it's almost certainly useless. (Bug 553180)
      tryAgain.hidden = true;

      // Adding a button for opening websites blocked for CSP and XFO violations
      // in a new window. (Bug 1461195)
      document.getElementById("errorShortDesc").hidden = true;

      document.l10n.setAttributes(longDesc, "csp-xfo-blocked-long-desc", {
        hostname: HOST_NAME,
      });
      longDesc = null;

      document.getElementById("openInNewWindowContainer").hidden =
        RPMGetBoolPref("security.xfocsp.hideOpenInNewWindow");

      const openInNewWindowButton = document.getElementById(
        "openInNewWindowButton"
      );
      openInNewWindowButton.href = document.location.href;

      // Add a learn more link
      learnMore.hidden = false;
      learnMoreLink.setAttribute("href", baseURL + "xframe-neterror-page");

      setupBlockingReportingUI();
      break;
    }

    case "dnsNotFound":
      pageTitleId = "neterror-dns-not-found-title";
      if (!isTRROnlyFailure) {
        RPMCheckAlternateHostAvailable();
      }

      break;
    case "inadequateSecurityError":
      // Remove the "Try again" button from pages that don't need it.
      // For HTTP/2 inadequate security, trying again won't help.
      tryAgain.hidden = true;
      break;

    case "malformedURI":
      pageTitleId = "neterror-malformed-uri-page-title";
      // Remove the "Try again" button from pages that don't need it.
      tryAgain.hidden = true;
      break;

    // TLS errors and non-overridable certificate errors (e.g. pinning
    // failures) are of type nssFailure2.
    case "nssFailure2": {
      learnMore.hidden = false;

      const netErrorInfo = document.getNetErrorInfo();
      void recordSecurityUITelemetry(
        "security.ui.tlserror",
        "load",
        "abouttlserror",
        netErrorInfo
      );
      const errorCode = netErrorInfo.errorCodeString;
      switch (errorCode) {
        case "SSL_ERROR_UNSUPPORTED_VERSION":
        case "SSL_ERROR_PROTOCOL_VERSION_ALERT": {
          const tlsNotice = document.getElementById("tlsVersionNotice");
          tlsNotice.hidden = false;
          document.l10n.setAttributes(tlsNotice, "cert-error-old-tls-version");
        }
        // fallthrough

        case "interrupted": // This happens with subresources that are above the max tls
        case "SSL_ERROR_NO_CIPHERS_SUPPORTED":
        case "SSL_ERROR_NO_CYPHER_OVERLAP":
        case "SSL_ERROR_SSL_DISABLED":
          RPMAddMessageListener("HasChangedCertPrefs", msg => {
            if (msg.data.hasChangedCertPrefs) {
              // Configuration overrides might have caused this; offer to reset.
              showPrefChangeContainer();
            }
          });
          RPMSendAsyncMessage("GetChangedCertPrefs");
      }

      break;
    }

    case "sslv3Used":
      learnMore.hidden = false;
      document.body.className = "certerror";
      break;
  }

  if (!KNOWN_ERROR_TITLE_IDS.has(bodyTitleId)) {
    console.error("No strings exist for error:", gErrorCode);
    bodyTitleId = "generic-title";
  }

  // The TRR errors may present options that direct users to settings only available on Firefox Desktop
  if (RPMIsFirefox()) {
    if (isTRROnlyFailure) {
      document.body.className = "certerror"; // Shows warning icon
      pageTitleId = "dns-not-found-trr-only-title2";
      document.l10n.setAttributes(docTitle, pageTitleId);
      bodyTitleId = "dns-not-found-trr-only-title2";
      document.l10n.setAttributes(bodyTitle, bodyTitleId);

      shortDesc.textContent = "";
      let skipReason = RPMGetTRRSkipReason();

      // enable buttons
      let trrExceptionButton = document.getElementById("trrExceptionButton");
      trrExceptionButton.addEventListener("click", () => {
        RPMSendQuery("Browser:AddTRRExcludedDomain", {
          hostname: HOST_NAME,
        }).then(() => {
          retryThis(trrExceptionButton);
        });
      });

      let isTrrServerError = true;
      if (RPMIsSiteSpecificTRRError()) {
        // Only show the exclude button if the failure is specific to this
        // domain. If the TRR server is inaccessible we don't want to allow
        // the user to add an exception just for this domain.
        trrExceptionButton.hidden = false;
        isTrrServerError = false;
      }
      let trrSettingsButton = document.getElementById("trrSettingsButton");
      trrSettingsButton.addEventListener("click", () => {
        RPMSendAsyncMessage("OpenTRRPreferences");
      });
      trrSettingsButton.hidden = false;
      let message = document.getElementById("trrOnlyMessage");
      document.l10n.setAttributes(
        message,
        "neterror-dns-not-found-trr-only-reason2",
        {
          hostname: HOST_NAME,
        }
      );

      let descriptionTag = "neterror-dns-not-found-trr-unknown-problem";
      let args = { trrDomain: RPMGetTRRDomain() };
      if (
        skipReason == "TRR_FAILED" ||
        skipReason == "TRR_CHANNEL_DNS_FAIL" ||
        skipReason == "TRR_UNKNOWN_CHANNEL_FAILURE" ||
        skipReason == "TRR_NET_REFUSED" ||
        skipReason == "TRR_NET_INTERRUPT" ||
        skipReason == "TRR_NET_INADEQ_SEQURITY"
      ) {
        descriptionTag = "neterror-dns-not-found-trr-only-could-not-connect";
      } else if (skipReason == "TRR_TIMEOUT") {
        descriptionTag = "neterror-dns-not-found-trr-only-timeout";
      } else if (
        skipReason == "TRR_BROWSER_IS_OFFLINE" ||
        skipReason == "TRR_NO_CONNECTIVITY"
      ) {
        descriptionTag = "neterror-dns-not-found-trr-offline";
      } else if (
        skipReason == "TRR_NO_ANSWERS" ||
        skipReason == "TRR_NXDOMAIN" ||
        skipReason == "TRR_RCODE_FAIL"
      ) {
        descriptionTag = "neterror-dns-not-found-trr-unknown-host2";
      } else if (
        skipReason == "TRR_DECODE_FAILED" ||
        skipReason == "TRR_SERVER_RESPONSE_ERR"
      ) {
        descriptionTag = "neterror-dns-not-found-trr-server-problem";
      } else if (skipReason == "TRR_BAD_URL") {
        descriptionTag = "neterror-dns-not-found-bad-trr-url";
      } else if (skipReason == "TRR_SYSTEM_SLEEP_MODE") {
        descriptionTag = "neterror-dns-not-found-system-sleep";
      }

      let trrMode = RPMGetIntPref("network.trr.mode").toString();
      recordTRREventTelemetry(
        "TRROnlyFailure",
        trrMode,
        args.trrDomain,
        skipReason
      );

      let description = document.getElementById("trrOnlyDescription");
      document.l10n.setAttributes(description, descriptionTag, args);

      const trrLearnMoreContainer = document.getElementById(
        "trrLearnMoreContainer"
      );
      trrLearnMoreContainer.hidden = false;
      let trrOnlyLearnMoreLink = document.getElementById(
        "trrOnlylearnMoreLink"
      );
      if (isTrrServerError) {
        // Go to DoH settings page
        trrOnlyLearnMoreLink.href = "about:preferences#privacy-doh";
        trrOnlyLearnMoreLink.addEventListener("click", event => {
          event.preventDefault();
          RPMSendAsyncMessage("OpenTRRPreferences");
          RPMRecordTelemetryEvent(
            "security.doh.neterror",
            "click",
            "settings_button",
            "TRROnlyFailure",
            {
              mode: trrMode,
              provider_key: args.trrDomain,
              skip_reason: skipReason,
            }
          );
        });
      } else {
        // This will be replaced at a later point with a link to an offline support page
        // https://bugzilla.mozilla.org/show_bug.cgi?id=1806257
        trrOnlyLearnMoreLink.href =
          RPMGetFormatURLPref("network.trr_ui.skip_reason_learn_more_url") +
          skipReason.toLowerCase().replaceAll("_", "-");
      }

      let div = document.getElementById("trrOnlyContainer");
      div.hidden = false;

      return;
    } else if (isNativeFallbackWarning) {
      showNativeFallbackWarning();
      return;
    }
  }

  document.l10n.setAttributes(docTitle, pageTitleId);
  document.l10n.setAttributes(bodyTitle, bodyTitleId);

  shortDesc.textContent = getDescription();
  setFocus("#netErrorButtonContainer > .try-again");

  if (longDesc) {
    const parts = getNetErrorDescParts();
    setNetErrorMessageFromParts(longDesc, parts);
  }

  setNetErrorMessageFromCode();
}

function showNativeFallbackWarning() {
  const docTitle = document.querySelector("title");
  const bodyTitle = document.querySelector(".title-text");
  const shortDesc = document.getElementById("errorShortDesc");

  let pageTitleId = "neterror-page-title";
  let bodyTitleId = gErrorCode + "-title";

  document.body.className = "certerror"; // Shows warning icon
  pageTitleId = "dns-not-found-native-fallback-title2";
  document.l10n.setAttributes(docTitle, pageTitleId);

  bodyTitleId = "dns-not-found-native-fallback-title2";
  document.l10n.setAttributes(bodyTitle, bodyTitleId);

  shortDesc.textContent = "";
  let nativeFallbackIgnoreButton = document.getElementById(
    "nativeFallbackIgnoreButton"
  );
  nativeFallbackIgnoreButton.addEventListener("click", () => {
    RPMSetPref("network.trr.display_fallback_warning", false);
    retryThis(nativeFallbackIgnoreButton);
  });

  let continueThisTimeButton = document.getElementById(
    "nativeFallbackContinueThisTimeButton"
  );
  continueThisTimeButton.addEventListener("click", () => {
    RPMSetTRRDisabledLoadFlags();
    document.location.reload();
  });
  continueThisTimeButton.hidden = false;

  nativeFallbackIgnoreButton.hidden = false;
  let message = document.getElementById("nativeFallbackMessage");
  document.l10n.setAttributes(
    message,
    "neterror-dns-not-found-native-fallback-reason2",
    {
      hostname: HOST_NAME,
    }
  );
  let skipReason = RPMGetTRRSkipReason();
  let descriptionTag = "neterror-dns-not-found-trr-unknown-problem";
  let args = { trrDomain: RPMGetTRRDomain() };

  if (skipReason.includes("HEURISTIC_TRIPPED")) {
    descriptionTag = "neterror-dns-not-found-native-fallback-heuristic";
  } else if (skipReason == "TRR_NOT_CONFIRMED") {
    descriptionTag = "neterror-dns-not-found-native-fallback-not-confirmed2";
  }

  let description = document.getElementById("nativeFallbackDescription");
  document.l10n.setAttributes(description, descriptionTag, args);

  let learnMoreContainer = document.getElementById(
    "nativeFallbackLearnMoreContainer"
  );
  learnMoreContainer.hidden = false;

  let learnMoreLink = document.getElementById("nativeFallbackLearnMoreLink");
  learnMoreLink.href =
    RPMGetFormatURLPref("network.trr_ui.skip_reason_learn_more_url") +
    skipReason.toLowerCase().replaceAll("_", "-");

  let div = document.getElementById("nativeFallbackContainer");
  div.hidden = false;

  recordTRREventTelemetry(
    "NativeFallbackWarning",
    RPMGetIntPref("network.trr.mode").toString(),
    args.trrDomain,
    skipReason
  );
}
/**
 * Builds HTML elements from `parts` and appends them to `parentElement`.
 *
 * @param {HTMLElement} parentElement
 * @param {Array<["li" | "p" | "span", string, Record<string, string> | undefined]>} parts
 */
function setNetErrorMessageFromParts(parentElement, parts) {
  let list = null;

  for (let [tag, l10nId, l10nArgs] of parts) {
    const elem = document.createElement(tag);
    elem.dataset.l10nId = l10nId;
    if (l10nArgs) {
      elem.dataset.l10nArgs = JSON.stringify(l10nArgs);
    }

    if (tag === "li") {
      if (!list) {
        list = document.createElement("ul");
        parentElement.appendChild(list);
      }
      list.appendChild(elem);
    } else {
      if (list) {
        list = null;
      }
      parentElement.appendChild(elem);
    }
  }
}

/**
 * Returns an array of tuples determining the parts of an error message:
 * - HTML tag name
 * - l10n id
 * - l10n args (optional)
 *
 * @returns { Array<["li" | "p" | "span", string, Record<string, string> | undefined]> }
 */
function getNetErrorDescParts() {
  switch (gErrorCode) {
    case "connectionFailure":
    case "netInterrupt":
    case "netReset":
    case "netTimeout":
    case "serverError":
      return [
        ["li", "neterror-load-error-try-again"],
        ["li", "neterror-load-error-connection"],
        ["li", "neterror-load-error-firewall"],
      ];

    case "blockedByPolicy":
    case "deniedPortAccess":
    case "malformedURI":
      return [];

    case "captivePortal":
      return [["p", ""]];
    case "contentEncodingError":
      return [["li", "neterror-content-encoding-error"]];
    case "corruptedContentErrorv2":
      return [
        ["p", "neterror-corrupted-content-intro"],
        ["li", "neterror-corrupted-content-contact-website"],
      ];
    case "dnsNotFound":
      return [
        ["span", "neterror-dns-not-found-hint-header"],
        ["li", "neterror-dns-not-found-hint-try-again"],
        ["li", "neterror-dns-not-found-hint-check-network"],
        ["li", "neterror-dns-not-found-hint-firewall"],
      ];
    case "fileAccessDenied":
      return [["li", "neterror-access-denied"]];
    case "fileNotFound":
      return [
        ["li", "neterror-file-not-found-filename"],
        ["li", "neterror-file-not-found-moved"],
      ];
    case "inadequateSecurityError":
      return [
        ["p", "neterror-inadequate-security-intro", { hostname: HOST_NAME }],
        ["p", "neterror-inadequate-security-code"],
      ];
    case "mitm": {
      const failedCertInfo = document.getFailedCertSecurityInfo();
      const errArgs = {
        hostname: HOST_NAME,
        mitm: getMitmName(failedCertInfo),
      };
      return [["span", "certerror-mitm", errArgs]];
    }
    case "netOffline":
      return [["li", "neterror-net-offline"]];
    case "networkProtocolError":
      return [
        ["p", "neterror-network-protocol-error-intro"],
        ["li", "neterror-network-protocol-error-contact-website"],
      ];
    case "notCached":
      return [
        ["p", "neterror-not-cached-intro"],
        ["li", "neterror-not-cached-sensitive"],
        ["li", "neterror-not-cached-try-again"],
      ];
    case "nssFailure2":
      return [
        ["li", "neterror-nss-failure-not-verified"],
        ["li", "neterror-nss-failure-contact-website"],
      ];
    case "proxyConnectFailure":
      return [
        ["li", "neterror-proxy-connect-failure-settings"],
        ["li", "neterror-proxy-connect-failure-contact-admin"],
      ];
    case "proxyResolveFailure":
      return [
        ["li", "neterror-proxy-resolve-failure-settings"],
        ["li", "neterror-proxy-resolve-failure-connection"],
        ["li", "neterror-proxy-resolve-failure-firewall"],
      ];
    case "redirectLoop":
      return [["li", "neterror-redirect-loop"]];
    case "sslv3Used":
      return [["span", "neterror-sslv3-used"]];
    case "unknownProtocolFound":
      return [["li", "neterror-unknown-protocol"]];
    case "unknownSocketType":
      return [
        ["li", "neterror-unknown-socket-type-psm-installed"],
        ["li", "neterror-unknown-socket-type-server-config"],
      ];
    case "unsafeContentType":
      return [["li", "neterror-unsafe-content-type"]];

    default:
      return [["p", "neterror-generic-error"]];
  }
}

function setNetErrorMessageFromCode() {
  let errorCode;
  try {
    errorCode = document.getNetErrorInfo().errorCodeString;
  } catch (ex) {
    // We don't have a securityInfo when this is for example a DNS error.
    return;
  }

  let errorMessage;
  if (errorCode) {
    const l10nId = errorCode.replace(/_/g, "-").toLowerCase();
    if (KNOWN_ERROR_MESSAGE_IDS.has(l10nId)) {
      const l10n = new Localization([ERROR_MESSAGES_FTL], true);
      errorMessage = l10n.formatValueSync(l10nId);
    }

    const shortDesc2 = document.getElementById("errorShortDesc2");
    document.l10n.setAttributes(shortDesc2, "cert-error-code-prefix", {
      error: errorCode,
    });
  } else {
    console.warn("This error page has no error code in its security info");
  }

  let hostname = HOST_NAME;
  const { port } = document.location;
  if (port && port != 443) {
    hostname += ":" + port;
  }

  const shortDesc = document.getElementById("errorShortDesc");
  document.l10n.setAttributes(shortDesc, "cert-error-ssl-connection-error", {
    errorMessage: errorMessage ?? errorCode ?? "",
    hostname,
  });
}

function setupBlockingReportingUI() {
  let checkbox = document.getElementById("automaticallyReportBlockingInFuture");

  let reportingAutomatic = RPMGetBoolPref(
    "security.xfocsp.errorReporting.automatic"
  );
  checkbox.checked = !!reportingAutomatic;

  checkbox.addEventListener("change", function ({ target: { checked } }) {
    RPMSetPref("security.xfocsp.errorReporting.automatic", checked);

    // If we're enabling reports, send a report for this failure.
    if (checked) {
      reportBlockingError();
    }
  });

  let reportingEnabled = RPMGetBoolPref(
    "security.xfocsp.errorReporting.enabled"
  );

  if (reportingEnabled) {
    // Display blocking error reporting UI for XFO error and CSP error.
    document.getElementById("blockingErrorReporting").hidden = false;

    if (reportingAutomatic) {
      reportBlockingError();
    }
  }
}

function reportBlockingError() {
  // We only report if we are in a frame.
  if (window === window.top) {
    return;
  }

  let err = gErrorCode;
  // Ensure we only deal with XFO and CSP here.
  if (!["xfoBlocked", "cspBlocked"].includes(err)) {
    return;
  }

  let xfo_header = RPMGetHttpResponseHeader("X-Frame-Options");
  let csp_header = RPMGetHttpResponseHeader("Content-Security-Policy");

  // Extract the 'CSP: frame-ancestors' from the CSP header.
  let reg = /(?:^|\s)frame-ancestors\s([^;]*)[$]*/i;
  let match = reg.exec(csp_header);
  csp_header = match ? match[1] : "";

  // If it's the csp error page without the CSP: frame-ancestors, this means
  // this error page is not triggered by CSP: frame-ancestors. So, we bail out
  // early.
  if (err === "cspBlocked" && !csp_header) {
    return;
  }

  let xfoAndCspInfo = {
    error_type: err === "xfoBlocked" ? "xfo" : "csp",
    xfo_header,
    csp_header,
  };

  // Trimming the tail colon symbol.
  let scheme = document.location.protocol.slice(0, -1);

  RPMSendAsyncMessage("ReportBlockingError", {
    scheme,
    host: document.location.host,
    port: parseInt(document.location.port) || -1,
    path: document.location.pathname,
    xfoAndCspInfo,
  });
}

function initPageCaptivePortal() {
  document.body.className = "captiveportal";
  document.getElementById("returnButton").hidden = true;
  const openButton = document.getElementById("openPortalLoginPageButton");
  openButton.hidden = false;
  openButton.addEventListener("click", () => {
    RPMSendAsyncMessage("Browser:OpenCaptivePortalPage");
  });

  setFocus("#openPortalLoginPageButton");
  setupAdvancedButton();
  disallowCertOverridesIfNeeded();

  // When the portal is freed, an event is sent by the parent process
  // that we can pick up and attempt to reload the original page.
  RPMAddMessageListener("AboutNetErrorCaptivePortalFreed", () => {
    document.location.reload();
  });
}

function initPageCertError() {
  document.body.classList.add("certerror");

  setFocus("#returnButton");
  setupAdvancedButton();
  disallowCertOverridesIfNeeded();

  const hideAddExceptionButton = RPMGetBoolPref(
    "security.certerror.hideAddException",
    false
  );
  if (hideAddExceptionButton) {
    document.getElementById("exceptionDialogButton").hidden = true;
  }

  const els = document.querySelectorAll("[data-telemetry-id]");
  for (let el of els) {
    el.addEventListener("click", recordClickTelemetry);
  }

  const failedCertInfo = document.getFailedCertSecurityInfo();
  void recordSecurityUITelemetry(
    "security.ui.certerror",
    "load",
    "aboutcerterror",
    failedCertInfo
  );

  setCertErrorDetails();
}

async function recordSecurityUITelemetry(category, evt, objectName, errorInfo) {
  // Truncate the error code to avoid going over the allowed
  // string size limit for telemetry events.
  let errorCode = errorInfo.errorCodeString.substring(0, 40);
  let extraKeys = {
    is_frame: (window.parent != window).toString(),
  };
  if (category == "security.ui.certerror") {
    extraKeys.has_sts = gHasSts.toString();
  }
  if (evt == "load") {
    extraKeys.channel_status = errorInfo.channelStatus.toString();
  }
  if (category == "security.ui.certerror" && evt == "load") {
    extraKeys.issued_by_cca = false.toString();
    let issuer = errorInfo.certChainStrings.at(-1);
    if (issuer && errorCode == "SEC_ERROR_UNKNOWN_ISSUER") {
      try {
        let parsed = await parse(pemToDER(issuer));
        extraKeys.issued_by_cca = (
          parsed.issuer.dn == "c=IN, o=India PKI, cn=CCA India 2022 SPL" ||
          parsed.issuer.dn == "c=IN, o=India PKI, cn=CCA India 2015 SPL"
        ).toString();
      } catch (e) {
        console.error("error parsing issuer certificate:", e);
      }
    }
  }
  RPMRecordTelemetryEvent(category, evt, objectName, errorCode, extraKeys);
}

function recordClickTelemetry(e) {
  let target = e.originalTarget;
  let telemetryId = target.dataset.telemetryId;
  let failedCertInfo = document.getFailedCertSecurityInfo();
  void recordSecurityUITelemetry(
    "security.ui.certerror",
    "click",
    telemetryId,
    failedCertInfo
  );
}

function initCertErrorPageActions() {
  document.getElementById(
    "certErrorAndCaptivePortalButtonContainer"
  ).hidden = false;
  document
    .getElementById("returnButton")
    .addEventListener("click", onReturnButtonClick);
  document
    .getElementById("advancedPanelReturnButton")
    .addEventListener("click", onReturnButtonClick);
  document
    .getElementById("copyToClipboardTop")
    .addEventListener("click", copyPEMToClipboard);
  document
    .getElementById("copyToClipboardBottom")
    .addEventListener("click", copyPEMToClipboard);
  document
    .getElementById("exceptionDialogButton")
    .addEventListener("click", addCertException);
}

function addCertException() {
  const isPermanent =
    !RPMIsWindowPrivate() &&
    RPMGetBoolPref("security.certerrors.permanentOverride");
  document.addCertException(!isPermanent).then(
    () => {
      location.reload();
    },
    () => {}
  );
}

function onReturnButtonClick() {
  RPMSendAsyncMessage("Browser:SSLErrorGoBack");
}

function copyPEMToClipboard() {
  const errorText = document.getElementById("certificateErrorText");
  navigator.clipboard.writeText(errorText.textContent);
}

async function getFailedCertificatesAsPEMString() {
  let locationUrl = document.location.href;
  let failedCertInfo = document.getFailedCertSecurityInfo();
  let errorMessage = failedCertInfo.errorMessage;
  let hasHSTS = failedCertInfo.hasHSTS.toString();
  let hasHPKP = failedCertInfo.hasHPKP.toString();
  let [hstsLabel, hpkpLabel, failedChainLabel] =
    await document.l10n.formatValues([
      { id: "cert-error-details-hsts-label", args: { hasHSTS } },
      { id: "cert-error-details-key-pinning-label", args: { hasHPKP } },
      { id: "cert-error-details-cert-chain-label" },
    ]);

  let certStrings = failedCertInfo.certChainStrings;
  let failedChainCertificates = "";
  for (let der64 of certStrings) {
    let wrapped = der64.replace(/(\S{64}(?!$))/g, "$1\r\n");
    failedChainCertificates +=
      "-----BEGIN CERTIFICATE-----\r\n" +
      wrapped +
      "\r\n-----END CERTIFICATE-----\r\n";
  }

  let details =
    locationUrl +
    "\r\n\r\n" +
    errorMessage +
    "\r\n\r\n" +
    hstsLabel +
    "\r\n" +
    hpkpLabel +
    "\r\n\r\n" +
    failedChainLabel +
    "\r\n\r\n" +
    failedChainCertificates;
  return details;
}

function setCertErrorDetails() {
  // Check if the connection is being man-in-the-middled. When the parent
  // detects an intercepted connection, the page may be reloaded with a new
  // error code (MOZILLA_PKIX_ERROR_MITM_DETECTED).
  const failedCertInfo = document.getFailedCertSecurityInfo();
  const mitmPrimingEnabled = RPMGetBoolPref(
    "security.certerrors.mitm.priming.enabled"
  );
  if (
    mitmPrimingEnabled &&
    failedCertInfo.errorCodeString == "SEC_ERROR_UNKNOWN_ISSUER" &&
    // Only do this check for top-level failures.
    window.parent == window
  ) {
    RPMSendAsyncMessage("Browser:PrimeMitm");
  }

  document.body.setAttribute("code", failedCertInfo.errorCodeString);

  const learnMore = document.getElementById("learnMoreContainer");
  learnMore.hidden = false;
  const learnMoreLink = document.getElementById("learnMoreLink");
  const baseURL = RPMGetFormatURLPref("app.support.baseURL");
  learnMoreLink.href = baseURL + "connection-not-secure";

  const bodyTitle = document.querySelector(".title-text");
  const shortDesc = document.getElementById("errorShortDesc");
  const shortDesc2 = document.getElementById("errorShortDesc2");

  let whatToDoParts = null;

  switch (failedCertInfo.errorCodeString) {
    case "SSL_ERROR_BAD_CERT_DOMAIN":
      whatToDoParts = [
        ["p", "certerror-bad-cert-domain-what-can-you-do-about-it"],
      ];
      break;

    case "SEC_ERROR_OCSP_INVALID_SIGNING_CERT": // FIXME - this would have thrown?
      break;

    case "SEC_ERROR_UNKNOWN_ISSUER":
      whatToDoParts = [
        ["p", "certerror-unknown-issuer-what-can-you-do-about-it-website"],
        [
          "p",
          "certerror-unknown-issuer-what-can-you-do-about-it-contact-admin",
        ],
      ];
      break;

    // This error code currently only exists for the Symantec distrust
    // in Firefox 63, so we add copy explaining that to the user.
    // In case of future distrusts of that scale we might need to add
    // additional parameters that allow us to identify the affected party
    // without replicating the complex logic from certverifier code.
    case "MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED": {
      document.l10n.setAttributes(
        shortDesc2,
        "cert-error-symantec-distrust-description",
        { hostname: HOST_NAME }
      );

      // FIXME - this does nothing
      const adminDesc = document.createElement("p");
      document.l10n.setAttributes(
        adminDesc,
        "cert-error-symantec-distrust-admin"
      );

      learnMoreLink.href = baseURL + "symantec-warning";
      break;
    }

    case "MOZILLA_PKIX_ERROR_MITM_DETECTED": {
      const autoEnabledEnterpriseRoots = RPMGetBoolPref(
        "security.enterprise_roots.auto-enabled",
        false
      );
      if (mitmPrimingEnabled && autoEnabledEnterpriseRoots) {
        RPMSendAsyncMessage("Browser:ResetEnterpriseRootsPref");
      }

      learnMoreLink.href = baseURL + "security-error";

      document.l10n.setAttributes(bodyTitle, "certerror-mitm-title");

      document.l10n.setAttributes(shortDesc, "certerror-mitm", {
        hostname: HOST_NAME,
        mitm: getMitmName(failedCertInfo),
      });

      const id3 = gHasSts
        ? "certerror-mitm-what-can-you-do-about-it-attack-sts"
        : "certerror-mitm-what-can-you-do-about-it-attack";
      whatToDoParts = [
        ["li", "certerror-mitm-what-can-you-do-about-it-antivirus"],
        ["li", "certerror-mitm-what-can-you-do-about-it-corporate"],
        ["li", id3, { mitm: getMitmName(failedCertInfo) }],
      ];
      break;
    }

    case "MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT":
      learnMoreLink.href = baseURL + "security-error";
      break;

    // In case the certificate expired we make sure the system clock
    // matches the remote-settings service (blocklist via Kinto) ping time
    // and is not before the build date.
    case "SEC_ERROR_EXPIRED_CERTIFICATE":
    case "SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE":
    case "MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE":
    case "MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE": {
      learnMoreLink.href = baseURL + "time-errors";

      // We check against the remote-settings server time first if available, because that allows us
      // to give the user an approximation of what the correct time is.
      const difference = RPMGetIntPref(
        "services.settings.clock_skew_seconds",
        0
      );
      const lastFetched =
        RPMGetIntPref("services.settings.last_update_seconds", 0) * 1000;

      // This is set to true later if the user's system clock is at fault for this error.
      let clockSkew = false;

      const now = Date.now();
      const certRange = {
        notBefore: failedCertInfo.certValidityRangeNotBefore,
        notAfter: failedCertInfo.certValidityRangeNotAfter,
      };
      const approximateDate = now - difference * 1000;
      // If the difference is more than a day, we last fetched the date in the last 5 days,
      // and adjusting the date per the interval would make the cert valid, warn the user:
      if (
        Math.abs(difference) > 60 * 60 * 24 &&
        now - lastFetched <= 60 * 60 * 24 * 5 * 1000 &&
        certRange.notBefore < approximateDate &&
        certRange.notAfter > approximateDate
      ) {
        clockSkew = true;
        // If there is no clock skew with Kinto servers, check against the build date.
        // (The Kinto ping could have happened when the time was still right, or not at all)
      } else {
        const appBuildID = RPMGetAppBuildID();
        const year = parseInt(appBuildID.substr(0, 4), 10);
        const month = parseInt(appBuildID.substr(4, 2), 10) - 1;
        const day = parseInt(appBuildID.substr(6, 2), 10);

        const buildDate = new Date(year, month, day);

        // We don't check the notBefore of the cert with the build date,
        // as it is of course almost certain that it is now later than the build date,
        // so we shouldn't exclude the possibility that the cert has become valid
        // since the build date.
        if (buildDate > now && new Date(certRange.notAfter) > buildDate) {
          clockSkew = true;
        }
      }

      if (clockSkew) {
        document.body.classList.add("clockSkewError");
        document.l10n.setAttributes(bodyTitle, "clockSkewError-title");
        document.l10n.setAttributes(shortDesc, "neterror-clock-skew-error", {
          hostname: HOST_NAME,
          now,
        });
        document.getElementById("returnButton").hidden = true;
        document.getElementById("certErrorTryAgainButton").hidden = false;
        document.getElementById("advancedButton").hidden = true;

        document.getElementById("advancedPanelReturnButton").hidden = true;
        document.getElementById("advancedPanelTryAgainButton").hidden = false;
        document.getElementById("exceptionDialogButton").hidden = true;
        break;
      }

      document.l10n.setAttributes(shortDesc, "certerror-expired-cert-intro", {
        hostname: HOST_NAME,
      });

      // The secondary description mentions expired certificates explicitly
      // and should only be shown if the certificate has actually expired
      // instead of being not yet valid.
      if (failedCertInfo.errorCodeString == "SEC_ERROR_EXPIRED_CERTIFICATE") {
        const sd2Id = gHasSts
          ? "certerror-expired-cert-sts-second-para"
          : "certerror-expired-cert-second-para";
        document.l10n.setAttributes(shortDesc2, sd2Id);
        if (
          Math.abs(difference) <= 60 * 60 * 24 &&
          now - lastFetched <= 60 * 60 * 24 * 5 * 1000
        ) {
          whatToDoParts = [
            ["p", "certerror-bad-cert-domain-what-can-you-do-about-it"],
          ];
        }
      }

      whatToDoParts ??= [
        [
          "p",
          "certerror-expired-cert-what-can-you-do-about-it-clock",
          { hostname: HOST_NAME, now },
        ],
        [
          "p",
          "certerror-expired-cert-what-can-you-do-about-it-contact-website",
        ],
      ];
      break;
    }
  }

  if (whatToDoParts) {
    setNetErrorMessageFromParts(
      document.getElementById("errorWhatToDoText"),
      whatToDoParts
    );
    document.getElementById("errorWhatToDo").hidden = false;
  }
}

async function getSubjectAltNames(failedCertInfo) {
  const serverCertBase64 = failedCertInfo.certChainStrings[0];
  const parsed = await parse(pemToDER(serverCertBase64));
  const subjectAltNamesExtension = parsed.ext.san;
  const subjectAltNames = [];
  if (subjectAltNamesExtension) {
    for (let [key, value] of subjectAltNamesExtension.altNames) {
      if (key === "DNS Name" && value.length) {
        subjectAltNames.push(value);
      }
    }
  }
  return subjectAltNames;
}

// The optional argument is only here for testing purposes.
function setTechnicalDetailsOnCertError(
  failedCertInfo = document.getFailedCertSecurityInfo()
) {
  let technicalInfo = document.getElementById("badCertTechnicalInfo");
  technicalInfo.textContent = "";

  function addLabel(l10nId, args = null, attrs = null) {
    let elem = document.createElement("label");
    technicalInfo.appendChild(elem);

    let newLines = document.createTextNode("\n \n");
    technicalInfo.appendChild(newLines);

    if (attrs) {
      let link = document.createElement("a");
      for (let [attr, value] of Object.entries(attrs)) {
        link.setAttribute(attr, value);
      }
      elem.appendChild(link);
    }

    document.l10n.setAttributes(elem, l10nId, args);
  }

  function addErrorCodeLink() {
    addLabel(
      "cert-error-code-prefix-link",
      { error: failedCertInfo.errorCodeString },
      {
        title: failedCertInfo.errorCodeString,
        id: "errorCode",
        "data-l10n-name": "error-code-link",
        "data-telemetry-id": "error_code_link",
        href: "#certificateErrorDebugInformation",
      }
    );

    // We're attaching the event listener to the parent element and not on
    // the errorCodeLink itself because event listeners cannot be attached
    // to fluent DOM overlays.
    technicalInfo.addEventListener("click", event => {
      if (event.target.id === "errorCode") {
        event.preventDefault();
        toggleCertErrorDebugInfoVisibility();
        recordClickTelemetry(event);
      }
    });
  }

  let hostname = HOST_NAME;
  const { port } = document.location;
  if (port && port != 443) {
    hostname += ":" + port;
  }

  switch (failedCertInfo.overridableErrorCategory) {
    case "trust-error":
      switch (failedCertInfo.errorCodeString) {
        case "MOZILLA_PKIX_ERROR_MITM_DETECTED":
          addLabel("cert-error-mitm-intro");
          addLabel("cert-error-mitm-mozilla");
          addLabel("cert-error-mitm-connection");
          break;
        case "SEC_ERROR_UNKNOWN_ISSUER":
          addLabel("cert-error-trust-unknown-issuer-intro");
          addLabel("cert-error-trust-unknown-issuer", { hostname });
          break;
        case "SEC_ERROR_CA_CERT_INVALID":
          addLabel("cert-error-intro", { hostname });
          addLabel("cert-error-trust-cert-invalid");
          break;
        case "SEC_ERROR_UNTRUSTED_ISSUER":
          addLabel("cert-error-intro", { hostname });
          addLabel("cert-error-trust-untrusted-issuer");
          break;
        case "SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED":
          addLabel("cert-error-intro", { hostname });
          addLabel("cert-error-trust-signature-algorithm-disabled");
          break;
        case "SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE":
          addLabel("cert-error-intro", { hostname });
          addLabel("cert-error-trust-expired-issuer");
          break;
        case "MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT":
          addLabel("cert-error-intro", { hostname });
          addLabel("cert-error-trust-self-signed");
          break;
        case "MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED":
          addLabel("cert-error-intro", { hostname });
          addLabel("cert-error-trust-symantec");
          break;
        default:
          addLabel("cert-error-intro", { hostname });
          addLabel("cert-error-untrusted-default");
      }
      addErrorCodeLink();
      break;

    case "expired-or-not-yet-valid": {
      const notBefore = failedCertInfo.validNotBefore;
      const notAfter = failedCertInfo.validNotAfter;
      if (notBefore && Date.now() < notAfter) {
        addLabel("cert-error-not-yet-valid-now", {
          hostname,
          "not-before-local-time": formatter.format(new Date(notBefore)),
        });
      } else {
        addLabel("cert-error-expired-now", {
          hostname,
          "not-after-local-time": formatter.format(new Date(notAfter)),
        });
      }
      addErrorCodeLink();
      break;
    }

    case "domain-mismatch":
      getSubjectAltNames(failedCertInfo).then(subjectAltNames => {
        if (!subjectAltNames.length) {
          addLabel("cert-error-domain-mismatch", { hostname });
        } else if (subjectAltNames.length > 1) {
          const names = subjectAltNames.join(", ");
          addLabel("cert-error-domain-mismatch-multiple", {
            hostname,
            "subject-alt-names": names,
          });
        } else {
          const altName = subjectAltNames[0];

          // If the alt name is a wildcard domain ("*.example.com")
          // let's use "www" instead.  "*.example.com" isn't going to
          // get anyone anywhere useful. bug 432491
          const okHost = altName.replace(/^\*\./, "www.");

          // Let's check if we want to make this a link.
          const showLink =
            /* case #1:
             * example.com uses an invalid security certificate.
             *
             * The certificate is only valid for www.example.com
             *
             * Make sure to include the "." ahead of thisHost so that a
             * MitM attack on paypal.com doesn't hyperlink to "notpaypal.com"
             *
             * We'd normally just use a RegExp here except that we lack a
             * library function to escape them properly (bug 248062), and
             * domain names are famous for having '.' characters in them,
             * which would allow spurious and possibly hostile matches.
             */
            okHost.endsWith("." + HOST_NAME) ||
            /* case #2:
             * browser.garage.maemo.org uses an invalid security certificate.
             *
             * The certificate is only valid for garage.maemo.org
             */
            HOST_NAME.endsWith("." + okHost);

          const l10nArgs = { hostname, "alt-name": altName };
          if (showLink) {
            // Set the link if we want it.
            const proto = document.location.protocol + "//";
            addLabel("cert-error-domain-mismatch-single", l10nArgs, {
              href: proto + okHost,
              "data-l10n-name": "domain-mismatch-link",
              id: "cert_domain_link",
            });

            // If we set a link, meaning there's something helpful for
            // the user here, expand the section by default
            if (getCSSClass() != "expertBadCert") {
              revealAdvancedPanelSlowlyAsync();
            }
          } else {
            addLabel("cert-error-domain-mismatch-single-nolink", l10nArgs);
          }
        }
        addErrorCodeLink();
      });
      break;
  }

  getFailedCertificatesAsPEMString().then(pemString => {
    const errorText = document.getElementById("certificateErrorText");
    errorText.textContent = pemString;
  });
}

/* Only focus if we're the toplevel frame; otherwise we
   don't want to call attention to ourselves!
*/
function setFocus(selector, position = "afterbegin") {
  if (window.top == window) {
    var button = document.querySelector(selector);
    button.parentNode.insertAdjacentElement(position, button);
    // It's possible setFocus was called via the DOMContentLoaded event
    // handler and that the button has no frame. Things without a frame cannot
    // be focused. We use a requestAnimationFrame to queue up the focus to occur
    // once the button has its frame.
    requestAnimationFrame(() => {
      button.focus({ focusVisible: false });
    });
  }
}

for (let button of document.querySelectorAll(".try-again")) {
  button.addEventListener("click", function () {
    retryThis(this);
  });
}

initPage();

// Dispatch this event so tests can detect that we finished loading the error page.
document.dispatchEvent(new CustomEvent("AboutNetErrorLoad", { bubbles: true }));
PK
!<�_֊b*b*2chrome/toolkit/content/global/aboutNetworking.html<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html>

<html>
  <head>
    <meta charset="utf-8" />
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:; object-src 'none'"
    />
    <meta name="color-scheme" content="light dark" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title data-l10n-id="about-networking-title"></title>
    <link rel="stylesheet" href="chrome://global/skin/aboutNetworking.css" />
    <script src="chrome://global/content/aboutNetworking.js"></script>
    <link rel="localization" href="toolkit/about/aboutNetworking.ftl" />
  </head>
  <body id="body">
    <div id="categories">
      <div class="category category-no-icon" selected="true" id="category-http">
        <span class="category-name" data-l10n-id="about-networking-http"></span>
      </div>
      <div class="category category-no-icon" id="category-sockets">
        <span
          class="category-name"
          data-l10n-id="about-networking-sockets"
        ></span>
      </div>
      <div class="category category-no-icon" id="category-dns">
        <span class="category-name" data-l10n-id="about-networking-dns"></span>
      </div>
      <div class="category category-no-icon" id="category-websockets">
        <span
          class="category-name"
          data-l10n-id="about-networking-websockets"
        ></span>
      </div>
      <hr />
      <div class="category category-no-icon" id="category-dnslookuptool">
        <span
          class="category-name"
          data-l10n-id="about-networking-dns-lookup"
        ></span>
      </div>
      <div class="category category-no-icon" id="category-logging">
        <span
          class="category-name"
          data-l10n-id="about-networking-logging"
        ></span>
      </div>
      <div class="category category-no-icon" id="category-rcwn">
        <span class="category-name" data-l10n-id="about-networking-rcwn"></span>
      </div>
      <div class="category category-no-icon" id="category-networkid">
        <span
          class="category-name"
          data-l10n-id="about-networking-networkid"
        ></span>
      </div>
    </div>
    <div class="main-content">
      <div class="header">
        <h1
          id="sectionTitle"
          class="header-name"
          data-l10n-id="about-networking-http"
        ></h1>
        <div id="refreshDiv">
          <button
            id="refreshButton"
            data-l10n-id="about-networking-refresh"
          ></button>
          <label class="toggle-container-with-text">
            <input
              id="autorefcheck"
              type="checkbox"
              name="Autorefresh"
              role="checkbox"
            />
            <span data-l10n-id="about-networking-auto-refresh"></span>
          </label>
        </div>
      </div>

      <div id="http" class="tab active">
        <button
          id="clearHTTPCache"
          data-l10n-id="about-networking-http-clear-cache-button"
        ></button>
        <table>
          <thead>
            <tr>
              <th data-l10n-id="about-networking-hostname"></th>
              <th data-l10n-id="about-networking-port"></th>
              <th data-l10n-id="about-networking-http-version"></th>
              <th data-l10n-id="about-networking-ssl"></th>
              <th data-l10n-id="about-networking-active"></th>
              <th data-l10n-id="about-networking-idle"></th>
            </tr>
          </thead>
          <tbody id="http_content"></tbody>
        </table>
      </div>

      <div id="sockets" class="tab" hidden="true">
        <table>
          <thead>
            <tr>
              <th data-l10n-id="about-networking-host"></th>
              <th data-l10n-id="about-networking-port"></th>
              <th data-l10n-id="about-networking-type"></th>
              <th data-l10n-id="about-networking-active"></th>
              <th data-l10n-id="about-networking-sent"></th>
              <th data-l10n-id="about-networking-received"></th>
            </tr>
          </thead>
          <tbody id="sockets_content"></tbody>
        </table>
      </div>

      <div id="dns" class="tab" hidden="true">
        <table>
          <thead>
            <tr>
              <th data-l10n-id="about-networking-dns-suffix"></th>
            </tr>
          </thead>
          <tbody id="dns_suffix_content"></tbody>
        </table>
        <table>
          <thead>
            <tr>
              <th data-l10n-id="about-networking-dns-trr-url"></th>
              <th data-l10n-id="about-networking-dns-trr-mode"></th>
            </tr>
          </thead>
          <tbody id="dns_trr_url"></tbody>
        </table>
        <br /><br />
        <button
          id="clearDNSCache"
          data-l10n-id="about-networking-dns-clear-cache-button"
        ></button>
        <br /><br />
        <table>
          <thead>
            <tr>
              <th data-l10n-id="about-networking-hostname"></th>
              <th data-l10n-id="about-networking-family"></th>
              <th data-l10n-id="about-networking-trr"></th>
              <th data-l10n-id="about-networking-addresses"></th>
              <th data-l10n-id="about-networking-expires"></th>
              <th data-l10n-id="about-networking-originAttributesSuffix"></th>
              <th data-l10n-id="about-networking-flags"></th>
            </tr>
          </thead>
          <tbody id="dns_content"></tbody>
        </table>
      </div>

      <div id="websockets" class="tab" hidden="true">
        <table>
          <thead>
            <tr>
              <th data-l10n-id="about-networking-hostname"></th>
              <th data-l10n-id="about-networking-ssl"></th>
              <th data-l10n-id="about-networking-messages-sent"></th>
              <th data-l10n-id="about-networking-messages-received"></th>
              <th data-l10n-id="about-networking-bytes-sent"></th>
              <th data-l10n-id="about-networking-bytes-received"></th>
            </tr>
          </thead>
          <tbody id="websockets_content"></tbody>
        </table>
      </div>

      <div id="dnslookuptool" class="tab" hidden="true">
        <label data-l10n-id="about-networking-dns-domain"></label>
        <input type="text" name="host" id="host" />
        <button
          id="dnsLookupButton"
          data-l10n-id="about-networking-dns-lookup-button"
        ></button>
        <hr />
        <table>
          <thead>
            <tr>
              <th data-l10n-id="about-networking-dns-lookup-table-column"></th>
            </tr>
          </thead>
          <tbody id="dnslookuptool_content"></tbody>
        </table>
        <hr />
        <table>
          <thead>
            <tr>
              <th
                data-l10n-id="about-networking-dns-https-rr-lookup-table-column"
              ></th>
            </tr>
          </thead>
          <tbody id="https_rr_content"></tbody>
        </table>
      </div>

      <div id="rcwn" class="tab" hidden="true">
        <table>
          <thead>
            <tr>
              <th data-l10n-id="about-networking-rcwn-status"></th>
              <th data-l10n-id="about-networking-total-network-requests"></th>
              <th data-l10n-id="about-networking-rcwn-cache-won-count"></th>
              <th data-l10n-id="about-networking-rcwn-net-won-count"></th>
            </tr>
          </thead>
          <tbody id="rcwn_content">
            <tr>
              <td id="rcwn_status"></td>
              <td id="total_req_count"></td>
              <td id="rcwn_cache_won_count"></td>
              <td id="rcwn_cache_net_count"></td>
            </tr>
          </tbody>
        </table>

        <br /><br />

        <table>
          <thead>
            <tr>
              <th data-l10n-id="about-networking-rcwn-operation"></th>
              <th data-l10n-id="about-networking-rcwn-avg-short"></th>
              <th data-l10n-id="about-networking-rcwn-avg-long"></th>
              <th data-l10n-id="about-networking-rcwn-std-dev-long"></th>
            </tr>
          </thead>
          <tbody id="cacheperf_content">
            <tr>
              <td data-l10n-id="about-networking-rcwn-perf-open"></td>
              <td id="rcwn_perfstats_open_avgShort"></td>
              <td id="rcwn_perfstats_open_avgLong"></td>
              <td id="rcwn_perfstats_open_stddevLong"></td>
            </tr>
            <tr>
              <td data-l10n-id="about-networking-rcwn-perf-read"></td>
              <td id="rcwn_perfstats_read_avgShort"></td>
              <td id="rcwn_perfstats_read_avgLong"></td>
              <td id="rcwn_perfstats_read_stddevLong"></td>
            </tr>
            <tr>
              <td data-l10n-id="about-networking-rcwn-perf-write"></td>
              <td id="rcwn_perfstats_write_avgShort"></td>
              <td id="rcwn_perfstats_write_avgLong"></td>
              <td id="rcwn_perfstats_write_stddevLong"></td>
            </tr>
            <tr>
              <td data-l10n-id="about-networking-rcwn-perf-entry-open"></td>
              <td id="rcwn_perfstats_entryopen_avgShort"></td>
              <td id="rcwn_perfstats_entryopen_avgLong"></td>
              <td id="rcwn_perfstats_entryopen_stddevLong"></td>
            </tr>
          </tbody>
        </table>

        <br /><br />

        <table>
          <thead>
            <tr>
              <th data-l10n-id="about-networking-rcwn-cache-slow"></th>
              <th data-l10n-id="about-networking-rcwn-cache-not-slow"></th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td id="rcwn_cache_slow"></td>
              <td id="rcwn_cache_not_slow"></td>
            </tr>
          </tbody>
        </table>
      </div>

      <div id="logging" class="tab" hidden="true">
        <span data-l10n-id="about-networking-moved-about-logging">
          <a data-l10n-name="about-logging-url" href="about:logging"></a>
        </span>
      </div>

      <div id="networkid" class="tab" hidden="true">
        <table>
          <thead>
            <tr>
              <th data-l10n-id="about-networking-networkid-is-up"></th>
              <th data-l10n-id="about-networking-networkid-status-known"></th>
              <th data-l10n-id="about-networking-networkid-id"></th>
            </tr>
          </thead>
          <tbody id="networkid_content">
            <tr>
              <td id="networkid_isUp"></td>
              <td id="networkid_statusKnown"></td>
              <td id="networkid_id"></td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </body>
</html>
PK
!<!'HI5I50chrome/toolkit/content/global/aboutNetworking.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const FileUtils = ChromeUtils.importESModule(
  "resource://gre/modules/FileUtils.sys.mjs"
).FileUtils;
const gDashboard = Cc["@mozilla.org/network/dashboard;1"].getService(
  Ci.nsIDashboard
);
const gDirServ = Cc["@mozilla.org/file/directory_service;1"].getService(
  Ci.nsIDirectoryServiceProvider
);
const gNetLinkSvc =
  Cc["@mozilla.org/network/network-link-service;1"] &&
  Cc["@mozilla.org/network/network-link-service;1"].getService(
    Ci.nsINetworkLinkService
  );

const gRequestNetworkingData = {
  http: gDashboard.requestHttpConnections,
  sockets: gDashboard.requestSockets,
  dns: gDashboard.requestDNSInfo,
  websockets: gDashboard.requestWebsocketConnections,
  dnslookuptool: () => {},
  rcwn: gDashboard.requestRcwnStats,
  networkid: displayNetworkID,
};
const gDashboardCallbacks = {
  http: displayHttp,
  sockets: displaySockets,
  dns: displayDns,
  websockets: displayWebsockets,
  rcwn: displayRcwnStats,
};

const REFRESH_INTERVAL_MS = 3000;

function col(element) {
  let col = document.createElement("td");
  let content = document.createTextNode(element);
  col.appendChild(content);
  return col;
}

function displayHttp(data) {
  let cont = document.getElementById("http_content");
  let parent = cont.parentNode;
  let new_cont = document.createElement("tbody");
  new_cont.setAttribute("id", "http_content");

  for (let i = 0; i < data.connections.length; i++) {
    let row = document.createElement("tr");
    row.appendChild(col(data.connections[i].host));
    row.appendChild(col(data.connections[i].port));
    row.appendChild(col(data.connections[i].httpVersion));
    row.appendChild(col(data.connections[i].ssl));
    row.appendChild(col(data.connections[i].active.length));
    row.appendChild(col(data.connections[i].idle.length));
    new_cont.appendChild(row);
  }

  parent.replaceChild(new_cont, cont);
}

function displaySockets(data) {
  let cont = document.getElementById("sockets_content");
  let parent = cont.parentNode;
  let new_cont = document.createElement("tbody");
  new_cont.setAttribute("id", "sockets_content");

  for (let i = 0; i < data.sockets.length; i++) {
    let row = document.createElement("tr");
    row.appendChild(col(data.sockets[i].host));
    row.appendChild(col(data.sockets[i].port));
    row.appendChild(col(data.sockets[i].type));
    row.appendChild(col(data.sockets[i].active));
    row.appendChild(col(data.sockets[i].sent));
    row.appendChild(col(data.sockets[i].received));
    new_cont.appendChild(row);
  }

  parent.replaceChild(new_cont, cont);
}

function displayDns(data) {
  let suffixContent = document.getElementById("dns_suffix_content");
  let suffixParent = suffixContent.parentNode;
  let suffixes = [];
  try {
    suffixes = gNetLinkSvc.dnsSuffixList; // May throw
  } catch (e) {}
  let suffix_tbody = document.createElement("tbody");
  suffix_tbody.id = "dns_suffix_content";
  for (let suffix of suffixes) {
    let row = document.createElement("tr");
    row.appendChild(col(suffix));
    suffix_tbody.appendChild(row);
  }
  suffixParent.replaceChild(suffix_tbody, suffixContent);

  let trr_url_tbody = document.createElement("tbody");
  trr_url_tbody.id = "dns_trr_url";
  let trr_url = document.createElement("tr");
  trr_url.appendChild(col(Services.dns.currentTrrURI));
  trr_url.appendChild(col(Services.dns.currentTrrMode));
  trr_url_tbody.appendChild(trr_url);
  let prevURL = document.getElementById("dns_trr_url");
  prevURL.parentNode.replaceChild(trr_url_tbody, prevURL);

  let cont = document.getElementById("dns_content");
  let parent = cont.parentNode;
  let new_cont = document.createElement("tbody");
  new_cont.setAttribute("id", "dns_content");

  for (let i = 0; i < data.entries.length; i++) {
    let row = document.createElement("tr");
    row.appendChild(col(data.entries[i].hostname));
    row.appendChild(col(data.entries[i].family));
    row.appendChild(col(data.entries[i].trr));
    let column = document.createElement("td");

    for (let j = 0; j < data.entries[i].hostaddr.length; j++) {
      column.appendChild(document.createTextNode(data.entries[i].hostaddr[j]));
      column.appendChild(document.createElement("br"));
    }

    row.appendChild(column);
    row.appendChild(col(data.entries[i].expiration));
    row.appendChild(col(data.entries[i].originAttributesSuffix));
    row.appendChild(col(data.entries[i].flags));
    new_cont.appendChild(row);
  }

  parent.replaceChild(new_cont, cont);
}

function displayWebsockets(data) {
  let cont = document.getElementById("websockets_content");
  let parent = cont.parentNode;
  let new_cont = document.createElement("tbody");
  new_cont.setAttribute("id", "websockets_content");

  for (let i = 0; i < data.websockets.length; i++) {
    let row = document.createElement("tr");
    row.appendChild(col(data.websockets[i].hostport));
    row.appendChild(col(data.websockets[i].encrypted));
    row.appendChild(col(data.websockets[i].msgsent));
    row.appendChild(col(data.websockets[i].msgreceived));
    row.appendChild(col(data.websockets[i].sentsize));
    row.appendChild(col(data.websockets[i].receivedsize));
    new_cont.appendChild(row);
  }

  parent.replaceChild(new_cont, cont);
}

function displayRcwnStats(data) {
  let status = Services.prefs.getBoolPref("network.http.rcwn.enabled");
  let linkType = Ci.nsINetworkLinkService.LINK_TYPE_UNKNOWN;
  try {
    linkType = gNetLinkSvc.linkType;
  } catch (e) {}
  if (
    !(
      linkType == Ci.nsINetworkLinkService.LINK_TYPE_UNKNOWN ||
      linkType == Ci.nsINetworkLinkService.LINK_TYPE_ETHERNET ||
      linkType == Ci.nsINetworkLinkService.LINK_TYPE_USB ||
      linkType == Ci.nsINetworkLinkService.LINK_TYPE_WIFI
    )
  ) {
    status = false;
  }

  let cacheWon = data.rcwnCacheWonCount;
  let netWon = data.rcwnNetWonCount;
  let total = data.totalNetworkRequests;
  let cacheSlow = data.cacheSlowCount;
  let cacheNotSlow = data.cacheNotSlowCount;

  document.getElementById("rcwn_status").innerText = status;
  document.getElementById("total_req_count").innerText = total;
  document.getElementById("rcwn_cache_won_count").innerText = cacheWon;
  document.getElementById("rcwn_cache_net_count").innerText = netWon;
  document.getElementById("rcwn_cache_slow").innerText = cacheSlow;
  document.getElementById("rcwn_cache_not_slow").innerText = cacheNotSlow;

  // Keep in sync with CachePerfStats::EDataType in CacheFileUtils.h
  const perfStatTypes = ["open", "read", "write", "entryopen"];

  const perfStatFieldNames = ["avgShort", "avgLong", "stddevLong"];

  for (let typeIndex in perfStatTypes) {
    for (let statFieldIndex in perfStatFieldNames) {
      document.getElementById(
        "rcwn_perfstats_" +
          perfStatTypes[typeIndex] +
          "_" +
          perfStatFieldNames[statFieldIndex]
      ).innerText =
        data.perfStats[typeIndex][perfStatFieldNames[statFieldIndex]];
    }
  }
}

function displayNetworkID() {
  try {
    let linkIsUp = gNetLinkSvc.isLinkUp;
    let linkStatusKnown = gNetLinkSvc.linkStatusKnown;
    let networkID = gNetLinkSvc.networkID;

    document.getElementById("networkid_isUp").innerText = linkIsUp;
    document.getElementById("networkid_statusKnown").innerText =
      linkStatusKnown;
    document.getElementById("networkid_id").innerText = networkID;
  } catch (e) {
    document.getElementById("networkid_isUp").innerText = "<unknown>";
    document.getElementById("networkid_statusKnown").innerText = "<unknown>";
    document.getElementById("networkid_id").innerText = "<unknown>";
  }
}

function requestAllNetworkingData() {
  for (let id in gRequestNetworkingData) {
    requestNetworkingDataForTab(id);
  }
}

function requestNetworkingDataForTab(id) {
  gRequestNetworkingData[id](gDashboardCallbacks[id]);
}

let gInited = false;
function init() {
  if (gInited) {
    return;
  }
  gInited = true;

  requestAllNetworkingData();

  let autoRefresh = document.getElementById("autorefcheck");
  if (autoRefresh.checked) {
    setAutoRefreshInterval(autoRefresh);
  }

  autoRefresh.addEventListener("click", function () {
    let refrButton = document.getElementById("refreshButton");
    if (this.checked) {
      setAutoRefreshInterval(this);
      refrButton.disabled = "disabled";
    } else {
      clearInterval(this.interval);
      refrButton.disabled = null;
    }
  });

  let refr = document.getElementById("refreshButton");
  refr.addEventListener("click", requestAllNetworkingData);
  if (document.getElementById("autorefcheck").checked) {
    refr.disabled = "disabled";
  }

  // Event delegation on #categories element
  let menu = document.getElementById("categories");
  menu.addEventListener("click", function click(e) {
    if (e.target && e.target.parentNode == menu) {
      show(e.target);
    }
  });

  let clearHTTPCache = document.getElementById("clearHTTPCache");
  clearHTTPCache.addEventListener("click", async function () {
    Services.cache2.clear();
  });

  let dnsLookupButton = document.getElementById("dnsLookupButton");
  dnsLookupButton.addEventListener("click", function () {
    doLookup();
  });

  let clearDNSCache = document.getElementById("clearDNSCache");
  clearDNSCache.addEventListener("click", function () {
    Services.dns.clearCache(true);
  });

  if (location.hash) {
    let sectionButton = document.getElementById(
      "category-" + location.hash.substring(1)
    );
    if (sectionButton) {
      sectionButton.click();
    }
  }
}

function show(button) {
  let current_tab = document.querySelector(".active");
  let category = button.getAttribute("id").substring("category-".length);
  let content = document.getElementById(category);
  if (current_tab == content) {
    return;
  }
  current_tab.classList.remove("active");
  current_tab.hidden = true;
  content.classList.add("active");
  content.hidden = false;

  let current_button = document.querySelector("[selected=true]");
  current_button.removeAttribute("selected");
  button.setAttribute("selected", "true");

  let autoRefresh = document.getElementById("autorefcheck");
  if (autoRefresh.checked) {
    clearInterval(autoRefresh.interval);
    setAutoRefreshInterval(autoRefresh);
  }

  let title = document.getElementById("sectionTitle");
  title.textContent = button.children[0].textContent;
  location.hash = category;
}

function setAutoRefreshInterval(checkBox) {
  let active_tab = document.querySelector(".active");
  checkBox.interval = setInterval(function () {
    requestNetworkingDataForTab(active_tab.id);
  }, REFRESH_INTERVAL_MS);
}

// We use the pageshow event instead of onload. This is needed because sometimes
// the page is loaded via session-restore/bfcache. In such cases we need to call
// init() to keep the page behaviour consistent with the ticked checkboxes.
// Mostly the issue is with the autorefresh checkbox.
window.addEventListener("pageshow", function () {
  init();
});

function doLookup() {
  let host = document.getElementById("host").value;
  if (host) {
    try {
      gDashboard.requestDNSLookup(host, displayDNSLookup);
    } catch (e) {}
    try {
      gDashboard.requestDNSHTTPSRRLookup(host, displayHTTPSRRLookup);
    } catch (e) {}
  }
}

function displayDNSLookup(data) {
  let cont = document.getElementById("dnslookuptool_content");
  let parent = cont.parentNode;
  let new_cont = document.createElement("tbody");
  new_cont.setAttribute("id", "dnslookuptool_content");

  if (data.answer) {
    for (let address of data.address) {
      let row = document.createElement("tr");
      row.appendChild(col(address));
      new_cont.appendChild(row);
    }
  } else {
    new_cont.appendChild(col(data.error));
  }

  parent.replaceChild(new_cont, cont);
}

function displayHTTPSRRLookup(data) {
  let cont = document.getElementById("https_rr_content");
  let parent = cont.parentNode;
  let new_cont = document.createElement("tbody");
  new_cont.setAttribute("id", "https_rr_content");

  if (data.answer) {
    for (let record of data.records) {
      let row = document.createElement("tr");
      let alpn = record.alpn ? `alpn="${record.alpn.alpn}" ` : "";
      let noDefaultAlpn = record.noDefaultAlpn ? "noDefaultAlpn " : "";
      let port = record.port ? `port="${record.port.port}" ` : "";
      let echConfig = record.echConfig
        ? `echConfig="${record.echConfig.echConfig}" `
        : "";
      let ODoHConfig = record.ODoHConfig
        ? `odoh="${record.ODoHConfig.ODoHConfig}" `
        : "";
      let ipv4hint = "";
      let ipv6hint = "";
      if (record.ipv4Hint) {
        let ipv4Str = "";
        for (let addr of record.ipv4Hint.address) {
          ipv4Str += `${addr}, `;
        }
        // Remove ", " at the end.
        ipv4Str = ipv4Str.slice(0, -2);
        ipv4hint = `ipv4hint="${ipv4Str}" `;
      }
      if (record.ipv6Hint) {
        let ipv6Str = "";
        for (let addr of record.ipv6Hint.address) {
          ipv6Str += `${addr}, `;
        }
        // Remove ", " at the end.
        ipv6Str = ipv6Str.slice(0, -2);
        ipv6hint = `ipv6hint="${ipv6Str}" `;
      }

      let str = `${record.priority} ${record.targetName} `;
      str += `(${alpn}${noDefaultAlpn}${port}`;
      str += `${ipv4hint}${echConfig}${ipv6hint}`;
      str += `${ODoHConfig})`;
      row.appendChild(col(str));
      new_cont.appendChild(row);
    }
  } else {
    new_cont.appendChild(col(data.error));
  }

  parent.replaceChild(new_cont, cont);
}
PK
!<᳞�WW0chrome/toolkit/content/global/aboutProcesses.css/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/in-content/common.css");

html {
  background-color: var(--in-content-page-background);
}
body {
  overflow-x: hidden;
}

#process-table {
  user-select: none;
  font-size: 1em;
  border-spacing: 0;
  background-color: var(--in-content-box-background);
  margin: 0;
  position: absolute;
  top: 0;
  inset-inline-start: 0;
  width: 100%;
  height: 100%;
}

/* Avoid scrolling the header */
#process-tbody {
  display: block;
  margin-top: 2em;
}
#process-thead {
  position: fixed;
  z-index: 1;
  height: 2em;
  border-bottom: 1px solid var(--in-content-border-color);
  width: 100%;
  background-color: var(--in-content-box-background);
}
tr {
  display: grid;
  /* Flexible width for the name column, 15% width for the memory and CPU column,
   * then fixed width (16px icon + 2*10px margin) for the actions column. */
  grid-template-columns: 1fr 15% 15% 36px;
  grid-auto-rows: 2em;
  width: 100%;
}

.cpu, .memory {
  text-align: end;
}

#process-thead > tr {
  height: inherit;
}

th {
  font-weight: normal;
  text-align: start;
  background-color: var(--in-content-button-background);
}
th:not(:first-child) {
  border-inline-start: 1px solid;
  border-image: linear-gradient(transparent 0%, transparent 20%, var(--in-content-box-border-color) 20%, var(--in-content-box-border-color) 80%, transparent 80%, transparent 100%) 1 1;
}
th, td {
  padding: 5px 10px;
  min-height: 16px;
  max-height: 2em;
  overflow: hidden;
  white-space: nowrap;
}
td.type, td.favicon {
  background-repeat: no-repeat;
  background-origin: border-box;
  background-size: 16px 16px;
  background-position: 11px center;
  padding-inline-start: 38px;
  -moz-context-properties: fill;
  fill: currentColor;
}
td.type:dir(rtl), td.favicon:dir(rtl) {
  background-position-x: right 11px;
}
td:first-child {
  text-overflow: ellipsis;
}

.profiler-icon {
  background: url("chrome://devtools/skin/images/tool-profiler.svg") no-repeat center;
  display: inline-block;
  height: 100%;
  width: 16px;
  padding: 5px 7px;
  /* The -10px is -5 to undo the td padding and -5 to undo the padding here. */
  margin: -10px 3px;
  -moz-context-properties: fill, fill-opacity;
  fill-opacity: 0.9;
  fill: currentColor;
}
.profiler-icon:hover {
  background-color: var(--in-content-button-background-hover);
  color: var(--in-content-button-text-color-hover);
}

.profiler-icon:hover:active {
  background-color: var(--in-content-button-background-active);
  color: var(--in-content-button-text-color-active);
  fill-opacity: 1;
}

.profiler-icon:not(.profiler-active) {
  transform: scaleX(-1);
}

.profiler-active {
  fill: var(--in-content-accent-color);
}
td:not(:hover) > .profiler-icon:not(.profiler-active) {
  display: none;
}

.twisty {
  position: relative;
}
/* Putting the background image in a positioned pseudo element lets us
* use CSS transforms on the background image, which we need for rtl. */
.twisty::before {
  content: url("chrome://global/skin/icons/arrow-right-12.svg");
  position: absolute;
  display: block;
  line-height: 50%;
  top: 4px; /* Half the image's height */
  inset-inline-start: -16px;
  width: 12px;
  -moz-context-properties: fill;
  fill: currentColor;
}
.twisty:dir(rtl)::before {
  content: url("chrome://global/skin/icons/arrow-left-12.svg");
}
.twisty.open::before {
  content: url("chrome://global/skin/icons/arrow-down-12.svg");
}
.twisty:-moz-focusring {
  outline: none;
}
.twisty:-moz-focusring::before {
  outline: var(--in-content-focus-outline);
}
.indent {
  padding-inline: 48px 0;
}
.double_indent {
  padding-inline: 58px 0;
}

tr[selected] > td {
  background-color: var(--in-content-item-selected);
  color: var(--in-content-item-selected-text);
}
#process-tbody > tr:hover {
  background-color: var(--in-content-item-hover);
  color: var(--in-content-item-hover-text);
}

/* Tab names and thread summary text can extend into memory and CPU columns. */
.window > :first-child,
.thread-summary > :first-child {
  grid-column: 1 / 4;
}

/* Thread names can extend into the memory column. */
.thread > :first-child {
  grid-column: 1 / 3;
}

.clickable {
  background-repeat: no-repeat;
  background-position: right 4px center;
}
.clickable:dir(rtl) {
  background-position-x: left 4px;
}

.arrow-up,
.arrow-down {
  -moz-context-properties: fill;
  fill: currentColor;
}

.arrow-up {
  background-image: url("chrome://global/skin/icons/arrow-up-12.svg");
}
.arrow-down {
  background-image: url("chrome://global/skin/icons/arrow-down-12.svg");
}

th.clickable:hover {
  background-color: var(--in-content-button-background-hover);
  color: var(--in-content-button-text-color-hover);
}
th.clickable:hover:active {
  background-color: var(--in-content-button-background-active);
  color: var(--in-content-button-text-color-active);
}

tr.process > td.type {
  font-weight: bold;
  user-select: text;
}
tr.thread {
  font-size-adjust: 0.5;
}

.killing {
  opacity: 0.3;
  transition-property: opacity;
  transition-duration: 1s;
}

.killed {
  opacity: 0.3;
}

/* icons */
.close-icon {
  background: url("chrome://global/skin/icons/close.svg") no-repeat center;
  opacity: 0;      /* Start out as transparent */
  -moz-context-properties: fill;
  fill: currentColor;
}

tr:is([selected], :hover):not(.killing) > .close-icon {
  opacity: 1;
}

.close-icon:hover {
  background-color: var(--in-content-button-background-hover);
  color: var(--in-content-button-text-color-hover);
}

.close-icon:hover:active {
  background-color: var(--in-content-button-background-active);
  color: var(--in-content-button-text-color-active);
}

/* column-name */

/* When the process is reported as frozen, we display an hourglass before its name. */
.process.hung > :first-child > :not(.twisty)::before {
  content: "⌛️";
}

/*
  Show a separation between process groups.
 */

tr.separate-from-previous-process-group {
  border-top: dotted 1px var(--in-content-box-border-color);
  margin-top: -1px;
}

/* Graphical view of CPU use. */
.cpu {
  --bar-width: 0;
  background: linear-gradient(to left, var(--blue-40) calc(var(--bar-width) * 1%), transparent calc(var(--bar-width) * 1%));
}
.cpu:dir(rtl) {
  background: linear-gradient(to right, var(--blue-40) calc(var(--bar-width) * 1%), transparent calc(var(--bar-width) * 1%));
}
PK
!<[��R1chrome/toolkit/content/global/aboutProcesses.html<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE html>
<html>
  <head>
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:;img-src data:; object-src 'none'"
    />
    <meta name="color-scheme" content="light dark" />
    <title data-l10n-id="about-processes-title"></title>
    <link
      rel="icon"
      id="favicon"
      href="chrome://global/skin/icons/performance.svg"
    />
    <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
    <link rel="localization" href="toolkit/about/aboutProcesses.ftl" />
    <link rel="localization" href="branding/brand.ftl" />
    <script src="chrome://global/content/aboutProcesses.js"></script>
    <link rel="stylesheet" href="chrome://global/content/aboutProcesses.css" />
  </head>
  <body>
    <table id="process-table">
      <thead id="process-thead">
        <tr>
          <th
            class="clickable"
            id="column-name"
            data-l10n-id="about-processes-column-name"
          ></th>
          <th
            class="clickable"
            id="column-memory-resident"
            data-l10n-id="about-processes-column-memory-resident"
          ></th>
          <!-- Memory usage. -->
          <th
            class="clickable"
            id="column-cpu-total"
            data-l10n-id="about-processes-column-cpu-total"
          ></th>
          <!--CPU time-->
          <th id="column-kill" data-l10n-id="about-processes-column-action">
            ⚙
          </th>
          <!-- Kill button. -->
        </tr>
      </thead>
      <tbody id="process-tbody"></tbody>
    </table>
  </body>
</html>
PK
!<H�c���/chrome/toolkit/content/global/aboutProcesses.js/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-*/
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// Time in ms before we start changing the sort order again after receiving a
// mousemove event.
const TIME_BEFORE_SORTING_AGAIN = 5000;

// How long we should wait between samples.
const MINIMUM_INTERVAL_BETWEEN_SAMPLES_MS = 1000;

// How often we should update
const UPDATE_INTERVAL_MS = 2000;

const NS_PER_US = 1000;
const NS_PER_MS = 1000 * 1000;
const NS_PER_S = 1000 * 1000 * 1000;
const NS_PER_MIN = NS_PER_S * 60;
const NS_PER_HOUR = NS_PER_MIN * 60;
const NS_PER_DAY = NS_PER_HOUR * 24;

const ONE_GIGA = 1024 * 1024 * 1024;
const ONE_MEGA = 1024 * 1024;
const ONE_KILO = 1024;

const { XPCOMUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/XPCOMUtils.sys.mjs"
);
const { AppConstants } = ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);

ChromeUtils.defineESModuleGetters(this, {
  ContextualIdentityService:
    "resource://gre/modules/ContextualIdentityService.sys.mjs",
});

ChromeUtils.defineLazyGetter(this, "ProfilerPopupBackground", function () {
  return ChromeUtils.importESModule(
    "resource://devtools/client/performance-new/shared/background.sys.mjs"
  );
});

const { WebExtensionPolicy } = Cu.getGlobalForObject(Services);

const SHOW_THREADS = Services.prefs.getBoolPref(
  "toolkit.aboutProcesses.showThreads"
);
const SHOW_ALL_SUBFRAMES = Services.prefs.getBoolPref(
  "toolkit.aboutProcesses.showAllSubframes"
);
const SHOW_PROFILER_ICONS = Services.prefs.getBoolPref(
  "toolkit.aboutProcesses.showProfilerIcons"
);
const PROFILE_DURATION = Math.max(
  1,
  Services.prefs.getIntPref("toolkit.aboutProcesses.profileDuration")
);

/**
 * For the time being, Fluent doesn't support duration or memory formats, so we need
 * to fetch units from Fluent. To avoid re-fetching at each update, we prefetch these
 * units during initialization, asynchronously, and keep them.
 *
 * @type {
 *   duration: { ns: String, us: String, ms: String, s: String, m: String, h: String, d: String },
 *   memory: { B: String, KB: String, MB: String, GB: String, TB: String, PB: String, EB: String }
 * }.
 */
let gLocalizedUnits;

let tabFinder = {
  update() {
    this._map = new Map();
    for (let win of Services.wm.getEnumerator("navigator:browser")) {
      let tabbrowser = win.gBrowser;
      for (let browser of tabbrowser.browsers) {
        let id = browser.outerWindowID; // May be `null` if the browser isn't loaded yet
        if (id != null) {
          this._map.set(id, browser);
        }
      }
      if (tabbrowser.preloadedBrowser) {
        let browser = tabbrowser.preloadedBrowser;
        if (browser.outerWindowID) {
          this._map.set(browser.outerWindowID, browser);
        }
      }
    }
  },

  /**
   * Find the <xul:tab> for a window id.
   *
   * This is useful e.g. for reloading or closing tabs.
   *
   * @return null If the xul:tab could not be found, e.g. if the
   * windowId is that of a chrome window.
   * @return {{tabbrowser: <xul:tabbrowser>, tab: <xul.tab>}} The
   * tabbrowser and tab if the latter could be found.
   */
  get(id) {
    let browser = this._map.get(id);
    if (!browser) {
      return null;
    }
    let tabbrowser = browser.getTabBrowser();
    if (!tabbrowser) {
      return {
        tabbrowser: null,
        tab: {
          getAttribute() {
            return "";
          },
          linkedBrowser: browser,
        },
      };
    }
    return { tabbrowser, tab: tabbrowser.getTabForBrowser(browser) };
  },
};

/**
 * Utilities for dealing with state
 */
var State = {
  // Store the previous and current samples so they can be compared.
  _previous: null,
  _latest: null,

  async _promiseSnapshot() {
    let date = Cu.now();
    let main = await ChromeUtils.requestProcInfo();
    main.date = date;

    let processes = new Map();
    processes.set(main.pid, main);
    for (let child of main.children) {
      child.date = date;
      processes.set(child.pid, child);
    }

    return { processes, date };
  },

  /**
   * Update the internal state.
   *
   * @return {Promise}
   */
  async update(force = false) {
    if (
      force ||
      !this._latest ||
      Cu.now() - this._latest.date > MINIMUM_INTERVAL_BETWEEN_SAMPLES_MS
    ) {
      // Replacing this._previous before we are done awaiting
      // this._promiseSnapshot can cause this._previous and this._latest to be
      // equal for a short amount of time, which can cause test failures when
      // a forced update of the display is triggered in the meantime.
      let newSnapshot = await this._promiseSnapshot();
      this._previous = this._latest;
      this._latest = newSnapshot;
    }
  },

  _getThreadDelta(cur, prev, deltaT) {
    let result = {
      tid: cur.tid,
      name: cur.name || `(${cur.tid})`,
      // Total amount of CPU used, in ns.
      totalCpu: cur.cpuTime,
      slopeCpu: null,
      active: null,
    };
    if (!deltaT) {
      return result;
    }
    result.slopeCpu = (result.totalCpu - (prev ? prev.cpuTime : 0)) / deltaT;
    result.active =
      !!result.slopeCpu || cur.cpuCycleCount > (prev ? prev.cpuCycleCount : 0);
    return result;
  },

  _getDOMWindows(process) {
    if (!process.windows) {
      return [];
    }
    if (!process.type == "extensions") {
      return [];
    }
    let windows = process.windows.map(win => {
      let tab = tabFinder.get(win.outerWindowId);
      let addon =
        process.type == "extension"
          ? WebExtensionPolicy.getByURI(win.documentURI)
          : null;
      let displayRank;
      if (tab) {
        displayRank = 1;
      } else if (win.isProcessRoot) {
        displayRank = 2;
      } else if (win.documentTitle) {
        displayRank = 3;
      } else {
        displayRank = 4;
      }
      return {
        outerWindowId: win.outerWindowId,
        documentURI: win.documentURI,
        documentTitle: win.documentTitle,
        isProcessRoot: win.isProcessRoot,
        isInProcess: win.isInProcess,
        tab,
        addon,
        // The number of instances we have collapsed.
        count: 1,
        // A rank used to quickly sort windows.
        displayRank,
      };
    });

    // We keep all tabs and addons but we collapse subframes that have the same host.

    // A map from host -> subframe.
    let collapsible = new Map();
    let result = [];
    for (let win of windows) {
      if (win.tab || win.addon) {
        result.push(win);
        continue;
      }
      let prev = collapsible.get(win.documentURI.prePath);
      if (prev) {
        prev.count += 1;
      } else {
        collapsible.set(win.documentURI.prePath, win);
        result.push(win);
      }
    }
    return result;
  },

  /**
   * Compute the delta between two process snapshots.
   *
   * @param {ProcessSnapshot} cur
   * @param {ProcessSnapshot?} prev
   */
  _getProcessDelta(cur, prev) {
    let windows = this._getDOMWindows(cur);
    let result = {
      pid: cur.pid,
      childID: cur.childID,
      totalRamSize: cur.memory,
      deltaRamSize: null,
      totalCpu: cur.cpuTime,
      slopeCpu: null,
      active: null,
      type: cur.type,
      origin: cur.origin || "",
      threads: null,
      displayRank: Control._getDisplayGroupRank(cur, windows),
      windows,
      utilityActors: cur.utilityActors,
      // If this process has an unambiguous title, store it here.
      title: null,
    };
    // Attempt to determine a title for this process.
    let titles = [
      ...new Set(
        result.windows
          .filter(win => win.documentTitle)
          .map(win => win.documentTitle)
      ),
    ];
    if (titles.length == 1) {
      result.title = titles[0];
    }
    if (!prev) {
      if (SHOW_THREADS) {
        result.threads = cur.threads.map(data => this._getThreadDelta(data));
      }
      return result;
    }
    if (prev.pid != cur.pid) {
      throw new Error("Assertion failed: A process cannot change pid.");
    }
    let deltaT = (cur.date - prev.date) * NS_PER_MS;
    let threads = null;
    if (SHOW_THREADS) {
      let prevThreads = new Map();
      for (let thread of prev.threads) {
        prevThreads.set(thread.tid, thread);
      }
      threads = cur.threads.map(curThread =>
        this._getThreadDelta(curThread, prevThreads.get(curThread.tid), deltaT)
      );
    }
    result.deltaRamSize = cur.memory - prev.memory;
    result.slopeCpu = (cur.cpuTime - prev.cpuTime) / deltaT;
    result.active = !!result.slopeCpu || cur.cpuCycleCount > prev.cpuCycleCount;
    result.threads = threads;
    return result;
  },

  getCounters() {
    tabFinder.update();

    let counters = [];

    for (let cur of this._latest.processes.values()) {
      let prev = this._previous?.processes.get(cur.pid);
      counters.push(this._getProcessDelta(cur, prev));
    }

    return counters;
  },
};

var View = {
  // Processes, tabs and subframes that we killed during the previous iteration.
  // Array<{pid:Number} | {windowId:Number}>
  _killedRecently: [],
  commit() {
    this._killedRecently.length = 0;
    let tbody = document.getElementById("process-tbody");

    let insertPoint = tbody.firstChild;
    let nextRow;
    while ((nextRow = this._orderedRows.shift())) {
      if (insertPoint && insertPoint === nextRow) {
        insertPoint = insertPoint.nextSibling;
      } else {
        tbody.insertBefore(nextRow, insertPoint);
      }
    }

    if (insertPoint) {
      while ((nextRow = insertPoint.nextSibling)) {
        this._removeRow(nextRow);
      }
      this._removeRow(insertPoint);
    }
  },
  // If we are not going to display the updated list of rows, drop references
  // to rows that haven't been inserted in the DOM tree.
  discardUpdate() {
    for (let row of this._orderedRows) {
      if (!row.parentNode) {
        this._rowsById.delete(row.rowId);
      }
    }
    this._orderedRows = [];
  },
  insertAfterRow(row) {
    let tbody = row.parentNode;
    let nextRow;
    while ((nextRow = this._orderedRows.pop())) {
      tbody.insertBefore(nextRow, row.nextSibling);
    }
  },

  _rowsById: new Map(),
  _removeRow(row) {
    this._rowsById.delete(row.rowId);

    row.remove();
  },
  _getOrCreateRow(rowId, cellCount) {
    let row = this._rowsById.get(rowId);
    if (!row) {
      row = document.createElement("tr");
      while (cellCount--) {
        row.appendChild(document.createElement("td"));
      }
      row.rowId = rowId;
      this._rowsById.set(rowId, row);
    }
    this._orderedRows.push(row);
    return row;
  },

  displayCpu(data, cpuCell, maxSlopeCpu) {
    // Put a value < 0% when we really don't want to see a bar as
    // otherwise it sometimes appears due to rounding errors when we
    // don't have an integer number of pixels.
    let barWidth = -0.5;
    if (data.slopeCpu == null) {
      this._fillCell(cpuCell, {
        fluentName: "about-processes-cpu-user-and-kernel-not-ready",
        classes: ["cpu"],
      });
    } else {
      let { duration, unit } = this._getDuration(data.totalCpu);
      if (data.totalCpu == 0) {
        // A thread having used exactly 0ns of CPU time is not possible.
        // When we get 0 it means the thread used less than the precision of
        // the measurement, and it makes more sense to show '0ms' than '0ns'.
        // This is useful on Linux where the minimum non-zero CPU time value
        // for threads of child processes is 10ms, and on Windows ARM64 where
        // the minimum non-zero value is 16ms.
        unit = "ms";
      }
      let localizedUnit = gLocalizedUnits.duration[unit];
      if (data.slopeCpu == 0) {
        let fluentName = data.active
          ? "about-processes-cpu-almost-idle"
          : "about-processes-cpu-fully-idle";
        this._fillCell(cpuCell, {
          fluentName,
          fluentArgs: {
            total: duration,
            unit: localizedUnit,
          },
          classes: ["cpu"],
        });
      } else {
        this._fillCell(cpuCell, {
          fluentName: "about-processes-cpu",
          fluentArgs: {
            percent: data.slopeCpu,
            total: duration,
            unit: localizedUnit,
          },
          classes: ["cpu"],
        });

        let cpuPercent = data.slopeCpu * 100;
        if (maxSlopeCpu > 1) {
          cpuPercent /= maxSlopeCpu;
        }
        // Ensure we always have a visible bar for non-0 values.
        barWidth = Math.max(0.5, cpuPercent);
      }
    }
    cpuCell.style.setProperty("--bar-width", barWidth);
  },

  /**
   * Display a row showing a single process (without its threads).
   *
   * @param {ProcessDelta} data The data to display.
   * @param {Number} maxSlopeCpu The largest slopeCpu value.
   * @return {DOMElement} The row displaying the process.
   */
  displayProcessRow(data, maxSlopeCpu) {
    const cellCount = 4;
    let rowId = "p:" + data.pid;
    let row = this._getOrCreateRow(rowId, cellCount);
    row.process = data;
    {
      let classNames = "process";
      if (data.isHung) {
        classNames += " hung";
      }
      row.className = classNames;
    }

    // Column: Name
    let nameCell = row.firstChild;
    {
      let classNames = [];
      let fluentName;
      let fluentArgs = {
        pid: "" + data.pid, // Make sure that this number is not localized
      };
      switch (data.type) {
        case "web":
          fluentName = "about-processes-web-process";
          break;
        case "webIsolated":
          fluentName = "about-processes-web-isolated-process";
          fluentArgs.origin = data.origin;
          break;
        case "webServiceWorker":
          fluentName = "about-processes-web-serviceworker";
          fluentArgs.origin = data.origin;
          break;
        case "file":
          fluentName = "about-processes-file-process";
          break;
        case "extension":
          fluentName = "about-processes-extension-process";
          classNames = ["extensions"];
          break;
        case "privilegedabout":
          fluentName = "about-processes-privilegedabout-process";
          break;
        case "privilegedmozilla":
          fluentName = "about-processes-privilegedmozilla-process";
          break;
        case "withCoopCoep":
          fluentName = "about-processes-with-coop-coep-process";
          fluentArgs.origin = data.origin;
          break;
        case "browser":
          fluentName = "about-processes-browser-process";
          break;
        case "plugin":
          fluentName = "about-processes-plugin-process";
          break;
        case "gmpPlugin":
          fluentName = "about-processes-gmp-plugin-process";
          break;
        case "gpu":
          fluentName = "about-processes-gpu-process";
          break;
        case "vr":
          fluentName = "about-processes-vr-process";
          break;
        case "rdd":
          fluentName = "about-processes-rdd-process";
          break;
        case "socket":
          fluentName = "about-processes-socket-process";
          break;
        case "remoteSandboxBroker":
          fluentName = "about-processes-remote-sandbox-broker-process";
          break;
        case "forkServer":
          fluentName = "about-processes-fork-server-process";
          break;
        case "preallocated":
          fluentName = "about-processes-preallocated-process";
          break;
        case "utility":
          fluentName = "about-processes-utility-process";
          break;
        case "inference":
          fluentName = "about-processes-inference-process";
          break;
        // The following are probably not going to show up for users
        // but let's handle the case anyway to avoid heisenoranges
        // during tests in case of a leftover process from a previous
        // test.
        default:
          fluentName = "about-processes-unknown-process";
          fluentArgs.type = data.type;
          break;
      }

      // Show container names instead of raw origin attribute suffixes.
      if (fluentArgs.origin?.includes("^")) {
        let origin = fluentArgs.origin;
        let privateBrowsingId, userContextId;
        try {
          ({ privateBrowsingId, userContextId } =
            ChromeUtils.createOriginAttributesFromOrigin(origin));
          fluentArgs.origin = origin.slice(0, origin.indexOf("^"));
        } catch (e) {
          // createOriginAttributesFromOrigin can throw NS_ERROR_FAILURE for incorrect origin strings.
        }
        if (userContextId) {
          let identityLabel =
            ContextualIdentityService.getUserContextLabel(userContextId);
          if (identityLabel) {
            fluentArgs.origin += ` — ${identityLabel}`;
          }
        }
        if (privateBrowsingId) {
          fluentName += "-private";
        }
      }

      let processNameElement = nameCell;
      if (SHOW_PROFILER_ICONS) {
        if (!nameCell.firstChild) {
          processNameElement = document.createElement("span");
          nameCell.appendChild(processNameElement);

          let profilerIcon = document.createElement("span");
          profilerIcon.className = "profiler-icon";
          document.l10n.setAttributes(
            profilerIcon,
            "about-processes-profile-process",
            { duration: PROFILE_DURATION }
          );
          nameCell.appendChild(profilerIcon);
        } else {
          processNameElement = nameCell.firstChild;
        }
      }
      document.l10n.setAttributes(processNameElement, fluentName, fluentArgs);
      nameCell.className = ["type", "favicon", ...classNames].join(" ");
      nameCell.setAttribute("id", data.pid + "-label");

      let image;
      switch (data.type) {
        case "browser":
        case "privilegedabout":
          image = "chrome://branding/content/icon32.png";
          break;
        case "extension":
          image = "chrome://mozapps/skin/extensions/extension.svg";
          break;
        default:
          // If all favicons match, pick the shared favicon.
          // Otherwise, pick a default icon.
          // If some tabs have no favicon, we ignore them.
          for (let win of data.windows || []) {
            if (!win.tab) {
              continue;
            }
            let favicon = win.tab.tab.getAttribute("image");
            if (!favicon) {
              // No favicon here, let's ignore the tab.
            } else if (!image) {
              // Let's pick a first favicon.
              // We'll remove it later if we find conflicting favicons.
              image = favicon;
            } else if (image == favicon) {
              // So far, no conflict, keep the favicon.
            } else {
              // Conflicting favicons, fallback to default.
              image = null;
              break;
            }
          }
          if (!image) {
            image = "chrome://global/skin/icons/link.svg";
          }
      }
      nameCell.style.backgroundImage = `url('${image}')`;
    }

    // Column: Memory
    let memoryCell = nameCell.nextSibling;
    {
      let formattedTotal = this._formatMemory(data.totalRamSize);
      if (data.deltaRamSize) {
        let formattedDelta = this._formatMemory(data.deltaRamSize);
        this._fillCell(memoryCell, {
          fluentName: "about-processes-total-memory-size-changed",
          fluentArgs: {
            total: formattedTotal.amount,
            totalUnit: gLocalizedUnits.memory[formattedTotal.unit],
            delta: Math.abs(formattedDelta.amount),
            deltaUnit: gLocalizedUnits.memory[formattedDelta.unit],
            deltaSign: data.deltaRamSize > 0 ? "+" : "-",
          },
          classes: ["memory"],
        });
      } else {
        this._fillCell(memoryCell, {
          fluentName: "about-processes-total-memory-size-no-change",
          fluentArgs: {
            total: formattedTotal.amount,
            totalUnit: gLocalizedUnits.memory[formattedTotal.unit],
          },
          classes: ["memory"],
        });
      }
    }

    // Column: CPU
    let cpuCell = memoryCell.nextSibling;
    this.displayCpu(data, cpuCell, maxSlopeCpu);

    // Column: Kill button – but not for all processes.
    let killButton = cpuCell.nextSibling;
    killButton.className = "action-icon";

    if (data.type != "browser") {
      // This type of process can be killed.
      if (this._killedRecently.some(kill => kill.pid && kill.pid == data.pid)) {
        // We're racing between the "kill" action and the visual refresh.
        // In a few cases, we could end up with the visual refresh showing
        // a process as un-killed while we actually just killed it.
        //
        // We still want to display the process in case something actually
        // went bad and the user needs the information to realize this.
        // But we also want to make it visible that the process is being
        // killed.
        row.classList.add("killed");
      } else {
        // Otherwise, let's display the kill button.
        killButton.classList.add("close-icon");
        let killButtonLabelId = data.type.startsWith("web")
          ? "about-processes-shutdown-process"
          : "about-processes-kill-process";
        document.l10n.setAttributes(killButton, killButtonLabelId);
      }
    }

    return row;
  },

  /**
   * Display a thread summary row with the thread count and a twisty to
   * open/close the list.
   *
   * @param {ProcessDelta} data The data to display.
   * @return {boolean} Whether the full thread list should be displayed.
   */
  displayThreadSummaryRow(data) {
    const cellCount = 2;
    let rowId = "ts:" + data.pid;
    let row = this._getOrCreateRow(rowId, cellCount);
    row.process = data;
    row.className = "thread-summary";
    let isOpen = false;

    // Column: Name
    let nameCell = row.firstChild;
    let threads = data.threads;
    let activeThreads = new Map();
    let activeThreadCount = 0;
    for (let t of data.threads) {
      if (!t.active) {
        continue;
      }
      ++activeThreadCount;
      let name = t.name.replace(/ ?#[0-9]+$/, "");
      if (!activeThreads.has(name)) {
        activeThreads.set(name, { name, slopeCpu: t.slopeCpu, count: 1 });
      } else {
        let thread = activeThreads.get(name);
        thread.count++;
        thread.slopeCpu += t.slopeCpu;
      }
    }
    let fluentName, fluentArgs;
    if (activeThreadCount) {
      let percentFormatter = new Intl.NumberFormat(undefined, {
        style: "percent",
        minimumSignificantDigits: 1,
      });

      let threadList = Array.from(activeThreads.values());
      threadList.sort((t1, t2) => t2.slopeCpu - t1.slopeCpu);

      fluentName = "about-processes-active-threads";
      fluentArgs = {
        number: threads.length,
        active: activeThreadCount,
        list: new Intl.ListFormat(undefined, { style: "narrow" }).format(
          threadList.map(t => {
            let name = t.count > 1 ? `${t.count} × ${t.name}` : t.name;
            let percent = Math.round(t.slopeCpu * 1000) / 1000;
            if (percent) {
              return `${name} ${percentFormatter.format(percent)}`;
            }
            return name;
          })
        ),
      };
    } else {
      fluentName = "about-processes-inactive-threads";
      fluentArgs = {
        number: threads.length,
      };
    }

    let span;
    if (!nameCell.firstChild) {
      nameCell.className = "name indent";
      // Create the nodes:
      let imgBtn = document.createElement("span");
      // Provide markup for an accessible disclosure button:
      imgBtn.className = "twisty";
      imgBtn.setAttribute("role", "button");
      imgBtn.setAttribute("tabindex", "0");
      // Label to include both summary and details texts
      imgBtn.setAttribute("aria-labelledby", `${data.pid}-label ${rowId}`);
      if (!imgBtn.hasAttribute("aria-expanded")) {
        imgBtn.setAttribute("aria-expanded", "false");
      }
      nameCell.appendChild(imgBtn);

      span = document.createElement("span");
      span.setAttribute("id", rowId);
      nameCell.appendChild(span);
    } else {
      // The only thing that can change is the thread count.
      let imgBtn = nameCell.firstChild;
      isOpen = imgBtn.classList.contains("open");
      span = imgBtn.nextSibling;
    }
    document.l10n.setAttributes(span, fluentName, fluentArgs);

    // Column: action
    let actionCell = nameCell.nextSibling;
    actionCell.className = "action-icon";

    return isOpen;
  },

  displayDOMWindowRow(data) {
    const cellCount = 2;
    let rowId = "w:" + data.outerWindowId;
    let row = this._getOrCreateRow(rowId, cellCount);
    row.win = data;
    row.className = "window";

    // Column: name
    let nameCell = row.firstChild;
    let tab = tabFinder.get(data.outerWindowId);
    let fluentName;
    let fluentArgs = {};
    let className;
    if (tab && tab.tabbrowser) {
      fluentName = "about-processes-tab-name";
      fluentArgs.name = tab.tab.label;
      className = "tab";
    } else if (tab) {
      fluentName = "about-processes-preloaded-tab";
      className = "preloaded-tab";
    } else if (data.count == 1) {
      fluentName = "about-processes-frame-name-one";
      fluentArgs.url = data.documentURI.spec;
      className = "frame-one";
    } else {
      fluentName = "about-processes-frame-name-many";
      fluentArgs.number = data.count;
      fluentArgs.shortUrl =
        data.documentURI.scheme == "about"
          ? data.documentURI.spec
          : data.documentURI.prePath;
      className = "frame-many";
    }
    this._fillCell(nameCell, {
      fluentName,
      fluentArgs,
      classes: ["name", "indent", "favicon", className],
    });
    let image = tab?.tab.getAttribute("image");
    if (image) {
      nameCell.style.backgroundImage = `url('${image}')`;
    }

    // Column: action
    let killButton = nameCell.nextSibling;
    killButton.className = "action-icon";

    if (data.tab && data.tab.tabbrowser) {
      // A tab. We want to be able to close it.
      if (
        this._killedRecently.some(
          kill => kill.windowId && kill.windowId == data.outerWindowId
        )
      ) {
        // We're racing between the "kill" action and the visual refresh.
        // In a few cases, we could end up with the visual refresh showing
        // a window as un-killed while we actually just killed it.
        //
        // We still want to display the window in case something actually
        // went bad and the user needs the information to realize this.
        // But we also want to make it visible that the window is being
        // killed.
        row.classList.add("killed");
      } else {
        // Otherwise, let's display the kill button.
        killButton.classList.add("close-icon");
        document.l10n.setAttributes(killButton, "about-processes-shutdown-tab");
      }
    }
  },

  utilityActorNameToFluentName(actorName) {
    let fluentName;
    switch (actorName) {
      case "audioDecoder_Generic":
        fluentName = "about-processes-utility-actor-audio-decoder-generic";
        break;

      case "audioDecoder_AppleMedia":
        fluentName = "about-processes-utility-actor-audio-decoder-applemedia";
        break;

      case "audioDecoder_WMF":
        fluentName = "about-processes-utility-actor-audio-decoder-wmf";
        break;

      case "mfMediaEngineCDM":
        fluentName = "about-processes-utility-actor-mf-media-engine";
        break;

      case "jSOracle":
        fluentName = "about-processes-utility-actor-js-oracle";
        break;

      case "windowsUtils":
        fluentName = "about-processes-utility-actor-windows-utils";
        break;

      case "windowsFileDialog":
        fluentName = "about-processes-utility-actor-windows-file-dialog";
        break;

      default:
        fluentName = "about-processes-utility-actor-unknown";
        break;
    }
    return fluentName;
  },

  displayUtilityActorRow(data, parent) {
    const cellCount = 2;
    // The actor name is expected to be unique within a given utility process.
    let rowId = "u:" + parent.pid + data.actorName;
    let row = this._getOrCreateRow(rowId, cellCount);
    row.actor = data;
    row.className = "actor";

    // Column: name
    let nameCell = row.firstChild;
    let fluentName = this.utilityActorNameToFluentName(data.actorName);
    let fluentArgs = {};
    this._fillCell(nameCell, {
      fluentName,
      fluentArgs,
      classes: ["name", "indent", "favicon"],
    });
  },

  /**
   * Display a row showing a single thread.
   *
   * @param {ThreadDelta} data The data to display.
   * @param {Number} maxSlopeCpu The largest slopeCpu value.
   */
  displayThreadRow(data, maxSlopeCpu) {
    const cellCount = 3;
    let rowId = "t:" + data.tid;
    let row = this._getOrCreateRow(rowId, cellCount);
    row.thread = data;
    row.className = "thread";

    // Column: name
    let nameCell = row.firstChild;
    this._fillCell(nameCell, {
      fluentName: "about-processes-thread-name-and-id",
      fluentArgs: {
        name: data.name,
        tid: "" + data.tid /* Make sure that this number is not localized */,
      },
      classes: ["name", "double_indent"],
    });

    // Column: CPU
    this.displayCpu(data, nameCell.nextSibling, maxSlopeCpu);

    // Third column (Buttons) is empty, nothing to do.
  },

  _orderedRows: [],
  _fillCell(elt, { classes, fluentName, fluentArgs }) {
    document.l10n.setAttributes(elt, fluentName, fluentArgs);
    elt.className = classes.join(" ");
  },

  _getDuration(rawDurationNS) {
    if (rawDurationNS <= NS_PER_US) {
      return { duration: rawDurationNS, unit: "ns" };
    }
    if (rawDurationNS <= NS_PER_MS) {
      return { duration: rawDurationNS / NS_PER_US, unit: "us" };
    }
    if (rawDurationNS <= NS_PER_S) {
      return { duration: rawDurationNS / NS_PER_MS, unit: "ms" };
    }
    if (rawDurationNS <= NS_PER_MIN) {
      return { duration: rawDurationNS / NS_PER_S, unit: "s" };
    }
    if (rawDurationNS <= NS_PER_HOUR) {
      return { duration: rawDurationNS / NS_PER_MIN, unit: "m" };
    }
    if (rawDurationNS <= NS_PER_DAY) {
      return { duration: rawDurationNS / NS_PER_HOUR, unit: "h" };
    }
    return { duration: rawDurationNS / NS_PER_DAY, unit: "d" };
  },

  /**
   * Format a value representing an amount of memory.
   *
   * As a special case, we also handle `null`, which represents the case in which we do
   * not have sufficient information to compute an amount of memory.
   *
   * @param {Number?} value The value to format. Must be either `null` or a non-negative number.
   * @return { {unit: "GB" | "MB" | "KB" | B" | "?"}, amount: Number } The formated amount and its
   *  unit, which may be used for e.g. additional CSS formating.
   */
  _formatMemory(value) {
    if (value == null) {
      return { unit: "?", amount: 0 };
    }
    if (typeof value != "number") {
      throw new Error(`Invalid memory value ${value}`);
    }
    let abs = Math.abs(value);
    if (abs >= ONE_GIGA) {
      return {
        unit: "GB",
        amount: value / ONE_GIGA,
      };
    }
    if (abs >= ONE_MEGA) {
      return {
        unit: "MB",
        amount: value / ONE_MEGA,
      };
    }
    if (abs >= ONE_KILO) {
      return {
        unit: "KB",
        amount: value / ONE_KILO,
      };
    }
    return {
      unit: "B",
      amount: value,
    };
  },
};

var Control = {
  // The set of all processes reported as "hung" by the process hang monitor.
  //
  // type: Set<ChildID>
  _hungItems: new Set(),
  _sortColumn: null,
  _sortAscendent: true,
  _removeSubtree(row) {
    let sibling = row.nextSibling;
    while (sibling && !sibling.classList.contains("process")) {
      let next = sibling.nextSibling;
      if (sibling.classList.contains("thread")) {
        View._removeRow(sibling);
      }
      sibling = next;
    }
  },
  init() {
    this._initHangReports();

    // Start prefetching units.
    this._promisePrefetchedUnits = (async function () {
      let [ns, us, ms, s, m, h, d, B, KB, MB, GB, TB, PB, EB] =
        await document.l10n.formatValues([
          { id: "duration-unit-ns" },
          { id: "duration-unit-us" },
          { id: "duration-unit-ms" },
          { id: "duration-unit-s" },
          { id: "duration-unit-m" },
          { id: "duration-unit-h" },
          { id: "duration-unit-d" },
          { id: "memory-unit-B" },
          { id: "memory-unit-KB" },
          { id: "memory-unit-MB" },
          { id: "memory-unit-GB" },
          { id: "memory-unit-TB" },
          { id: "memory-unit-PB" },
          { id: "memory-unit-EB" },
        ]);
      return {
        duration: { ns, us, ms, s, m, h, d },
        memory: { B, KB, MB, GB, TB, PB, EB },
      };
    })();

    let tbody = document.getElementById("process-tbody");

    // Single click:
    // - show or hide the contents of a twisty;
    // - close a process;
    // - profile a process;
    // - change selection.
    tbody.addEventListener("click", event => {
      this._updateLastMouseEvent();

      this._handleActivate(event.target);
    });

    // Enter or Space keypress:
    // - show or hide the contents of a twisty;
    // - close a process;
    // - profile a process;
    // - change selection.
    tbody.addEventListener("keypress", event => {
      // Handle showing or hiding subitems of a row, when keyboard is used.
      if (event.key === "Enter" || event.key === " ") {
        this._handleActivate(event.target);
      }
    });

    // Double click:
    // - navigate to tab;
    // - navigate to about:addons.
    tbody.addEventListener("dblclick", event => {
      this._updateLastMouseEvent();
      event.stopPropagation();

      // Bubble up the doubleclick manually.
      for (
        let target = event.target;
        target && target.getAttribute("id") != "process-tbody";
        target = target.parentNode
      ) {
        if (target.classList.contains("tab")) {
          // We've clicked on a tab, navigate.
          let { tab, tabbrowser } = target.parentNode.win.tab;
          tabbrowser.selectedTab = tab;
          tabbrowser.ownerGlobal.focus();
          return;
        }
        if (target.classList.contains("extensions")) {
          // We've clicked on the extensions process, open or reuse window.
          let parentWin =
            window.docShell.browsingContext.embedderElement.ownerGlobal;
          parentWin.BrowserAddonUI.openAddonsMgr();
          return;
        }
        // Otherwise, proceed.
      }
    });

    tbody.addEventListener("mousemove", () => {
      this._updateLastMouseEvent();
    });

    // Visibility change:
    // - stop updating while the user isn't looking;
    // - resume updating when the user returns.
    window.addEventListener("visibilitychange", () => {
      if (!document.hidden) {
        this._updateDisplay(true);
      }
    });

    document
      .getElementById("process-thead")
      .addEventListener("click", async event => {
        if (!event.target.classList.contains("clickable")) {
          return;
        }
        // Linux has conventions opposite to Windows and macOS on the direction of arrows
        // when sorting.
        const platformIsLinux = AppConstants.platform == "linux";
        const ascArrow = platformIsLinux ? "arrow-up" : "arrow-down";
        const descArrow = platformIsLinux ? "arrow-down" : "arrow-up";

        if (this._sortColumn) {
          const td = document.getElementById(this._sortColumn);
          td.classList.remove(ascArrow, descArrow);
        }

        const columnId = event.target.id;
        if (columnId == this._sortColumn) {
          // Reverse sorting order.
          this._sortAscendent = !this._sortAscendent;
        } else {
          this._sortColumn = columnId;
          this._sortAscendent = true;
        }

        event.target.classList.toggle(ascArrow, this._sortAscendent);
        event.target.classList.toggle(descArrow, !this._sortAscendent);

        await this._updateDisplay(true);
      });
  },
  _lastMouseEvent: 0,
  _updateLastMouseEvent() {
    this._lastMouseEvent = Date.now();
  },
  _initHangReports() {
    const PROCESS_HANG_REPORT_NOTIFICATION = "process-hang-report";

    // Receiving report of a hung child.
    // Let's store if for our next update.
    let hangReporter = report => {
      report.QueryInterface(Ci.nsIHangReport);
      this._hungItems.add(report.childID);
    };
    Services.obs.addObserver(hangReporter, PROCESS_HANG_REPORT_NOTIFICATION);

    // Don't forget to unregister the reporter.
    window.addEventListener(
      "unload",
      () => {
        Services.obs.removeObserver(
          hangReporter,
          PROCESS_HANG_REPORT_NOTIFICATION
        );
      },
      { once: true }
    );
  },
  async update(force = false) {
    await State.update(force);

    if (document.hidden) {
      return;
    }

    await this._updateDisplay(force);
  },

  // The force parameter can force a full update even when the mouse has been
  // moved recently.
  async _updateDisplay(force = false) {
    let counters = State.getCounters();
    if (this._promisePrefetchedUnits) {
      gLocalizedUnits = await this._promisePrefetchedUnits;
      this._promisePrefetchedUnits = null;
    }

    // We reset `_hungItems`, based on the assumption that the process hang
    // monitor will inform us again before the next update. Since the process hang monitor
    // pings its clients about once per second and we update about once per 2 seconds
    // (or more if the mouse moves), we should be ok.
    let hungItems = this._hungItems;
    this._hungItems = new Set();

    counters = this._sortProcesses(counters);

    // Stored because it is used when opening the list of threads.
    this._maxSlopeCpu = Math.max(...counters.map(process => process.slopeCpu));

    let previousProcess = null;
    for (let process of counters) {
      this._sortDOMWindows(process.windows);

      process.isHung = process.childID && hungItems.has(process.childID);

      let processRow = View.displayProcessRow(process, this._maxSlopeCpu);

      if (process.type != "extension") {
        // We do not want to display extensions.
        for (let win of process.windows) {
          if (SHOW_ALL_SUBFRAMES || win.tab || win.isProcessRoot) {
            View.displayDOMWindowRow(win, process);
          }
        }
      }

      if (process.type === "utility") {
        for (let actor of process.utilityActors) {
          View.displayUtilityActorRow(actor, process);
        }
      }

      if (SHOW_THREADS) {
        if (View.displayThreadSummaryRow(process)) {
          this._showThreads(processRow, this._maxSlopeCpu);
        }
      }
      if (
        this._sortColumn == null &&
        previousProcess &&
        previousProcess.displayRank != process.displayRank
      ) {
        // Add a separation between successive categories of processes.
        processRow.classList.add("separate-from-previous-process-group");
      }
      previousProcess = process;
    }

    if (
      !force &&
      Date.now() - this._lastMouseEvent < TIME_BEFORE_SORTING_AGAIN
    ) {
      // If there has been a recent mouse event, we don't want to reorder,
      // add or remove rows so that the table content under the mouse pointer
      // doesn't change when the user might be about to click to close a tab
      // or kill a process.
      // We didn't return earlier because updating CPU and memory values is
      // still valuable.
      View.discardUpdate();
      return;
    }

    View.commit();

    // Reset the selectedRow field if that row is no longer in the DOM
    // to avoid keeping forever references to dead processes.
    if (this.selectedRow && !this.selectedRow.parentNode) {
      this.selectedRow = null;
    }

    // Used by tests to differentiate full updates from l10n updates.
    document.dispatchEvent(new CustomEvent("AboutProcessesUpdated"));
  },
  _compareCpu(a, b) {
    return (
      b.slopeCpu - a.slopeCpu || b.active - a.active || b.totalCpu - a.totalCpu
    );
  },
  _showThreads(row, maxSlopeCpu) {
    let process = row.process;
    this._sortThreads(process.threads);
    for (let thread of process.threads) {
      View.displayThreadRow(thread, maxSlopeCpu);
    }
  },
  _sortThreads(threads) {
    return threads.sort((a, b) => {
      let order;
      switch (this._sortColumn) {
        case "column-name":
          order = a.name.localeCompare(b.name) || a.tid - b.tid;
          break;
        case "column-cpu-total":
          order = this._compareCpu(a, b);
          break;
        case "column-memory-resident":
        case null:
          order = a.tid - b.tid;
          break;
        default:
          throw new Error("Unsupported order: " + this._sortColumn);
      }
      if (!this._sortAscendent) {
        order = -order;
      }
      return order;
    });
  },
  _sortProcesses(counters) {
    return counters.sort((a, b) => {
      let order;
      switch (this._sortColumn) {
        case "column-name":
          order =
            String(a.origin).localeCompare(b.origin) ||
            String(a.type).localeCompare(b.type) ||
            a.pid - b.pid;
          break;
        case "column-cpu-total":
          order = this._compareCpu(a, b);
          break;
        case "column-memory-resident":
          order = b.totalRamSize - a.totalRamSize;
          break;
        case null:
          // Default order: classify processes by group.
          order =
            a.displayRank - b.displayRank ||
            // Other processes are ordered by origin.
            String(a.origin).localeCompare(b.origin);
          break;
        default:
          throw new Error("Unsupported order: " + this._sortColumn);
      }
      if (!this._sortAscendent) {
        order = -order;
      }
      return order;
    });
  },
  _sortDOMWindows(windows) {
    return windows.sort((a, b) => {
      let order =
        a.displayRank - b.displayRank ||
        a.documentTitle.localeCompare(b.documentTitle) ||
        a.documentURI.spec.localeCompare(b.documentURI.spec);
      if (!this._sortAscendent) {
        order = -order;
      }
      return order;
    });
  },

  // Assign a display rank to a process.
  //
  // The `browser` process comes first (rank 0).
  // Then come web tabs (rank 1).
  // Then come web frames (rank 2).
  // Then come special processes (minus preallocated) (rank 3).
  // Then come preallocated processes (rank 4).
  _getDisplayGroupRank(data, windows) {
    const RANK_BROWSER = 0;
    const RANK_WEB_TABS = 1;
    const RANK_WEB_FRAMES = 2;
    const RANK_UTILITY = 3;
    const RANK_PREALLOCATED = 4;
    let type = data.type;
    switch (type) {
      // Browser comes first.
      case "browser":
        return RANK_BROWSER;
      // Web content comes next.
      case "webIsolated":
      case "webServiceWorker":
      case "withCoopCoep": {
        if (windows.some(w => w.tab)) {
          return RANK_WEB_TABS;
        }
        return RANK_WEB_FRAMES;
      }
      // Preallocated processes come last.
      case "preallocated":
        return RANK_PREALLOCATED;
      // "web" is special, as it could be one of:
      // - web content currently loading/unloading/...
      // - a preallocated process.
      case "web":
        if (windows.some(w => w.tab)) {
          return RANK_WEB_TABS;
        }
        if (windows.length >= 1) {
          return RANK_WEB_FRAMES;
        }
        // For the time being, we do not display DOM workers
        // (and there's no API to get information on them).
        // Once the blockers for bug 1663737 have landed, we'll be able
        // to find out whether this process has DOM workers. If so, we'll
        // count this process as a content process.
        return RANK_PREALLOCATED;
      // Other special processes before preallocated.
      default:
        return RANK_UTILITY;
    }
  },

  // Handle events on image controls.
  _handleActivate(target) {
    if (target.classList.contains("twisty")) {
      this._handleTwisty(target);
      return;
    }
    if (target.classList.contains("close-icon")) {
      this._handleKill(target);
      return;
    }

    if (target.classList.contains("profiler-icon")) {
      this._handleProfiling(target);
      return;
    }

    this._handleSelection(target);
  },

  // Open/close list of threads.
  _handleTwisty(target) {
    let row = target.parentNode.parentNode;
    if (target.classList.toggle("open")) {
      target.setAttribute("aria-expanded", "true");
      this._showThreads(row, this._maxSlopeCpu);
      View.insertAfterRow(row);
    } else {
      target.setAttribute("aria-expanded", "false");
      this._removeSubtree(row);
    }
  },

  // Kill process/close tab/close subframe.
  _handleKill(target) {
    let row = target.parentNode;
    if (row.process) {
      // Kill process immediately.
      let pid = row.process.pid;

      // Make sure that the user can't click twice on the kill button.
      // Otherwise, chaos might ensue. Plus we risk crashing under Windows.
      View._killedRecently.push({ pid });

      // Discard tab contents and show that the process and all its contents are getting killed.
      row.classList.add("killing");

      // Avoid continuing to show the tooltip when the button isn't visible.
      target.removeAttribute("data-l10n-id");
      target.removeAttribute("title");
      for (
        let childRow = row.nextSibling;
        childRow && !childRow.classList.contains("process");
        childRow = childRow.nextSibling
      ) {
        childRow.classList.add("killing");
        let win = childRow.win;
        if (win) {
          View._killedRecently.push({ pid: win.outerWindowId });
          if (win.tab && win.tab.tabbrowser) {
            win.tab.tabbrowser.discardBrowser(
              win.tab.tab,
              /* aForceDiscard = */ true
            );
          }
        }
      }

      // Finally, kill the process.
      const ProcessTools = Cc["@mozilla.org/processtools-service;1"].getService(
        Ci.nsIProcessToolsService
      );
      ProcessTools.kill(pid);
    } else if (row.win && row.win.tab && row.win.tab.tabbrowser) {
      // This is a tab, close it.
      row.win.tab.tabbrowser.removeTab(row.win.tab.tab, {
        skipPermitUnload: true,
        animate: true,
      });
      View._killedRecently.push({ outerWindowId: row.win.outerWindowId });
      row.classList.add("killing");
      target.removeAttribute("data-l10n-id");
      target.removeAttribute("title");

      // If this was the only root window of the process, show that the process is also getting killed.
      if (row.previousSibling.classList.contains("process")) {
        let parentRow = row.previousSibling;
        let roots = 0;
        for (let win of parentRow.process.windows) {
          if (win.isProcessRoot) {
            roots += 1;
          }
        }
        if (roots <= 1) {
          // Yes, we're the only process root, so the process is dying.
          //
          // It might actually become a preloaded process rather than
          // dying. That's an acceptable error. Even if we display incorrectly
          // that the process is dying, this error will last only one refresh.
          View._killedRecently.push({ pid: parentRow.process.pid });
          parentRow.classList.add("killing");
          let actionIcon = parentRow.querySelector(".action-item");
          actionIcon.removeAttribute("data-l10n-id");
          actionIcon.removeAttribute("title");
        }
      }
    }
  },

  // Handle profiling of a process.
  _handleProfiling(target) {
    if (Services.profiler.IsActive()) {
      return;
    }
    Services.profiler.StartProfiler(
      10000000,
      1,
      ["default", "ipcmessages", "power"],
      ["pid:" + target.parentNode.parentNode.process.pid]
    );
    target.classList.add("profiler-active");
    setTimeout(() => {
      ProfilerPopupBackground.captureProfile("aboutprofiling");
      target.classList.remove("profiler-active");
    }, PROFILE_DURATION * 1000);
  },

  // Handle selection changes.
  _handleSelection(target) {
    let row = target.closest("tr");
    if (!row) {
      return;
    }
    if (this.selectedRow) {
      this.selectedRow.removeAttribute("selected");
      if (this.selectedRow.rowId == row.rowId) {
        // Clicking the same row again clears the selection.
        this.selectedRow = null;
        return;
      }
    }
    row.setAttribute("selected", "true");
    this.selectedRow = row;
  },
};

window.onload = async function () {
  Control.init();

  // Display immediately the list of processes. CPU values will be missing.
  await Control.update();

  // After the minimum interval between samples, force an update to show
  // valid CPU values asap.
  await new Promise(resolve =>
    setTimeout(resolve, MINIMUM_INTERVAL_BETWEEN_SAMPLES_MS)
  );
  await Control.update(true);

  // Then update at the normal frequency.
  window.setInterval(() => Control.update(), UPDATE_INTERVAL_MS);
};
PK
!<�~̿(�(.chrome/toolkit/content/global/aboutProfiles.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const { XPCOMUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/XPCOMUtils.sys.mjs"
);

XPCOMUtils.defineLazyServiceGetter(
  this,
  "ProfileService",
  "@mozilla.org/toolkit/profile-service;1",
  "nsIToolkitProfileService"
);

async function flush() {
  try {
    ProfileService.flush();
    rebuildProfileList();
  } catch (e) {
    let [title, msg, button] = await document.l10n.formatValues([
      { id: "profiles-flush-fail-title" },
      {
        id:
          e.result == Cr.NS_ERROR_DATABASE_CHANGED
            ? "profiles-flush-conflict"
            : "profiles-flush-failed",
      },
      { id: "profiles-flush-restart-button" },
    ]);

    const PS = Ci.nsIPromptService;
    let result = Services.prompt.confirmEx(
      window,
      title,
      msg,
      PS.BUTTON_POS_0 * PS.BUTTON_TITLE_CANCEL +
        PS.BUTTON_POS_1 * PS.BUTTON_TITLE_IS_STRING,
      null,
      button,
      null,
      null,
      {}
    );
    if (result == 1) {
      restart(false);
    }
  }
}

function rebuildProfileList() {
  let parent = document.getElementById("profiles");
  while (parent.firstChild) {
    parent.firstChild.remove();
  }

  let defaultProfile;
  try {
    defaultProfile = ProfileService.defaultProfile;
  } catch (e) {}

  let currentProfile = ProfileService.currentProfile;

  for (let profile of ProfileService.profiles) {
    let isCurrentProfile = profile == currentProfile;
    let isInUse = isCurrentProfile;
    if (!isInUse) {
      try {
        let lock = profile.lock({});
        lock.unlock();
      } catch (e) {
        if (
          e.result != Cr.NS_ERROR_FILE_NOT_DIRECTORY &&
          e.result != Cr.NS_ERROR_FILE_NOT_FOUND
        ) {
          isInUse = true;
        }
      }
    }
    display({
      profile,
      isDefault: profile == defaultProfile,
      isCurrentProfile,
      isInUse,
    });
  }
}

function display(profileData) {
  let parent = document.getElementById("profiles");

  let div = document.createElement("div");
  parent.appendChild(div);

  let name = document.createElement("h2");

  div.appendChild(name);
  document.l10n.setAttributes(name, "profiles-name", {
    name: profileData.profile.name,
  });

  if (profileData.isCurrentProfile) {
    let currentProfile = document.createElement("h3");
    document.l10n.setAttributes(currentProfile, "profiles-current-profile");
    div.appendChild(currentProfile);
  } else if (profileData.isInUse) {
    let currentProfile = document.createElement("h3");
    document.l10n.setAttributes(currentProfile, "profiles-in-use-profile");
    div.appendChild(currentProfile);
  }

  let table = document.createElement("table");
  div.appendChild(table);

  let tbody = document.createElement("tbody");
  table.appendChild(tbody);

  function createItem(title, value, dir = false) {
    let tr = document.createElement("tr");
    tbody.appendChild(tr);

    let th = document.createElement("th");
    th.setAttribute("class", "column");
    document.l10n.setAttributes(th, title);
    tr.appendChild(th);

    let td = document.createElement("td");
    tr.appendChild(td);

    if (dir) {
      td.appendChild(document.createTextNode(value.path));

      if (value.exists()) {
        let button = document.createElement("button");
        button.setAttribute("class", "opendir");
        document.l10n.setAttributes(button, "profiles-opendir");

        td.appendChild(button);

        button.addEventListener("click", function () {
          value.reveal();
        });
      }
    } else {
      document.l10n.setAttributes(td, value);
    }
  }

  createItem(
    "profiles-is-default",
    profileData.isDefault ? "profiles-yes" : "profiles-no"
  );

  createItem("profiles-rootdir", profileData.profile.rootDir, true);

  if (profileData.profile.localDir.path != profileData.profile.rootDir.path) {
    createItem("profiles-localdir", profileData.profile.localDir, true);
  }

  let renameButton = document.createElement("button");
  document.l10n.setAttributes(renameButton, "profiles-rename");
  renameButton.onclick = function () {
    renameProfile(profileData.profile);
  };
  div.appendChild(renameButton);

  if (!profileData.isInUse) {
    let removeButton = document.createElement("button");
    document.l10n.setAttributes(removeButton, "profiles-remove");
    removeButton.onclick = function () {
      removeProfile(profileData.profile);
    };

    div.appendChild(removeButton);
  }

  if (!profileData.isDefault) {
    let defaultButton = document.createElement("button");
    document.l10n.setAttributes(defaultButton, "profiles-set-as-default");
    defaultButton.onclick = function () {
      defaultProfile(profileData.profile);
    };
    div.appendChild(defaultButton);
  }

  if (!profileData.isInUse) {
    let runButton = document.createElement("button");
    document.l10n.setAttributes(runButton, "profiles-launch-profile");
    runButton.onclick = function () {
      openProfile(profileData.profile);
    };
    div.appendChild(runButton);
  }

  let sep = document.createElement("hr");
  div.appendChild(sep);
}

// This is called from the createProfileWizard.xhtml dialog.
function CreateProfile(profile) {
  // The wizard created a profile, just make it the default.
  defaultProfile(profile);
}

function createProfileWizard() {
  // This should be rewritten in HTML eventually.
  window.browsingContext.topChromeWindow.openDialog(
    "chrome://mozapps/content/profile/createProfileWizard.xhtml",
    "",
    "centerscreen,chrome,modal,titlebar",
    ProfileService,
    { CreateProfile }
  );
}

async function renameProfile(profile) {
  let newName = { value: profile.name };
  let [title, msg] = await document.l10n.formatValues([
    { id: "profiles-rename-profile-title" },
    { id: "profiles-rename-profile", args: { name: profile.name } },
  ]);

  if (Services.prompt.prompt(window, title, msg, newName, null, { value: 0 })) {
    newName = newName.value;

    if (newName == profile.name) {
      return;
    }

    try {
      profile.name = newName;
    } catch (e) {
      let [title, msg] = await document.l10n.formatValues([
        { id: "profiles-invalid-profile-name-title" },
        { id: "profiles-invalid-profile-name", args: { name: newName } },
      ]);

      Services.prompt.alert(window, title, msg);
      return;
    }

    flush();
  }
}

async function removeProfile(profile) {
  let deleteFiles = false;

  if (profile.rootDir.exists()) {
    let [title, msg, dontDeleteStr, deleteStr] =
      await document.l10n.formatValues([
        { id: "profiles-delete-profile-title" },
        {
          id: "profiles-delete-profile-confirm",
          args: { dir: profile.rootDir.path },
        },
        { id: "profiles-dont-delete-files" },
        { id: "profiles-delete-files" },
      ]);
    let buttonPressed = Services.prompt.confirmEx(
      window,
      title,
      msg,
      Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
        Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1 +
        Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_2,
      dontDeleteStr,
      null,
      deleteStr,
      null,
      { value: 0 }
    );
    if (buttonPressed == 1) {
      return;
    }

    if (buttonPressed == 2) {
      deleteFiles = true;
    }
  }

  // If we are deleting the default profile we must choose a different one.
  let isDefault = false;
  try {
    isDefault = ProfileService.defaultProfile == profile;
  } catch (e) {}

  if (isDefault) {
    for (let p of ProfileService.profiles) {
      if (profile == p) {
        continue;
      }

      if (isDefault) {
        try {
          ProfileService.defaultProfile = p;
        } catch (e) {
          // This can happen on dev-edition if a non-default profile is in use.
          // In such a case the next time that dev-edition is started it will
          // find no default profile and just create a new one.
        }
      }

      break;
    }
  }

  try {
    profile.removeInBackground(deleteFiles);
  } catch (e) {
    let [title, msg] = await document.l10n.formatValues([
      { id: "profiles-delete-profile-failed-title" },
      { id: "profiles-delete-profile-failed-message" },
    ]);

    Services.prompt.alert(window, title, msg);
    return;
  }

  flush();
}

async function defaultProfile(profile) {
  try {
    ProfileService.defaultProfile = profile;
    flush();
  } catch (e) {
    // This can happen on dev-edition.
    let [title, msg] = await document.l10n.formatValues([
      { id: "profiles-cannot-set-as-default-title" },
      { id: "profiles-cannot-set-as-default-message" },
    ]);

    Services.prompt.alert(window, title, msg);
  }
}

function openProfile(profile) {
  Services.startup.createInstanceWithProfile(profile);
}

function restart(safeMode) {
  let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
    Ci.nsISupportsPRBool
  );
  Services.obs.notifyObservers(
    cancelQuit,
    "quit-application-requested",
    "restart"
  );

  if (cancelQuit.data) {
    return;
  }

  let flags = Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart;

  if (safeMode) {
    Services.startup.restartInSafeMode(flags);
  } else {
    Services.startup.quit(flags);
  }
}

window.addEventListener(
  "DOMContentLoaded",
  function () {
    let createButton = document.getElementById("create-button");
    createButton.addEventListener("click", createProfileWizard);

    let restartSafeModeButton = document.getElementById(
      "restart-in-safe-mode-button"
    );
    if (!Services.policies || Services.policies.isAllowed("safeMode")) {
      restartSafeModeButton.addEventListener("click", () => {
        restart(true);
      });
    } else {
      restartSafeModeButton.setAttribute("disabled", "true");
    }

    let restartNormalModeButton = document.getElementById("restart-button");
    restartNormalModeButton.addEventListener("click", () => {
      restart(false);
    });

    if (ProfileService.isListOutdated) {
      document.getElementById("owned").hidden = true;
    } else {
      document.getElementById("conflict").hidden = true;
      rebuildProfileList();
    }
  },
  { once: true }
);
PK
!<�"��551chrome/toolkit/content/global/aboutProfiles.xhtml<?xml version="1.0" encoding="UTF-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:; object-src 'none'"
    />
    <meta name="color-scheme" content="light dark" />
    <title data-l10n-id="profiles-title"></title>
    <link
      rel="icon"
      type="image/png"
      id="favicon"
      href="chrome://branding/content/icon32.png"
    />
    <link
      rel="stylesheet"
      href="chrome://mozapps/skin/aboutProfiles.css"
      type="text/css"
    />
    <script src="chrome://global/content/aboutProfiles.js" />
    <link rel="localization" href="branding/brand.ftl" />
    <link rel="localization" href="toolkit/about/aboutProfiles.ftl" />
  </head>
  <body id="body" class="wide-container">
    <h1 data-l10n-id="profiles-title"></h1>

    <div id="conflict">
      <p data-l10n-id="profiles-conflict" />
    </div>

    <div class="header-flex">
      <div
        class="page-subtitle content-flex"
        data-l10n-id="profiles-subtitle"
      ></div>
      <div class="action-box">
        <h3 data-l10n-id="profiles-restart-title"></h3>
        <button
          id="restart-in-safe-mode-button"
          data-l10n-id="profiles-restart-in-safe-mode"
        ></button>
        <button
          id="restart-button"
          data-l10n-id="profiles-restart-normal"
        ></button>
      </div>
    </div>

    <div id="owned">
      <div>
        <button id="create-button" data-l10n-id="profiles-create"></button>
      </div>

      <div id="profiles" class="tab"></div>
    </div>
  </body>
</html>
PK
!<Ͷ�yss,chrome/toolkit/content/global/aboutRights.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

var servicesDiv = document.getElementById("webservices-container");
servicesDiv.style.display = "none";

function showServices() {
  servicesDiv.style.display = "";
}

// Fluent replaces the children of the element being overlayed which prevents us
// from putting an event handler directly on the children.
let rightsIntro =
  document.querySelector("[data-l10n-id=rights-intro-point-5]") ||
  document.querySelector("[data-l10n-id=rights-intro-point-5-unbranded]");
rightsIntro.addEventListener("click", event => {
  if (event.target.id == "showWebServices") {
    showServices();
  }
});

var disablingServicesDiv = document.getElementById(
  "disabling-webservices-container"
);

function showDisablingServices() {
  disablingServicesDiv.style.display = "";
}

if (disablingServicesDiv != null) {
  disablingServicesDiv.style.display = "none";
  // Same issue here with Fluent replacing the children affecting the event listeners.
  let rightsWebServices = document.querySelector(
    "[data-l10n-id=rights-webservices]"
  );
  rightsWebServices.addEventListener("click", event => {
    if (event.target.id == "showDisablingWebServices") {
      showDisablingServices();
    }
  });
}
PK
!<_�ff/chrome/toolkit/content/global/aboutRights.xhtml<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html [ <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
%htmlDTD; ]>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:; object-src 'none'"
    />
    <meta name="color-scheme" content="light dark" />
    <title data-l10n-id="rights-title"></title>
    <link
      rel="stylesheet"
      href="chrome://global/skin/in-content/info-pages.css"
      type="text/css"
    />
    <link
      rel="stylesheet"
      href="chrome://global/skin/aboutRights.css"
      type="text/css"
    />
    <link rel="localization" href="branding/brand.ftl" />
    <link rel="localization" href="toolkit/about/aboutRights.ftl" />
  </head>

  <body id="your-rights">
    <div class="container">
      <div class="rights-header">
        <div>
          <h1 data-l10n-id="rights-title"></h1>

          <p data-l10n-id="rights-intro"></p>
        </div>
      </div>

      <ul>
        <li data-l10n-id="rights-intro-point-1">
          <a
            href="http://www.mozilla.org/MPL/"
            data-l10n-name="mozilla-public-license-link"
          ></a>
        </li>
        <!-- Point 2 discusses Mozilla trademarks, and isn't needed when the build is unbranded.
    - Point 4 discusses privacy policy, unbranded builds get a placeholder (for the vendor to replace)
    - Point 5 discusses web service terms, unbranded builds gets a placeholder (for the vendor to replace) -->
        <li data-l10n-id="rights-intro-point-2">
          <a
            href="http://www.mozilla.org/foundation/trademarks/policy.html"
            data-l10n-name="mozilla-trademarks-link"
          ></a>
        </li>
        <li data-l10n-id="rights-intro-point-3"></li>
        <li data-l10n-id="rights-intro-point-4">
          <a
            href="https://www.mozilla.org/legal/privacy/firefox.html"
            data-l10n-name="mozilla-privacy-policy-link"
          ></a>
        </li>
        <li data-l10n-id="rights-intro-point-5">
          <a
            href="about:rights#webservices"
            id="showWebServices"
            data-l10n-name="mozilla-service-terms-link"
          ></a>
        </li>
        <li data-l10n-id="rights-intro-point-6"></li>
      </ul>

      <div id="webservices-container">
        <a name="webservices" />
        <h3 data-l10n-id="rights-webservices-header"></h3>

        <p data-l10n-id="rights-webservices">
          <a
            href="about:rights#disabling-webservices"
            id="showDisablingWebServices"
            data-l10n-name="mozilla-disable-service-link"
          ></a>
        </p>

        <div id="disabling-webservices-container" style="margin-left: 40px">
          <a name="disabling-webservices" />
          <p data-l10n-id="rights-safebrowsing"></p>
          <ul>
            <li data-l10n-id="rights-safebrowsing-term-1"></li>
            <li data-l10n-id="rights-safebrowsing-term-2"></li>
            <li data-l10n-id="rights-safebrowsing-term-3"></li>
            <li data-l10n-id="rights-safebrowsing-term-4"></li>
          </ul>

          <p data-l10n-id="rights-locationawarebrowsing"></p>
          <ul>
            <li data-l10n-id="rights-locationawarebrowsing-term-1"></li>
            <li data-l10n-id="rights-locationawarebrowsing-term-2"></li>
            <li data-l10n-id="rights-locationawarebrowsing-term-3"></li>
            <li data-l10n-id="rights-locationawarebrowsing-term-4"></li>
          </ul>
        </div>

        <ol>
          <!-- Terms only apply to official builds, unbranded builds get a placeholder. -->
          <li data-l10n-id="rights-webservices-term-1"></li>
          <li data-l10n-id="rights-webservices-term-2"></li>
          <li data-l10n-id="rights-webservices-term-3"></li>
          <li data-l10n-id="rights-webservices-term-4"></li>
          <li data-l10n-id="rights-webservices-term-5"></li>
          <li data-l10n-id="rights-webservices-term-6"></li>
          <li data-l10n-id="rights-webservices-term-7"></li>
        </ol>
      </div>
    </div>
  </body>
  <script src="chrome://global/content/aboutRights.js" />
</html>
PK
!<�����4chrome/toolkit/content/global/aboutServiceWorkers.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

var gSWM;
var gSWCount = 0;

function init() {
  let enabled = Services.prefs.getBoolPref("dom.serviceWorkers.enabled");
  if (!enabled) {
    let div = document.getElementById("warning_not_enabled");
    div.classList.add("active");
    return;
  }

  gSWM = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
    Ci.nsIServiceWorkerManager
  );
  if (!gSWM) {
    dump(
      "AboutServiceWorkers: Failed to get the ServiceWorkerManager service!\n"
    );
    return;
  }

  let data = gSWM.getAllRegistrations();
  if (!data) {
    dump("AboutServiceWorkers: Failed to retrieve the registrations.\n");
    return;
  }

  let length = data.length;
  if (!length) {
    let div = document.getElementById("warning_no_serviceworkers");
    div.classList.add("active");
    return;
  }

  let ps = undefined;
  try {
    ps = Cc["@mozilla.org/push/Service;1"].getService(Ci.nsIPushService);
  } catch (e) {
    dump("Could not acquire PushService\n");
  }

  for (let i = 0; i < length; ++i) {
    let info = data.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
    if (!info) {
      dump(
        "AboutServiceWorkers: Invalid nsIServiceWorkerRegistrationInfo interface.\n"
      );
      continue;
    }

    display(info, ps);
  }
}

async function display(info, pushService) {
  let parent = document.getElementById("serviceworkers");

  let div = document.createElement("div");
  parent.appendChild(div);

  let title = document.createElement("h2");
  document.l10n.setAttributes(title, "origin-title", {
    originTitle: info.principal.origin,
  });
  div.appendChild(title);

  let list = document.createElement("ul");
  div.appendChild(list);

  function createItem(l10nId, value, makeLink) {
    let item = document.createElement("li");
    list.appendChild(item);
    let bold = document.createElement("strong");
    bold.setAttribute("data-l10n-name", "item-label");
    item.appendChild(bold);
    // Falsey values like "" are still valid values, so check exactly against
    // undefined for the cases where the caller did not provide any value.
    if (value === undefined) {
      document.l10n.setAttributes(item, l10nId);
    } else if (makeLink) {
      let link = document.createElement("a");
      link.setAttribute("target", "_blank");
      link.setAttribute("data-l10n-name", "link");
      link.setAttribute("href", value);
      item.appendChild(link);
      document.l10n.setAttributes(item, l10nId, { url: value });
    } else {
      document.l10n.setAttributes(item, l10nId, { name: value });
    }
    return item;
  }

  createItem("scope", info.scope);
  createItem("script-spec", info.scriptSpec, true);
  let currentWorkerURL = info.activeWorker ? info.activeWorker.scriptSpec : "";
  createItem("current-worker-url", currentWorkerURL, true);
  let activeCacheName = info.activeWorker ? info.activeWorker.cacheName : "";
  createItem("active-cache-name", activeCacheName);
  let waitingCacheName = info.waitingWorker ? info.waitingWorker.cacheName : "";
  createItem("waiting-cache-name", waitingCacheName);

  let pushItem = createItem("push-end-point-waiting");
  if (pushService) {
    pushService.getSubscription(
      info.scope,
      info.principal,
      (status, pushRecord) => {
        if (Components.isSuccessCode(status)) {
          document.l10n.setAttributes(pushItem, "push-end-point-result", {
            name: JSON.stringify(pushRecord),
          });
        } else {
          dump("about:serviceworkers - retrieving push registration failed\n");
        }
      }
    );
  }

  let unregisterButton = document.createElement("button");
  document.l10n.setAttributes(unregisterButton, "unregister-button");
  div.appendChild(unregisterButton);

  let loadingMessage = document.createElement("span");
  document.l10n.setAttributes(loadingMessage, "waiting");
  loadingMessage.classList.add("inactive");
  div.appendChild(loadingMessage);

  unregisterButton.onclick = function () {
    let cb = {
      unregisterSucceeded() {
        parent.removeChild(div);

        if (!--gSWCount) {
          let div = document.getElementById("warning_no_serviceworkers");
          div.classList.add("active");
        }
      },

      async unregisterFailed() {
        let [alertMsg] = await document.l10n.formatValues([
          { id: "unregister-error" },
        ]);
        alert(alertMsg);
      },

      QueryInterface: ChromeUtils.generateQI([
        "nsIServiceWorkerUnregisterCallback",
      ]),
    };

    loadingMessage.classList.remove("inactive");
    gSWM.propagateUnregister(info.principal, cb, info.scope);
  };

  let sep = document.createElement("hr");
  div.appendChild(sep);

  ++gSWCount;
}

window.addEventListener(
  "DOMContentLoaded",
  function () {
    init();
  },
  { once: true }
);
PK
!<��8��7chrome/toolkit/content/global/aboutServiceWorkers.xhtml<?xml version="1.0" encoding="UTF-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html [ <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
%htmlDTD; ]>

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:; object-src 'none'"
    />
    <meta name="color-scheme" content="light dark" />
    <title data-l10n-id="about-service-workers-title"></title>
    <link
      rel="stylesheet"
      href="chrome://global/skin/in-content/info-pages.css"
      type="text/css"
    />
    <link
      rel="stylesheet"
      href="chrome://mozapps/skin/aboutServiceWorkers.css"
      type="text/css"
    />
    <link rel="localization" href="toolkit/about/aboutServiceWorkers.ftl" />
    <link rel="localization" href="branding/brand.ftl" />
    <script src="chrome://global/content/aboutServiceWorkers.js" />
  </head>
  <body class="wide-container">
    <div id="warning_not_enabled" class="warningBackground">
      <div
        class="warningMessage"
        data-l10n-id="about-service-workers-warning-not-enabled"
      ></div>
    </div>
    <div id="warning_no_serviceworkers" class="warningBackground">
      <div
        class="warningMessage"
        data-l10n-id="about-service-workers-warning-no-service-workers"
      ></div>
    </div>
    <div id="serviceworkers" class="tab active">
      <h1 data-l10n-id="about-service-workers-main-title"></h1>
    </div>
  </body>
</html>
PK
!<���R�R�-chrome/toolkit/content/global/aboutSupport.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const { Troubleshoot } = ChromeUtils.importESModule(
  "resource://gre/modules/Troubleshoot.sys.mjs"
);
const { ResetProfile } = ChromeUtils.importESModule(
  "resource://gre/modules/ResetProfile.sys.mjs"
);
const { AppConstants } = ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);

ChromeUtils.defineESModuleGetters(this, {
  DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs",
  PlacesDBUtils: "resource://gre/modules/PlacesDBUtils.sys.mjs",
  ProcessType: "resource://gre/modules/ProcessType.sys.mjs",
});

window.addEventListener("load", function onload() {
  try {
    window.removeEventListener("load", onload);
    Troubleshoot.snapshot().then(async snapshot => {
      for (let prop in snapshotFormatters) {
        try {
          await snapshotFormatters[prop](snapshot[prop]);
        } catch (e) {
          console.error(
            "stack of snapshot error for about:support: ",
            e,
            ": ",
            e.stack
          );
        }
      }
      if (location.hash) {
        scrollToSection();
      }
    }, console.error);
    populateActionBox();
    setupEventListeners();

    if (Services.sysinfo.getProperty("isPackagedApp")) {
      $("update-dir-row").hidden = true;
      $("update-history-row").hidden = true;
    }
  } catch (e) {
    console.error("stack of load error for about:support: ", e, ": ", e.stack);
  }
});

function prefsTable(data) {
  return sortedArrayFromObject(data).map(function ([name, value]) {
    return $.new("tr", [
      $.new("td", name, "pref-name"),
      // Very long preference values can cause users problems when they
      // copy and paste them into some text editors.  Long values generally
      // aren't useful anyway, so truncate them to a reasonable length.
      $.new("td", String(value).substr(0, 120), "pref-value"),
    ]);
  });
}

// Fluent uses lisp-case IDs so this converts
// the SentenceCase info IDs to lisp-case.
const FLUENT_IDENT_REGEX = /^[a-zA-Z][a-zA-Z0-9_-]*$/;
function toFluentID(str) {
  if (!FLUENT_IDENT_REGEX.test(str)) {
    return null;
  }
  return str
    .toString()
    .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
    .toLowerCase();
}

// Each property in this object corresponds to a property in Troubleshoot.sys.mjs's
// snapshot data.  Each function is passed its property's corresponding data,
// and it's the function's job to update the page with it.
var snapshotFormatters = {
  async application(data) {
    $("application-box").textContent = data.name;
    $("useragent-box").textContent = data.userAgent;
    $("os-box").textContent = data.osVersion;
    if (data.osTheme) {
      $("os-theme-box").textContent = data.osTheme;
    } else {
      $("os-theme-row").hidden = true;
    }
    if (AppConstants.platform == "macosx") {
      $("rosetta-box").textContent = data.rosetta;
    }
    if (AppConstants.platform == "win") {
      const translatedList = await Promise.all(
        data.pointingDevices.map(deviceName => {
          return document.l10n.formatValue(deviceName);
        })
      );

      const formatter = new Intl.ListFormat();

      $("pointing-devices-box").textContent = formatter.format(translatedList);
    }
    $("binary-box").textContent = Services.dirsvc.get(
      "XREExeF",
      Ci.nsIFile
    ).path;
    $("supportLink").href = data.supportURL;
    let version = AppConstants.MOZ_APP_VERSION_DISPLAY;
    if (data.vendor) {
      version += " (" + data.vendor + ")";
    }
    $("version-box").textContent = version;
    $("buildid-box").textContent = data.buildID;
    $("distributionid-box").textContent = data.distributionID;
    if (data.updateChannel) {
      $("updatechannel-box").textContent = data.updateChannel;
    }
    if (AppConstants.MOZ_UPDATER && AppConstants.platform != "android") {
      $("update-dir-box").textContent = Services.dirsvc.get(
        "UpdRootD",
        Ci.nsIFile
      ).path;
    }
    $("profile-dir-box").textContent = Services.dirsvc.get(
      "ProfD",
      Ci.nsIFile
    ).path;

    try {
      let launcherStatusTextId = "launcher-process-status-unknown";
      switch (data.launcherProcessState) {
        case 0:
        case 1:
        case 2:
          launcherStatusTextId =
            "launcher-process-status-" + data.launcherProcessState;
          break;
      }

      document.l10n.setAttributes(
        $("launcher-process-box"),
        launcherStatusTextId
      );
    } catch (e) {}

    const STATUS_STRINGS = {
      experimentControl: "fission-status-experiment-control",
      experimentTreatment: "fission-status-experiment-treatment",
      disabledByE10sEnv: "fission-status-disabled-by-e10s-env",
      enabledByEnv: "fission-status-enabled-by-env",
      disabledByEnv: "fission-status-disabled-by-env",
      enabledByDefault: "fission-status-enabled-by-default",
      disabledByDefault: "fission-status-disabled-by-default",
      enabledByUserPref: "fission-status-enabled-by-user-pref",
      disabledByUserPref: "fission-status-disabled-by-user-pref",
      disabledByE10sOther: "fission-status-disabled-by-e10s-other",
      enabledByRollout: "fission-status-enabled-by-rollout",
    };

    let statusTextId = STATUS_STRINGS[data.fissionDecisionStatus];

    document.l10n.setAttributes(
      $("multiprocess-box-process-count"),
      "multi-process-windows",
      {
        remoteWindows: data.numRemoteWindows,
        totalWindows: data.numTotalWindows,
      }
    );
    document.l10n.setAttributes(
      $("fission-box-process-count"),
      "fission-windows",
      {
        fissionWindows: data.numFissionWindows,
        totalWindows: data.numTotalWindows,
      }
    );
    document.l10n.setAttributes($("fission-box-status"), statusTextId);

    if (Services.policies) {
      let policiesStrId = "";
      let aboutPolicies = "about:policies";
      switch (data.policiesStatus) {
        case Services.policies.INACTIVE:
          policiesStrId = "policies-inactive";
          break;

        case Services.policies.ACTIVE:
          policiesStrId = "policies-active";
          aboutPolicies += "#active";
          break;

        default:
          policiesStrId = "policies-error";
          aboutPolicies += "#errors";
          break;
      }

      if (data.policiesStatus != Services.policies.INACTIVE) {
        let activePolicies = $.new("a", null, null, {
          href: aboutPolicies,
        });
        document.l10n.setAttributes(activePolicies, policiesStrId);
        $("policies-status").appendChild(activePolicies);
      } else {
        document.l10n.setAttributes($("policies-status"), policiesStrId);
      }
    } else {
      $("policies-status-row").hidden = true;
    }

    let keyLocationServiceGoogleFound = data.keyLocationServiceGoogleFound
      ? "found"
      : "missing";
    document.l10n.setAttributes(
      $("key-location-service-google-box"),
      keyLocationServiceGoogleFound
    );

    let keySafebrowsingGoogleFound = data.keySafebrowsingGoogleFound
      ? "found"
      : "missing";
    document.l10n.setAttributes(
      $("key-safebrowsing-google-box"),
      keySafebrowsingGoogleFound
    );

    let keyMozillaFound = data.keyMozillaFound ? "found" : "missing";
    document.l10n.setAttributes($("key-mozilla-box"), keyMozillaFound);

    $("safemode-box").textContent = data.safeMode;

    const formatHumanReadableBytes = (elem, bytes) => {
      let size = DownloadUtils.convertByteUnits(bytes);
      document.l10n.setAttributes(elem, "app-basics-data-size", {
        value: size[0],
        unit: size[1],
      });
    };

    formatHumanReadableBytes($("memory-size-box"), data.memorySizeBytes);
    formatHumanReadableBytes($("disk-available-box"), data.diskAvailableBytes);
  },

  async legacyUserStylesheets(legacyUserStylesheets) {
    $("legacyUserStylesheets-enabled").textContent =
      legacyUserStylesheets.active;
    $("legacyUserStylesheets-types").textContent =
      new Intl.ListFormat(undefined, { style: "short", type: "unit" }).format(
        legacyUserStylesheets.types
      ) ||
      document.l10n.setAttributes(
        $("legacyUserStylesheets-types"),
        "legacy-user-stylesheets-no-stylesheets-found"
      );
  },

  crashes(data) {
    if (!AppConstants.MOZ_CRASHREPORTER) {
      return;
    }

    let daysRange = Troubleshoot.kMaxCrashAge / (24 * 60 * 60 * 1000);
    document.l10n.setAttributes($("crashes"), "report-crash-for-days", {
      days: daysRange,
    });
    let reportURL;
    try {
      reportURL = Services.prefs.getCharPref("breakpad.reportURL");
      // Ignore any non http/https urls
      if (!/^https?:/i.test(reportURL)) {
        reportURL = null;
      }
    } catch (e) {}
    if (!reportURL) {
      $("crashes-noConfig").style.display = "block";
      $("crashes-noConfig").classList.remove("no-copy");
      return;
    }
    $("crashes-allReports").style.display = "block";

    if (data.pending > 0) {
      document.l10n.setAttributes(
        $("crashes-allReportsWithPending"),
        "pending-reports",
        { reports: data.pending }
      );
    }

    let dateNow = new Date();
    $.append(
      $("crashes-tbody"),
      data.submitted.map(function (crash) {
        let date = new Date(crash.date);
        let timePassed = dateNow - date;
        let formattedDateStrId;
        let formattedDateStrArgs;
        if (timePassed >= 24 * 60 * 60 * 1000) {
          let daysPassed = Math.round(timePassed / (24 * 60 * 60 * 1000));
          formattedDateStrId = "crashes-time-days";
          formattedDateStrArgs = { days: daysPassed };
        } else if (timePassed >= 60 * 60 * 1000) {
          let hoursPassed = Math.round(timePassed / (60 * 60 * 1000));
          formattedDateStrId = "crashes-time-hours";
          formattedDateStrArgs = { hours: hoursPassed };
        } else {
          let minutesPassed = Math.max(Math.round(timePassed / (60 * 1000)), 1);
          formattedDateStrId = "crashes-time-minutes";
          formattedDateStrArgs = { minutes: minutesPassed };
        }
        return $.new("tr", [
          $.new("td", [
            $.new("a", crash.id, null, { href: reportURL + crash.id }),
          ]),
          $.new("td", null, null, {
            "data-l10n-id": formattedDateStrId,
            "data-l10n-args": formattedDateStrArgs,
          }),
        ]);
      })
    );
  },

  addons(data) {
    $.append(
      $("addons-tbody"),
      data.map(function (addon) {
        return $.new("tr", [
          $.new("td", addon.name),
          $.new("td", addon.type),
          $.new("td", addon.version),
          $.new("td", addon.isActive),
          $.new("td", addon.id),
        ]);
      })
    );
  },

  securitySoftware(data) {
    if (AppConstants.platform !== "win") {
      $("security-software").hidden = true;
      $("security-software-table").hidden = true;
      return;
    }

    $("security-software-antivirus").textContent = data.registeredAntiVirus;
    $("security-software-antispyware").textContent = data.registeredAntiSpyware;
    $("security-software-firewall").textContent = data.registeredFirewall;
  },

  features(data) {
    $.append(
      $("features-tbody"),
      data.map(function (feature) {
        return $.new("tr", [
          $.new("td", feature.name),
          $.new("td", feature.version),
          $.new("td", feature.id),
        ]);
      })
    );
  },

  async processes(data) {
    async function buildEntry(name, value) {
      const fluentName = ProcessType.fluentNameFromProcessTypeString(name);
      let entryName = (await document.l10n.formatValue(fluentName)) || name;
      $("processes-tbody").appendChild(
        $.new("tr", [$.new("td", entryName), $.new("td", value)])
      );
    }

    let remoteProcessesCount = Object.values(data.remoteTypes).reduce(
      (a, b) => a + b,
      0
    );
    document.querySelector("#remoteprocesses-row a").textContent =
      remoteProcessesCount;

    // Display the regular "web" process type first in the list,
    // and with special formatting.
    if (data.remoteTypes.web) {
      await buildEntry(
        "web",
        `${data.remoteTypes.web} / ${data.maxWebContentProcesses}`
      );
      delete data.remoteTypes.web;
    }

    for (let remoteProcessType in data.remoteTypes) {
      await buildEntry(remoteProcessType, data.remoteTypes[remoteProcessType]);
    }
  },

  async experimentalFeatures(data) {
    if (!data) {
      return;
    }
    let titleL10nIds = data.map(([titleL10nId]) => titleL10nId);
    let titleL10nObjects = await document.l10n.formatMessages(titleL10nIds);
    if (titleL10nObjects.length != data.length) {
      throw Error("Missing localized title strings in experimental features");
    }
    for (let i = 0; i < titleL10nObjects.length; i++) {
      let localizedTitle = titleL10nObjects[i].attributes.find(
        a => a.name == "label"
      ).value;
      data[i] = [localizedTitle, data[i][1], data[i][2]];
    }

    $.append(
      $("experimental-features-tbody"),
      data.map(function ([title, pref, value]) {
        return $.new("tr", [
          $.new("td", `${title} (${pref})`, "pref-name"),
          $.new("td", value, "pref-value"),
        ]);
      })
    );
  },

  environmentVariables(data) {
    if (!data) {
      return;
    }
    $.append(
      $("environment-variables-tbody"),
      Object.entries(data).map(([name, value]) => {
        return $.new("tr", [
          $.new("td", name, "pref-name"),
          $.new("td", value, "pref-value"),
        ]);
      })
    );
  },

  modifiedPreferences(data) {
    $.append($("prefs-tbody"), prefsTable(data));
  },

  lockedPreferences(data) {
    $.append($("locked-prefs-tbody"), prefsTable(data));
  },

  places(data) {
    if (!AppConstants.MOZ_PLACES) {
      return;
    }
    const statsBody = $("place-database-stats-tbody");
    $.append(
      statsBody,
      data.map(function (entry) {
        return $.new("tr", [
          $.new("td", entry.entity),
          $.new("td", entry.count),
          $.new("td", entry.sizeBytes / 1024),
          $.new("td", entry.sizePerc),
          $.new("td", entry.efficiencyPerc),
          $.new("td", entry.sequentialityPerc),
        ]);
      })
    );
    statsBody.style.display = "none";
    $("place-database-stats-toggle").addEventListener(
      "click",
      function (event) {
        if (statsBody.style.display === "none") {
          document.l10n.setAttributes(
            event.target,
            "place-database-stats-hide"
          );
          statsBody.style.display = "";
        } else {
          document.l10n.setAttributes(
            event.target,
            "place-database-stats-show"
          );
          statsBody.style.display = "none";
        }
      }
    );
  },

  printingPreferences(data) {
    if (AppConstants.platform == "android") {
      return;
    }
    const tbody = $("support-printing-prefs-tbody");
    $.append(tbody, prefsTable(data));
    $("support-printing-clear-settings-button").addEventListener(
      "click",
      function () {
        for (let name in data) {
          Services.prefs.clearUserPref(name);
        }
        tbody.textContent = "";
      }
    );
  },

  async graphics(data) {
    function localizedMsg(msg) {
      if (typeof msg == "object" && msg.key) {
        return document.l10n.formatValue(msg.key, msg.args);
      }
      let msgId = toFluentID(msg);
      if (msgId) {
        return document.l10n.formatValue(msgId);
      }
      return "";
    }

    // Read APZ info out of data.info, stripping it out in the process.
    let apzInfo = [];
    let formatApzInfo = function (info) {
      let out = [];
      for (let type of [
        "Wheel",
        "Touch",
        "Drag",
        "Keyboard",
        "Autoscroll",
        "Zooming",
      ]) {
        let key = "Apz" + type + "Input";

        if (!(key in info)) {
          continue;
        }

        delete info[key];

        out.push(toFluentID(type.toLowerCase() + "Enabled"));
      }

      return out;
    };

    // Create a <tr> element with key and value columns.
    //
    // @key      Text in the key column. Localized automatically, unless starts with "#".
    // @value    Fluent ID for text in the value column, or array of children.
    function buildRow(key, value) {
      let title = key[0] == "#" ? key.substr(1) : key;
      let keyStrId = toFluentID(key);
      let valueStrId = Array.isArray(value) ? null : toFluentID(value);
      let td = $.new("td", value);
      td.style["white-space"] = "pre-wrap";
      if (valueStrId) {
        document.l10n.setAttributes(td, valueStrId);
      }

      let th = $.new("th", title, "column");
      if (!key.startsWith("#")) {
        document.l10n.setAttributes(th, keyStrId);
      }
      return $.new("tr", [th, td]);
    }

    // @where    The name in "graphics-<name>-tbody", of the element to append to.
    // @trs      Array of row elements.
    function addRows(where, trs) {
      $.append($("graphics-" + where + "-tbody"), trs);
    }

    // Build and append a row.
    //
    // @where    The name in "graphics-<name>-tbody", of the element to append to.
    function addRow(where, key, value) {
      addRows(where, [buildRow(key, value)]);
    }
    if ("info" in data) {
      apzInfo = formatApzInfo(data.info);

      let trs = sortedArrayFromObject(data.info).map(function ([prop, val]) {
        let td = $.new("td", String(val));
        td.style["word-break"] = "break-all";
        return $.new("tr", [$.new("th", prop, "column"), td]);
      });
      addRows("diagnostics", trs);

      delete data.info;
    }

    let windowUtils = window.windowUtils;
    let gpuProcessPid = windowUtils.gpuProcessPid;

    if (gpuProcessPid != -1) {
      let gpuProcessKillButton = null;
      if (AppConstants.NIGHTLY_BUILD || AppConstants.MOZ_DEV_EDITION) {
        gpuProcessKillButton = $.new("button");

        gpuProcessKillButton.addEventListener("click", function () {
          windowUtils.terminateGPUProcess();
        });

        document.l10n.setAttributes(
          gpuProcessKillButton,
          "gpu-process-kill-button"
        );
      }

      addRow("diagnostics", "gpu-process-pid", [new Text(gpuProcessPid)]);
      if (gpuProcessKillButton) {
        addRow("diagnostics", "gpu-process", [gpuProcessKillButton]);
      }
    }

    if (
      (AppConstants.NIGHTLY_BUILD || AppConstants.MOZ_DEV_EDITION) &&
      AppConstants.platform != "macosx"
    ) {
      let gpuDeviceResetButton = $.new("button");

      gpuDeviceResetButton.addEventListener("click", function () {
        windowUtils.triggerDeviceReset();
      });

      document.l10n.setAttributes(
        gpuDeviceResetButton,
        "gpu-device-reset-button"
      );
      addRow("diagnostics", "gpu-device-reset", [gpuDeviceResetButton]);
    }

    // graphics-failures-tbody tbody
    if ("failures" in data) {
      // If indices is there, it should be the same length as failures,
      // (see Troubleshoot.sys.mjs) but we check anyway:
      if ("indices" in data && data.failures.length == data.indices.length) {
        let combined = [];
        for (let i = 0; i < data.failures.length; i++) {
          let assembled = assembleFromGraphicsFailure(i, data);
          combined.push(assembled);
        }
        combined.sort(function (a, b) {
          if (a.index < b.index) {
            return -1;
          }
          if (a.index > b.index) {
            return 1;
          }
          return 0;
        });
        $.append(
          $("graphics-failures-tbody"),
          combined.map(function (val) {
            return $.new("tr", [
              $.new("th", val.header, "column"),
              $.new("td", val.message),
            ]);
          })
        );
        delete data.indices;
      } else {
        $.append($("graphics-failures-tbody"), [
          $.new("tr", [
            $.new("th", "LogFailure", "column"),
            $.new(
              "td",
              data.failures.map(function (val) {
                return $.new("p", val);
              })
            ),
          ]),
        ]);
      }
      delete data.failures;
    } else {
      $("graphics-failures-tbody").style.display = "none";
    }

    // Add a new row to the table, and take the key (or keys) out of data.
    //
    // @where        Table section to add to.
    // @key          Data key to use.
    // @colKey       The localization key to use, if different from key.
    async function addRowFromKey(where, key, colKey) {
      if (!(key in data)) {
        return;
      }
      colKey = colKey || key;

      let value;
      let messageKey = key + "Message";
      if (messageKey in data) {
        value = await localizedMsg(data[messageKey]);
        delete data[messageKey];
      } else {
        value = data[key];
      }
      delete data[key];

      if (value) {
        addRow(where, colKey, [new Text(value)]);
      }
    }

    // graphics-features-tbody
    let devicePixelRatios = data.graphicsDevicePixelRatios;
    addRow("features", "graphicsDevicePixelRatios", [
      new Text(devicePixelRatios),
    ]);

    let compositor = "";
    if (data.windowLayerManagerRemote) {
      compositor = data.windowLayerManagerType;
    } else {
      let noOMTCString = await document.l10n.formatValue("main-thread-no-omtc");
      compositor = "BasicLayers (" + noOMTCString + ")";
    }
    addRow("features", "compositing", [new Text(compositor)]);
    addRow("features", "supportFontDetermination", [
      new Text(data.supportFontDetermination),
    ]);
    delete data.windowLayerManagerRemote;
    delete data.windowLayerManagerType;
    delete data.numTotalWindows;
    delete data.numAcceleratedWindows;
    delete data.numAcceleratedWindowsMessage;
    delete data.graphicsDevicePixelRatios;

    addRow(
      "features",
      "asyncPanZoom",
      apzInfo.length
        ? [
            new Text(
              (
                await document.l10n.formatValues(
                  apzInfo.map(id => {
                    return { id };
                  })
                )
              ).join("; ")
            ),
          ]
        : "apz-none"
    );
    let featureKeys = [
      "webgl1WSIInfo",
      "webgl1Renderer",
      "webgl1Version",
      "webgl1DriverExtensions",
      "webgl1Extensions",
      "webgl2WSIInfo",
      "webgl2Renderer",
      "webgl2Version",
      "webgl2DriverExtensions",
      "webgl2Extensions",
      ["supportsHardwareH264", "hardware-h264"],
      ["direct2DEnabled", "#Direct2D"],
      ["windowProtocol", "graphics-window-protocol"],
      ["desktopEnvironment", "graphics-desktop-environment"],
      "targetFrameRate",
    ];
    for (let feature of featureKeys) {
      if (Array.isArray(feature)) {
        await addRowFromKey("features", feature[0], feature[1]);
        continue;
      }
      await addRowFromKey("features", feature);
    }

    featureKeys = ["webgpuDefaultAdapter", "webgpuFallbackAdapter"];
    for (let feature of featureKeys) {
      const obj = data[feature];
      if (obj) {
        const str = JSON.stringify(obj, null, "  ");
        await addRow("features", feature, [new Text(str)]);
        delete data[feature];
      }
    }

    if ("directWriteEnabled" in data) {
      let message = data.directWriteEnabled;
      if ("directWriteVersion" in data) {
        message += " (" + data.directWriteVersion + ")";
      }
      await addRow("features", "#DirectWrite", [new Text(message)]);
      delete data.directWriteEnabled;
      delete data.directWriteVersion;
    }

    // Adapter tbodies.
    let adapterKeys = [
      ["adapterDescription", "gpu-description"],
      ["adapterVendorID", "gpu-vendor-id"],
      ["adapterDeviceID", "gpu-device-id"],
      ["driverVendor", "gpu-driver-vendor"],
      ["driverVersion", "gpu-driver-version"],
      ["driverDate", "gpu-driver-date"],
      ["adapterDrivers", "gpu-drivers"],
      ["adapterSubsysID", "gpu-subsys-id"],
      ["adapterRAM", "gpu-ram"],
    ];

    function showGpu(id, suffix) {
      function get(prop) {
        return data[prop + suffix];
      }

      let trs = [];
      for (let [prop, key] of adapterKeys) {
        let value = get(prop);
        if (value === undefined || value === "") {
          continue;
        }
        trs.push(buildRow(key, [new Text(value)]));
      }

      if (!trs.length) {
        $("graphics-" + id + "-tbody").style.display = "none";
        return;
      }

      let active = "yes";
      if ("isGPU2Active" in data && (suffix == "2") != data.isGPU2Active) {
        active = "no";
      }

      addRow(id, "gpu-active", active);
      addRows(id, trs);
    }
    showGpu("gpu-1", "");
    showGpu("gpu-2", "2");

    // Remove adapter keys.
    for (let [prop /* key */] of adapterKeys) {
      delete data[prop];
      delete data[prop + "2"];
    }
    delete data.isGPU2Active;

    let featureLog = data.featureLog;
    delete data.featureLog;

    if (featureLog.features.length) {
      for (let feature of featureLog.features) {
        let trs = [];
        for (let entry of feature.log) {
          let bugNumber;
          if (entry.hasOwnProperty("failureId")) {
            // This is a failure ID. See nsIGfxInfo.idl.
            let m = /BUG_(\d+)/.exec(entry.failureId);
            if (m) {
              bugNumber = m[1];
            }
          }

          let failureIdSpan = $.new("span", "");
          if (bugNumber) {
            let bugHref = $.new("a");
            bugHref.href =
              "https://bugzilla.mozilla.org/show_bug.cgi?id=" + bugNumber;
            bugHref.setAttribute("data-l10n-name", "bug-link");
            failureIdSpan.append(bugHref);
            document.l10n.setAttributes(
              failureIdSpan,
              "support-blocklisted-bug",
              {
                bugNumber,
              }
            );
          } else if (
            entry.hasOwnProperty("failureId") &&
            entry.failureId.length
          ) {
            document.l10n.setAttributes(failureIdSpan, "unknown-failure", {
              failureCode: entry.failureId,
            });
          }

          let messageSpan = $.new("span", "");
          if (entry.hasOwnProperty("message") && entry.message.length) {
            messageSpan.innerText = entry.message;
          }

          let typeCol = $.new("td", entry.type);
          let statusCol = $.new("td", entry.status);
          let messageCol = $.new("td", "");
          let failureIdCol = $.new("td", "");
          typeCol.style.width = "10%";
          statusCol.style.width = "10%";
          messageCol.style.width = "30%";
          messageCol.appendChild(messageSpan);
          failureIdCol.style.width = "50%";
          failureIdCol.appendChild(failureIdSpan);

          trs.push($.new("tr", [typeCol, statusCol, messageCol, failureIdCol]));
        }
        addRow("decisions", "#" + feature.name, [$.new("table", trs)]);
      }
    } else {
      $("graphics-decisions-tbody").style.display = "none";
    }

    if (featureLog.fallbacks.length) {
      for (let fallback of featureLog.fallbacks) {
        addRow("workarounds", "#" + fallback.name, [
          new Text(fallback.message),
        ]);
      }
    } else {
      $("graphics-workarounds-tbody").style.display = "none";
    }

    let crashGuards = data.crashGuards;
    delete data.crashGuards;

    if (crashGuards.length) {
      for (let guard of crashGuards) {
        let resetButton = $.new("button");
        let onClickReset = function () {
          Services.prefs.setIntPref(guard.prefName, 0);
          resetButton.removeEventListener("click", onClickReset);
          resetButton.disabled = true;
        };

        document.l10n.setAttributes(resetButton, "reset-on-next-restart");
        resetButton.addEventListener("click", onClickReset);

        addRow("crashguards", guard.type + "CrashGuard", [resetButton]);
      }
    } else {
      $("graphics-crashguards-tbody").style.display = "none";
    }

    // Now that we're done, grab any remaining keys in data and drop them into
    // the diagnostics section.
    for (let key in data) {
      let value = data[key];
      addRow("diagnostics", key, [new Text(value)]);
    }
  },

  async media(data) {
    function insertBasicInfo(key, value) {
      function createRow(key, value) {
        let th = $.new("th", null, "column");
        document.l10n.setAttributes(th, key);
        let td = $.new("td", value);
        td.style["white-space"] = "pre-wrap";
        td.colSpan = 8;
        return $.new("tr", [th, td]);
      }
      $.append($("media-info-tbody"), [createRow(key, value)]);
    }

    function createDeviceInfoRow(device) {
      let deviceInfo = Ci.nsIAudioDeviceInfo;

      let states = {};
      states[deviceInfo.STATE_DISABLED] = "Disabled";
      states[deviceInfo.STATE_UNPLUGGED] = "Unplugged";
      states[deviceInfo.STATE_ENABLED] = "Enabled";

      let preferreds = {};
      preferreds[deviceInfo.PREF_NONE] = "None";
      preferreds[deviceInfo.PREF_MULTIMEDIA] = "Multimedia";
      preferreds[deviceInfo.PREF_VOICE] = "Voice";
      preferreds[deviceInfo.PREF_NOTIFICATION] = "Notification";
      preferreds[deviceInfo.PREF_ALL] = "All";

      let formats = {};
      formats[deviceInfo.FMT_S16LE] = "S16LE";
      formats[deviceInfo.FMT_S16BE] = "S16BE";
      formats[deviceInfo.FMT_F32LE] = "F32LE";
      formats[deviceInfo.FMT_F32BE] = "F32BE";

      function toPreferredString(preferred) {
        if (preferred == deviceInfo.PREF_NONE) {
          return preferreds[deviceInfo.PREF_NONE];
        } else if (preferred & deviceInfo.PREF_ALL) {
          return preferreds[deviceInfo.PREF_ALL];
        }
        let str = "";
        for (let pref of [
          deviceInfo.PREF_MULTIMEDIA,
          deviceInfo.PREF_VOICE,
          deviceInfo.PREF_NOTIFICATION,
        ]) {
          if (preferred & pref) {
            str += " " + preferreds[pref];
          }
        }
        return str;
      }

      function toFromatString(dev) {
        let str = "default: " + formats[dev.defaultFormat] + ", support:";
        for (let fmt of [
          deviceInfo.FMT_S16LE,
          deviceInfo.FMT_S16BE,
          deviceInfo.FMT_F32LE,
          deviceInfo.FMT_F32BE,
        ]) {
          if (dev.supportedFormat & fmt) {
            str += " " + formats[fmt];
          }
        }
        return str;
      }

      function toRateString(dev) {
        return (
          "default: " +
          dev.defaultRate +
          ", support: " +
          dev.minRate +
          " - " +
          dev.maxRate
        );
      }

      function toLatencyString(dev) {
        return dev.minLatency + " - " + dev.maxLatency;
      }

      return $.new("tr", [
        $.new("td", device.name),
        $.new("td", device.groupId),
        $.new("td", device.vendor),
        $.new("td", states[device.state]),
        $.new("td", toPreferredString(device.preferred)),
        $.new("td", toFromatString(device)),
        $.new("td", device.maxChannels),
        $.new("td", toRateString(device)),
        $.new("td", toLatencyString(device)),
      ]);
    }

    function insertDeviceInfo(side, devices) {
      let rows = [];
      for (let dev of devices) {
        rows.push(createDeviceInfoRow(dev));
      }
      $.append($("media-" + side + "-devices-tbody"), rows);
    }

    function insertEnumerateDatabase() {
      if (
        !Services.prefs.getBoolPref("media.mediacapabilities.from-database")
      ) {
        $("media-capabilities-tbody").style.display = "none";
        return;
      }
      let button = $("enumerate-database-button");
      if (button) {
        button.addEventListener("click", function () {
          let { KeyValueService } = ChromeUtils.importESModule(
            "resource://gre/modules/kvstore.sys.mjs"
          );
          let currProfDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
          currProfDir.append("mediacapabilities");
          let path = currProfDir.path;

          function enumerateDatabase(name) {
            KeyValueService.getOrCreate(path, name)
              .then(database => {
                return database.enumerate();
              })
              .then(enumerator => {
                var logs = [];
                logs.push(`${name}:`);
                while (enumerator.hasMoreElements()) {
                  const { key, value } = enumerator.getNext();
                  logs.push(`${key}: ${value}`);
                }
                $("enumerate-database-result").textContent +=
                  logs.join("\n") + "\n";
              })
              .catch(() => {
                $("enumerate-database-result").textContent += `${name}:\n`;
              });
          }

          $("enumerate-database-result").style.display = "block";
          $("enumerate-database-result").classList.remove("no-copy");
          $("enumerate-database-result").textContent = "";

          enumerateDatabase("video/av1");
          enumerateDatabase("video/vp8");
          enumerateDatabase("video/vp9");
          enumerateDatabase("video/avc");
          enumerateDatabase("video/theora");
        });
      }
    }

    function roundtripAudioLatency() {
      insertBasicInfo("roundtrip-latency", "...");
      window.windowUtils
        .defaultDevicesRoundTripLatency()
        .then(latency => {
          var latencyString = `${(latency[0] * 1000).toFixed(2)}ms (${(
            latency[1] * 1000
          ).toFixed(2)})`;
          data.defaultDevicesRoundTripLatency = latencyString;
          document.querySelector(
            'th[data-l10n-id="roundtrip-latency"]'
          ).nextSibling.textContent = latencyString;
        })
        .catch(() => {});
    }

    function createCDMInfoRow(cdmInfo) {
      function findElementInArray(array, name) {
        const rv = array.find(element => element.includes(name));
        return rv ? rv.split("=")[1] : "Unknown";
      }

      function getAudioRobustness(array) {
        return findElementInArray(array, "audio-robustness");
      }

      function getVideoRobustness(array) {
        return findElementInArray(array, "video-robustness");
      }

      function getSupportedCodecs(array) {
        const mp4Content = findElementInArray(array, "MP4");
        const webContent = findElementInArray(array, "WEBM");

        const mp4DecodingAndDecryptingCodecs = mp4Content
          .match(/decoding-and-decrypting:\[([^\]]*)\]/)[1]
          .split(",");
        const webmDecodingAndDecryptingCodecs = webContent
          .match(/decoding-and-decrypting:\[([^\]]*)\]/)[1]
          .split(",");

        const mp4DecryptingOnlyCodecs = mp4Content
          .match(/decrypting-only:\[([^\]]*)\]/)[1]
          .split(",");
        const webmDecryptingOnlyCodecs = webContent
          .match(/decrypting-only:\[([^\]]*)\]/)[1]
          .split(",");

        // Combine and get unique codecs for decoding-and-decrypting (always)
        // and decrypting-only (only set when it's not empty)
        let rv = {};
        rv.decodingAndDecrypting = [
          ...new Set(
            [
              ...mp4DecodingAndDecryptingCodecs,
              ...webmDecodingAndDecryptingCodecs,
            ].filter(Boolean)
          ),
        ];
        let temp = [
          ...new Set(
            [...mp4DecryptingOnlyCodecs, ...webmDecryptingOnlyCodecs].filter(
              Boolean
            )
          ),
        ];
        if (temp.length) {
          rv.decryptingOnly = temp;
        }
        return rv;
      }

      function getCapabilities(array) {
        let capabilities = {};
        capabilities.persistent = findElementInArray(array, "persistent");
        capabilities.distinctive = findElementInArray(array, "distinctive");
        capabilities.sessionType = findElementInArray(array, "sessionType");
        capabilities.codec = getSupportedCodecs(array);
        return JSON.stringify(capabilities);
      }

      const rvArray = cdmInfo.capabilities.split(" ");
      return $.new("tr", [
        $.new("td", cdmInfo.keySystemName),
        $.new("td", getVideoRobustness(rvArray)),
        $.new("td", getAudioRobustness(rvArray)),
        $.new("td", getCapabilities(rvArray), null, { colspan: "4" }),
        $.new("td", cdmInfo.clearlead ? "Yes" : "No"),
        $.new("td", cdmInfo.isHDCP22Compatible ? "Yes" : "No"),
      ]);
    }

    async function insertContentDecryptionModuleInfo() {
      let rows = [];
      // Retrieve information from GMPCDM
      let cdmInfo =
        await ChromeUtils.getGMPContentDecryptionModuleInformation();
      for (let info of cdmInfo) {
        rows.push(createCDMInfoRow(info));
      }
      // Retrieve information from WMFCDM, only works when MOZ_WMF_CDM is true
      if (ChromeUtils.getWMFContentDecryptionModuleInformation !== undefined) {
        cdmInfo = await ChromeUtils.getWMFContentDecryptionModuleInformation();
        for (let info of cdmInfo) {
          rows.push(createCDMInfoRow(info));
        }
      }
      $.append($("media-content-decryption-modules-tbody"), rows);
    }

    // Basic information
    insertBasicInfo("audio-backend", data.currentAudioBackend);
    insertBasicInfo("max-audio-channels", data.currentMaxAudioChannels);
    insertBasicInfo("sample-rate", data.currentPreferredSampleRate);

    if (AppConstants.platform == "macosx") {
      var micStatus = {};
      let permission = Cc["@mozilla.org/ospermissionrequest;1"].getService(
        Ci.nsIOSPermissionRequest
      );
      permission.getAudioCapturePermissionState(micStatus);
      if (micStatus.value == permission.PERMISSION_STATE_AUTHORIZED) {
        roundtripAudioLatency();
      }
    } else {
      roundtripAudioLatency();
    }

    // Output devices information
    insertDeviceInfo("output", data.audioOutputDevices);

    // Input devices information
    insertDeviceInfo("input", data.audioInputDevices);

    // Media Capabilitites
    insertEnumerateDatabase();

    // Create codec support matrix if possible
    let supportInfo = null;
    if (data.codecSupportInfo.length) {
      const [
        supportText,
        unsupportedText,
        codecNameHeaderText,
        codecSWDecodeText,
        codecHWDecodeText,
        lackOfExtensionText,
      ] = await document.l10n.formatValues([
        "media-codec-support-supported",
        "media-codec-support-unsupported",
        "media-codec-support-codec-name",
        "media-codec-support-sw-decoding",
        "media-codec-support-hw-decoding",
        "media-codec-support-lack-of-extension",
      ]);

      function formatCodecRowHeader(a, b, c) {
        let h1 = $.new("th", a);
        let h2 = $.new("th", b);
        let h3 = $.new("th", c);
        h1.classList.add("codec-table-name");
        h2.classList.add("codec-table-sw");
        h3.classList.add("codec-table-hw");
        return $.new("tr", [h1, h2, h3]);
      }

      function formatCodecRow(codec, sw, hw) {
        let swCell = $.new("td", sw ? supportText : unsupportedText);
        let hwCell = $.new("td", hw ? supportText : unsupportedText);
        if (sw) {
          swCell.classList.add("supported");
        } else {
          swCell.classList.add("unsupported");
        }
        if (hw) {
          hwCell.classList.add("supported");
        } else {
          hwCell.classList.add("unsupported");
        }
        return $.new("tr", [$.new("td", codec), swCell, hwCell]);
      }

      function formatCodecRowForLackOfExtension(codec, sw) {
        let swCell = $.new("td", sw ? supportText : unsupportedText);
        // Link to AV1 extension on MS store.
        let hwCell = $.new("td", [
          $.new("a", lackOfExtensionText, null, {
            href: "ms-windows-store://pdp/?ProductId=9MVZQVXJBQ9V",
          }),
        ]);
        if (sw) {
          swCell.classList.add("supported");
        } else {
          swCell.classList.add("unsupported");
        }
        hwCell.classList.add("lack-of-extension");
        return $.new("tr", [$.new("td", codec), swCell, hwCell]);
      }

      // Parse codec support string and create dictionary containing
      // SW/HW support information for each codec found
      let codecs = {};
      for (const codec_string of data.codecSupportInfo.split("\n")) {
        const s = codec_string.split(" ");
        const codec_name = s[0];
        const codec_support = s.slice(1);

        if (!(codec_name in codecs)) {
          codecs[codec_name] = {
            name: codec_name,
            sw: false,
            hw: false,
            lackOfExtension: false,
          };
        }

        if (codec_support.includes("SW")) {
          codecs[codec_name].sw = true;
        }
        if (codec_support.includes("HW")) {
          codecs[codec_name].hw = true;
        }
        if (codec_support.includes("LACK_OF_EXTENSION")) {
          codecs[codec_name].lackOfExtension = true;
        }
      }

      // Create row in support table for each codec
      let codecSupportRows = [];
      for (const c in codecs) {
        if (!codecs.hasOwnProperty(c)) {
          continue;
        }
        if (codecs[c].lackOfExtension) {
          codecSupportRows.push(
            formatCodecRowForLackOfExtension(codecs[c].name, codecs[c].sw)
          );
        } else {
          codecSupportRows.push(
            formatCodecRow(codecs[c].name, codecs[c].sw, codecs[c].hw)
          );
        }
      }

      let codecSupportTable = $.new("table", [
        formatCodecRowHeader(
          codecNameHeaderText,
          codecSWDecodeText,
          codecHWDecodeText
        ),
        $.new("tbody", codecSupportRows),
      ]);
      codecSupportTable.id = "codec-table";
      supportInfo = [codecSupportTable];
    } else {
      // Don't have access to codec support information
      supportInfo = await document.l10n.formatValue(
        "media-codec-support-error"
      );
    }
    if (["win", "macosx", "linux", "android"].includes(AppConstants.platform)) {
      insertBasicInfo("media-codec-support-info", supportInfo);
    }

    // CDM info
    insertContentDecryptionModuleInfo();
  },

  remoteAgent(data) {
    if (!AppConstants.ENABLE_WEBDRIVER) {
      return;
    }
    $("remote-debugging-accepting-connections").textContent = data.running;
    $("remote-debugging-url").textContent = data.url;
  },

  contentAnalysis(data) {
    $("content-analysis-active").textContent = data.active;
    if (data.active) {
      $("content-analysis-connected-to-agent").textContent = data.connected;
      $("content-analysis-agent-path").textContent = data.agentPath;
      $("content-analysis-agent-failed-signature-verification").textContent =
        data.failedSignatureVerification;
      $("content-analysis-request-count").textContent = data.requestCount;
    }
  },

  accessibility(data) {
    $("a11y-activated").textContent = data.isActive;
    $("a11y-force-disabled").textContent = data.forceDisabled || 0;

    let a11yInstantiator = $("a11y-instantiator");
    if (a11yInstantiator) {
      a11yInstantiator.textContent = data.instantiator;
    }
  },

  startupCache(data) {
    $("startup-cache-disk-cache-path").textContent = data.DiskCachePath;
    $("startup-cache-ignore-disk-cache").textContent = data.IgnoreDiskCache;
    $("startup-cache-found-disk-cache-on-init").textContent =
      data.FoundDiskCacheOnInit;
    $("startup-cache-wrote-to-disk-cache").textContent = data.WroteToDiskCache;
  },

  libraryVersions(data) {
    let trs = [
      $.new("tr", [
        $.new("th", ""),
        $.new("th", null, null, { "data-l10n-id": "min-lib-versions" }),
        $.new("th", null, null, { "data-l10n-id": "loaded-lib-versions" }),
      ]),
    ];
    sortedArrayFromObject(data).forEach(function ([name, val]) {
      trs.push(
        $.new("tr", [
          $.new("td", name),
          $.new("td", val.minVersion),
          $.new("td", val.version),
        ])
      );
    });
    $.append($("libversions-tbody"), trs);
  },

  userJS(data) {
    if (!data.exists) {
      return;
    }
    let userJSFile = Services.dirsvc.get("PrefD", Ci.nsIFile);
    userJSFile.append("user.js");
    $("prefs-user-js-link").href = Services.io.newFileURI(userJSFile).spec;
    $("prefs-user-js-section").style.display = "";
    // Clear the no-copy class
    $("prefs-user-js-section").className = "";
  },

  sandbox(data) {
    if (!AppConstants.MOZ_SANDBOX) {
      return;
    }

    let tbody = $("sandbox-tbody");
    for (let key in data) {
      // Simplify the display a little in the common case.
      if (
        key === "hasPrivilegedUserNamespaces" &&
        data[key] === data.hasUserNamespaces
      ) {
        continue;
      }
      if (key === "syscallLog") {
        // Not in this table.
        continue;
      }
      let keyStrId = toFluentID(key);
      let th = $.new("th", null, "column");
      document.l10n.setAttributes(th, keyStrId);
      let td = $.new("td", data[key]);
      // Warning not applicable to Flatpak (see Bug 1882881), Snap or
      // any "Packaged App" (eg. Debian package)
      const isPackagedApp = Services.sysinfo.getPropertyAsBool("isPackagedApp");
      if (key === "hasUserNamespaces" && !data[key] && !isPackagedApp) {
        td = $.new("td", "");
        td.classList.add("feature-unavailable");
        let span = document.createElement("span");
        document.l10n.setAttributes(
          span,
          "support-user-namespaces-unavailable",
          {
            status: data[key],
          }
        );
        let supportLink = document.createElement("a", {
          is: "moz-support-link",
        });
        supportLink.classList.add("user-namespaces-unavailabe-support-link");
        supportLink.setAttribute(
          "support-page",
          "install-firefox-linux#w_install-firefox-from-mozilla-builds"
        );
        td.appendChild(span);
        td.appendChild(supportLink);
      }
      tbody.appendChild($.new("tr", [th, td]));
    }

    if ("syscallLog" in data) {
      let syscallBody = $("sandbox-syscalls-tbody");
      let argsHead = $("sandbox-syscalls-argshead");
      for (let syscall of data.syscallLog) {
        if (argsHead.colSpan < syscall.args.length) {
          argsHead.colSpan = syscall.args.length;
        }
        let procTypeStrId = toFluentID(syscall.procType);
        let cells = [
          $.new("td", syscall.index, "integer"),
          $.new("td", syscall.msecAgo / 1000),
          $.new("td", syscall.pid, "integer"),
          $.new("td", syscall.tid, "integer"),
          $.new("td", null, null, {
            "data-l10n-id": "sandbox-proc-type-" + procTypeStrId,
          }),
          $.new("td", syscall.syscall, "integer"),
        ];
        for (let arg of syscall.args) {
          cells.push($.new("td", arg, "integer"));
        }
        syscallBody.appendChild($.new("tr", cells));
      }
    }
  },

  intl(data) {
    $("intl-locale-requested").textContent = JSON.stringify(
      data.localeService.requested
    );
    $("intl-locale-available").textContent = JSON.stringify(
      data.localeService.available
    );
    $("intl-locale-supported").textContent = JSON.stringify(
      data.localeService.supported
    );
    $("intl-locale-regionalprefs").textContent = JSON.stringify(
      data.localeService.regionalPrefs
    );
    $("intl-locale-default").textContent = JSON.stringify(
      data.localeService.defaultLocale
    );

    $("intl-osprefs-systemlocales").textContent = JSON.stringify(
      data.osPrefs.systemLocales
    );
    $("intl-osprefs-regionalprefs").textContent = JSON.stringify(
      data.osPrefs.regionalPrefsLocales
    );
  },

  remoteSettings(data) {
    if (!data) {
      return;
    }
    const { isSynchronizationBroken, lastCheck, localTimestamp, history } =
      data;

    $("support-remote-settings-status-ok").style.display =
      isSynchronizationBroken ? "none" : "block";
    $("support-remote-settings-status-broken").style.display =
      isSynchronizationBroken ? "block" : "none";
    $("support-remote-settings-last-check").textContent = lastCheck;
    $("support-remote-settings-local-timestamp").textContent = localTimestamp;
    $.append(
      $("support-remote-settings-sync-history-tbody"),
      history["settings-sync"].map(({ status, datetime, infos }) =>
        $.new("tr", [
          $.new("td", [document.createTextNode(status)]),
          $.new("td", [document.createTextNode(datetime)]),
          $.new("td", [document.createTextNode(JSON.stringify(infos))]),
        ])
      )
    );
  },

  normandy(data) {
    if (!data) {
      return;
    }

    const {
      prefStudies,
      addonStudies,
      prefRollouts,
      nimbusExperiments,
      nimbusRollouts,
    } = data;
    $.append(
      $("remote-features-tbody"),
      prefRollouts.map(({ slug, state }) =>
        $.new("tr", [
          $.new("td", [document.createTextNode(slug)]),
          $.new("td", [document.createTextNode(state)]),
        ])
      )
    );

    $.append(
      $("remote-features-tbody"),
      nimbusRollouts.map(({ userFacingName, branch }) =>
        $.new("tr", [
          $.new("td", [document.createTextNode(userFacingName)]),
          $.new("td", [document.createTextNode(`(${branch.slug})`)]),
        ])
      )
    );
    $.append(
      $("remote-experiments-tbody"),
      [addonStudies, prefStudies, nimbusExperiments]
        .flat()
        .map(({ userFacingName, branch }) =>
          $.new("tr", [
            $.new("td", [document.createTextNode(userFacingName)]),
            $.new("td", [document.createTextNode(branch?.slug || branch)]),
          ])
        )
    );
  },
};

var $ = document.getElementById.bind(document);

$.new = function $_new(tag, textContentOrChildren, className, attributes) {
  let elt = document.createElement(tag);
  if (className) {
    elt.className = className;
  }
  if (attributes) {
    if (attributes["data-l10n-id"]) {
      let args = attributes.hasOwnProperty("data-l10n-args")
        ? attributes["data-l10n-args"]
        : undefined;
      document.l10n.setAttributes(elt, attributes["data-l10n-id"], args);
      delete attributes["data-l10n-id"];
      if (args) {
        delete attributes["data-l10n-args"];
      }
    }

    for (let attrName in attributes) {
      elt.setAttribute(attrName, attributes[attrName]);
    }
  }
  if (Array.isArray(textContentOrChildren)) {
    this.append(elt, textContentOrChildren);
  } else if (!attributes || !attributes["data-l10n-id"]) {
    elt.textContent = String(textContentOrChildren);
  }
  return elt;
};

$.append = function $_append(parent, children) {
  children.forEach(c => parent.appendChild(c));
};

function assembleFromGraphicsFailure(i, data) {
  // Only cover the cases we have today; for example, we do not have
  // log failures that assert and we assume the log level is 1/error.
  let message = data.failures[i];
  let index = data.indices[i];
  let what = "";
  if (message.search(/\[GFX1-\]: \(LF\)/) == 0) {
    // Non-asserting log failure - the message is substring(14)
    what = "LogFailure";
    message = message.substring(14);
  } else if (message.search(/\[GFX1-\]: /) == 0) {
    // Non-asserting - the message is substring(9)
    what = "Error";
    message = message.substring(9);
  } else if (message.search(/\[GFX1\]: /) == 0) {
    // Asserting - the message is substring(8)
    what = "Assert";
    message = message.substring(8);
  }
  let assembled = {
    index,
    header: "(#" + index + ") " + what,
    message,
  };
  return assembled;
}

function sortedArrayFromObject(obj) {
  let tuples = [];
  for (let prop in obj) {
    tuples.push([prop, obj[prop]]);
  }
  tuples.sort(([prop1], [prop2]) => prop1.localeCompare(prop2));
  return tuples;
}

function copyRawDataToClipboard(button) {
  if (button) {
    button.disabled = true;
  }
  Troubleshoot.snapshot().then(
    async snapshot => {
      if (button) {
        button.disabled = false;
      }
      let str = Cc["@mozilla.org/supports-string;1"].createInstance(
        Ci.nsISupportsString
      );
      str.data = JSON.stringify(snapshot, undefined, 2);
      let transferable = Cc[
        "@mozilla.org/widget/transferable;1"
      ].createInstance(Ci.nsITransferable);
      transferable.init(getLoadContext());
      transferable.addDataFlavor("text/plain");
      transferable.setTransferData("text/plain", str);
      Services.clipboard.setData(
        transferable,
        null,
        Ci.nsIClipboard.kGlobalClipboard
      );
    },
    err => {
      if (button) {
        button.disabled = false;
      }
      console.error(err);
    }
  );
}

function getLoadContext() {
  return window.docShell.QueryInterface(Ci.nsILoadContext);
}

async function copyContentsToClipboard() {
  // Get the HTML and text representations for the important part of the page.
  let contentsDiv = $("contents").cloneNode(true);
  // Remove the items we don't want to copy from the clone:
  contentsDiv.querySelectorAll(".no-copy, [hidden]").forEach(n => n.remove());
  let dataHtml = contentsDiv.innerHTML;
  let dataText = createTextForElement(contentsDiv);

  // We can't use plain strings, we have to use nsSupportsString.
  let supportsStringClass = Cc["@mozilla.org/supports-string;1"];
  let ssHtml = supportsStringClass.createInstance(Ci.nsISupportsString);
  let ssText = supportsStringClass.createInstance(Ci.nsISupportsString);

  let transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(
    Ci.nsITransferable
  );
  transferable.init(getLoadContext());

  // Add the HTML flavor.
  transferable.addDataFlavor("text/html");
  ssHtml.data = dataHtml;
  transferable.setTransferData("text/html", ssHtml);

  // Add the plain text flavor.
  transferable.addDataFlavor("text/plain");
  ssText.data = dataText;
  transferable.setTransferData("text/plain", ssText);

  // Store the data into the clipboard.
  Services.clipboard.setData(
    transferable,
    null,
    Services.clipboard.kGlobalClipboard
  );
}

// Return the plain text representation of an element.  Do a little bit
// of pretty-printing to make it human-readable.
function createTextForElement(elem) {
  let serializer = new Serializer();
  let text = serializer.serialize(elem);

  // Actual CR/LF pairs are needed for some Windows text editors.
  if (AppConstants.platform == "win") {
    text = text.replace(/\n/g, "\r\n");
  }

  return text;
}

function Serializer() {}

Serializer.prototype = {
  serialize(rootElem) {
    this._lines = [];
    this._startNewLine();
    this._serializeElement(rootElem);
    this._startNewLine();
    return this._lines.join("\n").trim() + "\n";
  },

  // The current line is always the line that writing will start at next.  When
  // an element is serialized, the current line is updated to be the line at
  // which the next element should be written.
  get _currentLine() {
    return this._lines.length ? this._lines[this._lines.length - 1] : null;
  },

  set _currentLine(val) {
    this._lines[this._lines.length - 1] = val;
  },

  _serializeElement(elem) {
    // table
    if (elem.localName == "table") {
      this._serializeTable(elem);
      return;
    }

    // all other elements

    let hasText = false;
    for (let child of elem.childNodes) {
      if (child.nodeType == Node.TEXT_NODE) {
        let text = this._nodeText(child);
        this._appendText(text);
        hasText = hasText || !!text.trim();
      } else if (child.nodeType == Node.ELEMENT_NODE) {
        this._serializeElement(child);
      }
    }

    // For headings, draw a "line" underneath them so they stand out.
    let isHeader = /^h[0-9]+$/.test(elem.localName);
    if (isHeader) {
      let headerText = (this._currentLine || "").trim();
      if (headerText) {
        this._startNewLine();
        this._appendText("-".repeat(headerText.length));
      }
    }

    // Add a blank line underneath elements but only if they contain text.
    if (hasText && (isHeader || "p" == elem.localName)) {
      this._startNewLine();
      this._startNewLine();
    }
  },

  _startNewLine() {
    let currLine = this._currentLine;
    if (currLine) {
      // The current line is not empty.  Trim it.
      this._currentLine = currLine.trim();
      if (!this._currentLine) {
        // The current line became empty.  Discard it.
        this._lines.pop();
      }
    }
    this._lines.push("");
  },

  _appendText(text) {
    this._currentLine += text;
  },

  _isHiddenSubHeading(th) {
    return th.parentNode.parentNode.style.display == "none";
  },

  _serializeTable(table) {
    // Collect the table's column headings if in fact there are any.  First
    // check thead.  If there's no thead, check the first tr.
    let colHeadings = {};
    let tableHeadingElem = table.querySelector("thead");
    if (!tableHeadingElem) {
      tableHeadingElem = table.querySelector("tr");
    }
    if (tableHeadingElem) {
      let tableHeadingCols = tableHeadingElem.querySelectorAll("th,td");
      // If there's a contiguous run of th's in the children starting from the
      // rightmost child, then consider them to be column headings.
      for (let i = tableHeadingCols.length - 1; i >= 0; i--) {
        let col = tableHeadingCols[i];
        if (col.localName != "th" || col.classList.contains("title-column")) {
          break;
        }
        colHeadings[i] = this._nodeText(col).trim();
      }
    }
    let hasColHeadings = !!Object.keys(colHeadings).length;
    if (!hasColHeadings) {
      tableHeadingElem = null;
    }

    let trs = table.querySelectorAll("table > tr, tbody > tr");
    let startRow =
      tableHeadingElem && tableHeadingElem.localName == "tr" ? 1 : 0;

    if (startRow >= trs.length) {
      // The table's empty.
      return;
    }

    if (hasColHeadings) {
      // Use column headings.  Print each tr as a multi-line chunk like:
      //   Heading 1: Column 1 value
      //   Heading 2: Column 2 value
      for (let i = startRow; i < trs.length; i++) {
        let children = trs[i].querySelectorAll("td");
        for (let j = 0; j < children.length; j++) {
          let text = "";
          if (colHeadings[j]) {
            text += colHeadings[j] + ": ";
          }
          text += this._nodeText(children[j]).trim();
          this._appendText(text);
          this._startNewLine();
        }
        this._startNewLine();
      }
      return;
    }

    // Don't use column headings.  Assume the table has only two columns and
    // print each tr in a single line like:
    //   Column 1 value: Column 2 value
    for (let i = startRow; i < trs.length; i++) {
      let children = trs[i].querySelectorAll("th,td");
      let rowHeading = this._nodeText(children[0]).trim();
      if (children[0].classList.contains("title-column")) {
        if (!this._isHiddenSubHeading(children[0])) {
          this._appendText(rowHeading);
        }
      } else if (children.length == 1) {
        // This is a single-cell row.
        this._appendText(rowHeading);
      } else {
        let childTables = trs[i].querySelectorAll("table");
        if (childTables.length) {
          // If we have child tables, don't use nodeText - its trs are already
          // queued up from querySelectorAll earlier.
          this._appendText(rowHeading + ": ");
        } else {
          this._appendText(rowHeading + ": ");
          for (let k = 1; k < children.length; k++) {
            let l = this._nodeText(children[k]).trim();
            if (l == "") {
              continue;
            }
            if (k < children.length - 1) {
              l += ", ";
            }
            this._appendText(l);
          }
        }
      }
      this._startNewLine();
    }
    this._startNewLine();
  },

  _nodeText(node) {
    return node.textContent.replace(/\s+/g, " ");
  },
};

function openProfileDirectory() {
  // Get the profile directory.
  let currProfD = Services.dirsvc.get("ProfD", Ci.nsIFile);
  let profileDir = currProfD.path;

  // Show the profile directory.
  let nsLocalFile = Components.Constructor(
    "@mozilla.org/file/local;1",
    "nsIFile",
    "initWithPath"
  );
  new nsLocalFile(profileDir).reveal();
}

/**
 * Profile reset is only supported for the default profile if the appropriate migrator exists.
 */
function populateActionBox() {
  if (ResetProfile.resetSupported()) {
    $("reset-box").style.display = "block";
  }
  if (!Services.appinfo.inSafeMode && AppConstants.platform !== "android") {
    $("safe-mode-box").style.display = "block";

    if (Services.policies && !Services.policies.isAllowed("safeMode")) {
      $("restart-in-safe-mode-button").setAttribute("disabled", "true");
    }
  }
}

// Prompt user to restart the browser in safe mode
function safeModeRestart() {
  let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
    Ci.nsISupportsPRBool
  );
  Services.obs.notifyObservers(
    cancelQuit,
    "quit-application-requested",
    "restart"
  );

  if (!cancelQuit.data) {
    Services.startup.restartInSafeMode(Ci.nsIAppStartup.eAttemptQuit);
  }
}
/**
 * Set up event listeners for buttons.
 */
function setupEventListeners() {
  let button = $("reset-box-button");
  if (button) {
    button.addEventListener("click", function () {
      ResetProfile.openConfirmationDialog(window);
    });
  }
  button = $("clear-startup-cache-button");
  if (button) {
    button.addEventListener("click", async function () {
      const [promptTitle, promptBody, restartButtonLabel] =
        await document.l10n.formatValues([
          { id: "startup-cache-dialog-title2" },
          { id: "startup-cache-dialog-body2" },
          { id: "restart-button-label" },
        ]);
      const buttonFlags =
        Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
        Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL +
        Services.prompt.BUTTON_POS_0_DEFAULT;
      const result = Services.prompt.confirmEx(
        window.docShell.chromeEventHandler.ownerGlobal,
        promptTitle,
        promptBody,
        buttonFlags,
        restartButtonLabel,
        null,
        null,
        null,
        {}
      );
      if (result !== 0) {
        return;
      }
      Services.appinfo.invalidateCachesOnRestart();
      Services.startup.quit(
        Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit
      );
    });
  }
  button = $("restart-in-safe-mode-button");
  if (button) {
    button.addEventListener("click", function () {
      if (
        Services.obs
          .enumerateObservers("restart-in-safe-mode")
          .hasMoreElements()
      ) {
        Services.obs.notifyObservers(
          window.docShell.chromeEventHandler.ownerGlobal,
          "restart-in-safe-mode"
        );
      } else {
        safeModeRestart();
      }
    });
  }
  if (AppConstants.MOZ_UPDATER) {
    button = $("update-dir-button");
    if (button) {
      button.addEventListener("click", function () {
        // Get the update directory.
        let updateDir = Services.dirsvc.get("UpdRootD", Ci.nsIFile);
        if (!updateDir.exists()) {
          updateDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
        }
        let updateDirPath = updateDir.path;
        // Show the update directory.
        let nsLocalFile = Components.Constructor(
          "@mozilla.org/file/local;1",
          "nsIFile",
          "initWithPath"
        );
        new nsLocalFile(updateDirPath).reveal();
      });
    }
    button = $("show-update-history-button");
    if (button) {
      button.addEventListener("click", function () {
        window.browsingContext.topChromeWindow.openDialog(
          "chrome://mozapps/content/update/history.xhtml",
          "Update:History",
          "centerscreen,resizable=no,titlebar,modal"
        );
      });
    }
  }
  button = $("verify-place-integrity-button");
  if (button) {
    button.addEventListener("click", function () {
      PlacesDBUtils.checkAndFixDatabase().then(tasksStatusMap => {
        let logs = [];
        for (let [key, value] of tasksStatusMap) {
          logs.push(`> Task: ${key}`);
          let prefix = value.succeeded ? "+ " : "- ";
          logs = logs.concat(value.logs.map(m => `${prefix}${m}`));
        }
        $("verify-place-result").style.display = "block";
        $("verify-place-result").classList.remove("no-copy");
        $("verify-place-result").textContent = logs.join("\n");
      });
    });
  }

  $("copy-raw-data-to-clipboard").addEventListener("click", function () {
    copyRawDataToClipboard(this);
  });
  $("copy-to-clipboard").addEventListener("click", function () {
    copyContentsToClipboard();
  });
  $("profile-dir-button").addEventListener("click", function () {
    openProfileDirectory();
  });
}

/**
 * Scroll to section specified by location.hash
 */
function scrollToSection() {
  const id = location.hash.substr(1);
  const elem = $(id);

  if (elem) {
    elem.scrollIntoView();
  }
}
PK
!<�7��!l!l0chrome/toolkit/content/global/aboutSupport.xhtml<?xml version="1.0" encoding="UTF-8"?>


<!DOCTYPE html [
  <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> %htmlDTD;
]>

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'" />
    <meta name="color-scheme" content="light dark" />
    <title data-l10n-id="page-title"/>

    <link rel="icon" type="image/png" id="favicon"
          href="chrome://branding/content/icon32.png"/>
    <link rel="stylesheet" href="chrome://global/skin/aboutSupport.css"
          type="text/css"/>

    <script src="chrome://global/content/aboutSupport.js"/>
    <script
      type="module"
      src="chrome://global/content/elements/moz-support-link.mjs"
    />
    <link rel="localization" href="branding/brand.ftl"/>
    <link rel="localization" href="toolkit/about/aboutSupport.ftl"/>
    <link rel="localization" href="toolkit/global/resetProfile.ftl"/>
    <link rel="localization" href="toolkit/global/processTypes.ftl"/>
    <link rel="localization" href="toolkit/featuregates/features.ftl"/>
  </head>

  <body class="wide-container">
    <h1 data-l10n-id="page-title"/>
    <div class="header-flex">
      <div class="content-flex">
        <div class="page-subtitle" data-l10n-id="page-subtitle">
          <a id="supportLink" data-l10n-name="support-link"></a>
        </div>
        <div>
          <button id="copy-raw-data-to-clipboard" data-l10n-id="copy-raw-data-to-clipboard-label"/>
          <button id="copy-to-clipboard" data-l10n-id="copy-text-to-clipboard-label"/>
        </div>
      </div>

      <div class="action-box">
        <div id="reset-box">
          <h3 data-l10n-id="refresh-profile"/>
          <button id="reset-box-button" data-l10n-id="refresh-profile-button"/>
        </div>
        <div id="safe-mode-box">
          <h3 data-l10n-id="troubleshoot-mode-title"/>
          <button id="restart-in-safe-mode-button" data-l10n-id="restart-in-troubleshoot-mode-label"/>
        </div>
        <div id="clear-startup-cache-box">
          <h3 data-l10n-id="clear-startup-cache-title"/>
          <button id="clear-startup-cache-button" data-l10n-id="clear-startup-cache-label"/>
        </div>
      </div>
    </div>
    <div id="contents">

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="app-basics" data-l10n-id="app-basics-title"/>

      <table>
        <tbody>
          <tr>
            <th class="column" data-l10n-id="app-basics-name"/>

            <td id="application-box">
            </td>
          </tr>

          <tr>
            <th class="column" data-l10n-id="app-basics-version"/>

            <td id="version-box">
            </td>
          </tr>

          <tr>
            <th class="column" data-l10n-id="app-basics-build-id"/>
            <td id="buildid-box"></td>
          </tr>

          <tr>
            <th class="column" data-l10n-id="app-basics-distribution-id"/>
            <td id="distributionid-box"></td>
          </tr>



          <tr>
            <th class="column" data-l10n-id="app-basics-user-agent"/>

            <td id="useragent-box">
            </td>
          </tr>

          <tr>
            <th class="column" data-l10n-id="app-basics-os"/>

            <td id="os-box">
            </td>
          </tr>

          <tr id="os-theme-row">
            <th class="column" data-l10n-id="app-basics-os-theme"/>

            <td id="os-theme-box">
            </td>
          </tr>


          <tr class="no-copy">
            <th class="column" data-l10n-id="app-basics-binary"/>

            <td id="binary-box" dir="ltr">
            </td>
          </tr>

          <tr id="profile-row" class="no-copy">
            <th class="column" data-l10n-id="app-basics-profile-dir"/>

            <td>
              <button id="profile-dir-button" data-l10n-id="show-dir-label"/>
               <span id="profile-dir-box" dir="ltr">
               </span>
            </td>
          </tr>

          <tr class="no-copy">
            <th class="column" data-l10n-id="app-basics-build-config"/>

            <td>
              <a href="about:buildconfig">about:buildconfig</a>
            </td>
          </tr>

          <tr class="no-copy">
            <th class="column" data-l10n-id="app-basics-memory-use"/>

            <td>
              <a href="about:memory">about:memory</a>
            </td>
          </tr>

          <tr class="no-copy">
            <th class="column" data-l10n-id="app-basics-performance"/>

            <td>
              <a href="about:processes">about:processes</a>
            </td>
          </tr>

          <tr class="no-copy">
            <th class="column" data-l10n-id="app-basics-service-workers"/>

            <td>
              <a href="about:serviceworkers">about:serviceworkers</a>
            </td>
          </tr>



          <tr>
            <th class="column" data-l10n-id="app-basics-multi-process-support"/>

            <td id="multiprocess-box">
              <span id="multiprocess-box-process-count"/>
            </td>
          </tr>

          <tr>
            <th class="column" data-l10n-id="app-basics-fission-support"/>

            <td id="fission-box">
              <span id="fission-box-process-count"/>
              <span id="fission-box-status"/>
            </td>
          </tr>

          <tr id="remoteprocesses-row">
            <th class="column" data-l10n-id="app-basics-remote-processes-count"/>

            <td>
              <a href="#remote-processes"></a>
            </td>
          </tr>

          <tr id="policies-status-row">
            <th class="column" data-l10n-id="app-basics-enterprise-policies"/>

            <td id="policies-status">
            </td>
          </tr>

          <tr>
            <th class="column" data-l10n-id="app-basics-location-service-key-google"/>

            <td id="key-location-service-google-box">
            </td>
          </tr>

          <tr>
            <th class="column" data-l10n-id="app-basics-safebrowsing-key-google"/>

            <td id="key-safebrowsing-google-box">
            </td>
          </tr>

          <tr>
            <th class="column" data-l10n-id="app-basics-key-mozilla"/>

            <td id="key-mozilla-box">
            </td>
          </tr>

          <tr>
            <th class="column" data-l10n-id="app-basics-safe-mode"/>

            <td id="safemode-box">
            </td>
          </tr>

          <tr>
            <th class="column" data-l10n-id="app-basics-memory-size"/>

            <td id="memory-size-box">
            </td>
          </tr>

          <tr>
            <th class="column" data-l10n-id="app-basics-disk-available"/>

            <td id="disk-available-box">
            </td>
          </tr>

          <tr class="no-copy">
            <th class="column" data-l10n-id="app-basics-profiles"/>

            <td>
              <a href="about:profiles">about:profiles</a>
            </td>
          </tr>


        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="crashes" data-l10n-id="crashes-title"/>

      <table id="crashes-table">
        <thead>
          <tr>
            <th data-l10n-id="crashes-id"/>
            <th data-l10n-id="crashes-send-date"/>
          </tr>
        </thead>
        <tbody id="crashes-tbody">
        </tbody>
      </table>
      <p id="crashes-allReports" class="hidden no-copy">
        <a href="about:crashes" id="crashes-allReportsWithPending"
           class="block" data-l10n-id="crashes-all-reports"/>
      </p>
      <p id="crashes-noConfig" class="hidden no-copy" data-l10n-id="crashes-no-config"/>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="features" data-l10n-id="features-title"/>

      <table id="features-table">
        <thead>
          <tr>
            <th data-l10n-id="features-name"/>
            <th data-l10n-id="features-version"/>
            <th data-l10n-id="features-id"/>
          </tr>
        </thead>
        <tbody id="features-tbody">
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="remote-features" data-l10n-id="support-remote-features-title"/>

      <table class="prefs-table">
        <thead class="no-copy">
          <th data-l10n-id="support-remote-features-name"/>
          <th data-l10n-id="support-remote-features-status"/>
        </thead>

        <tbody id="remote-features-tbody">
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" data-l10n-id="processes-title" id="remote-processes"/>

      <table id="remote-processes-table">
        <thead>
          <tr>
            <th data-l10n-id="processes-type"/>
            <th data-l10n-id="processes-count"/>
          </tr>
        </thead>
        <tbody id="processes-tbody">
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="addons" data-l10n-id="support-addons-title"/>

      <table>
        <thead>
          <tr>
            <th data-l10n-id="support-addons-name"/>
            <th data-l10n-id="support-addons-type"/>
            <th data-l10n-id="support-addons-version"/>
            <th data-l10n-id="support-addons-enabled"/>
            <th data-l10n-id="support-addons-id"/>
          </tr>
        </thead>
        <tbody id="addons-tbody">
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="security-software" data-l10n-id="security-software-title"/>

      <table id="security-software-table">
        <thead>
          <tr>
            <th data-l10n-id="security-software-type"/>
            <th data-l10n-id="security-software-name"/>
          </tr>
        </thead>
        <tbody>
          <tr>
            <th class="column" data-l10n-id="security-software-antivirus"/>

            <td id="security-software-antivirus">
            </td>
          </tr>

          <tr>
            <th class="column" data-l10n-id="security-software-antispyware"/>

            <td id="security-software-antispyware">
            </td>
          </tr>

          <tr>
            <th class="column" data-l10n-id="security-software-firewall"/>

            <td id="security-software-firewall">
            </td>
          </tr>
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="legacyUserStylesheets" data-l10n-id="legacy-user-stylesheets-title"/>

      <table>
        <tbody id="legacyUserStylesheets-info-tbody">
          <tr>
            <th class="column" data-l10n-id="legacy-user-stylesheets-enabled"/>

            <td id="legacyUserStylesheets-enabled">
            </td>
          </tr>

          <tr>
            <th class="column" data-l10n-id="legacy-user-stylesheets-stylesheet-types"/>

            <td id="legacyUserStylesheets-types">
            </td>
          </tr>
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="graphics" data-l10n-id="graphics-title"/>

      <table>
        <tbody id="graphics-features-tbody">
          <tr>
            <th colspan="2" class="title-column" data-l10n-id="graphics-features-title"/>
          </tr>
        </tbody>

        <tbody id="graphics-gpu-1-tbody">
          <tr>
            <th colspan="2" class="title-column" data-l10n-id="graphics-gpu1-title"/>
          </tr>
        </tbody>

        <tbody id="graphics-gpu-2-tbody">
          <tr>
            <th colspan="2" class="title-column" data-l10n-id="graphics-gpu2-title"/>
          </tr>
        </tbody>

        <tbody id="graphics-diagnostics-tbody">
          <tr>
            <th colspan="2" class="title-column" data-l10n-id="graphics-diagnostics-title"/>
          </tr>
        </tbody>

        <tbody id="graphics-decisions-tbody">
          <tr>
            <th colspan="2" class="title-column" data-l10n-id="graphics-decision-log-title"/>
          </tr>
        </tbody>

        <tbody id="graphics-crashguards-tbody">
          <tr>
            <th colspan="2" class="title-column" data-l10n-id="graphics-crash-guards-title"/>
          </tr>
        </tbody>

        <tbody id="graphics-workarounds-tbody">
          <tr>
            <th colspan="2" class="title-column" data-l10n-id="graphics-workarounds-title"/>
          </tr>
        </tbody>

        <tbody id="graphics-failures-tbody">
          <tr>
            <th colspan="2" class="title-column" data-l10n-id="graphics-failure-log-title"/>
          </tr>
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="media" data-l10n-id="media-title"/>
      <table>
        <tbody id="media-info-tbody">
        </tbody>

        <tbody id="media-output-devices-tbody">
          <tr>
            <th colspan="9" class="title-column" data-l10n-id="media-output-devices-title"/>
          </tr>
          <tr>
            <th data-l10n-id="media-device-name"/>
            <th data-l10n-id="media-device-group"/>
            <th data-l10n-id="media-device-vendor"/>
            <th data-l10n-id="media-device-state"/>
            <th data-l10n-id="media-device-preferred"/>
            <th data-l10n-id="media-device-format"/>
            <th data-l10n-id="media-device-channels"/>
            <th data-l10n-id="media-device-rate"/>
            <th data-l10n-id="media-device-latency"/>
          </tr>
        </tbody>

        <tbody id="media-input-devices-tbody">
          <tr>
            <th colspan="9" class="title-column" data-l10n-id="media-input-devices-title"/>
          </tr>
          <tr>
            <th data-l10n-id="media-device-name"/>
            <th data-l10n-id="media-device-group"/>
            <th data-l10n-id="media-device-vendor"/>
            <th data-l10n-id="media-device-state"/>
            <th data-l10n-id="media-device-preferred"/>
            <th data-l10n-id="media-device-format"/>
            <th data-l10n-id="media-device-channels"/>
            <th data-l10n-id="media-device-rate"/>
            <th data-l10n-id="media-device-latency"/>
          </tr>
        </tbody>

        <tbody id="media-capabilities-tbody">
          <tr>
            <th colspan="9" class="title-column" data-l10n-id="media-capabilities-title"/>
          </tr>
          <tr>
            <td colspan="9">
              <button id="enumerate-database-button" data-l10n-id="media-capabilities-enumerate"/>
              <pre id="enumerate-database-result" class="hidden no-copy"></pre>
            </td>
          </tr>
        </tbody>

        <tbody id="media-content-decryption-modules-tbody">
          <tr>
            <th colspan="9" class="title-column" data-l10n-id="media-content-decryption-modules-title"/>
          </tr>
          <tr>
            <th data-l10n-id="media-key-system-name"/>
            <th data-l10n-id="media-video-robustness"/>
            <th data-l10n-id="media-audio-robustness"/>
            <th colspan="4" data-l10n-id="media-cdm-capabilities"/>
            <th data-l10n-id="media-cdm-clear-lead"/>
            <th data-l10n-id="media-hdcp-22-compatible"/>
          </tr>
        </tbody>

      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="environment-variables" data-l10n-id="environment-variables-title"/>

      <table class="prefs-table">
        <thead class="no-copy">
          <th class="name" data-l10n-id="environment-variables-name"/>

          <th class="value" data-l10n-id="environment-variables-value"/>
        </thead>

        <tbody id="environment-variables-tbody">
        </tbody>
      </table>


      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="experimental-features" data-l10n-id="experimental-features-title"/>

      <table class="prefs-table">
        <thead class="no-copy">
          <th class="name" data-l10n-id="experimental-features-name"/>

          <th class="value" data-l10n-id="experimental-features-value"/>
        </thead>

        <tbody id="experimental-features-tbody">
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="remote-settings" data-l10n-id="support-remote-settings-title"/>

      <table>
        <tbody>
          <tr>
            <th class="column" data-l10n-id="support-remote-settings-status"/>

            <td>
              <span id="support-remote-settings-status-ok" data-l10n-id="support-remote-settings-status-ok"/>
              <span id="support-remote-settings-status-broken" data-l10n-id="support-remote-settings-status-broken"/>
            </td>
          </tr>
          <tr>
            <th class="column" data-l10n-id="support-remote-settings-last-check"/>

            <td id="support-remote-settings-last-check">
            </td>
          </tr>
          <tr>
            <th class="column" data-l10n-id="support-remote-settings-local-timestamp"/>

            <td id="support-remote-settings-local-timestamp">
            </td>
          </tr>
          <tr>
            <th class="column" data-l10n-id="support-remote-settings-sync-history"/>

            <td>
              <table>
                <thead>
                  <tr>
                    <th data-l10n-id="support-remote-settings-sync-history-status"/>
                    <th data-l10n-id="support-remote-settings-sync-history-datetime"/>
                    <th data-l10n-id="support-remote-settings-sync-history-infos"/>
                  </tr>
                </thead>
                <tbody id="support-remote-settings-sync-history-tbody">
                </tbody>
              </table>
            </td>
          </tr>
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->
      <h2 class="major-section" id="remote-experiments" data-l10n-id="support-remote-experiments-title"/>

      <table class="prefs-table">
        <thead class="no-copy">
          <th data-l10n-id="support-remote-experiments-name"/>
          <th data-l10n-id="support-remote-experiments-branch"/>
        </thead>

        <tbody id="remote-experiments-tbody">
        </tbody>
      </table>

      <section id="about-studies-section" class="no-copy">
        <p data-l10n-id="support-remote-experiments-see-about-studies">
          <a data-l10n-name="support-about-studies-link" href="about:studies"></a>
        </p>
      </section>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="modified-key-prefs" data-l10n-id="modified-key-prefs-title"/>

      <table class="prefs-table">
        <thead class="no-copy">
          <th class="name" data-l10n-id="modified-prefs-name"/>

          <th class="value" data-l10n-id="modified-prefs-value"/>
        </thead>

        <tbody id="prefs-tbody">
        </tbody>
      </table>

      <section id="prefs-user-js-section" class="hidden no-copy">
        <h3 data-l10n-id="user-js-title"/>
        <p data-l10n-id="user-js-description">
          <a id="prefs-user-js-link" data-l10n-name="user-js-link"></a>
        </p>
      </section>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="locked-key-prefs" data-l10n-id="locked-key-prefs-title"/>

      <table class="prefs-table">
        <thead class="no-copy">
          <th class="name" data-l10n-id="locked-prefs-name"/>

          <th class="value" data-l10n-id="locked-prefs-value"/>
        </thead>

        <tbody id="locked-prefs-tbody">
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="place-database" data-l10n-id="place-database-title"/>

      <table>
        <tbody>
          <tr class="no-copy">
            <th class="column" data-l10n-id="place-database-integrity"/>

            <td colspan="5">
              <button id="verify-place-integrity-button" data-l10n-id="place-database-verify-integrity"/>
              <pre id="verify-place-result" class="hidden no-copy"></pre>
            </td>
          </tr>
          <tr class="no-copy">
            <th class="column" data-l10n-id="place-database-stats"/>

            <td colspan="5">
              <button id="place-database-stats-toggle" data-l10n-id="place-database-stats-show"/>
            </td>
          </tr>
        </tbody>
        <tbody id="place-database-stats-tbody">
          <tr>
            <th data-l10n-id="place-database-stats-entity"/>
            <th data-l10n-id="place-database-stats-count"/>
            <th data-l10n-id="place-database-stats-size-kib"/>
            <th data-l10n-id="place-database-stats-size-perc"/>
            <th data-l10n-id="place-database-stats-efficiency-perc"/>
            <th data-l10n-id="place-database-stats-sequentiality-perc"/>
          </tr>
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->
      <h2 class="major-section" id="a11y" data-l10n-id="a11y-title"/>

      <table>
        <tbody>
          <tr>
            <th class="column" data-l10n-id="a11y-activated"/>

            <td id="a11y-activated">
            </td>
          </tr>
          <tr>
            <th class="column" data-l10n-id="a11y-force-disabled"/>

            <td id="a11y-force-disabled">
            </td>
          </tr>
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->
      <h2 class="major-section" id="library-version" data-l10n-id="library-version-title"/>

      <table>
        <tbody id="libversions-tbody">
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="sandbox" data-l10n-id="sandbox-title"/>

      <table>
	<tbody id="sandbox-tbody">
	</tbody>
      </table>

      <h4 data-l10n-id="sandbox-sys-call-log-title"/>
      <table>
	<thead>
	  <tr>
	    <th data-l10n-id="sandbox-sys-call-index"/>
	    <th data-l10n-id="sandbox-sys-call-age"/>
	    <th data-l10n-id="sandbox-sys-call-pid"/>
	    <th data-l10n-id="sandbox-sys-call-tid"/>
	    <th data-l10n-id="sandbox-sys-call-proc-type"/>
	    <th data-l10n-id="sandbox-sys-call-number"/>
	    <th id="sandbox-syscalls-argshead" data-l10n-id="sandbox-sys-call-args"/>
	    </tr>
	</thead>
	<tbody id="sandbox-syscalls-tbody">
	</tbody>
      </table>

      <h2 class="major-section" id="startup-cache" data-l10n-id="startup-cache-title"/>

      <table>
        <tbody>
          <tr>
            <th class="column" data-l10n-id="startup-cache-disk-cache-path"/>

            <td id="startup-cache-disk-cache-path">
            </td>
          </tr>
          <tr>
            <th class="column" data-l10n-id="startup-cache-ignore-disk-cache"/>

            <td id="startup-cache-ignore-disk-cache">
            </td>
          </tr>
          <tr>
            <th class="column" data-l10n-id="startup-cache-found-disk-cache-on-init"/>

            <td id="startup-cache-found-disk-cache-on-init">
            </td>
          </tr>
          <tr>
            <th class="column" data-l10n-id="startup-cache-wrote-to-disk-cache"/>

            <td id="startup-cache-wrote-to-disk-cache">
            </td>
          </tr>
        </tbody>
      </table>

      <h2 class="major-section" id="intl" data-l10n-id="intl-title"/>

      <table>
        <tbody id="intl-localeservice-tbody">
          <tr>
            <th colspan="2" class="title-column" data-l10n-id="intl-app-title"/>
          </tr>
          <tr>
            <th class="column" data-l10n-id="intl-locales-requested"/>
            <td id="intl-locale-requested">
            </td>
          </tr>
          <tr>
            <th class="column" data-l10n-id="intl-locales-available"/>
            <td id="intl-locale-available">
            </td>
          </tr>
          <tr>
            <th class="column" data-l10n-id="intl-locales-supported"/>
            <td id="intl-locale-supported">
            </td>
          </tr>
          <tr>
            <th class="column" data-l10n-id="intl-regional-prefs"/>
            <td id="intl-locale-regionalprefs">
            </td>
          </tr>
          <tr>
            <th class="column" data-l10n-id="intl-locales-default"/>
            <td id="intl-locale-default">
            </td>
          </tr>
        </tbody>
        <tbody id="intl-ospreferences-tbody">
          <tr>
            <th colspan="2" class="title-column" data-l10n-id="intl-os-title"/>
          </tr>
          <tr>
            <th class="column" data-l10n-id="intl-os-prefs-system-locales"/>
            <td id="intl-osprefs-systemlocales">
            </td>
          </tr>
          <tr>
            <th class="column" data-l10n-id="intl-regional-prefs"/>
            <td id="intl-osprefs-regionalprefs">
            </td>
          </tr>
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="remote-debugging" data-l10n-id="remote-debugging-title"/>

      <table>
        <tbody>
          <tr>
            <th class="column" data-l10n-id="remote-debugging-accepting-connections"/>
            <td id="remote-debugging-accepting-connections"></td>
          </tr>
          <tr>
            <th class="column" data-l10n-id="remote-debugging-url"/>
            <td id="remote-debugging-url"></td>
          </tr>
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="printing" data-l10n-id="support-printing-title"/>

      <table>
        <tr class="no-copy">
          <th class="column" data-l10n-id="support-printing-troubleshoot"/>
          <td>
            <button id="support-printing-clear-settings-button" data-l10n-id="support-printing-clear-settings-button"/>
          </td>
        </tr>
      </table>

      <h3 data-l10n-id="support-printing-modified-settings"/>

      <table class="prefs-table">
        <thead class="no-copy">
          <th class="name" data-l10n-id="support-printing-prefs-name"/>

          <th class="value" data-l10n-id="support-printing-prefs-value"/>
        </thead>

        <tbody id="support-printing-prefs-tbody">
        </tbody>
      </table>

      <!-- - - - - - - - - - - - - - - - - - - - - -->

      <h2 class="major-section" id="content-analysis" data-l10n-id="content-analysis-title"/>

      <table>
        <tbody>
          <tr>
            <th class="column" data-l10n-id="content-analysis-active"/>
            <td id="content-analysis-active"/>
          </tr>
          <tr>
            <th class="column" data-l10n-id="content-analysis-connected-to-agent"/>
            <td id="content-analysis-connected-to-agent"/>
          </tr>
          <tr>
            <th class="column" data-l10n-id="content-analysis-agent-path"/>
            <td id="content-analysis-agent-path"/>
          </tr>
          <tr>
            <th class="column" data-l10n-id="content-analysis-agent-failed-signature-verification"/>
            <td id="content-analysis-agent-failed-signature-verification"/>
          </tr>
          <tr>
            <th class="column" data-l10n-id="content-analysis-request-count"/>
            <td id="content-analysis-request-count"/>
          </tr>
        </tbody>
      </table>


    </div>

  </body>

</html>
PK
!<������0chrome/toolkit/content/global/aboutTelemetry.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/in-content/common.css");

body {
  display: flex;
}

/* This is needed to make the sidebar not hide the last button but breaks printing by hiding overflowing content */
@media not print {
  html, body {
    height: 100%;
  }
}

#categories {
  padding-top: 0;
  overflow-y: auto;
  margin-bottom: 42px;
  user-select: none;
}

.main-content.search > section > *:not(.data) {
  display: none;
}

.main-content {
  flex: 1;
  line-height: 1.6;
}

#home-section {
  font-size: 18px;
}

#category-raw {
  background-color: var(--in-content-page-background);
  position: absolute;
  bottom: 0;
  inset-inline-start: 0;
}

.heading {
  display: flex;
  flex-direction: column;
  font-size: 17px;
  font-weight: 600;
  pointer-events: none;
  padding: 12px 8px;
}

.header {
  display: flex;
}

.header select {
  margin-inline-start: 4px;
}

#sectionTitle {
  flex-grow: 1;
}

#sectionFilters {
  display: flex;
  align-items: center;
  margin-inline-start: 5px;
}

#stores {
  padding-block: 5px;
  padding-inline-start: 5px;
}

#ping-type {
  flex-grow: 1;
  text-align: center;
  pointer-events: all;
  cursor: pointer;
}

#older-ping,
#newer-ping,
#ping-date {
  pointer-events: all;
  user-select: none;
  cursor: pointer;
  text-align: center;
}

.dropdown {
  background-image: url(chrome://global/skin/icons/arrow-down.svg);
  background-position: right 8px center;
  background-repeat: no-repeat;
  -moz-context-properties: fill;
  fill: currentColor;
}

.dropdown:dir(rtl) {
  background-position-x: left 8px;
}

#controls {
  display: flex;
  margin-top: 4px;
  justify-content: space-between;
}

.category:not(.has-data) {
  display: none;
}

.category {
  cursor: pointer;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 42px;
}

#categories > .category.category-no-icon {
  margin-inline-start: 0;
  margin-inline-end: 0;
  width: auto;
}

.category-name {
  padding: 13px 0;
}

.category-subsection {
  color: var(--in-content-text-color);
  padding: 8px 0;
  padding-inline-start: 16px;
  display: none;
}

.category-subsection.selected {
  color: inherit;
}

.category-subsection::first-letter {
  text-transform: uppercase;
}

.category.selected > .category-subsection {
  display: block;
}

.category-name {
  pointer-events: none;
}

section:not(.active) {
  display: none;
}

#ping-explanation > span {
  cursor: pointer;
  border-bottom-width: 2px;
  border-bottom-style: solid;
}

#no-search-results {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  display: flex;
  align-items: center;
  flex-direction: column;
}

#no-search-results-text {
  font-size: 17px;
  margin-bottom: 2em;
}

.hidden {
  display: none !important;
}

#ping-picker {
  min-width: 300px;
  position: fixed;
  z-index: 2;
  top: 32px;
  border-radius: 2px;
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.25);
  display: flex;
  padding: 24px;
  flex-direction: column;
  background-color: var(--in-content-box-background);
  border: 1px solid var(--in-content-box-border-color);
  margin: 12px 0;
  inset-inline-start: 12px;
}

#ping-picker .title {
  margin: 4px 0;
}

#ping-source-picker {
  margin-inline-start: 5px;
  margin-bottom: 10px;
}

#ping-source-archive-container.disabled {
  opacity: 0.5;
}

.stack-title {
  font-size: medium;
  font-weight: bold;
  text-decoration: underline;
}

#histograms {
  overflow: hidden;
}

.histogram {
  float: inline-start;
  white-space: nowrap;
  padding: 10px;
  position: relative; /* required for position:absolute of the contained .copy-node */
  padding-block: 12px;
  padding-inline: 20px;
  border: 1px solid var(--in-content-box-border-color);
  background-color: var(--in-content-box-background);
  border-radius: 2px;
  margin-bottom: 24px;
  margin-inline-end: 24px;
  min-height: 17.5em;
}

.histogram-title {
  text-overflow: ellipsis;
  width: 100%;
  white-space: nowrap;
  overflow: hidden;
  font-size: 17px
}

.histogram-stats {
  font-size: 13px;
}

.keyed-histogram {
  white-space: nowrap;
  position: relative; /* required for position:absolute of the contained .copy-node */
  overflow: hidden;
  margin-bottom: 1em;
}

.keyed-scalar,
.sub-section {
  margin-bottom: 1em;
}

.keyed-title {
  text-overflow: ellipsis;
  margin: 12px 0;
  font-size: 17px;
  white-space: nowrap;
}

.bar {
  font-size: 17px;
  width: 2em;
  margin: 2px;
  text-align: center;
  float: inline-start;
  font-family: monospace;
}

.bar-inner {
  background-color: var(--in-content-accent-color);
  border: 1px solid rgba(0,0,0,0.1);
  border-radius: 2px;
}

.bar:nth-child(even) .long-label {
  margin-bottom: 1em;
}

th, td, table {
  text-align: start;
  word-break: break-all;
  border-collapse: collapse;
}

table {
  table-layout: fixed;
  width: 100%;
  font-size: 15px;
}

td {
  padding-bottom: 0.25em;
  border-bottom: 1px solid var(--in-content-border-color);
}

tr:not(:first-child):hover {
  background-color: rgba(0, 0, 0, 0.05);
}

th {
  font-size: 13px;
  white-space: nowrap;
  padding: 0.5em 0;
}

caption {
  text-align: start;
  font-size: 22px;
  margin-block: 0.5em;
  margin-inline: 0;
}

.copy-node {
  visibility: hidden;
  position: absolute;
  bottom: 1px;
  inset-inline-end: 1px;
}

.histogram:hover .copy-node {
  visibility: visible;
}

#raw-ping-data {
  font-size: 15px;
}

.clearfix {
  clear: both;
}
PK
!<�r�0�0/chrome/toolkit/content/global/aboutTelemetry.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const { BrowserUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/BrowserUtils.sys.mjs"
);
const { TelemetryTimestamps } = ChromeUtils.importESModule(
  "resource://gre/modules/TelemetryTimestamps.sys.mjs"
);
const { TelemetryController } = ChromeUtils.importESModule(
  "resource://gre/modules/TelemetryController.sys.mjs"
);
const { TelemetryArchive } = ChromeUtils.importESModule(
  "resource://gre/modules/TelemetryArchive.sys.mjs"
);
const { TelemetrySend } = ChromeUtils.importESModule(
  "resource://gre/modules/TelemetrySend.sys.mjs"
);

const { AppConstants } = ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);
ChromeUtils.defineESModuleGetters(this, {
  ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
  Preferences: "resource://gre/modules/Preferences.sys.mjs",
});

const Telemetry = Services.telemetry;

// Maximum height of a histogram bar (in em for html, in chars for text)
const MAX_BAR_HEIGHT = 8;
const MAX_BAR_CHARS = 25;
const PREF_TELEMETRY_SERVER_OWNER = "toolkit.telemetry.server_owner";
const PREF_TELEMETRY_ENABLED = "toolkit.telemetry.enabled";
const PREF_DEBUG_SLOW_SQL = "toolkit.telemetry.debugSlowSql";
const PREF_SYMBOL_SERVER_URI = "profiler.symbolicationUrl";
const DEFAULT_SYMBOL_SERVER_URI =
  "https://symbolication.services.mozilla.com/symbolicate/v4";
const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";

// ms idle before applying the filter (allow uninterrupted typing)
const FILTER_IDLE_TIMEOUT = 500;

const isWindows = Services.appinfo.OS == "WINNT";
const EOL = isWindows ? "\r\n" : "\n";

// This is the ping object currently displayed in the page.
var gPingData = null;

// Cached value of document's RTL mode
var documentRTLMode = "";

/**
 * Helper function for determining whether the document direction is RTL.
 * Caches result of check on first invocation.
 */
function isRTL() {
  if (!documentRTLMode) {
    documentRTLMode = window.getComputedStyle(document.body).direction;
  }
  return documentRTLMode == "rtl";
}

function isFlatArray(obj) {
  if (!Array.isArray(obj)) {
    return false;
  }
  return !obj.some(e => typeof e == "object");
}

/**
 * This is a helper function for explodeObject.
 */
function flattenObject(obj, map, path, array) {
  for (let k of Object.keys(obj)) {
    let newPath = [...path, array ? "[" + k + "]" : k];
    let v = obj[k];
    if (!v || typeof v != "object") {
      map.set(newPath.join("."), v);
    } else if (isFlatArray(v)) {
      map.set(newPath.join("."), "[" + v.join(", ") + "]");
    } else {
      flattenObject(v, map, newPath, Array.isArray(v));
    }
  }
}

/**
 * This turns a JSON object into a "flat" stringified form.
 *
 * For an object like {a: "1", b: {c: "2", d: "3"}} it returns a Map of the
 * form Map(["a","1"], ["b.c", "2"], ["b.d", "3"]).
 */
function explodeObject(obj) {
  let map = new Map();
  flattenObject(obj, map, []);
  return map;
}

function filterObject(obj, filterOut) {
  let ret = {};
  for (let k of Object.keys(obj)) {
    if (!filterOut.includes(k)) {
      ret[k] = obj[k];
    }
  }
  return ret;
}

/**
 * This turns a JSON object into a "flat" stringified form, separated into top-level sections.
 *
 * For an object like:
 *   {
 *     a: {b: "1"},
 *     c: {d: "2", e: {f: "3"}}
 *   }
 * it returns a Map of the form:
 *   Map([
 *     ["a", Map(["b","1"])],
 *     ["c", Map([["d", "2"], ["e.f", "3"]])]
 *   ])
 */
function sectionalizeObject(obj) {
  let map = new Map();
  for (let k of Object.keys(obj)) {
    map.set(k, explodeObject(obj[k]));
  }
  return map;
}

/**
 * Obtain the main DOMWindow for the current context.
 */
function getMainWindow() {
  return window.browsingContext.topChromeWindow;
}

/**
 * Obtain the DOMWindow that can open a preferences pane.
 *
 * This is essentially "get the browser chrome window" with the added check
 * that the supposed browser chrome window is capable of opening a preferences
 * pane.
 *
 * This may return null if we can't find the browser chrome window.
 */
function getMainWindowWithPreferencesPane() {
  let mainWindow = getMainWindow();
  if (mainWindow && "openPreferences" in mainWindow) {
    return mainWindow;
  }
  return null;
}

/**
 * Remove all child nodes of a document node.
 */
function removeAllChildNodes(node) {
  while (node.hasChildNodes()) {
    node.removeChild(node.lastChild);
  }
}

var Settings = {
  attachObservers() {
    let elements = document.getElementsByClassName("change-data-choices-link");
    for (let el of elements) {
      el.parentElement.addEventListener("click", function (event) {
        if (event.target.localName === "a") {
          if (AppConstants.platform == "android") {
            var { EventDispatcher } = ChromeUtils.importESModule(
              "resource://gre/modules/Messaging.sys.mjs"
            );
            EventDispatcher.instance.sendRequest({
              type: "Settings:Show",
              resource: "preferences_privacy",
            });
          } else {
            // Show the data choices preferences on desktop.
            let mainWindow = getMainWindowWithPreferencesPane();
            mainWindow.openPreferences("privacy-reports");
          }
        }
      });
    }
  },

  /**
   * Updates the button & text at the top of the page to reflect Telemetry state.
   */
  render() {
    let settingsExplanation = document.getElementById("settings-explanation");
    let extendedEnabled = Services.telemetry.canRecordExtended;

    let channel = extendedEnabled ? "prerelease" : "release";
    let uploadcase = TelemetrySend.sendingEnabled() ? "enabled" : "disabled";

    document.l10n.setAttributes(
      settingsExplanation,
      "about-telemetry-settings-explanation",
      { channel, uploadcase }
    );

    this.attachObservers();
  },
};

var PingPicker = {
  viewCurrentPingData: null,
  _archivedPings: null,
  TYPE_ALL: "all",

  attachObservers() {
    let pingSourceElements = document.getElementsByName("choose-ping-source");
    for (let el of pingSourceElements) {
      el.addEventListener("change", () => this.onPingSourceChanged());
    }

    let displays = document.getElementsByName("choose-ping-display");
    for (let el of displays) {
      el.addEventListener("change", () => this.onPingDisplayChanged());
    }

    document
      .getElementById("show-subsession-data")
      .addEventListener("change", () => {
        this._updateCurrentPingData();
      });

    document.getElementById("choose-ping-id").addEventListener("change", () => {
      this._updateArchivedPingData();
    });
    document
      .getElementById("choose-ping-type")
      .addEventListener("change", () => {
        this.filterDisplayedPings();
      });

    document
      .getElementById("newer-ping")
      .addEventListener("click", () => this._movePingIndex(-1));
    document
      .getElementById("older-ping")
      .addEventListener("click", () => this._movePingIndex(1));

    let pingPickerNeedHide = false;
    let pingPicker = document.getElementById("ping-picker");
    pingPicker.addEventListener(
      "mouseenter",
      () => (pingPickerNeedHide = false)
    );
    pingPicker.addEventListener(
      "mouseleave",
      () => (pingPickerNeedHide = true)
    );
    document.addEventListener("click", () => {
      if (pingPickerNeedHide) {
        pingPicker.classList.add("hidden");
      }
    });
    document
      .getElementById("stores")
      .addEventListener("change", () => displayPingData(gPingData));
    Array.from(document.querySelectorAll(".change-ping")).forEach(el => {
      el.addEventListener("click", event => {
        if (!pingPicker.classList.contains("hidden")) {
          pingPicker.classList.add("hidden");
        } else {
          pingPicker.classList.remove("hidden");
          event.stopPropagation();
        }
      });
    });
  },

  onPingSourceChanged() {
    this.update();
  },

  onPingDisplayChanged() {
    this.update();
  },

  render() {
    // Display the type and controls if the ping is not current
    let pingDate = document.getElementById("ping-date");
    let pingType = document.getElementById("ping-type");
    let controls = document.getElementById("controls");
    let pingExplanation = document.getElementById("ping-explanation");

    if (!this.viewCurrentPingData) {
      let pingName = this._getSelectedPingName();
      // Change sidebar heading text.
      pingDate.textContent = pingName;
      pingDate.setAttribute("title", pingName);
      let pingTypeText = this._getSelectedPingType();
      controls.classList.remove("hidden");
      pingType.textContent = pingTypeText;
      document.l10n.setAttributes(
        pingExplanation,
        "about-telemetry-ping-details",
        { timestamp: pingTypeText, name: pingName }
      );
    } else {
      // Change sidebar heading text.
      controls.classList.add("hidden");
      document.l10n.setAttributes(
        pingType,
        "about-telemetry-current-data-sidebar"
      );
      // Change home page text.
      document.l10n.setAttributes(
        pingExplanation,
        "about-telemetry-data-details-current"
      );
    }

    GenericSubsection.deleteAllSubSections();
  },

  async update() {
    let viewCurrent = document.getElementById("ping-source-current").checked;
    let currentChanged = viewCurrent !== this.viewCurrentPingData;
    this.viewCurrentPingData = viewCurrent;

    // If we have no archived pings, disable the ping archive selection.
    // This can happen on new profiles or if the ping archive is disabled.
    let archivedPingList = await TelemetryArchive.promiseArchivedPingList();
    let sourceArchived = document.getElementById("ping-source-archive");
    let sourceArchivedContainer = document.getElementById(
      "ping-source-archive-container"
    );
    let archivedDisabled = !archivedPingList.length;
    sourceArchived.disabled = archivedDisabled;
    sourceArchivedContainer.classList.toggle("disabled", archivedDisabled);

    if (currentChanged) {
      if (this.viewCurrentPingData) {
        document.getElementById("current-ping-picker").hidden = false;
        document.getElementById("archived-ping-picker").hidden = true;
        this._updateCurrentPingData();
      } else {
        document.getElementById("current-ping-picker").hidden = true;
        await this._updateArchivedPingList(archivedPingList);
        document.getElementById("archived-ping-picker").hidden = false;
      }
    }
  },

  _updateCurrentPingData() {
    TelemetryController.ensureInitialized().then(() =>
      this._doUpdateCurrentPingData()
    );
  },

  _doUpdateCurrentPingData() {
    const subsession = document.getElementById("show-subsession-data").checked;
    let ping = TelemetryController.getCurrentPingData(subsession);
    if (!ping) {
      return;
    }

    let stores = Telemetry.getAllStores();
    let getData = {
      histograms: Telemetry.getSnapshotForHistograms,
      keyedHistograms: Telemetry.getSnapshotForKeyedHistograms,
      scalars: Telemetry.getSnapshotForScalars,
      keyedScalars: Telemetry.getSnapshotForKeyedScalars,
    };

    let data = {};
    for (const [name, fn] of Object.entries(getData)) {
      for (const store of stores) {
        if (!data[store]) {
          data[store] = {};
        }
        let measurement = fn(store, /* clear */ false, /* filterTest */ true);
        let processes = Object.keys(measurement);

        for (const process of processes) {
          if (!data[store][process]) {
            data[store][process] = {};
          }

          data[store][process][name] = measurement[process];
        }
      }
    }
    ping.payload.stores = data;

    // Delete the unused data from the payload of the current ping.
    // It's included in the above `stores` attribute.
    for (const data of Object.values(ping.payload.processes)) {
      delete data.scalars;
      delete data.keyedScalars;
      delete data.histograms;
      delete data.keyedHistograms;
    }
    delete ping.payload.histograms;
    delete ping.payload.keyedHistograms;

    // augment ping payload with event telemetry
    let eventSnapshot = Telemetry.snapshotEvents(
      Telemetry.DATASET_PRERELEASE_CHANNELS,
      false
    );
    for (let process of Object.keys(eventSnapshot)) {
      if (process in ping.payload.processes) {
        ping.payload.processes[process].events = eventSnapshot[process].filter(
          e => !e[1].startsWith("telemetry.test")
        );
      }
    }

    displayPingData(ping, true);
  },

  _updateArchivedPingData() {
    let id = this._getSelectedPingId();
    let res = Promise.resolve();
    if (id) {
      res = TelemetryArchive.promiseArchivedPingById(id).then(ping =>
        displayPingData(ping, true)
      );
    }
    return res;
  },

  async _updateArchivedPingList(pingList) {
    // The archived ping list is sorted in ascending timestamp order,
    // but descending is more practical for the operations we do here.
    pingList.reverse();
    this._archivedPings = pingList;
    // Render the archive data.
    this._renderPingList();
    // Update the displayed ping.
    await this._updateArchivedPingData();
  },

  _renderPingList() {
    let pingSelector = document.getElementById("choose-ping-id");
    Array.from(pingSelector.children).forEach(child =>
      removeAllChildNodes(child)
    );

    let pingTypes = new Set();
    pingTypes.add(this.TYPE_ALL);

    const today = new Date();
    today.setHours(0, 0, 0, 0);
    const yesterday = new Date(today);
    yesterday.setDate(today.getDate() - 1);

    for (let p of this._archivedPings) {
      pingTypes.add(p.type);
      const pingDate = new Date(p.timestampCreated);
      const datetimeText = new Services.intl.DateTimeFormat(undefined, {
        dateStyle: "short",
        timeStyle: "medium",
      }).format(pingDate);
      const pingName = `${datetimeText}, ${p.type}`;

      let option = document.createElement("option");
      let content = document.createTextNode(pingName);
      option.appendChild(content);
      option.setAttribute("value", p.id);
      option.dataset.type = p.type;
      option.dataset.date = datetimeText;

      pingDate.setHours(0, 0, 0, 0);
      if (pingDate.getTime() === today.getTime()) {
        pingSelector.children[0].appendChild(option);
      } else if (pingDate.getTime() === yesterday.getTime()) {
        pingSelector.children[1].appendChild(option);
      } else {
        pingSelector.children[2].appendChild(option);
      }
    }
    this._renderPingTypes(pingTypes);
  },

  _renderPingTypes(pingTypes) {
    let pingTypeSelector = document.getElementById("choose-ping-type");
    removeAllChildNodes(pingTypeSelector);
    pingTypes.forEach(type => {
      let option = document.createElement("option");
      option.appendChild(document.createTextNode(type));
      option.setAttribute("value", type);
      pingTypeSelector.appendChild(option);
    });
  },

  _movePingIndex(offset) {
    if (this.viewCurrentPingData) {
      return;
    }
    let typeSelector = document.getElementById("choose-ping-type");
    let type = typeSelector.selectedOptions.item(0).value;

    let id = this._getSelectedPingId();
    let index = this._archivedPings.findIndex(p => p.id == id);
    let newIndex = Math.min(
      Math.max(0, index + offset),
      this._archivedPings.length - 1
    );

    let pingList;
    if (offset > 0) {
      pingList = this._archivedPings.slice(newIndex);
    } else {
      pingList = this._archivedPings.slice(0, newIndex);
      pingList.reverse();
    }

    let ping = pingList.find(p => {
      return type == this.TYPE_ALL || p.type == type;
    });

    if (ping) {
      this.selectPing(ping);
      this._updateArchivedPingData();
    }
  },

  selectPing(ping) {
    let pingSelector = document.getElementById("choose-ping-id");
    // Use some() to break if we find the ping.
    Array.from(pingSelector.children).some(group => {
      return Array.from(group.children).some(option => {
        if (option.value == ping.id) {
          option.selected = true;
          return true;
        }
        return false;
      });
    });
  },

  filterDisplayedPings() {
    let pingSelector = document.getElementById("choose-ping-id");
    let typeSelector = document.getElementById("choose-ping-type");
    let type = typeSelector.selectedOptions.item(0).value;
    let first = true;
    Array.from(pingSelector.children).forEach(group => {
      Array.from(group.children).forEach(option => {
        if (first && option.dataset.type == type) {
          option.selected = true;
          first = false;
        }
        option.hidden = type != this.TYPE_ALL && option.dataset.type != type;
        // Arrow keys should only iterate over visible options
        option.disabled = option.hidden;
      });
    });
    this._updateArchivedPingData();
  },

  _getSelectedPingName() {
    let pingSelector = document.getElementById("choose-ping-id");
    let selected = pingSelector.selectedOptions.item(0);
    return selected.dataset.date;
  },

  _getSelectedPingType() {
    let pingSelector = document.getElementById("choose-ping-id");
    let selected = pingSelector.selectedOptions.item(0);
    return selected.dataset.type;
  },

  _getSelectedPingId() {
    let pingSelector = document.getElementById("choose-ping-id");
    let selected = pingSelector.selectedOptions.item(0);
    return selected.getAttribute("value");
  },

  _showRawPingData() {
    show(document.getElementById("category-raw"));
  },

  _showStructuredPingData() {
    show(document.getElementById("category-home"));
  },
};

var GeneralData = {
  /**
   * Renders the general data
   */
  render(aPing) {
    setHasData("general-data-section", true);
    let generalDataSection = document.getElementById("general-data");
    removeAllChildNodes(generalDataSection);

    const headings = [
      "about-telemetry-names-header",
      "about-telemetry-values-header",
    ];

    // The payload & environment parts are handled by other renderers.
    let ignoreSections = ["payload", "environment"];
    let data = explodeObject(filterObject(aPing, ignoreSections));

    const table = GenericTable.render(data, headings);
    generalDataSection.appendChild(table);
  },
};

var EnvironmentData = {
  /**
   * Renders the environment data
   */
  render(ping) {
    let dataDiv = document.getElementById("environment-data");
    removeAllChildNodes(dataDiv);
    const hasData = !!ping.environment;
    setHasData("environment-data-section", hasData);
    if (!hasData) {
      return;
    }

    let ignore = ["addons"];
    let env = filterObject(ping.environment, ignore);
    let sections = sectionalizeObject(env);
    GenericSubsection.render(sections, dataDiv, "environment-data-section");

    // We use specialized rendering here to make the addon and plugin listings
    // more readable.
    this.createAddonSection(dataDiv, ping);
  },

  renderAddonsObject(addonObj, addonSection, sectionTitle) {
    let table = document.createElement("table");
    table.setAttribute("id", sectionTitle);
    this.appendAddonSubsectionTitle(sectionTitle, table);

    for (let id of Object.keys(addonObj)) {
      let addon = addonObj[id];
      this.appendHeadingName(table, addon.name || id);
      this.appendAddonID(table, id);
      let data = explodeObject(addon);

      for (let [key, value] of data) {
        this.appendRow(table, key, value);
      }
    }

    addonSection.appendChild(table);
  },

  renderKeyValueObject(addonObj, addonSection, sectionTitle) {
    let data = explodeObject(addonObj);
    let table = GenericTable.render(data);
    table.setAttribute("class", sectionTitle);
    this.appendAddonSubsectionTitle(sectionTitle, table);
    addonSection.appendChild(table);
  },

  appendAddonID(table, addonID) {
    this.appendRow(table, "id", addonID);
  },

  appendHeadingName(table, name) {
    let headings = document.createElement("tr");
    this.appendColumn(headings, "th", name);
    headings.cells[0].colSpan = 2;
    table.appendChild(headings);
  },

  appendAddonSubsectionTitle(section, table) {
    let caption = document.createElement("caption");
    caption.appendChild(document.createTextNode(section));
    table.appendChild(caption);
  },

  createAddonSection(dataDiv, ping) {
    if (!ping || !("environment" in ping) || !("addons" in ping.environment)) {
      return;
    }
    let addonSection = document.createElement("div");
    addonSection.setAttribute("class", "subsection-data subdata");
    let addons = ping.environment.addons;
    this.renderAddonsObject(addons.activeAddons, addonSection, "activeAddons");
    this.renderKeyValueObject(addons.theme, addonSection, "theme");
    this.renderAddonsObject(
      addons.activeGMPlugins,
      addonSection,
      "activeGMPlugins"
    );

    let hasAddonData = !!Object.keys(ping.environment.addons).length;
    let s = GenericSubsection.renderSubsectionHeader(
      "addons",
      hasAddonData,
      "environment-data-section"
    );
    s.appendChild(addonSection);
    dataDiv.appendChild(s);
  },

  appendRow(table, id, value) {
    let row = document.createElement("tr");
    row.id = id;
    this.appendColumn(row, "td", id);
    this.appendColumn(row, "td", value);
    table.appendChild(row);
  },
  /**
   * Helper function for appending a column to the data table.
   *
   * @param aRowElement Parent row element
   * @param aColType Column's tag name
   * @param aColText Column contents
   */
  appendColumn(aRowElement, aColType, aColText) {
    let colElement = document.createElement(aColType);
    let colTextElement = document.createTextNode(aColText);
    colElement.appendChild(colTextElement);
    aRowElement.appendChild(colElement);
  },
};

var SlowSQL = {
  /**
   * Render slow SQL statistics
   */
  render: function SlowSQL_render(aPing) {
    // We can add the debug SQL data to the current ping later.
    // However, we need to be careful to never send that debug data
    // out due to privacy concerns.
    // We want to show the actual ping data for archived pings,
    // so skip this there.

    let debugSlowSql =
      PingPicker.viewCurrentPingData &&
      Preferences.get(PREF_DEBUG_SLOW_SQL, false);
    let slowSql = debugSlowSql ? Telemetry.debugSlowSQL : aPing.payload.slowSQL;
    if (!slowSql) {
      setHasData("slow-sql-section", false);
      return;
    }

    let { mainThread, otherThreads } = debugSlowSql
      ? Telemetry.debugSlowSQL
      : aPing.payload.slowSQL;

    let mainThreadCount = Object.keys(mainThread).length;
    let otherThreadCount = Object.keys(otherThreads).length;
    if (mainThreadCount == 0 && otherThreadCount == 0) {
      setHasData("slow-sql-section", false);
      return;
    }

    setHasData("slow-sql-section", true);
    if (debugSlowSql) {
      document.getElementById("sql-warning").hidden = false;
    }

    let slowSqlDiv = document.getElementById("slow-sql-tables");
    removeAllChildNodes(slowSqlDiv);

    // Main thread
    if (mainThreadCount > 0) {
      let table = document.createElement("table");
      this.renderTableHeader(table, "main");
      this.renderTable(table, mainThread);
      slowSqlDiv.appendChild(table);
    }

    // Other threads
    if (otherThreadCount > 0) {
      let table = document.createElement("table");
      this.renderTableHeader(table, "other");
      this.renderTable(table, otherThreads);
      slowSqlDiv.appendChild(table);
    }
  },

  /**
   * Creates a header row for a Slow SQL table
   * Tabs & newlines added to cells to make it easier to copy-paste.
   *
   * @param aTable Parent table element
   * @param aTitle Table's title
   */
  renderTableHeader: function SlowSQL_renderTableHeader(aTable, threadType) {
    let caption = document.createElement("caption");
    if (threadType == "main") {
      document.l10n.setAttributes(caption, "about-telemetry-slow-sql-main");
    }

    if (threadType == "other") {
      document.l10n.setAttributes(caption, "about-telemetry-slow-sql-other");
    }
    aTable.appendChild(caption);

    let headings = document.createElement("tr");
    document.l10n.setAttributes(
      this.appendColumn(headings, "th"),
      "about-telemetry-slow-sql-hits"
    );
    document.l10n.setAttributes(
      this.appendColumn(headings, "th"),
      "about-telemetry-slow-sql-average"
    );
    document.l10n.setAttributes(
      this.appendColumn(headings, "th"),
      "about-telemetry-slow-sql-statement"
    );
    aTable.appendChild(headings);
  },

  /**
   * Fills out the table body
   * Tabs & newlines added to cells to make it easier to copy-paste.
   *
   * @param aTable Parent table element
   * @param aSql SQL stats object
   */
  renderTable: function SlowSQL_renderTable(aTable, aSql) {
    for (let [sql, [hitCount, totalTime]] of Object.entries(aSql)) {
      let averageTime = totalTime / hitCount;

      let sqlRow = document.createElement("tr");

      this.appendColumn(sqlRow, "td", hitCount + "\t");
      this.appendColumn(sqlRow, "td", averageTime.toFixed(0) + "\t");
      this.appendColumn(sqlRow, "td", sql + "\n");

      aTable.appendChild(sqlRow);
    }
  },

  /**
   * Helper function for appending a column to a Slow SQL table.
   *
   * @param aRowElement Parent row element
   * @param aColType Column's tag name
   * @param aColText Column contents
   */
  appendColumn: function SlowSQL_appendColumn(
    aRowElement,
    aColType,
    aColText = ""
  ) {
    let colElement = document.createElement(aColType);
    if (aColText) {
      let colTextElement = document.createTextNode(aColText);
      colElement.appendChild(colTextElement);
    }
    aRowElement.appendChild(colElement);
    return colElement;
  },
};

var StackRenderer = {
  /**
   * Outputs the memory map associated with this hang report
   *
   * @param aDiv Output div
   */
  renderMemoryMap: async function StackRenderer_renderMemoryMap(
    aDiv,
    memoryMap
  ) {
    let memoryMapTitleElement = document.createElement("span");
    document.l10n.setAttributes(
      memoryMapTitleElement,
      "about-telemetry-memory-map-title"
    );
    aDiv.appendChild(memoryMapTitleElement);
    aDiv.appendChild(document.createElement("br"));

    for (let currentModule of memoryMap) {
      aDiv.appendChild(document.createTextNode(currentModule.join(" ")));
      aDiv.appendChild(document.createElement("br"));
    }

    aDiv.appendChild(document.createElement("br"));
  },

  /**
   * Outputs the raw PCs from the hang's stack
   *
   * @param aDiv Output div
   * @param aStack Array of PCs from the hang stack
   */
  renderStack: function StackRenderer_renderStack(aDiv, aStack) {
    let stackTitleElement = document.createElement("span");
    document.l10n.setAttributes(
      stackTitleElement,
      "about-telemetry-stack-title"
    );
    aDiv.appendChild(stackTitleElement);
    let stackText = " " + aStack.join(" ");
    aDiv.appendChild(document.createTextNode(stackText));

    aDiv.appendChild(document.createElement("br"));
    aDiv.appendChild(document.createElement("br"));
  },
  renderStacks: function StackRenderer_renderStacks(
    aPrefix,
    aStacks,
    aMemoryMap,
    aRenderHeader
  ) {
    let div = document.getElementById(aPrefix);
    removeAllChildNodes(div);

    let fetchE = document.getElementById(aPrefix + "-fetch-symbols");
    if (fetchE) {
      fetchE.hidden = false;
    }
    let hideE = document.getElementById(aPrefix + "-hide-symbols");
    if (hideE) {
      hideE.hidden = true;
    }

    if (!aStacks.length) {
      return;
    }

    setHasData(aPrefix + "-section", true);

    this.renderMemoryMap(div, aMemoryMap);

    for (let i = 0; i < aStacks.length; ++i) {
      let stack = aStacks[i];
      aRenderHeader(i);
      this.renderStack(div, stack);
    }
  },

  /**
   * Renders the title of the stack: e.g. "Late Write #1" or
   * "Hang Report #1 (6 seconds)".
   *
   * @param aDivId The id of the div to append the header to.
   * @param aL10nId The l10n id of the message to use for the title.
   * @param aL10nArgs The l10n args for the provided message id.
   */
  renderHeader: function StackRenderer_renderHeader(
    aDivId,
    aL10nId,
    aL10nArgs
  ) {
    let div = document.getElementById(aDivId);

    let titleElement = document.createElement("span");
    titleElement.className = "stack-title";

    document.l10n.setAttributes(titleElement, aL10nId, aL10nArgs);

    div.appendChild(titleElement);
    div.appendChild(document.createElement("br"));
  },
};

var RawPayloadData = {
  /**
   * Renders the raw pyaload.
   */
  render(aPing) {
    setHasData("raw-payload-section", true);
    let pre = document.getElementById("raw-payload-data");
    pre.textContent = JSON.stringify(aPing.payload, null, 2);
  },

  attachObservers() {
    document
      .getElementById("payload-json-viewer")
      .addEventListener("click", () => {
        openJsonInFirefoxJsonViewer(JSON.stringify(gPingData.payload, null, 2));
      });
  },
};

function SymbolicationRequest(
  aPrefix,
  aRenderHeader,
  aMemoryMap,
  aStacks,
  aDurations = null
) {
  this.prefix = aPrefix;
  this.renderHeader = aRenderHeader;
  this.memoryMap = aMemoryMap;
  this.stacks = aStacks;
  this.durations = aDurations;
}
/**
 * A callback for onreadystatechange. It replaces the numeric stack with
 * the symbolicated one returned by the symbolication server.
 */
SymbolicationRequest.prototype.handleSymbolResponse =
  async function SymbolicationRequest_handleSymbolResponse() {
    if (this.symbolRequest.readyState != 4) {
      return;
    }

    let fetchElement = document.getElementById(this.prefix + "-fetch-symbols");
    fetchElement.hidden = true;
    let hideElement = document.getElementById(this.prefix + "-hide-symbols");
    hideElement.hidden = false;
    let div = document.getElementById(this.prefix);
    removeAllChildNodes(div);
    let errorMessage = await document.l10n.formatValue(
      "about-telemetry-error-fetching-symbols"
    );

    if (this.symbolRequest.status != 200) {
      div.appendChild(document.createTextNode(errorMessage));
      return;
    }

    let jsonResponse = {};
    try {
      jsonResponse = JSON.parse(this.symbolRequest.responseText);
    } catch (e) {
      div.appendChild(document.createTextNode(errorMessage));
      return;
    }

    for (let i = 0; i < jsonResponse.length; ++i) {
      let stack = jsonResponse[i];
      this.renderHeader(i, this.durations);

      for (let symbol of stack) {
        div.appendChild(document.createTextNode(symbol));
        div.appendChild(document.createElement("br"));
      }
      div.appendChild(document.createElement("br"));
    }
  };
/**
 * Send a request to the symbolication server to symbolicate this stack.
 */
SymbolicationRequest.prototype.fetchSymbols =
  function SymbolicationRequest_fetchSymbols() {
    let symbolServerURI = Preferences.get(
      PREF_SYMBOL_SERVER_URI,
      DEFAULT_SYMBOL_SERVER_URI
    );
    let request = {
      memoryMap: this.memoryMap,
      stacks: this.stacks,
      version: 3,
    };
    let requestJSON = JSON.stringify(request);

    this.symbolRequest = new XMLHttpRequest();
    this.symbolRequest.open("POST", symbolServerURI, true);
    this.symbolRequest.setRequestHeader("Content-type", "application/json");
    this.symbolRequest.setRequestHeader("Content-length", requestJSON.length);
    this.symbolRequest.setRequestHeader("Connection", "close");
    this.symbolRequest.onreadystatechange =
      this.handleSymbolResponse.bind(this);
    this.symbolRequest.send(requestJSON);
  };

var Histogram = {
  /**
   * Renders a single Telemetry histogram
   *
   * @param aParent Parent element
   * @param aName Histogram name
   * @param aHgram Histogram information
   * @param aOptions Object with render options
   *                 * exponential: bars follow logarithmic scale
   */
  render: function Histogram_render(aParent, aName, aHgram, aOptions) {
    let options = aOptions || {};
    let hgram = this.processHistogram(aHgram, aName);

    let outerDiv = document.createElement("div");
    outerDiv.className = "histogram";
    outerDiv.id = aName;

    let divTitle = document.createElement("div");
    divTitle.classList.add("histogram-title");
    divTitle.appendChild(document.createTextNode(aName));
    outerDiv.appendChild(divTitle);

    let divStats = document.createElement("div");
    divStats.classList.add("histogram-stats");

    let histogramStatsArgs = {
      sampleCount: hgram.sample_count,
      prettyAverage: hgram.pretty_average,
      sum: hgram.sum,
    };

    document.l10n.setAttributes(
      divStats,
      "about-telemetry-histogram-stats",
      histogramStatsArgs
    );

    if (isRTL()) {
      hgram.values.reverse();
    }

    let textData = this.renderValues(outerDiv, hgram, options);

    // The 'Copy' button contains the textual data, copied to clipboard on click
    let copyButton = document.createElement("button");
    copyButton.className = "copy-node";
    document.l10n.setAttributes(copyButton, "about-telemetry-histogram-copy");

    copyButton.addEventListener("click", async function () {
      let divStatsString = await document.l10n.formatValue(
        "about-telemetry-histogram-stats",
        histogramStatsArgs
      );
      copyButton.histogramText =
        aName + EOL + divStatsString + EOL + EOL + textData;
      Cc["@mozilla.org/widget/clipboardhelper;1"]
        .getService(Ci.nsIClipboardHelper)
        .copyString(this.histogramText);
    });
    outerDiv.appendChild(copyButton);

    aParent.appendChild(outerDiv);
    return outerDiv;
  },

  processHistogram(aHgram) {
    const values = Object.keys(aHgram.values).map(k => aHgram.values[k]);
    if (!values.length) {
      // If we have no values collected for this histogram, just return
      // zero values so we still render it.
      return {
        values: [],
        pretty_average: 0,
        max: 0,
        sample_count: 0,
        sum: 0,
      };
    }

    const sample_count = values.reduceRight((a, b) => a + b);
    const average = Math.round((aHgram.sum * 10) / sample_count) / 10;
    const max_value = Math.max(...values);

    const labelledValues = Object.keys(aHgram.values).map(k => [
      Number(k),
      aHgram.values[k],
    ]);

    let result = {
      values: labelledValues,
      pretty_average: average,
      max: max_value,
      sample_count,
      sum: aHgram.sum,
    };

    return result;
  },

  /**
   * Return a non-negative, logarithmic representation of a non-negative number.
   * e.g. 0 => 0, 1 => 1, 10 => 2, 100 => 3
   *
   * @param aNumber Non-negative number
   */
  getLogValue(aNumber) {
    return Math.max(0, Math.log10(aNumber) + 1);
  },

  /**
   * Create histogram HTML bars, also returns a textual representation
   * Both aMaxValue and aSumValues must be positive.
   * Values are assumed to use 0 as baseline.
   *
   * @param aDiv Outer parent div
   * @param aHgram The histogram data
   * @param aOptions Object with render options (@see #render)
   */
  renderValues: function Histogram_renderValues(aDiv, aHgram, aOptions) {
    let text = "";
    // If the last label is not the longest string, alignment will break a little
    let labelPadTo = 0;
    if (aHgram.values.length) {
      labelPadTo = String(aHgram.values[aHgram.values.length - 1][0]).length;
    }
    let maxBarValue = aOptions.exponential
      ? this.getLogValue(aHgram.max)
      : aHgram.max;

    for (let [label, value] of aHgram.values) {
      label = String(label);
      let barValue = aOptions.exponential ? this.getLogValue(value) : value;

      // Create a text representation: <right-aligned-label> |<bar-of-#><value>  <percentage>
      text +=
        EOL +
        " ".repeat(Math.max(0, labelPadTo - label.length)) +
        label + // Right-aligned label
        " |" +
        "#".repeat(Math.round((MAX_BAR_CHARS * barValue) / maxBarValue)) + // Bar
        "  " +
        value + // Value
        "  " +
        Math.round((100 * value) / aHgram.sample_count) +
        "%"; // Percentage

      // Construct the HTML labels + bars
      let belowEm =
        Math.round(MAX_BAR_HEIGHT * (barValue / maxBarValue) * 10) / 10;
      let aboveEm = MAX_BAR_HEIGHT - belowEm;

      let barDiv = document.createElement("div");
      barDiv.className = "bar";
      barDiv.style.paddingTop = aboveEm + "em";

      // Add value label or an nbsp if no value
      barDiv.appendChild(document.createTextNode(value ? value : "\u00A0"));

      // Create the blue bar
      let bar = document.createElement("div");
      bar.className = "bar-inner";
      bar.style.height = belowEm + "em";
      barDiv.appendChild(bar);

      // Add a special class to move the text down to prevent text overlap
      if (label.length > 3) {
        bar.classList.add("long-label");
      }
      // Add bucket label
      barDiv.appendChild(document.createTextNode(label));

      aDiv.appendChild(barDiv);
    }

    return text.substr(EOL.length); // Trim the EOL before the first line
  },
};

var Search = {
  HASH_SEARCH: "search=",

  // A list of ids of sections that do not support search.
  blacklist: ["late-writes-section", "raw-payload-section"],

  // Pass if: all non-empty array items match (case-sensitive)
  isPassText(subject, filter) {
    for (let item of filter) {
      if (item.length && !subject.includes(item)) {
        return false; // mismatch and not a spurious space
      }
    }
    return true;
  },

  isPassRegex(subject, filter) {
    return filter.test(subject);
  },

  chooseFilter(filterText) {
    let filter = filterText.toString();
    // Setup normalized filter string (trimmed, lower cased and split on spaces if not RegEx)
    let isPassFunc; // filter function, set once, then applied to all elements
    filter = filter.trim();
    if (filter[0] != "/") {
      // Plain text: case insensitive, AND if multi-string
      isPassFunc = this.isPassText;
      filter = filter.toLowerCase().split(" ");
    } else {
      isPassFunc = this.isPassRegex;
      var r = filter.match(/^\/(.*)\/(i?)$/);
      try {
        filter = RegExp(r[1], r[2]);
      } catch (e) {
        // Incomplete or bad RegExp - always no match
        isPassFunc = function () {
          return false;
        };
      }
    }
    return [isPassFunc, filter];
  },

  filterTextRows(table, filterText) {
    let [isPassFunc, filter] = this.chooseFilter(filterText);
    let allElementHidden = true;

    let needLowerCase = isPassFunc === this.isPassText;
    let elements = table.rows;
    for (let element of elements) {
      if (element.firstChild.nodeName == "th") {
        continue;
      }
      for (let cell of element.children) {
        let subject = needLowerCase
          ? cell.textContent.toLowerCase()
          : cell.textContent;
        element.hidden = !isPassFunc(subject, filter);
        if (!element.hidden) {
          if (allElementHidden) {
            allElementHidden = false;
          }
          // Don't need to check the rest of this row.
          break;
        }
      }
    }
    // Unhide the first row:
    if (!allElementHidden) {
      table.rows[0].hidden = false;
    }
    return allElementHidden;
  },

  filterElements(elements, filterText) {
    let [isPassFunc, filter] = this.chooseFilter(filterText);
    let allElementHidden = true;

    let needLowerCase = isPassFunc === this.isPassText;
    for (let element of elements) {
      let subject = needLowerCase ? element.id.toLowerCase() : element.id;
      element.hidden = !isPassFunc(subject, filter);
      if (allElementHidden && !element.hidden) {
        allElementHidden = false;
      }
    }
    return allElementHidden;
  },

  filterKeyedElements(keyedElements, filterText) {
    let [isPassFunc, filter] = this.chooseFilter(filterText);
    let allElementsHidden = true;

    let needLowerCase = isPassFunc === this.isPassText;
    keyedElements.forEach(keyedElement => {
      let subject = needLowerCase
        ? keyedElement.key.id.toLowerCase()
        : keyedElement.key.id;
      if (!isPassFunc(subject, filter)) {
        // If the keyedHistogram's name is not matched
        let allKeyedElementsHidden = true;
        for (let element of keyedElement.datas) {
          let subject = needLowerCase ? element.id.toLowerCase() : element.id;
          let match = isPassFunc(subject, filter);
          element.hidden = !match;
          if (match) {
            allKeyedElementsHidden = false;
          }
        }
        if (allElementsHidden && !allKeyedElementsHidden) {
          allElementsHidden = false;
        }
        keyedElement.key.hidden = allKeyedElementsHidden;
      } else {
        // If the keyedHistogram's name is matched
        allElementsHidden = false;
        keyedElement.key.hidden = false;
        for (let element of keyedElement.datas) {
          element.hidden = false;
        }
      }
    });
    return allElementsHidden;
  },

  searchHandler(e) {
    if (this.idleTimeout) {
      clearTimeout(this.idleTimeout);
    }
    this.idleTimeout = setTimeout(
      () => Search.search(e.target.value),
      FILTER_IDLE_TIMEOUT
    );
  },

  search(text, sectionParam = null) {
    let section = sectionParam;
    if (!section) {
      let sectionId = document
        .querySelector(".category.selected")
        .getAttribute("value");
      section = document.getElementById(sectionId);
    }
    if (Search.blacklist.includes(section.id)) {
      return false;
    }
    let noSearchResults = true;
    // In the home section, we search all other sections:
    if (section.id === "home-section") {
      return this.homeSearch(text);
    }

    if (section.id === "histograms-section") {
      let histograms = section.getElementsByClassName("histogram");
      noSearchResults = this.filterElements(histograms, text);
    } else if (section.id === "keyed-histograms-section") {
      let keyedElements = [];
      let keyedHistograms = section.getElementsByClassName("keyed-histogram");
      for (let key of keyedHistograms) {
        let datas = key.getElementsByClassName("histogram");
        keyedElements.push({ key, datas });
      }
      noSearchResults = this.filterKeyedElements(keyedElements, text);
    } else if (section.id === "keyed-scalars-section") {
      let keyedElements = [];
      let keyedScalars = section.getElementsByClassName("keyed-scalar");
      for (let key of keyedScalars) {
        let datas = key.querySelector("table").rows;
        keyedElements.push({ key, datas });
      }
      noSearchResults = this.filterKeyedElements(keyedElements, text);
    } else if (section.matches(".text-search")) {
      let tables = section.querySelectorAll("table");
      for (let table of tables) {
        // If we unhide anything, flip noSearchResults to
        // false so we don't show the "no results" bits.
        if (!this.filterTextRows(table, text)) {
          noSearchResults = false;
        }
      }
    } else if (section.querySelector(".sub-section")) {
      let keyedSubSections = [];
      let subsections = section.querySelectorAll(".sub-section");
      for (let section of subsections) {
        let datas = section.querySelector("table").rows;
        keyedSubSections.push({ key: section, datas });
      }
      noSearchResults = this.filterKeyedElements(keyedSubSections, text);
    } else {
      let tables = section.querySelectorAll("table");
      for (let table of tables) {
        noSearchResults = this.filterElements(table.rows, text);
        if (table.caption) {
          table.caption.hidden = noSearchResults;
        }
      }
    }

    changeUrlSearch(text);

    if (!sectionParam) {
      // If we are not searching in all section.
      this.updateNoResults(text, noSearchResults);
    }
    return noSearchResults;
  },

  updateNoResults(text, noSearchResults) {
    document
      .getElementById("no-search-results")
      .classList.toggle("hidden", !noSearchResults);
    if (noSearchResults) {
      let section = document.querySelector(".category.selected > span");
      let searchResultsText = document.getElementById("no-search-results-text");
      if (section.parentElement.id === "category-home") {
        document.l10n.setAttributes(
          searchResultsText,
          "about-telemetry-no-search-results-all",
          { searchTerms: text }
        );
      } else {
        let sectionName = section.textContent.trim();
        text === ""
          ? document.l10n.setAttributes(
              searchResultsText,
              "about-telemetry-no-data-to-display",
              { sectionName }
            )
          : document.l10n.setAttributes(
              searchResultsText,
              "about-telemetry-no-search-results",
              { sectionName, currentSearchText: text }
            );
      }
    }
  },

  resetHome() {
    document.getElementById("main").classList.remove("search");
    document.getElementById("no-search-results").classList.add("hidden");
    adjustHeaderState();
    Array.from(document.querySelectorAll("section")).forEach(section => {
      section.classList.toggle("active", section.id == "home-section");
    });
  },

  homeSearch(text) {
    changeUrlSearch(text);
    removeSearchSectionTitles();
    if (text === "") {
      this.resetHome();
      return;
    }
    document.getElementById("main").classList.add("search");
    adjustHeaderState(text);
    let noSearchResults = true;
    Array.from(document.querySelectorAll("section")).forEach(section => {
      if (section.id == "home-section" || section.id == "raw-payload-section") {
        section.classList.remove("active");
        return;
      }
      section.classList.add("active");
      let sectionHidden = this.search(text, section);
      if (!sectionHidden) {
        let sectionTitle = document.querySelector(
          `.category[value="${section.id}"] .category-name`
        ).textContent;
        let sectionDataDiv = document.querySelector(
          `#${section.id}.has-data.active .data`
        );
        let titleDiv = document.createElement("h1");
        titleDiv.classList.add("data", "search-section-title");
        titleDiv.textContent = sectionTitle;
        section.insertBefore(titleDiv, sectionDataDiv);
        noSearchResults = false;
      } else {
        // Hide all subsections if the section is hidden
        let subsections = section.querySelectorAll(".sub-section");
        for (let subsection of subsections) {
          subsection.hidden = true;
        }
      }
    });
    this.updateNoResults(text, noSearchResults);
  },
};

/*
 * Helper function to render JS objects with white space between top level elements
 * so that they look better in the browser
 * @param   aObject JavaScript object or array to render
 * @return  String
 */
function RenderObject(aObject) {
  let output = "";
  if (Array.isArray(aObject)) {
    if (!aObject.length) {
      return "[]";
    }
    output = "[" + JSON.stringify(aObject[0]);
    for (let i = 1; i < aObject.length; i++) {
      output += ", " + JSON.stringify(aObject[i]);
    }
    return output + "]";
  }
  let keys = Object.keys(aObject);
  if (!keys.length) {
    return "{}";
  }
  output = '{"' + keys[0] + '":\u00A0' + JSON.stringify(aObject[keys[0]]);
  for (let i = 1; i < keys.length; i++) {
    output += ', "' + keys[i] + '":\u00A0' + JSON.stringify(aObject[keys[i]]);
  }
  return output + "}";
}

var GenericSubsection = {
  addSubSectionToSidebar(id, title) {
    let category = document.querySelector("#categories > [value=" + id + "]");
    category.classList.add("has-subsection");
    let subCategory = document.createElement("div");
    subCategory.classList.add("category-subsection");
    subCategory.setAttribute("value", id + "-" + title);
    subCategory.addEventListener("click", ev => {
      let section = ev.target;
      showSubSection(section);
    });
    subCategory.appendChild(document.createTextNode(title));
    category.appendChild(subCategory);
  },

  render(data, dataDiv, sectionID) {
    for (let [title, sectionData] of data) {
      let hasData = sectionData.size > 0;
      let s = this.renderSubsectionHeader(title, hasData, sectionID);
      s.appendChild(this.renderSubsectionData(title, sectionData));
      dataDiv.appendChild(s);
    }
  },

  renderSubsectionHeader(title, hasData, sectionID) {
    this.addSubSectionToSidebar(sectionID, title);
    let section = document.createElement("div");
    section.setAttribute("id", sectionID + "-" + title);
    section.classList.add("sub-section");
    if (hasData) {
      section.classList.add("has-subdata");
    }
    return section;
  },

  renderSubsectionData(title, data) {
    // Create data container
    let dataDiv = document.createElement("div");
    dataDiv.setAttribute("class", "subsection-data subdata");
    // Instanciate the data
    let table = GenericTable.render(data);
    let caption = document.createElement("caption");
    caption.textContent = title;
    table.appendChild(caption);
    dataDiv.appendChild(table);

    return dataDiv;
  },

  deleteAllSubSections() {
    let subsections = document.querySelectorAll(".category-subsection");
    subsections.forEach(el => {
      el.parentElement.removeChild(el);
    });
  },
};

var GenericTable = {
  // Returns a table with key and value headers
  defaultHeadings() {
    return ["about-telemetry-keys-header", "about-telemetry-values-header"];
  },

  /**
   * Returns a n-column table.
   * @param rows An array of arrays, each containing data to render
   *             for one row.
   * @param headings The column header strings.
   */
  render(rows, headings = this.defaultHeadings()) {
    let table = document.createElement("table");
    this.renderHeader(table, headings);
    this.renderBody(table, rows);
    return table;
  },

  /**
   * Create the table header.
   * Tabs & newlines added to cells to make it easier to copy-paste.
   *
   * @param table Table element
   * @param headings Array of column header strings.
   */
  renderHeader(table, headings) {
    let headerRow = document.createElement("tr");
    table.appendChild(headerRow);

    for (let i = 0; i < headings.length; ++i) {
      let column = document.createElement("th");
      document.l10n.setAttributes(column, headings[i]);
      headerRow.appendChild(column);
    }
  },

  /**
   * Create the table body
   * Tabs & newlines added to cells to make it easier to copy-paste.
   *
   * @param table Table element
   * @param rows An array of arrays, each containing data to render
   *             for one row.
   */
  renderBody(table, rows) {
    for (let row of rows) {
      row = row.map(value => {
        // use .valueOf() to unbox Number, String, etc. objects
        if (
          value &&
          typeof value == "object" &&
          typeof value.valueOf() == "object"
        ) {
          return RenderObject(value);
        }
        return value;
      });

      let newRow = document.createElement("tr");
      newRow.id = row[0];
      table.appendChild(newRow);

      for (let i = 0; i < row.length; ++i) {
        let suffix = i == row.length - 1 ? "\n" : "\t";
        let field = document.createElement("td");
        field.appendChild(document.createTextNode(row[i] + suffix));
        newRow.appendChild(field);
      }
    }
  },
};

var KeyedHistogram = {
  render(parent, id, keyedHistogram) {
    let outerDiv = document.createElement("div");
    outerDiv.className = "keyed-histogram";
    outerDiv.id = id;

    let divTitle = document.createElement("div");
    divTitle.classList.add("keyed-title");
    divTitle.appendChild(document.createTextNode(id));
    outerDiv.appendChild(divTitle);

    for (let [name, hgram] of Object.entries(keyedHistogram)) {
      Histogram.render(outerDiv, name, hgram);
    }

    parent.appendChild(outerDiv);
    return outerDiv;
  },
};

var AddonDetails = {
  /**
   * Render the addon details section as a series of headers followed by key/value tables
   * @param aPing A ping object to render the data from.
   */
  render(aPing) {
    let addonSection = document.getElementById("addon-details");
    removeAllChildNodes(addonSection);
    let addonDetails = aPing.payload.addonDetails;
    const hasData = addonDetails && !!Object.keys(addonDetails).length;
    setHasData("addon-details-section", hasData);
    if (!hasData) {
      return;
    }

    for (let provider in addonDetails) {
      let providerSection = document.createElement("caption");
      document.l10n.setAttributes(
        providerSection,
        "about-telemetry-addon-provider",
        { addonProvider: provider }
      );
      let headingStrings = [
        "about-telemetry-addon-table-id",
        "about-telemetry-addon-table-details",
      ];
      let table = GenericTable.render(
        explodeObject(addonDetails[provider]),
        headingStrings
      );
      table.appendChild(providerSection);
      addonSection.appendChild(table);
    }
  },
};

class Section {
  static renderContent(data, process, div, section) {
    if (data && Object.keys(data).length) {
      let s = GenericSubsection.renderSubsectionHeader(process, true, section);
      let heading = document.createElement("h2");
      document.l10n.setAttributes(heading, "about-telemetry-process", {
        process,
      });
      s.appendChild(heading);

      this.renderData(data, s);

      div.appendChild(s);
      let separator = document.createElement("div");
      separator.classList.add("clearfix");
      div.appendChild(separator);
    }
  }

  /**
   * Make parent process the first one, content process the second
   * then sort processes alphabetically
   */
  static processesComparator(a, b) {
    if (a === "parent" || (a === "content" && b !== "parent")) {
      return -1;
    } else if (b === "parent" || b === "content") {
      return 1;
    } else if (a < b) {
      return -1;
    } else if (a > b) {
      return 1;
    }
    return 0;
  }

  /**
   * Render sections
   */
  static renderSection(divName, section, aPayload) {
    let div = document.getElementById(divName);
    removeAllChildNodes(div);

    let data = {};
    let hasData = false;
    let selectedStore = getSelectedStore();

    let payload = aPayload.stores;

    let isCurrentPayload = !!payload;

    // Sort processes
    let sortedProcesses = isCurrentPayload
      ? Object.keys(payload[selectedStore]).sort(this.processesComparator)
      : Object.keys(aPayload.processes).sort(this.processesComparator);

    // Render content by process
    for (const process of sortedProcesses) {
      data = isCurrentPayload
        ? this.dataFiltering(payload, selectedStore, process)
        : this.archivePingDataFiltering(aPayload, process);
      hasData = hasData || !ObjectUtils.isEmpty(data);
      this.renderContent(data, process, div, section, this.renderData);
    }
    setHasData(section, hasData);
  }
}

class Scalars extends Section {
  /**
   * Return data from the current ping
   */
  static dataFiltering(payload, selectedStore, process) {
    return payload[selectedStore][process].scalars;
  }

  /**
   * Return data from an archived ping
   */
  static archivePingDataFiltering(payload, process) {
    return payload.processes[process].scalars;
  }

  static renderData(data, div) {
    const scalarsHeadings = [
      "about-telemetry-names-header",
      "about-telemetry-values-header",
    ];
    let scalarsTable = GenericTable.render(
      explodeObject(data),
      scalarsHeadings
    );
    div.appendChild(scalarsTable);
  }

  /**
   * Render the scalar data - if present - from the payload in a simple key-value table.
   * @param aPayload A payload object to render the data from.
   */
  static render(aPayload) {
    const divName = "scalars";
    const section = "scalars-section";
    this.renderSection(divName, section, aPayload);
  }
}

class KeyedScalars extends Section {
  /**
   * Return data from the current ping
   */
  static dataFiltering(payload, selectedStore, process) {
    return payload[selectedStore][process].keyedScalars;
  }

  /**
   * Return data from an archived ping
   */
  static archivePingDataFiltering(payload, process) {
    return payload.processes[process].keyedScalars;
  }

  static renderData(data, div) {
    const scalarsHeadings = [
      "about-telemetry-names-header",
      "about-telemetry-values-header",
    ];
    for (let scalarId in data) {
      // Add the name of the scalar.
      let container = document.createElement("div");
      container.classList.add("keyed-scalar");
      container.id = scalarId;
      let scalarNameSection = document.createElement("p");
      scalarNameSection.classList.add("keyed-title");
      scalarNameSection.appendChild(document.createTextNode(scalarId));
      container.appendChild(scalarNameSection);
      // Populate the section with the key-value pairs from the scalar.
      const table = GenericTable.render(
        explodeObject(data[scalarId]),
        scalarsHeadings
      );
      container.appendChild(table);
      div.appendChild(container);
    }
  }

  /**
   * Render the keyed scalar data - if present - from the payload in a simple key-value table.
   * @param aPayload A payload object to render the data from.
   */
  static render(aPayload) {
    const divName = "keyed-scalars";
    const section = "keyed-scalars-section";
    this.renderSection(divName, section, aPayload);
  }
}

var Events = {
  /**
   * Render the event data - if present - from the payload in a simple table.
   * @param aPayload A payload object to render the data from.
   */
  render(aPayload) {
    let eventsDiv = document.getElementById("events");
    removeAllChildNodes(eventsDiv);
    const headings = [
      "about-telemetry-time-stamp-header",
      "about-telemetry-category-header",
      "about-telemetry-method-header",
      "about-telemetry-object-header",
      "about-telemetry-values-header",
      "about-telemetry-extra-header",
    ];
    let payload = aPayload.processes;
    let hasData = false;
    if (payload) {
      for (const process of Object.keys(aPayload.processes)) {
        let data = aPayload.processes[process].events;
        if (data && Object.keys(data).length) {
          hasData = true;
          let s = GenericSubsection.renderSubsectionHeader(
            process,
            true,
            "events-section"
          );
          let heading = document.createElement("h2");
          heading.textContent = process;
          s.appendChild(heading);
          const table = GenericTable.render(data, headings);
          s.appendChild(table);
          eventsDiv.appendChild(s);
          let separator = document.createElement("div");
          separator.classList.add("clearfix");
          eventsDiv.appendChild(separator);
        }
      }
    } else {
      // handle archived ping
      for (const process of Object.keys(aPayload.events)) {
        let data = process;
        if (data && Object.keys(data).length) {
          hasData = true;
          let s = GenericSubsection.renderSubsectionHeader(
            process,
            true,
            "events-section"
          );
          let heading = document.createElement("h2");
          heading.textContent = process;
          s.appendChild(heading);
          const table = GenericTable.render(data, headings);
          eventsDiv.appendChild(table);
          let separator = document.createElement("div");
          separator.classList.add("clearfix");
          eventsDiv.appendChild(separator);
        }
      }
    }
    setHasData("events-section", hasData);
  },
};

/**
 * Helper function for showing either the toggle element or "No data collected" message for a section
 *
 * @param aSectionID ID of the section element that needs to be changed
 * @param aHasData true (default) indicates that toggle should be displayed
 */
function setHasData(aSectionID, aHasData) {
  let sectionElement = document.getElementById(aSectionID);
  sectionElement.classList[aHasData ? "add" : "remove"]("has-data");

  // Display or Hide the section in the sidebar
  let sectionCategory = document.querySelector(
    ".category[value=" + aSectionID + "]"
  );
  sectionCategory.classList[aHasData ? "add" : "remove"]("has-data");
}

/**
 * Sets l10n attributes based on the Telemetry Server Owner pref.
 */
function setupServerOwnerBranding() {
  let serverOwner = Preferences.get(PREF_TELEMETRY_SERVER_OWNER, "Mozilla");
  const elements = [
    [document.getElementById("page-subtitle"), "about-telemetry-page-subtitle"],
  ];
  for (const [elt, l10nName] of elements) {
    document.l10n.setAttributes(elt, l10nName, {
      telemetryServerOwner: serverOwner,
    });
  }
}

/**
 * Display the store selector if we are on one
 * of the whitelisted sections
 */
function displayStoresSelector(selectedSection) {
  let whitelist = [
    "scalars-section",
    "keyed-scalars-section",
    "histograms-section",
    "keyed-histograms-section",
  ];
  let stores = document.getElementById("stores");
  stores.hidden = !whitelist.includes(selectedSection);
  let storesLabel = document.getElementById("storesLabel");
  storesLabel.hidden = !whitelist.includes(selectedSection);
}

function refreshSearch() {
  removeSearchSectionTitles();
  let selectedSection = document
    .querySelector(".category.selected")
    .getAttribute("value");
  let search = document.getElementById("search");
  if (!Search.blacklist.includes(selectedSection)) {
    Search.search(search.value);
  }
}

function adjustSearchState() {
  removeSearchSectionTitles();
  let selectedSection = document
    .querySelector(".category.selected")
    .getAttribute("value");
  let search = document.getElementById("search");
  search.value = "";
  search.hidden = Search.blacklist.includes(selectedSection);
  document.getElementById("no-search-results").classList.add("hidden");
  Search.search(""); // reinitialize search state.
}

function removeSearchSectionTitles() {
  for (let sectionTitleDiv of Array.from(
    document.getElementsByClassName("search-section-title")
  )) {
    sectionTitleDiv.remove();
  }
}

function adjustSection() {
  let selectedCategory = document.querySelector(".category.selected");
  if (!selectedCategory.classList.contains("has-data")) {
    PingPicker._showStructuredPingData();
  }
}

function adjustHeaderState(title = null) {
  let selected = document.querySelector(".category.selected .category-name");
  let selectedTitle = selected.textContent.trim();
  let sectionTitle = document.getElementById("sectionTitle");
  if (title !== null) {
    document.l10n.setAttributes(
      sectionTitle,
      "about-telemetry-results-for-search",
      { searchTerms: title }
    );
  } else {
    sectionTitle.textContent = selectedTitle;
  }
  let search = document.getElementById("search");
  if (selected.parentElement.id === "category-home") {
    document.l10n.setAttributes(
      search,
      "about-telemetry-filter-all-placeholder"
    );
  } else {
    document.l10n.setAttributes(search, "about-telemetry-filter-placeholder", {
      selectedTitle,
    });
  }
}

/**
 * Change the url according to the current section displayed
 * e.g about:telemetry#general-data
 */
function changeUrlPath(selectedSection, subSection) {
  if (subSection) {
    let hash = window.location.hash.split("_")[0] + "_" + selectedSection;
    window.location.hash = hash;
  } else {
    window.location.hash = selectedSection.replace("-section", "-tab");
  }
}

/**
 * Change the url according to the current search text
 */
function changeUrlSearch(searchText) {
  let currentHash = window.location.hash;
  let hashWithoutSearch = currentHash.split(Search.HASH_SEARCH)[0];
  let hash = "";

  if (!currentHash && !searchText) {
    return;
  }
  if (!currentHash.includes(Search.HASH_SEARCH) && hashWithoutSearch) {
    hashWithoutSearch += "_";
  }
  if (searchText) {
    hash =
      hashWithoutSearch + Search.HASH_SEARCH + searchText.replace(/ /g, "+");
  } else if (hashWithoutSearch) {
    hash = hashWithoutSearch.slice(0, hashWithoutSearch.length - 1);
  }

  window.location.hash = hash;
}

/**
 * Change the section displayed
 */
function show(selected) {
  let selectedValue = selected.getAttribute("value");
  if (selectedValue === "raw-json-viewer") {
    openJsonInFirefoxJsonViewer(JSON.stringify(gPingData, null, 2));
    return;
  }

  let selected_section = document.getElementById(selectedValue);
  let subsections = selected_section.querySelectorAll(".sub-section");
  if (selected.classList.contains("has-subsection")) {
    for (let subsection of selected.children) {
      subsection.classList.remove("selected");
    }
  }
  if (subsections) {
    for (let subsection of subsections) {
      subsection.hidden = false;
    }
  }

  let current_button = document.querySelector(".category.selected");
  if (current_button == selected) {
    return;
  }
  current_button.classList.remove("selected");
  selected.classList.add("selected");

  document.querySelectorAll("section").forEach(section => {
    section.classList.remove("active");
  });
  selected_section.classList.add("active");

  adjustHeaderState();
  displayStoresSelector(selectedValue);
  adjustSearchState();
  changeUrlPath(selectedValue);
}

function showSubSection(selected) {
  if (!selected) {
    return;
  }
  let current_selection = document.querySelector(
    ".category-subsection.selected"
  );
  if (current_selection) {
    current_selection.classList.remove("selected");
  }
  selected.classList.add("selected");

  let section = document.getElementById(selected.getAttribute("value"));
  section.parentElement.childNodes.forEach(element => {
    element.hidden = true;
  });
  section.hidden = false;

  let title =
    selected.parentElement.querySelector(".category-name").textContent;
  let subsection = selected.textContent;
  document.getElementById("sectionTitle").textContent =
    title + " - " + subsection;
  changeUrlPath(subsection, true);
}

/**
 * Initializes load/unload, pref change and mouse-click listeners
 */
function setupListeners() {
  Settings.attachObservers();
  PingPicker.attachObservers();
  RawPayloadData.attachObservers();

  let menu = document.getElementById("categories");
  menu.addEventListener("click", e => {
    if (e.target && e.target.parentNode == menu) {
      show(e.target);
    }
  });

  let search = document.getElementById("search");
  search.addEventListener("input", Search.searchHandler);

  document
    .getElementById("late-writes-fetch-symbols")
    .addEventListener("click", function () {
      if (!gPingData) {
        return;
      }

      let lateWrites = gPingData.payload.lateWrites;
      let req = new SymbolicationRequest(
        "late-writes",
        LateWritesSingleton.renderHeader,
        lateWrites.memoryMap,
        lateWrites.stacks
      );
      req.fetchSymbols();
    });

  document
    .getElementById("late-writes-hide-symbols")
    .addEventListener("click", function () {
      if (!gPingData) {
        return;
      }

      LateWritesSingleton.renderLateWrites(gPingData.payload.lateWrites);
    });
}

// Restores the sections states
function urlSectionRestore(hash) {
  if (hash) {
    let section = hash.replace("-tab", "-section");
    let subsection = section.split("_")[1];
    section = section.split("_")[0];
    let category = document.querySelector(".category[value=" + section + "]");
    if (category) {
      show(category);
      if (subsection) {
        let selector =
          ".category-subsection[value=" + section + "-" + subsection + "]";
        let subcategory = document.querySelector(selector);
        showSubSection(subcategory);
      }
    }
  }
}

// Restore sections states and search terms
function urlStateRestore() {
  let hash = window.location.hash;
  let searchQuery = "";
  if (hash) {
    hash = hash.slice(1);
    if (hash.includes(Search.HASH_SEARCH)) {
      searchQuery = hash.split(Search.HASH_SEARCH)[1].replace(/[+]/g, " ");
      hash = hash.split(Search.HASH_SEARCH)[0];
    }
    urlSectionRestore(hash);
  }
  if (searchQuery) {
    let search = document.getElementById("search");
    search.value = searchQuery;
  }
}

function openJsonInFirefoxJsonViewer(json) {
  json = unescape(encodeURIComponent(json));
  try {
    window.open("data:application/json;base64," + btoa(json));
  } catch (e) {
    show(document.querySelector(".category[value=raw-payload-section]"));
  }
}

function onLoad() {
  window.removeEventListener("load", onLoad);
  // Set the text in the page header and elsewhere that needs the server owner.
  setupServerOwnerBranding();

  // Set up event listeners
  setupListeners();

  // Render settings.
  Settings.render();

  adjustHeaderState();

  urlStateRestore();

  // Update ping data when async Telemetry init is finished.
  Telemetry.asyncFetchTelemetryData(async () => {
    await PingPicker.update();
  });
}

var LateWritesSingleton = {
  renderHeader: function LateWritesSingleton_renderHeader(aIndex) {
    StackRenderer.renderHeader(
      "late-writes",
      "about-telemetry-late-writes-title",
      { lateWriteCount: aIndex + 1 }
    );
  },

  renderLateWrites: function LateWritesSingleton_renderLateWrites(lateWrites) {
    let hasData = !!(
      lateWrites &&
      lateWrites.stacks &&
      lateWrites.stacks.length
    );
    setHasData("late-writes-section", hasData);
    if (!hasData) {
      return;
    }

    let stacks = lateWrites.stacks;
    let memoryMap = lateWrites.memoryMap;
    StackRenderer.renderStacks(
      "late-writes",
      stacks,
      memoryMap,
      LateWritesSingleton.renderHeader
    );
  },
};

class HistogramSection extends Section {
  /**
   * Return data from the current ping
   */
  static dataFiltering(payload, selectedStore, process) {
    return payload[selectedStore][process].histograms;
  }

  /**
   * Return data from an archived ping
   */
  static archivePingDataFiltering(payload, process) {
    if (process === "parent") {
      return payload.histograms;
    }
    return payload.processes[process].histograms;
  }

  static renderData(data, div) {
    for (let [hName, hgram] of Object.entries(data)) {
      Histogram.render(div, hName, hgram, { unpacked: true });
    }
  }

  static render(aPayload) {
    const divName = "histograms";
    const section = "histograms-section";
    this.renderSection(divName, section, aPayload);
  }
}

class KeyedHistogramSection extends Section {
  /**
   * Return data from the current ping
   */
  static dataFiltering(payload, selectedStore, process) {
    return payload[selectedStore][process].keyedHistograms;
  }

  /**
   * Return data from an archived ping
   */
  static archivePingDataFiltering(payload, process) {
    if (process === "parent") {
      return payload.keyedHistograms;
    }
    return payload.processes[process].keyedHistograms;
  }

  static renderData(data, div) {
    for (let [id, keyed] of Object.entries(data)) {
      KeyedHistogram.render(div, id, keyed, { unpacked: true });
    }
  }

  static render(aPayload) {
    const divName = "keyed-histograms";
    const section = "keyed-histograms-section";
    this.renderSection(divName, section, aPayload);
  }
}

var SessionInformation = {
  render(aPayload) {
    let infoSection = document.getElementById("session-info");
    removeAllChildNodes(infoSection);

    let hasData = !!Object.keys(aPayload.info).length;
    setHasData("session-info-section", hasData);

    if (hasData) {
      const table = GenericTable.render(explodeObject(aPayload.info));
      infoSection.appendChild(table);
    }
  },
};

var SimpleMeasurements = {
  render(aPayload) {
    let simpleSection = document.getElementById("simple-measurements");
    removeAllChildNodes(simpleSection);

    let simpleMeasurements = this.sortStartupMilestones(
      aPayload.simpleMeasurements
    );
    let hasData = !!Object.keys(simpleMeasurements).length;
    setHasData("simple-measurements-section", hasData);

    if (hasData) {
      const table = GenericTable.render(explodeObject(simpleMeasurements));
      simpleSection.appendChild(table);
    }
  },

  /**
   * Helper function for sorting the startup milestones in the Simple Measurements
   * section into temporal order.
   *
   * @param aSimpleMeasurements Telemetry ping's "Simple Measurements" data
   * @return Sorted measurements
   */
  sortStartupMilestones(aSimpleMeasurements) {
    const telemetryTimestamps = TelemetryTimestamps.get();
    let startupEvents = Services.startup.getStartupInfo();
    delete startupEvents.process;

    function keyIsMilestone(k) {
      return k in startupEvents || k in telemetryTimestamps;
    }

    let sortedKeys = Object.keys(aSimpleMeasurements);

    // Sort the measurements, with startup milestones at the front + ordered by time
    sortedKeys.sort(function keyCompare(keyA, keyB) {
      let isKeyAMilestone = keyIsMilestone(keyA);
      let isKeyBMilestone = keyIsMilestone(keyB);

      // First order by startup vs non-startup measurement
      if (isKeyAMilestone && !isKeyBMilestone) {
        return -1;
      }
      if (!isKeyAMilestone && isKeyBMilestone) {
        return 1;
      }
      // Don't change order of non-startup measurements
      if (!isKeyAMilestone && !isKeyBMilestone) {
        return 0;
      }

      // If both keys are startup measurements, order them by value
      return aSimpleMeasurements[keyA] - aSimpleMeasurements[keyB];
    });

    // Insert measurements into a result object in sort-order
    let result = {};
    for (let key of sortedKeys) {
      result[key] = aSimpleMeasurements[key];
    }

    return result;
  },
};

/**
 * Render stores options
 */
function renderStoreList(payload) {
  let storeSelect = document.getElementById("stores");
  let storesLabel = document.getElementById("storesLabel");
  removeAllChildNodes(storeSelect);

  if (!("stores" in payload)) {
    storeSelect.classList.add("hidden");
    storesLabel.classList.add("hidden");
    return;
  }

  storeSelect.classList.remove("hidden");
  storesLabel.classList.remove("hidden");
  storeSelect.disabled = false;

  for (let store of Object.keys(payload.stores)) {
    let option = document.createElement("option");
    option.appendChild(document.createTextNode(store));
    option.setAttribute("value", store);
    // Select main store by default
    if (store === "main") {
      option.selected = true;
    }
    storeSelect.appendChild(option);
  }
}

/**
 * Return the selected store
 */
function getSelectedStore() {
  let storeSelect = document.getElementById("stores");
  let storeSelectedOption = storeSelect.selectedOptions.item(0);
  let selectedStore =
    storeSelectedOption !== null
      ? storeSelectedOption.getAttribute("value")
      : undefined;
  return selectedStore;
}

function togglePingSections(isMainPing) {
  // We always show the sections that are "common" to all pings.
  let commonSections = new Set([
    "heading",
    "home-section",
    "general-data-section",
    "environment-data-section",
    "raw-json-viewer",
  ]);

  let elements = document.querySelectorAll(".category");
  for (let section of elements) {
    if (commonSections.has(section.getAttribute("value"))) {
      continue;
    }
    // Only show the raw payload for non main ping.
    if (section.getAttribute("value") == "raw-payload-section") {
      section.classList.toggle("has-data", !isMainPing);
    } else {
      section.classList.toggle("has-data", isMainPing);
    }
  }
}

function displayPingData(ping, updatePayloadList = false) {
  gPingData = ping;
  try {
    PingPicker.render();
    displayRichPingData(ping, updatePayloadList);
    adjustSection();
    refreshSearch();
  } catch (err) {
    console.log(err);
    PingPicker._showRawPingData();
  }
}

function displayRichPingData(ping, updatePayloadList) {
  // Update the payload list and store lists
  if (updatePayloadList) {
    renderStoreList(ping.payload);
  }

  // Show general data.
  GeneralData.render(ping);

  // Show environment data.
  EnvironmentData.render(ping);

  RawPayloadData.render(ping);

  // We have special rendering code for the payloads from "main" and "event" pings.
  // For any other pings we just render the raw JSON payload.
  let isMainPing = ping.type == "main" || ping.type == "saved-session";
  let isEventPing = ping.type == "event";
  togglePingSections(isMainPing);

  if (isEventPing) {
    // Copy the payload, so we don't modify the raw representation
    // Ensure we always have at least the parent process.
    let payload = { processes: { parent: {} } };
    for (let process of Object.keys(ping.payload.events)) {
      payload.processes[process] = {
        events: ping.payload.events[process],
      };
    }

    // We transformed the actual payload, let's reload the store list if necessary.
    if (updatePayloadList) {
      renderStoreList(payload);
    }

    // Show event data.
    Events.render(payload);
    return;
  }

  if (!isMainPing) {
    return;
  }

  // Show slow SQL stats
  SlowSQL.render(ping);

  // Render Addon details.
  AddonDetails.render(ping);

  let payload = ping.payload;
  // Show basic session info gathered
  SessionInformation.render(payload);

  // Show scalar data.
  Scalars.render(payload);
  KeyedScalars.render(payload);

  // Show histogram data
  HistogramSection.render(payload);

  // Show keyed histogram data
  KeyedHistogramSection.render(payload);

  // Show event data.
  Events.render(payload);

  LateWritesSingleton.renderLateWrites(payload.lateWrites);

  // Show simple measurements
  SimpleMeasurements.render(payload);
}

window.addEventListener("load", onLoad);
PK
!<c�O�+�+2chrome/toolkit/content/global/aboutTelemetry.xhtml<?xml version="1.0" encoding="UTF-8"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome: resource:; object-src 'none'"
    />
    <meta name="color-scheme" content="light dark" />
    <title data-l10n-id="about-telemetry-page-title"></title>
    <link
      rel="stylesheet"
      href="chrome://global/content/aboutTelemetry.css"
      type="text/css"
    />

    <script src="chrome://global/content/aboutTelemetry.js" />
    <link rel="localization" href="branding/brand.ftl" />
    <link rel="localization" href="toolkit/about/aboutTelemetry.ftl" />
  </head>

  <body id="body">
    <div id="categories">
      <div class="heading">
        <span id="ping-type" class="change-ping dropdown"></span>
        <div id="controls" hidden="true">
          <span
            id="older-ping"
            data-l10n-id="about-telemetry-previous-ping"
          ></span>
          <span id="ping-date" class="change-ping"></span>
          <span id="newer-ping" data-l10n-id="about-telemetry-next-ping"></span>
        </div>
      </div>
      <div
        id="category-home"
        class="category category-no-icon has-data selected"
        value="home-section"
      >
        <span
          class="category-name"
          data-l10n-id="about-telemetry-home-section"
        ></span>
      </div>
      <div class="category category-no-icon" value="general-data-section">
        <span
          class="category-name"
          data-l10n-id="about-telemetry-general-data-section"
        ></span>
      </div>
      <div class="category category-no-icon" value="environment-data-section">
        <span
          class="category-name"
          data-l10n-id="about-telemetry-environment-data-section"
        ></span>
      </div>
      <div class="category category-no-icon" value="session-info-section">
        <span
          class="category-name"
          data-l10n-id="about-telemetry-session-info-section"
        ></span>
      </div>
      <div class="category category-no-icon" value="scalars-section">
        <span
          class="category-name"
          data-l10n-id="about-telemetry-scalar-section"
        ></span>
      </div>
      <div class="category category-no-icon" value="keyed-scalars-section">
        <span
          class="category-name"
          data-l10n-id="about-telemetry-keyed-scalar-section"
        ></span>
      </div>
      <div class="category category-no-icon" value="histograms-section">
        <span
          class="category-name"
          data-l10n-id="about-telemetry-histograms-section"
        ></span>
      </div>
      <div class="category category-no-icon" value="keyed-histograms-section">
        <span
          class="category-name"
          data-l10n-id="about-telemetry-keyed-histogram-section"
        ></span>
      </div>
      <div class="category category-no-icon" value="events-section">
        <span
          class="category-name"
          data-l10n-id="about-telemetry-events-section"
        ></span>
      </div>
      <div
        class="category category-no-icon"
        value="simple-measurements-section"
      >
        <span
          class="category-name"
          data-l10n-id="about-telemetry-simple-measurements-section"
        ></span>
      </div>
      <div class="category category-no-icon" value="slow-sql-section">
        <span
          class="category-name"
          data-l10n-id="about-telemetry-slow-sql-section"
        ></span>
      </div>
      <div class="category category-no-icon" value="addon-details-section">
        <span
          class="category-name"
          data-l10n-id="about-telemetry-addon-details-section"
        ></span>
      </div>
      <div class="category category-no-icon" value="late-writes-section">
        <span
          class="category-name"
          data-l10n-id="about-telemetry-late-writes-section"
        ></span>
      </div>
      <div
        class="category category-no-icon has-data"
        value="raw-payload-section"
      >
        <span
          class="category-name"
          data-l10n-id="about-telemetry-raw-payload-section"
        ></span>
      </div>
      <div
        id="category-raw"
        class="category category-no-icon has-data"
        value="raw-json-viewer"
      >
        <span class="category-name" data-l10n-id="about-telemetry-raw"></span>
      </div>
    </div>

    <div id="main" class="main-content">
      <div id="ping-picker" class="hidden">
        <div id="ping-source-picker">
          <h4
            class="title"
            data-l10n-id="about-telemetry-ping-data-source"
          ></h4>
          <label class="radio-container-with-text">
            <input
              type="radio"
              id="ping-source-current"
              name="choose-ping-source"
              value="current"
              checked="checked"
            />
            <span data-l10n-id="about-telemetry-show-current-data"></span>
          </label>
          <label
            id="ping-source-archive-container"
            class="radio-container-with-text"
          >
            <input
              type="radio"
              id="ping-source-archive"
              name="choose-ping-source"
              value="archive"
            />
            <span data-l10n-id="about-telemetry-show-archived-ping-data"></span>
          </label>
        </div>
        <label id="current-ping-picker" class="toggle-container-with-text">
          <input id="show-subsession-data" type="checkbox" checked="checked" />
          <span data-l10n-id="about-telemetry-show-subsession-data"></span>
        </label>
        <div id="archived-ping-picker">
          <h4 class="title" data-l10n-id="about-telemetry-choose-ping"></h4>
          <div>
            <h4
              class="title"
              data-l10n-id="about-telemetry-archive-ping-type"
            ></h4>
            <select id="choose-ping-type"></select>
          </div>
          <div>
            <h4
              class="title"
              data-l10n-id="about-telemetry-archive-ping-header"
            ></h4>
            <select id="choose-ping-id">
              <optgroup
                data-l10n-id="about-telemetry-option-group-today"
              ></optgroup>
              <optgroup
                data-l10n-id="about-telemetry-option-group-yesterday"
              ></optgroup>
              <optgroup
                data-l10n-id="about-telemetry-option-group-older"
              ></optgroup>
            </select>
          </div>
        </div>
      </div>

      <div class="header">
        <h1
          id="sectionTitle"
          class="header-name"
          data-l10n-id="about-telemetry-page-title"
        />
        <div id="sectionFilters">
          <label
            id="storesLabel"
            for="stores"
            hidden="true"
            data-l10n-id="about-telemetry-current-store"
          />
          <select id="stores" hidden="true"></select>
          <input type="text" id="search" placeholder="" />
        </div>
      </div>

      <div id="no-search-results" hidden="true" class="hidden">
        <span id="no-search-results-text"></span>
      </div>

      <section id="home-section" class="active">
        <p id="page-subtitle"></p>
        <p id="settings-explanation">
          <a
            id="uploadLink"
            data-l10n-name="upload-link"
            class="change-data-choices-link"
            href="#"
          ></a>
        </p>
        <p id="ping-explanation">
          <a
            id="pingLink"
            data-l10n-name="ping-link"
            href="https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/concepts/pings.html"
          ></a>
        </p>
        <p data-l10n-id="about-telemetry-more-information"></p>
        <ul>
          <li data-l10n-id="about-telemetry-firefox-data-doc">
            <a
              id="dataDocLink"
              data-l10n-name="data-doc-link"
              href="https://docs.telemetry.mozilla.org/"
            ></a>
          </li>
          <li data-l10n-id="about-telemetry-telemetry-client-doc">
            <a
              id="clientDocLink"
              data-l10n-name="client-doc-link"
              href="https://firefox-source-docs.mozilla.org/toolkit/components/telemetry/telemetry/index.html"
            ></a>
          </li>
          <li data-l10n-id="about-telemetry-telemetry-dashboard">
            <a
              id="dashboardLink"
              data-l10n-name="dashboard-link"
              href="https://telemetry.mozilla.org/"
            ></a>
          </li>
          <li data-l10n-id="about-telemetry-telemetry-probe-dictionary">
            <a
              id="probeDictionaryLink"
              data-l10n-name="probe-dictionary-link"
              href="https://probes.telemetry.mozilla.org/"
            ></a>
          </li>
        </ul>
      </section>

      <section id="raw-payload-section">
        <button
          id="payload-json-viewer"
          data-l10n-id="about-telemetry-show-in-Firefox-json-viewer"
        ></button>
        <pre id="raw-payload-data"></pre>
      </section>

      <section id="general-data-section">
        <div id="general-data" class="data"></div>
      </section>

      <section id="environment-data-section">
        <div id="environment-data" class="data"></div>
      </section>

      <section id="session-info-section">
        <div id="session-info" class="data"></div>
      </section>

      <section id="scalars-section">
        <div id="scalars" class="data"></div>
      </section>

      <section id="keyed-scalars-section">
        <div id="keyed-scalars" class="data"></div>
      </section>

      <section id="histograms-section">
        <div id="histograms" class="data"></div>
      </section>

      <section id="keyed-histograms-section">
        <div id="keyed-histograms" class="data"></div>
      </section>

      <section id="events-section" class="text-search">
        <div id="events" class="data"></div>
      </section>

      <section id="simple-measurements-section">
        <div id="simple-measurements" class="data"></div>
      </section>

      <section id="slow-sql-section">
        <p id="sql-warning" data-l10n-id="about-telemetry-full-sql-warning"></p>
        <div id="slow-sql-tables" class="data"></div>
      </section>

      <section id="late-writes-section">
        <a
          id="late-writes-fetch-symbols"
          href=""
          data-l10n-id="about-telemetry-fetch-stack-symbols"
        ></a>
        <a
          id="late-writes-hide-symbols"
          href=""
          data-l10n-id="about-telemetry-hide-stack-symbols"
        ></a>
        <div id="late-writes" class="data"></div>
      </section>

      <section id="addon-details-section">
        <div id="addon-details" class="data"></div>
      </section>
    </div>
  </body>
</html>
PK
!<�[���4chrome/toolkit/content/global/aboutUrlClassifier.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/in-content/info-pages.css");

.major-section {
  margin-block: 2em 1em;
  font-size: large;
  text-align: start;
  font-weight: bold;
}

#provider-table > tbody > tr >  td:last-child,
#cache-table > tbody > tr >  td:last-child {
  text-align: center;
}

#debug-table, #cache-table {
  margin-top: 20px;
}

.options > .toggle-container-with-text {
  display: inline-flex;
}

button {
  margin-inline: 0 8px;
  padding: 3px;
}
PK
!</��R�R3chrome/toolkit/content/global/aboutUrlClassifier.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const UPDATE_BEGIN = "safebrowsing-update-begin";
const UPDATE_FINISH = "safebrowsing-update-finished";
const JSLOG_PREF = "browser.safebrowsing.debug";

window.onunload = function () {
  Search.uninit();
  Provider.uninit();
  Cache.uninit();
  Debug.uninit();
};

window.onload = function () {
  Search.init();
  Provider.init();
  Cache.init();
  Debug.init();
};

/*
 * Search
 */
var Search = {
  init() {
    let classifier = Cc["@mozilla.org/url-classifier/dbservice;1"].getService(
      Ci.nsIURIClassifier
    );
    let featureNames = classifier.getFeatureNames();

    let fragment = document.createDocumentFragment();
    featureNames.forEach(featureName => {
      let container = document.createElement("label");
      container.className = "toggle-container-with-text";
      fragment.appendChild(container);

      let checkbox = document.createElement("input");
      checkbox.id = "feature_" + featureName;
      checkbox.type = "checkbox";
      checkbox.checked = true;
      container.appendChild(checkbox);

      let span = document.createElement("span");
      container.appendChild(span);

      let text = document.createTextNode(featureName);
      span.appendChild(text);
    });

    let list = document.getElementById("search-features");
    list.appendChild(fragment);

    let btn = document.getElementById("search-button");
    btn.addEventListener("click", this.search);

    this.hideError();
    this.hideResults();
  },

  uninit() {
    let list = document.getElementById("search-features");
    while (list.firstChild) {
      list.firstChild.remove();
    }

    let btn = document.getElementById("search-button");
    btn.removeEventListener("click", this.search);
  },

  search() {
    Search.hideError();
    Search.hideResults();

    let input = document.getElementById("search-input").value;

    let uri;
    try {
      uri = Services.io.newURI(input);
      if (!uri) {
        Search.reportError("url-classifier-search-error-invalid-url");
        return;
      }
    } catch (ex) {
      Search.reportError("url-classifier-search-error-invalid-url");
      return;
    }

    let classifier = Cc["@mozilla.org/url-classifier/dbservice;1"].getService(
      Ci.nsIURIClassifier
    );

    let featureNames = classifier.getFeatureNames();
    let features = [];
    featureNames.forEach(featureName => {
      if (document.getElementById("feature_" + featureName).checked) {
        let feature = classifier.getFeatureByName(featureName);
        if (feature) {
          features.push(feature);
        }
      }
    });

    if (!features.length) {
      Search.reportError("url-classifier-search-error-no-features");
      return;
    }

    let listType =
      document.getElementById("search-listtype").value == 0
        ? Ci.nsIUrlClassifierFeature.blocklist
        : Ci.nsIUrlClassifierFeature.entitylist;
    classifier.asyncClassifyLocalWithFeatures(uri, features, listType, list =>
      Search.showResults(list)
    );

    Search.hideError();
  },

  hideError() {
    let errorMessage = document.getElementById("search-error-message");
    errorMessage.style.display = "none";
  },

  reportError(msg) {
    let errorMessage = document.getElementById("search-error-message");
    document.l10n.setAttributes(errorMessage, msg);
    errorMessage.style.display = "";
  },

  hideResults() {
    let resultTitle = document.getElementById("result-title");
    resultTitle.style.display = "none";

    let resultTable = document.getElementById("result-table");
    resultTable.style.display = "none";
  },

  showResults(results) {
    let fragment = document.createDocumentFragment();
    results.forEach(result => {
      let tr = document.createElement("tr");
      fragment.appendChild(tr);

      let th = document.createElement("th");
      tr.appendChild(th);
      th.appendChild(document.createTextNode(result.feature.name));

      let td = document.createElement("td");
      tr.appendChild(td);

      let featureName = document.createElement("div");
      document.l10n.setAttributes(
        featureName,
        "url-classifier-search-result-uri",
        { uri: result.uri.spec }
      );
      td.appendChild(featureName);

      let list = document.createElement("div");
      document.l10n.setAttributes(list, "url-classifier-search-result-list", {
        list: result.list,
      });
      td.appendChild(list);
    });

    let resultTable = document.getElementById("result-table");
    while (resultTable.firstChild) {
      resultTable.firstChild.remove();
    }

    resultTable.appendChild(fragment);
    resultTable.style.display = "";

    let resultTitle = document.getElementById("result-title");
    resultTitle.style.display = "";
  },
};

/*
 * Provider
 */
var Provider = {
  providers: null,

  updatingProvider: "",

  init() {
    this.providers = new Set();
    let branch = Services.prefs.getBranch("browser.safebrowsing.provider.");
    let children = branch.getChildList("");
    for (let child of children) {
      let provider = child.split(".")[0];
      if (this.isActiveProvider(provider)) {
        this.providers.add(provider);
      }
    }

    this.register();
    this.render();
    this.refresh();
  },

  uninit() {
    Services.obs.removeObserver(this.onBeginUpdate, UPDATE_BEGIN);
    Services.obs.removeObserver(this.onFinishUpdate, UPDATE_FINISH);
  },

  onBeginUpdate(aSubject, aTopic, aData) {
    this.updatingProvider = aData;
    let p = this.updatingProvider;

    // Disable update button for the provider while we are doing update.
    document.getElementById("update-" + p).disabled = true;

    let elem = document.getElementById(p + "-col-lastupdateresult");
    document.l10n.setAttributes(elem, "url-classifier-updating");
  },

  onFinishUpdate(aSubject, aTopic, aData) {
    let p = this.updatingProvider;
    this.updatingProvider = "";

    // It is possible that we get update-finished event only because
    // about::url-classifier is opened after update-begin event is fired.
    if (p === "") {
      this.refresh();
      return;
    }

    this.refresh([p]);

    document.getElementById("update-" + p).disabled = false;

    let elem = document.getElementById(p + "-col-lastupdateresult");
    if (aData.startsWith("success")) {
      document.l10n.setAttributes(elem, "url-classifier-success");
    } else if (aData.startsWith("update error")) {
      document.l10n.setAttributes(elem, "url-classifier-update-error", {
        error: aData.split(": ")[1],
      });
    } else if (aData.startsWith("download error")) {
      document.l10n.setAttributes(elem, "url-classifier-download-error", {
        error: aData.split(": ")[1],
      });
    } else {
      elem.childNodes[0].nodeValue = aData;
    }
  },

  register() {
    // Handle begin update
    this.onBeginUpdate = this.onBeginUpdate.bind(this);
    Services.obs.addObserver(this.onBeginUpdate, UPDATE_BEGIN);

    // Handle finish update
    this.onFinishUpdate = this.onFinishUpdate.bind(this);
    Services.obs.addObserver(this.onFinishUpdate, UPDATE_FINISH);
  },

  // This should only be called once because we assume number of providers
  // won't change.
  render() {
    let tbody = document.getElementById("provider-table-body");

    for (let provider of this.providers) {
      let tr = document.createElement("tr");
      let cols = document.getElementById("provider-head-row").childNodes;
      for (let column of cols) {
        if (!column.id) {
          continue;
        }
        let td = document.createElement("td");
        td.id = provider + "-" + column.id;

        if (column.id === "col-update") {
          let btn = document.createElement("button");
          btn.id = "update-" + provider;
          btn.addEventListener("click", () => {
            this.update(provider);
          });

          document.l10n.setAttributes(btn, "url-classifier-trigger-update");
          td.appendChild(btn);
        } else if (column.id === "col-lastupdateresult") {
          document.l10n.setAttributes(td, "url-classifier-not-available");
        } else {
          td.appendChild(document.createTextNode(""));
        }
        tr.appendChild(td);
      }
      tbody.appendChild(tr);
    }
  },

  refresh(listProviders = this.providers) {
    for (let provider of listProviders) {
      let values = {};
      values["col-provider"] = provider;

      let pref =
        "browser.safebrowsing.provider." + provider + ".lastupdatetime";
      let lut = Services.prefs.getCharPref(pref, "");
      values["col-lastupdatetime"] = lut ? new Date(lut * 1) : null;

      pref = "browser.safebrowsing.provider." + provider + ".nextupdatetime";
      let nut = Services.prefs.getCharPref(pref, "");
      values["col-nextupdatetime"] = nut ? new Date(nut * 1) : null;

      let listmanager = Cc[
        "@mozilla.org/url-classifier/listmanager;1"
      ].getService(Ci.nsIUrlListManager);
      let bot = listmanager.getBackOffTime(provider);
      values["col-backofftime"] = bot ? new Date(bot * 1) : null;

      for (let key of Object.keys(values)) {
        let elem = document.getElementById(provider + "-" + key);
        if (values[key]) {
          elem.removeAttribute("data-l10n-id");
          elem.childNodes[0].nodeValue = values[key];
        } else {
          document.l10n.setAttributes(elem, "url-classifier-not-available");
        }
      }
    }
  },

  // Call update for the provider.
  update(provider) {
    let listmanager = Cc[
      "@mozilla.org/url-classifier/listmanager;1"
    ].getService(Ci.nsIUrlListManager);

    let pref = "browser.safebrowsing.provider." + provider + ".lists";
    let tables = Services.prefs.getCharPref(pref, "");

    if (!listmanager.forceUpdates(tables)) {
      // This may because of back-off algorithm.
      let elem = document.getElementById(provider + "-col-lastupdateresult");
      document.l10n.setAttributes(elem, "url-classifier-cannot-update");
    }
  },

  // if we can find any table registered an updateURL in the listmanager,
  // the provider is active. This is used to filter out google v2 provider
  // without changing the preference.
  isActiveProvider(provider) {
    let listmanager = Cc[
      "@mozilla.org/url-classifier/listmanager;1"
    ].getService(Ci.nsIUrlListManager);

    let pref = "browser.safebrowsing.provider." + provider + ".lists";
    let tables = Services.prefs.getCharPref(pref, "").split(",");

    for (let i = 0; i < tables.length; i++) {
      let updateUrl = listmanager.getUpdateUrl(tables[i]);
      if (updateUrl) {
        return true;
      }
    }

    return false;
  },
};

/*
 * Cache
 */
var Cache = {
  // Tables that show cahe entries.
  showCacheEnties: null,

  init() {
    this.showCacheEnties = new Set();

    this.register();
    this.render();
  },

  uninit() {
    Services.obs.removeObserver(this.refresh, UPDATE_FINISH);
  },

  register() {
    this.refresh = this.refresh.bind(this);
    Services.obs.addObserver(this.refresh, UPDATE_FINISH);
  },

  render() {
    this.createCacheEntries();

    let refreshBtn = document.getElementById("refresh-cache-btn");
    refreshBtn.addEventListener("click", () => {
      this.refresh();
    });

    let clearBtn = document.getElementById("clear-cache-btn");
    clearBtn.addEventListener("click", () => {
      let dbservice = Cc["@mozilla.org/url-classifier/dbservice;1"].getService(
        Ci.nsIUrlClassifierDBService
      );
      dbservice.clearCache();
      // Since clearCache is async call, we just simply assume it will be
      // updated in 100 milli-seconds.
      setTimeout(() => {
        this.refresh();
      }, 100);
    });
  },

  refresh() {
    this.clearCacheEntries();
    this.createCacheEntries();
  },

  clearCacheEntries() {
    let ctbody = document.getElementById("cache-table-body");
    while (ctbody.firstChild) {
      ctbody.firstChild.remove();
    }

    let cetbody = document.getElementById("cache-entries-table-body");
    while (cetbody.firstChild) {
      cetbody.firstChild.remove();
    }
  },

  createCacheEntries() {
    function createRow(tds, body, cols) {
      let tr = document.createElement("tr");
      tds.forEach(function (v, i) {
        let td = document.createElement("td");
        if (i == 0 && tds.length != cols) {
          td.setAttribute("colspan", cols - tds.length + 1);
        }

        if (typeof v === "object") {
          if (v.l10n) {
            document.l10n.setAttributes(td, v.l10n);
          } else {
            td.removeAttribute("data-l10n-id");
            td.appendChild(v);
          }
        } else {
          td.removeAttribute("data-l10n-id");
          td.textContent = v;
        }

        tr.appendChild(td);
      });
      body.appendChild(tr);
    }

    let dbservice = Cc["@mozilla.org/url-classifier/dbservice;1"].getService(
      Ci.nsIUrlClassifierInfo
    );

    for (let provider of Provider.providers) {
      let pref = "browser.safebrowsing.provider." + provider + ".lists";
      let tables = Services.prefs.getCharPref(pref, "").split(",");

      for (let table of tables) {
        dbservice.getCacheInfo(table, {
          onGetCacheComplete: aCache => {
            let entries = aCache.entries;
            if (entries.length === 0) {
              this.showCacheEnties.delete(table);
              return;
            }

            let positiveCacheCount = 0;
            for (let i = 0; i < entries.length; i++) {
              let entry = entries.queryElementAt(
                i,
                Ci.nsIUrlClassifierCacheEntry
              );
              let matches = entry.matches;
              positiveCacheCount += matches.length;

              // If we don't have to show cache entries for this table then just
              // skip the following code.
              if (!this.showCacheEnties.has(table)) {
                continue;
              }

              let tds = [
                table,
                entry.prefix,
                new Date(entry.expiry * 1000).toString(),
              ];
              let j = 0;
              do {
                if (matches.length >= 1) {
                  let match = matches.queryElementAt(
                    j,
                    Ci.nsIUrlClassifierPositiveCacheEntry
                  );
                  let list = [
                    match.fullhash,
                    new Date(match.expiry * 1000).toString(),
                  ];
                  tds = tds.concat(list);
                } else {
                  tds = tds.concat([
                    { l10n: "url-classifier-not-available" },
                    { l10n: "url-classifier-not-available" },
                  ]);
                }
                createRow(
                  tds,
                  document.getElementById("cache-entries-table-body"),
                  5
                );
                j++;
                tds = [""];
              } while (j < matches.length);
            }

            // Create cache information entries.
            let chk = document.createElement("input");
            chk.type = "checkbox";
            chk.checked = this.showCacheEnties.has(table);
            chk.addEventListener("click", () => {
              if (chk.checked) {
                this.showCacheEnties.add(table);
              } else {
                this.showCacheEnties.delete(table);
              }
              this.refresh();
            });

            let tds = [table, entries.length, positiveCacheCount, chk];
            createRow(
              tds,
              document.getElementById("cache-table-body"),
              tds.length
            );
          },
        });
      }
    }

    let entries_div = document.getElementById("cache-entries");
    entries_div.style.display =
      this.showCacheEnties.size == 0 ? "none" : "block";
  },
};

/*
 * Debug
 */
var Debug = {
  // url-classifier NSPR Log modules.
  modules: [
    "UrlClassifierDbService",
    "nsChannelClassifier",
    "UrlClassifier",
    "UrlClassifierProtocolParser",
    "UrlClassifierStreamUpdater",
    "UrlClassifierPrefixSet",
    "ApplicationReputation",
  ],

  init() {
    this.register();
    this.render();
    this.refresh();
  },

  uninit() {
    Services.prefs.removeObserver(JSLOG_PREF, this.refreshJSDebug);
  },

  register() {
    this.refreshJSDebug = this.refreshJSDebug.bind(this);
    Services.prefs.addObserver(JSLOG_PREF, this.refreshJSDebug);
  },

  render() {
    // This function update the log module text field if we click
    // safebrowsing log module check box.
    function logModuleUpdate(module) {
      let txt = document.getElementById("log-modules");
      let chk = document.getElementById("chk-" + module);

      let dst = chk.checked ? "," + module + ":5" : "";
      let re = new RegExp(",?" + module + ":[0-9]");

      let str = txt.value.replace(re, dst);
      if (chk.checked) {
        str = txt.value === str ? str + dst : str;
      }
      txt.value = str.replace(/^,/, "");
    }

    let setLog = document.getElementById("set-log-modules");
    setLog.addEventListener("click", this.nsprlog);

    let setLogFile = document.getElementById("set-log-file");
    setLogFile.addEventListener("click", this.logfile);

    let setJSLog = document.getElementById("js-log");
    setJSLog.addEventListener("click", this.jslog);

    let modules = document.getElementById("log-modules");
    let sbModules = document.getElementById("sb-log-modules");
    for (let module of this.modules) {
      let container = document.createElement("label");
      container.className = "toggle-container-with-text";
      sbModules.appendChild(container);

      let chk = document.createElement("input");
      chk.id = "chk-" + module;
      chk.type = "checkbox";
      chk.checked = true;
      chk.addEventListener("click", () => {
        logModuleUpdate(module);
      });
      container.appendChild(chk, modules);

      let span = document.createElement("span");
      span.appendChild(document.createTextNode(module));
      container.appendChild(span, modules);
    }

    this.modules.map(logModuleUpdate);

    let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
    file.append("safebrowsing.log");

    let logFile = document.getElementById("log-file");
    logFile.value = file.path;

    let curLog = document.getElementById("cur-log-modules");
    curLog.childNodes[0].nodeValue = "";

    let curLogFile = document.getElementById("cur-log-file");
    curLogFile.childNodes[0].nodeValue = "";
  },

  refresh() {
    this.refreshJSDebug();

    // Disable configure log modules if log modules are already set
    // by environment variable.

    let logModules =
      Services.env.get("MOZ_LOG") ||
      Services.env.get("MOZ_LOG_MODULES") ||
      Services.env.get("NSPR_LOG_MODULES");

    if (logModules.length) {
      document.getElementById("set-log-modules").disabled = true;
      for (let module of this.modules) {
        document.getElementById("chk-" + module).disabled = true;
      }

      let curLogModules = document.getElementById("cur-log-modules");
      curLogModules.childNodes[0].nodeValue = logModules;
    }

    // Disable set log file if log file is already set
    // by environment variable.
    let logFile =
      Services.env.get("MOZ_LOG_FILE") || Services.env.get("NSPR_LOG_FILE");
    if (logFile.length) {
      document.getElementById("set-log-file").disabled = true;
      document.getElementById("log-file").value = logFile;
    }
  },

  refreshJSDebug() {
    let enabled = Services.prefs.getBoolPref(JSLOG_PREF, false);

    let jsChk = document.getElementById("js-log");
    jsChk.checked = enabled;

    let curJSLog = document.getElementById("cur-js-log");
    if (enabled) {
      document.l10n.setAttributes(curJSLog, "url-classifier-enabled");
    } else {
      document.l10n.setAttributes(curJSLog, "url-classifier-disabled");
    }
  },

  jslog() {
    let enabled = Services.prefs.getBoolPref(JSLOG_PREF, false);
    Services.prefs.setBoolPref(JSLOG_PREF, !enabled);
  },

  nsprlog() {
    // Turn off debugging for all the modules.
    let children = Services.prefs.getBranch("logging.").getChildList("");
    for (let pref of children) {
      if (!pref.startsWith("config.")) {
        Services.prefs.clearUserPref(`logging.${pref}`);
      }
    }

    let value = document.getElementById("log-modules").value;
    let logModules = value.split(",");
    for (let module of logModules) {
      let [key, value] = module.split(":");
      Services.prefs.setIntPref(`logging.${key}`, parseInt(value, 10));
    }

    let curLogModules = document.getElementById("cur-log-modules");
    curLogModules.childNodes[0].nodeValue = value;
  },

  logfile() {
    let logFile = document.getElementById("log-file").value.trim();
    Services.prefs.setCharPref("logging.config.LOG_FILE", logFile);

    let curLogFile = document.getElementById("cur-log-file");
    curLogFile.childNodes[0].nodeValue = logFile;
  },
};
PK
!<�pdnn6chrome/toolkit/content/global/aboutUrlClassifier.xhtml<?xml version="1.0" encoding="UTF-8"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html [
<!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> %htmlDTD;
]>

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'" />
  <meta name="color-scheme" content="light dark" />
  <title data-l10n-id="url-classifier-title"></title>
  <link rel="stylesheet" href="chrome://global/content/aboutUrlClassifier.css" type="text/css"/>
  <link rel="localization" href="toolkit/about/url-classifier.ftl"/>
  <script src="chrome://global/content/aboutUrlClassifier.js"></script>
</head>

<body class="wide-container">
  <h1 data-l10n-id="url-classifier-title"></h1>
  <div id="search">
    <h2 class="major-section" data-l10n-id="url-classifier-search-title"></h2>
    <div class="options">
      <table id="search-table">
        <tbody>
        <tr>
          <th class="column" data-l10n-id="url-classifier-search-input"></th>
          <td>
            <input id="search-input" type="text" value=""/>
          </td>
        </tr>
        <tr>
          <th class="column" data-l10n-id="url-classifier-search-listType"></th>
          <td>
            <select id="search-listtype">
              <option value="0">Blocklist</option>
              <option value="1">Entitylist</option>
            </select>
          </td>
        </tr>
        <tr>
          <th class="column" data-l10n-id="url-classifier-search-features"></th>
          <td id="search-features"></td>
        </tr>
        <tr>
          <th></th>
          <td>
            <button id="search-button" data-l10n-id="url-classifier-search-btn"></button>
          </td>
        </tr>
        </tbody>
      </table>
      <p id="search-error-message"></p>
      <h2 class="major-section" id="result-title" data-l10n-id="url-classifier-search-result-title"></h2>
      <table id="result-table">
        <tr>
          <td>
            <input id="search-input" type="text" value=""/>
          </td>
        </tr>
      </table>
    </div>
  </div>
  <div id="provider">
    <h2 class="major-section" data-l10n-id="url-classifier-provider-title"></h2>
    <table id="provider-table">
      <thead>
        <tr id="provider-head-row">
          <th id="col-provider" data-l10n-id="url-classifier-provider"></th>
          <th id="col-lastupdatetime" data-l10n-id="url-classifier-provider-last-update-time"></th>
          <th id="col-nextupdatetime" data-l10n-id="url-classifier-provider-next-update-time"></th>
          <th id="col-backofftime" data-l10n-id="url-classifier-provider-back-off-time"></th>
          <th id="col-lastupdateresult" data-l10n-id="url-classifier-provider-last-update-status"></th>
          <th id="col-update" data-l10n-id="url-classifier-provider-update-btn"></th>
        </tr>
      </thead>
      <tbody id="provider-table-body">
        <!-- data is generated in javascript -->
      </tbody>
    </table>
  </div>
  <div id="cache">
    <h2 class="major-section" data-l10n-id="url-classifier-cache-title"></h2>
    <div id="cache-modules" class="options">
      <button id="refresh-cache-btn" data-l10n-id="url-classifier-cache-refresh-btn"></button>
      <button id="clear-cache-btn" data-l10n-id="url-classifier-cache-clear-btn"></button>
      <br></br>
    </div>
    <table id="cache-table">
      <thead>
        <tr id="cache-head-row">
          <th id="col-tablename" data-l10n-id="url-classifier-cache-table-name"></th>
          <th id="col-negativeentries" data-l10n-id="url-classifier-cache-ncache-entries"></th>
          <th id="col-positiveentries" data-l10n-id="url-classifier-cache-pcache-entries"></th>
          <th id="col-showentries" data-l10n-id="url-classifier-cache-show-entries"></th>
        </tr>
      </thead>
      <tbody id="cache-table-body">
        <!-- data is generated in javascript -->
      </tbody>
    </table>
    <br></br>
  </div>
  <div id="cache-entries">
    <h2 class="major-section" data-l10n-id="url-classifier-cache-entries"></h2>
    <table id="cache-entries-table">
      <thead>
        <tr id="cache-entries-row">
          <th id="col-table" data-l10n-id="url-classifier-cache-table-name"></th>
          <th id="col-prefix" data-l10n-id="url-classifier-cache-prefix"></th>
          <th id="col-n-expire" data-l10n-id="url-classifier-cache-ncache-expiry"></th>
          <th id="col-fullhash" data-l10n-id="url-classifier-cache-fullhash"></th>
          <th id="col-p-expire" data-l10n-id="url-classifier-cache-pcache-expiry"></th>
        </tr>
      </thead>
      <tbody id="cache-entries-table-body">
        <!-- data is generated in javascript -->
      </tbody>
    </table>
  </div>
  <div id="debug">
    <h2 class="major-section" data-l10n-id="url-classifier-debug-title"></h2>
    <div id="debug-modules" class="options">
      <input id="log-modules" type="text" value=""/>
      <button id="set-log-modules" data-l10n-id="url-classifier-debug-module-btn"></button>
      <br></br>
      <input id="log-file" type="text" value=""/>
      <button id="set-log-file" data-l10n-id="url-classifier-debug-file-btn"></button>
      <br></br>
      <label class="toggle-container-with-text">
        <input id="js-log" type="checkbox"/>
        <span data-l10n-id="url-classifier-debug-js-log-chk"></span>
      </label>
    </div>
    <table id="debug-table">
    <tbody>
    <tr>
      <th class="column" data-l10n-id="url-classifier-debug-sb-modules"></th>
      <td id="sb-log-modules">
      </td>
    </tr>
    <tr>
      <th class="column" data-l10n-id="url-classifier-debug-modules"></th>
      <td id="cur-log-modules">
      </td>
    </tr>
    <tr>
      <th class="column" data-l10n-id="url-classifier-debug-sbjs-modules"></th>
      <td id="cur-js-log">
      </td>
    </tr>
    <tr>
      <th class="column" data-l10n-id="url-classifier-debug-file"></th>
      <td id="cur-log-file">
      </td>
    </tr>
    </tbody>
    </table>
  </div>
</body>
</html>
PK
!<�]ؕ��/chrome/toolkit/content/global/aboutWebauthn.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/in-content/common.css");

html {
  height: 100%;
}

body {
  display: flex;
  align-items: stretch;
  height: 100%;
}

label {
  display: block;
}

#info-text-div {
  padding: 20px;
}

#ctap-listen-div {
  padding-top: 15px;
}
#ctap-listen-result {
  font-weight: 600;
  font-size: 1.5em;
  padding-inline-start: 15px;
  height: 1.5em;

  &.success {
    background-color: var(--background-color-success);
  }

  &.error {
    background-color: var(--red-50);
  }
}

.category {
  cursor: pointer;
  /* Center category names */
  display: flex;
  align-items: center;
}

.optional-category {
  display: none;
}

.disabled-category {
  pointer-events: none;
}

@media (max-width: 830px){
  #categories > .category {
    padding-inline-start: 5px;
    margin-inline-start: 0;
  }
}

#main-content {
  flex: 1;
}

.token-info-flex-box {
  display: flex;
}

.token-info-flex-child {
  flex: 1;
}

.token-info-flex-child#authenticator-options {
  margin-inline-end: 2px;
}

.bio-enrollment-sample {
  display: flex;
  gap: 0.5em;
}

.button-row {
  display: inline-block;
  margin-inline-end: 5px;
}

.delete-icon {
  margin-inline-end: 5px;
  -moz-context-properties: fill;
  fill: currentColor;
}

.delete-button {
  display: inline-flex;
}
PK
!<��P��%�%0chrome/toolkit/content/global/aboutWebauthn.html<!DOCTYPE html>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<html>
  <head>
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:; style-src chrome:; object-src 'none'"
    />
    <meta charset="utf-8" />
    <title id="page-title" data-l10n-id="about-webauthn-page-title"></title>
    <link rel="stylesheet" href="chrome://global/content/aboutWebauthn.css" />
    <script src="chrome://global/content/aboutWebauthn.js"></script>
    <link rel="localization" href="toolkit/about/aboutWebauthn.ftl" />
  </head>

  <body id="body">
    <div id="categories" role="tablist" aria-labelledby="page-title">
      <div
        id="info-tab-button"
        class="category"
        selected="true"
        role="tab"
        tabindex="0"
        aria-selected="true"
        aria-controls="token-info-section"
      >
        <span
          class="tablinks"
          data-l10n-id="about-webauthn-info-section-title"
        ></span>
      </div>
      <div
        id="pin-tab-button"
        class="category optional-category"
        role="tab"
        tabindex="-1"
        aria-selected="false"
        aria-controls="set-change-pin-section"
      >
        <span
          class="tablinks"
          data-l10n-id="about-webauthn-pin-section-title"
        ></span>
      </div>
      <div
        id="credentials-tab-button"
        class="category optional-category"
        role="tab"
        tabindex="-1"
        aria-selected="false"
        aria-controls="credential-management-section"
      >
        <span
          class="tablinks"
          data-l10n-id="about-webauthn-credential-management-section-title"
        ></span>
      </div>
      <div
        id="bio-enrollments-tab-button"
        class="category optional-category"
        role="tab"
        tabindex="-1"
        aria-selected="false"
        aria-controls="bio-enrollment-section"
      >
        <span
          class="tablinks"
          data-l10n-id="about-webauthn-bio-enrollment-section-title"
        ></span>
      </div>
    </div>

    <div id="main-content">
      <div id="ctap-listen-div">
        <label id="ctap-listen-result"></label>
      </div>

      <div
        class="tabcontent token-info-section"
        id="token-info-section"
        role="tabpanel"
        aria-labelledby="info-tab-button"
      >
        <h2
          class="categoryTitle"
          data-l10n-id="about-webauthn-info-section-title"
        ></h2>

        <div id="info-text-div">
          <label
            id="info-text-field"
            data-l10n-id="about-webauthn-text-connect-device"
          ></label>
        </div>

        <div id="ctap2-token-info" class="token-info-flex-box" display="none">
          <div id="ctap2-token-info-options" class="token-info-flex-child">
            <h3 data-l10n-id="about-webauthn-options-subsection-title"></h3>
            <table id="authenticator-options"></table>
          </div>
          <div id="ctap2-token-info-info" class="token-info-flex-child">
            <h3 data-l10n-id="about-webauthn-info-subsection-title"></h3>
            <table id="authenticator-info"></table>
          </div>
        </div>
      </div>

      <div
        hidden
        class="tabcontent pin-section"
        id="set-change-pin-section"
        role="tabpanel"
        aria-labelledby="pin-tab-button"
      >
        <h2
          class="categoryTitle"
          data-l10n-id="about-webauthn-pin-section-title"
        ></h2>
        <div id="new-pin-div">
          <label
            for="new-pin"
            data-l10n-id="about-webauthn-new-pin-label"
          ></label>
          <input type="password" id="new-pin" name="new-pin" required />
          <label
            for="new-pin-repeat"
            data-l10n-id="about-webauthn-repeat-pin-label"
          ></label>
          <input
            type="password"
            id="new-pin-repeat"
            name="new-pin-repeat"
            required
          />
        </div>
        <div id="current-pin-div">
          <label
            for="current-pin"
            data-l10n-id="about-webauthn-current-pin-label"
          ></label>
          <input type="password" id="current-pin" name="current-pin" required />
        </div>
        <button
          disabled
          id="set-pin-button"
          data-l10n-id="about-webauthn-current-set-pin-button"
        ></button>
        <button
          disabled
          id="change-pin-button"
          data-l10n-id="about-webauthn-current-change-pin-button"
        ></button>
        <label id="set-change-pin-result" class="ctap-result"></label>
      </div>

      <div
        hidden
        class="tabcontent credential-management-section"
        id="credential-management-section"
        role="tabpanel"
        aria-labelledby="credentials-tab-button"
      >
        <h2
          class="categoryTitle"
          data-l10n-id="about-webauthn-credential-management-section-title"
        ></h2>
        <div
          hidden
          id="credential-list-subsection"
          class="token-info-flex-child"
        >
          <h3
            data-l10n-id="about-webauthn-credential-list-subsection-title"
          ></h3>
          <div hidden id="credential-list-empty-label">
            <label
              hidden
              data-l10n-id="about-webauthn-credential-list-empty"
            ></label>
          </div>
          <table id="credential-list"></table>
        </div>
        <button
          class="credentials-button"
          id="list-credentials-button"
          data-l10n-id="about-webauthn-list-credentials-button"
        ></button>
      </div>

      <div
        hidden
        class="tabcontent bio-enrollment-section"
        id="bio-enrollment-section"
        role="tabpanel"
        aria-labelledby="bio-enrollments-tab-button"
      >
        <h2
          class="categoryTitle"
          data-l10n-id="about-webauthn-bio-enrollment-section-title"
        ></h2>
        <div
          hidden
          id="bio-enrollment-list-subsection"
          class="token-info-flex-child"
        >
          <h3
            data-l10n-id="about-webauthn-bio-enrollment-list-subsection-title"
          ></h3>
          <div hidden id="bio-enrollment-list-empty-label">
            <label
              hidden
              data-l10n-id="about-webauthn-enrollment-list-empty"
            ></label>
          </div>
          <table id="bio-enrollment-list"></table>
        </div>
        <button
          class="bio-enrollment-button button-row"
          id="list-bio-enrollments-button"
          data-l10n-id="about-webauthn-list-bio-enrollments-button"
        ></button>
        <button
          class="bio-enrollment-button button-row"
          id="add-bio-enrollment-button"
          data-l10n-id="about-webauthn-add-bio-enrollment-button"
        ></button>
      </div>
      <div
        hidden
        class="tabcontent add-bio-enrollment-section"
        id="add-bio-enrollment-section"
      >
        <h2
          class="categoryTitle"
          data-l10n-id="about-webauthn-add-bio-enrollment-section-title"
        ></h2>
        <label
          for="enrollment-name"
          data-l10n-id="about-webauthn-enrollment-name-label"
        ></label>
        <input id="enrollment-name" name="enrollment-name" autofocus />
        <button
          class="bio-enrollment-button button-row"
          id="start-enrollment-button"
          data-l10n-id="about-webauthn-start-enrollment-button"
        ></button>
        <button
          id="cancel-enrollment-button"
          class="button-row"
          data-l10n-id="about-webauthn-cancel-button"
        ></button>
        <div id="enrollment-update"></div>
      </div>

      <div
        hidden
        class="tabcontent pin-required-section"
        id="pin-required-section"
      >
        <h2
          class="categoryTitle"
          data-l10n-id="about-webauthn-pin-required-section-title"
        ></h2>
        <div id="pin-div">
          <label
            for="pin-required"
            data-l10n-id="about-webauthn-pin-required-label"
          ></label>
          <input
            type="password"
            id="pin-required"
            name="pin-required"
            required
            autofocus
          />
        </div>
        <button
          id="send-pin-button"
          class="button-row"
          data-l10n-id="about-webauthn-send-pin-button"
        ></button>
        <button
          id="cancel-send-pin-button"
          class="button-row"
          data-l10n-id="about-webauthn-cancel-button"
        ></button>
      </div>

      <div
        hidden
        class="tabcontent confirm-deletion-section"
        id="confirm-deletion-section"
      >
        <h2
          class="categoryTitle"
          data-l10n-id="about-webauthn-confirm-deletion-section-title"
        ></h2>
        <div id="confirmation-div">
          <label
            for="confirmation-context"
            data-l10n-id="about-webauthn-confirm-deletion-label"
          ></label>
          <label id="confirmation-context"></label>
        </div>
        <button
          id="confirm-deletion-button"
          class="button-row"
          data-l10n-id="about-webauthn-delete-button"
        ></button>
        <button
          id="cancel-confirmation-button"
          class="button-row"
          data-l10n-id="about-webauthn-cancel-button"
        ></button>
      </div>
    </div>
  </body>
</html>
PK
!<�&�=bvbv.chrome/toolkit/content/global/aboutWebauthn.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

let AboutWebauthnService = null;

var AboutWebauthnManagerJS = {
  _topic: "about-webauthn-prompt",
  _initialized: false,
  _l10n: null,
  _bio_l10n: null,
  _curr_data: null,
  _current_tab: "",
  _previous_tab: "",

  init() {
    if (this._initialized) {
      return;
    }
    this._l10n = new Localization(["toolkit/about/aboutWebauthn.ftl"], true);
    this._bio_l10n = new Map();
    this._bio_l10n.set(
      "Ctap2EnrollFeedbackFpGood",
      "about-webauthn-ctap2-enroll-feedback-good"
    );
    this._bio_l10n.set(
      "Ctap2EnrollFeedbackFpTooHigh",
      "about-webauthn-ctap2-enroll-feedback-too-high"
    );
    this._bio_l10n.set(
      "Ctap2EnrollFeedbackFpTooLow",
      "about-webauthn-ctap2-enroll-feedback-too-low"
    );
    this._bio_l10n.set(
      "Ctap2EnrollFeedbackFpTooLeft",
      "about-webauthn-ctap2-enroll-feedback-too-left"
    );
    this._bio_l10n.set(
      "Ctap2EnrollFeedbackFpTooRight",
      "about-webauthn-ctap2-enroll-feedback-too-right"
    );
    this._bio_l10n.set(
      "Ctap2EnrollFeedbackFpTooFast",
      "about-webauthn-ctap2-enroll-feedback-too-fast"
    );
    this._bio_l10n.set(
      "Ctap2EnrollFeedbackFpTooSlow",
      "about-webauthn-ctap2-enroll-feedback-too-slow"
    );
    this._bio_l10n.set(
      "Ctap2EnrollFeedbackFpPoorQuality",
      "about-webauthn-ctap2-enroll-feedback-poor-quality"
    );
    this._bio_l10n.set(
      "Ctap2EnrollFeedbackFpTooSkewed",
      "about-webauthn-ctap2-enroll-feedback-too-skewed"
    );
    this._bio_l10n.set(
      "Ctap2EnrollFeedbackFpTooShort",
      "about-webauthn-ctap2-enroll-feedback-too-short"
    );
    this._bio_l10n.set(
      "Ctap2EnrollFeedbackFpMergeFailure",
      "about-webauthn-ctap2-enroll-feedback-merge-failure"
    );
    this._bio_l10n.set(
      "Ctap2EnrollFeedbackFpExists",
      "about-webauthn-ctap2-enroll-feedback-exists"
    );
    this._bio_l10n.set(
      "Ctap2EnrollFeedbackNoUserActivity",
      "about-webauthn-ctap2-enroll-feedback-no-user-activity"
    );
    this._bio_l10n.set(
      "Ctap2EnrollFeedbackNoUserPresenceTransition",
      "about-webauthn-ctap2-enroll-feedback-no-user-presence-transition"
    );
    this._bio_l10n.set(
      "Ctap2EnrollFeedbackOther",
      "about-webauthn-ctap2-enroll-feedback-other"
    );

    Services.obs.addObserver(this, this._topic);
    this._initialized = true;
    reset_page();
  },

  uninit() {
    Services.obs.removeObserver(this, this._topic);
    this._initialized = false;
    this._l10n = null;
    this._current_tab = "";
    this._previous_tab = "";
  },

  observe(aSubject, aTopic, aData) {
    let data = JSON.parse(aData);

    // We have token
    if (data.type == "selected-device") {
      this._curr_data = data.auth_info;
      this.show_ui_based_on_authenticator_info(data);
      fake_click_event_for_id("info-tab-button");
    } else if (data.type == "select-device") {
      set_info_text("about-webauthn-text-select-device");
    } else if (data.type == "pin-required") {
      open_pin_required_tab();
    } else if (data.type == "pin-invalid") {
      let retries = data.retries ? data.retries : 0;
      show_results_banner(
        "error",
        "about-webauthn-results-pin-invalid-error",
        JSON.stringify({ retriesLeft: retries })
      );
      open_pin_required_tab();
    } else if (data.type == "bio-enrollment-update") {
      if (data.result.EnrollmentList) {
        show_results_banner("success", "about-webauthn-results-success");
        this.show_enrollment_list(data.result.EnrollmentList);
        bio_enrollment_in_progress(false);
      } else if (data.result.DeleteSuccess || data.result.AddSuccess) {
        show_results_banner("success", "about-webauthn-results-success");
        clear_bio_enrollment_samples();
        // Update AuthenticatorInfo
        this._curr_data = data.result.DeleteSuccess ?? data.result.AddSuccess;
        fake_click_event_for_id("bio-enrollments-tab-button");
        bio_enrollment_in_progress(false);
        // If we still have some enrollments to show, update the list
        // otherwise, remove it.
        if (
          this._curr_data.options.bioEnroll === true ||
          this._curr_data.options.userVerificationMgmtPreview === true
        ) {
          list_bio_enrollments();
        } else {
          // Hide the list, because it's empty
          document.getElementById(
            "bio-enrollment-list-subsection"
          ).hidden = true;
        }
      } else if (data.result.UpdateSuccess) {
        fake_click_event_for_id("bio-enrollments-tab-button");
        list_bio_enrollments();
      } else if (data.result.SampleStatus) {
        show_add_bio_enrollment_section();
        let up = document.getElementById("enrollment-update");
        let sample_update = document.createElement("div");
        let new_line = document.createElement("label");
        new_line.classList.add("sample");
        new_line.setAttribute(
          "data-l10n-id",
          this._bio_l10n.get(data.result.SampleStatus[0])
        );
        sample_update.appendChild(new_line);
        let samples_needed_line = document.createElement("label");
        samples_needed_line.setAttribute(
          "data-l10n-id",
          "about-webauthn-samples-still-needed"
        );
        samples_needed_line.setAttribute(
          "data-l10n-args",
          JSON.stringify({ repeatCount: data.result.SampleStatus[1] })
        );
        sample_update.classList.add("bio-enrollment-sample");
        sample_update.appendChild(samples_needed_line);
        up.appendChild(sample_update);
      }
    } else if (data.type == "credential-management-update") {
      credential_management_in_progress(false);
      if (data.result.CredentialList) {
        show_results_banner("success", "about-webauthn-results-success");
        this.show_credential_list(data.result.CredentialList.credential_list);
      } else {
        // DeleteSuccess or UpdateSuccess
        show_results_banner("success", "about-webauthn-results-success");
        list_credentials();
      }
    } else if (data.type == "listen-success") {
      reset_page();
      // Show results
      show_results_banner("success", "about-webauthn-results-success");
      this._reset_in_progress = "";
      AboutWebauthnService.listen();
    } else if (data.type == "listen-error") {
      reset_page();

      if (!data.error) {
        show_results_banner("error", "about-webauthn-results-general-error");
      } else if (data.error.type == "pin-auth-blocked") {
        show_results_banner(
          "error",
          "about-webauthn-results-pin-auth-blocked-error"
        );
      } else if (data.error.type == "pin-not-set") {
        show_results_banner(
          "error",
          "about-webauthn-results-pin-not-set-error"
        );
      } else if (data.error.type == "device-blocked") {
        show_results_banner(
          "error",
          "about-webauthn-results-pin-blocked-error"
        );
      } else if (data.error.type == "pin-is-too-short") {
        show_results_banner(
          "error",
          "about-webauthn-results-pin-too-short-error"
        );
      } else if (data.error.type == "pin-is-too-long") {
        show_results_banner(
          "error",
          "about-webauthn-results-pin-too-long-error"
        );
      } else if (data.error.type == "pin-invalid") {
        let retries = data.error.retries
          ? JSON.stringify({ retriesLeft: data.error.retries })
          : null;
        show_results_banner(
          "error",
          "about-webauthn-results-pin-invalid-error",
          retries
        );
      } else if (data.error.type == "cancel") {
        show_results_banner(
          "error",
          "about-webauthn-results-cancelled-by-user-error"
        );
      } else {
        show_results_banner("error", "about-webauthn-results-general-error");
      }
      AboutWebauthnService.listen();
    }
  },

  show_authenticator_options(options, element, l10n_base) {
    let table = document.getElementById(element);
    var empty_table = document.createElement("table");
    empty_table.id = element;
    table.parentNode.replaceChild(empty_table, table);
    table = document.getElementById(element);
    for (let key in options) {
      if (key == "options") {
        continue;
      }
      // Create an empty <tr> element and add it to the 1st position of the table:
      var row = table.insertRow(0);

      // Insert new cells (<td> elements) at the 1st and 2nd position of the "new" <tr> element:
      var cell1 = row.insertCell(0);
      var cell2 = row.insertCell(1);

      // Add some text to the new cells:
      let key_text = this._l10n.formatValueSync(
        l10n_base + "-" + key.toLowerCase().replace(/_/g, "-")
      );
      var key_node = document.createTextNode(key_text);
      cell1.appendChild(key_node);
      var raw_value = JSON.stringify(options[key]);
      var value = raw_value;
      if (["true", "false", "null"].includes(raw_value)) {
        value = this._l10n.formatValueSync(l10n_base + "-" + raw_value);
      }
      var value_node = document.createTextNode(value);
      cell2.appendChild(value_node);
    }
  },

  show_ui_based_on_authenticator_info(data) {
    // Hide the "Please plug in a token"-message
    document.getElementById("info-text-div").hidden = true;
    // Show options, based on what the token supports
    if (data.auth_info) {
      document.getElementById("ctap2-token-info").style.display = "flex";
      this.show_authenticator_options(
        data.auth_info.options,
        "authenticator-options",
        "about-webauthn-auth-option"
      );
      this.show_authenticator_options(
        data.auth_info,
        "authenticator-info",
        "about-webauthn-auth-info"
      );
      // Check if token supports PINs
      if (data.auth_info.options.clientPin != null) {
        document.getElementById("pin-tab-button").style.display = "flex";
        if (data.auth_info.options.clientPin === true) {
          // It has a Pin set
          document.getElementById("change-pin-button").style.display = "block";
          document.getElementById("set-pin-button").style.display = "none";
          document.getElementById("current-pin-div").hidden = false;
        } else {
          // It does not have a Pin set yet
          document.getElementById("change-pin-button").style.display = "none";
          document.getElementById("set-pin-button").style.display = "block";
          document.getElementById("current-pin-div").hidden = true;
        }
      } else {
        document.getElementById("pin-tab-button").style.display = "none";
      }

      if (
        data.auth_info.options.credMgmt ||
        data.auth_info.options.credentialMgmtPreview
      ) {
        document.getElementById("credentials-tab-button").style.display =
          "flex";
      } else {
        document.getElementById("credentials-tab-button").style.display =
          "none";
      }

      if (
        data.auth_info.options.bioEnroll != null ||
        data.auth_info.options.userVerificationMgmtPreview != null
      ) {
        document.getElementById("bio-enrollments-tab-button").style.display =
          "flex";
      } else {
        document.getElementById("bio-enrollments-tab-button").style.display =
          "none";
      }
    } else {
      // Currently auth-rs doesn't send this, because it filters out ctap2-devices.
      // U2F / CTAP1 tokens can't be managed
      set_info_text("about-webauthn-text-non-ctap2-device");
    }
  },

  show_credential_list(credential_list) {
    // We may have temporarily hidden the tab when asking the user for a PIN
    // so we have to show it again.
    fake_click_event_for_id("credentials-tab-button");
    document.getElementById("credential-list-subsection").hidden = false;
    let table = document.getElementById("credential-list");
    var empty_table = document.createElement("table");
    empty_table.id = "credential-list";
    table.parentNode.replaceChild(empty_table, table);
    if (!credential_list.length) {
      document.getElementById("credential-list-empty-label").hidden = false;
      return;
    }
    document.getElementById("credential-list-empty-label").hidden = true;
    table = document.getElementById("credential-list");
    credential_list.forEach(rp => {
      // Add some text to the new cells:
      let key_text = rp.rp.id;
      rp.credentials.forEach(cred => {
        let value_text = cred.user.name;
        // Create an empty <tr> element and add it to the 1st position of the table:
        var row = table.insertRow(0);
        var key_node = document.createTextNode(key_text);
        var value_node = document.createTextNode(value_text);
        row.insertCell(0).appendChild(key_node);
        row.insertCell(1).appendChild(value_node);
        var delete_button = document.createElement("button");
        delete_button.classList.add("delete-button");
        delete_button.classList.add("credentials-button");
        let garbage_icon = document.createElement("img");
        garbage_icon.setAttribute(
          "src",
          "chrome://global/skin/icons/delete.svg"
        );
        garbage_icon.classList.add("delete-icon");
        delete_button.appendChild(garbage_icon);
        let delete_text = document.createElement("span");
        delete_text.setAttribute(
          "data-l10n-id",
          "about-webauthn-delete-button"
        );
        delete_button.appendChild(delete_text);
        delete_button.addEventListener("click", function () {
          let context = document.getElementById("confirmation-context");
          context.textContent = key_text + " - " + value_text;
          credential_management_in_progress(true);
          let cmd = {
            CredentialManagement: { DeleteCredential: cred.credential_id },
          };
          context.setAttribute("data-ctap-command", JSON.stringify(cmd));
          open_delete_confirmation_tab();
        });
        row.insertCell(2).appendChild(delete_button);
      });
    });
  },

  show_enrollment_list(enrollment_list) {
    // We may have temporarily hidden the tab when asking the user for a PIN
    // so we have to show it again.
    fake_click_event_for_id("bio-enrollments-tab-button");
    document.getElementById("bio-enrollment-list-subsection").hidden = false;
    let table = document.getElementById("bio-enrollment-list");
    var empty_table = document.createElement("table");
    empty_table.id = "bio-enrollment-list";
    table.parentNode.replaceChild(empty_table, table);
    if (!enrollment_list.length) {
      document.getElementById("bio-enrollment-list-empty-label").hidden = false;
      return;
    }
    document.getElementById("bio-enrollment-list-empty-label").hidden = true;
    table = document.getElementById("bio-enrollment-list");
    enrollment_list.forEach(enrollment => {
      let key_text = enrollment.template_friendly_name ?? "<unnamed>";
      var row = table.insertRow(0);
      var key_node = document.createTextNode(key_text);
      row.insertCell(0).appendChild(key_node);
      var delete_button = document.createElement("button");
      delete_button.classList.add("delete-button");
      delete_button.classList.add("bio-enrollment-button");
      let garbage_icon = document.createElement("img");
      garbage_icon.setAttribute("src", "chrome://global/skin/icons/delete.svg");
      garbage_icon.classList.add("delete-icon");
      delete_button.appendChild(garbage_icon);
      let delete_text = document.createElement("span");
      delete_text.setAttribute("data-l10n-id", "about-webauthn-delete-button");
      delete_button.appendChild(delete_text);
      delete_button.addEventListener("click", function () {
        let context = document.getElementById("confirmation-context");
        context.textContent = key_text;
        bio_enrollment_in_progress(true);
        let cmd = {
          BioEnrollment: { DeleteEnrollment: enrollment.template_id },
        };
        context.setAttribute("data-ctap-command", JSON.stringify(cmd));
        open_delete_confirmation_tab();
      });
      row.insertCell(1).appendChild(delete_button);
    });
  },
};

function set_info_text(l10nId) {
  document.getElementById("info-text-div").hidden = false;
  let field = document.getElementById("info-text-field");
  field.setAttribute("data-l10n-id", l10nId);
  document.getElementById("ctap2-token-info").style.display = "none";
}

function show_results_banner(result, l10n, l10n_args) {
  let ctap_result = document.getElementById("ctap-listen-result");
  ctap_result.setAttribute("data-l10n-id", l10n);
  ctap_result.classList.add(result);
  if (l10n_args) {
    ctap_result.setAttribute("data-l10n-args", l10n_args);
  }
}

function hide_results_banner() {
  let res_banner = document.getElementById("ctap-listen-result");
  let res_div = document.getElementById("ctap-listen-div");
  let empty_banner = document.createElement("label");
  empty_banner.id = "ctap-listen-result";
  res_div.replaceChild(empty_banner, res_banner);
}

function operation_in_progress(name, in_progress) {
  let buttons = Array.from(document.getElementsByClassName(name));
  buttons.forEach(button => {
    button.disabled = in_progress;
  });
}

function credential_management_in_progress(in_progress) {
  operation_in_progress("credentials-button", in_progress);
}

function bio_enrollment_in_progress(in_progress) {
  operation_in_progress("bio-enrollment-button", in_progress);
}

function clear_bio_enrollment_samples() {
  // Remove all previous status updates
  let up = document.getElementById("enrollment-update");
  while (up.firstChild) {
    up.removeChild(up.lastChild);
  }
  document.getElementById("enrollment-name").value = "";
}

function fake_click_event_for_id(id) {
  // Not using document.getElementById(id).click();
  // here, because we have to add additional data, so we don't
  // hide the results-div here, if there is any. 'Normal' clicking
  // by the user will hide it.
  const evt = new CustomEvent("click", {
    detail: { skip_results_clearing: true },
  });
  document.getElementById(id).dispatchEvent(evt);
}

function reset_page() {
  // Hide everything that needs a device to know if it should be displayed
  document.getElementById("ctap2-token-info").style.display = "none";
  Array.from(document.getElementsByClassName("optional-category")).forEach(
    div => {
      div.style.display = "none";
    }
  );

  // Only display the "please connect a device" - text
  set_info_text("about-webauthn-text-connect-device");

  // Clear results and input fields
  hide_results_banner();
  var divs = Array.from(document.getElementsByTagName("input"));
  divs.forEach(div => {
    div.value = "";
  });

  sidebar_set_disabled(false);

  // ListCredentials
  credential_management_in_progress(false);
  document.getElementById("credential-list-subsection").hidden = true;

  // BioEnrollment
  clear_bio_enrollment_samples();
  document.getElementById("bio-enrollment-list-subsection").hidden = true;
  bio_enrollment_in_progress(false);

  AboutWebauthnManagerJS._previous_tab = "";
  AboutWebauthnManagerJS._current_tab = "";

  // Not using `document.getElementById("info-tab-button").click();`
  // here, because if we were focused on a category-button that got removed (e.g.
  // when unplugging the device), we have to reset the ARIA-related attributes
  // first, before we can click on the button, otherwise the a11y-tests
  // will complain. So we fake the click again.
  const evt = {
    detail: {},
    currentTarget: document.getElementById("info-tab-button"),
  };
  open_info_tab(evt);
}

function sidebar_set_disabled(disabled) {
  var cats = Array.from(document.getElementsByClassName("category"));
  cats.forEach(cat => {
    if (disabled) {
      cat.classList.add("disabled-category");
    } else {
      cat.classList.remove("disabled-category");
    }
  });
}

function check_pin_repeat_is_correct() {
  let pin = document.getElementById("new-pin");
  let pin_repeat = document.getElementById("new-pin-repeat");
  let has_current_pin = !document.getElementById("current-pin-div").hidden;
  let current_pin = document.getElementById("current-pin");
  let can_enable_button =
    pin.value != null && pin.value != "" && pin.value == pin_repeat.value;
  if (has_current_pin && !current_pin.value) {
    can_enable_button = false;
  }
  if (!can_enable_button) {
    pin.classList.add("different");
    pin_repeat.classList.add("different");
    document.getElementById("set-pin-button").disabled = true;
    document.getElementById("change-pin-button").disabled = true;
    return false;
  }
  pin.classList.remove("different");
  pin_repeat.classList.remove("different");
  document.getElementById("set-pin-button").disabled = false;
  document.getElementById("change-pin-button").disabled = false;
  return true;
}

function send_pin() {
  close_temporary_overlay_tab();
  let pin = document.getElementById("pin-required").value;
  AboutWebauthnService.pinCallback(0, pin);
}

function set_pin() {
  let pin = document.getElementById("new-pin").value;
  let cmd = { SetPIN: pin };
  AboutWebauthnService.runCommand(JSON.stringify(cmd));
}

function change_pin() {
  let curr_pin = document.getElementById("current-pin").value;
  let new_pin = document.getElementById("new-pin").value;
  let cmd = { ChangePIN: [curr_pin, new_pin] };
  AboutWebauthnService.runCommand(JSON.stringify(cmd));
}

function list_credentials() {
  credential_management_in_progress(true);
  let cmd = { CredentialManagement: "GetCredentials" };
  AboutWebauthnService.runCommand(JSON.stringify(cmd));
}

function list_bio_enrollments() {
  bio_enrollment_in_progress(true);
  let cmd = { BioEnrollment: "GetEnrollments" };
  AboutWebauthnService.runCommand(JSON.stringify(cmd));
}

function show_add_bio_enrollment_section() {
  const evt = new CustomEvent("click", {
    detail: { temporary_overlay: true },
  });
  open_tab(evt, "add-bio-enrollment-section");
  document.getElementById("enrollment-name").focus();
}

function start_bio_enrollment() {
  bio_enrollment_in_progress(true);
  let name = document.getElementById("enrollment-name").value;
  if (!name) {
    name = null; // Empty means "Not set"
  }
  let cmd = { BioEnrollment: { StartNewEnrollment: name } };
  AboutWebauthnService.runCommand(JSON.stringify(cmd));
}

function cancel_transaction() {
  credential_management_in_progress(false);
  bio_enrollment_in_progress(false);
  AboutWebauthnService.cancel(0);
}

function confirm_deletion() {
  let context = document.getElementById("confirmation-context");
  let cmd = context.getAttribute("data-ctap-command");
  AboutWebauthnService.runCommand(cmd);
}

function cancel_confirmation() {
  credential_management_in_progress(false);
  bio_enrollment_in_progress(false);
  close_temporary_overlay_tab();
}

async function onLoad() {
  document.getElementById("set-pin-button").addEventListener("click", set_pin);
  document
    .getElementById("change-pin-button")
    .addEventListener("click", change_pin);
  document
    .getElementById("list-credentials-button")
    .addEventListener("click", list_credentials);
  document
    .getElementById("list-bio-enrollments-button")
    .addEventListener("click", list_bio_enrollments);
  document
    .getElementById("add-bio-enrollment-button")
    .addEventListener("click", show_add_bio_enrollment_section);
  document
    .getElementById("start-enrollment-button")
    .addEventListener("click", start_bio_enrollment);
  document
    .getElementById("new-pin")
    .addEventListener("input", check_pin_repeat_is_correct);
  document
    .getElementById("new-pin-repeat")
    .addEventListener("input", check_pin_repeat_is_correct);
  document
    .getElementById("current-pin")
    .addEventListener("input", check_pin_repeat_is_correct);
  let info_button = document.getElementById("info-tab-button");
  info_button.addEventListener("click", open_info_tab);
  info_button.addEventListener("keydown", handle_keydowns);
  let pin_button = document.getElementById("pin-tab-button");
  pin_button.addEventListener("click", open_pin_tab);
  pin_button.addEventListener("keydown", handle_keydowns);
  let credentials_button = document.getElementById("credentials-tab-button");
  credentials_button.addEventListener("click", open_credentials_tab);
  credentials_button.addEventListener("keydown", handle_keydowns);
  let bio_enrollments_button = document.getElementById(
    "bio-enrollments-tab-button"
  );
  bio_enrollments_button.addEventListener("click", open_bio_enrollments_tab);
  bio_enrollments_button.addEventListener("keydown", handle_keydowns);
  document
    .getElementById("send-pin-button")
    .addEventListener("click", send_pin);
  document
    .getElementById("cancel-send-pin-button")
    .addEventListener("click", cancel_transaction);
  document
    .getElementById("cancel-enrollment-button")
    .addEventListener("click", cancel_transaction);
  document
    .getElementById("cancel-confirmation-button")
    .addEventListener("click", cancel_confirmation);
  document
    .getElementById("confirm-deletion-button")
    .addEventListener("click", confirm_deletion);
  AboutWebauthnManagerJS.init();
  try {
    AboutWebauthnService.listen();
  } catch (ex) {
    set_info_text("about-webauthn-text-not-available");
    AboutWebauthnManagerJS.uninit();
  }
}

function handle_keydowns(event) {
  let index;
  let event_was_handled = true;
  let tabs = Array.from(document.getElementsByClassName("category"));
  if (tabs.length <= 0) {
    return;
  }

  switch (event.key) {
    case "ArrowLeft":
    case "ArrowUp":
      if (event.currentTarget === tabs[0]) {
        event.currentTarget.focus();
      } else {
        index = tabs.indexOf(event.currentTarget);
        tabs[index - 1].focus();
      }
      break;

    case "ArrowRight":
    case "ArrowDown":
      if (event.currentTarget === tabs[tabs.length - 1]) {
        event.currentTarget.focus();
      } else {
        index = tabs.indexOf(event.currentTarget);
        tabs[index + 1].focus();
      }
      break;

    case "Home":
      tabs[0].focus();
      break;

    case "End":
      tabs[tabs.length - 1].focus();
      break;

    case "Enter":
    case " ":
      event.currentTarget.click();
      break;

    default:
      event_was_handled = false;
      break;
  }

  if (event_was_handled) {
    event.stopPropagation();
    event.preventDefault();
  }
}

function open_tab(evt, tabName) {
  var tabcontent, tablinks;
  // Hide all others
  tabcontent = Array.from(document.getElementsByClassName("tabcontent"));
  tabcontent.forEach(tab => {
    tab.style.display = "none";
  });
  // Display the one we selected
  document.getElementById(tabName).style.display = "block";

  // If this is a temporary overlay, like pin-required, we don't
  // touch the sidebar and which button is selected.
  if (!evt.detail.temporary_overlay) {
    tablinks = Array.from(document.getElementsByClassName("category"));
    tablinks.forEach(tablink => {
      tablink.removeAttribute("selected");
      tablink.setAttribute("aria-selected", "false");
      tablink.setAttribute("tabindex", "-1");
      tablink.disabled = false;
    });
    evt.currentTarget.setAttribute("selected", "true");
    evt.currentTarget.setAttribute("tabindex", "0");
    evt.currentTarget.setAttribute("aria-selected", "true");
  }

  if (!evt.detail.skip_results_clearing) {
    hide_results_banner();
  }
  sidebar_set_disabled(false);
  AboutWebauthnManagerJS._previous_tab = AboutWebauthnManagerJS._current_tab;
  AboutWebauthnManagerJS._current_tab = tabName;
}

function open_info_tab(evt) {
  open_tab(evt, "token-info-section");
}
function open_pin_tab(evt) {
  open_tab(evt, "set-change-pin-section");
}
function open_credentials_tab(evt) {
  open_tab(evt, "credential-management-section");
}
function open_bio_enrollments_tab(evt) {
  // We can only list, if there are any registered already
  if (
    AboutWebauthnManagerJS._curr_data.options.bioEnroll === true ||
    AboutWebauthnManagerJS._curr_data.options.userVerificationMgmtPreview ===
      true
  ) {
    document.getElementById("list-bio-enrollments-button").style.display =
      "inline-block";
  } else {
    document.getElementById("list-bio-enrollments-button").style.display =
      "none";
  }
  open_tab(evt, "bio-enrollment-section");
}
function open_reset_tab(evt) {
  open_tab(evt, "reset-token-section");
}
function open_pin_required_tab() {
  // Remove any old value we might have had
  document.getElementById("pin-required").value = "";
  const evt = new CustomEvent("click", {
    detail: {
      temporary_overlay: true,
      skip_results_clearing: true, // We might be called multiple times, if PIN was invalid
    },
  });
  open_tab(evt, "pin-required-section");
  document.getElementById("pin-required").focus();
  // This is a temporary overlay, so we don't want the
  // user to click away from it, unless via the Cancel-button.
  sidebar_set_disabled(true);
}
function close_temporary_overlay_tab() {
  const evt = new CustomEvent("click", {
    detail: { temporary_overlay: true },
  });
  open_tab(evt, AboutWebauthnManagerJS._previous_tab);
  sidebar_set_disabled(false);
}
function open_delete_confirmation_tab() {
  const evt = new CustomEvent("click", {
    detail: {
      temporary_overlay: true,
    },
  });
  open_tab(evt, "confirm-deletion-section");
  // This is a temporary overlay, so we don't want the
  // user to click away from it, unless via the Cancel-button.
  sidebar_set_disabled(true);
}

try {
  AboutWebauthnService = Cc["@mozilla.org/webauthn/service;1"].getService(
    Ci.nsIWebAuthnService
  );
  document.addEventListener("DOMContentLoaded", onLoad);
  window.addEventListener("beforeunload", () => {
    AboutWebauthnManagerJS.uninit();
    if (AboutWebauthnService) {
      AboutWebauthnService.cancel(0);
    }
  });
} catch (ex) {
  // Do nothing if we fail to create a singleton instance,
  // showing the default no-module message.
  console.error(ex);
}
PK
!<��,EE9chrome/toolkit/content/global/aboutconfig/aboutconfig.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:root {
  --prefs-table-border-width: 1px;
  --prefs-table-border: var(--prefs-table-border-width) solid var(--in-content-box-border-color);
}

.hidden {
  display: none;
}

.table-shown > #show-all,
.table-shown > .config-background-wrapper {
  display: none;
}

.config-background {
  background: url("chrome://global/content/aboutconfig/background.svg") no-repeat;
  height: 182px;
  margin: 32px auto;
  width: 235px;
}

.config-help-text {
  text-align: center;
}

.title {
  background-image: url("chrome://global/skin/icons/warning.svg");
  fill: #fcd100;
}

#toolbar {
  position: sticky;
  top: 0;
  z-index: 1;
  box-sizing: border-box;
  width: 100%;
  background-color: var(--in-content-page-background);
  padding: 10px;
  padding-bottom: 0;
  min-width: 644px;
  display: flex;
}

.checkbox-container {
  /* Center align and get rid of whitespace. */
  display: inline-flex;
  align-items: center;
  margin-inline-start: 1ch;
}

#about-config-search {
  -moz-context-properties: fill, fill-opacity;
  fill: currentColor;
  box-sizing: border-box;
  flex-grow: 1;
  background-image: url("chrome://global/skin/icons/search-glass.svg");
  background-repeat: no-repeat;
  background-position: 8px center;
  background-size: 16px;
  /* Set horizontal margin to 0 to ensure alignment with table. */
  margin-inline: 0;
  text-align: match-parent;
  /* All prefs must be left-to-right. */
  direction: ltr;
}

@media not (prefers-contrast) {
  #about-config-search {
    fill-opacity: 0.4;
  }
}

#about-config-search:placeholder-shown {
  /* Display the placeholder in its natural directionality,
   * even if the user changes the text direction manually
   * (e.g. via RightCtrl+Shift). */
  direction: inherit;
}

:root:dir(ltr) #about-config-search {
  /* Be explicit about padding direction since
   * `about-config-search` is forced to be LTR. */
  padding-left: 32px;
}

:root:dir(rtl) #about-config-search {
  background-position-x: right 8px;
  padding-right: 32px;
}

#show-all {
  display: block;
  margin: 10px auto;
}

#prefs {
  background-color: var(--in-content-box-background);
  color: var(--in-content-text-color);
  margin: 10px;
  table-layout: fixed;
  width: calc(100% - 20px);
  min-width: 644px;
  /* To stay consistent with about:preferences (664px - 20px margin). */
  border: var(--prefs-table-border);
  border-radius: 4px;
  border-spacing: 0;
}

#prefs > tr.odd {
  background-color: var(--in-content-box-background-odd);
}

#prefs > tr:hover {
  background-color: var(--in-content-item-hover);
  color: var(--in-content-item-hover-text);
}

#prefs > tr.has-user-value {
  font-weight: bold;
}

#prefs > tr.locked {
  opacity: 0.4;
  background-image: url("chrome://global/skin/icons/security.svg");
  background-repeat: no-repeat;
  background-position: 9px center;
  background-size: 16px 16px;
  -moz-context-properties: fill;
  fill: currentColor;
}

#prefs > tr.locked:dir(rtl) {
  background-position-x: right 9px;
}

#prefs > tr > td,
#prefs > tr > th {
  padding: 4px;
  font-weight: inherit;
}

#prefs > tr > th {
  direction: ltr;
  text-align: match-parent;
}

#prefs > tr:dir(ltr) > th {
  /* Be explicit about padding direction since `th` is forced to be LTR. */
  padding-left: 30px;
}

#prefs > tr:dir(rtl) > th {
  padding-right: 30px;
}

#prefs > tr.deleted > th > span {
  font-weight: bold;
  color: var(--text-color-deemphasized);
}

#prefs > tr > td.cell-edit,
#prefs > tr > td.cell-reset {
  width: 40px;
  padding: 0;
}

.cell-value {
  overflow-wrap: anywhere;
  white-space: pre-wrap;
  word-break: break-all;
}

tr:not(.deleted) > .cell-value {
  /* Always display the text in the value cell using left-to-right rules, but
     align it according to the page direction. This doesn't apply to the radio
     buttons shown for deleted preferences. */
  direction: ltr;
  text-align: match-parent;
}

:root:dir(ltr) tr:not(.deleted) > .cell-value > #form-edit {
  /* Make the text in the form stay in the same place as before editing the pref. */
  margin-left: -8px;
}

:root:dir(rtl) tr:not(.deleted) > .cell-value > #form-edit {
  margin-right: -8px;
}

#form-edit > label {
  /* Make the radiobutton's text wrap to a new line along with
     the radiobutton itself, when space is constrained. */
  display: inline-block;
  margin-inline-end: 30px;
}

#form-edit > label:last-of-type {
  margin-inline-end: 0;
}

#form-edit > label > :is(input[type="radio"], span) {
  vertical-align: middle;
}

td.cell-value > form > input[type="text"],
td.cell-value > form > input[type="number"] {
  appearance: textfield;
  margin: 0;
  width: 100%;
  box-sizing: border-box;
  /* Align the text inside the input field in the same way as the table cell,
     for both the left-to-right and right-to-left directions. */
  text-align: match-parent;
}

.button-add,
.button-save,
.button-edit,
.button-toggle,
.button-delete,
.button-reset {
  -moz-context-properties: fill;
  background-position: center;
  background-repeat: no-repeat;
  background-size: 16px;
  fill: currentColor;
  min-width: auto;
  width: 32px;
}

.button-add {
  background-image: url("chrome://global/skin/icons/plus.svg");
}

.button-save {
  background-image: url("chrome://global/skin/icons/check.svg");
}

.button-edit {
  background-image: url("chrome://global/skin/icons/edit.svg");
}

.button-toggle {
  background-image: url("chrome://global/content/aboutconfig/toggle.svg");
}

.button-delete {
  background-image: url("chrome://global/skin/icons/delete.svg");
}

.button-reset {
  background-image: url("chrome://global/skin/icons/undo.svg");
}

.button-reset:dir(rtl) {
  transform: scaleX(-1);
}

/* The ::before creates a blank space between the last visible pref and the add row. */
#prefs[has-visible-prefs] > .add > th::before,
#prefs[has-visible-prefs] > .add > td::before {
  content: "";
  display: block;
  position: absolute;
  top: 0;
  /* Make it wider by the border width so the border-inline is hidden. */
  inset-inline: calc(var(--prefs-table-border-width) * -1);
  height: 12px;
  background-color: var(--in-content-page-background);
  /* This is visually the top border on the add row. */
  border-bottom: var(--prefs-table-border);
}

#prefs[has-visible-prefs] > .add > th,
#prefs[has-visible-prefs] > .add > td {
  /* This is the border underneath the last existing pref row. */
  border-top: var(--prefs-table-border);
  padding-top: 14px;
  position: relative;
}

@media (prefers-contrast) {
  #prefs > tr.deleted:hover > th > span {
    color: inherit;
  }
}
PK
!<��F``:chrome/toolkit/content/global/aboutconfig/aboutconfig.html<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html>
<html>
  <head>
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:; object-src 'none'"
    />
    <meta charset="utf-8" />
    <meta name="color-scheme" content="light dark" />
    <link
      rel="stylesheet"
      media="screen, projection"
      type="text/css"
      href="chrome://global/skin/in-content/common.css"
    />
    <link
      rel="stylesheet"
      media="screen, projection"
      type="text/css"
      href="chrome://global/skin/in-content/info-pages.css"
      title="infop"
    />
    <link
      rel="stylesheet"
      type="text/css"
      href="chrome://global/content/aboutconfig/aboutconfig.css"
    />
    <link rel="localization" href="branding/brand.ftl" />
    <link rel="localization" href="toolkit/about/config.ftl" />
    <link rel="icon" href="chrome://global/skin/icons/settings.svg" />
    <script src="chrome://global/content/aboutconfig/aboutconfig.js"></script>
    <title data-l10n-id="about-config-page-title"></title>
  </head>
  <body>
    <div
      class="container"
      role="alertdialog"
      aria-labelledby="warningTitle"
      aria-describedby="warningDescription"
    >
      <div class="title">
        <h1
          id="warningTitle"
          class="title-text"
          data-l10n-id="about-config-intro-warning-title"
        ></h1>
      </div>

      <div class="description">
        <p
          id="warningDescription"
          data-l10n-id="about-config-intro-warning-text"
        ></p>
      </div>

      <div class="toggle-container-with-text">
        <input type="checkbox" id="showWarningNextTime" checked />
        <label
          for="showWarningNextTime"
          data-l10n-id="about-config-intro-warning-checkbox"
        ></label>
      </div>

      <div class="button-container">
        <button
          id="warningButton"
          class="primary"
          data-l10n-id="about-config-intro-warning-button"
        ></button>
      </div>
    </div>

    <template id="main">
      <div id="toolbar">
        <!-- Use a unique ID to prevent showing autocomplete results from other
             browser pages with similarly named fields. -->
        <input
          type="text"
          id="about-config-search"
          data-l10n-id="about-config-search-input1"
        />
        <label class="checkbox-container">
          <input type="checkbox" id="about-config-show-only-modified" />
          <span data-l10n-id="about-config-show-only-modified"></span>
        </label>
      </div>

      <table id="prefs"></table>

      <div class="config-background-wrapper">
        <button
          id="show-all"
          class="ghost-button"
          data-l10n-id="about-config-show-all"
        ></button>
        <div class="config-background"></div>
        <p
          class="config-help-text"
          data-l10n-id="about-config-caution-text"
        ></p>
      </div>
    </template>
  </body>
</html>
PK
!<>�ފU�U8chrome/toolkit/content/global/aboutconfig/aboutconfig.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { DeferredTask } = ChromeUtils.importESModule(
  "resource://gre/modules/DeferredTask.sys.mjs"
);
const { Preferences } = ChromeUtils.importESModule(
  "resource://gre/modules/Preferences.sys.mjs"
);

const SEARCH_TIMEOUT_MS = 100;
const SEARCH_AUTO_MIN_CRARACTERS = 3;

const GETTERS_BY_PREF_TYPE = {
  [Ci.nsIPrefBranch.PREF_BOOL]: "getBoolPref",
  [Ci.nsIPrefBranch.PREF_INT]: "getIntPref",
  [Ci.nsIPrefBranch.PREF_STRING]: "getStringPref",
};

const STRINGS_ADD_BY_TYPE = {
  Boolean: "about-config-pref-add-type-boolean",
  Number: "about-config-pref-add-type-number",
  String: "about-config-pref-add-type-string",
};

// Fluent limits the maximum length of placeables.
const MAX_PLACEABLE_LENGTH = 2500;

let gDefaultBranch = Services.prefs.getDefaultBranch("");
let gFilterPrefsTask = new DeferredTask(
  () => filterPrefs(),
  SEARCH_TIMEOUT_MS,
  0
);

/**
 * Maps the name of each preference in the back-end to its PrefRow object,
 * separating the preferences that actually exist. This is as an optimization to
 * avoid querying the preferences service each time the list is filtered.
 */
let gExistingPrefs = new Map();
let gDeletedPrefs = new Map();

/**
 * Also cache several values to improve the performance of common use cases.
 */
let gSortedExistingPrefs = null;
let gSearchInput = null;
let gShowOnlyModifiedCheckbox = null;
let gPrefsTable = null;

/**
 * Reference to the PrefRow currently being edited, if any.
 */
let gPrefInEdit = null;

/**
 * Lowercase substring that should be contained in the preference name.
 */
let gFilterString = null;

/**
 * RegExp that should be matched to the preference name.
 */
let gFilterPattern = null;

/**
 * True if we were requested to show all preferences.
 */
let gFilterShowAll = false;

class PrefRow {
  constructor(name, opts) {
    this.name = name;
    this.value = true;
    this.hidden = false;
    this.odd = false;
    this.editing = false;
    this.isAddRow = opts && opts.isAddRow;
    this.refreshValue();
  }

  refreshValue() {
    let prefType = Services.prefs.getPrefType(this.name);

    // If this preference has been deleted, we keep its last known value.
    if (prefType == Ci.nsIPrefBranch.PREF_INVALID) {
      this.hasDefaultValue = false;
      this.hasUserValue = false;
      this.isLocked = false;
      if (gExistingPrefs.has(this.name)) {
        gExistingPrefs.delete(this.name);
        gSortedExistingPrefs = null;
      }
      gDeletedPrefs.set(this.name, this);
      return;
    }

    if (!gExistingPrefs.has(this.name)) {
      gExistingPrefs.set(this.name, this);
      gSortedExistingPrefs = null;
    }
    gDeletedPrefs.delete(this.name);

    try {
      this.value = gDefaultBranch[GETTERS_BY_PREF_TYPE[prefType]](this.name);
      this.hasDefaultValue = true;
    } catch (ex) {
      this.hasDefaultValue = false;
    }
    this.hasUserValue = Services.prefs.prefHasUserValue(this.name);
    this.isLocked = Services.prefs.prefIsLocked(this.name);

    try {
      if (this.hasUserValue) {
        // This can throw for locked preferences without a default value.
        this.value = Services.prefs[GETTERS_BY_PREF_TYPE[prefType]](this.name);
      } else if (/^chrome:\/\/.+\/locale\/.+\.properties/.test(this.value)) {
        // We don't know which preferences should be read using getComplexValue,
        // so we use a heuristic to determine if this is a localized preference.
        // This can throw if there is no value in the localized files.
        this.value = Services.prefs.getComplexValue(
          this.name,
          Ci.nsIPrefLocalizedString
        ).data;
      }
    } catch (ex) {
      this.value = "";
    }
  }

  get type() {
    return this.value.constructor.name;
  }

  get exists() {
    return this.hasDefaultValue || this.hasUserValue;
  }

  get matchesFilter() {
    if (!this.matchesModifiedFilter) {
      return false;
    }

    return (
      gFilterShowAll ||
      (gFilterPattern && gFilterPattern.test(this.name)) ||
      (gFilterString && this.name.toLowerCase().includes(gFilterString))
    );
  }

  get matchesModifiedFilter() {
    const onlyShowModified = gShowOnlyModifiedCheckbox.checked;
    return !onlyShowModified || this.hasUserValue;
  }

  /**
   * Returns a reference to the table row element to be added to the document,
   * constructing and initializing it the first time this method is called.
   */
  getElement() {
    if (this._element) {
      return this._element;
    }

    this._element = document.createElement("tr");
    this._element._pref = this;

    let nameCell = document.createElement("th");
    let nameCellSpan = document.createElement("span");
    nameCell.appendChild(nameCellSpan);
    this._element.append(
      nameCell,
      (this.valueCell = document.createElement("td")),
      (this.editCell = document.createElement("td")),
      (this.resetCell = document.createElement("td"))
    );
    this.editCell.appendChild(
      (this.editButton = document.createElement("button"))
    );
    delete this.resetButton;

    nameCell.setAttribute("scope", "row");
    this.valueCell.className = "cell-value";
    this.editCell.className = "cell-edit";
    this.resetCell.className = "cell-reset";

    // Add <wbr> behind dots to prevent line breaking in random mid-word places.
    let parts = this.name.split(".");
    for (let i = 0; i < parts.length - 1; i++) {
      nameCellSpan.append(parts[i] + ".", document.createElement("wbr"));
    }
    nameCellSpan.append(parts[parts.length - 1]);

    this.refreshElement();

    return this._element;
  }

  refreshElement() {
    if (!this._element) {
      // No need to update if this preference was never added to the table.
      return;
    }

    if (this.exists && !this.editing) {
      // We need to place the text inside a "span" element to ensure that the
      // text copied to the clipboard includes all whitespace.
      let span = document.createElement("span");
      span.textContent = this.value;
      // We additionally need to wrap this with another "span" element to convey
      // the state to screen readers without affecting the visual presentation.
      span.setAttribute("aria-hidden", "true");
      let outerSpan = document.createElement("span");
      if (this.type == "String" && this.value.length > MAX_PLACEABLE_LENGTH) {
        // If the value is too long for localization, don't include the state.
        // Since the preferences system is designed to store short values, this
        // case happens very rarely, thus we keep the same DOM structure for
        // consistency even though we could avoid the extra "span" element.
        outerSpan.setAttribute("aria-label", this.value);
      } else {
        let spanL10nId = this.hasUserValue
          ? "about-config-pref-accessible-value-custom"
          : "about-config-pref-accessible-value-default";
        document.l10n.setAttributes(outerSpan, spanL10nId, {
          value: "" + this.value,
        });
      }
      outerSpan.appendChild(span);
      this.valueCell.textContent = "";
      this.valueCell.append(outerSpan);
      if (this.type == "Boolean") {
        document.l10n.setAttributes(
          this.editButton,
          "about-config-pref-toggle-button"
        );
        this.editButton.className = "button-toggle semi-transparent";
      } else {
        document.l10n.setAttributes(
          this.editButton,
          "about-config-pref-edit-button"
        );
        this.editButton.className = "button-edit semi-transparent";
      }
      this.editButton.removeAttribute("form");
      delete this.inputField;
    } else {
      this.valueCell.textContent = "";
      // The form is needed for the validation report to appear, but we need to
      // prevent the associated button from reloading the page.
      let form = document.createElement("form");
      form.addEventListener("submit", event => event.preventDefault());
      form.id = "form-edit";
      if (this.editing) {
        this.inputField = document.createElement("input");
        this.inputField.value = this.value;
        this.inputField.ariaLabel = this.name;
        if (this.type == "Number") {
          this.inputField.type = "number";
          this.inputField.required = true;
          this.inputField.min = -2147483648;
          this.inputField.max = 2147483647;
        } else {
          this.inputField.type = "text";
        }
        form.appendChild(this.inputField);
        document.l10n.setAttributes(
          this.editButton,
          "about-config-pref-save-button"
        );
        this.editButton.className = "primary button-save semi-transparent";
      } else {
        delete this.inputField;
        for (let type of ["Boolean", "Number", "String"]) {
          let radio = document.createElement("input");
          radio.type = "radio";
          radio.name = "type";
          radio.value = type;
          radio.checked = this.type == type;
          let radioSpan = document.createElement("span");
          document.l10n.setAttributes(radioSpan, STRINGS_ADD_BY_TYPE[type]);
          let radioLabel = document.createElement("label");
          radioLabel.append(radio, radioSpan);
          form.appendChild(radioLabel);
        }
        form.addEventListener("click", event => {
          if (event.target.name != "type") {
            return;
          }
          let type = event.target.value;
          if (this.type != type) {
            if (type == "Boolean") {
              this.value = true;
            } else if (type == "Number") {
              this.value = 0;
            } else {
              this.value = "";
            }
          }
        });
        document.l10n.setAttributes(
          this.editButton,
          "about-config-pref-add-button"
        );
        this.editButton.className = "button-add semi-transparent";
      }
      this.valueCell.appendChild(form);
      this.editButton.setAttribute("form", "form-edit");
    }
    this.editButton.disabled = this.isLocked;
    if (!this.isLocked && this.hasUserValue) {
      if (!this.resetButton) {
        this.resetButton = document.createElement("button");
        this.resetCell.appendChild(this.resetButton);
      }
      if (!this.hasDefaultValue) {
        document.l10n.setAttributes(
          this.resetButton,
          "about-config-pref-delete-button"
        );
        this.resetButton.className =
          "button-delete ghost-button semi-transparent";
      } else {
        document.l10n.setAttributes(
          this.resetButton,
          "about-config-pref-reset-button"
        );
        this.resetButton.className =
          "button-reset ghost-button semi-transparent";
      }
    } else if (this.resetButton) {
      this.resetButton.remove();
      delete this.resetButton;
    }

    this.refreshClass();
  }

  refreshClass() {
    if (!this._element) {
      // No need to update if this preference was never added to the table.
      return;
    }

    let className;
    if (this.hidden) {
      className = "hidden";
    } else {
      className =
        (this.hasUserValue ? "has-user-value " : "") +
        (this.isLocked ? "locked " : "") +
        (this.exists ? "" : "deleted ") +
        (this.isAddRow ? "add " : "") +
        (this.odd ? "odd " : "");
    }

    if (this._lastClassName !== className) {
      this._element.className = this._lastClassName = className;
    }
  }

  edit() {
    if (gPrefInEdit) {
      gPrefInEdit.endEdit();
    }
    gPrefInEdit = this;
    this.editing = true;
    this.refreshElement();
    // The type=number input isn't selected unless it's focused first.
    this.inputField.focus();
    this.inputField.select();
  }

  toggle() {
    Services.prefs.setBoolPref(this.name, !this.value);
  }

  editOrToggle() {
    if (this.type == "Boolean") {
      this.toggle();
    } else {
      this.edit();
    }
  }

  save() {
    if (this.type == "Number") {
      if (!this.inputField.reportValidity()) {
        return;
      }
      Services.prefs.setIntPref(this.name, parseInt(this.inputField.value));
    } else {
      Services.prefs.setStringPref(this.name, this.inputField.value);
    }
    this.refreshValue();
    this.endEdit();
    this.editButton.focus();
  }

  endEdit() {
    this.editing = false;
    this.refreshElement();
    gPrefInEdit = null;
  }
}

let gPrefObserverRegistered = false;
let gPrefObserver = {
  observe(subject, topic, data) {
    let pref = gExistingPrefs.get(data) || gDeletedPrefs.get(data);
    if (pref) {
      pref.refreshValue();
      if (!pref.editing) {
        pref.refreshElement();
      }
      return;
    }

    let newPref = new PrefRow(data);
    if (newPref.matchesFilter) {
      document.getElementById("prefs").appendChild(newPref.getElement());
    }
  },
};

if (!Preferences.get("browser.aboutConfig.showWarning")) {
  // When showing the filtered preferences directly, remove the warning elements
  // immediately to prevent flickering, but wait to filter the preferences until
  // the value of the textbox has been restored from previous sessions.
  document.addEventListener("DOMContentLoaded", loadPrefs, { once: true });
  window.addEventListener(
    "load",
    () => {
      if (document.getElementById("about-config-search").value) {
        filterPrefs();
      }
    },
    { once: true }
  );
} else {
  document.addEventListener("DOMContentLoaded", function () {
    let warningButton = document.getElementById("warningButton");
    warningButton.addEventListener("click", onWarningButtonClick);
    warningButton.focus({ focusVisible: false });
  });
}

function onWarningButtonClick() {
  Services.prefs.setBoolPref(
    "browser.aboutConfig.showWarning",
    document.getElementById("showWarningNextTime").checked
  );
  loadPrefs();
}

function loadPrefs() {
  [...document.styleSheets].find(s => s.title == "infop").disabled = true;

  let { content } = document.getElementById("main");
  document.body.textContent = "";
  document.body.appendChild(content);

  let search = (gSearchInput = document.getElementById("about-config-search"));
  let prefs = (gPrefsTable = document.getElementById("prefs"));
  let showAll = document.getElementById("show-all");
  gShowOnlyModifiedCheckbox = document.getElementById(
    "about-config-show-only-modified"
  );
  search.focus();
  gShowOnlyModifiedCheckbox.checked = false;

  for (let name of Services.prefs.getChildList("")) {
    new PrefRow(name);
  }

  search.addEventListener("keypress", event => {
    if (event.key == "Escape") {
      // The ESC key returns immediately to the initial empty page.
      search.value = "";
      gFilterPrefsTask.disarm();
      filterPrefs();
    } else if (event.key == "Enter") {
      // The Enter key filters immediately even if the search string is short.
      gFilterPrefsTask.disarm();
      filterPrefs({ shortString: true });
    }
  });

  search.addEventListener("input", () => {
    // We call "disarm" to restart the timer at every input.
    gFilterPrefsTask.disarm();
    if (search.value.trim().length < SEARCH_AUTO_MIN_CRARACTERS) {
      // Return immediately to the empty page if the search string is short.
      filterPrefs();
    } else {
      gFilterPrefsTask.arm();
    }
  });

  gShowOnlyModifiedCheckbox.addEventListener("change", () => {
    // This checkbox:
    // - Filters results to only modified prefs when search query is entered
    // - Shows all modified prefs, in show all mode, and after initial checkbox click
    let tableHidden = !document.body.classList.contains("table-shown");
    filterPrefs({
      showAll:
        gFilterShowAll || (gShowOnlyModifiedCheckbox.checked && tableHidden),
    });
  });

  showAll.addEventListener("click", () => {
    search.focus();
    search.value = "";
    gFilterPrefsTask.disarm();
    filterPrefs({ showAll: true });
  });

  function shouldBeginEdit(event) {
    if (
      event.target.localName != "button" &&
      event.target.localName != "input"
    ) {
      let row = event.target.closest("tr");
      return row && row._pref.exists;
    }
    return false;
  }

  // Disable double/triple-click text selection since that triggers edit/toggle.
  prefs.addEventListener("mousedown", event => {
    if (event.detail > 1 && shouldBeginEdit(event)) {
      event.preventDefault();
    }
  });

  prefs.addEventListener("click", event => {
    if (event.detail == 2 && shouldBeginEdit(event)) {
      event.target.closest("tr")._pref.editOrToggle();
      return;
    }

    if (event.target.localName != "button") {
      return;
    }

    let pref = event.target.closest("tr")._pref;
    let button = event.target.closest("button");

    if (button.classList.contains("button-add")) {
      pref.isAddRow = false;
      Preferences.set(pref.name, pref.value);
      if (pref.type == "Boolean") {
        pref.refreshClass();
      } else {
        pref.edit();
      }
    } else if (
      button.classList.contains("button-toggle") ||
      button.classList.contains("button-edit")
    ) {
      pref.editOrToggle();
    } else if (button.classList.contains("button-save")) {
      pref.save();
    } else {
      // This is "button-reset" or "button-delete".
      pref.editing = false;
      Services.prefs.clearUserPref(pref.name);
      pref.editButton.focus();
    }
  });

  window.addEventListener("keypress", event => {
    if (event.target != search && event.key == "Escape" && gPrefInEdit) {
      gPrefInEdit.endEdit();
    }
  });
}

function filterPrefs(options = {}) {
  if (gPrefInEdit) {
    gPrefInEdit.endEdit();
  }
  gDeletedPrefs.clear();

  let searchName = gSearchInput.value.trim();
  if (searchName.length < SEARCH_AUTO_MIN_CRARACTERS && !options.shortString) {
    searchName = "";
  }

  gFilterString = searchName.toLowerCase();
  gFilterShowAll = !!options.showAll;

  gFilterPattern = null;
  if (gFilterString.includes("*")) {
    gFilterPattern = new RegExp(gFilterString.replace(/\*+/g, ".*"), "i");
    gFilterString = "";
  }

  let showResults = gFilterString || gFilterPattern || gFilterShowAll;
  document.body.classList.toggle("table-shown", showResults);

  let prefArray = [];
  if (showResults) {
    if (!gSortedExistingPrefs) {
      gSortedExistingPrefs = [...gExistingPrefs.values()];
      gSortedExistingPrefs.sort((a, b) => a.name > b.name);
    }
    prefArray = gSortedExistingPrefs;
  }

  // The slowest operations tend to be the addition and removal of DOM nodes, so
  // this algorithm tries to reduce removals by hiding nodes instead. This
  // happens frequently when the set narrows while typing preference names. We
  // iterate the nodes already in the table in parallel to those we want to
  // show, because the two lists are sorted and they will often match already.
  let fragment = null;
  let indexInArray = 0;
  let elementInTable = gPrefsTable.firstElementChild;
  let odd = false;
  let hasVisiblePrefs = false;
  while (indexInArray < prefArray.length || elementInTable) {
    // For efficiency, filter the array while we are iterating.
    let prefInArray = prefArray[indexInArray];
    if (prefInArray) {
      if (!prefInArray.matchesFilter) {
        indexInArray++;
        continue;
      }
      prefInArray.hidden = false;
      prefInArray.odd = odd;
    }

    let prefInTable = elementInTable && elementInTable._pref;
    if (!prefInTable) {
      // We're at the end of the table, we just have to insert all the matching
      // elements that remain in the array. We can use a fragment to make the
      // insertions faster, which is useful during the initial filtering.
      if (!fragment) {
        fragment = document.createDocumentFragment();
      }
      fragment.appendChild(prefInArray.getElement());
    } else if (prefInTable == prefInArray) {
      // We got two matching elements, we just need to update the visibility.
      elementInTable = elementInTable.nextElementSibling;
    } else if (prefInArray && prefInArray.name < prefInTable.name) {
      // The iteration in the table is ahead of the iteration in the array.
      // Insert or move the array element, and advance the array index.
      gPrefsTable.insertBefore(prefInArray.getElement(), elementInTable);
    } else {
      // The iteration in the array is ahead of the iteration in the table.
      // Hide the element in the table, and advance to the next element.
      let nextElementInTable = elementInTable.nextElementSibling;
      if (!prefInTable.exists) {
        // Remove rows for deleted preferences, or temporary addition rows.
        elementInTable.remove();
      } else {
        // Keep the element for the next filtering if the preference exists.
        prefInTable.hidden = true;
        prefInTable.refreshClass();
      }
      elementInTable = nextElementInTable;
      continue;
    }

    prefInArray.refreshClass();
    odd = !odd;
    indexInArray++;
    hasVisiblePrefs = true;
  }

  if (fragment) {
    gPrefsTable.appendChild(fragment);
  }

  gPrefsTable.toggleAttribute("has-visible-prefs", hasVisiblePrefs);

  if (searchName && !gExistingPrefs.has(searchName)) {
    let addPrefRow = new PrefRow(searchName, { isAddRow: true });
    addPrefRow.odd = odd;
    gPrefsTable.appendChild(addPrefRow.getElement());
  }

  // We only start observing preference changes after the first search is done,
  // so that newly added preferences won't appear while the page is still empty.
  if (!gPrefObserverRegistered) {
    gPrefObserverRegistered = true;
    Services.prefs.addObserver("", gPrefObserver);
    window.addEventListener(
      "unload",
      () => {
        Services.prefs.removeObserver("", gPrefObserver);
      },
      { once: true }
    );
  }
}
PK
!<K���8chrome/toolkit/content/global/aboutconfig/background.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg"><linearGradient id="a"><stop offset="0" stop-color="#ccfbff"/><stop offset="1" stop-color="#c9e4ff"/></linearGradient><linearGradient id="b" gradientTransform="matrix(1 0 0 -1 0 320)" gradientUnits="userSpaceOnUse" x1="6.5576" x2="120.5576" href="#a" y1="318.3719" y2="152.8721"/><linearGradient id="c" gradientTransform="matrix(1 0 0 -1 0 320)" gradientUnits="userSpaceOnUse" x1="37.205" x2="151.205" href="#a" y1="339.4831" y2="173.9827"/><linearGradient id="d"><stop offset="0" stop-color="#00c8d7"/><stop offset="1" stop-color="#0a84ff"/></linearGradient><linearGradient id="e" gradientTransform="matrix(1 0 0 -1 0 320)" gradientUnits="userSpaceOnUse" x1="-51.3427" x2="242.3216" href="#d" y1="411.209" y2="56.9062"/><linearGradient id="f" gradientTransform="matrix(1 0 0 -1 0 320)" gradientUnits="userSpaceOnUse" x1="98.7014" x2="116.0054" href="#d" y1="238.5525" y2="212.9205"/><linearGradient id="g" gradientTransform="matrix(1 0 0 -1 0 320)" gradientUnits="userSpaceOnUse" x1="70.9844" x2="164.4848" href="#d" y1="291.4737" y2="152.9739"/><linearGradient id="h" gradientTransform="matrix(1 0 0 -1 0 320)" gradientUnits="userSpaceOnUse" x1="53.6667" x2="192.6669" href="#a" y1="337.889" y2="159.389"/><linearGradient id="i" gradientTransform="matrix(1 0 0 -1 0 320)" gradientUnits="userSpaceOnUse" x1="16.7153" x2="201.7151" href="#d" y1="374.8648" y2="156.3646"/><g fill="#eaeaee"><path d="m174.2 162.1c16.4-3.1 26.8-7.5 26.8-12.5 0-9.3-35.7-16.8-79.8-16.8s-79.8 7.5-79.8 16.8c0 7.8 25.2 14.3 59.3 16.2-1.5 1.2-2.3 2.5-2.3 3.8 0 6.5 19.1 11.7 42.8 11.7s42.8-5.3 42.8-11.7c-.1-2.8-3.7-5.4-9.8-7.5z"/><ellipse cx="203.2" cy="165.4" rx="12.3" ry="4"/><ellipse cx="78.9" cy="171.1" rx="9.5" ry="3.3"/><ellipse cx="207.9" cy="156.9" rx="4.8" ry="2"/><path d="m53.6 93.2h123.4c.6 0 1.1-.5 1.1-1.1s-.5-1.1-1.1-1.1h-123.4c-.6 0-1.1.5-1.1 1.1s.5 1.1 1.1 1.1zm16.7-5.6h30.9c.3 0 .6-.3.6-.6s-.3-.6-.6-.6h-30.9c-.3 0-.6.3-.6.6s.3.6.6.6zm-38.9 10.5h13.4c.3 0 .6-.3.6-.6s-.3-.6-.6-.6h-13.4c-.3 0-.6.3-.6.6s.3.6.6.6zm22.3-1.1c-.3 0-.6.3-.6.6s.3.6.6.6h3.3c.3 0 .6-.3.6-.6s-.3-.6-.6-.6zm123.1.5c0 .3.3.6.6.6h3.3c.3 0 .6-.3.6-.6s-.3-.5-.6-.5h-3.3c-.3 0-.6.2-.6.5zm8.4.6h1.1c.3 0 .6-.3.6-.6s-.3-.6-.6-.6h-1.1c-.3 0-.6.3-.6.6s.3.6.6.6zm-122.6-1.1h-1.1c-.3 0-.6.3-.6.6s.3.6.6.6h1.1c.3 0 .6-.3.6-.6 0-.4-.3-.6-.6-.6zm133.7 1.1h13.4c.3 0 .6-.3.6-.6s-.3-.5-.6-.5h-13.4c-.3 0-.6.3-.6.6s.3.5.6.5zm-27.3-.6c0-.3-.3-.6-.6-.6h-13.4c-.3 0-.6.3-.6.6s.3.6.6.6h13.4c.4 0 .6-.3.6-.6zm52.5 29.2h-56.7c-.6 0-1.1.5-1.1 1.1s.5 1.1 1.1 1.1h56.7c.6 0 1.1-.5 1.1-1.1s-.5-1.1-1.1-1.1zm-162 0h-50.6c-.6 0-1.1.5-1.1 1.1s.5 1.1 1.1 1.1h50.7c.6 0 1.1-.5 1.1-1.1s-.6-1.1-1.2-1.1z"/></g><path d="m163.4 155.3-12.3-103.2c-.3-2.1-2-3.7-4.2-3.7h-62c-2.4 0-4.1 1.8-4.6 4l-19.3 92.3c-.3 1.2.1 2.5.9 3.5s2 1.5 3.3 1.5h27.1l.8 6.5c.3 2.1 2 3.7 4.2 3.7h62c1.2 0 2.3-.5 3.1-1.4s1.1-2 1-3.2z" fill="#fff"/><path d="m94.7 144.1-10-83.1-17.3 83.1z" fill="url(#b)"/><path d="m86.9 54.2 12 100.2h59l-11.9-100.2z" fill="url(#c)"/><path d="m161.1 155.7-12.3-103.2c-.1-.8-.7-1.3-1.5-1.3h-62c-.9 0-1.6.5-1.8 1.3l-19.4 92.9c-.1.4 0 .9.3 1.3.3.3.7.6 1.2.6h29.4l1.1 8.9c.1.8.7 1.3 1.5 1.3h62c.4 0 .8-.2 1.1-.5.3-.5.5-.9.4-1.3zm-79.3-80.4 8 68.9h-22.4c0-.1 14.4-68.9 14.4-68.9zm17.1 79.1-12-100.2h59l12 100.2z" fill="url(#e)"/><path d="m91 57.7 11.3 92.5h51.9l-11.3-92.5z" fill="#f9f9fa"/><path d="m108.3 91.1c-2.2-.1-4 1.6-4.2 3.8-.1 2.3 1.5 4.2 3.6 4.4.8.1 1.5-.1 2.1-.5.4-1.5 1-3.6 1.6-5.6-.6-1.2-1.8-2-3.1-2.1z" fill="url(#f)"/><path d="m143 105.5-11.9-5.2c-.5-.2-1.2-.2-1.6.2l-5.8 4.8-.1-.1-6.8-6.2 10.4-10.3c.6-.6.7-1.7.1-2.4s-1.6-.7-2.3-.1l-10.1 10c1.2-4.1 3-10.4 4.3-14.8.3-.9-.2-1.8-1.1-2.1-.8-.3-1.7.2-2 1.1 0 0-.7 2.5-1.7 5.6-.4 1.2-.7 2.5-1.1 3.9-2.9 9.9-2.8 10.1-2.8 10.6 0 .3.2.6.3.8l.1.1c.2.3.4.6.6.8l8.2 7.5.2.2.1.1 5.2 4.8-5.5 9.9-1.4 2.6c-.4.8-.2 1.8.6 2.3.3.2.6.3.9.2.5 0 1-.3 1.3-.8l7.6-13.7c.4-.7.3-1.6-.3-2.1l-4.3-4.1.1-.1 1.7-1.4 4.7-3.9 7.8 3.4 3.2 1.4c.2.1.5.1.7.1.6 0 1.1-.4 1.4-1 .5-.8.2-1.8-.7-2.1z" fill="url(#g)"/><path d="m139.2 30h-27.2c-2.4-4.4-8.4-14-15.9-15.4-10-1.9-12 8-12 8s-6.6-17.2-23.4-15c-18.5 2.5-10.2 21.7-10 22.4h-28.3c-.7 0-1.3.6-1.3 1.3s.6 1.3 1.3 1.3h116.8c.7 0 1.3-.6 1.3-1.3s-.6-1.3-1.3-1.3z" fill="#fff"/><path d="m138.4 27.6h-7.3c-.4 0-.7-.3-.7-.7s.3-.7.7-.7h7.3c.4 0 .7.3.7.7s-.4.7-.7.7zm-19.1 0h-1.3c-.4 0-.7-.3-.7-.7s.3-.7.7-.7h1.3c.4 0 .7.3.7.7-.1.4-.4.7-.7.7zm-68.1-.8h-2.1c-.4 0-.7-.3-.7-.7s.3-.7.7-.7h1.1c-.1-.2-.2-.5-.3-.8s.1-.7.4-.8.7.1.8.4c.3 1 .6 1.6.6 1.6.1.2.1.4 0 .6 0 .3-.2.4-.5.4zm-12.6 0h-15.7c-.4 0-.7-.3-.7-.7s.3-.7.7-.7h15.7c.4 0 .7.3.7.7s-.3.7-.7.7zm74.6 0c-.2 0-.5-.1-.6-.3-.4-.8-1.1-1.9-2-3.3-.2-.3-.1-.7.2-.9s.7-.1.9.2c.9 1.4 1.7 2.6 2.1 3.4.2.3.1.7-.3.9-.1-.1-.2 0-.3 0zm-63.8-6.8c-.3 0-.6-.2-.6-.6-.1-.4-.1-.9-.1-1.3s.2-.7.6-.7.7.2.7.6.1.9.1 1.3c0 .3-.3.7-.7.7zm34.1-2.9c-.2 0-.5-.1-.6-.3-.2-.3-.4-.7-.6-1.1-.2-.3-.1-.7.2-.9s.7-.1.9.2c.2.4.5.8.7 1.2.2.3.1.7-.3.9-.1 0-.2 0-.3 0zm20.8-1.6c-.2 0-.3-.1-.4-.2-2.5-2.1-5-3.5-7.4-3.9-2.4-.5-4.5-.2-6.3.7-.3.2-.7 0-.9-.3s0-.7.3-.9c2-1 4.4-1.3 7.1-.8s5.4 1.9 8.1 4.2c.3.2.3.6.1.9-.2.2-.4.3-.6.3zm-24.5-3.7c-.2 0-.4-.1-.5-.2-.9-1-1.8-1.9-2.7-2.7-.3-.2-.3-.6-.1-.9s.6-.3.9-.1c1 .8 2 1.8 2.9 2.8.2.3.2.7-.1.9-.1.1-.3.2-.4.2zm-27-3.9c-.2 0-.4-.1-.5-.2-.3-.3-.3-.7 0-.9 2.1-1.9 5.1-3.1 8.8-3.6 2.3-.3 4.5-.3 6.6.1.4.1.6.4.5.8s-.4.6-.8.5c-2-.3-4.1-.4-6.2-.1-3.5.5-6.2 1.6-8.1 3.3 0 .1-.2.1-.3.1z" fill="#eaeaee"/><path d="m233.4 63.7h-15.4c-1.3-2.5-4.7-7.8-8.9-8.6-5.6-1.1-6.7 4.5-6.7 4.5s-3.7-9.6-13.1-8.4c-10.5 1.4-5.6 12.5-5.6 12.5h-15.8.5-.2c-.7 0-1.3.6-1.3 1.3s.6 1.3 1.3 1.3h65.2c.7 0 1.3-.6 1.3-1.3s-.6-1.3-1.3-1.3z" fill="#fff"/><path d="m184.2 61.9h-15.8c-.4 0-.7-.3-.7-.7s.3-.7.7-.7h15.8c.4 0 .7.3.7.7s-.3.7-.7.7zm48.6-.2h-.6c-.4 0-.7-.3-.7-.7s.3-.7.7-.7h.6c.4 0 .7.3.7.7s-.3.7-.7.7zm-5.8 0h-4c-.4 0-.7-.3-.7-.7s.3-.7.7-.7h3.9c.4 0 .7.3.7.7s-.3.7-.6.7zm-24.2-3.9c-.3 0-.5-.2-.6-.4s-.1-.4.2-1.1c.6-1.8 2.3-4.5 5.8-4.5.5 0 1 0 1.5.1 2 .4 3.9 1.5 5.9 3.4.3.3.3.7 0 .9-.3.3-.7.3-.9 0-1.8-1.7-3.5-2.7-5.2-3-.4-.1-.8-.1-1.2-.1-3.8 0-4.6 3.9-4.7 4.1-.2.4-.5.6-.8.6zm-18.3-5.9c-.1 0-.3-.1-.4-.2-.3-.2-.3-.6-.1-.9.9-1 2.1-1.8 3.6-2.3.3-.1.7.1.8.4s-.1.7-.4.8c-1.3.4-2.3 1-3 1.9-.1.2-.3.3-.5.3zm9.8-2.2c-.1 0-.1 0-.2 0-.4-.1-.8-.2-1.2-.3s-.6-.4-.6-.7.4-.6.7-.6c.5.1.9.2 1.4.3.3.1.6.5.5.8 0 .3-.3.5-.6.5z" fill="#eaeaee"/><path d="m128.8 68.2h-21c-1.7 0-3-1.3-3-3 0-1.6 1.3-3 3-3h21c1.6 0 3 1.3 3 3 0 1.6-1.3 3-3 3z" fill="url(#h)"/><path d="m128.8 68.8h-21c-2 0-3.6-1.6-3.6-3.6s1.6-3.6 3.6-3.6h21c2 0 3.6 1.6 3.6 3.6 0 1.9-1.6 3.6-3.6 3.6zm-21-6c-1.3 0-2.4 1.1-2.4 2.4s1.1 2.4 2.4 2.4h21c1.3 0 2.4-1.1 2.4-2.4s-1.1-2.4-2.4-2.4z" fill="url(#i)"/></svg>
PK
!<��8��4chrome/toolkit/content/global/aboutconfig/toggle.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="context-fill">
  <path d="M15 9H.5c-.39.39-.39 1.61 0 2l4.793 4.707a1 1 0 0 0 1.414-1.414L3.414 11H15a1 1 0 0 0 0-2zM1 6.988h14.5c.39-.39.39-1.61 0-2L10.707.28a1 1 0 0 0-1.414 1.414l3.293 3.293H1a1 1 0 0 0 0 2z"/>
</svg>

PK
!<�ή�&&9chrome/toolkit/content/global/aboutwebrtc/aboutWebrtc.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

body {
  margin: 8px;
}

table {
  font-family: monospace;
  border: 1px solid var(--in-content-border-color);
  border-spacing: 0;
  margin-block: 1em;
}

.controls {
  font-size: 1.1em;
  display: inline-block;
  margin: 0 0.5em;
}

.control {
  margin: 0.5em 0;
}

.message > p {
  margin: 4px;
}

.log p,
.prefs p {
  font-family: monospace;
  padding-inline-start: 2em;
  text-indent: -2em;
  margin-block: 2px;
}

#content > div,
#mediactx > div {
  padding: 1em 2em;
  margin: 1em 0;
  border: 1px solid var(--in-content-box-border-color);
  border-radius: 10px;
  background-color: var(--in-content-box-background);
}

.autorefresh {
  font-size: var(--font-size-small);
  margin-inline-end: 0.5em;
}

.section-heading {
  display: flex;
  align-items: center;

  > h3,
  > h4 {
    margin-inline-end: 1em;
  }

  > .fold-trigger {
    margin-inline-end: 1em;
  }

  > button {
    margin-inline: 1em;
  }
}

.fold-target {
  border-inline-start: 1px solid var(--in-content-border-color);
  padding-inline-start: 1em;

  .section-body > & {
    display: block;
  }
}

.peer-connection > h3 {
  background-color: var(--in-content-box-info-background);
  padding: 4px;
}

h3 > span {
  margin-inline-end: 0.5em;
}

.peer-connection > button {
  margin-inline-start: 0;
}

.peer-connection table {
  width: 100%;
  text-align: center;
}

.peer-connection table th {
  font-weight: bold;
}

.peer-connection table th,
.peer-connection table td {
  padding: 0.4em;
  border: 1px solid var(--in-content-border-color);
}

.peer-connection table tr:nth-child(odd) {
  background-color: var(--in-content-box-background-odd);
}

.peer-connection table caption {
  text-align: start;
}

.peer-connection table.raw-candidate {
  text-align: match-parent;
}

.bottom-border td {
  border-bottom: 2px solid currentColor;
}

.peer-connection-config div {
  margin-inline: 1em;
  padding: 4px;
  border: 1px solid var(--in-content-border-color);
}

.peer-connection-config div:nth-child(odd) {
  background-color: var(--in-content-box-background-odd);
}

/* The pale colour scheme is taken from:
   https://personal.sron.nl/~pault/#sec:qualitative */
.ice-trickled {
  background-color: #cceeff; /* pale cyan */
}
.ice-succeeded {
  background-color: #ccddaa; /* pale green */
}
.ice-failed {
  background-color: #ffcccc; /* pale red */
}
.ice-cancelled {
  background-color: #eeeebb; /* pale yellow */
}
.ice-trickled,
.ice-succeeded,
.ice-failed,
.ice-cancelled {
  color: black
}

.info-label {
  font-weight: bold;
}

.info-body,
.stat-label {
  padding-inline-start: 0.5em;
}

.section-ctrl {
  margin: 1em 1.5em;
}

div.fold-trigger {
  color: var(--blue-60);
  cursor: pointer;
}

@media screen {
  .fold-closed {
    display: none !important;
  }
}

@media print {
  .no-print {
    display: none !important;
  }
}

.tab-pane {
  display: none;
}

.active-tab-pane {
  border: 1px solid var(--in-content-border-color);
  display: block;
}

.tab-button {
  color: var(--in-content-button-text-color);
}

.active-tab-button {
  color: var(--in-content-button-text-color-active);
  background: var(--in-content-button-background-active);
}

.sdp-history {
  display: flex;
  height: 400px;
}

.sdp-history-link {
  text-decoration: underline;
}

.sdp-history h5 {
  background-color: var(--in-content-box-info-background);
}

.sdp-history div {
  border: 1px solid var(--in-content-border-color);
  padding: 1em;
  width: 50%;
  overflow: scroll;
}

.line-graph {
  border: 1px solid var(--in-content-border-color);
  margin-inline: 1px;
  padding: 1px;
}

.svg-graph {
  border: 1px solid var(--in-content-border-color);
  margin-inline: 1px;
}

.copy-button-base {
  padding-inline-end: 0.25em;
  cursor: default;
}

.copy-button {
  visibility: hidden;
}

.prefList > li {
  list-style-type: none;
  margin: 0;
  padding: 0;
}

.pathDisplay {
  margin-inline-end: 1em;
}

.subsection-heading > h4 > span {
  margin-inline-end: 0.5em;
}

.prefList > li:hover > .copy-button,
.subsection-heading:hover > h4 > .copy-button {
  visibility: visible;
}

.copy-button-fade-out {
  opacity: 0;
  transition: opacity 0.5s;
}

.copy-button-fade-in {
  opacity: 1;
  transition: opacity 0.5s;
}
PK
!<���==:chrome/toolkit/content/global/aboutwebrtc/aboutWebrtc.html<!DOCTYPE html>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<html>
  <head>
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:; object-src 'none'"
    />
    <meta charset="utf-8" />
    <meta name="color-scheme" content="light dark" />
    <title data-l10n-id="about-webrtc-document-title"></title>
    <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
    <link
      rel="stylesheet"
      type="text/css"
      media="all"
      href="chrome://global/content/aboutwebrtc/aboutWebrtc.css"
    />
    <script
      src="chrome://global/content/aboutwebrtc/aboutWebrtc.mjs"
      defer="defer"
      type="module"
    ></script>
    <link rel="localization" href="toolkit/about/aboutWebrtc.ftl" />
  </head>
  <body id="body">
    <div id="content"></div>
    <div id="mediactx"></div>
    <div id="controls" class="no-print"></div>
  </body>
</html>
PK
!<稒�w�w�9chrome/toolkit/content/global/aboutwebrtc/aboutWebrtc.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { GraphImpl } from "chrome://global/content/aboutwebrtc/graph.mjs";
import { GraphDb } from "chrome://global/content/aboutwebrtc/graphdb.mjs";
import { Disclosure } from "chrome://global/content/aboutwebrtc/disclosure.mjs";
import { ConfigurationList } from "chrome://global/content/aboutwebrtc/configurationList.mjs";
import { CopyButton } from "./copyButton.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
});

function makeFilePickerService() {
  const fpContractID = "@mozilla.org/filepicker;1";
  const fpIID = Ci.nsIFilePicker;
  return Cc[fpContractID].createInstance(fpIID);
}

const WGI = WebrtcGlobalInformation;

const LOGFILE_NAME_DEFAULT = "aboutWebrtc.html";

class Renderer {
  // Long function names preserved until code can be uniformly moved to new names
  renderElement(eleName, options, l10n_id, l10n_args) {
    let elem = Object.assign(document.createElement(eleName), options);
    if (l10n_id) {
      document.l10n.setAttributes(elem, l10n_id, l10n_args);
    }
    return elem;
  }
  elem() {
    return this.renderElement(...arguments);
  }
  text(eleName, textContent, options) {
    return this.renderElement(eleName, { textContent, ...options });
  }
  renderElements(eleName, options, list) {
    const element = renderElement(eleName, options);
    element.append(...list);
    return element;
  }
  elems() {
    return this.renderElements(...arguments);
  }
}

// Proxies a Renderer instance to provide some meta programming methods to make
// adding elements more readable, e.g. elemRenderer.elem_h4(...) instead of
// elemRenderer.elem("h4", ...).
const elemRenderer = new Proxy(new Renderer(), {
  get(target, prop) {
    // Function prefixes to proxy.
    const proxied = {
      elem_: (...args) => target.elem(...args),
      elems_: (...args) => target.elems(...args),
      text_: (...args) => target.text(...args),
    };
    for (let [prefix, func] of Object.entries(proxied)) {
      if (prop.startsWith(prefix) && prop.length > prefix.length) {
        return (...args) => func(prop.substring(prefix.length), ...args);
      }
    }
    // Pass non-matches to the base object
    return Reflect.get(...arguments);
  },
});

let graphData = [];
let mostRecentReports = {};
let sdpHistories = [];
let historyTsMemoForPcid = {};
let sdpHistoryTsMemoForPcid = {};

function clearStatsHistory() {
  graphData = [];
  mostRecentReports = {};
  sdpHistories = [];
  historyTsMemoForPcid = {};
  sdpHistoryTsMemoForPcid = {};
}

function appendReportToHistory(report) {
  appendSdpHistory(report);
  mostRecentReports[report.pcid] = report;
  if (graphData[report.pcid] === undefined) {
    graphData[report.pcid] ??= new GraphDb(report);
  } else {
    graphData[report.pcid].insertReportData(report);
  }
}

function appendSdpHistory({ pcid, sdpHistory: newHistory }) {
  sdpHistories[pcid] ??= [];
  let storedHistory = sdpHistories[pcid];
  newHistory.forEach(entry => {
    const { timestamp } = entry;
    if (!storedHistory.length || storedHistory.at(-1).timestamp < timestamp) {
      storedHistory.push(entry);
      sdpHistoryTsMemoForPcid[pcid] = timestamp;
    }
  });
}

function recentStats() {
  return Object.values(mostRecentReports);
}

// Returns the sdpHistory for a given stats report
function getSdpHistory({ pcid, timestamp: a }) {
  sdpHistories[pcid] ??= [];
  return sdpHistories[pcid].filter(({ timestamp: b }) => a >= b);
}

function appendStats(allStats) {
  allStats.forEach(appendReportToHistory);
}

function getAndUpdateStatsTsMemoForPcid(pcid) {
  historyTsMemoForPcid[pcid] = mostRecentReports[pcid]?.timestamp;
  return historyTsMemoForPcid[pcid] || null;
}

function getSdpTsMemoForPcid(pcid) {
  return sdpHistoryTsMemoForPcid[pcid] || null;
}

const REQUEST_FULL_REFRESH = true;
const REQUEST_UPDATE_ONLY_REFRESH = false;

async function getStats(requestFullRefresh) {
  if (
    requestFullRefresh ||
    !Services.prefs.getBoolPref("media.aboutwebrtc.hist.enabled")
  ) {
    // Upon clearing the history we need to get all the stats to rebuild what
    // will become the skeleton of the page.hg wip
    const { reports } = await new Promise(r => WGI.getAllStats(r));
    appendStats(reports);
    return reports.sort((a, b) => b.timestamp - a.timestamp);
  }
  const pcids = await new Promise(r => WGI.getStatsHistoryPcIds(r));
  await Promise.all(
    [...pcids].map(pcid =>
      new Promise(r =>
        WGI.getStatsHistorySince(
          r,
          pcid,
          getAndUpdateStatsTsMemoForPcid(pcid),
          getSdpTsMemoForPcid(pcid)
        )
      ).then(r => {
        appendStats(r.reports);
        r.sdpHistories.forEach(hist => appendSdpHistory(hist));
      })
    )
  );
  let recent = recentStats();
  return recent.sort((a, b) => b.timestamp - a.timestamp);
}

const renderElement = (eleName, options, l10n_id, l10n_args) =>
  elemRenderer.elem(eleName, options, l10n_id, l10n_args);

const renderText = (eleName, textContent, options) =>
  elemRenderer.text(eleName, textContent, options);

const renderElements = (eleName, options, list) =>
  elemRenderer.elems(eleName, options, list);

// Button control classes

class Control {
  label = null;
  message = null;
  messageArgs = null;
  messageHeader = null;

  render() {
    this.ctrl = renderElement(
      "button",
      { onclick: () => this.onClick() },
      this.label
    );
    this.msg = renderElement("p");
    this.update();
    return [this.ctrl, this.msg];
  }

  update() {
    document.l10n.setAttributes(this.ctrl, this.label);
    this.msg.textContent = "";
    if (this.message) {
      this.msg.append(
        renderElement(
          "span",
          {
            className: "info-label",
          },
          this.messageHeader
        ),
        renderElement(
          "span",
          {
            className: "info-body",
          },
          this.message,
          this.messageArgs
        )
      );
    }
  }
}

class SavePage extends Control {
  constructor() {
    super();
    this.messageHeader = "about-webrtc-save-page-label";
    this.label = "about-webrtc-save-page-label";
  }

  async onClick() {
    FoldEffect.expandAll();
    let [dialogTitle] = await document.l10n.formatValues([
      { id: "about-webrtc-save-page-dialog-title" },
    ]);
    let FilePicker = makeFilePickerService();
    const lazyFileUtils = lazy.FileUtils;
    FilePicker.init(window.browsingContext, dialogTitle, FilePicker.modeSave);
    FilePicker.defaultString = LOGFILE_NAME_DEFAULT;
    const rv = await new Promise(r => FilePicker.open(r));
    if (rv != FilePicker.returnOK && rv != FilePicker.returnReplace) {
      return;
    }
    const fout = lazyFileUtils.openAtomicFileOutputStream(
      FilePicker.file,
      lazyFileUtils.MODE_WRONLY | lazyFileUtils.MODE_CREATE
    );
    const content = document.querySelector("#content");
    const noPrintList = [...content.querySelectorAll(".no-print")];
    for (const node of noPrintList) {
      node.style.setProperty("display", "none");
    }
    try {
      fout.write(content.outerHTML, content.outerHTML.length);
    } finally {
      lazyFileUtils.closeAtomicFileOutputStream(fout);
      for (const node of noPrintList) {
        node.style.removeProperty("display");
      }
    }
    this.message = "about-webrtc-save-page-complete-msg";
    this.messageArgs = { path: FilePicker.file.path };
    this.update();
  }
}

class EnableLogging extends Control {
  constructor() {
    super();
    this.label = "about-webrtc-enable-logging-label";
    this.message = null;
  }

  onClick() {
    this.update();
    window.open("about:logging?preset=webrtc");
  }
}

class AecLogging extends Control {
  constructor() {
    super();
    this.messageHeader = "about-webrtc-aec-logging-msg-label";

    if (WGI.aecDebug) {
      this.setState(true);
    } else {
      this.label = "about-webrtc-aec-logging-off-state-label";
      this.message = null;
    }
  }

  setState(state) {
    this.label = state
      ? "about-webrtc-aec-logging-on-state-label"
      : "about-webrtc-aec-logging-off-state-label";
    try {
      if (!state) {
        const file = WGI.aecDebugLogDir;
        this.message = "about-webrtc-aec-logging-toggled-off-state-msg";
        this.messageArgs = { path: file };
      } else {
        this.message = "about-webrtc-aec-logging-toggled-on-state-msg";
      }
    } catch (e) {
      this.message = null;
    }
  }

  onClick() {
    if (Services.env.get("MOZ_DISABLE_CONTENT_SANDBOX") != "1") {
      this.message = "about-webrtc-aec-logging-unavailable-sandbox";
    } else {
      this.setState((WGI.aecDebug = !WGI.aecDebug));
    }
    this.update();
  }
}

class ShowTab extends Control {
  constructor(browserId) {
    super();
    this.label = "about-webrtc-show-tab-label";
    this.message = null;
    this.browserId = browserId;
  }

  onClick() {
    const globalBrowser =
      window.ownerGlobal.browsingContext.topChromeWindow.gBrowser;
    for (const tab of globalBrowser.visibleTabs) {
      if (tab.linkedBrowser && tab.linkedBrowser.browserId == this.browserId) {
        globalBrowser.selectedTab = tab;
        return;
      }
    }
    this.ctrl.disabled = true;
  }
}

(async () => {
  // Setup. Retrieve reports & log while page loads.

  const primarySections = [];
  let peerConnections = renderElement("div");
  let connectionLog = renderElement("div");
  let userModifiedConfigView = renderElement("div");

  const content = document.querySelector("#content");
  content.append(peerConnections, connectionLog, userModifiedConfigView);
  await new Promise(r => (window.onload = r));
  {
    const ctrl = renderElement("div", { className: "control" });
    const msg = renderElement("div", { className: "message" });
    const add = ([control, message]) => {
      ctrl.appendChild(control);
      msg.appendChild(message);
    };
    add(new SavePage().render());
    add(new EnableLogging().render());
    add(new AecLogging().render());

    const ctrls = document.querySelector("#controls");
    ctrls.append(renderElements("div", { className: "controls" }, [ctrl, msg]));

    const mediactx = document.querySelector("#mediactx");
    const mediaCtxSection = await renderMediaCtx(elemRenderer);
    primarySections.push(mediaCtxSection);
    mediactx.append(mediaCtxSection.view());
  }

  // This does not handle the auto-refresh, only the manual refreshes needed
  // for certain user actions, and the initial population of the data
  async function refresh() {
    const pcSection = await renderPeerConnectionSection();
    primarySections.push(pcSection);
    const pcDiv = pcSection.view();
    const connectionLogSection = await renderConnectionLog();
    primarySections.push(connectionLogSection);
    const logDiv = connectionLogSection.view();

    // Replace previous info
    peerConnections.replaceWith(pcDiv);
    connectionLog.replaceWith(logDiv);
    const userModifiedConfigSection = await renderUserPrefSection();
    primarySections.push(userModifiedConfigSection);
    userModifiedConfigView.replaceWith(userModifiedConfigSection.view());
    peerConnections = pcDiv;
    connectionLog = logDiv;
  }
  refresh();

  const INTERVAL_MS = 250;
  const HALF_INTERVAL_MS = INTERVAL_MS / 2;
  // This handles autorefresh and forced refresh, not initial document loading
  async function autorefresh() {
    const startTime = performance.now();
    await Promise.all(primarySections.map(s => s.autoUpdate()));
    const elapsed = performance.now() - startTime;
    // Using half the refresh interval as
    const timeout =
      elapsed > HALF_INTERVAL_MS ? INTERVAL_MS : INTERVAL_MS - elapsed;
    return timeout;
  }
  let timeout = INTERVAL_MS;
  while (true) {
    timeout = await autorefresh();
    await new Promise(r => setTimeout(r, timeout));
  }
})();

const peerConnectionAutoRefreshState = {
  /** @type HTMLInputElement */
  primaryCheckbox: undefined,
  /** @type [HTMLInputElement] */
  secondaryCheckboxes: [],

  secondaryClicked() {
    const { checkedBoxes, uncheckedBoxes } = this.secondaryCheckboxes
      .filter(cb => !cb.hidden)
      .reduce(
        (sums, { checked }) => {
          if (checked) {
            sums.checkedBoxes += 1;
          } else {
            sums.uncheckedBoxes += 1;
          }
          return sums;
        },
        {
          checkedBoxes: 0,
          uncheckedBoxes: 0,
        }
      );
    // Stay checked unless all secondary boxes are unchecked
    this.primaryCheckbox.checked = checkedBoxes > 0;
    // Display an indeterminate state when there are both checked and unchecked boxes
    this.primaryCheckbox.indeterminate = checkedBoxes && uncheckedBoxes;
  },
  primaryClicked() {
    for (const cb of this.secondaryCheckboxes.filter(c => !c.hidden)) {
      cb.checked = this.primaryCheckbox.checked;
    }
    this.primaryCheckbox.indeterminate = false;
  },
};

function renderCopyTextToClipboardButton(rndr, id, l10n_id, getTextFn) {
  return rndr.elem_button(
    {
      id: `copytextbutton-${id}`,
      onclick() {
        navigator.clipboard.writeText(getTextFn());
      },
    },
    l10n_id
  );
}

async function renderPeerConnectionSection() {
  // Render pcs and log
  let reports = await getStats();
  let needsFullUpdate = REQUEST_UPDATE_ONLY_REFRESH;
  reports.sort((a, b) => a.browserId - b.browserId);

  // Used by the renderTransportStats function to calculate stat deltas
  const hist = {};

  // Adding a pcid to this list will cause the stats for that list to be refreshed
  // on the next update interval. This is useful for one time refreshes like the
  // "Refresh" button. The list is cleared at the end of each refresh interval.
  const forceRefreshList = [];

  const openPeerConnectionReports = reports.filter(r => !r.closed);
  const closedPeerConnectionReports = reports.filter(r => r.closed);
  const closedPCSection = document.createElement("div");
  if (closedPeerConnectionReports.length) {
    const closedPeerConnectionDisclosure = renderFoldableSection(
      closedPCSection,
      {
        showMsg: "about-webrtc-closed-peerconnection-disclosure-show-msg",
        hideMsg: "about-webrtc-closed-peerconnection-disclosure-hide-msg",
        startsCollapsed: [...openPeerConnectionReports].size,
      }
    );
    closedPCSection.append(closedPeerConnectionDisclosure);
    closedPeerConnectionDisclosure.append(
      ...closedPeerConnectionReports.map(r =>
        renderPeerConnection(r, () => forceRefreshList.push(r.pcid))
      )
    );
  }

  const primarySection = await PrimarySection.make({
    headingL10nId: "about-webrtc-peerconnections-section-heading",
    disclosureShowL10nId: "about-webrtc-peerconnections-section-show-msg",
    disclosureHideL10nId: "about-webrtc-peerconnections-section-hide-msg",
    autoRefreshPref: "media.aboutwebrtc.auto_refresh.peerconnection_section",
    renderFn: async () => {
      const body = document.createElement("div");
      body.append(
        ...openPeerConnectionReports.map(r =>
          renderPeerConnection(r, () => forceRefreshList.push(r.pcid))
        ),
        closedPCSection
      );
      return body;
    },
    // Creates the filling for the disclosure
    updateFn: async () => {
      let statsReports = await getStats(needsFullUpdate);
      needsFullUpdate = REQUEST_UPDATE_ONLY_REFRESH;

      async function translate(element) {
        const frag = document.createDocumentFragment();
        frag.append(element);
        await document.l10n.translateFragment(frag);
        return frag;
      }

      const translateSection = async (report, id, renderFunc) => {
        const element = document.getElementById(`${id}: ${report.pcid}`);
        const result =
          element && (await translate(renderFunc(elemRenderer, report, hist)));
        return { element, translated: result };
      };

      const sections = (
        await Promise.all(
          // Add filter to check the refreshEnabledPcids
          statsReports
            .filter(
              ({ pcid }) =>
                document.getElementById(`autorefresh-${pcid}`)?.checked ||
                forceRefreshList.includes(pcid)
            )
            .flatMap(report => [
              translateSection(
                report,
                "pc-heading",
                renderPeerConnectionHeading
              ),
              translateSection(report, "ice-stats", renderICEStats),
              translateSection(
                report,
                "ice-raw-stats-fold",
                renderRawICEStatsFold
              ),
              translateSection(report, "rtp-stats", renderRTPStats),
              translateSection(report, "sdp-stats", renderSDPStats),
              translateSection(report, "bandwidth-stats", renderBandwidthStats),
              translateSection(report, "frame-stats", renderFrameRateStats),
            ])
        )
      ).filter(({ element }) => element);
      document.l10n.pauseObserving();
      for (const { element, translated } of sections) {
        element.replaceWith(translated);
      }
      document.l10n.resumeObserving();
      while (forceRefreshList.length) {
        forceRefreshList.pop();
      }
    },
    // Updates the contents.
    headerElementsFn: async () => {
      const clearStatsButton = document.createElement("button");
      Object.assign(clearStatsButton, {
        className: "no-print",
        onclick: async () => {
          WGI.clearAllStats();
          clearStatsHistory();
          needsFullUpdate = REQUEST_FULL_REFRESH;
          primarySection.updateFn();
        },
      });
      document.l10n.setAttributes(clearStatsButton, "about-webrtc-stats-clear");
      return [clearStatsButton];
    },
  });
  peerConnectionAutoRefreshState.primaryCheckbox = primarySection.autorefresh;
  let originalOnChange = primarySection.autorefresh.onchange;
  primarySection.autorefresh.onchange = () => {
    originalOnChange();
    peerConnectionAutoRefreshState.primaryClicked();
  };
  return primarySection;
}

function renderSubsectionHeading(l10n_id, copyFunc) {
  const heading = document.createElement("div");
  heading.className = "subsection-heading";
  const h4 = document.createElement("h4");
  if (copyFunc != undefined) {
    const copyButton = new CopyButton(copyFunc);
    h4.appendChild(copyButton.element);
  }
  const text = document.createElement("span");
  document.l10n.setAttributes(text, l10n_id);
  h4.appendChild(text);
  heading.appendChild(h4);
  return heading;
}

function renderPeerConnection(report, forceRefreshFn) {
  const rndr = elemRenderer;
  const { pcid, configuration } = report;
  const pcStats = report.peerConnectionStats[0];

  const pcDiv = renderElement("div", { className: "peer-connection" });
  pcDiv.append(renderPeerConnectionTools(rndr, report, forceRefreshFn));
  {
    const section = renderFoldableSection(pcDiv);
    section.append(
      renderElements("div", {}, [
        renderElement(
          "span",
          {
            className: "info-label",
          },
          "about-webrtc-peerconnection-id-label"
        ),
        renderText("span", pcid, { className: "info-body" }),
        rndr.elems_p({}, [
          rndr.elem_span(
            { className: "info-label" },
            "about-webrtc-data-channels-opened-label"
          ),
          rndr.text_span(pcStats.dataChannelsOpened, {
            className: "info-body",
          }),
        ]),
        rndr.elems_p({}, [
          rndr.elem_span(
            { className: "info-label" },
            "about-webrtc-data-channels-closed-label"
          ),
          rndr.text_span(pcStats.dataChannelsClosed, {
            className: "info-body",
          }),
        ]),
        renderConfiguration(rndr, configuration),
      ]),
      renderRTPStats(rndr, report),
      renderICEStats(rndr, report),
      renderRawICEStats(rndr, report),
      renderSDPStats(rndr, report),
      renderBandwidthStats(rndr, report),
      renderFrameRateStats(rndr, report)
    );
    pcDiv.append(section);
  }
  return pcDiv;
}

function renderPeerConnectionMediaSummary(rndr, report) {
  // Takes a codecId value and returns a corresponding codec stats object
  const getCodecById = aId => report.codecStats.find(({ id }) => id == aId);

  // Find all the codecs used by send streams
  const sendCodecs = new Set(
    [...report.outboundRtpStreamStats]
      .filter(({ codecId }) => codecId)
      .map(({ codecId }) => getCodecById(codecId).mimeType)
      .sort()
  );

  // Find all the codecs used by receive streams
  const recvCodecs = new Set(
    [...report.inboundRtpStreamStats]
      .filter(({ codecId }) => codecId)
      .map(({ codecId }) => getCodecById(codecId).mimeType)
      .sort()
  );

  // Take all the codecs that appear in both the send and receive codec lists
  const sendRecvCodecs = new Set(
    [...sendCodecs, ...recvCodecs].filter(
      c => sendCodecs.has(c) && recvCodecs.has(c)
    )
  );

  // Remove the common codecs from the send and receive codec lists.
  // sendCodecs will now contain send only codecs
  // receiveCodecs will now contain receive only codecs
  sendRecvCodecs.forEach(c => {
    sendCodecs.delete(c);
    recvCodecs.delete(c);
  });

  const formatter = new Intl.ListFormat("en", {
    style: "short",
    type: "conjunction",
  });

  // Create a label with the codecs common to send and receive streams
  const sendRecvSpan = sendRecvCodecs.size
    ? [
        rndr.elem_span({}, "about-webrtc-short-send-receive-direction", {
          codecs: formatter.format(sendRecvCodecs),
        }),
      ]
    : [];

  // Do the same for send only codecs
  const sendSpan = sendCodecs.size
    ? [
        rndr.elem_span({}, "about-webrtc-short-send-direction", {
          codecs: formatter.format(sendCodecs),
        }),
      ]
    : [];

  // Do the same for receive only codecs
  const recvSpan = recvCodecs.size
    ? [
        rndr.elem_span({}, "about-webrtc-short-receive-direction", {
          codecs: formatter.format(recvCodecs),
        }),
      ]
    : [];

  return [...sendRecvSpan, ...sendSpan, ...recvSpan];
}

function renderPeerConnectionHeading(rndr, report) {
  const { pcid, timestamp, closed: isClosed, browserId } = report;
  const id = pcid.match(/id=(\S+)/)[1];
  const url = pcid.match(/url=([^)]+)/)[1];
  const now = new Date(timestamp);
  return isClosed
    ? rndr.elems_div(
        {
          id: `pc-heading: ${pcid}`,
          class: "pc-heading",
        },
        [
          rndr.elems_h3({}, [
            rndr.elem_span({}, "about-webrtc-connection-closed", {
              "browser-id": browserId,
              id,
              url,
              now,
            }),
            ...renderPeerConnectionMediaSummary(rndr, report),
          ]),
        ]
      )
    : rndr.elems_div(
        {
          id: `pc-heading: ${pcid}`,
          class: "pc-heading",
        },
        [
          rndr.elems_h3({}, [
            rndr.elem_span({}, "about-webrtc-connection-open", {
              "browser-id": browserId,
              id,
              url,
              now,
            }),
            ...renderPeerConnectionMediaSummary(rndr, report),
          ]),
        ]
      );
}

function renderPeerConnectionTools(rndr, report, forceRefreshFn) {
  const { pcid, browserId } = report;
  const id = pcid.match(/id=(\S+)/)[1];
  const copyHistButton = !Services.prefs.getBoolPref(
    "media.aboutwebrtc.hist.enabled"
  )
    ? []
    : [
        rndr.elem_button(
          {
            id: `copytextbutton-hist-${id}`,
            onclick() {
              WGI.getStatsHistorySince(
                hist =>
                  navigator.clipboard.writeText(JSON.stringify(hist, null, 2)),
                pcid
              );
            },
          },
          "about-webrtc-copy-report-history-button"
        ),
      ];
  const autorefreshButton = rndr.elem_input({
    id: `autorefresh-${pcid}`,
    className: "autorefresh",
    type: "checkbox",
    hidden: report.closed,
    checked: Services.prefs.getBoolPref(
      "media.aboutwebrtc.auto_refresh.peerconnection_section"
    ),
    onchange: () => peerConnectionAutoRefreshState.secondaryClicked(),
  });
  peerConnectionAutoRefreshState.secondaryCheckboxes.push(autorefreshButton);
  const forceRefreshButton = rndr.elem_button(
    {
      id: `force-refresh-pc-${id}`,
      onclick() {
        forceRefreshFn();
      },
    },
    "about-webrtc-force-refresh-button"
  );
  const autorefreshLabel = rndr.elem_label(
    {
      className: "autorefresh",
      hidden: autorefreshButton.hidden,
    },
    "about-webrtc-auto-refresh-label"
  );
  return renderElements("div", { id: "pc-tools: " + pcid }, [
    renderPeerConnectionHeading(rndr, report),
    new ShowTab(browserId).render()[0],
    renderCopyTextToClipboardButton(
      rndr,
      report.pcid,
      "about-webrtc-copy-report-button",
      () => JSON.stringify({ ...report }, null, 2)
    ),
    ...copyHistButton,
    forceRefreshButton,
    autorefreshButton,
    autorefreshLabel,
  ]);
}

const trimNewlines = sdp => sdp.replaceAll("\r\n", "\n");

const tabElementProps = (element, elemSubId, pcid) => ({
  className:
    elemSubId != "answer"
      ? `tab-${element}`
      : `tab-${element} active-tab-${element}`,
  id: `tab_${element}_${elemSubId}_${pcid}`,
});

const renderSDPTab = (rndr, sdp, props) =>
  rndr.elems("div", props, [rndr.text("pre", trimNewlines(sdp))]);

const renderSDPHistoryTab = (rndr, hist, props) => {
  // All SDP in sequential order. Add onclick handler to scroll the associated
  // SDP into view below.
  let first = Math.min(...hist.map(({ timestamp }) => timestamp));
  const parts = hist.map(({ isLocal, timestamp, sdp, errors: errs }) => {
    let errorsSubSect = () => [
      rndr.elem_h5({}, "about-webrtc-sdp-parsing-errors-heading"),
      ...errs.map(({ lineNumber: n, error: e }) => rndr.text_br(`${n}: ${e}`)),
    ];

    let sdpSection = [
      rndr.elem_h5({}, "about-webrtc-sdp-set-timestamp", {
        timestamp,
        "relative-timestamp": timestamp - first,
      }),
      ...(errs && errs.length ? errorsSubSect() : []),
      rndr.text_pre(trimNewlines(sdp)),
    ];

    return {
      link: rndr.elems_div({}, [
        rndr.elem_h5(
          {
            className: "sdp-history-link",
            onclick: () => sdpSection[0].scrollIntoView(),
          },
          isLocal
            ? "about-webrtc-sdp-set-at-timestamp-local"
            : "about-webrtc-sdp-set-at-timestamp-remote",
          { timestamp }
        ),
      ]),
      ...(isLocal ? { local: sdpSection } : { remote: sdpSection }),
    };
  });

  return rndr.elems_div(props, [
    // Render the links
    rndr.elems_div(
      {},
      parts.map(({ link }) => link)
    ),
    rndr.elems_div({ className: "sdp-history" }, [
      // Render the SDP into separate columns for local and remote.
      rndr.elems_div({}, [
        rndr.elem_h4({}, "about-webrtc-local-sdp-heading"),
        ...parts.filter(({ local }) => local).flatMap(({ local }) => local),
      ]),
      rndr.elems_div({}, [
        rndr.elem_h4({}, "about-webrtc-remote-sdp-heading"),
        ...parts.filter(({ remote }) => remote).flatMap(({ remote }) => remote),
      ]),
    ]),
  ]);
};

function renderSDPStats(rndr, { offerer, pcid, timestamp }) {
  // Get the most recent (as of timestamp) local and remote SDPs from the
  // history
  const sdpEntries = getSdpHistory({ pcid, timestamp });
  const localSdp = sdpEntries.findLast(({ isLocal }) => isLocal)?.sdp || "";
  const remoteSdp = sdpEntries.findLast(({ isLocal }) => !isLocal)?.sdp || "";

  const sdps = offerer
    ? { offer: localSdp, answer: remoteSdp }
    : { offer: remoteSdp, answer: localSdp };

  const sdpLabels = offerer
    ? { offer: "local", answer: "remote" }
    : { offer: "remote", answer: "local" };

  sdpLabels.l10n = {
    offer: offerer
      ? "about-webrtc-local-sdp-heading-offer"
      : "about-webrtc-remote-sdp-heading-offer",
    answer: offerer
      ? "about-webrtc-remote-sdp-heading-answer"
      : "about-webrtc-local-sdp-heading-answer",
    history: "about-webrtc-sdp-history-heading",
  };

  const tabPaneProps = elemSubId => tabElementProps("pane", elemSubId, pcid);

  const panes = {
    answer: renderSDPTab(rndr, sdps.answer, tabPaneProps("answer")),
    offer: renderSDPTab(rndr, sdps.offer, tabPaneProps("offer")),
    history: renderSDPHistoryTab(
      rndr,
      getSdpHistory({ pcid, timestamp }),
      tabPaneProps("history")
    ),
  };

  // Creates the properties and l10n label for tab buttons
  const tabButtonProps = (elemSubId, pane) => [
    {
      ...tabElementProps("button", elemSubId, pcid),
      onclick({ currentTarget: t }) {
        const flipPane = c => c.classList.toggle("active-tab-pane", c == pane);
        Object.values(panes).forEach(flipPane);
        const selButton = c => c.classList.toggle("active-tab-button", c == t);
        [...t.parentElement.children].forEach(selButton);
      },
    },
    sdpLabels.l10n[elemSubId],
  ];

  const sdpDiv = renderSubsectionHeading("about-webrtc-sdp-heading", () =>
    JSON.stringify(
      {
        offer: {
          side: sdpLabels.offer,
          sdp: sdps.offer.split("\r\n"),
        },
        answer: {
          side: sdpLabels.answer,
          sdp: sdps.answer.split("\r\n"),
        },
      },
      null,
      2
    )
  );
  const outer = document.createElement("div", { id: "sdp-stats" + pcid });
  outer.appendChild(sdpDiv);
  let foldSection = renderFoldableSection(outer, {
    showMsg: "about-webrtc-show-msg-sdp",
    hideMsg: "about-webrtc-hide-msg-sdp",
  });
  foldSection.append(
    rndr.elems_div({ className: "tab-buttons" }, [
      ...Object.entries(panes).map(([elemSubId, pane]) =>
        rndr.elem_button(...tabButtonProps(elemSubId, pane))
      ),
      ...Object.values(panes),
    ])
  );
  outer.append(foldSection);
  return outer;
}

function renderBandwidthStats(rndr, report) {
  const statsDiv = renderElement("div", {
    id: "bandwidth-stats: " + report.pcid,
  });
  const table = renderSimpleTable(
    "",
    [
      "about-webrtc-track-identifier",
      "about-webrtc-send-bandwidth-bytes-sec",
      "about-webrtc-receive-bandwidth-bytes-sec",
      "about-webrtc-max-padding-bytes-sec",
      "about-webrtc-pacer-delay-ms",
      "about-webrtc-round-trip-time-ms",
    ],
    report.bandwidthEstimations.map(stat => [
      stat.trackIdentifier,
      stat.sendBandwidthBps,
      stat.receiveBandwidthBps,
      stat.maxPaddingBps,
      stat.pacerDelayMs,
      stat.rttMs,
    ])
  );
  statsDiv.append(
    renderElement("h4", {}, "about-webrtc-bandwidth-stats-heading"),
    table
  );
  return statsDiv;
}

function renderFrameRateStats(rndr, report) {
  const statsDiv = renderElement("div", { id: "frame-stats: " + report.pcid });
  report.videoFrameHistories.forEach(hist => {
    const stats = hist.entries.map(stat => {
      stat.elapsed = stat.lastFrameTimestamp - stat.firstFrameTimestamp;
      if (stat.elapsed < 1) {
        stat.elapsed = "0.00";
      }
      stat.elapsed = (stat.elapsed / 1_000).toFixed(3);
      if (stat.elapsed && stat.consecutiveFrames) {
        stat.avgFramerate = (stat.consecutiveFrames / stat.elapsed).toFixed(2);
      } else {
        stat.avgFramerate = "0.00";
      }
      return stat;
    });

    const table = renderSimpleTable(
      "",
      [
        "about-webrtc-width-px",
        "about-webrtc-height-px",
        "about-webrtc-consecutive-frames",
        "about-webrtc-time-elapsed",
        "about-webrtc-estimated-framerate",
        "about-webrtc-rotation-degrees",
        "about-webrtc-first-frame-timestamp",
        "about-webrtc-last-frame-timestamp",
        "about-webrtc-local-receive-ssrc",
        "about-webrtc-remote-send-ssrc",
      ],
      stats.map(stat =>
        [
          stat.width,
          stat.height,
          stat.consecutiveFrames,
          stat.elapsed,
          stat.avgFramerate,
          stat.rotationAngle,
          stat.firstFrameTimestamp,
          stat.lastFrameTimestamp,
          stat.localSsrc,
          stat.remoteSsrc || "?",
        ].map(entry => (Object.is(entry, undefined) ? "<<undefined>>" : entry))
      )
    );

    statsDiv.append(
      renderElement("h4", {}, "about-webrtc-frame-stats-heading", {
        "track-identifier": hist.trackIdentifier,
      }),
      table
    );
  });

  return statsDiv;
}

function renderRTPStats(rndr, report, hist) {
  const rtpStats = [
    ...(report.inboundRtpStreamStats || []),
    ...(report.outboundRtpStreamStats || []),
  ];
  const remoteRtpStats = [
    ...(report.remoteInboundRtpStreamStats || []),
    ...(report.remoteOutboundRtpStreamStats || []),
  ];

  // Generate an id-to-streamStat index for each remote streamStat. This will
  // be used next to link the remote to its local side.
  const remoteRtpStatsMap = {};
  for (const stat of remoteRtpStats) {
    remoteRtpStatsMap[stat.id] = stat;
  }

  // If a streamStat has a remoteId attribute, create a remoteRtpStats
  // attribute that references the remote streamStat entry directly.
  // That is, the index generated above is merged into the returned list.
  for (const stat of rtpStats.filter(s => "remoteId" in s)) {
    stat.remoteRtpStats = remoteRtpStatsMap[stat.remoteId];
  }
  for (const stat of rtpStats.filter(s => "codecId" in s)) {
    stat.codecStat = report.codecStats.find(({ id }) => id == stat.codecId);
  }
  const graphsByStat = stat =>
    (graphData[report.pcid]?.getGraphDataById(stat.id) || []).map(gd => {
      // For some (remote) graphs data comes in slowly.
      // Those graphs can be larger to show trends.
      const histSecs = gd.getConfig().histSecs;
      const width = (histSecs > 30 ? histSecs / 3 : 15) * 20;
      const height = 100;
      const graph = new GraphImpl(width, height);
      graph.startTime = () => stat.timestamp - histSecs * 1000;
      graph.stopTime = () => stat.timestamp;
      if (gd.subKey == "packetsLost") {
        const oldMaxColor = graph.maxColor;
        graph.maxColor = data => (data.value == 0 ? "red" : oldMaxColor(data));
      }
      // Get a bit more history for averages (20%)
      const dataSet = gd.getDataSetSince(
        graph.startTime() - histSecs * 0.2 * 1000
      );
      return graph.drawSparseValues(dataSet, gd.subKey, gd.getConfig());
    });
  // Render stats set
  return renderElements(
    "div",
    { id: "rtp-stats: " + report.pcid, className: "rtp-stats" },
    [
      renderSubsectionHeading("about-webrtc-rtp-stats-heading", () =>
        JSON.stringify([...rtpStats, ...remoteRtpStats], null, 2)
      ),
      ...rtpStats.map(stat => {
        const { ssrc, remoteId, remoteRtpStats: rtcpStats } = stat;
        const remoteGraphs = rtcpStats
          ? [
              rndr.elems_div({}, [
                rndr.text_h6(rtcpStats.type),
                ...graphsByStat(rtcpStats),
              ]),
            ]
          : [];
        const mime = stat?.codecStat?.mimeType?.concat(" - ") || "";
        const div = renderElements("div", {}, [
          rndr.text_h5(`${mime}SSRC ${ssrc}`),
          rndr.elems_div({}, [rndr.text_h6(stat.type), ...graphsByStat(stat)]),
          ...remoteGraphs,
          renderCodecStats(stat),
          renderTransportStats(stat, true, hist),
        ]);
        if (remoteId && rtcpStats) {
          div.append(renderTransportStats(rtcpStats, false));
        }
        return div;
      }),
    ]
  );
}

function renderCodecStats({
  codecStat,
  framesEncoded,
  framesDecoded,
  framesDropped,
  discardedPackets,
  packetsReceived,
}) {
  let elements = [];

  if (codecStat) {
    elements.push(
      renderText("span", `${codecStat.payloadType} ${codecStat.mimeType}`, {})
    );
    if (framesEncoded !== undefined || framesDecoded !== undefined) {
      elements.push(
        renderElement(
          "span",
          { className: "stat-label" },
          "about-webrtc-frames",
          {
            frames: framesEncoded || framesDecoded || 0,
          }
        )
      );
    }
    if (codecStat.channels !== undefined) {
      elements.push(
        renderElement(
          "span",
          { className: "stat-label" },
          "about-webrtc-channels",
          {
            channels: codecStat.channels,
          }
        )
      );
    }
    elements.push(
      renderText(
        "span",
        ` ${codecStat.clockRate} ${codecStat.sdpFmtpLine || ""}`,
        {}
      )
    );
  }
  if (framesDropped !== undefined) {
    elements.push(
      renderElement(
        "span",
        { className: "stat-label" },
        "about-webrtc-dropped-frames-label"
      )
    );
    elements.push(renderText("span", ` ${framesDropped}`, {}));
  }
  if (discardedPackets !== undefined) {
    elements.push(
      renderElement(
        "span",
        { className: "stat-label" },
        "about-webrtc-discarded-packets-label"
      )
    );
    elements.push(renderText("span", ` ${discardedPackets}`, {}));
  }
  if (elements.length) {
    if (packetsReceived !== undefined) {
      elements.unshift(
        renderElement("span", {}, "about-webrtc-decoder-label"),
        renderText("span", ": ")
      );
    } else {
      elements.unshift(
        renderElement("span", {}, "about-webrtc-encoder-label"),
        renderText("span", ": ")
      );
    }
  }
  return renderElements("div", {}, elements);
}

function renderTransportStats(
  {
    id,
    timestamp,
    type,
    packetsReceived,
    bytesReceived,
    packetsLost,
    jitter,
    roundTripTime,
    packetsSent,
    bytesSent,
  },
  local,
  hist
) {
  if (hist) {
    if (hist[id] === undefined) {
      hist[id] = {};
    }
  }

  const estimateKBps = (curTimestamp, lastTimestamp, bytes, lastBytes) => {
    if (!curTimestamp || !lastTimestamp || !bytes || !lastBytes) {
      return "0.0";
    }
    const elapsedTime = curTimestamp - lastTimestamp;
    if (elapsedTime <= 0) {
      return "0.0";
    }
    return ((bytes - lastBytes) / elapsedTime).toFixed(1);
  };

  let elements = [];

  if (local) {
    elements.push(
      renderElement("span", {}, "about-webrtc-type-local"),
      renderText("span", ": ")
    );
  } else {
    elements.push(
      renderElement("span", {}, "about-webrtc-type-remote"),
      renderText("span", ": ")
    );
  }

  const time = new Date(timestamp).toTimeString();
  elements.push(renderText("span", `${time} ${type}`));

  if (packetsReceived) {
    elements.push(
      renderElement(
        "span",
        { className: "stat-label" },
        "about-webrtc-received-label",
        {
          packets: packetsReceived,
        }
      )
    );

    if (bytesReceived) {
      let s = ` (${(bytesReceived / 1024).toFixed(2)} Kb`;
      if (local && hist) {
        s += ` , ${estimateKBps(
          timestamp,
          hist[id].lastTimestamp,
          bytesReceived,
          hist[id].lastBytesReceived
        )} KBps`;
      }
      s += ")";
      elements.push(renderText("span", s));
    }

    elements.push(
      renderElement(
        "span",
        { className: "stat-label" },
        "about-webrtc-lost-label",
        {
          packets: packetsLost,
        }
      )
    );
    elements.push(
      renderElement(
        "span",
        { className: "stat-label" },
        "about-webrtc-jitter-label",
        {
          jitter,
        }
      )
    );

    if (roundTripTime !== undefined) {
      elements.push(renderText("span", ` RTT: ${roundTripTime * 1000} ms`));
    }
  } else if (packetsSent) {
    elements.push(
      renderElement(
        "span",
        { className: "stat-label" },
        "about-webrtc-sent-label",
        {
          packets: packetsSent,
        }
      )
    );
    if (bytesSent) {
      let s = ` (${(bytesSent / 1024).toFixed(2)} Kb`;
      if (local && hist) {
        s += `, ${estimateKBps(
          timestamp,
          hist[id].lastTimestamp,
          bytesSent,
          hist[id].lastBytesSent
        )} KBps`;
      }
      s += ")";
      elements.push(renderText("span", s));
    }
  }

  // Update history
  if (hist) {
    hist[id].lastBytesReceived = bytesReceived;
    hist[id].lastBytesSent = bytesSent;
    hist[id].lastTimestamp = timestamp;
  }

  return renderElements("div", {}, elements);
}

function renderRawIceTable(caption, candidates) {
  const table = renderSimpleTable(
    "",
    [caption],
    [...new Set(candidates.sort())].filter(i => i).map(i => [i])
  );
  table.className = "raw-candidate";
  return table;
}

function renderConfiguration(rndr, c) {
  const provided = "about-webrtc-configuration-element-provided";
  const notProvided = "about-webrtc-configuration-element-not-provided";

  // Create the text for a configuration field
  const cfg = (obj, key) => [
    renderElement("br"),
    `${key}: `,
    key in obj ? obj[key] : renderElement("i", {}, notProvided),
  ];

  // Create the text for a fooProvided configuration field
  const pro = (obj, key) => [
    renderElement("br"),
    `${key}(`,
    renderElement("i", {}, provided),
    `/`,
    renderElement("i", {}, notProvided),
    `): `,
    renderElement("i", {}, obj[`${key}Provided`] ? provided : notProvided),
  ];

  const confDiv = rndr.elem_div({ display: "contents" });
  let disclosure = renderFoldableSection(confDiv, {
    showMsg: "about-webrtc-pc-configuration-show-msg",
    hideMsg: "about-webrtc-pc-configuration-hide-msg",
  });
  disclosure.append(
    rndr.elems_div({ classList: "peer-connection-config" }, [
      "RTCConfiguration",
      ...cfg(c, "bundlePolicy"),
      ...cfg(c, "iceTransportPolicy"),
      ...pro(c, "peerIdentity"),
      ...cfg(c, "sdpSemantics"),
      renderElement("br"),
      "iceServers: ",
      ...(!c.iceServers
        ? [renderElement("i", {}, notProvided)]
        : c.iceServers.map(i =>
            renderElements("div", {}, [
              `urls: ${JSON.stringify(i.urls)}`,
              ...pro(i, "credential"),
              ...pro(i, "userName"),
            ])
          )),
    ])
  );
  confDiv.append(disclosure);
  return confDiv;
}

function renderICEStats(rndr, report) {
  const iceDiv = renderElements("div", { id: "ice-stats: " + report.pcid }, [
    renderSubsectionHeading("about-webrtc-ice-stats-heading", () =>
      JSON.stringify(
        [...report.iceCandidateStats, ...report.iceCandidatePairStats],
        null,
        2
      )
    ),
  ]);

  // Render ICECandidate table
  {
    const caption = renderElement(
      "caption",
      { className: "no-print" },
      "about-webrtc-trickle-caption-msg"
    );

    // Generate ICE stats
    const stats = [];
    {
      // Create an index based on candidate ID for each element in the
      // iceCandidateStats array.
      const candidates = {};
      for (const candidate of report.iceCandidateStats) {
        candidates[candidate.id] = candidate;
      }

      // a method to see if a given candidate id is in the array of tickled
      // candidates.
      const isTrickled = candidateId =>
        report.trickledIceCandidateStats.some(({ id }) => id == candidateId);

      // A component may have a remote or local candidate address or both.
      // Combine those with both; these will be the peer candidates.
      const matched = {};

      for (const {
        localCandidateId,
        remoteCandidateId,
        componentId,
        state,
        priority,
        nominated,
        selected,
        bytesSent,
        bytesReceived,
      } of report.iceCandidatePairStats) {
        const local = candidates[localCandidateId];
        if (local) {
          const stat = {
            ["local-candidate"]: candidateToString(local),
            componentId,
            state,
            priority,
            nominated,
            selected,
            bytesSent,
            bytesReceived,
          };
          matched[local.id] = true;
          if (isTrickled(local.id)) {
            stat["local-trickled"] = true;
          }

          const remote = candidates[remoteCandidateId];
          if (remote) {
            stat["remote-candidate"] = candidateToString(remote);
            matched[remote.id] = true;
            if (isTrickled(remote.id)) {
              stat["remote-trickled"] = true;
            }
          }
          stats.push(stat);
        }
      }

      // sort (group by) componentId first, then bytesSent if available, else by
      // priority
      stats.sort((a, b) => {
        if (a.componentId != b.componentId) {
          return a.componentId - b.componentId;
        }
        return b.bytesSent
          ? b.bytesSent - (a.bytesSent || 0)
          : (b.priority || 0) - (a.priority || 0);
      });
    }
    // Render ICE stats
    // don't use |stat.x || ""| here because it hides 0 values
    const statsTable = renderSimpleTable(
      caption,
      [
        "about-webrtc-ice-state",
        "about-webrtc-nominated",
        "about-webrtc-selected",
        "about-webrtc-local-candidate",
        "about-webrtc-remote-candidate",
        "about-webrtc-ice-component-id",
        "about-webrtc-priority",
        "about-webrtc-ice-pair-bytes-sent",
        "about-webrtc-ice-pair-bytes-received",
      ],
      stats.map(stat =>
        [
          stat.state,
          stat.nominated,
          stat.selected,
          stat["local-candidate"],
          stat["remote-candidate"],
          stat.componentId,
          stat.priority,
          stat.bytesSent,
          stat.bytesReceived,
        ].map(entry => (Object.is(entry, undefined) ? "" : entry))
      )
    );

    // after rendering the table, we need to change the class name for each
    // candidate pair's local or remote candidate if it was trickled.
    let index = 0;
    for (const {
      state,
      nominated,
      selected,
      "local-trickled": localTrickled,
      "remote-trickled": remoteTrickled,
    } of stats) {
      // look at statsTable row index + 1 to skip column headers
      const { cells } = statsTable.rows[++index];
      cells[0].className = `ice-${state}`;
      if (nominated) {
        cells[1].className = "ice-succeeded";
      }
      if (selected) {
        cells[2].className = "ice-succeeded";
      }
      if (localTrickled) {
        cells[3].className = "ice-trickled";
      }
      if (remoteTrickled) {
        cells[4].className = "ice-trickled";
      }
    }

    // if the current row's component id changes, mark the bottom of the
    // previous row with a thin, black border to differentiate the
    // component id grouping.
    let previousRow;
    for (const row of statsTable.rows) {
      if (previousRow) {
        if (previousRow.cells[5].innerHTML != row.cells[5].innerHTML) {
          previousRow.className = "bottom-border";
        }
      }
      previousRow = row;
    }
    iceDiv.append(statsTable);
  }
  // restart/rollback counts.
  iceDiv.append(
    renderIceMetric("about-webrtc-ice-restart-count-label", report.iceRestarts),
    renderIceMetric(
      "about-webrtc-ice-rollback-count-label",
      report.iceRollbacks
    )
  );
  return iceDiv;
}

function renderRawICEStats(rndr, report) {
  const iceDiv = renderElements("div", { id: "ice-stats: " + report.pcid }, [
    renderSubsectionHeading("about-webrtc-raw-candidates-heading", () =>
      JSON.stringify(
        [...report.rawLocalCandidates, ...report.rawRemoteCandidates],
        null,
        2
      )
    ),
  ]);
  // Render raw ICECandidate section
  {
    const foldSection = renderFoldableSection(iceDiv, {
      showMsg: "about-webrtc-raw-cand-section-show-msg",
      hideMsg: "about-webrtc-raw-cand-section-hide-msg",
    });

    // render raw candidates
    foldSection.append(renderRawICEStatsFold(rndr, report));
    iceDiv.append(foldSection);
  }
  return iceDiv;
}

function renderRawICEStatsFold(rndr, report) {
  return renderElements("div", { id: "ice-raw-stats-fold: " + report.pcid }, [
    renderRawIceTable(
      "about-webrtc-raw-local-candidate",
      report.rawLocalCandidates
    ),
    renderRawIceTable(
      "about-webrtc-raw-remote-candidate",
      report.rawRemoteCandidates
    ),
  ]);
}

function renderIceMetric(label, value) {
  return renderElements("div", {}, [
    renderElement("span", { className: "info-label" }, label),
    renderText("span", value, { className: "info-body" }),
  ]);
}

function candidateToString({
  type,
  address,
  port,
  protocol,
  candidateType,
  relayProtocol,
  proxied,
} = {}) {
  if (!type) {
    return "*";
  }
  if (relayProtocol) {
    candidateType = `${candidateType}-${relayProtocol}`;
  }
  proxied = type == "local-candidate" ? ` [${proxied}]` : "";
  return `${address}:${port}/${protocol}(${candidateType})${proxied}`;
}

async function renderConnectionLog() {
  const getLog = () => new Promise(r => WGI.getLogging("", r));
  const logView = document.createElement("div");
  const displayLogs = logLines => {
    logView.replaceChildren();
    logView.append(
      ...logLines.map(line => {
        const e = document.createElement("p");
        e.textContent = line;
        return e;
      })
    );
  };
  const clearLogsButton = document.createElement("button");

  Object.assign(clearLogsButton, {
    className: "no-print",
    onclick: async () => {
      await WGI.clearLogging();
      displayLogs(await getLog());
    },
  });
  document.l10n.setAttributes(clearLogsButton, "about-webrtc-log-clear");
  return PrimarySection.make({
    headingL10nId: "about-webrtc-log-heading",
    disclosureShowL10nId: "about-webrtc-log-section-show-msg",
    disclosureHideL10nId: "about-webrtc-log-section-hide-msg",
    autoRefreshPref: "media.aboutwebrtc.auto_refresh.connection_log_section",
    renderFn: async () => {
      displayLogs(await getLog());
      return logView;
    },
    updateFn: async () => {
      displayLogs(await getLog());
    },
    headerElementsFn: async () => [clearLogsButton],
  });
}

const PREFERENCES = {
  branches: [
    "media.aboutwebrtc",
    "media.peerconnection",
    "media.navigator",
    "media.getusermedia",
    "media.gmp-gmpopenh264.enabled",
  ],
  hidden: [
    "media.aboutwebrtc.auto_refresh.peerconnection_section",
    "media.aboutwebrtc.auto_refresh.connection_log_section",
    "media.aboutwebrtc.auto_refresh.user_modified_config_section",
    "media.aboutwebrtc.auto_refresh.media_ctx_section",
  ],
};

async function renderUserPrefSection() {
  const getConfigPaths = () => {
    return PREFERENCES.branches
      .flatMap(Services.prefs.getChildList)
      .filter(Services.prefs.prefHasUserValue)
      .filter(p => !PREFERENCES.hidden.includes(p));
  };
  const prefList = new ConfigurationList(getConfigPaths());
  return PrimarySection.make({
    headingL10nId: "about-webrtc-user-modified-configuration-heading",
    disclosureShowL10nId: "about-webrtc-user-modified-configuration-show-msg",
    disclosureHideL10nId: "about-webrtc-user-modified-configuration-hide-msg",
    autoRefreshPref:
      "media.aboutwebrtc.auto_refresh.user_modified_config_section",
    renderFn: () => prefList.view(),
    updateFn: () => {
      prefList.setPrefPaths(getConfigPaths());
      prefList.update();
    },
  });
}

function renderFoldableSection(parentElem, options = {}) {
  const section = renderElement("div");
  if (parentElem) {
    const ctrl = renderElements("div", { className: "section-ctrl no-print" }, [
      new FoldEffect(section, options).render(),
    ]);
    parentElem.append(ctrl);
  }
  return section;
}

function renderSimpleTable(caption, headings, data) {
  const heads = headings.map(text => renderElement("th", {}, text));
  const renderCell = text => renderText("td", text);

  return renderElements("table", {}, [
    caption,
    renderElements("tr", {}, heads),
    ...data.map(line => renderElements("tr", {}, line.map(renderCell))),
  ]);
}

class FoldEffect {
  constructor(
    target,
    {
      showMsg = "about-webrtc-fold-default-show-msg",
      hideMsg = "about-webrtc-fold-default-hide-msg",
      startsCollapsed = true,
    } = {}
  ) {
    Object.assign(this, { target, showMsg, hideMsg, startsCollapsed });
  }

  render() {
    this.target.classList.add("fold-target");
    this.trigger = renderElement("div", { className: "fold-trigger" });
    this.trigger.classList.add("heading-medium", this.showMsg, this.hideMsg);
    if (this.startsCollapsed) {
      this.collapse();
    }
    this.trigger.onclick = () => {
      if (this.target.classList.contains("fold-closed")) {
        this.expand();
      } else {
        this.collapse();
      }
    };
    return this.trigger;
  }

  expand() {
    this.target.classList.remove("fold-closed");
    document.l10n.setAttributes(this.trigger, this.hideMsg);
  }

  collapse() {
    this.target.classList.add("fold-closed");
    document.l10n.setAttributes(this.trigger, this.showMsg);
  }

  static expandAll() {
    for (const target of document.getElementsByClassName("fold-closed")) {
      target.classList.remove("fold-closed");
    }
    for (const trigger of document.getElementsByClassName("fold-trigger")) {
      const hideMsg = trigger.classList[2];
      document.l10n.setAttributes(trigger, hideMsg);
    }
  }

  static collapseAll() {
    for (const target of document.getElementsByClassName("fold-target")) {
      target.classList.add("fold-closed");
    }
    for (const trigger of document.getElementsByClassName("fold-trigger")) {
      const showMsg = trigger.classList[1];
      document.l10n.setAttributes(trigger, showMsg);
    }
  }
}

class PrimarySection {
  /** @returns {Promise<PrimarySection>} */
  static async make({
    headingL10nId,
    disclosureShowL10nId,
    disclosureHideL10nId,
    autoRefreshPref,
    renderFn = async () => {}, // Creates the filling for the disclosure
    updateFn = async () => {}, // Updates the contents.
    headerElementsFn = async () => [], // Accessory elements for the heading
  }) {
    const newSect = new PrimarySection();
    Object.assign(newSect, {
      autoRefreshPref,
      renderFn,
      updateFn,
      headerElementsFn,
    });

    // Top level of the section
    const sectionContainer = document.createElement("div");
    // Section heading is always visible and contains the disclosure control,
    // the section title, the autorefresh button, and any accessory elements.
    const sectionHeading = document.createElement("div");
    sectionHeading.className = "section-heading";
    sectionContainer.appendChild(sectionHeading);
    // The section body is the portion that contains the disclosure body
    // container.
    const sectionBody = document.createElement("div");
    sectionBody.className = "section-body";
    sectionContainer.appendChild(sectionBody);

    const disclosure = new Disclosure({
      showMsg: disclosureShowL10nId,
      hideMsg: disclosureHideL10nId,
    });
    sectionHeading.appendChild(disclosure.control());

    const heading = document.createElement("h3");
    document.l10n.setAttributes(heading, headingL10nId);
    sectionHeading.append(heading);

    const autorefresh = document.createElement("input");
    Object.assign(autorefresh, {
      type: "checkbox",
      class: "autorefresh",
      id: autoRefreshPref,
      checked: Services.prefs.getBoolPref(autoRefreshPref),
      onchange: () =>
        Services.prefs.setBoolPref(autoRefreshPref, autorefresh.checked),
    });
    newSect.autorefresh = autorefresh;
    newSect.autorefreshPrefState = newSect.autorefresh.checked;
    const autorefreshLabel = document.createElement("label");
    autorefreshLabel.className = "autorefresh";
    autorefreshLabel.htmlFor = autorefresh.id;
    document.l10n.setAttributes(
      autorefreshLabel,
      "about-webrtc-auto-refresh-label"
    );
    sectionHeading.append(autorefresh, autorefreshLabel);

    let rendered = await renderFn();
    if (rendered) {
      disclosure.view().appendChild(rendered);
    }
    sectionBody.append(disclosure.view());

    let headerElements = (await newSect.headerElementsFn(newSect)) || [];
    sectionHeading.append(...headerElements);

    newSect.section = sectionContainer;
    return newSect;
  }
  view() {
    return this.section;
  }
  async update() {
    return this.updateFn(this);
  }
  async autoUpdate() {
    let prefState = Services.prefs.getBoolPref(this.autoRefreshPref);
    if (prefState != this.autorefreshPrefState) {
      this.autorefreshPrefState = prefState;
      this.autorefresh.checked = prefState;
    }
    if (this.autorefresh.checked || this.autorefresh.indeterminate) {
      return this.updateFn(this);
    }
    return null;
  }
}

async function renderMediaCtx(rndr) {
  const ctx = WGI.getMediaContext();
  const prefs = [
    "media.peerconnection.video.vp9_enabled",
    "media.peerconnection.video.vp9_preferred",
    "media.navigator.video.h264.level",
    "media.navigator.video.h264.max_mbps",
    "media.navigator.video.h264.max_mbps",
    "media.navigator.video.max_fs",
    "media.navigator.video.max_fr",
    "media.navigator.video.use_tmmbr",
    "media.navigator.video.use_remb",
    "media.navigator.video.use_transport_cc",
    "media.navigator.audio.use_fec",
    "media.navigator.video.red_ulpfec_enabled",
  ];

  const confList = new ConfigurationList(prefs);
  const hasH264Hardware = rndr.text_p(
    `hasH264Hardware: ${ctx.hasH264Hardware}`
  );
  hasH264Hardware.dataset.value = ctx.hasH264Hardware;
  const renderFn = async () =>
    rndr.elems_div({}, [hasH264Hardware, rndr.elem_hr(), confList.view()]);
  const updateFn = async () => {
    const newCtx = WGI.getMediaContext();
    if (hasH264Hardware.dataset.value != newCtx.hasH264Hardware) {
      hasH264Hardware.dataset.value = newCtx.hasH264Hardware;
      hasH264Hardware.textContent = `hasH264Hardware: ${newCtx.hasH264Hardware}`;
    }
    confList.update();
  };

  return PrimarySection.make({
    headingL10nId: "about-webrtc-media-context-heading",
    disclosureShowL10nId: "about-webrtc-media-context-show-msg",
    disclosureHideL10nId: "about-webrtc-media-context-hide-msg",
    autoRefreshPref: "media.aboutwebrtc.auto_refresh.media_ctx_section",
    renderFn,
    updateFn,
  });
}
PK
!<M,x��?chrome/toolkit/content/global/aboutwebrtc/configurationList.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { CopyButton } from "chrome://global/content/aboutwebrtc/copyButton.mjs";

function getPref(path) {
  switch (Services.prefs.getPrefType(path)) {
    case Services.prefs.PREF_BOOL:
      return Services.prefs.getBoolPref(path);
    case Services.prefs.PREF_INT:
      return Services.prefs.getIntPref(path);
    case Services.prefs.PREF_STRING:
      return Services.prefs.getStringPref(path);
  }
  return "";
}

/*
 * This provides a visual list of configuration settings given an array of
 * configuration paths. To change the list one can call setPrefPaths.
 */
class ConfigurationList {
  constructor(aPreferencePaths) {
    this.list = document.createElement("list");
    this.list.classList.add("prefList");
    this.setPrefPaths(aPreferencePaths);
  }

  /** @return {Element} */
  view() {
    return this.list;
  }

  /**
   * @return {Element[]}
   */
  getPrefListItems() {
    return [...this.list.children].flatMap(e =>
      e.dataset.prefPath !== undefined ? [e] : []
    );
  }

  /**
   * @return {string[]}
   */
  getPrefPaths() {
    return [...this.getPrefListItems()].map(e => e.dataset.prefPath);
  }

  //     setPrefPaths adds and removes list items from the list and updates
  //     existing elements

  setPrefPaths(aPreferencePaths) {
    const currentPaths = this.getPrefPaths();
    // Take the difference of the two arrays of preferences. There are three
    // groups of paths: those removed from the current list, those to remain
    // in the current list, and those to be added.
    const { kept: keptPaths, removed: removedPaths } = currentPaths.reduce(
      (acc, p) => {
        if (aPreferencePaths.includes(p)) {
          acc.kept.push(p);
        } else {
          acc.removed.push(p);
        }
        return acc;
      },
      { removed: [], kept: [] }
    );

    const addedPaths = aPreferencePaths.filter(p => !keptPaths.includes(p));

    // Remove items
    this.getPrefListItems()
      .filter(e => removedPaths.includes(e.dataset.prefPath))
      .forEach(e => e.remove() /* Remove from DOM*/);

    const addItemForPath = path => {
      const item = document.createElement("li");
      item.dataset.prefPath = path;

      item.appendChild(new CopyButton(() => path).element);

      const pathSpan = document.createElement("span");
      pathSpan.textContent = path;
      pathSpan.classList.add(["pathDisplay"]);
      item.appendChild(pathSpan);

      const valueSpan = document.createElement("span");
      valueSpan.classList.add(["valueDisplay"]);
      item.appendChild(valueSpan);

      this.list.appendChild(item);
    };

    // Add items
    addedPaths.forEach(addItemForPath);

    // Update all pref values
    this.updatePrefValues();
  }

  updatePrefValues() {
    for (const e of this.getPrefListItems()) {
      const value = getPref(e.dataset.prefPath);
      const valueSpan = e.getElementsByClassName("valueDisplay").item(0);
      if ("prefPath" in e.dataset) {
        valueSpan.textContent = value;
      }
    }
  }

  update() {
    this.updatePrefValues();
  }
}

export { ConfigurationList };
PK
!<B�"���8chrome/toolkit/content/global/aboutwebrtc/copyButton.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This creates a button that can be used to copy text to the clipboard.
 * Whenever the button is pressed the getCopyContentsFn passed into the
 * constructor is called and the resulting text is copied. It uses CSS
 * transitions to perform a short animation.
 */
class CopyButton {
  constructor(getCopyContentsFn) {
    const button = document.createElement("span");
    button.textContent = String.fromCodePoint(0x1f4cb);
    button.classList.add("copy-button", "copy-button-base");
    button.onclick = () => {
      if (!button.classList.contains("copy-button")) {
        return;
      }

      const handleAnimation = async () => {
        const switchFadeDirection = () => {
          if (button.classList.contains("copy-button-fade-out")) {
            // We just faded out so let's fade in
            button.classList.toggle("copy-button-fade-out");
            button.classList.toggle("copy-button-fade-in");
          } else {
            // We just faded in so let's fade out
            button.classList.toggle("copy-button-fade-out");
            button.classList.toggle("copy-button-fade-in");
          }
        };

        // Fade out clipboard icon
        // Fade out the clipboard character
        button.classList.toggle("copy-button-fade-out");
        // Wait for CSS transition to end
        await new Promise(r => (button.ontransitionend = r));

        // Fade in checkmark icon
        // This is the start of fade in.
        // Switch to the checkmark character
        button.textContent = String.fromCodePoint(0x2705);
        // Trigger CSS fade in transition
        switchFadeDirection();
        // Wait for CSS transition to end
        await new Promise(r => (button.ontransitionend = r));

        // Fade out clipboard icon
        // Trigger CSS fade out transition
        switchFadeDirection();
        // Wait for CSS transition to end
        await new Promise(r => (button.ontransitionend = r));

        // Fade in clipboard icon
        // This is the start of fade in.
        // Switch to the clipboard character
        button.textContent = String.fromCodePoint(0x1f4cb);
        // Trigger CSS fade in transition
        switchFadeDirection();
        // Wait for CSS transition to end
        await new Promise(r => (button.ontransitionend = r));

        // Remove fade
        button.classList.toggle("copy-button-fade-in");
        // Re-enable clicks and hidding when parent div has lost :hover
        button.classList.add("copy-button");
      };

      // Note the fade effect is handled in the CSS, we just need to swap
      // between the different CSS classes. This returns a promise that waits
      // for the current fade to end, starts the next fade, then resolves.

      navigator.clipboard.writeText(getCopyContentsFn());
      // Prevent animation from disappearing when parent div losses :hover,
      // and prevent additional clicks until the animation finishes.
      button.classList.remove("copy-button");
      handleAnimation(); // runs unawaited
    };
    this.element = button;
  }
}
export { CopyButton };
PK
!<���Oy
y
8chrome/toolkit/content/global/aboutwebrtc/disclosure.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const localization = new Localization(["toolkit/about/aboutWebrtc.ftl"], true);

/*
 * A disclosure area that has localized tooltips for expanding and collapsing
 * the area.
 */
class Disclosure {
  constructor({
    showMsg = "about-webrtc-fold-default-show-msg",
    hideMsg = "about-webrtc-fold-default-hide-msg",
    startsCollapsed = true,
  } = {}) {
    Object.assign(this, { showMsg, hideMsg, startsCollapsed });
    this.target = document.createElement("div");
    this.target.classList.add("fold-target");
    this.trigger = document.createElement("div");
    this.trigger.className = "fold-trigger";
    this.trigger.classList.add(
      "heading-medium",
      "no-print",
      this.showMsg,
      this.hideMsg
    );
    this.message = document.createElement("span");

    if (this.startsCollapsed) {
      this.collapse();
    } else {
      this.expand();
    }
    this.trigger.onclick = () => {
      if (this.target.classList.contains("fold-closed")) {
        this.expand();
      } else {
        this.collapse();
      }
    };
  }

  /** @return {Element} */
  control() {
    return this.trigger;
  }

  /** @return {Element} */
  view() {
    return this.target;
  }

  expand() {
    this.target.classList.remove("fold-closed");
    this.control().textContent = String.fromCodePoint(0x25bc);
    this.control().setAttribute(
      "title",
      localization.formatValueSync(this.hideMsg)
    );
    document.l10n.setAttributes(this.message, this.hideMsg);
  }

  collapse() {
    this.target.classList.add("fold-closed");
    this.trigger.textContent = String.fromCodePoint(0x25b6);
    this.control().setAttribute(
      "title",
      localization.formatValueSync(this.showMsg)
    );
    document.l10n.setAttributes(this.message, this.showMsg);
  }

  static expandAll() {
    for (const target of document.getElementsByClassName("fold-closed")) {
      target.classList.remove("fold-closed");
    }
    for (const trigger of document.getElementsByClassName("fold-trigger")) {
      const hideMsg = trigger.classList[2];
      document.l10n.setAttributes(trigger, hideMsg);
    }
  }

  static collapseAll() {
    for (const target of document.getElementsByClassName("fold-target")) {
      target.classList.add("fold-closed");
    }
    for (const trigger of document.getElementsByClassName("fold-trigger")) {
      const showMsg = trigger.classList[1];
      document.l10n.setAttributes(trigger, showMsg);
    }
  }
}

export { Disclosure };
PK
!<������3chrome/toolkit/content/global/aboutwebrtc/graph.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

function compStyle(property) {
  return getComputedStyle(window.document.body).getPropertyValue(property);
}

function toHumanReadable(num, fpDecimals) {
  const prefixes = [..." kMGTPEYZYRQ"];
  const inner = (curr, remainingPrefixes) => {
    return Math.abs(curr >= 1000)
      ? inner(curr / 1000, remainingPrefixes.slice(1, -1))
      : [curr.toFixed(fpDecimals), remainingPrefixes[0].trimEnd()];
  };
  return inner(num, prefixes);
}

class GraphImpl {
  constructor(width, height) {
    this.width = width;
    this.height = height;
  }

  // The returns the earliest time to graph
  startTime = dataSet => (dataSet.earliest() || { time: 0 }).time;

  // Returns the latest time to graph
  stopTime = dataSet => (dataSet.latest() || { time: 0 }).time;

  // The default background color
  bgColor = () => compStyle("--in-content-page-background");
  // The color to use for value graph lines
  valueLineColor = () => "grey";
  // The color to use for average graph lines and text
  averageLineColor = () => "green";
  // The color to use for the max value
  maxColor = () => "grey";
  // The color to use for the min value
  minColor = () => "grey";
  // Title color
  titleColor = () => compStyle("--in-content-page-color");
  // The color to use for a data point at a time.
  // The destination x coordinate and graph width are also provided.
  datumColor = () => "red";

  // Returns an SVG element that needs to be inserted into the DOM for display
  drawSparseValues = (dataSet, title, config) => {
    const { width, height } = this;
    // Clear the canvas
    const bgColor = this.bgColor();
    const mkSvgElem = type =>
      document.createElementNS("http://www.w3.org/2000/svg", type);
    const svgText = (x, y, text, color, subclass) => {
      const txt = mkSvgElem("text");
      txt.setAttribute("x", x);
      txt.setAttribute("y", y);
      txt.setAttribute("stroke", bgColor);
      txt.setAttribute("fill", color);
      txt.setAttribute("paint-order", "stroke");
      txt.textContent = text;
      txt.classList.add(["graph-text", ...[subclass]].join("-"));
      return txt;
    };
    const svg = mkSvgElem("svg");
    svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
    svg.setAttribute("version", "1.1");
    svg.setAttribute("width", width);
    svg.setAttribute("height", height);
    svg.classList.add("svg-graph");
    const rect = mkSvgElem("rect");
    rect.setAttribute("fill", bgColor);
    rect.setAttribute("width", width);
    rect.setAttribute("height", height);
    svg.appendChild(rect);

    if (config.toRate) {
      dataSet = dataSet.toRateDataSet();
    }

    const startTime = this.startTime(dataSet);
    const stopTime = this.stopTime(dataSet);
    let timeFilter = ({ time }) => time >= startTime && time <= stopTime;

    let avgDataSet = { dataPoints: [] };
    if (!config.noAvg) {
      avgDataSet = dataSet.toRollingAverageDataSet(config.avgPoints);
    }

    let filtered = dataSet.filter(timeFilter);
    if (filtered.dataPoints == []) {
      return svg;
    }

    let range = filtered.dataRange();
    if (range === undefined) {
      return svg;
    }
    let { min: rangeMin, max: rangeMax } = range;

    // Adjust the _display_ range to lift flat lines towards the center
    if (rangeMin == rangeMax) {
      rangeMin = rangeMin - 1;
      rangeMax = rangeMax + 1;
    }
    const yFactor = (height - 26) / (1 + rangeMax - rangeMin);
    const yPos = ({ value }) =>
      this.height - 1 - (value - rangeMin) * yFactor - 13;
    const xFactor = width / (1 + stopTime - startTime);
    const xPos = ({ time }) => (time - startTime) * xFactor;

    const toPathStr = dataPoints =>
      [...dataPoints]
        .map(
          (datum, index) => `${index ? "L" : "M"}${xPos(datum)} ${yPos(datum)}`
        )
        .join(" ");
    const valuePath = mkSvgElem("path");
    valuePath.setAttribute("d", toPathStr(filtered.dataPoints));
    valuePath.setAttribute("stroke", this.valueLineColor());
    valuePath.setAttribute("fill", "none");
    svg.appendChild(valuePath);

    const avgPath = mkSvgElem("path");
    avgPath.setAttribute("d", toPathStr(avgDataSet.dataPoints));
    avgPath.setAttribute("stroke", this.averageLineColor());
    avgPath.setAttribute("fill", "none");
    svg.appendChild(avgPath);
    const fixed = num => num.toFixed(config.fixedPointDecimals);
    const formatValue = value =>
      config.toHuman
        ? toHumanReadable(value, config.fixedPointDecimals).join("")
        : fixed(value);

    // Draw rolling average text
    avgDataSet.dataPoints.slice(-1).forEach(({ value }) => {
      svg.appendChild(
        svgText(
          5,
          height - 4,
          `AVG: ${formatValue(value)}`,
          this.averageLineColor(),
          "avg"
        )
      );
    });

    // Draw title text
    if (title) {
      svg.appendChild(
        svgText(
          5,
          12,
          `${title}${config.toRate ? "/s" : ""}`,
          this.titleColor(this),
          "title"
        )
      );
    }

    // Draw max value text
    const maxText = svgText(
      width - 5,
      12,
      `Max: ${formatValue(range.max)}`,
      this.maxColor(range.max),
      "max"
    );
    maxText.setAttribute("text-anchor", "end");
    svg.appendChild(maxText);

    // Draw min value text
    const minText = svgText(
      width - 5,
      height - 4,
      `Min: ${formatValue(range.min)}`,
      this.minColor(range.min),
      "min"
    );
    minText.setAttribute("text-anchor", "end");
    svg.appendChild(minText);
    return svg;
  };
}

export { GraphImpl };
PK
!<$߹��5chrome/toolkit/content/global/aboutwebrtc/graphdb.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const CHECK_RTC_STATS_COLLECTION = [
  "inboundRtpStreamStats",
  "outboundRtpStreamStats",
  "remoteInboundRtpStreamStats",
  "remoteOutboundRtpStreamStats",
];

const DEFAULT_PROPS = {
  avgPoints: 10,
  histSecs: 15,
  toRate: false,
  noAvg: false,
  fixedPointDecimals: 2,
  toHuman: false,
};

const REMOTE_RTP_PROPS = "avgPoints=2;histSecs=90";
const GRAPH_KEYS = [
  "inbound-rtp.framesPerSecond;noAvg",
  "inbound-rtp.packetsReceived;toRate",
  "inbound-rtp.packetsLost;toRate",
  "inbound-rtp.jitter;fixedPointDecimals=4",
  `remote-inbound-rtp.roundTripTime;${REMOTE_RTP_PROPS}`,
  `remote-inbound-rtp.packetsReceived;toRate;${REMOTE_RTP_PROPS}`,
  "outbound-rtp.packetsSent;toRate",
  "outbound-rtp.framesSent;toRate",
  "outbound-rtp.frameHeight;noAvg",
  "outbound-rtp.frameWidth;noAvg",
  "outbound-rtp.nackCount",
  "outbound-rtp.pliCount",
  "outbound-rtp.firCount",
  `remote-outbound-rtp.bytesSent;toHuman;toRate;${REMOTE_RTP_PROPS}`,
  `remote-outbound-rtp.packetsSent;toRate;${REMOTE_RTP_PROPS}`,
]
  .map(k => k.split(".", 2))
  .reduce((mapOfArr, [k, rest]) => {
    mapOfArr[k] ??= [];
    const [subKey, ...conf] = rest.split(";");
    let config = conf.reduce((c, v) => {
      let [configName, ...configVal] = v.split("=", 2);
      c[configName] = !configVal.length ? true : configVal[0];
      return c;
    }, {});
    mapOfArr[k].push({ subKey, config });
    return mapOfArr;
  }, {});

// Sliding window iterator of size n (where: n >= 1) over the array.
// Only returns full windows.
// Returns [] if n > array.length.
// eachN(['a','b','c','d','e'], 3) will yield the following values:
//   ['a','b','c'], ['b','c','d'], and ['c','d','e']
const eachN = (array, n) => {
  return {
    // Index state
    index: 0,
    // Iteration function
    next() {
      let slice = array.slice(this.index, this.index + n);
      this.index++;
      // Done is true _AFTER_ the last value has returned.
      // When done is true, value is ignored.
      return { value: slice, done: slice.length < n };
    },
    [Symbol.iterator]() {
      return this;
    },
  };
};

const msToSec = ms => 1000 * ms;

//
// A subset of the graph data
//
class GraphDataSet {
  constructor(dataPoints) {
    this.dataPoints = dataPoints;
  }

  // The latest
  latest = () => (this.dataPoints ? this.dataPoints.slice(-1)[0] : undefined);

  earliest = () => (this.dataPoints ? this.dataPoints[0] : undefined);

  // The returns the earliest time to graph
  startTime = () => (this.earliest() || { time: 0 }).time;

  // Returns the latest time to graph
  stopTime = () => (this.latest() || { time: 0 }).time;

  // Elapsed time within the display window
  elapsed = () =>
    this.dataPoints ? this.latest().time - this.earliest().time : 0;

  // Return a new data set that has been filtered
  filter = fn => new GraphDataSet([...this.dataPoints].filter(fn));

  // The range of values in the set or or undefined if the set is empty
  dataRange = () =>
    this.dataPoints.reduce(
      ({ min, max }, { value }) => ({
        min: Math.min(min, value),
        max: Math.max(max, value),
      }),
      this.dataPoints.length
        ? { min: this.dataPoints[0].value, max: this.dataPoints[0].value }
        : undefined
    );

  // Get the rates between points. By definition the rates will have
  // one fewer data points.
  toRateDataSet = () =>
    new GraphDataSet(
      [...eachN(this.dataPoints, 2)].map(([a, b]) => ({
        // Time mid point
        time: (b.time + a.time) / 2,
        value: msToSec(b.value - a.value) / (b.time - a.time),
      }))
    );

  average = samples =>
    samples.reduce(
      ({ time, value }, { time: t, value: v }) => ({
        time: time + t / samples.length,
        value: value + v / samples.length,
      }),
      { time: 0, value: 0 }
    );

  toRollingAverageDataSet = sampleSize =>
    new GraphDataSet([...eachN(this.dataPoints, sampleSize)].map(this.average));
}

class GraphData {
  constructor(id, key, subKey, config) {
    this.id = id;
    this.key = key;
    this.subKey = subKey;
    this.data = [];
    this.config = Object.assign({}, DEFAULT_PROPS, config);
  }

  setValueForTime(dataPoint) {
    this.data = this.data.filter(({ time: t }) => t != dataPoint.time);
    this.data.push(dataPoint);
  }

  getValuesSince = time => this.data.filter(dp => dp.time > time);

  getDataSetSince = time =>
    new GraphDataSet(this.data.filter(dp => dp.time > time));

  getConfig = () => this.config;

  // Cull old data, but keep twice the window size for average computation
  cullData = timeNow =>
    (this.data = this.data.filter(
      ({ time }) => time + msToSec(this.config.histSecs * 2) > timeNow
    ));
}

class GraphDb {
  constructor(report) {
    this.graphDatas = new Map();
    this.insertReportData(report);
  }

  mkStoreKey = ({ id, key, subKey }) => `${key}.${id}.${subKey}`;

  insertDataPoint(id, key, subKey, config, time, value) {
    let storeKey = this.mkStoreKey({ id, key, subKey });
    let data =
      this.graphDatas.get(storeKey) || new GraphData(id, key, subKey, config);
    data.setValueForTime({ time, value });
    data.cullData(time);
    this.graphDatas.set(storeKey, data);
  }

  insertReportData(report) {
    if (report.timestamp == this.lastReportTimestamp) {
      return;
    }
    this.lastReportTimestamp = report.timestamp;
    CHECK_RTC_STATS_COLLECTION.forEach(listName => {
      (report[listName] || []).forEach(stats => {
        (GRAPH_KEYS[stats.type] || []).forEach(({ subKey, config }) => {
          if (stats[subKey] !== undefined) {
            this.insertDataPoint(
              stats.id,
              stats.type,
              subKey,
              config,
              stats.timestamp,
              stats[subKey]
            );
          }
        });
      });
    });
  }

  getGraphDataById = id =>
    [...this.graphDatas.values()].filter(gd => gd.id == id);
}

export { GraphDb };
PK
!<������0chrome/toolkit/content/global/adjustableTitle.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

let { PromptUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/PromptUtils.sys.mjs"
);

const AdjustableTitle = {
  _cssSnippet: `
    #titleContainer {
      /* This gets display: flex by virtue of being a row in a subdialog, from
        * commonDialog.css . */
      flex-shrink: 0;

      flex-direction: row;
      align-items: baseline;

      margin-inline: 4px;
      /* Ensure we don't exceed the bounds of the dialog: */
      max-width: calc(100vw - 32px);

      --icon-size: 16px;
    }

    #titleContainer[noicon] > .titleIcon {
      display: none;
    }

    .titleIcon {
      width: var(--icon-size);
      height: var(--icon-size);
      padding-inline-end: 4px;
      flex-shrink: 0;

      background-image: var(--icon-url, url("chrome://global/skin/icons/defaultFavicon.svg"));
      background-size: 16px 16px;
      background-origin: content-box;
      background-repeat: no-repeat;
      background-color: var(--in-content-page-background);
      -moz-context-properties: fill;
      fill: currentColor;
    }

    #titleCropper:not([nomaskfade]) {
      display: inline-flex;
    }

    #titleCropper {
      overflow: hidden;

      justify-content: right;
      mask-repeat: no-repeat;
      /* go from left to right with the mask: */
      --mask-dir: right;
    }

    #titleContainer:not([noicon]) > #titleCropper {
      /* Align the icon and text: */
      translate: 0 calc(-1px - max(.6 * var(--icon-size) - .6em, 0px));
    }

    #titleCropper[rtlorigin] {
      justify-content: left;
      /* go from right to left with the mask: */
      --mask-dir: left;
    }


    #titleCropper:not([nomaskfade]) #titleText {
      display: inline-flex;
      white-space: nowrap;
    }

    #titleText {
      font-weight: 600;
      flex: 1 0 auto; /* Grow but do not shrink. */
      unicode-bidi: plaintext; /* Ensure we align RTL text correctly. */
      text-align: match-parent;
    }

    #titleCropper[overflown] {
      mask-image: linear-gradient(to var(--mask-dir), transparent, black 100px);
    }

    /* hide the old title */
    #infoTitle {
      display: none;
    }
   `,

  _insertMarkup() {
    let iconEl = document.createElement("span");
    iconEl.className = "titleIcon";
    this._titleCropEl = document.createElement("span");
    this._titleCropEl.id = "titleCropper";
    this._titleEl = document.createElement("span");
    this._titleEl.id = "titleText";
    this._containerEl = document.createElement("div");
    this._containerEl.id = "titleContainer";
    this._containerEl.className = "dialogRow titleContainer";
    this._titleCropEl.append(this._titleEl);
    this._containerEl.append(iconEl, this._titleCropEl);
    let targetID = document.documentElement.getAttribute("headerparent");
    document.getElementById(targetID).prepend(this._containerEl);
    let styleEl = document.createElement("style");
    styleEl.textContent = this._cssSnippet;
    document.documentElement.prepend(styleEl);
  },

  _overflowHandler() {
    requestAnimationFrame(async () => {
      let isOverflown;
      try {
        isOverflown = await window.promiseDocumentFlushed(() => {
          return (
            this._titleCropEl.getBoundingClientRect().width <
            this._titleEl.getBoundingClientRect().width
          );
        });
      } catch (ex) {
        // In automated tests, this can fail with a DOM exception if
        // the window has closed by the time layout tries to call us.
        // In this case, just bail, and only log any other errors:
        if (
          !DOMException.isInstance(ex) ||
          ex.name != "NoModificationAllowedError"
        ) {
          console.error(ex);
        }
        return;
      }
      this._titleCropEl.toggleAttribute("overflown", isOverflown);
      if (isOverflown) {
        this._titleEl.setAttribute("title", this._titleEl.textContent);
      } else {
        this._titleEl.removeAttribute("title");
      }
    });
  },

  _updateTitle(title) {
    title = JSON.parse(title);
    if (title.raw) {
      this._titleEl.textContent = title.raw;
      let { DIRECTION_RTL } = window.windowUtils;
      this._titleCropEl.toggleAttribute(
        "rtlorigin",
        window.windowUtils.getDirectionFromText(title.raw) == DIRECTION_RTL
      );
    } else {
      document.l10n.setAttributes(this._titleEl, title.l10nId);
    }

    if (!document.documentElement.hasAttribute("neediconheader")) {
      this._containerEl.setAttribute("noicon", "true");
    } else if (title.shouldUseMaskFade) {
      this._overflowHandler();
    } else {
      this._titleCropEl.toggleAttribute("nomaskfade", true);
    }
  },

  init() {
    // Only run this if we're embedded and proton modals are enabled.
    if (!window.docShell.chromeEventHandler) {
      return;
    }

    this._insertMarkup();
    let title = document.documentElement.getAttribute("headertitle");
    if (title) {
      this._updateTitle(title);
    }
    this._mutObs = new MutationObserver(() => {
      this._updateTitle(document.documentElement.getAttribute("headertitle"));
    });
    this._mutObs.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["headertitle"],
    });
  },
};

document.addEventListener(
  "DOMContentLoaded",
  () => {
    AdjustableTitle.init();
  },
  { once: true }
);
PK
!<%(.�.chrome/toolkit/content/global/alerts/alert.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#alertBox[animate] {
  animation-duration: 20s;
  animation-fill-mode: both;
  animation-name: alert-animation;
}

#alertBox[animate]:not([clicked], [closing]):hover {
  animation-play-state: paused;
}

#alertBox:not([hasOrigin]) > box > #alertTextBox > #alertFooter,
#alertBox:not([hasIcon]) > box > #alertIcon,
#alertImage:not([src]) {
  display: none;
}

#alertTitleBox {
  justify-content: center;
  align-items: center;
}

.alertText {
  white-space: pre-wrap;
}

@keyframes alert-animation {
  from {
    visibility: visible;
  }
  to {
    visibility: hidden;
  }
}
PK
!<С=�*1*1-chrome/toolkit/content/global/alerts/alert.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { AppConstants } = ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);

// Copied from nsILookAndFeel.h, see comments on eMetric_AlertNotificationOrigin
const NS_ALERT_HORIZONTAL = 1;
const NS_ALERT_LEFT = 2;
const NS_ALERT_TOP = 4;

const WINDOW_MARGIN = AppConstants.platform == "win" ? 0 : 10;
const BODY_TEXT_LIMIT = 200;
const WINDOW_SHADOW_SPREAD = AppConstants.platform == "win" ? 10 : 0;

var gOrigin = 0; // Default value: alert from bottom right.
var gReplacedWindow = null;
var gAlertListener = null;
var gAlertTextClickable = false;
var gAlertCookie = "";
var gIsActive = false;
var gIsReplaced = false;
var gRequireInteraction = false;

function prefillAlertInfo() {
  // unwrap all the args....
  // arguments[0] --> the image src url
  // arguments[1] --> the alert title
  // arguments[2] --> the alert text
  // arguments[3] --> is the text clickable?
  // arguments[4] --> the alert cookie to be passed back to the listener
  // arguments[5] --> the alert origin reported by the look and feel
  // arguments[6] --> bidi
  // arguments[7] --> lang
  // arguments[8] --> requires interaction
  // arguments[9] --> replaced alert window (nsIDOMWindow)
  // arguments[10] --> an optional callback listener (nsIObserver)
  // arguments[11] -> the nsIURI.hostPort of the origin, optional
  // arguments[12] -> the alert icon URL, optional

  switch (window.arguments.length) {
    default:
    case 13: {
      if (window.arguments[12]) {
        let alertBox = document.getElementById("alertBox");
        alertBox.setAttribute("hasIcon", true);

        let icon = document.getElementById("alertIcon");
        icon.src = window.arguments[12];
      }
    }
    // fall through
    case 12: {
      if (window.arguments[11]) {
        let alertBox = document.getElementById("alertBox");
        alertBox.setAttribute("hasOrigin", true);

        let hostPort = window.arguments[11];
        const ALERT_BUNDLE = Services.strings.createBundle(
          "chrome://alerts/locale/alert.properties"
        );
        const BRAND_BUNDLE = Services.strings.createBundle(
          "chrome://branding/locale/brand.properties"
        );
        const BRAND_NAME = BRAND_BUNDLE.GetStringFromName("brandShortName");
        let label = document.getElementById("alertSourceLabel");
        label.setAttribute(
          "value",
          ALERT_BUNDLE.formatStringFromName("source.label", [hostPort])
        );
        let doNotDisturbMenuItem = document.getElementById(
          "doNotDisturbMenuItem"
        );
        doNotDisturbMenuItem.setAttribute(
          "label",
          ALERT_BUNDLE.formatStringFromName("pauseNotifications.label", [
            BRAND_NAME,
          ])
        );
        let disableForOrigin = document.getElementById(
          "disableForOriginMenuItem"
        );
        disableForOrigin.setAttribute(
          "label",
          ALERT_BUNDLE.formatStringFromName(
            "webActions.disableForOrigin.label",
            [hostPort]
          )
        );
        let openSettings = document.getElementById("openSettingsMenuItem");
        openSettings.setAttribute(
          "label",
          ALERT_BUNDLE.GetStringFromName("webActions.settings.label")
        );
      }
    }
    // fall through
    case 11:
      gAlertListener = window.arguments[10];
    // fall through
    case 10:
      gReplacedWindow = window.arguments[9];
    // fall through
    case 9:
      gRequireInteraction = window.arguments[8];
    // fall through
    case 8:
      if (window.arguments[7]) {
        document
          .getElementById("alertTitleLabel")
          .setAttribute("lang", window.arguments[7]);
        document
          .getElementById("alertTextLabel")
          .setAttribute("lang", window.arguments[7]);
      }
    // fall through
    case 7:
      if (window.arguments[6]) {
        document.getElementById("alertNotification").style.direction =
          window.arguments[6];
      }
    // fall through
    case 6:
      gOrigin = window.arguments[5];
    // fall through
    case 5:
      gAlertCookie = window.arguments[4];
    // fall through
    case 4:
      gAlertTextClickable = window.arguments[3];
      if (gAlertTextClickable) {
        document
          .getElementById("alertNotification")
          .setAttribute("clickable", true);
        document
          .getElementById("alertTextLabel")
          .setAttribute("clickable", true);
      }
    // fall through
    case 3:
      if (window.arguments[2]) {
        document.getElementById("alertBox").setAttribute("hasBodyText", true);
        let bodyText = window.arguments[2];
        let bodyTextLabel = document.getElementById("alertTextLabel");

        if (bodyText.length > BODY_TEXT_LIMIT) {
          bodyTextLabel.setAttribute("tooltiptext", bodyText);

          let ellipsis = "\u2026";
          try {
            ellipsis = Services.prefs.getComplexValue(
              "intl.ellipsis",
              Ci.nsIPrefLocalizedString
            ).data;
          } catch (e) {}

          // Copied from nsContextMenu.js' formatSearchContextItem().
          // If the JS character after our truncation point is a trail surrogate,
          // include it in the truncated string to avoid splitting a surrogate pair.
          let truncLength = BODY_TEXT_LIMIT;
          let truncChar = bodyText[BODY_TEXT_LIMIT].charCodeAt(0);
          if (truncChar >= 0xdc00 && truncChar <= 0xdfff) {
            truncLength++;
          }

          bodyText = bodyText.substring(0, truncLength) + ellipsis;
        }
        bodyTextLabel.textContent = bodyText;
      }
    // fall through
    case 2:
      document
        .getElementById("alertTitleLabel")
        .setAttribute("value", window.arguments[1]);
    // fall through
    case 1:
      if (window.arguments[0]) {
        document.getElementById("alertBox").setAttribute("hasImage", true);
        document
          .getElementById("alertImage")
          .setAttribute("src", window.arguments[0]);
      }
    // fall through
    case 0:
      break;
  }
}

function onAlertLoad() {
  const ALERT_DURATION_IMMEDIATE = 20000;
  let alertTextBox = document.getElementById("alertTextBox");
  let alertImageBox = document.getElementById("alertImageBox");
  alertImageBox.style.minHeight = alertTextBox.scrollHeight + "px";

  window.sizeToContent();

  if (gReplacedWindow && !gReplacedWindow.closed) {
    moveWindowToReplace(gReplacedWindow);
    gReplacedWindow.gIsReplaced = true;
    gReplacedWindow.close();
  } else {
    moveWindowToEnd();
  }

  window.addEventListener("XULAlertClose", function () {
    window.close();
  });

  // If the require interaction flag is set, prevent auto-closing the notification.
  if (!gRequireInteraction) {
    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
      setTimeout(function () {
        window.close();
      }, ALERT_DURATION_IMMEDIATE);
    } else {
      let alertBox = document.getElementById("alertBox");
      alertBox.addEventListener("animationend", function hideAlert(event) {
        if (
          event.animationName == "alert-animation" ||
          event.animationName == "alert-clicked-animation" ||
          event.animationName == "alert-closing-animation"
        ) {
          alertBox.removeEventListener("animationend", hideAlert);
          window.close();
        }
      });
      alertBox.setAttribute("animate", true);
    }
  }

  let alertSettings = document.getElementById("alertSettings");
  alertSettings.addEventListener("focus", onAlertSettingsFocus);
  alertSettings.addEventListener("click", onAlertSettingsClick);

  gIsActive = true;

  let ev = new CustomEvent("AlertActive", { bubbles: true, cancelable: true });
  document.documentElement.dispatchEvent(ev);

  if (gAlertListener) {
    gAlertListener.observe(null, "alertshow", gAlertCookie);
  }
}

function moveWindowToReplace(aReplacedAlert) {
  let heightDelta = window.outerHeight - aReplacedAlert.outerHeight;

  // Move windows that come after the replaced alert if the height is different.
  if (heightDelta != 0) {
    for (let alertWindow of Services.wm.getEnumerator("alert:alert")) {
      if (!alertWindow.gIsActive) {
        continue;
      }
      // boolean to determine if the alert window is after the replaced alert.
      let alertIsAfter =
        gOrigin & NS_ALERT_TOP
          ? alertWindow.screenY > aReplacedAlert.screenY
          : aReplacedAlert.screenY > alertWindow.screenY;
      if (alertIsAfter) {
        // The new Y position of the window.
        let adjustedY =
          gOrigin & NS_ALERT_TOP
            ? alertWindow.screenY + heightDelta
            : alertWindow.screenY - heightDelta;
        alertWindow.moveTo(alertWindow.screenX, adjustedY);
      }
    }
  }

  let adjustedY =
    gOrigin & NS_ALERT_TOP
      ? aReplacedAlert.screenY
      : aReplacedAlert.screenY - heightDelta;
  window.moveTo(aReplacedAlert.screenX, adjustedY);
}

function moveWindowToEnd() {
  // Determine position
  let x =
    gOrigin & NS_ALERT_LEFT
      ? screen.availLeft
      : screen.availLeft + screen.availWidth - window.outerWidth;
  let y =
    gOrigin & NS_ALERT_TOP
      ? screen.availTop
      : screen.availTop + screen.availHeight - window.outerHeight;

  // Position the window at the end of all alerts.
  for (let alertWindow of Services.wm.getEnumerator("alert:alert")) {
    if (alertWindow != window && alertWindow.gIsActive) {
      if (gOrigin & NS_ALERT_TOP) {
        y = Math.max(
          y,
          alertWindow.screenY + alertWindow.outerHeight - WINDOW_SHADOW_SPREAD
        );
      } else {
        y = Math.min(
          y,
          alertWindow.screenY - window.outerHeight + WINDOW_SHADOW_SPREAD
        );
      }
    }
  }

  // Offset the alert by WINDOW_MARGIN pixels from the edge of the screen
  y += gOrigin & NS_ALERT_TOP ? WINDOW_MARGIN : -WINDOW_MARGIN;
  x += gOrigin & NS_ALERT_LEFT ? WINDOW_MARGIN : -WINDOW_MARGIN;

  window.moveTo(x, y);
}

function onAlertBeforeUnload() {
  if (!gIsReplaced) {
    // Move other alert windows to fill the gap left by closing alert.
    let heightDelta = window.outerHeight + WINDOW_MARGIN - WINDOW_SHADOW_SPREAD;
    for (let alertWindow of Services.wm.getEnumerator("alert:alert")) {
      if (alertWindow != window && alertWindow.gIsActive) {
        if (gOrigin & NS_ALERT_TOP) {
          if (alertWindow.screenY > window.screenY) {
            alertWindow.moveTo(
              alertWindow.screenX,
              alertWindow.screenY - heightDelta
            );
          }
        } else if (window.screenY > alertWindow.screenY) {
          alertWindow.moveTo(
            alertWindow.screenX,
            alertWindow.screenY + heightDelta
          );
        }
      }
    }
  }

  if (gAlertListener) {
    gAlertListener.observe(null, "alertfinished", gAlertCookie);
  }
}

function onAlertClick() {
  if (gAlertListener && gAlertTextClickable) {
    gAlertListener.observe(null, "alertclickcallback", gAlertCookie);
  }

  let alertBox = document.getElementById("alertBox");
  if (alertBox.getAttribute("animate") == "true") {
    // Closed when the animation ends.
    alertBox.setAttribute("clicked", "true");
  } else {
    window.close();
  }
}

function doNotDisturb() {
  const alertService = Cc["@mozilla.org/alerts-service;1"]
    .getService(Ci.nsIAlertsService)
    .QueryInterface(Ci.nsIAlertsDoNotDisturb);
  alertService.manualDoNotDisturb = true;
  onAlertClose();
}

function disableForOrigin() {
  gAlertListener.observe(null, "alertdisablecallback", gAlertCookie);
  onAlertClose();
}

function onAlertSettingsFocus(event) {
  event.target.removeAttribute("focusedViaMouse");
}

function onAlertSettingsClick(event) {
  // XXXjaws Hack used to remove the focus-ring only
  // from mouse interaction, but focus-ring drawing
  // should only be enabled when interacting via keyboard.
  event.target.setAttribute("focusedViaMouse", true);
  event.stopPropagation();
}

function openSettings() {
  gAlertListener.observe(null, "alertsettingscallback", gAlertCookie);
  onAlertClose();
}

function onAlertClose() {
  let alertBox = document.getElementById("alertBox");
  if (alertBox.getAttribute("animate") == "true") {
    // Closed when the animation ends.
    alertBox.setAttribute("closing", "true");
  } else {
    window.close();
  }
}
PK
!<fRX��	�	0chrome/toolkit/content/global/alerts/alert.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window>

<window
  id="alertNotification"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  windowtype="alert:alert"
  xmlns:xhtml="http://www.w3.org/1999/xhtml"
  role="alert"
  pack="start"
  hidechrome="true"
  onload="onAlertLoad();"
  onclick="onAlertClick();"
  onbeforeunload="onAlertBeforeUnload();"
>
  <linkset>
    <xhtml:link
      rel="stylesheet"
      href="chrome://global/content/alerts/alert.css"
    />
    <xhtml:link rel="stylesheet" href="chrome://global/skin/alert.css" />

    <xhtml:link rel="localization" href="toolkit/global/alert.ftl" />
  </linkset>

  <script src="chrome://global/content/alerts/alert.js" />

  <vbox id="alertBox">
    <box id="alertTitleBox">
      <image id="alertIcon" />
      <label id="alertTitleLabel" class="alertTitle" crop="end" />
      <vbox>
        <toolbarbutton
          class="close-icon"
          data-l10n-id="alert-close"
          onclick="event.stopPropagation();"
          oncommand="onAlertClose();"
        />
      </vbox>
    </box>
    <box>
      <hbox
        id="alertImageBox"
        class="alertImageBox"
        align="center"
        pack="center"
      >
        <image id="alertImage" />
      </hbox>

      <vbox id="alertTextBox" class="alertTextBox">
        <label id="alertTextLabel" class="alertText" />
        <spacer flex="1" />
        <box id="alertFooter">
          <label id="alertSourceLabel" class="alertSource" />
          <button
            type="menu"
            id="alertSettings"
            data-l10n-id="alert-settings-title"
          >
            <menupopup position="after_end">
              <menuitem id="doNotDisturbMenuItem" oncommand="doNotDisturb();" />
              <menuseparator />
              <menuitem
                id="disableForOriginMenuItem"
                oncommand="disableForOrigin();"
              />
              <menuitem id="openSettingsMenuItem" oncommand="openSettings();" />
            </menupopup>
          </button>
        </box>
      </vbox>
    </box>
  </vbox>

  <!-- This method is called inline because we want to make sure we establish the width
       and height of the alert before we fire the onload handler. -->
  <script>
    /* eslint-disable no-undef */
    prefillAlertInfo();
  </script>
</window>
PK
!<��fSS*chrome/toolkit/content/global/appPicker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { AppConstants } = ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);

function AppPicker() {}

AppPicker.prototype = {
  // Class members
  _incomingParams: null,

  /**
   * Init the dialog and populate the application list
   */
  appPickerLoad: function appPickerLoad() {
    const nsILocalHandlerApp = Ci.nsILocalHandlerApp;

    document.addEventListener("dialogaccept", function () {
      g_dialog.appPickerOK();
    });
    document.addEventListener("dialogcancel", function () {
      g_dialog.appPickerCancel();
    });
    document.addEventListener("dialogextra2", function () {
      g_dialog.appPickerBrowse();
    });

    this._incomingParams = window.arguments[0];
    this._incomingParams.handlerApp = null;

    document.title = this._incomingParams.title;

    // Header creation - at the very least, we must have
    // a mime type:
    //
    // (icon) Zip File
    // (icon) filename
    //
    // (icon) Web Feed
    // (icon) mime/type
    //
    // (icon) mime/type
    // (icon)

    var mimeInfo = this._incomingParams.mimeInfo;
    var filename = this._incomingParams.filename;
    if (!filename) {
      filename = mimeInfo.MIMEType;
    }
    var description = this._incomingParams.description;
    if (!description) {
      description = filename;
      filename = "";
    }

    // Setup the dialog header information
    document
      .getElementById("content-description")
      .setAttribute("value", description);
    document
      .getElementById("suggested-filename")
      .setAttribute("value", filename || "");
    document
      .getElementById("content-icon")
      .setAttribute(
        "src",
        "moz-icon://" + filename + "?size=32&contentType=" + mimeInfo.MIMEType
      );

    // Grab a list of nsILocalHandlerApp application helpers to list
    var fileList = mimeInfo.possibleLocalHandlers;

    var list = document.getElementById("app-picker-listbox");

    var primaryCount = 0;

    if (!fileList || !fileList.length) {
      // display a message saying nothing is configured
      document.getElementById("app-picker-notfound").removeAttribute("hidden");
      return;
    }

    for (var idx = 0; idx < fileList.length; idx++) {
      var file = fileList.queryElementAt(idx, nsILocalHandlerApp);
      try {
        if (!file.executable || !file.executable.isFile()) {
          continue;
        }
      } catch (err) {
        continue;
      }

      var item = document.createXULElement("richlistitem");
      item.handlerApp = file;
      list.appendChild(item);

      var image = document.createXULElement("image");
      image.setAttribute("src", this.getFileIconURL(file.executable));
      item.appendChild(image);

      var label = document.createXULElement("label");
      label.setAttribute("value", this.getFileDisplayName(file.executable));
      item.appendChild(label);

      primaryCount++;
    }

    if (primaryCount == 0) {
      // display a message saying nothing is configured
      document.getElementById("app-picker-notfound").removeAttribute("hidden");
    }
  },

  /**
   * Retrieve the moz-icon for the app
   */
  getFileIconURL: function getFileIconURL(file) {
    const nsIFileProtocolHandler = Ci.nsIFileProtocolHandler;

    var fph = Services.io
      .getProtocolHandler("file")
      .QueryInterface(nsIFileProtocolHandler);
    if (!fph) {
      return "";
    }

    var urlSpec = fph.getURLSpecFromActualFile(file);
    return "moz-icon://" + urlSpec + "?size=32";
  },

  /**
   * Retrieve the pretty description from the file
   */
  getFileDisplayName: function getFileDisplayName(file) {
    if (AppConstants.platform == "win") {
      if (file instanceof Ci.nsILocalFileWin) {
        try {
          return file.getVersionInfoField("FileDescription");
        } catch (e) {}
      }
    } else if (AppConstants.platform == "macosx") {
      if (file instanceof Ci.nsILocalFileMac) {
        try {
          return file.bundleDisplayName;
        } catch (e) {}
      }
    }
    return file.leafName;
  },

  /**
   * Double click accepts an app
   */
  appDoubleClick: function appDoubleClick() {
    var list = document.getElementById("app-picker-listbox");
    var selItem = list.selectedItem;

    if (!selItem) {
      this._incomingParams.handlerApp = null;
      return true;
    }

    this._incomingParams.handlerApp = selItem.handlerApp;
    window.close();

    return true;
  },

  appPickerOK: function appPickerOK() {
    if (this._incomingParams.handlerApp) {
      return;
    }

    var list = document.getElementById("app-picker-listbox");
    var selItem = list.selectedItem;

    if (!selItem) {
      this._incomingParams.handlerApp = null;
      return;
    }
    this._incomingParams.handlerApp = selItem.handlerApp;
  },

  appPickerCancel: function appPickerCancel() {
    this._incomingParams.handlerApp = null;
  },

  /**
   * User browse for an app.
   */
  appPickerBrowse: function appPickerBrowse() {
    var nsIFilePicker = Ci.nsIFilePicker;
    var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);

    fp.init(
      window.browsingContext,
      this._incomingParams.title,
      nsIFilePicker.modeOpen
    );
    fp.appendFilters(nsIFilePicker.filterApps);

    var startLocation;
    if (AppConstants.platform == "win") {
      startLocation = "ProgF"; // Program Files
    } else if (AppConstants.platform == "macosx") {
      startLocation = "LocApp"; // Local Applications
    } else {
      startLocation = "Home";
    }
    fp.displayDirectory = Services.dirsvc.get(startLocation, Ci.nsIFile);

    fp.open(rv => {
      if (rv == nsIFilePicker.returnOK && fp.file) {
        var localHandlerApp = Cc[
          "@mozilla.org/uriloader/local-handler-app;1"
        ].createInstance(Ci.nsILocalHandlerApp);
        localHandlerApp.executable = fp.file;

        this._incomingParams.handlerApp = localHandlerApp;
        window.close();
      }
    });
  },
};

// Global object
var g_dialog = new AppPicker();
PK
!<�
D�YY-chrome/toolkit/content/global/appPicker.xhtml<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window>

<window
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
  onload="g_dialog.appPickerLoad();"
  aria-describedby="content-description suggested-filename"
  persist="screenX screenY"
>
  <dialog
    id="app-picker"
    buttons="accept,cancel,extra2"
    data-l10n-id="app-picker-browse-button"
    data-l10n-attrs="buttonlabelextra2"
    defaultButton="accept"
  >
    <linkset>
      <html:link rel="stylesheet" href="chrome://global/skin/global.css" />
      <html:link rel="stylesheet" href="chrome://global/skin/appPicker.css" />

      <html:link rel="localization" href="toolkit/global/appPicker.ftl" />
    </linkset>
    <script src="chrome://global/content/appPicker.js" />

    <hbox id="file-info" align="center">
      <image id="content-icon" src="" />
      <vbox flex="1">
        <label id="content-description" crop="center" value="" />
        <label id="suggested-filename" crop="center" value="" />
      </vbox>
    </hbox>

    <label
      id="sendto-message"
      data-l10n-id="app-picker-send-msg"
      control="app-picker-listbox"
    />

    <richlistbox
      id="app-picker-listbox"
      ondblclick="g_dialog.appDoubleClick();"
    />

    <label
      id="app-picker-notfound"
      data-l10n-id="app-picker-no-app-found"
      hidden="true"
    />
  </dialog>
</window>
PK
!<�Wa8chrome/toolkit/content/global/backgroundPageThumbs.xhtml<!DOCTYPE html>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!-- This page is used to host a (remote) browser for background page
     thumbnailing purposes. It's always loaded as chrome:// . -->
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta charset="utf-8" />
    <title>backgroundPageThumbs.html</title>
  </head>
  <body></body>
</html>
PK
!<9N��`B`B2chrome/toolkit/content/global/bindings/calendar.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * Initialize the Calendar and generate nodes for week headers and days, and
 * attach event listeners.
 *
 * @param {Object} options
 *        {
 *          {Number} calViewSize: Number of days to appear on a calendar view
 *          {Function} getDayString: Transform day number to string
 *          {Function} getWeekHeaderString: Transform day of week number to string
 *          {Function} setSelection: Set selection for dateKeeper
 *          {Function} setCalendarMonth: Update the month shown by the dateView
 *                     to a specific month of a specific year
 *        }
 * @param {Object} context
 *        {
 *          {DOMElement} weekHeader
 *          {DOMElement} daysView
 *        }
 */
function Calendar(options, context) {
  this.context = context;
  this.context.DAYS_IN_A_WEEK = 7;
  this.state = {
    days: [],
    weekHeaders: [],
    setSelection: options.setSelection,
    setCalendarMonth: options.setCalendarMonth,
    getDayString: options.getDayString,
    getWeekHeaderString: options.getWeekHeaderString,
    focusedDate: null,
  };
  this.elements = {
    weekHeaders: this._generateNodes(
      this.context.DAYS_IN_A_WEEK,
      context.weekHeader
    ),
    daysView: this._generateNodes(options.calViewSize, context.daysView),
  };

  this._attachEventListeners();
}

Calendar.prototype = {
  /**
   * Set new properties and render them.
   *
   * @param {Object} props
   *        {
   *          {Boolean} isVisible: Whether or not the calendar is in view
   *          {Array<Object>} days: Data for days
   *          {
   *            {Date} dateObj
   *            {Number} content
   *            {Array<String>} classNames
   *            {Boolean} enabled
   *          }
   *          {Array<Object>} weekHeaders: Data for weekHeaders
   *          {
   *            {Number} content
   *            {Array<String>} classNames
   *          }
   *        }
   */
  setProps(props) {
    if (props.isVisible) {
      // Transform the days and weekHeaders array for rendering
      const days = props.days.map(
        ({ dateObj, content, classNames, enabled }) => {
          return {
            dateObj,
            textContent: this.state.getDayString(content),
            className: classNames.join(" "),
            enabled,
          };
        }
      );
      const weekHeaders = props.weekHeaders.map(({ content, classNames }) => {
        return {
          textContent: this.state.getWeekHeaderString(content),
          className: classNames.join(" "),
        };
      });
      // Update the DOM nodes states
      this._render({
        elements: this.elements.daysView,
        items: days,
        prevState: this.state.days,
      });
      this._render({
        elements: this.elements.weekHeaders,
        items: weekHeaders,
        prevState: this.state.weekHeaders,
      });
      // Update the state to current and place keyboard focus
      this.state.days = days;
      this.state.weekHeaders = weekHeaders;
      this.focusDay();
    }
  },

  /**
   * Render the items onto the DOM nodes
   * @param  {Object}
   *         {
   *           {Array<DOMElement>} elements
   *           {Array<Object>} items
   *           {Array<Object>} prevState: state of items from last render
   *         }
   */
  _render({ elements, items, prevState }) {
    let selected = {};
    let today = {};
    let sameDay = {};
    let firstDay = {};

    for (let i = 0, l = items.length; i < l; i++) {
      let el = elements[i];

      // Check if state from last render has changed, if so, update the elements
      if (!prevState[i] || prevState[i].textContent != items[i].textContent) {
        el.textContent = items[i].textContent;
      }
      if (!prevState[i] || prevState[i].className != items[i].className) {
        el.className = items[i].className;
      }

      if (el.tagName === "td") {
        el.setAttribute("role", "gridcell");

        // Flush states from the previous view
        el.removeAttribute("tabindex");
        el.removeAttribute("aria-disabled");
        el.removeAttribute("aria-selected");
        el.removeAttribute("aria-current");

        // Set new states and properties
        if (
          this.state.focusedDate &&
          this._isSameDayOfMonth(items[i].dateObj, this.state.focusedDate) &&
          !el.classList.contains("outside")
        ) {
          // When any other date was focused previously, send the focus
          // to the same day of month, but only within the current month
          sameDay.el = el;
          sameDay.dateObj = items[i].dateObj;
        }
        if (el.classList.contains("today")) {
          // Current date/today is communicated to assistive technology
          el.setAttribute("aria-current", "date");
          if (!el.classList.contains("outside")) {
            today.el = el;
            today.dateObj = items[i].dateObj;
          }
        }
        if (el.classList.contains("selection")) {
          // Selection is communicated to assistive technology
          // and may be included in the focus order when from the current month
          el.setAttribute("aria-selected", "true");

          if (!el.classList.contains("outside")) {
            selected.el = el;
            selected.dateObj = items[i].dateObj;
          }
        } else if (el.classList.contains("out-of-range")) {
          // Dates that are outside of the range are not selected and cannot be
          el.setAttribute("aria-disabled", "true");
          el.removeAttribute("aria-selected");
        } else {
          // Other dates are not selected, but could be
          el.setAttribute("aria-selected", "false");
        }
        if (el.textContent === "1" && !firstDay.el) {
          // When no previous day, no selection, or no current day/today
          // is present, make the first of the month focusable
          firstDay.dateObj = items[i].dateObj;
          firstDay.dateObj.setUTCDate("1");

          if (this._isSameDay(items[i].dateObj, firstDay.dateObj)) {
            firstDay.el = el;
            firstDay.dateObj = items[i].dateObj;
          }
        }
      }
    }

    // The previously focused date (if the picker is updated and the grid still
    // contains the date) is always focusable. The selected date on init is also
    // always focusable. If neither exist, we make the current day or the first
    // day of the month focusable.
    if (sameDay.el) {
      sameDay.el.setAttribute("tabindex", "0");
      this.state.focusedDate = new Date(sameDay.dateObj);
    } else if (selected.el) {
      selected.el.setAttribute("tabindex", "0");
      this.state.focusedDate = new Date(selected.dateObj);
    } else if (today.el) {
      today.el.setAttribute("tabindex", "0");
      this.state.focusedDate = new Date(today.dateObj);
    } else if (firstDay.el) {
      firstDay.el.setAttribute("tabindex", "0");
      this.state.focusedDate = new Date(firstDay.dateObj);
    }
  },

  /**
   * Generate DOM nodes with HTML table markup
   *
   * @param  {Number} size: Number of nodes to generate
   * @param  {DOMElement} context: Element to append the nodes to
   * @return {Array<DOMElement>}
   */
  _generateNodes(size, context) {
    let frag = document.createDocumentFragment();
    let refs = [];

    // Create table row to present a week:
    let rowEl = document.createElement("tr");
    for (let i = 0; i < size; i++) {
      // Create table cell for a table header (weekday) or body (date)
      let el;
      if (context.classList.contains("week-header")) {
        el = document.createElement("th");
        el.setAttribute("scope", "col");
        // Explicitly assigning the role as a workaround for the bug 1711273:
        el.setAttribute("role", "columnheader");
      } else {
        el = document.createElement("td");
      }

      el.dataset.id = i;
      refs.push(el);
      rowEl.appendChild(el);

      // Ensure each table row (week) has only
      // seven table cells (days) for a Gregorian calendar
      if ((i + 1) % this.context.DAYS_IN_A_WEEK === 0) {
        frag.appendChild(rowEl);
        rowEl = document.createElement("tr");
      }
    }
    context.appendChild(frag);

    return refs;
  },

  /**
   * Handle events
   * @param  {DOMEvent} event
   */
  handleEvent(event) {
    switch (event.type) {
      case "click": {
        if (this.context.daysView.contains(event.target)) {
          let targetId = event.target.dataset.id;
          let targetObj = this.state.days[targetId];
          if (targetObj.enabled) {
            this.state.setSelection(targetObj.dateObj);
          }
        }
        break;
      }

      case "keydown": {
        // Providing keyboard navigation support in accordance with
        // the ARIA Grid and Dialog design patterns
        if (this.context.daysView.contains(event.target)) {
          // If RTL, the offset direction for Right/Left needs to be reversed
          const direction = Services.locale.isAppLocaleRTL ? -1 : 1;

          switch (event.key) {
            case "Enter":
            case " ": {
              let targetId = event.target.dataset.id;
              let targetObj = this.state.days[targetId];
              if (targetObj.enabled) {
                this.state.setSelection(targetObj.dateObj);
              }
              break;
            }

            case "ArrowRight": {
              // Moves focus to the next day. If the next day is
              // out-of-range, update the view to show the next month
              this._handleKeydownEvent(1 * direction);
              break;
            }
            case "ArrowLeft": {
              // Moves focus to the previous day. If the next day is
              // out-of-range, update the view to show the previous month
              this._handleKeydownEvent(-1 * direction);
              break;
            }
            case "ArrowUp": {
              // Moves focus to the same day of the previous week. If the next
              // day is out-of-range, update the view to show the previous month
              this._handleKeydownEvent(-1 * this.context.DAYS_IN_A_WEEK);
              break;
            }
            case "ArrowDown": {
              // Moves focus to the same day of the next week. If the next
              // day is out-of-range, update the view to show the previous month
              this._handleKeydownEvent(1 * this.context.DAYS_IN_A_WEEK);
              break;
            }
            case "Home": {
              // Moves focus to the first day (ie. Sunday) of the current week
              if (event.ctrlKey) {
                // Moves focus to the first day of the current month
                this.state.focusedDate.setUTCDate(1);
                this._updateKeyboardFocus();
              } else {
                this._handleKeydownEvent(
                  this.state.focusedDate.getUTCDay() * -1
                );
              }
              break;
            }
            case "End": {
              // Moves focus to the last day (ie. Saturday) of the current week
              if (event.ctrlKey) {
                // Moves focus to the last day of the current month
                let lastDateOfMonth = new Date(
                  this.state.focusedDate.getUTCFullYear(),
                  this.state.focusedDate.getUTCMonth() + 1,
                  0
                );
                this.state.focusedDate = lastDateOfMonth;
                this._updateKeyboardFocus();
              } else {
                this._handleKeydownEvent(
                  this.context.DAYS_IN_A_WEEK -
                    1 -
                    this.state.focusedDate.getUTCDay()
                );
              }
              break;
            }
            case "PageUp": {
              // Changes the view to the previous month/year
              // and sets focus on the same day.
              // If that day does not exist, then moves focus
              // to the same day of the same week.
              if (event.shiftKey) {
                // Previous year
                let prevYear = this.state.focusedDate.getUTCFullYear() - 1;
                this.state.focusedDate.setUTCFullYear(prevYear);
              } else {
                // Previous month
                let prevMonth = this.state.focusedDate.getUTCMonth() - 1;
                this.state.focusedDate.setUTCMonth(prevMonth);
              }
              this.state.setCalendarMonth(
                this.state.focusedDate.getUTCFullYear(),
                this.state.focusedDate.getUTCMonth()
              );
              this._updateKeyboardFocus();
              break;
            }
            case "PageDown": {
              // Changes the view to the next month/year
              // and sets focus on the same day.
              // If that day does not exist, then moves focus
              // to the same day of the same week.
              if (event.shiftKey) {
                // Next year
                let nextYear = this.state.focusedDate.getUTCFullYear() + 1;
                this.state.focusedDate.setUTCFullYear(nextYear);
              } else {
                // Next month
                let nextMonth = this.state.focusedDate.getUTCMonth() + 1;
                this.state.focusedDate.setUTCMonth(nextMonth);
              }
              this.state.setCalendarMonth(
                this.state.focusedDate.getUTCFullYear(),
                this.state.focusedDate.getUTCMonth()
              );
              this._updateKeyboardFocus();
              break;
            }
          }
        }
        break;
      }
    }
  },

  /**
   * Attach event listener to daysView
   */
  _attachEventListeners() {
    this.context.daysView.addEventListener("click", this);
    this.context.daysView.addEventListener("keydown", this);
  },

  /**
   * Find Data-id of the next element to focus on the daysView grid
   * @param {Object} nextDate: Data object of the next element to focus
   */
  _calculateNextId(nextDate) {
    for (let i = 0; i < this.state.days.length; i++) {
      if (this._isSameDay(this.state.days[i].dateObj, nextDate)) {
        return i;
      }
    }
    return null;
  },

  /**
   * Comparing two date objects to ensure they produce the same date
   * @param  {Date} dateObj1: Date object from the updated state
   * @param  {Date} dateObj2: Date object from the previous state
   * @return {Boolean} If two date objects are the same day
   */
  _isSameDay(dateObj1, dateObj2) {
    return (
      dateObj1.getUTCFullYear() == dateObj2.getUTCFullYear() &&
      dateObj1.getUTCMonth() == dateObj2.getUTCMonth() &&
      dateObj1.getUTCDate() == dateObj2.getUTCDate()
    );
  },

  /**
   * Comparing two date objects to ensure they produce the same day of the month,
   * while being on different months
   * @param  {Date} dateObj1: Date object from the updated state
   * @param  {Date} dateObj2: Date object from the previous state
   * @return {Boolean} If two date objects are the same day of the month
   */
  _isSameDayOfMonth(dateObj1, dateObj2) {
    return dateObj1.getUTCDate() == dateObj2.getUTCDate();
  },

  /**
   * Manage focus for the keyboard navigation for the daysView grid
   * @param  {Number} offsetDays: The direction and the number of days to move
   *                            the focus by, where a negative number (i.e. -1)
   *                            moves the focus to the previous day
   */
  _handleKeydownEvent(offsetDays) {
    let newFocusedDay = this.state.focusedDate.getUTCDate() + offsetDays;
    let newFocusedDate = new Date(this.state.focusedDate);
    newFocusedDate.setUTCDate(newFocusedDay);

    // Update the month, if the next focused element is outside
    if (newFocusedDate.getUTCMonth() !== this.state.focusedDate.getUTCMonth()) {
      this.state.setCalendarMonth(
        newFocusedDate.getUTCFullYear(),
        newFocusedDate.getUTCMonth()
      );
    }
    this.state.focusedDate.setUTCDate(newFocusedDate.getUTCDate());
    this._updateKeyboardFocus();
  },

  /**
   * Update the daysView grid and send focus to the next day
   * based on the current state fo the Calendar
   */
  _updateKeyboardFocus() {
    this._render({
      elements: this.elements.daysView,
      items: this.state.days,
      prevState: this.state.days,
    });
    this.focusDay();
  },

  /**
   * Place keyboard focus on the calendar grid, when the datepicker is initiated or updated.
   * A "tabindex" attribute is provided to only one date within the grid
   * by the "render()" method and this focusable element will be focused.
   */
  focusDay() {
    const focusable = this.context.daysView.querySelector('[tabindex="0"]');
    if (focusable) {
      focusable.focus();
    }
  },
};
PK
!<j j��/�/4chrome/toolkit/content/global/bindings/datekeeper.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * DateKeeper keeps track of the date states.
 */
function DateKeeper(props) {
  this.init(props);
}

{
  const DAYS_IN_A_WEEK = 7,
    MONTHS_IN_A_YEAR = 12,
    YEAR_VIEW_SIZE = 200,
    YEAR_BUFFER_SIZE = 10,
    // The min value is 0001-01-01 based on HTML spec:
    // https://html.spec.whatwg.org/#valid-date-string
    MIN_DATE = -62135596800000,
    // The max value is derived from the ECMAScript spec (275760-09-13):
    // http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.1
    MAX_DATE = 8640000000000000,
    MAX_YEAR = 275760,
    MAX_MONTH = 9,
    // One day in ms since epoch.
    ONE_DAY = 86400000;

  DateKeeper.prototype = {
    get year() {
      return this.state.dateObj.getUTCFullYear();
    },

    get month() {
      return this.state.dateObj.getUTCMonth();
    },

    get selection() {
      return this.state.selection;
    },

    /**
     * Initialize DateKeeper
     * @param  {Number} year
     * @param  {Number} month
     * @param  {Number} day
     * @param  {Number} min
     * @param  {Number} max
     * @param  {Number} step
     * @param  {Number} stepBase
     * @param  {Number} firstDayOfWeek
     * @param  {Array<Number>} weekends
     * @param  {Number} calViewSize
     */
    init({
      year,
      month,
      day,
      min,
      max,
      step,
      stepBase,
      firstDayOfWeek = 0,
      weekends = [0],
      calViewSize = 42,
    }) {
      const today = new Date();

      this.state = {
        step,
        firstDayOfWeek,
        weekends,
        calViewSize,
        // min & max are NaN if empty or invalid
        min: new Date(Number.isNaN(min) ? MIN_DATE : min),
        max: new Date(Number.isNaN(max) ? MAX_DATE : max),
        stepBase: new Date(stepBase),
        today: this._newUTCDate(
          today.getFullYear(),
          today.getMonth(),
          today.getDate()
        ),
        weekHeaders: this._getWeekHeaders(firstDayOfWeek, weekends),
        years: [],
        dateObj: new Date(0),
        selection: { year, month, day },
      };

      if (year === undefined) {
        year = today.getFullYear();
      }
      if (month === undefined) {
        month = today.getMonth();
      }

      const minYear = this.state.min.getFullYear();
      const maxYear = this.state.max.getFullYear();

      // Choose a valid year for the value/min/max properties
      const selectedYear = Math.min(Math.max(year, minYear), maxYear);

      // Choose the month that correspond to the selectedYear
      let selectedMonth = 0;

      if (selectedYear === year) {
        selectedMonth = month;
      } else if (selectedYear === minYear) {
        selectedMonth = this.state.min.getMonth();
      } else if (selectedYear === maxYear) {
        selectedMonth = this.state.max.getMonth();
      }

      this.setCalendarMonth({
        year: selectedYear,
        month: selectedMonth,
      });
    },

    /**
     * Set new calendar month. The year is always treated as full year, so the
     * short-form is not supported.
     * @param {Object} date parts
     *        {
     *          {Number} year [optional]
     *          {Number} month [optional]
     *        }
     */
    setCalendarMonth({ year = this.year, month = this.month }) {
      // Make sure the date is valid before setting.
      // Use setUTCFullYear so that year 99 doesn't get parsed as 1999
      if (year > MAX_YEAR || (year === MAX_YEAR && month >= MAX_MONTH)) {
        this.state.dateObj.setUTCFullYear(MAX_YEAR, MAX_MONTH - 1, 1);
      } else if (year < 1 || (year === 1 && month < 0)) {
        this.state.dateObj.setUTCFullYear(1, 0, 1);
      } else {
        this.state.dateObj.setUTCFullYear(year, month, 1);
      }
    },

    /**
     * Set selection date
     * @param {Number} year
     * @param {Number} month
     * @param {Number} day
     */
    setSelection({ year, month, day }) {
      this.state.selection.year = year;
      this.state.selection.month = month;
      this.state.selection.day = day;
    },

    /**
     * Set month. Makes sure the day is <= the last day of the month
     * @param {Number} month
     */
    setMonth(month) {
      this.setCalendarMonth({ year: this.year, month });
    },

    /**
     * Set year. Makes sure the day is <= the last day of the month
     * @param {Number} year
     */
    setYear(year) {
      this.setCalendarMonth({ year, month: this.month });
    },

    /**
     * Set month by offset. Makes sure the day is <= the last day of the month
     * @param {Number} offset
     */
    setMonthByOffset(offset) {
      this.setCalendarMonth({ year: this.year, month: this.month + offset });
    },

    /**
     * Generate the array of months
     * @return {Array<Object>}
     *         {
     *           {Number} value: Month in int
     *           {Boolean} enabled
     *         }
     */
    getMonths() {
      let months = [];

      const currentYear = this.year;

      const minYear = this.state.min.getFullYear();
      const minMonth = this.state.min.getMonth();
      const maxYear = this.state.max.getFullYear();
      const maxMonth = this.state.max.getMonth();

      for (let i = 0; i < MONTHS_IN_A_YEAR; i++) {
        const disabled =
          (currentYear == minYear && i < minMonth) ||
          (currentYear == maxYear && i > maxMonth);
        months.push({
          value: i,
          enabled: !disabled,
        });
      }

      return months;
    },

    /**
     * Generate the array of years
     * @return {Array<Object>}
     *         {
     *           {Number} value: Year in int
     *           {Boolean} enabled
     *         }
     */
    getYears() {
      let years = [];

      const firstItem = this.state.years[0];
      const lastItem = this.state.years[this.state.years.length - 1];
      const currentYear = this.year;

      const minYear = Math.max(this.state.min.getFullYear(), 1);
      const maxYear = Math.min(this.state.max.getFullYear(), MAX_YEAR);

      // Generate new years array when the year is outside of the first &
      // last item range. If not, return the cached result.
      if (
        !firstItem ||
        !lastItem ||
        currentYear <= firstItem.value + YEAR_BUFFER_SIZE ||
        currentYear >= lastItem.value - YEAR_BUFFER_SIZE
      ) {
        // The year is set in the middle with items on both directions
        for (let i = -(YEAR_VIEW_SIZE / 2); i < YEAR_VIEW_SIZE / 2; i++) {
          const year = currentYear + i;

          if (year >= minYear && year <= maxYear) {
            years.push({
              value: year,
              enabled: true,
            });
          }
        }
        this.state.years = years;
      }
      return this.state.years;
    },

    /**
     * Get days for calendar
     * @return {Array<Object>}
     *         {
     *           {Date} dateObj
     *           {Number} content
     *           {Array<String>} classNames
     *           {Boolean} enabled
     *         }
     */
    getDays() {
      const firstDayOfMonth = this._getFirstCalendarDate(
        this.state.dateObj,
        this.state.firstDayOfWeek
      );
      const month = this.month;
      let days = [];

      for (let i = 0; i < this.state.calViewSize; i++) {
        const dateObj = this._newUTCDate(
          firstDayOfMonth.getUTCFullYear(),
          firstDayOfMonth.getUTCMonth(),
          firstDayOfMonth.getUTCDate() + i
        );

        let classNames = [];
        let enabled = true;

        const isValid =
          dateObj.getTime() >= MIN_DATE && dateObj.getTime() <= MAX_DATE;
        if (!isValid) {
          classNames.push("out-of-range");
          enabled = false;

          days.push({
            classNames,
            enabled,
          });
          continue;
        }

        const isWeekend = this.state.weekends.includes(dateObj.getUTCDay());
        const isCurrentMonth = month == dateObj.getUTCMonth();
        const isSelection =
          this.state.selection.year == dateObj.getUTCFullYear() &&
          this.state.selection.month == dateObj.getUTCMonth() &&
          this.state.selection.day == dateObj.getUTCDate();
        // The date is at 00:00, so if the minimum is that day at e.g. 01:00,
        // we should arguably still be able to select that date. So we need to
        // compare the date at the very end of the day for minimum purposes.
        const isOutOfRange =
          dateObj.getTime() + ONE_DAY - 1 < this.state.min.getTime() ||
          dateObj.getTime() > this.state.max.getTime();
        const isToday = this.state.today.getTime() == dateObj.getTime();
        const isOffStep = this._checkIsOffStep(
          dateObj,
          this._newUTCDate(
            dateObj.getUTCFullYear(),
            dateObj.getUTCMonth(),
            dateObj.getUTCDate() + 1
          )
        );

        if (isWeekend) {
          classNames.push("weekend");
        }
        if (!isCurrentMonth) {
          classNames.push("outside");
        }
        if (isSelection && !isOutOfRange && !isOffStep) {
          classNames.push("selection");
        }
        if (isOutOfRange) {
          classNames.push("out-of-range");
          enabled = false;
        }
        if (isToday) {
          classNames.push("today");
        }
        if (isOffStep) {
          classNames.push("off-step");
          enabled = false;
        }
        days.push({
          dateObj,
          content: dateObj.getUTCDate(),
          classNames,
          enabled,
        });
      }
      return days;
    },

    /**
     * Check if a date is off step given a starting point and the next increment
     * @param  {Date} start
     * @param  {Date} next
     * @return {Boolean}
     */
    _checkIsOffStep(start, next) {
      // If the increment is larger or equal to the step, it must not be off-step.
      if (next - start >= this.state.step) {
        return false;
      }
      // Calculate the last valid date
      const lastValidStep = Math.floor(
        (next - 1 - this.state.stepBase) / this.state.step
      );
      const lastValidTimeInMs =
        lastValidStep * this.state.step + this.state.stepBase.getTime();
      // The date is off-step if the last valid date is smaller than the start date
      return lastValidTimeInMs < start.getTime();
    },

    /**
     * Get week headers for calendar
     * @param  {Number} firstDayOfWeek
     * @param  {Array<Number>} weekends
     * @return {Array<Object>}
     *         {
     *           {Number} content
     *           {Array<String>} classNames
     *         }
     */
    _getWeekHeaders(firstDayOfWeek, weekends) {
      let headers = [];
      let dayOfWeek = firstDayOfWeek;

      for (let i = 0; i < DAYS_IN_A_WEEK; i++) {
        headers.push({
          content: dayOfWeek % DAYS_IN_A_WEEK,
          classNames: weekends.includes(dayOfWeek % DAYS_IN_A_WEEK)
            ? ["weekend"]
            : [],
        });
        dayOfWeek++;
      }
      return headers;
    },

    /**
     * Get the first day on a calendar month
     * @param  {Date} dateObj
     * @param  {Number} firstDayOfWeek
     * @return {Date}
     */
    _getFirstCalendarDate(dateObj, firstDayOfWeek) {
      const daysOffset = 1 - DAYS_IN_A_WEEK;
      let firstDayOfMonth = this._newUTCDate(
        dateObj.getUTCFullYear(),
        dateObj.getUTCMonth()
      );
      let dayOfWeek = firstDayOfMonth.getUTCDay();

      return this._newUTCDate(
        firstDayOfMonth.getUTCFullYear(),
        firstDayOfMonth.getUTCMonth(),
        // When first calendar date is the same as first day of the week, add
        // another row on top of it.
        firstDayOfWeek == dayOfWeek
          ? daysOffset
          : (firstDayOfWeek - dayOfWeek + daysOffset) % DAYS_IN_A_WEEK
      );
    },

    /**
     * Helper function for creating UTC dates
     * @param  {...[Number]} parts
     * @return {Date}
     */
    _newUTCDate(...parts) {
      return new Date(new Date(0).setUTCFullYear(...parts));
    },
  };
}
PK
!<pӏ!F!F4chrome/toolkit/content/global/bindings/datepicker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* import-globals-from datekeeper.js */
/* import-globals-from calendar.js */
/* import-globals-from spinner.js */

"use strict";

function DatePicker(context) {
  this.context = context;
  this._attachEventListeners();
}

{
  const CAL_VIEW_SIZE = 42;

  DatePicker.prototype = {
    /**
     * Initializes the date picker. Set the default states and properties.
     * @param  {Object} props
     *         {
     *           {Number} year [optional]
     *           {Number} month [optional]
     *           {Number} date [optional]
     *           {Number} min
     *           {Number} max
     *           {Number} step
     *           {Number} stepBase
     *           {Number} firstDayOfWeek
     *           {Array<Number>} weekends
     *           {Array<String>} monthStrings
     *           {Array<String>} weekdayStrings
     *           {String} locale [optional]: User preferred locale
     *         }
     */
    init(props = {}) {
      this.props = props;
      this._setDefaultState();
      this._createComponents();
      this._update();
      this.components.calendar.focusDay();
      // TODO(bug 1828721): This is a bit sad.
      window.PICKER_READY = true;
      document.dispatchEvent(new CustomEvent("PickerReady"));
    },

    /*
     * Set initial date picker states.
     */
    _setDefaultState() {
      const {
        year,
        month,
        day,
        min,
        max,
        step,
        stepBase,
        firstDayOfWeek,
        weekends,
        monthStrings,
        weekdayStrings,
        locale,
        dir,
      } = this.props;
      const dateKeeper = new DateKeeper({
        year,
        month,
        day,
        min,
        max,
        step,
        stepBase,
        firstDayOfWeek,
        weekends,
        calViewSize: CAL_VIEW_SIZE,
      });

      document.dir = dir;

      this.state = {
        dateKeeper,
        locale,
        isMonthPickerVisible: false,
        datetimeOrders: new Intl.DateTimeFormat(locale)
          .formatToParts(new Date(0))
          .map(part => part.type),
        getDayString: day =>
          day ? new Intl.NumberFormat(locale).format(day) : "",
        getWeekHeaderString: weekday => weekdayStrings[weekday],
        getMonthString: month => monthStrings[month],
        setSelection: date => {
          dateKeeper.setSelection({
            year: date.getUTCFullYear(),
            month: date.getUTCMonth(),
            day: date.getUTCDate(),
          });
          this._update();
          this._dispatchState();
          this._closePopup();
        },
        setMonthByOffset: offset => {
          dateKeeper.setMonthByOffset(offset);
          this._update();
        },
        setYear: year => {
          dateKeeper.setYear(year);
          dateKeeper.setSelection({
            year,
            month: dateKeeper.selection.month,
            day: dateKeeper.selection.day,
          });
          this._update();
          this._dispatchState();
        },
        setMonth: month => {
          dateKeeper.setMonth(month);
          dateKeeper.setSelection({
            year: dateKeeper.selection.year,
            month,
            day: dateKeeper.selection.day,
          });
          this._update();
          this._dispatchState();
        },
        toggleMonthPicker: () => {
          this.state.isMonthPickerVisible = !this.state.isMonthPickerVisible;
          this._update();
        },
      };
    },

    /**
     * Initalize the date picker components.
     */
    _createComponents() {
      this.components = {
        calendar: new Calendar(
          {
            calViewSize: CAL_VIEW_SIZE,
            locale: this.state.locale,
            setSelection: this.state.setSelection,
            // Year and month could be changed without changing a selection
            setCalendarMonth: (year, month) => {
              this.state.dateKeeper.setCalendarMonth({
                year,
                month,
              });
              this._update();
            },
            getDayString: this.state.getDayString,
            getWeekHeaderString: this.state.getWeekHeaderString,
          },
          {
            weekHeader: this.context.weekHeader,
            daysView: this.context.daysView,
          }
        ),
        monthYear: new MonthYear(
          {
            setYear: this.state.setYear,
            setMonth: this.state.setMonth,
            getMonthString: this.state.getMonthString,
            datetimeOrders: this.state.datetimeOrders,
            locale: this.state.locale,
          },
          {
            monthYear: this.context.monthYear,
            monthYearView: this.context.monthYearView,
          }
        ),
      };
    },

    /**
     * Update date picker and its components.
     */
    _update(options = {}) {
      const { dateKeeper, isMonthPickerVisible } = this.state;

      const calendarEls = [
        this.context.buttonPrev,
        this.context.buttonNext,
        this.context.weekHeader.parentNode,
        this.context.buttonClear,
      ];
      // Update MonthYear state and toggle visibility for sighted users
      // and for assistive technology:
      this.context.monthYearView.hidden = !isMonthPickerVisible;
      for (let el of calendarEls) {
        el.hidden = isMonthPickerVisible;
      }
      this.context.monthYearNav.toggleAttribute(
        "monthPickerVisible",
        isMonthPickerVisible
      );
      if (isMonthPickerVisible) {
        this.state.months = dateKeeper.getMonths();
        this.state.years = dateKeeper.getYears();
      } else {
        this.state.days = dateKeeper.getDays();
      }

      this.components.monthYear.setProps({
        isVisible: isMonthPickerVisible,
        dateObj: dateKeeper.state.dateObj,
        months: this.state.months,
        years: this.state.years,
        toggleMonthPicker: this.state.toggleMonthPicker,
        noSmoothScroll: options.noSmoothScroll,
      });
      this.components.calendar.setProps({
        isVisible: !isMonthPickerVisible,
        days: this.state.days,
        weekHeaders: dateKeeper.state.weekHeaders,
      });
    },

    /**
     * Use postMessage to close the picker.
     */
    _closePopup(clear = false) {
      window.postMessage(
        {
          name: "ClosePopup",
          detail: clear,
        },
        "*"
      );
    },

    /**
     * Use postMessage to pass the state of picker to the panel.
     */
    _dispatchState() {
      const { year, month, day } = this.state.dateKeeper.selection;

      // The panel is listening to window for postMessage event, so we
      // do postMessage to itself to send data to input boxes.
      window.postMessage(
        {
          name: "PickerPopupChanged",
          detail: {
            year,
            month,
            day,
          },
        },
        "*"
      );
    },

    /**
     * Attach event listeners
     */
    _attachEventListeners() {
      window.addEventListener("message", this);
      document.addEventListener("mouseup", this, { passive: true });
      document.addEventListener("pointerdown", this, { passive: true });
      document.addEventListener("mousedown", this);
      document.addEventListener("keydown", this);
    },

    /**
     * Handle events.
     *
     * @param  {Event} event
     */
    handleEvent(event) {
      switch (event.type) {
        case "message": {
          this.handleMessage(event);
          break;
        }
        case "keydown": {
          switch (event.key) {
            case "Enter":
            case " ":
            case "Escape": {
              // If the target is a toggle or a spinner on the month-year panel
              const isOnMonthPicker =
                this.context.monthYearView.parentNode.contains(event.target);

              if (this.state.isMonthPickerVisible && isOnMonthPicker) {
                // While a control on the month-year picker panel is focused,
                // keep the spinner's selection and close the month-year dialog
                event.stopPropagation();
                event.preventDefault();
                this.state.toggleMonthPicker();
                this.components.calendar.focusDay();
                break;
              }
              if (event.key == "Escape") {
                // Close the date picker on Escape from within the picker
                this._closePopup();
                break;
              }
              if (event.target == this.context.buttonPrev) {
                event.target.classList.add("active");
                this.state.setMonthByOffset(-1);
                this.context.buttonPrev.focus();
              } else if (event.target == this.context.buttonNext) {
                event.target.classList.add("active");
                this.state.setMonthByOffset(1);
                this.context.buttonNext.focus();
              } else if (event.target == this.context.buttonClear) {
                event.target.classList.add("active");
                this._closePopup(/* clear = */ true);
              }
              break;
            }
            case "Tab": {
              // Manage tab order of a daysView to prevent keyboard trap
              if (event.target.tagName === "td") {
                if (event.shiftKey) {
                  this.context.buttonNext.focus();
                } else if (!event.shiftKey) {
                  this.context.buttonClear.focus();
                }
                event.stopPropagation();
                event.preventDefault();
              }
              break;
            }
          }
          break;
        }
        case "pointerdown": {
          if (event.pointerType == "mouse") {
            event.target.setPointerCapture(event.pointerId);
          }
          break;
        }
        case "mousedown": {
          // Use preventDefault to keep focus on input boxes
          event.preventDefault();

          if (event.target == this.context.buttonClear) {
            event.target.classList.add("active");
            this._closePopup(/* clear = */ true);
          } else if (event.target == this.context.buttonPrev) {
            event.target.classList.add("active");
            this.state.dateKeeper.setMonthByOffset(-1);
            this._update();
          } else if (event.target == this.context.buttonNext) {
            event.target.classList.add("active");
            this.state.dateKeeper.setMonthByOffset(1);
            this._update();
          }
          break;
        }
        case "mouseup": {
          event.target.releasePointerCapture(event.pointerId);

          if (
            event.target == this.context.buttonPrev ||
            event.target == this.context.buttonNext
          ) {
            event.target.classList.remove("active");
          }
          break;
        }
      }
    },

    /**
     * Handle postMessage events.
     *
     * @param {Event} event
     */
    handleMessage(event) {
      switch (event.data.name) {
        case "PickerSetValue": {
          this.set(event.data.detail);
          break;
        }
        case "PickerInit": {
          this.init(event.data.detail);
          break;
        }
      }
    },

    /**
     * Set the date state and update the components with the new state.
     *
     * @param {Object} dateState
     *        {
     *          {Number} year [optional]
     *          {Number} month [optional]
     *          {Number} date [optional]
     *        }
     */
    set({ year, month, day }) {
      if (!this.state) {
        return;
      }

      const { dateKeeper } = this.state;

      dateKeeper.setCalendarMonth({
        year,
        month,
      });
      dateKeeper.setSelection({
        year,
        month,
        day,
      });
      this._update({ noSmoothScroll: true });
    },
  };

  /**
   * MonthYear is a component that handles the month & year spinners
   *
   * @param {Object} options
   *        {
   *          {String} locale
   *          {Function} setYear
   *          {Function} setMonth
   *          {Function} getMonthString
   *          {Array<String>} datetimeOrders
   *        }
   * @param {DOMElement} context
   */
  function MonthYear(options, context) {
    const spinnerSize = 5;
    const yearFormat = new Intl.DateTimeFormat(options.locale, {
      year: "numeric",
      timeZone: "UTC",
    }).format;
    const dateFormat = new Intl.DateTimeFormat(options.locale, {
      year: "numeric",
      month: "long",
      timeZone: "UTC",
    }).format;
    const spinnerOrder =
      options.datetimeOrders.indexOf("month") <
      options.datetimeOrders.indexOf("year")
        ? "order-month-year"
        : "order-year-month";

    context.monthYearView.classList.add(spinnerOrder);

    this.context = context;
    this.state = { dateFormat };
    this.props = {};
    this.components = {
      month: new Spinner(
        {
          id: "spinner-month",
          setValue: month => {
            this.state.isMonthSet = true;
            options.setMonth(month);
          },
          getDisplayString: options.getMonthString,
          viewportSize: spinnerSize,
        },
        context.monthYearView
      ),
      year: new Spinner(
        {
          id: "spinner-year",
          setValue: year => {
            this.state.isYearSet = true;
            options.setYear(year);
          },
          getDisplayString: year =>
            yearFormat(new Date(new Date(0).setUTCFullYear(year))),
          viewportSize: spinnerSize,
        },
        context.monthYearView
      ),
    };

    this._updateButtonLabels();
    this._attachEventListeners();
  }

  MonthYear.prototype = {
    /**
     * Set new properties and pass them to components
     *
     * @param {Object} props
     *        {
     *          {Boolean} isVisible
     *          {Date} dateObj
     *          {Array<Object>} months
     *          {Array<Object>} years
     *          {Function} toggleMonthPicker
     *         }
     */
    setProps(props) {
      this.context.monthYear.textContent = this.state.dateFormat(props.dateObj);
      const spinnerDialog = this.context.monthYearView.parentNode;

      if (props.isVisible) {
        this.context.monthYear.classList.add("active");
        this.context.monthYear.setAttribute("aria-expanded", "true");
        // To prevent redundancy, as spinners will announce their value on change
        this.context.monthYear.setAttribute("aria-live", "off");
        this.components.month.setState({
          value: props.dateObj.getUTCMonth(),
          items: props.months,
          isInfiniteScroll: true,
          isValueSet: this.state.isMonthSet,
          smoothScroll: !(this.state.firstOpened || props.noSmoothScroll),
        });
        this.components.year.setState({
          value: props.dateObj.getUTCFullYear(),
          items: props.years,
          isInfiniteScroll: false,
          isValueSet: this.state.isYearSet,
          smoothScroll: !(this.state.firstOpened || props.noSmoothScroll),
        });
        this.state.firstOpened = false;

        // Set up spinner dialog container properties for assistive technology:
        spinnerDialog.setAttribute("role", "dialog");
        spinnerDialog.setAttribute("aria-modal", "true");
      } else {
        this.context.monthYear.classList.remove("active");
        this.context.monthYear.setAttribute("aria-expanded", "false");
        // To ensure calendar month's changes are announced:
        this.context.monthYear.setAttribute("aria-live", "polite");
        // Remove spinner dialog container properties to ensure this hidden
        // modal will be ignored by assistive technology, because even though
        // the dialog is hidden, the toggle button is a visible descendant,
        // so we must not treat its container as a dialog:
        spinnerDialog.removeAttribute("role");
        spinnerDialog.removeAttribute("aria-modal");
        this.state.isMonthSet = false;
        this.state.isYearSet = false;
        this.state.firstOpened = true;
      }

      this.props = Object.assign(this.props, props);
    },

    /**
     * Handle events
     * @param  {DOMEvent} event
     */
    handleEvent(event) {
      switch (event.type) {
        case "click": {
          this.props.toggleMonthPicker();
          break;
        }
        case "keydown": {
          if (event.key === "Enter" || event.key === " ") {
            event.stopPropagation();
            event.preventDefault();
            this.props.toggleMonthPicker();
          }
          break;
        }
      }
    },

    /**
     * Update localizable IDs of the spinner and its Prev/Next buttons
     */
    _updateButtonLabels() {
      document.l10n.setAttributes(
        this.components.month.elements.spinner,
        "date-spinner-month"
      );
      document.l10n.setAttributes(
        this.components.year.elements.spinner,
        "date-spinner-year"
      );
      document.l10n.setAttributes(
        this.components.month.elements.up,
        "date-spinner-month-previous"
      );
      document.l10n.setAttributes(
        this.components.month.elements.down,
        "date-spinner-month-next"
      );
      document.l10n.setAttributes(
        this.components.year.elements.up,
        "date-spinner-year-previous"
      );
      document.l10n.setAttributes(
        this.components.year.elements.down,
        "date-spinner-year-next"
      );
      document.l10n.translateRoots();
    },

    /**
     * Attach event listener to monthYear button
     */
    _attachEventListeners() {
      this.context.monthYear.addEventListener("click", this);
      this.context.monthYear.addEventListener("keydown", this);
    },
  };
}
PK
!<�G�\��6chrome/toolkit/content/global/bindings/datetimebox.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

.datetimebox {
  display: flex;
  line-height: normal;
  /* TODO: Enable selection once bug 1455893 is fixed */
  user-select: none;
}

.datetime-input-box-wrapper {
  display: inline-flex;
  flex: 1;
  background-color: inherit;
  min-width: 0;
  justify-content: space-between;
  align-items: center;
}

.datetime-input-edit-wrapper {
  overflow: hidden;
  white-space: nowrap;
  flex-grow: 1;
}

.datetime-edit-field {
  display: inline;
  text-align: center;
  padding: 1px 3px;
  border: 0;
  margin: 0;
  ime-mode: disabled;
  outline: none;

  &:focus {
    background-color: Highlight;
    color: HighlightText;
    outline: none;
  }
}

.datetime-calendar-button {
  -moz-context-properties: fill;
  color: inherit;
  font-size: inherit;
  fill: currentColor;
  opacity: .65;
  background-color: transparent;
  border: none;
  border-radius: 0.2em;
  flex: none;
  margin-block: 0;
  margin-inline: 0.075em 0.15em;
  padding: 0 0.15em;
  line-height: 1;

  &:focus-visible {
    outline: 0.15em solid SelectedItem;
  }

  &:focus-visible,
  &:hover {
    opacity: 1;
  }

  @media (prefers-contrast) {
    opacity: 1;
    background-color: ButtonFace;
    color: ButtonText;

    > .datetime-calendar-button-svg {
      background-color: ButtonFace;
      -moz-context-properties: fill;
      fill: ButtonText;
    }

    &:focus-visible,
    &:hover {
      background-color: SelectedItem;

      > .datetime-calendar-button-svg {
        background-color: SelectedItem;
        -moz-context-properties: fill;
        fill: SelectedItemText;
      }
    }
  }
}

.datetime-calendar-button-svg {
  pointer-events: none;
  /* When using a very small font-size, we don't want the button to take extra
   * space (which will affect the baseline of the form control) */
  max-width: 1em;
  max-height: 1em;
}

:host(:is(:disabled, :read-only, [type="time"])) .datetime-calendar-button {
  display: none;
}

:host(:is(:disabled, :read-only)) .datetime-edit-field {
  user-select: none;
  pointer-events: none;
  -moz-user-focus: none;
}
PK
!<��&+V+V1chrome/toolkit/content/global/bindings/spinner.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/*
 * The spinner is responsible for displaying the items, and does
 * not care what the values represent. The setValue function is called
 * when it detects a change in value triggered by scroll event.
 * Supports scrolling, clicking on up or down, clicking on item, and
 * dragging.
 */

function Spinner(props, context) {
  this.context = context;
  this._init(props);
}

{
  const ITEM_HEIGHT = 2.5,
    VIEWPORT_SIZE = 7,
    VIEWPORT_COUNT = 5;

  Spinner.prototype = {
    /**
     * Initializes a spinner. Set the default states and properties, cache
     * element references, create the HTML markup, and add event listeners.
     *
     * @param  {Object} props [Properties passed in from parent]
     *         {
     *           {Function} setValue: Takes a value and set the state to
     *             the parent component.
     *           {Function} getDisplayString: Takes a value, and output it
     *             as localized strings.
     *           {Number} viewportSize [optional]: Number of items in a
     *             viewport.
     *           {Boolean} hideButtons [optional]: Hide up & down buttons
     *           {Number} rootFontSize [optional]: Used to support zoom in/out
     *         }
     */
    _init(props) {
      const {
        id,
        setValue,
        getDisplayString,
        hideButtons,
        rootFontSize = 10,
      } = props;

      const spinnerTemplate = document.getElementById("spinner-template");
      const spinnerElement = document.importNode(spinnerTemplate.content, true);

      // Make sure viewportSize is an odd number because we want to have the selected
      // item in the center. If it's an even number, use the default size instead.
      const viewportSize =
        props.viewportSize % 2 ? props.viewportSize : VIEWPORT_SIZE;

      this.state = {
        items: [],
        isScrolling: false,
      };
      this.props = {
        setValue,
        getDisplayString,
        viewportSize,
        rootFontSize,
        // We can assume that the viewportSize is an odd number. Calculate how many
        // items we need to insert on top of the spinner so that the selected is at
        // the center. Ex: if viewportSize is 5, we need 2 items on top.
        viewportTopOffset: (viewportSize - 1) / 2,
      };
      this.elements = {
        container: spinnerElement.querySelector(".spinner-container"),
        spinner: spinnerElement.querySelector(".spinner"),
        up: spinnerElement.querySelector(".up"),
        down: spinnerElement.querySelector(".down"),
        itemsViewElements: [],
      };

      this.elements.spinner.style.height = ITEM_HEIGHT * viewportSize + "rem";

      // Prepares the spinner container to function as a spinbutton and expose
      // its properties to assistive technology
      this.elements.spinner.setAttribute("role", "spinbutton");
      this.elements.spinner.setAttribute("tabindex", "0");
      // Remove up/down buttons from the focus order, because a keyboard-only
      // user can adjust values by pressing Up/Down arrow keys on a spinbutton,
      // otherwise it creates extra, redundant tab order stops for users
      this.elements.up.setAttribute("tabindex", "-1");
      this.elements.down.setAttribute("tabindex", "-1");

      if (id) {
        this.elements.container.id = id;
      }
      if (hideButtons) {
        this.elements.container.classList.add("hide-buttons");
      }

      this.context.appendChild(spinnerElement);
      this._attachEventListeners();
    },

    /**
     * Only the parent component calls setState on the spinner.
     * It checks if the items have changed and updates the spinner.
     * If only the value has changed, smooth scrolls to the new value.
     *
     * @param {Object} newState [The new spinner state]
     *        {
     *          {Number/String} value: The centered value
     *          {Array} items: The list of items for display
     *          {Boolean} isInfiniteScroll: Whether or not the spinner should
     *            have infinite scroll capability
     *          {Boolean} isValueSet: true if user has selected a value
     *        }
     */
    setState(newState) {
      const { value, items } = this.state;
      const {
        value: newValue,
        items: newItems,
        isValueSet,
        isInvalid,
        smoothScroll = true,
      } = newState;

      if (this._isArrayDiff(newItems, items)) {
        this.state = Object.assign(this.state, newState);
        this._updateItems();
        this._scrollTo(newValue, /* centering = */ true, /* smooth = */ false);
      } else if (newValue != value) {
        this.state = Object.assign(this.state, newState);
        this._scrollTo(newValue, /* centering = */ true, smoothScroll);
      }

      this.elements.spinner.setAttribute(
        "aria-valuemin",
        this.state.items[0].value
      );
      this.elements.spinner.setAttribute(
        "aria-valuemax",
        this.state.items.at(-1).value
      );
      this.elements.spinner.setAttribute("aria-valuenow", this.state.value);
      if (!this.elements.spinner.getAttribute("aria-valuetext")) {
        this.elements.spinner.setAttribute(
          "aria-valuetext",
          this.props.getDisplayString(this.state.value)
        );
      }

      // Show selection even if it's passed down from the parent
      if ((isValueSet && !isInvalid) || this.state.index) {
        this._updateSelection();
      } else {
        this._removeSelection();
      }
    },

    /**
     * Whenever scroll event is detected:
     * - Update the index state
     * - If the value has changed, update the [value] state and call [setValue]
     * - If infinite scrolling is on, reset the scrolling position if necessary
     */
    _onScroll() {
      const { items, itemsView, isInfiniteScroll } = this.state;
      const { viewportSize, viewportTopOffset } = this.props;
      const { spinner } = this.elements;

      this.state.index = this._getIndexByOffset(spinner.scrollTop);

      const value = itemsView[this.state.index + viewportTopOffset].value;

      // Call setValue if value has changed
      if (this.state.value != value) {
        this.state.value = value;
        this.props.setValue(value);
      }

      // Do infinite scroll when items length is bigger or equal to viewport
      // and isInfiniteScroll is not false.
      if (items.length >= viewportSize && isInfiniteScroll) {
        // If the scroll position is near the top or bottom, jump back to the middle
        // so user can keep scrolling up or down.
        if (
          this.state.index < viewportSize ||
          this.state.index > itemsView.length - viewportSize
        ) {
          this._scrollTo(this.state.value, true);
        }
      }

      this.elements.spinner.classList.add("scrolling");
    },

    /**
     * Remove the "scrolling" state on scrollend.
     */
    _onScrollend() {
      this.elements.spinner.classList.remove("scrolling");
      this.elements.spinner.setAttribute(
        "aria-valuetext",
        this.props.getDisplayString(this.state.value)
      );
    },

    /**
     * Updates the spinner items to the current states.
     */
    _updateItems() {
      const { viewportSize, viewportTopOffset } = this.props;
      const { items, isInfiniteScroll } = this.state;

      // Prepends null elements so the selected value is centered in spinner
      let itemsView = new Array(viewportTopOffset).fill({}).concat(items);

      if (items.length >= viewportSize && isInfiniteScroll) {
        // To achieve infinite scroll, we move the scroll position back to the
        // center when it is near the top or bottom. The scroll momentum could
        // be lost in the process, so to minimize that, we need at least 2 sets
        // of items to act as buffer: one for the top and one for the bottom.
        // But if the number of items is small ( < viewportSize * viewport count)
        // we should add more sets.
        let count =
          Math.ceil((viewportSize * VIEWPORT_COUNT) / items.length) * 2;
        for (let i = 0; i < count; i += 1) {
          itemsView.push(...items);
        }
      }

      // Reuse existing DOM nodes when possible. Create or remove
      // nodes based on how big itemsView is.
      this._prepareNodes(itemsView.length, this.elements.spinner);
      // Once DOM nodes are ready, set display strings using textContent
      this._setDisplayStringAndClass(
        itemsView,
        this.elements.itemsViewElements
      );

      this.state.itemsView = itemsView;
    },

    /**
     * Make sure the number or child elements is the same as length
     * and keep the elements' references for updating textContent
     *
     * @param {Number} length [The number of child elements]
     * @param {DOMElement} parent [The parent element reference]
     */
    _prepareNodes(length, parent) {
      const diff = length - parent.childElementCount;

      if (!diff) {
        return;
      }

      if (diff > 0) {
        // Add more elements if length is greater than current
        let frag = document.createDocumentFragment();

        // Remove margin bottom on the last element before appending
        if (parent.lastChild) {
          parent.lastChild.style.marginBottom = "";
        }

        for (let i = 0; i < diff; i++) {
          let el = document.createElement("div");
          // Spinbutton elements should be hidden from assistive technology:
          el.setAttribute("aria-hidden", "true");
          frag.appendChild(el);
          this.elements.itemsViewElements.push(el);
        }
        parent.appendChild(frag);
      } else if (diff < 0) {
        // Remove elements if length is less than current
        for (let i = 0; i < Math.abs(diff); i++) {
          parent.removeChild(parent.lastChild);
        }
        this.elements.itemsViewElements.splice(diff);
      }

      parent.lastChild.style.marginBottom =
        ITEM_HEIGHT * this.props.viewportTopOffset + "rem";
    },

    /**
     * Set the display string and class name to the elements.
     *
     * @param {Array<Object>} items
     *        [{
     *          {Number/String} value: The value in its original form
     *          {Boolean} enabled: Whether or not the item is enabled
     *        }]
     * @param {Array<DOMElement>} elements
     */
    _setDisplayStringAndClass(items, elements) {
      const { getDisplayString } = this.props;

      items.forEach((item, index) => {
        elements[index].textContent =
          item.value != undefined ? getDisplayString(item.value) : "";
        elements[index].className = item.enabled ? "" : "disabled";
      });
    },

    /**
     * Attach event listeners to the spinner and buttons.
     */
    _attachEventListeners() {
      const { spinner, container } = this.elements;

      spinner.addEventListener("scroll", this, { passive: true });
      spinner.addEventListener("scrollend", this, { passive: true });
      spinner.addEventListener("keydown", this);
      container.addEventListener("mouseup", this, { passive: true });
      container.addEventListener("mousedown", this, { passive: true });
      container.addEventListener("keydown", this);
    },

    /**
     * Handle events
     * @param  {DOMEvent} event
     */
    handleEvent(event) {
      const { mouseState = {}, index, itemsView } = this.state;
      const { viewportTopOffset, setValue } = this.props;
      const { spinner, up, down } = this.elements;

      switch (event.type) {
        case "scroll": {
          this._onScroll();
          break;
        }
        case "scrollend": {
          this._onScrollend();
          break;
        }
        case "mousedown": {
          this.state.mouseState = {
            down: true,
            layerX: event.layerX,
            layerY: event.layerY,
          };
          if (event.target == up) {
            // An "active" class is needed to simulate :active pseudo-class
            // because element is not focused.
            event.target.classList.add("active");
            this._smoothScrollToIndex(index - 1);
          }
          if (event.target == down) {
            event.target.classList.add("active");
            this._smoothScrollToIndex(index + 1);
          }
          if (event.target.parentNode == spinner) {
            // Listen to dragging events
            spinner.addEventListener("mousemove", this, { passive: true });
            spinner.addEventListener("mouseleave", this, { passive: true });
          }
          break;
        }
        case "mouseup": {
          this.state.mouseState.down = false;
          if (event.target == up || event.target == down) {
            event.target.classList.remove("active");
          }
          if (event.target.parentNode == spinner) {
            // Check if user clicks or drags, scroll to the item if clicked,
            // otherwise get the current index and smooth scroll there.
            if (
              event.layerX == mouseState.layerX &&
              event.layerY == mouseState.layerY
            ) {
              const newIndex =
                this._getIndexByOffset(event.target.offsetTop) -
                viewportTopOffset;
              if (index == newIndex) {
                // Set value manually if the clicked element is already centered.
                // This happens when the picker first opens, and user pick the
                // default value.
                setValue(itemsView[index + viewportTopOffset].value);
              } else {
                this._smoothScrollToIndex(newIndex);
              }
            } else {
              this._smoothScrollToIndex(
                this._getIndexByOffset(spinner.scrollTop)
              );
            }
            // Stop listening to dragging
            spinner.removeEventListener("mousemove", this, { passive: true });
            spinner.removeEventListener("mouseleave", this, { passive: true });
          }
          break;
        }
        case "mouseleave": {
          if (event.target == spinner) {
            // Stop listening to drag event if mouse is out of the spinner
            this._smoothScrollToIndex(
              this._getIndexByOffset(spinner.scrollTop)
            );
            spinner.removeEventListener("mousemove", this, { passive: true });
            spinner.removeEventListener("mouseleave", this, { passive: true });
          }
          break;
        }
        case "mousemove": {
          // Change spinner position on drag
          spinner.scrollTop -= event.movementY;
          break;
        }
        case "keydown": {
          // Providing keyboard navigation support in accordance with
          // the ARIA Spinbutton design pattern
          if (event.target === spinner) {
            switch (event.key) {
              case "ArrowUp": {
                // While the spinner is focused, selects previous value and centers it
                this._setValueForSpinner(event, index - 1);
                break;
              }
              case "ArrowDown": {
                // While the spinner is focused, selects next value and centers it
                this._setValueForSpinner(event, index + 1);
                break;
              }
              case "PageUp": {
                // While the spinner is focused, selects 5th value above and centers it
                this._setValueForSpinner(event, index - 5);
                break;
              }
              case "PageDown": {
                // While the spinner is focused, selects 5th value below and centers it
                this._setValueForSpinner(event, index + 5);
                break;
              }
              case "Home": {
                // While the spinner is focused, selects the min value and centers it
                let targetValue;
                for (let i = 0; i < this.state.items.length - 1; i++) {
                  if (this.state.items[i].enabled) {
                    targetValue = this.state.items[i].value;
                    break;
                  }
                }
                this._smoothScrollTo(targetValue);
                event.stopPropagation();
                event.preventDefault();
                break;
              }
              case "End": {
                // While the spinner is focused, selects the max value and centers it
                let targetValue;
                for (let i = this.state.items.length - 1; i >= 0; i--) {
                  if (this.state.items[i].enabled) {
                    targetValue = this.state.items[i].value;
                    break;
                  }
                }
                this._smoothScrollTo(targetValue);
                event.stopPropagation();
                event.preventDefault();
                break;
              }
            }
          }
        }
      }
    },

    /**
     * Find the index by offset
     * @param {Number} offset: Offset value in pixel.
     * @return {Number}  Index number
     */
    _getIndexByOffset(offset) {
      return Math.round(offset / (ITEM_HEIGHT * this.props.rootFontSize));
    },

    /**
     * Find the index of a value that is the closest to the current position.
     * If centering is true, find the index closest to the center.
     *
     * @param {Number/String} value: The value to find
     * @param {Boolean} centering: Whether or not to find the value closest to center
     * @return {Number} index of the value, returns -1 if value is not found
     */
    _getScrollIndex(value, centering) {
      const { itemsView } = this.state;
      const { viewportTopOffset } = this.props;

      // If index doesn't exist, or centering is true, start from the middle point
      let currentIndex =
        centering || this.state.index == undefined
          ? Math.round((itemsView.length - viewportTopOffset) / 2)
          : this.state.index;
      let closestIndex = itemsView.length;
      let indexes = [];
      let diff = closestIndex;
      let isValueFound = false;

      // Find indexes of items match the value
      itemsView.forEach((item, index) => {
        if (item.value == value) {
          indexes.push(index);
        }
      });

      // Find the index closest to currentIndex
      indexes.forEach(index => {
        let d = Math.abs(index - currentIndex);
        if (d < diff) {
          diff = d;
          closestIndex = index;
          isValueFound = true;
        }
      });

      return isValueFound ? closestIndex - viewportTopOffset : -1;
    },

    /**
     * Scroll to a value based on the index
     *
     * @param  {Number} index: Index number
     * @param  {Boolean} smooth: Whether or not scroll should be smooth by default
     */
    _scrollToIndex(index, smooth) {
      // Do nothing if the value is not found
      if (index < 0) {
        return;
      }
      this.state.index = index;
      const element = this.elements.spinner.children[index];
      if (!element) {
        return;
      }
      element.scrollIntoView({
        behavior: smooth ? "auto" : "instant",
        block: "start",
      });
    },

    /**
     * Scroll to a value.
     *
     * @param  {Number/String} value: Value to scroll to
     * @param  {Boolean} centering: Whether or not to scroll to center location
     * @param  {Boolean} smooth: Whether or not scroll should be smooth by default
     */
    _scrollTo(value, centering, smooth) {
      const index = this._getScrollIndex(value, centering);
      this._scrollToIndex(index, smooth);
    },

    _smoothScrollTo(value) {
      this._scrollTo(value, /* centering = */ false, /* smooth = */ true);
    },

    _smoothScrollToIndex(index) {
      this._scrollToIndex(index, /* smooth = */ true);
    },

    /**
     * Update the selection state.
     */
    _updateSelection() {
      const { itemsViewElements, selected } = this.elements;
      const { itemsView, index } = this.state;
      const { viewportTopOffset } = this.props;
      const currentItemIndex = index + viewportTopOffset;

      if (selected && selected != itemsViewElements[currentItemIndex]) {
        this._removeSelection();
      }

      this.elements.selected = itemsViewElements[currentItemIndex];
      if (itemsView[currentItemIndex] && itemsView[currentItemIndex].enabled) {
        this.elements.selected.classList.add("selection");
      }
    },

    /**
     * Remove selection if selected exists and different from current
     */
    _removeSelection() {
      const { selected } = this.elements;

      if (selected) {
        selected.classList.remove("selection");
      }
    },

    /**
     * Compares arrays of objects. It assumes the structure is an array of
     * objects, and objects in a and b have the same number of properties.
     *
     * @param  {Array<Object>} a
     * @param  {Array<Object>} b
     * @return {Boolean}  Returns true if a and b are different
     */
    _isArrayDiff(a, b) {
      // Check reference first, exit early if reference is the same.
      if (a == b) {
        return false;
      }

      if (a.length != b.length) {
        return true;
      }

      for (let i = 0; i < a.length; i++) {
        for (let prop in a[i]) {
          if (a[i][prop] != b[i][prop]) {
            return true;
          }
        }
      }
      return false;
    },

    /**
     * While the spinner is focused and keyboard command is used, selects an
     * appropriate index and centers it, while preventing default behavior and
     * stopping event propagation.
     *
     * @param {Object} event: Keyboard event
     * @param {Number} index: The index of the expected next item
     */
    _setValueForSpinner(event, index) {
      this._smoothScrollToIndex(index);
      event.stopPropagation();
      event.preventDefault();
    },
  };
}
PK
!<�Mݛ/�/4chrome/toolkit/content/global/bindings/timekeeper.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * TimeKeeper keeps track of the time states. Given min, max, step, and
 * format (12/24hr), TimeKeeper will determine the ranges of possible
 * selections, and whether or not the current time state is out of range
 * or off step.
 *
 * @param {Object} props
 *        {
 *          {Date} min
 *          {Date} max
 *          {Number} step
 *          {String} format: Either "12" or "24"
 *        }
 */
function TimeKeeper(props) {
  this.props = props;
  this.state = { time: new Date(0), ranges: {} };
}

{
  const DAY_PERIOD_IN_HOURS = 12,
    SECOND_IN_MS = 1000,
    MINUTE_IN_MS = 60000,
    HOUR_IN_MS = 3600000,
    DAY_PERIOD_IN_MS = 43200000,
    DAY_IN_MS = 86400000,
    TIME_FORMAT_24 = "24";

  TimeKeeper.prototype = {
    /**
     * Getters for different time units.
     * @return {Number}
     */
    get hour() {
      return this.state.time.getUTCHours();
    },
    get minute() {
      return this.state.time.getUTCMinutes();
    },
    get second() {
      return this.state.time.getUTCSeconds();
    },
    get millisecond() {
      return this.state.time.getUTCMilliseconds();
    },
    get dayPeriod() {
      // 0 stands for AM and 12 for PM
      return this.state.time.getUTCHours() < DAY_PERIOD_IN_HOURS
        ? 0
        : DAY_PERIOD_IN_HOURS;
    },

    /**
     * Get the ranges of different time units.
     * @return {Object}
     *         {
     *           {Array<Number>} dayPeriod
     *           {Array<Number>} hours
     *           {Array<Number>} minutes
     *           {Array<Number>} seconds
     *           {Array<Number>} milliseconds
     *         }
     */
    get ranges() {
      return this.state.ranges;
    },

    /**
     * Set new time, check if the current state is valid, and set ranges.
     *
     * @param {Object} timeState: The new time
     *        {
     *          {Number} hour [optional]
     *          {Number} minute [optional]
     *          {Number} second [optional]
     *          {Number} millisecond [optional]
     *        }
     */
    setState(timeState) {
      const { min, max } = this.props;
      const { hour, minute, second, millisecond } = timeState;

      if (hour != undefined) {
        this.state.time.setUTCHours(hour);
      }
      if (minute != undefined) {
        this.state.time.setUTCMinutes(minute);
      }
      if (second != undefined) {
        this.state.time.setUTCSeconds(second);
      }
      if (millisecond != undefined) {
        this.state.time.setUTCMilliseconds(millisecond);
      }

      this.state.isOffStep = this._isOffStep(this.state.time);
      this.state.isOutOfRange = this.state.time < min || this.state.time > max;
      this.state.isInvalid = this.state.isOutOfRange || this.state.isOffStep;

      this._setRanges(this.dayPeriod, this.hour, this.minute, this.second);
    },

    /**
     * Set day-period (AM/PM)
     * @param {Number} dayPeriod: 0 as AM, 12 as PM
     */
    setDayPeriod(dayPeriod) {
      if (dayPeriod == this.dayPeriod) {
        return;
      }

      if (dayPeriod == 0) {
        this.setState({ hour: this.hour - DAY_PERIOD_IN_HOURS });
      } else {
        this.setState({ hour: this.hour + DAY_PERIOD_IN_HOURS });
      }
    },

    /**
     * Set hour in 24hr format (0 ~ 23)
     * @param {Number} hour
     */
    setHour(hour) {
      this.setState({ hour });
    },

    /**
     * Set minute (0 ~ 59)
     * @param {Number} minute
     */
    setMinute(minute) {
      this.setState({ minute });
    },

    /**
     * Set second (0 ~ 59)
     * @param {Number} second
     */
    setSecond(second) {
      this.setState({ second });
    },

    /**
     * Set millisecond (0 ~ 999)
     * @param {Number} millisecond
     */
    setMillisecond(millisecond) {
      this.setState({ millisecond });
    },

    /**
     * Calculate the range of possible choices for each time unit.
     * Reuse the old result if the input has not changed.
     *
     * @param {Number} dayPeriod
     * @param {Number} hour
     * @param {Number} minute
     * @param {Number} second
     */
    _setRanges(dayPeriod, hour, minute, second) {
      this.state.ranges.dayPeriod =
        this.state.ranges.dayPeriod || this._getDayPeriodRange();

      if (this.state.dayPeriod != dayPeriod) {
        this.state.ranges.hours = this._getHoursRange(dayPeriod);
      }

      if (this.state.hour != hour) {
        this.state.ranges.minutes = this._getMinutesRange(hour);
      }

      if (this.state.hour != hour || this.state.minute != minute) {
        this.state.ranges.seconds = this._getSecondsRange(hour, minute);
      }

      if (
        this.state.hour != hour ||
        this.state.minute != minute ||
        this.state.second != second
      ) {
        this.state.ranges.milliseconds = this._getMillisecondsRange(
          hour,
          minute,
          second
        );
      }

      // Save the time states for comparison.
      this.state.dayPeriod = dayPeriod;
      this.state.hour = hour;
      this.state.minute = minute;
      this.state.second = second;
    },

    /**
     * Get the AM/PM range. Return an empty array if in 24hr mode.
     *
     * @return {Array<Number>}
     */
    _getDayPeriodRange() {
      if (this.props.format == TIME_FORMAT_24) {
        return [];
      }

      const start = 0;
      const end = DAY_IN_MS - 1;
      const minStep = DAY_PERIOD_IN_MS;
      const formatter = time =>
        new Date(time).getUTCHours() < DAY_PERIOD_IN_HOURS
          ? 0
          : DAY_PERIOD_IN_HOURS;

      return this._getSteps(start, end, minStep, formatter);
    },

    /**
     * Get the hours range.
     *
     * @param  {Number} dayPeriod
     * @return {Array<Number>}
     */
    _getHoursRange(dayPeriod) {
      const { format } = this.props;
      const start = format == "24" ? 0 : dayPeriod * HOUR_IN_MS;
      const end = format == "24" ? DAY_IN_MS - 1 : start + DAY_PERIOD_IN_MS - 1;
      const minStep = HOUR_IN_MS;
      const formatter = time => new Date(time).getUTCHours();

      return this._getSteps(start, end, minStep, formatter);
    },

    /**
     * Get the minutes range
     *
     * @param  {Number} hour
     * @return {Array<Number>}
     */
    _getMinutesRange(hour) {
      const start = hour * HOUR_IN_MS;
      const end = start + HOUR_IN_MS - 1;
      const minStep = MINUTE_IN_MS;
      const formatter = time => new Date(time).getUTCMinutes();

      return this._getSteps(start, end, minStep, formatter);
    },

    /**
     * Get the seconds range
     *
     * @param  {Number} hour
     * @param  {Number} minute
     * @return {Array<Number>}
     */
    _getSecondsRange(hour, minute) {
      const start = hour * HOUR_IN_MS + minute * MINUTE_IN_MS;
      const end = start + MINUTE_IN_MS - 1;
      const minStep = SECOND_IN_MS;
      const formatter = time => new Date(time).getUTCSeconds();

      return this._getSteps(start, end, minStep, formatter);
    },

    /**
     * Get the milliseconds range
     * @param  {Number} hour
     * @param  {Number} minute
     * @param  {Number} second
     * @return {Array<Number>}
     */
    _getMillisecondsRange(hour, minute, second) {
      const start =
        hour * HOUR_IN_MS + minute * MINUTE_IN_MS + second * SECOND_IN_MS;
      const end = start + SECOND_IN_MS - 1;
      const minStep = 1;
      const formatter = time => new Date(time).getUTCMilliseconds();

      return this._getSteps(start, end, minStep, formatter);
    },

    /**
     * Calculate the range of possible steps.
     *
     * @param  {Number} startValue: Start time in ms
     * @param  {Number} endValue: End time in ms
     * @param  {Number} minStep: Smallest step in ms for the time unit
     * @param  {Function} formatter: Outputs time in a particular format
     * @return {Array<Object>}
     *         {
     *           {Number} value
     *           {Boolean} enabled
     *         }
     */
    _getSteps(startValue, endValue, minStep, formatter) {
      const { min, max, step } = this.props;
      // The timeStep should be big enough so that there won't be
      // duplications. Ex: minimum step for minute should be 60000ms,
      // if smaller than that, next step might return the same minute.
      const timeStep = Math.max(minStep, step);

      // Make sure the starting point and end point is not off step
      let time =
        min.valueOf() +
        Math.ceil((startValue - min.valueOf()) / timeStep) * timeStep;
      let maxValue =
        min.valueOf() +
        Math.floor((max.valueOf() - min.valueOf()) / step) * step;
      let steps = [];

      // Increment by timeStep until reaching the end of the range.
      while (time <= endValue) {
        steps.push({
          value: formatter(time),
          // Check if the value is within the min and max. If it's out of range,
          // also check for the case when minStep is too large, and has stepped out
          // of range when it should be enabled.
          enabled:
            (time >= min.valueOf() && time <= max.valueOf()) ||
            (time > maxValue &&
              startValue <= maxValue &&
              endValue >= maxValue &&
              formatter(time) == formatter(maxValue)),
        });
        time += timeStep;
      }

      return steps;
    },

    /**
     * A generic function for stepping up or down from a value of a range.
     * It stops at the upper and lower limits.
     *
     * @param  {Number} current: The current value
     * @param  {Number} offset: The offset relative to current value
     * @param  {Array<Object>} range: List of possible steps
     * @return {Number} The new value
     */
    _step(current, offset, range) {
      const index = range.findIndex(step => step.value == current);
      const newIndex =
        offset > 0
          ? Math.min(index + offset, range.length - 1)
          : Math.max(index + offset, 0);
      return range[newIndex].value;
    },

    /**
     * Step up or down AM/PM
     *
     * @param  {Number} offset
     */
    stepDayPeriodBy(offset) {
      const current = this.dayPeriod;
      const dayPeriod = this._step(
        current,
        offset,
        this.state.ranges.dayPeriod
      );

      if (current != dayPeriod) {
        this.hour < DAY_PERIOD_IN_HOURS
          ? this.setState({ hour: this.hour + DAY_PERIOD_IN_HOURS })
          : this.setState({ hour: this.hour - DAY_PERIOD_IN_HOURS });
      }
    },

    /**
     * Step up or down hours
     *
     * @param  {Number} offset
     */
    stepHourBy(offset) {
      const current = this.hour;
      const hour = this._step(current, offset, this.state.ranges.hours);

      if (current != hour) {
        this.setState({ hour });
      }
    },

    /**
     * Step up or down minutes
     *
     * @param  {Number} offset
     */
    stepMinuteBy(offset) {
      const current = this.minute;
      const minute = this._step(current, offset, this.state.ranges.minutes);

      if (current != minute) {
        this.setState({ minute });
      }
    },

    /**
     * Step up or down seconds
     *
     * @param  {Number} offset
     */
    stepSecondBy(offset) {
      const current = this.second;
      const second = this._step(current, offset, this.state.ranges.seconds);

      if (current != second) {
        this.setState({ second });
      }
    },

    /**
     * Step up or down milliseconds
     *
     * @param  {Number} offset
     */
    stepMillisecondBy(offset) {
      const current = this.milliseconds;
      const millisecond = this._step(
        current,
        offset,
        this.state.ranges.millisecond
      );

      if (current != millisecond) {
        this.setState({ millisecond });
      }
    },

    /**
     * Checks if the time state is off step.
     *
     * @param  {Date} time
     * @return {Boolean}
     */
    _isOffStep(time) {
      const { min, step } = this.props;

      return (time.valueOf() - min.valueOf()) % step != 0;
    },
  };
}
PK
!<�iI�!�!4chrome/toolkit/content/global/bindings/timepicker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* import-globals-from timekeeper.js */
/* import-globals-from spinner.js */

"use strict";

function TimePicker(context) {
  this.context = context;
  this._attachEventListeners();
}

{
  const DAY_PERIOD_IN_HOURS = 12,
    DAY_IN_MS = 86400000;

  TimePicker.prototype = {
    /**
     * Initializes the time picker. Set the default states and properties.
     * @param  {Object} props
     *         {
     *           {Number} hour [optional]: Hour in 24 hours format (0~23), default is current hour
     *           {Number} minute [optional]: Minute (0~59), default is current minute
     *           {Number} min: Minimum time, in ms
     *           {Number} max: Maximum time, in ms
     *           {Number} step: Step size in ms
     *           {String} format [optional]: "12" for 12 hours, "24" for 24 hours format
     *           {String} locale [optional]: User preferred locale
     *         }
     */
    init(props) {
      this.props = props || {};
      this._setDefaultState();
      this._createComponents();
      this._setComponentStates();
      // TODO(bug 1828721): This is a bit sad.
      window.PICKER_READY = true;
      document.dispatchEvent(new CustomEvent("PickerReady"));
    },

    /*
     * Set initial time states. If there's no hour & minute, it will
     * use the current time. The Time module keeps track of the time states,
     * and calculates the valid options given the time, min, max, step,
     * and format (12 or 24).
     */
    _setDefaultState() {
      const { hour, minute, min, max, step, format } = this.props;
      const now = new Date();

      let timerHour = hour == undefined ? now.getHours() : hour;
      let timerMinute = minute == undefined ? now.getMinutes() : minute;
      let timeKeeper = new TimeKeeper({
        min: new Date(Number.isNaN(min) ? 0 : min),
        max: new Date(Number.isNaN(max) ? DAY_IN_MS - 1 : max),
        step,
        format: format || "12",
      });
      timeKeeper.setState({ hour: timerHour, minute: timerMinute });

      this.state = { timeKeeper };
    },

    /**
     * Initalize the spinner components.
     */
    _createComponents() {
      const { locale, format } = this.props;
      const { timeKeeper } = this.state;

      const wrapSetValueFn = setTimeFunction => {
        return value => {
          setTimeFunction(value);
          this._setComponentStates();
          this._dispatchState();
        };
      };
      const numberFormat = new Intl.NumberFormat(locale).format;

      this.components = {
        hour: new Spinner(
          {
            setValue: wrapSetValueFn(value => {
              timeKeeper.setHour(value);
              this.state.isHourSet = true;
            }),
            getDisplayString: hour => {
              if (format == "24") {
                return numberFormat(hour);
              }
              // Hour 0 in 12 hour format is displayed as 12.
              const hourIn12 = hour % DAY_PERIOD_IN_HOURS;
              return hourIn12 == 0 ? numberFormat(12) : numberFormat(hourIn12);
            },
          },
          this.context
        ),
        minute: new Spinner(
          {
            setValue: wrapSetValueFn(value => {
              timeKeeper.setMinute(value);
              this.state.isMinuteSet = true;
            }),
            getDisplayString: minute => numberFormat(minute),
          },
          this.context
        ),
      };

      this._insertLayoutElement({
        tag: "div",
        textContent: ":",
        className: "colon",
        insertBefore: this.components.minute.elements.container,
      });

      // The AM/PM spinner is only available in 12hr mode
      // TODO: Replace AM & PM string with localized string
      if (format == "12") {
        this.components.dayPeriod = new Spinner(
          {
            setValue: wrapSetValueFn(value => {
              timeKeeper.setDayPeriod(value);
              this.state.isDayPeriodSet = true;
            }),
            getDisplayString: dayPeriod => (dayPeriod == 0 ? "AM" : "PM"),
            hideButtons: true,
          },
          this.context
        );

        this._insertLayoutElement({
          tag: "div",
          className: "spacer",
          insertBefore: this.components.dayPeriod.elements.container,
        });
      }
    },

    /**
     * Insert element for layout purposes.
     *
     * @param {Object}
     *        {
     *          {String} tag: The tag to create
     *          {DOMElement} insertBefore: The DOM node to insert before
     *          {String} className [optional]: Class name
     *          {String} textContent [optional]: Text content
     *        }
     */
    _insertLayoutElement({ tag, insertBefore, className, textContent }) {
      let el = document.createElement(tag);
      el.textContent = textContent;
      el.className = className;
      this.context.insertBefore(el, insertBefore);
    },

    /**
     * Set component states.
     */
    _setComponentStates() {
      const { timeKeeper, isHourSet, isMinuteSet, isDayPeriodSet } = this.state;
      const isInvalid = timeKeeper.state.isInvalid;
      // Value is set to min if it's first opened and time state is invalid
      const setToMinValue =
        !isHourSet && !isMinuteSet && !isDayPeriodSet && isInvalid;

      this.components.hour.setState({
        value: setToMinValue
          ? timeKeeper.ranges.hours[0].value
          : timeKeeper.hour,
        items: timeKeeper.ranges.hours,
        isInfiniteScroll: true,
        isValueSet: isHourSet,
        isInvalid,
      });

      this.components.minute.setState({
        value: setToMinValue
          ? timeKeeper.ranges.minutes[0].value
          : timeKeeper.minute,
        items: timeKeeper.ranges.minutes,
        isInfiniteScroll: true,
        isValueSet: isMinuteSet,
        isInvalid,
      });

      // The AM/PM spinner is only available in 12hr mode
      if (this.props.format == "12") {
        this.components.dayPeriod.setState({
          value: setToMinValue
            ? timeKeeper.ranges.dayPeriod[0].value
            : timeKeeper.dayPeriod,
          items: timeKeeper.ranges.dayPeriod,
          isInfiniteScroll: false,
          isValueSet: isDayPeriodSet,
          isInvalid,
        });
      }
    },

    /**
     * Dispatch CustomEvent to pass the state of picker to the panel.
     */
    _dispatchState() {
      const { hour, minute } = this.state.timeKeeper;
      const { isHourSet, isMinuteSet, isDayPeriodSet } = this.state;
      // The panel is listening to window for postMessage event, so we
      // do postMessage to itself to send data to input boxes.
      window.postMessage(
        {
          name: "PickerPopupChanged",
          detail: {
            hour,
            minute,
            isHourSet,
            isMinuteSet,
            isDayPeriodSet,
          },
        },
        "*"
      );
    },
    _attachEventListeners() {
      window.addEventListener("message", this);
      document.addEventListener("mousedown", this);
    },

    /**
     * Handle events.
     *
     * @param  {Event} event
     */
    handleEvent(event) {
      switch (event.type) {
        case "message": {
          this.handleMessage(event);
          break;
        }
        case "mousedown": {
          // Use preventDefault to keep focus on input boxes
          event.preventDefault();
          event.target.setCapture();
          break;
        }
      }
    },

    /**
     * Handle postMessage events.
     *
     * @param {Event} event
     */
    handleMessage(event) {
      switch (event.data.name) {
        case "PickerSetValue": {
          this.set(event.data.detail);
          break;
        }
        case "PickerInit": {
          this.init(event.data.detail);
          break;
        }
      }
    },

    /**
     * Set the time state and update the components with the new state.
     *
     * @param {Object} timeState
     *        {
     *          {Number} hour [optional]
     *          {Number} minute [optional]
     *          {Number} second [optional]
     *          {Number} millisecond [optional]
     *        }
     */
    set(timeState) {
      if (timeState.hour != undefined) {
        this.state.isHourSet = true;
      }
      if (timeState.minute != undefined) {
        this.state.isMinuteSet = true;
      }
      this.state.timeKeeper.setState(timeState);
      this._setComponentStates();
    },
  };
}
PK
!<��/�00-chrome/toolkit/content/global/buildconfig.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

h2 {
  margin-top: 1.5em;
}

p {
  font: message-box;
}

.build-platform-table {
  width: auto;
}
PK
!<�E]��
�
.chrome/toolkit/content/global/buildconfig.html<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src chrome:; object-src 'none'" />
    <meta charset="UTF-8">
    <meta name="color-scheme" content="light dark">
    <meta name="viewport" content="width=device-width; user-scalable=false;">
    <title>Build Configuration</title>
    <link rel="stylesheet" href="chrome://global/skin/in-content/info-pages.css" type="text/css">
    <link rel="stylesheet" href="chrome://global/content/buildconfig.css" type="text/css">
  </head>
  <body>
    <div class="container">
      <h1>Build Configuration</h1>
      <p>Please be aware that this page doesn't reflect all the options used to build Firefox.</p>
      <h2>Source</h2>
      <p>Built from <a href="https://hg.mozilla.org/releases/mozilla-release/rev/61268a890b3c86ab4f5cfd7c6e1e3d14cc68f0b6">https://hg.mozilla.org/releases/mozilla-release/rev/61268a890b3c86ab4f5cfd7c6e1e3d14cc68f0b6</a></p>
      <h2>Build platform</h2>
      <table class="build-platform-table">
        <tbody>
          <tr>
            <th>target</th>
          </tr>
          <tr>
            <td>x86_64-pc-linux-gnu</td>
          </tr>
        </tbody>
      </table>
      <h2>Build tools</h2>
      <table>
        <tbody>
          <tr>
            <th>Compiler</th>
            <th>Version</th>
            <th>Compiler flags</th>
          </tr>
          <tr>
            <td>/build/firefox/stage/usr/bin/clang -std=gnu99</td>
            <td>16.0.4</td>
            <td>-pthread -ffunction-sections -fdata-sections -fno-math-errno -fPIC -isystem /build/firefox/parts/firefox/install/usr/include -isystem /build/firefox/stage/usr/include</td>
          </tr>
          <tr>
            <td>/build/firefox/stage/usr/bin/clang++</td>
            <td>16.0.4</td>
            <td>-isystem /build/firefox/parts/firefox/install/usr/include -isystem /build/firefox/stage/usr/include -fno-rtti -pthread -fno-sized-deallocation -fno-aligned-new -ffunction-sections -fdata-sections -fno-math-errno -fno-exceptions -fPIC -isystem /build/firefox/parts/firefox/install/usr/include -isystem /build/firefox/stage/usr/include -gdwarf-4 -O3 -fomit-frame-pointer -funwind-tables</td>
          </tr>
          <tr>
            <td>/build/firefox/stage/usr/bin/rustc</td>
            <td>1.76.0</td>
            <td></td>
          </tr>
        </tbody>
      </table>
      <h2>Configure options</h2>
      <p>MOZILLA_OFFICIAL=1 --enable-update-channel=release MOZBUILD_STATE_PATH=/build/firefox/parts/firefox/build/.mozbuild --prefix=/build/firefox/parts/firefox/install/usr --disable-tests 'CPPFLAGS=-isystem /build/firefox/parts/firefox/install/usr/include -isystem /build/firefox/stage/usr/include' 'CFLAGS=-isystem /build/firefox/parts/firefox/install/usr/include -isystem /build/firefox/stage/usr/include' 'CXXFLAGS=-isystem /build/firefox/parts/firefox/install/usr/include -isystem /build/firefox/stage/usr/include' 'LDFLAGS=-Wl,-rpath-link=/build/firefox/parts/firefox/build/obj-x86_64-pc-linux-gnu/dist/bin -Wl,-rpath-link=/snap/gnome-42-2204-sdk/current/usr/lib/x86_64-linux-gnu -Wl,-rpath-link=/snap/gnome-42-2204-sdk/current/usr/lib' --enable-linker=lld MOZ_PROFILE_USE=1 --enable-rust-simd --with-google-location-service-api-keyfile=/build/firefox/stage/gls-gapi.data --with-google-safebrowsing-api-keyfile=/build/firefox/stage/sb-gapi.data --enable-geckodriver WASI_SYSROOT=/build/firefox/stage/wasi-sysroot --disable-updater --enable-official-branding</p>
    </div>
  </body>
</html>
PK
!<Θpj��8chrome/toolkit/content/global/certviewer/certDecoder.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import {
  Certificate,
  ECNamedCurves,
  ECPublicKey,
  RSAPublicKey,
} from "./vendor/pkijs.js";

const getTimeZone = () => {
  let timeZone = new Date().toString().match(/\(([A-Za-z\s].*)\)/);
  if (timeZone === null) {
    // America/Chicago
    timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
  } else if (timeZone.length > 1) {
    timeZone = timeZone[1]; // Central Daylight Time
  } else {
    timeZone = "Local Time"; // not sure if this is right, but let's go with it for now
  }
  return timeZone;
};

const getPublicKeyInfo = x509 => {
  let publicKey = x509.subjectPublicKeyInfo.parsedKey;
  if (publicKey instanceof RSAPublicKey) {
    let modulusJSON = publicKey.modulus.toJSON();
    let modulusHex = modulusJSON.valueBlock.valueHex;
    return {
      e: publicKey.publicExponent.toJSON().valueBlock.valueDec,
      kty: "RSA",
      n: hashify(modulusHex),
      keysize: modulusHex.length * 4, // key size in bits
    };
  }
  if (publicKey instanceof ECPublicKey) {
    let x = hashify(publicKey.x);
    let y = hashify(publicKey.y);
    let curve = ECNamedCurves.find(publicKey.namedCurve);
    let keysize = curve ? curve.size * 8 : undefined;
    return {
      kty: "Elliptic Curve",
      keysize,
      x, // x coordinate
      y, // y coordinate
      xy: `04:${x}:${y}`, // 04 (uncompressed) public key
    };
  }
  return { kty: "Unknown" };
};

const getX509Ext = (extensions, v) => {
  for (var extension in extensions) {
    if (extensions[extension].extnID === v) {
      return extensions[extension].toJSON().parsedValue;
    }
  }
  return undefined;
};

const getKeyUsages = (x509, criticalExtensions) => {
  let keyUsages = {
    critical: criticalExtensions.includes("2.5.29.15"),
    purposes: [],
  };

  let keyUsagesExt = getX509Ext(x509.extensions, "2.5.29.15");
  if (keyUsagesExt !== undefined) {
    // zero pad or truncate the hex value to 4 digits
    let keyUsagesHex = keyUsagesExt.valueBlock.valueHex
      .slice(0, 4)
      .padEnd(4, "0");

    let keyUsagesInt = parseInt(keyUsagesHex, 16);

    // Clear any unused bits (accounting for padding or truncation above).
    let unusedBits = keyUsagesExt.valueBlock.unusedBits;
    if (keyUsagesExt.valueBlock.valueHex.length == 2) {
      unusedBits += 8;
    } else if (keyUsagesExt.valueBlock.valueHex.length > 4) {
      unusedBits = 0;
    }
    keyUsagesInt &= ~((1 << unusedBits) - 1);

    // iterate through the bit string
    strings.keyUsages.forEach(usage => {
      if (keyUsagesInt & 0x8000) {
        keyUsages.purposes.push(usage);
      }

      keyUsagesInt = keyUsagesInt << 1;
    });
  }

  return keyUsages;
};

const parseSubsidiary = distinguishedNames => {
  const subsidiary = {
    cn: "",
    dn: [],
    entries: [],
  };

  distinguishedNames.forEach(dn => {
    const distinguishedName = strings.names[dn.type];
    const value = dn.value.valueBlock.value;

    if (distinguishedName === undefined) {
      subsidiary.dn.push(`OID.${dn.type}=${value}`);
      subsidiary.entries.push([`OID.${dn.type}`, value]);
    } else if (distinguishedName.short === undefined) {
      subsidiary.dn.push(`OID.${dn.type}=${value}`);
      subsidiary.entries.push([distinguishedName.long, value]);
    } else {
      subsidiary.dn.push(`${distinguishedName.short}=${value}`);
      subsidiary.entries.push([distinguishedName.long, value]);

      // add the common name for tab display
      if (distinguishedName.short === "cn") {
        subsidiary.cn = value;
      }
    }
  });

  // turn path into a string
  subsidiary.dn = subsidiary.dn.join(", ");

  return subsidiary;
};

const getSubjectAltNames = (x509, criticalExtensions) => {
  let san = getX509Ext(x509.extensions, "2.5.29.17");
  if (san && san.hasOwnProperty("altNames")) {
    san = Object.keys(san.altNames).map(index => {
      const type = san.altNames[index].type;

      switch (type) {
        case 4: // directory
          return [
            strings.san[type],
            parseSubsidiary(san.altNames[index].value.typesAndValues).dn,
          ];
        case 7: // ip address
          let address = san.altNames[index].value.valueBlock.valueHex;

          if (address.length === 8) {
            // ipv4
            return [
              strings.san[type],
              address
                .match(/.{1,2}/g)
                .map(x => parseInt(x, 16))
                .join("."),
            ];
          } else if (address.length === 32) {
            // ipv6
            return [
              strings.san[type],
              address
                .toLowerCase()
                .match(/.{1,4}/g)
                .join(":")
                .replace(/\b:?(?:0+:?){2,}/, "::"),
            ];
          }
          return [strings.san[type], "Unknown IP address"];

        default:
          return [strings.san[type], san.altNames[index].value];
      }
    });
  } else {
    san = [];
  }
  san = {
    altNames: san,
    critical: criticalExtensions.includes("2.5.29.17"),
  };
  return san;
};

const getBasicConstraints = (x509, criticalExtensions) => {
  let basicConstraints;
  const basicConstraintsExt = getX509Ext(x509.extensions, "2.5.29.19");
  if (basicConstraintsExt) {
    basicConstraints = {
      cA: basicConstraintsExt.cA !== undefined && basicConstraintsExt.cA,
      critical: criticalExtensions.includes("2.5.29.19"),
    };
  }
  return basicConstraints;
};

const getEKeyUsages = (x509, criticalExtensions) => {
  let eKeyUsages = getX509Ext(x509.extensions, "2.5.29.37");
  if (eKeyUsages) {
    eKeyUsages = {
      critical: criticalExtensions.includes("2.5.29.37"),
      purposes: eKeyUsages.keyPurposes.map(x => strings.eKU[x] || x),
    };
  }
  return eKeyUsages;
};

const getSubjectKeyID = (x509, criticalExtensions) => {
  let sKID = getX509Ext(x509.extensions, "2.5.29.14");
  if (sKID) {
    sKID = {
      critical: criticalExtensions.includes("2.5.29.14"),
      id: hashify(sKID.valueBlock.valueHex),
    };
  }
  return sKID;
};

const getAuthorityKeyID = (x509, criticalExtensions) => {
  let aKID = getX509Ext(x509.extensions, "2.5.29.35");
  if (!aKID || !aKID.keyIdentifier) {
    return null;
  }
  aKID = {
    critical: criticalExtensions.includes("2.5.29.35"),
    id: hashify(aKID.keyIdentifier.valueBlock.valueHex),
  };
  return aKID;
};

const getCRLPoints = (x509, criticalExtensions) => {
  let crlPoints = getX509Ext(x509.extensions, "2.5.29.31");
  if (crlPoints) {
    crlPoints = {
      critical: criticalExtensions.includes("2.5.29.31"),
      points: crlPoints.distributionPoints.map(
        x => x.distributionPoint[0].value
      ),
    };
  }
  return crlPoints;
};

const getOcspStaple = (x509, criticalExtensions) => {
  let ocspStaple = getX509Ext(x509.extensions, "1.3.6.1.5.5.7.1.24");
  if (ocspStaple && ocspStaple.valueBeforeDecode === "3003020105") {
    ocspStaple = {
      critical: criticalExtensions.includes("1.3.6.1.5.5.7.1.24"),
      required: true,
    };
  } else {
    ocspStaple = {
      critical: criticalExtensions.includes("1.3.6.1.5.5.7.1.24"),
      required: false,
    };
  }
  return ocspStaple;
};

const getAuthorityInfoAccess = (x509, criticalExtensions) => {
  let aia = getX509Ext(x509.extensions, "1.3.6.1.5.5.7.1.1");
  if (aia) {
    aia = aia.accessDescriptions.map(x => {
      return {
        location: x.accessLocation.value,
        method: strings.aia[x.accessMethod],
      };
    });
  }

  aia = {
    descriptions: aia,
    critical: criticalExtensions.includes("1.3.6.1.5.5.7.1.1"),
  };
  return aia;
};

const getSCTs = (x509, criticalExtensions) => {
  let scts = getX509Ext(x509.extensions, "1.3.6.1.4.1.11129.2.4.2");
  if (scts) {
    scts = Object.keys(scts.timestamps).map(x => {
      let logId = scts.timestamps[x].logID.toLowerCase();
      let sctsTimestamp = scts.timestamps[x].timestamp;
      return {
        logId: hashify(logId),
        name: ctLogNames.hasOwnProperty(logId) ? ctLogNames[logId] : undefined,
        signatureAlgorithm: `${scts.timestamps[x].hashAlgorithm.replace(
          "sha",
          "SHA-"
        )} ${scts.timestamps[x].signatureAlgorithm.toUpperCase()}`,
        timestamp: `${sctsTimestamp.toLocaleString()} (${getTimeZone()})`,
        timestampUTC: sctsTimestamp.toUTCString(),
        version: scts.timestamps[x].version + 1,
      };
    });
  } else {
    scts = [];
  }

  scts = {
    critical: criticalExtensions.includes("1.3.6.1.4.1.11129.2.4.2"),
    timestamps: scts,
  };
  return scts;
};

const getCertificatePolicies = (x509, criticalExtensions) => {
  let cp = getX509Ext(x509.extensions, "2.5.29.32");
  if (cp && cp.hasOwnProperty("certificatePolicies")) {
    cp = cp.certificatePolicies.map(x => {
      let id = x.policyIdentifier;
      let certName = strings.cps.hasOwnProperty(id)
        ? strings.cps[id].name
        : undefined;
      let qualifiers = undefined;
      let value = strings.cps.hasOwnProperty(id)
        ? strings.cps[id].value
        : undefined;

      // ansi organization identifiers
      if (id.startsWith("2.16.840.")) {
        value = id;
        id = "2.16.840";
        certName = strings.cps["2.16.840"].name;
      }

      // statement identifiers
      if (id.startsWith("1.3.6.1.4.1")) {
        value = id;
        id = "1.3.6.1.4.1";
        certName = strings.cps["1.3.6.1.4.1"].name;
      }

      if (x.hasOwnProperty("policyQualifiers")) {
        qualifiers = x.policyQualifiers.map(qualifier => {
          let qualifierId = qualifier.policyQualifierId;
          let qualifierName = strings.cps.hasOwnProperty(qualifierId)
            ? strings.cps[qualifierId].name
            : undefined;
          let qualifierValue = qualifier.qualifier.valueBlock.value;

          // sometimes they are multiple qualifier subblocks, and for now we'll
          // only return the first one because it's getting really messy at this point
          if (Array.isArray(qualifierValue) && qualifierValue.length === 1) {
            qualifierValue = qualifierValue[0].valueBlock.value;
          } else if (
            Array.isArray(qualifierValue) &&
            qualifierValue.length > 1
          ) {
            qualifierValue = "(currently unsupported)";
          }

          return {
            qualifierId,
            qualifierName,
            qualifierValue,
          };
        });
      }

      return {
        id,
        name: certName,
        qualifiers,
        value,
      };
    });
  }

  cp = {
    critical: criticalExtensions.includes("2.5.29.32"),
    policies: cp,
  };
  return cp;
};

const getMicrosoftCryptographicExtensions = (x509, criticalExtensions) => {
  // now let's parse the Microsoft cryptographic extensions
  let msCrypto = {
    caVersion: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.21.1"),
    certificatePolicies: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.21.10"),
    certificateTemplate: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.21.7"),
    certificateType: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.20.2"),
    previousHash: getX509Ext(x509.extensions, "1.3.6.1.4.1.311.21.2"),
  };

  if (
    msCrypto.caVersion &&
    Number.isInteger(msCrypto.caVersion.keyIndex) &&
    Number.isInteger(msCrypto.caVersion.certificateIndex)
  ) {
    msCrypto.caVersion = {
      critical: criticalExtensions.includes("1.3.6.1.4.1.311.21.1"),
      caRenewals: msCrypto.caVersion.certificateIndex,
      keyReuses:
        msCrypto.caVersion.certificateIndex - msCrypto.caVersion.keyIndex,
    };
  }

  if (msCrypto.certificatePolicies) {
    msCrypto.certificatePolicies = {
      critical: criticalExtensions.includes("1.3.6.1.4.1.311.21.10"),
      purposes: msCrypto.certificatePolicies.certificatePolicies.map(
        x => strings.eKU[x.policyIdentifier] || x.policyIdentifier
      ),
    };
  }

  if (msCrypto.certificateTemplate) {
    msCrypto.certificateTemplate = {
      critical: criticalExtensions.includes("1.3.6.1.4.1.311.21.7"),
      id: msCrypto.certificateTemplate.extnID,
      major: msCrypto.certificateTemplate.templateMajorVersion,
      minor: msCrypto.certificateTemplate.templateMinorVersion,
    };
  }

  if (msCrypto.certificateType) {
    msCrypto.certificateType = {
      critical: criticalExtensions.includes("1.3.6.1.4.1.311.20.2"),
      type:
        strings.microsoftCertificateTypes[
          msCrypto.certificateType.valueBlock.value
        ] || "Unknown",
    };
  }

  if (msCrypto.previousHash) {
    msCrypto.previousHash = {
      critical: criticalExtensions.includes("1.3.6.1.4.1.311.21.2"),
      previousHash: hashify(msCrypto.previousHash.valueBlock.valueHex),
    };
  }

  msCrypto.exists = !!(
    msCrypto.caVersion ||
    msCrypto.certificatePolicies ||
    msCrypto.certificateTemplate ||
    msCrypto.certificateType ||
    msCrypto.previousHash
  );

  return msCrypto;
};

const b64ToPEM = string => {
  let wrapped = string.match(/.{1,64}/g).join("\r\n");
  return `-----BEGIN CERTIFICATE-----\r\n${wrapped}\r\n-----END CERTIFICATE-----\r\n`;
};

export const parse = async certificate => {
  // certificate could be an array of BER or an array of buffers
  const supportedExtensions = [
    "1.3.6.1.4.1.311.20.2", // microsoft certificate type
    "1.3.6.1.4.1.311.21.2", // microsoft certificate previous hash
    "1.3.6.1.4.1.311.21.7", // microsoft certificate template
    "1.3.6.1.4.1.311.21.1", // microsoft certification authority renewal
    "1.3.6.1.4.1.311.21.10", // microsoft certificate policies
    "1.3.6.1.4.1.11129.2.4.2", // embedded scts
    "1.3.6.1.5.5.7.1.1", // authority info access
    "1.3.6.1.5.5.7.1.24", // ocsp stapling
    "1.3.101.77", // ct redaction - deprecated and not displayed
    "2.5.29.14", // subject key identifier
    "2.5.29.15", // key usages
    "2.5.29.17", // subject alt names
    "2.5.29.19", // basic constraints
    "2.5.29.31", // crl points
    "2.5.29.32", // certificate policies
    "2.5.29.35", // authority key identifier
    "2.5.29.37", // extended key usage
  ];

  let timeZone = getTimeZone();

  // parse the certificate
  let x509 = Certificate.fromBER(certificate);

  // convert the cert to PEM
  const certPEM = b64ToPEM(
    btoa(String.fromCharCode.apply(null, new Uint8Array(certificate)))
  );

  // get which extensions are critical
  const criticalExtensions = [];
  if (x509.extensions) {
    x509.extensions.forEach(ext => {
      if (ext.hasOwnProperty("critical") && ext.critical === true) {
        criticalExtensions.push(ext.extnID);
      }
    });
  }
  const spki = getPublicKeyInfo(x509);
  const keyUsages = getKeyUsages(x509, criticalExtensions);
  const san = getSubjectAltNames(x509, criticalExtensions);
  const basicConstraints = getBasicConstraints(x509, criticalExtensions);
  const eKeyUsages = getEKeyUsages(x509, criticalExtensions);
  const sKID = getSubjectKeyID(x509, criticalExtensions);
  const aKID = getAuthorityKeyID(x509, criticalExtensions);
  const crlPoints = getCRLPoints(x509, criticalExtensions);
  const ocspStaple = getOcspStaple(x509, criticalExtensions);
  const aia = getAuthorityInfoAccess(x509, criticalExtensions);
  const scts = getSCTs(x509, criticalExtensions);
  const cp = getCertificatePolicies(x509, criticalExtensions);
  const msCrypto = getMicrosoftCryptographicExtensions(
    x509,
    criticalExtensions
  );

  // determine which extensions weren't supported
  let unsupportedExtensions = [];
  if (x509.extensions) {
    x509.extensions.forEach(ext => {
      if (!supportedExtensions.includes(ext.extnID)) {
        unsupportedExtensions.push(ext.extnID);
      }
    });
  }

  // the output shell
  return {
    ext: {
      aia,
      aKID,
      basicConstraints,
      crlPoints,
      cp,
      eKeyUsages,
      keyUsages,
      msCrypto,
      ocspStaple,
      scts,
      sKID,
      san,
    },
    files: {
      der: undefined, // TODO: implement!
      pem: encodeURI(certPEM),
    },
    fingerprint: {
      sha1: await hash("SHA-1", certificate),
      sha256: await hash("SHA-256", certificate),
    },
    issuer: parseSubsidiary(x509.issuer.typesAndValues),
    notBefore: `${x509.notBefore.value.toLocaleString()} (${timeZone})`,
    notBeforeUTC: x509.notBefore.value.toUTCString(),
    notAfter: `${x509.notAfter.value.toLocaleString()} (${timeZone})`,
    notAfterUTC: x509.notAfter.value.toUTCString(),
    subject: parseSubsidiary(x509.subject.typesAndValues),
    serialNumber: hashify(getObjPath(x509, "serialNumber.valueBlock.valueHex")),
    signature: {
      name: strings.signature[getObjPath(x509, "signature.algorithmId")],
      type: getObjPath(x509, "signature.algorithmId"),
    },
    subjectPublicKeyInfo: spki,
    unsupportedExtensions,
    version: (x509.version + 1).toString(),
  };
};

const ctLogNames = {
  "9606c02c690033aa1d145f59c6e2648d0549f0df96aab8db915a70d8ecf390a5":
    "Akamai CT",
  "39376f545f7b4607f59742d768cd5d2437bf3473b6534a4834bcf72e681c83c9":
    "Alpha CT",
  a577ac9ced7548dd8f025b67a241089df86e0f476ec203c2ecbedb185f282638: "CNNIC CT",
  cdb5179b7fc1c046feea31136a3f8f002e6182faf8896fecc8b2f5b5ab604900: "Certly.IO",
  "1fbc36e002ede97f40199e86b3573b8a4217d80187746ad0da03a06054d20df4":
    "Cloudflare “Nimbus2017”",
  db74afeecb29ecb1feca3e716d2ce5b9aabb36f7847183c75d9d4f37b61fbf64:
    "Cloudflare “Nimbus2018”",
  "747eda8331ad331091219cce254f4270c2bffd5e422008c6373579e6107bcc56":
    "Cloudflare “Nimbus2019”",
  "5ea773f9df56c0e7b536487dd049e0327a919a0c84a112128418759681714558":
    "Cloudflare “Nimbus2020”",
  "4494652eb0eeceafc44007d8a8fe28c0dae682bed8cb31b53fd33396b5b681a8":
    "Cloudflare “Nimbus2021”",
  "41c8cab1df22464a10c6a13a0942875e4e318b1b03ebeb4bc768f090629606f6":
    "Cloudflare “Nimbus2022”",
  "7a328c54d8b72db620ea38e0521ee98416703213854d3bd22bc13a57a352eb52":
    "Cloudflare “Nimbus2023”",
  "6ff141b5647e4222f7ef052cefae7c21fd608e27d2af5a6e9f4b8a37d6633ee5":
    "DigiCert Nessie2018",
  fe446108b1d01ab78a62ccfeab6ab2b2babff3abdad80a4d8b30df2d0008830c:
    "DigiCert Nessie2019",
  c652a0ec48ceb3fcab170992c43a87413309e80065a26252401ba3362a17c565:
    "DigiCert Nessie2020",
  eec095ee8d72640f92e3c3b91bc712a3696a097b4b6a1a1438e647b2cbedc5f9:
    "DigiCert Nessie2021",
  "51a3b0f5fd01799c566db837788f0ca47acc1b27cbf79e88429a0dfed48b05e5":
    "DigiCert Nessie2022",
  b3737707e18450f86386d605a9dc11094a792db1670c0b87dcf0030e7936a59a:
    "DigiCert Nessie2023",
  "5614069a2fd7c2ecd3f5e1bd44b23ec74676b9bc99115cc0ef949855d689d0dd":
    "DigiCert Server",
  "8775bfe7597cf88c43995fbdf36eff568d475636ff4ab560c1b4eaff5ea0830f":
    "DigiCert Server 2",
  c1164ae0a772d2d4392dc80ac10770d4f0c49bde991a4840c1fa075164f63360:
    "DigiCert Yeti2018",
  e2694bae26e8e94009e8861bb63b83d43ee7fe7488fba48f2893019dddf1dbfe:
    "DigiCert Yeti2019",
  f095a459f200d18240102d2f93888ead4bfe1d47e399e1d034a6b0a8aa8eb273:
    "DigiCert Yeti2020",
  "5cdc4392fee6ab4544b15e9ad456e61037fbd5fa47dca17394b25ee6f6c70eca":
    "DigiCert Yeti2021",
  "2245450759552456963fa12ff1f76d86e0232663adc04b7f5dc6835c6ee20f02":
    "DigiCert Yeti2022",
  "35cf191bbfb16c57bf0fad4c6d42cbbbb627202651ea3fe12aefa803c33bd64c":
    "DigiCert Yeti2023",
  "717ea7420975be84a2723553f1777c26dd51af4e102144094d9019b462fb6668": "GDCA 1",
  "14308d90ccd030135005c01ca526d81e84e87624e39b6248e08f724aea3bb42a": "GDCA 2",
  c9cf890a21109c666cc17a3ed065c930d0e0135a9feba85af14210b8072421aa:
    "GDCA CT #1",
  "924a30f909336ff435d6993a10ac75a2c641728e7fc2d659ae6188ffad40ce01":
    "GDCA CT #2",
  fad4c97cc49ee2f8ac85c5ea5cea09d0220dbbf4e49c6b50662ff868f86b8c28:
    "Google “Argon2017”",
  a4501269055a15545e6211ab37bc103f62ae5576a45e4b1714453e1b22106a25:
    "Google “Argon2018”",
  "63f2dbcde83bcc2ccf0b728427576b33a48d61778fbd75a638b1c768544bd88d":
    "Google “Argon2019”",
  b21e05cc8ba2cd8a204e8766f92bb98a2520676bdafa70e7b249532def8b905e:
    "Google “Argon2020”",
  f65c942fd1773022145418083094568ee34d131933bfdf0c2f200bcc4ef164e3:
    "Google “Argon2021”",
  "2979bef09e393921f056739f63a577e5be577d9c600af8f94d5d265c255dc784":
    "Google “Argon2022”",
  "68f698f81f6482be3a8ceeb9281d4cfc71515d6793d444d10a67acbb4f4ffbc4":
    "Google “Aviator”",
  c3bf03a7e1ca8841c607bae3ff4270fca5ec45b186ebbe4e2cf3fc778630f5f6:
    "Google “Crucible”",
  "1d024b8eb1498b344dfd87ea3efc0996f7506f235d1d497061a4773c439c25fb":
    "Google “Daedalus”",
  "293c519654c83965baaa50fc5807d4b76fbf587a2972dca4c30cf4e54547f478":
    "Google “Icarus”",
  a4b90990b418581487bb13a2cc67700a3c359804f91bdfb8e377cd0ec80ddc10:
    "Google “Pilot”",
  ee4bbdb775ce60bae142691fabe19e66a30f7e5fb072d88300c47b897aa8fdcb:
    "Google “Rocketeer”",
  bbd9dfbc1f8a71b593942397aa927b473857950aab52e81a909664368e1ed185:
    "Google “Skydiver”",
  "52eb4b225ec896974850675f23e43bc1d021e3214ce52ecd5fa87c203cdfca03":
    "Google “Solera2018”",
  "0b760e9a8b9a682f88985b15e947501a56446bba8830785c3842994386450c00":
    "Google “Solera2019”",
  "1fc72ce5a1b799f400c359bff96ca3913548e8644220610952e9ba1774f7bac7":
    "Google “Solera2020”",
  a3c99845e80ab7ce00157b3742df0207dd272b2b602ecf98ee2c12db9c5ae7e7:
    "Google “Solera2021”",
  "697aafca1a6b536fae21205046debad7e0eaea13d2432e6e9d8fb379f2b9aaf3":
    "Google “Solera2022”",
  a899d8780c9290aaf462f31880ccfbd52451e970d0fbf591ef75b0d99b645681:
    "Google “Submariner”",
  b0cc83e5a5f97d6baf7c09cc284904872ac7e88b132c6350b7c6fd26e16c6c77:
    "Google “Testtube”",
  b10cd559a6d67846811f7df9a51532739ac48d703bea0323da5d38755bc0ad4e:
    "Google “Xenon2018”",
  "084114980071532c16190460bcfc47fdc2653afa292c72b37ff863ae29ccc9f0":
    "Google “Xenon2019”",
  "07b75c1be57d68fff1b0c61d2315c7bae6577c5794b76aeebc613a1a69d3a21c":
    "Google “Xenon2020”",
  "7d3ef2f88fff88556824c2c0ca9e5289792bc50e78097f2e6a9768997e22f0d7":
    "Google “Xenon2021”",
  "46a555eb75fa912030b5a28969f4f37d112c4174befd49b885abf2fc70fe6d47":
    "Google “Xenon2022”",
  "7461b4a09cfb3d41d75159575b2e7649a445a8d27709b0cc564a6482b7eb41a3": "Izenpe",
  "8941449c70742e06b9fc9ce7b116ba0024aa36d59af44f0204404f00f7ea8566":
    "Izenpe “Argi”",
  "296afa2d568bca0d2ea844956ae9721fc35fa355ecda99693aafd458a71aefdd":
    "Let“s Encrypt ”Clicky”",
  "537b69a3564335a9c04904e39593b2c298eb8d7a6e83023635c627248cd6b440":
    "Nordu “flimsy”",
  aae70b7f3cb8d566c86c2f16979c9f445f69ab0eb4535589b2f77a030104f3cd:
    "Nordu “plausible”",
  e0127629e90496564e3d0147984498aa48f8adb16600eb7902a1ef9909906273:
    "PuChuangSiDa CT",
  cf55e28923497c340d5206d05353aeb25834b52f1f8dc9526809f212efdd7ca6:
    "SHECA CT 1",
  "32dc59c2d4c41968d56e14bc61ac8f0e45db39faf3c155aa4252f5001fa0c623":
    "SHECA CT 2",
  db76fdadac65e7d09508886e2159bd8b90352f5fead3e3dc5e22eb350acc7b98:
    "Sectigo (Comodo) “Dodo” CT",
  "6f5376ac31f03119d89900a45115ff77151c11d902c10029068db2089a37d913":
    "Sectigo (Comodo) “Mammoth” CT",
  "5581d4c2169036014aea0b9b573c53f0c0e43878702508172fa3aa1d0713d30c":
    "Sectigo (Comodo) “Sabre” CT",
  "34bb6ad6c3df9c03eea8a499ff7891486c9d5e5cac92d01f7bfd1bce19db48ef":
    "StartCom",
  ddeb1d2b7a0d4fa6208b81ad8168707e2e8e9d01d55c888d3d11c4cdb6ecbecc: "Symantec",
  a7ce4a4e6207e0addee5fdaa4b1f86768767b5d002a55d47310e7e670a95eab2:
    "Symantec Deneb",
  "15970488d7b997a05beb52512adee8d2e8b4a3165264121a9fabfbd5f85ad93f":
    "Symantec “Sirius”",
  bc78e1dfc5f63c684649334da10fa15f0979692009c081b4f3f6917f3ed9b8a5:
    "Symantec “Vega”",
  b0b784bc81c0ddc47544e883f05985bb9077d134d8ab88b2b2e533980b8e508b:
    "Up In The Air “Behind the Sofa”",
  ac3b9aed7fa9674757159e6d7d575672f9d98100941e9bdeffeca1313b75782d: "Venafi",
  "03019df3fd85a69a8ebd1facc6da9ba73e469774fe77f579fc5a08b8328c1d6b":
    "Venafi Gen2 CT",
  "41b2dc2e89e63ce4af1ba7bb29bf68c6dee6f9f1cc047e30dffae3b3ba259263": "WoSign",
  "63d0006026dde10bb0601f452446965ee2b6ea2cd4fbc95ac866a550af9075b7":
    "WoSign 2",
  "9e4ff73dc3ce220b69217c899e468076abf8d78636d5ccfc85a31a75628ba88b":
    "WoSign CT #1",
  "659b3350f43b12cc5ea5ab4ec765d3fde6c88243777778e72003f9eb2b8c3129":
    "Let's Encrypt Oak 2019",
  e712f2b0377e1a62fb8ec90c6184f1ea7b37cb561d11265bf3e0f34bf241546e:
    "Let's Encrypt Oak 2020",
  "9420bc1e8ed58d6c88731f828b222c0dd1da4d5e6c4f943d61db4e2f584da2c2":
    "Let's Encrypt Oak 2021",
  dfa55eab68824f1f6cadeeb85f4e3e5aeacda212a46a5e8e3b12c020445c2a73:
    "Let's Encrypt Oak 2022",
  b73efb24df9c4dba75f239c5ba58f46c5dfc42cf7a9f35c49e1d098125edb499:
    "Let's Encrypt Oak 2023",
  "849f5f7f58d2bf7b54ecbd74611cea45c49c98f1d6481bc6f69e8c174f24f3cf":
    "Let's Encrypt Testflume 2019",
  c63f2218c37d56a6aa06b596da8e53d4d7156d1e9bac8e44d2202de64d69d9dc:
    "Let's Encrypt Testflume 2020",
  "03edf1da9776b6f38c341e39ed9d707a7570369cf9844f327fe9e14138361b60":
    "Let's Encrypt Testflume 2021",
  "2327efda352510dbc019ef491ae3ff1cc5a479bce37878360ee318cffb64f8c8":
    "Let's Encrypt Testflume 2022",
  "5534b7ab5a6ac3a7cbeba65487b2a2d71b48f650fa17c5197c97a0cb2076f3c6":
    "Let's Encrypt Testflume 2023",
};

const strings = {
  ux: {
    upload: "Upload Certificate",
  },

  names: {
    // Directory Pilot Attributes
    "0.9.2342.19200300.100.1.1": {
      short: "uid",
      long: "User ID",
    },
    "0.9.2342.19200300.100.1.25": {
      short: "dc",
      long: "Domain Component",
    },

    // PKCS-9
    "1.2.840.113549.1.9.1": {
      short: "e",
      long: "Email Address",
    },

    // Incorporated Locations
    "1.3.6.1.4.1.311.60.2.1.1": {
      short: undefined,
      long: "Inc. Locality",
    },
    "1.3.6.1.4.1.311.60.2.1.2": {
      short: undefined,
      long: "Inc. State / Province",
    },
    "1.3.6.1.4.1.311.60.2.1.3": {
      short: undefined,
      long: "Inc. Country",
    },

    // microsoft cryptographic extensions
    "1.3.6.1.4.1.311.21.7": {
      name: {
        short: "Certificate Template",
        long: "Microsoft Certificate Template",
      },
    },
    "1.3.6.1.4.1.311.21.10": {
      name: {
        short: "Certificate Policies",
        long: "Microsoft Certificate Policies",
      },
    },

    // certificate extensions
    "1.3.6.1.4.1.11129.2.4.2": {
      name: {
        short: "Embedded SCTs",
        long: "Embedded Signed Certificate Timestamps",
      },
    },
    "1.3.6.1.5.5.7.1.1": {
      name: {
        short: undefined,
        long: "Authority Information Access",
      },
    },
    "1.3.6.1.5.5.7.1.24": {
      name: {
        short: "OCSP Stapling",
        long: "Online Certificate Status Protocol Stapling",
      },
    },

    // X.500 attribute types
    "2.5.4.1": {
      short: undefined,
      long: "Aliased Entry",
    },
    "2.5.4.2": {
      short: undefined,
      long: "Knowledge Information",
    },
    "2.5.4.3": {
      short: "cn",
      long: "Common Name",
    },
    "2.5.4.4": {
      short: "sn",
      long: "Surname",
    },
    "2.5.4.5": {
      short: "serialNumber",
      long: "Serial Number",
    },
    "2.5.4.6": {
      short: "c",
      long: "Country",
    },
    "2.5.4.7": {
      short: "l",
      long: "Locality",
    },
    "2.5.4.8": {
      short: "s",
      long: "State / Province",
    },
    "2.5.4.9": {
      short: "street",
      long: "Stress Address",
    },
    "2.5.4.10": {
      short: "o",
      long: "Organization",
    },
    "2.5.4.11": {
      short: "ou",
      long: "Organizational Unit",
    },
    "2.5.4.12": {
      short: "t",
      long: "Title",
    },
    "2.5.4.13": {
      short: "description",
      long: "Description",
    },
    "2.5.4.14": {
      short: undefined,
      long: "Search Guide",
    },
    "2.5.4.15": {
      short: undefined,
      long: "Business Category",
    },
    "2.5.4.16": {
      short: undefined,
      long: "Postal Address",
    },
    "2.5.4.17": {
      short: "postalCode",
      long: "Postal Code",
    },
    "2.5.4.18": {
      short: "POBox",
      long: "PO Box",
    },
    "2.5.4.19": {
      short: undefined,
      long: "Physical Delivery Office Name",
    },
    "2.5.4.20": {
      short: "phone",
      long: "Phone Number",
    },
    "2.5.4.21": {
      short: undefined,
      long: "Telex Number",
    },
    "2.5.4.22": {
      short: undefined,
      long: "Teletex Terminal Identifier",
    },
    "2.5.4.23": {
      short: undefined,
      long: "Fax Number",
    },
    "2.5.4.24": {
      short: undefined,
      long: "X.121 Address",
    },
    "2.5.4.25": {
      short: undefined,
      long: "International ISDN Number",
    },
    "2.5.4.26": {
      short: undefined,
      long: "Registered Address",
    },
    "2.5.4.27": {
      short: undefined,
      long: "Destination Indicator",
    },
    "2.5.4.28": {
      short: undefined,
      long: "Preferred Delivery Method",
    },
    "2.5.4.29": {
      short: undefined,
      long: "Presentation Address",
    },
    "2.5.4.30": {
      short: undefined,
      long: "Supported Application Context",
    },
    "2.5.4.31": {
      short: undefined,
      long: "Member",
    },
    "2.5.4.32": {
      short: undefined,
      long: "Owner",
    },
    "2.5.4.33": {
      short: undefined,
      long: "Role Occupant",
    },
    "2.5.4.34": {
      short: undefined,
      long: "See Also",
    },
    "2.5.4.35": {
      short: undefined,
      long: "User Password",
    },
    "2.5.4.36": {
      short: undefined,
      long: "User Certificate",
    },
    "2.5.4.37": {
      short: undefined,
      long: "CA Certificate",
    },
    "2.5.4.38": {
      short: undefined,
      long: "Authority Revocation List",
    },
    "2.5.4.39": {
      short: undefined,
      long: "Certificate Revocation List",
    },
    "2.5.4.40": {
      short: undefined,
      long: "Cross-certificate Pair",
    },
    "2.5.4.41": {
      short: undefined,
      long: "Name",
    },
    "2.5.4.42": {
      short: "g",
      long: "Given Name",
    },
    "2.5.4.43": {
      short: "i",
      long: "Initials",
    },
    "2.5.4.44": {
      short: undefined,
      long: "Generation Qualifier",
    },
    "2.5.4.45": {
      short: undefined,
      long: "Unique Identifier",
    },
    "2.5.4.46": {
      short: undefined,
      long: "DN Qualifier",
    },
    "2.5.4.47": {
      short: undefined,
      long: "Enhanced Search Guide",
    },
    "2.5.4.48": {
      short: undefined,
      long: "Protocol Information",
    },
    "2.5.4.49": {
      short: "dn",
      long: "Distinguished Name",
    },
    "2.5.4.50": {
      short: undefined,
      long: "Unique Member",
    },
    "2.5.4.51": {
      short: undefined,
      long: "House Identifier",
    },
    "2.5.4.52": {
      short: undefined,
      long: "Supported Algorithms",
    },
    "2.5.4.53": {
      short: undefined,
      long: "Delta Revocation List",
    },
    "2.5.4.58": {
      short: undefined,
      long: "Attribute Certificate Attribute", // huh
    },
    "2.5.4.65": {
      short: undefined,
      long: "Pseudonym",
    },

    // extensions
    "2.5.29.14": {
      name: {
        short: "Subject Key ID",
        long: "Subject Key Identifier",
      },
    },
    "2.5.29.15": {
      name: {
        short: undefined,
        long: "Key Usages",
      },
    },
    "2.5.29.17": {
      name: {
        short: "Subject Alt Names",
        long: "Subject Alternative Names",
      },
    },
    "2.5.29.19": {
      name: {
        short: undefined,
        long: "Basic Constraints",
      },
    },
    "2.5.29.31": {
      name: {
        short: "CRL Endpoints",
        long: "Certificate Revocation List Endpoints",
      },
    },
    "2.5.29.32": {
      name: {
        short: undefined,
        long: "Certificate Policies",
      },
    },
    "2.5.29.35": {
      name: {
        short: "Authority Key ID",
        long: "Authority Key Identifier",
      },
    },
    "2.5.29.37": {
      name: {
        short: undefined,
        long: "Extended Key Usages",
      },
    },
  },

  keyUsages: [
    "Digital Signature",
    "Non-Repudiation",
    "Key Encipherment",
    "Data Encipherment",
    "Key Agreement",
    "Certificate Signing",
    "CRL Signing",
    "Encipher Only",
    "Decipher Only",
  ],

  san: [
    "Other Name",
    "RFC 822 Name",
    "DNS Name",
    "X.400 Address",
    "Directory Name",
    "EDI Party Name",
    "URI",
    "IP Address",
    "Registered ID",
  ],

  eKU: {
    "1.3.6.1.4.1.311.10.3.1": "Certificate Trust List (CTL) Signing",
    "1.3.6.1.4.1.311.10.3.2": "Timestamp Signing",
    "1.3.6.1.4.1.311.10.3.4": "EFS Encryption",
    "1.3.6.1.4.1.311.10.3.4.1": "EFS Recovery",
    "1.3.6.1.4.1.311.10.3.5":
      "Windows Hardware Quality Labs (WHQL) Cryptography",
    "1.3.6.1.4.1.311.10.3.7": "Windows NT 5 Cryptography",
    "1.3.6.1.4.1.311.10.3.8": "Windows NT Embedded Cryptography",
    "1.3.6.1.4.1.311.10.3.10": "Qualified Subordination",
    "1.3.6.1.4.1.311.10.3.11": "Escrowed Key Recovery",
    "1.3.6.1.4.1.311.10.3.12": "Document Signing",
    "1.3.6.1.4.1.311.10.5.1": "Digital Rights Management",
    "1.3.6.1.4.1.311.10.6.1": "Key Pack Licenses",
    "1.3.6.1.4.1.311.10.6.2": "License Server",
    "1.3.6.1.4.1.311.20.2.1": "Enrollment Agent",
    "1.3.6.1.4.1.311.20.2.2": "Smartcard Login",
    "1.3.6.1.4.1.311.21.5": "Certificate Authority Private Key Archival",
    "1.3.6.1.4.1.311.21.6": "Key Recovery Agent",
    "1.3.6.1.4.1.311.21.19": "Directory Service Email Replication",
    "1.3.6.1.5.5.7.3.1": "Server Authentication",
    "1.3.6.1.5.5.7.3.2": "Client Authentication",
    "1.3.6.1.5.5.7.3.3": "Code Signing",
    "1.3.6.1.5.5.7.3.4": "E-mail Protection",
    "1.3.6.1.5.5.7.3.5": "IPsec End System",
    "1.3.6.1.5.5.7.3.6": "IPsec Tunnel",
    "1.3.6.1.5.5.7.3.7": "IPSec User",
    "1.3.6.1.5.5.7.3.8": "Timestamping",
    "1.3.6.1.5.5.7.3.9": "OCSP Signing",
    "1.3.6.1.5.5.8.2.2": "Internet Key Exchange (IKE)",
  },

  signature: {
    "1.2.840.113549.1.1.4": "MD5 with RSA Encryption",
    "1.2.840.113549.1.1.5": "SHA-1 with RSA Encryption",
    "1.2.840.113549.1.1.11": "SHA-256 with RSA Encryption",
    "1.2.840.113549.1.1.12": "SHA-384 with RSA Encryption",
    "1.2.840.113549.1.1.13": "SHA-512 with RSA Encryption",
    "1.2.840.10040.4.3": "DSA with SHA-1",
    "2.16.840.1.101.3.4.3.2": "DSA with SHA-256",
    "1.2.840.10045.4.1": "ECDSA with SHA-1",
    "1.2.840.10045.4.3.2": "ECDSA with SHA-256",
    "1.2.840.10045.4.3.3": "ECDSA with SHA-384",
    "1.2.840.10045.4.3.4": "ECDSA with SHA-512",
  },

  aia: {
    "1.3.6.1.5.5.7.48.1": "Online Certificate Status Protocol (OCSP)",
    "1.3.6.1.5.5.7.48.2": "CA Issuers",
  },

  // this includes qualifiers as well
  cps: {
    "1.3.6.1.4.1": {
      name: "Statement Identifier",
      value: undefined,
    },
    "1.3.6.1.5.5.7.2.1": {
      name: "Practices Statement",
      value: undefined,
    },
    "1.3.6.1.5.5.7.2.2": {
      name: "User Notice",
      value: undefined,
    },
    "2.16.840": {
      name: "ANSI Organizational Identifier",
      value: undefined,
    },
    "2.23.140.1.1": {
      name: "Certificate Type",
      value: "Extended Validation",
    },
    "2.23.140.1.2.1": {
      name: "Certificate Type",
      value: "Domain Validation",
    },
    "2.23.140.1.2.2": {
      name: "Certificate Type",
      value: "Organization Validation",
    },
    "2.23.140.1.2.3": {
      name: "Certificate Type",
      value: "Individual Validation",
    },
    "2.23.140.1.3": {
      name: "Certificate Type",
      value: "Extended Validation (Code Signing)",
    },
    "2.23.140.1.31": {
      name: "Certificate Type",
      value: ".onion Extended Validation",
    },
    "2.23.140.2.1": {
      name: "Certificate Type",
      value: "Test Certificate",
    },
  },

  microsoftCertificateTypes: {
    Administrator: "Administrator",
    CA: "Root Certification Authority",
    CAExchange: "CA Exchange",
    CEPEncryption: "CEP Encryption",
    CertificateRequestAgent: "Certificate Request Agent",
    ClientAuth: "Authenticated Session",
    CodeSigning: "Code Signing",
    CrossCA: "Cross Certification Authority",
    CTLSigning: "Trust List Signing",
    DirectoryEmailReplication: "Directory Email Replication",
    DomainController: "Domain Controller",
    DomainControllerAuthentication: "Domain Controller Authentication",
    EFS: "Basic EFS",
    EFSRecovery: "EFS Recovery Agent",
    EnrollmentAgent: "Enrollment Agent",
    EnrollmentAgentOffline: "Exchange Enrollment Agent (Offline request)",
    ExchangeUser: "Exchange User",
    ExchangeUserSignature: "Exchange Signature Only",
    IPSECIntermediateOffline: "IPSec (Offline request)",
    IPSECIntermediateOnline: "IPSEC",
    KerberosAuthentication: "Kerberos Authentication",
    KeyRecoveryAgent: "Key Recovery Agent",
    Machine: "Computer",
    MachineEnrollmentAgent: "Enrollment Agent (Computer)",
    OCSPResponseSigning: "OCSP Response Signing",
    OfflineRouter: "Router (Offline request)",
    RASAndIASServer: "RAS and IAS Server",
    SmartcardLogon: "Smartcard Logon",
    SmartcardUser: "Smartcard User",
    SubCA: "Subordinate Certification Authority",
    User: "User",
    UserSignature: "User Signature Only",
    WebServer: "Web Server",
    Workstation: "Workstation Authentication",
  },
};

function stringToArrayBuffer(string) {
  let result = new Uint8Array(string.length);
  for (let i = 0; i < string.length; i++) {
    result[i] = string.charCodeAt(i);
  }
  return result;
}

// this particular prototype override makes it easy to chain down complex objects
const getObjPath = (obj, path) => {
  path = path.split(".");
  for (let i = 0, len = path.length; i < len; i++) {
    if (Array.isArray(obj[path[i]])) {
      obj = obj[path[i]][path[i + 1]];
      i++;
    } else {
      obj = obj[path[i]];
    }
  }
  return obj;
};

const arrayBufferToHex = arrayBuffer => {
  const array = Array.from(new Uint8Array(arrayBuffer));

  return array
    .map(b => ("00" + b.toString(16)).slice(-2))
    .join(":")
    .toUpperCase();
};

const hash = async (algo, buffer) => {
  const hashBuffer = await crypto.subtle.digest(algo, buffer);
  return arrayBufferToHex(hashBuffer);
};

const hashify = rawHash => {
  if (typeof rawHash === "string") {
    return rawHash.match(/.{2}/g).join(":").toUpperCase();
  }
  if (rawHash instanceof ArrayBuffer) {
    return arrayBufferToHex(rawHash);
  }
  return rawHash.join(":").toUpperCase();
};

export const pemToDER = pem => {
  return stringToArrayBuffer(atob(pem));
};
PK
!<X��v��7chrome/toolkit/content/global/certviewer/certviewer.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

body {
  max-width: 800px;
  margin: 0 auto;
}
PK
!<�O�
�
8chrome/toolkit/content/global/certviewer/certviewer.html<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html>

<html>
  <head>
    <meta name="viewport" content="width=device-width" />
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:; object-src 'none'"
    />
    <meta name="color-scheme" content="light dark" />
    <link rel="localization" href="toolkit/about/certviewer.ftl" />
    <link rel="localization" href="branding/brand.ftl" />
    <script
      type="module"
      src="chrome://global/content/certviewer/certviewer.mjs"
    ></script>
    <script
      type="module"
      src="chrome://global/content/certviewer/components/certificate-section.mjs"
    ></script>
    <script
      type="module"
      src="chrome://global/content/certviewer/components/about-certificate-section.mjs"
    ></script>
    <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
    <link
      rel="stylesheet"
      href="chrome://global/content/certviewer/certviewer.css"
    />
    <title id="certTitle">about:certificate</title>
  </head>
  <body>
    <template id="certificate-section-template" class="section">
      <link
        rel="stylesheet"
        href="chrome://global/content/certviewer/components/certificate-section.css"
      />
      <h1 class="title"></h1>
    </template>

    <template id="certificate-tabs-template">
      <div class="certificate-tabs" role="tablist"></div>
    </template>

    <template id="info-groups-template"> </template>

    <template id="info-item-template">
      <link
        rel="stylesheet"
        href="chrome://global/skin/in-content/common.css"
      />
      <link
        rel="stylesheet"
        href="chrome://global/content/certviewer/components/info-item.css"
      />
      <label></label>
      <span class="info"></span>
    </template>

    <template id="info-group-template">
      <link
        rel="stylesheet"
        href="chrome://global/content/certviewer/components/info-group.css"
      />
      <span class="extension">
        <img
          src="chrome://global/skin/icons/error.svg"
          id="critical-info"
          data-l10n-id="certificate-viewer-critical-extension"
        />
        <h2 class="info-group-title"></h2>
      </span>
      <span class="info-group-title-hr"></span>
    </template>

    <template id="error-section-template">
      <link
        rel="stylesheet"
        href="chrome://global/content/certviewer/components/error-section.css"
      />
      <h1 class="title"></h1>
      <span class="error"></span>
    </template>

    <template id="about-certificate-template" class="section">
      <link
        rel="stylesheet"
        href="chrome://global/content/certviewer/components/certificate-section.css"
      />
      <h1 class="title"></h1>
    </template>

    <template id="about-certificate-items-template">
      <link
        rel="stylesheet"
        href="chrome://global/content/certviewer/components/about-certificate-section.css"
      />
    </template>

    <template id="list-item-template">
      <link
        rel="stylesheet"
        href="chrome://global/content/certviewer/components/list-item.css"
      />
      <a class="cert-url"><span class="item-name"></span></a>
      <button class="export"></button>
    </template>
  </body>
</html>
PK
!<���/�/7chrome/toolkit/content/global/certviewer/certviewer.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/remote-page */

import { normalizeToKebabCase } from "./components/utils.mjs";
import {
  parse,
  pemToDER,
} from "chrome://global/content/certviewer/certDecoder.mjs";

document.addEventListener("DOMContentLoaded", async () => {
  let url = new URL(document.URL);
  let certInfo = url.searchParams.getAll("cert");
  if (certInfo.length === 0) {
    render({}, false, true);
    return;
  }
  certInfo = certInfo.map(cert => decodeURIComponent(cert));
  await buildChain(certInfo);
});

export const updateSelectedItem = (() => {
  let state;
  return selectedItem => {
    let certificateSection =
      document.querySelector("certificate-section") ||
      document.querySelector("about-certificate-section");
    if (selectedItem) {
      if (state !== selectedItem) {
        state = selectedItem;
        certificateSection.updateCertificateSource(selectedItem);
        certificateSection.updateSelectedTab(selectedItem);
      }
    }
    return state;
  };
})();

const createEntryItem = (labelId, info, isHex = false) => {
  if (
    labelId == null ||
    info == null ||
    (Array.isArray(info) && !info.length)
  ) {
    return null;
  }
  return {
    labelId,
    info,
    isHex,
  };
};

const addToResultUsing = (callback, certItems, sectionId, Critical) => {
  let items = callback();
  if (items.length) {
    certItems.push({
      sectionId,
      sectionItems: items,
      Critical,
    });
  }
};

const getElementByPathOrFalse = (obj, pathString) => {
  let pathArray = pathString.split(".");
  let result = obj;
  for (let entry of pathArray) {
    result = result[entry];
    if (result == null) {
      return false;
    }
  }
  return result ? result : false;
};

export const adjustCertInformation = cert => {
  let certItems = [];
  let tabName = cert?.subject?.cn || "";
  if (cert && !tabName) {
    // No common name, use the value of the last item in the cert's entries.
    tabName = cert.subject?.entries?.slice(-1)[0]?.[1] || "";
  }

  if (!cert) {
    return {
      certItems,
      tabName,
    };
  }

  addToResultUsing(
    () => {
      let items = [];
      if (cert.subject && cert.subject.entries) {
        items = cert.subject.entries
          .map(entry =>
            createEntryItem(normalizeToKebabCase(entry[0]), entry[1])
          )
          .filter(elem => elem != null);
      }
      return items;
    },
    certItems,
    "subject-name",
    false
  );

  addToResultUsing(
    () => {
      let items = [];
      if (cert.issuer && cert.issuer.entries) {
        items = cert.issuer.entries
          .map(entry =>
            createEntryItem(normalizeToKebabCase(entry[0]), entry[1])
          )
          .filter(elem => elem != null);
      }
      return items;
    },
    certItems,
    "issuer-name",
    false
  );

  addToResultUsing(
    () => {
      let items = [];
      if (cert.notBefore && cert.notAfter) {
        items = [
          createEntryItem("not-before", {
            local: cert.notBefore,
            utc: cert.notBeforeUTC,
          }),
          createEntryItem("not-after", {
            local: cert.notAfter,
            utc: cert.notAfterUTC,
          }),
        ].filter(elem => elem != null);
      }
      return items;
    },
    certItems,
    "validity",
    false
  );

  addToResultUsing(
    () => {
      let items = [];
      if (cert.ext && cert.ext.san && cert.ext.san.altNames) {
        items = cert.ext.san.altNames
          .map(entry =>
            createEntryItem(normalizeToKebabCase(entry[0]), entry[1])
          )
          .filter(elem => elem != null);
      }
      return items;
    },
    certItems,
    "subject-alt-names",
    getElementByPathOrFalse(cert, "ext.san.critical")
  );

  addToResultUsing(
    () => {
      let items = [];
      if (cert.subjectPublicKeyInfo) {
        items = [
          createEntryItem("algorithm", cert.subjectPublicKeyInfo.kty),
          createEntryItem("key-size", cert.subjectPublicKeyInfo.keysize),
          createEntryItem("curve", cert.subjectPublicKeyInfo.crv),
          createEntryItem("public-value", cert.subjectPublicKeyInfo.xy, true),
          createEntryItem("exponent", cert.subjectPublicKeyInfo.e),
          createEntryItem("modulus", cert.subjectPublicKeyInfo.n, true),
        ].filter(elem => elem != null);
      }
      return items;
    },
    certItems,
    "public-key-info",
    false
  );

  addToResultUsing(
    () => {
      let items = [
        createEntryItem("serial-number", cert.serialNumber, true),
        createEntryItem(
          "signature-algorithm",
          cert.signature ? cert.signature.name : null
        ),
        createEntryItem("version", cert.version),
        createEntryItem("download", cert.files ? cert.files.pem : null),
      ].filter(elem => elem != null);
      return items;
    },
    certItems,
    "miscellaneous",
    false
  );

  addToResultUsing(
    () => {
      let items = [];
      if (cert.fingerprint) {
        items = [
          createEntryItem("sha-256", cert.fingerprint.sha256, true),
          createEntryItem("sha-1", cert.fingerprint.sha1, true),
        ].filter(elem => elem != null);
      }
      return items;
    },
    certItems,
    "fingerprints",
    false
  );

  if (!cert.ext) {
    return {
      certItems,
      tabName,
    };
  }

  addToResultUsing(
    () => {
      let items = [];
      if (cert.ext.basicConstraints) {
        items = [
          createEntryItem(
            "certificate-authority",
            cert.ext.basicConstraints.cA
          ),
        ].filter(elem => elem != null);
      }
      return items;
    },
    certItems,
    "basic-constraints",
    getElementByPathOrFalse(cert, "ext.basicConstraints.critical")
  );

  addToResultUsing(
    () => {
      let items = [];
      if (cert.ext.keyUsages) {
        items = [
          createEntryItem("purposes", cert.ext.keyUsages.purposes),
        ].filter(elem => elem != null);
      }
      return items;
    },
    certItems,
    "key-usages",
    getElementByPathOrFalse(cert, "ext.keyUsages.critical")
  );

  addToResultUsing(
    () => {
      let items = [];
      if (cert.ext.eKeyUsages) {
        items = [
          createEntryItem("purposes", cert.ext.eKeyUsages.purposes),
        ].filter(elem => elem != null);
      }
      return items;
    },
    certItems,
    "extended-key-usages",
    getElementByPathOrFalse(cert, "ext.eKeyUsages.critical")
  );

  addToResultUsing(
    () => {
      let items = [];
      if (cert.ext.ocspStaple && cert.ext.ocspStaple.required) {
        items = [createEntryItem("required", true)];
      }
      return items;
    },
    certItems,
    "ocsp-stapling",
    getElementByPathOrFalse(cert, "ext.ocspStaple.critical")
  );

  addToResultUsing(
    () => {
      let items = [];
      if (cert.ext.sKID) {
        items = [createEntryItem("key-id", cert.ext.sKID.id, true)].filter(
          elem => elem != null
        );
      }
      return items;
    },
    certItems,
    "subject-key-id",
    getElementByPathOrFalse(cert, "ext.sKID.critical")
  );

  addToResultUsing(
    () => {
      let items = [];
      if (cert.ext.aKID) {
        items = [createEntryItem("key-id", cert.ext.aKID.id, true)].filter(
          elem => elem != null
        );
      }
      return items;
    },
    certItems,
    "authority-key-id",
    getElementByPathOrFalse(cert, "ext.aKID.critical")
  );

  addToResultUsing(
    () => {
      let items = [];
      if (cert.ext.crlPoints && cert.ext.crlPoints.points) {
        items = cert.ext.crlPoints.points
          .map(entry => {
            let label = "distribution-point";
            return createEntryItem(label, entry);
          })
          .filter(elem => elem != null);
      }
      return items;
    },
    certItems,
    "crl-endpoints",
    getElementByPathOrFalse(cert, "ext.crlPoints.critical")
  );

  addToResultUsing(
    () => {
      let items = [];
      if (cert.ext.aia && cert.ext.aia.descriptions) {
        cert.ext.aia.descriptions.forEach(entry => {
          items.push(createEntryItem("location", entry.location));
          items.push(createEntryItem("method", entry.method));
        });
      }
      return items.filter(elem => elem != null);
    },
    certItems,
    "authority-info-aia",
    getElementByPathOrFalse(cert, "ext.aia.critical")
  );

  addToResultUsing(
    () => {
      let items = [];
      if (cert.ext.cp && cert.ext.cp.policies) {
        cert.ext.cp.policies.forEach(entry => {
          if (entry.name && entry.id) {
            items.push(
              createEntryItem("policy", entry.name + " ( " + entry.id + " )")
            );
          }
          items.push(createEntryItem("value", entry.value));
          if (entry.qualifiers) {
            entry.qualifiers.forEach(qualifier => {
              if (qualifier.qualifierName && qualifier.qualifierId) {
                items.push(
                  createEntryItem(
                    "qualifier",
                    qualifier.qualifierName +
                      " ( " +
                      qualifier.qualifierId +
                      " )"
                  )
                );
              }
              items.push(createEntryItem("value", qualifier.qualifierValue));
            });
          }
        });
      }
      return items.filter(elem => elem != null);
    },
    certItems,
    "certificate-policies",
    getElementByPathOrFalse(cert, "ext.cp.critical")
  );

  addToResultUsing(
    () => {
      let items = [];
      if (cert.ext.scts && cert.ext.scts.timestamps) {
        cert.ext.scts.timestamps.forEach(entry => {
          let timestamps = {};
          for (let key of Object.keys(entry)) {
            if (key.includes("timestamp")) {
              timestamps[key.includes("UTC") ? "utc" : "local"] = entry[key];
            } else {
              let isHex = false;
              if (key == "logId") {
                isHex = true;
              }
              items.push(
                createEntryItem(normalizeToKebabCase(key), entry[key], isHex)
              );
            }
          }
          items.push(createEntryItem("timestamp", timestamps));
        });
      }
      return items.filter(elem => elem != null);
    },
    certItems,
    "embedded-scts",
    getElementByPathOrFalse(cert, "ext.scts.critical")
  );

  return {
    certItems,
    tabName,
  };
};

// isAboutCertificate means to the standalone page about:certificate, which
// uses a different customElement than opening a certain certificate
const render = async (certs, error, isAboutCertificate = false) => {
  if (isAboutCertificate) {
    await customElements.whenDefined("about-certificate-section");
    const AboutCertificateSection = customElements.get(
      "about-certificate-section"
    );
    document.querySelector("body").append(new AboutCertificateSection());
  } else {
    await customElements.whenDefined("certificate-section");
    const CertificateSection = customElements.get("certificate-section");
    document.querySelector("body").append(new CertificateSection(certs, error));
  }
  return Promise.resolve();
};

const buildChain = async chain => {
  await Promise.all(
    chain
      .map(cert => {
        try {
          return pemToDER(cert);
        } catch (err) {
          return Promise.reject(err);
        }
      })
      .map(cert => {
        try {
          return parse(cert);
        } catch (err) {
          return Promise.reject(err);
        }
      })
  )
    .then(certs => {
      if (certs.length === 0) {
        return Promise.reject();
      }
      let certTitle = document.querySelector("#certTitle");
      let firstCertCommonName = certs[0].subject.cn;
      document.l10n.setAttributes(certTitle, "certificate-viewer-tab-title", {
        firstCertName: firstCertCommonName,
      });

      let adjustedCerts = certs.map(cert => adjustCertInformation(cert));
      return render(adjustedCerts, false);
    })
    .catch(() => {
      render(null, true);
    });
};
PK
!<��OB��Ochrome/toolkit/content/global/certviewer/components/about-certificate-items.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/remote-page */

import { ListItem } from "./list-item.mjs";

export class AboutCertificateItems extends HTMLElement {
  constructor(id, data) {
    super();
    this.id = id;
    this.data = data;
  }

  connectedCallback() {
    let template = document.getElementById("about-certificate-items-template");
    let templateHtml = template.content.cloneNode(true);

    this.attachShadow({ mode: "open" }).appendChild(templateHtml);

    document.l10n.connectRoot(this.shadowRoot);

    this.render();
  }

  render() {
    for (let cert of this.data) {
      this.shadowRoot.append(new ListItem(cert));
    }
  }
}
customElements.define("about-certificate-items", AboutCertificateItems);
PK
!<�QM��Qchrome/toolkit/content/global/certviewer/components/about-certificate-section.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:host {
  margin: 2em;
}
PK
!<vg��Qchrome/toolkit/content/global/certviewer/components/about-certificate-section.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/remote-page */

import { InfoGroupContainer } from "./info-group-container.mjs";
import { CertificateTabsSection } from "./certificate-tabs-section.mjs";

const TYPE_CA = 1;
const TYPE_USER = 2;
const TYPE_EMAIL = 4;
const TYPE_SERVER = 8;

export class AboutCertificateSection extends HTMLElement {
  constructor() {
    super();
  }

  connectedCallback() {
    let template = document.getElementById("about-certificate-template");
    let templateHtml = template.content.cloneNode(true);

    this.attachShadow({ mode: "open" }).appendChild(templateHtml);

    document.l10n.connectRoot(this.shadowRoot);

    this.certificateTabsSection = new CertificateTabsSection(true);
    this.shadowRoot.appendChild(this.certificateTabsSection.tabsElement);
    this.infoGroupsContainers = new InfoGroupContainer(true);

    this.render();
  }

  render() {
    RPMSendQuery("getCertificates").then(this.filterCerts.bind(this));

    let title = this.shadowRoot.querySelector(".title");
    document.l10n.setAttributes(
      title,
      "certificate-viewer-certificate-section-title"
    );
  }

  filterCerts(srcCerts) {
    let certs = [];
    if (srcCerts[TYPE_USER].length) {
      certs.push({
        name: "certificate-viewer-tab-mine",
        data: srcCerts[TYPE_USER],
      });
    }
    if (srcCerts[TYPE_EMAIL].length) {
      certs.push({
        name: "certificate-viewer-tab-people",
        data: srcCerts[TYPE_EMAIL],
      });
    }
    if (srcCerts[TYPE_SERVER].length) {
      certs.push({
        name: "certificate-viewer-tab-servers",
        data: srcCerts[TYPE_SERVER],
      });
    }
    if (srcCerts[TYPE_CA].length) {
      certs.push({
        name: "certificate-viewer-tab-ca",
        data: srcCerts[TYPE_CA],
      });
    }

    let i = 0;
    for (let cert of certs) {
      let final = i == certs.length - 1;
      this.infoGroupsContainers.createInfoGroupsContainers({}, i, final, cert);
      this.shadowRoot.appendChild(
        this.infoGroupsContainers.infoGroupsContainers[i]
      );
      this.certificateTabsSection.createTabSection(cert.name, i);
      this.infoGroupsContainers.addClass("selected", 0);
      i++;
    }
    this.setAccessibilityEventListeners();
    this.addClassForPadding();
  }

  // Adds class selector for items that need padding,
  // as nth-child/parent-based selectors aren't supported
  // due to the encapsulation of custom-element CSS.
  addClassForPadding() {
    let embeddedScts = this.shadowRoot.querySelector(".embedded-scts");
    if (!embeddedScts) {
      return;
    }
    let items = embeddedScts.shadowRoot.querySelectorAll(".timestamp");

    for (let i = 0; i < items.length; i++) {
      items[i].classList.add("padding");
    }
  }

  setAccessibilityEventListeners() {
    this.certificateTabsSection.setAccessibilityEventListeners();
  }

  updateSelectedTab(index) {
    this.certificateTabsSection.updateSelectedTab(index);
  }

  updateCertificateSource(index) {
    this.infoGroupsContainers.updateCertificateSource(index);
  }
}
customElements.define("about-certificate-section", AboutCertificateSection);
PK
!<����Kchrome/toolkit/content/global/certviewer/components/certificate-section.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/design-system/text-and-typography.css");

h1 {
  margin: 3em 0 1em;
}

.certificate-tabs {
  display: flex;
  text-align: center;
  border-bottom: 1px solid var(--in-content-border-color);
  box-shadow: var(--card-shadow);
}

.info-groups {
  display: none;
  outline: none;
  background-color: var(--in-content-box-background);
  box-shadow: var(--card-shadow);
  margin-bottom: 2em;
  border-radius: 0 0 4px 4px;
}

.info-groups.selected {
  display: block;
}

.tab {
  margin: 0;
  border-radius: 0;
  padding: 18px;
  padding-bottom: 15px; /* compensate for border-bottom below */
  border: none;
  border-bottom: 3px solid transparent;
  background-color: var(--in-content-box-background);
  color: var(--in-content-text-color);
  flex: 1 1 auto;
  text-overflow: ellipsis;
  overflow: hidden;
  font-size: 1.1em;
}

/* .tab can be LTR (by `dir="auto"`) for `about:certificate?cert=`
   pages, so set the border-radius according to the parent's direction. */
.certificate-tabs:dir(rtl) > .tab:first-of-type,
.certificate-tabs:dir(ltr) > .tab:last-of-type {
  border-top-right-radius: 4px;
}

.certificate-tabs:dir(ltr) > .tab:first-of-type,
.certificate-tabs:dir(rtl) > .tab:last-of-type {
  border-top-left-radius: 4px;
}

.certificate-tab:focus-visible {
  z-index: 1;
  outline: 2px solid var(--in-content-focus-outline-color);
}

.tab:hover {
  border-bottom-color: var(--in-content-border-color);
}

.tab.selected {
  border-bottom-color: currentColor;
  color: var(--in-content-accent-color);
  text-overflow: unset;
  overflow: visible;
}
PK
!<�*���Kchrome/toolkit/content/global/certviewer/components/certificate-section.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ErrorSection } from "./error-section.mjs";
import { InfoGroupContainer } from "./info-group-container.mjs";
import { CertificateTabsSection } from "./certificate-tabs-section.mjs";

class CertificateSection extends HTMLElement {
  constructor(certs, error) {
    super();
    this.certs = certs;
    this.error = error;
  }

  connectedCallback() {
    // Attach and connect before adding the template, or fluent
    // won't translate the template copy we insert into the
    // shadowroot.
    this.attachShadow({ mode: "open" });
    document.l10n.connectRoot(this.shadowRoot);

    let template = document.getElementById("certificate-section-template");
    let templateHtml = template.content.cloneNode(true);
    this.shadowRoot.appendChild(templateHtml);

    this.certificateTabsSection = new CertificateTabsSection();
    this.shadowRoot.appendChild(this.certificateTabsSection.tabsElement);
    this.infoGroupsContainers = new InfoGroupContainer();

    this.render();
  }

  render() {
    let title = this.shadowRoot.querySelector(".title");
    document.l10n.setAttributes(
      title,
      "certificate-viewer-certificate-section-title"
    );

    if (this.error) {
      title.classList.add("error");
      this.certificateTabsSection.appendChild(new ErrorSection());
      return;
    }
    let final = false;
    for (let i = 0; i < this.certs.length; i++) {
      if (i === this.certs.length - 1) {
        final = true;
      }
      this.infoGroupsContainers.createInfoGroupsContainers(
        this.certs[i].certItems,
        i,
        final
      );
      this.shadowRoot.appendChild(
        this.infoGroupsContainers.infoGroupsContainers[i]
      );
      this.certificateTabsSection.createTabSection(this.certs[i].tabName, i);
      this.infoGroupsContainers.addClass("selected", 0);
    }
    this.setAccessibilityEventListeners();
    this.addClassForPadding();
  }

  // Adds class selector for items that need padding,
  // as nth-child/parent-based selectors aren't supported
  // due to the encapsulation of custom-element CSS.
  addClassForPadding() {
    let embeddedScts = this.shadowRoot.querySelector(".embedded-scts");
    if (!embeddedScts) {
      return;
    }
    let items = embeddedScts.shadowRoot.querySelectorAll(".timestamp");

    for (let i = 0; i < items.length; i++) {
      items[i].classList.add("padding");
    }
  }

  setAccessibilityEventListeners() {
    this.certificateTabsSection.setAccessibilityEventListeners();
  }

  updateSelectedTab(index) {
    this.certificateTabsSection.updateSelectedTab(index);
  }

  updateCertificateSource(index) {
    this.infoGroupsContainers.updateCertificateSource(index);
  }
}
customElements.define("certificate-section", CertificateSection);
PK
!<7�UUPchrome/toolkit/content/global/certviewer/components/certificate-tabs-section.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { normalizeToKebabCase } from "./utils.mjs";
import { updateSelectedItem } from "../certviewer.mjs";

export class CertificateTabsSection extends HTMLElement {
  constructor(isAboutCertificate) {
    super();
    this.isAboutCertificate = isAboutCertificate || false;
    this.connectedCallback();
  }

  connectedCallback() {
    let certificateTabsTemplate = document.getElementById(
      "certificate-tabs-template"
    );
    this.attachShadow({ mode: "open" }).appendChild(
      certificateTabsTemplate.content.cloneNode(true)
    );
    this.render();
  }

  render() {
    this.tabsElement = this.shadowRoot.querySelector(".certificate-tabs");
  }

  appendChild(child) {
    this.tabsElement.appendChild(child);
  }

  createTabSection(tabName, i) {
    let tab = document.createElement("button");
    if (tabName) {
      tab.textContent = tabName;
    } else {
      document.l10n.setAttributes(
        tab,
        "certificate-viewer-unknown-group-label"
      );
    }
    tab.setAttribute("id", normalizeToKebabCase(tabName));
    tab.setAttribute("aria-controls", "panel" + i);
    tab.setAttribute("idnumber", i);
    tab.setAttribute("role", "tab");
    tab.classList.add("certificate-tab");
    tab.classList.add("tab");
    if (this.isAboutCertificate) {
      document.l10n.setAttributes(tab, tabName);
    } else {
      // Display tabs on `about:certificate?cert=` pages as dir=auto
      // to avoid text like `mozilla.org.*` in RTL.
      // Not needed in the standalone version of about:certificate
      // because the tab text there should be localized.
      tab.dir = "auto";
    }
    this.tabsElement.appendChild(tab);

    // If it is the first tab, allow it to be tabbable by the user.
    // If it isn't the first tab, do not allow tab functionality,
    // as arrow functionality is implemented in certviewer.mjs.
    if (i === 0) {
      tab.classList.add("selected");
      tab.setAttribute("tabindex", 0);
    } else {
      tab.setAttribute("tabindex", -1);
    }
  }

  updateSelectedTab(index) {
    let tabs = this.tabsElement.querySelectorAll(".certificate-tab");

    for (let tab of tabs) {
      tab.classList.remove("selected");
    }
    tabs[index].classList.add("selected");
  }

  /* Information on setAccessibilityEventListeners() can be found
   * at https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Roles/Tab_Role */
  setAccessibilityEventListeners() {
    let tabs = this.tabsElement.querySelectorAll('[role="tab"]');

    // Add a click event handler to each tab
    for (let tab of tabs) {
      tab.addEventListener("click", e =>
        updateSelectedItem(e.target.getAttribute("idnumber"))
      );
    }

    // Enable arrow navigation between tabs in the tab list
    let tabFocus = 0;

    this.tabsElement.addEventListener("keydown", e => {
      // Move right
      if (e.keyCode === 39 || e.keyCode === 37) {
        // After navigating away from the current tab,
        // prevent that tab from being tabbable -
        // so as to only allow arrow navigation within the tablist.
        tabs[tabFocus].setAttribute("tabindex", -1);
        if (e.keyCode === 39) {
          tabFocus++;
          // If we're at the end, go to the start
          if (tabFocus > tabs.length - 1) {
            tabFocus = 0;
          }
          // Move left
        } else if (e.keyCode === 37) {
          tabFocus--;
          // If we're at the start, move to the end
          if (tabFocus < 0) {
            tabFocus = tabs.length;
          }
        }
        tabs[tabFocus].setAttribute("tabindex", 0);
        tabs[tabFocus].focus();
      }
    });
  }
}

customElements.define("certificate-tabs-section", CertificateTabsSection);
PK
!<�����Echrome/toolkit/content/global/certviewer/components/error-section.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/design-system/text-and-typography.css");

:host {
  background-image: url("chrome://global/skin/illustrations/error-malformed-url.svg");
  min-height: 300px;
  background-repeat: no-repeat;
  padding-inline-start: 30%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin: 0 auto;
  max-width: 500px;
  background-size: 30%;
  background-position: left center;
}

:host(:dir(rtl)) {
  background-position-x: right;
}
PK
!< j���Echrome/toolkit/content/global/certviewer/components/error-section.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export class ErrorSection extends HTMLElement {
  constructor() {
    super();
  }

  connectedCallback() {
    let template = document.getElementById("error-section-template");
    let templateHtml = template.content.cloneNode(true);

    this.attachShadow({ mode: "open" }).appendChild(templateHtml);

    document.l10n.connectRoot(this.shadowRoot);
    this.render();
  }

  render() {
    let title = this.shadowRoot.querySelector(".title");
    document.l10n.setAttributes(title, "certificate-viewer-error-title");

    let errorMessage = this.shadowRoot.querySelector(".error");
    document.l10n.setAttributes(
      errorMessage,
      "certificate-viewer-error-message"
    );
  }
}
customElements.define("error-section", ErrorSection);
PK
!<V�
���Lchrome/toolkit/content/global/certviewer/components/info-group-container.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { InfoGroup } from "./info-group.mjs";
import { AboutCertificateItems } from "./about-certificate-items.mjs";

export class InfoGroupContainer extends HTMLElement {
  constructor(isAboutCertificate = false) {
    super();
    this.infoGroupsContainers = [];
    this.isAboutCertificate = isAboutCertificate;
  }

  connectedCallback() {
    let infoGroupContainerTemplate = document.getElementById(
      "info-groups-template"
    );
    this.attachShadow({ mode: "open" }).appendChild(
      infoGroupContainerTemplate.content.cloneNode(true)
    );
    this.render();
  }

  render() {}

  createInfoGroupsContainers(certArray, i, final, certData = []) {
    this.infoGroupsContainers[i] = document.createElement("div");
    this.infoGroupsContainers[i].setAttribute("id", "panel" + i);
    this.infoGroupsContainers[i].setAttribute("role", "tabpanel");
    this.infoGroupsContainers[i].setAttribute("tabindex", 0);
    this.infoGroupsContainers[i].setAttribute("aria-labelledby", "tab" + i);
    // Hiding all the certificzte contents except for the first tab that is
    // selected and shown by default
    if (i !== 0) {
      this.infoGroupsContainers[i].hidden = true;
    }
    this.infoGroupsContainers[i].classList.add("info-groups");

    if (this.isAboutCertificate) {
      this.infoGroupsContainers[i].appendChild(
        new AboutCertificateItems(certData.name, certData.data)
      );
    } else {
      for (let j = 0; j < certArray.length; j++) {
        this.infoGroupsContainers[i].appendChild(
          new InfoGroup(certArray[j], final)
        );
      }
    }
  }

  addClass(className, index) {
    this.infoGroupsContainers[index].classList.add(className);
  }

  updateCertificateSource(index) {
    for (let i = 0; i < this.infoGroupsContainers.length; i++) {
      this.infoGroupsContainers[i].classList.remove("selected");
    }
    this.infoGroupsContainers[index].classList.add("selected");
  }
}

customElements.define("info-group-container", InfoGroupContainer);
PK
!<���,,Bchrome/toolkit/content/global/certviewer/components/info-group.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:host {
  display: grid;
  grid-template-columns: minmax(8em, 25%) minmax(50%, 75%);
  grid-gap: 0 2em;
  padding: 1.75em 30px;
  align-items: center;
  border-top: 1px solid var(--in-content-border-color);
}

:host(:first-of-type) {
  border-top: none;
}

.info-group-title {
  margin: 0;
  text-align: end;
  font-weight: 700;
  color: var(--in-content-text-color);
  font-size: 1em;
  vertical-align: middle;
}

#critical-info {
  -moz-context-properties: fill;
  fill: currentColor;
  height: 16px;
  width: 16px;
  vertical-align: middle;
}

.extension {
  text-align: end;
  margin-block-end: 1em;
}
PK
!<4,�}�
�
Bchrome/toolkit/content/global/certviewer/components/info-group.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { InfoItem } from "./info-item.mjs";
import { updateSelectedItem } from "../certviewer.mjs";
import { normalizeToKebabCase } from "./utils.mjs";

export class InfoGroup extends HTMLElement {
  constructor(item, final) {
    super();
    this.item = item;
    this.final = final;
  }

  connectedCallback() {
    // Attach and connect before adding the template, or fluent
    // won't translate the template copy we insert into the
    // shadowroot.
    this.attachShadow({ mode: "open" });
    document.l10n.connectRoot(this.shadowRoot);

    let infoGroupTemplate = document.getElementById("info-group-template");
    this.shadowRoot.appendChild(infoGroupTemplate.content.cloneNode(true));
    this.render();
  }

  render() {
    let title = this.shadowRoot.querySelector(".info-group-title");
    document.l10n.setAttributes(
      title,
      `certificate-viewer-${this.item.sectionId}`
    );

    // Adds a class with the section title's name, to make
    // it easier to find when highlighting errors.
    this.classList.add(this.item.sectionId);
    for (let i = 0; i < this.item.sectionItems.length; i++) {
      this.shadowRoot.append(new InfoItem(this.item.sectionItems[i]));
    }

    if (this.item.sectionId === "issuer-name") {
      this.setLinkToTab();
    }

    let criticalIcon = this.shadowRoot.querySelector("#critical-info");
    if (!this.item.Critical) {
      criticalIcon.style.display = "none";
    }
  }

  setLinkToTab() {
    if (this.final) {
      return;
    }

    let issuerLabelElement =
      this.shadowRoot.querySelector(".common-name") ||
      this.shadowRoot.querySelector(".organizational-unit");

    issuerLabelElement = issuerLabelElement?.shadowRoot.querySelector(".info");

    if (!issuerLabelElement) {
      return;
    }

    let link = document.createElement("a");
    link.textContent = issuerLabelElement.textContent;
    if (!link.textContent) {
      link.setAttribute(
        "data-l10n-id",
        "certificate-viewer-unknown-group-label"
      );
    }
    link.setAttribute("href", "#");

    issuerLabelElement.textContent = "";
    issuerLabelElement.appendChild(link);

    link.addEventListener("click", () => {
      let id = normalizeToKebabCase(link.textContent);
      let issuerTab = document
        .querySelector("certificate-section")
        .shadowRoot.getElementById(id);

      let index = issuerTab.getAttribute("idnumber");

      updateSelectedItem(index);
    });
  }
}

customElements.define("info-group", InfoGroup);
PK
!<�t5Achrome/toolkit/content/global/certviewer/components/info-item.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:host {
  line-height: 22px;
  display: contents;
  word-break: break-word;
}

:host * {
  padding: 1px 0;
}

label {
  text-align: end;
  margin-inline-end: 0;
  color: var(--text-color-deemphasized);
  font-weight: 600;
  font-size: 1em;
}

.hex {
  overflow: hidden;
  word-break: break-all;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.download-link-chain {
  margin: 0 5px;
}

.long-hex {
  padding: 0;
  border-block: 1px solid transparent;
}

.long-hex:hover {
  border-block-color: var(--in-content-border-color);
  background-color: var(--in-content-item-hover);
  color: var(--in-content-item-hover-text);
}

.hex-open {
  white-space: normal;
}

:host(.value) *,
:host(.method) *,
:host(.padding) * {
  padding-bottom: 10px;
}

/* Elements that always need to be forced to LTR */
.hex,
:host(.dns-name) .info,
.url {
  direction: ltr;
  text-align: match-parent;
}

/* Display some elements according to their text directionality */
:host(.common-name) .info,
:host(.name) .info,
:host(.organization) .info {
  unicode-bidi: plaintext;
  text-align: match-parent;
}
PK
!<��[���Achrome/toolkit/content/global/certviewer/components/info-item.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { b64ToPEM, normalizeToKebabCase } from "./utils.mjs";

export class InfoItem extends HTMLElement {
  constructor(item) {
    super();
    this.item = item;
  }

  connectedCallback() {
    // Attach and connect before adding the template, or fluent
    // won't translate the template copy we insert into the
    // shadowroot.
    this.attachShadow({ mode: "open" });
    document.l10n.connectRoot(this.shadowRoot);

    let infoItemTemplate = document.getElementById("info-item-template");
    this.shadowRoot.appendChild(infoItemTemplate.content.cloneNode(true));

    this.render();
  }

  handleTimeZone(infoElement) {
    let localTime = this.item.info.local;
    let UTCTime = this.item.info.utc;
    infoElement.textContent = UTCTime;
    infoElement.setAttribute("title", localTime);
  }

  addLongHexOverflow(info) {
    info.classList.add("hex");

    // For visual appeal, we want to collapse large hex values into single
    // line items that can be clicked to expand.
    // This function measures the size of the info item relative to its
    // container and adds the "long-hex" class if it's overflowing. Since the
    // container size changes on window resize this function is hooked up to
    // a resize event listener.
    function resize() {
      if (info.classList.contains("hex-open")) {
        info.classList.toggle("long-hex", true);
        return;
      }

      // If the item is not currently drawn and we can't measure its dimensions
      // then attach an observer that will measure it once it appears.
      if (info.clientWidth <= 0) {
        let observer = new IntersectionObserver(function ([
          { intersectionRatio },
        ]) {
          if (intersectionRatio > 0) {
            info.classList.toggle(
              "long-hex",
              info.scrollWidth > info.clientWidth
            );
            observer.unobserve(info);
          }
        },
        {});

        observer.observe(info);
      }
      info.classList.toggle("long-hex", info.scrollWidth > info.clientWidth);
    }
    window.addEventListener("resize", resize);
    window.requestAnimationFrame(resize);

    this.addEventListener("mouseup", () => {
      // If a range of text is selected, don't toggle the class that
      // hides/shows additional text.
      if (
        info.classList.contains("long-hex") &&
        window.getSelection().type !== "Range"
      ) {
        info.classList.toggle("hex-open");
      }
    });
  }

  render() {
    let label = this.shadowRoot.querySelector("label");
    let labelId = this.item.labelId;

    // Map specific elements to a different message ID, to allow updates to
    // existing labels and avoid duplicates.
    let stringMapping = {
      signaturealgorithm: "signature-algorithm",
      "rfc-822-name": "email-address",
    };
    let fluentID = stringMapping[labelId] || labelId;

    document.l10n.setAttributes(label, `certificate-viewer-${fluentID}`);

    this.classList.add(labelId);

    let info = this.shadowRoot.querySelector(".info");
    if (this.item.info.hasOwnProperty("utc")) {
      this.handleTimeZone(info);
      return;
    }
    if (labelId === "other-name") {
      document.l10n.setAttributes(info, "certificate-viewer-unsupported");
      return;
    }
    if (typeof this.item.info === "boolean") {
      document.l10n.setAttributes(info, "certificate-viewer-boolean", {
        boolean: this.item.info,
      });
    } else {
      info.textContent = Array.isArray(this.item.info)
        ? this.item.info.join(", ")
        : this.item.info;
    }

    this.classList.add(labelId);

    if (this.item.isHex) {
      this.addLongHexOverflow(info);
    }

    if (labelId === "download") {
      this.setDownloadLinkInformation(info);
    }
  }

  setDownloadLinkInformation(info) {
    let link = document.createElement("a");
    link.setAttribute("href", "data:," + this.item.info);
    link.classList.add("download-link");

    let url = new URL(document.URL);
    let certArray = url.searchParams.getAll("cert");
    let encodedCertArray = [];
    for (let i = 0; i < certArray.length; i++) {
      encodedCertArray.push(encodeURI(b64ToPEM(certArray[i])));
    }
    encodedCertArray = encodedCertArray.join("");

    let chainLink = document.createElement("a");
    chainLink.setAttribute("href", "data:," + encodedCertArray);
    chainLink.classList.add("download-link");
    chainLink.classList.add("download-link-chain");

    info.textContent = "";
    info.appendChild(link);
    info.appendChild(chainLink);

    let commonName = document
      .querySelector("certificate-section")
      .shadowRoot.querySelector(".subject-name")
      .shadowRoot.querySelector(".common-name")
      .shadowRoot.querySelector(".info");

    let fileName = normalizeToKebabCase(commonName.textContent);

    document.l10n.setAttributes(link, "certificate-viewer-download-pem", {
      fileName,
    });

    document.l10n.setAttributes(
      chainLink,
      "certificate-viewer-download-pem-chain",
      {
        fileName,
      }
    );
  }
}

customElements.define("info-item", InfoItem);
PK
!<^
���Achrome/toolkit/content/global/certviewer/components/list-item.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:host {
  display: grid;
  padding: 1em 0 1em;
  border-block: 0.5px solid var(--in-content-border-color);
  border-inline: 0.5px solid transparent;
  position: relative;
}

:host(:hover) {
  background-color: var(--in-content-item-hover);
  color: var(--in-content-item-hover-text);
  border-inline-color: var(--in-content-border-color);
  cursor: pointer;
}

a {
  text-decoration: none;
  color: inherit;
}

.cert-url {
  height: 100%;
  width: 100%;
  position: absolute;
  align-items: center;
  display: flex;
}

.cert-url > .item-name {
  padding-inline-start: 0.8em;
}

.export {
  position: absolute;
  inset-inline-end: 2em;
  align-self: center;
  font-size: 1em;
}

.export a {
  position: relative;
}
PK
!<;�ssAchrome/toolkit/content/global/certviewer/components/list-item.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { normalizeToKebabCase } from "./utils.mjs";

export class ListItem extends HTMLElement {
  constructor(item) {
    super();
    this.item = item;
  }

  connectedCallback() {
    // Attach and connect before adding the template, or fluent
    // won't translate the template copy we insert into the
    // shadowroot.
    this.attachShadow({ mode: "open" });
    document.l10n.connectRoot(this.shadowRoot);

    let ListItemTemplate = document.getElementById("list-item-template");
    this.shadowRoot.appendChild(ListItemTemplate.content.cloneNode(true));

    this.render();
  }

  render() {
    let label = this.shadowRoot.querySelector(".item-name");
    label.textContent = this.item.displayName;

    this.handleExport();

    let link = this.shadowRoot.querySelector(".cert-url");
    let derb64 = encodeURIComponent(this.item.derb64);
    let url = `about:certificate?cert=${derb64}`;
    link.setAttribute("href", url);
  }

  handleExport() {
    let exportButton = this.shadowRoot.querySelector(".export");
    // Wrap the Base64 string into lines of 64 characters,
    // with CRLF line breaks (as specified in RFC 1421).
    let wrapped = this.item.derb64.replace(/(\S{64}(?!$))/g, "$1\r\n");
    let download =
      "-----BEGIN CERTIFICATE-----\r\n" +
      wrapped +
      "\r\n-----END CERTIFICATE-----\r\n";

    let element = document.createElement("a");
    element.setAttribute("href", "data:," + encodeURI(download));
    let fileName = normalizeToKebabCase(this.item.displayName);
    document.l10n.setAttributes(element, "certificate-viewer-export", {
      fileName,
    });
    exportButton.appendChild(element);
  }
}

customElements.define("list-item", ListItem);
PK
!<��3KK=chrome/toolkit/content/global/certviewer/components/utils.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export const normalizeToKebabCase = string => {
  let kebabString = string
    // Turn all dots into dashes
    .replace(/\./g, "-")
    // Turn whitespace into dashes
    .replace(/\s+/g, "-")
    // Remove all non-characters or numbers
    .replace(/[^a-z0-9\-]/gi, "")
    // De-dupe dashes
    .replace(/--/g, "-")
    // Remove trailing and leading dashes
    .replace(/^-/g, "")
    .replace(/-$/g, "")
    .toLowerCase();

  return kebabString;
};

export const b64ToPEM = string => {
  let wrapped = string.match(/.{1,64}/g).join("\r\n");
  return `-----BEGIN CERTIFICATE-----\r\n${wrapped}\r\n-----END CERTIFICATE-----\r\n`;
};
PK
!<ډc��8chrome/toolkit/content/global/certviewer/vendor/pkijs.js/*!
 * Copyright (c) 2014, GlobalSign
 * Copyright (c) 2015-2019, Peculiar Ventures
 * All rights reserved.
 * 
 * Author 2014-2019, Yury Strozhevsky
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 * 
 * * Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 * 
 * * Neither the name of the {organization} nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 */

/*!
 * MIT License
 * 
 * Copyright (c) 2017-2022 Peculiar Ventures, LLC
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * 
 */

const ARRAY_BUFFER_NAME = "[object ArrayBuffer]";
class BufferSourceConverter {
    static isArrayBuffer(data) {
        return Object.prototype.toString.call(data) === ARRAY_BUFFER_NAME;
    }
    static toArrayBuffer(data) {
        if (this.isArrayBuffer(data)) {
            return data;
        }
        if (data.byteLength === data.buffer.byteLength) {
            return data.buffer;
        }
        return this.toUint8Array(data).slice().buffer;
    }
    static toUint8Array(data) {
        return this.toView(data, Uint8Array);
    }
    static toView(data, type) {
        if (data.constructor === type) {
            return data;
        }
        if (this.isArrayBuffer(data)) {
            return new type(data);
        }
        if (this.isArrayBufferView(data)) {
            return new type(data.buffer, data.byteOffset, data.byteLength);
        }
        throw new TypeError("The provided value is not of type '(ArrayBuffer or ArrayBufferView)'");
    }
    static isBufferSource(data) {
        return this.isArrayBufferView(data)
            || this.isArrayBuffer(data);
    }
    static isArrayBufferView(data) {
        return ArrayBuffer.isView(data)
            || (data && this.isArrayBuffer(data.buffer));
    }
    static isEqual(a, b) {
        const aView = BufferSourceConverter.toUint8Array(a);
        const bView = BufferSourceConverter.toUint8Array(b);
        if (aView.length !== bView.byteLength) {
            return false;
        }
        for (let i = 0; i < aView.length; i++) {
            if (aView[i] !== bView[i]) {
                return false;
            }
        }
        return true;
    }
    static concat(...args) {
        if (Array.isArray(args[0])) {
            const buffers = args[0];
            let size = 0;
            for (const buffer of buffers) {
                size += buffer.byteLength;
            }
            const res = new Uint8Array(size);
            let offset = 0;
            for (const buffer of buffers) {
                const view = this.toUint8Array(buffer);
                res.set(view, offset);
                offset += view.length;
            }
            if (args[1]) {
                return this.toView(res, args[1]);
            }
            return res.buffer;
        }
        else {
            return this.concat(args);
        }
    }
}

class Utf8Converter {
    static fromString(text) {
        const s = unescape(encodeURIComponent(text));
        const uintArray = new Uint8Array(s.length);
        for (let i = 0; i < s.length; i++) {
            uintArray[i] = s.charCodeAt(i);
        }
        return uintArray.buffer;
    }
    static toString(buffer) {
        const buf = BufferSourceConverter.toUint8Array(buffer);
        let encodedString = "";
        for (let i = 0; i < buf.length; i++) {
            encodedString += String.fromCharCode(buf[i]);
        }
        const decodedString = decodeURIComponent(escape(encodedString));
        return decodedString;
    }
}
class Utf16Converter {
    static toString(buffer, littleEndian = false) {
        const arrayBuffer = BufferSourceConverter.toArrayBuffer(buffer);
        const dataView = new DataView(arrayBuffer);
        let res = "";
        for (let i = 0; i < arrayBuffer.byteLength; i += 2) {
            const code = dataView.getUint16(i, littleEndian);
            res += String.fromCharCode(code);
        }
        return res;
    }
    static fromString(text, littleEndian = false) {
        const res = new ArrayBuffer(text.length * 2);
        const dataView = new DataView(res);
        for (let i = 0; i < text.length; i++) {
            dataView.setUint16(i * 2, text.charCodeAt(i), littleEndian);
        }
        return res;
    }
}
class Convert {
    static isHex(data) {
        return typeof data === "string"
            && /^[a-z0-9]+$/i.test(data);
    }
    static isBase64(data) {
        return typeof data === "string"
            && /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(data);
    }
    static isBase64Url(data) {
        return typeof data === "string"
            && /^[a-zA-Z0-9-_]+$/i.test(data);
    }
    static ToString(buffer, enc = "utf8") {
        const buf = BufferSourceConverter.toUint8Array(buffer);
        switch (enc.toLowerCase()) {
            case "utf8":
                return this.ToUtf8String(buf);
            case "binary":
                return this.ToBinary(buf);
            case "hex":
                return this.ToHex(buf);
            case "base64":
                return this.ToBase64(buf);
            case "base64url":
                return this.ToBase64Url(buf);
            case "utf16le":
                return Utf16Converter.toString(buf, true);
            case "utf16":
            case "utf16be":
                return Utf16Converter.toString(buf);
            default:
                throw new Error(`Unknown type of encoding '${enc}'`);
        }
    }
    static FromString(str, enc = "utf8") {
        if (!str) {
            return new ArrayBuffer(0);
        }
        switch (enc.toLowerCase()) {
            case "utf8":
                return this.FromUtf8String(str);
            case "binary":
                return this.FromBinary(str);
            case "hex":
                return this.FromHex(str);
            case "base64":
                return this.FromBase64(str);
            case "base64url":
                return this.FromBase64Url(str);
            case "utf16le":
                return Utf16Converter.fromString(str, true);
            case "utf16":
            case "utf16be":
                return Utf16Converter.fromString(str);
            default:
                throw new Error(`Unknown type of encoding '${enc}'`);
        }
    }
    static ToBase64(buffer) {
        const buf = BufferSourceConverter.toUint8Array(buffer);
        if (typeof btoa !== "undefined") {
            const binary = this.ToString(buf, "binary");
            return btoa(binary);
        }
        else {
            return Buffer.from(buf).toString("base64");
        }
    }
    static FromBase64(base64) {
        const formatted = this.formatString(base64);
        if (!formatted) {
            return new ArrayBuffer(0);
        }
        if (!Convert.isBase64(formatted)) {
            throw new TypeError("Argument 'base64Text' is not Base64 encoded");
        }
        if (typeof atob !== "undefined") {
            return this.FromBinary(atob(formatted));
        }
        else {
            return new Uint8Array(Buffer.from(formatted, "base64")).buffer;
        }
    }
    static FromBase64Url(base64url) {
        const formatted = this.formatString(base64url);
        if (!formatted) {
            return new ArrayBuffer(0);
        }
        if (!Convert.isBase64Url(formatted)) {
            throw new TypeError("Argument 'base64url' is not Base64Url encoded");
        }
        return this.FromBase64(this.Base64Padding(formatted.replace(/\-/g, "+").replace(/\_/g, "/")));
    }
    static ToBase64Url(data) {
        return this.ToBase64(data).replace(/\+/g, "-").replace(/\//g, "_").replace(/\=/g, "");
    }
    static FromUtf8String(text, encoding = Convert.DEFAULT_UTF8_ENCODING) {
        switch (encoding) {
            case "ascii":
                return this.FromBinary(text);
            case "utf8":
                return Utf8Converter.fromString(text);
            case "utf16":
            case "utf16be":
                return Utf16Converter.fromString(text);
            case "utf16le":
            case "usc2":
                return Utf16Converter.fromString(text, true);
            default:
                throw new Error(`Unknown type of encoding '${encoding}'`);
        }
    }
    static ToUtf8String(buffer, encoding = Convert.DEFAULT_UTF8_ENCODING) {
        switch (encoding) {
            case "ascii":
                return this.ToBinary(buffer);
            case "utf8":
                return Utf8Converter.toString(buffer);
            case "utf16":
            case "utf16be":
                return Utf16Converter.toString(buffer);
            case "utf16le":
            case "usc2":
                return Utf16Converter.toString(buffer, true);
            default:
                throw new Error(`Unknown type of encoding '${encoding}'`);
        }
    }
    static FromBinary(text) {
        const stringLength = text.length;
        const resultView = new Uint8Array(stringLength);
        for (let i = 0; i < stringLength; i++) {
            resultView[i] = text.charCodeAt(i);
        }
        return resultView.buffer;
    }
    static ToBinary(buffer) {
        const buf = BufferSourceConverter.toUint8Array(buffer);
        let res = "";
        for (let i = 0; i < buf.length; i++) {
            res += String.fromCharCode(buf[i]);
        }
        return res;
    }
    static ToHex(buffer) {
        const buf = BufferSourceConverter.toUint8Array(buffer);
        const splitter = "";
        const res = [];
        const len = buf.length;
        for (let i = 0; i < len; i++) {
            const char = buf[i].toString(16).padStart(2, "0");
            res.push(char);
        }
        return res.join(splitter);
    }
    static FromHex(hexString) {
        let formatted = this.formatString(hexString);
        if (!formatted) {
            return new ArrayBuffer(0);
        }
        if (!Convert.isHex(formatted)) {
            throw new TypeError("Argument 'hexString' is not HEX encoded");
        }
        if (formatted.length % 2) {
            formatted = `0${formatted}`;
        }
        const res = new Uint8Array(formatted.length / 2);
        for (let i = 0; i < formatted.length; i = i + 2) {
            const c = formatted.slice(i, i + 2);
            res[i / 2] = parseInt(c, 16);
        }
        return res.buffer;
    }
    static ToUtf16String(buffer, littleEndian = false) {
        return Utf16Converter.toString(buffer, littleEndian);
    }
    static FromUtf16String(text, littleEndian = false) {
        return Utf16Converter.fromString(text, littleEndian);
    }
    static Base64Padding(base64) {
        const padCount = 4 - (base64.length % 4);
        if (padCount < 4) {
            for (let i = 0; i < padCount; i++) {
                base64 += "=";
            }
        }
        return base64;
    }
    static formatString(data) {
        return (data === null || data === void 0 ? void 0 : data.replace(/[\n\r\t ]/g, "")) || "";
    }
}
Convert.DEFAULT_UTF8_ENCODING = "utf8";

/*!
 Copyright (c) Peculiar Ventures, LLC
*/
function getParametersValue(parameters, name, defaultValue) {
    var _a;
    if ((parameters instanceof Object) === false) {
        return defaultValue;
    }
    return (_a = parameters[name]) !== null && _a !== void 0 ? _a : defaultValue;
}
function bufferToHexCodes(inputBuffer, inputOffset = 0, inputLength = (inputBuffer.byteLength - inputOffset), insertSpace = false) {
    let result = "";
    for (const item of (new Uint8Array(inputBuffer, inputOffset, inputLength))) {
        const str = item.toString(16).toUpperCase();
        if (str.length === 1) {
            result += "0";
        }
        result += str;
        if (insertSpace) {
            result += " ";
        }
    }
    return result.trim();
}
function utilFromBase(inputBuffer, inputBase) {
    let result = 0;
    if (inputBuffer.length === 1) {
        return inputBuffer[0];
    }
    for (let i = (inputBuffer.length - 1); i >= 0; i--) {
        result += inputBuffer[(inputBuffer.length - 1) - i] * Math.pow(2, inputBase * i);
    }
    return result;
}
function utilToBase(value, base, reserved = (-1)) {
    const internalReserved = reserved;
    let internalValue = value;
    let result = 0;
    let biggest = Math.pow(2, base);
    for (let i = 1; i < 8; i++) {
        if (value < biggest) {
            let retBuf;
            if (internalReserved < 0) {
                retBuf = new ArrayBuffer(i);
                result = i;
            }
            else {
                if (internalReserved < i) {
                    return (new ArrayBuffer(0));
                }
                retBuf = new ArrayBuffer(internalReserved);
                result = internalReserved;
            }
            const retView = new Uint8Array(retBuf);
            for (let j = (i - 1); j >= 0; j--) {
                const basis = Math.pow(2, j * base);
                retView[result - j - 1] = Math.floor(internalValue / basis);
                internalValue -= (retView[result - j - 1]) * basis;
            }
            return retBuf;
        }
        biggest *= Math.pow(2, base);
    }
    return new ArrayBuffer(0);
}
function utilConcatBuf(...buffers) {
    let outputLength = 0;
    let prevLength = 0;
    for (const buffer of buffers) {
        outputLength += buffer.byteLength;
    }
    const retBuf = new ArrayBuffer(outputLength);
    const retView = new Uint8Array(retBuf);
    for (const buffer of buffers) {
        retView.set(new Uint8Array(buffer), prevLength);
        prevLength += buffer.byteLength;
    }
    return retBuf;
}
function utilConcatView(...views) {
    let outputLength = 0;
    let prevLength = 0;
    for (const view of views) {
        outputLength += view.length;
    }
    const retBuf = new ArrayBuffer(outputLength);
    const retView = new Uint8Array(retBuf);
    for (const view of views) {
        retView.set(view, prevLength);
        prevLength += view.length;
    }
    return retView;
}
function utilDecodeTC() {
    const buf = new Uint8Array(this.valueHex);
    if (this.valueHex.byteLength >= 2) {
        const condition1 = (buf[0] === 0xFF) && (buf[1] & 0x80);
        const condition2 = (buf[0] === 0x00) && ((buf[1] & 0x80) === 0x00);
        if (condition1 || condition2) {
            this.warnings.push("Needlessly long format");
        }
    }
    const bigIntBuffer = new ArrayBuffer(this.valueHex.byteLength);
    const bigIntView = new Uint8Array(bigIntBuffer);
    for (let i = 0; i < this.valueHex.byteLength; i++) {
        bigIntView[i] = 0;
    }
    bigIntView[0] = (buf[0] & 0x80);
    const bigInt = utilFromBase(bigIntView, 8);
    const smallIntBuffer = new ArrayBuffer(this.valueHex.byteLength);
    const smallIntView = new Uint8Array(smallIntBuffer);
    for (let j = 0; j < this.valueHex.byteLength; j++) {
        smallIntView[j] = buf[j];
    }
    smallIntView[0] &= 0x7F;
    const smallInt = utilFromBase(smallIntView, 8);
    return (smallInt - bigInt);
}
function utilEncodeTC(value) {
    const modValue = (value < 0) ? (value * (-1)) : value;
    let bigInt = 128;
    for (let i = 1; i < 8; i++) {
        if (modValue <= bigInt) {
            if (value < 0) {
                const smallInt = bigInt - modValue;
                const retBuf = utilToBase(smallInt, 8, i);
                const retView = new Uint8Array(retBuf);
                retView[0] |= 0x80;
                return retBuf;
            }
            let retBuf = utilToBase(modValue, 8, i);
            let retView = new Uint8Array(retBuf);
            if (retView[0] & 0x80) {
                const tempBuf = retBuf.slice(0);
                const tempView = new Uint8Array(tempBuf);
                retBuf = new ArrayBuffer(retBuf.byteLength + 1);
                retView = new Uint8Array(retBuf);
                for (let k = 0; k < tempBuf.byteLength; k++) {
                    retView[k + 1] = tempView[k];
                }
                retView[0] = 0x00;
            }
            return retBuf;
        }
        bigInt *= Math.pow(2, 8);
    }
    return (new ArrayBuffer(0));
}
function isEqualBuffer(inputBuffer1, inputBuffer2) {
    if (inputBuffer1.byteLength !== inputBuffer2.byteLength) {
        return false;
    }
    const view1 = new Uint8Array(inputBuffer1);
    const view2 = new Uint8Array(inputBuffer2);
    for (let i = 0; i < view1.length; i++) {
        if (view1[i] !== view2[i]) {
            return false;
        }
    }
    return true;
}
function padNumber(inputNumber, fullLength) {
    const str = inputNumber.toString(10);
    if (fullLength < str.length) {
        return "";
    }
    const dif = fullLength - str.length;
    const padding = new Array(dif);
    for (let i = 0; i < dif; i++) {
        padding[i] = "0";
    }
    const paddingString = padding.join("");
    return paddingString.concat(str);
}
const base64Template = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
const base64UrlTemplate = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=";
function toBase64(input, useUrlTemplate = false, skipPadding = false, skipLeadingZeros = false) {
    let i = 0;
    let flag1 = 0;
    let flag2 = 0;
    let output = "";
    const template = (useUrlTemplate) ? base64UrlTemplate : base64Template;
    if (skipLeadingZeros) {
        let nonZeroPosition = 0;
        for (let i = 0; i < input.length; i++) {
            if (input.charCodeAt(i) !== 0) {
                nonZeroPosition = i;
                break;
            }
        }
        input = input.slice(nonZeroPosition);
    }
    while (i < input.length) {
        const chr1 = input.charCodeAt(i++);
        if (i >= input.length) {
            flag1 = 1;
        }
        const chr2 = input.charCodeAt(i++);
        if (i >= input.length) {
            flag2 = 1;
        }
        const chr3 = input.charCodeAt(i++);
        const enc1 = chr1 >> 2;
        const enc2 = ((chr1 & 0x03) << 4) | (chr2 >> 4);
        let enc3 = ((chr2 & 0x0F) << 2) | (chr3 >> 6);
        let enc4 = chr3 & 0x3F;
        if (flag1 === 1) {
            enc3 = enc4 = 64;
        }
        else {
            if (flag2 === 1) {
                enc4 = 64;
            }
        }
        if (skipPadding) {
            if (enc3 === 64) {
                output += `${template.charAt(enc1)}${template.charAt(enc2)}`;
            }
            else {
                if (enc4 === 64) {
                    output += `${template.charAt(enc1)}${template.charAt(enc2)}${template.charAt(enc3)}`;
                }
                else {
                    output += `${template.charAt(enc1)}${template.charAt(enc2)}${template.charAt(enc3)}${template.charAt(enc4)}`;
                }
            }
        }
        else {
            output += `${template.charAt(enc1)}${template.charAt(enc2)}${template.charAt(enc3)}${template.charAt(enc4)}`;
        }
    }
    return output;
}
function fromBase64(input, useUrlTemplate = false, cutTailZeros = false) {
    const template = (useUrlTemplate) ? base64UrlTemplate : base64Template;
    function indexOf(toSearch) {
        for (let i = 0; i < 64; i++) {
            if (template.charAt(i) === toSearch)
                return i;
        }
        return 64;
    }
    function test(incoming) {
        return ((incoming === 64) ? 0x00 : incoming);
    }
    let i = 0;
    let output = "";
    while (i < input.length) {
        const enc1 = indexOf(input.charAt(i++));
        const enc2 = (i >= input.length) ? 0x00 : indexOf(input.charAt(i++));
        const enc3 = (i >= input.length) ? 0x00 : indexOf(input.charAt(i++));
        const enc4 = (i >= input.length) ? 0x00 : indexOf(input.charAt(i++));
        const chr1 = (test(enc1) << 2) | (test(enc2) >> 4);
        const chr2 = ((test(enc2) & 0x0F) << 4) | (test(enc3) >> 2);
        const chr3 = ((test(enc3) & 0x03) << 6) | test(enc4);
        output += String.fromCharCode(chr1);
        if (enc3 !== 64) {
            output += String.fromCharCode(chr2);
        }
        if (enc4 !== 64) {
            output += String.fromCharCode(chr3);
        }
    }
    if (cutTailZeros) {
        const outputLength = output.length;
        let nonZeroStart = (-1);
        for (let i = (outputLength - 1); i >= 0; i--) {
            if (output.charCodeAt(i) !== 0) {
                nonZeroStart = i;
                break;
            }
        }
        if (nonZeroStart !== (-1)) {
            output = output.slice(0, nonZeroStart + 1);
        }
        else {
            output = "";
        }
    }
    return output;
}
function arrayBufferToString(buffer) {
    let resultString = "";
    const view = new Uint8Array(buffer);
    for (const element of view) {
        resultString += String.fromCharCode(element);
    }
    return resultString;
}
function stringToArrayBuffer(str) {
    const stringLength = str.length;
    const resultBuffer = new ArrayBuffer(stringLength);
    const resultView = new Uint8Array(resultBuffer);
    for (let i = 0; i < stringLength; i++) {
        resultView[i] = str.charCodeAt(i);
    }
    return resultBuffer;
}
const log2 = Math.log(2);
function nearestPowerOf2(length) {
    const base = (Math.log(length) / log2);
    const floor = Math.floor(base);
    const round = Math.round(base);
    return ((floor === round) ? floor : round);
}
function clearProps(object, propsArray) {
    for (const prop of propsArray) {
        delete object[prop];
    }
}

/*!
 * Copyright (c) 2014, GMO GlobalSign
 * Copyright (c) 2015-2022, Peculiar Ventures
 * All rights reserved.
 * 
 * Author 2014-2019, Yury Strozhevsky
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 * 
 * * Redistributions in binary form must reproduce the above copyright notice, this
 *   list of conditions and the following disclaimer in the documentation and/or
 *   other materials provided with the distribution.
 * 
 * * Neither the name of the copyright holder nor the names of its
 *   contributors may be used to endorse or promote products derived from
 *   this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 */

function assertBigInt() {
    if (typeof BigInt === "undefined") {
        throw new Error("BigInt is not defined. Your environment doesn't implement BigInt.");
    }
}
function concat(buffers) {
    let outputLength = 0;
    let prevLength = 0;
    for (let i = 0; i < buffers.length; i++) {
        const buffer = buffers[i];
        outputLength += buffer.byteLength;
    }
    const retView = new Uint8Array(outputLength);
    for (let i = 0; i < buffers.length; i++) {
        const buffer = buffers[i];
        retView.set(new Uint8Array(buffer), prevLength);
        prevLength += buffer.byteLength;
    }
    return retView.buffer;
}
function checkBufferParams(baseBlock, inputBuffer, inputOffset, inputLength) {
    if (!(inputBuffer instanceof Uint8Array)) {
        baseBlock.error = "Wrong parameter: inputBuffer must be 'Uint8Array'";
        return false;
    }
    if (!inputBuffer.byteLength) {
        baseBlock.error = "Wrong parameter: inputBuffer has zero length";
        return false;
    }
    if (inputOffset < 0) {
        baseBlock.error = "Wrong parameter: inputOffset less than zero";
        return false;
    }
    if (inputLength < 0) {
        baseBlock.error = "Wrong parameter: inputLength less than zero";
        return false;
    }
    if ((inputBuffer.byteLength - inputOffset - inputLength) < 0) {
        baseBlock.error = "End of input reached before message was fully decoded (inconsistent offset and length values)";
        return false;
    }
    return true;
}

class ViewWriter {
    constructor() {
        this.items = [];
    }
    write(buf) {
        this.items.push(buf);
    }
    final() {
        return concat(this.items);
    }
}

const powers2 = [new Uint8Array([1])];
const digitsString = "0123456789";
const NAME = "name";
const VALUE_HEX_VIEW = "valueHexView";
const IS_HEX_ONLY = "isHexOnly";
const ID_BLOCK = "idBlock";
const TAG_CLASS = "tagClass";
const TAG_NUMBER = "tagNumber";
const IS_CONSTRUCTED = "isConstructed";
const FROM_BER = "fromBER";
const TO_BER = "toBER";
const LOCAL = "local";
const EMPTY_STRING$1 = "";
const EMPTY_BUFFER$1 = new ArrayBuffer(0);
const EMPTY_VIEW = new Uint8Array(0);
const END_OF_CONTENT_NAME = "EndOfContent";
const OCTET_STRING_NAME = "OCTET STRING";
const BIT_STRING_NAME = "BIT STRING";

function HexBlock(BaseClass) {
    var _a;
    return _a = class Some extends BaseClass {
            constructor(...args) {
                var _a;
                super(...args);
                const params = args[0] || {};
                this.isHexOnly = (_a = params.isHexOnly) !== null && _a !== void 0 ? _a : false;
                this.valueHexView = params.valueHex ? BufferSourceConverter.toUint8Array(params.valueHex) : EMPTY_VIEW;
            }
            get valueHex() {
                return this.valueHexView.slice().buffer;
            }
            set valueHex(value) {
                this.valueHexView = new Uint8Array(value);
            }
            fromBER(inputBuffer, inputOffset, inputLength) {
                const view = inputBuffer instanceof ArrayBuffer ? new Uint8Array(inputBuffer) : inputBuffer;
                if (!checkBufferParams(this, view, inputOffset, inputLength)) {
                    return -1;
                }
                const endLength = inputOffset + inputLength;
                this.valueHexView = view.subarray(inputOffset, endLength);
                if (!this.valueHexView.length) {
                    this.warnings.push("Zero buffer length");
                    return inputOffset;
                }
                this.blockLength = inputLength;
                return endLength;
            }
            toBER(sizeOnly = false) {
                if (!this.isHexOnly) {
                    this.error = "Flag 'isHexOnly' is not set, abort";
                    return EMPTY_BUFFER$1;
                }
                if (sizeOnly) {
                    return new ArrayBuffer(this.valueHexView.byteLength);
                }
                return (this.valueHexView.byteLength === this.valueHexView.buffer.byteLength)
                    ? this.valueHexView.buffer
                    : this.valueHexView.slice().buffer;
            }
            toJSON() {
                return {
                    ...super.toJSON(),
                    isHexOnly: this.isHexOnly,
                    valueHex: Convert.ToHex(this.valueHexView),
                };
            }
        },
        _a.NAME = "hexBlock",
        _a;
}

class LocalBaseBlock {
    constructor({ blockLength = 0, error = EMPTY_STRING$1, warnings = [], valueBeforeDecode = EMPTY_VIEW, } = {}) {
        this.blockLength = blockLength;
        this.error = error;
        this.warnings = warnings;
        this.valueBeforeDecodeView = BufferSourceConverter.toUint8Array(valueBeforeDecode);
    }
    static blockName() {
        return this.NAME;
    }
    get valueBeforeDecode() {
        return this.valueBeforeDecodeView.slice().buffer;
    }
    set valueBeforeDecode(value) {
        this.valueBeforeDecodeView = new Uint8Array(value);
    }
    toJSON() {
        return {
            blockName: this.constructor.NAME,
            blockLength: this.blockLength,
            error: this.error,
            warnings: this.warnings,
            valueBeforeDecode: Convert.ToHex(this.valueBeforeDecodeView),
        };
    }
}
LocalBaseBlock.NAME = "baseBlock";

class ValueBlock extends LocalBaseBlock {
    fromBER(inputBuffer, inputOffset, inputLength) {
        throw TypeError("User need to make a specific function in a class which extends 'ValueBlock'");
    }
    toBER(sizeOnly, writer) {
        throw TypeError("User need to make a specific function in a class which extends 'ValueBlock'");
    }
}
ValueBlock.NAME = "valueBlock";

class LocalIdentificationBlock extends HexBlock(LocalBaseBlock) {
    constructor({ idBlock = {}, } = {}) {
        var _a, _b, _c, _d;
        super();
        if (idBlock) {
            this.isHexOnly = (_a = idBlock.isHexOnly) !== null && _a !== void 0 ? _a : false;
            this.valueHexView = idBlock.valueHex ? BufferSourceConverter.toUint8Array(idBlock.valueHex) : EMPTY_VIEW;
            this.tagClass = (_b = idBlock.tagClass) !== null && _b !== void 0 ? _b : -1;
            this.tagNumber = (_c = idBlock.tagNumber) !== null && _c !== void 0 ? _c : -1;
            this.isConstructed = (_d = idBlock.isConstructed) !== null && _d !== void 0 ? _d : false;
        }
        else {
            this.tagClass = -1;
            this.tagNumber = -1;
            this.isConstructed = false;
        }
    }
    toBER(sizeOnly = false) {
        let firstOctet = 0;
        switch (this.tagClass) {
            case 1:
                firstOctet |= 0x00;
                break;
            case 2:
                firstOctet |= 0x40;
                break;
            case 3:
                firstOctet |= 0x80;
                break;
            case 4:
                firstOctet |= 0xC0;
                break;
            default:
                this.error = "Unknown tag class";
                return EMPTY_BUFFER$1;
        }
        if (this.isConstructed)
            firstOctet |= 0x20;
        if (this.tagNumber < 31 && !this.isHexOnly) {
            const retView = new Uint8Array(1);
            if (!sizeOnly) {
                let number = this.tagNumber;
                number &= 0x1F;
                firstOctet |= number;
                retView[0] = firstOctet;
            }
            return retView.buffer;
        }
        if (!this.isHexOnly) {
            const encodedBuf = utilToBase(this.tagNumber, 7);
            const encodedView = new Uint8Array(encodedBuf);
            const size = encodedBuf.byteLength;
            const retView = new Uint8Array(size + 1);
            retView[0] = (firstOctet | 0x1F);
            if (!sizeOnly) {
                for (let i = 0; i < (size - 1); i++)
                    retView[i + 1] = encodedView[i] | 0x80;
                retView[size] = encodedView[size - 1];
            }
            return retView.buffer;
        }
        const retView = new Uint8Array(this.valueHexView.byteLength + 1);
        retView[0] = (firstOctet | 0x1F);
        if (!sizeOnly) {
            const curView = this.valueHexView;
            for (let i = 0; i < (curView.length - 1); i++)
                retView[i + 1] = curView[i] | 0x80;
            retView[this.valueHexView.byteLength] = curView[curView.length - 1];
        }
        return retView.buffer;
    }
    fromBER(inputBuffer, inputOffset, inputLength) {
        const inputView = BufferSourceConverter.toUint8Array(inputBuffer);
        if (!checkBufferParams(this, inputView, inputOffset, inputLength)) {
            return -1;
        }
        const intBuffer = inputView.subarray(inputOffset, inputOffset + inputLength);
        if (intBuffer.length === 0) {
            this.error = "Zero buffer length";
            return -1;
        }
        const tagClassMask = intBuffer[0] & 0xC0;
        switch (tagClassMask) {
            case 0x00:
                this.tagClass = (1);
                break;
            case 0x40:
                this.tagClass = (2);
                break;
            case 0x80:
                this.tagClass = (3);
                break;
            case 0xC0:
                this.tagClass = (4);
                break;
            default:
                this.error = "Unknown tag class";
                return -1;
        }
        this.isConstructed = (intBuffer[0] & 0x20) === 0x20;
        this.isHexOnly = false;
        const tagNumberMask = intBuffer[0] & 0x1F;
        if (tagNumberMask !== 0x1F) {
            this.tagNumber = (tagNumberMask);
            this.blockLength = 1;
        }
        else {
            let count = 1;
            let intTagNumberBuffer = this.valueHexView = new Uint8Array(255);
            let tagNumberBufferMaxLength = 255;
            while (intBuffer[count] & 0x80) {
                intTagNumberBuffer[count - 1] = intBuffer[count] & 0x7F;
                count++;
                if (count >= intBuffer.length) {
                    this.error = "End of input reached before message was fully decoded";
                    return -1;
                }
                if (count === tagNumberBufferMaxLength) {
                    tagNumberBufferMaxLength += 255;
                    const tempBufferView = new Uint8Array(tagNumberBufferMaxLength);
                    for (let i = 0; i < intTagNumberBuffer.length; i++)
                        tempBufferView[i] = intTagNumberBuffer[i];
                    intTagNumberBuffer = this.valueHexView = new Uint8Array(tagNumberBufferMaxLength);
                }
            }
            this.blockLength = (count + 1);
            intTagNumberBuffer[count - 1] = intBuffer[count] & 0x7F;
            const tempBufferView = new Uint8Array(count);
            for (let i = 0; i < count; i++)
                tempBufferView[i] = intTagNumberBuffer[i];
            intTagNumberBuffer = this.valueHexView = new Uint8Array(count);
            intTagNumberBuffer.set(tempBufferView);
            if (this.blockLength <= 9)
                this.tagNumber = utilFromBase(intTagNumberBuffer, 7);
            else {
                this.isHexOnly = true;
                this.warnings.push("Tag too long, represented as hex-coded");
            }
        }
        if (((this.tagClass === 1)) &&
            (this.isConstructed)) {
            switch (this.tagNumber) {
                case 1:
                case 2:
                case 5:
                case 6:
                case 9:
                case 13:
                case 14:
                case 23:
                case 24:
                case 31:
                case 32:
                case 33:
                case 34:
                    this.error = "Constructed encoding used for primitive type";
                    return -1;
            }
        }
        return (inputOffset + this.blockLength);
    }
    toJSON() {
        return {
            ...super.toJSON(),
            tagClass: this.tagClass,
            tagNumber: this.tagNumber,
            isConstructed: this.isConstructed,
        };
    }
}
LocalIdentificationBlock.NAME = "identificationBlock";

class LocalLengthBlock extends LocalBaseBlock {
    constructor({ lenBlock = {}, } = {}) {
        var _a, _b, _c;
        super();
        this.isIndefiniteForm = (_a = lenBlock.isIndefiniteForm) !== null && _a !== void 0 ? _a : false;
        this.longFormUsed = (_b = lenBlock.longFormUsed) !== null && _b !== void 0 ? _b : false;
        this.length = (_c = lenBlock.length) !== null && _c !== void 0 ? _c : 0;
    }
    fromBER(inputBuffer, inputOffset, inputLength) {
        const view = BufferSourceConverter.toUint8Array(inputBuffer);
        if (!checkBufferParams(this, view, inputOffset, inputLength)) {
            return -1;
        }
        const intBuffer = view.subarray(inputOffset, inputOffset + inputLength);
        if (intBuffer.length === 0) {
            this.error = "Zero buffer length";
            return -1;
        }
        if (intBuffer[0] === 0xFF) {
            this.error = "Length block 0xFF is reserved by standard";
            return -1;
        }
        this.isIndefiniteForm = intBuffer[0] === 0x80;
        if (this.isIndefiniteForm) {
            this.blockLength = 1;
            return (inputOffset + this.blockLength);
        }
        this.longFormUsed = !!(intBuffer[0] & 0x80);
        if (this.longFormUsed === false) {
            this.length = (intBuffer[0]);
            this.blockLength = 1;
            return (inputOffset + this.blockLength);
        }
        const count = intBuffer[0] & 0x7F;
        if (count > 8) {
            this.error = "Too big integer";
            return -1;
        }
        if ((count + 1) > intBuffer.length) {
            this.error = "End of input reached before message was fully decoded";
            return -1;
        }
        const lenOffset = inputOffset + 1;
        const lengthBufferView = view.subarray(lenOffset, lenOffset + count);
        if (lengthBufferView[count - 1] === 0x00)
            this.warnings.push("Needlessly long encoded length");
        this.length = utilFromBase(lengthBufferView, 8);
        if (this.longFormUsed && (this.length <= 127))
            this.warnings.push("Unnecessary usage of long length form");
        this.blockLength = count + 1;
        return (inputOffset + this.blockLength);
    }
    toBER(sizeOnly = false) {
        let retBuf;
        let retView;
        if (this.length > 127)
            this.longFormUsed = true;
        if (this.isIndefiniteForm) {
            retBuf = new ArrayBuffer(1);
            if (sizeOnly === false) {
                retView = new Uint8Array(retBuf);
                retView[0] = 0x80;
            }
            return retBuf;
        }
        if (this.longFormUsed) {
            const encodedBuf = utilToBase(this.length, 8);
            if (encodedBuf.byteLength > 127) {
                this.error = "Too big length";
                return (EMPTY_BUFFER$1);
            }
            retBuf = new ArrayBuffer(encodedBuf.byteLength + 1);
            if (sizeOnly)
                return retBuf;
            const encodedView = new Uint8Array(encodedBuf);
            retView = new Uint8Array(retBuf);
            retView[0] = encodedBuf.byteLength | 0x80;
            for (let i = 0; i < encodedBuf.byteLength; i++)
                retView[i + 1] = encodedView[i];
            return retBuf;
        }
        retBuf = new ArrayBuffer(1);
        if (sizeOnly === false) {
            retView = new Uint8Array(retBuf);
            retView[0] = this.length;
        }
        return retBuf;
    }
    toJSON() {
        return {
            ...super.toJSON(),
            isIndefiniteForm: this.isIndefiniteForm,
            longFormUsed: this.longFormUsed,
            length: this.length,
        };
    }
}
LocalLengthBlock.NAME = "lengthBlock";

const typeStore = {};

class BaseBlock extends LocalBaseBlock {
    constructor({ name = EMPTY_STRING$1, optional = false, primitiveSchema, ...parameters } = {}, valueBlockType) {
        super(parameters);
        this.name = name;
        this.optional = optional;
        if (primitiveSchema) {
            this.primitiveSchema = primitiveSchema;
        }
        this.idBlock = new LocalIdentificationBlock(parameters);
        this.lenBlock = new LocalLengthBlock(parameters);
        this.valueBlock = valueBlockType ? new valueBlockType(parameters) : new ValueBlock(parameters);
    }
    fromBER(inputBuffer, inputOffset, inputLength) {
        const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, (this.lenBlock.isIndefiniteForm) ? inputLength : this.lenBlock.length);
        if (resultOffset === -1) {
            this.error = this.valueBlock.error;
            return resultOffset;
        }
        if (!this.idBlock.error.length)
            this.blockLength += this.idBlock.blockLength;
        if (!this.lenBlock.error.length)
            this.blockLength += this.lenBlock.blockLength;
        if (!this.valueBlock.error.length)
            this.blockLength += this.valueBlock.blockLength;
        return resultOffset;
    }
    toBER(sizeOnly, writer) {
        const _writer = writer || new ViewWriter();
        if (!writer) {
            prepareIndefiniteForm(this);
        }
        const idBlockBuf = this.idBlock.toBER(sizeOnly);
        _writer.write(idBlockBuf);
        if (this.lenBlock.isIndefiniteForm) {
            _writer.write(new Uint8Array([0x80]).buffer);
            this.valueBlock.toBER(sizeOnly, _writer);
            _writer.write(new ArrayBuffer(2));
        }
        else {
            const valueBlockBuf = this.valueBlock.toBER(sizeOnly);
            this.lenBlock.length = valueBlockBuf.byteLength;
            const lenBlockBuf = this.lenBlock.toBER(sizeOnly);
            _writer.write(lenBlockBuf);
            _writer.write(valueBlockBuf);
        }
        if (!writer) {
            return _writer.final();
        }
        return EMPTY_BUFFER$1;
    }
    toJSON() {
        const object = {
            ...super.toJSON(),
            idBlock: this.idBlock.toJSON(),
            lenBlock: this.lenBlock.toJSON(),
            valueBlock: this.valueBlock.toJSON(),
            name: this.name,
            optional: this.optional,
        };
        if (this.primitiveSchema)
            object.primitiveSchema = this.primitiveSchema.toJSON();
        return object;
    }
    toString(encoding = "ascii") {
        if (encoding === "ascii") {
            return this.onAsciiEncoding();
        }
        return Convert.ToHex(this.toBER());
    }
    onAsciiEncoding() {
        return `${this.constructor.NAME} : ${Convert.ToHex(this.valueBlock.valueBeforeDecodeView)}`;
    }
    isEqual(other) {
        if (this === other) {
            return true;
        }
        if (!(other instanceof this.constructor)) {
            return false;
        }
        const thisRaw = this.toBER();
        const otherRaw = other.toBER();
        return isEqualBuffer(thisRaw, otherRaw);
    }
}
BaseBlock.NAME = "BaseBlock";
function prepareIndefiniteForm(baseBlock) {
    if (baseBlock instanceof typeStore.Constructed) {
        for (const value of baseBlock.valueBlock.value) {
            if (prepareIndefiniteForm(value)) {
                baseBlock.lenBlock.isIndefiniteForm = true;
            }
        }
    }
    return !!baseBlock.lenBlock.isIndefiniteForm;
}

class BaseStringBlock extends BaseBlock {
    constructor({ value = EMPTY_STRING$1, ...parameters } = {}, stringValueBlockType) {
        super(parameters, stringValueBlockType);
        if (value) {
            this.fromString(value);
        }
    }
    getValue() {
        return this.valueBlock.value;
    }
    setValue(value) {
        this.valueBlock.value = value;
    }
    fromBER(inputBuffer, inputOffset, inputLength) {
        const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, (this.lenBlock.isIndefiniteForm) ? inputLength : this.lenBlock.length);
        if (resultOffset === -1) {
            this.error = this.valueBlock.error;
            return resultOffset;
        }
        this.fromBuffer(this.valueBlock.valueHexView);
        if (!this.idBlock.error.length)
            this.blockLength += this.idBlock.blockLength;
        if (!this.lenBlock.error.length)
            this.blockLength += this.lenBlock.blockLength;
        if (!this.valueBlock.error.length)
            this.blockLength += this.valueBlock.blockLength;
        return resultOffset;
    }
    onAsciiEncoding() {
        return `${this.constructor.NAME} : '${this.valueBlock.value}'`;
    }
}
BaseStringBlock.NAME = "BaseStringBlock";

class LocalPrimitiveValueBlock extends HexBlock(ValueBlock) {
    constructor({ isHexOnly = true, ...parameters } = {}) {
        super(parameters);
        this.isHexOnly = isHexOnly;
    }
}
LocalPrimitiveValueBlock.NAME = "PrimitiveValueBlock";

var _a$w;
class Primitive extends BaseBlock {
    constructor(parameters = {}) {
        super(parameters, LocalPrimitiveValueBlock);
        this.idBlock.isConstructed = false;
    }
}
_a$w = Primitive;
(() => {
    typeStore.Primitive = _a$w;
})();
Primitive.NAME = "PRIMITIVE";

function localChangeType(inputObject, newType) {
    if (inputObject instanceof newType) {
        return inputObject;
    }
    const newObject = new newType();
    newObject.idBlock = inputObject.idBlock;
    newObject.lenBlock = inputObject.lenBlock;
    newObject.warnings = inputObject.warnings;
    newObject.valueBeforeDecodeView = inputObject.valueBeforeDecodeView;
    return newObject;
}
function localFromBER(inputBuffer, inputOffset = 0, inputLength = inputBuffer.length) {
    const incomingOffset = inputOffset;
    let returnObject = new BaseBlock({}, ValueBlock);
    const baseBlock = new LocalBaseBlock();
    if (!checkBufferParams(baseBlock, inputBuffer, inputOffset, inputLength)) {
        returnObject.error = baseBlock.error;
        return {
            offset: -1,
            result: returnObject
        };
    }
    const intBuffer = inputBuffer.subarray(inputOffset, inputOffset + inputLength);
    if (!intBuffer.length) {
        returnObject.error = "Zero buffer length";
        return {
            offset: -1,
            result: returnObject
        };
    }
    let resultOffset = returnObject.idBlock.fromBER(inputBuffer, inputOffset, inputLength);
    if (returnObject.idBlock.warnings.length) {
        returnObject.warnings.concat(returnObject.idBlock.warnings);
    }
    if (resultOffset === -1) {
        returnObject.error = returnObject.idBlock.error;
        return {
            offset: -1,
            result: returnObject
        };
    }
    inputOffset = resultOffset;
    inputLength -= returnObject.idBlock.blockLength;
    resultOffset = returnObject.lenBlock.fromBER(inputBuffer, inputOffset, inputLength);
    if (returnObject.lenBlock.warnings.length) {
        returnObject.warnings.concat(returnObject.lenBlock.warnings);
    }
    if (resultOffset === -1) {
        returnObject.error = returnObject.lenBlock.error;
        return {
            offset: -1,
            result: returnObject
        };
    }
    inputOffset = resultOffset;
    inputLength -= returnObject.lenBlock.blockLength;
    if (!returnObject.idBlock.isConstructed &&
        returnObject.lenBlock.isIndefiniteForm) {
        returnObject.error = "Indefinite length form used for primitive encoding form";
        return {
            offset: -1,
            result: returnObject
        };
    }
    let newASN1Type = BaseBlock;
    switch (returnObject.idBlock.tagClass) {
        case 1:
            if ((returnObject.idBlock.tagNumber >= 37) &&
                (returnObject.idBlock.isHexOnly === false)) {
                returnObject.error = "UNIVERSAL 37 and upper tags are reserved by ASN.1 standard";
                return {
                    offset: -1,
                    result: returnObject
                };
            }
            switch (returnObject.idBlock.tagNumber) {
                case 0:
                    if ((returnObject.idBlock.isConstructed) &&
                        (returnObject.lenBlock.length > 0)) {
                        returnObject.error = "Type [UNIVERSAL 0] is reserved";
                        return {
                            offset: -1,
                            result: returnObject
                        };
                    }
                    newASN1Type = typeStore.EndOfContent;
                    break;
                case 1:
                    newASN1Type = typeStore.Boolean;
                    break;
                case 2:
                    newASN1Type = typeStore.Integer;
                    break;
                case 3:
                    newASN1Type = typeStore.BitString;
                    break;
                case 4:
                    newASN1Type = typeStore.OctetString;
                    break;
                case 5:
                    newASN1Type = typeStore.Null;
                    break;
                case 6:
                    newASN1Type = typeStore.ObjectIdentifier;
                    break;
                case 10:
                    newASN1Type = typeStore.Enumerated;
                    break;
                case 12:
                    newASN1Type = typeStore.Utf8String;
                    break;
                case 13:
                    newASN1Type = typeStore.RelativeObjectIdentifier;
                    break;
                case 14:
                    newASN1Type = typeStore.TIME;
                    break;
                case 15:
                    returnObject.error = "[UNIVERSAL 15] is reserved by ASN.1 standard";
                    return {
                        offset: -1,
                        result: returnObject
                    };
                case 16:
                    newASN1Type = typeStore.Sequence;
                    break;
                case 17:
                    newASN1Type = typeStore.Set;
                    break;
                case 18:
                    newASN1Type = typeStore.NumericString;
                    break;
                case 19:
                    newASN1Type = typeStore.PrintableString;
                    break;
                case 20:
                    newASN1Type = typeStore.TeletexString;
                    break;
                case 21:
                    newASN1Type = typeStore.VideotexString;
                    break;
                case 22:
                    newASN1Type = typeStore.IA5String;
                    break;
                case 23:
                    newASN1Type = typeStore.UTCTime;
                    break;
                case 24:
                    newASN1Type = typeStore.GeneralizedTime;
                    break;
                case 25:
                    newASN1Type = typeStore.GraphicString;
                    break;
                case 26:
                    newASN1Type = typeStore.VisibleString;
                    break;
                case 27:
                    newASN1Type = typeStore.GeneralString;
                    break;
                case 28:
                    newASN1Type = typeStore.UniversalString;
                    break;
                case 29:
                    newASN1Type = typeStore.CharacterString;
                    break;
                case 30:
                    newASN1Type = typeStore.BmpString;
                    break;
                case 31:
                    newASN1Type = typeStore.DATE;
                    break;
                case 32:
                    newASN1Type = typeStore.TimeOfDay;
                    break;
                case 33:
                    newASN1Type = typeStore.DateTime;
                    break;
                case 34:
                    newASN1Type = typeStore.Duration;
                    break;
                default: {
                    const newObject = returnObject.idBlock.isConstructed
                        ? new typeStore.Constructed()
                        : new typeStore.Primitive();
                    newObject.idBlock = returnObject.idBlock;
                    newObject.lenBlock = returnObject.lenBlock;
                    newObject.warnings = returnObject.warnings;
                    returnObject = newObject;
                }
            }
            break;
        case 2:
        case 3:
        case 4:
        default: {
            newASN1Type = returnObject.idBlock.isConstructed
                ? typeStore.Constructed
                : typeStore.Primitive;
        }
    }
    returnObject = localChangeType(returnObject, newASN1Type);
    resultOffset = returnObject.fromBER(inputBuffer, inputOffset, returnObject.lenBlock.isIndefiniteForm ? inputLength : returnObject.lenBlock.length);
    returnObject.valueBeforeDecodeView = inputBuffer.subarray(incomingOffset, incomingOffset + returnObject.blockLength);
    return {
        offset: resultOffset,
        result: returnObject
    };
}
function fromBER(inputBuffer) {
    if (!inputBuffer.byteLength) {
        const result = new BaseBlock({}, ValueBlock);
        result.error = "Input buffer has zero length";
        return {
            offset: -1,
            result
        };
    }
    return localFromBER(BufferSourceConverter.toUint8Array(inputBuffer).slice(), 0, inputBuffer.byteLength);
}

function checkLen(indefiniteLength, length) {
    if (indefiniteLength) {
        return 1;
    }
    return length;
}
class LocalConstructedValueBlock extends ValueBlock {
    constructor({ value = [], isIndefiniteForm = false, ...parameters } = {}) {
        super(parameters);
        this.value = value;
        this.isIndefiniteForm = isIndefiniteForm;
    }
    fromBER(inputBuffer, inputOffset, inputLength) {
        const view = BufferSourceConverter.toUint8Array(inputBuffer);
        if (!checkBufferParams(this, view, inputOffset, inputLength)) {
            return -1;
        }
        this.valueBeforeDecodeView = view.subarray(inputOffset, inputOffset + inputLength);
        if (this.valueBeforeDecodeView.length === 0) {
            this.warnings.push("Zero buffer length");
            return inputOffset;
        }
        let currentOffset = inputOffset;
        while (checkLen(this.isIndefiniteForm, inputLength) > 0) {
            const returnObject = localFromBER(view, currentOffset, inputLength);
            if (returnObject.offset === -1) {
                this.error = returnObject.result.error;
                this.warnings.concat(returnObject.result.warnings);
                return -1;
            }
            currentOffset = returnObject.offset;
            this.blockLength += returnObject.result.blockLength;
            inputLength -= returnObject.result.blockLength;
            this.value.push(returnObject.result);
            if (this.isIndefiniteForm && returnObject.result.constructor.NAME === END_OF_CONTENT_NAME) {
                break;
            }
        }
        if (this.isIndefiniteForm) {
            if (this.value[this.value.length - 1].constructor.NAME === END_OF_CONTENT_NAME) {
                this.value.pop();
            }
            else {
                this.warnings.push("No EndOfContent block encoded");
            }
        }
        return currentOffset;
    }
    toBER(sizeOnly, writer) {
        const _writer = writer || new ViewWriter();
        for (let i = 0; i < this.value.length; i++) {
            this.value[i].toBER(sizeOnly, _writer);
        }
        if (!writer) {
            return _writer.final();
        }
        return EMPTY_BUFFER$1;
    }
    toJSON() {
        const object = {
            ...super.toJSON(),
            isIndefiniteForm: this.isIndefiniteForm,
            value: [],
        };
        for (const value of this.value) {
            object.value.push(value.toJSON());
        }
        return object;
    }
}
LocalConstructedValueBlock.NAME = "ConstructedValueBlock";

var _a$v;
class Constructed extends BaseBlock {
    constructor(parameters = {}) {
        super(parameters, LocalConstructedValueBlock);
        this.idBlock.isConstructed = true;
    }
    fromBER(inputBuffer, inputOffset, inputLength) {
        this.valueBlock.isIndefiniteForm = this.lenBlock.isIndefiniteForm;
        const resultOffset = this.valueBlock.fromBER(inputBuffer, inputOffset, (this.lenBlock.isIndefiniteForm) ? inputLength : this.lenBlock.length);
        if (resultOffset === -1) {
            this.error = this.valueBlock.error;
            return resultOffset;
        }
        if (!this.idBlock.error.length)
            this.blockLength += this.idBlock.blockLength;
        if (!this.lenBlock.error.length)
            this.blockLength += this.lenBlock.blockLength;
        if (!this.valueBlock.error.length)
            this.blockLength += this.valueBlock.blockLength;
        return resultOffset;
    }
    onAsciiEncoding() {
        const values = [];
        for (const value of this.valueBlock.value) {
            values.push(value.toString("ascii").split("\n").map(o => `  ${o}`).join("\n"));
        }
        const blockName = this.idBlock.tagClass === 3
            ? `[${this.idBlock.tagNumber}]`
            : this.constructor.NAME;
        return values.length
            ? `${blockName} :\n${values.join("\n")}`
            : `${blockName} :`;
    }
}
_a$v = Constructed;
(() => {
    typeStore.Constructed = _a$v;
})();
Constructed.NAME = "CONSTRUCTED";

class LocalEndOfContentValueBlock extends ValueBlock {
    fromBER(inputBuffer, inputOffset, inputLength) {
        return inputOffset;
    }
    toBER(sizeOnly) {
        return EMPTY_BUFFER$1;
    }
}
LocalEndOfContentValueBlock.override = "EndOfContentValueBlock";

var _a$u;
class EndOfContent extends BaseBlock {
    constructor(parameters = {}) {
        super(parameters, LocalEndOfContentValueBlock);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 0;
    }
}
_a$u = EndOfContent;
(() => {
    typeStore.EndOfContent = _a$u;
})();
EndOfContent.NAME = END_OF_CONTENT_NAME;

var _a$t;
class Null extends BaseBlock {
    constructor(parameters = {}) {
        super(parameters, ValueBlock);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 5;
    }
    fromBER(inputBuffer, inputOffset, inputLength) {
        if (this.lenBlock.length > 0)
            this.warnings.push("Non-zero length of value block for Null type");
        if (!this.idBlock.error.length)
            this.blockLength += this.idBlock.blockLength;
        if (!this.lenBlock.error.length)
            this.blockLength += this.lenBlock.blockLength;
        this.blockLength += inputLength;
        if ((inputOffset + inputLength) > inputBuffer.byteLength) {
            this.error = "End of input reached before message was fully decoded (inconsistent offset and length values)";
            return -1;
        }
        return (inputOffset + inputLength);
    }
    toBER(sizeOnly, writer) {
        const retBuf = new ArrayBuffer(2);
        if (!sizeOnly) {
            const retView = new Uint8Array(retBuf);
            retView[0] = 0x05;
            retView[1] = 0x00;
        }
        if (writer) {
            writer.write(retBuf);
        }
        return retBuf;
    }
    onAsciiEncoding() {
        return `${this.constructor.NAME}`;
    }
}
_a$t = Null;
(() => {
    typeStore.Null = _a$t;
})();
Null.NAME = "NULL";

class LocalBooleanValueBlock extends HexBlock(ValueBlock) {
    constructor({ value, ...parameters } = {}) {
        super(parameters);
        if (parameters.valueHex) {
            this.valueHexView = BufferSourceConverter.toUint8Array(parameters.valueHex);
        }
        else {
            this.valueHexView = new Uint8Array(1);
        }
        if (value) {
            this.value = value;
        }
    }
    get value() {
        for (const octet of this.valueHexView) {
            if (octet > 0) {
                return true;
            }
        }
        return false;
    }
    set value(value) {
        this.valueHexView[0] = value ? 0xFF : 0x00;
    }
    fromBER(inputBuffer, inputOffset, inputLength) {
        const inputView = BufferSourceConverter.toUint8Array(inputBuffer);
        if (!checkBufferParams(this, inputView, inputOffset, inputLength)) {
            return -1;
        }
        this.valueHexView = inputView.subarray(inputOffset, inputOffset + inputLength);
        if (inputLength > 1)
            this.warnings.push("Boolean value encoded in more then 1 octet");
        this.isHexOnly = true;
        utilDecodeTC.call(this);
        this.blockLength = inputLength;
        return (inputOffset + inputLength);
    }
    toBER() {
        return this.valueHexView.slice();
    }
    toJSON() {
        return {
            ...super.toJSON(),
            value: this.value,
        };
    }
}
LocalBooleanValueBlock.NAME = "BooleanValueBlock";

var _a$s;
class Boolean extends BaseBlock {
    constructor(parameters = {}) {
        super(parameters, LocalBooleanValueBlock);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 1;
    }
    getValue() {
        return this.valueBlock.value;
    }
    setValue(value) {
        this.valueBlock.value = value;
    }
    onAsciiEncoding() {
        return `${this.constructor.NAME} : ${this.getValue}`;
    }
}
_a$s = Boolean;
(() => {
    typeStore.Boolean = _a$s;
})();
Boolean.NAME = "BOOLEAN";

class LocalOctetStringValueBlock extends HexBlock(LocalConstructedValueBlock) {
    constructor({ isConstructed = false, ...parameters } = {}) {
        super(parameters);
        this.isConstructed = isConstructed;
    }
    fromBER(inputBuffer, inputOffset, inputLength) {
        let resultOffset = 0;
        if (this.isConstructed) {
            this.isHexOnly = false;
            resultOffset = LocalConstructedValueBlock.prototype.fromBER.call(this, inputBuffer, inputOffset, inputLength);
            if (resultOffset === -1)
                return resultOffset;
            for (let i = 0; i < this.value.length; i++) {
                const currentBlockName = this.value[i].constructor.NAME;
                if (currentBlockName === END_OF_CONTENT_NAME) {
                    if (this.isIndefiniteForm)
                        break;
                    else {
                        this.error = "EndOfContent is unexpected, OCTET STRING may consists of OCTET STRINGs only";
                        return -1;
                    }
                }
                if (currentBlockName !== OCTET_STRING_NAME) {
                    this.error = "OCTET STRING may consists of OCTET STRINGs only";
                    return -1;
                }
            }
        }
        else {
            this.isHexOnly = true;
            resultOffset = super.fromBER(inputBuffer, inputOffset, inputLength);
            this.blockLength = inputLength;
        }
        return resultOffset;
    }
    toBER(sizeOnly, writer) {
        if (this.isConstructed)
            return LocalConstructedValueBlock.prototype.toBER.call(this, sizeOnly, writer);
        return sizeOnly
            ? new ArrayBuffer(this.valueHexView.byteLength)
            : this.valueHexView.slice().buffer;
    }
    toJSON() {
        return {
            ...super.toJSON(),
            isConstructed: this.isConstructed,
        };
    }
}
LocalOctetStringValueBlock.NAME = "OctetStringValueBlock";

var _a$r;
class OctetString extends BaseBlock {
    constructor({ idBlock = {}, lenBlock = {}, ...parameters } = {}) {
        var _b, _c;
        (_b = parameters.isConstructed) !== null && _b !== void 0 ? _b : (parameters.isConstructed = !!((_c = parameters.value) === null || _c === void 0 ? void 0 : _c.length));
        super({
            idBlock: {
                isConstructed: parameters.isConstructed,
                ...idBlock,
            },
            lenBlock: {
                ...lenBlock,
                isIndefiniteForm: !!parameters.isIndefiniteForm,
            },
            ...parameters,
        }, LocalOctetStringValueBlock);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 4;
    }
    fromBER(inputBuffer, inputOffset, inputLength) {
        this.valueBlock.isConstructed = this.idBlock.isConstructed;
        this.valueBlock.isIndefiniteForm = this.lenBlock.isIndefiniteForm;
        if (inputLength === 0) {
            if (this.idBlock.error.length === 0)
                this.blockLength += this.idBlock.blockLength;
            if (this.lenBlock.error.length === 0)
                this.blockLength += this.lenBlock.blockLength;
            return inputOffset;
        }
        if (!this.valueBlock.isConstructed) {
            const view = inputBuffer instanceof ArrayBuffer ? new Uint8Array(inputBuffer) : inputBuffer;
            const buf = view.subarray(inputOffset, inputOffset + inputLength);
            try {
                if (buf.byteLength) {
                    const asn = localFromBER(buf, 0, buf.byteLength);
                    if (asn.offset !== -1 && asn.offset === inputLength) {
                        this.valueBlock.value = [asn.result];
                    }
                }
            }
            catch (e) {
            }
        }
        return super.fromBER(inputBuffer, inputOffset, inputLength);
    }
    onAsciiEncoding() {
        if (this.valueBlock.isConstructed || (this.valueBlock.value && this.valueBlock.value.length)) {
            return Constructed.prototype.onAsciiEncoding.call(this);
        }
        return `${this.constructor.NAME} : ${Convert.ToHex(this.valueBlock.valueHexView)}`;
    }
    getValue() {
        if (!this.idBlock.isConstructed) {
            return this.valueBlock.valueHexView.slice().buffer;
        }
        const array = [];
        for (const content of this.valueBlock.value) {
            if (content instanceof OctetString) {
                array.push(content.valueBlock.valueHexView);
            }
        }
        return BufferSourceConverter.concat(array);
    }
}
_a$r = OctetString;
(() => {
    typeStore.OctetString = _a$r;
})();
OctetString.NAME = OCTET_STRING_NAME;

class LocalBitStringValueBlock extends HexBlock(LocalConstructedValueBlock) {
    constructor({ unusedBits = 0, isConstructed = false, ...parameters } = {}) {
        super(parameters);
        this.unusedBits = unusedBits;
        this.isConstructed = isConstructed;
        this.blockLength = this.valueHexView.byteLength;
    }
    fromBER(inputBuffer, inputOffset, inputLength) {
        if (!inputLength) {
            return inputOffset;
        }
        let resultOffset = -1;
        if (this.isConstructed) {
            resultOffset = LocalConstructedValueBlock.prototype.fromBER.call(this, inputBuffer, inputOffset, inputLength);
            if (resultOffset === -1)
                return resultOffset;
            for (const value of this.value) {
                const currentBlockName = value.constructor.NAME;
                if (currentBlockName === END_OF_CONTENT_NAME) {
                    if (this.isIndefiniteForm)
                        break;
                    else {
                        this.error = "EndOfContent is unexpected, BIT STRING may consists of BIT STRINGs only";
                        return -1;
                    }
                }
                if (currentBlockName !== BIT_STRING_NAME) {
                    this.error = "BIT STRING may consists of BIT STRINGs only";
                    return -1;
                }
                const valueBlock = value.valueBlock;
                if ((this.unusedBits > 0) && (valueBlock.unusedBits > 0)) {
                    this.error = "Using of \"unused bits\" inside constructive BIT STRING allowed for least one only";
                    return -1;
                }
                this.unusedBits = valueBlock.unusedBits;
            }
            return resultOffset;
        }
        const inputView = BufferSourceConverter.toUint8Array(inputBuffer);
        if (!checkBufferParams(this, inputView, inputOffset, inputLength)) {
            return -1;
        }
        const intBuffer = inputView.subarray(inputOffset, inputOffset + inputLength);
        this.unusedBits = intBuffer[0];
        if (this.unusedBits > 7) {
            this.error = "Unused bits for BitString must be in range 0-7";
            return -1;
        }
        if (!this.unusedBits) {
            const buf = intBuffer.subarray(1);
            try {
                if (buf.byteLength) {
                    const asn = localFromBER(buf, 0, buf.byteLength);
                    if (asn.offset !== -1 && asn.offset === (inputLength - 1)) {
                        this.value = [asn.result];
                    }
                }
            }
            catch (e) {
            }
        }
        this.valueHexView = intBuffer.subarray(1);
        this.blockLength = intBuffer.length;
        return (inputOffset + inputLength);
    }
    toBER(sizeOnly, writer) {
        if (this.isConstructed) {
            return LocalConstructedValueBlock.prototype.toBER.call(this, sizeOnly, writer);
        }
        if (sizeOnly) {
            return new ArrayBuffer(this.valueHexView.byteLength + 1);
        }
        if (!this.valueHexView.byteLength) {
            return EMPTY_BUFFER$1;
        }
        const retView = new Uint8Array(this.valueHexView.length + 1);
        retView[0] = this.unusedBits;
        retView.set(this.valueHexView, 1);
        return retView.buffer;
    }
    toJSON() {
        return {
            ...super.toJSON(),
            unusedBits: this.unusedBits,
            isConstructed: this.isConstructed,
        };
    }
}
LocalBitStringValueBlock.NAME = "BitStringValueBlock";

var _a$q;
class BitString extends BaseBlock {
    constructor({ idBlock = {}, lenBlock = {}, ...parameters } = {}) {
        var _b, _c;
        (_b = parameters.isConstructed) !== null && _b !== void 0 ? _b : (parameters.isConstructed = !!((_c = parameters.value) === null || _c === void 0 ? void 0 : _c.length));
        super({
            idBlock: {
                isConstructed: parameters.isConstructed,
                ...idBlock,
            },
            lenBlock: {
                ...lenBlock,
                isIndefiniteForm: !!parameters.isIndefiniteForm,
            },
            ...parameters,
        }, LocalBitStringValueBlock);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 3;
    }
    fromBER(inputBuffer, inputOffset, inputLength) {
        this.valueBlock.isConstructed = this.idBlock.isConstructed;
        this.valueBlock.isIndefiniteForm = this.lenBlock.isIndefiniteForm;
        return super.fromBER(inputBuffer, inputOffset, inputLength);
    }
    onAsciiEncoding() {
        if (this.valueBlock.isConstructed || (this.valueBlock.value && this.valueBlock.value.length)) {
            return Constructed.prototype.onAsciiEncoding.call(this);
        }
        else {
            const bits = [];
            const valueHex = this.valueBlock.valueHexView;
            for (const byte of valueHex) {
                bits.push(byte.toString(2).padStart(8, "0"));
            }
            const bitsStr = bits.join("");
            return `${this.constructor.NAME} : ${bitsStr.substring(0, bitsStr.length - this.valueBlock.unusedBits)}`;
        }
    }
}
_a$q = BitString;
(() => {
    typeStore.BitString = _a$q;
})();
BitString.NAME = BIT_STRING_NAME;

var _a$p;
function viewAdd(first, second) {
    const c = new Uint8Array([0]);
    const firstView = new Uint8Array(first);
    const secondView = new Uint8Array(second);
    let firstViewCopy = firstView.slice(0);
    const firstViewCopyLength = firstViewCopy.length - 1;
    const secondViewCopy = secondView.slice(0);
    const secondViewCopyLength = secondViewCopy.length - 1;
    let value = 0;
    const max = (secondViewCopyLength < firstViewCopyLength) ? firstViewCopyLength : secondViewCopyLength;
    let counter = 0;
    for (let i = max; i >= 0; i--, counter++) {
        switch (true) {
            case (counter < secondViewCopy.length):
                value = firstViewCopy[firstViewCopyLength - counter] + secondViewCopy[secondViewCopyLength - counter] + c[0];
                break;
            default:
                value = firstViewCopy[firstViewCopyLength - counter] + c[0];
        }
        c[0] = value / 10;
        switch (true) {
            case (counter >= firstViewCopy.length):
                firstViewCopy = utilConcatView(new Uint8Array([value % 10]), firstViewCopy);
                break;
            default:
                firstViewCopy[firstViewCopyLength - counter] = value % 10;
        }
    }
    if (c[0] > 0)
        firstViewCopy = utilConcatView(c, firstViewCopy);
    return firstViewCopy;
}
function power2(n) {
    if (n >= powers2.length) {
        for (let p = powers2.length; p <= n; p++) {
            const c = new Uint8Array([0]);
            let digits = (powers2[p - 1]).slice(0);
            for (let i = (digits.length - 1); i >= 0; i--) {
                const newValue = new Uint8Array([(digits[i] << 1) + c[0]]);
                c[0] = newValue[0] / 10;
                digits[i] = newValue[0] % 10;
            }
            if (c[0] > 0)
                digits = utilConcatView(c, digits);
            powers2.push(digits);
        }
    }
    return powers2[n];
}
function viewSub(first, second) {
    let b = 0;
    const firstView = new Uint8Array(first);
    const secondView = new Uint8Array(second);
    const firstViewCopy = firstView.slice(0);
    const firstViewCopyLength = firstViewCopy.length - 1;
    const secondViewCopy = secondView.slice(0);
    const secondViewCopyLength = secondViewCopy.length - 1;
    let value;
    let counter = 0;
    for (let i = secondViewCopyLength; i >= 0; i--, counter++) {
        value = firstViewCopy[firstViewCopyLength - counter] - secondViewCopy[secondViewCopyLength - counter] - b;
        switch (true) {
            case (value < 0):
                b = 1;
                firstViewCopy[firstViewCopyLength - counter] = value + 10;
                break;
            default:
                b = 0;
                firstViewCopy[firstViewCopyLength - counter] = value;
        }
    }
    if (b > 0) {
        for (let i = (firstViewCopyLength - secondViewCopyLength + 1); i >= 0; i--, counter++) {
            value = firstViewCopy[firstViewCopyLength - counter] - b;
            if (value < 0) {
                b = 1;
                firstViewCopy[firstViewCopyLength - counter] = value + 10;
            }
            else {
                b = 0;
                firstViewCopy[firstViewCopyLength - counter] = value;
                break;
            }
        }
    }
    return firstViewCopy.slice();
}
class LocalIntegerValueBlock extends HexBlock(ValueBlock) {
    constructor({ value, ...parameters } = {}) {
        super(parameters);
        this._valueDec = 0;
        if (parameters.valueHex) {
            this.setValueHex();
        }
        if (value !== undefined) {
            this.valueDec = value;
        }
    }
    setValueHex() {
        if (this.valueHexView.length >= 4) {
            this.warnings.push("Too big Integer for decoding, hex only");
            this.isHexOnly = true;
            this._valueDec = 0;
        }
        else {
            this.isHexOnly = false;
            if (this.valueHexView.length > 0) {
                this._valueDec = utilDecodeTC.call(this);
            }
        }
    }
    set valueDec(v) {
        this._valueDec = v;
        this.isHexOnly = false;
        this.valueHexView = new Uint8Array(utilEncodeTC(v));
    }
    get valueDec() {
        return this._valueDec;
    }
    fromDER(inputBuffer, inputOffset, inputLength, expectedLength = 0) {
        const offset = this.fromBER(inputBuffer, inputOffset, inputLength);
        if (offset === -1)
            return offset;
        const view = this.valueHexView;
        if ((view[0] === 0x00) && ((view[1] & 0x80) !== 0)) {
            this.valueHexView = view.subarray(1);
        }
        else {
            if (expectedLength !== 0) {
                if (view.length < expectedLength) {
                    if ((expectedLength - view.length) > 1)
                        expectedLength = view.length + 1;
                    this.valueHexView = view.subarray(expectedLength - view.length);
                }
            }
        }
        return offset;
    }
    toDER(sizeOnly = false) {
        const view = this.valueHexView;
        switch (true) {
            case ((view[0] & 0x80) !== 0):
                {
                    const updatedView = new Uint8Array(this.valueHexView.length + 1);
                    updatedView[0] = 0x00;
                    updatedView.set(view, 1);
                    this.valueHexView = updatedView;
                }
                break;
            case ((view[0] === 0x00) && ((view[1] & 0x80) === 0)):
                {
                    this.valueHexView = this.valueHexView.subarray(1);
                }
                break;
        }
        return this.toBER(sizeOnly);
    }
    fromBER(inputBuffer, inputOffset, inputLength) {
        const resultOffset = super.fromBER(inputBuffer, inputOffset, inputLength);
        if (resultOffset === -1) {
            return resultOffset;
        }
        this.setValueHex();
        return resultOffset;
    }
    toBER(sizeOnly) {
        return sizeOnly
            ? new ArrayBuffer(this.valueHexView.length)
            : this.valueHexView.slice().buffer;
    }
    toJSON() {
        return {
            ...super.toJSON(),
            valueDec: this.valueDec,
        };
    }
    toString() {
        const firstBit = (this.valueHexView.length * 8) - 1;
        let digits = new Uint8Array((this.valueHexView.length * 8) / 3);
        let bitNumber = 0;
        let currentByte;
        const asn1View = this.valueHexView;
        let result = "";
        let flag = false;
        for (let byteNumber = (asn1View.byteLength - 1); byteNumber >= 0; byteNumber--) {
            currentByte = asn1View[byteNumber];
            for (let i = 0; i < 8; i++) {
                if ((currentByte & 1) === 1) {
                    switch (bitNumber) {
                        case firstBit:
                            digits = viewSub(power2(bitNumber), digits);
                            result = "-";
                            break;
                        default:
                            digits = viewAdd(digits, power2(bitNumber));
                    }
                }
                bitNumber++;
                currentByte >>= 1;
            }
        }
        for (let i = 0; i < digits.length; i++) {
            if (digits[i])
                flag = true;
            if (flag)
                result += digitsString.charAt(digits[i]);
        }
        if (flag === false)
            result += digitsString.charAt(0);
        return result;
    }
}
_a$p = LocalIntegerValueBlock;
LocalIntegerValueBlock.NAME = "IntegerValueBlock";
(() => {
    Object.defineProperty(_a$p.prototype, "valueHex", {
        set: function (v) {
            this.valueHexView = new Uint8Array(v);
            this.setValueHex();
        },
        get: function () {
            return this.valueHexView.slice().buffer;
        },
    });
})();

var _a$o;
class Integer extends BaseBlock {
    constructor(parameters = {}) {
        super(parameters, LocalIntegerValueBlock);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 2;
    }
    toBigInt() {
        assertBigInt();
        return BigInt(this.valueBlock.toString());
    }
    static fromBigInt(value) {
        assertBigInt();
        const bigIntValue = BigInt(value);
        const writer = new ViewWriter();
        const hex = bigIntValue.toString(16).replace(/^-/, "");
        const view = new Uint8Array(Convert.FromHex(hex));
        if (bigIntValue < 0) {
            const first = new Uint8Array(view.length + (view[0] & 0x80 ? 1 : 0));
            first[0] |= 0x80;
            const firstInt = BigInt(`0x${Convert.ToHex(first)}`);
            const secondInt = firstInt + bigIntValue;
            const second = BufferSourceConverter.toUint8Array(Convert.FromHex(secondInt.toString(16)));
            second[0] |= 0x80;
            writer.write(second);
        }
        else {
            if (view[0] & 0x80) {
                writer.write(new Uint8Array([0]));
            }
            writer.write(view);
        }
        const res = new Integer({
            valueHex: writer.final(),
        });
        return res;
    }
    convertToDER() {
        const integer = new Integer({ valueHex: this.valueBlock.valueHexView });
        integer.valueBlock.toDER();
        return integer;
    }
    convertFromDER() {
        return new Integer({
            valueHex: this.valueBlock.valueHexView[0] === 0
                ? this.valueBlock.valueHexView.subarray(1)
                : this.valueBlock.valueHexView,
        });
    }
    onAsciiEncoding() {
        return `${this.constructor.NAME} : ${this.valueBlock.toString()}`;
    }
}
_a$o = Integer;
(() => {
    typeStore.Integer = _a$o;
})();
Integer.NAME = "INTEGER";

var _a$n;
class Enumerated extends Integer {
    constructor(parameters = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 10;
    }
}
_a$n = Enumerated;
(() => {
    typeStore.Enumerated = _a$n;
})();
Enumerated.NAME = "ENUMERATED";

class LocalSidValueBlock extends HexBlock(ValueBlock) {
    constructor({ valueDec = -1, isFirstSid = false, ...parameters } = {}) {
        super(parameters);
        this.valueDec = valueDec;
        this.isFirstSid = isFirstSid;
    }
    fromBER(inputBuffer, inputOffset, inputLength) {
        if (!inputLength) {
            return inputOffset;
        }
        const inputView = BufferSourceConverter.toUint8Array(inputBuffer);
        if (!checkBufferParams(this, inputView, inputOffset, inputLength)) {
            return -1;
        }
        const intBuffer = inputView.subarray(inputOffset, inputOffset + inputLength);
        this.valueHexView = new Uint8Array(inputLength);
        for (let i = 0; i < inputLength; i++) {
            this.valueHexView[i] = intBuffer[i] & 0x7F;
            this.blockLength++;
            if ((intBuffer[i] & 0x80) === 0x00)
                break;
        }
        const tempView = new Uint8Array(this.blockLength);
        for (let i = 0; i < this.blockLength; i++) {
            tempView[i] = this.valueHexView[i];
        }
        this.valueHexView = tempView;
        if ((intBuffer[this.blockLength - 1] & 0x80) !== 0x00) {
            this.error = "End of input reached before message was fully decoded";
            return -1;
        }
        if (this.valueHexView[0] === 0x00)
            this.warnings.push("Needlessly long format of SID encoding");
        if (this.blockLength <= 8)
            this.valueDec = utilFromBase(this.valueHexView, 7);
        else {
            this.isHexOnly = true;
            this.warnings.push("Too big SID for decoding, hex only");
        }
        return (inputOffset + this.blockLength);
    }
    set valueBigInt(value) {
        assertBigInt();
        let bits = BigInt(value).toString(2);
        while (bits.length % 7) {
            bits = "0" + bits;
        }
        const bytes = new Uint8Array(bits.length / 7);
        for (let i = 0; i < bytes.length; i++) {
            bytes[i] = parseInt(bits.slice(i * 7, i * 7 + 7), 2) + (i + 1 < bytes.length ? 0x80 : 0);
        }
        this.fromBER(bytes.buffer, 0, bytes.length);
    }
    toBER(sizeOnly) {
        if (this.isHexOnly) {
            if (sizeOnly)
                return (new ArrayBuffer(this.valueHexView.byteLength));
            const curView = this.valueHexView;
            const retView = new Uint8Array(this.blockLength);
            for (let i = 0; i < (this.blockLength - 1); i++)
                retView[i] = curView[i] | 0x80;
            retView[this.blockLength - 1] = curView[this.blockLength - 1];
            return retView.buffer;
        }
        const encodedBuf = utilToBase(this.valueDec, 7);
        if (encodedBuf.byteLength === 0) {
            this.error = "Error during encoding SID value";
            return EMPTY_BUFFER$1;
        }
        const retView = new Uint8Array(encodedBuf.byteLength);
        if (!sizeOnly) {
            const encodedView = new Uint8Array(encodedBuf);
            const len = encodedBuf.byteLength - 1;
            for (let i = 0; i < len; i++)
                retView[i] = encodedView[i] | 0x80;
            retView[len] = encodedView[len];
        }
        return retView;
    }
    toString() {
        let result = "";
        if (this.isHexOnly)
            result = Convert.ToHex(this.valueHexView);
        else {
            if (this.isFirstSid) {
                let sidValue = this.valueDec;
                if (this.valueDec <= 39)
                    result = "0.";
                else {
                    if (this.valueDec <= 79) {
                        result = "1.";
                        sidValue -= 40;
                    }
                    else {
                        result = "2.";
                        sidValue -= 80;
                    }
                }
                result += sidValue.toString();
            }
            else
                result = this.valueDec.toString();
        }
        return result;
    }
    toJSON() {
        return {
            ...super.toJSON(),
            valueDec: this.valueDec,
            isFirstSid: this.isFirstSid,
        };
    }
}
LocalSidValueBlock.NAME = "sidBlock";

class LocalObjectIdentifierValueBlock extends ValueBlock {
    constructor({ value = EMPTY_STRING$1, ...parameters } = {}) {
        super(parameters);
        this.value = [];
        if (value) {
            this.fromString(value);
        }
    }
    fromBER(inputBuffer, inputOffset, inputLength) {
        let resultOffset = inputOffset;
        while (inputLength > 0) {
            const sidBlock = new LocalSidValueBlock();
            resultOffset = sidBlock.fromBER(inputBuffer, resultOffset, inputLength);
            if (resultOffset === -1) {
                this.blockLength = 0;
                this.error = sidBlock.error;
                return resultOffset;
            }
            if (this.value.length === 0)
                sidBlock.isFirstSid = true;
            this.blockLength += sidBlock.blockLength;
            inputLength -= sidBlock.blockLength;
            this.value.push(sidBlock);
        }
        return resultOffset;
    }
    toBER(sizeOnly) {
        const retBuffers = [];
        for (let i = 0; i < this.value.length; i++) {
            const valueBuf = this.value[i].toBER(sizeOnly);
            if (valueBuf.byteLength === 0) {
                this.error = this.value[i].error;
                return EMPTY_BUFFER$1;
            }
            retBuffers.push(valueBuf);
        }
        return concat(retBuffers);
    }
    fromString(string) {
        this.value = [];
        let pos1 = 0;
        let pos2 = 0;
        let sid = "";
        let flag = false;
        do {
            pos2 = string.indexOf(".", pos1);
            if (pos2 === -1)
                sid = string.substring(pos1);
            else
                sid = string.substring(pos1, pos2);
            pos1 = pos2 + 1;
            if (flag) {
                const sidBlock = this.value[0];
                let plus = 0;
                switch (sidBlock.valueDec) {
                    case 0:
                        break;
                    case 1:
                        plus = 40;
                        break;
                    case 2:
                        plus = 80;
                        break;
                    default:
                        this.value = [];
                        return;
                }
                const parsedSID = parseInt(sid, 10);
                if (isNaN(parsedSID))
                    return;
                sidBlock.valueDec = parsedSID + plus;
                flag = false;
            }
            else {
                const sidBlock = new LocalSidValueBlock();
                if (sid > Number.MAX_SAFE_INTEGER) {
                    assertBigInt();
                    const sidValue = BigInt(sid);
                    sidBlock.valueBigInt = sidValue;
                }
                else {
                    sidBlock.valueDec = parseInt(sid, 10);
                    if (isNaN(sidBlock.valueDec))
                        return;
                }
                if (!this.value.length) {
                    sidBlock.isFirstSid = true;
                    flag = true;
                }
                this.value.push(sidBlock);
            }
        } while (pos2 !== -1);
    }
    toString() {
        let result = "";
        let isHexOnly = false;
        for (let i = 0; i < this.value.length; i++) {
            isHexOnly = this.value[i].isHexOnly;
            let sidStr = this.value[i].toString();
            if (i !== 0)
                result = `${result}.`;
            if (isHexOnly) {
                sidStr = `{${sidStr}}`;
                if (this.value[i].isFirstSid)
                    result = `2.{${sidStr} - 80}`;
                else
                    result += sidStr;
            }
            else
                result += sidStr;
        }
        return result;
    }
    toJSON() {
        const object = {
            ...super.toJSON(),
            value: this.toString(),
            sidArray: [],
        };
        for (let i = 0; i < this.value.length; i++) {
            object.sidArray.push(this.value[i].toJSON());
        }
        return object;
    }
}
LocalObjectIdentifierValueBlock.NAME = "ObjectIdentifierValueBlock";

var _a$m;
class ObjectIdentifier extends BaseBlock {
    constructor(parameters = {}) {
        super(parameters, LocalObjectIdentifierValueBlock);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 6;
    }
    getValue() {
        return this.valueBlock.toString();
    }
    setValue(value) {
        this.valueBlock.fromString(value);
    }
    onAsciiEncoding() {
        return `${this.constructor.NAME} : ${this.valueBlock.toString() || "empty"}`;
    }
    toJSON() {
        return {
            ...super.toJSON(),
            value: this.getValue(),
        };
    }
}
_a$m = ObjectIdentifier;
(() => {
    typeStore.ObjectIdentifier = _a$m;
})();
ObjectIdentifier.NAME = "OBJECT IDENTIFIER";

class LocalRelativeSidValueBlock extends HexBlock(LocalBaseBlock) {
    constructor({ valueDec = 0, ...parameters } = {}) {
        super(parameters);
        this.valueDec = valueDec;
    }
    fromBER(inputBuffer, inputOffset, inputLength) {
        if (inputLength === 0)
            return inputOffset;
        const inputView = BufferSourceConverter.toUint8Array(inputBuffer);
        if (!checkBufferParams(this, inputView, inputOffset, inputLength))
            return -1;
        const intBuffer = inputView.subarray(inputOffset, inputOffset + inputLength);
        this.valueHexView = new Uint8Array(inputLength);
        for (let i = 0; i < inputLength; i++) {
            this.valueHexView[i] = intBuffer[i] & 0x7F;
            this.blockLength++;
            if ((intBuffer[i] & 0x80) === 0x00)
                break;
        }
        const tempView = new Uint8Array(this.blockLength);
        for (let i = 0; i < this.blockLength; i++)
            tempView[i] = this.valueHexView[i];
        this.valueHexView = tempView;
        if ((intBuffer[this.blockLength - 1] & 0x80) !== 0x00) {
            this.error = "End of input reached before message was fully decoded";
            return -1;
        }
        if (this.valueHexView[0] === 0x00)
            this.warnings.push("Needlessly long format of SID encoding");
        if (this.blockLength <= 8)
            this.valueDec = utilFromBase(this.valueHexView, 7);
        else {
            this.isHexOnly = true;
            this.warnings.push("Too big SID for decoding, hex only");
        }
        return (inputOffset + this.blockLength);
    }
    toBER(sizeOnly) {
        if (this.isHexOnly) {
            if (sizeOnly)
                return (new ArrayBuffer(this.valueHexView.byteLength));
            const curView = this.valueHexView;
            const retView = new Uint8Array(this.blockLength);
            for (let i = 0; i < (this.blockLength - 1); i++)
                retView[i] = curView[i] | 0x80;
            retView[this.blockLength - 1] = curView[this.blockLength - 1];
            return retView.buffer;
        }
        const encodedBuf = utilToBase(this.valueDec, 7);
        if (encodedBuf.byteLength === 0) {
            this.error = "Error during encoding SID value";
            return EMPTY_BUFFER$1;
        }
        const retView = new Uint8Array(encodedBuf.byteLength);
        if (!sizeOnly) {
            const encodedView = new Uint8Array(encodedBuf);
            const len = encodedBuf.byteLength - 1;
            for (let i = 0; i < len; i++)
                retView[i] = encodedView[i] | 0x80;
            retView[len] = encodedView[len];
        }
        return retView.buffer;
    }
    toString() {
        let result = "";
        if (this.isHexOnly)
            result = Convert.ToHex(this.valueHexView);
        else {
            result = this.valueDec.toString();
        }
        return result;
    }
    toJSON() {
        return {
            ...super.toJSON(),
            valueDec: this.valueDec,
        };
    }
}
LocalRelativeSidValueBlock.NAME = "relativeSidBlock";

class LocalRelativeObjectIdentifierValueBlock extends ValueBlock {
    constructor({ value = EMPTY_STRING$1, ...parameters } = {}) {
        super(parameters);
        this.value = [];
        if (value) {
            this.fromString(value);
        }
    }
    fromBER(inputBuffer, inputOffset, inputLength) {
        let resultOffset = inputOffset;
        while (inputLength > 0) {
            const sidBlock = new LocalRelativeSidValueBlock();
            resultOffset = sidBlock.fromBER(inputBuffer, resultOffset, inputLength);
            if (resultOffset === -1) {
                this.blockLength = 0;
                this.error = sidBlock.error;
                return resultOffset;
            }
            this.blockLength += sidBlock.blockLength;
            inputLength -= sidBlock.blockLength;
            this.value.push(sidBlock);
        }
        return resultOffset;
    }
    toBER(sizeOnly, writer) {
        const retBuffers = [];
        for (let i = 0; i < this.value.length; i++) {
            const valueBuf = this.value[i].toBER(sizeOnly);
            if (valueBuf.byteLength === 0) {
                this.error = this.value[i].error;
                return EMPTY_BUFFER$1;
            }
            retBuffers.push(valueBuf);
        }
        return concat(retBuffers);
    }
    fromString(string) {
        this.value = [];
        let pos1 = 0;
        let pos2 = 0;
        let sid = "";
        do {
            pos2 = string.indexOf(".", pos1);
            if (pos2 === -1)
                sid = string.substring(pos1);
            else
                sid = string.substring(pos1, pos2);
            pos1 = pos2 + 1;
            const sidBlock = new LocalRelativeSidValueBlock();
            sidBlock.valueDec = parseInt(sid, 10);
            if (isNaN(sidBlock.valueDec))
                return true;
            this.value.push(sidBlock);
        } while (pos2 !== -1);
        return true;
    }
    toString() {
        let result = "";
        let isHexOnly = false;
        for (let i = 0; i < this.value.length; i++) {
            isHexOnly = this.value[i].isHexOnly;
            let sidStr = this.value[i].toString();
            if (i !== 0)
                result = `${result}.`;
            if (isHexOnly) {
                sidStr = `{${sidStr}}`;
                result += sidStr;
            }
            else
                result += sidStr;
        }
        return result;
    }
    toJSON() {
        const object = {
            ...super.toJSON(),
            value: this.toString(),
            sidArray: [],
        };
        for (let i = 0; i < this.value.length; i++)
            object.sidArray.push(this.value[i].toJSON());
        return object;
    }
}
LocalRelativeObjectIdentifierValueBlock.NAME = "RelativeObjectIdentifierValueBlock";

var _a$l;
class RelativeObjectIdentifier extends BaseBlock {
    constructor(parameters = {}) {
        super(parameters, LocalRelativeObjectIdentifierValueBlock);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 13;
    }
    getValue() {
        return this.valueBlock.toString();
    }
    setValue(value) {
        this.valueBlock.fromString(value);
    }
    onAsciiEncoding() {
        return `${this.constructor.NAME} : ${this.valueBlock.toString() || "empty"}`;
    }
    toJSON() {
        return {
            ...super.toJSON(),
            value: this.getValue(),
        };
    }
}
_a$l = RelativeObjectIdentifier;
(() => {
    typeStore.RelativeObjectIdentifier = _a$l;
})();
RelativeObjectIdentifier.NAME = "RelativeObjectIdentifier";

var _a$k;
class Sequence extends Constructed {
    constructor(parameters = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 16;
    }
}
_a$k = Sequence;
(() => {
    typeStore.Sequence = _a$k;
})();
Sequence.NAME = "SEQUENCE";

var _a$j;
class Set extends Constructed {
    constructor(parameters = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 17;
    }
}
_a$j = Set;
(() => {
    typeStore.Set = _a$j;
})();
Set.NAME = "SET";

class LocalStringValueBlock extends HexBlock(ValueBlock) {
    constructor({ ...parameters } = {}) {
        super(parameters);
        this.isHexOnly = true;
        this.value = EMPTY_STRING$1;
    }
    toJSON() {
        return {
            ...super.toJSON(),
            value: this.value,
        };
    }
}
LocalStringValueBlock.NAME = "StringValueBlock";

class LocalSimpleStringValueBlock extends LocalStringValueBlock {
}
LocalSimpleStringValueBlock.NAME = "SimpleStringValueBlock";

class LocalSimpleStringBlock extends BaseStringBlock {
    constructor({ ...parameters } = {}) {
        super(parameters, LocalSimpleStringValueBlock);
    }
    fromBuffer(inputBuffer) {
        this.valueBlock.value = String.fromCharCode.apply(null, BufferSourceConverter.toUint8Array(inputBuffer));
    }
    fromString(inputString) {
        const strLen = inputString.length;
        const view = this.valueBlock.valueHexView = new Uint8Array(strLen);
        for (let i = 0; i < strLen; i++)
            view[i] = inputString.charCodeAt(i);
        this.valueBlock.value = inputString;
    }
}
LocalSimpleStringBlock.NAME = "SIMPLE STRING";

class LocalUtf8StringValueBlock extends LocalSimpleStringBlock {
    fromBuffer(inputBuffer) {
        this.valueBlock.valueHexView = BufferSourceConverter.toUint8Array(inputBuffer);
        try {
            this.valueBlock.value = Convert.ToUtf8String(inputBuffer);
        }
        catch (ex) {
            this.warnings.push(`Error during "decodeURIComponent": ${ex}, using raw string`);
            this.valueBlock.value = Convert.ToBinary(inputBuffer);
        }
    }
    fromString(inputString) {
        this.valueBlock.valueHexView = new Uint8Array(Convert.FromUtf8String(inputString));
        this.valueBlock.value = inputString;
    }
}
LocalUtf8StringValueBlock.NAME = "Utf8StringValueBlock";

var _a$i;
class Utf8String extends LocalUtf8StringValueBlock {
    constructor(parameters = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 12;
    }
}
_a$i = Utf8String;
(() => {
    typeStore.Utf8String = _a$i;
})();
Utf8String.NAME = "UTF8String";

class LocalBmpStringValueBlock extends LocalSimpleStringBlock {
    fromBuffer(inputBuffer) {
        this.valueBlock.value = Convert.ToUtf16String(inputBuffer);
        this.valueBlock.valueHexView = BufferSourceConverter.toUint8Array(inputBuffer);
    }
    fromString(inputString) {
        this.valueBlock.value = inputString;
        this.valueBlock.valueHexView = new Uint8Array(Convert.FromUtf16String(inputString));
    }
}
LocalBmpStringValueBlock.NAME = "BmpStringValueBlock";

var _a$h;
class BmpString extends LocalBmpStringValueBlock {
    constructor({ ...parameters } = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 30;
    }
}
_a$h = BmpString;
(() => {
    typeStore.BmpString = _a$h;
})();
BmpString.NAME = "BMPString";

class LocalUniversalStringValueBlock extends LocalSimpleStringBlock {
    fromBuffer(inputBuffer) {
        const copyBuffer = ArrayBuffer.isView(inputBuffer) ? inputBuffer.slice().buffer : inputBuffer.slice(0);
        const valueView = new Uint8Array(copyBuffer);
        for (let i = 0; i < valueView.length; i += 4) {
            valueView[i] = valueView[i + 3];
            valueView[i + 1] = valueView[i + 2];
            valueView[i + 2] = 0x00;
            valueView[i + 3] = 0x00;
        }
        this.valueBlock.value = String.fromCharCode.apply(null, new Uint32Array(copyBuffer));
    }
    fromString(inputString) {
        const strLength = inputString.length;
        const valueHexView = this.valueBlock.valueHexView = new Uint8Array(strLength * 4);
        for (let i = 0; i < strLength; i++) {
            const codeBuf = utilToBase(inputString.charCodeAt(i), 8);
            const codeView = new Uint8Array(codeBuf);
            if (codeView.length > 4)
                continue;
            const dif = 4 - codeView.length;
            for (let j = (codeView.length - 1); j >= 0; j--)
                valueHexView[i * 4 + j + dif] = codeView[j];
        }
        this.valueBlock.value = inputString;
    }
}
LocalUniversalStringValueBlock.NAME = "UniversalStringValueBlock";

var _a$g;
class UniversalString extends LocalUniversalStringValueBlock {
    constructor({ ...parameters } = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 28;
    }
}
_a$g = UniversalString;
(() => {
    typeStore.UniversalString = _a$g;
})();
UniversalString.NAME = "UniversalString";

var _a$f;
class NumericString extends LocalSimpleStringBlock {
    constructor(parameters = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 18;
    }
}
_a$f = NumericString;
(() => {
    typeStore.NumericString = _a$f;
})();
NumericString.NAME = "NumericString";

var _a$e;
class PrintableString extends LocalSimpleStringBlock {
    constructor(parameters = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 19;
    }
}
_a$e = PrintableString;
(() => {
    typeStore.PrintableString = _a$e;
})();
PrintableString.NAME = "PrintableString";

var _a$d;
class TeletexString extends LocalSimpleStringBlock {
    constructor(parameters = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 20;
    }
}
_a$d = TeletexString;
(() => {
    typeStore.TeletexString = _a$d;
})();
TeletexString.NAME = "TeletexString";

var _a$c;
class VideotexString extends LocalSimpleStringBlock {
    constructor(parameters = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 21;
    }
}
_a$c = VideotexString;
(() => {
    typeStore.VideotexString = _a$c;
})();
VideotexString.NAME = "VideotexString";

var _a$b;
class IA5String extends LocalSimpleStringBlock {
    constructor(parameters = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 22;
    }
}
_a$b = IA5String;
(() => {
    typeStore.IA5String = _a$b;
})();
IA5String.NAME = "IA5String";

var _a$a;
class GraphicString extends LocalSimpleStringBlock {
    constructor(parameters = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 25;
    }
}
_a$a = GraphicString;
(() => {
    typeStore.GraphicString = _a$a;
})();
GraphicString.NAME = "GraphicString";

var _a$9;
class VisibleString extends LocalSimpleStringBlock {
    constructor(parameters = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 26;
    }
}
_a$9 = VisibleString;
(() => {
    typeStore.VisibleString = _a$9;
})();
VisibleString.NAME = "VisibleString";

var _a$8;
class GeneralString extends LocalSimpleStringBlock {
    constructor(parameters = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 27;
    }
}
_a$8 = GeneralString;
(() => {
    typeStore.GeneralString = _a$8;
})();
GeneralString.NAME = "GeneralString";

var _a$7;
class CharacterString extends LocalSimpleStringBlock {
    constructor(parameters = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 29;
    }
}
_a$7 = CharacterString;
(() => {
    typeStore.CharacterString = _a$7;
})();
CharacterString.NAME = "CharacterString";

var _a$6;
class UTCTime extends VisibleString {
    constructor({ value, valueDate, ...parameters } = {}) {
        super(parameters);
        this.year = 0;
        this.month = 0;
        this.day = 0;
        this.hour = 0;
        this.minute = 0;
        this.second = 0;
        if (value) {
            this.fromString(value);
            this.valueBlock.valueHexView = new Uint8Array(value.length);
            for (let i = 0; i < value.length; i++)
                this.valueBlock.valueHexView[i] = value.charCodeAt(i);
        }
        if (valueDate) {
            this.fromDate(valueDate);
            this.valueBlock.valueHexView = new Uint8Array(this.toBuffer());
        }
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 23;
    }
    fromBuffer(inputBuffer) {
        this.fromString(String.fromCharCode.apply(null, BufferSourceConverter.toUint8Array(inputBuffer)));
    }
    toBuffer() {
        const str = this.toString();
        const buffer = new ArrayBuffer(str.length);
        const view = new Uint8Array(buffer);
        for (let i = 0; i < str.length; i++)
            view[i] = str.charCodeAt(i);
        return buffer;
    }
    fromDate(inputDate) {
        this.year = inputDate.getUTCFullYear();
        this.month = inputDate.getUTCMonth() + 1;
        this.day = inputDate.getUTCDate();
        this.hour = inputDate.getUTCHours();
        this.minute = inputDate.getUTCMinutes();
        this.second = inputDate.getUTCSeconds();
    }
    toDate() {
        return (new Date(Date.UTC(this.year, this.month - 1, this.day, this.hour, this.minute, this.second)));
    }
    fromString(inputString) {
        const parser = /(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})Z/ig;
        const parserArray = parser.exec(inputString);
        if (parserArray === null) {
            this.error = "Wrong input string for conversion";
            return;
        }
        const year = parseInt(parserArray[1], 10);
        if (year >= 50)
            this.year = 1900 + year;
        else
            this.year = 2000 + year;
        this.month = parseInt(parserArray[2], 10);
        this.day = parseInt(parserArray[3], 10);
        this.hour = parseInt(parserArray[4], 10);
        this.minute = parseInt(parserArray[5], 10);
        this.second = parseInt(parserArray[6], 10);
    }
    toString(encoding = "iso") {
        if (encoding === "iso") {
            const outputArray = new Array(7);
            outputArray[0] = padNumber(((this.year < 2000) ? (this.year - 1900) : (this.year - 2000)), 2);
            outputArray[1] = padNumber(this.month, 2);
            outputArray[2] = padNumber(this.day, 2);
            outputArray[3] = padNumber(this.hour, 2);
            outputArray[4] = padNumber(this.minute, 2);
            outputArray[5] = padNumber(this.second, 2);
            outputArray[6] = "Z";
            return outputArray.join("");
        }
        return super.toString(encoding);
    }
    onAsciiEncoding() {
        return `${this.constructor.NAME} : ${this.toDate().toISOString()}`;
    }
    toJSON() {
        return {
            ...super.toJSON(),
            year: this.year,
            month: this.month,
            day: this.day,
            hour: this.hour,
            minute: this.minute,
            second: this.second,
        };
    }
}
_a$6 = UTCTime;
(() => {
    typeStore.UTCTime = _a$6;
})();
UTCTime.NAME = "UTCTime";

var _a$5;
class GeneralizedTime extends UTCTime {
    constructor(parameters = {}) {
        var _b;
        super(parameters);
        (_b = this.millisecond) !== null && _b !== void 0 ? _b : (this.millisecond = 0);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 24;
    }
    fromDate(inputDate) {
        super.fromDate(inputDate);
        this.millisecond = inputDate.getUTCMilliseconds();
    }
    toDate() {
        return (new Date(Date.UTC(this.year, this.month - 1, this.day, this.hour, this.minute, this.second, this.millisecond)));
    }
    fromString(inputString) {
        let isUTC = false;
        let timeString = "";
        let dateTimeString = "";
        let fractionPart = 0;
        let parser;
        let hourDifference = 0;
        let minuteDifference = 0;
        if (inputString[inputString.length - 1] === "Z") {
            timeString = inputString.substring(0, inputString.length - 1);
            isUTC = true;
        }
        else {
            const number = new Number(inputString[inputString.length - 1]);
            if (isNaN(number.valueOf()))
                throw new Error("Wrong input string for conversion");
            timeString = inputString;
        }
        if (isUTC) {
            if (timeString.indexOf("+") !== -1)
                throw new Error("Wrong input string for conversion");
            if (timeString.indexOf("-") !== -1)
                throw new Error("Wrong input string for conversion");
        }
        else {
            let multiplier = 1;
            let differencePosition = timeString.indexOf("+");
            let differenceString = "";
            if (differencePosition === -1) {
                differencePosition = timeString.indexOf("-");
                multiplier = -1;
            }
            if (differencePosition !== -1) {
                differenceString = timeString.substring(differencePosition + 1);
                timeString = timeString.substring(0, differencePosition);
                if ((differenceString.length !== 2) && (differenceString.length !== 4))
                    throw new Error("Wrong input string for conversion");
                let number = parseInt(differenceString.substring(0, 2), 10);
                if (isNaN(number.valueOf()))
                    throw new Error("Wrong input string for conversion");
                hourDifference = multiplier * number;
                if (differenceString.length === 4) {
                    number = parseInt(differenceString.substring(2, 4), 10);
                    if (isNaN(number.valueOf()))
                        throw new Error("Wrong input string for conversion");
                    minuteDifference = multiplier * number;
                }
            }
        }
        let fractionPointPosition = timeString.indexOf(".");
        if (fractionPointPosition === -1)
            fractionPointPosition = timeString.indexOf(",");
        if (fractionPointPosition !== -1) {
            const fractionPartCheck = new Number(`0${timeString.substring(fractionPointPosition)}`);
            if (isNaN(fractionPartCheck.valueOf()))
                throw new Error("Wrong input string for conversion");
            fractionPart = fractionPartCheck.valueOf();
            dateTimeString = timeString.substring(0, fractionPointPosition);
        }
        else
            dateTimeString = timeString;
        switch (true) {
            case (dateTimeString.length === 8):
                parser = /(\d{4})(\d{2})(\d{2})/ig;
                if (fractionPointPosition !== -1)
                    throw new Error("Wrong input string for conversion");
                break;
            case (dateTimeString.length === 10):
                parser = /(\d{4})(\d{2})(\d{2})(\d{2})/ig;
                if (fractionPointPosition !== -1) {
                    let fractionResult = 60 * fractionPart;
                    this.minute = Math.floor(fractionResult);
                    fractionResult = 60 * (fractionResult - this.minute);
                    this.second = Math.floor(fractionResult);
                    fractionResult = 1000 * (fractionResult - this.second);
                    this.millisecond = Math.floor(fractionResult);
                }
                break;
            case (dateTimeString.length === 12):
                parser = /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})/ig;
                if (fractionPointPosition !== -1) {
                    let fractionResult = 60 * fractionPart;
                    this.second = Math.floor(fractionResult);
                    fractionResult = 1000 * (fractionResult - this.second);
                    this.millisecond = Math.floor(fractionResult);
                }
                break;
            case (dateTimeString.length === 14):
                parser = /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/ig;
                if (fractionPointPosition !== -1) {
                    const fractionResult = 1000 * fractionPart;
                    this.millisecond = Math.floor(fractionResult);
                }
                break;
            default:
                throw new Error("Wrong input string for conversion");
        }
        const parserArray = parser.exec(dateTimeString);
        if (parserArray === null)
            throw new Error("Wrong input string for conversion");
        for (let j = 1; j < parserArray.length; j++) {
            switch (j) {
                case 1:
                    this.year = parseInt(parserArray[j], 10);
                    break;
                case 2:
                    this.month = parseInt(parserArray[j], 10);
                    break;
                case 3:
                    this.day = parseInt(parserArray[j], 10);
                    break;
                case 4:
                    this.hour = parseInt(parserArray[j], 10) + hourDifference;
                    break;
                case 5:
                    this.minute = parseInt(parserArray[j], 10) + minuteDifference;
                    break;
                case 6:
                    this.second = parseInt(parserArray[j], 10);
                    break;
                default:
                    throw new Error("Wrong input string for conversion");
            }
        }
        if (isUTC === false) {
            const tempDate = new Date(this.year, this.month, this.day, this.hour, this.minute, this.second, this.millisecond);
            this.year = tempDate.getUTCFullYear();
            this.month = tempDate.getUTCMonth();
            this.day = tempDate.getUTCDay();
            this.hour = tempDate.getUTCHours();
            this.minute = tempDate.getUTCMinutes();
            this.second = tempDate.getUTCSeconds();
            this.millisecond = tempDate.getUTCMilliseconds();
        }
    }
    toString(encoding = "iso") {
        if (encoding === "iso") {
            const outputArray = [];
            outputArray.push(padNumber(this.year, 4));
            outputArray.push(padNumber(this.month, 2));
            outputArray.push(padNumber(this.day, 2));
            outputArray.push(padNumber(this.hour, 2));
            outputArray.push(padNumber(this.minute, 2));
            outputArray.push(padNumber(this.second, 2));
            if (this.millisecond !== 0) {
                outputArray.push(".");
                outputArray.push(padNumber(this.millisecond, 3));
            }
            outputArray.push("Z");
            return outputArray.join("");
        }
        return super.toString(encoding);
    }
    toJSON() {
        return {
            ...super.toJSON(),
            millisecond: this.millisecond,
        };
    }
}
_a$5 = GeneralizedTime;
(() => {
    typeStore.GeneralizedTime = _a$5;
})();
GeneralizedTime.NAME = "GeneralizedTime";

var _a$4;
class DATE$2 extends Utf8String {
    constructor(parameters = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 31;
    }
}
_a$4 = DATE$2;
(() => {
    typeStore.DATE = _a$4;
})();
DATE$2.NAME = "DATE";

var _a$3;
class TimeOfDay extends Utf8String {
    constructor(parameters = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 32;
    }
}
_a$3 = TimeOfDay;
(() => {
    typeStore.TimeOfDay = _a$3;
})();
TimeOfDay.NAME = "TimeOfDay";

var _a$2;
class DateTime extends Utf8String {
    constructor(parameters = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 33;
    }
}
_a$2 = DateTime;
(() => {
    typeStore.DateTime = _a$2;
})();
DateTime.NAME = "DateTime";

var _a$1;
class Duration extends Utf8String {
    constructor(parameters = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 34;
    }
}
_a$1 = Duration;
(() => {
    typeStore.Duration = _a$1;
})();
Duration.NAME = "Duration";

var _a$x;
class TIME extends Utf8String {
    constructor(parameters = {}) {
        super(parameters);
        this.idBlock.tagClass = 1;
        this.idBlock.tagNumber = 14;
    }
}
_a$x = TIME;
(() => {
    typeStore.TIME = _a$x;
})();
TIME.NAME = "TIME";

class Any {
    constructor({ name = EMPTY_STRING$1, optional = false, } = {}) {
        this.name = name;
        this.optional = optional;
    }
}

class Choice extends Any {
    constructor({ value = [], ...parameters } = {}) {
        super(parameters);
        this.value = value;
    }
}

class Repeated extends Any {
    constructor({ value = new Any(), local = false, ...parameters } = {}) {
        super(parameters);
        this.value = value;
        this.local = local;
    }
}

class RawData {
    constructor({ data = EMPTY_VIEW } = {}) {
        this.dataView = BufferSourceConverter.toUint8Array(data);
    }
    get data() {
        return this.dataView.slice().buffer;
    }
    set data(value) {
        this.dataView = BufferSourceConverter.toUint8Array(value);
    }
    fromBER(inputBuffer, inputOffset, inputLength) {
        const endLength = inputOffset + inputLength;
        this.dataView = BufferSourceConverter.toUint8Array(inputBuffer).subarray(inputOffset, endLength);
        return endLength;
    }
    toBER(sizeOnly) {
        return this.dataView.slice().buffer;
    }
}

function compareSchema(root, inputData, inputSchema) {
    if (inputSchema instanceof Choice) {
        for (let j = 0; j < inputSchema.value.length; j++) {
            const result = compareSchema(root, inputData, inputSchema.value[j]);
            if (result.verified) {
                return {
                    verified: true,
                    result: root
                };
            }
        }
        {
            const _result = {
                verified: false,
                result: {
                    error: "Wrong values for Choice type"
                },
            };
            if (inputSchema.hasOwnProperty(NAME))
                _result.name = inputSchema.name;
            return _result;
        }
    }
    if (inputSchema instanceof Any) {
        if (inputSchema.hasOwnProperty(NAME))
            root[inputSchema.name] = inputData;
        return {
            verified: true,
            result: root
        };
    }
    if ((root instanceof Object) === false) {
        return {
            verified: false,
            result: { error: "Wrong root object" }
        };
    }
    if ((inputData instanceof Object) === false) {
        return {
            verified: false,
            result: { error: "Wrong ASN.1 data" }
        };
    }
    if ((inputSchema instanceof Object) === false) {
        return {
            verified: false,
            result: { error: "Wrong ASN.1 schema" }
        };
    }
    if ((ID_BLOCK in inputSchema) === false) {
        return {
            verified: false,
            result: { error: "Wrong ASN.1 schema" }
        };
    }
    if ((FROM_BER in inputSchema.idBlock) === false) {
        return {
            verified: false,
            result: { error: "Wrong ASN.1 schema" }
        };
    }
    if ((TO_BER in inputSchema.idBlock) === false) {
        return {
            verified: false,
            result: { error: "Wrong ASN.1 schema" }
        };
    }
    const encodedId = inputSchema.idBlock.toBER(false);
    if (encodedId.byteLength === 0) {
        return {
            verified: false,
            result: { error: "Error encoding idBlock for ASN.1 schema" }
        };
    }
    const decodedOffset = inputSchema.idBlock.fromBER(encodedId, 0, encodedId.byteLength);
    if (decodedOffset === -1) {
        return {
            verified: false,
            result: { error: "Error decoding idBlock for ASN.1 schema" }
        };
    }
    if (inputSchema.idBlock.hasOwnProperty(TAG_CLASS) === false) {
        return {
            verified: false,
            result: { error: "Wrong ASN.1 schema" }
        };
    }
    if (inputSchema.idBlock.tagClass !== inputData.idBlock.tagClass) {
        return {
            verified: false,
            result: root
        };
    }
    if (inputSchema.idBlock.hasOwnProperty(TAG_NUMBER) === false) {
        return {
            verified: false,
            result: { error: "Wrong ASN.1 schema" }
        };
    }
    if (inputSchema.idBlock.tagNumber !== inputData.idBlock.tagNumber) {
        return {
            verified: false,
            result: root
        };
    }
    if (inputSchema.idBlock.hasOwnProperty(IS_CONSTRUCTED) === false) {
        return {
            verified: false,
            result: { error: "Wrong ASN.1 schema" }
        };
    }
    if (inputSchema.idBlock.isConstructed !== inputData.idBlock.isConstructed) {
        return {
            verified: false,
            result: root
        };
    }
    if (!(IS_HEX_ONLY in inputSchema.idBlock)) {
        return {
            verified: false,
            result: { error: "Wrong ASN.1 schema" }
        };
    }
    if (inputSchema.idBlock.isHexOnly !== inputData.idBlock.isHexOnly) {
        return {
            verified: false,
            result: root
        };
    }
    if (inputSchema.idBlock.isHexOnly) {
        if ((VALUE_HEX_VIEW in inputSchema.idBlock) === false) {
            return {
                verified: false,
                result: { error: "Wrong ASN.1 schema" }
            };
        }
        const schemaView = inputSchema.idBlock.valueHexView;
        const asn1View = inputData.idBlock.valueHexView;
        if (schemaView.length !== asn1View.length) {
            return {
                verified: false,
                result: root
            };
        }
        for (let i = 0; i < schemaView.length; i++) {
            if (schemaView[i] !== asn1View[1]) {
                return {
                    verified: false,
                    result: root
                };
            }
        }
    }
    if (inputSchema.name) {
        inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, EMPTY_STRING$1);
        if (inputSchema.name)
            root[inputSchema.name] = inputData;
    }
    if (inputSchema instanceof typeStore.Constructed) {
        let admission = 0;
        let result = {
            verified: false,
            result: {
                error: "Unknown error",
            }
        };
        let maxLength = inputSchema.valueBlock.value.length;
        if (maxLength > 0) {
            if (inputSchema.valueBlock.value[0] instanceof Repeated) {
                maxLength = inputData.valueBlock.value.length;
            }
        }
        if (maxLength === 0) {
            return {
                verified: true,
                result: root
            };
        }
        if ((inputData.valueBlock.value.length === 0) &&
            (inputSchema.valueBlock.value.length !== 0)) {
            let _optional = true;
            for (let i = 0; i < inputSchema.valueBlock.value.length; i++)
                _optional = _optional && (inputSchema.valueBlock.value[i].optional || false);
            if (_optional) {
                return {
                    verified: true,
                    result: root
                };
            }
            if (inputSchema.name) {
                inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, EMPTY_STRING$1);
                if (inputSchema.name)
                    delete root[inputSchema.name];
            }
            root.error = "Inconsistent object length";
            return {
                verified: false,
                result: root
            };
        }
        for (let i = 0; i < maxLength; i++) {
            if ((i - admission) >= inputData.valueBlock.value.length) {
                if (inputSchema.valueBlock.value[i].optional === false) {
                    const _result = {
                        verified: false,
                        result: root
                    };
                    root.error = "Inconsistent length between ASN.1 data and schema";
                    if (inputSchema.name) {
                        inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, EMPTY_STRING$1);
                        if (inputSchema.name) {
                            delete root[inputSchema.name];
                            _result.name = inputSchema.name;
                        }
                    }
                    return _result;
                }
            }
            else {
                if (inputSchema.valueBlock.value[0] instanceof Repeated) {
                    result = compareSchema(root, inputData.valueBlock.value[i], inputSchema.valueBlock.value[0].value);
                    if (result.verified === false) {
                        if (inputSchema.valueBlock.value[0].optional)
                            admission++;
                        else {
                            if (inputSchema.name) {
                                inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, EMPTY_STRING$1);
                                if (inputSchema.name)
                                    delete root[inputSchema.name];
                            }
                            return result;
                        }
                    }
                    if ((NAME in inputSchema.valueBlock.value[0]) && (inputSchema.valueBlock.value[0].name.length > 0)) {
                        let arrayRoot = {};
                        if ((LOCAL in inputSchema.valueBlock.value[0]) && (inputSchema.valueBlock.value[0].local))
                            arrayRoot = inputData;
                        else
                            arrayRoot = root;
                        if (typeof arrayRoot[inputSchema.valueBlock.value[0].name] === "undefined")
                            arrayRoot[inputSchema.valueBlock.value[0].name] = [];
                        arrayRoot[inputSchema.valueBlock.value[0].name].push(inputData.valueBlock.value[i]);
                    }
                }
                else {
                    result = compareSchema(root, inputData.valueBlock.value[i - admission], inputSchema.valueBlock.value[i]);
                    if (result.verified === false) {
                        if (inputSchema.valueBlock.value[i].optional)
                            admission++;
                        else {
                            if (inputSchema.name) {
                                inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, EMPTY_STRING$1);
                                if (inputSchema.name)
                                    delete root[inputSchema.name];
                            }
                            return result;
                        }
                    }
                }
            }
        }
        if (result.verified === false) {
            const _result = {
                verified: false,
                result: root
            };
            if (inputSchema.name) {
                inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, EMPTY_STRING$1);
                if (inputSchema.name) {
                    delete root[inputSchema.name];
                    _result.name = inputSchema.name;
                }
            }
            return _result;
        }
        return {
            verified: true,
            result: root
        };
    }
    if (inputSchema.primitiveSchema &&
        (VALUE_HEX_VIEW in inputData.valueBlock)) {
        const asn1 = localFromBER(inputData.valueBlock.valueHexView);
        if (asn1.offset === -1) {
            const _result = {
                verified: false,
                result: asn1.result
            };
            if (inputSchema.name) {
                inputSchema.name = inputSchema.name.replace(/^\s+|\s+$/g, EMPTY_STRING$1);
                if (inputSchema.name) {
                    delete root[inputSchema.name];
                    _result.name = inputSchema.name;
                }
            }
            return _result;
        }
        return compareSchema(root, asn1.result, inputSchema.primitiveSchema);
    }
    return {
        verified: true,
        result: root
    };
}

const EMPTY_BUFFER = new ArrayBuffer(0);
const EMPTY_STRING = "";

class ArgumentError extends TypeError {
    constructor() {
        super(...arguments);
        this.name = ArgumentError.NAME;
    }
    static isType(value, type) {
        if (typeof type === "string") {
            if (type === "Array" && Array.isArray(value)) {
                return true;
            }
            else if (type === "ArrayBuffer" && value instanceof ArrayBuffer) {
                return true;
            }
            else if (type === "ArrayBufferView" && ArrayBuffer.isView(value)) {
                return true;
            }
            else if (typeof value === type) {
                return true;
            }
        }
        else if (value instanceof type) {
            return true;
        }
        return false;
    }
    static assert(value, name, ...types) {
        for (const type of types) {
            if (this.isType(value, type)) {
                return;
            }
        }
        const typeNames = types.map(o => o instanceof Function && "name" in o ? o.name : `${o}`);
        throw new ArgumentError(`Parameter '${name}' is not of type ${typeNames.length > 1 ? `(${typeNames.join(" or ")})` : typeNames[0]}`);
    }
}
ArgumentError.NAME = "ArgumentError";

class ParameterError extends TypeError {
    constructor(field, target = null, message) {
        super();
        this.name = ParameterError.NAME;
        this.field = field;
        if (target) {
            this.target = target;
        }
        if (message) {
            this.message = message;
        }
        else {
            this.message = `Absent mandatory parameter '${field}' ${target ? ` in '${target}'` : EMPTY_STRING}`;
        }
    }
    static assert(...args) {
        let target = null;
        let params;
        let fields;
        if (typeof args[0] === "string") {
            target = args[0];
            params = args[1];
            fields = args.slice(2);
        }
        else {
            params = args[0];
            fields = args.slice(1);
        }
        ArgumentError.assert(params, "parameters", "object");
        for (const field of fields) {
            const value = params[field];
            if (value === undefined || value === null) {
                throw new ParameterError(field, target);
            }
        }
    }
    static assertEmpty(value, name, target) {
        if (value === undefined || value === null) {
            throw new ParameterError(name, target);
        }
    }
}
ParameterError.NAME = "ParameterError";

class AsnError extends Error {
    static assertSchema(asn1, target) {
        if (!asn1.verified) {
            throw new Error(`Object's schema was not verified against input data for ${target}`);
        }
    }
    static assert(asn, target) {
        if (asn.offset === -1) {
            throw new AsnError(`Error during parsing of ASN.1 data. Data is not correct for '${target}'.`);
        }
    }
    constructor(message) {
        super(message);
        this.name = "AsnError";
    }
}

class PkiObject {
    static blockName() {
        return this.CLASS_NAME;
    }
    static fromBER(raw) {
        const asn1 = fromBER(raw);
        AsnError.assert(asn1, this.name);
        try {
            return new this({ schema: asn1.result });
        }
        catch (e) {
            throw new AsnError(`Cannot create '${this.CLASS_NAME}' from ASN.1 object`);
        }
    }
    static defaultValues(memberName) {
        throw new Error(`Invalid member name for ${this.CLASS_NAME} class: ${memberName}`);
    }
    static schema(parameters = {}) {
        throw new Error(`Method '${this.CLASS_NAME}.schema' should be overridden`);
    }
    get className() {
        return this.constructor.CLASS_NAME;
    }
    toString(encoding = "hex") {
        let schema;
        try {
            schema = this.toSchema();
        }
        catch (_a) {
            schema = this.toSchema(true);
        }
        return Convert.ToString(schema.toBER(), encoding);
    }
}
PkiObject.CLASS_NAME = "PkiObject";

function stringPrep(inputString) {
    let isSpace = false;
    let cutResult = EMPTY_STRING;
    const result = inputString.trim();
    for (let i = 0; i < result.length; i++) {
        if (result.charCodeAt(i) === 32) {
            if (isSpace === false)
                isSpace = true;
        }
        else {
            if (isSpace) {
                cutResult += " ";
                isSpace = false;
            }
            cutResult += result[i];
        }
    }
    return cutResult.toLowerCase();
}

const TYPE$5 = "type";
const VALUE$6 = "value";
class AttributeTypeAndValue extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.type = getParametersValue(parameters, TYPE$5, AttributeTypeAndValue.defaultValues(TYPE$5));
        this.value = getParametersValue(parameters, VALUE$6, AttributeTypeAndValue.defaultValues(VALUE$6));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case TYPE$5:
                return EMPTY_STRING;
            case VALUE$6:
                return {};
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.type || EMPTY_STRING) }),
                new Any({ name: (names.value || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, [
            TYPE$5,
            "typeValue"
        ]);
        const asn1 = compareSchema(schema, schema, AttributeTypeAndValue.schema({
            names: {
                type: TYPE$5,
                value: "typeValue"
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.type = asn1.result.type.valueBlock.toString();
        this.value = asn1.result.typeValue;
    }
    toSchema() {
        return (new Sequence({
            value: [
                new ObjectIdentifier({ value: this.type }),
                this.value
            ]
        }));
    }
    toJSON() {
        const _object = {
            type: this.type
        };
        if (Object.keys(this.value).length !== 0) {
            _object.value = (this.value).toJSON();
        }
        else {
            _object.value = this.value;
        }
        return _object;
    }
    isEqual(compareTo) {
        const stringBlockNames = [
            Utf8String.blockName(),
            BmpString.blockName(),
            UniversalString.blockName(),
            NumericString.blockName(),
            PrintableString.blockName(),
            TeletexString.blockName(),
            VideotexString.blockName(),
            IA5String.blockName(),
            GraphicString.blockName(),
            VisibleString.blockName(),
            GeneralString.blockName(),
            CharacterString.blockName()
        ];
        if (compareTo instanceof ArrayBuffer) {
            return BufferSourceConverter.isEqual(this.value.valueBeforeDecodeView, compareTo);
        }
        if (compareTo.constructor.blockName() === AttributeTypeAndValue.blockName()) {
            if (this.type !== compareTo.type)
                return false;
            const isStringPair = [false, false];
            const thisName = this.value.constructor.blockName();
            for (const name of stringBlockNames) {
                if (thisName === name) {
                    isStringPair[0] = true;
                }
                if (compareTo.value.constructor.blockName() === name) {
                    isStringPair[1] = true;
                }
            }
            if (isStringPair[0] !== isStringPair[1]) {
                return false;
            }
            const isString = (isStringPair[0] && isStringPair[1]);
            if (isString) {
                const value1 = stringPrep(this.value.valueBlock.value);
                const value2 = stringPrep(compareTo.value.valueBlock.value);
                if (value1.localeCompare(value2) !== 0)
                    return false;
            }
            else {
                if (!BufferSourceConverter.isEqual(this.value.valueBeforeDecodeView, compareTo.value.valueBeforeDecodeView))
                    return false;
            }
            return true;
        }
        return false;
    }
}
AttributeTypeAndValue.CLASS_NAME = "AttributeTypeAndValue";

const TYPE_AND_VALUES = "typesAndValues";
const VALUE_BEFORE_DECODE = "valueBeforeDecode";
const RDN = "RDN";
class RelativeDistinguishedNames extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.typesAndValues = getParametersValue(parameters, TYPE_AND_VALUES, RelativeDistinguishedNames.defaultValues(TYPE_AND_VALUES));
        this.valueBeforeDecode = getParametersValue(parameters, VALUE_BEFORE_DECODE, RelativeDistinguishedNames.defaultValues(VALUE_BEFORE_DECODE));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case TYPE_AND_VALUES:
                return [];
            case VALUE_BEFORE_DECODE:
                return EMPTY_BUFFER;
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case TYPE_AND_VALUES:
                return (memberValue.length === 0);
            case VALUE_BEFORE_DECODE:
                return (memberValue.byteLength === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Repeated({
                    name: (names.repeatedSequence || EMPTY_STRING),
                    value: new Set({
                        value: [
                            new Repeated({
                                name: (names.repeatedSet || EMPTY_STRING),
                                value: AttributeTypeAndValue.schema(names.typeAndValue || {})
                            })
                        ]
                    })
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, [
            RDN,
            TYPE_AND_VALUES
        ]);
        const asn1 = compareSchema(schema, schema, RelativeDistinguishedNames.schema({
            names: {
                blockName: RDN,
                repeatedSet: TYPE_AND_VALUES
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (TYPE_AND_VALUES in asn1.result) {
            this.typesAndValues = Array.from(asn1.result.typesAndValues, element => new AttributeTypeAndValue({ schema: element }));
        }
        this.valueBeforeDecode = asn1.result.RDN.valueBeforeDecodeView.slice().buffer;
    }
    toSchema() {
        if (this.valueBeforeDecode.byteLength === 0) {
            return (new Sequence({
                value: [new Set({
                        value: Array.from(this.typesAndValues, o => o.toSchema())
                    })]
            }));
        }
        const asn1 = fromBER(this.valueBeforeDecode);
        AsnError.assert(asn1, "RelativeDistinguishedNames");
        if (!(asn1.result instanceof Sequence)) {
            throw new Error("ASN.1 result should be SEQUENCE");
        }
        return asn1.result;
    }
    toJSON() {
        return {
            typesAndValues: Array.from(this.typesAndValues, o => o.toJSON())
        };
    }
    isEqual(compareTo) {
        if (compareTo instanceof RelativeDistinguishedNames) {
            if (this.typesAndValues.length !== compareTo.typesAndValues.length)
                return false;
            for (const [index, typeAndValue] of this.typesAndValues.entries()) {
                if (typeAndValue.isEqual(compareTo.typesAndValues[index]) === false)
                    return false;
            }
            return true;
        }
        if (compareTo instanceof ArrayBuffer) {
            return isEqualBuffer(this.valueBeforeDecode, compareTo);
        }
        return false;
    }
}
RelativeDistinguishedNames.CLASS_NAME = "RelativeDistinguishedNames";

const TYPE$4 = "type";
const VALUE$5 = "value";
function builtInStandardAttributes(parameters = {}, optional = false) {
    const names = getParametersValue(parameters, "names", {});
    return (new Sequence({
        optional,
        value: [
            new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 2,
                    tagNumber: 1
                },
                name: (names.country_name || EMPTY_STRING),
                value: [
                    new Choice({
                        value: [
                            new NumericString(),
                            new PrintableString()
                        ]
                    })
                ]
            }),
            new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 2,
                    tagNumber: 2
                },
                name: (names.administration_domain_name || EMPTY_STRING),
                value: [
                    new Choice({
                        value: [
                            new NumericString(),
                            new PrintableString()
                        ]
                    })
                ]
            }),
            new Primitive({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                name: (names.network_address || EMPTY_STRING),
                isHexOnly: true
            }),
            new Primitive({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                name: (names.terminal_identifier || EMPTY_STRING),
                isHexOnly: true
            }),
            new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 2
                },
                name: (names.private_domain_name || EMPTY_STRING),
                value: [
                    new Choice({
                        value: [
                            new NumericString(),
                            new PrintableString()
                        ]
                    })
                ]
            }),
            new Primitive({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 3
                },
                name: (names.organization_name || EMPTY_STRING),
                isHexOnly: true
            }),
            new Primitive({
                optional: true,
                name: (names.numeric_user_identifier || EMPTY_STRING),
                idBlock: {
                    tagClass: 3,
                    tagNumber: 4
                },
                isHexOnly: true
            }),
            new Constructed({
                optional: true,
                name: (names.personal_name || EMPTY_STRING),
                idBlock: {
                    tagClass: 3,
                    tagNumber: 5
                },
                value: [
                    new Primitive({
                        idBlock: {
                            tagClass: 3,
                            tagNumber: 0
                        },
                        isHexOnly: true
                    }),
                    new Primitive({
                        optional: true,
                        idBlock: {
                            tagClass: 3,
                            tagNumber: 1
                        },
                        isHexOnly: true
                    }),
                    new Primitive({
                        optional: true,
                        idBlock: {
                            tagClass: 3,
                            tagNumber: 2
                        },
                        isHexOnly: true
                    }),
                    new Primitive({
                        optional: true,
                        idBlock: {
                            tagClass: 3,
                            tagNumber: 3
                        },
                        isHexOnly: true
                    })
                ]
            }),
            new Constructed({
                optional: true,
                name: (names.organizational_unit_names || EMPTY_STRING),
                idBlock: {
                    tagClass: 3,
                    tagNumber: 6
                },
                value: [
                    new Repeated({
                        value: new PrintableString()
                    })
                ]
            })
        ]
    }));
}
function builtInDomainDefinedAttributes(optional = false) {
    return (new Sequence({
        optional,
        value: [
            new PrintableString(),
            new PrintableString()
        ]
    }));
}
function extensionAttributes(optional = false) {
    return (new Set({
        optional,
        value: [
            new Primitive({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                isHexOnly: true
            }),
            new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                value: [new Any()]
            })
        ]
    }));
}
class GeneralName extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.type = getParametersValue(parameters, TYPE$4, GeneralName.defaultValues(TYPE$4));
        this.value = getParametersValue(parameters, VALUE$5, GeneralName.defaultValues(VALUE$5));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case TYPE$4:
                return 9;
            case VALUE$5:
                return {};
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case TYPE$4:
                return (memberValue === GeneralName.defaultValues(memberName));
            case VALUE$5:
                return (Object.keys(memberValue).length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Choice({
            value: [
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    name: (names.blockName || EMPTY_STRING),
                    value: [
                        new ObjectIdentifier(),
                        new Constructed({
                            idBlock: {
                                tagClass: 3,
                                tagNumber: 0
                            },
                            value: [new Any()]
                        })
                    ]
                }),
                new Primitive({
                    name: (names.blockName || EMPTY_STRING),
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    }
                }),
                new Primitive({
                    name: (names.blockName || EMPTY_STRING),
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 2
                    }
                }),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 3
                    },
                    name: (names.blockName || EMPTY_STRING),
                    value: [
                        builtInStandardAttributes((names.builtInStandardAttributes || {}), false),
                        builtInDomainDefinedAttributes(true),
                        extensionAttributes(true)
                    ]
                }),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 4
                    },
                    name: (names.blockName || EMPTY_STRING),
                    value: [RelativeDistinguishedNames.schema(names.directoryName || {})]
                }),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 5
                    },
                    name: (names.blockName || EMPTY_STRING),
                    value: [
                        new Constructed({
                            optional: true,
                            idBlock: {
                                tagClass: 3,
                                tagNumber: 0
                            },
                            value: [
                                new Choice({
                                    value: [
                                        new TeletexString(),
                                        new PrintableString(),
                                        new UniversalString(),
                                        new Utf8String(),
                                        new BmpString()
                                    ]
                                })
                            ]
                        }),
                        new Constructed({
                            idBlock: {
                                tagClass: 3,
                                tagNumber: 1
                            },
                            value: [
                                new Choice({
                                    value: [
                                        new TeletexString(),
                                        new PrintableString(),
                                        new UniversalString(),
                                        new Utf8String(),
                                        new BmpString()
                                    ]
                                })
                            ]
                        })
                    ]
                }),
                new Primitive({
                    name: (names.blockName || EMPTY_STRING),
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 6
                    }
                }),
                new Primitive({
                    name: (names.blockName || EMPTY_STRING),
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 7
                    }
                }),
                new Primitive({
                    name: (names.blockName || EMPTY_STRING),
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 8
                    }
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, [
            "blockName",
            "otherName",
            "rfc822Name",
            "dNSName",
            "x400Address",
            "directoryName",
            "ediPartyName",
            "uniformResourceIdentifier",
            "iPAddress",
            "registeredID"
        ]);
        const asn1 = compareSchema(schema, schema, GeneralName.schema({
            names: {
                blockName: "blockName",
                otherName: "otherName",
                rfc822Name: "rfc822Name",
                dNSName: "dNSName",
                x400Address: "x400Address",
                directoryName: {
                    names: {
                        blockName: "directoryName"
                    }
                },
                ediPartyName: "ediPartyName",
                uniformResourceIdentifier: "uniformResourceIdentifier",
                iPAddress: "iPAddress",
                registeredID: "registeredID"
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.type = asn1.result.blockName.idBlock.tagNumber;
        switch (this.type) {
            case 0:
                this.value = asn1.result.blockName;
                break;
            case 1:
            case 2:
            case 6:
                {
                    const value = asn1.result.blockName;
                    value.idBlock.tagClass = 1;
                    value.idBlock.tagNumber = 22;
                    const valueBER = value.toBER(false);
                    const asnValue = fromBER(valueBER);
                    AsnError.assert(asnValue, "GeneralName value");
                    this.value = asnValue.result.valueBlock.value;
                }
                break;
            case 3:
                this.value = asn1.result.blockName;
                break;
            case 4:
                this.value = new RelativeDistinguishedNames({ schema: asn1.result.directoryName });
                break;
            case 5:
                this.value = asn1.result.ediPartyName;
                break;
            case 7:
                this.value = new OctetString({ valueHex: asn1.result.blockName.valueBlock.valueHex });
                break;
            case 8:
                {
                    const value = asn1.result.blockName;
                    value.idBlock.tagClass = 1;
                    value.idBlock.tagNumber = 6;
                    const valueBER = value.toBER(false);
                    const asnValue = fromBER(valueBER);
                    AsnError.assert(asnValue, "GeneralName registeredID");
                    this.value = asnValue.result.valueBlock.toString();
                }
                break;
        }
    }
    toSchema() {
        switch (this.type) {
            case 0:
            case 3:
            case 5:
                return new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: this.type
                    },
                    value: [
                        this.value
                    ]
                });
            case 1:
            case 2:
            case 6:
                {
                    const value = new IA5String({ value: this.value });
                    value.idBlock.tagClass = 3;
                    value.idBlock.tagNumber = this.type;
                    return value;
                }
            case 4:
                return new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 4
                    },
                    value: [this.value.toSchema()]
                });
            case 7:
                {
                    const value = this.value;
                    value.idBlock.tagClass = 3;
                    value.idBlock.tagNumber = this.type;
                    return value;
                }
            case 8:
                {
                    const value = new ObjectIdentifier({ value: this.value });
                    value.idBlock.tagClass = 3;
                    value.idBlock.tagNumber = this.type;
                    return value;
                }
            default:
                return GeneralName.schema();
        }
    }
    toJSON() {
        const _object = {
            type: this.type,
            value: EMPTY_STRING
        };
        if ((typeof this.value) === "string")
            _object.value = this.value;
        else {
            try {
                _object.value = this.value.toJSON();
            }
            catch (ex) {
            }
        }
        return _object;
    }
}
GeneralName.CLASS_NAME = "GeneralName";

const ACCESS_METHOD = "accessMethod";
const ACCESS_LOCATION = "accessLocation";
const CLEAR_PROPS$1v = [
    ACCESS_METHOD,
    ACCESS_LOCATION,
];
class AccessDescription extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.accessMethod = getParametersValue(parameters, ACCESS_METHOD, AccessDescription.defaultValues(ACCESS_METHOD));
        this.accessLocation = getParametersValue(parameters, ACCESS_LOCATION, AccessDescription.defaultValues(ACCESS_LOCATION));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case ACCESS_METHOD:
                return EMPTY_STRING;
            case ACCESS_LOCATION:
                return new GeneralName();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.accessMethod || EMPTY_STRING) }),
                GeneralName.schema(names.accessLocation || {})
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1v);
        const asn1 = compareSchema(schema, schema, AccessDescription.schema({
            names: {
                accessMethod: ACCESS_METHOD,
                accessLocation: {
                    names: {
                        blockName: ACCESS_LOCATION
                    }
                }
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.accessMethod = asn1.result.accessMethod.valueBlock.toString();
        this.accessLocation = new GeneralName({ schema: asn1.result.accessLocation });
    }
    toSchema() {
        return (new Sequence({
            value: [
                new ObjectIdentifier({ value: this.accessMethod }),
                this.accessLocation.toSchema()
            ]
        }));
    }
    toJSON() {
        return {
            accessMethod: this.accessMethod,
            accessLocation: this.accessLocation.toJSON()
        };
    }
}
AccessDescription.CLASS_NAME = "AccessDescription";

const SECONDS = "seconds";
const MILLIS = "millis";
const MICROS = "micros";
class Accuracy extends PkiObject {
    constructor(parameters = {}) {
        super();
        if (SECONDS in parameters) {
            this.seconds = getParametersValue(parameters, SECONDS, Accuracy.defaultValues(SECONDS));
        }
        if (MILLIS in parameters) {
            this.millis = getParametersValue(parameters, MILLIS, Accuracy.defaultValues(MILLIS));
        }
        if (MICROS in parameters) {
            this.micros = getParametersValue(parameters, MICROS, Accuracy.defaultValues(MICROS));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case SECONDS:
            case MILLIS:
            case MICROS:
                return 0;
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case SECONDS:
            case MILLIS:
            case MICROS:
                return (memberValue === Accuracy.defaultValues(memberName));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            optional: true,
            value: [
                new Integer({
                    optional: true,
                    name: (names.seconds || EMPTY_STRING)
                }),
                new Primitive({
                    name: (names.millis || EMPTY_STRING),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    }
                }),
                new Primitive({
                    name: (names.micros || EMPTY_STRING),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    }
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, [
            SECONDS,
            MILLIS,
            MICROS,
        ]);
        const asn1 = compareSchema(schema, schema, Accuracy.schema({
            names: {
                seconds: SECONDS,
                millis: MILLIS,
                micros: MICROS,
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if ("seconds" in asn1.result) {
            this.seconds = asn1.result.seconds.valueBlock.valueDec;
        }
        if ("millis" in asn1.result) {
            const intMillis = new Integer({ valueHex: asn1.result.millis.valueBlock.valueHex });
            this.millis = intMillis.valueBlock.valueDec;
        }
        if ("micros" in asn1.result) {
            const intMicros = new Integer({ valueHex: asn1.result.micros.valueBlock.valueHex });
            this.micros = intMicros.valueBlock.valueDec;
        }
    }
    toSchema() {
        const outputArray = [];
        if (this.seconds !== undefined)
            outputArray.push(new Integer({ value: this.seconds }));
        if (this.millis !== undefined) {
            const intMillis = new Integer({ value: this.millis });
            outputArray.push(new Primitive({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                valueHex: intMillis.valueBlock.valueHexView
            }));
        }
        if (this.micros !== undefined) {
            const intMicros = new Integer({ value: this.micros });
            outputArray.push(new Primitive({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                valueHex: intMicros.valueBlock.valueHexView
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const _object = {};
        if (this.seconds !== undefined)
            _object.seconds = this.seconds;
        if (this.millis !== undefined)
            _object.millis = this.millis;
        if (this.micros !== undefined)
            _object.micros = this.micros;
        return _object;
    }
}
Accuracy.CLASS_NAME = "Accuracy";

const ALGORITHM_ID = "algorithmId";
const ALGORITHM_PARAMS = "algorithmParams";
const ALGORITHM$2 = "algorithm";
const PARAMS = "params";
const CLEAR_PROPS$1u = [
    ALGORITHM$2,
    PARAMS
];
class AlgorithmIdentifier extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.algorithmId = getParametersValue(parameters, ALGORITHM_ID, AlgorithmIdentifier.defaultValues(ALGORITHM_ID));
        if (ALGORITHM_PARAMS in parameters) {
            this.algorithmParams = getParametersValue(parameters, ALGORITHM_PARAMS, AlgorithmIdentifier.defaultValues(ALGORITHM_PARAMS));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case ALGORITHM_ID:
                return EMPTY_STRING;
            case ALGORITHM_PARAMS:
                return new Any();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case ALGORITHM_ID:
                return (memberValue === EMPTY_STRING);
            case ALGORITHM_PARAMS:
                return (memberValue instanceof Any);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            optional: (names.optional || false),
            value: [
                new ObjectIdentifier({ name: (names.algorithmIdentifier || EMPTY_STRING) }),
                new Any({ name: (names.algorithmParams || EMPTY_STRING), optional: true })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1u);
        const asn1 = compareSchema(schema, schema, AlgorithmIdentifier.schema({
            names: {
                algorithmIdentifier: ALGORITHM$2,
                algorithmParams: PARAMS
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.algorithmId = asn1.result.algorithm.valueBlock.toString();
        if (PARAMS in asn1.result) {
            this.algorithmParams = asn1.result.params;
        }
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(new ObjectIdentifier({ value: this.algorithmId }));
        if (this.algorithmParams && !(this.algorithmParams instanceof Any)) {
            outputArray.push(this.algorithmParams);
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const object = {
            algorithmId: this.algorithmId
        };
        if (this.algorithmParams && !(this.algorithmParams instanceof Any)) {
            object.algorithmParams = this.algorithmParams.toJSON();
        }
        return object;
    }
    isEqual(algorithmIdentifier) {
        if (!(algorithmIdentifier instanceof AlgorithmIdentifier)) {
            return false;
        }
        if (this.algorithmId !== algorithmIdentifier.algorithmId) {
            return false;
        }
        if (this.algorithmParams) {
            if (algorithmIdentifier.algorithmParams) {
                return JSON.stringify(this.algorithmParams) === JSON.stringify(algorithmIdentifier.algorithmParams);
            }
            return false;
        }
        if (algorithmIdentifier.algorithmParams) {
            return false;
        }
        return true;
    }
}
AlgorithmIdentifier.CLASS_NAME = "AlgorithmIdentifier";

const ALT_NAMES = "altNames";
const CLEAR_PROPS$1t = [
    ALT_NAMES
];
class AltName extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.altNames = getParametersValue(parameters, ALT_NAMES, AltName.defaultValues(ALT_NAMES));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case ALT_NAMES:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Repeated({
                    name: (names.altNames || EMPTY_STRING),
                    value: GeneralName.schema()
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1t);
        const asn1 = compareSchema(schema, schema, AltName.schema({
            names: {
                altNames: ALT_NAMES
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (ALT_NAMES in asn1.result) {
            this.altNames = Array.from(asn1.result.altNames, element => new GeneralName({ schema: element }));
        }
    }
    toSchema() {
        return (new Sequence({
            value: Array.from(this.altNames, o => o.toSchema())
        }));
    }
    toJSON() {
        return {
            altNames: Array.from(this.altNames, o => o.toJSON())
        };
    }
}
AltName.CLASS_NAME = "AltName";

const TYPE$3 = "type";
const VALUES$1 = "values";
const CLEAR_PROPS$1s = [
    TYPE$3,
    VALUES$1
];
class Attribute extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.type = getParametersValue(parameters, TYPE$3, Attribute.defaultValues(TYPE$3));
        this.values = getParametersValue(parameters, VALUES$1, Attribute.defaultValues(VALUES$1));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case TYPE$3:
                return EMPTY_STRING;
            case VALUES$1:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case TYPE$3:
                return (memberValue === EMPTY_STRING);
            case VALUES$1:
                return (memberValue.length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.type || EMPTY_STRING) }),
                new Set({
                    name: (names.setName || EMPTY_STRING),
                    value: [
                        new Repeated({
                            name: (names.values || EMPTY_STRING),
                            value: new Any()
                        })
                    ]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1s);
        const asn1 = compareSchema(schema, schema, Attribute.schema({
            names: {
                type: TYPE$3,
                values: VALUES$1
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.type = asn1.result.type.valueBlock.toString();
        this.values = asn1.result.values;
    }
    toSchema() {
        return (new Sequence({
            value: [
                new ObjectIdentifier({ value: this.type }),
                new Set({
                    value: this.values
                })
            ]
        }));
    }
    toJSON() {
        return {
            type: this.type,
            values: Array.from(this.values, o => o.toJSON())
        };
    }
}
Attribute.CLASS_NAME = "Attribute";

const NOT_BEFORE_TIME = "notBeforeTime";
const NOT_AFTER_TIME = "notAfterTime";
const CLEAR_PROPS$1r = [
    NOT_BEFORE_TIME,
    NOT_AFTER_TIME,
];
class AttCertValidityPeriod extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.notBeforeTime = getParametersValue(parameters, NOT_BEFORE_TIME, AttCertValidityPeriod.defaultValues(NOT_BEFORE_TIME));
        this.notAfterTime = getParametersValue(parameters, NOT_AFTER_TIME, AttCertValidityPeriod.defaultValues(NOT_AFTER_TIME));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case NOT_BEFORE_TIME:
            case NOT_AFTER_TIME:
                return new Date(0, 0, 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new GeneralizedTime({ name: (names.notBeforeTime || EMPTY_STRING) }),
                new GeneralizedTime({ name: (names.notAfterTime || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1r);
        const asn1 = compareSchema(schema, schema, AttCertValidityPeriod.schema({
            names: {
                notBeforeTime: NOT_BEFORE_TIME,
                notAfterTime: NOT_AFTER_TIME
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.notBeforeTime = asn1.result.notBeforeTime.toDate();
        this.notAfterTime = asn1.result.notAfterTime.toDate();
    }
    toSchema() {
        return (new Sequence({
            value: [
                new GeneralizedTime({ valueDate: this.notBeforeTime }),
                new GeneralizedTime({ valueDate: this.notAfterTime }),
            ]
        }));
    }
    toJSON() {
        return {
            notBeforeTime: this.notBeforeTime,
            notAfterTime: this.notAfterTime
        };
    }
}
AttCertValidityPeriod.CLASS_NAME = "AttCertValidityPeriod";

const NAMES = "names";
const GENERAL_NAMES = "generalNames";
class GeneralNames extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.names = getParametersValue(parameters, NAMES, GeneralNames.defaultValues(NAMES));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case "names":
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}, optional = false) {
        const names = getParametersValue(parameters, NAMES, {});
        return (new Sequence({
            optional,
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Repeated({
                    name: (names.generalNames || EMPTY_STRING),
                    value: GeneralName.schema()
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, [
            NAMES,
            GENERAL_NAMES
        ]);
        const asn1 = compareSchema(schema, schema, GeneralNames.schema({
            names: {
                blockName: NAMES,
                generalNames: GENERAL_NAMES
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.names = Array.from(asn1.result.generalNames, element => new GeneralName({ schema: element }));
    }
    toSchema() {
        return (new Sequence({
            value: Array.from(this.names, o => o.toSchema())
        }));
    }
    toJSON() {
        return {
            names: Array.from(this.names, o => o.toJSON())
        };
    }
}
GeneralNames.CLASS_NAME = "GeneralNames";

const id_SubjectDirectoryAttributes = "2.5.29.9";
const id_SubjectKeyIdentifier = "2.5.29.14";
const id_KeyUsage = "2.5.29.15";
const id_PrivateKeyUsagePeriod = "2.5.29.16";
const id_SubjectAltName = "2.5.29.17";
const id_IssuerAltName = "2.5.29.18";
const id_BasicConstraints = "2.5.29.19";
const id_CRLNumber = "2.5.29.20";
const id_BaseCRLNumber = "2.5.29.27";
const id_CRLReason = "2.5.29.21";
const id_InvalidityDate = "2.5.29.24";
const id_IssuingDistributionPoint = "2.5.29.28";
const id_CertificateIssuer = "2.5.29.29";
const id_NameConstraints = "2.5.29.30";
const id_CRLDistributionPoints = "2.5.29.31";
const id_FreshestCRL = "2.5.29.46";
const id_CertificatePolicies = "2.5.29.32";
const id_AnyPolicy = "2.5.29.32.0";
const id_MicrosoftAppPolicies = "1.3.6.1.4.1.311.21.10";
const id_PolicyMappings = "2.5.29.33";
const id_AuthorityKeyIdentifier = "2.5.29.35";
const id_PolicyConstraints = "2.5.29.36";
const id_ExtKeyUsage = "2.5.29.37";
const id_InhibitAnyPolicy = "2.5.29.54";
const id_AuthorityInfoAccess = "1.3.6.1.5.5.7.1.1";
const id_SubjectInfoAccess = "1.3.6.1.5.5.7.1.11";
const id_SignedCertificateTimestampList = "1.3.6.1.4.1.11129.2.4.2";
const id_MicrosoftCertTemplateV1 = "1.3.6.1.4.1.311.20.2";
const id_MicrosoftPrevCaCertHash = "1.3.6.1.4.1.311.21.2";
const id_MicrosoftCertTemplateV2 = "1.3.6.1.4.1.311.21.7";
const id_MicrosoftCaVersion = "1.3.6.1.4.1.311.21.1";
const id_QCStatements = "1.3.6.1.5.5.7.1.3";
const id_ContentType_Data = "1.2.840.113549.1.7.1";
const id_ContentType_SignedData = "1.2.840.113549.1.7.2";
const id_ContentType_EnvelopedData = "1.2.840.113549.1.7.3";
const id_ContentType_EncryptedData = "1.2.840.113549.1.7.6";
const id_eContentType_TSTInfo = "1.2.840.113549.1.9.16.1.4";
const id_CertBag_X509Certificate = "1.2.840.113549.1.9.22.1";
const id_CertBag_SDSICertificate = "1.2.840.113549.1.9.22.2";
const id_CertBag_AttributeCertificate = "1.2.840.113549.1.9.22.3";
const id_CRLBag_X509CRL = "1.2.840.113549.1.9.23.1";
const id_pkix = "1.3.6.1.5.5.7";
const id_ad = `${id_pkix}.48`;
const id_PKIX_OCSP_Basic = `${id_ad}.1.1`;
const id_ad_caIssuers = `${id_ad}.2`;
const id_ad_ocsp = `${id_ad}.1`;
const id_sha1 = "1.3.14.3.2.26";
const id_sha256 = "2.16.840.1.101.3.4.2.1";
const id_sha384 = "2.16.840.1.101.3.4.2.2";
const id_sha512 = "2.16.840.1.101.3.4.2.3";

const KEY_IDENTIFIER$1 = "keyIdentifier";
const AUTHORITY_CERT_ISSUER = "authorityCertIssuer";
const AUTHORITY_CERT_SERIAL_NUMBER = "authorityCertSerialNumber";
const CLEAR_PROPS$1q = [
    KEY_IDENTIFIER$1,
    AUTHORITY_CERT_ISSUER,
    AUTHORITY_CERT_SERIAL_NUMBER,
];
class AuthorityKeyIdentifier extends PkiObject {
    constructor(parameters = {}) {
        super();
        if (KEY_IDENTIFIER$1 in parameters) {
            this.keyIdentifier = getParametersValue(parameters, KEY_IDENTIFIER$1, AuthorityKeyIdentifier.defaultValues(KEY_IDENTIFIER$1));
        }
        if (AUTHORITY_CERT_ISSUER in parameters) {
            this.authorityCertIssuer = getParametersValue(parameters, AUTHORITY_CERT_ISSUER, AuthorityKeyIdentifier.defaultValues(AUTHORITY_CERT_ISSUER));
        }
        if (AUTHORITY_CERT_SERIAL_NUMBER in parameters) {
            this.authorityCertSerialNumber = getParametersValue(parameters, AUTHORITY_CERT_SERIAL_NUMBER, AuthorityKeyIdentifier.defaultValues(AUTHORITY_CERT_SERIAL_NUMBER));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case KEY_IDENTIFIER$1:
                return new OctetString();
            case AUTHORITY_CERT_ISSUER:
                return [];
            case AUTHORITY_CERT_SERIAL_NUMBER:
                return new Integer();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Primitive({
                    name: (names.keyIdentifier || EMPTY_STRING),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    }
                }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: [
                        new Repeated({
                            name: (names.authorityCertIssuer || EMPTY_STRING),
                            value: GeneralName.schema()
                        })
                    ]
                }),
                new Primitive({
                    name: (names.authorityCertSerialNumber || EMPTY_STRING),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 2
                    }
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1q);
        const asn1 = compareSchema(schema, schema, AuthorityKeyIdentifier.schema({
            names: {
                keyIdentifier: KEY_IDENTIFIER$1,
                authorityCertIssuer: AUTHORITY_CERT_ISSUER,
                authorityCertSerialNumber: AUTHORITY_CERT_SERIAL_NUMBER
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (KEY_IDENTIFIER$1 in asn1.result)
            this.keyIdentifier = new OctetString({ valueHex: asn1.result.keyIdentifier.valueBlock.valueHex });
        if (AUTHORITY_CERT_ISSUER in asn1.result)
            this.authorityCertIssuer = Array.from(asn1.result.authorityCertIssuer, o => new GeneralName({ schema: o }));
        if (AUTHORITY_CERT_SERIAL_NUMBER in asn1.result)
            this.authorityCertSerialNumber = new Integer({ valueHex: asn1.result.authorityCertSerialNumber.valueBlock.valueHex });
    }
    toSchema() {
        const outputArray = [];
        if (this.keyIdentifier) {
            outputArray.push(new Primitive({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                valueHex: this.keyIdentifier.valueBlock.valueHexView
            }));
        }
        if (this.authorityCertIssuer) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                value: Array.from(this.authorityCertIssuer, o => o.toSchema())
            }));
        }
        if (this.authorityCertSerialNumber) {
            outputArray.push(new Primitive({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 2
                },
                valueHex: this.authorityCertSerialNumber.valueBlock.valueHexView
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const object = {};
        if (this.keyIdentifier) {
            object.keyIdentifier = this.keyIdentifier.toJSON();
        }
        if (this.authorityCertIssuer) {
            object.authorityCertIssuer = Array.from(this.authorityCertIssuer, o => o.toJSON());
        }
        if (this.authorityCertSerialNumber) {
            object.authorityCertSerialNumber = this.authorityCertSerialNumber.toJSON();
        }
        return object;
    }
}
AuthorityKeyIdentifier.CLASS_NAME = "AuthorityKeyIdentifier";

const PATH_LENGTH_CONSTRAINT = "pathLenConstraint";
const CA = "cA";
class BasicConstraints extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.cA = getParametersValue(parameters, CA, false);
        if (PATH_LENGTH_CONSTRAINT in parameters) {
            this.pathLenConstraint = getParametersValue(parameters, PATH_LENGTH_CONSTRAINT, 0);
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case CA:
                return false;
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Boolean({
                    optional: true,
                    name: (names.cA || EMPTY_STRING)
                }),
                new Integer({
                    optional: true,
                    name: (names.pathLenConstraint || EMPTY_STRING)
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, [
            CA,
            PATH_LENGTH_CONSTRAINT
        ]);
        const asn1 = compareSchema(schema, schema, BasicConstraints.schema({
            names: {
                cA: CA,
                pathLenConstraint: PATH_LENGTH_CONSTRAINT
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (CA in asn1.result) {
            this.cA = asn1.result.cA.valueBlock.value;
        }
        if (PATH_LENGTH_CONSTRAINT in asn1.result) {
            if (asn1.result.pathLenConstraint.valueBlock.isHexOnly) {
                this.pathLenConstraint = asn1.result.pathLenConstraint;
            }
            else {
                this.pathLenConstraint = asn1.result.pathLenConstraint.valueBlock.valueDec;
            }
        }
    }
    toSchema() {
        const outputArray = [];
        if (this.cA !== BasicConstraints.defaultValues(CA))
            outputArray.push(new Boolean({ value: this.cA }));
        if (PATH_LENGTH_CONSTRAINT in this) {
            if (this.pathLenConstraint instanceof Integer) {
                outputArray.push(this.pathLenConstraint);
            }
            else {
                outputArray.push(new Integer({ value: this.pathLenConstraint }));
            }
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const object = {};
        if (this.cA !== BasicConstraints.defaultValues(CA)) {
            object.cA = this.cA;
        }
        if (PATH_LENGTH_CONSTRAINT in this) {
            if (this.pathLenConstraint instanceof Integer) {
                object.pathLenConstraint = this.pathLenConstraint.toJSON();
            }
            else {
                object.pathLenConstraint = this.pathLenConstraint;
            }
        }
        return object;
    }
}
BasicConstraints.CLASS_NAME = "BasicConstraints";

const CERTIFICATE_INDEX = "certificateIndex";
const KEY_INDEX = "keyIndex";
class CAVersion extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.certificateIndex = getParametersValue(parameters, CERTIFICATE_INDEX, CAVersion.defaultValues(CERTIFICATE_INDEX));
        this.keyIndex = getParametersValue(parameters, KEY_INDEX, CAVersion.defaultValues(KEY_INDEX));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case CERTIFICATE_INDEX:
            case KEY_INDEX:
                return 0;
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema() {
        return (new Integer());
    }
    fromSchema(schema) {
        if (schema.constructor.blockName() !== Integer.blockName()) {
            throw new Error("Object's schema was not verified against input data for CAVersion");
        }
        let value = schema.valueBlock.valueHex.slice(0);
        const valueView = new Uint8Array(value);
        switch (true) {
            case (value.byteLength < 4):
                {
                    const tempValue = new ArrayBuffer(4);
                    const tempValueView = new Uint8Array(tempValue);
                    tempValueView.set(valueView, 4 - value.byteLength);
                    value = tempValue.slice(0);
                }
                break;
            case (value.byteLength > 4):
                {
                    const tempValue = new ArrayBuffer(4);
                    const tempValueView = new Uint8Array(tempValue);
                    tempValueView.set(valueView.slice(0, 4));
                    value = tempValue.slice(0);
                }
                break;
        }
        const keyIndexBuffer = value.slice(0, 2);
        const keyIndexView8 = new Uint8Array(keyIndexBuffer);
        let temp = keyIndexView8[0];
        keyIndexView8[0] = keyIndexView8[1];
        keyIndexView8[1] = temp;
        const keyIndexView16 = new Uint16Array(keyIndexBuffer);
        this.keyIndex = keyIndexView16[0];
        const certificateIndexBuffer = value.slice(2);
        const certificateIndexView8 = new Uint8Array(certificateIndexBuffer);
        temp = certificateIndexView8[0];
        certificateIndexView8[0] = certificateIndexView8[1];
        certificateIndexView8[1] = temp;
        const certificateIndexView16 = new Uint16Array(certificateIndexBuffer);
        this.certificateIndex = certificateIndexView16[0];
    }
    toSchema() {
        const certificateIndexBuffer = new ArrayBuffer(2);
        const certificateIndexView = new Uint16Array(certificateIndexBuffer);
        certificateIndexView[0] = this.certificateIndex;
        const certificateIndexView8 = new Uint8Array(certificateIndexBuffer);
        let temp = certificateIndexView8[0];
        certificateIndexView8[0] = certificateIndexView8[1];
        certificateIndexView8[1] = temp;
        const keyIndexBuffer = new ArrayBuffer(2);
        const keyIndexView = new Uint16Array(keyIndexBuffer);
        keyIndexView[0] = this.keyIndex;
        const keyIndexView8 = new Uint8Array(keyIndexBuffer);
        temp = keyIndexView8[0];
        keyIndexView8[0] = keyIndexView8[1];
        keyIndexView8[1] = temp;
        return (new Integer({
            valueHex: utilConcatBuf(keyIndexBuffer, certificateIndexBuffer)
        }));
    }
    toJSON() {
        return {
            certificateIndex: this.certificateIndex,
            keyIndex: this.keyIndex
        };
    }
}
CAVersion.CLASS_NAME = "CAVersion";

const POLICY_QUALIFIER_ID = "policyQualifierId";
const QUALIFIER = "qualifier";
const CLEAR_PROPS$1p = [
    POLICY_QUALIFIER_ID,
    QUALIFIER
];
class PolicyQualifierInfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.policyQualifierId = getParametersValue(parameters, POLICY_QUALIFIER_ID, PolicyQualifierInfo.defaultValues(POLICY_QUALIFIER_ID));
        this.qualifier = getParametersValue(parameters, QUALIFIER, PolicyQualifierInfo.defaultValues(QUALIFIER));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case POLICY_QUALIFIER_ID:
                return EMPTY_STRING;
            case QUALIFIER:
                return new Any();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.policyQualifierId || EMPTY_STRING) }),
                new Any({ name: (names.qualifier || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1p);
        const asn1 = compareSchema(schema, schema, PolicyQualifierInfo.schema({
            names: {
                policyQualifierId: POLICY_QUALIFIER_ID,
                qualifier: QUALIFIER
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.policyQualifierId = asn1.result.policyQualifierId.valueBlock.toString();
        this.qualifier = asn1.result.qualifier;
    }
    toSchema() {
        return (new Sequence({
            value: [
                new ObjectIdentifier({ value: this.policyQualifierId }),
                this.qualifier
            ]
        }));
    }
    toJSON() {
        return {
            policyQualifierId: this.policyQualifierId,
            qualifier: this.qualifier.toJSON()
        };
    }
}
PolicyQualifierInfo.CLASS_NAME = "PolicyQualifierInfo";

const POLICY_IDENTIFIER = "policyIdentifier";
const POLICY_QUALIFIERS = "policyQualifiers";
const CLEAR_PROPS$1o = [
    POLICY_IDENTIFIER,
    POLICY_QUALIFIERS
];
class PolicyInformation extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.policyIdentifier = getParametersValue(parameters, POLICY_IDENTIFIER, PolicyInformation.defaultValues(POLICY_IDENTIFIER));
        if (POLICY_QUALIFIERS in parameters) {
            this.policyQualifiers = getParametersValue(parameters, POLICY_QUALIFIERS, PolicyInformation.defaultValues(POLICY_QUALIFIERS));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case POLICY_IDENTIFIER:
                return EMPTY_STRING;
            case POLICY_QUALIFIERS:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.policyIdentifier || EMPTY_STRING) }),
                new Sequence({
                    optional: true,
                    value: [
                        new Repeated({
                            name: (names.policyQualifiers || EMPTY_STRING),
                            value: PolicyQualifierInfo.schema()
                        })
                    ]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1o);
        const asn1 = compareSchema(schema, schema, PolicyInformation.schema({
            names: {
                policyIdentifier: POLICY_IDENTIFIER,
                policyQualifiers: POLICY_QUALIFIERS
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.policyIdentifier = asn1.result.policyIdentifier.valueBlock.toString();
        if (POLICY_QUALIFIERS in asn1.result) {
            this.policyQualifiers = Array.from(asn1.result.policyQualifiers, element => new PolicyQualifierInfo({ schema: element }));
        }
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(new ObjectIdentifier({ value: this.policyIdentifier }));
        if (this.policyQualifiers) {
            outputArray.push(new Sequence({
                value: Array.from(this.policyQualifiers, o => o.toSchema())
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            policyIdentifier: this.policyIdentifier
        };
        if (this.policyQualifiers)
            res.policyQualifiers = Array.from(this.policyQualifiers, o => o.toJSON());
        return res;
    }
}
PolicyInformation.CLASS_NAME = "PolicyInformation";

const CERTIFICATE_POLICIES = "certificatePolicies";
const CLEAR_PROPS$1n = [
    CERTIFICATE_POLICIES,
];
class CertificatePolicies extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.certificatePolicies = getParametersValue(parameters, CERTIFICATE_POLICIES, CertificatePolicies.defaultValues(CERTIFICATE_POLICIES));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case CERTIFICATE_POLICIES:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Repeated({
                    name: (names.certificatePolicies || EMPTY_STRING),
                    value: PolicyInformation.schema()
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1n);
        const asn1 = compareSchema(schema, schema, CertificatePolicies.schema({
            names: {
                certificatePolicies: CERTIFICATE_POLICIES
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.certificatePolicies = Array.from(asn1.result.certificatePolicies, element => new PolicyInformation({ schema: element }));
    }
    toSchema() {
        return (new Sequence({
            value: Array.from(this.certificatePolicies, o => o.toSchema())
        }));
    }
    toJSON() {
        return {
            certificatePolicies: Array.from(this.certificatePolicies, o => o.toJSON())
        };
    }
}
CertificatePolicies.CLASS_NAME = "CertificatePolicies";

const TEMPLATE_ID = "templateID";
const TEMPLATE_MAJOR_VERSION = "templateMajorVersion";
const TEMPLATE_MINOR_VERSION = "templateMinorVersion";
const CLEAR_PROPS$1m = [
    TEMPLATE_ID,
    TEMPLATE_MAJOR_VERSION,
    TEMPLATE_MINOR_VERSION
];
class CertificateTemplate extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.templateID = getParametersValue(parameters, TEMPLATE_ID, CertificateTemplate.defaultValues(TEMPLATE_ID));
        if (TEMPLATE_MAJOR_VERSION in parameters) {
            this.templateMajorVersion = getParametersValue(parameters, TEMPLATE_MAJOR_VERSION, CertificateTemplate.defaultValues(TEMPLATE_MAJOR_VERSION));
        }
        if (TEMPLATE_MINOR_VERSION in parameters) {
            this.templateMinorVersion = getParametersValue(parameters, TEMPLATE_MINOR_VERSION, CertificateTemplate.defaultValues(TEMPLATE_MINOR_VERSION));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case TEMPLATE_ID:
                return EMPTY_STRING;
            case TEMPLATE_MAJOR_VERSION:
            case TEMPLATE_MINOR_VERSION:
                return 0;
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.templateID || EMPTY_STRING) }),
                new Integer({
                    name: (names.templateMajorVersion || EMPTY_STRING),
                    optional: true
                }),
                new Integer({
                    name: (names.templateMinorVersion || EMPTY_STRING),
                    optional: true
                }),
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1m);
        const asn1 = compareSchema(schema, schema, CertificateTemplate.schema({
            names: {
                templateID: TEMPLATE_ID,
                templateMajorVersion: TEMPLATE_MAJOR_VERSION,
                templateMinorVersion: TEMPLATE_MINOR_VERSION
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.templateID = asn1.result.templateID.valueBlock.toString();
        if (TEMPLATE_MAJOR_VERSION in asn1.result) {
            this.templateMajorVersion = asn1.result.templateMajorVersion.valueBlock.valueDec;
        }
        if (TEMPLATE_MINOR_VERSION in asn1.result) {
            this.templateMinorVersion = asn1.result.templateMinorVersion.valueBlock.valueDec;
        }
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(new ObjectIdentifier({ value: this.templateID }));
        if (TEMPLATE_MAJOR_VERSION in this) {
            outputArray.push(new Integer({ value: this.templateMajorVersion }));
        }
        if (TEMPLATE_MINOR_VERSION in this) {
            outputArray.push(new Integer({ value: this.templateMinorVersion }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            templateID: this.templateID
        };
        if (TEMPLATE_MAJOR_VERSION in this)
            res.templateMajorVersion = this.templateMajorVersion;
        if (TEMPLATE_MINOR_VERSION in this)
            res.templateMinorVersion = this.templateMinorVersion;
        return res;
    }
}

const DISTRIBUTION_POINT$1 = "distributionPoint";
const DISTRIBUTION_POINT_NAMES$1 = "distributionPointNames";
const REASONS = "reasons";
const CRL_ISSUER = "cRLIssuer";
const CRL_ISSUER_NAMES = "cRLIssuerNames";
const CLEAR_PROPS$1l = [
    DISTRIBUTION_POINT$1,
    DISTRIBUTION_POINT_NAMES$1,
    REASONS,
    CRL_ISSUER,
    CRL_ISSUER_NAMES,
];
class DistributionPoint extends PkiObject {
    constructor(parameters = {}) {
        super();
        if (DISTRIBUTION_POINT$1 in parameters) {
            this.distributionPoint = getParametersValue(parameters, DISTRIBUTION_POINT$1, DistributionPoint.defaultValues(DISTRIBUTION_POINT$1));
        }
        if (REASONS in parameters) {
            this.reasons = getParametersValue(parameters, REASONS, DistributionPoint.defaultValues(REASONS));
        }
        if (CRL_ISSUER in parameters) {
            this.cRLIssuer = getParametersValue(parameters, CRL_ISSUER, DistributionPoint.defaultValues(CRL_ISSUER));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case DISTRIBUTION_POINT$1:
                return [];
            case REASONS:
                return new BitString();
            case CRL_ISSUER:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [
                        new Choice({
                            value: [
                                new Constructed({
                                    name: (names.distributionPoint || EMPTY_STRING),
                                    optional: true,
                                    idBlock: {
                                        tagClass: 3,
                                        tagNumber: 0
                                    },
                                    value: [
                                        new Repeated({
                                            name: (names.distributionPointNames || EMPTY_STRING),
                                            value: GeneralName.schema()
                                        })
                                    ]
                                }),
                                new Constructed({
                                    name: (names.distributionPoint || EMPTY_STRING),
                                    optional: true,
                                    idBlock: {
                                        tagClass: 3,
                                        tagNumber: 1
                                    },
                                    value: RelativeDistinguishedNames.schema().valueBlock.value
                                })
                            ]
                        })
                    ]
                }),
                new Primitive({
                    name: (names.reasons || EMPTY_STRING),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    }
                }),
                new Constructed({
                    name: (names.cRLIssuer || EMPTY_STRING),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 2
                    },
                    value: [
                        new Repeated({
                            name: (names.cRLIssuerNames || EMPTY_STRING),
                            value: GeneralName.schema()
                        })
                    ]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1l);
        const asn1 = compareSchema(schema, schema, DistributionPoint.schema({
            names: {
                distributionPoint: DISTRIBUTION_POINT$1,
                distributionPointNames: DISTRIBUTION_POINT_NAMES$1,
                reasons: REASONS,
                cRLIssuer: CRL_ISSUER,
                cRLIssuerNames: CRL_ISSUER_NAMES
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (DISTRIBUTION_POINT$1 in asn1.result) {
            if (asn1.result.distributionPoint.idBlock.tagNumber === 0) {
                this.distributionPoint = Array.from(asn1.result.distributionPointNames, element => new GeneralName({ schema: element }));
            }
            if (asn1.result.distributionPoint.idBlock.tagNumber === 1) {
                this.distributionPoint = new RelativeDistinguishedNames({
                    schema: new Sequence({
                        value: asn1.result.distributionPoint.valueBlock.value
                    })
                });
            }
        }
        if (REASONS in asn1.result) {
            this.reasons = new BitString({ valueHex: asn1.result.reasons.valueBlock.valueHex });
        }
        if (CRL_ISSUER in asn1.result) {
            this.cRLIssuer = Array.from(asn1.result.cRLIssuerNames, element => new GeneralName({ schema: element }));
        }
    }
    toSchema() {
        const outputArray = [];
        if (this.distributionPoint) {
            let internalValue;
            if (this.distributionPoint instanceof Array) {
                internalValue = new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: Array.from(this.distributionPoint, o => o.toSchema())
                });
            }
            else {
                internalValue = new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: [this.distributionPoint.toSchema()]
                });
            }
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [internalValue]
            }));
        }
        if (this.reasons) {
            outputArray.push(new Primitive({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                valueHex: this.reasons.valueBlock.valueHexView
            }));
        }
        if (this.cRLIssuer) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 2
                },
                value: Array.from(this.cRLIssuer, o => o.toSchema())
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const object = {};
        if (this.distributionPoint) {
            if (this.distributionPoint instanceof Array) {
                object.distributionPoint = Array.from(this.distributionPoint, o => o.toJSON());
            }
            else {
                object.distributionPoint = this.distributionPoint.toJSON();
            }
        }
        if (this.reasons) {
            object.reasons = this.reasons.toJSON();
        }
        if (this.cRLIssuer) {
            object.cRLIssuer = Array.from(this.cRLIssuer, o => o.toJSON());
        }
        return object;
    }
}
DistributionPoint.CLASS_NAME = "DistributionPoint";

const DISTRIBUTION_POINTS = "distributionPoints";
const CLEAR_PROPS$1k = [
    DISTRIBUTION_POINTS
];
class CRLDistributionPoints extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.distributionPoints = getParametersValue(parameters, DISTRIBUTION_POINTS, CRLDistributionPoints.defaultValues(DISTRIBUTION_POINTS));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case DISTRIBUTION_POINTS:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Repeated({
                    name: (names.distributionPoints || EMPTY_STRING),
                    value: DistributionPoint.schema()
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1k);
        const asn1 = compareSchema(schema, schema, CRLDistributionPoints.schema({
            names: {
                distributionPoints: DISTRIBUTION_POINTS
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.distributionPoints = Array.from(asn1.result.distributionPoints, element => new DistributionPoint({ schema: element }));
    }
    toSchema() {
        return (new Sequence({
            value: Array.from(this.distributionPoints, o => o.toSchema())
        }));
    }
    toJSON() {
        return {
            distributionPoints: Array.from(this.distributionPoints, o => o.toJSON())
        };
    }
}
CRLDistributionPoints.CLASS_NAME = "CRLDistributionPoints";

const KEY_PURPOSES = "keyPurposes";
const CLEAR_PROPS$1j = [
    KEY_PURPOSES,
];
class ExtKeyUsage extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.keyPurposes = getParametersValue(parameters, KEY_PURPOSES, ExtKeyUsage.defaultValues(KEY_PURPOSES));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case KEY_PURPOSES:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Repeated({
                    name: (names.keyPurposes || EMPTY_STRING),
                    value: new ObjectIdentifier()
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1j);
        const asn1 = compareSchema(schema, schema, ExtKeyUsage.schema({
            names: {
                keyPurposes: KEY_PURPOSES
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.keyPurposes = Array.from(asn1.result.keyPurposes, (element) => element.valueBlock.toString());
    }
    toSchema() {
        return (new Sequence({
            value: Array.from(this.keyPurposes, element => new ObjectIdentifier({ value: element }))
        }));
    }
    toJSON() {
        return {
            keyPurposes: Array.from(this.keyPurposes)
        };
    }
}
ExtKeyUsage.CLASS_NAME = "ExtKeyUsage";

const ACCESS_DESCRIPTIONS = "accessDescriptions";
class InfoAccess extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.accessDescriptions = getParametersValue(parameters, ACCESS_DESCRIPTIONS, InfoAccess.defaultValues(ACCESS_DESCRIPTIONS));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case ACCESS_DESCRIPTIONS:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Repeated({
                    name: (names.accessDescriptions || EMPTY_STRING),
                    value: AccessDescription.schema()
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, [
            ACCESS_DESCRIPTIONS
        ]);
        const asn1 = compareSchema(schema, schema, InfoAccess.schema({
            names: {
                accessDescriptions: ACCESS_DESCRIPTIONS
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.accessDescriptions = Array.from(asn1.result.accessDescriptions, element => new AccessDescription({ schema: element }));
    }
    toSchema() {
        return (new Sequence({
            value: Array.from(this.accessDescriptions, o => o.toSchema())
        }));
    }
    toJSON() {
        return {
            accessDescriptions: Array.from(this.accessDescriptions, o => o.toJSON())
        };
    }
}
InfoAccess.CLASS_NAME = "InfoAccess";

const DISTRIBUTION_POINT = "distributionPoint";
const DISTRIBUTION_POINT_NAMES = "distributionPointNames";
const ONLY_CONTAINS_USER_CERTS = "onlyContainsUserCerts";
const ONLY_CONTAINS_CA_CERTS = "onlyContainsCACerts";
const ONLY_SOME_REASON = "onlySomeReasons";
const INDIRECT_CRL = "indirectCRL";
const ONLY_CONTAINS_ATTRIBUTE_CERTS = "onlyContainsAttributeCerts";
const CLEAR_PROPS$1i = [
    DISTRIBUTION_POINT,
    DISTRIBUTION_POINT_NAMES,
    ONLY_CONTAINS_USER_CERTS,
    ONLY_CONTAINS_CA_CERTS,
    ONLY_SOME_REASON,
    INDIRECT_CRL,
    ONLY_CONTAINS_ATTRIBUTE_CERTS,
];
class IssuingDistributionPoint extends PkiObject {
    constructor(parameters = {}) {
        super();
        if (DISTRIBUTION_POINT in parameters) {
            this.distributionPoint = getParametersValue(parameters, DISTRIBUTION_POINT, IssuingDistributionPoint.defaultValues(DISTRIBUTION_POINT));
        }
        this.onlyContainsUserCerts = getParametersValue(parameters, ONLY_CONTAINS_USER_CERTS, IssuingDistributionPoint.defaultValues(ONLY_CONTAINS_USER_CERTS));
        this.onlyContainsCACerts = getParametersValue(parameters, ONLY_CONTAINS_CA_CERTS, IssuingDistributionPoint.defaultValues(ONLY_CONTAINS_CA_CERTS));
        if (ONLY_SOME_REASON in parameters) {
            this.onlySomeReasons = getParametersValue(parameters, ONLY_SOME_REASON, IssuingDistributionPoint.defaultValues(ONLY_SOME_REASON));
        }
        this.indirectCRL = getParametersValue(parameters, INDIRECT_CRL, IssuingDistributionPoint.defaultValues(INDIRECT_CRL));
        this.onlyContainsAttributeCerts = getParametersValue(parameters, ONLY_CONTAINS_ATTRIBUTE_CERTS, IssuingDistributionPoint.defaultValues(ONLY_CONTAINS_ATTRIBUTE_CERTS));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case DISTRIBUTION_POINT:
                return [];
            case ONLY_CONTAINS_USER_CERTS:
                return false;
            case ONLY_CONTAINS_CA_CERTS:
                return false;
            case ONLY_SOME_REASON:
                return 0;
            case INDIRECT_CRL:
                return false;
            case ONLY_CONTAINS_ATTRIBUTE_CERTS:
                return false;
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [
                        new Choice({
                            value: [
                                new Constructed({
                                    name: (names.distributionPoint || EMPTY_STRING),
                                    idBlock: {
                                        tagClass: 3,
                                        tagNumber: 0
                                    },
                                    value: [
                                        new Repeated({
                                            name: (names.distributionPointNames || EMPTY_STRING),
                                            value: GeneralName.schema()
                                        })
                                    ]
                                }),
                                new Constructed({
                                    name: (names.distributionPoint || EMPTY_STRING),
                                    idBlock: {
                                        tagClass: 3,
                                        tagNumber: 1
                                    },
                                    value: RelativeDistinguishedNames.schema().valueBlock.value
                                })
                            ]
                        })
                    ]
                }),
                new Primitive({
                    name: (names.onlyContainsUserCerts || EMPTY_STRING),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    }
                }),
                new Primitive({
                    name: (names.onlyContainsCACerts || EMPTY_STRING),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 2
                    }
                }),
                new Primitive({
                    name: (names.onlySomeReasons || EMPTY_STRING),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 3
                    }
                }),
                new Primitive({
                    name: (names.indirectCRL || EMPTY_STRING),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 4
                    }
                }),
                new Primitive({
                    name: (names.onlyContainsAttributeCerts || EMPTY_STRING),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 5
                    }
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1i);
        const asn1 = compareSchema(schema, schema, IssuingDistributionPoint.schema({
            names: {
                distributionPoint: DISTRIBUTION_POINT,
                distributionPointNames: DISTRIBUTION_POINT_NAMES,
                onlyContainsUserCerts: ONLY_CONTAINS_USER_CERTS,
                onlyContainsCACerts: ONLY_CONTAINS_CA_CERTS,
                onlySomeReasons: ONLY_SOME_REASON,
                indirectCRL: INDIRECT_CRL,
                onlyContainsAttributeCerts: ONLY_CONTAINS_ATTRIBUTE_CERTS
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (DISTRIBUTION_POINT in asn1.result) {
            switch (true) {
                case (asn1.result.distributionPoint.idBlock.tagNumber === 0):
                    this.distributionPoint = Array.from(asn1.result.distributionPointNames, element => new GeneralName({ schema: element }));
                    break;
                case (asn1.result.distributionPoint.idBlock.tagNumber === 1):
                    {
                        this.distributionPoint = new RelativeDistinguishedNames({
                            schema: new Sequence({
                                value: asn1.result.distributionPoint.valueBlock.value
                            })
                        });
                    }
                    break;
                default:
                    throw new Error("Unknown tagNumber for distributionPoint: {$asn1.result.distributionPoint.idBlock.tagNumber}");
            }
        }
        if (ONLY_CONTAINS_USER_CERTS in asn1.result) {
            const view = new Uint8Array(asn1.result.onlyContainsUserCerts.valueBlock.valueHex);
            this.onlyContainsUserCerts = (view[0] !== 0x00);
        }
        if (ONLY_CONTAINS_CA_CERTS in asn1.result) {
            const view = new Uint8Array(asn1.result.onlyContainsCACerts.valueBlock.valueHex);
            this.onlyContainsCACerts = (view[0] !== 0x00);
        }
        if (ONLY_SOME_REASON in asn1.result) {
            const view = new Uint8Array(asn1.result.onlySomeReasons.valueBlock.valueHex);
            this.onlySomeReasons = view[0];
        }
        if (INDIRECT_CRL in asn1.result) {
            const view = new Uint8Array(asn1.result.indirectCRL.valueBlock.valueHex);
            this.indirectCRL = (view[0] !== 0x00);
        }
        if (ONLY_CONTAINS_ATTRIBUTE_CERTS in asn1.result) {
            const view = new Uint8Array(asn1.result.onlyContainsAttributeCerts.valueBlock.valueHex);
            this.onlyContainsAttributeCerts = (view[0] !== 0x00);
        }
    }
    toSchema() {
        const outputArray = [];
        if (this.distributionPoint) {
            let value;
            if (this.distributionPoint instanceof Array) {
                value = new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: Array.from(this.distributionPoint, o => o.toSchema())
                });
            }
            else {
                value = this.distributionPoint.toSchema();
                value.idBlock.tagClass = 3;
                value.idBlock.tagNumber = 1;
            }
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [value]
            }));
        }
        if (this.onlyContainsUserCerts !== IssuingDistributionPoint.defaultValues(ONLY_CONTAINS_USER_CERTS)) {
            outputArray.push(new Primitive({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                valueHex: (new Uint8Array([0xFF])).buffer
            }));
        }
        if (this.onlyContainsCACerts !== IssuingDistributionPoint.defaultValues(ONLY_CONTAINS_CA_CERTS)) {
            outputArray.push(new Primitive({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 2
                },
                valueHex: (new Uint8Array([0xFF])).buffer
            }));
        }
        if (this.onlySomeReasons !== undefined) {
            const buffer = new ArrayBuffer(1);
            const view = new Uint8Array(buffer);
            view[0] = this.onlySomeReasons;
            outputArray.push(new Primitive({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 3
                },
                valueHex: buffer
            }));
        }
        if (this.indirectCRL !== IssuingDistributionPoint.defaultValues(INDIRECT_CRL)) {
            outputArray.push(new Primitive({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 4
                },
                valueHex: (new Uint8Array([0xFF])).buffer
            }));
        }
        if (this.onlyContainsAttributeCerts !== IssuingDistributionPoint.defaultValues(ONLY_CONTAINS_ATTRIBUTE_CERTS)) {
            outputArray.push(new Primitive({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 5
                },
                valueHex: (new Uint8Array([0xFF])).buffer
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const obj = {};
        if (this.distributionPoint) {
            if (this.distributionPoint instanceof Array) {
                obj.distributionPoint = Array.from(this.distributionPoint, o => o.toJSON());
            }
            else {
                obj.distributionPoint = this.distributionPoint.toJSON();
            }
        }
        if (this.onlyContainsUserCerts !== IssuingDistributionPoint.defaultValues(ONLY_CONTAINS_USER_CERTS)) {
            obj.onlyContainsUserCerts = this.onlyContainsUserCerts;
        }
        if (this.onlyContainsCACerts !== IssuingDistributionPoint.defaultValues(ONLY_CONTAINS_CA_CERTS)) {
            obj.onlyContainsCACerts = this.onlyContainsCACerts;
        }
        if (ONLY_SOME_REASON in this) {
            obj.onlySomeReasons = this.onlySomeReasons;
        }
        if (this.indirectCRL !== IssuingDistributionPoint.defaultValues(INDIRECT_CRL)) {
            obj.indirectCRL = this.indirectCRL;
        }
        if (this.onlyContainsAttributeCerts !== IssuingDistributionPoint.defaultValues(ONLY_CONTAINS_ATTRIBUTE_CERTS)) {
            obj.onlyContainsAttributeCerts = this.onlyContainsAttributeCerts;
        }
        return obj;
    }
}
IssuingDistributionPoint.CLASS_NAME = "IssuingDistributionPoint";

const BASE = "base";
const MINIMUM = "minimum";
const MAXIMUM = "maximum";
const CLEAR_PROPS$1h = [
    BASE,
    MINIMUM,
    MAXIMUM
];
class GeneralSubtree extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.base = getParametersValue(parameters, BASE, GeneralSubtree.defaultValues(BASE));
        this.minimum = getParametersValue(parameters, MINIMUM, GeneralSubtree.defaultValues(MINIMUM));
        if (MAXIMUM in parameters) {
            this.maximum = getParametersValue(parameters, MAXIMUM, GeneralSubtree.defaultValues(MAXIMUM));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case BASE:
                return new GeneralName();
            case MINIMUM:
                return 0;
            case MAXIMUM:
                return 0;
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                GeneralName.schema(names.base || {}),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [new Integer({ name: (names.minimum || EMPTY_STRING) })]
                }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: [new Integer({ name: (names.maximum || EMPTY_STRING) })]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1h);
        const asn1 = compareSchema(schema, schema, GeneralSubtree.schema({
            names: {
                base: {
                    names: {
                        blockName: BASE
                    }
                },
                minimum: MINIMUM,
                maximum: MAXIMUM
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.base = new GeneralName({ schema: asn1.result.base });
        if (MINIMUM in asn1.result) {
            if (asn1.result.minimum.valueBlock.isHexOnly)
                this.minimum = asn1.result.minimum;
            else
                this.minimum = asn1.result.minimum.valueBlock.valueDec;
        }
        if (MAXIMUM in asn1.result) {
            if (asn1.result.maximum.valueBlock.isHexOnly)
                this.maximum = asn1.result.maximum;
            else
                this.maximum = asn1.result.maximum.valueBlock.valueDec;
        }
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(this.base.toSchema());
        if (this.minimum !== 0) {
            let valueMinimum = 0;
            if (this.minimum instanceof Integer) {
                valueMinimum = this.minimum;
            }
            else {
                valueMinimum = new Integer({ value: this.minimum });
            }
            outputArray.push(new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [valueMinimum]
            }));
        }
        if (MAXIMUM in this) {
            let valueMaximum = 0;
            if (this.maximum instanceof Integer) {
                valueMaximum = this.maximum;
            }
            else {
                valueMaximum = new Integer({ value: this.maximum });
            }
            outputArray.push(new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                value: [valueMaximum]
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            base: this.base.toJSON()
        };
        if (this.minimum !== 0) {
            if (typeof this.minimum === "number") {
                res.minimum = this.minimum;
            }
            else {
                res.minimum = this.minimum.toJSON();
            }
        }
        if (this.maximum !== undefined) {
            if (typeof this.maximum === "number") {
                res.maximum = this.maximum;
            }
            else {
                res.maximum = this.maximum.toJSON();
            }
        }
        return res;
    }
}
GeneralSubtree.CLASS_NAME = "GeneralSubtree";

const PERMITTED_SUBTREES = "permittedSubtrees";
const EXCLUDED_SUBTREES = "excludedSubtrees";
const CLEAR_PROPS$1g = [
    PERMITTED_SUBTREES,
    EXCLUDED_SUBTREES
];
class NameConstraints extends PkiObject {
    constructor(parameters = {}) {
        super();
        if (PERMITTED_SUBTREES in parameters) {
            this.permittedSubtrees = getParametersValue(parameters, PERMITTED_SUBTREES, NameConstraints.defaultValues(PERMITTED_SUBTREES));
        }
        if (EXCLUDED_SUBTREES in parameters) {
            this.excludedSubtrees = getParametersValue(parameters, EXCLUDED_SUBTREES, NameConstraints.defaultValues(EXCLUDED_SUBTREES));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case PERMITTED_SUBTREES:
            case EXCLUDED_SUBTREES:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [
                        new Repeated({
                            name: (names.permittedSubtrees || EMPTY_STRING),
                            value: GeneralSubtree.schema()
                        })
                    ]
                }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: [
                        new Repeated({
                            name: (names.excludedSubtrees || EMPTY_STRING),
                            value: GeneralSubtree.schema()
                        })
                    ]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1g);
        const asn1 = compareSchema(schema, schema, NameConstraints.schema({
            names: {
                permittedSubtrees: PERMITTED_SUBTREES,
                excludedSubtrees: EXCLUDED_SUBTREES
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (PERMITTED_SUBTREES in asn1.result)
            this.permittedSubtrees = Array.from(asn1.result.permittedSubtrees, element => new GeneralSubtree({ schema: element }));
        if (EXCLUDED_SUBTREES in asn1.result)
            this.excludedSubtrees = Array.from(asn1.result.excludedSubtrees, element => new GeneralSubtree({ schema: element }));
    }
    toSchema() {
        const outputArray = [];
        if (this.permittedSubtrees) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: Array.from(this.permittedSubtrees, o => o.toSchema())
            }));
        }
        if (this.excludedSubtrees) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                value: Array.from(this.excludedSubtrees, o => o.toSchema())
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const object = {};
        if (this.permittedSubtrees) {
            object.permittedSubtrees = Array.from(this.permittedSubtrees, o => o.toJSON());
        }
        if (this.excludedSubtrees) {
            object.excludedSubtrees = Array.from(this.excludedSubtrees, o => o.toJSON());
        }
        return object;
    }
}
NameConstraints.CLASS_NAME = "NameConstraints";

const REQUIRE_EXPLICIT_POLICY = "requireExplicitPolicy";
const INHIBIT_POLICY_MAPPING = "inhibitPolicyMapping";
const CLEAR_PROPS$1f = [
    REQUIRE_EXPLICIT_POLICY,
    INHIBIT_POLICY_MAPPING,
];
class PolicyConstraints extends PkiObject {
    constructor(parameters = {}) {
        super();
        if (REQUIRE_EXPLICIT_POLICY in parameters) {
            this.requireExplicitPolicy = getParametersValue(parameters, REQUIRE_EXPLICIT_POLICY, PolicyConstraints.defaultValues(REQUIRE_EXPLICIT_POLICY));
        }
        if (INHIBIT_POLICY_MAPPING in parameters) {
            this.inhibitPolicyMapping = getParametersValue(parameters, INHIBIT_POLICY_MAPPING, PolicyConstraints.defaultValues(INHIBIT_POLICY_MAPPING));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case REQUIRE_EXPLICIT_POLICY:
                return 0;
            case INHIBIT_POLICY_MAPPING:
                return 0;
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Primitive({
                    name: (names.requireExplicitPolicy || EMPTY_STRING),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    }
                }),
                new Primitive({
                    name: (names.inhibitPolicyMapping || EMPTY_STRING),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    }
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1f);
        const asn1 = compareSchema(schema, schema, PolicyConstraints.schema({
            names: {
                requireExplicitPolicy: REQUIRE_EXPLICIT_POLICY,
                inhibitPolicyMapping: INHIBIT_POLICY_MAPPING
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (REQUIRE_EXPLICIT_POLICY in asn1.result) {
            const field1 = asn1.result.requireExplicitPolicy;
            field1.idBlock.tagClass = 1;
            field1.idBlock.tagNumber = 2;
            const ber1 = field1.toBER(false);
            const int1 = fromBER(ber1);
            AsnError.assert(int1, "Integer");
            this.requireExplicitPolicy = int1.result.valueBlock.valueDec;
        }
        if (INHIBIT_POLICY_MAPPING in asn1.result) {
            const field2 = asn1.result.inhibitPolicyMapping;
            field2.idBlock.tagClass = 1;
            field2.idBlock.tagNumber = 2;
            const ber2 = field2.toBER(false);
            const int2 = fromBER(ber2);
            AsnError.assert(int2, "Integer");
            this.inhibitPolicyMapping = int2.result.valueBlock.valueDec;
        }
    }
    toSchema() {
        const outputArray = [];
        if (REQUIRE_EXPLICIT_POLICY in this) {
            const int1 = new Integer({ value: this.requireExplicitPolicy });
            int1.idBlock.tagClass = 3;
            int1.idBlock.tagNumber = 0;
            outputArray.push(int1);
        }
        if (INHIBIT_POLICY_MAPPING in this) {
            const int2 = new Integer({ value: this.inhibitPolicyMapping });
            int2.idBlock.tagClass = 3;
            int2.idBlock.tagNumber = 1;
            outputArray.push(int2);
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {};
        if (REQUIRE_EXPLICIT_POLICY in this) {
            res.requireExplicitPolicy = this.requireExplicitPolicy;
        }
        if (INHIBIT_POLICY_MAPPING in this) {
            res.inhibitPolicyMapping = this.inhibitPolicyMapping;
        }
        return res;
    }
}
PolicyConstraints.CLASS_NAME = "PolicyConstraints";

const ISSUER_DOMAIN_POLICY = "issuerDomainPolicy";
const SUBJECT_DOMAIN_POLICY = "subjectDomainPolicy";
const CLEAR_PROPS$1e = [
    ISSUER_DOMAIN_POLICY,
    SUBJECT_DOMAIN_POLICY
];
class PolicyMapping extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.issuerDomainPolicy = getParametersValue(parameters, ISSUER_DOMAIN_POLICY, PolicyMapping.defaultValues(ISSUER_DOMAIN_POLICY));
        this.subjectDomainPolicy = getParametersValue(parameters, SUBJECT_DOMAIN_POLICY, PolicyMapping.defaultValues(SUBJECT_DOMAIN_POLICY));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case ISSUER_DOMAIN_POLICY:
                return EMPTY_STRING;
            case SUBJECT_DOMAIN_POLICY:
                return EMPTY_STRING;
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.issuerDomainPolicy || EMPTY_STRING) }),
                new ObjectIdentifier({ name: (names.subjectDomainPolicy || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1e);
        const asn1 = compareSchema(schema, schema, PolicyMapping.schema({
            names: {
                issuerDomainPolicy: ISSUER_DOMAIN_POLICY,
                subjectDomainPolicy: SUBJECT_DOMAIN_POLICY
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.issuerDomainPolicy = asn1.result.issuerDomainPolicy.valueBlock.toString();
        this.subjectDomainPolicy = asn1.result.subjectDomainPolicy.valueBlock.toString();
    }
    toSchema() {
        return (new Sequence({
            value: [
                new ObjectIdentifier({ value: this.issuerDomainPolicy }),
                new ObjectIdentifier({ value: this.subjectDomainPolicy })
            ]
        }));
    }
    toJSON() {
        return {
            issuerDomainPolicy: this.issuerDomainPolicy,
            subjectDomainPolicy: this.subjectDomainPolicy
        };
    }
}
PolicyMapping.CLASS_NAME = "PolicyMapping";

const MAPPINGS = "mappings";
const CLEAR_PROPS$1d = [
    MAPPINGS,
];
class PolicyMappings extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.mappings = getParametersValue(parameters, MAPPINGS, PolicyMappings.defaultValues(MAPPINGS));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case MAPPINGS:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Repeated({
                    name: (names.mappings || EMPTY_STRING),
                    value: PolicyMapping.schema()
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1d);
        const asn1 = compareSchema(schema, schema, PolicyMappings.schema({
            names: {
                mappings: MAPPINGS
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.mappings = Array.from(asn1.result.mappings, element => new PolicyMapping({ schema: element }));
    }
    toSchema() {
        return (new Sequence({
            value: Array.from(this.mappings, o => o.toSchema())
        }));
    }
    toJSON() {
        return {
            mappings: Array.from(this.mappings, o => o.toJSON())
        };
    }
}
PolicyMappings.CLASS_NAME = "PolicyMappings";

const NOT_BEFORE$1 = "notBefore";
const NOT_AFTER$1 = "notAfter";
const CLEAR_PROPS$1c = [
    NOT_BEFORE$1,
    NOT_AFTER$1
];
class PrivateKeyUsagePeriod extends PkiObject {
    constructor(parameters = {}) {
        super();
        if (NOT_BEFORE$1 in parameters) {
            this.notBefore = getParametersValue(parameters, NOT_BEFORE$1, PrivateKeyUsagePeriod.defaultValues(NOT_BEFORE$1));
        }
        if (NOT_AFTER$1 in parameters) {
            this.notAfter = getParametersValue(parameters, NOT_AFTER$1, PrivateKeyUsagePeriod.defaultValues(NOT_AFTER$1));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case NOT_BEFORE$1:
                return new Date();
            case NOT_AFTER$1:
                return new Date();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Primitive({
                    name: (names.notBefore || EMPTY_STRING),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    }
                }),
                new Primitive({
                    name: (names.notAfter || EMPTY_STRING),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    }
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1c);
        const asn1 = compareSchema(schema, schema, PrivateKeyUsagePeriod.schema({
            names: {
                notBefore: NOT_BEFORE$1,
                notAfter: NOT_AFTER$1
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (NOT_BEFORE$1 in asn1.result) {
            const localNotBefore = new GeneralizedTime();
            localNotBefore.fromBuffer(asn1.result.notBefore.valueBlock.valueHex);
            this.notBefore = localNotBefore.toDate();
        }
        if (NOT_AFTER$1 in asn1.result) {
            const localNotAfter = new GeneralizedTime({ valueHex: asn1.result.notAfter.valueBlock.valueHex });
            localNotAfter.fromBuffer(asn1.result.notAfter.valueBlock.valueHex);
            this.notAfter = localNotAfter.toDate();
        }
    }
    toSchema() {
        const outputArray = [];
        if (NOT_BEFORE$1 in this) {
            outputArray.push(new Primitive({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                valueHex: (new GeneralizedTime({ valueDate: this.notBefore })).valueBlock.valueHexView
            }));
        }
        if (NOT_AFTER$1 in this) {
            outputArray.push(new Primitive({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                valueHex: (new GeneralizedTime({ valueDate: this.notAfter })).valueBlock.valueHexView
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {};
        if (this.notBefore) {
            res.notBefore = this.notBefore;
        }
        if (this.notAfter) {
            res.notAfter = this.notAfter;
        }
        return res;
    }
}
PrivateKeyUsagePeriod.CLASS_NAME = "PrivateKeyUsagePeriod";

const ID = "id";
const TYPE$2 = "type";
const VALUES = "values";
const QC_STATEMENT_CLEAR_PROPS = [
    ID,
    TYPE$2
];
const QC_STATEMENTS_CLEAR_PROPS = [
    VALUES
];
class QCStatement extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.id = getParametersValue(parameters, ID, QCStatement.defaultValues(ID));
        if (TYPE$2 in parameters) {
            this.type = getParametersValue(parameters, TYPE$2, QCStatement.defaultValues(TYPE$2));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case ID:
                return EMPTY_STRING;
            case TYPE$2:
                return new Null();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case ID:
                return (memberValue === EMPTY_STRING);
            case TYPE$2:
                return (memberValue instanceof Null);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.id || EMPTY_STRING) }),
                new Any({
                    name: (names.type || EMPTY_STRING),
                    optional: true
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, QC_STATEMENT_CLEAR_PROPS);
        const asn1 = compareSchema(schema, schema, QCStatement.schema({
            names: {
                id: ID,
                type: TYPE$2
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.id = asn1.result.id.valueBlock.toString();
        if (TYPE$2 in asn1.result)
            this.type = asn1.result.type;
    }
    toSchema() {
        const value = [
            new ObjectIdentifier({ value: this.id })
        ];
        if (TYPE$2 in this)
            value.push(this.type);
        return (new Sequence({
            value,
        }));
    }
    toJSON() {
        const object = {
            id: this.id
        };
        if (this.type) {
            object.type = this.type.toJSON();
        }
        return object;
    }
}
QCStatement.CLASS_NAME = "QCStatement";
class QCStatements extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.values = getParametersValue(parameters, VALUES, QCStatements.defaultValues(VALUES));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VALUES:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case VALUES:
                return (memberValue.length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Repeated({
                    name: (names.values || EMPTY_STRING),
                    value: QCStatement.schema(names.value || {})
                }),
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, QC_STATEMENTS_CLEAR_PROPS);
        const asn1 = compareSchema(schema, schema, QCStatements.schema({
            names: {
                values: VALUES
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.values = Array.from(asn1.result.values, element => new QCStatement({ schema: element }));
    }
    toSchema() {
        return (new Sequence({
            value: Array.from(this.values, o => o.toSchema())
        }));
    }
    toJSON() {
        return {
            values: Array.from(this.values, o => o.toJSON())
        };
    }
}
QCStatements.CLASS_NAME = "QCStatements";

class ByteStream {
    constructor(parameters = {}) {
        if ("view" in parameters) {
            this.fromUint8Array(parameters.view);
        }
        else if ("buffer" in parameters) {
            this.fromArrayBuffer(parameters.buffer);
        }
        else if ("string" in parameters) {
            this.fromString(parameters.string);
        }
        else if ("hexstring" in parameters) {
            this.fromHexString(parameters.hexstring);
        }
        else {
            if ("length" in parameters && parameters.length > 0) {
                this.length = parameters.length;
                if (parameters.stub) {
                    for (let i = 0; i < this._view.length; i++) {
                        this._view[i] = parameters.stub;
                    }
                }
            }
            else {
                this.length = 0;
            }
        }
    }
    set buffer(value) {
        this._buffer = value;
        this._view = new Uint8Array(this._buffer);
    }
    get buffer() {
        return this._buffer;
    }
    set view(value) {
        this._buffer = new ArrayBuffer(value.length);
        this._view = new Uint8Array(this._buffer);
        this._view.set(value);
    }
    get view() {
        return this._view;
    }
    get length() {
        return this.view.byteLength;
    }
    set length(value) {
        this._buffer = new ArrayBuffer(value);
        this._view = new Uint8Array(this._buffer);
    }
    clear() {
        this._buffer = new ArrayBuffer(0);
        this._view = new Uint8Array(this._buffer);
    }
    fromArrayBuffer(array) {
        this._buffer = array;
        this._view = new Uint8Array(this._buffer);
    }
    fromUint8Array(array) {
        this.fromArrayBuffer(new Uint8Array(array).buffer);
    }
    fromString(string) {
        const stringLength = string.length;
        this.length = stringLength;
        for (let i = 0; i < stringLength; i++)
            this.view[i] = string.charCodeAt(i);
    }
    toString(start = 0, length = (this.view.length - start)) {
        let result = "";
        if ((start >= this.view.length) || (start < 0)) {
            start = 0;
        }
        if ((length >= this.view.length) || (length < 0)) {
            length = this.view.length - start;
        }
        for (let i = start; i < (start + length); i++)
            result += String.fromCharCode(this.view[i]);
        return result;
    }
    fromHexString(hexString) {
        const stringLength = hexString.length;
        this.buffer = new ArrayBuffer(stringLength >> 1);
        this.view = new Uint8Array(this.buffer);
        const hexMap = new Map();
        hexMap.set("0", 0x00);
        hexMap.set("1", 0x01);
        hexMap.set("2", 0x02);
        hexMap.set("3", 0x03);
        hexMap.set("4", 0x04);
        hexMap.set("5", 0x05);
        hexMap.set("6", 0x06);
        hexMap.set("7", 0x07);
        hexMap.set("8", 0x08);
        hexMap.set("9", 0x09);
        hexMap.set("A", 0x0A);
        hexMap.set("a", 0x0A);
        hexMap.set("B", 0x0B);
        hexMap.set("b", 0x0B);
        hexMap.set("C", 0x0C);
        hexMap.set("c", 0x0C);
        hexMap.set("D", 0x0D);
        hexMap.set("d", 0x0D);
        hexMap.set("E", 0x0E);
        hexMap.set("e", 0x0E);
        hexMap.set("F", 0x0F);
        hexMap.set("f", 0x0F);
        let j = 0;
        let temp = 0x00;
        for (let i = 0; i < stringLength; i++) {
            if (!(i % 2)) {
                temp = hexMap.get(hexString.charAt(i)) << 4;
            }
            else {
                temp |= hexMap.get(hexString.charAt(i));
                this.view[j] = temp;
                j++;
            }
        }
    }
    toHexString(start = 0, length = (this.view.length - start)) {
        let result = "";
        if ((start >= this.view.length) || (start < 0)) {
            start = 0;
        }
        if ((length >= this.view.length) || (length < 0)) {
            length = this.view.length - start;
        }
        for (let i = start; i < (start + length); i++) {
            const str = this.view[i].toString(16).toUpperCase();
            result = result + ((str.length == 1) ? "0" : "") + str;
        }
        return result;
    }
    copy(start = 0, length = (this.length - start)) {
        if (!start && !this.length) {
            return new ByteStream();
        }
        if ((start < 0) || (start > (this.length - 1))) {
            throw new Error(`Wrong start position: ${start}`);
        }
        const stream = new ByteStream({
            buffer: this._buffer.slice(start, start + length)
        });
        return stream;
    }
    slice(start = 0, end = this.length) {
        if (!start && !this.length) {
            return new ByteStream();
        }
        if ((start < 0) || (start > (this.length - 1))) {
            throw new Error(`Wrong start position: ${start}`);
        }
        const stream = new ByteStream({
            buffer: this._buffer.slice(start, end),
        });
        return stream;
    }
    realloc(size) {
        const buffer = new ArrayBuffer(size);
        const view = new Uint8Array(buffer);
        if (size > this._view.length)
            view.set(this._view);
        else {
            view.set(new Uint8Array(this._buffer, 0, size));
        }
        this._buffer = buffer;
        this._view = new Uint8Array(this._buffer);
    }
    append(stream) {
        const initialSize = this.length;
        const streamViewLength = stream.length;
        const subarrayView = stream._view.subarray();
        this.realloc(initialSize + streamViewLength);
        this._view.set(subarrayView, initialSize);
    }
    insert(stream, start = 0, length = (this.length - start)) {
        if (start > (this.length - 1))
            return false;
        if (length > (this.length - start)) {
            length = this.length - start;
        }
        if (length > stream.length) {
            length = stream.length;
        }
        if (length == stream.length)
            this._view.set(stream._view, start);
        else {
            this._view.set(stream._view.subarray(0, length), start);
        }
        return true;
    }
    isEqual(stream) {
        if (this.length != stream.length)
            return false;
        for (let i = 0; i < stream.length; i++) {
            if (this.view[i] != stream.view[i])
                return false;
        }
        return true;
    }
    isEqualView(view) {
        if (view.length != this.view.length)
            return false;
        for (let i = 0; i < view.length; i++) {
            if (this.view[i] != view[i])
                return false;
        }
        return true;
    }
    findPattern(pattern, start_, length_, backward_) {
        const { start, length, backward } = this.prepareFindParameters(start_, length_, backward_);
        const patternLength = pattern.length;
        if (patternLength > length) {
            return (-1);
        }
        const patternArray = [];
        for (let i = 0; i < patternLength; i++)
            patternArray.push(pattern.view[i]);
        for (let i = 0; i <= (length - patternLength); i++) {
            let equal = true;
            const equalStart = (backward) ? (start - patternLength - i) : (start + i);
            for (let j = 0; j < patternLength; j++) {
                if (this.view[j + equalStart] != patternArray[j]) {
                    equal = false;
                    break;
                }
            }
            if (equal) {
                return (backward) ? (start - patternLength - i) : (start + patternLength + i);
            }
        }
        return (-1);
    }
    findFirstIn(patterns, start_, length_, backward_) {
        const { start, length, backward } = this.prepareFindParameters(start_, length_, backward_);
        const result = {
            id: (-1),
            position: (backward) ? 0 : (start + length),
            length: 0
        };
        for (let i = 0; i < patterns.length; i++) {
            const position = this.findPattern(patterns[i], start, length, backward);
            if (position != (-1)) {
                let valid = false;
                const patternLength = patterns[i].length;
                if (backward) {
                    if ((position - patternLength) >= (result.position - result.length))
                        valid = true;
                }
                else {
                    if ((position - patternLength) <= (result.position - result.length))
                        valid = true;
                }
                if (valid) {
                    result.position = position;
                    result.id = i;
                    result.length = patternLength;
                }
            }
        }
        return result;
    }
    findAllIn(patterns, start_, length_) {
        let { start, length } = this.prepareFindParameters(start_, length_);
        const result = [];
        let patternFound = {
            id: (-1),
            position: start
        };
        do {
            const position = patternFound.position;
            patternFound = this.findFirstIn(patterns, patternFound.position, length);
            if (patternFound.id == (-1)) {
                break;
            }
            length -= (patternFound.position - position);
            result.push({
                id: patternFound.id,
                position: patternFound.position
            });
        } while (true);
        return result;
    }
    findAllPatternIn(pattern, start_, length_) {
        const { start, length } = this.prepareFindParameters(start_, length_);
        const result = [];
        const patternLength = pattern.length;
        if (patternLength > length) {
            return (-1);
        }
        const patternArray = Array.from(pattern.view);
        for (let i = 0; i <= (length - patternLength); i++) {
            let equal = true;
            const equalStart = start + i;
            for (let j = 0; j < patternLength; j++) {
                if (this.view[j + equalStart] != patternArray[j]) {
                    equal = false;
                    break;
                }
            }
            if (equal) {
                result.push(start + patternLength + i);
                i += (patternLength - 1);
            }
        }
        return result;
    }
    findFirstNotIn(patterns, start_, length_, backward_) {
        let { start, length, backward } = this.prepareFindParameters(start_, length_, backward_);
        const result = {
            left: {
                id: (-1),
                position: start
            },
            right: {
                id: (-1),
                position: 0
            },
            value: new ByteStream()
        };
        let currentLength = length;
        while (currentLength > 0) {
            result.right = this.findFirstIn(patterns, (backward) ? (start - length + currentLength) : (start + length - currentLength), currentLength, backward);
            if (result.right.id == (-1)) {
                length = currentLength;
                if (backward) {
                    start -= length;
                }
                else {
                    start = result.left.position;
                }
                result.value = new ByteStream({
                    buffer: this._buffer.slice(start, start + length),
                });
                break;
            }
            if (result.right.position != ((backward) ? (result.left.position - patterns[result.right.id].length) : (result.left.position + patterns[result.right.id].length))) {
                if (backward) {
                    start = result.right.position + patterns[result.right.id].length;
                    length = result.left.position - result.right.position - patterns[result.right.id].length;
                }
                else {
                    start = result.left.position;
                    length = result.right.position - result.left.position - patterns[result.right.id].length;
                }
                result.value = new ByteStream({
                    buffer: this._buffer.slice(start, start + length),
                });
                break;
            }
            result.left = result.right;
            currentLength -= patterns[result.right.id].length;
        }
        if (backward) {
            const temp = result.right;
            result.right = result.left;
            result.left = temp;
        }
        return result;
    }
    findAllNotIn(patterns, start_, length_) {
        let { start, length } = this.prepareFindParameters(start_, length_);
        const result = [];
        let patternFound = {
            left: {
                id: (-1),
                position: start
            },
            right: {
                id: (-1),
                position: start
            },
            value: new ByteStream()
        };
        do {
            const position = patternFound.right.position;
            patternFound = this.findFirstNotIn(patterns, patternFound.right.position, length);
            length -= (patternFound.right.position - position);
            result.push({
                left: {
                    id: patternFound.left.id,
                    position: patternFound.left.position
                },
                right: {
                    id: patternFound.right.id,
                    position: patternFound.right.position
                },
                value: patternFound.value
            });
        } while (patternFound.right.id != (-1));
        return result;
    }
    findFirstSequence(patterns, start_, length_, backward_) {
        let { start, length, backward } = this.prepareFindParameters(start_, length_, backward_);
        const firstIn = this.skipNotPatterns(patterns, start, length, backward);
        if (firstIn == (-1)) {
            return {
                position: (-1),
                value: new ByteStream()
            };
        }
        const firstNotIn = this.skipPatterns(patterns, firstIn, length - ((backward) ? (start - firstIn) : (firstIn - start)), backward);
        if (backward) {
            start = firstNotIn;
            length = (firstIn - firstNotIn);
        }
        else {
            start = firstIn;
            length = (firstNotIn - firstIn);
        }
        const value = new ByteStream({
            buffer: this._buffer.slice(start, start + length),
        });
        return {
            position: firstNotIn,
            value
        };
    }
    findAllSequences(patterns, start_, length_) {
        let { start, length } = this.prepareFindParameters(start_, length_);
        const result = [];
        let patternFound = {
            position: start,
            value: new ByteStream()
        };
        do {
            const position = patternFound.position;
            patternFound = this.findFirstSequence(patterns, patternFound.position, length);
            if (patternFound.position != (-1)) {
                length -= (patternFound.position - position);
                result.push({
                    position: patternFound.position,
                    value: patternFound.value,
                });
            }
        } while (patternFound.position != (-1));
        return result;
    }
    findPairedPatterns(leftPattern, rightPattern, start_, length_) {
        const result = [];
        if (leftPattern.isEqual(rightPattern))
            return result;
        const { start, length } = this.prepareFindParameters(start_, length_);
        let currentPositionLeft = 0;
        const leftPatterns = this.findAllPatternIn(leftPattern, start, length);
        if (!Array.isArray(leftPatterns) || leftPatterns.length == 0) {
            return result;
        }
        const rightPatterns = this.findAllPatternIn(rightPattern, start, length);
        if (!Array.isArray(rightPatterns) || rightPatterns.length == 0) {
            return result;
        }
        while (currentPositionLeft < leftPatterns.length) {
            if (rightPatterns.length == 0) {
                break;
            }
            if (leftPatterns[0] == rightPatterns[0]) {
                result.push({
                    left: leftPatterns[0],
                    right: rightPatterns[0]
                });
                leftPatterns.splice(0, 1);
                rightPatterns.splice(0, 1);
                continue;
            }
            if (leftPatterns[currentPositionLeft] > rightPatterns[0]) {
                break;
            }
            while (leftPatterns[currentPositionLeft] < rightPatterns[0]) {
                currentPositionLeft++;
                if (currentPositionLeft >= leftPatterns.length) {
                    break;
                }
            }
            result.push({
                left: leftPatterns[currentPositionLeft - 1],
                right: rightPatterns[0]
            });
            leftPatterns.splice(currentPositionLeft - 1, 1);
            rightPatterns.splice(0, 1);
            currentPositionLeft = 0;
        }
        result.sort((a, b) => (a.left - b.left));
        return result;
    }
    findPairedArrays(inputLeftPatterns, inputRightPatterns, start_, length_) {
        const { start, length } = this.prepareFindParameters(start_, length_);
        const result = [];
        let currentPositionLeft = 0;
        const leftPatterns = this.findAllIn(inputLeftPatterns, start, length);
        if (leftPatterns.length == 0)
            return result;
        const rightPatterns = this.findAllIn(inputRightPatterns, start, length);
        if (rightPatterns.length == 0)
            return result;
        while (currentPositionLeft < leftPatterns.length) {
            if (rightPatterns.length == 0) {
                break;
            }
            if (leftPatterns[0].position == rightPatterns[0].position) {
                result.push({
                    left: leftPatterns[0],
                    right: rightPatterns[0]
                });
                leftPatterns.splice(0, 1);
                rightPatterns.splice(0, 1);
                continue;
            }
            if (leftPatterns[currentPositionLeft].position > rightPatterns[0].position) {
                break;
            }
            while (leftPatterns[currentPositionLeft].position < rightPatterns[0].position) {
                currentPositionLeft++;
                if (currentPositionLeft >= leftPatterns.length) {
                    break;
                }
            }
            result.push({
                left: leftPatterns[currentPositionLeft - 1],
                right: rightPatterns[0]
            });
            leftPatterns.splice(currentPositionLeft - 1, 1);
            rightPatterns.splice(0, 1);
            currentPositionLeft = 0;
        }
        result.sort((a, b) => (a.left.position - b.left.position));
        return result;
    }
    replacePattern(searchPattern, replacePattern, start_, length_, findAllResult = null) {
        let result = [];
        let i;
        const output = {
            status: (-1),
            searchPatternPositions: [],
            replacePatternPositions: []
        };
        const { start, length } = this.prepareFindParameters(start_, length_);
        if (findAllResult == null) {
            result = this.findAllIn([searchPattern], start, length);
            if (result.length == 0) {
                return output;
            }
        }
        else {
            result = findAllResult;
        }
        output.searchPatternPositions.push(...Array.from(result, element => element.position));
        const patternDifference = searchPattern.length - replacePattern.length;
        const changedBuffer = new ArrayBuffer(this.view.length - (result.length * patternDifference));
        const changedView = new Uint8Array(changedBuffer);
        changedView.set(new Uint8Array(this.buffer, 0, start));
        for (i = 0; i < result.length; i++) {
            const currentPosition = (i == 0) ? start : result[i - 1].position;
            changedView.set(new Uint8Array(this.buffer, currentPosition, result[i].position - searchPattern.length - currentPosition), currentPosition - i * patternDifference);
            changedView.set(replacePattern.view, result[i].position - searchPattern.length - i * patternDifference);
            output.replacePatternPositions.push(result[i].position - searchPattern.length - i * patternDifference);
        }
        i--;
        changedView.set(new Uint8Array(this.buffer, result[i].position, this.length - result[i].position), result[i].position - searchPattern.length + replacePattern.length - i * patternDifference);
        this.buffer = changedBuffer;
        this.view = new Uint8Array(this.buffer);
        output.status = 1;
        return output;
    }
    skipPatterns(patterns, start_, length_, backward_) {
        const { start, length, backward } = this.prepareFindParameters(start_, length_, backward_);
        let result = start;
        for (let k = 0; k < patterns.length; k++) {
            const patternLength = patterns[k].length;
            const equalStart = (backward) ? (result - patternLength) : (result);
            let equal = true;
            for (let j = 0; j < patternLength; j++) {
                if (this.view[j + equalStart] != patterns[k].view[j]) {
                    equal = false;
                    break;
                }
            }
            if (equal) {
                k = (-1);
                if (backward) {
                    result -= patternLength;
                    if (result <= 0)
                        return result;
                }
                else {
                    result += patternLength;
                    if (result >= (start + length))
                        return result;
                }
            }
        }
        return result;
    }
    skipNotPatterns(patterns, start_, length_, backward_) {
        const { start, length, backward } = this.prepareFindParameters(start_, length_, backward_);
        let result = (-1);
        for (let i = 0; i < length; i++) {
            for (let k = 0; k < patterns.length; k++) {
                const patternLength = patterns[k].length;
                const equalStart = (backward) ? (start - i - patternLength) : (start + i);
                let equal = true;
                for (let j = 0; j < patternLength; j++) {
                    if (this.view[j + equalStart] != patterns[k].view[j]) {
                        equal = false;
                        break;
                    }
                }
                if (equal) {
                    result = (backward) ? (start - i) : (start + i);
                    break;
                }
            }
            if (result != (-1)) {
                break;
            }
        }
        return result;
    }
    prepareFindParameters(start = null, length = null, backward = false) {
        if (start === null) {
            start = (backward) ? this.length : 0;
        }
        if (start > this.length) {
            start = this.length;
        }
        if (backward) {
            if (length === null) {
                length = start;
            }
            if (length > start) {
                length = start;
            }
        }
        else {
            if (length === null) {
                length = this.length - start;
            }
            if (length > (this.length - start)) {
                length = this.length - start;
            }
        }
        return { start, length, backward };
    }
}

class SeqStream {
    constructor(parameters = {}) {
        this._stream = new ByteStream();
        this._length = 0;
        this._start = 0;
        this.backward = false;
        this.appendBlock = 0;
        this.prevLength = 0;
        this.prevStart = 0;
        if ("view" in parameters) {
            this.stream = new ByteStream({ view: parameters.view });
        }
        else if ("buffer" in parameters) {
            this.stream = new ByteStream({ buffer: parameters.buffer });
        }
        else if ("string" in parameters) {
            this.stream = new ByteStream({ string: parameters.string });
        }
        else if ("hexstring" in parameters) {
            this.stream = new ByteStream({ hexstring: parameters.hexstring });
        }
        else if ("stream" in parameters) {
            this.stream = parameters.stream.slice();
        }
        else {
            this.stream = new ByteStream();
        }
        if ("backward" in parameters && parameters.backward) {
            this.backward = parameters.backward;
            this._start = this.stream.length;
        }
        if ("length" in parameters && parameters.length > 0) {
            this._length = parameters.length;
        }
        if ("start" in parameters && parameters.start && parameters.start > 0) {
            this._start = parameters.start;
        }
        if ("appendBlock" in parameters && parameters.appendBlock && parameters.appendBlock > 0) {
            this.appendBlock = parameters.appendBlock;
        }
    }
    set stream(value) {
        this._stream = value;
        this.prevLength = this._length;
        this._length = value.length;
        this.prevStart = this._start;
        this._start = 0;
    }
    get stream() {
        return this._stream;
    }
    set length(value) {
        this.prevLength = this._length;
        this._length = value;
    }
    get length() {
        if (this.appendBlock) {
            return this.start;
        }
        return this._length;
    }
    set start(value) {
        if (value > this.stream.length)
            return;
        this.prevStart = this._start;
        this.prevLength = this._length;
        this._length -= (this.backward) ? (this._start - value) : (value - this._start);
        this._start = value;
    }
    get start() {
        return this._start;
    }
    get buffer() {
        return this._stream.buffer.slice(0, this._length);
    }
    resetPosition() {
        this._start = this.prevStart;
        this._length = this.prevLength;
    }
    findPattern(pattern, gap = null) {
        if ((gap == null) || (gap > this.length)) {
            gap = this.length;
        }
        const result = this.stream.findPattern(pattern, this.start, this.length, this.backward);
        if (result == (-1))
            return result;
        if (this.backward) {
            if (result < (this.start - pattern.length - gap)) {
                return (-1);
            }
        }
        else {
            if (result > (this.start + pattern.length + gap)) {
                return (-1);
            }
        }
        this.start = result;
        return result;
    }
    findFirstIn(patterns, gap = null) {
        if ((gap == null) || (gap > this.length)) {
            gap = this.length;
        }
        const result = this.stream.findFirstIn(patterns, this.start, this.length, this.backward);
        if (result.id == (-1))
            return result;
        if (this.backward) {
            if (result.position < (this.start - patterns[result.id].length - gap)) {
                return {
                    id: (-1),
                    position: (this.backward) ? 0 : (this.start + this.length)
                };
            }
        }
        else {
            if (result.position > (this.start + patterns[result.id].length + gap)) {
                return {
                    id: (-1),
                    position: (this.backward) ? 0 : (this.start + this.length)
                };
            }
        }
        this.start = result.position;
        return result;
    }
    findAllIn(patterns) {
        const start = (this.backward) ? (this.start - this.length) : this.start;
        return this.stream.findAllIn(patterns, start, this.length);
    }
    findFirstNotIn(patterns, gap = null) {
        if ((gap == null) || (gap > this._length)) {
            gap = this._length;
        }
        const result = this._stream.findFirstNotIn(patterns, this._start, this._length, this.backward);
        if ((result.left.id == (-1)) && (result.right.id == (-1))) {
            return result;
        }
        if (this.backward) {
            if (result.right.id != (-1)) {
                if (result.right.position < (this._start - patterns[result.right.id].length - gap)) {
                    return {
                        left: {
                            id: (-1),
                            position: this._start
                        },
                        right: {
                            id: (-1),
                            position: 0
                        },
                        value: new ByteStream()
                    };
                }
            }
        }
        else {
            if (result.left.id != (-1)) {
                if (result.left.position > (this._start + patterns[result.left.id].length + gap)) {
                    return {
                        left: {
                            id: (-1),
                            position: this._start
                        },
                        right: {
                            id: (-1),
                            position: 0
                        },
                        value: new ByteStream()
                    };
                }
            }
        }
        if (this.backward) {
            if (result.left.id == (-1)) {
                this.start = 0;
            }
            else {
                this.start = result.left.position;
            }
        }
        else {
            if (result.right.id == (-1)) {
                this.start = (this._start + this._length);
            }
            else {
                this.start = result.right.position;
            }
        }
        return result;
    }
    findAllNotIn(patterns) {
        const start = (this.backward) ? (this._start - this._length) : this._start;
        return this._stream.findAllNotIn(patterns, start, this._length);
    }
    findFirstSequence(patterns, length = null, gap = null) {
        if ((length == null) || (length > this._length)) {
            length = this._length;
        }
        if ((gap == null) || (gap > length)) {
            gap = length;
        }
        const result = this._stream.findFirstSequence(patterns, this._start, length, this.backward);
        if (result.value.length == 0) {
            return result;
        }
        if (this.backward) {
            if (result.position < (this._start - result.value.length - gap)) {
                return {
                    position: (-1),
                    value: new ByteStream()
                };
            }
        }
        else {
            if (result.position > (this._start + result.value.length + gap)) {
                return {
                    position: (-1),
                    value: new ByteStream()
                };
            }
        }
        this.start = result.position;
        return result;
    }
    findAllSequences(patterns) {
        const start = (this.backward) ? (this.start - this.length) : this.start;
        return this.stream.findAllSequences(patterns, start, this.length);
    }
    findPairedPatterns(leftPattern, rightPattern, gap = null) {
        if ((gap == null) || (gap > this.length)) {
            gap = this.length;
        }
        const start = (this.backward) ? (this.start - this.length) : this.start;
        const result = this.stream.findPairedPatterns(leftPattern, rightPattern, start, this.length);
        if (result.length) {
            if (this.backward) {
                if (result[0].right < (this.start - rightPattern.length - gap)) {
                    return [];
                }
            }
            else {
                if (result[0].left > (this.start + leftPattern.length + gap)) {
                    return [];
                }
            }
        }
        return result;
    }
    findPairedArrays(leftPatterns, rightPatterns, gap = null) {
        if ((gap == null) || (gap > this.length)) {
            gap = this.length;
        }
        const start = (this.backward) ? (this.start - this.length) : this.start;
        const result = this.stream.findPairedArrays(leftPatterns, rightPatterns, start, this.length);
        if (result.length) {
            if (this.backward) {
                if (result[0].right.position < (this.start - rightPatterns[result[0].right.id].length - gap)) {
                    return [];
                }
            }
            else {
                if (result[0].left.position > (this.start + leftPatterns[result[0].left.id].length + gap)) {
                    return [];
                }
            }
        }
        return result;
    }
    replacePattern(searchPattern, replacePattern) {
        const start = (this.backward) ? (this.start - this.length) : this.start;
        return this.stream.replacePattern(searchPattern, replacePattern, start, this.length);
    }
    skipPatterns(patterns) {
        const result = this.stream.skipPatterns(patterns, this.start, this.length, this.backward);
        this.start = result;
        return result;
    }
    skipNotPatterns(patterns) {
        const result = this.stream.skipNotPatterns(patterns, this.start, this.length, this.backward);
        if (result == (-1))
            return (-1);
        this.start = result;
        return result;
    }
    append(stream) {
        this.beforeAppend(stream.length);
        this._stream.view.set(stream.view, this._start);
        this._length += (stream.length * 2);
        this.start = (this._start + stream.length);
        this.prevLength -= (stream.length * 2);
    }
    appendView(view) {
        this.beforeAppend(view.length);
        this._stream.view.set(view, this._start);
        this._length += (view.length * 2);
        this.start = (this._start + view.length);
        this.prevLength -= (view.length * 2);
    }
    appendChar(char) {
        this.beforeAppend(1);
        this._stream.view[this._start] = char;
        this._length += 2;
        this.start = (this._start + 1);
        this.prevLength -= 2;
    }
    appendUint16(number) {
        this.beforeAppend(2);
        const value = new Uint16Array([number]);
        const view = new Uint8Array(value.buffer);
        this.stream.view[this._start] = view[1];
        this._stream.view[this._start + 1] = view[0];
        this._length += 4;
        this.start = this._start + 2;
        this.prevLength -= 4;
    }
    appendUint24(number) {
        this.beforeAppend(3);
        const value = new Uint32Array([number]);
        const view = new Uint8Array(value.buffer);
        this._stream.view[this._start] = view[2];
        this._stream.view[this._start + 1] = view[1];
        this._stream.view[this._start + 2] = view[0];
        this._length += 6;
        this.start = (this._start + 3);
        this.prevLength -= 6;
    }
    appendUint32(number) {
        this.beforeAppend(4);
        const value = new Uint32Array([number]);
        const view = new Uint8Array(value.buffer);
        this._stream.view[this._start] = view[3];
        this._stream.view[this._start + 1] = view[2];
        this._stream.view[this._start + 2] = view[1];
        this._stream.view[this._start + 3] = view[0];
        this._length += 8;
        this.start = (this._start + 4);
        this.prevLength -= 8;
    }
    appendInt16(number) {
        this.beforeAppend(2);
        const value = new Int16Array([number]);
        const view = new Uint8Array(value.buffer);
        this._stream.view[this._start] = view[1];
        this._stream.view[this._start + 1] = view[0];
        this._length += 4;
        this.start = (this._start + 2);
        this.prevLength -= 4;
    }
    appendInt32(number) {
        this.beforeAppend(4);
        const value = new Int32Array([number]);
        const view = new Uint8Array(value.buffer);
        this._stream.view[this._start] = view[3];
        this._stream.view[this._start + 1] = view[2];
        this._stream.view[this._start + 2] = view[1];
        this._stream.view[this._start + 3] = view[0];
        this._length += 8;
        this.start = (this._start + 4);
        this.prevLength -= 8;
    }
    getBlock(size, changeLength = true) {
        if (this._length <= 0) {
            return new Uint8Array(0);
        }
        if (this._length < size) {
            size = this._length;
        }
        let result;
        if (this.backward) {
            const view = this._stream.view.subarray(this._length - size, this._length);
            result = new Uint8Array(size);
            for (let i = 0; i < size; i++) {
                result[size - 1 - i] = view[i];
            }
        }
        else {
            result = this._stream.view.subarray(this._start, this._start + size);
        }
        if (changeLength) {
            this.start += ((this.backward) ? ((-1) * size) : size);
        }
        return result;
    }
    getUint16(changeLength = true) {
        const block = this.getBlock(2, changeLength);
        if (block.length < 2)
            return 0;
        const value = new Uint16Array(1);
        const view = new Uint8Array(value.buffer);
        view[0] = block[1];
        view[1] = block[0];
        return value[0];
    }
    getInt16(changeLength = true) {
        const block = this.getBlock(2, changeLength);
        if (block.length < 2)
            return 0;
        const value = new Int16Array(1);
        const view = new Uint8Array(value.buffer);
        view[0] = block[1];
        view[1] = block[0];
        return value[0];
    }
    getUint24(changeLength = true) {
        const block = this.getBlock(3, changeLength);
        if (block.length < 3)
            return 0;
        const value = new Uint32Array(1);
        const view = new Uint8Array(value.buffer);
        for (let i = 3; i >= 1; i--) {
            view[3 - i] = block[i - 1];
        }
        return value[0];
    }
    getUint32(changeLength = true) {
        const block = this.getBlock(4, changeLength);
        if (block.length < 4) {
            return 0;
        }
        const value = new Uint32Array(1);
        const view = new Uint8Array(value.buffer);
        for (let i = 3; i >= 0; i--) {
            view[3 - i] = block[i];
        }
        return value[0];
    }
    getInt32(changeLength = true) {
        const block = this.getBlock(4, changeLength);
        if (block.length < 4)
            return 0;
        const value = new Int32Array(1);
        const view = new Uint8Array(value.buffer);
        for (let i = 3; i >= 0; i--) {
            view[3 - i] = block[i];
        }
        return value[0];
    }
    beforeAppend(size) {
        if ((this._start + size) > this._stream.length) {
            if (size > this.appendBlock) {
                this.appendBlock = size + SeqStream.APPEND_BLOCK;
            }
            this._stream.realloc(this._stream.length + this.appendBlock);
        }
    }
}
SeqStream.APPEND_BLOCK = 1000;

/******************************************************************************
Copyright (c) Microsoft Corporation.

Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */

function __awaiter(thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
}

var _a;
class ECNamedCurves {
    static register(name, id, size) {
        this.namedCurves[name.toLowerCase()] = this.namedCurves[id] = { name, id, size };
    }
    static find(nameOrId) {
        return this.namedCurves[nameOrId.toLowerCase()] || null;
    }
}
_a = ECNamedCurves;
ECNamedCurves.namedCurves = {};
(() => {
    _a.register("P-256", "1.2.840.10045.3.1.7", 32);
    _a.register("P-384", "1.3.132.0.34", 48);
    _a.register("P-521", "1.3.132.0.35", 66);
    _a.register("brainpoolP256r1", "1.3.36.3.3.2.8.1.1.7", 32);
    _a.register("brainpoolP384r1", "1.3.36.3.3.2.8.1.1.11", 48);
    _a.register("brainpoolP512r1", "1.3.36.3.3.2.8.1.1.13", 64);
})();

const X = "x";
const Y = "y";
const NAMED_CURVE$1 = "namedCurve";
class ECPublicKey extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.x = getParametersValue(parameters, X, ECPublicKey.defaultValues(X));
        this.y = getParametersValue(parameters, Y, ECPublicKey.defaultValues(Y));
        this.namedCurve = getParametersValue(parameters, NAMED_CURVE$1, ECPublicKey.defaultValues(NAMED_CURVE$1));
        if (parameters.json) {
            this.fromJSON(parameters.json);
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case X:
            case Y:
                return EMPTY_BUFFER;
            case NAMED_CURVE$1:
                return EMPTY_STRING;
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case X:
            case Y:
                return memberValue instanceof ArrayBuffer &&
                    (isEqualBuffer(memberValue, ECPublicKey.defaultValues(memberName)));
            case NAMED_CURVE$1:
                return typeof memberValue === "string" &&
                    memberValue === ECPublicKey.defaultValues(memberName);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema() {
        return new RawData();
    }
    fromSchema(schema1) {
        const view = BufferSourceConverter.toUint8Array(schema1);
        if (view[0] !== 0x04) {
            throw new Error("Object's schema was not verified against input data for ECPublicKey");
        }
        const namedCurve = ECNamedCurves.find(this.namedCurve);
        if (!namedCurve) {
            throw new Error(`Incorrect curve OID: ${this.namedCurve}`);
        }
        const coordinateLength = namedCurve.size;
        if (view.byteLength !== (coordinateLength * 2 + 1)) {
            throw new Error("Object's schema was not verified against input data for ECPublicKey");
        }
        this.namedCurve = namedCurve.name;
        this.x = view.slice(1, coordinateLength + 1).buffer;
        this.y = view.slice(1 + coordinateLength, coordinateLength * 2 + 1).buffer;
    }
    toSchema() {
        return new RawData({
            data: utilConcatBuf((new Uint8Array([0x04])).buffer, this.x, this.y)
        });
    }
    toJSON() {
        const namedCurve = ECNamedCurves.find(this.namedCurve);
        return {
            crv: namedCurve ? namedCurve.name : this.namedCurve,
            x: toBase64(arrayBufferToString(this.x), true, true, false),
            y: toBase64(arrayBufferToString(this.y), true, true, false)
        };
    }
    fromJSON(json) {
        ParameterError.assert("json", json, "crv", "x", "y");
        let coordinateLength = 0;
        const namedCurve = ECNamedCurves.find(json.crv);
        if (namedCurve) {
            this.namedCurve = namedCurve.id;
            coordinateLength = namedCurve.size;
        }
        const xConvertBuffer = stringToArrayBuffer(fromBase64(json.x, true));
        if (xConvertBuffer.byteLength < coordinateLength) {
            this.x = new ArrayBuffer(coordinateLength);
            const view = new Uint8Array(this.x);
            const convertBufferView = new Uint8Array(xConvertBuffer);
            view.set(convertBufferView, 1);
        }
        else {
            this.x = xConvertBuffer.slice(0, coordinateLength);
        }
        const yConvertBuffer = stringToArrayBuffer(fromBase64(json.y, true));
        if (yConvertBuffer.byteLength < coordinateLength) {
            this.y = new ArrayBuffer(coordinateLength);
            const view = new Uint8Array(this.y);
            const convertBufferView = new Uint8Array(yConvertBuffer);
            view.set(convertBufferView, 1);
        }
        else {
            this.y = yConvertBuffer.slice(0, coordinateLength);
        }
    }
}
ECPublicKey.CLASS_NAME = "ECPublicKey";

const MODULUS$1 = "modulus";
const PUBLIC_EXPONENT$1 = "publicExponent";
const CLEAR_PROPS$1b = [MODULUS$1, PUBLIC_EXPONENT$1];
class RSAPublicKey extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.modulus = getParametersValue(parameters, MODULUS$1, RSAPublicKey.defaultValues(MODULUS$1));
        this.publicExponent = getParametersValue(parameters, PUBLIC_EXPONENT$1, RSAPublicKey.defaultValues(PUBLIC_EXPONENT$1));
        if (parameters.json) {
            this.fromJSON(parameters.json);
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case MODULUS$1:
                return new Integer();
            case PUBLIC_EXPONENT$1:
                return new Integer();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Integer({ name: (names.modulus || EMPTY_STRING) }),
                new Integer({ name: (names.publicExponent || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1b);
        const asn1 = compareSchema(schema, schema, RSAPublicKey.schema({
            names: {
                modulus: MODULUS$1,
                publicExponent: PUBLIC_EXPONENT$1
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.modulus = asn1.result.modulus.convertFromDER(256);
        this.publicExponent = asn1.result.publicExponent;
    }
    toSchema() {
        return (new Sequence({
            value: [
                this.modulus.convertToDER(),
                this.publicExponent
            ]
        }));
    }
    toJSON() {
        return {
            n: Convert.ToBase64Url(this.modulus.valueBlock.valueHexView),
            e: Convert.ToBase64Url(this.publicExponent.valueBlock.valueHexView),
        };
    }
    fromJSON(json) {
        ParameterError.assert("json", json, "n", "e");
        const array = stringToArrayBuffer(fromBase64(json.n, true));
        this.modulus = new Integer({ valueHex: array.slice(0, Math.pow(2, nearestPowerOf2(array.byteLength))) });
        this.publicExponent = new Integer({ valueHex: stringToArrayBuffer(fromBase64(json.e, true)).slice(0, 3) });
    }
}
RSAPublicKey.CLASS_NAME = "RSAPublicKey";

const ALGORITHM$1 = "algorithm";
const SUBJECT_PUBLIC_KEY = "subjectPublicKey";
const CLEAR_PROPS$1a = [ALGORITHM$1, SUBJECT_PUBLIC_KEY];
class PublicKeyInfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.algorithm = getParametersValue(parameters, ALGORITHM$1, PublicKeyInfo.defaultValues(ALGORITHM$1));
        this.subjectPublicKey = getParametersValue(parameters, SUBJECT_PUBLIC_KEY, PublicKeyInfo.defaultValues(SUBJECT_PUBLIC_KEY));
        const parsedKey = getParametersValue(parameters, "parsedKey", null);
        if (parsedKey) {
            this.parsedKey = parsedKey;
        }
        if (parameters.json) {
            this.fromJSON(parameters.json);
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    get parsedKey() {
        if (this._parsedKey === undefined) {
            switch (this.algorithm.algorithmId) {
                case "1.2.840.10045.2.1":
                    if ("algorithmParams" in this.algorithm) {
                        if (this.algorithm.algorithmParams.constructor.blockName() === ObjectIdentifier.blockName()) {
                            try {
                                this._parsedKey = new ECPublicKey({
                                    namedCurve: this.algorithm.algorithmParams.valueBlock.toString(),
                                    schema: this.subjectPublicKey.valueBlock.valueHexView
                                });
                            }
                            catch (ex) {
                            }
                        }
                    }
                    break;
                case "1.2.840.113549.1.1.1":
                    {
                        const publicKeyASN1 = fromBER(this.subjectPublicKey.valueBlock.valueHexView);
                        if (publicKeyASN1.offset !== -1) {
                            try {
                                this._parsedKey = new RSAPublicKey({ schema: publicKeyASN1.result });
                            }
                            catch (ex) {
                            }
                        }
                    }
                    break;
            }
            this._parsedKey || (this._parsedKey = null);
        }
        return this._parsedKey || undefined;
    }
    set parsedKey(value) {
        this._parsedKey = value;
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case ALGORITHM$1:
                return new AlgorithmIdentifier();
            case SUBJECT_PUBLIC_KEY:
                return new BitString();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                AlgorithmIdentifier.schema(names.algorithm || {}),
                new BitString({ name: (names.subjectPublicKey || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1a);
        const asn1 = compareSchema(schema, schema, PublicKeyInfo.schema({
            names: {
                algorithm: {
                    names: {
                        blockName: ALGORITHM$1
                    }
                },
                subjectPublicKey: SUBJECT_PUBLIC_KEY
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.algorithm = new AlgorithmIdentifier({ schema: asn1.result.algorithm });
        this.subjectPublicKey = asn1.result.subjectPublicKey;
    }
    toSchema() {
        return (new Sequence({
            value: [
                this.algorithm.toSchema(),
                this.subjectPublicKey
            ]
        }));
    }
    toJSON() {
        if (!this.parsedKey) {
            return {
                algorithm: this.algorithm.toJSON(),
                subjectPublicKey: this.subjectPublicKey.toJSON(),
            };
        }
        const jwk = {};
        switch (this.algorithm.algorithmId) {
            case "1.2.840.10045.2.1":
                jwk.kty = "EC";
                break;
            case "1.2.840.113549.1.1.1":
                jwk.kty = "RSA";
                break;
        }
        const publicKeyJWK = this.parsedKey.toJSON();
        Object.assign(jwk, publicKeyJWK);
        return jwk;
    }
    fromJSON(json) {
        if ("kty" in json) {
            switch (json.kty.toUpperCase()) {
                case "EC":
                    this.parsedKey = new ECPublicKey({ json });
                    this.algorithm = new AlgorithmIdentifier({
                        algorithmId: "1.2.840.10045.2.1",
                        algorithmParams: new ObjectIdentifier({ value: this.parsedKey.namedCurve })
                    });
                    break;
                case "RSA":
                    this.parsedKey = new RSAPublicKey({ json });
                    this.algorithm = new AlgorithmIdentifier({
                        algorithmId: "1.2.840.113549.1.1.1",
                        algorithmParams: new Null()
                    });
                    break;
                default:
                    throw new Error(`Invalid value for "kty" parameter: ${json.kty}`);
            }
            this.subjectPublicKey = new BitString({ valueHex: this.parsedKey.toSchema().toBER(false) });
        }
    }
    importKey(publicKey, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                if (!publicKey) {
                    throw new Error("Need to provide publicKey input parameter");
                }
                const exportedKey = yield crypto.exportKey("spki", publicKey);
                const asn1 = fromBER(exportedKey);
                try {
                    this.fromSchema(asn1.result);
                }
                catch (exception) {
                    throw new Error("Error during initializing object from schema");
                }
            }
            catch (e) {
                const message = e instanceof Error ? e.message : `${e}`;
                throw new Error(`Error during exporting public key: ${message}`);
            }
        });
    }
}
PublicKeyInfo.CLASS_NAME = "PublicKeyInfo";

const VERSION$l = "version";
const PRIVATE_KEY$1 = "privateKey";
const NAMED_CURVE = "namedCurve";
const PUBLIC_KEY$1 = "publicKey";
const CLEAR_PROPS$19 = [
    VERSION$l,
    PRIVATE_KEY$1,
    NAMED_CURVE,
    PUBLIC_KEY$1
];
class ECPrivateKey extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.version = getParametersValue(parameters, VERSION$l, ECPrivateKey.defaultValues(VERSION$l));
        this.privateKey = getParametersValue(parameters, PRIVATE_KEY$1, ECPrivateKey.defaultValues(PRIVATE_KEY$1));
        if (NAMED_CURVE in parameters) {
            this.namedCurve = getParametersValue(parameters, NAMED_CURVE, ECPrivateKey.defaultValues(NAMED_CURVE));
        }
        if (PUBLIC_KEY$1 in parameters) {
            this.publicKey = getParametersValue(parameters, PUBLIC_KEY$1, ECPrivateKey.defaultValues(PUBLIC_KEY$1));
        }
        if (parameters.json) {
            this.fromJSON(parameters.json);
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VERSION$l:
                return 1;
            case PRIVATE_KEY$1:
                return new OctetString();
            case NAMED_CURVE:
                return EMPTY_STRING;
            case PUBLIC_KEY$1:
                return new ECPublicKey();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case VERSION$l:
                return (memberValue === ECPrivateKey.defaultValues(memberName));
            case PRIVATE_KEY$1:
                return (memberValue.isEqual(ECPrivateKey.defaultValues(memberName)));
            case NAMED_CURVE:
                return (memberValue === EMPTY_STRING);
            case PUBLIC_KEY$1:
                return ((ECPublicKey.compareWithDefault(NAMED_CURVE, memberValue.namedCurve)) &&
                    (ECPublicKey.compareWithDefault("x", memberValue.x)) &&
                    (ECPublicKey.compareWithDefault("y", memberValue.y)));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Integer({ name: (names.version || EMPTY_STRING) }),
                new OctetString({ name: (names.privateKey || EMPTY_STRING) }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [
                        new ObjectIdentifier({ name: (names.namedCurve || EMPTY_STRING) })
                    ]
                }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: [
                        new BitString({ name: (names.publicKey || EMPTY_STRING) })
                    ]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$19);
        const asn1 = compareSchema(schema, schema, ECPrivateKey.schema({
            names: {
                version: VERSION$l,
                privateKey: PRIVATE_KEY$1,
                namedCurve: NAMED_CURVE,
                publicKey: PUBLIC_KEY$1
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.version = asn1.result.version.valueBlock.valueDec;
        this.privateKey = asn1.result.privateKey;
        if (NAMED_CURVE in asn1.result) {
            this.namedCurve = asn1.result.namedCurve.valueBlock.toString();
        }
        if (PUBLIC_KEY$1 in asn1.result) {
            const publicKeyData = { schema: asn1.result.publicKey.valueBlock.valueHex };
            if (NAMED_CURVE in this) {
                publicKeyData.namedCurve = this.namedCurve;
            }
            this.publicKey = new ECPublicKey(publicKeyData);
        }
    }
    toSchema() {
        const outputArray = [
            new Integer({ value: this.version }),
            this.privateKey
        ];
        if (this.namedCurve) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [
                    new ObjectIdentifier({ value: this.namedCurve })
                ]
            }));
        }
        if (this.publicKey) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                value: [
                    new BitString({ valueHex: this.publicKey.toSchema().toBER(false) })
                ]
            }));
        }
        return new Sequence({
            value: outputArray
        });
    }
    toJSON() {
        if (!this.namedCurve || ECPrivateKey.compareWithDefault(NAMED_CURVE, this.namedCurve)) {
            throw new Error("Not enough information for making JSON: absent \"namedCurve\" value");
        }
        const curve = ECNamedCurves.find(this.namedCurve);
        const privateKeyJSON = {
            crv: curve ? curve.name : this.namedCurve,
            d: Convert.ToBase64Url(this.privateKey.valueBlock.valueHexView),
        };
        if (this.publicKey) {
            const publicKeyJSON = this.publicKey.toJSON();
            privateKeyJSON.x = publicKeyJSON.x;
            privateKeyJSON.y = publicKeyJSON.y;
        }
        return privateKeyJSON;
    }
    fromJSON(json) {
        ParameterError.assert("json", json, "crv", "d");
        let coordinateLength = 0;
        const curve = ECNamedCurves.find(json.crv);
        if (curve) {
            this.namedCurve = curve.id;
            coordinateLength = curve.size;
        }
        const convertBuffer = Convert.FromBase64Url(json.d);
        if (convertBuffer.byteLength < coordinateLength) {
            const buffer = new ArrayBuffer(coordinateLength);
            const view = new Uint8Array(buffer);
            const convertBufferView = new Uint8Array(convertBuffer);
            view.set(convertBufferView, 1);
            this.privateKey = new OctetString({ valueHex: buffer });
        }
        else {
            this.privateKey = new OctetString({ valueHex: convertBuffer.slice(0, coordinateLength) });
        }
        if (json.x && json.y) {
            this.publicKey = new ECPublicKey({ json });
        }
    }
}
ECPrivateKey.CLASS_NAME = "ECPrivateKey";

const PRIME = "prime";
const EXPONENT = "exponent";
const COEFFICIENT$1 = "coefficient";
const CLEAR_PROPS$18 = [
    PRIME,
    EXPONENT,
    COEFFICIENT$1,
];
class OtherPrimeInfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.prime = getParametersValue(parameters, PRIME, OtherPrimeInfo.defaultValues(PRIME));
        this.exponent = getParametersValue(parameters, EXPONENT, OtherPrimeInfo.defaultValues(EXPONENT));
        this.coefficient = getParametersValue(parameters, COEFFICIENT$1, OtherPrimeInfo.defaultValues(COEFFICIENT$1));
        if (parameters.json) {
            this.fromJSON(parameters.json);
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case PRIME:
                return new Integer();
            case EXPONENT:
                return new Integer();
            case COEFFICIENT$1:
                return new Integer();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Integer({ name: (names.prime || EMPTY_STRING) }),
                new Integer({ name: (names.exponent || EMPTY_STRING) }),
                new Integer({ name: (names.coefficient || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$18);
        const asn1 = compareSchema(schema, schema, OtherPrimeInfo.schema({
            names: {
                prime: PRIME,
                exponent: EXPONENT,
                coefficient: COEFFICIENT$1
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.prime = asn1.result.prime.convertFromDER();
        this.exponent = asn1.result.exponent.convertFromDER();
        this.coefficient = asn1.result.coefficient.convertFromDER();
    }
    toSchema() {
        return (new Sequence({
            value: [
                this.prime.convertToDER(),
                this.exponent.convertToDER(),
                this.coefficient.convertToDER()
            ]
        }));
    }
    toJSON() {
        return {
            r: Convert.ToBase64Url(this.prime.valueBlock.valueHexView),
            d: Convert.ToBase64Url(this.exponent.valueBlock.valueHexView),
            t: Convert.ToBase64Url(this.coefficient.valueBlock.valueHexView),
        };
    }
    fromJSON(json) {
        ParameterError.assert("json", json, "r", "d", "r");
        this.prime = new Integer({ valueHex: Convert.FromBase64Url(json.r) });
        this.exponent = new Integer({ valueHex: Convert.FromBase64Url(json.d) });
        this.coefficient = new Integer({ valueHex: Convert.FromBase64Url(json.t) });
    }
}
OtherPrimeInfo.CLASS_NAME = "OtherPrimeInfo";

const VERSION$k = "version";
const MODULUS = "modulus";
const PUBLIC_EXPONENT = "publicExponent";
const PRIVATE_EXPONENT = "privateExponent";
const PRIME1 = "prime1";
const PRIME2 = "prime2";
const EXPONENT1 = "exponent1";
const EXPONENT2 = "exponent2";
const COEFFICIENT = "coefficient";
const OTHER_PRIME_INFOS = "otherPrimeInfos";
const CLEAR_PROPS$17 = [
    VERSION$k,
    MODULUS,
    PUBLIC_EXPONENT,
    PRIVATE_EXPONENT,
    PRIME1,
    PRIME2,
    EXPONENT1,
    EXPONENT2,
    COEFFICIENT,
    OTHER_PRIME_INFOS
];
class RSAPrivateKey extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.version = getParametersValue(parameters, VERSION$k, RSAPrivateKey.defaultValues(VERSION$k));
        this.modulus = getParametersValue(parameters, MODULUS, RSAPrivateKey.defaultValues(MODULUS));
        this.publicExponent = getParametersValue(parameters, PUBLIC_EXPONENT, RSAPrivateKey.defaultValues(PUBLIC_EXPONENT));
        this.privateExponent = getParametersValue(parameters, PRIVATE_EXPONENT, RSAPrivateKey.defaultValues(PRIVATE_EXPONENT));
        this.prime1 = getParametersValue(parameters, PRIME1, RSAPrivateKey.defaultValues(PRIME1));
        this.prime2 = getParametersValue(parameters, PRIME2, RSAPrivateKey.defaultValues(PRIME2));
        this.exponent1 = getParametersValue(parameters, EXPONENT1, RSAPrivateKey.defaultValues(EXPONENT1));
        this.exponent2 = getParametersValue(parameters, EXPONENT2, RSAPrivateKey.defaultValues(EXPONENT2));
        this.coefficient = getParametersValue(parameters, COEFFICIENT, RSAPrivateKey.defaultValues(COEFFICIENT));
        if (OTHER_PRIME_INFOS in parameters) {
            this.otherPrimeInfos = getParametersValue(parameters, OTHER_PRIME_INFOS, RSAPrivateKey.defaultValues(OTHER_PRIME_INFOS));
        }
        if (parameters.json) {
            this.fromJSON(parameters.json);
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VERSION$k:
                return 0;
            case MODULUS:
                return new Integer();
            case PUBLIC_EXPONENT:
                return new Integer();
            case PRIVATE_EXPONENT:
                return new Integer();
            case PRIME1:
                return new Integer();
            case PRIME2:
                return new Integer();
            case EXPONENT1:
                return new Integer();
            case EXPONENT2:
                return new Integer();
            case COEFFICIENT:
                return new Integer();
            case OTHER_PRIME_INFOS:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Integer({ name: (names.version || EMPTY_STRING) }),
                new Integer({ name: (names.modulus || EMPTY_STRING) }),
                new Integer({ name: (names.publicExponent || EMPTY_STRING) }),
                new Integer({ name: (names.privateExponent || EMPTY_STRING) }),
                new Integer({ name: (names.prime1 || EMPTY_STRING) }),
                new Integer({ name: (names.prime2 || EMPTY_STRING) }),
                new Integer({ name: (names.exponent1 || EMPTY_STRING) }),
                new Integer({ name: (names.exponent2 || EMPTY_STRING) }),
                new Integer({ name: (names.coefficient || EMPTY_STRING) }),
                new Sequence({
                    optional: true,
                    value: [
                        new Repeated({
                            name: (names.otherPrimeInfosName || EMPTY_STRING),
                            value: OtherPrimeInfo.schema(names.otherPrimeInfo || {})
                        })
                    ]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$17);
        const asn1 = compareSchema(schema, schema, RSAPrivateKey.schema({
            names: {
                version: VERSION$k,
                modulus: MODULUS,
                publicExponent: PUBLIC_EXPONENT,
                privateExponent: PRIVATE_EXPONENT,
                prime1: PRIME1,
                prime2: PRIME2,
                exponent1: EXPONENT1,
                exponent2: EXPONENT2,
                coefficient: COEFFICIENT,
                otherPrimeInfo: {
                    names: {
                        blockName: OTHER_PRIME_INFOS
                    }
                }
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.version = asn1.result.version.valueBlock.valueDec;
        this.modulus = asn1.result.modulus.convertFromDER(256);
        this.publicExponent = asn1.result.publicExponent;
        this.privateExponent = asn1.result.privateExponent.convertFromDER(256);
        this.prime1 = asn1.result.prime1.convertFromDER(128);
        this.prime2 = asn1.result.prime2.convertFromDER(128);
        this.exponent1 = asn1.result.exponent1.convertFromDER(128);
        this.exponent2 = asn1.result.exponent2.convertFromDER(128);
        this.coefficient = asn1.result.coefficient.convertFromDER(128);
        if (OTHER_PRIME_INFOS in asn1.result)
            this.otherPrimeInfos = Array.from(asn1.result.otherPrimeInfos, element => new OtherPrimeInfo({ schema: element }));
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(new Integer({ value: this.version }));
        outputArray.push(this.modulus.convertToDER());
        outputArray.push(this.publicExponent);
        outputArray.push(this.privateExponent.convertToDER());
        outputArray.push(this.prime1.convertToDER());
        outputArray.push(this.prime2.convertToDER());
        outputArray.push(this.exponent1.convertToDER());
        outputArray.push(this.exponent2.convertToDER());
        outputArray.push(this.coefficient.convertToDER());
        if (this.otherPrimeInfos) {
            outputArray.push(new Sequence({
                value: Array.from(this.otherPrimeInfos, o => o.toSchema())
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const jwk = {
            n: Convert.ToBase64Url(this.modulus.valueBlock.valueHexView),
            e: Convert.ToBase64Url(this.publicExponent.valueBlock.valueHexView),
            d: Convert.ToBase64Url(this.privateExponent.valueBlock.valueHexView),
            p: Convert.ToBase64Url(this.prime1.valueBlock.valueHexView),
            q: Convert.ToBase64Url(this.prime2.valueBlock.valueHexView),
            dp: Convert.ToBase64Url(this.exponent1.valueBlock.valueHexView),
            dq: Convert.ToBase64Url(this.exponent2.valueBlock.valueHexView),
            qi: Convert.ToBase64Url(this.coefficient.valueBlock.valueHexView),
        };
        if (this.otherPrimeInfos) {
            jwk.oth = Array.from(this.otherPrimeInfos, o => o.toJSON());
        }
        return jwk;
    }
    fromJSON(json) {
        ParameterError.assert("json", json, "n", "e", "d", "p", "q", "dp", "dq", "qi");
        this.modulus = new Integer({ valueHex: Convert.FromBase64Url(json.n) });
        this.publicExponent = new Integer({ valueHex: Convert.FromBase64Url(json.e) });
        this.privateExponent = new Integer({ valueHex: Convert.FromBase64Url(json.d) });
        this.prime1 = new Integer({ valueHex: Convert.FromBase64Url(json.p) });
        this.prime2 = new Integer({ valueHex: Convert.FromBase64Url(json.q) });
        this.exponent1 = new Integer({ valueHex: Convert.FromBase64Url(json.dp) });
        this.exponent2 = new Integer({ valueHex: Convert.FromBase64Url(json.dq) });
        this.coefficient = new Integer({ valueHex: Convert.FromBase64Url(json.qi) });
        if (json.oth) {
            this.otherPrimeInfos = Array.from(json.oth, (element) => new OtherPrimeInfo({ json: element }));
        }
    }
}
RSAPrivateKey.CLASS_NAME = "RSAPrivateKey";

const VERSION$j = "version";
const PRIVATE_KEY_ALGORITHM = "privateKeyAlgorithm";
const PRIVATE_KEY = "privateKey";
const ATTRIBUTES$5 = "attributes";
const PARSED_KEY = "parsedKey";
const CLEAR_PROPS$16 = [
    VERSION$j,
    PRIVATE_KEY_ALGORITHM,
    PRIVATE_KEY,
    ATTRIBUTES$5
];
class PrivateKeyInfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.version = getParametersValue(parameters, VERSION$j, PrivateKeyInfo.defaultValues(VERSION$j));
        this.privateKeyAlgorithm = getParametersValue(parameters, PRIVATE_KEY_ALGORITHM, PrivateKeyInfo.defaultValues(PRIVATE_KEY_ALGORITHM));
        this.privateKey = getParametersValue(parameters, PRIVATE_KEY, PrivateKeyInfo.defaultValues(PRIVATE_KEY));
        if (ATTRIBUTES$5 in parameters) {
            this.attributes = getParametersValue(parameters, ATTRIBUTES$5, PrivateKeyInfo.defaultValues(ATTRIBUTES$5));
        }
        if (PARSED_KEY in parameters) {
            this.parsedKey = getParametersValue(parameters, PARSED_KEY, PrivateKeyInfo.defaultValues(PARSED_KEY));
        }
        if (parameters.json) {
            this.fromJSON(parameters.json);
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VERSION$j:
                return 0;
            case PRIVATE_KEY_ALGORITHM:
                return new AlgorithmIdentifier();
            case PRIVATE_KEY:
                return new OctetString();
            case ATTRIBUTES$5:
                return [];
            case PARSED_KEY:
                return {};
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Integer({ name: (names.version || EMPTY_STRING) }),
                AlgorithmIdentifier.schema(names.privateKeyAlgorithm || {}),
                new OctetString({ name: (names.privateKey || EMPTY_STRING) }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [
                        new Repeated({
                            name: (names.attributes || EMPTY_STRING),
                            value: Attribute.schema()
                        })
                    ]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$16);
        const asn1 = compareSchema(schema, schema, PrivateKeyInfo.schema({
            names: {
                version: VERSION$j,
                privateKeyAlgorithm: {
                    names: {
                        blockName: PRIVATE_KEY_ALGORITHM
                    }
                },
                privateKey: PRIVATE_KEY,
                attributes: ATTRIBUTES$5
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.version = asn1.result.version.valueBlock.valueDec;
        this.privateKeyAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.privateKeyAlgorithm });
        this.privateKey = asn1.result.privateKey;
        if (ATTRIBUTES$5 in asn1.result)
            this.attributes = Array.from(asn1.result.attributes, element => new Attribute({ schema: element }));
        switch (this.privateKeyAlgorithm.algorithmId) {
            case "1.2.840.113549.1.1.1":
                {
                    const privateKeyASN1 = fromBER(this.privateKey.valueBlock.valueHexView);
                    if (privateKeyASN1.offset !== -1)
                        this.parsedKey = new RSAPrivateKey({ schema: privateKeyASN1.result });
                }
                break;
            case "1.2.840.10045.2.1":
                if ("algorithmParams" in this.privateKeyAlgorithm) {
                    if (this.privateKeyAlgorithm.algorithmParams instanceof ObjectIdentifier) {
                        const privateKeyASN1 = fromBER(this.privateKey.valueBlock.valueHexView);
                        if (privateKeyASN1.offset !== -1) {
                            this.parsedKey = new ECPrivateKey({
                                namedCurve: this.privateKeyAlgorithm.algorithmParams.valueBlock.toString(),
                                schema: privateKeyASN1.result
                            });
                        }
                    }
                }
                break;
        }
    }
    toSchema() {
        const outputArray = [
            new Integer({ value: this.version }),
            this.privateKeyAlgorithm.toSchema(),
            this.privateKey
        ];
        if (this.attributes) {
            outputArray.push(new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: Array.from(this.attributes, o => o.toSchema())
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        if (!this.parsedKey) {
            const object = {
                version: this.version,
                privateKeyAlgorithm: this.privateKeyAlgorithm.toJSON(),
                privateKey: this.privateKey.toJSON(),
            };
            if (this.attributes) {
                object.attributes = Array.from(this.attributes, o => o.toJSON());
            }
            return object;
        }
        const jwk = {};
        switch (this.privateKeyAlgorithm.algorithmId) {
            case "1.2.840.10045.2.1":
                jwk.kty = "EC";
                break;
            case "1.2.840.113549.1.1.1":
                jwk.kty = "RSA";
                break;
        }
        const publicKeyJWK = this.parsedKey.toJSON();
        Object.assign(jwk, publicKeyJWK);
        return jwk;
    }
    fromJSON(json) {
        if ("kty" in json) {
            switch (json.kty.toUpperCase()) {
                case "EC":
                    this.parsedKey = new ECPrivateKey({ json });
                    this.privateKeyAlgorithm = new AlgorithmIdentifier({
                        algorithmId: "1.2.840.10045.2.1",
                        algorithmParams: new ObjectIdentifier({ value: this.parsedKey.namedCurve })
                    });
                    break;
                case "RSA":
                    this.parsedKey = new RSAPrivateKey({ json });
                    this.privateKeyAlgorithm = new AlgorithmIdentifier({
                        algorithmId: "1.2.840.113549.1.1.1",
                        algorithmParams: new Null()
                    });
                    break;
                default:
                    throw new Error(`Invalid value for "kty" parameter: ${json.kty}`);
            }
            this.privateKey = new OctetString({ valueHex: this.parsedKey.toSchema().toBER(false) });
        }
    }
}
PrivateKeyInfo.CLASS_NAME = "PrivateKeyInfo";

const CONTENT_TYPE$1 = "contentType";
const CONTENT_ENCRYPTION_ALGORITHM = "contentEncryptionAlgorithm";
const ENCRYPTED_CONTENT = "encryptedContent";
const CLEAR_PROPS$15 = [
    CONTENT_TYPE$1,
    CONTENT_ENCRYPTION_ALGORITHM,
    ENCRYPTED_CONTENT,
];
class EncryptedContentInfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.contentType = getParametersValue(parameters, CONTENT_TYPE$1, EncryptedContentInfo.defaultValues(CONTENT_TYPE$1));
        this.contentEncryptionAlgorithm = getParametersValue(parameters, CONTENT_ENCRYPTION_ALGORITHM, EncryptedContentInfo.defaultValues(CONTENT_ENCRYPTION_ALGORITHM));
        if (ENCRYPTED_CONTENT in parameters && parameters.encryptedContent) {
            this.encryptedContent = parameters.encryptedContent;
            if ((this.encryptedContent.idBlock.tagClass === 1) &&
                (this.encryptedContent.idBlock.tagNumber === 4)) {
                if (this.encryptedContent.idBlock.isConstructed === false) {
                    const constrString = new OctetString({
                        idBlock: { isConstructed: true },
                        isConstructed: true
                    });
                    let offset = 0;
                    const valueHex = this.encryptedContent.valueBlock.valueHexView.slice().buffer;
                    let length = valueHex.byteLength;
                    const pieceSize = 1024;
                    while (length > 0) {
                        const pieceView = new Uint8Array(valueHex, offset, ((offset + pieceSize) > valueHex.byteLength) ? (valueHex.byteLength - offset) : pieceSize);
                        const _array = new ArrayBuffer(pieceView.length);
                        const _view = new Uint8Array(_array);
                        for (let i = 0; i < _view.length; i++)
                            _view[i] = pieceView[i];
                        constrString.valueBlock.value.push(new OctetString({ valueHex: _array }));
                        length -= pieceView.length;
                        offset += pieceView.length;
                    }
                    this.encryptedContent = constrString;
                }
            }
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case CONTENT_TYPE$1:
                return EMPTY_STRING;
            case CONTENT_ENCRYPTION_ALGORITHM:
                return new AlgorithmIdentifier();
            case ENCRYPTED_CONTENT:
                return new OctetString();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case CONTENT_TYPE$1:
                return (memberValue === EMPTY_STRING);
            case CONTENT_ENCRYPTION_ALGORITHM:
                return ((memberValue.algorithmId === EMPTY_STRING) && (("algorithmParams" in memberValue) === false));
            case ENCRYPTED_CONTENT:
                return (memberValue.isEqual(EncryptedContentInfo.defaultValues(ENCRYPTED_CONTENT)));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.contentType || EMPTY_STRING) }),
                AlgorithmIdentifier.schema(names.contentEncryptionAlgorithm || {}),
                new Choice({
                    value: [
                        new Constructed({
                            name: (names.encryptedContent || EMPTY_STRING),
                            idBlock: {
                                tagClass: 3,
                                tagNumber: 0
                            },
                            value: [
                                new Repeated({
                                    value: new OctetString()
                                })
                            ]
                        }),
                        new Primitive({
                            name: (names.encryptedContent || EMPTY_STRING),
                            idBlock: {
                                tagClass: 3,
                                tagNumber: 0
                            }
                        })
                    ]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$15);
        const asn1 = compareSchema(schema, schema, EncryptedContentInfo.schema({
            names: {
                contentType: CONTENT_TYPE$1,
                contentEncryptionAlgorithm: {
                    names: {
                        blockName: CONTENT_ENCRYPTION_ALGORITHM
                    }
                },
                encryptedContent: ENCRYPTED_CONTENT
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.contentType = asn1.result.contentType.valueBlock.toString();
        this.contentEncryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.contentEncryptionAlgorithm });
        if (ENCRYPTED_CONTENT in asn1.result) {
            this.encryptedContent = asn1.result.encryptedContent;
            this.encryptedContent.idBlock.tagClass = 1;
            this.encryptedContent.idBlock.tagNumber = 4;
        }
    }
    toSchema() {
        const sequenceLengthBlock = {
            isIndefiniteForm: false
        };
        const outputArray = [];
        outputArray.push(new ObjectIdentifier({ value: this.contentType }));
        outputArray.push(this.contentEncryptionAlgorithm.toSchema());
        if (this.encryptedContent) {
            sequenceLengthBlock.isIndefiniteForm = this.encryptedContent.idBlock.isConstructed;
            const encryptedValue = this.encryptedContent;
            encryptedValue.idBlock.tagClass = 3;
            encryptedValue.idBlock.tagNumber = 0;
            encryptedValue.lenBlock.isIndefiniteForm = this.encryptedContent.idBlock.isConstructed;
            outputArray.push(encryptedValue);
        }
        return (new Sequence({
            lenBlock: sequenceLengthBlock,
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            contentType: this.contentType,
            contentEncryptionAlgorithm: this.contentEncryptionAlgorithm.toJSON()
        };
        if (this.encryptedContent) {
            res.encryptedContent = this.encryptedContent.toJSON();
        }
        return res;
    }
    getEncryptedContent() {
        if (!this.encryptedContent) {
            throw new Error("Parameter 'encryptedContent' is undefined");
        }
        return OctetString.prototype.getValue.call(this.encryptedContent);
    }
}
EncryptedContentInfo.CLASS_NAME = "EncryptedContentInfo";

const HASH_ALGORITHM$4 = "hashAlgorithm";
const MASK_GEN_ALGORITHM$1 = "maskGenAlgorithm";
const SALT_LENGTH = "saltLength";
const TRAILER_FIELD = "trailerField";
const CLEAR_PROPS$14 = [
    HASH_ALGORITHM$4,
    MASK_GEN_ALGORITHM$1,
    SALT_LENGTH,
    TRAILER_FIELD
];
class RSASSAPSSParams extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.hashAlgorithm = getParametersValue(parameters, HASH_ALGORITHM$4, RSASSAPSSParams.defaultValues(HASH_ALGORITHM$4));
        this.maskGenAlgorithm = getParametersValue(parameters, MASK_GEN_ALGORITHM$1, RSASSAPSSParams.defaultValues(MASK_GEN_ALGORITHM$1));
        this.saltLength = getParametersValue(parameters, SALT_LENGTH, RSASSAPSSParams.defaultValues(SALT_LENGTH));
        this.trailerField = getParametersValue(parameters, TRAILER_FIELD, RSASSAPSSParams.defaultValues(TRAILER_FIELD));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case HASH_ALGORITHM$4:
                return new AlgorithmIdentifier({
                    algorithmId: "1.3.14.3.2.26",
                    algorithmParams: new Null()
                });
            case MASK_GEN_ALGORITHM$1:
                return new AlgorithmIdentifier({
                    algorithmId: "1.2.840.113549.1.1.8",
                    algorithmParams: (new AlgorithmIdentifier({
                        algorithmId: "1.3.14.3.2.26",
                        algorithmParams: new Null()
                    })).toSchema()
                });
            case SALT_LENGTH:
                return 20;
            case TRAILER_FIELD:
                return 1;
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    optional: true,
                    value: [AlgorithmIdentifier.schema(names.hashAlgorithm || {})]
                }),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    optional: true,
                    value: [AlgorithmIdentifier.schema(names.maskGenAlgorithm || {})]
                }),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 2
                    },
                    optional: true,
                    value: [new Integer({ name: (names.saltLength || EMPTY_STRING) })]
                }),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 3
                    },
                    optional: true,
                    value: [new Integer({ name: (names.trailerField || EMPTY_STRING) })]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$14);
        const asn1 = compareSchema(schema, schema, RSASSAPSSParams.schema({
            names: {
                hashAlgorithm: {
                    names: {
                        blockName: HASH_ALGORITHM$4
                    }
                },
                maskGenAlgorithm: {
                    names: {
                        blockName: MASK_GEN_ALGORITHM$1
                    }
                },
                saltLength: SALT_LENGTH,
                trailerField: TRAILER_FIELD
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (HASH_ALGORITHM$4 in asn1.result)
            this.hashAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.hashAlgorithm });
        if (MASK_GEN_ALGORITHM$1 in asn1.result)
            this.maskGenAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.maskGenAlgorithm });
        if (SALT_LENGTH in asn1.result)
            this.saltLength = asn1.result.saltLength.valueBlock.valueDec;
        if (TRAILER_FIELD in asn1.result)
            this.trailerField = asn1.result.trailerField.valueBlock.valueDec;
    }
    toSchema() {
        const outputArray = [];
        if (!this.hashAlgorithm.isEqual(RSASSAPSSParams.defaultValues(HASH_ALGORITHM$4))) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [this.hashAlgorithm.toSchema()]
            }));
        }
        if (!this.maskGenAlgorithm.isEqual(RSASSAPSSParams.defaultValues(MASK_GEN_ALGORITHM$1))) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                value: [this.maskGenAlgorithm.toSchema()]
            }));
        }
        if (this.saltLength !== RSASSAPSSParams.defaultValues(SALT_LENGTH)) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 2
                },
                value: [new Integer({ value: this.saltLength })]
            }));
        }
        if (this.trailerField !== RSASSAPSSParams.defaultValues(TRAILER_FIELD)) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 3
                },
                value: [new Integer({ value: this.trailerField })]
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {};
        if (!this.hashAlgorithm.isEqual(RSASSAPSSParams.defaultValues(HASH_ALGORITHM$4))) {
            res.hashAlgorithm = this.hashAlgorithm.toJSON();
        }
        if (!this.maskGenAlgorithm.isEqual(RSASSAPSSParams.defaultValues(MASK_GEN_ALGORITHM$1))) {
            res.maskGenAlgorithm = this.maskGenAlgorithm.toJSON();
        }
        if (this.saltLength !== RSASSAPSSParams.defaultValues(SALT_LENGTH)) {
            res.saltLength = this.saltLength;
        }
        if (this.trailerField !== RSASSAPSSParams.defaultValues(TRAILER_FIELD)) {
            res.trailerField = this.trailerField;
        }
        return res;
    }
}
RSASSAPSSParams.CLASS_NAME = "RSASSAPSSParams";

const SALT = "salt";
const ITERATION_COUNT = "iterationCount";
const KEY_LENGTH = "keyLength";
const PRF = "prf";
const CLEAR_PROPS$13 = [
    SALT,
    ITERATION_COUNT,
    KEY_LENGTH,
    PRF
];
class PBKDF2Params extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.salt = getParametersValue(parameters, SALT, PBKDF2Params.defaultValues(SALT));
        this.iterationCount = getParametersValue(parameters, ITERATION_COUNT, PBKDF2Params.defaultValues(ITERATION_COUNT));
        if (KEY_LENGTH in parameters) {
            this.keyLength = getParametersValue(parameters, KEY_LENGTH, PBKDF2Params.defaultValues(KEY_LENGTH));
        }
        if (PRF in parameters) {
            this.prf = getParametersValue(parameters, PRF, PBKDF2Params.defaultValues(PRF));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case SALT:
                return {};
            case ITERATION_COUNT:
                return (-1);
            case KEY_LENGTH:
                return 0;
            case PRF:
                return new AlgorithmIdentifier({
                    algorithmId: "1.3.14.3.2.26",
                    algorithmParams: new Null()
                });
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Choice({
                    value: [
                        new OctetString({ name: (names.saltPrimitive || EMPTY_STRING) }),
                        AlgorithmIdentifier.schema(names.saltConstructed || {})
                    ]
                }),
                new Integer({ name: (names.iterationCount || EMPTY_STRING) }),
                new Integer({
                    name: (names.keyLength || EMPTY_STRING),
                    optional: true
                }),
                AlgorithmIdentifier.schema(names.prf || {
                    names: {
                        optional: true
                    }
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$13);
        const asn1 = compareSchema(schema, schema, PBKDF2Params.schema({
            names: {
                saltPrimitive: SALT,
                saltConstructed: {
                    names: {
                        blockName: SALT
                    }
                },
                iterationCount: ITERATION_COUNT,
                keyLength: KEY_LENGTH,
                prf: {
                    names: {
                        blockName: PRF,
                        optional: true
                    }
                }
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.salt = asn1.result.salt;
        this.iterationCount = asn1.result.iterationCount.valueBlock.valueDec;
        if (KEY_LENGTH in asn1.result)
            this.keyLength = asn1.result.keyLength.valueBlock.valueDec;
        if (PRF in asn1.result)
            this.prf = new AlgorithmIdentifier({ schema: asn1.result.prf });
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(this.salt);
        outputArray.push(new Integer({ value: this.iterationCount }));
        if (KEY_LENGTH in this) {
            if (PBKDF2Params.defaultValues(KEY_LENGTH) !== this.keyLength)
                outputArray.push(new Integer({ value: this.keyLength }));
        }
        if (this.prf) {
            if (PBKDF2Params.defaultValues(PRF).isEqual(this.prf) === false)
                outputArray.push(this.prf.toSchema());
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            salt: this.salt.toJSON(),
            iterationCount: this.iterationCount
        };
        if (KEY_LENGTH in this) {
            if (PBKDF2Params.defaultValues(KEY_LENGTH) !== this.keyLength)
                res.keyLength = this.keyLength;
        }
        if (this.prf) {
            if (PBKDF2Params.defaultValues(PRF).isEqual(this.prf) === false)
                res.prf = this.prf.toJSON();
        }
        return res;
    }
}
PBKDF2Params.CLASS_NAME = "PBKDF2Params";

const KEY_DERIVATION_FUNC = "keyDerivationFunc";
const ENCRYPTION_SCHEME = "encryptionScheme";
const CLEAR_PROPS$12 = [
    KEY_DERIVATION_FUNC,
    ENCRYPTION_SCHEME
];
class PBES2Params extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.keyDerivationFunc = getParametersValue(parameters, KEY_DERIVATION_FUNC, PBES2Params.defaultValues(KEY_DERIVATION_FUNC));
        this.encryptionScheme = getParametersValue(parameters, ENCRYPTION_SCHEME, PBES2Params.defaultValues(ENCRYPTION_SCHEME));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case KEY_DERIVATION_FUNC:
                return new AlgorithmIdentifier();
            case ENCRYPTION_SCHEME:
                return new AlgorithmIdentifier();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                AlgorithmIdentifier.schema(names.keyDerivationFunc || {}),
                AlgorithmIdentifier.schema(names.encryptionScheme || {})
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$12);
        const asn1 = compareSchema(schema, schema, PBES2Params.schema({
            names: {
                keyDerivationFunc: {
                    names: {
                        blockName: KEY_DERIVATION_FUNC
                    }
                },
                encryptionScheme: {
                    names: {
                        blockName: ENCRYPTION_SCHEME
                    }
                }
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.keyDerivationFunc = new AlgorithmIdentifier({ schema: asn1.result.keyDerivationFunc });
        this.encryptionScheme = new AlgorithmIdentifier({ schema: asn1.result.encryptionScheme });
    }
    toSchema() {
        return (new Sequence({
            value: [
                this.keyDerivationFunc.toSchema(),
                this.encryptionScheme.toSchema()
            ]
        }));
    }
    toJSON() {
        return {
            keyDerivationFunc: this.keyDerivationFunc.toJSON(),
            encryptionScheme: this.encryptionScheme.toJSON()
        };
    }
}
PBES2Params.CLASS_NAME = "PBES2Params";

class AbstractCryptoEngine {
    constructor(parameters) {
        this.crypto = parameters.crypto;
        this.subtle = "webkitSubtle" in parameters.crypto
            ? parameters.crypto.webkitSubtle
            : parameters.crypto.subtle;
        this.name = getParametersValue(parameters, "name", EMPTY_STRING);
    }
    encrypt(...args) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.subtle.encrypt(...args);
        });
    }
    decrypt(...args) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.subtle.decrypt(...args);
        });
    }
    sign(...args) {
        return this.subtle.sign(...args);
    }
    verify(...args) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.subtle.verify(...args);
        });
    }
    digest(...args) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.subtle.digest(...args);
        });
    }
    generateKey(...args) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.subtle.generateKey(...args);
        });
    }
    deriveKey(...args) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.subtle.deriveKey(...args);
        });
    }
    deriveBits(...args) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.subtle.deriveBits(...args);
        });
    }
    wrapKey(...args) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.subtle.wrapKey(...args);
        });
    }
    unwrapKey(...args) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.subtle.unwrapKey(...args);
        });
    }
    exportKey(...args) {
        return this.subtle.exportKey(...args);
    }
    importKey(...args) {
        return this.subtle.importKey(...args);
    }
    getRandomValues(array) {
        return this.crypto.getRandomValues(array);
    }
}

function makePKCS12B2Key(cryptoEngine, hashAlgorithm, keyLength, password, salt, iterationCount) {
    return __awaiter(this, void 0, void 0, function* () {
        let u;
        let v;
        const result = [];
        switch (hashAlgorithm.toUpperCase()) {
            case "SHA-1":
                u = 20;
                v = 64;
                break;
            case "SHA-256":
                u = 32;
                v = 64;
                break;
            case "SHA-384":
                u = 48;
                v = 128;
                break;
            case "SHA-512":
                u = 64;
                v = 128;
                break;
            default:
                throw new Error("Unsupported hashing algorithm");
        }
        const passwordViewInitial = new Uint8Array(password);
        const passwordTransformed = new ArrayBuffer((password.byteLength * 2) + 2);
        const passwordTransformedView = new Uint8Array(passwordTransformed);
        for (let i = 0; i < passwordViewInitial.length; i++) {
            passwordTransformedView[i * 2] = 0x00;
            passwordTransformedView[i * 2 + 1] = passwordViewInitial[i];
        }
        passwordTransformedView[passwordTransformedView.length - 2] = 0x00;
        passwordTransformedView[passwordTransformedView.length - 1] = 0x00;
        password = passwordTransformed.slice(0);
        const D = new ArrayBuffer(v);
        const dView = new Uint8Array(D);
        for (let i = 0; i < D.byteLength; i++)
            dView[i] = 3;
        const saltLength = salt.byteLength;
        const sLen = v * Math.ceil(saltLength / v);
        const S = new ArrayBuffer(sLen);
        const sView = new Uint8Array(S);
        const saltView = new Uint8Array(salt);
        for (let i = 0; i < sLen; i++)
            sView[i] = saltView[i % saltLength];
        const passwordLength = password.byteLength;
        const pLen = v * Math.ceil(passwordLength / v);
        const P = new ArrayBuffer(pLen);
        const pView = new Uint8Array(P);
        const passwordView = new Uint8Array(password);
        for (let i = 0; i < pLen; i++)
            pView[i] = passwordView[i % passwordLength];
        const sPlusPLength = S.byteLength + P.byteLength;
        let I = new ArrayBuffer(sPlusPLength);
        let iView = new Uint8Array(I);
        iView.set(sView);
        iView.set(pView, sView.length);
        const c = Math.ceil((keyLength >> 3) / u);
        let internalSequence = Promise.resolve(I);
        for (let i = 0; i <= c; i++) {
            internalSequence = internalSequence.then(_I => {
                const dAndI = new ArrayBuffer(D.byteLength + _I.byteLength);
                const dAndIView = new Uint8Array(dAndI);
                dAndIView.set(dView);
                dAndIView.set(iView, dView.length);
                return dAndI;
            });
            for (let j = 0; j < iterationCount; j++)
                internalSequence = internalSequence.then(roundBuffer => cryptoEngine.digest({ name: hashAlgorithm }, new Uint8Array(roundBuffer)));
            internalSequence = internalSequence.then(roundBuffer => {
                const B = new ArrayBuffer(v);
                const bView = new Uint8Array(B);
                for (let j = 0; j < B.byteLength; j++)
                    bView[j] = roundBuffer[j % roundBuffer.byteLength];
                const k = Math.ceil(saltLength / v) + Math.ceil(passwordLength / v);
                const iRound = [];
                let sliceStart = 0;
                let sliceLength = v;
                for (let j = 0; j < k; j++) {
                    const chunk = Array.from(new Uint8Array(I.slice(sliceStart, sliceStart + sliceLength)));
                    sliceStart += v;
                    if ((sliceStart + v) > I.byteLength)
                        sliceLength = I.byteLength - sliceStart;
                    let x = 0x1ff;
                    for (let l = (B.byteLength - 1); l >= 0; l--) {
                        x >>= 8;
                        x += bView[l] + chunk[l];
                        chunk[l] = (x & 0xff);
                    }
                    iRound.push(...chunk);
                }
                I = new ArrayBuffer(iRound.length);
                iView = new Uint8Array(I);
                iView.set(iRound);
                result.push(...(new Uint8Array(roundBuffer)));
                return I;
            });
        }
        internalSequence = internalSequence.then(() => {
            const resultBuffer = new ArrayBuffer(keyLength >> 3);
            const resultView = new Uint8Array(resultBuffer);
            resultView.set((new Uint8Array(result)).slice(0, keyLength >> 3));
            return resultBuffer;
        });
        return internalSequence;
    });
}
function prepareAlgorithm(data) {
    const res = typeof data === "string"
        ? { name: data }
        : data;
    if ("hash" in res) {
        return Object.assign(Object.assign({}, res), { hash: prepareAlgorithm(res.hash) });
    }
    return res;
}
class CryptoEngine extends AbstractCryptoEngine {
    importKey(format, keyData, algorithm, extractable, keyUsages) {
        var _a, _b, _c, _d, _e, _f;
        return __awaiter(this, void 0, void 0, function* () {
            let jwk = {};
            const alg = prepareAlgorithm(algorithm);
            switch (format.toLowerCase()) {
                case "raw":
                    return this.subtle.importKey("raw", keyData, algorithm, extractable, keyUsages);
                case "spki":
                    {
                        const asn1 = fromBER(BufferSourceConverter.toArrayBuffer(keyData));
                        AsnError.assert(asn1, "keyData");
                        const publicKeyInfo = new PublicKeyInfo();
                        try {
                            publicKeyInfo.fromSchema(asn1.result);
                        }
                        catch (_g) {
                            throw new ArgumentError("Incorrect keyData");
                        }
                        switch (alg.name.toUpperCase()) {
                            case "RSA-PSS":
                                {
                                    if (!alg.hash) {
                                        throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed");
                                    }
                                    switch (alg.hash.name.toUpperCase()) {
                                        case "SHA-1":
                                            jwk.alg = "PS1";
                                            break;
                                        case "SHA-256":
                                            jwk.alg = "PS256";
                                            break;
                                        case "SHA-384":
                                            jwk.alg = "PS384";
                                            break;
                                        case "SHA-512":
                                            jwk.alg = "PS512";
                                            break;
                                        default:
                                            throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`);
                                    }
                                }
                            case "RSASSA-PKCS1-V1_5":
                                {
                                    keyUsages = ["verify"];
                                    jwk.kty = "RSA";
                                    jwk.ext = extractable;
                                    jwk.key_ops = keyUsages;
                                    if (publicKeyInfo.algorithm.algorithmId !== "1.2.840.113549.1.1.1")
                                        throw new Error(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`);
                                    if (!jwk.alg) {
                                        if (!alg.hash) {
                                            throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed");
                                        }
                                        switch (alg.hash.name.toUpperCase()) {
                                            case "SHA-1":
                                                jwk.alg = "RS1";
                                                break;
                                            case "SHA-256":
                                                jwk.alg = "RS256";
                                                break;
                                            case "SHA-384":
                                                jwk.alg = "RS384";
                                                break;
                                            case "SHA-512":
                                                jwk.alg = "RS512";
                                                break;
                                            default:
                                                throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`);
                                        }
                                    }
                                    const publicKeyJSON = publicKeyInfo.toJSON();
                                    Object.assign(jwk, publicKeyJSON);
                                }
                                break;
                            case "ECDSA":
                                keyUsages = ["verify"];
                            case "ECDH":
                                {
                                    jwk = {
                                        kty: "EC",
                                        ext: extractable,
                                        key_ops: keyUsages
                                    };
                                    if (publicKeyInfo.algorithm.algorithmId !== "1.2.840.10045.2.1") {
                                        throw new Error(`Incorrect public key algorithm: ${publicKeyInfo.algorithm.algorithmId}`);
                                    }
                                    const publicKeyJSON = publicKeyInfo.toJSON();
                                    Object.assign(jwk, publicKeyJSON);
                                }
                                break;
                            case "RSA-OAEP":
                                {
                                    jwk.kty = "RSA";
                                    jwk.ext = extractable;
                                    jwk.key_ops = keyUsages;
                                    if (this.name.toLowerCase() === "safari")
                                        jwk.alg = "RSA-OAEP";
                                    else {
                                        if (!alg.hash) {
                                            throw new ParameterError("hash", "algorithm.hash", "Incorrect hash algorithm: Hash algorithm is missed");
                                        }
                                        switch (alg.hash.name.toUpperCase()) {
                                            case "SHA-1":
                                                jwk.alg = "RSA-OAEP";
                                                break;
                                            case "SHA-256":
                                                jwk.alg = "RSA-OAEP-256";
                                                break;
                                            case "SHA-384":
                                                jwk.alg = "RSA-OAEP-384";
                                                break;
                                            case "SHA-512":
                                                jwk.alg = "RSA-OAEP-512";
                                                break;
                                            default:
                                                throw new Error(`Incorrect hash algorithm: ${alg.hash.name.toUpperCase()}`);
                                        }
                                    }
                                    const publicKeyJSON = publicKeyInfo.toJSON();
                                    Object.assign(jwk, publicKeyJSON);
                                }
                                break;
                            case "RSAES-PKCS1-V1_5":
                                {
                                    jwk.kty = "RSA";
                                    jwk.ext = extractable;
                                    jwk.key_ops = keyUsages;
                                    jwk.alg = "PS1";
                                    const publicKeyJSON = publicKeyInfo.toJSON();
                                    Object.assign(jwk, publicKeyJSON);
                                }
                                break;
                            default:
                                throw new Error(`Incorrect algorithm name: ${alg.name.toUpperCase()}`);
                        }
                    }
                    break;
                case "pkcs8":
                    {
                        const privateKeyInfo = new PrivateKeyInfo();
                        const asn1 = fromBER(BufferSourceConverter.toArrayBuffer(keyData));
                        AsnError.assert(asn1, "keyData");
                        try {
                            privateKeyInfo.fromSchema(asn1.result);
                        }
                        catch (ex) {
                            throw new Error("Incorrect keyData");
                        }
                        if (!privateKeyInfo.parsedKey)
                            throw new Error("Incorrect keyData");
                        switch (alg.name.toUpperCase()) {
                            case "RSA-PSS":
                                {
                                    switch ((_a = alg.hash) === null || _a === void 0 ? void 0 : _a.name.toUpperCase()) {
                                        case "SHA-1":
                                            jwk.alg = "PS1";
                                            break;
                                        case "SHA-256":
                                            jwk.alg = "PS256";
                                            break;
                                        case "SHA-384":
                                            jwk.alg = "PS384";
                                            break;
                                        case "SHA-512":
                                            jwk.alg = "PS512";
                                            break;
                                        default:
                                            throw new Error(`Incorrect hash algorithm: ${(_b = alg.hash) === null || _b === void 0 ? void 0 : _b.name.toUpperCase()}`);
                                    }
                                }
                            case "RSASSA-PKCS1-V1_5":
                                {
                                    keyUsages = ["sign"];
                                    jwk.kty = "RSA";
                                    jwk.ext = extractable;
                                    jwk.key_ops = keyUsages;
                                    if (privateKeyInfo.privateKeyAlgorithm.algorithmId !== "1.2.840.113549.1.1.1")
                                        throw new Error(`Incorrect private key algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`);
                                    if (("alg" in jwk) === false) {
                                        switch ((_c = alg.hash) === null || _c === void 0 ? void 0 : _c.name.toUpperCase()) {
                                            case "SHA-1":
                                                jwk.alg = "RS1";
                                                break;
                                            case "SHA-256":
                                                jwk.alg = "RS256";
                                                break;
                                            case "SHA-384":
                                                jwk.alg = "RS384";
                                                break;
                                            case "SHA-512":
                                                jwk.alg = "RS512";
                                                break;
                                            default:
                                                throw new Error(`Incorrect hash algorithm: ${(_d = alg.hash) === null || _d === void 0 ? void 0 : _d.name.toUpperCase()}`);
                                        }
                                    }
                                    const privateKeyJSON = privateKeyInfo.toJSON();
                                    Object.assign(jwk, privateKeyJSON);
                                }
                                break;
                            case "ECDSA":
                                keyUsages = ["sign"];
                            case "ECDH":
                                {
                                    jwk = {
                                        kty: "EC",
                                        ext: extractable,
                                        key_ops: keyUsages
                                    };
                                    if (privateKeyInfo.privateKeyAlgorithm.algorithmId !== "1.2.840.10045.2.1")
                                        throw new Error(`Incorrect algorithm: ${privateKeyInfo.privateKeyAlgorithm.algorithmId}`);
                                    const privateKeyJSON = privateKeyInfo.toJSON();
                                    Object.assign(jwk, privateKeyJSON);
                                }
                                break;
                            case "RSA-OAEP":
                                {
                                    jwk.kty = "RSA";
                                    jwk.ext = extractable;
                                    jwk.key_ops = keyUsages;
                                    if (this.name.toLowerCase() === "safari")
                                        jwk.alg = "RSA-OAEP";
                                    else {
                                        switch ((_e = alg.hash) === null || _e === void 0 ? void 0 : _e.name.toUpperCase()) {
                                            case "SHA-1":
                                                jwk.alg = "RSA-OAEP";
                                                break;
                                            case "SHA-256":
                                                jwk.alg = "RSA-OAEP-256";
                                                break;
                                            case "SHA-384":
                                                jwk.alg = "RSA-OAEP-384";
                                                break;
                                            case "SHA-512":
                                                jwk.alg = "RSA-OAEP-512";
                                                break;
                                            default:
                                                throw new Error(`Incorrect hash algorithm: ${(_f = alg.hash) === null || _f === void 0 ? void 0 : _f.name.toUpperCase()}`);
                                        }
                                    }
                                    const privateKeyJSON = privateKeyInfo.toJSON();
                                    Object.assign(jwk, privateKeyJSON);
                                }
                                break;
                            case "RSAES-PKCS1-V1_5":
                                {
                                    keyUsages = ["decrypt"];
                                    jwk.kty = "RSA";
                                    jwk.ext = extractable;
                                    jwk.key_ops = keyUsages;
                                    jwk.alg = "PS1";
                                    const privateKeyJSON = privateKeyInfo.toJSON();
                                    Object.assign(jwk, privateKeyJSON);
                                }
                                break;
                            default:
                                throw new Error(`Incorrect algorithm name: ${alg.name.toUpperCase()}`);
                        }
                    }
                    break;
                case "jwk":
                    jwk = keyData;
                    break;
                default:
                    throw new Error(`Incorrect format: ${format}`);
            }
            if (this.name.toLowerCase() === "safari") {
                try {
                    return this.subtle.importKey("jwk", stringToArrayBuffer(JSON.stringify(jwk)), algorithm, extractable, keyUsages);
                }
                catch (_h) {
                    return this.subtle.importKey("jwk", jwk, algorithm, extractable, keyUsages);
                }
            }
            return this.subtle.importKey("jwk", jwk, algorithm, extractable, keyUsages);
        });
    }
    exportKey(format, key) {
        return __awaiter(this, void 0, void 0, function* () {
            let jwk = yield this.subtle.exportKey("jwk", key);
            if (this.name.toLowerCase() === "safari") {
                if (jwk instanceof ArrayBuffer) {
                    jwk = JSON.parse(arrayBufferToString(jwk));
                }
            }
            switch (format.toLowerCase()) {
                case "raw":
                    return this.subtle.exportKey("raw", key);
                case "spki": {
                    const publicKeyInfo = new PublicKeyInfo();
                    try {
                        publicKeyInfo.fromJSON(jwk);
                    }
                    catch (ex) {
                        throw new Error("Incorrect key data");
                    }
                    return publicKeyInfo.toSchema().toBER(false);
                }
                case "pkcs8": {
                    const privateKeyInfo = new PrivateKeyInfo();
                    try {
                        privateKeyInfo.fromJSON(jwk);
                    }
                    catch (ex) {
                        throw new Error("Incorrect key data");
                    }
                    return privateKeyInfo.toSchema().toBER(false);
                }
                case "jwk":
                    return jwk;
                default:
                    throw new Error(`Incorrect format: ${format}`);
            }
        });
    }
    convert(inputFormat, outputFormat, keyData, algorithm, extractable, keyUsages) {
        return __awaiter(this, void 0, void 0, function* () {
            if (inputFormat.toLowerCase() === outputFormat.toLowerCase()) {
                return keyData;
            }
            const key = yield this.importKey(inputFormat, keyData, algorithm, extractable, keyUsages);
            return this.exportKey(outputFormat, key);
        });
    }
    getAlgorithmByOID(oid, safety = false, target) {
        switch (oid) {
            case "1.2.840.113549.1.1.1":
                return {
                    name: "RSAES-PKCS1-v1_5"
                };
            case "1.2.840.113549.1.1.5":
                return {
                    name: "RSASSA-PKCS1-v1_5",
                    hash: {
                        name: "SHA-1"
                    }
                };
            case "1.2.840.113549.1.1.11":
                return {
                    name: "RSASSA-PKCS1-v1_5",
                    hash: {
                        name: "SHA-256"
                    }
                };
            case "1.2.840.113549.1.1.12":
                return {
                    name: "RSASSA-PKCS1-v1_5",
                    hash: {
                        name: "SHA-384"
                    }
                };
            case "1.2.840.113549.1.1.13":
                return {
                    name: "RSASSA-PKCS1-v1_5",
                    hash: {
                        name: "SHA-512"
                    }
                };
            case "1.2.840.113549.1.1.10":
                return {
                    name: "RSA-PSS"
                };
            case "1.2.840.113549.1.1.7":
                return {
                    name: "RSA-OAEP"
                };
            case "1.2.840.10045.2.1":
            case "1.2.840.10045.4.1":
                return {
                    name: "ECDSA",
                    hash: {
                        name: "SHA-1"
                    }
                };
            case "1.2.840.10045.4.3.2":
                return {
                    name: "ECDSA",
                    hash: {
                        name: "SHA-256"
                    }
                };
            case "1.2.840.10045.4.3.3":
                return {
                    name: "ECDSA",
                    hash: {
                        name: "SHA-384"
                    }
                };
            case "1.2.840.10045.4.3.4":
                return {
                    name: "ECDSA",
                    hash: {
                        name: "SHA-512"
                    }
                };
            case "1.3.133.16.840.63.0.2":
                return {
                    name: "ECDH",
                    kdf: "SHA-1"
                };
            case "1.3.132.1.11.1":
                return {
                    name: "ECDH",
                    kdf: "SHA-256"
                };
            case "1.3.132.1.11.2":
                return {
                    name: "ECDH",
                    kdf: "SHA-384"
                };
            case "1.3.132.1.11.3":
                return {
                    name: "ECDH",
                    kdf: "SHA-512"
                };
            case "2.16.840.1.101.3.4.1.2":
                return {
                    name: "AES-CBC",
                    length: 128
                };
            case "2.16.840.1.101.3.4.1.22":
                return {
                    name: "AES-CBC",
                    length: 192
                };
            case "2.16.840.1.101.3.4.1.42":
                return {
                    name: "AES-CBC",
                    length: 256
                };
            case "2.16.840.1.101.3.4.1.6":
                return {
                    name: "AES-GCM",
                    length: 128
                };
            case "2.16.840.1.101.3.4.1.26":
                return {
                    name: "AES-GCM",
                    length: 192
                };
            case "2.16.840.1.101.3.4.1.46":
                return {
                    name: "AES-GCM",
                    length: 256
                };
            case "2.16.840.1.101.3.4.1.4":
                return {
                    name: "AES-CFB",
                    length: 128
                };
            case "2.16.840.1.101.3.4.1.24":
                return {
                    name: "AES-CFB",
                    length: 192
                };
            case "2.16.840.1.101.3.4.1.44":
                return {
                    name: "AES-CFB",
                    length: 256
                };
            case "2.16.840.1.101.3.4.1.5":
                return {
                    name: "AES-KW",
                    length: 128
                };
            case "2.16.840.1.101.3.4.1.25":
                return {
                    name: "AES-KW",
                    length: 192
                };
            case "2.16.840.1.101.3.4.1.45":
                return {
                    name: "AES-KW",
                    length: 256
                };
            case "1.2.840.113549.2.7":
                return {
                    name: "HMAC",
                    hash: {
                        name: "SHA-1"
                    }
                };
            case "1.2.840.113549.2.9":
                return {
                    name: "HMAC",
                    hash: {
                        name: "SHA-256"
                    }
                };
            case "1.2.840.113549.2.10":
                return {
                    name: "HMAC",
                    hash: {
                        name: "SHA-384"
                    }
                };
            case "1.2.840.113549.2.11":
                return {
                    name: "HMAC",
                    hash: {
                        name: "SHA-512"
                    }
                };
            case "1.2.840.113549.1.9.16.3.5":
                return {
                    name: "DH"
                };
            case "1.3.14.3.2.26":
                return {
                    name: "SHA-1"
                };
            case "2.16.840.1.101.3.4.2.1":
                return {
                    name: "SHA-256"
                };
            case "2.16.840.1.101.3.4.2.2":
                return {
                    name: "SHA-384"
                };
            case "2.16.840.1.101.3.4.2.3":
                return {
                    name: "SHA-512"
                };
            case "1.2.840.113549.1.5.12":
                return {
                    name: "PBKDF2"
                };
            case "1.2.840.10045.3.1.7":
                return {
                    name: "P-256"
                };
            case "1.3.132.0.34":
                return {
                    name: "P-384"
                };
            case "1.3.132.0.35":
                return {
                    name: "P-521"
                };
        }
        if (safety) {
            throw new Error(`Unsupported algorithm identifier ${target ? `for ${target} ` : EMPTY_STRING}: ${oid}`);
        }
        return {};
    }
    getOIDByAlgorithm(algorithm, safety = false, target) {
        let result = EMPTY_STRING;
        switch (algorithm.name.toUpperCase()) {
            case "RSAES-PKCS1-V1_5":
                result = "1.2.840.113549.1.1.1";
                break;
            case "RSASSA-PKCS1-V1_5":
                switch (algorithm.hash.name.toUpperCase()) {
                    case "SHA-1":
                        result = "1.2.840.113549.1.1.5";
                        break;
                    case "SHA-256":
                        result = "1.2.840.113549.1.1.11";
                        break;
                    case "SHA-384":
                        result = "1.2.840.113549.1.1.12";
                        break;
                    case "SHA-512":
                        result = "1.2.840.113549.1.1.13";
                        break;
                }
                break;
            case "RSA-PSS":
                result = "1.2.840.113549.1.1.10";
                break;
            case "RSA-OAEP":
                result = "1.2.840.113549.1.1.7";
                break;
            case "ECDSA":
                switch (algorithm.hash.name.toUpperCase()) {
                    case "SHA-1":
                        result = "1.2.840.10045.4.1";
                        break;
                    case "SHA-256":
                        result = "1.2.840.10045.4.3.2";
                        break;
                    case "SHA-384":
                        result = "1.2.840.10045.4.3.3";
                        break;
                    case "SHA-512":
                        result = "1.2.840.10045.4.3.4";
                        break;
                }
                break;
            case "ECDH":
                switch (algorithm.kdf.toUpperCase()) {
                    case "SHA-1":
                        result = "1.3.133.16.840.63.0.2";
                        break;
                    case "SHA-256":
                        result = "1.3.132.1.11.1";
                        break;
                    case "SHA-384":
                        result = "1.3.132.1.11.2";
                        break;
                    case "SHA-512":
                        result = "1.3.132.1.11.3";
                        break;
                }
                break;
            case "AES-CTR":
                break;
            case "AES-CBC":
                switch (algorithm.length) {
                    case 128:
                        result = "2.16.840.1.101.3.4.1.2";
                        break;
                    case 192:
                        result = "2.16.840.1.101.3.4.1.22";
                        break;
                    case 256:
                        result = "2.16.840.1.101.3.4.1.42";
                        break;
                }
                break;
            case "AES-CMAC":
                break;
            case "AES-GCM":
                switch (algorithm.length) {
                    case 128:
                        result = "2.16.840.1.101.3.4.1.6";
                        break;
                    case 192:
                        result = "2.16.840.1.101.3.4.1.26";
                        break;
                    case 256:
                        result = "2.16.840.1.101.3.4.1.46";
                        break;
                }
                break;
            case "AES-CFB":
                switch (algorithm.length) {
                    case 128:
                        result = "2.16.840.1.101.3.4.1.4";
                        break;
                    case 192:
                        result = "2.16.840.1.101.3.4.1.24";
                        break;
                    case 256:
                        result = "2.16.840.1.101.3.4.1.44";
                        break;
                }
                break;
            case "AES-KW":
                switch (algorithm.length) {
                    case 128:
                        result = "2.16.840.1.101.3.4.1.5";
                        break;
                    case 192:
                        result = "2.16.840.1.101.3.4.1.25";
                        break;
                    case 256:
                        result = "2.16.840.1.101.3.4.1.45";
                        break;
                }
                break;
            case "HMAC":
                switch (algorithm.hash.name.toUpperCase()) {
                    case "SHA-1":
                        result = "1.2.840.113549.2.7";
                        break;
                    case "SHA-256":
                        result = "1.2.840.113549.2.9";
                        break;
                    case "SHA-384":
                        result = "1.2.840.113549.2.10";
                        break;
                    case "SHA-512":
                        result = "1.2.840.113549.2.11";
                        break;
                }
                break;
            case "DH":
                result = "1.2.840.113549.1.9.16.3.5";
                break;
            case "SHA-1":
                result = "1.3.14.3.2.26";
                break;
            case "SHA-256":
                result = "2.16.840.1.101.3.4.2.1";
                break;
            case "SHA-384":
                result = "2.16.840.1.101.3.4.2.2";
                break;
            case "SHA-512":
                result = "2.16.840.1.101.3.4.2.3";
                break;
            case "CONCAT":
                break;
            case "HKDF":
                break;
            case "PBKDF2":
                result = "1.2.840.113549.1.5.12";
                break;
            case "P-256":
                result = "1.2.840.10045.3.1.7";
                break;
            case "P-384":
                result = "1.3.132.0.34";
                break;
            case "P-521":
                result = "1.3.132.0.35";
                break;
        }
        if (!result && safety) {
            throw new Error(`Unsupported algorithm ${target ? `for ${target} ` : EMPTY_STRING}: ${algorithm.name}`);
        }
        return result;
    }
    getAlgorithmParameters(algorithmName, operation) {
        let result = {
            algorithm: {},
            usages: []
        };
        switch (algorithmName.toUpperCase()) {
            case "RSAES-PKCS1-V1_5":
            case "RSASSA-PKCS1-V1_5":
                switch (operation.toLowerCase()) {
                    case "generatekey":
                        result = {
                            algorithm: {
                                name: "RSASSA-PKCS1-v1_5",
                                modulusLength: 2048,
                                publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
                                hash: {
                                    name: "SHA-256"
                                }
                            },
                            usages: ["sign", "verify"]
                        };
                        break;
                    case "verify":
                    case "sign":
                    case "importkey":
                        result = {
                            algorithm: {
                                name: "RSASSA-PKCS1-v1_5",
                                hash: {
                                    name: "SHA-256"
                                }
                            },
                            usages: ["verify"]
                        };
                        break;
                    case "exportkey":
                    default:
                        return {
                            algorithm: {
                                name: "RSASSA-PKCS1-v1_5"
                            },
                            usages: []
                        };
                }
                break;
            case "RSA-PSS":
                switch (operation.toLowerCase()) {
                    case "sign":
                    case "verify":
                        result = {
                            algorithm: {
                                name: "RSA-PSS",
                                hash: {
                                    name: "SHA-1"
                                },
                                saltLength: 20
                            },
                            usages: ["sign", "verify"]
                        };
                        break;
                    case "generatekey":
                        result = {
                            algorithm: {
                                name: "RSA-PSS",
                                modulusLength: 2048,
                                publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
                                hash: {
                                    name: "SHA-1"
                                }
                            },
                            usages: ["sign", "verify"]
                        };
                        break;
                    case "importkey":
                        result = {
                            algorithm: {
                                name: "RSA-PSS",
                                hash: {
                                    name: "SHA-1"
                                }
                            },
                            usages: ["verify"]
                        };
                        break;
                    case "exportkey":
                    default:
                        return {
                            algorithm: {
                                name: "RSA-PSS"
                            },
                            usages: []
                        };
                }
                break;
            case "RSA-OAEP":
                switch (operation.toLowerCase()) {
                    case "encrypt":
                    case "decrypt":
                        result = {
                            algorithm: {
                                name: "RSA-OAEP"
                            },
                            usages: ["encrypt", "decrypt"]
                        };
                        break;
                    case "generatekey":
                        result = {
                            algorithm: {
                                name: "RSA-OAEP",
                                modulusLength: 2048,
                                publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
                                hash: {
                                    name: "SHA-256"
                                }
                            },
                            usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
                        };
                        break;
                    case "importkey":
                        result = {
                            algorithm: {
                                name: "RSA-OAEP",
                                hash: {
                                    name: "SHA-256"
                                }
                            },
                            usages: ["encrypt"]
                        };
                        break;
                    case "exportkey":
                    default:
                        return {
                            algorithm: {
                                name: "RSA-OAEP"
                            },
                            usages: []
                        };
                }
                break;
            case "ECDSA":
                switch (operation.toLowerCase()) {
                    case "generatekey":
                        result = {
                            algorithm: {
                                name: "ECDSA",
                                namedCurve: "P-256"
                            },
                            usages: ["sign", "verify"]
                        };
                        break;
                    case "importkey":
                        result = {
                            algorithm: {
                                name: "ECDSA",
                                namedCurve: "P-256"
                            },
                            usages: ["verify"]
                        };
                        break;
                    case "verify":
                    case "sign":
                        result = {
                            algorithm: {
                                name: "ECDSA",
                                hash: {
                                    name: "SHA-256"
                                }
                            },
                            usages: ["sign"]
                        };
                        break;
                    default:
                        return {
                            algorithm: {
                                name: "ECDSA"
                            },
                            usages: []
                        };
                }
                break;
            case "ECDH":
                switch (operation.toLowerCase()) {
                    case "exportkey":
                    case "importkey":
                    case "generatekey":
                        result = {
                            algorithm: {
                                name: "ECDH",
                                namedCurve: "P-256"
                            },
                            usages: ["deriveKey", "deriveBits"]
                        };
                        break;
                    case "derivekey":
                    case "derivebits":
                        result = {
                            algorithm: {
                                name: "ECDH",
                                namedCurve: "P-256",
                                public: []
                            },
                            usages: ["encrypt", "decrypt"]
                        };
                        break;
                    default:
                        return {
                            algorithm: {
                                name: "ECDH"
                            },
                            usages: []
                        };
                }
                break;
            case "AES-CTR":
                switch (operation.toLowerCase()) {
                    case "importkey":
                    case "exportkey":
                    case "generatekey":
                        result = {
                            algorithm: {
                                name: "AES-CTR",
                                length: 256
                            },
                            usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
                        };
                        break;
                    case "decrypt":
                    case "encrypt":
                        result = {
                            algorithm: {
                                name: "AES-CTR",
                                counter: new Uint8Array(16),
                                length: 10
                            },
                            usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
                        };
                        break;
                    default:
                        return {
                            algorithm: {
                                name: "AES-CTR"
                            },
                            usages: []
                        };
                }
                break;
            case "AES-CBC":
                switch (operation.toLowerCase()) {
                    case "importkey":
                    case "exportkey":
                    case "generatekey":
                        result = {
                            algorithm: {
                                name: "AES-CBC",
                                length: 256
                            },
                            usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
                        };
                        break;
                    case "decrypt":
                    case "encrypt":
                        result = {
                            algorithm: {
                                name: "AES-CBC",
                                iv: this.getRandomValues(new Uint8Array(16))
                            },
                            usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
                        };
                        break;
                    default:
                        return {
                            algorithm: {
                                name: "AES-CBC"
                            },
                            usages: []
                        };
                }
                break;
            case "AES-GCM":
                switch (operation.toLowerCase()) {
                    case "importkey":
                    case "exportkey":
                    case "generatekey":
                        result = {
                            algorithm: {
                                name: "AES-GCM",
                                length: 256
                            },
                            usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
                        };
                        break;
                    case "decrypt":
                    case "encrypt":
                        result = {
                            algorithm: {
                                name: "AES-GCM",
                                iv: this.getRandomValues(new Uint8Array(16))
                            },
                            usages: ["encrypt", "decrypt", "wrapKey", "unwrapKey"]
                        };
                        break;
                    default:
                        return {
                            algorithm: {
                                name: "AES-GCM"
                            },
                            usages: []
                        };
                }
                break;
            case "AES-KW":
                switch (operation.toLowerCase()) {
                    case "importkey":
                    case "exportkey":
                    case "generatekey":
                    case "wrapkey":
                    case "unwrapkey":
                        result = {
                            algorithm: {
                                name: "AES-KW",
                                length: 256
                            },
                            usages: ["wrapKey", "unwrapKey"]
                        };
                        break;
                    default:
                        return {
                            algorithm: {
                                name: "AES-KW"
                            },
                            usages: []
                        };
                }
                break;
            case "HMAC":
                switch (operation.toLowerCase()) {
                    case "sign":
                    case "verify":
                        result = {
                            algorithm: {
                                name: "HMAC"
                            },
                            usages: ["sign", "verify"]
                        };
                        break;
                    case "importkey":
                    case "exportkey":
                    case "generatekey":
                        result = {
                            algorithm: {
                                name: "HMAC",
                                length: 32,
                                hash: {
                                    name: "SHA-256"
                                }
                            },
                            usages: ["sign", "verify"]
                        };
                        break;
                    default:
                        return {
                            algorithm: {
                                name: "HMAC"
                            },
                            usages: []
                        };
                }
                break;
            case "HKDF":
                switch (operation.toLowerCase()) {
                    case "derivekey":
                        result = {
                            algorithm: {
                                name: "HKDF",
                                hash: "SHA-256",
                                salt: new Uint8Array([]),
                                info: new Uint8Array([])
                            },
                            usages: ["encrypt", "decrypt"]
                        };
                        break;
                    default:
                        return {
                            algorithm: {
                                name: "HKDF"
                            },
                            usages: []
                        };
                }
                break;
            case "PBKDF2":
                switch (operation.toLowerCase()) {
                    case "derivekey":
                        result = {
                            algorithm: {
                                name: "PBKDF2",
                                hash: { name: "SHA-256" },
                                salt: new Uint8Array([]),
                                iterations: 10000
                            },
                            usages: ["encrypt", "decrypt"]
                        };
                        break;
                    default:
                        return {
                            algorithm: {
                                name: "PBKDF2"
                            },
                            usages: []
                        };
                }
                break;
        }
        return result;
    }
    getHashAlgorithm(signatureAlgorithm) {
        let result = EMPTY_STRING;
        switch (signatureAlgorithm.algorithmId) {
            case "1.2.840.10045.4.1":
            case "1.2.840.113549.1.1.5":
                result = "SHA-1";
                break;
            case "1.2.840.10045.4.3.2":
            case "1.2.840.113549.1.1.11":
                result = "SHA-256";
                break;
            case "1.2.840.10045.4.3.3":
            case "1.2.840.113549.1.1.12":
                result = "SHA-384";
                break;
            case "1.2.840.10045.4.3.4":
            case "1.2.840.113549.1.1.13":
                result = "SHA-512";
                break;
            case "1.2.840.113549.1.1.10":
                {
                    try {
                        const params = new RSASSAPSSParams({ schema: signatureAlgorithm.algorithmParams });
                        if (params.hashAlgorithm) {
                            const algorithm = this.getAlgorithmByOID(params.hashAlgorithm.algorithmId);
                            if ("name" in algorithm) {
                                result = algorithm.name;
                            }
                            else {
                                return EMPTY_STRING;
                            }
                        }
                        else
                            result = "SHA-1";
                    }
                    catch (_a) {
                    }
                }
                break;
        }
        return result;
    }
    encryptEncryptedContentInfo(parameters) {
        return __awaiter(this, void 0, void 0, function* () {
            ParameterError.assert(parameters, "password", "contentEncryptionAlgorithm", "hmacHashAlgorithm", "iterationCount", "contentToEncrypt", "contentToEncrypt", "contentType");
            const contentEncryptionOID = this.getOIDByAlgorithm(parameters.contentEncryptionAlgorithm, true, "contentEncryptionAlgorithm");
            const pbkdf2OID = this.getOIDByAlgorithm({
                name: "PBKDF2"
            }, true, "PBKDF2");
            const hmacOID = this.getOIDByAlgorithm({
                name: "HMAC",
                hash: {
                    name: parameters.hmacHashAlgorithm
                }
            }, true, "hmacHashAlgorithm");
            const ivBuffer = new ArrayBuffer(16);
            const ivView = new Uint8Array(ivBuffer);
            this.getRandomValues(ivView);
            const saltBuffer = new ArrayBuffer(64);
            const saltView = new Uint8Array(saltBuffer);
            this.getRandomValues(saltView);
            const contentView = new Uint8Array(parameters.contentToEncrypt);
            const pbkdf2Params = new PBKDF2Params({
                salt: new OctetString({ valueHex: saltBuffer }),
                iterationCount: parameters.iterationCount,
                prf: new AlgorithmIdentifier({
                    algorithmId: hmacOID,
                    algorithmParams: new Null()
                })
            });
            const passwordView = new Uint8Array(parameters.password);
            const pbkdfKey = yield this.importKey("raw", passwordView, "PBKDF2", false, ["deriveKey"]);
            const derivedKey = yield this.deriveKey({
                name: "PBKDF2",
                hash: {
                    name: parameters.hmacHashAlgorithm
                },
                salt: saltView,
                iterations: parameters.iterationCount
            }, pbkdfKey, parameters.contentEncryptionAlgorithm, false, ["encrypt"]);
            const encryptedData = yield this.encrypt({
                name: parameters.contentEncryptionAlgorithm.name,
                iv: ivView
            }, derivedKey, contentView);
            const pbes2Parameters = new PBES2Params({
                keyDerivationFunc: new AlgorithmIdentifier({
                    algorithmId: pbkdf2OID,
                    algorithmParams: pbkdf2Params.toSchema()
                }),
                encryptionScheme: new AlgorithmIdentifier({
                    algorithmId: contentEncryptionOID,
                    algorithmParams: new OctetString({ valueHex: ivBuffer })
                })
            });
            return new EncryptedContentInfo({
                contentType: parameters.contentType,
                contentEncryptionAlgorithm: new AlgorithmIdentifier({
                    algorithmId: "1.2.840.113549.1.5.13",
                    algorithmParams: pbes2Parameters.toSchema()
                }),
                encryptedContent: new OctetString({ valueHex: encryptedData })
            });
        });
    }
    decryptEncryptedContentInfo(parameters) {
        return __awaiter(this, void 0, void 0, function* () {
            ParameterError.assert(parameters, "password", "encryptedContentInfo");
            if (parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId !== "1.2.840.113549.1.5.13")
                throw new Error(`Unknown "contentEncryptionAlgorithm": ${parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId}`);
            let pbes2Parameters;
            try {
                pbes2Parameters = new PBES2Params({ schema: parameters.encryptedContentInfo.contentEncryptionAlgorithm.algorithmParams });
            }
            catch (ex) {
                throw new Error("Incorrectly encoded \"pbes2Parameters\"");
            }
            let pbkdf2Params;
            try {
                pbkdf2Params = new PBKDF2Params({ schema: pbes2Parameters.keyDerivationFunc.algorithmParams });
            }
            catch (ex) {
                throw new Error("Incorrectly encoded \"pbkdf2Params\"");
            }
            const contentEncryptionAlgorithm = this.getAlgorithmByOID(pbes2Parameters.encryptionScheme.algorithmId, true);
            const ivBuffer = pbes2Parameters.encryptionScheme.algorithmParams.valueBlock.valueHex;
            const ivView = new Uint8Array(ivBuffer);
            const saltBuffer = pbkdf2Params.salt.valueBlock.valueHex;
            const saltView = new Uint8Array(saltBuffer);
            const iterationCount = pbkdf2Params.iterationCount;
            let hmacHashAlgorithm = "SHA-1";
            if (pbkdf2Params.prf) {
                const algorithm = this.getAlgorithmByOID(pbkdf2Params.prf.algorithmId, true);
                hmacHashAlgorithm = algorithm.hash.name;
            }
            const pbkdfKey = yield this.importKey("raw", parameters.password, "PBKDF2", false, ["deriveKey"]);
            const result = yield this.deriveKey({
                name: "PBKDF2",
                hash: {
                    name: hmacHashAlgorithm
                },
                salt: saltView,
                iterations: iterationCount
            }, pbkdfKey, contentEncryptionAlgorithm, false, ["decrypt"]);
            const dataBuffer = parameters.encryptedContentInfo.getEncryptedContent();
            return this.decrypt({
                name: contentEncryptionAlgorithm.name,
                iv: ivView
            }, result, dataBuffer);
        });
    }
    stampDataWithPassword(parameters) {
        return __awaiter(this, void 0, void 0, function* () {
            if ((parameters instanceof Object) === false)
                throw new Error("Parameters must have type \"Object\"");
            ParameterError.assert(parameters, "password", "hashAlgorithm", "iterationCount", "salt", "contentToStamp");
            let length;
            switch (parameters.hashAlgorithm.toLowerCase()) {
                case "sha-1":
                    length = 160;
                    break;
                case "sha-256":
                    length = 256;
                    break;
                case "sha-384":
                    length = 384;
                    break;
                case "sha-512":
                    length = 512;
                    break;
                default:
                    throw new Error(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`);
            }
            const hmacAlgorithm = {
                name: "HMAC",
                length,
                hash: {
                    name: parameters.hashAlgorithm
                }
            };
            const pkcsKey = yield makePKCS12B2Key(this, parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount);
            const hmacKey = yield this.importKey("raw", new Uint8Array(pkcsKey), hmacAlgorithm, false, ["sign"]);
            return this.sign(hmacAlgorithm, hmacKey, new Uint8Array(parameters.contentToStamp));
        });
    }
    verifyDataStampedWithPassword(parameters) {
        return __awaiter(this, void 0, void 0, function* () {
            ParameterError.assert(parameters, "password", "hashAlgorithm", "salt", "iterationCount", "contentToVerify", "signatureToVerify");
            let length = 0;
            switch (parameters.hashAlgorithm.toLowerCase()) {
                case "sha-1":
                    length = 160;
                    break;
                case "sha-256":
                    length = 256;
                    break;
                case "sha-384":
                    length = 384;
                    break;
                case "sha-512":
                    length = 512;
                    break;
                default:
                    throw new Error(`Incorrect "parameters.hashAlgorithm" parameter: ${parameters.hashAlgorithm}`);
            }
            const hmacAlgorithm = {
                name: "HMAC",
                length,
                hash: {
                    name: parameters.hashAlgorithm
                }
            };
            const pkcsKey = yield makePKCS12B2Key(this, parameters.hashAlgorithm, length, parameters.password, parameters.salt, parameters.iterationCount);
            const hmacKey = yield this.importKey("raw", new Uint8Array(pkcsKey), hmacAlgorithm, false, ["verify"]);
            return this.verify(hmacAlgorithm, hmacKey, new Uint8Array(parameters.signatureToVerify), new Uint8Array(parameters.contentToVerify));
        });
    }
    getSignatureParameters(privateKey, hashAlgorithm = "SHA-1") {
        return __awaiter(this, void 0, void 0, function* () {
            this.getOIDByAlgorithm({ name: hashAlgorithm }, true, "hashAlgorithm");
            const signatureAlgorithm = new AlgorithmIdentifier();
            const parameters = this.getAlgorithmParameters(privateKey.algorithm.name, "sign");
            if (!Object.keys(parameters.algorithm).length) {
                throw new Error("Parameter 'algorithm' is empty");
            }
            const algorithm = parameters.algorithm;
            algorithm.hash.name = hashAlgorithm;
            switch (privateKey.algorithm.name.toUpperCase()) {
                case "RSASSA-PKCS1-V1_5":
                case "ECDSA":
                    signatureAlgorithm.algorithmId = this.getOIDByAlgorithm(algorithm, true);
                    break;
                case "RSA-PSS":
                    {
                        switch (hashAlgorithm.toUpperCase()) {
                            case "SHA-256":
                                algorithm.saltLength = 32;
                                break;
                            case "SHA-384":
                                algorithm.saltLength = 48;
                                break;
                            case "SHA-512":
                                algorithm.saltLength = 64;
                                break;
                        }
                        const paramsObject = {};
                        if (hashAlgorithm.toUpperCase() !== "SHA-1") {
                            const hashAlgorithmOID = this.getOIDByAlgorithm({ name: hashAlgorithm }, true, "hashAlgorithm");
                            paramsObject.hashAlgorithm = new AlgorithmIdentifier({
                                algorithmId: hashAlgorithmOID,
                                algorithmParams: new Null()
                            });
                            paramsObject.maskGenAlgorithm = new AlgorithmIdentifier({
                                algorithmId: "1.2.840.113549.1.1.8",
                                algorithmParams: paramsObject.hashAlgorithm.toSchema()
                            });
                        }
                        if (algorithm.saltLength !== 20)
                            paramsObject.saltLength = algorithm.saltLength;
                        const pssParameters = new RSASSAPSSParams(paramsObject);
                        signatureAlgorithm.algorithmId = "1.2.840.113549.1.1.10";
                        signatureAlgorithm.algorithmParams = pssParameters.toSchema();
                    }
                    break;
                default:
                    throw new Error(`Unsupported signature algorithm: ${privateKey.algorithm.name}`);
            }
            return {
                signatureAlgorithm,
                parameters
            };
        });
    }
    signWithPrivateKey(data, privateKey, parameters) {
        return __awaiter(this, void 0, void 0, function* () {
            const signature = yield this.sign(parameters.algorithm, privateKey, data);
            if (parameters.algorithm.name === "ECDSA") {
                return createCMSECDSASignature(signature);
            }
            return signature;
        });
    }
    fillPublicKeyParameters(publicKeyInfo, signatureAlgorithm) {
        const parameters = {};
        const shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm);
        if (shaAlgorithm === EMPTY_STRING)
            throw new Error(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`);
        let algorithmId;
        if (signatureAlgorithm.algorithmId === "1.2.840.113549.1.1.10")
            algorithmId = signatureAlgorithm.algorithmId;
        else
            algorithmId = publicKeyInfo.algorithm.algorithmId;
        const algorithmObject = this.getAlgorithmByOID(algorithmId, true);
        parameters.algorithm = this.getAlgorithmParameters(algorithmObject.name, "importKey");
        if ("hash" in parameters.algorithm.algorithm)
            parameters.algorithm.algorithm.hash.name = shaAlgorithm;
        if (algorithmObject.name === "ECDSA") {
            const publicKeyAlgorithm = publicKeyInfo.algorithm;
            if (!publicKeyAlgorithm.algorithmParams) {
                throw new Error("Algorithm parameters for ECDSA public key are missed");
            }
            const publicKeyAlgorithmParams = publicKeyAlgorithm.algorithmParams;
            if ("idBlock" in publicKeyAlgorithm.algorithmParams) {
                if (!((publicKeyAlgorithmParams.idBlock.tagClass === 1) && (publicKeyAlgorithmParams.idBlock.tagNumber === 6))) {
                    throw new Error("Incorrect type for ECDSA public key parameters");
                }
            }
            const curveObject = this.getAlgorithmByOID(publicKeyAlgorithmParams.valueBlock.toString(), true);
            parameters.algorithm.algorithm.namedCurve = curveObject.name;
        }
        return parameters;
    }
    getPublicKey(publicKeyInfo, signatureAlgorithm, parameters) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!parameters) {
                parameters = this.fillPublicKeyParameters(publicKeyInfo, signatureAlgorithm);
            }
            const publicKeyInfoBuffer = publicKeyInfo.toSchema().toBER(false);
            return this.importKey("spki", publicKeyInfoBuffer, parameters.algorithm.algorithm, true, parameters.algorithm.usages);
        });
    }
    verifyWithPublicKey(data, signature, publicKeyInfo, signatureAlgorithm, shaAlgorithm) {
        return __awaiter(this, void 0, void 0, function* () {
            let publicKey;
            if (!shaAlgorithm) {
                shaAlgorithm = this.getHashAlgorithm(signatureAlgorithm);
                if (!shaAlgorithm)
                    throw new Error(`Unsupported signature algorithm: ${signatureAlgorithm.algorithmId}`);
                publicKey = yield this.getPublicKey(publicKeyInfo, signatureAlgorithm);
            }
            else {
                const parameters = {};
                let algorithmId;
                if (signatureAlgorithm.algorithmId === "1.2.840.113549.1.1.10")
                    algorithmId = signatureAlgorithm.algorithmId;
                else
                    algorithmId = publicKeyInfo.algorithm.algorithmId;
                const algorithmObject = this.getAlgorithmByOID(algorithmId, true);
                parameters.algorithm = this.getAlgorithmParameters(algorithmObject.name, "importKey");
                if ("hash" in parameters.algorithm.algorithm)
                    parameters.algorithm.algorithm.hash.name = shaAlgorithm;
                if (algorithmObject.name === "ECDSA") {
                    let algorithmParamsChecked = false;
                    if (("algorithmParams" in publicKeyInfo.algorithm) === true) {
                        if ("idBlock" in publicKeyInfo.algorithm.algorithmParams) {
                            if ((publicKeyInfo.algorithm.algorithmParams.idBlock.tagClass === 1) && (publicKeyInfo.algorithm.algorithmParams.idBlock.tagNumber === 6))
                                algorithmParamsChecked = true;
                        }
                    }
                    if (algorithmParamsChecked === false) {
                        throw new Error("Incorrect type for ECDSA public key parameters");
                    }
                    const curveObject = this.getAlgorithmByOID(publicKeyInfo.algorithm.algorithmParams.valueBlock.toString(), true);
                    parameters.algorithm.algorithm.namedCurve = curveObject.name;
                }
                publicKey = yield this.getPublicKey(publicKeyInfo, null, parameters);
            }
            const algorithm = this.getAlgorithmParameters(publicKey.algorithm.name, "verify");
            if ("hash" in algorithm.algorithm)
                algorithm.algorithm.hash.name = shaAlgorithm;
            let signatureValue = signature.valueBlock.valueHexView;
            if (publicKey.algorithm.name === "ECDSA") {
                const namedCurve = ECNamedCurves.find(publicKey.algorithm.namedCurve);
                if (!namedCurve) {
                    throw new Error("Unsupported named curve in use");
                }
                const asn1 = fromBER(signatureValue);
                AsnError.assert(asn1, "Signature value");
                signatureValue = createECDSASignatureFromCMS(asn1.result, namedCurve.size);
            }
            if (publicKey.algorithm.name === "RSA-PSS") {
                const pssParameters = new RSASSAPSSParams({ schema: signatureAlgorithm.algorithmParams });
                if ("saltLength" in pssParameters)
                    algorithm.algorithm.saltLength = pssParameters.saltLength;
                else
                    algorithm.algorithm.saltLength = 20;
                let hashAlgo = "SHA-1";
                if ("hashAlgorithm" in pssParameters) {
                    const hashAlgorithm = this.getAlgorithmByOID(pssParameters.hashAlgorithm.algorithmId, true);
                    hashAlgo = hashAlgorithm.name;
                }
                algorithm.algorithm.hash.name = hashAlgo;
            }
            return this.verify(algorithm.algorithm, publicKey, signatureValue, data);
        });
    }
}

let engine = {
    name: "none",
    crypto: null,
};
function isCryptoEngine(engine) {
    return engine
        && typeof engine === "object"
        && "crypto" in engine
        ? true
        : false;
}
function setEngine(name, ...args) {
    let crypto = null;
    if (args.length < 2) {
        if (args.length) {
            crypto = args[0];
        }
        else {
            crypto = typeof self !== "undefined" && self.crypto ? new CryptoEngine({ name: "browser", crypto: self.crypto }) : null;
        }
    }
    else {
        const cryptoArg = args[0];
        const subtleArg = args[1];
        if (isCryptoEngine(subtleArg)) {
            crypto = subtleArg;
        }
        else if (isCryptoEngine(cryptoArg)) {
            crypto = cryptoArg;
        }
        else if ("subtle" in cryptoArg && "getRandomValues" in cryptoArg) {
            crypto = new CryptoEngine({
                crypto: cryptoArg,
            });
        }
    }
    if ((typeof process !== "undefined") && ("pid" in process) && (typeof global !== "undefined") && (typeof window === "undefined")) {
        if (typeof global[process.pid] === "undefined") {
            global[process.pid] = {};
        }
        else {
            if (typeof global[process.pid] !== "object") {
                throw new Error(`Name global.${process.pid} already exists and it is not an object`);
            }
        }
        if (typeof global[process.pid].pkijs === "undefined") {
            global[process.pid].pkijs = {};
        }
        else {
            if (typeof global[process.pid].pkijs !== "object") {
                throw new Error(`Name global.${process.pid}.pkijs already exists and it is not an object`);
            }
        }
        global[process.pid].pkijs.engine = {
            name: name,
            crypto,
        };
    }
    else {
        engine = {
            name: name,
            crypto,
        };
    }
}
function getEngine() {
    if ((typeof process !== "undefined") && ("pid" in process) && (typeof global !== "undefined") && (typeof window === "undefined")) {
        let _engine;
        try {
            _engine = global[process.pid].pkijs.engine;
        }
        catch (ex) {
            throw new Error("Please call 'setEngine' before call to 'getEngine'");
        }
        return _engine;
    }
    return engine;
}
function getCrypto(safety = false) {
    const _engine = getEngine();
    if (!_engine.crypto && safety) {
        throw new Error("Unable to create WebCrypto object");
    }
    return _engine.crypto;
}
function getRandomValues(view) {
    return getCrypto(true).getRandomValues(view);
}
function getOIDByAlgorithm(algorithm, safety, target) {
    return getCrypto(true).getOIDByAlgorithm(algorithm, safety, target);
}
function getAlgorithmParameters(algorithmName, operation) {
    return getCrypto(true).getAlgorithmParameters(algorithmName, operation);
}
function createCMSECDSASignature(signatureBuffer) {
    if ((signatureBuffer.byteLength % 2) !== 0)
        return EMPTY_BUFFER;
    const length = signatureBuffer.byteLength / 2;
    const rBuffer = new ArrayBuffer(length);
    const rView = new Uint8Array(rBuffer);
    rView.set(new Uint8Array(signatureBuffer, 0, length));
    const rInteger = new Integer({ valueHex: rBuffer });
    const sBuffer = new ArrayBuffer(length);
    const sView = new Uint8Array(sBuffer);
    sView.set(new Uint8Array(signatureBuffer, length, length));
    const sInteger = new Integer({ valueHex: sBuffer });
    return (new Sequence({
        value: [
            rInteger.convertToDER(),
            sInteger.convertToDER()
        ]
    })).toBER(false);
}
function createECDSASignatureFromCMS(cmsSignature, pointSize) {
    if (!(cmsSignature instanceof Sequence
        && cmsSignature.valueBlock.value.length === 2
        && cmsSignature.valueBlock.value[0] instanceof Integer
        && cmsSignature.valueBlock.value[1] instanceof Integer))
        return EMPTY_BUFFER;
    const rValueView = cmsSignature.valueBlock.value[0].convertFromDER().valueBlock.valueHexView;
    const sValueView = cmsSignature.valueBlock.value[1].convertFromDER().valueBlock.valueHexView;
    const res = new Uint8Array(pointSize * 2);
    res.set(rValueView, pointSize - rValueView.byteLength);
    res.set(sValueView, (2 * pointSize) - sValueView.byteLength);
    return res.buffer;
}
function getAlgorithmByOID(oid, safety = false, target) {
    return getCrypto(true).getAlgorithmByOID(oid, safety, target);
}
function getHashAlgorithm(signatureAlgorithm) {
    return getCrypto(true).getHashAlgorithm(signatureAlgorithm);
}
function kdfWithCounter(hashFunction, zBuffer, Counter, SharedInfo, crypto) {
    return __awaiter(this, void 0, void 0, function* () {
        switch (hashFunction.toUpperCase()) {
            case "SHA-1":
            case "SHA-256":
            case "SHA-384":
            case "SHA-512":
                break;
            default:
                throw new ArgumentError(`Unknown hash function: ${hashFunction}`);
        }
        ArgumentError.assert(zBuffer, "zBuffer", "ArrayBuffer");
        if (zBuffer.byteLength === 0)
            throw new ArgumentError("'zBuffer' has zero length, error");
        ArgumentError.assert(SharedInfo, "SharedInfo", "ArrayBuffer");
        if (Counter > 255)
            throw new ArgumentError("Please set 'Counter' argument to value less or equal to 255");
        const counterBuffer = new ArrayBuffer(4);
        const counterView = new Uint8Array(counterBuffer);
        counterView[0] = 0x00;
        counterView[1] = 0x00;
        counterView[2] = 0x00;
        counterView[3] = Counter;
        let combinedBuffer = EMPTY_BUFFER;
        combinedBuffer = utilConcatBuf(combinedBuffer, zBuffer);
        combinedBuffer = utilConcatBuf(combinedBuffer, counterBuffer);
        combinedBuffer = utilConcatBuf(combinedBuffer, SharedInfo);
        const result = yield crypto.digest({ name: hashFunction }, combinedBuffer);
        return {
            counter: Counter,
            result
        };
    });
}
function kdf(hashFunction, Zbuffer, keydatalen, SharedInfo, crypto = getCrypto(true)) {
    return __awaiter(this, void 0, void 0, function* () {
        let hashLength = 0;
        let maxCounter = 1;
        switch (hashFunction.toUpperCase()) {
            case "SHA-1":
                hashLength = 160;
                break;
            case "SHA-256":
                hashLength = 256;
                break;
            case "SHA-384":
                hashLength = 384;
                break;
            case "SHA-512":
                hashLength = 512;
                break;
            default:
                throw new ArgumentError(`Unknown hash function: ${hashFunction}`);
        }
        ArgumentError.assert(Zbuffer, "Zbuffer", "ArrayBuffer");
        if (Zbuffer.byteLength === 0)
            throw new ArgumentError("'Zbuffer' has zero length, error");
        ArgumentError.assert(SharedInfo, "SharedInfo", "ArrayBuffer");
        const quotient = keydatalen / hashLength;
        if (Math.floor(quotient) > 0) {
            maxCounter = Math.floor(quotient);
            if ((quotient - maxCounter) > 0)
                maxCounter++;
        }
        const incomingResult = [];
        for (let i = 1; i <= maxCounter; i++)
            incomingResult.push(yield kdfWithCounter(hashFunction, Zbuffer, i, SharedInfo, crypto));
        let combinedBuffer = EMPTY_BUFFER;
        let currentCounter = 1;
        let found = true;
        while (found) {
            found = false;
            for (const result of incomingResult) {
                if (result.counter === currentCounter) {
                    combinedBuffer = utilConcatBuf(combinedBuffer, result.result);
                    found = true;
                    break;
                }
            }
            currentCounter++;
        }
        keydatalen >>= 3;
        if (combinedBuffer.byteLength > keydatalen) {
            const newBuffer = new ArrayBuffer(keydatalen);
            const newView = new Uint8Array(newBuffer);
            const combinedView = new Uint8Array(combinedBuffer);
            for (let i = 0; i < keydatalen; i++)
                newView[i] = combinedView[i];
            return newBuffer;
        }
        return combinedBuffer;
    });
}

const VERSION$i = "version";
const LOG_ID = "logID";
const EXTENSIONS$6 = "extensions";
const TIMESTAMP = "timestamp";
const HASH_ALGORITHM$3 = "hashAlgorithm";
const SIGNATURE_ALGORITHM$8 = "signatureAlgorithm";
const SIGNATURE$7 = "signature";
const NONE = "none";
const MD5 = "md5";
const SHA1 = "sha1";
const SHA224 = "sha224";
const SHA256 = "sha256";
const SHA384 = "sha384";
const SHA512 = "sha512";
const ANONYMOUS = "anonymous";
const RSA = "rsa";
const DSA = "dsa";
const ECDSA = "ecdsa";
class SignedCertificateTimestamp extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.version = getParametersValue(parameters, VERSION$i, SignedCertificateTimestamp.defaultValues(VERSION$i));
        this.logID = getParametersValue(parameters, LOG_ID, SignedCertificateTimestamp.defaultValues(LOG_ID));
        this.timestamp = getParametersValue(parameters, TIMESTAMP, SignedCertificateTimestamp.defaultValues(TIMESTAMP));
        this.extensions = getParametersValue(parameters, EXTENSIONS$6, SignedCertificateTimestamp.defaultValues(EXTENSIONS$6));
        this.hashAlgorithm = getParametersValue(parameters, HASH_ALGORITHM$3, SignedCertificateTimestamp.defaultValues(HASH_ALGORITHM$3));
        this.signatureAlgorithm = getParametersValue(parameters, SIGNATURE_ALGORITHM$8, SignedCertificateTimestamp.defaultValues(SIGNATURE_ALGORITHM$8));
        this.signature = getParametersValue(parameters, SIGNATURE$7, SignedCertificateTimestamp.defaultValues(SIGNATURE$7));
        if ("stream" in parameters && parameters.stream) {
            this.fromStream(parameters.stream);
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VERSION$i:
                return 0;
            case LOG_ID:
            case EXTENSIONS$6:
                return EMPTY_BUFFER;
            case TIMESTAMP:
                return new Date(0);
            case HASH_ALGORITHM$3:
            case SIGNATURE_ALGORITHM$8:
                return EMPTY_STRING;
            case SIGNATURE$7:
                return new Any();
            default:
                return super.defaultValues(memberName);
        }
    }
    fromSchema(schema) {
        if ((schema instanceof RawData) === false)
            throw new Error("Object's schema was not verified against input data for SignedCertificateTimestamp");
        const seqStream = new SeqStream({
            stream: new ByteStream({
                buffer: schema.data
            })
        });
        this.fromStream(seqStream);
    }
    fromStream(stream) {
        const blockLength = stream.getUint16();
        this.version = (stream.getBlock(1))[0];
        if (this.version === 0) {
            this.logID = (new Uint8Array(stream.getBlock(32))).buffer.slice(0);
            this.timestamp = new Date(utilFromBase(new Uint8Array(stream.getBlock(8)), 8));
            const extensionsLength = stream.getUint16();
            this.extensions = (new Uint8Array(stream.getBlock(extensionsLength))).buffer.slice(0);
            switch ((stream.getBlock(1))[0]) {
                case 0:
                    this.hashAlgorithm = NONE;
                    break;
                case 1:
                    this.hashAlgorithm = MD5;
                    break;
                case 2:
                    this.hashAlgorithm = SHA1;
                    break;
                case 3:
                    this.hashAlgorithm = SHA224;
                    break;
                case 4:
                    this.hashAlgorithm = SHA256;
                    break;
                case 5:
                    this.hashAlgorithm = SHA384;
                    break;
                case 6:
                    this.hashAlgorithm = SHA512;
                    break;
                default:
                    throw new Error("Object's stream was not correct for SignedCertificateTimestamp");
            }
            switch ((stream.getBlock(1))[0]) {
                case 0:
                    this.signatureAlgorithm = ANONYMOUS;
                    break;
                case 1:
                    this.signatureAlgorithm = RSA;
                    break;
                case 2:
                    this.signatureAlgorithm = DSA;
                    break;
                case 3:
                    this.signatureAlgorithm = ECDSA;
                    break;
                default:
                    throw new Error("Object's stream was not correct for SignedCertificateTimestamp");
            }
            const signatureLength = stream.getUint16();
            const signatureData = new Uint8Array(stream.getBlock(signatureLength)).buffer.slice(0);
            const asn1 = fromBER(signatureData);
            AsnError.assert(asn1, "SignedCertificateTimestamp");
            this.signature = asn1.result;
            if (blockLength !== (47 + extensionsLength + signatureLength)) {
                throw new Error("Object's stream was not correct for SignedCertificateTimestamp");
            }
        }
    }
    toSchema() {
        const stream = this.toStream();
        return new RawData({ data: stream.stream.buffer });
    }
    toStream() {
        const stream = new SeqStream();
        stream.appendUint16(47 + this.extensions.byteLength + this.signature.valueBeforeDecodeView.byteLength);
        stream.appendChar(this.version);
        stream.appendView(new Uint8Array(this.logID));
        const timeBuffer = new ArrayBuffer(8);
        const timeView = new Uint8Array(timeBuffer);
        const baseArray = utilToBase(this.timestamp.valueOf(), 8);
        timeView.set(new Uint8Array(baseArray), 8 - baseArray.byteLength);
        stream.appendView(timeView);
        stream.appendUint16(this.extensions.byteLength);
        if (this.extensions.byteLength)
            stream.appendView(new Uint8Array(this.extensions));
        let _hashAlgorithm;
        switch (this.hashAlgorithm.toLowerCase()) {
            case NONE:
                _hashAlgorithm = 0;
                break;
            case MD5:
                _hashAlgorithm = 1;
                break;
            case SHA1:
                _hashAlgorithm = 2;
                break;
            case SHA224:
                _hashAlgorithm = 3;
                break;
            case SHA256:
                _hashAlgorithm = 4;
                break;
            case SHA384:
                _hashAlgorithm = 5;
                break;
            case SHA512:
                _hashAlgorithm = 6;
                break;
            default:
                throw new Error(`Incorrect data for hashAlgorithm: ${this.hashAlgorithm}`);
        }
        stream.appendChar(_hashAlgorithm);
        let _signatureAlgorithm;
        switch (this.signatureAlgorithm.toLowerCase()) {
            case ANONYMOUS:
                _signatureAlgorithm = 0;
                break;
            case RSA:
                _signatureAlgorithm = 1;
                break;
            case DSA:
                _signatureAlgorithm = 2;
                break;
            case ECDSA:
                _signatureAlgorithm = 3;
                break;
            default:
                throw new Error(`Incorrect data for signatureAlgorithm: ${this.signatureAlgorithm}`);
        }
        stream.appendChar(_signatureAlgorithm);
        const _signature = this.signature.toBER(false);
        stream.appendUint16(_signature.byteLength);
        stream.appendView(new Uint8Array(_signature));
        return stream;
    }
    toJSON() {
        return {
            version: this.version,
            logID: bufferToHexCodes(this.logID),
            timestamp: this.timestamp,
            extensions: bufferToHexCodes(this.extensions),
            hashAlgorithm: this.hashAlgorithm,
            signatureAlgorithm: this.signatureAlgorithm,
            signature: this.signature.toJSON()
        };
    }
    verify(logs, data, dataType = 0, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            const logId = toBase64(arrayBufferToString(this.logID));
            let publicKeyBase64 = null;
            const stream = new SeqStream();
            for (const log of logs) {
                if (log.log_id === logId) {
                    publicKeyBase64 = log.key;
                    break;
                }
            }
            if (!publicKeyBase64) {
                throw new Error(`Public key not found for CT with logId: ${logId}`);
            }
            const pki = stringToArrayBuffer(fromBase64(publicKeyBase64));
            const publicKeyInfo = PublicKeyInfo.fromBER(pki);
            stream.appendChar(0x00);
            stream.appendChar(0x00);
            const timeBuffer = new ArrayBuffer(8);
            const timeView = new Uint8Array(timeBuffer);
            const baseArray = utilToBase(this.timestamp.valueOf(), 8);
            timeView.set(new Uint8Array(baseArray), 8 - baseArray.byteLength);
            stream.appendView(timeView);
            stream.appendUint16(dataType);
            if (dataType === 0)
                stream.appendUint24(data.byteLength);
            stream.appendView(new Uint8Array(data));
            stream.appendUint16(this.extensions.byteLength);
            if (this.extensions.byteLength !== 0)
                stream.appendView(new Uint8Array(this.extensions));
            return crypto.verifyWithPublicKey(stream.buffer.slice(0, stream.length), new OctetString({ valueHex: this.signature.toBER(false) }), publicKeyInfo, { algorithmId: EMPTY_STRING }, "SHA-256");
        });
    }
}
SignedCertificateTimestamp.CLASS_NAME = "SignedCertificateTimestamp";
function verifySCTsForCertificate(certificate, issuerCertificate, logs, index = (-1), crypto = getCrypto(true)) {
    return __awaiter(this, void 0, void 0, function* () {
        let parsedValue = null;
        const stream = new SeqStream();
        for (let i = 0; certificate.extensions && i < certificate.extensions.length; i++) {
            switch (certificate.extensions[i].extnID) {
                case id_SignedCertificateTimestampList:
                    {
                        parsedValue = certificate.extensions[i].parsedValue;
                        if (!parsedValue || parsedValue.timestamps.length === 0)
                            throw new Error("Nothing to verify in the certificate");
                        certificate.extensions.splice(i, 1);
                    }
                    break;
            }
        }
        if (parsedValue === null)
            throw new Error("No SignedCertificateTimestampList extension in the specified certificate");
        const tbs = certificate.encodeTBS().toBER();
        const issuerId = yield crypto.digest({ name: "SHA-256" }, new Uint8Array(issuerCertificate.subjectPublicKeyInfo.toSchema().toBER(false)));
        stream.appendView(new Uint8Array(issuerId));
        stream.appendUint24(tbs.byteLength);
        stream.appendView(new Uint8Array(tbs));
        const preCert = stream.stream.slice(0, stream.length);
        if (index === (-1)) {
            const verifyArray = [];
            for (const timestamp of parsedValue.timestamps) {
                const verifyResult = yield timestamp.verify(logs, preCert.buffer, 1, crypto);
                verifyArray.push(verifyResult);
            }
            return verifyArray;
        }
        if (index >= parsedValue.timestamps.length)
            index = (parsedValue.timestamps.length - 1);
        return [yield parsedValue.timestamps[index].verify(logs, preCert.buffer, 1, crypto)];
    });
}

const TIMESTAMPS = "timestamps";
class SignedCertificateTimestampList extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.timestamps = getParametersValue(parameters, TIMESTAMPS, SignedCertificateTimestampList.defaultValues(TIMESTAMPS));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case TIMESTAMPS:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case TIMESTAMPS:
                return (memberValue.length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        var _a;
        const names = getParametersValue(parameters, "names", {});
        (_a = names.optional) !== null && _a !== void 0 ? _a : (names.optional = false);
        return (new OctetString({
            name: (names.blockName || "SignedCertificateTimestampList"),
            optional: names.optional
        }));
    }
    fromSchema(schema) {
        if ((schema instanceof OctetString) === false) {
            throw new Error("Object's schema was not verified against input data for SignedCertificateTimestampList");
        }
        const seqStream = new SeqStream({
            stream: new ByteStream({
                buffer: schema.valueBlock.valueHex
            })
        });
        const dataLength = seqStream.getUint16();
        if (dataLength !== seqStream.length) {
            throw new Error("Object's schema was not verified against input data for SignedCertificateTimestampList");
        }
        while (seqStream.length) {
            this.timestamps.push(new SignedCertificateTimestamp({ stream: seqStream }));
        }
    }
    toSchema() {
        const stream = new SeqStream();
        let overallLength = 0;
        const timestampsData = [];
        for (const timestamp of this.timestamps) {
            const timestampStream = timestamp.toStream();
            timestampsData.push(timestampStream);
            overallLength += timestampStream.stream.buffer.byteLength;
        }
        stream.appendUint16(overallLength);
        for (const timestamp of timestampsData) {
            stream.appendView(timestamp.stream.view);
        }
        return new OctetString({ valueHex: stream.stream.buffer.slice(0) });
    }
    toJSON() {
        return {
            timestamps: Array.from(this.timestamps, o => o.toJSON())
        };
    }
}
SignedCertificateTimestampList.CLASS_NAME = "SignedCertificateTimestampList";

const ATTRIBUTES$4 = "attributes";
const CLEAR_PROPS$11 = [
    ATTRIBUTES$4
];
class SubjectDirectoryAttributes extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.attributes = getParametersValue(parameters, ATTRIBUTES$4, SubjectDirectoryAttributes.defaultValues(ATTRIBUTES$4));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case ATTRIBUTES$4:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Repeated({
                    name: (names.attributes || EMPTY_STRING),
                    value: Attribute.schema()
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$11);
        const asn1 = compareSchema(schema, schema, SubjectDirectoryAttributes.schema({
            names: {
                attributes: ATTRIBUTES$4
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.attributes = Array.from(asn1.result.attributes, element => new Attribute({ schema: element }));
    }
    toSchema() {
        return (new Sequence({
            value: Array.from(this.attributes, o => o.toSchema())
        }));
    }
    toJSON() {
        return {
            attributes: Array.from(this.attributes, o => o.toJSON())
        };
    }
}
SubjectDirectoryAttributes.CLASS_NAME = "SubjectDirectoryAttributes";

class ExtensionValueFactory {
    static getItems() {
        if (!this.types) {
            this.types = {};
            ExtensionValueFactory.register(id_SubjectAltName, "SubjectAltName", AltName);
            ExtensionValueFactory.register(id_IssuerAltName, "IssuerAltName", AltName);
            ExtensionValueFactory.register(id_AuthorityKeyIdentifier, "AuthorityKeyIdentifier", AuthorityKeyIdentifier);
            ExtensionValueFactory.register(id_BasicConstraints, "BasicConstraints", BasicConstraints);
            ExtensionValueFactory.register(id_MicrosoftCaVersion, "MicrosoftCaVersion", CAVersion);
            ExtensionValueFactory.register(id_CertificatePolicies, "CertificatePolicies", CertificatePolicies);
            ExtensionValueFactory.register(id_MicrosoftAppPolicies, "CertificatePoliciesMicrosoft", CertificatePolicies);
            ExtensionValueFactory.register(id_MicrosoftCertTemplateV2, "MicrosoftCertTemplateV2", CertificateTemplate);
            ExtensionValueFactory.register(id_CRLDistributionPoints, "CRLDistributionPoints", CRLDistributionPoints);
            ExtensionValueFactory.register(id_FreshestCRL, "FreshestCRL", CRLDistributionPoints);
            ExtensionValueFactory.register(id_ExtKeyUsage, "ExtKeyUsage", ExtKeyUsage);
            ExtensionValueFactory.register(id_CertificateIssuer, "CertificateIssuer", GeneralNames);
            ExtensionValueFactory.register(id_AuthorityInfoAccess, "AuthorityInfoAccess", InfoAccess);
            ExtensionValueFactory.register(id_SubjectInfoAccess, "SubjectInfoAccess", InfoAccess);
            ExtensionValueFactory.register(id_IssuingDistributionPoint, "IssuingDistributionPoint", IssuingDistributionPoint);
            ExtensionValueFactory.register(id_NameConstraints, "NameConstraints", NameConstraints);
            ExtensionValueFactory.register(id_PolicyConstraints, "PolicyConstraints", PolicyConstraints);
            ExtensionValueFactory.register(id_PolicyMappings, "PolicyMappings", PolicyMappings);
            ExtensionValueFactory.register(id_PrivateKeyUsagePeriod, "PrivateKeyUsagePeriod", PrivateKeyUsagePeriod);
            ExtensionValueFactory.register(id_QCStatements, "QCStatements", QCStatements);
            ExtensionValueFactory.register(id_SignedCertificateTimestampList, "SignedCertificateTimestampList", SignedCertificateTimestampList);
            ExtensionValueFactory.register(id_SubjectDirectoryAttributes, "SubjectDirectoryAttributes", SubjectDirectoryAttributes);
        }
        return this.types;
    }
    static fromBER(id, raw) {
        const asn1 = fromBER(raw);
        if (asn1.offset === -1) {
            return null;
        }
        const item = this.find(id);
        if (item) {
            try {
                return new item.type({ schema: asn1.result });
            }
            catch (ex) {
                const res = new item.type();
                res.parsingError = `Incorrectly formatted value of extension ${item.name} (${id})`;
                return res;
            }
        }
        return asn1.result;
    }
    static find(id) {
        const types = this.getItems();
        return types[id] || null;
    }
    static register(id, name, type) {
        this.getItems()[id] = { name, type };
    }
}

const EXTN_ID = "extnID";
const CRITICAL = "critical";
const EXTN_VALUE = "extnValue";
const PARSED_VALUE$5 = "parsedValue";
const CLEAR_PROPS$10 = [
    EXTN_ID,
    CRITICAL,
    EXTN_VALUE
];
class Extension extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.extnID = getParametersValue(parameters, EXTN_ID, Extension.defaultValues(EXTN_ID));
        this.critical = getParametersValue(parameters, CRITICAL, Extension.defaultValues(CRITICAL));
        if (EXTN_VALUE in parameters) {
            this.extnValue = new OctetString({ valueHex: parameters.extnValue });
        }
        else {
            this.extnValue = Extension.defaultValues(EXTN_VALUE);
        }
        if (PARSED_VALUE$5 in parameters) {
            this.parsedValue = getParametersValue(parameters, PARSED_VALUE$5, Extension.defaultValues(PARSED_VALUE$5));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    get parsedValue() {
        if (this._parsedValue === undefined) {
            const parsedValue = ExtensionValueFactory.fromBER(this.extnID, this.extnValue.valueBlock.valueHexView);
            this._parsedValue = parsedValue;
        }
        return this._parsedValue || undefined;
    }
    set parsedValue(value) {
        this._parsedValue = value;
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case EXTN_ID:
                return EMPTY_STRING;
            case CRITICAL:
                return false;
            case EXTN_VALUE:
                return new OctetString();
            case PARSED_VALUE$5:
                return {};
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.extnID || EMPTY_STRING) }),
                new Boolean({
                    name: (names.critical || EMPTY_STRING),
                    optional: true
                }),
                new OctetString({ name: (names.extnValue || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$10);
        const asn1 = compareSchema(schema, schema, Extension.schema({
            names: {
                extnID: EXTN_ID,
                critical: CRITICAL,
                extnValue: EXTN_VALUE
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.extnID = asn1.result.extnID.valueBlock.toString();
        if (CRITICAL in asn1.result) {
            this.critical = asn1.result.critical.valueBlock.value;
        }
        this.extnValue = asn1.result.extnValue;
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(new ObjectIdentifier({ value: this.extnID }));
        if (this.critical !== Extension.defaultValues(CRITICAL)) {
            outputArray.push(new Boolean({ value: this.critical }));
        }
        outputArray.push(this.extnValue);
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const object = {
            extnID: this.extnID,
            extnValue: this.extnValue.toJSON(),
        };
        if (this.critical !== Extension.defaultValues(CRITICAL)) {
            object.critical = this.critical;
        }
        if (this.parsedValue && this.parsedValue.toJSON) {
            object.parsedValue = this.parsedValue.toJSON();
        }
        return object;
    }
}
Extension.CLASS_NAME = "Extension";

const EXTENSIONS$5 = "extensions";
const CLEAR_PROPS$$ = [
    EXTENSIONS$5,
];
class Extensions extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.extensions = getParametersValue(parameters, EXTENSIONS$5, Extensions.defaultValues(EXTENSIONS$5));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case EXTENSIONS$5:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}, optional = false) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            optional,
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Repeated({
                    name: (names.extensions || EMPTY_STRING),
                    value: Extension.schema(names.extension || {})
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$$);
        const asn1 = compareSchema(schema, schema, Extensions.schema({
            names: {
                extensions: EXTENSIONS$5
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.extensions = Array.from(asn1.result.extensions, element => new Extension({ schema: element }));
    }
    toSchema() {
        return (new Sequence({
            value: Array.from(this.extensions, o => o.toSchema())
        }));
    }
    toJSON() {
        return {
            extensions: this.extensions.map(o => o.toJSON())
        };
    }
}
Extensions.CLASS_NAME = "Extensions";

const ISSUER$5 = "issuer";
const SERIAL_NUMBER$6 = "serialNumber";
const ISSUER_UID = "issuerUID";
const CLEAR_PROPS$_ = [
    ISSUER$5,
    SERIAL_NUMBER$6,
    ISSUER_UID,
];
class IssuerSerial extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.issuer = getParametersValue(parameters, ISSUER$5, IssuerSerial.defaultValues(ISSUER$5));
        this.serialNumber = getParametersValue(parameters, SERIAL_NUMBER$6, IssuerSerial.defaultValues(SERIAL_NUMBER$6));
        if (ISSUER_UID in parameters) {
            this.issuerUID = getParametersValue(parameters, ISSUER_UID, IssuerSerial.defaultValues(ISSUER_UID));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case ISSUER$5:
                return new GeneralNames();
            case SERIAL_NUMBER$6:
                return new Integer();
            case ISSUER_UID:
                return new BitString();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                GeneralNames.schema(names.issuer || {}),
                new Integer({ name: (names.serialNumber || EMPTY_STRING) }),
                new BitString({
                    optional: true,
                    name: (names.issuerUID || EMPTY_STRING)
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$_);
        const asn1 = compareSchema(schema, schema, IssuerSerial.schema({
            names: {
                issuer: {
                    names: {
                        blockName: ISSUER$5
                    }
                },
                serialNumber: SERIAL_NUMBER$6,
                issuerUID: ISSUER_UID
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.issuer = new GeneralNames({ schema: asn1.result.issuer });
        this.serialNumber = asn1.result.serialNumber;
        if (ISSUER_UID in asn1.result)
            this.issuerUID = asn1.result.issuerUID;
    }
    toSchema() {
        const result = new Sequence({
            value: [
                this.issuer.toSchema(),
                this.serialNumber
            ]
        });
        if (this.issuerUID) {
            result.valueBlock.value.push(this.issuerUID);
        }
        return result;
    }
    toJSON() {
        const result = {
            issuer: this.issuer.toJSON(),
            serialNumber: this.serialNumber.toJSON()
        };
        if (this.issuerUID) {
            result.issuerUID = this.issuerUID.toJSON();
        }
        return result;
    }
}
IssuerSerial.CLASS_NAME = "IssuerSerial";

const VERSION$h = "version";
const BASE_CERTIFICATE_ID$2 = "baseCertificateID";
const SUBJECT_NAME = "subjectName";
const ISSUER$4 = "issuer";
const SIGNATURE$6 = "signature";
const SERIAL_NUMBER$5 = "serialNumber";
const ATTR_CERT_VALIDITY_PERIOD$1 = "attrCertValidityPeriod";
const ATTRIBUTES$3 = "attributes";
const ISSUER_UNIQUE_ID$2 = "issuerUniqueID";
const EXTENSIONS$4 = "extensions";
const CLEAR_PROPS$Z = [
    VERSION$h,
    BASE_CERTIFICATE_ID$2,
    SUBJECT_NAME,
    ISSUER$4,
    SIGNATURE$6,
    SERIAL_NUMBER$5,
    ATTR_CERT_VALIDITY_PERIOD$1,
    ATTRIBUTES$3,
    ISSUER_UNIQUE_ID$2,
    EXTENSIONS$4,
];
class AttributeCertificateInfoV1 extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.version = getParametersValue(parameters, VERSION$h, AttributeCertificateInfoV1.defaultValues(VERSION$h));
        if (BASE_CERTIFICATE_ID$2 in parameters) {
            this.baseCertificateID = getParametersValue(parameters, BASE_CERTIFICATE_ID$2, AttributeCertificateInfoV1.defaultValues(BASE_CERTIFICATE_ID$2));
        }
        if (SUBJECT_NAME in parameters) {
            this.subjectName = getParametersValue(parameters, SUBJECT_NAME, AttributeCertificateInfoV1.defaultValues(SUBJECT_NAME));
        }
        this.issuer = getParametersValue(parameters, ISSUER$4, AttributeCertificateInfoV1.defaultValues(ISSUER$4));
        this.signature = getParametersValue(parameters, SIGNATURE$6, AttributeCertificateInfoV1.defaultValues(SIGNATURE$6));
        this.serialNumber = getParametersValue(parameters, SERIAL_NUMBER$5, AttributeCertificateInfoV1.defaultValues(SERIAL_NUMBER$5));
        this.attrCertValidityPeriod = getParametersValue(parameters, ATTR_CERT_VALIDITY_PERIOD$1, AttributeCertificateInfoV1.defaultValues(ATTR_CERT_VALIDITY_PERIOD$1));
        this.attributes = getParametersValue(parameters, ATTRIBUTES$3, AttributeCertificateInfoV1.defaultValues(ATTRIBUTES$3));
        if (ISSUER_UNIQUE_ID$2 in parameters)
            this.issuerUniqueID = getParametersValue(parameters, ISSUER_UNIQUE_ID$2, AttributeCertificateInfoV1.defaultValues(ISSUER_UNIQUE_ID$2));
        if (EXTENSIONS$4 in parameters) {
            this.extensions = getParametersValue(parameters, EXTENSIONS$4, AttributeCertificateInfoV1.defaultValues(EXTENSIONS$4));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VERSION$h:
                return 0;
            case BASE_CERTIFICATE_ID$2:
                return new IssuerSerial();
            case SUBJECT_NAME:
                return new GeneralNames();
            case ISSUER$4:
                return new GeneralNames();
            case SIGNATURE$6:
                return new AlgorithmIdentifier();
            case SERIAL_NUMBER$5:
                return new Integer();
            case ATTR_CERT_VALIDITY_PERIOD$1:
                return new AttCertValidityPeriod();
            case ATTRIBUTES$3:
                return [];
            case ISSUER_UNIQUE_ID$2:
                return new BitString();
            case EXTENSIONS$4:
                return new Extensions();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Integer({ name: (names.version || EMPTY_STRING) }),
                new Choice({
                    value: [
                        new Constructed({
                            name: (names.baseCertificateID || EMPTY_STRING),
                            idBlock: {
                                tagClass: 3,
                                tagNumber: 0
                            },
                            value: IssuerSerial.schema().valueBlock.value
                        }),
                        new Constructed({
                            name: (names.subjectName || EMPTY_STRING),
                            idBlock: {
                                tagClass: 3,
                                tagNumber: 1
                            },
                            value: GeneralNames.schema().valueBlock.value
                        }),
                    ]
                }),
                GeneralNames.schema({
                    names: {
                        blockName: (names.issuer || EMPTY_STRING)
                    }
                }),
                AlgorithmIdentifier.schema(names.signature || {}),
                new Integer({ name: (names.serialNumber || EMPTY_STRING) }),
                AttCertValidityPeriod.schema(names.attrCertValidityPeriod || {}),
                new Sequence({
                    name: (names.attributes || EMPTY_STRING),
                    value: [
                        new Repeated({
                            value: Attribute.schema()
                        })
                    ]
                }),
                new BitString({
                    optional: true,
                    name: (names.issuerUniqueID || EMPTY_STRING)
                }),
                Extensions.schema(names.extensions || {}, true)
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$Z);
        const asn1 = compareSchema(schema, schema, AttributeCertificateInfoV1.schema({
            names: {
                version: VERSION$h,
                baseCertificateID: BASE_CERTIFICATE_ID$2,
                subjectName: SUBJECT_NAME,
                issuer: ISSUER$4,
                signature: {
                    names: {
                        blockName: SIGNATURE$6
                    }
                },
                serialNumber: SERIAL_NUMBER$5,
                attrCertValidityPeriod: {
                    names: {
                        blockName: ATTR_CERT_VALIDITY_PERIOD$1
                    }
                },
                attributes: ATTRIBUTES$3,
                issuerUniqueID: ISSUER_UNIQUE_ID$2,
                extensions: {
                    names: {
                        blockName: EXTENSIONS$4
                    }
                }
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.version = asn1.result.version.valueBlock.valueDec;
        if (BASE_CERTIFICATE_ID$2 in asn1.result) {
            this.baseCertificateID = new IssuerSerial({
                schema: new Sequence({
                    value: asn1.result.baseCertificateID.valueBlock.value
                })
            });
        }
        if (SUBJECT_NAME in asn1.result) {
            this.subjectName = new GeneralNames({
                schema: new Sequence({
                    value: asn1.result.subjectName.valueBlock.value
                })
            });
        }
        this.issuer = asn1.result.issuer;
        this.signature = new AlgorithmIdentifier({ schema: asn1.result.signature });
        this.serialNumber = asn1.result.serialNumber;
        this.attrCertValidityPeriod = new AttCertValidityPeriod({ schema: asn1.result.attrCertValidityPeriod });
        this.attributes = Array.from(asn1.result.attributes.valueBlock.value, element => new Attribute({ schema: element }));
        if (ISSUER_UNIQUE_ID$2 in asn1.result) {
            this.issuerUniqueID = asn1.result.issuerUniqueID;
        }
        if (EXTENSIONS$4 in asn1.result) {
            this.extensions = new Extensions({ schema: asn1.result.extensions });
        }
    }
    toSchema() {
        const result = new Sequence({
            value: [new Integer({ value: this.version })]
        });
        if (this.baseCertificateID) {
            result.valueBlock.value.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: this.baseCertificateID.toSchema().valueBlock.value
            }));
        }
        if (this.subjectName) {
            result.valueBlock.value.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                value: this.subjectName.toSchema().valueBlock.value
            }));
        }
        result.valueBlock.value.push(this.issuer.toSchema());
        result.valueBlock.value.push(this.signature.toSchema());
        result.valueBlock.value.push(this.serialNumber);
        result.valueBlock.value.push(this.attrCertValidityPeriod.toSchema());
        result.valueBlock.value.push(new Sequence({
            value: Array.from(this.attributes, o => o.toSchema())
        }));
        if (this.issuerUniqueID) {
            result.valueBlock.value.push(this.issuerUniqueID);
        }
        if (this.extensions) {
            result.valueBlock.value.push(this.extensions.toSchema());
        }
        return result;
    }
    toJSON() {
        const result = {
            version: this.version
        };
        if (this.baseCertificateID) {
            result.baseCertificateID = this.baseCertificateID.toJSON();
        }
        if (this.subjectName) {
            result.subjectName = this.subjectName.toJSON();
        }
        result.issuer = this.issuer.toJSON();
        result.signature = this.signature.toJSON();
        result.serialNumber = this.serialNumber.toJSON();
        result.attrCertValidityPeriod = this.attrCertValidityPeriod.toJSON();
        result.attributes = Array.from(this.attributes, o => o.toJSON());
        if (this.issuerUniqueID) {
            result.issuerUniqueID = this.issuerUniqueID.toJSON();
        }
        if (this.extensions) {
            result.extensions = this.extensions.toJSON();
        }
        return result;
    }
}
AttributeCertificateInfoV1.CLASS_NAME = "AttributeCertificateInfoV1";

const ACINFO$1 = "acinfo";
const SIGNATURE_ALGORITHM$7 = "signatureAlgorithm";
const SIGNATURE_VALUE$4 = "signatureValue";
const CLEAR_PROPS$Y = [
    ACINFO$1,
    SIGNATURE_VALUE$4,
    SIGNATURE_ALGORITHM$7
];
class AttributeCertificateV1 extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.acinfo = getParametersValue(parameters, ACINFO$1, AttributeCertificateV1.defaultValues(ACINFO$1));
        this.signatureAlgorithm = getParametersValue(parameters, SIGNATURE_ALGORITHM$7, AttributeCertificateV1.defaultValues(SIGNATURE_ALGORITHM$7));
        this.signatureValue = getParametersValue(parameters, SIGNATURE_VALUE$4, AttributeCertificateV1.defaultValues(SIGNATURE_VALUE$4));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case ACINFO$1:
                return new AttributeCertificateInfoV1();
            case SIGNATURE_ALGORITHM$7:
                return new AlgorithmIdentifier();
            case SIGNATURE_VALUE$4:
                return new BitString();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                AttributeCertificateInfoV1.schema(names.acinfo || {}),
                AlgorithmIdentifier.schema(names.signatureAlgorithm || {}),
                new BitString({ name: (names.signatureValue || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$Y);
        const asn1 = compareSchema(schema, schema, AttributeCertificateV1.schema({
            names: {
                acinfo: {
                    names: {
                        blockName: ACINFO$1
                    }
                },
                signatureAlgorithm: {
                    names: {
                        blockName: SIGNATURE_ALGORITHM$7
                    }
                },
                signatureValue: SIGNATURE_VALUE$4
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.acinfo = new AttributeCertificateInfoV1({ schema: asn1.result.acinfo });
        this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm });
        this.signatureValue = asn1.result.signatureValue;
    }
    toSchema() {
        return (new Sequence({
            value: [
                this.acinfo.toSchema(),
                this.signatureAlgorithm.toSchema(),
                this.signatureValue
            ]
        }));
    }
    toJSON() {
        return {
            acinfo: this.acinfo.toJSON(),
            signatureAlgorithm: this.signatureAlgorithm.toJSON(),
            signatureValue: this.signatureValue.toJSON(),
        };
    }
}
AttributeCertificateV1.CLASS_NAME = "AttributeCertificateV1";

const DIGESTED_OBJECT_TYPE = "digestedObjectType";
const OTHER_OBJECT_TYPE_ID = "otherObjectTypeID";
const DIGEST_ALGORITHM$2 = "digestAlgorithm";
const OBJECT_DIGEST = "objectDigest";
const CLEAR_PROPS$X = [
    DIGESTED_OBJECT_TYPE,
    OTHER_OBJECT_TYPE_ID,
    DIGEST_ALGORITHM$2,
    OBJECT_DIGEST,
];
class ObjectDigestInfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.digestedObjectType = getParametersValue(parameters, DIGESTED_OBJECT_TYPE, ObjectDigestInfo.defaultValues(DIGESTED_OBJECT_TYPE));
        if (OTHER_OBJECT_TYPE_ID in parameters) {
            this.otherObjectTypeID = getParametersValue(parameters, OTHER_OBJECT_TYPE_ID, ObjectDigestInfo.defaultValues(OTHER_OBJECT_TYPE_ID));
        }
        this.digestAlgorithm = getParametersValue(parameters, DIGEST_ALGORITHM$2, ObjectDigestInfo.defaultValues(DIGEST_ALGORITHM$2));
        this.objectDigest = getParametersValue(parameters, OBJECT_DIGEST, ObjectDigestInfo.defaultValues(OBJECT_DIGEST));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case DIGESTED_OBJECT_TYPE:
                return new Enumerated();
            case OTHER_OBJECT_TYPE_ID:
                return new ObjectIdentifier();
            case DIGEST_ALGORITHM$2:
                return new AlgorithmIdentifier();
            case OBJECT_DIGEST:
                return new BitString();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Enumerated({ name: (names.digestedObjectType || EMPTY_STRING) }),
                new ObjectIdentifier({
                    optional: true,
                    name: (names.otherObjectTypeID || EMPTY_STRING)
                }),
                AlgorithmIdentifier.schema(names.digestAlgorithm || {}),
                new BitString({ name: (names.objectDigest || EMPTY_STRING) }),
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$X);
        const asn1 = compareSchema(schema, schema, ObjectDigestInfo.schema({
            names: {
                digestedObjectType: DIGESTED_OBJECT_TYPE,
                otherObjectTypeID: OTHER_OBJECT_TYPE_ID,
                digestAlgorithm: {
                    names: {
                        blockName: DIGEST_ALGORITHM$2
                    }
                },
                objectDigest: OBJECT_DIGEST
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.digestedObjectType = asn1.result.digestedObjectType;
        if (OTHER_OBJECT_TYPE_ID in asn1.result) {
            this.otherObjectTypeID = asn1.result.otherObjectTypeID;
        }
        this.digestAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.digestAlgorithm });
        this.objectDigest = asn1.result.objectDigest;
    }
    toSchema() {
        const result = new Sequence({
            value: [this.digestedObjectType]
        });
        if (this.otherObjectTypeID) {
            result.valueBlock.value.push(this.otherObjectTypeID);
        }
        result.valueBlock.value.push(this.digestAlgorithm.toSchema());
        result.valueBlock.value.push(this.objectDigest);
        return result;
    }
    toJSON() {
        const result = {
            digestedObjectType: this.digestedObjectType.toJSON(),
            digestAlgorithm: this.digestAlgorithm.toJSON(),
            objectDigest: this.objectDigest.toJSON(),
        };
        if (this.otherObjectTypeID) {
            result.otherObjectTypeID = this.otherObjectTypeID.toJSON();
        }
        return result;
    }
}
ObjectDigestInfo.CLASS_NAME = "ObjectDigestInfo";

const ISSUER_NAME = "issuerName";
const BASE_CERTIFICATE_ID$1 = "baseCertificateID";
const OBJECT_DIGEST_INFO$1 = "objectDigestInfo";
const CLEAR_PROPS$W = [
    ISSUER_NAME,
    BASE_CERTIFICATE_ID$1,
    OBJECT_DIGEST_INFO$1
];
class V2Form extends PkiObject {
    constructor(parameters = {}) {
        super();
        if (ISSUER_NAME in parameters) {
            this.issuerName = getParametersValue(parameters, ISSUER_NAME, V2Form.defaultValues(ISSUER_NAME));
        }
        if (BASE_CERTIFICATE_ID$1 in parameters) {
            this.baseCertificateID = getParametersValue(parameters, BASE_CERTIFICATE_ID$1, V2Form.defaultValues(BASE_CERTIFICATE_ID$1));
        }
        if (OBJECT_DIGEST_INFO$1 in parameters) {
            this.objectDigestInfo = getParametersValue(parameters, OBJECT_DIGEST_INFO$1, V2Form.defaultValues(OBJECT_DIGEST_INFO$1));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case ISSUER_NAME:
                return new GeneralNames();
            case BASE_CERTIFICATE_ID$1:
                return new IssuerSerial();
            case OBJECT_DIGEST_INFO$1:
                return new ObjectDigestInfo();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                GeneralNames.schema({
                    names: {
                        blockName: names.issuerName
                    }
                }, true),
                new Constructed({
                    optional: true,
                    name: (names.baseCertificateID || EMPTY_STRING),
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: IssuerSerial.schema().valueBlock.value
                }),
                new Constructed({
                    optional: true,
                    name: (names.objectDigestInfo || EMPTY_STRING),
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: ObjectDigestInfo.schema().valueBlock.value
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$W);
        const asn1 = compareSchema(schema, schema, V2Form.schema({
            names: {
                issuerName: ISSUER_NAME,
                baseCertificateID: BASE_CERTIFICATE_ID$1,
                objectDigestInfo: OBJECT_DIGEST_INFO$1
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (ISSUER_NAME in asn1.result)
            this.issuerName = new GeneralNames({ schema: asn1.result.issuerName });
        if (BASE_CERTIFICATE_ID$1 in asn1.result) {
            this.baseCertificateID = new IssuerSerial({
                schema: new Sequence({
                    value: asn1.result.baseCertificateID.valueBlock.value
                })
            });
        }
        if (OBJECT_DIGEST_INFO$1 in asn1.result) {
            this.objectDigestInfo = new ObjectDigestInfo({
                schema: new Sequence({
                    value: asn1.result.objectDigestInfo.valueBlock.value
                })
            });
        }
    }
    toSchema() {
        const result = new Sequence();
        if (this.issuerName)
            result.valueBlock.value.push(this.issuerName.toSchema());
        if (this.baseCertificateID) {
            result.valueBlock.value.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: this.baseCertificateID.toSchema().valueBlock.value
            }));
        }
        if (this.objectDigestInfo) {
            result.valueBlock.value.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                value: this.objectDigestInfo.toSchema().valueBlock.value
            }));
        }
        return result;
    }
    toJSON() {
        const result = {};
        if (this.issuerName) {
            result.issuerName = this.issuerName.toJSON();
        }
        if (this.baseCertificateID) {
            result.baseCertificateID = this.baseCertificateID.toJSON();
        }
        if (this.objectDigestInfo) {
            result.objectDigestInfo = this.objectDigestInfo.toJSON();
        }
        return result;
    }
}
V2Form.CLASS_NAME = "V2Form";

const BASE_CERTIFICATE_ID = "baseCertificateID";
const ENTITY_NAME = "entityName";
const OBJECT_DIGEST_INFO = "objectDigestInfo";
const CLEAR_PROPS$V = [
    BASE_CERTIFICATE_ID,
    ENTITY_NAME,
    OBJECT_DIGEST_INFO
];
class Holder extends PkiObject {
    constructor(parameters = {}) {
        super();
        if (BASE_CERTIFICATE_ID in parameters) {
            this.baseCertificateID = getParametersValue(parameters, BASE_CERTIFICATE_ID, Holder.defaultValues(BASE_CERTIFICATE_ID));
        }
        if (ENTITY_NAME in parameters) {
            this.entityName = getParametersValue(parameters, ENTITY_NAME, Holder.defaultValues(ENTITY_NAME));
        }
        if (OBJECT_DIGEST_INFO in parameters) {
            this.objectDigestInfo = getParametersValue(parameters, OBJECT_DIGEST_INFO, Holder.defaultValues(OBJECT_DIGEST_INFO));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case BASE_CERTIFICATE_ID:
                return new IssuerSerial();
            case ENTITY_NAME:
                return new GeneralNames();
            case OBJECT_DIGEST_INFO:
                return new ObjectDigestInfo();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Constructed({
                    optional: true,
                    name: (names.baseCertificateID || EMPTY_STRING),
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: IssuerSerial.schema().valueBlock.value
                }),
                new Constructed({
                    optional: true,
                    name: (names.entityName || EMPTY_STRING),
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: GeneralNames.schema().valueBlock.value
                }),
                new Constructed({
                    optional: true,
                    name: (names.objectDigestInfo || EMPTY_STRING),
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 2
                    },
                    value: ObjectDigestInfo.schema().valueBlock.value
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$V);
        const asn1 = compareSchema(schema, schema, Holder.schema({
            names: {
                baseCertificateID: BASE_CERTIFICATE_ID,
                entityName: ENTITY_NAME,
                objectDigestInfo: OBJECT_DIGEST_INFO
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (BASE_CERTIFICATE_ID in asn1.result) {
            this.baseCertificateID = new IssuerSerial({
                schema: new Sequence({
                    value: asn1.result.baseCertificateID.valueBlock.value
                })
            });
        }
        if (ENTITY_NAME in asn1.result) {
            this.entityName = new GeneralNames({
                schema: new Sequence({
                    value: asn1.result.entityName.valueBlock.value
                })
            });
        }
        if (OBJECT_DIGEST_INFO in asn1.result) {
            this.objectDigestInfo = new ObjectDigestInfo({
                schema: new Sequence({
                    value: asn1.result.objectDigestInfo.valueBlock.value
                })
            });
        }
    }
    toSchema() {
        const result = new Sequence();
        if (this.baseCertificateID) {
            result.valueBlock.value.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: this.baseCertificateID.toSchema().valueBlock.value
            }));
        }
        if (this.entityName) {
            result.valueBlock.value.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                value: this.entityName.toSchema().valueBlock.value
            }));
        }
        if (this.objectDigestInfo) {
            result.valueBlock.value.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 2
                },
                value: this.objectDigestInfo.toSchema().valueBlock.value
            }));
        }
        return result;
    }
    toJSON() {
        const result = {};
        if (this.baseCertificateID) {
            result.baseCertificateID = this.baseCertificateID.toJSON();
        }
        if (this.entityName) {
            result.entityName = this.entityName.toJSON();
        }
        if (this.objectDigestInfo) {
            result.objectDigestInfo = this.objectDigestInfo.toJSON();
        }
        return result;
    }
}
Holder.CLASS_NAME = "Holder";

const VERSION$g = "version";
const HOLDER = "holder";
const ISSUER$3 = "issuer";
const SIGNATURE$5 = "signature";
const SERIAL_NUMBER$4 = "serialNumber";
const ATTR_CERT_VALIDITY_PERIOD = "attrCertValidityPeriod";
const ATTRIBUTES$2 = "attributes";
const ISSUER_UNIQUE_ID$1 = "issuerUniqueID";
const EXTENSIONS$3 = "extensions";
const CLEAR_PROPS$U = [
    VERSION$g,
    HOLDER,
    ISSUER$3,
    SIGNATURE$5,
    SERIAL_NUMBER$4,
    ATTR_CERT_VALIDITY_PERIOD,
    ATTRIBUTES$2,
    ISSUER_UNIQUE_ID$1,
    EXTENSIONS$3
];
class AttributeCertificateInfoV2 extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.version = getParametersValue(parameters, VERSION$g, AttributeCertificateInfoV2.defaultValues(VERSION$g));
        this.holder = getParametersValue(parameters, HOLDER, AttributeCertificateInfoV2.defaultValues(HOLDER));
        this.issuer = getParametersValue(parameters, ISSUER$3, AttributeCertificateInfoV2.defaultValues(ISSUER$3));
        this.signature = getParametersValue(parameters, SIGNATURE$5, AttributeCertificateInfoV2.defaultValues(SIGNATURE$5));
        this.serialNumber = getParametersValue(parameters, SERIAL_NUMBER$4, AttributeCertificateInfoV2.defaultValues(SERIAL_NUMBER$4));
        this.attrCertValidityPeriod = getParametersValue(parameters, ATTR_CERT_VALIDITY_PERIOD, AttributeCertificateInfoV2.defaultValues(ATTR_CERT_VALIDITY_PERIOD));
        this.attributes = getParametersValue(parameters, ATTRIBUTES$2, AttributeCertificateInfoV2.defaultValues(ATTRIBUTES$2));
        if (ISSUER_UNIQUE_ID$1 in parameters) {
            this.issuerUniqueID = getParametersValue(parameters, ISSUER_UNIQUE_ID$1, AttributeCertificateInfoV2.defaultValues(ISSUER_UNIQUE_ID$1));
        }
        if (EXTENSIONS$3 in parameters) {
            this.extensions = getParametersValue(parameters, EXTENSIONS$3, AttributeCertificateInfoV2.defaultValues(EXTENSIONS$3));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VERSION$g:
                return 1;
            case HOLDER:
                return new Holder();
            case ISSUER$3:
                return {};
            case SIGNATURE$5:
                return new AlgorithmIdentifier();
            case SERIAL_NUMBER$4:
                return new Integer();
            case ATTR_CERT_VALIDITY_PERIOD:
                return new AttCertValidityPeriod();
            case ATTRIBUTES$2:
                return [];
            case ISSUER_UNIQUE_ID$1:
                return new BitString();
            case EXTENSIONS$3:
                return new Extensions();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Integer({ name: (names.version || EMPTY_STRING) }),
                Holder.schema(names.holder || {}),
                new Choice({
                    value: [
                        GeneralNames.schema({
                            names: {
                                blockName: (names.issuer || EMPTY_STRING)
                            }
                        }),
                        new Constructed({
                            name: (names.issuer || EMPTY_STRING),
                            idBlock: {
                                tagClass: 3,
                                tagNumber: 0
                            },
                            value: V2Form.schema().valueBlock.value
                        })
                    ]
                }),
                AlgorithmIdentifier.schema(names.signature || {}),
                new Integer({ name: (names.serialNumber || EMPTY_STRING) }),
                AttCertValidityPeriod.schema(names.attrCertValidityPeriod || {}),
                new Sequence({
                    name: (names.attributes || EMPTY_STRING),
                    value: [
                        new Repeated({
                            value: Attribute.schema()
                        })
                    ]
                }),
                new BitString({
                    optional: true,
                    name: (names.issuerUniqueID || EMPTY_STRING)
                }),
                Extensions.schema(names.extensions || {}, true)
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$U);
        const asn1 = compareSchema(schema, schema, AttributeCertificateInfoV2.schema({
            names: {
                version: VERSION$g,
                holder: {
                    names: {
                        blockName: HOLDER
                    }
                },
                issuer: ISSUER$3,
                signature: {
                    names: {
                        blockName: SIGNATURE$5
                    }
                },
                serialNumber: SERIAL_NUMBER$4,
                attrCertValidityPeriod: {
                    names: {
                        blockName: ATTR_CERT_VALIDITY_PERIOD
                    }
                },
                attributes: ATTRIBUTES$2,
                issuerUniqueID: ISSUER_UNIQUE_ID$1,
                extensions: {
                    names: {
                        blockName: EXTENSIONS$3
                    }
                }
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.version = asn1.result.version.valueBlock.valueDec;
        this.holder = new Holder({ schema: asn1.result.holder });
        switch (asn1.result.issuer.idBlock.tagClass) {
            case 3:
                this.issuer = new V2Form({
                    schema: new Sequence({
                        value: asn1.result.issuer.valueBlock.value
                    })
                });
                break;
            case 1:
            default:
                throw new Error("Incorrect value for 'issuer' in AttributeCertificateInfoV2");
        }
        this.signature = new AlgorithmIdentifier({ schema: asn1.result.signature });
        this.serialNumber = asn1.result.serialNumber;
        this.attrCertValidityPeriod = new AttCertValidityPeriod({ schema: asn1.result.attrCertValidityPeriod });
        this.attributes = Array.from(asn1.result.attributes.valueBlock.value, element => new Attribute({ schema: element }));
        if (ISSUER_UNIQUE_ID$1 in asn1.result) {
            this.issuerUniqueID = asn1.result.issuerUniqueID;
        }
        if (EXTENSIONS$3 in asn1.result) {
            this.extensions = new Extensions({ schema: asn1.result.extensions });
        }
    }
    toSchema() {
        const result = new Sequence({
            value: [
                new Integer({ value: this.version }),
                this.holder.toSchema(),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: this.issuer.toSchema().valueBlock.value
                }),
                this.signature.toSchema(),
                this.serialNumber,
                this.attrCertValidityPeriod.toSchema(),
                new Sequence({
                    value: Array.from(this.attributes, o => o.toSchema())
                })
            ]
        });
        if (this.issuerUniqueID) {
            result.valueBlock.value.push(this.issuerUniqueID);
        }
        if (this.extensions) {
            result.valueBlock.value.push(this.extensions.toSchema());
        }
        return result;
    }
    toJSON() {
        const result = {
            version: this.version,
            holder: this.holder.toJSON(),
            issuer: this.issuer.toJSON(),
            signature: this.signature.toJSON(),
            serialNumber: this.serialNumber.toJSON(),
            attrCertValidityPeriod: this.attrCertValidityPeriod.toJSON(),
            attributes: Array.from(this.attributes, o => o.toJSON())
        };
        if (this.issuerUniqueID) {
            result.issuerUniqueID = this.issuerUniqueID.toJSON();
        }
        if (this.extensions) {
            result.extensions = this.extensions.toJSON();
        }
        return result;
    }
}
AttributeCertificateInfoV2.CLASS_NAME = "AttributeCertificateInfoV2";

const ACINFO = "acinfo";
const SIGNATURE_ALGORITHM$6 = "signatureAlgorithm";
const SIGNATURE_VALUE$3 = "signatureValue";
const CLEAR_PROPS$T = [
    ACINFO,
    SIGNATURE_ALGORITHM$6,
    SIGNATURE_VALUE$3,
];
class AttributeCertificateV2 extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.acinfo = getParametersValue(parameters, ACINFO, AttributeCertificateV2.defaultValues(ACINFO));
        this.signatureAlgorithm = getParametersValue(parameters, SIGNATURE_ALGORITHM$6, AttributeCertificateV2.defaultValues(SIGNATURE_ALGORITHM$6));
        this.signatureValue = getParametersValue(parameters, SIGNATURE_VALUE$3, AttributeCertificateV2.defaultValues(SIGNATURE_VALUE$3));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case ACINFO:
                return new AttributeCertificateInfoV2();
            case SIGNATURE_ALGORITHM$6:
                return new AlgorithmIdentifier();
            case SIGNATURE_VALUE$3:
                return new BitString();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                AttributeCertificateInfoV2.schema(names.acinfo || {}),
                AlgorithmIdentifier.schema(names.signatureAlgorithm || {}),
                new BitString({ name: (names.signatureValue || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$T);
        const asn1 = compareSchema(schema, schema, AttributeCertificateV2.schema({
            names: {
                acinfo: {
                    names: {
                        blockName: ACINFO
                    }
                },
                signatureAlgorithm: {
                    names: {
                        blockName: SIGNATURE_ALGORITHM$6
                    }
                },
                signatureValue: SIGNATURE_VALUE$3
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.acinfo = new AttributeCertificateInfoV2({ schema: asn1.result.acinfo });
        this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm });
        this.signatureValue = asn1.result.signatureValue;
    }
    toSchema() {
        return (new Sequence({
            value: [
                this.acinfo.toSchema(),
                this.signatureAlgorithm.toSchema(),
                this.signatureValue
            ]
        }));
    }
    toJSON() {
        return {
            acinfo: this.acinfo.toJSON(),
            signatureAlgorithm: this.signatureAlgorithm.toJSON(),
            signatureValue: this.signatureValue.toJSON(),
        };
    }
}
AttributeCertificateV2.CLASS_NAME = "AttributeCertificateV2";

const CONTENT_TYPE = "contentType";
const CONTENT = "content";
const CLEAR_PROPS$S = [CONTENT_TYPE, CONTENT];
class ContentInfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.contentType = getParametersValue(parameters, CONTENT_TYPE, ContentInfo.defaultValues(CONTENT_TYPE));
        this.content = getParametersValue(parameters, CONTENT, ContentInfo.defaultValues(CONTENT));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case CONTENT_TYPE:
                return EMPTY_STRING;
            case CONTENT:
                return new Any();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case CONTENT_TYPE:
                return (typeof memberValue === "string" &&
                    memberValue === this.defaultValues(CONTENT_TYPE));
            case CONTENT:
                return (memberValue instanceof Any);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        if (("optional" in names) === false) {
            names.optional = false;
        }
        return (new Sequence({
            name: (names.blockName || "ContentInfo"),
            optional: names.optional,
            value: [
                new ObjectIdentifier({ name: (names.contentType || CONTENT_TYPE) }),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [new Any({ name: (names.content || CONTENT) })]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$S);
        const asn1 = compareSchema(schema, schema, ContentInfo.schema());
        AsnError.assertSchema(asn1, this.className);
        this.contentType = asn1.result.contentType.valueBlock.toString();
        this.content = asn1.result.content;
    }
    toSchema() {
        return (new Sequence({
            value: [
                new ObjectIdentifier({ value: this.contentType }),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [this.content]
                })
            ]
        }));
    }
    toJSON() {
        const object = {
            contentType: this.contentType
        };
        if (!(this.content instanceof Any)) {
            object.content = this.content.toJSON();
        }
        return object;
    }
}
ContentInfo.CLASS_NAME = "ContentInfo";
ContentInfo.DATA = id_ContentType_Data;
ContentInfo.SIGNED_DATA = id_ContentType_SignedData;
ContentInfo.ENVELOPED_DATA = id_ContentType_EnvelopedData;
ContentInfo.ENCRYPTED_DATA = id_ContentType_EncryptedData;

const TYPE$1 = "type";
const VALUE$4 = "value";
const UTC_TIME_NAME = "utcTimeName";
const GENERAL_TIME_NAME = "generalTimeName";
const CLEAR_PROPS$R = [UTC_TIME_NAME, GENERAL_TIME_NAME];
var TimeType;
(function (TimeType) {
    TimeType[TimeType["UTCTime"] = 0] = "UTCTime";
    TimeType[TimeType["GeneralizedTime"] = 1] = "GeneralizedTime";
    TimeType[TimeType["empty"] = 2] = "empty";
})(TimeType || (TimeType = {}));
class Time extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.type = getParametersValue(parameters, TYPE$1, Time.defaultValues(TYPE$1));
        this.value = getParametersValue(parameters, VALUE$4, Time.defaultValues(VALUE$4));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case TYPE$1:
                return 0;
            case VALUE$4:
                return new Date(0, 0, 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}, optional = false) {
        const names = getParametersValue(parameters, "names", {});
        return (new Choice({
            optional,
            value: [
                new UTCTime({ name: (names.utcTimeName || EMPTY_STRING) }),
                new GeneralizedTime({ name: (names.generalTimeName || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$R);
        const asn1 = compareSchema(schema, schema, Time.schema({
            names: {
                utcTimeName: UTC_TIME_NAME,
                generalTimeName: GENERAL_TIME_NAME
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (UTC_TIME_NAME in asn1.result) {
            this.type = 0;
            this.value = asn1.result.utcTimeName.toDate();
        }
        if (GENERAL_TIME_NAME in asn1.result) {
            this.type = 1;
            this.value = asn1.result.generalTimeName.toDate();
        }
    }
    toSchema() {
        if (this.type === 0) {
            return new UTCTime({ valueDate: this.value });
        }
        else if (this.type === 1) {
            return new GeneralizedTime({ valueDate: this.value });
        }
        return {};
    }
    toJSON() {
        return {
            type: this.type,
            value: this.value
        };
    }
}
Time.CLASS_NAME = "Time";

const TBS$4 = "tbs";
const VERSION$f = "version";
const SERIAL_NUMBER$3 = "serialNumber";
const SIGNATURE$4 = "signature";
const ISSUER$2 = "issuer";
const NOT_BEFORE = "notBefore";
const NOT_AFTER = "notAfter";
const SUBJECT$1 = "subject";
const SUBJECT_PUBLIC_KEY_INFO = "subjectPublicKeyInfo";
const ISSUER_UNIQUE_ID = "issuerUniqueID";
const SUBJECT_UNIQUE_ID = "subjectUniqueID";
const EXTENSIONS$2 = "extensions";
const SIGNATURE_ALGORITHM$5 = "signatureAlgorithm";
const SIGNATURE_VALUE$2 = "signatureValue";
const TBS_CERTIFICATE = "tbsCertificate";
const TBS_CERTIFICATE_VERSION = `${TBS_CERTIFICATE}.${VERSION$f}`;
const TBS_CERTIFICATE_SERIAL_NUMBER = `${TBS_CERTIFICATE}.${SERIAL_NUMBER$3}`;
const TBS_CERTIFICATE_SIGNATURE = `${TBS_CERTIFICATE}.${SIGNATURE$4}`;
const TBS_CERTIFICATE_ISSUER = `${TBS_CERTIFICATE}.${ISSUER$2}`;
const TBS_CERTIFICATE_NOT_BEFORE = `${TBS_CERTIFICATE}.${NOT_BEFORE}`;
const TBS_CERTIFICATE_NOT_AFTER = `${TBS_CERTIFICATE}.${NOT_AFTER}`;
const TBS_CERTIFICATE_SUBJECT = `${TBS_CERTIFICATE}.${SUBJECT$1}`;
const TBS_CERTIFICATE_SUBJECT_PUBLIC_KEY = `${TBS_CERTIFICATE}.${SUBJECT_PUBLIC_KEY_INFO}`;
const TBS_CERTIFICATE_ISSUER_UNIQUE_ID = `${TBS_CERTIFICATE}.${ISSUER_UNIQUE_ID}`;
const TBS_CERTIFICATE_SUBJECT_UNIQUE_ID = `${TBS_CERTIFICATE}.${SUBJECT_UNIQUE_ID}`;
const TBS_CERTIFICATE_EXTENSIONS = `${TBS_CERTIFICATE}.${EXTENSIONS$2}`;
const CLEAR_PROPS$Q = [
    TBS_CERTIFICATE,
    TBS_CERTIFICATE_VERSION,
    TBS_CERTIFICATE_SERIAL_NUMBER,
    TBS_CERTIFICATE_SIGNATURE,
    TBS_CERTIFICATE_ISSUER,
    TBS_CERTIFICATE_NOT_BEFORE,
    TBS_CERTIFICATE_NOT_AFTER,
    TBS_CERTIFICATE_SUBJECT,
    TBS_CERTIFICATE_SUBJECT_PUBLIC_KEY,
    TBS_CERTIFICATE_ISSUER_UNIQUE_ID,
    TBS_CERTIFICATE_SUBJECT_UNIQUE_ID,
    TBS_CERTIFICATE_EXTENSIONS,
    SIGNATURE_ALGORITHM$5,
    SIGNATURE_VALUE$2
];
function tbsCertificate(parameters = {}) {
    const names = getParametersValue(parameters, "names", {});
    return (new Sequence({
        name: (names.blockName || TBS_CERTIFICATE),
        value: [
            new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [
                    new Integer({ name: (names.tbsCertificateVersion || TBS_CERTIFICATE_VERSION) })
                ]
            }),
            new Integer({ name: (names.tbsCertificateSerialNumber || TBS_CERTIFICATE_SERIAL_NUMBER) }),
            AlgorithmIdentifier.schema(names.signature || {
                names: {
                    blockName: TBS_CERTIFICATE_SIGNATURE
                }
            }),
            RelativeDistinguishedNames.schema(names.issuer || {
                names: {
                    blockName: TBS_CERTIFICATE_ISSUER
                }
            }),
            new Sequence({
                name: (names.tbsCertificateValidity || "tbsCertificate.validity"),
                value: [
                    Time.schema(names.notBefore || {
                        names: {
                            utcTimeName: TBS_CERTIFICATE_NOT_BEFORE,
                            generalTimeName: TBS_CERTIFICATE_NOT_BEFORE
                        }
                    }),
                    Time.schema(names.notAfter || {
                        names: {
                            utcTimeName: TBS_CERTIFICATE_NOT_AFTER,
                            generalTimeName: TBS_CERTIFICATE_NOT_AFTER
                        }
                    })
                ]
            }),
            RelativeDistinguishedNames.schema(names.subject || {
                names: {
                    blockName: TBS_CERTIFICATE_SUBJECT
                }
            }),
            PublicKeyInfo.schema(names.subjectPublicKeyInfo || {
                names: {
                    blockName: TBS_CERTIFICATE_SUBJECT_PUBLIC_KEY
                }
            }),
            new Primitive({
                name: (names.tbsCertificateIssuerUniqueID || TBS_CERTIFICATE_ISSUER_UNIQUE_ID),
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                }
            }),
            new Primitive({
                name: (names.tbsCertificateSubjectUniqueID || TBS_CERTIFICATE_SUBJECT_UNIQUE_ID),
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 2
                }
            }),
            new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 3
                },
                value: [Extensions.schema(names.extensions || {
                        names: {
                            blockName: TBS_CERTIFICATE_EXTENSIONS
                        }
                    })]
            })
        ]
    }));
}
class Certificate extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.tbsView = new Uint8Array(getParametersValue(parameters, TBS$4, Certificate.defaultValues(TBS$4)));
        this.version = getParametersValue(parameters, VERSION$f, Certificate.defaultValues(VERSION$f));
        this.serialNumber = getParametersValue(parameters, SERIAL_NUMBER$3, Certificate.defaultValues(SERIAL_NUMBER$3));
        this.signature = getParametersValue(parameters, SIGNATURE$4, Certificate.defaultValues(SIGNATURE$4));
        this.issuer = getParametersValue(parameters, ISSUER$2, Certificate.defaultValues(ISSUER$2));
        this.notBefore = getParametersValue(parameters, NOT_BEFORE, Certificate.defaultValues(NOT_BEFORE));
        this.notAfter = getParametersValue(parameters, NOT_AFTER, Certificate.defaultValues(NOT_AFTER));
        this.subject = getParametersValue(parameters, SUBJECT$1, Certificate.defaultValues(SUBJECT$1));
        this.subjectPublicKeyInfo = getParametersValue(parameters, SUBJECT_PUBLIC_KEY_INFO, Certificate.defaultValues(SUBJECT_PUBLIC_KEY_INFO));
        if (ISSUER_UNIQUE_ID in parameters) {
            this.issuerUniqueID = getParametersValue(parameters, ISSUER_UNIQUE_ID, Certificate.defaultValues(ISSUER_UNIQUE_ID));
        }
        if (SUBJECT_UNIQUE_ID in parameters) {
            this.subjectUniqueID = getParametersValue(parameters, SUBJECT_UNIQUE_ID, Certificate.defaultValues(SUBJECT_UNIQUE_ID));
        }
        if (EXTENSIONS$2 in parameters) {
            this.extensions = getParametersValue(parameters, EXTENSIONS$2, Certificate.defaultValues(EXTENSIONS$2));
        }
        this.signatureAlgorithm = getParametersValue(parameters, SIGNATURE_ALGORITHM$5, Certificate.defaultValues(SIGNATURE_ALGORITHM$5));
        this.signatureValue = getParametersValue(parameters, SIGNATURE_VALUE$2, Certificate.defaultValues(SIGNATURE_VALUE$2));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    get tbs() {
        return BufferSourceConverter.toArrayBuffer(this.tbsView);
    }
    set tbs(value) {
        this.tbsView = new Uint8Array(value);
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case TBS$4:
                return EMPTY_BUFFER;
            case VERSION$f:
                return 0;
            case SERIAL_NUMBER$3:
                return new Integer();
            case SIGNATURE$4:
                return new AlgorithmIdentifier();
            case ISSUER$2:
                return new RelativeDistinguishedNames();
            case NOT_BEFORE:
                return new Time();
            case NOT_AFTER:
                return new Time();
            case SUBJECT$1:
                return new RelativeDistinguishedNames();
            case SUBJECT_PUBLIC_KEY_INFO:
                return new PublicKeyInfo();
            case ISSUER_UNIQUE_ID:
                return EMPTY_BUFFER;
            case SUBJECT_UNIQUE_ID:
                return EMPTY_BUFFER;
            case EXTENSIONS$2:
                return [];
            case SIGNATURE_ALGORITHM$5:
                return new AlgorithmIdentifier();
            case SIGNATURE_VALUE$2:
                return new BitString();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                tbsCertificate(names.tbsCertificate),
                AlgorithmIdentifier.schema(names.signatureAlgorithm || {
                    names: {
                        blockName: SIGNATURE_ALGORITHM$5
                    }
                }),
                new BitString({ name: (names.signatureValue || SIGNATURE_VALUE$2) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$Q);
        const asn1 = compareSchema(schema, schema, Certificate.schema({
            names: {
                tbsCertificate: {
                    names: {
                        extensions: {
                            names: {
                                extensions: TBS_CERTIFICATE_EXTENSIONS
                            }
                        }
                    }
                }
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.tbsView = asn1.result.tbsCertificate.valueBeforeDecodeView;
        if (TBS_CERTIFICATE_VERSION in asn1.result)
            this.version = asn1.result[TBS_CERTIFICATE_VERSION].valueBlock.valueDec;
        this.serialNumber = asn1.result[TBS_CERTIFICATE_SERIAL_NUMBER];
        this.signature = new AlgorithmIdentifier({ schema: asn1.result[TBS_CERTIFICATE_SIGNATURE] });
        this.issuer = new RelativeDistinguishedNames({ schema: asn1.result[TBS_CERTIFICATE_ISSUER] });
        this.notBefore = new Time({ schema: asn1.result[TBS_CERTIFICATE_NOT_BEFORE] });
        this.notAfter = new Time({ schema: asn1.result[TBS_CERTIFICATE_NOT_AFTER] });
        this.subject = new RelativeDistinguishedNames({ schema: asn1.result[TBS_CERTIFICATE_SUBJECT] });
        this.subjectPublicKeyInfo = new PublicKeyInfo({ schema: asn1.result[TBS_CERTIFICATE_SUBJECT_PUBLIC_KEY] });
        if (TBS_CERTIFICATE_ISSUER_UNIQUE_ID in asn1.result)
            this.issuerUniqueID = asn1.result[TBS_CERTIFICATE_ISSUER_UNIQUE_ID].valueBlock.valueHex;
        if (TBS_CERTIFICATE_SUBJECT_UNIQUE_ID in asn1.result)
            this.subjectUniqueID = asn1.result[TBS_CERTIFICATE_SUBJECT_UNIQUE_ID].valueBlock.valueHex;
        if (TBS_CERTIFICATE_EXTENSIONS in asn1.result)
            this.extensions = Array.from(asn1.result[TBS_CERTIFICATE_EXTENSIONS], element => new Extension({ schema: element }));
        this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm });
        this.signatureValue = asn1.result.signatureValue;
    }
    encodeTBS() {
        const outputArray = [];
        if ((VERSION$f in this) && (this.version !== Certificate.defaultValues(VERSION$f))) {
            outputArray.push(new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [
                    new Integer({ value: this.version })
                ]
            }));
        }
        outputArray.push(this.serialNumber);
        outputArray.push(this.signature.toSchema());
        outputArray.push(this.issuer.toSchema());
        outputArray.push(new Sequence({
            value: [
                this.notBefore.toSchema(),
                this.notAfter.toSchema()
            ]
        }));
        outputArray.push(this.subject.toSchema());
        outputArray.push(this.subjectPublicKeyInfo.toSchema());
        if (this.issuerUniqueID) {
            outputArray.push(new Primitive({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                valueHex: this.issuerUniqueID
            }));
        }
        if (this.subjectUniqueID) {
            outputArray.push(new Primitive({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 2
                },
                valueHex: this.subjectUniqueID
            }));
        }
        if (this.extensions) {
            outputArray.push(new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 3
                },
                value: [new Sequence({
                        value: Array.from(this.extensions, o => o.toSchema())
                    })]
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toSchema(encodeFlag = false) {
        let tbsSchema;
        if (encodeFlag === false) {
            if (!this.tbsView.byteLength) {
                return Certificate.schema().value[0];
            }
            const asn1 = fromBER(this.tbsView);
            AsnError.assert(asn1, "TBS Certificate");
            tbsSchema = asn1.result;
        }
        else {
            tbsSchema = this.encodeTBS();
        }
        return (new Sequence({
            value: [
                tbsSchema,
                this.signatureAlgorithm.toSchema(),
                this.signatureValue
            ]
        }));
    }
    toJSON() {
        const res = {
            tbs: Convert.ToHex(this.tbsView),
            version: this.version,
            serialNumber: this.serialNumber.toJSON(),
            signature: this.signature.toJSON(),
            issuer: this.issuer.toJSON(),
            notBefore: this.notBefore.toJSON(),
            notAfter: this.notAfter.toJSON(),
            subject: this.subject.toJSON(),
            subjectPublicKeyInfo: this.subjectPublicKeyInfo.toJSON(),
            signatureAlgorithm: this.signatureAlgorithm.toJSON(),
            signatureValue: this.signatureValue.toJSON(),
        };
        if ((VERSION$f in this) && (this.version !== Certificate.defaultValues(VERSION$f))) {
            res.version = this.version;
        }
        if (this.issuerUniqueID) {
            res.issuerUniqueID = Convert.ToHex(this.issuerUniqueID);
        }
        if (this.subjectUniqueID) {
            res.subjectUniqueID = Convert.ToHex(this.subjectUniqueID);
        }
        if (this.extensions) {
            res.extensions = Array.from(this.extensions, o => o.toJSON());
        }
        return res;
    }
    getPublicKey(parameters, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            return crypto.getPublicKey(this.subjectPublicKeyInfo, this.signatureAlgorithm, parameters);
        });
    }
    getKeyHash(hashAlgorithm = "SHA-1", crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            return crypto.digest({ name: hashAlgorithm }, this.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHexView);
        });
    }
    sign(privateKey, hashAlgorithm = "SHA-1", crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!privateKey) {
                throw new Error("Need to provide a private key for signing");
            }
            const signatureParameters = yield crypto.getSignatureParameters(privateKey, hashAlgorithm);
            const parameters = signatureParameters.parameters;
            this.signature = signatureParameters.signatureAlgorithm;
            this.signatureAlgorithm = signatureParameters.signatureAlgorithm;
            this.tbsView = new Uint8Array(this.encodeTBS().toBER());
            const signature = yield crypto.signWithPrivateKey(this.tbsView, privateKey, parameters);
            this.signatureValue = new BitString({ valueHex: signature });
        });
    }
    verify(issuerCertificate, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            let subjectPublicKeyInfo;
            if (issuerCertificate) {
                subjectPublicKeyInfo = issuerCertificate.subjectPublicKeyInfo;
            }
            else if (this.issuer.isEqual(this.subject)) {
                subjectPublicKeyInfo = this.subjectPublicKeyInfo;
            }
            if (!(subjectPublicKeyInfo instanceof PublicKeyInfo)) {
                throw new Error("Please provide issuer certificate as a parameter");
            }
            return crypto.verifyWithPublicKey(this.tbsView, this.signatureValue, subjectPublicKeyInfo, this.signatureAlgorithm);
        });
    }
}
Certificate.CLASS_NAME = "Certificate";
function checkCA(cert, signerCert = null) {
    if (signerCert && cert.issuer.isEqual(signerCert.issuer) && cert.serialNumber.isEqual(signerCert.serialNumber)) {
        return null;
    }
    let isCA = false;
    if (cert.extensions) {
        for (const extension of cert.extensions) {
            if (extension.extnID === id_BasicConstraints && extension.parsedValue instanceof BasicConstraints) {
                if (extension.parsedValue.cA) {
                    isCA = true;
                    break;
                }
            }
        }
    }
    if (isCA) {
        return cert;
    }
    return null;
}

const CERT_ID$1 = "certId";
const CERT_VALUE = "certValue";
const PARSED_VALUE$4 = "parsedValue";
const CLEAR_PROPS$P = [
    CERT_ID$1,
    CERT_VALUE
];
class CertBag extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.certId = getParametersValue(parameters, CERT_ID$1, CertBag.defaultValues(CERT_ID$1));
        this.certValue = getParametersValue(parameters, CERT_VALUE, CertBag.defaultValues(CERT_VALUE));
        if (PARSED_VALUE$4 in parameters) {
            this.parsedValue = getParametersValue(parameters, PARSED_VALUE$4, CertBag.defaultValues(PARSED_VALUE$4));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case CERT_ID$1:
                return EMPTY_STRING;
            case CERT_VALUE:
                return (new Any());
            case PARSED_VALUE$4:
                return {};
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case CERT_ID$1:
                return (memberValue === EMPTY_STRING);
            case CERT_VALUE:
                return (memberValue instanceof Any);
            case PARSED_VALUE$4:
                return ((memberValue instanceof Object) && (Object.keys(memberValue).length === 0));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.id || "id") }),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [new Any({ name: (names.value || "value") })]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$P);
        const asn1 = compareSchema(schema, schema, CertBag.schema({
            names: {
                id: CERT_ID$1,
                value: CERT_VALUE
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.certId = asn1.result.certId.valueBlock.toString();
        this.certValue = asn1.result.certValue;
        const certValueHex = this.certValue.valueBlock.valueHexView;
        switch (this.certId) {
            case id_CertBag_X509Certificate:
                {
                    try {
                        this.parsedValue = Certificate.fromBER(certValueHex);
                    }
                    catch (ex) {
                        AttributeCertificateV2.fromBER(certValueHex);
                    }
                }
                break;
            case id_CertBag_AttributeCertificate:
                {
                    this.parsedValue = AttributeCertificateV2.fromBER(certValueHex);
                }
                break;
            case id_CertBag_SDSICertificate:
            default:
                throw new Error(`Incorrect CERT_ID value in CertBag: ${this.certId}`);
        }
    }
    toSchema() {
        if (PARSED_VALUE$4 in this) {
            if ("acinfo" in this.parsedValue) {
                this.certId = id_CertBag_AttributeCertificate;
            }
            else {
                this.certId = id_CertBag_X509Certificate;
            }
            this.certValue = new OctetString({ valueHex: this.parsedValue.toSchema().toBER(false) });
        }
        return (new Sequence({
            value: [
                new ObjectIdentifier({ value: this.certId }),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [(("toSchema" in this.certValue) ? this.certValue.toSchema() : this.certValue)]
                })
            ]
        }));
    }
    toJSON() {
        return {
            certId: this.certId,
            certValue: this.certValue.toJSON()
        };
    }
}
CertBag.CLASS_NAME = "CertBag";

const USER_CERTIFICATE = "userCertificate";
const REVOCATION_DATE = "revocationDate";
const CRL_ENTRY_EXTENSIONS = "crlEntryExtensions";
const CLEAR_PROPS$O = [
    USER_CERTIFICATE,
    REVOCATION_DATE,
    CRL_ENTRY_EXTENSIONS
];
class RevokedCertificate extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.userCertificate = getParametersValue(parameters, USER_CERTIFICATE, RevokedCertificate.defaultValues(USER_CERTIFICATE));
        this.revocationDate = getParametersValue(parameters, REVOCATION_DATE, RevokedCertificate.defaultValues(REVOCATION_DATE));
        if (CRL_ENTRY_EXTENSIONS in parameters) {
            this.crlEntryExtensions = getParametersValue(parameters, CRL_ENTRY_EXTENSIONS, RevokedCertificate.defaultValues(CRL_ENTRY_EXTENSIONS));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case USER_CERTIFICATE:
                return new Integer();
            case REVOCATION_DATE:
                return new Time();
            case CRL_ENTRY_EXTENSIONS:
                return new Extensions();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Integer({ name: (names.userCertificate || USER_CERTIFICATE) }),
                Time.schema({
                    names: {
                        utcTimeName: (names.revocationDate || REVOCATION_DATE),
                        generalTimeName: (names.revocationDate || REVOCATION_DATE)
                    }
                }),
                Extensions.schema({
                    names: {
                        blockName: (names.crlEntryExtensions || CRL_ENTRY_EXTENSIONS)
                    }
                }, true)
            ]
        });
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$O);
        const asn1 = compareSchema(schema, schema, RevokedCertificate.schema());
        AsnError.assertSchema(asn1, this.className);
        this.userCertificate = asn1.result.userCertificate;
        this.revocationDate = new Time({ schema: asn1.result.revocationDate });
        if (CRL_ENTRY_EXTENSIONS in asn1.result) {
            this.crlEntryExtensions = new Extensions({ schema: asn1.result.crlEntryExtensions });
        }
    }
    toSchema() {
        const outputArray = [
            this.userCertificate,
            this.revocationDate.toSchema()
        ];
        if (this.crlEntryExtensions) {
            outputArray.push(this.crlEntryExtensions.toSchema());
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            userCertificate: this.userCertificate.toJSON(),
            revocationDate: this.revocationDate.toJSON(),
        };
        if (this.crlEntryExtensions) {
            res.crlEntryExtensions = this.crlEntryExtensions.toJSON();
        }
        return res;
    }
}
RevokedCertificate.CLASS_NAME = "RevokedCertificate";

const TBS$3 = "tbs";
const VERSION$e = "version";
const SIGNATURE$3 = "signature";
const ISSUER$1 = "issuer";
const THIS_UPDATE$1 = "thisUpdate";
const NEXT_UPDATE$1 = "nextUpdate";
const REVOKED_CERTIFICATES = "revokedCertificates";
const CRL_EXTENSIONS = "crlExtensions";
const SIGNATURE_ALGORITHM$4 = "signatureAlgorithm";
const SIGNATURE_VALUE$1 = "signatureValue";
const TBS_CERT_LIST = "tbsCertList";
const TBS_CERT_LIST_VERSION = `${TBS_CERT_LIST}.version`;
const TBS_CERT_LIST_SIGNATURE = `${TBS_CERT_LIST}.signature`;
const TBS_CERT_LIST_ISSUER = `${TBS_CERT_LIST}.issuer`;
const TBS_CERT_LIST_THIS_UPDATE = `${TBS_CERT_LIST}.thisUpdate`;
const TBS_CERT_LIST_NEXT_UPDATE = `${TBS_CERT_LIST}.nextUpdate`;
const TBS_CERT_LIST_REVOKED_CERTIFICATES = `${TBS_CERT_LIST}.revokedCertificates`;
const TBS_CERT_LIST_EXTENSIONS = `${TBS_CERT_LIST}.extensions`;
const CLEAR_PROPS$N = [
    TBS_CERT_LIST,
    TBS_CERT_LIST_VERSION,
    TBS_CERT_LIST_SIGNATURE,
    TBS_CERT_LIST_ISSUER,
    TBS_CERT_LIST_THIS_UPDATE,
    TBS_CERT_LIST_NEXT_UPDATE,
    TBS_CERT_LIST_REVOKED_CERTIFICATES,
    TBS_CERT_LIST_EXTENSIONS,
    SIGNATURE_ALGORITHM$4,
    SIGNATURE_VALUE$1
];
function tbsCertList(parameters = {}) {
    const names = getParametersValue(parameters, "names", {});
    return (new Sequence({
        name: (names.blockName || TBS_CERT_LIST),
        value: [
            new Integer({
                optional: true,
                name: (names.tbsCertListVersion || TBS_CERT_LIST_VERSION),
                value: 2
            }),
            AlgorithmIdentifier.schema(names.signature || {
                names: {
                    blockName: TBS_CERT_LIST_SIGNATURE
                }
            }),
            RelativeDistinguishedNames.schema(names.issuer || {
                names: {
                    blockName: TBS_CERT_LIST_ISSUER
                }
            }),
            Time.schema(names.tbsCertListThisUpdate || {
                names: {
                    utcTimeName: TBS_CERT_LIST_THIS_UPDATE,
                    generalTimeName: TBS_CERT_LIST_THIS_UPDATE
                }
            }),
            Time.schema(names.tbsCertListNextUpdate || {
                names: {
                    utcTimeName: TBS_CERT_LIST_NEXT_UPDATE,
                    generalTimeName: TBS_CERT_LIST_NEXT_UPDATE
                }
            }, true),
            new Sequence({
                optional: true,
                value: [
                    new Repeated({
                        name: (names.tbsCertListRevokedCertificates || TBS_CERT_LIST_REVOKED_CERTIFICATES),
                        value: new Sequence({
                            value: [
                                new Integer(),
                                Time.schema(),
                                Extensions.schema({}, true)
                            ]
                        })
                    })
                ]
            }),
            new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [Extensions.schema(names.crlExtensions || {
                        names: {
                            blockName: TBS_CERT_LIST_EXTENSIONS
                        }
                    })]
            })
        ]
    }));
}
const WELL_KNOWN_EXTENSIONS = [
    id_AuthorityKeyIdentifier,
    id_IssuerAltName,
    id_CRLNumber,
    id_BaseCRLNumber,
    id_IssuingDistributionPoint,
    id_FreshestCRL,
    id_AuthorityInfoAccess,
    id_CRLReason,
    id_InvalidityDate,
    id_CertificateIssuer,
];
class CertificateRevocationList extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.tbsView = new Uint8Array(getParametersValue(parameters, TBS$3, CertificateRevocationList.defaultValues(TBS$3)));
        this.version = getParametersValue(parameters, VERSION$e, CertificateRevocationList.defaultValues(VERSION$e));
        this.signature = getParametersValue(parameters, SIGNATURE$3, CertificateRevocationList.defaultValues(SIGNATURE$3));
        this.issuer = getParametersValue(parameters, ISSUER$1, CertificateRevocationList.defaultValues(ISSUER$1));
        this.thisUpdate = getParametersValue(parameters, THIS_UPDATE$1, CertificateRevocationList.defaultValues(THIS_UPDATE$1));
        if (NEXT_UPDATE$1 in parameters) {
            this.nextUpdate = getParametersValue(parameters, NEXT_UPDATE$1, CertificateRevocationList.defaultValues(NEXT_UPDATE$1));
        }
        if (REVOKED_CERTIFICATES in parameters) {
            this.revokedCertificates = getParametersValue(parameters, REVOKED_CERTIFICATES, CertificateRevocationList.defaultValues(REVOKED_CERTIFICATES));
        }
        if (CRL_EXTENSIONS in parameters) {
            this.crlExtensions = getParametersValue(parameters, CRL_EXTENSIONS, CertificateRevocationList.defaultValues(CRL_EXTENSIONS));
        }
        this.signatureAlgorithm = getParametersValue(parameters, SIGNATURE_ALGORITHM$4, CertificateRevocationList.defaultValues(SIGNATURE_ALGORITHM$4));
        this.signatureValue = getParametersValue(parameters, SIGNATURE_VALUE$1, CertificateRevocationList.defaultValues(SIGNATURE_VALUE$1));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    get tbs() {
        return BufferSourceConverter.toArrayBuffer(this.tbsView);
    }
    set tbs(value) {
        this.tbsView = new Uint8Array(value);
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case TBS$3:
                return EMPTY_BUFFER;
            case VERSION$e:
                return 0;
            case SIGNATURE$3:
                return new AlgorithmIdentifier();
            case ISSUER$1:
                return new RelativeDistinguishedNames();
            case THIS_UPDATE$1:
                return new Time();
            case NEXT_UPDATE$1:
                return new Time();
            case REVOKED_CERTIFICATES:
                return [];
            case CRL_EXTENSIONS:
                return new Extensions();
            case SIGNATURE_ALGORITHM$4:
                return new AlgorithmIdentifier();
            case SIGNATURE_VALUE$1:
                return new BitString();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || "CertificateList"),
            value: [
                tbsCertList(parameters),
                AlgorithmIdentifier.schema(names.signatureAlgorithm || {
                    names: {
                        blockName: SIGNATURE_ALGORITHM$4
                    }
                }),
                new BitString({ name: (names.signatureValue || SIGNATURE_VALUE$1) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$N);
        const asn1 = compareSchema(schema, schema, CertificateRevocationList.schema());
        AsnError.assertSchema(asn1, this.className);
        this.tbsView = asn1.result.tbsCertList.valueBeforeDecodeView;
        if (TBS_CERT_LIST_VERSION in asn1.result) {
            this.version = asn1.result[TBS_CERT_LIST_VERSION].valueBlock.valueDec;
        }
        this.signature = new AlgorithmIdentifier({ schema: asn1.result[TBS_CERT_LIST_SIGNATURE] });
        this.issuer = new RelativeDistinguishedNames({ schema: asn1.result[TBS_CERT_LIST_ISSUER] });
        this.thisUpdate = new Time({ schema: asn1.result[TBS_CERT_LIST_THIS_UPDATE] });
        if (TBS_CERT_LIST_NEXT_UPDATE in asn1.result) {
            this.nextUpdate = new Time({ schema: asn1.result[TBS_CERT_LIST_NEXT_UPDATE] });
        }
        if (TBS_CERT_LIST_REVOKED_CERTIFICATES in asn1.result) {
            this.revokedCertificates = Array.from(asn1.result[TBS_CERT_LIST_REVOKED_CERTIFICATES], element => new RevokedCertificate({ schema: element }));
        }
        if (TBS_CERT_LIST_EXTENSIONS in asn1.result) {
            this.crlExtensions = new Extensions({ schema: asn1.result[TBS_CERT_LIST_EXTENSIONS] });
        }
        this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm });
        this.signatureValue = asn1.result.signatureValue;
    }
    encodeTBS() {
        const outputArray = [];
        if (this.version !== CertificateRevocationList.defaultValues(VERSION$e)) {
            outputArray.push(new Integer({ value: this.version }));
        }
        outputArray.push(this.signature.toSchema());
        outputArray.push(this.issuer.toSchema());
        outputArray.push(this.thisUpdate.toSchema());
        if (this.nextUpdate) {
            outputArray.push(this.nextUpdate.toSchema());
        }
        if (this.revokedCertificates) {
            outputArray.push(new Sequence({
                value: Array.from(this.revokedCertificates, o => o.toSchema())
            }));
        }
        if (this.crlExtensions) {
            outputArray.push(new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [
                    this.crlExtensions.toSchema()
                ]
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toSchema(encodeFlag = false) {
        let tbsSchema;
        if (!encodeFlag) {
            if (!this.tbsView.byteLength) {
                return CertificateRevocationList.schema();
            }
            const asn1 = fromBER(this.tbsView);
            AsnError.assert(asn1, "TBS Certificate Revocation List");
            tbsSchema = asn1.result;
        }
        else {
            tbsSchema = this.encodeTBS();
        }
        return (new Sequence({
            value: [
                tbsSchema,
                this.signatureAlgorithm.toSchema(),
                this.signatureValue
            ]
        }));
    }
    toJSON() {
        const res = {
            tbs: Convert.ToHex(this.tbsView),
            version: this.version,
            signature: this.signature.toJSON(),
            issuer: this.issuer.toJSON(),
            thisUpdate: this.thisUpdate.toJSON(),
            signatureAlgorithm: this.signatureAlgorithm.toJSON(),
            signatureValue: this.signatureValue.toJSON()
        };
        if (this.version !== CertificateRevocationList.defaultValues(VERSION$e))
            res.version = this.version;
        if (this.nextUpdate) {
            res.nextUpdate = this.nextUpdate.toJSON();
        }
        if (this.revokedCertificates) {
            res.revokedCertificates = Array.from(this.revokedCertificates, o => o.toJSON());
        }
        if (this.crlExtensions) {
            res.crlExtensions = this.crlExtensions.toJSON();
        }
        return res;
    }
    isCertificateRevoked(certificate) {
        if (!this.issuer.isEqual(certificate.issuer)) {
            return false;
        }
        if (!this.revokedCertificates) {
            return false;
        }
        for (const revokedCertificate of this.revokedCertificates) {
            if (revokedCertificate.userCertificate.isEqual(certificate.serialNumber)) {
                return true;
            }
        }
        return false;
    }
    sign(privateKey, hashAlgorithm = "SHA-1", crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!privateKey) {
                throw new Error("Need to provide a private key for signing");
            }
            const signatureParameters = yield crypto.getSignatureParameters(privateKey, hashAlgorithm);
            const { parameters } = signatureParameters;
            this.signature = signatureParameters.signatureAlgorithm;
            this.signatureAlgorithm = signatureParameters.signatureAlgorithm;
            this.tbsView = new Uint8Array(this.encodeTBS().toBER());
            const signature = yield crypto.signWithPrivateKey(this.tbsView, privateKey, parameters);
            this.signatureValue = new BitString({ valueHex: signature });
        });
    }
    verify(parameters = {}, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            let subjectPublicKeyInfo;
            if (parameters.issuerCertificate) {
                subjectPublicKeyInfo = parameters.issuerCertificate.subjectPublicKeyInfo;
                if (!this.issuer.isEqual(parameters.issuerCertificate.subject)) {
                    return false;
                }
            }
            if (parameters.publicKeyInfo) {
                subjectPublicKeyInfo = parameters.publicKeyInfo;
            }
            if (!subjectPublicKeyInfo) {
                throw new Error("Issuer's certificate must be provided as an input parameter");
            }
            if (this.crlExtensions) {
                for (const extension of this.crlExtensions.extensions) {
                    if (extension.critical) {
                        if (!WELL_KNOWN_EXTENSIONS.includes(extension.extnID))
                            return false;
                    }
                }
            }
            return crypto.verifyWithPublicKey(this.tbsView, this.signatureValue, subjectPublicKeyInfo, this.signatureAlgorithm);
        });
    }
}
CertificateRevocationList.CLASS_NAME = "CertificateRevocationList";

const CRL_ID = "crlId";
const CRL_VALUE = "crlValue";
const PARSED_VALUE$3 = "parsedValue";
const CLEAR_PROPS$M = [
    CRL_ID,
    CRL_VALUE,
];
class CRLBag extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.crlId = getParametersValue(parameters, CRL_ID, CRLBag.defaultValues(CRL_ID));
        this.crlValue = getParametersValue(parameters, CRL_VALUE, CRLBag.defaultValues(CRL_VALUE));
        if (PARSED_VALUE$3 in parameters) {
            this.parsedValue = getParametersValue(parameters, PARSED_VALUE$3, CRLBag.defaultValues(PARSED_VALUE$3));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case CRL_ID:
                return EMPTY_STRING;
            case CRL_VALUE:
                return (new Any());
            case PARSED_VALUE$3:
                return {};
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case CRL_ID:
                return (memberValue === EMPTY_STRING);
            case CRL_VALUE:
                return (memberValue instanceof Any);
            case PARSED_VALUE$3:
                return ((memberValue instanceof Object) && (Object.keys(memberValue).length === 0));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.id || "id") }),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [new Any({ name: (names.value || "value") })]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$M);
        const asn1 = compareSchema(schema, schema, CRLBag.schema({
            names: {
                id: CRL_ID,
                value: CRL_VALUE
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.crlId = asn1.result.crlId.valueBlock.toString();
        this.crlValue = asn1.result.crlValue;
        switch (this.crlId) {
            case id_CRLBag_X509CRL:
                {
                    this.parsedValue = CertificateRevocationList.fromBER(this.certValue.valueBlock.valueHex);
                }
                break;
            default:
                throw new Error(`Incorrect CRL_ID value in CRLBag: ${this.crlId}`);
        }
    }
    toSchema() {
        if (this.parsedValue) {
            this.crlId = id_CRLBag_X509CRL;
            this.crlValue = new OctetString({ valueHex: this.parsedValue.toSchema().toBER(false) });
        }
        return (new Sequence({
            value: [
                new ObjectIdentifier({ value: this.crlId }),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [this.crlValue.toSchema()]
                })
            ]
        }));
    }
    toJSON() {
        return {
            crlId: this.crlId,
            crlValue: this.crlValue.toJSON()
        };
    }
}
CRLBag.CLASS_NAME = "CRLBag";

const VERSION$d = "version";
const ENCRYPTED_CONTENT_INFO$1 = "encryptedContentInfo";
const UNPROTECTED_ATTRS$1 = "unprotectedAttrs";
const CLEAR_PROPS$L = [
    VERSION$d,
    ENCRYPTED_CONTENT_INFO$1,
    UNPROTECTED_ATTRS$1,
];
class EncryptedData extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.version = getParametersValue(parameters, VERSION$d, EncryptedData.defaultValues(VERSION$d));
        this.encryptedContentInfo = getParametersValue(parameters, ENCRYPTED_CONTENT_INFO$1, EncryptedData.defaultValues(ENCRYPTED_CONTENT_INFO$1));
        if (UNPROTECTED_ATTRS$1 in parameters) {
            this.unprotectedAttrs = getParametersValue(parameters, UNPROTECTED_ATTRS$1, EncryptedData.defaultValues(UNPROTECTED_ATTRS$1));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VERSION$d:
                return 0;
            case ENCRYPTED_CONTENT_INFO$1:
                return new EncryptedContentInfo();
            case UNPROTECTED_ATTRS$1:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case VERSION$d:
                return (memberValue === 0);
            case ENCRYPTED_CONTENT_INFO$1:
                return ((EncryptedContentInfo.compareWithDefault("contentType", memberValue.contentType)) &&
                    (EncryptedContentInfo.compareWithDefault("contentEncryptionAlgorithm", memberValue.contentEncryptionAlgorithm)) &&
                    (EncryptedContentInfo.compareWithDefault("encryptedContent", memberValue.encryptedContent)));
            case UNPROTECTED_ATTRS$1:
                return (memberValue.length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Integer({ name: (names.version || EMPTY_STRING) }),
                EncryptedContentInfo.schema(names.encryptedContentInfo || {}),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: [
                        new Repeated({
                            name: (names.unprotectedAttrs || EMPTY_STRING),
                            value: Attribute.schema()
                        })
                    ]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$L);
        const asn1 = compareSchema(schema, schema, EncryptedData.schema({
            names: {
                version: VERSION$d,
                encryptedContentInfo: {
                    names: {
                        blockName: ENCRYPTED_CONTENT_INFO$1
                    }
                },
                unprotectedAttrs: UNPROTECTED_ATTRS$1
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.version = asn1.result.version.valueBlock.valueDec;
        this.encryptedContentInfo = new EncryptedContentInfo({ schema: asn1.result.encryptedContentInfo });
        if (UNPROTECTED_ATTRS$1 in asn1.result)
            this.unprotectedAttrs = Array.from(asn1.result.unprotectedAttrs, element => new Attribute({ schema: element }));
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(new Integer({ value: this.version }));
        outputArray.push(this.encryptedContentInfo.toSchema());
        if (this.unprotectedAttrs) {
            outputArray.push(new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                value: Array.from(this.unprotectedAttrs, o => o.toSchema())
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            version: this.version,
            encryptedContentInfo: this.encryptedContentInfo.toJSON()
        };
        if (this.unprotectedAttrs)
            res.unprotectedAttrs = Array.from(this.unprotectedAttrs, o => o.toJSON());
        return res;
    }
    encrypt(parameters) {
        return __awaiter(this, void 0, void 0, function* () {
            ArgumentError.assert(parameters, "parameters", "object");
            const encryptParams = Object.assign(Object.assign({}, parameters), { contentType: "1.2.840.113549.1.7.1" });
            this.encryptedContentInfo = yield getCrypto(true).encryptEncryptedContentInfo(encryptParams);
        });
    }
    decrypt(parameters, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            ArgumentError.assert(parameters, "parameters", "object");
            const decryptParams = Object.assign(Object.assign({}, parameters), { encryptedContentInfo: this.encryptedContentInfo });
            return crypto.decryptEncryptedContentInfo(decryptParams);
        });
    }
}
EncryptedData.CLASS_NAME = "EncryptedData";

const ENCRYPTION_ALGORITHM = "encryptionAlgorithm";
const ENCRYPTED_DATA = "encryptedData";
const PARSED_VALUE$2 = "parsedValue";
const CLEAR_PROPS$K = [
    ENCRYPTION_ALGORITHM,
    ENCRYPTED_DATA,
];
class PKCS8ShroudedKeyBag extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.encryptionAlgorithm = getParametersValue(parameters, ENCRYPTION_ALGORITHM, PKCS8ShroudedKeyBag.defaultValues(ENCRYPTION_ALGORITHM));
        this.encryptedData = getParametersValue(parameters, ENCRYPTED_DATA, PKCS8ShroudedKeyBag.defaultValues(ENCRYPTED_DATA));
        if (PARSED_VALUE$2 in parameters) {
            this.parsedValue = getParametersValue(parameters, PARSED_VALUE$2, PKCS8ShroudedKeyBag.defaultValues(PARSED_VALUE$2));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case ENCRYPTION_ALGORITHM:
                return (new AlgorithmIdentifier());
            case ENCRYPTED_DATA:
                return (new OctetString());
            case PARSED_VALUE$2:
                return {};
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case ENCRYPTION_ALGORITHM:
                return ((AlgorithmIdentifier.compareWithDefault("algorithmId", memberValue.algorithmId)) &&
                    (("algorithmParams" in memberValue) === false));
            case ENCRYPTED_DATA:
                return (memberValue.isEqual(PKCS8ShroudedKeyBag.defaultValues(memberName)));
            case PARSED_VALUE$2:
                return ((memberValue instanceof Object) && (Object.keys(memberValue).length === 0));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                AlgorithmIdentifier.schema(names.encryptionAlgorithm || {
                    names: {
                        blockName: ENCRYPTION_ALGORITHM
                    }
                }),
                new Choice({
                    value: [
                        new OctetString({ name: (names.encryptedData || ENCRYPTED_DATA) }),
                        new OctetString({
                            idBlock: {
                                isConstructed: true
                            },
                            name: (names.encryptedData || ENCRYPTED_DATA)
                        })
                    ]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$K);
        const asn1 = compareSchema(schema, schema, PKCS8ShroudedKeyBag.schema({
            names: {
                encryptionAlgorithm: {
                    names: {
                        blockName: ENCRYPTION_ALGORITHM
                    }
                },
                encryptedData: ENCRYPTED_DATA
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.encryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.encryptionAlgorithm });
        this.encryptedData = asn1.result.encryptedData;
    }
    toSchema() {
        return (new Sequence({
            value: [
                this.encryptionAlgorithm.toSchema(),
                this.encryptedData
            ]
        }));
    }
    toJSON() {
        return {
            encryptionAlgorithm: this.encryptionAlgorithm.toJSON(),
            encryptedData: this.encryptedData.toJSON(),
        };
    }
    parseInternalValues(parameters, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            const cmsEncrypted = new EncryptedData({
                encryptedContentInfo: new EncryptedContentInfo({
                    contentEncryptionAlgorithm: this.encryptionAlgorithm,
                    encryptedContent: this.encryptedData
                })
            });
            const decryptedData = yield cmsEncrypted.decrypt(parameters, crypto);
            this.parsedValue = PrivateKeyInfo.fromBER(decryptedData);
        });
    }
    makeInternalValues(parameters) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.parsedValue) {
                throw new Error("Please initialize \"parsedValue\" first");
            }
            const cmsEncrypted = new EncryptedData();
            const encryptParams = Object.assign(Object.assign({}, parameters), { contentToEncrypt: this.parsedValue.toSchema().toBER(false) });
            yield cmsEncrypted.encrypt(encryptParams);
            if (!cmsEncrypted.encryptedContentInfo.encryptedContent) {
                throw new Error("The filed `encryptedContent` in EncryptedContentInfo is empty");
            }
            this.encryptionAlgorithm = cmsEncrypted.encryptedContentInfo.contentEncryptionAlgorithm;
            this.encryptedData = cmsEncrypted.encryptedContentInfo.encryptedContent;
        });
    }
}
PKCS8ShroudedKeyBag.CLASS_NAME = "PKCS8ShroudedKeyBag";

const SECRET_TYPE_ID = "secretTypeId";
const SECRET_VALUE = "secretValue";
const CLEAR_PROPS$J = [
    SECRET_TYPE_ID,
    SECRET_VALUE,
];
class SecretBag extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.secretTypeId = getParametersValue(parameters, SECRET_TYPE_ID, SecretBag.defaultValues(SECRET_TYPE_ID));
        this.secretValue = getParametersValue(parameters, SECRET_VALUE, SecretBag.defaultValues(SECRET_VALUE));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case SECRET_TYPE_ID:
                return EMPTY_STRING;
            case SECRET_VALUE:
                return (new Any());
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case SECRET_TYPE_ID:
                return (memberValue === EMPTY_STRING);
            case SECRET_VALUE:
                return (memberValue instanceof Any);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.id || "id") }),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [new Any({ name: (names.value || "value") })]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$J);
        const asn1 = compareSchema(schema, schema, SecretBag.schema({
            names: {
                id: SECRET_TYPE_ID,
                value: SECRET_VALUE
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.secretTypeId = asn1.result.secretTypeId.valueBlock.toString();
        this.secretValue = asn1.result.secretValue;
    }
    toSchema() {
        return (new Sequence({
            value: [
                new ObjectIdentifier({ value: this.secretTypeId }),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [this.secretValue.toSchema()]
                })
            ]
        }));
    }
    toJSON() {
        return {
            secretTypeId: this.secretTypeId,
            secretValue: this.secretValue.toJSON()
        };
    }
}
SecretBag.CLASS_NAME = "SecretBag";

class SafeBagValueFactory {
    static getItems() {
        if (!this.items) {
            this.items = {};
            SafeBagValueFactory.register("1.2.840.113549.1.12.10.1.1", PrivateKeyInfo);
            SafeBagValueFactory.register("1.2.840.113549.1.12.10.1.2", PKCS8ShroudedKeyBag);
            SafeBagValueFactory.register("1.2.840.113549.1.12.10.1.3", CertBag);
            SafeBagValueFactory.register("1.2.840.113549.1.12.10.1.4", CRLBag);
            SafeBagValueFactory.register("1.2.840.113549.1.12.10.1.5", SecretBag);
            SafeBagValueFactory.register("1.2.840.113549.1.12.10.1.6", SafeContents);
        }
        return this.items;
    }
    static register(id, type) {
        this.getItems()[id] = type;
    }
    static find(id) {
        return this.getItems()[id] || null;
    }
}

const BAG_ID = "bagId";
const BAG_VALUE = "bagValue";
const BAG_ATTRIBUTES = "bagAttributes";
const CLEAR_PROPS$I = [
    BAG_ID,
    BAG_VALUE,
    BAG_ATTRIBUTES
];
class SafeBag extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.bagId = getParametersValue(parameters, BAG_ID, SafeBag.defaultValues(BAG_ID));
        this.bagValue = getParametersValue(parameters, BAG_VALUE, SafeBag.defaultValues(BAG_VALUE));
        if (BAG_ATTRIBUTES in parameters) {
            this.bagAttributes = getParametersValue(parameters, BAG_ATTRIBUTES, SafeBag.defaultValues(BAG_ATTRIBUTES));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case BAG_ID:
                return EMPTY_STRING;
            case BAG_VALUE:
                return (new Any());
            case BAG_ATTRIBUTES:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case BAG_ID:
                return (memberValue === EMPTY_STRING);
            case BAG_VALUE:
                return (memberValue instanceof Any);
            case BAG_ATTRIBUTES:
                return (memberValue.length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.bagId || BAG_ID) }),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [new Any({ name: (names.bagValue || BAG_VALUE) })]
                }),
                new Set({
                    optional: true,
                    value: [
                        new Repeated({
                            name: (names.bagAttributes || BAG_ATTRIBUTES),
                            value: Attribute.schema()
                        })
                    ]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$I);
        const asn1 = compareSchema(schema, schema, SafeBag.schema({
            names: {
                bagId: BAG_ID,
                bagValue: BAG_VALUE,
                bagAttributes: BAG_ATTRIBUTES
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.bagId = asn1.result.bagId.valueBlock.toString();
        const bagType = SafeBagValueFactory.find(this.bagId);
        if (!bagType) {
            throw new Error(`Invalid BAG_ID for SafeBag: ${this.bagId}`);
        }
        this.bagValue = new bagType({ schema: asn1.result.bagValue });
        if (BAG_ATTRIBUTES in asn1.result) {
            this.bagAttributes = Array.from(asn1.result.bagAttributes, element => new Attribute({ schema: element }));
        }
    }
    toSchema() {
        const outputArray = [
            new ObjectIdentifier({ value: this.bagId }),
            new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [this.bagValue.toSchema()]
            })
        ];
        if (this.bagAttributes) {
            outputArray.push(new Set({
                value: Array.from(this.bagAttributes, o => o.toSchema())
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const output = {
            bagId: this.bagId,
            bagValue: this.bagValue.toJSON()
        };
        if (this.bagAttributes) {
            output.bagAttributes = Array.from(this.bagAttributes, o => o.toJSON());
        }
        return output;
    }
}
SafeBag.CLASS_NAME = "SafeBag";

const SAFE_BUGS = "safeBags";
class SafeContents extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.safeBags = getParametersValue(parameters, SAFE_BUGS, SafeContents.defaultValues(SAFE_BUGS));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case SAFE_BUGS:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case SAFE_BUGS:
                return (memberValue.length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Repeated({
                    name: (names.safeBags || EMPTY_STRING),
                    value: SafeBag.schema()
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, [
            SAFE_BUGS
        ]);
        const asn1 = compareSchema(schema, schema, SafeContents.schema({
            names: {
                safeBags: SAFE_BUGS
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.safeBags = Array.from(asn1.result.safeBags, element => new SafeBag({ schema: element }));
    }
    toSchema() {
        return (new Sequence({
            value: Array.from(this.safeBags, o => o.toSchema())
        }));
    }
    toJSON() {
        return {
            safeBags: Array.from(this.safeBags, o => o.toJSON())
        };
    }
}
SafeContents.CLASS_NAME = "SafeContents";

const OTHER_CERT_FORMAT = "otherCertFormat";
const OTHER_CERT = "otherCert";
const CLEAR_PROPS$H = [
    OTHER_CERT_FORMAT,
    OTHER_CERT
];
class OtherCertificateFormat extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.otherCertFormat = getParametersValue(parameters, OTHER_CERT_FORMAT, OtherCertificateFormat.defaultValues(OTHER_CERT_FORMAT));
        this.otherCert = getParametersValue(parameters, OTHER_CERT, OtherCertificateFormat.defaultValues(OTHER_CERT));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case OTHER_CERT_FORMAT:
                return EMPTY_STRING;
            case OTHER_CERT:
                return new Any();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.otherCertFormat || OTHER_CERT_FORMAT) }),
                new Any({ name: (names.otherCert || OTHER_CERT) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$H);
        const asn1 = compareSchema(schema, schema, OtherCertificateFormat.schema());
        AsnError.assertSchema(asn1, this.className);
        this.otherCertFormat = asn1.result.otherCertFormat.valueBlock.toString();
        this.otherCert = asn1.result.otherCert;
    }
    toSchema() {
        return (new Sequence({
            value: [
                new ObjectIdentifier({ value: this.otherCertFormat }),
                this.otherCert
            ]
        }));
    }
    toJSON() {
        const res = {
            otherCertFormat: this.otherCertFormat
        };
        if (!(this.otherCert instanceof Any)) {
            res.otherCert = this.otherCert.toJSON();
        }
        return res;
    }
}

const CERTIFICATES$1 = "certificates";
const CLEAR_PROPS$G = [
    CERTIFICATES$1,
];
class CertificateSet extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.certificates = getParametersValue(parameters, CERTIFICATES$1, CertificateSet.defaultValues(CERTIFICATES$1));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case CERTIFICATES$1:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Set({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Repeated({
                    name: (names.certificates || CERTIFICATES$1),
                    value: new Choice({
                        value: [
                            Certificate.schema(),
                            new Constructed({
                                idBlock: {
                                    tagClass: 3,
                                    tagNumber: 0
                                },
                                value: [
                                    new Any()
                                ]
                            }),
                            new Constructed({
                                idBlock: {
                                    tagClass: 3,
                                    tagNumber: 1
                                },
                                value: [
                                    new Sequence
                                ]
                            }),
                            new Constructed({
                                idBlock: {
                                    tagClass: 3,
                                    tagNumber: 2
                                },
                                value: AttributeCertificateV2.schema().valueBlock.value
                            }),
                            new Constructed({
                                idBlock: {
                                    tagClass: 3,
                                    tagNumber: 3
                                },
                                value: OtherCertificateFormat.schema().valueBlock.value
                            })
                        ]
                    })
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$G);
        const asn1 = compareSchema(schema, schema, CertificateSet.schema());
        AsnError.assertSchema(asn1, this.className);
        this.certificates = Array.from(asn1.result.certificates || [], (element) => {
            const initialTagNumber = element.idBlock.tagNumber;
            if (element.idBlock.tagClass === 1)
                return new Certificate({ schema: element });
            const elementSequence = new Sequence({
                value: element.valueBlock.value
            });
            switch (initialTagNumber) {
                case 1:
                    if (elementSequence.valueBlock.value[0].valueBlock.value[0].valueBlock.valueDec === 1) {
                        return new AttributeCertificateV2({ schema: elementSequence });
                    }
                    else {
                        return new AttributeCertificateV1({ schema: elementSequence });
                    }
                case 2:
                    return new AttributeCertificateV2({ schema: elementSequence });
                case 3:
                    return new OtherCertificateFormat({ schema: elementSequence });
            }
            return element;
        });
    }
    toSchema() {
        return (new Set({
            value: Array.from(this.certificates, element => {
                switch (true) {
                    case (element instanceof Certificate):
                        return element.toSchema();
                    case (element instanceof AttributeCertificateV1):
                        return new Constructed({
                            idBlock: {
                                tagClass: 3,
                                tagNumber: 1
                            },
                            value: element.toSchema().valueBlock.value
                        });
                    case (element instanceof AttributeCertificateV2):
                        return new Constructed({
                            idBlock: {
                                tagClass: 3,
                                tagNumber: 2
                            },
                            value: element.toSchema().valueBlock.value
                        });
                    case (element instanceof OtherCertificateFormat):
                        return new Constructed({
                            idBlock: {
                                tagClass: 3,
                                tagNumber: 3
                            },
                            value: element.toSchema().valueBlock.value
                        });
                }
                return element.toSchema();
            })
        }));
    }
    toJSON() {
        return {
            certificates: Array.from(this.certificates, o => o.toJSON())
        };
    }
}
CertificateSet.CLASS_NAME = "CertificateSet";

const OTHER_REV_INFO_FORMAT = "otherRevInfoFormat";
const OTHER_REV_INFO = "otherRevInfo";
const CLEAR_PROPS$F = [
    OTHER_REV_INFO_FORMAT,
    OTHER_REV_INFO
];
class OtherRevocationInfoFormat extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.otherRevInfoFormat = getParametersValue(parameters, OTHER_REV_INFO_FORMAT, OtherRevocationInfoFormat.defaultValues(OTHER_REV_INFO_FORMAT));
        this.otherRevInfo = getParametersValue(parameters, OTHER_REV_INFO, OtherRevocationInfoFormat.defaultValues(OTHER_REV_INFO));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case OTHER_REV_INFO_FORMAT:
                return EMPTY_STRING;
            case OTHER_REV_INFO:
                return new Any();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.otherRevInfoFormat || OTHER_REV_INFO_FORMAT) }),
                new Any({ name: (names.otherRevInfo || OTHER_REV_INFO) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$F);
        const asn1 = compareSchema(schema, schema, OtherRevocationInfoFormat.schema());
        AsnError.assertSchema(asn1, this.className);
        this.otherRevInfoFormat = asn1.result.otherRevInfoFormat.valueBlock.toString();
        this.otherRevInfo = asn1.result.otherRevInfo;
    }
    toSchema() {
        return (new Sequence({
            value: [
                new ObjectIdentifier({ value: this.otherRevInfoFormat }),
                this.otherRevInfo
            ]
        }));
    }
    toJSON() {
        const res = {
            otherRevInfoFormat: this.otherRevInfoFormat
        };
        if (!(this.otherRevInfo instanceof Any)) {
            res.otherRevInfo = this.otherRevInfo.toJSON();
        }
        return res;
    }
}
OtherRevocationInfoFormat.CLASS_NAME = "OtherRevocationInfoFormat";

const CRLS$3 = "crls";
const OTHER_REVOCATION_INFOS = "otherRevocationInfos";
const CLEAR_PROPS$E = [
    CRLS$3
];
class RevocationInfoChoices extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.crls = getParametersValue(parameters, CRLS$3, RevocationInfoChoices.defaultValues(CRLS$3));
        this.otherRevocationInfos = getParametersValue(parameters, OTHER_REVOCATION_INFOS, RevocationInfoChoices.defaultValues(OTHER_REVOCATION_INFOS));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case CRLS$3:
                return [];
            case OTHER_REVOCATION_INFOS:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Set({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Repeated({
                    name: (names.crls || EMPTY_STRING),
                    value: new Choice({
                        value: [
                            CertificateRevocationList.schema(),
                            new Constructed({
                                idBlock: {
                                    tagClass: 3,
                                    tagNumber: 1
                                },
                                value: [
                                    new ObjectIdentifier(),
                                    new Any()
                                ]
                            })
                        ]
                    })
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$E);
        const asn1 = compareSchema(schema, schema, RevocationInfoChoices.schema({
            names: {
                crls: CRLS$3
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (asn1.result.crls) {
            for (const element of asn1.result.crls) {
                if (element.idBlock.tagClass === 1)
                    this.crls.push(new CertificateRevocationList({ schema: element }));
                else
                    this.otherRevocationInfos.push(new OtherRevocationInfoFormat({ schema: element }));
            }
        }
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(...Array.from(this.crls, o => o.toSchema()));
        outputArray.push(...Array.from(this.otherRevocationInfos, element => {
            const schema = element.toSchema();
            schema.idBlock.tagClass = 3;
            schema.idBlock.tagNumber = 1;
            return schema;
        }));
        return (new Set({
            value: outputArray
        }));
    }
    toJSON() {
        return {
            crls: Array.from(this.crls, o => o.toJSON()),
            otherRevocationInfos: Array.from(this.otherRevocationInfos, o => o.toJSON())
        };
    }
}
RevocationInfoChoices.CLASS_NAME = "RevocationInfoChoices";

const CERTS$3 = "certs";
const CRLS$2 = "crls";
const CLEAR_PROPS$D = [
    CERTS$3,
    CRLS$2,
];
class OriginatorInfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.crls = getParametersValue(parameters, CRLS$2, OriginatorInfo.defaultValues(CRLS$2));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case CERTS$3:
                return new CertificateSet();
            case CRLS$2:
                return new RevocationInfoChoices();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case CERTS$3:
                return (memberValue.certificates.length === 0);
            case CRLS$2:
                return ((memberValue.crls.length === 0) && (memberValue.otherRevocationInfos.length === 0));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Constructed({
                    name: (names.certs || EMPTY_STRING),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: CertificateSet.schema().valueBlock.value
                }),
                new Constructed({
                    name: (names.crls || EMPTY_STRING),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: RevocationInfoChoices.schema().valueBlock.value
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$D);
        const asn1 = compareSchema(schema, schema, OriginatorInfo.schema({
            names: {
                certs: CERTS$3,
                crls: CRLS$2
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (CERTS$3 in asn1.result) {
            this.certs = new CertificateSet({
                schema: new Set({
                    value: asn1.result.certs.valueBlock.value
                })
            });
        }
        if (CRLS$2 in asn1.result) {
            this.crls = new RevocationInfoChoices({
                schema: new Set({
                    value: asn1.result.crls.valueBlock.value
                })
            });
        }
    }
    toSchema() {
        const sequenceValue = [];
        if (this.certs) {
            sequenceValue.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: this.certs.toSchema().valueBlock.value
            }));
        }
        if (this.crls) {
            sequenceValue.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                value: this.crls.toSchema().valueBlock.value
            }));
        }
        return (new Sequence({
            value: sequenceValue
        }));
    }
    toJSON() {
        const res = {};
        if (this.certs) {
            res.certs = this.certs.toJSON();
        }
        if (this.crls) {
            res.crls = this.crls.toJSON();
        }
        return res;
    }
}
OriginatorInfo.CLASS_NAME = "OriginatorInfo";

const ISSUER = "issuer";
const SERIAL_NUMBER$2 = "serialNumber";
const CLEAR_PROPS$C = [
    ISSUER,
    SERIAL_NUMBER$2,
];
class IssuerAndSerialNumber extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.issuer = getParametersValue(parameters, ISSUER, IssuerAndSerialNumber.defaultValues(ISSUER));
        this.serialNumber = getParametersValue(parameters, SERIAL_NUMBER$2, IssuerAndSerialNumber.defaultValues(SERIAL_NUMBER$2));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case ISSUER:
                return new RelativeDistinguishedNames();
            case SERIAL_NUMBER$2:
                return new Integer();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                RelativeDistinguishedNames.schema(names.issuer || {}),
                new Integer({ name: (names.serialNumber || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$C);
        const asn1 = compareSchema(schema, schema, IssuerAndSerialNumber.schema({
            names: {
                issuer: {
                    names: {
                        blockName: ISSUER
                    }
                },
                serialNumber: SERIAL_NUMBER$2
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.issuer = new RelativeDistinguishedNames({ schema: asn1.result.issuer });
        this.serialNumber = asn1.result.serialNumber;
    }
    toSchema() {
        return (new Sequence({
            value: [
                this.issuer.toSchema(),
                this.serialNumber
            ]
        }));
    }
    toJSON() {
        return {
            issuer: this.issuer.toJSON(),
            serialNumber: this.serialNumber.toJSON(),
        };
    }
}
IssuerAndSerialNumber.CLASS_NAME = "IssuerAndSerialNumber";

const VARIANT$3 = "variant";
const VALUE$3 = "value";
const CLEAR_PROPS$B = [
    "blockName"
];
class RecipientIdentifier extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.variant = getParametersValue(parameters, VARIANT$3, RecipientIdentifier.defaultValues(VARIANT$3));
        if (VALUE$3 in parameters) {
            this.value = getParametersValue(parameters, VALUE$3, RecipientIdentifier.defaultValues(VALUE$3));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VARIANT$3:
                return (-1);
            case VALUE$3:
                return {};
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case VARIANT$3:
                return (memberValue === (-1));
            case VALUE$3:
                return (Object.keys(memberValue).length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Choice({
            value: [
                IssuerAndSerialNumber.schema({
                    names: {
                        blockName: (names.blockName || EMPTY_STRING)
                    }
                }),
                new Primitive({
                    name: (names.blockName || EMPTY_STRING),
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    }
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$B);
        const asn1 = compareSchema(schema, schema, RecipientIdentifier.schema({
            names: {
                blockName: "blockName"
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (asn1.result.blockName.idBlock.tagClass === 1) {
            this.variant = 1;
            this.value = new IssuerAndSerialNumber({ schema: asn1.result.blockName });
        }
        else {
            this.variant = 2;
            this.value = new OctetString({ valueHex: asn1.result.blockName.valueBlock.valueHex });
        }
    }
    toSchema() {
        switch (this.variant) {
            case 1:
                if (!(this.value instanceof IssuerAndSerialNumber)) {
                    throw new Error("Incorrect type of RecipientIdentifier.value. It should be IssuerAndSerialNumber.");
                }
                return this.value.toSchema();
            case 2:
                if (!(this.value instanceof OctetString)) {
                    throw new Error("Incorrect type of RecipientIdentifier.value. It should be ASN.1 OctetString.");
                }
                return new Primitive({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    valueHex: this.value.valueBlock.valueHexView
                });
            default:
                return new Any();
        }
    }
    toJSON() {
        const res = {
            variant: this.variant
        };
        if ((this.variant === 1 || this.variant === 2) && this.value) {
            res.value = this.value.toJSON();
        }
        return res;
    }
}
RecipientIdentifier.CLASS_NAME = "RecipientIdentifier";

const VERSION$c = "version";
const RID$1 = "rid";
const KEY_ENCRYPTION_ALGORITHM$3 = "keyEncryptionAlgorithm";
const ENCRYPTED_KEY$3 = "encryptedKey";
const RECIPIENT_CERTIFICATE$1 = "recipientCertificate";
const CLEAR_PROPS$A = [
    VERSION$c,
    RID$1,
    KEY_ENCRYPTION_ALGORITHM$3,
    ENCRYPTED_KEY$3,
];
class KeyTransRecipientInfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.version = getParametersValue(parameters, VERSION$c, KeyTransRecipientInfo.defaultValues(VERSION$c));
        this.rid = getParametersValue(parameters, RID$1, KeyTransRecipientInfo.defaultValues(RID$1));
        this.keyEncryptionAlgorithm = getParametersValue(parameters, KEY_ENCRYPTION_ALGORITHM$3, KeyTransRecipientInfo.defaultValues(KEY_ENCRYPTION_ALGORITHM$3));
        this.encryptedKey = getParametersValue(parameters, ENCRYPTED_KEY$3, KeyTransRecipientInfo.defaultValues(ENCRYPTED_KEY$3));
        this.recipientCertificate = getParametersValue(parameters, RECIPIENT_CERTIFICATE$1, KeyTransRecipientInfo.defaultValues(RECIPIENT_CERTIFICATE$1));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VERSION$c:
                return (-1);
            case RID$1:
                return {};
            case KEY_ENCRYPTION_ALGORITHM$3:
                return new AlgorithmIdentifier();
            case ENCRYPTED_KEY$3:
                return new OctetString();
            case RECIPIENT_CERTIFICATE$1:
                return new Certificate();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case VERSION$c:
                return (memberValue === KeyTransRecipientInfo.defaultValues(VERSION$c));
            case RID$1:
                return (Object.keys(memberValue).length === 0);
            case KEY_ENCRYPTION_ALGORITHM$3:
            case ENCRYPTED_KEY$3:
                return memberValue.isEqual(KeyTransRecipientInfo.defaultValues(memberName));
            case RECIPIENT_CERTIFICATE$1:
                return false;
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Integer({ name: (names.version || EMPTY_STRING) }),
                RecipientIdentifier.schema(names.rid || {}),
                AlgorithmIdentifier.schema(names.keyEncryptionAlgorithm || {}),
                new OctetString({ name: (names.encryptedKey || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$A);
        const asn1 = compareSchema(schema, schema, KeyTransRecipientInfo.schema({
            names: {
                version: VERSION$c,
                rid: {
                    names: {
                        blockName: RID$1
                    }
                },
                keyEncryptionAlgorithm: {
                    names: {
                        blockName: KEY_ENCRYPTION_ALGORITHM$3
                    }
                },
                encryptedKey: ENCRYPTED_KEY$3
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.version = asn1.result.version.valueBlock.valueDec;
        if (asn1.result.rid.idBlock.tagClass === 3) {
            this.rid = new OctetString({ valueHex: asn1.result.rid.valueBlock.valueHex });
        }
        else {
            this.rid = new IssuerAndSerialNumber({ schema: asn1.result.rid });
        }
        this.keyEncryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.keyEncryptionAlgorithm });
        this.encryptedKey = asn1.result.encryptedKey;
    }
    toSchema() {
        const outputArray = [];
        if (this.rid instanceof IssuerAndSerialNumber) {
            this.version = 0;
            outputArray.push(new Integer({ value: this.version }));
            outputArray.push(this.rid.toSchema());
        }
        else {
            this.version = 2;
            outputArray.push(new Integer({ value: this.version }));
            outputArray.push(new Primitive({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                valueHex: this.rid.valueBlock.valueHexView
            }));
        }
        outputArray.push(this.keyEncryptionAlgorithm.toSchema());
        outputArray.push(this.encryptedKey);
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        return {
            version: this.version,
            rid: this.rid.toJSON(),
            keyEncryptionAlgorithm: this.keyEncryptionAlgorithm.toJSON(),
            encryptedKey: this.encryptedKey.toJSON(),
        };
    }
}
KeyTransRecipientInfo.CLASS_NAME = "KeyTransRecipientInfo";

const ALGORITHM = "algorithm";
const PUBLIC_KEY = "publicKey";
const CLEAR_PROPS$z = [
    ALGORITHM,
    PUBLIC_KEY
];
class OriginatorPublicKey extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.algorithm = getParametersValue(parameters, ALGORITHM, OriginatorPublicKey.defaultValues(ALGORITHM));
        this.publicKey = getParametersValue(parameters, PUBLIC_KEY, OriginatorPublicKey.defaultValues(PUBLIC_KEY));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case ALGORITHM:
                return new AlgorithmIdentifier();
            case PUBLIC_KEY:
                return new BitString();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case ALGORITHM:
            case PUBLIC_KEY:
                return (memberValue.isEqual(OriginatorPublicKey.defaultValues(memberName)));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                AlgorithmIdentifier.schema(names.algorithm || {}),
                new BitString({ name: (names.publicKey || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$z);
        const asn1 = compareSchema(schema, schema, OriginatorPublicKey.schema({
            names: {
                algorithm: {
                    names: {
                        blockName: ALGORITHM
                    }
                },
                publicKey: PUBLIC_KEY
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.algorithm = new AlgorithmIdentifier({ schema: asn1.result.algorithm });
        this.publicKey = asn1.result.publicKey;
    }
    toSchema() {
        return (new Sequence({
            value: [
                this.algorithm.toSchema(),
                this.publicKey
            ]
        }));
    }
    toJSON() {
        return {
            algorithm: this.algorithm.toJSON(),
            publicKey: this.publicKey.toJSON(),
        };
    }
}
OriginatorPublicKey.CLASS_NAME = "OriginatorPublicKey";

const VARIANT$2 = "variant";
const VALUE$2 = "value";
const CLEAR_PROPS$y = [
    "blockName",
];
class OriginatorIdentifierOrKey extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.variant = getParametersValue(parameters, VARIANT$2, OriginatorIdentifierOrKey.defaultValues(VARIANT$2));
        if (VALUE$2 in parameters) {
            this.value = getParametersValue(parameters, VALUE$2, OriginatorIdentifierOrKey.defaultValues(VALUE$2));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VARIANT$2:
                return (-1);
            case VALUE$2:
                return {};
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case VARIANT$2:
                return (memberValue === (-1));
            case VALUE$2:
                return (Object.keys(memberValue).length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Choice({
            value: [
                IssuerAndSerialNumber.schema({
                    names: {
                        blockName: (names.blockName || EMPTY_STRING)
                    }
                }),
                new Primitive({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    name: (names.blockName || EMPTY_STRING)
                }),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    name: (names.blockName || EMPTY_STRING),
                    value: OriginatorPublicKey.schema().valueBlock.value
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$y);
        const asn1 = compareSchema(schema, schema, OriginatorIdentifierOrKey.schema({
            names: {
                blockName: "blockName"
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (asn1.result.blockName.idBlock.tagClass === 1) {
            this.variant = 1;
            this.value = new IssuerAndSerialNumber({ schema: asn1.result.blockName });
        }
        else {
            if (asn1.result.blockName.idBlock.tagNumber === 0) {
                asn1.result.blockName.idBlock.tagClass = 1;
                asn1.result.blockName.idBlock.tagNumber = 4;
                this.variant = 2;
                this.value = asn1.result.blockName;
            }
            else {
                this.variant = 3;
                this.value = new OriginatorPublicKey({
                    schema: new Sequence({
                        value: asn1.result.blockName.valueBlock.value
                    })
                });
            }
        }
    }
    toSchema() {
        switch (this.variant) {
            case 1:
                return this.value.toSchema();
            case 2:
                this.value.idBlock.tagClass = 3;
                this.value.idBlock.tagNumber = 0;
                return this.value;
            case 3:
                {
                    const _schema = this.value.toSchema();
                    _schema.idBlock.tagClass = 3;
                    _schema.idBlock.tagNumber = 1;
                    return _schema;
                }
            default:
                return new Any();
        }
    }
    toJSON() {
        const res = {
            variant: this.variant
        };
        if ((this.variant === 1) || (this.variant === 2) || (this.variant === 3)) {
            res.value = this.value.toJSON();
        }
        return res;
    }
}
OriginatorIdentifierOrKey.CLASS_NAME = "OriginatorIdentifierOrKey";

const KEY_ATTR_ID = "keyAttrId";
const KEY_ATTR = "keyAttr";
const CLEAR_PROPS$x = [
    KEY_ATTR_ID,
    KEY_ATTR,
];
class OtherKeyAttribute extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.keyAttrId = getParametersValue(parameters, KEY_ATTR_ID, OtherKeyAttribute.defaultValues(KEY_ATTR_ID));
        if (KEY_ATTR in parameters) {
            this.keyAttr = getParametersValue(parameters, KEY_ATTR, OtherKeyAttribute.defaultValues(KEY_ATTR));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case KEY_ATTR_ID:
                return EMPTY_STRING;
            case KEY_ATTR:
                return {};
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case KEY_ATTR_ID:
                return (typeof memberValue === "string" && memberValue === EMPTY_STRING);
            case KEY_ATTR:
                return (Object.keys(memberValue).length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            optional: (names.optional || true),
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.keyAttrId || EMPTY_STRING) }),
                new Any({
                    optional: true,
                    name: (names.keyAttr || EMPTY_STRING)
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$x);
        const asn1 = compareSchema(schema, schema, OtherKeyAttribute.schema({
            names: {
                keyAttrId: KEY_ATTR_ID,
                keyAttr: KEY_ATTR
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.keyAttrId = asn1.result.keyAttrId.valueBlock.toString();
        if (KEY_ATTR in asn1.result) {
            this.keyAttr = asn1.result.keyAttr;
        }
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(new ObjectIdentifier({ value: this.keyAttrId }));
        if (KEY_ATTR in this) {
            outputArray.push(this.keyAttr);
        }
        return (new Sequence({
            value: outputArray,
        }));
    }
    toJSON() {
        const res = {
            keyAttrId: this.keyAttrId
        };
        if (KEY_ATTR in this) {
            res.keyAttr = this.keyAttr.toJSON();
        }
        return res;
    }
}
OtherKeyAttribute.CLASS_NAME = "OtherKeyAttribute";

const SUBJECT_KEY_IDENTIFIER = "subjectKeyIdentifier";
const DATE$1 = "date";
const OTHER$1 = "other";
const CLEAR_PROPS$w = [
    SUBJECT_KEY_IDENTIFIER,
    DATE$1,
    OTHER$1,
];
class RecipientKeyIdentifier extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.subjectKeyIdentifier = getParametersValue(parameters, SUBJECT_KEY_IDENTIFIER, RecipientKeyIdentifier.defaultValues(SUBJECT_KEY_IDENTIFIER));
        if (DATE$1 in parameters) {
            this.date = getParametersValue(parameters, DATE$1, RecipientKeyIdentifier.defaultValues(DATE$1));
        }
        if (OTHER$1 in parameters) {
            this.other = getParametersValue(parameters, OTHER$1, RecipientKeyIdentifier.defaultValues(OTHER$1));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case SUBJECT_KEY_IDENTIFIER:
                return new OctetString();
            case DATE$1:
                return new GeneralizedTime();
            case OTHER$1:
                return new OtherKeyAttribute();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case SUBJECT_KEY_IDENTIFIER:
                return (memberValue.isEqual(RecipientKeyIdentifier.defaultValues(SUBJECT_KEY_IDENTIFIER)));
            case DATE$1:
                return ((memberValue.year === 0) &&
                    (memberValue.month === 0) &&
                    (memberValue.day === 0) &&
                    (memberValue.hour === 0) &&
                    (memberValue.minute === 0) &&
                    (memberValue.second === 0) &&
                    (memberValue.millisecond === 0));
            case OTHER$1:
                return ((memberValue.keyAttrId === EMPTY_STRING) && (("keyAttr" in memberValue) === false));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new OctetString({ name: (names.subjectKeyIdentifier || EMPTY_STRING) }),
                new GeneralizedTime({
                    optional: true,
                    name: (names.date || EMPTY_STRING)
                }),
                OtherKeyAttribute.schema(names.other || {})
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$w);
        const asn1 = compareSchema(schema, schema, RecipientKeyIdentifier.schema({
            names: {
                subjectKeyIdentifier: SUBJECT_KEY_IDENTIFIER,
                date: DATE$1,
                other: {
                    names: {
                        blockName: OTHER$1
                    }
                }
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.subjectKeyIdentifier = asn1.result.subjectKeyIdentifier;
        if (DATE$1 in asn1.result)
            this.date = asn1.result.date;
        if (OTHER$1 in asn1.result)
            this.other = new OtherKeyAttribute({ schema: asn1.result.other });
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(this.subjectKeyIdentifier);
        if (this.date) {
            outputArray.push(this.date);
        }
        if (this.other) {
            outputArray.push(this.other.toSchema());
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            subjectKeyIdentifier: this.subjectKeyIdentifier.toJSON()
        };
        if (this.date) {
            res.date = this.date.toJSON();
        }
        if (this.other) {
            res.other = this.other.toJSON();
        }
        return res;
    }
}
RecipientKeyIdentifier.CLASS_NAME = "RecipientKeyIdentifier";

const VARIANT$1 = "variant";
const VALUE$1 = "value";
const CLEAR_PROPS$v = [
    "blockName",
];
class KeyAgreeRecipientIdentifier extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.variant = getParametersValue(parameters, VARIANT$1, KeyAgreeRecipientIdentifier.defaultValues(VARIANT$1));
        this.value = getParametersValue(parameters, VALUE$1, KeyAgreeRecipientIdentifier.defaultValues(VALUE$1));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VARIANT$1:
                return (-1);
            case VALUE$1:
                return {};
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case VARIANT$1:
                return (memberValue === (-1));
            case VALUE$1:
                return (Object.keys(memberValue).length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Choice({
            value: [
                IssuerAndSerialNumber.schema(names.issuerAndSerialNumber || {
                    names: {
                        blockName: (names.blockName || EMPTY_STRING)
                    }
                }),
                new Constructed({
                    name: (names.blockName || EMPTY_STRING),
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: RecipientKeyIdentifier.schema(names.rKeyId || {
                        names: {
                            blockName: (names.blockName || EMPTY_STRING)
                        }
                    }).valueBlock.value
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$v);
        const asn1 = compareSchema(schema, schema, KeyAgreeRecipientIdentifier.schema({
            names: {
                blockName: "blockName"
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (asn1.result.blockName.idBlock.tagClass === 1) {
            this.variant = 1;
            this.value = new IssuerAndSerialNumber({ schema: asn1.result.blockName });
        }
        else {
            this.variant = 2;
            this.value = new RecipientKeyIdentifier({
                schema: new Sequence({
                    value: asn1.result.blockName.valueBlock.value
                })
            });
        }
    }
    toSchema() {
        switch (this.variant) {
            case 1:
                return this.value.toSchema();
            case 2:
                return new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: this.value.toSchema().valueBlock.value
                });
            default:
                return new Any();
        }
    }
    toJSON() {
        const res = {
            variant: this.variant,
        };
        if ((this.variant === 1) || (this.variant === 2)) {
            res.value = this.value.toJSON();
        }
        return res;
    }
}
KeyAgreeRecipientIdentifier.CLASS_NAME = "KeyAgreeRecipientIdentifier";

const RID = "rid";
const ENCRYPTED_KEY$2 = "encryptedKey";
const CLEAR_PROPS$u = [
    RID,
    ENCRYPTED_KEY$2,
];
class RecipientEncryptedKey extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.rid = getParametersValue(parameters, RID, RecipientEncryptedKey.defaultValues(RID));
        this.encryptedKey = getParametersValue(parameters, ENCRYPTED_KEY$2, RecipientEncryptedKey.defaultValues(ENCRYPTED_KEY$2));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case RID:
                return new KeyAgreeRecipientIdentifier();
            case ENCRYPTED_KEY$2:
                return new OctetString();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case RID:
                return ((memberValue.variant === (-1)) && (("value" in memberValue) === false));
            case ENCRYPTED_KEY$2:
                return (memberValue.isEqual(RecipientEncryptedKey.defaultValues(ENCRYPTED_KEY$2)));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                KeyAgreeRecipientIdentifier.schema(names.rid || {}),
                new OctetString({ name: (names.encryptedKey || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$u);
        const asn1 = compareSchema(schema, schema, RecipientEncryptedKey.schema({
            names: {
                rid: {
                    names: {
                        blockName: RID
                    }
                },
                encryptedKey: ENCRYPTED_KEY$2
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.rid = new KeyAgreeRecipientIdentifier({ schema: asn1.result.rid });
        this.encryptedKey = asn1.result.encryptedKey;
    }
    toSchema() {
        return (new Sequence({
            value: [
                this.rid.toSchema(),
                this.encryptedKey
            ]
        }));
    }
    toJSON() {
        return {
            rid: this.rid.toJSON(),
            encryptedKey: this.encryptedKey.toJSON(),
        };
    }
}
RecipientEncryptedKey.CLASS_NAME = "RecipientEncryptedKey";

const ENCRYPTED_KEYS = "encryptedKeys";
const RECIPIENT_ENCRYPTED_KEYS = "RecipientEncryptedKeys";
const CLEAR_PROPS$t = [
    RECIPIENT_ENCRYPTED_KEYS,
];
class RecipientEncryptedKeys extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.encryptedKeys = getParametersValue(parameters, ENCRYPTED_KEYS, RecipientEncryptedKeys.defaultValues(ENCRYPTED_KEYS));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case ENCRYPTED_KEYS:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case ENCRYPTED_KEYS:
                return (memberValue.length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Repeated({
                    name: (names.RecipientEncryptedKeys || EMPTY_STRING),
                    value: RecipientEncryptedKey.schema()
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$t);
        const asn1 = compareSchema(schema, schema, RecipientEncryptedKeys.schema({
            names: {
                RecipientEncryptedKeys: RECIPIENT_ENCRYPTED_KEYS
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.encryptedKeys = Array.from(asn1.result.RecipientEncryptedKeys, element => new RecipientEncryptedKey({ schema: element }));
    }
    toSchema() {
        return (new Sequence({
            value: Array.from(this.encryptedKeys, o => o.toSchema())
        }));
    }
    toJSON() {
        return {
            encryptedKeys: Array.from(this.encryptedKeys, o => o.toJSON())
        };
    }
}
RecipientEncryptedKeys.CLASS_NAME = "RecipientEncryptedKeys";

const VERSION$b = "version";
const ORIGINATOR = "originator";
const UKM = "ukm";
const KEY_ENCRYPTION_ALGORITHM$2 = "keyEncryptionAlgorithm";
const RECIPIENT_ENCRYPTED_KEY = "recipientEncryptedKeys";
const RECIPIENT_CERTIFICATE = "recipientCertificate";
const RECIPIENT_PUBLIC_KEY = "recipientPublicKey";
const CLEAR_PROPS$s = [
    VERSION$b,
    ORIGINATOR,
    UKM,
    KEY_ENCRYPTION_ALGORITHM$2,
    RECIPIENT_ENCRYPTED_KEY,
];
class KeyAgreeRecipientInfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.version = getParametersValue(parameters, VERSION$b, KeyAgreeRecipientInfo.defaultValues(VERSION$b));
        this.originator = getParametersValue(parameters, ORIGINATOR, KeyAgreeRecipientInfo.defaultValues(ORIGINATOR));
        if (UKM in parameters) {
            this.ukm = getParametersValue(parameters, UKM, KeyAgreeRecipientInfo.defaultValues(UKM));
        }
        this.keyEncryptionAlgorithm = getParametersValue(parameters, KEY_ENCRYPTION_ALGORITHM$2, KeyAgreeRecipientInfo.defaultValues(KEY_ENCRYPTION_ALGORITHM$2));
        this.recipientEncryptedKeys = getParametersValue(parameters, RECIPIENT_ENCRYPTED_KEY, KeyAgreeRecipientInfo.defaultValues(RECIPIENT_ENCRYPTED_KEY));
        this.recipientCertificate = getParametersValue(parameters, RECIPIENT_CERTIFICATE, KeyAgreeRecipientInfo.defaultValues(RECIPIENT_CERTIFICATE));
        this.recipientPublicKey = getParametersValue(parameters, RECIPIENT_PUBLIC_KEY, KeyAgreeRecipientInfo.defaultValues(RECIPIENT_PUBLIC_KEY));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VERSION$b:
                return 0;
            case ORIGINATOR:
                return new OriginatorIdentifierOrKey();
            case UKM:
                return new OctetString();
            case KEY_ENCRYPTION_ALGORITHM$2:
                return new AlgorithmIdentifier();
            case RECIPIENT_ENCRYPTED_KEY:
                return new RecipientEncryptedKeys();
            case RECIPIENT_CERTIFICATE:
                return new Certificate();
            case RECIPIENT_PUBLIC_KEY:
                return null;
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case VERSION$b:
                return (memberValue === 0);
            case ORIGINATOR:
                return ((memberValue.variant === (-1)) && (("value" in memberValue) === false));
            case UKM:
                return (memberValue.isEqual(KeyAgreeRecipientInfo.defaultValues(UKM)));
            case KEY_ENCRYPTION_ALGORITHM$2:
                return ((memberValue.algorithmId === EMPTY_STRING) && (("algorithmParams" in memberValue) === false));
            case RECIPIENT_ENCRYPTED_KEY:
                return (memberValue.encryptedKeys.length === 0);
            case RECIPIENT_CERTIFICATE:
                return false;
            case RECIPIENT_PUBLIC_KEY:
                return false;
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: names.blockName || EMPTY_STRING,
            value: [
                new Integer({ name: names.version || EMPTY_STRING }),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [
                        OriginatorIdentifierOrKey.schema(names.originator || {})
                    ]
                }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: [new OctetString({ name: names.ukm || EMPTY_STRING })]
                }),
                AlgorithmIdentifier.schema(names.keyEncryptionAlgorithm || {}),
                RecipientEncryptedKeys.schema(names.recipientEncryptedKeys || {})
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$s);
        const asn1 = compareSchema(schema, schema, KeyAgreeRecipientInfo.schema({
            names: {
                version: VERSION$b,
                originator: {
                    names: {
                        blockName: ORIGINATOR
                    }
                },
                ukm: UKM,
                keyEncryptionAlgorithm: {
                    names: {
                        blockName: KEY_ENCRYPTION_ALGORITHM$2
                    }
                },
                recipientEncryptedKeys: {
                    names: {
                        blockName: RECIPIENT_ENCRYPTED_KEY
                    }
                }
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.version = asn1.result.version.valueBlock.valueDec;
        this.originator = new OriginatorIdentifierOrKey({ schema: asn1.result.originator });
        if (UKM in asn1.result)
            this.ukm = asn1.result.ukm;
        this.keyEncryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.keyEncryptionAlgorithm });
        this.recipientEncryptedKeys = new RecipientEncryptedKeys({ schema: asn1.result.recipientEncryptedKeys });
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(new Integer({ value: this.version }));
        outputArray.push(new Constructed({
            idBlock: {
                tagClass: 3,
                tagNumber: 0
            },
            value: [this.originator.toSchema()]
        }));
        if (this.ukm) {
            outputArray.push(new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                value: [this.ukm]
            }));
        }
        outputArray.push(this.keyEncryptionAlgorithm.toSchema());
        outputArray.push(this.recipientEncryptedKeys.toSchema());
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            version: this.version,
            originator: this.originator.toJSON(),
            keyEncryptionAlgorithm: this.keyEncryptionAlgorithm.toJSON(),
            recipientEncryptedKeys: this.recipientEncryptedKeys.toJSON(),
        };
        if (this.ukm) {
            res.ukm = this.ukm.toJSON();
        }
        return res;
    }
}
KeyAgreeRecipientInfo.CLASS_NAME = "KeyAgreeRecipientInfo";

const KEY_IDENTIFIER = "keyIdentifier";
const DATE = "date";
const OTHER = "other";
const CLEAR_PROPS$r = [
    KEY_IDENTIFIER,
    DATE,
    OTHER,
];
class KEKIdentifier extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.keyIdentifier = getParametersValue(parameters, KEY_IDENTIFIER, KEKIdentifier.defaultValues(KEY_IDENTIFIER));
        if (DATE in parameters) {
            this.date = getParametersValue(parameters, DATE, KEKIdentifier.defaultValues(DATE));
        }
        if (OTHER in parameters) {
            this.other = getParametersValue(parameters, OTHER, KEKIdentifier.defaultValues(OTHER));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case KEY_IDENTIFIER:
                return new OctetString();
            case DATE:
                return new GeneralizedTime();
            case OTHER:
                return new OtherKeyAttribute();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case KEY_IDENTIFIER:
                return (memberValue.isEqual(KEKIdentifier.defaultValues(KEY_IDENTIFIER)));
            case DATE:
                return ((memberValue.year === 0) &&
                    (memberValue.month === 0) &&
                    (memberValue.day === 0) &&
                    (memberValue.hour === 0) &&
                    (memberValue.minute === 0) &&
                    (memberValue.second === 0) &&
                    (memberValue.millisecond === 0));
            case OTHER:
                return ((memberValue.compareWithDefault("keyAttrId", memberValue.keyAttrId)) &&
                    (("keyAttr" in memberValue) === false));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new OctetString({ name: (names.keyIdentifier || EMPTY_STRING) }),
                new GeneralizedTime({
                    optional: true,
                    name: (names.date || EMPTY_STRING)
                }),
                OtherKeyAttribute.schema(names.other || {})
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$r);
        const asn1 = compareSchema(schema, schema, KEKIdentifier.schema({
            names: {
                keyIdentifier: KEY_IDENTIFIER,
                date: DATE,
                other: {
                    names: {
                        blockName: OTHER
                    }
                }
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.keyIdentifier = asn1.result.keyIdentifier;
        if (DATE in asn1.result)
            this.date = asn1.result.date;
        if (OTHER in asn1.result)
            this.other = new OtherKeyAttribute({ schema: asn1.result.other });
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(this.keyIdentifier);
        if (this.date) {
            outputArray.push(this.date);
        }
        if (this.other) {
            outputArray.push(this.other.toSchema());
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            keyIdentifier: this.keyIdentifier.toJSON()
        };
        if (this.date) {
            res.date = this.date;
        }
        if (this.other) {
            res.other = this.other.toJSON();
        }
        return res;
    }
}
KEKIdentifier.CLASS_NAME = "KEKIdentifier";

const VERSION$a = "version";
const KEK_ID = "kekid";
const KEY_ENCRYPTION_ALGORITHM$1 = "keyEncryptionAlgorithm";
const ENCRYPTED_KEY$1 = "encryptedKey";
const PER_DEFINED_KEK = "preDefinedKEK";
const CLEAR_PROPS$q = [
    VERSION$a,
    KEK_ID,
    KEY_ENCRYPTION_ALGORITHM$1,
    ENCRYPTED_KEY$1,
];
class KEKRecipientInfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.version = getParametersValue(parameters, VERSION$a, KEKRecipientInfo.defaultValues(VERSION$a));
        this.kekid = getParametersValue(parameters, KEK_ID, KEKRecipientInfo.defaultValues(KEK_ID));
        this.keyEncryptionAlgorithm = getParametersValue(parameters, KEY_ENCRYPTION_ALGORITHM$1, KEKRecipientInfo.defaultValues(KEY_ENCRYPTION_ALGORITHM$1));
        this.encryptedKey = getParametersValue(parameters, ENCRYPTED_KEY$1, KEKRecipientInfo.defaultValues(ENCRYPTED_KEY$1));
        this.preDefinedKEK = getParametersValue(parameters, PER_DEFINED_KEK, KEKRecipientInfo.defaultValues(PER_DEFINED_KEK));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VERSION$a:
                return 0;
            case KEK_ID:
                return new KEKIdentifier();
            case KEY_ENCRYPTION_ALGORITHM$1:
                return new AlgorithmIdentifier();
            case ENCRYPTED_KEY$1:
                return new OctetString();
            case PER_DEFINED_KEK:
                return EMPTY_BUFFER;
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case "KEKRecipientInfo":
                return (memberValue === KEKRecipientInfo.defaultValues(VERSION$a));
            case KEK_ID:
                return ((memberValue.compareWithDefault("keyIdentifier", memberValue.keyIdentifier)) &&
                    (("date" in memberValue) === false) &&
                    (("other" in memberValue) === false));
            case KEY_ENCRYPTION_ALGORITHM$1:
                return ((memberValue.algorithmId === EMPTY_STRING) && (("algorithmParams" in memberValue) === false));
            case ENCRYPTED_KEY$1:
                return (memberValue.isEqual(KEKRecipientInfo.defaultValues(ENCRYPTED_KEY$1)));
            case PER_DEFINED_KEK:
                return (memberValue.byteLength === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Integer({ name: (names.version || EMPTY_STRING) }),
                KEKIdentifier.schema(names.kekid || {}),
                AlgorithmIdentifier.schema(names.keyEncryptionAlgorithm || {}),
                new OctetString({ name: (names.encryptedKey || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$q);
        const asn1 = compareSchema(schema, schema, KEKRecipientInfo.schema({
            names: {
                version: VERSION$a,
                kekid: {
                    names: {
                        blockName: KEK_ID
                    }
                },
                keyEncryptionAlgorithm: {
                    names: {
                        blockName: KEY_ENCRYPTION_ALGORITHM$1
                    }
                },
                encryptedKey: ENCRYPTED_KEY$1
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.version = asn1.result.version.valueBlock.valueDec;
        this.kekid = new KEKIdentifier({ schema: asn1.result.kekid });
        this.keyEncryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.keyEncryptionAlgorithm });
        this.encryptedKey = asn1.result.encryptedKey;
    }
    toSchema() {
        return (new Sequence({
            value: [
                new Integer({ value: this.version }),
                this.kekid.toSchema(),
                this.keyEncryptionAlgorithm.toSchema(),
                this.encryptedKey
            ]
        }));
    }
    toJSON() {
        return {
            version: this.version,
            kekid: this.kekid.toJSON(),
            keyEncryptionAlgorithm: this.keyEncryptionAlgorithm.toJSON(),
            encryptedKey: this.encryptedKey.toJSON(),
        };
    }
}
KEKRecipientInfo.CLASS_NAME = "KEKRecipientInfo";

const VERSION$9 = "version";
const KEY_DERIVATION_ALGORITHM = "keyDerivationAlgorithm";
const KEY_ENCRYPTION_ALGORITHM = "keyEncryptionAlgorithm";
const ENCRYPTED_KEY = "encryptedKey";
const PASSWORD = "password";
const CLEAR_PROPS$p = [
    VERSION$9,
    KEY_DERIVATION_ALGORITHM,
    KEY_ENCRYPTION_ALGORITHM,
    ENCRYPTED_KEY
];
class PasswordRecipientinfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.version = getParametersValue(parameters, VERSION$9, PasswordRecipientinfo.defaultValues(VERSION$9));
        if (KEY_DERIVATION_ALGORITHM in parameters) {
            this.keyDerivationAlgorithm = getParametersValue(parameters, KEY_DERIVATION_ALGORITHM, PasswordRecipientinfo.defaultValues(KEY_DERIVATION_ALGORITHM));
        }
        this.keyEncryptionAlgorithm = getParametersValue(parameters, KEY_ENCRYPTION_ALGORITHM, PasswordRecipientinfo.defaultValues(KEY_ENCRYPTION_ALGORITHM));
        this.encryptedKey = getParametersValue(parameters, ENCRYPTED_KEY, PasswordRecipientinfo.defaultValues(ENCRYPTED_KEY));
        this.password = getParametersValue(parameters, PASSWORD, PasswordRecipientinfo.defaultValues(PASSWORD));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VERSION$9:
                return (-1);
            case KEY_DERIVATION_ALGORITHM:
                return new AlgorithmIdentifier();
            case KEY_ENCRYPTION_ALGORITHM:
                return new AlgorithmIdentifier();
            case ENCRYPTED_KEY:
                return new OctetString();
            case PASSWORD:
                return EMPTY_BUFFER;
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case VERSION$9:
                return (memberValue === (-1));
            case KEY_DERIVATION_ALGORITHM:
            case KEY_ENCRYPTION_ALGORITHM:
                return ((memberValue.algorithmId === EMPTY_STRING) && (("algorithmParams" in memberValue) === false));
            case ENCRYPTED_KEY:
                return (memberValue.isEqual(PasswordRecipientinfo.defaultValues(ENCRYPTED_KEY)));
            case PASSWORD:
                return (memberValue.byteLength === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Integer({ name: (names.version || EMPTY_STRING) }),
                new Constructed({
                    name: (names.keyDerivationAlgorithm || EMPTY_STRING),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: AlgorithmIdentifier.schema().valueBlock.value
                }),
                AlgorithmIdentifier.schema(names.keyEncryptionAlgorithm || {}),
                new OctetString({ name: (names.encryptedKey || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$p);
        const asn1 = compareSchema(schema, schema, PasswordRecipientinfo.schema({
            names: {
                version: VERSION$9,
                keyDerivationAlgorithm: KEY_DERIVATION_ALGORITHM,
                keyEncryptionAlgorithm: {
                    names: {
                        blockName: KEY_ENCRYPTION_ALGORITHM
                    }
                },
                encryptedKey: ENCRYPTED_KEY
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.version = asn1.result.version.valueBlock.valueDec;
        if (KEY_DERIVATION_ALGORITHM in asn1.result) {
            this.keyDerivationAlgorithm = new AlgorithmIdentifier({
                schema: new Sequence({
                    value: asn1.result.keyDerivationAlgorithm.valueBlock.value
                })
            });
        }
        this.keyEncryptionAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.keyEncryptionAlgorithm });
        this.encryptedKey = asn1.result.encryptedKey;
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(new Integer({ value: this.version }));
        if (this.keyDerivationAlgorithm) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: this.keyDerivationAlgorithm.toSchema().valueBlock.value
            }));
        }
        outputArray.push(this.keyEncryptionAlgorithm.toSchema());
        outputArray.push(this.encryptedKey);
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            version: this.version,
            keyEncryptionAlgorithm: this.keyEncryptionAlgorithm.toJSON(),
            encryptedKey: this.encryptedKey.toJSON(),
        };
        if (this.keyDerivationAlgorithm) {
            res.keyDerivationAlgorithm = this.keyDerivationAlgorithm.toJSON();
        }
        return res;
    }
}
PasswordRecipientinfo.CLASS_NAME = "PasswordRecipientInfo";

const ORI_TYPE = "oriType";
const ORI_VALUE = "oriValue";
const CLEAR_PROPS$o = [
    ORI_TYPE,
    ORI_VALUE
];
class OtherRecipientInfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.oriType = getParametersValue(parameters, ORI_TYPE, OtherRecipientInfo.defaultValues(ORI_TYPE));
        this.oriValue = getParametersValue(parameters, ORI_VALUE, OtherRecipientInfo.defaultValues(ORI_VALUE));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case ORI_TYPE:
                return EMPTY_STRING;
            case ORI_VALUE:
                return {};
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case ORI_TYPE:
                return (memberValue === EMPTY_STRING);
            case ORI_VALUE:
                return (Object.keys(memberValue).length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.oriType || EMPTY_STRING) }),
                new Any({ name: (names.oriValue || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$o);
        const asn1 = compareSchema(schema, schema, OtherRecipientInfo.schema({
            names: {
                oriType: ORI_TYPE,
                oriValue: ORI_VALUE
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.oriType = asn1.result.oriType.valueBlock.toString();
        this.oriValue = asn1.result.oriValue;
    }
    toSchema() {
        return (new Sequence({
            value: [
                new ObjectIdentifier({ value: this.oriType }),
                this.oriValue
            ]
        }));
    }
    toJSON() {
        const res = {
            oriType: this.oriType
        };
        if (!OtherRecipientInfo.compareWithDefault(ORI_VALUE, this.oriValue)) {
            res.oriValue = this.oriValue.toJSON();
        }
        return res;
    }
}
OtherRecipientInfo.CLASS_NAME = "OtherRecipientInfo";

const VARIANT = "variant";
const VALUE = "value";
const CLEAR_PROPS$n = [
    "blockName"
];
class RecipientInfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.variant = getParametersValue(parameters, VARIANT, RecipientInfo.defaultValues(VARIANT));
        if (VALUE in parameters) {
            this.value = getParametersValue(parameters, VALUE, RecipientInfo.defaultValues(VALUE));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VARIANT:
                return (-1);
            case VALUE:
                return {};
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case VARIANT:
                return (memberValue === RecipientInfo.defaultValues(memberName));
            case VALUE:
                return (Object.keys(memberValue).length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Choice({
            value: [
                KeyTransRecipientInfo.schema({
                    names: {
                        blockName: (names.blockName || EMPTY_STRING)
                    }
                }),
                new Constructed({
                    name: (names.blockName || EMPTY_STRING),
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: KeyAgreeRecipientInfo.schema().valueBlock.value
                }),
                new Constructed({
                    name: (names.blockName || EMPTY_STRING),
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 2
                    },
                    value: KEKRecipientInfo.schema().valueBlock.value
                }),
                new Constructed({
                    name: (names.blockName || EMPTY_STRING),
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 3
                    },
                    value: PasswordRecipientinfo.schema().valueBlock.value
                }),
                new Constructed({
                    name: (names.blockName || EMPTY_STRING),
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 4
                    },
                    value: OtherRecipientInfo.schema().valueBlock.value
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$n);
        const asn1 = compareSchema(schema, schema, RecipientInfo.schema({
            names: {
                blockName: "blockName"
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (asn1.result.blockName.idBlock.tagClass === 1) {
            this.variant = 1;
            this.value = new KeyTransRecipientInfo({ schema: asn1.result.blockName });
        }
        else {
            const blockSequence = new Sequence({
                value: asn1.result.blockName.valueBlock.value
            });
            switch (asn1.result.blockName.idBlock.tagNumber) {
                case 1:
                    this.variant = 2;
                    this.value = new KeyAgreeRecipientInfo({ schema: blockSequence });
                    break;
                case 2:
                    this.variant = 3;
                    this.value = new KEKRecipientInfo({ schema: blockSequence });
                    break;
                case 3:
                    this.variant = 4;
                    this.value = new PasswordRecipientinfo({ schema: blockSequence });
                    break;
                case 4:
                    this.variant = 5;
                    this.value = new OtherRecipientInfo({ schema: blockSequence });
                    break;
                default:
                    throw new Error("Incorrect structure of RecipientInfo block");
            }
        }
    }
    toSchema() {
        ParameterError.assertEmpty(this.value, "value", "RecipientInfo");
        const _schema = this.value.toSchema();
        switch (this.variant) {
            case 1:
                return _schema;
            case 2:
            case 3:
            case 4:
                _schema.idBlock.tagClass = 3;
                _schema.idBlock.tagNumber = (this.variant - 1);
                return _schema;
            default:
                return new Any();
        }
    }
    toJSON() {
        const res = {
            variant: this.variant
        };
        if (this.value && (this.variant >= 1) && (this.variant <= 4)) {
            res.value = this.value.toJSON();
        }
        return res;
    }
}
RecipientInfo.CLASS_NAME = "RecipientInfo";

const HASH_ALGORITHM$2 = "hashAlgorithm";
const MASK_GEN_ALGORITHM = "maskGenAlgorithm";
const P_SOURCE_ALGORITHM = "pSourceAlgorithm";
const CLEAR_PROPS$m = [
    HASH_ALGORITHM$2,
    MASK_GEN_ALGORITHM,
    P_SOURCE_ALGORITHM
];
class RSAESOAEPParams extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.hashAlgorithm = getParametersValue(parameters, HASH_ALGORITHM$2, RSAESOAEPParams.defaultValues(HASH_ALGORITHM$2));
        this.maskGenAlgorithm = getParametersValue(parameters, MASK_GEN_ALGORITHM, RSAESOAEPParams.defaultValues(MASK_GEN_ALGORITHM));
        this.pSourceAlgorithm = getParametersValue(parameters, P_SOURCE_ALGORITHM, RSAESOAEPParams.defaultValues(P_SOURCE_ALGORITHM));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case HASH_ALGORITHM$2:
                return new AlgorithmIdentifier({
                    algorithmId: "1.3.14.3.2.26",
                    algorithmParams: new Null()
                });
            case MASK_GEN_ALGORITHM:
                return new AlgorithmIdentifier({
                    algorithmId: "1.2.840.113549.1.1.8",
                    algorithmParams: (new AlgorithmIdentifier({
                        algorithmId: "1.3.14.3.2.26",
                        algorithmParams: new Null()
                    })).toSchema()
                });
            case P_SOURCE_ALGORITHM:
                return new AlgorithmIdentifier({
                    algorithmId: "1.2.840.113549.1.1.9",
                    algorithmParams: new OctetString({ valueHex: (new Uint8Array([0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09])).buffer })
                });
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    optional: true,
                    value: [AlgorithmIdentifier.schema(names.hashAlgorithm || {})]
                }),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    optional: true,
                    value: [AlgorithmIdentifier.schema(names.maskGenAlgorithm || {})]
                }),
                new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 2
                    },
                    optional: true,
                    value: [AlgorithmIdentifier.schema(names.pSourceAlgorithm || {})]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$m);
        const asn1 = compareSchema(schema, schema, RSAESOAEPParams.schema({
            names: {
                hashAlgorithm: {
                    names: {
                        blockName: HASH_ALGORITHM$2
                    }
                },
                maskGenAlgorithm: {
                    names: {
                        blockName: MASK_GEN_ALGORITHM
                    }
                },
                pSourceAlgorithm: {
                    names: {
                        blockName: P_SOURCE_ALGORITHM
                    }
                }
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        if (HASH_ALGORITHM$2 in asn1.result)
            this.hashAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.hashAlgorithm });
        if (MASK_GEN_ALGORITHM in asn1.result)
            this.maskGenAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.maskGenAlgorithm });
        if (P_SOURCE_ALGORITHM in asn1.result)
            this.pSourceAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.pSourceAlgorithm });
    }
    toSchema() {
        const outputArray = [];
        if (!this.hashAlgorithm.isEqual(RSAESOAEPParams.defaultValues(HASH_ALGORITHM$2))) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [this.hashAlgorithm.toSchema()]
            }));
        }
        if (!this.maskGenAlgorithm.isEqual(RSAESOAEPParams.defaultValues(MASK_GEN_ALGORITHM))) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                value: [this.maskGenAlgorithm.toSchema()]
            }));
        }
        if (!this.pSourceAlgorithm.isEqual(RSAESOAEPParams.defaultValues(P_SOURCE_ALGORITHM))) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 2
                },
                value: [this.pSourceAlgorithm.toSchema()]
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {};
        if (!this.hashAlgorithm.isEqual(RSAESOAEPParams.defaultValues(HASH_ALGORITHM$2))) {
            res.hashAlgorithm = this.hashAlgorithm.toJSON();
        }
        if (!this.maskGenAlgorithm.isEqual(RSAESOAEPParams.defaultValues(MASK_GEN_ALGORITHM))) {
            res.maskGenAlgorithm = this.maskGenAlgorithm.toJSON();
        }
        if (!this.pSourceAlgorithm.isEqual(RSAESOAEPParams.defaultValues(P_SOURCE_ALGORITHM))) {
            res.pSourceAlgorithm = this.pSourceAlgorithm.toJSON();
        }
        return res;
    }
}
RSAESOAEPParams.CLASS_NAME = "RSAESOAEPParams";

const KEY_INFO = "keyInfo";
const ENTITY_U_INFO = "entityUInfo";
const SUPP_PUB_INFO = "suppPubInfo";
const CLEAR_PROPS$l = [
    KEY_INFO,
    ENTITY_U_INFO,
    SUPP_PUB_INFO
];
class ECCCMSSharedInfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.keyInfo = getParametersValue(parameters, KEY_INFO, ECCCMSSharedInfo.defaultValues(KEY_INFO));
        if (ENTITY_U_INFO in parameters) {
            this.entityUInfo = getParametersValue(parameters, ENTITY_U_INFO, ECCCMSSharedInfo.defaultValues(ENTITY_U_INFO));
        }
        this.suppPubInfo = getParametersValue(parameters, SUPP_PUB_INFO, ECCCMSSharedInfo.defaultValues(SUPP_PUB_INFO));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case KEY_INFO:
                return new AlgorithmIdentifier();
            case ENTITY_U_INFO:
                return new OctetString();
            case SUPP_PUB_INFO:
                return new OctetString();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case KEY_INFO:
            case ENTITY_U_INFO:
            case SUPP_PUB_INFO:
                return (memberValue.isEqual(ECCCMSSharedInfo.defaultValues(memberName)));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                AlgorithmIdentifier.schema(names.keyInfo || {}),
                new Constructed({
                    name: (names.entityUInfo || EMPTY_STRING),
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    optional: true,
                    value: [new OctetString()]
                }),
                new Constructed({
                    name: (names.suppPubInfo || EMPTY_STRING),
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 2
                    },
                    value: [new OctetString()]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$l);
        const asn1 = compareSchema(schema, schema, ECCCMSSharedInfo.schema({
            names: {
                keyInfo: {
                    names: {
                        blockName: KEY_INFO
                    }
                },
                entityUInfo: ENTITY_U_INFO,
                suppPubInfo: SUPP_PUB_INFO
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.keyInfo = new AlgorithmIdentifier({ schema: asn1.result.keyInfo });
        if (ENTITY_U_INFO in asn1.result)
            this.entityUInfo = asn1.result.entityUInfo.valueBlock.value[0];
        this.suppPubInfo = asn1.result.suppPubInfo.valueBlock.value[0];
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(this.keyInfo.toSchema());
        if (this.entityUInfo) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [this.entityUInfo]
            }));
        }
        outputArray.push(new Constructed({
            idBlock: {
                tagClass: 3,
                tagNumber: 2
            },
            value: [this.suppPubInfo]
        }));
        return new Sequence({
            value: outputArray
        });
    }
    toJSON() {
        const res = {
            keyInfo: this.keyInfo.toJSON(),
            suppPubInfo: this.suppPubInfo.toJSON(),
        };
        if (this.entityUInfo) {
            res.entityUInfo = this.entityUInfo.toJSON();
        }
        return res;
    }
}
ECCCMSSharedInfo.CLASS_NAME = "ECCCMSSharedInfo";

const VERSION$8 = "version";
const ORIGINATOR_INFO = "originatorInfo";
const RECIPIENT_INFOS = "recipientInfos";
const ENCRYPTED_CONTENT_INFO = "encryptedContentInfo";
const UNPROTECTED_ATTRS = "unprotectedAttrs";
const CLEAR_PROPS$k = [
    VERSION$8,
    ORIGINATOR_INFO,
    RECIPIENT_INFOS,
    ENCRYPTED_CONTENT_INFO,
    UNPROTECTED_ATTRS
];
const defaultEncryptionParams = {
    kdfAlgorithm: "SHA-512",
    kekEncryptionLength: 256
};
const curveLengthByName = {
    "P-256": 256,
    "P-384": 384,
    "P-521": 528
};
class EnvelopedData extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.version = getParametersValue(parameters, VERSION$8, EnvelopedData.defaultValues(VERSION$8));
        if (ORIGINATOR_INFO in parameters) {
            this.originatorInfo = getParametersValue(parameters, ORIGINATOR_INFO, EnvelopedData.defaultValues(ORIGINATOR_INFO));
        }
        this.recipientInfos = getParametersValue(parameters, RECIPIENT_INFOS, EnvelopedData.defaultValues(RECIPIENT_INFOS));
        this.encryptedContentInfo = getParametersValue(parameters, ENCRYPTED_CONTENT_INFO, EnvelopedData.defaultValues(ENCRYPTED_CONTENT_INFO));
        if (UNPROTECTED_ATTRS in parameters) {
            this.unprotectedAttrs = getParametersValue(parameters, UNPROTECTED_ATTRS, EnvelopedData.defaultValues(UNPROTECTED_ATTRS));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VERSION$8:
                return 0;
            case ORIGINATOR_INFO:
                return new OriginatorInfo();
            case RECIPIENT_INFOS:
                return [];
            case ENCRYPTED_CONTENT_INFO:
                return new EncryptedContentInfo();
            case UNPROTECTED_ATTRS:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case VERSION$8:
                return (memberValue === EnvelopedData.defaultValues(memberName));
            case ORIGINATOR_INFO:
                return ((memberValue.certs.certificates.length === 0) && (memberValue.crls.crls.length === 0));
            case RECIPIENT_INFOS:
            case UNPROTECTED_ATTRS:
                return (memberValue.length === 0);
            case ENCRYPTED_CONTENT_INFO:
                return ((EncryptedContentInfo.compareWithDefault("contentType", memberValue.contentType)) &&
                    (EncryptedContentInfo.compareWithDefault("contentEncryptionAlgorithm", memberValue.contentEncryptionAlgorithm) &&
                        (EncryptedContentInfo.compareWithDefault("encryptedContent", memberValue.encryptedContent))));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Integer({ name: (names.version || EMPTY_STRING) }),
                new Constructed({
                    name: (names.originatorInfo || EMPTY_STRING),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: OriginatorInfo.schema().valueBlock.value
                }),
                new Set({
                    value: [
                        new Repeated({
                            name: (names.recipientInfos || EMPTY_STRING),
                            value: RecipientInfo.schema()
                        })
                    ]
                }),
                EncryptedContentInfo.schema(names.encryptedContentInfo || {}),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: [
                        new Repeated({
                            name: (names.unprotectedAttrs || EMPTY_STRING),
                            value: Attribute.schema()
                        })
                    ]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$k);
        const asn1 = compareSchema(schema, schema, EnvelopedData.schema({
            names: {
                version: VERSION$8,
                originatorInfo: ORIGINATOR_INFO,
                recipientInfos: RECIPIENT_INFOS,
                encryptedContentInfo: {
                    names: {
                        blockName: ENCRYPTED_CONTENT_INFO
                    }
                },
                unprotectedAttrs: UNPROTECTED_ATTRS
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.version = asn1.result.version.valueBlock.valueDec;
        if (ORIGINATOR_INFO in asn1.result) {
            this.originatorInfo = new OriginatorInfo({
                schema: new Sequence({
                    value: asn1.result.originatorInfo.valueBlock.value
                })
            });
        }
        this.recipientInfos = Array.from(asn1.result.recipientInfos, o => new RecipientInfo({ schema: o }));
        this.encryptedContentInfo = new EncryptedContentInfo({ schema: asn1.result.encryptedContentInfo });
        if (UNPROTECTED_ATTRS in asn1.result)
            this.unprotectedAttrs = Array.from(asn1.result.unprotectedAttrs, o => new Attribute({ schema: o }));
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(new Integer({ value: this.version }));
        if (this.originatorInfo) {
            outputArray.push(new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: this.originatorInfo.toSchema().valueBlock.value
            }));
        }
        outputArray.push(new Set({
            value: Array.from(this.recipientInfos, o => o.toSchema())
        }));
        outputArray.push(this.encryptedContentInfo.toSchema());
        if (this.unprotectedAttrs) {
            outputArray.push(new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                value: Array.from(this.unprotectedAttrs, o => o.toSchema())
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            version: this.version,
            recipientInfos: Array.from(this.recipientInfos, o => o.toJSON()),
            encryptedContentInfo: this.encryptedContentInfo.toJSON(),
        };
        if (this.originatorInfo)
            res.originatorInfo = this.originatorInfo.toJSON();
        if (this.unprotectedAttrs)
            res.unprotectedAttrs = Array.from(this.unprotectedAttrs, o => o.toJSON());
        return res;
    }
    addRecipientByCertificate(certificate, parameters, variant, crypto = getCrypto(true)) {
        const encryptionParameters = Object.assign({ useOAEP: true, oaepHashAlgorithm: "SHA-512" }, defaultEncryptionParams, parameters || {});
        if (certificate.subjectPublicKeyInfo.algorithm.algorithmId.indexOf("1.2.840.113549") !== (-1))
            variant = 1;
        else {
            if (certificate.subjectPublicKeyInfo.algorithm.algorithmId.indexOf("1.2.840.10045") !== (-1))
                variant = 2;
            else
                throw new Error(`Unknown type of certificate's public key: ${certificate.subjectPublicKeyInfo.algorithm.algorithmId}`);
        }
        switch (variant) {
            case 1:
                {
                    let algorithmId;
                    let algorithmParams;
                    if (encryptionParameters.useOAEP === true) {
                        algorithmId = crypto.getOIDByAlgorithm({
                            name: "RSA-OAEP"
                        }, true, "keyEncryptionAlgorithm");
                        const hashOID = crypto.getOIDByAlgorithm({
                            name: encryptionParameters.oaepHashAlgorithm
                        }, true, "RSAES-OAEP-params");
                        const hashAlgorithm = new AlgorithmIdentifier({
                            algorithmId: hashOID,
                            algorithmParams: new Null()
                        });
                        const rsaOAEPParams = new RSAESOAEPParams({
                            hashAlgorithm,
                            maskGenAlgorithm: new AlgorithmIdentifier({
                                algorithmId: "1.2.840.113549.1.1.8",
                                algorithmParams: hashAlgorithm.toSchema()
                            })
                        });
                        algorithmParams = rsaOAEPParams.toSchema();
                    }
                    else {
                        algorithmId = crypto.getOIDByAlgorithm({
                            name: "RSAES-PKCS1-v1_5"
                        });
                        if (algorithmId === EMPTY_STRING)
                            throw new Error("Can not find OID for RSAES-PKCS1-v1_5");
                        algorithmParams = new Null();
                    }
                    const keyInfo = new KeyTransRecipientInfo({
                        version: 0,
                        rid: new IssuerAndSerialNumber({
                            issuer: certificate.issuer,
                            serialNumber: certificate.serialNumber
                        }),
                        keyEncryptionAlgorithm: new AlgorithmIdentifier({
                            algorithmId,
                            algorithmParams
                        }),
                        recipientCertificate: certificate,
                    });
                    this.recipientInfos.push(new RecipientInfo({
                        variant: 1,
                        value: keyInfo
                    }));
                }
                break;
            case 2:
                {
                    const recipientIdentifier = new KeyAgreeRecipientIdentifier({
                        variant: 1,
                        value: new IssuerAndSerialNumber({
                            issuer: certificate.issuer,
                            serialNumber: certificate.serialNumber
                        })
                    });
                    this._addKeyAgreeRecipientInfo(recipientIdentifier, encryptionParameters, { recipientCertificate: certificate }, crypto);
                }
                break;
            default:
                throw new Error(`Unknown "variant" value: ${variant}`);
        }
        return true;
    }
    addRecipientByPreDefinedData(preDefinedData, parameters = {}, variant, crypto = getCrypto(true)) {
        ArgumentError.assert(preDefinedData, "preDefinedData", "ArrayBuffer");
        if (!preDefinedData.byteLength) {
            throw new Error("Pre-defined data could have zero length");
        }
        if (!parameters.keyIdentifier) {
            const keyIdentifierBuffer = new ArrayBuffer(16);
            const keyIdentifierView = new Uint8Array(keyIdentifierBuffer);
            crypto.getRandomValues(keyIdentifierView);
            parameters.keyIdentifier = keyIdentifierBuffer;
        }
        if (!parameters.hmacHashAlgorithm)
            parameters.hmacHashAlgorithm = "SHA-512";
        if (parameters.iterationCount === undefined) {
            parameters.iterationCount = 2048;
        }
        if (!parameters.keyEncryptionAlgorithm) {
            parameters.keyEncryptionAlgorithm = {
                name: "AES-KW",
                length: 256
            };
        }
        if (!parameters.keyEncryptionAlgorithmParams)
            parameters.keyEncryptionAlgorithmParams = new Null();
        switch (variant) {
            case 1:
                {
                    const kekOID = crypto.getOIDByAlgorithm(parameters.keyEncryptionAlgorithm, true, "keyEncryptionAlgorithm");
                    const keyInfo = new KEKRecipientInfo({
                        version: 4,
                        kekid: new KEKIdentifier({
                            keyIdentifier: new OctetString({ valueHex: parameters.keyIdentifier })
                        }),
                        keyEncryptionAlgorithm: new AlgorithmIdentifier({
                            algorithmId: kekOID,
                            algorithmParams: parameters.keyEncryptionAlgorithmParams
                        }),
                        preDefinedKEK: preDefinedData
                    });
                    this.recipientInfos.push(new RecipientInfo({
                        variant: 3,
                        value: keyInfo
                    }));
                }
                break;
            case 2:
                {
                    const pbkdf2OID = crypto.getOIDByAlgorithm({ name: "PBKDF2" }, true, "keyDerivationAlgorithm");
                    const saltBuffer = new ArrayBuffer(64);
                    const saltView = new Uint8Array(saltBuffer);
                    crypto.getRandomValues(saltView);
                    const hmacOID = crypto.getOIDByAlgorithm({
                        name: "HMAC",
                        hash: {
                            name: parameters.hmacHashAlgorithm
                        }
                    }, true, "hmacHashAlgorithm");
                    const pbkdf2Params = new PBKDF2Params({
                        salt: new OctetString({ valueHex: saltBuffer }),
                        iterationCount: parameters.iterationCount,
                        prf: new AlgorithmIdentifier({
                            algorithmId: hmacOID,
                            algorithmParams: new Null()
                        })
                    });
                    const kekOID = crypto.getOIDByAlgorithm(parameters.keyEncryptionAlgorithm, true, "keyEncryptionAlgorithm");
                    const keyInfo = new PasswordRecipientinfo({
                        version: 0,
                        keyDerivationAlgorithm: new AlgorithmIdentifier({
                            algorithmId: pbkdf2OID,
                            algorithmParams: pbkdf2Params.toSchema()
                        }),
                        keyEncryptionAlgorithm: new AlgorithmIdentifier({
                            algorithmId: kekOID,
                            algorithmParams: parameters.keyEncryptionAlgorithmParams
                        }),
                        password: preDefinedData
                    });
                    this.recipientInfos.push(new RecipientInfo({
                        variant: 4,
                        value: keyInfo
                    }));
                }
                break;
            default:
                throw new Error(`Unknown value for "variant": ${variant}`);
        }
    }
    addRecipientByKeyIdentifier(key, keyId, parameters, crypto = getCrypto(true)) {
        const encryptionParameters = Object.assign({}, defaultEncryptionParams, parameters || {});
        const recipientIdentifier = new KeyAgreeRecipientIdentifier({
            variant: 2,
            value: new RecipientKeyIdentifier({
                subjectKeyIdentifier: new OctetString({ valueHex: keyId }),
            })
        });
        this._addKeyAgreeRecipientInfo(recipientIdentifier, encryptionParameters, { recipientPublicKey: key }, crypto);
    }
    _addKeyAgreeRecipientInfo(recipientIdentifier, encryptionParameters, extraRecipientInfoParams, crypto = getCrypto(true)) {
        const encryptedKey = new RecipientEncryptedKey({
            rid: recipientIdentifier
        });
        const aesKWoid = crypto.getOIDByAlgorithm({
            name: "AES-KW",
            length: encryptionParameters.kekEncryptionLength
        }, true, "keyEncryptionAlgorithm");
        const aesKW = new AlgorithmIdentifier({
            algorithmId: aesKWoid,
        });
        const ecdhOID = crypto.getOIDByAlgorithm({
            name: "ECDH",
            kdf: encryptionParameters.kdfAlgorithm
        }, true, "KeyAgreeRecipientInfo");
        const ukmBuffer = new ArrayBuffer(64);
        const ukmView = new Uint8Array(ukmBuffer);
        crypto.getRandomValues(ukmView);
        const recipientInfoParams = {
            version: 3,
            ukm: new OctetString({ valueHex: ukmBuffer }),
            keyEncryptionAlgorithm: new AlgorithmIdentifier({
                algorithmId: ecdhOID,
                algorithmParams: aesKW.toSchema()
            }),
            recipientEncryptedKeys: new RecipientEncryptedKeys({
                encryptedKeys: [encryptedKey]
            })
        };
        const keyInfo = new KeyAgreeRecipientInfo(Object.assign(recipientInfoParams, extraRecipientInfoParams));
        this.recipientInfos.push(new RecipientInfo({
            variant: 2,
            value: keyInfo
        }));
    }
    encrypt(contentEncryptionAlgorithm, contentToEncrypt, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            const ivBuffer = new ArrayBuffer(16);
            const ivView = new Uint8Array(ivBuffer);
            crypto.getRandomValues(ivView);
            const contentView = new Uint8Array(contentToEncrypt);
            const contentEncryptionOID = crypto.getOIDByAlgorithm(contentEncryptionAlgorithm, true, "contentEncryptionAlgorithm");
            const sessionKey = yield crypto.generateKey(contentEncryptionAlgorithm, true, ["encrypt"]);
            const encryptedContent = yield crypto.encrypt({
                name: contentEncryptionAlgorithm.name,
                iv: ivView
            }, sessionKey, contentView);
            const exportedSessionKey = yield crypto.exportKey("raw", sessionKey);
            this.version = 2;
            this.encryptedContentInfo = new EncryptedContentInfo({
                contentType: "1.2.840.113549.1.7.1",
                contentEncryptionAlgorithm: new AlgorithmIdentifier({
                    algorithmId: contentEncryptionOID,
                    algorithmParams: new OctetString({ valueHex: ivBuffer })
                }),
                encryptedContent: new OctetString({ valueHex: encryptedContent })
            });
            const SubKeyAgreeRecipientInfo = (index) => __awaiter(this, void 0, void 0, function* () {
                const recipientInfo = this.recipientInfos[index].value;
                let recipientCurve;
                let recipientPublicKey;
                if (recipientInfo.recipientPublicKey) {
                    recipientCurve = recipientInfo.recipientPublicKey.algorithm.namedCurve;
                    recipientPublicKey = recipientInfo.recipientPublicKey;
                }
                else if (recipientInfo.recipientCertificate) {
                    const curveObject = recipientInfo.recipientCertificate.subjectPublicKeyInfo.algorithm.algorithmParams;
                    if (curveObject.constructor.blockName() !== ObjectIdentifier.blockName())
                        throw new Error(`Incorrect "recipientCertificate" for index ${index}`);
                    const curveOID = curveObject.valueBlock.toString();
                    switch (curveOID) {
                        case "1.2.840.10045.3.1.7":
                            recipientCurve = "P-256";
                            break;
                        case "1.3.132.0.34":
                            recipientCurve = "P-384";
                            break;
                        case "1.3.132.0.35":
                            recipientCurve = "P-521";
                            break;
                        default:
                            throw new Error(`Incorrect curve OID for index ${index}`);
                    }
                    recipientPublicKey = yield recipientInfo.recipientCertificate.getPublicKey({
                        algorithm: {
                            algorithm: {
                                name: "ECDH",
                                namedCurve: recipientCurve
                            },
                            usages: []
                        }
                    }, crypto);
                }
                else {
                    throw new Error("Unsupported RecipientInfo");
                }
                const recipientCurveLength = curveLengthByName[recipientCurve];
                const ecdhKeys = yield crypto.generateKey({ name: "ECDH", namedCurve: recipientCurve }, true, ["deriveBits"]);
                const exportedECDHPublicKey = yield crypto.exportKey("spki", ecdhKeys.publicKey);
                const derivedBits = yield crypto.deriveBits({
                    name: "ECDH",
                    public: recipientPublicKey
                }, ecdhKeys.privateKey, recipientCurveLength);
                const aesKWAlgorithm = new AlgorithmIdentifier({ schema: recipientInfo.keyEncryptionAlgorithm.algorithmParams });
                const kwAlgorithm = crypto.getAlgorithmByOID(aesKWAlgorithm.algorithmId, true, "aesKWAlgorithm");
                let kwLength = kwAlgorithm.length;
                const kwLengthBuffer = new ArrayBuffer(4);
                const kwLengthView = new Uint8Array(kwLengthBuffer);
                for (let j = 3; j >= 0; j--) {
                    kwLengthView[j] = kwLength;
                    kwLength >>= 8;
                }
                const eccInfo = new ECCCMSSharedInfo({
                    keyInfo: new AlgorithmIdentifier({
                        algorithmId: aesKWAlgorithm.algorithmId
                    }),
                    entityUInfo: recipientInfo.ukm,
                    suppPubInfo: new OctetString({ valueHex: kwLengthBuffer })
                });
                const encodedInfo = eccInfo.toSchema().toBER(false);
                const ecdhAlgorithm = crypto.getAlgorithmByOID(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "ecdhAlgorithm");
                const derivedKeyRaw = yield kdf(ecdhAlgorithm.kdf, derivedBits, kwAlgorithm.length, encodedInfo, crypto);
                const awsKW = yield crypto.importKey("raw", derivedKeyRaw, { name: "AES-KW" }, true, ["wrapKey"]);
                const wrappedKey = yield crypto.wrapKey("raw", sessionKey, awsKW, { name: "AES-KW" });
                const originator = new OriginatorIdentifierOrKey();
                originator.variant = 3;
                originator.value = OriginatorPublicKey.fromBER(exportedECDHPublicKey);
                recipientInfo.originator = originator;
                recipientInfo.recipientEncryptedKeys.encryptedKeys[0].encryptedKey = new OctetString({ valueHex: wrappedKey });
                return { ecdhPrivateKey: ecdhKeys.privateKey };
            });
            const SubKeyTransRecipientInfo = (index) => __awaiter(this, void 0, void 0, function* () {
                const recipientInfo = this.recipientInfos[index].value;
                const algorithmParameters = crypto.getAlgorithmByOID(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "keyEncryptionAlgorithm");
                if (algorithmParameters.name === "RSA-OAEP") {
                    const schema = recipientInfo.keyEncryptionAlgorithm.algorithmParams;
                    const rsaOAEPParams = new RSAESOAEPParams({ schema });
                    algorithmParameters.hash = crypto.getAlgorithmByOID(rsaOAEPParams.hashAlgorithm.algorithmId);
                    if (("name" in algorithmParameters.hash) === false)
                        throw new Error(`Incorrect OID for hash algorithm: ${rsaOAEPParams.hashAlgorithm.algorithmId}`);
                }
                try {
                    const publicKey = yield recipientInfo.recipientCertificate.getPublicKey({
                        algorithm: {
                            algorithm: algorithmParameters,
                            usages: ["encrypt", "wrapKey"]
                        }
                    }, crypto);
                    const encryptedKey = yield crypto.encrypt(publicKey.algorithm, publicKey, exportedSessionKey);
                    recipientInfo.encryptedKey = new OctetString({ valueHex: encryptedKey });
                }
                catch (_a) {
                }
            });
            const SubKEKRecipientInfo = (index) => __awaiter(this, void 0, void 0, function* () {
                const recipientInfo = this.recipientInfos[index].value;
                const kekAlgorithm = crypto.getAlgorithmByOID(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "kekAlgorithm");
                const kekKey = yield crypto.importKey("raw", new Uint8Array(recipientInfo.preDefinedKEK), kekAlgorithm, true, ["wrapKey"]);
                const wrappedKey = yield crypto.wrapKey("raw", sessionKey, kekKey, kekAlgorithm);
                recipientInfo.encryptedKey = new OctetString({ valueHex: wrappedKey });
            });
            const SubPasswordRecipientinfo = (index) => __awaiter(this, void 0, void 0, function* () {
                const recipientInfo = this.recipientInfos[index].value;
                let pbkdf2Params;
                if (!recipientInfo.keyDerivationAlgorithm)
                    throw new Error("Please append encoded \"keyDerivationAlgorithm\"");
                if (!recipientInfo.keyDerivationAlgorithm.algorithmParams)
                    throw new Error("Incorrectly encoded \"keyDerivationAlgorithm\"");
                try {
                    pbkdf2Params = new PBKDF2Params({ schema: recipientInfo.keyDerivationAlgorithm.algorithmParams });
                }
                catch (ex) {
                    throw new Error("Incorrectly encoded \"keyDerivationAlgorithm\"");
                }
                const passwordView = new Uint8Array(recipientInfo.password);
                const derivationKey = yield crypto.importKey("raw", passwordView, "PBKDF2", false, ["deriveKey"]);
                const kekAlgorithm = crypto.getAlgorithmByOID(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "kekAlgorithm");
                let hmacHashAlgorithm = "SHA-1";
                if (pbkdf2Params.prf) {
                    const prfAlgorithm = crypto.getAlgorithmByOID(pbkdf2Params.prf.algorithmId, true, "prfAlgorithm");
                    hmacHashAlgorithm = prfAlgorithm.hash.name;
                }
                const saltView = new Uint8Array(pbkdf2Params.salt.valueBlock.valueHex);
                const iterations = pbkdf2Params.iterationCount;
                const derivedKey = yield crypto.deriveKey({
                    name: "PBKDF2",
                    hash: {
                        name: hmacHashAlgorithm
                    },
                    salt: saltView,
                    iterations
                }, derivationKey, kekAlgorithm, true, ["wrapKey"]);
                const wrappedKey = yield crypto.wrapKey("raw", sessionKey, derivedKey, kekAlgorithm);
                recipientInfo.encryptedKey = new OctetString({ valueHex: wrappedKey });
            });
            const res = [];
            for (let i = 0; i < this.recipientInfos.length; i++) {
                switch (this.recipientInfos[i].variant) {
                    case 1:
                        res.push(yield SubKeyTransRecipientInfo(i));
                        break;
                    case 2:
                        res.push(yield SubKeyAgreeRecipientInfo(i));
                        break;
                    case 3:
                        res.push(yield SubKEKRecipientInfo(i));
                        break;
                    case 4:
                        res.push(yield SubPasswordRecipientinfo(i));
                        break;
                    default:
                        throw new Error(`Unknown recipient type in array with index ${i}`);
                }
            }
            return res;
        });
    }
    decrypt(recipientIndex, parameters, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            const decryptionParameters = parameters || {};
            if ((recipientIndex + 1) > this.recipientInfos.length) {
                throw new Error(`Maximum value for "index" is: ${this.recipientInfos.length - 1}`);
            }
            const SubKeyAgreeRecipientInfo = (index) => __awaiter(this, void 0, void 0, function* () {
                const recipientInfo = this.recipientInfos[index].value;
                let curveOID;
                let recipientCurve;
                let recipientCurveLength;
                const originator = recipientInfo.originator;
                if (decryptionParameters.recipientCertificate) {
                    const curveObject = decryptionParameters.recipientCertificate.subjectPublicKeyInfo.algorithm.algorithmParams;
                    if (curveObject.constructor.blockName() !== ObjectIdentifier.blockName()) {
                        throw new Error(`Incorrect "recipientCertificate" for index ${index}`);
                    }
                    curveOID = curveObject.valueBlock.toString();
                }
                else if (originator.value.algorithm.algorithmParams) {
                    const curveObject = originator.value.algorithm.algorithmParams;
                    if (curveObject.constructor.blockName() !== ObjectIdentifier.blockName()) {
                        throw new Error(`Incorrect originator for index ${index}`);
                    }
                    curveOID = curveObject.valueBlock.toString();
                }
                else {
                    throw new Error("Parameter \"recipientCertificate\" is mandatory for \"KeyAgreeRecipientInfo\" if algorithm params are missing from originator");
                }
                if (!decryptionParameters.recipientPrivateKey)
                    throw new Error("Parameter \"recipientPrivateKey\" is mandatory for \"KeyAgreeRecipientInfo\"");
                switch (curveOID) {
                    case "1.2.840.10045.3.1.7":
                        recipientCurve = "P-256";
                        recipientCurveLength = 256;
                        break;
                    case "1.3.132.0.34":
                        recipientCurve = "P-384";
                        recipientCurveLength = 384;
                        break;
                    case "1.3.132.0.35":
                        recipientCurve = "P-521";
                        recipientCurveLength = 528;
                        break;
                    default:
                        throw new Error(`Incorrect curve OID for index ${index}`);
                }
                const ecdhPrivateKey = yield crypto.importKey("pkcs8", decryptionParameters.recipientPrivateKey, {
                    name: "ECDH",
                    namedCurve: recipientCurve
                }, true, ["deriveBits"]);
                if (("algorithmParams" in originator.value.algorithm) === false)
                    originator.value.algorithm.algorithmParams = new ObjectIdentifier({ value: curveOID });
                const buffer = originator.value.toSchema().toBER(false);
                const ecdhPublicKey = yield crypto.importKey("spki", buffer, {
                    name: "ECDH",
                    namedCurve: recipientCurve
                }, true, []);
                const sharedSecret = yield crypto.deriveBits({
                    name: "ECDH",
                    public: ecdhPublicKey
                }, ecdhPrivateKey, recipientCurveLength);
                function applyKDF(includeAlgorithmParams) {
                    return __awaiter(this, void 0, void 0, function* () {
                        includeAlgorithmParams = includeAlgorithmParams || false;
                        const aesKWAlgorithm = new AlgorithmIdentifier({ schema: recipientInfo.keyEncryptionAlgorithm.algorithmParams });
                        const kwAlgorithm = crypto.getAlgorithmByOID(aesKWAlgorithm.algorithmId, true, "kwAlgorithm");
                        let kwLength = kwAlgorithm.length;
                        const kwLengthBuffer = new ArrayBuffer(4);
                        const kwLengthView = new Uint8Array(kwLengthBuffer);
                        for (let j = 3; j >= 0; j--) {
                            kwLengthView[j] = kwLength;
                            kwLength >>= 8;
                        }
                        const keyInfoAlgorithm = {
                            algorithmId: aesKWAlgorithm.algorithmId
                        };
                        if (includeAlgorithmParams) {
                            keyInfoAlgorithm.algorithmParams = new Null();
                        }
                        const eccInfo = new ECCCMSSharedInfo({
                            keyInfo: new AlgorithmIdentifier(keyInfoAlgorithm),
                            entityUInfo: recipientInfo.ukm,
                            suppPubInfo: new OctetString({ valueHex: kwLengthBuffer })
                        });
                        const encodedInfo = eccInfo.toSchema().toBER(false);
                        const ecdhAlgorithm = crypto.getAlgorithmByOID(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "ecdhAlgorithm");
                        if (!ecdhAlgorithm.name) {
                            throw new Error(`Incorrect OID for key encryption algorithm: ${recipientInfo.keyEncryptionAlgorithm.algorithmId}`);
                        }
                        return kdf(ecdhAlgorithm.kdf, sharedSecret, kwAlgorithm.length, encodedInfo, crypto);
                    });
                }
                const kdfResult = yield applyKDF();
                const importAesKwKey = (kdfResult) => __awaiter(this, void 0, void 0, function* () {
                    return crypto.importKey("raw", kdfResult, { name: "AES-KW" }, true, ["unwrapKey"]);
                });
                const aesKwKey = yield importAesKwKey(kdfResult);
                const unwrapSessionKey = (aesKwKey) => __awaiter(this, void 0, void 0, function* () {
                    const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId;
                    const contentEncryptionAlgorithm = crypto.getAlgorithmByOID(algorithmId, true, "contentEncryptionAlgorithm");
                    return crypto.unwrapKey("raw", recipientInfo.recipientEncryptedKeys.encryptedKeys[0].encryptedKey.valueBlock.valueHexView, aesKwKey, { name: "AES-KW" }, contentEncryptionAlgorithm, true, ["decrypt"]);
                });
                try {
                    return yield unwrapSessionKey(aesKwKey);
                }
                catch (_a) {
                    const kdfResult = yield applyKDF(true);
                    const aesKwKey = yield importAesKwKey(kdfResult);
                    return unwrapSessionKey(aesKwKey);
                }
            });
            const SubKeyTransRecipientInfo = (index) => __awaiter(this, void 0, void 0, function* () {
                const recipientInfo = this.recipientInfos[index].value;
                if (!decryptionParameters.recipientPrivateKey) {
                    throw new Error("Parameter \"recipientPrivateKey\" is mandatory for \"KeyTransRecipientInfo\"");
                }
                const algorithmParameters = crypto.getAlgorithmByOID(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "keyEncryptionAlgorithm");
                if (algorithmParameters.name === "RSA-OAEP") {
                    const schema = recipientInfo.keyEncryptionAlgorithm.algorithmParams;
                    const rsaOAEPParams = new RSAESOAEPParams({ schema });
                    algorithmParameters.hash = crypto.getAlgorithmByOID(rsaOAEPParams.hashAlgorithm.algorithmId);
                    if (("name" in algorithmParameters.hash) === false)
                        throw new Error(`Incorrect OID for hash algorithm: ${rsaOAEPParams.hashAlgorithm.algorithmId}`);
                }
                const privateKey = yield crypto.importKey("pkcs8", decryptionParameters.recipientPrivateKey, algorithmParameters, true, ["decrypt"]);
                const sessionKey = yield crypto.decrypt(privateKey.algorithm, privateKey, recipientInfo.encryptedKey.valueBlock.valueHexView);
                const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId;
                const contentEncryptionAlgorithm = crypto.getAlgorithmByOID(algorithmId, true, "contentEncryptionAlgorithm");
                if (("name" in contentEncryptionAlgorithm) === false)
                    throw new Error(`Incorrect "contentEncryptionAlgorithm": ${algorithmId}`);
                return crypto.importKey("raw", sessionKey, contentEncryptionAlgorithm, true, ["decrypt"]);
            });
            const SubKEKRecipientInfo = (index) => __awaiter(this, void 0, void 0, function* () {
                const recipientInfo = this.recipientInfos[index].value;
                if (!decryptionParameters.preDefinedData)
                    throw new Error("Parameter \"preDefinedData\" is mandatory for \"KEKRecipientInfo\"");
                const kekAlgorithm = crypto.getAlgorithmByOID(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "kekAlgorithm");
                const importedKey = yield crypto.importKey("raw", decryptionParameters.preDefinedData, kekAlgorithm, true, ["unwrapKey"]);
                const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId;
                const contentEncryptionAlgorithm = crypto.getAlgorithmByOID(algorithmId, true, "contentEncryptionAlgorithm");
                if (!contentEncryptionAlgorithm.name) {
                    throw new Error(`Incorrect "contentEncryptionAlgorithm": ${algorithmId}`);
                }
                return crypto.unwrapKey("raw", recipientInfo.encryptedKey.valueBlock.valueHexView, importedKey, kekAlgorithm, contentEncryptionAlgorithm, true, ["decrypt"]);
            });
            const SubPasswordRecipientinfo = (index) => __awaiter(this, void 0, void 0, function* () {
                const recipientInfo = this.recipientInfos[index].value;
                let pbkdf2Params;
                if (!decryptionParameters.preDefinedData) {
                    throw new Error("Parameter \"preDefinedData\" is mandatory for \"KEKRecipientInfo\"");
                }
                if (!recipientInfo.keyDerivationAlgorithm) {
                    throw new Error("Please append encoded \"keyDerivationAlgorithm\"");
                }
                if (!recipientInfo.keyDerivationAlgorithm.algorithmParams) {
                    throw new Error("Incorrectly encoded \"keyDerivationAlgorithm\"");
                }
                try {
                    pbkdf2Params = new PBKDF2Params({ schema: recipientInfo.keyDerivationAlgorithm.algorithmParams });
                }
                catch (ex) {
                    throw new Error("Incorrectly encoded \"keyDerivationAlgorithm\"");
                }
                const pbkdf2Key = yield crypto.importKey("raw", decryptionParameters.preDefinedData, "PBKDF2", false, ["deriveKey"]);
                const kekAlgorithm = crypto.getAlgorithmByOID(recipientInfo.keyEncryptionAlgorithm.algorithmId, true, "keyEncryptionAlgorithm");
                const hmacHashAlgorithm = pbkdf2Params.prf
                    ? crypto.getAlgorithmByOID(pbkdf2Params.prf.algorithmId, true, "prfAlgorithm").hash.name
                    : "SHA-1";
                const saltView = new Uint8Array(pbkdf2Params.salt.valueBlock.valueHex);
                const iterations = pbkdf2Params.iterationCount;
                const kekKey = yield crypto.deriveKey({
                    name: "PBKDF2",
                    hash: {
                        name: hmacHashAlgorithm
                    },
                    salt: saltView,
                    iterations
                }, pbkdf2Key, kekAlgorithm, true, ["unwrapKey"]);
                const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId;
                const contentEncryptionAlgorithm = crypto.getAlgorithmByOID(algorithmId, true, "contentEncryptionAlgorithm");
                return crypto.unwrapKey("raw", recipientInfo.encryptedKey.valueBlock.valueHexView, kekKey, kekAlgorithm, contentEncryptionAlgorithm, true, ["decrypt"]);
            });
            let unwrappedKey;
            switch (this.recipientInfos[recipientIndex].variant) {
                case 1:
                    unwrappedKey = yield SubKeyTransRecipientInfo(recipientIndex);
                    break;
                case 2:
                    unwrappedKey = yield SubKeyAgreeRecipientInfo(recipientIndex);
                    break;
                case 3:
                    unwrappedKey = yield SubKEKRecipientInfo(recipientIndex);
                    break;
                case 4:
                    unwrappedKey = yield SubPasswordRecipientinfo(recipientIndex);
                    break;
                default:
                    throw new Error(`Unknown recipient type in array with index ${recipientIndex}`);
            }
            const algorithmId = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId;
            const contentEncryptionAlgorithm = crypto.getAlgorithmByOID(algorithmId, true, "contentEncryptionAlgorithm");
            const ivBuffer = this.encryptedContentInfo.contentEncryptionAlgorithm.algorithmParams.valueBlock.valueHex;
            const ivView = new Uint8Array(ivBuffer);
            if (!this.encryptedContentInfo.encryptedContent) {
                throw new Error("Required property `encryptedContent` is empty");
            }
            const dataBuffer = this.encryptedContentInfo.getEncryptedContent();
            return crypto.decrypt({
                name: contentEncryptionAlgorithm.name,
                iv: ivView
            }, unwrappedKey, dataBuffer);
        });
    }
}
EnvelopedData.CLASS_NAME = "EnvelopedData";

const SAFE_CONTENTS = "safeContents";
const PARSED_VALUE$1 = "parsedValue";
const CONTENT_INFOS = "contentInfos";
class AuthenticatedSafe extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.safeContents = getParametersValue(parameters, SAFE_CONTENTS, AuthenticatedSafe.defaultValues(SAFE_CONTENTS));
        if (PARSED_VALUE$1 in parameters) {
            this.parsedValue = getParametersValue(parameters, PARSED_VALUE$1, AuthenticatedSafe.defaultValues(PARSED_VALUE$1));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case SAFE_CONTENTS:
                return [];
            case PARSED_VALUE$1:
                return {};
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case SAFE_CONTENTS:
                return (memberValue.length === 0);
            case PARSED_VALUE$1:
                return ((memberValue instanceof Object) && (Object.keys(memberValue).length === 0));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Repeated({
                    name: (names.contentInfos || EMPTY_STRING),
                    value: ContentInfo.schema()
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, [
            CONTENT_INFOS
        ]);
        const asn1 = compareSchema(schema, schema, AuthenticatedSafe.schema({
            names: {
                contentInfos: CONTENT_INFOS
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.safeContents = Array.from(asn1.result.contentInfos, element => new ContentInfo({ schema: element }));
    }
    toSchema() {
        return (new Sequence({
            value: Array.from(this.safeContents, o => o.toSchema())
        }));
    }
    toJSON() {
        return {
            safeContents: Array.from(this.safeContents, o => o.toJSON())
        };
    }
    parseInternalValues(parameters, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            ParameterError.assert(parameters, SAFE_CONTENTS);
            ArgumentError.assert(parameters.safeContents, SAFE_CONTENTS, "Array");
            if (parameters.safeContents.length !== this.safeContents.length) {
                throw new ArgumentError("Length of \"parameters.safeContents\" must be equal to \"this.safeContents.length\"");
            }
            this.parsedValue = {
                safeContents: [],
            };
            for (const [index, content] of this.safeContents.entries()) {
                const safeContent = parameters.safeContents[index];
                const errorTarget = `parameters.safeContents[${index}]`;
                switch (content.contentType) {
                    case id_ContentType_Data:
                        {
                            ArgumentError.assert(content.content, "this.safeContents[j].content", OctetString);
                            const authSafeContent = content.content.getValue();
                            this.parsedValue.safeContents.push({
                                privacyMode: 0,
                                value: SafeContents.fromBER(authSafeContent)
                            });
                        }
                        break;
                    case id_ContentType_EnvelopedData:
                        {
                            const cmsEnveloped = new EnvelopedData({ schema: content.content });
                            ParameterError.assert(errorTarget, safeContent, "recipientCertificate", "recipientKey");
                            const envelopedData = safeContent;
                            const recipientCertificate = envelopedData.recipientCertificate;
                            const recipientKey = envelopedData.recipientKey;
                            const decrypted = yield cmsEnveloped.decrypt(0, {
                                recipientCertificate,
                                recipientPrivateKey: recipientKey
                            }, crypto);
                            this.parsedValue.safeContents.push({
                                privacyMode: 2,
                                value: SafeContents.fromBER(decrypted),
                            });
                        }
                        break;
                    case id_ContentType_EncryptedData:
                        {
                            const cmsEncrypted = new EncryptedData({ schema: content.content });
                            ParameterError.assert(errorTarget, safeContent, "password");
                            const password = safeContent.password;
                            const decrypted = yield cmsEncrypted.decrypt({
                                password
                            }, crypto);
                            this.parsedValue.safeContents.push({
                                privacyMode: 1,
                                value: SafeContents.fromBER(decrypted),
                            });
                        }
                        break;
                    default:
                        throw new Error(`Unknown "contentType" for AuthenticatedSafe: " ${content.contentType}`);
                }
            }
        });
    }
    makeInternalValues(parameters, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!(this.parsedValue)) {
                throw new Error("Please run \"parseValues\" first or add \"parsedValue\" manually");
            }
            ArgumentError.assert(this.parsedValue, "this.parsedValue", "object");
            ArgumentError.assert(this.parsedValue.safeContents, "this.parsedValue.safeContents", "Array");
            ArgumentError.assert(parameters, "parameters", "object");
            ParameterError.assert(parameters, "safeContents");
            ArgumentError.assert(parameters.safeContents, "parameters.safeContents", "Array");
            if (parameters.safeContents.length !== this.parsedValue.safeContents.length) {
                throw new ArgumentError("Length of \"parameters.safeContents\" must be equal to \"this.parsedValue.safeContents\"");
            }
            this.safeContents = [];
            for (const [index, content] of this.parsedValue.safeContents.entries()) {
                ParameterError.assert("content", content, "privacyMode", "value");
                ArgumentError.assert(content.value, "content.value", SafeContents);
                switch (content.privacyMode) {
                    case 0:
                        {
                            const contentBuffer = content.value.toSchema().toBER(false);
                            this.safeContents.push(new ContentInfo({
                                contentType: "1.2.840.113549.1.7.1",
                                content: new OctetString({ valueHex: contentBuffer })
                            }));
                        }
                        break;
                    case 1:
                        {
                            const cmsEncrypted = new EncryptedData();
                            const currentParameters = parameters.safeContents[index];
                            currentParameters.contentToEncrypt = content.value.toSchema().toBER(false);
                            yield cmsEncrypted.encrypt(currentParameters);
                            this.safeContents.push(new ContentInfo({
                                contentType: "1.2.840.113549.1.7.6",
                                content: cmsEncrypted.toSchema()
                            }));
                        }
                        break;
                    case 2:
                        {
                            const cmsEnveloped = new EnvelopedData();
                            const contentToEncrypt = content.value.toSchema().toBER(false);
                            const safeContent = parameters.safeContents[index];
                            ParameterError.assert(`parameters.safeContents[${index}]`, safeContent, "encryptingCertificate", "encryptionAlgorithm");
                            switch (true) {
                                case (safeContent.encryptionAlgorithm.name.toLowerCase() === "aes-cbc"):
                                case (safeContent.encryptionAlgorithm.name.toLowerCase() === "aes-gcm"):
                                    break;
                                default:
                                    throw new Error(`Incorrect parameter "encryptionAlgorithm" in "parameters.safeContents[i]": ${safeContent.encryptionAlgorithm}`);
                            }
                            switch (true) {
                                case (safeContent.encryptionAlgorithm.length === 128):
                                case (safeContent.encryptionAlgorithm.length === 192):
                                case (safeContent.encryptionAlgorithm.length === 256):
                                    break;
                                default:
                                    throw new Error(`Incorrect parameter "encryptionAlgorithm.length" in "parameters.safeContents[i]": ${safeContent.encryptionAlgorithm.length}`);
                            }
                            const encryptionAlgorithm = safeContent.encryptionAlgorithm;
                            cmsEnveloped.addRecipientByCertificate(safeContent.encryptingCertificate, {}, undefined, crypto);
                            yield cmsEnveloped.encrypt(encryptionAlgorithm, contentToEncrypt, crypto);
                            this.safeContents.push(new ContentInfo({
                                contentType: "1.2.840.113549.1.7.3",
                                content: cmsEnveloped.toSchema()
                            }));
                        }
                        break;
                    default:
                        throw new Error(`Incorrect value for "content.privacyMode": ${content.privacyMode}`);
                }
            }
            return this;
        });
    }
}
AuthenticatedSafe.CLASS_NAME = "AuthenticatedSafe";

const HASH_ALGORITHM$1 = "hashAlgorithm";
const ISSUER_NAME_HASH = "issuerNameHash";
const ISSUER_KEY_HASH = "issuerKeyHash";
const SERIAL_NUMBER$1 = "serialNumber";
const CLEAR_PROPS$j = [
    HASH_ALGORITHM$1,
    ISSUER_NAME_HASH,
    ISSUER_KEY_HASH,
    SERIAL_NUMBER$1,
];
class CertID extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.hashAlgorithm = getParametersValue(parameters, HASH_ALGORITHM$1, CertID.defaultValues(HASH_ALGORITHM$1));
        this.issuerNameHash = getParametersValue(parameters, ISSUER_NAME_HASH, CertID.defaultValues(ISSUER_NAME_HASH));
        this.issuerKeyHash = getParametersValue(parameters, ISSUER_KEY_HASH, CertID.defaultValues(ISSUER_KEY_HASH));
        this.serialNumber = getParametersValue(parameters, SERIAL_NUMBER$1, CertID.defaultValues(SERIAL_NUMBER$1));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static create(certificate, parameters, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            const certID = new CertID();
            yield certID.createForCertificate(certificate, parameters, crypto);
            return certID;
        });
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case HASH_ALGORITHM$1:
                return new AlgorithmIdentifier();
            case ISSUER_NAME_HASH:
            case ISSUER_KEY_HASH:
                return new OctetString();
            case SERIAL_NUMBER$1:
                return new Integer();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case HASH_ALGORITHM$1:
                return ((memberValue.algorithmId === EMPTY_STRING) && (("algorithmParams" in memberValue) === false));
            case ISSUER_NAME_HASH:
            case ISSUER_KEY_HASH:
            case SERIAL_NUMBER$1:
                return (memberValue.isEqual(CertID.defaultValues(SERIAL_NUMBER$1)));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                AlgorithmIdentifier.schema(names.hashAlgorithmObject || {
                    names: {
                        blockName: (names.hashAlgorithm || EMPTY_STRING)
                    }
                }),
                new OctetString({ name: (names.issuerNameHash || EMPTY_STRING) }),
                new OctetString({ name: (names.issuerKeyHash || EMPTY_STRING) }),
                new Integer({ name: (names.serialNumber || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$j);
        const asn1 = compareSchema(schema, schema, CertID.schema({
            names: {
                hashAlgorithm: HASH_ALGORITHM$1,
                issuerNameHash: ISSUER_NAME_HASH,
                issuerKeyHash: ISSUER_KEY_HASH,
                serialNumber: SERIAL_NUMBER$1
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.hashAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.hashAlgorithm });
        this.issuerNameHash = asn1.result.issuerNameHash;
        this.issuerKeyHash = asn1.result.issuerKeyHash;
        this.serialNumber = asn1.result.serialNumber;
    }
    toSchema() {
        return (new Sequence({
            value: [
                this.hashAlgorithm.toSchema(),
                this.issuerNameHash,
                this.issuerKeyHash,
                this.serialNumber
            ]
        }));
    }
    toJSON() {
        return {
            hashAlgorithm: this.hashAlgorithm.toJSON(),
            issuerNameHash: this.issuerNameHash.toJSON(),
            issuerKeyHash: this.issuerKeyHash.toJSON(),
            serialNumber: this.serialNumber.toJSON(),
        };
    }
    isEqual(certificateID) {
        if (this.hashAlgorithm.algorithmId !== certificateID.hashAlgorithm.algorithmId) {
            return false;
        }
        if (!BufferSourceConverter.isEqual(this.issuerNameHash.valueBlock.valueHexView, certificateID.issuerNameHash.valueBlock.valueHexView)) {
            return false;
        }
        if (!BufferSourceConverter.isEqual(this.issuerKeyHash.valueBlock.valueHexView, certificateID.issuerKeyHash.valueBlock.valueHexView)) {
            return false;
        }
        if (!this.serialNumber.isEqual(certificateID.serialNumber)) {
            return false;
        }
        return true;
    }
    createForCertificate(certificate, parameters, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            ParameterError.assert(parameters, HASH_ALGORITHM$1, "issuerCertificate");
            const hashOID = crypto.getOIDByAlgorithm({ name: parameters.hashAlgorithm }, true, "hashAlgorithm");
            this.hashAlgorithm = new AlgorithmIdentifier({
                algorithmId: hashOID,
                algorithmParams: new Null()
            });
            const issuerCertificate = parameters.issuerCertificate;
            this.serialNumber = certificate.serialNumber;
            const hashIssuerName = yield crypto.digest({ name: parameters.hashAlgorithm }, issuerCertificate.subject.toSchema().toBER(false));
            this.issuerNameHash = new OctetString({ valueHex: hashIssuerName });
            const issuerKeyBuffer = issuerCertificate.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHexView;
            const hashIssuerKey = yield crypto.digest({ name: parameters.hashAlgorithm }, issuerKeyBuffer);
            this.issuerKeyHash = new OctetString({ valueHex: hashIssuerKey });
        });
    }
}
CertID.CLASS_NAME = "CertID";

const CERT_ID = "certID";
const CERT_STATUS = "certStatus";
const THIS_UPDATE = "thisUpdate";
const NEXT_UPDATE = "nextUpdate";
const SINGLE_EXTENSIONS = "singleExtensions";
const CLEAR_PROPS$i = [
    CERT_ID,
    CERT_STATUS,
    THIS_UPDATE,
    NEXT_UPDATE,
    SINGLE_EXTENSIONS,
];
class SingleResponse extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.certID = getParametersValue(parameters, CERT_ID, SingleResponse.defaultValues(CERT_ID));
        this.certStatus = getParametersValue(parameters, CERT_STATUS, SingleResponse.defaultValues(CERT_STATUS));
        this.thisUpdate = getParametersValue(parameters, THIS_UPDATE, SingleResponse.defaultValues(THIS_UPDATE));
        if (NEXT_UPDATE in parameters) {
            this.nextUpdate = getParametersValue(parameters, NEXT_UPDATE, SingleResponse.defaultValues(NEXT_UPDATE));
        }
        if (SINGLE_EXTENSIONS in parameters) {
            this.singleExtensions = getParametersValue(parameters, SINGLE_EXTENSIONS, SingleResponse.defaultValues(SINGLE_EXTENSIONS));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case CERT_ID:
                return new CertID();
            case CERT_STATUS:
                return {};
            case THIS_UPDATE:
            case NEXT_UPDATE:
                return new Date(0, 0, 0);
            case SINGLE_EXTENSIONS:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case CERT_ID:
                return ((CertID.compareWithDefault("hashAlgorithm", memberValue.hashAlgorithm)) &&
                    (CertID.compareWithDefault("issuerNameHash", memberValue.issuerNameHash)) &&
                    (CertID.compareWithDefault("issuerKeyHash", memberValue.issuerKeyHash)) &&
                    (CertID.compareWithDefault("serialNumber", memberValue.serialNumber)));
            case CERT_STATUS:
                return (Object.keys(memberValue).length === 0);
            case THIS_UPDATE:
            case NEXT_UPDATE:
                return (memberValue === SingleResponse.defaultValues(memberName));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                CertID.schema(names.certID || {}),
                new Choice({
                    value: [
                        new Primitive({
                            name: (names.certStatus || EMPTY_STRING),
                            idBlock: {
                                tagClass: 3,
                                tagNumber: 0
                            },
                        }),
                        new Constructed({
                            name: (names.certStatus || EMPTY_STRING),
                            idBlock: {
                                tagClass: 3,
                                tagNumber: 1
                            },
                            value: [
                                new GeneralizedTime(),
                                new Constructed({
                                    optional: true,
                                    idBlock: {
                                        tagClass: 3,
                                        tagNumber: 0
                                    },
                                    value: [new Enumerated()]
                                })
                            ]
                        }),
                        new Primitive({
                            name: (names.certStatus || EMPTY_STRING),
                            idBlock: {
                                tagClass: 3,
                                tagNumber: 2
                            },
                            lenBlock: { length: 1 }
                        })
                    ]
                }),
                new GeneralizedTime({ name: (names.thisUpdate || EMPTY_STRING) }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [new GeneralizedTime({ name: (names.nextUpdate || EMPTY_STRING) })]
                }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: [Extensions.schema(names.singleExtensions || {})]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$i);
        const asn1 = compareSchema(schema, schema, SingleResponse.schema({
            names: {
                certID: {
                    names: {
                        blockName: CERT_ID
                    }
                },
                certStatus: CERT_STATUS,
                thisUpdate: THIS_UPDATE,
                nextUpdate: NEXT_UPDATE,
                singleExtensions: {
                    names: {
                        blockName: SINGLE_EXTENSIONS
                    }
                }
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.certID = new CertID({ schema: asn1.result.certID });
        this.certStatus = asn1.result.certStatus;
        this.thisUpdate = asn1.result.thisUpdate.toDate();
        if (NEXT_UPDATE in asn1.result)
            this.nextUpdate = asn1.result.nextUpdate.toDate();
        if (SINGLE_EXTENSIONS in asn1.result)
            this.singleExtensions = Array.from(asn1.result.singleExtensions.valueBlock.value, element => new Extension({ schema: element }));
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(this.certID.toSchema());
        outputArray.push(this.certStatus);
        outputArray.push(new GeneralizedTime({ valueDate: this.thisUpdate }));
        if (this.nextUpdate) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [new GeneralizedTime({ valueDate: this.nextUpdate })]
            }));
        }
        if (this.singleExtensions) {
            outputArray.push(new Sequence({
                value: Array.from(this.singleExtensions, o => o.toSchema())
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            certID: this.certID.toJSON(),
            certStatus: this.certStatus.toJSON(),
            thisUpdate: this.thisUpdate
        };
        if (this.nextUpdate) {
            res.nextUpdate = this.nextUpdate;
        }
        if (this.singleExtensions) {
            res.singleExtensions = Array.from(this.singleExtensions, o => o.toJSON());
        }
        return res;
    }
}
SingleResponse.CLASS_NAME = "SingleResponse";

const TBS$2 = "tbs";
const VERSION$7 = "version";
const RESPONDER_ID = "responderID";
const PRODUCED_AT = "producedAt";
const RESPONSES = "responses";
const RESPONSE_EXTENSIONS = "responseExtensions";
const RESPONSE_DATA = "ResponseData";
const RESPONSE_DATA_VERSION = `${RESPONSE_DATA}.${VERSION$7}`;
const RESPONSE_DATA_RESPONDER_ID = `${RESPONSE_DATA}.${RESPONDER_ID}`;
const RESPONSE_DATA_PRODUCED_AT = `${RESPONSE_DATA}.${PRODUCED_AT}`;
const RESPONSE_DATA_RESPONSES = `${RESPONSE_DATA}.${RESPONSES}`;
const RESPONSE_DATA_RESPONSE_EXTENSIONS = `${RESPONSE_DATA}.${RESPONSE_EXTENSIONS}`;
const CLEAR_PROPS$h = [
    RESPONSE_DATA,
    RESPONSE_DATA_VERSION,
    RESPONSE_DATA_RESPONDER_ID,
    RESPONSE_DATA_PRODUCED_AT,
    RESPONSE_DATA_RESPONSES,
    RESPONSE_DATA_RESPONSE_EXTENSIONS
];
class ResponseData extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.tbsView = new Uint8Array(getParametersValue(parameters, TBS$2, ResponseData.defaultValues(TBS$2)));
        if (VERSION$7 in parameters) {
            this.version = getParametersValue(parameters, VERSION$7, ResponseData.defaultValues(VERSION$7));
        }
        this.responderID = getParametersValue(parameters, RESPONDER_ID, ResponseData.defaultValues(RESPONDER_ID));
        this.producedAt = getParametersValue(parameters, PRODUCED_AT, ResponseData.defaultValues(PRODUCED_AT));
        this.responses = getParametersValue(parameters, RESPONSES, ResponseData.defaultValues(RESPONSES));
        if (RESPONSE_EXTENSIONS in parameters) {
            this.responseExtensions = getParametersValue(parameters, RESPONSE_EXTENSIONS, ResponseData.defaultValues(RESPONSE_EXTENSIONS));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    get tbs() {
        return BufferSourceConverter.toArrayBuffer(this.tbsView);
    }
    set tbs(value) {
        this.tbsView = new Uint8Array(value);
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VERSION$7:
                return 0;
            case TBS$2:
                return EMPTY_BUFFER;
            case RESPONDER_ID:
                return {};
            case PRODUCED_AT:
                return new Date(0, 0, 0);
            case RESPONSES:
            case RESPONSE_EXTENSIONS:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case TBS$2:
                return (memberValue.byteLength === 0);
            case RESPONDER_ID:
                return (Object.keys(memberValue).length === 0);
            case PRODUCED_AT:
                return (memberValue === ResponseData.defaultValues(memberName));
            case RESPONSES:
            case RESPONSE_EXTENSIONS:
                return (memberValue.length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || RESPONSE_DATA),
            value: [
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [new Integer({ name: (names.version || RESPONSE_DATA_VERSION) })]
                }),
                new Choice({
                    value: [
                        new Constructed({
                            name: (names.responderID || RESPONSE_DATA_RESPONDER_ID),
                            idBlock: {
                                tagClass: 3,
                                tagNumber: 1
                            },
                            value: [RelativeDistinguishedNames.schema(names.ResponseDataByName || {
                                    names: {
                                        blockName: "ResponseData.byName"
                                    }
                                })]
                        }),
                        new Constructed({
                            name: (names.responderID || RESPONSE_DATA_RESPONDER_ID),
                            idBlock: {
                                tagClass: 3,
                                tagNumber: 2
                            },
                            value: [new OctetString({ name: (names.ResponseDataByKey || "ResponseData.byKey") })]
                        })
                    ]
                }),
                new GeneralizedTime({ name: (names.producedAt || RESPONSE_DATA_PRODUCED_AT) }),
                new Sequence({
                    value: [
                        new Repeated({
                            name: RESPONSE_DATA_RESPONSES,
                            value: SingleResponse.schema(names.response || {})
                        })
                    ]
                }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: [Extensions.schema(names.extensions || {
                            names: {
                                blockName: RESPONSE_DATA_RESPONSE_EXTENSIONS
                            }
                        })]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$h);
        const asn1 = compareSchema(schema, schema, ResponseData.schema());
        AsnError.assertSchema(asn1, this.className);
        this.tbsView = asn1.result.ResponseData.valueBeforeDecodeView;
        if (RESPONSE_DATA_VERSION in asn1.result)
            this.version = asn1.result[RESPONSE_DATA_VERSION].valueBlock.valueDec;
        if (asn1.result[RESPONSE_DATA_RESPONDER_ID].idBlock.tagNumber === 1)
            this.responderID = new RelativeDistinguishedNames({ schema: asn1.result[RESPONSE_DATA_RESPONDER_ID].valueBlock.value[0] });
        else
            this.responderID = asn1.result[RESPONSE_DATA_RESPONDER_ID].valueBlock.value[0];
        this.producedAt = asn1.result[RESPONSE_DATA_PRODUCED_AT].toDate();
        this.responses = Array.from(asn1.result[RESPONSE_DATA_RESPONSES], element => new SingleResponse({ schema: element }));
        if (RESPONSE_DATA_RESPONSE_EXTENSIONS in asn1.result)
            this.responseExtensions = Array.from(asn1.result[RESPONSE_DATA_RESPONSE_EXTENSIONS].valueBlock.value, element => new Extension({ schema: element }));
    }
    toSchema(encodeFlag = false) {
        let tbsSchema;
        if (encodeFlag === false) {
            if (!this.tbsView.byteLength) {
                return ResponseData.schema();
            }
            const asn1 = fromBER(this.tbsView);
            AsnError.assert(asn1, "TBS Response Data");
            tbsSchema = asn1.result;
        }
        else {
            const outputArray = [];
            if (VERSION$7 in this) {
                outputArray.push(new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [new Integer({ value: this.version })]
                }));
            }
            if (this.responderID instanceof RelativeDistinguishedNames) {
                outputArray.push(new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: [this.responderID.toSchema()]
                }));
            }
            else {
                outputArray.push(new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 2
                    },
                    value: [this.responderID]
                }));
            }
            outputArray.push(new GeneralizedTime({ valueDate: this.producedAt }));
            outputArray.push(new Sequence({
                value: Array.from(this.responses, o => o.toSchema())
            }));
            if (this.responseExtensions) {
                outputArray.push(new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: [new Sequence({
                            value: Array.from(this.responseExtensions, o => o.toSchema())
                        })]
                }));
            }
            tbsSchema = new Sequence({
                value: outputArray
            });
        }
        return tbsSchema;
    }
    toJSON() {
        const res = {};
        if (VERSION$7 in this) {
            res.version = this.version;
        }
        if (this.responderID) {
            res.responderID = this.responderID;
        }
        if (this.producedAt) {
            res.producedAt = this.producedAt;
        }
        if (this.responses) {
            res.responses = Array.from(this.responses, o => o.toJSON());
        }
        if (this.responseExtensions) {
            res.responseExtensions = Array.from(this.responseExtensions, o => o.toJSON());
        }
        return res;
    }
}
ResponseData.CLASS_NAME = "ResponseData";

const TRUSTED_CERTS = "trustedCerts";
const CERTS$2 = "certs";
const CRLS$1 = "crls";
const OCSPS$1 = "ocsps";
const CHECK_DATE = "checkDate";
const FIND_ORIGIN = "findOrigin";
const FIND_ISSUER = "findIssuer";
var ChainValidationCode;
(function (ChainValidationCode) {
    ChainValidationCode[ChainValidationCode["unknown"] = -1] = "unknown";
    ChainValidationCode[ChainValidationCode["success"] = 0] = "success";
    ChainValidationCode[ChainValidationCode["noRevocation"] = 11] = "noRevocation";
    ChainValidationCode[ChainValidationCode["noPath"] = 60] = "noPath";
    ChainValidationCode[ChainValidationCode["noValidPath"] = 97] = "noValidPath";
})(ChainValidationCode || (ChainValidationCode = {}));
class ChainValidationError extends Error {
    constructor(code, message) {
        super(message);
        this.name = ChainValidationError.NAME;
        this.code = code;
        this.message = message;
    }
}
ChainValidationError.NAME = "ChainValidationError";
function isTrusted(cert, trustedList) {
    for (let i = 0; i < trustedList.length; i++) {
        if (BufferSourceConverter.isEqual(cert.tbsView, trustedList[i].tbsView)) {
            return true;
        }
    }
    return false;
}
class CertificateChainValidationEngine {
    constructor(parameters = {}) {
        this.trustedCerts = getParametersValue(parameters, TRUSTED_CERTS, this.defaultValues(TRUSTED_CERTS));
        this.certs = getParametersValue(parameters, CERTS$2, this.defaultValues(CERTS$2));
        this.crls = getParametersValue(parameters, CRLS$1, this.defaultValues(CRLS$1));
        this.ocsps = getParametersValue(parameters, OCSPS$1, this.defaultValues(OCSPS$1));
        this.checkDate = getParametersValue(parameters, CHECK_DATE, this.defaultValues(CHECK_DATE));
        this.findOrigin = getParametersValue(parameters, FIND_ORIGIN, this.defaultValues(FIND_ORIGIN));
        this.findIssuer = getParametersValue(parameters, FIND_ISSUER, this.defaultValues(FIND_ISSUER));
    }
    static defaultFindOrigin(certificate, validationEngine) {
        if (certificate.tbsView.byteLength === 0) {
            certificate.tbsView = new Uint8Array(certificate.encodeTBS().toBER());
        }
        for (const localCert of validationEngine.certs) {
            if (localCert.tbsView.byteLength === 0) {
                localCert.tbsView = new Uint8Array(localCert.encodeTBS().toBER());
            }
            if (BufferSourceConverter.isEqual(certificate.tbsView, localCert.tbsView))
                return "Intermediate Certificates";
        }
        for (const trustedCert of validationEngine.trustedCerts) {
            if (trustedCert.tbsView.byteLength === 0)
                trustedCert.tbsView = new Uint8Array(trustedCert.encodeTBS().toBER());
            if (BufferSourceConverter.isEqual(certificate.tbsView, trustedCert.tbsView))
                return "Trusted Certificates";
        }
        return "Unknown";
    }
    defaultFindIssuer(certificate, validationEngine, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            const result = [];
            let keyIdentifier = null;
            let authorityCertIssuer = null;
            let authorityCertSerialNumber = null;
            if (certificate.subject.isEqual(certificate.issuer)) {
                try {
                    const verificationResult = yield certificate.verify(undefined, crypto);
                    if (verificationResult) {
                        return [certificate];
                    }
                }
                catch (ex) {
                }
            }
            if (certificate.extensions) {
                for (const extension of certificate.extensions) {
                    if (extension.extnID === id_AuthorityKeyIdentifier && extension.parsedValue instanceof AuthorityKeyIdentifier) {
                        if (extension.parsedValue.keyIdentifier) {
                            keyIdentifier = extension.parsedValue.keyIdentifier;
                        }
                        else {
                            if (extension.parsedValue.authorityCertIssuer) {
                                authorityCertIssuer = extension.parsedValue.authorityCertIssuer;
                            }
                            if (extension.parsedValue.authorityCertSerialNumber) {
                                authorityCertSerialNumber = extension.parsedValue.authorityCertSerialNumber;
                            }
                        }
                        break;
                    }
                }
            }
            function checkCertificate(possibleIssuer) {
                if (keyIdentifier !== null) {
                    if (possibleIssuer.extensions) {
                        let extensionFound = false;
                        for (const extension of possibleIssuer.extensions) {
                            if (extension.extnID === id_SubjectKeyIdentifier && extension.parsedValue) {
                                extensionFound = true;
                                if (BufferSourceConverter.isEqual(extension.parsedValue.valueBlock.valueHex, keyIdentifier.valueBlock.valueHexView)) {
                                    result.push(possibleIssuer);
                                }
                                break;
                            }
                        }
                        if (extensionFound) {
                            return;
                        }
                    }
                }
                let authorityCertSerialNumberEqual = false;
                if (authorityCertSerialNumber !== null)
                    authorityCertSerialNumberEqual = possibleIssuer.serialNumber.isEqual(authorityCertSerialNumber);
                if (authorityCertIssuer !== null) {
                    if (possibleIssuer.subject.isEqual(authorityCertIssuer)) {
                        if (authorityCertSerialNumberEqual)
                            result.push(possibleIssuer);
                    }
                }
                else {
                    if (certificate.issuer.isEqual(possibleIssuer.subject))
                        result.push(possibleIssuer);
                }
            }
            for (const trustedCert of validationEngine.trustedCerts) {
                checkCertificate(trustedCert);
            }
            for (const intermediateCert of validationEngine.certs) {
                checkCertificate(intermediateCert);
            }
            for (let i = 0; i < result.length; i++) {
                try {
                    const verificationResult = yield certificate.verify(result[i], crypto);
                    if (verificationResult === false)
                        result.splice(i, 1);
                }
                catch (ex) {
                    result.splice(i, 1);
                }
            }
            return result;
        });
    }
    defaultValues(memberName) {
        switch (memberName) {
            case TRUSTED_CERTS:
                return [];
            case CERTS$2:
                return [];
            case CRLS$1:
                return [];
            case OCSPS$1:
                return [];
            case CHECK_DATE:
                return new Date();
            case FIND_ORIGIN:
                return CertificateChainValidationEngine.defaultFindOrigin;
            case FIND_ISSUER:
                return this.defaultFindIssuer;
            default:
                throw new Error(`Invalid member name for CertificateChainValidationEngine class: ${memberName}`);
        }
    }
    sort(passedWhenNotRevValues = false, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            const localCerts = [];
            const buildPath = (certificate, crypto) => __awaiter(this, void 0, void 0, function* () {
                const result = [];
                function checkUnique(array) {
                    let unique = true;
                    for (let i = 0; i < array.length; i++) {
                        for (let j = 0; j < array.length; j++) {
                            if (j === i)
                                continue;
                            if (array[i] === array[j]) {
                                unique = false;
                                break;
                            }
                        }
                        if (!unique)
                            break;
                    }
                    return unique;
                }
                if (isTrusted(certificate, this.trustedCerts)) {
                    return [[certificate]];
                }
                const findIssuerResult = yield this.findIssuer(certificate, this, crypto);
                if (findIssuerResult.length === 0) {
                    throw new Error("No valid certificate paths found");
                }
                for (let i = 0; i < findIssuerResult.length; i++) {
                    if (BufferSourceConverter.isEqual(findIssuerResult[i].tbsView, certificate.tbsView)) {
                        result.push([findIssuerResult[i]]);
                        continue;
                    }
                    const buildPathResult = yield buildPath(findIssuerResult[i], crypto);
                    for (let j = 0; j < buildPathResult.length; j++) {
                        const copy = buildPathResult[j].slice();
                        copy.splice(0, 0, findIssuerResult[i]);
                        if (checkUnique(copy))
                            result.push(copy);
                        else
                            result.push(buildPathResult[j]);
                    }
                }
                return result;
            });
            const findCRL = (certificate) => __awaiter(this, void 0, void 0, function* () {
                const issuerCertificates = [];
                const crls = [];
                const crlsAndCertificates = [];
                issuerCertificates.push(...localCerts.filter(element => certificate.issuer.isEqual(element.subject)));
                if (issuerCertificates.length === 0) {
                    return {
                        status: 1,
                        statusMessage: "No certificate's issuers"
                    };
                }
                crls.push(...this.crls.filter(o => o.issuer.isEqual(certificate.issuer)));
                if (crls.length === 0) {
                    return {
                        status: 2,
                        statusMessage: "No CRLs for specific certificate issuer"
                    };
                }
                for (let i = 0; i < crls.length; i++) {
                    const crl = crls[i];
                    if (crl.nextUpdate && crl.nextUpdate.value < this.checkDate) {
                        continue;
                    }
                    for (let j = 0; j < issuerCertificates.length; j++) {
                        try {
                            const result = yield crls[i].verify({ issuerCertificate: issuerCertificates[j] }, crypto);
                            if (result) {
                                crlsAndCertificates.push({
                                    crl: crls[i],
                                    certificate: issuerCertificates[j]
                                });
                                break;
                            }
                        }
                        catch (ex) {
                        }
                    }
                }
                if (crlsAndCertificates.length) {
                    return {
                        status: 0,
                        statusMessage: EMPTY_STRING,
                        result: crlsAndCertificates
                    };
                }
                return {
                    status: 3,
                    statusMessage: "No valid CRLs found"
                };
            });
            const findOCSP = (certificate, issuerCertificate) => __awaiter(this, void 0, void 0, function* () {
                const hashAlgorithm = crypto.getAlgorithmByOID(certificate.signatureAlgorithm.algorithmId);
                if (!hashAlgorithm.name) {
                    return 1;
                }
                if (!hashAlgorithm.hash) {
                    return 1;
                }
                for (let i = 0; i < this.ocsps.length; i++) {
                    const ocsp = this.ocsps[i];
                    const result = yield ocsp.getCertificateStatus(certificate, issuerCertificate, crypto);
                    if (result.isForCertificate) {
                        if (result.status === 0)
                            return 0;
                        return 1;
                    }
                }
                return 2;
            });
            function checkForCA(certificate, needToCheckCRL = false) {
                return __awaiter(this, void 0, void 0, function* () {
                    let isCA = false;
                    let mustBeCA = false;
                    let keyUsagePresent = false;
                    let cRLSign = false;
                    if (certificate.extensions) {
                        for (let j = 0; j < certificate.extensions.length; j++) {
                            const extension = certificate.extensions[j];
                            if (extension.critical && !extension.parsedValue) {
                                return {
                                    result: false,
                                    resultCode: 6,
                                    resultMessage: `Unable to parse critical certificate extension: ${extension.extnID}`
                                };
                            }
                            if (extension.extnID === id_KeyUsage) {
                                keyUsagePresent = true;
                                const view = new Uint8Array(extension.parsedValue.valueBlock.valueHex);
                                if ((view[0] & 0x04) === 0x04)
                                    mustBeCA = true;
                                if ((view[0] & 0x02) === 0x02)
                                    cRLSign = true;
                            }
                            if (extension.extnID === id_BasicConstraints) {
                                if ("cA" in extension.parsedValue) {
                                    if (extension.parsedValue.cA === true)
                                        isCA = true;
                                }
                            }
                        }
                        if ((mustBeCA === true) && (isCA === false)) {
                            return {
                                result: false,
                                resultCode: 3,
                                resultMessage: "Unable to build certificate chain - using \"keyCertSign\" flag set without BasicConstraints"
                            };
                        }
                        if ((keyUsagePresent === true) && (isCA === true) && (mustBeCA === false)) {
                            return {
                                result: false,
                                resultCode: 4,
                                resultMessage: "Unable to build certificate chain - \"keyCertSign\" flag was not set"
                            };
                        }
                        if ((isCA === true) && (keyUsagePresent === true) && ((needToCheckCRL) && (cRLSign === false))) {
                            return {
                                result: false,
                                resultCode: 5,
                                resultMessage: "Unable to build certificate chain - intermediate certificate must have \"cRLSign\" key usage flag"
                            };
                        }
                    }
                    if (isCA === false) {
                        return {
                            result: false,
                            resultCode: 7,
                            resultMessage: "Unable to build certificate chain - more than one possible end-user certificate"
                        };
                    }
                    return {
                        result: true,
                        resultCode: 0,
                        resultMessage: EMPTY_STRING
                    };
                });
            }
            const basicCheck = (path, checkDate) => __awaiter(this, void 0, void 0, function* () {
                for (let i = 0; i < path.length; i++) {
                    if ((path[i].notBefore.value > checkDate) ||
                        (path[i].notAfter.value < checkDate)) {
                        return {
                            result: false,
                            resultCode: 8,
                            resultMessage: "The certificate is either not yet valid or expired"
                        };
                    }
                }
                if (path.length < 2) {
                    return {
                        result: false,
                        resultCode: 9,
                        resultMessage: "Too short certificate path"
                    };
                }
                for (let i = (path.length - 2); i >= 0; i--) {
                    if (path[i].issuer.isEqual(path[i].subject) === false) {
                        if (path[i].issuer.isEqual(path[i + 1].subject) === false) {
                            return {
                                result: false,
                                resultCode: 10,
                                resultMessage: "Incorrect name chaining"
                            };
                        }
                    }
                }
                if ((this.crls.length !== 0) || (this.ocsps.length !== 0)) {
                    for (let i = 0; i < (path.length - 1); i++) {
                        let ocspResult = 2;
                        let crlResult = {
                            status: 0,
                            statusMessage: EMPTY_STRING
                        };
                        if (this.ocsps.length !== 0) {
                            ocspResult = yield findOCSP(path[i], path[i + 1]);
                            switch (ocspResult) {
                                case 0:
                                    continue;
                                case 1:
                                    return {
                                        result: false,
                                        resultCode: 12,
                                        resultMessage: "One of certificates was revoked via OCSP response"
                                    };
                            }
                        }
                        if (this.crls.length !== 0) {
                            crlResult = yield findCRL(path[i]);
                            if (crlResult.status === 0 && crlResult.result) {
                                for (let j = 0; j < crlResult.result.length; j++) {
                                    const isCertificateRevoked = crlResult.result[j].crl.isCertificateRevoked(path[i]);
                                    if (isCertificateRevoked) {
                                        return {
                                            result: false,
                                            resultCode: 12,
                                            resultMessage: "One of certificates had been revoked"
                                        };
                                    }
                                    const isCertificateCA = yield checkForCA(crlResult.result[j].certificate, true);
                                    if (isCertificateCA.result === false) {
                                        return {
                                            result: false,
                                            resultCode: 13,
                                            resultMessage: "CRL issuer certificate is not a CA certificate or does not have crlSign flag"
                                        };
                                    }
                                }
                            }
                            else {
                                if (passedWhenNotRevValues === false) {
                                    throw new ChainValidationError(ChainValidationCode.noRevocation, `No revocation values found for one of certificates: ${crlResult.statusMessage}`);
                                }
                            }
                        }
                        else {
                            if (ocspResult === 2) {
                                return {
                                    result: false,
                                    resultCode: 11,
                                    resultMessage: "No revocation values found for one of certificates"
                                };
                            }
                        }
                        if ((ocspResult === 2) && (crlResult.status === 2) && passedWhenNotRevValues) {
                            const issuerCertificate = path[i + 1];
                            let extensionFound = false;
                            if (issuerCertificate.extensions) {
                                for (const extension of issuerCertificate.extensions) {
                                    switch (extension.extnID) {
                                        case id_CRLDistributionPoints:
                                        case id_FreshestCRL:
                                        case id_AuthorityInfoAccess:
                                            extensionFound = true;
                                            break;
                                    }
                                }
                            }
                            if (extensionFound) {
                                throw new ChainValidationError(ChainValidationCode.noRevocation, `No revocation values found for one of certificates: ${crlResult.statusMessage}`);
                            }
                        }
                    }
                }
                for (const [i, cert] of path.entries()) {
                    if (!i) {
                        continue;
                    }
                    const result = yield checkForCA(cert);
                    if (!result.result) {
                        return {
                            result: false,
                            resultCode: 14,
                            resultMessage: "One of intermediate certificates is not a CA certificate"
                        };
                    }
                }
                return {
                    result: true
                };
            });
            localCerts.push(...this.trustedCerts);
            localCerts.push(...this.certs);
            for (let i = 0; i < localCerts.length; i++) {
                for (let j = 0; j < localCerts.length; j++) {
                    if (i === j)
                        continue;
                    if (BufferSourceConverter.isEqual(localCerts[i].tbsView, localCerts[j].tbsView)) {
                        localCerts.splice(j, 1);
                        i = 0;
                        break;
                    }
                }
            }
            const leafCert = localCerts[localCerts.length - 1];
            let result;
            const certificatePath = [leafCert];
            result = yield buildPath(leafCert, crypto);
            if (result.length === 0) {
                throw new ChainValidationError(ChainValidationCode.noPath, "Unable to find certificate path");
            }
            for (let i = 0; i < result.length; i++) {
                let found = false;
                for (let j = 0; j < (result[i]).length; j++) {
                    const certificate = (result[i])[j];
                    for (let k = 0; k < this.trustedCerts.length; k++) {
                        if (BufferSourceConverter.isEqual(certificate.tbsView, this.trustedCerts[k].tbsView)) {
                            found = true;
                            break;
                        }
                    }
                    if (found)
                        break;
                }
                if (!found) {
                    result.splice(i, 1);
                    i = 0;
                }
            }
            if (result.length === 0) {
                throw new ChainValidationError(ChainValidationCode.noValidPath, "No valid certificate paths found");
            }
            let shortestLength = result[0].length;
            let shortestIndex = 0;
            for (let i = 0; i < result.length; i++) {
                if (result[i].length < shortestLength) {
                    shortestLength = result[i].length;
                    shortestIndex = i;
                }
            }
            for (let i = 0; i < result[shortestIndex].length; i++)
                certificatePath.push((result[shortestIndex])[i]);
            result = yield basicCheck(certificatePath, this.checkDate);
            if (result.result === false)
                throw result;
            return certificatePath;
        });
    }
    verify(parameters = {}, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            function compareDNSName(name, constraint) {
                const namePrepared = stringPrep(name);
                const constraintPrepared = stringPrep(constraint);
                const nameSplitted = namePrepared.split(".");
                const constraintSplitted = constraintPrepared.split(".");
                const nameLen = nameSplitted.length;
                const constrLen = constraintSplitted.length;
                if ((nameLen === 0) || (constrLen === 0) || (nameLen < constrLen)) {
                    return false;
                }
                for (let i = 0; i < nameLen; i++) {
                    if (nameSplitted[i].length === 0) {
                        return false;
                    }
                }
                for (let i = 0; i < constrLen; i++) {
                    if (constraintSplitted[i].length === 0) {
                        if (i === 0) {
                            if (constrLen === 1) {
                                return false;
                            }
                            continue;
                        }
                        return false;
                    }
                }
                for (let i = 0; i < constrLen; i++) {
                    if (constraintSplitted[constrLen - 1 - i].length === 0) {
                        continue;
                    }
                    if (nameSplitted[nameLen - 1 - i].localeCompare(constraintSplitted[constrLen - 1 - i]) !== 0) {
                        return false;
                    }
                }
                return true;
            }
            function compareRFC822Name(name, constraint) {
                const namePrepared = stringPrep(name);
                const constraintPrepared = stringPrep(constraint);
                const nameSplitted = namePrepared.split("@");
                const constraintSplitted = constraintPrepared.split("@");
                if ((nameSplitted.length === 0) || (constraintSplitted.length === 0) || (nameSplitted.length < constraintSplitted.length))
                    return false;
                if (constraintSplitted.length === 1) {
                    const result = compareDNSName(nameSplitted[1], constraintSplitted[0]);
                    if (result) {
                        const ns = nameSplitted[1].split(".");
                        const cs = constraintSplitted[0].split(".");
                        if (cs[0].length === 0)
                            return true;
                        return ns.length === cs.length;
                    }
                    return false;
                }
                return (namePrepared.localeCompare(constraintPrepared) === 0);
            }
            function compareUniformResourceIdentifier(name, constraint) {
                let namePrepared = stringPrep(name);
                const constraintPrepared = stringPrep(constraint);
                const ns = namePrepared.split("/");
                const cs = constraintPrepared.split("/");
                if (cs.length > 1)
                    return false;
                if (ns.length > 1) {
                    for (let i = 0; i < ns.length; i++) {
                        if ((ns[i].length > 0) && (ns[i].charAt(ns[i].length - 1) !== ":")) {
                            const nsPort = ns[i].split(":");
                            namePrepared = nsPort[0];
                            break;
                        }
                    }
                }
                const result = compareDNSName(namePrepared, constraintPrepared);
                if (result) {
                    const nameSplitted = namePrepared.split(".");
                    const constraintSplitted = constraintPrepared.split(".");
                    if (constraintSplitted[0].length === 0)
                        return true;
                    return nameSplitted.length === constraintSplitted.length;
                }
                return false;
            }
            function compareIPAddress(name, constraint) {
                const nameView = name.valueBlock.valueHexView;
                const constraintView = constraint.valueBlock.valueHexView;
                if ((nameView.length === 4) && (constraintView.length === 8)) {
                    for (let i = 0; i < 4; i++) {
                        if ((nameView[i] ^ constraintView[i]) & constraintView[i + 4])
                            return false;
                    }
                    return true;
                }
                if ((nameView.length === 16) && (constraintView.length === 32)) {
                    for (let i = 0; i < 16; i++) {
                        if ((nameView[i] ^ constraintView[i]) & constraintView[i + 16])
                            return false;
                    }
                    return true;
                }
                return false;
            }
            function compareDirectoryName(name, constraint) {
                if ((name.typesAndValues.length === 0) || (constraint.typesAndValues.length === 0))
                    return true;
                if (name.typesAndValues.length < constraint.typesAndValues.length)
                    return false;
                let result = true;
                let nameStart = 0;
                for (let i = 0; i < constraint.typesAndValues.length; i++) {
                    let localResult = false;
                    for (let j = nameStart; j < name.typesAndValues.length; j++) {
                        localResult = name.typesAndValues[j].isEqual(constraint.typesAndValues[i]);
                        if (name.typesAndValues[j].type === constraint.typesAndValues[i].type)
                            result = result && localResult;
                        if (localResult === true) {
                            if ((nameStart === 0) || (nameStart === j)) {
                                nameStart = j + 1;
                                break;
                            }
                            else
                                return false;
                        }
                    }
                    if (localResult === false)
                        return false;
                }
                return (nameStart === 0) ? false : result;
            }
            try {
                if (this.certs.length === 0)
                    throw new Error("Empty certificate array");
                const passedWhenNotRevValues = parameters.passedWhenNotRevValues || false;
                const initialPolicySet = parameters.initialPolicySet || [id_AnyPolicy];
                const initialExplicitPolicy = parameters.initialExplicitPolicy || false;
                const initialPolicyMappingInhibit = parameters.initialPolicyMappingInhibit || false;
                const initialInhibitPolicy = parameters.initialInhibitPolicy || false;
                const initialPermittedSubtreesSet = parameters.initialPermittedSubtreesSet || [];
                const initialExcludedSubtreesSet = parameters.initialExcludedSubtreesSet || [];
                const initialRequiredNameForms = parameters.initialRequiredNameForms || [];
                let explicitPolicyIndicator = initialExplicitPolicy;
                let policyMappingInhibitIndicator = initialPolicyMappingInhibit;
                let inhibitAnyPolicyIndicator = initialInhibitPolicy;
                const pendingConstraints = [
                    false,
                    false,
                    false,
                ];
                let explicitPolicyPending = 0;
                let policyMappingInhibitPending = 0;
                let inhibitAnyPolicyPending = 0;
                let permittedSubtrees = initialPermittedSubtreesSet;
                let excludedSubtrees = initialExcludedSubtreesSet;
                const requiredNameForms = initialRequiredNameForms;
                let pathDepth = 1;
                this.certs = yield this.sort(passedWhenNotRevValues, crypto);
                const allPolicies = [];
                allPolicies.push(id_AnyPolicy);
                const policiesAndCerts = [];
                const anyPolicyArray = new Array(this.certs.length - 1);
                for (let ii = 0; ii < (this.certs.length - 1); ii++)
                    anyPolicyArray[ii] = true;
                policiesAndCerts.push(anyPolicyArray);
                const policyMappings = new Array(this.certs.length - 1);
                const certPolicies = new Array(this.certs.length - 1);
                let explicitPolicyStart = (explicitPolicyIndicator) ? (this.certs.length - 1) : (-1);
                for (let i = (this.certs.length - 2); i >= 0; i--, pathDepth++) {
                    const cert = this.certs[i];
                    if (cert.extensions) {
                        for (let j = 0; j < cert.extensions.length; j++) {
                            const extension = cert.extensions[j];
                            if (extension.extnID === id_CertificatePolicies) {
                                certPolicies[i] = extension.parsedValue;
                                for (let s = 0; s < allPolicies.length; s++) {
                                    if (allPolicies[s] === id_AnyPolicy) {
                                        delete (policiesAndCerts[s])[i];
                                        break;
                                    }
                                }
                                for (let k = 0; k < extension.parsedValue.certificatePolicies.length; k++) {
                                    let policyIndex = (-1);
                                    const policyId = extension.parsedValue.certificatePolicies[k].policyIdentifier;
                                    for (let s = 0; s < allPolicies.length; s++) {
                                        if (policyId === allPolicies[s]) {
                                            policyIndex = s;
                                            break;
                                        }
                                    }
                                    if (policyIndex === (-1)) {
                                        allPolicies.push(policyId);
                                        const certArray = new Array(this.certs.length - 1);
                                        certArray[i] = true;
                                        policiesAndCerts.push(certArray);
                                    }
                                    else
                                        (policiesAndCerts[policyIndex])[i] = true;
                                }
                            }
                            if (extension.extnID === id_PolicyMappings) {
                                if (policyMappingInhibitIndicator) {
                                    return {
                                        result: false,
                                        resultCode: 98,
                                        resultMessage: "Policy mapping prohibited"
                                    };
                                }
                                policyMappings[i] = extension.parsedValue;
                            }
                            if (extension.extnID === id_PolicyConstraints) {
                                if (explicitPolicyIndicator === false) {
                                    if (extension.parsedValue.requireExplicitPolicy === 0) {
                                        explicitPolicyIndicator = true;
                                        explicitPolicyStart = i;
                                    }
                                    else {
                                        if (pendingConstraints[0] === false) {
                                            pendingConstraints[0] = true;
                                            explicitPolicyPending = extension.parsedValue.requireExplicitPolicy;
                                        }
                                        else
                                            explicitPolicyPending = (explicitPolicyPending > extension.parsedValue.requireExplicitPolicy) ? extension.parsedValue.requireExplicitPolicy : explicitPolicyPending;
                                    }
                                    if (extension.parsedValue.inhibitPolicyMapping === 0)
                                        policyMappingInhibitIndicator = true;
                                    else {
                                        if (pendingConstraints[1] === false) {
                                            pendingConstraints[1] = true;
                                            policyMappingInhibitPending = extension.parsedValue.inhibitPolicyMapping + 1;
                                        }
                                        else
                                            policyMappingInhibitPending = (policyMappingInhibitPending > (extension.parsedValue.inhibitPolicyMapping + 1)) ? (extension.parsedValue.inhibitPolicyMapping + 1) : policyMappingInhibitPending;
                                    }
                                }
                            }
                            if (extension.extnID === id_InhibitAnyPolicy) {
                                if (inhibitAnyPolicyIndicator === false) {
                                    if (extension.parsedValue.valueBlock.valueDec === 0)
                                        inhibitAnyPolicyIndicator = true;
                                    else {
                                        if (pendingConstraints[2] === false) {
                                            pendingConstraints[2] = true;
                                            inhibitAnyPolicyPending = extension.parsedValue.valueBlock.valueDec;
                                        }
                                        else
                                            inhibitAnyPolicyPending = (inhibitAnyPolicyPending > extension.parsedValue.valueBlock.valueDec) ? extension.parsedValue.valueBlock.valueDec : inhibitAnyPolicyPending;
                                    }
                                }
                            }
                        }
                        if (inhibitAnyPolicyIndicator === true) {
                            let policyIndex = (-1);
                            for (let searchAnyPolicy = 0; searchAnyPolicy < allPolicies.length; searchAnyPolicy++) {
                                if (allPolicies[searchAnyPolicy] === id_AnyPolicy) {
                                    policyIndex = searchAnyPolicy;
                                    break;
                                }
                            }
                            if (policyIndex !== (-1))
                                delete (policiesAndCerts[0])[i];
                        }
                        if (explicitPolicyIndicator === false) {
                            if (pendingConstraints[0] === true) {
                                explicitPolicyPending--;
                                if (explicitPolicyPending === 0) {
                                    explicitPolicyIndicator = true;
                                    explicitPolicyStart = i;
                                    pendingConstraints[0] = false;
                                }
                            }
                        }
                        if (policyMappingInhibitIndicator === false) {
                            if (pendingConstraints[1] === true) {
                                policyMappingInhibitPending--;
                                if (policyMappingInhibitPending === 0) {
                                    policyMappingInhibitIndicator = true;
                                    pendingConstraints[1] = false;
                                }
                            }
                        }
                        if (inhibitAnyPolicyIndicator === false) {
                            if (pendingConstraints[2] === true) {
                                inhibitAnyPolicyPending--;
                                if (inhibitAnyPolicyPending === 0) {
                                    inhibitAnyPolicyIndicator = true;
                                    pendingConstraints[2] = false;
                                }
                            }
                        }
                    }
                }
                for (let i = 0; i < (this.certs.length - 1); i++) {
                    if ((i < (this.certs.length - 2)) && (typeof policyMappings[i + 1] !== "undefined")) {
                        for (let k = 0; k < policyMappings[i + 1].mappings.length; k++) {
                            if ((policyMappings[i + 1].mappings[k].issuerDomainPolicy === id_AnyPolicy) || (policyMappings[i + 1].mappings[k].subjectDomainPolicy === id_AnyPolicy)) {
                                return {
                                    result: false,
                                    resultCode: 99,
                                    resultMessage: "The \"anyPolicy\" should not be a part of policy mapping scheme"
                                };
                            }
                            let issuerDomainPolicyIndex = (-1);
                            let subjectDomainPolicyIndex = (-1);
                            for (let n = 0; n < allPolicies.length; n++) {
                                if (allPolicies[n] === policyMappings[i + 1].mappings[k].issuerDomainPolicy)
                                    issuerDomainPolicyIndex = n;
                                if (allPolicies[n] === policyMappings[i + 1].mappings[k].subjectDomainPolicy)
                                    subjectDomainPolicyIndex = n;
                            }
                            if (typeof (policiesAndCerts[issuerDomainPolicyIndex])[i] !== "undefined")
                                delete (policiesAndCerts[issuerDomainPolicyIndex])[i];
                            for (let j = 0; j < certPolicies[i].certificatePolicies.length; j++) {
                                if (policyMappings[i + 1].mappings[k].subjectDomainPolicy === certPolicies[i].certificatePolicies[j].policyIdentifier) {
                                    if ((issuerDomainPolicyIndex !== (-1)) && (subjectDomainPolicyIndex !== (-1))) {
                                        for (let m = 0; m <= i; m++) {
                                            if (typeof (policiesAndCerts[subjectDomainPolicyIndex])[m] !== "undefined") {
                                                (policiesAndCerts[issuerDomainPolicyIndex])[m] = true;
                                                delete (policiesAndCerts[subjectDomainPolicyIndex])[m];
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                for (let i = 0; i < allPolicies.length; i++) {
                    if (allPolicies[i] === id_AnyPolicy) {
                        for (let j = 0; j < explicitPolicyStart; j++)
                            delete (policiesAndCerts[i])[j];
                    }
                }
                const authConstrPolicies = [];
                for (let i = 0; i < policiesAndCerts.length; i++) {
                    let found = true;
                    for (let j = 0; j < (this.certs.length - 1); j++) {
                        let anyPolicyFound = false;
                        if ((j < explicitPolicyStart) && (allPolicies[i] === id_AnyPolicy) && (allPolicies.length > 1)) {
                            found = false;
                            break;
                        }
                        if (typeof (policiesAndCerts[i])[j] === "undefined") {
                            if (j >= explicitPolicyStart) {
                                for (let k = 0; k < allPolicies.length; k++) {
                                    if (allPolicies[k] === id_AnyPolicy) {
                                        if ((policiesAndCerts[k])[j] === true)
                                            anyPolicyFound = true;
                                        break;
                                    }
                                }
                            }
                            if (!anyPolicyFound) {
                                found = false;
                                break;
                            }
                        }
                    }
                    if (found === true)
                        authConstrPolicies.push(allPolicies[i]);
                }
                let userConstrPolicies = [];
                if ((initialPolicySet.length === 1) && (initialPolicySet[0] === id_AnyPolicy) && (explicitPolicyIndicator === false))
                    userConstrPolicies = initialPolicySet;
                else {
                    if ((authConstrPolicies.length === 1) && (authConstrPolicies[0] === id_AnyPolicy))
                        userConstrPolicies = initialPolicySet;
                    else {
                        for (let i = 0; i < authConstrPolicies.length; i++) {
                            for (let j = 0; j < initialPolicySet.length; j++) {
                                if ((initialPolicySet[j] === authConstrPolicies[i]) || (initialPolicySet[j] === id_AnyPolicy)) {
                                    userConstrPolicies.push(authConstrPolicies[i]);
                                    break;
                                }
                            }
                        }
                    }
                }
                const policyResult = {
                    result: (userConstrPolicies.length > 0),
                    resultCode: 0,
                    resultMessage: (userConstrPolicies.length > 0) ? EMPTY_STRING : "Zero \"userConstrPolicies\" array, no intersections with \"authConstrPolicies\"",
                    authConstrPolicies,
                    userConstrPolicies,
                    explicitPolicyIndicator,
                    policyMappings,
                    certificatePath: this.certs
                };
                if (userConstrPolicies.length === 0)
                    return policyResult;
                if (policyResult.result === false)
                    return policyResult;
                pathDepth = 1;
                for (let i = (this.certs.length - 2); i >= 0; i--, pathDepth++) {
                    const cert = this.certs[i];
                    let subjectAltNames = [];
                    let certPermittedSubtrees = [];
                    let certExcludedSubtrees = [];
                    if (cert.extensions) {
                        for (let j = 0; j < cert.extensions.length; j++) {
                            const extension = cert.extensions[j];
                            if (extension.extnID === id_NameConstraints) {
                                if ("permittedSubtrees" in extension.parsedValue)
                                    certPermittedSubtrees = certPermittedSubtrees.concat(extension.parsedValue.permittedSubtrees);
                                if ("excludedSubtrees" in extension.parsedValue)
                                    certExcludedSubtrees = certExcludedSubtrees.concat(extension.parsedValue.excludedSubtrees);
                            }
                            if (extension.extnID === id_SubjectAltName)
                                subjectAltNames = subjectAltNames.concat(extension.parsedValue.altNames);
                        }
                    }
                    let formFound = (requiredNameForms.length <= 0);
                    for (let j = 0; j < requiredNameForms.length; j++) {
                        switch (requiredNameForms[j].base.type) {
                            case 4:
                                {
                                    if (requiredNameForms[j].base.value.typesAndValues.length !== cert.subject.typesAndValues.length)
                                        continue;
                                    formFound = true;
                                    for (let k = 0; k < cert.subject.typesAndValues.length; k++) {
                                        if (cert.subject.typesAndValues[k].type !== requiredNameForms[j].base.value.typesAndValues[k].type) {
                                            formFound = false;
                                            break;
                                        }
                                    }
                                    if (formFound === true)
                                        break;
                                }
                                break;
                            default:
                        }
                    }
                    if (formFound === false) {
                        policyResult.result = false;
                        policyResult.resultCode = 21;
                        policyResult.resultMessage = "No necessary name form found";
                        throw policyResult;
                    }
                    const constrGroups = [
                        [],
                        [],
                        [],
                        [],
                        [],
                    ];
                    for (let j = 0; j < permittedSubtrees.length; j++) {
                        switch (permittedSubtrees[j].base.type) {
                            case 1:
                                constrGroups[0].push(permittedSubtrees[j]);
                                break;
                            case 2:
                                constrGroups[1].push(permittedSubtrees[j]);
                                break;
                            case 4:
                                constrGroups[2].push(permittedSubtrees[j]);
                                break;
                            case 6:
                                constrGroups[3].push(permittedSubtrees[j]);
                                break;
                            case 7:
                                constrGroups[4].push(permittedSubtrees[j]);
                                break;
                            default:
                        }
                    }
                    for (let p = 0; p < 5; p++) {
                        let groupPermitted = false;
                        let valueExists = false;
                        const group = constrGroups[p];
                        for (let j = 0; j < group.length; j++) {
                            switch (p) {
                                case 0:
                                    if (subjectAltNames.length > 0) {
                                        for (let k = 0; k < subjectAltNames.length; k++) {
                                            if (subjectAltNames[k].type === 1) {
                                                valueExists = true;
                                                groupPermitted = groupPermitted || compareRFC822Name(subjectAltNames[k].value, group[j].base.value);
                                            }
                                        }
                                    }
                                    else {
                                        for (let k = 0; k < cert.subject.typesAndValues.length; k++) {
                                            if ((cert.subject.typesAndValues[k].type === "1.2.840.113549.1.9.1") ||
                                                (cert.subject.typesAndValues[k].type === "0.9.2342.19200300.100.1.3")) {
                                                valueExists = true;
                                                groupPermitted = groupPermitted || compareRFC822Name(cert.subject.typesAndValues[k].value.valueBlock.value, group[j].base.value);
                                            }
                                        }
                                    }
                                    break;
                                case 1:
                                    if (subjectAltNames.length > 0) {
                                        for (let k = 0; k < subjectAltNames.length; k++) {
                                            if (subjectAltNames[k].type === 2) {
                                                valueExists = true;
                                                groupPermitted = groupPermitted || compareDNSName(subjectAltNames[k].value, group[j].base.value);
                                            }
                                        }
                                    }
                                    break;
                                case 2:
                                    valueExists = true;
                                    groupPermitted = compareDirectoryName(cert.subject, group[j].base.value);
                                    break;
                                case 3:
                                    if (subjectAltNames.length > 0) {
                                        for (let k = 0; k < subjectAltNames.length; k++) {
                                            if (subjectAltNames[k].type === 6) {
                                                valueExists = true;
                                                groupPermitted = groupPermitted || compareUniformResourceIdentifier(subjectAltNames[k].value, group[j].base.value);
                                            }
                                        }
                                    }
                                    break;
                                case 4:
                                    if (subjectAltNames.length > 0) {
                                        for (let k = 0; k < subjectAltNames.length; k++) {
                                            if (subjectAltNames[k].type === 7) {
                                                valueExists = true;
                                                groupPermitted = groupPermitted || compareIPAddress(subjectAltNames[k].value, group[j].base.value);
                                            }
                                        }
                                    }
                                    break;
                                default:
                            }
                            if (groupPermitted)
                                break;
                        }
                        if ((groupPermitted === false) && (group.length > 0) && valueExists) {
                            policyResult.result = false;
                            policyResult.resultCode = 41;
                            policyResult.resultMessage = "Failed to meet \"permitted sub-trees\" name constraint";
                            throw policyResult;
                        }
                    }
                    let excluded = false;
                    for (let j = 0; j < excludedSubtrees.length; j++) {
                        switch (excludedSubtrees[j].base.type) {
                            case 1:
                                if (subjectAltNames.length >= 0) {
                                    for (let k = 0; k < subjectAltNames.length; k++) {
                                        if (subjectAltNames[k].type === 1)
                                            excluded = excluded || compareRFC822Name(subjectAltNames[k].value, excludedSubtrees[j].base.value);
                                    }
                                }
                                else {
                                    for (let k = 0; k < cert.subject.typesAndValues.length; k++) {
                                        if ((cert.subject.typesAndValues[k].type === "1.2.840.113549.1.9.1") ||
                                            (cert.subject.typesAndValues[k].type === "0.9.2342.19200300.100.1.3"))
                                            excluded = excluded || compareRFC822Name(cert.subject.typesAndValues[k].value.valueBlock.value, excludedSubtrees[j].base.value);
                                    }
                                }
                                break;
                            case 2:
                                if (subjectAltNames.length > 0) {
                                    for (let k = 0; k < subjectAltNames.length; k++) {
                                        if (subjectAltNames[k].type === 2)
                                            excluded = excluded || compareDNSName(subjectAltNames[k].value, excludedSubtrees[j].base.value);
                                    }
                                }
                                break;
                            case 4:
                                excluded = excluded || compareDirectoryName(cert.subject, excludedSubtrees[j].base.value);
                                break;
                            case 6:
                                if (subjectAltNames.length > 0) {
                                    for (let k = 0; k < subjectAltNames.length; k++) {
                                        if (subjectAltNames[k].type === 6)
                                            excluded = excluded || compareUniformResourceIdentifier(subjectAltNames[k].value, excludedSubtrees[j].base.value);
                                    }
                                }
                                break;
                            case 7:
                                if (subjectAltNames.length > 0) {
                                    for (let k = 0; k < subjectAltNames.length; k++) {
                                        if (subjectAltNames[k].type === 7)
                                            excluded = excluded || compareIPAddress(subjectAltNames[k].value, excludedSubtrees[j].base.value);
                                    }
                                }
                                break;
                            default:
                        }
                        if (excluded)
                            break;
                    }
                    if (excluded === true) {
                        policyResult.result = false;
                        policyResult.resultCode = 42;
                        policyResult.resultMessage = "Failed to meet \"excluded sub-trees\" name constraint";
                        throw policyResult;
                    }
                    permittedSubtrees = permittedSubtrees.concat(certPermittedSubtrees);
                    excludedSubtrees = excludedSubtrees.concat(certExcludedSubtrees);
                }
                return policyResult;
            }
            catch (error) {
                if (error instanceof Error) {
                    if (error instanceof ChainValidationError) {
                        return {
                            result: false,
                            resultCode: error.code,
                            resultMessage: error.message,
                            error: error,
                        };
                    }
                    return {
                        result: false,
                        resultCode: ChainValidationCode.unknown,
                        resultMessage: error.message,
                        error: error,
                    };
                }
                if (error && typeof error === "object" && "resultMessage" in error) {
                    return error;
                }
                return {
                    result: false,
                    resultCode: -1,
                    resultMessage: `${error}`,
                };
            }
        });
    }
}

const TBS_RESPONSE_DATA = "tbsResponseData";
const SIGNATURE_ALGORITHM$3 = "signatureAlgorithm";
const SIGNATURE$2 = "signature";
const CERTS$1 = "certs";
const BASIC_OCSP_RESPONSE = "BasicOCSPResponse";
const BASIC_OCSP_RESPONSE_TBS_RESPONSE_DATA = `${BASIC_OCSP_RESPONSE}.${TBS_RESPONSE_DATA}`;
const BASIC_OCSP_RESPONSE_SIGNATURE_ALGORITHM = `${BASIC_OCSP_RESPONSE}.${SIGNATURE_ALGORITHM$3}`;
const BASIC_OCSP_RESPONSE_SIGNATURE = `${BASIC_OCSP_RESPONSE}.${SIGNATURE$2}`;
const BASIC_OCSP_RESPONSE_CERTS = `${BASIC_OCSP_RESPONSE}.${CERTS$1}`;
const CLEAR_PROPS$g = [
    BASIC_OCSP_RESPONSE_TBS_RESPONSE_DATA,
    BASIC_OCSP_RESPONSE_SIGNATURE_ALGORITHM,
    BASIC_OCSP_RESPONSE_SIGNATURE,
    BASIC_OCSP_RESPONSE_CERTS
];
class BasicOCSPResponse extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.tbsResponseData = getParametersValue(parameters, TBS_RESPONSE_DATA, BasicOCSPResponse.defaultValues(TBS_RESPONSE_DATA));
        this.signatureAlgorithm = getParametersValue(parameters, SIGNATURE_ALGORITHM$3, BasicOCSPResponse.defaultValues(SIGNATURE_ALGORITHM$3));
        this.signature = getParametersValue(parameters, SIGNATURE$2, BasicOCSPResponse.defaultValues(SIGNATURE$2));
        if (CERTS$1 in parameters) {
            this.certs = getParametersValue(parameters, CERTS$1, BasicOCSPResponse.defaultValues(CERTS$1));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case TBS_RESPONSE_DATA:
                return new ResponseData();
            case SIGNATURE_ALGORITHM$3:
                return new AlgorithmIdentifier();
            case SIGNATURE$2:
                return new BitString();
            case CERTS$1:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case "type":
                {
                    let comparisonResult = ((ResponseData.compareWithDefault("tbs", memberValue.tbs)) &&
                        (ResponseData.compareWithDefault("responderID", memberValue.responderID)) &&
                        (ResponseData.compareWithDefault("producedAt", memberValue.producedAt)) &&
                        (ResponseData.compareWithDefault("responses", memberValue.responses)));
                    if ("responseExtensions" in memberValue)
                        comparisonResult = comparisonResult && (ResponseData.compareWithDefault("responseExtensions", memberValue.responseExtensions));
                    return comparisonResult;
                }
            case SIGNATURE_ALGORITHM$3:
                return ((memberValue.algorithmId === EMPTY_STRING) && (("algorithmParams" in memberValue) === false));
            case SIGNATURE$2:
                return (memberValue.isEqual(BasicOCSPResponse.defaultValues(memberName)));
            case CERTS$1:
                return (memberValue.length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || BASIC_OCSP_RESPONSE),
            value: [
                ResponseData.schema(names.tbsResponseData || {
                    names: {
                        blockName: BASIC_OCSP_RESPONSE_TBS_RESPONSE_DATA
                    }
                }),
                AlgorithmIdentifier.schema(names.signatureAlgorithm || {
                    names: {
                        blockName: BASIC_OCSP_RESPONSE_SIGNATURE_ALGORITHM
                    }
                }),
                new BitString({ name: (names.signature || BASIC_OCSP_RESPONSE_SIGNATURE) }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [
                        new Sequence({
                            value: [new Repeated({
                                    name: BASIC_OCSP_RESPONSE_CERTS,
                                    value: Certificate.schema(names.certs || {})
                                })]
                        })
                    ]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$g);
        const asn1 = compareSchema(schema, schema, BasicOCSPResponse.schema());
        AsnError.assertSchema(asn1, this.className);
        this.tbsResponseData = new ResponseData({ schema: asn1.result[BASIC_OCSP_RESPONSE_TBS_RESPONSE_DATA] });
        this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result[BASIC_OCSP_RESPONSE_SIGNATURE_ALGORITHM] });
        this.signature = asn1.result[BASIC_OCSP_RESPONSE_SIGNATURE];
        if (BASIC_OCSP_RESPONSE_CERTS in asn1.result) {
            this.certs = Array.from(asn1.result[BASIC_OCSP_RESPONSE_CERTS], element => new Certificate({ schema: element }));
        }
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(this.tbsResponseData.toSchema());
        outputArray.push(this.signatureAlgorithm.toSchema());
        outputArray.push(this.signature);
        if (this.certs) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [
                    new Sequence({
                        value: Array.from(this.certs, o => o.toSchema())
                    })
                ]
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            tbsResponseData: this.tbsResponseData.toJSON(),
            signatureAlgorithm: this.signatureAlgorithm.toJSON(),
            signature: this.signature.toJSON(),
        };
        if (this.certs) {
            res.certs = Array.from(this.certs, o => o.toJSON());
        }
        return res;
    }
    getCertificateStatus(certificate, issuerCertificate, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            const result = {
                isForCertificate: false,
                status: 2
            };
            const hashesObject = {};
            const certIDs = [];
            for (const response of this.tbsResponseData.responses) {
                const hashAlgorithm = crypto.getAlgorithmByOID(response.certID.hashAlgorithm.algorithmId, true, "CertID.hashAlgorithm");
                if (!hashesObject[hashAlgorithm.name]) {
                    hashesObject[hashAlgorithm.name] = 1;
                    const certID = new CertID();
                    certIDs.push(certID);
                    yield certID.createForCertificate(certificate, {
                        hashAlgorithm: hashAlgorithm.name,
                        issuerCertificate
                    }, crypto);
                }
            }
            for (const response of this.tbsResponseData.responses) {
                for (const id of certIDs) {
                    if (response.certID.isEqual(id)) {
                        result.isForCertificate = true;
                        try {
                            switch (response.certStatus.idBlock.isConstructed) {
                                case true:
                                    if (response.certStatus.idBlock.tagNumber === 1)
                                        result.status = 1;
                                    break;
                                case false:
                                    switch (response.certStatus.idBlock.tagNumber) {
                                        case 0:
                                            result.status = 0;
                                            break;
                                        case 2:
                                            result.status = 2;
                                            break;
                                        default:
                                    }
                                    break;
                                default:
                            }
                        }
                        catch (ex) {
                        }
                        return result;
                    }
                }
            }
            return result;
        });
    }
    sign(privateKey, hashAlgorithm = "SHA-1", crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!privateKey) {
                throw new Error("Need to provide a private key for signing");
            }
            const signatureParams = yield crypto.getSignatureParameters(privateKey, hashAlgorithm);
            const algorithm = signatureParams.parameters.algorithm;
            if (!("name" in algorithm)) {
                throw new Error("Empty algorithm");
            }
            this.signatureAlgorithm = signatureParams.signatureAlgorithm;
            this.tbsResponseData.tbsView = new Uint8Array(this.tbsResponseData.toSchema(true).toBER());
            const signature = yield crypto.signWithPrivateKey(this.tbsResponseData.tbsView, privateKey, { algorithm });
            this.signature = new BitString({ valueHex: signature });
        });
    }
    verify(params = {}, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            let signerCert = null;
            let certIndex = -1;
            const trustedCerts = params.trustedCerts || [];
            if (!this.certs) {
                throw new Error("No certificates attached to the BasicOCSPResponse");
            }
            switch (true) {
                case (this.tbsResponseData.responderID instanceof RelativeDistinguishedNames):
                    for (const [index, certificate] of this.certs.entries()) {
                        if (certificate.subject.isEqual(this.tbsResponseData.responderID)) {
                            certIndex = index;
                            break;
                        }
                    }
                    break;
                case (this.tbsResponseData.responderID instanceof OctetString):
                    for (const [index, cert] of this.certs.entries()) {
                        const hash = yield crypto.digest({ name: "sha-1" }, cert.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHexView);
                        if (isEqualBuffer(hash, this.tbsResponseData.responderID.valueBlock.valueHex)) {
                            certIndex = index;
                            break;
                        }
                    }
                    break;
                default:
                    throw new Error("Wrong value for responderID");
            }
            if (certIndex === (-1))
                throw new Error("Correct certificate was not found in OCSP response");
            signerCert = this.certs[certIndex];
            const additionalCerts = [signerCert];
            for (const cert of this.certs) {
                const caCert = yield checkCA(cert, signerCert);
                if (caCert) {
                    additionalCerts.push(caCert);
                }
            }
            const certChain = new CertificateChainValidationEngine({
                certs: additionalCerts,
                trustedCerts,
            });
            const verificationResult = yield certChain.verify({}, crypto);
            if (!verificationResult.result) {
                throw new Error("Validation of signer's certificate failed");
            }
            return crypto.verifyWithPublicKey(this.tbsResponseData.tbsView, this.signature, this.certs[certIndex].subjectPublicKeyInfo, this.signatureAlgorithm);
        });
    }
}
BasicOCSPResponse.CLASS_NAME = "BasicOCSPResponse";

const TBS$1 = "tbs";
const VERSION$6 = "version";
const SUBJECT = "subject";
const SPKI = "subjectPublicKeyInfo";
const ATTRIBUTES$1 = "attributes";
const SIGNATURE_ALGORITHM$2 = "signatureAlgorithm";
const SIGNATURE_VALUE = "signatureValue";
const CSR_INFO = "CertificationRequestInfo";
const CSR_INFO_VERSION = `${CSR_INFO}.version`;
const CSR_INFO_SUBJECT = `${CSR_INFO}.subject`;
const CSR_INFO_SPKI = `${CSR_INFO}.subjectPublicKeyInfo`;
const CSR_INFO_ATTRS = `${CSR_INFO}.attributes`;
const CLEAR_PROPS$f = [
    CSR_INFO,
    CSR_INFO_VERSION,
    CSR_INFO_SUBJECT,
    CSR_INFO_SPKI,
    CSR_INFO_ATTRS,
    SIGNATURE_ALGORITHM$2,
    SIGNATURE_VALUE
];
function CertificationRequestInfo(parameters = {}) {
    const names = getParametersValue(parameters, "names", {});
    return (new Sequence({
        name: (names.CertificationRequestInfo || CSR_INFO),
        value: [
            new Integer({ name: (names.CertificationRequestInfoVersion || CSR_INFO_VERSION) }),
            RelativeDistinguishedNames.schema(names.subject || {
                names: {
                    blockName: CSR_INFO_SUBJECT
                }
            }),
            PublicKeyInfo.schema({
                names: {
                    blockName: CSR_INFO_SPKI
                }
            }),
            new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [
                    new Repeated({
                        optional: true,
                        name: (names.CertificationRequestInfoAttributes || CSR_INFO_ATTRS),
                        value: Attribute.schema(names.attributes || {})
                    })
                ]
            })
        ]
    }));
}
class CertificationRequest extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.tbsView = new Uint8Array(getParametersValue(parameters, TBS$1, CertificationRequest.defaultValues(TBS$1)));
        this.version = getParametersValue(parameters, VERSION$6, CertificationRequest.defaultValues(VERSION$6));
        this.subject = getParametersValue(parameters, SUBJECT, CertificationRequest.defaultValues(SUBJECT));
        this.subjectPublicKeyInfo = getParametersValue(parameters, SPKI, CertificationRequest.defaultValues(SPKI));
        if (ATTRIBUTES$1 in parameters) {
            this.attributes = getParametersValue(parameters, ATTRIBUTES$1, CertificationRequest.defaultValues(ATTRIBUTES$1));
        }
        this.signatureAlgorithm = getParametersValue(parameters, SIGNATURE_ALGORITHM$2, CertificationRequest.defaultValues(SIGNATURE_ALGORITHM$2));
        this.signatureValue = getParametersValue(parameters, SIGNATURE_VALUE, CertificationRequest.defaultValues(SIGNATURE_VALUE));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    get tbs() {
        return BufferSourceConverter.toArrayBuffer(this.tbsView);
    }
    set tbs(value) {
        this.tbsView = new Uint8Array(value);
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case TBS$1:
                return EMPTY_BUFFER;
            case VERSION$6:
                return 0;
            case SUBJECT:
                return new RelativeDistinguishedNames();
            case SPKI:
                return new PublicKeyInfo();
            case ATTRIBUTES$1:
                return [];
            case SIGNATURE_ALGORITHM$2:
                return new AlgorithmIdentifier();
            case SIGNATURE_VALUE:
                return new BitString();
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            value: [
                CertificationRequestInfo(names.certificationRequestInfo || {}),
                new Sequence({
                    name: (names.signatureAlgorithm || SIGNATURE_ALGORITHM$2),
                    value: [
                        new ObjectIdentifier(),
                        new Any({ optional: true })
                    ]
                }),
                new BitString({ name: (names.signatureValue || SIGNATURE_VALUE) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$f);
        const asn1 = compareSchema(schema, schema, CertificationRequest.schema());
        AsnError.assertSchema(asn1, this.className);
        this.tbsView = asn1.result.CertificationRequestInfo.valueBeforeDecodeView;
        this.version = asn1.result[CSR_INFO_VERSION].valueBlock.valueDec;
        this.subject = new RelativeDistinguishedNames({ schema: asn1.result[CSR_INFO_SUBJECT] });
        this.subjectPublicKeyInfo = new PublicKeyInfo({ schema: asn1.result[CSR_INFO_SPKI] });
        if (CSR_INFO_ATTRS in asn1.result) {
            this.attributes = Array.from(asn1.result[CSR_INFO_ATTRS], element => new Attribute({ schema: element }));
        }
        this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm });
        this.signatureValue = asn1.result.signatureValue;
    }
    encodeTBS() {
        const outputArray = [
            new Integer({ value: this.version }),
            this.subject.toSchema(),
            this.subjectPublicKeyInfo.toSchema()
        ];
        if (ATTRIBUTES$1 in this) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: Array.from(this.attributes || [], o => o.toSchema())
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toSchema(encodeFlag = false) {
        let tbsSchema;
        if (encodeFlag === false) {
            if (this.tbsView.byteLength === 0) {
                return CertificationRequest.schema();
            }
            const asn1 = fromBER(this.tbsView);
            AsnError.assert(asn1, "PKCS#10 Certificate Request");
            tbsSchema = asn1.result;
        }
        else {
            tbsSchema = this.encodeTBS();
        }
        return (new Sequence({
            value: [
                tbsSchema,
                this.signatureAlgorithm.toSchema(),
                this.signatureValue
            ]
        }));
    }
    toJSON() {
        const object = {
            tbs: Convert.ToHex(this.tbsView),
            version: this.version,
            subject: this.subject.toJSON(),
            subjectPublicKeyInfo: this.subjectPublicKeyInfo.toJSON(),
            signatureAlgorithm: this.signatureAlgorithm.toJSON(),
            signatureValue: this.signatureValue.toJSON(),
        };
        if (ATTRIBUTES$1 in this) {
            object.attributes = Array.from(this.attributes || [], o => o.toJSON());
        }
        return object;
    }
    sign(privateKey, hashAlgorithm = "SHA-1", crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!privateKey) {
                throw new Error("Need to provide a private key for signing");
            }
            const signatureParams = yield crypto.getSignatureParameters(privateKey, hashAlgorithm);
            const parameters = signatureParams.parameters;
            this.signatureAlgorithm = signatureParams.signatureAlgorithm;
            this.tbsView = new Uint8Array(this.encodeTBS().toBER());
            const signature = yield crypto.signWithPrivateKey(this.tbsView, privateKey, parameters);
            this.signatureValue = new BitString({ valueHex: signature });
        });
    }
    verify(crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            return crypto.verifyWithPublicKey(this.tbsView, this.signatureValue, this.subjectPublicKeyInfo, this.signatureAlgorithm);
        });
    }
    getPublicKey(parameters, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            return crypto.getPublicKey(this.subjectPublicKeyInfo, this.signatureAlgorithm, parameters);
        });
    }
}
CertificationRequest.CLASS_NAME = "CertificationRequest";

const DIGEST_ALGORITHM$1 = "digestAlgorithm";
const DIGEST = "digest";
const CLEAR_PROPS$e = [
    DIGEST_ALGORITHM$1,
    DIGEST
];
class DigestInfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.digestAlgorithm = getParametersValue(parameters, DIGEST_ALGORITHM$1, DigestInfo.defaultValues(DIGEST_ALGORITHM$1));
        this.digest = getParametersValue(parameters, DIGEST, DigestInfo.defaultValues(DIGEST));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case DIGEST_ALGORITHM$1:
                return new AlgorithmIdentifier();
            case DIGEST:
                return new OctetString();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case DIGEST_ALGORITHM$1:
                return ((AlgorithmIdentifier.compareWithDefault("algorithmId", memberValue.algorithmId)) &&
                    (("algorithmParams" in memberValue) === false));
            case DIGEST:
                return (memberValue.isEqual(DigestInfo.defaultValues(memberName)));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                AlgorithmIdentifier.schema(names.digestAlgorithm || {
                    names: {
                        blockName: DIGEST_ALGORITHM$1
                    }
                }),
                new OctetString({ name: (names.digest || DIGEST) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$e);
        const asn1 = compareSchema(schema, schema, DigestInfo.schema({
            names: {
                digestAlgorithm: {
                    names: {
                        blockName: DIGEST_ALGORITHM$1
                    }
                },
                digest: DIGEST
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.digestAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.digestAlgorithm });
        this.digest = asn1.result.digest;
    }
    toSchema() {
        return (new Sequence({
            value: [
                this.digestAlgorithm.toSchema(),
                this.digest
            ]
        }));
    }
    toJSON() {
        return {
            digestAlgorithm: this.digestAlgorithm.toJSON(),
            digest: this.digest.toJSON(),
        };
    }
}
DigestInfo.CLASS_NAME = "DigestInfo";

const E_CONTENT_TYPE = "eContentType";
const E_CONTENT = "eContent";
const CLEAR_PROPS$d = [
    E_CONTENT_TYPE,
    E_CONTENT,
];
class EncapsulatedContentInfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.eContentType = getParametersValue(parameters, E_CONTENT_TYPE, EncapsulatedContentInfo.defaultValues(E_CONTENT_TYPE));
        if (E_CONTENT in parameters) {
            this.eContent = getParametersValue(parameters, E_CONTENT, EncapsulatedContentInfo.defaultValues(E_CONTENT));
            if ((this.eContent.idBlock.tagClass === 1) &&
                (this.eContent.idBlock.tagNumber === 4)) {
                if (this.eContent.idBlock.isConstructed === false) {
                    const constrString = new OctetString({
                        idBlock: { isConstructed: true },
                        isConstructed: true
                    });
                    let offset = 0;
                    const viewHex = this.eContent.valueBlock.valueHexView.slice().buffer;
                    let length = viewHex.byteLength;
                    while (length > 0) {
                        const pieceView = new Uint8Array(viewHex, offset, ((offset + 65536) > viewHex.byteLength) ? (viewHex.byteLength - offset) : 65536);
                        const _array = new ArrayBuffer(pieceView.length);
                        const _view = new Uint8Array(_array);
                        for (let i = 0; i < _view.length; i++) {
                            _view[i] = pieceView[i];
                        }
                        constrString.valueBlock.value.push(new OctetString({ valueHex: _array }));
                        length -= pieceView.length;
                        offset += pieceView.length;
                    }
                    this.eContent = constrString;
                }
            }
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case E_CONTENT_TYPE:
                return EMPTY_STRING;
            case E_CONTENT:
                return new OctetString();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case E_CONTENT_TYPE:
                return (memberValue === EMPTY_STRING);
            case E_CONTENT:
                {
                    if ((memberValue.idBlock.tagClass === 1) && (memberValue.idBlock.tagNumber === 4))
                        return (memberValue.isEqual(EncapsulatedContentInfo.defaultValues(E_CONTENT)));
                    return false;
                }
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.eContentType || EMPTY_STRING) }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [
                        new Any({ name: (names.eContent || EMPTY_STRING) })
                    ]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$d);
        const asn1 = compareSchema(schema, schema, EncapsulatedContentInfo.schema({
            names: {
                eContentType: E_CONTENT_TYPE,
                eContent: E_CONTENT
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.eContentType = asn1.result.eContentType.valueBlock.toString();
        if (E_CONTENT in asn1.result)
            this.eContent = asn1.result.eContent;
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(new ObjectIdentifier({ value: this.eContentType }));
        if (this.eContent) {
            if (EncapsulatedContentInfo.compareWithDefault(E_CONTENT, this.eContent) === false) {
                outputArray.push(new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [this.eContent]
                }));
            }
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            eContentType: this.eContentType
        };
        if (this.eContent && EncapsulatedContentInfo.compareWithDefault(E_CONTENT, this.eContent) === false) {
            res.eContent = this.eContent.toJSON();
        }
        return res;
    }
}
EncapsulatedContentInfo.CLASS_NAME = "EncapsulatedContentInfo";

class KeyBag extends PrivateKeyInfo {
    constructor(parameters = {}) {
        super(parameters);
    }
}

const MAC = "mac";
const MAC_SALT = "macSalt";
const ITERATIONS = "iterations";
const CLEAR_PROPS$c = [
    MAC,
    MAC_SALT,
    ITERATIONS
];
class MacData extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.mac = getParametersValue(parameters, MAC, MacData.defaultValues(MAC));
        this.macSalt = getParametersValue(parameters, MAC_SALT, MacData.defaultValues(MAC_SALT));
        if (ITERATIONS in parameters) {
            this.iterations = getParametersValue(parameters, ITERATIONS, MacData.defaultValues(ITERATIONS));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case MAC:
                return new DigestInfo();
            case MAC_SALT:
                return new OctetString();
            case ITERATIONS:
                return 1;
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case MAC:
                return ((DigestInfo.compareWithDefault("digestAlgorithm", memberValue.digestAlgorithm)) &&
                    (DigestInfo.compareWithDefault("digest", memberValue.digest)));
            case MAC_SALT:
                return (memberValue.isEqual(MacData.defaultValues(memberName)));
            case ITERATIONS:
                return (memberValue === MacData.defaultValues(memberName));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            optional: (names.optional || true),
            value: [
                DigestInfo.schema(names.mac || {
                    names: {
                        blockName: MAC
                    }
                }),
                new OctetString({ name: (names.macSalt || MAC_SALT) }),
                new Integer({
                    optional: true,
                    name: (names.iterations || ITERATIONS)
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$c);
        const asn1 = compareSchema(schema, schema, MacData.schema({
            names: {
                mac: {
                    names: {
                        blockName: MAC
                    }
                },
                macSalt: MAC_SALT,
                iterations: ITERATIONS
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.mac = new DigestInfo({ schema: asn1.result.mac });
        this.macSalt = asn1.result.macSalt;
        if (ITERATIONS in asn1.result)
            this.iterations = asn1.result.iterations.valueBlock.valueDec;
    }
    toSchema() {
        const outputArray = [
            this.mac.toSchema(),
            this.macSalt
        ];
        if (this.iterations !== undefined) {
            outputArray.push(new Integer({ value: this.iterations }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            mac: this.mac.toJSON(),
            macSalt: this.macSalt.toJSON(),
        };
        if (this.iterations !== undefined) {
            res.iterations = this.iterations;
        }
        return res;
    }
}
MacData.CLASS_NAME = "MacData";

const HASH_ALGORITHM = "hashAlgorithm";
const HASHED_MESSAGE = "hashedMessage";
const CLEAR_PROPS$b = [
    HASH_ALGORITHM,
    HASHED_MESSAGE,
];
class MessageImprint extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.hashAlgorithm = getParametersValue(parameters, HASH_ALGORITHM, MessageImprint.defaultValues(HASH_ALGORITHM));
        this.hashedMessage = getParametersValue(parameters, HASHED_MESSAGE, MessageImprint.defaultValues(HASHED_MESSAGE));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static create(hashAlgorithm, message, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            const hashAlgorithmOID = crypto.getOIDByAlgorithm({ name: hashAlgorithm }, true, "hashAlgorithm");
            const hashedMessage = yield crypto.digest(hashAlgorithm, message);
            const res = new MessageImprint({
                hashAlgorithm: new AlgorithmIdentifier({
                    algorithmId: hashAlgorithmOID,
                    algorithmParams: new Null(),
                }),
                hashedMessage: new OctetString({ valueHex: hashedMessage })
            });
            return res;
        });
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case HASH_ALGORITHM:
                return new AlgorithmIdentifier();
            case HASHED_MESSAGE:
                return new OctetString();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case HASH_ALGORITHM:
                return ((memberValue.algorithmId === EMPTY_STRING) && (("algorithmParams" in memberValue) === false));
            case HASHED_MESSAGE:
                return (memberValue.isEqual(MessageImprint.defaultValues(memberName)) === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                AlgorithmIdentifier.schema(names.hashAlgorithm || {}),
                new OctetString({ name: (names.hashedMessage || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$b);
        const asn1 = compareSchema(schema, schema, MessageImprint.schema({
            names: {
                hashAlgorithm: {
                    names: {
                        blockName: HASH_ALGORITHM
                    }
                },
                hashedMessage: HASHED_MESSAGE
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.hashAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.hashAlgorithm });
        this.hashedMessage = asn1.result.hashedMessage;
    }
    toSchema() {
        return (new Sequence({
            value: [
                this.hashAlgorithm.toSchema(),
                this.hashedMessage
            ]
        }));
    }
    toJSON() {
        return {
            hashAlgorithm: this.hashAlgorithm.toJSON(),
            hashedMessage: this.hashedMessage.toJSON(),
        };
    }
}
MessageImprint.CLASS_NAME = "MessageImprint";

const REQ_CERT = "reqCert";
const SINGLE_REQUEST_EXTENSIONS = "singleRequestExtensions";
const CLEAR_PROPS$a = [
    REQ_CERT,
    SINGLE_REQUEST_EXTENSIONS,
];
class Request extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.reqCert = getParametersValue(parameters, REQ_CERT, Request.defaultValues(REQ_CERT));
        if (SINGLE_REQUEST_EXTENSIONS in parameters) {
            this.singleRequestExtensions = getParametersValue(parameters, SINGLE_REQUEST_EXTENSIONS, Request.defaultValues(SINGLE_REQUEST_EXTENSIONS));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case REQ_CERT:
                return new CertID();
            case SINGLE_REQUEST_EXTENSIONS:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case REQ_CERT:
                return (memberValue.isEqual(Request.defaultValues(memberName)));
            case SINGLE_REQUEST_EXTENSIONS:
                return (memberValue.length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                CertID.schema(names.reqCert || {}),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [Extension.schema(names.extensions || {
                            names: {
                                blockName: (names.singleRequestExtensions || EMPTY_STRING)
                            }
                        })]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$a);
        const asn1 = compareSchema(schema, schema, Request.schema({
            names: {
                reqCert: {
                    names: {
                        blockName: REQ_CERT
                    }
                },
                extensions: {
                    names: {
                        blockName: SINGLE_REQUEST_EXTENSIONS
                    }
                }
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.reqCert = new CertID({ schema: asn1.result.reqCert });
        if (SINGLE_REQUEST_EXTENSIONS in asn1.result) {
            this.singleRequestExtensions = Array.from(asn1.result.singleRequestExtensions.valueBlock.value, element => new Extension({ schema: element }));
        }
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(this.reqCert.toSchema());
        if (this.singleRequestExtensions) {
            outputArray.push(new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [
                    new Sequence({
                        value: Array.from(this.singleRequestExtensions, o => o.toSchema())
                    })
                ]
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            reqCert: this.reqCert.toJSON()
        };
        if (this.singleRequestExtensions) {
            res.singleRequestExtensions = Array.from(this.singleRequestExtensions, o => o.toJSON());
        }
        return res;
    }
}
Request.CLASS_NAME = "Request";

const TBS = "tbs";
const VERSION$5 = "version";
const REQUESTOR_NAME = "requestorName";
const REQUEST_LIST = "requestList";
const REQUEST_EXTENSIONS = "requestExtensions";
const TBS_REQUEST$1 = "TBSRequest";
const TBS_REQUEST_VERSION = `${TBS_REQUEST$1}.${VERSION$5}`;
const TBS_REQUEST_REQUESTOR_NAME = `${TBS_REQUEST$1}.${REQUESTOR_NAME}`;
const TBS_REQUEST_REQUESTS = `${TBS_REQUEST$1}.requests`;
const TBS_REQUEST_REQUEST_EXTENSIONS = `${TBS_REQUEST$1}.${REQUEST_EXTENSIONS}`;
const CLEAR_PROPS$9 = [
    TBS_REQUEST$1,
    TBS_REQUEST_VERSION,
    TBS_REQUEST_REQUESTOR_NAME,
    TBS_REQUEST_REQUESTS,
    TBS_REQUEST_REQUEST_EXTENSIONS
];
class TBSRequest extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.tbsView = new Uint8Array(getParametersValue(parameters, TBS, TBSRequest.defaultValues(TBS)));
        if (VERSION$5 in parameters) {
            this.version = getParametersValue(parameters, VERSION$5, TBSRequest.defaultValues(VERSION$5));
        }
        if (REQUESTOR_NAME in parameters) {
            this.requestorName = getParametersValue(parameters, REQUESTOR_NAME, TBSRequest.defaultValues(REQUESTOR_NAME));
        }
        this.requestList = getParametersValue(parameters, REQUEST_LIST, TBSRequest.defaultValues(REQUEST_LIST));
        if (REQUEST_EXTENSIONS in parameters) {
            this.requestExtensions = getParametersValue(parameters, REQUEST_EXTENSIONS, TBSRequest.defaultValues(REQUEST_EXTENSIONS));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    get tbs() {
        return BufferSourceConverter.toArrayBuffer(this.tbsView);
    }
    set tbs(value) {
        this.tbsView = new Uint8Array(value);
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case TBS:
                return EMPTY_BUFFER;
            case VERSION$5:
                return 0;
            case REQUESTOR_NAME:
                return new GeneralName();
            case REQUEST_LIST:
            case REQUEST_EXTENSIONS:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case TBS:
                return (memberValue.byteLength === 0);
            case VERSION$5:
                return (memberValue === TBSRequest.defaultValues(memberName));
            case REQUESTOR_NAME:
                return ((memberValue.type === GeneralName.defaultValues("type")) && (Object.keys(memberValue.value).length === 0));
            case REQUEST_LIST:
            case REQUEST_EXTENSIONS:
                return (memberValue.length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || TBS_REQUEST$1),
            value: [
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [new Integer({ name: (names.TBSRequestVersion || TBS_REQUEST_VERSION) })]
                }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: [GeneralName.schema(names.requestorName || {
                            names: {
                                blockName: TBS_REQUEST_REQUESTOR_NAME
                            }
                        })]
                }),
                new Sequence({
                    name: (names.requestList || "TBSRequest.requestList"),
                    value: [
                        new Repeated({
                            name: (names.requests || TBS_REQUEST_REQUESTS),
                            value: Request.schema(names.requestNames || {})
                        })
                    ]
                }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 2
                    },
                    value: [Extensions.schema(names.extensions || {
                            names: {
                                blockName: (names.requestExtensions || TBS_REQUEST_REQUEST_EXTENSIONS)
                            }
                        })]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$9);
        const asn1 = compareSchema(schema, schema, TBSRequest.schema());
        AsnError.assertSchema(asn1, this.className);
        this.tbsView = asn1.result.TBSRequest.valueBeforeDecodeView;
        if (TBS_REQUEST_VERSION in asn1.result)
            this.version = asn1.result[TBS_REQUEST_VERSION].valueBlock.valueDec;
        if (TBS_REQUEST_REQUESTOR_NAME in asn1.result)
            this.requestorName = new GeneralName({ schema: asn1.result[TBS_REQUEST_REQUESTOR_NAME] });
        this.requestList = Array.from(asn1.result[TBS_REQUEST_REQUESTS], element => new Request({ schema: element }));
        if (TBS_REQUEST_REQUEST_EXTENSIONS in asn1.result)
            this.requestExtensions = Array.from(asn1.result[TBS_REQUEST_REQUEST_EXTENSIONS].valueBlock.value, element => new Extension({ schema: element }));
    }
    toSchema(encodeFlag = false) {
        let tbsSchema;
        if (encodeFlag === false) {
            if (this.tbsView.byteLength === 0)
                return TBSRequest.schema();
            const asn1 = fromBER(this.tbsView);
            AsnError.assert(asn1, "TBS Request");
            if (!(asn1.result instanceof Sequence)) {
                throw new Error("ASN.1 result should be SEQUENCE");
            }
            tbsSchema = asn1.result;
        }
        else {
            const outputArray = [];
            if (this.version !== undefined) {
                outputArray.push(new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [new Integer({ value: this.version })]
                }));
            }
            if (this.requestorName) {
                outputArray.push(new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: [this.requestorName.toSchema()]
                }));
            }
            outputArray.push(new Sequence({
                value: Array.from(this.requestList, o => o.toSchema())
            }));
            if (this.requestExtensions) {
                outputArray.push(new Constructed({
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 2
                    },
                    value: [
                        new Sequence({
                            value: Array.from(this.requestExtensions, o => o.toSchema())
                        })
                    ]
                }));
            }
            tbsSchema = new Sequence({
                value: outputArray
            });
        }
        return tbsSchema;
    }
    toJSON() {
        const res = {};
        if (this.version != undefined)
            res.version = this.version;
        if (this.requestorName) {
            res.requestorName = this.requestorName.toJSON();
        }
        res.requestList = Array.from(this.requestList, o => o.toJSON());
        if (this.requestExtensions) {
            res.requestExtensions = Array.from(this.requestExtensions, o => o.toJSON());
        }
        return res;
    }
}
TBSRequest.CLASS_NAME = "TBSRequest";

const SIGNATURE_ALGORITHM$1 = "signatureAlgorithm";
const SIGNATURE$1 = "signature";
const CERTS = "certs";
class Signature extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.signatureAlgorithm = getParametersValue(parameters, SIGNATURE_ALGORITHM$1, Signature.defaultValues(SIGNATURE_ALGORITHM$1));
        this.signature = getParametersValue(parameters, SIGNATURE$1, Signature.defaultValues(SIGNATURE$1));
        if (CERTS in parameters) {
            this.certs = getParametersValue(parameters, CERTS, Signature.defaultValues(CERTS));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case SIGNATURE_ALGORITHM$1:
                return new AlgorithmIdentifier();
            case SIGNATURE$1:
                return new BitString();
            case CERTS:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case SIGNATURE_ALGORITHM$1:
                return ((memberValue.algorithmId === EMPTY_STRING) && (("algorithmParams" in memberValue) === false));
            case SIGNATURE$1:
                return (memberValue.isEqual(Signature.defaultValues(memberName)));
            case CERTS:
                return (memberValue.length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                AlgorithmIdentifier.schema(names.signatureAlgorithm || {}),
                new BitString({ name: (names.signature || EMPTY_STRING) }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [
                        new Sequence({
                            value: [new Repeated({
                                    name: (names.certs || EMPTY_STRING),
                                    value: Certificate.schema({})
                                })]
                        })
                    ]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, [
            SIGNATURE_ALGORITHM$1,
            SIGNATURE$1,
            CERTS
        ]);
        const asn1 = compareSchema(schema, schema, Signature.schema({
            names: {
                signatureAlgorithm: {
                    names: {
                        blockName: SIGNATURE_ALGORITHM$1
                    }
                },
                signature: SIGNATURE$1,
                certs: CERTS
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result.signatureAlgorithm });
        this.signature = asn1.result.signature;
        if (CERTS in asn1.result)
            this.certs = Array.from(asn1.result.certs, element => new Certificate({ schema: element }));
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(this.signatureAlgorithm.toSchema());
        outputArray.push(this.signature);
        if (this.certs) {
            outputArray.push(new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [
                    new Sequence({
                        value: Array.from(this.certs, o => o.toSchema())
                    })
                ]
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            signatureAlgorithm: this.signatureAlgorithm.toJSON(),
            signature: this.signature.toJSON(),
        };
        if (this.certs) {
            res.certs = Array.from(this.certs, o => o.toJSON());
        }
        return res;
    }
}
Signature.CLASS_NAME = "Signature";

const TBS_REQUEST = "tbsRequest";
const OPTIONAL_SIGNATURE = "optionalSignature";
const CLEAR_PROPS$8 = [
    TBS_REQUEST,
    OPTIONAL_SIGNATURE
];
class OCSPRequest extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.tbsRequest = getParametersValue(parameters, TBS_REQUEST, OCSPRequest.defaultValues(TBS_REQUEST));
        if (OPTIONAL_SIGNATURE in parameters) {
            this.optionalSignature = getParametersValue(parameters, OPTIONAL_SIGNATURE, OCSPRequest.defaultValues(OPTIONAL_SIGNATURE));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case TBS_REQUEST:
                return new TBSRequest();
            case OPTIONAL_SIGNATURE:
                return new Signature();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case TBS_REQUEST:
                return ((TBSRequest.compareWithDefault("tbs", memberValue.tbs)) &&
                    (TBSRequest.compareWithDefault("version", memberValue.version)) &&
                    (TBSRequest.compareWithDefault("requestorName", memberValue.requestorName)) &&
                    (TBSRequest.compareWithDefault("requestList", memberValue.requestList)) &&
                    (TBSRequest.compareWithDefault("requestExtensions", memberValue.requestExtensions)));
            case OPTIONAL_SIGNATURE:
                return ((Signature.compareWithDefault("signatureAlgorithm", memberValue.signatureAlgorithm)) &&
                    (Signature.compareWithDefault("signature", memberValue.signature)) &&
                    (Signature.compareWithDefault("certs", memberValue.certs)));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: names.blockName || "OCSPRequest",
            value: [
                TBSRequest.schema(names.tbsRequest || {
                    names: {
                        blockName: TBS_REQUEST
                    }
                }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [
                        Signature.schema(names.optionalSignature || {
                            names: {
                                blockName: OPTIONAL_SIGNATURE
                            }
                        })
                    ]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$8);
        const asn1 = compareSchema(schema, schema, OCSPRequest.schema());
        AsnError.assertSchema(asn1, this.className);
        this.tbsRequest = new TBSRequest({ schema: asn1.result.tbsRequest });
        if (OPTIONAL_SIGNATURE in asn1.result)
            this.optionalSignature = new Signature({ schema: asn1.result.optionalSignature });
    }
    toSchema(encodeFlag = false) {
        const outputArray = [];
        outputArray.push(this.tbsRequest.toSchema(encodeFlag));
        if (this.optionalSignature)
            outputArray.push(new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [
                    this.optionalSignature.toSchema()
                ]
            }));
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            tbsRequest: this.tbsRequest.toJSON()
        };
        if (this.optionalSignature) {
            res.optionalSignature = this.optionalSignature.toJSON();
        }
        return res;
    }
    createForCertificate(certificate, parameters, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            const certID = new CertID();
            yield certID.createForCertificate(certificate, parameters, crypto);
            this.tbsRequest.requestList.push(new Request({
                reqCert: certID,
            }));
        });
    }
    sign(privateKey, hashAlgorithm = "SHA-1", crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            ParameterError.assertEmpty(privateKey, "privateKey", "OCSPRequest.sign method");
            if (!this.optionalSignature) {
                throw new Error("Need to create \"optionalSignature\" field before signing");
            }
            const signatureParams = yield crypto.getSignatureParameters(privateKey, hashAlgorithm);
            const parameters = signatureParams.parameters;
            this.optionalSignature.signatureAlgorithm = signatureParams.signatureAlgorithm;
            const tbs = this.tbsRequest.toSchema(true).toBER(false);
            const signature = yield crypto.signWithPrivateKey(tbs, privateKey, parameters);
            this.optionalSignature.signature = new BitString({ valueHex: signature });
        });
    }
    verify() {
    }
}
OCSPRequest.CLASS_NAME = "OCSPRequest";

const RESPONSE_TYPE = "responseType";
const RESPONSE = "response";
const CLEAR_PROPS$7 = [
    RESPONSE_TYPE,
    RESPONSE
];
class ResponseBytes extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.responseType = getParametersValue(parameters, RESPONSE_TYPE, ResponseBytes.defaultValues(RESPONSE_TYPE));
        this.response = getParametersValue(parameters, RESPONSE, ResponseBytes.defaultValues(RESPONSE));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case RESPONSE_TYPE:
                return EMPTY_STRING;
            case RESPONSE:
                return new OctetString();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case RESPONSE_TYPE:
                return (memberValue === EMPTY_STRING);
            case RESPONSE:
                return (memberValue.isEqual(ResponseBytes.defaultValues(memberName)));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new ObjectIdentifier({ name: (names.responseType || EMPTY_STRING) }),
                new OctetString({ name: (names.response || EMPTY_STRING) })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$7);
        const asn1 = compareSchema(schema, schema, ResponseBytes.schema({
            names: {
                responseType: RESPONSE_TYPE,
                response: RESPONSE
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.responseType = asn1.result.responseType.valueBlock.toString();
        this.response = asn1.result.response;
    }
    toSchema() {
        return (new Sequence({
            value: [
                new ObjectIdentifier({ value: this.responseType }),
                this.response
            ]
        }));
    }
    toJSON() {
        return {
            responseType: this.responseType,
            response: this.response.toJSON(),
        };
    }
}
ResponseBytes.CLASS_NAME = "ResponseBytes";

const RESPONSE_STATUS = "responseStatus";
const RESPONSE_BYTES = "responseBytes";
class OCSPResponse extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.responseStatus = getParametersValue(parameters, RESPONSE_STATUS, OCSPResponse.defaultValues(RESPONSE_STATUS));
        if (RESPONSE_BYTES in parameters) {
            this.responseBytes = getParametersValue(parameters, RESPONSE_BYTES, OCSPResponse.defaultValues(RESPONSE_BYTES));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case RESPONSE_STATUS:
                return new Enumerated();
            case RESPONSE_BYTES:
                return new ResponseBytes();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case RESPONSE_STATUS:
                return (memberValue.isEqual(OCSPResponse.defaultValues(memberName)));
            case RESPONSE_BYTES:
                return ((ResponseBytes.compareWithDefault("responseType", memberValue.responseType)) &&
                    (ResponseBytes.compareWithDefault("response", memberValue.response)));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || "OCSPResponse"),
            value: [
                new Enumerated({ name: (names.responseStatus || RESPONSE_STATUS) }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [
                        ResponseBytes.schema(names.responseBytes || {
                            names: {
                                blockName: RESPONSE_BYTES
                            }
                        })
                    ]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, [
            RESPONSE_STATUS,
            RESPONSE_BYTES
        ]);
        const asn1 = compareSchema(schema, schema, OCSPResponse.schema());
        AsnError.assertSchema(asn1, this.className);
        this.responseStatus = asn1.result.responseStatus;
        if (RESPONSE_BYTES in asn1.result)
            this.responseBytes = new ResponseBytes({ schema: asn1.result.responseBytes });
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(this.responseStatus);
        if (this.responseBytes) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [this.responseBytes.toSchema()]
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            responseStatus: this.responseStatus.toJSON()
        };
        if (this.responseBytes) {
            res.responseBytes = this.responseBytes.toJSON();
        }
        return res;
    }
    getCertificateStatus(certificate, issuerCertificate, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            let basicResponse;
            const result = {
                isForCertificate: false,
                status: 2
            };
            if (!this.responseBytes)
                return result;
            if (this.responseBytes.responseType !== id_PKIX_OCSP_Basic)
                return result;
            try {
                const asn1Basic = fromBER(this.responseBytes.response.valueBlock.valueHexView);
                AsnError.assert(asn1Basic, "Basic OCSP response");
                basicResponse = new BasicOCSPResponse({ schema: asn1Basic.result });
            }
            catch (ex) {
                return result;
            }
            return basicResponse.getCertificateStatus(certificate, issuerCertificate, crypto);
        });
    }
    sign(privateKey, hashAlgorithm, crypto = getCrypto(true)) {
        var _a;
        return __awaiter(this, void 0, void 0, function* () {
            if (this.responseBytes && this.responseBytes.responseType === id_PKIX_OCSP_Basic) {
                const basicResponse = BasicOCSPResponse.fromBER(this.responseBytes.response.valueBlock.valueHexView);
                return basicResponse.sign(privateKey, hashAlgorithm, crypto);
            }
            throw new Error(`Unknown ResponseBytes type: ${((_a = this.responseBytes) === null || _a === void 0 ? void 0 : _a.responseType) || "Unknown"}`);
        });
    }
    verify(issuerCertificate = null, crypto = getCrypto(true)) {
        var _a;
        return __awaiter(this, void 0, void 0, function* () {
            if ((RESPONSE_BYTES in this) === false)
                throw new Error("Empty ResponseBytes field");
            if (this.responseBytes && this.responseBytes.responseType === id_PKIX_OCSP_Basic) {
                const basicResponse = BasicOCSPResponse.fromBER(this.responseBytes.response.valueBlock.valueHexView);
                if (issuerCertificate !== null) {
                    if (!basicResponse.certs) {
                        basicResponse.certs = [];
                    }
                    basicResponse.certs.push(issuerCertificate);
                }
                return basicResponse.verify({}, crypto);
            }
            throw new Error(`Unknown ResponseBytes type: ${((_a = this.responseBytes) === null || _a === void 0 ? void 0 : _a.responseType) || "Unknown"}`);
        });
    }
}
OCSPResponse.CLASS_NAME = "OCSPResponse";

const TYPE = "type";
const ATTRIBUTES = "attributes";
const ENCODED_VALUE = "encodedValue";
const CLEAR_PROPS$6 = [
    ATTRIBUTES
];
class SignedAndUnsignedAttributes extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.type = getParametersValue(parameters, TYPE, SignedAndUnsignedAttributes.defaultValues(TYPE));
        this.attributes = getParametersValue(parameters, ATTRIBUTES, SignedAndUnsignedAttributes.defaultValues(ATTRIBUTES));
        this.encodedValue = getParametersValue(parameters, ENCODED_VALUE, SignedAndUnsignedAttributes.defaultValues(ENCODED_VALUE));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case TYPE:
                return (-1);
            case ATTRIBUTES:
                return [];
            case ENCODED_VALUE:
                return EMPTY_BUFFER;
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case TYPE:
                return (memberValue === SignedAndUnsignedAttributes.defaultValues(TYPE));
            case ATTRIBUTES:
                return (memberValue.length === 0);
            case ENCODED_VALUE:
                return (memberValue.byteLength === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Constructed({
            name: (names.blockName || EMPTY_STRING),
            optional: true,
            idBlock: {
                tagClass: 3,
                tagNumber: names.tagNumber || 0
            },
            value: [
                new Repeated({
                    name: (names.attributes || EMPTY_STRING),
                    value: Attribute.schema()
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$6);
        const asn1 = compareSchema(schema, schema, SignedAndUnsignedAttributes.schema({
            names: {
                tagNumber: this.type,
                attributes: ATTRIBUTES
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.type = asn1.result.idBlock.tagNumber;
        this.encodedValue = BufferSourceConverter.toArrayBuffer(asn1.result.valueBeforeDecodeView);
        const encodedView = new Uint8Array(this.encodedValue);
        encodedView[0] = 0x31;
        if ((ATTRIBUTES in asn1.result) === false) {
            if (this.type === 0)
                throw new Error("Wrong structure of SignedUnsignedAttributes");
            else
                return;
        }
        this.attributes = Array.from(asn1.result.attributes, element => new Attribute({ schema: element }));
    }
    toSchema() {
        if (SignedAndUnsignedAttributes.compareWithDefault(TYPE, this.type) || SignedAndUnsignedAttributes.compareWithDefault(ATTRIBUTES, this.attributes))
            throw new Error("Incorrectly initialized \"SignedAndUnsignedAttributes\" class");
        return (new Constructed({
            optional: true,
            idBlock: {
                tagClass: 3,
                tagNumber: this.type
            },
            value: Array.from(this.attributes, o => o.toSchema())
        }));
    }
    toJSON() {
        if (SignedAndUnsignedAttributes.compareWithDefault(TYPE, this.type) || SignedAndUnsignedAttributes.compareWithDefault(ATTRIBUTES, this.attributes))
            throw new Error("Incorrectly initialized \"SignedAndUnsignedAttributes\" class");
        return {
            type: this.type,
            attributes: Array.from(this.attributes, o => o.toJSON())
        };
    }
}
SignedAndUnsignedAttributes.CLASS_NAME = "SignedAndUnsignedAttributes";

const VERSION$4 = "version";
const SID = "sid";
const DIGEST_ALGORITHM = "digestAlgorithm";
const SIGNED_ATTRS = "signedAttrs";
const SIGNATURE_ALGORITHM = "signatureAlgorithm";
const SIGNATURE = "signature";
const UNSIGNED_ATTRS = "unsignedAttrs";
const SIGNER_INFO = "SignerInfo";
const SIGNER_INFO_VERSION = `${SIGNER_INFO}.${VERSION$4}`;
const SIGNER_INFO_SID = `${SIGNER_INFO}.${SID}`;
const SIGNER_INFO_DIGEST_ALGORITHM = `${SIGNER_INFO}.${DIGEST_ALGORITHM}`;
const SIGNER_INFO_SIGNED_ATTRS = `${SIGNER_INFO}.${SIGNED_ATTRS}`;
const SIGNER_INFO_SIGNATURE_ALGORITHM = `${SIGNER_INFO}.${SIGNATURE_ALGORITHM}`;
const SIGNER_INFO_SIGNATURE = `${SIGNER_INFO}.${SIGNATURE}`;
const SIGNER_INFO_UNSIGNED_ATTRS = `${SIGNER_INFO}.${UNSIGNED_ATTRS}`;
const CLEAR_PROPS$5 = [
    SIGNER_INFO_VERSION,
    SIGNER_INFO_SID,
    SIGNER_INFO_DIGEST_ALGORITHM,
    SIGNER_INFO_SIGNED_ATTRS,
    SIGNER_INFO_SIGNATURE_ALGORITHM,
    SIGNER_INFO_SIGNATURE,
    SIGNER_INFO_UNSIGNED_ATTRS
];
class SignerInfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.version = getParametersValue(parameters, VERSION$4, SignerInfo.defaultValues(VERSION$4));
        this.sid = getParametersValue(parameters, SID, SignerInfo.defaultValues(SID));
        this.digestAlgorithm = getParametersValue(parameters, DIGEST_ALGORITHM, SignerInfo.defaultValues(DIGEST_ALGORITHM));
        if (SIGNED_ATTRS in parameters) {
            this.signedAttrs = getParametersValue(parameters, SIGNED_ATTRS, SignerInfo.defaultValues(SIGNED_ATTRS));
        }
        this.signatureAlgorithm = getParametersValue(parameters, SIGNATURE_ALGORITHM, SignerInfo.defaultValues(SIGNATURE_ALGORITHM));
        this.signature = getParametersValue(parameters, SIGNATURE, SignerInfo.defaultValues(SIGNATURE));
        if (UNSIGNED_ATTRS in parameters) {
            this.unsignedAttrs = getParametersValue(parameters, UNSIGNED_ATTRS, SignerInfo.defaultValues(UNSIGNED_ATTRS));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VERSION$4:
                return 0;
            case SID:
                return new Any();
            case DIGEST_ALGORITHM:
                return new AlgorithmIdentifier();
            case SIGNED_ATTRS:
                return new SignedAndUnsignedAttributes({ type: 0 });
            case SIGNATURE_ALGORITHM:
                return new AlgorithmIdentifier();
            case SIGNATURE:
                return new OctetString();
            case UNSIGNED_ATTRS:
                return new SignedAndUnsignedAttributes({ type: 1 });
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case VERSION$4:
                return (SignerInfo.defaultValues(VERSION$4) === memberValue);
            case SID:
                return (memberValue instanceof Any);
            case DIGEST_ALGORITHM:
                if ((memberValue instanceof AlgorithmIdentifier) === false)
                    return false;
                return memberValue.isEqual(SignerInfo.defaultValues(DIGEST_ALGORITHM));
            case SIGNED_ATTRS:
                return ((SignedAndUnsignedAttributes.compareWithDefault("type", memberValue.type))
                    && (SignedAndUnsignedAttributes.compareWithDefault("attributes", memberValue.attributes))
                    && (SignedAndUnsignedAttributes.compareWithDefault("encodedValue", memberValue.encodedValue)));
            case SIGNATURE_ALGORITHM:
                if ((memberValue instanceof AlgorithmIdentifier) === false)
                    return false;
                return memberValue.isEqual(SignerInfo.defaultValues(SIGNATURE_ALGORITHM));
            case SIGNATURE:
            case UNSIGNED_ATTRS:
                return ((SignedAndUnsignedAttributes.compareWithDefault("type", memberValue.type))
                    && (SignedAndUnsignedAttributes.compareWithDefault("attributes", memberValue.attributes))
                    && (SignedAndUnsignedAttributes.compareWithDefault("encodedValue", memberValue.encodedValue)));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: SIGNER_INFO,
            value: [
                new Integer({ name: (names.version || SIGNER_INFO_VERSION) }),
                new Choice({
                    value: [
                        IssuerAndSerialNumber.schema(names.sidSchema || {
                            names: {
                                blockName: SIGNER_INFO_SID
                            }
                        }),
                        new Choice({
                            value: [
                                new Constructed({
                                    optional: true,
                                    name: (names.sid || SIGNER_INFO_SID),
                                    idBlock: {
                                        tagClass: 3,
                                        tagNumber: 0
                                    },
                                    value: [new OctetString()]
                                }),
                                new Primitive({
                                    optional: true,
                                    name: (names.sid || SIGNER_INFO_SID),
                                    idBlock: {
                                        tagClass: 3,
                                        tagNumber: 0
                                    }
                                }),
                            ]
                        }),
                    ]
                }),
                AlgorithmIdentifier.schema(names.digestAlgorithm || {
                    names: {
                        blockName: SIGNER_INFO_DIGEST_ALGORITHM
                    }
                }),
                SignedAndUnsignedAttributes.schema(names.signedAttrs || {
                    names: {
                        blockName: SIGNER_INFO_SIGNED_ATTRS,
                        tagNumber: 0
                    }
                }),
                AlgorithmIdentifier.schema(names.signatureAlgorithm || {
                    names: {
                        blockName: SIGNER_INFO_SIGNATURE_ALGORITHM
                    }
                }),
                new OctetString({ name: (names.signature || SIGNER_INFO_SIGNATURE) }),
                SignedAndUnsignedAttributes.schema(names.unsignedAttrs || {
                    names: {
                        blockName: SIGNER_INFO_UNSIGNED_ATTRS,
                        tagNumber: 1
                    }
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$5);
        const asn1 = compareSchema(schema, schema, SignerInfo.schema());
        AsnError.assertSchema(asn1, this.className);
        this.version = asn1.result[SIGNER_INFO_VERSION].valueBlock.valueDec;
        const currentSid = asn1.result[SIGNER_INFO_SID];
        if (currentSid.idBlock.tagClass === 1)
            this.sid = new IssuerAndSerialNumber({ schema: currentSid });
        else
            this.sid = currentSid;
        this.digestAlgorithm = new AlgorithmIdentifier({ schema: asn1.result[SIGNER_INFO_DIGEST_ALGORITHM] });
        if (SIGNER_INFO_SIGNED_ATTRS in asn1.result)
            this.signedAttrs = new SignedAndUnsignedAttributes({ type: 0, schema: asn1.result[SIGNER_INFO_SIGNED_ATTRS] });
        this.signatureAlgorithm = new AlgorithmIdentifier({ schema: asn1.result[SIGNER_INFO_SIGNATURE_ALGORITHM] });
        this.signature = asn1.result[SIGNER_INFO_SIGNATURE];
        if (SIGNER_INFO_UNSIGNED_ATTRS in asn1.result)
            this.unsignedAttrs = new SignedAndUnsignedAttributes({ type: 1, schema: asn1.result[SIGNER_INFO_UNSIGNED_ATTRS] });
    }
    toSchema() {
        if (SignerInfo.compareWithDefault(SID, this.sid))
            throw new Error("Incorrectly initialized \"SignerInfo\" class");
        const outputArray = [];
        outputArray.push(new Integer({ value: this.version }));
        if (this.sid instanceof IssuerAndSerialNumber)
            outputArray.push(this.sid.toSchema());
        else
            outputArray.push(this.sid);
        outputArray.push(this.digestAlgorithm.toSchema());
        if (this.signedAttrs) {
            if (SignerInfo.compareWithDefault(SIGNED_ATTRS, this.signedAttrs) === false)
                outputArray.push(this.signedAttrs.toSchema());
        }
        outputArray.push(this.signatureAlgorithm.toSchema());
        outputArray.push(this.signature);
        if (this.unsignedAttrs) {
            if (SignerInfo.compareWithDefault(UNSIGNED_ATTRS, this.unsignedAttrs) === false)
                outputArray.push(this.unsignedAttrs.toSchema());
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        if (SignerInfo.compareWithDefault(SID, this.sid)) {
            throw new Error("Incorrectly initialized \"SignerInfo\" class");
        }
        const res = {
            version: this.version,
            digestAlgorithm: this.digestAlgorithm.toJSON(),
            signatureAlgorithm: this.signatureAlgorithm.toJSON(),
            signature: this.signature.toJSON(),
        };
        if (!(this.sid instanceof Any))
            res.sid = this.sid.toJSON();
        if (this.signedAttrs && SignerInfo.compareWithDefault(SIGNED_ATTRS, this.signedAttrs) === false) {
            res.signedAttrs = this.signedAttrs.toJSON();
        }
        if (this.unsignedAttrs && SignerInfo.compareWithDefault(UNSIGNED_ATTRS, this.unsignedAttrs) === false) {
            res.unsignedAttrs = this.unsignedAttrs.toJSON();
        }
        return res;
    }
}
SignerInfo.CLASS_NAME = "SignerInfo";

const VERSION$3 = "version";
const POLICY = "policy";
const MESSAGE_IMPRINT$1 = "messageImprint";
const SERIAL_NUMBER = "serialNumber";
const GEN_TIME = "genTime";
const ORDERING = "ordering";
const NONCE$1 = "nonce";
const ACCURACY = "accuracy";
const TSA = "tsa";
const EXTENSIONS$1 = "extensions";
const TST_INFO = "TSTInfo";
const TST_INFO_VERSION = `${TST_INFO}.${VERSION$3}`;
const TST_INFO_POLICY = `${TST_INFO}.${POLICY}`;
const TST_INFO_MESSAGE_IMPRINT = `${TST_INFO}.${MESSAGE_IMPRINT$1}`;
const TST_INFO_SERIAL_NUMBER = `${TST_INFO}.${SERIAL_NUMBER}`;
const TST_INFO_GEN_TIME = `${TST_INFO}.${GEN_TIME}`;
const TST_INFO_ACCURACY = `${TST_INFO}.${ACCURACY}`;
const TST_INFO_ORDERING = `${TST_INFO}.${ORDERING}`;
const TST_INFO_NONCE = `${TST_INFO}.${NONCE$1}`;
const TST_INFO_TSA = `${TST_INFO}.${TSA}`;
const TST_INFO_EXTENSIONS = `${TST_INFO}.${EXTENSIONS$1}`;
const CLEAR_PROPS$4 = [
    TST_INFO_VERSION,
    TST_INFO_POLICY,
    TST_INFO_MESSAGE_IMPRINT,
    TST_INFO_SERIAL_NUMBER,
    TST_INFO_GEN_TIME,
    TST_INFO_ACCURACY,
    TST_INFO_ORDERING,
    TST_INFO_NONCE,
    TST_INFO_TSA,
    TST_INFO_EXTENSIONS
];
class TSTInfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.version = getParametersValue(parameters, VERSION$3, TSTInfo.defaultValues(VERSION$3));
        this.policy = getParametersValue(parameters, POLICY, TSTInfo.defaultValues(POLICY));
        this.messageImprint = getParametersValue(parameters, MESSAGE_IMPRINT$1, TSTInfo.defaultValues(MESSAGE_IMPRINT$1));
        this.serialNumber = getParametersValue(parameters, SERIAL_NUMBER, TSTInfo.defaultValues(SERIAL_NUMBER));
        this.genTime = getParametersValue(parameters, GEN_TIME, TSTInfo.defaultValues(GEN_TIME));
        if (ACCURACY in parameters) {
            this.accuracy = getParametersValue(parameters, ACCURACY, TSTInfo.defaultValues(ACCURACY));
        }
        if (ORDERING in parameters) {
            this.ordering = getParametersValue(parameters, ORDERING, TSTInfo.defaultValues(ORDERING));
        }
        if (NONCE$1 in parameters) {
            this.nonce = getParametersValue(parameters, NONCE$1, TSTInfo.defaultValues(NONCE$1));
        }
        if (TSA in parameters) {
            this.tsa = getParametersValue(parameters, TSA, TSTInfo.defaultValues(TSA));
        }
        if (EXTENSIONS$1 in parameters) {
            this.extensions = getParametersValue(parameters, EXTENSIONS$1, TSTInfo.defaultValues(EXTENSIONS$1));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VERSION$3:
                return 0;
            case POLICY:
                return EMPTY_STRING;
            case MESSAGE_IMPRINT$1:
                return new MessageImprint();
            case SERIAL_NUMBER:
                return new Integer();
            case GEN_TIME:
                return new Date(0, 0, 0);
            case ACCURACY:
                return new Accuracy();
            case ORDERING:
                return false;
            case NONCE$1:
                return new Integer();
            case TSA:
                return new GeneralName();
            case EXTENSIONS$1:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case VERSION$3:
            case POLICY:
            case GEN_TIME:
            case ORDERING:
                return (memberValue === TSTInfo.defaultValues(ORDERING));
            case MESSAGE_IMPRINT$1:
                return ((MessageImprint.compareWithDefault(HASH_ALGORITHM, memberValue.hashAlgorithm)) &&
                    (MessageImprint.compareWithDefault(HASHED_MESSAGE, memberValue.hashedMessage)));
            case SERIAL_NUMBER:
            case NONCE$1:
                return (memberValue.isEqual(TSTInfo.defaultValues(NONCE$1)));
            case ACCURACY:
                return ((Accuracy.compareWithDefault(SECONDS, memberValue.seconds)) &&
                    (Accuracy.compareWithDefault(MILLIS, memberValue.millis)) &&
                    (Accuracy.compareWithDefault(MICROS, memberValue.micros)));
            case TSA:
                return ((GeneralName.compareWithDefault(TYPE$4, memberValue.type)) &&
                    (GeneralName.compareWithDefault(VALUE$5, memberValue.value)));
            case EXTENSIONS$1:
                return (memberValue.length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || TST_INFO),
            value: [
                new Integer({ name: (names.version || TST_INFO_VERSION) }),
                new ObjectIdentifier({ name: (names.policy || TST_INFO_POLICY) }),
                MessageImprint.schema(names.messageImprint || {
                    names: {
                        blockName: TST_INFO_MESSAGE_IMPRINT
                    }
                }),
                new Integer({ name: (names.serialNumber || TST_INFO_SERIAL_NUMBER) }),
                new GeneralizedTime({ name: (names.genTime || TST_INFO_GEN_TIME) }),
                Accuracy.schema(names.accuracy || {
                    names: {
                        blockName: TST_INFO_ACCURACY
                    }
                }),
                new Boolean({
                    name: (names.ordering || TST_INFO_ORDERING),
                    optional: true
                }),
                new Integer({
                    name: (names.nonce || TST_INFO_NONCE),
                    optional: true
                }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [GeneralName.schema(names.tsa || {
                            names: {
                                blockName: TST_INFO_TSA
                            }
                        })]
                }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: [
                        new Repeated({
                            name: (names.extensions || TST_INFO_EXTENSIONS),
                            value: Extension.schema(names.extension || {})
                        })
                    ]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$4);
        const asn1 = compareSchema(schema, schema, TSTInfo.schema());
        AsnError.assertSchema(asn1, this.className);
        this.version = asn1.result[TST_INFO_VERSION].valueBlock.valueDec;
        this.policy = asn1.result[TST_INFO_POLICY].valueBlock.toString();
        this.messageImprint = new MessageImprint({ schema: asn1.result[TST_INFO_MESSAGE_IMPRINT] });
        this.serialNumber = asn1.result[TST_INFO_SERIAL_NUMBER];
        this.genTime = asn1.result[TST_INFO_GEN_TIME].toDate();
        if (TST_INFO_ACCURACY in asn1.result)
            this.accuracy = new Accuracy({ schema: asn1.result[TST_INFO_ACCURACY] });
        if (TST_INFO_ORDERING in asn1.result)
            this.ordering = asn1.result[TST_INFO_ORDERING].valueBlock.value;
        if (TST_INFO_NONCE in asn1.result)
            this.nonce = asn1.result[TST_INFO_NONCE];
        if (TST_INFO_TSA in asn1.result)
            this.tsa = new GeneralName({ schema: asn1.result[TST_INFO_TSA] });
        if (TST_INFO_EXTENSIONS in asn1.result)
            this.extensions = Array.from(asn1.result[TST_INFO_EXTENSIONS], element => new Extension({ schema: element }));
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(new Integer({ value: this.version }));
        outputArray.push(new ObjectIdentifier({ value: this.policy }));
        outputArray.push(this.messageImprint.toSchema());
        outputArray.push(this.serialNumber);
        outputArray.push(new GeneralizedTime({ valueDate: this.genTime }));
        if (this.accuracy)
            outputArray.push(this.accuracy.toSchema());
        if (this.ordering !== undefined)
            outputArray.push(new Boolean({ value: this.ordering }));
        if (this.nonce)
            outputArray.push(this.nonce);
        if (this.tsa) {
            outputArray.push(new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: [this.tsa.toSchema()]
            }));
        }
        if (this.extensions) {
            outputArray.push(new Constructed({
                optional: true,
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                value: Array.from(this.extensions, o => o.toSchema())
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            version: this.version,
            policy: this.policy,
            messageImprint: this.messageImprint.toJSON(),
            serialNumber: this.serialNumber.toJSON(),
            genTime: this.genTime
        };
        if (this.accuracy)
            res.accuracy = this.accuracy.toJSON();
        if (this.ordering !== undefined)
            res.ordering = this.ordering;
        if (this.nonce)
            res.nonce = this.nonce.toJSON();
        if (this.tsa)
            res.tsa = this.tsa.toJSON();
        if (this.extensions)
            res.extensions = Array.from(this.extensions, o => o.toJSON());
        return res;
    }
    verify(params, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!params.data) {
                throw new Error("\"data\" is a mandatory attribute for TST_INFO verification");
            }
            const data = params.data;
            if (params.notBefore) {
                if (this.genTime < params.notBefore)
                    throw new Error("Generation time for TSTInfo object is less than notBefore value");
            }
            if (params.notAfter) {
                if (this.genTime > params.notAfter)
                    throw new Error("Generation time for TSTInfo object is more than notAfter value");
            }
            const shaAlgorithm = crypto.getAlgorithmByOID(this.messageImprint.hashAlgorithm.algorithmId, true, "MessageImprint.hashAlgorithm");
            const hash = yield crypto.digest(shaAlgorithm.name, new Uint8Array(data));
            return BufferSourceConverter.isEqual(hash, this.messageImprint.hashedMessage.valueBlock.valueHexView);
        });
    }
}
TSTInfo.CLASS_NAME = "TSTInfo";

const VERSION$2 = "version";
const DIGEST_ALGORITHMS = "digestAlgorithms";
const ENCAP_CONTENT_INFO = "encapContentInfo";
const CERTIFICATES = "certificates";
const CRLS = "crls";
const SIGNER_INFOS = "signerInfos";
const OCSPS = "ocsps";
const SIGNED_DATA = "SignedData";
const SIGNED_DATA_VERSION = `${SIGNED_DATA}.${VERSION$2}`;
const SIGNED_DATA_DIGEST_ALGORITHMS = `${SIGNED_DATA}.${DIGEST_ALGORITHMS}`;
const SIGNED_DATA_ENCAP_CONTENT_INFO = `${SIGNED_DATA}.${ENCAP_CONTENT_INFO}`;
const SIGNED_DATA_CERTIFICATES = `${SIGNED_DATA}.${CERTIFICATES}`;
const SIGNED_DATA_CRLS = `${SIGNED_DATA}.${CRLS}`;
const SIGNED_DATA_SIGNER_INFOS = `${SIGNED_DATA}.${SIGNER_INFOS}`;
const CLEAR_PROPS$3 = [
    SIGNED_DATA_VERSION,
    SIGNED_DATA_DIGEST_ALGORITHMS,
    SIGNED_DATA_ENCAP_CONTENT_INFO,
    SIGNED_DATA_CERTIFICATES,
    SIGNED_DATA_CRLS,
    SIGNED_DATA_SIGNER_INFOS
];
class SignedDataVerifyError extends Error {
    constructor({ message, code = 0, date = new Date(), signatureVerified = null, signerCertificate = null, signerCertificateVerified = null, timestampSerial = null, certificatePath = [], }) {
        super(message);
        this.name = "SignedDataVerifyError";
        this.date = date;
        this.code = code;
        this.timestampSerial = timestampSerial;
        this.signatureVerified = signatureVerified;
        this.signerCertificate = signerCertificate;
        this.signerCertificateVerified = signerCertificateVerified;
        this.certificatePath = certificatePath;
    }
}
class SignedData extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.version = getParametersValue(parameters, VERSION$2, SignedData.defaultValues(VERSION$2));
        this.digestAlgorithms = getParametersValue(parameters, DIGEST_ALGORITHMS, SignedData.defaultValues(DIGEST_ALGORITHMS));
        this.encapContentInfo = getParametersValue(parameters, ENCAP_CONTENT_INFO, SignedData.defaultValues(ENCAP_CONTENT_INFO));
        if (CERTIFICATES in parameters) {
            this.certificates = getParametersValue(parameters, CERTIFICATES, SignedData.defaultValues(CERTIFICATES));
        }
        if (CRLS in parameters) {
            this.crls = getParametersValue(parameters, CRLS, SignedData.defaultValues(CRLS));
        }
        if (OCSPS in parameters) {
            this.ocsps = getParametersValue(parameters, OCSPS, SignedData.defaultValues(OCSPS));
        }
        this.signerInfos = getParametersValue(parameters, SIGNER_INFOS, SignedData.defaultValues(SIGNER_INFOS));
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VERSION$2:
                return 0;
            case DIGEST_ALGORITHMS:
                return [];
            case ENCAP_CONTENT_INFO:
                return new EncapsulatedContentInfo();
            case CERTIFICATES:
                return [];
            case CRLS:
                return [];
            case OCSPS:
                return [];
            case SIGNER_INFOS:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case VERSION$2:
                return (memberValue === SignedData.defaultValues(VERSION$2));
            case ENCAP_CONTENT_INFO:
                return EncapsulatedContentInfo.compareWithDefault("eContentType", memberValue.eContentType) &&
                    EncapsulatedContentInfo.compareWithDefault("eContent", memberValue.eContent);
            case DIGEST_ALGORITHMS:
            case CERTIFICATES:
            case CRLS:
            case OCSPS:
            case SIGNER_INFOS:
                return (memberValue.length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        if (names.optional === undefined) {
            names.optional = false;
        }
        return (new Sequence({
            name: (names.blockName || SIGNED_DATA),
            optional: names.optional,
            value: [
                new Integer({ name: (names.version || SIGNED_DATA_VERSION) }),
                new Set({
                    value: [
                        new Repeated({
                            name: (names.digestAlgorithms || SIGNED_DATA_DIGEST_ALGORITHMS),
                            value: AlgorithmIdentifier.schema()
                        })
                    ]
                }),
                EncapsulatedContentInfo.schema(names.encapContentInfo || {
                    names: {
                        blockName: SIGNED_DATA_ENCAP_CONTENT_INFO
                    }
                }),
                new Constructed({
                    name: (names.certificates || SIGNED_DATA_CERTIFICATES),
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: CertificateSet.schema().valueBlock.value
                }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 1
                    },
                    value: RevocationInfoChoices.schema(names.crls || {
                        names: {
                            crls: SIGNED_DATA_CRLS
                        }
                    }).valueBlock.value
                }),
                new Set({
                    value: [
                        new Repeated({
                            name: (names.signerInfos || SIGNED_DATA_SIGNER_INFOS),
                            value: SignerInfo.schema()
                        })
                    ]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$3);
        const asn1 = compareSchema(schema, schema, SignedData.schema());
        AsnError.assertSchema(asn1, this.className);
        this.version = asn1.result[SIGNED_DATA_VERSION].valueBlock.valueDec;
        if (SIGNED_DATA_DIGEST_ALGORITHMS in asn1.result)
            this.digestAlgorithms = Array.from(asn1.result[SIGNED_DATA_DIGEST_ALGORITHMS], algorithm => new AlgorithmIdentifier({ schema: algorithm }));
        this.encapContentInfo = new EncapsulatedContentInfo({ schema: asn1.result[SIGNED_DATA_ENCAP_CONTENT_INFO] });
        if (SIGNED_DATA_CERTIFICATES in asn1.result) {
            const certificateSet = new CertificateSet({
                schema: new Set({
                    value: asn1.result[SIGNED_DATA_CERTIFICATES].valueBlock.value
                })
            });
            this.certificates = certificateSet.certificates.slice(0);
        }
        if (SIGNED_DATA_CRLS in asn1.result) {
            this.crls = Array.from(asn1.result[SIGNED_DATA_CRLS], (crl) => {
                if (crl.idBlock.tagClass === 1)
                    return new CertificateRevocationList({ schema: crl });
                crl.idBlock.tagClass = 1;
                crl.idBlock.tagNumber = 16;
                return new OtherRevocationInfoFormat({ schema: crl });
            });
        }
        if (SIGNED_DATA_SIGNER_INFOS in asn1.result)
            this.signerInfos = Array.from(asn1.result[SIGNED_DATA_SIGNER_INFOS], signerInfoSchema => new SignerInfo({ schema: signerInfoSchema }));
    }
    toSchema(encodeFlag = false) {
        const outputArray = [];
        if ((this.certificates && this.certificates.length && this.certificates.some(o => o instanceof OtherCertificateFormat))
            || (this.crls && this.crls.length && this.crls.some(o => o instanceof OtherRevocationInfoFormat))) {
            this.version = 5;
        }
        else if (this.certificates && this.certificates.length && this.certificates.some(o => o instanceof AttributeCertificateV2)) {
            this.version = 4;
        }
        else if ((this.certificates && this.certificates.length && this.certificates.some(o => o instanceof AttributeCertificateV1))
            || this.signerInfos.some(o => o.version === 3)
            || this.encapContentInfo.eContentType !== SignedData.ID_DATA) {
            this.version = 3;
        }
        else {
            this.version = 1;
        }
        outputArray.push(new Integer({ value: this.version }));
        outputArray.push(new Set({
            value: Array.from(this.digestAlgorithms, algorithm => algorithm.toSchema())
        }));
        outputArray.push(this.encapContentInfo.toSchema());
        if (this.certificates) {
            const certificateSet = new CertificateSet({ certificates: this.certificates });
            const certificateSetSchema = certificateSet.toSchema();
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: certificateSetSchema.valueBlock.value
            }));
        }
        if (this.crls) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 1
                },
                value: Array.from(this.crls, crl => {
                    if (crl instanceof OtherRevocationInfoFormat) {
                        const crlSchema = crl.toSchema();
                        crlSchema.idBlock.tagClass = 3;
                        crlSchema.idBlock.tagNumber = 1;
                        return crlSchema;
                    }
                    return crl.toSchema(encodeFlag);
                })
            }));
        }
        outputArray.push(new Set({
            value: Array.from(this.signerInfos, signerInfo => signerInfo.toSchema())
        }));
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            version: this.version,
            digestAlgorithms: Array.from(this.digestAlgorithms, algorithm => algorithm.toJSON()),
            encapContentInfo: this.encapContentInfo.toJSON(),
            signerInfos: Array.from(this.signerInfos, signerInfo => signerInfo.toJSON()),
        };
        if (this.certificates) {
            res.certificates = Array.from(this.certificates, certificate => certificate.toJSON());
        }
        if (this.crls) {
            res.crls = Array.from(this.crls, crl => crl.toJSON());
        }
        return res;
    }
    verify({ signer = (-1), data = (EMPTY_BUFFER), trustedCerts = [], checkDate = (new Date()), checkChain = false, passedWhenNotRevValues = false, extendedMode = false, findOrigin = null, findIssuer = null } = {}, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            let signerCert = null;
            let timestampSerial = null;
            try {
                let messageDigestValue = EMPTY_BUFFER;
                let shaAlgorithm = EMPTY_STRING;
                let certificatePath = [];
                const signerInfo = this.signerInfos[signer];
                if (!signerInfo) {
                    throw new SignedDataVerifyError({
                        date: checkDate,
                        code: 1,
                        message: "Unable to get signer by supplied index",
                    });
                }
                if (!this.certificates) {
                    throw new SignedDataVerifyError({
                        date: checkDate,
                        code: 2,
                        message: "No certificates attached to this signed data",
                    });
                }
                if (signerInfo.sid instanceof IssuerAndSerialNumber) {
                    for (const certificate of this.certificates) {
                        if (!(certificate instanceof Certificate))
                            continue;
                        if ((certificate.issuer.isEqual(signerInfo.sid.issuer)) &&
                            (certificate.serialNumber.isEqual(signerInfo.sid.serialNumber))) {
                            signerCert = certificate;
                            break;
                        }
                    }
                }
                else {
                    const sid = signerInfo.sid;
                    const keyId = sid.idBlock.isConstructed
                        ? sid.valueBlock.value[0].valueBlock.valueHex
                        : sid.valueBlock.valueHex;
                    for (const certificate of this.certificates) {
                        if (!(certificate instanceof Certificate)) {
                            continue;
                        }
                        const digest = yield crypto.digest({ name: "sha-1" }, certificate.subjectPublicKeyInfo.subjectPublicKey.valueBlock.valueHexView);
                        if (isEqualBuffer(digest, keyId)) {
                            signerCert = certificate;
                            break;
                        }
                    }
                }
                if (!signerCert) {
                    throw new SignedDataVerifyError({
                        date: checkDate,
                        code: 3,
                        message: "Unable to find signer certificate",
                    });
                }
                if (this.encapContentInfo.eContentType === id_eContentType_TSTInfo) {
                    if (!this.encapContentInfo.eContent) {
                        throw new SignedDataVerifyError({
                            date: checkDate,
                            code: 15,
                            message: "Error during verification: TSTInfo eContent is empty",
                            signatureVerified: null,
                            signerCertificate: signerCert,
                            timestampSerial,
                            signerCertificateVerified: true
                        });
                    }
                    let tstInfo;
                    try {
                        tstInfo = TSTInfo.fromBER(this.encapContentInfo.eContent.valueBlock.valueHexView);
                    }
                    catch (ex) {
                        throw new SignedDataVerifyError({
                            date: checkDate,
                            code: 15,
                            message: "Error during verification: TSTInfo wrong ASN.1 schema ",
                            signatureVerified: null,
                            signerCertificate: signerCert,
                            timestampSerial,
                            signerCertificateVerified: true
                        });
                    }
                    checkDate = tstInfo.genTime;
                    timestampSerial = tstInfo.serialNumber.valueBlock.valueHexView.slice();
                    if (data.byteLength === 0) {
                        throw new SignedDataVerifyError({
                            date: checkDate,
                            code: 4,
                            message: "Missed detached data input array",
                        });
                    }
                    if (!(yield tstInfo.verify({ data }, crypto))) {
                        throw new SignedDataVerifyError({
                            date: checkDate,
                            code: 15,
                            message: "Error during verification: TSTInfo verification is failed",
                            signatureVerified: false,
                            signerCertificate: signerCert,
                            timestampSerial,
                            signerCertificateVerified: true
                        });
                    }
                }
                if (checkChain) {
                    const certs = this.certificates.filter(certificate => (certificate instanceof Certificate && !!checkCA(certificate, signerCert)));
                    const chainParams = {
                        checkDate,
                        certs,
                        trustedCerts,
                    };
                    if (findIssuer) {
                        chainParams.findIssuer = findIssuer;
                    }
                    if (findOrigin) {
                        chainParams.findOrigin = findOrigin;
                    }
                    const chainEngine = new CertificateChainValidationEngine(chainParams);
                    chainEngine.certs.push(signerCert);
                    if (this.crls) {
                        for (const crl of this.crls) {
                            if ("thisUpdate" in crl)
                                chainEngine.crls.push(crl);
                            else {
                                if (crl.otherRevInfoFormat === id_PKIX_OCSP_Basic)
                                    chainEngine.ocsps.push(new BasicOCSPResponse({ schema: crl.otherRevInfo }));
                            }
                        }
                    }
                    if (this.ocsps) {
                        chainEngine.ocsps.push(...(this.ocsps));
                    }
                    const verificationResult = yield chainEngine.verify({ passedWhenNotRevValues }, crypto)
                        .catch(e => {
                        throw new SignedDataVerifyError({
                            date: checkDate,
                            code: 5,
                            message: `Validation of signer's certificate failed with error: ${((e instanceof Object) ? e.resultMessage : e)}`,
                            signerCertificate: signerCert,
                            signerCertificateVerified: false
                        });
                    });
                    if (verificationResult.certificatePath) {
                        certificatePath = verificationResult.certificatePath;
                    }
                    if (!verificationResult.result)
                        throw new SignedDataVerifyError({
                            date: checkDate,
                            code: 5,
                            message: `Validation of signer's certificate failed: ${verificationResult.resultMessage}`,
                            signerCertificate: signerCert,
                            signerCertificateVerified: false
                        });
                }
                const signerInfoHashAlgorithm = crypto.getAlgorithmByOID(signerInfo.digestAlgorithm.algorithmId);
                if (!("name" in signerInfoHashAlgorithm)) {
                    throw new SignedDataVerifyError({
                        date: checkDate,
                        code: 7,
                        message: `Unsupported signature algorithm: ${signerInfo.digestAlgorithm.algorithmId}`,
                        signerCertificate: signerCert,
                        signerCertificateVerified: true
                    });
                }
                shaAlgorithm = signerInfoHashAlgorithm.name;
                const eContent = this.encapContentInfo.eContent;
                if (eContent) {
                    if ((eContent.idBlock.tagClass === 1) &&
                        (eContent.idBlock.tagNumber === 4)) {
                        data = eContent.getValue();
                    }
                    else
                        data = eContent.valueBlock.valueBeforeDecodeView;
                }
                else {
                    if (data.byteLength === 0) {
                        throw new SignedDataVerifyError({
                            date: checkDate,
                            code: 8,
                            message: "Missed detached data input array",
                            signerCertificate: signerCert,
                            signerCertificateVerified: true
                        });
                    }
                }
                if (signerInfo.signedAttrs) {
                    let foundContentType = false;
                    let foundMessageDigest = false;
                    for (const attribute of signerInfo.signedAttrs.attributes) {
                        if (attribute.type === "1.2.840.113549.1.9.3")
                            foundContentType = true;
                        if (attribute.type === "1.2.840.113549.1.9.4") {
                            foundMessageDigest = true;
                            messageDigestValue = attribute.values[0].valueBlock.valueHex;
                        }
                        if (foundContentType && foundMessageDigest)
                            break;
                    }
                    if (foundContentType === false) {
                        throw new SignedDataVerifyError({
                            date: checkDate,
                            code: 9,
                            message: "Attribute \"content-type\" is a mandatory attribute for \"signed attributes\"",
                            signerCertificate: signerCert,
                            signerCertificateVerified: true
                        });
                    }
                    if (foundMessageDigest === false) {
                        throw new SignedDataVerifyError({
                            date: checkDate,
                            code: 10,
                            message: "Attribute \"message-digest\" is a mandatory attribute for \"signed attributes\"",
                            signatureVerified: null,
                            signerCertificate: signerCert,
                            signerCertificateVerified: true
                        });
                    }
                }
                if (signerInfo.signedAttrs) {
                    const messageDigest = yield crypto.digest(shaAlgorithm, new Uint8Array(data));
                    if (!isEqualBuffer(messageDigest, messageDigestValue)) {
                        throw new SignedDataVerifyError({
                            date: checkDate,
                            code: 15,
                            message: "Error during verification: Message digest doesn't match",
                            signatureVerified: null,
                            signerCertificate: signerCert,
                            timestampSerial,
                            signerCertificateVerified: true
                        });
                    }
                    data = signerInfo.signedAttrs.encodedValue;
                }
                const verifyResult = yield crypto.verifyWithPublicKey(data, signerInfo.signature, signerCert.subjectPublicKeyInfo, signerCert.signatureAlgorithm, shaAlgorithm);
                if (extendedMode) {
                    return {
                        date: checkDate,
                        code: 14,
                        message: EMPTY_STRING,
                        signatureVerified: verifyResult,
                        signerCertificate: signerCert,
                        timestampSerial,
                        signerCertificateVerified: true,
                        certificatePath
                    };
                }
                else {
                    return verifyResult;
                }
            }
            catch (e) {
                if (e instanceof SignedDataVerifyError) {
                    throw e;
                }
                throw new SignedDataVerifyError({
                    date: checkDate,
                    code: 15,
                    message: `Error during verification: ${e instanceof Error ? e.message : e}`,
                    signatureVerified: null,
                    signerCertificate: signerCert,
                    timestampSerial,
                    signerCertificateVerified: true
                });
            }
        });
    }
    sign(privateKey, signerIndex, hashAlgorithm = "SHA-1", data = (EMPTY_BUFFER), crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!privateKey)
                throw new Error("Need to provide a private key for signing");
            const hashAlgorithmOID = crypto.getOIDByAlgorithm({ name: hashAlgorithm }, true, "hashAlgorithm");
            if ((this.digestAlgorithms.filter(algorithm => algorithm.algorithmId === hashAlgorithmOID)).length === 0) {
                this.digestAlgorithms.push(new AlgorithmIdentifier({
                    algorithmId: hashAlgorithmOID,
                    algorithmParams: new Null()
                }));
            }
            const signerInfo = this.signerInfos[signerIndex];
            if (!signerInfo) {
                throw new RangeError("SignerInfo index is out of range");
            }
            signerInfo.digestAlgorithm = new AlgorithmIdentifier({
                algorithmId: hashAlgorithmOID,
                algorithmParams: new Null()
            });
            const signatureParams = yield crypto.getSignatureParameters(privateKey, hashAlgorithm);
            const parameters = signatureParams.parameters;
            signerInfo.signatureAlgorithm = signatureParams.signatureAlgorithm;
            if (signerInfo.signedAttrs) {
                if (signerInfo.signedAttrs.encodedValue.byteLength !== 0)
                    data = signerInfo.signedAttrs.encodedValue;
                else {
                    data = signerInfo.signedAttrs.toSchema().toBER();
                    const view = BufferSourceConverter.toUint8Array(data);
                    view[0] = 0x31;
                }
            }
            else {
                const eContent = this.encapContentInfo.eContent;
                if (eContent) {
                    if ((eContent.idBlock.tagClass === 1) &&
                        (eContent.idBlock.tagNumber === 4)) {
                        data = eContent.getValue();
                    }
                    else
                        data = eContent.valueBlock.valueBeforeDecodeView;
                }
                else {
                    if (data.byteLength === 0)
                        throw new Error("Missed detached data input array");
                }
            }
            const signature = yield crypto.signWithPrivateKey(data, privateKey, parameters);
            signerInfo.signature = new OctetString({ valueHex: signature });
        });
    }
}
SignedData.CLASS_NAME = "SignedData";
SignedData.ID_DATA = id_ContentType_Data;

const VERSION$1 = "version";
const AUTH_SAFE = "authSafe";
const MAC_DATA = "macData";
const PARSED_VALUE = "parsedValue";
const CLERA_PROPS = [
    VERSION$1,
    AUTH_SAFE,
    MAC_DATA
];
class PFX extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.version = getParametersValue(parameters, VERSION$1, PFX.defaultValues(VERSION$1));
        this.authSafe = getParametersValue(parameters, AUTH_SAFE, PFX.defaultValues(AUTH_SAFE));
        if (MAC_DATA in parameters) {
            this.macData = getParametersValue(parameters, MAC_DATA, PFX.defaultValues(MAC_DATA));
        }
        if (PARSED_VALUE in parameters) {
            this.parsedValue = getParametersValue(parameters, PARSED_VALUE, PFX.defaultValues(PARSED_VALUE));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VERSION$1:
                return 3;
            case AUTH_SAFE:
                return (new ContentInfo());
            case MAC_DATA:
                return (new MacData());
            case PARSED_VALUE:
                return {};
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case VERSION$1:
                return (memberValue === PFX.defaultValues(memberName));
            case AUTH_SAFE:
                return ((ContentInfo.compareWithDefault("contentType", memberValue.contentType)) &&
                    (ContentInfo.compareWithDefault("content", memberValue.content)));
            case MAC_DATA:
                return ((MacData.compareWithDefault("mac", memberValue.mac)) &&
                    (MacData.compareWithDefault("macSalt", memberValue.macSalt)) &&
                    (MacData.compareWithDefault("iterations", memberValue.iterations)));
            case PARSED_VALUE:
                return ((memberValue instanceof Object) && (Object.keys(memberValue).length === 0));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Integer({ name: (names.version || VERSION$1) }),
                ContentInfo.schema(names.authSafe || {
                    names: {
                        blockName: AUTH_SAFE
                    }
                }),
                MacData.schema(names.macData || {
                    names: {
                        blockName: MAC_DATA,
                        optional: true
                    }
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLERA_PROPS);
        const asn1 = compareSchema(schema, schema, PFX.schema({
            names: {
                version: VERSION$1,
                authSafe: {
                    names: {
                        blockName: AUTH_SAFE
                    }
                },
                macData: {
                    names: {
                        blockName: MAC_DATA
                    }
                }
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        this.version = asn1.result.version.valueBlock.valueDec;
        this.authSafe = new ContentInfo({ schema: asn1.result.authSafe });
        if (MAC_DATA in asn1.result)
            this.macData = new MacData({ schema: asn1.result.macData });
    }
    toSchema() {
        const outputArray = [
            new Integer({ value: this.version }),
            this.authSafe.toSchema()
        ];
        if (this.macData) {
            outputArray.push(this.macData.toSchema());
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const output = {
            version: this.version,
            authSafe: this.authSafe.toJSON()
        };
        if (this.macData) {
            output.macData = this.macData.toJSON();
        }
        return output;
    }
    makeInternalValues(parameters = {}, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            ArgumentError.assert(parameters, "parameters", "object");
            if (!this.parsedValue) {
                throw new Error("Please call \"parseValues\" function first in order to make \"parsedValue\" data");
            }
            ParameterError.assertEmpty(this.parsedValue.integrityMode, "integrityMode", "parsedValue");
            ParameterError.assertEmpty(this.parsedValue.authenticatedSafe, "authenticatedSafe", "parsedValue");
            switch (this.parsedValue.integrityMode) {
                case 0:
                    {
                        if (!("iterations" in parameters))
                            throw new ParameterError("iterations");
                        ParameterError.assertEmpty(parameters.pbkdf2HashAlgorithm, "pbkdf2HashAlgorithm");
                        ParameterError.assertEmpty(parameters.hmacHashAlgorithm, "hmacHashAlgorithm");
                        ParameterError.assertEmpty(parameters.password, "password");
                        const saltBuffer = new ArrayBuffer(64);
                        const saltView = new Uint8Array(saltBuffer);
                        crypto.getRandomValues(saltView);
                        const data = this.parsedValue.authenticatedSafe.toSchema().toBER(false);
                        this.authSafe = new ContentInfo({
                            contentType: ContentInfo.DATA,
                            content: new OctetString({ valueHex: data })
                        });
                        const result = yield crypto.stampDataWithPassword({
                            password: parameters.password,
                            hashAlgorithm: parameters.hmacHashAlgorithm,
                            salt: saltBuffer,
                            iterationCount: parameters.iterations,
                            contentToStamp: data
                        });
                        this.macData = new MacData({
                            mac: new DigestInfo({
                                digestAlgorithm: new AlgorithmIdentifier({
                                    algorithmId: crypto.getOIDByAlgorithm({ name: parameters.hmacHashAlgorithm }, true, "hmacHashAlgorithm"),
                                }),
                                digest: new OctetString({ valueHex: result })
                            }),
                            macSalt: new OctetString({ valueHex: saltBuffer }),
                            iterations: parameters.iterations
                        });
                    }
                    break;
                case 1:
                    {
                        if (!("signingCertificate" in parameters)) {
                            throw new ParameterError("signingCertificate");
                        }
                        ParameterError.assertEmpty(parameters.privateKey, "privateKey");
                        ParameterError.assertEmpty(parameters.hashAlgorithm, "hashAlgorithm");
                        const toBeSigned = this.parsedValue.authenticatedSafe.toSchema().toBER(false);
                        const cmsSigned = new SignedData({
                            version: 1,
                            encapContentInfo: new EncapsulatedContentInfo({
                                eContentType: "1.2.840.113549.1.7.1",
                                eContent: new OctetString({ valueHex: toBeSigned })
                            }),
                            certificates: [parameters.signingCertificate]
                        });
                        const result = yield crypto.digest({ name: parameters.hashAlgorithm }, new Uint8Array(toBeSigned));
                        const signedAttr = [];
                        signedAttr.push(new Attribute({
                            type: "1.2.840.113549.1.9.3",
                            values: [
                                new ObjectIdentifier({ value: "1.2.840.113549.1.7.1" })
                            ]
                        }));
                        signedAttr.push(new Attribute({
                            type: "1.2.840.113549.1.9.5",
                            values: [
                                new UTCTime({ valueDate: new Date() })
                            ]
                        }));
                        signedAttr.push(new Attribute({
                            type: "1.2.840.113549.1.9.4",
                            values: [
                                new OctetString({ valueHex: result })
                            ]
                        }));
                        cmsSigned.signerInfos.push(new SignerInfo({
                            version: 1,
                            sid: new IssuerAndSerialNumber({
                                issuer: parameters.signingCertificate.issuer,
                                serialNumber: parameters.signingCertificate.serialNumber
                            }),
                            signedAttrs: new SignedAndUnsignedAttributes({
                                type: 0,
                                attributes: signedAttr
                            })
                        }));
                        yield cmsSigned.sign(parameters.privateKey, 0, parameters.hashAlgorithm, undefined, crypto);
                        this.authSafe = new ContentInfo({
                            contentType: "1.2.840.113549.1.7.2",
                            content: cmsSigned.toSchema(true)
                        });
                    }
                    break;
                default:
                    throw new Error(`Parameter "integrityMode" has unknown value: ${this.parsedValue.integrityMode}`);
            }
        });
    }
    parseInternalValues(parameters, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            ArgumentError.assert(parameters, "parameters", "object");
            if (parameters.checkIntegrity === undefined) {
                parameters.checkIntegrity = true;
            }
            this.parsedValue = {};
            switch (this.authSafe.contentType) {
                case ContentInfo.DATA:
                    {
                        ParameterError.assertEmpty(parameters.password, "password");
                        this.parsedValue.integrityMode = 0;
                        ArgumentError.assert(this.authSafe.content, "authSafe.content", OctetString);
                        const authSafeContent = this.authSafe.content.getValue();
                        this.parsedValue.authenticatedSafe = AuthenticatedSafe.fromBER(authSafeContent);
                        if (parameters.checkIntegrity) {
                            if (!this.macData) {
                                throw new Error("Absent \"macData\" value, can not check PKCS#12 data integrity");
                            }
                            const hashAlgorithm = crypto.getAlgorithmByOID(this.macData.mac.digestAlgorithm.algorithmId, true, "digestAlgorithm");
                            const result = yield crypto.verifyDataStampedWithPassword({
                                password: parameters.password,
                                hashAlgorithm: hashAlgorithm.name,
                                salt: BufferSourceConverter.toArrayBuffer(this.macData.macSalt.valueBlock.valueHexView),
                                iterationCount: this.macData.iterations || 1,
                                contentToVerify: authSafeContent,
                                signatureToVerify: BufferSourceConverter.toArrayBuffer(this.macData.mac.digest.valueBlock.valueHexView),
                            });
                            if (!result) {
                                throw new Error("Integrity for the PKCS#12 data is broken!");
                            }
                        }
                    }
                    break;
                case ContentInfo.SIGNED_DATA:
                    {
                        this.parsedValue.integrityMode = 1;
                        const cmsSigned = new SignedData({ schema: this.authSafe.content });
                        const eContent = cmsSigned.encapContentInfo.eContent;
                        ParameterError.assert(eContent, "eContent", "cmsSigned.encapContentInfo");
                        ArgumentError.assert(eContent, "eContent", OctetString);
                        const data = eContent.getValue();
                        this.parsedValue.authenticatedSafe = AuthenticatedSafe.fromBER(data);
                        const ok = yield cmsSigned.verify({ signer: 0, checkChain: false }, crypto);
                        if (!ok) {
                            throw new Error("Integrity for the PKCS#12 data is broken!");
                        }
                    }
                    break;
                default:
                    throw new Error(`Incorrect value for "this.authSafe.contentType": ${this.authSafe.contentType}`);
            }
        });
    }
}
PFX.CLASS_NAME = "PFX";

const STATUS$1 = "status";
const STATUS_STRINGS = "statusStrings";
const FAIL_INFO = "failInfo";
const CLEAR_PROPS$2 = [
    STATUS$1,
    STATUS_STRINGS,
    FAIL_INFO
];
var PKIStatus;
(function (PKIStatus) {
    PKIStatus[PKIStatus["granted"] = 0] = "granted";
    PKIStatus[PKIStatus["grantedWithMods"] = 1] = "grantedWithMods";
    PKIStatus[PKIStatus["rejection"] = 2] = "rejection";
    PKIStatus[PKIStatus["waiting"] = 3] = "waiting";
    PKIStatus[PKIStatus["revocationWarning"] = 4] = "revocationWarning";
    PKIStatus[PKIStatus["revocationNotification"] = 5] = "revocationNotification";
})(PKIStatus || (PKIStatus = {}));
class PKIStatusInfo extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.status = getParametersValue(parameters, STATUS$1, PKIStatusInfo.defaultValues(STATUS$1));
        if (STATUS_STRINGS in parameters) {
            this.statusStrings = getParametersValue(parameters, STATUS_STRINGS, PKIStatusInfo.defaultValues(STATUS_STRINGS));
        }
        if (FAIL_INFO in parameters) {
            this.failInfo = getParametersValue(parameters, FAIL_INFO, PKIStatusInfo.defaultValues(FAIL_INFO));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case STATUS$1:
                return 2;
            case STATUS_STRINGS:
                return [];
            case FAIL_INFO:
                return new BitString();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case STATUS$1:
                return (memberValue === PKIStatusInfo.defaultValues(memberName));
            case STATUS_STRINGS:
                return (memberValue.length === 0);
            case FAIL_INFO:
                return (memberValue.isEqual(PKIStatusInfo.defaultValues(memberName)));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || EMPTY_STRING),
            value: [
                new Integer({ name: (names.status || EMPTY_STRING) }),
                new Sequence({
                    optional: true,
                    value: [
                        new Repeated({
                            name: (names.statusStrings || EMPTY_STRING),
                            value: new Utf8String()
                        })
                    ]
                }),
                new BitString({
                    name: (names.failInfo || EMPTY_STRING),
                    optional: true
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$2);
        const asn1 = compareSchema(schema, schema, PKIStatusInfo.schema({
            names: {
                status: STATUS$1,
                statusStrings: STATUS_STRINGS,
                failInfo: FAIL_INFO
            }
        }));
        AsnError.assertSchema(asn1, this.className);
        const _status = asn1.result.status;
        if ((_status.valueBlock.isHexOnly === true) ||
            (_status.valueBlock.valueDec < 0) ||
            (_status.valueBlock.valueDec > 5))
            throw new Error("PKIStatusInfo \"status\" has invalid value");
        this.status = _status.valueBlock.valueDec;
        if (STATUS_STRINGS in asn1.result)
            this.statusStrings = asn1.result.statusStrings;
        if (FAIL_INFO in asn1.result)
            this.failInfo = asn1.result.failInfo;
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(new Integer({ value: this.status }));
        if (this.statusStrings) {
            outputArray.push(new Sequence({
                optional: true,
                value: this.statusStrings
            }));
        }
        if (this.failInfo) {
            outputArray.push(this.failInfo);
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            status: this.status
        };
        if (this.statusStrings) {
            res.statusStrings = Array.from(this.statusStrings, o => o.toJSON());
        }
        if (this.failInfo) {
            res.failInfo = this.failInfo.toJSON();
        }
        return res;
    }
}
PKIStatusInfo.CLASS_NAME = "PKIStatusInfo";

const VERSION = "version";
const MESSAGE_IMPRINT = "messageImprint";
const REQ_POLICY = "reqPolicy";
const NONCE = "nonce";
const CERT_REQ = "certReq";
const EXTENSIONS = "extensions";
const TIME_STAMP_REQ = "TimeStampReq";
const TIME_STAMP_REQ_VERSION = `${TIME_STAMP_REQ}.${VERSION}`;
const TIME_STAMP_REQ_MESSAGE_IMPRINT = `${TIME_STAMP_REQ}.${MESSAGE_IMPRINT}`;
const TIME_STAMP_REQ_POLICY = `${TIME_STAMP_REQ}.${REQ_POLICY}`;
const TIME_STAMP_REQ_NONCE = `${TIME_STAMP_REQ}.${NONCE}`;
const TIME_STAMP_REQ_CERT_REQ = `${TIME_STAMP_REQ}.${CERT_REQ}`;
const TIME_STAMP_REQ_EXTENSIONS = `${TIME_STAMP_REQ}.${EXTENSIONS}`;
const CLEAR_PROPS$1 = [
    TIME_STAMP_REQ_VERSION,
    TIME_STAMP_REQ_MESSAGE_IMPRINT,
    TIME_STAMP_REQ_POLICY,
    TIME_STAMP_REQ_NONCE,
    TIME_STAMP_REQ_CERT_REQ,
    TIME_STAMP_REQ_EXTENSIONS,
];
class TimeStampReq extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.version = getParametersValue(parameters, VERSION, TimeStampReq.defaultValues(VERSION));
        this.messageImprint = getParametersValue(parameters, MESSAGE_IMPRINT, TimeStampReq.defaultValues(MESSAGE_IMPRINT));
        if (REQ_POLICY in parameters) {
            this.reqPolicy = getParametersValue(parameters, REQ_POLICY, TimeStampReq.defaultValues(REQ_POLICY));
        }
        if (NONCE in parameters) {
            this.nonce = getParametersValue(parameters, NONCE, TimeStampReq.defaultValues(NONCE));
        }
        if (CERT_REQ in parameters) {
            this.certReq = getParametersValue(parameters, CERT_REQ, TimeStampReq.defaultValues(CERT_REQ));
        }
        if (EXTENSIONS in parameters) {
            this.extensions = getParametersValue(parameters, EXTENSIONS, TimeStampReq.defaultValues(EXTENSIONS));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case VERSION:
                return 0;
            case MESSAGE_IMPRINT:
                return new MessageImprint();
            case REQ_POLICY:
                return EMPTY_STRING;
            case NONCE:
                return new Integer();
            case CERT_REQ:
                return false;
            case EXTENSIONS:
                return [];
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case VERSION:
            case REQ_POLICY:
            case CERT_REQ:
                return (memberValue === TimeStampReq.defaultValues(memberName));
            case MESSAGE_IMPRINT:
                return ((MessageImprint.compareWithDefault("hashAlgorithm", memberValue.hashAlgorithm)) &&
                    (MessageImprint.compareWithDefault("hashedMessage", memberValue.hashedMessage)));
            case NONCE:
                return (memberValue.isEqual(TimeStampReq.defaultValues(memberName)));
            case EXTENSIONS:
                return (memberValue.length === 0);
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || TIME_STAMP_REQ),
            value: [
                new Integer({ name: (names.version || TIME_STAMP_REQ_VERSION) }),
                MessageImprint.schema(names.messageImprint || {
                    names: {
                        blockName: TIME_STAMP_REQ_MESSAGE_IMPRINT
                    }
                }),
                new ObjectIdentifier({
                    name: (names.reqPolicy || TIME_STAMP_REQ_POLICY),
                    optional: true
                }),
                new Integer({
                    name: (names.nonce || TIME_STAMP_REQ_NONCE),
                    optional: true
                }),
                new Boolean({
                    name: (names.certReq || TIME_STAMP_REQ_CERT_REQ),
                    optional: true
                }),
                new Constructed({
                    optional: true,
                    idBlock: {
                        tagClass: 3,
                        tagNumber: 0
                    },
                    value: [new Repeated({
                            name: (names.extensions || TIME_STAMP_REQ_EXTENSIONS),
                            value: Extension.schema()
                        })]
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS$1);
        const asn1 = compareSchema(schema, schema, TimeStampReq.schema());
        AsnError.assertSchema(asn1, this.className);
        this.version = asn1.result[TIME_STAMP_REQ_VERSION].valueBlock.valueDec;
        this.messageImprint = new MessageImprint({ schema: asn1.result[TIME_STAMP_REQ_MESSAGE_IMPRINT] });
        if (TIME_STAMP_REQ_POLICY in asn1.result)
            this.reqPolicy = asn1.result[TIME_STAMP_REQ_POLICY].valueBlock.toString();
        if (TIME_STAMP_REQ_NONCE in asn1.result)
            this.nonce = asn1.result[TIME_STAMP_REQ_NONCE];
        if (TIME_STAMP_REQ_CERT_REQ in asn1.result)
            this.certReq = asn1.result[TIME_STAMP_REQ_CERT_REQ].valueBlock.value;
        if (TIME_STAMP_REQ_EXTENSIONS in asn1.result)
            this.extensions = Array.from(asn1.result[TIME_STAMP_REQ_EXTENSIONS], element => new Extension({ schema: element }));
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(new Integer({ value: this.version }));
        outputArray.push(this.messageImprint.toSchema());
        if (this.reqPolicy)
            outputArray.push(new ObjectIdentifier({ value: this.reqPolicy }));
        if (this.nonce)
            outputArray.push(this.nonce);
        if ((CERT_REQ in this) && (TimeStampReq.compareWithDefault(CERT_REQ, this.certReq) === false))
            outputArray.push(new Boolean({ value: this.certReq }));
        if (this.extensions) {
            outputArray.push(new Constructed({
                idBlock: {
                    tagClass: 3,
                    tagNumber: 0
                },
                value: Array.from(this.extensions, o => o.toSchema())
            }));
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            version: this.version,
            messageImprint: this.messageImprint.toJSON()
        };
        if (this.reqPolicy !== undefined)
            res.reqPolicy = this.reqPolicy;
        if (this.nonce !== undefined)
            res.nonce = this.nonce.toJSON();
        if ((this.certReq !== undefined) && (TimeStampReq.compareWithDefault(CERT_REQ, this.certReq) === false))
            res.certReq = this.certReq;
        if (this.extensions) {
            res.extensions = Array.from(this.extensions, o => o.toJSON());
        }
        return res;
    }
}
TimeStampReq.CLASS_NAME = "TimeStampReq";

const STATUS = "status";
const TIME_STAMP_TOKEN = "timeStampToken";
const TIME_STAMP_RESP = "TimeStampResp";
const TIME_STAMP_RESP_STATUS = `${TIME_STAMP_RESP}.${STATUS}`;
const TIME_STAMP_RESP_TOKEN = `${TIME_STAMP_RESP}.${TIME_STAMP_TOKEN}`;
const CLEAR_PROPS = [
    TIME_STAMP_RESP_STATUS,
    TIME_STAMP_RESP_TOKEN
];
class TimeStampResp extends PkiObject {
    constructor(parameters = {}) {
        super();
        this.status = getParametersValue(parameters, STATUS, TimeStampResp.defaultValues(STATUS));
        if (TIME_STAMP_TOKEN in parameters) {
            this.timeStampToken = getParametersValue(parameters, TIME_STAMP_TOKEN, TimeStampResp.defaultValues(TIME_STAMP_TOKEN));
        }
        if (parameters.schema) {
            this.fromSchema(parameters.schema);
        }
    }
    static defaultValues(memberName) {
        switch (memberName) {
            case STATUS:
                return new PKIStatusInfo();
            case TIME_STAMP_TOKEN:
                return new ContentInfo();
            default:
                return super.defaultValues(memberName);
        }
    }
    static compareWithDefault(memberName, memberValue) {
        switch (memberName) {
            case STATUS:
                return ((PKIStatusInfo.compareWithDefault(STATUS, memberValue.status)) &&
                    (("statusStrings" in memberValue) === false) &&
                    (("failInfo" in memberValue) === false));
            case TIME_STAMP_TOKEN:
                return ((memberValue.contentType === EMPTY_STRING) &&
                    (memberValue.content instanceof Any));
            default:
                return super.defaultValues(memberName);
        }
    }
    static schema(parameters = {}) {
        const names = getParametersValue(parameters, "names", {});
        return (new Sequence({
            name: (names.blockName || TIME_STAMP_RESP),
            value: [
                PKIStatusInfo.schema(names.status || {
                    names: {
                        blockName: TIME_STAMP_RESP_STATUS
                    }
                }),
                ContentInfo.schema(names.timeStampToken || {
                    names: {
                        blockName: TIME_STAMP_RESP_TOKEN,
                        optional: true
                    }
                })
            ]
        }));
    }
    fromSchema(schema) {
        clearProps(schema, CLEAR_PROPS);
        const asn1 = compareSchema(schema, schema, TimeStampResp.schema());
        AsnError.assertSchema(asn1, this.className);
        this.status = new PKIStatusInfo({ schema: asn1.result[TIME_STAMP_RESP_STATUS] });
        if (TIME_STAMP_RESP_TOKEN in asn1.result)
            this.timeStampToken = new ContentInfo({ schema: asn1.result[TIME_STAMP_RESP_TOKEN] });
    }
    toSchema() {
        const outputArray = [];
        outputArray.push(this.status.toSchema());
        if (this.timeStampToken) {
            outputArray.push(this.timeStampToken.toSchema());
        }
        return (new Sequence({
            value: outputArray
        }));
    }
    toJSON() {
        const res = {
            status: this.status.toJSON()
        };
        if (this.timeStampToken) {
            res.timeStampToken = this.timeStampToken.toJSON();
        }
        return res;
    }
    sign(privateKey, hashAlgorithm, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            this.assertContentType();
            const signed = new SignedData({ schema: this.timeStampToken.content });
            return signed.sign(privateKey, 0, hashAlgorithm, undefined, crypto);
        });
    }
    verify(verificationParameters = { signer: 0, trustedCerts: [], data: EMPTY_BUFFER }, crypto = getCrypto(true)) {
        return __awaiter(this, void 0, void 0, function* () {
            this.assertContentType();
            const signed = new SignedData({ schema: this.timeStampToken.content });
            return signed.verify(verificationParameters, crypto);
        });
    }
    assertContentType() {
        if (!this.timeStampToken) {
            throw new Error("timeStampToken is absent in TSP response");
        }
        if (this.timeStampToken.contentType !== id_ContentType_SignedData) {
            throw new Error(`Wrong format of timeStampToken: ${this.timeStampToken.contentType}`);
        }
    }
}
TimeStampResp.CLASS_NAME = "TimeStampResp";

function initCryptoEngine() {
    if (typeof self !== "undefined") {
        if ("crypto" in self) {
            let engineName = "webcrypto";
            if ("webkitSubtle" in self.crypto) {
                engineName = "safari";
            }
            setEngine(engineName, new CryptoEngine({ name: engineName, crypto: crypto }));
        }
    }
    else if (typeof crypto !== "undefined" && "webcrypto" in crypto) {
        const name = "NodeJS ^15";
        const nodeCrypto = crypto.webcrypto;
        setEngine(name, new CryptoEngine({ name, crypto: nodeCrypto }));
    }
}

initCryptoEngine();

export { AbstractCryptoEngine, AccessDescription, Accuracy, AlgorithmIdentifier, AltName, ArgumentError, AsnError, AttCertValidityPeriod, Attribute, AttributeCertificateInfoV1, AttributeCertificateInfoV2, AttributeCertificateV1, AttributeCertificateV2, AttributeTypeAndValue, AuthenticatedSafe, AuthorityKeyIdentifier, BasicConstraints, BasicOCSPResponse, CAVersion, CRLBag, CRLDistributionPoints, CertBag, CertID, Certificate, CertificateChainValidationEngine, CertificatePolicies, CertificateRevocationList, CertificateSet, CertificateTemplate, CertificationRequest, ChainValidationCode, ChainValidationError, ContentInfo, CryptoEngine, DigestInfo, DistributionPoint, ECCCMSSharedInfo, ECNamedCurves, ECPrivateKey, ECPublicKey, EncapsulatedContentInfo, EncryptedContentInfo, EncryptedData, EnvelopedData, ExtKeyUsage, Extension, ExtensionValueFactory, Extensions, GeneralName, GeneralNames, GeneralSubtree, HASHED_MESSAGE, HASH_ALGORITHM, Holder, InfoAccess, IssuerAndSerialNumber, IssuerSerial, IssuingDistributionPoint, KEKIdentifier, KEKRecipientInfo, KeyAgreeRecipientIdentifier, KeyAgreeRecipientInfo, KeyBag, KeyTransRecipientInfo, MICROS, MILLIS, MacData, MessageImprint, NameConstraints, OCSPRequest, OCSPResponse, ObjectDigestInfo, OriginatorIdentifierOrKey, OriginatorInfo, OriginatorPublicKey, OtherCertificateFormat, OtherKeyAttribute, OtherPrimeInfo, OtherRecipientInfo, OtherRevocationInfoFormat, PBES2Params, PBKDF2Params, PFX, PKCS8ShroudedKeyBag, PKIStatus, PKIStatusInfo, POLICY_IDENTIFIER, POLICY_QUALIFIERS, ParameterError, PasswordRecipientinfo, PkiObject, PolicyConstraints, PolicyInformation, PolicyMapping, PolicyMappings, PolicyQualifierInfo, PrivateKeyInfo, PrivateKeyUsagePeriod, PublicKeyInfo, QCStatement, QCStatements, RDN, RSAESOAEPParams, RSAPrivateKey, RSAPublicKey, RSASSAPSSParams, RecipientEncryptedKey, RecipientEncryptedKeys, RecipientIdentifier, RecipientInfo, RecipientKeyIdentifier, RelativeDistinguishedNames, Request, ResponseBytes, ResponseData, RevocationInfoChoices, RevokedCertificate, SECONDS, SafeBag, SafeBagValueFactory, SafeContents, SecretBag, Signature, SignedAndUnsignedAttributes, SignedCertificateTimestamp, SignedCertificateTimestampList, SignedData, SignedDataVerifyError, SignerInfo, SingleResponse, SubjectDirectoryAttributes, TBSRequest, TSTInfo, TYPE$4 as TYPE, TYPE_AND_VALUES, Time, TimeStampReq, TimeStampResp, TimeType, V2Form, VALUE$5 as VALUE, VALUE_BEFORE_DECODE, checkCA, createCMSECDSASignature, createECDSASignatureFromCMS, engine, getAlgorithmByOID, getAlgorithmParameters, getCrypto, getEngine, getHashAlgorithm, getOIDByAlgorithm, getRandomValues, id_AnyPolicy, id_AuthorityInfoAccess, id_AuthorityKeyIdentifier, id_BaseCRLNumber, id_BasicConstraints, id_CRLBag_X509CRL, id_CRLDistributionPoints, id_CRLNumber, id_CRLReason, id_CertBag_AttributeCertificate, id_CertBag_SDSICertificate, id_CertBag_X509Certificate, id_CertificateIssuer, id_CertificatePolicies, id_ContentType_Data, id_ContentType_EncryptedData, id_ContentType_EnvelopedData, id_ContentType_SignedData, id_ExtKeyUsage, id_FreshestCRL, id_InhibitAnyPolicy, id_InvalidityDate, id_IssuerAltName, id_IssuingDistributionPoint, id_KeyUsage, id_MicrosoftAppPolicies, id_MicrosoftCaVersion, id_MicrosoftCertTemplateV1, id_MicrosoftCertTemplateV2, id_MicrosoftPrevCaCertHash, id_NameConstraints, id_PKIX_OCSP_Basic, id_PolicyConstraints, id_PolicyMappings, id_PrivateKeyUsagePeriod, id_QCStatements, id_SignedCertificateTimestampList, id_SubjectAltName, id_SubjectDirectoryAttributes, id_SubjectInfoAccess, id_SubjectKeyIdentifier, id_ad, id_ad_caIssuers, id_ad_ocsp, id_eContentType_TSTInfo, id_pkix, id_sha1, id_sha256, id_sha384, id_sha512, kdf, setEngine, stringPrep, verifySCTsForCertificate };
PK
!<�y��~
~
.chrome/toolkit/content/global/commonDialog.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:root {
  min-width: 29em;
}

dialog[insecureauth] {
  --icon-url: url("chrome://global/skin/icons/security-broken.svg");
}

#dialogGrid {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: center;
  max-height: 100%;
}

.dialogRow:not([hidden]) {
  display: contents;
}

#iconContainer {
  align-self: start;
}

#infoContainer {
  max-width: 45em;
}

#infoTitle {
  margin-bottom: 1em;
}

#infoBody {
  -moz-user-focus: normal;
  user-select: text;
  cursor: text !important;
  white-space: pre-wrap;
  word-break: break-word;
  unicode-bidi: plaintext;
  overflow-y: auto;
}

.sizeDetermined,
.sizeDetermined::part(content-box),
.sizeDetermined #infoBody,
.sizeDetermined #infoRow,
.sizeDetermined #infoContainer {
  /* Allow stuff to shrink */
  min-height: 0;
}

.sizeDetermined #infoRow {
  /* Make the info row take all the available space, potentially shrinking,
   * since it's what contains the infoBody, which is scrollable */
  flex: 1;
}

#loginLabel, #password1Label {
  text-align: start;
}

#loginTextbox,
#password1Textbox {
  text-align: match-parent;
}

/* use flexbox instead of grid: */
dialog[subdialog],
dialog[subdialog] #dialogGrid,
dialog[subdialog] #infoContainer,
dialog[subdialog] .dialogRow:not([hidden]) {
  display: flex;
  flex-direction: column;
  align-items: stretch;
}

dialog[subdialog] #iconContainer {
  display: none;
}

/* Fix padding/spacing */
dialog[subdialog] {
  --grid-padding: 16px;
  /* All the inner items should have 4px inline margin, leading to 1.16em spacing
   * between the dialog and its contents, and 8px horizontal spacing between items. */
  padding: var(--grid-padding) calc(var(--grid-padding) - 4px);
}

/* Use an ID selector for the dialog to win on specificity... */
#commonDialog[subdialog] description,
#commonDialog[subdialog] checkbox {
  margin: 0 4px;
}

#commonDialog[subdialog] label {
  margin: 4px; /* Labels for input boxes should get block as well as the regular inline margin. */
}

#commonDialog[subdialog] .checkbox-label {
  /* The checkbox already has the horizontal margin, so override the rule
   * above. */
  margin: 0;
}

#commonDialog[subdialog] input {
  margin: 0 4px 4px;
}

/* Add vertical spaces between rows: */
dialog[subdialog] .dialogRow {
  margin-block: 0 var(--grid-padding);
}

/* Fix spacing in the `prompt()` case: */
dialog[subdialog] #loginLabel[value=""] {
  display: none;
}

dialog[subdialog][windowtype="prompt:prompt"] #infoRow {
  margin-bottom: 4px;
}
PK
!<	���

-chrome/toolkit/content/global/commonDialog.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { CommonDialog } = ChromeUtils.importESModule(
  "resource://gre/modules/CommonDialog.sys.mjs"
);
const { XPCOMUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/XPCOMUtils.sys.mjs"
);

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "gContentAnalysis",
  "@mozilla.org/contentanalysis;1",
  Ci.nsIContentAnalysis
);

// imported by adjustableTitle.js loaded in the same context:
/* globals PromptUtils */

var propBag, args, Dialog;

function commonDialogOnLoad() {
  propBag = window.arguments[0]
    .QueryInterface(Ci.nsIWritablePropertyBag2)
    .QueryInterface(Ci.nsIWritablePropertyBag);
  // Convert to a JS object
  args = {};
  for (let prop of propBag.enumerator) {
    args[prop.name] = prop.value;
  }

  let dialog = document.getElementById("commonDialog");

  let needIconifiedHeader =
    args.modalType == Ci.nsIPrompt.MODAL_TYPE_CONTENT ||
    ["promptUserAndPass", "promptPassword"].includes(args.promptType) ||
    args.headerIconCSSValue;
  let root = document.documentElement;
  if (needIconifiedHeader) {
    root.setAttribute("neediconheader", "true");
  }
  let title = { raw: args.title };
  let { useTitle, promptPrincipal } = args;
  if (!useTitle) {
    if (promptPrincipal) {
      if (promptPrincipal.isNullPrincipal) {
        title = { l10nId: "common-dialog-title-null" };
      } else if (promptPrincipal.isSystemPrincipal) {
        title = { l10nId: "common-dialog-title-system" };
        root.style.setProperty("--icon-url", CommonDialog.DEFAULT_APP_ICON_CSS);
      } else if (promptPrincipal.addonPolicy) {
        title.raw = promptPrincipal.addonPolicy.name;
      } else if (promptPrincipal.isContentPrincipal) {
        try {
          title.raw = promptPrincipal.URI.displayHostPort;
        } catch (ex) {
          // hostPort getter can throw, e.g. for about URIs.
          title.raw = promptPrincipal.originNoSuffix;
        }
        // hostPort can be empty for file URIs.
        if (!title.raw) {
          title.raw = promptPrincipal.prePath;
        }
      } else {
        title = { l10nId: "common-dialog-title-unknown" };
      }
    } else if (args.authOrigin) {
      title = { raw: args.authOrigin };
    }
  }
  if (args.headerIconCSSValue) {
    root.style.setProperty("--icon-url", args.headerIconCSSValue);
  }
  // Fade and crop potentially long raw titles, e.g., origins and hostnames.
  title.shouldUseMaskFade =
    !useTitle && title.raw && (args.authOrigin || promptPrincipal);
  root.setAttribute("headertitle", JSON.stringify(title));
  if (args.isInsecureAuth) {
    dialog.setAttribute("insecureauth", "true");
  }

  let ui = {
    prompt: window,
    loginContainer: document.getElementById("loginContainer"),
    loginTextbox: document.getElementById("loginTextbox"),
    loginLabel: document.getElementById("loginLabel"),
    password1Container: document.getElementById("password1Container"),
    password1Textbox: document.getElementById("password1Textbox"),
    password1Label: document.getElementById("password1Label"),
    infoRow: document.getElementById("infoRow"),
    infoBody: document.getElementById("infoBody"),
    infoTitle: document.getElementById("infoTitle"),
    infoIcon: document.getElementById("infoIcon"),
    checkbox: document.getElementById("checkbox"),
    checkboxContainer: document.getElementById("checkboxContainer"),
    button3: dialog.getButton("extra2"),
    button2: dialog.getButton("extra1"),
    button1: dialog.getButton("cancel"),
    button0: dialog.getButton("accept"),
    focusTarget: window,
  };

  Dialog = new CommonDialog(args, ui);
  window.addEventListener("dialogclosing", function (aEvent) {
    if (aEvent.detail?.abort) {
      Dialog.abortPrompt();
    }
  });
  document.addEventListener("dialogaccept", function () {
    Dialog.onButton0();
  });
  document.addEventListener("dialogcancel", function () {
    Dialog.onButton1();
  });
  document.addEventListener("dialogextra1", function () {
    Dialog.onButton2();
    window.close();
  });
  document.addEventListener("dialogextra2", function () {
    Dialog.onButton3();
    window.close();
  });
  document.subDialogSetDefaultFocus = isInitialFocus =>
    Dialog.setDefaultFocus(isInitialFocus);
  Dialog.onLoad(dialog);

  // resize the window to the content
  window.sizeToContent();

  // If the icon hasn't loaded yet, size the window to the content again when
  // it does, as its layout can change.
  ui.infoIcon.addEventListener("load", () => window.sizeToContent());
  if (lazy.gContentAnalysis.isActive && args.owningBrowsingContext?.isContent) {
    ui.loginTextbox?.addEventListener("paste", async event => {
      let data = event.clipboardData.getData("text/plain");
      if (data?.length > 0) {
        // Prevent the paste from happening until content analysis returns a response
        event.preventDefault();
        // Selections can be forward or backward, so use min/max
        const startIndex = Math.min(
          ui.loginTextbox.selectionStart,
          ui.loginTextbox.selectionEnd
        );
        const endIndex = Math.max(
          ui.loginTextbox.selectionStart,
          ui.loginTextbox.selectionEnd
        );
        const selectionDirection =
          endIndex < startIndex ? "backward" : "forward";
        try {
          const response = await lazy.gContentAnalysis.analyzeContentRequest(
            {
              requestToken: Services.uuid.generateUUID().toString(),
              resources: [],
              analysisType: Ci.nsIContentAnalysisRequest.eBulkDataEntry,
              operationTypeForDisplay: Ci.nsIContentAnalysisRequest.eClipboard,
              url: args.owningBrowsingContext.currentURI,
              textContent: data,
              windowGlobalParent:
                args.owningBrowsingContext.currentWindowContext,
            },
            true
          );
          if (response.shouldAllowContent) {
            ui.loginTextbox.value =
              ui.loginTextbox.value.slice(0, startIndex) +
              data +
              ui.loginTextbox.value.slice(endIndex);
            ui.loginTextbox.focus();
            if (startIndex !== endIndex) {
              // Select the pasted text
              ui.loginTextbox.setSelectionRange(
                startIndex,
                startIndex + data.length,
                selectionDirection
              );
            }
          }
        } catch (error) {
          console.error("Content analysis request returned error: ", error);
        }
      }
    });
  }

  window.getAttention();
}

function commonDialogOnUnload() {
  // Convert args back into property bag
  for (let propName in args) {
    propBag.setProperty(propName, args[propName]);
  }
}
PK
!<�4�
�
0chrome/toolkit/content/global/commonDialog.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window>

<window
  id="commonDialogWindow"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
  xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  aria-describedby="infoBody"
  headerparent="dialogGrid"
  onunload="commonDialogOnUnload();"
>
  <dialog id="commonDialog" buttonpack="end">
    <linkset>
      <html:link rel="stylesheet" href="chrome://global/skin/global.css" />
      <html:link
        rel="stylesheet"
        href="chrome://global/content/commonDialog.css"
      />
      <html:link
        rel="stylesheet"
        href="chrome://global/skin/commonDialog.css"
      />

      <html:link rel="localization" href="branding/brand.ftl" />
      <html:link rel="localization" href="toolkit/global/commonDialog.ftl" />
    </linkset>
    <script src="chrome://global/content/adjustableTitle.js" />
    <script src="chrome://global/content/commonDialog.js" />
    <script src="chrome://global/content/globalOverlay.js" />
    <script src="chrome://global/content/editMenuOverlay.js" />
    <script src="chrome://global/content/customElements.js" />
    <script>
      /* eslint-disable no-undef */
      document.addEventListener("DOMContentLoaded", function () {
        commonDialogOnLoad();
      });
    </script>

    <commandset id="selectEditMenuItems">
      <command
        id="cmd_copy"
        oncommand="goDoCommand('cmd_copy')"
        disabled="true"
      />
      <command id="cmd_selectAll" oncommand="goDoCommand('cmd_selectAll')" />
    </commandset>

    <popupset id="contentAreaContextSet">
      <menupopup
        id="contentAreaContextMenu"
        onpopupshowing="goUpdateCommand('cmd_copy')"
      >
        <menuitem
          id="context-copy"
          data-l10n-id="common-dialog-copy-cmd"
          command="cmd_copy"
          disabled="true"
        />
        <menuitem
          id="context-selectall"
          data-l10n-id="common-dialog-select-all-cmd"
          command="cmd_selectAll"
        />
      </menupopup>
    </popupset>

    <div xmlns="http://www.w3.org/1999/xhtml" id="dialogGrid">
      <div class="dialogRow" id="infoRow" hidden="hidden">
        <div id="iconContainer">
          <xul:image id="infoIcon" />
        </div>
        <div id="infoContainer">
          <xul:description id="infoTitle" />
          <xul:description
            id="infoBody"
            context="contentAreaContextMenu"
            noinitialfocus="true"
          />
        </div>
      </div>
      <div id="loginContainer" class="dialogRow" hidden="hidden">
        <xul:label
          id="loginLabel"
          data-l10n-id="common-dialog-username"
          control="loginTextbox"
        />
        <input type="text" id="loginTextbox" dir="ltr" />
      </div>
      <div id="password1Container" class="dialogRow" hidden="hidden">
        <xul:label
          id="password1Label"
          data-l10n-id="common-dialog-password"
          control="password1Textbox"
        />
        <input type="password" id="password1Textbox" dir="ltr" />
      </div>
      <div id="checkboxContainer" class="dialogRow" hidden="hidden">
        <div />
        <!-- spacer -->
        <xul:checkbox id="checkbox" oncommand="Dialog.onCheckbox()" />
      </div>
    </div>
  </dialog>
</window>
PK
!<ZA�2����1chrome/toolkit/content/global/contentAreaUtils.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

var { AppConstants } = ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);
var { XPCOMUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/XPCOMUtils.sys.mjs"
);

ChromeUtils.defineESModuleGetters(this, {
  BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
  DownloadLastDir: "resource://gre/modules/DownloadLastDir.sys.mjs",
  DownloadPaths: "resource://gre/modules/DownloadPaths.sys.mjs",
  Downloads: "resource://gre/modules/Downloads.sys.mjs",
  FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

var ContentAreaUtils = {
  get stringBundle() {
    delete this.stringBundle;
    return (this.stringBundle = Services.strings.createBundle(
      "chrome://global/locale/contentAreaCommands.properties"
    ));
  },
};

function urlSecurityCheck(
  aURL,
  aPrincipal,
  aFlags = Services.scriptSecurityManager
) {
  if (aURL instanceof Ci.nsIURI) {
    Services.scriptSecurityManager.checkLoadURIWithPrincipal(
      aPrincipal,
      aURL,
      aFlags
    );
  } else {
    Services.scriptSecurityManager.checkLoadURIStrWithPrincipal(
      aPrincipal,
      aURL,
      aFlags
    );
  }
}

// Clientele: (Make sure you don't break any of these)
//  - File    ->  Save Page/Frame As...
//  - Context ->  Save Page/Frame As...
//  - Context ->  Save Link As...
//  - Alt-Click links in web pages
//  - Alt-Click links in the UI
//
// Try saving each of these types:
// - A complete webpage using File->Save Page As, and Context->Save Page As
// - A webpage as HTML only using the above methods
// - A webpage as Text only using the above methods
// - An image with an extension (e.g. .jpg) in its file name, using
//   Context->Save Image As...
// - An image without an extension (e.g. a banner ad on cnn.com) using
//   the above method.
// - A linked document using Save Link As...
// - A linked document using Alt-click Save Link As...
//
function saveURL(
  aURL,
  aOriginalURL,
  aFileName,
  aFilePickerTitleKey,
  aShouldBypassCache,
  aSkipPrompt,
  aReferrerInfo,
  aCookieJarSettings,
  aSourceDocument,
  aIsContentWindowPrivate,
  aPrincipal,
  aSaveCompleteCallback
) {
  internalSave(
    aURL,
    aOriginalURL,
    null,
    aFileName,
    null,
    null,
    aShouldBypassCache,
    aFilePickerTitleKey,
    null,
    aReferrerInfo,
    aCookieJarSettings,
    aSourceDocument,
    aSkipPrompt,
    null,
    aIsContentWindowPrivate,
    aPrincipal,
    aSaveCompleteCallback
  );
}

// Save the current document inside any browser/frame-like element,
// whether in-process or out-of-process.
function saveBrowser(aBrowser, aSkipPrompt, aBrowsingContext = null) {
  if (!aBrowser) {
    throw new Error("Must have a browser when calling saveBrowser");
  }
  let persistable = aBrowser.frameLoader;
  // PDF.js has its own way to handle saving PDFs since it may need to
  // generate a new PDF to save modified form data.
  if (aBrowser.contentPrincipal.spec == "resource://pdf.js/web/viewer.html") {
    aBrowser.sendMessageToActor("PDFJS:Save", {}, "Pdfjs");
    return;
  }
  let stack = Components.stack.caller;
  persistable.startPersistence(aBrowsingContext, {
    onDocumentReady(document) {
      if (!document || !(document instanceof Ci.nsIWebBrowserPersistDocument)) {
        throw new Error("Must have an nsIWebBrowserPersistDocument!");
      }

      // If we are downloading a document that is saving as a URL, we should use
      // a principal constructed from the document URL. This is important for
      // documents whose URL is different that their principal, e.g files in the
      // JSON viewer.
      let principal = null;
      if (document.principal?.spec == "resource://devtools/client/jsonview/") {
        let ssm = Services.scriptSecurityManager;
        principal = ssm.createContentPrincipal(
          makeURI(document.documentURI),
          document.principal.originAttributes
        );
      }

      internalSave(
        document.documentURI,
        null, // originalURL
        document,
        null, // file name
        document.contentDisposition,
        document.contentType,
        false, // bypass cache
        null, // file picker title key
        null, // chosen file data
        document.referrerInfo,
        document.cookieJarSettings,
        document,
        aSkipPrompt,
        document.cacheKey,
        undefined,
        principal
      );
    },
    onError(status) {
      throw new Components.Exception(
        "saveBrowser failed asynchronously in startPersistence",
        status,
        stack
      );
    },
  });
}

function DownloadListener(win, transfer) {
  function makeClosure(name) {
    return function () {
      transfer[name].apply(transfer, arguments);
    };
  }

  this.window = win;

  // Now... we need to forward all calls to our transfer
  for (var i in transfer) {
    if (i != "QueryInterface") {
      this[i] = makeClosure(i);
    }
  }
}

DownloadListener.prototype = {
  QueryInterface: ChromeUtils.generateQI([
    "nsIInterfaceRequestor",
    "nsIWebProgressListener",
    "nsIWebProgressListener2",
  ]),

  getInterface: function dl_gi(aIID) {
    if (aIID.equals(Ci.nsIAuthPrompt) || aIID.equals(Ci.nsIAuthPrompt2)) {
      var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].getService(
        Ci.nsIPromptFactory
      );
      return ww.getPrompt(this.window, aIID);
    }

    throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
  },
};

const kSaveAsType_Complete = 0; // Save document with attached objects.
XPCOMUtils.defineConstant(this, "kSaveAsType_Complete", 0);
// const kSaveAsType_URL      = 1; // Save document or URL by itself.
const kSaveAsType_Text = 2; // Save document, converting to plain text.
XPCOMUtils.defineConstant(this, "kSaveAsType_Text", kSaveAsType_Text);

/**
 * internalSave: Used when saving a document or URL.
 *
 * If aChosenData is null, this method:
 *  - Determines a local target filename to use
 *  - Prompts the user to confirm the destination filename and save mode
 *    (aContentType affects this)
 *  - [Note] This process involves the parameters aURL, aReferrerInfo,
 *    aDocument, aDefaultFileName, aFilePickerTitleKey, and aSkipPrompt.
 *
 * If aChosenData is non-null, this method:
 *  - Uses the provided source URI and save file name
 *  - Saves the document as complete DOM if possible (aDocument present and
 *    right aContentType)
 *  - [Note] The parameters aURL, aDefaultFileName, aFilePickerTitleKey, and
 *    aSkipPrompt are ignored.
 *
 * In any case, this method:
 *  - Creates a 'Persist' object (which will perform the saving in the
 *    background) and then starts it.
 *  - [Note] This part of the process only involves the parameters aDocument,
 *    aShouldBypassCache and aReferrerInfo. The source, the save name and the
 *    save mode are the ones determined previously.
 *
 * @param aURL
 *        The String representation of the URL of the document being saved
 * @param aOriginalURL
 *        The String representation of the original URL of the document being
 *        saved. It can useful in case aURL is a blob.
 * @param aDocument
 *        The document to be saved
 * @param aDefaultFileName
 *        The caller-provided suggested filename if we don't
 *        find a better one
 * @param aContentDisposition
 *        The caller-provided content-disposition header to use.
 * @param aContentType
 *        The caller-provided content-type to use
 * @param aShouldBypassCache
 *        If true, the document will always be refetched from the server
 * @param aFilePickerTitleKey
 *        Alternate title for the file picker
 * @param aChosenData
 *        If non-null this contains an instance of object AutoChosen (see below)
 *        which holds pre-determined data so that the user does not need to be
 *        prompted for a target filename.
 * @param aReferrerInfo
 *        the referrerInfo object to use, or null if no referrer should be sent.
 * @param aCookieJarSettings
 *        the cookieJarSettings object to use. This will be used for the channel
 *        used to save.
 * @param aInitiatingDocument [optional]
 *        The document from which the save was initiated.
 *        If this is omitted then aIsContentWindowPrivate has to be provided.
 * @param aSkipPrompt [optional]
 *        If set to true, we will attempt to save the file to the
 *        default downloads folder without prompting.
 * @param aCacheKey [optional]
 *        If set will be passed to saveURI.  See nsIWebBrowserPersist for
 *        allowed values.
 * @param aIsContentWindowPrivate [optional]
 *        This parameter is provided when the aInitiatingDocument is not a
 *        real document object. Stores whether aInitiatingDocument.defaultView
 *        was private or not.
 * @param aPrincipal [optional]
 *        This parameter is provided when neither aDocument nor
 *        aInitiatingDocument is provided. Used to determine what level of
 *        privilege to load the URI with.
 * @param aSaveCompleteCallback [optional]
 *        A callback function to call when the save is complete.
 */
function internalSave(
  aURL,
  aOriginalURL,
  aDocument,
  aDefaultFileName,
  aContentDisposition,
  aContentType,
  aShouldBypassCache,
  aFilePickerTitleKey,
  aChosenData,
  aReferrerInfo,
  aCookieJarSettings,
  aInitiatingDocument,
  aSkipPrompt,
  aCacheKey,
  aIsContentWindowPrivate,
  aPrincipal,
  aSaveCompleteCallback
) {
  if (aSkipPrompt == undefined) {
    aSkipPrompt = false;
  }

  if (aCacheKey == undefined) {
    aCacheKey = 0;
  }

  // Note: aDocument == null when this code is used by save-link-as...
  var saveMode = GetSaveModeForContentType(aContentType, aDocument);

  var file, sourceURI, saveAsType;
  let contentPolicyType = Ci.nsIContentPolicy.TYPE_SAVEAS_DOWNLOAD;
  // Find the URI object for aURL and the FileName/Extension to use when saving.
  // FileName/Extension will be ignored if aChosenData supplied.
  if (aChosenData) {
    file = aChosenData.file;
    sourceURI = aChosenData.uri;
    saveAsType = kSaveAsType_Complete;

    continueSave();
  } else {
    var charset = null;
    if (aDocument) {
      charset = aDocument.characterSet;
    }
    var fileInfo = new FileInfo(aDefaultFileName);
    initFileInfo(
      fileInfo,
      aURL,
      charset,
      aDocument,
      aContentType,
      aContentDisposition
    );
    sourceURI = fileInfo.uri;

    if (aContentType && aContentType.startsWith("image/")) {
      contentPolicyType = Ci.nsIContentPolicy.TYPE_IMAGE;
    }
    var fpParams = {
      fpTitleKey: aFilePickerTitleKey,
      fileInfo,
      contentType: aContentType,
      saveMode,
      saveAsType: kSaveAsType_Complete,
      file,
    };

    // Find a URI to use for determining last-downloaded-to directory
    let relatedURI =
      aOriginalURL || aReferrerInfo?.originalReferrer || sourceURI;

    promiseTargetFile(fpParams, aSkipPrompt, relatedURI)
      .then(aDialogAccepted => {
        if (!aDialogAccepted) {
          aSaveCompleteCallback?.();
          return;
        }

        saveAsType = fpParams.saveAsType;
        file = fpParams.file;

        continueSave();
      })
      .catch(console.error);
  }

  function continueSave() {
    // XXX We depend on the following holding true in appendFiltersForContentType():
    // If we should save as a complete page, the saveAsType is kSaveAsType_Complete.
    // If we should save as text, the saveAsType is kSaveAsType_Text.
    var useSaveDocument =
      aDocument &&
      ((saveMode & SAVEMODE_COMPLETE_DOM &&
        saveAsType == kSaveAsType_Complete) ||
        (saveMode & SAVEMODE_COMPLETE_TEXT && saveAsType == kSaveAsType_Text));
    // If we're saving a document, and are saving either in complete mode or
    // as converted text, pass the document to the web browser persist component.
    // If we're just saving the HTML (second option in the list), send only the URI.

    let isPrivate = aIsContentWindowPrivate;
    if (isPrivate === undefined) {
      isPrivate =
        aInitiatingDocument.nodeType == 9 /* DOCUMENT_NODE */
          ? PrivateBrowsingUtils.isContentWindowPrivate(
              aInitiatingDocument.defaultView
            )
          : aInitiatingDocument.isPrivate;
    }

    // We have to cover the cases here where we were either passed an explicit
    // principal, or a 'real' document (with a nodePrincipal property), or an
    // nsIWebBrowserPersistDocument which has a principal property.
    let sourcePrincipal =
      aPrincipal ||
      (aDocument && (aDocument.nodePrincipal || aDocument.principal)) ||
      (aInitiatingDocument && aInitiatingDocument.nodePrincipal);

    let sourceOriginalURI = aOriginalURL ? makeURI(aOriginalURL) : null;

    var persistArgs = {
      sourceURI,
      sourceOriginalURI,
      sourcePrincipal,
      sourceReferrerInfo: aReferrerInfo,
      sourceDocument: useSaveDocument ? aDocument : null,
      targetContentType: saveAsType == kSaveAsType_Text ? "text/plain" : null,
      targetFile: file,
      sourceCacheKey: aCacheKey,
      sourcePostData: aDocument ? getPostData(aDocument) : null,
      bypassCache: aShouldBypassCache,
      contentPolicyType,
      cookieJarSettings: aCookieJarSettings,
      isPrivate,
      saveCompleteCallback: aSaveCompleteCallback,
    };

    // Start the actual save process
    internalPersist(persistArgs);
  }
}

/**
 * internalPersist: Creates a 'Persist' object (which will perform the saving
 *  in the background) and then starts it.
 *
 * @param persistArgs.sourceURI
 *        The nsIURI of the document being saved
 * @param persistArgs.sourceCacheKey [optional]
 *        If set will be passed to saveURI
 * @param persistArgs.sourceDocument [optional]
 *        The document to be saved, or null if not saving a complete document
 * @param persistArgs.sourceReferrerInfo
 *        Required and used only when persistArgs.sourceDocument is NOT present,
 *        the nsIReferrerInfo of the referrer info to use, or null if no
 *        referrer should be sent.
 * @param persistArgs.sourcePostData
 *        Required and used only when persistArgs.sourceDocument is NOT present,
 *        represents the POST data to be sent along with the HTTP request, and
 *        must be null if no POST data should be sent.
 * @param persistArgs.targetFile
 *        The nsIFile of the file to create
 * @param persistArgs.contentPolicyType
 *        The type of content we're saving. Will be used to determine what
 *        content is accepted, enforce sniffing restrictions, etc.
 * @param persistArgs.cookieJarSettings [optional]
 *        The nsICookieJarSettings that will be used for the saving channel, or
 *        null that saveURI will create one based on the current
 *        state of the prefs/permissions
 * @param persistArgs.targetContentType
 *        Required and used only when persistArgs.sourceDocument is present,
 *        determines the final content type of the saved file, or null to use
 *        the same content type as the source document. Currently only
 *        "text/plain" is meaningful.
 * @param persistArgs.bypassCache
 *        If true, the document will always be refetched from the server
 * @param persistArgs.isPrivate
 *        Indicates whether this is taking place in a private browsing context.
 * @param persistArgs.saveCompleteCallback [optional]
 *        A callback function to call when the save is complete.
 */
function internalPersist(persistArgs) {
  var persist = makeWebBrowserPersist();

  // Calculate persist flags.
  const nsIWBP = Ci.nsIWebBrowserPersist;
  const flags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
  if (persistArgs.bypassCache) {
    persist.persistFlags = flags | nsIWBP.PERSIST_FLAGS_BYPASS_CACHE;
  } else {
    persist.persistFlags = flags | nsIWBP.PERSIST_FLAGS_FROM_CACHE;
  }

  // Leave it to WebBrowserPersist to discover the encoding type (or lack thereof):
  persist.persistFlags |= nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;

  // Find the URI associated with the target file
  var targetFileURL = makeFileURI(persistArgs.targetFile);

  // Create download and initiate it (below)
  var tr = Cc["@mozilla.org/transfer;1"].createInstance(Ci.nsITransfer);
  tr.init(
    persistArgs.sourceURI,
    persistArgs.sourceOriginalURI,
    targetFileURL,
    "",
    null,
    null,
    null,
    persist,
    persistArgs.isPrivate,
    Ci.nsITransfer.DOWNLOAD_ACCEPTABLE,
    persistArgs.sourceReferrerInfo
  );
  persist.progressListener = new DownloadListener(window, tr);
  const { saveCompleteCallback } = persistArgs;
  if (saveCompleteCallback) {
    tr.downloadPromise
      .then(aDownload => aDownload.whenSucceeded())
      .catch(console.error)
      .finally(saveCompleteCallback);
  }

  if (persistArgs.sourceDocument) {
    // Saving a Document, not a URI:
    var filesFolder = null;
    if (persistArgs.targetContentType != "text/plain") {
      // Create the local directory into which to save associated files.
      filesFolder = persistArgs.targetFile.clone();

      var nameWithoutExtension = getFileBaseName(filesFolder.leafName);
      var filesFolderLeafName =
        ContentAreaUtils.stringBundle.formatStringFromName("filesFolder", [
          nameWithoutExtension,
        ]);

      filesFolder.leafName = filesFolderLeafName;
    }

    var encodingFlags = 0;
    if (persistArgs.targetContentType == "text/plain") {
      encodingFlags |= nsIWBP.ENCODE_FLAGS_FORMATTED;
      encodingFlags |= nsIWBP.ENCODE_FLAGS_ABSOLUTE_LINKS;
      encodingFlags |= nsIWBP.ENCODE_FLAGS_NOFRAMES_CONTENT;
    } else {
      encodingFlags |= nsIWBP.ENCODE_FLAGS_ENCODE_BASIC_ENTITIES;
    }

    const kWrapColumn = 80;
    persist.saveDocument(
      persistArgs.sourceDocument,
      targetFileURL,
      filesFolder,
      persistArgs.targetContentType,
      encodingFlags,
      kWrapColumn
    );
  } else {
    persist.saveURI(
      persistArgs.sourceURI,
      persistArgs.sourcePrincipal,
      persistArgs.sourceCacheKey,
      persistArgs.sourceReferrerInfo,
      persistArgs.cookieJarSettings,
      persistArgs.sourcePostData,
      null,
      targetFileURL,
      persistArgs.contentPolicyType || Ci.nsIContentPolicy.TYPE_SAVEAS_DOWNLOAD,
      persistArgs.isPrivate
    );
  }
}

/**
 * Structure for holding info about automatically supplied parameters for
 * internalSave(...). This allows parameters to be supplied so the user does not
 * need to be prompted for file info.
 * @param aFileAutoChosen This is an nsIFile object that has been
 *        pre-determined as the filename for the target to save to
 * @param aUriAutoChosen  This is the nsIURI object for the target
 */
function AutoChosen(aFileAutoChosen, aUriAutoChosen) {
  this.file = aFileAutoChosen;
  this.uri = aUriAutoChosen;
}

/**
 * Structure for holding info about a URL and the target filename it should be
 * saved to. This structure is populated by initFileInfo(...).
 * @param aSuggestedFileName This is used by initFileInfo(...) when it
 *        cannot 'discover' the filename from the url
 * @param aFileName The target filename
 * @param aFileBaseName The filename without the file extension
 * @param aFileExt The extension of the filename
 * @param aUri An nsIURI object for the url that is being saved
 */
function FileInfo(
  aSuggestedFileName,
  aFileName,
  aFileBaseName,
  aFileExt,
  aUri
) {
  this.suggestedFileName = aSuggestedFileName;
  this.fileName = aFileName;
  this.fileBaseName = aFileBaseName;
  this.fileExt = aFileExt;
  this.uri = aUri;
}

/**
 * Determine what the 'default' filename string is, its file extension and the
 * filename without the extension. This filename is used when prompting the user
 * for confirmation in the file picker dialog.
 * @param aFI A FileInfo structure into which we'll put the results of this method.
 * @param aURL The String representation of the URL of the document being saved
 * @param aURLCharset The charset of aURL.
 * @param aDocument The document to be saved
 * @param aContentType The content type we're saving, if it could be
 *        determined by the caller.
 * @param aContentDisposition The content-disposition header for the object
 *        we're saving, if it could be determined by the caller.
 */
function initFileInfo(
  aFI,
  aURL,
  aURLCharset,
  aDocument,
  aContentType,
  aContentDisposition
) {
  try {
    let uriExt = null;
    // Get an nsIURI object from aURL if possible:
    try {
      aFI.uri = makeURI(aURL, aURLCharset);
      // Assuming nsiUri is valid, calling QueryInterface(...) on it will
      // populate extra object fields (eg filename and file extension).
      uriExt = aFI.uri.QueryInterface(Ci.nsIURL).fileExtension;
    } catch (e) {}

    // Get the default filename:
    let fileName = getDefaultFileName(
      aFI.suggestedFileName || aFI.fileName,
      aFI.uri,
      aDocument,
      aContentDisposition
    );

    let mimeService = this.getMIMEService();
    aFI.fileName = mimeService.validateFileNameForSaving(
      fileName,
      aContentType,
      mimeService.VALIDATE_FORCE_APPEND_EXTENSION
    );

    // If uriExt is blank, consider: aFI.suggestedFileName is supplied if
    // saveURL(...) was the original caller (hence both aContentType and
    // aDocument are blank). If they were saving a link to a website then make
    // the extension .htm .
    if (
      !uriExt &&
      !aDocument &&
      !aContentType &&
      /^http(s?):\/\//i.test(aURL)
    ) {
      aFI.fileExt = "htm";
      aFI.fileBaseName = aFI.fileName;
    } else {
      let idx = aFI.fileName.lastIndexOf(".");
      aFI.fileBaseName =
        idx >= 0 ? aFI.fileName.substring(0, idx) : aFI.fileName;
      aFI.fileExt = idx >= 0 ? aFI.fileName.substring(idx + 1) : null;
    }
  } catch (e) {}
}

/**
 * Given the Filepicker Parameters (aFpP), show the file picker dialog,
 * prompting the user to confirm (or change) the fileName.
 * @param aFpP
 *        A structure (see definition in internalSave(...) method)
 *        containing all the data used within this method.
 * @param aSkipPrompt
 *        If true, attempt to save the file automatically to the user's default
 *        download directory, thus skipping the explicit prompt for a file name,
 *        but only if the associated preference is set.
 *        If false, don't save the file automatically to the user's
 *        default download directory, even if the associated preference
 *        is set, but ask for the target explicitly.
 * @param aRelatedURI
 *        An nsIURI associated with the download. The last used
 *        directory of the picker is retrieved from/stored in the
 *        Content Pref Service using this URI.
 * @return Promise
 * @resolve a boolean. When true, it indicates that the file picker dialog
 *          is accepted.
 */
function promiseTargetFile(
  aFpP,
  /* optional */ aSkipPrompt,
  /* optional */ aRelatedURI
) {
  return (async function () {
    let downloadLastDir = new DownloadLastDir(window);
    let prefBranch = Services.prefs.getBranch("browser.download.");
    let useDownloadDir = prefBranch.getBoolPref("useDownloadDir");

    if (!aSkipPrompt) {
      useDownloadDir = false;
    }

    // Default to the user's default downloads directory configured
    // through download prefs.
    let dirPath = await Downloads.getPreferredDownloadsDirectory();
    let dirExists = await IOUtils.exists(dirPath);
    let dir = new FileUtils.File(dirPath);

    if (useDownloadDir && dirExists) {
      dir.append(aFpP.fileInfo.fileName);
      aFpP.file = uniqueFile(dir);
      return true;
    }

    // We must prompt for the file name explicitly.
    // If we must prompt because we were asked to...
    let file = null;
    if (!useDownloadDir) {
      file = await downloadLastDir.getFileAsync(aRelatedURI);
    }
    if (file && (await IOUtils.exists(file.path))) {
      dir = file;
      dirExists = true;
    }

    if (!dirExists) {
      // Default to desktop.
      dir = Services.dirsvc.get("Desk", Ci.nsIFile);
    }

    let fp = makeFilePicker();
    let titleKey = aFpP.fpTitleKey || "SaveLinkTitle";
    fp.init(
      window.browsingContext,
      ContentAreaUtils.stringBundle.GetStringFromName(titleKey),
      Ci.nsIFilePicker.modeSave
    );

    fp.displayDirectory = dir;
    fp.defaultExtension = aFpP.fileInfo.fileExt;
    fp.defaultString = aFpP.fileInfo.fileName;
    appendFiltersForContentType(
      fp,
      aFpP.contentType,
      aFpP.fileInfo.fileExt,
      aFpP.saveMode
    );

    // The index of the selected filter is only preserved and restored if there's
    // more than one filter in addition to "All Files".
    if (aFpP.saveMode != SAVEMODE_FILEONLY) {
      // eslint-disable-next-line mozilla/use-default-preference-values
      try {
        fp.filterIndex = prefBranch.getIntPref("save_converter_index");
      } catch (e) {}
    }

    let result = await new Promise(resolve => {
      fp.open(function (aResult) {
        resolve(aResult);
      });
    });
    if (result == Ci.nsIFilePicker.returnCancel || !fp.file) {
      return false;
    }

    if (aFpP.saveMode != SAVEMODE_FILEONLY) {
      prefBranch.setIntPref("save_converter_index", fp.filterIndex);
    }

    // Do not store the last save directory as a pref inside the private browsing mode
    downloadLastDir.setFile(aRelatedURI, fp.file.parent);

    aFpP.saveAsType = fp.filterIndex;
    aFpP.file = fp.file;
    aFpP.file.leafName = validateFileName(aFpP.file.leafName);

    return true;
  })();
}

// Since we're automatically downloading, we don't get the file picker's
// logic to check for existing files, so we need to do that here.
//
// Note - this code is identical to that in
//   mozilla/toolkit/mozapps/downloads/src/nsHelperAppDlg.js.in
// If you are updating this code, update that code too! We can't share code
// here since that code is called in a js component.
function uniqueFile(aLocalFile) {
  var collisionCount = 0;
  while (aLocalFile.exists()) {
    collisionCount++;
    if (collisionCount == 1) {
      // Append "(2)" before the last dot in (or at the end of) the filename
      // special case .ext.gz etc files so we don't wind up with .tar(2).gz
      if (aLocalFile.leafName.match(/\.[^\.]{1,3}\.(gz|bz2|Z)$/i)) {
        aLocalFile.leafName = aLocalFile.leafName.replace(
          /\.[^\.]{1,3}\.(gz|bz2|Z)$/i,
          "(2)$&"
        );
      } else {
        aLocalFile.leafName = aLocalFile.leafName.replace(
          /(\.[^\.]*)?$/,
          "(2)$&"
        );
      }
    } else {
      // replace the last (n) in the filename with (n+1)
      aLocalFile.leafName = aLocalFile.leafName.replace(
        /^(.*\()\d+\)/,
        "$1" + (collisionCount + 1) + ")"
      );
    }
  }
  return aLocalFile;
}

/**
 * Download a URL using the Downloads API.
 *
 * @param aURL
 *        the url to download
 * @param [optional] aFileName
 *        the destination file name, if omitted will be obtained from the url.
 * @param aInitiatingDocument
 *        The document from which the download was initiated.
 */
function DownloadURL(aURL, aFileName, aInitiatingDocument) {
  // For private browsing, try to get document out of the most recent browser
  // window, or provide our own if there's no browser window.
  let isPrivate = aInitiatingDocument.defaultView.docShell.QueryInterface(
    Ci.nsILoadContext
  ).usePrivateBrowsing;

  let fileInfo = new FileInfo(aFileName);
  initFileInfo(fileInfo, aURL, null, null, null, null);

  let filepickerParams = {
    fileInfo,
    saveMode: SAVEMODE_FILEONLY,
  };

  (async function () {
    let accepted = await promiseTargetFile(
      filepickerParams,
      true,
      fileInfo.uri
    );
    if (!accepted) {
      return;
    }

    let file = filepickerParams.file;
    let download = await Downloads.createDownload({
      source: { url: aURL, isPrivate },
      target: { path: file.path, partFilePath: file.path + ".part" },
    });
    download.tryToKeepPartialData = true;

    // Ignore errors because failures are reported through the download list.
    download.start().catch(() => {});

    // Add the download to the list, allowing it to be managed.
    let list = await Downloads.getList(Downloads.ALL);
    list.add(download);
  })().catch(console.error);
}

// We have no DOM, and can only save the URL as is.
const SAVEMODE_FILEONLY = 0x00;
XPCOMUtils.defineConstant(this, "SAVEMODE_FILEONLY", SAVEMODE_FILEONLY);
// We have a DOM and can save as complete.
const SAVEMODE_COMPLETE_DOM = 0x01;
XPCOMUtils.defineConstant(this, "SAVEMODE_COMPLETE_DOM", SAVEMODE_COMPLETE_DOM);
// We have a DOM which we can serialize as text.
const SAVEMODE_COMPLETE_TEXT = 0x02;
XPCOMUtils.defineConstant(
  this,
  "SAVEMODE_COMPLETE_TEXT",
  SAVEMODE_COMPLETE_TEXT
);

// If we are able to save a complete DOM, the 'save as complete' filter
// must be the first filter appended.  The 'save page only' counterpart
// must be the second filter appended.  And the 'save as complete text'
// filter must be the third filter appended.
function appendFiltersForContentType(
  aFilePicker,
  aContentType,
  aFileExtension,
  aSaveMode
) {
  // The bundle name for saving only a specific content type.
  var bundleName;
  // The corresponding filter string for a specific content type.
  var filterString;

  // Every case where GetSaveModeForContentType can return non-FILEONLY
  // modes must be handled here.
  if (aSaveMode != SAVEMODE_FILEONLY) {
    switch (aContentType) {
      case "text/html":
        bundleName = "WebPageHTMLOnlyFilter";
        filterString = "*.htm; *.html";
        break;

      case "application/xhtml+xml":
        bundleName = "WebPageXHTMLOnlyFilter";
        filterString = "*.xht; *.xhtml";
        break;

      case "image/svg+xml":
        bundleName = "WebPageSVGOnlyFilter";
        filterString = "*.svg; *.svgz";
        break;

      case "text/xml":
      case "application/xml":
        bundleName = "WebPageXMLOnlyFilter";
        filterString = "*.xml";
        break;
    }
  }

  if (!bundleName) {
    if (aSaveMode != SAVEMODE_FILEONLY) {
      throw new Error(`Invalid save mode for type '${aContentType}'`);
    }

    var mimeInfo = getMIMEInfoForType(aContentType, aFileExtension);
    if (mimeInfo) {
      var extString = "";
      for (var extension of mimeInfo.getFileExtensions()) {
        if (extString) {
          extString += "; ";
        } // If adding more than one extension,
        // separate by semi-colon
        extString += "*." + extension;
      }

      if (extString) {
        aFilePicker.appendFilter(mimeInfo.description, extString);
      }
    }
  }

  if (aSaveMode & SAVEMODE_COMPLETE_DOM) {
    aFilePicker.appendFilter(
      ContentAreaUtils.stringBundle.GetStringFromName("WebPageCompleteFilter"),
      filterString
    );
    // We should always offer a choice to save document only if
    // we allow saving as complete.
    aFilePicker.appendFilter(
      ContentAreaUtils.stringBundle.GetStringFromName(bundleName),
      filterString
    );
  }

  if (aSaveMode & SAVEMODE_COMPLETE_TEXT) {
    aFilePicker.appendFilters(Ci.nsIFilePicker.filterText);
  }

  // Always append the all files (*) filter
  aFilePicker.appendFilters(Ci.nsIFilePicker.filterAll);
}

function getPostData(aDocument) {
  if (aDocument instanceof Ci.nsIWebBrowserPersistDocument) {
    return aDocument.postData;
  }
  try {
    // Find the session history entry corresponding to the given document. In
    // the current implementation, nsIWebPageDescriptor.currentDescriptor always
    // returns a session history entry.
    let sessionHistoryEntry = aDocument.defaultView.docShell
      .QueryInterface(Ci.nsIWebPageDescriptor)
      .currentDescriptor.QueryInterface(Ci.nsISHEntry);
    return sessionHistoryEntry.postData;
  } catch (e) {}
  return null;
}

function makeWebBrowserPersist() {
  const persistContractID =
    "@mozilla.org/embedding/browser/nsWebBrowserPersist;1";
  const persistIID = Ci.nsIWebBrowserPersist;
  return Cc[persistContractID].createInstance(persistIID);
}

function makeURI(aURL, aOriginCharset, aBaseURI) {
  return Services.io.newURI(aURL, aOriginCharset, aBaseURI);
}

function makeFileURI(aFile) {
  return Services.io.newFileURI(aFile);
}

function makeFilePicker() {
  const fpContractID = "@mozilla.org/filepicker;1";
  const fpIID = Ci.nsIFilePicker;
  return Cc[fpContractID].createInstance(fpIID);
}

function getMIMEService() {
  const mimeSvcContractID = "@mozilla.org/mime;1";
  const mimeSvcIID = Ci.nsIMIMEService;
  const mimeSvc = Cc[mimeSvcContractID].getService(mimeSvcIID);
  return mimeSvc;
}

// Given aFileName, find the fileName without the extension on the end.
function getFileBaseName(aFileName) {
  // Remove the file extension from aFileName:
  return aFileName.replace(/\.[^.]*$/, "");
}

function getMIMETypeForURI(aURI) {
  try {
    return getMIMEService().getTypeFromURI(aURI);
  } catch (e) {}
  return null;
}

function getMIMEInfoForType(aMIMEType, aExtension) {
  if (aMIMEType || aExtension) {
    try {
      return getMIMEService().getFromTypeAndExtension(aMIMEType, aExtension);
    } catch (e) {}
  }
  return null;
}

function getDefaultFileName(
  aDefaultFileName,
  aURI,
  aDocument,
  aContentDisposition
) {
  // 1) look for a filename in the content-disposition header, if any
  if (aContentDisposition) {
    const mhpContractID = "@mozilla.org/network/mime-hdrparam;1";
    const mhpIID = Ci.nsIMIMEHeaderParam;
    const mhp = Cc[mhpContractID].getService(mhpIID);
    var dummy = { value: null }; // Need an out param...
    var charset = getCharsetforSave(aDocument);

    var fileName = null;
    try {
      fileName = mhp.getParameter(
        aContentDisposition,
        "filename",
        charset,
        true,
        dummy
      );
    } catch (e) {
      try {
        fileName = mhp.getParameter(
          aContentDisposition,
          "name",
          charset,
          true,
          dummy
        );
      } catch (e) {}
    }
    if (fileName) {
      return Services.textToSubURI.unEscapeURIForUI(
        fileName,
        /* dontEscape = */ true
      );
    }
  }

  let docTitle;
  if (aDocument && aDocument.title && aDocument.title.trim()) {
    // If the document looks like HTML or XML, try to use its original title.
    let contentType = aDocument.contentType;
    if (
      contentType == "application/xhtml+xml" ||
      contentType == "application/xml" ||
      contentType == "image/svg+xml" ||
      contentType == "text/html" ||
      contentType == "text/xml"
    ) {
      // 2) Use the document title
      return aDocument.title;
    }
  }

  try {
    var url = aURI.QueryInterface(Ci.nsIURL);
    if (url.fileName != "") {
      // 3) Use the actual file name, if present
      return Services.textToSubURI.unEscapeURIForUI(
        url.fileName,
        /* dontEscape = */ true
      );
    }
  } catch (e) {
    // This is something like a data: and so forth URI... no filename here.
  }

  // Don't use the title if it's from a data URI
  if (docTitle && aURI?.scheme != "data") {
    // 4) Use the document title
    return docTitle;
  }

  if (aDefaultFileName) {
    // 5) Use the caller-provided name, if any
    return aDefaultFileName;
  }

  try {
    if (aURI.host) {
      // 6) Use the host.
      return aURI.host;
    }
  } catch (e) {
    // Some files have no information at all, like Javascript generated pages
  }

  return "";
}

// This is only used after the user has entered a filename.
function validateFileName(aFileName) {
  let processed =
    DownloadPaths.sanitize(aFileName, {
      compressWhitespaces: false,
      allowInvalidFilenames: true,
    }) || "_";
  if (AppConstants.platform == "android") {
    // If a large part of the filename has been sanitized, then we
    // will use a default filename instead
    if (processed.replace(/_/g, "").length <= processed.length / 2) {
      // We purposefully do not use a localized default filename,
      // which we could have done using
      // ContentAreaUtils.stringBundle.GetStringFromName("UntitledSaveFileName")
      // since it may contain invalid characters.
      var original = processed;
      processed = "download";

      // Preserve a suffix, if there is one
      if (original.includes(".")) {
        var suffix = original.split(".").slice(-1)[0];
        if (suffix && !suffix.includes("_")) {
          processed += "." + suffix;
        }
      }
    }
  }
  return processed;
}

function GetSaveModeForContentType(aContentType, aDocument) {
  // We can only save a complete page if we have a loaded document,
  if (!aDocument) {
    return SAVEMODE_FILEONLY;
  }

  // Find the possible save modes using the provided content type
  var saveMode = SAVEMODE_FILEONLY;
  switch (aContentType) {
    case "text/html":
    case "application/xhtml+xml":
    case "image/svg+xml":
      saveMode |= SAVEMODE_COMPLETE_TEXT;
    // Fall through
    case "text/xml":
    case "application/xml":
      saveMode |= SAVEMODE_COMPLETE_DOM;
      break;
  }

  return saveMode;
}

function getCharsetforSave(aDocument) {
  if (aDocument) {
    return aDocument.characterSet;
  }

  if (document.commandDispatcher.focusedWindow) {
    return document.commandDispatcher.focusedWindow.document.characterSet;
  }

  return window.content.document.characterSet;
}

/**
 * Open a URL from chrome, determining if we can handle it internally or need to
 *  launch an external application to handle it.
 * @param aURL The URL to be opened
 *
 * WARNING: Please note that openURL() does not perform any content security checks!!!
 */
function openURL(aURL) {
  var uri = aURL instanceof Ci.nsIURI ? aURL : makeURI(aURL);

  var protocolSvc = Cc[
    "@mozilla.org/uriloader/external-protocol-service;1"
  ].getService(Ci.nsIExternalProtocolService);

  let recentWindow = Services.wm.getMostRecentWindow("navigator:browser");

  if (!protocolSvc.isExposedProtocol(uri.scheme)) {
    // If we're not a browser, use the external protocol service to load the URI.
    protocolSvc.loadURI(uri, recentWindow?.document.contentPrincipal);
  } else {
    if (recentWindow) {
      recentWindow.openWebLinkIn(uri.spec, "tab", {
        triggeringPrincipal: recentWindow.document.contentPrincipal,
      });
      return;
    }

    var loadgroup = Cc["@mozilla.org/network/load-group;1"].createInstance(
      Ci.nsILoadGroup
    );
    var appstartup = Services.startup;

    var loadListener = {
      onStartRequest: function ll_start() {
        appstartup.enterLastWindowClosingSurvivalArea();
      },
      onStopRequest: function ll_stop() {
        appstartup.exitLastWindowClosingSurvivalArea();
      },
      QueryInterface: ChromeUtils.generateQI([
        "nsIRequestObserver",
        "nsISupportsWeakReference",
      ]),
    };
    loadgroup.groupObserver = loadListener;

    var uriListener = {
      doContent() {
        return false;
      },
      isPreferred() {
        return false;
      },
      canHandleContent() {
        return false;
      },
      loadCookie: null,
      parentContentListener: null,
      getInterface(iid) {
        if (iid.equals(Ci.nsIURIContentListener)) {
          return this;
        }
        if (iid.equals(Ci.nsILoadGroup)) {
          return loadgroup;
        }
        throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
      },
    };

    var channel = NetUtil.newChannel({
      uri,
      loadUsingSystemPrincipal: true,
    });

    if (channel) {
      channel.channelIsForDownload = true;
    }

    var uriLoader = Cc["@mozilla.org/uriloader;1"].getService(Ci.nsIURILoader);
    uriLoader.openURI(
      channel,
      Ci.nsIURILoader.IS_CONTENT_PREFERRED,
      uriListener
    );
  }
}
PK
!<G����Hchrome/toolkit/content/global/cookiebanners/CookieBannerRule.schema.json{
  "type": "object",
  "definitions": {
    "cookie": {
      "type": "object",
      "required": ["name", "value"],
      "description": "JSON representation of a cookie to inject.",
      "properties": {
        "name": {
          "title": "Name / Key",
          "type": "string",
          "description": "The name of the cookie."
        },
        "value": {
          "title": "Value",
          "type": "string",
          "description": "The cookie value."
        },
        "host": {
          "title": "Host",
          "type": "string",
          "description": "Host to set cookie for. Defaults to .<domain> if unset."
        },
        "path": {
          "title": "Path",
          "type": "string",
          "description": "The path pertaining to the cookie."
        },
        "expiryRelative": {
          "title": "Relative Expiry Time",
          "type": "number",
          "description": "Expiry time of the cookie in seconds relative to the injection time. Defaults to pref value for cookiebanners.cookieInjector.defaultExpiryRelative."
        },
        "unsetValue": {
          "title": "Unset Value",
          "type": "string",
          "description": "If an existing cookie of the same name sets this value it may be overwritten by this rule."
        },
        "isSecure": {
          "title": "Secure Cookie",
          "type": "boolean",
          "description": "true if the cookie was transmitted over ssl, false otherwise."
        },
        "sameSite": {
          "title": "SameSite",
          "type": "number",
          "enum": [0, 1, 2],
          "description": "The SameSite attribute. See nsICookie.idl."
        },
        "isSession": {
          "title": "Session Cookie",
          "type": "boolean",
          "description": "true if the cookie is a session cookie."
        },
        "schemeMap": {
          "title": "Scheme Map",
          "type": "number",
          "description": "Bitmap of schemes."
        },
        "isHTTPOnly": {
          "title": "HTTP-Only",
          "type": "boolean",
          "description": "true if the cookie is an http only cookie."
        }
      }
    }
  },
  "title": "Cookie Banner Rule",
  "required": ["id", "domains"],
  "additionalProperties": false,
  "properties": {
    "id": {
      "title": "ID",
      "type": "string",
      "description": "Unique identifier of the rule."
    },
    "domains": {
      "title": "Domains",
      "type": ["array"],
      "items": {
        "type": "string"
      },
      "uniqueItems": true,
      "description": "List of domains of the sites the rule describes. Leave empty for global rules which should apply to every site."
    },
    "cookies": {
      "title": "Cookies",
      "description": "Cookie banner related cookies to be injected when the side loads.",
      "type": "object",
      "properties": {
        "optIn": {
          "title": "Opt-in cookies",
          "type": "array",
          "items": {
            "$ref": "#/definitions/cookie"
          },
          "description": "Cookies to be set to signal opt-in state."
        },
        "optOut": {
          "title": "Opt-out cookies",
          "type": "array",
          "items": {
            "$ref": "#/definitions/cookie"
          },
          "description": "Cookies to be set to signal opt-out state."
        }
      }
    },
    "click": {
      "title": "Click",
      "description": "Rules for detection of the cookie banner and simulated clicks.",
      "type": "object",
      "properties": {
        "presence": {
          "title": "Presence Selector",
          "type": "string",
          "description": "Query selector to detect cookie banner element."
        },
        "skipPresenceVisibilityCheck": {
          "title": "Skip Presence Visibility Check",
          "type": "boolean",
          "description": "Whether to skip checking if the banner is visible before clicking it."
        },
        "runContext": {
          "title": "Run Context",
          "type": "string",
          "enum": ["top", "child", "all"],
          "description": "Where the click rule should be executed. Defaults to only top window. top: Only in the top window; child: Only in child frames; all: Both top window and child frames."
        },
        "hide": {
          "title": "Hide Selector",
          "type": "string",
          "description": "Query selector for element to hide while handling cookie banner. Defaults to 'presence' selector."
        },
        "optOut": {
          "title": "Opt-out Selector",
          "type": "string",
          "description": "Query selector for opt-out / reject all button"
        },
        "optIn": {
          "title": "Opt-in Selector",
          "type": "string",
          "description": "Query selector for opt-in / accept all button"
        }
      },
      "dependencies": {
        "hide": ["presence"],
        "optOut": ["presence"],
        "optIn": ["presence"]
      }
    },
    "filter_expression": {
      "type": "string",
      "description": "This is NOT used by the cookie banner handling feature, but has special functionality in Remote Settings. See https://remote-settings.readthedocs.io/en/latest/target-filters.html#how"
    }
  },
  "description": "Rule containing instructions on how to handle a cookie banner on a specific site."
}
PK
!<�Rp�nn)chrome/toolkit/content/global/crashes.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:root {
  font-family: sans-serif;
  margin: 40px auto;
  min-width: 30em;
  max-width: 60em;
}

.hidden {
  display: none;
}

/* Table layout */

table {
  width: 100%;
  padding-bottom: 2em;
  border-spacing: 0;
}

th {
  text-align: start;
}

th, td {
  border-bottom: 1px solid var(--in-content-border-color);
}

th,
#submitted td {
  /* Unsubmitted table already gets spacing from button */
  padding-block: 10px;
}

.submit-button,
.crash-link {
  float: inline-end;
}

/* Other elements */

.table-title-container {
  align-items: center;
  display: flex;
  justify-content: space-between;
}

.submitting {
  background-image: url("chrome://global/skin/icons/loading.svg");
  background-position: center;
  background-repeat: no-repeat;
  background-size: 16px;
  -moz-context-properties: fill;
  fill: currentColor;
}

.submitting .submit-crash-button-label {
  display: none;
}

.failed-to-submit {
  color: #ca8695;
}
PK
!<�y��
�
*chrome/toolkit/content/global/crashes.html<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html>
<html>
  <head>
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:; object-src 'none'"
    />
    <meta charset="utf-8" />
    <meta nem="color-scheme" content="light dark" />
    <link rel="localization" href="crashreporter/aboutcrashes.ftl" />
    <link rel="stylesheet" href="chrome://global/content/crashes.css" />
    <link
      rel="stylesheet"
      media="screen, projection"
      href="chrome://global/skin/in-content/common.css"
    />
    <script src="chrome://global/content/crashes.js"></script>
    <title data-l10n-id="crash-reports-title"></title>
  </head>

  <body>
    <p id="noConfig" class="hidden" data-l10n-id="no-config-label"></p>
    <p
      id="noSubmittedReports"
      class="hidden"
      data-l10n-id="no-reports-label"
    ></p>

    <div id="reportListUnsubmitted" class="hidden">
      <div class="table-title-container">
        <h2 data-l10n-id="crashes-unsubmitted-label"></h2>
        <button
          id="submitAllUnsubmittedReports"
          class="submit-button"
          data-l10n-id="submit-all-button-label"
        ></button>
        <button
          id="clearUnsubmittedReports"
          data-l10n-id="delete-button-label"
        ></button>
      </div>
      <table>
        <thead>
          <tr>
            <th data-l10n-id="id-heading"></th>
            <th data-l10n-id="date-crashed-heading"></th>
            <th></th>
          </tr>
        </thead>
        <tbody id="unsubmitted"></tbody>
      </table>
    </div>

    <div id="reportListSubmitted" class="hidden">
      <div class="table-title-container">
        <h2 data-l10n-id="crashes-submitted-label"></h2>
        <button
          id="clearSubmittedReports"
          data-l10n-id="delete-button-label"
        ></button>
      </div>
      <table>
        <thead>
          <tr>
            <th data-l10n-id="id-heading"></th>
            <th data-l10n-id="date-submitted-heading"></th>
            <th></th>
          </tr>
        </thead>
        <tbody id="submitted"></tbody>
      </table>
    </div>
  </body>

  <template id="crashReportRow">
    <tr>
      <td class="crash-id"></td>
      <td></td>
      <td></td>
    </tr>
  </template>

  <template id="crashSubmitButton">
    <button class="submit-button">
      <span
        class="submit-crash-button-label"
        data-l10n-id="submit-crash-button-label"
      ></span>
    </button>
  </template>

  <template id="viewCrashLink">
    <a class="crash-link" data-l10n-id="view-crash-button-label"></a>
  </template>
</html>
PK
!<�;���'�'(chrome/toolkit/content/global/crashes.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

let reportURL;

const { CrashReports } = ChromeUtils.importESModule(
  "resource://gre/modules/CrashReports.sys.mjs"
);

ChromeUtils.defineESModuleGetters(this, {
  CrashSubmit: "resource://gre/modules/CrashSubmit.sys.mjs",
});

document.addEventListener("DOMContentLoaded", () => {
  populateReportLists();
  document
    .getElementById("clearUnsubmittedReports")
    .addEventListener("click", () => {
      clearUnsubmittedReports().catch(console.error);
    });
  document
    .getElementById("submitAllUnsubmittedReports")
    .addEventListener("click", () => {
      submitAllUnsubmittedReports().catch(console.error);
    });
  document
    .getElementById("clearSubmittedReports")
    .addEventListener("click", () => {
      clearSubmittedReports().catch(console.error);
    });
});

const buildID = Services.appinfo.appBuildID;

/**
 * Adds the crash reports with submission buttons and links
 * to the unsubmitted and submitted crash report lists.
 * If breakpad.reportURL is not set, displays a misconfiguration message
 * instead.
 */
function populateReportLists() {
  try {
    reportURL = Services.prefs.getCharPref("breakpad.reportURL");
    // Ignore any non http/https urls
    if (!/^https?:/i.test(reportURL)) {
      reportURL = null;
    }
  } catch (e) {
    reportURL = null;
  }
  if (!reportURL) {
    document.getElementById("noConfig").classList.remove("hidden");
    return;
  }

  const reports = CrashReports.getReports();
  const dateFormatter = new Services.intl.DateTimeFormat(undefined, {
    timeStyle: "short",
    dateStyle: "short",
  });
  reports.forEach(report =>
    addReportRow(report.pending, report.id, report.date, dateFormatter)
  );
  showAppropriateSections();
}

/**
 * Adds a crash report with the appropriate submission button
 * or viewing link to the unsubmitted or submitted report list
 * based on isPending.
 *
 * @param {Boolean} isPending     whether the crash is up for submission
 * @param {String}  id            the unique id of the crash report
 * @param {Date}    date          either the date of crash or date of submission
 * @param {Object}  dateFormatter formatter for presenting dates to users
 */
function addReportRow(isPending, id, date, dateFormatter) {
  const rowTemplate = document.getElementById("crashReportRow");
  const row = document
    .importNode(rowTemplate.content, true)
    .querySelector("tr");
  row.id = id;

  const cells = row.querySelectorAll("td");
  cells[0].appendChild(document.createTextNode(id));
  cells[1].appendChild(document.createTextNode(dateFormatter.format(date)));

  if (isPending) {
    const buttonTemplate = document.getElementById("crashSubmitButton");
    const button = document
      .importNode(buttonTemplate.content, true)
      .querySelector("button");
    const buttonText = button.querySelector("span");
    button.addEventListener("click", () =>
      submitPendingReport(id, row, button, buttonText, dateFormatter)
    );
    cells[2].appendChild(button);
    document.getElementById("unsubmitted").appendChild(row);
  } else {
    const linkTemplate = document.getElementById("viewCrashLink");
    const link = document
      .importNode(linkTemplate.content, true)
      .querySelector("a");
    link.href = `${reportURL}${id}`;
    cells[2].appendChild(link);
    document.getElementById("submitted").appendChild(row);
  }
}

/**
 * Shows or hides each of the unsubmitted and submitted report list
 * based on whether they contain at least one crash report.
 * If hidden, the submitted report list is replaced by a message
 * indicating that no crash reports have been submitted.
 */
function showAppropriateSections() {
  let hasUnsubmitted =
    document.getElementById("unsubmitted").childElementCount > 0;
  document
    .getElementById("reportListUnsubmitted")
    .classList.toggle("hidden", !hasUnsubmitted);

  let hasSubmitted = document.getElementById("submitted").childElementCount > 0;
  document
    .getElementById("reportListSubmitted")
    .classList.toggle("hidden", !hasSubmitted);
  document
    .getElementById("noSubmittedReports")
    .classList.toggle("hidden", hasSubmitted);
}

/**
 * Changes the provided button to display a spinner. Then, tries to submit the
 * crash report for the provided id. On success, removes the crash report from
 * the list of unsubmitted crash reports and adds a new crash report to the list
 * of submitted crash reports. On failure, changes the provided button to display
 * a red error message.
 *
 * @param {String}              reportId      the unique id of the crash report
 * @param {HTMLTableRowElement} row           the table row of the crash report
 * @param {HTMLButtonElement}   button        the button pressed to start the submission
 * @param {HTMLSpanElement}     buttonText    the text inside the pressed button
 * @param {Object}              dateFormatter formatter for presenting dates to users
 */
function submitPendingReport(reportId, row, button, buttonText, dateFormatter) {
  button.classList.add("submitting");
  document.getElementById("submitAllUnsubmittedReports").disabled = true;
  CrashSubmit.submit(reportId, CrashSubmit.SUBMITTED_FROM_ABOUT_CRASHES, {
    noThrottle: true,
  })
    .then(
      remoteCrashID => {
        document.getElementById("unsubmitted").removeChild(row);
        const report = CrashReports.getReports().filter(
          report => report.id === remoteCrashID
        );
        addReportRow(false, remoteCrashID, report.date, dateFormatter);
        showAppropriateSections();
        dispatchCustomEvent("CrashSubmitSucceeded");
      },
      () => {
        button.classList.remove("submitting");
        button.classList.add("failed-to-submit");
        document.l10n.setAttributes(
          buttonText,
          "submit-crash-button-failure-label"
        );
        dispatchCustomEvent("CrashSubmitFailed");
      }
    )
    .finally(() => {
      document.getElementById("submitAllUnsubmittedReports").disabled = false;
    });
}

/**
 * Deletes unsubmitted and old crash reports from the user's device.
 * Then, hides the list of unsubmitted crash reports.
 */
async function clearUnsubmittedReports() {
  const [title, description] = await document.l10n.formatValues([
    { id: "delete-confirm-title" },
    { id: "delete-unsubmitted-description" },
  ]);
  if (!Services.prompt.confirm(window, title, description)) {
    return;
  }

  await enqueueCleanup(() => cleanupFolder(CrashReports.pendingDir.path));
  await enqueueCleanup(clearOldReports);
  document.getElementById("reportListUnsubmitted").classList.add("hidden");
}

/**
 * Submits all the pending crash reports and removes all pending reports from pending reports list
 * and add them to submitted crash reports.
 */
async function submitAllUnsubmittedReports() {
  for (
    var i = 0;
    i < document.getElementById("unsubmitted").childNodes.length;
    i++
  ) {
    document
      .getElementById("unsubmitted")
      .childNodes[i].cells[2].childNodes[0].click();
  }
}

/**
 * Deletes submitted and old crash reports from the user's device.
 * Then, hides the list of submitted crash reports.
 */
async function clearSubmittedReports() {
  const [title, description] = await document.l10n.formatValues([
    { id: "delete-confirm-title" },
    { id: "delete-submitted-description" },
  ]);
  if (!Services.prompt.confirm(window, title, description)) {
    return;
  }

  await enqueueCleanup(async () =>
    cleanupFolder(
      CrashReports.submittedDir.path,
      async entry => entry.name.startsWith("bp-") && entry.name.endsWith(".txt")
    )
  );
  await enqueueCleanup(clearOldReports);
  document.getElementById("reportListSubmitted").classList.add("hidden");
  document.getElementById("noSubmittedReports").classList.remove("hidden");
}

/**
 * Deletes old crash reports from the user's device.
 */
async function clearOldReports() {
  const oneYearAgo = Date.now() - 31586000000;
  await cleanupFolder(CrashReports.reportsDir.path, async entry => {
    if (
      !entry.name.startsWith("InstallTime") ||
      entry.name == "InstallTime" + buildID
    ) {
      return false;
    }

    const stat = await IOUtils.stat(entry.path);
    return stat.lastModified < oneYearAgo;
  });
}

/**
 * Deletes files from the user's device at the specified path
 * that match the provided filter.
 *
 * @param {String}   path   the directory location to delete form
 * @param {Function} filter function taking in a file entry and
 *                          returning whether to delete the file
 */
async function cleanupFolder(path, filter) {
  function entry(path) {
    return {
      path,
      name: PathUtils.filename(path),
    };
  }
  let children;
  try {
    children = await IOUtils.getChildren(path);
  } catch (e) {
    if (DOMException.isInstance(e) || e.name !== "NotFoundError") {
      throw e;
    }
  }

  for (const childPath of children) {
    if (!filter || (await filter(entry(childPath)))) {
      await IOUtils.remove(childPath);
    }
  }
}

/**
 * Dispatches an event with the specified name.
 *
 * @param {String} name the name of the event
 */
function dispatchCustomEvent(name) {
  document.dispatchEvent(
    new CustomEvent(name, { bubbles: true, cancelable: false })
  );
}

let cleanupQueue = Promise.resolve();

/**
 * Enqueue a cleanup function.
 *
 * Instead of directly calling cleanup functions as a result of DOM
 * interactions, queue them through this function so that we do not have
 * overlapping executions of cleanup functions.
 *
 * Cleanup functions overlapping could cause a race where one function is
 * attempting to stat a file while another function is attempting to delete it,
 * causing an exception.
 *
 * @param fn The cleanup function to call. It will be called once the last
 *           cleanup function has resolved.
 *
 * @returns A promise to await instead of awaiting the cleanup function.
 */
function enqueueCleanup(fn) {
  cleanupQueue = cleanupQueue.then(fn);
  return cleanupQueue;
}
PK
!<�xuzuz/chrome/toolkit/content/global/customElements.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// This file defines these globals on the window object.
// Define them here so that ESLint can find them:
/* globals MozXULElement, MozHTMLElement, MozElements */

"use strict";

// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
(() => {
  // Handle customElements.js being loaded as a script in addition to the subscriptLoader
  // from MainProcessSingleton, to handle pages that can open both before and after
  // MainProcessSingleton starts. See Bug 1501845.
  if (window.MozXULElement) {
    return;
  }

  const MozElements = {};
  window.MozElements = MozElements;

  const { AppConstants } = ChromeUtils.importESModule(
    "resource://gre/modules/AppConstants.sys.mjs"
  );
  const instrumentClasses = Services.env.get("MOZ_INSTRUMENT_CUSTOM_ELEMENTS");
  const instrumentedClasses = instrumentClasses ? new Set() : null;
  const instrumentedBaseClasses = instrumentClasses ? new WeakSet() : null;

  // If requested, wrap the normal customElements.define to give us a chance
  // to modify the class so we can instrument function calls in local development:
  if (instrumentClasses) {
    let define = window.customElements.define;
    window.customElements.define = function (name, c, opts) {
      instrumentCustomElementClass(c);
      return define.call(this, name, c, opts);
    };
    window.addEventListener(
      "load",
      () => {
        MozElements.printInstrumentation(true);
      },
      { once: true, capture: true }
    );
  }

  MozElements.printInstrumentation = function (collapsed) {
    let summaries = [];
    let totalCalls = 0;
    let totalTime = 0;
    for (let c of instrumentedClasses) {
      // Allow passing in something like MOZ_INSTRUMENT_CUSTOM_ELEMENTS=MozXULElement,Button to filter
      let includeClass =
        instrumentClasses == 1 ||
        instrumentClasses
          .split(",")
          .some(n => c.name.toLowerCase().includes(n.toLowerCase()));
      let summary = c.__instrumentation_summary;
      if (includeClass && summary) {
        summaries.push(summary);
        totalCalls += summary.totalCalls;
        totalTime += summary.totalTime;
      }
    }
    if (summaries.length) {
      let groupName = `Instrumentation data for custom elements in ${document.documentURI}`;
      console[collapsed ? "groupCollapsed" : "group"](groupName);
      console.log(
        `Total function calls ${totalCalls} and total time spent inside ${totalTime.toFixed(
          2
        )}`
      );
      for (let summary of summaries) {
        console.log(`${summary.name} (# instances: ${summary.instances})`);
        if (Object.keys(summary.data).length > 1) {
          console.table(summary.data);
        }
      }
      console.groupEnd(groupName);
    }
  };

  function instrumentCustomElementClass(c) {
    // Climb up prototype chain to see if we inherit from a MozElement.
    // Keep track of classes to instrument, for example:
    //   MozMenuCaption->MozMenuBase->BaseText->BaseControl->MozXULElement
    let inheritsFromBase = instrumentedBaseClasses.has(c);
    let classesToInstrument = [c];
    let proto = Object.getPrototypeOf(c);
    while (proto) {
      classesToInstrument.push(proto);
      if (instrumentedBaseClasses.has(proto)) {
        inheritsFromBase = true;
        break;
      }
      proto = Object.getPrototypeOf(proto);
    }

    if (inheritsFromBase) {
      for (let c of classesToInstrument.reverse()) {
        instrumentIndividualClass(c);
      }
    }
  }

  function instrumentIndividualClass(c) {
    if (instrumentedClasses.has(c)) {
      return;
    }

    instrumentedClasses.add(c);
    let data = { instances: 0 };

    function wrapFunction(name, fn) {
      return function () {
        if (!data[name]) {
          data[name] = { time: 0, calls: 0 };
        }
        data[name].calls++;
        let n = performance.now();
        let r = fn.apply(this, arguments);
        data[name].time += performance.now() - n;
        return r;
      };
    }
    function wrapPropertyDescriptor(obj, name) {
      if (name == "constructor") {
        return;
      }
      let prop = Object.getOwnPropertyDescriptor(obj, name);
      if (prop.get) {
        prop.get = wrapFunction(`<get> ${name}`, prop.get);
      }
      if (prop.set) {
        prop.set = wrapFunction(`<set> ${name}`, prop.set);
      }
      if (prop.writable && prop.value && prop.value.apply) {
        prop.value = wrapFunction(name, prop.value);
      }
      Object.defineProperty(obj, name, prop);
    }

    // Handle static properties
    for (let name of Object.getOwnPropertyNames(c)) {
      wrapPropertyDescriptor(c, name);
    }

    // Handle instance properties
    for (let name of Object.getOwnPropertyNames(c.prototype)) {
      wrapPropertyDescriptor(c.prototype, name);
    }

    c.__instrumentation_data = data;
    Object.defineProperty(c, "__instrumentation_summary", {
      enumerable: false,
      configurable: false,
      get() {
        if (data.instances == 0) {
          return null;
        }

        let clonedData = JSON.parse(JSON.stringify(data));
        delete clonedData.instances;
        let totalCalls = 0;
        let totalTime = 0;
        for (let d in clonedData) {
          let { time, calls } = clonedData[d];
          time = parseFloat(time.toFixed(2));
          totalCalls += calls;
          totalTime += time;
          clonedData[d]["time (ms)"] = time;
          delete clonedData[d].time;
          clonedData[d].timePerCall = parseFloat((time / calls).toFixed(4));
        }

        let timePerCall = parseFloat((totalTime / totalCalls).toFixed(4));
        totalTime = parseFloat(totalTime.toFixed(2));

        // Add a spaced-out final row with summed up totals
        clonedData["\ntotals"] = {
          "time (ms)": `\n${totalTime}`,
          calls: `\n${totalCalls}`,
          timePerCall: `\n${timePerCall}`,
        };
        return {
          instances: data.instances,
          data: clonedData,
          name: c.name,
          totalCalls,
          totalTime,
        };
      },
    });
  }

  // The listener of DOMContentLoaded must be set on window, rather than
  // document, because the window can go away before the event is fired.
  // In that case, we don't want to initialize anything, otherwise we
  // may be leaking things because they will never be destroyed after.
  let gIsDOMContentLoaded = false;
  const gElementsPendingConnection = new Set();
  window.addEventListener(
    "DOMContentLoaded",
    () => {
      gIsDOMContentLoaded = true;
      for (let element of gElementsPendingConnection) {
        try {
          if (element.isConnected) {
            element.isRunningDelayedConnectedCallback = true;
            element.connectedCallback();
          }
        } catch (ex) {
          console.error(ex);
        }
        element.isRunningDelayedConnectedCallback = false;
      }
      gElementsPendingConnection.clear();
    },
    { once: true, capture: true }
  );

  const gXULDOMParser = new DOMParser();
  gXULDOMParser.forceEnableXULXBL();

  MozElements.MozElementMixin = Base => {
    let MozElementBase = class extends Base {
      constructor() {
        super();

        if (instrumentClasses) {
          let proto = this.constructor;
          while (proto && proto != Base) {
            proto.__instrumentation_data.instances++;
            proto = Object.getPrototypeOf(proto);
          }
        }
      }
      /*
       * A declarative way to wire up attribute inheritance and automatically generate
       * the `observedAttributes` getter.  For example, if you returned:
       *    {
       *      ".foo": "bar,baz=bat"
       *    }
       *
       * Then the base class will automatically return ["bar", "bat"] from `observedAttributes`,
       * and set up an `attributeChangedCallback` to pass those attributes down onto an element
       * matching the ".foo" selector.
       *
       * See the `inheritAttribute` function for more details on the attribute string format.
       *
       * @return {Object<string selector, string attributes>}
       */
      static get inheritedAttributes() {
        return null;
      }

      static get flippedInheritedAttributes() {
        // Have to be careful here, if a subclass overrides inheritedAttributes
        // and its parent class is instantiated first, then reading
        // this._flippedInheritedAttributes on the child class will return the
        // computed value from the parent.  We store it separately on each class
        // to ensure everything works correctly when inheritedAttributes is
        // overridden.
        if (!this.hasOwnProperty("_flippedInheritedAttributes")) {
          let { inheritedAttributes } = this;
          if (!inheritedAttributes) {
            this._flippedInheritedAttributes = null;
          } else {
            this._flippedInheritedAttributes = {};
            for (let selector in inheritedAttributes) {
              let attrRules = inheritedAttributes[selector].split(",");
              for (let attrRule of attrRules) {
                let attrName = attrRule;
                let attrNewName = attrRule;
                let split = attrName.split("=");
                if (split.length == 2) {
                  attrName = split[1];
                  attrNewName = split[0];
                }

                if (!this._flippedInheritedAttributes[attrName]) {
                  this._flippedInheritedAttributes[attrName] = [];
                }
                this._flippedInheritedAttributes[attrName].push([
                  selector,
                  attrNewName,
                ]);
              }
            }
          }
        }

        return this._flippedInheritedAttributes;
      }
      /*
       * Generate this array based on `inheritedAttributes`, if any. A class is free to override
       * this if it needs to do something more complex or wants to opt out of this behavior.
       */
      static get observedAttributes() {
        return Object.keys(this.flippedInheritedAttributes || {});
      }

      /*
       * Provide default lifecycle callback for attribute changes that will inherit attributes
       * based on the static `inheritedAttributes` Object. This can be overridden by callers.
       */
      attributeChangedCallback(name, oldValue, newValue) {
        if (oldValue === newValue || !this.initializedAttributeInheritance) {
          return;
        }

        let list = this.constructor.flippedInheritedAttributes[name];
        if (list) {
          this.inheritAttribute(list, name);
        }
      }

      /*
       * After setting content, calling this will cache the elements from selectors in the
       * static `inheritedAttributes` Object. It'll also do an initial call to `this.inheritAttributes()`,
       * so in the simple case, this is the only function you need to call.
       *
       * This should be called any time the children that are inheriting attributes changes. For instance,
       * it's common in a connectedCallback to do something like:
       *
       *   this.textContent = "";
       *   this.append(MozXULElement.parseXULToFragment(`<label />`))
       *   this.initializeAttributeInheritance();
       *
       */
      initializeAttributeInheritance() {
        let { flippedInheritedAttributes } = this.constructor;
        if (!flippedInheritedAttributes) {
          return;
        }

        // Clear out any existing cached elements:
        this._inheritedElements = null;

        this.initializedAttributeInheritance = true;
        for (let attr in flippedInheritedAttributes) {
          if (this.hasAttribute(attr)) {
            this.inheritAttribute(flippedInheritedAttributes[attr], attr);
          }
        }
      }

      /*
       * Implements attribute value inheritance by child elements.
       *
       * @param {array} list
       *        An array of (to-element-selector, to-attr) pairs.
       * @param {string} attr
       *        An attribute to propagate.
       */
      inheritAttribute(list, attr) {
        if (!this._inheritedElements) {
          this._inheritedElements = {};
        }

        let hasAttr = this.hasAttribute(attr);
        let attrValue = this.getAttribute(attr);

        for (let [selector, newAttr] of list) {
          if (!(selector in this._inheritedElements)) {
            this._inheritedElements[selector] =
              this.getElementForAttrInheritance(selector);
          }
          let el = this._inheritedElements[selector];
          if (el) {
            if (newAttr == "text") {
              el.textContent = hasAttr ? attrValue : "";
            } else if (hasAttr) {
              el.setAttribute(newAttr, attrValue);
            } else {
              el.removeAttribute(newAttr);
            }
          }
        }
      }

      /**
       * Used in setting up attribute inheritance. Takes a selector and returns
       * an element for that selector from shadow DOM if there is a shadowRoot,
       * or from the light DOM if not.
       *
       * Here's one problem this solves. ElementB extends ElementA which extends
       * MozXULElement. ElementA has a shadowRoot. ElementB tries to inherit
       * attributes in light DOM by calling `initializeAttributeInheritance`
       * but that fails because it defaults to inheriting from the shadow DOM
       * and not the light DOM. (See bug 1545824.)
       *
       * To solve this, ElementB can override `getElementForAttrInheritance` so
       * it queries the light DOM for some selectors as needed. For example:
       *
       *  class ElementA extends MozXULElement {
       *    static get inheritedAttributes() {
       *      return { ".one": "attr" };
       *    }
       *  }
       *
       *  class ElementB extends customElements.get("elementa") {
       *    static get inheritedAttributes() {
       *      return Object.assign({}, super.inheritedAttributes(), {
       *        ".two": "attr",
       *      });
       *    }
       *    getElementForAttrInheritance(selector) {
       *      if (selector == ".two") {
       *        return this.querySelector(selector)
       *      } else {
       *        return super.getElementForAttrInheritance(selector);
       *      }
       *    }
       *  }
       *
       * @param {string} selector
       *        A selector used to query an element.
       *
       * @return {Element} The element found by the selector.
       */
      getElementForAttrInheritance(selector) {
        let parent = this.shadowRoot || this;
        return parent.querySelector(selector);
      }

      /**
       * Sometimes an element may not want to run connectedCallback logic during
       * parse. This could be because we don't want to initialize the element before
       * the element's contents have been fully parsed, or for performance reasons.
       * If you'd like to opt-in to this, then add this to the beginning of your
       * `connectedCallback` and `disconnectedCallback`:
       *
       *    if (this.delayConnectedCallback()) { return }
       *
       * And this at the beginning of your `attributeChangedCallback`
       *
       *    if (!this.isConnectedAndReady) { return; }
       */
      delayConnectedCallback() {
        if (gIsDOMContentLoaded) {
          return false;
        }
        gElementsPendingConnection.add(this);
        return true;
      }

      get isConnectedAndReady() {
        return gIsDOMContentLoaded && this.isConnected;
      }

      /**
       * Passes DOM events to the on_<event type> methods.
       */
      handleEvent(event) {
        let methodName = "on_" + event.type;
        if (methodName in this) {
          this[methodName](event);
        } else {
          throw new Error("Unrecognized event: " + event.type);
        }
      }

      /**
       * Used by custom elements for caching fragments. We now would be
       * caching once per class while also supporting subclasses.
       *
       * If available, returns the cached fragment.
       * Otherwise, creates it.
       *
       * Example:
       *
       *  class ElementA extends MozXULElement {
       *    static get markup() {
       *      return `<hbox class="example"`;
       *    }
       *
       *    connectedCallback() {
       *      this.appendChild(this.constructor.fragment);
       *    }
       *  }
       *
       * @return {importedNode} The imported node that has not been
       * inserted into document tree.
       */
      static get fragment() {
        if (!this.hasOwnProperty("_fragment")) {
          let markup = this.markup;
          if (markup) {
            this._fragment = MozXULElement.parseXULToFragment(
              markup,
              this.entities
            );
          } else {
            throw new Error("Markup is null");
          }
        }
        return document.importNode(this._fragment, true);
      }

      /**
       * Allows eager deterministic construction of XUL elements with XBL attached, by
       * parsing an element tree and returning a DOM fragment to be inserted in the
       * document before any of the inner elements is referenced by JavaScript.
       *
       * This process is required instead of calling the createElement method directly
       * because bindings get attached when:
       *
       * 1. the node gets a layout frame constructed, or
       * 2. the node gets its JavaScript reflector created, if it's in the document,
       *
       * whichever happens first. The createElement method would return a JavaScript
       * reflector, but the element wouldn't be in the document, so the node wouldn't
       * get XBL attached. After that point, even if the node is inserted into a
       * document, it won't get XBL attached until either the frame is constructed or
       * the reflector is garbage collected and the element is touched again.
       *
       * @param {string} str
       *        String with the XML representation of XUL elements.
       * @param {string[]} [entities]
       *        An array of DTD URLs containing entity definitions.
       *
       * @return {DocumentFragment} `DocumentFragment` instance containing
       *         the corresponding element tree, including element nodes
       *         but excluding any text node.
       */
      static parseXULToFragment(str, entities = []) {
        let doc = gXULDOMParser.parseFromSafeString(
          `
      ${
        entities.length
          ? `<!DOCTYPE bindings [
        ${entities.reduce((preamble, url, index) => {
          return (
            preamble +
            `<!ENTITY % _dtd-${index} SYSTEM "${url}">
            %_dtd-${index};
            `
          );
        }, "")}
      ]>`
          : ""
      }
      <box xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
           xmlns:html="http://www.w3.org/1999/xhtml">
        ${str}
      </box>
    `,
          "application/xml"
        );

        if (doc.documentElement.localName === "parsererror") {
          throw new Error("not well-formed XML");
        }

        // The XUL/XBL parser is set to ignore all-whitespace nodes, whereas (X)HTML
        // does not do this. Most XUL code assumes that the whitespace has been
        // stripped out, so we simply remove all text nodes after using the parser.
        let nodeIterator = doc.createNodeIterator(doc, NodeFilter.SHOW_TEXT);
        let currentNode = nodeIterator.nextNode();
        while (currentNode) {
          // Remove whitespace-only nodes. Regex is taken from:
          // https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace_in_the_DOM
          if (!/[^\t\n\r ]/.test(currentNode.textContent)) {
            currentNode.remove();
          }

          currentNode = nodeIterator.nextNode();
        }
        // We use a range here so that we don't access the inner DOM elements from
        // JavaScript before they are imported and inserted into a document.
        let range = doc.createRange();
        range.selectNodeContents(doc.querySelector("box"));
        return range.extractContents();
      }

      /**
       * Insert a localization link to an FTL file. This is used so that
       * a Custom Element can wait to inject the link until it's connected,
       * and so that consuming documents don't require the correct <link>
       * present in the markup.
       *
       * @param path
       *        The path to the FTL file
       */
      static insertFTLIfNeeded(path) {
        let container = document.head || document.querySelector("linkset");
        if (!container) {
          if (
            document.documentElement.namespaceURI ===
            "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          ) {
            container = document.createXULElement("linkset");
            document.documentElement.appendChild(container);
          } else if (document.documentURI == AppConstants.BROWSER_CHROME_URL) {
            // Special case for browser.xhtml. Here `document.head` is null, so
            // just insert the link at the end of the window.
            container = document.documentElement;
          } else {
            throw new Error(
              "Attempt to inject localization link before document.head is available"
            );
          }
        }

        for (let link of container.querySelectorAll("link")) {
          if (link.getAttribute("href") == path) {
            return;
          }
        }

        let link = document.createElementNS(
          "http://www.w3.org/1999/xhtml",
          "link"
        );
        link.setAttribute("rel", "localization");
        link.setAttribute("href", path);

        container.appendChild(link);
      }

      /**
       * Indicate that a class defining a XUL element implements one or more
       * XPCOM interfaces by adding a getCustomInterface implementation to it,
       * as well as an implementation of QueryInterface.
       *
       * The supplied class should implement the properties and methods of
       * all of the interfaces that are specified.
       *
       * @param cls
       *        The class that implements the interface.
       * @param names
       *        Array of interface names.
       */
      static implementCustomInterface(cls, ifaces) {
        if (cls.prototype.customInterfaces) {
          ifaces.push(...cls.prototype.customInterfaces);
        }
        cls.prototype.customInterfaces = ifaces;

        cls.prototype.QueryInterface = ChromeUtils.generateQI(ifaces);
        cls.prototype.getCustomInterfaceCallback =
          function getCustomInterfaceCallback(ifaceToCheck) {
            if (
              cls.prototype.customInterfaces.some(iface =>
                iface.equals(ifaceToCheck)
              )
            ) {
              return getInterfaceProxy(this);
            }
            return null;
          };
      }
    };

    // Rename the class so we can distinguish between MozXULElement and MozXULPopupElement, for example.
    Object.defineProperty(MozElementBase, "name", { value: `Moz${Base.name}` });
    if (instrumentedBaseClasses) {
      instrumentedBaseClasses.add(MozElementBase);
    }
    return MozElementBase;
  };

  const MozXULElement = MozElements.MozElementMixin(XULElement);
  const MozHTMLElement = MozElements.MozElementMixin(HTMLElement);

  /**
   * Given an object, add a proxy that reflects interface implementations
   * onto the object itself.
   */
  function getInterfaceProxy(obj) {
    /* globals MozQueryInterface */
    if (!obj._customInterfaceProxy) {
      obj._customInterfaceProxy = new Proxy(obj, {
        get(target, prop, receiver) {
          let propOrMethod = target[prop];
          if (typeof propOrMethod == "function") {
            if (MozQueryInterface.isInstance(propOrMethod)) {
              return Reflect.get(target, prop, receiver);
            }
            return function (...args) {
              return propOrMethod.apply(target, args);
            };
          }
          return propOrMethod;
        },
      });
    }

    return obj._customInterfaceProxy;
  }

  MozElements.BaseControlMixin = Base => {
    class BaseControl extends Base {
      get disabled() {
        return this.getAttribute("disabled") == "true";
      }

      set disabled(val) {
        if (val) {
          this.setAttribute("disabled", "true");
        } else {
          this.removeAttribute("disabled");
        }
      }

      get tabIndex() {
        return parseInt(this.getAttribute("tabindex")) || 0;
      }

      set tabIndex(val) {
        if (val) {
          this.setAttribute("tabindex", val);
        } else {
          this.removeAttribute("tabindex");
        }
      }
    }

    MozXULElement.implementCustomInterface(BaseControl, [
      Ci.nsIDOMXULControlElement,
    ]);
    return BaseControl;
  };
  MozElements.BaseControl = MozElements.BaseControlMixin(MozXULElement);

  const BaseTextMixin = Base =>
    class BaseText extends MozElements.BaseControlMixin(Base) {
      set label(val) {
        this.setAttribute("label", val);
      }

      get label() {
        return this.getAttribute("label") || "";
      }

      set image(val) {
        this.setAttribute("image", val);
      }

      get image() {
        return this.getAttribute("image");
      }

      set command(val) {
        this.setAttribute("command", val);
      }

      get command() {
        return this.getAttribute("command");
      }

      set accessKey(val) {
        // Always store on the control
        this.setAttribute("accesskey", val);
        // If there is a label, change the accesskey on the labelElement
        // if it's also set there
        if (this.labelElement) {
          this.labelElement.accessKey = val;
        }
      }

      get accessKey() {
        return this.labelElement?.accessKey || this.getAttribute("accesskey");
      }
    };
  MozElements.BaseTextMixin = BaseTextMixin;
  MozElements.BaseText = BaseTextMixin(MozXULElement);

  // Attach the base class to the window so other scripts can use it:
  window.MozXULElement = MozXULElement;
  window.MozHTMLElement = MozHTMLElement;

  customElements.setElementCreationCallback("browser", () => {
    Services.scriptloader.loadSubScript(
      "chrome://global/content/elements/browser-custom-element.js",
      window
    );
  });

  // Skip loading any extra custom elements in the extension dummy document
  // and GeckoView windows.
  const loadExtraCustomElements = !(
    document.documentURI == "chrome://extensions/content/dummy.xhtml" ||
    document.documentURI == "chrome://geckoview/content/geckoview.xhtml"
  );
  if (loadExtraCustomElements) {
    // Lazily load the following elements
    for (let [tag, script] of [
      ["button-group", "chrome://global/content/elements/named-deck.js"],
      ["findbar", "chrome://global/content/elements/findbar.js"],
      ["menulist", "chrome://global/content/elements/menulist.js"],
      ["named-deck", "chrome://global/content/elements/named-deck.js"],
      ["named-deck-button", "chrome://global/content/elements/named-deck.js"],
      ["panel-list", "chrome://global/content/elements/panel-list.js"],
      ["search-textbox", "chrome://global/content/elements/search-textbox.js"],
      ["stringbundle", "chrome://global/content/elements/stringbundle.js"],
      [
        "printpreview-pagination",
        "chrome://global/content/printPreviewPagination.js",
      ],
      [
        "autocomplete-input",
        "chrome://global/content/elements/autocomplete-input.js",
      ],
      ["editor", "chrome://global/content/elements/editor.js"],
    ]) {
      customElements.setElementCreationCallback(tag, () => {
        Services.scriptloader.loadSubScript(script, window);
      });
    }

    document.addEventListener(
      "DOMContentLoaded",
      () => {
        // Only sync-import widgets once the document has loaded. If a widget is
        // used before DOMContentLoaded it will be imported and upgraded when
        // registering the customElements.setElementCreationCallback().
        for (let [tag, script] of [
          ["moz-button", "chrome://global/content/elements/moz-button.mjs"],
          [
            "moz-button-group",
            "chrome://global/content/elements/moz-button-group.mjs",
          ],
          ["moz-card", "chrome://global/content/elements/moz-card.mjs"],
          ["moz-checkbox", "chrome://global/content/elements/moz-checkbox.mjs"],
          ["moz-fieldset", "chrome://global/content/elements/moz-fieldset.mjs"],
          [
            "moz-five-star",
            "chrome://global/content/elements/moz-five-star.mjs",
          ],
          ["moz-label", "chrome://global/content/elements/moz-label.mjs"],
          [
            "moz-message-bar",
            "chrome://global/content/elements/moz-message-bar.mjs",
          ],
          ["moz-page-nav", "chrome://global/content/elements/moz-page-nav.mjs"],
          ["moz-radio", "chrome://global/content/elements/moz-radio-group.mjs"],
          [
            "moz-radio-group",
            "chrome://global/content/elements/moz-radio-group.mjs",
          ],
          [
            "moz-support-link",
            "chrome://global/content/elements/moz-support-link.mjs",
          ],
          ["moz-toggle", "chrome://global/content/elements/moz-toggle.mjs"],
        ]) {
          if (!customElements.get(tag)) {
            customElements.setElementCreationCallback(
              tag,
              function customElementCreationCallback() {
                ChromeUtils.importESModule(script, { global: "current" });
              }
            );
          }
        }
      },
      { once: true }
    );

    // Immediately load the following elements
    for (let script of [
      "chrome://global/content/elements/arrowscrollbox.js",
      "chrome://global/content/elements/dialog.js",
      "chrome://global/content/elements/general.js",
      "chrome://global/content/elements/button.js",
      "chrome://global/content/elements/checkbox.js",
      "chrome://global/content/elements/menu.js",
      "chrome://global/content/elements/menupopup.js",
      "chrome://global/content/elements/moz-input-box.js",
      "chrome://global/content/elements/notificationbox.js",
      "chrome://global/content/elements/panel.js",
      "chrome://global/content/elements/popupnotification.js",
      "chrome://global/content/elements/radio.js",
      "chrome://global/content/elements/richlistbox.js",
      "chrome://global/content/elements/autocomplete-popup.js",
      "chrome://global/content/elements/autocomplete-richlistitem.js",
      "chrome://global/content/elements/tabbox.js",
      "chrome://global/content/elements/text.js",
      "chrome://global/content/elements/toolbarbutton.js",
      "chrome://global/content/elements/tree.js",
      "chrome://global/content/elements/wizard.js",
    ]) {
      Services.scriptloader.loadSubScript(script, window);
    }
  }
})();
PK
!<��Y�ee.chrome/toolkit/content/global/datepicker.xhtml<?xml version="1.0" encoding="UTF-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this file,
   - You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html [ <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
%htmlDTD; ]>
<html
  xmlns="http://www.w3.org/1999/xhtml"
  xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
>
  <head>
    <link
      rel="stylesheet"
      href="chrome://global/skin/datetimeinputpickers.css"
    />
    <link rel="localization" href="toolkit/global/datepicker.ftl" />
    <script src="chrome://global/content/bindings/datekeeper.js"></script>
    <script src="chrome://global/content/bindings/spinner.js"></script>
    <script src="chrome://global/content/bindings/calendar.js"></script>
    <script src="chrome://global/content/bindings/datepicker.js"></script>
  </head>
  <body>
    <div
      id="date-picker"
      role="dialog"
      data-l10n-id="date-picker-label"
      aria-modal="true"
    >
      <div class="calendar-container">
        <div class="month-year-nav" data-l10n-id="date-spinner-label">
          <button class="prev" data-l10n-id="date-picker-previous" />
          <div class="month-year-container">
            <button
              class="month-year"
              id="month-year-label"
              aria-live="polite"
            />
          </div>
          <button class="next" data-l10n-id="date-picker-next" />
          <template id="spinner-template">
            <div class="spinner-container">
              <button class="up" />
              <div class="spinner"></div>
              <button class="down" />
            </div>
          </template>
          <div class="month-year-view"></div>
        </div>
        <table role="grid" aria-labelledby="month-year-label">
          <thead class="week-header"></thead>
          <tbody class="days-view"></tbody>
        </table>
      </div>
      <button id="clear-button" data-l10n-id="date-picker-clear-button" />
    </div>
    <script>
      /* import-globals-from widgets/datepicker.js */
      // Create a DatePicker instance and prepare to be
      // initialized by the "DatePickerInit" event from datetimepopup.xml
      const root = document.getElementById("date-picker");
      new DatePicker({
        monthYearNav: root.querySelector(".month-year-nav"),
        monthYear: root.querySelector(".month-year"),
        monthYearView: root.querySelector(".month-year-view"),
        buttonPrev: root.querySelector(".prev"),
        buttonNext: root.querySelector(".next"),
        weekHeader: root.querySelector(".week-header"),
        daysView: root.querySelector(".days-view"),
        buttonClear: document.getElementById("clear-button"),
      });
    </script>
  </body>
</html>
PK
!<����EE0chrome/toolkit/content/global/editMenuOverlay.js// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// update menu items that rely on focus or on the current selection
function goUpdateGlobalEditMenuItems(force) {
  // Don't bother updating the edit commands if they aren't visible in any way
  // (i.e. the Edit menu isn't open, nor is the context menu open, nor have the
  // cut, copy, and paste buttons been added to the toolbars) for performance.
  // This only works in applications/on platforms that set the gEditUIVisible
  // flag, so we check to see if that flag is defined before using it.
  if (!force && typeof gEditUIVisible != "undefined" && !gEditUIVisible) {
    return;
  }

  goUpdateUndoEditMenuItems();
  goUpdateCommand("cmd_cut");
  goUpdateCommand("cmd_copy");
  goUpdatePasteMenuItems();
  goUpdateCommand("cmd_selectAll");
  goUpdateCommand("cmd_delete");
  goUpdateCommand("cmd_switchTextDirection");
}

// update menu items that relate to undo/redo
function goUpdateUndoEditMenuItems() {
  goUpdateCommand("cmd_undo");
  goUpdateCommand("cmd_redo");
}

// update menu items that depend on clipboard contents
function goUpdatePasteMenuItems() {
  goUpdateCommand("cmd_paste");
  goUpdateCommand("cmd_pasteNoFormatting");
}

// Inject the commandset here instead of relying on preprocessor to share this across documents.
window.addEventListener(
  "DOMContentLoaded",
  () => {
    let container =
      document.querySelector("commandset") || document.documentElement;
    let fragment = MozXULElement.parseXULToFragment(`
      <commandset id="editMenuCommands">
        <commandset id="editMenuCommandSetAll" commandupdater="true" events="focus,select" />
        <commandset id="editMenuCommandSetUndo" commandupdater="true" events="undo" />
        <commandset id="editMenuCommandSetPaste" commandupdater="true" events="clipboard" />
        <command id="cmd_undo" internal="true"/>
        <command id="cmd_redo" internal="true" />
        <command id="cmd_cut" internal="true" />
        <command id="cmd_copy" internal="true" />
        <command id="cmd_paste" internal="true" />
        <command id="cmd_pasteNoFormatting" internal="true" />
        <command id="cmd_delete" />
        <command id="cmd_selectAll" internal="true" />
        <command id="cmd_switchTextDirection" />
      </commandset>
    `);

    let editMenuCommandSetAll = fragment.querySelector(
      "#editMenuCommandSetAll"
    );
    editMenuCommandSetAll.addEventListener("commandupdate", function () {
      goUpdateGlobalEditMenuItems();
    });

    let editMenuCommandSetUndo = fragment.querySelector(
      "#editMenuCommandSetUndo"
    );
    editMenuCommandSetUndo.addEventListener("commandupdate", function () {
      goUpdateUndoEditMenuItems();
    });

    let editMenuCommandSetPaste = fragment.querySelector(
      "#editMenuCommandSetPaste"
    );
    editMenuCommandSetPaste.addEventListener("commandupdate", function () {
      goUpdatePasteMenuItems();
    });

    fragment.firstElementChild.addEventListener("command", event => {
      let commandID = event.target.id;
      goDoCommand(commandID);
    });

    container.appendChild(fragment);
  },
  { once: true }
);

// Support context menus on html textareas in the parent process:
window.addEventListener("contextmenu", e => {
  const HTML_NS = "http://www.w3.org/1999/xhtml";
  let needsContextMenu =
    e.composedTarget.ownerDocument == document &&
    !e.defaultPrevented &&
    e.composedTarget.parentNode.nodeName != "moz-input-box" &&
    ((["textarea", "input"].includes(e.composedTarget.localName) &&
      e.composedTarget.namespaceURI == HTML_NS) ||
      e.composedTarget.closest("search-textbox"));

  if (!needsContextMenu) {
    return;
  }

  let popup = document.getElementById("textbox-contextmenu");
  if (!popup) {
    MozXULElement.insertFTLIfNeeded("toolkit/global/textActions.ftl");
    document.documentElement.appendChild(
      MozXULElement.parseXULToFragment(`
      <menupopup id="textbox-contextmenu" class="textbox-contextmenu">
        <menuitem data-l10n-id="text-action-undo" command="cmd_undo"></menuitem>
        <menuitem data-l10n-id="text-action-redo" command="cmd_redo"></menuitem>
        <menuseparator></menuseparator>
        <menuitem data-l10n-id="text-action-cut" command="cmd_cut"></menuitem>
        <menuitem data-l10n-id="text-action-copy" command="cmd_copy"></menuitem>
        <menuitem data-l10n-id="text-action-paste" command="cmd_paste"></menuitem>
        <menuitem data-l10n-id="text-action-delete" command="cmd_delete"></menuitem>
        <menuitem data-l10n-id="text-action-select-all" command="cmd_selectAll"></menuitem>
      </menupopup>
    `)
    );
    popup = document.documentElement.lastElementChild;
  }

  goUpdateGlobalEditMenuItems(true);
  popup.openPopupAtScreen(e.screenX, e.screenY, true, e);
  // Don't show any other context menu at the same time. There can be a
  // context menu from an ancestor too but we only want to show this one.
  e.preventDefault();
});
PK
!<Z�1a1a8chrome/toolkit/content/global/elements/arrowscrollbox.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
  class MozArrowScrollbox extends MozElements.BaseControl {
    static #startEndVertical = ["top", "bottom"];
    static #startEndHorizontal = ["left", "right"];
    #scrollButtonUpdatePending = false;

    static get inheritedAttributes() {
      return {
        "#scrollbutton-up": "disabled=scrolledtostart",
        scrollbox: "orient,align,pack,dir,smoothscroll",
        "#scrollbutton-down": "disabled=scrolledtoend",
      };
    }

    get markup() {
      return `
      <html:link rel="stylesheet" href="chrome://global/skin/toolbarbutton.css"/>
      <html:link rel="stylesheet" href="chrome://global/skin/arrowscrollbox.css"/>
      <toolbarbutton id="scrollbutton-up" part="scrollbutton-up" keyNav="false" data-l10n-id="overflow-scroll-button-backwards"/>
      <spacer part="overflow-start-indicator"/>
      <scrollbox part="scrollbox" flex="1">
        <html:slot/>
      </scrollbox>
      <spacer part="overflow-end-indicator"/>
      <toolbarbutton id="scrollbutton-down" part="scrollbutton-down" keyNav="false" data-l10n-id="overflow-scroll-button-forwards"/>
      `;
    }

    constructor() {
      super();
      this.attachShadow({ mode: "open" });
      this.shadowRoot.appendChild(this.fragment);

      this.scrollbox = this.shadowRoot.querySelector("scrollbox");
      this._scrollButtonUp = this.shadowRoot.getElementById("scrollbutton-up");
      this._scrollButtonDown =
        this.shadowRoot.getElementById("scrollbutton-down");

      MozXULElement.insertFTLIfNeeded("toolkit/global/arrowscrollbox.ftl");

      this._arrowScrollAnim = {
        scrollbox: this,
        requestHandle: 0,
        /* 0 indicates there is no pending request */
        start() {
          this.lastFrameTime = window.performance.now();
          if (!this.requestHandle) {
            this.requestHandle = window.requestAnimationFrame(
              this.sample.bind(this)
            );
          }
        },
        stop() {
          window.cancelAnimationFrame(this.requestHandle);
          this.requestHandle = 0;
        },
        sample(timeStamp) {
          const scrollIndex = this.scrollbox._scrollIndex;
          const timePassed = timeStamp - this.lastFrameTime;
          this.lastFrameTime = timeStamp;

          const scrollDelta = 0.5 * timePassed * scrollIndex;
          this.scrollbox.scrollByPixels(scrollDelta, true);
          this.requestHandle = window.requestAnimationFrame(
            this.sample.bind(this)
          );
        },
      };

      this._scrollIndex = 0;
      this._scrollIncrement = null;
      this._ensureElementIsVisibleAnimationFrame = 0;
      this._prevMouseScrolls = [null, null];
      this._touchStart = -1;
      this._isScrolling = false;
      this._destination = 0;
      this._direction = 0;

      this.addEventListener("wheel", this);
      this.addEventListener("touchstart", this);
      this.addEventListener("touchmove", this);
      this.addEventListener("touchend", this);
      this.shadowRoot.addEventListener("click", this);
      this.shadowRoot.addEventListener("mousedown", this);
      this.shadowRoot.addEventListener("mouseover", this);
      this.shadowRoot.addEventListener("mouseup", this);
      this.shadowRoot.addEventListener("mouseout", this);
      this.scrollbox.addEventListener("scroll", this);
      this.scrollbox.addEventListener("scrollend", this);

      let slot = this.shadowRoot.querySelector("slot");
      let overflowObserver = new ResizeObserver(_ => {
        let contentSize =
          slot.getBoundingClientRect()[this.#verticalMode ? "height" : "width"];
        // NOTE(emilio): This should be contentSize > scrollClientSize, but due
        // to how Gecko internally rounds in those cases, we allow for some
        // minor differences (the internal Gecko layout size is 1/60th of a
        // pixel, so 0.02 should cover it).
        let overflowing = contentSize - this.scrollClientSize > 0.02;
        if (overflowing == this.overflowing) {
          if (overflowing) {
            // Update scroll buttons' disabled state when the slot or scrollbox
            // size changes while we were already overflowing.
            this.#updateScrollButtonsDisabledState(/* aRafCount = */ 1);
          }
          return;
        }
        window.requestAnimationFrame(() => {
          this.toggleAttribute("overflowing", overflowing);
          this.#updateScrollButtonsDisabledState(/* aRafCount = */ 1);
          this.dispatchEvent(
            new CustomEvent(overflowing ? "overflow" : "underflow")
          );
        });
      });
      overflowObserver.observe(slot);
      overflowObserver.observe(this.scrollbox);
    }

    connectedCallback() {
      this.removeAttribute("overflowing");

      if (this.hasConnected) {
        return;
      }
      this.hasConnected = true;

      document.l10n.connectRoot(this.shadowRoot);

      if (!this.hasAttribute("smoothscroll")) {
        this.smoothScroll = Services.prefs.getBoolPref(
          "toolkit.scrollbox.smoothScroll",
          true
        );
      }

      this.initializeAttributeInheritance();
      this.#updateScrollButtonsDisabledState();
    }

    get overflowing() {
      return this.hasAttribute("overflowing");
    }

    get fragment() {
      if (!this.constructor.hasOwnProperty("_fragment")) {
        this.constructor._fragment = MozXULElement.parseXULToFragment(
          this.markup
        );
      }
      return document.importNode(this.constructor._fragment, true);
    }

    get #verticalMode() {
      return this.getAttribute("orient") == "vertical";
    }

    get _clickToScroll() {
      return this.hasAttribute("clicktoscroll");
    }

    get _scrollDelay() {
      if (this._clickToScroll) {
        return Services.prefs.getIntPref(
          "toolkit.scrollbox.clickToScroll.scrollDelay",
          150
        );
      }

      // Use the same REPEAT_DELAY as "nsRepeatService.h".
      return /Mac/.test(navigator.platform) ? 25 : 50;
    }

    get scrollIncrement() {
      if (this._scrollIncrement === null) {
        this._scrollIncrement = Services.prefs.getIntPref(
          "toolkit.scrollbox.scrollIncrement",
          20
        );
      }
      return this._scrollIncrement;
    }

    set smoothScroll(val) {
      this.setAttribute("smoothscroll", !!val);
    }

    get smoothScroll() {
      return this.getAttribute("smoothscroll") == "true";
    }

    get scrollClientRect() {
      return this.scrollbox.getBoundingClientRect();
    }

    get scrollClientSize() {
      return this.scrollbox[
        this.#verticalMode ? "clientHeightDouble" : "clientWidthDouble"
      ];
    }

    get scrollSize() {
      return this.scrollbox[
        this.#verticalMode ? "scrollHeight" : "scrollWidth"
      ];
    }

    get lineScrollAmount() {
      // line scroll amout should be the width (at horizontal scrollbox) or
      // the height (at vertical scrollbox) of the scrolled elements.
      // However, the elements may have different width or height.  So,
      // for consistent speed, let's use average width of the elements.
      var elements = this._getScrollableElements();
      return elements.length && this.scrollSize / elements.length;
    }

    get scrollPosition() {
      return this.scrollbox[this.#verticalMode ? "scrollTop" : "scrollLeft"];
    }

    get startEndProps() {
      return this.#verticalMode
        ? MozArrowScrollbox.#startEndVertical
        : MozArrowScrollbox.#startEndHorizontal;
    }

    get isRTLScrollbox() {
      if (this.#verticalMode) {
        return false;
      }
      if (!("_isRTLScrollbox" in this)) {
        this._isRTLScrollbox =
          document.defaultView.getComputedStyle(this.scrollbox).direction ==
          "rtl";
      }
      return this._isRTLScrollbox;
    }

    _onButtonClick(event) {
      if (this._clickToScroll) {
        this._distanceScroll(event);
      }
    }

    _onButtonMouseDown(event, index) {
      if (this._clickToScroll && event.button == 0) {
        this._startScroll(index);
      }
    }

    _onButtonMouseUp(event) {
      if (this._clickToScroll && event.button == 0) {
        this._stopScroll();
      }
    }

    _onButtonMouseOver(index) {
      if (this._clickToScroll) {
        this._continueScroll(index);
      } else {
        this._startScroll(index);
      }
    }

    _onButtonMouseOut() {
      if (this._clickToScroll) {
        this._pauseScroll();
      } else {
        this._stopScroll();
      }
    }

    _boundsWithoutFlushing(element) {
      if (!("_DOMWindowUtils" in this)) {
        this._DOMWindowUtils = window.windowUtils;
      }

      return this._DOMWindowUtils
        ? this._DOMWindowUtils.getBoundsWithoutFlushing(element)
        : element.getBoundingClientRect();
    }

    _canScrollToElement(element) {
      if (element.hidden) {
        return false;
      }

      // See if the element is hidden via CSS without the hidden attribute.
      // If we get only zeros for the client rect, this means the element
      // is hidden. As a performance optimization, we don't flush layout
      // here which means that on the fly changes aren't fully supported.
      let rect = this._boundsWithoutFlushing(element);
      return !!(rect.top || rect.left || rect.width || rect.height);
    }

    ensureElementIsVisible(element, aInstant) {
      if (!this._canScrollToElement(element)) {
        return;
      }

      if (this._ensureElementIsVisibleAnimationFrame) {
        window.cancelAnimationFrame(this._ensureElementIsVisibleAnimationFrame);
      }
      this._ensureElementIsVisibleAnimationFrame = window.requestAnimationFrame(
        () => {
          element.scrollIntoView({
            block: "nearest",
            behavior: aInstant ? "instant" : "auto",
          });
          this._ensureElementIsVisibleAnimationFrame = 0;
        }
      );
    }

    scrollByIndex(index, aInstant) {
      if (index == 0) {
        return;
      }

      var rect = this.scrollClientRect;
      var [start, end] = this.startEndProps;
      var x = index > 0 ? rect[end] + 1 : rect[start] - 1;
      var nextElement = this._elementFromPoint(x, index);
      if (!nextElement) {
        return;
      }

      var targetElement;
      if (this.isRTLScrollbox) {
        index *= -1;
      }
      while (index < 0 && nextElement) {
        if (this._canScrollToElement(nextElement)) {
          targetElement = nextElement;
        }
        nextElement = nextElement.previousElementSibling;
        index++;
      }
      while (index > 0 && nextElement) {
        if (this._canScrollToElement(nextElement)) {
          targetElement = nextElement;
        }
        nextElement = nextElement.nextElementSibling;
        index--;
      }
      if (!targetElement) {
        return;
      }

      this.ensureElementIsVisible(targetElement, aInstant);
    }

    _getScrollableElements() {
      let nodes = this.children;
      if (nodes.length == 1) {
        let node = nodes[0];
        if (
          node.localName == "slot" &&
          node.namespaceURI == "http://www.w3.org/1999/xhtml"
        ) {
          nodes = node.getRootNode().host.children;
        }
      }
      return Array.prototype.filter.call(nodes, this._canScrollToElement, this);
    }

    _elementFromPoint(aX, aPhysicalScrollDir) {
      var elements = this._getScrollableElements();
      if (!elements.length) {
        return null;
      }

      if (this.isRTLScrollbox) {
        elements.reverse();
      }

      var [start, end] = this.startEndProps;
      var low = 0;
      var high = elements.length - 1;

      if (
        aX < elements[low].getBoundingClientRect()[start] ||
        aX > elements[high].getBoundingClientRect()[end]
      ) {
        return null;
      }

      var mid, rect;
      while (low <= high) {
        mid = Math.floor((low + high) / 2);
        rect = elements[mid].getBoundingClientRect();
        if (rect[start] > aX) {
          high = mid - 1;
        } else if (rect[end] < aX) {
          low = mid + 1;
        } else {
          return elements[mid];
        }
      }

      // There's no element at the requested coordinate, but the algorithm
      // from above yields an element next to it, in a random direction.
      // The desired scrolling direction leads to the correct element.

      if (!aPhysicalScrollDir) {
        return null;
      }

      if (aPhysicalScrollDir < 0 && rect[start] > aX) {
        mid = Math.max(mid - 1, 0);
      } else if (aPhysicalScrollDir > 0 && rect[end] < aX) {
        mid = Math.min(mid + 1, elements.length - 1);
      }

      return elements[mid];
    }

    _startScroll(index) {
      if (this.isRTLScrollbox) {
        index *= -1;
      }

      if (this._clickToScroll) {
        this._scrollIndex = index;
        this._mousedown = true;

        if (this.smoothScroll) {
          this._arrowScrollAnim.start();
          return;
        }
      }

      if (!this._scrollTimer) {
        this._scrollTimer = Cc["@mozilla.org/timer;1"].createInstance(
          Ci.nsITimer
        );
      } else {
        this._scrollTimer.cancel();
      }

      let callback;
      if (this._clickToScroll) {
        callback = () => {
          if (!document && this._scrollTimer) {
            this._scrollTimer.cancel();
          }
          this.scrollByIndex(this._scrollIndex);
        };
      } else {
        callback = () => this.scrollByPixels(this.scrollIncrement * index);
      }

      this._scrollTimer.initWithCallback(
        callback,
        this._scrollDelay,
        Ci.nsITimer.TYPE_REPEATING_SLACK
      );

      callback();
    }

    _stopScroll() {
      if (this._scrollTimer) {
        this._scrollTimer.cancel();
      }

      if (this._clickToScroll) {
        this._mousedown = false;
        if (!this._scrollIndex || !this.smoothScroll) {
          return;
        }

        this.scrollByIndex(this._scrollIndex);
        this._scrollIndex = 0;

        this._arrowScrollAnim.stop();
      }
    }

    _pauseScroll() {
      if (this._mousedown) {
        this._stopScroll();
        this._mousedown = true;

        let mouseUpOrBlur = aEvent => {
          if (
            aEvent.type == "mouseup" ||
            (aEvent.type == "blur" && aEvent.target == document)
          ) {
            this._mousedown = false;
            document.removeEventListener("mouseup", mouseUpOrBlur);
            document.removeEventListener("blur", mouseUpOrBlur, true);
          }
        };
        document.addEventListener("mouseup", mouseUpOrBlur);
        document.addEventListener("blur", mouseUpOrBlur, true);
      }
    }

    _continueScroll(index) {
      if (this._mousedown) {
        this._startScroll(index);
      }
    }

    _distanceScroll(aEvent) {
      if (aEvent.detail < 2 || aEvent.detail > 3) {
        return;
      }

      var scrollBack = aEvent.originalTarget == this._scrollButtonUp;
      var scrollLeftOrUp = this.isRTLScrollbox ? !scrollBack : scrollBack;
      var targetElement;

      if (aEvent.detail == 2) {
        // scroll by the size of the scrollbox
        let [start, end] = this.startEndProps;
        let x;
        if (scrollLeftOrUp) {
          x = this.scrollClientRect[start] - this.scrollClientSize;
        } else {
          x = this.scrollClientRect[end] + this.scrollClientSize;
        }
        targetElement = this._elementFromPoint(x, scrollLeftOrUp ? -1 : 1);

        // the next partly-hidden element will become fully visible,
        // so don't scroll too far
        if (targetElement) {
          targetElement = scrollBack
            ? targetElement.nextElementSibling
            : targetElement.previousElementSibling;
        }
      }

      if (!targetElement) {
        // scroll to the first resp. last element
        let elements = this._getScrollableElements();
        targetElement = scrollBack
          ? elements[0]
          : elements[elements.length - 1];
      }

      this.ensureElementIsVisible(targetElement);
    }

    scrollByPixels(aPixels, aInstant) {
      let scrollOptions = { behavior: aInstant ? "instant" : "auto" };
      scrollOptions[this.startEndProps[0]] = aPixels;
      this.scrollbox.scrollBy(scrollOptions);
    }

    // @param aRafCount how many animation frames we need to wait to get
    // current layout data from getBoundsWithoutFlushing.
    #updateScrollButtonsDisabledState(aRafCount = 2) {
      if (!this.overflowing) {
        this.toggleAttribute("scrolledtoend", true);
        this.toggleAttribute("scrolledtostart", true);
        this.#scrollButtonUpdatePending = false;
        return;
      }

      if (aRafCount) {
        if (this.#scrollButtonUpdatePending) {
          return;
        }

        this.#scrollButtonUpdatePending = true;
        let oneIter = () => {
          if (aRafCount--) {
            if (this.#scrollButtonUpdatePending && this.isConnected) {
              window.requestAnimationFrame(oneIter);
            }
          } else {
            this.#updateScrollButtonsDisabledState(0);
          }
        };
        oneIter();
        return;
      }

      this.#scrollButtonUpdatePending = false;

      let scrolledToStart = false;
      let scrolledToEnd = false;

      if (!this.overflowing) {
        scrolledToStart = true;
        scrolledToEnd = true;
      } else {
        let isAtEdge = (element, start) => {
          let edge = start ? this.startEndProps[0] : this.startEndProps[1];
          let scrollEdge = this._boundsWithoutFlushing(this.scrollbox)[edge];
          let elementEdge = this._boundsWithoutFlushing(element)[edge];
          // This is enough slop (>2/3) so that no subpixel value should
          // get us confused about whether we reached the end.
          const EPSILON = 0.7;
          if (start) {
            return scrollEdge <= elementEdge + EPSILON;
          }
          return elementEdge <= scrollEdge + EPSILON;
        };

        let elements = this._getScrollableElements();
        let [startElement, endElement] = [
          elements[0],
          elements[elements.length - 1],
        ];
        if (this.isRTLScrollbox) {
          [startElement, endElement] = [endElement, startElement];
        }
        scrolledToStart =
          startElement && isAtEdge(startElement, /* start = */ true);
        scrolledToEnd = endElement && isAtEdge(endElement, /* start = */ false);
        if (this.isRTLScrollbox) {
          [scrolledToStart, scrolledToEnd] = [scrolledToEnd, scrolledToStart];
        }
      }

      this.toggleAttribute("scrolledtoend", scrolledToEnd);
      this.toggleAttribute("scrolledtostart", scrolledToStart);
    }

    disconnectedCallback() {
      // Release timer to avoid reference cycles.
      if (this._scrollTimer) {
        this._scrollTimer.cancel();
        this._scrollTimer = null;
      }
      document.l10n.disconnectRoot(this.shadowRoot);
    }

    on_wheel(event) {
      // Don't consume the event if we can't scroll.
      if (!this.overflowing) {
        return;
      }

      const { deltaMode } = event;
      let doScroll = false;
      let instant;
      let scrollAmount = 0;
      if (this.#verticalMode) {
        doScroll = true;
        scrollAmount = event.deltaY;
        if (deltaMode == event.DOM_DELTA_PIXEL) {
          instant = true;
        }
      } else {
        // We allow vertical scrolling to scroll a horizontal scrollbox
        // because many users have a vertical scroll wheel but no
        // horizontal support.
        // Because of this, we need to avoid scrolling chaos on trackpads
        // and mouse wheels that support simultaneous scrolling in both axes.
        // We do this by scrolling only when the last two scroll events were
        // on the same axis as the current scroll event.
        // For diagonal scroll events we only respect the dominant axis.
        let isVertical = Math.abs(event.deltaY) > Math.abs(event.deltaX);
        let delta = isVertical ? event.deltaY : event.deltaX;
        let scrollByDelta = isVertical && this.isRTLScrollbox ? -delta : delta;

        if (this._prevMouseScrolls.every(prev => prev == isVertical)) {
          doScroll = true;
          scrollAmount = scrollByDelta;
          if (deltaMode == event.DOM_DELTA_PIXEL) {
            instant = true;
          }
        }

        if (this._prevMouseScrolls.length > 1) {
          this._prevMouseScrolls.shift();
        }
        this._prevMouseScrolls.push(isVertical);
      }

      if (doScroll) {
        let direction = scrollAmount < 0 ? -1 : 1;

        if (deltaMode == event.DOM_DELTA_PAGE) {
          scrollAmount *= this.scrollClientSize;
        } else if (deltaMode == event.DOM_DELTA_LINE) {
          // Try to not scroll by more than one page when using line scrolling,
          // so that all elements are scrollable.
          let lineAmount = this.lineScrollAmount;
          let clientSize = this.scrollClientSize;
          if (Math.abs(scrollAmount * lineAmount) > clientSize) {
            // NOTE: This still tries to scroll a non-fractional amount of
            // items per line scrolled.
            scrollAmount =
              Math.max(1, Math.floor(clientSize / lineAmount)) * direction;
          }
          scrollAmount *= lineAmount;
        } else {
          // DOM_DELTA_PIXEL, leave scrollAmount untouched.
        }
        let startPos = this.scrollPosition;

        if (!this._isScrolling || this._direction != direction) {
          this._destination = startPos + scrollAmount;
          this._direction = direction;
        } else {
          // We were already in the process of scrolling in this direction
          this._destination = this._destination + scrollAmount;
          scrollAmount = this._destination - startPos;
        }
        this.scrollByPixels(scrollAmount, instant);
      }

      event.stopPropagation();
      event.preventDefault();
    }

    on_touchstart(event) {
      if (event.touches.length > 1) {
        // Multiple touch points detected, abort. In particular this aborts
        // the panning gesture when the user puts a second finger down after
        // already panning with one finger. Aborting at this point prevents
        // the pan gesture from being resumed until all fingers are lifted
        // (as opposed to when the user is back down to one finger).
        this._touchStart = -1;
      } else {
        this._touchStart =
          event.touches[0][this.#verticalMode ? "screenY" : "screenX"];
      }
    }

    on_touchmove(event) {
      if (event.touches.length == 1 && this._touchStart >= 0) {
        let touchPoint =
          event.touches[0][this.#verticalMode ? "screenY" : "screenX"];
        let delta = this._touchStart - touchPoint;
        if (Math.abs(delta) > 0) {
          this.scrollByPixels(delta, true);
          this._touchStart = touchPoint;
        }
        event.preventDefault();
      }
    }

    on_touchend() {
      this._touchStart = -1;
    }

    on_scroll() {
      this._isScrolling = true;
      this.#updateScrollButtonsDisabledState();
      this.dispatchEvent(new Event("scroll"));
    }

    on_scrollend() {
      this._isScrolling = false;
      this._destination = 0;
      this._direction = 0;
      this.dispatchEvent(new Event("scrollend"));
    }

    on_click(event) {
      if (
        event.originalTarget != this._scrollButtonUp &&
        event.originalTarget != this._scrollButtonDown
      ) {
        return;
      }
      this._onButtonClick(event);
    }

    on_mousedown(event) {
      if (event.originalTarget == this._scrollButtonUp) {
        this._onButtonMouseDown(event, -1);
      }
      if (event.originalTarget == this._scrollButtonDown) {
        this._onButtonMouseDown(event, 1);
      }
    }

    on_mouseup(event) {
      if (
        event.originalTarget != this._scrollButtonUp &&
        event.originalTarget != this._scrollButtonDown
      ) {
        return;
      }
      this._onButtonMouseUp(event);
    }

    on_mouseover(event) {
      if (event.originalTarget == this._scrollButtonUp) {
        this._onButtonMouseOver(-1);
      }
      if (event.originalTarget == this._scrollButtonDown) {
        this._onButtonMouseOver(1);
      }
    }

    on_mouseout(event) {
      if (
        event.originalTarget != this._scrollButtonUp &&
        event.originalTarget != this._scrollButtonDown
      ) {
        return;
      }
      this._onButtonMouseOut();
    }
  }

  customElements.define("arrowscrollbox", MozArrowScrollbox);
}
PK
!<^�X�B�B<chrome/toolkit/content/global/elements/autocomplete-input.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
  const { AppConstants } = ChromeUtils.importESModule(
    "resource://gre/modules/AppConstants.sys.mjs"
  );
  const { XPCOMUtils } = ChromeUtils.importESModule(
    "resource://gre/modules/XPCOMUtils.sys.mjs"
  );

  class AutocompleteInput extends HTMLInputElement {
    constructor() {
      super();

      this.popupSelectedIndex = -1;

      ChromeUtils.defineESModuleGetters(this, {
        PrivateBrowsingUtils:
          "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
      });

      XPCOMUtils.defineLazyPreferenceGetter(
        this,
        "disablePopupAutohide",
        "ui.popup.disable_autohide",
        false
      );

      this.addEventListener("input", event => {
        this.onInput(event);
      });

      this.addEventListener("keydown", event => this.handleKeyDown(event));

      this.addEventListener(
        "compositionstart",
        () => {
          if (
            this.mController.input.wrappedJSObject == this.nsIAutocompleteInput
          ) {
            this.mController.handleStartComposition();
          }
        },
        true
      );

      this.addEventListener(
        "compositionend",
        () => {
          if (
            this.mController.input.wrappedJSObject == this.nsIAutocompleteInput
          ) {
            this.mController.handleEndComposition();
          }
        },
        true
      );

      this.addEventListener(
        "focus",
        () => {
          this.attachController();
          if (
            window.gBrowser &&
            window.gBrowser.selectedBrowser.hasAttribute("usercontextid")
          ) {
            this.userContextId = parseInt(
              window.gBrowser.selectedBrowser.getAttribute("usercontextid")
            );
          } else {
            this.userContextId = 0;
          }
        },
        true
      );

      this.addEventListener(
        "blur",
        () => {
          if (!this._dontBlur) {
            if (this.forceComplete && this.mController.matchCount >= 1) {
              // If forceComplete is requested, we need to call the enter processing
              // on blur so the input will be forced to the closest match.
              // Thunderbird is the only consumer of forceComplete and this is used
              // to force an recipient's email to the exact address book entry.
              this.mController.handleEnter(true);
            }
            if (!this.ignoreBlurWhileSearching) {
              this._dontClosePopup = this.disablePopupAutohide;
              this.detachController();
            }
          }
        },
        true
      );
    }

    connectedCallback() {
      this.setAttribute("is", "autocomplete-input");
      this.setAttribute("autocomplete", "off");

      this.mController = Cc[
        "@mozilla.org/autocomplete/controller;1"
      ].getService(Ci.nsIAutoCompleteController);
      this.mSearchNames = null;
      this.mIgnoreInput = false;
      this.noRollupOnEmptySearch = false;

      this._popup = null;

      this.nsIAutocompleteInput = this.getCustomInterfaceCallback(
        Ci.nsIAutoCompleteInput
      );

      this.valueIsTyped = false;
    }

    get popup() {
      // Memoize the result in a field rather than replacing this property,
      // so that it can be reset along with the binding.
      if (this._popup) {
        return this._popup;
      }

      let popup = null;
      let popupId = this.getAttribute("autocompletepopup");
      if (popupId) {
        popup = document.getElementById(popupId);
      }

      /* This path is only used in tests, we have the <popupset> and <panel>
         in document for other usages */
      if (!popup) {
        popup = document.createXULElement("panel", {
          is: "autocomplete-richlistbox-popup",
        });
        popup.setAttribute("type", "autocomplete-richlistbox");
        popup.setAttribute("noautofocus", "true");

        if (!this._popupset) {
          this._popupset = document.createXULElement("popupset");
          document.documentElement.appendChild(this._popupset);
        }

        this._popupset.appendChild(popup);
      }
      popup.mInput = this;

      return (this._popup = popup);
    }

    get popupElement() {
      return this.popup;
    }

    get controller() {
      return this.mController;
    }

    set popupOpen(val) {
      if (val) {
        this.openPopup();
      } else {
        this.closePopup();
      }
    }

    get popupOpen() {
      return this.popup.popupOpen;
    }

    set disableAutoComplete(val) {
      this.setAttribute("disableautocomplete", val);
    }

    get disableAutoComplete() {
      return this.getAttribute("disableautocomplete") == "true";
    }

    set completeDefaultIndex(val) {
      this.setAttribute("completedefaultindex", val);
    }

    get completeDefaultIndex() {
      return this.getAttribute("completedefaultindex") == "true";
    }

    set completeSelectedIndex(val) {
      this.setAttribute("completeselectedindex", val);
    }

    get completeSelectedIndex() {
      return this.getAttribute("completeselectedindex") == "true";
    }

    set forceComplete(val) {
      this.setAttribute("forcecomplete", val);
    }

    get forceComplete() {
      return this.getAttribute("forcecomplete") == "true";
    }

    set minResultsForPopup(val) {
      this.setAttribute("minresultsforpopup", val);
    }

    get minResultsForPopup() {
      var m = parseInt(this.getAttribute("minresultsforpopup"));
      return isNaN(m) ? 1 : m;
    }

    set timeout(val) {
      this.setAttribute("timeout", val);
    }

    get timeout() {
      var t = parseInt(this.getAttribute("timeout"));
      return isNaN(t) ? 50 : t;
    }

    set searchParam(val) {
      this.setAttribute("autocompletesearchparam", val);
    }

    get searchParam() {
      return this.getAttribute("autocompletesearchparam") || "";
    }

    get searchCount() {
      this.initSearchNames();
      return this.mSearchNames.length;
    }

    get inPrivateContext() {
      return this.PrivateBrowsingUtils.isWindowPrivate(window);
    }

    get noRollupOnCaretMove() {
      return this.popup.getAttribute("norolluponanchor") == "true";
    }

    set textValue(val) {
      // "input" event is automatically dispatched by the editor if
      // necessary.
      this._setValueInternal(val, true);
    }

    get textValue() {
      return this.value;
    }
    /**
     * =================== nsIDOMXULMenuListElement ===================
     */
    get editable() {
      return true;
    }

    set open(val) {
      if (val) {
        this.showHistoryPopup();
      } else {
        this.closePopup();
      }
    }

    get open() {
      return this.getAttribute("open") == "true";
    }

    set value(val) {
      this._setValueInternal(val, false);
    }

    get value() {
      return super.value;
    }

    get focused() {
      return this === document.activeElement;
    }
    /**
     * maximum number of rows to display at a time when opening the popup normally
     * (e.g., focus element and press the down arrow)
     */
    set maxRows(val) {
      this.setAttribute("maxrows", val);
    }

    get maxRows() {
      return parseInt(this.getAttribute("maxrows")) || 0;
    }
    /**
     * maximum number of rows to display at a time when opening the popup by
     * clicking the dropmarker (for inputs that have one)
     */
    set maxdropmarkerrows(val) {
      this.setAttribute("maxdropmarkerrows", val);
    }

    get maxdropmarkerrows() {
      return parseInt(this.getAttribute("maxdropmarkerrows"), 10) || 14;
    }
    /**
     * option to allow scrolling through the list via the tab key, rather than
     * tab moving focus out of the textbox
     */
    set tabScrolling(val) {
      this.setAttribute("tabscrolling", val);
    }

    get tabScrolling() {
      return this.getAttribute("tabscrolling") == "true";
    }
    /**
     * option to completely ignore any blur events while searches are
     * still going on.
     */
    set ignoreBlurWhileSearching(val) {
      this.setAttribute("ignoreblurwhilesearching", val);
    }

    get ignoreBlurWhileSearching() {
      return this.getAttribute("ignoreblurwhilesearching") == "true";
    }
    /**
     * option to highlight entries that don't have any matches
     */
    set highlightNonMatches(val) {
      this.setAttribute("highlightnonmatches", val);
    }

    get highlightNonMatches() {
      return this.getAttribute("highlightnonmatches") == "true";
    }

    getSearchAt(aIndex) {
      this.initSearchNames();
      return this.mSearchNames[aIndex];
    }

    selectTextRange(aStartIndex, aEndIndex) {
      super.setSelectionRange(aStartIndex, aEndIndex);
    }

    onSearchBegin() {
      if (this.popup && typeof this.popup.onSearchBegin == "function") {
        this.popup.onSearchBegin();
      }
    }

    onSearchComplete() {
      if (this.mController.matchCount == 0) {
        this.setAttribute("nomatch", "true");
      } else {
        this.removeAttribute("nomatch");
      }

      if (this.ignoreBlurWhileSearching && !this.focused) {
        this.handleEnter();
        this.detachController();
      }
    }

    onTextEntered(event) {
      if (this.getAttribute("notifylegacyevents") === "true") {
        let e = new CustomEvent("textEntered", {
          bubbles: false,
          cancelable: true,
          detail: { rootEvent: event },
        });
        return !this.dispatchEvent(e);
      }
      return false;
    }

    onTextReverted(event) {
      if (this.getAttribute("notifylegacyevents") === "true") {
        let e = new CustomEvent("textReverted", {
          bubbles: false,
          cancelable: true,
          detail: { rootEvent: event },
        });
        return !this.dispatchEvent(e);
      }
      return false;
    }

    /**
     * =================== PRIVATE MEMBERS ===================
     */

    /*
     * ::::::::::::: autocomplete controller :::::::::::::
     */

    attachController() {
      this.mController.input = this.nsIAutocompleteInput;
    }

    detachController() {
      if (
        this.mController.input &&
        this.mController.input.wrappedJSObject == this.nsIAutocompleteInput
      ) {
        this.mController.input = null;
      }
    }

    /**
     * ::::::::::::: popup opening :::::::::::::
     */
    openPopup() {
      if (this.focused) {
        this.popup.openAutocompletePopup(this.nsIAutocompleteInput, this);
      }
    }

    closePopup() {
      if (this._dontClosePopup) {
        delete this._dontClosePopup;
        return;
      }
      this.popup.closePopup();
    }

    showHistoryPopup() {
      // Store our "normal" maxRows on the popup, so that it can reset the
      // value when the popup is hidden.
      this.popup._normalMaxRows = this.maxRows;

      // Temporarily change our maxRows, since we want the dropdown to be a
      // different size in this case. The popup's popupshowing/popuphiding
      // handlers will take care of resetting this.
      this.maxRows = this.maxdropmarkerrows;

      // Ensure that we have focus.
      if (!this.focused) {
        this.focus();
      }
      this.attachController();
      this.mController.startSearch("");
    }

    toggleHistoryPopup() {
      if (!this.popup.popupOpen) {
        this.showHistoryPopup();
      } else {
        this.closePopup();
      }
    }

    handleKeyDown(aEvent) {
      // Re: urlbarDeferred, see the comment in urlbarBindings.xml.
      if (aEvent.defaultPrevented && !aEvent.urlbarDeferred) {
        return false;
      }

      if (
        typeof this.onBeforeHandleKeyDown == "function" &&
        this.onBeforeHandleKeyDown(aEvent)
      ) {
        return true;
      }

      const isMac = AppConstants.platform == "macosx";
      var cancel = false;

      // Catch any keys that could potentially move the caret. Ctrl can be
      // used in combination with these keys on Windows and Linux; and Alt
      // can be used on OS X, so make sure the unused one isn't used.
      let metaKey = isMac ? aEvent.ctrlKey : aEvent.altKey;
      if (!metaKey) {
        switch (aEvent.keyCode) {
          case KeyEvent.DOM_VK_LEFT:
          case KeyEvent.DOM_VK_RIGHT:
          case KeyEvent.DOM_VK_HOME:
            cancel = this.mController.handleKeyNavigation(aEvent.keyCode);
            break;
        }
      }

      // Handle keys that are not part of a keyboard shortcut (no Ctrl or Alt)
      if (!aEvent.ctrlKey && !aEvent.altKey) {
        switch (aEvent.keyCode) {
          case KeyEvent.DOM_VK_TAB:
            if (this.tabScrolling && this.popup.popupOpen) {
              cancel = this.mController.handleKeyNavigation(
                aEvent.shiftKey ? KeyEvent.DOM_VK_UP : KeyEvent.DOM_VK_DOWN
              );
            } else if (this.forceComplete && this.mController.matchCount >= 1) {
              this.mController.handleTab();
            }
            break;
          case KeyEvent.DOM_VK_UP:
          case KeyEvent.DOM_VK_DOWN:
          case KeyEvent.DOM_VK_PAGE_UP:
          case KeyEvent.DOM_VK_PAGE_DOWN:
            cancel = this.mController.handleKeyNavigation(aEvent.keyCode);
            break;
        }
      }

      // Handle readline/emacs-style navigation bindings on Mac.
      if (
        isMac &&
        this.popup.popupOpen &&
        aEvent.ctrlKey &&
        (aEvent.key === "n" || aEvent.key === "p")
      ) {
        const effectiveKey =
          aEvent.key === "p" ? KeyEvent.DOM_VK_UP : KeyEvent.DOM_VK_DOWN;
        cancel = this.mController.handleKeyNavigation(effectiveKey);
      }

      // Handle keys we know aren't part of a shortcut, even with Alt or
      // Ctrl.
      switch (aEvent.keyCode) {
        case KeyEvent.DOM_VK_ESCAPE:
          cancel = this.mController.handleEscape();
          break;
        case KeyEvent.DOM_VK_RETURN:
          if (isMac) {
            // Prevent the default action, since it will beep on Mac
            if (aEvent.metaKey) {
              aEvent.preventDefault();
            }
          }
          if (this.popup.selectedIndex >= 0) {
            this.popupSelectedIndex = this.popup.selectedIndex;
          }
          cancel = this.handleEnter(aEvent);
          break;
        case KeyEvent.DOM_VK_DELETE:
          if (isMac && !aEvent.shiftKey) {
            break;
          }
          cancel = this.handleDelete();
          break;
        case KeyEvent.DOM_VK_BACK_SPACE:
          if (isMac && aEvent.shiftKey) {
            cancel = this.handleDelete();
          }
          break;
        case KeyEvent.DOM_VK_DOWN:
        case KeyEvent.DOM_VK_UP:
          if (aEvent.altKey) {
            this.toggleHistoryPopup();
          }
          break;
        case KeyEvent.DOM_VK_F4:
          if (!isMac) {
            this.toggleHistoryPopup();
          }
          break;
      }

      if (cancel) {
        aEvent.stopPropagation();
        aEvent.preventDefault();
      }

      return true;
    }

    handleEnter(event) {
      return this.mController.handleEnter(false, event || null);
    }

    handleDelete() {
      return this.mController.handleDelete();
    }

    /**
     * ::::::::::::: miscellaneous :::::::::::::
     */
    initSearchNames() {
      if (!this.mSearchNames) {
        var names = this.getAttribute("autocompletesearch");
        if (!names) {
          this.mSearchNames = [];
        } else {
          this.mSearchNames = names.split(" ");
        }
      }
    }

    _focus() {
      this._dontBlur = true;
      this.focus();
      this._dontBlur = false;
    }

    resetActionType() {
      if (this.mIgnoreInput) {
        return;
      }
      this.removeAttribute("actiontype");
    }

    _setValueInternal(value, isUserInput) {
      this.mIgnoreInput = true;

      if (typeof this.onBeforeValueSet == "function") {
        value = this.onBeforeValueSet(value);
      }

      this.valueIsTyped = false;
      if (isUserInput) {
        super.setUserInput(value);
      } else {
        super.value = value;
      }

      this.mIgnoreInput = false;
      var event = document.createEvent("Events");
      event.initEvent("ValueChange", true, true);
      super.dispatchEvent(event);
      return value;
    }

    onInput() {
      if (
        !this.mIgnoreInput &&
        this.mController.input.wrappedJSObject == this.nsIAutocompleteInput
      ) {
        this.valueIsTyped = true;
        this.mController.handleText();
      }
      this.resetActionType();
    }
  }

  MozHTMLElement.implementCustomInterface(AutocompleteInput, [
    Ci.nsIAutoCompleteInput,
    Ci.nsIDOMXULMenuListElement,
  ]);
  customElements.define("autocomplete-input", AutocompleteInput, {
    extends: "input",
  });
}
PK
!<�^}N�Q�Q<chrome/toolkit/content/global/elements/autocomplete-popup.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
  const MozPopupElement = MozElements.MozElementMixin(XULPopupElement);
  MozElements.MozAutocompleteRichlistboxPopup = class MozAutocompleteRichlistboxPopup extends (
    MozPopupElement
  ) {
    constructor() {
      super();

      this.attachShadow({ mode: "open" });

      {
        let slot = document.createElement("slot");
        slot.part = "content";
        this.shadowRoot.appendChild(slot);
      }

      this.mInput = null;
      this.mPopupOpen = false;
      this._currentIndex = 0;
      this._disabledItemClicked = false;

      this.setListeners();
    }

    initialize() {
      this.setAttribute("ignorekeys", "true");
      this.setAttribute("level", "top");
      this.setAttribute("consumeoutsideclicks", "never");

      this.textContent = "";
      this.appendChild(this.constructor.fragment);

      /**
       * This is the default number of rows that we give the autocomplete
       * popup when the textbox doesn't have a "maxrows" attribute
       * for us to use.
       */
      this.defaultMaxRows = 6;

      /**
       * In some cases (e.g. when the input's dropmarker button is clicked),
       * the input wants to display a popup with more rows. In that case, it
       * should increase its maxRows property and store the "normal" maxRows
       * in this field. When the popup is hidden, we restore the input's
       * maxRows to the value stored in this field.
       *
       * This field is set to -1 between uses so that we can tell when it's
       * been set by the input and when we need to set it in the popupshowing
       * handler.
       */
      this._normalMaxRows = -1;
      this._previousSelectedIndex = -1;
      this.mLastMoveTime = Date.now();
      this.mousedOverIndex = -1;
      this._richlistbox = this.querySelector(".autocomplete-richlistbox");

      if (!this.listEvents) {
        this.listEvents = {
          handleEvent: event => {
            if (!this.parentNode) {
              return;
            }

            switch (event.type) {
              case "mousedown":
                this._disabledItemClicked =
                  !!event.target.closest("richlistitem")?.disabled;
                break;
              case "mouseup":
                // Don't call onPopupClick for the scrollbar buttons, thumb,
                // slider, etc. If we hit the richlistbox and not a
                // richlistitem, we ignore the event.
                if (
                  event.target.closest("richlistbox,richlistitem").localName ==
                    "richlistitem" &&
                  !this._disabledItemClicked
                ) {
                  this.onPopupClick(event);
                }
                this._disabledItemClicked = false;
                break;
              case "mousemove":
                if (Date.now() - this.mLastMoveTime <= 30) {
                  return;
                }

                let item = event.target.closest("richlistbox,richlistitem");

                // If we hit the richlistbox and not a richlistitem, we ignore
                // the event.
                if (item.localName == "richlistbox") {
                  return;
                }

                let index = this.richlistbox.getIndexOfItem(item);

                this.mousedOverIndex = index;

                if (item.selectedByMouseOver) {
                  this.richlistbox.selectedIndex = index;
                }

                this.mLastMoveTime = Date.now();
                break;
            }
          },
        };
      }
      this.richlistbox.addEventListener("mousedown", this.listEvents);
      this.richlistbox.addEventListener("mouseup", this.listEvents);
      this.richlistbox.addEventListener("mousemove", this.listEvents);
    }

    get richlistbox() {
      if (!this._richlistbox) {
        this.initialize();
      }
      return this._richlistbox;
    }

    static get markup() {
      return `
      <richlistbox class="autocomplete-richlistbox"/>
    `;
    }

    /**
     * nsIAutoCompletePopup
     */
    get input() {
      return this.mInput;
    }

    get overrideValue() {
      return null;
    }

    get popupOpen() {
      return this.mPopupOpen;
    }

    get maxRows() {
      return (this.mInput && this.mInput.maxRows) || this.defaultMaxRows;
    }

    set selectedIndex(val) {
      if (val != this.richlistbox.selectedIndex) {
        this._previousSelectedIndex = this.richlistbox.selectedIndex;
      }
      this.richlistbox.selectedIndex = val;
      // Since ensureElementIsVisible may cause an expensive Layout flush,
      // invoke it only if there may be a scrollbar, so if we could fetch
      // more results than we can show at once.
      // maxResults is the maximum number of fetched results, maxRows is the
      // maximum number of rows we show at once, without a scrollbar.
      if (this.mPopupOpen && this.maxResults > this.maxRows) {
        // when clearing the selection (val == -1, so selectedItem will be
        // null), we want to scroll back to the top.  see bug #406194
        this.richlistbox.ensureElementIsVisible(
          this.richlistbox.selectedItem || this.richlistbox.firstElementChild
        );
      }
    }

    get selectedIndex() {
      return this.richlistbox.selectedIndex;
    }

    get maxResults() {
      // This is how many richlistitems will be kept around.
      // Note, this getter may be overridden, or instances
      // can have the nomaxresults attribute set to have no
      // limit.
      if (this.getAttribute("nomaxresults") == "true") {
        return Infinity;
      }
      return 20;
    }

    get matchCount() {
      return Math.min(this.mInput.controller.matchCount, this.maxResults);
    }

    get overflowPadding() {
      return Number(this.getAttribute("overflowpadding"));
    }

    set view(val) {}

    get view() {
      return this.mInput.controller;
    }

    closePopup() {
      if (this.mPopupOpen) {
        this.hidePopup();
        this.style.removeProperty("--panel-width");
      }
    }

    getNextIndex(aReverse, aAmount, aIndex, aMaxRow) {
      if (aMaxRow < 0) {
        return -1;
      }

      do {
        var newIdx = aIndex + (aReverse ? -1 : 1) * aAmount;
        if (
          (aReverse && aIndex == -1) ||
          (newIdx > aMaxRow && aIndex != aMaxRow)
        ) {
          newIdx = aMaxRow;
        } else if ((!aReverse && aIndex == -1) || (newIdx < 0 && aIndex != 0)) {
          newIdx = 0;
        }

        if (
          (newIdx < 0 && aIndex == 0) ||
          (newIdx > aMaxRow && aIndex == aMaxRow)
        ) {
          aIndex = -1;
        } else {
          aIndex = newIdx;
        }

        if (aIndex == -1) {
          return -1;
        }
      } while (
        !this.richlistbox.canUserSelect(this.richlistbox.getItemAtIndex(aIndex))
      );

      return aIndex;
    }

    onPopupClick(aEvent) {
      this.input.controller.handleEnter(true, aEvent);
    }

    onSearchBegin() {
      this.mousedOverIndex = -1;

      if (typeof this._onSearchBegin == "function") {
        this._onSearchBegin();
      }
    }

    openAutocompletePopup(aInput, aElement) {
      // until we have "baseBinding", (see bug #373652) this allows
      // us to override openAutocompletePopup(), but still call
      // the method on the base class
      this._openAutocompletePopup(aInput, aElement);
    }

    _openAutocompletePopup(aInput, aElement) {
      if (!this._richlistbox) {
        this.initialize();
      }

      if (!this.mPopupOpen) {
        // It's possible that the panel is hidden initially
        // to avoid impacting startup / new window performance
        aInput.popup.hidden = false;

        this.mInput = aInput;
        // clear any previous selection, see bugs 400671 and 488357
        this.selectedIndex = -1;

        var width = aElement.getBoundingClientRect().width;
        this.style.setProperty("--panel-width", Math.max(width, 100) + "px");
        // invalidate() depends on the width attribute
        this._invalidate();

        this.openPopup(aElement, "after_start", 0, 0, false, false);
      }
    }

    invalidate(reason) {
      // Don't bother doing work if we're not even showing
      if (!this.mPopupOpen) {
        return;
      }

      this._invalidate(reason);
    }

    _invalidate(reason) {
      // collapsed if no matches
      this.richlistbox.collapsed = this.matchCount == 0;

      // Update the richlistbox height.
      if (this._adjustHeightRAFToken) {
        cancelAnimationFrame(this._adjustHeightRAFToken);
        this._adjustHeightRAFToken = null;
      }

      if (this.mPopupOpen) {
        this._adjustHeightOnPopupShown = false;
        this._adjustHeightRAFToken = requestAnimationFrame(() =>
          this.adjustHeight()
        );
      } else {
        this._adjustHeightOnPopupShown = true;
      }

      this._currentIndex = 0;
      if (this._appendResultTimeout) {
        clearTimeout(this._appendResultTimeout);
      }
      this._appendCurrentResult(reason);
    }

    _collapseUnusedItems() {
      let existingItemsCount = this.richlistbox.children.length;
      for (let i = this.matchCount; i < existingItemsCount; ++i) {
        this.richlistbox.children[i].collapsed = true;
      }
    }

    adjustHeight() {
      // Figure out how many rows to show
      let rows = this.richlistbox.children;
      let numRows = Math.min(this.matchCount, this.maxRows, rows.length);

      // Default the height to 0 if we have no rows to show
      let height = 0;
      if (numRows) {
        let firstRowRect = rows[0].getBoundingClientRect();
        if (this._rlbPadding == undefined) {
          let style = window.getComputedStyle(this.richlistbox);
          let paddingTop = parseInt(style.paddingTop) || 0;
          let paddingBottom = parseInt(style.paddingBottom) || 0;
          this._rlbPadding = paddingTop + paddingBottom;
        }

        // The class `forceHandleUnderflow` is for the item might need to
        // handle OverUnderflow or Overflow when the height of an item will
        // be changed dynamically.
        for (let i = 0; i < numRows; i++) {
          if (rows[i].classList.contains("forceHandleUnderflow")) {
            rows[i].handleOverUnderflow();
          }
        }

        let lastRowRect = rows[numRows - 1].getBoundingClientRect();
        // Calculate the height to have the first row to last row shown
        height = lastRowRect.bottom - firstRowRect.top + this._rlbPadding;
      }

      this._collapseUnusedItems();

      // We need to get the ceiling of the calculated value to ensure that the
      // box fully contains all of its contents and doesn't cause a scrollbar.
      this.richlistbox.style.height = Math.ceil(height) + "px";
    }

    _appendCurrentResult(invalidateReason) {
      var controller = this.mInput.controller;
      var matchCount = this.matchCount;
      var existingItemsCount = this.richlistbox.children.length;

      // Process maxRows per chunk to improve performance and user experience
      for (let i = 0; i < this.maxRows; i++) {
        if (this._currentIndex >= matchCount) {
          break;
        }
        let item;
        let itemExists = this._currentIndex < existingItemsCount;

        let originalValue, originalText, originalType;
        let style = controller.getStyleAt(this._currentIndex);
        let value =
          style && style.includes("autofill")
            ? controller.getFinalCompleteValueAt(this._currentIndex)
            : controller.getValueAt(this._currentIndex);
        let label = controller.getLabelAt(this._currentIndex);
        let comment = controller.getCommentAt(this._currentIndex);
        let image = controller.getImageAt(this._currentIndex);
        // trim the leading/trailing whitespace
        let trimmedSearchString = controller.searchString
          .replace(/^\s+/, "")
          .replace(/\s+$/, "");

        // Generic items can pack their details as JSON inside label
        try {
          const details = JSON.parse(label);
          if (details.title) {
            value = details.title;
            label = details.subtitle ?? "";
          }
        } catch {}

        let reusable = false;
        if (itemExists) {
          item = this.richlistbox.children[this._currentIndex];

          // Url may be a modified version of value, see _adjustAcItem().
          originalValue =
            item.getAttribute("url") || item.getAttribute("ac-value");
          originalText = item.getAttribute("ac-text");
          originalType = item.getAttribute("originaltype");

          // The styles on the list which have different <content> structure and overrided
          // _adjustAcItem() are unreusable.
          const UNREUSEABLE_STYLES = [
            "autofill",
            "action",
            "status",
            "generatedPassword",
            "generic",
            "importableLearnMore",
            "importableLogins",
            "insecureWarning",
            "loginsFooter",
            "loginWithOrigin",
          ];
          // Reuse the item when its style is exactly equal to the previous style or
          // neither of their style are in the UNREUSEABLE_STYLES.
          reusable =
            originalType === style ||
            !(
              UNREUSEABLE_STYLES.includes(style) ||
              UNREUSEABLE_STYLES.includes(originalType)
            );
        }

        // If no reusable item available, then create a new item.
        if (!reusable) {
          let options = null;
          switch (style) {
            case "autofill":
              options = { is: "autocomplete-autofill-richlistitem" };
              break;
            case "action":
              options = { is: "autocomplete-action-richlistitem" };
              break;
            case "status":
              options = { is: "autocomplete-status-richlistitem" };
              break;
            case "generic":
              options = { is: "autocomplete-two-line-richlistitem" };
              break;
            case "importableLearnMore":
              options = {
                is: "autocomplete-importable-learn-more-richlistitem",
              };
              break;
            case "importableLogins":
              options = { is: "autocomplete-importable-logins-richlistitem" };
              break;
            case "generatedPassword":
              options = { is: "autocomplete-generated-password-richlistitem" };
              break;
            case "insecureWarning":
              options = { is: "autocomplete-richlistitem-insecure-warning" };
              break;
            case "loginsFooter":
              options = { is: "autocomplete-richlistitem-logins-footer" };
              break;
            case "loginWithOrigin":
              options = { is: "autocomplete-login-richlistitem" };
              break;
            default:
              options = { is: "autocomplete-richlistitem" };
          }
          item = document.createXULElement("richlistitem", options);
          item.className = "autocomplete-richlistitem";
        }

        item.setAttribute("dir", this.style.direction);
        item.setAttribute("ac-image", image);
        item.setAttribute("ac-value", value);
        item.setAttribute("ac-label", label);
        item.setAttribute("ac-comment", comment);
        item.setAttribute("ac-text", trimmedSearchString);

        // Completely reuse the existing richlistitem for invalidation
        // due to new results, but only when: the item is the same, *OR*
        // we are about to replace the currently moused-over item, to
        // avoid surprising the user.
        let iface = Ci.nsIAutoCompletePopup;
        if (
          reusable &&
          originalText == trimmedSearchString &&
          invalidateReason == iface.INVALIDATE_REASON_NEW_RESULT &&
          (originalValue == value ||
            this.mousedOverIndex === this._currentIndex)
        ) {
          // try to re-use the existing item
          item._reuseAcItem();
          this._currentIndex++;
          continue;
        } else {
          if (typeof item._cleanup == "function") {
            item._cleanup();
          }
          item.setAttribute("originaltype", style);
        }

        if (reusable) {
          // Adjust only when the result's type is reusable for existing
          // item's. Otherwise, we might insensibly call old _adjustAcItem()
          // as new binding has not been attached yet.
          // We don't need to worry about switching to new binding, since
          // _adjustAcItem() will fired by its own constructor accordingly.
          item._adjustAcItem();
          item.collapsed = false;
        } else if (itemExists) {
          let oldItem = this.richlistbox.children[this._currentIndex];
          this.richlistbox.replaceChild(item, oldItem);
        } else {
          this.richlistbox.appendChild(item);
        }

        this._currentIndex++;
      }

      if (typeof this.onResultsAdded == "function") {
        // The items bindings may not be attached yet, so we must delay this
        // before we can properly handle items properly without breaking
        // the richlistbox.
        Services.tm.dispatchToMainThread(() => this.onResultsAdded());
      }

      if (this._currentIndex < matchCount) {
        // yield after each batch of items so that typing the url bar is
        // responsive
        this._appendResultTimeout = setTimeout(
          () => this._appendCurrentResult(),
          0
        );
      }
    }

    selectBy(aReverse, aPage) {
      try {
        var amount = aPage ? 5 : 1;

        // because we collapsed unused items, we can't use this.richlistbox.getRowCount(), we need to use the matchCount
        this.selectedIndex = this.getNextIndex(
          aReverse,
          amount,
          this.selectedIndex,
          this.matchCount - 1
        );
        if (this.selectedIndex == -1) {
          this.input._focus();
        }
      } catch (ex) {
        // do nothing - occasionally timer-related js errors happen here
        // e.g. "this.selectedIndex has no properties", when you type fast and hit a
        // navigation key before this popup has opened
      }
    }

    disconnectedCallback() {
      if (this.listEvents) {
        this.richlistbox.removeEventListener("mousedown", this.listEvents);
        this.richlistbox.removeEventListener("mouseup", this.listEvents);
        this.richlistbox.removeEventListener("mousemove", this.listEvents);
        delete this.listEvents;
      }
    }

    setListeners() {
      this.addEventListener("popupshowing", () => {
        // If normalMaxRows wasn't already set by the input, then set it here
        // so that we restore the correct number when the popup is hidden.

        // Null-check this.mInput; see bug 1017914
        if (this._normalMaxRows < 0 && this.mInput) {
          this._normalMaxRows = this.mInput.maxRows;
        }

        this.mPopupOpen = true;
      });

      this.addEventListener("popupshown", () => {
        if (this._adjustHeightOnPopupShown) {
          this._adjustHeightOnPopupShown = false;
          this.adjustHeight();
        }
      });

      this.addEventListener("popuphiding", () => {
        var isListActive = true;
        if (this.selectedIndex == -1) {
          isListActive = false;
        }
        this.input.controller.stopSearch();

        this.mPopupOpen = false;

        // Reset the maxRows property to the cached "normal" value (if there's
        // any), and reset normalMaxRows so that we can detect whether it was set
        // by the input when the popupshowing handler runs.

        // Null-check this.mInput; see bug 1017914
        if (this.mInput && this._normalMaxRows > 0) {
          this.mInput.maxRows = this._normalMaxRows;
        }
        this._normalMaxRows = -1;
        // If the list was being navigated and then closed, make sure
        // we fire accessible focus event back to textbox

        // Null-check this.mInput; see bug 1017914
        if (isListActive && this.mInput) {
          this.mInput.mIgnoreFocus = true;
          this.mInput._focus();
          this.mInput.mIgnoreFocus = false;
        }
      });
    }
  };

  MozPopupElement.implementCustomInterface(
    MozElements.MozAutocompleteRichlistboxPopup,
    [Ci.nsIAutoCompletePopup]
  );

  customElements.define(
    "autocomplete-richlistbox-popup",
    MozElements.MozAutocompleteRichlistboxPopup,
    {
      extends: "panel",
    }
  );
}
PK
!<:m\�v�vCchrome/toolkit/content/global/elements/autocomplete-richlistitem.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
  const { LoginHelper } = ChromeUtils.importESModule(
    "resource://gre/modules/LoginHelper.sys.mjs"
  );

  MozElements.MozAutocompleteRichlistitem = class MozAutocompleteRichlistitem extends (
    MozElements.MozRichlistitem
  ) {
    constructor() {
      super();

      /**
       * This overrides listitem's mousedown handler because we want to set the
       * selected item even when the shift or accel keys are pressed.
       */
      this.addEventListener("mousedown", () => {
        // Call this.control only once since it's not a simple getter.
        let control = this.control;
        if (!control || control.disabled) {
          return;
        }
        if (!this.selected) {
          control.selectItem(this);
        }
        control.currentItem = this;
      });

      this.addEventListener("mouseover", event => {
        // The point of implementing this handler is to allow drags to change
        // the selected item.  If the user mouses down on an item, it becomes
        // selected.  If they then drag the mouse to another item, select it.
        // Handle all three primary mouse buttons: right, left, and wheel, since
        // all three change the selection on mousedown.
        let mouseDown = event.buttons & 0b111;
        if (!mouseDown) {
          return;
        }
        // Call this.control only once since it's not a simple getter.
        let control = this.control;
        if (!control || control.disabled) {
          return;
        }
        if (!this.selected) {
          control.selectItem(this);
        }
        control.currentItem = this;
      });

      this.addEventListener("overflow", () => this._onOverflow());
      this.addEventListener("underflow", () => this._onUnderflow());
    }

    connectedCallback() {
      if (this.delayConnectedCallback()) {
        return;
      }

      this.textContent = "";
      this.appendChild(this.constructor.fragment);
      this.initializeAttributeInheritance();

      this._boundaryCutoff = null;
      this._inOverflow = false;

      this._adjustAcItem();
    }

    static get inheritedAttributes() {
      return {
        ".ac-type-icon": "selected,current,type",
        ".ac-site-icon": "src=image,selected,type",
        ".ac-title": "selected",
        ".ac-title-text": "selected",
        ".ac-separator": "selected,type",
        ".ac-url": "selected",
        ".ac-url-text": "selected",
      };
    }

    static get markup() {
      return `
      <image class="ac-type-icon"/>
      <image class="ac-site-icon"/>
      <hbox class="ac-title" align="center">
        <description class="ac-text-overflow-container">
          <description class="ac-title-text"/>
        </description>
      </hbox>
      <hbox class="ac-separator" align="center">
        <description class="ac-separator-text" value="—"/>
      </hbox>
      <hbox class="ac-url" align="center" aria-hidden="true">
        <description class="ac-text-overflow-container">
          <description class="ac-url-text"/>
        </description>
      </hbox>
    `;
    }

    get _typeIcon() {
      return this.querySelector(".ac-type-icon");
    }

    get _titleText() {
      return this.querySelector(".ac-title-text");
    }

    get _separator() {
      return this.querySelector(".ac-separator");
    }

    get _urlText() {
      return this.querySelector(".ac-url-text");
    }

    get _stringBundle() {
      if (!this.__stringBundle) {
        this.__stringBundle = Services.strings.createBundle(
          "chrome://global/locale/autocomplete.properties"
        );
      }
      return this.__stringBundle;
    }

    get boundaryCutoff() {
      if (!this._boundaryCutoff) {
        this._boundaryCutoff = Services.prefs.getIntPref(
          "toolkit.autocomplete.richBoundaryCutoff"
        );
      }
      return this._boundaryCutoff;
    }

    _cleanup() {
      this.removeAttribute("url");
      this.removeAttribute("image");
      this.removeAttribute("title");
      this.removeAttribute("text");
    }

    _onOverflow() {
      this._inOverflow = true;
      this._handleOverflow();
    }

    _onUnderflow() {
      this._inOverflow = false;
      this._handleOverflow();
    }

    _getBoundaryIndices(aText, aSearchTokens) {
      // Short circuit for empty search ([""] == "")
      if (aSearchTokens == "") {
        return [0, aText.length];
      }

      // Find which regions of text match the search terms
      let regions = [];
      for (let search of Array.prototype.slice.call(aSearchTokens)) {
        let matchIndex = -1;
        let searchLen = search.length;

        // Find all matches of the search terms, but stop early for perf
        let lowerText = aText.substr(0, this.boundaryCutoff).toLowerCase();
        while ((matchIndex = lowerText.indexOf(search, matchIndex + 1)) >= 0) {
          regions.push([matchIndex, matchIndex + searchLen]);
        }
      }

      // Sort the regions by start position then end position
      regions = regions.sort((a, b) => {
        let start = a[0] - b[0];
        return start == 0 ? a[1] - b[1] : start;
      });

      // Generate the boundary indices from each region
      let start = 0;
      let end = 0;
      let boundaries = [];
      let len = regions.length;
      for (let i = 0; i < len; i++) {
        // We have a new boundary if the start of the next is past the end
        let region = regions[i];
        if (region[0] > end) {
          // First index is the beginning of match
          boundaries.push(start);
          // Second index is the beginning of non-match
          boundaries.push(end);

          // Track the new region now that we've stored the previous one
          start = region[0];
        }

        // Push back the end index for the current or new region
        end = Math.max(end, region[1]);
      }

      // Add the last region
      boundaries.push(start);
      boundaries.push(end);

      // Put on the end boundary if necessary
      if (end < aText.length) {
        boundaries.push(aText.length);
      }

      // Skip the first item because it's always 0
      return boundaries.slice(1);
    }

    _getSearchTokens(aSearch) {
      let search = aSearch.toLowerCase();
      return search.split(/\s+/);
    }

    _setUpDescription(aDescriptionElement, aText) {
      // Get rid of all previous text
      if (!aDescriptionElement) {
        return;
      }
      while (aDescriptionElement.hasChildNodes()) {
        aDescriptionElement.firstChild.remove();
      }

      // Get the indices that separate match and non-match text
      let search = this.getAttribute("text");
      let tokens = this._getSearchTokens(search);
      let indices = this._getBoundaryIndices(aText, tokens);

      this._appendDescriptionSpans(
        indices,
        aText,
        aDescriptionElement,
        aDescriptionElement
      );
    }

    _appendDescriptionSpans(
      indices,
      text,
      spansParentElement,
      descriptionElement
    ) {
      let next;
      let start = 0;
      let len = indices.length;
      // Even indexed boundaries are matches, so skip the 0th if it's empty
      for (let i = indices[0] == 0 ? 1 : 0; i < len; i++) {
        next = indices[i];
        let spanText = text.substr(start, next - start);
        start = next;

        if (i % 2 == 0) {
          // Emphasize the text for even indices
          let span = spansParentElement.appendChild(
            document.createElementNS("http://www.w3.org/1999/xhtml", "span")
          );
          this._setUpEmphasisSpan(span, descriptionElement);
          span.textContent = spanText;
        } else {
          // Otherwise, it's plain text
          spansParentElement.appendChild(document.createTextNode(spanText));
        }
      }
    }

    _setUpEmphasisSpan(aSpan, aDescriptionElement) {
      aSpan.classList.add("ac-emphasize-text");
      switch (aDescriptionElement) {
        case this._titleText:
          aSpan.classList.add("ac-emphasize-text-title");
          break;
        case this._urlText:
          aSpan.classList.add("ac-emphasize-text-url");
          break;
      }
    }

    /**
     * This will generate an array of emphasis pairs for use with
     * _setUpEmphasisedSections(). Each pair is a tuple (array) that
     * represents a block of text - containing the text of that block, and a
     * boolean for whether that block should have an emphasis styling applied
     * to it.
     *
     * These pairs are generated by parsing a localised string (aSourceString)
     * with parameters, in the format that is used by
     * nsIStringBundle.formatStringFromName():
     *
     * "textA %1$S textB textC %2$S"
     *
     * Or:
     *
     * "textA %S"
     *
     * Where "%1$S", "%2$S", and "%S" are intended to be replaced by provided
     * replacement strings. These are specified an array of tuples
     * (aReplacements), each containing the replacement text and a boolean for
     * whether that text should have an emphasis styling applied. This is used
     * as a 1-based array - ie, "%1$S" is replaced by the item in the first
     * index of aReplacements, "%2$S" by the second, etc. "%S" will always
     * match the first index.
     */
    _generateEmphasisPairs(aSourceString, aReplacements) {
      let pairs = [];

      // Split on %S, %1$S, %2$S, etc. ie:
      //   "textA %S"
      //     becomes ["textA ", "%S"]
      //   "textA %1$S textB textC %2$S"
      //     becomes ["textA ", "%1$S", " textB textC ", "%2$S"]
      let parts = aSourceString.split(/(%(?:[0-9]+\$)?S)/);

      for (let part of parts) {
        // The above regex will actually give us an empty string at the
        // end - we don't want that, as we don't want to later generate an
        // empty text node for it.
        if (part.length === 0) {
          continue;
        }

        // Determine if this token is a replacement token or a normal text
        // token. If it is a replacement token, we want to extract the
        // numerical number. However, we still want to match on "$S".
        let match = part.match(/^%(?:([0-9]+)\$)?S$/);

        if (match) {
          // "%S" doesn't have a numerical number in it, but will always
          // be assumed to be 1. Furthermore, the input string specifies
          // these with a 1-based index, but we want a 0-based index.
          let index = (match[1] || 1) - 1;

          if (index >= 0 && index < aReplacements.length) {
            pairs.push([...aReplacements[index]]);
          }
        } else {
          pairs.push([part]);
        }
      }

      return pairs;
    }

    /**
     * _setUpEmphasisedSections() has the same use as _setUpDescription,
     * except instead of taking a string and highlighting given tokens, it takes
     * an array of pairs generated by _generateEmphasisPairs(). This allows
     * control over emphasising based on specific blocks of text, rather than
     * search for substrings.
     */
    _setUpEmphasisedSections(aDescriptionElement, aTextPairs) {
      // Get rid of all previous text
      while (aDescriptionElement.hasChildNodes()) {
        aDescriptionElement.firstChild.remove();
      }

      for (let [text, emphasise] of aTextPairs) {
        if (emphasise) {
          let span = aDescriptionElement.appendChild(
            document.createElementNS("http://www.w3.org/1999/xhtml", "span")
          );
          span.textContent = text;
          switch (emphasise) {
            case "match":
              this._setUpEmphasisSpan(span, aDescriptionElement);
              break;
          }
        } else {
          aDescriptionElement.appendChild(document.createTextNode(text));
        }
      }
    }

    _unescapeUrl(url) {
      return Services.textToSubURI.unEscapeURIForUI(url);
    }

    _reuseAcItem() {
      this.collapsed = false;

      // The popup may have changed size between now and the last
      // time the item was shown, so always handle over/underflow.
      let dwu = window.windowUtils;
      let popupWidth = dwu.getBoundsWithoutFlushing(this.parentNode).width;
      if (!this._previousPopupWidth || this._previousPopupWidth != popupWidth) {
        this._previousPopupWidth = popupWidth;
        this.handleOverUnderflow();
      }
    }

    _adjustAcItem() {
      let originalUrl = this.getAttribute("ac-value");
      let title = this.getAttribute("ac-label");
      this.setAttribute("url", originalUrl);
      this.setAttribute("image", this.getAttribute("ac-image"));
      this.setAttribute("title", title);
      this.setAttribute("text", this.getAttribute("ac-text"));

      let type = this.getAttribute("originaltype");
      let types = new Set(type.split(/\s+/));
      // Remove types that should ultimately not be in the `type` string.
      types.delete("autofill");
      type = [...types][0] || "";
      this.setAttribute("type", type);

      let displayUrl = this._unescapeUrl(originalUrl);

      // Show the domain as the title if we don't have a title.
      if (!title) {
        try {
          let uri = Services.io.newURI(originalUrl);
          // Not all valid URLs have a domain.
          if (uri.host) {
            title = uri.host;
          }
        } catch (e) {}
        if (!title) {
          title = displayUrl;
        }
      }

      if (Array.isArray(title)) {
        this._setUpEmphasisedSections(this._titleText, title);
      } else {
        this._setUpDescription(this._titleText, title);
      }
      this._setUpDescription(this._urlText, displayUrl);

      // Removing the max-width may be jarring when the item is visible, but
      // we have no other choice to properly crop the text.
      // Removing max-widths may cause overflow or underflow events, that
      // will set the _inOverflow property. In case both the old and the new
      // text are overflowing, the overflow event won't happen, and we must
      // enforce an _handleOverflow() call to update the max-widths.
      let wasInOverflow = this._inOverflow;
      this._removeMaxWidths();
      if (wasInOverflow && this._inOverflow) {
        this._handleOverflow();
      }
    }

    _removeMaxWidths() {
      if (this._hasMaxWidths) {
        this._titleText.style.removeProperty("max-width");
        this._urlText.style.removeProperty("max-width");
        this._hasMaxWidths = false;
      }
    }

    /**
     * This method truncates the displayed strings as necessary.
     */
    _handleOverflow() {
      let itemRect = this.parentNode.getBoundingClientRect();
      let titleRect = this._titleText.getBoundingClientRect();
      let separatorRect = this._separator.getBoundingClientRect();
      let urlRect = this._urlText.getBoundingClientRect();
      let separatorURLWidth = separatorRect.width + urlRect.width;

      // Total width for the title and URL is the width of the item
      // minus the start of the title text minus a little optional extra padding.
      // This extra padding amount is basically arbitrary but keeps the text
      // from getting too close to the popup's edge.
      let dir = this.getAttribute("dir");
      let titleStart =
        dir == "rtl"
          ? itemRect.right - titleRect.right
          : titleRect.left - itemRect.left;

      let popup = this.parentNode.parentNode;
      let itemWidth =
        itemRect.width -
        titleStart -
        popup.overflowPadding -
        (popup.margins ? popup.margins.end : 0);

      let titleWidth = titleRect.width;
      if (titleWidth + separatorURLWidth > itemWidth) {
        // The percentage of the item width allocated to the title.
        let titlePct = 0.66;

        let titleAvailable = itemWidth - separatorURLWidth;
        let titleMaxWidth = Math.max(titleAvailable, itemWidth * titlePct);
        if (titleWidth > titleMaxWidth) {
          this._titleText.style.maxWidth = titleMaxWidth + "px";
        }
        let urlMaxWidth = Math.max(
          itemWidth - titleWidth,
          itemWidth * (1 - titlePct)
        );
        urlMaxWidth -= separatorRect.width;
        this._urlText.style.maxWidth = urlMaxWidth + "px";
        this._hasMaxWidths = true;
      }
    }

    handleOverUnderflow() {
      this._removeMaxWidths();
      this._handleOverflow();
    }
  };

  MozXULElement.implementCustomInterface(
    MozElements.MozAutocompleteRichlistitem,
    [Ci.nsIDOMXULSelectControlItemElement]
  );

  class MozAutocompleteRichlistitemInsecureWarning extends MozElements.MozAutocompleteRichlistitem {
    constructor() {
      super();

      this.addEventListener("click", event => {
        if (event.button != 0) {
          return;
        }

        let comment = this.getAttribute("ac-comment");
        if (comment && JSON.parse(comment)?.noLearnMore) {
          return;
        }

        let baseURL = Services.urlFormatter.formatURLPref(
          "app.support.baseURL"
        );
        window.openTrustedLinkIn(baseURL + "insecure-password", "tab", {
          relatedToCurrent: true,
        });
      });
    }

    connectedCallback() {
      if (this.delayConnectedCallback()) {
        return;
      }

      super.connectedCallback();

      // Unlike other autocomplete items, the height of the insecure warning
      // increases by wrapping. So "forceHandleUnderflow" is for container to
      // recalculate an item's height and width.
      this.classList.add("forceHandleUnderflow");
    }

    static get inheritedAttributes() {
      return {
        ".ac-type-icon": "selected,current,type",
        ".ac-site-icon": "src=image,selected,type",
        ".ac-title-text": "selected",
        ".ac-separator": "selected,type",
        ".ac-url": "selected",
        ".ac-url-text": "selected",
      };
    }

    static get markup() {
      return `
      <image class="ac-type-icon"/>
      <image class="ac-site-icon"/>
      <vbox class="ac-title" align="left">
        <description class="ac-text-overflow-container">
          <description class="ac-title-text"/>
        </description>
      </vbox>
      <hbox class="ac-separator" align="center">
        <description class="ac-separator-text" value="—"/>
      </hbox>
      <hbox class="ac-url" align="center">
        <description class="ac-text-overflow-container">
          <description class="ac-url-text"/>
        </description>
      </hbox>
    `;
    }

    get _learnMoreString() {
      if (!this.__learnMoreString) {
        this.__learnMoreString = Services.strings
          .createBundle("chrome://passwordmgr/locale/passwordmgr.properties")
          .GetStringFromName("insecureFieldWarningLearnMore");
      }
      return this.__learnMoreString;
    }

    /**
     * Override _getSearchTokens to have the Learn More text emphasized
     */
    _getSearchTokens() {
      return [this._learnMoreString.toLowerCase()];
    }
  }

  class MozAutocompleteRichlistitemLoginsFooter extends MozElements.MozAutocompleteRichlistitem {}

  class MozAutocompleteImportableLearnMoreRichlistitem extends MozElements.MozAutocompleteRichlistitem {
    constructor() {
      super();
      MozXULElement.insertFTLIfNeeded("toolkit/main-window/autocomplete.ftl");
    }

    static get markup() {
      return `
      <image class="ac-type-icon"/>
      <image class="ac-site-icon"/>
      <vbox class="ac-title" align="left">
        <description class="ac-text-overflow-container">
          <description class="ac-title-text"
                       data-l10n-id="autocomplete-import-learn-more"/>
        </description>
      </vbox>
      <hbox class="ac-separator" align="center">
        <description class="ac-separator-text" value="—"/>
      </hbox>
      <hbox class="ac-url" align="center">
        <description class="ac-text-overflow-container">
          <description class="ac-url-text"/>
        </description>
      </hbox>
    `;
    }

    // Override to avoid clearing out fluent description.
    _setUpDescription() {}
  }

  class MozAutocompleteTwoLineRichlistitem extends MozElements.MozRichlistitem {
    connectedCallback() {
      if (this.delayConnectedCallback()) {
        return;
      }

      this.textContent = "";
      this.appendChild(this.constructor.fragment);
      this.initializeAttributeInheritance();
      this.initializeSecondaryAction();
      this._adjustAcItem();
    }

    initializeSecondaryAction() {
      const button = this.querySelector(".ac-secondary-action");

      if (this.onSecondaryAction) {
        button.addEventListener("mousedown", event => {
          event.stopPropagation();
          this.onSecondaryAction();
        });
      } else {
        button?.remove();
      }
    }

    static get inheritedAttributes() {
      return {
        // getLabelAt:
        ".line1-label": "text=ac-label",
        ".ac-site-icon": "src=ac-image",
      };
    }

    static get markup() {
      return `
      <div xmlns="http://www.w3.org/1999/xhtml"
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
           class="two-line-wrapper">
        <xul:image class="ac-site-icon"></xul:image>
        <div class="labels-wrapper">
          <div class="label-row line1-label"></div>
          <div class="label-row line2-label"></div>
        </div>
        <button class="ac-secondary-action"></button>
      </div>
    `;
    }

    _adjustAcItem() {
      let comment = JSON.parse(this.getAttribute("ac-comment"));
      this.querySelector(".line2-label").textContent = comment?.secondary || "";

      this.querySelector(".ac-site-icon").collapsed =
        this.getAttribute("ac-image") == "";
    }

    _onOverflow() {}

    _onUnderflow() {}

    handleOverUnderflow() {}
  }

  class MozAutocompleteLoginRichlistitem extends MozAutocompleteTwoLineRichlistitem {
    connectedCallback() {
      super.connectedCallback();
      this.firstChild.classList.add("ac-login-item");
    }

    onSecondaryAction() {
      const comment = JSON.parse(this.getAttribute("ac-comment"));
      LoginHelper.openPasswordManager(window, {
        loginGuid: comment?.guid,
      });
    }

    static get inheritedAttributes() {
      return {
        // getLabelAt:
        ".line1-label": "text=ac-label",
        ".ac-site-icon": "src=ac-image",
      };
    }
  }

  // This type has an action that is triggered when activated. The comment
  // for that result should contain a fillMessageName which is the message to send.
  class MozAutocompleteActionRichlistitem extends MozAutocompleteTwoLineRichlistitem {
    constructor() {
      super();
      this.selectedByMouseOver = true;
    }
  }

  // A row that conveys status information assigned from the status field
  // within the comment associated with the selected item in the list.
  class MozAutocompleteStatusRichlistitem extends MozAutocompleteTwoLineRichlistitem {
    static get markup() {
      return `<div class="ac-status" xmlns="http://www.w3.org/1999/xhtml"></div>`;
    }

    connectedCallback() {
      super.connectedCallback();
      this.parentNode.addEventListener("select", this);
      this.eventListenerParentNode = this.parentNode;
    }

    disconnectedCallback() {
      this.eventListenerParentNode?.removeEventListener("select", this);
      this.eventListenerParentNode = null;
    }

    handleEvent(event) {
      if (event.type == "select") {
        let selectedItem = event.target.selectedItem;
        if (selectedItem) {
          this.#setStatus(selectedItem);
        }
      }
    }

    #setStatus(item) {
      // For normal rows, use that row's comment, otherwise use the status's
      // comment which serves as the default label.
      let target =
        !item || item instanceof MozAutocompleteActionRichlistitem
          ? this
          : item;

      let comment = JSON.parse(target.getAttribute("ac-comment"));

      let statusBox = this.querySelector(".ac-status");
      statusBox.textContent = comment?.status || "";
    }

    _adjustAcItem() {
      this.#setStatus(this);
      this.setAttribute("disabled", "true");
    }
  }

  class MozAutocompleteAutoFillRichlistitem extends MozAutocompleteTwoLineRichlistitem {
    constructor() {
      super();
      this.selectedByMouseOver = true;
    }

    _adjustAcItem() {
      let label = this.getAttribute("ac-label");
      this.querySelector(".line1-label").textContent = label;

      let { secondary, ariaLabel } = JSON.parse(
        this.getAttribute("ac-comment")
      );

      let line2Label = this.querySelector(".line2-label");
      line2Label.textContent = secondary ?? "";

      if (ariaLabel) {
        this.setAttribute("aria-label", ariaLabel);
      }

      this.querySelector(".ac-site-icon").collapsed =
        this.getAttribute("ac-image") == "";
    }

    set selected(val) {
      if (val) {
        this.setAttribute("selected", "true");
      } else {
        this.removeAttribute("selected");
      }

      setTimeout(() => {
        const { AutoCompleteParent } = ChromeUtils.importESModule(
          "resource://gre/actors/AutoCompleteParent.sys.mjs"
        );
        const actor = AutoCompleteParent.getCurrentActor();
        actor?.previewAutoCompleteEntry();
      }, 0);
    }

    get selected() {
      return this.getAttribute("selected") == "true";
    }
  }

  class MozAutocompleteGeneratedPasswordRichlistitem extends MozAutocompleteTwoLineRichlistitem {
    constructor() {
      super();

      // Line 2 and line 3 both display text with a different line-height than
      // line 1 but we want the line-height to be the same so we wrap the text
      // in <span> and only adjust the line-height via font CSS properties on them.
      this.generatedPasswordText = document.createElement("span");

      this.line3Text = document.createElement("span");
      this.line3 = document.createElement("div");
      this.line3.className = "label-row generated-password-autosave";
      this.line3.append(this.line3Text);
    }

    get _autoSaveString() {
      if (!this.__autoSaveString) {
        let brandShorterName = Services.strings
          .createBundle("chrome://branding/locale/brand.properties")
          .GetStringFromName("brandShorterName");
        this.__autoSaveString = Services.strings
          .createBundle("chrome://passwordmgr/locale/passwordmgr.properties")
          .formatStringFromName("generatedPasswordWillBeSaved", [
            brandShorterName,
          ]);
      }
      return this.__autoSaveString;
    }

    _adjustAcItem() {
      let { generatedPassword, willAutoSaveGeneratedPassword } = JSON.parse(
        this.getAttribute("ac-comment")
      );
      let line2Label = this.querySelector(".line2-label");
      line2Label.textContent = "";
      this.generatedPasswordText.textContent = generatedPassword;
      line2Label.append(this.generatedPasswordText);

      if (willAutoSaveGeneratedPassword) {
        this.line3Text.textContent = this._autoSaveString;
        this.querySelector(".labels-wrapper").append(this.line3);
      } else {
        this.line3.remove();
      }

      super._adjustAcItem();
    }
  }

  class MozAutocompleteImportableLoginsRichlistitem extends MozAutocompleteTwoLineRichlistitem {
    constructor() {
      super();
      MozXULElement.insertFTLIfNeeded("toolkit/main-window/autocomplete.ftl");
    }

    static get inheritedAttributes() {
      return {
        // getLabelAt:
        ".line1-label": "text=ac-label",
      };
    }

    static get markup() {
      return `
      <div xmlns="http://www.w3.org/1999/xhtml"
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
           class="two-line-wrapper">
        <xul:image class="ac-site-icon" />
        <div class="labels-wrapper">
          <div class="label-row line1-label" data-l10n-name="line1" />
          <div class="label-row line2-label" data-l10n-name="line2" />
        </div>
      </div>
    `;
    }

    _adjustAcItem() {
      super._adjustAcItem();
      document.l10n.setAttributes(
        this.querySelector(".labels-wrapper"),
        `autocomplete-import-logins-${this.getAttribute("ac-value")}`,
        {
          host: JSON.parse(this.getAttribute("ac-comment")).hostname.replace(
            /^www\./,
            ""
          ),
        }
      );
    }
  }

  customElements.define(
    "autocomplete-richlistitem",
    MozElements.MozAutocompleteRichlistitem,
    {
      extends: "richlistitem",
    }
  );

  customElements.define(
    "autocomplete-richlistitem-insecure-warning",
    MozAutocompleteRichlistitemInsecureWarning,
    {
      extends: "richlistitem",
    }
  );

  customElements.define(
    "autocomplete-richlistitem-logins-footer",
    MozAutocompleteRichlistitemLoginsFooter,
    {
      extends: "richlistitem",
    }
  );

  customElements.define(
    "autocomplete-two-line-richlistitem",
    MozAutocompleteTwoLineRichlistitem,
    {
      extends: "richlistitem",
    }
  );

  customElements.define(
    "autocomplete-autofill-richlistitem",
    MozAutocompleteAutoFillRichlistitem,
    {
      extends: "richlistitem",
    }
  );

  customElements.define(
    "autocomplete-login-richlistitem",
    MozAutocompleteLoginRichlistitem,
    {
      extends: "richlistitem",
    }
  );

  customElements.define(
    "autocomplete-action-richlistitem",
    MozAutocompleteActionRichlistitem,
    {
      extends: "richlistitem",
    }
  );

  customElements.define(
    "autocomplete-status-richlistitem",
    MozAutocompleteStatusRichlistitem,
    {
      extends: "richlistitem",
    }
  );

  customElements.define(
    "autocomplete-generated-password-richlistitem",
    MozAutocompleteGeneratedPasswordRichlistitem,
    {
      extends: "richlistitem",
    }
  );

  customElements.define(
    "autocomplete-importable-learn-more-richlistitem",
    MozAutocompleteImportableLearnMoreRichlistitem,
    {
      extends: "richlistitem",
    }
  );

  customElements.define(
    "autocomplete-importable-logins-richlistitem",
    MozAutocompleteImportableLoginsRichlistitem,
    {
      extends: "richlistitem",
    }
  );
}
PK
!<:!�y��@chrome/toolkit/content/global/elements/browser-custom-element.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
  const { AppConstants } = ChromeUtils.importESModule(
    "resource://gre/modules/AppConstants.sys.mjs"
  );

  const { XPCOMUtils } = ChromeUtils.importESModule(
    "resource://gre/modules/XPCOMUtils.sys.mjs"
  );

  let lazy = {};

  ChromeUtils.defineESModuleGetters(lazy, {
    BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
    Finder: "resource://gre/modules/Finder.sys.mjs",
    FinderParent: "resource://gre/modules/FinderParent.sys.mjs",
    PopupBlocker: "resource://gre/actors/PopupBlockingParent.sys.mjs",
    SelectParentHelper: "resource://gre/actors/SelectParent.sys.mjs",
    RemoteWebNavigation: "resource://gre/modules/RemoteWebNavigation.sys.mjs",
  });

  ChromeUtils.defineLazyGetter(lazy, "blankURI", () =>
    Services.io.newURI("about:blank")
  );

  let lazyPrefs = {};
  XPCOMUtils.defineLazyPreferenceGetter(
    lazyPrefs,
    "unloadTimeoutMs",
    "dom.beforeunload_timeout_ms"
  );
  Object.defineProperty(lazy, "ProcessHangMonitor", {
    configurable: true,
    get() {
      // Import if we can - this is a browser/ module so it may not be
      // available, in which case we return null. We replace this getter
      // when the module becomes available (should be on delayed startup
      // when the first browser window loads, via BrowserGlue.sys.mjs).
      const kURL = "resource:///modules/ProcessHangMonitor.sys.mjs";
      if (Cu.isESModuleLoaded(kURL)) {
        let { ProcessHangMonitor } = ChromeUtils.importESModule(kURL);
        // eslint-disable-next-line mozilla/valid-lazy
        Object.defineProperty(lazy, "ProcessHangMonitor", {
          value: ProcessHangMonitor,
        });
        return ProcessHangMonitor;
      }
      return null;
    },
  });

  // Get SessionStore module in the same as ProcessHangMonitor above.
  Object.defineProperty(lazy, "SessionStore", {
    configurable: true,
    get() {
      const kURL = "resource:///modules/sessionstore/SessionStore.sys.mjs";
      if (Cu.isESModuleLoaded(kURL)) {
        let { SessionStore } = ChromeUtils.importESModule(kURL);
        // eslint-disable-next-line mozilla/valid-lazy
        Object.defineProperty(lazy, "SessionStore", {
          value: SessionStore,
        });
        return SessionStore;
      }
      return null;
    },
  });

  const elementsToDestroyOnUnload = new Set();

  window.addEventListener(
    "unload",
    () => {
      for (let element of elementsToDestroyOnUnload.values()) {
        element.destroy();
      }
      elementsToDestroyOnUnload.clear();
    },
    { mozSystemGroup: true, once: true }
  );

  class MozBrowser extends MozElements.MozElementMixin(XULFrameElement) {
    static get observedAttributes() {
      return ["remote"];
    }

    constructor() {
      super();

      this.onPageHide = this.onPageHide.bind(this);

      this.isNavigating = false;

      this._documentURI = null;
      this._characterSet = null;
      this._documentContentType = null;

      this._inPermitUnload = new WeakSet();

      this._originalURI = null;
      this._searchTerms = "";
      // When we open a prompt in reaction to a 401, if this 401 comes from
      // a different base domain, the url of that site will be stored here
      // and will be used for auth prompt spoofing protections.
      // See bug 791594 for reference.
      this._currentAuthPromptURI = null;
      /**
       * These are managed by the tabbrowser:
       */
      this.droppedLinkHandler = null;
      this.mIconURL = null;
      this.lastURI = null;

      ChromeUtils.defineLazyGetter(this, "popupBlocker", () => {
        return new lazy.PopupBlocker(this);
      });

      this.addEventListener(
        "dragover",
        event => {
          if (!this.droppedLinkHandler || event.defaultPrevented) {
            return;
          }

          // For drags that appear to be internal text (for example, tab drags),
          // set the dropEffect to 'none'. This prevents the drop even if some
          // other listener cancelled the event.
          var types = event.dataTransfer.types;
          if (
            types.includes("text/x-moz-text-internal") &&
            !types.includes("text/plain")
          ) {
            event.dataTransfer.dropEffect = "none";
            event.stopPropagation();
            event.preventDefault();
          }

          // No need to handle "dragover" in e10s, since nsDocShellTreeOwner.cpp in the child process
          // handles that case using "@mozilla.org/content/dropped-link-handler;1" service.
          if (this.isRemoteBrowser) {
            return;
          }

          let linkHandler = Services.droppedLinkHandler;
          if (linkHandler.canDropLink(event, false)) {
            event.preventDefault();
          }
        },
        { mozSystemGroup: true }
      );

      this.addEventListener(
        "drop",
        event => {
          // No need to handle "drop" in e10s, since nsDocShellTreeOwner.cpp in the child process
          // handles that case using "@mozilla.org/content/dropped-link-handler;1" service.
          if (
            !this.droppedLinkHandler ||
            event.defaultPrevented ||
            this.isRemoteBrowser
          ) {
            return;
          }

          let linkHandler = Services.droppedLinkHandler;
          try {
            if (!linkHandler.canDropLink(event, false)) {
              return;
            }

            // Pass true to prevent the dropping of javascript:/data: URIs
            var links = linkHandler.dropLinks(event, true);
          } catch (ex) {
            return;
          }

          if (links.length) {
            let triggeringPrincipal = linkHandler.getTriggeringPrincipal(event);
            this.droppedLinkHandler(event, links, triggeringPrincipal);
          }
        },
        { mozSystemGroup: true }
      );

      this.addEventListener("dragstart", event => {
        // If we're a remote browser dealing with a dragstart, stop it
        // from propagating up, since our content process should be dealing
        // with the mouse movement.
        if (this.isRemoteBrowser) {
          event.stopPropagation();
        }
      });
    }

    resetFields() {
      if (this.observer) {
        try {
          Services.obs.removeObserver(
            this.observer,
            "browser:purge-session-history"
          );
        } catch (ex) {
          // It's not clear why this sometimes throws an exception.
        }
        this.observer = null;
      }

      let browser = this;
      this.observer = {
        observe(aSubject, aTopic, aState) {
          if (aTopic == "browser:purge-session-history") {
            browser.purgeSessionHistory();
          } else if (aTopic == "apz:cancel-autoscroll") {
            if (aState == browser._autoScrollScrollId) {
              // Set this._autoScrollScrollId to null, so in stopScroll() we
              // don't call stopApzAutoscroll() (since it's APZ that
              // initiated the stopping).
              browser._autoScrollScrollId = null;
              browser._autoScrollPresShellId = null;

              browser._autoScrollPopup.hidePopup();
            }
          }
        },
        QueryInterface: ChromeUtils.generateQI([
          "nsIObserver",
          "nsISupportsWeakReference",
        ]),
      };

      this._documentURI = null;

      this._originalURI = null;

      this._currentAuthPromptURI = null;

      this._searchTerms = "";

      this._documentContentType = null;

      this._loadContext = null;

      this._webBrowserFind = null;

      this._finder = null;

      this._remoteFinder = null;

      this._fastFind = null;

      this._lastSearchString = null;

      this._characterSet = "";

      this._mayEnableCharacterEncodingMenu = null;

      this._contentPrincipal = null;

      this._contentPartitionedPrincipal = null;

      this._csp = null;

      this._referrerInfo = null;

      this._contentRequestContextID = null;

      this._rdmFullZoom = 1.0;

      this._isSyntheticDocument = false;

      this.mPrefs = Services.prefs;

      this._audioMuted = false;

      this._hasAnyPlayingMediaBeenBlocked = false;

      this._unselectedTabHoverMessageListenerCount = 0;

      this.urlbarChangeTracker = {
        _startedLoadSinceLastUserTyping: false,

        startedLoad() {
          this._startedLoadSinceLastUserTyping = true;
        },
        finishedLoad() {
          this._startedLoadSinceLastUserTyping = false;
        },
        userTyped() {
          this._startedLoadSinceLastUserTyping = false;
        },
      };

      this._userTypedValue = null;

      this._AUTOSCROLL_SNAP = 10;

      this._autoScrollBrowsingContext = null;

      this._startX = null;

      this._startY = null;

      this._autoScrollPopup = null;

      /**
       * These IDs identify the scroll frame being autoscrolled.
       */
      this._autoScrollScrollId = null;

      this._autoScrollPresShellId = null;
    }

    connectedCallback() {
      // We typically use this to avoid running JS that triggers a layout during parse
      // (see comment on the delayConnectedCallback implementation). In this case, we
      // are using it to avoid a leak - see https://bugzilla.mozilla.org/show_bug.cgi?id=1441935#c20.
      if (this.delayConnectedCallback()) {
        return;
      }

      this.construct();
    }

    disconnectedCallback() {
      this.destroy();
    }

    get autoscrollEnabled() {
      if (this.getAttribute("autoscroll") == "false") {
        return false;
      }

      return this.mPrefs.getBoolPref("general.autoScroll", true);
    }

    get canGoBack() {
      return this.webNavigation.canGoBack;
    }

    get canGoForward() {
      return this.webNavigation.canGoForward;
    }

    // While an auth prompt from a base domain different than the current sites is open, we want to display the url of the cross domain site.
    // This is to prevent possible auth spoofing scenarios.
    // The URL of the requesting origin is provided by 'currentAuthPromptURI', this will only be non null while an auth prompt is open.
    // See bug 791594 for reference.
    get currentURI() {
      if (this.currentAuthPromptURI) {
        return this.currentAuthPromptURI;
      }
      if (this.webNavigation) {
        return this.webNavigation.currentURI;
      }
      return null;
    }

    get documentURI() {
      return this.isRemoteBrowser
        ? this._documentURI
        : this.contentDocument?.documentURIObject;
    }

    get documentContentType() {
      if (this.isRemoteBrowser) {
        return this._documentContentType;
      }
      return this.contentDocument ? this.contentDocument.contentType : null;
    }

    set documentContentType(aContentType) {
      if (aContentType != null) {
        if (this.isRemoteBrowser) {
          this._documentContentType = aContentType;
        } else {
          this.contentDocument.documentContentType = aContentType;
        }
      }
    }

    get loadContext() {
      if (this._loadContext) {
        return this._loadContext;
      }

      let { frameLoader } = this;
      if (!frameLoader) {
        return null;
      }
      this._loadContext = frameLoader.loadContext;
      return this._loadContext;
    }

    get autoCompletePopup() {
      return document.getElementById(this.getAttribute("autocompletepopup"));
    }

    set suspendMediaWhenInactive(val) {
      this.browsingContext.suspendMediaWhenInactive = val;
    }

    get suspendMediaWhenInactive() {
      return !!this.browsingContext?.suspendMediaWhenInactive;
    }

    set docShellIsActive(val) {
      this.browsingContext.isActive = val;
      if (this.isRemoteBrowser) {
        let remoteTab = this.frameLoader?.remoteTab;
        if (remoteTab) {
          remoteTab.renderLayers = val;
        }
      }
    }

    get docShellIsActive() {
      return !!this.browsingContext?.isActive;
    }

    set renderLayers(val) {
      if (this.isRemoteBrowser) {
        let remoteTab = this.frameLoader?.remoteTab;
        if (remoteTab) {
          remoteTab.renderLayers = val;
        }
      } else {
        this.docShellIsActive = val;
      }
    }

    get renderLayers() {
      if (this.isRemoteBrowser) {
        return !!this.frameLoader?.remoteTab?.renderLayers;
      }
      return this.docShellIsActive;
    }

    get hasLayers() {
      if (this.isRemoteBrowser) {
        return !!this.frameLoader?.remoteTab?.hasLayers;
      }
      return this.docShellIsActive;
    }

    get isRemoteBrowser() {
      return this.getAttribute("remote") == "true";
    }

    get remoteType() {
      return this.browsingContext?.currentRemoteType;
    }

    get isCrashed() {
      if (!this.isRemoteBrowser || !this.frameLoader) {
        return false;
      }

      return !this.frameLoader.remoteTab;
    }

    get messageManager() {
      // Bug 1524084 - Trying to get at the message manager while in the crashed state will
      // create a new message manager that won't shut down properly when the crashed browser
      // is removed from the DOM. We work around that right now by returning null if we're
      // in the crashed state.
      if (this.frameLoader && !this.isCrashed) {
        return this.frameLoader.messageManager;
      }
      return null;
    }

    get webBrowserFind() {
      if (!this._webBrowserFind) {
        this._webBrowserFind = this.docShell
          .QueryInterface(Ci.nsIInterfaceRequestor)
          .getInterface(Ci.nsIWebBrowserFind);
      }
      return this._webBrowserFind;
    }

    get finder() {
      if (this.isRemoteBrowser) {
        if (!this._remoteFinder) {
          this._remoteFinder = new lazy.FinderParent(this);
        }
        return this._remoteFinder;
      }
      if (!this._finder) {
        if (!this.docShell) {
          return null;
        }

        this._finder = new lazy.Finder(this.docShell);
      }
      return this._finder;
    }

    get fastFind() {
      if (!this._fastFind) {
        if (!("@mozilla.org/typeaheadfind;1" in Cc)) {
          return null;
        }

        var tabBrowser = this.getTabBrowser();
        if (tabBrowser && "fastFind" in tabBrowser) {
          return (this._fastFind = tabBrowser.fastFind);
        }

        if (!this.docShell) {
          return null;
        }

        this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance(
          Ci.nsITypeAheadFind
        );
        this._fastFind.init(this.docShell);
      }
      return this._fastFind;
    }

    get outerWindowID() {
      return this.browsingContext?.currentWindowGlobal?.outerWindowId;
    }

    get innerWindowID() {
      return this.browsingContext?.currentWindowGlobal?.innerWindowId || null;
    }

    get browsingContext() {
      if (this.frameLoader) {
        return this.frameLoader.browsingContext;
      }
      return null;
    }
    /**
     * Note that this overrides webNavigation on XULFrameElement, and duplicates the return value for the non-remote case
     */
    get webNavigation() {
      return this.isRemoteBrowser
        ? this._remoteWebNavigation
        : this.docShell && this.docShell.QueryInterface(Ci.nsIWebNavigation);
    }

    get webProgress() {
      return this.browsingContext?.webProgress;
    }

    get sessionHistory() {
      return this.webNavigation.sessionHistory;
    }

    get contentTitle() {
      return (
        (this.isRemoteBrowser
          ? this.browsingContext?.currentWindowGlobal?.documentTitle
          : this.contentDocument.title) ?? ""
      );
    }

    forceEncodingDetection() {
      if (this.isRemoteBrowser) {
        this.sendMessageToActor("ForceEncodingDetection", {}, "BrowserTab");
      } else {
        this.docShell.forceEncodingDetection();
      }
    }

    get characterSet() {
      return this.isRemoteBrowser ? this._characterSet : this.docShell.charset;
    }

    get mayEnableCharacterEncodingMenu() {
      return this.isRemoteBrowser
        ? this._mayEnableCharacterEncodingMenu
        : this.docShell.mayEnableCharacterEncodingMenu;
    }

    set mayEnableCharacterEncodingMenu(aMayEnable) {
      if (this.isRemoteBrowser) {
        this._mayEnableCharacterEncodingMenu = aMayEnable;
      }
    }

    get contentPrincipal() {
      return this.isRemoteBrowser
        ? this._contentPrincipal
        : this.contentDocument.nodePrincipal;
    }

    get contentPartitionedPrincipal() {
      return this.isRemoteBrowser
        ? this._contentPartitionedPrincipal
        : this.contentDocument.partitionedPrincipal;
    }

    get cookieJarSettings() {
      return this.isRemoteBrowser
        ? this.browsingContext?.currentWindowGlobal?.cookieJarSettings
        : this.contentDocument.cookieJarSettings;
    }

    get csp() {
      return this.isRemoteBrowser ? this._csp : this.contentDocument.csp;
    }

    get contentRequestContextID() {
      if (this.isRemoteBrowser) {
        return this._contentRequestContextID;
      }
      try {
        return this.contentDocument.documentLoadGroup.requestContextID;
      } catch (e) {
        return null;
      }
    }

    get referrerInfo() {
      return this.isRemoteBrowser
        ? this._referrerInfo
        : this.contentDocument.referrerInfo;
    }

    set fullZoom(val) {
      if (val.toFixed(2) == this.fullZoom.toFixed(2)) {
        return;
      }
      if (this.browsingContext.inRDMPane) {
        this._rdmFullZoom = val;
        let event = document.createEvent("Events");
        event.initEvent("FullZoomChange", true, false);
        this.dispatchEvent(event);
      } else {
        this.browsingContext.fullZoom = val;
      }
    }

    get fullZoom() {
      if (this.browsingContext.inRDMPane) {
        return this._rdmFullZoom;
      }
      return this.browsingContext.fullZoom;
    }

    set textZoom(val) {
      if (val.toFixed(2) == this.textZoom.toFixed(2)) {
        return;
      }
      this.browsingContext.textZoom = val;
    }

    get textZoom() {
      return this.browsingContext.textZoom;
    }

    enterResponsiveMode() {
      if (this.browsingContext.inRDMPane) {
        return;
      }
      this.browsingContext.inRDMPane = true;
      this._rdmFullZoom = this.browsingContext.fullZoom;
      this.browsingContext.fullZoom = 1.0;
    }

    leaveResponsiveMode() {
      if (!this.browsingContext.inRDMPane) {
        return;
      }
      this.browsingContext.inRDMPane = false;
      this.browsingContext.fullZoom = this._rdmFullZoom;
    }

    get isSyntheticDocument() {
      if (this.isRemoteBrowser) {
        return this._isSyntheticDocument;
      }
      return this.contentDocument.mozSyntheticDocument;
    }

    get hasContentOpener() {
      return !!this.browsingContext.opener;
    }

    get audioMuted() {
      return this._audioMuted;
    }

    get shouldHandleUnselectedTabHover() {
      return this._unselectedTabHoverMessageListenerCount > 0;
    }

    set shouldHandleUnselectedTabHover(value) {
      this._unselectedTabHoverMessageListenerCount += value ? 1 : -1;
    }

    get securityUI() {
      return this.browsingContext.secureBrowserUI;
    }

    set userTypedValue(val) {
      this.urlbarChangeTracker.userTyped();
      this._userTypedValue = val;
    }

    get userTypedValue() {
      return this._userTypedValue;
    }

    get dontPromptAndDontUnload() {
      return 1;
    }

    get dontPromptAndUnload() {
      return 2;
    }

    set originalURI(aURI) {
      if (aURI instanceof Ci.nsIURI) {
        this._originalURI = aURI;
      }
    }

    get originalURI() {
      return this._originalURI;
    }

    set searchTerms(val) {
      this._searchTerms = val;
    }

    get searchTerms() {
      return this._searchTerms;
    }

    set currentAuthPromptURI(aURI) {
      this._currentAuthPromptURI = aURI;
    }

    get currentAuthPromptURI() {
      return this._currentAuthPromptURI;
    }
    _wrapURIChangeCall(fn) {
      if (!this.isRemoteBrowser) {
        this.isNavigating = true;
        try {
          fn();
        } finally {
          this.isNavigating = false;
        }
      } else {
        fn();
      }
    }

    goBack(
      requireUserInteraction = lazy.BrowserUtils
        .navigationRequireUserInteraction
    ) {
      var webNavigation = this.webNavigation;
      if (webNavigation.canGoBack) {
        this._wrapURIChangeCall(() =>
          webNavigation.goBack(requireUserInteraction)
        );
      }
    }

    goForward(
      requireUserInteraction = lazy.BrowserUtils
        .navigationRequireUserInteraction
    ) {
      var webNavigation = this.webNavigation;
      if (webNavigation.canGoForward) {
        this._wrapURIChangeCall(() =>
          webNavigation.goForward(requireUserInteraction)
        );
      }
    }

    reload() {
      const nsIWebNavigation = Ci.nsIWebNavigation;
      const flags = nsIWebNavigation.LOAD_FLAGS_NONE;
      this.reloadWithFlags(flags);
    }

    reloadWithFlags(aFlags) {
      this.webNavigation.reload(aFlags);
    }

    stop() {
      const nsIWebNavigation = Ci.nsIWebNavigation;
      const flags = nsIWebNavigation.STOP_ALL;
      this.webNavigation.stop(flags);
    }

    _fixLoadParamsToLoadURIOptions(params) {
      let loadFlags =
        params.loadFlags || params.flags || Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
      delete params.flags;
      params.loadFlags = loadFlags;
    }

    /**
     * throws exception for unknown schemes
     */
    loadURI(uri, params = {}) {
      if (!uri) {
        uri = lazy.blankURI;
      }
      this._fixLoadParamsToLoadURIOptions(params);
      this._wrapURIChangeCall(() => this.webNavigation.loadURI(uri, params));
    }

    /**
     * throws exception for unknown schemes
     */
    fixupAndLoadURIString(uriString, params = {}) {
      if (!uriString) {
        this.loadURI(null, params);
        return;
      }
      this._fixLoadParamsToLoadURIOptions(params);
      this._wrapURIChangeCall(() =>
        this.webNavigation.fixupAndLoadURIString(uriString, params)
      );
    }

    gotoIndex(aIndex) {
      this._wrapURIChangeCall(() => this.webNavigation.gotoIndex(aIndex));
    }

    preserveLayers(preserve) {
      if (!this.isRemoteBrowser) {
        return;
      }
      let { frameLoader } = this;
      if (frameLoader.remoteTab) {
        frameLoader.remoteTab.preserveLayers(preserve);
      }
    }

    deprioritize() {
      if (!this.isRemoteBrowser) {
        return;
      }
      let { remoteTab } = this.frameLoader;
      if (remoteTab) {
        remoteTab.priorityHint = false;
        remoteTab.deprioritize();
      }
    }

    getTabBrowser() {
      if (this?.ownerGlobal?.gBrowser?.getTabForBrowser(this)) {
        return this.ownerGlobal.gBrowser;
      }
      return null;
    }

    addProgressListener(aListener, aNotifyMask) {
      if (!aNotifyMask) {
        aNotifyMask = Ci.nsIWebProgress.NOTIFY_ALL;
      }

      this.webProgress.addProgressListener(aListener, aNotifyMask);
    }

    removeProgressListener(aListener) {
      this.webProgress.removeProgressListener(aListener);
    }

    onPageHide() {
      // If we're browsing from the tab crashed UI to a URI that keeps
      // this browser non-remote, we'll handle that here.
      lazy.SessionStore?.maybeExitCrashedState(this);

      if (!this.docShell || !this.fastFind) {
        return;
      }
      var tabBrowser = this.getTabBrowser();
      if (
        !tabBrowser ||
        !("fastFind" in tabBrowser) ||
        tabBrowser.selectedBrowser == this
      ) {
        this.fastFind.setDocShell(this.docShell);
      }
    }

    audioPlaybackStarted() {
      if (this._audioMuted) {
        return;
      }
      let event = document.createEvent("Events");
      event.initEvent("DOMAudioPlaybackStarted", true, false);
      this.dispatchEvent(event);
    }

    audioPlaybackStopped() {
      let event = document.createEvent("Events");
      event.initEvent("DOMAudioPlaybackStopped", true, false);
      this.dispatchEvent(event);
    }

    /**
     * When the pref "media.block-autoplay-until-in-foreground" is on,
     * Gecko delays starting playback of media resources in tabs until the
     * tab has been in the foreground or resumed by tab's play tab icon.
     * - When Gecko delays starting playback of a media resource in a window,
     * it sends a message to call activeMediaBlockStarted(). This causes the
     * tab audio indicator to show.
     * - When a tab is foregrounded, Gecko starts playing all delayed media
     * resources in that tab, and sends a message to call
     * activeMediaBlockStopped(). This causes the tab audio indicator to hide.
     */
    activeMediaBlockStarted() {
      this._hasAnyPlayingMediaBeenBlocked = true;
      let event = document.createEvent("Events");
      event.initEvent("DOMAudioPlaybackBlockStarted", true, false);
      this.dispatchEvent(event);
    }

    activeMediaBlockStopped() {
      if (!this._hasAnyPlayingMediaBeenBlocked) {
        return;
      }
      this._hasAnyPlayingMediaBeenBlocked = false;
      let event = document.createEvent("Events");
      event.initEvent("DOMAudioPlaybackBlockStopped", true, false);
      this.dispatchEvent(event);
    }

    mute(transientState) {
      if (!transientState) {
        this._audioMuted = true;
      }
      let context = this.frameLoader.browsingContext;
      context.notifyMediaMutedChanged(true);
    }

    unmute() {
      this._audioMuted = false;
      let context = this.frameLoader.browsingContext;
      context.notifyMediaMutedChanged(false);
    }

    resumeMedia() {
      this.frameLoader.browsingContext.notifyStartDelayedAutoplayMedia();
      if (this._hasAnyPlayingMediaBeenBlocked) {
        this._hasAnyPlayingMediaBeenBlocked = false;
        let event = document.createEvent("Events");
        event.initEvent("DOMAudioPlaybackBlockStopped", true, false);
        this.dispatchEvent(event);
      }
    }

    unselectedTabHover(hovered) {
      if (!this.shouldHandleUnselectedTabHover) {
        return;
      }
      this.sendMessageToActor(
        "Browser:UnselectedTabHover",
        {
          hovered,
        },
        "UnselectedTabHover",
        "roots"
      );
    }

    didStartLoadSinceLastUserTyping() {
      return (
        !this.isNavigating &&
        this.urlbarChangeTracker._startedLoadSinceLastUserTyping
      );
    }

    constrainPopup(popup) {
      if (this.getAttribute("constrainpopups") != "false") {
        let constraintRect = this.getBoundingClientRect();
        constraintRect = new DOMRect(
          constraintRect.left + window.mozInnerScreenX,
          constraintRect.top + window.mozInnerScreenY,
          constraintRect.width,
          constraintRect.height
        );
        popup.setConstraintRect(constraintRect);
      } else {
        popup.setConstraintRect(new DOMRect(0, 0, 0, 0));
      }
    }

    construct() {
      elementsToDestroyOnUnload.add(this);
      this.resetFields();
      this.mInitialized = true;
      if (this.isRemoteBrowser) {
        /*
         * Don't try to send messages from this function. The message manager for
         * the <browser> element may not be initialized yet.
         */

        this._remoteWebNavigation = new lazy.RemoteWebNavigation(this);

        // Initialize contentPrincipal to the about:blank principal for this loadcontext
        let aboutBlank = Services.io.newURI("about:blank");
        let ssm = Services.scriptSecurityManager;
        this._contentPrincipal = ssm.getLoadContextContentPrincipal(
          aboutBlank,
          this.loadContext
        );
        this._contentPartitionedPrincipal = this._contentPrincipal;
        // CSP for about:blank is null; if we ever change _contentPrincipal above,
        // we should re-evaluate the CSP here.
        this._csp = null;

        if (!this.hasAttribute("disablehistory")) {
          Services.obs.addObserver(
            this.observer,
            "browser:purge-session-history",
            true
          );
        }
      }

      try {
        // |webNavigation.sessionHistory| will have been set by the frame
        // loader when creating the docShell as long as this xul:browser
        // doesn't have the 'disablehistory' attribute set.
        if (this.docShell && this.webNavigation.sessionHistory) {
          Services.obs.addObserver(
            this.observer,
            "browser:purge-session-history",
            true
          );

          // enable global history if we weren't told otherwise
          if (
            !this.hasAttribute("disableglobalhistory") &&
            !this.isRemoteBrowser
          ) {
            try {
              this.docShell.browsingContext.useGlobalHistory = true;
            } catch (ex) {
              // This can occur if the Places database is locked
              console.error("Error enabling browser global history: ", ex);
            }
          }
        }
      } catch (e) {
        console.error(e);
      }
      try {
        // Ensures the securityUI is initialized.
        var securityUI = this.securityUI; // eslint-disable-line no-unused-vars
      } catch (e) {}

      if (!this.isRemoteBrowser) {
        this._remoteWebNavigation = null;
        this.addEventListener("pagehide", this.onPageHide, true);
      }
    }

    /**
     * This is necessary because custom elements don't have a "real" destructor.
     * This method is called explicitly by tabbrowser, when changing remoteness,
     * and when we're disconnected or the window unloads.
     */
    destroy() {
      elementsToDestroyOnUnload.delete(this);

      // If we're browsing from the tab crashed UI to a URI that causes the tab
      // to go remote again, we catch this here, because swapping out the
      // non-remote browser for a remote one doesn't cause the pagehide event
      // to be fired. Previously, we used to do this in the frame script's
      // unload handler.
      lazy.SessionStore?.maybeExitCrashedState(this);

      // Make sure that any open select is closed.
      let menulist = document.getElementById("ContentSelectDropdown");
      if (menulist?.open) {
        lazy.SelectParentHelper.hide(menulist, this);
      }

      this.resetFields();

      if (!this.mInitialized) {
        return;
      }

      this.mInitialized = false;
      this.lastURI = null;

      if (!this.isRemoteBrowser) {
        this.removeEventListener("pagehide", this.onPageHide, true);
      }
    }

    updateForStateChange(aCharset, aDocumentURI, aContentType) {
      if (this.isRemoteBrowser && this.messageManager) {
        if (aCharset != null) {
          this._characterSet = aCharset;
        }

        if (aDocumentURI != null) {
          this._documentURI = aDocumentURI;
        }

        if (aContentType != null) {
          this._documentContentType = aContentType;
        }
      }
    }

    updateWebNavigationForLocationChange(aCanGoBack, aCanGoForward) {
      if (
        this.isRemoteBrowser &&
        this.messageManager &&
        !Services.appinfo.sessionHistoryInParent
      ) {
        this._remoteWebNavigation._canGoBack = aCanGoBack;
        this._remoteWebNavigation._canGoForward = aCanGoForward;
      }
    }

    updateForLocationChange(
      aLocation,
      aCharset,
      aMayEnableCharacterEncodingMenu,
      aDocumentURI,
      aTitle,
      aContentPrincipal,
      aContentPartitionedPrincipal,
      aCSP,
      aReferrerInfo,
      aIsSynthetic,
      aHaveRequestContextID,
      aRequestContextID,
      aContentType
    ) {
      if (this.isRemoteBrowser && this.messageManager) {
        if (aCharset != null) {
          this._characterSet = aCharset;
          this._mayEnableCharacterEncodingMenu =
            aMayEnableCharacterEncodingMenu;
        }

        if (aContentType != null) {
          this._documentContentType = aContentType;
        }

        this._remoteWebNavigation._currentURI = aLocation;
        this._documentURI = aDocumentURI;
        this._contentPrincipal = aContentPrincipal;
        this._contentPartitionedPrincipal = aContentPartitionedPrincipal;
        this._csp = aCSP;
        this._referrerInfo = aReferrerInfo;
        this._isSyntheticDocument = aIsSynthetic;
        this._contentRequestContextID = aHaveRequestContextID
          ? aRequestContextID
          : null;
      }
    }

    purgeSessionHistory() {
      if (this.isRemoteBrowser && !Services.appinfo.sessionHistoryInParent) {
        this._remoteWebNavigation._canGoBack = false;
        this._remoteWebNavigation._canGoForward = false;
      }

      try {
        if (Services.appinfo.sessionHistoryInParent) {
          let sessionHistory = this.browsingContext?.sessionHistory;
          if (!sessionHistory) {
            return;
          }

          // place the entry at current index at the end of the history list, so it won't get removed
          if (sessionHistory.index < sessionHistory.count - 1) {
            let indexEntry = sessionHistory.getEntryAtIndex(
              sessionHistory.index
            );
            sessionHistory.addEntry(indexEntry, true);
          }

          let purge = sessionHistory.count;
          if (
            this.browsingContext.currentWindowGlobal.documentURI !=
            "about:blank"
          ) {
            --purge; // Don't remove the page the user's staring at from shistory
          }

          if (purge > 0) {
            sessionHistory.purgeHistory(purge);
          }

          return;
        }

        this.sendMessageToActor(
          "Browser:PurgeSessionHistory",
          {},
          "PurgeSessionHistory",
          "roots"
        );
      } catch (ex) {
        // This can throw if the browser has started to go away.
        if (ex.result != Cr.NS_ERROR_NOT_INITIALIZED) {
          throw ex;
        }
      }
    }

    createAboutBlankDocumentViewer(aPrincipal, aPartitionedPrincipal) {
      let principal = lazy.BrowserUtils.principalWithMatchingOA(
        aPrincipal,
        this.contentPrincipal
      );
      let partitionedPrincipal = lazy.BrowserUtils.principalWithMatchingOA(
        aPartitionedPrincipal,
        this.contentPartitionedPrincipal
      );

      if (this.isRemoteBrowser) {
        this.frameLoader.remoteTab.createAboutBlankDocumentViewer(
          principal,
          partitionedPrincipal
        );
      } else {
        this.docShell.createAboutBlankDocumentViewer(
          principal,
          partitionedPrincipal
        );
      }
    }

    _acquireAutoScrollWakeLock() {
      const pm = Cc["@mozilla.org/power/powermanagerservice;1"].getService(
        Ci.nsIPowerManagerService
      );
      this._autoScrollWakelock = pm.newWakeLock("autoscroll", window);
    }

    _releaseAutoScrollWakeLock() {
      if (this._autoScrollWakelock) {
        try {
          this._autoScrollWakelock.unlock();
        } catch (e) {
          // Ignore error since wake lock is already unlocked
        }
        this._autoScrollWakelock = null;
      }
    }

    stopScroll() {
      if (this._autoScrollBrowsingContext) {
        window.removeEventListener("mousemove", this, true);
        window.removeEventListener("mousedown", this, true);
        window.removeEventListener("mouseup", this, true);
        window.removeEventListener("DOMMouseScroll", this, true);
        window.removeEventListener("contextmenu", this, true);
        window.removeEventListener("keydown", this, true);
        window.removeEventListener("keypress", this, true);
        window.removeEventListener("keyup", this, true);

        let autoScrollWnd = this._autoScrollBrowsingContext.currentWindowGlobal;
        if (autoScrollWnd) {
          autoScrollWnd
            .getActor("AutoScroll")
            .sendAsyncMessage("Autoscroll:Stop", {});
        }

        try {
          Services.obs.removeObserver(this.observer, "apz:cancel-autoscroll");
        } catch (ex) {
          // It's not clear why this sometimes throws an exception
        }

        if (this._autoScrollScrollId != null) {
          this._autoScrollBrowsingContext.stopApzAutoscroll(
            this._autoScrollScrollId,
            this._autoScrollPresShellId
          );

          this._autoScrollScrollId = null;
          this._autoScrollPresShellId = null;
        }

        this._autoScrollBrowsingContext = null;
        this._releaseAutoScrollWakeLock();
      }
    }

    _getAndMaybeCreateAutoScrollPopup() {
      let autoscrollPopup = document.getElementById("autoscroller");
      if (!autoscrollPopup) {
        autoscrollPopup = document.createXULElement("panel");
        autoscrollPopup.className = "autoscroller";
        autoscrollPopup.setAttribute("consumeoutsideclicks", "true");
        autoscrollPopup.setAttribute("rolluponmousewheel", "true");
        autoscrollPopup.id = "autoscroller";
      }

      return autoscrollPopup;
    }

    startScroll({
      scrolldir,
      screenXDevPx,
      screenYDevPx,
      scrollId,
      presShellId,
      browsingContext,
    }) {
      if (!this.autoscrollEnabled) {
        return { autoscrollEnabled: false, usingApz: false };
      }

      // The popup size is 32px for the circle plus space for a 4px box-shadow
      // on each side.
      const POPUP_SIZE = 40;
      if (!this._autoScrollPopup) {
        this._autoScrollPopup = this._getAndMaybeCreateAutoScrollPopup();
        document.documentElement.appendChild(this._autoScrollPopup);
        this._autoScrollPopup.removeAttribute("hidden");
        this._autoScrollPopup.setAttribute("noautofocus", "true");
        this._autoScrollPopup.style.height = POPUP_SIZE + "px";
        this._autoScrollPopup.style.width = POPUP_SIZE + "px";
        this._autoScrollPopup.style.margin = -POPUP_SIZE / 2 + "px";
      }

      // In desktop pixels.
      let screenXDesktopPx = screenXDevPx / window.desktopToDeviceScale;
      let screenYDesktopPx = screenYDevPx / window.desktopToDeviceScale;

      let screenManager = Cc["@mozilla.org/gfx/screenmanager;1"].getService(
        Ci.nsIScreenManager
      );
      let screen = screenManager.screenForRect(
        screenXDesktopPx,
        screenYDesktopPx,
        1,
        1
      );

      // we need these attributes so themers don't need to create per-platform packages
      if (screen.colorDepth > 8) {
        // need high color for transparency
        // Exclude second-rate platforms
        this._autoScrollPopup.setAttribute(
          "transparent",
          !/BeOS|OS\/2/.test(navigator.appVersion)
        );
        // Enable translucency on Windows and Mac
        this._autoScrollPopup.setAttribute(
          "translucent",
          AppConstants.platform == "win" || AppConstants.platform == "macosx"
        );
      }

      this._autoScrollPopup.setAttribute("scrolldir", scrolldir);
      this._autoScrollPopup.addEventListener("popuphidden", this, true);

      // In CSS pixels
      let popupX;
      let popupY;
      {
        let cssToDesktopScale =
          window.devicePixelRatio / window.desktopToDeviceScale;

        // Sanitize screenX/screenY for available screen size with half the size
        // of the popup removed. The popup uses negative margins to center on the
        // coordinates we pass. Use desktop pixels to deal correctly with
        // multi-monitor / multi-dpi scenarios.
        let left = {},
          top = {},
          width = {},
          height = {};
        screen.GetAvailRectDisplayPix(left, top, width, height);

        let popupSizeDesktopPx = POPUP_SIZE * cssToDesktopScale;
        let minX = left.value + 0.5 * popupSizeDesktopPx;
        let maxX = left.value + width.value - 0.5 * popupSizeDesktopPx;
        let minY = top.value + 0.5 * popupSizeDesktopPx;
        let maxY = top.value + height.value - 0.5 * popupSizeDesktopPx;

        popupX =
          Math.max(minX, Math.min(maxX, screenXDesktopPx)) / cssToDesktopScale;
        popupY =
          Math.max(minY, Math.min(maxY, screenYDesktopPx)) / cssToDesktopScale;
      }

      // In CSS pixels.
      let screenX = screenXDevPx / window.devicePixelRatio;
      let screenY = screenYDevPx / window.devicePixelRatio;

      this._autoScrollPopup.openPopupAtScreen(popupX, popupY);
      this._ignoreMouseEvents = true;
      this._startX = screenX;
      this._startY = screenY;
      this._autoScrollBrowsingContext = browsingContext;
      this._acquireAutoScrollWakeLock();

      window.addEventListener("mousemove", this, true);
      window.addEventListener("mousedown", this, true);
      window.addEventListener("mouseup", this, true);
      window.addEventListener("DOMMouseScroll", this, true);
      window.addEventListener("contextmenu", this, true);
      window.addEventListener("keydown", this, true);
      window.addEventListener("keypress", this, true);
      window.addEventListener("keyup", this, true);

      let usingApz = false;

      if (
        scrollId != null &&
        this.mPrefs.getBoolPref("apz.autoscroll.enabled", false)
      ) {
        // If APZ is handling the autoscroll, it may decide to cancel
        // it of its own accord, so register an observer to allow it
        // to notify us of that.
        Services.obs.addObserver(this.observer, "apz:cancel-autoscroll", true);

        usingApz = browsingContext.startApzAutoscroll(
          screenXDevPx,
          screenYDevPx,
          scrollId,
          presShellId
        );

        // Save the IDs for later
        this._autoScrollScrollId = scrollId;
        this._autoScrollPresShellId = presShellId;
      }

      return { autoscrollEnabled: true, usingApz };
    }

    cancelScroll() {
      this._autoScrollPopup.hidePopup();
    }

    handleEvent(aEvent) {
      if (this._autoScrollBrowsingContext) {
        switch (aEvent.type) {
          case "mousemove": {
            var x = aEvent.screenX - this._startX;
            var y = aEvent.screenY - this._startY;

            if (
              x > this._AUTOSCROLL_SNAP ||
              x < -this._AUTOSCROLL_SNAP ||
              y > this._AUTOSCROLL_SNAP ||
              y < -this._AUTOSCROLL_SNAP
            ) {
              this._ignoreMouseEvents = false;
            }
            break;
          }
          case "mouseup":
          case "mousedown":
            // The following mouse click/auxclick event on the autoscroller
            // shouldn't be fired in web content for compatibility with Chrome.
            aEvent.preventClickEvent();
          // fallthrough
          case "contextmenu": {
            if (!this._ignoreMouseEvents) {
              // Use a timeout to prevent the mousedown from opening the popup again.
              // Ideally, we could use preventDefault here, but contenteditable
              // and middlemouse paste don't interact well. See bug 1188536.
              setTimeout(() => this._autoScrollPopup.hidePopup(), 0);
            }
            this._ignoreMouseEvents = false;
            break;
          }
          case "DOMMouseScroll": {
            this._autoScrollPopup.hidePopup();
            aEvent.preventDefault();
            break;
          }
          case "popuphidden": {
            // TODO: When the autoscroller is closed by clicking outside of it,
            //       we need to prevent following click event for compatibility
            //       with Chrome.  However, there is no way to do that for now.
            this._autoScrollPopup.removeEventListener(
              "popuphidden",
              this,
              true
            );
            this.stopScroll();
            break;
          }
          case "keydown": {
            if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
              // the escape key will be processed by
              // nsXULPopupManager::KeyDown and the panel will be closed.
              // So, don't consume the key event here.
              break;
            }
            // don't break here. we need to eat keydown events.
          }
          // fall through
          case "keypress":
          case "keyup": {
            // All keyevents should be eaten here during autoscrolling.
            aEvent.stopPropagation();
            aEvent.preventDefault();
            break;
          }
        }
      }
    }

    closeBrowser() {
      // The request comes from a XPCOM component, we'd want to redirect
      // the request to tabbrowser.
      let tabbrowser = this.getTabBrowser();
      if (tabbrowser) {
        let tab = tabbrowser.getTabForBrowser(this);
        if (tab) {
          tabbrowser.removeTab(tab);
          return;
        }
      }

      throw new Error(
        "Closing a browser which was not attached to a tabbrowser is unsupported."
      );
    }

    swapBrowsers(aOtherBrowser) {
      // The request comes from a XPCOM component, we'd want to redirect
      // the request to tabbrowser so tabbrowser will be setup correctly,
      // and it will eventually call swapDocShells.
      let ourTabBrowser = this.getTabBrowser();
      let otherTabBrowser = aOtherBrowser.getTabBrowser();
      if (ourTabBrowser && otherTabBrowser) {
        let ourTab = ourTabBrowser.getTabForBrowser(this);
        let otherTab = otherTabBrowser.getTabForBrowser(aOtherBrowser);
        ourTabBrowser.swapBrowsers(ourTab, otherTab);
        return;
      }

      // One of us is not connected to a tabbrowser, so just swap.
      this.swapDocShells(aOtherBrowser);
    }

    swapDocShells(aOtherBrowser) {
      if (this.isRemoteBrowser != aOtherBrowser.isRemoteBrowser) {
        throw new Error(
          "Can only swap docshells between browsers in the same process."
        );
      }

      // Give others a chance to swap state.
      // IMPORTANT: Since a swapDocShells call does not swap the messageManager
      //            instances attached to a browser to aOtherBrowser, others
      //            will need to add the message listeners to the new
      //            messageManager.
      //            This is not a bug in swapDocShells or the FrameLoader,
      //            merely a design decision: If message managers were swapped,
      //            so that no new listeners were needed, the new
      //            aOtherBrowser.messageManager would have listeners pointing
      //            to the JS global of the current browser, which would rather
      //            easily create leaks while swapping.
      // IMPORTANT2: When the current browser element is removed from DOM,
      //             which is quite common after a swapDocShells call, its
      //             frame loader is destroyed, and that destroys the relevant
      //             message manager, which will remove the listeners.
      let event = new CustomEvent("SwapDocShells", { detail: aOtherBrowser });
      this.dispatchEvent(event);
      event = new CustomEvent("SwapDocShells", { detail: this });
      aOtherBrowser.dispatchEvent(event);

      // We need to swap fields that are tied to our docshell or related to
      // the loaded page
      // Fields which are built as a result of notifactions (pageshow/hide,
      // DOMLinkAdded/Removed, onStateChange) should not be swapped here,
      // because these notifications are dispatched again once the docshells
      // are swapped.
      var fieldsToSwap = ["_webBrowserFind", "_rdmFullZoom"];

      if (this.isRemoteBrowser) {
        fieldsToSwap.push(
          ...[
            "_remoteWebNavigation",
            "_remoteFinder",
            "_documentURI",
            "_documentContentType",
            "_characterSet",
            "_mayEnableCharacterEncodingMenu",
            "_contentPrincipal",
            "_contentPartitionedPrincipal",
            "_isSyntheticDocument",
            "_originalURI",
            "_userTypedValue",
          ]
        );
      }

      var ourFieldValues = {};
      var otherFieldValues = {};
      for (let field of fieldsToSwap) {
        ourFieldValues[field] = this[field];
        otherFieldValues[field] = aOtherBrowser[field];
      }

      if (window.PopupNotifications) {
        PopupNotifications._swapBrowserNotifications(aOtherBrowser, this);
      }

      try {
        this.swapFrameLoaders(aOtherBrowser);
      } catch (ex) {
        // This may not be implemented for browser elements that are not
        // attached to a BrowserDOMWindow.
      }

      for (let field of fieldsToSwap) {
        this[field] = otherFieldValues[field];
        aOtherBrowser[field] = ourFieldValues[field];
      }

      if (!this.isRemoteBrowser) {
        // Null the current nsITypeAheadFind instances so that they're
        // lazily re-created on access. We need to do this because they
        // might have attached the wrong docShell.
        this._fastFind = aOtherBrowser._fastFind = null;
      } else {
        // Rewire the remote listeners
        this._remoteWebNavigation.swapBrowser(this);
        aOtherBrowser._remoteWebNavigation.swapBrowser(aOtherBrowser);

        if (this._remoteFinder) {
          this._remoteFinder.swapBrowser(this);
        }
        if (aOtherBrowser._remoteFinder) {
          aOtherBrowser._remoteFinder.swapBrowser(aOtherBrowser);
        }
      }

      event = new CustomEvent("EndSwapDocShells", { detail: aOtherBrowser });
      this.dispatchEvent(event);
      event = new CustomEvent("EndSwapDocShells", { detail: this });
      aOtherBrowser.dispatchEvent(event);
    }

    getInPermitUnload(aCallback) {
      if (this.isRemoteBrowser) {
        let { remoteTab } = this.frameLoader;
        if (!remoteTab) {
          // If we're crashed, we're definitely not in this state anymore.
          aCallback(false);
          return;
        }

        aCallback(
          this._inPermitUnload.has(this.browsingContext.currentWindowGlobal)
        );
        return;
      }

      if (!this.docShell || !this.docShell.docViewer) {
        aCallback(false);
        return;
      }
      aCallback(this.docShell.docViewer.inPermitUnload);
    }

    async asyncPermitUnload(action) {
      let wgp = this.browsingContext.currentWindowGlobal;
      if (this._inPermitUnload.has(wgp)) {
        throw new Error("permitUnload is already running for this tab.");
      }

      this._inPermitUnload.add(wgp);
      try {
        let permitUnload = await wgp.permitUnload(
          action,
          lazyPrefs.unloadTimeoutMs
        );
        return { permitUnload };
      } finally {
        this._inPermitUnload.delete(wgp);
      }
    }

    get hasBeforeUnload() {
      function hasBeforeUnload(bc) {
        if (bc.currentWindowContext?.hasBeforeUnload) {
          return true;
        }
        return bc.children.some(hasBeforeUnload);
      }
      return hasBeforeUnload(this.browsingContext);
    }

    permitUnload(action) {
      if (this.isRemoteBrowser) {
        if (!this.hasBeforeUnload) {
          return { permitUnload: true };
        }

        // Don't bother asking if this browser is hung:
        if (
          lazy.ProcessHangMonitor?.findActiveReport(this) ||
          lazy.ProcessHangMonitor?.findPausedReport(this)
        ) {
          return { permitUnload: true };
        }

        let result;
        let success;

        this.asyncPermitUnload(action).then(
          val => {
            result = val;
            success = true;
          },
          err => {
            result = err;
            success = false;
          }
        );

        // The permitUnload() promise will, alas, not call its resolution
        // callbacks after the browser window the promise lives in has closed,
        // so we have to check for that case explicitly.
        Services.tm.spinEventLoopUntilOrQuit(
          "browser-custom-element.js:permitUnload",
          () => window.closed || success !== undefined
        );
        if (success) {
          return result;
        }
        throw result;
      }

      if (!this.docShell || !this.docShell.docViewer) {
        return { permitUnload: true };
      }
      return {
        permitUnload: this.docShell.docViewer.permitUnload(),
      };
    }

    /**
     * Gets a screenshot of this browser as an ImageBitmap.
     *
     * @param {Number} x
     *   The x coordinate of the region from the underlying document to capture
     *   as a screenshot. This is ignored if fullViewport is true.
     * @param {Number} y
     *   The y coordinate of the region from the underlying document to capture
     *   as a screenshot. This is ignored if fullViewport is true.
     * @param {Number} w
     *   The width of the region from the underlying document to capture as a
     *   screenshot. This is ignored if fullViewport is true.
     * @param {Number} h
     *   The height of the region from the underlying document to capture as a
     *   screenshot. This is ignored if fullViewport is true.
     * @param {Number} scale
     *   The scale factor for the captured screenshot. See the documentation for
     *   WindowGlobalParent.drawSnapshot for more detail.
     * @param {String} backgroundColor
     *   The default background color for the captured screenshot. See the
     *   documentation for WindowGlobalParent.drawSnapshot for more detail.
     * @param {boolean|undefined} fullViewport
     *   True if the viewport rect should be captured. If this is true, the
     *   x, y, w and h parameters are ignored. Defaults to false.
     * @returns {Promise}
     * @resolves {ImageBitmap}
     */
    async drawSnapshot(
      x,
      y,
      w,
      h,
      scale,
      backgroundColor,
      fullViewport = false
    ) {
      let rect = fullViewport ? null : new DOMRect(x, y, w, h);
      try {
        return this.browsingContext.currentWindowGlobal.drawSnapshot(
          rect,
          scale,
          backgroundColor
        );
      } catch (e) {
        return false;
      }
    }

    dropLinks(aLinks, aTriggeringPrincipal) {
      if (!this.droppedLinkHandler) {
        return false;
      }
      let links = [];
      for (let i = 0; i < aLinks.length; i += 3) {
        links.push({
          url: aLinks[i],
          name: aLinks[i + 1],
          type: aLinks[i + 2],
        });
      }
      this.droppedLinkHandler(null, links, aTriggeringPrincipal);
      return true;
    }

    getContentBlockingLog() {
      let windowGlobal = this.browsingContext.currentWindowGlobal;
      if (!windowGlobal) {
        return null;
      }
      return windowGlobal.contentBlockingLog;
    }

    getContentBlockingEvents() {
      let windowGlobal = this.browsingContext.currentWindowGlobal;
      if (!windowGlobal) {
        return 0;
      }
      return windowGlobal.contentBlockingEvents;
    }

    // Send an asynchronous message to the remote child via an actor.
    // Note: use this only for messages through an actor. For old-style
    // messages, use the message manager.
    // The value of the scope argument determines which browsing contexts
    // are sent to:
    //   'all' - send to actors associated with all descendant child frames.
    //   'roots' - send only to actors associated with process roots.
    //   undefined/'' - send only to the top-level actor and not any descendants.
    sendMessageToActor(messageName, args, actorName, scope) {
      if (!this.frameLoader) {
        return;
      }

      function sendToChildren(browsingContext, childScope) {
        let windowGlobal = browsingContext.currentWindowGlobal;
        // If 'roots' is set, only send if windowGlobal.isProcessRoot is true.
        if (
          windowGlobal &&
          (childScope != "roots" || windowGlobal.isProcessRoot)
        ) {
          windowGlobal.getActor(actorName).sendAsyncMessage(messageName, args);
        }

        // Iterate as long as scope in assigned. Note that we use the original
        // passed in scope, not childScope here.
        if (scope) {
          for (let context of browsingContext.children) {
            sendToChildren(context, scope);
          }
        }
      }

      // Pass no second argument to always send to the top-level browsing context.
      sendToChildren(this.browsingContext);
    }

    enterModalState() {
      this.sendMessageToActor("EnterModalState", {}, "BrowserElement", "roots");
    }

    leaveModalState() {
      this.sendMessageToActor(
        "LeaveModalState",
        { forceLeave: true },
        "BrowserElement",
        "roots"
      );
    }

    /**
     * Can be called for a window with or without modal state.
     * If the window is not in modal state, this is a no-op.
     */
    maybeLeaveModalState() {
      this.sendMessageToActor(
        "LeaveModalState",
        { forceLeave: false },
        "BrowserElement",
        "roots"
      );
    }

    getDevicePermissionOrigins(key) {
      if (typeof key !== "string" || key.length === 0) {
        throw new Error("Key must be non empty string.");
      }
      if (!this._devicePermissionOrigins) {
        this._devicePermissionOrigins = new Map();
      }
      let origins = this._devicePermissionOrigins.get(key);
      if (!origins) {
        origins = new Set();
        this._devicePermissionOrigins.set(key, origins);
      }
      return origins;
    }

    // This method is replaced by frontend code in order to delay performing the
    // process switch until some async operatin is completed.
    //
    // This is used by tabbrowser to flush SessionStore before a process switch.
    async prepareToChangeRemoteness() {
      /* no-op unless replaced */
    }

    // This method is replaced by frontend code in order to handle restoring
    // remote session history
    //
    // Called immediately after changing remoteness.  If this method returns
    // `true`, Gecko will assume frontend handled resuming the load, and will
    // not attempt to resume the load itself.
    afterChangeRemoteness() {
      /* no-op unless replaced */
      return false;
    }

    // Called by Gecko before the remoteness change happens, allowing for
    // listeners, etc. to be stashed before the process switch.
    beforeChangeRemoteness() {
      // Fire the `WillChangeBrowserRemoteness` event, which may be hooked by
      // frontend code for custom behaviour.
      let event = document.createEvent("Events");
      event.initEvent("WillChangeBrowserRemoteness", true, false);
      this.dispatchEvent(event);

      // Destroy ourselves to unregister from observer notifications
      // FIXME: Can we get away with something less destructive here?
      this.destroy();
    }

    finishChangeRemoteness(redirectLoadSwitchId) {
      // Re-construct ourselves after the destroy in `beforeChangeRemoteness`.
      this.construct();

      // Fire the `DidChangeBrowserRemoteness` event, which may be hooked by
      // frontend code for custom behaviour.
      let event = document.createEvent("Events");
      event.initEvent("DidChangeBrowserRemoteness", true, false);
      this.dispatchEvent(event);

      // Call into frontend code which may want to handle the load (e.g. to
      // while restoring session state).
      return this.afterChangeRemoteness(redirectLoadSwitchId);
    }
  }

  MozXULElement.implementCustomInterface(MozBrowser, [Ci.nsIBrowser]);
  customElements.define("browser", MozBrowser);
}
PK
!<~�g%##0chrome/toolkit/content/global/elements/button.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
  class MozButtonBase extends MozElements.BaseText {
    constructor() {
      super();

      /**
       * While it would seem we could do this by handling oncommand, we can't
       * because any external oncommand handlers might get called before ours,
       * and then they would see the incorrect value of checked. Additionally
       * a command attribute would redirect the command events anyway.
       */
      this.addEventListener("click", event => {
        if (event.button != 0) {
          return;
        }
        this._handleClick();
      });

      this.addEventListener("keypress", event => {
        if (event.key != " ") {
          return;
        }
        this._handleClick();
        // Prevent page from scrolling on the space key.
        event.preventDefault();
      });

      this.addEventListener("keypress", event => {
        if (this.hasMenu()) {
          if (this.open) {
            return;
          }
        } else if (!this.inRichListItem) {
          if (
            event.keyCode == KeyEvent.DOM_VK_UP ||
            (event.keyCode == KeyEvent.DOM_VK_LEFT &&
              document.defaultView.getComputedStyle(this.parentNode)
                .direction == "ltr") ||
            (event.keyCode == KeyEvent.DOM_VK_RIGHT &&
              document.defaultView.getComputedStyle(this.parentNode)
                .direction == "rtl")
          ) {
            event.preventDefault();
            window.document.commandDispatcher.rewindFocus();
            return;
          }

          if (
            event.keyCode == KeyEvent.DOM_VK_DOWN ||
            (event.keyCode == KeyEvent.DOM_VK_RIGHT &&
              document.defaultView.getComputedStyle(this.parentNode)
                .direction == "ltr") ||
            (event.keyCode == KeyEvent.DOM_VK_LEFT &&
              document.defaultView.getComputedStyle(this.parentNode)
                .direction == "rtl")
          ) {
            event.preventDefault();
            window.document.commandDispatcher.advanceFocus();
            return;
          }
        }

        if (
          event.keyCode ||
          event.charCode <= 32 ||
          event.altKey ||
          event.ctrlKey ||
          event.metaKey
        ) {
          return;
        } // No printable char pressed, not a potential accesskey

        // Possible accesskey pressed
        var charPressedLower = String.fromCharCode(
          event.charCode
        ).toLowerCase();

        // If the accesskey of the current button is pressed, just activate it
        if (this.accessKey?.toLowerCase() == charPressedLower) {
          this.click();
          return;
        }

        // Search for accesskey in the list of buttons for this doc and each subdoc
        // Get the buttons for the main document and all sub-frames
        for (
          var frameCount = -1;
          frameCount < window.top.frames.length;
          frameCount++
        ) {
          var doc =
            frameCount == -1
              ? window.top.document
              : window.top.frames[frameCount].document;
          if (this.fireAccessKeyButton(doc.documentElement, charPressedLower)) {
            return;
          }
        }

        // Test dialog buttons
        let buttonBox = window.top.document.querySelector("dialog")?.buttonBox;
        if (buttonBox) {
          this.fireAccessKeyButton(buttonBox, charPressedLower);
        }
      });
    }

    set type(val) {
      this.setAttribute("type", val);
    }

    get type() {
      return this.getAttribute("type");
    }

    set disabled(val) {
      if (val) {
        this.setAttribute("disabled", "true");
      } else {
        this.removeAttribute("disabled");
      }
    }

    get disabled() {
      return this.getAttribute("disabled") == "true";
    }

    set group(val) {
      this.setAttribute("group", val);
    }

    get group() {
      return this.getAttribute("group");
    }

    set open(val) {
      if (this.hasMenu()) {
        this.openMenu(val);
      } else if (val) {
        // Fall back to just setting the attribute
        this.setAttribute("open", "true");
      } else {
        this.removeAttribute("open");
      }
    }

    get open() {
      return this.hasAttribute("open");
    }

    set checked(val) {
      if (this.type == "radio" && val) {
        var sibs = this.parentNode.getElementsByAttribute("group", this.group);
        for (var i = 0; i < sibs.length; ++i) {
          sibs[i].removeAttribute("checked");
        }
      }

      if (val) {
        this.setAttribute("checked", "true");
      } else {
        this.removeAttribute("checked");
      }
    }

    get checked() {
      return this.hasAttribute("checked");
    }

    filterButtons(node) {
      // if the node isn't visible, don't descend into it.
      var cs = node.ownerGlobal.getComputedStyle(node);
      if (cs.visibility != "visible" || cs.display == "none") {
        return NodeFilter.FILTER_REJECT;
      }
      // but it may be a popup element, in which case we look at "state"...
      if (XULPopupElement.isInstance(node) && node.state != "open") {
        return NodeFilter.FILTER_REJECT;
      }
      // OK - the node seems visible, so it is a candidate.
      if (node.localName == "button" && node.accessKey && !node.disabled) {
        return NodeFilter.FILTER_ACCEPT;
      }
      return NodeFilter.FILTER_SKIP;
    }

    fireAccessKeyButton(aSubtree, aAccessKeyLower) {
      var iterator = aSubtree.ownerDocument.createTreeWalker(
        aSubtree,
        NodeFilter.SHOW_ELEMENT,
        this.filterButtons
      );
      while (iterator.nextNode()) {
        var test = iterator.currentNode;
        if (
          test.accessKey?.toLowerCase() == aAccessKeyLower &&
          !test.disabled &&
          !test.collapsed &&
          !test.hidden
        ) {
          test.focus();
          test.click();
          return true;
        }
      }
      return false;
    }

    _handleClick() {
      if (!this.disabled) {
        if (this.type == "checkbox") {
          this.checked = !this.checked;
        } else if (this.type == "radio") {
          this.checked = true;
        }
      }
    }
  }

  MozXULElement.implementCustomInterface(MozButtonBase, [
    Ci.nsIDOMXULButtonElement,
  ]);

  MozElements.ButtonBase = MozButtonBase;

  class MozButton extends MozButtonBase {
    static get inheritedAttributes() {
      return {
        ".box-inherit": "align,dir,pack,orient",
        ".button-icon": "src=image",
        ".button-text": "value=label,accesskey,crop",
        ".button-menu-dropmarker": "open,disabled,label",
      };
    }

    get icon() {
      return this.querySelector(".button-icon");
    }

    static get buttonFragment() {
      let frag = document.importNode(
        MozXULElement.parseXULToFragment(`
        <hbox class="box-inherit button-box" align="center" pack="center" flex="1" anonid="button-box">
          <image class="button-icon"/>
          <label class="button-text"/>
        </hbox>`),
        true
      );
      Object.defineProperty(this, "buttonFragment", { value: frag });
      return frag;
    }

    static get menuFragment() {
      let frag = document.importNode(
        MozXULElement.parseXULToFragment(`
        <hbox class="box-inherit button-box" align="center" pack="center" flex="1">
          <hbox class="box-inherit" align="center" pack="center" flex="1">
            <image class="button-icon"/>
            <label class="button-text"/>
          </hbox>
          <dropmarker class="button-menu-dropmarker"/>
        </hbox>`),
        true
      );
      Object.defineProperty(this, "menuFragment", { value: frag });
      return frag;
    }

    get _hasConnected() {
      return this.querySelector(":scope > .button-box") != null;
    }

    connectedCallback() {
      if (this.delayConnectedCallback() || this._hasConnected) {
        return;
      }

      let fragment;
      if (this.type === "menu") {
        fragment = MozButton.menuFragment;

        this.addEventListener("keypress", event => {
          if (event.keyCode != KeyEvent.DOM_VK_RETURN && event.key != " ") {
            return;
          }

          this.open = true;
          // Prevent page from scrolling on the space key.
          if (event.key == " ") {
            event.preventDefault();
          }
        });
      } else {
        fragment = this.constructor.buttonFragment;
      }

      this.appendChild(fragment.cloneNode(true));
      this.initializeAttributeInheritance();
      this.inRichListItem = !!this.closest("richlistitem");
    }
  }

  customElements.define("button", MozButton);
}
PK
!<_�,[		2chrome/toolkit/content/global/elements/checkbox.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
  class MozCheckbox extends MozElements.BaseText {
    static get markup() {
      return `
      <image class="checkbox-check"/>
      <hbox class="checkbox-label-box" flex="1">
        <image class="checkbox-icon"/>
        <label class="checkbox-label" flex="1"/>
      </hbox>
      `;
    }

    constructor() {
      super();

      // While it would seem we could do this by handling oncommand, we need can't
      // because any external oncommand handlers might get called before ours, and
      // then they would see the incorrect value of checked.
      this.addEventListener("click", event => {
        if (event.button === 0 && !this.disabled) {
          this.checked = !this.checked;
        }
      });
      this.addEventListener("keypress", event => {
        if (event.key == " ") {
          this.checked = !this.checked;
          // Prevent page from scrolling on the space key.
          event.preventDefault();
        }
      });
    }

    static get inheritedAttributes() {
      return {
        ".checkbox-check": "disabled,checked,native",
        ".checkbox-label": "text=label,accesskey,native",
        ".checkbox-icon": "src,native",
      };
    }

    connectedCallback() {
      if (this.delayConnectedCallback()) {
        return;
      }

      this.textContent = "";
      this.appendChild(this.constructor.fragment);

      this.initializeAttributeInheritance();
    }

    set checked(val) {
      let change = val != (this.getAttribute("checked") == "true");
      if (val) {
        this.setAttribute("checked", "true");
      } else {
        this.removeAttribute("checked");
      }

      if (change) {
        let event = document.createEvent("Events");
        event.initEvent("CheckboxStateChange", true, true);
        this.dispatchEvent(event);
      }
    }

    get checked() {
      return this.getAttribute("checked") == "true";
    }
  }

  MozCheckbox.contentFragment = null;

  customElements.define("checkbox", MozCheckbox);
}
PK
!<��>�k�k�5chrome/toolkit/content/global/elements/datetimebox.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is a UA widget. It runs in per-origin UA widget scope,
// to be loaded by UAWidgetsChild.sys.mjs.

/*
 * This is the class of entry. It will construct the actual implementation
 * according to the value of the "type" property.
 */
this.DateTimeBoxWidget = class {
  constructor(shadowRoot, prefs) {
    this.shadowRoot = shadowRoot;
    this.element = shadowRoot.host;
    this.document = this.element.ownerDocument;
    this.window = this.document.defaultView;
    // When undefined, DOMLocalization will use the app locales.
    let locales;
    if (prefs["privacy.resistFingerprinting"]) {
      locales = [...this.window.getWebExposedLocales()];
      // Make sure to always include en-US, in case the web exposed languages do
      // not include a translation for the widget.
      if (!locales.includes("en-US")) {
        locales.push("en-US");
      }
    }
    // The DOMLocalization instance needs to allow for sync methods so that
    // the placeholder value may be determined and set during the
    // createEditFieldAndAppend() call.
    this.l10n = new this.window.DOMLocalization(
      ["toolkit/global/datetimebox.ftl"],
      /* aSync = */ true,
      undefined,
      locales
    );
  }

  /*
   * Callback called by UAWidgets right after constructor.
   */
  onsetup() {
    this.onchange(/* aDestroy = */ false);
  }

  /*
   * Callback called by UAWidgets when the "type" property changes.
   */
  onchange(aDestroy = true) {
    let newType = this.element.type;
    if (this.type == newType) {
      return;
    }

    if (aDestroy) {
      this.teardown();
    }
    this.type = newType;
    this.setup();
  }

  shouldShowTime() {
    return this.type == "time" || this.type == "datetime-local";
  }

  shouldShowDate() {
    return this.type == "date" || this.type == "datetime-local";
  }

  teardown() {
    this.mInputElement.removeEventListener("keydown", this, {
      capture: true,
      mozSystemGroup: true,
    });
    this.mInputElement.removeEventListener("click", this, {
      mozSystemGroup: true,
    });

    this.CONTROL_EVENTS.forEach(eventName => {
      this.mDateTimeBoxElement.removeEventListener(eventName, this);
    });

    this.l10n.disconnectRoot(this.shadowRoot);

    this.removeEditFields();
    this.removeEventListenersToField(this.mCalendarButton);

    this.mInputElement = null;

    this.shadowRoot.firstChild.remove();
  }

  removeEditFields() {
    this.removeEventListenersToField(this.mYearField);
    this.removeEventListenersToField(this.mMonthField);
    this.removeEventListenersToField(this.mDayField);
    this.removeEventListenersToField(this.mHourField);
    this.removeEventListenersToField(this.mMinuteField);
    this.removeEventListenersToField(this.mSecondField);
    this.removeEventListenersToField(this.mMillisecField);
    this.removeEventListenersToField(this.mDayPeriodField);

    this.mYearField = null;
    this.mMonthField = null;
    this.mDayField = null;
    this.mHourField = null;
    this.mMinuteField = null;
    this.mSecondField = null;
    this.mMillisecField = null;
    this.mDayPeriodField = null;

    let root = this.shadowRoot.getElementById("edit-wrapper");
    while (root.firstChild) {
      root.firstChild.remove();
    }
  }

  rebuildEditFieldsIfNeeded() {
    if (
      this.shouldShowSecondField() == !!this.mSecondField &&
      this.shouldShowMillisecField() == !!this.mMillisecField
    ) {
      return;
    }

    let focused = this.mInputElement.matches(":focus");

    this.removeEditFields();
    this.buildEditFields();

    if (focused) {
      this._focusFirstField();
    }
  }

  _focusFirstField() {
    this.shadowRoot.querySelector(".datetime-edit-field")?.focus();
  }

  setup() {
    this.DEBUG = false;

    this.l10n.connectRoot(this.shadowRoot);

    this.generateContent();

    this.mDateTimeBoxElement = this.shadowRoot.firstChild;
    this.mCalendarButton = this.shadowRoot.getElementById("calendar-button");
    this.mInputElement = this.element;
    this.mLocales = this.window.getWebExposedLocales();

    this.mIsRTL = false;
    let intlUtils = this.window.intlUtils;
    if (intlUtils) {
      this.mIsRTL = intlUtils.isAppLocaleRTL();
    }

    if (this.mIsRTL) {
      let inputBoxWrapper = this.shadowRoot.getElementById("input-box-wrapper");
      inputBoxWrapper.dir = "rtl";
    }

    this.mIsPickerOpen = false;

    this.mMinMonth = 1;
    this.mMaxMonth = 12;
    this.mMinDay = 1;
    this.mMaxDay = 31;
    this.mMinYear = 1;
    // Maximum year limited by ISO 8601.
    this.mMaxYear = 9999;
    this.mMonthDayLength = 2;
    this.mYearLength = 4;
    this.mMonthPageUpDownInterval = 3;
    this.mDayPageUpDownInterval = 7;
    this.mYearPageUpDownInterval = 10;

    const kDefaultAMString = "AM";
    const kDefaultPMString = "PM";

    let { amString, pmString } = this.getStringsForLocale(this.mLocales);

    this.mAMIndicator = amString || kDefaultAMString;
    this.mPMIndicator = pmString || kDefaultPMString;

    this.mHour12 = this.is12HourTime(this.mLocales);
    this.mMillisecSeparatorText = ".";
    this.mMaxLength = 2;
    this.mMillisecMaxLength = 3;
    this.mDefaultStep = 60 * 1000; // in milliseconds

    this.mMinHour = this.mHour12 ? 1 : 0;
    this.mMaxHour = this.mHour12 ? 12 : 23;
    this.mMinMinute = 0;
    this.mMaxMinute = 59;
    this.mMinSecond = 0;
    this.mMaxSecond = 59;
    this.mMinMillisecond = 0;
    this.mMaxMillisecond = 999;

    this.mHourPageUpDownInterval = 3;
    this.mMinSecPageUpDownInterval = 10;

    this.mInputElement.addEventListener(
      "keydown",
      this,
      {
        capture: true,
        mozSystemGroup: true,
      },
      false
    );
    // This is to open the picker when input element is tapped on Android
    // or for type=time inputs (this includes padding area).
    this.isAndroid = this.window.navigator.appVersion.includes("Android");
    if (this.isAndroid || this.type == "time") {
      this.mInputElement.addEventListener(
        "click",
        this,
        { mozSystemGroup: true },
        false
      );
    }

    // Those events are dispatched to <div class="datetimebox"> with bubble set
    // to false. They are trapped inside UA Widget Shadow DOM and are not
    // dispatched to the document.
    this.CONTROL_EVENTS.forEach(eventName => {
      this.mDateTimeBoxElement.addEventListener(eventName, this, {}, false);
    });

    this.buildEditFields();
    this.buildCalendarBtn();
    this.updateEditAttributes();

    if (this.mInputElement.value) {
      this.setFieldsFromInputValue();
    }

    if (this.mInputElement.matches(":focus")) {
      this._focusFirstField();
    }
  }

  generateContent() {
    const parser = new this.window.DOMParser();
    let parserDoc = parser.parseFromString(
      `<div class="datetimebox" xmlns="http://www.w3.org/1999/xhtml" role="none">
        <link rel="stylesheet" type="text/css" href="chrome://global/content/bindings/datetimebox.css" />
        <div class="datetime-input-box-wrapper" id="input-box-wrapper" role="presentation">
          <span class="datetime-input-edit-wrapper"
                     id="edit-wrapper">
            <!-- Each of the date/time input types will append their input child
               - elements here -->
          </span>
          <button data-l10n-id="datetime-calendar" class="datetime-calendar-button" id="calendar-button" aria-expanded="false">
            <svg role="none" class="datetime-calendar-button-svg" xmlns="http://www.w3.org/2000/svg" id="calendar-16" viewBox="0 0 16 16" width="16" height="16">
              <path d="M13.5 2H13V1c0-.6-.4-1-1-1s-1 .4-1 1v1H5V1c0-.6-.4-1-1-1S3 .4 3 1v1h-.5C1.1 2 0 3.1 0 4.5v9C0 14.9 1.1 16 2.5 16h11c1.4 0 2.5-1.1 2.5-2.5v-9C16 3.1 14.9 2 13.5 2zm0 12.5h-11c-.6 0-1-.4-1-1V6h13v7.5c0 .6-.4 1-1 1z"/>
            </svg>
          </button>
        </div>
      </div>`,
      "application/xml"
    );

    this.shadowRoot.importNodeAndAppendChildAt(
      this.shadowRoot,
      parserDoc.documentElement,
      true
    );
    this.l10n.translateRoots();
  }

  get FIELD_EVENTS() {
    return ["focus", "blur", "copy", "cut", "paste"];
  }

  get CONTROL_EVENTS() {
    return [
      "MozDateTimeValueChanged",
      "MozNotifyMinMaxStepAttrChanged",
      "MozDateTimeAttributeChanged",
      "MozPickerValueChanged",
      "MozSetDateTimePickerState",
      "MozDateTimeShowPickerForJS",
    ];
  }

  get showPickerOnClick() {
    return this.isAndroid || this.type == "time";
  }

  addEventListenersToField(aElement) {
    // These events don't bubble out of the Shadow DOM, so we'll have to add
    // event listeners specifically on each of the fields, not just
    // on the <input>
    this.FIELD_EVENTS.forEach(eventName => {
      aElement.addEventListener(
        eventName,
        this,
        { mozSystemGroup: true },
        false
      );
    });
  }

  removeEventListenersToField(aElement) {
    if (!aElement) {
      return;
    }

    this.FIELD_EVENTS.forEach(eventName => {
      aElement.removeEventListener(eventName, this, { mozSystemGroup: true });
    });
  }

  log(aMsg) {
    if (this.DEBUG) {
      this.window.dump("[DateTimeBox] " + aMsg + "\n");
    }
  }

  createEditFieldAndAppend(
    aL10nId,
    aPlaceholderId,
    aIsNumeric,
    aMinDigits,
    aMaxLength,
    aMinValue,
    aMaxValue,
    aPageUpDownInterval
  ) {
    let root = this.shadowRoot.getElementById("edit-wrapper");
    let field = this.shadowRoot.createElementAndAppendChildAt(root, "span");
    field.classList.add("datetime-edit-field");
    field.setAttribute("aria-valuetext", "");
    this.setFieldTabIndexAttribute(field);

    const placeholder = this.l10n.formatValueSync(aPlaceholderId);
    field.placeholder = placeholder;
    field.textContent = placeholder;
    this.l10n.setAttributes(field, aL10nId);

    // Used to store the non-formatted value, cleared when value is
    // cleared.
    // DateTimeInputTypeBase::HasBadInput() will read this to decide
    // if the input has value.
    field.setAttribute("value", "");

    if (aIsNumeric) {
      field.classList.add("numeric");
      // Maximum value allowed.
      field.setAttribute("min", aMinValue);
      // Minumim value allowed.
      field.setAttribute("max", aMaxValue);
      // Interval when pressing pageUp/pageDown key.
      field.setAttribute("pginterval", aPageUpDownInterval);
      // Used to store what the user has already typed in the field,
      // cleared when value is cleared and when field is blurred.
      field.setAttribute("typeBuffer", "");
      // Minimum digits to display, padded with leading 0s.
      field.setAttribute("mindigits", aMinDigits);
      // Maximum length for the field, will be advance to the next field
      // automatically if exceeded.
      field.setAttribute("maxlength", aMaxLength);
      // Set spinbutton ARIA role
      field.setAttribute("role", "spinbutton");

      if (this.mIsRTL) {
        // Force the direction to be "ltr", so that the field stays in the
        // same order even when it's empty (with placeholder). By using
        // "embed", the text inside the element is still displayed based
        // on its directionality.
        field.style.unicodeBidi = "embed";
        field.style.direction = "ltr";
      }
    } else {
      // Set generic textbox ARIA role
      field.setAttribute("role", "textbox");
    }

    return field;
  }

  updateCalendarButtonState(isExpanded) {
    this.mCalendarButton.setAttribute("aria-expanded", isExpanded);
  }

  notifyInputElementValueChanged() {
    this.log("inputElementValueChanged");
    this.setFieldsFromInputValue();
  }

  notifyMinMaxStepAttrChanged() {
    // Second and millisecond part are optional, rebuild edit fields if
    // needed.
    this.rebuildEditFieldsIfNeeded();
    // Fill in values again.
    this.setFieldsFromInputValue();
  }

  setValueFromPicker(aValue) {
    if (aValue) {
      this.setFieldsFromPicker(aValue);
    } else {
      this.clearInputFields();
    }
  }

  advanceToNextField(aReverse) {
    this.log("advanceToNextField");

    let focusedInput = this.mLastFocusedElement;
    let next = aReverse
      ? focusedInput.previousElementSibling
      : focusedInput.nextElementSibling;
    if (!next && !aReverse) {
      this.setInputValueFromFields();
      return;
    }

    while (next) {
      if (next.matches("span.datetime-edit-field")) {
        next.focus();
        break;
      }
      next = aReverse ? next.previousElementSibling : next.nextElementSibling;
    }
  }

  setPickerState(aIsOpen) {
    this.log("picker is now " + (aIsOpen ? "opened" : "closed"));
    this.mIsPickerOpen = aIsOpen;
    // Calendar button's expanded state mirrors this.mIsPickerOpen
    this.updateCalendarButtonState(this.mIsPickerOpen);
  }

  setFieldTabIndexAttribute(field) {
    field.tabIndex = this.mInputElement.tabIndex;
  }

  updateEditAttributes() {
    this.log("updateEditAttributes");

    let editRoot = this.shadowRoot.getElementById("edit-wrapper");

    for (let child of editRoot.querySelectorAll(
      ":scope > span.datetime-edit-field"
    )) {
      this.setFieldTabIndexAttribute(child);
    }
  }

  isEmpty(aValue) {
    return aValue == undefined || 0 === aValue.length;
  }

  getFieldValue(aField) {
    if (!aField || !aField.classList.contains("numeric")) {
      return undefined;
    }

    let value = aField.getAttribute("value");
    // Avoid returning 0 when field is empty.
    return this.isEmpty(value) ? undefined : Number(value);
  }

  clearFieldValue(aField) {
    aField.textContent = aField.placeholder;
    aField.setAttribute("value", "");
    aField.setAttribute("aria-valuetext", "");
    if (aField.classList.contains("numeric")) {
      aField.setAttribute("typeBuffer", "");
    }
  }

  openDateTimePicker() {
    this.mInputElement.openDateTimePicker(this.getCurrentValue());
  }

  closeDateTimePicker() {
    if (this.mIsPickerOpen) {
      this.mInputElement.closeDateTimePicker();
    }
  }

  notifyPicker() {
    if (this.mIsPickerOpen && this.isAnyFieldAvailable(true)) {
      this.mInputElement.updateDateTimePicker(this.getCurrentValue());
    }
  }

  isDisabled() {
    return this.mInputElement.matches(":disabled");
  }

  isReadonly() {
    return this.mInputElement.matches(":read-only");
  }

  isEditable() {
    return !this.isDisabled() && !this.isReadonly();
  }

  isRequired() {
    return this.mInputElement.hasAttribute("required");
  }

  containingTree() {
    return this.mInputElement.containingShadowRoot || this.document;
  }

  handleEvent(aEvent) {
    this.log("handleEvent: " + aEvent.type);

    if (!aEvent.isTrusted) {
      return;
    }

    switch (aEvent.type) {
      case "MozDateTimeValueChanged": {
        this.notifyInputElementValueChanged();
        break;
      }
      case "MozNotifyMinMaxStepAttrChanged": {
        this.notifyMinMaxStepAttrChanged();
        break;
      }
      case "MozDateTimeAttributeChanged": {
        this.updateEditAttributes();
        break;
      }
      case "MozPickerValueChanged": {
        this.setValueFromPicker(aEvent.detail);
        break;
      }
      case "MozSetDateTimePickerState": {
        this.setPickerState(aEvent.detail);
        break;
      }
      case "MozDateTimeShowPickerForJS": {
        this.openDateTimePicker();
        break;
      }
      case "keydown": {
        this.onKeyDown(aEvent);
        break;
      }
      case "click": {
        this.onClick(aEvent);
        break;
      }
      case "focus": {
        this.onFocus(aEvent);
        break;
      }
      case "blur": {
        this.onBlur(aEvent);
        break;
      }
      case "mousedown":
      case "copy":
      case "cut":
      case "paste": {
        aEvent.preventDefault();
        break;
      }
      default:
        break;
    }
  }

  onFocus(aEvent) {
    this.log("onFocus originalTarget: " + aEvent.originalTarget);
    if (this.containingTree().activeElement != this.mInputElement) {
      return;
    }

    let target = aEvent.originalTarget;
    if (target.matches(".datetime-edit-field,.datetime-calendar-button")) {
      if (target.disabled) {
        return;
      }
      this.mLastFocusedElement = target;
      this.mInputElement.setFocusState(true);
    }
    if (this.mIsPickerOpen && this.isPickerIrrelevantField(target)) {
      this.closeDateTimePicker();
    }
  }

  onBlur(aEvent) {
    this.log(
      "onBlur originalTarget: " +
        aEvent.originalTarget +
        " target: " +
        aEvent.target +
        " rt: " +
        aEvent.relatedTarget +
        " open: " +
        this.mIsPickerOpen
    );

    let target = aEvent.originalTarget;
    target.setAttribute("typeBuffer", "");
    this.setInputValueFromFields();
    // No need to set and unset the focus state (or closing the picker) if the
    // focus is staying within our input.
    if (aEvent.relatedTarget == this.mInputElement) {
      return;
    }

    // If we're in chrome and the focus moves to a separate document
    // (relatedTarget is null) we also don't want to close it, since it
    // could've moved to the datetime popup itself.
    if (
      !aEvent.relatedTarget &&
      this.window.isChromeWindow &&
      this.window == this.window.top
    ) {
      return;
    }

    this.mInputElement.setFocusState(false);
    if (this.mIsPickerOpen) {
      this.closeDateTimePicker();
    }
  }

  isTimeField(field) {
    return (
      field == this.mHourField ||
      field == this.mMinuteField ||
      field == this.mSecondField ||
      field == this.mDayPeriodField
    );
  }

  shouldOpenDateTimePickerOnKeyDown() {
    if (!this.mLastFocusedElement) {
      return true;
    }
    return !this.isPickerIrrelevantField(this.mLastFocusedElement);
  }

  shouldOpenDateTimePickerOnClick(target) {
    return !this.isPickerIrrelevantField(target);
  }

  // Whether a given field is irrelevant for the purposes of the datetime
  // picker. This is useful for datetime-local, which as of right now only
  // shows a date picker (not a time picker).
  isPickerIrrelevantField(field) {
    if (this.type != "datetime-local") {
      return false;
    }
    return this.isTimeField(field);
  }

  onKeyDown(aEvent) {
    this.log("onKeyDown key: " + aEvent.key);

    if (aEvent.defaultPrevented) {
      return;
    }

    switch (aEvent.key) {
      // Toggle the picker on Space/Enter on Calendar button or Space on input,
      // close on Escape anywhere.
      case "Escape": {
        if (this.mIsPickerOpen) {
          this.closeDateTimePicker();
          aEvent.preventDefault();
        }
        break;
      }
      case "Enter":
      case " ": {
        // always close, if opened
        if (this.mIsPickerOpen) {
          this.closeDateTimePicker();
        } else if (
          // open on Space from anywhere within the input
          aEvent.key == " " &&
          this.shouldOpenDateTimePickerOnKeyDown()
        ) {
          this.openDateTimePicker();
        } else if (
          // open from the Calendar button on either keydown
          aEvent.originalTarget == this.mCalendarButton &&
          this.shouldOpenDateTimePickerOnKeyDown()
        ) {
          this.openDateTimePicker();
        } else {
          // Don't preventDefault();
          break;
        }
        aEvent.preventDefault();
        break;
      }
      case "Delete":
      case "Backspace": {
        if (aEvent.originalTarget == this.mCalendarButton) {
          // Do not remove Calendar button
          aEvent.preventDefault();
          break;
        }
        // Ctrl+Backspace/Delete on non-macOS and
        // Cmd+Backspace/Delete on macOS to clear the field
        if (aEvent.getModifierState("Accel")) {
          // Clear the input's value
          this.clearInputFields(false);
        } else {
          let targetField = aEvent.originalTarget;
          this.clearFieldValue(targetField);
          this.setInputValueFromFields();
        }
        aEvent.preventDefault();
        break;
      }
      case "ArrowRight":
      case "ArrowLeft": {
        this.advanceToNextField(!(aEvent.key == "ArrowRight"));
        aEvent.preventDefault();
        break;
      }
      case "ArrowUp":
      case "ArrowDown":
      case "PageUp":
      case "PageDown":
      case "Home":
      case "End": {
        this.handleKeyboardNav(aEvent);
        aEvent.preventDefault();
        break;
      }
      default: {
        // Handle printable characters (e.g. letters, digits and numpad digits)
        if (
          aEvent.key.length === 1 &&
          !(aEvent.ctrlKey || aEvent.altKey || aEvent.metaKey)
        ) {
          this.handleKeydown(aEvent);
          aEvent.preventDefault();
        }
        break;
      }
    }
  }

  onClick(aEvent) {
    this.log(
      "onClick originalTarget: " +
        aEvent.originalTarget +
        " target: " +
        aEvent.target
    );

    if (aEvent.defaultPrevented || !this.isEditable()) {
      return;
    }

    // We toggle the picker on click on the Calendar button on any platform.
    // For Android and for type=time inputs, we also toggle the picker when
    // clicking on the input field.
    //
    // We do not toggle the picker when clicking the input field for Calendar
    // on desktop to avoid interfering with the default Calendar behavior.
    if (
      aEvent.originalTarget == this.mCalendarButton ||
      this.showPickerOnClick
    ) {
      if (
        !this.mIsPickerOpen &&
        this.shouldOpenDateTimePickerOnClick(aEvent.originalTarget)
      ) {
        this.openDateTimePicker();
      } else {
        this.closeDateTimePicker();
      }
    }
  }

  buildEditFields() {
    let root = this.shadowRoot.getElementById("edit-wrapper");

    let options = {};

    if (this.shouldShowTime()) {
      options.hour = options.minute = "numeric";
      options.hour12 = this.mHour12;
      if (this.shouldShowSecondField()) {
        options.second = "numeric";
      }
    }

    if (this.shouldShowDate()) {
      options.year = options.month = options.day = "numeric";
    }

    let formatter = Intl.DateTimeFormat(this.mLocales, options);
    formatter.formatToParts(Date.now()).map(part => {
      switch (part.type) {
        case "year":
          this.mYearField = this.createEditFieldAndAppend(
            "datetime-year",
            "datetime-year-placeholder",
            true,
            this.mYearLength,
            this.mMaxYear.toString().length,
            this.mMinYear,
            this.mMaxYear,
            this.mYearPageUpDownInterval
          );
          this.addEventListenersToField(this.mYearField);
          break;
        case "month":
          this.mMonthField = this.createEditFieldAndAppend(
            "datetime-month",
            "datetime-month-placeholder",
            true,
            this.mMonthDayLength,
            this.mMonthDayLength,
            this.mMinMonth,
            this.mMaxMonth,
            this.mMonthPageUpDownInterval
          );
          this.addEventListenersToField(this.mMonthField);
          break;
        case "day":
          this.mDayField = this.createEditFieldAndAppend(
            "datetime-day",
            "datetime-day-placeholder",
            true,
            this.mMonthDayLength,
            this.mMonthDayLength,
            this.mMinDay,
            this.mMaxDay,
            this.mDayPageUpDownInterval
          );
          this.addEventListenersToField(this.mDayField);
          break;
        case "hour":
          this.mHourField = this.createEditFieldAndAppend(
            "datetime-hour",
            "datetime-time-placeholder",
            true,
            this.mMaxLength,
            this.mMaxLength,
            this.mMinHour,
            this.mMaxHour,
            this.mHourPageUpDownInterval
          );
          this.addEventListenersToField(this.mHourField);
          break;
        case "minute":
          this.mMinuteField = this.createEditFieldAndAppend(
            "datetime-minute",
            "datetime-time-placeholder",
            true,
            this.mMaxLength,
            this.mMaxLength,
            this.mMinMinute,
            this.mMaxMinute,
            this.mMinSecPageUpDownInterval
          );
          this.addEventListenersToField(this.mMinuteField);
          break;
        case "second":
          this.mSecondField = this.createEditFieldAndAppend(
            "datetime-second",
            "datetime-time-placeholder",
            true,
            this.mMaxLength,
            this.mMaxLength,
            this.mMinSecond,
            this.mMaxSecond,
            this.mMinSecPageUpDownInterval
          );
          this.addEventListenersToField(this.mSecondField);
          if (this.shouldShowMillisecField()) {
            // Intl.DateTimeFormat does not support millisecond, so we
            // need to handle this on our own.
            let span = this.shadowRoot.createElementAndAppendChildAt(
              root,
              "span"
            );
            span.textContent = this.mMillisecSeparatorText;
            this.mMillisecField = this.createEditFieldAndAppend(
              "datetime-millisecond",
              "datetime-time-placeholder",
              true,
              this.mMillisecMaxLength,
              this.mMillisecMaxLength,
              this.mMinMillisecond,
              this.mMaxMillisecond,
              this.mMinSecPageUpDownInterval
            );
            this.addEventListenersToField(this.mMillisecField);
          }
          break;
        case "dayPeriod":
          this.mDayPeriodField = this.createEditFieldAndAppend(
            "datetime-dayperiod",
            "datetime-time-placeholder",
            false
          );
          this.addEventListenersToField(this.mDayPeriodField);

          // Give aria autocomplete hint for am/pm
          this.mDayPeriodField.setAttribute("aria-autocomplete", "inline");
          break;
        default:
          let span = this.shadowRoot.createElementAndAppendChildAt(
            root,
            "span"
          );
          span.textContent = part.value;
          break;
      }
    });
  }

  buildCalendarBtn() {
    this.addEventListenersToField(this.mCalendarButton);
    // This is to open the picker when a Calendar button is clicked (this
    // includes padding area).
    this.mCalendarButton.addEventListener(
      "click",
      this,
      { mozSystemGroup: true },
      false
    );
  }

  clearInputFields(aFromInputElement) {
    this.log("clearInputFields");

    if (this.mMonthField) {
      this.clearFieldValue(this.mMonthField);
    }

    if (this.mDayField) {
      this.clearFieldValue(this.mDayField);
    }

    if (this.mYearField) {
      this.clearFieldValue(this.mYearField);
    }

    if (this.mHourField) {
      this.clearFieldValue(this.mHourField);
    }

    if (this.mMinuteField) {
      this.clearFieldValue(this.mMinuteField);
    }

    if (this.mSecondField) {
      this.clearFieldValue(this.mSecondField);
    }

    if (this.mMillisecField) {
      this.clearFieldValue(this.mMillisecField);
    }

    if (this.mDayPeriodField) {
      this.clearFieldValue(this.mDayPeriodField);
    }

    if (!aFromInputElement) {
      if (this.mInputElement.value) {
        this.mInputElement.setUserInput("");
      } else {
        this.mInputElement.updateValidityState();
      }
    }
  }

  setFieldsFromInputValue() {
    // Second and millisecond part are optional, rebuild edit fields if
    // needed.
    this.rebuildEditFieldsIfNeeded();

    let value = this.mInputElement.value;
    if (!value) {
      this.clearInputFields(true);
      return;
    }

    let { year, month, day, hour, minute, second, millisecond } =
      this.getInputElementValues();
    if (this.shouldShowDate()) {
      this.log("setFieldsFromInputValue: " + value);
      this.setFieldValue(this.mYearField, year);
      this.setFieldValue(this.mMonthField, month);
      this.setFieldValue(this.mDayField, day);
    }

    if (this.shouldShowTime()) {
      if (this.isEmpty(hour) && this.isEmpty(minute)) {
        this.clearInputFields(true);
        return;
      }

      this.setFieldValue(this.mHourField, hour);
      this.setFieldValue(this.mMinuteField, minute);
      if (this.mHour12) {
        this.setDayPeriodValue(
          hour >= this.mMaxHour ? this.mPMIndicator : this.mAMIndicator
        );
      }

      if (this.mSecondField) {
        this.setFieldValue(this.mSecondField, second || 0);
      }

      if (this.mMillisecField) {
        this.setFieldValue(this.mMillisecField, millisecond || 0);
      }
    }

    this.notifyPicker();
  }

  setInputValueFromFields() {
    if (this.isAnyFieldEmpty()) {
      // Clear input element's value if any of the field has been cleared,
      // otherwise update the validity state, since it may become "not"
      // invalid if fields are not complete.
      if (this.mInputElement.value) {
        this.mInputElement.setUserInput("");
      } else {
        this.mInputElement.updateValidityState();
      }
      // We still need to notify picker in case any of the field has
      // changed.
      this.notifyPicker();
      return;
    }

    let { year, month, day, hour, minute, second, millisecond, dayPeriod } =
      this.getCurrentValue();

    let time = "";
    let date = "";

    // Convert to a valid time string according to:
    // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-time-string
    if (this.shouldShowTime()) {
      if (this.mHour12) {
        if (dayPeriod == this.mPMIndicator && hour < this.mMaxHour) {
          hour += this.mMaxHour;
        } else if (dayPeriod == this.mAMIndicator && hour == this.mMaxHour) {
          hour = 0;
        }
      }

      hour = hour < 10 ? "0" + hour : hour;
      minute = minute < 10 ? "0" + minute : minute;

      time = hour + ":" + minute;
      if (second != undefined) {
        second = second < 10 ? "0" + second : second;
        time += ":" + second;
      }

      if (millisecond != undefined) {
        // Convert milliseconds to fraction of second.
        millisecond = millisecond
          .toString()
          .padStart(this.mMillisecMaxLength, "0");
        time += "." + millisecond;
      }
    }

    if (this.shouldShowDate()) {
      // Convert to a valid date string according to:
      // https://html.spec.whatwg.org/multipage/infrastructure.html#valid-date-string
      year = year.toString().padStart(this.mYearLength, "0");
      month = month < 10 ? "0" + month : month;
      day = day < 10 ? "0" + day : day;
      date = [year, month, day].join("-");
    }

    let value;
    if (date) {
      value = date;
    }
    if (time) {
      // https://html.spec.whatwg.org/#valid-normalised-local-date-and-time-string
      value = value ? value + "T" + time : time;
    }

    if (value == this.mInputElement.value) {
      return;
    }
    this.log("setInputValueFromFields: " + value);
    this.notifyPicker();
    this.mInputElement.setUserInput(value);
  }

  setFieldsFromPicker({ year, month, day, hour, minute }) {
    if (!this.isEmpty(hour)) {
      this.setFieldValue(this.mHourField, hour);
      if (this.mHour12) {
        this.setDayPeriodValue(
          hour >= this.mMaxHour ? this.mPMIndicator : this.mAMIndicator
        );
      }
    }

    if (!this.isEmpty(minute)) {
      this.setFieldValue(this.mMinuteField, minute);
    }

    if (!this.isEmpty(year)) {
      this.setFieldValue(this.mYearField, year);
    }

    if (!this.isEmpty(month)) {
      this.setFieldValue(this.mMonthField, month);
    }

    if (!this.isEmpty(day)) {
      this.setFieldValue(this.mDayField, day);
    }

    // Update input element's .value if needed.
    this.setInputValueFromFields();
  }

  handleKeydown(aEvent) {
    if (!this.isEditable()) {
      return;
    }

    let targetField = aEvent.originalTarget;
    let key = aEvent.key;

    if (targetField == this.mDayPeriodField) {
      if (key == "a" || key == "A") {
        this.setDayPeriodValue(this.mAMIndicator);
      } else if (key == "p" || key == "P") {
        this.setDayPeriodValue(this.mPMIndicator);
      }
      if (!this.isAnyFieldEmpty()) {
        this.setInputValueFromFields();
      }
      return;
    }

    if (targetField.classList.contains("numeric") && key.match(/[0-9]/)) {
      let buffer = targetField.getAttribute("typeBuffer") || "";

      buffer = buffer.concat(key);
      this.setFieldValue(targetField, buffer);

      let n = Number(buffer);
      let max = targetField.getAttribute("max");
      let maxLength = targetField.getAttribute("maxlength");
      if (targetField == this.mHourField) {
        if (n * 10 > 23 || buffer.length === 2) {
          buffer = "";
          this.advanceToNextField();
        }
      } else if (buffer.length >= maxLength || n * 10 > max) {
        buffer = "";
        this.advanceToNextField();
      }
      targetField.setAttribute("typeBuffer", buffer);
      if (!this.isAnyFieldEmpty()) {
        this.setInputValueFromFields();
      }
    }
  }

  getCurrentValue() {
    let value = {};
    if (this.shouldShowDate()) {
      value.year = this.getFieldValue(this.mYearField);
      value.month = this.getFieldValue(this.mMonthField);
      value.day = this.getFieldValue(this.mDayField);
    }

    if (this.shouldShowTime()) {
      let dayPeriod = this.getDayPeriodValue();
      let hour = this.getFieldValue(this.mHourField);
      if (!this.isEmpty(hour)) {
        if (this.mHour12) {
          if (dayPeriod == this.mPMIndicator && hour < this.mMaxHour) {
            hour += this.mMaxHour;
          } else if (dayPeriod == this.mAMIndicator && hour == this.mMaxHour) {
            hour = 0;
          }
        }
      }
      value.hour = hour;
      value.dayPeriod = dayPeriod;
      value.minute = this.getFieldValue(this.mMinuteField);
      value.second = this.getFieldValue(this.mSecondField);
      value.millisecond = this.getFieldValue(this.mMillisecField);
    }

    this.log("getCurrentValue: " + JSON.stringify(value));
    return value;
  }

  setFieldValue(aField, aValue) {
    if (!aField || !aField.classList.contains("numeric")) {
      return;
    }

    let value = Number(aValue);
    if (isNaN(value)) {
      this.log("NaN on setFieldValue!");
      return;
    }

    if (aField == this.mHourField) {
      if (this.mHour12) {
        // Try to change to 12hr format if user input is 0 or greater
        // than 12.
        switch (true) {
          case value == 0 && aValue.length == 2:
            value = this.mMaxHour;
            this.setDayPeriodValue(this.mAMIndicator);
            break;

          case value == this.mMaxHour:
            this.setDayPeriodValue(this.mPMIndicator);
            break;

          case value < 12:
            if (!this.getDayPeriodValue()) {
              this.setDayPeriodValue(this.mAMIndicator);
            }
            break;

          case value > 12 && value < 24:
            value = value % this.mMaxHour;
            this.setDayPeriodValue(this.mPMIndicator);
            break;

          default:
            value = Math.floor(value / 10);
            break;
        }
      } else if (value > this.mMaxHour) {
        value = this.mMaxHour;
      }
    }

    let maxLength = aField.getAttribute("maxlength");
    if (aValue.length == maxLength) {
      let min = Number(aField.getAttribute("min"));
      let max = Number(aField.getAttribute("max"));

      if (value < min) {
        value = min;
      } else if (value > max) {
        value = max;
      }
    }

    aField.setAttribute("value", value);

    let minDigits = aField.getAttribute("mindigits");
    let formatted = value.toLocaleString(this.mLocales, {
      minimumIntegerDigits: minDigits,
      useGrouping: false,
    });

    aField.textContent = formatted;
    aField.setAttribute("aria-valuetext", formatted);
  }

  isAnyFieldAvailable(aForPicker = false) {
    let { year, month, day, hour, minute, second, millisecond } =
      this.getCurrentValue();
    if (
      !this.isEmpty(year) ||
      !this.isEmpty(month) ||
      !this.isEmpty(day) ||
      !this.isEmpty(hour) ||
      !this.isEmpty(minute)
    ) {
      return true;
    }

    // Picker doesn't care about seconds / milliseconds / day period.
    if (aForPicker) {
      return false;
    }

    let dayPeriod = this.getDayPeriodValue();
    return (
      (this.mDayPeriodField && !this.isEmpty(dayPeriod)) ||
      (this.mSecondField && !this.isEmpty(second)) ||
      (this.mMillisecField && !this.isEmpty(millisecond))
    );
  }

  isAnyFieldEmpty() {
    let { year, month, day, hour, minute, second, millisecond } =
      this.getCurrentValue();
    return (
      (this.mYearField && this.isEmpty(year)) ||
      (this.mMonthField && this.isEmpty(month)) ||
      (this.mDayField && this.isEmpty(day)) ||
      (this.mHourField && this.isEmpty(hour)) ||
      (this.mMinuteField && this.isEmpty(minute)) ||
      (this.mDayPeriodField && this.isEmpty(this.getDayPeriodValue())) ||
      (this.mSecondField && this.isEmpty(second)) ||
      (this.mMillisecField && this.isEmpty(millisecond))
    );
  }

  get kMsPerSecond() {
    return 1000;
  }

  get kMsPerMinute() {
    return 60 * 1000;
  }

  getInputElementValues() {
    let value = this.mInputElement.value;
    if (value.length === 0) {
      return {};
    }

    let date, time;

    let year, month, day, hour, minute, second, millisecond;
    if (this.type == "date") {
      date = value;
    }
    if (this.type == "time") {
      time = value;
    }
    if (this.type == "datetime-local") {
      // https://html.spec.whatwg.org/#valid-normalised-local-date-and-time-string
      [date, time] = value.split("T");
    }
    if (date) {
      [year, month, day] = date.split("-");
    }
    if (time) {
      [hour, minute, second] = time.split(":");
      if (second) {
        [second, millisecond] = second.split(".");

        // Convert fraction of second to milliseconds.
        if (millisecond && millisecond.length === 1) {
          millisecond *= 100;
        } else if (millisecond && millisecond.length === 2) {
          millisecond *= 10;
        }
      }
    }
    return { year, month, day, hour, minute, second, millisecond };
  }

  shouldShowSecondField() {
    if (!this.shouldShowTime()) {
      return false;
    }
    let { second } = this.getInputElementValues();
    if (second != undefined) {
      return true;
    }

    let stepBase = this.mInputElement.getStepBase();
    if (stepBase % this.kMsPerMinute != 0) {
      return true;
    }

    let step = this.mInputElement.getStep();
    if (step % this.kMsPerMinute != 0) {
      return true;
    }

    return false;
  }

  shouldShowMillisecField() {
    if (!this.shouldShowTime()) {
      return false;
    }

    let { millisecond } = this.getInputElementValues();
    if (millisecond != undefined) {
      return true;
    }

    let stepBase = this.mInputElement.getStepBase();
    if (stepBase % this.kMsPerSecond != 0) {
      return true;
    }

    let step = this.mInputElement.getStep();
    if (step % this.kMsPerSecond != 0) {
      return true;
    }

    return false;
  }

  getStringsForLocale(aLocales) {
    this.log("getStringsForLocale: " + aLocales);

    let intlUtils = this.window.intlUtils;
    if (!intlUtils) {
      return {};
    }

    let result = intlUtils.getDisplayNames(this.mLocales, {
      type: "dayPeriod",
      style: "short",
      calendar: "gregory",
      keys: ["am", "pm"],
    });

    let [amString, pmString] = result.values;

    return { amString, pmString };
  }

  is12HourTime(aLocales) {
    let options = new Intl.DateTimeFormat(aLocales, {
      hour: "numeric",
    }).resolvedOptions();

    return options.hour12;
  }

  incrementFieldValue(aTargetField, aTimes) {
    let value = this.getFieldValue(aTargetField);

    // Use current time if field is empty.
    if (this.isEmpty(value)) {
      let now = new Date();

      if (aTargetField == this.mYearField) {
        value = now.getFullYear();
      } else if (aTargetField == this.mMonthField) {
        value = now.getMonth() + 1;
      } else if (aTargetField == this.mDayField) {
        value = now.getDate();
      } else if (aTargetField == this.mHourField) {
        value = now.getHours();
        if (this.mHour12) {
          value = value % this.mMaxHour || this.mMaxHour;
        }
      } else if (aTargetField == this.mMinuteField) {
        value = now.getMinutes();
      } else if (aTargetField == this.mSecondField) {
        value = now.getSeconds();
      } else if (aTargetField == this.mMillisecField) {
        value = now.getMilliseconds();
      } else {
        this.log("Field not supported in incrementFieldValue.");
        return;
      }
    }

    let min = +aTargetField.getAttribute("min");
    let max = +aTargetField.getAttribute("max");

    value += Number(aTimes);
    if (value > max) {
      value -= max - min + 1;
    } else if (value < min) {
      value += max - min + 1;
    }

    this.setFieldValue(aTargetField, value);
  }

  handleKeyboardNav(aEvent) {
    if (!this.isEditable()) {
      return;
    }

    let targetField = aEvent.originalTarget;
    let key = aEvent.key;

    if (targetField == this.mYearField && (key == "Home" || key == "End")) {
      // Home/End key does nothing on year field.
      return;
    }

    if (targetField == this.mDayPeriodField) {
      // Home/End key does nothing on AM/PM field.
      if (key == "Home" || key == "End") {
        return;
      }

      this.setDayPeriodValue(
        this.getDayPeriodValue() == this.mAMIndicator
          ? this.mPMIndicator
          : this.mAMIndicator
      );
      this.setInputValueFromFields();
      return;
    }

    switch (key) {
      case "ArrowUp":
        this.incrementFieldValue(targetField, 1);
        break;
      case "ArrowDown":
        this.incrementFieldValue(targetField, -1);
        break;
      case "PageUp": {
        let interval = targetField.getAttribute("pginterval");
        this.incrementFieldValue(targetField, interval);
        break;
      }
      case "PageDown": {
        let interval = targetField.getAttribute("pginterval");
        this.incrementFieldValue(targetField, 0 - interval);
        break;
      }
      case "Home":
        let min = targetField.getAttribute("min");
        this.setFieldValue(targetField, min);
        break;
      case "End":
        let max = targetField.getAttribute("max");
        this.setFieldValue(targetField, max);
        break;
    }
    this.setInputValueFromFields();
  }

  getDayPeriodValue() {
    if (!this.mDayPeriodField) {
      return "";
    }

    let placeholder = this.mDayPeriodField.placeholder;
    let value = this.mDayPeriodField.textContent;
    return value == placeholder ? "" : value;
  }

  setDayPeriodValue(aValue) {
    if (!this.mDayPeriodField) {
      return;
    }

    this.mDayPeriodField.textContent = aValue;
    this.mDayPeriodField.setAttribute("value", aValue);
  }
};
PK
!<E�f4C4C0chrome/toolkit/content/global/elements/dialog.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
  const { AppConstants } = ChromeUtils.importESModule(
    "resource://gre/modules/AppConstants.sys.mjs"
  );

  class MozDialog extends MozXULElement {
    constructor() {
      super();
    }

    static get observedAttributes() {
      return super.observedAttributes.concat("subdialog");
    }

    attributeChangedCallback(name, oldValue, newValue) {
      if (name == "subdialog") {
        console.assert(
          newValue,
          `Turning off subdialog style is not supported`
        );
        if (this.isConnectedAndReady && !oldValue && newValue) {
          this.shadowRoot.appendChild(
            MozXULElement.parseXULToFragment(this.inContentStyle)
          );
        }
        return;
      }
      super.attributeChangedCallback(name, oldValue, newValue);
    }

    static get inheritedAttributes() {
      return {
        ".dialog-button-box":
          "pack=buttonpack,align=buttonalign,dir=buttondir,orient=buttonorient",
        "[dlgtype='accept']": "disabled=buttondisabledaccept",
      };
    }

    get inContentStyle() {
      return `
      <html:link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
    `;
    }

    get _markup() {
      let buttons = AppConstants.XP_UNIX
        ? `
      <hbox class="dialog-button-box">
        <button dlgtype="disclosure" hidden="true"/>
        <button dlgtype="extra2" hidden="true"/>
        <button dlgtype="extra1" hidden="true"/>
        <spacer class="button-spacer" part="button-spacer" flex="1"/>
        <button dlgtype="cancel"/>
        <button dlgtype="accept"/>
      </hbox>`
        : `
      <hbox class="dialog-button-box" pack="end">
        <button dlgtype="extra2" hidden="true"/>
        <spacer class="button-spacer" part="button-spacer" flex="1" hidden="true"/>
        <button dlgtype="accept"/>
        <button dlgtype="extra1" hidden="true"/>
        <button dlgtype="cancel"/>
        <button dlgtype="disclosure" hidden="true"/>
      </hbox>`;

      return `
      <html:link rel="stylesheet" href="chrome://global/skin/button.css"/>
      <html:link rel="stylesheet" href="chrome://global/skin/dialog.css"/>
      ${this.hasAttribute("subdialog") ? this.inContentStyle : ""}
      <vbox class="box-inherit" part="content-box">
        <html:slot></html:slot>
      </vbox>
      ${buttons}`;
    }

    connectedCallback() {
      if (this.delayConnectedCallback()) {
        return;
      }
      if (this.hasConnected) {
        return;
      }
      this.hasConnected = true;
      this.attachShadow({ mode: "open" });

      document.documentElement.setAttribute("role", "dialog");
      document.l10n?.connectRoot(this.shadowRoot);

      this.shadowRoot.textContent = "";
      this.shadowRoot.appendChild(
        MozXULElement.parseXULToFragment(this._markup)
      );
      this.initializeAttributeInheritance();

      this._configureButtons(this.buttons);

      window.moveToAlertPosition = this.moveToAlertPosition;
      window.centerWindowOnScreen = this.centerWindowOnScreen;

      document.addEventListener(
        "keypress",
        event => {
          if (event.keyCode == KeyEvent.DOM_VK_RETURN) {
            this._hitEnter(event);
          } else if (
            event.keyCode == KeyEvent.DOM_VK_ESCAPE &&
            !event.defaultPrevented
          ) {
            this.cancelDialog();
          }
        },
        { mozSystemGroup: true }
      );

      if (AppConstants.platform == "macosx") {
        document.addEventListener(
          "keypress",
          event => {
            if (event.key == "." && event.metaKey) {
              this.cancelDialog();
            }
          },
          true
        );
      } else {
        this.addEventListener("focus", this, true);
        this.shadowRoot.addEventListener("focus", this, true);
      }

      // listen for when window is closed via native close buttons
      window.addEventListener("close", event => {
        if (!this.cancelDialog()) {
          event.preventDefault();
        }
      });

      // Call postLoadInit for things that we need to initialize after onload.
      if (document.readyState == "complete") {
        this._postLoadInit();
      } else {
        window.addEventListener("load", () => this._postLoadInit());
      }
    }

    set buttons(val) {
      this._configureButtons(val);
    }

    get buttons() {
      return this.getAttribute("buttons");
    }

    set defaultButton(val) {
      this._setDefaultButton(val);
    }

    get defaultButton() {
      if (this.hasAttribute("defaultButton")) {
        return this.getAttribute("defaultButton");
      }
      return "accept"; // default to the accept button
    }

    get _strBundle() {
      if (!this.__stringBundle) {
        this.__stringBundle = Services.strings.createBundle(
          "chrome://global/locale/dialog.properties"
        );
      }
      return this.__stringBundle;
    }

    acceptDialog() {
      return this._doButtonCommand("accept");
    }

    cancelDialog() {
      return this._doButtonCommand("cancel");
    }

    getButton(aDlgType) {
      return this._buttons[aDlgType];
    }

    get buttonBox() {
      return this.shadowRoot.querySelector(".dialog-button-box");
    }

    // NOTE(emilio): This has to match AppWindow::IntrinsicallySizeShell, to
    // prevent flickering, see bug 1799394.
    _sizeToPreferredSize() {
      const docEl = document.documentElement;
      const prefWidth = (() => {
        if (docEl.hasAttribute("width")) {
          return parseInt(docEl.getAttribute("width"));
        }
        let prefWidthProp = docEl.getAttribute("prefwidth");
        if (prefWidthProp) {
          let minWidth = parseFloat(
            getComputedStyle(docEl).getPropertyValue(prefWidthProp)
          );
          if (isFinite(minWidth)) {
            return minWidth;
          }
        }
        return 0;
      })();
      window.sizeToContentConstrained({ prefWidth });
    }

    moveToAlertPosition() {
      // hack. we need this so the window has something like its final size
      if (window.outerWidth == 1) {
        dump(
          "Trying to position a sizeless window; caller should have called sizeToContent() or sizeTo(). See bug 75649.\n"
        );
        this._sizeToPreferredSize();
      }

      if (opener) {
        var xOffset = (opener.outerWidth - window.outerWidth) / 2;
        var yOffset = opener.outerHeight / 5;

        var newX = opener.screenX + xOffset;
        var newY = opener.screenY + yOffset;
      } else {
        newX = (screen.availWidth - window.outerWidth) / 2;
        newY = (screen.availHeight - window.outerHeight) / 2;
      }

      // ensure the window is fully onscreen (if smaller than the screen)
      if (newX < screen.availLeft) {
        newX = screen.availLeft + 20;
      }
      if (newX + window.outerWidth > screen.availLeft + screen.availWidth) {
        newX = screen.availLeft + screen.availWidth - window.outerWidth - 20;
      }

      if (newY < screen.availTop) {
        newY = screen.availTop + 20;
      }
      if (newY + window.outerHeight > screen.availTop + screen.availHeight) {
        newY = screen.availTop + screen.availHeight - window.outerHeight - 60;
      }

      window.moveTo(newX, newY);
    }

    centerWindowOnScreen() {
      var xOffset = screen.availWidth / 2 - window.outerWidth / 2;
      var yOffset = screen.availHeight / 2 - window.outerHeight / 2;

      xOffset = xOffset > 0 ? xOffset : 0;
      yOffset = yOffset > 0 ? yOffset : 0;
      window.moveTo(xOffset, yOffset);
    }

    // Give focus to the first focusable element in the dialog
    _setInitialFocusIfNeeded() {
      let focusedElt = document.commandDispatcher.focusedElement;
      if (focusedElt) {
        return;
      }

      const defaultButton = this.getButton(this.defaultButton);
      Services.focus.moveFocus(
        window,
        null,
        Services.focus.MOVEFOCUS_FORWARD,
        Services.focus.FLAG_NOPARENTFRAME
      );

      focusedElt = document.commandDispatcher.focusedElement;
      if (!focusedElt) {
        return; // No focusable element?
      }

      let firstFocusedElt = focusedElt;
      while (
        focusedElt.localName == "tab" ||
        focusedElt.getAttribute("noinitialfocus") == "true"
      ) {
        Services.focus.moveFocus(
          window,
          focusedElt,
          Services.focus.MOVEFOCUS_FORWARD,
          Services.focus.FLAG_NOPARENTFRAME
        );
        focusedElt = document.commandDispatcher.focusedElement;
        if (focusedElt == firstFocusedElt) {
          if (focusedElt.getAttribute("noinitialfocus") == "true") {
            focusedElt.blur();
          }
          // Didn't find anything else to focus, we're done.
          return;
        }
      }

      if (firstFocusedElt.localName == "tab") {
        if (focusedElt.hasAttribute("dlgtype")) {
          // We don't want to focus on anonymous OK, Cancel, etc. buttons,
          // so return focus to the tab itself
          firstFocusedElt.focus();
        }
      } else if (
        AppConstants.platform != "macosx" &&
        focusedElt.hasAttribute("dlgtype") &&
        focusedElt != defaultButton
      ) {
        defaultButton.focus();
        if (document.commandDispatcher.focusedElement != defaultButton) {
          // If the default button is not focusable, then return focus to the
          // initial element if possible, or blur otherwise.
          if (firstFocusedElt.getAttribute("noinitialfocus") == "true") {
            focusedElt.blur();
          } else {
            firstFocusedElt.focus();
          }
        }
      }
    }

    async _postLoadInit() {
      this._setInitialFocusIfNeeded();
      let finalStep = () => {
        this._sizeToPreferredSize();
        this._snapCursorToDefaultButtonIfNeeded();
      };
      // As a hack to ensure Windows sizes the window correctly,
      // _sizeToPreferredSize() needs to happen after
      // AppWindow::OnChromeLoaded. That one is called right after the load
      // event dispatch but within the same task. Using direct dispatch let's
      // all this code run before the next task (which might be a task to
      // paint the window).
      // But, MacOS doesn't like resizing after window/dialog becoming visible.
      // Linux seems to be able to handle both cases.
      if (Services.appinfo.OS == "Darwin") {
        finalStep();
      } else {
        Services.tm.dispatchDirectTaskToCurrentThread(finalStep);
      }
    }

    // This snaps the cursor to the default button rect on windows, when
    // SPI_GETSNAPTODEFBUTTON is set.
    async _snapCursorToDefaultButtonIfNeeded() {
      const defaultButton = this.getButton(this.defaultButton);
      if (!defaultButton) {
        return;
      }
      try {
        // FIXME(emilio, bug 1797624): This setTimeout() ensures enough time
        // has passed so that the dialog vertical margin has been set by the
        // front-end. For subdialogs, cursor positioning should probably be
        // done by the opener instead, once the dialog is positioned.
        await new Promise(r => setTimeout(r, 0));
        await window.promiseDocumentFlushed(() => {});
        window.notifyDefaultButtonLoaded(defaultButton);
      } catch (e) {}
    }

    _configureButtons(aButtons) {
      // by default, get all the anonymous button elements
      var buttons = {};
      this._buttons = buttons;

      for (let type of ["accept", "cancel", "extra1", "extra2", "disclosure"]) {
        buttons[type] = this.shadowRoot.querySelector(`[dlgtype="${type}"]`);
      }

      // look for any overriding explicit button elements
      var exBtns = this.getElementsByAttribute("dlgtype", "*");
      var dlgtype;
      for (let i = 0; i < exBtns.length; ++i) {
        dlgtype = exBtns[i].getAttribute("dlgtype");
        buttons[dlgtype].hidden = true; // hide the anonymous button
        buttons[dlgtype] = exBtns[i];
      }

      // add the label and oncommand handler to each button
      for (dlgtype in buttons) {
        var button = buttons[dlgtype];
        button.addEventListener(
          "command",
          this._handleButtonCommand.bind(this),
          true
        );

        // don't override custom labels with pre-defined labels on explicit buttons
        if (!button.hasAttribute("label")) {
          // dialog attributes override the default labels in dialog.properties
          if (this.hasAttribute("buttonlabel" + dlgtype)) {
            button.setAttribute(
              "label",
              this.getAttribute("buttonlabel" + dlgtype)
            );
            if (this.hasAttribute("buttonaccesskey" + dlgtype)) {
              button.setAttribute(
                "accesskey",
                this.getAttribute("buttonaccesskey" + dlgtype)
              );
            }
          } else if (this.hasAttribute("buttonid" + dlgtype)) {
            document.l10n.setAttributes(
              button,
              this.getAttribute("buttonid" + dlgtype)
            );
          } else if (dlgtype != "extra1" && dlgtype != "extra2") {
            button.setAttribute(
              "label",
              this._strBundle.GetStringFromName("button-" + dlgtype)
            );
            var accessKey = this._strBundle.GetStringFromName(
              "accesskey-" + dlgtype
            );
            if (accessKey) {
              button.setAttribute("accesskey", accessKey);
            }
          }
        }
      }

      // ensure that hitting enter triggers the default button command
      // eslint-disable-next-line no-self-assign
      this.defaultButton = this.defaultButton;

      // if there is a special button configuration, use it
      if (aButtons) {
        // expect a comma delimited list of dlgtype values
        var list = aButtons.split(",");

        // mark shown dlgtypes as true
        var shown = {
          accept: false,
          cancel: false,
          disclosure: false,
          extra1: false,
          extra2: false,
        };
        for (let i = 0; i < list.length; ++i) {
          shown[list[i].replace(/ /g, "")] = true;
        }

        // hide/show the buttons we want
        for (dlgtype in buttons) {
          buttons[dlgtype].hidden = !shown[dlgtype];
        }

        // show the spacer on Windows only when the extra2 button is present
        if (AppConstants.platform == "win") {
          let spacer = this.shadowRoot.querySelector(".button-spacer");
          spacer.removeAttribute("hidden");
          spacer.setAttribute("flex", shown.extra2 ? "1" : "0");
        }
      }
    }

    _setDefaultButton(aNewDefault) {
      // remove the default attribute from the previous default button, if any
      var oldDefaultButton = this.getButton(this.defaultButton);
      if (oldDefaultButton) {
        oldDefaultButton.removeAttribute("default");
      }

      var newDefaultButton = this.getButton(aNewDefault);
      if (newDefaultButton) {
        this.setAttribute("defaultButton", aNewDefault);
        newDefaultButton.setAttribute("default", "true");
      } else {
        this.setAttribute("defaultButton", "none");
        if (aNewDefault != "none") {
          dump(
            "invalid new default button: " + aNewDefault + ", assuming: none\n"
          );
        }
      }
    }

    _handleButtonCommand(aEvent) {
      return this._doButtonCommand(aEvent.target.getAttribute("dlgtype"));
    }

    _doButtonCommand(aDlgType) {
      var button = this.getButton(aDlgType);
      if (!button.disabled) {
        var noCancel = this._fireButtonEvent(aDlgType);
        if (noCancel) {
          if (aDlgType == "accept" || aDlgType == "cancel") {
            var closingEvent = new CustomEvent("dialogclosing", {
              bubbles: true,
              detail: { button: aDlgType },
            });
            this.dispatchEvent(closingEvent);
            window.close();
          }
        }
        return noCancel;
      }
      return true;
    }

    _fireButtonEvent(aDlgType) {
      var event = document.createEvent("Events");
      event.initEvent("dialog" + aDlgType, true, true);

      // handle dom event handlers
      return this.dispatchEvent(event);
    }

    _hitEnter(evt) {
      if (evt.defaultPrevented) {
        return;
      }

      var btn = this.getButton(this.defaultButton);
      if (btn && !btn.hidden) {
        this._doButtonCommand(this.defaultButton);
      }
    }

    on_focus(event) {
      let btn = this.getButton(this.defaultButton);
      if (btn) {
        btn.setAttribute(
          "default",
          event.originalTarget == btn ||
            !(
              event.originalTarget.localName == "button" ||
              event.originalTarget.localName == "toolbarbutton"
            )
        );
      }
    }
  }

  customElements.define("dialog", MozDialog);
}
PK
!<�SR��0chrome/toolkit/content/global/elements/editor.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
  /* globals XULFrameElement */

  class MozEditor extends XULFrameElement {
    connectedCallback() {
      this._editorContentListener = {
        QueryInterface: ChromeUtils.generateQI([
          "nsIURIContentListener",
          "nsISupportsWeakReference",
        ]),
        doContent() {
          return false;
        },
        isPreferred() {
          return false;
        },
        canHandleContent() {
          return false;
        },
        loadCookie: null,
        parentContentListener: null,
      };

      this._finder = null;

      this._fastFind = null;

      this._lastSearchString = null;

      // Make window editable immediately only
      //   if the "editortype" attribute is supplied
      // This allows using same contentWindow for different editortypes,
      //   where the type is determined during the apps's window.onload handler.
      if (this.editortype) {
        this.makeEditable(this.editortype, true);
      }
    }

    get finder() {
      if (!this._finder) {
        if (!this.docShell) {
          return null;
        }

        let { Finder } = ChromeUtils.importESModule(
          "resource://gre/modules/Finder.sys.mjs"
        );
        this._finder = new Finder(this.docShell);
      }
      return this._finder;
    }

    get fastFind() {
      if (!this._fastFind) {
        if (!("@mozilla.org/typeaheadfind;1" in Cc)) {
          return null;
        }

        if (!this.docShell) {
          return null;
        }

        this._fastFind = Cc["@mozilla.org/typeaheadfind;1"].createInstance(
          Ci.nsITypeAheadFind
        );
        this._fastFind.init(this.docShell);
      }
      return this._fastFind;
    }

    set editortype(val) {
      this.setAttribute("editortype", val);
    }

    get editortype() {
      return this.getAttribute("editortype");
    }

    get currentURI() {
      return this.webNavigation.currentURI;
    }

    get webBrowserFind() {
      return this.docShell
        .QueryInterface(Ci.nsIInterfaceRequestor)
        .getInterface(Ci.nsIWebBrowserFind);
    }

    get editingSession() {
      return this.docShell.editingSession;
    }

    get commandManager() {
      return this.webNavigation
        .QueryInterface(Ci.nsIInterfaceRequestor)
        .getInterface(Ci.nsICommandManager);
    }

    set fullZoom(val) {
      this.browsingContext.fullZoom = val;
    }

    get fullZoom() {
      return this.browsingContext.fullZoom;
    }

    set textZoom(val) {
      this.browsingContext.textZoom = val;
    }

    get textZoom() {
      return this.browsingContext.textZoom;
    }

    get isSyntheticDocument() {
      return this.contentDocument.isSyntheticDocument;
    }

    get messageManager() {
      if (this.frameLoader) {
        return this.frameLoader.messageManager;
      }
      return null;
    }

    // Copied from toolkit/content/widgets/browser-custom-element.js.
    // Send an asynchronous message to the remote child via an actor.
    // Note: use this only for messages through an actor. For old-style
    // messages, use the message manager.
    // The value of the scope argument determines which browsing contexts
    // are sent to:
    //   'all' - send to actors associated with all descendant child frames.
    //   'roots' - send only to actors associated with process roots.
    //   undefined/'' - send only to the top-level actor and not any descendants.
    sendMessageToActor(messageName, args, actorName, scope) {
      if (!this.frameLoader) {
        return;
      }

      function sendToChildren(browsingContext, childScope) {
        let windowGlobal = browsingContext.currentWindowGlobal;
        // If 'roots' is set, only send if windowGlobal.isProcessRoot is true.
        if (
          windowGlobal &&
          (childScope != "roots" || windowGlobal.isProcessRoot)
        ) {
          windowGlobal.getActor(actorName).sendAsyncMessage(messageName, args);
        }

        // Iterate as long as scope in assigned. Note that we use the original
        // passed in scope, not childScope here.
        if (scope) {
          for (let context of browsingContext.children) {
            sendToChildren(context, scope);
          }
        }
      }

      // Pass no second argument to always send to the top-level browsing context.
      sendToChildren(this.browsingContext);
    }

    get outerWindowID() {
      return this.docShell.outerWindowID;
    }

    makeEditable(editortype, waitForUrlLoad) {
      let win = this.contentWindow;
      this.editingSession.makeWindowEditable(
        win,
        editortype,
        waitForUrlLoad,
        true,
        false
      );
      this.setAttribute("editortype", editortype);

      this.docShell
        .QueryInterface(Ci.nsIInterfaceRequestor)
        .getInterface(Ci.nsIURIContentListener).parentContentListener =
        this._editorContentListener;
    }

    getEditor(containingWindow) {
      return this.editingSession.getEditorForWindow(containingWindow);
    }

    getHTMLEditor(containingWindow) {
      var editor = this.editingSession.getEditorForWindow(containingWindow);
      return editor.QueryInterface(Ci.nsIHTMLEditor);
    }
  }

  customElements.define("editor", MozEditor);
}
PK
!<�l�����1chrome/toolkit/content/global/elements/findbar.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
  const { AppConstants } = ChromeUtils.importESModule(
    "resource://gre/modules/AppConstants.sys.mjs"
  );

  const PREFS_TO_OBSERVE_BOOL = new Map([
    ["findAsYouType", "accessibility.typeaheadfind"],
    ["manualFAYT", "accessibility.typeaheadfind.manual"],
    ["typeAheadLinksOnly", "accessibility.typeaheadfind.linksonly"],
    ["entireWord", "findbar.entireword"],
    ["highlightAll", "findbar.highlightAll"],
    ["useModalHighlight", "findbar.modalHighlight"],
  ]);
  const PREFS_TO_OBSERVE_INT = new Map([
    ["typeAheadCaseSensitive", "accessibility.typeaheadfind.casesensitive"],
    ["matchDiacritics", "findbar.matchdiacritics"],
  ]);
  const PREFS_TO_OBSERVE_ALL = new Map([
    ...PREFS_TO_OBSERVE_BOOL,
    ...PREFS_TO_OBSERVE_INT,
  ]);
  const TOPIC_MAC_APP_ACTIVATE = "mac_app_activate";

  class MozFindbar extends MozXULElement {
    static get markup() {
      return `
      <hbox anonid="findbar-container" class="findbar-container" flex="1" align="center">
        <hbox anonid="findbar-textbox-wrapper" align="stretch">
          <html:input anonid="findbar-textbox" class="findbar-textbox" />
          <toolbarbutton anonid="find-previous" class="findbar-find-previous tabbable"
            data-l10n-attrs="tooltiptext" data-l10n-id="findbar-previous"
            oncommand="onFindAgainCommand(true);" disabled="true" />
          <toolbarbutton anonid="find-next" class="findbar-find-next tabbable"
            data-l10n-id="findbar-next" oncommand="onFindAgainCommand(false);" disabled="true" />
        </hbox>
        <checkbox anonid="highlight" class="findbar-highlight tabbable"
          data-l10n-id="findbar-highlight-all2" oncommand="toggleHighlight(this.checked);"/>
        <checkbox anonid="find-case-sensitive" class="findbar-case-sensitive tabbable"
          data-l10n-id="findbar-case-sensitive" oncommand="_setCaseSensitivity(this.checked ? 1 : 0);"/>
        <checkbox anonid="find-match-diacritics" class="findbar-match-diacritics tabbable"
          data-l10n-id="findbar-match-diacritics" oncommand="_setDiacriticMatching(this.checked ? 1 : 0);"/>
        <checkbox anonid="find-entire-word" class="findbar-entire-word tabbable"
          data-l10n-id="findbar-entire-word" oncommand="toggleEntireWord(this.checked);"/>
        <label anonid="match-case-status" class="findbar-label"
          data-l10n-id="findbar-case-sensitive-status" hidden="true" />
        <label anonid="match-diacritics-status" class="findbar-label"
          data-l10n-id="findbar-match-diacritics-status" hidden="true" />
        <label anonid="entire-word-status" class="findbar-label"
          data-l10n-id="findbar-entire-word-status" hidden="true" />
        <label anonid="found-matches" class="findbar-label found-matches" hidden="true" />
        <image anonid="find-status-icon" class="find-status-icon" />
        <description anonid="find-status" control="findbar-textbox" class="findbar-label findbar-find-status" />
      </hbox>
      <toolbarbutton anonid="find-closebutton" class="findbar-closebutton tabbable close-icon"
        data-l10n-id="findbar-find-button-close" oncommand="close();"/>
      `;
    }

    constructor() {
      super();
      MozXULElement.insertFTLIfNeeded("toolkit/main-window/findbar.ftl");
      this.destroy = this.destroy.bind(this);

      // We have to guard against `this.close` being |null| due to an unknown
      // issue, which is tracked in bug 957999.
      this.addEventListener(
        "keypress",
        event => {
          if (event.keyCode == event.DOM_VK_ESCAPE) {
            if (this.close) {
              this.close();
            }
            event.preventDefault();
          }
        },
        true
      );
    }

    connectedCallback() {
      // Hide the findbar immediately without animation. This prevents a flicker in the case where
      // we'll never be shown (i.e. adopting a tab that has a previously-opened-but-now-closed
      // findbar into a new window).
      this.setAttribute("noanim", "true");
      this.hidden = true;
      this.appendChild(this.constructor.fragment);
      if (AppConstants.platform == "macosx") {
        this.insertBefore(
          this.getElement("find-closebutton"),
          this.getElement("findbar-container")
        );
      }

      /**
       * Please keep in sync with toolkit/modules/FindBarContent.sys.mjs
       */
      this.FIND_NORMAL = 0;

      this.FIND_TYPEAHEAD = 1;

      this.FIND_LINKS = 2;

      this._findMode = 0;

      this._flashFindBar = 0;

      this._initialFlashFindBarCount = 6;

      /**
       * For tests that need to know when the find bar is finished
       * initializing, we store a promise to notify on.
       */
      this._startFindDeferred = null;

      this._browser = null;

      this._destroyed = false;

      this._xulBrowserWindow = null;

      // These elements are accessed frequently and are therefore cached.
      this._findField = this.getElement("findbar-textbox");
      this._foundMatches = this.getElement("found-matches");
      this._findStatusIcon = this.getElement("find-status-icon");
      this._findStatusDesc = this.getElement("find-status");

      this._foundURL = null;

      let prefsvc = Services.prefs;

      this.quickFindTimeoutLength = prefsvc.getIntPref(
        "accessibility.typeaheadfind.timeout"
      );
      this._flashFindBar = prefsvc.getIntPref(
        "accessibility.typeaheadfind.flashBar"
      );

      let observe = (this._observe = this.observe.bind(this));
      for (let [propName, prefName] of PREFS_TO_OBSERVE_ALL) {
        prefsvc.addObserver(prefName, observe);
        let prefGetter = PREFS_TO_OBSERVE_BOOL.has(propName) ? "Bool" : "Int";
        this["_" + propName] = prefsvc[`get${prefGetter}Pref`](prefName);
      }
      Services.obs.addObserver(observe, TOPIC_MAC_APP_ACTIVATE);

      this._findResetTimeout = -1;

      // Make sure the FAYT keypress listener is attached by initializing the
      // browser property.
      if (this.getAttribute("browserid")) {
        setTimeout(() => {
          // eslint-disable-next-line no-self-assign
          this.browser = this.browser;
        }, 0);
      }

      window.addEventListener("unload", this.destroy);

      this._findField.addEventListener("input", () => {
        // We should do nothing during composition.  E.g., composing string
        // before converting may matches a forward word of expected word.
        // After that, even if user converts the composition string to the
        // expected word, it may find second or later searching word in the
        // document.
        if (this._isIMEComposing) {
          return;
        }

        const value = this._findField.value;
        if (this._hadValue && !value) {
          this._willfullyDeleted = true;
          this._hadValue = false;
        } else if (value.trim()) {
          this._hadValue = true;
          this._willfullyDeleted = false;
        }
        this._find(value);
      });

      this._findField.addEventListener("keypress", event => {
        switch (event.keyCode) {
          case KeyEvent.DOM_VK_RETURN:
            if (this.findMode == this.FIND_NORMAL) {
              let findString = this._findField;
              if (!findString.value) {
                return;
              }
              if (event.getModifierState("Accel")) {
                this.getElement("highlight").click();
                return;
              }

              this.onFindAgainCommand(event.shiftKey);
            } else {
              this._finishFAYT(event);
            }
            break;
          case KeyEvent.DOM_VK_TAB:
            let shouldHandle =
              !event.altKey && !event.ctrlKey && !event.metaKey;
            if (shouldHandle && this.findMode != this.FIND_NORMAL) {
              this._finishFAYT(event);
            }
            break;
          case KeyEvent.DOM_VK_PAGE_UP:
          case KeyEvent.DOM_VK_PAGE_DOWN:
            if (
              !event.altKey &&
              !event.ctrlKey &&
              !event.metaKey &&
              !event.shiftKey
            ) {
              this.browser.finder.keyPress(event);
              event.preventDefault();
            }
            break;
          case KeyEvent.DOM_VK_UP:
          case KeyEvent.DOM_VK_DOWN:
            this.browser.finder.keyPress(event);
            event.preventDefault();
            break;
        }
      });

      this._findField.addEventListener("blur", () => {
        // Note: This code used to remove the selection
        // if it matched an editable.
        this.browser.finder.enableSelection();
      });

      this._findField.addEventListener("focus", () => {
        this._updateBrowserWithState();
      });

      this._findField.addEventListener("compositionstart", () => {
        // Don't close the find toolbar while IME is composing.
        let findbar = this;
        findbar._isIMEComposing = true;
        if (findbar._quickFindTimeout) {
          clearTimeout(findbar._quickFindTimeout);
          findbar._quickFindTimeout = null;
          findbar._updateBrowserWithState();
        }
      });

      this._findField.addEventListener("compositionend", () => {
        this._isIMEComposing = false;
        if (this.findMode != this.FIND_NORMAL) {
          this._setFindCloseTimeout();
        }
      });

      this._findField.addEventListener("dragover", event => {
        if (event.dataTransfer.types.includes("text/plain")) {
          event.preventDefault();
        }
      });

      this._findField.addEventListener("drop", event => {
        let value = event.dataTransfer.getData("text/plain");
        this._findField.value = value;
        this._find(value);
        event.stopPropagation();
        event.preventDefault();
      });
    }

    set findMode(val) {
      this._findMode = val;
      this._updateBrowserWithState();
    }

    get findMode() {
      return this._findMode;
    }

    set prefillWithSelection(val) {
      this.setAttribute("prefillwithselection", val);
    }

    get prefillWithSelection() {
      return this.getAttribute("prefillwithselection") != "false";
    }

    get hasTransactions() {
      if (this._findField.value) {
        return true;
      }

      // Watch out for lazy editor init
      if (this._findField.editor) {
        return this._findField.editor.canUndo || this._findField.editor.canRedo;
      }
      return false;
    }

    set browser(val) {
      function setFindbarInActor(browser, findbar) {
        if (!browser.frameLoader) {
          return;
        }

        let windowGlobal = browser.browsingContext.currentWindowGlobal;
        if (windowGlobal) {
          let findbarParent = windowGlobal.getActor("FindBar");
          if (findbarParent) {
            findbarParent.setFindbar(browser, findbar);
          }
        }
      }

      if (this._browser) {
        setFindbarInActor(this._browser, null);

        let finder = this._browser.finder;
        if (finder) {
          finder.removeResultListener(this);
        }
      }

      this._browser = val;
      if (this._browser) {
        // Need to do this to ensure the correct initial state.
        this._updateBrowserWithState();

        setFindbarInActor(this._browser, this);

        this._browser.finder.addResultListener(this);
      }
    }

    get browser() {
      if (!this._browser) {
        const id = this.getAttribute("browserid");
        if (id) {
          this._browser = document.getElementById(id);
        }
      }
      return this._browser;
    }

    observe(subject, topic, prefName) {
      if (topic == TOPIC_MAC_APP_ACTIVATE) {
        this._onAppActivateMac();
        return;
      }

      if (topic != "nsPref:changed") {
        return;
      }

      let prefsvc = Services.prefs;

      switch (prefName) {
        case "accessibility.typeaheadfind":
          this._findAsYouType = prefsvc.getBoolPref(prefName);
          break;
        case "accessibility.typeaheadfind.manual":
          this._manualFAYT = prefsvc.getBoolPref(prefName);
          break;
        case "accessibility.typeaheadfind.timeout":
          this.quickFindTimeoutLength = prefsvc.getIntPref(prefName);
          break;
        case "accessibility.typeaheadfind.linksonly":
          this._typeAheadLinksOnly = prefsvc.getBoolPref(prefName);
          break;
        case "accessibility.typeaheadfind.casesensitive":
          this._setCaseSensitivity(prefsvc.getIntPref(prefName));
          break;
        case "findbar.entireword":
          this._entireWord = prefsvc.getBoolPref(prefName);
          this.toggleEntireWord(this._entireWord, true);
          break;
        case "findbar.highlightAll":
          this.toggleHighlight(prefsvc.getBoolPref(prefName), true);
          break;
        case "findbar.matchdiacritics":
          this._setDiacriticMatching(prefsvc.getIntPref(prefName));
          break;
        case "findbar.modalHighlight":
          this._useModalHighlight = prefsvc.getBoolPref(prefName);
          if (this.browser.finder) {
            this.browser.finder.onModalHighlightChange(this._useModalHighlight);
          }
          break;
      }
    }

    getElement(aAnonymousID) {
      return this.querySelector(`[anonid=${aAnonymousID}]`);
    }

    /**
     * This is necessary because custom elements don't have a "real" destructor.
     * This method is called explicitly from disconnectedCallback, and from
     * an unload event handler that we add.
     */
    destroy() {
      if (this._destroyed) {
        return;
      }
      window.removeEventListener("unload", this.destroy);
      this._destroyed = true;

      this.browser?._finder?.destroy();

      // Invoking this setter also removes the message listeners.
      this.browser = null;

      let prefsvc = Services.prefs;
      let observe = this._observe;
      for (let [, prefName] of PREFS_TO_OBSERVE_ALL) {
        prefsvc.removeObserver(prefName, observe);
      }

      Services.obs.removeObserver(observe, TOPIC_MAC_APP_ACTIVATE);

      // Clear all timers that might still be running.
      this._cancelTimers();
    }

    _cancelTimers() {
      if (this._flashFindBarTimeout) {
        clearInterval(this._flashFindBarTimeout);
        this._flashFindBarTimeout = null;
      }
      if (this._quickFindTimeout) {
        clearTimeout(this._quickFindTimeout);
        this._quickFindTimeout = null;
      }
      if (this._findResetTimeout) {
        clearTimeout(this._findResetTimeout);
        this._findResetTimeout = null;
      }
    }

    _setFindCloseTimeout() {
      if (this._quickFindTimeout) {
        clearTimeout(this._quickFindTimeout);
      }

      // Don't close the find toolbar while IME is composing OR when the
      // findbar is already hidden.
      if (this._isIMEComposing || this.hidden) {
        this._quickFindTimeout = null;
        this._updateBrowserWithState();
        return;
      }

      if (this.quickFindTimeoutLength < 1) {
        this._quickFindTimeout = null;
      } else {
        this._quickFindTimeout = setTimeout(() => {
          if (this.findMode != this.FIND_NORMAL) {
            this.close();
          }
          this._quickFindTimeout = null;
        }, this.quickFindTimeoutLength);
      }
      this._updateBrowserWithState();
    }

    /**
     * Updates the search match count after each find operation on a new string.
     */
    _updateMatchesCount() {
      if (!this._dispatchFindEvent("matchescount")) {
        return;
      }

      this.browser.finder.requestMatchesCount(
        this._findField.value,
        this.findMode == this.FIND_LINKS
      );
    }

    /**
     * Turns highlighting of all occurrences on or off.
     *
     * @param {Boolean} highlight Whether to turn the highlight on or off.
     * @param {Boolean} fromPrefObserver Whether the callee is the pref
     *                                   observer, which means we should not set
     *                                   the same pref again.
     */
    toggleHighlight(highlight, fromPrefObserver) {
      if (highlight === this._highlightAll) {
        return;
      }

      this.browser.finder.onHighlightAllChange(highlight);

      this._setHighlightAll(highlight, fromPrefObserver);

      if (!this._dispatchFindEvent("highlightallchange")) {
        return;
      }

      let word = this._findField.value;
      // Bug 429723. Don't attempt to highlight ""
      if (highlight && !word) {
        return;
      }

      this.browser.finder.highlight(
        highlight,
        word,
        this.findMode == this.FIND_LINKS
      );

      // Update the matches count
      this._updateMatchesCount(Ci.nsITypeAheadFind.FIND_FOUND);
    }

    /**
     * Updates the highlight-all mode of the findbar and its UI.
     *
     * @param {Boolean} highlight Whether to turn the highlight on or off.
     * @param {Boolean} fromPrefObserver Whether the callee is the pref
     *                                   observer, which means we should not set
     *                                   the same pref again.
     */
    _setHighlightAll(highlight, fromPrefObserver) {
      if (typeof highlight != "boolean") {
        highlight = this._highlightAll;
      }
      if (highlight !== this._highlightAll) {
        this._highlightAll = highlight;
        if (!fromPrefObserver) {
          Services.telemetry.scalarAdd("findbar.highlight_all", 1);
          Services.prefs.setBoolPref("findbar.highlightAll", highlight);
        }
      }
      let checkbox = this.getElement("highlight");
      checkbox.checked = this._highlightAll;
    }

    /**
     * Updates the case-sensitivity mode of the findbar and its UI.
     *
     * @param {String} [str] The string for which case sensitivity might be
     *                       turned on. This only used when case-sensitivity is
     *                       in auto mode, see `_shouldBeCaseSensitive`. The
     *                       default value for this parameter is the find-field
     *                       value.
     * @see _shouldBeCaseSensitive
     */
    _updateCaseSensitivity(str) {
      let val = str || this._findField.value;

      let caseSensitive = this._shouldBeCaseSensitive(val);
      let checkbox = this.getElement("find-case-sensitive");
      let statusLabel = this.getElement("match-case-status");
      checkbox.checked = caseSensitive;

      // Show the checkbox on the full Find bar in non-auto mode.
      // Show the label in all other cases.
      if (
        this.findMode == this.FIND_NORMAL &&
        (this._typeAheadCaseSensitive == 0 || this._typeAheadCaseSensitive == 1)
      ) {
        checkbox.hidden = false;
        statusLabel.hidden = true;
      } else {
        checkbox.hidden = true;
        statusLabel.hidden = !caseSensitive;
      }

      this.browser.finder.caseSensitive = caseSensitive;
    }

    /**
     * Sets the findbar case-sensitivity mode.
     *
     * @param {Number} caseSensitivity 0 - case insensitive,
     *                                 1 - case sensitive,
     *                                 2 - auto = case sensitive if the matching
     *                                     string contains upper case letters.
     * @see _shouldBeCaseSensitive
     */
    _setCaseSensitivity(caseSensitivity) {
      this._typeAheadCaseSensitive = caseSensitivity;
      this._updateCaseSensitivity();
      this._findFailedString = null;
      this._find();

      this._dispatchFindEvent("casesensitivitychange");
      Services.telemetry.scalarAdd("findbar.match_case", 1);
    }

    /**
     * Updates the diacritic-matching mode of the findbar and its UI.
     *
     * @param {String} [str] The string for which diacritic matching might be
     *                       turned on. This is only used when diacritic
     *                       matching is in auto mode, see
     *                       `_shouldMatchDiacritics`. The default value for
     *                       this parameter is the find-field value.
     * @see _shouldMatchDiacritics.
     */
    _updateDiacriticMatching(str) {
      let val = str || this._findField.value;

      let matchDiacritics = this._shouldMatchDiacritics(val);
      let checkbox = this.getElement("find-match-diacritics");
      let statusLabel = this.getElement("match-diacritics-status");
      checkbox.checked = matchDiacritics;

      // Show the checkbox on the full Find bar in non-auto mode.
      // Show the label in all other cases.
      if (
        this.findMode == this.FIND_NORMAL &&
        (this._matchDiacritics == 0 || this._matchDiacritics == 1)
      ) {
        checkbox.hidden = false;
        statusLabel.hidden = true;
      } else {
        checkbox.hidden = true;
        statusLabel.hidden = !matchDiacritics;
      }

      this.browser.finder.matchDiacritics = matchDiacritics;
    }

    /**
     * Sets the findbar diacritic-matching mode
     * @param {Number} diacriticMatching 0 - ignore diacritics,
     *                                   1 - match diacritics,
     *                                   2 - auto = match diacritics if the
     *                                       matching string contains
     *                                       diacritics.
     * @see _shouldMatchDiacritics
     */
    _setDiacriticMatching(diacriticMatching) {
      this._matchDiacritics = diacriticMatching;
      this._updateDiacriticMatching();
      this._findFailedString = null;
      this._find();

      this._dispatchFindEvent("diacriticmatchingchange");

      Services.telemetry.scalarAdd("findbar.match_diacritics", 1);
    }

    /**
     * Updates the entire-word mode of the findbar and its UI.
     */
    _setEntireWord() {
      let entireWord = this._entireWord;
      let checkbox = this.getElement("find-entire-word");
      let statusLabel = this.getElement("entire-word-status");
      checkbox.checked = entireWord;

      // Show the checkbox on the full Find bar.
      // Show the label in all other cases.
      if (this.findMode == this.FIND_NORMAL) {
        checkbox.hidden = false;
        statusLabel.hidden = true;
      } else {
        checkbox.hidden = true;
        statusLabel.hidden = !entireWord;
      }

      this.browser.finder.entireWord = entireWord;
    }

    /**
     * Sets the findbar entire-word mode.
     *
     * @param {Boolean} entireWord Whether or not entire-word mode should be
     *                             turned on.
     */
    toggleEntireWord(entireWord, fromPrefObserver) {
      if (!fromPrefObserver) {
        // Just set the pref; our observer will change the find bar behavior.
        Services.prefs.setBoolPref("findbar.entireword", entireWord);

        Services.telemetry.scalarAdd("findbar.whole_words", 1);
        return;
      }

      this._findFailedString = null;
      this._find();
    }

    /**
     * Opens and displays the find bar.
     *
     * @param {Number} mode The find mode to be used, which is either
     *                      FIND_NORMAL, FIND_TYPEAHEAD or FIND_LINKS. If not
     *                      passed, we revert to the last find mode if any or
     *                      FIND_NORMAL.
     * @return {Boolean} `true` if the find bar wasn't previously open, `false`
     *                   otherwise.
     */
    open(mode) {
      if (mode != undefined) {
        this.findMode = mode;
      }

      this._findFailedString = null;

      this._updateFindUI();
      if (this.hidden) {
        Services.telemetry.scalarAdd("findbar.shown", 1);
        this.removeAttribute("noanim");
        this.hidden = false;

        this._updateStatusUI(Ci.nsITypeAheadFind.FIND_FOUND);

        let event = document.createEvent("Events");
        event.initEvent("findbaropen", true, false);
        this.dispatchEvent(event);

        this.browser.finder.onFindbarOpen();

        return true;
      }
      return false;
    }

    /**
     * Closes the findbar.
     *
     * @param {Boolean} [noAnim] Whether to disable to closing animation. Used
     *                           to close instantly and synchronously, when
     *                           other operations depend on this state.
     */
    close(noAnim) {
      if (this.hidden) {
        return;
      }

      if (noAnim) {
        this.setAttribute("noanim", true);
      }
      this.hidden = true;

      let event = document.createEvent("Events");
      event.initEvent("findbarclose", true, false);
      this.dispatchEvent(event);

      // 'focusContent()' iterates over all listeners in the chrome
      // process, so we need to call it from here.
      this.browser.finder.focusContent();
      this.browser.finder.onFindbarClose();

      this._cancelTimers();
      this._updateBrowserWithState();

      this._findFailedString = null;
    }

    clear() {
      this.browser.finder.removeSelection();
      // Clear value and undo/redo transactions
      this._findField.value = "";
      this._findField.editor?.clearUndoRedo();
      this.toggleHighlight(false);
      this._updateStatusUI();
      this._enableFindButtons(false);
    }

    _dispatchKeypressEvent(target, fakeEvent) {
      if (!target) {
        return;
      }

      // The event information comes from the child process.
      let event = new target.ownerGlobal.KeyboardEvent(
        fakeEvent.type,
        fakeEvent
      );
      target.dispatchEvent(event);
    }

    _updateStatusUIBar(foundURL) {
      if (!this._xulBrowserWindow) {
        try {
          this._xulBrowserWindow = window.docShell.treeOwner
            .QueryInterface(Ci.nsIInterfaceRequestor)
            .getInterface(Ci.nsIAppWindow).XULBrowserWindow;
        } catch (ex) {}
        if (!this._xulBrowserWindow) {
          return false;
        }
      }

      // Call this has the same effect like hovering over link,
      // the browser shows the URL as a tooltip.
      this._xulBrowserWindow.setOverLink(foundURL || "");
      return true;
    }

    _finishFAYT(keypressEvent) {
      this.browser.finder.focusContent();

      if (keypressEvent) {
        keypressEvent.preventDefault();
      }

      this.browser.finder.keyPress(keypressEvent);

      this.close();
      return true;
    }

    _shouldBeCaseSensitive(str) {
      if (this._typeAheadCaseSensitive == 0) {
        return false;
      }
      if (this._typeAheadCaseSensitive == 1) {
        return true;
      }

      return str != str.toLowerCase();
    }

    _shouldMatchDiacritics(str) {
      if (this._matchDiacritics == 0) {
        return false;
      }
      if (this._matchDiacritics == 1) {
        return true;
      }

      return str != str.normalize("NFD");
    }

    onMouseUp() {
      if (!this.hidden && this.findMode != this.FIND_NORMAL) {
        this.close();
      }
    }

    /**
     * We get a fake event object through an IPC message when FAYT is being used
     * from within the browser. We then stuff that input in the find bar here.
     *
     * @param {Object} fakeEvent Event object that looks and quacks like a
     *                           native DOM KeyPress event.
     */
    _onBrowserKeypress(fakeEvent) {
      const FAYT_LINKS_KEY = "'";
      const FAYT_TEXT_KEY = "/";

      if (!this.hidden && this._findField == document.activeElement) {
        this._dispatchKeypressEvent(this._findField, fakeEvent);
        return;
      }

      if (this.findMode != this.FIND_NORMAL && this._quickFindTimeout) {
        this._findField.select();
        this._findField.focus();
        this._dispatchKeypressEvent(this._findField, fakeEvent);
        return;
      }

      let key = fakeEvent.charCode
        ? String.fromCharCode(fakeEvent.charCode)
        : null;
      let manualstartFAYT =
        (key == FAYT_LINKS_KEY || key == FAYT_TEXT_KEY) && this._manualFAYT;
      let autostartFAYT =
        !manualstartFAYT && this._findAsYouType && key && key != " ";
      if (manualstartFAYT || autostartFAYT) {
        let mode =
          key == FAYT_LINKS_KEY || (autostartFAYT && this._typeAheadLinksOnly)
            ? this.FIND_LINKS
            : this.FIND_TYPEAHEAD;

        // Clear bar first, so that when openFindBar() calls setCaseSensitivity()
        // it doesn't get confused by a lingering value
        this._findField.value = "";

        this.open(mode);
        this._setFindCloseTimeout();
        this._findField.select();
        this._findField.focus();

        if (autostartFAYT) {
          this._dispatchKeypressEvent(this._findField, fakeEvent);
        } else {
          this._updateStatusUI(Ci.nsITypeAheadFind.FIND_FOUND);
        }
      }
    }

    _updateBrowserWithState() {
      if (this._browser) {
        this._browser.sendMessageToActor(
          "Findbar:UpdateState",
          {
            findMode: this.findMode,
            isOpenAndFocused:
              !this.hidden && document.activeElement == this._findField,
            hasQuickFindTimeout: !!this._quickFindTimeout,
          },
          "FindBar",
          "all"
        );
      }
    }

    _enableFindButtons(aEnable) {
      this.getElement("find-next").disabled = this.getElement(
        "find-previous"
      ).disabled = !aEnable;
    }

    /**
     * Determines whether minimalist or general-purpose search UI is to be
     * displayed when the find bar is activated.
     */
    _updateFindUI() {
      let showMinimalUI = this.findMode != this.FIND_NORMAL;

      let nodes = this.getElement("findbar-container").children;
      let wrapper = this.getElement("findbar-textbox-wrapper");
      let foundMatches = this._foundMatches;
      for (let node of nodes) {
        if (node == wrapper || node == foundMatches) {
          continue;
        }
        node.hidden = showMinimalUI;
      }
      this.getElement("find-next").hidden = this.getElement(
        "find-previous"
      ).hidden = showMinimalUI;
      foundMatches.hidden = showMinimalUI || !foundMatches.value;
      this._updateCaseSensitivity();
      this._updateDiacriticMatching();
      this._setEntireWord();
      this._setHighlightAll();

      if (showMinimalUI) {
        this._findField.classList.add("minimal");
      } else {
        this._findField.classList.remove("minimal");
      }

      let l10nId;
      if (this.findMode == this.FIND_TYPEAHEAD) {
        l10nId = "findbar-fast-find";
      } else if (this.findMode == this.FIND_LINKS) {
        l10nId = "findbar-fast-find-links";
      } else {
        l10nId = "findbar-normal-find";
      }
      document.l10n.setAttributes(this._findField, l10nId);
    }

    _find(value) {
      if (!this._dispatchFindEvent("")) {
        return;
      }

      let val = value || this._findField.value;

      // We have to carry around an explicit version of this, because
      // finder.searchString doesn't update on failed searches.
      this.browser._lastSearchString = val;

      // Only search on input if we don't have a last-failed string,
      // or if the current search string doesn't start with it.
      // In entire-word mode we always attemp a find; since sequential matching
      // is not guaranteed, the first character typed may not be a word (no
      // match), but the with the second character it may well be a word,
      // thus a match.
      if (
        !this._findFailedString ||
        !val.startsWith(this._findFailedString) ||
        this._entireWord
      ) {
        // Getting here means the user commanded a find op. Make sure any
        // initial prefilling is ignored if it hasn't happened yet.
        if (this._startFindDeferred) {
          this._startFindDeferred.resolve();
          this._startFindDeferred = null;
        }

        this._enableFindButtons(val);
        this._updateCaseSensitivity(val);
        this._updateDiacriticMatching(val);
        this._setEntireWord();

        this.browser.finder.fastFind(
          val,
          this.findMode == this.FIND_LINKS,
          this.findMode != this.FIND_NORMAL
        );
      }

      if (this.findMode != this.FIND_NORMAL) {
        this._setFindCloseTimeout();
      }

      if (this._findResetTimeout != -1) {
        clearTimeout(this._findResetTimeout);
      }

      // allow a search to happen on input again after a second has expired
      // since the previous input, to allow for dynamic content and/ or page
      // loading.
      this._findResetTimeout = setTimeout(() => {
        this._findFailedString = null;
        this._findResetTimeout = -1;
      }, 1000);
    }

    _flash() {
      if (this._flashFindBarCount === undefined) {
        this._flashFindBarCount = this._initialFlashFindBarCount;
      }

      if (this._flashFindBarCount-- == 0) {
        clearInterval(this._flashFindBarTimeout);
        this._findField.removeAttribute("flash");
        this._flashFindBarCount = 6;
        return;
      }

      this._findField.setAttribute(
        "flash",
        this._flashFindBarCount % 2 == 0 ? "false" : "true"
      );
    }

    _findAgain(findPrevious) {
      this.browser.finder.findAgain(
        this._findField.value,
        findPrevious,
        this.findMode == this.FIND_LINKS,
        this.findMode != this.FIND_NORMAL
      );
    }

    _updateStatusUI(res, findPrevious) {
      let statusL10nId;
      switch (res) {
        case Ci.nsITypeAheadFind.FIND_WRAPPED:
          this._findStatusIcon.setAttribute("status", "wrapped");
          this._findField.removeAttribute("status");
          statusL10nId = findPrevious
            ? "findbar-wrapped-to-bottom"
            : "findbar-wrapped-to-top";
          break;
        case Ci.nsITypeAheadFind.FIND_NOTFOUND:
          this._findStatusDesc.setAttribute("status", "notfound");
          this._findStatusIcon.setAttribute("status", "notfound");
          this._findField.setAttribute("status", "notfound");
          this._foundMatches.hidden = true;
          statusL10nId = "findbar-not-found";
          break;
        case Ci.nsITypeAheadFind.FIND_PENDING:
          this._findStatusIcon.setAttribute("status", "pending");
          this._findField.removeAttribute("status");
          this._findStatusDesc.removeAttribute("status");
          statusL10nId = "";
          break;
        case Ci.nsITypeAheadFind.FIND_FOUND:
        default:
          this._findStatusIcon.removeAttribute("status");
          this._findField.removeAttribute("status");
          this._findStatusDesc.removeAttribute("status");
          statusL10nId = "";
          break;
      }
      if (statusL10nId) {
        document.l10n.setAttributes(this._findStatusDesc, statusL10nId);
      } else {
        delete this._findStatusDesc.dataset.l10nId;
        this._findStatusDesc.textContent = "";
      }
    }

    updateControlState(result, findPrevious) {
      this._updateStatusUI(result, findPrevious);
      this._enableFindButtons(
        result !== Ci.nsITypeAheadFind.FIND_NOTFOUND && !!this._findField.value
      );
    }

    _dispatchFindEvent(type, findPrevious) {
      let event = document.createEvent("CustomEvent");
      event.initCustomEvent("find" + type, true, true, {
        query: this._findField.value,
        caseSensitive: !!this._typeAheadCaseSensitive,
        matchDiacritics: !!this._matchDiacritics,
        entireWord: this._entireWord,
        highlightAll: this._highlightAll,
        findPrevious,
      });
      return this.dispatchEvent(event);
    }

    /**
     * Opens the findbar, focuses the findfield and selects its contents.
     * Also flashes the findbar the first time it's used.
     *
     * @param {Number} mode The find mode to be used, which is either
     *                      FIND_NORMAL, FIND_TYPEAHEAD or FIND_LINKS. If not
     *                      passed, we revert to the last find mode if any or
     *                      FIND_NORMAL.
     * @return {Promise} A promise that will be resolved when the findbar is
     *                   fully opened.
     */
    startFind(mode) {
      let prefsvc = Services.prefs;
      let userWantsPrefill = true;
      this.open(mode);

      if (this._flashFindBar) {
        this._flashFindBarTimeout = setInterval(() => this._flash(), 500);
        prefsvc.setIntPref(
          "accessibility.typeaheadfind.flashBar",
          --this._flashFindBar
        );
      }

      this._startFindDeferred = Promise.withResolvers();
      let startFindPromise = this._startFindDeferred.promise;

      if (this.prefillWithSelection) {
        userWantsPrefill = prefsvc.getBoolPref(
          "accessibility.typeaheadfind.prefillwithselection"
        );
      }

      if (this.prefillWithSelection && userWantsPrefill) {
        this.browser.finder.getInitialSelection();

        // NB: We have to focus this._findField here so tests that send
        // key events can open and close the find bar synchronously.
        this._findField.focus();

        // (e10s) since we focus lets also select it, otherwise that would
        // only happen in this.onCurrentSelection and, because it is async,
        // there's a chance keypresses could come inbetween, leading to
        // jumbled up queries.
        this._findField.select();

        return startFindPromise;
      }

      // If userWantsPrefill is false but prefillWithSelection is true,
      // then we might need to check the selection clipboard. Call
      // onCurrentSelection to do so.
      // Note: this.onCurrentSelection clears this._startFindDeferred.
      this.onCurrentSelection("", true);
      return startFindPromise;
    }

    /**
     * Convenient alias to startFind(gFindBar.FIND_NORMAL);
     *
     * You should generally map the window's find command to this method.
     *   e.g. <command name="cmd_find" oncommand="gFindBar.onFindCommand();"/>
     */
    onFindCommand() {
      return this.startFind(this.FIND_NORMAL);
    }

    /**
     * Stub for find-next and find-previous commands.
     *
     * @param {Boolean} findPrevious `true` for find-previous, `false`
     *                               otherwise.
     */
    onFindAgainCommand(findPrevious) {
      if (findPrevious) {
        Services.telemetry.scalarAdd("findbar.find_prev", 1);
      } else {
        Services.telemetry.scalarAdd("findbar.find_next", 1);
      }

      let findString =
        this._browser.finder.searchString || this._findField.value;
      if (!findString) {
        return this.startFind();
      }

      // We dispatch the findAgain event here instead of in _findAgain since
      // if there is a find event handler that prevents the default then
      // finder.searchString will never get updated which in turn means
      // there would never be findAgain events because of the logic below.
      if (!this._dispatchFindEvent("again", findPrevious)) {
        return undefined;
      }

      // user explicitly requested another search, so do it even if we think it'll fail
      this._findFailedString = null;

      // Ensure the stored SearchString is in sync with what we want to find
      if (this._findField.value != this._browser.finder.searchString) {
        this._find(this._findField.value);
      } else {
        this._findAgain(findPrevious);
        if (this._useModalHighlight) {
          this.open();
          this._findField.focus();
        }
      }

      return undefined;
    }

    /**
     * Fetches the currently selected text and sets that as the text to search
     * next. This is a MacOS specific feature.
     */
    onFindSelectionCommand() {
      this.browser.finder.setSearchStringToSelection().then(searchInfo => {
        if (searchInfo.selectedText) {
          this._findField.value = searchInfo.selectedText;
        }
      });
    }

    _onAppActivateMac() {
      const kPref = "accessibility.typeaheadfind.prefillwithselection";
      if (this.prefillWithSelection && Services.prefs.getBoolPref(kPref)) {
        return;
      }

      let clipboardSearchString = this._browser.finder.clipboardSearchString;
      if (
        clipboardSearchString &&
        this._findField.value != clipboardSearchString &&
        !this._findField._willfullyDeleted
      ) {
        this._findField.value = clipboardSearchString;
        this._findField._hadValue = true;
        // Changing the search string makes the previous status invalid, so
        // we better clear it here.
        this._updateStatusUI();
      }
    }

    /**
     * This handles all the result changes for both type-ahead-find and
     * highlighting.
     *
     * @param {Object} data A dictionary that holds the following properties:
     *                      - {Number} result  One of the FIND_* constants
     *                                         indicating the result of a search
     *                                         operation.
     *                      - {Boolean} findBackwards If the search was done
     *                                                from the bottom to the
     *                                                top. This is used for
     *                                                status messages when
     *                                                reaching "the end of the
     *                                                page".
     *                      - {String} linkURL When a link matched, then its
     *                                         URL. Always null when not in
     *                                         FIND_LINKS mode.
     */
    onFindResult(data) {
      if (data.result == Ci.nsITypeAheadFind.FIND_NOTFOUND) {
        // If an explicit Find Again command fails, re-open the toolbar.
        if (data.storeResult && this.open()) {
          this._findField.select();
          this._findField.focus();
        }
        this._findFailedString = data.searchString;
      } else {
        this._findFailedString = null;
      }

      this._updateStatusUI(data.result, data.findBackwards);
      this._updateStatusUIBar(data.linkURL);

      if (this.findMode != this.FIND_NORMAL) {
        this._setFindCloseTimeout();
      }
    }

    /**
     * This handles all the result changes for matches counts.
     *
     * @param {Object} result Result Object, containing the total amount of
     *                 matches and a vector of the current result.
     *                 - {Number} total Total count number of matches found.
     *                 - {Number} limit Current setting of the number of matches
     *                                  to hit to hit the limit.
     *                 - {Number} current Vector of the current result.
     */
    onMatchesCountResult(result) {
      if (!result.total) {
        delete this._foundMatches.dataset.l10nId;
        this._foundMatches.hidden = true;
        this._foundMatches.setAttribute("value", "");
      } else {
        const l10nId =
          result.total === -1
            ? "findbar-found-matches-count-limit"
            : "findbar-found-matches";
        this._foundMatches.hidden = false;
        document.l10n.setAttributes(this._foundMatches, l10nId, result);
      }
    }

    onHighlightFinished() {
      // Noop.
    }

    onCurrentSelection(selectionString, isInitialSelection) {
      // Ignore the prefill if the user has already typed in the findbar,
      // it would have been overwritten anyway. See bug 1198465.
      if (isInitialSelection && !this._startFindDeferred) {
        return;
      }

      if (
        AppConstants.platform == "macosx" &&
        isInitialSelection &&
        !selectionString
      ) {
        let clipboardSearchString = this.browser.finder.clipboardSearchString;
        if (clipboardSearchString) {
          selectionString = clipboardSearchString;
        }
      }

      if (selectionString) {
        this._findField.value = selectionString;
      }

      if (isInitialSelection) {
        this._enableFindButtons(!!this._findField.value);
        this._findField.select();
        this._findField.focus();

        this._startFindDeferred.resolve();
        this._startFindDeferred = null;
      }
    }

    /**
     * This handler may cancel a request to focus content by returning |false|
     * explicitly.
     */
    shouldFocusContent() {
      const fm = Services.focus;
      if (fm.focusedWindow != window) {
        return false;
      }

      let focusedElement = fm.focusedElement;
      if (!focusedElement) {
        return false;
      }

      let focusedParent = focusedElement.closest("findbar");
      if (focusedParent != this && focusedParent != this._findField) {
        return false;
      }

      return true;
    }

    disconnectedCallback() {
      // Empty the DOM. We will rebuild if reconnected.
      while (this.lastChild) {
        this.removeChild(this.lastChild);
      }
      this.destroy();
    }
  }

  customElements.define("findbar", MozFindbar);
}
PK
!<�#A���1chrome/toolkit/content/global/elements/general.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
  class MozCommandSet extends MozXULElement {
    connectedCallback() {
      if (this.getAttribute("commandupdater") === "true") {
        const events = this.getAttribute("events") || "*";
        const targets = this.getAttribute("targets") || "*";
        document.commandDispatcher.addCommandUpdater(this, events, targets);
      }
    }

    disconnectedCallback() {
      if (this.getAttribute("commandupdater") === "true") {
        document.commandDispatcher.removeCommandUpdater(this);
      }
    }
  }

  customElements.define("commandset", MozCommandSet);
}
PK
!<;��2chrome/toolkit/content/global/elements/marquee.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

slot {
  display: block;
  will-change: translate;
}

/* Disable the animation on contenteditable */
:host(:read-write) > slot {
  translate: none !important;
}

/* When printing or when the user doesn't want movement, we disable scrolling */
@media print, (prefers-reduced-motion) {
  slot {
    translate: none !important;
  }
}
PK
!<�
�s�%�%1chrome/toolkit/content/global/elements/marquee.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.MarqueeWidget = class {
  constructor(shadowRoot) {
    this.shadowRoot = shadowRoot;
    this.element = shadowRoot.host;
    this.document = this.element.ownerDocument;
    this.window = this.document.defaultView;
    // This needed for behavior=alternate, in order to know in which of the two
    // directions we're going.
    this.dirsign = 1;
    this._currentLoop = this.element.loop;
    this.animation = null;
    this._restartScheduled = null;
  }

  onsetup() {
    // White-space isn't allowed because a marquee could be
    // inside 'white-space: pre'
    this.shadowRoot.innerHTML = `<link rel="stylesheet" href="chrome://global/content/elements/marquee.css"
      /><slot></slot>`;

    this._mutationObserver = new this.window.MutationObserver(aMutations =>
      this._mutationActor(aMutations)
    );
    this._mutationObserver.observe(this.element, {
      attributes: true,
      attributeOldValue: true,
      attributeFilter: ["loop", "direction", "behavior"],
    });

    // init needs to be run after the page has loaded in order to calculate
    // the correct height/width
    if (this.document.readyState == "complete") {
      this.init();
    } else {
      this.window.addEventListener("load", this, { once: true });
    }

    this.shadowRoot.addEventListener("marquee-start", this);
    this.shadowRoot.addEventListener("marquee-stop", this);
  }

  teardown() {
    this.doStop();
    this._mutationObserver.disconnect();

    this.window.removeEventListener("load", this);
    this.shadowRoot.removeEventListener("marquee-start", this);
    this.shadowRoot.removeEventListener("marquee-stop", this);
    this.shadowRoot.replaceChildren();
  }

  handleEvent(aEvent) {
    if (!aEvent.isTrusted) {
      return;
    }

    switch (aEvent.type) {
      case "load":
        this.init();
        break;
      case "marquee-start":
        this.doStart();
        break;
      case "marquee-stop":
        this.doStop();
        break;
      case "finish":
        this._animationFinished();
        break;
    }
  }

  _animationFinished() {
    let behavior = this.element.behavior;
    let shouldLoop =
      this._currentLoop > 1 || (this._currentLoop == -1 && behavior != "slide");
    if (shouldLoop) {
      if (this._currentLoop > 0) {
        this._currentLoop--;
      }
      if (behavior == "alternate") {
        this.dirsign = -this.dirsign;
      }
      this.doStop();
      this.doStart();
    }
  }

  get scrollDelayWithTruespeed() {
    if (this.element.scrollDelay < 60 && !this.element.trueSpeed) {
      return 60;
    }
    return this.element.scrollDelay;
  }

  get slot() {
    return this.shadowRoot.lastChild;
  }

  /**
   * Computes CSS-derived values needed to compute the transform of the
   * contents.
   *
   * In particular, it measures the auto width and height of the contents,
   * and the effective width and height of the marquee itself, along with its
   * css directionality (which affects the effective direction).
   */
  getMetrics() {
    let slot = this.slot;
    slot.style.width = "max-content";
    let slotCS = this.window.getComputedStyle(slot);
    let marqueeCS = this.window.getComputedStyle(this.element);
    let contentWidth = parseFloat(slotCS.width) || 0;
    let contentHeight = parseFloat(slotCS.height) || 0;
    let marqueeWidth = parseFloat(marqueeCS.width) || 0;
    let marqueeHeight = parseFloat(marqueeCS.height) || 0;
    slot.style.width = "";
    return {
      contentWidth,
      contentHeight,
      marqueeWidth,
      marqueeHeight,
      cssDirection: marqueeCS.direction,
    };
  }

  /**
   * Gets the layout metrics from getMetrics(), and returns an object
   * describing the start, end, and axis of the animation for the given marquee
   * behavior and direction.
   */
  getTransformParameters({
    contentWidth,
    contentHeight,
    marqueeWidth,
    marqueeHeight,
    cssDirection,
  }) {
    const innerWidth = marqueeWidth - contentWidth;
    const innerHeight = marqueeHeight - contentHeight;
    const dir = this.element.direction;

    let start = 0;
    let end = 0;
    const axis = dir == "up" || dir == "down" ? "y" : "x";
    switch (this.element.behavior) {
      case "alternate":
        switch (dir) {
          case "up":
          case "down": {
            if (innerHeight >= 0) {
              start = innerHeight;
              end = 0;
            } else {
              start = 0;
              end = innerHeight;
            }
            if (dir == "down") {
              [start, end] = [end, start];
            }
            if (this.dirsign == -1) {
              [start, end] = [end, start];
            }
            break;
          }
          case "right":
          case "left":
          default: {
            if (innerWidth >= 0) {
              start = innerWidth;
              end = 0;
            } else {
              start = 0;
              end = innerWidth;
            }
            if (dir == "right") {
              [start, end] = [end, start];
            }
            if (cssDirection == "rtl") {
              [start, end] = [end, start];
            }
            if (this.dirsign == -1) {
              [start, end] = [end, start];
            }
            break;
          }
        }
        break;
      case "slide":
        switch (dir) {
          case "up": {
            start = marqueeHeight;
            end = 0;
            break;
          }
          case "down": {
            start = -contentHeight;
            end = innerHeight;
            break;
          }
          case "right":
          default: {
            let isRight = dir == "right";
            if (cssDirection == "rtl") {
              isRight = !isRight;
            }
            if (isRight) {
              start = -contentWidth;
              end = innerWidth;
            } else {
              start = marqueeWidth;
              end = 0;
            }
            break;
          }
        }
        break;
      case "scroll":
      default:
        switch (dir) {
          case "up":
          case "down": {
            start = marqueeHeight;
            end = -contentHeight;
            if (dir == "down") {
              [start, end] = [end, start];
            }
            break;
          }
          case "right":
          case "left":
          default: {
            start = marqueeWidth;
            end = -contentWidth;
            if (dir == "right") {
              [start, end] = [end, start];
            }
            if (cssDirection == "rtl") {
              [start, end] = [end, start];
            }
            break;
          }
        }
        break;
    }
    return { start, end, axis };
  }

  /**
   * Measures the marquee contents, and starts the marquee animation if needed.
   * The translate animation is applied to the <slot> element.
   * Bouncing and looping is implemented in the finish event handler for the
   * given animation (see _animationFinished()).
   */
  doStart() {
    if (this.animation) {
      return;
    }
    let scrollAmount = this.element.scrollAmount;
    if (!scrollAmount) {
      return;
    }
    let metrics = this.getMetrics();
    let { axis, start, end } = this.getTransformParameters(metrics);
    let duration =
      (Math.abs(end - start) * this.scrollDelayWithTruespeed) / scrollAmount;
    let startValue = start + "px";
    let endValue = end + "px";
    if (axis == "y") {
      startValue = "0 " + startValue;
      endValue = "0 " + endValue;
    }
    // NOTE(emilio): It seems tempting to use `iterations` here, but doing so
    // wouldn't be great because this uses current layout values (via
    // getMetrics()), so sizes wouldn't update. This way we update once per
    // animation iteration.
    //
    // fill: forwards is needed so that behavior=slide doesn't jump back to the
    // start after the animation finishes.
    this.animation = this.slot.animate(
      {
        translate: [startValue, endValue],
      },
      {
        duration,
        easing: "linear",
        fill: "forwards",
      }
    );
    this.animation.addEventListener("finish", this, { once: true });
  }

  doStop() {
    if (!this.animation) {
      return;
    }
    if (this._restartScheduled) {
      this.window.cancelAnimationFrame(this._restartScheduled);
      this._restartScheduled = null;
    }
    this.animation.removeEventListener("finish", this);
    this.animation.cancel();
    this.animation = null;
  }

  init() {
    this.element.stop();
    this.doStart();
  }

  _mutationActor(aMutations) {
    while (aMutations.length) {
      let mutation = aMutations.shift();
      let attrName = mutation.attributeName.toLowerCase();
      let oldValue = mutation.oldValue;
      let newValue = this.element.getAttribute(attrName);
      if (oldValue == newValue) {
        continue;
      }
      if (attrName == "loop") {
        this._currentLoop = this.element.loop;
      }
      if (attrName == "direction" || attrName == "behavior") {
        this._scheduleRestartIfNeeded();
      }
    }
  }

  // Schedule a restart with the new parameters if we're running.
  _scheduleRestartIfNeeded() {
    if (!this.animation || this._restartScheduled != null) {
      return;
    }
    this._restartScheduled = this.window.requestAnimationFrame(() => {
      if (this.animation) {
        this.doStop();
        this.doStart();
      }
    });
  }
};
PK
!<_�;">">.chrome/toolkit/content/global/elements/menu.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
  let imports = {};
  ChromeUtils.defineESModuleGetters(imports, {
    ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
  });

  const MozMenuItemBaseMixin = Base => {
    class MozMenuItemBase extends MozElements.BaseTextMixin(Base) {
      // nsIDOMXULSelectControlItemElement
      set value(val) {
        this.setAttribute("value", val);
      }
      get value() {
        return this.getAttribute("value") || "";
      }

      // nsIDOMXULSelectControlItemElement
      get selected() {
        return this.getAttribute("selected") == "true";
      }

      // nsIDOMXULSelectControlItemElement
      get control() {
        var parent = this.parentNode;
        // Return the parent if it is a menu or menulist.
        if (parent && XULMenuElement.isInstance(parent.parentNode)) {
          return parent.parentNode;
        }
        return null;
      }

      // nsIDOMXULContainerItemElement
      get parentContainer() {
        for (var parent = this.parentNode; parent; parent = parent.parentNode) {
          if (XULMenuElement.isInstance(parent)) {
            return parent;
          }
        }
        return null;
      }
    }
    MozXULElement.implementCustomInterface(MozMenuItemBase, [
      Ci.nsIDOMXULSelectControlItemElement,
      Ci.nsIDOMXULContainerItemElement,
    ]);
    return MozMenuItemBase;
  };

  const MozMenuBaseMixin = Base => {
    class MozMenuBase extends MozMenuItemBaseMixin(Base) {
      set open(val) {
        this.openMenu(val);
      }

      get open() {
        return this.hasAttribute("open");
      }

      get itemCount() {
        var menupopup = this.menupopup;
        return menupopup ? menupopup.children.length : 0;
      }

      get menupopup() {
        const XUL_NS =
          "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

        for (
          var child = this.firstElementChild;
          child;
          child = child.nextElementSibling
        ) {
          if (child.namespaceURI == XUL_NS && child.localName == "menupopup") {
            return child;
          }
        }
        return null;
      }

      appendItem(aLabel, aValue) {
        var menupopup = this.menupopup;
        if (!menupopup) {
          menupopup = this.ownerDocument.createXULElement("menupopup");
          this.appendChild(menupopup);
        }

        var menuitem = this.ownerDocument.createXULElement("menuitem");
        menuitem.setAttribute("label", aLabel);
        menuitem.setAttribute("value", aValue);

        return menupopup.appendChild(menuitem);
      }

      getIndexOfItem(aItem) {
        var menupopup = this.menupopup;
        if (menupopup) {
          var items = menupopup.children;
          var length = items.length;
          for (var index = 0; index < length; ++index) {
            if (items[index] == aItem) {
              return index;
            }
          }
        }
        return -1;
      }

      getItemAtIndex(aIndex) {
        var menupopup = this.menupopup;
        if (!menupopup || aIndex < 0 || aIndex >= menupopup.children.length) {
          return null;
        }

        return menupopup.children[aIndex];
      }
    }
    MozXULElement.implementCustomInterface(MozMenuBase, [
      Ci.nsIDOMXULContainerElement,
    ]);
    return MozMenuBase;
  };

  // The <menucaption> element is used for rendering <html:optgroup> inside of <html:select>,
  // See SelectParentHelper.sys.mjs.
  class MozMenuCaption extends MozMenuBaseMixin(MozXULElement) {
    static get inheritedAttributes() {
      return {
        ".menu-iconic-left": "selected,disabled,checked",
        ".menu-iconic-icon": "src=image,validate,src",
        ".menu-iconic-text": "value=label,crop,highlightable",
        ".menu-iconic-highlightable-text": "text=label,crop,highlightable",
      };
    }

    connectedCallback() {
      this.textContent = "";
      this.appendChild(
        MozXULElement.parseXULToFragment(`
      <hbox class="menu-iconic-left" align="center" pack="center" aria-hidden="true">
        <image class="menu-iconic-icon" aria-hidden="true"></image>
      </hbox>
      <label class="menu-iconic-text" flex="1" crop="end" aria-hidden="true"></label>
      <label class="menu-iconic-highlightable-text" crop="end" aria-hidden="true"></label>
    `)
      );
      this.initializeAttributeInheritance();
    }
  }

  customElements.define("menucaption", MozMenuCaption);

  // In general, wait to render menus and menuitems inside menupopups
  // until they are going to be visible:
  window.addEventListener(
    "popupshowing",
    e => {
      if (e.originalTarget.ownerDocument != document) {
        return;
      }
      e.originalTarget.setAttribute("hasbeenopened", "true");
      for (let el of e.originalTarget.querySelectorAll("menuitem, menu")) {
        el.render();
      }
    },
    { capture: true }
  );

  class MozMenuItem extends MozMenuItemBaseMixin(MozXULElement) {
    static get observedAttributes() {
      return super.observedAttributes.concat("acceltext", "key");
    }

    attributeChangedCallback(name, oldValue, newValue) {
      if (name == "acceltext") {
        if (this._ignoreAccelTextChange) {
          this._ignoreAccelTextChange = false;
        } else {
          this._accelTextIsDerived = false;
          this._computeAccelTextFromKeyIfNeeded();
        }
      }
      if (name == "key") {
        this._computeAccelTextFromKeyIfNeeded();
      }
      super.attributeChangedCallback(name, oldValue, newValue);
    }

    static get inheritedAttributes() {
      return {
        ".menu-iconic-text": "value=label,crop,accesskey,highlightable",
        ".menu-text": "value=label,crop,accesskey,highlightable",
        ".menu-iconic-highlightable-text":
          "text=label,crop,accesskey,highlightable",
        ".menu-iconic-left": "selected,_moz-menuactive,disabled,checked",
        ".menu-iconic-icon":
          "src=image,validate,triggeringprincipal=iconloadingprincipal",
        ".menu-iconic-accel": "value=acceltext",
        ".menu-accel": "value=acceltext",
      };
    }

    static get iconicNoAccelFragment() {
      // Add aria-hidden="true" on all DOM, since XULMenuAccessible handles accessibility here.
      let frag = document.importNode(
        MozXULElement.parseXULToFragment(`
      <hbox class="menu-iconic-left" align="center" pack="center" aria-hidden="true">
        <image class="menu-iconic-icon"/>
      </hbox>
      <label class="menu-iconic-text" flex="1" crop="end" aria-hidden="true"/>
      <label class="menu-iconic-highlightable-text" crop="end" aria-hidden="true"/>
    `),
        true
      );
      Object.defineProperty(this, "iconicNoAccelFragment", { value: frag });
      return frag;
    }

    static get iconicFragment() {
      let frag = document.importNode(
        MozXULElement.parseXULToFragment(`
      <hbox class="menu-iconic-left" align="center" pack="center" aria-hidden="true">
        <image class="menu-iconic-icon"/>
      </hbox>
      <label class="menu-iconic-text" flex="1" crop="end" aria-hidden="true"/>
      <label class="menu-iconic-highlightable-text" crop="end" aria-hidden="true"/>
      <hbox class="menu-accel-container" aria-hidden="true">
        <label class="menu-iconic-accel"/>
      </hbox>
    `),
        true
      );
      Object.defineProperty(this, "iconicFragment", { value: frag });
      return frag;
    }

    static get plainFragment() {
      let frag = document.importNode(
        MozXULElement.parseXULToFragment(`
      <label class="menu-text" crop="end" aria-hidden="true"/>
      <hbox class="menu-accel-container" aria-hidden="true">
        <label class="menu-accel"/>
      </hbox>
    `),
        true
      );
      Object.defineProperty(this, "plainFragment", { value: frag });
      return frag;
    }

    get isIconic() {
      let type = this.getAttribute("type");
      return (
        type == "checkbox" ||
        type == "radio" ||
        this.classList.contains("menuitem-iconic")
      );
    }

    get isMenulistChild() {
      return this.matches("menulist > menupopup > menuitem");
    }

    get isInHiddenMenupopup() {
      return this.matches("menupopup:not([hasbeenopened]) menuitem");
    }

    _computeAccelTextFromKeyIfNeeded() {
      if (!this._accelTextIsDerived && this.getAttribute("acceltext")) {
        return;
      }
      let accelText = (() => {
        if (!document.contains(this)) {
          return null;
        }
        let keyId = this.getAttribute("key");
        if (!keyId) {
          return null;
        }
        let key = document.getElementById(keyId);
        if (!key) {
          let msg =
            `Key ${keyId} of menuitem ${this.getAttribute("label")} ` +
            `could not be found`;
          if (keyId.startsWith("ext-key-id-")) {
            console.info(msg);
          } else {
            console.error(msg);
          }
          return null;
        }
        return imports.ShortcutUtils.prettifyShortcut(key);
      })();

      this._accelTextIsDerived = true;
      // We need to ignore the next attribute change callback for acceltext, in
      // order to not reenter here.
      this._ignoreAccelTextChange = true;
      if (accelText) {
        this.setAttribute("acceltext", accelText);
      } else {
        this.removeAttribute("acceltext");
      }
    }

    render() {
      if (this.renderedOnce) {
        return;
      }
      this.renderedOnce = true;
      this.textContent = "";
      if (this.isMenulistChild) {
        this.append(this.constructor.iconicNoAccelFragment.cloneNode(true));
      } else if (this.isIconic) {
        this.append(this.constructor.iconicFragment.cloneNode(true));
      } else {
        this.append(this.constructor.plainFragment.cloneNode(true));
      }

      this._computeAccelTextFromKeyIfNeeded();
      this.initializeAttributeInheritance();
    }

    connectedCallback() {
      if (this.renderedOnce) {
        this._computeAccelTextFromKeyIfNeeded();
      }
      // Eagerly render if we are being inserted into a menulist (since we likely need to
      // size it), or into an already-opened menupopup (since we are already visible).
      // Checking isConnectedAndReady is an optimization that will let us quickly skip
      // non-menulists that are being connected during parse.
      if (
        this.isMenulistChild ||
        (this.isConnectedAndReady && !this.isInHiddenMenupopup)
      ) {
        this.render();
      }
    }
  }

  customElements.define("menuitem", MozMenuItem);

  const isHiddenWindow =
    document.documentURI == "chrome://browser/content/hiddenWindowMac.xhtml";

  class MozMenu extends MozMenuBaseMixin(
    MozElements.MozElementMixin(XULMenuElement)
  ) {
    static get inheritedAttributes() {
      return {
        ".menubar-text": "value=label,accesskey,crop",
        ".menu-iconic-text": "value=label,accesskey,crop,highlightable",
        ".menu-text": "value=label,accesskey,crop",
        ".menu-iconic-highlightable-text":
          "text=label,crop,accesskey,highlightable",
        ".menubar-left": "src=image",
        ".menu-iconic-icon":
          "src=image,triggeringprincipal=iconloadingprincipal,validate",
        ".menu-iconic-accel": "value=acceltext",
        ".menu-right": "_moz-menuactive,disabled",
        ".menu-accel": "value=acceltext",
      };
    }

    get needsEagerRender() {
      return (
        this.isMenubarChild || this.isMenulistChild || !this.isInHiddenMenupopup
      );
    }

    get isMenubarChild() {
      return this.matches("menubar > menu");
    }

    get isMenulistChild() {
      return this.matches("menulist > menupopup > menu");
    }

    get isInHiddenMenupopup() {
      return this.matches("menupopup:not([hasbeenopened]) menu");
    }

    get isIconic() {
      return this.classList.contains("menu-iconic");
    }

    get fragment() {
      let { isMenubarChild, isIconic } = this;
      let fragment = null;
      // Add aria-hidden="true" on all DOM, since XULMenuAccessible handles accessibility here.
      if (isMenubarChild && isIconic) {
        if (!MozMenu.menubarIconicFrag) {
          MozMenu.menubarIconicFrag = MozXULElement.parseXULToFragment(`
          <image class="menubar-left" aria-hidden="true"/>
          <label class="menubar-text" crop="end" aria-hidden="true"/>
        `);
        }
        fragment = document.importNode(MozMenu.menubarIconicFrag, true);
      }
      if (isMenubarChild && !isIconic) {
        if (!MozMenu.menubarFrag) {
          MozMenu.menubarFrag = MozXULElement.parseXULToFragment(`
          <label class="menubar-text" crop="end" aria-hidden="true"/>
        `);
        }
        fragment = document.importNode(MozMenu.menubarFrag, true);
      }
      if (!isMenubarChild && isIconic) {
        if (!MozMenu.normalIconicFrag) {
          MozMenu.normalIconicFrag = MozXULElement.parseXULToFragment(`
          <hbox class="menu-iconic-left" align="center" pack="center" aria-hidden="true">
            <image class="menu-iconic-icon"/>
          </hbox>
          <label class="menu-iconic-text" flex="1" crop="end" aria-hidden="true"/>
          <label class="menu-iconic-highlightable-text" crop="end" aria-hidden="true"/>
          <hbox class="menu-accel-container" anonid="accel" aria-hidden="true">
            <label class="menu-iconic-accel"/>
          </hbox>
          <hbox align="center" class="menu-right" aria-hidden="true">
            <image/>
          </hbox>
       `);
        }

        fragment = document.importNode(MozMenu.normalIconicFrag, true);
      }
      if (!isMenubarChild && !isIconic) {
        if (!MozMenu.normalFrag) {
          MozMenu.normalFrag = MozXULElement.parseXULToFragment(`
          <label class="menu-text" crop="end" aria-hidden="true"/>
          <hbox class="menu-accel-container" anonid="accel" aria-hidden="true">
            <label class="menu-accel"/>
          </hbox>
          <hbox align="center" class="menu-right" aria-hidden="true">
            <image/>
          </hbox>
       `);
        }

        fragment = document.importNode(MozMenu.normalFrag, true);
      }
      return fragment;
    }

    render() {
      // There are 2 main types of menus:
      //  (1) direct descendant of a menubar
      //  (2) all other menus
      // There is also an "iconic" variation of (1) and (2) based on the class.
      // To make this as simple as possible, we don't support menus being changed from one
      // of these types to another after the initial DOM connection. It'd be possible to make
      // this work by keeping track of the markup we prepend and then removing / re-prepending
      // during a change, but it's not a feature we use anywhere currently.
      if (this.renderedOnce) {
        return;
      }
      this.renderedOnce = true;

      // There will be a <menupopup /> already. Don't clear it out, just put our markup before it.
      this.prepend(this.fragment);
      this.initializeAttributeInheritance();
    }

    connectedCallback() {
      // On OSX we will have a bunch of menus in the hidden window. They get converted
      // into native menus based on the host attributes, so the inner DOM doesn't need
      // to be created.
      if (isHiddenWindow) {
        return;
      }

      if (this.delayConnectedCallback()) {
        return;
      }

      // Wait until we are going to be visible or required for sizing a popup.
      if (!this.needsEagerRender) {
        return;
      }

      this.render();
    }
  }

  customElements.define("menu", MozMenu);
}
PK
!<b�,�,2chrome/toolkit/content/global/elements/menulist.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
  const { AppConstants } = ChromeUtils.importESModule(
    "resource://gre/modules/AppConstants.sys.mjs"
  );
  const MozXULMenuElement = MozElements.MozElementMixin(XULMenuElement);
  const MenuBaseControl = MozElements.BaseControlMixin(MozXULMenuElement);

  class MozMenuList extends MenuBaseControl {
    constructor() {
      super();

      this.addEventListener(
        "command",
        event => {
          if (event.target.parentNode.parentNode == this) {
            this.selectedItem = event.target;
          }
        },
        true
      );

      this.addEventListener("popupshowing", event => {
        if (event.target.parentNode == this) {
          this.activeChild = null;
          if (this.selectedItem) {
            // Not ready for auto-setting the active child in hierarchies yet.
            // For now, only do this when the outermost menupopup opens.
            this.activeChild = this.mSelectedInternal;
          }
        }
      });

      this.addEventListener(
        "keypress",
        event => {
          if (
            event.defaultPrevented ||
            event.altKey ||
            event.ctrlKey ||
            event.metaKey
          ) {
            return;
          }

          if (
            AppConstants.platform === "macosx" &&
            !this.open &&
            (event.keyCode == KeyEvent.DOM_VK_UP ||
              event.keyCode == KeyEvent.DOM_VK_DOWN)
          ) {
            // This should open the menulist on macOS, see
            // XULButtonElement::PostHandleEvent.
            return;
          }

          if (
            event.keyCode == KeyEvent.DOM_VK_UP ||
            event.keyCode == KeyEvent.DOM_VK_DOWN ||
            event.keyCode == KeyEvent.DOM_VK_PAGE_UP ||
            event.keyCode == KeyEvent.DOM_VK_PAGE_DOWN ||
            event.keyCode == KeyEvent.DOM_VK_HOME ||
            event.keyCode == KeyEvent.DOM_VK_END ||
            event.keyCode == KeyEvent.DOM_VK_BACK_SPACE ||
            event.charCode > 0
          ) {
            // Moving relative to an item: start from the currently selected item
            this.activeChild = this.mSelectedInternal;
            if (this.handleKeyPress(event)) {
              this.activeChild.doCommand();
              event.preventDefault();
            }
          }
        },
        { mozSystemGroup: true }
      );

      this.attachShadow({ mode: "open" });
    }

    static get inheritedAttributes() {
      return {
        image: "src=image",
        "#label": "value=label,crop,accesskey",
        "#highlightable-label": "text=label,crop,accesskey",
        dropmarker: "disabled,open",
      };
    }

    static get markup() {
      // Accessibility information of these nodes will be presented
      // on XULComboboxAccessible generated from <menulist>;
      // hide these nodes from the accessibility tree.
      return `
        <html:link href="chrome://global/skin/menulist.css" rel="stylesheet"/>
        <hbox id="label-box" part="label-box" flex="1" role="none">
          <image part="icon" role="none"/>
          <label id="label" part="label" crop="end" flex="1" role="none"/>
          <label id="highlightable-label" part="label" crop="end" flex="1" role="none"/>
        </hbox>
        <dropmarker part="dropmarker" type="menu" role="none"/>
        <html:slot/>
    `;
    }

    connectedCallback() {
      if (this.delayConnectedCallback()) {
        return;
      }

      // Append and track children so they can be removed in disconnectedCallback.
      if (!this.hasAttribute("popuponly")) {
        this.shadowRoot.appendChild(this.constructor.fragment);
      } else {
        this.shadowRoot.appendChild(document.createElement("slot"));
      }

      this._managedNodes = [];
      if (this.shadowRoot.children) {
        let childElements = Array.from(this.shadowRoot.children);
        childElements.forEach(child => {
          this._managedNodes.push(child);
        });
      }

      if (!this.hasAttribute("popuponly")) {
        this.initializeAttributeInheritance();
      }

      this.setInitialSelection();
    }

    // nsIDOMXULSelectControlElement
    set value(val) {
      // if the new value is null, we still need to remove the old value
      if (val == null) {
        this.selectedItem = val;
        return;
      }

      var arr = null;
      var popup = this.menupopup;
      if (popup) {
        arr = popup.getElementsByAttribute("value", val);
      }

      if (arr && arr.item(0)) {
        this.selectedItem = arr[0];
      } else {
        this.selectedItem = null;
        this.setAttribute("value", val);
      }
    }

    // nsIDOMXULSelectControlElement
    get value() {
      return this.getAttribute("value") || "";
    }

    // nsIDOMXULMenuListElement
    set image(val) {
      this.setAttribute("image", val);
    }

    // nsIDOMXULMenuListElement
    get image() {
      return this.getAttribute("image") || "";
    }

    // nsIDOMXULMenuListElement
    get label() {
      return this.getAttribute("label") || "";
    }

    set description(val) {
      this.setAttribute("description", val);
    }

    get description() {
      return this.getAttribute("description") || "";
    }

    // nsIDOMXULMenuListElement
    set open(val) {
      this.openMenu(val);
    }

    // nsIDOMXULMenuListElement
    get open() {
      return this.hasAttribute("open");
    }

    // nsIDOMXULSelectControlElement
    get itemCount() {
      return this.menupopup ? this.menupopup.children.length : 0;
    }

    get menupopup() {
      var popup = this.firstElementChild;
      while (popup && popup.localName != "menupopup") {
        popup = popup.nextElementSibling;
      }
      return popup;
    }

    // nsIDOMXULSelectControlElement
    set selectedIndex(val) {
      var popup = this.menupopup;
      if (popup && 0 <= val) {
        if (val < popup.children.length) {
          this.selectedItem = popup.children[val];
        }
      } else {
        this.selectedItem = null;
      }
    }

    // nsIDOMXULSelectControlElement
    get selectedIndex() {
      // Quick and dirty. We won't deal with hierarchical menulists yet.
      if (
        !this.selectedItem ||
        !this.mSelectedInternal.parentNode ||
        this.mSelectedInternal.parentNode.parentNode != this
      ) {
        return -1;
      }

      var children = this.mSelectedInternal.parentNode.children;
      var i = children.length;
      while (i--) {
        if (children[i] == this.mSelectedInternal) {
          break;
        }
      }

      return i;
    }

    // nsIDOMXULSelectControlElement
    set selectedItem(val) {
      var oldval = this.mSelectedInternal;
      if (oldval == val) {
        return;
      }

      if (val && !this.contains(val)) {
        return;
      }

      if (oldval) {
        oldval.removeAttribute("selected");
        this.mAttributeObserver.disconnect();
      }

      this.mSelectedInternal = val;
      let attributeFilter = ["value", "label", "image", "description"];
      if (val) {
        val.setAttribute("selected", "true");
        for (let attr of attributeFilter) {
          if (val.hasAttribute(attr)) {
            this.setAttribute(attr, val.getAttribute(attr));
          } else {
            this.removeAttribute(attr);
          }
        }

        this.mAttributeObserver = new MutationObserver(
          this.handleMutation.bind(this)
        );
        this.mAttributeObserver.observe(val, { attributeFilter });
      } else {
        for (let attr of attributeFilter) {
          this.removeAttribute(attr);
        }
      }

      var event = document.createEvent("Events");
      event.initEvent("select", true, true);
      this.dispatchEvent(event);

      event = document.createEvent("Events");
      event.initEvent("ValueChange", true, true);
      this.dispatchEvent(event);
    }

    // nsIDOMXULSelectControlElement
    get selectedItem() {
      return this.mSelectedInternal;
    }

    setInitialSelection() {
      if (this.getAttribute("noinitialselection") === "true") {
        return;
      }

      this.mSelectedInternal = null;
      this.mAttributeObserver = null;

      var popup = this.menupopup;
      if (popup) {
        var arr = popup.getElementsByAttribute("selected", "true");

        var editable = this.editable;
        var value = this.value;
        if (!arr.item(0) && value) {
          arr = popup.getElementsByAttribute(
            editable ? "label" : "value",
            value
          );
        }

        if (arr.item(0)) {
          this.selectedItem = arr[0];
        } else if (!editable) {
          this.selectedIndex = 0;
        }
      }
    }

    contains(item) {
      if (!item) {
        return false;
      }

      var parent = item.parentNode;
      return parent && parent.parentNode == this;
    }

    handleMutation(aRecords) {
      for (let record of aRecords) {
        let t = record.target;
        if (t == this.mSelectedInternal) {
          let attrName = record.attributeName;
          switch (attrName) {
            case "value":
            case "label":
            case "image":
            case "description":
              if (t.hasAttribute(attrName)) {
                this.setAttribute(attrName, t.getAttribute(attrName));
              } else {
                this.removeAttribute(attrName);
              }
          }
        }
      }
    }

    // nsIDOMXULSelectControlElement
    getIndexOfItem(item) {
      var popup = this.menupopup;
      if (popup) {
        var children = popup.children;
        var i = children.length;
        while (i--) {
          if (children[i] == item) {
            return i;
          }
        }
      }
      return -1;
    }

    // nsIDOMXULSelectControlElement
    getItemAtIndex(index) {
      var popup = this.menupopup;
      if (popup) {
        var children = popup.children;
        if (index >= 0 && index < children.length) {
          return children[index];
        }
      }
      return null;
    }

    appendItem(label, value, description) {
      if (!this.menupopup) {
        this.appendChild(MozXULElement.parseXULToFragment(`<menupopup />`));
      }

      var popup = this.menupopup;
      popup.appendChild(MozXULElement.parseXULToFragment(`<menuitem />`));

      var item = popup.lastElementChild;
      if (label !== undefined) {
        item.setAttribute("label", label);
      }
      item.setAttribute("value", value);
      if (description) {
        item.setAttribute("description", description);
      }

      return item;
    }

    removeAllItems() {
      this.selectedItem = null;
      var popup = this.menupopup;
      if (popup) {
        this.removeChild(popup);
      }
    }

    disconnectedCallback() {
      if (this.mAttributeObserver) {
        this.mAttributeObserver.disconnect();
      }

      if (this._managedNodes) {
        this._managedNodes.forEach(node => node.remove());
        this._managedNodes = null;
      }
    }
  }

  MenuBaseControl.implementCustomInterface(MozMenuList, [
    Ci.nsIDOMXULMenuListElement,
    Ci.nsIDOMXULSelectControlElement,
  ]);

  customElements.define("menulist", MozMenuList);
}
PK
!<"�hz�$�$3chrome/toolkit/content/global/elements/menupopup.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
  const { AppConstants } = ChromeUtils.importESModule(
    "resource://gre/modules/AppConstants.sys.mjs"
  );

  document.addEventListener(
    "popupshowing",
    function (e) {
      // For the non-native context menu styling, we need to know if we need
      // a gutter for checkboxes. To do this, check whether there are any
      // radio/checkbox type menuitems in a menupopup when showing it.
      if (e.target.nodeName == "menupopup") {
        let haveCheckableChild = e.target.querySelector(
          `:scope > menuitem:not([hidden]):is(${
            // On macOS, selected menuitems are checked regardless of type
            AppConstants.platform == "macosx"
              ? "[checked=true],[selected=true]"
              : "[type=checkbox],[type=radio]"
          })`
        );
        e.target.toggleAttribute("needsgutter", haveCheckableChild);
      }
    },
    // we use a system bubbling event listener to ensure we run *after* the
    // "normal" popupshowing listeners, so (visibility) changes they make to
    // their items take effect first, before we check for checkable menuitems.
    { mozSystemGroup: true }
  );

  class MozMenuPopup extends MozElements.MozElementMixin(XULPopupElement) {
    constructor() {
      super();

      this.AUTOSCROLL_INTERVAL = 25;
      this.NOT_DRAGGING = 0;
      this.DRAG_OVER_BUTTON = -1;
      this.DRAG_OVER_POPUP = 1;
      this._draggingState = this.NOT_DRAGGING;
      this._scrollTimer = 0;

      this.attachShadow({ mode: "open" });

      this.addEventListener("popupshowing", event => {
        if (event.target != this) {
          return;
        }

        // Make sure we generated shadow DOM to place menuitems into.
        this.ensureInitialized();
      });

      this.addEventListener("DOMMenuItemActive", this);
    }

    connectedCallback() {
      if (this.delayConnectedCallback() || this.hasConnected) {
        return;
      }

      this.hasConnected = true;
      if (this.parentNode?.localName == "menulist") {
        this._setUpMenulistPopup();
      }
    }

    initShadowDOM() {
      // Retarget events from shadow DOM arrowscrollbox to the host.
      this.scrollBox.addEventListener("scroll", () =>
        this.dispatchEvent(new Event("scroll"))
      );
      this.scrollBox.addEventListener("overflow", () =>
        this.dispatchEvent(new Event("overflow"))
      );
      this.scrollBox.addEventListener("underflow", () =>
        this.dispatchEvent(new Event("underflow"))
      );
    }

    ensureInitialized() {
      this.shadowRoot;
    }

    get shadowRoot() {
      if (!super.shadowRoot.firstChild) {
        // We generate shadow DOM lazily on popupshowing event to avoid extra
        // load on the system during browser startup.
        super.shadowRoot.appendChild(this.fragment);
        this.initShadowDOM();
      }
      return super.shadowRoot;
    }

    get fragment() {
      if (!this.constructor.hasOwnProperty("_fragment")) {
        this.constructor._fragment = MozXULElement.parseXULToFragment(
          this.markup
        );
      }
      return document.importNode(this.constructor._fragment, true);
    }

    get markup() {
      return `
        <html:link rel="stylesheet" href="chrome://global/skin/global.css"/>
        <html:style>${this.styles}</html:style>
        <arrowscrollbox class="menupopup-arrowscrollbox"
                        part="arrowscrollbox content"
                        exportparts="scrollbox: arrowscrollbox-scrollbox"
                        flex="1"
                        orient="vertical"
                        smoothscroll="false">
          <html:slot></html:slot>
        </arrowscrollbox>
      `;
    }

    get styles() {
      return `
        :host(.in-menulist) arrowscrollbox::part(scrollbutton-up),
        :host(.in-menulist) arrowscrollbox::part(scrollbutton-down) {
          display: none;
        }
        :host(.in-menulist) arrowscrollbox::part(scrollbox) {
          overflow: auto;
          margin: 0;
          padding: 0;
        }
      `;
    }

    get scrollBox() {
      if (!this._scrollBox) {
        this._scrollBox = this.shadowRoot.querySelector("arrowscrollbox");
      }
      return this._scrollBox;
    }

    /**
     * Adds event listeners for a MozMenuPopup inside a menulist element.
     */
    _setUpMenulistPopup() {
      // Access shadow root to generate menupoup shadow DOMs. We do generate
      // shadow DOM on popupshowing, but it doesn't work for HTML:selects,
      // which are implemented via menulist elements living in the main process.
      // So make them a special case then.
      this.ensureInitialized();
      this.classList.add("in-menulist");

      this.addEventListener("popupshown", () => {
        // Enable drag scrolling even when the mouse wasn't used. The
        // mousemove handler will remove it if the mouse isn't down.
        this._enableDragScrolling(false);
      });

      this.addEventListener("popuphidden", () => {
        this._draggingState = this.NOT_DRAGGING;
        this._clearScrollTimer();
        this.releaseCapture();
        this.scrollBox.scrollbox.scrollTop = 0;
      });

      this.addEventListener("mousedown", event => {
        if (event.button != 0) {
          return;
        }

        if (
          this.state == "open" &&
          (event.target.localName == "menuitem" ||
            event.target.localName == "menu" ||
            event.target.localName == "menucaption")
        ) {
          this._enableDragScrolling(true);
        }
      });

      this.addEventListener("mouseup", event => {
        if (event.button != 0) {
          return;
        }

        this._draggingState = this.NOT_DRAGGING;
        this._clearScrollTimer();
      });

      this.addEventListener("mousemove", event => {
        if (!this._draggingState) {
          return;
        }

        this._clearScrollTimer();

        // If the user released the mouse before the menupopup opens, we will
        // still be capturing, so check that the button is still pressed. If
        // not, release the capture and do nothing else. This also handles if
        // the dropdown was opened via the keyboard.
        if (!(event.buttons & 1)) {
          this._draggingState = this.NOT_DRAGGING;
          this.releaseCapture();
          return;
        }

        // If dragging outside the top or bottom edge of the menupopup, but
        // within the menupopup area horizontally, scroll the list in that
        // direction. The _draggingState flag is used to ensure that scrolling
        // does not start until the mouse has moved over the menupopup first,
        // preventing scrolling while over the dropdown button.
        let popupRect = this.getOuterScreenRect();
        if (
          event.screenX >= popupRect.left &&
          event.screenX <= popupRect.right
        ) {
          if (this._draggingState == this.DRAG_OVER_BUTTON) {
            if (
              event.screenY > popupRect.top &&
              event.screenY < popupRect.bottom
            ) {
              this._draggingState = this.DRAG_OVER_POPUP;
            }
          }

          if (
            this._draggingState == this.DRAG_OVER_POPUP &&
            (event.screenY <= popupRect.top ||
              event.screenY >= popupRect.bottom)
          ) {
            let scrollAmount = event.screenY <= popupRect.top ? -1 : 1;
            this.scrollBox.scrollByIndex(scrollAmount, true);

            let win = this.ownerGlobal;
            this._scrollTimer = win.setInterval(() => {
              this.scrollBox.scrollByIndex(scrollAmount, true);
            }, this.AUTOSCROLL_INTERVAL);
          }
        }
      });

      this._menulistPopupIsSetUp = true;
    }

    _enableDragScrolling(overItem) {
      if (!this._draggingState) {
        this.setCaptureAlways();
        this._draggingState = overItem
          ? this.DRAG_OVER_POPUP
          : this.DRAG_OVER_BUTTON;
      }
    }

    _clearScrollTimer() {
      if (this._scrollTimer) {
        this.ownerGlobal.clearInterval(this._scrollTimer);
        this._scrollTimer = 0;
      }
    }

    on_DOMMenuItemActive(event) {
      // Scroll buttons may overlap the active item. In that case, scroll
      // further to stay clear of the buttons.
      if (
        this.parentNode?.localName == "menulist" ||
        !this.scrollBox.hasAttribute("overflowing")
      ) {
        return;
      }
      let item = event.target;
      if (item.parentNode != this) {
        return;
      }
      let itemRect = item.getBoundingClientRect();
      let buttonRect = this.scrollBox._scrollButtonUp.getBoundingClientRect();
      if (buttonRect.bottom > itemRect.top) {
        this.scrollBox.scrollByPixels(itemRect.top - buttonRect.bottom, true);
      } else {
        buttonRect = this.scrollBox._scrollButtonDown.getBoundingClientRect();
        if (buttonRect.top < itemRect.bottom) {
          this.scrollBox.scrollByPixels(itemRect.bottom - buttonRect.top, true);
        }
      }
    }
  }

  customElements.define("menupopup", MozMenuPopup);

  MozElements.MozMenuPopup = MozMenuPopup;
}
PK
!<�Uң��;chrome/toolkit/content/global/elements/moz-button-group.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { html } from "chrome://global/content/vendor/lit.all.mjs";
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";

export const PLATFORM_LINUX = "linux";
export const PLATFORM_MACOS = "macosx";
export const PLATFORM_WINDOWS = "win";

/**
 * A grouping of buttons. Primary button order will be set automatically based
 * on class="primary", type="submit" or autofocus attribute. Set slot="primary"
 * on a primary button that does not have primary styling to set its position.
 *
 * @tagname moz-button-group
 * @property {string} platform - The detected platform, set automatically.
 */
export default class MozButtonGroup extends MozLitElement {
  static queries = {
    defaultSlotEl: "slot:not([name])",
    primarySlotEl: "slot[name=primary]",
  };

  static properties = {
    platform: { state: true },
  };

  constructor() {
    super();
    this.#detectPlatform();
  }

  #detectPlatform() {
    if (typeof AppConstants !== "undefined") {
      this.platform = AppConstants.platform;
    } else if (navigator.platform.includes("Linux")) {
      this.platform = PLATFORM_LINUX;
    } else if (navigator.platform.includes("Mac")) {
      this.platform = PLATFORM_MACOS;
    } else {
      this.platform = PLATFORM_WINDOWS;
    }
  }

  onSlotchange() {
    for (let child of this.defaultSlotEl.assignedNodes()) {
      if (!(child instanceof Element)) {
        // Text nodes won't support classList or getAttribute.
        continue;
      }
      switch (child.localName) {
        case "button":
          if (
            child.classList.contains("primary") ||
            child.getAttribute("type") == "submit" ||
            child.hasAttribute("autofocus") ||
            child.hasAttribute("default")
          ) {
            child.slot = "primary";
          }
          break;
        case "moz-button":
          if (child.type == "primary" || child.type == "destructive") {
            child.slot = "primary";
          }
          break;
      }
    }
    this.#reorderLightDom();
  }

  #reorderLightDom() {
    let primarySlottedChildren = [...this.primarySlotEl.assignedNodes()];
    if (this.platform == PLATFORM_WINDOWS) {
      primarySlottedChildren.reverse();
      for (let child of primarySlottedChildren) {
        child.parentElement.prepend(child);
      }
    } else {
      for (let child of primarySlottedChildren) {
        // Ensure the primary buttons are at the end of the light DOM.
        child.parentElement.append(child);
      }
    }
  }

  updated(changedProperties) {
    if (changedProperties.has("platform")) {
      this.#reorderLightDom();
    }
  }

  render() {
    let slots = [
      html` <slot @slotchange=${this.onSlotchange}></slot> `,
      html` <slot name="primary"></slot> `,
    ];
    if (this.platform == PLATFORM_WINDOWS) {
      slots = [slots[1], slots[0]];
    }
    return html`
      <link
        rel="stylesheet"
        href="chrome://global/content/elements/moz-button-group.css"
      />
      ${slots}
    `;
  }
}
customElements.define("moz-button-group", MozButtonGroup);
PK
!<
�]���3chrome/toolkit/content/global/elements/moz-card.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

 :host {
  --card-border-color: color-mix(in srgb, currentColor 10%, transparent);
  --card-border-radius: var(--border-radius-medium);
  --card-border-width: var(--border-width);
  --card-border: var(--card-border-width) solid var(--card-border-color);
  --card-background-color: var(--background-color-box);
  --card-focus-outline: var(--focus-outline);
  --card-box-shadow: var(--box-shadow-10);
  --card-padding: var(--space-large);
  --card-gap: var(--card-padding);
  --card-article-gap: var(--space-small);

  @media (prefers-contrast) {
    --card-border-color: color-mix(in srgb, currentColor 41%, transparent);
  }
}

:host {
  display: block;
  border: var(--card-border);
  border-radius: var(--card-border-radius);
  background-color: var(--card-background-color);
  box-shadow: var(--card-box-shadow);
  box-sizing: border-box;
}

:host([type=accordion]) {
  summary {
    padding-block: var(--card-padding);
  }
  #content {
    padding-block-end: var(--card-padding);
  }
}
:host(:not([type=accordion])) {
  .moz-card {
    padding-block: var(--card-padding);
  }
}

.moz-card {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--card-article-gap);
}

#moz-card-details {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--card-article-gap);
}

summary {
  cursor: pointer;
}

#heading-wrapper {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--card-gap);
  padding-inline: var(--card-padding);
  border-radius: var(--card-border-radius);

  .chevron-icon {
    background-image: url("chrome://global/skin/icons/arrow-down.svg");

    details[open] & {
      background-image: url("chrome://global/skin/icons/arrow-up.svg");
    }
  }

  .chevron-icon,
  #heading-icon {
    background-position: center;
    background-repeat: no-repeat;
    -moz-context-properties: fill;
    fill: currentColor;
    width: 24px;
    height: 24px;
    min-width: 24px;
    min-height: 24px;
    padding: 0;
    flex-shrink: 0;
    align-self: flex-start;
  }
}

#heading {
  font-size: var(--font-size-root);
  font-weight: var(--font-weight-bold);
}

#content {
  align-self: stretch;
  padding-inline: var(--card-padding);
  border-end-start-radius: var(--card-border-radius);
  border-end-end-radius: var(--card-border-radius);

  @media (prefers-contrast) {
    :host([type=accordion]) & {
      border-block-start: 0;
      padding-block-start: var(--card-padding);
    }
  }
}

details  {
  > summary {
    list-style: none;
    border-radius: var(--card-border-radius);
    cursor: pointer;

    &:hover {
      background-color: var(--button-background-color-hover);
    }
    @media (prefers-contrast) {
      outline: var(--button-border-color) solid var(--border-width);

      &:hover {
        outline-color: var(--button-border-color-hover);
      }

      &:active {
        outline-color: var(--button-border-color-active);
      }
    }

    @media (forced-colors) {
      color: var(--button-text-color);
      background-color: var(--button-background-color);

      &:hover {
        background-color: var(--button-background-color-hover);
        color: var(--button-text-color-hover);
      }

      &:active {
        background-color: var(--button-background-color-active);
        color: var(--button-text-color-active);
      }
    }
  }

  &[open] {
    summary {
      border-end-start-radius: 0;
      border-end-end-radius: 0;
    }
    @media not (prefers-contrast) {
      #content {
        /*
        There is a border shown above this element in prefers-contrast.
        When there isn't a border, there's no need for the extra space.
         */
        padding-block-start: 0;
      }
    }
  }

  &:focus-visible {
    outline: var(--card-focus-outline);
  }
}
PK
!<���`��3chrome/toolkit/content/global/elements/moz-card.mjs/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import {
  html,
  ifDefined,
  when,
} from "chrome://global/content/vendor/lit.all.mjs";
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";

/**
 * Cards contain content and actions about a single subject.
 * There are two card types:
 * The default type where no type attribute is required and the card
 * will have no extra functionality.
 *
 * The "accordion" type will initially not show any content. The card
 * will contain an arrow to expand the card so that all of the content
 * is visible. You can use the "expanded" attribute to force the accordion
 * card to show its content on initial render.
 *
 *
 * @property {string} heading - The heading text that will be used for the card.
 * @property {string} icon - (optional) A flag to indicate the header should include an icon
 * @property {string} type - (optional) The type of card. No type specified
 *   will be the default card. The other available type is "accordion"
 * @property {boolean} expanded - A flag to indicate whether the card is
 *  expanded or not. Can be used to expand the content section of the
 *  accordion card on initial render.
 * @slot content - The content to show inside of the card.
 */
export default class MozCard extends MozLitElement {
  static queries = {
    detailsEl: "#moz-card-details",
    headingEl: "#heading",
    contentSlotEl: "#content",
  };

  static properties = {
    heading: { type: String },
    icon: { type: Boolean },
    type: { type: String, reflect: true },
    expanded: { type: Boolean },
  };

  constructor() {
    super();
    this.type = "default";
    this.expanded = false;
  }

  headingTemplate() {
    if (!this.heading) {
      return "";
    }
    return html`
      <div id="heading-wrapper">
        ${when(
          this.type == "accordion",
          () => html`<div class="chevron-icon"></div>`
        )}
        ${when(
          this.icon,
          () =>
            html`<div part="icon" id="heading-icon" role="presentation"></div>`
        )}
        <span id="heading" title=${ifDefined(this.heading)} part="heading"
          >${this.heading}</span
        >
      </div>
    `;
  }

  cardTemplate() {
    if (this.type === "accordion") {
      return html`
        <details
          id="moz-card-details"
          @toggle="${this.onToggle}"
          ?open=${this.expanded}
        >
          <summary part="summary">${this.headingTemplate()}</summary>
          <div id="content"><slot></slot></div>
        </details>
      `;
    }

    return html`
      <div id="moz-card-details">
        ${this.headingTemplate()}
        <div id="content" aria-describedby="content">
          <slot></slot>
        </div>
      </div>
    `;
  }
  /**
   * Handles the click event on the chevron icon.
   *
   * Without this, the click event would be passed to
   * toggleDetails which would force the details element
   * to stay open.
   *
   * @memberof MozCard
   */
  onDetailsClick() {
    this.toggleDetails();
  }

  /**
   * @param {boolean} force - Used to force open or force close the
   * details element.
   * @memberof MozCard
   */
  toggleDetails(force) {
    this.expanded = force ?? !this.detailsEl.open;
  }

  onToggle() {
    this.expanded = this.detailsEl.open;
    this.dispatchEvent(
      new ToggleEvent("toggle", {
        newState: this.detailsEl.open ? "open" : "closed",
        oldState: this.detailsEl.open ? "closed" : "open",
      })
    );
  }

  render() {
    return html`
      <link
        rel="stylesheet"
        href="chrome://global/content/elements/moz-card.css"
      />
      <article
        class="moz-card"
        aria-labelledby=${ifDefined(this.heading ? "heading" : undefined)}
      >
        ${this.cardTemplate()}
      </article>
    `;
  }
}
customElements.define("moz-card", MozCard);
PK
!<����7chrome/toolkit/content/global/elements/moz-checkbox.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

 @import url("chrome://global/skin/design-system/text-and-typography.css");

:host {
  display: flex;
  flex-direction: column;
  gap: var(--space-xxsmall);
  --checkbox-space-offset: calc(var(--checkbox-size) + var(--space-small));
  --icon-space-offset: calc(var(--icon-size-default) + var(--space-small));
}

label {
  display: flex;
  gap: var(--space-small);
  align-items: start;
}

input {
  min-width: var(--checkbox-size);
  height: var(--checkbox-size);
  accent-color: var(--color-accent-primary);
  -moz-theme: non-native;
  font-size: inherit;
  /* Bug 1901865: Due to the "MS Shell Dlg 2" font, we need inherits to
   * keep the checkbox aligned */
  font-family: inherit;
  line-height: inherit;
}

input,
.icon {
  margin: calc((1lh - var(--checkbox-size)) / 2) 0;
}

.label-content {
  display: flex;
  position: relative;
}

.icon {
  -moz-context-properties: fill, fill-opacity, stroke;
  fill: currentColor;
  stroke: currentColor;
  width: var(--icon-size-default);
  height: var(--icon-size-default);
  position: absolute;

  & + .text {
    text-indent: var(--icon-space-offset)
  }
}

.description {
  margin-inline-start: var(--checkbox-space-offset);
}

.label {
  padding-inline-end: var(--space-xsmall);
}
PK
!<A����7chrome/toolkit/content/global/elements/moz-checkbox.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { html, ifDefined, when } from "../vendor/lit.all.mjs";
import { MozLitElement } from "../lit-utils.mjs";

// eslint-disable-next-line import/no-unassigned-import
import "chrome://global/content/elements/moz-label.mjs";
// eslint-disable-next-line import/no-unassigned-import
import "chrome://global/content/elements/moz-support-link.mjs";

/**
 * A checkbox input with a label.
 *
 * @tagname moz-checkbox
 * @property {string} label - The text of the label element
 * @property {string} name - The name of the checkbox input control
 * @property {string} value - The value of the checkbox input control
 * @property {boolean} checked - The state of the checkbox element,
 *  also controls whether the checkbox is initially rendered as
 *  being checked.
 * @property {boolean} disabled - The disabled state of the checkbox input
 * @property {string} iconSrc - The src for an optional icon
 * @property {string} description - The text for the description element that helps describe the checkbox
 * @property {string} supportPage - Name of the SUMO support page to link to.
 */
export default class MozCheckbox extends MozLitElement {
  static properties = {
    label: { type: String, fluent: true },
    name: { type: String },
    value: { type: String },
    iconSrc: { type: String },
    checked: { type: Boolean, reflect: true },
    disabled: { type: Boolean, reflect: true },
    description: { type: String, fluent: true },
    accessKeyAttribute: {
      type: String,
      attribute: "accesskey",
      reflect: true,
    },
    accessKey: { type: String, state: true },
    supportPage: { type: String, attribute: "support-page" },
  };

  static get queries() {
    return {
      checkboxEl: "#moz-checkbox",
      labelEl: "label",
      icon: ".icon",
      descriptionEl: "#description",
    };
  }

  constructor() {
    super();
    this.checked = false;
    this.disabled = false;
  }

  click() {
    this.checkboxEl.click();
  }

  focus() {
    this.checkboxEl.focus();
  }

  /**
   * Handles click events and keeps the checkbox checked value in sync
   *
   * @param {Event} event
   * @memberof MozCheckbox
   */
  handleStateChange(event) {
    this.checked = event.target.checked;
  }

  /**
   * Dispatches an event from the host element so that outside
   * listeners can react to these events
   *
   * @param {Event} event
   * @memberof MozCheckbox
   */
  redispatchEvent(event) {
    let newEvent = new Event(event.type, event);
    this.dispatchEvent(newEvent);
  }

  willUpdate(changes) {
    if (changes.has("accessKeyAttribute")) {
      this.accessKey = this.accessKeyAttribute;
      this.accessKeyAttribute = null;
    }
  }

  iconTemplate() {
    if (this.iconSrc) {
      return html`<img src=${this.iconSrc} role="presentation" class="icon" />`;
    }
    return "";
  }

  descriptionTemplate() {
    return html`
      <span id="description" class="description text-deemphasized">
        ${this.description ?? html`<slot name="description"></slot>`}
      </span>
    `;
  }

  supportLinkTemplate() {
    return html`<slot name="support-link">
      ${when(
        this.supportPage,
        () =>
          html`<a
            is="moz-support-link"
            support-page=${this.supportPage}
            part="support-link"
          ></a>`
      )}
    </slot>`;
  }

  render() {
    return html`
      <link
        rel="stylesheet"
        href="chrome://global/content/elements/moz-checkbox.css"
      />
      <label
        is="moz-label"
        for="moz-checkbox"
        part="label"
        shownaccesskey=${ifDefined(this.accessKey)}
      >
        <input
          id="moz-checkbox"
          type="checkbox"
          name=${this.name}
          value=${this.value}
          .checked=${this.checked}
          @click=${this.handleStateChange}
          @change=${this.redispatchEvent}
          .disabled=${this.disabled}
          aria-describedby="description"
          accesskey=${ifDefined(this.accessKey)}
        />
        <span class="label-content">
          ${this.iconTemplate()}
          <span class="text">
            <span class="label">${this.label}</span>
            ${this.supportLinkTemplate()}
          </span>
        </span>
      </label>
      ${this.descriptionTemplate()}
    `;
  }
}
customElements.define("moz-checkbox", MozCheckbox);
PK
!<�qq7chrome/toolkit/content/global/elements/moz-fieldset.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/design-system/text-and-typography.css");

:host {
  display: block;
}

fieldset {
  border: none;
  padding: 0;
  margin: 0;
}

legend {
  padding: 0;
  font-weight: var(--font-weight-bold);
}

#description {
  margin: 0;
  margin-block-start: var(--space-xxsmall);
}

#inputs {
  display: flex;
  flex-direction: column;
  gap: var(--space-large);
  margin-top: var(--space-small);
}
PK
!<�� T<<7chrome/toolkit/content/global/elements/moz-fieldset.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { html, ifDefined } from "../vendor/lit.all.mjs";
import { MozLitElement } from "../lit-utils.mjs";

/**
 * Fieldset wrapper to lay out form inputs consistently.
 *
 * @tagname moz-fieldset
 * @property {string} label - The label for the fieldset's legend.
 * @property {string} description - The description for the fieldset.
 */
export default class MozFieldset extends MozLitElement {
  static properties = {
    label: { type: String, fluent: true },
    description: { type: String, fluent: true },
  };

  render() {
    return html`
      <link
        rel="stylesheet"
        href="chrome://global/content/elements/moz-fieldset.css"
      />
      <fieldset
        aria-describedby=${ifDefined(this.description ? "description" : null)}
      >
        <legend part="label">${this.label}</legend>
        ${this.description
          ? html`<p id="description" class="text-deemphasized">
              ${this.description}
            </p>`
          : ""}
        <div id="inputs" part="inputs">
          <slot></slot>
        </div>
      </fieldset>
    `;
  }
}
customElements.define("moz-fieldset", MozFieldset);
PK
!<k�$~~8chrome/toolkit/content/global/elements/moz-five-star.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:host {
  display: flex;
  justify-content: space-between;
}

:host([hidden]) {
  display: none;
}

.stars {
  --rating-star-size: 1em;
  --rating-star-spacing: 0.3ch;

  display: inline-grid;
  grid-template-columns: repeat(5, var(--rating-star-size));
  grid-column-gap: var(--rating-star-spacing);
  align-content: center;
}

.rating-star {
  display: inline-block;
  width: var(--rating-star-size);
  height: var(--rating-star-size);
  background-image: url("chrome://global/skin/icons/rating-star.svg#empty");
  background-position: center;
  background-repeat: no-repeat;
  background-size: 100%;

  fill: var(--icon-color);
  -moz-context-properties: fill;
}

.rating-star[fill="half"] {
  background-image: url("chrome://global/skin/icons/rating-star.svg#half");
}
.rating-star[fill="full"] {
  background-image: url("chrome://global/skin/icons/rating-star.svg#full");
}

.rating-star[fill="half"]:dir(rtl) {
  transform: scaleX(-1);
}
PK
!<��Is��8chrome/toolkit/content/global/elements/moz-five-star.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ifDefined, html } from "../vendor/lit.all.mjs";
import { MozLitElement } from "../lit-utils.mjs";

window.MozXULElement?.insertFTLIfNeeded("toolkit/global/mozFiveStar.ftl");

/**
 * The visual representation is five stars, each of them either empty,
 * half-filled or full. The fill state is derived from the rating,
 * rounded to the nearest half.
 *
 * @tagname moz-five-star
 * @property {number} rating - The rating out of 5.
 * @property {string} title - The title text.
 */
export default class MozFiveStar extends MozLitElement {
  static properties = {
    rating: { type: Number, reflect: true },
    title: { type: String },
  };

  static get queries() {
    return {
      starEls: { all: ".rating-star" },
      starsWrapperEl: ".stars",
    };
  }

  getStarsFill() {
    let starFill = [];
    let roundedRating = Math.round(this.rating * 2) / 2;
    for (let i = 1; i <= 5; i++) {
      if (i <= roundedRating) {
        starFill.push("full");
      } else if (i - roundedRating === 0.5) {
        starFill.push("half");
      } else {
        starFill.push("empty");
      }
    }
    return starFill;
  }

  render() {
    let starFill = this.getStarsFill();
    return html`
      <link
        rel="stylesheet"
        href="chrome://global/content/elements/moz-five-star.css"
      />
      <div
        class="stars"
        role="img"
        data-l10n-id=${ifDefined(
          this.title ? undefined : "moz-five-star-rating"
        )}
        data-l10n-args=${ifDefined(
          this.title ? undefined : JSON.stringify({ rating: this.rating ?? 0 })
        )}
      >
        ${starFill.map(
          fill => html`<span class="rating-star" fill="${fill}"></span>`
        )}
      </div>
    `;
  }
}
customElements.define("moz-five-star", MozFiveStar);
PK
!<�.<l��7chrome/toolkit/content/global/elements/moz-input-box.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
  const cachedFragments = {
    get editMenuItems() {
      return `
      <menuitem data-l10n-id="text-action-undo" cmd="cmd_undo"></menuitem>
      <menuitem data-l10n-id="text-action-redo" cmd="cmd_redo"></menuitem>
      <menuseparator></menuseparator>
      <menuitem data-l10n-id="text-action-cut" cmd="cmd_cut"></menuitem>
      <menuitem data-l10n-id="text-action-copy" cmd="cmd_copy"></menuitem>
      <menuitem data-l10n-id="text-action-paste" cmd="cmd_paste"></menuitem>
      <menuitem data-l10n-id="text-action-delete" cmd="cmd_delete"></menuitem>
      <menuitem data-l10n-id="text-action-select-all" cmd="cmd_selectAll"></menuitem>
    `;
    },
    get normal() {
      delete this.normal;
      this.normal = MozXULElement.parseXULToFragment(
        `
      <menupopup class="textbox-contextmenu" showservicesmenu="true">
        ${this.editMenuItems}
      </menupopup>
    `
      );
      MozXULElement.insertFTLIfNeeded("toolkit/global/textActions.ftl");
      return this.normal;
    },
    get spellcheck() {
      delete this.spellcheck;
      this.spellcheck = MozXULElement.parseXULToFragment(
        `
      <menupopup class="textbox-contextmenu" showservicesmenu="true">
        <menuitem data-l10n-id="text-action-spell-no-suggestions" anonid="spell-no-suggestions" disabled="true"></menuitem>
        <menuitem data-l10n-id="text-action-spell-add-to-dictionary" anonid="spell-add-to-dictionary" oncommand="this.parentNode.parentNode.spellCheckerUI.addToDictionary();"></menuitem>
        <menuitem data-l10n-id="text-action-spell-undo-add-to-dictionary" anonid="spell-undo-add-to-dictionary" oncommand="this.parentNode.parentNode.spellCheckerUI.undoAddToDictionary();"></menuitem>
        <menuseparator anonid="spell-suggestions-separator"></menuseparator>
        ${this.editMenuItems}
        <menuseparator anonid="spell-check-separator"></menuseparator>
        <menuitem data-l10n-id="text-action-spell-check-toggle" type="checkbox" anonid="spell-check-enabled" oncommand="this.parentNode.parentNode.spellCheckerUI.toggleEnabled();"></menuitem>
        <menu data-l10n-id="text-action-spell-dictionaries" anonid="spell-dictionaries">
          <menupopup anonid="spell-dictionaries-menu" onpopupshowing="event.stopPropagation();" onpopuphiding="event.stopPropagation();"></menupopup>
        </menu>
      </menupopup>
    `
      );
      return this.spellcheck;
    },
  };

  class MozInputBox extends MozXULElement {
    static get observedAttributes() {
      return ["spellcheck"];
    }

    attributeChangedCallback(name, oldValue, newValue) {
      if (name === "spellcheck" && oldValue != newValue) {
        this._initUI();
      }
    }

    connectedCallback() {
      this._initUI();
    }

    _initUI() {
      this.spellcheck = this.hasAttribute("spellcheck");
      if (this.menupopup) {
        this.menupopup.remove();
      }

      this.setAttribute("context", "_child");
      this.appendChild(
        this.spellcheck
          ? cachedFragments.spellcheck.cloneNode(true)
          : cachedFragments.normal.cloneNode(true)
      );
      this.menupopup = this.querySelector(".textbox-contextmenu");

      this.menupopup.addEventListener("popupshowing", event => {
        let input = this._input;
        if (document.commandDispatcher.focusedElement != input) {
          input.focus();
        }
        this._doPopupItemEnabling(event);
      });

      if (this.spellcheck) {
        this.menupopup.addEventListener("popuphiding", () => {
          if (this.spellCheckerUI) {
            this.spellCheckerUI.clearSuggestionsFromMenu();
            this.spellCheckerUI.clearDictionaryListFromMenu();
          }
        });
      }

      this.menupopup.addEventListener("command", event => {
        var cmd = event.originalTarget.getAttribute("cmd");
        if (cmd) {
          this.doCommand(cmd);
          event.stopPropagation();
        }
      });
    }

    _doPopupItemEnablingSpell(event) {
      var spellui = this.spellCheckerUI;
      if (!spellui || !spellui.canSpellCheck) {
        this._setMenuItemVisibility("spell-no-suggestions", false);
        this._setMenuItemVisibility("spell-check-enabled", false);
        this._setMenuItemVisibility("spell-check-separator", false);
        this._setMenuItemVisibility("spell-add-to-dictionary", false);
        this._setMenuItemVisibility("spell-undo-add-to-dictionary", false);
        this._setMenuItemVisibility("spell-suggestions-separator", false);
        this._setMenuItemVisibility("spell-dictionaries", false);
        return;
      }

      spellui.initFromEvent(event.rangeParent, event.rangeOffset);

      var enabled = spellui.enabled;
      var showUndo = spellui.canSpellCheck && spellui.canUndo();

      var enabledCheckbox = this.getMenuItem("spell-check-enabled");
      enabledCheckbox.setAttribute("checked", enabled);

      var overMisspelling = spellui.overMisspelling;
      this._setMenuItemVisibility("spell-add-to-dictionary", overMisspelling);
      this._setMenuItemVisibility("spell-undo-add-to-dictionary", showUndo);
      this._setMenuItemVisibility(
        "spell-suggestions-separator",
        overMisspelling || showUndo
      );

      // suggestion list
      var suggestionsSeparator = this.getMenuItem("spell-no-suggestions");
      var numsug = spellui.addSuggestionsToMenuOnParent(
        event.target,
        suggestionsSeparator,
        5
      );
      this._setMenuItemVisibility(
        "spell-no-suggestions",
        overMisspelling && numsug == 0
      );

      // dictionary list
      var dictionariesMenu = this.getMenuItem("spell-dictionaries-menu");
      var numdicts = spellui.addDictionaryListToMenu(dictionariesMenu, null);
      this._setMenuItemVisibility(
        "spell-dictionaries",
        enabled && numdicts > 1
      );
    }

    _doPopupItemEnabling(event) {
      if (this.spellcheck) {
        this._doPopupItemEnablingSpell(event);
      }

      let popupNode = event.target;
      var children = popupNode.childNodes;
      for (var i = 0; i < children.length; i++) {
        var command = children[i].getAttribute("cmd");
        if (command) {
          var controller =
            document.commandDispatcher.getControllerForCommand(command);
          var enabled = controller.isCommandEnabled(command);
          if (enabled) {
            children[i].removeAttribute("disabled");
          } else {
            children[i].setAttribute("disabled", "true");
          }
        }
      }
    }

    get spellCheckerUI() {
      if (!this._spellCheckInitialized) {
        this._spellCheckInitialized = true;

        try {
          const { InlineSpellChecker } = ChromeUtils.importESModule(
            "resource://gre/modules/InlineSpellChecker.sys.mjs"
          );
          this.InlineSpellCheckerUI = new InlineSpellChecker(
            this._input.editor
          );
        } catch (ex) {}
      }

      return this.InlineSpellCheckerUI;
    }

    getMenuItem(anonid) {
      return this.querySelector(`[anonid="${anonid}"]`);
    }

    _setMenuItemVisibility(anonid, visible) {
      this.getMenuItem(anonid).hidden = !visible;
    }

    doCommand(command) {
      var controller =
        document.commandDispatcher.getControllerForCommand(command);
      controller.doCommand(command);
    }

    get _input() {
      return (
        this.getElementsByAttribute("anonid", "input")[0] ||
        this.querySelector(".textbox-input")
      );
    }
  }

  customElements.define("moz-input-box", MozInputBox);
}
PK
!<g{� ��>chrome/toolkit/content/global/elements/moz-page-nav-button.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:host {
  border-radius: var(--border-radius-small);
  font-size: var(--font-size-large);
  &:focus-visible {
    outline-offset: var(--page-nav-focus-outline-inset);
  }
}

a[href],
button {
  background-color: var(--page-nav-button-background-color);
  border: 1px solid var(--page-nav-border-color);
  -moz-context-properties: fill, fill-opacity;
  fill: var(--icon-color);
  display: grid;
  grid-template-columns: min-content 1fr;
  gap: var(--space-medium);
  align-items: center;
  font-size: inherit;
  width: 100%;
  font-weight: normal;
  border-radius: var(--page-nav-button-border-radius);
  color: var(--page-nav-button-text-color);
  text-align: start;
  transition: background-color 150ms;
  padding: var(--page-nav-button-padding);
}

a[href] {
  text-decoration: none;
  box-sizing: border-box;
}

button:hover {
  cursor: pointer;
}

@media not (prefers-contrast) {
  a[href],
  button {
    position: relative;
  }

  button::before {
    content: "";
    display: block;
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    width: 2px;
    background-color: transparent;
  }

  button[selected]::before {
    background-color: var(--color-accent-primary);
  }

  button[selected]:hover::before {
    background-color: var(--icon-color);
  }

  a[href]:hover,
  button:hover,
  button[selected]:hover {
    background-color: var(--page-nav-button-background-color-hover);
  }

  button[selected]:not(:hover) {
    color: var(--color-accent-primary);
    background-color: color-mix(in srgb, var(--page-nav-button-background-color-selected) 5%, transparent);
  }
}

@media (prefers-color-scheme: dark) {
  button[selected] {
    background-color: color-mix(in srgb, var(--page-nav-button-background-color-selected) 12%, transparent);
  }
}

a[href]:focus-visible,
button:focus-visible,
button[selected]:focus-visible {
  outline: var(--focus-outline);
  outline-offset: 0;
  border-radius: var(--border-radius-small);
}

.page-nav-icon {
  height: 20px;
  width: 20px;
  -moz-context-properties: fill;
  fill: currentColor;
}

:host(.secondary-nav-item) {
  font-size: var(--font-size-small);

  & .page-nav-icon {
    height: var(--icon-size-default);
    width: var(--icon-size-default);
  }
}

@media (prefers-contrast) {
  a[href],
  button {
    transition: none;
    border-color: ButtonText;
    background-color: var(--button-background-color);
  }

  a[href]:hover,
  button:hover {
    color: SelectedItem;
  }

  button[selected] {
    color: SelectedItemText;
    background-color: SelectedItem;
    border-color: SelectedItem;
  }
}

slot {
  margin: 0;
  padding-inline-start: 0;
  user-select: none;
}

@media (max-width: 52rem) {
  a[href],
  button {
    grid-template-columns: min-content;
    justify-content: center;
    margin-inline: 0;
  }

  slot {
    display: none;
  }
}
PK
!<��K��7chrome/toolkit/content/global/elements/moz-page-nav.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:host {
  --page-nav-button-border-radius: var(--button-border-radius);
  --page-nav-button-text-color: var(--button-text-color);
  --page-nav-button-background-color: transparent;
  --page-nav-button-background-color-hover: var(--button-background-color-hover);
  --page-nav-button-background-color-selected: var(--color-accent-primary);
  --page-nav-button-padding: var(--space-small);
  --page-nav-margin-top: 72px;
  --page-nav-margin-bottom: 36px;
  --page-nav-gap: 25px;
  --page-nav-button-gap: var(--space-xsmall);
  --page-nav-border-color: var(--border-color);
  --page-nav-focus-outline-inset: var(--focus-outline-inset);
  --page-nav-width: 240px;
  margin-inline-start: 42px;
  position: sticky;
  top: 0;
  height: 100vh;
  display: block;
  width: var(--page-nav-width);

  @media (prefers-reduced-motion) {
    /* (See Bug 1610081) Setting border-inline-end to add clear differentiation between side navigation and main content area */
    border-inline-end: 1px solid var(--page-nav-border-color);
  }

  @media (max-width: 52rem) {
    grid-template-rows: 1fr auto;
    --page-nav-width: 118px;
  }
}

nav {
  display: grid;
  grid-template-rows: min-content 1fr auto;
  gap: var(--page-nav-gap);
  margin-block: var(--page-nav-margin-top) var(--page-nav-margin-bottom);
  height: calc(100% - var(--page-nav-margin-top) - var(--page-nav-margin-bottom));
  @media (max-width: 52rem) {
    grid-template-rows: 1fr auto;
  }
}

.page-nav-header {
  /* Align the header text/icon with the page nav button icons */
  margin-inline-start: var(--page-nav-button-padding);
  font-size: var(--font-size-xlarge);
  font-weight: var(--font-weight-bold);
  margin-block: 0;

  @media (max-width: 52rem) {
    display: none;
  }
}

.primary-nav-group,
#secondary-nav-group {
  display: grid;
  grid-template-columns: 1fr;
  grid-auto-rows: min-content;
  gap: var(--page-nav-button-gap);

  @media (max-width: 52rem) {
    justify-content: center;
    grid-template-columns: min-content;
  }
}

@media (prefers-contrast) {
  .primary-nav-group {
    gap: var(--space-small);
  }
}
PK
!<j�'��7chrome/toolkit/content/global/elements/moz-page-nav.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { html } from "chrome://global/content/vendor/lit.all.mjs";
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
// eslint-disable-next-line import/no-unassigned-import
import "chrome://global/content/elements/moz-support-link.mjs";

/**
 * A grouping of navigation buttons that is displayed at the page level,
 * intended to change the selected view, provide a heading, and have links
 * to external resources.
 *
 * @tagname moz-page-nav
 * @property {string} currentView - The currently selected view.
 * @property {string} heading - A heading to be displayed at the top of the navigation.
 * @slot [default] - Used to append moz-page-nav-button elements to the navigation.
 */
export default class MozPageNav extends MozLitElement {
  static properties = {
    currentView: { type: String },
    heading: { type: String },
  };

  static queries = {
    headingEl: "#page-nav-header",
    primaryNavGroupSlot: ".primary-nav-group slot",
    secondaryNavGroupSlot: "#secondary-nav-group slot",
  };

  get pageNavButtons() {
    return this.primaryNavGroupSlot
      .assignedNodes()
      .filter(
        node => node?.localName === "moz-page-nav-button" && !node.hidden
      );
  }

  get secondaryNavButtons() {
    return this.secondaryNavGroupSlot
      .assignedNodes()
      .filter(
        node => node?.localName === "moz-page-nav-button" && !node.hidden
      );
  }

  onChangeView(e) {
    this.currentView = e.target.view;
  }

  handleFocus(e) {
    if (e.key == "ArrowDown" || e.key == "ArrowRight") {
      e.preventDefault();
      this.focusNextView();
    } else if (e.key == "ArrowUp" || e.key == "ArrowLeft") {
      e.preventDefault();
      this.focusPreviousView();
    }
  }

  focusPreviousView() {
    let pageNavButtons = this.pageNavButtons;
    let currentIndex = pageNavButtons.findIndex(b => b.selected);
    let prev = pageNavButtons[currentIndex - 1];
    if (prev) {
      prev.activate();
      prev.buttonEl.focus();
    }
  }

  focusNextView() {
    let pageNavButtons = this.pageNavButtons;
    let currentIndex = pageNavButtons.findIndex(b => b.selected);
    let next = pageNavButtons[currentIndex + 1];
    if (next) {
      next.activate();
      next.buttonEl.focus();
    }
  }

  onSecondaryNavChange() {
    this.secondaryNavGroupSlot.assignedElements()?.forEach(el => {
      el.classList.add("secondary-nav-item");
    });
  }

  render() {
    return html`
      <link
        rel="stylesheet"
        href="chrome://global/content/elements/moz-page-nav.css"
      />
      <nav>
        <h1 class="page-nav-header" id="page-nav-header">${this.heading}</h1>
        <div
          class="primary-nav-group"
          role="tablist"
          aria-orientation="vertical"
          aria-labelledby="page-nav-header"
        >
          <slot
            @change-view=${this.onChangeView}
            @keydown=${this.handleFocus}
          ></slot>
        </div>
        <div id="secondary-nav-group" role="group">
          <slot
            name="secondary-nav"
            @slotchange=${this.onSecondaryNavChange}
          ></slot>
        </div>
      </nav>
    `;
  }

  updated() {
    let isViewSelected = false;
    let assignedPageNavButtons = this.pageNavButtons;
    for (let button of assignedPageNavButtons) {
      button.selected = button.view == this.currentView;
      isViewSelected = isViewSelected || button.selected;
    }
    if (!isViewSelected && assignedPageNavButtons.length) {
      // Current page nav has no matching view, reset to the first view.
      assignedPageNavButtons[0].activate();
    }
  }
}
customElements.define("moz-page-nav", MozPageNav);

/**
 * A navigation button intended to change the selected view within a page.
 *
 * @tagname moz-page-nav-button
 * @property {string} href - (optional) The url for an external link if not a support page URL
 * @property {string} iconSrc - The chrome:// url for the icon used for the button.
 * @property {boolean} selected - Whether or not the button is currently selected.
 * @property {string} supportPage - (optional) The short name for the support page a secondary link should launch to
 * @slot [default] - Used to append the l10n string to the button.
 */
export class MozPageNavButton extends MozLitElement {
  static properties = {
    iconSrc: { type: String },
    href: { type: String },
    selected: { type: Boolean },
    supportPage: { type: String, attribute: "support-page" },
  };

  connectedCallback() {
    super.connectedCallback();
    this.setAttribute("role", "none");
  }

  static queries = {
    buttonEl: "button",
    linkEl: "a",
  };

  get view() {
    return this.getAttribute("view");
  }

  activate() {
    this.dispatchEvent(
      new CustomEvent("change-view", {
        bubbles: true,
        composed: true,
      })
    );
  }

  itemTemplate() {
    if (this.href || this.supportPage) {
      return this.linkTemplate();
    }
    return this.buttonTemplate();
  }

  buttonTemplate() {
    return html`
      <button
        aria-selected=${this.selected}
        tabindex=${this.selected ? 0 : -1}
        role="tab"
        ?selected=${this.selected}
        @click=${this.activate}
      >
        ${this.innerContentTemplate()}
      </button>
    `;
  }

  linkTemplate() {
    if (this.supportPage) {
      return html`
        <a
          is="moz-support-link"
          class="moz-page-nav-link"
          support-page=${this.supportPage}
        >
          ${this.innerContentTemplate()}
        </a>
      `;
    }
    return html`
      <a href=${this.href} class="moz-page-nav-link" target="_blank">
        ${this.innerContentTemplate()}
      </a>
    `;
  }

  innerContentTemplate() {
    return html`
      ${this.iconSrc
        ? html`<img
            class="page-nav-icon"
            src=${this.iconSrc}
            role="presentation"
          />`
        : ""}
      <slot></slot>
    `;
  }

  render() {
    return html`
      <link
        rel="stylesheet"
        href="chrome://global/content/elements/moz-page-nav-button.css"
      />
      ${this.itemTemplate()}
    `;
  }
}
customElements.define("moz-page-nav-button", MozPageNavButton);
PK
!<ɿQE@)@):chrome/toolkit/content/global/elements/moz-radio-group.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { html, ifDefined } from "../vendor/lit.all.mjs";
import { MozLitElement } from "../lit-utils.mjs";
// eslint-disable-next-line import/no-unassigned-import
import "chrome://global/content/elements/moz-label.mjs";
// eslint-disable-next-line import/no-unassigned-import
import "chrome://global/content/elements/moz-fieldset.mjs";

const NAVIGATION_FORWARD = "forward";
const NAVIGATION_BACKWARD = "backward";

const NAVIGATION_VALUE = {
  [NAVIGATION_FORWARD]: 1,
  [NAVIGATION_BACKWARD]: -1,
};

const DIRECTION_RIGHT = "Right";
const DIRECTION_LEFT = "Left";

const NAVIGATION_DIRECTIONS = {
  LTR: {
    FORWARD: DIRECTION_RIGHT,
    BACKWARD: DIRECTION_LEFT,
  },
  RTL: {
    FORWARD: DIRECTION_LEFT,
    BACKWARD: DIRECTION_RIGHT,
  },
};

/**
 * Element used to group and associate moz-radio buttons so that they function
 * as a single form-control element.
 *
 * @tagname moz-radio-group
 * @property {boolean} disabled - Whether or not the fieldset is disabled.
 * @property {string} label - Label for the group of moz-radio elements.
 * @property {string} name
 *  Input name of the radio group. Propagates to moz-radio children.
 * @property {string} value
 *  Selected value for the group. Changing the value updates the checked
 *  state of moz-radio children and vice versa.
 * @slot default - The radio group's content, intended for moz-radio elements.
 */
export class MozRadioGroup extends MozLitElement {
  #radioButtons = [];
  #value;

  static properties = {
    disabled: { type: Boolean, reflect: true },
    label: { type: String, fluent: true },
    name: { type: String },
  };

  static queries = {
    defaultSlot: "slot:not([name])",
    fieldset: "moz-fieldset",
  };

  set value(newValue) {
    this.#value = newValue;
    this.#radioButtons.forEach(button => {
      button.checked = this.value === button.value;
    });
    this.syncFocusState();
  }

  get value() {
    return this.#value;
  }

  get focusableIndex() {
    if (this.#value) {
      let selectedIndex = this.#radioButtons.findIndex(
        button => button.value === this.#value && !button.disabled
      );
      if (selectedIndex !== -1) {
        return selectedIndex;
      }
    }
    return this.#radioButtons.findIndex(button => !button.disabled);
  }

  constructor() {
    super();
    this.disabled = false;
    this.addEventListener("keydown", e => this.handleKeydown(e));
  }

  firstUpdated() {
    this.syncStateToRadioButtons();
  }

  async getUpdateComplete() {
    await super.getUpdateComplete();
    await Promise.all(this.#radioButtons.map(button => button.updateComplete));
  }

  syncStateToRadioButtons() {
    this.#radioButtons = this.defaultSlot
      ?.assignedElements()
      .filter(el => el.localName === "moz-radio");

    this.#radioButtons.forEach(button => {
      if (button.checked && this.value == undefined) {
        this.value = button.value;
      }
      button.name = this.name;
    });
    this.syncFocusState();
  }

  syncFocusState() {
    let focusableIndex = this.focusableIndex;
    this.#radioButtons.forEach((button, index) => {
      button.inputTabIndex = focusableIndex === index ? 0 : -1;
    });
  }

  // NB: We may need to revise this to avoid bugs when we add more focusable
  // elements to moz-radio-group / moz-radio.
  handleKeydown(event) {
    let directions = this.getNavigationDirections();
    switch (event.key) {
      case "Down":
      case "ArrowDown":
      case directions.FORWARD:
      case `Arrow${directions.FORWARD}`: {
        event.preventDefault();
        this.navigate(NAVIGATION_FORWARD);
        break;
      }
      case "Up":
      case "ArrowUp":
      case directions.BACKWARD:
      case `Arrow${directions.BACKWARD}`: {
        event.preventDefault();
        this.navigate(NAVIGATION_BACKWARD);
        break;
      }
    }
  }

  getNavigationDirections() {
    if (this.isDocumentRTL) {
      return NAVIGATION_DIRECTIONS.RTL;
    }
    return NAVIGATION_DIRECTIONS.LTR;
  }

  get isDocumentRTL() {
    if (typeof Services !== "undefined") {
      return Services.locale.isAppLocaleRTL;
    }
    return document.dir === "rtl";
  }

  navigate(direction) {
    let currentIndex = this.focusableIndex;
    let indexStep = this.#radioButtons.length + NAVIGATION_VALUE[direction];

    for (let i = 1; i < this.#radioButtons.length; i++) {
      let nextIndex =
        (currentIndex + indexStep * i) % this.#radioButtons.length;
      if (!this.#radioButtons[nextIndex].disabled) {
        this.#radioButtons[nextIndex].click();
        return;
      }
    }
  }

  willUpdate(changedProperties) {
    if (changedProperties.has("name")) {
      this.handleSetName();
    }
    if (changedProperties.has("disabled")) {
      this.#radioButtons.forEach(button => {
        button.requestUpdate();
      });
    }
  }

  handleSetName() {
    this.#radioButtons.forEach(button => {
      button.name = this.name;
    });
  }

  // Re-dispatch change event so it's re-targeted to moz-radio-group.
  handleChange(event) {
    event.stopPropagation();
    this.dispatchEvent(new Event(event.type));
  }

  render() {
    return html`
      <moz-fieldset
        part="fieldset"
        role="radiogroup"
        ?disabled=${this.disabled}
        label=${this.label}
        exportparts="inputs"
      >
        <slot
          @slotchange=${this.syncStateToRadioButtons}
          @change=${this.handleChange}
        ></slot>
      </moz-fieldset>
    `;
  }
}
customElements.define("moz-radio-group", MozRadioGroup);

/**
 * Input element that allows a user to select one option from a group of options.
 *
 * @tagname moz-radio
 * @property {boolean} checked - Whether or not the input is selected.
 * @property {string} description - Description for the input.
 * @property {boolean} disabled - Whether or not the input is disabled.
 * @property {string} iconSrc - Path to an icon displayed next to the input.
 * @property {number} inputTabIndex - Tabindex of the input element.
 * @property {string} label - Label for the radio input.
 * @property {string} name
 *  Name of the input control, set by the associated moz-radio-group element.
 * @property {number} value - Value of the radio input.
 */
export class MozRadio extends MozLitElement {
  #controller;

  static properties = {
    accessKey: { type: String, state: true },
    accessKeyAttribute: {
      type: String,
      attribute: "accesskey",
      reflect: true,
    },
    checked: { type: Boolean, reflect: true },
    description: { type: String, fluent: true },
    disabled: { type: Boolean, reflect: true },
    iconSrc: { type: String },
    inputTabIndex: { type: Number, state: true },
    label: { type: String, fluent: true },
    name: { type: String, attribute: false },
    value: { type: String },
  };

  static queries = {
    radioButton: "#radio-button",
    labelEl: "label",
    icon: ".icon",
    descriptionEl: "#description",
  };

  constructor() {
    super();
    this.checked = false;
    this.disabled = false;
  }

  connectedCallback() {
    super.connectedCallback();

    let hostRadioGroup = this.parentElement || this.getRootNode().host;
    if (!(hostRadioGroup instanceof MozRadioGroup)) {
      console.error("moz-radio can only be used in moz-radio-group element.");
    }

    this.#controller = hostRadioGroup;
  }

  willUpdate(changedProperties) {
    // Handle setting checked directly via JS.
    if (
      changedProperties.has("checked") &&
      this.checked &&
      this.#controller.value &&
      this.value !== this.#controller.value
    ) {
      this.#controller.value = this.value;
    }
    // Handle un-checking directly via JS. If the checked input is un-checked,
    // the value of the associated moz-radio-group needs to be un-set.
    if (
      changedProperties.has("checked") &&
      !this.checked &&
      this.#controller.value &&
      this.value === this.#controller.value
    ) {
      this.#controller.value = "";
    }

    if (changedProperties.has("disabled")) {
      // Prevent enabling a radio button if containing radio-group is disabled.
      if (this.disabled === false && this.#controller.disabled) {
        this.disabled = true;
      } else if (this.checked || !this.#controller.value) {
        // Update buttons via moz-radio-group for proper keyboard nav behavior.
        this.#controller.syncFocusState();
      }
    }

    if (changedProperties.has("accessKeyAttribute")) {
      this.accessKey = this.accessKeyAttribute;
      this.accessKeyAttribute = null;
    }
  }

  handleClick() {
    this.#controller.value = this.value;
    this.focus();
  }

  // Re-dispatch change event so it propagates out of moz-radio.
  handleChange(e) {
    this.dispatchEvent(new Event(e.type, e));
  }

  // Delegate click to the input element.
  click() {
    this.radioButton.click();
    this.focus();
  }

  // Delegate focus to the input element.
  focus() {
    this.radioButton.focus();
  }

  iconTemplate() {
    if (this.iconSrc) {
      return html`<img src=${this.iconSrc} role="presentation" class="icon" />`;
    }
    return "";
  }

  inputTemplate() {
    return html`<input
      type="radio"
      id="radio-button"
      value=${this.value}
      name=${this.name}
      .checked=${this.checked}
      aria-checked=${this.checked}
      aria-describedby="description"
      tabindex=${this.inputTabIndex}
      ?disabled=${this.disabled || this.#controller.disabled}
      accesskey=${ifDefined(this.accessKey)}
      @click=${this.handleClick}
      @change=${this.handleChange}
    />`;
  }

  labelTemplate() {
    return html`<span class="label-content">
      ${this.iconTemplate()}
      <span class="text">${this.label}</span>
    </span>`;
  }

  descriptionTemplate() {
    return html`
      <span id="description" class="description text-deemphasized">
        ${this.description ?? html`<slot name="description"></slot>`}
      </span>
    `;
  }

  render() {
    return html`
      <link
        rel="stylesheet"
        href="chrome://global/content/elements/moz-radio.css"
      />
      <label
        part="label"
        is="moz-label"
        shownaccesskey=${ifDefined(this.accessKey)}
      >
        ${this.inputTemplate()}${this.labelTemplate()}
      </label>
      ${this.descriptionTemplate()}
    `;
  }
}
customElements.define("moz-radio", MozRadio);
PK
!<"4@ܨ�4chrome/toolkit/content/global/elements/moz-radio.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/design-system/text-and-typography.css");

:host {
  --radio-size: var(--size-item-small);
  --radio-space-offset: calc(var(--radio-size) + var(--space-small));
}

label {
  display: block;
  padding-inline-start: var(--radio-space-offset);
}

input {
  -moz-theme: non-native;
  font-size: inherit;
  min-height: var(--radio-size);
  min-width: var(--radio-size);
  font-family: inherit;
  margin-inline-start: calc(-1 * var(--radio-space-offset));
}

input,
.icon {
  margin-block: calc((1lh - var(--radio-size)) / 2);
}

.label-content {
  position: relative;
}

.icon {
  -moz-context-properties: fill, fill-opacity, stroke;
  fill: currentColor;
  stroke: currentColor;
  width: var(--icon-size-default);
  height: var(--icon-size-default);
  position: absolute;

  & + .text {
    margin-inline-start: calc(var(--icon-size-default) + var(--space-small));
  }
}

.description {
  margin-inline-start: var(--radio-space-offset);
  display: block;
}
PK
!<q.�\\;chrome/toolkit/content/global/elements/moz-support-link.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

window.MozXULElement?.insertFTLIfNeeded("toolkit/global/mozSupportLink.ftl");

/**
 * An extension of the anchor element that helps create links to Mozilla's
 * support documentation. This should be used for SUMO links only - other "Learn
 * more" links can use the regular anchor element.
 *
 * @tagname moz-support-link
 * @attribute {string} support-page - Short-hand string from SUMO to the specific support page.
 * @attribute {string} utm-content - UTM parameter for a URL, if it is an AMO URL.
 * @attribute {string} data-l10n-id - Fluent ID used to generate the text content.
 */
export default class MozSupportLink extends HTMLAnchorElement {
  static SUPPORT_URL = "https://www.mozilla.org/";
  static get observedAttributes() {
    return ["support-page", "utm-content"];
  }

  /**
   * Handles setting up the SUPPORT_URL preference getter.
   * Without this, the tests for this component may not behave
   * as expected.
   * @private
   * @memberof MozSupportLink
   */
  #register() {
    if (window.document.nodePrincipal?.isSystemPrincipal) {
      ChromeUtils.defineESModuleGetters(MozSupportLink, {
        BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
      });

      // eslint-disable-next-line no-shadow
      let { XPCOMUtils } = window.XPCOMUtils
        ? window
        : ChromeUtils.importESModule(
            "resource://gre/modules/XPCOMUtils.sys.mjs"
          );
      XPCOMUtils.defineLazyPreferenceGetter(
        MozSupportLink,
        "SUPPORT_URL",
        "app.support.baseURL",
        "",
        null,
        val => Services.urlFormatter.formatURL(val)
      );
    } else if (!window.IS_STORYBOOK) {
      MozSupportLink.SUPPORT_URL = window.RPMGetFormatURLPref(
        "app.support.baseURL"
      );
    }
  }

  connectedCallback() {
    this.#register();
    this.#setHref();
    this.setAttribute("target", "_blank");
    this.addEventListener("click", this);
    if (
      !this.getAttribute("data-l10n-id") &&
      !this.getAttribute("data-l10n-name") &&
      !this.childElementCount
    ) {
      document.l10n.setAttributes(this, "moz-support-link-text");
    }
    document.l10n.translateFragment(this);
  }

  disconnectedCallback() {
    this.removeEventListener("click", this);
  }

  handleEvent(e) {
    if (e.type == "click") {
      if (window.openTrustedLinkIn) {
        let where = MozSupportLink.BrowserUtils.whereToOpenLink(e, false, true);
        if (where == "current") {
          where = "tab";
        }
        e.preventDefault();
        openTrustedLinkIn(this.href, where);
      }
    }
  }

  attributeChangedCallback(attrName) {
    if (attrName === "support-page" || attrName === "utm-content") {
      this.#setHref();
    }
  }

  #setHref() {
    let supportPage = this.getAttribute("support-page") ?? "";
    let base = MozSupportLink.SUPPORT_URL + supportPage;
    this.href = this.hasAttribute("utm-content")
      ? formatUTMParams(this.getAttribute("utm-content"), base)
      : base;
  }
}
customElements.define("moz-support-link", MozSupportLink, { extends: "a" });

/**
 * Adds UTM parameters to a given URL, if it is an AMO URL.
 *
 * @param {string} contentAttribute
 *        Identifies the part of the UI with which the link is associated.
 * @param {string} url
 * @returns {string}
 *          The url with UTM parameters if it is an AMO URL.
 *          Otherwise the url in unmodified form.
 */
export function formatUTMParams(contentAttribute, url) {
  if (!contentAttribute) {
    return url;
  }
  let parsedUrl = new URL(url);
  let domain = `.${parsedUrl.hostname}`;
  if (
    !domain.endsWith(".mozilla.org") &&
    // For testing: addons-dev.allizom.org and addons.allizom.org
    !domain.endsWith(".allizom.org")
  ) {
    return url;
  }

  parsedUrl.searchParams.set("utm_source", "firefox-browser");
  parsedUrl.searchParams.set("utm_medium", "firefox-browser");
  parsedUrl.searchParams.set("utm_content", contentAttribute);
  return parsedUrl.href;
}
PK
!<�]6
��5chrome/toolkit/content/global/elements/moz-toggle.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/design-system/text-and-typography.css");

:host {
  display: flex;
  flex-direction: column;
  gap: var(--space-xsmall);
}

:host([disabled]) {
  opacity: 0.4
}

::slotted(a[is="moz-support-link"]) {
  display: inline-block;
}

#moz-toggle-label {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-small);
}

.description-wrapper,
.description-wrapper ::slotted([slot="support-link"]) {
  margin: 0;
}

.toggle-button {
  --toggle-background-color: var(--button-background-color);
  --toggle-background-color-hover: var(--button-background-color-hover);
  --toggle-background-color-active: var(--button-background-color-active);
  --toggle-background-color-pressed: var(--color-accent-primary);
  --toggle-background-color-pressed-hover: var(--color-accent-primary-hover);
  --toggle-background-color-pressed-active: var(--color-accent-primary-active);
  --toggle-border-color: var(--border-color-interactive);
  --toggle-border-color-hover: var(--toggle-border-color);
  --toggle-border-color-active: var(--toggle-border-color);
  --toggle-border-radius: var(--border-radius-circle);
  --toggle-border-width: var(--border-width);
  --toggle-height: var(--size-item-small);
  --toggle-width: var(--size-item-large);
  --toggle-dot-background-color: var(--toggle-border-color);
  --toggle-dot-background-color-hover: var(--toggle-dot-background-color);
  --toggle-dot-background-color-active: var(--toggle-dot-background-color);
  --toggle-dot-background-color-on-pressed: var(--background-color-canvas);
  --toggle-dot-margin: 1px;
  --toggle-dot-height: calc(var(--toggle-height) - 2 * var(--toggle-dot-margin) - 2 * var(--toggle-border-width));
  --toggle-dot-width: var(--toggle-dot-height);
  --toggle-dot-transform-x: calc(var(--toggle-width) - 4 * var(--toggle-dot-margin) - var(--toggle-dot-width));
}

.toggle-button {
  appearance: none;
  padding: 0;
  margin: 0;
  border: var(--toggle-border-width) solid var(--toggle-border-color);
  height: var(--toggle-height);
  width: var(--toggle-width);
  border-radius: var(--toggle-border-radius);
  background: var(--toggle-background-color);
  box-sizing: border-box;
  flex-shrink: 0;
}

.toggle-button:focus-visible {
  outline: var(--focus-outline);
  outline-offset: var(--focus-outline-offset);
}

.toggle-button:enabled:hover {
  background: var(--toggle-background-color-hover);
  border-color: var(--toggle-border-color);
}

.toggle-button:enabled:hover:active {
  background: var(--toggle-background-color-active);
  border-color: var(--toggle-border-color);
}

.toggle-button[aria-pressed="true"] {
  background: var(--toggle-background-color-pressed);
  border-color: transparent;
}

.toggle-button[aria-pressed="true"]:enabled:hover {
  background: var(--toggle-background-color-pressed-hover);
  border-color: transparent;
}

.toggle-button[aria-pressed="true"]:enabled:hover:active {
  background: var(--toggle-background-color-pressed-active);
  border-color: transparent;
}

.toggle-button::before {
  display: block;
  content: "";
  background-color: var(--toggle-dot-background-color);
  height: var(--toggle-dot-height);
  width: var(--toggle-dot-width);
  margin: var(--toggle-dot-margin);
  border-radius: var(--toggle-border-radius);
  translate: 0;
}

.toggle-button[aria-pressed="true"]::before {
  translate: var(--toggle-dot-transform-x);
  background-color: var(--toggle-dot-background-color-on-pressed);
}

.toggle-button[aria-pressed="true"]:enabled:hover::before,
.toggle-button[aria-pressed="true"]:enabled:hover:active::before {
  background-color: var(--toggle-dot-background-color-on-pressed);
}

.toggle-button[aria-pressed="true"]:-moz-locale-dir(rtl)::before,
.toggle-button[aria-pressed="true"]:dir(rtl)::before {
  translate: calc(-1 * var(--toggle-dot-transform-x));
}

@media (prefers-reduced-motion: no-preference) {
  .toggle-button::before {
    transition: translate 100ms;
  }
}

@media (prefers-contrast) {
  :host([disabled]) {
    opacity: 1;
  }

  :host([disabled]) > .toggle-button[aria-pressed="true"],
  :host([disabled]) > .toggle-button {
    background-color: var(--toggle-background-color-disabled);
    border-color: var(--toggle-border-color-disabled);
  }

  :host([disabled]) > .toggle-button[aria-pressed="false"]::before,
  :host([disabled]) > .toggle-button[aria-pressed="true"]::before {
    background-color: var(--toggle-background-color-disabled);
  }

  .toggle-button:enabled:hover {
    border-color: var(--toggle-border-color-hover);
  }

  .toggle-button:enabled:hover:active {
    border-color: var(--toggle-border-color-active);
  }

  .toggle-button[aria-pressed="true"]:enabled {
    border-color: var(--toggle-border-color);
    position: relative;
  }

  .toggle-button[aria-pressed="true"]:enabled:hover,
  .toggle-button[aria-pressed="true"]:enabled:hover:active  {
    border-color: var(--toggle-border-color-hover);
  }

  .toggle-button[aria-pressed="true"]:enabled:hover:active {
    background-color: var(--toggle-dot-background-color-active);
    border-color: var(--toggle-dot-background-color-hover);
  }

  .toggle-button:hover::before,
  .toggle-button:hover:active::before {
    background-color: var(--toggle-dot-background-color-hover);
  }
}

@media (forced-colors) {
  .toggle-button {
    --toggle-dot-background-color: var(--color-accent-primary);
    --toggle-dot-background-color-hover: var(--color-accent-primary-hover);
    --toggle-dot-background-color-active: var(--color-accent-primary-active);
    --toggle-dot-background-color-on-pressed: var(--button-background-color);
    --toggle-background-color-disabled: var(--button-background-color-disabled);
    --toggle-border-color-hover: var(--border-color-interactive-hover);
    --toggle-border-color-active: var(--border-color-interactive-active);
    --toggle-border-color-disabled: var(--border-color-interactive-disabled);
  }

  .toggle-button[aria-pressed="true"]:enabled::after {
    border: 1px solid var(--button-background-color);
    content: '';
    position: absolute;
    height: var(--toggle-height);
    width: var(--toggle-width);
    display: block;
    border-radius: var(--toggle-border-radius);
    inset: -2px;
  }

  .toggle-button[aria-pressed="true"]:enabled:hover:active::after {
    border-color: var(--toggle-border-color-active);
  }
}
PK
!<��y��5chrome/toolkit/content/global/elements/moz-toggle.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at htp://mozilla.org/MPL/2.0/. */

import { html, ifDefined } from "../vendor/lit.all.mjs";
import { MozLitElement } from "../lit-utils.mjs";
// eslint-disable-next-line import/no-unassigned-import
import "chrome://global/content/elements/moz-label.mjs";

/**
 * A simple toggle element that can be used to switch between two states.
 *
 * @tagname moz-toggle
 * @property {boolean} pressed - Whether or not the element is pressed.
 * @property {boolean} disabled - Whether or not the element is disabled.
 * @property {string} label - The label text.
 * @property {string} description - The description text.
 * @property {string} ariaLabel
 *  The aria-label text for cases where there is no visible label.
 * @slot support-link - Used to append a moz-support-link to the description.
 * @fires toggle
 *  Custom event indicating that the toggle's pressed state has changed.
 */
export default class MozToggle extends MozLitElement {
  static properties = {
    pressed: { type: Boolean, reflect: true },
    disabled: { type: Boolean, reflect: true },
    label: { type: String },
    description: { type: String },
    ariaLabel: { type: String, attribute: "aria-label" },
    accessKeyAttribute: { type: String, attribute: "accesskey", reflect: true },
    accessKey: { type: String, state: true },
  };

  static get queries() {
    return {
      buttonEl: "#moz-toggle-button",
      labelEl: "#moz-toggle-label",
      descriptionEl: "#moz-toggle-description",
    };
  }

  constructor() {
    super();
    this.pressed = false;
    this.disabled = false;
  }

  handleClick() {
    this.pressed = !this.pressed;
    this.dispatchOnUpdateComplete(
      new CustomEvent("toggle", {
        bubbles: true,
        composed: true,
      })
    );
  }

  // Delegate clicks on the host to the input element
  click() {
    this.buttonEl.click();
  }

  // Delegate focus to the input element
  focus() {
    this.buttonEl.focus();
  }

  willUpdate(changes) {
    if (changes.has("accessKeyAttribute")) {
      this.accessKey = this.accessKeyAttribute;
      this.accessKeyAttribute = null;
    }
  }

  descriptionTemplate() {
    if (this.description) {
      return html`
        <p
          id="moz-toggle-description"
          class="description-wrapper text-deemphasized"
          part="description"
        >
          ${this.description} ${this.supportLinkTemplate()}
        </p>
      `;
    }
    return "";
  }

  supportLinkTemplate() {
    return html` <slot name="support-link"></slot> `;
  }

  buttonTemplate() {
    const { pressed, disabled, description, ariaLabel, handleClick } = this;
    return html`
      <button
        id="moz-toggle-button"
        part="button"
        type="button"
        class="toggle-button"
        ?disabled=${disabled}
        aria-pressed=${pressed}
        aria-label=${ifDefined(ariaLabel ?? undefined)}
        aria-describedby=${ifDefined(
          description ? "moz-toggle-description" : undefined
        )}
        accesskey=${ifDefined(this.accessKey)}
        @click=${handleClick}
      ></button>
    `;
  }

  render() {
    return html`
      <link
        rel="stylesheet"
        href="chrome://global/content/elements/moz-toggle.css"
      />
      ${this.label
        ? html`
            <label
              is="moz-label"
              id="moz-toggle-label"
              part="label"
              for="moz-toggle-button"
              shownaccesskey=${ifDefined(this.accessKey)}
            >
              <span>
                ${this.label}
                ${!this.description ? this.supportLinkTemplate() : ""}
              </span>
              ${this.buttonTemplate()}
            </label>
          `
        : this.buttonTemplate()}
      ${this.descriptionTemplate()}
    `;
  }
}
customElements.define("moz-toggle", MozToggle);
PK
!<��:�k�k9chrome/toolkit/content/global/elements/notificationbox.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into chrome windows with the subscript loader. If you need to
// define globals, wrap in a block to prevent leaking onto `window`.
{
  MozElements.NotificationBox = class NotificationBox {
    /**
     * Creates a new class to handle a notification box, but does not add any
     * elements to the DOM until a notification has to be displayed.
     *
     * @param insertElementFn
     *        Called with the "notification-stack" element as an argument when the
     *        first notification has to be displayed.
     */
    constructor(insertElementFn) {
      this._insertElementFn = insertElementFn;
      this._animating = false;
      this.currentNotification = null;
    }

    get stack() {
      if (!this._stack) {
        let stack = document.createXULElement("vbox");
        stack._notificationBox = this;
        stack.className = "notificationbox-stack";
        stack.addEventListener("transitionend", event => {
          if (
            (event.target.localName == "notification" ||
              event.target.localName == "notification-message") &&
            event.propertyName == "margin-top"
          ) {
            this._finishAnimation();
          }
        });
        this._stack = stack;
        this._insertElementFn(stack);
      }
      return this._stack;
    }

    get _allowAnimation() {
      return window.matchMedia("(prefers-reduced-motion: no-preference)")
        .matches;
    }

    get allNotifications() {
      // Don't create any DOM if no new notification has been added yet.
      if (!this._stack) {
        return [];
      }

      var closedNotification = this._closedNotification;
      var notifications = [
        ...this.stack.getElementsByTagName("notification"),
        ...this.stack.getElementsByTagName("notification-message"),
      ];
      return notifications.filter(n => n != closedNotification);
    }

    getNotificationWithValue(aValue) {
      var notifications = this.allNotifications;
      for (var n = notifications.length - 1; n >= 0; n--) {
        if (aValue == notifications[n].getAttribute("value")) {
          return notifications[n];
        }
      }
      return null;
    }

    /**
     * Creates a <notification> element and shows it. The calling code can modify
     * the element synchronously to add features to the notification.
     *
     * aType
     *        String identifier that can uniquely identify the type of the notification.
     * aNotification
     *        Object that contains any of the following properties, where only the
     *        priority must be specified:
     *    priority
     *        One of the PRIORITY_ constants. These determine the appearance of
     *        the notification based on severity (using the "type" attribute), and
     *        only the notification with the highest priority is displayed.
     *    label
     *        The main message text (as string), or object (with l10n-id, l10n-args),
     *        or a DocumentFragment containing elements to
     *        add as children of the notification's main <description> element.
     *    eventCallback
     *        This may be called with the "removed", "dismissed" or "disconnected"
     *        parameter:
     *          removed - notification has been removed
     *          dismissed - user dismissed notification
     *          disconnected - notification removed in any way
     *    notificationIs
     *        Defines a Custom Element name to use as the "is" value on creation.
     *        This allows subclassing the created element.
     *    telemetry
     *        Specifies the telemetry key to use that triggers when the notification
     *        is shown, dismissed and an action taken. This telemetry is a keyed scalar with keys for:
     *          'shown', 'dismissed' and 'action'. If a button specifies a separate key,
     *        then 'action' is replaced by values specific to each button. The value telemetryFilter
     *        can be used to filter out each type.
     *    telemetryFilter
     *        If assigned, then an array of the telemetry types to send telemetry for. If not set,
     *        then all telemetry is sent.
     * aButtons
     *        Array of objects defining action buttons:
     *        {
     *          label:
     *            Label of the <button> element.
     *          accessKey:
     *            Access key character for the <button> element.
     *          "l10n-id"
     *            Localization id for the <button>, to be used instead of
     *            specifying a separate label and access key.
     *          callback:
     *            When the button is used, this is called with the arguments:
     *             1. The <notification> element.
     *             2. This button object definition.
     *             3. The <button> element.
     *             4. The "command" event.
     *            If the callback returns false, the notification is closed.
     *          link:
     *             A url to open when the button is clicked. The button is
     *             rendered like a link. The callback is called as well.
     *          supportPage:
     *            Used for a support page link. If no other properties are specified,
     *            defaults to a link with a 'Learn more' label.
     *          popup:
     *            If specified, the button will open the popup element with this
     *            ID, anchored to the button. This is alternative to "callback".
     *          telemetry:
     *            Specifies the key to add for the telemetry to trigger when the
     *            button is pressed. If not specified, then 'action' is used for
     *            a press on any button. Specify this only if you want to distinguish
     *            which button has been pressed in telemetry data.
     *          is:
     *            Defines a Custom Element name to use as the "is" value on
     *            button creation.
     *        }
     *
     * @return The <notification> element that is shown.
     */
    async appendNotification(aType, aNotification, aButtons) {
      if (
        aNotification.priority < this.PRIORITY_SYSTEM ||
        aNotification.priority > this.PRIORITY_CRITICAL_HIGH
      ) {
        throw new Error(
          "Invalid notification priority " + aNotification.priority
        );
      }

      MozXULElement.insertFTLIfNeeded("toolkit/global/notification.ftl");

      // Create the Custom Element and connect it to the document immediately.
      let newitem;
      if (!aNotification.notificationIs) {
        if (!customElements.get("notification-message")) {
          // There's some weird timing stuff when this element is created at
          // script load time, we don't need it until now anyway so be lazy.
          // Wrapped in a try/catch to handle rare cases where we start creating
          // a notification but then the window gets closed/goes away.
          try {
            await createNotificationMessageElement();
          } catch (err) {
            console.warn(err);
            throw err;
          }
        }
        newitem = document.createElement("notification-message");
        newitem.setAttribute("message-bar-type", "infobar");
      } else {
        newitem = document.createXULElement(
          "notification",
          aNotification.notificationIs
            ? { is: aNotification.notificationIs }
            : {}
        );
      }

      // Append or prepend notification, based on stack preference.
      if (this.stack.hasAttribute("prepend-notifications")) {
        this.stack.prepend(newitem);
      } else {
        this.stack.append(newitem);
      }

      if (newitem.localName === "notification-message" && aNotification.label) {
        newitem.label = aNotification.label;
      } else if (newitem.messageText) {
        // Custom notification classes may not have the messageText property.
        // Can't use instanceof in case this was created from a different document:
        if (
          aNotification.label &&
          typeof aNotification.label == "object" &&
          aNotification.label.nodeType &&
          aNotification.label.nodeType ==
            aNotification.label.DOCUMENT_FRAGMENT_NODE
        ) {
          newitem.messageText.appendChild(aNotification.label);
        } else if (
          aNotification.label &&
          typeof aNotification.label == "object" &&
          "l10n-id" in aNotification.label
        ) {
          let message = document.createElement("span");
          document.l10n.setAttributes(
            message,
            aNotification.label["l10n-id"],
            aNotification.label["l10n-args"]
          );
          newitem.messageText.appendChild(message);
        } else {
          newitem.messageText.textContent = aNotification.label;
        }
      }
      newitem.setAttribute("value", aType);

      newitem.eventCallback = aNotification.eventCallback;

      if (aButtons) {
        newitem.setButtons(aButtons);
      }

      if (aNotification.telemetry) {
        newitem.telemetry = aNotification.telemetry;
        if (aNotification.telemetryFilter) {
          newitem.telemetryFilter = aNotification.telemetryFilter;
        }
      }

      newitem.priority = aNotification.priority;
      if (aNotification.priority == this.PRIORITY_SYSTEM) {
        newitem.setAttribute("type", "system");
      } else if (aNotification.priority >= this.PRIORITY_CRITICAL_LOW) {
        newitem.setAttribute("type", "critical");
      } else if (aNotification.priority <= this.PRIORITY_INFO_HIGH) {
        newitem.setAttribute("type", "info");
      } else {
        newitem.setAttribute("type", "warning");
      }

      // Animate the notification.
      newitem.style.display = "block";
      newitem.style.position = "fixed";
      newitem.style.top = "100%";
      newitem.style.marginTop = "-15px";
      newitem.style.opacity = "0";

      // Ensure the DOM has been created for the Lit-based notification-message
      // element so that we add the .animated class + it animates as expected.
      await newitem.updateComplete;
      this._showNotification(newitem, true);

      // Fire event for accessibility APIs
      var event = document.createEvent("Events");
      event.initEvent("AlertActive", true, true);
      newitem.dispatchEvent(event);

      // If the notification is not visible, don't call shown() on the
      // new notification until it is visible. This will typically be
      // a tabbrowser that does this when a tab is selected.
      if (this.isShown) {
        newitem.shown();
      }

      return newitem;
    }

    removeNotification(aItem, aSkipAnimation) {
      if (!aItem.parentNode) {
        return;
      }
      this.currentNotification = aItem;
      this.removeCurrentNotification(aSkipAnimation);
    }

    _removeNotificationElement(aChild) {
      let hadFocus = aChild.matches(":focus-within");

      if (aChild.eventCallback) {
        aChild.eventCallback("removed");
      }
      aChild.remove();

      // Make sure focus doesn't get lost (workaround for bug 570835).
      if (hadFocus) {
        Services.focus.moveFocus(
          window,
          this.stack,
          Services.focus.MOVEFOCUS_FORWARD,
          0
        );
      }
    }

    removeCurrentNotification(aSkipAnimation) {
      this._showNotification(this.currentNotification, false, aSkipAnimation);
    }

    removeAllNotifications(aImmediate) {
      var notifications = this.allNotifications;
      for (var n = notifications.length - 1; n >= 0; n--) {
        if (aImmediate) {
          this._removeNotificationElement(notifications[n]);
        } else {
          this.removeNotification(notifications[n]);
        }
      }
      this.currentNotification = null;

      // Clean up any currently-animating notification; this is necessary
      // if a notification was just opened and is still animating, but we
      // want to close it *without* animating.  This can even happen if
      // animations get disabled (via prefers-reduced-motion) and this method
      // is called immediately after an animated notification was displayed
      // (although this case isn't very likely).
      if (aImmediate || !this._allowAnimation) {
        this._finishAnimation();
      }
    }

    removeTransientNotifications() {
      var notifications = this.allNotifications;
      for (var n = notifications.length - 1; n >= 0; n--) {
        var notification = notifications[n];
        if (notification.persistence) {
          notification.persistence--;
        } else if (Date.now() > notification.timeout) {
          this.removeNotification(notification, true);
        }
      }
    }

    shown() {
      for (let notification of this.allNotifications) {
        notification.shown();
      }
    }

    get isShown() {
      let stack = this.stack;
      let parent = this.stack.parentNode;
      if (parent.localName == "named-deck") {
        return parent.selectedViewName == stack.getAttribute("name");
      }

      return true;
    }

    _showNotification(aNotification, aSlideIn, aSkipAnimation) {
      this._finishAnimation();

      let { marginTop, marginBottom } = getComputedStyle(aNotification);
      let baseHeight = aNotification.getBoundingClientRect().height;
      var height =
        baseHeight + parseInt(marginTop, 10) + parseInt(marginBottom, 10);
      var skipAnimation =
        aSkipAnimation || baseHeight == 0 || !this._allowAnimation;
      aNotification.classList.toggle("animated", !skipAnimation);

      if (aSlideIn) {
        this.currentNotification = aNotification;
        aNotification.style.removeProperty("display");
        aNotification.style.removeProperty("position");
        aNotification.style.removeProperty("top");
        aNotification.style.removeProperty("margin-top");
        aNotification.style.removeProperty("opacity");

        if (skipAnimation) {
          return;
        }
      } else {
        this._closedNotification = aNotification;
        var notifications = this.allNotifications;
        var idx = notifications.length - 1;
        this.currentNotification = idx >= 0 ? notifications[idx] : null;

        if (skipAnimation) {
          this._removeNotificationElement(this._closedNotification);
          delete this._closedNotification;
          return;
        }

        aNotification.style.marginTop = -height + "px";
        aNotification.style.opacity = 0;
      }

      this._animating = true;
    }

    _finishAnimation() {
      if (this._animating) {
        this._animating = false;
        if (this._closedNotification) {
          this._removeNotificationElement(this._closedNotification);
          delete this._closedNotification;
        }
      }
    }
  };

  // These are defined on the instance prototype for backwards compatibility.
  Object.assign(MozElements.NotificationBox.prototype, {
    PRIORITY_SYSTEM: 0,
    PRIORITY_INFO_LOW: 1,
    PRIORITY_INFO_MEDIUM: 2,
    PRIORITY_INFO_HIGH: 3,
    PRIORITY_WARNING_LOW: 4,
    PRIORITY_WARNING_MEDIUM: 5,
    PRIORITY_WARNING_HIGH: 6,
    PRIORITY_CRITICAL_LOW: 7,
    PRIORITY_CRITICAL_MEDIUM: 8,
    PRIORITY_CRITICAL_HIGH: 9,
  });

  MozElements.Notification = class Notification extends MozXULElement {
    static get markup() {
      return `
      <hbox class="messageDetails" align="center" flex="1"
            oncommand="this.parentNode._doButtonCommand(event);">
        <image class="messageImage"/>
        <description class="messageText" flex="1"/>
        <spacer flex="1"/>
      </hbox>
      <toolbarbutton ondblclick="event.stopPropagation();"
                     class="messageCloseButton close-icon tabbable"
                     data-l10n-id="close-notification-message"
                     oncommand="this.parentNode.dismiss();"/>
      `;
    }

    constructor() {
      super();
      this.persistence = 0;
      this.priority = 0;
      this.timeout = 0;
      this.telemetry = null;
      this._shown = false;
    }

    connectedCallback() {
      MozXULElement.insertFTLIfNeeded("toolkit/global/notification.ftl");
      this.appendChild(this.constructor.fragment);

      for (let [propertyName, selector] of [
        ["messageDetails", ".messageDetails"],
        ["messageImage", ".messageImage"],
        ["messageText", ".messageText"],
        ["spacer", "spacer"],
        ["buttonContainer", ".messageDetails"],
        ["closeButton", ".messageCloseButton"],
      ]) {
        this[propertyName] = this.querySelector(selector);
      }
    }

    disconnectedCallback() {
      if (this.eventCallback) {
        this.eventCallback("disconnected");
      }
    }

    setButtons(aButtons) {
      for (let button of aButtons) {
        let buttonElem;

        let link = button.link;
        let localeId = button["l10n-id"];
        if (!link && button.supportPage) {
          link =
            Services.urlFormatter.formatURLPref("app.support.baseURL") +
            button.supportPage;
          if (!button.label && !localeId) {
            localeId = "notification-learnmore-default-label";
          }
        }

        if (link) {
          buttonElem = document.createXULElement("label", {
            is: "text-link",
          });
          buttonElem.setAttribute("href", link);
          buttonElem.classList.add("notification-link");
          buttonElem.onclick = (...args) => this._doButtonCommand(...args);
        } else {
          buttonElem = document.createXULElement(
            "button",
            button.is ? { is: button.is } : {}
          );
          buttonElem.classList.add("notification-button");

          if (button.primary) {
            buttonElem.classList.add("primary");
          }
        }

        if (localeId) {
          document.l10n.setAttributes(buttonElem, localeId);
        } else {
          buttonElem.setAttribute(link ? "value" : "label", button.label);
          if (typeof button.accessKey == "string") {
            buttonElem.setAttribute("accesskey", button.accessKey);
          }
        }

        if (link) {
          this.messageText.appendChild(buttonElem);
        } else {
          this.messageDetails.appendChild(buttonElem);
        }
        buttonElem.buttonInfo = button;
      }
    }

    get control() {
      return this.closest(".notificationbox-stack")._notificationBox;
    }

    /**
     * Changes the text of an existing notification. If the notification was
     * created with a custom fragment, it will be overwritten with plain text
     * or a localized message.
     *
     * @param {string | { "l10n-id": string, "l10n-args"?: string }} value
     */
    set label(value) {
      if (value && typeof value == "object" && "l10n-id" in value) {
        const message = document.createElement("span");
        document.l10n.setAttributes(
          message,
          value["l10n-id"],
          value["l10n-args"]
        );
        while (this.messageText.firstChild) {
          this.messageText.firstChild.remove();
        }
        this.messageText.appendChild(message);
      } else {
        this.messageText.textContent = value;
      }
    }

    /**
     * This method should only be called when the user has manually closed the
     * notification. If you want to programmatically close the notification, you
     * should call close() instead.
     */
    dismiss() {
      this._doTelemetry("dismissed");

      if (this.eventCallback) {
        this.eventCallback("dismissed");
      }
      this.close();
    }

    close() {
      if (!this.parentNode) {
        return;
      }
      this.control.removeNotification(this);
    }

    // This will be called when the host (such as a tabbrowser) determines that
    // the notification is made visible to the user.
    shown() {
      if (!this._shown) {
        this._shown = true;
        this._doTelemetry("shown");
      }
    }

    _doTelemetry(type) {
      if (
        this.telemetry &&
        (!this.telemetryFilter || this.telemetryFilter.includes(type))
      ) {
        Services.telemetry.keyedScalarAdd(this.telemetry, type, 1);
      }
    }

    _doButtonCommand(event) {
      if (!("buttonInfo" in event.target)) {
        return;
      }

      var button = event.target.buttonInfo;
      this._doTelemetry(button.telemetry || "action");

      if (button.popup) {
        document
          .getElementById(button.popup)
          .openPopup(
            event.originalTarget,
            "after_start",
            0,
            0,
            false,
            false,
            event
          );
        event.stopPropagation();
      } else {
        var callback = button.callback;
        if (callback) {
          var result = callback(this, button, event.target, event);
          if (!result) {
            this.close();
          }
          event.stopPropagation();
        }
      }
    }
  };

  customElements.define("notification", MozElements.Notification);

  async function createNotificationMessageElement() {
    document.createElement("moz-message-bar");
    let MozMessageBar = await customElements.whenDefined("moz-message-bar");
    class NotificationMessage extends MozMessageBar {
      static queries = {
        ...MozMessageBar.queries,
        messageText: ".message",
        messageImage: ".icon",
      };

      constructor() {
        super();
        this.persistence = 0;
        this.priority = 0;
        this.timeout = 0;
        this.telemetry = null;
        this.dismissable = true;
        this._shown = false;

        this.addEventListener("click", this);
        this.addEventListener("command", this);
      }

      connectedCallback() {
        super.connectedCallback();
        this.#setStyles();

        this.classList.add("infobar");
        this.setAlertRole();

        this.buttonContainer = document.createElement("span");
        this.buttonContainer.classList.add("notification-button-container");
        this.buttonContainer.setAttribute("slot", "actions");
        this.appendChild(this.buttonContainer);
      }

      disconnectedCallback() {
        super.disconnectedCallback();
        if (this.eventCallback) {
          this.eventCallback("disconnected");
        }
      }

      closeButtonTemplate() {
        return super.closeButtonTemplate({ size: "small" });
      }

      #setStyles() {
        let style = document.createElement("link");
        style.rel = "stylesheet";
        style.href = "chrome://global/content/elements/infobar.css";
        this.renderRoot.append(style);
      }

      _doTelemetry(type) {
        if (
          this.telemetry &&
          (!this.telemetryFilter || this.telemetryFilter.includes(type))
        ) {
          Services.telemetry.keyedScalarAdd(this.telemetry, type, 1);
        }
      }

      get control() {
        return this.closest(".notificationbox-stack")._notificationBox;
      }

      close() {
        if (!this.parentNode) {
          return;
        }
        this.control.removeNotification(this);
      }

      // This will be called when the host (such as a tabbrowser) determines that
      // the notification is made visible to the user.
      shown() {
        if (!this._shown) {
          this._shown = true;
          this._doTelemetry("shown");
        }
      }

      setAlertRole() {
        // Wait a little for this to render before setting the role for more
        // consistent alerts to screen readers.
        this.removeAttribute("role");
        window.requestAnimationFrame(() => {
          window.requestAnimationFrame(() => {
            this.setAttribute("role", "alert");
          });
        });
      }

      handleEvent(e) {
        if (e.type == "click" && e.target.localName != "label") {
          return;
        }

        if ("buttonInfo" in e.target) {
          let { buttonInfo } = e.target;
          let { callback, popup } = buttonInfo;

          this._doTelemetry(buttonInfo.telemetry || "action");

          if (popup) {
            document
              .getElementById(popup)
              .openPopup(
                e.originalTarget,
                "after_start",
                0,
                0,
                false,
                false,
                e
              );
            e.stopPropagation();
          } else if (callback) {
            if (!callback(this, buttonInfo, e.target, e)) {
              this.close();
            }
            e.stopPropagation();
          }
        }
      }

      /**
       * Changes the text of an existing notification. If the notification was
       * created with a custom fragment, it will be overwritten with plain text
       * or a localized message.
       *
       * @param {string | { "l10n-id": string, "l10n-args"?: string }} value
       */
      set label(value) {
        if (value && typeof value == "object" && "l10n-id" in value) {
          this.messageL10nId = value["l10n-id"];
          this.messageL10nArgs = value["l10n-args"];
        } else {
          this.message = value;
        }
        this.setAlertRole();
      }

      setButtons(buttons) {
        this._buttons = buttons;
        for (let button of buttons) {
          let link = button.link || button.supportPage;
          let localeId = button["l10n-id"];

          let buttonElem;
          if (button.hasOwnProperty("supportPage")) {
            buttonElem = document.createElement("a", {
              is: "moz-support-link",
            });
            buttonElem.classList.add("notification-link");
            buttonElem.setAttribute("support-page", button.supportPage);
          } else if (link) {
            buttonElem = document.createXULElement("label", {
              is: "text-link",
            });
            buttonElem.setAttribute("href", link);
            buttonElem.classList.add("notification-link", "text-link");
          } else {
            buttonElem = document.createXULElement(
              "button",
              button.is ? { is: button.is } : {}
            );
            buttonElem.classList.add(
              "notification-button",
              "small-button",
              "footer-button"
            );

            if (button.primary) {
              buttonElem.classList.add("primary");
            }
          }

          if (localeId) {
            document.l10n.setAttributes(buttonElem, localeId);
          } else {
            buttonElem.setAttribute(link ? "value" : "label", button.label);
            if (typeof button.accessKey == "string") {
              buttonElem.setAttribute("accesskey", button.accessKey);
            }
          }

          if (link) {
            buttonElem.setAttribute("slot", "support-link");
            this.appendChild(buttonElem);
          } else {
            this.buttonContainer.appendChild(buttonElem);
          }
          buttonElem.buttonInfo = button;
        }
      }

      dismiss() {
        this._doTelemetry("dismissed");

        if (this.eventCallback) {
          this.eventCallback("dismissed");
        }
        super.dismiss();
      }
    }
    if (!customElements.get("notification-message")) {
      customElements.define("notification-message", NotificationMessage);
    }
  }
}
PK
!<+_���5chrome/toolkit/content/global/elements/panel-item.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:host(:not([hidden])) {
  display: flex;
  align-items: center;
}

::slotted(a) {
  margin-inline-end: 12px;
}

:host button {
  -moz-context-properties: fill;
  fill: currentColor;
}

:host([checked]) button {
  background-image: url("chrome://global/skin/icons/check.svg");
}

button {
  background-color: transparent;
  color: inherit;
  background-position: 8px center;
  background-repeat: no-repeat;
  background-size: 16px;
  border: none;
  position: relative;
  display: block;
  font: inherit;
  padding: 4px 8px;
  padding-inline-start: 32px;
  text-align: start;
  width: 100%;
}

button:dir(rtl),
button:-moz-locale-dir(rtl) {
  background-position-x: right 8px;
}

:host([badged]) button::after {
  content: "";
  display: block;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background-color: var(--color-accent-primary);
  position: absolute;
  top: 4px;
  inset-inline-start: 24px;
}

button:enabled:hover {
  background-color: var(--button-background-color-hover);
  color: var(--button-text-color-hover);
}

button:enabled:hover:active {
  background-color: var(--button-background-color-active);
  color: var(--button-text-color-active);
}

button:focus-visible {
  outline-offset: var(--focus-outline-inset);
}

button:disabled {
  opacity: 0.4;
}

.submenu-container {
  display: flex;
  flex-direction: row;
}

.submenu-icon {
  display: inline-block;
  background-image: url("chrome://global/skin/icons/arrow-right.svg");
  background-position: center center;
  background-repeat: no-repeat;
  fill: currentColor;
  width: var(--size-item-small);
  height: var(--size-item-small);
  flex: 1 1 auto;

  &:dir(rtl) {
    background-image: url("chrome://global/skin/icons/arrow-left.svg");
  }
}

.submenu-label {
  flex: 90% 1 0;
}
PK
!<<�J1��5chrome/toolkit/content/global/elements/panel-list.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:host([showing]) {
  visibility: hidden;
}

:host(:not([open])) {
  display: none;
}

:host {
  position: absolute;
  font: menu;
  background-color: var(--background-color-box);
  color: var(--text-color);
  border-radius: 4px;
  padding: 6px 0;
  margin-bottom: 16px;
  box-shadow: var(--shadow-30);
  min-width: 12em;
  z-index: var(--z-index-popup, 10);
  white-space: nowrap;
  cursor: default;
  overflow-y: auto;
  box-sizing: border-box;
}

:host([has-submenu]) {
  overflow-y: visible;
}

:host(:not([slot=submenu])) {
  max-height: 100%;
}

:host([stay-open]) {
  position: initial;
  display: inline-block;
}

:host([inxulpanel]) {
  position: static;
  margin: 0;
}

:host(:not([inxulpanel])) {
  border: 1px solid var(--border-color-deemphasized);
}

.list {
  margin: 0;
  padding: 0;
}

::slotted(hr:not([hidden])) {
  display: block !important;
  height: 1px !important;
  background-color: var(--border-color-deemphasized) !important;
  color: inherit !important;
  padding: 0 !important;
  margin: 6px 0 !important;
  border: none !important;
}
PK
!<�n�]MiMi4chrome/toolkit/content/global/elements/panel-list.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

{
  class PanelList extends HTMLElement {
    static get observedAttributes() {
      return ["open"];
    }

    static get fragment() {
      if (!this._template) {
        let parser = new DOMParser();
        let cssPath = "chrome://global/content/elements/panel-list.css";
        let doc = parser.parseFromString(
          `
          <template>
            <link rel="stylesheet" href=${cssPath}>
            <div class="arrow top" role="presentation"></div>
            <div class="list" role="presentation">
              <slot></slot>
            </div>
            <div class="arrow bottom" role="presentation"></div>
          </template>
        `,
          "text/html"
        );
        this._template = document.importNode(
          doc.querySelector("template"),
          true
        );
      }
      return this._template.content.cloneNode(true);
    }

    constructor() {
      super();
      this.attachShadow({ mode: "open" });
      this.shadowRoot.appendChild(this.constructor.fragment);
    }

    connectedCallback() {
      this.setAttribute("role", "menu");
    }

    attributeChangedCallback(name, oldVal, newVal) {
      if (name == "open" && newVal != oldVal) {
        if (this.open) {
          this.onShow();
        } else {
          this.onHide();
        }
      }
    }

    get open() {
      return this.hasAttribute("open");
    }

    set open(val) {
      this.toggleAttribute("open", val);
    }

    get stayOpen() {
      return this.hasAttribute("stay-open");
    }

    set stayOpen(val) {
      this.toggleAttribute("stay-open", val);
    }

    getTargetForEvent(event) {
      if (!event) {
        return null;
      }
      if (event._savedComposedTarget) {
        return event._savedComposedTarget;
      }
      if (event.composed) {
        event._savedComposedTarget =
          event.composedTarget || event.composedPath()[0];
      }
      return event._savedComposedTarget || event.target;
    }

    show(triggeringEvent, target) {
      this.triggeringEvent = triggeringEvent;
      this.lastAnchorNode =
        target || this.getTargetForEvent(this.triggeringEvent);

      this.wasOpenedByKeyboard =
        triggeringEvent &&
        (triggeringEvent.inputSource == MouseEvent.MOZ_SOURCE_KEYBOARD ||
          triggeringEvent.inputSource == MouseEvent.MOZ_SOURCE_UNKNOWN ||
          triggeringEvent.code == "ArrowRight" ||
          triggeringEvent.code == "ArrowLeft");
      this.open = true;

      if (this.parentIsXULPanel()) {
        this.toggleAttribute("inxulpanel", true);
        let panel = this.parentElement;
        panel.hidden = false;
        // Bug 1842070 - There appears to be a race here where panel-lists
        // embedded in XUL panels won't appear during the first call to show()
        // without waiting for a mix of rAF and another tick of the event
        // loop.
        requestAnimationFrame(() => {
          setTimeout(() => {
            panel.openPopup(
              this.lastAnchorNode,
              "after_start",
              0,
              0,
              false,
              false,
              this.triggeringEvent
            );
          }, 0);
        });
      } else {
        this.toggleAttribute("inxulpanel", false);
      }
    }

    hide(triggeringEvent, { force = false } = {}, eventTarget) {
      // It's possible this is being used in an unprivileged context, in which
      // case it won't have access to Services / Services will be undeclared.
      const autohideDisabled = this.hasServices()
        ? Services.prefs.getBoolPref("ui.popup.disable_autohide", false)
        : false;

      if (autohideDisabled && !force) {
        // Don't hide if this wasn't "forced" (using escape or click in menu).
        return;
      }
      let openingEvent = this.triggeringEvent;
      this.triggeringEvent = triggeringEvent;
      this.open = false;

      if (this.parentIsXULPanel()) {
        // It's possible that we're being programattically hidden, in which
        // case, we need to hide the XUL panel we're embedded in. If, however,
        // we're being hidden because the XUL panel is being hidden, calling
        // hidePopup again on it is a no-op.
        let panel = this.parentElement;
        panel.hidePopup();
      }

      let target = eventTarget || this.getTargetForEvent(openingEvent);
      // Refocus the button that opened the menu if we have one.
      if (target && this.wasOpenedByKeyboard) {
        target.focus();
      }
    }

    toggle(triggeringEvent, target = null) {
      if (this.open) {
        this.hide(triggeringEvent, { force: true }, target);
      } else {
        this.show(triggeringEvent, target);
      }
    }

    hasServices() {
      // Safely check for Services without throwing a ReferenceError.
      return typeof Services !== "undefined";
    }

    isDocumentRTL() {
      if (this.hasServices()) {
        return Services.locale.isAppLocaleRTL;
      }
      return document.dir === "rtl";
    }

    parentIsXULPanel() {
      const XUL_NS =
        "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
      return (
        this.parentElement?.namespaceURI == XUL_NS &&
        this.parentElement?.localName == "panel"
      );
    }

    async setAlign() {
      const hostElement = this.parentElement || this.getRootNode().host;
      if (!hostElement) {
        // This could get called before we're added to the DOM.
        // Nothing to do in that case.
        return;
      }

      // Set the showing attribute to hide the panel until its alignment is set.
      this.setAttribute("showing", "true");
      // Tell the host element to hide any overflow in case the panel extends off
      // the page before the alignment is set.
      hostElement.style.overflow = "hidden";

      // Wait for a layout flush, then find the bounds.
      let {
        anchorBottom, // distance from the bottom of the anchor el to top of viewport.
        anchorLeft,
        anchorTop,
        anchorWidth,
        panelHeight,
        panelWidth,
        winHeight,
        winScrollY,
        winScrollX,
        clientWidth,
      } = await new Promise(resolve => {
        this.style.left = 0;
        this.style.top = 0;

        requestAnimationFrame(() =>
          setTimeout(() => {
            let target = this.getTargetForEvent(this.triggeringEvent);
            let anchorElement = target || hostElement;
            // It's possible this is being used in a context where windowUtils is
            // not available. In that case, fallback to using the element.
            let getBounds = el =>
              window.windowUtils
                ? window.windowUtils.getBoundsWithoutFlushing(el)
                : el.getBoundingClientRect();
            // Use y since top is reserved.
            let anchorBounds = getBounds(anchorElement);
            let panelBounds = getBounds(this);
            let clientWidth = document.scrollingElement.clientWidth;

            resolve({
              anchorBottom: anchorBounds.bottom,
              anchorHeight: anchorBounds.height,
              anchorLeft: anchorBounds.left,
              anchorTop: anchorBounds.top,
              anchorWidth: anchorBounds.width,
              panelHeight: panelBounds.height,
              panelWidth: panelBounds.width,
              winHeight: innerHeight,
              winScrollX: scrollX,
              winScrollY: scrollY,
              clientWidth,
            });
          }, 0)
        );
      });

      // If we're embedded in a XUL panel, let it handle alignment.
      if (!this.parentIsXULPanel()) {
        // Calculate the left/right alignment.
        let align;
        let leftOffset;
        let leftAlignX = anchorLeft;
        let rightAlignX = anchorLeft + anchorWidth - panelWidth;

        if (this.isDocumentRTL()) {
          // Prefer aligning on the right.
          align = rightAlignX < 0 ? "left" : "right";
        } else {
          // Prefer aligning on the left.
          align = leftAlignX + panelWidth > clientWidth ? "right" : "left";
        }
        leftOffset = align === "left" ? leftAlignX : rightAlignX;

        let bottomSpaceY = winHeight - anchorBottom;

        let valign;
        let topOffset;
        const VIEWPORT_PANEL_MIN_MARGIN = 10; // 10px ensures that the panel is not flush with the viewport.

        // Only want to valign top when there's more space between the bottom of the anchor element and the top of the viewport.
        // If there's more space between the bottom of the anchor element and the bottom of the viewport, we valign bottom.
        if (
          anchorBottom > bottomSpaceY &&
          anchorBottom + panelHeight > winHeight
        ) {
          // Never want to have a negative value for topOffset, so ensure it's at least 10px.
          topOffset = Math.max(
            anchorTop - panelHeight,
            VIEWPORT_PANEL_MIN_MARGIN
          );
          // Provide a max-height for larger elements which will provide scrolling as needed.
          this.style.maxHeight = `${anchorTop + VIEWPORT_PANEL_MIN_MARGIN}px`;
          valign = "top";
        } else {
          topOffset = anchorBottom;
          this.style.maxHeight = `${
            bottomSpaceY - VIEWPORT_PANEL_MIN_MARGIN
          }px`;
          valign = "bottom";
        }

        // Set the alignments and show the panel.
        this.setAttribute("align", align);
        this.setAttribute("valign", valign);
        hostElement.style.overflow = "";

        this.style.left = `${leftOffset + winScrollX}px`;
        this.style.top = `${topOffset + winScrollY}px`;
      }

      this.style.minWidth = this.hasAttribute("min-width-from-anchor")
        ? `${anchorWidth}px`
        : "";

      this.removeAttribute("showing");
    }

    addHideListeners() {
      if (this.hasAttribute("stay-open") && !this.lastAnchorNode?.hasSubmenu) {
        // This is intended for inspection in Storybook.
        return;
      }
      // Hide when a panel-item is clicked in the list.
      this.addEventListener("click", this);
      // Allows submenus to stopPropagation when focus is already in the menu
      this.addEventListener("keydown", this);
      // We need Escape/Tab/ArrowDown to work when opened with the mouse.
      document.addEventListener("keydown", this);
      // Hide when a click is initiated outside the panel.
      document.addEventListener("mousedown", this);
      // Hide if focus changes and the panel isn't in focus.
      document.addEventListener("focusin", this);
      // Reset or focus tracking, we treat the first focusin differently.
      this.focusHasChanged = false;
      // Hide on resize, scroll or losing window focus.
      window.addEventListener("resize", this);
      window.addEventListener("scroll", this, { capture: true });
      window.addEventListener("blur", this);
      if (this.parentIsXULPanel()) {
        this.parentElement.addEventListener("popuphidden", this);
      }
    }

    removeHideListeners() {
      this.removeEventListener("click", this);
      this.removeEventListener("keydown", this);
      document.removeEventListener("keydown", this);
      document.removeEventListener("mousedown", this);
      document.removeEventListener("focusin", this);
      window.removeEventListener("resize", this);
      window.removeEventListener("scroll", this, { capture: true });
      window.removeEventListener("blur", this);
      if (this.parentIsXULPanel()) {
        this.parentElement.removeEventListener("popuphidden", this);
      }
    }

    handleEvent(e) {
      // Ignore the event if it caused the panel to open.
      if (e == this.triggeringEvent) {
        return;
      }

      let target = this.getTargetForEvent(e);
      let inPanelList = e.composed
        ? e.composedPath().some(el => el == this)
        : e.target.closest && e.target.closest("panel-list") == this;

      switch (e.type) {
        case "resize":
        case "scroll":
          if (inPanelList) {
            break;
          }
        // Intentional fall-through
        case "blur":
        case "popuphidden":
          this.hide();
          break;
        case "click":
          if (inPanelList) {
            this.hide(undefined, { force: true });
          } else {
            // Avoid falling through to the default click handler of the parent.
            e.stopPropagation();
          }
          break;
        case "mousedown":
          // Close if there's a click started outside the panel.
          if (!inPanelList) {
            this.hide();
          }
          break;
        case "keydown":
          if (e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "Tab") {
            // Ignore tabbing with a modifer other than shift.
            if (e.key === "Tab" && (e.altKey || e.ctrlKey || e.metaKey)) {
              return;
            }

            // Don't scroll the page or let the regular tab order take effect.
            e.preventDefault();

            // Prevents the host panel list from responding to these events while
            // the submenu is active.
            e.stopPropagation();

            // Keep moving to the next/previous element sibling until we find a
            // panel-item that isn't hidden.
            let moveForward =
              e.key === "ArrowDown" || (e.key === "Tab" && !e.shiftKey);

            let nextItem = moveForward
              ? this.focusWalker.nextNode()
              : this.focusWalker.previousNode();

            // If the next item wasn't found, try looping to the top/bottom.
            if (!nextItem) {
              this.focusWalker.currentNode = this;
              if (moveForward) {
                nextItem = this.focusWalker.firstChild();
              } else {
                nextItem = this.focusWalker.lastChild();
              }
            }
            break;
          } else if (e.key === "Escape") {
            this.hide(undefined, { force: true });
          } else if (!e.metaKey && !e.ctrlKey && !e.shiftKey && !e.altKey) {
            // Check if any of the children have an accesskey for this letter.
            let item = this.querySelector(
              `[accesskey="${e.key.toLowerCase()}"],
              [accesskey="${e.key.toUpperCase()}"]`
            );
            if (item) {
              item.click();
            }
          }
          break;
        case "focusin":
          if (
            this.triggeringEvent &&
            target == this.getTargetForEvent(this.triggeringEvent) &&
            !this.focusHasChanged
          ) {
            // There will be a focusin after the mousedown that opens the panel
            // using the mouse. Ignore the first focusin event if it's on the
            // triggering target.
            this.focusHasChanged = true;
          } else {
            // Just record that there was a focusin event.
            this.focusHasChanged = true;
          }
          break;
      }
    }

    /**
     * A TreeWalker that can be used to focus elements. The returned element will
     * be the element that has gained focus based on the requested movement
     * through the tree.
     *
     * Example:
     *
     *   this.focusWalker.currentNode = this;
     *   // Focus and get the first focusable child.
     *   let focused = this.focusWalker.nextNode();
     *   // Focus the second focusable child.
     *   this.focusWalker.nextNode();
     */
    get focusWalker() {
      if (!this._focusWalker) {
        this._focusWalker = document.createTreeWalker(
          this,
          NodeFilter.SHOW_ELEMENT,
          {
            acceptNode: node => {
              // No need to look at hidden nodes.
              if (node.hidden) {
                return NodeFilter.FILTER_REJECT;
              }

              // Focus the node, if it worked then this is the node we want.
              node.focus();
              if (node === node.getRootNode().activeElement) {
                return NodeFilter.FILTER_ACCEPT;
              }

              // Continue into child nodes if the parent couldn't be focused.
              return NodeFilter.FILTER_SKIP;
            },
          }
        );
      }
      return this._focusWalker;
    }
    async setSubmenuAlign() {
      const hostElement =
        this.lastAnchorNode.parentElement || this.getRootNode().host;
      // The showing attribute allows layout of the panel while remaining hidden
      // from the user until alignment is set.
      this.setAttribute("showing", "true");

      // Wait for a layout flush, then find the bounds.
      let {
        anchorLeft,
        anchorWidth,
        anchorTop,
        parentPanelTop,
        panelWidth,
        clientWidth,
      } = await new Promise(resolve => {
        requestAnimationFrame(() => {
          // It's possible this is being used in a context where windowUtils is
          // not available. In that case, fallback to using the element.
          let getBounds = el =>
            window.windowUtils
              ? window.windowUtils.getBoundsWithoutFlushing(el)
              : el.getBoundingClientRect();
          // submenu item in the parent panel list
          let anchorBounds = getBounds(this.lastAnchorNode);
          let parentPanelBounds = getBounds(hostElement);
          let panelBounds = getBounds(this);
          let clientWidth = document.scrollingElement.clientWidth;

          resolve({
            anchorLeft: anchorBounds.left,
            anchorWidth: anchorBounds.width,
            anchorTop: anchorBounds.top,
            parentPanelTop: parentPanelBounds.top,
            panelWidth: panelBounds.width,
            clientWidth,
          });
        });
      });

      let align = hostElement.getAttribute("align");

      // we use document.scrollingElement.clientWidth to exclude the width
      // of vertical scrollbars, because its inclusion can cause the submenu
      // to open to the wrong side and be overlapped by the scrollbar.
      if (
        align == "left" &&
        anchorLeft + anchorWidth + panelWidth < clientWidth
      ) {
        this.style.left = `${anchorWidth}px`;
        this.style.right = "";
      } else {
        this.style.right = `${anchorWidth}px`;
        this.style.left = "";
      }

      let topOffset =
        anchorTop -
        parentPanelTop -
        (parseFloat(window.getComputedStyle(this)?.paddingTop) || 0);
      this.style.top = `${topOffset}px`;

      this.removeAttribute("showing");
    }

    async onShow() {
      this.sendEvent("showing");
      this.addHideListeners();

      if (this.lastAnchorNode?.hasSubmenu) {
        await this.setSubmenuAlign();
      } else {
        await this.setAlign();
      }

      // Always reset this regardless of how the panel list is opened
      // so the first child will be focusable.
      this.focusWalker.currentNode = this;

      // Wait until the next paint for the alignment to be set and panel to be
      // visible.
      requestAnimationFrame(() => {
        if (this.wasOpenedByKeyboard) {
          // Focus the first focusable panel-item if opened by keyboard.
          this.focusWalker.nextNode();
        }

        this.lastAnchorNode?.setAttribute("aria-expanded", "true");

        this.sendEvent("shown");
      });
    }

    onHide() {
      requestAnimationFrame(() => {
        this.sendEvent("hidden");
        this.lastAnchorNode?.setAttribute("aria-expanded", "false");
      });
      this.removeHideListeners();
    }

    sendEvent(name, detail) {
      this.dispatchEvent(
        new CustomEvent(name, { detail, bubbles: true, composed: true })
      );
    }
  }
  customElements.define("panel-list", PanelList);

  class PanelItem extends HTMLElement {
    #initialized = false;
    #defaultSlot;

    static get observedAttributes() {
      return ["accesskey"];
    }

    constructor() {
      super();
      this.attachShadow({ mode: "open" });

      let style = document.createElement("link");
      style.rel = "stylesheet";
      style.href = "chrome://global/content/elements/panel-item.css";

      this.button = document.createElement("button");
      this.button.setAttribute("role", "menuitem");
      this.button.setAttribute("part", "button");
      // Use a XUL label element if possible to show the accesskey.
      this.label = document.createXULElement
        ? document.createXULElement("label")
        : document.createElement("span");

      this.button.appendChild(this.label);

      let supportLinkSlot = document.createElement("slot");
      supportLinkSlot.name = "support-link";

      this.#defaultSlot = document.createElement("slot");
      this.#defaultSlot.style.display = "none";

      this.shadowRoot.append(
        style,
        this.button,
        supportLinkSlot,
        this.#defaultSlot
      );
    }

    connectedCallback() {
      if (!this._l10nRootConnected && document.l10n) {
        document.l10n.connectRoot(this.shadowRoot);
        this._l10nRootConnected = true;
      }

      this.panel =
        this.getRootNode()?.host?.closest("panel-list") ||
        this.closest("panel-list");

      if (!this.#initialized) {
        this.#initialized = true;
        // When click listeners are added to the panel-item it creates a node in
        // the a11y tree for this element. This breaks the association between the
        // menu and the button[role="menuitem"] in this shadow DOM and causes
        // announcement issues with screen readers. (bug 995064)
        this.setAttribute("role", "presentation");

        this.#setLabelContents();

        // When our content changes, move the text into the label. It doesn't work
        // with a <slot>, unfortunately.
        new MutationObserver(() => this.#setLabelContents()).observe(this, {
          characterData: true,
          childList: true,
          subtree: true,
        });

        if (this.hasSubmenu) {
          this.panel.setAttribute("has-submenu", "");
          this.icon = document.createElement("div");
          this.icon.setAttribute("class", "submenu-icon");
          this.label.setAttribute("class", "submenu-label");

          this.button.setAttribute("class", "submenu-container");
          this.button.appendChild(this.icon);

          this.submenuSlot = document.createElement("slot");
          this.submenuSlot.name = "submenu";

          this.shadowRoot.append(this.submenuSlot);

          this.setSubmenuContents();
        }
      }

      if (this.panel) {
        this.panel.addEventListener("hidden", this);
        this.panel.addEventListener("shown", this);
      }

      if (this.hasSubmenu) {
        this.addEventListener("mouseenter", this);
        this.addEventListener("mouseleave", this);
        this.addEventListener("keydown", this);
      }
    }

    disconnectedCallback() {
      if (this._l10nRootConnected) {
        document.l10n.disconnectRoot(this.shadowRoot);
        this._l10nRootConnected = false;
      }

      if (this.panel) {
        this.panel.removeEventListener("hidden", this);
        this.panel.removeEventListener("shown", this);
        this.panel = null;
      }

      if (this.hasSubmenu) {
        this.removeEventListener("mouseenter", this);
        this.removeEventListener("mouseleave", this);
        this.removeEventListener("keydown", this);
      }
    }

    get hasSubmenu() {
      return this.hasAttribute("submenu");
    }

    attributeChangedCallback(name, oldVal, newVal) {
      if (name === "accesskey") {
        // Bug 1037709 - Accesskey doesn't work in shadow DOM.
        // Ideally we'd have the accesskey set in shadow DOM, and on
        // attributeChangedCallback we'd just update the shadow DOM accesskey.

        // Skip this change event if we caused it.
        if (this._modifyingAccessKey) {
          this._modifyingAccessKey = false;
          return;
        }

        this.label.accessKey = newVal || "";

        // Bug 1588156 - Accesskey is not ignored for hidden non-input elements.
        // Since the accesskey won't be ignored, we need to remove it ourselves
        // when the panel is closed, and move it back when it opens.
        if (!this.panel || !this.panel.open) {
          // When the panel isn't open, just store the key for later.
          this._accessKey = newVal || null;
          this._modifyingAccessKey = true;
          this.accessKey = "";
        } else {
          this._accessKey = null;
        }
      }
    }

    #setLabelContents() {
      this.label.textContent = this.#defaultSlot
        .assignedNodes()
        .map(node => node.textContent)
        .join("");
    }

    setSubmenuContents() {
      this.submenuPanel = this.submenuSlot.assignedNodes()[0];
      if (this.submenuPanel) {
        this.shadowRoot.append(this.submenuPanel);
      }
    }

    get disabled() {
      return this.button.hasAttribute("disabled");
    }

    set disabled(val) {
      this.button.toggleAttribute("disabled", val);
    }

    get checked() {
      return this.hasAttribute("checked");
    }

    set checked(val) {
      this.toggleAttribute("checked", val);
    }

    focus() {
      this.button.focus();
    }

    setArrowKeyRTL() {
      let arrowOpenKey = "ArrowRight";
      let arrowCloseKey = "ArrowLeft";

      if (this.submenuPanel.isDocumentRTL()) {
        arrowOpenKey = "ArrowLeft";
        arrowCloseKey = "ArrowRight";
      }
      return [arrowOpenKey, arrowCloseKey];
    }

    handleEvent(e) {
      // Bug 1588156 - Accesskey is not ignored for hidden non-input elements.
      // Since the accesskey won't be ignored, we need to remove it ourselves
      // when the panel is closed, and move it back when it opens.
      switch (e.type) {
        case "shown":
          if (this._accessKey) {
            this.accessKey = this._accessKey;
            this._accessKey = null;
          }
          break;
        case "hidden":
          if (this.accessKey) {
            this._accessKey = this.accessKey;
            this._modifyingAccessKey = true;
            this.accessKey = "";
          }
          break;
        case "mouseenter":
        case "mouseleave":
          this.submenuPanel.toggle(e);
          break;
        case "keydown":
          let [arrowOpenKey, arrowCloseKey] = this.setArrowKeyRTL();
          if (e.key === arrowOpenKey) {
            this.submenuPanel.show(e, e.target);
            e.stopPropagation();
          }
          if (e.key === arrowCloseKey) {
            this.submenuPanel.hide(e, { force: true }, e.target);
            e.stopPropagation();
          }
          break;
      }
    }
  }
  customElements.define("panel-item", PanelItem);
}
PK
!<�M�U�"�"/chrome/toolkit/content/global/elements/panel.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
  class MozPanel extends MozElements.MozElementMixin(XULPopupElement) {
    static get markup() {
      return `<html:slot part="content" style="display: none !important"/>`;
    }
    constructor() {
      super();

      this._prevFocus = 0;
      this._fadeTimer = null;

      this.attachShadow({ mode: "open" });

      this.addEventListener("popupshowing", this);
      this.addEventListener("popupshown", this);
      this.addEventListener("popuphiding", this);
      this.addEventListener("popuphidden", this);
      this.addEventListener("popuppositioned", this);
    }

    connectedCallback() {
      // Create shadow DOM lazily if a panel is hidden. It helps to reduce
      // cycles on startup.
      if (!this.hidden) {
        this.ensureInitialized();
      }

      if (this.isArrowPanel) {
        if (!this.hasAttribute("flip")) {
          this.setAttribute("flip", "both");
        }
        if (!this.hasAttribute("side")) {
          this.setAttribute("side", "top");
        }
        if (!this.hasAttribute("position")) {
          this.setAttribute("position", "bottomleft topleft");
        }
        if (!this.hasAttribute("consumeoutsideclicks")) {
          this.setAttribute("consumeoutsideclicks", "false");
        }
      }
    }

    ensureInitialized() {
      // As an optimization, we don't slot contents if the panel is [hidden] in
      // connectedCallback this means we can avoid running this code at startup
      // and only need to do it when a panel is about to be shown.  We then
      // override the `hidden` setter and `removeAttribute` and call this
      // function if the node is about to be shown.
      if (this.shadowRoot.firstChild) {
        return;
      }

      this.shadowRoot.appendChild(this.constructor.fragment);
      if (this.hasAttribute("neverhidden")) {
        this.panelContent.style.display = "";
      }
    }

    get panelContent() {
      return this.shadowRoot.querySelector("[part=content]");
    }

    get hidden() {
      return super.hidden;
    }

    set hidden(v) {
      if (!v) {
        this.ensureInitialized();
      }
      super.hidden = v;
    }

    removeAttribute(name) {
      if (name == "hidden") {
        this.ensureInitialized();
      }
      super.removeAttribute(name);
    }

    get isArrowPanel() {
      return this.getAttribute("type") == "arrow";
    }

    get noOpenOnAnchor() {
      return this.hasAttribute("no-open-on-anchor");
    }

    _setSideAttribute(event) {
      if (!this.isArrowPanel || !event.isAnchored) {
        return;
      }

      let position = event.alignmentPosition;
      if (position.indexOf("start_") == 0 || position.indexOf("end_") == 0) {
        // The assigned side stays the same regardless of direction.
        let isRTL = window.getComputedStyle(this).direction == "rtl";

        if (position.indexOf("start_") == 0) {
          this.setAttribute("side", isRTL ? "left" : "right");
        } else {
          this.setAttribute("side", isRTL ? "right" : "left");
        }
      } else if (
        position.indexOf("before_") == 0 ||
        position.indexOf("after_") == 0
      ) {
        if (position.indexOf("before_") == 0) {
          this.setAttribute("side", "bottom");
        } else {
          this.setAttribute("side", "top");
        }
      }

      // This method isn't implemented by panel.js, but it can be added to
      // individual instances that need to show an arrow.
      this.setArrowPosition?.(event);
    }

    on_popupshowing(event) {
      if (event.target == this) {
        this.panelContent.style.display = "";
      }
      if (this.isArrowPanel && event.target == this) {
        if (this.anchorNode && !this.noOpenOnAnchor) {
          let anchorRoot =
            this.anchorNode.closest("toolbarbutton, .anchor-root") ||
            this.anchorNode;
          anchorRoot.setAttribute("open", "true");
        }

        if (this.getAttribute("animate") != "false") {
          this.setAttribute("animate", "open");
          // the animating attribute prevents user interaction during transition
          // it is removed when popupshown fires
          this.setAttribute("animating", "true");
        }

        // set fading
        var fade = this.getAttribute("fade");
        var fadeDelay = 0;
        if (fade == "fast") {
          fadeDelay = 1;
        } else if (fade == "slow") {
          fadeDelay = 4000;
        }

        if (fadeDelay != 0) {
          this._fadeTimer = setTimeout(
            () => this.hidePopup(true),
            fadeDelay,
            this
          );
        }
      }

      // Capture the previous focus before has a chance to get set inside the panel
      try {
        this._prevFocus = Cu.getWeakReference(
          document.commandDispatcher.focusedElement
        );
        if (!this._prevFocus.get()) {
          this._prevFocus = Cu.getWeakReference(document.activeElement);
        }
      } catch (ex) {
        this._prevFocus = Cu.getWeakReference(document.activeElement);
      }
    }

    on_popupshown(event) {
      if (this.isArrowPanel && event.target == this) {
        this.removeAttribute("animating");
        this.setAttribute("panelopen", "true");
      }

      // Fire event for accessibility APIs
      let alertEvent = document.createEvent("Events");
      alertEvent.initEvent("AlertActive", true, true);
      this.dispatchEvent(alertEvent);
    }

    on_popuphiding(event) {
      if (this.isArrowPanel && event.target == this) {
        let animate = this.getAttribute("animate") != "false";

        if (this._fadeTimer) {
          clearTimeout(this._fadeTimer);
          if (animate) {
            this.setAttribute("animate", "fade");
          }
        } else if (animate) {
          this.setAttribute("animate", "cancel");
        }

        if (this.anchorNode && !this.noOpenOnAnchor) {
          let anchorRoot =
            this.anchorNode.closest("toolbarbutton, .anchor-root") ||
            this.anchorNode;
          anchorRoot.removeAttribute("open");
        }
      }

      try {
        this._currentFocus = document.commandDispatcher.focusedElement;
      } catch (e) {
        this._currentFocus = document.activeElement;
      }
    }

    on_popuphidden(event) {
      if (event.target == this && !this.hasAttribute("neverhidden")) {
        this.panelContent.style.setProperty("display", "none", "important");
      }
      if (this.isArrowPanel && event.target == this) {
        this.removeAttribute("panelopen");
        if (this.getAttribute("animate") != "false") {
          this.removeAttribute("animate");
        }
      }

      function doFocus() {
        // Focus was set on an element inside this panel,
        // so we need to move it back to where it was previously.
        // Elements can use refocused-by-panel to change their focus behaviour
        // when re-focused by a panel hiding.
        prevFocus.setAttribute("refocused-by-panel", true);
        try {
          let fm = Services.focus;
          fm.setFocus(prevFocus, fm.FLAG_NOSCROLL);
        } catch (e) {
          prevFocus.focus();
        }
        prevFocus.removeAttribute("refocused-by-panel");
      }
      var currentFocus = this._currentFocus;
      var prevFocus = this._prevFocus ? this._prevFocus.get() : null;
      this._currentFocus = null;
      this._prevFocus = null;

      // Avoid changing focus if focus changed while we hide the popup
      // (This can happen e.g. if the popup is hiding as a result of a
      // click/keypress that focused something)
      let nowFocus;
      try {
        nowFocus = document.commandDispatcher.focusedElement;
      } catch (e) {
        nowFocus = document.activeElement;
      }
      if (nowFocus && nowFocus != currentFocus) {
        return;
      }

      if (prevFocus && this.getAttribute("norestorefocus") != "true") {
        // Try to restore focus
        try {
          if (document.commandDispatcher.focusedWindow != window) {
            // Focus has already been set to a window outside of this panel
            return;
          }
        } catch (ex) {}

        if (!currentFocus) {
          doFocus();
          return;
        }
        while (currentFocus) {
          if (currentFocus == this) {
            doFocus();
            return;
          }
          currentFocus = currentFocus.parentNode;
        }
      }
    }

    on_popuppositioned(event) {
      if (event.target == this) {
        this._setSideAttribute(event);
      }
    }
  }

  customElements.define("panel", MozPanel);
}
PK
!<z��;chrome/toolkit/content/global/elements/popupnotification.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
  class MozPopupNotification extends MozXULElement {
    static get inheritedAttributes() {
      return {
        ".popup-notification-icon": "popupid,src=icon,class=iconclass,hasicon",
        ".popup-notification-body": "popupid",
        ".popup-notification-origin": "value=origin,tooltiptext=origin",
        ".popup-notification-description": "popupid,id=descriptionid",
        ".popup-notification-description > span:first-of-type":
          "text=label,popupid",
        ".popup-notification-description > b:first-of-type":
          "text=name,popupid",
        ".popup-notification-description > span:nth-of-type(2)":
          "text=endlabel,popupid",
        ".popup-notification-description > b:last-of-type":
          "text=secondname,popupid",
        ".popup-notification-description > span:last-of-type":
          "text=secondendlabel,popupid",
        ".popup-notification-hint-text": "text=hinttext",
        ".popup-notification-closebutton":
          "oncommand=closebuttoncommand,hidden=closebuttonhidden",
        ".popup-notification-learnmore-link":
          "onclick=learnmoreclick,href=learnmoreurl",
        ".popup-notification-warning": "hidden=warninghidden,text=warninglabel",
        ".popup-notification-secondary-button":
          "oncommand=secondarybuttoncommand,label=secondarybuttonlabel,accesskey=secondarybuttonaccesskey,hidden=secondarybuttonhidden,dropmarkerhidden",
        ".popup-notification-dropmarker":
          "onpopupshown=dropmarkerpopupshown,hidden=dropmarkerhidden",
        ".popup-notification-dropmarker > menupopup": "oncommand=menucommand",
        ".popup-notification-primary-button":
          "oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey,default=buttonhighlight,disabled=mainactiondisabled",
      };
    }

    attributeChangedCallback(name, oldValue, newValue) {
      if (!this._hasSlotted) {
        return;
      }

      // If the label and/or accesskey for the primary button is set by
      // inherited attributes, its data-l10n-id needs to be unset or
      // DOM Localization will overwrite the values.
      if (name === "buttonlabel" || name === "buttonaccesskey") {
        this.button?.removeAttribute("data-l10n-id");
      }

      super.attributeChangedCallback(name, oldValue, newValue);
    }

    show() {
      this.slotContents();

      if (this.checkboxState) {
        this.checkbox.checked = this.checkboxState.checked;
        this.checkbox.setAttribute("label", this.checkboxState.label);
        this.checkbox.hidden = false;
      } else {
        this.checkbox.hidden = true;
        // Reset checked state to avoid wrong using of previous value.
        this.checkbox.checked = false;
      }

      this.hidden = false;
    }

    static get markup() {
      return `
      <hbox class="popup-notification-header-container"></hbox>
      <hbox align="start" class="popup-notification-body-container">
        <image class="popup-notification-icon"/>
        <vbox pack="start" class="popup-notification-body">
          <label class="popup-notification-origin header" crop="center"></label>
          <!-- These need to be on the same line to avoid creating
              whitespace between them (whitespace is added in the
              localization file, if necessary). -->
          <description class="popup-notification-description"><html:span></html:span><html:b></html:b><html:span></html:span><html:b></html:b><html:span></html:span></description>
          <description class="popup-notification-hint-text"></description>
          <vbox class="popup-notification-bottom-content" align="start">
            <label class="popup-notification-learnmore-link" is="text-link" data-l10n-id="popup-notification-learn-more"></label>
            <checkbox class="popup-notification-checkbox" oncommand="PopupNotifications._onCheckboxCommand(event)"/>
            <description class="popup-notification-warning"/>
          </vbox>
        </vbox>
        <toolbarbutton class="messageCloseButton close-icon popup-notification-closebutton tabbable" data-l10n-id="close-notification-message"></toolbarbutton>
      </hbox>
      <hbox class="popup-notification-footer-container"></hbox>
      <html:moz-button-group class="panel-footer">
        <button class="popup-notification-secondary-button footer-button"/>
        <button type="menu" class="popup-notification-dropmarker footer-button" data-l10n-id="popup-notification-more-actions-button">
          <menupopup position="after_end" data-l10n-id="popup-notification-more-actions-button">
          </menupopup>
        </button>
        <button class="popup-notification-primary-button primary footer-button" data-l10n-id="popup-notification-default-button"/>
      </html:moz-button-group>
      `;
    }

    slotContents() {
      if (this._hasSlotted) {
        return;
      }
      this._hasSlotted = true;
      MozXULElement.insertFTLIfNeeded("toolkit/global/notification.ftl");
      MozXULElement.insertFTLIfNeeded("toolkit/global/popupnotification.ftl");
      this.appendChild(this.constructor.fragment);

      this.button = this.querySelector(".popup-notification-primary-button");
      if (
        this.hasAttribute("buttonlabel") ||
        this.hasAttribute("buttonaccesskey")
      ) {
        this.button.removeAttribute("data-l10n-id");
      }
      this.secondaryButton = this.querySelector(
        ".popup-notification-secondary-button"
      );
      this.checkbox = this.querySelector(".popup-notification-checkbox");
      this.closebutton = this.querySelector(".popup-notification-closebutton");
      this.menubutton = this.querySelector(".popup-notification-dropmarker");
      this.menupopup = this.menubutton.querySelector("menupopup");

      let popupnotificationfooter = this.querySelector(
        "popupnotificationfooter"
      );
      if (popupnotificationfooter) {
        this.querySelector(".popup-notification-footer-container").append(
          popupnotificationfooter
        );
      }

      let popupnotificationheader = this.querySelector(
        "popupnotificationheader"
      );
      if (popupnotificationheader) {
        this.querySelector(".popup-notification-header-container").append(
          popupnotificationheader
        );
      }

      for (let popupnotificationcontent of this.querySelectorAll(
        "popupnotificationcontent"
      )) {
        this.appendNotificationContent(popupnotificationcontent);
      }

      this.initializeAttributeInheritance();
    }

    appendNotificationContent(el) {
      this.querySelector(".popup-notification-bottom-content").before(el);
    }
  }

  customElements.define("popupnotification", MozPopupNotification);
}
PK
!<=V�X==/chrome/toolkit/content/global/elements/radio.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
(() => {
  class MozRadiogroup extends MozElements.BaseControl {
    constructor() {
      super();

      this.addEventListener("mousedown", event => {
        if (this.disabled) {
          event.preventDefault();
        }
      });

      /**
       * keyboard navigation  Here's how keyboard navigation works in radio groups on Windows:
       * The group takes 'focus'
       * The user is then free to navigate around inside the group
       * using the arrow keys. Accessing previous or following radio buttons
       * is done solely through the arrow keys and not the tab button. Tab
       * takes you to the next widget in the tab order
       */
      this.addEventListener("keypress", event => {
        if (event.key != " " || event.originalTarget != this) {
          return;
        }
        this.selectedItem = this.focusedItem;
        this.selectedItem.doCommand();
        // Prevent page from scrolling on the space key.
        event.preventDefault();
      });

      this.addEventListener("keypress", event => {
        if (
          event.keyCode != KeyEvent.DOM_VK_UP ||
          event.originalTarget != this
        ) {
          return;
        }
        this.checkAdjacentElement(false);
        event.stopPropagation();
        event.preventDefault();
      });

      this.addEventListener("keypress", event => {
        if (
          event.keyCode != KeyEvent.DOM_VK_LEFT ||
          event.originalTarget != this
        ) {
          return;
        }
        // left arrow goes back when we are ltr, forward when we are rtl
        this.checkAdjacentElement(
          document.defaultView.getComputedStyle(this).direction == "rtl"
        );
        event.stopPropagation();
        event.preventDefault();
      });

      this.addEventListener("keypress", event => {
        if (
          event.keyCode != KeyEvent.DOM_VK_DOWN ||
          event.originalTarget != this
        ) {
          return;
        }
        this.checkAdjacentElement(true);
        event.stopPropagation();
        event.preventDefault();
      });

      this.addEventListener("keypress", event => {
        if (
          event.keyCode != KeyEvent.DOM_VK_RIGHT ||
          event.originalTarget != this
        ) {
          return;
        }
        // right arrow goes forward when we are ltr, back when we are rtl
        this.checkAdjacentElement(
          document.defaultView.getComputedStyle(this).direction == "ltr"
        );
        event.stopPropagation();
        event.preventDefault();
      });

      /**
       * set a focused attribute on the selected item when the group
       * receives focus so that we can style it as if it were focused even though
       * it is not (Windows platform behaviour is for the group to receive focus,
       * not the item
       */
      this.addEventListener("focus", event => {
        if (event.originalTarget != this) {
          return;
        }
        this.setAttribute("focused", "true");
        if (this.focusedItem) {
          return;
        }

        var val = this.selectedItem;
        if (!val || val.disabled || val.hidden || val.collapsed) {
          var children = this._getRadioChildren();
          for (var i = 0; i < children.length; ++i) {
            if (
              !children[i].hidden &&
              !children[i].collapsed &&
              !children[i].disabled
            ) {
              val = children[i];
              break;
            }
          }
        }
        this.focusedItem = val;
      });

      this.addEventListener("blur", event => {
        if (event.originalTarget != this) {
          return;
        }
        this.removeAttribute("focused");
        this.focusedItem = null;
      });
    }

    connectedCallback() {
      if (this.delayConnectedCallback()) {
        return;
      }

      // When this is called via `connectedCallback` there are two main variations:
      //   1) The radiogroup and radio children are defined in markup.
      //   2) We are appending a DocumentFragment
      // In both cases, the <radiogroup> connectedCallback fires first. But in (2),
      // the children <radio>s won't be upgraded yet, so r.control will be undefined.
      // To avoid churn in this case where we would have to reinitialize the list as each
      // child radio gets upgraded as a result of init(), ignore the resulting calls
      // to radioAttached.
      this.ignoreRadioChildConstruction = true;
      this.init();
      this.ignoreRadioChildConstruction = false;
      if (!this.value) {
        this.selectedIndex = 0;
      }
    }

    init() {
      this._radioChildren = null;

      if (this.getAttribute("disabled") == "true") {
        this.disabled = true;
      }

      var children = this._getRadioChildren();
      var length = children.length;
      for (var i = 0; i < length; i++) {
        if (children[i].getAttribute("selected") == "true") {
          this.selectedIndex = i;
          return;
        }
      }

      var value = this.value;
      if (value) {
        this.value = value;
      }
    }

    /**
     * Called when a new <radio> gets added to an already connected radiogroup.
     * This can happen due to DOM getting appended after the <radiogroup> is created.
     * When this happens, reinitialize the UI if necessary to make sure the state is
     * consistent.
     *
     * @param {DOMNode} child
     *                  The <radio> element that got added
     */
    radioAttached(child) {
      if (this.ignoreRadioChildConstruction) {
        return;
      }
      if (!this._radioChildren || !this._radioChildren.includes(child)) {
        this.init();
      }
    }

    /**
     * Called when a new <radio> gets removed from a radio group.
     *
     * @param {DOMNode} child
     *                  The <radio> element that got removed
     */
    radioUnattached() {
      // Just invalidate the cache, next time it's fetched it'll get rebuilt.
      this._radioChildren = null;
    }

    set value(val) {
      this.setAttribute("value", val);
      var children = this._getRadioChildren();
      for (var i = 0; i < children.length; i++) {
        if (String(children[i].value) == String(val)) {
          this.selectedItem = children[i];
          break;
        }
      }
    }

    get value() {
      return this.getAttribute("value") || "";
    }

    set disabled(val) {
      if (val) {
        this.setAttribute("disabled", "true");
      } else {
        this.removeAttribute("disabled");
      }
      var children = this._getRadioChildren();
      for (var i = 0; i < children.length; ++i) {
        children[i].disabled = val;
      }
    }

    get disabled() {
      if (this.getAttribute("disabled") == "true") {
        return true;
      }
      var children = this._getRadioChildren();
      for (var i = 0; i < children.length; ++i) {
        if (
          !children[i].hidden &&
          !children[i].collapsed &&
          !children[i].disabled
        ) {
          return false;
        }
      }
      return true;
    }

    get itemCount() {
      return this._getRadioChildren().length;
    }

    set selectedIndex(val) {
      this.selectedItem = this._getRadioChildren()[val];
    }

    get selectedIndex() {
      var children = this._getRadioChildren();
      for (var i = 0; i < children.length; ++i) {
        if (children[i].selected) {
          return i;
        }
      }
      return -1;
    }

    set selectedItem(val) {
      var focused = this.getAttribute("focused") == "true";
      var alreadySelected = false;

      if (val) {
        alreadySelected = val.getAttribute("selected") == "true";
        val.setAttribute("focused", focused);
        val.setAttribute("selected", "true");
        this.setAttribute("value", val.value);
      } else {
        this.removeAttribute("value");
      }

      // uncheck all other group nodes
      var children = this._getRadioChildren();
      var previousItem = null;
      for (var i = 0; i < children.length; ++i) {
        if (children[i] != val) {
          if (children[i].getAttribute("selected") == "true") {
            previousItem = children[i];
          }

          children[i].removeAttribute("selected");
          children[i].removeAttribute("focused");
        }
      }

      var event = document.createEvent("Events");
      event.initEvent("select", false, true);
      this.dispatchEvent(event);

      if (focused) {
        if (alreadySelected) {
          // Notify accessibility that this item got focus.
          event = document.createEvent("Events");
          event.initEvent("DOMMenuItemActive", true, true);
          val.dispatchEvent(event);
        } else {
          // Only report if actual change
          if (val) {
            // Accessibility will fire focus for this.
            event = document.createEvent("Events");
            event.initEvent("RadioStateChange", true, true);
            val.dispatchEvent(event);
          }

          if (previousItem) {
            event = document.createEvent("Events");
            event.initEvent("RadioStateChange", true, true);
            previousItem.dispatchEvent(event);
          }
        }
      }
    }

    get selectedItem() {
      var children = this._getRadioChildren();
      for (var i = 0; i < children.length; ++i) {
        if (children[i].selected) {
          return children[i];
        }
      }
      return null;
    }

    set focusedItem(val) {
      if (val) {
        val.setAttribute("focused", "true");
        // Notify accessibility that this item got focus.
        let event = document.createEvent("Events");
        event.initEvent("DOMMenuItemActive", true, true);
        val.dispatchEvent(event);
      }

      // unfocus all other group nodes
      var children = this._getRadioChildren();
      for (var i = 0; i < children.length; ++i) {
        if (children[i] != val) {
          children[i].removeAttribute("focused");
        }
      }
    }

    get focusedItem() {
      var children = this._getRadioChildren();
      for (var i = 0; i < children.length; ++i) {
        if (children[i].getAttribute("focused") == "true") {
          return children[i];
        }
      }
      return null;
    }

    checkAdjacentElement(aNextFlag) {
      var currentElement = this.focusedItem || this.selectedItem;
      var i;
      var children = this._getRadioChildren();
      for (i = 0; i < children.length; ++i) {
        if (children[i] == currentElement) {
          break;
        }
      }
      var index = i;

      if (aNextFlag) {
        do {
          if (++i == children.length) {
            i = 0;
          }
          if (i == index) {
            break;
          }
        } while (
          children[i].hidden ||
          children[i].collapsed ||
          children[i].disabled
        );
        // XXX check for display/visibility props too

        this.selectedItem = children[i];
        children[i].doCommand();
      } else {
        do {
          if (i == 0) {
            i = children.length;
          }
          if (--i == index) {
            break;
          }
        } while (
          children[i].hidden ||
          children[i].collapsed ||
          children[i].disabled
        );
        // XXX check for display/visibility props too

        this.selectedItem = children[i];
        children[i].doCommand();
      }
    }

    _getRadioChildren() {
      if (this._radioChildren) {
        return this._radioChildren;
      }

      let radioChildren = [];
      if (this.hasChildNodes()) {
        for (let radio of this.querySelectorAll("radio")) {
          customElements.upgrade(radio);
          if (radio.control == this) {
            radioChildren.push(radio);
          }
        }
      } else {
        const XUL_NS =
          "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
        for (let radio of this.ownerDocument.getElementsByAttribute(
          "group",
          this.id
        )) {
          if (radio.namespaceURI == XUL_NS && radio.localName == "radio") {
            customElements.upgrade(radio);
            radioChildren.push(radio);
          }
        }
      }

      return (this._radioChildren = radioChildren);
    }

    getIndexOfItem(item) {
      return this._getRadioChildren().indexOf(item);
    }

    getItemAtIndex(index) {
      var children = this._getRadioChildren();
      return index >= 0 && index < children.length ? children[index] : null;
    }

    appendItem(label, value) {
      var radio = document.createXULElement("radio");
      radio.setAttribute("label", label);
      radio.setAttribute("value", value);
      this.appendChild(radio);
      return radio;
    }
  }

  MozXULElement.implementCustomInterface(MozRadiogroup, [
    Ci.nsIDOMXULSelectControlElement,
    Ci.nsIDOMXULRadioGroupElement,
  ]);

  customElements.define("radiogroup", MozRadiogroup);

  class MozRadio extends MozElements.BaseText {
    static get markup() {
      return `
      <image class="radio-check"></image>
      <hbox class="radio-label-box" align="center" flex="1">
        <image class="radio-icon"></image>
        <label class="radio-label" flex="1"></label>
      </hbox>
      `;
    }

    static get inheritedAttributes() {
      return {
        ".radio-check": "disabled,selected",
        ".radio-label": "text=label,accesskey,crop",
        ".radio-icon": "src",
      };
    }

    constructor() {
      super();
      this.addEventListener("click", () => {
        if (!this.disabled) {
          this.control.selectedItem = this;
        }
      });

      this.addEventListener("mousedown", () => {
        if (!this.disabled) {
          this.control.focusedItem = this;
        }
      });
    }

    connectedCallback() {
      if (this.delayConnectedCallback()) {
        return;
      }

      if (!this.connectedOnce) {
        this.connectedOnce = true;
        // If the caller didn't provide custom content then append the default:
        if (!this.firstElementChild) {
          this.appendChild(this.constructor.fragment);
          this.initializeAttributeInheritance();
        }
      }

      var control = (this._control = this.control);
      if (control) {
        control.radioAttached(this);
      }
    }

    disconnectedCallback() {
      if (this.control) {
        this.control.radioUnattached(this);
      }
      this._control = null;
    }

    set value(val) {
      this.setAttribute("value", val);
    }

    get value() {
      return this.getAttribute("value") || "";
    }

    get selected() {
      return this.hasAttribute("selected");
    }

    get radioGroup() {
      return this.control;
    }

    get control() {
      if (this._control) {
        return this._control;
      }

      var radiogroup = this.closest("radiogroup");
      if (radiogroup) {
        return radiogroup;
      }

      var group = this.getAttribute("group");
      if (!group) {
        return null;
      }

      var parent = this.ownerDocument.getElementById(group);
      if (!parent || parent.localName != "radiogroup") {
        parent = null;
      }
      return parent;
    }
  }

  MozXULElement.implementCustomInterface(MozRadio, [
    Ci.nsIDOMXULSelectControlItemElement,
  ]);
  customElements.define("radio", MozRadio);
})();
PK
!<���,�u�u5chrome/toolkit/content/global/elements/richlistbox.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
  const { AppConstants } = ChromeUtils.importESModule(
    "resource://gre/modules/AppConstants.sys.mjs"
  );

  /**
   * XUL:richlistbox element.
   */
  MozElements.RichListBox = class RichListBox extends MozElements.BaseControl {
    constructor() {
      super();

      this.selectedItems = new ChromeNodeList();
      this._currentIndex = null;
      this._lastKeyTime = 0;
      this._incrementalString = "";
      this._suppressOnSelect = false;
      this._userSelecting = false;
      this._selectTimeout = null;
      this._currentItem = null;
      this._selectionStart = null;

      this.addEventListener(
        "keypress",
        event => {
          if (event.altKey || event.metaKey) {
            return;
          }

          switch (event.keyCode) {
            case KeyEvent.DOM_VK_UP:
              this._moveByOffsetFromUserEvent(-1, event);
              break;
            case KeyEvent.DOM_VK_DOWN:
              this._moveByOffsetFromUserEvent(1, event);
              break;
            case KeyEvent.DOM_VK_HOME:
              this._moveByOffsetFromUserEvent(-this.currentIndex, event);
              break;
            case KeyEvent.DOM_VK_END:
              this._moveByOffsetFromUserEvent(
                this.getRowCount() - this.currentIndex - 1,
                event
              );
              break;
            case KeyEvent.DOM_VK_PAGE_UP:
              this._moveByOffsetFromUserEvent(this.scrollOnePage(-1), event);
              break;
            case KeyEvent.DOM_VK_PAGE_DOWN:
              this._moveByOffsetFromUserEvent(this.scrollOnePage(1), event);
              break;
          }
        },
        { mozSystemGroup: true }
      );

      this.addEventListener("keypress", event => {
        if (event.target != this) {
          return;
        }

        if (
          event.key == " " &&
          event.ctrlKey &&
          !event.shiftKey &&
          !event.altKey &&
          !event.metaKey &&
          this.currentItem &&
          this.selType == "multiple"
        ) {
          this.toggleItemSelection(this.currentItem);
        }

        if (!event.charCode || event.altKey || event.ctrlKey || event.metaKey) {
          return;
        }

        if (event.timeStamp - this._lastKeyTime > 1000) {
          this._incrementalString = "";
        }

        var key = String.fromCharCode(event.charCode).toLowerCase();
        this._incrementalString += key;
        this._lastKeyTime = event.timeStamp;

        // If all letters in the incremental string are the same, just
        // try to match the first one
        var incrementalString = /^(.)\1+$/.test(this._incrementalString)
          ? RegExp.$1
          : this._incrementalString;
        var length = incrementalString.length;

        var rowCount = this.getRowCount();
        var l = this.selectedItems.length;
        var start = l > 0 ? this.getIndexOfItem(this.selectedItems[l - 1]) : -1;
        // start from the first element if none was selected or from the one
        // following the selected one if it's a new or a repeated-letter search
        if (start == -1 || length == 1) {
          start++;
        }

        for (var i = 0; i < rowCount; i++) {
          var k = (start + i) % rowCount;
          var listitem = this.getItemAtIndex(k);
          if (!this.canUserSelect(listitem)) {
            continue;
          }
          // allow richlistitems to specify the string being searched for
          var searchText =
            "searchLabel" in listitem
              ? listitem.searchLabel
              : listitem.getAttribute("label") || ""; // (see also bug 250123)
          searchText = searchText.substring(0, length).toLowerCase();
          if (searchText == incrementalString) {
            this.ensureIndexIsVisible(k);
            this.timedSelect(listitem, this._selectDelay);
            break;
          }
        }
      });

      this.addEventListener("focus", () => {
        if (this.getRowCount() > 0) {
          if (this.currentIndex == -1) {
            this.currentIndex = this.getIndexOfFirstVisibleRow();
            let currentItem = this.getItemAtIndex(this.currentIndex);
            if (currentItem) {
              this.selectItem(currentItem);
            }
          } else {
            this._fireEvent(this.currentItem, "DOMMenuItemActive");
          }
        }
        this._lastKeyTime = 0;
      });

      this.addEventListener("click", event => {
        // clicking into nothing should unselect multiple selections
        if (event.originalTarget == this && this.selType == "multiple") {
          this.clearSelection();
          this.currentItem = null;
        }
      });

      this.addEventListener("MozSwipeGesture", event => {
        // Only handle swipe gestures up and down
        switch (event.direction) {
          case event.DIRECTION_DOWN:
            this.scrollTop = this.scrollHeight;
            break;
          case event.DIRECTION_UP:
            this.scrollTop = 0;
            break;
        }
      });
    }

    connectedCallback() {
      if (this.delayConnectedCallback()) {
        return;
      }

      this.setAttribute("allowevents", "true");
      this._refreshSelection();
    }

    // nsIDOMXULSelectControlElement
    set selectedItem(val) {
      this.selectItem(val);
    }
    get selectedItem() {
      return this.selectedItems.length ? this.selectedItems[0] : null;
    }

    // nsIDOMXULSelectControlElement
    set selectedIndex(val) {
      if (val >= 0) {
        // This is a micro-optimization so that a call to getIndexOfItem or
        // getItemAtIndex caused by _fireOnSelect (especially for derived
        // widgets) won't loop the children.
        this._selecting = {
          item: this.getItemAtIndex(val),
          index: val,
        };
        this.selectItem(this._selecting.item);
        delete this._selecting;
      } else {
        this.clearSelection();
        this.currentItem = null;
      }
    }
    get selectedIndex() {
      if (this.selectedItems.length) {
        return this.getIndexOfItem(this.selectedItems[0]);
      }
      return -1;
    }

    // nsIDOMXULSelectControlElement
    set value(val) {
      var kids = this.getElementsByAttribute("value", val);
      if (kids && kids.item(0)) {
        this.selectItem(kids[0]);
      }
    }
    get value() {
      if (this.selectedItems.length) {
        return this.selectedItem.value;
      }
      return null;
    }

    // nsIDOMXULSelectControlElement
    get itemCount() {
      return this.itemChildren.length;
    }

    // nsIDOMXULSelectControlElement
    set selType(val) {
      this.setAttribute("seltype", val);
    }
    get selType() {
      return this.getAttribute("seltype") || "";
    }

    // nsIDOMXULSelectControlElement
    set currentItem(val) {
      if (this._currentItem == val) {
        return;
      }

      if (this._currentItem) {
        this._currentItem.current = false;
        if (!val && !this.suppressMenuItemEvent) {
          // An item is losing focus and there is no new item to focus.
          // Notify a11y that there is no focused item.
          this._fireEvent(this._currentItem, "DOMMenuItemInactive");
        }
      }
      this._currentItem = val;

      if (val) {
        val.current = true;
        if (!this.suppressMenuItemEvent) {
          // Notify a11y that this item got focus.
          this._fireEvent(val, "DOMMenuItemActive");
        }
      }
    }
    get currentItem() {
      return this._currentItem;
    }

    // nsIDOMXULSelectControlElement
    set currentIndex(val) {
      if (val >= 0) {
        this.currentItem = this.getItemAtIndex(val);
      } else {
        this.currentItem = null;
      }
    }
    get currentIndex() {
      return this.currentItem ? this.getIndexOfItem(this.currentItem) : -1;
    }

    // nsIDOMXULSelectControlElement
    get selectedCount() {
      return this.selectedItems.length;
    }

    get itemChildren() {
      let children = Array.from(this.children).filter(
        node => node.localName == "richlistitem"
      );
      return children;
    }

    set suppressOnSelect(val) {
      this.setAttribute("suppressonselect", val);
    }
    get suppressOnSelect() {
      return this.getAttribute("suppressonselect") == "true";
    }

    set _selectDelay(val) {
      this.setAttribute("_selectDelay", val);
    }
    get _selectDelay() {
      return this.getAttribute("_selectDelay") || 50;
    }

    _fireOnSelect() {
      // make sure not to modify last-selected when suppressing select events
      // (otherwise we'll lose the selection when a template gets rebuilt)
      if (this._suppressOnSelect || this.suppressOnSelect) {
        return;
      }

      // remember the current item and all selected items with IDs
      var state = this.currentItem ? this.currentItem.id : "";
      if (this.selType == "multiple" && this.selectedCount) {
        let getId = function getId(aItem) {
          return aItem.id;
        };
        state +=
          " " + [...this.selectedItems].filter(getId).map(getId).join(" ");
      }
      if (state) {
        this.setAttribute("last-selected", state);
      } else {
        this.removeAttribute("last-selected");
      }

      // preserve the index just in case no IDs are available
      if (this.currentIndex > -1) {
        this._currentIndex = this.currentIndex + 1;
      }

      var event = document.createEvent("Events");
      event.initEvent("select", true, true);
      this.dispatchEvent(event);

      // always call this (allows a commandupdater without controller)
      document.commandDispatcher.updateCommands("richlistbox-select");
    }

    getNextItem(aStartItem, aDelta) {
      while (aStartItem) {
        aStartItem = aStartItem.nextSibling;
        if (
          aStartItem &&
          aStartItem.localName == "richlistitem" &&
          (!this._userSelecting || this.canUserSelect(aStartItem))
        ) {
          --aDelta;
          if (aDelta == 0) {
            return aStartItem;
          }
        }
      }
      return null;
    }

    getPreviousItem(aStartItem, aDelta) {
      while (aStartItem) {
        aStartItem = aStartItem.previousSibling;
        if (
          aStartItem &&
          aStartItem.localName == "richlistitem" &&
          (!this._userSelecting || this.canUserSelect(aStartItem))
        ) {
          --aDelta;
          if (aDelta == 0) {
            return aStartItem;
          }
        }
      }
      return null;
    }

    appendItem(aLabel, aValue) {
      var item = this.ownerDocument.createXULElement("richlistitem");
      item.setAttribute("value", aValue);

      var label = this.ownerDocument.createXULElement("label");
      label.setAttribute("value", aLabel);
      label.setAttribute("flex", "1");
      label.setAttribute("crop", "end");
      item.appendChild(label);

      this.appendChild(item);

      return item;
    }

    // nsIDOMXULSelectControlElement
    getIndexOfItem(aItem) {
      // don't search the children, if we're looking for none of them
      if (aItem == null) {
        return -1;
      }
      if (this._selecting && this._selecting.item == aItem) {
        return this._selecting.index;
      }
      return this.itemChildren.indexOf(aItem);
    }

    // nsIDOMXULSelectControlElement
    getItemAtIndex(aIndex) {
      if (this._selecting && this._selecting.index == aIndex) {
        return this._selecting.item;
      }
      return this.itemChildren[aIndex] || null;
    }

    // nsIDOMXULMultiSelectControlElement
    addItemToSelection(aItem) {
      if (this.selType != "multiple" && this.selectedCount) {
        return;
      }

      if (aItem.selected) {
        return;
      }

      this.selectedItems.append(aItem);
      aItem.selected = true;

      this._fireOnSelect();
    }

    // nsIDOMXULMultiSelectControlElement
    removeItemFromSelection(aItem) {
      if (!aItem.selected) {
        return;
      }

      this.selectedItems.remove(aItem);
      aItem.selected = false;
      this._fireOnSelect();
    }

    // nsIDOMXULMultiSelectControlElement
    toggleItemSelection(aItem) {
      if (aItem.selected) {
        this.removeItemFromSelection(aItem);
      } else {
        this.addItemToSelection(aItem);
      }
    }

    // nsIDOMXULMultiSelectControlElement
    selectItem(aItem) {
      if (!aItem || aItem.disabled) {
        return;
      }

      if (this.selectedItems.length == 1 && this.selectedItems[0] == aItem) {
        return;
      }

      this._selectionStart = null;

      var suppress = this._suppressOnSelect;
      this._suppressOnSelect = true;

      this.clearSelection();
      this.addItemToSelection(aItem);
      this.currentItem = aItem;

      this._suppressOnSelect = suppress;
      this._fireOnSelect();
    }

    // nsIDOMXULMultiSelectControlElement
    selectItemRange(aStartItem, aEndItem) {
      if (this.selType != "multiple") {
        return;
      }

      if (!aStartItem) {
        aStartItem = this._selectionStart
          ? this._selectionStart
          : this.currentItem;
      }

      if (!aStartItem) {
        aStartItem = aEndItem;
      }

      var suppressSelect = this._suppressOnSelect;
      this._suppressOnSelect = true;

      this._selectionStart = aStartItem;

      var currentItem;
      var startIndex = this.getIndexOfItem(aStartItem);
      var endIndex = this.getIndexOfItem(aEndItem);
      if (endIndex < startIndex) {
        currentItem = aEndItem;
        aEndItem = aStartItem;
        aStartItem = currentItem;
      } else {
        currentItem = aStartItem;
      }

      while (currentItem) {
        this.addItemToSelection(currentItem);
        if (currentItem == aEndItem) {
          currentItem = this.getNextItem(currentItem, 1);
          break;
        }
        currentItem = this.getNextItem(currentItem, 1);
      }

      // Clear around new selection
      // Don't use clearSelection() because it causes a lot of noise
      // with respect to selection removed notifications used by the
      // accessibility API support.
      var userSelecting = this._userSelecting;
      this._userSelecting = false; // that's US automatically unselecting
      for (; currentItem; currentItem = this.getNextItem(currentItem, 1)) {
        this.removeItemFromSelection(currentItem);
      }

      for (
        currentItem = this.getItemAtIndex(0);
        currentItem != aStartItem;
        currentItem = this.getNextItem(currentItem, 1)
      ) {
        this.removeItemFromSelection(currentItem);
      }
      this._userSelecting = userSelecting;

      this._suppressOnSelect = suppressSelect;

      this._fireOnSelect();
    }

    // nsIDOMXULMultiSelectControlElement
    selectAll() {
      this._selectionStart = null;

      var suppress = this._suppressOnSelect;
      this._suppressOnSelect = true;

      var item = this.getItemAtIndex(0);
      while (item) {
        this.addItemToSelection(item);
        item = this.getNextItem(item, 1);
      }

      this._suppressOnSelect = suppress;
      this._fireOnSelect();
    }

    // nsIDOMXULMultiSelectControlElement
    clearSelection() {
      if (this.selectedItems) {
        while (this.selectedItems.length) {
          let item = this.selectedItems[0];
          item.selected = false;
          this.selectedItems.remove(item);
        }
      }

      this._selectionStart = null;
      this._fireOnSelect();
    }

    // nsIDOMXULMultiSelectControlElement
    getSelectedItem(aIndex) {
      return aIndex < this.selectedItems.length
        ? this.selectedItems[aIndex]
        : null;
    }

    ensureIndexIsVisible(aIndex) {
      return this.ensureElementIsVisible(this.getItemAtIndex(aIndex));
    }

    ensureElementIsVisible(aElement, aAlignToTop) {
      if (!aElement) {
        return;
      }

      // These calculations assume that there is no padding on the
      // "richlistbox" element, although there might be a margin.
      var targetRect = aElement.getBoundingClientRect();
      var scrollRect = this.getBoundingClientRect();
      var offset = targetRect.top - scrollRect.top;
      if (!aAlignToTop && offset >= 0) {
        // scrollRect.bottom wouldn't take a horizontal scroll bar into account
        let scrollRectBottom = scrollRect.top + this.clientHeight;
        offset = targetRect.bottom - scrollRectBottom;
        if (offset <= 0) {
          return;
        }
      }
      this.scrollTop += offset;
    }

    getIndexOfFirstVisibleRow() {
      var children = this.itemChildren;

      for (var ix = 0; ix < children.length; ix++) {
        if (this._isItemVisible(children[ix])) {
          return ix;
        }
      }

      return -1;
    }

    getRowCount() {
      return this.itemChildren.length;
    }

    scrollOnePage(aDirection) {
      var children = this.itemChildren;

      if (!children.length) {
        return 0;
      }

      // If nothing is selected, we just select the first element
      // at the extreme we're moving away from
      if (!this.currentItem) {
        return aDirection == -1 ? children.length : 0;
      }

      // If the current item is visible, scroll by one page so that
      // the new current item is at approximately the same position as
      // the existing current item.
      let height = this.getBoundingClientRect().height;
      if (this._isItemVisible(this.currentItem)) {
        this.scrollBy(0, height * aDirection);
      }

      // Figure out, how many items fully fit into the view port
      // (including the currently selected one), and determine
      // the index of the first one lying (partially) outside
      let currentItemRect = this.currentItem.getBoundingClientRect();
      var startBorder = currentItemRect.y;
      if (aDirection == -1) {
        startBorder += currentItemRect.height;
      }

      var index = this.currentIndex;
      for (var ix = index; 0 <= ix && ix < children.length; ix += aDirection) {
        let childRect = children[ix].getBoundingClientRect();
        if (childRect.height == 0) {
          continue; // hidden children have a y of 0
        }
        var endBorder = childRect.y + (aDirection == -1 ? childRect.height : 0);
        if ((endBorder - startBorder) * aDirection > height) {
          break; // we've reached the desired distance
        }
        index = ix;
      }

      return index != this.currentIndex
        ? index - this.currentIndex
        : aDirection;
    }

    _refreshSelection() {
      // when this method is called, we know that either the currentItem
      // and selectedItems we have are null (ctor) or a reference to an
      // element no longer in the DOM (template).

      // first look for the last-selected attribute
      var state = this.getAttribute("last-selected");
      if (state) {
        var ids = state.split(" ");

        var suppressSelect = this._suppressOnSelect;
        this._suppressOnSelect = true;
        this.clearSelection();
        for (let i = 1; i < ids.length; i++) {
          var selectedItem = document.getElementById(ids[i]);
          if (selectedItem) {
            this.addItemToSelection(selectedItem);
          }
        }

        var currentItem = document.getElementById(ids[0]);
        if (!currentItem && this._currentIndex) {
          currentItem = this.getItemAtIndex(
            Math.min(this._currentIndex - 1, this.getRowCount())
          );
        }
        if (currentItem) {
          this.currentItem = currentItem;
          if (this.selType != "multiple" && this.selectedCount == 0) {
            this.selectedItem = currentItem;
          }

          if (this.getBoundingClientRect().height) {
            this.ensureElementIsVisible(currentItem);
          } else {
            // XXX hack around a bug in ensureElementIsVisible as it will
            // scroll beyond the last element, bug 493645.
            this.ensureElementIsVisible(currentItem.previousElementSibling);
          }
        }
        this._suppressOnSelect = suppressSelect;
        // XXX actually it's just a refresh, but at least
        // the Extensions manager expects this:
        this._fireOnSelect();
        return;
      }

      // try to restore the selected items according to their IDs
      // (applies after a template rebuild, if last-selected was not set)
      if (this.selectedItems) {
        let itemIds = [];
        for (let i = this.selectedCount - 1; i >= 0; i--) {
          let selectedItem = this.selectedItems[i];
          itemIds.push(selectedItem.id);
          this.selectedItems.remove(selectedItem);
        }
        for (let i = 0; i < itemIds.length; i++) {
          let selectedItem = document.getElementById(itemIds[i]);
          if (selectedItem) {
            this.selectedItems.append(selectedItem);
          }
        }
      }
      if (this.currentItem && this.currentItem.id) {
        this.currentItem = document.getElementById(this.currentItem.id);
      } else {
        this.currentItem = null;
      }

      // if we have no previously current item or if the above check fails to
      // find the previous nodes (which causes it to clear selection)
      if (!this.currentItem && this.selectedCount == 0) {
        this.currentIndex = this._currentIndex ? this._currentIndex - 1 : 0;

        // cf. listbox constructor:
        // select items according to their attributes
        var children = this.itemChildren;
        for (let i = 0; i < children.length; ++i) {
          if (children[i].getAttribute("selected") == "true") {
            this.selectedItems.append(children[i]);
          }
        }
      }

      if (this.selType != "multiple" && this.selectedCount == 0) {
        this.selectedItem = this.currentItem;
      }
    }

    _isItemVisible(aItem) {
      if (!aItem) {
        return false;
      }

      var y = this.getBoundingClientRect().y;

      // Partially visible items are also considered visible
      let itemRect = aItem.getBoundingClientRect();
      return (
        itemRect.y + itemRect.height > y && itemRect.y < y + this.clientHeight
      );
    }

    moveByOffset(aOffset, aIsSelecting, aIsSelectingRange, aEvent) {
      if ((aIsSelectingRange || !aIsSelecting) && this.selType != "multiple") {
        return;
      }

      var newIndex = this.currentIndex + aOffset;
      if (newIndex < 0) {
        newIndex = 0;
      }

      var numItems = this.getRowCount();
      if (newIndex > numItems - 1) {
        newIndex = numItems - 1;
      }

      var newItem = this.getItemAtIndex(newIndex);
      // make sure that the item is actually visible/selectable
      if (this._userSelecting && newItem && !this.canUserSelect(newItem)) {
        newItem =
          aOffset > 0
            ? this.getNextItem(newItem, 1) || this.getPreviousItem(newItem, 1)
            : this.getPreviousItem(newItem, 1) || this.getNextItem(newItem, 1);
      }
      if (newItem) {
        let hadFocus = this.currentItem.contains(document.activeElement);
        this.ensureIndexIsVisible(this.getIndexOfItem(newItem));
        if (aIsSelectingRange) {
          this.selectItemRange(null, newItem);
        } else if (aIsSelecting) {
          this.selectItem(newItem);
        }
        if (hadFocus) {
          let flags =
            Services.focus[
              aEvent.type.startsWith("key") ? "FLAG_BYKEY" : "FLAG_BYJS"
            ];
          Services.focus.moveFocus(
            window,
            newItem,
            Services.focus.MOVEFOCUS_FIRST,
            flags
          );
        }

        this.currentItem = newItem;
      }
    }

    _moveByOffsetFromUserEvent(aOffset, aEvent) {
      if (!aEvent.defaultPrevented) {
        this._userSelecting = true;
        this.moveByOffset(aOffset, !aEvent.ctrlKey, aEvent.shiftKey, aEvent);
        this._userSelecting = false;
        aEvent.preventDefault();
      }
    }

    canUserSelect(aItem) {
      if (aItem.disabled) {
        return false;
      }

      var style = document.defaultView.getComputedStyle(aItem);
      return (
        style.display != "none" &&
        style.visibility == "visible" &&
        style.MozUserInput != "none"
      );
    }

    _selectTimeoutHandler(aMe) {
      aMe._fireOnSelect();
      aMe._selectTimeout = null;
    }

    timedSelect(aItem, aTimeout) {
      var suppress = this._suppressOnSelect;
      if (aTimeout != -1) {
        this._suppressOnSelect = true;
      }

      this.selectItem(aItem);

      this._suppressOnSelect = suppress;

      if (aTimeout != -1) {
        if (this._selectTimeout) {
          window.clearTimeout(this._selectTimeout);
        }
        this._selectTimeout = window.setTimeout(
          this._selectTimeoutHandler,
          aTimeout,
          this
        );
      }
    }

    /**
     * For backwards-compatibility and for convenience.
     * Use ensureElementIsVisible instead
     */
    ensureSelectedElementIsVisible() {
      return this.ensureElementIsVisible(this.selectedItem);
    }

    _fireEvent(aTarget, aName) {
      let event = document.createEvent("Events");
      event.initEvent(aName, true, true);
      aTarget.dispatchEvent(event);
    }
  };

  MozXULElement.implementCustomInterface(MozElements.RichListBox, [
    Ci.nsIDOMXULSelectControlElement,
    Ci.nsIDOMXULMultiSelectControlElement,
  ]);

  customElements.define("richlistbox", MozElements.RichListBox);

  /**
   * XUL:richlistitem element.
   */
  MozElements.MozRichlistitem = class MozRichlistitem extends (
    MozElements.BaseText
  ) {
    constructor() {
      super();

      this.selectedByMouseOver = false;

      /**
       * If there is no modifier key, we select on mousedown, not
       * click, so that drags work correctly.
       */
      this.addEventListener("mousedown", event => {
        var control = this.control;
        if (!control || this.disabled || control.disabled) {
          return;
        }
        if (
          (!event.ctrlKey ||
            (AppConstants.platform == "macosx" && event.button == 2)) &&
          !event.shiftKey &&
          !event.metaKey
        ) {
          if (!this.selected) {
            control.selectItem(this);
          }
          control.currentItem = this;
        }
      });

      /**
       * On a click (up+down on the same item), deselect everything
       * except this item.
       */
      this.addEventListener("click", event => {
        if (event.button != 0) {
          return;
        }

        var control = this.control;
        if (!control || this.disabled || control.disabled) {
          return;
        }
        control._userSelecting = true;
        if (control.selType != "multiple") {
          control.selectItem(this);
        } else if (event.ctrlKey || event.metaKey) {
          control.toggleItemSelection(this);
          control.currentItem = this;
        } else if (event.shiftKey) {
          control.selectItemRange(null, this);
          control.currentItem = this;
        } else {
          /* We want to deselect all the selected items except what was
          clicked, UNLESS it was a right-click.  We have to do this
          in click rather than mousedown so that you can drag a
          selected group of items */

          // use selectItemRange instead of selectItem, because this
          // doesn't de- and reselect this item if it is selected
          control.selectItemRange(this, this);
        }
        control._userSelecting = false;
      });
    }

    connectedCallback() {
      this._updateInnerControlsForSelection(this.selected);
    }

    /**
     * nsIDOMXULSelectControlItemElement
     */
    get label() {
      const XUL_NS =
        "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
      return Array.from(
        this.getElementsByTagNameNS(XUL_NS, "label"),
        label => label.value
      ).join(" ");
    }

    set searchLabel(val) {
      if (val !== null) {
        this.setAttribute("searchlabel", val);
      }
      // fall back to the label property (default value)
      else {
        this.removeAttribute("searchlabel");
      }
    }

    get searchLabel() {
      return this.hasAttribute("searchlabel")
        ? this.getAttribute("searchlabel")
        : this.label;
    }
    /**
     * nsIDOMXULSelectControlItemElement
     */
    set value(val) {
      this.setAttribute("value", val);
    }

    get value() {
      return this.getAttribute("value") || "";
    }

    /**
     * nsIDOMXULSelectControlItemElement
     */
    set selected(val) {
      if (val) {
        this.setAttribute("selected", "true");
      } else {
        this.removeAttribute("selected");
      }
      this._updateInnerControlsForSelection(val);
    }

    get selected() {
      return this.getAttribute("selected") == "true";
    }
    /**
     * nsIDOMXULSelectControlItemElement
     */
    get control() {
      var parent = this.parentNode;
      while (parent) {
        if (parent.localName == "richlistbox") {
          return parent;
        }
        parent = parent.parentNode;
      }
      return null;
    }

    set current(val) {
      if (val) {
        this.setAttribute("current", "true");
      } else {
        this.removeAttribute("current");
      }
    }

    get current() {
      return this.getAttribute("current") == "true";
    }

    _updateInnerControlsForSelection(selected) {
      for (let control of this.querySelectorAll("button,menulist")) {
        if (!selected && control.tabIndex == 0) {
          control.tabIndex = -1;
        } else if (selected && control.tabIndex == -1) {
          control.tabIndex = 0;
        }
      }
    }
  };

  MozXULElement.implementCustomInterface(MozElements.MozRichlistitem, [
    Ci.nsIDOMXULSelectControlItemElement,
  ]);

  customElements.define("richlistitem", MozElements.MozRichlistitem);
}
PK
!<&�(���8chrome/toolkit/content/global/elements/search-textbox.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
  class MozSearchTextbox extends MozXULElement {
    constructor() {
      super();

      MozXULElement.insertFTLIfNeeded("toolkit/global/textActions.ftl");

      this.inputField = document.createElement("input");

      const METHODS = [
        "focus",
        "blur",
        "select",
        "setUserInput",
        "setSelectionRange",
      ];
      for (const method of METHODS) {
        this[method] = (...args) => this.inputField[method](...args);
      }

      const READ_WRITE_PROPERTIES = [
        "defaultValue",
        "placeholder",
        "readOnly",
        "size",
        "selectionStart",
        "selectionEnd",
      ];
      for (const property of READ_WRITE_PROPERTIES) {
        Object.defineProperty(this, property, {
          enumerable: true,
          get() {
            return this.inputField[property];
          },
          set(val) {
            this.inputField[property] = val;
          },
        });
      }

      this.attachShadow({ mode: "open" });
      this.addEventListener("input", this);
      this.addEventListener("keypress", this);
      this.addEventListener("mousedown", this);
    }

    static get inheritedAttributes() {
      return {
        input:
          "value,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,inputmode,spellcheck",
        ".textbox-search-icon": "label=searchbuttonlabel,disabled",
        ".textbox-search-clear": "disabled",
      };
    }

    connectedCallback() {
      if (this.delayConnectedCallback() || this.connected) {
        return;
      }

      document.l10n.connectRoot(this.shadowRoot);

      this.connected = true;
      this.textContent = "";

      const stylesheet = document.createElement("link");
      stylesheet.rel = "stylesheet";
      stylesheet.href = "chrome://global/skin/search-textbox.css";

      const textboxSign = document.createXULElement("image");
      textboxSign.className = "textbox-search-sign";
      textboxSign.part = "search-sign";

      const input = this.inputField;
      input.setAttribute("inputmode", "search");
      input.autocomplete = "off"; // not applicable in XUL docs and confuses aria.
      input.addEventListener("focus", this);
      input.addEventListener("blur", this);

      const searchBtn = (this._searchButtonIcon =
        document.createXULElement("image"));
      searchBtn.className = "textbox-search-icon";
      searchBtn.addEventListener("click", e => this._iconClick(e));

      const clearBtn = document.createXULElement("image");
      clearBtn.className = "textbox-search-clear";
      clearBtn.part = "clear-icon";
      clearBtn.setAttribute("role", "button");
      document.l10n.setAttributes(
        clearBtn,
        "text-action-search-text-box-clear"
      );
      clearBtn.addEventListener("click", () => this._clearSearch());

      const deck = (this._searchIcons = document.createXULElement("deck"));
      deck.className = "textbox-search-icons";
      deck.append(searchBtn, clearBtn);
      this.shadowRoot.append(stylesheet, textboxSign, input, deck);

      this._timer = null;

      // Ensure the button state is up to date:
      // eslint-disable-next-line no-self-assign
      this.searchButton = this.searchButton;

      this.initializeAttributeInheritance();
    }

    disconnectedCallback() {
      document.l10n.disconnectRoot(this.shadowRoot);
    }

    set timeout(val) {
      this.setAttribute("timeout", val);
    }

    get timeout() {
      return parseInt(this.getAttribute("timeout")) || 500;
    }

    set searchButton(val) {
      if (val) {
        this.setAttribute("searchbutton", "true");
        this.inputField.removeAttribute("aria-autocomplete");
        this._searchButtonIcon.setAttribute("role", "button");
      } else {
        this.removeAttribute("searchbutton");
        this.inputField.setAttribute("aria-autocomplete", "list");
        this._searchButtonIcon.setAttribute("role", "none");
      }
    }

    get searchButton() {
      return this.getAttribute("searchbutton") == "true";
    }

    set value(val) {
      this.inputField.value = val;

      if (val) {
        this._searchIcons.selectedIndex = this.searchButton ? 0 : 1;
      } else {
        this._searchIcons.selectedIndex = 0;
      }

      if (this._timer) {
        clearTimeout(this._timer);
      }
    }

    get value() {
      return this.inputField.value;
    }

    get editor() {
      return this.inputField.editor;
    }

    set disabled(val) {
      this.inputField.disabled = val;
      if (val) {
        this.setAttribute("disabled", "true");
      } else {
        this.removeAttribute("disabled");
      }
    }

    get disabled() {
      return this.inputField.disabled;
    }

    on_blur() {
      this.removeAttribute("focused");
    }

    on_focus() {
      this.setAttribute("focused", "true");
    }

    on_input() {
      if (this.searchButton) {
        this._searchIcons.selectedIndex = 0;
        return;
      }
      if (this._timer) {
        clearTimeout(this._timer);
      }
      this._timer =
        this.timeout && setTimeout(this._fireCommand, this.timeout, this);
      this._searchIcons.selectedIndex = this.value ? 1 : 0;
    }

    on_keypress(event) {
      switch (event.keyCode) {
        case KeyEvent.DOM_VK_ESCAPE:
          if (this._clearSearch()) {
            event.preventDefault();
            event.stopPropagation();
          }
          break;
        case KeyEvent.DOM_VK_RETURN:
          this._enterSearch();
          event.preventDefault();
          event.stopPropagation();
          break;
      }
    }

    on_mousedown() {
      if (!this.hasAttribute("focused")) {
        this.setSelectionRange(0, 0);
        this.focus();
      }
    }

    _fireCommand(me) {
      if (me._timer) {
        clearTimeout(me._timer);
      }
      me._timer = null;
      me.doCommand();
    }

    _iconClick() {
      if (this.searchButton) {
        this._enterSearch();
      } else {
        this.focus();
      }
    }

    _enterSearch() {
      if (this.disabled) {
        return;
      }
      if (this.searchButton && this.value && !this.readOnly) {
        this._searchIcons.selectedIndex = 1;
      }
      this._fireCommand(this);
    }

    _clearSearch() {
      if (!this.disabled && !this.readOnly && this.value) {
        this.value = "";
        this._fireCommand(this);
        this._searchIcons.selectedIndex = 0;
        return true;
      }
      return false;
    }
  }

  customElements.define("search-textbox", MozSearchTextbox);
}
PK
!<���6chrome/toolkit/content/global/elements/stringbundle.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
  class MozStringbundle extends MozXULElement {
    get stringBundle() {
      if (!this._bundle) {
        try {
          this._bundle = Services.strings.createBundle(this.src);
        } catch (e) {
          dump("Failed to get stringbundle:\n");
          dump(e + "\n");
        }
      }
      return this._bundle;
    }

    set src(val) {
      this._bundle = null;
      this.setAttribute("src", val);
    }

    get src() {
      return this.getAttribute("src");
    }

    get strings() {
      // Note: this is a sucky method name! Should be:
      //       readonly attribute nsISimpleEnumerator strings;
      return this.stringBundle.getSimpleEnumeration();
    }

    getString(aStringKey) {
      try {
        return this.stringBundle.GetStringFromName(aStringKey);
      } catch (e) {
        dump(
          "*** Failed to get string " +
            aStringKey +
            " in bundle: " +
            this.src +
            "\n"
        );
        throw e;
      }
    }

    getFormattedString(aStringKey, aStringsArray) {
      try {
        return this.stringBundle.formatStringFromName(
          aStringKey,
          aStringsArray
        );
      } catch (e) {
        dump(
          "*** Failed to format string " +
            aStringKey +
            " in bundle: " +
            this.src +
            "\n"
        );
        throw e;
      }
    }
  }

  customElements.define("stringbundle", MozStringbundle);
}
PK
!<�g��^�^0chrome/toolkit/content/global/elements/tabbox.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
  const { AppConstants } = ChromeUtils.importESModule(
    "resource://gre/modules/AppConstants.sys.mjs"
  );

  let imports = {};
  ChromeUtils.defineESModuleGetters(imports, {
    ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
  });

  class MozTabbox extends MozXULElement {
    constructor() {
      super();
      this._handleMetaAltArrows = AppConstants.platform == "macosx";
      this.disconnectedCallback = this.disconnectedCallback.bind(this);
    }

    connectedCallback() {
      document.addEventListener("keydown", this, { mozSystemGroup: true });
      window.addEventListener("unload", this.disconnectedCallback, {
        once: true,
      });
    }

    disconnectedCallback() {
      document.removeEventListener("keydown", this, { mozSystemGroup: true });
      window.removeEventListener("unload", this.disconnectedCallback);
    }

    set handleCtrlTab(val) {
      this.setAttribute("handleCtrlTab", val);
    }

    get handleCtrlTab() {
      return this.getAttribute("handleCtrlTab") != "false";
    }

    get tabs() {
      if (this.hasAttribute("tabcontainer")) {
        return document.getElementById(this.getAttribute("tabcontainer"));
      }
      return this.getElementsByTagNameNS(
        "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
        "tabs"
      ).item(0);
    }

    get tabpanels() {
      return this.getElementsByTagNameNS(
        "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
        "tabpanels"
      ).item(0);
    }

    set selectedIndex(val) {
      let tabs = this.tabs;
      if (tabs) {
        tabs.selectedIndex = val;
      }
      this.setAttribute("selectedIndex", val);
    }

    get selectedIndex() {
      let tabs = this.tabs;
      return tabs ? tabs.selectedIndex : -1;
    }

    set selectedTab(val) {
      if (val) {
        let tabs = this.tabs;
        if (tabs) {
          tabs.selectedItem = val;
        }
      }
    }

    get selectedTab() {
      let tabs = this.tabs;
      return tabs && tabs.selectedItem;
    }

    set selectedPanel(val) {
      if (val) {
        let tabpanels = this.tabpanels;
        if (tabpanels) {
          tabpanels.selectedPanel = val;
        }
      }
    }

    get selectedPanel() {
      let tabpanels = this.tabpanels;
      return tabpanels && tabpanels.selectedPanel;
    }

    handleEvent(event) {
      if (!event.isTrusted) {
        // Don't let untrusted events mess with tabs.
        return;
      }

      // Skip this only if something has explicitly cancelled it.
      if (event.defaultCancelled) {
        return;
      }

      // Skip if chrome code has cancelled this:
      if (event.defaultPreventedByChrome) {
        return;
      }

      // Don't check if the event was already consumed because tab
      // navigation should always work for better user experience.

      const { ShortcutUtils } = imports;

      switch (ShortcutUtils.getSystemActionForEvent(event)) {
        case ShortcutUtils.CYCLE_TABS:
          Services.telemetry.keyedScalarAdd(
            "browser.ui.interaction.keyboard",
            "ctrl-tab",
            1
          );
          Services.prefs.setBoolPref(
            "browser.engagement.ctrlTab.has-used",
            true
          );
          if (this.tabs && this.handleCtrlTab) {
            this.tabs.advanceSelectedTab(event.shiftKey ? -1 : 1, true);
            event.preventDefault();
          }
          break;
        case ShortcutUtils.PREVIOUS_TAB:
          if (this.tabs) {
            this.tabs.advanceSelectedTab(-1, true);
            event.preventDefault();
          }
          break;
        case ShortcutUtils.NEXT_TAB:
          if (this.tabs) {
            this.tabs.advanceSelectedTab(1, true);
            event.preventDefault();
          }
          break;
      }
    }
  }

  customElements.define("tabbox", MozTabbox);

  class MozDeck extends MozXULElement {
    get isAsync() {
      return this.getAttribute("async") == "true";
    }

    connectedCallback() {
      if (this.delayConnectedCallback()) {
        return;
      }
      this._selectedPanel = null;
      this._inAsyncOperation = false;

      let selectCurrentIndex = () => {
        // Try to select the new node if any.
        let index = this.selectedIndex;
        let oldPanel = this._selectedPanel;
        this._selectedPanel = this.children.item(index) || null;
        this.updateSelectedIndex(index, oldPanel);
      };

      this._mutationObserver = new MutationObserver(records => {
        let anyRemovals = records.some(record => !!record.removedNodes.length);
        if (anyRemovals) {
          // Try to keep the current selected panel in-place first.
          let index = Array.from(this.children).indexOf(this._selectedPanel);
          if (index != -1) {
            // Try to keep the same node selected.
            this.setAttribute("selectedIndex", index);
          }
        }
        // Select the current index if needed in case mutations have made that
        // available where it wasn't before.
        if (!this._inAsyncOperation) {
          selectCurrentIndex();
        }
      });

      this._mutationObserver.observe(this, {
        childList: true,
      });

      selectCurrentIndex();
    }

    disconnectedCallback() {
      this._mutationObserver?.disconnect();
      this._mutationObserver = null;
    }

    updateSelectedIndex(
      val,
      oldPanel = this.querySelector(":scope > .deck-selected")
    ) {
      this._inAsyncOperation = false;
      if (oldPanel != this._selectedPanel) {
        oldPanel?.classList.remove("deck-selected");
        this._selectedPanel?.classList.add("deck-selected");
      }
      this.setAttribute("selectedIndex", val);
    }

    set selectedIndex(val) {
      if (val < 0 || val >= this.children.length) {
        return;
      }

      let oldPanel = this._selectedPanel;
      this._selectedPanel = this.children[val];

      this._inAsyncOperation = this.isAsync;
      if (!this._inAsyncOperation) {
        this.updateSelectedIndex(val, oldPanel);
      }

      if (this._selectedPanel != oldPanel) {
        let event = document.createEvent("Events");
        event.initEvent("select", true, true);
        this.dispatchEvent(event);
      }
    }

    get selectedIndex() {
      let indexStr = this.getAttribute("selectedIndex");
      return indexStr ? parseInt(indexStr) : 0;
    }

    set selectedPanel(val) {
      this.selectedIndex = Array.from(this.children).indexOf(val);
    }

    get selectedPanel() {
      return this._selectedPanel;
    }
  }

  customElements.define("deck", MozDeck);

  class MozTabpanels extends MozDeck {
    constructor() {
      super();
      this._tabbox = null;
    }

    get tabbox() {
      // Memoize the result rather than replacing this getter, so that
      // it can be reset if the parent changes.
      if (this._tabbox) {
        return this._tabbox;
      }

      return (this._tabbox = this.closest("tabbox"));
    }

    /**
     * nsIDOMXULRelatedElement
     */
    getRelatedElement(aTabPanelElm) {
      if (!aTabPanelElm) {
        return null;
      }

      let tabboxElm = this.tabbox;
      if (!tabboxElm) {
        return null;
      }

      let tabsElm = tabboxElm.tabs;
      if (!tabsElm) {
        return null;
      }

      // Return tab element having 'linkedpanel' attribute equal to the id
      // of the tab panel or the same index as the tab panel element.
      let tabpanelIdx = Array.prototype.indexOf.call(
        this.children,
        aTabPanelElm
      );
      if (tabpanelIdx == -1) {
        return null;
      }

      let tabElms = tabsElm.allTabs;
      let tabElmFromIndex = tabElms[tabpanelIdx];

      let tabpanelId = aTabPanelElm.id;
      if (tabpanelId) {
        for (let idx = 0; idx < tabElms.length; idx++) {
          let tabElm = tabElms[idx];
          if (tabElm.linkedPanel == tabpanelId) {
            return tabElm;
          }
        }
      }

      return tabElmFromIndex;
    }
  }

  MozXULElement.implementCustomInterface(MozTabpanels, [
    Ci.nsIDOMXULRelatedElement,
  ]);
  customElements.define("tabpanels", MozTabpanels);

  MozElements.MozTab = class MozTab extends MozElements.BaseText {
    static get markup() {
      return `
        <hbox class="tab-middle box-inherit" flex="1">
          <image class="tab-icon" role="presentation"></image>
          <label class="tab-text" flex="1" role="presentation"></label>
        </hbox>
      `;
    }

    constructor() {
      super();

      this.addEventListener("mousedown", this);
      this.addEventListener("keydown", this);

      this.arrowKeysShouldWrap = AppConstants.platform == "macosx";
    }

    static get inheritedAttributes() {
      return {
        ".tab-middle": "align,dir,pack,orient,selected,visuallyselected",
        ".tab-icon": "validate,src=image",
        ".tab-text": "value=label,accesskey,crop,disabled",
      };
    }

    connectedCallback() {
      if (!this._initialized) {
        this.textContent = "";
        this.appendChild(this.constructor.fragment);
        this.initializeAttributeInheritance();
        this._initialized = true;
      }
    }

    on_mousedown(event) {
      if (event.button != 0 || this.disabled) {
        return;
      }

      this.container.ariaFocusedItem = null;

      if (this == this.container.selectedItem) {
        // This tab is already selected and we will fall
        // through to mousedown behavior which sets focus on the current tab,
        // Only a click on an already selected tab should focus the tab itself.
        return;
      }

      let stopwatchid = this.container.getAttribute("stopwatchid");
      if (stopwatchid) {
        TelemetryStopwatch.start(stopwatchid);
      }

      // Call this before setting the 'ignorefocus' attribute because this
      // will pass on focus if the formerly selected tab was focused as well.
      this.container._selectNewTab(this);

      var isTabFocused = false;
      try {
        isTabFocused = document.commandDispatcher.focusedElement == this;
      } catch (e) {}

      // Set '-moz-user-focus' to 'ignore' so that PostHandleEvent() can't
      // focus the tab; we only want tabs to be focusable by the mouse if
      // they are already focused. After a short timeout we'll reset
      // '-moz-user-focus' so that tabs can be focused by keyboard again.
      if (!isTabFocused) {
        this.setAttribute("ignorefocus", "true");
        setTimeout(tab => tab.removeAttribute("ignorefocus"), 0, this);
      }

      if (stopwatchid) {
        TelemetryStopwatch.finish(stopwatchid);
      }
    }

    on_keydown(event) {
      if (event.ctrlKey || event.altKey || event.metaKey || event.shiftKey) {
        return;
      }
      switch (event.keyCode) {
        case KeyEvent.DOM_VK_LEFT: {
          let direction = window.getComputedStyle(this.container).direction;
          this.container.advanceSelectedTab(
            direction == "ltr" ? -1 : 1,
            this.arrowKeysShouldWrap
          );
          event.preventDefault();
          break;
        }

        case KeyEvent.DOM_VK_RIGHT: {
          let direction = window.getComputedStyle(this.container).direction;
          this.container.advanceSelectedTab(
            direction == "ltr" ? 1 : -1,
            this.arrowKeysShouldWrap
          );
          event.preventDefault();
          break;
        }

        case KeyEvent.DOM_VK_UP:
          this.container.advanceSelectedTab(-1, this.arrowKeysShouldWrap);
          event.preventDefault();
          break;

        case KeyEvent.DOM_VK_DOWN:
          this.container.advanceSelectedTab(1, this.arrowKeysShouldWrap);
          event.preventDefault();
          break;

        case KeyEvent.DOM_VK_HOME:
          this.container._selectNewTab(this.container.allTabs[0]);
          event.preventDefault();
          break;

        case KeyEvent.DOM_VK_END: {
          let { allTabs } = this.container;
          this.container._selectNewTab(allTabs[allTabs.length - 1], -1);
          event.preventDefault();
          break;
        }
      }
    }

    set value(val) {
      this.setAttribute("value", val);
    }

    get value() {
      return this.getAttribute("value") || "";
    }

    get container() {
      return this.closest("tabs");
    }

    // nsIDOMXULSelectControlItemElement
    get control() {
      return this.container;
    }

    get selected() {
      return this.getAttribute("selected") == "true";
    }

    set _selected(val) {
      if (val) {
        this.setAttribute("selected", "true");
        this.setAttribute("visuallyselected", "true");
      } else {
        this.removeAttribute("selected");
        this.removeAttribute("visuallyselected");
      }
    }

    set linkedPanel(val) {
      this.setAttribute("linkedpanel", val);
    }

    get linkedPanel() {
      return this.getAttribute("linkedpanel");
    }
  };

  MozXULElement.implementCustomInterface(MozElements.MozTab, [
    Ci.nsIDOMXULSelectControlItemElement,
  ]);
  customElements.define("tab", MozElements.MozTab);

  class TabsBase extends MozElements.BaseControl {
    constructor() {
      super();

      this.addEventListener("DOMMouseScroll", event => {
        if (Services.prefs.getBoolPref("toolkit.tabbox.switchByScrolling")) {
          if (event.detail > 0) {
            this.advanceSelectedTab(1, false);
          } else {
            this.advanceSelectedTab(-1, false);
          }
          event.stopPropagation();
        }
      });
    }

    // to be called from derived class connectedCallback
    baseConnect() {
      this._tabbox = null;
      this.ACTIVE_DESCENDANT_ID =
        "keyboard-focused-tab-" + Math.trunc(Math.random() * 1000000);

      if (!this.hasAttribute("orient")) {
        this.setAttribute("orient", "horizontal");
      }

      if (this.tabbox && this.tabbox.hasAttribute("selectedIndex")) {
        let selectedIndex = parseInt(this.tabbox.getAttribute("selectedIndex"));
        this.selectedIndex = selectedIndex > 0 ? selectedIndex : 0;
        return;
      }

      let children = this.allTabs;
      let length = children.length;
      for (var i = 0; i < length; i++) {
        if (children[i].getAttribute("selected") == "true") {
          this.selectedIndex = i;
          return;
        }
      }

      var value = this.value;
      if (value) {
        this.value = value;
      } else {
        this.selectedIndex = 0;
      }
    }

    /**
     * nsIDOMXULSelectControlElement
     */
    get itemCount() {
      return this.allTabs.length;
    }

    set value(val) {
      this.setAttribute("value", val);
      var children = this.allTabs;
      for (var c = children.length - 1; c >= 0; c--) {
        if (children[c].value == val) {
          this.selectedIndex = c;
          break;
        }
      }
    }

    get value() {
      return this.getAttribute("value") || "";
    }

    get tabbox() {
      if (!this._tabbox) {
        // Memoize the result in a field rather than replacing this property,
        // so that it can be reset along with the binding.
        this._tabbox = this.closest("tabbox");
      }

      return this._tabbox;
    }

    set selectedIndex(val) {
      var tab = this.getItemAtIndex(val);
      if (!tab) {
        return;
      }
      for (let otherTab of this.allTabs) {
        if (otherTab != tab && otherTab.selected) {
          otherTab._selected = false;
        }
      }
      tab._selected = true;

      this.setAttribute("value", tab.value);

      let linkedPanel = this.getRelatedElement(tab);
      if (linkedPanel) {
        this.tabbox.setAttribute("selectedIndex", val);

        // This will cause an onselect event to fire for the tabpanel
        // element.
        this.tabbox.tabpanels.selectedPanel = linkedPanel;
      }
    }

    get selectedIndex() {
      const tabs = this.allTabs;
      for (var i = 0; i < tabs.length; i++) {
        if (tabs[i].selected) {
          return i;
        }
      }
      return -1;
    }

    set selectedItem(val) {
      if (val && !val.selected) {
        // The selectedIndex setter ignores invalid values
        // such as -1 if |val| isn't one of our child nodes.
        this.selectedIndex = this.getIndexOfItem(val);
      }
    }

    get selectedItem() {
      const tabs = this.allTabs;
      for (var i = 0; i < tabs.length; i++) {
        if (tabs[i].selected) {
          return tabs[i];
        }
      }
      return null;
    }

    get ariaFocusedIndex() {
      const tabs = this.allTabs;
      for (var i = 0; i < tabs.length; i++) {
        if (tabs[i].id == this.ACTIVE_DESCENDANT_ID) {
          return i;
        }
      }
      return -1;
    }

    set ariaFocusedItem(val) {
      let setNewItem = val && this.getIndexOfItem(val) != -1;
      let clearExistingItem = this.ariaFocusedItem && (!val || setNewItem);
      if (clearExistingItem) {
        let ariaFocusedItem = this.ariaFocusedItem;
        ariaFocusedItem.classList.remove("keyboard-focused-tab");
        ariaFocusedItem.id = "";
        this.selectedItem.removeAttribute("aria-activedescendant");
        let evt = new CustomEvent("AriaFocus");
        this.selectedItem.dispatchEvent(evt);
      }

      if (setNewItem) {
        this.ariaFocusedItem = null;
        val.id = this.ACTIVE_DESCENDANT_ID;
        val.classList.add("keyboard-focused-tab");
        this.selectedItem.setAttribute(
          "aria-activedescendant",
          this.ACTIVE_DESCENDANT_ID
        );
        let evt = new CustomEvent("AriaFocus");
        val.dispatchEvent(evt);
      }
    }

    get ariaFocusedItem() {
      return document.getElementById(this.ACTIVE_DESCENDANT_ID);
    }

    /**
     * nsIDOMXULRelatedElement
     */
    getRelatedElement(aTabElm) {
      if (!aTabElm) {
        return null;
      }

      let tabboxElm = this.tabbox;
      if (!tabboxElm) {
        return null;
      }

      let tabpanelsElm = tabboxElm.tabpanels;
      if (!tabpanelsElm) {
        return null;
      }

      // Get linked tab panel by 'linkedpanel' attribute on the given tab
      // element.
      let linkedPanelId = aTabElm.linkedPanel;
      if (linkedPanelId) {
        return this.ownerDocument.getElementById(linkedPanelId);
      }

      // otherwise linked tabpanel element has the same index as the given
      // tab element.
      let tabElmIdx = this.getIndexOfItem(aTabElm);
      return tabpanelsElm.children[tabElmIdx];
    }

    getIndexOfItem(item) {
      return Array.prototype.indexOf.call(this.allTabs, item);
    }

    getItemAtIndex(index) {
      return this.allTabs[index] || null;
    }

    /**
     * Find an adjacent tab.
     *
     * @param {Node} startTab         A <tab> element to start searching from.
     * @param {Number} opts.direction 1 to search forward, -1 to search backward.
     * @param {Boolean} opts.wrap     If true, wrap around if the search reaches
     *                                the end (or beginning) of the tab strip.
     * @param {Boolean} opts.startWithAdjacent
     *                                If true (which is the default), start
     *                                searching from the  next tab after (or
     *                                before) startTab.  If false, startTab may
     *                                be returned if it passes the filter.
     * @param {Boolean} opts.advance  If false, start searching with startTab.  If
     *                                true, start searching with an adjacent tab.
     * @param {Function} opts.filter  A function to select which tabs to return.
     *
     * @return {Node | null}     The next <tab> element or, if none exists, null.
     */
    findNextTab(startTab, opts = {}) {
      let {
        direction = 1,
        wrap = false,
        startWithAdjacent = true,
        filter = () => true,
      } = opts;

      let tab = startTab;
      if (!startWithAdjacent && filter(tab)) {
        return tab;
      }

      let children = this.allTabs;
      let i = children.indexOf(tab);
      if (i < 0) {
        return null;
      }

      while (true) {
        i += direction;
        if (wrap) {
          if (i < 0) {
            i = children.length - 1;
          } else if (i >= children.length) {
            i = 0;
          }
        } else if (i < 0 || i >= children.length) {
          return null;
        }

        tab = children[i];
        if (tab == startTab) {
          return null;
        }
        if (filter(tab)) {
          return tab;
        }
      }
    }

    _selectNewTab(aNewTab, aFallbackDir, aWrap) {
      this.ariaFocusedItem = null;

      aNewTab = this.findNextTab(aNewTab, {
        direction: aFallbackDir,
        wrap: aWrap,
        startWithAdjacent: false,
        filter: tab =>
          !tab.hidden && !tab.disabled && this._canAdvanceToTab(tab),
      });

      var isTabFocused = false;
      try {
        isTabFocused =
          document.commandDispatcher.focusedElement == this.selectedItem;
      } catch (e) {}
      this.selectedItem = aNewTab;
      if (isTabFocused) {
        aNewTab.focus();
      } else if (this.getAttribute("setfocus") != "false") {
        let selectedPanel = this.tabbox.selectedPanel;
        document.commandDispatcher.advanceFocusIntoSubtree(selectedPanel);

        // Make sure that the focus doesn't move outside the tabbox
        if (this.tabbox) {
          try {
            let el = document.commandDispatcher.focusedElement;
            while (el && el != this.tabbox.tabpanels) {
              if (el == this.tabbox || el == selectedPanel) {
                return;
              }
              el = el.parentNode;
            }
            aNewTab.focus();
          } catch (e) {}
        }
      }
    }

    _canAdvanceToTab() {
      return true;
    }

    advanceSelectedTab(aDir, aWrap) {
      let startTab = this.ariaFocusedItem || this.selectedItem;
      let newTab = null;

      // Handle keyboard navigation for a hidden tab that can be selected, like the Firefox View tab,
      // which has a random placement in this.allTabs.
      if (startTab.hidden) {
        if (aDir == 1) {
          newTab = this.allTabs.find(tab => !tab.hidden);
        } else {
          newTab = this.allTabs.findLast(tab => !tab.hidden);
        }
      } else {
        newTab = this.findNextTab(startTab, {
          direction: aDir,
          wrap: aWrap,
        });
      }

      if (newTab && newTab != startTab) {
        this._selectNewTab(newTab, aDir, aWrap);
      }
    }

    appendItem(label, value) {
      var tab = document.createXULElement("tab");
      tab.setAttribute("label", label);
      tab.setAttribute("value", value);
      this.appendChild(tab);
      return tab;
    }
  }

  MozXULElement.implementCustomInterface(TabsBase, [
    Ci.nsIDOMXULSelectControlElement,
    Ci.nsIDOMXULRelatedElement,
  ]);

  MozElements.TabsBase = TabsBase;

  class MozTabs extends TabsBase {
    connectedCallback() {
      if (this.delayConnectedCallback()) {
        return;
      }

      let start = MozXULElement.parseXULToFragment(
        `<spacer class="tabs-left"/>`
      );
      this.insertBefore(start, this.firstChild);

      let end = MozXULElement.parseXULToFragment(
        `<spacer class="tabs-right" flex="1"/>`
      );
      this.insertBefore(end, null);

      this.baseConnect();
    }

    // Accessor for tabs.  This element has spacers as the first and
    // last elements and <tab>s are everything in between.
    get allTabs() {
      let children = Array.from(this.children);
      return children.splice(1, children.length - 2);
    }

    appendChild(tab) {
      // insert before the end spacer.
      this.insertBefore(tab, this.lastChild);
    }
  }

  customElements.define("tabs", MozTabs);
}
PK
!<|jA�~+~+.chrome/toolkit/content/global/elements/text.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
  const MozXULTextElement = MozElements.MozElementMixin(XULTextElement);

  let gInsertSeparator = false;
  let gAlwaysAppendAccessKey = false;
  let gUnderlineAccesskey =
    Services.prefs.getIntPref("ui.key.menuAccessKey") != 0;
  if (gUnderlineAccesskey) {
    try {
      const nsIPrefLocalizedString = Ci.nsIPrefLocalizedString;
      const prefNameInsertSeparator =
        "intl.menuitems.insertseparatorbeforeaccesskeys";
      const prefNameAlwaysAppendAccessKey =
        "intl.menuitems.alwaysappendaccesskeys";

      let val = Services.prefs.getComplexValue(
        prefNameInsertSeparator,
        nsIPrefLocalizedString
      ).data;
      gInsertSeparator = val == "true";

      val = Services.prefs.getComplexValue(
        prefNameAlwaysAppendAccessKey,
        nsIPrefLocalizedString
      ).data;
      gAlwaysAppendAccessKey = val == "true";
    } catch (e) {
      gInsertSeparator = gAlwaysAppendAccessKey = true;
    }
  }

  class MozTextLabel extends MozXULTextElement {
    constructor() {
      super();
      this._lastFormattedAccessKey = null;
      this.addEventListener("click", this._onClick);
    }

    static get observedAttributes() {
      return ["accesskey"];
    }

    set textContent(val) {
      super.textContent = val;
      this._lastFormattedAccessKey = null;
      this.formatAccessKey();
    }

    get textContent() {
      return super.textContent;
    }

    attributeChangedCallback(name, oldValue, newValue) {
      if (!this.isConnectedAndReady || oldValue == newValue) {
        return;
      }

      // Note that this is only happening when "accesskey" attribute change:
      this.formatAccessKey();
    }

    _onClick() {
      let controlElement = this.labeledControlElement;
      if (!controlElement || this.disabled) {
        return;
      }
      controlElement.focus();
      const XUL_NS =
        "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

      if (controlElement.namespaceURI != XUL_NS) {
        return;
      }

      if (
        (controlElement.localName == "checkbox" ||
          controlElement.localName == "radio") &&
        controlElement.getAttribute("disabled") == "true"
      ) {
        return;
      }

      if (controlElement.localName == "checkbox") {
        controlElement.checked = !controlElement.checked;
      } else if (controlElement.localName == "radio") {
        controlElement.control.selectedItem = controlElement;
      }
    }

    connectedCallback() {
      if (this.delayConnectedCallback()) {
        return;
      }

      this.formatAccessKey();
    }

    set accessKey(val) {
      this.setAttribute("accesskey", val);
      var control = this.labeledControlElement;
      if (control) {
        control.setAttribute("accesskey", val);
      }
    }

    get accessKey() {
      let accessKey = this.getAttribute("accesskey");
      return accessKey ? accessKey[0] : null;
    }

    get labeledControlElement() {
      let control = this.control;
      return control ? document.getElementById(control) : null;
    }

    set control(val) {
      this.setAttribute("control", val);
    }

    get control() {
      return this.getAttribute("control");
    }

    // This is used to match the rendering of accesskeys from nsTextBoxFrame.cpp (i.e. when the
    // label uses [value]). So this is just for when we have textContent.
    formatAccessKey() {
      // Skip doing any DOM manipulation whenever possible:
      let accessKey = this.accessKey;
      if (
        !gUnderlineAccesskey ||
        !this.isConnectedAndReady ||
        this._lastFormattedAccessKey == accessKey ||
        !this.textContent
      ) {
        return;
      }
      this._lastFormattedAccessKey = accessKey;
      if (this.accessKeySpan) {
        // Clear old accesskey
        mergeElement(this.accessKeySpan);
        this.accessKeySpan = null;
      }

      if (this.hiddenColon) {
        mergeElement(this.hiddenColon);
        this.hiddenColon = null;
      }

      if (this.accessKeyParens) {
        this.accessKeyParens.remove();
        this.accessKeyParens = null;
      }

      // If we used to have an accessKey but not anymore, we're done here
      if (!accessKey) {
        return;
      }

      let labelText = this.textContent;
      let accessKeyIndex = -1;
      if (!gAlwaysAppendAccessKey) {
        accessKeyIndex = labelText.indexOf(accessKey);
        if (accessKeyIndex < 0) {
          // Try again in upper case
          accessKeyIndex = labelText
            .toUpperCase()
            .indexOf(accessKey.toUpperCase());
        }
      } else if (labelText.endsWith(`(${accessKey.toUpperCase()})`)) {
        accessKeyIndex = labelText.length - (1 + accessKey.length); // = index of accessKey.
      }

      const HTML_NS = "http://www.w3.org/1999/xhtml";
      this.accessKeySpan = document.createElementNS(HTML_NS, "span");
      this.accessKeySpan.className = "accesskey";

      // Note that if you change the following code, see the comment of
      // nsTextBoxFrame::UpdateAccessTitle.

      // If accesskey is in the string, underline it:
      if (accessKeyIndex >= 0) {
        wrapChar(this, this.accessKeySpan, accessKeyIndex);
        return;
      }

      // If accesskey is not in string, append in parentheses
      // If end is colon, we should insert before colon.
      // i.e., "label:" -> "label(X):"
      let colonHidden = false;
      if (/:$/.test(labelText)) {
        labelText = labelText.slice(0, -1);
        this.hiddenColon = document.createElementNS(HTML_NS, "span");
        this.hiddenColon.className = "hiddenColon";
        this.hiddenColon.style.display = "none";
        // Hide the last colon by using span element.
        // I.e., label<span style="display:none;">:</span>
        wrapChar(this, this.hiddenColon, labelText.length);
        colonHidden = true;
      }
      // If end is space(U+20),
      // we should not add space before parentheses.
      let endIsSpace = false;
      if (/ $/.test(labelText)) {
        endIsSpace = true;
      }

      this.accessKeyParens = document.createElementNS(
        "http://www.w3.org/1999/xhtml",
        "span"
      );
      this.appendChild(this.accessKeyParens);
      if (gInsertSeparator && !endIsSpace) {
        this.accessKeyParens.textContent = " (";
      } else {
        this.accessKeyParens.textContent = "(";
      }
      this.accessKeySpan.textContent = accessKey.toUpperCase();
      this.accessKeyParens.appendChild(this.accessKeySpan);
      if (!colonHidden) {
        this.accessKeyParens.appendChild(document.createTextNode(")"));
      } else {
        this.accessKeyParens.appendChild(document.createTextNode("):"));
      }
    }
  }

  customElements.define("label", MozTextLabel);

  function mergeElement(element) {
    // If the element has been removed already, return:
    if (!element.isConnected) {
      return;
    }
    if (Text.isInstance(element.previousSibling)) {
      element.previousSibling.appendData(element.textContent);
    } else {
      element.parentNode.insertBefore(element.firstChild, element);
    }
    element.remove();
  }

  function wrapChar(parent, element, index) {
    let treeWalker = document.createNodeIterator(
      parent,
      NodeFilter.SHOW_TEXT,
      null
    );
    let node = treeWalker.nextNode();
    while (index >= node.length) {
      index -= node.length;
      node = treeWalker.nextNode();
    }
    if (index) {
      node = node.splitText(index);
    }

    node.parentNode.insertBefore(element, node);
    if (node.length > 1) {
      node.splitText(1);
    }
    element.appendChild(node);
  }

  class MozTextLink extends MozXULTextElement {
    constructor() {
      super();

      this.addEventListener(
        "click",
        event => {
          if (event.button == 0 || event.button == 1) {
            this.open(event);
          }
        },
        true
      );

      this.addEventListener("keypress", event => {
        if (event.keyCode != KeyEvent.DOM_VK_RETURN) {
          return;
        }
        this.click();
      });
    }

    connectedCallback() {
      this.classList.add("text-link");
    }

    set href(val) {
      this.setAttribute("href", val);
    }

    get href() {
      return this.getAttribute("href");
    }

    open(aEvent) {
      var href = this.href;
      if (!href || this.disabled || aEvent.defaultPrevented) {
        return;
      }

      var uri = null;
      try {
        const nsISSM = Ci.nsIScriptSecurityManager;
        const secMan =
          Cc["@mozilla.org/scriptsecuritymanager;1"].getService(nsISSM);

        uri = Services.io.newURI(href);

        let principal;
        if (this.getAttribute("useoriginprincipal") == "true") {
          principal = this.nodePrincipal;
        } else {
          principal = secMan.createNullPrincipal({});
        }
        try {
          secMan.checkLoadURIWithPrincipal(
            principal,
            uri,
            nsISSM.DISALLOW_INHERIT_PRINCIPAL
          );
        } catch (ex) {
          var msg =
            "Error: Cannot open a " +
            uri.scheme +
            ": link using \
                         the text-link binding.";
          console.error(msg);
          return;
        }

        const cID = "@mozilla.org/uriloader/external-protocol-service;1";
        const nsIEPS = Ci.nsIExternalProtocolService;
        var protocolSvc = Cc[cID].getService(nsIEPS);

        // if the scheme is not an exposed protocol, then opening this link
        // should be deferred to the system's external protocol handler
        if (!protocolSvc.isExposedProtocol(uri.scheme)) {
          protocolSvc.loadURI(uri, principal);
          aEvent.preventDefault();
          return;
        }
      } catch (ex) {
        console.error(ex);
      }

      aEvent.preventDefault();
      href = uri ? uri.spec : href;

      // Try handing off the link to the host application, e.g. for
      // opening it in a tabbed browser.
      var linkHandled = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
        Ci.nsISupportsPRBool
      );
      linkHandled.data = false;
      let { shiftKey, ctrlKey, metaKey, altKey, button } = aEvent;
      let data = { shiftKey, ctrlKey, metaKey, altKey, button, href };
      Services.obs.notifyObservers(
        linkHandled,
        "handle-xul-text-link",
        JSON.stringify(data)
      );
      if (linkHandled.data) {
        return;
      }

      // otherwise, fall back to opening the anchor directly
      var win = window;
      if (window.isChromeWindow) {
        while (win.opener && !win.opener.closed) {
          win = win.opener;
        }
      }
      win.open(href, "_blank", "noopener");
    }
  }

  customElements.define("text-link", MozTextLink, { extends: "label" });
}
PK
!<r�xI�'�'9chrome/toolkit/content/global/elements/textrecognition.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

// This is a UA widget. It runs in per-origin UA widget scope,
// to be loaded by UAWidgetsChild.sys.mjs.

this.TextRecognitionWidget = class {
  /**
   * @param {ShadowRoot} shadowRoot
   * @param {Record<string, string | boolean | number>} _prefs
   */
  constructor(shadowRoot, _prefs) {
    /** @type {ShadowRoot} */
    this.shadowRoot = shadowRoot;
    /** @type {HTMLElement} */
    this.element = shadowRoot.host;
    /** @type {Document} */
    this.document = this.element.ownerDocument;
    /** @type {Window} */
    this.window = this.document.defaultView;
    /** @type {ResizeObserver} */
    this.resizeObserver = null;
    /** @type {Map<HTMLSpanElement, DOMRect} */
    this.spanRects = new Map();
    /** @type {boolean} */
    this.isInitialized = false;
    /** @type {null | number} */
    this.lastCanvasStyleWidth = null;
  }

  /*
   * Callback called by UAWidgets right after constructor.
   */
  onsetup() {
    this.resizeObserver = new this.window.ResizeObserver(() => {
      this.positionSpans();
    });
    this.resizeObserver.observe(this.element);
  }

  positionSpans() {
    if (!this.shadowRoot.firstChild) {
      return;
    }
    this.lazilyInitialize();

    /** @type {HTMLDivElement} */
    const div = this.shadowRoot.firstChild;
    const canvas = div.querySelector("canvas");
    const spans = div.querySelectorAll("span");

    // TODO Bug 1770438 - The <img> element does not currently let child elements be
    // sized relative to the size of the containing <img> element. It would be better
    // to teach the <img> element how to do this. For the prototype, do the more expensive
    // operation of getting the bounding client rect, and handle the positioning manually.
    const imgRect = this.element.getBoundingClientRect();
    div.style.width = imgRect.width + "px";
    div.style.height = imgRect.height + "px";
    canvas.style.width = imgRect.width + "px";
    canvas.style.height = imgRect.height + "px";

    // The ctx is only available when redrawing the canvas. This is operation is only
    // done when necessary, as it can be expensive.
    /** @type {null | CanvasRenderingContext2D} */
    let ctx = null;

    if (
      // The canvas hasn't been drawn to yet.
      this.lastCanvasStyleWidth === null ||
      // Only redraw when the image has grown 25% larger. This percentage was chosen
      // as it visually seemed to work well, with the canvas never appearing blurry
      // when manually testing it.
      imgRect.width > this.lastCanvasStyleWidth * 1.25
    ) {
      const dpr = this.window.devicePixelRatio;
      canvas.width = imgRect.width * dpr;
      canvas.height = imgRect.height * dpr;
      this.lastCanvasStyleWidth = imgRect.width;

      ctx = canvas.getContext("2d");
      ctx.scale(dpr, dpr);
      ctx.fillStyle = "#00000088";
      ctx.fillRect(0, 0, imgRect.width, imgRect.height);

      ctx.beginPath();
    }

    for (const span of spans) {
      let spanRect = this.spanRects.get(span);
      if (!spanRect) {
        // This only needs to happen once.
        spanRect = span.getBoundingClientRect();
        this.spanRects.set(span, spanRect);
      }

      const points = span.dataset.points.split(",").map(p => Number(p));
      // Use the points in the string, e.g.
      // "0.0275349,0.14537,0.0275349,0.244662,0.176966,0.244565,0.176966,0.145273"
      //  0         1       2         3        4        5        6        7
      //  ^ bottomleft      ^ topleft          ^ topright        ^ bottomright
      let [
        bottomLeftX,
        bottomLeftY,
        topLeftX,
        topLeftY,
        topRightX,
        topRightY,
        bottomRightX,
        bottomRightY,
      ] = points;

      // Invert the Y.
      topLeftY = 1 - topLeftY;
      topRightY = 1 - topRightY;
      bottomLeftY = 1 - bottomLeftY;
      bottomRightY = 1 - bottomRightY;

      // Create a projection matrix to position the <span> relative to the bounds.
      // prettier-ignore
      const mat4 = projectPoints(
        spanRect.width,               spanRect.height,
        imgRect.width * topLeftX,     imgRect.height * topLeftY,
        imgRect.width * topRightX,    imgRect.height * topRightY,
        imgRect.width * bottomLeftX,  imgRect.height * bottomLeftY,
        imgRect.width * bottomRightX, imgRect.height * bottomRightY
      );

      span.style.transform = "matrix3d(" + mat4.join(", ") + ")";

      if (ctx) {
        const inset = 3;
        ctx.moveTo(
          imgRect.width * bottomLeftX + inset,
          imgRect.height * bottomLeftY - inset
        );
        ctx.lineTo(
          imgRect.width * topLeftX + inset,
          imgRect.height * topLeftY + inset
        );
        ctx.lineTo(
          imgRect.width * topRightX - inset,
          imgRect.height * topRightY + inset
        );
        ctx.lineTo(
          imgRect.width * bottomRightX - inset,
          imgRect.height * bottomRightY - inset
        );
        ctx.closePath();
      }
    }

    if (ctx) {
      // This composite operation will cut out the quads. The color is arbitrary.
      ctx.globalCompositeOperation = "destination-out";
      ctx.fillStyle = "#ffffff";
      ctx.fill();

      // Creating a round line will grow the selection slightly, and round the corners.
      ctx.lineWidth = 10;
      ctx.lineJoin = "round";
      ctx.strokeStyle = "#ffffff";
      ctx.stroke();
    }
  }

  teardown() {
    this.shadowRoot.firstChild.remove();
    this.resizeObserver.disconnect();
    this.spanRects.clear();
  }

  lazilyInitialize() {
    if (this.isInitialized) {
      return;
    }
    this.isInitialized = true;

    const parser = new this.window.DOMParser();
    let parserDoc = parser.parseFromString(
      `<div class="textrecognition" xmlns="http://www.w3.org/1999/xhtml" role="none">
        <link rel="stylesheet" href="chrome://global/skin/media/textrecognition.css" />
        <canvas />
        <!-- The spans will be reattached here -->
      </div>`,
      "application/xml"
    );
    if (
      this.shadowRoot.children.length !== 1 ||
      this.shadowRoot.firstChild.tagName !== "DIV"
    ) {
      throw new Error(
        "Expected the shadowRoot to have a single div as the root element."
      );
    }

    const spansDiv = this.shadowRoot.firstChild;
    // Example layout of spansDiv:
    // <div>
    //   <span data-points="0.0275349,0.14537,0.0275349,0.244662,0.176966,0.244565,0.176966,0.145273">
    //     Text that has been recognized
    //   </span>
    //   ...
    // </div>
    spansDiv.remove();

    this.shadowRoot.importNodeAndAppendChildAt(
      this.shadowRoot,
      parserDoc.documentElement,
      true /* deep */
    );

    this.shadowRoot.importNodeAndAppendChildAt(
      this.shadowRoot.firstChild,
      spansDiv,
      true /* deep */
    );
  }
};

/**
 * A three dimensional vector.
 *
 * @typedef {[number, number, number]} Vec3
 */

/**
 * A 3x3 matrix.
 *
 * @typedef {[number, number, number,
 *            number, number, number,
 *            number, number, number]} Matrix3
 */

/**
 * A 4x4 matrix.
 *
 * @typedef {[number, number, number, number,
 *            number, number, number, number,
 *            number, number, number, number,
 *            number, number, number, number]} Matrix4
 */

/**
 * Compute the adjugate matrix.
 * https://en.wikipedia.org/wiki/Adjugate_matrix
 *
 * @param {Matrix3} m
 * @returns {Matrix3}
 */
function computeAdjugate(m) {
  // prettier-ignore
  return [
    m[4] * m[8] - m[5] * m[7],
    m[2] * m[7] - m[1] * m[8],
    m[1] * m[5] - m[2] * m[4],
    m[5] * m[6] - m[3] * m[8],
    m[0] * m[8] - m[2] * m[6],
    m[2] * m[3] - m[0] * m[5],
    m[3] * m[7] - m[4] * m[6],
    m[1] * m[6] - m[0] * m[7],
    m[0] * m[4] - m[1] * m[3],
  ];
}

/**
 * @param {Matrix3} a
 * @param {Matrix3} b
 * @returns {Matrix3}
 */
function multiplyMat3(a, b) {
  let out = [];
  for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
      let sum = 0;
      for (let k = 0; k < 3; k++) {
        sum += a[3 * i + k] * b[3 * k + j];
      }
      out[3 * i + j] = sum;
    }
  }
  return out;
}

/**
 * @param {Matrix3} m
 * @param {Vec3} v
 * @returns {Vec3}
 */
function multiplyMat3Vec3(m, v) {
  // prettier-ignore
  return [
    m[0] * v[0] + m[1] * v[1] + m[2] * v[2],
    m[3] * v[0] + m[4] * v[1] + m[5] * v[2],
    m[6] * v[0] + m[7] * v[1] + m[8] * v[2],
  ];
}

/**
 * @returns {Matrix3}
 */
function basisToPoints(x1, y1, x2, y2, x3, y3, x4, y4) {
  /** @type {Matrix3} */
  let mat3 = [x1, x2, x3, y1, y2, y3, 1, 1, 1];
  let vec3 = multiplyMat3Vec3(computeAdjugate(mat3), [x4, y4, 1]);
  // prettier-ignore
  return multiplyMat3(
    mat3,
    [
      vec3[0], 0,       0,
      0,       vec3[1], 0,
      0,       0,       vec3[2]
    ]
  );
}

/**
 * @type {(...Matrix4) => Matrix3}
 */
// prettier-ignore
function general2DProjection(
  x1s, y1s, x1d, y1d,
  x2s, y2s, x2d, y2d,
  x3s, y3s, x3d, y3d,
  x4s, y4s, x4d, y4d
) {
  let s = basisToPoints(x1s, y1s, x2s, y2s, x3s, y3s, x4s, y4s);
  let d = basisToPoints(x1d, y1d, x2d, y2d, x3d, y3d, x4d, y4d);
  return multiplyMat3(d, computeAdjugate(s));
}

/**
 * Given a width and height, compute a projection matrix to points 1-4.
 *
 * The points (x1,y1) through (x4, y4) use the following ordering:
 *
 *         w
 *      ┌─────┐      project     1 ─────── 2
 *    h │     │       -->        │        /
 *      └─────┘                  │       /
 *                               3 ──── 4
 *
 * @returns {Matrix4}
 */
function projectPoints(w, h, x1, y1, x2, y2, x3, y3, x4, y4) {
  // prettier-ignore
  const mat3 = general2DProjection(
    0, 0, x1, y1,
    w, 0, x2, y2,
    0, h, x3, y3,
    w, h, x4, y4
  );

  for (let i = 0; i < 9; i++) {
    mat3[i] = mat3[i] / mat3[8];
  }

  // prettier-ignore
  return [
    mat3[0], mat3[3], 0, mat3[6],
    mat3[1], mat3[4], 0, mat3[7],
    0,       0,       1, 0,
    mat3[2], mat3[5], 0, mat3[8],
  ];
}
PK
!<�qw���7chrome/toolkit/content/global/elements/toolbarbutton.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
  const KEEP_CHILDREN = new Set([
    "observes",
    "template",
    "menupopup",
    "panel",
    "tooltip",
  ]);

  window.addEventListener(
    "popupshowing",
    e => {
      if (e.originalTarget.ownerDocument != document) {
        return;
      }

      e.originalTarget.setAttribute("hasbeenopened", "true");
      for (let el of e.originalTarget.querySelectorAll("toolbarbutton")) {
        el.render();
      }
    },
    { capture: true }
  );

  class MozToolbarbutton extends MozElements.ButtonBase {
    static get inheritedAttributes() {
      // Note: if you remove 'wrap' or 'label' from the inherited attributes,
      // you'll need to add them to observedAttributes.
      return {
        ".toolbarbutton-icon":
          "validate,src=image,label,type,consumeanchor,triggeringprincipal=iconloadingprincipal",
        ".toolbarbutton-text": "accesskey,crop,dragover-top,wrap",
        ".toolbarbutton-menu-dropmarker": "disabled,label",

        ".toolbarbutton-badge": "text=badge,style=badgeStyle",
      };
    }

    static get fragment() {
      let frag = document.importNode(
        MozXULElement.parseXULToFragment(`
        <image class="toolbarbutton-icon"></image>
        <label class="toolbarbutton-text" crop="end" flex="1"></label>
        `),
        true
      );
      Object.defineProperty(this, "fragment", { value: frag });
      return frag;
    }

    static get badgedFragment() {
      let frag = document.importNode(
        MozXULElement.parseXULToFragment(`
        <stack class="toolbarbutton-badge-stack">
          <image class="toolbarbutton-icon"/>
          <html:label class="toolbarbutton-badge"/>
        </stack>
        <label class="toolbarbutton-text" crop="end" flex="1"/>
        `),
        true
      );
      Object.defineProperty(this, "badgedFragment", { value: frag });
      return frag;
    }

    static get dropmarkerFragment() {
      let frag = document.importNode(
        MozXULElement.parseXULToFragment(`
          <dropmarker type="menu" class="toolbarbutton-menu-dropmarker"></dropmarker>
        `),
        true
      );
      Object.defineProperty(this, "dropmarkerFragment", { value: frag });
      return frag;
    }

    get _hasRendered() {
      return this.querySelector(":scope > .toolbarbutton-text") != null;
    }

    get _textNode() {
      let node = this.getElementForAttrInheritance(".toolbarbutton-text");
      if (node) {
        Object.defineProperty(this, "_textNode", { value: node });
      }
      return node;
    }

    _setLabel() {
      let label = this.getAttribute("label") || "";
      let hasLabel = this.hasAttribute("label");
      if (this.getAttribute("wrap") == "true") {
        this._textNode.removeAttribute("value");
        this._textNode.textContent = label;
      } else {
        this._textNode.textContent = "";
        if (hasLabel) {
          this._textNode.setAttribute("value", label);
        } else {
          this._textNode.removeAttribute("value");
        }
      }
    }

    attributeChangedCallback(name, oldValue, newValue) {
      if (oldValue === newValue || !this.initializedAttributeInheritance) {
        return;
      }
      // Deal with single/multiline label inheritance:
      if (name == "label" || name == "wrap") {
        this._setLabel();
      }
      // The normal implementation will deal with everything else.
      super.attributeChangedCallback(name, oldValue, newValue);
    }

    connectedCallback() {
      if (this.delayConnectedCallback()) {
        return;
      }

      // Defer creating DOM elements for content inside popups.
      // These will be added in the popupshown handler above.
      let panel = this.closest("panel");
      if (panel && !panel.hasAttribute("hasbeenopened")) {
        return;
      }

      this.render();
    }

    render() {
      if (this._hasRendered) {
        return;
      }

      let badged = this.getAttribute("badged") == "true";

      if (badged) {
        let moveChildren = [];
        for (let child of this.children) {
          if (!KEEP_CHILDREN.has(child.tagName)) {
            moveChildren.push(child);
          }
        }

        this.appendChild(this.constructor.badgedFragment.cloneNode(true));

        if (this.hasAttribute("wantdropmarker")) {
          this.appendChild(this.constructor.dropmarkerFragment.cloneNode(true));
        }

        if (moveChildren.length) {
          let { badgeStack, icon } = this;
          for (let child of moveChildren) {
            if (child.getAttribute("move-after-stack") === "true") {
              this.appendChild(child);
            } else {
              badgeStack.insertBefore(child, icon);
            }
          }
        }
      } else {
        let moveChildren = [];
        for (let child of this.children) {
          if (!KEEP_CHILDREN.has(child.tagName) && child.tagName != "box") {
            // XBL toolbarbutton doesn't insert any anonymous content
            // if it has a child of any other type
            return;
          }

          if (child.tagName == "box") {
            moveChildren.push(child);
          }
        }

        this.appendChild(this.constructor.fragment.cloneNode(true));

        if (this.hasAttribute("wantdropmarker")) {
          this.appendChild(this.constructor.dropmarkerFragment.cloneNode(true));
        }

        // XBL toolbarbutton explicitly places any <box> children
        // right before the menu marker.
        for (let child of moveChildren) {
          this.insertBefore(child, this.lastChild);
        }
      }

      this.initializeAttributeInheritance();
      this._setLabel();
    }

    get icon() {
      return this.querySelector(".toolbarbutton-icon");
    }

    get badgeLabel() {
      return this.querySelector(".toolbarbutton-badge");
    }

    get badgeStack() {
      return this.querySelector(".toolbarbutton-badge-stack");
    }

    get multilineLabel() {
      if (this.getAttribute("wrap") == "true") {
        return this._textNode;
      }
      return null;
    }

    get dropmarker() {
      return this.querySelector(".toolbarbutton-menu-dropmarker");
    }

    get menupopup() {
      return this.querySelector("menupopup");
    }
  }

  customElements.define("toolbarbutton", MozToolbarbutton);
}
PK
!<f����.chrome/toolkit/content/global/elements/tree.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* globals XULTreeElement */

"use strict";

// This is loaded into all XUL windows. Wrap in a block to prevent
// leaking to window scope.
{
  const { AppConstants } = ChromeUtils.importESModule(
    "resource://gre/modules/AppConstants.sys.mjs"
  );

  class MozTreeChildren extends MozElements.BaseControl {
    constructor() {
      super();

      /**
       * If there is no modifier key, we select on mousedown, not
       * click, so that drags work correctly.
       */
      this.addEventListener("mousedown", event => {
        if (this.parentNode.disabled) {
          return;
        }
        if (
          ((!event.getModifierState("Accel") ||
            !this.parentNode.pageUpOrDownMovesSelection) &&
            !event.shiftKey &&
            !event.metaKey) ||
          this.parentNode.view.selection.single
        ) {
          var b = this.parentNode;
          var cell = b.getCellAt(event.clientX, event.clientY);
          var view = this.parentNode.view;

          // save off the last selected row
          this._lastSelectedRow = cell.row;

          if (cell.row == -1) {
            return;
          }

          if (cell.childElt == "twisty") {
            return;
          }

          if (cell.col && event.button == 0) {
            if (cell.col.cycler) {
              view.cycleCell(cell.row, cell.col);
              return;
            } else if (cell.col.type == window.TreeColumn.TYPE_CHECKBOX) {
              if (
                this.parentNode.editable &&
                cell.col.editable &&
                view.isEditable(cell.row, cell.col)
              ) {
                var value = view.getCellValue(cell.row, cell.col);
                value = value == "true" ? "false" : "true";
                view.setCellValue(cell.row, cell.col, value);
                return;
              }
            }
          }

          if (!view.selection.isSelected(cell.row)) {
            view.selection.select(cell.row);
            b.ensureRowIsVisible(cell.row);
          }
        }
      });

      /**
       * On a click (up+down on the same item), deselect everything
       * except this item.
       */
      this.addEventListener("click", event => {
        if (event.button != 0) {
          return;
        }
        if (this.parentNode.disabled) {
          return;
        }
        var b = this.parentNode;
        var cell = b.getCellAt(event.clientX, event.clientY);
        var view = this.parentNode.view;

        if (cell.row == -1) {
          return;
        }

        if (cell.childElt == "twisty") {
          if (
            view.selection.currentIndex >= 0 &&
            view.isContainerOpen(cell.row)
          ) {
            var parentIndex = view.getParentIndex(view.selection.currentIndex);
            while (parentIndex >= 0 && parentIndex != cell.row) {
              parentIndex = view.getParentIndex(parentIndex);
            }
            if (parentIndex == cell.row) {
              var parentSelectable = true;
              if (parentSelectable) {
                view.selection.select(parentIndex);
              }
            }
          }
          this.parentNode.changeOpenState(cell.row);
          return;
        }

        if (!view.selection.single) {
          var augment = event.getModifierState("Accel");
          if (event.shiftKey) {
            view.selection.rangedSelect(-1, cell.row, augment);
            b.ensureRowIsVisible(cell.row);
            return;
          }
          if (augment) {
            view.selection.toggleSelect(cell.row);
            b.ensureRowIsVisible(cell.row);
            view.selection.currentIndex = cell.row;
            return;
          }
        }

        /* We want to deselect all the selected items except what was
          clicked, UNLESS it was a right-click.  We have to do this
          in click rather than mousedown so that you can drag a
          selected group of items */

        if (!cell.col) {
          return;
        }

        // if the last row has changed in between the time we
        // mousedown and the time we click, don't fire the select handler.
        // see bug #92366
        if (
          !cell.col.cycler &&
          this._lastSelectedRow == cell.row &&
          cell.col.type != window.TreeColumn.TYPE_CHECKBOX
        ) {
          view.selection.select(cell.row);
          b.ensureRowIsVisible(cell.row);
        }
      });

      /**
       * double-click
       */
      this.addEventListener("dblclick", event => {
        if (this.parentNode.disabled) {
          return;
        }
        var tree = this.parentNode;
        var view = this.parentNode.view;
        var row = view.selection.currentIndex;

        if (row == -1) {
          return;
        }

        var cell = tree.getCellAt(event.clientX, event.clientY);

        if (cell.childElt != "twisty") {
          this.parentNode.startEditing(row, cell.col);
        }

        if (this.parentNode._editingColumn || !view.isContainer(row)) {
          return;
        }

        // Cyclers and twisties respond to single clicks, not double clicks
        if (cell.col && !cell.col.cycler && cell.childElt != "twisty") {
          this.parentNode.changeOpenState(row);
        }
      });
    }

    connectedCallback() {
      if (this.delayConnectedCallback()) {
        return;
      }

      this.setAttribute("slot", "treechildren");

      this._lastSelectedRow = -1;

      if ("_ensureColumnOrder" in this.parentNode) {
        this.parentNode._ensureColumnOrder();
      }
    }
  }

  customElements.define("treechildren", MozTreeChildren);

  class MozTreecolPicker extends MozElements.BaseControl {
    static get markup() {
      return `
      <button class="tree-columnpicker-button"/>
      <menupopup anonid="popup">
        <menuseparator anonid="menuseparator"/>
        <menuitem anonid="menuitem" data-l10n-id="tree-columnpicker-restore-order"/>
      </menupopup>
      `;
    }
    constructor() {
      super();

      window.MozXULElement.insertFTLIfNeeded("toolkit/global/tree.ftl");
    }

    connectedCallback() {
      if (this.delayConnectedCallback()) {
        return;
      }

      this.textContent = "";
      this.appendChild(this.constructor.fragment);

      let button = this.querySelector(".tree-columnpicker-button");
      let popup = this.querySelector('[anonid="popup"]');
      let menuitem = this.querySelector('[anonid="menuitem"]');

      button.addEventListener("command", e => {
        this.buildPopup(popup);
        popup.openPopup(this, "after_end");
        e.preventDefault();
      });

      menuitem.addEventListener("command", e => {
        let tree = this.parentNode.parentNode;
        tree.stopEditing(true);
        this.style.order = "";
        tree._ensureColumnOrder(tree.NATURAL_ORDER);
        e.preventDefault();
      });
    }

    buildPopup(aPopup) {
      // We no longer cache the picker content, remove the old content related to
      // the cols - menuitem and separator should stay.
      aPopup.querySelectorAll("[colindex]").forEach(e => {
        e.remove();
      });

      var refChild = aPopup.firstChild;

      var tree = this.parentNode.parentNode;
      for (
        var currCol = tree.columns.getFirstColumn();
        currCol;
        currCol = currCol.getNext()
      ) {
        // Construct an entry for each column in the row, unless
        // it is not being shown.
        var currElement = currCol.element;
        if (!currElement.hasAttribute("ignoreincolumnpicker")) {
          var popupChild = document.createXULElement("menuitem");
          popupChild.setAttribute("type", "checkbox");
          var columnName =
            currElement.getAttribute("display") ||
            currElement.getAttribute("label");
          popupChild.setAttribute("label", columnName);
          popupChild.setAttribute("colindex", currCol.index);
          if (currElement.getAttribute("hidden") != "true") {
            popupChild.setAttribute("checked", "true");
          }
          if (currCol.primary) {
            popupChild.setAttribute("disabled", "true");
          }
          if (currElement.hasAttribute("closemenu")) {
            popupChild.setAttribute(
              "closemenu",
              currElement.getAttribute("closemenu")
            );
          }

          popupChild.addEventListener("command", function () {
            let colindex = this.getAttribute("colindex");
            let column = tree.columns[colindex];
            if (column) {
              var element = column.element;
              element.hidden = !element.hidden;
            }
          });

          aPopup.insertBefore(popupChild, refChild);
        }
      }

      var hidden = !tree.enableColumnDrag;
      aPopup.querySelectorAll(":scope > :not([colindex])").forEach(e => {
        e.hidden = hidden;
      });
    }
  }

  customElements.define("treecolpicker", MozTreecolPicker);

  class MozTreecol extends MozElements.BaseControl {
    static get observedAttributes() {
      return ["primary", ...super.observedAttributes];
    }

    static get inheritedAttributes() {
      return {
        ".treecol-sortdirection": "sortdirection,hidden=hideheader",
        ".treecol-text": "value=label,crop",
      };
    }

    static get markup() {
      return `
        <label class="treecol-text" flex="1" crop="end"></label>
        <image class="treecol-sortdirection"></image>
      `;
    }

    get _tree() {
      return this.parentNode?.parentNode;
    }

    _invalidate() {
      let tree = this._tree;
      if (!tree || !XULTreeElement.isInstance(tree)) {
        return;
      }
      tree.invalidate();
      tree.columns?.invalidateColumns();
    }

    constructor() {
      super();

      this.addEventListener("mousedown", event => {
        if (event.button != 0) {
          return;
        }
        if (this._tree.enableColumnDrag) {
          var XUL_NS =
            "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
          var cols = this.parentNode.getElementsByTagNameNS(XUL_NS, "treecol");

          // only start column drag operation if there are at least 2 visible columns
          var visible = 0;
          for (var i = 0; i < cols.length; ++i) {
            if (cols[i].getBoundingClientRect().width > 0) {
              ++visible;
            }
          }

          if (visible > 1) {
            window.addEventListener("mousemove", this._onDragMouseMove, true);
            window.addEventListener("mouseup", this._onDragMouseUp, true);
            document.treecolDragging = this;
            this.mDragGesturing = true;
            this.mStartDragX = event.clientX;
            this.mStartDragY = event.clientY;
          }
        }
      });

      this.addEventListener("click", event => {
        if (event.button != 0) {
          return;
        }
        if (event.target != event.originalTarget) {
          return;
        }

        // On Windows multiple clicking on tree columns only cycles one time
        // every 2 clicks.
        if (AppConstants.platform == "win" && event.detail % 2 == 0) {
          return;
        }

        var tree = this._tree;
        if (tree.columns) {
          tree.view.cycleHeader(tree.columns.getColumnFor(this));
        }
      });
    }

    connectedCallback() {
      if (this.delayConnectedCallback()) {
        return;
      }

      this.textContent = "";
      this.appendChild(this.constructor.fragment);
      this.initializeAttributeInheritance();
      if (this.hasAttribute("ordinal")) {
        this.style.order = this.getAttribute("ordinal");
      }
      if (this.hasAttribute("width")) {
        this.style.width = this.getAttribute("width") + "px";
      }

      this._resizeObserver = new ResizeObserver(() => {
        this._invalidate();
      });
      this._resizeObserver.observe(this);
    }

    disconnectedCallback() {
      this._resizeObserver?.unobserve(this);
      this._resizeObserver = null;
    }

    attributeChangedCallback(name, oldValue, newValue) {
      super.attributeChangedCallback(name, oldValue, newValue);
      this._invalidate();
    }

    set ordinal(val) {
      this.style.order = val;
      this.setAttribute("ordinal", val);
    }

    get ordinal() {
      var val = this.style.order;
      if (val == "") {
        return "1";
      }

      return "" + (val == "0" ? 0 : parseInt(val));
    }

    get _previousVisibleColumn() {
      var tree = this.parentNode.parentNode;
      let sib = tree.columns.getColumnFor(this).previousColumn;
      while (sib) {
        if (sib.element && sib.element.getBoundingClientRect().width > 0) {
          return sib.element;
        }

        sib = sib.previousColumn;
      }

      return null;
    }

    _onDragMouseMove(aEvent) {
      var col = document.treecolDragging;
      if (!col) {
        return;
      }

      // determine if we have moved the mouse far enough
      // to initiate a drag
      if (col.mDragGesturing) {
        if (
          Math.abs(aEvent.clientX - col.mStartDragX) < 5 &&
          Math.abs(aEvent.clientY - col.mStartDragY) < 5
        ) {
          return;
        }
        col.mDragGesturing = false;
        col.setAttribute("dragging", "true");
        window.addEventListener("click", col._onDragMouseClick, true);
      }

      var pos = {};
      var targetCol = col.parentNode.parentNode._getColumnAtX(
        aEvent.clientX,
        0.5,
        pos
      );

      // bail if we haven't mousemoved to a different column
      if (col.mTargetCol == targetCol && col.mTargetDir == pos.value) {
        return;
      }

      var tree = col.parentNode.parentNode;
      var sib;
      var column;
      if (col.mTargetCol) {
        // remove previous insertbefore/after attributes
        col.mTargetCol.removeAttribute("insertbefore");
        col.mTargetCol.removeAttribute("insertafter");
        column = tree.columns.getColumnFor(col.mTargetCol);
        tree.invalidateColumn(column);
        sib = col.mTargetCol._previousVisibleColumn;
        if (sib) {
          sib.removeAttribute("insertafter");
          column = tree.columns.getColumnFor(sib);
          tree.invalidateColumn(column);
        }
        col.mTargetCol = null;
        col.mTargetDir = null;
      }

      if (targetCol) {
        // set insertbefore/after attributes
        if (pos.value == "after") {
          targetCol.setAttribute("insertafter", "true");
        } else {
          targetCol.setAttribute("insertbefore", "true");
          sib = targetCol._previousVisibleColumn;
          if (sib) {
            sib.setAttribute("insertafter", "true");
            column = tree.columns.getColumnFor(sib);
            tree.invalidateColumn(column);
          }
        }
        column = tree.columns.getColumnFor(targetCol);
        tree.invalidateColumn(column);
        col.mTargetCol = targetCol;
        col.mTargetDir = pos.value;
      }
    }

    _onDragMouseUp() {
      var col = document.treecolDragging;
      if (!col) {
        return;
      }

      if (!col.mDragGesturing) {
        if (col.mTargetCol) {
          // remove insertbefore/after attributes
          var before = col.mTargetCol.hasAttribute("insertbefore");
          col.mTargetCol.removeAttribute(
            before ? "insertbefore" : "insertafter"
          );

          var sib = col.mTargetCol._previousVisibleColumn;
          if (before && sib) {
            sib.removeAttribute("insertafter");
          }

          // Move the column only if it will result in a different column
          // ordering
          var move = true;

          // If this is a before move and the previous visible column is
          // the same as the column we're moving, don't move
          if (before && col == sib) {
            move = false;
          } else if (!before && col == col.mTargetCol) {
            // If this is an after move and the column we're moving is
            // the same as the target column, don't move.
            move = false;
          }

          if (move) {
            col.parentNode.parentNode._reorderColumn(
              col,
              col.mTargetCol,
              before
            );
          }

          // repaint to remove lines
          col.parentNode.parentNode.invalidate();

          col.mTargetCol = null;
        }
      } else {
        col.mDragGesturing = false;
      }

      document.treecolDragging = null;
      col.removeAttribute("dragging");

      window.removeEventListener("mousemove", col._onDragMouseMove, true);
      window.removeEventListener("mouseup", col._onDragMouseUp, true);
      // we have to wait for the click event to fire before removing
      // cancelling handler
      var clickHandler = function (handler) {
        window.removeEventListener("click", handler, true);
      };
      window.setTimeout(clickHandler, 0, col._onDragMouseClick);
    }

    _onDragMouseClick(aEvent) {
      // prevent click event from firing after column drag and drop
      aEvent.stopPropagation();
      aEvent.preventDefault();
    }
  }

  customElements.define("treecol", MozTreecol);

  class MozTreecols extends MozElements.BaseControl {
    static get inheritedAttributes() {
      return {
        treecolpicker: "tooltiptext=pickertooltiptext",
      };
    }

    static get markup() {
      return `
      <treecolpicker fixed="true"></treecolpicker>
      `;
    }

    connectedCallback() {
      if (this.delayConnectedCallback()) {
        return;
      }

      this.setAttribute("slot", "treecols");

      if (!this.querySelector("treecolpicker")) {
        this.appendChild(this.constructor.fragment);
        this.initializeAttributeInheritance();
      }

      // Set resizeafter="farthest" on the splitters if nothing else has been
      // specified.
      for (let splitter of this.getElementsByTagName("splitter")) {
        if (!splitter.hasAttribute("resizeafter")) {
          splitter.setAttribute("resizeafter", "farthest");
        }
      }
    }
  }

  customElements.define("treecols", MozTreecols);

  class MozTree extends MozElements.BaseControlMixin(
    MozElements.MozElementMixin(XULTreeElement)
  ) {
    static get markup() {
      return `
      <html:link rel="stylesheet" href="chrome://global/content/widgets.css" />
      <html:slot name="treecols"></html:slot>
      <stack class="tree-stack" flex="1">
        <hbox class="tree-rows" flex="1">
          <hbox flex="1" class="tree-bodybox">
            <html:slot name="treechildren"></html:slot>
          </hbox>
          <scrollbar height="0" minwidth="0" minheight="0" orient="vertical"
                     class="hidevscroll-scrollbar scrollbar-topmost"
                     ></scrollbar>
        </hbox>
        <html:input class="tree-input" type="text" hidden="true"/>
      </stack>
      <hbox class="hidehscroll-box">
        <scrollbar orient="horizontal" flex="1" increment="16" class="scrollbar-topmost" ></scrollbar>
        <scrollcorner class="hidevscroll-scrollcorner"></scrollcorner>
      </hbox>
      `;
    }

    constructor() {
      super();

      // These enumerated constants are used as the first argument to
      // _ensureColumnOrder to specify what column ordering should be used.
      this.CURRENT_ORDER = 0;
      this.NATURAL_ORDER = 1; // The original order, which is the DOM ordering

      this.attachShadow({ mode: "open" });
      let handledElements = this.constructor.fragment.querySelectorAll(
        "scrollbar,scrollcorner"
      );
      let stopAndPrevent = e => {
        e.stopPropagation();
        e.preventDefault();
      };
      let stopProp = e => e.stopPropagation();
      for (let el of handledElements) {
        el.addEventListener("click", stopAndPrevent);
        el.addEventListener("contextmenu", stopAndPrevent);
        el.addEventListener("dblclick", stopProp);
        el.addEventListener("command", stopProp);
      }
      this.shadowRoot.appendChild(this.constructor.fragment);

      this.#verticalScrollbar = this.shadowRoot.querySelector(
        "scrollbar[orient='vertical']"
      );
    }

    static get inheritedAttributes() {
      return {
        ".hidehscroll-box": "collapsed=hidehscroll",
        ".hidevscroll-scrollbar": "collapsed=hidevscroll",
        ".hidevscroll-scrollcorner": "collapsed=hidevscroll",
      };
    }

    connectedCallback() {
      if (this.delayConnectedCallback()) {
        return;
      }
      if (!this._eventListenersSetup) {
        this._eventListenersSetup = true;
        this.setupEventListeners();
      }

      this.setAttribute("hidevscroll", "true");
      this.setAttribute("hidehscroll", "true");

      this.initializeAttributeInheritance();

      this.pageUpOrDownMovesSelection = AppConstants.platform != "macosx";

      this._inputField = null;

      this._editingRow = -1;

      this._editingColumn = null;

      this._columnsDirty = true;

      this._lastKeyTime = 0;

      this._incrementalString = "";

      this._touchY = -1;
    }

    setupEventListeners() {
      this.addEventListener("underflow", event => {
        // Scrollport event orientation
        // 0: vertical
        // 1: horizontal
        // 2: both (not used)
        if (event.target.tagName != "treechildren") {
          return;
        }
        if (event.detail == 1) {
          this.setAttribute("hidehscroll", "true");
        } else if (event.detail == 0) {
          this.setAttribute("hidevscroll", "true");
        }
        event.stopPropagation();
      });

      this.addEventListener("overflow", event => {
        if (event.target.tagName != "treechildren") {
          return;
        }
        if (event.detail == 1) {
          this.removeAttribute("hidehscroll");
        } else if (event.detail == 0) {
          this.removeAttribute("hidevscroll");
        }
        event.stopPropagation();
      });

      this.addEventListener("touchstart", event => {
        function isScrollbarElement(target) {
          return (
            (target.localName == "thumb" || target.localName == "slider") &&
            target.namespaceURI ==
              "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
          );
        }
        if (
          event.touches.length > 1 ||
          isScrollbarElement(event.touches[0].target)
        ) {
          // Multiple touch points detected, abort. In particular this aborts
          // the panning gesture when the user puts a second finger down after
          // already panning with one finger. Aborting at this point prevents
          // the pan gesture from being resumed until all fingers are lifted
          // (as opposed to when the user is back down to one finger).
          // Additionally, if the user lands on the scrollbar don't use this
          // code for scrolling, instead allow gecko to handle scrollbar
          // interaction normally.
          this._touchY = -1;
        } else {
          this._touchY = event.touches[0].screenY;
        }
      });

      this.addEventListener("touchmove", event => {
        if (event.touches.length == 1 && this._touchY >= 0) {
          var deltaY = this._touchY - event.touches[0].screenY;
          var lines = Math.trunc(deltaY / this.rowHeight);
          if (Math.abs(lines) > 0) {
            this.scrollByLines(lines);
            deltaY -= lines * this.rowHeight;
            this._touchY = event.touches[0].screenY + deltaY;
          }
          event.preventDefault();
        }
      });

      this.addEventListener("touchend", () => {
        this._touchY = -1;
      });

      // This event doesn't retarget, so listen on the shadow DOM directly
      this.shadowRoot.addEventListener("MozMousePixelScroll", event => {
        if (this.#canScroll(event)) {
          event.preventDefault();
        }
      });

      // This event doesn't retarget, so listen on the shadow DOM directly
      this.shadowRoot.addEventListener("DOMMouseScroll", event => {
        if (!this.#canScroll(event)) {
          return;
        }

        event.preventDefault();

        if (this._editingColumn) {
          return;
        }

        var rows = event.detail;
        if (rows == UIEvent.SCROLL_PAGE_UP) {
          this.scrollByPages(-1);
        } else if (rows == UIEvent.SCROLL_PAGE_DOWN) {
          this.scrollByPages(1);
        } else {
          this.scrollByLines(rows);
        }
      });

      this.addEventListener("MozSwipeGesture", event => {
        // Figure out which row to show
        let targetRow = 0;

        // Only handle swipe gestures up and down
        switch (event.direction) {
          case event.DIRECTION_DOWN:
            targetRow = this.view.rowCount - 1;
          // Fall through for actual action
          case event.DIRECTION_UP:
            this.ensureRowIsVisible(targetRow);
            break;
        }
      });

      this.addEventListener("select", event => {
        if (event.originalTarget == this) {
          this.stopEditing(true);
        }
      });

      this.addEventListener("focus", () => {
        this.focused = true;
        if (this.currentIndex == -1 && this.view.rowCount > 0) {
          this.currentIndex = this.getFirstVisibleRow();
        }
      });

      this.addEventListener(
        "blur",
        event => {
          this.focused = false;
          if (event.target == this.inputField) {
            this.stopEditing(true);
          }
        },
        true
      );

      this.addEventListener("keydown", event => {
        if (event.altKey) {
          return;
        }

        let toggleClose = () => {
          if (this._editingColumn) {
            return;
          }

          let row = this.currentIndex;
          if (row < 0) {
            return;
          }

          if (this.changeOpenState(this.currentIndex, false)) {
            event.preventDefault();
            return;
          }

          let parentIndex = this.view.getParentIndex(this.currentIndex);
          if (parentIndex >= 0) {
            this.view.selection.select(parentIndex);
            this.ensureRowIsVisible(parentIndex);
            event.preventDefault();
          }
        };

        let toggleOpen = () => {
          if (this._editingColumn) {
            return;
          }

          let row = this.currentIndex;
          if (row < 0) {
            return;
          }

          if (this.changeOpenState(row, true)) {
            event.preventDefault();
            return;
          }
          let c = row + 1;
          let view = this.view;
          if (c < view.rowCount && view.getParentIndex(c) == row) {
            // If already opened, select the first child.
            // The getParentIndex test above ensures that the children
            // are already populated and ready.
            this.view.selection.timedSelect(c, this._selectDelay);
            this.ensureRowIsVisible(c);
            event.preventDefault();
          }
        };

        switch (event.keyCode) {
          case KeyEvent.DOM_VK_RETURN: {
            if (this._handleEnter(event)) {
              event.stopPropagation();
              event.preventDefault();
            }
            break;
          }
          case KeyEvent.DOM_VK_ESCAPE: {
            if (this._editingColumn) {
              this.stopEditing(false);
              this.focus();
              event.stopPropagation();
              event.preventDefault();
            }
            break;
          }
          case KeyEvent.DOM_VK_LEFT: {
            if (!this.isRTL) {
              toggleClose();
            } else {
              toggleOpen();
            }
            break;
          }
          case KeyEvent.DOM_VK_RIGHT: {
            if (!this.isRTL) {
              toggleOpen();
            } else {
              toggleClose();
            }
            break;
          }
          case KeyEvent.DOM_VK_UP: {
            if (this._editingColumn) {
              return;
            }

            if (event.getModifierState("Shift")) {
              this._moveByOffsetShift(-1, 0, event);
            } else {
              this._moveByOffset(-1, 0, event);
            }
            break;
          }
          case KeyEvent.DOM_VK_DOWN: {
            if (this._editingColumn) {
              return;
            }
            if (event.getModifierState("Shift")) {
              this._moveByOffsetShift(1, this.view.rowCount - 1, event);
            } else {
              this._moveByOffset(1, this.view.rowCount - 1, event);
            }
            break;
          }
          case KeyEvent.DOM_VK_PAGE_UP: {
            if (this._editingColumn) {
              return;
            }

            if (event.getModifierState("Shift")) {
              this._moveByPageShift(-1, 0, event);
            } else {
              this._moveByPage(-1, 0, event);
            }
            break;
          }
          case KeyEvent.DOM_VK_PAGE_DOWN: {
            if (this._editingColumn) {
              return;
            }

            if (event.getModifierState("Shift")) {
              this._moveByPageShift(1, this.view.rowCount - 1, event);
            } else {
              this._moveByPage(1, this.view.rowCount - 1, event);
            }
            break;
          }
          case KeyEvent.DOM_VK_HOME: {
            if (this._editingColumn) {
              return;
            }

            if (event.getModifierState("Shift")) {
              this._moveToEdgeShift(0, event);
            } else {
              this._moveToEdge(0, event);
            }
            break;
          }
          case KeyEvent.DOM_VK_END: {
            if (this._editingColumn) {
              return;
            }

            if (event.getModifierState("Shift")) {
              this._moveToEdgeShift(this.view.rowCount - 1, event);
            } else {
              this._moveToEdge(this.view.rowCount - 1, event);
            }
            break;
          }
        }
      });

      this.addEventListener("keypress", event => {
        if (this._editingColumn) {
          return;
        }

        if (event.charCode == " ".charCodeAt(0)) {
          var c = this.currentIndex;
          if (
            !this.view.selection.isSelected(c) ||
            (!this.view.selection.single && event.getModifierState("Accel"))
          ) {
            this.view.selection.toggleSelect(c);
            event.preventDefault();
          }
        } else if (
          !this.disableKeyNavigation &&
          event.charCode > 0 &&
          !event.altKey &&
          !event.getModifierState("Accel") &&
          !event.metaKey &&
          !event.ctrlKey
        ) {
          var l = this._keyNavigate(event);
          if (l >= 0) {
            this.view.selection.timedSelect(l, this._selectDelay);
            this.ensureRowIsVisible(l);
          }
          event.preventDefault();
        }
      });
    }

    get body() {
      return this.treeBody;
    }

    get isRTL() {
      return document.defaultView.getComputedStyle(this).direction == "rtl";
    }

    set editable(val) {
      if (val) {
        this.setAttribute("editable", "true");
      } else {
        this.removeAttribute("editable");
      }
    }

    get editable() {
      return this.getAttribute("editable") == "true";
    }
    /**
     * ///////////////// nsIDOMXULSelectControlElement /////////////////  ///////////////// nsIDOMXULMultiSelectControlElement /////////////////
     */
    set selType(val) {
      this.setAttribute("seltype", val);
    }

    get selType() {
      return this.getAttribute("seltype") || "";
    }

    set currentIndex(val) {
      if (this.view) {
        this.view.selection.currentIndex = val;
      }
    }

    get currentIndex() {
      if (this.view && this.view.selection) {
        return this.view.selection.currentIndex;
      }
      return -1;
    }

    set keepCurrentInView(val) {
      if (val) {
        this.setAttribute("keepcurrentinview", "true");
      } else {
        this.removeAttribute("keepcurrentinview");
      }
    }

    get keepCurrentInView() {
      return this.getAttribute("keepcurrentinview") == "true";
    }

    set enableColumnDrag(val) {
      if (val) {
        this.setAttribute("enableColumnDrag", "true");
      } else {
        this.removeAttribute("enableColumnDrag");
      }
    }

    get enableColumnDrag() {
      return this.hasAttribute("enableColumnDrag");
    }

    get inputField() {
      if (!this._inputField) {
        this._inputField = this.shadowRoot.querySelector(".tree-input");
        this._inputField.addEventListener("blur", () => this.stopEditing(true));
      }
      return this._inputField;
    }

    set disableKeyNavigation(val) {
      if (val) {
        this.setAttribute("disableKeyNavigation", "true");
      } else {
        this.removeAttribute("disableKeyNavigation");
      }
    }

    get disableKeyNavigation() {
      return this.hasAttribute("disableKeyNavigation");
    }

    get editingRow() {
      return this._editingRow;
    }

    get editingColumn() {
      return this._editingColumn;
    }

    set _selectDelay(val) {
      this.setAttribute("_selectDelay", val);
    }

    get _selectDelay() {
      return this.getAttribute("_selectDelay") || 50;
    }

    // The first argument (order) can be either one of these constants:
    //   this.CURRENT_ORDER
    //   this.NATURAL_ORDER
    _ensureColumnOrder(order = this.CURRENT_ORDER) {
      if (this.columns) {
        // update the ordinal position of each column to assure that it is
        // an odd number and 2 positions above its next sibling
        var cols = [];

        if (order == this.CURRENT_ORDER) {
          for (
            let col = this.columns.getFirstColumn();
            col;
            col = col.getNext()
          ) {
            cols.push(col.element);
          }
        } else {
          // order == this.NATURAL_ORDER
          cols = this.getElementsByTagName("treecol");
        }

        for (let i = 0; i < cols.length; ++i) {
          cols[i].ordinal = i * 2 + 1;
        }
        // update the ordinal positions of splitters to even numbers, so that
        // they are in between columns
        var splitters = this.getElementsByTagName("splitter");
        for (let i = 0; i < splitters.length; ++i) {
          splitters[i].style.order = (i + 1) * 2;
        }
      }
    }

    _reorderColumn(aColMove, aColBefore, aBefore) {
      this._ensureColumnOrder();

      var i;
      var cols = [];
      var col = this.columns.getColumnFor(aColBefore);
      if (parseInt(aColBefore.ordinal) < parseInt(aColMove.ordinal)) {
        if (aBefore) {
          cols.push(aColBefore);
        }
        for (
          col = col.getNext();
          col.element != aColMove;
          col = col.getNext()
        ) {
          cols.push(col.element);
        }

        aColMove.ordinal = cols[0].ordinal;
        for (i = 0; i < cols.length; ++i) {
          cols[i].ordinal = parseInt(cols[i].ordinal) + 2;
        }
      } else if (aColBefore.ordinal != aColMove.ordinal) {
        if (!aBefore) {
          cols.push(aColBefore);
        }
        for (
          col = col.getPrevious();
          col.element != aColMove;
          col = col.getPrevious()
        ) {
          cols.push(col.element);
        }

        aColMove.ordinal = cols[0].ordinal;
        for (i = 0; i < cols.length; ++i) {
          cols[i].ordinal = parseInt(cols[i].ordinal) - 2;
        }
      } else {
        return;
      }
      this.columns.invalidateColumns();
    }

    _getColumnAtX(aX, aThresh, aPos) {
      let isRTL = this.isRTL;

      if (aPos) {
        aPos.value = isRTL ? "after" : "before";
      }

      var columns = [];
      var col = this.columns.getFirstColumn();
      while (col) {
        columns.push(col);
        col = col.getNext();
      }
      if (isRTL) {
        columns.reverse();
      }
      var currentX = this.getBoundingClientRect().x;
      var adjustedX = aX + this.horizontalPosition;
      for (var i = 0; i < columns.length; ++i) {
        col = columns[i];
        var cw = col.element.getBoundingClientRect().width;
        if (cw > 0) {
          currentX += cw;
          if (currentX - cw * aThresh > adjustedX) {
            return col.element;
          }
        }
      }

      if (aPos) {
        aPos.value = isRTL ? "before" : "after";
      }
      return columns.pop().element;
    }

    changeOpenState(row, openState) {
      if (row < 0 || !this.view.isContainer(row)) {
        return false;
      }

      if (this.view.isContainerOpen(row) != openState) {
        this.view.toggleOpenState(row);
        if (row == this.currentIndex) {
          // Only fire event when current row is expanded or collapsed
          // because that's all the assistive technology really cares about.
          var event = document.createEvent("Events");
          event.initEvent("OpenStateChange", true, true);
          this.dispatchEvent(event);
        }
        return true;
      }
      return false;
    }

    _keyNavigate(event) {
      var key = String.fromCharCode(event.charCode).toLowerCase();
      if (event.timeStamp - this._lastKeyTime > 1000) {
        this._incrementalString = key;
      } else {
        this._incrementalString += key;
      }
      this._lastKeyTime = event.timeStamp;

      var length = this._incrementalString.length;
      var incrementalString = this._incrementalString;
      var charIndex = 1;
      while (
        charIndex < length &&
        incrementalString[charIndex] == incrementalString[charIndex - 1]
      ) {
        charIndex++;
      }
      // If all letters in incremental string are same, just try to match the first one
      if (charIndex == length) {
        length = 1;
        incrementalString = incrementalString.substring(0, length);
      }

      var keyCol = this.columns.getKeyColumn();
      var rowCount = this.view.rowCount;
      var start = 1;

      var c = this.currentIndex;
      if (length > 1) {
        start = 0;
        if (c < 0) {
          c = 0;
        }
      }

      for (var i = 0; i < rowCount; i++) {
        var l = (i + start + c) % rowCount;
        var cellText = this.view.getCellText(l, keyCol);
        cellText = cellText.substring(0, length).toLowerCase();
        if (cellText == incrementalString) {
          return l;
        }
      }
      return -1;
    }

    startEditing(row, column) {
      if (!this.editable) {
        return false;
      }
      if (row < 0 || row >= this.view.rowCount || !column) {
        return false;
      }
      if (column.type !== window.TreeColumn.TYPE_TEXT) {
        return false;
      }
      if (column.cycler || !this.view.isEditable(row, column)) {
        return false;
      }

      // Beyond this point, we are going to edit the cell.
      if (this._editingColumn) {
        this.stopEditing();
      }

      var input = this.inputField;

      this.ensureCellIsVisible(row, column);

      // Get the coordinates of the text inside the cell.
      var textRect = this.getCoordsForCellItem(row, column, "text");

      // Get the coordinates of the cell itself.
      var cellRect = this.getCoordsForCellItem(row, column, "cell");

      // Calculate the top offset of the textbox.
      var style = window.getComputedStyle(input);
      var topadj = parseInt(style.borderTopWidth) + parseInt(style.paddingTop);
      input.style.top = `${textRect.y - topadj}px`;

      // The leftside of the textbox is aligned to the left side of the text
      // in LTR mode, and left side of the cell in RTL mode.
      let left = style.direction == "rtl" ? cellRect.x : textRect.x;
      let scrollbarWidth = window.windowUtils.getBoundsWithoutFlushing(
        this.#verticalScrollbar
      ).width;
      // Note: this won't be quite right in RTL for trees using twisties
      // or indentation. bug 1708159 tracks fixing the implementation
      // of getCoordsForCellItem which we called above so it provides
      // better numbers in those cases.
      let widthdiff = Math.abs(textRect.x - cellRect.x) - scrollbarWidth;

      input.style.left = `${left}px`;
      input.style.height = `${
        textRect.height +
        topadj +
        parseInt(style.borderBottomWidth) +
        parseInt(style.paddingBottom)
      }px`;
      input.style.width = `${cellRect.width - widthdiff}px`;
      input.hidden = false;

      input.value = this.view.getCellText(row, column);

      input.select();
      input.focus();

      this._editingRow = row;
      this._editingColumn = column;
      this.setAttribute("editing", "true");

      this.invalidateCell(row, column);
      return true;
    }

    stopEditing(accept) {
      if (!this._editingColumn) {
        return;
      }

      var input = this.inputField;
      var editingRow = this._editingRow;
      var editingColumn = this._editingColumn;
      this._editingRow = -1;
      this._editingColumn = null;

      // `this.view` could be null if the tree was hidden before we were called.
      if (accept && this.view) {
        var value = input.value;
        this.view.setCellText(editingRow, editingColumn, value);
      }
      input.hidden = true;
      input.value = "";
      this.removeAttribute("editing");
    }

    _moveByOffset(offset, edge, event) {
      event.preventDefault();

      if (this.view.rowCount == 0) {
        return;
      }

      if (event.getModifierState("Accel") && this.view.selection.single) {
        this.scrollByLines(offset);
        return;
      }

      var c = this.currentIndex + offset;
      if (offset > 0 ? c > edge : c < edge) {
        if (
          this.view.selection.isSelected(edge) &&
          this.view.selection.count <= 1
        ) {
          return;
        }
        c = edge;
      }

      if (!event.getModifierState("Accel")) {
        this.view.selection.timedSelect(c, this._selectDelay);
      }
      // Ctrl+Up/Down moves the anchor without selecting
      else {
        this.currentIndex = c;
      }
      this.ensureRowIsVisible(c);
    }

    _moveByOffsetShift(offset, edge, event) {
      event.preventDefault();

      if (this.view.rowCount == 0) {
        return;
      }

      if (this.view.selection.single) {
        this.scrollByLines(offset);
        return;
      }

      if (this.view.rowCount == 1 && !this.view.selection.isSelected(0)) {
        this.view.selection.timedSelect(0, this._selectDelay);
        return;
      }

      var c = this.currentIndex;
      if (c == -1) {
        c = 0;
      }

      if (c == edge) {
        if (this.view.selection.isSelected(c)) {
          return;
        }
      }

      // Extend the selection from the existing pivot, if any
      this.view.selection.rangedSelect(
        -1,
        c + offset,
        event.getModifierState("Accel")
      );
      this.ensureRowIsVisible(c + offset);
    }

    _moveByPage(offset, edge, event) {
      event.preventDefault();

      if (this.view.rowCount == 0) {
        return;
      }

      if (this.pageUpOrDownMovesSelection == event.getModifierState("Accel")) {
        this.scrollByPages(offset);
        return;
      }

      if (this.view.rowCount == 1 && !this.view.selection.isSelected(0)) {
        this.view.selection.timedSelect(0, this._selectDelay);
        return;
      }

      var c = this.currentIndex;
      if (c == -1) {
        return;
      }

      if (c == edge && this.view.selection.isSelected(c)) {
        this.ensureRowIsVisible(c);
        return;
      }
      var i = this.getFirstVisibleRow();
      var p = this.getPageLength();

      if (offset > 0) {
        i += p - 1;
        if (c >= i) {
          i = c + p;
          this.ensureRowIsVisible(i > edge ? edge : i);
        }
        i = i > edge ? edge : i;
      } else if (c <= i) {
        i = c <= p ? 0 : c - p;
        this.ensureRowIsVisible(i);
      }
      this.view.selection.timedSelect(i, this._selectDelay);
    }

    _moveByPageShift(offset, edge, event) {
      event.preventDefault();

      if (this.view.rowCount == 0) {
        return;
      }

      if (
        this.view.rowCount == 1 &&
        !this.view.selection.isSelected(0) &&
        !(this.pageUpOrDownMovesSelection == event.getModifierState("Accel"))
      ) {
        this.view.selection.timedSelect(0, this._selectDelay);
        return;
      }

      if (this.view.selection.single) {
        return;
      }

      var c = this.currentIndex;
      if (c == -1) {
        return;
      }
      if (c == edge && this.view.selection.isSelected(c)) {
        this.ensureRowIsVisible(edge);
        return;
      }
      var i = this.getFirstVisibleRow();
      var p = this.getPageLength();

      if (offset > 0) {
        i += p - 1;
        if (c >= i) {
          i = c + p;
          this.ensureRowIsVisible(i > edge ? edge : i);
        }
        // Extend the selection from the existing pivot, if any
        this.view.selection.rangedSelect(
          -1,
          i > edge ? edge : i,
          event.getModifierState("Accel")
        );
      } else {
        if (c <= i) {
          i = c <= p ? 0 : c - p;
          this.ensureRowIsVisible(i);
        }
        // Extend the selection from the existing pivot, if any
        this.view.selection.rangedSelect(
          -1,
          i,
          event.getModifierState("Accel")
        );
      }
    }

    _moveToEdge(edge, event) {
      event.preventDefault();

      if (this.view.rowCount == 0) {
        return;
      }

      if (
        this.view.selection.isSelected(edge) &&
        this.view.selection.count == 1
      ) {
        this.currentIndex = edge;
        return;
      }

      // Normal behaviour is to select the first/last row
      if (!event.getModifierState("Accel")) {
        this.view.selection.timedSelect(edge, this._selectDelay);
      }
      // In a multiselect tree Ctrl+Home/End moves the anchor
      else if (!this.view.selection.single) {
        this.currentIndex = edge;
      }

      this.ensureRowIsVisible(edge);
    }

    _moveToEdgeShift(edge, event) {
      event.preventDefault();

      if (this.view.rowCount == 0) {
        return;
      }

      if (this.view.rowCount == 1 && !this.view.selection.isSelected(0)) {
        this.view.selection.timedSelect(0, this._selectDelay);
        return;
      }

      if (
        this.view.selection.single ||
        (this.view.selection.isSelected(edge) &&
          this.view.selection.isSelected(this.currentIndex))
      ) {
        return;
      }

      // Extend the selection from the existing pivot, if any.
      // -1 doesn't work here, so using currentIndex instead
      this.view.selection.rangedSelect(
        this.currentIndex,
        edge,
        event.getModifierState("Accel")
      );

      this.ensureRowIsVisible(edge);
    }

    _handleEnter() {
      if (this._editingColumn) {
        this.stopEditing(true);
        this.focus();
        return true;
      }

      return this.changeOpenState(this.currentIndex);
    }

    #verticalScrollbar = null;
    #lastScrollEventTimeStampMap = new Map();

    #canScroll(event) {
      const lastScrollEventTimeStamp = this.#lastScrollEventTimeStampMap.get(
        event.type
      );
      this.#lastScrollEventTimeStampMap.set(event.type, event.timeStamp);

      if (
        window.windowUtils.getWheelScrollTarget() ||
        event.axis == event.HORIZONTAL_AXIS ||
        (this.getAttribute("allowunderflowscroll") == "true" &&
          this.getAttribute("hidevscroll") == "true")
      ) {
        return false;
      }

      if (
        event.timeStamp - (lastScrollEventTimeStamp ?? 0) <
        Services.prefs.getIntPref("mousewheel.scroll_series_timeout")
      ) {
        // If the time difference of previous event does not over the timeout,
        // handle the event in tree as the same seies of events even if the
        // current position is edge.
        return true;
      }

      const curpos = Number(this.#verticalScrollbar.getAttribute("curpos"));
      return (
        (event.detail < 0 && 0 < curpos) ||
        (event.detail > 0 &&
          curpos < Number(this.#verticalScrollbar.getAttribute("maxpos")))
      );
    }
  }

  MozXULElement.implementCustomInterface(MozTree, [
    Ci.nsIDOMXULMultiSelectControlElement,
  ]);
  customElements.define("tree", MozTree);
}
PK
!<tfsj����7chrome/toolkit/content/global/elements/videocontrols.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is a UA widget. It runs in per-origin UA widget scope,
// to be loaded by UAWidgetsChild.sys.mjs.

/*
 * This is the class of entry. It will construct the actual implementation
 * according to the value of the "controls" property.
 */
this.VideoControlsWidget = class {
  constructor(shadowRoot, prefs) {
    this.shadowRoot = shadowRoot;
    this.prefs = prefs;
    this.element = shadowRoot.host;
    this.document = this.element.ownerDocument;
    this.window = this.document.defaultView;

    this.isMobile = this.window.navigator.appVersion.includes("Android");
  }

  /*
   * Callback called by UAWidgets right after constructor.
   */
  onsetup() {
    this.switchImpl();
  }

  /*
   * Callback called by UAWidgets when the "controls" property changes.
   */
  onchange() {
    this.switchImpl();
  }

  /*
   * Actually switch the implementation.
   * - With "controls" set, the VideoControlsImplWidget controls should load.
   * - Without it, on mobile, the NoControlsMobileImplWidget should load, so
   *   the user could see the click-to-play button when the video/audio is blocked.
   * - Without it, on desktop, the NoControlsPictureInPictureImpleWidget should load
   *   if the video is being viewed in Picture-in-Picture.
   */
  switchImpl() {
    let newImpl;
    let pageURI = this.document.documentURI;
    if (this.element.controls) {
      newImpl = VideoControlsImplWidget;
    } else if (this.isMobile) {
      newImpl = NoControlsMobileImplWidget;
    } else if (VideoControlsWidget.isPictureInPictureVideo(this.element)) {
      newImpl = NoControlsPictureInPictureImplWidget;
    } else if (
      pageURI.startsWith("http://") ||
      pageURI.startsWith("https://")
    ) {
      newImpl = NoControlsDesktopImplWidget;
    }

    // Skip if we are asked to load the same implementation, and
    // the underlying element state hasn't changed in ways that we
    // care about. This can happen if the property is set again
    // without a value change.
    if (this.impl && this.impl.constructor == newImpl) {
      this.impl.onchange();
      return;
    }
    if (this.impl) {
      this.impl.teardown();
      this.shadowRoot.firstChild.remove();
    }
    if (newImpl) {
      this.impl = new newImpl(this.shadowRoot, this.prefs);

      this.mDirection = "ltr";
      let intlUtils = this.window.intlUtils;
      if (intlUtils) {
        this.mDirection = intlUtils.isAppLocaleRTL() ? "rtl" : "ltr";
      }

      this.impl.onsetup(this.mDirection);
    } else {
      this.impl = undefined;
    }
  }

  teardown() {
    if (!this.impl) {
      return;
    }
    this.impl.teardown();
    this.shadowRoot.firstChild.remove();
    delete this.impl;
  }

  onPrefChange(prefName, prefValue) {
    this.prefs[prefName] = prefValue;

    if (!this.impl) {
      return;
    }

    this.impl.onPrefChange(prefName, prefValue);
  }

  // If you change this, also change SEEK_TIME_SECS in PictureInPictureChild.sys.mjs
  static SEEK_TIME_SECS = 5;

  static isPictureInPictureVideo(someVideo) {
    return someVideo.isCloningElementVisually;
  }

  /**
   * Returns true if a <video> meets the requirements to show the Picture-in-Picture
   * toggle. Those requirements currently are:
   *
   * 1. The video must be 45 seconds in length or longer.
   * 2. Neither the width or the height of the video can be less than 140px.
   * 3. The video must have audio.
   * 4. The video must not a MediaStream video (Bug 1592539)
   *
   * This can be overridden via the
   * media.videocontrols.picture-in-picture.video-toggle.always-show pref, which
   * is mostly used for testing.
   *
   * @param {Object} prefs
   *   The preferences set that was passed to the UAWidget.
   * @param {Element} someVideo
   *   The <video> to test.
   * @param {Object} reflowedDimensions
   *   An object representing the reflowed dimensions of the <video>. Properties
   *   are:
   *
   *     videoWidth (Number):
   *       The width of the video in pixels.
   *
   *     videoHeight (Number):
   *       The height of the video in pixels.
   *
   * @return {Boolean}
   */
  static shouldShowPictureInPictureToggle(
    prefs,
    someVideo,
    reflowedDimensions
  ) {
    if (
      prefs[
        "media.videocontrols.picture-in-picture.respect-disablePictureInPicture"
      ] &&
      someVideo.disablePictureInPicture
    ) {
      return false;
    }

    if (isNaN(someVideo.duration)) {
      return false;
    }

    if (
      prefs["media.videocontrols.picture-in-picture.video-toggle.always-show"]
    ) {
      return true;
    }

    const MIN_VIDEO_LENGTH =
      prefs[
        "media.videocontrols.picture-in-picture.video-toggle.min-video-secs"
      ];

    if (someVideo.duration < MIN_VIDEO_LENGTH) {
      return false;
    }

    const MIN_VIDEO_DIMENSION = 140; // pixels
    if (
      reflowedDimensions.videoWidth < MIN_VIDEO_DIMENSION ||
      reflowedDimensions.videoHeight < MIN_VIDEO_DIMENSION
    ) {
      return false;
    }

    return true;
  }

  /**
   * Some variations on the Picture-in-Picture toggle are being experimented with.
   * These variations have slightly different setup parameters from the currently
   * shipping toggle, so this method sets up the experimental toggles in the event
   * that they're being used. It also will enable the appropriate stylesheet for
   * the preferred toggle experiment.
   *
   * @param {Object} prefs
   *   The preferences set that was passed to the UAWidget.
   * @param {ShadowRoot} shadowRoot
   *   The shadowRoot of the <video> element where the video controls are.
   * @param {Element} toggle
   *   The toggle element.
   * @param {Object} reflowedDimensions
   *   An object representing the reflowed dimensions of the <video>. Properties
   *   are:
   *
   *     videoWidth (Number):
   *       The width of the video in pixels.
   *
   *     videoHeight (Number):
   *       The height of the video in pixels.
   */
  static setupToggle(prefs, toggle, reflowedDimensions) {
    // These thresholds are all in pixels
    const SMALL_VIDEO_WIDTH_MAX = 320;
    const MEDIUM_VIDEO_WIDTH_MAX = 720;

    let isSmall = reflowedDimensions.videoWidth <= SMALL_VIDEO_WIDTH_MAX;
    toggle.toggleAttribute("small-video", isSmall);
    toggle.toggleAttribute(
      "medium-video",
      !isSmall && reflowedDimensions.videoWidth <= MEDIUM_VIDEO_WIDTH_MAX
    );

    toggle.setAttribute(
      "position",
      prefs["media.videocontrols.picture-in-picture.video-toggle.position"]
    );
    toggle.toggleAttribute(
      "has-used",
      prefs["media.videocontrols.picture-in-picture.video-toggle.has-used"]
    );
  }
};

this.VideoControlsImplWidget = class {
  constructor(shadowRoot, prefs) {
    this.shadowRoot = shadowRoot;
    this.prefs = prefs;
    this.element = shadowRoot.host;
    this.document = this.element.ownerDocument;
    this.window = this.document.defaultView;
  }

  onsetup(direction) {
    this.generateContent();

    this.shadowRoot.firstElementChild.setAttribute("localedir", direction);

    this.Utils = {
      debug: false,
      video: null,
      videocontrols: null,
      controlBar: null,
      playButton: null,
      muteButton: null,
      volumeControl: null,
      durationLabel: null,
      positionLabel: null,
      scrubber: null,
      progressBar: null,
      bufferBar: null,
      statusOverlay: null,
      controlsSpacer: null,
      clickToPlay: null,
      controlsOverlay: null,
      fullscreenButton: null,
      layoutControls: null,
      isShowingPictureInPictureMessage: false,
      l10n: this.l10n,

      textTracksCount: 0,
      videoEvents: [
        "play",
        "pause",
        "ended",
        "volumechange",
        "loadeddata",
        "loadstart",
        "timeupdate",
        "progress",
        "playing",
        "waiting",
        "canplay",
        "canplaythrough",
        "seeking",
        "seeked",
        "emptied",
        "loadedmetadata",
        "error",
        "suspend",
        "stalled",
        "mozvideoonlyseekbegin",
        "mozvideoonlyseekcompleted",
        "durationchange",
      ],

      firstFrameShown: false,
      timeUpdateCount: 0,
      maxCurrentTimeSeen: 0,
      isPausedByDragging: false,
      _isAudioOnly: false,

      get isAudioTag() {
        return this.video.localName == "audio";
      },

      get isAudioOnly() {
        return this._isAudioOnly;
      },
      set isAudioOnly(val) {
        this._isAudioOnly = val;
        this.setFullscreenButtonState();
        this.updatePictureInPictureToggleDisplay();

        if (!this.isTopLevelSyntheticDocument) {
          return;
        }
        if (this._isAudioOnly) {
          this.video.style.height = this.controlBarMinHeight + "px";
          this.video.style.width = "66%";
        } else {
          this.video.style.removeProperty("height");
          this.video.style.removeProperty("width");
        }
      },

      suppressError: false,

      setupStatusFader(immediate) {
        // Since the play button will be showing, we don't want to
        // show the throbber behind it. The throbber here will
        // only show if needed after the play button has been pressed.
        if (!this.clickToPlay.hidden) {
          this.startFadeOut(this.statusOverlay, true);
          return;
        }

        var show = false;
        if (
          this.video.seeking ||
          (this.video.error && !this.suppressError) ||
          this.video.networkState == this.video.NETWORK_NO_SOURCE ||
          (this.video.networkState == this.video.NETWORK_LOADING &&
            (this.video.paused || this.video.ended
              ? this.video.readyState < this.video.HAVE_CURRENT_DATA
              : this.video.readyState < this.video.HAVE_FUTURE_DATA)) ||
          (this.timeUpdateCount <= 1 &&
            !this.video.ended &&
            this.video.readyState < this.video.HAVE_FUTURE_DATA &&
            this.video.networkState == this.video.NETWORK_LOADING)
        ) {
          show = true;
        }

        // Explicitly hide the status fader if this
        // is audio only until bug 619421 is fixed.
        if (this.isAudioOnly) {
          show = false;
        }

        if (this._showThrobberTimer) {
          show = true;
        }

        this.log(
          "Status overlay: seeking=" +
            this.video.seeking +
            " error=" +
            this.video.error +
            " readyState=" +
            this.video.readyState +
            " paused=" +
            this.video.paused +
            " ended=" +
            this.video.ended +
            " networkState=" +
            this.video.networkState +
            " timeUpdateCount=" +
            this.timeUpdateCount +
            " _showThrobberTimer=" +
            this._showThrobberTimer +
            " --> " +
            (show ? "SHOW" : "HIDE")
        );
        this.startFade(this.statusOverlay, show, immediate);
      },

      /*
       * Set the initial state of the controls. The UA widget is normally created along
       * with video element, but could be attached at any point (eg, if the video is
       * removed from the document and then reinserted). Thus, some one-time events may
       * have already fired, and so we'll need to explicitly check the initial state.
       */
      setupInitialState() {
        this.setPlayButtonState(this.video.paused);

        this.setFullscreenButtonState();

        var duration = Math.round(this.video.duration * 1000); // in ms
        var currentTime = Math.round(this.video.currentTime * 1000); // in ms
        this.log(
          "Initial playback position is at " + currentTime + " of " + duration
        );
        // It would be nice to retain maxCurrentTimeSeen, but it would be difficult
        // to determine if the media source changed while we were detached.
        this.maxCurrentTimeSeen = currentTime;
        this.showPosition(currentTime, duration);

        // If we have metadata, check if this is a <video> without
        // video data, or a video with no audio track.
        if (this.video.readyState >= this.video.HAVE_METADATA) {
          if (
            this.video.localName == "video" &&
            (this.video.videoWidth == 0 || this.video.videoHeight == 0)
          ) {
            this.isAudioOnly = true;
          }

          // We have to check again if the media has audio here.
          let noAudio = !this.isAudioOnly && !this.video.mozHasAudio;
          this.muteButton.toggleAttribute("noAudio", noAudio);
          this.muteButton.disabled = noAudio;
        }

        // The video itself might not be fullscreen, but part of the
        // document might be, in which case we set this attribute to
        // apply any styles for the DOM fullscreen case.
        if (this.document.fullscreenElement) {
          this.videocontrols.setAttribute("inDOMFullscreen", true);
        }

        if (this.isAudioOnly) {
          this.startFadeOut(this.clickToPlay, true);
        }

        // If the first frame hasn't loaded, kick off a throbber fade-in.
        if (this.video.readyState >= this.video.HAVE_CURRENT_DATA) {
          this.firstFrameShown = true;
        }

        // We can't determine the exact buffering status, but do know if it's
        // fully loaded. (If it's still loading, it will fire a progress event
        // and we'll figure out the exact state then.)
        this.bufferBar.max = 100;
        if (this.video.readyState >= this.video.HAVE_METADATA) {
          this.showBuffered();
        } else {
          this.bufferBar.value = 0;
        }

        // Set the current status icon.
        if (this.hasError()) {
          this.startFadeOut(this.clickToPlay, true);
          this.statusIcon.setAttribute("type", "error");
          this.updateErrorText();
          this.setupStatusFader(true);
        }

        this.updatePictureInPictureMessage();

        if (this.video.readyState >= this.video.HAVE_METADATA) {
          // According to the spec[1], at the HAVE_METADATA (or later) state, we know
          // the video duration and dimensions, which means we can calculate whether or
          // not to show the Picture-in-Picture toggle now.
          //
          // [1]: https://www.w3.org/TR/html50/embedded-content-0.html#dom-media-have_metadata
          this.updatePictureInPictureToggleDisplay();
        }

        let adjustableControls = [
          ...this.prioritizedControls,
          this.controlBar,
          this.clickToPlay,
        ];

        for (let control of adjustableControls) {
          if (!control) {
            break;
          }

          this.defineControlProperties(control);
        }
        this.adjustControlSize();

        // Can only update the volume controls once we've computed
        // _volumeControlWidth, since the volume slider implementation
        // depends on it.
        this.updateVolumeControls();
      },

      defineControlProperties(control) {
        let throwOnGet = {
          get() {
            throw new Error("Please don't trigger reflow. See bug 1493525.");
          },
        };
        Object.defineProperties(control, {
          // We should directly access CSSOM to get pre-defined style instead of
          // retrieving computed dimensions from layout.
          minWidth: {
            get: () => {
              let controlId = control.id;
              let propertyName = `--${controlId}-width`;
              if (control.modifier) {
                propertyName += "-" + control.modifier;
              }
              let preDefinedSize =
                this.controlBarComputedStyles.getPropertyValue(propertyName);

              // The stylesheet from <link> might not be loaded if the
              // element was inserted into a hidden iframe.
              // We can safely return 0 here for now, given that the controls
              // will be resized again, by the resizevideocontrols event,
              // from nsVideoFrame, when the element is visible.
              if (!preDefinedSize) {
                return 0;
              }

              return parseInt(preDefinedSize, 10);
            },
          },
          offsetLeft: throwOnGet,
          offsetTop: throwOnGet,
          offsetWidth: throwOnGet,
          offsetHeight: throwOnGet,
          offsetParent: throwOnGet,
          clientLeft: throwOnGet,
          clientTop: throwOnGet,
          clientWidth: throwOnGet,
          clientHeight: throwOnGet,
          getClientRects: throwOnGet,
          getBoundingClientRect: throwOnGet,
          isAdjustableControl: {
            value: true,
          },
          modifier: {
            value: "",
            writable: true,
          },
          isWanted: {
            value: true,
            writable: true,
          },
          hidden: {
            set: v => {
              control._isHiddenExplicitly = v;
              control._updateHiddenAttribute();
            },
            get: () => {
              return (
                control.hasAttribute("hidden") ||
                control.classList.contains("fadeout")
              );
            },
          },
          hiddenByAdjustment: {
            set: v => {
              control._isHiddenByAdjustment = v;
              control._updateHiddenAttribute();
            },
            get: () => control._isHiddenByAdjustment,
          },
          _isHiddenByAdjustment: {
            value: false,
            writable: true,
          },
          _isHiddenExplicitly: {
            value: false,
            writable: true,
          },
          _updateHiddenAttribute: {
            value: () => {
              control.toggleAttribute(
                "hidden",
                control._isHiddenExplicitly || control._isHiddenByAdjustment
              );
            },
          },
        });
      },

      updatePictureInPictureToggleDisplay() {
        if (this.isAudioOnly) {
          this.pictureInPictureToggle.hidden = true;
          return;
        }

        // We only want to show the toggle when the closed captions menu
        // is closed, in order to avoid visual overlap.
        if (
          this.pipToggleEnabled &&
          !this.isShowingPictureInPictureMessage &&
          this.textTrackListContainer.hidden &&
          VideoControlsWidget.shouldShowPictureInPictureToggle(
            this.prefs,
            this.video,
            this.reflowedDimensions
          )
        ) {
          this.pictureInPictureToggle.hidden = false;
          VideoControlsWidget.setupToggle(
            this.prefs,
            this.pictureInPictureToggle,
            this.reflowedDimensions
          );
        } else {
          this.pictureInPictureToggle.hidden = true;
        }
      },

      setupNewLoadState() {
        // For videos with |autoplay| set, we'll leave the controls initially hidden,
        // so that they don't get in the way of the playing video. Otherwise we'll
        // go ahead and reveal the controls now, so they're an obvious user cue.
        var shouldShow =
          !this.dynamicControls || (this.video.paused && !this.video.autoplay);
        // Hide the overlay if the video time is non-zero or if an error occurred to workaround bug 718107.
        let shouldClickToPlayShow =
          shouldShow &&
          !this.isAudioOnly &&
          this.video.currentTime == 0 &&
          !this.hasError() &&
          !this.isShowingPictureInPictureMessage;
        this.startFade(this.clickToPlay, shouldClickToPlayShow, true);
        this.startFade(this.controlBar, shouldShow, true);
      },

      get dynamicControls() {
        // Don't fade controls for <audio> elements.
        var enabled = !this.isAudioOnly;

        // Allow tests to explicitly suppress the fading of controls.
        if (this.video.hasAttribute("mozNoDynamicControls")) {
          enabled = false;
        }

        // If the video hits an error, suppress controls if it
        // hasn't managed to do anything else yet.
        if (!this.firstFrameShown && this.hasError()) {
          enabled = false;
        }

        return enabled;
      },

      updateVolume() {
        const volume = this.volumeControl.value;
        this.setVolume(volume / 100);
      },

      updateVolumeControls() {
        var volume = this.video.muted ? 0 : this.video.volume;
        var volumePercentage = Math.round(volume * 100);
        this.updateMuteButtonState();
        this.volumeControl.value = volumePercentage;
      },

      /*
       * We suspend a video element's video decoder if the video
       * element is invisible. However, resuming the video decoder
       * takes time and we show the throbber UI if it takes more than
       * 250 ms.
       *
       * When an already-suspended video element becomes visible, we
       * resume its video decoder immediately and queue a video-only seek
       * task to seek the resumed video decoder to the current position;
       * meanwhile, we also file a "mozvideoonlyseekbegin" event which
       * we used to start the timer here.
       *
       * Once the queued seek operation is done, we dispatch a
       * "canplay" event which indicates that the resuming operation
       * is completed.
       */
      SHOW_THROBBER_TIMEOUT_MS: 250,
      _showThrobberTimer: null,
      _delayShowThrobberWhileResumingVideoDecoder() {
        this._showThrobberTimer = this.window.setTimeout(() => {
          this.statusIcon.setAttribute("type", "throbber");
          // Show the throbber immediately since we have waited for SHOW_THROBBER_TIMEOUT_MS.
          // We don't want to wait for another animation delay(750ms) and the
          // animation duration(300ms).
          this.setupStatusFader(true);
        }, this.SHOW_THROBBER_TIMEOUT_MS);
      },
      _cancelShowThrobberWhileResumingVideoDecoder() {
        if (this._showThrobberTimer) {
          this.window.clearTimeout(this._showThrobberTimer);
          this._showThrobberTimer = null;
        }
      },

      handleEvent(aEvent) {
        if (!aEvent.isTrusted) {
          this.log("Drop untrusted event ----> " + aEvent.type);
          return;
        }

        this.log("Got event ----> " + aEvent.type);

        if (this.videoEvents.includes(aEvent.type)) {
          this.handleVideoEvent(aEvent);
        } else {
          this.handleControlEvent(aEvent);
        }
      },

      handleVideoEvent(aEvent) {
        switch (aEvent.type) {
          case "play":
            this.setPlayButtonState(false);
            this.setupStatusFader();
            if (
              !this._triggeredByControls &&
              this.dynamicControls &&
              this.isTouchControls
            ) {
              this.startFadeOut(this.controlBar);
            }
            if (!this._triggeredByControls) {
              this.startFadeOut(this.clickToPlay, true);
            }
            this._triggeredByControls = false;
            break;
          case "pause":
            // Little white lie: if we've internally paused the video
            // while dragging the scrubber, don't change the button state.
            if (!this.scrubber.isDragging) {
              this.setPlayButtonState(true);
            }
            this.setupStatusFader();
            break;
          case "ended":
            this.setPlayButtonState(true);
            // We throttle timechange events, so the thumb might not be
            // exactly at the end when the video finishes.
            this.showPosition(
              Math.round(this.video.currentTime * 1000),
              Math.round(this.video.duration * 1000)
            );
            this.startFadeIn(this.controlBar);
            this.setupStatusFader();
            break;
          case "volumechange":
            this.updateVolumeControls();
            // Show the controls to highlight the changing volume,
            // but only if the click-to-play overlay has already
            // been hidden (we don't hide controls when the overlay is visible).
            if (this.clickToPlay.hidden && !this.isAudioOnly) {
              this.startFadeIn(this.controlBar);
              this.window.clearTimeout(this._hideControlsTimeout);
              this._hideControlsTimeout = this.window.setTimeout(
                () => this._hideControlsFn(),
                this.HIDE_CONTROLS_TIMEOUT_MS
              );
            }
            break;
          case "loadedmetadata": {
            // If a <video> doesn't have any video data, treat it as <audio>
            // and show the controls (they won't fade back out)
            if (
              this.video.localName == "video" &&
              (this.video.videoWidth == 0 || this.video.videoHeight == 0)
            ) {
              this.isAudioOnly = true;
              this.startFadeOut(this.clickToPlay, true);
              this.startFadeIn(this.controlBar);
              this.setFullscreenButtonState();
            }
            this.showPosition(
              Math.round(this.video.currentTime * 1000),
              Math.round(this.video.duration * 1000)
            );
            let noAudio = !this.isAudioOnly && !this.video.mozHasAudio;
            this.muteButton.toggleAttribute("noAudio", noAudio);
            this.muteButton.disabled = noAudio;
            this.adjustControlSize();
            this.updatePictureInPictureToggleDisplay();
            break;
          }
          case "durationchange":
            this.updatePictureInPictureToggleDisplay();
            break;
          case "loadeddata":
            this.firstFrameShown = true;
            this.setupStatusFader();
            break;
          case "loadstart":
            this.maxCurrentTimeSeen = 0;
            this.controlsSpacer.removeAttribute("aria-label");
            this.statusOverlay.removeAttribute("status");
            this.statusIcon.setAttribute("type", "throbber");
            this.isAudioOnly = this.isAudioTag;
            this.setPlayButtonState(true);
            this.setupNewLoadState();
            this.setupStatusFader();
            break;
          case "progress":
            this.statusIcon.removeAttribute("stalled");
            this.showBuffered();
            this.setupStatusFader();
            break;
          case "stalled":
            this.statusIcon.setAttribute("stalled", "true");
            this.statusIcon.setAttribute("type", "throbber");
            this.setupStatusFader();
            break;
          case "suspend":
            this.setupStatusFader();
            break;
          case "timeupdate":
            var currentTime = Math.round(this.video.currentTime * 1000); // in ms
            var duration = Math.round(this.video.duration * 1000); // in ms

            // If playing/seeking after the video ended, we won't get a "play"
            // event, so update the button state here.
            if (!this.video.paused) {
              this.setPlayButtonState(false);
            }

            this.timeUpdateCount++;
            // Whether we show the statusOverlay sometimes depends
            // on whether we've seen more than one timeupdate
            // event (if we haven't, there hasn't been any
            // "playback activity" and we may wish to show the
            // statusOverlay while we wait for HAVE_ENOUGH_DATA).
            // If we've seen more than 2 timeupdate events,
            // the count is no longer relevant to setupStatusFader.
            if (this.timeUpdateCount <= 2) {
              this.setupStatusFader();
            }

            // If the user is dragging the scrubber ignore the delayed seek
            // responses (don't yank the thumb away from the user)
            if (this.scrubber.isDragging) {
              return;
            }
            this.showPosition(currentTime, duration);
            this.showBuffered();
            break;
          case "emptied":
            this.bufferBar.value = 0;
            this.showPosition(0, 0);
            break;
          case "seeking":
            this.showBuffered();
            this.statusIcon.setAttribute("type", "throbber");
            this.setupStatusFader();
            break;
          case "waiting":
            this.statusIcon.setAttribute("type", "throbber");
            this.setupStatusFader();
            break;
          case "seeked":
          case "playing":
          case "canplay":
          case "canplaythrough":
            this.setupStatusFader();
            break;
          case "error":
            // We'll show the error status icon when we receive an error event
            // under either of the following conditions:
            // 1. The video has its error attribute set; this means we're loading
            //    from our src attribute, and the load failed, or we we're loading
            //    from source children and the decode or playback failed after we
            //    determined our selected resource was playable.
            // 2. The video's networkState is NETWORK_NO_SOURCE. This means we we're
            //    loading from child source elements, but we were unable to select
            //    any of the child elements for playback during resource selection.
            if (this.hasError()) {
              this.suppressError = false;
              this.startFadeOut(this.clickToPlay, true);
              this.statusIcon.setAttribute("type", "error");
              this.updateErrorText();
              this.setupStatusFader(true);
              // If video hasn't shown anything yet, disable the controls.
              if (!this.firstFrameShown && !this.isAudioOnly) {
                this.startFadeOut(this.controlBar);
              }
              this.controlsSpacer.removeAttribute("hideCursor");
            }
            break;
          case "mozvideoonlyseekbegin":
            this._delayShowThrobberWhileResumingVideoDecoder();
            break;
          case "mozvideoonlyseekcompleted":
            this._cancelShowThrobberWhileResumingVideoDecoder();
            this.setupStatusFader();
            break;
          default:
            this.log("!!! media event " + aEvent.type + " not handled!");
        }
      },

      handleControlEvent(aEvent) {
        switch (aEvent.type) {
          case "click":
            switch (aEvent.currentTarget) {
              case this.muteButton:
                this.toggleMute();
                break;
              case this.castingButton:
                this.toggleCasting();
                break;
              case this.closedCaptionButton:
                this.toggleClosedCaption();
                break;
              case this.fullscreenButton:
                this.toggleFullscreen();
                break;
              case this.playButton:
              case this.clickToPlay:
              case this.controlsSpacer:
                this.clickToPlayClickHandler(aEvent);
                break;
              case this.textTrackList:
                const index = +aEvent.originalTarget.getAttribute("index");
                this.changeTextTrack(index);
                this.closedCaptionButton.focus();
                break;
              case this.videocontrols:
                // Prevent any click event within media controls from dispatching through to video.
                aEvent.stopPropagation();
                break;
            }
            break;
          case "dblclick":
            this.toggleFullscreen();
            break;
          case "resizevideocontrols":
            // Since this event come from the layout, this is the only place
            // we are sure of that probing into layout won't trigger or force
            // reflow.
            // FIXME(emilio): We should rewrite this to just use
            // ResizeObserver, probably.
            this.reflowTriggeringCallValidator.isReflowTriggeringPropsAllowed = true;
            this.updateReflowedDimensions();
            this.reflowTriggeringCallValidator.isReflowTriggeringPropsAllowed = false;

            let scrubberWasHidden = this.scrubberStack.hidden;
            this.adjustControlSize();
            if (scrubberWasHidden && !this.scrubberStack.hidden) {
              // requestAnimationFrame + setTimeout of 0ms is a best effort way to avoid
              // triggering reflows, but cannot fully guarantee a reflow will not happen.
              this.window.requestAnimationFrame(() =>
                this.window.setTimeout(() => {
                  this.reflowTriggeringCallValidator.isReflowTriggeringPropsAllowed = true;
                  this.updateReflowedDimensions();
                  this.reflowTriggeringCallValidator.isReflowTriggeringPropsAllowed = false;
                }, 0)
              );
            }
            this.updatePictureInPictureToggleDisplay();
            break;
          case "fullscreenchange":
            this.onFullscreenChange();
            break;
          case "keypress":
            this.keyHandler(aEvent);
            break;
          case "dragstart":
            aEvent.preventDefault(); // prevent dragging of controls image (bug 517114)
            break;
          case "input":
            switch (aEvent.currentTarget) {
              case this.scrubber:
                this.onScrubberInput(aEvent);
                break;
              case this.volumeControl:
                this.updateVolume();
                break;
            }
            break;
          case "change":
            switch (aEvent.currentTarget) {
              case this.scrubber:
                this.onScrubberChange(aEvent);
                break;
              case this.video.textTracks:
                this.setClosedCaptionButtonState();
                break;
            }
            break;
          case "mouseup":
            // add mouseup listener additionally to handle the case that `change` event
            // isn't fired when the input value before/after dragging are the same. (bug 1328061)
            this.onScrubberChange(aEvent);
            break;
          case "addtrack":
            this.onTextTrackAdd(aEvent);
            break;
          case "removetrack":
            this.onTextTrackRemove(aEvent);
            break;
          case "media-videoCasting":
            this.updateCasting(aEvent.detail);
            break;
          case "focusin":
            // Show the controls to highlight the focused control, but only
            // under certain conditions:
            if (
              this.prefs["media.videocontrols.keyboard-tab-to-all-controls"] &&
              // The click-to-play overlay must already be hidden (we don't
              // hide controls when the overlay is visible).
              this.clickToPlay.hidden &&
              // Don't do this if the controls are static.
              this.dynamicControls &&
              // If the mouse is hovering over the control bar, the controls
              // are already showing and they shouldn't hide, so don't mess
              // with them.
              // We use "div:hover" instead of just ":hover" so this works in
              // quirks mode documents. See
              // https://quirks.spec.whatwg.org/#the-active-and-hover-quirk
              !this.controlBar.matches("div:hover")
            ) {
              this.startFadeIn(this.controlBar);
              this.window.clearTimeout(this._hideControlsTimeout);
              this._hideControlsTimeout = this.window.setTimeout(
                () => this._hideControlsFn(),
                this.HIDE_CONTROLS_TIMEOUT_MS
              );
            }
            break;
          case "mousedown":
            // We only listen for mousedown on sliders.
            // If this slider isn't focused already, mousedown will focus it.
            // We don't want that because it will then handle additional keys.
            // For example, we don't want the up/down arrow keys to seek after
            // the scrubber is clicked. To prevent that, we need to redirect
            // focus. However, dragging only works while the slider is focused,
            // so we must redirect focus after mouseup.
            if (
              this.prefs["media.videocontrols.keyboard-tab-to-all-controls"] &&
              !aEvent.currentTarget.matches(":focus")
            ) {
              aEvent.currentTarget.addEventListener(
                "mouseup",
                aEvent => {
                  if (aEvent.currentTarget.matches(":focus")) {
                    // We can't use target.blur() because that will blur the
                    // video element as well.
                    this.video.focus();
                  }
                },
                { once: true }
              );
            }
            break;
          default:
            this.log("!!! control event " + aEvent.type + " not handled!");
        }
      },

      terminate() {
        if (this.videoEvents) {
          for (let event of this.videoEvents) {
            try {
              this.video.removeEventListener(event, this, {
                capture: true,
                mozSystemGroup: true,
              });
            } catch (ex) {}
          }
        }

        try {
          for (let { el, type, capture = false } of this.controlsEvents) {
            el.removeEventListener(type, this, {
              mozSystemGroup: true,
              capture,
            });
          }
        } catch (ex) {}

        this.window.clearTimeout(this._showControlsTimeout);
        this.window.clearTimeout(this._hideControlsTimeout);
        this._cancelShowThrobberWhileResumingVideoDecoder();

        this.log("--- videocontrols terminated ---");
      },

      hasError() {
        // We either have an explicit error, or the resource selection
        // algorithm is running and we've tried to load something and failed.
        // Note: we don't consider the case where we've tried to load but
        // there's no sources to load as an error condition, as sites may
        // do this intentionally to work around requires-user-interaction to
        // play restrictions, and we don't want to display a debug message
        // if that's the case.
        return (
          this.video.error != null ||
          (this.video.networkState == this.video.NETWORK_NO_SOURCE &&
            this.hasSources())
        );
      },

      updatePictureInPictureMessage() {
        let showMessage =
          !this.hasError() &&
          VideoControlsWidget.isPictureInPictureVideo(this.video);
        this.pictureInPictureOverlay.hidden = !showMessage;
        this.isShowingPictureInPictureMessage = showMessage;
      },

      hasSources() {
        if (
          this.video.hasAttribute("src") &&
          this.video.getAttribute("src") !== ""
        ) {
          return true;
        }
        for (
          var child = this.video.firstChild;
          child !== null;
          child = child.nextElementSibling
        ) {
          if (child instanceof this.window.HTMLSourceElement) {
            return true;
          }
        }
        return false;
      },

      updateErrorText() {
        let error;
        let v = this.video;
        // It is possible to have both v.networkState == NETWORK_NO_SOURCE
        // as well as v.error being non-null. In this case, we will show
        // the v.error.code instead of the v.networkState error.
        if (v.error) {
          switch (v.error.code) {
            case v.error.MEDIA_ERR_ABORTED:
              error = "errorAborted";
              break;
            case v.error.MEDIA_ERR_NETWORK:
              error = "errorNetwork";
              break;
            case v.error.MEDIA_ERR_DECODE:
              error = "errorDecode";
              break;
            case v.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
              error =
                v.networkState == v.NETWORK_NO_SOURCE
                  ? "errorNoSource"
                  : "errorSrcNotSupported";
              break;
            default:
              error = "errorGeneric";
              break;
          }
        } else if (v.networkState == v.NETWORK_NO_SOURCE) {
          error = "errorNoSource";
        } else {
          return; // No error found.
        }

        let label = this.shadowRoot.getElementById(error);
        this.controlsSpacer.setAttribute("aria-label", label.textContent);
        this.statusOverlay.setAttribute("status", error);
      },

      formatTime(aTime) {
        aTime = Math.round(aTime / 1000);
        let hours = Math.floor(aTime / 3600);
        let mins = Math.floor((aTime % 3600) / 60);
        let secs = Math.floor(aTime % 60);
        let timeString;
        if (secs < 10) {
          secs = "0" + secs;
        }
        if (hours) {
          if (mins < 10) {
            mins = "0" + mins;
          }
          timeString = hours + ":" + mins + ":" + secs;
        } else {
          timeString = mins + ":" + secs;
        }
        return timeString;
      },

      pauseVideoDuringDragging() {
        if (
          !this.video.paused &&
          !this.isPausedByDragging &&
          this.scrubber.isDragging
        ) {
          this.isPausedByDragging = true;
          this.video.pause();
        }
      },

      onScrubberInput() {
        const duration = Math.round(this.video.duration * 1000); // in ms
        let time = this.scrubber.value;

        this.seekToPosition(time);
        this.showPosition(time, duration);
        this.updateScrubberProgress();

        this.scrubber.isDragging = true;
        this.pauseVideoDuringDragging();
      },

      onScrubberChange() {
        this.scrubber.isDragging = false;

        if (this.isPausedByDragging) {
          this.video.play();
          this.isPausedByDragging = false;
        }
      },

      updateScrubberProgress() {
        const positionPercent = (this.scrubber.value / this.scrubber.max) * 100;

        if (!isNaN(positionPercent) && positionPercent != Infinity) {
          this.progressBar.value = positionPercent;
        } else {
          this.progressBar.value = 0;
        }
      },

      seekToPosition(newPosition) {
        newPosition /= 1000; // convert from ms
        this.log("+++ seeking to " + newPosition);
        this.video.currentTime = newPosition;
      },

      setVolume(newVolume) {
        this.log("*** setting volume to " + newVolume);
        this.video.volume = newVolume;
        this.video.muted = false;
      },

      showPosition(currentTimeMs, durationMs) {
        // If the duration is unknown (because the server didn't provide
        // it, or the video is a stream), then we want to fudge the duration
        // by using the maximum playback position that's been seen.
        if (currentTimeMs > this.maxCurrentTimeSeen) {
          this.maxCurrentTimeSeen = currentTimeMs;
        }
        this.log(
          "time update @ " + currentTimeMs + "ms of " + durationMs + "ms"
        );

        let durationIsInfinite = durationMs == Infinity;
        if (isNaN(durationMs) || durationIsInfinite) {
          durationMs = this.maxCurrentTimeSeen;
        }
        this.log("durationMs is " + durationMs + "ms.\n");

        let scrubberProgress = Math.abs(
          currentTimeMs / durationMs - this.scrubber.value / this.scrubber.max
        );
        let devPxProgress =
          scrubberProgress *
          this.reflowedDimensions.scrubberWidth *
          this.window.devicePixelRatio;
        // Hack: if we haven't updated the scrubber width to be non-0, but
        // the scrubber stack is visible, assume there is progress.
        // This should be rectified by the next time we do layout (see handling
        // of resizevideocontrols events in handleEvent).
        if (
          !this.reflowedDimensions.scrubberWidth &&
          !this.scrubberStack.hidden
        ) {
          devPxProgress = 1;
        }
        // Update the scrubber only if it will move by at least 1 pixel
        // Note that this.scrubber.max can be "" if unitialized,
        // and either or both of currentTimeMs or durationMs can be 0, leading
        // to NaN or Infinity values for devPxProgress.
        if (!this.scrubber.max || isNaN(devPxProgress) || devPxProgress > 0.5) {
          this.scrubber.max = durationMs;
          this.scrubber.value = currentTimeMs;
          this.updateScrubberProgress();
        }

        // If the duration is over an hour, thumb should show h:mm:ss instead
        // of mm:ss, which makes it bigger. We set the modifier prop which
        // informs CSS custom properties used elsewhere to determine minimum
        // widths we need to show stuff.
        let modifier = durationMs >= 3600000 ? "long" : "";
        this.positionDurationBox.modifier = this.durationSpan.modifier =
          modifier;

        // Update the text-based labels:
        let position = this.formatTime(currentTimeMs);
        let duration = durationIsInfinite ? "" : this.formatTime(durationMs);
        if (
          this.positionString != position ||
          this.durationString != duration
        ) {
          // Only update the DOM if there is a visible change.
          this._updatePositionLabels(position, duration);
        }
      },

      _updatePositionLabels(position, duration) {
        this.positionString = position;
        this.durationString = duration;

        this.l10n.setAttributes(
          this.positionDurationBox,
          "videocontrols-position-and-duration-labels",
          { position, duration }
        );
        this.l10n.setAttributes(
          this.scrubber,
          "videocontrols-scrubber-position-and-duration",
          { position, duration }
        );
      },

      showBuffered() {
        function bsearch(haystack, needle, cmp) {
          var length = haystack.length;
          var low = 0;
          var high = length;
          while (low < high) {
            var probe = low + ((high - low) >> 1);
            var r = cmp(haystack, probe, needle);
            if (r == 0) {
              return probe;
            } else if (r > 0) {
              low = probe + 1;
            } else {
              high = probe;
            }
          }
          return -1;
        }

        function bufferedCompare(buffered, i, time) {
          if (time > buffered.end(i)) {
            return 1;
          } else if (time >= buffered.start(i)) {
            return 0;
          }
          return -1;
        }

        var duration = Math.round(this.video.duration * 1000);
        if (isNaN(duration) || duration == Infinity) {
          duration = this.maxCurrentTimeSeen;
        }

        // Find the range that the current play position is in and use that
        // range for bufferBar.  At some point we may support multiple ranges
        // displayed in the bar.
        var currentTime = this.video.currentTime;
        var buffered = this.video.buffered;
        var index = bsearch(buffered, currentTime, bufferedCompare);
        var endTime = 0;
        if (index >= 0) {
          endTime = Math.round(buffered.end(index) * 1000);
        }
        if (this.duration == duration && this.buffered == endTime) {
          // Avoid modifying the DOM if there is no update to show.
          return;
        }

        this.bufferBar.max = this.duration = duration;
        this.bufferBar.value = this.buffered = endTime;
        // Progress bars are automatically reported by screen readers even when
        // they aren't focused, which intrudes on the audio being played.
        // Ideally, we'd just change the a11y role of bufferBar, but there's
        // no role which will let us just expose text via an ARIA attribute.
        // Therefore, we hide bufferBar for a11y and expose the info as
        // off-screen text.
        this.bufferA11yVal.textContent =
          (this.bufferBar.position * 100).toFixed() + "%";
      },

      _controlsHiddenByTimeout: false,
      _showControlsTimeout: 0,
      SHOW_CONTROLS_TIMEOUT_MS: 500,
      _showControlsFn() {
        if (this.video.matches("video:hover")) {
          this.startFadeIn(this.controlBar, false);
          this._showControlsTimeout = 0;
          this._controlsHiddenByTimeout = false;
        }
      },

      _hideControlsTimeout: 0,
      _hideControlsFn() {
        if (!this.scrubber.isDragging) {
          this.startFade(this.controlBar, false);
          this._hideControlsTimeout = 0;
          this._controlsHiddenByTimeout = true;
        }
      },
      HIDE_CONTROLS_TIMEOUT_MS: 2000,

      // By "Video" we actually mean the video controls container,
      // because we don't want to consider the padding of <video> added
      // by the web content.
      isMouseOverVideo(event) {
        // XXX: this triggers reflow too, but the layout should only be dirty
        // if the web content touches it while the mouse is moving.
        let el = this.shadowRoot.elementFromPoint(event.clientX, event.clientY);

        // As long as this is not null, the cursor is over something within our
        // Shadow DOM.
        return !!el;
      },

      isMouseOverControlBar(event) {
        // XXX: this triggers reflow too, but the layout should only be dirty
        // if the web content touches it while the mouse is moving.
        let el = this.shadowRoot.elementFromPoint(event.clientX, event.clientY);
        while (el && el !== this.shadowRoot) {
          if (el == this.controlBar) {
            return true;
          }
          el = el.parentNode;
        }
        return false;
      },

      onMouseMove(event) {
        // If the controls are static, don't change anything.
        if (!this.dynamicControls) {
          return;
        }

        this.window.clearTimeout(this._hideControlsTimeout);

        // Suppress fading out the controls until the video has rendered
        // its first frame. But since autoplay videos start off with no
        // controls, let them fade-out so the controls don't get stuck on.
        if (!this.firstFrameShown && !this.video.autoplay) {
          return;
        }

        if (this._controlsHiddenByTimeout) {
          this._showControlsTimeout = this.window.setTimeout(
            () => this._showControlsFn(),
            this.SHOW_CONTROLS_TIMEOUT_MS
          );
        } else {
          this.startFade(this.controlBar, true);
        }

        // Hide the controls if the mouse cursor is left on top of the video
        // but above the control bar and if the click-to-play overlay is hidden.
        if (
          (this._controlsHiddenByTimeout ||
            !this.isMouseOverControlBar(event)) &&
          this.clickToPlay.hidden
        ) {
          this._hideControlsTimeout = this.window.setTimeout(
            () => this._hideControlsFn(),
            this.HIDE_CONTROLS_TIMEOUT_MS
          );
        }
      },

      onMouseInOut(event) {
        // If the controls are static, don't change anything.
        if (!this.dynamicControls) {
          return;
        }

        this.window.clearTimeout(this._hideControlsTimeout);

        let isMouseOverVideo = this.isMouseOverVideo(event);

        // Suppress fading out the controls until the video has rendered
        // its first frame. But since autoplay videos start off with no
        // controls, let them fade-out so the controls don't get stuck on.
        if (
          !this.firstFrameShown &&
          !isMouseOverVideo &&
          !this.video.autoplay
        ) {
          return;
        }

        if (!isMouseOverVideo && !this.isMouseOverControlBar(event)) {
          this.adjustControlSize();

          // Keep the controls visible if the click-to-play is visible.
          if (!this.clickToPlay.hidden) {
            return;
          }

          this.startFadeOut(this.controlBar, false);
          this.hideClosedCaptionMenu();
          this.window.clearTimeout(this._showControlsTimeout);
          this._controlsHiddenByTimeout = false;
        }
      },

      startFadeIn(element, immediate) {
        this.startFade(element, true, immediate);
      },

      startFadeOut(element, immediate) {
        this.startFade(element, false, immediate);
      },

      animationMap: new WeakMap(),

      animationProps: {
        clickToPlay: {
          keyframes: [
            { transform: "scale(3)", opacity: 0 },
            { transform: "scale(1)", opacity: 0.55 },
          ],
          options: {
            easing: "ease",
            duration: 400,
            // The fill mode here and below is a workaround to avoid flicker
            // due to bug 1495350.
            fill: "both",
          },
        },
        controlBar: {
          keyframes: [{ opacity: 0 }, { opacity: 1 }],
          options: {
            easing: "ease",
            duration: 200,
            fill: "both",
          },
        },
        statusOverlay: {
          keyframes: [
            { opacity: 0 },
            { opacity: 0, offset: 0.72 }, // ~750ms into animation
            { opacity: 1 },
          ],
          options: {
            duration: 1050,
            fill: "both",
          },
        },
      },

      startFade(element, fadeIn, immediate = false) {
        let animationProp = this.animationProps[element.id];
        if (!animationProp) {
          throw new Error(
            "Element " +
              element.id +
              " has no transition. Toggle the hidden property directly."
          );
        }

        let animation = this.animationMap.get(element);
        if (!animation) {
          animation = new this.window.Animation(
            new this.window.KeyframeEffect(
              element,
              animationProp.keyframes,
              animationProp.options
            )
          );

          this.animationMap.set(element, animation);
        }

        if (fadeIn) {
          if (element == this.controlBar) {
            this.controlsSpacer.removeAttribute("hideCursor");
            // Ensure the Full Screen button is in the tab order.
            this.fullscreenButton.removeAttribute("tabindex");
          }

          // hidden state should be controlled by adjustControlSize
          if (element.isAdjustableControl && element.hiddenByAdjustment) {
            return;
          }

          // No need to fade in again if the hidden property returns false
          // (not hidden and not fading out.)
          if (!element.hidden) {
            return;
          }

          // Unhide
          element.hidden = false;
        } else {
          if (element == this.controlBar) {
            if (!this.hasError() && this.isVideoInFullScreen) {
              this.controlsSpacer.setAttribute("hideCursor", true);
            }
            if (
              !this.prefs["media.videocontrols.keyboard-tab-to-all-controls"]
            ) {
              // The Full Screen button is currently the only tabbable button
              // when the controls are shown. Remove it from the tab order when
              // visually hidden to prevent visual confusion.
              this.fullscreenButton.setAttribute("tabindex", "-1");
            }
          }

          // No need to fade out if the hidden property returns true
          // (hidden or is fading out)
          if (element.hidden) {
            return;
          }
        }

        element.classList.toggle("fadeout", !fadeIn);
        element.classList.toggle("fadein", fadeIn);
        let finishedPromise;
        if (!immediate) {
          // At this point, if there is a pending animation, we just stop it to avoid it happening.
          // If there is a running animation, we reverse it, to have it rewind to the beginning.
          // If there is an idle/finished animation, we schedule a new one that reverses the finished one.
          if (animation.pending) {
            // Animation is running but pending.
            // Just cancel the pending animation to stop its effect.
            animation.cancel();
            finishedPromise = Promise.resolve();
          } else {
            switch (animation.playState) {
              case "idle":
              case "finished":
                // There is no animation currently playing.
                // Schedule a new animation with the desired playback direction.
                animation.playbackRate = fadeIn ? 1 : -1;
                animation.play();
                break;
              case "running":
                // Allow the animation to play from its current position in
                // reverse to finish.
                animation.reverse();
                break;
              case "pause":
                throw new Error("Animation should never reach pause state.");
              default:
                throw new Error(
                  "Unknown Animation playState: " + animation.playState
                );
            }
            finishedPromise = animation.finished;
          }
        } else {
          // immediate
          animation.cancel();
          finishedPromise = Promise.resolve();
        }
        finishedPromise.then(
          animation => {
            if (element == this.controlBar) {
              this.onControlBarAnimationFinished();
            }
            element.classList.remove(fadeIn ? "fadein" : "fadeout");
            if (!fadeIn) {
              element.hidden = true;
            }
            if (animation) {
              // Explicitly clear the animation effect so that filling animations
              // stop overwriting stylesheet styles. Remove when bug 1495350 is
              // fixed and animations are no longer filling animations.
              // This also stops them from accumulating (See bug 1253476).
              animation.cancel();
            }
          },
          () => {
            /* Do nothing on rejection */
          }
        );
      },

      _triggeredByControls: false,

      startPlay() {
        this._triggeredByControls = true;
        this.hideClickToPlay();
        this.video.play();
      },

      togglePause() {
        if (this.video.paused || this.video.ended) {
          this.startPlay();
        } else {
          this.video.pause();
        }

        // We'll handle style changes in the event listener for
        // the "play" and "pause" events, same as if content
        // script was controlling video playback.
      },

      get isVideoWithoutAudioTrack() {
        return (
          this.video.readyState >= this.video.HAVE_METADATA &&
          !this.isAudioOnly &&
          !this.video.mozHasAudio
        );
      },

      toggleMute() {
        if (this.isVideoWithoutAudioTrack) {
          return;
        }
        this.video.muted = !this.isEffectivelyMuted;
        if (this.video.volume === 0) {
          this.video.volume = 0.5;
        }

        // We'll handle style changes in the event listener for
        // the "volumechange" event, same as if content script was
        // controlling volume.
      },

      get isVideoInFullScreen() {
        return this.video.isSameNode(
          this.video.getRootNode().fullscreenElement
        );
      },

      toggleFullscreen() {
        // audio tags cannot toggle fullscreen
        if (!this.isAudioTag) {
          this.isVideoInFullScreen
            ? this.document.exitFullscreen()
            : this.video.requestFullscreen();
        }
      },

      setFullscreenButtonState() {
        if (this.isAudioOnly || !this.document.fullscreenEnabled) {
          this.controlBar.setAttribute("fullscreen-unavailable", true);
          this.adjustControlSize();
          return;
        }
        this.controlBar.removeAttribute("fullscreen-unavailable");
        this.adjustControlSize();

        var id = this.isVideoInFullScreen
          ? "videocontrols-exitfullscreen-button"
          : "videocontrols-enterfullscreen-button";
        this.l10n.setAttributes(this.fullscreenButton, id);

        if (this.isVideoInFullScreen) {
          this.fullscreenButton.setAttribute("fullscreened", "true");
        } else {
          this.fullscreenButton.removeAttribute("fullscreened");
        }
      },

      onFullscreenChange() {
        if (this.document.fullscreenElement) {
          this.videocontrols.setAttribute("inDOMFullscreen", true);
        } else {
          this.videocontrols.removeAttribute("inDOMFullscreen");
        }

        if (this.isVideoInFullScreen) {
          this.startFadeOut(this.controlBar, true);
        }

        this.setFullscreenButtonState();
      },

      clickToPlayClickHandler(e) {
        if (e.button != 0) {
          return;
        }
        if (this.hasError() && !this.suppressError) {
          // Errors that can be dismissed should be placed here as we discover them.
          if (this.video.error.code != this.video.error.MEDIA_ERR_ABORTED) {
            return;
          }
          this.startFadeOut(this.statusOverlay, true);
          this.suppressError = true;
          return;
        }
        if (e.defaultPrevented) {
          return;
        }
        if (this.playButton.hasAttribute("paused")) {
          this.startPlay();
        } else {
          this.video.pause();
        }
      },
      hideClickToPlay() {
        let videoHeight = this.reflowedDimensions.videoHeight;
        let videoWidth = this.reflowedDimensions.videoWidth;

        // The play button will animate to 3x its size. This
        // shows the animation unless the video is too small
        // to show 2/3 of the animation.
        let animationScale = 2;
        let animationMinSize = this.clickToPlay.minWidth * animationScale;

        let immediate =
          animationMinSize > videoWidth ||
          animationMinSize > videoHeight - this.controlBarMinHeight;
        this.startFadeOut(this.clickToPlay, immediate);
      },

      setPlayButtonState(aPaused) {
        if (aPaused) {
          this.playButton.setAttribute("paused", "true");
        } else {
          this.playButton.removeAttribute("paused");
        }

        var id = aPaused
          ? "videocontrols-play-button"
          : "videocontrols-pause-button";
        this.l10n.setAttributes(this.playButton, id);
        this.l10n.setAttributes(this.clickToPlay, id);
      },

      get isEffectivelyMuted() {
        return this.video.muted || !this.video.volume;
      },

      updateMuteButtonState() {
        var muted = this.isEffectivelyMuted;
        this.muteButton.toggleAttribute("muted", muted);

        var id = muted
          ? "videocontrols-unmute-button"
          : "videocontrols-mute-button";
        this.l10n.setAttributes(this.muteButton, id);
      },

      keyboardVolumeDecrease() {
        const oldval = this.video.volume;
        this.video.volume = oldval < 0.1 ? 0 : oldval - 0.1;
        this.video.muted = false;
      },

      keyboardVolumeIncrease() {
        const oldval = this.video.volume;
        this.video.volume = oldval > 0.9 ? 1 : oldval + 0.1;
        this.video.muted = false;
      },

      keyboardSeekBack(tenPercent) {
        const oldval = this.video.currentTime;
        let newval;
        if (tenPercent) {
          newval =
            oldval -
            (this.video.duration || this.maxCurrentTimeSeen / 1000) / 10;
        } else {
          newval = oldval - VideoControlsWidget.SEEK_TIME_SECS;
        }
        this.video.currentTime = Math.max(0, newval);
      },

      keyboardSeekForward(tenPercent) {
        const oldval = this.video.currentTime;
        const maxtime = this.video.duration || this.maxCurrentTimeSeen / 1000;
        let newval;
        if (tenPercent) {
          newval = oldval + maxtime / 10;
        } else {
          newval = oldval + VideoControlsWidget.SEEK_TIME_SECS;
        }
        this.video.currentTime = Math.min(newval, maxtime);
      },

      keyHandler(event) {
        // Ignore keys when content might be providing its own.
        if (!this.video.hasAttribute("controls")) {
          return;
        }

        let keystroke = "";
        if (event.altKey) {
          keystroke += "alt-";
        }
        if (event.shiftKey) {
          keystroke += "shift-";
        }
        if (this.window.navigator.platform.startsWith("Mac")) {
          if (event.metaKey) {
            keystroke += "accel-";
          }
          if (event.ctrlKey) {
            keystroke += "control-";
          }
        } else {
          if (event.metaKey) {
            keystroke += "meta-";
          }
          if (event.ctrlKey) {
            keystroke += "accel-";
          }
        }
        if (event.key == " ") {
          keystroke += "Space";
        } else {
          keystroke += event.key;
        }

        this.log("Got keystroke: " + keystroke);

        // If unmodified cursor keys are pressed when a slider is focused, we
        // should act on that slider. For example, if we're focused on the
        // volume slider, rightArrow should increase the volume, not seek.
        // Normally, we'd just pass the keys through to the slider in this case.
        // However, the native adjustment is too small, so we override it.
        try {
          const target = event.originalTarget;
          const allTabbable =
            this.prefs["media.videocontrols.keyboard-tab-to-all-controls"];
          switch (keystroke) {
            case "Space" /* Play */:
              if (target.localName === "button" && !target.disabled) {
                break;
              }
              this.togglePause();
              break;
            case "ArrowDown" /* Volume decrease */:
              if (allTabbable && target == this.scrubber) {
                this.keyboardSeekBack(/* tenPercent */ false);
              } else if (target.classList.contains("textTrackItem")) {
                target.nextSibling?.focus();
              } else {
                this.keyboardVolumeDecrease();
              }
              break;
            case "ArrowUp" /* Volume increase */:
              if (allTabbable && target == this.scrubber) {
                this.keyboardSeekForward(/* tenPercent */ false);
              } else if (target.classList.contains("textTrackItem")) {
                target.previousSibling?.focus();
              } else {
                this.keyboardVolumeIncrease();
              }
              break;
            case "accel-ArrowDown" /* Mute */:
              this.video.muted = true;
              break;
            case "accel-ArrowUp" /* Unmute */:
              this.video.muted = false;
              break;
            case "ArrowLeft" /* Seek back 5 seconds */:
              if (allTabbable && target == this.volumeControl) {
                this.keyboardVolumeDecrease();
              } else {
                this.keyboardSeekBack(/* tenPercent */ false);
              }
              break;
            case "accel-ArrowLeft" /* Seek back 10% */:
              this.keyboardSeekBack(/* tenPercent */ true);
              break;
            case "ArrowRight" /* Seek forward 5 seconds */:
              if (allTabbable && target == this.volumeControl) {
                this.keyboardVolumeIncrease();
              } else {
                this.keyboardSeekForward(/* tenPercent */ false);
              }
              break;
            case "accel-ArrowRight" /* Seek forward 10% */:
              this.keyboardSeekForward(/* tenPercent */ true);
              break;
            case "Home" /* Seek to beginning */:
              this.video.currentTime = 0;
              break;
            case "End" /* Seek to end */:
              if (this.video.currentTime != this.video.duration) {
                this.video.currentTime =
                  this.video.duration || this.maxCurrentTimeSeen / 1000;
              }
              break;
            case "Escape" /* Escape */:
              if (
                target.classList.contains("textTrackItem") &&
                !this.textTrackListContainer.hidden
              ) {
                this.toggleClosedCaption();
                this.closedCaptionButton.focus();
              }
              break;
            default:
              return;
          }
        } catch (e) {
          /* ignore any exception from setting .currentTime */
        }

        event.preventDefault(); // Prevent page scrolling
      },

      checkTextTrackSupport(textTrack) {
        return textTrack.kind == "subtitles" || textTrack.kind == "captions";
      },

      get isCastingAvailable() {
        return !this.isAudioOnly && this.video.mozAllowCasting;
      },

      get isClosedCaptionAvailable() {
        // There is no rendering area, no need to show the caption.
        if (this.isAudioOnly) {
          return false;
        }
        return this.overlayableTextTracks.length;
      },

      get overlayableTextTracks() {
        return Array.prototype.filter.call(
          this.video.textTracks,
          this.checkTextTrackSupport
        );
      },

      get currentTextTrackIndex() {
        const showingTT = this.overlayableTextTracks.find(
          tt => tt.mode == "showing"
        );

        // fallback to off button if there's no showing track.
        return showingTT ? showingTT.index : 0;
      },

      get isCastingOn() {
        return this.isCastingAvailable && this.video.mozIsCasting;
      },

      setCastingButtonState() {
        this.castingButton.toggleAttribute("enabled", this.isCastingOn);
        this.adjustControlSize();
      },

      updateCasting(eventDetail) {
        let castingData = JSON.parse(eventDetail);
        if ("allow" in castingData) {
          this.video.mozAllowCasting = !!castingData.allow;
        }

        if ("active" in castingData) {
          this.video.mozIsCasting = !!castingData.active;
        }
        this.setCastingButtonState();
      },

      get isClosedCaptionOn() {
        for (let tt of this.overlayableTextTracks) {
          if (tt.mode === "showing") {
            return true;
          }
        }

        return false;
      },

      setClosedCaptionButtonState() {
        this.closedCaptionButton.toggleAttribute(
          "enabled",
          this.isClosedCaptionOn
        );
        let ttItems = this.textTrackList.childNodes;

        for (let tti of ttItems) {
          const idx = +tti.getAttribute("index");
          tti.setAttribute("aria-checked", idx == this.currentTextTrackIndex);
        }

        this.adjustControlSize();
      },

      addNewTextTrack(tt) {
        if (!this.checkTextTrackSupport(tt)) {
          return;
        }

        if (tt.index && tt.index < this.textTracksCount) {
          // Don't create items for initialized tracks. However, we
          // still need to care about mode since TextTrackManager would
          // turn on the first available track automatically.
          if (tt.mode === "showing") {
            this.changeTextTrack(tt.index);
          }
          return;
        }

        tt.index = this.textTracksCount++;

        const ttBtn = this.shadowRoot.createElementAndAppendChildAt(
          this.textTrackList,
          "button"
        );
        ttBtn.textContent = tt.label || "";

        ttBtn.classList.add("textTrackItem");
        ttBtn.setAttribute("index", tt.index);
        ttBtn.setAttribute("role", "menuitemradio");

        if (tt.mode === "showing" && tt.index) {
          this.changeTextTrack(tt.index);
        }
      },

      changeTextTrack(index) {
        for (let tt of this.overlayableTextTracks) {
          if (tt.index === index) {
            tt.mode = "showing";
          } else {
            tt.mode = "disabled";
          }
        }

        if (!this.textTrackListContainer.hidden) {
          this.toggleClosedCaption();
        }
      },

      onControlBarAnimationFinished() {
        this.hideClosedCaptionMenu();
        this.video.dispatchEvent(
          new this.window.CustomEvent("controlbarchange")
        );
        this.adjustControlSize();
      },

      toggleCasting() {
        this.videocontrols.dispatchEvent(
          new this.window.CustomEvent("VideoBindingCast")
        );
      },

      hideClosedCaptionMenu() {
        this.textTrackListContainer.hidden = true;
        this.closedCaptionButton.setAttribute("aria-expanded", "false");
        this.updatePictureInPictureToggleDisplay();
      },

      showClosedCaptionMenu() {
        this.textTrackListContainer.hidden = false;
        this.closedCaptionButton.setAttribute("aria-expanded", "true");
        this.updatePictureInPictureToggleDisplay();
      },

      toggleClosedCaption() {
        if (this.textTrackListContainer.hidden) {
          this.showClosedCaptionMenu();
          if (this.prefs["media.videocontrols.keyboard-tab-to-all-controls"]) {
            // If we're about to hide the controls after focus, prevent that, as
            // that will dismiss the CC menu before the user can use it.
            this.textTrackList.firstChild.focus();
            this.window.clearTimeout(this._hideControlsTimeout);
            this._hideControlsTimeout = 0;
          }
        } else {
          this.hideClosedCaptionMenu();
          // If the CC menu was shown via the keyboard, we may have prevented
          // the controls from hiding. We can now hide them.
          if (
            this.prefs["media.videocontrols.keyboard-tab-to-all-controls"] &&
            !this.controlBar.hidden &&
            // The click-to-play overlay must already be hidden (we don't
            // hide controls when the overlay is visible).
            this.clickToPlay.hidden &&
            // Don't do this if the controls are static.
            this.dynamicControls &&
            // If the mouse is hovering over the control bar, the controls
            // shouldn't hide.
            // We use "div:hover" instead of just ":hover" so this works in
            // quirks mode documents. See
            // https://quirks.spec.whatwg.org/#the-active-and-hover-quirk
            !this.controlBar.matches("div:hover")
          ) {
            this.window.clearTimeout(this._hideControlsTimeout);
            this._hideControlsTimeout = this.window.setTimeout(
              () => this._hideControlsFn(),
              this.HIDE_CONTROLS_TIMEOUT_MS
            );
          }
        }
      },

      onTextTrackAdd(trackEvent) {
        this.addNewTextTrack(trackEvent.track);
        this.setClosedCaptionButtonState();
      },

      onTextTrackRemove(trackEvent) {
        const toRemoveIndex = trackEvent.track.index;
        const ttItems = this.textTrackList.childNodes;

        if (!ttItems) {
          return;
        }

        for (let tti of ttItems) {
          const idx = +tti.getAttribute("index");

          if (idx === toRemoveIndex) {
            tti.remove();
            this.textTracksCount--;
          }

          this.video.dispatchEvent(
            new this.window.CustomEvent("texttrackchange")
          );
        }

        this.setClosedCaptionButtonState();
      },

      initTextTracks() {
        // add 'off' button anyway as new text track might be
        // dynamically added after initialization.
        const offLabel = this.textTrackList.getAttribute("offlabel");
        this.addNewTextTrack({
          label: offLabel,
          kind: "subtitles",
        });

        for (let tt of this.overlayableTextTracks) {
          this.addNewTextTrack(tt);
        }

        this.setClosedCaptionButtonState();
        // Hide the Closed Caption menu when the user moves focus
        this.hideClosedCaptionMenu = this.hideClosedCaptionMenu.bind(this);
        this.closedCaptionButton.addEventListener(
          "focus",
          this.hideClosedCaptionMenu
        );
        this.fullscreenButton.addEventListener(
          "focus",
          this.hideClosedCaptionMenu
        );
      },

      log(msg) {
        if (this.debug) {
          this.window.console.log("videoctl: " + msg + "\n");
        }
      },

      get isTopLevelSyntheticDocument() {
        return (
          this.document.mozSyntheticDocument && this.window === this.window.top
        );
      },

      controlBarMinHeight: 40,
      controlBarMinVisibleHeight: 28,

      reflowTriggeringCallValidator: {
        isReflowTriggeringPropsAllowed: false,
        reflowTriggeringProps: Object.freeze([
          "offsetLeft",
          "offsetTop",
          "offsetWidth",
          "offsetHeight",
          "offsetParent",
          "clientLeft",
          "clientTop",
          "clientWidth",
          "clientHeight",
          "getClientRects",
          "getBoundingClientRect",
        ]),
        get(obj, prop) {
          if (
            !this.isReflowTriggeringPropsAllowed &&
            this.reflowTriggeringProps.includes(prop)
          ) {
            throw new Error("Please don't trigger reflow. See bug 1493525.");
          }
          let val = obj[prop];
          if (typeof val == "function") {
            return function () {
              return val.apply(obj, arguments);
            };
          }
          return val;
        },

        set(obj, prop, value) {
          return Reflect.set(obj, prop, value);
        },
      },

      installReflowCallValidator(element) {
        return new Proxy(element, this.reflowTriggeringCallValidator);
      },

      reflowedDimensions: {
        // Set the dimensions to intrinsic <video> dimensions before the first
        // update.
        // These values are not picked up by <audio> in adjustControlSize()
        // (except for the fact that they are non-zero),
        // it takes controlBarMinHeight and the value below instead.
        videoHeight: 150,
        videoWidth: 300,

        // <audio> takes this width to grow/shrink controls.
        // The initial value has to be smaller than the calculated minRequiredWidth
        // so that we don't run into bug 1495821 (see comment on adjustControlSize()
        // below)
        videocontrolsWidth: 0,

        // Used to decide if updating the scrubber progress will make a visible
        // change (ie. make it move by at least one pixel).
        // The default value is set to Infinity so that any small change is
        // assumed to cause a visible change until updateReflowedDimensions
        // has been called. (See bug 1817604)
        scrubberWidth: Infinity,
      },

      updateReflowedDimensions() {
        this.reflowedDimensions.videoHeight = this.video.clientHeight;
        this.reflowedDimensions.videoWidth = this.video.clientWidth;
        this.reflowedDimensions.videocontrolsWidth =
          this.videocontrols.clientWidth;
        this.reflowedDimensions.scrubberWidth = this.scrubber.clientWidth;
      },

      /**
       * adjustControlSize() considers outer dimensions of the <video>/<audio> element
       * from layout, and accordingly, sets/hides the controls, and adjusts
       * the width/height of the control bar.
       *
       * It's important to remember that for <audio>, layout (specifically,
       * nsVideoFrame) rely on us to expose the intrinsic dimensions of the
       * control bar to properly size the <audio> element. We interact with layout
       * by:
       *
       * 1) When the element has a non-zero height, explicitly set the height
       *    of the control bar to a size between controlBarMinHeight and
       *    controlBarMinVisibleHeight in response.
       *    Note: the logic here is flawed and had caused the end height to be
       *    depend on its previous state, see bug 1495817.
       * 2) When the element has a outer width smaller or equal to minControlBarPaddingWidth,
       *    explicitly set the control bar to minRequiredWidth, so that when the
       *    outer width is unset, the audio element could go back to minRequiredWidth.
       *    Otherwise, set the width of the control bar to be the current outer width.
       *    Note: the logic here is also flawed; when the control bar is set to
       *    the current outer width, it never go back when the width is unset,
       *    see bug 1495821.
       */
      adjustControlSize() {
        const minControlBarPaddingWidth = 18;

        this.fullscreenButton.isWanted = !this.controlBar.hasAttribute(
          "fullscreen-unavailable"
        );
        this.castingButton.isWanted = this.isCastingAvailable;
        this.closedCaptionButton.isWanted = this.isClosedCaptionAvailable;
        this.volumeStack.isWanted = !this.muteButton.hasAttribute("noAudio");

        let minRequiredWidth = this.prioritizedControls
          .filter(control => control && control.isWanted)
          .reduce(
            (accWidth, cc) => accWidth + cc.minWidth,
            minControlBarPaddingWidth
          );
        // Skip the adjustment in case the stylesheets haven't been loaded yet.
        if (!minRequiredWidth) {
          return;
        }

        let givenHeight = this.reflowedDimensions.videoHeight;
        let videoWidth =
          (this.isAudioOnly
            ? this.reflowedDimensions.videocontrolsWidth
            : this.reflowedDimensions.videoWidth) || minRequiredWidth;
        let videoHeight = this.isAudioOnly
          ? this.controlBarMinHeight
          : givenHeight;
        let videocontrolsWidth = this.reflowedDimensions.videocontrolsWidth;

        let widthUsed = minControlBarPaddingWidth;
        let preventAppendControl = false;

        for (let [index, control] of this.prioritizedControls.entries()) {
          // The "durationSpan" element is disconnected from the document during l10n so
          // we check if our reference to "durationSpan" is the connected one and if not we
          // replace it with the correct one
          if (control.id === "durationSpan" && !control.isConnected) {
            const durationSpan = this.durationSpan;
            if (durationSpan) {
              this.defineControlProperties(durationSpan);
              this.prioritizedControls[index] = durationSpan;
              control = durationSpan;
            }
          }
          if (!control.isWanted) {
            control.hiddenByAdjustment = true;
            continue;
          }

          control.hiddenByAdjustment =
            preventAppendControl || widthUsed + control.minWidth > videoWidth;

          if (control.hiddenByAdjustment) {
            preventAppendControl = true;
          } else {
            widthUsed += control.minWidth;
          }
        }

        // Use flexible spacer to separate controls when scrubber is hidden.
        // As long as muteButton hidden, which means only play button presents,
        // hide spacer and make playButton centered.
        this.controlBarSpacer.hidden =
          !this.scrubberStack.hidden || this.muteButton.hidden;

        // Since the size of videocontrols is expanded with controlBar in <audio>, we
        // should fix the dimensions in order not to recursively trigger reflow afterwards.
        if (this.isAudioTag) {
          if (givenHeight) {
            // The height of controlBar should be capped with the bounds between controlBarMinHeight
            // and controlBarMinVisibleHeight.
            let controlBarHeight = Math.max(
              Math.min(givenHeight, this.controlBarMinHeight),
              this.controlBarMinVisibleHeight
            );
            this.controlBar.style.height = `${controlBarHeight}px`;
          }
          // Bug 1367875: Set minimum required width to controlBar if the given size is smaller than padding.
          // This can help us expand the control and restore to the default size the next time we need
          // to adjust the sizing.
          if (videocontrolsWidth <= minControlBarPaddingWidth) {
            this.controlBar.style.width = `${minRequiredWidth}px`;
          } else {
            this.controlBar.style.width = `${videoWidth}px`;
          }
          return;
        }

        if (
          videoHeight < this.controlBarMinHeight ||
          widthUsed === minControlBarPaddingWidth
        ) {
          this.controlBar.setAttribute("size", "hidden");
          this.controlBar.hiddenByAdjustment = true;
        } else {
          this.controlBar.removeAttribute("size");
          this.controlBar.hiddenByAdjustment = false;
        }

        // Adjust clickToPlayButton size.
        const minVideoSideLength = Math.min(videoWidth, videoHeight);
        const clickToPlayViewRatio = 0.15;
        const clickToPlayScaledSize = Math.max(
          this.clickToPlay.minWidth,
          minVideoSideLength * clickToPlayViewRatio
        );

        if (
          clickToPlayScaledSize >= videoWidth ||
          clickToPlayScaledSize + this.controlBarMinHeight / 2 >=
            videoHeight / 2
        ) {
          this.clickToPlay.hiddenByAdjustment = true;
        } else {
          if (
            this.clickToPlay.hidden &&
            !this.video.played.length &&
            this.video.paused
          ) {
            this.clickToPlay.hiddenByAdjustment = false;
          }
          this.clickToPlay.style.width = `${clickToPlayScaledSize}px`;
          this.clickToPlay.style.height = `${clickToPlayScaledSize}px`;
        }
      },

      get pipToggleEnabled() {
        return (
          this.prefs[
            "media.videocontrols.picture-in-picture.video-toggle.enabled"
          ] && this.prefs["media.videocontrols.picture-in-picture.enabled"]
        );
      },

      get positionDurationBox() {
        return this.shadowRoot.getElementById("positionDurationBox");
      },

      get durationSpan() {
        return this.positionDurationBox?.getElementsByTagName("span")[0];
      },

      init(shadowRoot, prefs) {
        this.shadowRoot = shadowRoot;
        this.video = this.installReflowCallValidator(shadowRoot.host);
        this.videocontrols = this.installReflowCallValidator(
          shadowRoot.firstChild
        );
        this.document = this.videocontrols.ownerDocument;
        this.window = this.document.defaultView;
        this.shadowRoot = shadowRoot;
        this.prefs = prefs;

        this.controlsContainer =
          this.shadowRoot.getElementById("controlsContainer");
        this.statusIcon = this.shadowRoot.getElementById("statusIcon");
        this.controlBar = this.shadowRoot.getElementById("controlBar");
        this.playButton = this.shadowRoot.getElementById("playButton");
        this.controlBarSpacer =
          this.shadowRoot.getElementById("controlBarSpacer");
        this.muteButton = this.shadowRoot.getElementById("muteButton");
        this.volumeStack = this.shadowRoot.getElementById("volumeStack");
        this.volumeControl = this.shadowRoot.getElementById("volumeControl");
        this.progressBar = this.shadowRoot.getElementById("progressBar");
        this.bufferBar = this.shadowRoot.getElementById("bufferBar");
        this.bufferA11yVal = this.shadowRoot.getElementById("bufferA11yVal");
        this.scrubberStack = this.shadowRoot.getElementById("scrubberStack");
        this.scrubber = this.shadowRoot.getElementById("scrubber");
        this.durationLabel = this.shadowRoot.getElementById("durationLabel");
        this.positionLabel = this.shadowRoot.getElementById("positionLabel");
        this.statusOverlay = this.shadowRoot.getElementById("statusOverlay");
        this.controlsOverlay =
          this.shadowRoot.getElementById("controlsOverlay");
        this.pictureInPictureOverlay = this.shadowRoot.getElementById(
          "pictureInPictureOverlay"
        );
        this.controlsSpacer = this.shadowRoot.getElementById("controlsSpacer");
        this.clickToPlay = this.shadowRoot.getElementById("clickToPlay");
        this.fullscreenButton =
          this.shadowRoot.getElementById("fullscreenButton");
        this.castingButton = this.shadowRoot.getElementById("castingButton");
        this.closedCaptionButton = this.shadowRoot.getElementById(
          "closedCaptionButton"
        );
        this.textTrackList = this.shadowRoot.getElementById("textTrackList");
        this.textTrackListContainer = this.shadowRoot.getElementById(
          "textTrackListContainer"
        );
        this.pictureInPictureToggle = this.shadowRoot.getElementById(
          "pictureInPictureToggle"
        );

        let isMobile = this.window.navigator.appVersion.includes("Android");
        if (isMobile) {
          this.controlsContainer.classList.add("mobile");
        }

        // TODO: Switch to touch controls on touch-based desktops (bug 1447547)
        this.isTouchControls = isMobile;
        if (this.isTouchControls) {
          this.controlsContainer.classList.add("touch");
        }

        // XXX: Calling getComputedStyle() here by itself doesn't cause any reflow,
        // but there is no guard proventing accessing any properties and methods
        // of this saved CSSStyleDeclaration instance that could trigger reflow.
        this.controlBarComputedStyles = this.window.getComputedStyle(
          this.controlBar
        );

        // Hide and show control in certain order.
        this.prioritizedControls = [
          this.playButton,
          this.muteButton,
          this.fullscreenButton,
          this.castingButton,
          this.closedCaptionButton,
          this.positionDurationBox,
          this.scrubberStack,
          this.durationSpan,
          this.volumeStack,
        ];

        this.isAudioOnly = this.isAudioTag;
        this.setupInitialState();
        this.setupNewLoadState();
        this.initTextTracks();

        // Use the handleEvent() callback for all media events.
        // Only the "error" event listener must capture, so that it can trap error
        // events from <source> children, which don't bubble. But we use capture
        // for all events in order to simplify the event listener add/remove.
        for (let event of this.videoEvents) {
          this.video.addEventListener(event, this, {
            capture: true,
            mozSystemGroup: true,
          });
        }

        this.controlsEvents = [
          { el: this.muteButton, type: "click" },
          { el: this.castingButton, type: "click" },
          { el: this.closedCaptionButton, type: "click" },
          { el: this.fullscreenButton, type: "click" },
          { el: this.playButton, type: "click" },
          { el: this.clickToPlay, type: "click" },

          // On touch videocontrols, tapping controlsSpacer should show/hide
          // the control bar, instead of playing the video or toggle fullscreen.
          { el: this.controlsSpacer, type: "click", nonTouchOnly: true },
          { el: this.controlsSpacer, type: "dblclick", nonTouchOnly: true },

          { el: this.textTrackList, type: "click" },

          { el: this.videocontrols, type: "resizevideocontrols" },

          { el: this.document, type: "fullscreenchange" },
          { el: this.video, type: "keypress", capture: true },

          // Prevent any click event within media controls from dispatching through to video.
          { el: this.videocontrols, type: "click", mozSystemGroup: false },

          // prevent dragging of controls image (bug 517114)
          { el: this.videocontrols, type: "dragstart" },

          { el: this.scrubber, type: "input" },
          { el: this.scrubber, type: "change" },
          // add mouseup listener additionally to handle the case that `change` event
          // isn't fired when the input value before/after dragging are the same. (bug 1328061)
          { el: this.scrubber, type: "mouseup" },
          { el: this.volumeControl, type: "input" },
          { el: this.video.textTracks, type: "addtrack" },
          { el: this.video.textTracks, type: "removetrack" },
          { el: this.video.textTracks, type: "change" },

          { el: this.video, type: "media-videoCasting", touchOnly: true },

          { el: this.controlBar, type: "focusin" },
          { el: this.scrubber, type: "mousedown" },
          { el: this.volumeControl, type: "mousedown" },
        ];

        for (let {
          el,
          type,
          nonTouchOnly = false,
          touchOnly = false,
          mozSystemGroup = true,
          capture = false,
        } of this.controlsEvents) {
          if (
            (this.isTouchControls && nonTouchOnly) ||
            (!this.isTouchControls && touchOnly)
          ) {
            continue;
          }
          el.addEventListener(type, this, { mozSystemGroup, capture });
        }

        this.log("--- videocontrols initialized ---");
      },
    };

    this.TouchUtils = {
      videocontrols: null,
      video: null,
      controlsTimer: null,
      controlsTimeout: 5000,

      get visible() {
        return (
          !this.Utils.controlBar.hasAttribute("fadeout") &&
          !this.Utils.controlBar.hidden
        );
      },

      firstShow: false,

      toggleControls() {
        if (!this.Utils.dynamicControls || !this.visible) {
          this.showControls();
        } else {
          this.delayHideControls(0);
        }
      },

      showControls() {
        if (this.Utils.dynamicControls) {
          this.Utils.startFadeIn(this.Utils.controlBar);
          this.delayHideControls(this.controlsTimeout);
        }
      },

      clearTimer() {
        if (this.controlsTimer) {
          this.window.clearTimeout(this.controlsTimer);
          this.controlsTimer = null;
        }
      },

      delayHideControls(aTimeout) {
        this.clearTimer();
        this.controlsTimer = this.window.setTimeout(
          () => this.hideControls(),
          aTimeout
        );
      },

      hideControls() {
        if (!this.Utils.dynamicControls) {
          return;
        }
        this.Utils.startFadeOut(this.Utils.controlBar);
      },

      handleEvent(aEvent) {
        switch (aEvent.type) {
          case "click":
            switch (aEvent.currentTarget) {
              case this.Utils.playButton:
                if (!this.video.paused) {
                  this.delayHideControls(0);
                } else {
                  this.showControls();
                }
                break;
              case this.Utils.muteButton:
                this.delayHideControls(this.controlsTimeout);
                break;
            }
            break;
          case "touchstart":
            this.clearTimer();
            break;
          case "touchend":
            this.delayHideControls(this.controlsTimeout);
            break;
          case "mouseup":
            if (aEvent.originalTarget == this.Utils.controlsSpacer) {
              if (this.firstShow) {
                this.Utils.video.play();
                this.firstShow = false;
              }
              this.toggleControls();
            }

            break;
        }
      },

      terminate() {
        try {
          for (let { el, type, mozSystemGroup = true } of this.controlsEvents) {
            el.removeEventListener(type, this, { mozSystemGroup });
          }
        } catch (ex) {}

        this.clearTimer();
      },

      init(shadowRoot, utils) {
        this.Utils = utils;
        this.videocontrols = this.Utils.videocontrols;
        this.video = this.Utils.video;
        this.document = this.videocontrols.ownerDocument;
        this.window = this.document.defaultView;
        this.shadowRoot = shadowRoot;

        this.controlsEvents = [
          { el: this.Utils.playButton, type: "click" },
          { el: this.Utils.scrubber, type: "touchstart" },
          { el: this.Utils.scrubber, type: "touchend" },
          { el: this.Utils.muteButton, type: "click" },
          { el: this.Utils.controlsSpacer, type: "mouseup" },
        ];

        for (let { el, type, mozSystemGroup = true } of this.controlsEvents) {
          el.addEventListener(type, this, { mozSystemGroup });
        }

        // The first time the controls appear we want to just display
        // a play button that does not fade away. The firstShow property
        // makes that happen. But because of bug 718107 this init() method
        // may be called again when we switch in or out of fullscreen
        // mode. So we only set firstShow if we're not autoplaying and
        // if we are at the beginning of the video and not already playing
        if (
          !this.video.autoplay &&
          this.Utils.dynamicControls &&
          this.video.paused &&
          this.video.currentTime === 0
        ) {
          this.firstShow = true;
        }

        // If the video is not at the start, then we probably just
        // transitioned into or out of fullscreen mode, and we don't want
        // the controls to remain visible. this.controlsTimeout is a full
        // 5s, which feels too long after the transition.
        if (this.video.currentTime !== 0) {
          this.delayHideControls(this.Utils.HIDE_CONTROLS_TIMEOUT_MS);
        }
      },
    };

    this.Utils.init(this.shadowRoot, this.prefs);
    if (this.Utils.isTouchControls) {
      this.TouchUtils.init(this.shadowRoot, this.Utils);
    }
    this._setupEventListeners();
  }

  generateContent() {
    const parser = new this.window.DOMParser();
    let parserDoc = parser.parseFromString(
      `<div class="videocontrols" xmlns="http://www.w3.org/1999/xhtml" role="none">
        <link rel="stylesheet" href="chrome://global/skin/media/videocontrols.css" />
        <link rel="stylesheet" href="chrome://global/skin/media/pipToggle.css" />

        <div id="controlsContainer" class="controlsContainer" role="none">
          <div id="statusOverlay" class="statusOverlay stackItem" hidden="true">
            <div id="statusIcon" class="statusIcon"></div>
            <bdi class="statusLabel" id="errorAborted" data-l10n-id="videocontrols-error-aborted"></bdi>
            <bdi class="statusLabel" id="errorNetwork" data-l10n-id="videocontrols-error-network"></bdi>
            <bdi class="statusLabel" id="errorDecode" data-l10n-id="videocontrols-error-decode"></bdi>
            <bdi class="statusLabel" id="errorSrcNotSupported" data-l10n-id="videocontrols-error-src-not-supported"></bdi>
            <bdi class="statusLabel" id="errorNoSource" data-l10n-id="videocontrols-error-no-source"></bdi>
            <bdi class="statusLabel" id="errorGeneric" data-l10n-id="videocontrols-error-generic"></bdi>
          </div>

          <div id="pictureInPictureOverlay" class="pictureInPictureOverlay stackItem" status="pictureInPicture" hidden="true">
            <div class="statusIcon" type="pictureInPicture"></div>
            <bdi class="statusLabel" id="pictureInPicture" data-l10n-id="videocontrols-status-picture-in-picture"></bdi>
          </div>

          <div id="controlsOverlay" class="controlsOverlay stackItem" role="none">
            <div class="controlsSpacerStack">
              <div id="controlsSpacer" class="controlsSpacer stackItem" role="none"></div>
              <button id="clickToPlay" class="clickToPlay" hidden="true"></button>
            </div>

            <button id="pictureInPictureToggle" class="pip-wrapper" position="left" hidden="true">
              <div class="pip-small clickable"></div>
              <div class="pip-expanded clickable">
                <span class="pip-icon-label clickable">
                  <span class="pip-icon"></span>
                  <span class="pip-label" data-l10n-id="videocontrols-picture-in-picture-toggle-label2"></span>
                </span>
                <div class="pip-explainer clickable" data-l10n-id="videocontrols-picture-in-picture-explainer3"></div>
              </div>
              <div class="pip-icon clickable"></div>
            </button>

            <div id="controlBar" class="controlBar" role="none" hidden="true">
              <button id="playButton"
                      class="button playButton"
                      tabindex="-1"/>
              <div id="scrubberStack" class="scrubberStack progressContainer" role="none">
                <div class="progressBackgroundBar stackItem" role="none">
                  <div class="progressStack" role="none">
                    <progress id="bufferBar" class="bufferBar" value="0" max="100" aria-hidden="true"></progress>
                    <span class="a11y-only" role="status" aria-live="off">
                      <span data-l10n-id="videocontrols-buffer-bar-label"></span>
                      <span id="bufferA11yVal"></span>
                    </span>
                    <progress id="progressBar" class="progressBar" value="0" max="100" aria-hidden="true"></progress>
                  </div>
                </div>
                <input type="range" id="scrubber" class="scrubber" tabindex="-1" data-l10n-attrs="aria-valuetext" value="0"/>
              </div>
              <bdi id="positionLabel" class="positionLabel" role="presentation"></bdi>
              <bdi id="durationLabel" class="durationLabel" role="presentation"></bdi>
              <bdi id="positionDurationBox" class="positionDurationBox" aria-hidden="true">
                <span id="durationSpan" class="duration" role="none"
                      data-l10n-name="position-duration-format"></span>
              </bdi>
              <div id="controlBarSpacer" class="controlBarSpacer" hidden="true" role="none"></div>
              <button id="muteButton"
                      class="button muteButton"
                      tabindex="-1"/>
              <div id="volumeStack" class="volumeStack progressContainer" role="none">
                <input type="range" id="volumeControl" class="volumeControl" min="0" max="100" step="1" tabindex="-1"
                       data-l10n-id="videocontrols-volume-control"/>
              </div>
              <button id="castingButton" class="button castingButton"
                      data-l10n-id="videocontrols-casting-button-label"/>
              <button id="closedCaptionButton" class="button closedCaptionButton" aria-controls="textTrackList"
                      aria-haspopup="menu" aria-expanded="false" data-l10n-id="videocontrols-closed-caption-button"/>
              <div id="textTrackListContainer" class="textTrackListContainer" hidden="true" role="presentation">
                <div id="textTrackList" role="menu" class="textTrackList"
                     data-l10n-id="videocontrols-closed-caption-off" data-l10n-attrs="offlabel"/>
              </div>
              <button id="fullscreenButton"
                      class="button fullscreenButton"/>
            </div>
          </div>
        </div>
      </div>`,
      "application/xml"
    );
    this.l10n = new this.window.DOMLocalization(
      ["branding/brand.ftl", "toolkit/global/videocontrols.ftl"],
      true
    );
    this.l10n.connectRoot(this.shadowRoot);
    if (this.prefs["media.videocontrols.keyboard-tab-to-all-controls"]) {
      // Make all of the individual controls tabbable.
      for (const el of parserDoc.documentElement.querySelectorAll(
        '[tabindex="-1"]'
      )) {
        el.removeAttribute("tabindex");
      }
    }
    this.shadowRoot.importNodeAndAppendChildAt(
      this.shadowRoot,
      parserDoc.documentElement,
      true
    );
    this.l10n.translateRoots();
  }

  onchange() {
    this.Utils.updatePictureInPictureMessage();
    this.shadowRoot.firstChild.removeAttribute("flipped");
  }

  teardown() {
    this.Utils.terminate();
    this.TouchUtils.terminate();
    this.l10n.disconnectRoot(this.shadowRoot);
    this.l10n = null;
  }

  onPrefChange(prefName, prefValue) {
    this.prefs[prefName] = prefValue;
    this.Utils.updatePictureInPictureToggleDisplay();
  }

  _setupEventListeners() {
    this.shadowRoot.firstChild.addEventListener("mouseover", event => {
      if (!this.Utils.isTouchControls) {
        this.Utils.onMouseInOut(event);
      }
    });

    this.shadowRoot.firstChild.addEventListener("mouseout", event => {
      if (!this.Utils.isTouchControls) {
        this.Utils.onMouseInOut(event);
      }
    });

    this.shadowRoot.firstChild.addEventListener("mousemove", event => {
      if (!this.Utils.isTouchControls) {
        this.Utils.onMouseMove(event);
      }
    });
  }
};

this.NoControlsMobileImplWidget = class {
  constructor(shadowRoot) {
    this.shadowRoot = shadowRoot;
    this.element = shadowRoot.host;
    this.document = this.element.ownerDocument;
    this.window = this.document.defaultView;
  }

  onsetup(direction) {
    this.generateContent();

    this.shadowRoot.firstElementChild.setAttribute("localedir", direction);

    this.Utils = {
      videoEvents: ["play", "playing"],
      videoControlEvents: ["MozNoControlsBlockedVideo"],
      terminate() {
        for (let event of this.videoEvents) {
          try {
            this.video.removeEventListener(event, this, {
              capture: true,
              mozSystemGroup: true,
            });
          } catch (ex) {}
        }

        for (let event of this.videoControlEvents) {
          try {
            this.videocontrols.removeEventListener(event, this);
          } catch (ex) {}
        }

        try {
          this.clickToPlay.removeEventListener("click", this, {
            mozSystemGroup: true,
          });
        } catch (ex) {}
      },

      hasError() {
        return (
          this.video.error != null ||
          this.video.networkState == this.video.NETWORK_NO_SOURCE
        );
      },

      handleEvent(aEvent) {
        switch (aEvent.type) {
          case "play":
            this.noControlsOverlay.hidden = true;
            break;
          case "playing":
            this.noControlsOverlay.hidden = true;
            break;
          case "MozNoControlsBlockedVideo":
            this.blockedVideoHandler();
            break;
          case "click":
            this.clickToPlayClickHandler(aEvent);
            break;
        }
      },

      blockedVideoHandler() {
        if (this.hasError()) {
          this.noControlsOverlay.hidden = true;
          return;
        }
        this.noControlsOverlay.hidden = false;
      },

      clickToPlayClickHandler(e) {
        if (e.button != 0) {
          return;
        }

        this.noControlsOverlay.hidden = true;
        this.video.play();
      },

      init(shadowRoot) {
        this.shadowRoot = shadowRoot;
        this.video = shadowRoot.host;
        this.videocontrols = shadowRoot.firstChild;
        this.document = this.videocontrols.ownerDocument;
        this.window = this.document.defaultView;
        this.shadowRoot = shadowRoot;

        this.controlsContainer =
          this.shadowRoot.getElementById("controlsContainer");
        this.clickToPlay = this.shadowRoot.getElementById("clickToPlay");
        this.noControlsOverlay =
          this.shadowRoot.getElementById("controlsContainer");

        let isMobile = this.window.navigator.appVersion.includes("Android");
        if (isMobile) {
          this.controlsContainer.classList.add("mobile");
        }

        // TODO: Switch to touch controls on touch-based desktops (bug 1447547)
        this.isTouchControls = isMobile;
        if (this.isTouchControls) {
          this.controlsContainer.classList.add("touch");
        }

        this.clickToPlay.addEventListener("click", this, {
          mozSystemGroup: true,
        });

        for (let event of this.videoEvents) {
          this.video.addEventListener(event, this, {
            capture: true,
            mozSystemGroup: true,
          });
        }

        for (let event of this.videoControlEvents) {
          this.videocontrols.addEventListener(event, this);
        }
      },
    };
    this.Utils.init(this.shadowRoot);
  }

  onchange() {}

  teardown() {
    this.Utils.terminate();
  }

  onPrefChange(prefName, prefValue) {
    this.prefs[prefName] = prefValue;
  }

  generateContent() {
    const parser = new this.window.DOMParser();
    let parserDoc = parser.parseFromString(
      `<div class="videocontrols" xmlns="http://www.w3.org/1999/xhtml" role="none">
        <link rel="stylesheet" href="chrome://global/skin/media/videocontrols.css" />
        <div id="controlsContainer" class="controlsContainer" role="none" hidden="true">
          <div class="controlsOverlay stackItem">
            <div class="controlsSpacerStack">
              <button id="clickToPlay" class="clickToPlay"></button>
            </div>
          </div>
        </div>
      </div>`,
      "application/xml"
    );
    this.shadowRoot.importNodeAndAppendChildAt(
      this.shadowRoot,
      parserDoc.documentElement,
      true
    );
  }
};

this.NoControlsPictureInPictureImplWidget = class {
  constructor(shadowRoot, prefs) {
    this.shadowRoot = shadowRoot;
    this.prefs = prefs;
    this.element = shadowRoot.host;
    this.document = this.element.ownerDocument;
    this.window = this.document.defaultView;
  }

  onsetup(direction) {
    this.generateContent();

    this.shadowRoot.firstElementChild.setAttribute("localedir", direction);
  }

  onchange() {}

  teardown() {}

  onPrefChange(prefName, prefValue) {
    this.prefs[prefName] = prefValue;
  }

  generateContent() {
    const parser = new this.window.DOMParser();
    let parserDoc = parser.parseFromString(
      `<div class="videocontrols" xmlns="http://www.w3.org/1999/xhtml" role="none">
        <link rel="stylesheet" href="chrome://global/skin/media/videocontrols.css" />
        <div id="controlsContainer" class="controlsContainer" role="none">
          <div class="pictureInPictureOverlay stackItem" status="pictureInPicture">
            <div id="statusIcon" class="statusIcon" type="pictureInPicture"></div>
            <bdi class="statusLabel" id="pictureInPicture" data-l10n-id="videocontrols-status-picture-in-picture"></bdi>
          </div>
          <div class="controlsOverlay stackItem"></div>
        </div>
      </div>`,
      "application/xml"
    );
    this.shadowRoot.importNodeAndAppendChildAt(
      this.shadowRoot,
      parserDoc.documentElement,
      true
    );
    this.l10n = new this.window.DOMLocalization([
      "branding/brand.ftl",
      "toolkit/global/videocontrols.ftl",
    ]);
    this.l10n.connectRoot(this.shadowRoot);
    this.l10n.translateRoots();
  }
};

this.NoControlsDesktopImplWidget = class {
  constructor(shadowRoot, prefs) {
    this.shadowRoot = shadowRoot;
    this.element = shadowRoot.host;
    this.document = this.element.ownerDocument;
    this.window = this.document.defaultView;
    this.prefs = prefs;
  }

  onsetup(direction) {
    this.generateContent();

    this.shadowRoot.firstElementChild.setAttribute("localedir", direction);

    this.Utils = {
      handleEvent(event) {
        switch (event.type) {
          case "fullscreenchange": {
            if (this.document.fullscreenElement) {
              this.videocontrols.setAttribute("inDOMFullscreen", true);
            } else {
              this.videocontrols.removeAttribute("inDOMFullscreen");
            }
            break;
          }
          case "resizevideocontrols": {
            this.updateReflowedDimensions();
            this.updatePictureInPictureToggleDisplay();
            break;
          }
          case "durationchange":
          // Intentional fall-through
          case "emptied":
          // Intentional fall-through
          case "loadedmetadata": {
            this.updatePictureInPictureToggleDisplay();
            break;
          }
        }
      },

      updatePictureInPictureToggleDisplay() {
        if (
          this.pipToggleEnabled &&
          VideoControlsWidget.shouldShowPictureInPictureToggle(
            this.prefs,
            this.video,
            this.reflowedDimensions
          )
        ) {
          this.pictureInPictureToggle.hidden = false;
          VideoControlsWidget.setupToggle(
            this.prefs,
            this.pictureInPictureToggle,
            this.reflowedDimensions
          );
        } else {
          this.pictureInPictureToggle.hidden = true;
        }
      },

      init(shadowRoot, prefs) {
        this.shadowRoot = shadowRoot;
        this.prefs = prefs;
        this.video = shadowRoot.host;
        this.videocontrols = shadowRoot.firstChild;
        this.document = this.videocontrols.ownerDocument;
        this.window = this.document.defaultView;
        this.shadowRoot = shadowRoot;

        this.pictureInPictureToggle = this.shadowRoot.getElementById(
          "pictureInPictureToggle"
        );

        if (this.document.fullscreenElement) {
          this.videocontrols.setAttribute("inDOMFullscreen", true);
        }

        // Default the Picture-in-Picture toggle button to being hidden. We might unhide it
        // later if we determine that this video is qualified to show it.
        this.pictureInPictureToggle.hidden = true;

        if (this.video.readyState >= this.video.HAVE_METADATA) {
          // According to the spec[1], at the HAVE_METADATA (or later) state, we know
          // the video duration and dimensions, which means we can calculate whether or
          // not to show the Picture-in-Picture toggle now.
          //
          // [1]: https://www.w3.org/TR/html50/embedded-content-0.html#dom-media-have_metadata
          this.updatePictureInPictureToggleDisplay();
        }

        this.document.addEventListener("fullscreenchange", this, {
          capture: true,
        });

        this.video.addEventListener("emptied", this);
        this.video.addEventListener("loadedmetadata", this);
        this.video.addEventListener("durationchange", this);
        this.videocontrols.addEventListener("resizevideocontrols", this);
      },

      terminate() {
        this.document.removeEventListener("fullscreenchange", this, {
          capture: true,
        });

        this.video.removeEventListener("emptied", this);
        this.video.removeEventListener("loadedmetadata", this);
        this.video.removeEventListener("durationchange", this);
        this.videocontrols.removeEventListener("resizevideocontrols", this);
      },

      updateReflowedDimensions() {
        this.reflowedDimensions.videoHeight = this.video.clientHeight;
        this.reflowedDimensions.videoWidth = this.video.clientWidth;
        this.reflowedDimensions.videocontrolsWidth =
          this.videocontrols.clientWidth;
      },

      reflowedDimensions: {
        // Set the dimensions to intrinsic <video> dimensions before the first
        // update.
        videoHeight: 150,
        videoWidth: 300,
        videocontrolsWidth: 0,
      },

      get pipToggleEnabled() {
        return (
          this.prefs[
            "media.videocontrols.picture-in-picture.video-toggle.enabled"
          ] && this.prefs["media.videocontrols.picture-in-picture.enabled"]
        );
      },
    };
    this.Utils.init(this.shadowRoot, this.prefs);
  }

  onchange() {}

  teardown() {
    this.Utils.terminate();
  }

  onPrefChange(prefName, prefValue) {
    this.prefs[prefName] = prefValue;
    this.Utils.updatePictureInPictureToggleDisplay();
  }

  generateContent() {
    const parser = new this.window.DOMParser();
    let parserDoc = parser.parseFromString(
      `<div class="videocontrols" xmlns="http://www.w3.org/1999/xhtml" role="none">
        <link rel="stylesheet" href="chrome://global/skin/media/videocontrols.css" />
        <link rel="stylesheet" href="chrome://global/skin/media/pipToggle.css" />

        <div id="controlsContainer" class="controlsContainer" role="none">
          <div class="controlsOverlay stackItem">
            <button id="pictureInPictureToggle" class="pip-wrapper" position="left" hidden="true">
              <div class="pip-small clickable"></div>
              <div class="pip-expanded clickable">
                <span class="pip-icon-label clickable">
                  <span class="pip-icon"></span>
                  <span class="pip-label" data-l10n-id="videocontrols-picture-in-picture-toggle-label2"></span>
                </span>
                <div class="pip-explainer clickable" data-l10n-id="videocontrols-picture-in-picture-explainer3"></div>
              </div>
              <div class="pip-icon"></div>
            </button>
          </div>
        </div>
      </div>`,
      "application/xml"
    );
    this.shadowRoot.importNodeAndAppendChildAt(
      this.shadowRoot,
      parserDoc.documentElement,
      true
    );
    this.l10n = new this.window.DOMLocalization([
      "branding/brand.ftl",
      "toolkit/global/videocontrols.ftl",
    ]);
    this.l10n.connectRoot(this.shadowRoot);
    this.l10n.translateRoots();
  }
};
PK
!<ew�ݢJ�J0chrome/toolkit/content/global/elements/wizard.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// This is loaded into chrome windows with the subscript loader. Wrap in
// a block to prevent accidentally leaking globals onto `window`.
{
  const { AppConstants } = ChromeUtils.importESModule(
    "resource://gre/modules/AppConstants.sys.mjs"
  );

  const XUL_NS =
    "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

  // Note: MozWizard currently supports adding, but not removing MozWizardPage
  //       children.
  class MozWizard extends MozXULElement {
    constructor() {
      super();

      // About this._accessMethod:
      //   There are two possible access methods: "sequential" and "random".
      //   "sequential" causes the MozWizardPage's to be displayed in the order
      //   that they are added to the DOM.
      //   The "random" method name is a bit misleading since the pages aren't
      //   displayed in a random order. Instead, each MozWizardPage must have
      //   a "next" attribute containing the id of the MozWizardPage that should
      //   be loaded next.
      this._accessMethod = null;
      this._currentPage = null;
      this._canAdvance = true;
      this._canRewind = false;
      this._hasLoaded = false;
      this._hasStarted = false; // Whether any MozWizardPage has been shown yet
      this._wizardButtonsReady = false;
      this.pageCount = 0;
      this._pageStack = [];

      this._bundle = Services.strings.createBundle(
        "chrome://global/locale/wizard.properties"
      );

      this.addEventListener(
        "keypress",
        event => {
          if (event.keyCode == KeyEvent.DOM_VK_RETURN) {
            this._hitEnter(event);
          } else if (
            event.keyCode == KeyEvent.DOM_VK_ESCAPE &&
            !event.defaultPrevented
          ) {
            this.cancel();
          }
        },
        { mozSystemGroup: true }
      );

      /*
        XXX(ntim): We import button.css here for the wizard-buttons children
        This won't be needed after bug 1624888.
      */
      this.attachShadow({ mode: "open" }).appendChild(
        MozXULElement.parseXULToFragment(`
        <html:link rel="stylesheet" href="chrome://global/skin/button.css"/>
        <html:link rel="stylesheet" href="chrome://global/skin/wizard.css"/>
        <hbox class="wizard-header"></hbox>
        <html:slot name="wizardpage" class="wizard-page-box" style="display: grid; flex: 1;"/>
        <html:slot/>
        <wizard-buttons class="wizard-buttons"></wizard-buttons>
    `)
      );
      this.initializeAttributeInheritance();

      this._wizardButtons = this.shadowRoot.querySelector(".wizard-buttons");

      this._wizardHeader = this.shadowRoot.querySelector(".wizard-header");
      this._wizardHeader.appendChild(
        MozXULElement.parseXULToFragment(
          AppConstants.platform == "macosx"
            ? `<stack class="wizard-header-stack" flex="1">
           <vbox class="wizard-header-box-1">
             <vbox class="wizard-header-box-text">
               <label class="wizard-header-label"/>
             </vbox>
           </vbox>
           <hbox class="wizard-header-box-icon">
             <spacer flex="1"/>
             <image class="wizard-header-icon"/>
           </hbox>
         </stack>`
            : `<hbox class="wizard-header-box-1" flex="1">
           <vbox class="wizard-header-box-text" flex="1">
             <label class="wizard-header-label"/>
             <label class="wizard-header-description"/>
           </vbox>
           <image class="wizard-header-icon"/>
         </hbox>`
        )
      );
    }

    static get inheritedAttributes() {
      return {
        ".wizard-buttons": "pagestep,firstpage,lastpage",
      };
    }

    connectedCallback() {
      if (document.l10n) {
        document.l10n.connectRoot(this.shadowRoot);
      }
      document.documentElement.setAttribute("role", "dialog");
      document.documentElement.classList.add("wizard-window");
      this._maybeStartWizard();

      window.addEventListener("close", event => {
        if (this.cancel()) {
          event.preventDefault();
        }
      });

      // Give focus to the first focusable element in the wizard, do it after
      // onload completes, see bug 103197.
      window.addEventListener("load", () =>
        window.setTimeout(() => {
          this._hasLoaded = true;
          if (!document.commandDispatcher.focusedElement) {
            document.commandDispatcher.advanceFocusIntoSubtree(this);
          }
          try {
            let button = this._wizardButtons.defaultButton;
            if (button) {
              window.notifyDefaultButtonLoaded(button);
            }
          } catch (e) {}
        }, 0)
      );
    }

    set title(val) {
      document.title = val;
    }

    get title() {
      return document.title;
    }

    set canAdvance(val) {
      this.getButton("next").disabled = !val;
      this._canAdvance = val;
    }

    get canAdvance() {
      return this._canAdvance;
    }

    set canRewind(val) {
      this.getButton("back").disabled = !val;
      this._canRewind = val;
    }

    get canRewind() {
      return this._canRewind;
    }

    get pageStep() {
      return this._pageStack.length;
    }

    get wizardPages() {
      return this.getElementsByTagNameNS(XUL_NS, "wizardpage");
    }

    set currentPage(val) {
      if (!val) {
        return;
      }

      this._currentPage?.classList.remove("selected");
      val.classList.add("selected");

      this._currentPage = val;

      // Setting this attribute allows wizard's clients to dynamically
      // change the styles of each page based on purpose of the page.
      this.setAttribute("currentpageid", val.pageid);

      this._initCurrentPage();

      this._advanceFocusToPage(val);

      this._fireEvent(val, "pageshow");
    }

    get currentPage() {
      return this._currentPage;
    }

    set pageIndex(val) {
      if (val < 0 || val >= this.pageCount) {
        return;
      }

      var page = this.wizardPages[val];
      this._pageStack[this._pageStack.length - 1] = page;
      this.currentPage = page;
    }

    get pageIndex() {
      return this._currentPage ? this._currentPage.pageIndex : -1;
    }

    get onFirstPage() {
      return this._pageStack.length == 1;
    }

    get onLastPage() {
      var cp = this.currentPage;
      return (
        cp &&
        ((this._accessMethod == "sequential" &&
          cp.pageIndex == this.pageCount - 1) ||
          (this._accessMethod == "random" && !cp.next))
      );
    }

    getButton(aDlgType) {
      return this._wizardButtons.getButton(aDlgType);
    }

    getPageById(aPageId) {
      var els = this.getElementsByAttribute("pageid", aPageId);
      return els.item(0);
    }

    extra1() {
      if (this.currentPage) {
        this._fireEvent(this.currentPage, "extra1");
      }
    }

    extra2() {
      if (this.currentPage) {
        this._fireEvent(this.currentPage, "extra2");
      }
    }

    rewind() {
      if (!this.canRewind) {
        return;
      }

      if (this.currentPage && !this._fireEvent(this.currentPage, "pagehide")) {
        return;
      }

      if (
        this.currentPage &&
        !this._fireEvent(this.currentPage, "pagerewound")
      ) {
        return;
      }

      if (!this._fireEvent(this, "wizardback")) {
        return;
      }

      this._pageStack.pop();
      this.currentPage = this._pageStack[this._pageStack.length - 1];
      this.setAttribute("pagestep", this._pageStack.length);
    }

    advance(aPageId) {
      if (!this.canAdvance) {
        return;
      }

      if (this.currentPage && !this._fireEvent(this.currentPage, "pagehide")) {
        return;
      }

      if (
        this.currentPage &&
        !this._fireEvent(this.currentPage, "pageadvanced")
      ) {
        return;
      }

      if (this.onLastPage && !aPageId) {
        if (this._fireEvent(this, "wizardfinish")) {
          window.setTimeout(function () {
            window.close();
          }, 1);
        }
      } else {
        if (!this._fireEvent(this, "wizardnext")) {
          return;
        }

        let page;
        if (aPageId) {
          page = this.getPageById(aPageId);
        } else if (this.currentPage) {
          if (this._accessMethod == "random") {
            page = this.getPageById(this.currentPage.next);
          } else {
            page = this.wizardPages[this.currentPage.pageIndex + 1];
          }
        } else {
          page = this.wizardPages[0];
        }

        if (page) {
          this._pageStack.push(page);
          this.setAttribute("pagestep", this._pageStack.length);

          this.currentPage = page;
        }
      }
    }

    goTo(aPageId) {
      var page = this.getPageById(aPageId);
      if (page) {
        this._pageStack[this._pageStack.length - 1] = page;
        this.currentPage = page;
      }
    }

    cancel() {
      if (!this._fireEvent(this, "wizardcancel")) {
        return true;
      }

      window.close();
      window.setTimeout(function () {
        window.close();
      }, 1);
      return false;
    }

    _initCurrentPage() {
      this.canRewind = !this.onFirstPage;
      this.setAttribute("firstpage", String(this.onFirstPage));
      if (AppConstants.platform == "linux") {
        this.getButton("back").hidden = this.onFirstPage;
      }

      if (this.onLastPage) {
        this.canAdvance = true;
        this.setAttribute("lastpage", "true");
      } else {
        this.setAttribute("lastpage", "false");
      }

      this._adjustWizardHeader();
      this._wizardButtons.onPageChange();
    }

    _advanceFocusToPage() {
      if (!this._hasLoaded) {
        return;
      }

      // XXX: it'd be correct to advance focus into the panel, however we can't do
      // it until bug 1558990 is fixed, so moving the focus into a wizard itsef
      // as a workaround - it's same behavior but less optimal.
      document.commandDispatcher.advanceFocusIntoSubtree(this);

      // if advanceFocusIntoSubtree tries to focus one of our
      // dialog buttons, then remove it and put it on the root
      var focused = document.commandDispatcher.focusedElement;
      if (focused && focused.hasAttribute("dlgtype")) {
        this.focus();
      }
    }

    _registerPage(aPage) {
      aPage.pageIndex = this.pageCount;
      this.pageCount += 1;
      if (!this._accessMethod) {
        this._accessMethod = aPage.next ? "random" : "sequential";
      }
      if (!this._maybeStartWizard() && this._hasStarted) {
        // If the wizard has already started, adding a page might require
        // updating elements to reflect that (ex: changing the Finish button to
        // the Next button).
        this._initCurrentPage();
      }
    }

    _onWizardButtonsReady() {
      this._wizardButtonsReady = true;
      this._maybeStartWizard();
    }

    _maybeStartWizard() {
      if (
        !this._hasStarted &&
        this.isConnected &&
        this._wizardButtonsReady &&
        this.pageCount > 0
      ) {
        this._hasStarted = true;
        this.advance();
        return true;
      }
      return false;
    }

    _adjustWizardHeader() {
      let labelElement = this._wizardHeader.querySelector(
        ".wizard-header-label"
      );
      // First deal with fluent. Ideally, we'd stop supporting anything else,
      // but some comm-central consumers still use DTDs. (bug 1627049).
      // Removing the DTD support is bug 1627051.
      if (this.currentPage.hasAttribute("data-header-label-id")) {
        let id = this.currentPage.getAttribute("data-header-label-id");
        document.l10n.setAttributes(labelElement, id);
      } else {
        // Otherwise, make sure we remove any fluent IDs leftover:
        if (labelElement.hasAttribute("data-l10n-id")) {
          labelElement.removeAttribute("data-l10n-id");
        }
        // And use the label attribute or the default:
        var label = this.currentPage.getAttribute("label") || "";
        if (!label && this.onFirstPage && this._bundle) {
          if (AppConstants.platform == "macosx") {
            label = this._bundle.GetStringFromName("default-first-title-mac");
          } else {
            label = this._bundle.formatStringFromName("default-first-title", [
              this.title,
            ]);
          }
        } else if (!label && this.onLastPage && this._bundle) {
          if (AppConstants.platform == "macosx") {
            label = this._bundle.GetStringFromName("default-last-title-mac");
          } else {
            label = this._bundle.formatStringFromName("default-last-title", [
              this.title,
            ]);
          }
        }
        labelElement.textContent = label;
      }
      let headerDescEl = this._wizardHeader.querySelector(
        ".wizard-header-description"
      );
      if (headerDescEl) {
        headerDescEl.textContent = this.currentPage.getAttribute("description");
      }
    }

    _hitEnter(evt) {
      if (!evt.defaultPrevented) {
        this.advance();
      }
    }

    _fireEvent(aTarget, aType) {
      var event = document.createEvent("Events");
      event.initEvent(aType, true, true);

      // handle dom event handlers
      return aTarget.dispatchEvent(event);
    }
  }

  customElements.define("wizard", MozWizard);

  class MozWizardPage extends MozXULElement {
    constructor() {
      super();
      this.pageIndex = -1;
    }
    connectedCallback() {
      this.setAttribute("slot", "wizardpage");

      let wizard = this.closest("wizard");
      if (wizard) {
        wizard._registerPage(this);
      }
    }
    get pageid() {
      return this.getAttribute("pageid");
    }
    set pageid(val) {
      this.setAttribute("pageid", val);
    }
    get next() {
      return this.getAttribute("next");
    }
    set next(val) {
      this.setAttribute("next", val);
      this.parentNode._accessMethod = "random";
    }
  }

  customElements.define("wizardpage", MozWizardPage);

  class MozWizardButtons extends MozXULElement {
    connectedCallback() {
      this._wizard = this.getRootNode().host;

      this.textContent = "";
      this.appendChild(this.constructor.fragment);

      MozXULElement.insertFTLIfNeeded("toolkit/global/wizard.ftl");

      this._wizardButtonDeck = this.querySelector(".wizard-next-deck");

      this.initializeAttributeInheritance();

      const listeners = [
        ["back", () => this._wizard.rewind()],
        ["next", () => this._wizard.advance()],
        ["finish", () => this._wizard.advance()],
        ["cancel", () => this._wizard.cancel()],
        ["extra1", () => this._wizard.extra1()],
        ["extra2", () => this._wizard.extra2()],
      ];
      for (let [name, listener] of listeners) {
        let btn = this.getButton(name);
        if (btn) {
          btn.addEventListener("command", listener);
        }
      }

      this._wizard._onWizardButtonsReady();
    }

    static get inheritedAttributes() {
      return AppConstants.platform == "macosx"
        ? {
            "[dlgtype='next']": "hidden=lastpage",
          }
        : null;
    }

    static get markup() {
      if (AppConstants.platform == "macosx") {
        return `
        <vbox flex="1">
          <hbox class="wizard-buttons-btm">
            <button class="wizard-button" dlgtype="extra1" hidden="true"/>
            <button class="wizard-button" dlgtype="extra2" hidden="true"/>
            <button data-l10n-id="wizard-macos-button-cancel"
                    class="wizard-button" dlgtype="cancel"/>
            <spacer flex="1"/>
            <button data-l10n-id="wizard-macos-button-back"
                    class="wizard-button wizard-nav-button" dlgtype="back"/>
            <button data-l10n-id="wizard-macos-button-next"
                    class="wizard-button wizard-nav-button" dlgtype="next"
                    default="true" />
            <button data-l10n-id="wizard-macos-button-finish" class="wizard-button"
                    dlgtype="finish" default="true" />
          </hbox>
        </vbox>`;
      }

      let buttons =
        AppConstants.platform == "linux"
          ? `
      <button data-l10n-id="wizard-linux-button-cancel"
              class="wizard-button"
              dlgtype="cancel"/>
      <spacer style="width: 24px;"/>
      <button data-l10n-id="wizard-linux-button-back"
              class="wizard-button" dlgtype="back"/>
      <deck class="wizard-next-deck">
        <hbox>
          <button data-l10n-id="wizard-linux-button-finish"
                  class="wizard-button"
                  dlgtype="finish" default="true" flex="1"/>
        </hbox>
        <hbox>
          <button data-l10n-id="wizard-linux-button-next"
                  class="wizard-button" dlgtype="next"
                  default="true" flex="1"/>
        </hbox>
      </deck>`
          : `
      <button data-l10n-id="wizard-win-button-back"
              class="wizard-button" dlgtype="back"/>
      <deck class="wizard-next-deck">
        <hbox>
          <button data-l10n-id="wizard-win-button-finish"
                  class="wizard-button"
                  dlgtype="finish" default="true" flex="1"/>
        </hbox>
        <hbox>
          <button data-l10n-id="wizard-win-button-next"
                  class="wizard-button" dlgtype="next"
                  default="true" flex="1"/>
        </hbox>
      </deck>
      <button data-l10n-id="wizard-win-button-cancel"
              class="wizard-button"
              dlgtype="cancel"/>`;

      return `
      <vbox class="wizard-buttons-box-1" flex="1">
        <separator class="wizard-buttons-separator groove"/>
        <hbox class="wizard-buttons-box-2">
          <button class="wizard-button" dlgtype="extra1" hidden="true"/>
          <button class="wizard-button" dlgtype="extra2" hidden="true"/>
          <spacer flex="1" anonid="spacer"/>
          ${buttons}
        </hbox>
      </vbox>`;
    }

    onPageChange() {
      if (AppConstants.platform == "macosx") {
        this.getButton("finish").hidden = !(
          this.getAttribute("lastpage") == "true"
        );
      } else if (this.getAttribute("lastpage") == "true") {
        this._wizardButtonDeck.selectedIndex = 0;
      } else {
        this._wizardButtonDeck.selectedIndex = 1;
      }
    }

    getButton(type) {
      return this.querySelector(`[dlgtype="${type}"]`);
    }

    get defaultButton() {
      let buttons = this._wizardButtonDeck.selectedPanel.getElementsByTagNameNS(
        XUL_NS,
        "button"
      );
      for (let i = 0; i < buttons.length; i++) {
        if (
          buttons[i].getAttribute("default") == "true" &&
          !buttons[i].hidden &&
          !buttons[i].disabled
        ) {
          return buttons[i];
        }
      }
      return null;
    }
  }

  customElements.define("wizard-buttons", MozWizardButtons);
}
PK
!<�#�<<3chrome/toolkit/content/global/filepicker.properties
allFilter=*
htmlFilter=*.html; *.htm; *.shtml; *.xhtml
textFilter=*.txt; *.text
imageFilter=*.jpe; *.jpg; *.jpeg; *.gif; *.png; *.bmp; *.ico; *.svg; *.svgz; *.tif; *.tiff; *.ai; *.drw; *.pct; *.psp; *.xcf; *.psd; *.raw; *.webp; *.heic
xmlFilter=*.xml
xulFilter=*.xul
audioFilter=*.aac; *.aif; *.flac; *.iff; *.m4a; *.m4b; *.mid; *.midi; *.mp3; *.mpa; *.mpc; *.oga; *.ogg; *.opus; *.ra; *.ram; *.snd; *.wav; *.wma
videoFilter=*.avi; *.divx; *.flv; *.m4v; *.mkv; *.mov; *.mp4; *.mpeg; *.mpg; *.ogm; *.ogv; *.ogx; *.rm; *.rmvb; *.smil; *.webm; *.wmv; *.xvid
pdfFilter=*.pdf
PK
!<�b44.chrome/toolkit/content/global/globalOverlay.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

function closeWindow(aClose, aPromptFunction, aSource) {
  let { AppConstants } = ChromeUtils.importESModule(
    "resource://gre/modules/AppConstants.sys.mjs"
  );

  // Closing the last window doesn't quit the application on OS X.
  if (AppConstants.platform != "macosx") {
    var windowCount = 0;
    for (let w of Services.wm.getEnumerator(null)) {
      if (w.closed) {
        continue;
      }
      if (++windowCount == 2) {
        break;
      }
    }

    // If we're down to the last window and someone tries to shut down, check to make sure we can!
    if (windowCount == 1 && !canQuitApplication("lastwindow", aSource)) {
      return false;
    }
    if (
      windowCount != 1 &&
      typeof aPromptFunction == "function" &&
      !aPromptFunction(aSource)
    ) {
      return false;
    }

    // If the user explicitly closes the last tabs in the window close remaining tabs. Bug 490136
    if (aClose) {
      window.SessionStore?.maybeDontRestoreTabs(window);
    }
  } else if (
    typeof aPromptFunction == "function" &&
    !aPromptFunction(aSource)
  ) {
    return false;
  }

  if (aClose) {
    window.close();
    return window.closed;
  }

  return true;
}

function canQuitApplication(aData, aSource) {
  const kCID = "@mozilla.org/browser/browserglue;1";
  if (kCID in Cc && !(aData || "").includes("restart")) {
    let BrowserGlue = Cc[kCID].getService(Ci.nsISupports).wrappedJSObject;
    BrowserGlue._registerQuitSource(aSource);
  }
  try {
    var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
      Ci.nsISupportsPRBool
    );
    Services.obs.notifyObservers(
      cancelQuit,
      "quit-application-requested",
      aData || null
    );

    // Something aborted the quit process.
    if (cancelQuit.data) {
      return false;
    }
  } catch (ex) {}
  return true;
}

function goQuitApplication(event) {
  // We can't know for sure if the user used a shortcut to trigger quit.
  // Proxy by means of checking for the shortcut modifier.
  let isMac = navigator.platform.startsWith("Mac");
  let key = isMac ? "metaKey" : "ctrlKey";
  let source = "OS";
  if (event[key]) {
    source = "shortcut";
    // Note that macOS likes pretending something came from this menu even if
    // activated by keyboard shortcut, hence checking that first.
  } else if (event.sourceEvent?.target?.id?.startsWith("menu_")) {
    source = "menuitem";
  } else if (event.sourceEvent?.target?.id?.startsWith("appMenu")) {
    source = "appmenu";
  }
  if (!canQuitApplication(undefined, source)) {
    return false;
  }

  Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit);
  return true;
}

//
// Command Updater functions
//
function goUpdateCommand(aCommand) {
  try {
    var controller =
      top.document.commandDispatcher.getControllerForCommand(aCommand);

    var enabled = false;
    if (controller) {
      enabled = controller.isCommandEnabled(aCommand);
    }

    goSetCommandEnabled(aCommand, enabled);
  } catch (e) {
    console.error("An error occurred updating the ", aCommand, " command: ", e);
  }
}

function goDoCommand(aCommand) {
  try {
    var controller =
      top.document.commandDispatcher.getControllerForCommand(aCommand);
    if (controller && controller.isCommandEnabled(aCommand)) {
      controller.doCommand(aCommand);
    }
  } catch (e) {
    console.error(
      "An error occurred executing the ",
      aCommand,
      " command: ",
      e
    );
  }
}

function goSetCommandEnabled(aID, aEnabled) {
  var node = document.getElementById(aID);

  if (node) {
    if (aEnabled) {
      node.removeAttribute("disabled");
    } else {
      node.setAttribute("disabled", "true");
    }
  }
}
PK
!<oO��<
<
7chrome/toolkit/content/global/gmp-sources/openh264.json{
  "hashFunction": "sha512",
  "name": "OpenH264-2.3.2",
  "schema_version": 1000,
  "vendors": {
    "gmp-gmpopenh264": {
      "platforms": {
        "Darwin_aarch64-gcc3": {
          "fileUrl": "http://ciscobinary.openh264.org/openh264-macosx64-aarch64-31c4d2e4a037526fd30d4e5c39f60885986cf865.zip",
          "filesize": 477938,
          "hashValue": "391efb184373d533713a9e99a9e63c3bbaf614e8d8bdfdd84d4d5e53b9a737e75032187309dd00e58b58bb1033ab68d199f994744f6add57dd08f5fbb654d2f3"
        },
        "Darwin_x86_64-gcc3": {
          "fileUrl": "http://ciscobinary.openh264.org/openh264-macosx64-31c4d2e4a037526fd30d4e5c39f60885986cf865.zip",
          "filesize": 552074,
          "hashValue": "c649bfa20c48406ccbae1917d7478773cd5250ef995828b58cc56cc4db0a3c7ce3f89eb187bd1b3d26cda0e5e65322b710cfd66a2953adeea0e4361c51488add"
        },
        "Linux_aarch64-gcc3": {
          "fileUrl": "http://ciscobinary.openh264.org/openh264-linux64-aarch64-31c4d2e4a037526fd30d4e5c39f60885986cf865.zip",
          "filesize": 537296,
          "hashValue": "eab3fca253c10739c4930bdaf83ca7a9a0a3580937e0e945dde3a45bcdb39b6240ae0d7133ebfb10029442322f4ce6e8647917d15afea601351dc8a5f0dbf3ec"
        },
        "Linux_x86-gcc3": {
          "fileUrl": "http://ciscobinary.openh264.org/openh264-linux32-31c4d2e4a037526fd30d4e5c39f60885986cf865.zip",
          "filesize": 623151,
          "hashValue": "d80508260c6419acc09c9fde539b18e9231899786df549bc8c86e564d1fb8c500327adb96bba204a386326dd5213ae907108c9ca4fc64ae385fbb2291e0e8cc5"
        },
        "Linux_x86_64-gcc3": {
          "fileUrl": "http://ciscobinary.openh264.org/openh264-linux64-31c4d2e4a037526fd30d4e5c39f60885986cf865.zip",
          "filesize": 583674,
          "hashValue": "53a58bfb4c8124ad4f7655b99bfdea290033a085e0796b19245b33b91c0948fdac9f0c3e817130b352493a65d9a7a0fc8a7c1eedc618cdaa2b4580734a11cd9c"
        },
        "Linux_x86_64-gcc3-asan": {
          "alias": "Linux_x86_64-gcc3"
        },
        "WINNT_aarch64-msvc-aarch64": {
          "fileUrl": "http://ciscobinary.openh264.org/openh264-win64-aarch64-31c4d2e4a037526fd30d4e5c39f60885986cf865.zip",
          "filesize": 412806,
          "hashValue": "cc3306873d6ad8ea6a27096c8ce75831463633a530d8f1794ae3eba3efd4a572459b6b0f3ac73f46552a7d6c7a29b152e299d862a2f76420eee07d1d39979182"
        },
        "WINNT_x86-msvc": {
          "fileUrl": "http://ciscobinary.openh264.org/openh264-win32-31c4d2e4a037526fd30d4e5c39f60885986cf865.zip",
          "filesize": 472465,
          "hashValue": "b5c8290bbee9503b6f9d2fcc3cbcf94f9b2d4e8ee143e37cfc8c68de593c8189aecfe447b811accecdb564c8788bd05e116f4eda62ac29b3c6b8a6fa5f564ee0"
        },
        "WINNT_x86-msvc-x64": {
          "alias": "WINNT_x86-msvc"
        },
        "WINNT_x86-msvc-x86": {
          "alias": "WINNT_x86-msvc"
        },
        "WINNT_x86_64-msvc": {
          "fileUrl": "http://ciscobinary.openh264.org/openh264-win64-31c4d2e4a037526fd30d4e5c39f60885986cf865.zip",
          "filesize": 491284,
          "hashValue": "b667086ed49579592d435df2b486fe30ba1b62ddd169f19e700cd079239747dd3e20058c285fa9c10a533e34f22b5198ed9b1f92ae560a3067f3e3feacc724f1"
        },
        "WINNT_x86_64-msvc-x64": {
          "alias": "WINNT_x86_64-msvc"
        },
        "WINNT_x86_64-msvc-x64-asan": {
          "alias": "WINNT_x86_64-msvc"
        }
      },
      "version": "2.3.2"
    }
  }
}
PK
!<A��
�
:chrome/toolkit/content/global/gmp-sources/widevinecdm.json{
  "hashFunction": "sha512",
  "name": "Widevine-4.10.2710.0",
  "schema_version": 1000,
  "vendors": {
    "gmp-widevinecdm": {
      "platforms": {
        "Darwin_aarch64-gcc3": {
          "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/4.10.2710.0-mac-arm64.zip",
          "filesize": 14190373,
          "hashValue": "3aa1e3e34abffb781fbbcb411a0381a4eb641793042987a8b6bcffdb2c366b52b0cb059c36dceff7146e80fbb98c5ccb2f98af726ce2619fa7bbd4b1d388414e"
        },
        "Darwin_x86_64-gcc3": {
          "alias": "Darwin_x86_64-gcc3-u-i386-x86_64"
        },
        "Darwin_x86_64-gcc3-u-i386-x86_64": {
          "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/4.10.2710.0-mac-x64.zip",
          "filesize": 14934332,
          "hashValue": "02e2e5d30cd35d74c8a32192d48b35863bcc71756a323eab84a3c71acfd41dcb56bbb18a0555139cd111f74f7d18dcd821a89f3be0a34b7517fadeaf8b535ac0"
        },
        "Linux_x86_64-gcc3": {
          "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/4.10.2710.0-linux-x64.zip",
          "filesize": 13922119,
          "hashValue": "661ad969099a89a278384f56a17ae912c3542d585ea4981f3b9a3c6e1a07f8da6ffad9db29cee194bf7834adc3ca258c775cd2b0980e3e6cb7ee8b39600dad58"
        },
        "Linux_x86_64-gcc3-asan": {
          "alias": "Linux_x86_64-gcc3"
        },
        "WINNT_aarch64-msvc-aarch64": {
          "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/4.10.2710.0-win-arm64.zip",
          "filesize": 13900511,
          "hashValue": "0f42c5dc0e040036653501fe32cb646123a1018804af4d8890d71bbd716c4e379a81a7d70c0cc5ca4b6ec3aa9cc2612cbc7599f63cbee5c82b03de499df3742e"
        },
        "WINNT_x86-msvc": {
          "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/4.10.2710.0-win-x86.zip",
          "filesize": 14250607,
          "hashValue": "5e4b8672e9fd6bf7db4c85d7d49bf28a5ca2ed352238fe93610205d16c9af67855aa0b02c71b7410ad45716410fa540ddf5046201f0ff052b2c2374b4c9a4760"
        },
        "WINNT_x86-msvc-x64": {
          "alias": "WINNT_x86-msvc"
        },
        "WINNT_x86-msvc-x86": {
          "alias": "WINNT_x86-msvc"
        },
        "WINNT_x86_64-msvc": {
          "fileUrl": "https://redirector.gvt1.com/edgedl/widevine-cdm/4.10.2710.0-win-x64.zip",
          "filesize": 14485862,
          "hashValue": "59521f8c61236641b3299ab460c58c8f5f26fa67e828de853c2cf372f9614d58b9f541aae325b1600ec4f3a47953caacb8122b0dfce7481acfec81045735947d"
        },
        "WINNT_x86_64-msvc-x64": {
          "alias": "WINNT_x86_64-msvc"
        },
        "WINNT_x86_64-msvc-x64-asan": {
          "alias": "WINNT_x86_64-msvc"
        }
      },
      "version": "4.10.2710.0"
    }
  }
}
PK
!<~��--=chrome/toolkit/content/global/gmp-sources/widevinecdm_l1.json{
  "hashFunction": "sha512",
  "name": "Widevine-L1-1.0.2738.0",
  "schema_version": 1000,
  "vendors": {
    "gmp-widevinecdm-l1": {
      "platforms": {
        "WINNT_x86_64-msvc": {
          "fileUrl": "https://redirector.gvt1.com/edgedl/release2/chrome_component/imoffpf67hel7kbknqflao2oo4_1.0.2738.0/neifaoindggfcjicffkgpmnlppeffabd_1.0.2738.0_win64_kj4dp5kifwxbdodqls7e5nzhtm.crx3",
          "filesize": 1181927,
          "hashValue": "4fd27594c459fb1cd94a857be10f7d1d6216dbf202cd43e8a3fa395a268c72fc5f5c456c9cb314f2220d766af741db469c8bb106acbed419149a44a3b87619f1"
        },
        "WINNT_x86_64-msvc-x64": {
          "alias": "WINNT_x86_64-msvc"
        },
        "WINNT_x86_64-msvc-x64-asan": {
          "alias": "WINNT_x86_64-msvc"
        }
      },
      "version": "1.0.2738.0"
    }
  }
}
PK
!<��-�;
;
;chrome/toolkit/content/global/httpsonlyerror/errorpage.html<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html>

<html>
  <head>
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:; object-src 'none'"
    />
    <meta name="color-scheme" content="light dark" />
    <link
      rel="stylesheet"
      href="chrome://global/skin/in-content/info-pages.css"
    />
    <link
      rel="stylesheet"
      href="chrome://global/skin/aboutHttpsOnlyError.css"
    />
    <link rel="localization" href="branding/brand.ftl" />
    <link rel="localization" href="toolkit/about/aboutHttpsOnlyError.ftl" />
    <!-- If the location of the favicon is changed here, the FAVICON_ERRORPAGE_URL symbol in
         toolkit/components/places/src/nsFaviconService.h should be updated. -->
    <link rel="icon" id="favicon" href="chrome://global/skin/icons/info.svg" />
    <title data-l10n-id="about-httpsonly-title-site-not-available"></title>
  </head>
  <body>
    <main class="container">
      <div class="title">
        <h2 data-l10n-id="about-httpsonly-title-alert"></h2>
        <h1
          class="title-text"
          data-l10n-id="about-httpsonly-title-site-not-available"
        ></h1>
      </div>
      <p
        id="insecure-explanation-unavailable"
        data-l10n-id="about-httpsonly-explanation-unavailable2"
        data-l10n-args='{"websiteUrl": ""}'
      ></p>
      <p id="learn-more-container">
        <a
          id="learnMoreLink"
          target="_blank"
          data-l10n-id="about-httpsonly-link-learn-more"
        ></a>
      </p>

      <b data-l10n-id="about-httpsonly-explanation-question"></b>
      <ul>
        <li data-l10n-id="about-httpsonly-explanation-nosupport"></li>
        <li data-l10n-id="about-httpsonly-explanation-risk"></li>
      </ul>

      <p
        id="explanation-continue"
        data-l10n-id="about-httpsonly-explanation-continue"
      ></p>
      <div class="button-container">
        <button
          id="goBack"
          class="primary"
          data-l10n-id="about-httpsonly-button-go-back"
        ></button>
        <button
          id="openInsecure"
          data-l10n-id="about-httpsonly-button-continue-to-site"
          inert
        ></button>
      </div>
      <div class="suggestion-box" hidden>
        <h2 data-l10n-id="about-httpsonly-suggestion-box-header"></h2>
      </div>
    </main>
    <script src="chrome://global/content/httpsonlyerror/errorpage.js"></script>
  </body>
</html>
PK
!<�2���9chrome/toolkit/content/global/httpsonlyerror/errorpage.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env mozilla/remote-page */

"use strict";

const searchParams = new URLSearchParams(document.documentURI.split("?")[1]);

function initPage() {
  if (!searchParams.get("e")) {
    document.getElementById("error").remove();
  }

  const explanation1 = document.getElementById(
    "insecure-explanation-unavailable"
  );

  const pageUrl = new URL(window.location.href.replace(/^view-source:/, ""));

  document.l10n.setAttributes(
    explanation1,
    "about-httpsonly-explanation-unavailable2",
    { websiteUrl: pageUrl.host }
  );

  const baseSupportURL = RPMGetFormatURLPref("app.support.baseURL");
  document
    .getElementById("learnMoreLink")
    .setAttribute("href", baseSupportURL + "https-only-prefs");

  document
    .getElementById("openInsecure")
    .addEventListener("click", onOpenInsecureButtonClick);

  const delay = RPMGetIntPref("security.dialog_enable_delay", 1000);
  setTimeout(() => {
    document.getElementById("openInsecure").removeAttribute("inert");
  }, delay);

  if (window.top == window) {
    document
      .getElementById("goBack")
      .addEventListener("click", onReturnButtonClick);
    addAutofocus("#goBack", "beforeend");
  } else {
    document.getElementById("goBack").remove();
  }

  const isTopLevel = window.top == window;
  const hasWWWPrefix = pageUrl.href.startsWith("https://www.");
  if (isTopLevel && !hasWWWPrefix) {
    // HTTPS-Only generally simply replaces http: with https:;
    // here we additionally try to add www and see if that allows to upgrade the connection if it is top level

    window.addEventListener("pingSecureWWWLinkSuccess", () => {
      activateSuggestionBox();
      displayWWWSuggestion(pageUrl.host);
    });

    // try to ping secure www link in the AboutHttpsOnlyErrorChild
    RPMTryPingSecureWWWLink();
  }
}

/*  Suggestion Box */

function activateSuggestionBox() {
  const suggestionBox = document.querySelector(".suggestion-box");
  suggestionBox.hidden = false;
}

function displayWWWSuggestion(aURL) {
  const suggestionBox = document.querySelector(".suggestion-box");
  const suggestionWWWText = document.createElement("p");
  const suggestionWWWButton = document.createElement("button");
  const suggestionButtonContainer = document.createElement("div");

  document.l10n.setAttributes(
    suggestionWWWText,
    "about-httpsonly-suggestion-box-www-text",
    { websiteUrl: aURL }
  );

  suggestionWWWButton.setAttribute("id", "openWWW");
  document.l10n.setAttributes(
    suggestionWWWButton,
    "about-httpsonly-suggestion-box-www-button",
    { websiteUrl: aURL }
  );
  suggestionWWWButton.addEventListener("click", openSecureWWWButtonClick);

  suggestionButtonContainer.classList.add("button-container");

  suggestionBox.appendChild(suggestionWWWText);
  suggestionButtonContainer.appendChild(suggestionWWWButton);
  suggestionBox.appendChild(suggestionButtonContainer);
}

/*  Button Events  */

function openSecureWWWButtonClick() {
  RPMOpenSecureWWWLink();
}

function onOpenInsecureButtonClick() {
  document.reloadWithHttpsOnlyException();
}

function onReturnButtonClick() {
  RPMSendAsyncMessage("goBack");
}

/*  Utils */

function addAutofocus(selector, position = "afterbegin") {
  if (window.top != window) {
    return;
  }
  var button = document.querySelector(selector);
  var parent = button.parentNode;
  button.remove();
  button.setAttribute("autofocus", "true");
  parent.insertAdjacentElement(position, button);
}

/* Initialize Page */

initPage();
// Dispatch this event so tests can detect that we finished loading the error page.
// We're using the same event name as neterror because BrowserTestUtils.sys.mjs relies on that.
let event = new CustomEvent("AboutNetErrorLoad", { bubbles: true });
document.dispatchEvent(event);
PK
!<.3.��>chrome/toolkit/content/global/httpsonlyerror/secure-broken.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="context-fill #424e5a"><path d="M18.75 9.977h-.727L6 22h12.75A2.25 2.25 0 0 0 21 19.75v-7.523a2.25 2.25 0 0 0-2.25-2.25zm-9.75 0V7a3 3 0 0 1 6 0v1.5l2.838-2.838A5.994 5.994 0 0 0 6 7v2.977h-.75A2.25 2.25 0 0 0 3 12.227v7.523a2.224 2.224 0 0 0 .105.645L13.523 9.977z"></path><path d="M2.5 23a1.5 1.5 0 0 1-1.061-2.561l19-19A1.5 1.5 0 0 1 22.56 3.56l-19 19A1.5 1.5 0 0 1 2.5 23z" fill="#ff0039"></path></svg>
PK
!<m��6]�]�*chrome/toolkit/content/global/license.html<!DOCTYPE HTML>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this file,
   - You can obtain one at http://mozilla.org/MPL/2.0/.  -->

<html lang="en">
  <head>
    <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src chrome:; img-src chrome:; object-src 'none'">
    <meta charset="utf-8">
    <meta name="color-scheme" content="light dark">
    <title>Licenses</title>
    <link rel="stylesheet" href="chrome://global/skin/in-content/info-pages.css">
    <link rel="stylesheet" href="chrome://global/skin/aboutLicense.css">
  </head>

  <body id="lic-info">
  <div class="license-header">
    <div>
      <h1><a id="top"></a>Licenses</h1>
    </div>
  </div>
  <div>
    <p>All of the <b>source code</b> to this product is
       available under licenses which are both
       <a href="https://www.gnu.org/philosophy/free-sw.html">free</a> and
       <a href="https://www.opensource.org/docs/definition.php">open source</a>.
       A URL identifying the specific source code used to create this copy can be found
       on the <a href="about:buildconfig">build configuration page</a>, and you can read
       <a href="https://firefox-source-docs.mozilla.org/contributing/contribution_quickref.html">instructions
       on how to download and build the code for yourself</a>.
    </p>

    <p>More specifically, most of the source code is available under the
       <a href="about:license#mpl">Mozilla Public License 2.0</a> (MPL).
       The MPL has a
       <a href="https://www.mozilla.org/MPL/2.0/FAQ/">FAQ</a> to help
       you understand it. The remainder of the software which is not
       under the MPL is available under one of a variety of other
       free and open source licenses. Those that require reproduction
       of the license text in the distribution are given below.
       (Note: your copy of this product may not contain code covered by one
       or more of the licenses listed here, depending on the exact product
       and version you choose.)
    </p>

    <ul>
      <li><a href="about:license#mpl">Mozilla Public License 2.0</a>
      <br><br>
      </li>
      <li><a href="about:license#lgpl">GNU Lesser General Public License 2.1</a>
      <br><br>
      </li>
      <li><a href="about:license#lgpl-3.0">GNU Lesser General Public License 3.0</a>
      <br><br>
      </li>
      <li><a href="about:license#acorn">acorn License</a></li>
      <li><a href="about:license#android">Android Open Source License</a></li>
      <li><a href="about:license#angle">ANGLE License</a></li>
      <li><a href="about:license#apache">Apache License 2.0</a></li>
      <li><a href="about:license#apple">Apple License</a></li>
      <li><a href="about:license#apple-password-rules-parser">Apple Password Rules Parser License</a></li>
      <li><a href="about:license#arm">ARM License</a></li>
      <li><a href="about:license#babel">Babel License</a></li>
      <li><a href="about:license#babylon">Babylon License</a></li>
      <li><a href="about:license#boost">boost License</a></li>
      <li><a href="about:license#bsd2clause">BSD 2-Clause License</a></li>
      <li><a href="about:license#bsd3clause">BSD 3-Clause License</a></li>
      <li><a href="about:license#bspatch">bspatch License</a></li>
      <li><a href="about:license#cairo">Cairo Component Licenses</a></li>
      <li><a href="about:license#chromium">Chromium License</a></li>
      <li><a href="about:license#codemirror">CodeMirror License</a></li>
      <li><a href="about:license#cryptogams">CRYPTOGAMS License</a></li>
      <li><a href="about:license#cubic-bezier">cubic-bezier License</a></li>
      <li><a href="about:license#d3">D3 License</a></li>
      <li><a href="about:license#dagre-d3">Dagre-D3 License</a></li>
      <li><a href="about:license#diff">diff License</a></li>
      <li><a href="about:license#disconnect.me">Disconnect.Me License</a>
      <li><a href="about:license#dtoa">dtoa License</a></li>
      <li><a href="about:license#hunspell-nl">Dutch Spellchecking Dictionary License</a></li>
      <li><a href="about:license#hunspell-en">English Spellchecking Dictionary Licenses</a></li>
      <li><a href="about:license#hunspell-ee">Estonian Spellchecking Dictionary License</a></li>
      <li><a href="about:license#expat">Expat License</a></li>
      <li><a href="about:license#firebug">Firebug License</a></li>
      <li><a href="about:license#gfx-font-list">gfxFontList License</a></li>
      <li><a href="about:license#google-bsd">Google BSD License</a></li>
      <li><a href="about:license#gears-istumbler">Google Gears/iStumbler License</a></li>
      <li><a href="about:license#vp8">Google VP8 License</a></li>
      <li><a href="about:license#gyp">gyp License</a></li>
      <li><a href="about:license#halloc">halloc License</a></li>
      <li><a href="about:license#harfbuzz">HarfBuzz License</a></li>
      <li><a href="about:license#icu">ICU License</a></li>
      <li><a href="about:license#immutable">Immutable.js License</a></li>
      <li><a href="about:license#jpnic">Japan Network Information Center License</a></li>
      <li><a href="about:license#jemalloc">jemalloc License</a></li>
      <li><a href="about:license#jszip">JSZip License</a></li>
      <li><a href="about:license#jquery">jQuery License</a></li>
      <li><a href="about:license#k_exp">k_exp License</a></li>
      <li><a href="about:license#libcubeb">libcubeb License</a></li>
      <li><a href="about:license#libevent">libevent License</a></li>
      <li><a href="about:license#libffi">libffi License</a></li>
      <li><a href="about:license#libjingle">libjingle License</a></li>
      <li><a href="about:license#libnestegg">libnestegg License</a></li>
      <li><a href="about:license#libsoundtouch">libsoundtouch License</a></li>
      <li><a href="about:license#libyuv">libyuv License</a></li>
      <li><a href="about:license#hunspell-lt">Lithuanian Spellchecking Dictionary License</a></li>
      <li><a href="about:license#lodash">lodash License</a></li>
      <li><a href="about:license#matches">matches License</a></li>
      <li><a href="about:license#mit">MIT License</a></li>
      <li><a href="about:license#myspell">MySpell License</a></li>
      <li><a href="about:license#nicer">nICEr License</a></li>
      <li><a href="about:license#node-md5">node-md5 License</a></li>
      <li><a href="about:license#nom">nom License</a></li>
      <li><a href="about:license#nrappkit">nrappkit License</a></li>
      <li><a href="about:license#openldap">OpenLDAP Public License</a></li>
      <li><a href="about:license#openvision">OpenVision License</a></li>
      <li><a href="about:license#openvr">OpenVR License</a></li>
      <li><a href="about:license#praton">praton License</a></li>
      <li><a href="about:license#praton1">praton and inet_ntop License</a></li>
      <li><a href="about:license#qcms">qcms License</a></li>
      <li><a href="about:license#qrcode-generator">QR Code Generator License</a></li>
      <li><a href="about:license#react">React License</a></li>
      <li><a href="about:license#react-redux">React-Redux License</a></li>
      <li><a href="about:license#xdg">Red Hat xdg_user_dir_lookup License</a></li>
      <li><a href="about:license#redux">Redux License</a></li>
      <li><a href="about:license#hunspell-ru">Russian Spellchecking Dictionary License</a></li>
      <li><a href="about:license#sctp">SCTP Licenses</a></li>
      <li><a href="about:license#skia">Skia License</a></li>
      <li><a href="about:license#snappy">Snappy License</a></li>
      <li><a href="about:license#sprintf.js">sprintf.js License</a></li>
      <li><a href="about:license#sunsoft">SunSoft License</a></li>
      <li><a href="about:license#superfasthash">SuperFastHash License</a></li>
      <li><a href="about:license#twemoji">Twemoji License</a></li>
      <li><a href="about:license#unicase">unicase License</a></li>
      <li><a href="about:license#unicode">Unicode License</a></li>
      <li><a href="about:license#unicode-v3">Unicode License V3</a></li>
      <li><a href="about:license#ucal">University of California License</a></li>
      <li><a href="about:license#v8">V8 License</a></li>
      <li><a href="about:license#validator">Validator License</a></li>
      <li><a href="about:license#vtune">VTune License</a></li>
      <li><a href="about:license#webrtc">WebRTC License</a></li>
      <li><a href="about:license#x264">x264 License</a></li>
      <li><a href="about:license#xiph">Xiph.org Foundation License</a></li>
    </ul>

<br>

    <ul>
      <li><a href="about:license#other-notices">Other Required Notices</a>
      <li><a href="about:license#optional-notices">Optional Notices</a>
    </ul>


    </div>

    <hr>

    <table>
      <thead>
        <th>Name</th>
        <th>Paths</th>
        <th>License</th>
      </thead>
      <tbody>
        <tr>
          <td>
            <h1 id="mpl">Mozilla Public License 2.0</h1>
          </td>
          <td>
            <p>N/A</p>
          </td>
          <td>
            <h2 id="definitions">1. Definitions</h2>

            <dl>
              <dt>1.1. "Contributor"</dt>

              <dd>
                <p>means each individual or legal entity that creates, contributes to
                the creation of, or owns Covered Software.</p>
              </dd>

              <dt>1.2. "Contributor Version"</dt>

              <dd>
                <p>means the combination of the Contributions of others (if any) used
                by a Contributor and that particular Contributor's Contribution.</p>
              </dd>

              <dt>1.3. "Contribution"</dt>

              <dd>
                <p>means Covered Software of a particular Contributor.</p>
              </dd>

              <dt>1.4. "Covered Software"</dt>

              <dd>
                <p>means Source Code Form to which the initial Contributor has attached
                the notice in Exhibit A, the Executable Form of such Source Code Form,
                and Modifications of such Source Code Form, in each case including
                portions thereof.</p>
              </dd>

              <dt>1.5. "Incompatible With Secondary Licenses"</dt>

              <dd>
                <p>means</p>

                <ol type="a">
                  <li>
                    <p>that the initial Contributor has attached the notice described
                    in Exhibit B to the Covered Software; or</p>
                  </li>

                  <li>
                    <p>that the Covered Software was made available under the terms of
                    version 1.1 or earlier of the License, but not also under the terms
                    of a Secondary License.</p>
                  </li>
                </ol>
              </dd>

              <dt>1.6. "Executable Form"</dt>

              <dd>
                <p>means any form of the work other than Source Code Form.</p>
              </dd>

              <dt>1.7. "Larger Work"</dt>

              <dd>
                <p>means a work that combines Covered Software with other material, in
                a separate file or files, that is not Covered Software.</p>
              </dd>

              <dt>1.8. "License"</dt>

              <dd>
                <p>means this document.</p>
              </dd>

              <dt>1.9. "Licensable"</dt>

              <dd>
                <p>means having the right to grant, to the maximum extent possible,
                whether at the time of the initial grant or subsequently, any and all
                of the rights conveyed by this License.</p>
              </dd>

              <dt>1.10. "Modifications"</dt>

              <dd>
                <p>means any of the following:</p>

                <ol type="a">
                  <li>
                    <p>any file in Source Code Form that results from an addition to,
                    deletion from, or modification of the contents of Covered Software;
                    or</p>
                  </li>

                  <li>
                    <p>any new file in Source Code Form that contains any Covered
                    Software.</p>
                  </li>
                </ol>
              </dd>

              <dt>1.11. "Patent Claims" of a Contributor</dt>

              <dd>
                <p>means any patent claim(s), including without limitation, method,
                process, and apparatus claims, in any patent Licensable by such
                Contributor that would be infringed, but for the grant of the License,
                by the making, using, selling, offering for sale, having made, import,
                or transfer of either its Contributions or its Contributor Version.</p>
              </dd>

              <dt>1.12. "Secondary License"</dt>

              <dd>
                <p>means either the GNU General Public License, Version 2.0, the GNU
                Lesser General Public License, Version 2.1, the GNU Affero General
                Public License, Version 3.0, or any later versions of those
                licenses.</p>
              </dd>

              <dt>1.13. "Source Code Form"</dt>

              <dd>
                <p>means the form of the work preferred for making modifications.</p>
              </dd>

              <dt>1.14. "You" (or "Your")</dt>

              <dd>
                <p>means an individual or a legal entity exercising rights under this
                License. For legal entities, "You" includes any entity that controls,
                is controlled by, or is under common control with You. For purposes of
                this definition, "control" means (a) the power, direct or indirect, to
                cause the direction or management of such entity, whether by contract
                or otherwise, or (b) ownership of more than fifty percent (50%) of the
                outstanding shares or beneficial ownership of such entity.</p>
              </dd>
            </dl>

            <h2 id="license-grants-and-conditions">2. License Grants and
            Conditions</h2>

            <h3 id="grants">2.1. Grants</h3>

            <p>Each Contributor hereby grants You a world-wide, royalty-free,
            non-exclusive license:</p>

            <ol type="a">
              <li>
                <p>under intellectual property rights (other than patent or trademark)
                Licensable by such Contributor to use, reproduce, make available,
                modify, display, perform, distribute, and otherwise exploit its
                Contributions, either on an unmodified basis, with Modifications, or as
                part of a Larger Work; and</p>
              </li>

              <li>
                <p>under Patent Claims of such Contributor to make, use, sell, offer
                for sale, have made, import, and otherwise transfer either its
                Contributions or its Contributor Version.</p>
              </li>
            </ol>

            <h3 id="effective-date">2.2. Effective Date</h3>

            <p>The licenses granted in Section 2.1 with respect to any Contribution
            become effective for each Contribution on the date the Contributor first
            distributes such Contribution.</p>

            <h3 id="limitations-on-grant-scope">2.3. Limitations on Grant Scope</h3>

            <p>The licenses granted in this Section 2 are the only rights granted under
            this License. No additional rights or licenses will be implied from the
            distribution or licensing of Covered Software under this License.
            Notwithstanding Section 2.1(b) above, no patent license is granted by a
            Contributor:</p>

            <ol type="a">
              <li>
                <p>for any code that a Contributor has removed from Covered Software;
                or</p>
              </li>

              <li>
                <p>for infringements caused by: (i) Your and any other third party's
                modifications of Covered Software, or (ii) the combination of its
                Contributions with other software (except as part of its Contributor
                Version); or</p>
              </li>

              <li>
                <p>under Patent Claims infringed by Covered Software in the absence of
                its Contributions.</p>
              </li>
            </ol>

            <p>This License does not grant any rights in the trademarks, service marks,
            or logos of any Contributor (except as may be necessary to comply with the
            notice requirements in Section 3.4).</p>

            <h3 id="subsequent-licenses">2.4. Subsequent Licenses</h3>

            <p>No Contributor makes additional grants as a result of Your choice to
            distribute the Covered Software under a subsequent version of this License
            (see Section 10.2) or under the terms of a Secondary License (if permitted
            under the terms of Section 3.3).</p>

            <h3 id="representation">2.5. Representation</h3>

            <p>Each Contributor represents that the Contributor believes its
            Contributions are its original creation(s) or it has sufficient rights to
            grant the rights to its Contributions conveyed by this License.</p>

            <h3 id="fair-use">2.6. Fair Use</h3>

            <p>This License is not intended to limit any rights You have under
            applicable copyright doctrines of fair use, fair dealing, or other
            equivalents.</p>

            <h3 id="conditions">2.7. Conditions</h3>

            <p>Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
            in Section 2.1.</p>

            <h2 id="responsibilities">3. Responsibilities</h2>

            <h3 id="distribution-of-source-form">3.1. Distribution of Source Form</h3>

            <p>All distribution of Covered Software in Source Code Form, including any
            Modifications that You create or to which You contribute, must be under the
            terms of this License. You must inform recipients that the Source Code Form
            of the Covered Software is governed by the terms of this License, and how
            they can obtain a copy of this License. You may not attempt to alter or
            restrict the recipients' rights in the Source Code Form.</p>

            <h3 id="distribution-of-executable-form">3.2. Distribution of Executable
            Form</h3>

            <p>If You distribute Covered Software in Executable Form then:</p>

            <ol type="a">
              <li>
                <p>such Covered Software must also be made available in Source Code
                Form, as described in Section 3.1, and You must inform recipients of
                the Executable Form how they can obtain a copy of such Source Code Form
                by reasonable means in a timely manner, at a charge no more than the
                cost of distribution to the recipient; and</p>
              </li>

              <li>
                <p>You may distribute such Executable Form under the terms of this
                License, or sublicense it under different terms, provided that the
                license for the Executable Form does not attempt to limit or alter the
                recipients' rights in the Source Code Form under this License.</p>
              </li>
            </ol>

            <h3 id="distribution-of-a-larger-work">3.3. Distribution of a Larger
            Work</h3>

            <p>You may create and distribute a Larger Work under terms of Your choice,
            provided that You also comply with the requirements of this License for the
            Covered Software. If the Larger Work is a combination of Covered Software
            with a work governed by one or more Secondary Licenses, and the Covered
            Software is not Incompatible With Secondary Licenses, this License permits
            You to additionally distribute such Covered Software under the terms of
            such Secondary License(s), so that the recipient of the Larger Work may, at
            their option, further distribute the Covered Software under the terms of
            either this License or such Secondary License(s).</p>

            <h3 id="notices">3.4. Notices</h3>

            <p>You may not remove or alter the substance of any license notices
            (including copyright notices, patent notices, disclaimers of warranty, or
            limitations of liability) contained within the Source Code Form of the
            Covered Software, except that You may alter any license notices to the
            extent required to remedy known factual inaccuracies.</p>

            <h3 id="application-of-additional-terms">3.5. Application of Additional
            Terms</h3>

            <p>You may choose to offer, and to charge a fee for, warranty, support,
            indemnity or liability obligations to one or more recipients of Covered
            Software. However, You may do so only on Your own behalf, and not on behalf
            of any Contributor. You must make it absolutely clear that any such
            warranty, support, indemnity, or liability obligation is offered by You
            alone, and You hereby agree to indemnify every Contributor for any
            liability incurred by such Contributor as a result of warranty, support,
            indemnity or liability terms You offer. You may include additional
            disclaimers of warranty and limitations of liability specific to any
            jurisdiction.</p>

            <h2 id="inability-to-comply-due-to-statute-or-regulation">4. Inability to
            Comply Due to Statute or Regulation</h2>

            <p>If it is impossible for You to comply with any of the terms of this
            License with respect to some or all of the Covered Software due to statute,
            judicial order, or regulation then You must: (a) comply with the terms of
            this License to the maximum extent possible; and (b) describe the
            limitations and the code they affect. Such description must be placed in a
            text file included with all distributions of the Covered Software under
            this License. Except to the extent prohibited by statute or regulation,
            such description must be sufficiently detailed for a recipient of ordinary
            skill to be able to understand it.</p>

            <h2 id="termination">5. Termination</h2>

            <h3>5.1.</h3>

            <p>The rights granted under this License will terminate automatically
            if You fail to comply with any of its terms. However, if You become
            compliant, then the rights granted under this License from a particular
            Contributor are reinstated (a) provisionally, unless and until such
            Contributor explicitly and finally terminates Your grants, and (b) on an
            ongoing basis, if such Contributor fails to notify You of the
            non-compliance by some reasonable means prior to 60 days after You have
            come back into compliance. Moreover, Your grants from a particular
            Contributor are reinstated on an ongoing basis if such Contributor notifies
            You of the non-compliance by some reasonable means, this is the first time
            You have received notice of non-compliance with this License from such
            Contributor, and You become compliant prior to 30 days after Your receipt
            of the notice.</p>

            <h3>5.2.</h3>

            <p>If You initiate litigation against any entity by asserting a patent
            infringement claim (excluding declaratory judgment actions, counter-claims,
            and cross-claims) alleging that a Contributor Version directly or
            indirectly infringes any patent, then the rights granted to You by any and
            all Contributors for the Covered Software under Section 2.1 of this License
            shall terminate.</p>

            <h3>5.3.</h3>

            <p>In the event of termination under Sections 5.1 or 5.2 above, all
            end user license agreements (excluding distributors and resellers) which
            have been validly granted by You or Your distributors under this License
            prior to termination shall survive termination.</p>

            <h2 id="disclaimer-of-warranty">6. Disclaimer of Warranty</h2>

            <p><em>Covered Software is provided under this License on an "as is" basis,
            without warranty of any kind, either expressed, implied, or statutory,
            including, without limitation, warranties that the Covered Software is free
            of defects, merchantable, fit for a particular purpose or non-infringing.
            The entire risk as to the quality and performance of the Covered Software
            is with You. Should any Covered Software prove defective in any respect,
            You (not any Contributor) assume the cost of any necessary servicing,
            repair, or correction. This disclaimer of warranty constitutes an essential
            part of this License. No use of any Covered Software is authorized under
            this License except under this disclaimer.</em></p>

            <h2 id="limitation-of-liability">7. Limitation of Liability</h2>

            <p><em>Under no circumstances and under no legal theory, whether tort
            (including negligence), contract, or otherwise, shall any Contributor, or
            anyone who distributes Covered Software as permitted above, be liable to
            You for any direct, indirect, special, incidental, or consequential damages
            of any character including, without limitation, damages for lost profits,
            loss of goodwill, work stoppage, computer failure or malfunction, or any
            and all other commercial damages or losses, even if such party shall have
            been informed of the possibility of such damages. This limitation of
            liability shall not apply to liability for death or personal injury
            resulting from such party's negligence to the extent applicable law
            prohibits such limitation. Some jurisdictions do not allow the exclusion or
            limitation of incidental or consequential damages, so this exclusion and
            limitation may not apply to You.</em></p>

            <h2 id="litigation">8. Litigation</h2>

            <p>Any litigation relating to this License may be brought only in the
            courts of a jurisdiction where the defendant maintains its principal place
            of business and such litigation shall be governed by laws of that
            jurisdiction, without reference to its conflict-of-law provisions. Nothing
            in this Section shall prevent a party's ability to bring cross-claims or
            counter-claims.</p>

            <h2 id="miscellaneous">9. Miscellaneous</h2>

            <p>This License represents the complete agreement concerning the subject
            matter hereof. If any provision of this License is held to be
            unenforceable, such provision shall be reformed only to the extent
            necessary to make it enforceable. Any law or regulation which provides that
            the language of a contract shall be construed against the drafter shall not
            be used to construe this License against a Contributor.</p>

            <h2 id="versions-of-the-license">10. Versions of the License</h2>

            <h3 id="new-versions">10.1. New Versions</h3>

            <p>Mozilla Foundation is the license steward. Except as provided in Section
            10.3, no one other than the license steward has the right to modify or
            publish new versions of this License. Each version will be given a
            distinguishing version number.</p>

            <h3 id="effect-of-new-versions">10.2. Effect of New Versions</h3>

            <p>You may distribute the Covered Software under the terms of the version
            of the License under which You originally received the Covered Software, or
            under the terms of any subsequent version published by the license
            steward.</p>

            <h3 id="modified-versions">10.3. Modified Versions</h3>

            <p>If you create software not governed by this License, and you want to
            create a new license for such software, you may create and use a modified
            version of this License if you rename the license and remove any references
            to the name of the license steward (except to note that such modified
            license differs from this License).</p>

            <h3 id=
            "distributing-source-code-form-that-is-incompatible-with-secondary-licenses">
            10.4. Distributing Source Code Form that is Incompatible With Secondary
            Licenses</h3>

            <p>If You choose to distribute Source Code Form that is Incompatible With
            Secondary Licenses under the terms of this version of the License, the
            notice described in Exhibit B of this License must be attached.</p>

            <h2 id="exhibit-a---source-code-form-license-notice">Exhibit A - Source
            Code Form License Notice</h2>

            <blockquote>
              <p>This Source Code Form is subject to the terms of the Mozilla Public
              License, v. 2.0. If a copy of the MPL was not distributed with this file,
              You can obtain one at https://mozilla.org/MPL/2.0/.</p>
            </blockquote>

            <p>If it is not possible or desirable to put the notice in a particular
            file, then You may include the notice in a location (such as a LICENSE file
            in a relevant directory) where a recipient would be likely to look for such
            a notice.</p>

            <p>You may add additional accurate notices of copyright ownership.</p>

            <h2 id="exhibit-b---incompatible-with-secondary-licenses-notice">Exhibit B
            - "Incompatible With Secondary Licenses" Notice</h2>

            <blockquote>
              <p>This Source Code Form is "Incompatible With Secondary Licenses", as
              defined by the Mozilla Public License, v. 2.0.</p>
            </blockquote>
          </td>
        </tr>

        <tr>
          <td><h1 id="lgpl">GNU Lesser General Public License 2.1</h1></td>
          <td>
            <p>This product contains code from the following LGPLed libraries:</p>
            <ul>
              <li><a href="https://www.surina.net/soundtouch/">libsoundtouch</a>
              <li><a href="https://libav.org/">Libav</a>
              <li><a href="https://ffmpeg.org/">FFmpeg</a>
            </ul>
          </td>
          <td>
            <pre>
            Copyright (C) 1991, 1999 Free Software Foundation, Inc.
            51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
            Everyone is permitted to copy and distribute verbatim copies
            of this license document, but changing it is not allowed.

            [This is the first released version of the Lesser GPL.  It also counts
              as the successor of the GNU Library Public License, version 2, hence
              the version number 2.1.]
            </pre>

            <h3><a id="SEC2">Preamble</a></h3>

            <p>
              The licenses for most software are designed to take away your
            freedom to share and change it.  By contrast, the GNU General Public
            Licenses are intended to guarantee your freedom to share and change
            free software--to make sure the software is free for all its users.
            </p>
            <p>
              This license, the Lesser General Public License, applies to some
            specially designated software packages--typically libraries--of the
            Free Software Foundation and other authors who decide to use it.  You
            can use it too, but we suggest you first think carefully about whether
            this license or the ordinary General Public License is the better
            strategy to use in any particular case, based on the explanations below.
            </p>
            <p>
              When we speak of free software, we are referring to freedom of use,
            not price.  Our General Public Licenses are designed to make sure that
            you have the freedom to distribute copies of free software (and charge
            for this service if you wish); that you receive source code or can get
            it if you want it; that you can change the software and use pieces of
            it in new free programs; and that you are informed that you can do
            these things.
            </p>
            <p>
              To protect your rights, we need to make restrictions that forbid
            distributors to deny you these rights or to ask you to surrender these
            rights.  These restrictions translate to certain responsibilities for
            you if you distribute copies of the library or if you modify it.
            </p>
            <p>
              For example, if you distribute copies of the library, whether gratis
            or for a fee, you must give the recipients all the rights that we gave
            you.  You must make sure that they, too, receive or can get the source
            code.  If you link other code with the library, you must provide
            complete object files to the recipients, so that they can relink them
            with the library after making changes to the library and recompiling
            it.  And you must show them these terms so they know their rights.
            </p>
            <p>
              We protect your rights with a two-step method: (1) we copyright the
            library, and (2) we offer you this license, which gives you legal
            permission to copy, distribute and/or modify the library.
            </p>
            <p>
              To protect each distributor, we want to make it very clear that
            there is no warranty for the free library.  Also, if the library is
            modified by someone else and passed on, the recipients should know
            that what they have is not the original version, so that the original
            author's reputation will not be affected by problems that might be
            introduced by others.
            </p>
            <p>
              Finally, software patents pose a constant threat to the existence of
            any free program.  We wish to make sure that a company cannot
            effectively restrict the users of a free program by obtaining a
            restrictive license from a patent holder.  Therefore, we insist that
            any patent license obtained for a version of the library must be
            consistent with the full freedom of use specified in this license.
            </p>
            <p>
              Most GNU software, including some libraries, is covered by the
            ordinary GNU General Public License.  This license, the GNU Lesser
            General Public License, applies to certain designated libraries, and
            is quite different from the ordinary General Public License.  We use
            this license for certain libraries in order to permit linking those
            libraries into non-free programs.
            </p>
            <p>
              When a program is linked with a library, whether statically or using
            a shared library, the combination of the two is legally speaking a
            combined work, a derivative of the original library.  The ordinary
            General Public License therefore permits such linking only if the
            entire combination fits its criteria of freedom.  The Lesser General
            Public License permits more lax criteria for linking other code with
            the library.
            </p>
            <p>
              We call this license the "Lesser" General Public License because it
            does Less to protect the user's freedom than the ordinary General
            Public License.  It also provides other free software developers Less
            of an advantage over competing non-free programs.  These disadvantages
            are the reason we use the ordinary General Public License for many
            libraries.  However, the Lesser license provides advantages in certain
            special circumstances.
            </p>
            <p>
              For example, on rare occasions, there may be a special need to
            encourage the widest possible use of a certain library, so that it becomes
            a de-facto standard.  To achieve this, non-free programs must be
            allowed to use the library.  A more frequent case is that a free
            library does the same job as widely used non-free libraries.  In this
            case, there is little to gain by limiting the free library to free
            software only, so we use the Lesser General Public License.
            </p>
            <p>
              In other cases, permission to use a particular library in non-free
            programs enables a greater number of people to use a large body of
            free software.  For example, permission to use the GNU C Library in
            non-free programs enables many more people to use the whole GNU
            operating system, as well as its variant, the GNU/Linux operating
            system.
            </p>
            <p>
              Although the Lesser General Public License is Less protective of the
            users' freedom, it does ensure that the user of a program that is
            linked with the Library has the freedom and the wherewithal to run
            that program using a modified version of the Library.
            </p>
            <p>
              The precise terms and conditions for copying, distribution and
            modification follow.  Pay close attention to the difference between a
            "work based on the library" and a "work that uses the library".  The
            former contains code derived from the library, whereas the latter must
            be combined with the library in order to run.
            </p>

            <h3><a id="SEC3">TERMS AND CONDITIONS FOR COPYING,
            DISTRIBUTION AND MODIFICATION</a></h3>


            <p>
            <strong>0.</strong>
            This License Agreement applies to any software library or other
            program which contains a notice placed by the copyright holder or
            other authorized party saying it may be distributed under the terms of
            this Lesser General Public License (also called "this License").
            Each licensee is addressed as "you".
            </p>
            <p>
              A "library" means a collection of software functions and/or data
            prepared so as to be conveniently linked with application programs
            (which use some of those functions and data) to form executables.
            </p>
            <p>
              The "Library", below, refers to any such software library or work
            which has been distributed under these terms.  A "work based on the
            Library" means either the Library or any derivative work under
            copyright law: that is to say, a work containing the Library or a
            portion of it, either verbatim or with modifications and/or translated
            straightforwardly into another language.  (Hereinafter, translation is
            included without limitation in the term "modification".)
            </p>
            <p>
              "Source code" for a work means the preferred form of the work for
            making modifications to it.  For a library, complete source code means
            all the source code for all modules it contains, plus any associated
            interface definition files, plus the scripts used to control compilation
            and installation of the library.
            </p>
            <p>
              Activities other than copying, distribution and modification are not
            covered by this License; they are outside its scope.  The act of
            running a program using the Library is not restricted, and output from
            such a program is covered only if its contents constitute a work based
            on the Library (independent of the use of the Library in a tool for
            writing it).  Whether that is true depends on what the Library does
            and what the program that uses the Library does.
            </p>
            <p>
            <strong>1.</strong>
            You may copy and distribute verbatim copies of the Library's
            complete source code as you receive it, in any medium, provided that
            you conspicuously and appropriately publish on each copy an
            appropriate copyright notice and disclaimer of warranty; keep intact
            all the notices that refer to this License and to the absence of any
            warranty; and distribute a copy of this License along with the
            Library.
            </p>
            <p>
              You may charge a fee for the physical act of transferring a copy,
            and you may at your option offer warranty protection in exchange for a
            fee.
            </p>
            <p>
            <strong>2.</strong>
            You may modify your copy or copies of the Library or any portion
            of it, thus forming a work based on the Library, and copy and
            distribute such modifications or work under the terms of Section 1
            above, provided that you also meet all of these conditions:
            </p>

            <ul>
              <li><strong>a)</strong>
                    The modified work must itself be a software library.</li>
              <li><strong>b)</strong>
                    You must cause the files modified to carry prominent notices
                    stating that you changed the files and the date of any change.</li>

              <li><strong>c)</strong>
                    You must cause the whole of the work to be licensed at no
                    charge to all third parties under the terms of this License.</li>

              <li><strong>d)</strong>
                    If a facility in the modified Library refers to a function or a
                    table of data to be supplied by an application program that uses
                    the facility, other than as an argument passed when the facility
                    is invoked, then you must make a good faith effort to ensure that,
                    in the event an application does not supply such function or
                    table, the facility still operates, and performs whatever part of
                    its purpose remains meaningful.
                    <p>
                    (For example, a function in a library to compute square roots has
                    a purpose that is entirely well-defined independent of the
                    application.  Therefore, Subsection 2d requires that any
                    application-supplied function or table used by this function must
                    be optional: if the application does not supply it, the square
                    root function must still compute square roots.)</p></li>
            </ul>

            <p>
            These requirements apply to the modified work as a whole.  If identifiable
            sections of that work are not derived from the Library, and can be
            reasonably considered independent and separate works in themselves, then
            this License, and its terms, do not apply to those sections when you
            distribute them as separate works.  But when you distribute the same
            sections as part of a whole which is a work based on the Library, the
            distribution of the whole must be on the terms of this License, whose
            permissions for other licensees extend to the entire whole, and thus to
            each and every part regardless of who wrote it.
            </p>
            <p>
            Thus, it is not the intent of this section to claim rights or contest your
            rights to work written entirely by you; rather, the intent is to exercise
            the right to control the distribution of derivative or collective works
            based on the Library.
            </p>
            <p>
            In addition, mere aggregation of another work not based on the Library with
            the Library (or with a work based on the Library) on a volume of a storage
            or distribution medium does not bring the other work under the scope of
            this License.
            </p>
            <p>
            <strong>3.</strong>
            You may opt to apply the terms of the ordinary GNU General Public
            License instead of this License to a given copy of the Library.  To do
            this, you must alter all the notices that refer to this License, so
            that they refer to the ordinary GNU General Public License, version 2,
            instead of to this License.  (If a newer version than version 2 of the
            ordinary GNU General Public License has appeared, then you can specify
            that version instead if you wish.)  Do not make any other change in
            these notices.
            </p>
            <p>
              Once this change is made in a given copy, it is irreversible for
            that copy, so the ordinary GNU General Public License applies to all
            subsequent copies and derivative works made from that copy.
            </p>
            <p>
              This option is useful when you wish to copy part of the code of
            the Library into a program that is not a library.
            </p>
            <p>
            <strong>4.</strong>
            You may copy and distribute the Library (or a portion or
            derivative of it, under Section 2) in object code or executable form
            under the terms of Sections 1 and 2 above provided that you accompany
            it with the complete corresponding machine-readable source code, which
            must be distributed under the terms of Sections 1 and 2 above on a
            medium customarily used for software interchange.
            </p>
            <p>
              If distribution of object code is made by offering access to copy
            from a designated place, then offering equivalent access to copy the
            source code from the same place satisfies the requirement to
            distribute the source code, even though third parties are not
            compelled to copy the source along with the object code.
            </p>
            <p>
            <strong>5.</strong>
            A program that contains no derivative of any portion of the
            Library, but is designed to work with the Library by being compiled or
            linked with it, is called a "work that uses the Library".  Such a
            work, in isolation, is not a derivative work of the Library, and
            therefore falls outside the scope of this License.
            </p>
            <p>
              However, linking a "work that uses the Library" with the Library
            creates an executable that is a derivative of the Library (because it
            contains portions of the Library), rather than a "work that uses the
            library".  The executable is therefore covered by this License.
            Section 6 states terms for distribution of such executables.
            </p>
            <p>
              When a "work that uses the Library" uses material from a header file
            that is part of the Library, the object code for the work may be a
            derivative work of the Library even though the source code is not.
            Whether this is true is especially significant if the work can be
            linked without the Library, or if the work is itself a library.  The
            threshold for this to be true is not precisely defined by law.
            </p>
            <p>
              If such an object file uses only numerical parameters, data
            structure layouts and accessors, and small macros and small inline
            functions (ten lines or less in length), then the use of the object
            file is unrestricted, regardless of whether it is legally a derivative
            work.  (Executables containing this object code plus portions of the
            Library will still fall under Section 6.)
            </p>
            <p>
              Otherwise, if the work is a derivative of the Library, you may
            distribute the object code for the work under the terms of Section 6.
            Any executables containing that work also fall under Section 6,
            whether or not they are linked directly with the Library itself.
            </p>
            <p>
            <strong>6.</strong>
            As an exception to the Sections above, you may also combine or
            link a "work that uses the Library" with the Library to produce a
            work containing portions of the Library, and distribute that work
            under terms of your choice, provided that the terms permit
            modification of the work for the customer's own use and reverse
            engineering for debugging such modifications.
            </p>
            <p>
              You must give prominent notice with each copy of the work that the
            Library is used in it and that the Library and its use are covered by
            this License.  You must supply a copy of this License.  If the work
            during execution displays copyright notices, you must include the
            copyright notice for the Library among them, as well as a reference
            directing the user to the copy of this License.  Also, you must do one
            of these things:
            </p>

            <ul>
                <li><strong>a)</strong> Accompany the work with the complete
                corresponding machine-readable source code for the Library
                including whatever changes were used in the work (which must be
                distributed under Sections 1 and 2 above); and, if the work is an
                executable linked with the Library, with the complete
                machine-readable "work that uses the Library", as object code
                and/or source code, so that the user can modify the Library and
                then relink to produce a modified executable containing the
                modified Library.  (It is understood that the user who changes the
                contents of definitions files in the Library will not necessarily
                be able to recompile the application to use the modified
                definitions.)</li>

                <li><strong>b)</strong> Use a suitable shared library mechanism
                for linking with the Library.  A suitable mechanism is one that
                (1) uses at run time a copy of the library already present on the
                user's computer system, rather than copying library functions into
                the executable, and (2) will operate properly with a modified
                version of the library, if the user installs one, as long as the
                modified version is interface-compatible with the version that the
                work was made with.</li>

                <li><strong>c)</strong> Accompany the work with a written offer,
                valid for at least three years, to give the same user the
                materials specified in Subsection 6a, above, for a charge no more
                than the cost of performing this distribution.</li>

                <li><strong>d)</strong> If distribution of the work is made by
                offering access to copy from a designated place, offer equivalent
                access to copy the above specified materials from the same
                place.</li>

                <li><strong>e)</strong> Verify that the user has already received
                a copy of these materials or that you have already sent this user
                a copy.</li>
            </ul>

            <p>
              For an executable, the required form of the "work that uses the
            Library" must include any data and utility programs needed for
            reproducing the executable from it.  However, as a special exception,
            the materials to be distributed need not include anything that is
            normally distributed (in either source or binary form) with the major
            components (compiler, kernel, and so on) of the operating system on
            which the executable runs, unless that component itself accompanies
            the executable.
            </p>
            <p>
              It may happen that this requirement contradicts the license
            restrictions of other proprietary libraries that do not normally
            accompany the operating system.  Such a contradiction means you cannot
            use both them and the Library together in an executable that you
            distribute.
            </p>
            <p>
            <strong>7.</strong> You may place library facilities that are a work
            based on the Library side-by-side in a single library together with
            other library facilities not covered by this License, and distribute
            such a combined library, provided that the separate distribution of
            the work based on the Library and of the other library facilities is
            otherwise permitted, and provided that you do these two things:
            </p>

            <ul>
                <li><strong>a)</strong> Accompany the combined library with a copy
                of the same work based on the Library, uncombined with any other
                library facilities.  This must be distributed under the terms of
                the Sections above.</li>

                <li><strong>b)</strong> Give prominent notice with the combined
                library of the fact that part of it is a work based on the
                Library, and explaining where to find the accompanying uncombined
                form of the same work.</li>
            </ul>

            <p>
            <strong>8.</strong> You may not copy, modify, sublicense, link with,
            or distribute the Library except as expressly provided under this
            License.  Any attempt otherwise to copy, modify, sublicense, link
            with, or distribute the Library is void, and will automatically
            terminate your rights under this License.  However, parties who have
            received copies, or rights, from you under this License will not have
            their licenses terminated so long as such parties remain in full
            compliance.
            </p>
            <p>
            <strong>9.</strong>
            You are not required to accept this License, since you have not
            signed it.  However, nothing else grants you permission to modify or
            distribute the Library or its derivative works.  These actions are
            prohibited by law if you do not accept this License.  Therefore, by
            modifying or distributing the Library (or any work based on the
            Library), you indicate your acceptance of this License to do so, and
            all its terms and conditions for copying, distributing or modifying
            the Library or works based on it.
            </p>
            <p>
            <strong>10.</strong>
            Each time you redistribute the Library (or any work based on the
            Library), the recipient automatically receives a license from the
            original licensor to copy, distribute, link with or modify the Library
            subject to these terms and conditions.  You may not impose any further
            restrictions on the recipients' exercise of the rights granted herein.
            You are not responsible for enforcing compliance by third parties with
            this License.
            </p>
            <p>
            <strong>11.</strong>
            If, as a consequence of a court judgment or allegation of patent
            infringement or for any other reason (not limited to patent issues),
            conditions are imposed on you (whether by court order, agreement or
            otherwise) that contradict the conditions of this License, they do not
            excuse you from the conditions of this License.  If you cannot
            distribute so as to satisfy simultaneously your obligations under this
            License and any other pertinent obligations, then as a consequence you
            may not distribute the Library at all.  For example, if a patent
            license would not permit royalty-free redistribution of the Library by
            all those who receive copies directly or indirectly through you, then
            the only way you could satisfy both it and this License would be to
            refrain entirely from distribution of the Library.
            </p>
            <p>
            If any portion of this section is held invalid or unenforceable under any
            particular circumstance, the balance of the section is intended to apply,
            and the section as a whole is intended to apply in other circumstances.
            </p>
            <p>
            It is not the purpose of this section to induce you to infringe any
            patents or other property right claims or to contest validity of any
            such claims; this section has the sole purpose of protecting the
            integrity of the free software distribution system which is
            implemented by public license practices.  Many people have made
            generous contributions to the wide range of software distributed
            through that system in reliance on consistent application of that
            system; it is up to the author/donor to decide if he or she is willing
            to distribute software through any other system and a licensee cannot
            impose that choice.
            </p>
            <p>
            This section is intended to make thoroughly clear what is believed to
            be a consequence of the rest of this License.
            </p>
            <p>
            <strong>12.</strong>
            If the distribution and/or use of the Library is restricted in
            certain countries either by patents or by copyrighted interfaces, the
            original copyright holder who places the Library under this License may add
            an explicit geographical distribution limitation excluding those countries,
            so that distribution is permitted only in or among countries not thus
            excluded.  In such case, this License incorporates the limitation as if
            written in the body of this License.
            </p>
            <p>
            <strong>13.</strong>
            The Free Software Foundation may publish revised and/or new
            versions of the Lesser General Public License from time to time.
            Such new versions will be similar in spirit to the present version,
            but may differ in detail to address new problems or concerns.
            </p>
            <p>
            Each version is given a distinguishing version number.  If the Library
            specifies a version number of this License which applies to it and
            "any later version", you have the option of following the terms and
            conditions either of that version or of any later version published by
            the Free Software Foundation.  If the Library does not specify a
            license version number, you may choose any version ever published by
            the Free Software Foundation.
            </p>
            <p>
            <strong>14.</strong>
            If you wish to incorporate parts of the Library into other free
            programs whose distribution conditions are incompatible with these,
            write to the author to ask for permission.  For software which is
            copyrighted by the Free Software Foundation, write to the Free
            Software Foundation; we sometimes make exceptions for this.  Our
            decision will be guided by the two goals of preserving the free status
            of all derivatives of our free software and of promoting the sharing
            and reuse of software generally.
            </p>
            <p>
            <strong>NO WARRANTY</strong>
            </p>
            <p>
            <strong>15.</strong>
            BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
            WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
            EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
            OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
            KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
            IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
            PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
            LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
            THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
            </p>
            <p>
            <strong>16.</strong>
            IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
            WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
            AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
            FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
            CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
            LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
            RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
            FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
            SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
            DAMAGES.
            </p>
          </td>
        </tr>

        <tr>
          <td>
            <h1 id="lgpl-3.0">GNU Lesser General Public License 3.0</h1>
          </td>
          <td>
            <p>Some versions of this product contains code from the following LGPLed libraries:</p>
            <ul>
              <li>
                <a href="https://addons.mozilla.org/en-US/firefox/addon/görans-hemmasnickrade-ordli/">Swedish dictionary</a>
            </ul>
          </td>
          <td>
            <pre>Copyright &copy; 2007 Free Software Foundation, Inc.
              &lt;<a href="https://www.fsf.org/">https://www.fsf.org/</a>&gt;

            Everyone is permitted to copy and distribute verbatim copies
            of this license document, but changing it is not allowed.</pre>

            <p>This version of the GNU Lesser General Public License incorporates
            the terms and conditions of version 3 of the GNU General Public
            License, supplemented by the additional permissions listed below.</p>

            <h3><a id="section0">0. Additional Definitions</a></h3>

            <p>As used herein, &ldquo;this License&rdquo; refers to version 3 of the GNU Lesser
            General Public License, and the &ldquo;GNU GPL&rdquo; refers to version 3 of the GNU
            General Public License.</p>

            <p>&ldquo;The Library&rdquo; refers to a covered work governed by this License,
            other than an Application or a Combined Work as defined below.</p>

            <p>An &ldquo;Application&rdquo; is any work that makes use of an interface provided
            by the Library, but which is not otherwise based on the Library.
            Defining a subclass of a class defined by the Library is deemed a mode
            of using an interface provided by the Library.</p>

            <p>A &ldquo;Combined Work&rdquo; is a work produced by combining or linking an
            Application with the Library.  The particular version of the Library
            with which the Combined Work was made is also called the &ldquo;Linked
            Version&rdquo;.</p>

            <p>The &ldquo;Minimal Corresponding Source&rdquo; for a Combined Work means the
            Corresponding Source for the Combined Work, excluding any source code
            for portions of the Combined Work that, considered in isolation, are
            based on the Application, and not on the Linked Version.</p>

            <p>The &ldquo;Corresponding Application Code&rdquo; for a Combined Work means the
            object code and/or source code for the Application, including any data
            and utility programs needed for reproducing the Combined Work from the
            Application, but excluding the System Libraries of the Combined Work.</p>

            <h3><a id="section1">1. Exception to Section 3 of the GNU GPL.</a></h3>

            <p>You may convey a covered work under sections 3 and 4 of this License
            without being bound by section 3 of the GNU GPL.</p>

            <h3><a id="section2">2. Conveying Modified Versions.</a></h3>

            <p>If you modify a copy of the Library, and, in your modifications, a
            facility refers to a function or data to be supplied by an Application
            that uses the facility (other than as an argument passed when the
            facility is invoked), then you may convey a copy of the modified
            version:</p>

            <ul>
            <li>a) under this License, provided that you make a good faith effort to
                ensure that, in the event an Application does not supply the
                function or data, the facility still operates, and performs
                whatever part of its purpose remains meaningful, or</li>

            <li>b) under the GNU GPL, with none of the additional permissions of
                this License applicable to that copy.</li>
            </ul>

            <h3><a id="section3">3. Object Code Incorporating Material from Library Header Files.</a></h3>

            <p>The object code form of an Application may incorporate material from
            a header file that is part of the Library.  You may convey such object
            code under terms of your choice, provided that, if the incorporated
            material is not limited to numerical parameters, data structure
            layouts and accessors, or small macros, inline functions and templates
            (ten or fewer lines in length), you do both of the following:</p>

            <ul>
            <li>a) Give prominent notice with each copy of the object code that the
                Library is used in it and that the Library and its use are
                covered by this License.</li>

            <li>b) Accompany the object code with a copy of the GNU GPL and this license
                document.</li>
            </ul>

            <h3><a id="section4">4. Combined Works.</a></h3>

            <p>You may convey a Combined Work under terms of your choice that,
            taken together, effectively do not restrict modification of the
            portions of the Library contained in the Combined Work and reverse
            engineering for debugging such modifications, if you also do each of
            the following:</p>

            <ul>
            <li>a) Give prominent notice with each copy of the Combined Work that
                the Library is used in it and that the Library and its use are
                covered by this License.</li>

            <li>b) Accompany the Combined Work with a copy of the GNU GPL and this license
                document.</li>

            <li>c) For a Combined Work that displays copyright notices during
                execution, include the copyright notice for the Library among
                these notices, as well as a reference directing the user to the
                copies of the GNU GPL and this license document.</li>

            <li>d) Do one of the following:

            <ul>
            <li>0) Convey the Minimal Corresponding Source under the terms of this
                    License, and the Corresponding Application Code in a form
                    suitable for, and under terms that permit, the user to
                    recombine or relink the Application with a modified version of
                    the Linked Version to produce a modified Combined Work, in the
                    manner specified by section 6 of the GNU GPL for conveying
                    Corresponding Source.</li>

            <li>1) Use a suitable shared library mechanism for linking with the
                    Library.  A suitable mechanism is one that (a) uses at run time
                    a copy of the Library already present on the user's computer
                    system, and (b) will operate properly with a modified version
                    of the Library that is interface-compatible with the Linked
                    Version.</li>
            </ul></li>

            <li>e) Provide Installation Information, but only if you would otherwise
                be required to provide such information under section 6 of the
                GNU GPL, and only to the extent that such information is
                necessary to install and execute a modified version of the
                Combined Work produced by recombining or relinking the
                Application with a modified version of the Linked Version. (If
                you use option 4d0, the Installation Information must accompany
                the Minimal Corresponding Source and Corresponding Application
                Code. If you use option 4d1, you must provide the Installation
                Information in the manner specified by section 6 of the GNU GPL
                for conveying Corresponding Source.)</li>
            </ul>

            <h3><a id="section5">5. Combined Libraries.</a></h3>

            <p>You may place library facilities that are a work based on the
            Library side by side in a single library together with other library
            facilities that are not Applications and are not covered by this
            License, and convey such a combined library under terms of your
            choice, if you do both of the following:</p>

            <ul>
            <li>a) Accompany the combined library with a copy of the same work based
                on the Library, uncombined with any other library facilities,
                conveyed under the terms of this License.</li>

            <li>b) Give prominent notice with the combined library that part of it
                is a work based on the Library, and explaining where to find the
                accompanying uncombined form of the same work.</li>
            </ul>

            <h3><a id="section6">6. Revised Versions of the GNU Lesser General Public License.</a></h3>

            <p>The Free Software Foundation may publish revised and/or new versions
            of the GNU Lesser General Public License from time to time. Such new
            versions will be similar in spirit to the present version, but may
            differ in detail to address new problems or concerns.</p>

            <p>Each version is given a distinguishing version number. If the
            Library as you received it specifies that a certain numbered version
            of the GNU Lesser General Public License &ldquo;or any later version&rdquo;
            applies to it, you have the option of following the terms and
            conditions either of that published version or of any later version
            published by the Free Software Foundation. If the Library as you
            received it does not specify a version number of the GNU Lesser
            General Public License, you may choose any version of the GNU Lesser
            General Public License ever published by the Free Software Foundation.</p>

            <p>If the Library as you received it specifies that a proxy can decide
            whether future versions of the GNU Lesser General Public License shall
            apply, that proxy's public statement of acceptance of any version is
            permanent authorization for you to choose that version for the
            Library.</p>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="acorn"></a>acorn License</h1>
          </td>
          <td>
            <p>This license applies to part of the <code>devtools/shared/jsbeautify/src/beautify-js.js</code> file.
            </p>
          </td>
          <td>
            <pre>
            Copyright (C) 2012 by Marijn Haverbeke &lt;marijnh@gmail.com&gt;

            Permission is hereby granted, free of charge, to any person obtaining a copy
            of this software and associated documentation files (the "Software"), to deal
            in the Software without restriction, including without limitation the rights
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
            copies of the Software, and to permit persons to whom the Software is
            furnished to do so, subject to the following conditions:

            The above copyright notice and this permission notice shall be included in
            all copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
            THE SOFTWARE.

            Please note that some subdirectories of the CodeMirror distribution
            include their own LICENSE files, and are released under different
            licences.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="android"></a>Android Open Source License</h1>
          </td>
          <td>
            <p>This license applies to various files in the Mozilla codebase, including those in the directory <code>gfx/skia/</code>.</p>
            <!-- This is the wrong directory, what was intended? -->
          </td>
          <td>
            <pre>
            Copyright 2009, The Android Open Source Project

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:
              * Redistributions of source code must retain the above copyright
                notice, this list of conditions and the following disclaimer.
              * Redistributions in binary form must reproduce the above copyright
                notice, this list of conditions and the following disclaimer in the
                documentation and/or other materials provided with the distribution.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
            EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
            IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
            PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
            CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
            EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
            PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
            PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
            OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="angle"></a>ANGLE License</h1>
          </td>
          <td>
            <p>This license applies to files in the directory <code>gfx/angle/</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (C) 2002-2010 The ANGLE Project Authors.
            All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:

                Redistributions of source code must retain the above copyright
                notice, this list of conditions and the following disclaimer.

                Redistributions in binary form must reproduce the above
                copyright notice, this list of conditions and the following
                disclaimer in the documentation and/or other materials provided
                with the distribution.

                Neither the name of TransGaming Inc., Google Inc., 3DLabs Inc.
                Ltd., nor the names of their contributors may be used to endorse
                or promote products derived from this software without specific
                prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
            FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
            COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
            INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
            BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
            LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
            CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
            LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
            ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
            POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="apache"></a>Apache License 2.0</h1>
          </td>
          <td>
            <p>This license applies to various files in the Mozilla codebase including, but not limited to:</p>
              <ul>
                <li><code>devtools/client/netmonitor/src/components/messages/parsers/signalr/HandshakeProtocol.js</code></li>
                <li><code>devtools/client/netmonitor/src/components/messages/parsers/signalr/IHubProtocol.js</code></li>
                <li><code>devtools/client/netmonitor/src/components/messages/parsers/signalr/JSONHubProtocol.js</code></li>
                <li><code>devtools/client/netmonitor/src/components/messages/parsers/signalr/TextMessageFormat.js</code></li>
                <li><code>devtools/client/netmonitor/src/components/messages/parsers/signalr/Utils.js</code></li>
                <li><code>third_party/cups/include</code></li>
                <li><code>third_party/rust/calendrical_calculations</code></li>
            </ul>
          </td>
          <td>
            <pre>
                                            Apache License
                                      Version 2.0, January 2004
                                    https://www.apache.org/licenses/

              TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

              1. Definitions.

                  "License" shall mean the terms and conditions for use, reproduction,
                  and distribution as defined by Sections 1 through 9 of this document.

                  "Licensor" shall mean the copyright owner or entity authorized by
                  the copyright owner that is granting the License.

                  "Legal Entity" shall mean the union of the acting entity and all
                  other entities that control, are controlled by, or are under common
                  control with that entity. For the purposes of this definition,
                  "control" means (i) the power, direct or indirect, to cause the
                  direction or management of such entity, whether by contract or
                  otherwise, or (ii) ownership of fifty percent (50%) or more of the
                  outstanding shares, or (iii) beneficial ownership of such entity.

                  "You" (or "Your") shall mean an individual or Legal Entity
                  exercising permissions granted by this License.

                  "Source" form shall mean the preferred form for making modifications,
                  including but not limited to software source code, documentation
                  source, and configuration files.

                  "Object" form shall mean any form resulting from mechanical
                  transformation or translation of a Source form, including but
                  not limited to compiled object code, generated documentation,
                  and conversions to other media types.

                  "Work" shall mean the work of authorship, whether in Source or
                  Object form, made available under the License, as indicated by a
                  copyright notice that is included in or attached to the work
                  (an example is provided in the Appendix below).

                  "Derivative Works" shall mean any work, whether in Source or Object
                  form, that is based on (or derived from) the Work and for which the
                  editorial revisions, annotations, elaborations, or other modifications
                  represent, as a whole, an original work of authorship. For the purposes
                  of this License, Derivative Works shall not include works that remain
                  separable from, or merely link (or bind by name) to the interfaces of,
                  the Work and Derivative Works thereof.

                  "Contribution" shall mean any work of authorship, including
                  the original version of the Work and any modifications or additions
                  to that Work or Derivative Works thereof, that is intentionally
                  submitted to Licensor for inclusion in the Work by the copyright owner
                  or by an individual or Legal Entity authorized to submit on behalf of
                  the copyright owner. For the purposes of this definition, "submitted"
                  means any form of electronic, verbal, or written communication sent
                  to the Licensor or its representatives, including but not limited to
                  communication on electronic mailing lists, source code control systems,
                  and issue tracking systems that are managed by, or on behalf of, the
                  Licensor for the purpose of discussing and improving the Work, but
                  excluding communication that is conspicuously marked or otherwise
                  designated in writing by the copyright owner as "Not a Contribution."

                  "Contributor" shall mean Licensor and any individual or Legal Entity
                  on behalf of whom a Contribution has been received by Licensor and
                  subsequently incorporated within the Work.

              2. Grant of Copyright License. Subject to the terms and conditions of
                  this License, each Contributor hereby grants to You a perpetual,
                  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
                  copyright license to reproduce, prepare Derivative Works of,
                  publicly display, publicly perform, sublicense, and distribute the
                  Work and such Derivative Works in Source or Object form.

              3. Grant of Patent License. Subject to the terms and conditions of
                  this License, each Contributor hereby grants to You a perpetual,
                  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
                  (except as stated in this section) patent license to make, have made,
                  use, offer to sell, sell, import, and otherwise transfer the Work,
                  where such license applies only to those patent claims licensable
                  by such Contributor that are necessarily infringed by their
                  Contribution(s) alone or by combination of their Contribution(s)
                  with the Work to which such Contribution(s) was submitted. If You
                  institute patent litigation against any entity (including a
                  cross-claim or counterclaim in a lawsuit) alleging that the Work
                  or a Contribution incorporated within the Work constitutes direct
                  or contributory patent infringement, then any patent licenses
                  granted to You under this License for that Work shall terminate
                  as of the date such litigation is filed.

              4. Redistribution. You may reproduce and distribute copies of the
                  Work or Derivative Works thereof in any medium, with or without
                  modifications, and in Source or Object form, provided that You
                  meet the following conditions:

                  (a) You must give any other recipients of the Work or
                      Derivative Works a copy of this License; and

                  (b) You must cause any modified files to carry prominent notices
                      stating that You changed the files; and

                  (c) You must retain, in the Source form of any Derivative Works
                      that You distribute, all copyright, patent, trademark, and
                      attribution notices from the Source form of the Work,
                      excluding those notices that do not pertain to any part of
                      the Derivative Works; and

                  (d) If the Work includes a "NOTICE" text file as part of its
                      distribution, then any Derivative Works that You distribute must
                      include a readable copy of the attribution notices contained
                      within such NOTICE file, excluding those notices that do not
                      pertain to any part of the Derivative Works, in at least one
                      of the following places: within a NOTICE text file distributed
                      as part of the Derivative Works; within the Source form or
                      documentation, if provided along with the Derivative Works; or,
                      within a display generated by the Derivative Works, if and
                      wherever such third-party notices normally appear. The contents
                      of the NOTICE file are for informational purposes only and
                      do not modify the License. You may add Your own attribution
                      notices within Derivative Works that You distribute, alongside
                      or as an addendum to the NOTICE text from the Work, provided
                      that such additional attribution notices cannot be construed
                      as modifying the License.

                  You may add Your own copyright statement to Your modifications and
                  may provide additional or different license terms and conditions
                  for use, reproduction, or distribution of Your modifications, or
                  for any such Derivative Works as a whole, provided Your use,
                  reproduction, and distribution of the Work otherwise complies with
                  the conditions stated in this License.

              5. Submission of Contributions. Unless You explicitly state otherwise,
                  any Contribution intentionally submitted for inclusion in the Work
                  by You to the Licensor shall be under the terms and conditions of
                  this License, without any additional terms or conditions.
                  Notwithstanding the above, nothing herein shall supersede or modify
                  the terms of any separate license agreement you may have executed
                  with Licensor regarding such Contributions.

              6. Trademarks. This License does not grant permission to use the trade
                  names, trademarks, service marks, or product names of the Licensor,
                  except as required for reasonable and customary use in describing the
                  origin of the Work and reproducing the content of the NOTICE file.

              7. Disclaimer of Warranty. Unless required by applicable law or
                  agreed to in writing, Licensor provides the Work (and each
                  Contributor provides its Contributions) on an "AS IS" BASIS,
                  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
                  implied, including, without limitation, any warranties or conditions
                  of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
                  PARTICULAR PURPOSE. You are solely responsible for determining the
                  appropriateness of using or redistributing the Work and assume any
                  risks associated with Your exercise of permissions under this License.

              8. Limitation of Liability. In no event and under no legal theory,
                  whether in tort (including negligence), contract, or otherwise,
                  unless required by applicable law (such as deliberate and grossly
                  negligent acts) or agreed to in writing, shall any Contributor be
                  liable to You for damages, including any direct, indirect, special,
                  incidental, or consequential damages of any character arising as a
                  result of this License or out of the use or inability to use the
                  Work (including but not limited to damages for loss of goodwill,
                  work stoppage, computer failure or malfunction, or any and all
                  other commercial damages or losses), even if such Contributor
                  has been advised of the possibility of such damages.

              9. Accepting Warranty or Additional Liability. While redistributing
                  the Work or Derivative Works thereof, You may choose to offer,
                  and charge a fee for, acceptance of support, warranty, indemnity,
                  or other liability obligations and/or rights consistent with this
                  License. However, in accepting such obligations, You may act only
                  on Your own behalf and on Your sole responsibility, not on behalf
                  of any other Contributor, and only if You agree to indemnify,
                  defend, and hold each Contributor harmless for any liability
                  incurred by, or claims asserted against, such Contributor by reason
                  of your accepting any such warranty or additional liability.

              END OF TERMS AND CONDITIONS
            </pre>
          </td>
        </tr>


        <tr>
          <td>
            <h1><a id="apple"></a>Apple License</h1>
          </td>
          <td>
            <p>This license applies to parts of the code in:</p>
            <ul>
                <li><code>xpcom/base/DarwinObjectPtr.h</code></li>
            </ul>
            <p>and also some files in these directories:</p>
            <ul>
              <li><code>dom/media/webaudio/blink</code></li>
              <li><code>widget/cocoa</code></li>
            </ul>
          </td>
          <td>
            <pre>
              Copyright (C) 2008, 2009 Apple Inc. All rights reserved.

              Redistribution and use in source and binary forms, with or without
              modification, are permitted provided that the following conditions
              are met:
              1. Redistributions of source code must retain the above copyright
                notice, this list of conditions and the following disclaimer.
              2. Redistributions in binary form must reproduce the above copyright
                notice, this list of conditions and the following disclaimer in the
                documentation and/or other materials provided with the distribution.

              THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
              EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
              IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
              PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
              CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
              EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
              PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
              PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
              OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
              (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
              OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="apple-password-rules-parser"></a>Apple Password Rules Parser License</h1>
          </td>
          <td>
            <p>This license applies to the file <code>toolkit/components/passwordmgr/shared/PasswordRulesParser.sys.mjs</code>.</p>
          </td>
          <td>
            <pre>
            Copyright 2020 Apple Inc.

            Permission is hereby granted, free of charge, to any person obtaining a copy
            of this software and associated documentation files (the "Software"), to deal
            in the Software without restriction, including without limitation the rights
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
            copies of the Software, and to permit persons to whom the Software is
            furnished to do so, subject to the following conditions:

            The above copyright notice and this permission notice shall be included in all
            copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
            SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="arm"></a>ARM License</h1>
          </td>
          <td>
            <p>This license applies to files in the directory <code>js/src/jit/arm64/vixl/</code>.</p>
          </td>
          <td>
            <pre>
            Copyright 2013, ARM Limited
            All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions are met:

              * Redistributions of source code must retain the above copyright notice,
                this list of conditions and the following disclaimer.
              * Redistributions in binary form must reproduce the above copyright notice,
                this list of conditions and the following disclaimer in the documentation
                and/or other materials provided with the distribution.
              * Neither the name of ARM Limited nor the names of its contributors may be
                used to endorse or promote products derived from this software without
                specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
            ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
            WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
            DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
            FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
            DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
            SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
            CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
            OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="babel"></a>Babel License</h1>
          </td>
          <td>
            <p>This license applies to file bundled in <code>devtools/client/debugger/dist</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2014-2017 Sebastian McKenzie <sebmck@gmail.com>

            Permission is hereby granted, free of charge, to any person obtaining
            a copy of this software and associated documentation files (the
            "Software"), to deal in the Software without restriction, including
            without limitation the rights to use, copy, modify, merge, publish,
            distribute, sublicense, and/or sell copies of the Software, and to
            permit persons to whom the Software is furnished to do so, subject to
            the following conditions:

            The above copyright notice and this permission notice shall be
            included in all copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="babylon"></a>Babylon License</h1>
          </td>
          <td>
            <p>This license applies to file bundled in <code>devtools/client/debugger/dist</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (C) 2012-2014 by various contributors (see AUTHORS)

            Permission is hereby granted, free of charge, to any person obtaining a copy
            of this software and associated documentation files (the "Software"), to deal
            in the Software without restriction, including without limitation the rights
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
            copies of the Software, and to permit persons to whom the Software is
            furnished to do so, subject to the following conditions:

            The above copyright notice and this permission notice shall be included in
            all copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
            THE SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="boost"></a>boost License</h1>
          </td>
          <td>
            <p>This license applies to files in the following directories:
            <ul>
                <li><code>third_party/function2</code></li>
                <li><code>third_party/msgpack</code></li>
            </ul>
            See the individual LICENSE files for copyright owners.</p>
          </td>
          <td>
            <pre>
            Boost Software License - Version 1.0 - August 17th, 2003

            Permission is hereby granted, free of charge, to any person or organization
            obtaining a copy of the software and accompanying documentation covered by
            this license (the "Software") to use, reproduce, display, distribute,
            execute, and transmit the Software, and to prepare derivative works of the
            Software, and to permit third-parties to whom the Software is furnished to
            do so, all subject to the following:

            The copyright notices in the Software and this entire statement, including
            the above license grant, this restriction and the following disclaimer,
            must be included in all copies of the Software, in whole or in part, and
            all derivative works of the Software, unless such copies or derivative
            works are solely in the form of machine-executable object code generated by
            a source language processor.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
            FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
            SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
            FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
            ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
            DEALINGS IN THE SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="bsd2clause"></a>BSD 2-Clause License</h1>
          </td>
          <td>
            <p>This license applies to files in the following directories:
            <ul>
                <li><code>third_party/rust/arrayref</code></li>
                <li><code>third_party/rust/mach</code></li>
                <li><code>third_party/rust/qlog</code></li>
            </ul>
            See the individual LICENSE files for copyright owners.</p>
          </td>
          <td>
            <pre>
            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions are
            met:

            1. Redistributions of source code must retain the above copyright
              notice, this list of conditions and the following disclaimer.

            2. Redistributions in binary form must reproduce the above copyright
              notice, this list of conditions and the following disclaimer in the
              documentation and/or other materials provided with the
              distribution.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
            HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
            SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="bsd3clause"></a>BSD 3-Clause License</h1>
          </td>
          <td>
            <p>This license applies to portions of WHATWG specification incorporated into source code and to files in the following directories:
            <ul>
                <li><code>browser/components/newtab/vendor/react-transition-group.js</code></li>
                <li><code>third_party/rust/bindgen/</code></li>
                <li><code>third_party/rust/subtle/</code></li>
                <li><code>third_party/xsimd/</code></li>
                <li><code>third_party/zstd/</code></li>
            </ul>
            See the individual LICENSE files for copyright owners.</p>
          </td>
          <td>
            <pre>
            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions are
            met:

            1. Redistributions of source code must retain the above copyright
              notice, this list of conditions and the following disclaimer.

            2. Redistributions in binary form must reproduce the above copyright
              notice, this list of conditions and the following disclaimer in the
              documentation and/or other materials provided with the distribution.

            3. Neither the name of the copyright holder nor the names of its
              contributors may be used to endorse or promote products derived from
              this software without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
            HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
            SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="bspatch"></a>bspatch License</h1>
          </td>
          <td>
            <p>
              This license applies to the following files:
              <ul>
                <li>
                  <code>toolkit/mozapps/update/updater/bspatch/bspatch.cpp</code>
                </li>
                <li>
                  <code>toolkit/mozapps/update/updater/bspatch/bspatch.h</code>
                </li>
              </ul>
            </p>
          </td>
          <td>
            <pre>
            Copyright 2003,2004 Colin Percival
            All rights reserved

            Redistribution and use in source and binary forms, with or without
            modification, are permitted providing that the following conditions
            are met:
            1. Redistributions of source code must retain the above copyright
              notice, this list of conditions and the following disclaimer.
            2. Redistributions in binary form must reproduce the above copyright
              notice, this list of conditions and the following disclaimer in the
              documentation and/or other materials provided with the distribution.

            THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
            IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
            WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
            ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
            DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
            DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
            OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
            HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
            STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
            IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
            POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="cairo"></a>Cairo Component Licenses</h1>
          </td>
          <td>
            <p>This license, with different copyright holders, applies to certain files in the directory <code>gfx/cairo/</code>.
            The copyright holders and the applicable ranges of dates are as follows:</p>
            <ul>
              <li>2004 Richard D. Worth
              <li>2004, 2005 Red Hat, Inc.
              <li>2003 USC, Information Sciences Institute
              <li>2004 David Reveman
              <li>2005 Novell, Inc.
              <li>2004 David Reveman, Peter Nilsson
              <li>2000 Keith Packard, member of The XFree86 Project, Inc.
              <li>2005 Lars Knoll &amp; Zack Rusin, Trolltech
              <li>1998, 2000, 2002, 2004 Keith Packard
              <li>2004 Nicholas Miell
              <li>2005 Trolltech AS
              <li>2000 SuSE, Inc.
              <li>2003 Carl Worth
              <li>1987, 1988, 1989, 1998 The Open Group
              <li>1987, 1988, 1989 Digital Equipment Corporation, Maynard, Massachusetts.
              <li>1998 Keith Packard
              <li>2003 Richard Henderson
            </ul>
          </td>
          <td>
            <pre>
            Copyright &copy; &lt;date&gt; &lt;copyright holder&gt;

            Permission to use, copy, modify, distribute, and sell this software
            and its documentation for any purpose is hereby granted without
            fee, provided that the above copyright notice appear in all copies
            and that both that copyright notice and this permission notice
            appear in supporting documentation, and that the name of
            &lt;copyright holder&gt; not be used in advertising or publicity pertaining to
            distribution of the software without specific, written prior permission.
            &lt;copyright holder&gt; makes no representations about the suitability of this
            software for any purpose. It is provided "as is" without express or
            implied warranty.

            &lt;COPYRIGHT HOLDER&gt; DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
            INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
            NO EVENT SHALL &lt;COPYRIGHT HOLDER&gt; BE LIABLE FOR ANY SPECIAL, INDIRECT OR
            CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
            OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
            NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
            WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="chromium"></a>Chromium License</h1>
          </td>
          <td>
            <p>This license applies to parts of the code in:</p>
            <ul>
                <li><code>browser/extensions/formautofill</code></li>
                <li><code>toolkit/components/formautofill/shared/FormAutofillHeuristics.sys.mjs</code></li>
                <li><code>toolkit/components/formautofill/shared/FormAutofillNameUtils.sys.mjs</code></li>
                <li><code>editor/libeditor/EditorEventListener.cpp</code></li>
                <li><code>security/sandbox/</code></li>
                <li><code>toolkit/components/passwordmgr/shared/PasswordGenerator.sys.mjs</code></li>
                <li><code>widget/cocoa/GfxInfo.mm</code></li>
                <li><code>widget/windows/nsWindow.cpp</code></li>
            </ul>
            <p>and also some files in these directories:</p>
            <ul>
                <li><code>dom/media/webspeech/recognition/</code></li>
                <li><code>dom/plugins/</code></li>
                <li><code>gfx/ots/</code></li>
                <li><code>gfx/ycbcr/</code></li>
                <li><code>ipc/chromium/</code></li>
                <li><code>toolkit/components/reputationservice/</code></li>
                <li><code>toolkit/components/url-classifier/chromium/</code></li>
                <li><code>tools/profiler/</code></li>
            </ul>
          </td>
          <td>
            <pre>
            Copyright (c) 2006-2018 The Chromium Authors. All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions are
            met:

              * Redistributions of source code must retain the above copyright
            notice, this list of conditions and the following disclaimer.
              * Redistributions in binary form must reproduce the above
            copyright notice, this list of conditions and the following disclaimer
            in the documentation and/or other materials provided with the
            distribution.
              * Neither the name of Google Inc. nor the names of its
            contributors may be used to endorse or promote products derived from
            this software without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
            OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
            SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="codemirror"></a>CodeMirror License</h1>
          </td>
          <td>
            <p>This license applies to all files in <code>devtools/client/shared/sourceeditor/codemirror</code> and to the following files:</p>
            <ul>
              <li><code>devtools/client/shared/sourceeditor/test/cm_mode_ruby.js</code></li>
              <li><code>devtools/client/shared/sourceeditor/test/codemirror/mode/javascript/test.js</code></li>
              <li><code>devtools/client/shared/sourceeditor/test/codemirror/comment_test.js</code></li>
              <li><code>devtools/client/shared/sourceeditor/test/codemirror/driver.js</code></li>
              <li><code>devtools/client/shared/sourceeditor/test/codemirror/mode_test.css</code></li>
              <li><code>devtools/client/shared/sourceeditor/test/codemirror/mode_test.js</code></li>
              <li><code>devtools/client/shared/sourceeditor/test/codemirror/test.js</code></li>
            </ul>
          </td>
          <td>
            <pre>
            Copyright (C) 2013 by Marijn Haverbeke &lt;marijnh@gmail.com&gt;

            Permission is hereby granted, free of charge, to any person obtaining a copy
            of this software and associated documentation files (the "Software"), to deal
            in the Software without restriction, including without limitation the rights
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
            copies of the Software, and to permit persons to whom the Software is
            furnished to do so, subject to the following conditions:

            The above copyright notice and this permission notice shall be included in
            all copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
            THE SOFTWARE.

            Please note that some subdirectories of the CodeMirror distribution
            include their own LICENSE files, and are released under different
            licences.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="cryptogams"></a>CRYPTOGAMS License</h1>
          </td>
          <td>
            <p>This license applies all files in <code>security/nss/lib/freebl/scripts/</code>
            and to the file <code>security/nss/lib/freebl/sha512-p8.s</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2006, CRYPTOGAMS by &lt;appro@openssl.org&gt;
            All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:

                  *  Redistributions of source code must retain copyright notices,
            this list of conditions and the following disclaimer.

                  *  Redistributions in binary form must reproduce the above
            copyright notice, this list of conditions and the following
            disclaimer in the documentation and/or other materials
            provided with the distribution.

                  *  Neither the name of the CRYPTOGAMS nor the names of its
            copyright holder and contributors may be used to endorse or
            promote products derived from this software without specific
            prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS
            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
            OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
            SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="cubic-bezier"></a>cubic-bezier License</h1>
          </td>
          <td>
            <p>This license applies to the file <code>devtools/client/shared/widgets/CubicBezierWidget.js </code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2013 Lea Verou. All rights reserved.

            Permission is hereby granted, free of charge, to any person obtaining a
            copy of this software and associated documentation files (the "Software"),
            to deal in the Software without restriction, including without limitation
            the rights to use, copy, modify, merge, publish, distribute, sublicense,
            and/or sell copies of the Software, and to permit persons to whom the
            Software is furnished to do so, subject to the following conditions:

            The above copyright notice and this permission notice shall be included in
            all copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
            FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
            DEALINGS IN THE SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="d3"></a>D3 License</h1>
          </td>
          <td>
            <p>This license applies to the file <code>third_party/js/d3/d3.js</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2010-2016, Michael Bostock
            All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions are met:

            * Redistributions of source code must retain the above copyright notice, this
              list of conditions and the following disclaimer.

            * Redistributions in binary form must reproduce the above copyright notice,
              this list of conditions and the following disclaimer in the documentation
              and/or other materials provided with the distribution.

            * The name Michael Bostock may not be used to endorse or promote products
              derived from this software without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
            AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
            IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
            DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
            INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
            BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
            OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
            NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
            EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="dagre-d3"></a>Dagre-D3 License</h1>
          </td>
          <td>
            <p>This license applies to the file <code>devtools/client/shared/vendor/dagre-d3.js</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2013 Chris Pettitt

            Permission is hereby granted, free of charge, to any person obtaining a copy
            of this software and associated documentation files (the "Software"), to deal
            in the Software without restriction, including without limitation the rights
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
            copies of the Software, and to permit persons to whom the Software is
            furnished to do so, subject to the following conditions:

            The above copyright notice and this permission notice shall be included in
            all copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
            THE SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="diff"></a>diff License</h1>
          </td>
          <td>
            <p>This license applies to the file <code>devtools/client/inspector/markup/test/helper_diff.js</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2014 Slava

            Permission is hereby granted, free of charge, to any person obtaining a copy
            of this software and associated documentation files (the "Software"), to deal
            in the Software without restriction, including without limitation the rights
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
            copies of the Software, and to permit persons to whom the Software is
            furnished to do so, subject to the following conditions:

            The above copyright notice and this permission notice shall be included in
            all copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
            THE SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="disconnect.me"></a>Disconnect.Me License - Creative Commons BY-NC-SA-4.0</h1>
          </td>
          <td>
            <p>This license does not apply to any of the code shipped with Firefox,
            but may apply to blocklists downloaded after installation for use with
            the tracking protection feature. Our blocklist is based on one originally
            written by Disconnect.me that is provided to the Mozilla Corporation for use
            in Firefox pursuant to a contract between Mozilla and Disconnect.me. For
            use outside Firefox, the blocklist is licensed under the Creative Commons
            Attribution-NonCommercial-ShareAlike 4.0 International License.</p>
          </td>
          <td>
            <pre>
            The Creative Commons' page for that license is at <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/">https://creativecommons.org/licenses/by-nc-sa/4.0/</a>
            and full license text is available at <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode">https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode</a>.</p>
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="dtoa"></a>dtoa License</h1>
          </td>
          <td>
            <p>This license applies to the file <code>nsprpub/pr/src/misc/dtoa.c</code>.</p>
          </td>
          <td>
            <pre>
            The author of this software is David M. Gay.

            Copyright (c) 1991, 2000, 2001 by Lucent Technologies.

            Permission to use, copy, modify, and distribute this software for any
            purpose without fee is hereby granted, provided that this entire notice
            is included in all copies of any software which is or includes a copy
            or modification of this software and in all copies of the supporting
            documentation for such software.

            THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
            WARRANTY.  IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY
            REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
            OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="hunspell-nl"></a>Dutch Spellchecking Dictionary License</h1>
          </td>
          <td>
            <p>This license applies to the Dutch Spellchecking Dictionary. (This code only ships in some localized versions of this product.)</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2006, 2007 OpenTaal
            Copyright (c) 2001, 2002, 2003, 2005 Simon Brouwer e.a.
            Copyright (c) 1996 Nederlandstalige Tex Gebruikersgroep

            All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions are met:

            * Redistributions of source code must retain the above copyright notice, this
            list of conditions and the following disclaimer.
            * Redistributions in binary form must reproduce the above copyright notice,
            this list of conditions and the following disclaimer in the documentation
            and/or other materials provided with the distribution.
            * Neither the name of the OpenTaal, Simon Brouwer e.a., or Nederlandstalige Tex
            Gebruikersgroep nor the names of its contributors may be used to endorse or
            promote products derived from this software without specific prior written
            permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
            CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
            EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
            PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
            PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
            LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
            NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
            SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="hunspell-en"></a>English Spellchecking Dictionary Licenses</h1>
          </td>
          <td>
            <p>These licenses apply to certain files in the directory <code>extensions/spellcheck/locales/en-US/hunspell/</code>,
            and the Canadian English dictionary. (This code only ships in some localized versions of this product.)</p>
          </td>
          <td>
            <pre>
            Different parts of the SCOWL English dictionaries are subject to the
            following licenses as shown below. For additional details, sources,
            credits, and public domain references, see <a href="https://searchfox.org/mozilla-central/source/extensions/spellcheck/locales/en-US/hunspell/README_en_US.txt">README.txt</a>.

            The collective work of the Spell Checking Oriented Word Lists (SCOWL) is under
            the  following copyright:

            Copyright 2000-2007 by Kevin Atkinson
            Permission to use, copy, modify, distribute and sell these word lists, the
            associated scripts, the output created from the scripts, and its documentation
            for any purpose is hereby granted without fee, provided that the above
            copyright notice appears in all copies and that both that copyright notice and
            this permission notice appear in supporting documentation. Kevin Atkinson makes
            no representations about the suitability of this array for any purpose. It is
            provided  "as is" without express or implied warranty.

            The WordNet database is under the following copyright:

            This software and database is being provided to you, the LICENSEE, by Princeton
            University under the following license.  By obtaining, using and/or copying
            this software and database, you agree that you have read, understood, and will
            comply with these terms and conditions:
            Permission to use, copy, modify and distribute this software and database and
            its documentation for any purpose and without fee or royalty is hereby granted,
            provided that you agree to comply with the following copyright notice and
            statements, including the disclaimer, and that the same appear on ALL copies of
            the software, database and documentation, including modifications that you make
            for internal use or for distribution.
            WordNet 1.6 Copyright 1997 by Princeton University.  All rights reserved.
            THIS SOFTWARE AND DATABASE IS PROVIDED "AS IS" AND PRINCETON UNIVERSITY
            MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED.  BY WAY OF
            EXAMPLE, BUT NOT LIMITATION, PRINCETON UNIVERSITY MAKES NO
            REPRESENTATIONS OR WARRANTIES OF MERCHANT- ABILITY OR FITNESS FOR ANY
            PARTICULAR PURPOSE OR THAT THE USE OF THE LICENSED SOFTWARE, DATABASE OR
            DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS,
            TRADEMARKS OR OTHER RIGHTS.
            The name of Princeton University or Princeton may not be used in advertising or
            publicity pertaining to distribution of the software and/or database.  Title to
            copyright in this software, database and any associated documentation shall at
            all times remain with Princeton University and LICENSEE agrees to preserve same.

            The "UK Advanced Cryptics Dictionary" is under the following copyright:

            Copyright (c) J Ross Beresford 1993-1999. All Rights Reserved.
            The following restriction is placed on the use of this publication: if The UK
            Advanced Cryptics Dictionary is used in a software package or redistributed in
            any form, the copyright notice must be prominently displayed and the text of
            this document must be included verbatim.   There are no other restrictions: I
            would like to see the list distributed as widely as possible.

            Various parts are under the Ispell copyright:

            Copyright 1993, Geoff Kuenning, Granada Hills, CA
            All rights reserved.   Redistribution and use in source and binary forms, with
            or without modification, are permitted provided that the following conditions
            are met:
              1. Redistributions of source code must retain the above copyright notice,
            this list of conditions and the following disclaimer.
              2. Redistributions in binary form must reproduce the above copyright notice,
            this list of conditions and the following disclaimer in the documentation
            and/or other materials provided with the distribution.
              3. All modifications to the source code must be clearly marked as such.
            Binary redistributions based on modified source code must be clearly marked as
            modified versions in the documentation and/or other materials provided with
            the distribution.
              (clause 4 removed with permission from Geoff Kuenning)
              5. The name of Geoff Kuenning may not be used to endorse or promote products
            derived from this software without specific prior written permission.
            THIS SOFTWARE IS PROVIDED BY GEOFF KUENNING AND CONTRIBUTORS ``AS  IS'' AND
            ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
            IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
            PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL GEOFF KUENNING OR CONTRIBUTORS
            BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
            CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
            SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
            INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
            CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
            ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
            POSSIBILITY OF SUCH DAMAGE.

            Additional Contributors:

            Alan Beale &lt;biljir@pobox.com&gt;
            M Cooper &lt;thegrendel@theriver.com&gt;
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="hunspell-ee"></a>Estonian Spellchecking Dictionary License</h1>
          </td>
          <td>
            <p>This license applies to precursor works to certain files which are part of the Estonian Spellchecking Dictionary. The shipped
            versions are under the GNU Lesser General Public License. (This code only ships in some localized versions of this product.)</p>
          </td>
          <td>
            <pre>
            Copyright © Institute of the Estonian Language

            E-mail: litsents@eki.ee
            URL: https://www.eki.ee/tarkvara/

            The present Licence Agreement gives the user of this Software Product
            (hereinafter: Product) the right to use the Product for whatever purpose
            (incl. distribution, copying, altering, inclusion in other software, and
            selling) on the following conditions:

            1. The present Licence Agreement should belong unaltered to each copy ever
              made of this Product;
            2. Neither the Institute of the Estonian Language (hereinafter: IEL) nor the
              author(s) of the Product will take responsibility for any detriment, direct
              or indirect, possibly ensuing from the application of the Product;
            3. The IEL is ready to share the Product with other users as we wish to
              advance research on the Estonian language and to promote the use of
              Estonian in rapidly developing infotechnology, yet we refuse to bind
              ourselves to any further obligation, which means that the IEL is not
              obliged either to warrant the suitability of the Product for a specific
              purpose, to improve the software, or to provide a more detailed description
              of the underlying algorithms. (Which does not mean, though, that we may not
              do it.)

            Notification Request:

            As a courtesy, we would appreciate being informed whenever our linguistic
            products are used to create derivative works. If you modify our software or
            include it in other products, please inform us by sending e-mail to
            litsents@eki.ee or by letter to

            Institute of the Estonian Language
            Roosikrantsi 6
            10119 Tallinn
            ESTONIA

            Phone &amp; Fax: +372 6411443
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="expat"></a>Expat License</h1>
          </td>
          <td>
            <p>This license applies to certain files in the directory <code>parser/expat/</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 1998, 1999, 2000 Thai Open Source Software Center Ltd
                                          and Clark Cooper
            Copyright (c) 2001, 2002, 2003 Expat maintainers.

            Permission is hereby granted, free of charge, to any person obtaining
            a copy of this software and associated documentation files (the
            "Software"), to deal in the Software without restriction, including
            without limitation the rights to use, copy, modify, merge, publish,
            distribute, sublicense, and/or sell copies of the Software, and to
            permit persons to whom the Software is furnished to do so, subject to
            the following conditions:

            The above copyright notice and this permission notice shall be included
            in all copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
            IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
            CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
            TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
            SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="firebug"></a>Firebug License</h1>
          </td>
          <td>
            <p>This license applies to the code <code>devtools/shared/network-observer/NetworkHelper.sys.mjs</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2007, Parakey Inc.
            All rights reserved.

            Redistribution and use of this software in source and binary forms, with or
            without modification, are permitted provided that the following conditions are
            met:

            * Redistributions of source code must retain the above
              copyright notice, this list of conditions and the
              following disclaimer.

            * Redistributions in binary form must reproduce the above
              copyright notice, this list of conditions and the
              following disclaimer in the documentation and/or other
              materials provided with the distribution.

            * Neither the name of Parakey Inc. nor the names of its
              contributors may be used to endorse or promote products
              derived from this software without specific prior
              written permission of Parakey Inc.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
            AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
            IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
            DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
            FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
            DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
            SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
            CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
            OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="gfx-font-list"></a>gfxFontList License</h1>
          </td>
          <td>
            <p>This license applies to the following files:</p>
              <ul>
                <li><code>gfx/thebes/gfxMacPlatformFontList.mm</code></li>
                <li><code>gfx/thebes/gfxPlatformFontList.cpp</code></li>
              </ul>
            </p>
          </td>
          <td>
            <pre>
            Copyright (C) 2006 Apple Computer, Inc.  All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:

            1.  Redistributions of source code must retain the above copyright
                notice, this list of conditions and the following disclaimer.
            2.  Redistributions in binary form must reproduce the above copyright
                notice, this list of conditions and the following disclaimer in the
                documentation and/or other materials provided with the distribution.
            3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
                its contributors may be used to endorse or promote products derived
                from this software without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
            EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
            WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
            DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
            DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
            (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
            LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
            ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
            THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="google-bsd"></a>Google BSD License</h1>
          </td>
          <td>
            <p>This license applies to files in the following directories:</p>
            <ul>
              <li><code>toolkit/crashreporter/google-breakpad/</code></li>
              <li><code>toolkit/components/protobuf/</code></li>
              <li><code>devtools/client/netmonitor/src/utils/filter-text-utils.js</code></li>
            </ul>
          </td>
          <td>
            <pre>
            Copyright (c) 2006, Google Inc.
            All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions are
            met:

                * Redistributions of source code must retain the above copyright
            notice, this list of conditions and the following disclaimer.
                * Redistributions in binary form must reproduce the above
            copyright notice, this list of conditions and the following disclaimer
            in the documentation and/or other materials provided with the
            distribution.
                * Neither the name of Google Inc. nor the names of its
            contributors may be used to endorse or promote products derived from
            this software without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
            OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
            SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="gears-istumbler"></a>Google Gears/iStumbler License</h1>
          </td>
          <td>
            <p>This license applies to the file <code>netwerk/wifi/mac/Wifi.h</code>.</p>
          </td>
          <td>
            <pre>
              Copyright 2008, Google Inc.

              Redistribution and use in source and binary forms, with or without
              modification, are permitted provided that the following conditions are met:

              1. Redistributions of source code must retain the above copyright notice,
                  this list of conditions and the following disclaimer.
              2. Redistributions in binary form must reproduce the above copyright notice,
                  this list of conditions and the following disclaimer in the documentation
                  and/or other materials provided with the distribution.
              3. Neither the name of Google Inc. nor the names of its contributors may be
                  used to endorse or promote products derived from this software without
                  specific prior written permission.

              THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
              WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
              MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
              EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
              SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
              PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
              OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
              WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
              OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
              ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

              The contents of this file are taken from Apple80211.h from the iStumbler
              project (https://istumbler.net/). This project is released under the BSD
              license with the following restrictions.

              Copyright (c) 02006, Alf Watt (alf@istumbler.net). All rights reserved.

              Redistribution and use in source and binary forms, with or without
              modification, are permitted provided that the following conditions
              are met:

              * Redistributions of source code must retain the above copyright
                notice, this list of conditions and the following disclaimer.

              * Redistributions in binary form must reproduce the above copyright
                notice, this list of conditions and the following disclaimer in the
                documentation and/or other materials provided with the distribution.

              * Neither the name of iStumbler nor the names of its contributors may be
                used to endorse or promote products derived from this software without
                specific prior written permission.

              THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
              IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
              TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
              PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
              OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
              EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
              PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
              PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
              LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
              NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
              SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
              </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="vp8"></a>Google VP8 License</h1>
          </td>
          <td>
            <p>This license applies to certain files in the directory <code>media/libvpx</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2010, Google, Inc.

            All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:

            - Redistributions of source code must retain the above copyright
              notice, this list of conditions and the following disclaimer.

            - Redistributions in binary form must reproduce the above
              copyright notice, this list of conditions and the following
              disclaimer in the documentation and/or other materials provided
              with the distribution.

            - Neither the name of Google nor the names of its contributors may
              be used to endorse or promote products derived from this software
              without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
            HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
            SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

            Subject to the terms and conditions of the above License, Google
            hereby grants to You a perpetual, worldwide, non-exclusive,
            no-charge, royalty-free, irrevocable (except as stated in this
            section) patent license to make, have made, use, offer to sell, sell,
            import, and otherwise transfer this implementation of VP8, where such
            license applies only to those patent claims, both currently owned by
            Google and acquired in the future, licensable by Google that are
            necessarily infringed by this implementation of VP8. If You or your
            agent or exclusive licensee institute or order or agree to the
            institution of patent litigation against any entity (including a
            cross-claim or counterclaim in a lawsuit) alleging that this
            implementation of VP8 or any code incorporated within this
            implementation of VP8 constitutes direct or contributory patent
            infringement, or inducement of patent infringement, then any rights
            granted to You under this License for this implementation of VP8
            shall terminate as of the date such litigation is filed.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="gyp"></a>gyp License</h1>
          </td>
          <td>
            <p>This license applies to certain files in the directory <code>third_party/python/gyp</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2009 Google Inc. All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions are
            met:

              * Redistributions of source code must retain the above copyright
            notice, this list of conditions and the following disclaimer.
              * Redistributions in binary form must reproduce the above
            copyright notice, this list of conditions and the following disclaimer
            in the documentation and/or other materials provided with the
            distribution.
              * Neither the name of Google Inc. nor the names of its
            contributors may be used to endorse or promote products derived from
            this software without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
            OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
            SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="halloc"></a>halloc License</h1>
          </td>
          <td>
            <p>This license applies to certain files in the directory <code>media/libnestegg/src</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2004-2010 Alex Pankratov. All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions are met:

                * Redistributions of source code must retain the above copyright notice,
                  this list of conditions and the following disclaimer.
                * Redistributions in binary form must reproduce the above copyright notice,
                  this list of conditions and the following disclaimer in the documentation
                  and/or other materials provided with the distribution.
                * Neither the name of the project nor the names of its contributors may be
                  used to endorse or promote products derived from this software without
                  specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
            ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
            WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
            DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
            FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
            DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
            SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
            CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
            OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="harfbuzz"></a>HarfBuzz License</h1>
          </td>
          <td>
            <p>This license, with different copyright holders, applies to the files in the directory <code>gfx/harfbuzz/</code>.
            The copyright holders and the applicable ranges of dates are as follows:</p>
            <ul>
                <li>1998-2004  David Turner and Werner Lemberg</li>
                <li>2004, 2007, 2008, 2009, 2010  Red Hat, Inc.</li>
                <li>2006  Behdad Esfahbod</li>
                <li>2007  Chris Wilson</li>
                <li>2009  Keith Stribley &lt;devel@thanlwinsoft.org&gt;</li>
                <li>2010  Mozilla Foundation</li>
            </ul>
          </td>
          <td>
            <pre>
            Copyright (C) &lt;date&gt; &lt;copyright holder&gt;

            This is part of HarfBuzz, an OpenType Layout engine library.

            Permission is hereby granted, without written agreement and without
            license or royalty fees, to use, copy, modify, and distribute this
            software and its documentation for any purpose, provided that the
            above copyright notice and the following two paragraphs appear in
            all copies of this software.

            IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
            DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
            ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
            IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
            DAMAGE.

            THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
            BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
            FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
            ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
            PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="icu"></a>ICU License</h1>
          </td>
          <td>
            <p>This license applies to some code in the <code>gfx/thebes</code> directory.</p>
          </td>
          <td>
            <pre>
            ICU License - ICU 1.8.1 and later

            COPYRIGHT AND PERMISSION NOTICE

            Copyright (c) 1995-2012 International Business Machines Corporation and
            others

            All rights reserved.

            Permission is hereby granted, free of charge, to any person obtaining a
            copy of this software and associated documentation files (the "Software"),
            to deal in the Software without restriction, including without limitation
            the rights to use, copy, modify, merge, publish, distribute, and/or sell
            copies of the Software, and to permit persons to whom the Software is
            furnished to do so, provided that the above copyright notice(s) and this
            permission notice appear in all copies of the Software and that both the
            above copyright notice(s) and this permission notice appear in supporting
            documentation.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
            IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
            BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
            OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
            WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
            ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
            SOFTWARE.

            Except as contained in this notice, the name of a copyright holder shall
            not be used in advertising or otherwise to promote the sale, use or other
            dealings in this Software without prior written authorization of the
            copyright holder.
            All trademarks and registered trademarks mentioned herein are the property
            of their respective owners.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="immutable"></a>Immutable.js License</h1>
          </td>
          <td>
            <p>N/A</p>
          </td>
          <td>
            <pre>
            BSD License

            For Immutable JS software

            Copyright (c) 2014-2015, Facebook, Inc. All rights reserved.

            Redistribution and use in source and binary forms, with or without modification,
            are permitted provided that the following conditions are met:

            * Redistributions of source code must retain the above copyright notice, this
              list of conditions and the following disclaimer.

            * Redistributions in binary form must reproduce the above copyright notice,
              this list of conditions and the following disclaimer in the documentation
              and/or other materials provided with the distribution.

            * Neither the name Facebook nor the names of its contributors may be used to
              endorse or promote products derived from this software without specific
              prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
            ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
            WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
            DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
            ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
            (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
            LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
            ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
            SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>
        <hr>

        <tr>
          <td>
            <h1><a id="jpnic"></a>Japan Network Information Center License</h1>
          </td>
          <td>
            <p>This license applies to certain files in the directory <code>netwerk/dns/</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2001,2002 Japan Network Information Center.
            All rights reserved.

            By using this file, you agree to the terms and conditions set forth below.

                LICENSE TERMS AND CONDITIONS

            The following License Terms and Conditions apply, unless a different
            license is obtained from Japan Network Information Center ("JPNIC"),
            a Japanese association, Kokusai-Kougyou-Kanda Bldg 6F, 2-3-4 Uchi-Kanda,
            Chiyoda-ku, Tokyo 101-0047, Japan.

            1. Use, Modification and Redistribution (including distribution of any
              modified or derived work) in source and/or binary forms is permitted
              under this License Terms and Conditions.

            2. Redistribution of source code must retain the copyright notices as they
              appear in each source code file, this License Terms and Conditions.

            3. Redistribution in binary form must reproduce the Copyright Notice,
              this License Terms and Conditions, in the documentation and/or other
              materials provided with the distribution.  For the purposes of binary
              distribution the "Copyright Notice" refers to the following language:
              "Copyright (c) 2000-2002 Japan Network Information Center. All rights
              reserved."

            4. The name of JPNIC may not be used to endorse or promote products
              derived from this Software without specific prior written approval of
              JPNIC.

            5. Disclaimer/Limitation of Liability: THIS SOFTWARE IS PROVIDED BY JPNIC
              "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
              LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
              PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL JPNIC BE LIABLE
              FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
              CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
              SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
              BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
              WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
              OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
              ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="jemalloc"></a>jemalloc License</h1>
          </td>
          <td>
            <p>This license applies to portions of the files in the directory <code>memory/build/</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (C) 2006-2008 Jason Evans &lt;jasone@canonware.com&gt;.
            All rights reserved.
            Copyright (C) 2007-2017 Mozilla Foundation.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:
            1. Redistributions of source code must retain the above copyright
                notice(s), this list of conditions and the following disclaimer as
                the first lines of this file unmodified other than the possible
                addition of one or more copyright notices.
            2. Redistributions in binary form must reproduce the above copyright
                notice(s), this list of conditions and the following disclaimer in
                the documentation and/or other materials provided with the
                distribution.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY
            EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
            IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
            PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE
            LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
            CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
            SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
            BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
            WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
            OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
            EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="jszip"></a>JSZip License</h1>
          </td>
          <td>
            <p>This license applies to the file <code>devtools/client/shared/vendor/jszip.js</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2009-2016 Stuart Knightley, David Duponchel, Franz Buchinger, António Afonso

            Permission is hereby granted, free of charge, to any person obtaining a copy
            of this software and associated documentation files (the "Software"), to deal
            in the Software without restriction, including without limitation the rights
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
            copies of the Software, and to permit persons to whom the Software is
            furnished to do so, subject to the following conditions:

            The above copyright notice and this permission notice shall be included in
            all copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
            THE SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="jquery"></a>jQuery License</h1>
          </td>
          <td>
            <p>This license applies to all copies of jQuery in the code.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2010 John Resig, https://jquery.com/

            Permission is hereby granted, free of charge, to any person obtaining
            a copy of this software and associated documentation files (the
            "Software"), to deal in the Software without restriction, including
            without limitation the rights to use, copy, modify, merge, publish,
            distribute, sublicense, and/or sell copies of the Software, and to
            permit persons to whom the Software is furnished to do so, subject to
            the following conditions:

            The above copyright notice and this permission notice shall be
            included in all copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="k_exp"></a>k_exp License</h1>
          </td>
          <td>
            <p>This license applies to the file <code>modules/fdlibm/src/k_exp.cpp</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2011 David Schultz &lt;das@FreeBSD.ORG&gt;
            All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:
            1. Redistributions of source code must retain the above copyright
              notice, this list of conditions and the following disclaimer.
            2. Redistributions in binary form must reproduce the above copyright
              notice, this list of conditions and the following disclaimer in the
              documentation and/or other materials provided with the distribution.

            THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
            ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
            IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
            ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
            FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
            DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
            OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
            HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
            LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
            OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
            SUCH DAMAGE.
            </pre>
          </td>
        </tr>


        <tr>
          <td>
            <h1><a id="libcubeb"></a>libcubeb License</h1>
          </td>
          <td>
            <p class="correctme">This license applies to files in the directory <code>media/libcubeb</code>.</p>
          </td>
          <td>
            <pre>
            Copyright &copy; 2011 Mozilla Foundation

            Permission to use, copy, modify, and distribute this software for any
            purpose with or without fee is hereby granted, provided that the above
            copyright notice and this permission notice appear in all copies.

            THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
            WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
            MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
            ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
            WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
            ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
            OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="libevent"></a>libevent License</h1>
          </td>
          <td>
            <p>This license applies to files in the directory <code>ipc/chromium/src/third_party/libevent/</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2000-2007 Niels Provos &lt;provos@citi.umich.edu&gt;
            Copyright (c) 2007-2012 Niels Provos and Nick Mathewson

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:
            1. Redistributions of source code must retain the above copyright
              notice, this list of conditions and the following disclaimer.
            2. Redistributions in binary form must reproduce the above copyright
              notice, this list of conditions and the following disclaimer in the
              documentation and/or other materials provided with the distribution.
            3. The name of the author may not be used to endorse or promote products
              derived from this software without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
            IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
            OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
            IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
            INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
            NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
            THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

            ==============================

            Portions of Libevent are based on works by others, also made available by
            them under the three-clause BSD license above.  The copyright notices are
            available in the corresponding source files; the license is as above.  Here's
            a list:

            log.c:
              Copyright (c) 2000 Dug Song &lt;dugsong@monkey.org&gt;
              Copyright (c) 1993 The Regents of the University of California.

            strlcpy.c:
              Copyright (c) 1998 Todd C. Miller &lt;Todd.Miller@courtesan.com&gt;

            evport.c:
              Copyright (c) 2007 Sun Microsystems

            ht-internal.h:
              Copyright (c) 2002 Christopher Clark

            minheap-internal.h:
              Copyright (c) 2006 Maxim Yegorushkin &lt;maxim.yegorushkin@gmail.com&gt;

            ==============================

            The arc4module is available under the following, sometimes called the
            "OpenBSD" license:

              Copyright (c) 1996, David Mazieres &lt;dm@uun.org&gt;
              Copyright (c) 2008, Damien Miller &lt;djm@openbsd.org&gt;

              Permission to use, copy, modify, and distribute this software for any
              purpose with or without fee is hereby granted, provided that the above
              copyright notice and this permission notice appear in all copies.

              THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
              WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
              MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
              ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
              WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
              ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
              OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="libffi"></a>libffi License</h1>
          </td>
          <td>
            <p>This license applies to files in the directory <code>js/src/ctypes/libffi/</code>.</p>
          </td>
          <td>
            <pre>
            libffi - Copyright (c) 1996-2008 Red Hat, Inc and others.
            See source files for details.

            Permission is hereby granted, free of charge, to any person obtaining
            a copy of this software and associated documentation files (the
            ``Software''), to deal in the Software without restriction, including
            without limitation the rights to use, copy, modify, merge, publish,
            distribute, sublicense, and/or sell copies of the Software, and to
            permit persons to whom the Software is furnished to do so, subject to
            the following conditions:

            The above copyright notice and this permission notice shall be included
            in all copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND,
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
            IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
            CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
            TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
            SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="libjingle"></a>libjingle License</h1>
          </td>
          <td>
            <p>This license applies to the following files:</p>
            <ul>
              <li><code>dom/media/webrtc/transport/sigslot.h</code></li>
              <li><code>dom/media/webrtc/transport/test/gtest_utils.h</code></li>
            </ul>
          </td>
          <td>
            <pre>
            Copyright (c) 2004--2005, Google Inc.
            All rights reserved.

            Redistribution and use in source and binary forms, with or without modification,
            are permitted provided that the following conditions are met:

                * Redistributions of source code must retain the above copyright notice,
                  this list of conditions and the following disclaimer.
                * Redistributions in binary form must reproduce the above copyright notice,
                  this list of conditions and the following disclaimer in the documentation
                  and/or other materials provided with the distribution.
                * The name of the author may not be used to endorse or promote products
                  derived from this software without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
            AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
            IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
            ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
            LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
            CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
            GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
            HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
            STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
            WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
            SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="libnestegg"></a>libnestegg License</h1>
          </td>
          <td>
            <p>This license applies to certain files in the directory <code>media/libnestegg</code>.</p>
          </td>
          <td>
            <pre>
            Copyright &copy; 2010 Mozilla Foundation

            Permission to use, copy, modify, and distribute this software for any
            purpose with or without fee is hereby granted, provided that the above
            copyright notice and this permission notice appear in all copies.

            THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
            WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
            MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
            ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
            WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
            ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
            OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="libsoundtouch"></a>libsoundtouch License</h1>
          </td>
          <td>
            <p>This license applies to certain files in the directory <code>media/libsoundtouch/src/</code>.
            </p>
          </td>
          <td>
            <pre>
            The SoundTouch Library Copyright &copy; Olli Parviainen 2001-2012

            This library is free software; you can redistribute it and/or
            modify it under the terms of the GNU Lesser General Public
            License as published by the Free Software Foundation; either
            version 2.1 of the License, or (at your option) any later version.

            This library is distributed in the hope that it will be useful,
            but WITHOUT ANY WARRANTY; without even the implied warranty of
            MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
            Lesser General Public License for more details.

            You should have received a copy of the GNU Lesser General Public
            License along with this library; if not, write to the Free Software
            Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="libyuv"></a>libyuv License</h1>
          </td>
          <td>
            <p>This license applies to files in the directory <code>media/libyuv</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2011, The LibYuv project authors. All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions are
            met:

              * Redistributions of source code must retain the above copyright
                notice, this list of conditions and the following disclaimer.

              * Redistributions in binary form must reproduce the above copyright
                notice, this list of conditions and the following disclaimer in
                the documentation and/or other materials provided with the
                distribution.

              * Neither the name of Google nor the names of its contributors may
                be used to endorse or promote products derived from this software
                without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
            HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
            SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="hunspell-lt"></a>Lithuanian Spellchecking Dictionary License</h1>
          </td>
          <td>
            <p>This license applies to the Lithuanian Spellchecking Dictionary. (This code only ships in some localized versions of this product.)</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2000-2013, Albertas Agejevas and contributors.
            All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:
            1. Redistributions of source code must retain the above copyright
              notice, this list of conditions and the following disclaimer.
            2. Redistributions in binary form must reproduce the above copyright
              notice, this list of conditions and the following disclaimer in the
              documentation and/or other materials provided with the distribution.
            3. Neither the name of the copyright holders nor the names of its contributors
              may be used to endorse or promote products derived from this software
              without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND
            ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
            IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
            ARE DISCLAIMED.  IN NO EVENT SHALL ALBERTAS AGEJEVAS OR CONTRIBUTORS BE LIABLE
            FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
            DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
            OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
            HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
            LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
            OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
            SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="lodash"></a>lodash License</h1>
          </td>
          <td>
            <p>This license applies to some of the code in <var>node_modules/lodash/lodash.js</var>.</p>
          </td>
          <td>
            <pre>
            Copyright JS Foundation and other contributors &lt;https://js.foundation/&gt;

            Based on Underscore.js, copyright Jeremy Ashkenas,
            DocumentCloud and Investigative Reporters &amp; Editors <https://underscorejs.org/>

            This software consists of voluntary contributions made by many
            individuals. For exact contribution history, see the revision history
            available at https://github.com/lodash/lodash

            The following license applies to all parts of this software except as
            documented below:

            ====

            Permission is hereby granted, free of charge, to any person obtaining
            a copy of this software and associated documentation files (the
            "Software"), to deal in the Software without restriction, including
            without limitation the rights to use, copy, modify, merge, publish,
            distribute, sublicense, and/or sell copies of the Software, and to
            permit persons to whom the Software is furnished to do so, subject to
            the following conditions:

            The above copyright notice and this permission notice shall be
            included in all copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

            ====

            Copyright and related rights for sample code are waived via CC0. Sample
            code is defined as all source code displayed within the prose of the
            documentation.

            CC0: https://creativecommons.org/publicdomain/zero/1.0/

            ====

            Files located in the node_modules and vendor directories are externally
            maintained libraries used by this software which have their own
            licenses; we recommend you read them, as their terms may differ from the
            terms above.

            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="matches"></a>matches License</h1>
          </td>
          <td>
            <p>This license applies to files in the directory <code>third_party/rust/matches</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2014-2016 Simon Sapin

            Permission is hereby granted, free of charge, to any
            person obtaining a copy of this software and associated
            documentation files (the "Software"), to deal in the
            Software without restriction, including without
            limitation the rights to use, copy, modify, merge,
            publish, distribute, sublicense, and/or sell copies of
            the Software, and to permit persons to whom the Software
            is furnished to do so, subject to the following
            conditions:

            The above copyright notice and this permission notice
            shall be included in all copies or substantial portions
            of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
            ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
            TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
            PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
            SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
            CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
            IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
            DEALINGS IN THE SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="mit"></a>MIT License</h1>
          </td>
          <td>
            <p>This license applies to the following files or to files in the following directories:
            <ul>
              <li><code>third_party/rust/bincode</code></li>
              <li><code>third_party/rust/byteorder</code></li>
              <li><code>third_party/js/cfworker/json-schema.js</code></li>
              <li><code>security/nss/lib/freebl/ecl/ecp_secp384r1.c</code> and
                  <code>security/nss/lib/freebl/ecl/ecp_secp521r1.c</code></li>
              <li><code>security/nss/lib/freebl/ecl/curve25519_32.c</code>,
                  <code>security/nss/lib/freebl/ecl/ecp_secp384r1.c</code> and
                  <code>security/nss/lib/freebl/ecl/ecp_secp521r1.c</code></li>
              <li><code>devtools/client/netmonitor/src/components/messages/parsers/socket-io/component-emitter.js</code></li>
              <li><code>mfbt/Span.h</code> and <code>mfbt/tests/gtest/TestSpan.cpp</code></li>
              <li><code>third_party/rust/lazy_static</code></li>
              <li><code>third_party/rust/libm</code> (with parts dual licensed MIT/<a href="about:license#apache">Apache-2.0</a>)</li>
              <li><code>devtools/client/shared/vendor/micromatch/micromatch.js</code></li>
              <li><code>devtools/client/shared/vendor/fuzzaldrin-plus.js</code></li>
              <li><code>devtools/shared/natural-sort.js</code></li>
              <li><code>devtools/shared/node-properties/node-properties.js</code></li>
              <li><code>third_party/rust/ordered-float</code></li>
              <li><code>third_party/rust/phf</code>,
                  <code>third_party/rust/phf_codegen</code>,
                  <code>third_party/rust/phf_generator</code>, and
                  <code>third_party/rust/phf_shared</code></li>
              <li><code>third_party/rust/precomputed-hash</code></li>
              <li><code>browser/components/newtab/vendor/prop-types*</code></li>
              <li><code>devtools/client/shared/vendor/react*</code>,
                  <code>browser/components/newtab/vendor/react*</code>,
                  <code>browser/components/pocket/content/panels/js/vendor.bundle.js</code> and
                  <code>devtools/client/debugger/test/mochitest/examples/react/build/main.js</code></li>
              <li><code>devtools/client/shared/vendor/react-router-dom.js</code></li>
              <li><code>devtools/client/shared/vendor/reselect.js</code> and
                  <code>browser/components/newtab/data/content/activity-stream.bundle.js</code></li>
              <li><code>third_party/rlbox</code></li>
              <li><code>devtools/client/netmonitor/src/components/messages/parsers/socket-io/binary.js</code>,
                  <code>devtools/client/netmonitor/src/components/messages/parsers/socket-io/index.js</code> and
                  <code>devtools/client/netmonitor/src/components/messages/parsers/socket-io/is-buffer.js</code></li>
              <li><code>devtools/client/netmonitor/src/components/messages/parsers/sockjs/index.js</code></li>
              <li><code>devtools/client/netmonitor/src/components/messages/parsers/stomp/byte.js</code>,
                  <code>devtools/client/netmonitor/src/components/messages/parsers/stomp/frame.js</code> and
                  <code>devtools/client/netmonitor/src/components/messages/parsers/stomp/parser.js</code></li>
              <li><code>third_party/rust/synstructure</code></li>
              <li><code>third_party/rust/void</code></li>
              <li><code>js/src/zydis</code> (unless otherwise specified)</li>
              <li><code>js/src/vm/Float16.h</code>(the code contained in the half namespace)</li>
              <li><code>toolkit/components/resistfingerprinting/content/gl-matrix.js</code></li>
              <li><code>toolkit/components/resistfingerprinting/content/ssdeep.js</code></li>
            </ul>
            See the individual LICENSE files or headers for copyright owners.</p>
          </td>
          <td>
            <pre>
            The MIT License (MIT)

            Permission is hereby granted, free of charge, to any person obtaining a copy
            of this software and associated documentation files (the "Software"), to deal
            in the Software without restriction, including without limitation the rights
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
            copies of the Software, and to permit persons to whom the Software is
            furnished to do so, subject to the following conditions:

            The above copyright notice and this permission notice shall be included in all
            copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
            SOFTWARE.

            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="myspell"></a>MySpell License</h1>
          </td>
          <td>
            <p>This license applies to some files in the directory <code>extensions/spellcheck/hunspell</code>.</p>
          </td>
          <td>
            <pre>
            Copyright 2002 Kevin B. Hendricks, Stratford, Ontario, Canada
            And Contributors.  All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:

            1. Redistributions of source code must retain the above copyright
              notice, this list of conditions and the following disclaimer.

            2. Redistributions in binary form must reproduce the above copyright
              notice, this list of conditions and the following disclaimer in the
              documentation and/or other materials provided with the distribution.

            3. All modifications to the source code must be clearly marked as
              such.  Binary redistributions based on modified source code
              must be clearly marked as modified versions in the documentation
              and/or other materials provided with the distribution.

            THIS SOFTWARE IS PROVIDED BY KEVIN B. HENDRICKS AND CONTRIBUTORS
            ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
            FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
            KEVIN B. HENDRICKS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
            INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
            BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
            LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
            HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
            LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
            OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
            SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="nicer"></a>nICEr License</h1>
          </td>
          <td>
            <p>This license applies to certain files in the directory <code>dom/media/webrtc/transport/third_party/nICEr</code>.</p>
          </td>
          <td>
            <pre>
              Copyright (C) 2007, Adobe Systems Inc.
              Copyright (C) 2007-2008, Network Resonance, Inc.

            Each source file bears an individual copyright notice.

            The following license applies to this distribution as a whole.


            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions are
            met:

            * Redistributions of source code must retain the above copyright
              notice, this list of conditions and the following disclaimer.

            * Redistributions in binary form must reproduce the above copyright
              notice, this list of conditions and the following disclaimer in the
              documentation and/or other materials provided with the distribution.

            * Neither the name of Adobe Systems, Network Resonance nor the names of its
              contributors may be used to endorse or promote products derived from
              this software without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
            OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
            SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="node-md5"></a>node-md5 License</h1>
          </td>
          <td>
            <p>This license applies to some of the code in <code>devtools/client/shared/vendor</code>.</p>
          </td>
          <td>
            <pre>
            Copyright © 2011-2012, Paul Vorbach.
            Copyright © 2009, Jeff Mott.

            All rights reserved.

            Redistribution and use in source and binary forms, with or without modification,
            are permitted provided that the following conditions are met:

            * Redistributions of source code must retain the above copyright notice, this
              list of conditions and the following disclaimer.
            * Redistributions in binary form must reproduce the above copyright notice, this
              list of conditions and the following disclaimer in the documentation and/or
              other materials provided with the distribution.
            * Neither the name Crypto-JS nor the names of its contributors may be used to
              endorse or promote products derived from this software without specific prior
              written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
            ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
            WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
            DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
            ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
            (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
            LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
            ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
            SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="nom"></a>nom License</h1>
          </td>
          <td>
            <p>This license applies to files in the directory <code>third_party/rust/nom</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2015 Geoffroy Couprie

            Permission is hereby granted, free of charge, to any person obtaining
            a copy of this software and associated documentation files (the
            "Software"), to deal in the Software without restriction, including
            without limitation the rights to use, copy, modify, merge, publish,
            distribute, sublicense, and/or sell copies of the Software, and to
            permit persons to whom the Software is furnished to do so, subject to
            the following conditions:

            The above copyright notice and this permission notice shall be
            included in all copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="nrappkit"></a>nrappkit License</h1>
          </td>
          <td>
            <p>This license applies to certain files in the directory <code>dom/media/webrtc/transport/third_party/nrappkit</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (C) 2001-2007, Network Resonance, Inc.
            All Rights Reserved

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:

            1. Redistributions of source code must retain the above copyright
              notice, this list of conditions and the following disclaimer.
            2. Redistributions in binary form must reproduce the above copyright
              notice, this list of conditions and the following disclaimer in the
              documentation and/or other materials provided with the distribution.
            3. Neither the name of Network Resonance, Inc. nor the name of any
              contributors to this software may be used to endorse or promote
              products derived from this software without specific prior written
              permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
            AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
            IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
            ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
            LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
            CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
            SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
            INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
            CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
            ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
            POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="nrappkit-cont-1"></a>nrappkit License (cont.)</h1>
          </td>
          <td>
            <p>This license applies to certain files in the directory <code>dom/media/webrtc/transport/third_party/nrappkit</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (C) 1999-2003 RTFM, Inc.
            All Rights Reserved

            This package is a SSLv3/TLS protocol analyzer written by Eric Rescorla
            &lt;ekr@rtfm.com&gt; and licensed by RTFM, Inc.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:
            1. Redistributions of source code must retain the above copyright
                notice, this list of conditions and the following disclaimer.
            2. Redistributions in binary form must reproduce the above copyright
                notice, this list of conditions and the following disclaimer in the
                documentation and/or other materials provided with the distribution.
            3. All advertising materials mentioning features or use of this software
                must display the following acknowledgement:

                This product includes software developed by Eric Rescorla for
                RTFM, Inc.

            4. Neither the name of RTFM, Inc. nor the name of Eric Rescorla may be
                used to endorse or promote products derived from this
                software without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE ERIC RESCORLA AND RTFM ``AS IS'' AND
            ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
            IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
            ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
            FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
            oDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
            OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
            HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
            LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
            OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
            SUCH DAMAGE.
            </pre>
            <p>Note that RTFM, Inc. has waived clause (3) above as of June 20, 2012
              for files appearing in this distribution. This waiver applies only to
              files included in this distribution. it does not apply to any other
              part of ssldump not included in this distribution.</p>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="nrappkit-cont-2"></a>nrappkit License (cont.)</h1>
          </td>
          <td>
            <p>This license applies to the file <code>dom/media/webrtc/transport/third_party/nrappkit/src/port/generic/include/sys/queue.h</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 1991, 1993
              The Regents of the University of California.  All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:
            1. Redistributions of source code must retain the above copyright
                notice, this list of conditions and the following disclaimer.
            2. Redistributions in binary form must reproduce the above copyright
                notice, this list of conditions and the following disclaimer in the
                documentation and/or other materials provided with the distribution.
            4. Neither the name of the University nor the names of its contributors
                may be used to endorse or promote products derived from this software
                without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
            ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
            IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
            ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
            FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
            DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
            OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
            HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
            LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
            OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
            SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="nrappkit-cont-3"></a>nrappkit License (cont.)</h1>
          </td>
          <td>
            <p>This license applies to the file: <code>dom/media/webrtc/transport/third_party/nrappkit/src/util/util.c</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 1998 Todd C. Miller &gt;Todd.Miller@courtesan.com&lt;
            All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:
            1. Redistributions of source code must retain the above copyright
                notice, this list of conditions and the following disclaimer.
            2. Redistributions in binary form must reproduce the above copyright
                notice, this list of conditions and the following disclaimer in the
                documentation and/or other materials provided with the distribution.
            3. The name of the author may not be used to endorse or promote products
                derived from this software without specific prior written permission.

            THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
            INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
            AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
            THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
            EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
            PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
            OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
            WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
            OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
            ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="openldap"></a>OpenLDAP Public License</h1>
          </td>
          <td>
            <p>This license applies to certain files in the directory <code>third_party/rust/lmdb-rkv-sys/lmdb/libraries/liblmdb</code>.
            </p>
          </td>
          <td>
            <pre>
            The OpenLDAP Public License
              Version 2.8, 17 August 2003

            Redistribution and use of this software and associated documentation
            ("Software"), with or without modification, are permitted provided
            that the following conditions are met:

            1. Redistributions in source form must retain copyright statements
              and notices,

            2. Redistributions in binary form must reproduce applicable copyright
              statements and notices, this list of conditions, and the following
              disclaimer in the documentation and/or other materials provided
              with the distribution, and

            3. Redistributions must contain a verbatim copy of this document.

            The OpenLDAP Foundation may revise this license from time to time.
            Each revision is distinguished by a version number.  You may use
            this Software under terms of this license revision or under the
            terms of any subsequent revision of the license.

            THIS SOFTWARE IS PROVIDED BY THE OPENLDAP FOUNDATION AND ITS
            CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
            INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
            AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT
            SHALL THE OPENLDAP FOUNDATION, ITS CONTRIBUTORS, OR THE AUTHOR(S)
            OR OWNER(S) OF THE SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
            INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
            BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
            LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
            CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
            LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
            ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
            POSSIBILITY OF SUCH DAMAGE.

            The names of the authors and copyright holders must not be used in
            advertising or otherwise to promote the sale, use or other dealing
            in this Software without specific, written prior permission.  Title
            to copyright in this Software shall at all times remain with copyright
            holders.

            OpenLDAP is a registered trademark of the OpenLDAP Foundation.

            Copyright 1999-2003 The OpenLDAP Foundation, Redwood City,
            California, USA.  All Rights Reserved.  Permission to copy and
            distribute verbatim copies of this document is granted.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="openvision"></a>OpenVision License</h1>
          </td>
          <td>
            <p>This license applies to the file <code>extensions/auth/gssapi.h</code>.</p>
          </td>
          <td>
            <pre>
            Copyright 1993 by OpenVision Technologies, Inc.

            Permission to use, copy, modify, distribute, and sell this software
            and its documentation for any purpose is hereby granted without fee,
            provided that the above copyright notice appears in all copies and
            that both that copyright notice and this permission notice appear in
            supporting documentation, and that the name of OpenVision not be used
            in advertising or publicity pertaining to distribution of the software
            without specific, written prior permission. OpenVision makes no
            representations about the suitability of this software for any
            purpose.  It is provided "as is" without express or implied warranty.

            OPENVISION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
            INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
            EVENT SHALL OPENVISION BE LIABLE FOR ANY SPECIAL, INDIRECT OR
            CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
            USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
            OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
            PERFORMANCE OF THIS SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="openvr"></a>OpenVR License</h1>
          </td>
          <td>
            <p>This license applies to certain files in the directory <code>gfx/vr/service/openvr</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2015, Valve Corporation
            All rights reserved.

            Redistribution and use in source and binary forms, with or without modification,
            are permitted provided that the following conditions are met:

            1. Redistributions of source code must retain the above copyright notice, this
            list of conditions and the following disclaimer.

            2. Redistributions in binary form must reproduce the above copyright notice,
            this list of conditions and the following disclaimer in the documentation and/or
            other materials provided with the distribution.

            3. Neither the name of the copyright holder nor the names of its contributors
            may be used to endorse or promote products derived from this software without
            specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
            ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
            WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
            DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
            ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
            (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
            LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
            ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
            SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="praton"></a>praton License</h1>
          </td>
          <td>
            <p>This license applies to the file <code>nsprpub/pr/src/misc/praton.c</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 1983, 1990, 1993
              The Regents of the University of California.  All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:
            1. Redistributions of source code must retain the above copyright
              notice, this list of conditions and the following disclaimer.
            2. Redistributions in binary form must reproduce the above copyright
              notice, this list of conditions and the following disclaimer in the
              documentation and/or other materials provided with the distribution.
            4. Neither the name of the University nor the names of its contributors
              may be used to endorse or promote products derived from this software
              without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
            ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
            IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
            ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
            FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
            DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
            OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
            HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
            LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
            OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
            SUCH DAMAGE.


            Portions Copyright (c) 1993 by Digital Equipment Corporation.

            Permission to use, copy, modify, and distribute this software for any
            purpose with or without fee is hereby granted, provided that the above
            copyright notice and this permission notice appear in all copies, and that
            the name of Digital Equipment Corporation not be used in advertising or
            publicity pertaining to distribution of the document or software without
            specific, written prior permission.

            THE SOFTWARE IS PROVIDED "AS IS" AND DIGITAL EQUIPMENT CORP. DISCLAIMS ALL
            WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES
            OF MERCHANTABILITY AND FITNESS.   IN NO EVENT SHALL DIGITAL EQUIPMENT
            CORPORATION BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
            DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
            PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
            ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
            SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="praton1"></a>praton and inet_ntop License</h1>
          </td>
          <td>
            <p>This license applies to the files <code>nsprpub/pr/src/misc/praton.c</code> and
            <code>dom/media/webrtc/transport/third_party/nrappkit/src/util/util.c</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
            Portions Copyright (c) 1996-1999 by Internet Software Consortium.

            Permission to use, copy, modify, and distribute this software for any
            purpose with or without fee is hereby granted, provided that the above
            copyright notice and this permission notice appear in all copies.

            THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
            WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
            MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
            ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
            WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
            ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
            OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="qcms"></a>qcms License</h1>
          </td>
          <td>
            <p>This license applies to certain files in the directory <code>gfx/qcms/</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (C) 2009 Mozilla Corporation
            Copyright (C) 1998-2007 Marti Maria

            Permission is hereby granted, free of charge, to any person
            obtaining a copy of this software and associated documentation
            files (the "Software"), to deal in the Software without restriction,
            including without limitation the rights to use, copy, modify, merge,
            publish, distribute, sublicense, and/or sell copies of the Software,
            and to permit persons to whom the Software is furnished to do so, subject
            to the following conditions:

            The above copyright notice and this permission notice shall be included
            in all copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
            OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
            WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
            CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="qrcode-generator"></a>QR Code Generator License</h1>
          </td>
          <td>
            <p>This license applies to certain files in the directory <code>devtools/shared/qrcode/encoder/</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2009 Kazuhiko Arase

            Permission is hereby granted, free of charge, to any person obtaining a copy
            of this software and associated documentation files (the "Software"), to deal
            in the Software without restriction, including without limitation the rights
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
            copies of the Software, and to permit persons to whom the Software is
            furnished to do so, subject to the following conditions:

            The above copyright notice and this permission notice shall be included in
            all copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
            THE SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="react"></a>React License</h1>
          </td>
          <td>
            <p>This license applies to various files in the Mozilla codebase.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2013-2015, Facebook, Inc.
            All rights reserved.

            Redistribution and use in source and binary forms, with or without modification,
            are permitted provided that the following conditions are met:

            * Redistributions of source code must retain the above copyright notice, this
              list of conditions and the following disclaimer.

            * Redistributions in binary form must reproduce the above copyright notice,
              this list of conditions and the following disclaimer in the documentation
              and/or other materials provided with the distribution.

            * Neither the name Facebook nor the names of its contributors may be used to
              endorse or promote products derived from this software without specific
              prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
            ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
            WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
            DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
            ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
            (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
            LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
            ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
            SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="react-redux"></a>React-Redux License</h1>
          </td>
          <td>
            <p>This license applies to the following files:</p>
            <ul>
              <li><code>devtools/client/shared/vendor/react-redux.js</code></li>
              <li><code>browser/components/newtab/vendor/react-redux.js</code></li>
            </ul>
          </td>
          <td>
            <pre>
            Copyright (c) 2015 Dan Abramov

            Permission is hereby granted, free of charge, to any person obtaining a copy
            of this software and associated documentation files (the "Software"), to deal
            in the Software without restriction, including without limitation the rights
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
            copies of the Software, and to permit persons to whom the Software is
            furnished to do so, subject to the following conditions:

            The above copyright notice and this permission notice shall be included in all
            copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
            SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="xdg"></a>Red Hat xdg_user_dir_lookup License</h1>
          </td>
          <td>
            <p>This license applies to the <var>xdg_user_dir_lookup</var> function in <code>xpcom/io/SpecialSystemDirectory.cpp</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2007 Red Hat, Inc.

            Permission is hereby granted, free of charge, to any person
            obtaining a copy of this software and associated documentation files
            (the "Software"), to deal in the Software without restriction,
            including without limitation the rights to use, copy, modify, merge,
            publish, distribute, sublicense, and/or sell copies of the Software,
            and to permit persons to whom the Software is furnished to do so,
            subject to the following conditions:

            The above copyright notice and this permission notice shall be
            included in all copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
            BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
            ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
            CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
            SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="redux"></a>Redux License</h1>
          </td>
          <td>
            <p>This license applies to the following files:</p>
            <ul>
              <li><code>devtools/client/shared/vendor/redux.js</code></li>
              <li><code>browser/components/newtab/vendor/Redux.sys.mjs</code></li>
            </ul>
          </td>
          <td>
            <pre>
            Copyright (c) 2015 Dan Abramov

            Permission is hereby granted, free of charge, to any person obtaining a copy
            of this software and associated documentation files (the "Software"), to deal
            in the Software without restriction, including without limitation the rights
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
            copies of the Software, and to permit persons to whom the Software is
            furnished to do so, subject to the following conditions:

            The above copyright notice and this permission notice shall be included in all
            copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
            SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="hunspell-ru"></a>Russian Spellchecking Dictionary License</h1>
          </td>
          <td>
            <p>This license applies to the Russian Spellchecking Dictionary. (This code only ships in some localized versions of this product.)</p>
          </td>
          <td>
            <pre>
            * Copyright (c) 1997-2008, Alexander I. Lebedev

            All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:
            * Redistributions of source code must retain the above copyright
              notice, this list of conditions and the following disclaimer.
            * Redistributions in binary form must reproduce the above copyright
              notice, this list of conditions and the following disclaimer in the
              documentation and/or other materials provided with the distribution.
            * Modified versions must be clearly marked as such.
            * The name of Alexander I. Lebedev may not be used to endorse or promote
              products derived from this software without specific prior written
              permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
            AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
            IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
            ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
            LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
            CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
            SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
            INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
            CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
            ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
            POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="sctp"></a>SCTP Licenses</h1>
          </td>
          <td>
            <p>These licenses apply to certain files in the directory <code>netwerk/sctp/src/</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2009-2010 Brad Penoff
            Copyright (c) 2009-2010 Humaira Kamal
            Copyright (c) 2011-2012 Irene Ruengeler
            Copyright (c) 2010-2012, by Michael Tuexen. All rights reserved.
            Copyright (c) 2010-2012, by Randall Stewart. All rights reserved.
            Copyright (c) 2010-2012, by Robin Seggelmann. All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:
            1. Redistributions of source code must retain the above copyright
              notice, this list of conditions and the following disclaimer.
            2. Redistributions in binary form must reproduce the above copyright
              notice, this list of conditions and the following disclaimer in the
              documentation and/or other materials provided with the distribution.

            THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
            ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
            IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
            ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
            FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
            DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
            OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
            HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
            LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
            OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
            SUCH DAMAGE.

            Copyright (c) 2001-2008, by Cisco Systems, Inc. All rights reserved.
            Copyright (c) 2008-2012, by Randall Stewart. All rights reserved.
            Copyright (c) 2008-2012, by Michael Tuexen. All rights reserved.
            Copyright (c) 2008-2012, by Brad Penoff. All rights reserved.
            Copyright (c) 1980, 1982, 1986, 1987, 1988, 1990, 1993
              The Regents of the University of California.
            Copyright (c) 2005 Robert N. M. Watson All rights reserved.
            Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions are met:
            a) Redistributions of source code must retain the above copyright notice,
              this list of conditions and the following disclaimer.
            b) Redistributions in binary form must reproduce the above copyright
              notice, this list of conditions and the following disclaimer in
              the documentation and/or other materials provided with the distribution.
            c) Neither the name of Cisco Systems, Inc, the name of the university,
              the WIDE project, nor the names of its contributors may be used to
              endorse or promote products derived from this software without specific
              prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
            THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
            ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
            LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
            CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
            SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
            INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
            CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
            ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
            THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="skia"></a>Skia License</h1>
          </td>
          <td>
            <p>This license applies to certain files in the directory <code>gfx/skia/</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2011 Google Inc. All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions are
            met:

            * Redistributions of source code must retain the above copyright
            notice, this list of conditions and the following disclaimer.
            * Redistributions in binary form must reproduce the above
            copyright notice, this list of conditions and the following disclaimer
            in the documentation and/or other materials provided with the
            distribution.
            * Neither the name of Google Inc. nor the names of its
            contributors may be used to endorse or promote products derived from
            this software without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
            OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
            SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="snappy"></a>Snappy License</h1>
          </td>
          <td>
            <p>This license applies to certain files in the directory <code>other-licenses/snappy/</code>.</p>
          </td>
          <td>
            <pre>
            Copyright 2011, Google Inc.
            All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions are
            met:

                * Redistributions of source code must retain the above copyright
            notice, this list of conditions and the following disclaimer.
                * Redistributions in binary form must reproduce the above
            copyright notice, this list of conditions and the following disclaimer
            in the documentation and/or other materials provided with the
            distribution.
                * Neither the name of Google Inc. nor the names of its
            contributors may be used to endorse or promote products derived from
            this software without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
            OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
            SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="sprintf.js"></a>sprintf.js License</h1>
          </td>
          <td>
            <p>This license applies to <code>devtools/shared/sprintfjs/sprintf.js</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2007-2016, Alexandru Marasteanu &lt;hello [at) alexei (dot] ro&gt;
            All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions are met:
            * Redistributions of source code must retain the above copyright
              notice, this list of conditions and the following disclaimer.
            * Redistributions in binary form must reproduce the above copyright
              notice, this list of conditions and the following disclaimer in the
              documentation and/or other materials provided with the distribution.
            * Neither the name of this software nor the names of its contributors may be
              used to endorse or promote products derived from this software without
              specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
            ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
            WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
            DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
            ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
            (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
            LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
            ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
            SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="sunsoft"></a>SunSoft License</h1>
          </td>
          <td>
            <p>This license applies to the <var>ICC_H</var> block in <code>gfx/qcms/qcms.h</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 1994-1996 SunSoft, Inc.

                                Rights Reserved

            Permission is hereby granted, free of charge, to any person
            obtaining a copy of this software and associated documentation
            files (the "Software"), to deal in the Software without restrict-
            ion, including without limitation the rights to use, copy, modify,
            merge, publish distribute, sublicense, and/or sell copies of the
            Software, and to permit persons to whom the Software is furnished
            to do so, subject to the following conditions:

            The above copyright notice and this permission notice shall be
            included in all copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
            OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-
            INFRINGEMENT.  IN NO EVENT SHALL SUNSOFT, INC. OR ITS PARENT
            COMPANY BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
            WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
            FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
            OTHER DEALINGS IN THE SOFTWARE.

            Except as contained in this notice, the name of SunSoft, Inc.
            shall not be used in advertising or otherwise to promote the
            sale, use or other dealings in this Software without written
            authorization from SunSoft Inc.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="superfasthash"></a>SuperFastHash License</h1>
          </td>
          <td>
            <p>This license applies to files in the directory <code>security/sandbox/chromium/base/third_party/superfasthash/</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2010, Paul Hsieh
            All rights reserved.

            Redistribution and use in source and binary forms, with or without modification,
            are permitted provided that the following conditions are met:

            * Redistributions of source code must retain the above copyright notice, this
              list of conditions and the following disclaimer.
            * Redistributions in binary form must reproduce the above copyright notice, this
              list of conditions and the following disclaimer in the documentation and/or
              other materials provided with the distribution.
            * Neither my name, Paul Hsieh, nor the names of any other contributors to the
              code use may not be used to endorse or promote products derived from this
              software without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
            ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
            WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
            DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
            ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
            (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
            LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
            ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
            SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="twemoji"></a>Twemoji License</h1>
          </td>
          <td>
            <p>This license applies to the emoji art contained within the bundled emoji font file.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2018 Twitter, Inc and other contributors.

            Creative Commons Attribution 4.0 International (CC BY 4.0)

            See https://creativecommons.org/licenses/by/4.0/legalcode or
            for the human readable summary: https://creativecommons.org/licenses/by/4.0/

            You are free to:

            Share — copy and redistribute the material in any medium or format

            Adapt — remix, transform, and build upon the material for any purpose, even commercially.

            The licensor cannot revoke these freedoms as long as you follow the license terms.

            Under the following terms:

            Attribution — You must give appropriate credit, provide a link to the license,
            and indicate if changes were made. You may do so in any reasonable manner,
            but not in any way that suggests the licensor endorses you or your use.

            No additional restrictions — You may not apply legal terms or technological
            measures that legally restrict others from doing anything the license permits.

            Notices:

            You do not have to comply with the license for elements of the material in
            the public domain or where your use is permitted by an applicable exception or
            limitation. No warranties are given. The license may not give you all of the
            permissions necessary for your intended use. For example, other rights such as
            publicity, privacy, or moral rights may limit how you use the material.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="unicase"></a>unicase License</h1>
          </td>
          <td>
            <p>This license applies to files in the directory <code>third_party/rust/unicase</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2014-2015 Sean McArthur

            Permission is hereby granted, free of charge, to any person obtaining a copy
            of this software and associated documentation files (the "Software"), to deal
            in the Software without restriction, including without limitation the rights
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
            copies of the Software, and to permit persons to whom the Software is
            furnished to do so, subject to the following conditions:

            The above copyright notice and this permission notice shall be included in
            all copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
            THE SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="unicode"></a>Unicode License</h1>
          </td>
          <td>
            <p>This license applies to the following files or, in the case of directories, certain files in those directories:</p>
            <ul>
              <li><code>intl/icu</code></li>
              <li><code>intl/tzdata</code></li>
              <li><code>js/src/util</code></li>
            </ul>
          </td>
          <td>
            <pre>
            COPYRIGHT AND PERMISSION NOTICE

            Copyright © 1991-2016 Unicode, Inc. All rights reserved.
            Distributed under the Terms of Use in https://www.unicode.org/copyright.html.

            Permission is hereby granted, free of charge, to any person obtaining
            a copy of the Unicode data files and any associated documentation
            (the "Data Files") or Unicode software and any associated documentation
            (the "Software") to deal in the Data Files or Software
            without restriction, including without limitation the rights to use,
            copy, modify, merge, publish, distribute, and/or sell copies of
            the Data Files or Software, and to permit persons to whom the Data Files
            or Software are furnished to do so, provided that either
            (a) this copyright and permission notice appear with all copies
            of the Data Files or Software, or
            (b) this copyright and permission notice appear in associated
            Documentation.

            THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF
            ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
            WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
            NONINFRINGEMENT OF THIRD PARTY RIGHTS.
            IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS
            NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
            DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
            DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
            TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
            PERFORMANCE OF THE DATA FILES OR SOFTWARE.

            Except as contained in this notice, the name of a copyright holder
            shall not be used in advertising or otherwise to promote the sale,
            use or other dealings in these Data Files or Software without prior
            written authorization of the copyright holder.

            ---------------------

            Third-Party Software Licenses

            This section contains third-party software notices and/or additional
            terms for licensed third-party software components included within ICU
            libraries.

            1. ICU License - ICU 1.8.1 to ICU 57.1

            COPYRIGHT AND PERMISSION NOTICE

            Copyright (c) 1995-2016 International Business Machines Corporation and others
            All rights reserved.

            Permission is hereby granted, free of charge, to any person obtaining
            a copy of this software and associated documentation files (the
            "Software"), to deal in the Software without restriction, including
            without limitation the rights to use, copy, modify, merge, publish,
            distribute, and/or sell copies of the Software, and to permit persons
            to whom the Software is furnished to do so, provided that the above
            copyright notice(s) and this permission notice appear in all copies of
            the Software and that both the above copyright notice(s) and this
            permission notice appear in supporting documentation.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
            OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
            HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY
            SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
            RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
            CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
            CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

            Except as contained in this notice, the name of a copyright holder
            shall not be used in advertising or otherwise to promote the sale, use
            or other dealings in this Software without prior written authorization
            of the copyright holder.

            All trademarks and registered trademarks mentioned herein are the
            property of their respective owners.

            2. Chinese/Japanese Word Break Dictionary Data (cjdict.txt)

            #     The Google Chrome software developed by Google is licensed under
            # the BSD license. Other software included in this distribution is
            # provided under other licenses, as set forth below.
            #
            #  The BSD License
            #  https://opensource.org/licenses/bsd-license.php
            #  Copyright (C) 2006-2008, Google Inc.
            #
            #  All rights reserved.
            #
            #  Redistribution and use in source and binary forms, with or without
            # modification, are permitted provided that the following conditions are met:
            #
            #  Redistributions of source code must retain the above copyright notice,
            # this list of conditions and the following disclaimer.
            #  Redistributions in binary form must reproduce the above
            # copyright notice, this list of conditions and the following
            # disclaimer in the documentation and/or other materials provided with
            # the distribution.
            #  Neither the name of  Google Inc. nor the names of its
            # contributors may be used to endorse or promote products derived from
            # this software without specific prior written permission.
            #
            #
            #  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
            # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
            # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
            # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
            # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
            # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
            # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
            # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
            # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
            # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
            # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
            # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            #
            #
            #  The word list in cjdict.txt are generated by combining three word lists
            # listed below with further processing for compound word breaking. The
            # frequency is generated with an iterative training against Google web
            # corpora.
            #
            #  * Libtabe (Chinese)
            #    - https://sourceforge.net/project/?group_id=1519
            #    - Its license terms and conditions are shown below.
            #
            #  * IPADIC (Japanese)
            #    - http://chasen.aist-nara.ac.jp/chasen/distribution.html
            #    - Its license terms and conditions are shown below.
            #
            #  ---------COPYING.libtabe ---- BEGIN--------------------
            #
            #  /*
            #   * Copyrighy (c) 1999 TaBE Project.
            #   * Copyright (c) 1999 Pai-Hsiang Hsiao.
            #   * All rights reserved.
            #   *
            #   * Redistribution and use in source and binary forms, with or without
            #   * modification, are permitted provided that the following conditions
            #   * are met:
            #   *
            #   * . Redistributions of source code must retain the above copyright
            #   *   notice, this list of conditions and the following disclaimer.
            #   * . Redistributions in binary form must reproduce the above copyright
            #   *   notice, this list of conditions and the following disclaimer in
            #   *   the documentation and/or other materials provided with the
            #   *   distribution.
            #   * . Neither the name of the TaBE Project nor the names of its
            #   *   contributors may be used to endorse or promote products derived
            #   *   from this software without specific prior written permission.
            #   *
            #   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            #   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            #   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
            #   * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
            #   * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
            #   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
            #   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
            #   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
            #   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
            #   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
            #   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
            #   * OF THE POSSIBILITY OF SUCH DAMAGE.
            #   */
            #
            #  /*
            #   * Copyright (c) 1999 Computer Systems and Communication Lab,
            #   *                    Institute of Information Science, Academia
            #       *                    Sinica. All rights reserved.
            #   *
            #   * Redistribution and use in source and binary forms, with or without
            #   * modification, are permitted provided that the following conditions
            #   * are met:
            #   *
            #   * . Redistributions of source code must retain the above copyright
            #   *   notice, this list of conditions and the following disclaimer.
            #   * . Redistributions in binary form must reproduce the above copyright
            #   *   notice, this list of conditions and the following disclaimer in
            #   *   the documentation and/or other materials provided with the
            #   *   distribution.
            #   * . Neither the name of the Computer Systems and Communication Lab
            #   *   nor the names of its contributors may be used to endorse or
            #   *   promote products derived from this software without specific
            #   *   prior written permission.
            #   *
            #   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            #   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            #   * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
            #   * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
            #   * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
            #   * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
            #   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
            #   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
            #   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
            #   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
            #   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
            #   * OF THE POSSIBILITY OF SUCH DAMAGE.
            #   */
            #
            #  Copyright 1996 Chih-Hao Tsai @ Beckman Institute,
            #      University of Illinois
            #  c-tsai4@uiuc.edu  http://casper.beckman.uiuc.edu/~c-tsai4
            #
            #  ---------------COPYING.libtabe-----END--------------------------------
            #
            #
            #  ---------------COPYING.ipadic-----BEGIN-------------------------------
            #
            #  Copyright 2000, 2001, 2002, 2003 Nara Institute of Science
            #  and Technology.  All Rights Reserved.
            #
            #  Use, reproduction, and distribution of this software is permitted.
            #  Any copy of this software, whether in its original form or modified,
            #  must include both the above copyright notice and the following
            #  paragraphs.
            #
            #  Nara Institute of Science and Technology (NAIST),
            #  the copyright holders, disclaims all warranties with regard to this
            #  software, including all implied warranties of merchantability and
            #  fitness, in no event shall NAIST be liable for
            #  any special, indirect or consequential damages or any damages
            #  whatsoever resulting from loss of use, data or profits, whether in an
            #  action of contract, negligence or other tortuous action, arising out
            #  of or in connection with the use or performance of this software.
            #
            #  A large portion of the dictionary entries
            #  originate from ICOT Free Software.  The following conditions for ICOT
            #  Free Software applies to the current dictionary as well.
            #
            #  Each User may also freely distribute the Program, whether in its
            #  original form or modified, to any third party or parties, PROVIDED
            #  that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear
            #  on, or be attached to, the Program, which is distributed substantially
            #  in the same form as set out herein and that such intended
            #  distribution, if actually made, will neither violate or otherwise
            #  contravene any of the laws and regulations of the countries having
            #  jurisdiction over the User or the intended distribution itself.
            #
            #  NO WARRANTY
            #
            #  The program was produced on an experimental basis in the course of the
            #  research and development conducted during the project and is provided
            #  to users as so produced on an experimental basis.  Accordingly, the
            #  program is provided without any warranty whatsoever, whether express,
            #  implied, statutory or otherwise.  The term "warranty" used herein
            #  includes, but is not limited to, any warranty of the quality,
            #  performance, merchantability and fitness for a particular purpose of
            #  the program and the nonexistence of any infringement or violation of
            #  any right of any third party.
            #
            #  Each user of the program will agree and understand, and be deemed to
            #  have agreed and understood, that there is no warranty whatsoever for
            #  the program and, accordingly, the entire risk arising from or
            #  otherwise connected with the program is assumed by the user.
            #
            #  Therefore, neither ICOT, the copyright holder, or any other
            #  organization that participated in or was otherwise related to the
            #  development of the program and their respective officials, directors,
            #  officers and other employees shall be held liable for any and all
            #  damages, including, without limitation, general, special, incidental
            #  and consequential damages, arising out of or otherwise in connection
            #  with the use or inability to use the program or any product, material
            #  or result produced or otherwise obtained by using the program,
            #  regardless of whether they have been advised of, or otherwise had
            #  knowledge of, the possibility of such damages at any time during the
            #  project or thereafter.  Each user will be deemed to have agreed to the
            #  foregoing by his or her commencement of use of the program.  The term
            #  "use" as used herein includes, but is not limited to, the use,
            #  modification, copying and distribution of the program and the
            #  production of secondary products from the program.
            #
            #  In the case where the program, whether in its original form or
            #  modified, was distributed or delivered to or received by a user from
            #  any person, organization or entity other than ICOT, unless it makes or
            #  grants independently of ICOT any specific warranty to the user in
            #  writing, such person, organization or entity, will also be exempted
            #  from and not be held liable to the user for any such damages as noted
            #  above as far as the program is concerned.
            #
            #  ---------------COPYING.ipadic-----END----------------------------------

            3. Lao Word Break Dictionary Data (laodict.txt)

            #  Copyright (c) 2013 International Business Machines Corporation
            #  and others. All Rights Reserved.
            #
            # Project: https://code.google.com/p/lao-dictionary/
            # Dictionary: https://lao-dictionary.googlecode.com/git/Lao-Dictionary.txt
            # License: https://lao-dictionary.googlecode.com/git/Lao-Dictionary-LICENSE.txt
            #              (copied below)
            #
            #  This file is derived from the above dictionary, with slight
            #  modifications.
            #  ----------------------------------------------------------------------
            #  Copyright (C) 2013 Brian Eugene Wilson, Robert Martin Campbell.
            #  All rights reserved.
            #
            #  Redistribution and use in source and binary forms, with or without
            #  modification,
            #  are permitted provided that the following conditions are met:
            #
            #
            # Redistributions of source code must retain the above copyright notice, this
            #  list of conditions and the following disclaimer. Redistributions in
            #  binary form must reproduce the above copyright notice, this list of
            #  conditions and the following disclaimer in the documentation and/or
            #  other materials provided with the distribution.
            #
            #
            # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
            # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
            # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
            # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
            # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
            # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
            # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
            # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
            # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
            # OF THE POSSIBILITY OF SUCH DAMAGE.
            #  --------------------------------------------------------------------------

            4. Burmese Word Break Dictionary Data (burmesedict.txt)

            #  Copyright (c) 2014 International Business Machines Corporation
            #  and others. All Rights Reserved.
            #
            #  This list is part of a project hosted at:
            #    github.com/kanyawtech/myanmar-karen-word-lists
            #
            #  --------------------------------------------------------------------------
            #  Copyright (c) 2013, LeRoy Benjamin Sharon
            #  All rights reserved.
            #
            #  Redistribution and use in source and binary forms, with or without
            #  modification, are permitted provided that the following conditions
            #  are met: Redistributions of source code must retain the above
            #  copyright notice, this list of conditions and the following
            #  disclaimer.  Redistributions in binary form must reproduce the
            #  above copyright notice, this list of conditions and the following
            #  disclaimer in the documentation and/or other materials provided
            #  with the distribution.
            #
            #    Neither the name Myanmar Karen Word Lists, nor the names of its
            #    contributors may be used to endorse or promote products derived
            #    from this software without specific prior written permission.
            #
            #  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
            #  CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
            #  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
            #  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
            #  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
            #  BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
            #  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
            #  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
            #  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
            #  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
            #  TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
            #  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
            #  SUCH DAMAGE.
            #  --------------------------------------------------------------------------

            5. Time Zone Database

              ICU uses the public domain data and code derived from Time Zone
            Database for its time zone support. The ownership of the TZ database
            is explained in BCP 175: Procedure for Maintaining the Time Zone
            Database section 7.

            # 7.  Database Ownership
            #
            #    The TZ database itself is not an IETF Contribution or an IETF
            #    document.  Rather it is a pre-existing and regularly updated work
            #    that is in the public domain, and is intended to remain in the
            #    public domain.  Therefore, BCPs 78 [RFC5378] and 79 [RFC3979] do
            #    not apply to the TZ Database or contributions that individuals make
            #    to it.  Should any claims be made and substantiated against the TZ
            #    Database, the organization that is providing the IANA
            #    Considerations defined in this RFC, under the memorandum of
            #    understanding with the IETF, currently ICANN, may act in accordance
            #    with all competent court orders.  No ownership claims will be made
            #    by ICANN or the IETF Trust on the database or the code.  Any person
            #    making a contribution to the database or code waives all rights to
            #    future claims in that contribution or in the TZ Database.</pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="unicode-v3"></a>Unicode License V3</h1>
          </td>
          <td>
            <p>This license applies to the following files or, in the case of directories, certain files in those directories:</p>
            <ul>
              <li><code>intl/icu_capi</code></li>
              <li><code>intl/icu_segmenter_data</code></li>
              <li><code>third_party/rust/icu_calendar</code></li>
              <li><code>third_party/rust/icu_calendar_data</code></li>
              <li><code>third_party/rust/icu_collections</code></li>
              <li><code>third_party/rust/icu_locid</code></li>
              <li><code>third_party/rust/icu_locid_transform</code></li>
              <li><code>third_party/rust/icu_provider</code></li>
              <li><code>third_party/rust/icu_provider_adapters</code></li>
              <li><code>third_party/rust/icu_provider_macros</code></li>
              <li><code>third_party/rust/icu_segmenter</code></li>
              <li><code>third_party/rust/litemap</code></li>
              <li><code>third_party/rust/tinystr</code></li>
              <li><code>third_party/rust/writeable</code></li>
              <li><code>third_party/rust/yoke</code></li>
              <li><code>third_party/rust/yoke-derive</code></li>
              <li><code>third_party/rust/zerofrom</code></li>
              <li><code>third_party/rust/zerofrom-derive</code></li>
              <li><code>third_party/rust/zerovec</code></li>
              <li><code>third_party/rust/zerovec-derive</code></li>
            </ul>
          </td>
          <td>
            <pre>
            UNICODE LICENSE V3

            COPYRIGHT AND PERMISSION NOTICE

            Copyright © 2020-2023 Unicode, Inc.

            NOTICE TO USER: Carefully read the following legal agreement. BY
            DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
            SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
            TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
            DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.

            Permission is hereby granted, free of charge, to any person obtaining a
            copy of data files and any associated documentation (the "Data Files") or
            software and any associated documentation (the "Software") to deal in the
            Data Files or Software without restriction, including without limitation
            the rights to use, copy, modify, merge, publish, distribute, and/or sell
            copies of the Data Files or Software, and to permit persons to whom the
            Data Files or Software are furnished to do so, provided that either (a)
            this copyright and permission notice appear with all copies of the Data
            Files or Software, or (b) this copyright and permission notice appear in
            associated Documentation.

            THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
            KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
            THIRD PARTY RIGHTS.

            IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
            BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
            OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
            WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
            ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
            FILES OR SOFTWARE.

            Except as contained in this notice, the name of a copyright holder shall
            not be used in advertising or otherwise to promote the sale, use or other
            dealings in these Data Files or Software without prior written
            authorization of the copyright holder.

            —

            Portions of ICU4X may have been adapted from ICU4C and/or ICU4J.
            ICU 1.8.1 to ICU 57.1 © 1995-2016 International Business Machines Corporation and others.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="ucal"></a>University of California License</h1>
          </td>
          <td>
            <p>This license applies to the following files or, in the case of directories, certain files in those directories:</p>
            <ul>
              <li><code>security/nss/lib/dbm</code></li>
              <li><code>nsprpub/pr/src/misc/praton.c</code></li>
              <li><code>dom/media/webrtc/transport/third_party/nICEr/src/stun/addrs.c</code></li>
            </ul>
          </td>
          <td>
            <pre>
            Copyright (c) 1990, 1993
            The Regents of the University of California.  All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:
            1. Redistributions of source code must retain the above copyright
              notice, this list of conditions and the following disclaimer.
            2. Redistributions in binary form must reproduce the above copyright
              notice, this list of conditions and the following disclaimer in the
              documentation and/or other materials provided with the distribution.
            [3 Deleted as of 22nd July 1999; see
                <a href="ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change">ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change</a>
                for details]
            4. Neither the name of the University nor the names of its contributors
              may be used to endorse or promote products derived from this software
              without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
            ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
            IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
            ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
            FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
            DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
            OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
            HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
            LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
            OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
            SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="v8"></a>V8 License</h1>
          </td>
          <td>
            <p>This license applies to certain files in the following directories:</p>
            <ul>
              <li><code>js/src/irregexp</code></li>
              <li><code>js/src/builtin</code></li>
              <li><code>js/src/jit/arm</code></li>
              <li><code>js/src/jit/mips*</code></li>
              <li><code>js/src/jit/riscv64</code></li>
            </ul>
          </td>
          <td>
            <pre>
            Copyright 2006-2012 the V8 project authors. All rights reserved.
            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions are
            met

                * Redistributions of source code must retain the above copyright
                  notice, this list of conditions and the following disclaimer.
                * Redistributions in binary form must reproduce the above
                  copyright notice, this list of conditions and the following
                  disclaimer in the documentation and/or other materials provided
                  with the distribution.
                * Neither the name of Google Inc. nor the names of its
                  contributors may be used to endorse or promote products derived
                  from this software without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
            OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
            SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="validator"></a>Validator License</h1>
          </td>
          <td>
            <p>This license applies to certain files in the directory <code>devtools/shared/storage/vendor/stringvalidator/</code></p>
          </td>
          <td>
            <pre>

            Copyright (c) 2016 Chris O"Hara &lt;cohara87@gmail.com&gt;

            Permission is hereby granted, free of charge, to any person obtaining
            a copy of this software and associated documentation files (the
            "Software"), to deal in the Software without restriction, including
            without limitation the rights to use, copy, modify, merge, publish,
            distribute, sublicense, and/or sell copies of the Software, and to
            permit persons to whom the Software is furnished to do so, subject to
            the following conditions:

            The above copyright notice and this permission notice shall be
            included in all copies or substantial portions of the Software.

            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="vtune"></a>VTune License</h1>
          </td>
          <td>
            <p>This license applies to certain files in the directory <code>js/src/vtune</code> and <code>tools/profiler/core/vtune</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2011 Intel Corporation.
            All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:

              * Redistributions of source code must retain the above copyright
                notice, this list of conditions and the following disclaimer.
              * Redistributions in binary form must reproduce the above copyright
                notice, this list of conditions and the following disclaimer in
                the documentation and/or other materials provided with the
                distribution.
              * Neither the name of Intel Corporation nor the names of its
                contributors may be used to endorse or promote products derived
                from this software without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
            OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
            SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="webrtc"></a>WebRTC License</h1>
          </td>
          <td>
            <p>This license applies to certain files in the directory <code>third_party/libwebrtc/</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (c) 2011, The WebRTC project authors. All rights reserved.

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions are
            met:

              * Redistributions of source code must retain the above copyright
                notice, this list of conditions and the following disclaimer.

              * Redistributions in binary form must reproduce the above copyright
                notice, this list of conditions and the following disclaimer in
                the documentation and/or other materials provided with the
                distribution.

              * Neither the name of Google nor the names of its contributors may
                be used to endorse or promote products derived from this software
                without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
            A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
            HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
            SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

        <tr>
          <td>
            <h1><a id="x264"></a>x264 License</h1>
          </td>
          <td>
            <p>This license applies to the file <code> third_party/aom/third_party/x86inc/x86inc.asm</code>.</p>
          </td>
          <td>
            <pre>
            Copyright (C) 2005-2012 x264 project

            Authors: Loren Merritt &lt;lorenm@u.washington.edu&gt;
                    Anton Mitrofanov &lt;BugMaster@narod.ru&gt;
                    Jason Garrett-Glaser &lt;darkshikari@gmail.com&gt;
                    Henrik Gramner &lt;hengar-6@student.ltu.se&gt;

            Permission to use, copy, modify, and/or distribute this software for any
            purpose with or without fee is hereby granted, provided that the above
            copyright notice and this permission notice appear in all copies.

            THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
            WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
            MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
            ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
            WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
            ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
            OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
            </pre>
          </td>
        </tr>

    <hr>

    <h1><a id="xiph"></a>Xiph.org Foundation License</h1>

    <p>This license applies to files in the following directories
      with the specified copyright year ranges:</p>
    <ul>
      <li><code>media/libogg/</code>, 2002</li>
      <li><code>media/libvorbis/</code>, 2002-2004</li>
      <li><code>media/libspeex_resampler/</code>, 2002-2008</li>
    </ul>

<pre>
Copyright (c) &lt;year&gt;, Xiph.org Foundation
        <tr>
          <td>
            <h1><a id="xiph"></a>Xiph.org Foundation License</h1>
          </td>
          <td>
            <p>This license applies to files in the following directories with the specified copyright year ranges:</p>
            <ul>
              <li><code>media/libogg/</code>, 2002</li>
              <li><code>media/libvorbis/</code>, 2002-2004</li>
              <li><code>media/libspeex_resampler/</code>, 2002-2008</li>
            </ul>
          </td>
          <td>
            <pre>
            Copyright (c) &lt;year&gt;, Xiph.org Foundation

            Redistribution and use in source and binary forms, with or without
            modification, are permitted provided that the following conditions
            are met:

            - Redistributions of source code must retain the above copyright
            notice, this list of conditions and the following disclaimer.

            - Redistributions in binary form must reproduce the above copyright
            notice, this list of conditions and the following disclaimer in the
            documentation and/or other materials provided with the distribution.

            - Neither the name of the Xiph.org Foundation nor the names of its
            contributors may be used to endorse or promote products derived from
            this software without specific prior written permission.

            THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
            ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
            LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
            A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION
            OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
            SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
            LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
            DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
            THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
            (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
            OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
            </pre>
          </td>
        </tr>

      </tbody>
    </table>

    <hr>

    <h1><a id="other-notices"></a>Other Required Notices</h1>

    <ul>
      <li>This software is based in part on the work of the Independent
          JPEG Group.</li>
      <li>Portions of the OS/2 and Android versions
          of this software are copyright &copy;1996-2012
          <a href="https://www.freetype.org/">The FreeType Project</a>.
          All rights reserved.</li>
      <li>Google Play and the Google Play logo are trademarks of Google LLC.</li>
      <li>App Store® and the App Store® logo are trademarks of Apple, Inc.</li>
    </ul>


    <hr>

    <h1><a id="optional-notices"></a>Optional Notices</h1>

    <p>Some permissive software licenses request but do not require an
    acknowledgement of the use of their software. We are very grateful
    to the following people and projects for their contributions to
    this product:</p>

    <ul>
      <li>The <a href="https://www.zlib.net/">zlib</a> compression library
          (Jean-loup Gailly, Mark Adler and team)</li>
      <li>The <a href="http://www.libpng.org/pub/png/">libpng</a> graphics library
          (Glenn Randers-Pehrson and team)</li>
      <li>The <a href="https://www.sqlite.org/">sqlite</a> database engine
          (D. Richard Hipp and team)</li>
      <li>The <a href="http://nsis.sourceforge.net/">Nullsoft Scriptable Install System</a>
          (Amir Szekely and team)</li>
      <li>The <a href="https://mattmccutchen.net/bigint/">C++ Big Integer Library</a>
          (Matt McCutchen)</li>
    </ul>





      <hr>

      <p><a href="about:license#top">Return to top</a>.</p>
    </div>
  </body>
</html>
PK
!<��..+chrome/toolkit/content/global/lit-utils.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { LitElement } from "chrome://global/content/vendor/lit.all.mjs";

/**
 * Helper for our replacement of @query. Used with `static queries` property.
 *
 * https://github.com/lit/lit/blob/main/packages/reactive-element/src/decorators/query.ts
 */
function query(el, selector) {
  return () => el.renderRoot.querySelector(selector);
}

/**
 * Helper for our replacement of @queryAll. Used with `static queries` property.
 *
 * https://github.com/lit/lit/blob/main/packages/reactive-element/src/decorators/query-all.ts
 */
function queryAll(el, selector) {
  return () => el.renderRoot.querySelectorAll(selector);
}

/**
 * MozLitElement provides extensions to the lit-provided LitElement class.
 *
 *******
 *
 * `@query` support (define a getter for a querySelector):
 *
 * static get queries() {
 *   return {
 *     propertyName: ".aNormal .cssSelector",
 *     anotherName: { all: ".selectorFor .querySelectorAll" },
 *   };
 * }
 *
 * This example would add properties that would be written like this without
 * using `queries`:
 *
 * get propertyName() {
 *   return this.renderRoot?.querySelector(".aNormal .cssSelector");
 * }
 *
 * get anotherName() {
 *   return this.renderRoot?.querySelectorAll(".selectorFor .querySelectorAll");
 * }
 *******
 *
 * Automatic Fluent support for shadow DOM.
 *
 * Fluent requires that a shadowRoot be connected before it can use Fluent.
 * Shadow roots will get connected automatically.
 *
 *******
 *
 * Automatic Fluent support for localized Reactive Properties
 *
 * When a Reactive Property can be set by fluent, set `fluent: true` in its
 * property definition and it will automatically be added to the data-l10n-attrs
 * attribute so that fluent will allow setting the attribute.
 *
 *******
 *
 * Test helper for sending events after a change: `dispatchOnUpdateComplete`
 *
 * When some async stuff is going on and you want to wait for it in a test, you
 * can use `this.dispatchOnUpdateComplete(myEvent)` and have the test wait on
 * your event.
 *
 * The component will then wait for your reactive property change to take effect
 * and dispatch the desired event.
 *
 * Example:
 *
 * async onClick() {
 *   let response = await this.getServerResponse(this.data);
 *   // Show the response status to the user.
 *   this.responseStatus = respose.status;
 *   this.dispatchOnUpdateComplete(
 *     new CustomEvent("status-shown")
 *   );
 * }
 *
 * add_task(async testButton() {
 *   let button = this.setupAndGetButton();
 *   button.click();
 *   await BrowserTestUtils.waitForEvent(button, "status-shown");
 * });
 */
export class MozLitElement extends LitElement {
  #l10nObj;
  #l10nRootConnected = false;

  static _finalizeFluentProperties() {
    if (!this.fluentProperties) {
      this.fluentProperties = [];
      for (let [propName, attributes] of this.elementProperties.entries()) {
        if (attributes.fluent) {
          this.fluentProperties.push(
            attributes.attribute || propName.toLowerCase()
          );
        }
      }
    }
  }

  constructor() {
    super();
    let { queries } = this.constructor;
    if (queries) {
      for (let [selectorName, selector] of Object.entries(queries)) {
        if (selector.all) {
          Object.defineProperty(this, selectorName, {
            get: queryAll(this, selector.all),
          });
        } else {
          Object.defineProperty(this, selectorName, {
            get: query(this, selector),
          });
        }
      }
    }
    this.constructor._finalizeFluentProperties();
  }

  connectedCallback() {
    super.connectedCallback();
    if (
      this.renderRoot == this.shadowRoot &&
      !this.#l10nRootConnected &&
      this.#l10n
    ) {
      this.#l10n.connectRoot(this.renderRoot);
      this.#l10nRootConnected = true;

      if (this.constructor.fluentProperties.length) {
        this.dataset.l10nAttrs = this.constructor.fluentProperties.join(",");
        if (this.dataset.l10nId) {
          this.#l10n.translateElements([this]);
        }
      }
    }
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    if (
      this.renderRoot == this.shadowRoot &&
      this.#l10nRootConnected &&
      this.#l10n
    ) {
      this.#l10n.disconnectRoot(this.renderRoot);
      this.#l10nRootConnected = false;
    }
  }

  get #l10n() {
    if (!this.#l10nObj) {
      this.#l10nObj =
        (window.Cu?.isInAutomation && window.mockL10n) || document.l10n;
    }
    return this.#l10nObj;
  }

  async dispatchOnUpdateComplete(event) {
    await this.updateComplete;
    this.dispatchEvent(event);
  }

  update() {
    super.update();
    if (this.#l10n) {
      this.#l10n.translateFragment(this.renderRoot);
    }
  }
}
PK
!<���/rr8chrome/toolkit/content/global/megalist/MegalistAlpha.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { html } from "chrome://global/content/vendor/lit.all.mjs";
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";

// eslint-disable-next-line import/no-unassigned-import
import "chrome://global/content/megalist/PasswordCard.mjs";

const DISPLAY_MODES = {
  ALERTS: "SortByAlerts",
  ALL: "SortByName",
};

export class MegalistAlpha extends MozLitElement {
  constructor() {
    super();
    this.selectedIndex = 0;
    this.searchText = "";
    this.records = [];
    this.header = null;
    this.displayMode = DISPLAY_MODES.ALL;

    window.addEventListener("MessageFromViewModel", ev =>
      this.#onMessageFromViewModel(ev)
    );
  }

  static get properties() {
    return {
      selectedIndex: { type: Number },
      searchText: { type: String },
      records: { type: Array },
      header: { type: Object },
      displayMode: { type: String },
    };
  }

  connectedCallback() {
    super.connectedCallback();
    this.#messageToViewModel("Refresh");
  }

  #onMessageFromViewModel({ detail }) {
    const functionName = `receive${detail.name}`;
    if (!(functionName in this)) {
      console.warn(`Received unknown message "${detail.name}"`);
    }
    this[functionName]?.(detail.data);
  }

  #onInputChange(e) {
    const searchText = e.target.value;
    this.searchText = searchText;
    this.#messageToViewModel("UpdateFilter", { searchText });
  }

  #onAddButtonClick() {
    // TODO: implement me!
  }

  #onRadioButtonChange(e) {
    this.displayMode = e.target.value;
    this.#sendCommand(this.displayMode);
  }

  #openMenu(e) {
    const panelList = this.shadowRoot.querySelector("panel-list");
    panelList.toggle(e);
  }

  #messageToViewModel(messageName, data) {
    window.windowGlobalChild
      .getActor("Megalist")
      .sendAsyncMessage(messageName, data);
  }

  #sendCommand(commandId, options = {}) {
    // TODO(Bug 1913302): snapshotId should be optional for global commands.
    // Right now, we always pass 0 and overwrite when needed.
    this.#messageToViewModel("Command", {
      commandId,
      snapshotId: 0,
      ...options,
    });
  }

  receiveShowSnapshots({ snapshots }) {
    const [header, records] = this.#createLoginRecords(snapshots);
    this.header = header;
    this.records = records;
  }

  receiveSnapshot({ snapshotId, snapshot }) {
    const recordIndex = Math.floor((snapshotId - 1) / 3);
    const field = snapshot.field;
    this.records[recordIndex][field] = snapshot;
    this.requestUpdate();
  }

  #createLoginRecords(snapshots) {
    const header = snapshots.shift();
    const records = [];

    for (let i = 0; i < snapshots.length; i += 3) {
      records.push({
        origin: snapshots[i],
        username: snapshots[i + 1],
        password: snapshots[i + 2],
      });
    }

    return [header, records];
  }

  // TODO: This should be passed to virtualized list with an explicit height.
  renderListItem({ origin: displayOrigin, username, password }) {
    return html`<password-card
      .origin=${displayOrigin}
      .username=${username}
      .password=${password}
      .messageToViewModel=${this.#messageToViewModel.bind(this)}
    ></password-card>`;
  }

  // TODO: Temporary. Should be rendered by the virtualized list.
  renderList() {
    return this.records.length
      ? html`
          <div class="passwords-list">
            ${this.records.map(record => this.renderListItem(record))}
          </div>
        `
      : "";
  }

  renderSearch() {
    return html`
      <div class="searchContainer">
        <div class="searchIcon"></div>
        <input
          class="search"
          type="search"
          data-l10n-id="filter-input"
          .value=${this.searchText}
          @input=${e => this.#onInputChange(e)}
        />
      </div>
    `;
  }

  renderFirstRow() {
    return html`<div class="first-row">
      ${this.renderSearch()}
      <moz-button
        @click=${this.#onAddButtonClick}
        data-l10n-id="create-login-button"
        type="icon"
        iconSrc="chrome://global/skin/icons/plus.svg"
      ></moz-button>
    </div>`;
  }

  renderRadioButtons() {
    return html`
      <div data-l10n-id="passwords-radiogroup-label" role="radiogroup">
        <input
          @change=${this.#onRadioButtonChange}
          checked
          type="radio"
          id="allLogins"
          name="logins"
          value=${DISPLAY_MODES.ALL}
        />
        <label
          for="allLogins"
          data-l10n-id="passwords-radiobutton-all"
          data-l10n-args=${JSON.stringify({ total: this.header.value.total })}
        ></label>

        <input
          @change=${this.#onRadioButtonChange}
          type="radio"
          id="alerts"
          name="logins"
          value=${DISPLAY_MODES.ALERTS}
        />
        <label
          for="alerts"
          data-l10n-id="passwords-radiobutton-alerts"
          data-l10n-args=${JSON.stringify({ total: this.header.value.alerts })}
        ></label>
      </div>
    `;
  }

  renderMenu() {
    return html`
      <moz-button
        @click=${this.#openMenu}
        type="icon ghost"
        iconSrc="chrome://global/skin/icons/more.svg"
        aria-expanded="false"
        aria-haspopup="menu"
        data-l10n-id="menu-more-options-button"
        id="more-options-menubutton"
      ></moz-button>
      <panel-list
        role="menu"
        aria-labelledby="more-options-menubutton"
        data-l10n-id="more-options-popup"
      >
        <panel-item
          action="import-from-browser"
          data-l10n-id="about-logins-menu-menuitem-import-from-another-browser"
          @click=${() => this.#sendCommand("ImportFromBrowser")}
        ></panel-item>
        <panel-item
          action="import-from-file"
          data-l10n-id="about-logins-menu-menuitem-import-from-a-file"
        ></panel-item>
        <panel-item
          action="export-logins"
          data-l10n-id="about-logins-menu-menuitem-export-logins2"
        ></panel-item>
        <panel-item
          action="remove-all-logins"
          data-l10n-id="about-logins-menu-menuitem-remove-all-logins2"
        ></panel-item>
        <hr />
        <panel-item
          action="open-preferences"
          data-l10n-id="menu-menuitem-preferences"
          @click=${() => this.#sendCommand("Settings")}
        ></panel-item>
        <panel-item
          action="open-help"
          data-l10n-id="about-logins-menu-menuitem-help"
          @click=${() => this.#sendCommand("Help")}
        ></panel-item>
      </panel-list>
    `;
  }

  renderSecondRow() {
    if (!this.header) {
      return "";
    }

    return html`<div class="second-row">
      ${this.renderRadioButtons()} ${this.renderMenu()}
    </div>`;
  }

  render() {
    return html`
      <link
        rel="stylesheet"
        href="chrome://global/content/megalist/megalist.css"
      />
      <div class="container">
        ${this.renderFirstRow()} ${this.renderSecondRow()} ${this.renderList()}
      </div>
    `;
  }
}

customElements.define("megalist-alpha", MegalistAlpha);
PK
!<v��B��7chrome/toolkit/content/global/megalist/PasswordCard.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { html } from "chrome://global/content/vendor/lit.all.mjs";
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";

/* eslint-disable-next-line import/no-unassigned-import, mozilla/no-browser-refs-in-toolkit */
import "chrome://browser/content/aboutlogins/components/input-field/login-origin-field.mjs";
/* eslint-disable-next-line import/no-unassigned-import, mozilla/no-browser-refs-in-toolkit */
import "chrome://browser/content/aboutlogins/components/input-field/login-username-field.mjs";
/* eslint-disable-next-line import/no-unassigned-import, mozilla/no-browser-refs-in-toolkit */
import "chrome://browser/content/aboutlogins/components/input-field/login-password-field.mjs";

export class PasswordCard extends MozLitElement {
  static properties = {
    origin: { type: Object },
    username: { type: Object },
    password: { type: Object },
    messageToViewModel: { type: Function },
  };

  #revealIconSrc(concealed) {
    return !concealed
      ? /* eslint-disable-next-line mozilla/no-browser-refs-in-toolkit */
        "chrome://browser/content/aboutlogins/icons/password-hide.svg"
      : /* eslint-disable-next-line mozilla/no-browser-refs-in-toolkit */
        "chrome://browser/content/aboutlogins/icons/password.svg";
  }

  handleCommand(commandId, lineIndex) {
    this.messageToViewModel("Command", { commandId, snapshotId: lineIndex });
  }

  onEditButtonClick() {
    // TODO: Implement me!
  }

  onCopyButtonClick(lineIndex) {
    this.handleCommand("Copy", lineIndex);
  }

  onPasswordRevealClick(concealed, lineIndex) {
    if (concealed) {
      this.handleCommand("Reveal", lineIndex);
    } else {
      this.handleCommand("Conceal", lineIndex);
    }
  }

  renderOriginField() {
    return html`<login-origin-field readonly .value=${this.origin.value}>
      <moz-button
        slot="actions"
        @click=${() => this.onCopyButtonClick(this.origin.lineIndex)}
        type="icon ghost"
        iconSrc="chrome://global/skin/icons/edit-copy.svg"
      ></moz-button>
    </login-origin-field>`;
  }

  renderUsernameField() {
    return html`<login-username-field readonly .value=${this.username.value}>
      <moz-button
        slot="actions"
        @click=${() => this.onCopyButtonClick(this.username.lineIndex)}
        @click=${this.onCopyButtonClick}
        type="icon ghost"
        iconSrc="chrome://global/skin/icons/edit-copy.svg"
      ></moz-button>
    </login-username-field>`;
  }

  renderPasswordField() {
    return html`<login-password-field
      readonly
      .value=${this.password.value}
      .visible=${!this.password.concealed}
    >
      <moz-button
        slot="actions"
        @click=${() =>
          this.onPasswordRevealClick(
            this.password.concealed,
            this.password.lineIndex
          )}
        type="icon ghost"
        iconSrc=${this.#revealIconSrc(this.password.concealed)}
      ></moz-button>
      <moz-button
        slot="actions"
        @click=${() => this.onCopyButtonClick(this.password.lineIndex)}
        type="icon ghost"
        iconSrc="chrome://global/skin/icons/edit-copy.svg"
      ></moz-button>
    </login-password-field>`;
  }

  renderButton() {
    return html`<moz-button
      data-l10n-id="login-item-edit-button"
      class="edit-button"
      @click=${this.onEditButtonClick}
    ></moz-button>`;
  }

  render() {
    return html` <link
        rel="stylesheet"
        href="chrome://global/content/megalist/megalist.css"
      />
      <moz-card>
        <div class="password-card-container">
          ${this.renderOriginField()} ${this.renderUsernameField()}
          ${this.renderPasswordField()} ${this.renderButton()}
        </div>
      </moz-card>`;
  }
}

customElements.define("password-card", PasswordCard);
PK
!<3	����3chrome/toolkit/content/global/megalist/megalist.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* Bug 1869845 - Styles in this file are still experimental!  */

.container {
  display: flex;
  flex-direction: column;
  padding: 20px;
}

virtualized-list {
  position: relative;
  overflow: auto;
  margin-block: 20px;
  padding-inline: 20px;
  flex-grow: 1;
  .lines-container {
    padding-inline-start: unset;
  }
}

.line {
  display: flex;
  align-items: stretch;
  position: absolute;
  width: calc(100% - 40px);
  user-select: none;
  box-sizing: border-box;
  height: 64px;

  background-color: var(--in-content-box-background-odd);
  border-inline: 1px solid var(--in-content-border-color);

  color: var(--in-content-text-color);

  &.start {
    border-block-start: 1px solid var(--in-content-border-color);
    border-start-start-radius: 8px;
    border-start-end-radius: 8px;
  }

  &.end {
    border-block-end: 1px solid var(--in-content-border-color);
    border-end-start-radius: 8px;
    border-end-end-radius: 8px;
    height: 54px;
  }

  > .menuButton {
    position: relative;
    visibility: hidden;

    > button {
      border: none;
      border-radius: 4px;
      margin-inline-start: 2px;
      padding: 2px;
      background-color: transparent;
      /* Fix: too lazy to load the svg  */
      width: 32px;
      color: unset;

      &:hover {
        background-color: var(--in-content-button-background-hover);
      }
    }

    > .menuPopup {
      position: absolute;
      inset-inline-end: 0;
      box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
      z-index: 1;
      background-color: var(--in-content-table-background);
      padding: 4px;

      > .separator {
        border-block-start: 1px solid var(--in-content-border-color);
        margin: 4px 0;
      }

      > button {
        text-align: start;
        border-style: none;
        padding: 12px;
        margin-block-end: 2px;
        width: 100%;
        text-wrap: nowrap;
      }
    }
  }

  > .content {
    flex-grow: 1;

    &:not(.section) {
      display: grid;
      grid-template-rows: max-content 1fr;
      grid-template-columns: max-content;
      grid-column-gap: 8px;
      padding-inline-start: 8px;
      padding-block-start: 4px;
    }

    > div {
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;

      &:last-child {
        padding-block-end: 10px;
      }
    }

    > .icon {
      border-radius: 4px;
      margin-inline-start: 4px;
      width: 16px;
      height: 16px;
      -moz-context-properties: fill;
      fill: currentColor;

      &:hover {
        background-color: var(--in-content-button-background-hover);
      }
    }

    > .label {
      color: var(--text-color-deemphasized);
      padding-block: 2px 4px;
      grid-row: 1;
      align-content: end;
    }

    > .value {
      user-select: text;

      > .icon {
        -moz-context-properties: fill;
        fill: currentColor;
        width: auto;
        height: 16px;
        margin-inline-end: 4px;
        vertical-align: text-bottom;
      }

      > .icon:not([src]) {
        display: none;
      }

      &:is(a) {
        color: currentColor;
      }
    }

    > .stickers {
      grid-row: 1;
      align-content: start;

      > span {
        padding: 4px;
        margin-inline-end: 2px;
        border-radius: 24px;
        font-size: xx-small;
      }

      /* Hard-coded colors will be addressed in FXCM-1013 */
      > span.risk {
        background-color: slateblue;
        border: 1px solid darkslateblue;
        color: whitesmoke;
      }

      > span.warning {
        background-color: firebrick;
        border: 1px solid maroon;
        color: whitesmoke;
      }

      > span.error {
        background-color: orange;
        border: 1px solid orangered;
        color: black;
      }
    }

    &.section {
      font-size: larger;
      overflow: hidden;
      display: flex;
      min-width: 0;

      > .label {
        display: inline-block;
        margin: 0;
        color: unset;
        margin-inline: 8px;
        align-content: start;
      }

      > .value {
        margin-inline-end: 8px;
        text-align: end;
        font-size: smaller;
        color: var(--text-color-deemphasized);
        user-select: unset;
        align-content: end;
        flex-grow: 1;
      }
    }
  }

  &.selected {
    color: var(--in-content-item-selected-text);
    background-color: var(--in-content-item-selected);

    > .menuButton {
      visibility: inherit;
    }
  }

  &:hover {
    color: var(--in-content-item-hover-text);
    background-color: var(--in-content-item-hover);

    > .menuButton {
      visibility: visible;
    }
  }
}

/* Dialog styles */
.dialog-overlay {
  display: flex;
  justify-content: center;
  align-items: center;
  padding: 16px;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 1;
  background-color: rgba(0, 0, 0, 0.5);
  box-sizing: border-box;
  /* TODO: probably want to remove this later ? */
  backdrop-filter: blur(6px);
}

.dialog-container {
  display: grid;
  padding: 16px 32px;
  color: var(--in-content-text-color);
  background-color: var(--in-content-box-background);
  border: 1px solid var(--in-content-border-color);
  box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.1);
}

.dialog-title {
  margin: 0;
}

.dismiss-button {
  justify-self: end;
}

.dialog-content {
  margin-block-start: 16px;
  margin-block-end: 16px;

  .checkbox-text {
    margin-block-start: 8px;
  }
}

/* Password Card styles */
password-card {
  margin-block: var(--space-small);
}

.passwords-list {
  display: flex;
  flex-direction: column;
}

.password-card-container {
  display: grid;
  grid-template-rows: 1fr 1fr 1fr;
  grid-gap: var(--space-medium);
}

.edit-button {
  justify-self: end;
}

.searchContainer {
  height: 32px;
  padding: 8px;
  cursor: text;
  border-radius: 4px;
  border: 1px solid var(--in-content-border-color);
  box-sizing: border-box;
  width: 100%;
  display: flex;

  &:focus-within {
    outline: 1px auto;
  }
}

.searchIcon {
  background-image: url(chrome://global/skin/icons/search-textbox.svg);
  background-position: center;
  background-repeat: no-repeat;
  fill: currentColor;
  -moz-context-properties: fill;
  width: 18px;
  margin-inline-end: 4px;
  flex-shrink: 0;
}

.search {
  border: none;
  background-color: transparent;
  outline: none;
}

.first-row {
  display: grid;
  grid-template-columns: 1fr min-content;
  grid-gap: 12px;
  align-items: center;
  margin-block-end: 20px;
}

.second-row {
  display: flex;
  justify-content: space-between;
}

.selected::part(button) {
  background-color: var(--button-background-color-active);
}
PK
!<ڳ�O��4chrome/toolkit/content/global/megalist/megalist.html<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
    />
    <script
      type="module"
      src="chrome://global/content/megalist/MegalistAlpha.mjs"
    ></script>
    <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
    <link
      rel="stylesheet"
      href="chrome://global/content/megalist/megalist.css"
    />
    <link rel="localization" href="preview/megalist.ftl" />
    <link rel="localization" href="browser/aboutLogins.ftl" />
  </head>

  <body>
    <megalist-alpha></megalist-alpha>

    <template id="lineElement">
      <li class="line">
        <div class="content"></div>
        <div class="menuButton">
          <button
            data-l10n-id="menu-more-options-button"
            aria-haspopup="true"
            aria-expanded="false"
          ></button>
        </div>
      </li>
    </template>

    <template id="collapsedSectionTemplate">
      <div class="content section">
        <img
          class="icon collapsed"
          draggable="false"
          src="chrome://global/skin/icons/arrow-down.svg"
        />
        <h4 class="label"></h4>
      </div>
    </template>

    <template id="expandedSectionTemplate">
      <div class="content section">
        <img
          class="icon expanded"
          draggable="false"
          src="chrome://global/skin/icons/arrow-up.svg"
        />
        <h4 class="label"></h4>
        <div class="value"></div>
      </div>
    </template>

    <template id="lineTemplate">
      <div class="content">
        <div class="label"></div>
        <div class="stickers"></div>
        <div class="value">
          <img class="icon" />
          <span></span>
        </div>
      </div>
    </template>

    <template id="editingLineTemplate">
      <div class="content">
        <div class="label"></div>
        <div class="value">
          <img class="icon" />
          <input type="text" />
        </div>
        <div class="stickers"></div>
      </div>
    </template>

    <template id="dialog-template">
      <div class="dialog-overlay">
        <div class="dialog-container">
          <moz-button
            data-l10n-id="confirmation-dialog-dismiss-button"
            iconSrc="chrome://global/skin/icons/close.svg"
            size="small"
            type="icon ghost"
            class="dismiss-button"
            close-dialog
          >
          </moz-button>
          <div class="dialog-wrapper"></div>
        </div>
      </div>
    </template>

    <template id="remove-logins-dialog-template">
      <h2
        class="dialog-title"
        data-l10n-id="about-logins-confirm-remove-all-sync-dialog-title2"
        localizable
      ></h2>
      <div class="dialog-content" slot="dialog-content">
        <p data-l10n-id="about-logins-confirm-export-dialog-message2"></p>
        <label>
          <input type="checkbox" class="confirm-checkbox checkbox" autofocus />
          <span
            class="checkbox-text"
            data-l10n-id="about-logins-confirm-remove-all-dialog-checkbox-label2"
          ></span>
        </label>
      </div>
      <moz-button-group>
        <button
          class="primary danger-button"
          data-l10n-id="about-logins-confirm-remove-all-dialog-confirm-button-label"
          data-command="LoginDataSource.confirmRemoveAll"
        ></button>
        <button
          close-dialog
          data-l10n-id="confirmation-dialog-cancel-button"
        ></button>
      </moz-button-group>
    </template>

    <template id="remove-login-dialog-template">
      <h2
        class="dialog-title"
        data-l10n-id="about-logins-confirm-delete-dialog-title"
      ></h2>
      <div class="dialog-content" slot="dialog-content">
        <p data-l10n-id="about-logins-confirm-delete-dialog-message"></p>
      </div>
      <moz-button-group>
        <button
          class="primary danger-button"
          data-l10n-id="about-logins-confirm-remove-dialog-confirm-button"
          data-command="LoginDataSource.confirmRemoveLogin"
        ></button>
        <button
          close-dialog
          data-l10n-id="confirmation-dialog-cancel-button"
        ></button>
      </moz-button-group>
    </template>

    <template id="export-logins-dialog-template">
      <h2
        class="dialog-title"
        data-l10n-id="about-logins-confirm-export-dialog-title2"
      ></h2>
      <div class="dialog-content" slot="dialog-content">
        <p data-l10n-id="about-logins-confirm-export-dialog-message2"></p>
      </div>
      <moz-button-group>
        <button
          class="primary danger-button"
          data-l10n-id="about-logins-confirm-export-dialog-confirm-button2"
          data-command="LoginDataSource.confirmExportLogins"
        ></button>
        <button
          close-dialog
          data-l10n-id="confirmation-dialog-cancel-button"
        ></button>
      </moz-button-group>
    </template>

    <template id="import-logins-dialog-template">
      <h2
        class="dialog-title"
        data-l10n-id="about-logins-import-dialog-title"
      ></h2>
      <div class="dialog-content">
        <div data-l10n-id="about-logins-import-dialog-items-added2" localizable>
          <span></span>
          <span data-l10n-name="count"></span>
        </div>
        <div
          data-l10n-id="about-logins-import-dialog-items-modified2"
          localizable
        >
          <span></span>
          <span data-l10n-name="count"></span>
        </div>
        <div
          data-l10n-id="about-logins-import-dialog-items-no-change2"
          data-l10n-name="no-change"
          localizable
        >
          <span></span>
          <span data-l10n-name="count"></span>
          <span data-l10n-name="meta"></span>
        </div>
        <div data-l10n-id="about-logins-import-dialog-items-error" localizable>
          <span></span>
          <span data-l10n-name="count"></span>
          <span data-l10n-name="meta"></span>
        </div>
        <a
          class="open-detailed-report"
          href="about:loginsimportreport"
          target="_blank"
          data-l10n-id="about-logins-alert-import-message"
        ></a>
      </div>
      <button
        class="primary"
        data-l10n-id="about-logins-import-dialog-done"
        close-dialog
      ></button>
    </template>

    <template id="import-error-dialog-template">
      <h2
        class="dialog-title"
        data-l10n-id="about-logins-import-dialog-error-title"
      ></h2>
      <div class="dialog-content">
        <p
          data-l10n-id="about-logins-import-dialog-error-file-format-title"
        ></p>
        <p
          data-l10n-id="about-logins-import-dialog-error-file-format-description"
        ></p>
        <p
          data-l10n-id="about-logins-import-dialog-error-no-logins-imported"
        ></p>
        <a
          class="error-learn-more-link"
          href="https://support.mozilla.org/kb/import-login-data-file"
          data-l10n-id="about-logins-import-dialog-error-learn-more"
          target="_blank"
          rel="noreferrer"
        ></a>
      </div>
      <moz-button-group>
        <button
          class="primary"
          data-l10n-id="about-logins-import-dialog-error-try-import-again"
          data-command="LoginDataSource.confirmRetryImport"
        ></button>
        <button
          close-dialog
          data-l10n-id="confirmation-dialog-cancel-button"
        ></button>
      </moz-button-group>
    </template>
  </body>
</html>
PK
!<�G(�.chrome/toolkit/content/global/ml/MLEngine.html<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome: resource:; object-src 'none'"
    />
    <!-- Run the machine learning inference engine in its own singleton content process. -->
  </head>
  <body></body>
</html>
PK
!<��3�QQ4chrome/toolkit/content/global/ml/MLEngine.worker.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(
  lazy,
  {
    PromiseWorker: "resource://gre/modules/workers/PromiseWorker.mjs",
    Pipeline: "chrome://global/content/ml/ONNXPipeline.mjs",
    PipelineOptions: "chrome://global/content/ml/EngineProcess.sys.mjs",
    modelToResponse: "chrome://global/content/ml/Utils.sys.mjs",
  },
  { global: "current" }
);

/**
 * The actual MLEngine lives here in a worker.
 */
class MLEngineWorker {
  #pipeline;

  constructor() {
    // Connect the provider to the worker.
    this.#connectToPromiseWorker();
  }

  /**
   * Implements the `match` function from the Cache API for Transformers.js custom cache.
   *
   * See https://developer.mozilla.org/en-US/docs/Web/API/Cache
   *
   * Attempts to match and retrieve a model file based on a provided key.
   * Fetches a model file by delegating the call to the worker's main thread.
   * Then wraps the fetched model file into a response object compatible with Transformers.js expectations.
   *
   * @param {string} key The unique identifier for the model to fetch.
   * @returns {Promise<Response|null>} A promise that resolves with a Response object containing the model file or null if not found.
   */
  async match(key) {
    // if the key starts with NO_LOCAL, we return null immediately to tell transformers.js
    // we don't server local files, and it will do a second call with the full URL:w
    if (key.startsWith("NO_LOCAL")) {
      return null;
    }
    let res = await this.getModelFile(key);
    if (res.fail) {
      return null;
    }

    // Transformers.js expects a response object, so we wrap the array buffer
    return lazy.modelToResponse(res.ok[2], res.ok[1]);
  }

  async getModelFile(...args) {
    let result = await self.callMainThread("getModelFile", args);
    return result;
  }

  /**
   * Placeholder for the `put` method from the Cache API for Transformers.js custom cache.
   *
   * @throws {Error} Always thrown to indicate the method is not implemented.
   */
  put() {
    throw new Error("Method not implemented.");
  }

  /**
   * @param {ArrayBuffer} wasm
   * @param {object} options received as an object, converted to a PipelineOptions instance
   */
  async initializeEngine(wasm, options) {
    this.#pipeline = await lazy.Pipeline.initialize(
      this,
      wasm,
      new lazy.PipelineOptions(options)
    );
  }
  /**
   * Run the worker.
   *
   * @param {string} request
   */
  async run(request) {
    if (request === "throw") {
      throw new Error(
        'Received the message "throw", so intentionally throwing an error.'
      );
    }
    return await this.#pipeline.run(request);
  }

  /**
   * Glue code to connect the `MLEngineWorker` to the PromiseWorker interface.
   */
  #connectToPromiseWorker() {
    const worker = new lazy.PromiseWorker.AbstractWorker();
    worker.dispatch = (method, args = []) => {
      if (!this[method]) {
        throw new Error("Method does not exist: " + method);
      }
      return this[method](...args);
    };
    worker.close = () => self.close();
    worker.postMessage = (message, ...transfers) => {
      self.postMessage(message, ...transfers);
    };

    self.callMainThread = worker.callMainThread.bind(worker);
    self.addEventListener("message", msg => worker.handleMessage(msg));
    self.addEventListener("unhandledrejection", function (error) {
      throw error.reason?.fail ?? error.reason;
    });
  }
}

new MLEngineWorker();
PK
!<��75v�v�1chrome/toolkit/content/global/ml/ModelHub.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

/**
 * @typedef {import("./Utils.sys.mjs").ProgressAndStatusCallbackParams} ProgressAndStatusCallbackParams
 */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
  ObjectUtils: "resource://gre/modules/ObjectUtils.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
  Progress: "chrome://global/content/ml/Utils.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  return console.createInstance({
    maxLogLevelPref: "browser.ml.logLevel",
    prefix: "ML:ModelHub",
  });
});

const ALLOWED_HUBS = [
  "chrome://*",
  "resource://*",
  "http://localhost",
  "https://localhost",
  "https://model-hub.mozilla.org",
];

const ALLOWED_HEADERS_KEYS = [
  "Content-Type",
  "ETag",
  "status",
  "fileSize", // the size in bytes we store
  "Content-Length", // the size we download (can be different when gzipped)
];
const DEFAULT_URL_TEMPLATE = "{model}/resolve/{revision}";

// Default indexedDB revision.
const DEFAULT_MODEL_REVISION = 2;

// The origin to use for storage. If null uses system.
const DEFAULT_PRINCIPAL_ORIGIN = null;

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "DEFAULT_MAX_CACHE_SIZE",
  "browser.ml.modelCacheMaxSizeBytes"
);

/**
 * Checks if a given URL string corresponds to an allowed hub.
 *
 * This function validates a URL against a list of allowed hubs, ensuring that it:
 * - Is well-formed according to the URL standard.
 * - Does not include a username or password.
 * - Matches the allowed scheme and hostname.
 *
 * @param {string} urlString The URL string to validate.
 * @returns {boolean} True if the URL is allowed; false otherwise.
 */
function allowedHub(urlString) {
  if (Services.env.exists("MOZ_ALLOW_EXTERNAL_ML_HUB")) {
    return true;
  }

  try {
    const url = new URL(urlString);
    // Check for username or password in the URL
    if (url.username !== "" || url.password !== "") {
      return false; // Reject URLs with username or password
    }
    const scheme = url.protocol;
    const host = url.hostname;
    const fullPrefix = `${scheme}//${host}`;

    return ALLOWED_HUBS.some(allowedHub => {
      const [allowedScheme, allowedHost] = allowedHub.split("://");
      if (allowedHost === "*") {
        return `${allowedScheme}:` === scheme;
      }
      const allowedPrefix = `${allowedScheme}://${allowedHost}`;
      return fullPrefix === allowedPrefix;
    });
  } catch (error) {
    lazy.console.error("Error parsing URL:", error);
    return false;
  }
}

const NO_ETAG = "NO_ETAG";

/**
 * Class for managing a cache stored in IndexedDB.
 */
export class IndexedDBCache {
  /**
   * Reference to the IndexedDB database.
   *
   * @type {IDBDatabase|null}
   */
  db = null;

  /**
   * Version of the database. Null if not set.
   *
   * @type {number|null}
   */
  dbVersion = null;

  /**
   * Name of the database used by IndexedDB.
   *
   * @type {string}
   */
  dbName;

  /**
   * Name of the object store for storing files.
   * Files are expected to be unique for a given tuple of file name, model, file, revision
   *
   * @type {string}
   */
  fileStoreName;

  /**
   * Name of the object store for storing headers.
   * Headers are expected to be unique for a given triplet model, file, revision
   *
   * @type {string}
   */
  headersStoreName;

  /**
   * Name of the object store for storing task names.
   * Tasks are expected to be unique for a given tuple of task name, model, file, revision
   *
   * @type {string}
   */
  taskStoreName;

  /**
   * Name and KeyPath for indices to be created on object stores.
   *
   * @type {object}
   */
  #indices = {
    modelRevisionIndex: {
      name: "modelRevisionIndex",
      keyPath: ["model", "revision"],
    },
    modelRevisionFileIndex: {
      name: "modelRevisionFileIndex",
      keyPath: ["model", "revision", "file"],
    },
    taskModelRevisionIndex: {
      name: "taskModelRevisionIndex",
      keyPath: ["taskName", "model", "revision"],
    },
  };
  /**
   * Maximum size of the cache in bytes. Defaults to "browser.ml.modelCacheMaxSizeBytes".
   *
   * @type {number}
   */
  #maxSize = lazy.DEFAULT_MAX_CACHE_SIZE;

  /**
   * Private constructor to prevent direct instantiation.
   * Use IndexedDBCache.init to create an instance.
   *
   * @param {object} config
   * @param {string} config.dbName - The name of the database file.
   * @param {number} config.version - The version number of the database.
   * @param {number} config.maxSize Maximum size of the cache in bytes. Defaults to "browser.ml.modelCacheMaxSizeBytes".
   */
  constructor({
    dbName = "modelFiles",
    version = DEFAULT_MODEL_REVISION,
    maxSize = lazy.DEFAULT_MAX_CACHE_SIZE,
  } = {}) {
    this.dbName = dbName;
    this.dbVersion = version;
    this.fileStoreName = "files";
    this.headersStoreName = "headers";
    this.taskStoreName = "tasks";
    this.#maxSize = maxSize;
  }

  /**
   * Static method to create and initialize an instance of IndexedDBCache.
   *
   * @param {object} config
   * @param {string} [config.dbName="modelFiles"] - The name of the database.
   * @param {number} [config.version] - The version number of the database.
   * @param {number} config.maxSize Maximum size of the cache in bytes. Defaults to "browser.ml.modelCacheMaxSizeBytes".
   * @returns {Promise<IndexedDBCache>} An initialized instance of IndexedDBCache.
   */
  static async init({
    dbName = "modelFiles",
    version = DEFAULT_MODEL_REVISION,
    maxSize = lazy.DEFAULT_MAX_CACHE_SIZE,
  } = {}) {
    const cacheInstance = new IndexedDBCache({
      dbName,
      version,
      maxSize,
    });
    cacheInstance.db = await cacheInstance.#openDB();

    return cacheInstance;
  }

  /**
   * Called to close the DB connection and dispose the instance
   *
   */
  async dispose() {
    if (this.db) {
      this.db.close();
      this.db = null;
    }
  }

  #migrateStore(db, oldVersion) {
    const newVersion = db.version;
    // Delete all existing data when migrating from 1 to 2
    if (oldVersion == 1 && newVersion == 2) {
      // Version 1 may contains task depe
      for (const name of [
        this.fileStoreName,
        this.headersStoreName,
        this.taskStoreName,
      ]) {
        if (db.objectStoreNames.contains(name)) {
          db.deleteObjectStore(name);
        }
      }
    }
  }

  #createOrMigrateIndices({ store, name, keyPath }) {
    if (store.indexNames.contains(name)) {
      if (!lazy.ObjectUtils.deepEqual(store.index(name).keyPath, keyPath)) {
        lazy.console.debug(
          `Deleting and recreating index ${name} on store ${store.name}`
        );
        store.deleteIndex(name);
        store.createIndex(name, keyPath);
      }
    } else {
      // index does not exist, so create
      lazy.console.debug(`Creating index ${name} on store ${store.name}`);
      store.createIndex(name, keyPath);
    }
  }

  /**
   * Enable persistence for a principal.
   *
   * @param {Ci.nsIPrincipal} principal - The principal
   * @returns {Promise<boolean>} Wether persistence was successfully enabled.
   */

  async #ensurePersistentStorage(principal) {
    try {
      const { promise, resolve, reject } = Promise.withResolvers();
      const request = Services.qms.persist(principal);

      request.callback = () => {
        if (request.resultCode === Cr.NS_OK) {
          resolve();
        } else {
          reject(
            new Error(
              `Failed to persist storage for principal: ${principal.originNoSuffix}`
            )
          );
        }
      };

      await promise;
      return true;
    } catch (error) {
      lazy.console.error("An unexpected error occurred:", error);
      return false;
    }
  }

  /**
   * Opens or creates the IndexedDB database.
   *
   * @returns {Promise<IDBDatabase>}
   */
  async #openDB() {
    return new Promise((resolve, reject) => {
      const principal = DEFAULT_PRINCIPAL_ORIGIN
        ? Services.scriptSecurityManager.createContentPrincipalFromOrigin(
            DEFAULT_PRINCIPAL_ORIGIN
          )
        : Services.scriptSecurityManager.getSystemPrincipal();

      if (DEFAULT_PRINCIPAL_ORIGIN) {
        this.#ensurePersistentStorage(principal);
      }

      const request = indexedDB.openForPrincipal(
        principal,
        this.dbName,
        this.dbVersion
      );
      request.onerror = event => reject(event.target.error);
      request.onsuccess = event => {
        const db = event.target.result;
        // This is called when a version upgrade event is sent from elsewhere
        // for example from another tab/window from the same computer.
        db.onversionchange = _onVersionChangeevent => {
          lazy.console.debug(
            "The version of this database is changing. Closing."
          );
          // Closing allow the change from elsewhere to go through and invalidate
          // this version.
          db.close();
        };
        return resolve(event.target.result);
      };
      // If you make any change to onupgradeneeded, then you must change
      // the version of the database, otherwise, the changes would not apply.
      request.onupgradeneeded = event => {
        const db = event.target.result;

        // Migrating is required anytime the keyPath for an existing store changes
        this.#migrateStore(db, event.oldVersion);

        if (!db.objectStoreNames.contains(this.fileStoreName)) {
          db.createObjectStore(this.fileStoreName, { keyPath: "id" });
        }
        if (!db.objectStoreNames.contains(this.headersStoreName)) {
          db.createObjectStore(this.headersStoreName, {
            keyPath: ["model", "revision", "file"],
          });
        }

        const headerStore = request.transaction.objectStore(
          this.headersStoreName
        );

        this.#createOrMigrateIndices({
          store: headerStore,
          name: this.#indices.modelRevisionIndex.name,
          keyPath: this.#indices.modelRevisionIndex.keyPath,
        });

        if (!db.objectStoreNames.contains(this.taskStoreName)) {
          db.createObjectStore(this.taskStoreName, {
            keyPath: ["taskName", "model", "revision", "file"],
          });
        }

        const taskStore = request.transaction.objectStore(this.taskStoreName);
        for (const { name, keyPath } of Object.values(this.#indices)) {
          this.#createOrMigrateIndices({ store: taskStore, name, keyPath });
        }
      };
    });
  }

  /**
   * Generic method to get the data from a specified object store.
   *
   * @param {object} config
   * @param {string} config.storeName - The name of the object store.
   * @param {string} config.key - The key within the object store to retrieve the data from.
   * @param {?string} config.indexName - The store index to use.
   * @returns {Promise<Array<any>>}
   */
  async #getData({ storeName, key, indexName }) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([storeName], "readonly");
      const store = transaction.objectStore(storeName);
      const request = (indexName ? store.index(indexName) : store).getAll(key);
      request.onerror = event => reject(event.target.error);
      request.onsuccess = event => resolve(event.target.result);
    });
  }

  /**
   * Generic method to get the unique keys from a specified object store.
   *
   * @param {object} config
   * @param {string} config.storeName - The name of the object store.
   * @param {string} config.key - The key within the object store to retrieve the data from.
   * @param {?string} config.indexName - The store index to use.
   * @param {?function(IDBCursor):boolean} config.filterFn - A function to execute for each key found.
   *  It should return a truthy value to keep the key, and a falsy value otherwise.
   *
   * @returns {Promise<Array<{primaryKey:any, key:any}>>}
   */
  async #getKeys({ storeName, key, indexName, filterFn }) {
    const keys = [];
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([storeName], "readonly");
      const store = transaction.objectStore(storeName);
      const request = (
        indexName ? store.index(indexName) : store
      ).openKeyCursor(key, "nextunique");
      request.onerror = event => reject(event.target.error);
      request.onsuccess = event => {
        const cursor = event.target.result;

        if (cursor) {
          if (!filterFn || filterFn(cursor)) {
            keys.push({ primaryKey: cursor.primaryKey, key: cursor.key });
          }

          cursor.continue();
        } else {
          resolve(keys);
        }
      };
    });
  }

  /**
   * Generic method to check if data exists from a specified object store.
   *
   * @param {object} config
   * @param {string} config.storeName - The name of the object store.
   * @param {string} config.key - The key within the object store to retrieve the data from.
   * @param {?string} config.indexName - The store index to use.
   * @returns {Promise<boolean>} A promise that resolves with `true` if the key exists, otherwise `false`.
   */
  async #hasData({ storeName, key, indexName }) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([storeName], "readonly");
      const store = transaction.objectStore(storeName);
      const request = (indexName ? store.index(indexName) : store).getKey(key);
      request.onerror = event => reject(event.target.error);
      request.onsuccess = event => resolve(event.target.result !== undefined);
    });
  }

  // Used in tests
  async _testGetData(storeName, key) {
    return (await this.#getData({ storeName, key }))[0];
  }

  /**
   * Generic method to update data in a specified object store.
   *
   * @param {string} storeName - The name of the object store.
   * @param {object} data - The data to store.
   * @returns {Promise<void>}
   */
  async #updateData(storeName, data) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([storeName], "readwrite");
      const store = transaction.objectStore(storeName);
      const request = store.put(data);
      request.onerror = event => reject(event.target.error);
      request.onsuccess = () => resolve();
    });
  }

  /**
   * Deletes a specific cache entry.
   *
   * @param {string} storeName - The name of the object store.
   * @param {string} key - The key of the entry to delete.
   * @returns {Promise<void>}
   */
  async #deleteData(storeName, key) {
    return new Promise((resolve, reject) => {
      const transaction = this.db.transaction([storeName], "readwrite");
      const store = transaction.objectStore(storeName);
      const request = store.delete(key);
      request.onerror = event => reject(event.target.error);
      request.onsuccess = () => resolve();
    });
  }

  /**
   * Generates an IndexedDB query to retrieve entries from the appropriate index.
   *
   * @param {object} config - Configuration object.
   * @param {?string} config.taskName - The name of the inference task. Retrieves all tasks if null.
   * @param {?string} config.model - The model name (organization/name). Retrieves all models if null.
   * @param {?string} config.revision - The model revision. Retrieves all revisions if null.
   *
   * @returns {object} queryIndex - The query and index for retrieving entries.
   * @returns {?string} queryIndex.query - The query.
   * @returns {?string} queryIndex.indexName - The index name.
   */
  #getFileQuery({ taskName, model, revision }) {
    // See https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange for explanation on the query.

    // Case 1: Query to retrieve all entries matching taskName, model, and revision
    if (taskName && model && revision) {
      return {
        key: [taskName, model, revision],
        indexName: this.#indices.taskModelRevisionIndex.name,
      };
    }
    // Case 2: Query to retrieve all entries with taskName
    if (taskName && !model && !revision) {
      return {
        key: IDBKeyRange.bound([taskName], [taskName, []]),
        indexName: this.#indices.taskModelRevisionIndex.name,
      };
    }
    // Case 3: Query to retrieve all entries matching model and revision
    if (!taskName && model && revision) {
      return {
        key: [model, revision],
        indexName: this.#indices.modelRevisionIndex.name,
      };
    }
    // Case 4: Query to retrieve all entries
    if (!taskName && !model && !revision) {
      return { key: null, indexName: null };
    }
    // Case 5: Query to retrieve all entries matching taskName and model
    if (taskName && model && !revision) {
      return {
        key: IDBKeyRange.bound([taskName, model], [taskName, model, []]),
        indexName: this.#indices.taskModelRevisionIndex.name,
      };
    }
    throw new Error("Invalid query configuration.");
  }

  /**
   * Checks if a specified model file exists in storage.
   *
   * @param {object} config
   * @param {string} config.model - The model name (organization/name)
   * @param {string} config.revision - The model revision.
   * @param {string} config.file - The file name.
   * @returns {Promise<boolean>} A promise that resolves with `true` if the key exists, otherwise `false`.
   */
  async fileExists({ model, revision, file }) {
    return this.#hasData({
      storeName: this.fileStoreName,
      key: this.#generatePrimaryKey({ model, revision, file }),
    });
  }

  /**
   * Generate the primary key to uniquely identify a model file.
   *
   * @param {object} config
   * @param {string} config.model - The model name (organization/name)
   * @param {string} config.revision - The model revision.
   * @param {string} config.file - The file name.
   * @returns {string} The generated primary key.
   */
  #generatePrimaryKey({ model, revision, file }) {
    return `${model}/${revision}/${file}`;
  }

  /**
   * Retrieves the headers for a specific cache entry.
   *
   * @param {object} config
   * @param {string} config.model - The model name (organization/name)
   * @param {string} config.revision - The model revision.
   * @param {string} config.file - The file name.
   * @returns {Promise<object|null>} The headers or null if not found.
   */
  async getHeaders({ model, revision, file }) {
    return (
      await this.#getData({
        storeName: this.headersStoreName,
        key: [model, revision, file],
      })
    )[0]?.headers;
  }

  /**
   * Retrieves the file for a specific cache entry.
   *
   * @param {object} config
   * @param {string} config.model - The model name (organization/name)
   * @param {string} config.revision - The model revision.
   * @param {string} config.file - The file name.
   * @returns {Promise<[ArrayBuffer, object]|null>} The file ArrayBuffer and its headers or null if not found.
   */
  async getFile({ model, revision, file }) {
    const cacheKey = this.#generatePrimaryKey({ model, revision, file });
    const stored = (
      await this.#getData({ storeName: this.fileStoreName, key: cacheKey })
    )[0];
    if (stored) {
      const headers = await this.getHeaders({ model, revision, file });
      return [stored.data, headers];
    }
    return null; // Return null if no file is found
  }

  /**
   * Adds or updates task entry.
   *
   * @param {object} config
   * @param {string} config.taskName - name of the inference task.
   * @param {string} config.model - The model name (organization/name).
   * @param {string} config.revision - The model version.
   * @param {string} config.file - The file name.
   * @returns {Promise<void>}
   */
  async updateTask({ taskName, model, revision, file }) {
    await this.#updateData(this.taskStoreName, {
      taskName,
      model,
      revision,
      file,
    });
  }

  /**
   * Estimate the disk size in bytes for a domain origin. If no origin is provided, assume the system origin.
   *
   * @param {?string} origin - The origin.
   * @returns {Promise<number>} The estimated size.
   */
  async #estimateUsageForOrigin(origin) {
    const { promise, resolve, reject } = Promise.withResolvers();
    try {
      const principal = origin
        ? Services.scriptSecurityManager.createContentPrincipalFromOrigin(
            origin
          )
        : Services.scriptSecurityManager.getSystemPrincipal();
      Services.qms.getUsageForPrincipal(principal, request => {
        if (request.resultCode == Cr.NS_OK) {
          resolve(request.result.usage);
        } else {
          reject(new Error(request.resultCode));
        }
      });
    } catch (error) {
      reject(new Error(`An unexpected error occurred: ${error.message}`));
    }

    return promise;
  }

  /**
   * Adds or updates a cache entry.
   *
   * @param {object} config
   * @param {string} config.taskName - name of the inference task.
   * @param {string} config.model - The model name (organization/name).
   * @param {string} config.revision - The model version.
   * @param {string} config.file - The file name.
   * @param {Blob} config.data - The data to cache.
   * @param {object} [config.headers] - The headers for the file.
   * @returns {Promise<void>}
   */
  async put({ taskName, model, revision, file, data, headers }) {
    const updatePromises = [];
    const fileSize = data.size;
    const cacheKey = this.#generatePrimaryKey({ model, revision, file });
    const totalSize = await this.#estimateUsageForOrigin(
      DEFAULT_PRINCIPAL_ORIGIN
    );

    if (totalSize + fileSize > this.#maxSize) {
      throw new Error(`Exceeding cache size limit of ${this.#maxSize} bytes"`);
    }

    const fileEntry = { id: cacheKey, data };

    // Store the file data
    lazy.console.debug(`Storing ${cacheKey} with size:`, file);
    updatePromises.push(this.#updateData(this.fileStoreName, fileEntry));

    // Store task metadata
    updatePromises.push(
      this.updateTask({
        taskName,
        model,
        revision,
        file,
      })
    );

    // Update headers store - whith defaults for ETag and Content-Type
    headers = headers || {};
    headers["Content-Type"] =
      headers["Content-Type"] ?? "application/octet-stream";
    headers.fileSize = fileSize;
    headers.ETag = headers.ETag ?? NO_ETAG;

    // filter out any keys that are not allowed
    headers = Object.keys(headers)
      .filter(key => ALLOWED_HEADERS_KEYS.includes(key))
      .reduce((obj, key) => {
        obj[key] = headers[key];
        return obj;
      }, {});

    lazy.console.debug(`Storing ${cacheKey} with headers:`, headers);

    // Update headers
    updatePromises.push(
      this.#updateData(this.headersStoreName, {
        model,
        revision,
        file,
        headers,
      })
    );

    await Promise.all(updatePromises);
  }
  /**
   * Deletes all data related to the specifed models.
   *
   * @param {object} config
   *
   * @param {?string} config.model - The model name (organization/name) to delete.
   * @param {?string} config.revision - The model version to delete.
   *  If both model and revision are null, delete models of any name and version.
   *
   * @param {?string} config.taskName - name of the inference task to delete.
   *                                    If null, delete specified models for all tasks.
   *
   * @param {?function(IDBCursor):boolean} config.filterFn - A function to execute for each model file candidate for deletion.
   * It should return a truthy value to delete the model file, and a falsy value otherwise.
   *
   * @throws {Error} If a revision is defined, the model must also be defined.
   *                 If the model is not defined, the revision should also not be defined.
   *                 Otherwise, an error will be thrown.

   * @returns {Promise<void>}
   */
  async deleteModels({ taskName, model, revision, filterFn }) {
    const tasks = await this.#getData({
      storeName: this.taskStoreName,
      ...this.#getFileQuery({ taskName, model, revision }),
    });

    let deletePromises = [];
    const filesToMaybeDelete = new Set();
    for (const { taskName, model, revision, file } of tasks) {
      if (filterFn && !filterFn({ taskName, model, revision, file })) {
        continue;
      }
      filesToMaybeDelete.add(JSON.stringify([model, revision, file]));
      deletePromises.push(
        this.#deleteData(this.taskStoreName, [taskName, model, revision, file])
      );
    }
    await Promise.all(deletePromises);

    deletePromises = [];

    const remainingFileKeys = await this.#getKeys({
      storeName: this.taskStoreName,
      indexName: this.#indices.modelRevisionFileIndex.name,
    });

    const remainingFiles = new Set();

    for (const { key } of remainingFileKeys) {
      remainingFiles.add(JSON.stringify(key));
    }

    const filesToDelete = filesToMaybeDelete.difference(remainingFiles);

    for (const key of filesToDelete) {
      const [model, revision, file] = JSON.parse(key);

      deletePromises.push(
        this.#deleteData(
          this.fileStoreName,
          this.#generatePrimaryKey({ model, revision, file })
        )
      );

      deletePromises.push(
        this.#deleteData(this.headersStoreName, [model, revision, file])
      );
    }

    await Promise.all(deletePromises);
  }

  /**
   * Lists all files for a given model and revision stored in the cache.
   *
   * @param {object} config
   * @param {?string} config.model - The model name (organization/name).
   * @param {?string} config.revision - The model version.
   * @param {?string} config.taskName - name of the inference :wtask.
   * @returns {Promise<Array<{path:string, headers: object}>>} An array of file identifiers.
   */
  async listFiles({ taskName, model, revision }) {
    let modelRevisions = [{ model, revision }];

    if (taskName) {
      // Get all model/revision associated to this task.
      const data = await this.#getKeys({
        storeName: this.taskStoreName,
        ...this.#getFileQuery({ taskName, model, revision }),
      });

      modelRevisions = [];
      for (const { key } of data) {
        modelRevisions.push({ model: key[1], revision: key[2] });
      }
    }

    const filePromises = [];

    for (const { model, revision } of modelRevisions) {
      filePromises.push(
        this.#getData({
          storeName: this.headersStoreName,
          indexName: this.#indices.modelRevisionIndex.name,
          key: [model, revision],
        })
      );
    }

    const data = (await Promise.all(filePromises)).flat();

    const files = [];
    for (const { file: path, headers } of data) {
      files.push({ path, headers });
    }

    return files;
  }

  /**
   * Lists all models stored in the cache.
   *
   * @returns {Promise<Array<{name:string, revision:string}>>} An array of model identifiers.
   */
  async listModels() {
    const modelRevisions = await this.#getKeys({
      storeName: this.taskStoreName,
      indexName: this.#indices.modelRevisionIndex.name,
    });

    const models = [];

    for (const { key } of modelRevisions) {
      models.push({ name: key[0], revision: key[1] });
    }
    return models;
  }
}

export class ModelHub {
  /**
   * Create an instance of ModelHub.
   *
   * @param {object} config
   * @param {string} config.rootUrl - Root URL used to download models.
   * @param {string} config.urlTemplate - The template to retrieve the full URL using a model name and revision.
   */
  constructor({ rootUrl, urlTemplate = DEFAULT_URL_TEMPLATE } = {}) {
    // Early error when the hub is created on a disallowed url - #fileURL also checks this so API calls with custom hubs are also covered.
    if (!allowedHub(rootUrl)) {
      throw new Error(`Invalid model hub root url: ${rootUrl}`);
    }
    this.rootUrl = rootUrl;
    this.cache = null;

    // Ensures the URL template is well-formed and does not contain any invalid characters.
    const pattern = /^(?:\{\w+\}|\w+)(?:\/(?:\{\w+\}|\w+))*$/;
    //               ^                                         $   Start and end of string
    //                (?:\{\w+\}|\w+)                            Match a {placeholder} or alphanumeric characters
    //                                 (?:\/(?:\$\{\w+\}|\w+))*    Zero or more groups of a forward slash followed by a ${placeholder} or alphanumeric characters
    if (!pattern.test(urlTemplate)) {
      throw new Error(`Invalid URL template: ${urlTemplate}`);
    }
    this.urlTemplate = urlTemplate;
  }

  async #initCache() {
    if (this.cache) {
      return;
    }
    this.cache = await IndexedDBCache.init();
  }

  /**
   * This method takes a model URL and parses it to extract the
   * model name, optional model version, and file path.
   *
   * The expected URL format are :
   *
   * `/organization/model/revision/filePath`
   * `https://hub/organization/model/revision/filePath`
   *
   * @param {string} url - The full URL to the model, including protocol and domain - or the relative path.
   * @returns {object} An object containing the parsed components of the URL. The
   *                   object has properties `model`, and `file`,
   *                   and optionally `revision` if the URL includes a version.
   * @throws {Error} Throws an error if the URL does not start with `this.rootUrl` or
   *                 if the URL format does not match the expected structure.
   *
   * @example
   * // For a URL
   * parseModelUrl("https://example.com/org1/model1/v1/file/path");
   * // returns { model: "org1/model1", revision: "v1", file: "file/path" }
   *
   * @example
   * // For a relative URL
   * parseModelUrl("/org1/model1/revision/file/path");
   * // returns { model: "org1/model1", revision: "v1", file: "file/path" }
   */
  parseUrl(url) {
    let parts;
    if (url.startsWith("/")) {
      // relative URL
      parts = url.slice(1).split("/");
    } else {
      // absolute URL
      if (!url.startsWith(this.rootUrl)) {
        throw new Error(`Invalid domain for model URL: ${url}`);
      }
      const urlObject = new URL(url);
      const rootUrlObject = new URL(this.rootUrl);

      // Remove the root URL's pathname from the full URL's pathname
      const relativePath = urlObject.pathname.substring(
        rootUrlObject.pathname.length
      );

      parts = relativePath.slice(1).split("/");
    }

    if (parts.length < 3) {
      throw new Error(`Invalid model URL: ${url}`);
    }

    const file = parts.slice(3).join("/");
    if (file == null || !file.length) {
      throw new Error(`Invalid model URL: ${url}`);
    }

    return {
      model: `${parts[0]}/${parts[1]}`,
      revision: parts[2],
      file,
    };
  }

  /** Creates the file URL from the organization, model, and version.
   *
   * @param {object} config - The configuration object to be updated.
   * @param {string} config.model - model name
   * @param {string} config.revision - model revision
   * @param {string} config.file - filename
   * @param {string} config.modelHubRootUrl - root url of the model hub
   * @param {string} config.modelHubUrlTemplate - url template of the model hub
   * @returns {string} The full URL
   */
  #fileUrl({ model, revision, file, modelHubRootUrl, modelHubUrlTemplate }) {
    const rootUrl = modelHubRootUrl || this.rootUrl;
    if (!allowedHub(rootUrl)) {
      throw new Error(`Invalid model hub root url: ${rootUrl}`);
    }
    const urlTemplate = modelHubUrlTemplate || this.urlTemplate;
    const baseUrl = new URL(rootUrl);

    if (!baseUrl.pathname.endsWith("/")) {
      baseUrl.pathname += "/";
    }
    // Replace placeholders in the URL template with the provided data.
    // If some keys are missing in the data object, the placeholder is left as is.
    // If the placeholder is not found in the data object, it is left as is.
    const data = {
      model,
      revision,
    };
    let path = urlTemplate.replace(
      /\{(\w+)\}/g,
      (match, key) => data[key] || match
    );
    path = `${path}/${file}`;

    const fullPath = `${baseUrl.pathname}${
      path.startsWith("/") ? path.slice(1) : path
    }`;

    const urlObject = new URL(fullPath, baseUrl.origin);
    urlObject.searchParams.append("download", "true");
    return urlObject.toString();
  }

  /** Checks the model and revision inputs.
   *
   * @param { string } model
   * @param { string } revision
   * @param { string } file
   * @returns { Error } The error instance(can be null)
   */
  #checkInput(model, revision, file) {
    // Matches a string with the format 'organization/model' where:
    // - 'organization' consists only of letters, digits, and hyphens, cannot start or end with a hyphen,
    //   and cannot contain consecutive hyphens.
    // - 'model' can contain letters, digits, hyphens, underscores, or periods.
    //
    // Pattern breakdown:
    //   ^                                     Start of string
    //    (?!-)                                Negative lookahead for 'organization' not starting with hyphen
    //         (?!.*--)                        Negative lookahead for 'organization' not containing consecutive hyphens
    //                 [A-Za-z0-9-]+           'organization' part: Alphanumeric characters or hyphens
    //                            (?<!-)       Negative lookbehind for 'organization' not ending with a hyphen
    //                                  \/     Literal '/' character separating 'organization' and 'model'
    //                                    [A-Za-z0-9-_.]+    'model' part: Alphanumeric characters, hyphens, underscores, or periods
    //                                                  $    End of string
    const modelRegex = /^(?!-)(?!.*--)[A-Za-z0-9-]+(?<!-)\/[A-Za-z0-9-_.]+$/;

    // Matches strings consisting of alphanumeric characters, hyphens, or periods.
    //
    //                    ^               $   Start and end of string
    //                     [A-Za-z0-9-.]+     Alphanum characters, hyphens, or periods, one or more times
    const versionRegex = /^[A-Za-z0-9-.]+$/;

    // Matches filenames with subdirectories, starting with alphanumeric or underscore,
    // and optionally ending with a dot followed by a 2-4 letter extension.
    //
    //                 ^                                    $   Start and end of string
    //                  (?:\/)?                                  Optional leading slash (for absolute paths or root directory)
    //                        (?!\/)                             Negative lookahead for not starting with a slash
    //                              [A-Za-z0-9-_]+               First directory or filename
    //                                           (?:            Begin non-capturing group for additional directories or file
    //                                              \/              Directory separator
    //                                                [A-Za-z0-9-_]+ Directory or file name
    //                                                             )* Zero or more times
    //                                                                 (?:[.][A-Za-z]{2,4})?   Optional non-capturing group for file extension
    const fileRegex =
      /^(?:\/)?(?!\/)[A-Za-z0-9-_]+(?:\/[A-Za-z0-9-_]+)*(?:[.][A-Za-z]{2,4})?$/;

    if (!modelRegex.test(model)) {
      return new Error("Invalid model name.");
    }

    if (
      !versionRegex.test(revision) ||
      revision.includes(" ") ||
      /[\^$]/.test(revision)
    ) {
      return new Error("Invalid version identifier.");
    }

    if (!fileRegex.test(file)) {
      return new Error("Invalid file name");
    }

    return null;
  }

  /**
   * Deletes all model files for the specified task and model, except for the specified revision.
   *
   * @param {object} config - Configuration object.
   * @param {string} config.taskName - The name of the inference task.
   * @param {string} config.model - The model name (organization/name).
   * @param {string} config.targetRevision - The revision to keep.
   *
   * @returns {Promise<void>}
   */
  async deleteNonMatchingModelRevisions({ taskName, model, targetRevision }) {
    // Ensure all required parameters are provided
    if (!taskName || !model || !targetRevision) {
      throw new Error("taskName, model, and targetRevision are required.");
    }

    await this.#initCache();

    // Delete models with revisions that do not match the targetRevision
    return this.cache.deleteModels({
      taskName,
      model,
      filterFn: record => record.revision !== targetRevision,
    });
  }

  /**
   * Returns the ETag value given an URL
   *
   * @param {string} url
   * @param {number} timeout in ms. Default is 1000
   * @returns {Promise<string>} ETag (can be null)
   */
  async getETag(url, timeout = 1000) {
    const controller = new AbortController();
    const id = lazy.setTimeout(() => controller.abort(), timeout);

    try {
      const headResponse = await fetch(url, {
        method: "HEAD",
        signal: controller.signal,
      });
      const currentEtag = headResponse.headers.get("ETag");
      return currentEtag;
    } catch (error) {
      lazy.console.warn("An error occurred when calling HEAD:", error);
      return null;
    } finally {
      lazy.clearTimeout(id);
    }
  }

  /**
   * Given an organization, model, and version, fetch a model file in the hub as a Response.
   *
   * @param {object} config
   * @param {string} config.taskName - name of the inference task.
   * @param {string} config.model - The model name (organization/name).
   * @param {string} config.revision - The model revision.
   * @param {string} config.file - The file name.
   * @param {string} config.modelHubRootUrl - root url of the model hub
   * @param {string} config.modelHubUrlTemplate - url template of the model hub
   * @returns {Promise<Response>} The file content
   */
  async getModelFileAsResponse({
    taskName,
    model,
    revision,
    file,
    modelHubRootUrl,
    modelHubUrlTemplate,
  }) {
    const [blob, headers] = await this.getModelFileAsBlob({
      taskName,
      model,
      revision,
      file,
      modelHubRootUrl,
      modelHubUrlTemplate,
    });

    return new Response(blob, { headers });
  }

  /**
   * Given an organization, model, and version, fetch a model file in the hub as an blob.
   *
   * @param {object} config
   * @param {string} config.taskName - name of the inference task.
   * @param {string} config.model - The model name (organization/name).
   * @param {string} config.revision - The model revision.
   * @param {string} config.file - The file name.
   * @param {string} config.modelHubRootUrl - root url of the model hub
   * @param {string} config.modelHubUrlTemplate - url template of the model hub
   * @returns {Promise<[Blob, object]>} The file content
   */
  async getModelFileAsBlob({
    taskName,
    model,
    revision,
    file,
    modelHubRootUrl,
    modelHubUrlTemplate,
  }) {
    const [buffer, headers] = await this.getModelFileAsArrayBuffer({
      taskName,
      model,
      revision,
      file,
      modelHubRootUrl,
      modelHubUrlTemplate,
    });
    return [new Blob([buffer]), headers];
  }

  /**
   * Given an organization, model, and version, fetch a model file in the hub as an ArrayBuffer
   * while supporting status callback.
   *
   * @param {object} config
   * @param {string} config.taskName - name of the inference task.
   * @param {string} config.model - The model name (organization/name).
   * @param {string} config.revision - The model revision.
   * @param {string} config.file - The file name.
   * @param {string} config.modelHubRootUrl - root url of the model hub
   * @param {string} config.modelHubUrlTemplate - url template of the model hub
   * @param {?function(ProgressAndStatusCallbackParams):void} config.progressCallback A function to call to indicate progress status.
   * @returns {Promise<[ArrayBuffer, headers]>} The file content
   */
  async getModelFileAsArrayBuffer({
    taskName,
    model,
    revision,
    file,
    modelHubRootUrl,
    modelHubUrlTemplate,
    progressCallback,
  }) {
    // Make sure inputs are clean. We don't sanitize them but throw an exception
    let checkError = this.#checkInput(model, revision, file);
    if (checkError) {
      throw checkError;
    }
    const url = this.#fileUrl({
      model,
      revision,
      file,
      modelHubRootUrl,
      modelHubUrlTemplate,
    });
    lazy.console.debug(`Getting model file from ${url}`);

    await this.#initCache();

    let useCached;

    // If the revision is `main` we want to check the ETag in the hub
    if (revision === "main") {
      // this can be null if no ETag was found or there were a network error
      const hubETag = await this.getETag(url);

      // Storage ETag lookup
      const cachedHeaders = await this.cache.getHeaders({
        model,
        revision,
        file,
      });
      const cachedEtag = cachedHeaders ? cachedHeaders.ETag : null;

      // If we have something in store, and the hub ETag is null or it matches the cached ETag, return the cached response
      useCached =
        cachedEtag !== null && (hubETag === null || cachedEtag === hubETag);
    } else {
      // If we are dealing with a pinned revision, we ignore the ETag, to spare HEAD hits on every call
      useCached = await this.cache.fileExists({ model, revision, file });
    }

    const progressInfo = {
      progress: null,
      totalLoaded: null,
      currentLoaded: null,
      total: null,
    };

    const statusInfo = {
      metadata: { model, revision, file, url, taskName },
      ok: true,
      id: url,
    };

    if (useCached) {
      lazy.console.debug(`Cache Hit for ${url}`);
      progressCallback?.(
        new lazy.Progress.ProgressAndStatusCallbackParams({
          ...statusInfo,
          ...progressInfo,
          type: lazy.Progress.ProgressType.LOAD_FROM_CACHE,
          statusText: lazy.Progress.ProgressStatusText.INITIATE,
        })
      );
      const [blob, headers] = await this.cache.getFile({
        model,
        revision,
        file,
      });

      // Ensure that we indicate that the taskName is stored
      await this.cache.updateTask({ taskName, model, revision, file });

      progressCallback?.(
        new lazy.Progress.ProgressAndStatusCallbackParams({
          ...statusInfo,
          ...progressInfo,
          type: lazy.Progress.ProgressType.LOAD_FROM_CACHE,
          statusText: lazy.Progress.ProgressStatusText.DONE,
        })
      );
      return [await blob.arrayBuffer(), headers];
    }

    progressCallback?.(
      new lazy.Progress.ProgressAndStatusCallbackParams({
        ...statusInfo,
        ...progressInfo,
        type: lazy.Progress.ProgressType.DOWNLOAD,
        statusText: lazy.Progress.ProgressStatusText.INITIATE,
      })
    );

    lazy.console.debug(`Fetching ${url}`);
    try {
      let response = await fetch(url);
      let isFirstCall = true;
      let responseContentArray = await lazy.Progress.readResponse(
        response,
        progressData => {
          progressCallback?.(
            new lazy.Progress.ProgressAndStatusCallbackParams({
              ...progressInfo,
              ...progressData,
              statusText: isFirstCall
                ? lazy.Progress.ProgressStatusText.SIZE_ESTIMATE
                : lazy.Progress.ProgressStatusText.IN_PROGRESS,
              type: lazy.Progress.ProgressType.DOWNLOAD,
              ...statusInfo,
            })
          );
          isFirstCall = false;
        }
      );
      let responseContent = responseContentArray.buffer.slice(
        responseContentArray.byteOffset,
        responseContentArray.byteLength + responseContentArray.byteOffset
      );

      if (response.ok) {
        const headers = {
          // We don't store the boundary or the charset, just the content type,
          // so we drop what's after the semicolon.
          "Content-Type": response.headers.get("Content-Type").split(";")[0],
          "Content-Length": response.headers.get("Content-Length"),
          ETag: response.headers.get("ETag"),
        };

        await this.cache.put({
          taskName,
          model,
          revision,
          file,
          data: new Blob([responseContent]),
          headers,
        });

        progressCallback?.(
          new lazy.Progress.ProgressAndStatusCallbackParams({
            ...statusInfo,
            ...progressInfo,
            type: lazy.Progress.ProgressType.DOWNLOAD,
            statusText: lazy.Progress.ProgressStatusText.DONE,
          })
        );

        return [responseContent, headers];
      }
    } catch (error) {
      lazy.console.error(`Failed to fetch ${url}:`, error);
    }

    // Indicate there is an error
    progressCallback?.(
      new lazy.Progress.ProgressAndStatusCallbackParams({
        ...statusInfo,
        ...progressInfo,
        type: lazy.Progress.ProgressType.DOWNLOAD,
        statusText: lazy.Progress.ProgressStatusText.DONE,
        ok: false,
      })
    );

    throw new Error(`Failed to fetch the model file: ${url}`);
  }
}
PK
!<i�
�U6U61chrome/toolkit/content/global/ml/ONNXPipeline.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-disable-next-line mozilla/reject-import-system-module-from-non-system */
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

/**
 * Log level set by the pipeline.
 *
 * @type {string}
 */
let _logLevel = "Error";

/**
 * Lazy initialization container.
 *
 * @type {object}
 */
const lazy = {};

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  return console.createInstance({
    maxLogLevel: _logLevel, // we can't use maxLogLevelPref in workers.
    prefix: "ML:ONNXPipeline",
  });
});

ChromeUtils.defineESModuleGetters(
  lazy,
  {
    arrayBufferToBlobURL: "chrome://global/content/ml/Utils.sys.mjs",
  },
  { global: "current" }
);

/**
 * Conditional import for Transformer.js
 *
 * The library will be lazily await on first usage in the Pipeline constructor.
 * If we are in Nightly, we are using the non-minified version.
 */
let transformersPromise;
let transformers = null;
let transformersDev;

if (AppConstants.NIGHTLY_BUILD) {
  transformersPromise = import(
    "chrome://global/content/ml/transformers-dev.js"
  );
  transformersDev = true;
} else {
  transformersPromise = import("chrome://global/content/ml/transformers.js");
  transformersDev = false;
}

/**
 * Echo inference for testing purposes.
 *
 * @async
 * @param {object} request - The request object containing args.
 * @param {object} _model - The model used for inference.
 * @param {object} _tokenizer - The tokenizer used for decoding.
 * @param {object} _processor - The processor used for preparing  data.
 * @param {object} config - The config
 * @returns {Promise<object>} The result object containing the processed text.
 */
async function echo(request, _model, _tokenizer, _processor, config) {
  let result = {};
  for (let key in config) {
    result[key] = String(config[key]);
  }
  result.echo = request.data;

  // Sleeping to simulate inference latency
  await new Promise(resolve => setTimeout(resolve, request.sleepTime ?? 100));

  return {
    metrics: {
      tokenizingTime: 0,
    },
    output: result,
  };
}

/**
 * Converts an image to text using a machine learning model.
 *
 * @async
 * @param {object} request - The request object containing image data.
 * @param {string} [request.url] - The URL of the image to process. If `url` is not provided, other fields are used.
 * @param {ArrayBuffer} [request.data] - The raw image data to process. Ignored if `url` is provided.
 * @param {number} [request.width] - The image width. Ignored if `url` is provided.
 * @param {number} [request.height] - The image height. Ignored if `url` is provided.
 * @param {number} [request.channels] - The image channels. Can be 1, 2, 3 or 4. Defaults to 4. Ignored if `url` is provided.
 * @param {object} model - The model used for inference.
 * @param {object} tokenizer - The tokenizer used for decoding.
 * @param {object} processor - The processor used for preparing image data.
 * @param {object} _config - The config
 * @returns {Promise<object>} The result object containing the processed text.
 */
async function imageToText(request, model, tokenizer, processor, _config) {
  let result = {
    metrics: {
      inferenceTime: 0,
      tokenizingTime: 0,
    },
  };
  let start = Date.now();
  let rawImage;

  if ("url" in request) {
    rawImage = await transformers.RawImage.fromURL(request.url);
  } else {
    rawImage = new transformers.RawImage(
      request.data,
      request.width,
      request.height,
      request.channels || 4
    );
  }

  lazy.console.debug("Image loaded in ", Date.now() - start);

  const { pixel_values } = await processor(rawImage);
  result.metrics.tokenizingTime += Date.now() - start;
  const toReturn = [];
  for (const batch of pixel_values) {
    batch.dims = [1, ...batch.dims];
    start = Date.now();
    const output = await model.generate(batch);
    result.metrics.inferenceTime += Date.now() - start;
    start = Date.now();
    const decoded = tokenizer
      .batch_decode(output, {
        skip_special_tokens: true,
      })
      .map(x => ({ generated_text: x.trim() }));
    result.metrics.tokenizingTime += Date.now() - start;
    toReturn.push(decoded);
  }
  lazy.console.debug("Inference done in ", Date.now() - start);
  result.output = toReturn[0][0].generated_text;

  // Bug 1918220 - replace the result for models with that bug
  if (result.output === "T") {
    lazy.console.debug("Replacing `T` with `Text document.`");
    result.output = "Text document.";
  }
  return result;
}

/**
 * Configuration for engine. Each task has a configuration object that
 * gets merged at runtime with the options from PipelineOptions.
 *
 * When a key exists in both the default configuration and the options,
 * the value from the options is used.
 *
 * The configuration keys that are not exposed as options are all the
 * callables that are used in the pipeline:
 *
 * - modelClass
 * - tokenizerClass
 * - processorClass
 * - pipelineFunction
 *
 * @type {object}
 */
const ENGINE_CONFIGURATION = {
  "moz-image-to-text": {
    modelId: "mozilla/distilvit",
    modelClass: "AutoModelForVision2Seq",
    tokenizerId: "mozilla/distilvit",
    tokenizerClass: "AutoTokenizer",
    processorId: "mozilla/distilvit",
    processorClass: "AutoProcessor",
    pipelineFunction: imageToText,
  },
  "moz-echo": {
    modelId: null,
    modelClass: null,
    tokenizerId: null,
    tokenizerClass: null,
    processorId: null,
    processorClass: null,
    pipelineFunction: echo,
  },
};

/**
 * Represents a pipeline for processing machine learning tasks.
 */
export class Pipeline {
  #modelCache = null;
  #model = null;
  #tokenizer = null;
  #processor = null;
  #pipelineFunction = null;
  #genericPipelineFunction = null;
  #initTime = 0;
  #isReady = false;
  #config = null;

  /**
   * Creates an instance of a Pipeline.
   *
   * @param {object} modelCache - Implements the Cache interface and used to get models
   * @param {object} config - The configuration options
   */
  constructor(modelCache, config) {
    let start = Date.now();
    this.#modelCache = modelCache;

    _logLevel = config.logLevel || "Error";

    // Setting up the Transformers.js environment
    // See https://huggingface.co/docs/transformers.js/api/env

    // Caching strategy.
    // Here we make sure that everytime transformers.js requires a file, it uses
    // modelCache, which transfers the request to the main thread and uses the
    // ModelHub that caches files into IndexDB.
    transformers.env.useBrowserCache = false;
    transformers.env.allowLocalModels = false;
    transformers.env.remoteHost = config.modelHubRootUrl;
    transformers.env.remotePathTemplate = config.modelHubUrlTemplate;
    transformers.env.useCustomCache = true;
    transformers.env.customCache = this.#modelCache;
    // using `NO_LOCAL` so when the custom cache is used, we don't try to fetch it (see MLEngineWorker.match)
    transformers.env.localModelPath = "NO_LOCAL";

    // ONNX runtime - we set up the wasm runtime we got from RS for the ONNX backend to pick
    lazy.console.debug(
      "Setting up ONNX backend for runtime",
      config.runtimeFilename
    );
    transformers.env.backends.onnx.wasm.wasmPaths = {};
    transformers.env.backends.onnx.wasm.wasmPaths[config.runtimeFilename] =
      lazy.arrayBufferToBlobURL(config.runtime);
    lazy.console.debug("Transformers.js env", transformers.env);

    if (config.pipelineFunction && config.taskName != "test-echo") {
      lazy.console.debug("Using internal inference function");

      // use the model revision of the tokenizer or processor don't have one
      if (!config.tokenizerRevision) {
        config.tokenizerRevision = config.modelRevision;
      }
      if (!config.processorRevision) {
        config.processorRevision = config.modelRevision;
      }

      this.#pipelineFunction = config.pipelineFunction;

      if (config.modelClass && config.modelId) {
        lazy.console.debug(
          `Loading model ${config.modelId} with class ${config.modelClass}`
        );
        this.#model = transformers[config.modelClass].from_pretrained(
          config.modelId,
          {
            revision: config.modelRevision,
          }
        );
      }
      if (config.tokenizerClass && config.tokenizerId) {
        lazy.console.debug(
          `Loading tokenizer ${config.tokenizerId} with class ${config.tokenizerClass}`
        );
        this.#tokenizer = transformers[config.tokenizerClass].from_pretrained(
          config.tokenizerId,
          { revision: config.tokenizerRevision }
        );
      }
      if (config.processorClass && config.processorId) {
        lazy.console.debug(
          `Loading processor ${config.processorId} with class ${config.processorClass}`
        );
        this.#processor = transformers[config.processorClass].from_pretrained(
          config.processorId,
          { revision: config.processorRevision }
        );
      }
    } else {
      lazy.console.debug("Using generic pipeline function");
      this.#genericPipelineFunction = transformers.pipeline(
        config.taskName,
        config.modelId,
        { revision: config.modelRevision }
      );
    }
    this.#initTime = Date.now() - start;
    this.#config = config;
    lazy.console.debug("Pipeline initialized, took ", this.#initTime);
  }

  /**
   * Initializes the pipeline with given options.
   *
   * @static
   * @async
   * @param {object} modelCache - Implements the Cache interface and used to get models
   * @param {ArrayBuffer} runtime - The runtime wasm file.
   * @param {PipelineOptions} options - The options for initialization.
   * @returns {Promise<Pipeline>} The initialized pipeline instance.
   */
  static async initialize(modelCache, runtime, options) {
    const taskName = options.taskName;
    lazy.console.debug(`Initializing Pipeline for task ${taskName}`);
    let config;

    if (!ENGINE_CONFIGURATION[taskName]) {
      lazy.console.debug(`Unknown internal task ${taskName}`);
      // generic pipeline function
      config = {
        pipelineFunction: null,
        taskName,
        modelId: options.modelId,
        modelRevision: options.modelRevision || "default",
      };
    } else {
      // Loading the config defaults for the task
      lazy.console.debug(`Internal task detected ${taskName}`);
      config = { ...ENGINE_CONFIGURATION[taskName] };
    }
    config.runtime = runtime;

    // Overriding the defaults with the options
    options.applyToConfig(config);

    if (!transformers) {
      if (transformersDev) {
        lazy.console.debug("Nightly detected. Using transformers-dev.js");
      } else {
        lazy.console.debug("Beta or Release detected, using transformers.js");
      }
      transformers = await transformersPromise;
    }

    const pipeline = new Pipeline(modelCache, config);
    await pipeline.ensurePipelineIsReady();
    return pipeline;
  }

  /**
   * Ensure all promises are resolved to complete file downloads and model initialization in memory.
   */
  async ensurePipelineIsReady() {
    if (!this.#isReady) {
      let start = Date.now();

      // deactive console.warn, see https://bugzilla.mozilla.org/show_bug.cgi?id=1891003
      const originalWarn = console.warn;
      console.warn = () => {};
      try {
        if (this.#genericPipelineFunction) {
          lazy.console.debug("Initializing pipeline");
          if (this.#config.modelId != "test-echo") {
            this.#genericPipelineFunction = await this.#genericPipelineFunction;
          }
        } else {
          lazy.console.debug("Initializing model, tokenizer and processor");

          try {
            [this.#model, this.#tokenizer, this.#processor] = await Promise.all(
              [this.#model, this.#tokenizer, this.#processor]
            );
            this.#isReady = true;
          } catch (error) {
            lazy.console.debug("Error initializing pipeline", error);
            throw error;
          }
        }
      } finally {
        console.warn = originalWarn;
      }

      this.#initTime += Date.now() - start;
      lazy.console.debug(
        "Pipeline is fully initialized, took ",
        this.#initTime
      );
    }
  }

  /**
   * Runs the pipeline with the given request.
   *
   * @async
   * @param {T} request - The request object to be processed. The fields it may contain
   * depends on the task. See each pipeline function for more details.
   * When the pipeline is initialized with the generic pipeline function, the request contains
   * `args` and `options` fields. The `args` field is an array of values that are passed
   * to the generic pipeline function. The `options` field is an object that contains the options for the pipeline.
   * @returns {Promise<object>} The result object from the pipeline execution.
   */
  async run(request) {
    lazy.console.debug("Running task: ", this.#config.taskName);

    let result;

    if (this.#genericPipelineFunction) {
      if (this.#config.modelId === "test-echo") {
        result = { output: request.args, config: this.#config };
      } else {
        result = await this.#genericPipelineFunction(
          ...request.args,
          request.options || {}
        );

        // When the pipeline returns Tensors they are Proxy objects that cannot be cloned.
        // Workaround: convert to JSON and back to JS objects.
        result = JSON.parse(JSON.stringify(result));
      }
    } else {
      result = await this.#pipelineFunction(
        request,
        this.#model,
        this.#tokenizer,
        this.#processor,
        this.#config
      );
      result.metrics.initTime = this.#initTime;
    }
    return result;
  }
}
PK
!<��{+{+.chrome/toolkit/content/global/ml/Utils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

/**
 * Converts an ArrayBuffer to a Blob URL.
 *
 * @param {ArrayBuffer} buffer - The ArrayBuffer to convert.
 * @returns {string} The Blob URL.
 */
export function arrayBufferToBlobURL(buffer) {
  let blob = new Blob([buffer], { type: "application/wasm" });
  return URL.createObjectURL(blob);
}

/**
 * Validate some simple Wasm that uses a SIMD operation.
 */
export function detectSimdSupport() {
  return WebAssembly.validate(
    new Uint8Array(
      // ```
      // ;; Detect SIMD support.
      // ;; Compile by running: wat2wasm --enable-all simd-detect.wat
      //
      // (module
      //   (func (result v128)
      //     i32.const 0
      //     i8x16.splat
      //     i8x16.popcnt
      //   )
      // )
      // ```

      // prettier-ignore
      [
        0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x05, 0x01, 0x60, 0x00,
        0x01, 0x7b, 0x03, 0x02, 0x01, 0x00, 0x0a, 0x0a, 0x01, 0x08, 0x00, 0x41, 0x00,
        0xfd, 0x0f, 0xfd, 0x62, 0x0b
      ]
    )
  );
}

let cachedRuntimeWasmFilename = null;

/**
 * Determines the appropriate WebAssembly (Wasm) filename based on the runtime capabilities of the browser.
 * This function considers both SIMD and multi-threading support.
 * It returns a filename that matches the browser's capabilities, ensuring the most optimized version of the Wasm file is used.
 *
 * The result is cached to avoid re-computation.
 *
 * @param {Window|null} browsingContext - The browsing context to use for feature detection.
 * @returns {string} The filename of the Wasm file best suited for the current browser's capabilities.
 */
export function getRuntimeWasmFilename(browsingContext = null) {
  if (cachedRuntimeWasmFilename != null) {
    return cachedRuntimeWasmFilename;
  }

  // The cross-origin isolation flag is used to determine if we have multi-threading support.
  const hasMultiThreadSupport = browsingContext
    ? browsingContext.crossOriginIsolated
    : false;

  let res;
  if (detectSimdSupport()) {
    res = hasMultiThreadSupport
      ? "ort-wasm-simd-threaded.wasm"
      : "ort-wasm-simd.wasm";
  } else {
    res = hasMultiThreadSupport ? "ort-wasm-threaded.wasm" : "ort-wasm.wasm";
  }
  cachedRuntimeWasmFilename = res;
  return res;
}

/**
 * Enumeration for the progress status text.
 */
export const ProgressStatusText = Object.freeze({
  // The value of the status text indicating that an operation is started.
  INITIATE: "initiate",
  // The value of the status text indicating an estimate for the size of the operation.
  SIZE_ESTIMATE: "size_estimate",
  // The value of the status text indicating that an operation is in progress.
  IN_PROGRESS: "in_progress",
  // The value of the status text indicating that an operation has completed.
  DONE: "done",
});

/**
 * Enumeration for type of progress operations.
 */
export const ProgressType = Object.freeze({
  // The value of the operation type for a remote downloading.
  DOWNLOAD: "downloading",
  // The value of the operation type when loading from cache
  LOAD_FROM_CACHE: "loading_from_cache",
});

/**
 * This class encapsulates the parameters supported by a progress and status callback.
 */
export class ProgressAndStatusCallbackParams {
  // Params for progress callback

  /**
   * A float indicating the percentage of data loaded. Note that
   * 100% does not necessarily mean the operation is complete.
   *
   * @type {?float}
   */
  progress = null;

  /**
   * A float indicating the total amount of data loaded so far.
   * In particular, this is the sum of currentLoaded across all call of the callback.
   *
   * @type {?float}
   */
  totalLoaded = null;

  /**
   * The amount of data loaded in the current callback call.
   *
   * @type {?float}
   */
  currentLoaded = null;

  /**
   * A float indicating an estimate of the total amount of data to be loaded.
   * Do not rely on this number as this is an estimate and the true total could be
   * either lower or higher.
   *
   * @type {?float}
   */
  total = null;

  /**
   * The units in which the amounts are reported.
   *
   * @type {?string}
   */
  units = null;

  // Params for status callback
  /**
   * The name of the operation being tracked.
   *
   * @type {?string}
   */
  type = null;

  /**
   * A message indicating the status of the tracked operation.
   *
   * @type {?string}
   */
  statusText = null;

  /**
   * An ID uniquely identifying the object/file being tracked.
   *
   * @type {?string}
   */
  id = null;

  /**
   * A boolean indicating if the operation was successful.
   * true means we have a successful operation.
   *
   * @type {?boolean}
   */
  ok = null;

  /**
   * Any additional metadata for the operation being tracked.
   *
   * @type {?object}
   */
  metadata = null;

  constructor(params = {}) {
    this.update(params);
  }

  update(params = {}) {
    const allowedKeys = new Set(Object.keys(this));
    const invalidKeys = Object.keys(params).filter(x => !allowedKeys.has(x));
    if (invalidKeys.length) {
      throw new Error(`Received Invalid option: ${invalidKeys}`);
    }
    for (const key of allowedKeys) {
      if (key in params) {
        this[key] = params[key];
      }
    }
  }
}

/**
 * Read and track progress when reading a Response object
 *
 * @param {any} response The Response object to read
 * @param {?function(ProgressAndStatusCallbackParams):void} progressCallback The function to call with progress updates
 *
 * @returns {Promise<Uint8Array>} A Promise that resolves with the Uint8Array buffer
 */
export async function readResponse(response, progressCallback) {
  const contentLength = response.headers.get("Content-Length");
  if (!contentLength) {
    console.warn(
      "Unable to determine content-length from response headers. Will expand buffer when needed."
    );
  }
  let total = parseInt(contentLength ?? "0");

  progressCallback?.(
    new ProgressAndStatusCallbackParams({
      progress: 0,
      totalLoaded: 0,
      currentLoaded: 0,
      total,
      units: "bytes",
    })
  );

  let buffer = new Uint8Array(total);
  let loaded = 0;

  for await (const value of response.body) {
    let newLoaded = loaded + value.length;
    if (newLoaded > total) {
      total = newLoaded;

      // Adding the new data will overflow buffer.
      // In this case, we extend the buffer
      // Happened when the content-length is lower than the actual lenght
      let newBuffer = new Uint8Array(total);

      // copy contents
      newBuffer.set(buffer);

      buffer = newBuffer;
    }
    buffer.set(value, loaded);
    loaded = newLoaded;

    const progress = (loaded / total) * 100;

    progressCallback?.(
      new ProgressAndStatusCallbackParams({
        progress,
        totalLoaded: loaded,
        currentLoaded: value.length,
        total,
        units: "bytes",
      })
    );
  }

  // Ensure that buffer is not bigger than loaded
  // Sometimes content length is larger than the actual size
  buffer = buffer.slice(0, loaded);

  return buffer;
}

/**
 * Class for watching the progress bar of multiple events and combining
 * then into a single progress bar.
 */
export class MultiProgressAggregator {
  /**
   * A function to call with the aggregated statistics.
   *
   * @type {?function(ProgressAndStatusCallbackParams):void}
   */
  progressCallback = null;

  /**
   * The name of the key that contains status information.
   *
   * @type {Set<string>}
   */
  watchedTypes;

  /**
   * The total amount of information loaded so far.
   *
   * @type {float}
   */
  #combinedLoaded = 0;

  /**
   * The total amount of information to be loaded.
   *
   * @type {float}
   */
  #combinedTotal = 0;

  /**
   * The number of operations that are yet to be completed.
   *
   * @type {float}
   */
  #remainingEvents = 0;

  /**
   * The type of operation seen so far.
   *
   * @type {Set<string>}
   */
  #seenTypes;

  /**
   * The status of text seen so far.
   *
   * @type {Set<string>}
   */
  #seenStatus;

  /**
   * @param {object} config
   * @param {?function(ProgressAndStatusCallbackParams):void} config.progressCallback - A function to call with the aggregated statistics.
   * @param {Iterable<string>} config.watchedTypes - The types to watch for aggregation
   */
  constructor({ progressCallback, watchedTypes = [ProgressType.DOWNLOAD] }) {
    this.progressCallback = progressCallback;
    this.watchedTypes = new Set(watchedTypes);

    this.#seenTypes = new Set();
    this.#seenStatus = new Set();
  }

  /**
   * Callback function that will combined data from different objects/files.
   *
   * @param {ProgressAndStatusCallbackParams} data - object containing the data
   */
  aggregateCallback(data) {
    if (this.watchedTypes.has(data.type)) {
      this.#seenTypes.add(data.type);
      this.#seenStatus.add(data.statusText);
      if (data.statusText == ProgressStatusText.INITIATE) {
        this.#remainingEvents += 1;
      }

      if (data.statusText == ProgressStatusText.SIZE_ESTIMATE) {
        this.#combinedTotal += data.total ?? 0;
      }

      if (data.statusText == ProgressStatusText.DONE) {
        this.#remainingEvents -= 1;
      }

      this.#combinedLoaded += data.currentLoaded ?? 0;

      if (this.progressCallback) {
        let statusText = data.statusText;
        if (this.#seenStatus.has(ProgressStatusText.IN_PROGRESS)) {
          statusText = ProgressStatusText.IN_PROGRESS;
        }

        if (this.#remainingEvents == 0) {
          statusText = ProgressStatusText.DONE;
        }

        this.progressCallback(
          new ProgressAndStatusCallbackParams({
            type: data.type,
            statusText,
            id: data.id,
            total: this.#combinedTotal,
            currentLoaded: data.currentLoaded,
            totalLoaded: this.#combinedLoaded,
            progress: (this.#combinedLoaded / this.#combinedTotal) * 100,
            ok: data.ok,
            units: data.units,
            metadata: data,
          })
        );
      }
    }
  }
}

/**
 * Converts a model and its headers to a Response object.
 *
 * @param {ArrayBuffer} modelFile
 * @param {object|null} headers
 * @returns {Response} The generated Response instance
 */
export function modelToResponse(modelFile, headers) {
  let responseHeaders = {};

  if (headers) {
    // Headers are converted to strings, as the cache may hold int keys like fileSize
    for (let key in headers) {
      if (headers[key] != null) {
        responseHeaders[key] = headers[key].toString();
      }
    }
  }

  return new Response(modelFile, {
    status: 200,
    headers: responseHeaders,
  });
}

// Create a "namespace" to make it easier to import multiple names.
export var Progress = Progress || {};
Progress.ProgressAndStatusCallbackParams = ProgressAndStatusCallbackParams;
Progress.ProgressStatusText = ProgressStatusText;
Progress.ProgressType = ProgressType;
Progress.readResponse = readResponse;
PK
!<�����'chrome/toolkit/content/global/ml/ort.js/*!
* ONNX Runtime Web v1.14.0
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License.
*/
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ort=e():t.ort=e()}(self,(()=>(()=>{var __webpack_modules__={8453:(t,e,n)=>{"use strict";n.r(e),n.d(e,{InferenceSession:()=>h,Tensor:()=>f,env:()=>a,registerBackend:()=>o});const r={},i=[],o=(t,e,n)=>{if(!e||"function"!=typeof e.init||"function"!=typeof e.createSessionHandler)throw new TypeError("not a valid backend");{const o=r[t];if(void 0===o)r[t]={backend:e,priority:n};else{if(o.priority>n)return;if(o.priority===n&&o.backend!==e)throw new Error(`cannot register backend "${t}" using priority ${n}`)}if(n>=0){const e=i.indexOf(t);-1!==e&&i.splice(e,1);for(let e=0;e<i.length;e++)if(r[i[e]].priority<=n)return void i.splice(e,0,t);i.push(t)}}},a=new class{constructor(){this.wasm={},this.webgl={},this.logLevelInternal="warning"}set logLevel(t){if(void 0!==t){if("string"!=typeof t||-1===["verbose","info","warning","error","fatal"].indexOf(t))throw new Error(`Unsupported logging level: ${t}`);this.logLevelInternal=t}}get logLevel(){return this.logLevelInternal}},s="undefined"!=typeof BigInt64Array&&"function"==typeof BigInt64Array.from,u="undefined"!=typeof BigUint64Array&&"function"==typeof BigUint64Array.from,c=new Map([["float32",Float32Array],["uint8",Uint8Array],["int8",Int8Array],["uint16",Uint16Array],["int16",Int16Array],["int32",Int32Array],["bool",Uint8Array],["float64",Float64Array],["uint32",Uint32Array]]),l=new Map([[Float32Array,"float32"],[Uint8Array,"uint8"],[Int8Array,"int8"],[Uint16Array,"uint16"],[Int16Array,"int16"],[Int32Array,"int32"],[Float64Array,"float64"],[Uint32Array,"uint32"]]);s&&(c.set("int64",BigInt64Array),l.set(BigInt64Array,"int64")),u&&(c.set("uint64",BigUint64Array),l.set(BigUint64Array,"uint64"));class p{constructor(t,e,n){let r,i,o;if("string"==typeof t)if(r=t,o=n,"string"===t){if(!Array.isArray(e))throw new TypeError("A string tensor's data must be a string array.");i=e}else{const n=c.get(t);if(void 0===n)throw new TypeError(`Unsupported tensor type: ${t}.`);if(Array.isArray(e))i=n.from(e);else{if(!(e instanceof n))throw new TypeError(`A ${r} tensor's data must be type of ${n}`);i=e}}else if(o=e,Array.isArray(t)){if(0===t.length)throw new TypeError("Tensor type cannot be inferred from an empty array.");const e=typeof t[0];if("string"===e)r="string",i=t;else{if("boolean"!==e)throw new TypeError(`Invalid element type of data array: ${e}.`);r="bool",i=Uint8Array.from(t)}}else{const e=l.get(t.constructor);if(void 0===e)throw new TypeError(`Unsupported type for tensor data: ${t.constructor}.`);r=e,i=t}if(void 0===o)o=[i.length];else if(!Array.isArray(o))throw new TypeError("A tensor's dims must be a number array");const a=(t=>{let e=1;for(let n=0;n<t.length;n++){const r=t[n];if("number"!=typeof r||!Number.isSafeInteger(r))throw new TypeError(`dims[${n}] must be an integer, got: ${r}`);if(r<0)throw new RangeError(`dims[${n}] must be a non-negative integer, got: ${r}`);e*=r}return e})(o);if(a!==i.length)throw new Error(`Tensor's size(${a}) does not match data length(${i.length}).`);this.dims=o,this.type=r,this.data=i,this.size=a}static bufferToTensor(t,e){if(void 0===t)throw new Error("Image buffer must be defined");if(void 0===e.height||void 0===e.width)throw new Error("Image height and width must be defined");const{height:n,width:r}=e,i=e.norm;let o,a;o=void 0===i||void 0===i.mean?255:i.mean,a=void 0===i||void 0===i.bias?0:i.bias;const s=void 0!==e.bitmapFormat?e.bitmapFormat:"RGBA",u=void 0!==e.tensorFormat&&void 0!==e.tensorFormat?e.tensorFormat:"RGB",c=n*r,l="RGBA"===u?new Float32Array(4*c):new Float32Array(3*c);let f=4,d=0,h=1,g=2,b=3,m=0,y=c,_=2*c,v=-1;"RGB"===s&&(f=3,d=0,h=1,g=2,b=-1),"RGBA"===u?v=3*c:"RBG"===u?(m=0,_=c,y=2*c):"BGR"===u&&(_=0,y=c,m=2*c);for(let e=0;e<c;e++,d+=f,g+=f,h+=f,b+=f)l[m++]=(t[d]+a)/o,l[y++]=(t[h]+a)/o,l[_++]=(t[g]+a)/o,-1!==v&&-1!==b&&(l[v++]=(t[b]+a)/o);return new p("float32",l,"RGBA"===u?[1,4,n,r]:[1,3,n,r])}static async fromImage(t,e){const n="undefined"!=typeof HTMLImageElement&&t instanceof HTMLImageElement,r="undefined"!=typeof ImageData&&t instanceof ImageData,i="undefined"!=typeof ImageBitmap&&t instanceof ImageBitmap,o="undefined"!=typeof String&&(t instanceof String||"string"==typeof t);let a,s={};if(n){const n=document.createElement("canvas"),r=n.getContext("2d");if(null==r)throw new Error("Can not access image data");{let i=t.naturalHeight,o=t.naturalWidth;if(void 0!==e&&void 0!==e.resizedHeight&&void 0!==e.resizedWidth&&(i=e.resizedHeight,o=e.resizedWidth),void 0!==e){if(s=e,void 0!==e.tensorFormat)throw new Error("Image input config format must be RGBA for HTMLImageElement");if(s.tensorFormat="RGBA",void 0!==e.height&&e.height!==i)throw new Error("Image input config height doesn't match HTMLImageElement height");if(s.height=i,void 0!==e.width&&e.width!==o)throw new Error("Image input config width doesn't match HTMLImageElement width");s.width=o}else s.tensorFormat="RGBA",s.height=i,s.width=o;n.width=o,n.height=i,r.drawImage(t,0,0,o,i),a=r.getImageData(0,0,o,i).data}}else{if(!r){if(i){if(void 0===e)throw new Error("Please provide image config with format for Imagebitmap");if(void 0!==e.bitmapFormat)throw new Error("Image input config format must be defined for ImageBitmap");const n=document.createElement("canvas").getContext("2d");if(null!=n){const r=t.height,i=t.width;if(n.drawImage(t,0,0,i,r),a=n.getImageData(0,0,i,r).data,void 0!==e){if(void 0!==e.height&&e.height!==r)throw new Error("Image input config height doesn't match ImageBitmap height");if(s.height=r,void 0!==e.width&&e.width!==i)throw new Error("Image input config width doesn't match ImageBitmap width");s.width=i}else s.height=r,s.width=i;return p.bufferToTensor(a,s)}throw new Error("Can not access image data")}if(o)return new Promise(((n,r)=>{const i=document.createElement("canvas"),o=i.getContext("2d");if(!t||!o)return r();const a=new Image;a.crossOrigin="Anonymous",a.src=t,a.onload=()=>{i.width=a.width,i.height=a.height,o.drawImage(a,0,0,i.width,i.height);const t=o.getImageData(0,0,i.width,i.height);if(void 0!==e){if(void 0!==e.height&&e.height!==i.height)throw new Error("Image input config height doesn't match ImageBitmap height");if(s.height=i.height,void 0!==e.width&&e.width!==i.width)throw new Error("Image input config width doesn't match ImageBitmap width");s.width=i.width}else s.height=i.height,s.width=i.width;n(p.bufferToTensor(t.data,s))}}));throw new Error("Input data provided is not supported - aborted tensor creation")}{const n="RGBA";let r,i;if(void 0!==e&&void 0!==e.resizedWidth&&void 0!==e.resizedHeight?(r=e.resizedHeight,i=e.resizedWidth):(r=t.height,i=t.width),void 0!==e){if(s=e,void 0!==e.bitmapFormat&&e.bitmapFormat!==n)throw new Error("Image input config format must be RGBA for ImageData");s.bitmapFormat="RGBA"}else s.bitmapFormat="RGBA";if(s.height=r,s.width=i,void 0!==e){const e=document.createElement("canvas");e.width=i,e.height=r;const n=e.getContext("2d");if(null==n)throw new Error("Can not access image data");n.putImageData(t,0,0),a=n.getImageData(0,0,i,r).data}else a=t.data}}if(void 0!==a)return p.bufferToTensor(a,s);throw new Error("Input data provided is not supported - aborted tensor creation")}toImageData(t){var e,n;const r=document.createElement("canvas").getContext("2d");let i;if(null==r)throw new Error("Can not access image data");{const o=this.dims[3],a=this.dims[2],s=this.dims[1],u=void 0!==t&&void 0!==t.format?t.format:"RGB",c=void 0!==t&&void 0!==(null===(e=t.norm)||void 0===e?void 0:e.mean)?t.norm.mean:255,l=void 0!==t&&void 0!==(null===(n=t.norm)||void 0===n?void 0:n.bias)?t.norm.bias:0,p=a*o;if(void 0!==t){if(void 0!==t.height&&t.height!==a)throw new Error("Image output config height doesn't match tensor height");if(void 0!==t.width&&t.width!==o)throw new Error("Image output config width doesn't match tensor width");if(void 0!==t.format&&4===s&&"RGBA"!==t.format||3===s&&"RGB"!==t.format&&"BGR"!==t.format)throw new Error("Tensor format doesn't match input tensor dims")}const f=4;let d=0,h=1,g=2,b=3,m=0,y=p,_=2*p,v=-1;"RGBA"===u?(m=0,y=p,_=2*p,v=3*p):"RGB"===u?(m=0,y=p,_=2*p):"RBG"===u&&(m=0,_=p,y=2*p),i=r.createImageData(o,a);for(let t=0;t<a*o;d+=f,h+=f,g+=f,b+=f,t++)i.data[d]=(this.data[m++]-l)*c,i.data[h]=(this.data[y++]-l)*c,i.data[g]=(this.data[_++]-l)*c,i.data[b]=-1===v?255:(this.data[v++]-l)*c}return i}reshape(t){return new p(this.type,this.data,t)}}const f=p;class d{constructor(t){this.handler=t}async run(t,e,n){const r={};let i={};if("object"!=typeof t||null===t||t instanceof f||Array.isArray(t))throw new TypeError("'feeds' must be an object that use input names as keys and OnnxValue as corresponding values.");let o=!0;if("object"==typeof e){if(null===e)throw new TypeError("Unexpected argument[1]: cannot be null.");if(e instanceof f)throw new TypeError("'fetches' cannot be a Tensor");if(Array.isArray(e)){if(0===e.length)throw new TypeError("'fetches' cannot be an empty array.");o=!1;for(const t of e){if("string"!=typeof t)throw new TypeError("'fetches' must be a string array or an object.");if(-1===this.outputNames.indexOf(t))throw new RangeError(`'fetches' contains invalid output name: ${t}.`);r[t]=null}if("object"==typeof n&&null!==n)i=n;else if(void 0!==n)throw new TypeError("'options' must be an object.")}else{let t=!1;const a=Object.getOwnPropertyNames(e);for(const n of this.outputNames)if(-1!==a.indexOf(n)){const i=e[n];(null===i||i instanceof f)&&(t=!0,o=!1,r[n]=i)}if(t){if("object"==typeof n&&null!==n)i=n;else if(void 0!==n)throw new TypeError("'options' must be an object.")}else i=e}}else if(void 0!==e)throw new TypeError("Unexpected argument[1]: must be 'fetches' or 'options'.");for(const e of this.inputNames)if(void 0===t[e])throw new Error(`input '${e}' is missing in 'feeds'.`);if(o)for(const t of this.outputNames)r[t]=null;const a=await this.handler.run(t,r,i),s={};for(const t in a)Object.hasOwnProperty.call(a,t)&&(s[t]=new f(a[t].type,a[t].data,a[t].dims));return s}static async create(t,e,n,o){let a,s={};if("string"==typeof t){if(a=t,"object"==typeof e&&null!==e)s=e;else if(void 0!==e)throw new TypeError("'options' must be an object.")}else if(t instanceof Uint8Array){if(a=t,"object"==typeof e&&null!==e)s=e;else if(void 0!==e)throw new TypeError("'options' must be an object.")}else{if(!(t instanceof ArrayBuffer||"undefined"!=typeof SharedArrayBuffer&&t instanceof SharedArrayBuffer))throw new TypeError("Unexpected argument[0]: must be 'path' or 'buffer'.");{const r=t;let i=0,u=t.byteLength;if("object"==typeof e&&null!==e)s=e;else if("number"==typeof e){if(i=e,!Number.isSafeInteger(i))throw new RangeError("'byteOffset' must be an integer.");if(i<0||i>=r.byteLength)throw new RangeError(`'byteOffset' is out of range [0, ${r.byteLength}).`);if(u=t.byteLength-i,"number"==typeof n){if(u=n,!Number.isSafeInteger(u))throw new RangeError("'byteLength' must be an integer.");if(u<=0||i+u>r.byteLength)throw new RangeError(`'byteLength' is out of range (0, ${r.byteLength-i}].`);if("object"==typeof o&&null!==o)s=o;else if(void 0!==o)throw new TypeError("'options' must be an object.")}else if(void 0!==n)throw new TypeError("'byteLength' must be a number.")}else if(void 0!==e)throw new TypeError("'options' must be an object.");a=new Uint8Array(r,i,u)}}const u=(s.executionProviders||[]).map((t=>"string"==typeof t?t:t.name)),c=await(async t=>{const e=0===t.length?i:t,n=[];for(const t of e){const e=r[t];if(e){if(e.initialized)return e.backend;if(e.aborted)continue;const r=!!e.initPromise;try{return r||(e.initPromise=e.backend.init()),await e.initPromise,e.initialized=!0,e.backend}catch(i){r||n.push({name:t,err:i}),e.aborted=!0}finally{delete e.initPromise}}}throw new Error(`no available backend found. ERR: ${n.map((t=>`[${t.name}] ${t.err}`)).join(", ")}`)})(u),l=await c.createSessionHandler(a,s);return new d(l)}startProfiling(){this.handler.startProfiling()}endProfiling(){this.handler.endProfiling()}get inputNames(){return this.handler.inputNames}get outputNames(){return this.handler.outputNames}}const h=d},3474:(t,e,n)=>{var _scriptDir,r=(_scriptDir=(_scriptDir="undefined"!=typeof document&&document.currentScript?document.currentScript.src:void 0)||"/index.js",function(t){function e(){return $.buffer!=C&&H($.buffer),F}function r(){return $.buffer!=C&&H($.buffer),N}function i(){return $.buffer!=C&&H($.buffer),R}function o(){return $.buffer!=C&&H($.buffer),L}function a(){return $.buffer!=C&&H($.buffer),j}var s,u,c;t=t||{},s||(s=void 0!==t?t:{}),s.ready=new Promise((function(t,e){u=t,c=e}));var l,p,f,d,h,g,b=Object.assign({},s),m="./this.program",y=(t,e)=>{throw e},_="object"==typeof window,v="function"==typeof importScripts,w="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node,x=s.ENVIRONMENT_IS_PTHREAD||!1,T="";function S(t){return s.locateFile?s.locateFile(t,T):T+t}if(w){let e;T=v?n(908).dirname(T)+"/":"//",g=()=>{h||(d=n(1384),h=n(908))},l=function(t,e){return g(),t=h.normalize(t),d.readFileSync(t,e?void 0:"utf8")},f=t=>((t=l(t,!0)).buffer||(t=new Uint8Array(t)),t),p=(t,e,n)=>{g(),t=h.normalize(t),d.readFile(t,(function(t,r){t?n(t):e(r.buffer)}))},1<process.argv.length&&(m=process.argv[1].replace(/\\/g,"/")),process.argv.slice(2),process.on("uncaughtException",(function(t){if(!(t instanceof ut))throw t})),process.on("unhandledRejection",(function(t){throw t})),y=(t,e)=>{if(J())throw process.exitCode=t,e;e instanceof ut||P("exiting due to exception: "+e),process.exit(t)},s.inspect=function(){return"[Emscripten Module object]"};try{e=n(9925)}catch(t){throw console.error('The "worker_threads" module is not supported in this node.js build - perhaps a newer version is needed?'),t}n.g.Worker=e.Worker}else(_||v)&&(v?T=self.location.href:"undefined"!=typeof document&&document.currentScript&&(T=document.currentScript.src),_scriptDir&&(T=_scriptDir),T=0!==T.indexOf("blob:")?T.substr(0,T.replace(/[?#].*/,"").lastIndexOf("/")+1):"",w||(l=t=>{var e=new XMLHttpRequest;return e.open("GET",t,!1),e.send(null),e.responseText},v&&(f=t=>{var e=new XMLHttpRequest;return e.open("GET",t,!1),e.responseType="arraybuffer",e.send(null),new Uint8Array(e.response)}),p=(t,e,n)=>{var r=new XMLHttpRequest;r.open("GET",t,!0),r.responseType="arraybuffer",r.onload=()=>{200==r.status||0==r.status&&r.response?e(r.response):n()},r.onerror=n,r.send(null)}));w&&"undefined"==typeof performance&&(n.g.performance=n(6953).performance);var O=console.log.bind(console),A=console.warn.bind(console);w&&(g(),O=t=>d.writeSync(1,t+"\n"),A=t=>d.writeSync(2,t+"\n"));var E,I=s.print||O,P=s.printErr||A;Object.assign(s,b),b=null,s.thisProgram&&(m=s.thisProgram),s.quit&&(y=s.quit),s.wasmBinary&&(E=s.wasmBinary);var D=s.noExitRuntime||!1;"object"!=typeof WebAssembly&&it("no native wasm support detected");var $,k,C,F,N,R,L,j,M=!1,U="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0;function V(t,e,n){var r=(e>>>=0)+n;for(n=e;t[n]&&!(n>=r);)++n;if(16<n-e&&t.buffer&&U)return U.decode(t.buffer instanceof SharedArrayBuffer?t.slice(e,n):t.subarray(e,n));for(r="";e<n;){var i=t[e++];if(128&i){var o=63&t[e++];if(192==(224&i))r+=String.fromCharCode((31&i)<<6|o);else{var a=63&t[e++];65536>(i=224==(240&i)?(15&i)<<12|o<<6|a:(7&i)<<18|o<<12|a<<6|63&t[e++])?r+=String.fromCharCode(i):(i-=65536,r+=String.fromCharCode(55296|i>>10,56320|1023&i))}}else r+=String.fromCharCode(i)}return r}function B(t,e){return(t>>>=0)?V(r(),t,e):""}function z(t,e,n,r){if(!(0<r))return 0;var i=n>>>=0;r=n+r-1;for(var o=0;o<t.length;++o){var a=t.charCodeAt(o);if(55296<=a&&57343>=a&&(a=65536+((1023&a)<<10)|1023&t.charCodeAt(++o)),127>=a){if(n>=r)break;e[n++>>>0]=a}else{if(2047>=a){if(n+1>=r)break;e[n++>>>0]=192|a>>6}else{if(65535>=a){if(n+2>=r)break;e[n++>>>0]=224|a>>12}else{if(n+3>=r)break;e[n++>>>0]=240|a>>18,e[n++>>>0]=128|a>>12&63}e[n++>>>0]=128|a>>6&63}e[n++>>>0]=128|63&a}}return e[n>>>0]=0,n-i}function G(t){for(var e=0,n=0;n<t.length;++n){var r=t.charCodeAt(n);127>=r?e++:2047>=r?e+=2:55296<=r&&57343>=r?(e+=4,++n):e+=3}return e}function H(t){C=t,s.HEAP8=F=new Int8Array(t),s.HEAP16=new Int16Array(t),s.HEAP32=R=new Int32Array(t),s.HEAPU8=N=new Uint8Array(t),s.HEAPU16=new Uint16Array(t),s.HEAPU32=L=new Uint32Array(t),s.HEAPF32=new Float32Array(t),s.HEAPF64=j=new Float64Array(t)}x&&(C=s.buffer);var W=s.INITIAL_MEMORY||16777216;if(x)$=s.wasmMemory,C=s.buffer;else if(s.wasmMemory)$=s.wasmMemory;else if(!(($=new WebAssembly.Memory({initial:W/65536,maximum:65536,shared:!0})).buffer instanceof SharedArrayBuffer))throw P("requested a shared WebAssembly.Memory but the returned buffer is not a SharedArrayBuffer, indicating that while the browser has SharedArrayBuffer it does not have WebAssembly threads support - you may need to set a flag"),w&&console.log("(on node you may need: --experimental-wasm-threads --experimental-wasm-bulk-memory and also use a recent version)"),Error("bad memory");$&&(C=$.buffer),W=C.byteLength,H(C);var q,X=[],Y=[],K=[],Z=[];function J(){return D||!1}function Q(){var t=s.preRun.shift();X.unshift(t)}var tt,et=0,nt=null,rt=null;function it(t){throw x?postMessage({cmd:"onAbort",arg:t}):s.onAbort&&s.onAbort(t),P(t="Aborted("+t+")"),M=!0,t=new WebAssembly.RuntimeError(t+". Build with -sASSERTIONS for more info."),c(t),t}function ot(){return tt.startsWith("data:application/octet-stream;base64,")}function at(){var t=tt;try{if(t==tt&&E)return new Uint8Array(E);if(f)return f(t);throw"both async and sync fetching of the wasm failed"}catch(t){it(t)}}tt="ort-wasm-threaded.wasm",ot()||(tt=S(tt));var st={};function ut(t){this.name="ExitStatus",this.message="Program terminated with exit("+t+")",this.status=t}function ct(t){(t=dt.Vb[t])||it(),dt.mc(t)}function lt(t){var e=dt.Cc();if(!e)return 6;dt.ac.push(e),dt.Vb[t.Ub]=e,e.Ub=t.Ub;var n={cmd:"run",start_routine:t.Ic,arg:t.zc,pthread_ptr:t.Ub};return e.$b=()=>{n.time=performance.now(),e.postMessage(n,t.Nc)},e.loaded&&(e.$b(),delete e.$b),0}function pt(t){if(x)return qt(1,1,t);J()||(dt.oc(),s.onExit&&s.onExit(t),M=!0),y(t,new ut(t))}function ft(t,e){if(!e&&x)throw bt(t),"unwind";J()||x||(me(),ht(K),be(0),re[1].length&&ie(1,10),re[2].length&&ie(2,10),dt.oc()),pt(t)}var dt={Yb:[],ac:[],qc:[],Vb:{},fc:function(){x&&dt.Ec()},Pc:function(){},Ec:function(){dt.receiveObjectTransfer=dt.Gc,dt.threadInitTLS=dt.pc,dt.setExitStatus=dt.nc,D=!1},nc:function(){},oc:function(){for(var t of Object.values(dt.Vb))dt.mc(t);for(t of dt.Yb)t.terminate();dt.Yb=[]},mc:function(t){var e=t.Ub;delete dt.Vb[e],dt.Yb.push(t),dt.ac.splice(dt.ac.indexOf(t),1),t.Ub=0,xe(e)},Gc:function(){},pc:function(){dt.qc.forEach((t=>t()))},Fc:function(t,e){t.onmessage=n=>{var r=(n=n.data).cmd;if(t.Ub&&(dt.Bc=t.Ub),n.targetThread&&n.targetThread!=de()){var i=dt.Vb[n.Qc];i?i.postMessage(n,n.transferList):P('Internal error! Worker sent a message "'+r+'" to target pthread '+n.targetThread+", but that thread no longer exists!")}else"processProxyingQueue"===r?Vt(n.queue):"spawnThread"===r?lt(n):"cleanupThread"===r?ct(n.thread):"killThread"===r?(n=n.thread,r=dt.Vb[n],delete dt.Vb[n],r.terminate(),xe(n),dt.ac.splice(dt.ac.indexOf(r),1),r.Ub=0):"cancelThread"===r?dt.Vb[n.thread].postMessage({cmd:"cancel"}):"loaded"===r?(t.loaded=!0,e&&e(t),t.$b&&(t.$b(),delete t.$b)):"print"===r?I("Thread "+n.threadId+": "+n.text):"printErr"===r?P("Thread "+n.threadId+": "+n.text):"alert"===r?alert("Thread "+n.threadId+": "+n.text):"setimmediate"===n.target?t.postMessage(n):"onAbort"===r?s.onAbort&&s.onAbort(n.arg):r&&P("worker sent an unknown command "+r);dt.Bc=void 0},t.onerror=t=>{throw P("worker sent an error! "+t.filename+":"+t.lineno+": "+t.message),t},w&&(t.on("message",(function(e){t.onmessage({data:e})})),t.on("error",(function(e){t.onerror(e)})),t.on("detachedExit",(function(){}))),t.postMessage({cmd:"load",urlOrBlob:s.mainScriptUrlOrBlob||_scriptDir,wasmMemory:$,wasmModule:k})},yc:function(){var t=S("ort-wasm-threaded.worker.js");dt.Yb.push(new Worker(t))},Cc:function(){return 0==dt.Yb.length&&(dt.yc(),dt.Fc(dt.Yb[0])),dt.Yb.pop()}};function ht(t){for(;0<t.length;)t.shift()(s)}function gt(t){var e=Ae();return t=t(),Ee(e),t}function bt(t){if(x)return qt(2,0,t);try{ft(t)}catch(t){t instanceof ut||"unwind"==t||y(1,t)}}s.PThread=dt,s.establishStackSpace=function(){var t=de(),e=i()[t+44>>2>>>0];t=i()[t+48>>2>>>0],Oe(e,e-t),Ee(e)};var mt=[];function yt(t){var e=mt[t];return e||(t>=mt.length&&(mt.length=t+1),mt[t]=e=q.get(t)),e}s.invokeEntryPoint=function(t,e){t=yt(t)(e),J()?dt.nc(t):Te(t)};var _t,vt,wt=[],xt=0,Tt=0;function St(t){this.Zb=t,this.Sb=t-24,this.xc=function(t){o()[this.Sb+4>>2>>>0]=t},this.bc=function(){return o()[this.Sb+4>>2>>>0]},this.wc=function(t){o()[this.Sb+8>>2>>>0]=t},this.Dc=function(){return o()[this.Sb+8>>2>>>0]},this.rc=function(){i()[this.Sb>>2>>>0]=0},this.hc=function(t){t=t?1:0,e()[this.Sb+12>>0>>>0]=t},this.uc=function(){return 0!=e()[this.Sb+12>>0>>>0]},this.ic=function(t){t=t?1:0,e()[this.Sb+13>>0>>>0]=t},this.kc=function(){return 0!=e()[this.Sb+13>>0>>>0]},this.fc=function(t,e){this.cc(0),this.xc(t),this.wc(e),this.rc(),this.hc(!1),this.ic(!1)},this.sc=function(){Atomics.add(i(),this.Sb>>2,1)},this.Hc=function(){return 1===Atomics.sub(i(),this.Sb>>2,1)},this.cc=function(t){o()[this.Sb+16>>2>>>0]=t},this.tc=function(){return o()[this.Sb+16>>2>>>0]},this.vc=function(){if(De(this.bc()))return o()[this.Zb>>2>>>0];var t=this.tc();return 0!==t?t:this.Zb}}function Ot(t){return ge(new St(t).Sb)}function At(t,e,n,r){return x?qt(3,1,t,e,n,r):Et(t,e,n,r)}function Et(t,e,n,r){if("undefined"==typeof SharedArrayBuffer)return P("Current environment does not support SharedArrayBuffer, pthreads are not available!"),6;var i=[];return x&&0===i.length?At(t,e,n,r):(t={Ic:n,Ub:t,zc:r,Nc:i},x?(t.Oc="spawnThread",postMessage(t,i),0):lt(t))}function It(t,e,n){return x?qt(4,1,t,e,n):0}function Pt(t,e){if(x)return qt(5,1,t,e)}function Dt(t,e){if(x)return qt(6,1,t,e)}function $t(t,e,n){if(x)return qt(7,1,t,e,n)}function kt(t,e,n){return x?qt(8,1,t,e,n):0}function Ct(t,e){if(x)return qt(9,1,t,e)}function Ft(t,e,n){if(x)return qt(10,1,t,e,n)}function Nt(t,e,n,r){if(x)return qt(11,1,t,e,n,r)}function Rt(t,e,n,r){if(x)return qt(12,1,t,e,n,r)}function Lt(t,e,n,r){if(x)return qt(13,1,t,e,n,r)}function jt(t){if(x)return qt(14,1,t)}function Mt(t,e){if(x)return qt(15,1,t,e)}function Ut(t,e,n){if(x)return qt(16,1,t,e,n)}function Vt(t){Atomics.store(i(),t>>2,1),de()&&we(t),Atomics.compareExchange(i(),t>>2,1,0)}function Bt(t){return o()[t>>>2]+4294967296*i()[t+4>>>2]}function zt(t,e,n,r,i,o){return x?qt(17,1,t,e,n,r,i,o):-52}function Gt(t,e,n,r,i,o){if(x)return qt(18,1,t,e,n,r,i,o)}function Ht(t){var n=G(t)+1,r=he(n);return r&&z(t,e(),r,n),r}function Wt(t,e,n){function r(t){return(t=t.toTimeString().match(/\(([A-Za-z ]+)\)$/))?t[1]:"GMT"}if(x)return qt(19,1,t,e,n);var a=(new Date).getFullYear(),s=new Date(a,0,1),u=new Date(a,6,1);a=s.getTimezoneOffset();var c=u.getTimezoneOffset(),l=Math.max(a,c);i()[t>>2>>>0]=60*l,i()[e>>2>>>0]=Number(a!=c),t=r(s),e=r(u),t=Ht(t),e=Ht(e),c<a?(o()[n>>2>>>0]=t,o()[n+4>>2>>>0]=e):(o()[n>>2>>>0]=e,o()[n+4>>2>>>0]=t)}function qt(t,e){var n=arguments.length-2,r=arguments;return gt((()=>{for(var i=Ie(8*n),o=i>>3,s=0;s<n;s++){var u=r[2+s];a()[o+s>>>0]=u}return ve(t,n,i,e)}))}s.executeNotifiedProxyingQueue=Vt,vt=w?()=>{var t=process.hrtime();return 1e3*t[0]+t[1]/1e6}:x?()=>performance.now()-s.__performance_now_clock_drift:()=>performance.now();var Xt,Yt=[],Kt={};function Zt(){if(!Xt){var t,e={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:m||"./this.program"};for(t in Kt)void 0===Kt[t]?delete e[t]:e[t]=Kt[t];var n=[];for(t in e)n.push(t+"="+e[t]);Xt=n}return Xt}function Jt(t,n){if(x)return qt(20,1,t,n);var r=0;return Zt().forEach((function(i,a){var s=n+r;for(a=o()[t+4*a>>2>>>0]=s,s=0;s<i.length;++s)e()[a++>>0>>>0]=i.charCodeAt(s);e()[a>>0>>>0]=0,r+=i.length+1})),0}function Qt(t,e){if(x)return qt(21,1,t,e);var n=Zt();o()[t>>2>>>0]=n.length;var r=0;return n.forEach((function(t){r+=t.length+1})),o()[e>>2>>>0]=r,0}function te(t){return x?qt(22,1,t):52}function ee(t,e,n,r){return x?qt(23,1,t,e,n,r):52}function ne(t,e,n,r,i){return x?qt(24,1,t,e,n,r,i):70}var re=[null,[],[]];function ie(t,e){var n=re[t];0===e||10===e?((1===t?I:P)(V(n,0)),n.length=0):n.push(e)}function oe(t,e,n,i){if(x)return qt(25,1,t,e,n,i);for(var a=0,s=0;s<n;s++){var u=o()[e>>2>>>0],c=o()[e+4>>2>>>0];e+=8;for(var l=0;l<c;l++)ie(t,r()[u+l>>>0]);a+=c}return o()[i>>2>>>0]=a,0}var ae=0;function se(t){return 0==t%4&&(0!=t%100||0==t%400)}var ue=[31,29,31,30,31,30,31,31,30,31,30,31],ce=[31,28,31,30,31,30,31,31,30,31,30,31];function le(t,n,r,o){function a(t,e,n){for(t="number"==typeof t?t.toString():t||"";t.length<e;)t=n[0]+t;return t}function s(t,e){return a(t,e,"0")}function u(t,e){function n(t){return 0>t?-1:0<t?1:0}var r;return 0===(r=n(t.getFullYear()-e.getFullYear()))&&0===(r=n(t.getMonth()-e.getMonth()))&&(r=n(t.getDate()-e.getDate())),r}function c(t){switch(t.getDay()){case 0:return new Date(t.getFullYear()-1,11,29);case 1:return t;case 2:return new Date(t.getFullYear(),0,3);case 3:return new Date(t.getFullYear(),0,2);case 4:return new Date(t.getFullYear(),0,1);case 5:return new Date(t.getFullYear()-1,11,31);case 6:return new Date(t.getFullYear()-1,11,30)}}function l(t){var e=t.Wb;for(t=new Date(new Date(t.Xb+1900,0,1).getTime());0<e;){var n=t.getMonth(),r=(se(t.getFullYear())?ue:ce)[n];if(!(e>r-t.getDate())){t.setDate(t.getDate()+e);break}e-=r-t.getDate()+1,t.setDate(1),11>n?t.setMonth(n+1):(t.setMonth(0),t.setFullYear(t.getFullYear()+1))}return n=new Date(t.getFullYear()+1,0,4),e=c(new Date(t.getFullYear(),0,4)),n=c(n),0>=u(e,t)?0>=u(n,t)?t.getFullYear()+1:t.getFullYear():t.getFullYear()-1}var p=i()[o+40>>2>>>0];for(var f in o={Lc:i()[o>>2>>>0],Kc:i()[o+4>>2>>>0],dc:i()[o+8>>2>>>0],jc:i()[o+12>>2>>>0],ec:i()[o+16>>2>>>0],Xb:i()[o+20>>2>>>0],Tb:i()[o+24>>2>>>0],Wb:i()[o+28>>2>>>0],Rc:i()[o+32>>2>>>0],Jc:i()[o+36>>2>>>0],Mc:p?B(p):""},r=B(r),p={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"})r=r.replace(new RegExp(f,"g"),p[f]);var d="Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),h="January February March April May June July August September October November December".split(" ");for(f in p={"%a":function(t){return d[t.Tb].substring(0,3)},"%A":function(t){return d[t.Tb]},"%b":function(t){return h[t.ec].substring(0,3)},"%B":function(t){return h[t.ec]},"%C":function(t){return s((t.Xb+1900)/100|0,2)},"%d":function(t){return s(t.jc,2)},"%e":function(t){return a(t.jc,2," ")},"%g":function(t){return l(t).toString().substring(2)},"%G":function(t){return l(t)},"%H":function(t){return s(t.dc,2)},"%I":function(t){return 0==(t=t.dc)?t=12:12<t&&(t-=12),s(t,2)},"%j":function(t){for(var e=0,n=0;n<=t.ec-1;e+=(se(t.Xb+1900)?ue:ce)[n++]);return s(t.jc+e,3)},"%m":function(t){return s(t.ec+1,2)},"%M":function(t){return s(t.Kc,2)},"%n":function(){return"\n"},"%p":function(t){return 0<=t.dc&&12>t.dc?"AM":"PM"},"%S":function(t){return s(t.Lc,2)},"%t":function(){return"\t"},"%u":function(t){return t.Tb||7},"%U":function(t){return s(Math.floor((t.Wb+7-t.Tb)/7),2)},"%V":function(t){var e=Math.floor((t.Wb+7-(t.Tb+6)%7)/7);if(2>=(t.Tb+371-t.Wb-2)%7&&e++,e)53==e&&(4==(n=(t.Tb+371-t.Wb)%7)||3==n&&se(t.Xb)||(e=1));else{e=52;var n=(t.Tb+7-t.Wb-1)%7;(4==n||5==n&&se(t.Xb%400-1))&&e++}return s(e,2)},"%w":function(t){return t.Tb},"%W":function(t){return s(Math.floor((t.Wb+7-(t.Tb+6)%7)/7),2)},"%y":function(t){return(t.Xb+1900).toString().substring(2)},"%Y":function(t){return t.Xb+1900},"%z":function(t){var e=0<=(t=t.Jc);return t=Math.abs(t)/60,(e?"+":"-")+String("0000"+(t/60*100+t%60)).slice(-4)},"%Z":function(t){return t.Mc},"%%":function(){return"%"}},r=r.replace(/%%/g,"\0\0"),p)r.includes(f)&&(r=r.replace(new RegExp(f,"g"),p[f](o)));return f=function(t){var e=Array(G(t)+1);return z(t,e,0,e.length),e}(r=r.replace(/\0\0/g,"%")),f.length>n?0:(function(t,n){e().set(t,n>>>0)}(f,t),f.length-1)}dt.fc();var pe=[null,pt,bt,At,It,Pt,Dt,$t,kt,Ct,Ft,Nt,Rt,Lt,jt,Mt,Ut,zt,Gt,Wt,Jt,Qt,te,ee,ne,oe],fe={b:function(t){return he(t+24)+24},n:function(t){return(t=new St(t)).uc()||(t.hc(!0),xt--),t.ic(!1),wt.push(t),t.sc(),t.vc()},ma:function(t){throw P("Unexpected exception thrown, this is not properly supported - aborting"),M=!0,t},x:function(){Se(0);var t=wt.pop();if(t.Hc()&&!t.kc()){var e=t.Dc();e&&yt(e)(t.Zb),Ot(t.Zb)}Tt=0},e:function(){var t=Tt;if(!t)return ae=0;var e=new St(t);e.cc(t);var n=e.bc();if(!n)return ae=0,t;for(var r=Array.prototype.slice.call(arguments),i=0;i<r.length;i++){var o=r[i];if(0===o||o===n)break;if(Pe(o,n,e.Sb+16))return ae=o,t}return ae=n,t},l:function(){var t=Tt;if(!t)return ae=0;var e=new St(t);e.cc(t);var n=e.bc();if(!n)return ae=0,t;for(var r=Array.prototype.slice.call(arguments),i=0;i<r.length;i++){var o=r[i];if(0===o||o===n)break;if(Pe(o,n,e.Sb+16))return ae=o,t}return ae=n,t},h:function(){var t=Tt;if(!t)return ae=0;var e=new St(t);e.cc(t);var n=e.bc();if(!n)return ae=0,t;for(var r=Array.prototype.slice.call(arguments),i=0;i<r.length;i++){var o=r[i];if(0===o||o===n)break;if(Pe(o,n,e.Sb+16))return ae=o,t}return ae=n,t},t:Ot,M:function(){var t=wt.pop();t||it("no exception to throw");var e=t.Zb;throw t.kc()||(wt.push(t),t.ic(!0),t.hc(!1),xt++),Tt=e,e},c:function(t,e,n){throw new St(t).fc(e,n),Tt=t,xt++,t},pa:function(){return xt},Fa:function(t){ye(t,!v,1,!_),dt.pc()},T:function(t){x?postMessage({cmd:"cleanupThread",thread:t}):ct(t)},xa:Et,j:function(t){throw Tt||(Tt=t),t},H:It,Ma:Pt,ua:Dt,wa:$t,oa:kt,Ka:Ct,Ca:Ft,Ja:Nt,V:Rt,va:Lt,sa:jt,La:Mt,ta:Ut,Ta:function(){},X:function(){it("To use dlopen, you need enable dynamic linking, see https://github.com/emscripten-core/emscripten/wiki/Linking")},Ua:function(){it("To use dlopen, you need enable dynamic linking, see https://github.com/emscripten-core/emscripten/wiki/Linking")},W:function(){return Date.now()},ya:function(){return 2097152},Oa:function(){return!0},za:function(t,e,n,r){if(t==e)setTimeout((()=>Vt(r)));else if(x)postMessage({targetThread:t,cmd:"processProxyingQueue",queue:r});else{if(!(t=dt.Vb[t]))return;t.postMessage({cmd:"processProxyingQueue",queue:r})}return 1},Ea:function(){return-1},Pa:function(t,e){t=new Date(1e3*Bt(t)),i()[e>>2>>>0]=t.getUTCSeconds(),i()[e+4>>2>>>0]=t.getUTCMinutes(),i()[e+8>>2>>>0]=t.getUTCHours(),i()[e+12>>2>>>0]=t.getUTCDate(),i()[e+16>>2>>>0]=t.getUTCMonth(),i()[e+20>>2>>>0]=t.getUTCFullYear()-1900,i()[e+24>>2>>>0]=t.getUTCDay(),t=(t.getTime()-Date.UTC(t.getUTCFullYear(),0,1,0,0,0,0))/864e5|0,i()[e+28>>2>>>0]=t},Qa:function(t,e){t=new Date(1e3*Bt(t)),i()[e>>2>>>0]=t.getSeconds(),i()[e+4>>2>>>0]=t.getMinutes(),i()[e+8>>2>>>0]=t.getHours(),i()[e+12>>2>>>0]=t.getDate(),i()[e+16>>2>>>0]=t.getMonth(),i()[e+20>>2>>>0]=t.getFullYear()-1900,i()[e+24>>2>>>0]=t.getDay();var n=new Date(t.getFullYear(),0,1),r=(t.getTime()-n.getTime())/864e5|0;i()[e+28>>2>>>0]=r,i()[e+36>>2>>>0]=-60*t.getTimezoneOffset(),r=new Date(t.getFullYear(),6,1).getTimezoneOffset(),t=0|(r!=(n=n.getTimezoneOffset())&&t.getTimezoneOffset()==Math.min(n,r)),i()[e+32>>2>>>0]=t},Ra:function(t){var e=new Date(i()[t+20>>2>>>0]+1900,i()[t+16>>2>>>0],i()[t+12>>2>>>0],i()[t+8>>2>>>0],i()[t+4>>2>>>0],i()[t>>2>>>0],0),n=i()[t+32>>2>>>0],r=e.getTimezoneOffset(),o=new Date(e.getFullYear(),0,1),a=new Date(e.getFullYear(),6,1).getTimezoneOffset(),s=o.getTimezoneOffset(),u=Math.min(s,a);return 0>n?i()[t+32>>2>>>0]=Number(a!=s&&u==r):0<n!=(u==r)&&(a=Math.max(s,a),e.setTime(e.getTime()+6e4*((0<n?u:a)-r))),i()[t+24>>2>>>0]=e.getDay(),n=(e.getTime()-o.getTime())/864e5|0,i()[t+28>>2>>>0]=n,i()[t>>2>>>0]=e.getSeconds(),i()[t+4>>2>>>0]=e.getMinutes(),i()[t+8>>2>>>0]=e.getHours(),i()[t+12>>2>>>0]=e.getDate(),i()[t+16>>2>>>0]=e.getMonth(),e.getTime()/1e3|0},Aa:zt,Ba:Gt,Sa:function t(e,n,r){t.Ac||(t.Ac=!0,Wt(e,n,r))},y:function(){it("")},U:function(){if(!w&&!v){var t="Blocking on the main thread is very dangerous, see https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread";_t||(_t={}),_t[t]||(_t[t]=1,w&&(t="warning: "+t),P(t))}},ra:function(){return 4294901760},B:vt,Ia:function(t,e,n){r().copyWithin(t>>>0,e>>>0,e+n>>>0)},F:function(){return w?n(3993).cpus().length:navigator.hardwareConcurrency},Da:function(t,e,n){Yt.length=e,n>>=3;for(var r=0;r<e;r++)Yt[r]=a()[n+r>>>0];return(0>t?st[-t-1]:pe[t]).apply(null,Yt)},qa:function(t){var e=r().length;if((t>>>=0)<=e||4294901760<t)return!1;for(var n=1;4>=n;n*=2){var i=e*(1+.2/n);i=Math.min(i,t+100663296);var o=Math;i=Math.max(t,i),o=o.min.call(o,4294901760,i+(65536-i%65536)%65536);t:{try{$.grow(o-C.byteLength+65535>>>16),H($.buffer);var a=1;break t}catch(t){}a=void 0}if(a)return!0}return!1},Na:function(){throw"unwind"},Ga:Jt,Ha:Qt,J:ft,I:te,S:ee,ga:ne,R:oe,d:function(){return ae},na:function t(r,i){t.lc||(t.lc=function(){if("object"==typeof crypto&&"function"==typeof crypto.getRandomValues){var t=new Uint8Array(1);return()=>(crypto.getRandomValues(t),t[0])}if(w)try{var e=n(Object(function(){var t=new Error("Cannot find module 'crypto'");throw t.code="MODULE_NOT_FOUND",t}()));return()=>e.randomBytes(1)[0]}catch(t){}return()=>it("randomDevice")}());for(var o=0;o<i;o++)e()[r+o>>0>>>0]=t.lc();return 0},ia:function(t,e,n){var r=Ae();try{return yt(t)(e,n)}catch(t){if(Ee(r),t!==t+0)throw t;Se(1,0)}},ja:function(t,e,n){var r=Ae();try{return yt(t)(e,n)}catch(t){if(Ee(r),t!==t+0)throw t;Se(1,0)}},K:function(t){var e=Ae();try{return yt(t)()}catch(t){if(Ee(e),t!==t+0)throw t;Se(1,0)}},f:function(t,e){var n=Ae();try{return yt(t)(e)}catch(t){if(Ee(n),t!==t+0)throw t;Se(1,0)}},P:function(t,e,n){var r=Ae();try{return yt(t)(e,n)}catch(t){if(Ee(r),t!==t+0)throw t;Se(1,0)}},Q:function(t,e,n){var r=Ae();try{return yt(t)(e,n)}catch(t){if(Ee(r),t!==t+0)throw t;Se(1,0)}},k:function(t,e,n){var r=Ae();try{return yt(t)(e,n)}catch(t){if(Ee(r),t!==t+0)throw t;Se(1,0)}},p:function(t,e,n,r){var i=Ae();try{return yt(t)(e,n,r)}catch(t){if(Ee(i),t!==t+0)throw t;Se(1,0)}},q:function(t,e,n,r,i){var o=Ae();try{return yt(t)(e,n,r,i)}catch(t){if(Ee(o),t!==t+0)throw t;Se(1,0)}},N:function(t,e,n,r,i,o){var a=Ae();try{return yt(t)(e,n,r,i,o)}catch(t){if(Ee(a),t!==t+0)throw t;Se(1,0)}},s:function(t,e,n,r,i,o){var a=Ae();try{return yt(t)(e,n,r,i,o)}catch(t){if(Ee(a),t!==t+0)throw t;Se(1,0)}},w:function(t,e,n,r,i,o,a){var s=Ae();try{return yt(t)(e,n,r,i,o,a)}catch(t){if(Ee(s),t!==t+0)throw t;Se(1,0)}},L:function(t,e,n,r,i,o,a,s){var u=Ae();try{return yt(t)(e,n,r,i,o,a,s)}catch(t){if(Ee(u),t!==t+0)throw t;Se(1,0)}},E:function(t,e,n,r,i,o,a,s,u,c,l,p){var f=Ae();try{return yt(t)(e,n,r,i,o,a,s,u,c,l,p)}catch(t){if(Ee(f),t!==t+0)throw t;Se(1,0)}},aa:function(t,e,n,r,i,o,a,s){var u=Ae();try{return Me(t,e,n,r,i,o,a,s)}catch(t){if(Ee(u),t!==t+0)throw t;Se(1,0)}},_:function(t,e,n,r,i,o,a){var s=Ae();try{return ke(t,e,n,r,i,o,a)}catch(t){if(Ee(s),t!==t+0)throw t;Se(1,0)}},Z:function(t,e,n,r,i){var o=Ae();try{return Ue(t,e,n,r,i)}catch(t){if(Ee(o),t!==t+0)throw t;Se(1,0)}},ca:function(t,e,n,r){var i=Ae();try{return Le(t,e,n,r)}catch(t){if(Ee(i),t!==t+0)throw t;Se(1,0)}},$:function(t){var e=Ae();try{return $e(t)}catch(t){if(Ee(e),t!==t+0)throw t;Se(1,0)}},ba:function(t,e){var n=Ae();try{return je(t,e)}catch(t){if(Ee(n),t!==t+0)throw t;Se(1,0)}},Y:function(t,e,n){var r=Ae();try{return Ce(t,e,n)}catch(t){if(Ee(r),t!==t+0)throw t;Se(1,0)}},g:function(t){var e=Ae();try{yt(t)()}catch(t){if(Ee(e),t!==t+0)throw t;Se(1,0)}},r:function(t,e){var n=Ae();try{yt(t)(e)}catch(t){if(Ee(n),t!==t+0)throw t;Se(1,0)}},i:function(t,e,n){var r=Ae();try{yt(t)(e,n)}catch(t){if(Ee(r),t!==t+0)throw t;Se(1,0)}},ha:function(t,e,n,r){var i=Ae();try{yt(t)(e,n,r)}catch(t){if(Ee(i),t!==t+0)throw t;Se(1,0)}},m:function(t,e,n,r){var i=Ae();try{yt(t)(e,n,r)}catch(t){if(Ee(i),t!==t+0)throw t;Se(1,0)}},v:function(t,e,n,r,i){var o=Ae();try{yt(t)(e,n,r,i)}catch(t){if(Ee(o),t!==t+0)throw t;Se(1,0)}},u:function(t,e,n,r,i,o){var a=Ae();try{yt(t)(e,n,r,i,o)}catch(t){if(Ee(a),t!==t+0)throw t;Se(1,0)}},O:function(t,e,n,r,i,o,a){var s=Ae();try{yt(t)(e,n,r,i,o,a)}catch(t){if(Ee(s),t!==t+0)throw t;Se(1,0)}},A:function(t,e,n,r,i,o,a,s){var u=Ae();try{yt(t)(e,n,r,i,o,a,s)}catch(t){if(Ee(u),t!==t+0)throw t;Se(1,0)}},ka:function(t,e,n,r,i,o,a,s,u){var c=Ae();try{yt(t)(e,n,r,i,o,a,s,u)}catch(t){if(Ee(c),t!==t+0)throw t;Se(1,0)}},C:function(t,e,n,r,i,o,a,s,u,c,l){var p=Ae();try{yt(t)(e,n,r,i,o,a,s,u,c,l)}catch(t){if(Ee(p),t!==t+0)throw t;Se(1,0)}},D:function(t,e,n,r,i,o,a,s,u,c,l,p,f,d,h,g){var b=Ae();try{yt(t)(e,n,r,i,o,a,s,u,c,l,p,f,d,h,g)}catch(t){if(Ee(b),t!==t+0)throw t;Se(1,0)}},fa:function(t,e,n,r,i,o,a,s){var u=Ae();try{Fe(t,e,n,r,i,o,a,s)}catch(t){if(Ee(u),t!==t+0)throw t;Se(1,0)}},da:function(t,e,n,r,i,o,a,s,u,c,l,p){var f=Ae();try{Re(t,e,n,r,i,o,a,s,u,c,l,p)}catch(t){if(Ee(f),t!==t+0)throw t;Se(1,0)}},ea:function(t,e,n,r,i,o){var a=Ae();try{Ne(t,e,n,r,i,o)}catch(t){if(Ee(a),t!==t+0)throw t;Se(1,0)}},o:function(t){return t},a:$||s.wasmMemory,G:function(t){ae=t},la:le,z:function(t,e,n,r){return le(t,e,n,r)}};!function(){function t(t,e){s.asm=t.exports,dt.qc.push(s.asm.sb),q=s.asm.ub,Y.unshift(s.asm.Va),k=e,x||(et--,s.monitorRunDependencies&&s.monitorRunDependencies(et),0==et&&(null!==nt&&(clearInterval(nt),nt=null),rt&&(t=rt,rt=null,t())))}function e(e){t(e.instance,e.module)}function n(t){return function(){if(!E&&(_||v)){if("function"==typeof fetch&&!tt.startsWith("file://"))return fetch(tt,{credentials:"same-origin"}).then((function(t){if(!t.ok)throw"failed to load wasm binary file at '"+tt+"'";return t.arrayBuffer()})).catch((function(){return at()}));if(p)return new Promise((function(t,e){p(tt,(function(e){t(new Uint8Array(e))}),e)}))}return Promise.resolve().then((function(){return at()}))}().then((function(t){return WebAssembly.instantiate(t,r)})).then((function(t){return t})).then(t,(function(t){P("failed to asynchronously prepare wasm: "+t),it(t)}))}var r={a:fe};if(x||(et++,s.monitorRunDependencies&&s.monitorRunDependencies(et)),s.instantiateWasm)try{return s.instantiateWasm(r,t)}catch(t){return P("Module.instantiateWasm callback failed with error: "+t),!1}(E||"function"!=typeof WebAssembly.instantiateStreaming||ot()||tt.startsWith("file://")||w||"function"!=typeof fetch?n(e):fetch(tt,{credentials:"same-origin"}).then((function(t){return WebAssembly.instantiateStreaming(t,r).then(e,(function(t){return P("wasm streaming compile failed: "+t),P("falling back to ArrayBuffer instantiation"),n(e)}))}))).catch(c)}(),s.___wasm_call_ctors=function(){return(s.___wasm_call_ctors=s.asm.Va).apply(null,arguments)},s._OrtInit=function(){return(s._OrtInit=s.asm.Wa).apply(null,arguments)},s._OrtCreateSessionOptions=function(){return(s._OrtCreateSessionOptions=s.asm.Xa).apply(null,arguments)},s._OrtAppendExecutionProvider=function(){return(s._OrtAppendExecutionProvider=s.asm.Ya).apply(null,arguments)},s._OrtAddSessionConfigEntry=function(){return(s._OrtAddSessionConfigEntry=s.asm.Za).apply(null,arguments)},s._OrtReleaseSessionOptions=function(){return(s._OrtReleaseSessionOptions=s.asm._a).apply(null,arguments)},s._OrtCreateSession=function(){return(s._OrtCreateSession=s.asm.$a).apply(null,arguments)},s._OrtReleaseSession=function(){return(s._OrtReleaseSession=s.asm.ab).apply(null,arguments)},s._OrtGetInputCount=function(){return(s._OrtGetInputCount=s.asm.bb).apply(null,arguments)},s._OrtGetOutputCount=function(){return(s._OrtGetOutputCount=s.asm.cb).apply(null,arguments)},s._OrtGetInputName=function(){return(s._OrtGetInputName=s.asm.db).apply(null,arguments)},s._OrtGetOutputName=function(){return(s._OrtGetOutputName=s.asm.eb).apply(null,arguments)},s._OrtFree=function(){return(s._OrtFree=s.asm.fb).apply(null,arguments)},s._OrtCreateTensor=function(){return(s._OrtCreateTensor=s.asm.gb).apply(null,arguments)},s._OrtGetTensorData=function(){return(s._OrtGetTensorData=s.asm.hb).apply(null,arguments)},s._OrtReleaseTensor=function(){return(s._OrtReleaseTensor=s.asm.ib).apply(null,arguments)},s._OrtCreateRunOptions=function(){return(s._OrtCreateRunOptions=s.asm.jb).apply(null,arguments)},s._OrtAddRunConfigEntry=function(){return(s._OrtAddRunConfigEntry=s.asm.kb).apply(null,arguments)},s._OrtReleaseRunOptions=function(){return(s._OrtReleaseRunOptions=s.asm.lb).apply(null,arguments)},s._OrtRun=function(){return(s._OrtRun=s.asm.mb).apply(null,arguments)},s._OrtEndProfiling=function(){return(s._OrtEndProfiling=s.asm.nb).apply(null,arguments)};var de=s._pthread_self=function(){return(de=s._pthread_self=s.asm.ob).apply(null,arguments)},he=s._malloc=function(){return(he=s._malloc=s.asm.pb).apply(null,arguments)},ge=s._free=function(){return(ge=s._free=s.asm.qb).apply(null,arguments)},be=s._fflush=function(){return(be=s._fflush=s.asm.rb).apply(null,arguments)};s.__emscripten_tls_init=function(){return(s.__emscripten_tls_init=s.asm.sb).apply(null,arguments)};var me=s.___funcs_on_exit=function(){return(me=s.___funcs_on_exit=s.asm.tb).apply(null,arguments)},ye=s.__emscripten_thread_init=function(){return(ye=s.__emscripten_thread_init=s.asm.vb).apply(null,arguments)};s.__emscripten_thread_crashed=function(){return(s.__emscripten_thread_crashed=s.asm.wb).apply(null,arguments)};var _e,ve=s._emscripten_run_in_main_runtime_thread_js=function(){return(ve=s._emscripten_run_in_main_runtime_thread_js=s.asm.xb).apply(null,arguments)},we=s.__emscripten_proxy_execute_task_queue=function(){return(we=s.__emscripten_proxy_execute_task_queue=s.asm.yb).apply(null,arguments)},xe=s.__emscripten_thread_free_data=function(){return(xe=s.__emscripten_thread_free_data=s.asm.zb).apply(null,arguments)},Te=s.__emscripten_thread_exit=function(){return(Te=s.__emscripten_thread_exit=s.asm.Ab).apply(null,arguments)},Se=s._setThrew=function(){return(Se=s._setThrew=s.asm.Bb).apply(null,arguments)},Oe=s._emscripten_stack_set_limits=function(){return(Oe=s._emscripten_stack_set_limits=s.asm.Cb).apply(null,arguments)},Ae=s.stackSave=function(){return(Ae=s.stackSave=s.asm.Db).apply(null,arguments)},Ee=s.stackRestore=function(){return(Ee=s.stackRestore=s.asm.Eb).apply(null,arguments)},Ie=s.stackAlloc=function(){return(Ie=s.stackAlloc=s.asm.Fb).apply(null,arguments)},Pe=s.___cxa_can_catch=function(){return(Pe=s.___cxa_can_catch=s.asm.Gb).apply(null,arguments)},De=s.___cxa_is_pointer_type=function(){return(De=s.___cxa_is_pointer_type=s.asm.Hb).apply(null,arguments)},$e=s.dynCall_j=function(){return($e=s.dynCall_j=s.asm.Ib).apply(null,arguments)},ke=s.dynCall_iiiiij=function(){return(ke=s.dynCall_iiiiij=s.asm.Jb).apply(null,arguments)},Ce=s.dynCall_jii=function(){return(Ce=s.dynCall_jii=s.asm.Kb).apply(null,arguments)},Fe=s.dynCall_viiiiij=function(){return(Fe=s.dynCall_viiiiij=s.asm.Lb).apply(null,arguments)},Ne=s.dynCall_vjji=function(){return(Ne=s.dynCall_vjji=s.asm.Mb).apply(null,arguments)},Re=s.dynCall_viiijjjii=function(){return(Re=s.dynCall_viiijjjii=s.asm.Nb).apply(null,arguments)},Le=s.dynCall_iij=function(){return(Le=s.dynCall_iij=s.asm.Ob).apply(null,arguments)},je=s.dynCall_ji=function(){return(je=s.dynCall_ji=s.asm.Pb).apply(null,arguments)},Me=s.dynCall_iiiiiij=function(){return(Me=s.dynCall_iiiiiij=s.asm.Qb).apply(null,arguments)},Ue=s.dynCall_iiij=function(){return(Ue=s.dynCall_iiij=s.asm.Rb).apply(null,arguments)};function Ve(){function t(){if(!_e&&(_e=!0,s.calledRun=!0,!M)&&(x||ht(Y),u(s),s.onRuntimeInitialized&&s.onRuntimeInitialized(),!x)){if(s.postRun)for("function"==typeof s.postRun&&(s.postRun=[s.postRun]);s.postRun.length;){var t=s.postRun.shift();Z.unshift(t)}ht(Z)}}if(!(0<et))if(x)u(s),x||ht(Y),postMessage({cmd:"loaded"});else{if(s.preRun)for("function"==typeof s.preRun&&(s.preRun=[s.preRun]);s.preRun.length;)Q();ht(X),0<et||(s.setStatus?(s.setStatus("Running..."),setTimeout((function(){setTimeout((function(){s.setStatus("")}),1),t()}),1)):t())}}if(s.UTF8ToString=B,s.stringToUTF8=function(t,e,n){return z(t,r(),e,n)},s.lengthBytesUTF8=G,s.keepRuntimeAlive=J,s.wasmMemory=$,s.stackSave=Ae,s.stackRestore=Ee,s.stackAlloc=Ie,s.ExitStatus=ut,s.PThread=dt,rt=function t(){_e||Ve(),_e||(rt=t)},s.preInit)for("function"==typeof s.preInit&&(s.preInit=[s.preInit]);0<s.preInit.length;)s.preInit.pop()();return Ve(),t.ready});t.exports=r},932:(t,e,n)=>{var _scriptDir,r=(_scriptDir=(_scriptDir="undefined"!=typeof document&&document.currentScript?document.currentScript.src:void 0)||"/index.js",function(t){var e,r,i;t=t||{},e||(e=void 0!==t?t:{}),e.ready=new Promise((function(t,e){r=t,i=e}));var o,a,s,u,c,l,p=Object.assign({},e),f="./this.program",d=(t,e)=>{throw e},h="object"==typeof window,g="function"==typeof importScripts,b="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node,m="";b?(m=g?n(908).dirname(m)+"/":"//",l=()=>{c||(u=n(1384),c=n(908))},o=function(t,e){return l(),t=c.normalize(t),u.readFileSync(t,e?void 0:"utf8")},s=t=>((t=o(t,!0)).buffer||(t=new Uint8Array(t)),t),a=(t,e,n)=>{l(),t=c.normalize(t),u.readFile(t,(function(t,r){t?n(t):e(r.buffer)}))},1<process.argv.length&&(f=process.argv[1].replace(/\\/g,"/")),process.argv.slice(2),process.on("uncaughtException",(function(t){if(!(t instanceof K))throw t})),process.on("unhandledRejection",(function(t){throw t})),d=(t,e)=>{if(w||0<U)throw process.exitCode=t,e;e instanceof K||v("exiting due to exception: "+e),process.exit(t)},e.inspect=function(){return"[Emscripten Module object]"}):(h||g)&&(g?m=self.location.href:"undefined"!=typeof document&&document.currentScript&&(m=document.currentScript.src),_scriptDir&&(m=_scriptDir),m=0!==m.indexOf("blob:")?m.substr(0,m.replace(/[?#].*/,"").lastIndexOf("/")+1):"",o=t=>{var e=new XMLHttpRequest;return e.open("GET",t,!1),e.send(null),e.responseText},g&&(s=t=>{var e=new XMLHttpRequest;return e.open("GET",t,!1),e.responseType="arraybuffer",e.send(null),new Uint8Array(e.response)}),a=(t,e,n)=>{var r=new XMLHttpRequest;r.open("GET",t,!0),r.responseType="arraybuffer",r.onload=()=>{200==r.status||0==r.status&&r.response?e(r.response):n()},r.onerror=n,r.send(null)});var y,_=e.print||console.log.bind(console),v=e.printErr||console.warn.bind(console);Object.assign(e,p),p=null,e.thisProgram&&(f=e.thisProgram),e.quit&&(d=e.quit),e.wasmBinary&&(y=e.wasmBinary);var w=e.noExitRuntime||!1;"object"!=typeof WebAssembly&&W("no native wasm support detected");var x,T,S,O,A,E,I=!1,P="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0;function D(t,e,n){var r=(e>>>=0)+n;for(n=e;t[n]&&!(n>=r);)++n;if(16<n-e&&t.buffer&&P)return P.decode(t.subarray(e,n));for(r="";e<n;){var i=t[e++];if(128&i){var o=63&t[e++];if(192==(224&i))r+=String.fromCharCode((31&i)<<6|o);else{var a=63&t[e++];65536>(i=224==(240&i)?(15&i)<<12|o<<6|a:(7&i)<<18|o<<12|a<<6|63&t[e++])?r+=String.fromCharCode(i):(i-=65536,r+=String.fromCharCode(55296|i>>10,56320|1023&i))}}else r+=String.fromCharCode(i)}return r}function $(t,e){return(t>>>=0)?D(O,t,e):""}function k(t,e,n,r){if(!(0<r))return 0;var i=n>>>=0;r=n+r-1;for(var o=0;o<t.length;++o){var a=t.charCodeAt(o);if(55296<=a&&57343>=a&&(a=65536+((1023&a)<<10)|1023&t.charCodeAt(++o)),127>=a){if(n>=r)break;e[n++>>>0]=a}else{if(2047>=a){if(n+1>=r)break;e[n++>>>0]=192|a>>6}else{if(65535>=a){if(n+2>=r)break;e[n++>>>0]=224|a>>12}else{if(n+3>=r)break;e[n++>>>0]=240|a>>18,e[n++>>>0]=128|a>>12&63}e[n++>>>0]=128|a>>6&63}e[n++>>>0]=128|63&a}}return e[n>>>0]=0,n-i}function C(t){for(var e=0,n=0;n<t.length;++n){var r=t.charCodeAt(n);127>=r?e++:2047>=r?e+=2:55296<=r&&57343>=r?(e+=4,++n):e+=3}return e}function F(){var t=x.buffer;T=t,e.HEAP8=S=new Int8Array(t),e.HEAP16=new Int16Array(t),e.HEAP32=A=new Int32Array(t),e.HEAPU8=O=new Uint8Array(t),e.HEAPU16=new Uint16Array(t),e.HEAPU32=E=new Uint32Array(t),e.HEAPF32=new Float32Array(t),e.HEAPF64=new Float64Array(t)}var N,R=[],L=[],j=[],M=[],U=0;function V(){var t=e.preRun.shift();R.unshift(t)}var B,z=0,G=null,H=null;function W(t){throw e.onAbort&&e.onAbort(t),v(t="Aborted("+t+")"),I=!0,t=new WebAssembly.RuntimeError(t+". Build with -sASSERTIONS for more info."),i(t),t}function q(){return B.startsWith("data:application/octet-stream;base64,")}if(B="ort-wasm.wasm",!q()){var X=B;B=e.locateFile?e.locateFile(X,m):m+X}function Y(){var t=B;try{if(t==B&&y)return new Uint8Array(y);if(s)return s(t);throw"both async and sync fetching of the wasm failed"}catch(t){W(t)}}function K(t){this.name="ExitStatus",this.message="Program terminated with exit("+t+")",this.status=t}function Z(t){for(;0<t.length;)t.shift()(e)}var J=[],Q=0,tt=0;function et(t){this.Db=t,this.zb=t-24,this.Ub=function(t){E[this.zb+4>>2>>>0]=t},this.Eb=function(){return E[this.zb+4>>2>>>0]},this.Sb=function(t){E[this.zb+8>>2>>>0]=t},this.Wb=function(){return E[this.zb+8>>2>>>0]},this.Tb=function(){A[this.zb>>2>>>0]=0},this.Ib=function(t){S[this.zb+12>>0>>>0]=t?1:0},this.Pb=function(){return 0!=S[this.zb+12>>0>>>0]},this.Jb=function(t){S[this.zb+13>>0>>>0]=t?1:0},this.Lb=function(){return 0!=S[this.zb+13>>0>>>0]},this.Rb=function(t,e){this.Fb(0),this.Ub(t),this.Sb(e),this.Tb(),this.Ib(!1),this.Jb(!1)},this.Nb=function(){A[this.zb>>2>>>0]+=1},this.Xb=function(){var t=A[this.zb>>2>>>0];return A[this.zb>>2>>>0]=t-1,1===t},this.Fb=function(t){E[this.zb+16>>2>>>0]=t},this.Ob=function(){return E[this.zb+16>>2>>>0]},this.Qb=function(){if(Et(this.Eb()))return E[this.Db>>2>>>0];var t=this.Ob();return 0!==t?t:this.Db}}function nt(t){return _t(new et(t).zb)}var rt=[];function it(t){var e=rt[t];return e||(t>=rt.length&&(rt.length=t+1),rt[t]=e=N.get(t)),e}function ot(t){var e=C(t)+1,n=yt(e);return n&&k(t,S,n,e),n}var at={};function st(){if(!ut){var t,e={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:f||"./this.program"};for(t in at)void 0===at[t]?delete e[t]:e[t]=at[t];var n=[];for(t in e)n.push(t+"="+e[t]);ut=n}return ut}var ut,ct=[null,[],[]];function lt(t,e){var n=ct[t];0===e||10===e?((1===t?_:v)(D(n,0)),n.length=0):n.push(e)}var pt=0;function ft(t){return 0==t%4&&(0!=t%100||0==t%400)}var dt=[31,29,31,30,31,30,31,31,30,31,30,31],ht=[31,28,31,30,31,30,31,31,30,31,30,31];function gt(t,e,n,r){function i(t,e,n){for(t="number"==typeof t?t.toString():t||"";t.length<e;)t=n[0]+t;return t}function o(t,e){return i(t,e,"0")}function a(t,e){function n(t){return 0>t?-1:0<t?1:0}var r;return 0===(r=n(t.getFullYear()-e.getFullYear()))&&0===(r=n(t.getMonth()-e.getMonth()))&&(r=n(t.getDate()-e.getDate())),r}function s(t){switch(t.getDay()){case 0:return new Date(t.getFullYear()-1,11,29);case 1:return t;case 2:return new Date(t.getFullYear(),0,3);case 3:return new Date(t.getFullYear(),0,2);case 4:return new Date(t.getFullYear(),0,1);case 5:return new Date(t.getFullYear()-1,11,31);case 6:return new Date(t.getFullYear()-1,11,30)}}function u(t){var e=t.Bb;for(t=new Date(new Date(t.Cb+1900,0,1).getTime());0<e;){var n=t.getMonth(),r=(ft(t.getFullYear())?dt:ht)[n];if(!(e>r-t.getDate())){t.setDate(t.getDate()+e);break}e-=r-t.getDate()+1,t.setDate(1),11>n?t.setMonth(n+1):(t.setMonth(0),t.setFullYear(t.getFullYear()+1))}return n=new Date(t.getFullYear()+1,0,4),e=s(new Date(t.getFullYear(),0,4)),n=s(n),0>=a(e,t)?0>=a(n,t)?t.getFullYear()+1:t.getFullYear():t.getFullYear()-1}var c=A[r+40>>2>>>0];for(var l in r={$b:A[r>>2>>>0],Zb:A[r+4>>2>>>0],Gb:A[r+8>>2>>>0],Kb:A[r+12>>2>>>0],Hb:A[r+16>>2>>>0],Cb:A[r+20>>2>>>0],Ab:A[r+24>>2>>>0],Bb:A[r+28>>2>>>0],bc:A[r+32>>2>>>0],Yb:A[r+36>>2>>>0],ac:c?$(c):""},n=$(n),c={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"})n=n.replace(new RegExp(l,"g"),c[l]);var p="Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),f="January February March April May June July August September October November December".split(" ");for(l in c={"%a":function(t){return p[t.Ab].substring(0,3)},"%A":function(t){return p[t.Ab]},"%b":function(t){return f[t.Hb].substring(0,3)},"%B":function(t){return f[t.Hb]},"%C":function(t){return o((t.Cb+1900)/100|0,2)},"%d":function(t){return o(t.Kb,2)},"%e":function(t){return i(t.Kb,2," ")},"%g":function(t){return u(t).toString().substring(2)},"%G":function(t){return u(t)},"%H":function(t){return o(t.Gb,2)},"%I":function(t){return 0==(t=t.Gb)?t=12:12<t&&(t-=12),o(t,2)},"%j":function(t){for(var e=0,n=0;n<=t.Hb-1;e+=(ft(t.Cb+1900)?dt:ht)[n++]);return o(t.Kb+e,3)},"%m":function(t){return o(t.Hb+1,2)},"%M":function(t){return o(t.Zb,2)},"%n":function(){return"\n"},"%p":function(t){return 0<=t.Gb&&12>t.Gb?"AM":"PM"},"%S":function(t){return o(t.$b,2)},"%t":function(){return"\t"},"%u":function(t){return t.Ab||7},"%U":function(t){return o(Math.floor((t.Bb+7-t.Ab)/7),2)},"%V":function(t){var e=Math.floor((t.Bb+7-(t.Ab+6)%7)/7);if(2>=(t.Ab+371-t.Bb-2)%7&&e++,e)53==e&&(4==(n=(t.Ab+371-t.Bb)%7)||3==n&&ft(t.Cb)||(e=1));else{e=52;var n=(t.Ab+7-t.Bb-1)%7;(4==n||5==n&&ft(t.Cb%400-1))&&e++}return o(e,2)},"%w":function(t){return t.Ab},"%W":function(t){return o(Math.floor((t.Bb+7-(t.Ab+6)%7)/7),2)},"%y":function(t){return(t.Cb+1900).toString().substring(2)},"%Y":function(t){return t.Cb+1900},"%z":function(t){var e=0<=(t=t.Yb);return t=Math.abs(t)/60,(e?"+":"-")+String("0000"+(t/60*100+t%60)).slice(-4)},"%Z":function(t){return t.ac},"%%":function(){return"%"}},n=n.replace(/%%/g,"\0\0"),c)n.includes(l)&&(n=n.replace(new RegExp(l,"g"),c[l](r)));return l=function(t){var e=Array(C(t)+1);return k(t,e,0,e.length),e}(n=n.replace(/\0\0/g,"%")),l.length>e?0:(S.set(l,t>>>0),l.length-1)}var bt={a:function(t){return yt(t+24)+24},m:function(t){return(t=new et(t)).Pb()||(t.Ib(!0),Q--),t.Jb(!1),J.push(t),t.Nb(),t.Qb()},ia:function(t){throw v("Unexpected exception thrown, this is not properly supported - aborting"),I=!0,t},w:function(){xt(0);var t=J.pop();if(t.Xb()&&!t.Lb()){var e=t.Wb();e&&it(e)(t.Db),nt(t.Db)}tt=0},d:function(){var t=tt;if(!t)return pt=0;var e=new et(t);e.Fb(t);var n=e.Eb();if(!n)return pt=0,t;for(var r=Array.prototype.slice.call(arguments),i=0;i<r.length;i++){var o=r[i];if(0===o||o===n)break;if(At(o,n,e.zb+16))return pt=o,t}return pt=n,t},k:function(){var t=tt;if(!t)return pt=0;var e=new et(t);e.Fb(t);var n=e.Eb();if(!n)return pt=0,t;for(var r=Array.prototype.slice.call(arguments),i=0;i<r.length;i++){var o=r[i];if(0===o||o===n)break;if(At(o,n,e.zb+16))return pt=o,t}return pt=n,t},g:function(){var t=tt;if(!t)return pt=0;var e=new et(t);e.Fb(t);var n=e.Eb();if(!n)return pt=0,t;for(var r=Array.prototype.slice.call(arguments),i=0;i<r.length;i++){var o=r[i];if(0===o||o===n)break;if(At(o,n,e.zb+16))return pt=o,t}return pt=n,t},s:nt,L:function(){var t=J.pop();t||W("no exception to throw");var e=t.Db;throw t.Lb()||(J.push(t),t.Jb(!0),t.Ib(!1),Q++),tt=e,e},b:function(t,e,n){throw new et(t).Rb(e,n),tt=t,Q++,t},la:function(){return Q},i:function(t){throw tt||(tt=t),t},H:function(){return 0},Ba:function(){},pa:function(){},ra:function(){},ka:function(){return 0},za:function(){},ua:function(){},ya:function(){},R:function(){},qa:function(){},na:function(){},Aa:function(){},oa:function(){},Ha:function(){},Ja:function(){W("To use dlopen, you need enable dynamic linking, see https://github.com/emscripten-core/emscripten/wiki/Linking")},Ia:function(){W("To use dlopen, you need enable dynamic linking, see https://github.com/emscripten-core/emscripten/wiki/Linking")},S:function(){return Date.now()},Ca:function(){return!0},Da:function(t,e){t=new Date(1e3*(E[t>>>2]+4294967296*A[t+4>>>2])),A[e>>2>>>0]=t.getUTCSeconds(),A[e+4>>2>>>0]=t.getUTCMinutes(),A[e+8>>2>>>0]=t.getUTCHours(),A[e+12>>2>>>0]=t.getUTCDate(),A[e+16>>2>>>0]=t.getUTCMonth(),A[e+20>>2>>>0]=t.getUTCFullYear()-1900,A[e+24>>2>>>0]=t.getUTCDay(),A[e+28>>2>>>0]=(t.getTime()-Date.UTC(t.getUTCFullYear(),0,1,0,0,0,0))/864e5|0},Ea:function(t,e){t=new Date(1e3*(E[t>>>2]+4294967296*A[t+4>>>2])),A[e>>2>>>0]=t.getSeconds(),A[e+4>>2>>>0]=t.getMinutes(),A[e+8>>2>>>0]=t.getHours(),A[e+12>>2>>>0]=t.getDate(),A[e+16>>2>>>0]=t.getMonth(),A[e+20>>2>>>0]=t.getFullYear()-1900,A[e+24>>2>>>0]=t.getDay();var n=new Date(t.getFullYear(),0,1);A[e+28>>2>>>0]=(t.getTime()-n.getTime())/864e5|0,A[e+36>>2>>>0]=-60*t.getTimezoneOffset();var r=new Date(t.getFullYear(),6,1).getTimezoneOffset();n=n.getTimezoneOffset(),A[e+32>>2>>>0]=0|(r!=n&&t.getTimezoneOffset()==Math.min(n,r))},Fa:function(t){var e=new Date(A[t+20>>2>>>0]+1900,A[t+16>>2>>>0],A[t+12>>2>>>0],A[t+8>>2>>>0],A[t+4>>2>>>0],A[t>>2>>>0],0),n=A[t+32>>2>>>0],r=e.getTimezoneOffset(),i=new Date(e.getFullYear(),0,1),o=new Date(e.getFullYear(),6,1).getTimezoneOffset(),a=i.getTimezoneOffset(),s=Math.min(a,o);return 0>n?A[t+32>>2>>>0]=Number(o!=a&&s==r):0<n!=(s==r)&&(o=Math.max(a,o),e.setTime(e.getTime()+6e4*((0<n?s:o)-r))),A[t+24>>2>>>0]=e.getDay(),A[t+28>>2>>>0]=(e.getTime()-i.getTime())/864e5|0,A[t>>2>>>0]=e.getSeconds(),A[t+4>>2>>>0]=e.getMinutes(),A[t+8>>2>>>0]=e.getHours(),A[t+12>>2>>>0]=e.getDate(),A[t+16>>2>>>0]=e.getMonth(),e.getTime()/1e3|0},sa:function(){return-52},ta:function(){},Ga:function t(e,n,r){t.Vb||(t.Vb=!0,function(t,e,n){function r(t){return(t=t.toTimeString().match(/\(([A-Za-z ]+)\)$/))?t[1]:"GMT"}var i=(new Date).getFullYear(),o=new Date(i,0,1),a=new Date(i,6,1);i=o.getTimezoneOffset();var s=a.getTimezoneOffset();A[t>>2>>>0]=60*Math.max(i,s),A[e>>2>>>0]=Number(i!=s),t=r(o),e=r(a),t=ot(t),e=ot(e),s<i?(E[n>>2>>>0]=t,E[n+4>>2>>>0]=e):(E[n>>2>>>0]=e,E[n+4>>2>>>0]=t)}(e,n,r))},B:function(){W("")},ma:function(){return 4294901760},I:b?()=>{var t=process.hrtime();return 1e3*t[0]+t[1]/1e6}:()=>performance.now(),xa:function(t,e,n){O.copyWithin(t>>>0,e>>>0,e+n>>>0)},G:function(t){var e=O.length;if(4294901760<(t>>>=0))return!1;for(var n=1;4>=n;n*=2){var r=e*(1+.2/n);r=Math.min(r,t+100663296);var i=Math;r=Math.max(t,r),i=i.min.call(i,4294901760,r+(65536-r%65536)%65536);t:{try{x.grow(i-T.byteLength+65535>>>16),F();var o=1;break t}catch(t){}o=void 0}if(o)return!0}return!1},va:function(t,e){var n=0;return st().forEach((function(r,i){var o=e+n;for(i=E[t+4*i>>2>>>0]=o,o=0;o<r.length;++o)S[i++>>0>>>0]=r.charCodeAt(o);S[i>>0>>>0]=0,n+=r.length+1})),0},wa:function(t,e){var n=st();E[t>>2>>>0]=n.length;var r=0;return n.forEach((function(t){r+=t.length+1})),E[e>>2>>>0]=r,0},ba:function(t){w||0<U||(wt(),Z(j),vt(0),ct[1].length&&lt(1,10),ct[2].length&&lt(2,10)),w||0<U||(e.onExit&&e.onExit(t),I=!0),d(t,new K(t))},E:function(){return 52},Q:function(){return 52},ca:function(){return 70},P:function(t,e,n,r){for(var i=0,o=0;o<n;o++){var a=E[e>>2>>>0],s=E[e+4>>2>>>0];e+=8;for(var u=0;u<s;u++)lt(t,O[a+u>>>0]);i+=s}return E[r>>2>>>0]=i,0},c:function(){return pt},ja:function t(e,r){t.Mb||(t.Mb=function(){if("object"==typeof crypto&&"function"==typeof crypto.getRandomValues){var t=new Uint8Array(1);return()=>(crypto.getRandomValues(t),t[0])}if(b)try{var e=n(Object(function(){var t=new Error("Cannot find module 'crypto'");throw t.code="MODULE_NOT_FOUND",t}()));return()=>e.randomBytes(1)[0]}catch(t){}return()=>W("randomDevice")}());for(var i=0;i<r;i++)S[e+i>>0>>>0]=t.Mb();return 0},ea:function(t,e,n){var r=Tt();try{return it(t)(e,n)}catch(t){if(St(r),t!==t+0)throw t;xt(1,0)}},fa:function(t,e,n){var r=Tt();try{return it(t)(e,n)}catch(t){if(St(r),t!==t+0)throw t;xt(1,0)}},J:function(t){var e=Tt();try{return it(t)()}catch(t){if(St(e),t!==t+0)throw t;xt(1,0)}},e:function(t,e){var n=Tt();try{return it(t)(e)}catch(t){if(St(n),t!==t+0)throw t;xt(1,0)}},N:function(t,e,n){var r=Tt();try{return it(t)(e,n)}catch(t){if(St(r),t!==t+0)throw t;xt(1,0)}},O:function(t,e,n){var r=Tt();try{return it(t)(e,n)}catch(t){if(St(r),t!==t+0)throw t;xt(1,0)}},j:function(t,e,n){var r=Tt();try{return it(t)(e,n)}catch(t){if(St(r),t!==t+0)throw t;xt(1,0)}},o:function(t,e,n,r){var i=Tt();try{return it(t)(e,n,r)}catch(t){if(St(i),t!==t+0)throw t;xt(1,0)}},p:function(t,e,n,r,i){var o=Tt();try{return it(t)(e,n,r,i)}catch(t){if(St(o),t!==t+0)throw t;xt(1,0)}},M:function(t,e,n,r,i,o){var a=Tt();try{return it(t)(e,n,r,i,o)}catch(t){if(St(a),t!==t+0)throw t;xt(1,0)}},r:function(t,e,n,r,i,o){var a=Tt();try{return it(t)(e,n,r,i,o)}catch(t){if(St(a),t!==t+0)throw t;xt(1,0)}},v:function(t,e,n,r,i,o,a){var s=Tt();try{return it(t)(e,n,r,i,o,a)}catch(t){if(St(s),t!==t+0)throw t;xt(1,0)}},K:function(t,e,n,r,i,o,a,s){var u=Tt();try{return it(t)(e,n,r,i,o,a,s)}catch(t){if(St(u),t!==t+0)throw t;xt(1,0)}},D:function(t,e,n,r,i,o,a,s,u,c,l,p){var f=Tt();try{return it(t)(e,n,r,i,o,a,s,u,c,l,p)}catch(t){if(St(f),t!==t+0)throw t;xt(1,0)}},X:function(t,e,n,r,i,o,a,s){var u=Tt();try{return Rt(t,e,n,r,i,o,a,s)}catch(t){if(St(u),t!==t+0)throw t;xt(1,0)}},V:function(t,e,n,r,i,o,a){var s=Tt();try{return Pt(t,e,n,r,i,o,a)}catch(t){if(St(s),t!==t+0)throw t;xt(1,0)}},U:function(t,e,n,r,i){var o=Tt();try{return Lt(t,e,n,r,i)}catch(t){if(St(o),t!==t+0)throw t;xt(1,0)}},Z:function(t,e,n,r){var i=Tt();try{return Ft(t,e,n,r)}catch(t){if(St(i),t!==t+0)throw t;xt(1,0)}},W:function(t){var e=Tt();try{return It(t)}catch(t){if(St(e),t!==t+0)throw t;xt(1,0)}},Y:function(t,e){var n=Tt();try{return Nt(t,e)}catch(t){if(St(n),t!==t+0)throw t;xt(1,0)}},T:function(t,e,n){var r=Tt();try{return Dt(t,e,n)}catch(t){if(St(r),t!==t+0)throw t;xt(1,0)}},f:function(t){var e=Tt();try{it(t)()}catch(t){if(St(e),t!==t+0)throw t;xt(1,0)}},q:function(t,e){var n=Tt();try{it(t)(e)}catch(t){if(St(n),t!==t+0)throw t;xt(1,0)}},h:function(t,e,n){var r=Tt();try{it(t)(e,n)}catch(t){if(St(r),t!==t+0)throw t;xt(1,0)}},da:function(t,e,n,r){var i=Tt();try{it(t)(e,n,r)}catch(t){if(St(i),t!==t+0)throw t;xt(1,0)}},l:function(t,e,n,r){var i=Tt();try{it(t)(e,n,r)}catch(t){if(St(i),t!==t+0)throw t;xt(1,0)}},t:function(t,e,n,r,i){var o=Tt();try{it(t)(e,n,r,i)}catch(t){if(St(o),t!==t+0)throw t;xt(1,0)}},u:function(t,e,n,r,i,o){var a=Tt();try{it(t)(e,n,r,i,o)}catch(t){if(St(a),t!==t+0)throw t;xt(1,0)}},x:function(t,e,n,r,i,o,a){var s=Tt();try{it(t)(e,n,r,i,o,a)}catch(t){if(St(s),t!==t+0)throw t;xt(1,0)}},z:function(t,e,n,r,i,o,a,s){var u=Tt();try{it(t)(e,n,r,i,o,a,s)}catch(t){if(St(u),t!==t+0)throw t;xt(1,0)}},ga:function(t,e,n,r,i,o,a,s,u){var c=Tt();try{it(t)(e,n,r,i,o,a,s,u)}catch(t){if(St(c),t!==t+0)throw t;xt(1,0)}},A:function(t,e,n,r,i,o,a,s,u,c,l){var p=Tt();try{it(t)(e,n,r,i,o,a,s,u,c,l)}catch(t){if(St(p),t!==t+0)throw t;xt(1,0)}},C:function(t,e,n,r,i,o,a,s,u,c,l,p,f,d,h,g){var b=Tt();try{it(t)(e,n,r,i,o,a,s,u,c,l,p,f,d,h,g)}catch(t){if(St(b),t!==t+0)throw t;xt(1,0)}},aa:function(t,e,n,r,i,o,a,s){var u=Tt();try{$t(t,e,n,r,i,o,a,s)}catch(t){if(St(u),t!==t+0)throw t;xt(1,0)}},_:function(t,e,n,r,i,o,a,s,u,c,l,p){var f=Tt();try{Ct(t,e,n,r,i,o,a,s,u,c,l,p)}catch(t){if(St(f),t!==t+0)throw t;xt(1,0)}},$:function(t,e,n,r,i,o){var a=Tt();try{kt(t,e,n,r,i,o)}catch(t){if(St(a),t!==t+0)throw t;xt(1,0)}},n:function(t){return t},F:function(t){pt=t},ha:gt,y:function(t,e,n,r){return gt(t,e,n,r)}};!function(){function t(t){e.asm=t.exports,x=e.asm.Ka,F(),N=e.asm.ib,L.unshift(e.asm.La),z--,e.monitorRunDependencies&&e.monitorRunDependencies(z),0==z&&(null!==G&&(clearInterval(G),G=null),H&&(t=H,H=null,t()))}function n(e){t(e.instance)}function r(t){return function(){if(!y&&(h||g)){if("function"==typeof fetch&&!B.startsWith("file://"))return fetch(B,{credentials:"same-origin"}).then((function(t){if(!t.ok)throw"failed to load wasm binary file at '"+B+"'";return t.arrayBuffer()})).catch((function(){return Y()}));if(a)return new Promise((function(t,e){a(B,(function(e){t(new Uint8Array(e))}),e)}))}return Promise.resolve().then((function(){return Y()}))}().then((function(t){return WebAssembly.instantiate(t,o)})).then((function(t){return t})).then(t,(function(t){v("failed to asynchronously prepare wasm: "+t),W(t)}))}var o={a:bt};if(z++,e.monitorRunDependencies&&e.monitorRunDependencies(z),e.instantiateWasm)try{return e.instantiateWasm(o,t)}catch(t){return v("Module.instantiateWasm callback failed with error: "+t),!1}(y||"function"!=typeof WebAssembly.instantiateStreaming||q()||B.startsWith("file://")||b||"function"!=typeof fetch?r(n):fetch(B,{credentials:"same-origin"}).then((function(t){return WebAssembly.instantiateStreaming(t,o).then(n,(function(t){return v("wasm streaming compile failed: "+t),v("falling back to ArrayBuffer instantiation"),r(n)}))}))).catch(i)}(),e.___wasm_call_ctors=function(){return(e.___wasm_call_ctors=e.asm.La).apply(null,arguments)},e._OrtInit=function(){return(e._OrtInit=e.asm.Ma).apply(null,arguments)},e._OrtCreateSessionOptions=function(){return(e._OrtCreateSessionOptions=e.asm.Na).apply(null,arguments)},e._OrtAppendExecutionProvider=function(){return(e._OrtAppendExecutionProvider=e.asm.Oa).apply(null,arguments)},e._OrtAddSessionConfigEntry=function(){return(e._OrtAddSessionConfigEntry=e.asm.Pa).apply(null,arguments)},e._OrtReleaseSessionOptions=function(){return(e._OrtReleaseSessionOptions=e.asm.Qa).apply(null,arguments)},e._OrtCreateSession=function(){return(e._OrtCreateSession=e.asm.Ra).apply(null,arguments)},e._OrtReleaseSession=function(){return(e._OrtReleaseSession=e.asm.Sa).apply(null,arguments)},e._OrtGetInputCount=function(){return(e._OrtGetInputCount=e.asm.Ta).apply(null,arguments)},e._OrtGetOutputCount=function(){return(e._OrtGetOutputCount=e.asm.Ua).apply(null,arguments)},e._OrtGetInputName=function(){return(e._OrtGetInputName=e.asm.Va).apply(null,arguments)},e._OrtGetOutputName=function(){return(e._OrtGetOutputName=e.asm.Wa).apply(null,arguments)},e._OrtFree=function(){return(e._OrtFree=e.asm.Xa).apply(null,arguments)},e._OrtCreateTensor=function(){return(e._OrtCreateTensor=e.asm.Ya).apply(null,arguments)},e._OrtGetTensorData=function(){return(e._OrtGetTensorData=e.asm.Za).apply(null,arguments)},e._OrtReleaseTensor=function(){return(e._OrtReleaseTensor=e.asm._a).apply(null,arguments)},e._OrtCreateRunOptions=function(){return(e._OrtCreateRunOptions=e.asm.$a).apply(null,arguments)},e._OrtAddRunConfigEntry=function(){return(e._OrtAddRunConfigEntry=e.asm.ab).apply(null,arguments)},e._OrtReleaseRunOptions=function(){return(e._OrtReleaseRunOptions=e.asm.bb).apply(null,arguments)},e._OrtRun=function(){return(e._OrtRun=e.asm.cb).apply(null,arguments)},e._OrtEndProfiling=function(){return(e._OrtEndProfiling=e.asm.db).apply(null,arguments)};var mt,yt=e._malloc=function(){return(yt=e._malloc=e.asm.eb).apply(null,arguments)},_t=e._free=function(){return(_t=e._free=e.asm.fb).apply(null,arguments)},vt=e._fflush=function(){return(vt=e._fflush=e.asm.gb).apply(null,arguments)},wt=e.___funcs_on_exit=function(){return(wt=e.___funcs_on_exit=e.asm.hb).apply(null,arguments)},xt=e._setThrew=function(){return(xt=e._setThrew=e.asm.jb).apply(null,arguments)},Tt=e.stackSave=function(){return(Tt=e.stackSave=e.asm.kb).apply(null,arguments)},St=e.stackRestore=function(){return(St=e.stackRestore=e.asm.lb).apply(null,arguments)},Ot=e.stackAlloc=function(){return(Ot=e.stackAlloc=e.asm.mb).apply(null,arguments)},At=e.___cxa_can_catch=function(){return(At=e.___cxa_can_catch=e.asm.nb).apply(null,arguments)},Et=e.___cxa_is_pointer_type=function(){return(Et=e.___cxa_is_pointer_type=e.asm.ob).apply(null,arguments)},It=e.dynCall_j=function(){return(It=e.dynCall_j=e.asm.pb).apply(null,arguments)},Pt=e.dynCall_iiiiij=function(){return(Pt=e.dynCall_iiiiij=e.asm.qb).apply(null,arguments)},Dt=e.dynCall_jii=function(){return(Dt=e.dynCall_jii=e.asm.rb).apply(null,arguments)},$t=e.dynCall_viiiiij=function(){return($t=e.dynCall_viiiiij=e.asm.sb).apply(null,arguments)},kt=e.dynCall_vjji=function(){return(kt=e.dynCall_vjji=e.asm.tb).apply(null,arguments)},Ct=e.dynCall_viiijjjii=function(){return(Ct=e.dynCall_viiijjjii=e.asm.ub).apply(null,arguments)},Ft=e.dynCall_iij=function(){return(Ft=e.dynCall_iij=e.asm.vb).apply(null,arguments)},Nt=e.dynCall_ji=function(){return(Nt=e.dynCall_ji=e.asm.wb).apply(null,arguments)},Rt=e.dynCall_iiiiiij=function(){return(Rt=e.dynCall_iiiiiij=e.asm.xb).apply(null,arguments)},Lt=e.dynCall_iiij=function(){return(Lt=e.dynCall_iiij=e.asm.yb).apply(null,arguments)};function jt(){function t(){if(!mt&&(mt=!0,e.calledRun=!0,!I)){if(Z(L),r(e),e.onRuntimeInitialized&&e.onRuntimeInitialized(),e.postRun)for("function"==typeof e.postRun&&(e.postRun=[e.postRun]);e.postRun.length;){var t=e.postRun.shift();M.unshift(t)}Z(M)}}if(!(0<z)){if(e.preRun)for("function"==typeof e.preRun&&(e.preRun=[e.preRun]);e.preRun.length;)V();Z(R),0<z||(e.setStatus?(e.setStatus("Running..."),setTimeout((function(){setTimeout((function(){e.setStatus("")}),1),t()}),1)):t())}}if(e.UTF8ToString=$,e.stringToUTF8=function(t,e,n){return k(t,O,e,n)},e.lengthBytesUTF8=C,e.stackSave=Tt,e.stackRestore=St,e.stackAlloc=Ot,H=function t(){mt||jt(),mt||(H=t)},e.preInit)for("function"==typeof e.preInit&&(e.preInit=[e.preInit]);0<e.preInit.length;)e.preInit.pop()();return jt(),t.ready});t.exports=r},4537:t=>{"use strict";t.exports=function(t,e){for(var n=new Array(arguments.length-1),r=0,i=2,o=!0;i<arguments.length;)n[r++]=arguments[i++];return new Promise((function(i,a){n[r]=function(t){if(o)if(o=!1,t)a(t);else{for(var e=new Array(arguments.length-1),n=0;n<e.length;)e[n++]=arguments[n];i.apply(null,e)}};try{t.apply(e||null,n)}catch(t){o&&(o=!1,a(t))}}))}},7419:(t,e)=>{"use strict";var n=e;n.length=function(t){var e=t.length;if(!e)return 0;for(var n=0;--e%4>1&&"="===t.charAt(e);)++n;return Math.ceil(3*t.length)/4-n};for(var r=new Array(64),i=new Array(123),o=0;o<64;)i[r[o]=o<26?o+65:o<52?o+71:o<62?o-4:o-59|43]=o++;n.encode=function(t,e,n){for(var i,o=null,a=[],s=0,u=0;e<n;){var c=t[e++];switch(u){case 0:a[s++]=r[c>>2],i=(3&c)<<4,u=1;break;case 1:a[s++]=r[i|c>>4],i=(15&c)<<2,u=2;break;case 2:a[s++]=r[i|c>>6],a[s++]=r[63&c],u=0}s>8191&&((o||(o=[])).push(String.fromCharCode.apply(String,a)),s=0)}return u&&(a[s++]=r[i],a[s++]=61,1===u&&(a[s++]=61)),o?(s&&o.push(String.fromCharCode.apply(String,a.slice(0,s))),o.join("")):String.fromCharCode.apply(String,a.slice(0,s))};var a="invalid encoding";n.decode=function(t,e,n){for(var r,o=n,s=0,u=0;u<t.length;){var c=t.charCodeAt(u++);if(61===c&&s>1)break;if(void 0===(c=i[c]))throw Error(a);switch(s){case 0:r=c,s=1;break;case 1:e[n++]=r<<2|(48&c)>>4,r=c,s=2;break;case 2:e[n++]=(15&r)<<4|(60&c)>>2,r=c,s=3;break;case 3:e[n++]=(3&r)<<6|c,s=0}}if(1===s)throw Error(a);return n-o},n.test=function(t){return/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(t)}},9211:t=>{"use strict";function e(){this._listeners={}}t.exports=e,e.prototype.on=function(t,e,n){return(this._listeners[t]||(this._listeners[t]=[])).push({fn:e,ctx:n||this}),this},e.prototype.off=function(t,e){if(void 0===t)this._listeners={};else if(void 0===e)this._listeners[t]=[];else for(var n=this._listeners[t],r=0;r<n.length;)n[r].fn===e?n.splice(r,1):++r;return this},e.prototype.emit=function(t){var e=this._listeners[t];if(e){for(var n=[],r=1;r<arguments.length;)n.push(arguments[r++]);for(r=0;r<e.length;)e[r].fn.apply(e[r++].ctx,n)}return this}},945:t=>{"use strict";function e(t){return"undefined"!=typeof Float32Array?function(){var e=new Float32Array([-0]),n=new Uint8Array(e.buffer),r=128===n[3];function i(t,r,i){e[0]=t,r[i]=n[0],r[i+1]=n[1],r[i+2]=n[2],r[i+3]=n[3]}function o(t,r,i){e[0]=t,r[i]=n[3],r[i+1]=n[2],r[i+2]=n[1],r[i+3]=n[0]}function a(t,r){return n[0]=t[r],n[1]=t[r+1],n[2]=t[r+2],n[3]=t[r+3],e[0]}function s(t,r){return n[3]=t[r],n[2]=t[r+1],n[1]=t[r+2],n[0]=t[r+3],e[0]}t.writeFloatLE=r?i:o,t.writeFloatBE=r?o:i,t.readFloatLE=r?a:s,t.readFloatBE=r?s:a}():function(){function e(t,e,n,r){var i=e<0?1:0;if(i&&(e=-e),0===e)t(1/e>0?0:2147483648,n,r);else if(isNaN(e))t(2143289344,n,r);else if(e>34028234663852886e22)t((i<<31|2139095040)>>>0,n,r);else if(e<11754943508222875e-54)t((i<<31|Math.round(e/1401298464324817e-60))>>>0,n,r);else{var o=Math.floor(Math.log(e)/Math.LN2);t((i<<31|o+127<<23|8388607&Math.round(e*Math.pow(2,-o)*8388608))>>>0,n,r)}}function a(t,e,n){var r=t(e,n),i=2*(r>>31)+1,o=r>>>23&255,a=8388607&r;return 255===o?a?NaN:i*(1/0):0===o?1401298464324817e-60*i*a:i*Math.pow(2,o-150)*(a+8388608)}t.writeFloatLE=e.bind(null,n),t.writeFloatBE=e.bind(null,r),t.readFloatLE=a.bind(null,i),t.readFloatBE=a.bind(null,o)}(),"undefined"!=typeof Float64Array?function(){var e=new Float64Array([-0]),n=new Uint8Array(e.buffer),r=128===n[7];function i(t,r,i){e[0]=t,r[i]=n[0],r[i+1]=n[1],r[i+2]=n[2],r[i+3]=n[3],r[i+4]=n[4],r[i+5]=n[5],r[i+6]=n[6],r[i+7]=n[7]}function o(t,r,i){e[0]=t,r[i]=n[7],r[i+1]=n[6],r[i+2]=n[5],r[i+3]=n[4],r[i+4]=n[3],r[i+5]=n[2],r[i+6]=n[1],r[i+7]=n[0]}function a(t,r){return n[0]=t[r],n[1]=t[r+1],n[2]=t[r+2],n[3]=t[r+3],n[4]=t[r+4],n[5]=t[r+5],n[6]=t[r+6],n[7]=t[r+7],e[0]}function s(t,r){return n[7]=t[r],n[6]=t[r+1],n[5]=t[r+2],n[4]=t[r+3],n[3]=t[r+4],n[2]=t[r+5],n[1]=t[r+6],n[0]=t[r+7],e[0]}t.writeDoubleLE=r?i:o,t.writeDoubleBE=r?o:i,t.readDoubleLE=r?a:s,t.readDoubleBE=r?s:a}():function(){function e(t,e,n,r,i,o){var a=r<0?1:0;if(a&&(r=-r),0===r)t(0,i,o+e),t(1/r>0?0:2147483648,i,o+n);else if(isNaN(r))t(0,i,o+e),t(2146959360,i,o+n);else if(r>17976931348623157e292)t(0,i,o+e),t((a<<31|2146435072)>>>0,i,o+n);else{var s;if(r<22250738585072014e-324)t((s=r/5e-324)>>>0,i,o+e),t((a<<31|s/4294967296)>>>0,i,o+n);else{var u=Math.floor(Math.log(r)/Math.LN2);1024===u&&(u=1023),t(4503599627370496*(s=r*Math.pow(2,-u))>>>0,i,o+e),t((a<<31|u+1023<<20|1048576*s&1048575)>>>0,i,o+n)}}}function a(t,e,n,r,i){var o=t(r,i+e),a=t(r,i+n),s=2*(a>>31)+1,u=a>>>20&2047,c=4294967296*(1048575&a)+o;return 2047===u?c?NaN:s*(1/0):0===u?5e-324*s*c:s*Math.pow(2,u-1075)*(c+4503599627370496)}t.writeDoubleLE=e.bind(null,n,0,4),t.writeDoubleBE=e.bind(null,r,4,0),t.readDoubleLE=a.bind(null,i,0,4),t.readDoubleBE=a.bind(null,o,4,0)}(),t}function n(t,e,n){e[n]=255&t,e[n+1]=t>>>8&255,e[n+2]=t>>>16&255,e[n+3]=t>>>24}function r(t,e,n){e[n]=t>>>24,e[n+1]=t>>>16&255,e[n+2]=t>>>8&255,e[n+3]=255&t}function i(t,e){return(t[e]|t[e+1]<<8|t[e+2]<<16|t[e+3]<<24)>>>0}function o(t,e){return(t[e]<<24|t[e+1]<<16|t[e+2]<<8|t[e+3])>>>0}t.exports=e(e)},7199:module=>{"use strict";function inquire(moduleName){return null}module.exports=inquire},6662:t=>{"use strict";t.exports=function(t,e,n){var r=n||8192,i=r>>>1,o=null,a=r;return function(n){if(n<1||n>i)return t(n);a+n>r&&(o=t(r),a=0);var s=e.call(o,a,a+=n);return 7&a&&(a=1+(7|a)),s}}},4997:(t,e)=>{"use strict";var n=e;n.length=function(t){for(var e=0,n=0,r=0;r<t.length;++r)(n=t.charCodeAt(r))<128?e+=1:n<2048?e+=2:55296==(64512&n)&&56320==(64512&t.charCodeAt(r+1))?(++r,e+=4):e+=3;return e},n.read=function(t,e,n){if(n-e<1)return"";for(var r,i=null,o=[],a=0;e<n;)(r=t[e++])<128?o[a++]=r:r>191&&r<224?o[a++]=(31&r)<<6|63&t[e++]:r>239&&r<365?(r=((7&r)<<18|(63&t[e++])<<12|(63&t[e++])<<6|63&t[e++])-65536,o[a++]=55296+(r>>10),o[a++]=56320+(1023&r)):o[a++]=(15&r)<<12|(63&t[e++])<<6|63&t[e++],a>8191&&((i||(i=[])).push(String.fromCharCode.apply(String,o)),a=0);return i?(a&&i.push(String.fromCharCode.apply(String,o.slice(0,a))),i.join("")):String.fromCharCode.apply(String,o.slice(0,a))},n.write=function(t,e,n){for(var r,i,o=n,a=0;a<t.length;++a)(r=t.charCodeAt(a))<128?e[n++]=r:r<2048?(e[n++]=r>>6|192,e[n++]=63&r|128):55296==(64512&r)&&56320==(64512&(i=t.charCodeAt(a+1)))?(r=65536+((1023&r)<<10)+(1023&i),++a,e[n++]=r>>18|240,e[n++]=r>>12&63|128,e[n++]=r>>6&63|128,e[n++]=63&r|128):(e[n++]=r>>12|224,e[n++]=r>>6&63|128,e[n++]=63&r|128);return n-o}},3442:(t,e)=>{"use strict";e.__esModule=!0;var n=function(){function t(e){if(!e)throw new TypeError("Invalid argument; `value` has no value.");this.value=t.EMPTY,e&&t.isGuid(e)&&(this.value=e)}return t.isGuid=function(e){var n=e.toString();return e&&(e instanceof t||t.validator.test(n))},t.create=function(){return new t([t.gen(2),t.gen(1),t.gen(1),t.gen(1),t.gen(3)].join("-"))},t.createEmpty=function(){return new t("emptyguid")},t.parse=function(e){return new t(e)},t.raw=function(){return[t.gen(2),t.gen(1),t.gen(1),t.gen(1),t.gen(3)].join("-")},t.gen=function(t){for(var e="",n=0;n<t;n++)e+=(65536*(1+Math.random())|0).toString(16).substring(1);return e},t.prototype.equals=function(e){return t.isGuid(e)&&this.value===e.toString()},t.prototype.isEmpty=function(){return this.value===t.EMPTY},t.prototype.toString=function(){return this.value},t.prototype.toJSON=function(){return{value:this.value}},t.validator=new RegExp("^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$","i"),t.EMPTY="00000000-0000-0000-0000-000000000000",t}();e.Guid=n},3720:t=>{t.exports=n;var e=null;try{e=new WebAssembly.Instance(new WebAssembly.Module(new Uint8Array([0,97,115,109,1,0,0,0,1,13,2,96,0,1,127,96,4,127,127,127,127,1,127,3,7,6,0,1,1,1,1,1,6,6,1,127,1,65,0,11,7,50,6,3,109,117,108,0,1,5,100,105,118,95,115,0,2,5,100,105,118,95,117,0,3,5,114,101,109,95,115,0,4,5,114,101,109,95,117,0,5,8,103,101,116,95,104,105,103,104,0,0,10,191,1,6,4,0,35,0,11,36,1,1,126,32,0,173,32,1,173,66,32,134,132,32,2,173,32,3,173,66,32,134,132,126,34,4,66,32,135,167,36,0,32,4,167,11,36,1,1,126,32,0,173,32,1,173,66,32,134,132,32,2,173,32,3,173,66,32,134,132,127,34,4,66,32,135,167,36,0,32,4,167,11,36,1,1,126,32,0,173,32,1,173,66,32,134,132,32,2,173,32,3,173,66,32,134,132,128,34,4,66,32,135,167,36,0,32,4,167,11,36,1,1,126,32,0,173,32,1,173,66,32,134,132,32,2,173,32,3,173,66,32,134,132,129,34,4,66,32,135,167,36,0,32,4,167,11,36,1,1,126,32,0,173,32,1,173,66,32,134,132,32,2,173,32,3,173,66,32,134,132,130,34,4,66,32,135,167,36,0,32,4,167,11])),{}).exports}catch(t){}function n(t,e,n){this.low=0|t,this.high=0|e,this.unsigned=!!n}function r(t){return!0===(t&&t.__isLong__)}n.prototype.__isLong__,Object.defineProperty(n.prototype,"__isLong__",{value:!0}),n.isLong=r;var i={},o={};function a(t,e){var n,r,a;return e?(a=0<=(t>>>=0)&&t<256)&&(r=o[t])?r:(n=u(t,(0|t)<0?-1:0,!0),a&&(o[t]=n),n):(a=-128<=(t|=0)&&t<128)&&(r=i[t])?r:(n=u(t,t<0?-1:0,!1),a&&(i[t]=n),n)}function s(t,e){if(isNaN(t))return e?m:b;if(e){if(t<0)return m;if(t>=d)return x}else{if(t<=-h)return T;if(t+1>=h)return w}return t<0?s(-t,e).neg():u(t%f|0,t/f|0,e)}function u(t,e,r){return new n(t,e,r)}n.fromInt=a,n.fromNumber=s,n.fromBits=u;var c=Math.pow;function l(t,e,n){if(0===t.length)throw Error("empty string");if("NaN"===t||"Infinity"===t||"+Infinity"===t||"-Infinity"===t)return b;if("number"==typeof e?(n=e,e=!1):e=!!e,(n=n||10)<2||36<n)throw RangeError("radix");var r;if((r=t.indexOf("-"))>0)throw Error("interior hyphen");if(0===r)return l(t.substring(1),e,n).neg();for(var i=s(c(n,8)),o=b,a=0;a<t.length;a+=8){var u=Math.min(8,t.length-a),p=parseInt(t.substring(a,a+u),n);if(u<8){var f=s(c(n,u));o=o.mul(f).add(s(p))}else o=(o=o.mul(i)).add(s(p))}return o.unsigned=e,o}function p(t,e){return"number"==typeof t?s(t,e):"string"==typeof t?l(t,e):u(t.low,t.high,"boolean"==typeof e?e:t.unsigned)}n.fromString=l,n.fromValue=p;var f=4294967296,d=f*f,h=d/2,g=a(1<<24),b=a(0);n.ZERO=b;var m=a(0,!0);n.UZERO=m;var y=a(1);n.ONE=y;var _=a(1,!0);n.UONE=_;var v=a(-1);n.NEG_ONE=v;var w=u(-1,2147483647,!1);n.MAX_VALUE=w;var x=u(-1,-1,!0);n.MAX_UNSIGNED_VALUE=x;var T=u(0,-2147483648,!1);n.MIN_VALUE=T;var S=n.prototype;S.toInt=function(){return this.unsigned?this.low>>>0:this.low},S.toNumber=function(){return this.unsigned?(this.high>>>0)*f+(this.low>>>0):this.high*f+(this.low>>>0)},S.toString=function(t){if((t=t||10)<2||36<t)throw RangeError("radix");if(this.isZero())return"0";if(this.isNegative()){if(this.eq(T)){var e=s(t),n=this.div(e),r=n.mul(e).sub(this);return n.toString(t)+r.toInt().toString(t)}return"-"+this.neg().toString(t)}for(var i=s(c(t,6),this.unsigned),o=this,a="";;){var u=o.div(i),l=(o.sub(u.mul(i)).toInt()>>>0).toString(t);if((o=u).isZero())return l+a;for(;l.length<6;)l="0"+l;a=""+l+a}},S.getHighBits=function(){return this.high},S.getHighBitsUnsigned=function(){return this.high>>>0},S.getLowBits=function(){return this.low},S.getLowBitsUnsigned=function(){return this.low>>>0},S.getNumBitsAbs=function(){if(this.isNegative())return this.eq(T)?64:this.neg().getNumBitsAbs();for(var t=0!=this.high?this.high:this.low,e=31;e>0&&0==(t&1<<e);e--);return 0!=this.high?e+33:e+1},S.isZero=function(){return 0===this.high&&0===this.low},S.eqz=S.isZero,S.isNegative=function(){return!this.unsigned&&this.high<0},S.isPositive=function(){return this.unsigned||this.high>=0},S.isOdd=function(){return 1==(1&this.low)},S.isEven=function(){return 0==(1&this.low)},S.equals=function(t){return r(t)||(t=p(t)),(this.unsigned===t.unsigned||this.high>>>31!=1||t.high>>>31!=1)&&this.high===t.high&&this.low===t.low},S.eq=S.equals,S.notEquals=function(t){return!this.eq(t)},S.neq=S.notEquals,S.ne=S.notEquals,S.lessThan=function(t){return this.comp(t)<0},S.lt=S.lessThan,S.lessThanOrEqual=function(t){return this.comp(t)<=0},S.lte=S.lessThanOrEqual,S.le=S.lessThanOrEqual,S.greaterThan=function(t){return this.comp(t)>0},S.gt=S.greaterThan,S.greaterThanOrEqual=function(t){return this.comp(t)>=0},S.gte=S.greaterThanOrEqual,S.ge=S.greaterThanOrEqual,S.compare=function(t){if(r(t)||(t=p(t)),this.eq(t))return 0;var e=this.isNegative(),n=t.isNegative();return e&&!n?-1:!e&&n?1:this.unsigned?t.high>>>0>this.high>>>0||t.high===this.high&&t.low>>>0>this.low>>>0?-1:1:this.sub(t).isNegative()?-1:1},S.comp=S.compare,S.negate=function(){return!this.unsigned&&this.eq(T)?T:this.not().add(y)},S.neg=S.negate,S.add=function(t){r(t)||(t=p(t));var e=this.high>>>16,n=65535&this.high,i=this.low>>>16,o=65535&this.low,a=t.high>>>16,s=65535&t.high,c=t.low>>>16,l=0,f=0,d=0,h=0;return d+=(h+=o+(65535&t.low))>>>16,f+=(d+=i+c)>>>16,l+=(f+=n+s)>>>16,l+=e+a,u((d&=65535)<<16|(h&=65535),(l&=65535)<<16|(f&=65535),this.unsigned)},S.subtract=function(t){return r(t)||(t=p(t)),this.add(t.neg())},S.sub=S.subtract,S.multiply=function(t){if(this.isZero())return b;if(r(t)||(t=p(t)),e)return u(e.mul(this.low,this.high,t.low,t.high),e.get_high(),this.unsigned);if(t.isZero())return b;if(this.eq(T))return t.isOdd()?T:b;if(t.eq(T))return this.isOdd()?T:b;if(this.isNegative())return t.isNegative()?this.neg().mul(t.neg()):this.neg().mul(t).neg();if(t.isNegative())return this.mul(t.neg()).neg();if(this.lt(g)&&t.lt(g))return s(this.toNumber()*t.toNumber(),this.unsigned);var n=this.high>>>16,i=65535&this.high,o=this.low>>>16,a=65535&this.low,c=t.high>>>16,l=65535&t.high,f=t.low>>>16,d=65535&t.low,h=0,m=0,y=0,_=0;return y+=(_+=a*d)>>>16,m+=(y+=o*d)>>>16,y&=65535,m+=(y+=a*f)>>>16,h+=(m+=i*d)>>>16,m&=65535,h+=(m+=o*f)>>>16,m&=65535,h+=(m+=a*l)>>>16,h+=n*d+i*f+o*l+a*c,u((y&=65535)<<16|(_&=65535),(h&=65535)<<16|(m&=65535),this.unsigned)},S.mul=S.multiply,S.divide=function(t){if(r(t)||(t=p(t)),t.isZero())throw Error("division by zero");var n,i,o;if(e)return this.unsigned||-2147483648!==this.high||-1!==t.low||-1!==t.high?u((this.unsigned?e.div_u:e.div_s)(this.low,this.high,t.low,t.high),e.get_high(),this.unsigned):this;if(this.isZero())return this.unsigned?m:b;if(this.unsigned){if(t.unsigned||(t=t.toUnsigned()),t.gt(this))return m;if(t.gt(this.shru(1)))return _;o=m}else{if(this.eq(T))return t.eq(y)||t.eq(v)?T:t.eq(T)?y:(n=this.shr(1).div(t).shl(1)).eq(b)?t.isNegative()?y:v:(i=this.sub(t.mul(n)),o=n.add(i.div(t)));if(t.eq(T))return this.unsigned?m:b;if(this.isNegative())return t.isNegative()?this.neg().div(t.neg()):this.neg().div(t).neg();if(t.isNegative())return this.div(t.neg()).neg();o=b}for(i=this;i.gte(t);){n=Math.max(1,Math.floor(i.toNumber()/t.toNumber()));for(var a=Math.ceil(Math.log(n)/Math.LN2),l=a<=48?1:c(2,a-48),f=s(n),d=f.mul(t);d.isNegative()||d.gt(i);)d=(f=s(n-=l,this.unsigned)).mul(t);f.isZero()&&(f=y),o=o.add(f),i=i.sub(d)}return o},S.div=S.divide,S.modulo=function(t){return r(t)||(t=p(t)),e?u((this.unsigned?e.rem_u:e.rem_s)(this.low,this.high,t.low,t.high),e.get_high(),this.unsigned):this.sub(this.div(t).mul(t))},S.mod=S.modulo,S.rem=S.modulo,S.not=function(){return u(~this.low,~this.high,this.unsigned)},S.and=function(t){return r(t)||(t=p(t)),u(this.low&t.low,this.high&t.high,this.unsigned)},S.or=function(t){return r(t)||(t=p(t)),u(this.low|t.low,this.high|t.high,this.unsigned)},S.xor=function(t){return r(t)||(t=p(t)),u(this.low^t.low,this.high^t.high,this.unsigned)},S.shiftLeft=function(t){return r(t)&&(t=t.toInt()),0==(t&=63)?this:t<32?u(this.low<<t,this.high<<t|this.low>>>32-t,this.unsigned):u(0,this.low<<t-32,this.unsigned)},S.shl=S.shiftLeft,S.shiftRight=function(t){return r(t)&&(t=t.toInt()),0==(t&=63)?this:t<32?u(this.low>>>t|this.high<<32-t,this.high>>t,this.unsigned):u(this.high>>t-32,this.high>=0?0:-1,this.unsigned)},S.shr=S.shiftRight,S.shiftRightUnsigned=function(t){if(r(t)&&(t=t.toInt()),0==(t&=63))return this;var e=this.high;return t<32?u(this.low>>>t|e<<32-t,e>>>t,this.unsigned):u(32===t?e:e>>>t-32,0,this.unsigned)},S.shru=S.shiftRightUnsigned,S.shr_u=S.shiftRightUnsigned,S.toSigned=function(){return this.unsigned?u(this.low,this.high,!1):this},S.toUnsigned=function(){return this.unsigned?this:u(this.low,this.high,!0)},S.toBytes=function(t){return t?this.toBytesLE():this.toBytesBE()},S.toBytesLE=function(){var t=this.high,e=this.low;return[255&e,e>>>8&255,e>>>16&255,e>>>24,255&t,t>>>8&255,t>>>16&255,t>>>24]},S.toBytesBE=function(){var t=this.high,e=this.low;return[t>>>24,t>>>16&255,t>>>8&255,255&t,e>>>24,e>>>16&255,e>>>8&255,255&e]},n.fromBytes=function(t,e,r){return r?n.fromBytesLE(t,e):n.fromBytesBE(t,e)},n.fromBytesLE=function(t,e){return new n(t[0]|t[1]<<8|t[2]<<16|t[3]<<24,t[4]|t[5]<<8|t[6]<<16|t[7]<<24,e)},n.fromBytesBE=function(t,e){return new n(t[4]<<24|t[5]<<16|t[6]<<8|t[7],t[0]<<24|t[1]<<16|t[2]<<8|t[3],e)}},1446:(t,e,n)=>{"use strict";var r,i,o,a=n(2100),s=a.Reader,u=a.Writer,c=a.util,l=a.roots.default||(a.roots.default={});l.onnx=((o={}).Version=(r={},(i=Object.create(r))[r[0]="_START_VERSION"]=0,i[r[1]="IR_VERSION_2017_10_10"]=1,i[r[2]="IR_VERSION_2017_10_30"]=2,i[r[3]="IR_VERSION_2017_11_3"]=3,i[r[4]="IR_VERSION_2019_1_22"]=4,i[r[5]="IR_VERSION"]=5,i),o.AttributeProto=function(){function t(t){if(this.floats=[],this.ints=[],this.strings=[],this.tensors=[],this.graphs=[],t)for(var e=Object.keys(t),n=0;n<e.length;++n)null!=t[e[n]]&&(this[e[n]]=t[e[n]])}return t.prototype.name="",t.prototype.refAttrName="",t.prototype.docString="",t.prototype.type=0,t.prototype.f=0,t.prototype.i=c.Long?c.Long.fromBits(0,0,!1):0,t.prototype.s=c.newBuffer([]),t.prototype.t=null,t.prototype.g=null,t.prototype.floats=c.emptyArray,t.prototype.ints=c.emptyArray,t.prototype.strings=c.emptyArray,t.prototype.tensors=c.emptyArray,t.prototype.graphs=c.emptyArray,t.create=function(e){return new t(e)},t.encode=function(t,e){if(e||(e=u.create()),null!=t.name&&t.hasOwnProperty("name")&&e.uint32(10).string(t.name),null!=t.f&&t.hasOwnProperty("f")&&e.uint32(21).float(t.f),null!=t.i&&t.hasOwnProperty("i")&&e.uint32(24).int64(t.i),null!=t.s&&t.hasOwnProperty("s")&&e.uint32(34).bytes(t.s),null!=t.t&&t.hasOwnProperty("t")&&l.onnx.TensorProto.encode(t.t,e.uint32(42).fork()).ldelim(),null!=t.g&&t.hasOwnProperty("g")&&l.onnx.GraphProto.encode(t.g,e.uint32(50).fork()).ldelim(),null!=t.floats&&t.floats.length){e.uint32(58).fork();for(var n=0;n<t.floats.length;++n)e.float(t.floats[n]);e.ldelim()}if(null!=t.ints&&t.ints.length){for(e.uint32(66).fork(),n=0;n<t.ints.length;++n)e.int64(t.ints[n]);e.ldelim()}if(null!=t.strings&&t.strings.length)for(n=0;n<t.strings.length;++n)e.uint32(74).bytes(t.strings[n]);if(null!=t.tensors&&t.tensors.length)for(n=0;n<t.tensors.length;++n)l.onnx.TensorProto.encode(t.tensors[n],e.uint32(82).fork()).ldelim();if(null!=t.graphs&&t.graphs.length)for(n=0;n<t.graphs.length;++n)l.onnx.GraphProto.encode(t.graphs[n],e.uint32(90).fork()).ldelim();return null!=t.docString&&t.hasOwnProperty("docString")&&e.uint32(106).string(t.docString),null!=t.type&&t.hasOwnProperty("type")&&e.uint32(160).int32(t.type),null!=t.refAttrName&&t.hasOwnProperty("refAttrName")&&e.uint32(170).string(t.refAttrName),e},t.encodeDelimited=function(t,e){return this.encode(t,e).ldelim()},t.decode=function(t,e){t instanceof s||(t=s.create(t));for(var n=void 0===e?t.len:t.pos+e,r=new l.onnx.AttributeProto;t.pos<n;){var i=t.uint32();switch(i>>>3){case 1:r.name=t.string();break;case 21:r.refAttrName=t.string();break;case 13:r.docString=t.string();break;case 20:r.type=t.int32();break;case 2:r.f=t.float();break;case 3:r.i=t.int64();break;case 4:r.s=t.bytes();break;case 5:r.t=l.onnx.TensorProto.decode(t,t.uint32());break;case 6:r.g=l.onnx.GraphProto.decode(t,t.uint32());break;case 7:if(r.floats&&r.floats.length||(r.floats=[]),2==(7&i))for(var o=t.uint32()+t.pos;t.pos<o;)r.floats.push(t.float());else r.floats.push(t.float());break;case 8:if(r.ints&&r.ints.length||(r.ints=[]),2==(7&i))for(o=t.uint32()+t.pos;t.pos<o;)r.ints.push(t.int64());else r.ints.push(t.int64());break;case 9:r.strings&&r.strings.length||(r.strings=[]),r.strings.push(t.bytes());break;case 10:r.tensors&&r.tensors.length||(r.tensors=[]),r.tensors.push(l.onnx.TensorProto.decode(t,t.uint32()));break;case 11:r.graphs&&r.graphs.length||(r.graphs=[]),r.graphs.push(l.onnx.GraphProto.decode(t,t.uint32()));break;default:t.skipType(7&i)}}return r},t.decodeDelimited=function(t){return t instanceof s||(t=new s(t)),this.decode(t,t.uint32())},t.verify=function(t){if("object"!=typeof t||null===t)return"object expected";if(null!=t.name&&t.hasOwnProperty("name")&&!c.isString(t.name))return"name: string expected";if(null!=t.refAttrName&&t.hasOwnProperty("refAttrName")&&!c.isString(t.refAttrName))return"refAttrName: string expected";if(null!=t.docString&&t.hasOwnProperty("docString")&&!c.isString(t.docString))return"docString: string expected";if(null!=t.type&&t.hasOwnProperty("type"))switch(t.type){default:return"type: enum value expected";case 0:case 1:case 2:case 3:case 4:case 5:case 6:case 7:case 8:case 9:case 10:}if(null!=t.f&&t.hasOwnProperty("f")&&"number"!=typeof t.f)return"f: number expected";if(null!=t.i&&t.hasOwnProperty("i")&&!(c.isInteger(t.i)||t.i&&c.isInteger(t.i.low)&&c.isInteger(t.i.high)))return"i: integer|Long expected";if(null!=t.s&&t.hasOwnProperty("s")&&!(t.s&&"number"==typeof t.s.length||c.isString(t.s)))return"s: buffer expected";if(null!=t.t&&t.hasOwnProperty("t")&&(n=l.onnx.TensorProto.verify(t.t)))return"t."+n;if(null!=t.g&&t.hasOwnProperty("g")&&(n=l.onnx.GraphProto.verify(t.g)))return"g."+n;if(null!=t.floats&&t.hasOwnProperty("floats")){if(!Array.isArray(t.floats))return"floats: array expected";for(var e=0;e<t.floats.length;++e)if("number"!=typeof t.floats[e])return"floats: number[] expected"}if(null!=t.ints&&t.hasOwnProperty("ints")){if(!Array.isArray(t.ints))return"ints: array expected";for(e=0;e<t.ints.length;++e)if(!(c.isInteger(t.ints[e])||t.ints[e]&&c.isInteger(t.ints[e].low)&&c.isInteger(t.ints[e].high)))return"ints: integer|Long[] expected"}if(null!=t.strings&&t.hasOwnProperty("strings")){if(!Array.isArray(t.strings))return"strings: array expected";for(e=0;e<t.strings.length;++e)if(!(t.strings[e]&&"number"==typeof t.strings[e].length||c.isString(t.strings[e])))return"strings: buffer[] expected"}if(null!=t.tensors&&t.hasOwnProperty("tensors")){if(!Array.isArray(t.tensors))return"tensors: array expected";for(e=0;e<t.tensors.length;++e)if(n=l.onnx.TensorProto.verify(t.tensors[e]))return"tensors."+n}if(null!=t.graphs&&t.hasOwnProperty("graphs")){if(!Array.isArray(t.graphs))return"graphs: array expected";for(e=0;e<t.graphs.length;++e){var n;if(n=l.onnx.GraphProto.verify(t.graphs[e]))return"graphs."+n}}return null},t.fromObject=function(t){if(t instanceof l.onnx.AttributeProto)return t;var e=new l.onnx.AttributeProto;switch(null!=t.name&&(e.name=String(t.name)),null!=t.refAttrName&&(e.refAttrName=String(t.refAttrName)),null!=t.docString&&(e.docString=String(t.docString)),t.type){case"UNDEFINED":case 0:e.type=0;break;case"FLOAT":case 1:e.type=1;break;case"INT":case 2:e.type=2;break;case"STRING":case 3:e.type=3;break;case"TENSOR":case 4:e.type=4;break;case"GRAPH":case 5:e.type=5;break;case"FLOATS":case 6:e.type=6;break;case"INTS":case 7:e.type=7;break;case"STRINGS":case 8:e.type=8;break;case"TENSORS":case 9:e.type=9;break;case"GRAPHS":case 10:e.type=10}if(null!=t.f&&(e.f=Number(t.f)),null!=t.i&&(c.Long?(e.i=c.Long.fromValue(t.i)).unsigned=!1:"string"==typeof t.i?e.i=parseInt(t.i,10):"number"==typeof t.i?e.i=t.i:"object"==typeof t.i&&(e.i=new c.LongBits(t.i.low>>>0,t.i.high>>>0).toNumber())),null!=t.s&&("string"==typeof t.s?c.base64.decode(t.s,e.s=c.newBuffer(c.base64.length(t.s)),0):t.s.length&&(e.s=t.s)),null!=t.t){if("object"!=typeof t.t)throw TypeError(".onnx.AttributeProto.t: object expected");e.t=l.onnx.TensorProto.fromObject(t.t)}if(null!=t.g){if("object"!=typeof t.g)throw TypeError(".onnx.AttributeProto.g: object expected");e.g=l.onnx.GraphProto.fromObject(t.g)}if(t.floats){if(!Array.isArray(t.floats))throw TypeError(".onnx.AttributeProto.floats: array expected");e.floats=[];for(var n=0;n<t.floats.length;++n)e.floats[n]=Number(t.floats[n])}if(t.ints){if(!Array.isArray(t.ints))throw TypeError(".onnx.AttributeProto.ints: array expected");for(e.ints=[],n=0;n<t.ints.length;++n)c.Long?(e.ints[n]=c.Long.fromValue(t.ints[n])).unsigned=!1:"string"==typeof t.ints[n]?e.ints[n]=parseInt(t.ints[n],10):"number"==typeof t.ints[n]?e.ints[n]=t.ints[n]:"object"==typeof t.ints[n]&&(e.ints[n]=new c.LongBits(t.ints[n].low>>>0,t.ints[n].high>>>0).toNumber())}if(t.strings){if(!Array.isArray(t.strings))throw TypeError(".onnx.AttributeProto.strings: array expected");for(e.strings=[],n=0;n<t.strings.length;++n)"string"==typeof t.strings[n]?c.base64.decode(t.strings[n],e.strings[n]=c.newBuffer(c.base64.length(t.strings[n])),0):t.strings[n].length&&(e.strings[n]=t.strings[n])}if(t.tensors){if(!Array.isArray(t.tensors))throw TypeError(".onnx.AttributeProto.tensors: array expected");for(e.tensors=[],n=0;n<t.tensors.length;++n){if("object"!=typeof t.tensors[n])throw TypeError(".onnx.AttributeProto.tensors: object expected");e.tensors[n]=l.onnx.TensorProto.fromObject(t.tensors[n])}}if(t.graphs){if(!Array.isArray(t.graphs))throw TypeError(".onnx.AttributeProto.graphs: array expected");for(e.graphs=[],n=0;n<t.graphs.length;++n){if("object"!=typeof t.graphs[n])throw TypeError(".onnx.AttributeProto.graphs: object expected");e.graphs[n]=l.onnx.GraphProto.fromObject(t.graphs[n])}}return e},t.toObject=function(t,e){e||(e={});var n={};if((e.arrays||e.defaults)&&(n.floats=[],n.ints=[],n.strings=[],n.tensors=[],n.graphs=[]),e.defaults){if(n.name="",n.f=0,c.Long){var r=new c.Long(0,0,!1);n.i=e.longs===String?r.toString():e.longs===Number?r.toNumber():r}else n.i=e.longs===String?"0":0;e.bytes===String?n.s="":(n.s=[],e.bytes!==Array&&(n.s=c.newBuffer(n.s))),n.t=null,n.g=null,n.docString="",n.type=e.enums===String?"UNDEFINED":0,n.refAttrName=""}if(null!=t.name&&t.hasOwnProperty("name")&&(n.name=t.name),null!=t.f&&t.hasOwnProperty("f")&&(n.f=e.json&&!isFinite(t.f)?String(t.f):t.f),null!=t.i&&t.hasOwnProperty("i")&&("number"==typeof t.i?n.i=e.longs===String?String(t.i):t.i:n.i=e.longs===String?c.Long.prototype.toString.call(t.i):e.longs===Number?new c.LongBits(t.i.low>>>0,t.i.high>>>0).toNumber():t.i),null!=t.s&&t.hasOwnProperty("s")&&(n.s=e.bytes===String?c.base64.encode(t.s,0,t.s.length):e.bytes===Array?Array.prototype.slice.call(t.s):t.s),null!=t.t&&t.hasOwnProperty("t")&&(n.t=l.onnx.TensorProto.toObject(t.t,e)),null!=t.g&&t.hasOwnProperty("g")&&(n.g=l.onnx.GraphProto.toObject(t.g,e)),t.floats&&t.floats.length){n.floats=[];for(var i=0;i<t.floats.length;++i)n.floats[i]=e.json&&!isFinite(t.floats[i])?String(t.floats[i]):t.floats[i]}if(t.ints&&t.ints.length)for(n.ints=[],i=0;i<t.ints.length;++i)"number"==typeof t.ints[i]?n.ints[i]=e.longs===String?String(t.ints[i]):t.ints[i]:n.ints[i]=e.longs===String?c.Long.prototype.toString.call(t.ints[i]):e.longs===Number?new c.LongBits(t.ints[i].low>>>0,t.ints[i].high>>>0).toNumber():t.ints[i];if(t.strings&&t.strings.length)for(n.strings=[],i=0;i<t.strings.length;++i)n.strings[i]=e.bytes===String?c.base64.encode(t.strings[i],0,t.strings[i].length):e.bytes===Array?Array.prototype.slice.call(t.strings[i]):t.strings[i];if(t.tensors&&t.tensors.length)for(n.tensors=[],i=0;i<t.tensors.length;++i)n.tensors[i]=l.onnx.TensorProto.toObject(t.tensors[i],e);if(t.graphs&&t.graphs.length)for(n.graphs=[],i=0;i<t.graphs.length;++i)n.graphs[i]=l.onnx.GraphProto.toObject(t.graphs[i],e);return null!=t.docString&&t.hasOwnProperty("docString")&&(n.docString=t.docString),null!=t.type&&t.hasOwnProperty("type")&&(n.type=e.enums===String?l.onnx.AttributeProto.AttributeType[t.type]:t.type),null!=t.refAttrName&&t.hasOwnProperty("refAttrName")&&(n.refAttrName=t.refAttrName),n},t.prototype.toJSON=function(){return this.constructor.toObject(this,a.util.toJSONOptions)},t.AttributeType=function(){var t={},e=Object.create(t);return e[t[0]="UNDEFINED"]=0,e[t[1]="FLOAT"]=1,e[t[2]="INT"]=2,e[t[3]="STRING"]=3,e[t[4]="TENSOR"]=4,e[t[5]="GRAPH"]=5,e[t[6]="FLOATS"]=6,e[t[7]="INTS"]=7,e[t[8]="STRINGS"]=8,e[t[9]="TENSORS"]=9,e[t[10]="GRAPHS"]=10,e}(),t}(),o.ValueInfoProto=function(){function t(t){if(t)for(var e=Object.keys(t),n=0;n<e.length;++n)null!=t[e[n]]&&(this[e[n]]=t[e[n]])}return t.prototype.name="",t.prototype.type=null,t.prototype.docString="",t.create=function(e){return new t(e)},t.encode=function(t,e){return e||(e=u.create()),null!=t.name&&t.hasOwnProperty("name")&&e.uint32(10).string(t.name),null!=t.type&&t.hasOwnProperty("type")&&l.onnx.TypeProto.encode(t.type,e.uint32(18).fork()).ldelim(),null!=t.docString&&t.hasOwnProperty("docString")&&e.uint32(26).string(t.docString),e},t.encodeDelimited=function(t,e){return this.encode(t,e).ldelim()},t.decode=function(t,e){t instanceof s||(t=s.create(t));for(var n=void 0===e?t.len:t.pos+e,r=new l.onnx.ValueInfoProto;t.pos<n;){var i=t.uint32();switch(i>>>3){case 1:r.name=t.string();break;case 2:r.type=l.onnx.TypeProto.decode(t,t.uint32());break;case 3:r.docString=t.string();break;default:t.skipType(7&i)}}return r},t.decodeDelimited=function(t){return t instanceof s||(t=new s(t)),this.decode(t,t.uint32())},t.verify=function(t){if("object"!=typeof t||null===t)return"object expected";if(null!=t.name&&t.hasOwnProperty("name")&&!c.isString(t.name))return"name: string expected";if(null!=t.type&&t.hasOwnProperty("type")){var e=l.onnx.TypeProto.verify(t.type);if(e)return"type."+e}return null!=t.docString&&t.hasOwnProperty("docString")&&!c.isString(t.docString)?"docString: string expected":null},t.fromObject=function(t){if(t instanceof l.onnx.ValueInfoProto)return t;var e=new l.onnx.ValueInfoProto;if(null!=t.name&&(e.name=String(t.name)),null!=t.type){if("object"!=typeof t.type)throw TypeError(".onnx.ValueInfoProto.type: object expected");e.type=l.onnx.TypeProto.fromObject(t.type)}return null!=t.docString&&(e.docString=String(t.docString)),e},t.toObject=function(t,e){e||(e={});var n={};return e.defaults&&(n.name="",n.type=null,n.docString=""),null!=t.name&&t.hasOwnProperty("name")&&(n.name=t.name),null!=t.type&&t.hasOwnProperty("type")&&(n.type=l.onnx.TypeProto.toObject(t.type,e)),null!=t.docString&&t.hasOwnProperty("docString")&&(n.docString=t.docString),n},t.prototype.toJSON=function(){return this.constructor.toObject(this,a.util.toJSONOptions)},t}(),o.NodeProto=function(){function t(t){if(this.input=[],this.output=[],this.attribute=[],t)for(var e=Object.keys(t),n=0;n<e.length;++n)null!=t[e[n]]&&(this[e[n]]=t[e[n]])}return t.prototype.input=c.emptyArray,t.prototype.output=c.emptyArray,t.prototype.name="",t.prototype.opType="",t.prototype.domain="",t.prototype.attribute=c.emptyArray,t.prototype.docString="",t.create=function(e){return new t(e)},t.encode=function(t,e){if(e||(e=u.create()),null!=t.input&&t.input.length)for(var n=0;n<t.input.length;++n)e.uint32(10).string(t.input[n]);if(null!=t.output&&t.output.length)for(n=0;n<t.output.length;++n)e.uint32(18).string(t.output[n]);if(null!=t.name&&t.hasOwnProperty("name")&&e.uint32(26).string(t.name),null!=t.opType&&t.hasOwnProperty("opType")&&e.uint32(34).string(t.opType),null!=t.attribute&&t.attribute.length)for(n=0;n<t.attribute.length;++n)l.onnx.AttributeProto.encode(t.attribute[n],e.uint32(42).fork()).ldelim();return null!=t.docString&&t.hasOwnProperty("docString")&&e.uint32(50).string(t.docString),null!=t.domain&&t.hasOwnProperty("domain")&&e.uint32(58).string(t.domain),e},t.encodeDelimited=function(t,e){return this.encode(t,e).ldelim()},t.decode=function(t,e){t instanceof s||(t=s.create(t));for(var n=void 0===e?t.len:t.pos+e,r=new l.onnx.NodeProto;t.pos<n;){var i=t.uint32();switch(i>>>3){case 1:r.input&&r.input.length||(r.input=[]),r.input.push(t.string());break;case 2:r.output&&r.output.length||(r.output=[]),r.output.push(t.string());break;case 3:r.name=t.string();break;case 4:r.opType=t.string();break;case 7:r.domain=t.string();break;case 5:r.attribute&&r.attribute.length||(r.attribute=[]),r.attribute.push(l.onnx.AttributeProto.decode(t,t.uint32()));break;case 6:r.docString=t.string();break;default:t.skipType(7&i)}}return r},t.decodeDelimited=function(t){return t instanceof s||(t=new s(t)),this.decode(t,t.uint32())},t.verify=function(t){if("object"!=typeof t||null===t)return"object expected";if(null!=t.input&&t.hasOwnProperty("input")){if(!Array.isArray(t.input))return"input: array expected";for(var e=0;e<t.input.length;++e)if(!c.isString(t.input[e]))return"input: string[] expected"}if(null!=t.output&&t.hasOwnProperty("output")){if(!Array.isArray(t.output))return"output: array expected";for(e=0;e<t.output.length;++e)if(!c.isString(t.output[e]))return"output: string[] expected"}if(null!=t.name&&t.hasOwnProperty("name")&&!c.isString(t.name))return"name: string expected";if(null!=t.opType&&t.hasOwnProperty("opType")&&!c.isString(t.opType))return"opType: string expected";if(null!=t.domain&&t.hasOwnProperty("domain")&&!c.isString(t.domain))return"domain: string expected";if(null!=t.attribute&&t.hasOwnProperty("attribute")){if(!Array.isArray(t.attribute))return"attribute: array expected";for(e=0;e<t.attribute.length;++e){var n=l.onnx.AttributeProto.verify(t.attribute[e]);if(n)return"attribute."+n}}return null!=t.docString&&t.hasOwnProperty("docString")&&!c.isString(t.docString)?"docString: string expected":null},t.fromObject=function(t){if(t instanceof l.onnx.NodeProto)return t;var e=new l.onnx.NodeProto;if(t.input){if(!Array.isArray(t.input))throw TypeError(".onnx.NodeProto.input: array expected");e.input=[];for(var n=0;n<t.input.length;++n)e.input[n]=String(t.input[n])}if(t.output){if(!Array.isArray(t.output))throw TypeError(".onnx.NodeProto.output: array expected");for(e.output=[],n=0;n<t.output.length;++n)e.output[n]=String(t.output[n])}if(null!=t.name&&(e.name=String(t.name)),null!=t.opType&&(e.opType=String(t.opType)),null!=t.domain&&(e.domain=String(t.domain)),t.attribute){if(!Array.isArray(t.attribute))throw TypeError(".onnx.NodeProto.attribute: array expected");for(e.attribute=[],n=0;n<t.attribute.length;++n){if("object"!=typeof t.attribute[n])throw TypeError(".onnx.NodeProto.attribute: object expected");e.attribute[n]=l.onnx.AttributeProto.fromObject(t.attribute[n])}}return null!=t.docString&&(e.docString=String(t.docString)),e},t.toObject=function(t,e){e||(e={});var n={};if((e.arrays||e.defaults)&&(n.input=[],n.output=[],n.attribute=[]),e.defaults&&(n.name="",n.opType="",n.docString="",n.domain=""),t.input&&t.input.length){n.input=[];for(var r=0;r<t.input.length;++r)n.input[r]=t.input[r]}if(t.output&&t.output.length)for(n.output=[],r=0;r<t.output.length;++r)n.output[r]=t.output[r];if(null!=t.name&&t.hasOwnProperty("name")&&(n.name=t.name),null!=t.opType&&t.hasOwnProperty("opType")&&(n.opType=t.opType),t.attribute&&t.attribute.length)for(n.attribute=[],r=0;r<t.attribute.length;++r)n.attribute[r]=l.onnx.AttributeProto.toObject(t.attribute[r],e);return null!=t.docString&&t.hasOwnProperty("docString")&&(n.docString=t.docString),null!=t.domain&&t.hasOwnProperty("domain")&&(n.domain=t.domain),n},t.prototype.toJSON=function(){return this.constructor.toObject(this,a.util.toJSONOptions)},t}(),o.ModelProto=function(){function t(t){if(this.opsetImport=[],this.metadataProps=[],t)for(var e=Object.keys(t),n=0;n<e.length;++n)null!=t[e[n]]&&(this[e[n]]=t[e[n]])}return t.prototype.irVersion=c.Long?c.Long.fromBits(0,0,!1):0,t.prototype.opsetImport=c.emptyArray,t.prototype.producerName="",t.prototype.producerVersion="",t.prototype.domain="",t.prototype.modelVersion=c.Long?c.Long.fromBits(0,0,!1):0,t.prototype.docString="",t.prototype.graph=null,t.prototype.metadataProps=c.emptyArray,t.create=function(e){return new t(e)},t.encode=function(t,e){if(e||(e=u.create()),null!=t.irVersion&&t.hasOwnProperty("irVersion")&&e.uint32(8).int64(t.irVersion),null!=t.producerName&&t.hasOwnProperty("producerName")&&e.uint32(18).string(t.producerName),null!=t.producerVersion&&t.hasOwnProperty("producerVersion")&&e.uint32(26).string(t.producerVersion),null!=t.domain&&t.hasOwnProperty("domain")&&e.uint32(34).string(t.domain),null!=t.modelVersion&&t.hasOwnProperty("modelVersion")&&e.uint32(40).int64(t.modelVersion),null!=t.docString&&t.hasOwnProperty("docString")&&e.uint32(50).string(t.docString),null!=t.graph&&t.hasOwnProperty("graph")&&l.onnx.GraphProto.encode(t.graph,e.uint32(58).fork()).ldelim(),null!=t.opsetImport&&t.opsetImport.length)for(var n=0;n<t.opsetImport.length;++n)l.onnx.OperatorSetIdProto.encode(t.opsetImport[n],e.uint32(66).fork()).ldelim();if(null!=t.metadataProps&&t.metadataProps.length)for(n=0;n<t.metadataProps.length;++n)l.onnx.StringStringEntryProto.encode(t.metadataProps[n],e.uint32(114).fork()).ldelim();return e},t.encodeDelimited=function(t,e){return this.encode(t,e).ldelim()},t.decode=function(t,e){t instanceof s||(t=s.create(t));for(var n=void 0===e?t.len:t.pos+e,r=new l.onnx.ModelProto;t.pos<n;){var i=t.uint32();switch(i>>>3){case 1:r.irVersion=t.int64();break;case 8:r.opsetImport&&r.opsetImport.length||(r.opsetImport=[]),r.opsetImport.push(l.onnx.OperatorSetIdProto.decode(t,t.uint32()));break;case 2:r.producerName=t.string();break;case 3:r.producerVersion=t.string();break;case 4:r.domain=t.string();break;case 5:r.modelVersion=t.int64();break;case 6:r.docString=t.string();break;case 7:r.graph=l.onnx.GraphProto.decode(t,t.uint32());break;case 14:r.metadataProps&&r.metadataProps.length||(r.metadataProps=[]),r.metadataProps.push(l.onnx.StringStringEntryProto.decode(t,t.uint32()));break;default:t.skipType(7&i)}}return r},t.decodeDelimited=function(t){return t instanceof s||(t=new s(t)),this.decode(t,t.uint32())},t.verify=function(t){if("object"!=typeof t||null===t)return"object expected";if(null!=t.irVersion&&t.hasOwnProperty("irVersion")&&!(c.isInteger(t.irVersion)||t.irVersion&&c.isInteger(t.irVersion.low)&&c.isInteger(t.irVersion.high)))return"irVersion: integer|Long expected";if(null!=t.opsetImport&&t.hasOwnProperty("opsetImport")){if(!Array.isArray(t.opsetImport))return"opsetImport: array expected";for(var e=0;e<t.opsetImport.length;++e)if(n=l.onnx.OperatorSetIdProto.verify(t.opsetImport[e]))return"opsetImport."+n}if(null!=t.producerName&&t.hasOwnProperty("producerName")&&!c.isString(t.producerName))return"producerName: string expected";if(null!=t.producerVersion&&t.hasOwnProperty("producerVersion")&&!c.isString(t.producerVersion))return"producerVersion: string expected";if(null!=t.domain&&t.hasOwnProperty("domain")&&!c.isString(t.domain))return"domain: string expected";if(null!=t.modelVersion&&t.hasOwnProperty("modelVersion")&&!(c.isInteger(t.modelVersion)||t.modelVersion&&c.isInteger(t.modelVersion.low)&&c.isInteger(t.modelVersion.high)))return"modelVersion: integer|Long expected";if(null!=t.docString&&t.hasOwnProperty("docString")&&!c.isString(t.docString))return"docString: string expected";if(null!=t.graph&&t.hasOwnProperty("graph")&&(n=l.onnx.GraphProto.verify(t.graph)))return"graph."+n;if(null!=t.metadataProps&&t.hasOwnProperty("metadataProps")){if(!Array.isArray(t.metadataProps))return"metadataProps: array expected";for(e=0;e<t.metadataProps.length;++e){var n;if(n=l.onnx.StringStringEntryProto.verify(t.metadataProps[e]))return"metadataProps."+n}}return null},t.fromObject=function(t){if(t instanceof l.onnx.ModelProto)return t;var e=new l.onnx.ModelProto;if(null!=t.irVersion&&(c.Long?(e.irVersion=c.Long.fromValue(t.irVersion)).unsigned=!1:"string"==typeof t.irVersion?e.irVersion=parseInt(t.irVersion,10):"number"==typeof t.irVersion?e.irVersion=t.irVersion:"object"==typeof t.irVersion&&(e.irVersion=new c.LongBits(t.irVersion.low>>>0,t.irVersion.high>>>0).toNumber())),t.opsetImport){if(!Array.isArray(t.opsetImport))throw TypeError(".onnx.ModelProto.opsetImport: array expected");e.opsetImport=[];for(var n=0;n<t.opsetImport.length;++n){if("object"!=typeof t.opsetImport[n])throw TypeError(".onnx.ModelProto.opsetImport: object expected");e.opsetImport[n]=l.onnx.OperatorSetIdProto.fromObject(t.opsetImport[n])}}if(null!=t.producerName&&(e.producerName=String(t.producerName)),null!=t.producerVersion&&(e.producerVersion=String(t.producerVersion)),null!=t.domain&&(e.domain=String(t.domain)),null!=t.modelVersion&&(c.Long?(e.modelVersion=c.Long.fromValue(t.modelVersion)).unsigned=!1:"string"==typeof t.modelVersion?e.modelVersion=parseInt(t.modelVersion,10):"number"==typeof t.modelVersion?e.modelVersion=t.modelVersion:"object"==typeof t.modelVersion&&(e.modelVersion=new c.LongBits(t.modelVersion.low>>>0,t.modelVersion.high>>>0).toNumber())),null!=t.docString&&(e.docString=String(t.docString)),null!=t.graph){if("object"!=typeof t.graph)throw TypeError(".onnx.ModelProto.graph: object expected");e.graph=l.onnx.GraphProto.fromObject(t.graph)}if(t.metadataProps){if(!Array.isArray(t.metadataProps))throw TypeError(".onnx.ModelProto.metadataProps: array expected");for(e.metadataProps=[],n=0;n<t.metadataProps.length;++n){if("object"!=typeof t.metadataProps[n])throw TypeError(".onnx.ModelProto.metadataProps: object expected");e.metadataProps[n]=l.onnx.StringStringEntryProto.fromObject(t.metadataProps[n])}}return e},t.toObject=function(t,e){e||(e={});var n={};if((e.arrays||e.defaults)&&(n.opsetImport=[],n.metadataProps=[]),e.defaults){if(c.Long){var r=new c.Long(0,0,!1);n.irVersion=e.longs===String?r.toString():e.longs===Number?r.toNumber():r}else n.irVersion=e.longs===String?"0":0;n.producerName="",n.producerVersion="",n.domain="",c.Long?(r=new c.Long(0,0,!1),n.modelVersion=e.longs===String?r.toString():e.longs===Number?r.toNumber():r):n.modelVersion=e.longs===String?"0":0,n.docString="",n.graph=null}if(null!=t.irVersion&&t.hasOwnProperty("irVersion")&&("number"==typeof t.irVersion?n.irVersion=e.longs===String?String(t.irVersion):t.irVersion:n.irVersion=e.longs===String?c.Long.prototype.toString.call(t.irVersion):e.longs===Number?new c.LongBits(t.irVersion.low>>>0,t.irVersion.high>>>0).toNumber():t.irVersion),null!=t.producerName&&t.hasOwnProperty("producerName")&&(n.producerName=t.producerName),null!=t.producerVersion&&t.hasOwnProperty("producerVersion")&&(n.producerVersion=t.producerVersion),null!=t.domain&&t.hasOwnProperty("domain")&&(n.domain=t.domain),null!=t.modelVersion&&t.hasOwnProperty("modelVersion")&&("number"==typeof t.modelVersion?n.modelVersion=e.longs===String?String(t.modelVersion):t.modelVersion:n.modelVersion=e.longs===String?c.Long.prototype.toString.call(t.modelVersion):e.longs===Number?new c.LongBits(t.modelVersion.low>>>0,t.modelVersion.high>>>0).toNumber():t.modelVersion),null!=t.docString&&t.hasOwnProperty("docString")&&(n.docString=t.docString),null!=t.graph&&t.hasOwnProperty("graph")&&(n.graph=l.onnx.GraphProto.toObject(t.graph,e)),t.opsetImport&&t.opsetImport.length){n.opsetImport=[];for(var i=0;i<t.opsetImport.length;++i)n.opsetImport[i]=l.onnx.OperatorSetIdProto.toObject(t.opsetImport[i],e)}if(t.metadataProps&&t.metadataProps.length)for(n.metadataProps=[],i=0;i<t.metadataProps.length;++i)n.metadataProps[i]=l.onnx.StringStringEntryProto.toObject(t.metadataProps[i],e);return n},t.prototype.toJSON=function(){return this.constructor.toObject(this,a.util.toJSONOptions)},t}(),o.StringStringEntryProto=function(){function t(t){if(t)for(var e=Object.keys(t),n=0;n<e.length;++n)null!=t[e[n]]&&(this[e[n]]=t[e[n]])}return t.prototype.key="",t.prototype.value="",t.create=function(e){return new t(e)},t.encode=function(t,e){return e||(e=u.create()),null!=t.key&&t.hasOwnProperty("key")&&e.uint32(10).string(t.key),null!=t.value&&t.hasOwnProperty("value")&&e.uint32(18).string(t.value),e},t.encodeDelimited=function(t,e){return this.encode(t,e).ldelim()},t.decode=function(t,e){t instanceof s||(t=s.create(t));for(var n=void 0===e?t.len:t.pos+e,r=new l.onnx.StringStringEntryProto;t.pos<n;){var i=t.uint32();switch(i>>>3){case 1:r.key=t.string();break;case 2:r.value=t.string();break;default:t.skipType(7&i)}}return r},t.decodeDelimited=function(t){return t instanceof s||(t=new s(t)),this.decode(t,t.uint32())},t.verify=function(t){return"object"!=typeof t||null===t?"object expected":null!=t.key&&t.hasOwnProperty("key")&&!c.isString(t.key)?"key: string expected":null!=t.value&&t.hasOwnProperty("value")&&!c.isString(t.value)?"value: string expected":null},t.fromObject=function(t){if(t instanceof l.onnx.StringStringEntryProto)return t;var e=new l.onnx.StringStringEntryProto;return null!=t.key&&(e.key=String(t.key)),null!=t.value&&(e.value=String(t.value)),e},t.toObject=function(t,e){e||(e={});var n={};return e.defaults&&(n.key="",n.value=""),null!=t.key&&t.hasOwnProperty("key")&&(n.key=t.key),null!=t.value&&t.hasOwnProperty("value")&&(n.value=t.value),n},t.prototype.toJSON=function(){return this.constructor.toObject(this,a.util.toJSONOptions)},t}(),o.TensorAnnotation=function(){function t(t){if(this.quantParameterTensorNames=[],t)for(var e=Object.keys(t),n=0;n<e.length;++n)null!=t[e[n]]&&(this[e[n]]=t[e[n]])}return t.prototype.tensorName="",t.prototype.quantParameterTensorNames=c.emptyArray,t.create=function(e){return new t(e)},t.encode=function(t,e){if(e||(e=u.create()),null!=t.tensorName&&t.hasOwnProperty("tensorName")&&e.uint32(10).string(t.tensorName),null!=t.quantParameterTensorNames&&t.quantParameterTensorNames.length)for(var n=0;n<t.quantParameterTensorNames.length;++n)l.onnx.StringStringEntryProto.encode(t.quantParameterTensorNames[n],e.uint32(18).fork()).ldelim();return e},t.encodeDelimited=function(t,e){return this.encode(t,e).ldelim()},t.decode=function(t,e){t instanceof s||(t=s.create(t));for(var n=void 0===e?t.len:t.pos+e,r=new l.onnx.TensorAnnotation;t.pos<n;){var i=t.uint32();switch(i>>>3){case 1:r.tensorName=t.string();break;case 2:r.quantParameterTensorNames&&r.quantParameterTensorNames.length||(r.quantParameterTensorNames=[]),r.quantParameterTensorNames.push(l.onnx.StringStringEntryProto.decode(t,t.uint32()));break;default:t.skipType(7&i)}}return r},t.decodeDelimited=function(t){return t instanceof s||(t=new s(t)),this.decode(t,t.uint32())},t.verify=function(t){if("object"!=typeof t||null===t)return"object expected";if(null!=t.tensorName&&t.hasOwnProperty("tensorName")&&!c.isString(t.tensorName))return"tensorName: string expected";if(null!=t.quantParameterTensorNames&&t.hasOwnProperty("quantParameterTensorNames")){if(!Array.isArray(t.quantParameterTensorNames))return"quantParameterTensorNames: array expected";for(var e=0;e<t.quantParameterTensorNames.length;++e){var n=l.onnx.StringStringEntryProto.verify(t.quantParameterTensorNames[e]);if(n)return"quantParameterTensorNames."+n}}return null},t.fromObject=function(t){if(t instanceof l.onnx.TensorAnnotation)return t;var e=new l.onnx.TensorAnnotation;if(null!=t.tensorName&&(e.tensorName=String(t.tensorName)),t.quantParameterTensorNames){if(!Array.isArray(t.quantParameterTensorNames))throw TypeError(".onnx.TensorAnnotation.quantParameterTensorNames: array expected");e.quantParameterTensorNames=[];for(var n=0;n<t.quantParameterTensorNames.length;++n){if("object"!=typeof t.quantParameterTensorNames[n])throw TypeError(".onnx.TensorAnnotation.quantParameterTensorNames: object expected");e.quantParameterTensorNames[n]=l.onnx.StringStringEntryProto.fromObject(t.quantParameterTensorNames[n])}}return e},t.toObject=function(t,e){e||(e={});var n={};if((e.arrays||e.defaults)&&(n.quantParameterTensorNames=[]),e.defaults&&(n.tensorName=""),null!=t.tensorName&&t.hasOwnProperty("tensorName")&&(n.tensorName=t.tensorName),t.quantParameterTensorNames&&t.quantParameterTensorNames.length){n.quantParameterTensorNames=[];for(var r=0;r<t.quantParameterTensorNames.length;++r)n.quantParameterTensorNames[r]=l.onnx.StringStringEntryProto.toObject(t.quantParameterTensorNames[r],e)}return n},t.prototype.toJSON=function(){return this.constructor.toObject(this,a.util.toJSONOptions)},t}(),o.GraphProto=function(){function t(t){if(this.node=[],this.initializer=[],this.input=[],this.output=[],this.valueInfo=[],this.quantizationAnnotation=[],t)for(var e=Object.keys(t),n=0;n<e.length;++n)null!=t[e[n]]&&(this[e[n]]=t[e[n]])}return t.prototype.node=c.emptyArray,t.prototype.name="",t.prototype.initializer=c.emptyArray,t.prototype.docString="",t.prototype.input=c.emptyArray,t.prototype.output=c.emptyArray,t.prototype.valueInfo=c.emptyArray,t.prototype.quantizationAnnotation=c.emptyArray,t.create=function(e){return new t(e)},t.encode=function(t,e){if(e||(e=u.create()),null!=t.node&&t.node.length)for(var n=0;n<t.node.length;++n)l.onnx.NodeProto.encode(t.node[n],e.uint32(10).fork()).ldelim();if(null!=t.name&&t.hasOwnProperty("name")&&e.uint32(18).string(t.name),null!=t.initializer&&t.initializer.length)for(n=0;n<t.initializer.length;++n)l.onnx.TensorProto.encode(t.initializer[n],e.uint32(42).fork()).ldelim();if(null!=t.docString&&t.hasOwnProperty("docString")&&e.uint32(82).string(t.docString),null!=t.input&&t.input.length)for(n=0;n<t.input.length;++n)l.onnx.ValueInfoProto.encode(t.input[n],e.uint32(90).fork()).ldelim();if(null!=t.output&&t.output.length)for(n=0;n<t.output.length;++n)l.onnx.ValueInfoProto.encode(t.output[n],e.uint32(98).fork()).ldelim();if(null!=t.valueInfo&&t.valueInfo.length)for(n=0;n<t.valueInfo.length;++n)l.onnx.ValueInfoProto.encode(t.valueInfo[n],e.uint32(106).fork()).ldelim();if(null!=t.quantizationAnnotation&&t.quantizationAnnotation.length)for(n=0;n<t.quantizationAnnotation.length;++n)l.onnx.TensorAnnotation.encode(t.quantizationAnnotation[n],e.uint32(114).fork()).ldelim();return e},t.encodeDelimited=function(t,e){return this.encode(t,e).ldelim()},t.decode=function(t,e){t instanceof s||(t=s.create(t));for(var n=void 0===e?t.len:t.pos+e,r=new l.onnx.GraphProto;t.pos<n;){var i=t.uint32();switch(i>>>3){case 1:r.node&&r.node.length||(r.node=[]),r.node.push(l.onnx.NodeProto.decode(t,t.uint32()));break;case 2:r.name=t.string();break;case 5:r.initializer&&r.initializer.length||(r.initializer=[]),r.initializer.push(l.onnx.TensorProto.decode(t,t.uint32()));break;case 10:r.docString=t.string();break;case 11:r.input&&r.input.length||(r.input=[]),r.input.push(l.onnx.ValueInfoProto.decode(t,t.uint32()));break;case 12:r.output&&r.output.length||(r.output=[]),r.output.push(l.onnx.ValueInfoProto.decode(t,t.uint32()));break;case 13:r.valueInfo&&r.valueInfo.length||(r.valueInfo=[]),r.valueInfo.push(l.onnx.ValueInfoProto.decode(t,t.uint32()));break;case 14:r.quantizationAnnotation&&r.quantizationAnnotation.length||(r.quantizationAnnotation=[]),r.quantizationAnnotation.push(l.onnx.TensorAnnotation.decode(t,t.uint32()));break;default:t.skipType(7&i)}}return r},t.decodeDelimited=function(t){return t instanceof s||(t=new s(t)),this.decode(t,t.uint32())},t.verify=function(t){if("object"!=typeof t||null===t)return"object expected";if(null!=t.node&&t.hasOwnProperty("node")){if(!Array.isArray(t.node))return"node: array expected";for(var e=0;e<t.node.length;++e)if(n=l.onnx.NodeProto.verify(t.node[e]))return"node."+n}if(null!=t.name&&t.hasOwnProperty("name")&&!c.isString(t.name))return"name: string expected";if(null!=t.initializer&&t.hasOwnProperty("initializer")){if(!Array.isArray(t.initializer))return"initializer: array expected";for(e=0;e<t.initializer.length;++e)if(n=l.onnx.TensorProto.verify(t.initializer[e]))return"initializer."+n}if(null!=t.docString&&t.hasOwnProperty("docString")&&!c.isString(t.docString))return"docString: string expected";if(null!=t.input&&t.hasOwnProperty("input")){if(!Array.isArray(t.input))return"input: array expected";for(e=0;e<t.input.length;++e)if(n=l.onnx.ValueInfoProto.verify(t.input[e]))return"input."+n}if(null!=t.output&&t.hasOwnProperty("output")){if(!Array.isArray(t.output))return"output: array expected";for(e=0;e<t.output.length;++e)if(n=l.onnx.ValueInfoProto.verify(t.output[e]))return"output."+n}if(null!=t.valueInfo&&t.hasOwnProperty("valueInfo")){if(!Array.isArray(t.valueInfo))return"valueInfo: array expected";for(e=0;e<t.valueInfo.length;++e)if(n=l.onnx.ValueInfoProto.verify(t.valueInfo[e]))return"valueInfo."+n}if(null!=t.quantizationAnnotation&&t.hasOwnProperty("quantizationAnnotation")){if(!Array.isArray(t.quantizationAnnotation))return"quantizationAnnotation: array expected";for(e=0;e<t.quantizationAnnotation.length;++e){var n;if(n=l.onnx.TensorAnnotation.verify(t.quantizationAnnotation[e]))return"quantizationAnnotation."+n}}return null},t.fromObject=function(t){if(t instanceof l.onnx.GraphProto)return t;var e=new l.onnx.GraphProto;if(t.node){if(!Array.isArray(t.node))throw TypeError(".onnx.GraphProto.node: array expected");e.node=[];for(var n=0;n<t.node.length;++n){if("object"!=typeof t.node[n])throw TypeError(".onnx.GraphProto.node: object expected");e.node[n]=l.onnx.NodeProto.fromObject(t.node[n])}}if(null!=t.name&&(e.name=String(t.name)),t.initializer){if(!Array.isArray(t.initializer))throw TypeError(".onnx.GraphProto.initializer: array expected");for(e.initializer=[],n=0;n<t.initializer.length;++n){if("object"!=typeof t.initializer[n])throw TypeError(".onnx.GraphProto.initializer: object expected");e.initializer[n]=l.onnx.TensorProto.fromObject(t.initializer[n])}}if(null!=t.docString&&(e.docString=String(t.docString)),t.input){if(!Array.isArray(t.input))throw TypeError(".onnx.GraphProto.input: array expected");for(e.input=[],n=0;n<t.input.length;++n){if("object"!=typeof t.input[n])throw TypeError(".onnx.GraphProto.input: object expected");e.input[n]=l.onnx.ValueInfoProto.fromObject(t.input[n])}}if(t.output){if(!Array.isArray(t.output))throw TypeError(".onnx.GraphProto.output: array expected");for(e.output=[],n=0;n<t.output.length;++n){if("object"!=typeof t.output[n])throw TypeError(".onnx.GraphProto.output: object expected");e.output[n]=l.onnx.ValueInfoProto.fromObject(t.output[n])}}if(t.valueInfo){if(!Array.isArray(t.valueInfo))throw TypeError(".onnx.GraphProto.valueInfo: array expected");for(e.valueInfo=[],n=0;n<t.valueInfo.length;++n){if("object"!=typeof t.valueInfo[n])throw TypeError(".onnx.GraphProto.valueInfo: object expected");e.valueInfo[n]=l.onnx.ValueInfoProto.fromObject(t.valueInfo[n])}}if(t.quantizationAnnotation){if(!Array.isArray(t.quantizationAnnotation))throw TypeError(".onnx.GraphProto.quantizationAnnotation: array expected");for(e.quantizationAnnotation=[],n=0;n<t.quantizationAnnotation.length;++n){if("object"!=typeof t.quantizationAnnotation[n])throw TypeError(".onnx.GraphProto.quantizationAnnotation: object expected");e.quantizationAnnotation[n]=l.onnx.TensorAnnotation.fromObject(t.quantizationAnnotation[n])}}return e},t.toObject=function(t,e){e||(e={});var n={};if((e.arrays||e.defaults)&&(n.node=[],n.initializer=[],n.input=[],n.output=[],n.valueInfo=[],n.quantizationAnnotation=[]),e.defaults&&(n.name="",n.docString=""),t.node&&t.node.length){n.node=[];for(var r=0;r<t.node.length;++r)n.node[r]=l.onnx.NodeProto.toObject(t.node[r],e)}if(null!=t.name&&t.hasOwnProperty("name")&&(n.name=t.name),t.initializer&&t.initializer.length)for(n.initializer=[],r=0;r<t.initializer.length;++r)n.initializer[r]=l.onnx.TensorProto.toObject(t.initializer[r],e);if(null!=t.docString&&t.hasOwnProperty("docString")&&(n.docString=t.docString),t.input&&t.input.length)for(n.input=[],r=0;r<t.input.length;++r)n.input[r]=l.onnx.ValueInfoProto.toObject(t.input[r],e);if(t.output&&t.output.length)for(n.output=[],r=0;r<t.output.length;++r)n.output[r]=l.onnx.ValueInfoProto.toObject(t.output[r],e);if(t.valueInfo&&t.valueInfo.length)for(n.valueInfo=[],r=0;r<t.valueInfo.length;++r)n.valueInfo[r]=l.onnx.ValueInfoProto.toObject(t.valueInfo[r],e);if(t.quantizationAnnotation&&t.quantizationAnnotation.length)for(n.quantizationAnnotation=[],r=0;r<t.quantizationAnnotation.length;++r)n.quantizationAnnotation[r]=l.onnx.TensorAnnotation.toObject(t.quantizationAnnotation[r],e);return n},t.prototype.toJSON=function(){return this.constructor.toObject(this,a.util.toJSONOptions)},t}(),o.TensorProto=function(){function t(t){if(this.dims=[],this.floatData=[],this.int32Data=[],this.stringData=[],this.int64Data=[],this.externalData=[],this.doubleData=[],this.uint64Data=[],t)for(var e=Object.keys(t),n=0;n<e.length;++n)null!=t[e[n]]&&(this[e[n]]=t[e[n]])}return t.prototype.dims=c.emptyArray,t.prototype.dataType=0,t.prototype.segment=null,t.prototype.floatData=c.emptyArray,t.prototype.int32Data=c.emptyArray,t.prototype.stringData=c.emptyArray,t.prototype.int64Data=c.emptyArray,t.prototype.name="",t.prototype.docString="",t.prototype.rawData=c.newBuffer([]),t.prototype.externalData=c.emptyArray,t.prototype.dataLocation=0,t.prototype.doubleData=c.emptyArray,t.prototype.uint64Data=c.emptyArray,t.create=function(e){return new t(e)},t.encode=function(t,e){if(e||(e=u.create()),null!=t.dims&&t.dims.length){e.uint32(10).fork();for(var n=0;n<t.dims.length;++n)e.int64(t.dims[n]);e.ldelim()}if(null!=t.dataType&&t.hasOwnProperty("dataType")&&e.uint32(16).int32(t.dataType),null!=t.segment&&t.hasOwnProperty("segment")&&l.onnx.TensorProto.Segment.encode(t.segment,e.uint32(26).fork()).ldelim(),null!=t.floatData&&t.floatData.length){for(e.uint32(34).fork(),n=0;n<t.floatData.length;++n)e.float(t.floatData[n]);e.ldelim()}if(null!=t.int32Data&&t.int32Data.length){for(e.uint32(42).fork(),n=0;n<t.int32Data.length;++n)e.int32(t.int32Data[n]);e.ldelim()}if(null!=t.stringData&&t.stringData.length)for(n=0;n<t.stringData.length;++n)e.uint32(50).bytes(t.stringData[n]);if(null!=t.int64Data&&t.int64Data.length){for(e.uint32(58).fork(),n=0;n<t.int64Data.length;++n)e.int64(t.int64Data[n]);e.ldelim()}if(null!=t.name&&t.hasOwnProperty("name")&&e.uint32(66).string(t.name),null!=t.rawData&&t.hasOwnProperty("rawData")&&e.uint32(74).bytes(t.rawData),null!=t.doubleData&&t.doubleData.length){for(e.uint32(82).fork(),n=0;n<t.doubleData.length;++n)e.double(t.doubleData[n]);e.ldelim()}if(null!=t.uint64Data&&t.uint64Data.length){for(e.uint32(90).fork(),n=0;n<t.uint64Data.length;++n)e.uint64(t.uint64Data[n]);e.ldelim()}if(null!=t.docString&&t.hasOwnProperty("docString")&&e.uint32(98).string(t.docString),null!=t.externalData&&t.externalData.length)for(n=0;n<t.externalData.length;++n)l.onnx.StringStringEntryProto.encode(t.externalData[n],e.uint32(106).fork()).ldelim();return null!=t.dataLocation&&t.hasOwnProperty("dataLocation")&&e.uint32(112).int32(t.dataLocation),e},t.encodeDelimited=function(t,e){return this.encode(t,e).ldelim()},t.decode=function(t,e){t instanceof s||(t=s.create(t));for(var n=void 0===e?t.len:t.pos+e,r=new l.onnx.TensorProto;t.pos<n;){var i=t.uint32();switch(i>>>3){case 1:if(r.dims&&r.dims.length||(r.dims=[]),2==(7&i))for(var o=t.uint32()+t.pos;t.pos<o;)r.dims.push(t.int64());else r.dims.push(t.int64());break;case 2:r.dataType=t.int32();break;case 3:r.segment=l.onnx.TensorProto.Segment.decode(t,t.uint32());break;case 4:if(r.floatData&&r.floatData.length||(r.floatData=[]),2==(7&i))for(o=t.uint32()+t.pos;t.pos<o;)r.floatData.push(t.float());else r.floatData.push(t.float());break;case 5:if(r.int32Data&&r.int32Data.length||(r.int32Data=[]),2==(7&i))for(o=t.uint32()+t.pos;t.pos<o;)r.int32Data.push(t.int32());else r.int32Data.push(t.int32());break;case 6:r.stringData&&r.stringData.length||(r.stringData=[]),r.stringData.push(t.bytes());break;case 7:if(r.int64Data&&r.int64Data.length||(r.int64Data=[]),2==(7&i))for(o=t.uint32()+t.pos;t.pos<o;)r.int64Data.push(t.int64());else r.int64Data.push(t.int64());break;case 8:r.name=t.string();break;case 12:r.docString=t.string();break;case 9:r.rawData=t.bytes();break;case 13:r.externalData&&r.externalData.length||(r.externalData=[]),r.externalData.push(l.onnx.StringStringEntryProto.decode(t,t.uint32()));break;case 14:r.dataLocation=t.int32();break;case 10:if(r.doubleData&&r.doubleData.length||(r.doubleData=[]),2==(7&i))for(o=t.uint32()+t.pos;t.pos<o;)r.doubleData.push(t.double());else r.doubleData.push(t.double());break;case 11:if(r.uint64Data&&r.uint64Data.length||(r.uint64Data=[]),2==(7&i))for(o=t.uint32()+t.pos;t.pos<o;)r.uint64Data.push(t.uint64());else r.uint64Data.push(t.uint64());break;default:t.skipType(7&i)}}return r},t.decodeDelimited=function(t){return t instanceof s||(t=new s(t)),this.decode(t,t.uint32())},t.verify=function(t){if("object"!=typeof t||null===t)return"object expected";if(null!=t.dims&&t.hasOwnProperty("dims")){if(!Array.isArray(t.dims))return"dims: array expected";for(var e=0;e<t.dims.length;++e)if(!(c.isInteger(t.dims[e])||t.dims[e]&&c.isInteger(t.dims[e].low)&&c.isInteger(t.dims[e].high)))return"dims: integer|Long[] expected"}if(null!=t.dataType&&t.hasOwnProperty("dataType")&&!c.isInteger(t.dataType))return"dataType: integer expected";if(null!=t.segment&&t.hasOwnProperty("segment")&&(n=l.onnx.TensorProto.Segment.verify(t.segment)))return"segment."+n;if(null!=t.floatData&&t.hasOwnProperty("floatData")){if(!Array.isArray(t.floatData))return"floatData: array expected";for(e=0;e<t.floatData.length;++e)if("number"!=typeof t.floatData[e])return"floatData: number[] expected"}if(null!=t.int32Data&&t.hasOwnProperty("int32Data")){if(!Array.isArray(t.int32Data))return"int32Data: array expected";for(e=0;e<t.int32Data.length;++e)if(!c.isInteger(t.int32Data[e]))return"int32Data: integer[] expected"}if(null!=t.stringData&&t.hasOwnProperty("stringData")){if(!Array.isArray(t.stringData))return"stringData: array expected";for(e=0;e<t.stringData.length;++e)if(!(t.stringData[e]&&"number"==typeof t.stringData[e].length||c.isString(t.stringData[e])))return"stringData: buffer[] expected"}if(null!=t.int64Data&&t.hasOwnProperty("int64Data")){if(!Array.isArray(t.int64Data))return"int64Data: array expected";for(e=0;e<t.int64Data.length;++e)if(!(c.isInteger(t.int64Data[e])||t.int64Data[e]&&c.isInteger(t.int64Data[e].low)&&c.isInteger(t.int64Data[e].high)))return"int64Data: integer|Long[] expected"}if(null!=t.name&&t.hasOwnProperty("name")&&!c.isString(t.name))return"name: string expected";if(null!=t.docString&&t.hasOwnProperty("docString")&&!c.isString(t.docString))return"docString: string expected";if(null!=t.rawData&&t.hasOwnProperty("rawData")&&!(t.rawData&&"number"==typeof t.rawData.length||c.isString(t.rawData)))return"rawData: buffer expected";if(null!=t.externalData&&t.hasOwnProperty("externalData")){if(!Array.isArray(t.externalData))return"externalData: array expected";for(e=0;e<t.externalData.length;++e){var n;if(n=l.onnx.StringStringEntryProto.verify(t.externalData[e]))return"externalData."+n}}if(null!=t.dataLocation&&t.hasOwnProperty("dataLocation"))switch(t.dataLocation){default:return"dataLocation: enum value expected";case 0:case 1:}if(null!=t.doubleData&&t.hasOwnProperty("doubleData")){if(!Array.isArray(t.doubleData))return"doubleData: array expected";for(e=0;e<t.doubleData.length;++e)if("number"!=typeof t.doubleData[e])return"doubleData: number[] expected"}if(null!=t.uint64Data&&t.hasOwnProperty("uint64Data")){if(!Array.isArray(t.uint64Data))return"uint64Data: array expected";for(e=0;e<t.uint64Data.length;++e)if(!(c.isInteger(t.uint64Data[e])||t.uint64Data[e]&&c.isInteger(t.uint64Data[e].low)&&c.isInteger(t.uint64Data[e].high)))return"uint64Data: integer|Long[] expected"}return null},t.fromObject=function(t){if(t instanceof l.onnx.TensorProto)return t;var e=new l.onnx.TensorProto;if(t.dims){if(!Array.isArray(t.dims))throw TypeError(".onnx.TensorProto.dims: array expected");e.dims=[];for(var n=0;n<t.dims.length;++n)c.Long?(e.dims[n]=c.Long.fromValue(t.dims[n])).unsigned=!1:"string"==typeof t.dims[n]?e.dims[n]=parseInt(t.dims[n],10):"number"==typeof t.dims[n]?e.dims[n]=t.dims[n]:"object"==typeof t.dims[n]&&(e.dims[n]=new c.LongBits(t.dims[n].low>>>0,t.dims[n].high>>>0).toNumber())}if(null!=t.dataType&&(e.dataType=0|t.dataType),null!=t.segment){if("object"!=typeof t.segment)throw TypeError(".onnx.TensorProto.segment: object expected");e.segment=l.onnx.TensorProto.Segment.fromObject(t.segment)}if(t.floatData){if(!Array.isArray(t.floatData))throw TypeError(".onnx.TensorProto.floatData: array expected");for(e.floatData=[],n=0;n<t.floatData.length;++n)e.floatData[n]=Number(t.floatData[n])}if(t.int32Data){if(!Array.isArray(t.int32Data))throw TypeError(".onnx.TensorProto.int32Data: array expected");for(e.int32Data=[],n=0;n<t.int32Data.length;++n)e.int32Data[n]=0|t.int32Data[n]}if(t.stringData){if(!Array.isArray(t.stringData))throw TypeError(".onnx.TensorProto.stringData: array expected");for(e.stringData=[],n=0;n<t.stringData.length;++n)"string"==typeof t.stringData[n]?c.base64.decode(t.stringData[n],e.stringData[n]=c.newBuffer(c.base64.length(t.stringData[n])),0):t.stringData[n].length&&(e.stringData[n]=t.stringData[n])}if(t.int64Data){if(!Array.isArray(t.int64Data))throw TypeError(".onnx.TensorProto.int64Data: array expected");for(e.int64Data=[],n=0;n<t.int64Data.length;++n)c.Long?(e.int64Data[n]=c.Long.fromValue(t.int64Data[n])).unsigned=!1:"string"==typeof t.int64Data[n]?e.int64Data[n]=parseInt(t.int64Data[n],10):"number"==typeof t.int64Data[n]?e.int64Data[n]=t.int64Data[n]:"object"==typeof t.int64Data[n]&&(e.int64Data[n]=new c.LongBits(t.int64Data[n].low>>>0,t.int64Data[n].high>>>0).toNumber())}if(null!=t.name&&(e.name=String(t.name)),null!=t.docString&&(e.docString=String(t.docString)),null!=t.rawData&&("string"==typeof t.rawData?c.base64.decode(t.rawData,e.rawData=c.newBuffer(c.base64.length(t.rawData)),0):t.rawData.length&&(e.rawData=t.rawData)),t.externalData){if(!Array.isArray(t.externalData))throw TypeError(".onnx.TensorProto.externalData: array expected");for(e.externalData=[],n=0;n<t.externalData.length;++n){if("object"!=typeof t.externalData[n])throw TypeError(".onnx.TensorProto.externalData: object expected");e.externalData[n]=l.onnx.StringStringEntryProto.fromObject(t.externalData[n])}}switch(t.dataLocation){case"DEFAULT":case 0:e.dataLocation=0;break;case"EXTERNAL":case 1:e.dataLocation=1}if(t.doubleData){if(!Array.isArray(t.doubleData))throw TypeError(".onnx.TensorProto.doubleData: array expected");for(e.doubleData=[],n=0;n<t.doubleData.length;++n)e.doubleData[n]=Number(t.doubleData[n])}if(t.uint64Data){if(!Array.isArray(t.uint64Data))throw TypeError(".onnx.TensorProto.uint64Data: array expected");for(e.uint64Data=[],n=0;n<t.uint64Data.length;++n)c.Long?(e.uint64Data[n]=c.Long.fromValue(t.uint64Data[n])).unsigned=!0:"string"==typeof t.uint64Data[n]?e.uint64Data[n]=parseInt(t.uint64Data[n],10):"number"==typeof t.uint64Data[n]?e.uint64Data[n]=t.uint64Data[n]:"object"==typeof t.uint64Data[n]&&(e.uint64Data[n]=new c.LongBits(t.uint64Data[n].low>>>0,t.uint64Data[n].high>>>0).toNumber(!0))}return e},t.toObject=function(t,e){e||(e={});var n={};if((e.arrays||e.defaults)&&(n.dims=[],n.floatData=[],n.int32Data=[],n.stringData=[],n.int64Data=[],n.doubleData=[],n.uint64Data=[],n.externalData=[]),e.defaults&&(n.dataType=0,n.segment=null,n.name="",e.bytes===String?n.rawData="":(n.rawData=[],e.bytes!==Array&&(n.rawData=c.newBuffer(n.rawData))),n.docString="",n.dataLocation=e.enums===String?"DEFAULT":0),t.dims&&t.dims.length){n.dims=[];for(var r=0;r<t.dims.length;++r)"number"==typeof t.dims[r]?n.dims[r]=e.longs===String?String(t.dims[r]):t.dims[r]:n.dims[r]=e.longs===String?c.Long.prototype.toString.call(t.dims[r]):e.longs===Number?new c.LongBits(t.dims[r].low>>>0,t.dims[r].high>>>0).toNumber():t.dims[r]}if(null!=t.dataType&&t.hasOwnProperty("dataType")&&(n.dataType=t.dataType),null!=t.segment&&t.hasOwnProperty("segment")&&(n.segment=l.onnx.TensorProto.Segment.toObject(t.segment,e)),t.floatData&&t.floatData.length)for(n.floatData=[],r=0;r<t.floatData.length;++r)n.floatData[r]=e.json&&!isFinite(t.floatData[r])?String(t.floatData[r]):t.floatData[r];if(t.int32Data&&t.int32Data.length)for(n.int32Data=[],r=0;r<t.int32Data.length;++r)n.int32Data[r]=t.int32Data[r];if(t.stringData&&t.stringData.length)for(n.stringData=[],r=0;r<t.stringData.length;++r)n.stringData[r]=e.bytes===String?c.base64.encode(t.stringData[r],0,t.stringData[r].length):e.bytes===Array?Array.prototype.slice.call(t.stringData[r]):t.stringData[r];if(t.int64Data&&t.int64Data.length)for(n.int64Data=[],r=0;r<t.int64Data.length;++r)"number"==typeof t.int64Data[r]?n.int64Data[r]=e.longs===String?String(t.int64Data[r]):t.int64Data[r]:n.int64Data[r]=e.longs===String?c.Long.prototype.toString.call(t.int64Data[r]):e.longs===Number?new c.LongBits(t.int64Data[r].low>>>0,t.int64Data[r].high>>>0).toNumber():t.int64Data[r];if(null!=t.name&&t.hasOwnProperty("name")&&(n.name=t.name),null!=t.rawData&&t.hasOwnProperty("rawData")&&(n.rawData=e.bytes===String?c.base64.encode(t.rawData,0,t.rawData.length):e.bytes===Array?Array.prototype.slice.call(t.rawData):t.rawData),t.doubleData&&t.doubleData.length)for(n.doubleData=[],r=0;r<t.doubleData.length;++r)n.doubleData[r]=e.json&&!isFinite(t.doubleData[r])?String(t.doubleData[r]):t.doubleData[r];if(t.uint64Data&&t.uint64Data.length)for(n.uint64Data=[],r=0;r<t.uint64Data.length;++r)"number"==typeof t.uint64Data[r]?n.uint64Data[r]=e.longs===String?String(t.uint64Data[r]):t.uint64Data[r]:n.uint64Data[r]=e.longs===String?c.Long.prototype.toString.call(t.uint64Data[r]):e.longs===Number?new c.LongBits(t.uint64Data[r].low>>>0,t.uint64Data[r].high>>>0).toNumber(!0):t.uint64Data[r];if(null!=t.docString&&t.hasOwnProperty("docString")&&(n.docString=t.docString),t.externalData&&t.externalData.length)for(n.externalData=[],r=0;r<t.externalData.length;++r)n.externalData[r]=l.onnx.StringStringEntryProto.toObject(t.externalData[r],e);return null!=t.dataLocation&&t.hasOwnProperty("dataLocation")&&(n.dataLocation=e.enums===String?l.onnx.TensorProto.DataLocation[t.dataLocation]:t.dataLocation),n},t.prototype.toJSON=function(){return this.constructor.toObject(this,a.util.toJSONOptions)},t.DataType=function(){var t={},e=Object.create(t);return e[t[0]="UNDEFINED"]=0,e[t[1]="FLOAT"]=1,e[t[2]="UINT8"]=2,e[t[3]="INT8"]=3,e[t[4]="UINT16"]=4,e[t[5]="INT16"]=5,e[t[6]="INT32"]=6,e[t[7]="INT64"]=7,e[t[8]="STRING"]=8,e[t[9]="BOOL"]=9,e[t[10]="FLOAT16"]=10,e[t[11]="DOUBLE"]=11,e[t[12]="UINT32"]=12,e[t[13]="UINT64"]=13,e[t[14]="COMPLEX64"]=14,e[t[15]="COMPLEX128"]=15,e[t[16]="BFLOAT16"]=16,e}(),t.Segment=function(){function t(t){if(t)for(var e=Object.keys(t),n=0;n<e.length;++n)null!=t[e[n]]&&(this[e[n]]=t[e[n]])}return t.prototype.begin=c.Long?c.Long.fromBits(0,0,!1):0,t.prototype.end=c.Long?c.Long.fromBits(0,0,!1):0,t.create=function(e){return new t(e)},t.encode=function(t,e){return e||(e=u.create()),null!=t.begin&&t.hasOwnProperty("begin")&&e.uint32(8).int64(t.begin),null!=t.end&&t.hasOwnProperty("end")&&e.uint32(16).int64(t.end),e},t.encodeDelimited=function(t,e){return this.encode(t,e).ldelim()},t.decode=function(t,e){t instanceof s||(t=s.create(t));for(var n=void 0===e?t.len:t.pos+e,r=new l.onnx.TensorProto.Segment;t.pos<n;){var i=t.uint32();switch(i>>>3){case 1:r.begin=t.int64();break;case 2:r.end=t.int64();break;default:t.skipType(7&i)}}return r},t.decodeDelimited=function(t){return t instanceof s||(t=new s(t)),this.decode(t,t.uint32())},t.verify=function(t){return"object"!=typeof t||null===t?"object expected":null!=t.begin&&t.hasOwnProperty("begin")&&!(c.isInteger(t.begin)||t.begin&&c.isInteger(t.begin.low)&&c.isInteger(t.begin.high))?"begin: integer|Long expected":null!=t.end&&t.hasOwnProperty("end")&&!(c.isInteger(t.end)||t.end&&c.isInteger(t.end.low)&&c.isInteger(t.end.high))?"end: integer|Long expected":null},t.fromObject=function(t){if(t instanceof l.onnx.TensorProto.Segment)return t;var e=new l.onnx.TensorProto.Segment;return null!=t.begin&&(c.Long?(e.begin=c.Long.fromValue(t.begin)).unsigned=!1:"string"==typeof t.begin?e.begin=parseInt(t.begin,10):"number"==typeof t.begin?e.begin=t.begin:"object"==typeof t.begin&&(e.begin=new c.LongBits(t.begin.low>>>0,t.begin.high>>>0).toNumber())),null!=t.end&&(c.Long?(e.end=c.Long.fromValue(t.end)).unsigned=!1:"string"==typeof t.end?e.end=parseInt(t.end,10):"number"==typeof t.end?e.end=t.end:"object"==typeof t.end&&(e.end=new c.LongBits(t.end.low>>>0,t.end.high>>>0).toNumber())),e},t.toObject=function(t,e){e||(e={});var n={};if(e.defaults){if(c.Long){var r=new c.Long(0,0,!1);n.begin=e.longs===String?r.toString():e.longs===Number?r.toNumber():r}else n.begin=e.longs===String?"0":0;c.Long?(r=new c.Long(0,0,!1),n.end=e.longs===String?r.toString():e.longs===Number?r.toNumber():r):n.end=e.longs===String?"0":0}return null!=t.begin&&t.hasOwnProperty("begin")&&("number"==typeof t.begin?n.begin=e.longs===String?String(t.begin):t.begin:n.begin=e.longs===String?c.Long.prototype.toString.call(t.begin):e.longs===Number?new c.LongBits(t.begin.low>>>0,t.begin.high>>>0).toNumber():t.begin),null!=t.end&&t.hasOwnProperty("end")&&("number"==typeof t.end?n.end=e.longs===String?String(t.end):t.end:n.end=e.longs===String?c.Long.prototype.toString.call(t.end):e.longs===Number?new c.LongBits(t.end.low>>>0,t.end.high>>>0).toNumber():t.end),n},t.prototype.toJSON=function(){return this.constructor.toObject(this,a.util.toJSONOptions)},t}(),t.DataLocation=function(){var t={},e=Object.create(t);return e[t[0]="DEFAULT"]=0,e[t[1]="EXTERNAL"]=1,e}(),t}(),o.TensorShapeProto=function(){function t(t){if(this.dim=[],t)for(var e=Object.keys(t),n=0;n<e.length;++n)null!=t[e[n]]&&(this[e[n]]=t[e[n]])}return t.prototype.dim=c.emptyArray,t.create=function(e){return new t(e)},t.encode=function(t,e){if(e||(e=u.create()),null!=t.dim&&t.dim.length)for(var n=0;n<t.dim.length;++n)l.onnx.TensorShapeProto.Dimension.encode(t.dim[n],e.uint32(10).fork()).ldelim();return e},t.encodeDelimited=function(t,e){return this.encode(t,e).ldelim()},t.decode=function(t,e){t instanceof s||(t=s.create(t));for(var n=void 0===e?t.len:t.pos+e,r=new l.onnx.TensorShapeProto;t.pos<n;){var i=t.uint32();i>>>3==1?(r.dim&&r.dim.length||(r.dim=[]),r.dim.push(l.onnx.TensorShapeProto.Dimension.decode(t,t.uint32()))):t.skipType(7&i)}return r},t.decodeDelimited=function(t){return t instanceof s||(t=new s(t)),this.decode(t,t.uint32())},t.verify=function(t){if("object"!=typeof t||null===t)return"object expected";if(null!=t.dim&&t.hasOwnProperty("dim")){if(!Array.isArray(t.dim))return"dim: array expected";for(var e=0;e<t.dim.length;++e){var n=l.onnx.TensorShapeProto.Dimension.verify(t.dim[e]);if(n)return"dim."+n}}return null},t.fromObject=function(t){if(t instanceof l.onnx.TensorShapeProto)return t;var e=new l.onnx.TensorShapeProto;if(t.dim){if(!Array.isArray(t.dim))throw TypeError(".onnx.TensorShapeProto.dim: array expected");e.dim=[];for(var n=0;n<t.dim.length;++n){if("object"!=typeof t.dim[n])throw TypeError(".onnx.TensorShapeProto.dim: object expected");e.dim[n]=l.onnx.TensorShapeProto.Dimension.fromObject(t.dim[n])}}return e},t.toObject=function(t,e){e||(e={});var n={};if((e.arrays||e.defaults)&&(n.dim=[]),t.dim&&t.dim.length){n.dim=[];for(var r=0;r<t.dim.length;++r)n.dim[r]=l.onnx.TensorShapeProto.Dimension.toObject(t.dim[r],e)}return n},t.prototype.toJSON=function(){return this.constructor.toObject(this,a.util.toJSONOptions)},t.Dimension=function(){function t(t){if(t)for(var e=Object.keys(t),n=0;n<e.length;++n)null!=t[e[n]]&&(this[e[n]]=t[e[n]])}var e;return t.prototype.dimValue=c.Long?c.Long.fromBits(0,0,!1):0,t.prototype.dimParam="",t.prototype.denotation="",Object.defineProperty(t.prototype,"value",{get:c.oneOfGetter(e=["dimValue","dimParam"]),set:c.oneOfSetter(e)}),t.create=function(e){return new t(e)},t.encode=function(t,e){return e||(e=u.create()),null!=t.dimValue&&t.hasOwnProperty("dimValue")&&e.uint32(8).int64(t.dimValue),null!=t.dimParam&&t.hasOwnProperty("dimParam")&&e.uint32(18).string(t.dimParam),null!=t.denotation&&t.hasOwnProperty("denotation")&&e.uint32(26).string(t.denotation),e},t.encodeDelimited=function(t,e){return this.encode(t,e).ldelim()},t.decode=function(t,e){t instanceof s||(t=s.create(t));for(var n=void 0===e?t.len:t.pos+e,r=new l.onnx.TensorShapeProto.Dimension;t.pos<n;){var i=t.uint32();switch(i>>>3){case 1:r.dimValue=t.int64();break;case 2:r.dimParam=t.string();break;case 3:r.denotation=t.string();break;default:t.skipType(7&i)}}return r},t.decodeDelimited=function(t){return t instanceof s||(t=new s(t)),this.decode(t,t.uint32())},t.verify=function(t){if("object"!=typeof t||null===t)return"object expected";var e={};if(null!=t.dimValue&&t.hasOwnProperty("dimValue")&&(e.value=1,!(c.isInteger(t.dimValue)||t.dimValue&&c.isInteger(t.dimValue.low)&&c.isInteger(t.dimValue.high))))return"dimValue: integer|Long expected";if(null!=t.dimParam&&t.hasOwnProperty("dimParam")){if(1===e.value)return"value: multiple values";if(e.value=1,!c.isString(t.dimParam))return"dimParam: string expected"}return null!=t.denotation&&t.hasOwnProperty("denotation")&&!c.isString(t.denotation)?"denotation: string expected":null},t.fromObject=function(t){if(t instanceof l.onnx.TensorShapeProto.Dimension)return t;var e=new l.onnx.TensorShapeProto.Dimension;return null!=t.dimValue&&(c.Long?(e.dimValue=c.Long.fromValue(t.dimValue)).unsigned=!1:"string"==typeof t.dimValue?e.dimValue=parseInt(t.dimValue,10):"number"==typeof t.dimValue?e.dimValue=t.dimValue:"object"==typeof t.dimValue&&(e.dimValue=new c.LongBits(t.dimValue.low>>>0,t.dimValue.high>>>0).toNumber())),null!=t.dimParam&&(e.dimParam=String(t.dimParam)),null!=t.denotation&&(e.denotation=String(t.denotation)),e},t.toObject=function(t,e){e||(e={});var n={};return e.defaults&&(n.denotation=""),null!=t.dimValue&&t.hasOwnProperty("dimValue")&&("number"==typeof t.dimValue?n.dimValue=e.longs===String?String(t.dimValue):t.dimValue:n.dimValue=e.longs===String?c.Long.prototype.toString.call(t.dimValue):e.longs===Number?new c.LongBits(t.dimValue.low>>>0,t.dimValue.high>>>0).toNumber():t.dimValue,e.oneofs&&(n.value="dimValue")),null!=t.dimParam&&t.hasOwnProperty("dimParam")&&(n.dimParam=t.dimParam,e.oneofs&&(n.value="dimParam")),null!=t.denotation&&t.hasOwnProperty("denotation")&&(n.denotation=t.denotation),n},t.prototype.toJSON=function(){return this.constructor.toObject(this,a.util.toJSONOptions)},t}(),t}(),o.TypeProto=function(){function t(t){if(t)for(var e=Object.keys(t),n=0;n<e.length;++n)null!=t[e[n]]&&(this[e[n]]=t[e[n]])}var e;return t.prototype.tensorType=null,t.prototype.denotation="",Object.defineProperty(t.prototype,"value",{get:c.oneOfGetter(e=["tensorType"]),set:c.oneOfSetter(e)}),t.create=function(e){return new t(e)},t.encode=function(t,e){return e||(e=u.create()),null!=t.tensorType&&t.hasOwnProperty("tensorType")&&l.onnx.TypeProto.Tensor.encode(t.tensorType,e.uint32(10).fork()).ldelim(),null!=t.denotation&&t.hasOwnProperty("denotation")&&e.uint32(50).string(t.denotation),e},t.encodeDelimited=function(t,e){return this.encode(t,e).ldelim()},t.decode=function(t,e){t instanceof s||(t=s.create(t));for(var n=void 0===e?t.len:t.pos+e,r=new l.onnx.TypeProto;t.pos<n;){var i=t.uint32();switch(i>>>3){case 1:r.tensorType=l.onnx.TypeProto.Tensor.decode(t,t.uint32());break;case 6:r.denotation=t.string();break;default:t.skipType(7&i)}}return r},t.decodeDelimited=function(t){return t instanceof s||(t=new s(t)),this.decode(t,t.uint32())},t.verify=function(t){if("object"!=typeof t||null===t)return"object expected";if(null!=t.tensorType&&t.hasOwnProperty("tensorType")){var e=l.onnx.TypeProto.Tensor.verify(t.tensorType);if(e)return"tensorType."+e}return null!=t.denotation&&t.hasOwnProperty("denotation")&&!c.isString(t.denotation)?"denotation: string expected":null},t.fromObject=function(t){if(t instanceof l.onnx.TypeProto)return t;var e=new l.onnx.TypeProto;if(null!=t.tensorType){if("object"!=typeof t.tensorType)throw TypeError(".onnx.TypeProto.tensorType: object expected");e.tensorType=l.onnx.TypeProto.Tensor.fromObject(t.tensorType)}return null!=t.denotation&&(e.denotation=String(t.denotation)),e},t.toObject=function(t,e){e||(e={});var n={};return e.defaults&&(n.denotation=""),null!=t.tensorType&&t.hasOwnProperty("tensorType")&&(n.tensorType=l.onnx.TypeProto.Tensor.toObject(t.tensorType,e),e.oneofs&&(n.value="tensorType")),null!=t.denotation&&t.hasOwnProperty("denotation")&&(n.denotation=t.denotation),n},t.prototype.toJSON=function(){return this.constructor.toObject(this,a.util.toJSONOptions)},t.Tensor=function(){function t(t){if(t)for(var e=Object.keys(t),n=0;n<e.length;++n)null!=t[e[n]]&&(this[e[n]]=t[e[n]])}return t.prototype.elemType=0,t.prototype.shape=null,t.create=function(e){return new t(e)},t.encode=function(t,e){return e||(e=u.create()),null!=t.elemType&&t.hasOwnProperty("elemType")&&e.uint32(8).int32(t.elemType),null!=t.shape&&t.hasOwnProperty("shape")&&l.onnx.TensorShapeProto.encode(t.shape,e.uint32(18).fork()).ldelim(),e},t.encodeDelimited=function(t,e){return this.encode(t,e).ldelim()},t.decode=function(t,e){t instanceof s||(t=s.create(t));for(var n=void 0===e?t.len:t.pos+e,r=new l.onnx.TypeProto.Tensor;t.pos<n;){var i=t.uint32();switch(i>>>3){case 1:r.elemType=t.int32();break;case 2:r.shape=l.onnx.TensorShapeProto.decode(t,t.uint32());break;default:t.skipType(7&i)}}return r},t.decodeDelimited=function(t){return t instanceof s||(t=new s(t)),this.decode(t,t.uint32())},t.verify=function(t){if("object"!=typeof t||null===t)return"object expected";if(null!=t.elemType&&t.hasOwnProperty("elemType")&&!c.isInteger(t.elemType))return"elemType: integer expected";if(null!=t.shape&&t.hasOwnProperty("shape")){var e=l.onnx.TensorShapeProto.verify(t.shape);if(e)return"shape."+e}return null},t.fromObject=function(t){if(t instanceof l.onnx.TypeProto.Tensor)return t;var e=new l.onnx.TypeProto.Tensor;if(null!=t.elemType&&(e.elemType=0|t.elemType),null!=t.shape){if("object"!=typeof t.shape)throw TypeError(".onnx.TypeProto.Tensor.shape: object expected");e.shape=l.onnx.TensorShapeProto.fromObject(t.shape)}return e},t.toObject=function(t,e){e||(e={});var n={};return e.defaults&&(n.elemType=0,n.shape=null),null!=t.elemType&&t.hasOwnProperty("elemType")&&(n.elemType=t.elemType),null!=t.shape&&t.hasOwnProperty("shape")&&(n.shape=l.onnx.TensorShapeProto.toObject(t.shape,e)),n},t.prototype.toJSON=function(){return this.constructor.toObject(this,a.util.toJSONOptions)},t}(),t}(),o.OperatorSetIdProto=function(){function t(t){if(t)for(var e=Object.keys(t),n=0;n<e.length;++n)null!=t[e[n]]&&(this[e[n]]=t[e[n]])}return t.prototype.domain="",t.prototype.version=c.Long?c.Long.fromBits(0,0,!1):0,t.create=function(e){return new t(e)},t.encode=function(t,e){return e||(e=u.create()),null!=t.domain&&t.hasOwnProperty("domain")&&e.uint32(10).string(t.domain),null!=t.version&&t.hasOwnProperty("version")&&e.uint32(16).int64(t.version),e},t.encodeDelimited=function(t,e){return this.encode(t,e).ldelim()},t.decode=function(t,e){t instanceof s||(t=s.create(t));for(var n=void 0===e?t.len:t.pos+e,r=new l.onnx.OperatorSetIdProto;t.pos<n;){var i=t.uint32();switch(i>>>3){case 1:r.domain=t.string();break;case 2:r.version=t.int64();break;default:t.skipType(7&i)}}return r},t.decodeDelimited=function(t){return t instanceof s||(t=new s(t)),this.decode(t,t.uint32())},t.verify=function(t){return"object"!=typeof t||null===t?"object expected":null!=t.domain&&t.hasOwnProperty("domain")&&!c.isString(t.domain)?"domain: string expected":null!=t.version&&t.hasOwnProperty("version")&&!(c.isInteger(t.version)||t.version&&c.isInteger(t.version.low)&&c.isInteger(t.version.high))?"version: integer|Long expected":null},t.fromObject=function(t){if(t instanceof l.onnx.OperatorSetIdProto)return t;var e=new l.onnx.OperatorSetIdProto;return null!=t.domain&&(e.domain=String(t.domain)),null!=t.version&&(c.Long?(e.version=c.Long.fromValue(t.version)).unsigned=!1:"string"==typeof t.version?e.version=parseInt(t.version,10):"number"==typeof t.version?e.version=t.version:"object"==typeof t.version&&(e.version=new c.LongBits(t.version.low>>>0,t.version.high>>>0).toNumber())),e},t.toObject=function(t,e){e||(e={});var n={};if(e.defaults)if(n.domain="",c.Long){var r=new c.Long(0,0,!1);n.version=e.longs===String?r.toString():e.longs===Number?r.toNumber():r}else n.version=e.longs===String?"0":0;return null!=t.domain&&t.hasOwnProperty("domain")&&(n.domain=t.domain),null!=t.version&&t.hasOwnProperty("version")&&("number"==typeof t.version?n.version=e.longs===String?String(t.version):t.version:n.version=e.longs===String?c.Long.prototype.toString.call(t.version):e.longs===Number?new c.LongBits(t.version.low>>>0,t.version.high>>>0).toNumber():t.version),n},t.prototype.toJSON=function(){return this.constructor.toObject(this,a.util.toJSONOptions)},t}(),o),t.exports=l},2100:(t,e,n)=>{"use strict";t.exports=n(9482)},9482:(t,e,n)=>{"use strict";var r=e;function i(){r.util._configure(),r.Writer._configure(r.BufferWriter),r.Reader._configure(r.BufferReader)}r.build="minimal",r.Writer=n(1173),r.BufferWriter=n(3155),r.Reader=n(1408),r.BufferReader=n(593),r.util=n(9693),r.rpc=n(5994),r.roots=n(5054),r.configure=i,i()},1408:(t,e,n)=>{"use strict";t.exports=u;var r,i=n(9693),o=i.LongBits,a=i.utf8;function s(t,e){return RangeError("index out of range: "+t.pos+" + "+(e||1)+" > "+t.len)}function u(t){this.buf=t,this.pos=0,this.len=t.length}var c,l="undefined"!=typeof Uint8Array?function(t){if(t instanceof Uint8Array||Array.isArray(t))return new u(t);throw Error("illegal buffer")}:function(t){if(Array.isArray(t))return new u(t);throw Error("illegal buffer")},p=function(){return i.Buffer?function(t){return(u.create=function(t){return i.Buffer.isBuffer(t)?new r(t):l(t)})(t)}:l};function f(){var t=new o(0,0),e=0;if(!(this.len-this.pos>4)){for(;e<3;++e){if(this.pos>=this.len)throw s(this);if(t.lo=(t.lo|(127&this.buf[this.pos])<<7*e)>>>0,this.buf[this.pos++]<128)return t}return t.lo=(t.lo|(127&this.buf[this.pos++])<<7*e)>>>0,t}for(;e<4;++e)if(t.lo=(t.lo|(127&this.buf[this.pos])<<7*e)>>>0,this.buf[this.pos++]<128)return t;if(t.lo=(t.lo|(127&this.buf[this.pos])<<28)>>>0,t.hi=(t.hi|(127&this.buf[this.pos])>>4)>>>0,this.buf[this.pos++]<128)return t;if(e=0,this.len-this.pos>4){for(;e<5;++e)if(t.hi=(t.hi|(127&this.buf[this.pos])<<7*e+3)>>>0,this.buf[this.pos++]<128)return t}else for(;e<5;++e){if(this.pos>=this.len)throw s(this);if(t.hi=(t.hi|(127&this.buf[this.pos])<<7*e+3)>>>0,this.buf[this.pos++]<128)return t}throw Error("invalid varint encoding")}function d(t,e){return(t[e-4]|t[e-3]<<8|t[e-2]<<16|t[e-1]<<24)>>>0}function h(){if(this.pos+8>this.len)throw s(this,8);return new o(d(this.buf,this.pos+=4),d(this.buf,this.pos+=4))}u.create=p(),u.prototype._slice=i.Array.prototype.subarray||i.Array.prototype.slice,u.prototype.uint32=(c=4294967295,function(){if(c=(127&this.buf[this.pos])>>>0,this.buf[this.pos++]<128)return c;if(c=(c|(127&this.buf[this.pos])<<7)>>>0,this.buf[this.pos++]<128)return c;if(c=(c|(127&this.buf[this.pos])<<14)>>>0,this.buf[this.pos++]<128)return c;if(c=(c|(127&this.buf[this.pos])<<21)>>>0,this.buf[this.pos++]<128)return c;if(c=(c|(15&this.buf[this.pos])<<28)>>>0,this.buf[this.pos++]<128)return c;if((this.pos+=5)>this.len)throw this.pos=this.len,s(this,10);return c}),u.prototype.int32=function(){return 0|this.uint32()},u.prototype.sint32=function(){var t=this.uint32();return t>>>1^-(1&t)|0},u.prototype.bool=function(){return 0!==this.uint32()},u.prototype.fixed32=function(){if(this.pos+4>this.len)throw s(this,4);return d(this.buf,this.pos+=4)},u.prototype.sfixed32=function(){if(this.pos+4>this.len)throw s(this,4);return 0|d(this.buf,this.pos+=4)},u.prototype.float=function(){if(this.pos+4>this.len)throw s(this,4);var t=i.float.readFloatLE(this.buf,this.pos);return this.pos+=4,t},u.prototype.double=function(){if(this.pos+8>this.len)throw s(this,4);var t=i.float.readDoubleLE(this.buf,this.pos);return this.pos+=8,t},u.prototype.bytes=function(){var t=this.uint32(),e=this.pos,n=this.pos+t;if(n>this.len)throw s(this,t);return this.pos+=t,Array.isArray(this.buf)?this.buf.slice(e,n):e===n?new this.buf.constructor(0):this._slice.call(this.buf,e,n)},u.prototype.string=function(){var t=this.bytes();return a.read(t,0,t.length)},u.prototype.skip=function(t){if("number"==typeof t){if(this.pos+t>this.len)throw s(this,t);this.pos+=t}else do{if(this.pos>=this.len)throw s(this)}while(128&this.buf[this.pos++]);return this},u.prototype.skipType=function(t){switch(t){case 0:this.skip();break;case 1:this.skip(8);break;case 2:this.skip(this.uint32());break;case 3:for(;4!=(t=7&this.uint32());)this.skipType(t);break;case 5:this.skip(4);break;default:throw Error("invalid wire type "+t+" at offset "+this.pos)}return this},u._configure=function(t){r=t,u.create=p(),r._configure();var e=i.Long?"toLong":"toNumber";i.merge(u.prototype,{int64:function(){return f.call(this)[e](!1)},uint64:function(){return f.call(this)[e](!0)},sint64:function(){return f.call(this).zzDecode()[e](!1)},fixed64:function(){return h.call(this)[e](!0)},sfixed64:function(){return h.call(this)[e](!1)}})}},593:(t,e,n)=>{"use strict";t.exports=o;var r=n(1408);(o.prototype=Object.create(r.prototype)).constructor=o;var i=n(9693);function o(t){r.call(this,t)}o._configure=function(){i.Buffer&&(o.prototype._slice=i.Buffer.prototype.slice)},o.prototype.string=function(){var t=this.uint32();return this.buf.utf8Slice?this.buf.utf8Slice(this.pos,this.pos=Math.min(this.pos+t,this.len)):this.buf.toString("utf-8",this.pos,this.pos=Math.min(this.pos+t,this.len))},o._configure()},5054:t=>{"use strict";t.exports={}},5994:(t,e,n)=>{"use strict";e.Service=n(7948)},7948:(t,e,n)=>{"use strict";t.exports=i;var r=n(9693);function i(t,e,n){if("function"!=typeof t)throw TypeError("rpcImpl must be a function");r.EventEmitter.call(this),this.rpcImpl=t,this.requestDelimited=Boolean(e),this.responseDelimited=Boolean(n)}(i.prototype=Object.create(r.EventEmitter.prototype)).constructor=i,i.prototype.rpcCall=function t(e,n,i,o,a){if(!o)throw TypeError("request must be specified");var s=this;if(!a)return r.asPromise(t,s,e,n,i,o);if(s.rpcImpl)try{return s.rpcImpl(e,n[s.requestDelimited?"encodeDelimited":"encode"](o).finish(),(function(t,n){if(t)return s.emit("error",t,e),a(t);if(null!==n){if(!(n instanceof i))try{n=i[s.responseDelimited?"decodeDelimited":"decode"](n)}catch(t){return s.emit("error",t,e),a(t)}return s.emit("data",n,e),a(null,n)}s.end(!0)}))}catch(t){return s.emit("error",t,e),void setTimeout((function(){a(t)}),0)}else setTimeout((function(){a(Error("already ended"))}),0)},i.prototype.end=function(t){return this.rpcImpl&&(t||this.rpcImpl(null,null,null),this.rpcImpl=null,this.emit("end").off()),this}},1945:(t,e,n)=>{"use strict";t.exports=i;var r=n(9693);function i(t,e){this.lo=t>>>0,this.hi=e>>>0}var o=i.zero=new i(0,0);o.toNumber=function(){return 0},o.zzEncode=o.zzDecode=function(){return this},o.length=function(){return 1};var a=i.zeroHash="\0\0\0\0\0\0\0\0";i.fromNumber=function(t){if(0===t)return o;var e=t<0;e&&(t=-t);var n=t>>>0,r=(t-n)/4294967296>>>0;return e&&(r=~r>>>0,n=~n>>>0,++n>4294967295&&(n=0,++r>4294967295&&(r=0))),new i(n,r)},i.from=function(t){if("number"==typeof t)return i.fromNumber(t);if(r.isString(t)){if(!r.Long)return i.fromNumber(parseInt(t,10));t=r.Long.fromString(t)}return t.low||t.high?new i(t.low>>>0,t.high>>>0):o},i.prototype.toNumber=function(t){if(!t&&this.hi>>>31){var e=1+~this.lo>>>0,n=~this.hi>>>0;return e||(n=n+1>>>0),-(e+4294967296*n)}return this.lo+4294967296*this.hi},i.prototype.toLong=function(t){return r.Long?new r.Long(0|this.lo,0|this.hi,Boolean(t)):{low:0|this.lo,high:0|this.hi,unsigned:Boolean(t)}};var s=String.prototype.charCodeAt;i.fromHash=function(t){return t===a?o:new i((s.call(t,0)|s.call(t,1)<<8|s.call(t,2)<<16|s.call(t,3)<<24)>>>0,(s.call(t,4)|s.call(t,5)<<8|s.call(t,6)<<16|s.call(t,7)<<24)>>>0)},i.prototype.toHash=function(){return String.fromCharCode(255&this.lo,this.lo>>>8&255,this.lo>>>16&255,this.lo>>>24,255&this.hi,this.hi>>>8&255,this.hi>>>16&255,this.hi>>>24)},i.prototype.zzEncode=function(){var t=this.hi>>31;return this.hi=((this.hi<<1|this.lo>>>31)^t)>>>0,this.lo=(this.lo<<1^t)>>>0,this},i.prototype.zzDecode=function(){var t=-(1&this.lo);return this.lo=((this.lo>>>1|this.hi<<31)^t)>>>0,this.hi=(this.hi>>>1^t)>>>0,this},i.prototype.length=function(){var t=this.lo,e=(this.lo>>>28|this.hi<<4)>>>0,n=this.hi>>>24;return 0===n?0===e?t<16384?t<128?1:2:t<2097152?3:4:e<16384?e<128?5:6:e<2097152?7:8:n<128?9:10}},9693:function(t,e,n){"use strict";var r=e;function i(t,e,n){for(var r=Object.keys(e),i=0;i<r.length;++i)void 0!==t[r[i]]&&n||(t[r[i]]=e[r[i]]);return t}function o(t){function e(t,n){if(!(this instanceof e))return new e(t,n);Object.defineProperty(this,"message",{get:function(){return t}}),Error.captureStackTrace?Error.captureStackTrace(this,e):Object.defineProperty(this,"stack",{value:(new Error).stack||""}),n&&i(this,n)}return(e.prototype=Object.create(Error.prototype)).constructor=e,Object.defineProperty(e.prototype,"name",{get:function(){return t}}),e.prototype.toString=function(){return this.name+": "+this.message},e}r.asPromise=n(4537),r.base64=n(7419),r.EventEmitter=n(9211),r.float=n(945),r.inquire=n(7199),r.utf8=n(4997),r.pool=n(6662),r.LongBits=n(1945),r.isNode=Boolean(void 0!==n.g&&n.g&&n.g.process&&n.g.process.versions&&n.g.process.versions.node),r.global=r.isNode&&n.g||"undefined"!=typeof window&&window||"undefined"!=typeof self&&self||this,r.emptyArray=Object.freeze?Object.freeze([]):[],r.emptyObject=Object.freeze?Object.freeze({}):{},r.isInteger=Number.isInteger||function(t){return"number"==typeof t&&isFinite(t)&&Math.floor(t)===t},r.isString=function(t){return"string"==typeof t||t instanceof String},r.isObject=function(t){return t&&"object"==typeof t},r.isset=r.isSet=function(t,e){var n=t[e];return!(null==n||!t.hasOwnProperty(e))&&("object"!=typeof n||(Array.isArray(n)?n.length:Object.keys(n).length)>0)},r.Buffer=function(){try{var t=r.inquire("buffer").Buffer;return t.prototype.utf8Write?t:null}catch(t){return null}}(),r._Buffer_from=null,r._Buffer_allocUnsafe=null,r.newBuffer=function(t){return"number"==typeof t?r.Buffer?r._Buffer_allocUnsafe(t):new r.Array(t):r.Buffer?r._Buffer_from(t):"undefined"==typeof Uint8Array?t:new Uint8Array(t)},r.Array="undefined"!=typeof Uint8Array?Uint8Array:Array,r.Long=r.global.dcodeIO&&r.global.dcodeIO.Long||r.global.Long||r.inquire("long"),r.key2Re=/^true|false|0|1$/,r.key32Re=/^-?(?:0|[1-9][0-9]*)$/,r.key64Re=/^(?:[\\x00-\\xff]{8}|-?(?:0|[1-9][0-9]*))$/,r.longToHash=function(t){return t?r.LongBits.from(t).toHash():r.LongBits.zeroHash},r.longFromHash=function(t,e){var n=r.LongBits.fromHash(t);return r.Long?r.Long.fromBits(n.lo,n.hi,e):n.toNumber(Boolean(e))},r.merge=i,r.lcFirst=function(t){return t.charAt(0).toLowerCase()+t.substring(1)},r.newError=o,r.ProtocolError=o("ProtocolError"),r.oneOfGetter=function(t){for(var e={},n=0;n<t.length;++n)e[t[n]]=1;return function(){for(var t=Object.keys(this),n=t.length-1;n>-1;--n)if(1===e[t[n]]&&void 0!==this[t[n]]&&null!==this[t[n]])return t[n]}},r.oneOfSetter=function(t){return function(e){for(var n=0;n<t.length;++n)t[n]!==e&&delete this[t[n]]}},r.toJSONOptions={longs:String,enums:String,bytes:String,json:!0},r._configure=function(){var t=r.Buffer;t?(r._Buffer_from=t.from!==Uint8Array.from&&t.from||function(e,n){return new t(e,n)},r._Buffer_allocUnsafe=t.allocUnsafe||function(e){return new t(e)}):r._Buffer_from=r._Buffer_allocUnsafe=null}},1173:(t,e,n)=>{"use strict";t.exports=p;var r,i=n(9693),o=i.LongBits,a=i.base64,s=i.utf8;function u(t,e,n){this.fn=t,this.len=e,this.next=void 0,this.val=n}function c(){}function l(t){this.head=t.head,this.tail=t.tail,this.len=t.len,this.next=t.states}function p(){this.len=0,this.head=new u(c,0,0),this.tail=this.head,this.states=null}var f=function(){return i.Buffer?function(){return(p.create=function(){return new r})()}:function(){return new p}};function d(t,e,n){e[n]=255&t}function h(t,e){this.len=t,this.next=void 0,this.val=e}function g(t,e,n){for(;t.hi;)e[n++]=127&t.lo|128,t.lo=(t.lo>>>7|t.hi<<25)>>>0,t.hi>>>=7;for(;t.lo>127;)e[n++]=127&t.lo|128,t.lo=t.lo>>>7;e[n++]=t.lo}function b(t,e,n){e[n]=255&t,e[n+1]=t>>>8&255,e[n+2]=t>>>16&255,e[n+3]=t>>>24}p.create=f(),p.alloc=function(t){return new i.Array(t)},i.Array!==Array&&(p.alloc=i.pool(p.alloc,i.Array.prototype.subarray)),p.prototype._push=function(t,e,n){return this.tail=this.tail.next=new u(t,e,n),this.len+=e,this},h.prototype=Object.create(u.prototype),h.prototype.fn=function(t,e,n){for(;t>127;)e[n++]=127&t|128,t>>>=7;e[n]=t},p.prototype.uint32=function(t){return this.len+=(this.tail=this.tail.next=new h((t>>>=0)<128?1:t<16384?2:t<2097152?3:t<268435456?4:5,t)).len,this},p.prototype.int32=function(t){return t<0?this._push(g,10,o.fromNumber(t)):this.uint32(t)},p.prototype.sint32=function(t){return this.uint32((t<<1^t>>31)>>>0)},p.prototype.uint64=function(t){var e=o.from(t);return this._push(g,e.length(),e)},p.prototype.int64=p.prototype.uint64,p.prototype.sint64=function(t){var e=o.from(t).zzEncode();return this._push(g,e.length(),e)},p.prototype.bool=function(t){return this._push(d,1,t?1:0)},p.prototype.fixed32=function(t){return this._push(b,4,t>>>0)},p.prototype.sfixed32=p.prototype.fixed32,p.prototype.fixed64=function(t){var e=o.from(t);return this._push(b,4,e.lo)._push(b,4,e.hi)},p.prototype.sfixed64=p.prototype.fixed64,p.prototype.float=function(t){return this._push(i.float.writeFloatLE,4,t)},p.prototype.double=function(t){return this._push(i.float.writeDoubleLE,8,t)};var m=i.Array.prototype.set?function(t,e,n){e.set(t,n)}:function(t,e,n){for(var r=0;r<t.length;++r)e[n+r]=t[r]};p.prototype.bytes=function(t){var e=t.length>>>0;if(!e)return this._push(d,1,0);if(i.isString(t)){var n=p.alloc(e=a.length(t));a.decode(t,n,0),t=n}return this.uint32(e)._push(m,e,t)},p.prototype.string=function(t){var e=s.length(t);return e?this.uint32(e)._push(s.write,e,t):this._push(d,1,0)},p.prototype.fork=function(){return this.states=new l(this),this.head=this.tail=new u(c,0,0),this.len=0,this},p.prototype.reset=function(){return this.states?(this.head=this.states.head,this.tail=this.states.tail,this.len=this.states.len,this.states=this.states.next):(this.head=this.tail=new u(c,0,0),this.len=0),this},p.prototype.ldelim=function(){var t=this.head,e=this.tail,n=this.len;return this.reset().uint32(n),n&&(this.tail.next=t.next,this.tail=e,this.len+=n),this},p.prototype.finish=function(){for(var t=this.head.next,e=this.constructor.alloc(this.len),n=0;t;)t.fn(t.val,e,n),n+=t.len,t=t.next;return e},p._configure=function(t){r=t,p.create=f(),r._configure()}},3155:(t,e,n)=>{"use strict";t.exports=o;var r=n(1173);(o.prototype=Object.create(r.prototype)).constructor=o;var i=n(9693);function o(){r.call(this)}function a(t,e,n){t.length<40?i.utf8.write(t,e,n):e.utf8Write?e.utf8Write(t,n):e.write(t,n)}o._configure=function(){o.alloc=i._Buffer_allocUnsafe,o.writeBytesBuffer=i.Buffer&&i.Buffer.prototype instanceof Uint8Array&&"set"===i.Buffer.prototype.set.name?function(t,e,n){e.set(t,n)}:function(t,e,n){if(t.copy)t.copy(e,n,0,t.length);else for(var r=0;r<t.length;)e[n++]=t[r++]}},o.prototype.bytes=function(t){i.isString(t)&&(t=i._Buffer_from(t,"base64"));var e=t.length>>>0;return this.uint32(e),e&&this._push(o.writeBytesBuffer,e,t),this},o.prototype.string=function(t){var e=i.Buffer.byteLength(t);return this.uint32(e),e&&this._push(a,e,t),this},o._configure()},7714:(t,e,n)=>{"use strict";e.R=void 0;const r=n(6919),i=n(7448);e.R=new class{async init(){}async createSessionHandler(t,e){const n=new r.Session(e);return await n.loadModel(t),new i.OnnxjsSessionHandler(n)}}},4200:(t,e,n)=>{"use strict";e.c8=e.rX=void 0;const r=n(8453),i=n(5381),o=n(2157),a=n(2306);e.rX=()=>{if(("number"!=typeof r.env.wasm.initTimeout||r.env.wasm.initTimeout<0)&&(r.env.wasm.initTimeout=0),"boolean"!=typeof r.env.wasm.simd&&(r.env.wasm.simd=!0),"boolean"!=typeof r.env.wasm.proxy&&(r.env.wasm.proxy=!1),"number"!=typeof r.env.wasm.numThreads||!Number.isInteger(r.env.wasm.numThreads)||r.env.wasm.numThreads<=0){const t="undefined"==typeof navigator?(0,i.cpus)().length:navigator.hardwareConcurrency;r.env.wasm.numThreads=Math.min(4,Math.ceil((t||1)/2))}},e.c8=new class{async init(){(0,e.rX)(),await(0,o.initWasm)()}async createSessionHandler(t,e){const n=new a.OnnxruntimeWebAssemblySessionHandler;return await n.loadModel(t,e),Promise.resolve(n)}}},6018:function(t,e,n){"use strict";var r=this&&this.__createBinding||(Object.create?function(t,e,n,r){void 0===r&&(r=n);var i=Object.getOwnPropertyDescriptor(e,n);i&&!("get"in i?!e.__esModule:i.writable||i.configurable)||(i={enumerable:!0,get:function(){return e[n]}}),Object.defineProperty(t,r,i)}:function(t,e,n,r){void 0===r&&(r=n),t[r]=e[n]}),i=this&&this.__exportStar||function(t,e){for(var n in t)"default"===n||Object.prototype.hasOwnProperty.call(e,n)||r(e,t,n)};Object.defineProperty(e,"__esModule",{value:!0}),i(n(8453),e);const o=n(8453);{const t=n(7714).R;(0,o.registerBackend)("webgl",t,-10)}{const t=n(4200).c8;(0,o.registerBackend)("cpu",t,10),(0,o.registerBackend)("wasm",t,10),(0,o.registerBackend)("xnnpack",t,9)}},246:(t,e)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.createAttributeWithCacheKey=void 0;class n{constructor(t){Object.assign(this,t)}get cacheKey(){return this._cacheKey||(this._cacheKey=Object.getOwnPropertyNames(this).sort().map((t=>`${this[t]}`)).join(";")),this._cacheKey}}e.createAttributeWithCacheKey=t=>new n(t)},7778:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.Attribute=void 0;const r=n(1446),i=n(9395),o=n(9162),a=n(2517);var s=i.onnxruntime.experimental.fbs;class u{constructor(t){if(this._attributes=new Map,null!=t){for(const e of t)e instanceof r.onnx.AttributeProto?this._attributes.set(e.name,[u.getValue(e),u.getType(e)]):e instanceof s.Attribute&&this._attributes.set(e.name(),[u.getValue(e),u.getType(e)]);if(this._attributes.size<t.length)throw new Error("duplicated attribute names")}}set(t,e,n){this._attributes.set(t,[n,e])}delete(t){this._attributes.delete(t)}getFloat(t,e){return this.get(t,"float",e)}getInt(t,e){return this.get(t,"int",e)}getString(t,e){return this.get(t,"string",e)}getTensor(t,e){return this.get(t,"tensor",e)}getFloats(t,e){return this.get(t,"floats",e)}getInts(t,e){return this.get(t,"ints",e)}getStrings(t,e){return this.get(t,"strings",e)}getTensors(t,e){return this.get(t,"tensors",e)}get(t,e,n){const r=this._attributes.get(t);if(void 0===r){if(void 0!==n)return n;throw new Error(`required attribute not found: ${t}`)}if(r[1]!==e)throw new Error(`type mismatch: expected ${e} but got ${r[1]}`);return r[0]}static getType(t){const e=t instanceof r.onnx.AttributeProto?t.type:t.type();switch(e){case r.onnx.AttributeProto.AttributeType.FLOAT:return"float";case r.onnx.AttributeProto.AttributeType.INT:return"int";case r.onnx.AttributeProto.AttributeType.STRING:return"string";case r.onnx.AttributeProto.AttributeType.TENSOR:return"tensor";case r.onnx.AttributeProto.AttributeType.FLOATS:return"floats";case r.onnx.AttributeProto.AttributeType.INTS:return"ints";case r.onnx.AttributeProto.AttributeType.STRINGS:return"strings";case r.onnx.AttributeProto.AttributeType.TENSORS:return"tensors";default:throw new Error(`attribute type is not supported yet: ${r.onnx.AttributeProto.AttributeType[e]}`)}}static getValue(t){const e=t instanceof r.onnx.AttributeProto?t.type:t.type();if(e===r.onnx.AttributeProto.AttributeType.GRAPH||e===r.onnx.AttributeProto.AttributeType.GRAPHS)throw new Error("graph attribute is not supported yet");const n=this.getValueNoCheck(t);if(e===r.onnx.AttributeProto.AttributeType.INT&&a.LongUtil.isLong(n))return a.LongUtil.longToNumber(n);if(e===r.onnx.AttributeProto.AttributeType.INTS){const t=n,e=new Array(t.length);for(let n=0;n<t.length;n++){const r=t[n];e[n]=a.LongUtil.longToNumber(r)}return e}if(e===r.onnx.AttributeProto.AttributeType.TENSOR)return t instanceof r.onnx.AttributeProto?o.Tensor.fromProto(n):o.Tensor.fromOrtTensor(n);if(e===r.onnx.AttributeProto.AttributeType.TENSORS){if(t instanceof r.onnx.AttributeProto)return n.map((t=>o.Tensor.fromProto(t)));if(t instanceof s.Attribute)return n.map((t=>o.Tensor.fromOrtTensor(t)))}if(e===r.onnx.AttributeProto.AttributeType.STRING&&t instanceof r.onnx.AttributeProto){const t=n;return(0,a.decodeUtf8String)(t)}return e===r.onnx.AttributeProto.AttributeType.STRINGS&&t instanceof r.onnx.AttributeProto?n.map(a.decodeUtf8String):n}static getValueNoCheck(t){return t instanceof r.onnx.AttributeProto?this.getValueNoCheckFromOnnxFormat(t):this.getValueNoCheckFromOrtFormat(t)}static getValueNoCheckFromOnnxFormat(t){switch(t.type){case r.onnx.AttributeProto.AttributeType.FLOAT:return t.f;case r.onnx.AttributeProto.AttributeType.INT:return t.i;case r.onnx.AttributeProto.AttributeType.STRING:return t.s;case r.onnx.AttributeProto.AttributeType.TENSOR:return t.t;case r.onnx.AttributeProto.AttributeType.GRAPH:return t.g;case r.onnx.AttributeProto.AttributeType.FLOATS:return t.floats;case r.onnx.AttributeProto.AttributeType.INTS:return t.ints;case r.onnx.AttributeProto.AttributeType.STRINGS:return t.strings;case r.onnx.AttributeProto.AttributeType.TENSORS:return t.tensors;case r.onnx.AttributeProto.AttributeType.GRAPHS:return t.graphs;default:throw new Error(`unsupported attribute type: ${r.onnx.AttributeProto.AttributeType[t.type]}`)}}static getValueNoCheckFromOrtFormat(t){switch(t.type()){case s.AttributeType.FLOAT:return t.f();case s.AttributeType.INT:return t.i();case s.AttributeType.STRING:return t.s();case s.AttributeType.TENSOR:return t.t();case s.AttributeType.GRAPH:return t.g();case s.AttributeType.FLOATS:return t.floatsArray();case s.AttributeType.INTS:{const e=[];for(let n=0;n<t.intsLength();n++)e.push(t.ints(n));return e}case s.AttributeType.STRINGS:{const e=[];for(let n=0;n<t.stringsLength();n++)e.push(t.strings(n));return e}case s.AttributeType.TENSORS:{const e=[];for(let n=0;n<t.tensorsLength();n++)e.push(t.tensors(n));return e}default:throw new Error(`unsupported attribute type: ${s.AttributeType[t.type()]}`)}}}e.Attribute=u},7091:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.resolveBackend=e.backend=void 0;const r=n(5038),i=new Map;async function o(t){const n=e.backend;if(void 0!==n[t]&&function(t){const e=t;return"initialize"in e&&"function"==typeof e.initialize&&"createSessionHandler"in e&&"function"==typeof e.createSessionHandler&&"dispose"in e&&"function"==typeof e.dispose}(n[t])){const e=n[t];let r=e.initialize();if("object"==typeof r&&"then"in r&&(r=await r),r)return i.set(t,e),e}}e.backend={webgl:new r.WebGLBackend},e.resolveBackend=async function t(e){if(!e)return t(["webgl"]);{const t="string"==typeof e?[e]:e;for(const e of t){const t=i.get(e);if(t)return t;const n=await o(e);if(n)return n}}throw new Error("no available backend to use")}},5038:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.WebGLBackend=void 0;const r=n(8453),i=n(6231),o=n(6416),a=n(7305);e.WebGLBackend=class{get contextId(){return r.env.webgl.contextId}set contextId(t){r.env.webgl.contextId=t}get matmulMaxBatchSize(){return r.env.webgl.matmulMaxBatchSize}set matmulMaxBatchSize(t){r.env.webgl.matmulMaxBatchSize=t}get textureCacheMode(){return r.env.webgl.textureCacheMode}set textureCacheMode(t){r.env.webgl.textureCacheMode=t}get pack(){return r.env.webgl.pack}set pack(t){r.env.webgl.pack=t}get async(){return r.env.webgl.async}set async(t){r.env.webgl.async=t}initialize(){try{return this.glContext=(0,a.createWebGLContext)(this.contextId),"number"!=typeof this.matmulMaxBatchSize&&(this.matmulMaxBatchSize=16),"string"!=typeof this.textureCacheMode&&(this.textureCacheMode="full"),"boolean"!=typeof this.pack&&(this.pack=!1),"boolean"!=typeof this.async&&(this.async=!1),i.Logger.setWithEnv(r.env),i.Logger.verbose("WebGLBackend",`Created WebGLContext: ${typeof this.glContext} with matmulMaxBatchSize: ${this.matmulMaxBatchSize}; textureCacheMode: ${this.textureCacheMode}; pack: ${this.pack}; async: ${this.async}.`),!0}catch(t){return i.Logger.warning("WebGLBackend",`Unable to initialize WebGLBackend. ${t}`),!1}}createSessionHandler(t){return new o.WebGLSessionHandler(this,t)}dispose(){this.glContext.dispose()}}},5107:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.CoordsGlslLib=void 0;const r=n(2517),i=n(8520),o=n(5060),a=n(7859),s=n(9390);class u extends i.GlslLib{constructor(t){super(t)}getFunctions(){return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},this.offsetToCoords()),this.coordsToOffset()),this.toVec()),this.valueFrom()),this.getCommonUtilFuncs()),this.getInputsSamplingSnippets()),this.getOutputSamplingSnippet())}getCustomTypes(){return{}}offsetToCoords(){return{offsetToCoords:new i.GlslLibRoutine("\n      vec2 offsetToCoords(int offset, int width, int height) {\n        int t = offset / width;\n        int s = offset - t*width;\n        vec2 coords = (vec2(s,t) + vec2(0.5,0.5)) / vec2(width, height);\n        return coords;\n      }\n      ")}}coordsToOffset(){return{coordsToOffset:new i.GlslLibRoutine("\n      int coordsToOffset(vec2 coords, int width, int height) {\n        float s = coords.s * float(width);\n        float t = coords.t * float(height);\n        int offset = int(t) * width + int(s);\n        return offset;\n      }\n      ")}}getOutputSamplingSnippet(){const t=this.context.outputTextureLayout;return t.isPacked?this.getPackedOutputSamplingSnippet(t):this.getUnpackedOutputSamplingSnippet(t)}getPackedOutputSamplingSnippet(t){const e=t.unpackedShape,n=[t.width,t.height],r={},a="getOutputCoords";switch(e.length){case 0:r[a]=this.getOutputScalarCoords();break;case 1:r[a]=this.getOutputPacked1DCoords(e,n);break;case 2:r[a]=this.getOutputPacked2DCoords(e,n);break;case 3:r[a]=this.getOutputPacked3DCoords(e,n);break;default:r[a]=this.getOutputPackedNDCoords(e,n)}const s=`\n      void setOutput(vec4 val) {\n        ${(0,o.getGlsl)(this.context.glContext.version).output} = val;\n      }\n    `;return r.floatTextureSetRGBA=new i.GlslLibRoutine(s),r}getUnpackedOutputSamplingSnippet(t){const e=t.unpackedShape,n=[t.width,t.height],r={},a="getOutputCoords";switch(e.length){case 0:r[a]=this.getOutputScalarCoords();break;case 1:r[a]=this.getOutputUnpacked1DCoords(e,n);break;case 2:r[a]=this.getOutputUnpacked2DCoords(e,n);break;case 3:r[a]=this.getOutputUnpacked3DCoords(e,n);break;case 4:r[a]=this.getOutputUnpacked4DCoords(e,n);break;case 5:r[a]=this.getOutputUnpacked5DCoords(e,n);break;case 6:r[a]=this.getOutputUnpacked6DCoords(e,n);break;default:throw new Error(`Unsupported output dimensionality: ${e.length}`)}const s=`\n        void setOutput(float val) {\n          ${(0,o.getGlsl)(this.context.glContext.version).output} = vec4(val, 0, 0, 0);\n        }\n    `;return r.floatTextureSetR=new i.GlslLibRoutine(s),r}getOutputScalarCoords(){return new i.GlslLibRoutine("\n      int getOutputCoords() {\n        return 0;\n      }\n    ")}getOutputPacked1DCoords(t,e){const n=e;let r="";return 1===n[0]?(r=`\n          int getOutputCoords() {\n            return 2 * int(TexCoords.y * ${n[1]}.0);\n          }\n        `,new i.GlslLibRoutine(r)):1===n[1]?(r=`\n          int getOutputCoords() {\n            return 2 * int(TexCoords.x * ${n[0]}.0);\n          }\n        `,new i.GlslLibRoutine(r)):(r=`\n        int getOutputCoords() {\n          ivec2 resTexRC = ivec2(TexCoords.xy *\n                                 vec2(${n[0]}, ${n[1]}));\n          return 2 * (resTexRC.y * ${n[0]} + resTexRC.x);\n        }\n      `,new i.GlslLibRoutine(r))}getOutputPacked2DCoords(t,e){let n="";if(r.ArrayUtil.arraysEqual(t,e))return n=`\n        ivec2 getOutputCoords() {\n          return 2 * ivec2(TexCoords.xy * vec2(${e[0]}, ${e[1]}));\n        }\n      `,new i.GlslLibRoutine(n);const o=e,a=Math.ceil(t[1]/2);return n=`\n        ivec2 getOutputCoords() {\n          ivec2 resTexRC = ivec2(TexCoords.xy *\n                                vec2(${o[0]}, ${o[1]}));\n\n          int index = resTexRC.y * ${o[0]} + resTexRC.x;\n\n          // reverse r and c order for packed texture\n          int r = imod(index, ${a}) * 2;\n          int c = 2 * (index / ${a});\n\n          return ivec2(r, c);\n        }\n      `,new i.GlslLibRoutine(n)}getOutputPacked3DCoords(t,e){const n=[e[0],e[1]],r=Math.ceil(t[2]/2),o=r*Math.ceil(t[1]/2),a=`\n        ivec3 getOutputCoords() {\n          ivec2 resTexRC = ivec2(TexCoords.xy *\n                                vec2(${n[0]}, ${n[1]}));\n          int index = resTexRC.y * ${n[0]} + resTexRC.x;\n\n          int b = index / ${o};\n          index -= b * ${o};\n\n          // reverse r and c order for packed texture\n          int r = imod(index, ${r}) * 2;\n          int c = 2 * (index / ${r});\n\n          return ivec3(b, r, c);\n        }\n      `;return new i.GlslLibRoutine(a)}getOutputPackedNDCoords(t,e){const n=[e[0],e[1]],r=Math.ceil(t[t.length-1]/2),o=r*Math.ceil(t[t.length-2]/2);let a=o,s="",u="b, r, c";for(let e=2;e<t.length-1;e++)a*=t[t.length-e-1],s=`\n      int b${e} = index / ${a};\n      index -= b${e} * ${a};\n    `+s,u=`b${e}, `+u;const c=`\n      ivec${t.length} getOutputCoords() {\n        ivec2 resTexRC = ivec2(TexCoords.xy *\n                              vec2(${n[0]}, ${n[1]}));\n        int index = resTexRC.y * ${n[0]} + resTexRC.x;\n\n        ${s}\n\n        int b = index / ${o};\n        index -= b * ${o};\n\n        // reverse r and c order for packed texture\n        int r = imod(index, ${r}) * 2;\n        int c = 2 * (index / ${r});\n\n        return ivec${t.length}(${u});\n      }\n    `;return new i.GlslLibRoutine(c)}getOutputUnpacked1DCoords(t,e){const n=`\n        int getOutputCoords() {\n          ivec2 resTexRC = ivec2(TexCoords.xy *\n                                vec2(${e[0]}, ${e[1]}));\n          return resTexRC.y * ${e[0]} + resTexRC.x;\n        }\n      `;return new i.GlslLibRoutine(n)}getOutputUnpacked2DCoords(t,e){const n=`\n        ivec2 getOutputCoords() {\n          ivec2 resTexRC = ivec2(TexCoords.xy *\n                                vec2(${e[0]}, ${e[1]}));\n          int index = resTexRC.y * ${e[0]} + resTexRC.x;\n          int r = index / ${t[1]};\n          int c = index - r * ${t[1]};\n          return ivec2(r, c);\n        }\n      `;return new i.GlslLibRoutine(n)}getOutputUnpacked3DCoords(t,e){let n="";const r=t.length;let o=null;r<2&&(o=[]),o=new Array(r-1),o[r-2]=t[r-1];for(let e=r-3;e>=0;--e)o[e]=o[e+1]*t[e+1];const a=["r","c","d"],s=o.map(((t,e)=>`int ${a[e]} = index / ${t}; ${e===o.length-1?`int ${a[e+1]} = index - ${a[e]} * ${t}`:`index -= ${a[e]} * ${t}`};`)).join("");return n=`\n        ivec3 getOutputCoords() {\n          ivec2 resTexRC = ivec2(TexCoords.xy *\n                                vec2(${e[0]}, ${e[1]}));\n          int index = resTexRC.y * ${e[0]} + resTexRC.x;\n          ${s}\n          return ivec3(r, c, d);\n        }\n      `,new i.GlslLibRoutine(n)}getOutputUnpacked4DCoords(t,e){let n="";const r=t.length;let o=null;r<2&&(o=[]),o=new Array(r-1),o[r-2]=t[r-1];for(let e=r-3;e>=0;--e)o[e]=o[e+1]*t[e+1];const a=["r","c","d","d2"],s=o.map(((t,e)=>`int ${a[e]} = index / ${t}; ${e===o.length-1?`int ${a[e+1]} = index - ${a[e]} * ${t}`:`index -= ${a[e]} * ${t}`};`)).join("");return n=`\n      ivec4 getOutputCoords() {\n          ivec2 resTexRC = ivec2(TexCoords.xy *\n                                vec2(${e[0]}, ${e[1]}));\n          int index = resTexRC.y * ${e[0]} + resTexRC.x;\n          ${s}\n          return ivec4(r, c, d, d2);\n        }\n      `,new i.GlslLibRoutine(n)}getOutputUnpacked5DCoords(t,e){let n="";const r=t.length;let o=null;r<2&&(o=[]),o=new Array(r-1),o[r-2]=t[r-1];for(let e=r-3;e>=0;--e)o[e]=o[e+1]*t[e+1];const a=["r","c","d","d2","d3"],s=o.map(((t,e)=>`int ${a[e]} = index / ${t}; ${e===o.length-1?`int ${a[e+1]} = index - ${a[e]} * ${t}`:`index -= ${a[e]} * ${t}`};`)).join("");return n=`\n      ivec5 getOutputCoords() {\n          ivec2 resTexRC = ivec2(TexCoords.xy *\n                                vec2(${e[0]}, ${e[1]}));\n          int index = resTexRC.y * ${e[0]} + resTexRC.x;\n          ${s}\n          return ivec5(r, c, d, d2, d3);\n        }\n      `,new i.GlslLibRoutine(n)}getOutputUnpacked6DCoords(t,e){let n="";const r=t.length;let o=null;r<2&&(o=[]),o=new Array(r-1),o[r-2]=t[r-1];for(let e=r-3;e>=0;--e)o[e]=o[e+1]*t[e+1];const a=["r","c","d","d2","d3","d4"],s=o.map(((t,e)=>`int ${a[e]} = index / ${t}; ${e===o.length-1?`int ${a[e+1]} = index - ${a[e]} * ${t}`:`index -= ${a[e]} * ${t}`};`)).join("");return n=`\n     ivec6 getOutputCoords() {\n         ivec2 resTexRC = ivec2(TexCoords.xy *\n                               vec2(${e[0]}, ${e[1]}));\n         int index = resTexRC.y * ${e[0]} + resTexRC.x;\n         ${s}\n         return ivec6(r, c, d, d2, d3, d4);\n       }\n     `,new i.GlslLibRoutine(n)}getCommonUtilFuncs(){const t={};let e="uvFromFlat";t[e]=new i.GlslLibRoutine("\n    vec2 uvFromFlat(int texNumR, int texNumC, int index) {\n      int texC = index / texNumR;\n      int texR = index - texC * texNumR;\n      // TODO: swap texR, texC order in following function so row is corresponding to u and column is corresponding to\n      //       v.\n      return (vec2(texR, texC) + halfCR) / vec2(texNumR, texNumC);\n    }\n    "),e="packedUVfrom1D",t[e]=new i.GlslLibRoutine("\n      vec2 packedUVfrom1D(int texNumR, int texNumC, int index) {\n        int texelIndex = index / 2;\n        int texR = texelIndex / texNumC;\n        int texC = texelIndex - texR * texNumC;\n        return (vec2(texC, texR) + halfCR) / vec2(texNumC, texNumR);\n      }\n      "),e="packedUVfrom2D",t[e]=new i.GlslLibRoutine("\n      vec2 packedUVfrom2D(int texNumR, int texNumC, int texelsInLogicalRow, int row, int col) {\n        int texelIndex = (row / 2) * texelsInLogicalRow + (col / 2);\n        int texR = texelIndex / texNumC;\n        int texC = texelIndex - texR * texNumC;\n        return (vec2(texC, texR) + halfCR) / vec2(texNumC, texNumR);\n      }\n      "),e="packedUVfrom3D",t[e]=new i.GlslLibRoutine("\n      vec2 packedUVfrom3D(int texNumR, int texNumC,\n          int texelsInBatch, int texelsInLogicalRow, int b,\n          int row, int col) {\n        int index = b * texelsInBatch + (row / 2) * texelsInLogicalRow + (col / 2);\n        int texR = index / texNumC;\n        int texC = index - texR * texNumC;\n        return (vec2(texC, texR) + halfCR) / vec2(texNumC, texNumR);\n      }\n      "),e="sampleTexture";const n=(0,o.getGlsl)(this.context.glContext.version);return t[e]=new i.GlslLibRoutine(`\n        float sampleTexture(sampler2D textureSampler, vec2 uv) {\n            return ${n.texture2D}(textureSampler, uv).r;\n        }`),t}getInputsSamplingSnippets(){const t={},e=this.context.outputTextureLayout;return this.context.programInfo.inputNames.forEach(((n,r)=>{const i=this.context.inputTextureLayouts[r],o=(0,s.generateShaderFuncNameFromInputSamplerName)(n);i.isPacked?t[o]=this.getPackedSamplerFromInput(o,n,i):t[o]=this.getUnpackedSamplerFromInput(o,n,i);const a=(0,s.generateShaderFuncNameFromInputSamplerNameAtOutCoords)(n);i.unpackedShape.length<=e.unpackedShape.length&&(i.isPacked?t[a]=this.getPackedSamplerAtOutputCoords(a,i,e,n):t[a]=this.getUnpackedSamplerAtOutputCoords(a,i,e,n))})),t}getPackedSamplerAtOutputCoords(t,e,n,o){const a=e.unpackedShape,u=n.unpackedShape,c=o,l=(0,s.generateShaderFuncNameFromInputSamplerName)(c),p=a.length,f=u.length,d=r.BroadcastUtil.getBroadcastDims(a,u),h=(0,s.getCoordsDataType)(f),g=f-p;let b;const m=(0,s.getGlChannels)();b=0===p?"":f<2&&d.length>=1?"coords = 0;":d.map((t=>`coords.${m[t+g]} = 0;`)).join("\n");let y="";y=f<2&&p>0?"coords":a.map(((t,e)=>`coords.${m[e+g]}`)).join(", ");let _="return outputValue;";const v=1===r.ShapeUtil.size(a),w=1===r.ShapeUtil.size(u);if(1!==p||v||w){if(v&&!w)_=1===f?"\n          return vec4(outputValue.x, outputValue.x, 0., 0.);\n        ":"\n          return vec4(outputValue.x);\n        ";else if(d.length){const t=p-2,e=p-1;d.indexOf(t)>-1&&d.indexOf(e)>-1?_="return vec4(outputValue.x);":d.indexOf(t)>-1?_="return vec4(outputValue.x, outputValue.y, outputValue.x, outputValue.y);":d.indexOf(e)>-1&&(_="return vec4(outputValue.xx, outputValue.zz);")}}else _="\n        return vec4(outputValue.xy, outputValue.xy);\n      ";const x=`\n      vec4 ${t}() {\n        ${h} coords = getOutputCoords();\n        \n        int lastDim = coords.${m[f-1]};\n        coords.${m[f-1]} = coords.${m[f-2]};\n        coords.${m[f-2]} = lastDim;\n      \n        ${b}\n        vec4 outputValue = ${l}(${y});\n        ${_}\n      }\n    `;return new i.GlslLibRoutine(x,["coordinates.getOutputCoords"])}getUnpackedSamplerAtOutputCoords(t,e,n,o){const a=[n.width,n.height],u=[e.width,e.height],c=e.unpackedShape.length,l=n.unpackedShape.length,p=e.unpackedShape,f=n.unpackedShape,d=(0,s.generateShaderFuncNameFromInputSamplerName)(o);if(c===l&&r.ArrayUtil.arraysEqual(u,a)){const e=`\n          float ${t}() {\n            return sampleTexture(${o}, TexCoords);\n          }\n        `;return new i.GlslLibRoutine(e,["coordinates.sampleTexture"])}const h=(0,s.getCoordsDataType)(l),g=r.BroadcastUtil.getBroadcastDims(p,f),b=l-c;let m;const y=(0,s.getGlChannels)();m=0===c?"":l<2&&g.length>=1?"coords = 0;":g.map((t=>`coords.${y[t+b]} = 0;`)).join("\n");let _="";_=l<2&&c>0?"coords":e.unpackedShape.map(((t,e)=>`coords.${y[e+b]}`)).join(", ");const v=`\n        float ${t}() {\n          ${h} coords = getOutputCoords();\n          ${m}\n          return ${d}(${_});\n        }\n      `;return new i.GlslLibRoutine(v,["coordinates.getOutputCoords"])}getPackedSamplerFromInput(t,e,n){switch(n.unpackedShape.length){case 0:return this.getPackedSamplerScalar(t,e);case 1:return this.getPackedSampler1D(t,e,n);case 2:return this.getPackedSampler2D(t,e,n);case 3:return this.getPackedSampler3D(t,e,n);default:return this.getPackedSamplerND(t,e,n)}}getUnpackedSamplerFromInput(t,e,n){const r=n.unpackedShape;switch(r.length){case 0:return this.getUnpackedSamplerScalar(t,e,n);case 1:return this.getUnpackedSampler1D(t,e,n);case 2:return this.getUnpackedSampler2D(t,e,n);case 3:return this.getUnpackedSampler3D(t,e,n);case 4:return this.getUnpackedSampler4D(t,e,n);case 5:return this.getUnpackedSampler5D(t,e,n);case 6:return this.getUnpackedSampler6D(t,e,n);default:throw new Error(`Unsupported dimension ${r.length}-D`)}}getPackedSamplerScalar(t,e){const n=`\n          vec4 ${t}() {\n            return ${(0,o.getGlsl)(this.context.glContext.version).texture2D}(${e}, halfCR);\n          }\n        `;return new i.GlslLibRoutine(n)}getPackedSampler1D(t,e,n){const r=[n.width,n.height],a=[r[1],r[0]],s=(0,o.getGlsl)(this.context.glContext.version),u=`vec4 ${t}(int index) {\n      vec2 uv = packedUVfrom1D(\n      ${a[0]}, ${a[1]}, index);\n      return ${s.texture2D}(${e}, uv);\n    }`;return new i.GlslLibRoutine(u,["coordinates.packedUVfrom1D"])}getPackedSampler2D(t,e,n){const a=n.unpackedShape,s=[n.width,n.height],u=(0,o.getGlsl)(this.context.glContext.version),c=s[0],l=s[1];if(null!=s&&r.ArrayUtil.arraysEqual(a,s)){const n=`vec4 ${t}(int row, int col) {\n        vec2 uv = (vec2(col, row) + halfCR) / vec2(${l}.0, ${c}.0);\n        return ${u.texture2D}(${e}, uv);\n      }`;return new i.GlslLibRoutine(n)}const p=s,f=Math.ceil(a[1]/2),d=`vec4 ${t}(int row, int col) {\n      vec2 uv = packedUVfrom2D(${p[1]}, ${p[0]}, ${f}, row, col);\n      return ${u.texture2D}(${e}, uv);\n    }`;return new i.GlslLibRoutine(d,["coordinates.packedUVfrom2D"])}getPackedSampler3D(t,e,n){const r=n.unpackedShape,a=[n.width,n.height],u=[a[0],a[1]],c=(0,o.getGlsl)(this.context.glContext.version);if(1===r[0]){const o=r.slice(1),a=[1,2],u=(0,s.squeezeInputShape)(r,o),c=["b","row","col"],l=JSON.parse(JSON.stringify(n));l.unpackedShape=u;const p=this.getPackedSamplerFromInput(t,e,l),f=`${p.routineBody}\n      vec4 ${t}(int b, int row, int col) {\n        return ${t}(${(0,s.getSqueezedParams)(c,a)});\n      } `;return new i.GlslLibRoutine(f,p.dependencies)}const l=u[0],p=u[1],f=Math.ceil(r[2]/2),d=`vec4 ${t}(int b, int row, int col) {\n      vec2 uv = packedUVfrom3D(\n        ${p}, ${l}, ${f*Math.ceil(r[1]/2)}, ${f}, b, row, col);\n      return ${c.texture2D}(${e}, uv);}`;return new i.GlslLibRoutine(d,["coordinates.packedUVfrom3D"])}getPackedSamplerND(t,e,n){const r=n.unpackedShape,a=r.length,s=[n.width,n.height],u=(0,o.getGlsl)(this.context.glContext.version),c=[s[0],s[1]],l=c[1],p=c[0],f=Math.ceil(r[a-1]/2);let d=f*Math.ceil(r[a-2]/2),h="int b, int row, int col",g=`b * ${d} + (row / 2) * ${f} + (col / 2)`;for(let t=2;t<a-1;t++)h=`int b${t}, `+h,d*=r[a-t-1],g=`b${t} * ${d} + `+g;const b=`vec4 ${t}(${h}) {\n      int index = ${g};\n      int texR = index / ${p};\n      int texC = index - texR * ${p};\n      vec2 uv = (vec2(texC, texR) + halfCR) / vec2(${p}, ${l});\n      return ${u.texture2D}(${e}, uv);\n    }`;return new i.GlslLibRoutine(b)}getUnpackedSamplerScalar(t,e,n){const[r,o]=[n.width,n.height];if(1===r&&1===o){const n=`\n          float ${t}() {\n            return sampleTexture(${e}, halfCR);\n          }\n        `;return new i.GlslLibRoutine(n,["coordinates.sampleTexture"])}const a=`\n        float ${t}() {\n          int offset_${e} = coordsToOffset(TexCoords, ${r}, ${o});\n          vec2 uv = uvFromFlat(${r}, ${o}, offset_${e});\n          return sampleTexture(${e}, uv);\n        }\n      `;return new i.GlslLibRoutine(a,["coordinates.uvFromFlat","coordinates.sampleTexture","coordinates.coordsToOffset"])}getUnpackedSampler1D(t,e,n){const r=n.width,o=n.height;if(1===o&&1===r){const n=`\n        float ${t}(int index) {\n          return sampleTexture(${e}, halfCR);\n        }\n      `;return new i.GlslLibRoutine(n,["coordinates.sampleTexture"])}if(1===o){const n=`\n          float ${t}(int index) {\n            vec2 uv = vec2((float(index) + 0.5) / ${r}.0, 0.5);\n            return sampleTexture(${e}, uv);\n          }\n        `;return new i.GlslLibRoutine(n,["coordinates.sampleTexture"])}if(1===r){const n=`\n          float ${t}(int index) {\n            vec2 uv = vec2(0.5, (float(index) + 0.5) / ${o}.0);\n            return sampleTexture(${e}, uv);\n          }\n        `;return new i.GlslLibRoutine(n,["coordinates.sampleTexture"])}const a=`\n        float ${t}(int index) {\n          vec2 uv = uvFromFlat(${r}, ${o}, index);\n          return sampleTexture(${e}, uv);\n        }\n      `;return new i.GlslLibRoutine(a,["coordinates.uvFromFlat","coordinates.sampleTexture"])}getUnpackedSampler2D(t,e,n){const o=n.unpackedShape,u=[n.height,n.width];if(null!=u&&r.ArrayUtil.arraysEqual(o,u)){const n=`\n          float ${t}(int row, int col) {\n            vec2 uv = (vec2(row, col) + halfCR) / vec2(${u[1]}.0, ${u[0]}.0);\n            return sampleTexture(${e}, uv);\n          }\n        `;return new i.GlslLibRoutine(n,["coordinates.sampleTexture"])}const{newShape:c,keptDims:l}=(0,a.squeezeShape)(o),p=c;if(p.length<o.length){const r=(0,s.squeezeInputShape)(o,p),a=JSON.parse(JSON.stringify(n));a.unpackedShape=r;const u=["col","row"],c=`\n          ${this.getUnpackedSamplerFromInput(t,e,a).routineBody}\n          float ${t}(int row, int col) {\n            return ${t}(${(0,s.getSqueezedParams)(u,l)});\n          }\n        `;return new i.GlslLibRoutine(c,["coordinates.sampleTexture"])}const f=u[1],d=u[0];if(1===d){const n=`\n          float ${t}(int row, int col) {\n            int offset_${e} = coordsToOffset(TexCoords, ${f}, ${d});\n            float index = dot(vec3(row, col, offset_${e}), vec3(${o[1]}, 1, 1));\n            vec2 uv = vec2(0.5, (index + 0.5) / ${f}.0);\n            return sampleTexture(${e}, uv);\n          }\n        `;return new i.GlslLibRoutine(n,["coordinates.sampleTexture","coordinates.coordsToOffset"])}if(1===f){const n=`\n          float ${t}(int row, int col) {\n            int offset_${e} = coordsToOffset(TexCoords, ${f}, ${d});\n            float index = dot(vec3(row, col, offset_${e}), vec3(${o[1]}, 1, 1));\n            vec2 uv = vec2((index + 0.5) / ${d}.0, 0.5);\n            return sampleTexture(${e}, uv);\n          }\n        `;return new i.GlslLibRoutine(n,["coordinates.sampleTexture","coordinates.coordsToOffset"])}const h=`\n        float ${t}(int row, int col) {\n          int index = col * ${o[1]} + row;\n          vec2 uv = uvFromFlat(${f}, ${d}, index);\n          return sampleTexture(${e}, uv);\n        }\n      `;return new i.GlslLibRoutine(h,["coordinates.uvFromFlat","coordinates.sampleTexture","coordinates.coordsToOffset"])}getUnpackedSampler3D(t,e,n){const r=n.unpackedShape,o=r[1]*r[2],u=r[2],{newShape:c,keptDims:l}=(0,a.squeezeShape)(r),p=c;if(p.length<r.length){const o=(0,s.squeezeInputShape)(r,p),a=["batch","col","row"],u=JSON.parse(JSON.stringify(n));u.unpackedShape=o;const c=this.getUnpackedSamplerFromInput(t,e,u),f=l.reverse(),d=`\n          ${c.routineBody}\n          float ${t}(int batch, int row, int col) {\n            return ${t}(${(0,s.getSqueezedParams)(a,f)});\n          }\n        `;return new i.GlslLibRoutine(d,c.dependencies)}const f=`\n          float ${t}(int depth, int row, int col) {\n            // Explicitly use integer operations as dot() only works on floats.\n            int index = depth * ${o} + col * ${u} + row;\n            vec2 uv = uvFromFlat(${n.width}, ${n.height}, index);\n            return sampleTexture(${e}, uv);\n          }\n      `;return new i.GlslLibRoutine(f,["coordinates.uvFromFlat","coordinates.sampleTexture","coordinates.coordsToOffset"])}getUnpackedSampler4D(t,e,n){const r=n.unpackedShape,o=r[3],a=r[2]*o,s=`\n        float ${t}(int row, int col, int depth, int depth2) {\n          int index = row * ${r[1]*a} + col * ${a} +\n              depth2 * ${o} + depth;\n          vec2 uv = uvFromFlat(${n.width}, ${n.height}, index);\n          return sampleTexture(${e}, uv);\n        }\n      `;return new i.GlslLibRoutine(s,["coordinates.uvFromFlat","coordinates.sampleTexture"])}getUnpackedSampler5D(t,e,n){const r=n.unpackedShape,o=r[4],u=r[3]*o,c=r[2]*u,l=r[1]*c,{newShape:p,keptDims:f}=(0,a.squeezeShape)(r);if(p.length<r.length){const o=(0,s.squeezeInputShape)(r,p),a=["row","col","depth","depth2","depth3"],u=JSON.parse(JSON.stringify(n));u.unpackedShape=o;const c=`\n          ${this.getUnpackedSamplerFromInput(t,e,u).routineBody}\n          float ${t}(int row, int col, int depth, int depth2, int depth3) {\n            return ${t}(${(0,s.getSqueezedParams)(a,f)});\n          }\n        `;return new i.GlslLibRoutine(c,["coordinates.sampleTexture","coordinates.uvFromFlat"])}const d=`\n        float ${t}(int row, int col, int depth, int depth2, int depth3) {\n          int index = row * ${l} + col * ${c} + depth * ${u} +\n          depth3 * ${o} + depth2;\n          vec2 uv = uvFromFlat(${n.width}, ${n.height}, index);\n          return sampleTexture(${e}, uv);\n        }\n      `;return new i.GlslLibRoutine(d,["coordinates.sampleTexture","coordinates.uvFromFlat"])}getUnpackedSampler6D(t,e,n){const r=n.unpackedShape,o=r[5],u=r[4]*o,c=r[3]*u,l=r[2]*c,p=r[1]*l,{newShape:f,keptDims:d}=(0,a.squeezeShape)(r);if(f.length<r.length){const o=(0,s.squeezeInputShape)(r,f),a=["row","col","depth","depth2","depth3","depth4"],u=JSON.parse(JSON.stringify(n));u.unpackedShape=o;const c=`\n            ${this.getUnpackedSamplerFromInput(t,e,u).routineBody}\n            float ${t}(int row, int col, int depth,\n              int depth2, int depth3, int depth4) {\n              return ${t}(${(0,s.getSqueezedParams)(a,d)});\n            }\n          `;return new i.GlslLibRoutine(c,["coordinates.sampleTexture","coordinates.uvFromFlat"])}const h=`\n          float ${t}(int row, int col, int depth,\n            int depth2, int depth3, int depth4) {\n            int index = row * ${p} + col * ${l} + depth * ${c} +\n            depth2 * ${u} + depth3 * ${o} + depth4;\n            vec2 uv = uvFromFlat(${n.width}, ${n.height}, index);\n            return sampleTexture(${e}, uv);\n          }\n        `;return new i.GlslLibRoutine(h,["coordinates.uvFromFlat","coordinates.sampleTexture","coordinates.coordsToOffset"])}toVec(){const t=this.context.outputTextureLayout,e=t.shape.length,n=t.strides,r=t.width,o=t.height,a=[];for(let t=0;t<e-1;++t)a.push(`\n        c[${t}] = offset / ${n[t]};`),a.push(`\n        offset -= c[${t}] * ${n[t]};`);a.push(`\n        c[${e-1}] = offset;`);const s=`\n      void toVec(vec2 texCoords, out int c[${e}]) {\n        int offset = coordsToOffset(texCoords, ${r}, ${o});\n        ${a.join("")}\n      }\n      void toVec(int offset, out int c[${e}]) {\n        ${a.join("")}\n      }\n    `;return{toVec:new i.GlslLibRoutine(s,["coordinates.coordsToOffset"])}}valueFrom(){const t={};return this.context.programInfo.inputNames.forEach(((e,n)=>{const r=this.context.inputTextureLayouts[n],o=(r.unpackedShape.length>0?r.unpackedShape:r.shape).length;let a=`_${e}`;t[a]=new i.GlslLibRoutine(this.getValueFromSingle(e,o,r.width,r.height,!1),[`shapeUtils.indicesToOffset${a}`,"coordinates.offsetToCoords","fragcolor.getColorAsFloat"]),a+="_T",t[a]=new i.GlslLibRoutine(this.getValueFromSingle(e,o,r.width,r.height,!0),[`shapeUtils.indicesToOffset${a}`,"coordinates.offsetToCoords","fragcolor.getColorAsFloat"])})),t}getValueFromSingle(t,e,n,r,i){let a=`_${t}`;return i&&(a+="_T"),`\n        float ${a}(int m[${e}]) {\n          int offset = indicesToOffset${a}(m);\n          vec2 coords = offsetToCoords(offset, ${n}, ${r});\n          float value = getColorAsFloat(${(0,o.getGlsl)(this.context.glContext.version).texture2D}(${t}, coords));\n          return value;\n        }\n        `}getPackedValueFrom(t,e,n,r,i){let a=`_${t}_Pack`;return i&&(a+="_T"),`\n        vec4 ${a}(int m[${e}]) {\n          int offset = indicesToOffset_${t}(m);\n          vec2 coords = offsetToCoords(offset, ${n}, ${r});\n          return ${(0,o.getGlsl)(this.context.glContext.version).texture2D}(${t}, coords);\n        }\n        `}}e.CoordsGlslLib=u},8520:(t,e)=>{"use strict";var n;Object.defineProperty(e,"__esModule",{value:!0}),e.TopologicalSortGlslRoutines=e.GlslLibRoutineNode=e.GlslLibRoutine=e.GlslLib=e.GlslContext=e.FunctionType=void 0,(n=e.FunctionType||(e.FunctionType={}))[n.ValueBased=0]="ValueBased",n[n.Positional=1]="Positional",e.GlslContext=class{constructor(t,e,n,r){this.glContext=t,this.programInfo=e,this.inputTextureLayouts=n,this.outputTextureLayout=r}},e.GlslLib=class{constructor(t){this.context=t}},e.GlslLibRoutine=class{constructor(t,e){this.routineBody=t,this.dependencies=e}},e.GlslLibRoutineNode=class{constructor(t,e,n){this.name=t,this.dependencies=n||[],e&&(this.routineBody=e)}addDependency(t){t&&this.dependencies.push(t)}},e.TopologicalSortGlslRoutines=class{static returnOrderedNodes(t){if(!t||0===t.length)return[];if(1===t.length)return t;const e=new Set,n=new Set,r=new Array;return this.createOrderedNodes(t,e,n,r),r}static createOrderedNodes(t,e,n,r){for(let i=0;i<t.length;++i)this.dfsTraverse(t[i],e,n,r)}static dfsTraverse(t,e,n,r){if(!t||n.has(t.name))return;if(e.has(t.name))throw new Error("Cyclic dependency detected. Can't topologically sort routines needed for shader.");e.add(t.name);const i=t.dependencies;if(i&&i.length>0)for(let t=0;t<i.length;++t)this.dfsTraverse(i[t],e,n,r);r.push(t),n.add(t.name),e.delete(t.name)}}},7341:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.EncodingGlslLib=void 0;const r=n(8520);class i extends r.GlslLib{constructor(t){super(t)}getFunctions(){return Object.assign(Object.assign({},this.encodeFloat32()),this.decodeFloat32())}getCustomTypes(){return{}}encodeFloat32(){return{encode:new r.GlslLibRoutine("highp vec4 encode(highp float f) {\n        return vec4(f, 0.0, 0.0, 0.0);\n      }\n        ")}}decodeFloat32(){return{decode:new r.GlslLibRoutine("highp float decode(highp vec4 rgba) {\n        return rgba.r;\n      }\n        ")}}encodeUint8(){const t=i.isLittleEndian()?"rgba.rgba=rgba.abgr;":"";return{encode:new r.GlslLibRoutine(`\n      highp vec4 encode(highp float f) {\n        highp float F = abs(f);\n        highp float Sign = step(0.0,-f);\n        highp float Exponent = floor(log2(F));\n        highp float Mantissa = (exp2(- Exponent) * F);\n        Exponent = floor(log2(F) + 127.0) + floor(log2(Mantissa));\n        highp vec4 rgba;\n        rgba[0] = 128.0 * Sign  + floor(Exponent*exp2(-1.0));\n        rgba[1] = 128.0 * mod(Exponent,2.0) + mod(floor(Mantissa*128.0),128.0);\n        rgba[2] = floor(mod(floor(Mantissa*exp2(23.0 -8.0)),exp2(8.0)));\n        rgba[3] = floor(exp2(23.0)*mod(Mantissa,exp2(-15.0)));\n        ${t}\n        rgba = rgba / 255.0; // values need to be normalized to [0,1]\n        return rgba;\n    }\n        `)}}decodeUint8(){const t=i.isLittleEndian()?"rgba.rgba=rgba.abgr;":"";return{decode:new r.GlslLibRoutine(`\n        highp float decode(highp vec4 rgba) {\n          rgba = rgba * 255.0; // values need to be de-normalized from [0,1] to [0,255]\n          ${t}\n          highp float Sign = 1.0 - step(128.0,rgba[0])*2.0;\n          highp float Exponent = 2.0 * mod(rgba[0],128.0) + step(128.0,rgba[1]) - 127.0;\n          highp float Mantissa = mod(rgba[1],128.0)*65536.0 + rgba[2]*256.0 +rgba[3] + float(0x800000);\n          highp float Result =  Sign * exp2(Exponent) * (Mantissa * exp2(-23.0 ));\n          return Result;\n      }\n        `)}}static isLittleEndian(){const t=new ArrayBuffer(4),e=new Uint32Array(t),n=new Uint8Array(t);if(e[0]=3735928559,239===n[0])return!0;if(222===n[0])return!1;throw new Error("unknown endianness")}}e.EncodingGlslLib=i},9894:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.FragColorGlslLib=void 0;const r=n(8520),i=n(5060);class o extends r.GlslLib{constructor(t){super(t)}getFunctions(){return Object.assign(Object.assign({},this.setFragColor()),this.getColorAsFloat())}getCustomTypes(){return{}}setFragColor(){const t=(0,i.getGlsl)(this.context.glContext.version);return{setFragColor:new r.GlslLibRoutine(`\n        void setFragColor(float value) {\n            ${t.output} = encode(value);\n        }\n        `,["encoding.encode"])}}getColorAsFloat(){return{getColorAsFloat:new r.GlslLibRoutine("\n        float getColorAsFloat(vec4 color) {\n            return decode(color);\n        }\n        ",["encoding.decode"])}}}e.FragColorGlslLib=o},2848:(t,e)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.replaceInlines=void 0;const n=/@inline[\s\n\r]+(\w+)[\s\n\r]+([0-9a-zA-Z_]+)\s*\(([^)]*)\)\s*{(([^}]|[\n\r])*)}/gm;e.replaceInlines=function(t){const e={};let r;for(;null!==(r=n.exec(t));){const t=r[3].split(",").map((t=>{const e=t.trim().split(" ");return e&&2===e.length?{type:e[0],name:e[1]}:null})).filter((t=>null!==t));e[r[2]]={params:t,body:r[4]}}for(const n in e){const i="(\\w+)?\\s+([_0-9a-zA-Z]+)\\s+=\\s+__FUNC__\\((.*)\\)\\s*;".replace("__FUNC__",n),o=new RegExp(i,"gm");for(;null!==(r=o.exec(t));){const i=r[1],o=r[2],a=r[3].split(","),s=i?`${i} ${o};`:"";let u=e[n].body,c="";e[n].params.forEach(((t,e)=>{t&&(c+=`${t.type} ${t.name} = ${a[e]};\n`)})),u=`${c}\n ${u}`,u=u.replace("return",`${o} = `);const l=`\n      ${s}\n      {\n        ${u}\n      }\n      `;t=t.replace(r[0],l)}}return t.replace(n,"")}},8879:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.GlslPreprocessor=void 0;const r=n(8520),i=n(2848),o=n(5483),a=n(5060);e.GlslPreprocessor=class{constructor(t,e,n,i){this.libs={},this.glslLibRoutineDependencyGraph={},this.context=new r.GlslContext(t,e,n,i),Object.keys(o.glslRegistry).forEach((t=>{const e=new o.glslRegistry[t](this.context);this.libs[t]=e}));const a=this.glslLibRoutineDependencyGraph;for(const t in this.libs){const e=this.libs[t].getFunctions();for(const n in e){const i=t+"."+n;let o;a[i]?(o=a[i],o.routineBody=e[n].routineBody):(o=new r.GlslLibRoutineNode(i,e[n].routineBody),a[i]=o);const s=e[n].dependencies;if(s)for(let t=0;t<s.length;++t)if(a[s[t]])o.addDependency(a[s[t]]);else{const e=new r.GlslLibRoutineNode(s[t]);a[s[t]]=e,o.addDependency(e)}}}}preprocess(){const t=this.context.programInfo;let e=t.shaderSource;return this.context.programInfo.hasMain||(e=`${e}\n      ${(0,a.getDefaultFragShaderMain)(this.context.glContext.version,this.context.outputTextureLayout.shape.length)}`),e=(0,i.replaceInlines)(e),`${(0,a.getFragShaderPreamble)(this.context.glContext.version)}\n    ${this.getUniforms(t.inputNames,t.variables)}\n    ${this.getImports(e)}\n    ${e}`}getImports(t){const e=this.selectGlslLibRoutinesToBeIncluded(t);if(0===e.length)return"";let n="";for(let t=0;t<e.length;++t){if(!e[t].routineBody)throw new Error(`Missing body for the Glsl Library routine: ${e[t].name}`);n+=e[t].routineBody+"\n"}return n}selectGlslLibRoutinesToBeIncluded(t){const e=[];return Object.keys(this.glslLibRoutineDependencyGraph).forEach((n=>{const r=n.split(".")[1];-1!==t.indexOf(r)&&e.push(this.glslLibRoutineDependencyGraph[n])})),r.TopologicalSortGlslRoutines.returnOrderedNodes(e)}getUniforms(t,e){const n=[];if(t)for(const e of t)n.push(`uniform sampler2D ${e};`);if(e)for(const t of e)n.push(`uniform ${t.type} ${t.name}${t.arrayLength?`[${t.arrayLength}]`:""};`);return n.join("\n")}}},5483:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.glslRegistry=void 0;const r=n(5107),i=n(7341),o=n(9894),a=n(2655),s=n(3891);e.glslRegistry={encoding:i.EncodingGlslLib,fragcolor:o.FragColorGlslLib,vec:s.VecGlslLib,shapeUtils:a.ShapeUtilsGlslLib,coordinates:r.CoordsGlslLib}},2655:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.ShapeUtilsGlslLib=void 0;const r=n(8520);class i extends r.GlslLib{constructor(t){super(t)}getFunctions(){return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},this.bcastIndex()),this.bcastMatmulIndex()),this.offsetToIndices()),this.indicesToOffset()),this.incrementIndices())}getCustomTypes(){return{}}bcastIndex(){const t=this.context.outputTextureLayout.shape.length,e={};return this.context.programInfo.inputNames.forEach(((n,i)=>{const o=this.context.inputTextureLayouts[i].unpackedShape;if(o.length<=t){const i=o.length,a=t-i,s=`bcastIndices_${n}`;let u="";for(let t=0;t<i;++t)u+=`\n          realIndices[${t}] = int( mod(float(bcastedIndices[${a+t}]), ${o[t]}.0) );\n          `;const c=`\n        void ${s} (int bcastedIndices[${t}], out int realIndices[${i}]) {\n          ${u}\n        }\n        `;e[s]=new r.GlslLibRoutine(c)}})),e}bcastMatmulIndex(){const t=this.context.outputTextureLayout.shape.length,e={};return this.context.programInfo.inputNames.forEach(((n,i)=>{const o=this.context.inputTextureLayouts[i].shape;if(!(o.length<2||o.length>t)){const i=o.length,a=t-i,s=`bcastMatmulIndices_${n}`;let u="";for(let t=0;t<i-2;++t)u+=`\n          realIndices[${t}] = int( mod(float(bcastedIndices[${a+t}]), ${o[t]}.0) );\n          `;const c=`\n        void ${s}(int bcastedIndices[${t}], out int realIndices[${i}]) {\n          ${u}\n          realIndices[${i-1}] = bcastedIndices[${t-1}];\n          realIndices[${i-2}] = bcastedIndices[${t-2}];\n        }\n        `;e[s]=new r.GlslLibRoutine(c)}})),e}indicesToOffset(){const t={};return this.context.programInfo.inputNames.forEach(((e,n)=>{const o=this.context.inputTextureLayouts[n].shape,a=this.context.inputTextureLayouts[n].strides,s=o.length;let u=`indicesToOffset_${e}`;t[u]=new r.GlslLibRoutine(i.indexToOffsetSingle(u,s,a)),u=`indicesToOffset_${e}_T`,t[u]=new r.GlslLibRoutine(i.indexToOffsetSingle(u,s,a.slice().reverse()))})),t}static indexToOffsetSingle(t,e,n){let r="";for(let t=e-1;t>=0;--t)r+=`\n        offset += indices[${t}] * ${n[t]};\n        `;return`\n      int ${t}(int indices[${e}]) {\n        int offset = 0;\n        ${r}\n        return offset;\n      }\n      `}offsetToIndices(){const t={};return this.context.programInfo.inputNames.forEach(((e,n)=>{const o=this.context.inputTextureLayouts[n].shape,a=this.context.inputTextureLayouts[n].strides,s=o.length;let u=`offsetToIndices_${e}`;t[u]=new r.GlslLibRoutine(i.offsetToIndicesSingle(u,s,a)),u=`offsetToIndices_${e}_T`,t[u]=new r.GlslLibRoutine(i.offsetToIndicesSingle(u,s,a.slice().reverse()))})),t}static offsetToIndicesSingle(t,e,n){const r=[];for(let t=0;t<e-1;++t)r.push(`\n      indices[${t}] = offset / ${n[t]};`),r.push(`\n        offset -= indices[${t}] * ${n[t]};`);return r.push(`\n      indices[${e-1}] = offset;`),`\n      void ${t}(int offset, out int indices[${e}]) {\n        ${r.join("")}\n      }\n      `}incrementIndices(){const t={};return this.context.programInfo.inputNames.forEach(((e,n)=>{const i=this.context.inputTextureLayouts[n].shape,o=i.length,a=`incrementIndices_${e}`;let s="";for(let t=0;t<o;++t)s+=`\n        shape[${t}] = ${i[t]};`;const u=`\n        void ${a}(int axis, out int indices[${o}]) {\n          int shape[${o}];\n          ${s};\n          for(int i = ${o} -1 ; i >= 0; --i) {\n            if(i > axis) continue;\n            indices[i] += 1;\n            if(indices[i] < shape[i]) {\n              break;\n            }\n            indices[i] = 0;\n          }\n        }\n        `;t[a]=new r.GlslLibRoutine(u)})),t}}e.ShapeUtilsGlslLib=i},5060:(t,e)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.getDefaultFragShaderMain=e.getFragShaderPreamble=e.getVertexShaderSource=e.getGlsl=void 0;const n={version:"",attribute:"attribute",varyingVertex:"varying",varyingFrag:"varying",texture2D:"texture2D",output:"gl_FragColor",outputDeclaration:""},r={version:"#version 300 es",attribute:"in",varyingVertex:"out",varyingFrag:"in",texture2D:"texture",output:"outputColor",outputDeclaration:"out vec4 outputColor;"};function i(t){return 1===t?n:r}e.getGlsl=i,e.getVertexShaderSource=function(t){const e=i(t);return`${e.version}\n      precision highp float;\n      ${e.attribute} vec3 position;\n      ${e.attribute} vec2 textureCoord;\n\n      ${e.varyingVertex} vec2 TexCoords;\n\n      void main()\n      {\n          gl_Position = vec4(position, 1.0);\n          TexCoords = textureCoord;\n      }`},e.getFragShaderPreamble=function(t){const e=i(t);return`${e.version}\n    precision highp float;\n    precision highp int;\n    precision highp sampler2D;\n    ${e.varyingFrag} vec2 TexCoords;\n    ${e.outputDeclaration}\n    const vec2 halfCR = vec2(0.5, 0.5);\n\n    // Custom vector types to handle higher dimenalities.\n    struct ivec5\n    {\n      int x;\n      int y;\n      int z;\n      int w;\n      int u;\n    };\n\n    struct ivec6\n    {\n      int x;\n      int y;\n      int z;\n      int w;\n      int u;\n      int v;\n    };\n\n    int imod(int x, int y) {\n      return x - y * (x / y);\n    }\n\n    `},e.getDefaultFragShaderMain=function(t,e){return`\n  void main() {\n    int indices[${e}];\n    toVec(TexCoords, indices);\n    vec4 result = vec4(process(indices));\n    ${i(t).output} = result;\n  }\n  `}},3891:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.VecGlslLib=void 0;const r=n(8520);class i extends r.GlslLib{constructor(t){super(t)}getCustomTypes(){return{}}getFunctions(){return Object.assign(Object.assign(Object.assign(Object.assign({},this.binaryVecFunctions()),this.copyVec()),this.setVecItem()),this.getVecItem())}binaryVecFunctions(){const t=this.context.outputTextureLayout.shape.length,e={add:"+=",sub:"-=",mul:"*=",div:"/="},n={};for(const i in e){const o=`${i}Vec`;let a="";for(let n=0;n<t;++n)a+=`\n          dest[${n}] ${e[i]} src[${n}];\n          `;const s=`\n        void ${o}(int src[${t}], out int dest[${t}]) {\n          ${a}\n        }\n        `;n[o]=new r.GlslLibRoutine(s)}return n}copyVec(){const t=this.context.outputTextureLayout.shape.length;let e="";for(let n=0;n<t;++n)e+=`\n        dest[${n}] = src[${n}];\n        `;const n=`\n      void copyVec(int src[${t}], out int dest[${t}]) {\n        ${e}\n      }\n      `;return{copyVec:new r.GlslLibRoutine(n)}}setVecItem(){const t=this.context.outputTextureLayout.shape.length;let e=`\n        if(index < 0)\n            index =${t} + index;\n        if (index == 0)\n            m[0] = value;\n        `;for(let n=1;n<t-1;++n)e+=`\n        else if (index == ${n})\n            m[${n}] = value;\n            `;e+=`\n        else\n            m[${t-1}] = value;\n        `;const n=`\n      void setVecItem(out int m[${t}], int index, int value) {\n        ${e}\n      }\n        `;return{setVecItem:new r.GlslLibRoutine(n)}}getVecItem(){const t=this.context.outputTextureLayout.shape.length;let e=`\n        if(index < 0)\n            index = ${t} + index;\n        if (index == 0)\n            return m[0];\n      `;for(let n=1;n<t-1;++n)e+=`\n        else if (index == ${n})\n            return m[${n}];\n      `;e+=`\n        else\n            return m[${t-1}];\n        `;const n=`\n      int getVecItem(int m[${t}], int index) {\n        ${e}\n      }\n    `;return{getVecItem:new r.GlslLibRoutine(n)}}}e.VecGlslLib=i},8316:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.WebGLInferenceHandler=void 0;const r=n(6231),i=n(9162),o=n(2517),a=n(2403),s=n(7019),u=n(8710),c=n(5611),l=n(4057),p=n(2039);e.WebGLInferenceHandler=class{constructor(t){this.session=t,this.packedTextureDataCache=new Map,this.unpackedTextureDataCache=new Map}calculateTextureWidthAndHeight(t,e){return(0,l.calculateTextureWidthAndHeight)(this.session.layoutStrategy,t,e)}executeProgram(t,e){if(e.length<t.inputNames.length)throw new Error(`Input size mustn't be less than ${t.inputNames.length}.`);if(t.inputNames.length!==t.inputTypes.length)throw new Error("input names size does not match input types");const n=[];for(let r=0;r<t.inputNames.length;++r)n[r]=this.getOrCreateTextureData(e[r],t.inputTypes[r]);const r=((t,e)=>{const n=e.map((t=>`${t.unpackedShape.join(",")};${t.width}x${t.height}`)).join("_");let r=t.name;return t.cacheHint&&(r+="["+t.cacheHint+"]"),r+=":"+n,r})(t,n);let i=this.session.programManager.getArtifact(r);const o=i?i.programInfo:"function"==typeof t.get?t.get():t,a=(0,l.createTextureLayoutFromTextureType)(this.session.layoutStrategy,o.output.dims,o.output.textureType),s=this.createTextureData(a,o.output.type);return i||(i=this.session.programManager.build(o,n,s),this.session.programManager.setArtifact(r,i)),this.runProgram(i,n,s),s}run(t,e){return this.executeProgram(t,e).tensor}runProgram(t,e,n){for(let n=0;n<e.length;++n)if(!!e[n].isPacked!=(t.programInfo.inputTypes[n]===p.TextureType.packed))throw new Error(`input[${n}] property packed inconsistent`);if(!!n.isPacked!=(t.programInfo.output.textureType===p.TextureType.packed))throw new Error("output property packed inconsistent");this.session.programManager.run(t,e,n)}getOrCreateTextureData(t,e){let n=this.getTextureData(t.dataId,e===p.TextureType.packed);if(!n&&(n=this.getTextureData(t.dataId,e!==p.TextureType.packed),n))return e===p.TextureType.packed?this.pack(n):this.unpack(n);if(!n){const r=(0,l.createTextureLayoutFromTextureType)(this.session.layoutStrategy,t.dims,e);if(e===p.TextureType.packedLastDimension){const n=1,r=4,i=t.dims;if(4===i.length){const o=[i[0],Math.ceil(i[1]*i[2]*i[3]/r)],a=(0,l.createTextureLayoutFromTextureType)(this.session.layoutStrategy,o,e);let s=t.numberData;if(i[1]*i[2]*i[3]%r!=0){const e=i[0],o=i[1]*i[2]*i[3],a=Math.ceil(o*n/r)*r;s=new Float32Array(e*a);for(let r=0;r<e;++r){const e=r*o,i=r*a+r%n*o;s.set(t.numberData.subarray(e,e+o),i)}}return this.createTextureData(a,t.type,s,t,1)}}if(e===p.TextureType.packed){const e=(0,l.createTextureLayoutFromShape)(this.session.layoutStrategy,t.dims,1,[],{reverseWH:!0}),r=this.createTextureData(e,t.type,t.numberData,t,1);n=this.pack(r)}else n=this.createTextureData(r,t.type,t.numberData,t,1)}return n}createTextureDataFromLayoutBindTensor(t,e,n,r){return this.createTextureData(t,e,n,r,1)}createTextureData(t,e,n,i,o){r.Logger.verbose("InferenceHandler",`Creating TextureData: layout:[${JSON.stringify(t)}]`);const a=this.session.textureManager.createTextureFromLayout(e,t,n,o);return this.createTextureDataFromTexture(t,e,a,i)}reshapeUnpacked(t,e){const n=this.getOrCreateTextureData(t,p.TextureType.unpacked),r={channels:n.channels,height:n.height,width:n.width,shape:0!==e.length?e:[1],strides:o.ShapeUtil.computeStrides(e),unpackedShape:e};return this.createTextureDataFromTexture(r,t.type,n.texture).tensor}reshapePacked(t,e){const n=this.getOrCreateTextureData(t,p.TextureType.packed);if((0,s.isReshapeCheap)(t.dims,e)){const r={channels:n.channels,height:n.height,width:n.width,shape:0!==e.length?e:[1],strides:o.ShapeUtil.computeStrides(e),unpackedShape:e,isPacked:!0};return this.createTextureDataFromTexture(r,t.type,n.texture).tensor}const r=(0,s.processDims3D)(t.dims),i=(0,s.processDims3D)(e),a=this.reshapePacked(t,r),u=this.run((0,s.createPackedReshape3DProgramInfoLoader)(this,a,i),[a]);return this.reshapePacked(u,e)}cast(t,e){const n=this.getOrCreateTextureData(t,p.TextureType.unpacked);return this.createTextureDataFromTexture(n,e,n.texture).tensor}createTextureDataFromTexture(t,e,n,r,o){const a=Object.assign(Object.assign({},t),{tensor:r||new i.Tensor(t.unpackedShape,e,(t=>this.readTexture(a)),(async t=>this.readTextureAsync(a)),void 0,o),texture:n});return this.setTextureData(a.tensor.dataId,a,t.isPacked),a}getTextureData(t,e=!1){return this.session.isInitializer(t)?this.session.getTextureData(t,e):e?this.packedTextureDataCache.get(t):this.unpackedTextureDataCache.get(t)}setTextureData(t,e,n=!1){this.session.isInitializer(t)?this.session.setTextureData(t,e,n):(n?this.packedTextureDataCache:this.unpackedTextureDataCache).set(t,e)}isTextureLayoutCached(t,e=!1){return!!this.getTextureData(t.dataId,e)}dispose(){this.session.textureManager.clearActiveTextures(),this.packedTextureDataCache.forEach((t=>this.session.textureManager.releaseTexture(t))),this.packedTextureDataCache=new Map,this.unpackedTextureDataCache.forEach((t=>this.session.textureManager.releaseTexture(t))),this.unpackedTextureDataCache=new Map}readTexture(t){return t.isPacked?this.readTexture(this.unpack(t)):this.session.backend.glContext.isFloat32DownloadSupported?this.session.textureManager.readTexture(t,t.tensor.type,t.channels):this.session.textureManager.readUint8TextureAsFloat((0,u.encodeAsUint8)(this,t))}async readTextureAsync(t){return t.isPacked?this.readTextureAsync(this.unpack(t)):this.session.backend.glContext.isFloat32DownloadSupported?this.session.textureManager.readTextureAsync(t,t.tensor.type,t.channels):this.session.textureManager.readUint8TextureAsFloat((0,u.encodeAsUint8)(this,t))}pack(t){return this.executeProgram((0,a.createPackProgramInfoLoader)(this,t.tensor),[t.tensor])}unpack(t){return this.executeProgram((0,c.createUnpackProgramInfoLoader)(this,t.tensor),[t.tensor])}}},1640:function(t,e,n){"use strict";var r=this&&this.__createBinding||(Object.create?function(t,e,n,r){void 0===r&&(r=n);var i=Object.getOwnPropertyDescriptor(e,n);i&&!("get"in i?!e.__esModule:i.writable||i.configurable)||(i={enumerable:!0,get:function(){return e[n]}}),Object.defineProperty(t,r,i)}:function(t,e,n,r){void 0===r&&(r=n),t[r]=e[n]}),i=this&&this.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),o=this&&this.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)"default"!==n&&Object.prototype.hasOwnProperty.call(t,n)&&r(e,t,n);return i(e,t),e};Object.defineProperty(e,"__esModule",{value:!0}),e.WEBGL_OP_RESOLVE_RULES=void 0;const a=n(2898),s=o(n(7839)),u=n(4196),c=n(2069),l=n(8138),p=n(9663),f=n(5193),d=n(7992),h=n(1253),g=n(4776),b=n(6572),m=n(3346),y=n(5623),_=n(2870),v=n(2143),w=n(4939),x=n(718),T=n(2268),S=n(8117),O=n(2278),A=n(5524),E=n(5975),I=n(3933),P=n(6558),D=n(5723),$=n(3738),k=o(n(4909)),C=n(8428),F=n(9793);e.WEBGL_OP_RESOLVE_RULES=[["Abs","","6+",k.abs],["Acos","","7+",k.acos],["Add","","7+",s.add],["And","","7+",s.and],["Asin","","7+",k.asin],["Atan","","7+",k.atan],["AveragePool","","7+",v.averagePool,v.parseAveragePoolAttributes],["BatchNormalization","","7+",a.batchNormalization,a.parseBatchNormalizationAttributes],["Cast","","6+",u.cast,u.parseCastAttributes],["Ceil","","6+",k.ceil],["Clip","","6-10",k.clip,k.parseClipAttributes],["Clip","","11+",k.clipV11],["Concat","","4+",c.concat,c.parseConcatAttributes],["Conv","","1+",l.conv,l.parseConvAttributes],["ConvTranspose","","1+",p.convTranspose,p.parseConvTransposeAttributes],["Cos","","7+",k.cos],["Div","","7+",s.div],["Dropout","","7+",k.identity],["DepthToSpace","","1+",f.depthToSpace,f.parseDepthToSpaceAttributes],["Equal","","7+",s.equal],["Elu","","6+",k.elu,k.parseEluAttributes],["Exp","","6+",k.exp],["Flatten","","1+",d.flatten,d.parseFlattenAttributes],["Floor","","6+",k.floor],["FusedConv","com.microsoft","1+",l.conv,l.parseConvAttributes],["Gather","","1+",h.gather,h.parseGatherAttributes],["Gemm","","7-10",g.gemm,g.parseGemmAttributesV7],["Gemm","","11+",g.gemm,g.parseGemmAttributesV11],["GlobalAveragePool","","1+",v.globalAveragePool,v.parseGlobalAveragePoolAttributes],["GlobalMaxPool","","1+",v.globalMaxPool],["Greater","","7+",s.greater],["Identity","","1+",k.identity],["ImageScaler","","1+",b.imageScaler,b.parseImageScalerAttributes],["InstanceNormalization","","6+",m.instanceNormalization,m.parseInstanceNormalizationAttributes],["LeakyRelu","","6+",k.leakyRelu,k.parseLeakyReluAttributes],["Less","","7+",s.less],["Log","","6+",k.log],["MatMul","","1+",y.matMul,y.parseMatMulAttributes],["MaxPool","","1+",v.maxPool,v.parseMaxPoolAttributes],["Mul","","7+",s.mul],["Neg","","6+",k.neg],["Not","","1+",k.not],["Or","","7+",s.or],["Pad","","2-10",_.padV2,_.parsePadAttributesV2],["Pad","","11+",_.padV11,_.parsePadAttributesV11],["Pow","","7+",s.pow],["PRelu","","7+",s.pRelu],["ReduceLogSum","","1+",w.reduceLogSum,w.parseReduceAttributes],["ReduceMax","","1+",w.reduceMax,w.parseReduceAttributes],["ReduceMean","","1+",w.reduceMean,w.parseReduceAttributes],["ReduceMin","","1+",w.reduceMin,w.parseReduceAttributes],["ReduceProd","","1+",w.reduceProd,w.parseReduceAttributes],["ReduceSum","","1-12",w.reduceSum,w.parseReduceAttributes],["ReduceSumSquare","","1+",w.reduceLogSumSquare,w.parseReduceAttributes],["Relu","","6+",k.relu],["Reshape","","5+",x.reshape],["Resize","","10",T.resize,T.parseResizeAttributesV10],["Resize","","11+",T.resize,T.parseResizeAttributesV11],["Shape","","1+",S.shape],["Sigmoid","","6+",k.sigmoid],["Sin","","7+",k.sin],["Slice","","10+",O.sliceV10],["Slice","","1-9",O.slice,O.parseSliceAttributes],["Softmax","","1-12",A.softmax,A.parseSoftmaxAttributes],["Softmax","","13+",A.softmaxV13,A.parseSoftmaxAttributesV13],["Split","","2-12",E.split,E.parseSplitAttributes],["Sqrt","","6+",k.sqrt],["Squeeze","","1-12",I.squeeze,I.parseSqueezeAttributes],["Squeeze","","13+",I.squeezeV13],["Sub","","7+",s.sub],["Sum","","6+",P.sum],["Tan","","7+",k.tan],["Tanh","","6+",k.tanh],["Tile","","6+",D.tile],["Transpose","","1+",$.transpose,$.parseTransposeAttributes],["Upsample","","7-8",F.upsample,F.parseUpsampleAttributesV7],["Upsample","","9",F.upsample,F.parseUpsampleAttributesV9],["Unsqueeze","","1-12",C.unsqueeze,C.parseUnsqueezeAttributes],["Unsqueeze","","13+",C.unsqueezeV13],["Xor","","7+",s.xor]]},2898:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.parseBatchNormalizationAttributes=e.batchNormalization=void 0;const r=n(246),i=n(5060),o=n(2039),a={name:"BatchNormalization",inputNames:["A","Scale","B","Mean","Variance"],inputTypes:[o.TextureType.unpacked,o.TextureType.unpacked,o.TextureType.unpacked,o.TextureType.unpacked,o.TextureType.unpacked]};e.batchNormalization=(t,e,n)=>(u(e),[t.run(Object.assign(Object.assign({},a),{cacheHint:n.cacheKey,get:()=>s(t,e,n)}),e)]),e.parseBatchNormalizationAttributes=t=>{const e=t.attributes.getFloat("epsilon",1e-5),n=t.attributes.getFloat("momentum",.9),i=t.attributes.getInt("spatial",1);return(0,r.createAttributeWithCacheKey)({epsilon:e,momentum:n,spatial:i})};const s=(t,e,n)=>{const r=(0,i.getGlsl)(t.session.backend.glContext.version),s=e[0].dims.length,[u,c]=t.calculateTextureWidthAndHeight(e[1].dims,o.TextureType.unpacked),l=`\n  float process(int[${s}] indices) {\n    vec2 position = offsetToCoords(indices[1], ${u}, ${c});\n    float scale = getColorAsFloat(${r.texture2D}(Scale, position));\n    float mean = getColorAsFloat(${r.texture2D}(Mean, position));\n    float variance = getColorAsFloat(${r.texture2D}(Variance, position));\n    float b = getColorAsFloat(${r.texture2D}(B, position));\n\n    return scale * ( (_A(indices) - mean) / sqrt(variance + float(${n.epsilon})) ) + b;\n  }`;return Object.assign(Object.assign({},a),{output:{dims:e[0].dims,type:e[0].type,textureType:o.TextureType.unpacked},shaderSource:l})},u=t=>{if(!t||5!==t.length)throw new Error("BatchNormalization requires 5 inputs.");const e=t[0],n=t[1],r=t[2],i=t[3],o=t[4];if(e.dims.length<3||1!==n.dims.length||1!==r.dims.length||1!==i.dims.length||1!==o.dims.length)throw new Error("invalid input shape.");if(n.dims[0]!==e.dims[1]||r.dims[0]!==e.dims[1]||i.dims[0]!==e.dims[1]||o.dims[0]!==e.dims[1])throw new Error("invalid input shape.");if("float32"!==e.type&&"float64"!==e.type||"float32"!==n.type&&"float64"!==n.type||"float32"!==r.type&&"float64"!==r.type||"float32"!==i.type&&"float64"!==i.type||"float32"!==o.type&&"float64"!==o.type)throw new Error("invalid input tensor types.")}},7839:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.xor=e.sub=e.pRelu=e.pow=e.or=e.mul=e.less=e.greater=e.equal=e.div=e.and=e.add=e.glslPRelu=e.glslPow=e.glslXor=e.glslOr=e.glslAnd=e.glslLess=e.glslGreater=e.glslEqual=e.glslSub=e.glslMul=e.glslDiv=e.glslAdd=void 0;const r=n(2517),i=n(8520),o=n(5060),a=n(2039);function s(){const t="add_";return{body:`\n  float ${t}(float a, float b) {\n    return a + b;\n  }\n  vec4 ${t}(vec4 v1, vec4 v2) {\n    return v1 + v2;\n  }\n  `,name:t,type:i.FunctionType.ValueBased}}function u(){const t="div_";return{body:`\n  float ${t}(float a, float b) {\n    return a / b;\n  }\n  vec4 ${t}(vec4 v1, vec4 v2) {\n    return v1 / v2;\n  }\n  `,name:t,type:i.FunctionType.ValueBased}}function c(){const t="mul_";return{body:`\n  float ${t}(float a, float b) {\n    return a * b;\n  }\n  vec4 ${t}(vec4 v1, vec4 v2) {\n    return v1 * v2;\n  }\n  `,name:t,type:i.FunctionType.ValueBased}}function l(){const t="sub_";return{body:`\n  float ${t}(float a, float b) {\n    return a - b;\n  }\n  vec4 ${t}(vec4 v1, vec4 v2) {\n    return v1 - v2;\n  }\n  `,name:t,type:i.FunctionType.ValueBased}}function p(){const t="equal_";return{body:`\n  float ${t}(float a, float b) {\n    return float(a == b);\n  }\n  vec4 ${t}(vec4 v1, vec4 v2) {\n    return vec4(equal(v1, v2));\n  }\n  `,name:t,type:i.FunctionType.ValueBased}}function f(){const t="greater_";return{body:`\n  float ${t}(float a, float b) {\n    return float(a > b);\n  }\n  vec4 ${t}(vec4 v1, vec4 v2) {\n    return vec4( v1.r > v2.r ,\n      v1.g > v2.g,\n      v1.b > v2.b,\n      v1.a > v2.a );\n  }\n  `,name:t,type:i.FunctionType.ValueBased}}function d(){const t="less_";return{body:`\n  float ${t}(float a, float b) {\n    return float(a < b);\n  }\n  vec4 ${t}(vec4 v1, vec4 v2) {\n    return vec4( v1.r < v2.r ,\n                v1.g < v2.g,\n                v1.b < v2.b,\n                v1.a < v2.a );\n  }\n  `,name:t,type:i.FunctionType.ValueBased}}function h(){const t="and_";return{body:`\n  float ${t}(float a, float b) {\n    return float( bool(a) && bool(b) );\n  }\n  vec4 ${t}(vec4 v1, vec4 v2) {\n    bvec4 b1 = bvec4(v1);\n    bvec4 b2 = bvec4(v2);\n    return vec4( b1.r && b2.r ,\n                b1.g && b2.g,\n                b1.b && b2.b,\n                b1.a && b2.a );\n  }\n  `,name:t,type:i.FunctionType.ValueBased}}function g(){const t="or_";return{body:`\n  float ${t}(float a, float b) {\n    return float( bool(a) || bool(b) );\n  }\n  vec4 ${t}(vec4 v1, vec4 v2) {\n    bvec4 b1 = bvec4(v1);\n    bvec4 b2 = bvec4(v2);\n    return vec4( b1.r || b2.r ,\n                b1.g || b2.g,\n                b1.b || b2.b,\n                b1.a || b2.a );\n  }\n  `,name:t,type:i.FunctionType.ValueBased}}function b(){const t="xor_";return{body:`\n  float ${t}(float a, float b) {\n    return float( bool(a) ^^ bool(b) );\n  }\n  vec4 ${t}(vec4 v1, vec4 v2) {\n    bvec4 b1 = bvec4(v1);\n    bvec4 b2 = bvec4(v2);\n    return vec4( b1.r ^^ b2.r ,\n                b1.g ^^ b2.g,\n                b1.b ^^ b2.b,\n                b1.a ^^ b2.a );\n  }\n  `,name:t,type:i.FunctionType.ValueBased}}function m(){return function(t){const e=`${t}_`;return{body:`\n  float ${e}(float a, float b) {\n    return ${t}(a, b);\n  }\n  vec4 ${e}(vec4 v1, vec4 v2) {\n    return ${t}(v1, v2);\n  }\n  `,name:e,type:i.FunctionType.ValueBased}}("pow")}function y(){const t="prelu_";return{body:`\n  float ${t}(float a, float b) {\n    return a < 0.0 ? a * b: a;\n  }\n  vec4 ${t}(vec4 v1, vec4 v2) {\n    return vec4(\n      v1.r < 0.0 ? v1.r * v2.r: v1.r,\n      v1.g < 0.0 ? v1.g * v2.g: v1.g,\n      v1.b < 0.0 ? v1.b * v2.b: v1.b,\n      v1.a < 0.0 ? v1.a * v2.a: v1.a\n      );\n  }\n  `,name:t,type:i.FunctionType.ValueBased}}e.glslAdd=s,e.glslDiv=u,e.glslMul=c,e.glslSub=l,e.glslEqual=p,e.glslGreater=f,e.glslLess=d,e.glslAnd=h,e.glslOr=g,e.glslXor=b,e.glslPow=m,e.glslPRelu=y;const _=(t,e,n,r=e[0].type,i)=>{const o=t.session.pack?a.TextureType.packed:a.TextureType.unpacked;return{name:n.name,inputNames:["A","B"],inputTypes:[o,o],cacheHint:i,get:()=>v(t,e,n,r)}},v=(t,e,n,i=e[0].type)=>{const s=t.session.pack?a.TextureType.packed:a.TextureType.unpacked,u=!r.ShapeUtil.areEqual(e[0].dims,e[1].dims);let c=e[0].dims;const l=t.session.pack;if(u){const a=r.BroadcastUtil.calcShape(e[0].dims,e[1].dims,!1);if(!a)throw new Error("Can't perform binary op on the given tensors");c=a;const u=c.length,p=0!==e[0].dims.length?e[0].dims.length:1,f=0!==e[1].dims.length?e[1].dims.length:1,d=0!==e[0].dims.length?"bcastIndices_A(indices, aindices);":"aindices[0] = 0;",h=0!==e[1].dims.length?"bcastIndices_B(indices, bindices);":"bindices[0] = 0;",g=(0,o.getGlsl)(t.session.backend.glContext.version),b=l?`\n      ${n.body}\n      void main() {\n        vec4 a = getAAtOutCoords();\n        vec4 b = getBAtOutCoords();\n        vec4 result = ${n.name}(a, b);\n        ${g.output} = result;\n      }`:`\n      ${n.body}\n      float process(int indices[${u}]) {\n        int aindices[${p}];\n        int bindices[${f}];\n        ${d}\n        ${h}\n        return ${n.name}(_A(aindices), _B(bindices));\n      }`;return{name:n.name,inputNames:["A","B"],inputTypes:[s,s],output:{dims:c,type:i,textureType:s},shaderSource:b,hasMain:l}}const p=(0,o.getGlsl)(t.session.backend.glContext.version),f=`\n    ${n.body}\n    void main() {\n      vec4 v1 = ${p.texture2D}(A, TexCoords);\n      vec4 v2 = ${p.texture2D}(B, TexCoords);\n      vec4 result = ${n.name}(v1, v2);\n      ${p.output} = result;\n    }\n    `;return{name:n.name,inputNames:["A","B"],inputTypes:[s,s],output:{dims:e[0].dims,type:i,textureType:s},shaderSource:f,hasMain:!0}};e.add=(t,e)=>[t.run(_(t,e,s()),e)],e.and=(t,e)=>[t.run(_(t,e,h(),"bool"),e)],e.div=(t,e)=>[t.run(_(t,e,u()),e)],e.equal=(t,e)=>[t.run(_(t,e,p(),"bool"),e)],e.greater=(t,e)=>[t.run(_(t,e,f(),"bool"),e)],e.less=(t,e)=>[t.run(_(t,e,d(),"bool"),e)],e.mul=(t,e)=>[t.run(_(t,e,c()),e)],e.or=(t,e)=>[t.run(_(t,e,g(),"bool"),e)],e.pow=(t,e)=>[t.run(_(t,e,m()),e)],e.pRelu=(t,e)=>[t.run(_(t,e,y()),e)],e.sub=(t,e)=>[t.run(_(t,e,l()),e)],e.xor=(t,e)=>[t.run(_(t,e,b(),"bool"),e)]},4196:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.parseCastAttributes=e.cast=void 0;const r=n(2517);e.cast=(t,e,n)=>(i(e),[t.cast(e[0],n)]),e.parseCastAttributes=t=>r.ProtoUtil.tensorDataTypeFromProto(t.attributes.getInt("to"));const i=t=>{if(!t||1!==t.length)throw new Error("Cast requires 1 input.");if("string"===t[0].type)throw new Error("Invalid input type.")}},1163:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.createPackedConcatProgramInfoLoader=void 0;const r=n(5060),i=n(2039),o=n(9390),a=n(2827);e.createPackedConcatProgramInfoLoader=(t,e,n)=>{const u=(c=e.length,l=n.cacheKey,{name:"Concat (packed)",inputNames:Array.from({length:c},((t,e)=>`X${e}`)),inputTypes:Array(c).fill(i.TextureType.packed),cacheHint:l});var c,l;return Object.assign(Object.assign({},u),{get:()=>((t,e,n,u)=>{const c=n[0].dims.slice();if(u>=c.length||u<-1*c.length)throw new Error("axis specified for concat doesn't match input dimensionality");u<0&&(u=c.length+u);const l=c.slice(0);for(let t=1;t<n.length;t++){const e=n[t].dims.slice();for(let t=0;t<c.length;t++)if(t===u)l[u]+=e[t];else if(c[t]!==e[t])throw new Error("non concat dimensions must match")}const p=l.length,f=(0,a.getChannels)("coords",p),d=(0,o.getCoordsDataType)(p),h=(0,a.unpackFromChannel)(),g=n.map((t=>t.dims)),b=(0,o.getGlChannels)(p),m=new Array(g.length-1);m[0]=g[0][u];for(let t=1;t<m.length;t++)m[t]=m[t-1]+g[t][u];const y=b[u],_=b.slice(-2),v=b.join();let w=`if (${y} < ${m[0]}) {\n        return getChannel(\n            getX0(${v}), vec2(${_.join()}));\n        }`;for(let t=1;t<m.length;t++){const e=m[t-1];w+=`\n            if (${y} < ${m[t]}  && ${y} >= ${m[t-1]}) {\n              return getChannel(\n                getX${t}(${s(b,y,e)}),\n                vec2(${s(_,y,e)}));\n            }`}const x=m.length,T=m[m.length-1];w+=`\n            return getChannel(\n              getX${x}(${s(b,y,T)}),\n              vec2(${s(_,y,T)}));`;const S=(0,r.getGlsl)(t.session.backend.glContext.version),O=`\n          ${h}\n          float getValue(${b.map((t=>"int "+t))}) {\n            ${w}\n          }\n\n          void main() {\n            ${d} coords = getOutputCoords();\n            int lastDim = coords.${b[p-1]};\n            coords.${b[p-1]} = coords.${b[p-2]};\n            coords.${b[p-2]} = lastDim;\n\n            vec4 result = vec4(getValue(${f}), 0., 0., 0.);\n\n            ${f[p-1]} = ${f[p-1]} + 1;\n            if (${f[p-1]} < ${l[p-1]}) {\n              result.g = getValue(${f});\n            }\n\n            ${f[p-2]} = ${f[p-2]} + 1;\n            if (${f[p-2]} < ${l[p-2]}) {\n              result.a = getValue(${f});\n            }\n\n            ${f[p-1]} = ${f[p-1]} - 1;\n            if (${f[p-2]} < ${l[p-2]} &&\n                ${f[p-1]} < ${l[p-1]}) {\n              result.b = getValue(${f});\n            }\n            ${S.output} = result;\n          }\n        `;return Object.assign(Object.assign({},e),{output:{dims:l,type:n[0].type,textureType:i.TextureType.packed},shaderSource:O,hasMain:!0})})(t,u,e,n.axis)})};const s=(t,e,n)=>{const r=t.indexOf(e);return t.map(((t,e)=>e===r?`${t} - ${n}`:t)).join()}},2069:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.parseConcatAttributes=e.concat=void 0;const r=n(246),i=n(2039),o=n(1163);e.concat=(t,e,n)=>(p(e),t.session.pack&&e[0].dims.length>1?[t.run((0,o.createPackedConcatProgramInfoLoader)(t,e,n),e)]:[t.run(a(t,e,n),e)]);const a=(t,e,n)=>{const r=(o=e.length,a=n.cacheKey,{name:"Concat",inputNames:Array.from({length:o},((t,e)=>`X${e}`)),inputTypes:Array(o).fill(i.TextureType.unpacked),cacheHint:a});var o,a;return Object.assign(Object.assign({},r),{get:()=>((t,e,n,r)=>{const o=n[0].dims.slice();if(r>=o.length||r<-1*o.length)throw new Error("axis specified for concat doesn't match input dimensionality");r<0&&(r=o.length+r);const a=o.slice(0);for(let t=1;t<n.length;t++){const e=n[t].dims.slice();for(let t=0;t<o.length;t++)if(t===r)a[r]+=e[t];else if(o[t]!==e[t])throw new Error("non concat dimensions must match")}const p=a.length,f=new Array(n.length);let d=0;for(let t=0;t<f.length;++t)d+=n[t].dims[r],f[t]=d;let h="";h=n.length<5?s(f):u(f);const g=`\n        ${c(n.length,p)}\n        ${l(f)}\n        ${h}\n        float process(int indices[${p}]) {\n          int textureIndex = getTextureWhereDataResides (indices[${r}]);\n\n          if(textureIndex != 0) {\n            indices[${r}] = indices[${r}] - int(getSizeInConcatAxisValueFromIndex(textureIndex-int(1)));\n          }\n\n          return fetchDataFromCorrectTexture(textureIndex, indices);\n        }`;return Object.assign(Object.assign({},e),{output:{dims:a,type:n[0].type,textureType:i.TextureType.unpacked},shaderSource:g})})(0,r,e,n.axis)})},s=t=>`int getTextureWhereDataResides(int index) {\n      ${t.map(((t,e)=>`if(index<${t}) {return ${e};}\n`)).join("")}\n    }`,u=t=>s(t),c=(t,e)=>{const n=[`float fetchDataFromCorrectTexture(int textureIndex, int indices[${e}]) {`];for(let e=0;e<t;++e)0===e?n.push(`\tif (textureIndex == ${e}) { return _X${e}(indices); }`):e===t-1?n.push(`\telse { return _X${e}(indices); }`):n.push(`\telse if (textureIndex == ${e}) { return _X${e}(indices); }`);return n.push("\t}"),n.join("\n")},l=t=>{const e=["int getSizeInConcatAxisValueFromIndex(int index) {"];for(let n=0;n<t.length;++n)0===n?e.push(`\tif (index == ${n}) { return ${t[n]}; }`):n===t.length-1?e.push(`\telse { return ${t[n]}; }`):e.push(`\telse if (index == ${n}) { return ${t[n]}; }`);return e.push("\t}"),e.join("\n")};e.parseConcatAttributes=t=>(0,r.createAttributeWithCacheKey)({axis:t.attributes.getInt("axis")});const p=t=>{if(!t||t.length<1)throw new Error("too few inputs");const e=t[0].type,n=t[0].dims.length;if("string"===e)throw new Error("string tensor is not supported yet");for(const r of t){if(r.type!==e)throw new Error("input tensors should be one type");if(r.dims.length!==n)throw new Error("input tensors should have the same shape")}}},4770:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.createUnpackedGroupedConvProgramInfoLoader=void 0;const r=n(6231),i=n(5060),o=n(2039),a=n(8138),s=n(2823);e.createUnpackedGroupedConvProgramInfoLoader=(t,e,n)=>{const u=(c=e.length>2,l=n.cacheKey,{name:"GroupedConv",inputNames:c?["X","W","Bias"]:["X","W"],inputTypes:c?[o.TextureType.unpacked,o.TextureType.unpacked,o.TextureType.unpacked]:[o.TextureType.unpacked,o.TextureType.unpacked],cacheHint:l});var c,l;return Object.assign(Object.assign({},u),{get:()=>((t,e,n,u)=>{const c=e.length>2?"value += getBias(output_channel);":"",l=e[0].dims.slice(),p=e[1].dims.slice(),f=p[0]/u.group;r.Logger.verbose("GroupedConv",`autpPad:${u.autoPad}, dilations:${u.dilations}, group:${u.group}, kernelShape:${u.kernelShape}, pads:${u.pads}, strides:${u.strides}`);const d=(0,a.calculateOutputShape)(l,p,u.dilations,u.pads,u.strides),h=(0,i.getGlsl)(t.session.backend.glContext.version),{activationFunction:g,applyActivation:b}=(0,s.getActivationSnippet)(u),m=`\n  const ivec2 strides = ivec2(${u.strides[0]}, ${u.strides[1]});\n  const ivec2 pads = ivec2(${u.pads[0]}, ${u.pads[1]});\n  ${g}\n  void main() {\n    ivec4 coords = getOutputCoords();\n    int batch = coords.x;\n    int output_channel = coords.y;\n    ivec2 xRCCorner = coords.zw * strides - pads;\n    int group_id = output_channel / ${f};\n\n    float value = 0.0;\n    for (int wInChannel = 0; wInChannel < ${p[1]}; wInChannel++) {\n      int input_channel = group_id * ${p[1]} + wInChannel;\n      for (int wHeight = 0; wHeight < ${p[2]}; wHeight++) {\n        int xHeight = xRCCorner.x + wHeight * ${u.dilations[0]};\n\n        if (xHeight < 0 || xHeight >= ${l[2]}) {\n          continue;\n        }\n\n        for (int wWidth = 0; wWidth < ${p[3]}; wWidth++) {\n          int xWidth = xRCCorner.y + wWidth * ${u.dilations[1]};\n          if (xWidth < 0 || xWidth >= ${l[3]}) {\n            continue;\n          }\n\n          float xVal = getX(batch, input_channel, xWidth, xHeight);\n          float wVal = getW(output_channel, wInChannel, wWidth, wHeight);\n          value += xVal*wVal;\n        }\n      }\n    }\n    ${c}\n    ${b}\n    ${h.output} = vec4(value, .0, .0, .0);\n  }\n`;return Object.assign(Object.assign({},n),{output:{dims:d,type:e[0].type,textureType:o.TextureType.unpacked},shaderSource:m,hasMain:!0})})(t,e,u,n)})}},1386:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.conv2DPacked=e.conv2DPackedPointwise=void 0;const r=n(8138),i=n(8555),o=n(708);e.conv2DPackedPointwise=(t,e,n)=>{const i=e[0].dims,a=e[1].dims,s=(0,r.calculateOutputShape)(i,a,n.dilations,n.pads,n.strides),u=t.reshapePacked(e[0],[i[1],i[2]*i[3]]),c=t.reshapePacked(e[1],[a[0],a[1]]),l=e.length>2?[c,u,e[2]]:[c,u],p=t.run((0,o.createPackedMatmulProgramInfoLoader)(t,l,n),l);return t.reshapePacked(p,s)},e.conv2DPacked=(t,e,n)=>{const a=e[0].dims,s=e[1].dims,u=(0,r.calculateOutputShape)(a,s,n.dilations,n.pads,n.strides),c=t.run((0,i.createPackedIm2ColProgramInfoLoader)(t,e[0],e[1],u,n),[e[0]]),l=t.reshapePacked(e[1],[s[0],s[1]*s[2]*s[3]]),p=3===e.length?[l,c,e[2]]:[l,c],f=t.run((0,o.createPackedMatmulProgramInfoLoader)(t,p,n),p);return t.reshapePacked(f,u)}},9663:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.parseConvTransposeAttributes=e.convTranspose=void 0;const r=n(246),i=n(5060),o=n(2039),a=n(2823),s=(t,e,n,r,i,o)=>(t-1)*e+n+(r-1)*i+1-o,u=(t,e,n,r,i)=>{const o=Math.floor(t/2);"SAME_UPPER"===e?(n[r]=o,n[i]=t-o):"SAME_LOWER"===e&&(n[r]=t-o,n[i]=o)};e.convTranspose=(t,e,n)=>(f(e,n),c(t,e,n));const c=(t,e,n)=>{const r=p(n,e);return[l(t,e,r)]},l=(t,e,n)=>t.run(((t,e,n)=>{const r=(s=e.length>2,u=n.cacheKey,{name:"ConvTranspose",inputNames:s?["X","W","B"]:["X","W"],inputTypes:s?[o.TextureType.unpacked,o.TextureType.unpacked,o.TextureType.unpacked]:[o.TextureType.unpacked,o.TextureType.unpacked],cacheHint:u});var s,u;return Object.assign(Object.assign({},r),{get:()=>((t,e,n,r)=>{const s=e.length>2?"getB(output_channel)":"0.0",u=e[0].dims,c=e[1].dims,l=c[1],p=c[0]/r.group,f=[e[0].dims[0],e[1].dims[1]*r.group,...r.outputShape],d=(0,i.getGlsl)(t.session.backend.glContext.version),{activationFunction:h,applyActivation:g}=(0,a.getActivationSnippet)(r),b=`\n  const ivec2 strides = ivec2(${r.strides[0]}, ${r.strides[1]});\n  const ivec2 pads = ivec2(${r.pads[0]}, ${r.pads[1]});\n  ${h}\n  void main() {\n    ivec4 coords = getOutputCoords();\n    int batch = coords.x;\n    int output_channel = coords.y;\n\n    ivec2 loc = coords.zw + pads;\n\n    int group_id = output_channel / ${l};\n    int wOutChannel = output_channel - group_id * ${l};\n\n    float value = ${s};\n    for (int inChannelOffset = 0; inChannelOffset < ${p}; inChannelOffset++) {\n      int input_channel = group_id * ${p} + inChannelOffset;\n      for (int wWOff = 0; wWOff < ${c[2]}; wWOff++) {\n        for (int wHOff = 0; wHOff < ${c[3]}; wHOff++) {\n          ivec2 wOff = ivec2(wWOff * ${r.dilations[0]}, wHOff * ${r.dilations[1]});\n          ivec2 wLoc = loc - wOff;\n          ivec2 wLocIn = wLoc / strides;\n          if (\n            wLocIn * strides == wLoc &&\n            wLocIn.x >= 0 && wLocIn.x < ${u[2]} &&\n            wLocIn.y >= 0 && wLocIn.y < ${u[3]}\n          ) {\n            float xVal = getX(batch, input_channel, wLocIn.y, wLocIn.x);\n            float wVal = getW(input_channel, wOutChannel, wHOff, wWOff);\n            value += xVal * wVal;\n          }\n        }\n      }\n    }\n    ${g}\n    ${d.output} = vec4(value, .0, .0, .0);\n  }\n`;return Object.assign(Object.assign({},n),{output:{dims:f,type:e[0].type,textureType:o.TextureType.unpacked},shaderSource:b,hasMain:!0})})(t,e,r,n)})})(t,e,n),e),p=(t,e)=>{const n=t.kernelShape.slice();if(0===t.kernelShape.length)for(let t=2;t<e[1].dims.length;++t)n.push(e[1].dims[t]);const r=t.pads.slice(),i=t.outputShape.slice();((t,e,n,r,i,o,a,c)=>{const l=t.length-2,p=0===c.length;for(let f=0;f<l;++f){const d=p?t[f+2]*o[f]:c[f],h=s(t[f+2],o[f],i[f],e[f],n[f],d);u(h,r,i,f,f+l),p&&c.push(o[f]*(t[f+2]-1)+a[f]+(e[f]-1)*n[f]+1-i[f]-i[f+l])}})(e[0].dims,n,t.dilations,t.autoPad,r,t.strides,t.outputPadding,i);const o=Object.assign({},t);return Object.assign(o,{kernelShape:n,pads:r,outputShape:i,cacheKey:t.cacheKey}),o};e.parseConvTransposeAttributes=t=>{const e=t.attributes,n=(0,a.parseInternalActivationAttributes)(e),i=e.getString("auto_pad","NOTSET"),o=e.getInts("dilations",[1,1]),s=e.getInt("group",1),u=e.getInts("kernel_shape",[]),c=e.getInts("output_padding",[0,0]),l=e.getInts("output_shape",[]),p=e.getInts("pads",[0,0,0,0]),f=e.getInts("strides",[1,1]);return(0,r.createAttributeWithCacheKey)(Object.assign({autoPad:i,dilations:o,group:s,kernelShape:u,outputPadding:c,outputShape:l,pads:p,strides:f},n))};const f=(t,e)=>{if(!t||2!==t.length&&3!==t.length)throw new Error("Conv requires 2 or 3 inputs");if(4!==t[0].dims.length||4!==t[1].dims.length)throw new Error("currently only support 2-dimensional conv");if(t[0].dims[1]!==t[1].dims[0])throw new Error("FILTER_IN_CHANNEL should be equal to DATA_CHANNEL");const n=t[1].dims[1]*e.group;if(3===t.length&&(1!==t[2].dims.length||t[2].dims[0]!==n))throw new Error("invalid bias");const r=t[0].dims.length-2;if(e.dilations.length!==r)throw new Error(`dilations should be ${r}D`);if(e.strides.length!==r)throw new Error(`strides should be ${r}D`);if(e.pads.length!==2*r)throw new Error(`pads should be ${2*r}D`);if(e.outputPadding.length!==r)throw new Error(`output_padding should be ${r}D`);if(0!==e.kernelShape.length&&e.kernelShape.length!==t[1].dims.length-2)throw new Error("invalid kernel shape");if(0!==e.outputShape.length&&e.outputShape.length!==t[0].dims.length-2)throw new Error("invalid output shape");if("float32"!==t[0].type||"float32"!==t[1].type)throw new Error("ConvTranspose input(X,W) should be float tensor");if(3===t.length&&"float32"!==t[2].type)throw new Error("ConvTranspose input(bias) should be float tensor")}},8138:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.parseConvAttributes=e.conv=e.calculateOutputShape=void 0;const r=n(246),i=n(2517),o=n(4770),a=n(1386),s=n(9828),u=n(2823),c=n(3248),l=n(5623);e.calculateOutputShape=(t,e,n,r,i)=>{const o=t[0],a=t.slice(2),s=a.length,u=e[0],c=e.slice(2).map(((t,e)=>t+(t-1)*(n[e]-1))),l=a.map(((t,e)=>t+r[e]+r[e+s])).map(((t,e)=>Math.floor((t-c[e]+i[e])/i[e])));return[o,u].concat(...l)},e.conv=(t,e,n)=>(g(e,n),p(t,e,n));const p=(t,e,n)=>{const r=h(n,e),i=t.session.pack,s=1===r.kernelShape[0]&&1===r.kernelShape[1];return r.group>1?[t.run((0,o.createUnpackedGroupedConvProgramInfoLoader)(t,e,r),e)]:s&&i?[f(t,e,r)]:i&&4===e[0].dims.length&&1===e[0].dims[0]&&!s?[(0,a.conv2DPacked)(t,e,r)]:[d(t,e,r)]},f=(t,n,r)=>{const i=n[0].dims,o=n[1].dims,a=(0,e.calculateOutputShape)(i,o,r.dilations,r.pads,r.strides),s=t.reshapeUnpacked(n[0],[i[1],i[2]*i[3]]),u=t.reshapeUnpacked(n[1],[o[0],o[1]]),c=n.length>2?[u,s,n[2]]:[u,s],p=t.run((0,l.createMatmulProgramInfoLoader)(c,r),c);return t.reshapeUnpacked(p,a)},d=(t,n,r)=>{const i=n[0].dims,o=n[1].dims,a=(0,e.calculateOutputShape)(i,o,r.dilations,r.pads,r.strides),u=t.run((0,c.createIm2ColProgramInfoLoader)(t,n[0],n[1],a,r),[n[0]]),l=3===n.length?[u,n[1],n[2]]:[u,n[1]];return t.run((0,s.createDotProductProgramInfoLoader)(t,n,a,r),l)},h=(t,e)=>{const n=t.kernelShape.slice();if(0===t.kernelShape.length)for(let t=2;t<e[1].dims.length;++t)n.push(e[1].dims[t]);const r=t.pads.slice();i.PoolConvUtil.adjustPadsBasedOnAutoPad(e[0].dims,t.strides,t.dilations,n,r,t.autoPad);const o=Object.assign({},t);return Object.assign(o,{kernelShape:n,pads:r,cacheKey:t.cacheKey}),o};e.parseConvAttributes=t=>{const e=t.attributes,n=(0,u.parseInternalActivationAttributes)(e),i=e.getString("auto_pad","NOTSET"),o=e.getInts("dilations",[1,1]),a=e.getInt("group",1),s=e.getInts("kernel_shape",[]),c=e.getInts("pads",[0,0,0,0]),l=e.getInts("strides",[1,1]);return(0,r.createAttributeWithCacheKey)(Object.assign({autoPad:i,dilations:o,group:a,kernelShape:s,pads:c,strides:l},n))};const g=(t,e)=>{if(!t||2!==t.length&&3!==t.length)throw new Error("Conv requires 2 or 3 inputs");if(4!==t[0].dims.length||4!==t[1].dims.length)throw new Error("currently only support 2-dimensional conv");if(t[0].dims[1]!==t[1].dims[1]*e.group)throw new Error("FILTER_IN_CHANNEL should be equal to DATA_CHANNEL");if(3===t.length&&(1!==t[2].dims.length||t[1].dims[0]!==t[2].dims[0]))throw new Error("invalid bias");const n=t[0].dims.length-2;if(e.dilations.length!==n)throw new Error(`dilations should be ${n}D`);if(e.strides.length!==n)throw new Error(`strides should be ${n}D`);if(e.pads.length!==2*n)throw new Error(`pads should be ${2*n}D`);if(0!==e.kernelShape.length&&e.kernelShape.length!==t[1].dims.length-2)throw new Error("invalid kernel shape");if("float32"!==t[0].type||"float32"!==t[1].type)throw new Error("Conv input(X,W) should be float tensor");if(3===t.length&&"float32"!==t[2].type)throw new Error("Conv input(bias) should be float tensor")}},5193:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.parseDepthToSpaceAttributes=e.depthToSpace=void 0;const r=n(3738);e.depthToSpace=(t,e,n)=>{i(e);const o=n.blocksize,a=o*o,s="DCR"===n.mode?[0,3,4,1,5,2]:[0,1,4,2,5,3],u="DCR"===n.mode?[e[0].dims[0],o,o,e[0].dims[1]/a,e[0].dims[2],e[0].dims[3]]:[e[0].dims[0],e[0].dims[1]/a,o,o,e[0].dims[2],e[0].dims[3]],c=t.reshapeUnpacked(e[0],u),l={perm:s,cacheKey:`${s}`},[p]=(0,r.transpose)(t,[c],l),f=[e[0].dims[0],e[0].dims[1]/a,e[0].dims[2]*o,e[0].dims[3]*o];return[t.reshapeUnpacked(p,f)]},e.parseDepthToSpaceAttributes=t=>{const e=t.attributes.getInt("blocksize");if(e<1)throw new Error(`blocksize must be >= 1, but got : ${e} for DepthToSpace`);const n=t.attributes.getString("mode","DCR");if("DCR"!==n&&"CRD"!==n)throw new Error(`unrecognized mode: ${n} for DepthToSpace`);return{mode:n,blocksize:e}};const i=t=>{if(1!==t.length)throw new Error(`DepthToSpace expect 1 inputs, but got ${t.length}`);if("string"===t[0].type||4!==t[0].dims.length)throw new TypeError("DepthToSpace input should be a 4-D numeric tensor")}},9828:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.createDotProductProgramInfoLoader=void 0;const r=n(2517),i=n(5060),o=n(2039),a=n(2823),s=n(3248);e.createDotProductProgramInfoLoader=(t,e,n,u)=>{const c=((t,e)=>({name:"ConvDotProduct",inputNames:t?["Im2Col","K","B"]:["Im2Col","K"],inputTypes:t?[o.TextureType.unpacked,o.TextureType.packedLastDimension,o.TextureType.unpacked]:[o.TextureType.unpacked,o.TextureType.packedLastDimension],cacheKey:e.activationCacheKey}))(e.length>2,u);return Object.assign(Object.assign({},c),{get:()=>((t,e,n,u,c)=>{const l=n[0].dims,p=n[1].dims,f=[p[0],Math.ceil(l[1]*p[2]*p[3]/4)],d=(0,s.calculateIm2ColDims)(l,p,u),[h,g]=t.calculateTextureWidthAndHeight(f,o.TextureType.packedLastDimension),b=r.ShapeUtil.computeStrides(d),[m,y]=t.calculateTextureWidthAndHeight(d,o.TextureType.packedLastDimension),_=u.length,v=n.length<3?"0.0":"_B(b)",w=Math.ceil(l[1]*p[2]*p[3]/4),{activationFunction:x,applyActivation:T}=(0,a.getActivationSnippet)(c),S=(0,i.getGlsl)(t.session.backend.glContext.version),O=`\n${x}\nfloat process(int indices[${_}]) {\n  int b[1];\n  b[0] = indices[1];\n  int im2col[4];\n  im2col[0] = indices[0];\n  im2col[1] = indices[2];\n  im2col[2] = indices[3];\n  int im2colOffset = im2col[0] * ${b[0]} + im2col[1] * ${b[1]} + im2col[2] * ${b[2]};\n  int kernelOffset = indices[1] * ${f[1]};\n  float value = ${v};\n  for (int i = 0; i < ${w}; ++i) {\n    vec2 im2colCoords = offsetToCoords(im2colOffset, ${m}, ${y});\n    vec2 kernelCoords = offsetToCoords(kernelOffset, ${h}, ${g});\n    value += dot(${S.texture2D}(Im2Col, im2colCoords), ${S.texture2D}(K, kernelCoords));\n    ++im2colOffset;\n    ++kernelOffset;\n  }\n  ${T}\n  return value;\n}`;return Object.assign(Object.assign({},e),{output:{dims:u,type:n[0].type,textureType:o.TextureType.unpacked},shaderSource:O})})(t,c,e,n,u)})}},7992:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.parseFlattenAttributes=e.flatten=void 0;const r=n(2517);e.flatten=(t,e,n)=>{i(e,n);const o=r.ShapeUtil.flattenShape(e[0].dims,n);return[t.reshapeUnpacked(e[0],o)]},e.parseFlattenAttributes=t=>t.attributes.getInt("axis",1);const i=(t,e)=>{if(!t||1!==t.length)throw new Error("Flatten requires 1 input.");const n=t[0].dims.length;if(0===n)throw new Error("scalar tensor is not supported.");if(e<-n||e>n)throw new Error("Invalid axis");if("string"===t[0].type)throw new Error("string tensor is not supported.")}},2823:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.parseInternalActivationAttributes=e.getActivationSnippet=void 0;const r=n(2517),i=n(4909);e.getActivationSnippet=function(t){let e;switch(t.activation){case"Relu":e=(0,i.glslRelu)();break;case"Sigmoid":e=(0,i.glslSigmoid)();break;case"Clip":e=(0,i.glslClip)(t.clipMin,t.clipMax);break;default:return{activationFunction:"",applyActivation:""}}const n=e.name;return{activationFunction:e.body,applyActivation:`value = ${n}_(value);`}},e.parseInternalActivationAttributes=t=>{const e=t.getString("activation","");if("Clip"===e){const[n,i]=t.getFloats("activation_params",[r.MIN_CLIP,r.MAX_CLIP]);return{activation:e,clipMax:i,clipMin:n,activationCacheKey:`${e}:${n},${i}`}}return{activation:e,activationCacheKey:e}}},1253:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.parseGatherAttributes=e.gather=void 0;const r=n(246),i=n(782),o=n(2517),a=n(2039);e.gather=(t,e,n)=>(c(e,n.axis),[t.run(u(t,e,n),e)]),e.parseGatherAttributes=t=>(0,r.createAttributeWithCacheKey)({axis:t.attributes.getInt("axis",0)});const s={name:"Gather",inputNames:["A","B"],inputTypes:[a.TextureType.unpacked,a.TextureType.unpacked]},u=(t,e,n)=>{const r=Object.assign(Object.assign({},s),{cacheHint:n.cacheKey});return Object.assign(Object.assign({},r),{get:()=>((t,e,n,r)=>{const i=n[0].dims.slice(),s=n[1].dims.slice(),u=new Array(i.length+s.length-1);r=o.ShapeUtil.normalizeAxis(r,i.length);const c=[];for(let t=0;t<u.length;t++)t<r?(u[t]=i[t],c.push(`inputIdx[${t}] = outputIdx[${t}];`)):t<r+s.length?(u[t]=s[t-r],c.push(`indexDataIdx[${t-r}] = outputIdx[${t}];`)):(u[t]=i[t-s.length+1],c.push(`inputIdx[${t-s.length+1}] = outputIdx[${t}];`));const l=`\n      float process(int outputIdx[${u.length||1}]) {\n        int inputIdx[${i.length}];\n        int indexDataIdx[${s.length||1}];\n        indexDataIdx[0] = 0;\n        ${c.join("\n        ")}\n        int idx = int(_B(indexDataIdx));\n        inputIdx[${r}] = idx < 0 ? idx + ${i[r]} : idx;\n        return _A(inputIdx);\n      }`;return Object.assign(Object.assign({},e),{output:{dims:u,type:n[0].type,textureType:a.TextureType.unpacked},shaderSource:l})})(0,r,e,n.axis)})},c=(t,e)=>{if(!t||2!==t.length)throw new Error("Gather requires 2 inputs.");const n=t[0].dims.length;if(n<1)throw new Error("Invalid input shape.");if(e<-n||e>n-1)throw new Error("Invalid axis.");if(-1===i.NUMBER_TYPES.indexOf(t[0].type))throw new Error("Invaid input type.");if("int32"!==t[1].type&&"int16"!==t[1].type)throw new Error("Invaid input type.")}},4776:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.parseGemmAttributesV11=e.parseGemmAttributesV7=e.gemm=void 0;const r=n(246),i=n(2517),o=n(2039);e.gemm=(t,e,n)=>(c(e,n),[t.run(s(e,n),e)]);const a=(t,e)=>{const n=0!==t.attributes.getInt("transA",0),i=0!==t.attributes.getInt("transB",0),o=t.attributes.getFloat("alpha",1),a=t.attributes.getFloat("beta",1);return(0,r.createAttributeWithCacheKey)({transA:n,transB:i,alpha:o,beta:a,isOptionalC:e})};e.parseGemmAttributesV7=t=>a(t,!1),e.parseGemmAttributesV11=t=>a(t,!0);const s=(t,e)=>{const n={name:"Gemm",inputNames:3===t.length?["A","B","C"]:["A","B"],inputTypes:3===t.length?[o.TextureType.unpacked,o.TextureType.unpacked,o.TextureType.unpacked]:[o.TextureType.unpacked,o.TextureType.unpacked],key:e.cacheKey};return Object.assign(Object.assign({},n),{get:()=>u(n,t,e)})},u=(t,e,n)=>{const r=e[0].dims.slice(),a=e[1].dims.slice(),[s,u]=i.GemmUtil.getShapeOfGemmResult(r,n.transA,a,n.transB,3===e.length?e[2].dims:void 0),c=[s,u];if(!c)throw new Error("Can't use gemm on the given tensors");let l=r[r.length-1],p="";n.transA&&(l=r[0]),n.transA&&n.transB?p="value += _A_T(a) * _B_T(b);":n.transA&&!n.transB?p="value += _A_T(a) * _B(b);":!n.transA&&n.transB?p="value += _A(a) * _B_T(b);":n.transA||n.transB||(p="value += _A(a) * _B(b);");const f=c.length,d=`\n      float process(int indices[${f}]) {\n          int a[${f}];\n          int b[${f}];\n          ${3===e.length?`int c[${e[2].dims.length}];`:""}\n\n          copyVec(indices, a);\n          copyVec(indices, b);\n          ${3===e.length?"bcastIndices_C(indices, c);":""}\n\n          float value = 0.0;\n          for (int k=0; k<${l}; ++k) {\n              a[${f-1}] = k;\n              b[${f-2}] = k;\n              ${p}\n          }\n\n          value = value * alpha;\n          ${3===e.length?"value += beta * _C(c);":""}\n          return value;\n      }`;return Object.assign(Object.assign({},t),{output:{dims:c,type:e[0].type,textureType:o.TextureType.unpacked},variables:[{name:"alpha",type:"float",data:n.alpha},{name:"beta",type:"float",data:n.beta}],shaderSource:d})},c=(t,e)=>{if(!t)throw new Error("Input is missing");if(e.isOptionalC&&(t.length<2||t.length>3))throw new Error("Invaid input shape.");if(!e.isOptionalC&&3!==t.length)throw new Error("Gemm requires 3 inputs");if(3===t.length&&1!==t[2].dims.length&&2!==t[2].dims.length)throw new Error("Invalid input shape of C");if("float32"!==t[0].type&&"float64"!==t[0].type||"float32"!==t[1].type&&"float64"!==t[1].type||3===t.length&&"float32"!==t[2].type&&"float64"!==t[2].type)throw new Error("Invalid input type.");if(t[0].type!==t[1].type||3===t.length&&t[0].type!==t[2].type)throw new Error("Input types are mismatched")}},8555:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.createPackedIm2ColProgramInfoLoader=void 0;const r=n(5060),i=n(2039),o=n(2827);e.createPackedIm2ColProgramInfoLoader=(t,e,n,a,s)=>{const u=(c=s.cacheKey,{name:"Im2Col (packed)",inputNames:["A"],inputTypes:[i.TextureType.packed],cacheHint:c});var c;return Object.assign(Object.assign({},u),{get:()=>((t,e,n,a,s,u)=>{const c=n.dims,l=a.dims,p=s.length,f=[l[1]*l[2]*l[3],s[2]*s[3]],d=l[2]*l[3],h=(0,o.unpackFromChannel)(),g=(0,r.getGlsl)(t.session.backend.glContext.version);let b="";for(let t=0;t<=1;t++)for(let e=0;e<=1;e++)b+=`\n            blockIndex = rc.x + ${e};\n            pos = rc.y + ${t};\n\n            if(blockIndex < ${f[1]} && pos < ${f[0]}) {\n              offsetY = int(blockIndex / (${s[p-1]})) * ${u.strides[0]} -\n                ${u.pads[0]};\n              d0 = offsetY + ${u.dilations[0]} * (imod(pos, ${d}) / ${l[2]});\n\n              if(d0 < ${c[2]} && d0 >= 0) {\n                offsetX = imod(blockIndex, ${s[p-1]}) * ${u.strides[1]} -\n                  ${u.pads[1]};\n                d1 = offsetX + ${u.dilations[1]} * imod(imod(pos, ${d}), ${l[2]});\n\n                if(d1 < ${c[3]} && d1 >= 0) {\n\n                  ch = int(float(pos)/ ${d}.);\n                    innerDims = vec2(d0, d1);\n                    result[${2*t+e}] = getChannel(\n                      getA(0, ch, int(innerDims.x),\n                      int(innerDims.y)), innerDims);\n                }\n              }\n            }\n\n          `;const m=`\n      ${h}\n\n      void main() {\n        ivec2 rc = getOutputCoords();\n          vec4 result = vec4(0.0);\n          int blockIndex, pos, offsetY, d0, offsetX, d1, ch;\n          vec2 innerDims;\n          ${b}\n          ${g.output} = result;\n      }\n            `;return Object.assign(Object.assign({},e),{output:{dims:f,type:n.type,textureType:i.TextureType.packed},shaderSource:m,hasMain:!0})})(t,u,e,n,a,s)})}},3248:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.calculateIm2ColDims=e.createIm2ColProgramInfoLoader=void 0;const r=n(2039);e.createIm2ColProgramInfoLoader=(t,n,i,o,a)=>{const s=(u=a.cacheKey,{name:"Im2Col",inputNames:["X"],inputTypes:[r.TextureType.unpacked],cacheHint:u});var u;return Object.assign(Object.assign({},s),{get:()=>((t,n,i,o,a,s)=>{const u=i.dims,c=o.dims,l=a.length,p=(0,e.calculateIm2ColDims)(u,c,a,4),f=`\n        const int XC = ${u[1]};\n        const int XH = ${u[2]};\n        const int XW = ${u[3]};\n        const int KH = ${s.kernelShape[0]};\n        const int KW = ${s.kernelShape[1]};\n        const int dilationH = ${s.dilations[0]};\n        const int dilationW = ${s.dilations[1]};\n        const int strideH = ${s.strides[0]};\n        const int strideW = ${s.strides[1]};\n        const int padH = ${s.pads[0]};\n        const int padW = ${s.pads[1]};\n        const int KHKW = KH*KW;\n        const int XCKHKW = XC * KHKW;\n        const int outputChannels = 4;\n        vec4 process(int indices[${l}]) {\n          int b  = indices[0]; // batch size\n          int oh = indices[1] * strideH - padH; //output height\n          int ow = indices[2] * strideW - padW; //output width\n          int p = indices[3] * outputChannels; //patch\n          vec4 value = vec4(0.0);\n          for(int i=0; i < outputChannels; ++i) {\n            if(p < XCKHKW) {\n              int patchC = p / KHKW;\n              int patchH = (p - patchC*KHKW) / KW;\n              int patchW = (p - patchC*KHKW) - patchH * KW;\n              int xh2 = oh + patchH * dilationH;\n              int xw2 = ow + patchW * dilationW;\n              int x[${u.length}];\n              x[0] = b;\n              x[1] = patchC;\n              x[2] = xh2;\n              x[3] = xw2;\n              if(xh2 >= 0 &&\n                  xh2 < XH &&\n                  xw2 >= 0 &&\n                  xw2 < XW) {\n                value[i] = _X(x);\n              }\n            }\n            ++p;\n          }\n          return value;\n        }\n        `;return Object.assign(Object.assign({},n),{output:{dims:p,type:i.type,textureType:r.TextureType.packedLastDimension},shaderSource:f})})(0,s,n,i,o,a)})},e.calculateIm2ColDims=(t,e,n,r=4)=>[n[0],n[2],n[3],Math.ceil(t[1]*e[2]*e[3]/r)]},6572:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.parseImageScalerAttributes=e.imageScaler=void 0;const r=n(246),i=n(2039);e.imageScaler=(t,e,n)=>(u(e),[t.run(a(t,e,n),e)]),e.parseImageScalerAttributes=t=>{const e=t.attributes.getFloat("scale"),n=t.attributes.getFloats("bias");return(0,r.createAttributeWithCacheKey)({scale:e,bias:n})};const o={name:"ImageScaler",inputNames:["X"],inputTypes:[i.TextureType.unpacked]},a=(t,e,n)=>{const r=Object.assign(Object.assign({},o),{cacheHint:n.cacheKey});return Object.assign(Object.assign({},r),{get:()=>((t,e,n,r)=>{const o=n[0].dims.slice(),a=o.length,u=`\n      ${s(r.bias.length)}\n      float process(int indices[${a}]) {\n        return _X(indices) * scale + getBias(bias, indices[1]);\n      }`;return Object.assign(Object.assign({},e),{output:{dims:o,type:n[0].type,textureType:i.TextureType.unpacked},variables:[{name:"bias",type:"float",arrayLength:r.bias.length,data:r.bias},{name:"scale",type:"float",data:r.scale}],shaderSource:u})})(0,r,e,n)})},s=t=>{const e=[`float getBias(float bias[${t}], int channel) {`];for(let n=0;n<t;++n)0===n?e.push(`\tif (channel == ${n}) { return bias[${n}]; }`):n===t-1?e.push(`\telse { return bias[${n}]; }`):e.push(`\telse if (channel == ${n}) { return bias[${n}]; }`);return e.push("\t}"),e.join("\n")},u=t=>{if(!t||1!==t.length)throw new Error("ImageScaler requires 1 input.");if(4!==t[0].dims.length)throw new Error("Invalid input shape.");if("float32"!==t[0].type&&"float64"!==t[0].type)throw new Error("Invalid input type.")}},3346:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.parseInstanceNormalizationAttributes=e.instanceNormalization=void 0;const r=n(5060),i=n(2039);e.instanceNormalization=(t,e,n)=>{c(e);const r=t.run(a(e[0]),e);return[t.run(u(t,e[0],n,r.dims),[e[0],r,e[1],e[2]])]},e.parseInstanceNormalizationAttributes=t=>t.attributes.getFloat("epsilon",1e-5);const o={name:"InstanceNormalization_MeanAndVariance",inputNames:["X"],inputTypes:[i.TextureType.unpacked]},a=t=>Object.assign(Object.assign({},o),{get:()=>((t,e)=>{const n=e.dims.slice(),r=n[1],o=n[2]*n[3],a=[n[0],r],s=`\n      vec4 process(int[2] indices) {\n        vec4 v = vec4(0.0);\n        int a[4];\n        a[0] = indices[0];\n        a[1] = indices[1];\n        float temp = 0.0;\n        for(int a2=0; a2<${n[2]}; a2++) {\n          a[2] = a2;\n          for(int a3=0; a3<${n[3]}; a3++) {\n            a[3] = a3;\n            float x = _X(a);\n            temp += x;\n          }\n        }\n        float mean = temp / float(${o});\n        temp = 0.0;\n        for(int a2=0; a2<${n[2]}; a2++) {\n          a[2] = a2;\n          for(int a3=0; a3<${n[3]}; a3++) {\n            a[3] = a3;\n            float x = _X(a);\n            temp += (x - mean) * (x - mean);\n          }\n        }\n        v.r = mean;\n        v.g = temp / float(${o});\n\n        return v;\n      }`;return Object.assign(Object.assign({},t),{output:{dims:a,type:e.type,textureType:i.TextureType.packedLastDimension},shaderSource:s})})(o,t)}),s={name:"InstanceNormalization_ComputeOutput",inputNames:["X","MeanAndVariance","Scale","B"],inputTypes:[i.TextureType.unpacked,i.TextureType.packedLastDimension,i.TextureType.unpacked,i.TextureType.unpacked]},u=(t,e,n,o)=>{const a=Object.assign(Object.assign({},s),{cacheHint:`${n}`});return Object.assign(Object.assign({},a),{get:()=>((t,e,n,o,a)=>{const s=(0,r.getGlsl)(t.session.backend.glContext.version),[u,c]=t.calculateTextureWidthAndHeight(a,i.TextureType.packedLastDimension),[l,p]=[u/4,c],f=`\n      vec4 get_MeanAndVariance(int[2] mv) {\n        int offset = indicesToOffset_MeanAndVariance(mv);\n        vec2 coords = offsetToCoords(offset, ${l}, ${p});\n        return ${s.texture2D}(MeanAndVariance, coords);\n      }\n\n      float process(int[4] indices) {\n        int mv[2];\n        mv[0] = indices[0];\n        mv[1] = indices[1];\n        vec4 mean_and_variance = get_MeanAndVariance(mv);\n        float mean = mean_and_variance.r;\n        float variance = mean_and_variance.g;\n\n        int sb[1];\n        sb[0] = indices[1];\n        float scale = _Scale(sb);\n        float b = _B(sb);\n\n        return scale * (_X(indices) - mean) / sqrt(variance + epsilon) + b;\n      }`;return Object.assign(Object.assign({},e),{output:{dims:n.dims,type:n.type,textureType:i.TextureType.unpacked},variables:[{name:"epsilon",type:"float",data:o}],shaderSource:f})})(t,a,e,n,o)})},c=t=>{if(!t||3!==t.length)throw new Error("InstanceNormalization requires 3 inputs.");const e=t[0],n=t[1],r=t[2];if(e.dims.length<3||1!==n.dims.length||1!==r.dims.length)throw new Error("Invalid input shape.");if(n.dims[0]!==e.dims[1]||r.dims[0]!==e.dims[1])throw new Error("Input shapes are mismatched.");if("float32"!==e.type&&"float64"!==e.type||"float32"!==n.type&&"float64"!==n.type||"float32"!==r.type&&"float64"!==r.type)throw new Error("Invalid input type.");if(4!==t[0].dims.length)throw new Error("Only support 4-D input shape.")}},708:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.createPackedMatmulProgramInfoLoader=void 0;const r=n(2517),i=n(5060),o=n(2039),a=n(9390),s=n(2823),u=n(5623);e.createPackedMatmulProgramInfoLoader=(t,e,n)=>{const c=(l=e.length>2,p=n.activationCacheKey,{name:"MatMul (packed)",inputNames:l?["A","B","Bias"]:["A","B"],inputTypes:l?[o.TextureType.packed,o.TextureType.packed,o.TextureType.packed]:[o.TextureType.packed,o.TextureType.packed],cacheHint:p});var l,p;return Object.assign(Object.assign({},c),{get:()=>((t,e,n,c)=>{const l=n.length>2,p=l?"value += getBiasForMatmul();":"",f=n[0].dims,d=n[1].dims,h=r.BroadcastUtil.calcShape(f,d,!0),g=!r.ShapeUtil.areEqual(n[0].dims,n[1].dims);if(!h)throw new Error("Can't use matmul on the given tensors");const b=f[f.length-1],m=Math.ceil(b/2),y=f.length,_=d.length,v=(0,i.getGlsl)(t.session.backend.glContext.version),w=(0,a.getCoordsDataType)(h.length),x=h.length,T=(0,a.getGlChannels)(),{activationFunction:S,applyActivation:O}=(0,s.getActivationSnippet)(c),A=l?`${(0,u.getBiasForMatmul)(w,T,n[2].dims,h,!0)}`:"",E=g?`${function(t,e,n,i){let o=[],a=[];const s=n[0].dims,u=n[1].dims,c=s.length,l=u.length,p=i.length,f=p-c,d=p-l;o=s.map(((t,n)=>`coords.${e[n+f]}`)),o[c-1]="i*2",o.join(", "),a=u.map(((t,n)=>`coords.${e[n+d]}`)),a[l-2]="i*2",a.join(", ");const h=r.BroadcastUtil.getBroadcastDims(s,i),g=r.BroadcastUtil.getBroadcastDims(u,i),b=h.map((t=>`coords.${e[t+f]} = 0;`)).join("\n"),m=g.map((t=>`coords.${e[t+d]} = 0;`)).join("\n"),y=`int lastDim = coords.${e[p-1]};\n  coords.${e[p-1]} = coords.${e[p-2]};\n  coords.${e[p-2]} = lastDim;`;return`\nvec4 getAAtOutCoordsMatmul(int i) {\n  ${t} coords = getOutputCoords();\n  ${y}\n  ${b}\n  vec4 outputValue = getA(${o});\n  return outputValue;\n}\n\nvec4 getBAtOutCoordsMatmul(int i) {\n  ${t} coords = getOutputCoords();\n  ${y}\n  ${m}\n  vec4 outputValue = getB(${a});\n  return outputValue;\n}`}(w,T,n,h)}`:"",I=g?"getAAtOutCoordsMatmul(i)":`getA(${function(t,e){let n="";for(let r=0;r<e-2;r++)n+=`rc.${t[r]}, `;return n+=`rc.${t[e-2]}, i*2`,n}(T,y)})`,P=g?"getBAtOutCoordsMatmul(i)":`getB(${function(t,e){let n="";for(let r=0;r<e-2;r++)n+=`rc.${t[r]}, `;return n+=`i*2, rc.${t[e-1]}`,n}(T,_)})`,D=`\n            ${E}\n            ${A}\n            ${S}\n            void main() {\n              ${g?"":`${w} rc =\n          getOutputCoords(); int lastDim = rc.${T[x-1]}; rc.${T[x-1]} =\n          rc.${T[x-2]}; rc.${T[x-2]} = lastDim;\n      `}\n\n              vec4 value = vec4(0);\n              for (int i = 0; i < ${m}; i++) {\n                vec4 a = ${I};\n                vec4 b = ${P};\n\n                value += (a.rrbb * b.rgrg);\n                value += (a.ggaa * b.baba);\n              }\n              ${p}\n              ${O}\n              ${v.output} = value;\n            }`;return Object.assign(Object.assign({},e),{output:{dims:h,type:n[0].type,textureType:o.TextureType.packed},shaderSource:D,hasMain:!0})})(t,c,e,n)})}},5623:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.getBiasForMatmul=e.createMatmulProgramInfoLoader=e.parseMatMulAttributes=e.matMul=void 0;const r=n(2517),i=n(2039),o=n(9390),a=n(2823),s=n(708);function u(t,e){const n=(s=t.length>2,u=e.activationCacheKey,{name:"MatMul",inputNames:s?["A","B","Bias"]:["A","B"],inputTypes:s?[i.TextureType.unpacked,i.TextureType.unpacked,i.TextureType.unpacked]:[i.TextureType.unpacked,i.TextureType.unpacked],cacheHint:u});var s,u;return Object.assign(Object.assign({},n),{get:()=>function(t,e,n){const s=e[0].dims,u=e[1].dims,c=r.BroadcastUtil.calcShape(s,u,!0);if(!c)throw new Error("Can't use matmul on the given tensors");const p=(0,o.getCoordsDataType)(c.length),f=(0,o.getGlChannels)(),{activationFunction:d,applyActivation:h}=(0,a.getActivationSnippet)(n),g=e.length>2,b=g?"value += getBiasForMatmul();":"",m=g?`${l(p,f,e[2].dims,c,!1)}`:"",y=c.length,_=s.length,v=u.length,w=`\n    ${d}\n    ${m}\n    float process(int indices[${y}]) {\n        int a[${_}];\n        int b[${v}];\n        bcastMatmulIndices_A(indices, a);\n        bcastMatmulIndices_B(indices, b);\n\n        float value;\n        for (int k=0; k<${s[s.length-1]}; ++k) {\n            a[${_-1}] = k;\n            b[${v-2}] = k;\n            value += _A(a) * _B(b);\n        }\n        ${b}\n        ${h}\n        return value;\n    }`;return Object.assign(Object.assign({},t),{output:{dims:c,type:e[0].type,textureType:i.TextureType.unpacked},shaderSource:w})}(n,t,e)})}e.matMul=(t,e,n)=>(c(e),t.session.pack?[t.run((0,s.createPackedMatmulProgramInfoLoader)(t,e,n),e)]:[t.run(u(e,n),e)]),e.parseMatMulAttributes=t=>(0,a.parseInternalActivationAttributes)(t.attributes),e.createMatmulProgramInfoLoader=u;const c=t=>{if(!t||2!==t.length)throw new Error("MatMul requires 2 inputs.");if(t[0].dims[t[0].dims.length-1]!==t[1].dims[t[1].dims.length-2])throw new Error("shared dimension does not match.");if("float32"!==t[0].type&&"float64"!==t[0].type||"float32"!==t[1].type&&"float64"!==t[1].type)throw new Error("inputs should be float type");if(t[0].type!==t[1].type)throw new Error("inputs types should match")};function l(t,e,n,i,o){let a="";const s=n.length,u=i.length,c=u-s;a=u<2&&s>0?"coords":n.map(((t,n)=>`coords.${e[n+c]}`)).join(", ");const l=r.BroadcastUtil.getBroadcastDims(n,i).map((t=>`coords.${e[t+c]} = 0;`)).join("\n");let p="vec4(outputValue.xx, outputValue.yy)";return 1===r.ShapeUtil.size(n)&&(p="vec4(outputValue.x)"),o?`\nvec4 getBiasForMatmul() {\n  ${t} coords = getOutputCoords();\n  ${l}\n  vec4 outputValue = getBias(${a});\n  return ${p};\n}`:`\nfloat getBiasForMatmul() {\n  ${t} coords = getOutputCoords();\n  ${l}\n  return getBias(coords.x);\n}`}e.getBiasForMatmul=l},2403:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.createPackProgramInfoLoader=void 0;const r=n(5060),i=n(2039),o=n(9390),a=n(2827),s={name:"pack",inputNames:["A"],inputTypes:[i.TextureType.unpackedReversed]};e.createPackProgramInfoLoader=(t,e)=>Object.assign(Object.assign({},s),{get:()=>((t,e)=>{const n=(0,r.getGlsl)(t.session.backend.glContext.version),u=e.dims,c=u.length,l=e.dims.length,p=(0,o.getCoordsDataType)(l),f=(0,a.getChannels)("rc",l),d=(h=l,g=f,b=u[u.length-2],m=u[u.length-1],0===h||1===h?"":`\n    int r = ${g[h-2]};\n    int c = ${g[h-1]};\n    int rp1 = ${g[h-2]} + 1;\n    int cp1 = ${g[h-1]} + 1;\n    bool rEdge = rp1 >= ${m};\n    bool cEdge = cp1 >= ${b};\n    `);var h,g,b,m;let y;y=0===c?[1,1]:1===c?[u[0],1]:[u[l-1],u[l-2]];const _=function(t,e,n){if(0===t)return"false";if(1===t)return`rc > ${e[0]}`;let r="";for(let i=t-2;i<t;i++)r+=`${n[i]} >= ${e[i-t+2]}`,i<t-1&&(r+="||");return r}(l,y,f),v=function(t,e){const n=t.length;if(0===n)return"getA(), 0, 0, 0";if(1===n)return`getA(rc),\n            rc + 1 >= ${t[0]} ? 0. : getA(rc + 1),\n            0, 0`;let r="";if(n>2)for(let t=0;t<n-2;++t)r+=`${e[t]},`;return`getA(${r}r, c),\n          rEdge ? 0. : getA(${r}rp1, c),\n          cEdge ? 0. : getA(${r}r, cp1),\n          rEdge || cEdge ? 0. : getA(${r}rp1, cp1)`}(u,f),w=`\n        void main() {\n          ${p} rc = getOutputCoords();\n\n          if(${_}) {\n            ${n.output} = vec4(0);\n          } else {\n            ${d}\n\n            ${n.output} = vec4(${v});\n          }\n        }\n      `;return Object.assign(Object.assign({},s),{hasMain:!0,output:{dims:e.dims,type:e.type,textureType:i.TextureType.packed},shaderSource:w})})(t,e)})},2827:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.unpackFromChannel=e.getChannels=e.getVecChannels=void 0;const r=n(9390);function i(t,e){return(0,r.getGlChannels)(e).map((e=>`${t}.${e}`))}e.getVecChannels=i,e.getChannels=function(t,e){return 1===e?[t]:i(t,e)},e.unpackFromChannel=function(){return"\n    float getChannel(vec4 frag, int dim) {\n      int modCoord = imod(dim, 2);\n      return modCoord == 0 ? frag.r : frag.g;\n    }\n\n    float getChannel(vec4 frag, vec2 innerDims) {\n      vec2 modCoord = mod(innerDims, 2.);\n      return modCoord.x == 0. ?\n        (modCoord.y == 0. ? frag.r : frag.g) :\n        (modCoord.y == 0. ? frag.b : frag.a);\n    }\n  "}},2870:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.parsePadAttributesV11=e.padV11=e.parsePadAttributesV2=e.padV2=void 0;const r=n(246),i=n(2517),o=n(5060),a=n(2039),s={name:"Pad",inputNames:["A"],inputTypes:[a.TextureType.unpacked]};e.padV2=(t,e,n)=>(l(e),[t.run(Object.assign(Object.assign({},s),{cacheHint:n.cacheKey,get:()=>c(t,e[0],n)}),e)]),e.parsePadAttributesV2=t=>{const e=t.attributes.getString("mode","constant"),n=t.attributes.getFloat("value",0),i=t.attributes.getInts("pads");return(0,r.createAttributeWithCacheKey)({mode:e,value:n,pads:i})},e.padV11=(t,n,r)=>{p(n);const i=u(t,n,r);return(0,e.padV2)(t,[n[0]],i)},e.parsePadAttributesV11=t=>t.attributes.getString("mode","constant");const u=(t,e,n)=>{if(!t.session.isInitializer(e[1].dataId)||e.length>=3&&!t.session.isInitializer(e[2].dataId))throw new Error("dynamic pad attributes are not allowed");const i=Array.from(e[1].integerData),o=e.length>=3?e[2].floatData[0]:0;return(0,r.createAttributeWithCacheKey)({mode:n,pads:i,value:o})},c=(t,e,n)=>{const r=i.ShapeUtil.padShape(e.dims.slice(),n.pads),o=r.length,s=`\n      ${f(t,e,n)}\n      float process(int[${o}] indices) {\n          return padA(indices);\n      }`;return{name:"Pad",inputNames:["A"],inputTypes:[a.TextureType.unpacked],output:{dims:r,type:e.type,textureType:a.TextureType.unpacked},shaderSource:s}},l=t=>{if(!t||1!==t.length)throw new Error("Pad requires 1 input");if("float32"!==t[0].type&&"float64"!==t[0].type)throw new Error("Invalid input type.")},p=t=>{if(!t||2!==t.length&&3!==t.length)throw new Error("Pad requires 2 or 3 inputs");if("int32"!==t[1].type)throw new Error("Invalid input type.");if(t.length>=3&&"string"===t[2].type)throw new Error("Invalid input type.")},f=(t,e,n)=>{const r=(0,o.getGlsl)(t.session.backend.glContext.version),[s,u]=t.calculateTextureWidthAndHeight(e.dims,a.TextureType.unpacked),c=i.ShapeUtil.computeStrides(e.dims);switch(n.mode){case"constant":return d(r,e.dims,c,s,u,n.pads,n.value);case"reflect":return h(r,e.dims,c,s,u,n.pads);case"edge":return g(r,e.dims,c,s,u,n.pads);default:throw new Error("Invalid mode")}},d=(t,e,n,r,i,o,a)=>{const s=e.length;let u="";for(let t=s-1;t>=0;--t)u+=`\n        k = m[${t}] - ${o[t]};\n        if (k < 0)  return constant;\n        if (k >= ${e[t]}) return constant;\n        offset += k * ${n[t]};\n        `;return`\n      float padA(int m[${s}]) {\n        const float constant = float(${a});\n        int offset = 0;\n        int k = 0;\n        ${u}\n        vec2 coords = offsetToCoords(offset, ${r}, ${i});\n        float value = getColorAsFloat(${t.texture2D}(A, coords));\n        return value;\n      }\n      `},h=(t,e,n,r,i,o)=>{const a=e.length;let s="";for(let t=a-1;t>=0;--t)s+=`\n        k = m[${t}] - ${o[t]};\n        if (k < 0) { k = -k; }\n        {\n          const int _2n_1 = ${2*(e[t]-1)};\n          k = int( mod( float(k), float(_2n_1) ) ) ;\n          if(k >= ${e[t]}) { k = _2n_1 - k; }\n        }\n        offset += k * ${n[t]};\n        `;return`\n      float padA(int m[${a}]) {\n        int offset = 0;\n        int k = 0;\n        ${s}\n        vec2 coords = offsetToCoords(offset, ${r}, ${i});\n        float value = getColorAsFloat(${t.texture2D}(A, coords));\n        return value;\n      }\n      `},g=(t,e,n,r,i,o)=>{const a=e.length;let s="";for(let t=a-1;t>=0;--t)s+=`\n        k = m[${t}] - ${o[t]};\n        if (k < 0)  k = 0;\n        if (k >= ${e[t]}) k = ${e[t]-1};\n        offset += k * ${n[t]};\n      `;return`\n      float padA(int m[${a}]) {\n        int offset = 0;\n        int k = 0;\n        ${s}\n        vec2 coords = offsetToCoords(offset, ${r}, ${i});\n        float value = getColorAsFloat(${t.texture2D}(A, coords));\n        return value;\n      }\n      `}},2143:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.globalMaxPool=e.parseMaxPoolAttributes=e.maxPool=e.parseGlobalAveragePoolAttributes=e.globalAveragePool=e.parseAveragePoolAttributes=e.averagePool=void 0;const r=n(246),i=n(2517),o=n(2039);e.averagePool=(t,e,n)=>{p(e);const r={name:"AveragePool",inputNames:["X"],inputTypes:[o.TextureType.unpacked],cacheHint:n.cacheKey};return[t.run(Object.assign(Object.assign({},r),{get:()=>a(e,r,!1,n)}),e)]},e.parseAveragePoolAttributes=t=>{const e=t.attributes.getString("auto_pad","NOTSET"),n=t.attributes.getInt("ceil_mode",0),i=0!==t.attributes.getInt("count_include_pad",0),o=t.attributes.getInts("kernel_shape"),a=t.attributes.getInts("strides",[]),s=t.attributes.getInts("pads",[]);if(0!==n)throw new Error("using ceil() in shape computation is not yet supported for AveragePool");return(0,r.createAttributeWithCacheKey)({autoPad:e,ceilMode:n,countIncludePad:i,kernelShape:o,strides:a,pads:s})};const a=(t,e,n,r)=>{const[a,s]=u(t,r,n),c=i.ShapeUtil.size(a.kernelShape);let l="";a.countIncludePad?l+=`value /= float(${c});`:l+=`value /= float(${c} - pad);`;const p=`\n        ${f(t[0].dims,a,"value += _X(x);",l,"0.0")}\n      `;return Object.assign(Object.assign({},e),{output:{dims:s,type:t[0].type,textureType:o.TextureType.unpacked},shaderSource:p})};e.globalAveragePool=(t,e,n)=>{p(e);const r={name:"GlobalAveragePool",inputNames:["X"],inputTypes:[o.TextureType.unpacked],cacheHint:`${n.countIncludePad}`};return[t.run(Object.assign(Object.assign({},r),{get:()=>a(e,r,!0,n)}),e)]},e.parseGlobalAveragePoolAttributes=t=>{const e=0!==t.attributes.getInt("count_include_pad",0);return(0,r.createAttributeWithCacheKey)({autoPad:"",ceilMode:0,countIncludePad:e,kernelShape:[],strides:[],pads:[]})},e.maxPool=(t,e,n)=>{p(e);const r={name:"MaxPool",inputNames:["X"],inputTypes:[o.TextureType.unpacked],cacheHint:n.cacheKey};return[t.run(Object.assign(Object.assign({},r),{get:()=>s(e,r,!1,n)}),e)]},e.parseMaxPoolAttributes=t=>{const e=t.attributes.getString("auto_pad","NOTSET"),n=t.attributes.getInt("ceil_mode",0),i=t.attributes.getInts("kernel_shape"),o=t.attributes.getInts("strides",[]),a=t.attributes.getInts("pads",[]),s=t.attributes.getInt("storage_order",0),u=t.attributes.getInts("dilations",[]);if(0!==s)throw new Error("column major storage order is not yet supported for MaxPool");if(0!==n)throw new Error("using ceil() in shape computation is not yet supported for MaxPool");return(0,r.createAttributeWithCacheKey)({autoPad:e,ceilMode:n,countIncludePad:!1,kernelShape:i,strides:o,pads:a,storageOrder:s,dilations:u})};const s=(t,e,n,r)=>{const[i,a]=u(t,r,n),s=`\n      ${f(t[0].dims,i,"\n      value = max(_X(x), value);\n    ","","-1e5")}\n    `;return Object.assign(Object.assign({},e),{output:{dims:a,type:t[0].type,textureType:o.TextureType.unpacked},shaderSource:s})},u=(t,e,n)=>{const r=t[0].dims.slice(),o=Object.hasOwnProperty.call(e,"dilations"),a=e.kernelShape.slice(),s=e.strides.slice(),u=o?e.dilations.slice():[],c=e.pads.slice();i.PoolConvUtil.adjustPoolAttributes(n,r,a,s,u,c);const l=i.PoolConvUtil.computePoolOutputShape(n,r,s,u,a,c,e.autoPad),p=Object.assign({},e);return o?Object.assign(p,{kernelShape:a,strides:s,pads:c,dilations:u,cacheKey:e.cacheKey}):Object.assign(p,{kernelShape:a,strides:s,pads:c,cacheKey:e.cacheKey}),[p,l]},c={autoPad:"",ceilMode:0,countIncludePad:!1,kernelShape:[],strides:[],pads:[],storageOrder:0,dilations:[],cacheKey:""},l={name:"GlobalMaxPool",inputNames:["X"],inputTypes:[o.TextureType.unpacked]};e.globalMaxPool=(t,e)=>(p(e),[t.run(Object.assign(Object.assign({},l),{get:()=>s(e,l,!0,c)}),e)]);const p=t=>{if(!t||1!==t.length)throw new Error("Pool ops requires 1 input.");if("float32"!==t[0].type&&"float64"!==t[0].type)throw new Error("Invalid input type.")},f=(t,e,n,r,o)=>{const a=t.length;if(e.kernelShape.length<=2){const i=e.kernelShape[e.kernelShape.length-1],s=e.strides[e.strides.length-1],u=e.pads[e.pads.length/2-1],c=e.pads[e.pads.length-1],l=t[a-1];let p="",f="",d="";if(p=u+c!==0?`\n          for (int i = 0; i < ${i}; i++) {\n            x[${a} - 1] = indices[${a} - 1] * ${s} - ${u} + i;\n            if (x[${a} - 1] < 0 || x[${a} - 1] >= ${l}) {\n              pad++;\n              continue;\n            }\n            ${n}\n          }`:`\n          for (int i = 0; i < ${i}; i++) {\n            x[${a} - 1] = indices[${a} - 1] * ${s} - ${u} + i;\n            ${n}\n          }`,2===e.kernelShape.length){const n=e.kernelShape[e.kernelShape.length-2],r=e.strides[e.strides.length-2],o=e.pads[e.pads.length/2-2],s=e.pads[e.pads.length-2],u=t[a-2];f=o+s!==0?`\n            for (int j = 0; j < ${n}; j++) {\n              x[${a} - 2] = indices[${a} - 2] * ${r} - ${o} + j;\n              if (x[${a} - 2] < 0 || x[${a} - 2] >= ${u}) {\n                pad+= ${i};\n                continue;\n              }\n          `:`\n            for (int j = 0; j < ${n}; j++) {\n              x[${a} - 2] = indices[${a} - 2] * ${r} - ${o} + j;\n            `,d="\n          }\n        "}return`\n        float process(int indices[${a}]) {\n          int x[${a}];\n          copyVec(indices, x);\n\n          float value = ${o};\n          int pad = 0;\n          ${f}\n          ${p}\n          ${d}\n          ${r}\n          return value;\n        }\n      `}{const s=i.ShapeUtil.size(e.kernelShape),u=i.ShapeUtil.computeStrides(e.kernelShape),c=u.length,l=e.pads.length,p=h(c),f=d(t,"inputDims"),g=d(e.pads,"pads"),b=d(u,"kernelStrides"),m=d(e.strides,"strides");let y="";return y=e.pads.reduce(((t,e)=>t+e))?`\n            if (x[j] >= inputDims[j] || x[j] < 0) {\n              pad++;\n              isPad = true;\n              break;\n            }\n          }\n          if (!isPad) {\n            ${n}\n          }`:`\n          }\n          ${n}\n        `,`\n        ${p}\n        float process(int indices[${a}]) {\n          int x[${a}];\n          copyVec(indices, x);\n          int offset[${c}];\n          int pads[${l}];\n          int inputDims[${a}];\n          int kernelStrides[${c}];\n          int strides[${c}];\n          ${g}\n          ${f}\n          ${m}\n          ${b}\n\n          float value = ${o};\n          int pad = 0;\n          bool isPad = false;\n          for (int i = 0; i < ${s}; i++) {\n            offsetToIndices(i, kernelStrides, offset);\n            isPad = false;\n            for (int j = ${a} - ${c}; j < ${a}; j++) {\n              x[j] = indices[j] * strides[j - ${a} + ${c}]\n                + offset[j - ${a} + ${c}] - pads[j - 2];\n              ${y}\n          }\n          ${r}\n\n          return value;\n        }\n      `}},d=(t,e)=>{let n="";for(let r=0;r<t.length;r++)n+=`\n      ${e}[${r}] = ${t[r]};\n    `;return n},h=t=>`\n  void offsetToIndices(int offset, int[${t}] strides, out int[${t}] indices) {\n    if (${t} == 0) {\n      return;\n    }\n    for (int i = 0; i < ${t} - 1; ++i) {\n      indices[i] = offset / strides[i];\n      offset -= indices[i] * strides[i];\n    }\n    indices[${t} - 1] = offset;\n  }`},4939:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.reduceLogSumSquare=e.reduceLogSum=e.reduceProd=e.reduceMin=e.reduceMax=e.reduceMean=e.reduceSum=e.parseReduceAttributes=void 0;const r=n(246),i=n(782),o=n(2517),a=n(2039),s=(t,e,n,r,i)=>{c(e);const o={name:r,inputNames:["A"],inputTypes:[a.TextureType.unpacked]};return[t.run(Object.assign(Object.assign({},o),{cacheHint:n.cacheKey,get:()=>u(t,e,n,r,i,o)}),e)]};e.parseReduceAttributes=t=>{const e=t.attributes.getInts("axes",[]),n=1===t.attributes.getInt("keepdims",1);return(0,r.createAttributeWithCacheKey)({axes:e,keepDims:n})};const u=(t,e,n,r,i,s)=>{const u=[],c=e[0].dims.length||1,l=[],p=o.ShapeUtil.normalizeAxes(n.axes,e[0].dims.length),f=i(e,p);let d=f[1];for(let t=0;t<e[0].dims.length;t++)p.indexOf(t)>=0||0===p.length?(n.keepDims&&u.push(1),d=`\n          for(int j${t} = 0; j${t} < ${e[0].dims[t]}; j${t}++) {\n            inputIdx[${t}] = j${t};\n            ${d}\n          }`):(l.push(`inputIdx[${t}] = outputIdx[${u.length}];`),u.push(e[0].dims[t]));const h=`\n      float process(int outputIdx[${u.length||1}]) {\n        float value;                 // final result\n        int inputIdx[${c}];      // addressing input data\n        ${l.join("\n")}\n        ${f[0]}       // init ops for reduce max/min\n        ${d}\n        ${f[2]}       // final computation for reduce mean\n        return value;\n      }`;return Object.assign(Object.assign({},s),{output:{dims:u,type:e[0].type,textureType:a.TextureType.unpacked},shaderSource:h})},c=t=>{if(!t||1!==t.length)throw new Error("Reduce op requires 1 input.");if(-1===i.NUMBER_TYPES.indexOf(t[0].type))throw new Error("Invalid input type.")};e.reduceSum=(t,e,n)=>s(t,e,n,"ReduceSum",(()=>["value = 0.0;","value += _A(inputIdx);",""])),e.reduceMean=(t,e,n)=>s(t,e,n,"ReduceMean",((t,e)=>{let n=1;for(let r=0;r<t[0].dims.length;r++)(e.indexOf(r)>=0||0===e.length)&&(n*=t[0].dims[r]);return["value = 0.0;","value += _A(inputIdx);",`value /= ${n}.;`]})),e.reduceMax=(t,e,n)=>s(t,e,n,"ReduceMax",((t,e)=>{const n=[];for(let r=0;r<t[0].dims.length;r++)(e.indexOf(r)>=0||0===e.length)&&n.push(`inputIdx[${r}] = 0;`);return[`${n.join("\n")}\nvalue = _A(inputIdx);`,"value = max(value, _A(inputIdx));",""]})),e.reduceMin=(t,e,n)=>s(t,e,n,"ReduceMin",((t,e)=>{const n=[];for(let r=0;r<t[0].dims.length;r++)(e.indexOf(r)>=0||0===e.length)&&n.push(`inputIdx[${r}] = 0;`);return[`${n.join("\n")}\nvalue = _A(inputIdx);`,"value = min(value, _A(inputIdx));",""]})),e.reduceProd=(t,e,n)=>s(t,e,n,"ReduceProd",(()=>["value = 1.0;","value *= _A(inputIdx);",""])),e.reduceLogSum=(t,e,n)=>s(t,e,n,"ReduceLogSum",(()=>["value = 0.0;","value += _A(inputIdx);","value = log(value);"])),e.reduceLogSumSquare=(t,e,n)=>s(t,e,n,"ReduceLogSumSquare",(()=>["float t; value = 0.0;","t = _A(inputIdx); value += t * t;",""]))},7019:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.isReshapeCheap=e.processDims3D=e.createPackedReshape3DProgramInfoLoader=void 0;const r=n(2517),i=n(5060),o=n(2039),a=n(2827);e.createPackedReshape3DProgramInfoLoader=(t,e,n)=>{const s=(t=>({name:"Reshape (packed)",inputTypes:[o.TextureType.packed],inputNames:["A"],cacheHint:`${t}`}))(n);return Object.assign(Object.assign({},s),{get:()=>((t,e,n,s)=>{const u=e.dims,c=s;let l="";for(let t=0;t<4;t++){let e="";switch(t){case 0:e="outputCoords = rc;";break;case 1:e="outputCoords = ivec3(rc.x, rc.y+1, rc.z);";break;case 2:e="outputCoords = ivec3(rc.x, rc.y, rc.z+1);";break;case 3:e="outputCoords = ivec3(rc.x, rc.y+1, rc.z+1);";break;default:throw new Error}l+=`\n        ${e}\n        ${t>0?"if(outputCoords.y < rows && outputCoords.z < cols){":""}\n          int flattenedIndex = getFlattenedIndex(outputCoords);\n\n          ivec3 inputRC = inputCoordsFromReshapedOutCoords(flattenedIndex);\n          vec2 innerDims = vec2(float(inputRC.y),float(inputRC.z));\n\n          result[${t}] = getChannel(getA(inputRC.x, inputRC.y, inputRC.z), innerDims);\n\n        ${t>0?"}":""}\n      `}const p=(0,i.getGlsl)(t.session.backend.glContext.version),f=`\n      ${function(t){const e=r.ShapeUtil.computeStrides(t),n=["b","r","c"],i="index";return`\n    ivec3 inputCoordsFromReshapedOutCoords(int index) {\n      ${e.map(((t,r)=>`int ${n[r]} = ${i} / ${t}; ${r===e.length-1?`int ${n[r+1]} = ${i} - ${n[r]} * ${t}`:`index -= ${n[r]} * ${t}`};`)).join("")}\n      return ivec3(b, r, c);\n    }\n  `}(u)}\n      ${function(t){const e=r.ShapeUtil.computeStrides(t);return`\n  int getFlattenedIndex(ivec3 coords) {\n    // reverse y, z order\n    return coords.x * ${e[0]} + coords.z * ${e[1]} + coords.y;\n  }\n`}(c)}\n      ${(0,a.unpackFromChannel)()}\n\n      void main() {\n        ivec3 rc = getOutputCoords();\n\n        vec4 result = vec4(0.0);\n\n        ivec3 outputCoords;\n        int rows = ${c[2]};\n        int cols = ${c[1]};\n\n        ${l}\n        ${p.output} = result;\n      }\n    `;return Object.assign(Object.assign({},n),{output:{dims:c,type:e.type,textureType:o.TextureType.packed},shaderSource:f,hasMain:!0})})(t,e,s,n)})},e.processDims3D=function(t){if(0===t.length)return[1,1,1];let e=1;for(let n=0;n<t.length-2;++n)e*=t[n];return[e,t.length>1?t[t.length-2]:1,t[t.length-1]]},e.isReshapeCheap=function(t,e){let n=!1;return n=0===t.length||0===e.length||(t.length<2||e.length<2?t[t.length-1]===e[e.length-1]:t[t.length-1]===e[e.length-1]&&t[t.length-2]===e[e.length-2]),n}},718:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.reshape=void 0;const r=n(2517);e.reshape=(t,e)=>{const n=r.ShapeUtil.calculateReshapedDims(e[0].dims,e[1].integerData);return t.session.pack?[t.reshapePacked(e[0],n)]:[t.reshapeUnpacked(e[0],n)]}},2268:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.parseResizeAttributesV11=e.parseResizeAttributesV10=e.resize=void 0;const r=n(5060),i=n(2039),o=n(9390),a=n(2827),s=n(9793),u={name:"Resize",inputNames:["A"],inputTypes:[i.TextureType.packed]};e.resize=(t,e,n)=>((0,s.validateInputs)(e,n),[t.run(Object.assign(Object.assign({},u),{cacheHint:n.cacheKey,get:()=>c(t,e,n)}),e)]),e.parseResizeAttributesV10=t=>(0,s.parseUpsampleAttributes)(t,10),e.parseResizeAttributesV11=t=>(0,s.parseUpsampleAttributes)(t,11);const c=(t,e,n)=>{const s=(0,r.getGlsl)(t.session.backend.glContext.version),[c,p]=l(e,n);if(c.every((t=>1===t))&&"tf_crop_and_resize"!==n.coordinateTransformMode)return Object.assign(Object.assign({},u),{output:{dims:p,type:e[0].type,textureType:i.TextureType.packed},hasMain:!0,shaderSource:`void main() {\n                    vec4 v = ${s.texture2D}(X, TexCoords);\n                    ${s.output} = v;\n                }`});const f=p.length;if(f<2)throw new Error(`output dimension should be at least 2, but got ${f}`);const d=p[f-2],h=p[f-1],g=e[0].dims;if(f!==g.length)throw new Error(`output dimension should match input ${g.length}, but got ${f}`);const b=g[f-2],m=g[f-1],y=c[f-2],_=c[f-1];let v="";if("linear"!==n.mode)throw new Error(`resize (packed) does not support mode: '${n.mode}'`);switch(n.coordinateTransformMode){case"asymmetric":v="\n                    vec4 getSourceFracIndex(ivec4 coords) {\n                        return vec4(coords) / scaleWHWH;\n                    }\n                ";break;case"half_pixel":v="\n                    vec4 getSourceFracIndex(ivec4 coords) {\n                        return (vec4(coords) + 0.5) / scaleWHWH - 0.5;\n                    }\n                ";break;case"pytorch_half_pixel":v=`\n                    vec4 getSourceFracIndex(ivec4 coords) {\n                        vec4 fcoords = vec4(coords);\n                        return vec4(\n                            ${h}.0 > 1.0 ? (fcoords.x + 0.5) / scaleWHWH.x - 0.5 : 0.0,\n                            ${d}.0 > 1.0 ? (fcoords.y + 0.5) / scaleWHWH.y - 0.5 : 0.0,\n                            ${h}.0 > 1.0 ? (fcoords.z + 0.5) / scaleWHWH.z - 0.5 : 0.0,\n                            ${d}.0 > 1.0 ? (fcoords.w + 0.5) / scaleWHWH.w - 0.5 : 0.0\n                          );\n                    }\n                `;break;case"align_corners":v=`\n                    vec4 getSourceFracIndex(ivec4 coords) {\n                        vec4 resized = vec4(${h}.0 - 1.0, ${d}.0 - 1.0, ${h}.0 - 1.0,\n                            ${d}.0 - 1.0);\n                        vec4 original = vec4(${m}.0 - 1.0, ${b}.0 - 1.0, ${m}.0 - 1.0,\n                            ${b}.0 - 1.0);\n                        vec4 new_scale = original / resized;\n                        return vec4(coords) * new_scale;\n                    }\n                `;break;default:throw new Error(`resize (packed) does not support coordinateTransformMode:                                 '${n.coordinateTransformMode}'`)}const w=(0,o.getCoordsDataType)(f),x=`\n            const vec2 inputWH = vec2(${b}.0, ${m}.0);\n            const vec4 scaleWHWH = vec4(float(${y}), float(${_}), float(${y}), float(${_}));\n            ${(0,a.unpackFromChannel)()}\n            ${v}\n            float getAValue(int x10, int r, int c, int d) {\n                return getChannel(getA(x10, r, c, d), vec2(c, d));\n            }\n            void main() {\n                ${w} rc = getOutputCoords();\n\n                int batch = rc[0];\n                int depth = rc[1];\n\n                // retrieve the 4 coordinates that is used in the 4 packed output values.\n                ivec4 coords = ivec4(rc.wz, rc.w + 1, rc.z + 1);\n\n                // calculate the source index in fraction\n                vec4 sourceFrac = getSourceFracIndex(coords);\n\n                // get the lower and upper bound of the 4 values that will be packed into one texel.\n                ivec4 x00 = ivec4(max(sourceFrac.xy, vec2(0.0)), min(inputWH - 1.0, ceil(sourceFrac.xy)));\n                ivec4 x01 = ivec4(max(sourceFrac.xw, vec2(0.0)), min(inputWH - 1.0, ceil(sourceFrac.xw)));\n                ivec4 x10 = ivec4(max(sourceFrac.zy, vec2(0.0)), min(inputWH - 1.0, ceil(sourceFrac.zy)));\n                ivec4 x11 = ivec4(max(sourceFrac.zw, vec2(0.0)), min(inputWH - 1.0, ceil(sourceFrac.zw)));\n\n                bool hasNextRow = rc.w < ${d-1};\n                bool hasNextCol = rc.z < ${h-1};\n\n                // pack x00, x01, x10, x11's top-left corner into one vec4 structure\n                vec4 topLeft = vec4(\n                    getAValue(batch, depth, x00.x, x00.y),\n                    hasNextCol ? getAValue(batch, depth, x01.x, x01.y) : 0.0,\n                    hasNextRow ? getAValue(batch, depth, x10.x, x10.y) : 0.0,\n                    (hasNextRow && hasNextCol) ? getAValue(batch, depth, x11.x, x11.y) : 0.0);\n\n                // pack x00, x01, x10, x11's top-right corner into one vec4 structure\n                vec4 topRight = vec4(\n                    getAValue(batch, depth, x00.x, x00.w),\n                    hasNextCol ? getAValue(batch, depth, x01.x, x01.w) : 0.0,\n                    hasNextRow ? getAValue(batch, depth, x10.x, x10.w) : 0.0,\n                    (hasNextRow && hasNextCol) ? getAValue(batch, depth, x11.x, x11.w) : 0.0);\n\n                // pack x00, x01, x10, x11's bottom-left corner into one vec4 structure\n                vec4 bottomLeft = vec4(\n                    getAValue(batch, depth, x00.z, x00.y),\n                    hasNextCol ? getAValue(batch, depth, x01.z, x01.y) : 0.0,\n                    hasNextRow ? getAValue(batch, depth, x10.z, x10.y) : 0.0,\n                    (hasNextRow && hasNextCol) ? getAValue(batch, depth, x11.z, x11.y) : 0.0);\n\n                // pack x00, x01, x10, x11's bottom-right corner into one vec4 structure\n                vec4 bottomRight = vec4(\n                    getAValue(batch, depth, x00.z, x00.w),\n                    hasNextCol ? getAValue(batch, depth, x01.z, x01.w) : 0.0,\n                    hasNextRow ? getAValue(batch, depth, x10.z, x10.w) : 0.0,\n                    (hasNextRow && hasNextCol) ? getAValue(batch, depth, x11.z, x11.w) : 0.0);\n\n                // calculate the interpolation fraction on u and v direction\n                vec4 frac = vec4(sourceFrac) - floor(sourceFrac);\n                vec4 clampFrac = clamp(frac, vec4(0.0), vec4(1.0));\n\n                vec4 top = mix(topLeft, topRight, clampFrac.ywyw);\n                vec4 bottom = mix(bottomLeft, bottomRight, clampFrac.ywyw);\n                vec4 newValue = mix(top, bottom, clampFrac.xxzz);\n\n                ${s.output} = vec4(newValue);\n            }\n        `;return Object.assign(Object.assign({},u),{output:{dims:p,type:e[0].type,textureType:i.TextureType.packed},hasMain:!0,shaderSource:x})},l=(t,e)=>{const n=t[0].dims;let r,i=e.scales;if(0===i.length){const o=t[e.scalesInputIdx];if(o&&0!==o.size){if(t[e.sizesInputIdx])throw new Error("Only one of scales or sizes must be provided as input.");i=p(o,e.mode,e.isResize)}else{const o=t[e.sizesInputIdx];if(!o||0===o.size)throw new Error("Either scales or sizes MUST be provided as input.");r=Array.from(o.integerData),i=f(r,n,e.mode,e.isResize)}}else if(t[e.sizesInputIdx])throw new Error("Only one of scales or sizes must be provided as input.");const o=r||n.map(((t,e)=>Math.floor(t*i[e])));return[i,o]},p=(t,e,n)=>{const r=Array.from(t.floatData);return(0,s.scalesValidation)(r,e,n),r},f=(t,e,n,r)=>{const i=e.length,o=new Array(i);for(let n=0,r=i;n<r;n++)if(0===e[n]){if(0!==t[n])throw new Error("Input dim is zero but required output dim is non-zero.");o[n]=1}else o[n]=t[n]/e[n];return(0,s.scalesValidation)(o,n,r),o}},8117:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.shape=void 0;const r=n(9162);e.shape=(t,e)=>(i(e),[new r.Tensor([e[0].dims.length],"int32",void 0,void 0,new Int32Array(e[0].dims))]);const i=t=>{if(!t||1!==t.length)throw new Error("Shape requires 1 input.")}},2278:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.sliceV10=e.parseSliceAttributes=e.slice=void 0;const r=n(246),i=n(782),o=n(2517),a=n(2039),s={name:"Slice",inputNames:["A"],inputTypes:[a.TextureType.unpacked]};e.slice=(t,e,n)=>(c(e),[t.run(Object.assign(Object.assign({},s),{cacheHint:n.cacheKey,get:()=>u(t,e[0],n)}),e)]),e.parseSliceAttributes=t=>{const e=t.attributes.getInts("starts"),n=t.attributes.getInts("ends"),i=t.attributes.getInts("axes",[]);return(0,r.createAttributeWithCacheKey)({starts:e,ends:n,axes:i})};const u=(t,e,n)=>{const r=0===n.axes.length?e.dims.slice(0).map(((t,e)=>e)):n.axes,i=o.ShapeUtil.normalizeAxes(r,e.dims.length),u=n.starts.map(((t,n)=>t>e.dims[i[n]]-1?e.dims[i[n]]:o.ShapeUtil.normalizeAxis(t,e.dims[i[n]]))),c=n.ends.map(((t,n)=>t>e.dims[i[n]]-1?e.dims[i[n]]:o.ShapeUtil.normalizeAxis(t,e.dims[i[n]]))),l=e.dims.slice(),p=[];for(let t=0;t<i.length;t++)l[i[t]]=c[t]-u[t],u[t]>0&&p.push(`outputIdx[${i[t]}] += ${u[t]};`);const f=`\n      float process(int outputIdx[${l.length}]) {\n        ${p.join("\n      ")}\n        return _A(outputIdx);\n      }`;return Object.assign(Object.assign({},s),{output:{dims:l,type:e.type,textureType:a.TextureType.unpacked},shaderSource:f})},c=t=>{if(!t||1!==t.length)throw new Error("Slice requires 1 input.");if(-1===i.NUMBER_TYPES.indexOf(t[0].type))throw new Error("Invalid input type.")};e.sliceV10=(t,e)=>{p(e);const n=l(t,e);return[t.run(Object.assign(Object.assign({},s),{cacheHint:n.cacheKey,get:()=>u(t,e[0],n)}),[e[0]])]};const l=(t,e)=>{if(!t.session.isInitializer(e[1].dataId)||!t.session.isInitializer(e[2].dataId)||e.length>=4&&!t.session.isInitializer(e[3].dataId)||e.length>=5&&!t.session.isInitializer(e[4].dataId))throw new Error("dynamic slice attributes are not allowed");if(e.length>=5&&e[4].integerData.some((t=>1!==t)))throw new Error("currently non-1 steps is not supported for Slice");const n=Array.from(e[1].integerData),r=Array.from(e[2].integerData),i=e.length>=4?Array.from(e[3].integerData):[];return{starts:n,ends:r,axes:i,cacheKey:`${i};${n};${r}`}},p=t=>{if(!t||t.length<3||t.length>5)throw new Error("Invalid input number.");if("int32"!==t[1].type||1!==t[1].dims.length)throw new Error("Invalid input type.");if("int32"!==t[2].type||1!==t[2].dims.length)throw new Error("Invalid input type.");if(t.length>=4&&("int32"!==t[3].type||1!==t[3].dims.length))throw new Error("Invalid input type.");if(t.length>=5&&("int32"!==t[4].type||1!==t[4].dims.length))throw new Error("Invalid input type.")}},5524:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.softmaxV13=e.parseSoftmaxAttributesV13=e.parseSoftmaxAttributes=e.softmax=void 0;const r=n(246),i=n(2517),o=n(5060),a=n(2039),s=n(3738),u={name:"SoftmaxComputeMax",inputNames:["A"],inputTypes:[a.TextureType.unpacked]},c={name:"SoftmaxComputeScale",inputNames:["A","Max"],inputTypes:[a.TextureType.unpacked,a.TextureType.unpacked]},l={name:"SoftMax",inputNames:["A","Max","Norm"],inputTypes:[a.TextureType.unpacked,a.TextureType.unpacked,a.TextureType.unpacked]};e.softmax=(t,e,n)=>{g(e);const r=e[0].dims.slice(),o=i.ShapeUtil.normalizeAxis(n.axis,r.length),a=i.ShapeUtil.sizeToDimension(r,o),s=i.ShapeUtil.sizeFromDimension(r,o);return p(t,e,n,a,s)},e.parseSoftmaxAttributes=t=>(0,r.createAttributeWithCacheKey)({axis:t.attributes.getInt("axis",1)}),e.parseSoftmaxAttributesV13=t=>(0,r.createAttributeWithCacheKey)({axis:t.attributes.getInt("axis",-1)}),e.softmaxV13=(t,e,n)=>{g(e);const o=e[0].dims.slice(),a=i.ShapeUtil.normalizeAxis(n.axis,o.length),u=o.length,c=a!==u-1,l=[];let f,d=[],h=[];c&&(d=Array.from({length:u}).map(((t,e)=>e)),d[a]=u-1,d[u-1]=a,d.map((t=>l.push(o[t]))),f=(0,r.createAttributeWithCacheKey)({perm:d}),h=(0,s.transpose)(t,e,f));const b=c?i.ShapeUtil.sizeToDimension(l,u-1):i.ShapeUtil.sizeToDimension(o,u-1),m=c?i.ShapeUtil.sizeFromDimension(l,u-1):i.ShapeUtil.sizeFromDimension(o,u-1),y=p(t,c?h:e,n,b,m);return c?(0,s.transpose)(t,y,f):y};const p=(t,e,n,r,i)=>{const o=f(t,e[0],r,i,[r]),a=t.run(Object.assign(Object.assign({},u),{cacheHint:n.cacheKey,get:()=>o}),e),s=d(t,e[0],r,i,o.output.dims,[r]),p=t.run(Object.assign(Object.assign({},c),{cacheHint:n.cacheKey,get:()=>s}),[e[0],a]),g=h(t,e[0],r,i,o.output.dims,s.output.dims);return[t.run(Object.assign(Object.assign({},l),{cacheHint:n.cacheKey,get:()=>g}),[e[0],a,p])]},f=(t,e,n,r,i)=>{const[s,c]=t.calculateTextureWidthAndHeight(e.dims,a.TextureType.unpacked),l=i.length;if(n<1||r<1)throw new Error("Logical row count N and feature count D must be greater than or equal to 1");if(1!==i.length)throw new Error("Dimensionality of the output should be 1");if(i[0]!==n)throw new Error("Shape of the output should be equal to logical row count");const p=(0,o.getGlsl)(t.session.backend.glContext.version),f=`\n      float process(int[${l}] indices) {\n        int logical_row_start_offset = indices[0] * ${r};\n\n        float max = getColorAsFloat(${p.texture2D}(A, offsetToCoords(logical_row_start_offset, ${s},\n        ${c} )));\n        for(int i=1; i<${r}; ++i)\n        {\n          float current = getColorAsFloat(${p.texture2D}(A, offsetToCoords(logical_row_start_offset + i,\n            ${s}, ${c})));\n          if(current > max)\n          max = current;\n        }\n\n        return max;\n      }`;return Object.assign(Object.assign({},u),{output:{dims:i,type:e.type,textureType:a.TextureType.unpacked},shaderSource:f})},d=(t,e,n,r,i,s)=>{const[u,l]=t.calculateTextureWidthAndHeight(e.dims,a.TextureType.unpacked),p=s.length;if(n<1||r<1)throw new Error("Logical row count N and feature count D must be greater than or equal to 1");if(1!==s.length)throw new Error("Dimensionality of the output should be 1");if(s[0]!==n)throw new Error("Shape of the output should be equal to logical row count");if(1!==i.length)throw new Error("Dimensionality of the intermediate results should be 1");if(i[0]!==n)throw new Error("Shape of the intermediate results should be equal to logical row count");const f=`\n      float process(int[${p}] indices) {\n        int logical_row_start_offset = indices[0] * ${r};\n\n        float norm_factor = 0.0;\n        float max = _Max(indices);\n        for(int i=0; i<${r}; ++i)\n        {\n          norm_factor += exp(getColorAsFloat(${(0,o.getGlsl)(t.session.backend.glContext.version).texture2D}(A, offsetToCoords(logical_row_start_offset + i,\n            ${u}, ${l}))) - max);\n        }\n\n        return norm_factor;\n      }`;return Object.assign(Object.assign({},c),{output:{dims:s,type:e.type,textureType:a.TextureType.unpacked},shaderSource:f})},h=(t,e,n,r,i,o)=>{const[s,u]=t.calculateTextureWidthAndHeight(e.dims,a.TextureType.unpacked),c=e.dims.length;if(n<1||r<1)throw new Error("Logical row count N and feature count D must be greater than or equal to 1");if(1!==i.length||1!==o.length)throw new Error("Dimensionality of the intermediate results should be 1");if(i[0]!==n||o[0]!==n)throw new Error("Shape of the intermediate results should be equal to logical row count");const p=`\n      float process(int[${c}] indices) {\n\n      // get offset of current logical tensor index from the 2-D texture coordinates (TexCoords)\n      int offset = coordsToOffset(TexCoords, ${s}, ${u});\n\n      //determine the logical row for this index\n      int logical_row_index[1];\n      logical_row_index[0] = offset / ${r};\n\n      float norm_factor = _Norm(logical_row_index);\n\n      // avoid possible division by 0\n      // if norm_facor is 0, all elements are zero\n      // if so, return 0\n      if(norm_factor == 0.0)\n        return 0.0;\n\n      return exp(_A(indices) - _Max(logical_row_index)) / norm_factor;\n    }`;return Object.assign(Object.assign({},l),{output:{dims:e.dims,type:e.type,textureType:a.TextureType.unpacked},shaderSource:p})},g=t=>{if(!t||1!==t.length)throw new Error("Softmax requires 1 input.");if("float32"!==t[0].type&&"float64"!==t[0].type)throw new Error("Invalid input type")}},5975:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.parseSplitAttributes=e.split=void 0;const r=n(246),i=n(2517),o=n(2039),a={name:"Split",inputNames:["A"],inputTypes:[o.TextureType.unpacked]};e.split=(t,e,n)=>{c(e);const r=i.ShapeUtil.normalizeAxis(n.axis,e[0].dims.length),o=s(t,e,r,n),l=[];for(let i=0;i<o;++i)l.push(t.run(Object.assign(Object.assign({},a),{cacheHint:`${n.cacheKey};${i}`,get:()=>u(t,e[0],n,r,i)}),e));return l},e.parseSplitAttributes=t=>{const e=t.attributes.getInt("axis",0),n=t.attributes.getInts("split",[]),i=t.outputs.length;return(0,r.createAttributeWithCacheKey)({axis:e,split:n,numOutputs:i})};const s=(t,e,n,r)=>{const[,o]=i.SplitUtil.splitShape(e[0].dims,n,r.split,r.numOutputs);return o.length},u=(t,e,n,r,s)=>{const[u,c]=i.SplitUtil.splitShape(e.dims,r,n.split,n.numOutputs),l=c[s],p=u[s],f=`\n      float process(int indices[${p.length}]) {\n        indices[${r}] += ${l};\n        return _A(indices);\n      }\n    `;return Object.assign(Object.assign({},a),{cacheHint:`${n.cacheKey}:${s}`,output:{dims:p,type:e.type,textureType:o.TextureType.unpacked},shaderSource:f})},c=t=>{if(!t||1!==t.length)throw new Error("Split requires one input.");if("int8"!==t[0].type&&"uint8"!==t[0].type&&"int16"!==t[0].type&&"uint16"!==t[0].type&&"int32"!==t[0].type&&"uint32"!==t[0].type&&"float32"!==t[0].type&&"float64"!==t[0].type&&"bool"!==t[0].type)throw new Error("Invalid input type.")}},3933:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.parseSqueezeAttributes=e.squeezeV13=e.squeeze=void 0;const r=n(2517);e.squeeze=(t,e,n)=>{i(e);const o=r.ShapeUtil.squeezeShape(e[0].dims,n);return[t.reshapeUnpacked(e[0],o)]},e.squeezeV13=(t,n)=>(o(n),(0,e.squeeze)(t,[n[0]],Array.from(n[1].integerData))),e.parseSqueezeAttributes=t=>t.attributes.getInts("axes");const i=t=>{if(!t||1!==t.length)throw new Error("Squeeze requires 1 input.");if("string"===t[0].type)throw new Error("invalid input tensor types.")},o=t=>{if(!t||2!==t.length)throw new Error("Squeeze requires 2 inputs.");if("int32"!==t[1].type)throw new Error("Invalid input type.")}},6558:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.sum=void 0;const r=n(5060),i=n(2039);e.sum=(t,e)=>{a(e);const n={name:"Sum",inputNames:e.map(((t,e)=>`X${e}`)),inputTypes:new Array(e.length).fill(i.TextureType.unpacked)};return[t.run(Object.assign(Object.assign({},n),{get:()=>o(t,e,n)}),e)]};const o=(t,e,n)=>{const o=(0,r.getGlsl)(t.session.backend.glContext.version),a=e[0].dims.slice(),s=`\n      void main() {\n        vec4 result = ${e.map(((t,e)=>`${o.texture2D}(X${e},TexCoords)`)).join(" + ")};\n        ${o.output} = result;\n      }\n    `;return Object.assign(Object.assign({},n),{output:{dims:a,type:e[0].type,textureType:i.TextureType.unpacked},hasMain:!0,shaderSource:s})},a=t=>{if(!t||0===t.length)throw new Error("Sum requires inputs.");const e=t[0].dims.length;for(let n=1;n<t.length;n++){if(e!==t[n].dims.length)throw new Error("Input shapes are mismatched.");for(let r=0;r<e;r++)if(t[0].dims[r]!==t[n].dims[r])throw new Error("Input shapes are not matched.")}if("float32"!==t[0].type&&"float64"!==t[0].type)throw new Error("Invalid input type.");for(let e=1;e<t.length;e++)if(t[0].type!==t[e].type)throw new Error("Input types are not matched.")}},5723:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.tile=void 0;const r=n(782),i=n(2039);e.tile=(t,e)=>{a(e);const n={name:"Tile",inputNames:["A"],inputTypes:[i.TextureType.unpacked]};return[t.run(Object.assign(Object.assign({},n),{get:()=>o(t,e,n)}),e)]};const o=(t,e,n)=>{const r=e[0].dims.slice(),o=new Array(r.length),a=[];for(let t=0;t<r.length;t++)o[t]=r[t]*e[1].numberData[t],a.push(`inputIdx[${t}] = int(mod(float(outputIdx[${t}]), ${r[t]}.));`);const s=o.length,u=`\n      float process(int outputIdx[${s}]) {\n        int inputIdx[${s}];\n        ${a.join("\n")}\n        return _A(inputIdx);\n      }\n    `;return Object.assign(Object.assign({},n),{output:{dims:o,type:e[0].type,textureType:i.TextureType.unpacked},shaderSource:u})},a=t=>{if(!t||2!==t.length)throw new Error("Tile requires 2 input.");if(1!==t[1].dims.length)throw new Error("The second input shape must 1 dimension.");if(t[1].dims[0]!==t[0].dims.length)throw new Error("Invalid input shape.");if(-1===r.NUMBER_TYPES.indexOf(t[0].type))throw new Error("Invalid input type.");if("int32"!==t[1].type&&"int16"!==t[1].type)throw new Error("Invalid repeat type.")}},3738:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.parseTransposeAttributes=e.transpose=void 0;const r=n(246),i=n(2517),o=n(2039),a={name:"Transpose",inputNames:["A"],inputTypes:[o.TextureType.unpacked]};e.transpose=(t,e,n)=>(p(e),[t.run(Object.assign(Object.assign({},a),{cacheHint:n.cacheKey,get:()=>s(t,e[0],n.perm)}),e)]),e.parseTransposeAttributes=t=>(0,r.createAttributeWithCacheKey)({perm:t.attributes.getInts("perm",[])});const s=(t,e,n)=>{const r=e.dims;n=u(r,n);const i=c(r,n),s=r.length,p=`\n      ${l("perm",n,s)}\n      float process(int indices[${s}]) {\n        int a[${s}];\n        perm(a, indices);\n        return _A(a);\n      }`;return Object.assign(Object.assign({},a),{output:{dims:i,type:e.type,textureType:o.TextureType.unpacked},shaderSource:p})},u=(t,e)=>(e&&e.length!==t.length&&(e=[...t.keys()].reverse()),e),c=(t,e)=>(e=u(t,e),i.ShapeUtil.sortBasedOnPerm(t,e)),l=(t,e,n)=>{const r=[];r.push(`void ${t}(out int a[${n}], int src[${n}]) {`);for(let t=0;t<n;++t)r.push(`\ta[${e[t]}]=src[${t}];`);return r.push("\t}"),r.join("\n")},p=t=>{if(!t||1!==t.length)throw new Error("Transpose requires 1 input.");if("float32"!==t[0].type&&"float64"!==t[0].type)throw new Error("input should be float tensor")}},8710:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.encodeAsUint8=void 0;const r=n(5060),i=n(2039);e.encodeAsUint8=(t,e)=>{const n=e.shape,o=(0,r.getGlsl)(t.session.backend.glContext.version),a=`\n    const float FLOAT_MAX = 1.70141184e38;\n    const float FLOAT_MIN = 1.17549435e-38;\n\n    bool isNaN(float val) {\n      return (val < 1.0 || 0.0 < val || val == 0.0) ? false : true;\n    }\n\n    highp vec4 encodeAsUint8(highp float v) {\n      if (isNaN(v)) {\n        return vec4(255, 255, 255, 255);\n      }\n\n      highp float av = abs(v);\n\n      if(av < FLOAT_MIN) {\n        return vec4(0.0, 0.0, 0.0, 0.0);\n      } else if(v > FLOAT_MAX) {\n        return vec4(0.0, 0.0, 128.0, 127.0) / 255.0;\n      } else if(v < -FLOAT_MAX) {\n        return vec4(0.0, 0.0,  128.0, 255.0) / 255.0;\n      }\n\n      highp vec4 c = vec4(0,0,0,0);\n\n      highp float e = floor(log2(av));\n      highp float m = exp2(fract(log2(av))) - 1.0;\n\n      c[2] = floor(128.0 * m);\n      m -= c[2] / 128.0;\n      c[1] = floor(32768.0 * m);\n      m -= c[1] / 32768.0;\n      c[0] = floor(8388608.0 * m);\n\n      highp float ebias = e + 127.0;\n      c[3] = floor(ebias / 2.0);\n      ebias -= c[3] * 2.0;\n      c[2] += floor(ebias) * 128.0;\n\n      c[3] += 128.0 * step(0.0, -v);\n\n      return c / 255.0;\n    }\n\n    void main() {\n      float value = ${o.texture2D}(X,TexCoords).r;\n      ${o.output} = encodeAsUint8(value);\n    }`,s={name:"Uint8Encode",inputTypes:[i.TextureType.unpacked],inputNames:["X"],output:{dims:n,type:e.tensor.type,textureType:i.TextureType.downloadUint8AsFloat},shaderSource:a,hasMain:!0};return t.executeProgram(s,[e.tensor])}},4909:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.tanh=e.tan=e.sqrt=e.sin=e.sigmoid=e.relu=e.not=e.neg=e.log=e.parseLeakyReluAttributes=e.leakyRelu=e.identity=e.floor=e.exp=e.parseEluAttributes=e.elu=e.cos=e.ceil=e.clipV11=e.parseClipAttributes=e.clip=e.atan=e.asin=e.acos=e.abs=e.glslTanh=e.glslTan=e.glslSqrt=e.glslSigmoid=e.glslRelu=e.glslSin=e.glslNot=e.glslNeg=e.glslLog=e.glslLeakyRelu=e.glslIdentity=e.glslClip=e.glslFloor=e.glslExp=e.glslElu=e.glslCos=e.glslCeil=e.glslAtan=e.glslAsin=e.glslAcos=e.glslAbs=void 0;const r=n(246),i=n(2517),o=n(8520),a=n(5060),s=n(2039);function u(){return P("abs")}function c(){return P("acos")}function l(){return P("asin")}function p(){return P("atan")}function f(){return P("ceil")}function d(){return P("cos")}function h(t){const e="elu";return{body:`\n  const float alpha = float(${t});\n\n  float ${e}_(float a) {\n    return a >= 0.0 ? a: (exp(a) - 1.0) * alpha;\n  }\n  vec4 ${e}_(vec4 v) {\n    return vec4(${e}_(v.x), ${e}_(v.y), ${e}_(v.z), ${e}_(v.w));\n  }\n  `,name:e,type:o.FunctionType.ValueBased}}function g(){return P("exp")}function b(){return P("floor")}function m(t,e){const n="clip";return{body:`\n  const float min = float(${t});\n  const float max = float(${e});\n\n  float ${n}_(float a) {\n    return clamp(a, min, max);\n  }\n  vec4 ${n}_(vec4 v) {\n    return clamp(v, min, max);\n  }\n  `,name:n,type:o.FunctionType.ValueBased}}function y(){const t="indentity";return{body:`\n  float ${t}_(float a) {\n    return a;\n  }\n  vec4 ${t}_(vec4 v) {\n    return v;\n  }\n  `,name:t,type:o.FunctionType.ValueBased}}function _(t){const e="leakyRelu";return{body:`\n  const float alpha = float(${t});\n\n  float ${e}_(float a) {\n    return a < 0.0 ? a * alpha : a;\n  }\n  vec4 ${e}_(vec4 v) {\n    return vec4(${e}_(v.x), ${e}_(v.y), ${e}_(v.z), ${e}_(v.w));\n  }\n  `,name:e,type:o.FunctionType.ValueBased}}function v(){return P("log")}function w(){const t="neg";return{body:`\n  float ${t}_(float a) {\n    return -a;\n  }\n  vec4 ${t}_(vec4 v) {\n    return -v;\n  }\n  `,name:t,type:o.FunctionType.ValueBased}}function x(){const t="not";return{body:`\n  float ${t}_(float a) {\n    return float( ! bool(a) );\n  }\n  bool ${t}_(bool a) {\n    return !a;\n  }\n  vec4 ${t}_(vec4 v) {\n    return vec4(!bool(v.x), !bool(v.y), !bool(v.z), !bool(v.w));\n  }\n  bvec4 ${t}_(bvec4 v) {\n    return bvec4(!v.x, !v.y, !v.z, !v.w);\n  }\n  `,name:t,type:o.FunctionType.ValueBased}}function T(){return P("sin")}function S(){const t="relu";return{body:`\n  float ${t}_(float a) {\n    return max( a, 0.0 );\n  }\n  vec4 ${t}_(vec4 v) {\n    return max( v, 0.0 );\n  }\n  `,name:t,type:o.FunctionType.ValueBased}}function O(){const t="sigmoid";return{body:`\n  float ${t}_(float a) {\n    return 1.0 / (1.0 + exp(-a));\n  }\n  vec4 ${t}_(vec4 v) {\n    return 1.0 / (1.0 + exp(-v));\n  }\n  `,name:t,type:o.FunctionType.ValueBased}}function A(){return P("sqrt")}function E(){return P("tan")}function I(){const t="tanh";return{body:`\n  float ${t}_(float a) {\n    a = clamp(a, -10., 10.);\n    a = exp(2.*a);\n    return (a - 1.) / (a + 1.);\n  }\n  vec4 ${t}_(vec4 v) {\n    v = clamp(v, -10., 10.);\n    v = exp(2.*v);\n    return (v - 1.) / (v + 1.);\n  }\n  `,name:t,type:o.FunctionType.ValueBased}}function P(t){return{body:`\n  float ${t}_(float a) {\n    return ${t}(a);\n  }\n  vec4 ${t}_(vec4 v) {\n    return ${t}(v);\n  }\n  `,name:t,type:o.FunctionType.ValueBased}}e.glslAbs=u,e.glslAcos=c,e.glslAsin=l,e.glslAtan=p,e.glslCeil=f,e.glslCos=d,e.glslElu=h,e.glslExp=g,e.glslFloor=b,e.glslClip=m,e.glslIdentity=y,e.glslLeakyRelu=_,e.glslLog=v,e.glslNeg=w,e.glslNot=x,e.glslSin=T,e.glslRelu=S,e.glslSigmoid=O,e.glslSqrt=A,e.glslTan=E,e.glslTanh=I;const D=(t,e,n,r)=>{const i=t.session.pack?s.TextureType.packed:s.TextureType.unpacked,o={name:n.name,inputTypes:[i],inputNames:["A"],cacheHint:r};return Object.assign(Object.assign({},o),{get:()=>((t,e,n,r)=>{const i=t.session.pack?s.TextureType.packed:s.TextureType.unpacked,o=(0,a.getGlsl)(t.session.backend.glContext.version);return Object.assign(Object.assign({},e),{output:{dims:n.dims,type:n.type,textureType:i},shaderSource:`\n     ${r.body}\n     void main() {\n       vec4 v = ${o.texture2D}(A, TexCoords);\n       v = ${r.name}_(v);\n       ${o.output} = v;\n     }\n     `,hasMain:!0})})(t,o,e,n)})};e.abs=(t,e)=>[t.run(D(t,e[0],u()),e)],e.acos=(t,e)=>[t.run(D(t,e[0],c()),e)],e.asin=(t,e)=>[t.run(D(t,e[0],l()),e)],e.atan=(t,e)=>[t.run(D(t,e[0],p()),e)],e.clip=(t,e,n)=>[t.run(D(t,e[0],m(n.min,n.max),n.cacheKey),e)],e.parseClipAttributes=t=>(0,r.createAttributeWithCacheKey)({min:t.attributes.getFloat("min",i.MIN_CLIP),max:t.attributes.getFloat("max",i.MAX_CLIP)}),e.clipV11=(t,n)=>{const r=$(t,n);return(0,e.clip)(t,[n[0]],r)};const $=(t,e)=>{if(e.length>=3&&(!t.session.isInitializer(e[1].dataId)||!t.session.isInitializer(e[2].dataId)))throw new Error("dynamic clip attributes are not allowed");const n=e.length>=3?e[1].numberData[0]:i.MIN_CLIP,o=e.length>=3?e[2].numberData[0]:i.MAX_CLIP;return(0,r.createAttributeWithCacheKey)({min:n,max:o})};e.ceil=(t,e)=>[t.run(D(t,e[0],f()),e)],e.cos=(t,e)=>[t.run(D(t,e[0],d()),e)],e.elu=(t,e,n)=>[t.run(D(t,e[0],h(n.alpha),n.cacheKey),e)],e.parseEluAttributes=t=>(0,r.createAttributeWithCacheKey)({alpha:t.attributes.getFloat("alpha",1)}),e.exp=(t,e)=>[t.run(D(t,e[0],g()),e)],e.floor=(t,e)=>[t.run(D(t,e[0],b()),e)],e.identity=(t,e)=>[t.run(D(t,e[0],y()),e)],e.leakyRelu=(t,e,n)=>[t.run(D(t,e[0],_(n.alpha),n.cacheKey),e)],e.parseLeakyReluAttributes=t=>(0,r.createAttributeWithCacheKey)({alpha:t.attributes.getFloat("alpha",.01)}),e.log=(t,e)=>[t.run(D(t,e[0],v()),e)],e.neg=(t,e)=>[t.run(D(t,e[0],w()),e)],e.not=(t,e)=>[t.run(D(t,e[0],x()),e)],e.relu=(t,e)=>[t.run(D(t,e[0],S()),e)],e.sigmoid=(t,e)=>[t.run(D(t,e[0],O()),e)],e.sin=(t,e)=>[t.run(D(t,e[0],T()),e)],e.sqrt=(t,e)=>[t.run(D(t,e[0],A()),e)],e.tan=(t,e)=>[t.run(D(t,e[0],E()),e)],e.tanh=(t,e)=>[t.run(D(t,e[0],I()),e)]},5611:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.createUnpackProgramInfoLoader=e.createUnpackProgramInfo=void 0;const r=n(5060),i=n(2039),o=n(9390),a=n(2827),s={name:"unpack",inputNames:["A"],inputTypes:[i.TextureType.packed]};e.createUnpackProgramInfo=(t,e)=>{const n=e.dims.length,u=(0,a.getChannels)("rc",n),c=u.slice(-2),l=(0,o.getCoordsDataType)(n),p=(0,a.unpackFromChannel)(),f=0===e.dims.length?"":function(t,e){if(1===t)return"rc";let n="";for(let r=0;r<t;r++)n+=e[r],r<t-1&&(n+=",");return n}(n,u),d=n<=1?"rc":`vec2(${c.join(",")})`,h=`\n    ${p}\n    void main() {\n      ${l} rc = getOutputCoords();\n\n       // Sample the texture with the coords to get the rgba channel value.\n       vec4 packedInput = getA(${f});\n\n       ${(0,r.getGlsl)(t.session.backend.glContext.version).output} = vec4(getChannel(packedInput, ${d}), 0, 0, 0);\n     }\n   `;return Object.assign(Object.assign({},s),{hasMain:!0,output:{dims:e.dims,type:e.type,textureType:i.TextureType.unpacked},shaderSource:h})},e.createUnpackProgramInfoLoader=(t,n)=>Object.assign(Object.assign({},s),{get:()=>(0,e.createUnpackProgramInfo)(t,n)})},8428:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.parseUnsqueezeAttributes=e.unsqueezeV13=e.unsqueeze=void 0;const r=n(2517);e.unsqueeze=(t,e,n)=>{i(e);const o=r.ShapeUtil.unsqueezeShape(e[0].dims,n);return[t.reshapeUnpacked(e[0],o)]},e.unsqueezeV13=(t,n)=>(o(n),(0,e.unsqueeze)(t,[n[0]],Array.from(n[1].integerData))),e.parseUnsqueezeAttributes=t=>t.attributes.getInts("axes");const i=t=>{if(!t||1!==t.length)throw new Error("Unsqueeze requires 1 input.");if("string"===t[0].type)throw new Error("invalid input tensor types.")},o=t=>{if(!t||2!==t.length)throw new Error("Unsqueeze requires 2 inputs.");if("int32"!==t[1].type)throw new Error("Invalid input type.")}},9793:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.scalesValidation=e.validateInputs=e.parseUpsampleAttributes=e.parseUpsampleAttributesV9=e.parseUpsampleAttributesV7=e.upsample=void 0;const r=n(246),i=n(5060),o=n(2039),a={name:"Upsample",inputNames:["X"],inputTypes:[o.TextureType.unpacked]};e.upsample=(t,n,r)=>((0,e.validateInputs)(n,r),[t.run(Object.assign(Object.assign({},a),{cacheHint:r.cacheKey,get:()=>s(t,n,r)}),n)]),e.parseUpsampleAttributesV7=t=>(0,e.parseUpsampleAttributes)(t,7),e.parseUpsampleAttributesV9=t=>(0,e.parseUpsampleAttributes)(t,9),e.parseUpsampleAttributes=(t,n)=>{const i=n>=10,o=t.attributes.getString("mode","nearest");if("nearest"!==o&&"linear"!==o&&(n<11||"cubic"!==o))throw new Error(`unrecognized mode: ${o}`);let a=[];n<9&&(a=t.attributes.getFloats("scales"),(0,e.scalesValidation)(a,o,i));const s=t.attributes.getFloat("extrapolation_value",0),u=n>10?t.attributes.getString("coordinate_transformation_mode","half_pixel"):"asymmetric";if(-1===["asymmetric","pytorch_half_pixel","tf_half_pixel_for_nn","align_corners","tf_crop_and_resize","half_pixel"].indexOf(u))throw new Error(`coordinate_transform_mode '${u}' is not supported`);const c="tf_crop_and_resize"===u,l=c,p="nearest"===o&&n>=11?t.attributes.getString("nearest_mode","round_prefer_floor"):"";if(-1===["round_prefer_floor","round_prefer_ceil","floor","ceil",""].indexOf(p))throw new Error(`nearest_mode '${p}' is not supported`);const f=t.attributes.getFloat("cubic_coeff_a",-.75),d=0!==t.attributes.getInt("exclude_outside",0);if(d&&"cubic"!==o)throw new Error("exclude_outside can be set to 1 only when mode is CUBIC.");const h=n<11||"nearest"===o&&"asymmetric"===u&&"floor"===p;let g=0,b=0,m=0;return n>10?t.inputs.length>2?(g=1,b=2,m=3):(b=1,m=2):9===n&&(b=1),(0,r.createAttributeWithCacheKey)({opset:n,isResize:i,mode:o,scales:a,extrapolationValue:s,coordinateTransformMode:u,useExtrapolation:l,needRoiInput:c,nearestMode:p,cubicCoefficientA:f,excludeOutside:d,useNearest2xOptimization:h,roiInputIdx:g,scalesInputIdx:b,sizesInputIdx:m})};const s=(t,e,n)=>{const r=(0,i.getGlsl)(t.session.backend.glContext.version),[s,u]=t.calculateTextureWidthAndHeight(e[0].dims,o.TextureType.unpacked),c=e[0].dims.map(((t,e)=>Math.floor(t*n.scales[e]))),[l,p]=t.calculateTextureWidthAndHeight(c,o.TextureType.unpacked),f=c.length,d=new Array(f),h=new Array(f);let g=`\n      int output_pitches[${f}];\n      int input_pitches[${f}];\n      `;for(let t=f-1;t>=0;t--)d[t]=t===f-1?1:d[t+1]*c[t+1],h[t]=t===f-1?1:h[t+1]*e[0].dims[t+1],g+=`\n        output_pitches[${t}] = ${d[t]};\n        input_pitches[${t}] = ${h[t]};\n        `;const b=`\n      float getInputFloat(int index) {\n        vec2 coords = offsetToCoords(index, ${s}, ${u});\n        float value = getColorAsFloat(${r.texture2D}(X, coords));\n        return value;\n      }\n      `,m="nearest"===n.mode?`\n    ${b}\n    float process(int indices[${f}]) {\n      int input_index = 0;\n      int output_index = coordsToOffset(TexCoords, ${l}, ${p});\n\n      ${g}\n\n      int d, m;\n      for (int dim = 0; dim < ${f}; ++dim) {\n        d = output_index / output_pitches[dim];\n        m = output_index - d * output_pitches[dim];\n        output_index = m;\n\n        if (scales[dim] != 1 && d > 0) {\n          int d2 = d / scales[dim];\n          m = d - d2 * scales[dim];\n          d = d2;\n        }\n        input_index += input_pitches[dim] * d;\n      }\n\n      return getInputFloat(input_index);\n    }`:4===f?`\n    ${b}\n    float process(int indices[4]) {\n      int input_index = 0;\n      int output_index = coordsToOffset(TexCoords, ${l}, ${p});\n\n      ${g}\n\n      int m;\n      int index_of_dim0, index_of_dim1, index_of_dim2, index_of_dim3;\n      index_of_dim0 = output_index / output_pitches[0];\n      m = output_index - index_of_dim0 * output_pitches[0];\n      index_of_dim1 = m / output_pitches[1];\n      m = m - index_of_dim1 * output_pitches[1];\n      index_of_dim2 = m / output_pitches[2];\n      m = m - index_of_dim2 * output_pitches[2];\n      index_of_dim3 = m;\n\n      int index_of_input_dim2, index_of_input_dim3, x_offset, y_offset;\n      index_of_input_dim2 = index_of_dim2 / scales[2];\n      y_offset = index_of_dim2 - index_of_input_dim2 * scales[2];\n      index_of_input_dim3 = index_of_dim3 / scales[3];\n      x_offset = index_of_dim3 - index_of_input_dim3 * scales[3];\n\n      input_index = index_of_dim0 * input_pitches[0] +\n            index_of_dim1 * input_pitches[1] +\n            index_of_input_dim2 * input_pitches[2] +\n            index_of_input_dim3;\n\n      float x00 = getInputFloat(input_index);\n      float x10, x01, x11;\n\n      bool end_of_dim2 = false;\n      if (index_of_input_dim2 == (${e[0].dims[2]} - 1)) {\n        // It's the end in dimension 2\n        x01 = x00;\n        end_of_dim2 = true;\n      } else {\n        x01 = getInputFloat(input_index + input_pitches[2]);\n      }\n\n      if (index_of_input_dim3 == (input_pitches[2] - 1)) {\n        // It's the end in dimension 3\n        x10 = x00;\n        x11 = x01;\n      }\n      else {\n        x10 = getInputFloat(input_index + 1);\n        x11 = end_of_dim2 ? x10 : getInputFloat(input_index + input_pitches[2] + 1);\n      }\n\n      float y0 = x00 + float(y_offset) * (x01 - x00) / float(scales[2]);\n      float y1 = x10 + float(y_offset) * (x11 - x10) / float(scales[2]);\n      return y0 + float(x_offset) * (y1 - y0) / float(scales[3]);\n    }`:`\n    ${b}\n    float process(int indices[2]) {\n      int input_index = 0;\n      int output_index = coordsToOffset(TexCoords, ${l}, ${p});\n\n      ${g}\n\n      int m;\n      int index_of_dim0, index_of_dim1;\n      index_of_dim0 = output_index / output_pitches[0];\n      m = output_index - index_of_dim0 * output_pitches[0];\n      index_of_dim1 = m;\n\n      int index_of_input_dim0, index_of_input_dim1, x_offset, y_offset;\n      index_of_input_dim0 = index_of_dim0 / scales[0];\n      y_offset = index_of_dim0 - index_of_input_dim0 * scales[0];\n      index_of_input_dim1 = index_of_dim1 / scales[1];\n      x_offset = index_of_dim1 - index_of_input_dim1 * scales[1];\n\n      input_index = index_of_input_dim0 * input_pitches[0] + index_of_input_dim1;\n\n      float x00 = getInputFloat(input_index);\n      float x10, x01, x11;\n\n      bool end_of_dim0 = false;\n      if (index_of_input_dim0 == (${e[0].dims[0]} - 1)) {\n        // It's the end in dimension 0\n        x01 = x00;\n        end_of_dim0 = true;\n      } else {\n        x01 = getInputFloat(input_index + input_pitches[0]);\n      }\n\n      if (index_of_input_dim1 == (input_pitches[0] - 1)) {\n        // It's the end in dimension 1\n        x10 = x00;\n        x11 = x01;\n      }\n      else {\n        x10 = getInputFloat(input_index + 1);\n        x11 = end_of_dim0 ? x10 : getInputFloat(input_index + input_pitches[0] + 1);\n      }\n\n      float y0 = x00 + float(y_offset) * (x01 - x00) / float(scales[0]);\n      float y1 = x10 + float(y_offset) * (x11 - x10) / float(scales[0]);\n      return y0 + float(x_offset) * (y1 - y0) / float(scales[1]);\n    }`;return Object.assign(Object.assign({},a),{output:{dims:c,type:e[0].type,textureType:o.TextureType.unpacked},shaderSource:m,variables:[{name:"scales",type:"int",arrayLength:n.scales.length,data:n.scales.map((t=>Math.ceil(t)))}]})};e.validateInputs=(t,e)=>{if(!t||e.opset<9&&1!==t.length||e.opset>=9&&e.opset<11&&2!==t.length||e.opset>=11&&t.length<2)throw new Error("invalid inputs.");if(e.scales.length>0&&t[0].dims.length!==e.scales.length)throw new Error("Invalid input shape.");if("string"===t[0].type)throw new Error("Invalid input tensor types.")},e.scalesValidation=(t,e,n)=>{if(n){for(const e of t)if(e<=0)throw new Error("Scale value should be greater than 0.")}else for(const e of t)if(e<1)throw new Error("Scale value should be greater than or equal to 1.");if(!("linear"!==e&&"cubic"!==e||2===t.length||4===t.length&&1===t[0]&&1===t[1]))throw new Error(`'Linear' mode and 'Cubic' mode only support 2-D inputs ('Bilinear', 'Bicubic')         or 4-D inputs with the corresponding outermost 2 scale values being 1         in the ${n?"Resize":"Upsample"} opeartor.`)}},1958:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.ProgramManager=void 0;const r=n(8453),i=n(6231),o=n(8879),a=n(5060);e.ProgramManager=class{constructor(t,e,n){this.profiler=t,this.glContext=e,this.textureLayoutStrategy=n,this.repo=new Map,this.attributesBound=!1}getArtifact(t){return this.repo.get(t)}setArtifact(t,e){this.repo.set(t,e)}run(t,e,n){var r;this.profiler.event("op",`ProgramManager.run ${null!==(r=t.programInfo.name)&&void 0!==r?r:"unknown kernel"}`,(()=>{var r;const o=this.glContext.gl,a=t.program;o.useProgram(a);try{this.bindOutput(n),this.attributesBound||this.bindAttributes(t.attribLocations),this.bindUniforms(t.uniformLocations,null!==(r=t.programInfo.variables)&&void 0!==r?r:[],e)}catch(e){throw i.Logger.error("ProgramManager",t.programInfo.shaderSource),e}this.profiler.event("backend","GlContext.draw()",(()=>{this.glContext.draw()}))}),this.glContext)}dispose(){this.vertexShader&&this.glContext.deleteShader(this.vertexShader),this.repo.forEach((t=>this.glContext.deleteProgram(t.program)))}build(t,e,n){return this.profiler.event("backend","ProgramManager.build",(()=>{const r=new o.GlslPreprocessor(this.glContext,t,e,n),i=r.preprocess(),a=this.compile(i);return{programInfo:t,program:a,uniformLocations:this.getUniformLocations(a,r.context.programInfo.inputNames,r.context.programInfo.variables),attribLocations:this.getAttribLocations(a)}}))}compile(t){if(!this.vertexShader){i.Logger.verbose("ProrgramManager","Compiling and caching Vertex shader for the first time");const t=(0,a.getVertexShaderSource)(this.glContext.version);this.vertexShader=this.glContext.compileShader(t,this.glContext.gl.VERTEX_SHADER)}r.env.debug&&i.Logger.verbose("ProrgramManager",`FragShader:\n${t}\n`);const e=this.glContext.compileShader(t,this.glContext.gl.FRAGMENT_SHADER),n=this.glContext.createProgram(this.vertexShader,e);return this.glContext.deleteShader(e),n}bindOutput(t){const e=t.width,n=t.height;i.Logger.verbose("ProrgramManager",`Binding output texture to Framebuffer: w/h=${e}/${n}, shape=${t.shape}, type=${t.tensor.type}`),this.glContext.attachFramebuffer(t.texture,e,n)}bindAttributes(t){const e=t.position,n=t.textureCoord;this.glContext.setVertexAttributes(e,n),this.attributesBound=!0}bindUniforms(t,e,n){var r;const i=this.glContext.gl;let o=0;for(const{name:a,type:s,location:u,arrayLength:c}of t){const t=null===(r=e.find((t=>t.name===a)))||void 0===r?void 0:r.data;if("sampler2D"!==s&&!t)throw new Error(`variable '${a}' does not have data defined in program info`);switch(s){case"sampler2D":this.bindTexture(n[o],u,o),o++;break;case"float":c?i.uniform1fv(u,t):i.uniform1f(u,t);break;case"int":c?i.uniform1iv(u,t):i.uniform1i(u,t);break;default:throw new Error(`Uniform not implemented: ${s}`)}}}bindTexture(t,e,n){this.glContext.bindTextureToUniform(t.texture,n,e)}getAttribLocations(t){return{position:this.getAttribLocation(t,"position"),textureCoord:this.getAttribLocation(t,"textureCoord")}}getUniformLocations(t,e,n){const r=[];if(e)for(const n of e)r.push({name:n,type:"sampler2D",location:this.getUniformLocation(t,n)});if(n)for(const e of n)r.push(Object.assign(Object.assign({},e),{location:this.getUniformLocation(t,e.name)}));return r}getUniformLocation(t,e){const n=this.glContext.gl.getUniformLocation(t,e);if(null===n)throw new Error(`Uniform ${e} not found.`);return n}getAttribLocation(t,e){return this.glContext.gl.getAttribLocation(t,e)}}},6416:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.WebGLSessionHandler=void 0;const r=n(6231),i=n(1047),o=n(8316),a=n(1640),s=n(1958),u=n(7859),c=n(5702);e.WebGLSessionHandler=class{constructor(t,e){this.backend=t,this.context=e,this.layoutStrategy=new u.PreferLogicalStrategy(t.glContext.maxTextureSize),this.programManager=new s.ProgramManager(this.context.profiler,t.glContext,this.layoutStrategy),this.textureManager=new c.TextureManager(t.glContext,this.layoutStrategy,this.context.profiler,{reuseTextures:"full"===t.textureCacheMode}),this.packedTextureDataCache=new Map,this.unpackedTextureDataCache=new Map,this.pack=t.pack,this.pack2unpackMap=new Map,this.unpack2packMap=new Map}createInferenceHandler(){return new o.WebGLInferenceHandler(this)}onGraphInitialized(t){const e=t.getValues().filter((t=>-1===t.from&&t.tensor)).map((t=>t.tensor.dataId));this.initializers=new Set(e)}isInitializer(t){return!!this.initializers&&this.initializers.has(t)}addInitializer(t){this.initializers.add(t)}getTextureData(t,e){return e?this.packedTextureDataCache.get(t):this.unpackedTextureDataCache.get(t)}setTextureData(t,e,n=!1){r.Logger.verbose("WebGLSessionHandler","Storing Texture data in cache"),n?this.packedTextureDataCache.set(t,e):this.unpackedTextureDataCache.set(t,e)}dispose(){this.programManager.dispose(),this.textureManager.clearActiveTextures(),this.packedTextureDataCache.forEach((t=>this.textureManager.releaseTexture(t,!0))),this.packedTextureDataCache=new Map,this.unpackedTextureDataCache.forEach((t=>this.textureManager.releaseTexture(t,!0))),this.unpackedTextureDataCache=new Map}resolve(t,e,n){const r=(0,i.resolveOperator)(t,e,a.WEBGL_OP_RESOLVE_RULES);return{impl:r.opImpl,context:r.opInit?r.opInit(t,n):t}}}},7769:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.Uint8DataEncoder=e.RGBAFloatDataEncoder=e.RedFloat32DataEncoder=void 0;const r=n(6231);e.RedFloat32DataEncoder=class{constructor(t,e=1){if(1===e)this.internalFormat=t.R32F,this.format=t.RED,this.textureType=t.FLOAT,this.channelSize=e;else{if(4!==e)throw new Error(`Invalid number of channels: ${e}`);this.internalFormat=t.RGBA32F,this.format=t.RGBA,this.textureType=t.FLOAT,this.channelSize=e}}encode(t,e){let n,i;return t.constructor!==Float32Array&&(r.Logger.warning("Encoder","data was not of type Float32; creating new Float32Array"),i=new Float32Array(t)),e*this.channelSize>t.length?(r.Logger.warning("Encoder","Source data too small. Allocating larger array"),i=t,n=this.allocate(e*this.channelSize),i.forEach(((t,e)=>n[e]=t))):(i=t,n=i),n}allocate(t){return new Float32Array(4*t)}decode(t,e){return 1===this.channelSize?t.filter(((t,e)=>e%4==0)).subarray(0,e):t.subarray(0,e)}},e.RGBAFloatDataEncoder=class{constructor(t,e=1,n){if(1!==e&&4!==e)throw new Error(`Invalid number of channels: ${e}`);this.internalFormat=t.RGBA,this.format=t.RGBA,this.channelSize=e,this.textureType=n||t.FLOAT}encode(t,e){let n=t;return 1===this.channelSize&&(r.Logger.verbose("Encoder","Exploding into a larger array"),n=this.allocate(e),t.forEach(((t,e)=>n[4*e]=t))),n}allocate(t){return new Float32Array(4*t)}decode(t,e){return 1===this.channelSize?t.filter(((t,e)=>e%4==0)).subarray(0,e):t.subarray(0,e)}},e.Uint8DataEncoder=class{constructor(t,e=1){if(this.channelSize=4,1===e)this.internalFormat=t.ALPHA,this.format=t.ALPHA,this.textureType=t.UNSIGNED_BYTE,this.channelSize=e;else{if(4!==e)throw new Error(`Invalid number of channels: ${e}`);this.internalFormat=t.RGBA,this.format=t.RGBA,this.textureType=t.UNSIGNED_BYTE,this.channelSize=e}}encode(t,e){return new Uint8Array(t.buffer,t.byteOffset,t.byteLength)}allocate(t){return new Uint8Array(t*this.channelSize)}decode(t,e){if(t instanceof Uint8Array)return t.subarray(0,e);throw new Error(`Invalid array type: ${t.constructor}`)}}},7859:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.getBatchDim=e.sizeToSquarishShape=e.getRowsCols=e.sizeFromShape=e.isInt=e.parseAxisParam=e.squeezeShape=e.PreferLogicalStrategy=e.AlwaysKeepOriginalSizeStrategy=void 0;const r=n(6231),i=n(2517);function o(t,e){const n=[],r=[],i=null!=e&&Array.isArray(e)&&0===e.length,o=null==e||i?null:a(e,t).sort();let s=0;for(let e=0;e<t.length;++e){if(null!=o){if(o[s]===e&&1!==t[e])throw new Error(`Can't squeeze axis ${e} since its dim '${t[e]}' is not 1`);(null==o[s]||o[s]>e)&&1===t[e]&&(n.push(t[e]),r.push(e)),o[s]<=e&&s++}1!==t[e]&&(n.push(t[e]),r.push(e))}return{newShape:n,keptDims:r}}function a(t,e){const n=e.length;return t=null==t?e.map(((t,e)=>e)):[].concat(t),(0,i.assert)(t.every((t=>t>=-n&&t<n)),(()=>`All values in axis param must be in range [-${n}, ${n}) but got axis ${t}`)),(0,i.assert)(t.every(s),(()=>`All values in axis param must be integers but got axis ${t}`)),t.map((t=>t<0?n+t:t))}function s(t){return t%1==0}function u(t){if(0===t.length)return 1;let e=t[0];for(let n=1;n<t.length;n++)e*=t[n];return e}function c(t){const e=Math.ceil(Math.sqrt(t));return[e,Math.ceil(t/e)]}e.AlwaysKeepOriginalSizeStrategy=class{constructor(t){this.maxTextureSize=t}computeTextureWH(t,e){if(0===t.length)return[1,1];const n=this.maxTextureSize;if(e&&void 0!==e.breakAxis){const i=e.breakAxis>=t.length?1:t.slice(e.breakAxis).reduce(((t,e)=>t*e)),o=e.breakAxis<=0?1:t.slice(0,e.breakAxis).reduce(((t,e)=>t*e));if(!(i>n||o>n))return[i,o];r.Logger.verbose("TextureLayout",`Given width/height preferences were unattainable: shape:${t}, breakAxis:${e.breakAxis}`)}const i=t.reduce(((t,e)=>t*e));let o=Math.floor(Math.sqrt(i));for(;o<n&&o<i&&i%o!=0;o++);if(o>=n||i%o!=0)throw new Error(`The given dimensions are outside this GPU's boundaries: ${t}`);return[o,i/o]}},e.PreferLogicalStrategy=class{constructor(t){this.maxTextureSize=t}computeTextureWH(t,e){const n=this.computeTexture(t,e);return e&&e.isPacked&&(n[0]/=2,n[1]/=2),e&&e.reverseWH?[n[1],n[0]]:n}computeTexture(t,e){const n=e&&e.isPacked;if(0===t.length)return n?[2,2]:[1,1];let i=this.maxTextureSize;if(e&&void 0!==e.breakAxis){const n=e.breakAxis>=t.length?1:t.slice(e.breakAxis).reduce(((t,e)=>t*e)),o=e.breakAxis<=0?1:t.slice(0,e.breakAxis).reduce(((t,e)=>t*e));if(!(n>i||o>i))return[n,o];r.Logger.verbose("TextureLayout",`Given width/height preferences were unattainable: shape:${t}, breakAxis:${e.breakAxis}`)}let a=t.slice(0);if(n&&(i*=2,a=a.map(((t,e)=>e>=a.length-2?a[e]%2==0?a[e]:a[e]+1:a[e])),1===a.length&&(a=[2,a[0]])),2!==a.length){const t=o(a);a=t.newShape}const s=u(a);return a.length<=1&&s<=i?[1,s]:2===a.length&&a[0]<=i&&a[1]<=i?a:3===a.length&&a[0]*a[1]<=i&&a[2]<=i?[a[0]*a[1],a[2]]:3===a.length&&a[0]<=i&&a[1]*a[2]<=i?[a[0],a[1]*a[2]]:4===a.length&&a[0]*a[1]*a[2]<=i&&a[3]<=i?[a[0]*a[1]*a[2],a[3]]:4===a.length&&a[0]<=i&&a[1]*a[2]*a[3]<=i?[a[0],a[1]*a[2]*a[3]]:n?c(s/4).map((t=>2*t)):c(s)}},e.squeezeShape=o,e.parseAxisParam=a,e.isInt=s,e.sizeFromShape=u,e.getRowsCols=function(t){if(0===t.length)throw Error("Cannot get rows and columns of an empty shape array.");return[t.length>1?t[t.length-2]:1,t[t.length-1]]},e.sizeToSquarishShape=c,e.getBatchDim=function(t,e=2){return u(t.slice(0,t.length-e))}},4057:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.createTextureLayoutFromShape=e.calculateTextureWidthAndHeight=e.createTextureLayoutFromTextureType=void 0;const r=n(2517),i=n(2039);e.createTextureLayoutFromTextureType=(t,n,r)=>{const o=r===i.TextureType.unpacked||r===i.TextureType.unpackedReversed?1:4,a=r===i.TextureType.packed,s=r===i.TextureType.unpackedReversed||r===i.TextureType.packed,u=r===i.TextureType.packedLastDimension?n.length-1:void 0,c=r===i.TextureType.packedLastDimension?n.map(((t,e)=>e===n.length-1?4*t:t)):void 0;return(0,e.createTextureLayoutFromShape)(t,n,o,c,{isPacked:a,reverseWH:s,breakAxis:u})},e.calculateTextureWidthAndHeight=(t,n,r)=>{const i=(0,e.createTextureLayoutFromTextureType)(t,n,r);return[i.width,i.height]},e.createTextureLayoutFromShape=(t,e,n=1,i,o)=>{const a=!(!o||!o.isPacked),[s,u]=t.computeTextureWH(a&&i||e,o),c=e.length;let l=e.slice(0);if(0===c&&(l=[1]),1===n)i=e;else if(a){if(4!==n)throw new Error("a packed texture must be 4-channel");i=e,c>0&&(l[c-1]=Math.ceil(l[c-1]/2)),c>1&&(l[c-2]=Math.ceil(l[c-2]/2))}else if(!i)throw new Error("Unpacked shape is needed when using channels > 1");return{width:s,height:u,channels:n,isPacked:a,shape:l,strides:r.ShapeUtil.computeStrides(l),unpackedShape:i,reversedWH:o&&o.reverseWH}}},5702:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.TextureManager=void 0;const r=n(6231);e.TextureManager=class{constructor(t,e,n,r){this.glContext=t,this.layoutStrategy=e,this.profiler=n,this.config=r,this.pendingRead=new Map,r.reuseTextures&&(this.inUseTextures=new Map,this.idleTextures=new Map,this.textureLookup=new Map)}createTextureFromLayout(t,e,n,i){const o=this.toEncoderType(t),a=this.glContext.getEncoder(o,e.channels||1,i);if(e.isPacked&&1===i)throw new Error("not implemented");const s=e.width,u=e.height;let c,l;if(this.config.reuseTextures){c=`${s}x${u}_${a.format}_${a.internalFormat}_${a.textureType}`,l=this.inUseTextures.get(c),l||(l=[],this.inUseTextures.set(c,l));const e=this.idleTextures.get(c);if(e&&e.length>0){const r=e.pop();return l.push(r),1===i&&this.glContext.updateTexture(r,s,u,a,this.toTextureData(t,n)),r}}r.Logger.verbose("TextureManager",`Creating new texture of size ${e.width}x${e.height}`);const p=this.glContext.allocateTexture(s,u,a,this.toTextureData(t,n));return this.config.reuseTextures&&(l.push(p),this.textureLookup.set(p,c)),p}readTexture(t,e,n){return n||(n=1),this.profiler.event("backend","TextureManager.readTexture",(()=>{const r=t.shape.reduce(((t,e)=>t*e))*n,i=this.glContext.readTexture(t.texture,t.width,t.height,r,this.toEncoderType(e),n);return this.toTensorData(e,i)}))}async readTextureAsync(t,e,n){const r=t.tensor.dataId;if(n||(n=1),this.pendingRead.has(r)){const t=this.pendingRead.get(r);return new Promise((e=>null==t?void 0:t.push(e)))}return this.profiler.event("backend","TextureManager.readTextureAsync",(async()=>{this.pendingRead.set(r,[]);const i=t.shape.reduce(((t,e)=>t*e))*n;await this.glContext.createAndWaitForFence();const o=this.glContext.readTexture(t.texture,t.width,t.height,i,this.toEncoderType(e),n),a=this.toTensorData(e,o),s=this.pendingRead.get(r);return this.pendingRead.delete(r),null==s||s.forEach((t=>t(a))),a}))}readUint8TextureAsFloat(t){return this.profiler.event("backend","TextureManager.readUint8TextureAsFloat",(()=>{const e=t.shape.reduce(((t,e)=>t*e)),n=this.glContext.readTexture(t.texture,t.width,t.height,4*e,"byte",4);return new Float32Array(n.buffer,n.byteOffset,e)}))}releaseTexture(t,e){let n;if(this.config.reuseTextures&&(n=this.textureLookup.get(t.texture),n)){e&&this.textureLookup.delete(n);const r=this.inUseTextures.get(n);if(r){const e=r.indexOf(t.texture);if(-1!==e){r.splice(e,1);let i=this.idleTextures.get(n);i||(i=[],this.idleTextures.set(n,i)),i.push(t.texture)}}}n&&!e||(r.Logger.verbose("TextureManager",`Deleting texture of size ${t.width}x${t.height}`),this.glContext.deleteTexture(t.texture))}toTensorData(t,e){switch(t){case"int16":return e instanceof Int16Array?e:Int16Array.from(e);case"int32":return e instanceof Int32Array?e:Int32Array.from(e);case"int8":return e instanceof Int8Array?e:Int8Array.from(e);case"uint16":return e instanceof Uint16Array?e:Uint16Array.from(e);case"uint32":return e instanceof Uint32Array?e:Uint32Array.from(e);case"uint8":case"bool":return e instanceof Uint8Array?e:Uint8Array.from(e);case"float32":return e instanceof Float32Array?e:Float32Array.from(e);case"float64":return e instanceof Float64Array?e:Float64Array.from(e);default:throw new Error(`TensorData type ${t} is not supported`)}}toTextureData(t,e){if(e)return e instanceof Float32Array?e:new Float32Array(e)}toEncoderType(t){return"float"}clearActiveTextures(){this.glContext.clearActiveTextures()}}},2039:(t,e)=>{"use strict";var n;Object.defineProperty(e,"__esModule",{value:!0}),e.TextureType=void 0,(n=e.TextureType||(e.TextureType={}))[n.unpacked=0]="unpacked",n[n.unpackedReversed=1]="unpackedReversed",n[n.packed=2]="packed",n[n.downloadUint8AsFloat=3]="downloadUint8AsFloat",n[n.packedLastDimension=4]="packedLastDimension"},9390:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.getGlChannels=e.getCoordsDataType=e.getSqueezedParams=e.squeezeInputShape=e.generateShaderFuncNameFromInputSamplerNameAtOutCoords=e.generateShaderFuncNameFromInputSamplerName=e.repeatedTry=e.getPackedShape=void 0;const r=n(2517);e.getPackedShape=function(t){const e=t.length;return t.slice(0,e-1).concat(t[e-1]/4)},e.repeatedTry=async function(t,e=(t=>0),n){return new Promise(((r,i)=>{let o=0;const a=()=>{if(t())return void r();o++;const s=e(o);null!=n&&o>=n?i():setTimeout(a,s)};a()}))},e.generateShaderFuncNameFromInputSamplerName=function(t){return(0,r.assert)(void 0!==t&&0!==t.length,(()=>"empty string found for sampler name")),"get"+t.charAt(0).toUpperCase()+t.slice(1)},e.generateShaderFuncNameFromInputSamplerNameAtOutCoords=function(t){return(0,r.assert)(void 0!==t&&0!==t.length,(()=>"empty string found for sampler name")),"get"+t.charAt(0).toUpperCase()+t.slice(1)+"AtOutCoords"},e.squeezeInputShape=function(t,e){let n=JSON.parse(JSON.stringify(t));return n=e,n},e.getSqueezedParams=function(t,e){return e.map((e=>t[e])).join(", ")},e.getCoordsDataType=function(t){if(t<=1)return"int";if(2===t)return"ivec2";if(3===t)return"ivec3";if(4===t)return"ivec4";if(5===t)return"ivec5";if(6===t)return"ivec6";throw Error(`GPU for rank ${t} is not yet supported`)},e.getGlChannels=function(t=6){return["x","y","z","w","u","v"].slice(0,t)}},7305:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.createNewWebGLContext=e.createWebGLContext=void 0;const r=n(6231),i=n(1713),o={};function a(t){const e=function(){if("undefined"==typeof document){if("undefined"==typeof OffscreenCanvas)throw new TypeError("failed to create canvas: OffscreenCanvas is not supported");return new OffscreenCanvas(1,1)}const t=document.createElement("canvas");return t.width=1,t.height=1,t}();let n;const o={alpha:!1,depth:!1,antialias:!1,stencil:!1,preserveDrawingBuffer:!1,premultipliedAlpha:!1,failIfMajorPerformanceCaveat:!1};if((!t||"webgl2"===t)&&(n=e.getContext("webgl2",o),n))try{return new i.WebGLContext(n,2)}catch(t){r.Logger.warning("GlContextFactory",`failed to create WebGLContext using contextId 'webgl2'. Error: ${t}`)}if((!t||"webgl"===t)&&(n=e.getContext("webgl",o)||e.getContext("experimental-webgl",o),n))try{return new i.WebGLContext(n,1)}catch(t){r.Logger.warning("GlContextFactory",`failed to create WebGLContext using contextId 'webgl' or 'experimental-webgl'. Error: ${t}`)}throw new Error("WebGL is not supported")}e.createWebGLContext=function t(e){let n;e&&"webgl2"!==e||!("webgl2"in o)?e&&"webgl"!==e||!("webgl"in o)||(n=o.webgl):n=o.webgl2,n=n||a(e),e=e||1===n.version?"webgl":"webgl2";const r=n.gl;return o[e]=n,r.isContextLost()?(delete o[e],t(e)):(r.disable(r.DEPTH_TEST),r.disable(r.STENCIL_TEST),r.disable(r.BLEND),r.disable(r.DITHER),r.disable(r.POLYGON_OFFSET_FILL),r.disable(r.SAMPLE_COVERAGE),r.enable(r.SCISSOR_TEST),r.enable(r.CULL_FACE),r.cullFace(r.BACK),n)},e.createNewWebGLContext=a},1713:function(t,e,n){"use strict";var r=this&&this.__createBinding||(Object.create?function(t,e,n,r){void 0===r&&(r=n);var i=Object.getOwnPropertyDescriptor(e,n);i&&!("get"in i?!e.__esModule:i.writable||i.configurable)||(i={enumerable:!0,get:function(){return e[n]}}),Object.defineProperty(t,r,i)}:function(t,e,n,r){void 0===r&&(r=n),t[r]=e[n]}),i=this&&this.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),o=this&&this.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)"default"!==n&&Object.prototype.hasOwnProperty.call(t,n)&&r(e,t,n);return i(e,t),e};Object.defineProperty(e,"__esModule",{value:!0}),e.WebGLContext=e.linearSearchLastTrue=void 0;const a=n(8453),s=o(n(7769)),u=n(9390);function c(t){let e=0;for(;e<t.length&&t[e]();++e);return e-1}e.linearSearchLastTrue=c,e.WebGLContext=class{constructor(t,e){this.frameBufferBound=!1,this.itemsToPoll=[],this.gl=t,this.version=e,this.getExtensions(),this.vertexbuffer=this.createVertexbuffer(),this.framebuffer=this.createFramebuffer(),this.queryVitalParameters()}allocateTexture(t,e,n,r){const i=this.gl,o=i.createTexture();i.bindTexture(i.TEXTURE_2D,o),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_MIN_FILTER,i.NEAREST),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_MAG_FILTER,i.NEAREST),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_S,i.CLAMP_TO_EDGE),i.texParameteri(i.TEXTURE_2D,i.TEXTURE_WRAP_T,i.CLAMP_TO_EDGE);const a=r?n.encode(r,t*e):null;return i.texImage2D(i.TEXTURE_2D,0,n.internalFormat,t,e,0,n.format,n.textureType,a),this.checkError(),o}updateTexture(t,e,n,r,i){const o=this.gl;o.bindTexture(o.TEXTURE_2D,t);const a=r.encode(i,e*n);o.texSubImage2D(o.TEXTURE_2D,0,0,0,e,n,r.format,r.textureType,a),this.checkError()}attachFramebuffer(t,e,n){const r=this.gl;r.bindTexture(r.TEXTURE_2D,t),r.bindFramebuffer(r.FRAMEBUFFER,this.framebuffer),r.framebufferTexture2D(r.FRAMEBUFFER,r.COLOR_ATTACHMENT0,r.TEXTURE_2D,t,0),this.checkError(),r.viewport(0,0,e,n),r.scissor(0,0,e,n)}readTexture(t,e,n,r,i,o){const a=this.gl;o||(o=1),this.frameBufferBound||this.attachFramebuffer(t,e,n);const s=this.getEncoder(i,o),u=s.allocate(e*n);return a.bindTexture(a.TEXTURE_2D,t),a.framebufferTexture2D(a.FRAMEBUFFER,a.COLOR_ATTACHMENT0,a.TEXTURE_2D,t,0),a.readPixels(0,0,e,n,a.RGBA,s.textureType,u),this.checkError(),s.decode(u,r)}isFramebufferReady(){return!0}getActiveTexture(){const t=this.gl;return"TEXTURE"+(t.getParameter(this.gl.ACTIVE_TEXTURE)-t.TEXTURE0)}getTextureBinding(){return this.gl.getParameter(this.gl.TEXTURE_BINDING_2D)}getFramebufferBinding(){return this.gl.getParameter(this.gl.FRAMEBUFFER_BINDING)}setVertexAttributes(t,e){const n=this.gl;n.vertexAttribPointer(t,3,n.FLOAT,!1,20,0),n.enableVertexAttribArray(t),-1!==e&&(n.vertexAttribPointer(e,2,n.FLOAT,!1,20,12),n.enableVertexAttribArray(e)),this.checkError()}createProgram(t,e){const n=this.gl,r=n.createProgram();return n.attachShader(r,t),n.attachShader(r,e),n.linkProgram(r),r}compileShader(t,e){const n=this.gl,r=n.createShader(e);if(!r)throw new Error(`createShader() returned null with type ${e}`);if(n.shaderSource(r,t),n.compileShader(r),!1===n.getShaderParameter(r,n.COMPILE_STATUS))throw new Error(`Failed to compile shader: ${n.getShaderInfoLog(r)}\nShader source:\n${t}`);return r}deleteShader(t){this.gl.deleteShader(t)}bindTextureToUniform(t,e,n){const r=this.gl;r.activeTexture(r.TEXTURE0+e),this.checkError(),r.bindTexture(r.TEXTURE_2D,t),this.checkError(),r.uniform1i(n,e),this.checkError()}draw(){this.gl.drawArrays(this.gl.TRIANGLE_STRIP,0,4),this.checkError()}checkError(){if(a.env.debug){const t=this.gl,e=t.getError();let n="";switch(e){case t.NO_ERROR:return;case t.INVALID_ENUM:n="INVALID_ENUM";break;case t.INVALID_VALUE:n="INVALID_VALUE";break;case t.INVALID_OPERATION:n="INVALID_OPERATION";break;case t.INVALID_FRAMEBUFFER_OPERATION:n="INVALID_FRAMEBUFFER_OPERATION";break;case t.OUT_OF_MEMORY:n="OUT_OF_MEMORY";break;case t.CONTEXT_LOST_WEBGL:n="CONTEXT_LOST_WEBGL";break;default:n=`Unknown WebGL Error: ${e.toString(16)}`}throw new Error(n)}}deleteTexture(t){this.gl.deleteTexture(t)}deleteProgram(t){this.gl.deleteProgram(t)}getEncoder(t,e,n=0){if(2===this.version)return new s.RedFloat32DataEncoder(this.gl,e);switch(t){case"float":return 1===n||this.isRenderFloat32Supported?new s.RGBAFloatDataEncoder(this.gl,e):new s.RGBAFloatDataEncoder(this.gl,e,this.textureHalfFloatExtension.HALF_FLOAT_OES);case"int":throw new Error("not implemented");case"byte":return new s.Uint8DataEncoder(this.gl,e);default:throw new Error(`Invalid dataType: ${t}`)}}clearActiveTextures(){const t=this.gl;for(let e=0;e<this.maxTextureImageUnits;++e)t.activeTexture(t.TEXTURE0+e),t.bindTexture(t.TEXTURE_2D,null)}dispose(){if(this.disposed)return;const t=this.gl;t.bindFramebuffer(t.FRAMEBUFFER,null),t.deleteFramebuffer(this.framebuffer),t.bindBuffer(t.ARRAY_BUFFER,null),t.deleteBuffer(this.vertexbuffer),t.bindBuffer(t.ELEMENT_ARRAY_BUFFER,null),t.finish(),this.disposed=!0}createDefaultGeometry(){return new Float32Array([-1,1,0,0,1,-1,-1,0,0,0,1,1,0,1,1,1,-1,0,1,0])}createVertexbuffer(){const t=this.gl,e=t.createBuffer();if(!e)throw new Error("createBuffer() returned null");const n=this.createDefaultGeometry();return t.bindBuffer(t.ARRAY_BUFFER,e),t.bufferData(t.ARRAY_BUFFER,n,t.STATIC_DRAW),this.checkError(),e}createFramebuffer(){const t=this.gl.createFramebuffer();if(!t)throw new Error("createFramebuffer returned null");return t}queryVitalParameters(){const t=this.gl;if(this.isFloatTextureAttachableToFrameBuffer=this.checkFloatTextureAttachableToFrameBuffer(),this.isRenderFloat32Supported=this.checkRenderFloat32(),this.isFloat32DownloadSupported=this.checkFloat32Download(),1===this.version&&!this.textureHalfFloatExtension&&!this.isRenderFloat32Supported)throw new Error("both float32 and float16 TextureType are not supported");this.isBlendSupported=!this.isRenderFloat32Supported||this.checkFloat32Blend(),this.maxTextureSize=t.getParameter(t.MAX_TEXTURE_SIZE),this.maxTextureImageUnits=t.getParameter(t.MAX_TEXTURE_IMAGE_UNITS),this.version}getExtensions(){2===this.version?(this.colorBufferFloatExtension=this.gl.getExtension("EXT_color_buffer_float"),this.disjointTimerQueryWebgl2Extension=this.gl.getExtension("EXT_disjoint_timer_query_webgl2")):(this.textureFloatExtension=this.gl.getExtension("OES_texture_float"),this.textureHalfFloatExtension=this.gl.getExtension("OES_texture_half_float"))}checkFloatTextureAttachableToFrameBuffer(){const t=this.gl,e=t.createTexture();t.bindTexture(t.TEXTURE_2D,e);const n=2===this.version?t.RGBA32F:t.RGBA;t.texImage2D(t.TEXTURE_2D,0,n,1,1,0,t.RGBA,t.FLOAT,null);const r=t.createFramebuffer();t.bindFramebuffer(t.FRAMEBUFFER,r),t.framebufferTexture2D(t.FRAMEBUFFER,t.COLOR_ATTACHMENT0,t.TEXTURE_2D,e,0);const i=t.checkFramebufferStatus(t.FRAMEBUFFER)===t.FRAMEBUFFER_COMPLETE;return t.bindTexture(t.TEXTURE_2D,null),t.bindFramebuffer(t.FRAMEBUFFER,null),t.deleteTexture(e),t.deleteFramebuffer(r),i}checkRenderFloat32(){if(2===this.version){if(!this.colorBufferFloatExtension)return!1}else if(!this.textureFloatExtension)return!1;return this.isFloatTextureAttachableToFrameBuffer}checkFloat32Download(){if(2===this.version){if(!this.colorBufferFloatExtension)return!1}else{if(!this.textureFloatExtension)return!1;if(!this.gl.getExtension("WEBGL_color_buffer_float"))return!1}return this.isFloatTextureAttachableToFrameBuffer}checkFloat32Blend(){const t=this.gl;let e,n,r,i,o;try{e=t.createTexture(),n=t.createFramebuffer(),t.bindTexture(t.TEXTURE_2D,e);const a=2===this.version?t.RGBA32F:t.RGBA;return t.texImage2D(t.TEXTURE_2D,0,a,1,1,0,t.RGBA,t.FLOAT,null),t.bindFramebuffer(t.FRAMEBUFFER,n),t.framebufferTexture2D(t.FRAMEBUFFER,t.COLOR_ATTACHMENT0,t.TEXTURE_2D,e,0),t.enable(t.BLEND),r=t.createShader(t.VERTEX_SHADER),!!r&&(t.shaderSource(r,"void main(){}"),t.compileShader(r),i=t.createShader(t.FRAGMENT_SHADER),!!i&&(t.shaderSource(i,"precision highp float;void main(){gl_FragColor=vec4(0.5);}"),t.compileShader(i),o=t.createProgram(),!!o&&(t.attachShader(o,r),t.attachShader(o,i),t.linkProgram(o),t.useProgram(o),t.drawArrays(t.POINTS,0,1),t.getError()===t.NO_ERROR)))}finally{t.disable(t.BLEND),o&&t.deleteProgram(o),r&&t.deleteShader(r),i&&t.deleteShader(i),n&&(t.bindFramebuffer(t.FRAMEBUFFER,null),t.deleteFramebuffer(n)),e&&(t.bindTexture(t.TEXTURE_2D,null),t.deleteTexture(e))}}beginTimer(){if(2===this.version&&this.disjointTimerQueryWebgl2Extension){const t=this.gl,e=this.disjointTimerQueryWebgl2Extension,n=t.createQuery();return t.beginQuery(e.TIME_ELAPSED_EXT,n),n}throw new Error("WebGL1 profiling currently not supported.")}endTimer(){if(2!==this.version||!this.disjointTimerQueryWebgl2Extension)throw new Error("WebGL1 profiling currently not supported");{const t=this.gl,e=this.disjointTimerQueryWebgl2Extension;t.endQuery(e.TIME_ELAPSED_EXT)}}isTimerResultAvailable(t){let e=!1,n=!1;if(2!==this.version||!this.disjointTimerQueryWebgl2Extension)throw new Error("WebGL1 profiling currently not supported");{const r=this.gl,i=this.disjointTimerQueryWebgl2Extension;e=r.getQueryParameter(t,r.QUERY_RESULT_AVAILABLE),n=r.getParameter(i.GPU_DISJOINT_EXT)}return e&&!n}getTimerResult(t){let e=0;if(2!==this.version)throw new Error("WebGL1 profiling currently not supported");{const n=this.gl;e=n.getQueryParameter(t,n.QUERY_RESULT),n.deleteQuery(t)}return e/1e6}async waitForQueryAndGetTime(t){return await(0,u.repeatedTry)((()=>this.isTimerResultAvailable(t))),this.getTimerResult(t)}async createAndWaitForFence(){const t=this.createFence(this.gl);return this.pollFence(t)}createFence(t){let e;const n=t,r=n.fenceSync(n.SYNC_GPU_COMMANDS_COMPLETE,0);return t.flush(),e=null===r?()=>!0:()=>{const t=n.clientWaitSync(r,0,0);return t===n.ALREADY_SIGNALED||t===n.CONDITION_SATISFIED},{query:r,isFencePassed:e}}async pollFence(t){return new Promise((e=>{this.addItemToPoll((()=>t.isFencePassed()),(()=>e()))}))}pollItems(){const t=c(this.itemsToPoll.map((t=>t.isDoneFn)));for(let e=0;e<=t;++e){const{resolveFn:t}=this.itemsToPoll[e];t()}this.itemsToPoll=this.itemsToPoll.slice(t+1)}async addItemToPoll(t,e){this.itemsToPoll.push({isDoneFn:t,resolveFn:e}),this.itemsToPoll.length>1||await(0,u.repeatedTry)((()=>(this.pollItems(),0===this.itemsToPoll.length)))}}},1036:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.ExecutionPlan=void 0;const r=n(6231);class i{constructor(t,e){this.op=t,this.node=e}}e.ExecutionPlan=class{constructor(t,e,n){this.graph=t,this.profiler=n,this.initialize(e)}initialize(t){this.profiler.event("session","ExecutionPlan.initialize",(()=>{const e=this.graph.getNodes();if(e.length!==t.length)throw new Error("The size of nodes and OPs do not match.");this._ops=t.map(((t,n)=>new i(t,e[n]))),this.reset(),this._starter=[],this._ops.forEach(((t,e)=>{let n=!0;for(const e of t.node.inputs)if(!this._values[e]&&-1===this.graph.getInputIndices().indexOf(e)){n=!1;break}n&&this._starter.push(e)}))}))}reset(){this._values=this.graph.getValues().map((t=>t.tensor))}async execute(t,e){return this.profiler.event("session","ExecutionPlan.execute",(async()=>{this.reset();const n=t.createInferenceHandler(),i=this.graph.getInputIndices();if(e.length!==i.length)throw new Error(`number of input tensors don't match the number of inputs to the model: actual: ${e.length} expected: ${i.length}`);e.forEach(((t,e)=>{const n=i[e];this._values[n]=t}));const o=this._starter.slice(0),a=this.graph.getValues(),s=this.graph.getNodes();let u=0;for(;u<o.length;){const t=o[u++],e=this._ops[t],i=e.node.inputs.map((t=>this._values[t]));if(-1!==i.indexOf(void 0))throw new Error(`unresolved input detected: op: ${e.node}`);const c=i;r.Logger.verbose("ExecPlan",`Runing op:${e.node.name} (${c.map(((t,n)=>`'${e.node.inputs[n]}': ${t.type}[${t.dims.join(",")}]`)).join(", ")})`);const l=await this.profiler.event("node",e.node.name,(async()=>e.op.impl(n,c,e.op.context)));if(l.length!==e.node.outputs.length)throw new Error("the size of output does not match model definition.");l.forEach(((t,n)=>{const r=e.node.outputs[n];if(this._values[r])throw new Error(`output [${r}] already has value: op:${e.node.name}`);this._values[r]=t}));const p=new Set;l.forEach(((t,n)=>{const r=e.node.outputs[n];for(const t of a[r].to){const e=s[t];let n=!0;for(const t of e.inputs)if(!this._values[t]){n=!1;break}n&&p.add(t)}})),o.push(...p)}const c=[];for(let t=0;t<this.graph.getOutputIndices().length;t++){const e=this.graph.getOutputIndices()[t],n=this._values[e];if(void 0===n)throw new Error(`required output [${e}] does not have value`);0===e?await n.getData():n.data,c.push(n)}return r.Logger.verbose("ExecPlan","disposing of inferenceHandler"),n.dispose(),c}))}}},7070:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.Graph=void 0;const r=n(1446),i=n(7778),o=n(9395),a=n(9162),s=n(2517);var u=o.onnxruntime.experimental.fbs;e.Graph={from:(t,e)=>new p(t,e)};class c{constructor(t){this._from=void 0,this._to=[],this.tensor=void 0,this.type=void 0,t&&(this.type=s.ProtoUtil.tensorValueTypeFromProto(t.type.tensorType))}get from(){return this._from}get to(){return this._to}}class l{constructor(t,e){t instanceof r.onnx.NodeProto?(this.name=t.name,this.opType=t.opType,this.attributes=new i.Attribute(t.attribute)):t instanceof u.Node&&(this.name=null!=e?e:t.name(),this.opType=t.opType(),this.attributes=new i.Attribute(s.ProtoUtil.tensorAttributesFromORTFormat(t))),this.inputs=[],this.outputs=[],this.executeNode=!0}}class p{constructor(t,e){if(!t)throw new TypeError("graph is empty");this.buildGraph(t),this.transformGraph(e),this.checkIsAcyclic()}getInputIndices(){return this._allInputIndices}getInputNames(){return this._allInputNames}getOutputIndices(){return this._allOutputIndices}getOutputNames(){return this._allOutputNames}getValues(){return this._allData}getNodes(){return this._nodes}buildGraph(t){if(t instanceof r.onnx.GraphProto)this.buildGraphFromOnnxFormat(t);else{if(!(t instanceof u.Graph))throw new TypeError("Graph type is not supported.");this.buildGraphFromOrtFormat(t)}}buildGraphFromOnnxFormat(t){const e=new Map;this._allData=[],this._allInputIndices=[],this._allInputNames=[],this._allOutputIndices=[],this._allOutputNames=[],this._nodes=[];const n=new Map;if(!t.input)throw new Error("missing information in graph: input");const r=[];for(const n of t.input){if(e.has(n.name))throw new Error(`duplicated input name: ${n.name}`);const t=this._allData.push(new c(n))-1;e.set(n.name,t),r.push(n.name)}if(!t.initializer)throw new Error("missing information in graph: initializer");for(const n of t.initializer){let t=e.get(n.name);if(void 0===t){const r=new c;r.type={shape:{dims:s.ProtoUtil.tensorDimsFromProto(n.dims)},tensorType:s.ProtoUtil.tensorDataTypeFromProto(n.dataType)},t=this._allData.push(r)-1,e.set(n.name,t)}this._allData[t]._from=-1,this._allData[t].tensor=a.Tensor.fromProto(n)}for(let t=0;t<this._allData.length;t++)this._allData[t].tensor||(this._allInputIndices.push(t),this._allInputNames.push(r[t]));if(!t.output)throw new Error("missing information in graph: output");for(const n of t.output){if(e.has(n.name))throw new Error(`duplicated output name: ${n.name}`);const t=this._allData.push(new c(n))-1;e.set(n.name,t),this._allOutputIndices.push(t),this._allOutputNames.push(n.name)}if(!t.node)throw new Error("missing information in graph: node");for(const e of t.node){if(!e.name)for(let t=0;;t++){const r=`unnamed_${e.opType}_${t}`;if(!n.has(r)){e.name=r;break}}if(n.has(e.name))throw new Error(`duplicated node name: ${e.name}`);const t=this._nodes.push(new l(e))-1;n.set(e.name,t)}for(let n=0;n<this._nodes.length;n++){const r=this._nodes[n],i=t.node[n];if(!i.output)throw new Error(`missing output for node: ${i.name}`);for(const t of i.output){let o=e.get(t);if(void 0===o&&(o=this._allData.push(new c)-1,e.set(t,o)),r.outputs.push(o),void 0!==this._allData[o]._from)throw new Error(`multiple nodes output to one data value: ${o}`);if(this._allData[o]._from=n,"Constant"===i.opType){if(!i.attribute||1!==i.attribute.length||!i.attribute[0].t)throw new Error("missing attributes or missing tensor value in attributes for this Constant operator");if(!i.output||1!==i.output.length)throw new Error("missing output or incorrect number of outputs for this Constant operator");r.outputs.pop(),r.executeNode=!1,this._allData[o]._from=-1,this._allData[o].tensor=a.Tensor.fromProto(i.attribute[0].t)}}}for(let n=0;n<this._nodes.length;n++){const r=this._nodes[n],i=t.node[n];if(!i.input)throw new Error(`missing input for node: ${i.name}`);for(const t of i.input){const o=e.get(t);if(void 0===o){if(""===t&&3===i.input.length&&"Resize"===i.opType)continue;throw new Error(`unrecognized input '${t}' for node: ${i.name}`)}r.inputs.push(o),this._allData[o]._to.push(n)}}return!0}buildGraphFromOrtFormat(t){var e,n,r;const i=new Map;this._allData=[],this._allInputIndices=[],this._allInputNames=[],this._allOutputIndices=[],this._allOutputNames=[],this._nodes=[];const o=new Map,p=[];for(let o=0;o<t.inputsLength();o++){const a=t.inputs(o);if(i.has(a))throw new Error(`duplicated input name: ${a}`);for(let o=0;o<t.nodeArgsLength();o++)if((null===(e=t.nodeArgs(o))||void 0===e?void 0:e.name())===a){const e=new c;if((null===(r=null===(n=t.nodeArgs(o))||void 0===n?void 0:n.type())||void 0===r?void 0:r.valueType())!==u.TypeInfoValue.tensor_type)throw new Error("Unexpected value type for the nodeArg.");const l=t.nodeArgs(o).type().value(new u.TensorTypeAndShape),f=s.ProtoUtil.tensorDataTypeFromProto(l.elemType()),d=l.shape(),h=[];for(let t=0;t<d.dimLength();t++)h.push(s.LongUtil.longToNumber(d.dim(t).value().dimValue()));e.type={shape:{dims:h},tensorType:f};const g=this._allData.push(e)-1;i.set(a,g),p.push(a)}}for(let e=0;e<t.initializersLength();e++){const n=t.initializers(e);let r=i.get(n.name());if(void 0===r){const t=new c,e=s.ProtoUtil.tensorDimsFromORTFormat(n),o=s.ProtoUtil.tensorDataTypeFromProto(n.dataType());t.type={shape:{dims:e},tensorType:o},r=this._allData.push(t)-1,i.set(n.name(),r)}this._allData[r]._from=-1,this._allData[r].tensor=a.Tensor.fromOrtTensor(n)}for(let t=0;t<this._allData.length;t++)this._allData[t].tensor||(this._allInputIndices.push(t),this._allInputNames.push(p[t]));for(let e=0;e<t.outputsLength();e++){const n=t.outputs(e);if(i.has(n))throw new Error(`duplicated output name: ${n}`);const r=this._allData.push(new c)-1;i.set(n,r),this._allOutputIndices.push(r),this._allOutputNames.push(n)}if(!t.nodes)throw new Error("missing information in graph: node");for(let e=0;e<t.nodesLength();e++){const n=t.nodes(e);let r=n.name();if(!r)for(let t=0;r=`unnamed_${n.opType()}_${t}`,o.has(r);t++);if(o.has(r))throw new Error(`duplicated node name: ${r}`);const i=this._nodes.push(new l(n,r))-1;o.set(r,i)}for(let e=0;e<this._nodes.length;e++){const n=this._nodes[e],r=t.nodes(e);if(null==r)throw new Error(`No node exists at index ${e}`);if(0===(null==r?void 0:r.outputsLength()))throw new Error(`missing output for node: ${r.name}`);for(let t=0;t<(null==r?void 0:r.outputsLength());t++){const o=null==r?void 0:r.outputs(t);let s=i.get(o);if(void 0===s&&(s=this._allData.push(new c)-1,i.set(o,s)),n.outputs.push(s),void 0!==this._allData[s]._from)throw new Error(`multiple nodes output to one data value: ${s}`);if(this._allData[s]._from=e,"Constant"===r.opType()){if(1!==r.attributesLength()||!r.attributes(0).t())throw new Error("missing attributes or missing tensor value in attributes for this Constant operator");if(1!==r.outputsLength())throw new Error("missing output or incorrect number of outputs for this Constant operator");n.outputs.pop(),n.executeNode=!1,this._allData[s]._from=-1,this._allData[s].tensor=a.Tensor.fromOrtTensor(r.attributes(0).t())}}}for(let e=0;e<this._nodes.length;e++){const n=this._nodes[e],r=t.nodes(e);if(0===r.inputsLength())throw new Error(`missing input for node: ${r.name}`);for(let t=0;t<r.inputsLength();t++){const o=r.inputs(t),a=i.get(o);if(void 0===a)throw new Error(`unrecognized input '${o}' for node: ${r.name()}`);n.inputs.push(a),this._allData[a]._to.push(e)}}}checkIsAcyclic(){const t=new Set;this._allInputIndices.forEach((e=>{this._allData[e]._to.forEach((e=>{t.add(e)}))}));const e=Array.from(t),n=new Array(this._nodes.length).fill("white");for(;e.length>0;){const t=e.pop();"gray"===n[t]?n[t]="black":(e.push(t),n[t]="gray",this._nodes[t].outputs.forEach((r=>{const i=this._allData[r];if(void 0!==i.tensor)throw new Error("node outputs should not be initialized");if(i._from!==t)throw new Error("from property of the Value object doesn't match index of Node being processed");i._to.forEach((t=>{if("gray"===n[t])throw new Error("model graph is cyclic");"white"===n[t]&&e.push(t)}))})))}}transformGraph(t){this.removeAllIdentityNodes(),this.removeAllDropoutNodes(),this.fuseConvActivationNodes(),t&&t.transformGraph(this),this.finalizeGraph()}finalizeGraph(){let t=0;for(let e=0;e<this._nodes.length;e++)this._nodes[e].executeNode?t>0&&(this._nodes[e].inputs.forEach((n=>{const r=this._allData[n]._to.indexOf(e+t);-1!==r&&(this._allData[n]._to[r]=e)})),this._nodes[e].outputs.forEach((n=>{this._allData[n]._from&&this._allData[n]._from===e+t&&(this._allData[n]._from=e)}))):(t++,this._nodes[e].outputs.forEach((t=>{this._allData[t]._from=-2})),this._nodes.splice(e,1),e--);t=0;for(let e=0;e<this._allData.length;e++)if(-2!==this._allData[e].from||-1!==this._allOutputIndices.indexOf(e+t)){if(t>0){let n=-1;void 0!==this._allData[e].from&&-1!==this._allData[e].from?(n=this._nodes[this._allData[e].from].outputs.indexOf(e+t),-1!==n&&(this._nodes[this._allData[e].from].outputs[n]=e)):(n=this._allInputIndices.indexOf(e+t),-1!==n&&(this._allInputIndices[n]=e)),this._allData[e].to.forEach((r=>{n=this._nodes[r].inputs.indexOf(e+t),-1!==n&&(this._nodes[r].inputs[n]=e)})),0===this._allData[e].to.length&&(n=this._allOutputIndices.indexOf(e+t),-1!==n&&(this._allOutputIndices[n]=e))}}else t++,this._allData.splice(e,1),e--}deleteNode(t){const e=this._nodes[t];if(e.outputs.length>1)for(let t=1;t<e.outputs.length;t++)if(this._allData[e.outputs[t]].to.length>0)throw new Error("Node deletion with more than one output connected to other nodes is not supported. ");e.executeNode=!1;const n=e.inputs[0],r=e.outputs[0],i=this._allData[r].to,o=this._allData[n].to.indexOf(t);if(-1===o)throw new Error("The Value object doesn't have the current Node in it's 'to' property ");this._allData[n].to.splice(o,1),this._allData[r]._to=[];const a=this._allOutputIndices.indexOf(r);if(-1!==a&&(this._allOutputIndices[a]=n),i&&i.length>0)for(const t of i){const e=this._nodes[t].inputs.indexOf(r);if(-1===e)throw new Error("The Node object doesn't have the output Value in it's 'inputs' property ");this._nodes[t].inputs[e]=n,this._allData[n].to.push(t)}}removeAllDropoutNodes(){let t=0;for(const e of this._nodes){if("Dropout"===e.opType){if(1!==e.inputs.length)throw new Error("Dropout nodes should only contain one input. ");if(1!==e.outputs.length&&2!==e.outputs.length)throw new Error("Dropout nodes should contain either 1 or 2 output(s)");if(2===e.outputs.length&&0!==this._allData[e.outputs[1]]._to.length)throw new Error("Dropout nodes's second output should not be referenced by other nodes");this.deleteNode(t)}t++}}removeAllIdentityNodes(){let t=0;for(const e of this._nodes)"Identity"===e.opType&&this.deleteNode(t),t++}isActivation(t){switch(t.opType){case"Relu":case"Sigmoid":case"Clip":return!0;default:return!1}}fuseConvActivationNodes(){for(const t of this._nodes)if("Conv"===t.opType){const e=this._allData[t.outputs[0]]._to;if(1===e.length&&this.isActivation(this._nodes[e[0]])){const n=this._nodes[e[0]];if("Clip"===n.opType)if(1===n.inputs.length)try{t.attributes.set("activation_params","floats",[n.attributes.getFloat("min"),n.attributes.getFloat("max")])}catch(e){t.attributes.set("activation_params","floats",[s.MIN_CLIP,s.MAX_CLIP])}else{if(!(n.inputs.length>=3&&void 0!==this._allData[n.inputs[1]].tensor&&void 0!==this._allData[n.inputs[2]].tensor))continue;t.attributes.set("activation_params","floats",[this._allData[n.inputs[1]].tensor.floatData[0],this._allData[n.inputs[2]].tensor.floatData[0]])}t.attributes.set("activation","string",n.opType),this.deleteNode(e[0])}}}}},6231:(t,e)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.now=e.Profiler=e.Logger=void 0;const n={verbose:1e3,info:2e3,warning:4e3,error:5e3,fatal:6e3},r={none:new class{log(t,e,n){}},console:new class{log(t,e,n){console.log(`${this.color(t)} ${n?""+n+" ":""}${e}`)}color(t){switch(t){case"verbose":return"v";case"info":return"i";case"warning":return"w";case"error":return"e";case"fatal":return"f";default:throw new Error(`unsupported severity: ${t}`)}}}},i={provider:"console",minimalSeverity:"warning",logDateTime:!0,logSourceLocation:!1};let o={"":i};function a(t,e,n,r){if(void 0===e)return i=t,{verbose:a.verbose.bind(null,i),info:a.info.bind(null,i),warning:a.warning.bind(null,i),error:a.error.bind(null,i),fatal:a.fatal.bind(null,i)};if(void 0===n)s(t,e);else if("number"==typeof n&&void 0===r)s(t,e);else if("string"==typeof n&&void 0===r)s(t,n,0,e);else{if("string"!=typeof n||"number"!=typeof r)throw new TypeError("input is valid");s(t,n,0,e)}var i}function s(t,e,i,a){const s=o[a||""]||o[""];n[t]<n[s.minimalSeverity]||(s.logDateTime&&(e=`${(new Date).toISOString()}|${e}`),s.logSourceLocation,r[s.provider].log(t,e,a))}!function(t){function e(t){o={},n("",t||{})}function n(t,n){if("*"===t)e(n);else{const e=o[t]||i;o[t]={provider:n.provider||e.provider,minimalSeverity:n.minimalSeverity||e.minimalSeverity,logDateTime:void 0===n.logDateTime?e.logDateTime:n.logDateTime,logSourceLocation:void 0===n.logSourceLocation?e.logSourceLocation:n.logSourceLocation}}}t.verbose=function(e,n){t("verbose",e,n)},t.info=function(e,n){t("info",e,n)},t.warning=function(e,n){t("warning",e,n)},t.error=function(e,n){t("error",e,n)},t.fatal=function(e,n){t("fatal",e,n)},t.reset=e,t.set=n,t.setWithEnv=function(t){const e={};t.logLevel&&(e.minimalSeverity=t.logLevel),n("",e)}}(a||(a={})),e.Logger=a;class u{constructor(t,e,n,r,i,o){this.category=t,this.name=e,this.startTime=n,this.endCallback=r,this.timer=i,this.ctx=o}end(){return this.endCallback(this)}async checkTimer(){if(void 0===this.ctx||void 0===this.timer)throw new Error("No webgl timer found");return this.ctx.endTimer(),this.ctx.waitForQueryAndGetTime(this.timer)}}class c{constructor(t,e,n,r){this.category=t,this.name=e,this.startTime=n,this.endTime=r}}e.Profiler=class{static create(t){return void 0===t?new this:new this(t.maxNumberEvents,t.flushBatchSize,t.flushIntervalInMilliseconds)}constructor(t,e,n){this._started=!1,this._flushPointer=0,this._started=!1,this._maxNumberEvents=void 0===t?1e4:t,this._flushBatchSize=void 0===e?10:e,this._flushIntervalInMilliseconds=void 0===n?5e3:n}start(){this._started=!0,this._timingEvents=[],this._flushTime=(0,e.now)(),this._flushPointer=0}stop(){for(this._started=!1;this._flushPointer<this._timingEvents.length;this._flushPointer++)this.logOneEvent(this._timingEvents[this._flushPointer])}event(t,e,n,r){const i=this._started?this.begin(t,e,r):void 0;let o=!1;const a=n();if(a&&"function"==typeof a.then)return o=!0,new Promise(((t,e)=>{a.then((async e=>{i&&await i.end(),t(e)}),(async t=>{i&&await i.end(),e(t)}))}));if(!o&&i){const t=i.end();if(t&&"function"==typeof t.then)return new Promise(((e,n)=>{t.then((()=>{e(a)}),(t=>{n(t)}))}))}return a}begin(t,n,r){if(!this._started)throw new Error("profiler is not started yet");if(void 0===r){const r=(0,e.now)();return this.flush(r),new u(t,n,r,(t=>this.endSync(t)))}{const e=r.beginTimer();return new u(t,n,0,(async t=>this.end(t)),e,r)}}async end(t){const e=await t.checkTimer();this._timingEvents.length<this._maxNumberEvents&&(this._timingEvents.push(new c(t.category,t.name,t.startTime,e)),this.flush(e))}endSync(t){const n=(0,e.now)();this._timingEvents.length<this._maxNumberEvents&&(this._timingEvents.push(new c(t.category,t.name,t.startTime,n)),this.flush(n))}logOneEvent(t){e.Logger.verbose(`Profiler.${t.category}`,`${(t.endTime-t.startTime).toFixed(2)}ms on event '${t.name}' at ${t.endTime.toFixed(2)}`)}flush(t){if(this._timingEvents.length-this._flushPointer>=this._flushBatchSize||t-this._flushTime>=this._flushIntervalInMilliseconds){for(const t=this._flushPointer;this._flushPointer<t+this._flushBatchSize&&this._flushPointer<this._timingEvents.length;this._flushPointer++)this.logOneEvent(this._timingEvents[this._flushPointer]);this._flushTime=(0,e.now)()}}get started(){return this._started}},e.now="undefined"!=typeof performance&&performance.now?()=>performance.now():Date.now},2644:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.Model=void 0;const r=n(5686),i=n(1446),o=n(7070),a=n(9395),s=n(2517);var u=a.onnxruntime.experimental.fbs;e.Model=class{constructor(){}load(t,e,n){if(!n)try{return void this.loadFromOnnxFormat(t,e)}catch(t){if(void 0!==n)throw t}this.loadFromOrtFormat(t,e)}loadFromOnnxFormat(t,e){const n=i.onnx.ModelProto.decode(t);if(s.LongUtil.longToNumber(n.irVersion)<3)throw new Error("only support ONNX model with IR_VERSION>=3");this._opsets=n.opsetImport.map((t=>({domain:t.domain,version:s.LongUtil.longToNumber(t.version)}))),this._graph=o.Graph.from(n.graph,e)}loadFromOrtFormat(t,e){const n=new r.flatbuffers.ByteBuffer(t),i=u.InferenceSession.getRootAsInferenceSession(n).model();if(s.LongUtil.longToNumber(i.irVersion())<3)throw new Error("only support ONNX model with IR_VERSION>=3");this._opsets=[];for(let t=0;t<i.opsetImportLength();t++){const e=i.opsetImport(t);this._opsets.push({domain:null==e?void 0:e.domain(),version:s.LongUtil.longToNumber(e.version())})}this._graph=o.Graph.from(i.graph(),e)}get graph(){return this._graph}get opsets(){return this._opsets}}},782:(t,e)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.FLOAT_TYPES=e.INT_TYPES=e.NUMBER_TYPES=void 0,e.NUMBER_TYPES=["float32","float64","int32","int16","int8","uint16","uint32","uint8"],e.INT_TYPES=["int32","int16","int8","uint16","uint32","uint8"],e.FLOAT_TYPES=["float32","float64"]},1047:(t,e)=>{"use strict";function n(t,e){if(e.endsWith("+")){const n=Number.parseInt(e.substring(0,e.length-1),10);return!isNaN(n)&&n<=t}if(2===e.split("-").length){const n=e.split("-"),r=Number.parseInt(n[0],10),i=Number.parseInt(n[1],10);return!isNaN(r)&&!isNaN(i)&&r<=t&&t<=i}return Number.parseInt(e,10)===t}Object.defineProperty(e,"__esModule",{value:!0}),e.resolveOperator=void 0,e.resolveOperator=function(t,e,r){for(const i of r){const r=i[0],o=i[1],a=i[2],s=i[3],u=i[4];if(t.opType===r)for(const t of e)if((t.domain===o||"ai.onnx"===t.domain&&""===o)&&n(t.version,a))return{opImpl:s,opInit:u}}throw new TypeError(`cannot resolve operator '${t.opType}' with opsets: ${e.map((t=>`${t.domain||"ai.onnx"} v${t.version}`)).join(", ")}`)}},9395:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.onnxruntime=void 0;const r=n(5686);var i,o;i=e.onnxruntime||(e.onnxruntime={}),function(t){let e;!function(t){t[t.UNDEFINED=0]="UNDEFINED",t[t.FLOAT=1]="FLOAT",t[t.INT=2]="INT",t[t.STRING=3]="STRING",t[t.TENSOR=4]="TENSOR",t[t.GRAPH=5]="GRAPH",t[t.FLOATS=6]="FLOATS",t[t.INTS=7]="INTS",t[t.STRINGS=8]="STRINGS",t[t.TENSORS=9]="TENSORS",t[t.GRAPHS=10]="GRAPHS",t[t.SPARSE_TENSOR=11]="SPARSE_TENSOR",t[t.SPARSE_TENSORS=12]="SPARSE_TENSORS"}(e=t.AttributeType||(t.AttributeType={}))}((o=i.experimental||(i.experimental={})).fbs||(o.fbs={})),function(t){!function(t){!function(t){let e;!function(t){t[t.UNKNOWN=0]="UNKNOWN",t[t.VALUE=1]="VALUE",t[t.PARAM=2]="PARAM"}(e=t.DimensionValueType||(t.DimensionValueType={}))}(t.fbs||(t.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(t){!function(t){let e;!function(t){t[t.UNDEFINED=0]="UNDEFINED",t[t.FLOAT=1]="FLOAT",t[t.UINT8=2]="UINT8",t[t.INT8=3]="INT8",t[t.UINT16=4]="UINT16",t[t.INT16=5]="INT16",t[t.INT32=6]="INT32",t[t.INT64=7]="INT64",t[t.STRING=8]="STRING",t[t.BOOL=9]="BOOL",t[t.FLOAT16=10]="FLOAT16",t[t.DOUBLE=11]="DOUBLE",t[t.UINT32=12]="UINT32",t[t.UINT64=13]="UINT64",t[t.COMPLEX64=14]="COMPLEX64",t[t.COMPLEX128=15]="COMPLEX128",t[t.BFLOAT16=16]="BFLOAT16"}(e=t.TensorDataType||(t.TensorDataType={}))}(t.fbs||(t.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(t){!function(t){let e;!function(t){t[t.Primitive=0]="Primitive",t[t.Fused=1]="Fused"}(e=t.NodeType||(t.NodeType={}))}(t.fbs||(t.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(t){!function(t){let e;!function(t){t[t.NONE=0]="NONE",t[t.tensor_type=1]="tensor_type",t[t.sequence_type=2]="sequence_type",t[t.map_type=3]="map_type"}(e=t.TypeInfoValue||(t.TypeInfoValue={}))}(t.fbs||(t.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(e){!function(e){class n{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsShape(t,e){return(e||new n).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsShape(t,e){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(e||new n).__init(t.readInt32(t.position())+t.position(),t)}dim(e,n){let r=this.bb.__offset(this.bb_pos,4);return r?(n||new t.experimental.fbs.Dimension).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos+r)+4*e),this.bb):null}dimLength(){let t=this.bb.__offset(this.bb_pos,4);return t?this.bb.__vector_len(this.bb_pos+t):0}static startShape(t){t.startObject(1)}static addDim(t,e){t.addFieldOffset(0,e,0)}static createDimVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addOffset(e[n]);return t.endVector()}static startDimVector(t,e){t.startVector(4,e,4)}static endShape(t){return t.endObject()}static createShape(t,e){return n.startShape(t),n.addDim(t,e),n.endShape(t)}}e.Shape=n}(e.fbs||(e.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(e){!function(e){class n{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsDimension(t,e){return(e||new n).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsDimension(t,e){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(e||new n).__init(t.readInt32(t.position())+t.position(),t)}value(e){let n=this.bb.__offset(this.bb_pos,4);return n?(e||new t.experimental.fbs.DimensionValue).__init(this.bb.__indirect(this.bb_pos+n),this.bb):null}denotation(t){let e=this.bb.__offset(this.bb_pos,6);return e?this.bb.__string(this.bb_pos+e,t):null}static startDimension(t){t.startObject(2)}static addValue(t,e){t.addFieldOffset(0,e,0)}static addDenotation(t,e){t.addFieldOffset(1,e,0)}static endDimension(t){return t.endObject()}static createDimension(t,e,r){return n.startDimension(t),n.addValue(t,e),n.addDenotation(t,r),n.endDimension(t)}}e.Dimension=n}(e.fbs||(e.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(e){!function(e){class n{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsDimensionValue(t,e){return(e||new n).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsDimensionValue(t,e){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(e||new n).__init(t.readInt32(t.position())+t.position(),t)}dimType(){let e=this.bb.__offset(this.bb_pos,4);return e?this.bb.readInt8(this.bb_pos+e):t.experimental.fbs.DimensionValueType.UNKNOWN}dimValue(){let t=this.bb.__offset(this.bb_pos,6);return t?this.bb.readInt64(this.bb_pos+t):this.bb.createLong(0,0)}dimParam(t){let e=this.bb.__offset(this.bb_pos,8);return e?this.bb.__string(this.bb_pos+e,t):null}static startDimensionValue(t){t.startObject(3)}static addDimType(e,n){e.addFieldInt8(0,n,t.experimental.fbs.DimensionValueType.UNKNOWN)}static addDimValue(t,e){t.addFieldInt64(1,e,t.createLong(0,0))}static addDimParam(t,e){t.addFieldOffset(2,e,0)}static endDimensionValue(t){return t.endObject()}static createDimensionValue(t,e,r,i){return n.startDimensionValue(t),n.addDimType(t,e),n.addDimValue(t,r),n.addDimParam(t,i),n.endDimensionValue(t)}}e.DimensionValue=n}(e.fbs||(e.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(e){!function(e){class n{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsTensorTypeAndShape(t,e){return(e||new n).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsTensorTypeAndShape(t,e){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(e||new n).__init(t.readInt32(t.position())+t.position(),t)}elemType(){let e=this.bb.__offset(this.bb_pos,4);return e?this.bb.readInt32(this.bb_pos+e):t.experimental.fbs.TensorDataType.UNDEFINED}shape(e){let n=this.bb.__offset(this.bb_pos,6);return n?(e||new t.experimental.fbs.Shape).__init(this.bb.__indirect(this.bb_pos+n),this.bb):null}static startTensorTypeAndShape(t){t.startObject(2)}static addElemType(e,n){e.addFieldInt32(0,n,t.experimental.fbs.TensorDataType.UNDEFINED)}static addShape(t,e){t.addFieldOffset(1,e,0)}static endTensorTypeAndShape(t){return t.endObject()}static createTensorTypeAndShape(t,e,r){return n.startTensorTypeAndShape(t),n.addElemType(t,e),n.addShape(t,r),n.endTensorTypeAndShape(t)}}e.TensorTypeAndShape=n}(e.fbs||(e.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(e){!function(e){class n{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsMapType(t,e){return(e||new n).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsMapType(t,e){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(e||new n).__init(t.readInt32(t.position())+t.position(),t)}keyType(){let e=this.bb.__offset(this.bb_pos,4);return e?this.bb.readInt32(this.bb_pos+e):t.experimental.fbs.TensorDataType.UNDEFINED}valueType(e){let n=this.bb.__offset(this.bb_pos,6);return n?(e||new t.experimental.fbs.TypeInfo).__init(this.bb.__indirect(this.bb_pos+n),this.bb):null}static startMapType(t){t.startObject(2)}static addKeyType(e,n){e.addFieldInt32(0,n,t.experimental.fbs.TensorDataType.UNDEFINED)}static addValueType(t,e){t.addFieldOffset(1,e,0)}static endMapType(t){return t.endObject()}static createMapType(t,e,r){return n.startMapType(t),n.addKeyType(t,e),n.addValueType(t,r),n.endMapType(t)}}e.MapType=n}(e.fbs||(e.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(e){!function(e){class n{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsSequenceType(t,e){return(e||new n).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsSequenceType(t,e){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(e||new n).__init(t.readInt32(t.position())+t.position(),t)}elemType(e){let n=this.bb.__offset(this.bb_pos,4);return n?(e||new t.experimental.fbs.TypeInfo).__init(this.bb.__indirect(this.bb_pos+n),this.bb):null}static startSequenceType(t){t.startObject(1)}static addElemType(t,e){t.addFieldOffset(0,e,0)}static endSequenceType(t){return t.endObject()}static createSequenceType(t,e){return n.startSequenceType(t),n.addElemType(t,e),n.endSequenceType(t)}}e.SequenceType=n}(e.fbs||(e.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(t){(t.fbs||(t.fbs={})).EdgeEnd=class{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}nodeIndex(){return this.bb.readUint32(this.bb_pos)}srcArgIndex(){return this.bb.readInt32(this.bb_pos+4)}dstArgIndex(){return this.bb.readInt32(this.bb_pos+8)}static createEdgeEnd(t,e,n,r){return t.prep(4,12),t.writeInt32(r),t.writeInt32(n),t.writeInt32(e),t.offset()}}}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(e){!function(e){class n{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsNodeEdge(t,e){return(e||new n).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsNodeEdge(t,e){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(e||new n).__init(t.readInt32(t.position())+t.position(),t)}nodeIndex(){let t=this.bb.__offset(this.bb_pos,4);return t?this.bb.readUint32(this.bb_pos+t):0}inputEdges(e,n){let r=this.bb.__offset(this.bb_pos,6);return r?(n||new t.experimental.fbs.EdgeEnd).__init(this.bb.__vector(this.bb_pos+r)+12*e,this.bb):null}inputEdgesLength(){let t=this.bb.__offset(this.bb_pos,6);return t?this.bb.__vector_len(this.bb_pos+t):0}outputEdges(e,n){let r=this.bb.__offset(this.bb_pos,8);return r?(n||new t.experimental.fbs.EdgeEnd).__init(this.bb.__vector(this.bb_pos+r)+12*e,this.bb):null}outputEdgesLength(){let t=this.bb.__offset(this.bb_pos,8);return t?this.bb.__vector_len(this.bb_pos+t):0}static startNodeEdge(t){t.startObject(3)}static addNodeIndex(t,e){t.addFieldInt32(0,e,0)}static addInputEdges(t,e){t.addFieldOffset(1,e,0)}static startInputEdgesVector(t,e){t.startVector(12,e,4)}static addOutputEdges(t,e){t.addFieldOffset(2,e,0)}static startOutputEdgesVector(t,e){t.startVector(12,e,4)}static endNodeEdge(t){return t.endObject()}static createNodeEdge(t,e,r,i){return n.startNodeEdge(t),n.addNodeIndex(t,e),n.addInputEdges(t,r),n.addOutputEdges(t,i),n.endNodeEdge(t)}}e.NodeEdge=n}(e.fbs||(e.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(e){!function(e){class n{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsNode(t,e){return(e||new n).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsNode(t,e){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(e||new n).__init(t.readInt32(t.position())+t.position(),t)}name(t){let e=this.bb.__offset(this.bb_pos,4);return e?this.bb.__string(this.bb_pos+e,t):null}docString(t){let e=this.bb.__offset(this.bb_pos,6);return e?this.bb.__string(this.bb_pos+e,t):null}domain(t){let e=this.bb.__offset(this.bb_pos,8);return e?this.bb.__string(this.bb_pos+e,t):null}sinceVersion(){let t=this.bb.__offset(this.bb_pos,10);return t?this.bb.readInt32(this.bb_pos+t):0}index(){let t=this.bb.__offset(this.bb_pos,12);return t?this.bb.readUint32(this.bb_pos+t):0}opType(t){let e=this.bb.__offset(this.bb_pos,14);return e?this.bb.__string(this.bb_pos+e,t):null}type(){let e=this.bb.__offset(this.bb_pos,16);return e?this.bb.readInt32(this.bb_pos+e):t.experimental.fbs.NodeType.Primitive}executionProviderType(t){let e=this.bb.__offset(this.bb_pos,18);return e?this.bb.__string(this.bb_pos+e,t):null}inputs(t,e){let n=this.bb.__offset(this.bb_pos,20);return n?this.bb.__string(this.bb.__vector(this.bb_pos+n)+4*t,e):null}inputsLength(){let t=this.bb.__offset(this.bb_pos,20);return t?this.bb.__vector_len(this.bb_pos+t):0}outputs(t,e){let n=this.bb.__offset(this.bb_pos,22);return n?this.bb.__string(this.bb.__vector(this.bb_pos+n)+4*t,e):null}outputsLength(){let t=this.bb.__offset(this.bb_pos,22);return t?this.bb.__vector_len(this.bb_pos+t):0}attributes(e,n){let r=this.bb.__offset(this.bb_pos,24);return r?(n||new t.experimental.fbs.Attribute).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos+r)+4*e),this.bb):null}attributesLength(){let t=this.bb.__offset(this.bb_pos,24);return t?this.bb.__vector_len(this.bb_pos+t):0}inputArgCounts(t){let e=this.bb.__offset(this.bb_pos,26);return e?this.bb.readInt32(this.bb.__vector(this.bb_pos+e)+4*t):0}inputArgCountsLength(){let t=this.bb.__offset(this.bb_pos,26);return t?this.bb.__vector_len(this.bb_pos+t):0}inputArgCountsArray(){let t=this.bb.__offset(this.bb_pos,26);return t?new Int32Array(this.bb.bytes().buffer,this.bb.bytes().byteOffset+this.bb.__vector(this.bb_pos+t),this.bb.__vector_len(this.bb_pos+t)):null}implicitInputs(t,e){let n=this.bb.__offset(this.bb_pos,28);return n?this.bb.__string(this.bb.__vector(this.bb_pos+n)+4*t,e):null}implicitInputsLength(){let t=this.bb.__offset(this.bb_pos,28);return t?this.bb.__vector_len(this.bb_pos+t):0}static startNode(t){t.startObject(13)}static addName(t,e){t.addFieldOffset(0,e,0)}static addDocString(t,e){t.addFieldOffset(1,e,0)}static addDomain(t,e){t.addFieldOffset(2,e,0)}static addSinceVersion(t,e){t.addFieldInt32(3,e,0)}static addIndex(t,e){t.addFieldInt32(4,e,0)}static addOpType(t,e){t.addFieldOffset(5,e,0)}static addType(e,n){e.addFieldInt32(6,n,t.experimental.fbs.NodeType.Primitive)}static addExecutionProviderType(t,e){t.addFieldOffset(7,e,0)}static addInputs(t,e){t.addFieldOffset(8,e,0)}static createInputsVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addOffset(e[n]);return t.endVector()}static startInputsVector(t,e){t.startVector(4,e,4)}static addOutputs(t,e){t.addFieldOffset(9,e,0)}static createOutputsVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addOffset(e[n]);return t.endVector()}static startOutputsVector(t,e){t.startVector(4,e,4)}static addAttributes(t,e){t.addFieldOffset(10,e,0)}static createAttributesVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addOffset(e[n]);return t.endVector()}static startAttributesVector(t,e){t.startVector(4,e,4)}static addInputArgCounts(t,e){t.addFieldOffset(11,e,0)}static createInputArgCountsVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addInt32(e[n]);return t.endVector()}static startInputArgCountsVector(t,e){t.startVector(4,e,4)}static addImplicitInputs(t,e){t.addFieldOffset(12,e,0)}static createImplicitInputsVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addOffset(e[n]);return t.endVector()}static startImplicitInputsVector(t,e){t.startVector(4,e,4)}static endNode(t){return t.endObject()}static createNode(t,e,r,i,o,a,s,u,c,l,p,f,d,h){return n.startNode(t),n.addName(t,e),n.addDocString(t,r),n.addDomain(t,i),n.addSinceVersion(t,o),n.addIndex(t,a),n.addOpType(t,s),n.addType(t,u),n.addExecutionProviderType(t,c),n.addInputs(t,l),n.addOutputs(t,p),n.addAttributes(t,f),n.addInputArgCounts(t,d),n.addImplicitInputs(t,h),n.endNode(t)}}e.Node=n}(e.fbs||(e.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(e){!function(e){class n{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsValueInfo(t,e){return(e||new n).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsValueInfo(t,e){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(e||new n).__init(t.readInt32(t.position())+t.position(),t)}name(t){let e=this.bb.__offset(this.bb_pos,4);return e?this.bb.__string(this.bb_pos+e,t):null}docString(t){let e=this.bb.__offset(this.bb_pos,6);return e?this.bb.__string(this.bb_pos+e,t):null}type(e){let n=this.bb.__offset(this.bb_pos,8);return n?(e||new t.experimental.fbs.TypeInfo).__init(this.bb.__indirect(this.bb_pos+n),this.bb):null}static startValueInfo(t){t.startObject(3)}static addName(t,e){t.addFieldOffset(0,e,0)}static addDocString(t,e){t.addFieldOffset(1,e,0)}static addType(t,e){t.addFieldOffset(2,e,0)}static endValueInfo(t){return t.endObject()}static createValueInfo(t,e,r,i){return n.startValueInfo(t),n.addName(t,e),n.addDocString(t,r),n.addType(t,i),n.endValueInfo(t)}}e.ValueInfo=n}(e.fbs||(e.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(e){!function(e){class n{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsTypeInfo(t,e){return(e||new n).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsTypeInfo(t,e){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(e||new n).__init(t.readInt32(t.position())+t.position(),t)}denotation(t){let e=this.bb.__offset(this.bb_pos,4);return e?this.bb.__string(this.bb_pos+e,t):null}valueType(){let e=this.bb.__offset(this.bb_pos,6);return e?this.bb.readUint8(this.bb_pos+e):t.experimental.fbs.TypeInfoValue.NONE}value(t){let e=this.bb.__offset(this.bb_pos,8);return e?this.bb.__union(t,this.bb_pos+e):null}static startTypeInfo(t){t.startObject(3)}static addDenotation(t,e){t.addFieldOffset(0,e,0)}static addValueType(e,n){e.addFieldInt8(1,n,t.experimental.fbs.TypeInfoValue.NONE)}static addValue(t,e){t.addFieldOffset(2,e,0)}static endTypeInfo(t){return t.endObject()}static createTypeInfo(t,e,r,i){return n.startTypeInfo(t),n.addDenotation(t,e),n.addValueType(t,r),n.addValue(t,i),n.endTypeInfo(t)}}e.TypeInfo=n}(e.fbs||(e.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(t){!function(t){class e{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsOperatorSetId(t,n){return(n||new e).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsOperatorSetId(t,n){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(n||new e).__init(t.readInt32(t.position())+t.position(),t)}domain(t){let e=this.bb.__offset(this.bb_pos,4);return e?this.bb.__string(this.bb_pos+e,t):null}version(){let t=this.bb.__offset(this.bb_pos,6);return t?this.bb.readInt64(this.bb_pos+t):this.bb.createLong(0,0)}static startOperatorSetId(t){t.startObject(2)}static addDomain(t,e){t.addFieldOffset(0,e,0)}static addVersion(t,e){t.addFieldInt64(1,e,t.createLong(0,0))}static endOperatorSetId(t){return t.endObject()}static createOperatorSetId(t,n,r){return e.startOperatorSetId(t),e.addDomain(t,n),e.addVersion(t,r),e.endOperatorSetId(t)}}t.OperatorSetId=e}(t.fbs||(t.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(e){!function(e){class n{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsTensor(t,e){return(e||new n).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsTensor(t,e){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(e||new n).__init(t.readInt32(t.position())+t.position(),t)}name(t){let e=this.bb.__offset(this.bb_pos,4);return e?this.bb.__string(this.bb_pos+e,t):null}docString(t){let e=this.bb.__offset(this.bb_pos,6);return e?this.bb.__string(this.bb_pos+e,t):null}dims(t){let e=this.bb.__offset(this.bb_pos,8);return e?this.bb.readInt64(this.bb.__vector(this.bb_pos+e)+8*t):this.bb.createLong(0,0)}dimsLength(){let t=this.bb.__offset(this.bb_pos,8);return t?this.bb.__vector_len(this.bb_pos+t):0}dataType(){let e=this.bb.__offset(this.bb_pos,10);return e?this.bb.readInt32(this.bb_pos+e):t.experimental.fbs.TensorDataType.UNDEFINED}rawData(t){let e=this.bb.__offset(this.bb_pos,12);return e?this.bb.readUint8(this.bb.__vector(this.bb_pos+e)+t):0}rawDataLength(){let t=this.bb.__offset(this.bb_pos,12);return t?this.bb.__vector_len(this.bb_pos+t):0}rawDataArray(){let t=this.bb.__offset(this.bb_pos,12);return t?new Uint8Array(this.bb.bytes().buffer,this.bb.bytes().byteOffset+this.bb.__vector(this.bb_pos+t),this.bb.__vector_len(this.bb_pos+t)):null}stringData(t,e){let n=this.bb.__offset(this.bb_pos,14);return n?this.bb.__string(this.bb.__vector(this.bb_pos+n)+4*t,e):null}stringDataLength(){let t=this.bb.__offset(this.bb_pos,14);return t?this.bb.__vector_len(this.bb_pos+t):0}static startTensor(t){t.startObject(6)}static addName(t,e){t.addFieldOffset(0,e,0)}static addDocString(t,e){t.addFieldOffset(1,e,0)}static addDims(t,e){t.addFieldOffset(2,e,0)}static createDimsVector(t,e){t.startVector(8,e.length,8);for(let n=e.length-1;n>=0;n--)t.addInt64(e[n]);return t.endVector()}static startDimsVector(t,e){t.startVector(8,e,8)}static addDataType(e,n){e.addFieldInt32(3,n,t.experimental.fbs.TensorDataType.UNDEFINED)}static addRawData(t,e){t.addFieldOffset(4,e,0)}static createRawDataVector(t,e){t.startVector(1,e.length,1);for(let n=e.length-1;n>=0;n--)t.addInt8(e[n]);return t.endVector()}static startRawDataVector(t,e){t.startVector(1,e,1)}static addStringData(t,e){t.addFieldOffset(5,e,0)}static createStringDataVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addOffset(e[n]);return t.endVector()}static startStringDataVector(t,e){t.startVector(4,e,4)}static endTensor(t){return t.endObject()}static createTensor(t,e,r,i,o,a,s){return n.startTensor(t),n.addName(t,e),n.addDocString(t,r),n.addDims(t,i),n.addDataType(t,o),n.addRawData(t,a),n.addStringData(t,s),n.endTensor(t)}}e.Tensor=n}(e.fbs||(e.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(e){!function(e){class n{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsSparseTensor(t,e){return(e||new n).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsSparseTensor(t,e){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(e||new n).__init(t.readInt32(t.position())+t.position(),t)}values(e){let n=this.bb.__offset(this.bb_pos,4);return n?(e||new t.experimental.fbs.Tensor).__init(this.bb.__indirect(this.bb_pos+n),this.bb):null}indices(e){let n=this.bb.__offset(this.bb_pos,6);return n?(e||new t.experimental.fbs.Tensor).__init(this.bb.__indirect(this.bb_pos+n),this.bb):null}dims(t){let e=this.bb.__offset(this.bb_pos,8);return e?this.bb.readInt64(this.bb.__vector(this.bb_pos+e)+8*t):this.bb.createLong(0,0)}dimsLength(){let t=this.bb.__offset(this.bb_pos,8);return t?this.bb.__vector_len(this.bb_pos+t):0}static startSparseTensor(t){t.startObject(3)}static addValues(t,e){t.addFieldOffset(0,e,0)}static addIndices(t,e){t.addFieldOffset(1,e,0)}static addDims(t,e){t.addFieldOffset(2,e,0)}static createDimsVector(t,e){t.startVector(8,e.length,8);for(let n=e.length-1;n>=0;n--)t.addInt64(e[n]);return t.endVector()}static startDimsVector(t,e){t.startVector(8,e,8)}static endSparseTensor(t){return t.endObject()}static createSparseTensor(t,e,r,i){return n.startSparseTensor(t),n.addValues(t,e),n.addIndices(t,r),n.addDims(t,i),n.endSparseTensor(t)}}e.SparseTensor=n}(e.fbs||(e.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(e){!function(e){class n{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsAttribute(t,e){return(e||new n).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsAttribute(t,e){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(e||new n).__init(t.readInt32(t.position())+t.position(),t)}name(t){let e=this.bb.__offset(this.bb_pos,4);return e?this.bb.__string(this.bb_pos+e,t):null}docString(t){let e=this.bb.__offset(this.bb_pos,6);return e?this.bb.__string(this.bb_pos+e,t):null}type(){let e=this.bb.__offset(this.bb_pos,8);return e?this.bb.readInt32(this.bb_pos+e):t.experimental.fbs.AttributeType.UNDEFINED}f(){let t=this.bb.__offset(this.bb_pos,10);return t?this.bb.readFloat32(this.bb_pos+t):0}i(){let t=this.bb.__offset(this.bb_pos,12);return t?this.bb.readInt64(this.bb_pos+t):this.bb.createLong(0,0)}s(t){let e=this.bb.__offset(this.bb_pos,14);return e?this.bb.__string(this.bb_pos+e,t):null}t(e){let n=this.bb.__offset(this.bb_pos,16);return n?(e||new t.experimental.fbs.Tensor).__init(this.bb.__indirect(this.bb_pos+n),this.bb):null}g(e){let n=this.bb.__offset(this.bb_pos,18);return n?(e||new t.experimental.fbs.Graph).__init(this.bb.__indirect(this.bb_pos+n),this.bb):null}floats(t){let e=this.bb.__offset(this.bb_pos,20);return e?this.bb.readFloat32(this.bb.__vector(this.bb_pos+e)+4*t):0}floatsLength(){let t=this.bb.__offset(this.bb_pos,20);return t?this.bb.__vector_len(this.bb_pos+t):0}floatsArray(){let t=this.bb.__offset(this.bb_pos,20);return t?new Float32Array(this.bb.bytes().buffer,this.bb.bytes().byteOffset+this.bb.__vector(this.bb_pos+t),this.bb.__vector_len(this.bb_pos+t)):null}ints(t){let e=this.bb.__offset(this.bb_pos,22);return e?this.bb.readInt64(this.bb.__vector(this.bb_pos+e)+8*t):this.bb.createLong(0,0)}intsLength(){let t=this.bb.__offset(this.bb_pos,22);return t?this.bb.__vector_len(this.bb_pos+t):0}strings(t,e){let n=this.bb.__offset(this.bb_pos,24);return n?this.bb.__string(this.bb.__vector(this.bb_pos+n)+4*t,e):null}stringsLength(){let t=this.bb.__offset(this.bb_pos,24);return t?this.bb.__vector_len(this.bb_pos+t):0}tensors(e,n){let r=this.bb.__offset(this.bb_pos,26);return r?(n||new t.experimental.fbs.Tensor).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos+r)+4*e),this.bb):null}tensorsLength(){let t=this.bb.__offset(this.bb_pos,26);return t?this.bb.__vector_len(this.bb_pos+t):0}graphs(e,n){let r=this.bb.__offset(this.bb_pos,28);return r?(n||new t.experimental.fbs.Graph).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos+r)+4*e),this.bb):null}graphsLength(){let t=this.bb.__offset(this.bb_pos,28);return t?this.bb.__vector_len(this.bb_pos+t):0}static startAttribute(t){t.startObject(13)}static addName(t,e){t.addFieldOffset(0,e,0)}static addDocString(t,e){t.addFieldOffset(1,e,0)}static addType(e,n){e.addFieldInt32(2,n,t.experimental.fbs.AttributeType.UNDEFINED)}static addF(t,e){t.addFieldFloat32(3,e,0)}static addI(t,e){t.addFieldInt64(4,e,t.createLong(0,0))}static addS(t,e){t.addFieldOffset(5,e,0)}static addT(t,e){t.addFieldOffset(6,e,0)}static addG(t,e){t.addFieldOffset(7,e,0)}static addFloats(t,e){t.addFieldOffset(8,e,0)}static createFloatsVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addFloat32(e[n]);return t.endVector()}static startFloatsVector(t,e){t.startVector(4,e,4)}static addInts(t,e){t.addFieldOffset(9,e,0)}static createIntsVector(t,e){t.startVector(8,e.length,8);for(let n=e.length-1;n>=0;n--)t.addInt64(e[n]);return t.endVector()}static startIntsVector(t,e){t.startVector(8,e,8)}static addStrings(t,e){t.addFieldOffset(10,e,0)}static createStringsVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addOffset(e[n]);return t.endVector()}static startStringsVector(t,e){t.startVector(4,e,4)}static addTensors(t,e){t.addFieldOffset(11,e,0)}static createTensorsVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addOffset(e[n]);return t.endVector()}static startTensorsVector(t,e){t.startVector(4,e,4)}static addGraphs(t,e){t.addFieldOffset(12,e,0)}static createGraphsVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addOffset(e[n]);return t.endVector()}static startGraphsVector(t,e){t.startVector(4,e,4)}static endAttribute(t){return t.endObject()}static createAttribute(t,e,r,i,o,a,s,u,c,l,p,f,d,h){return n.startAttribute(t),n.addName(t,e),n.addDocString(t,r),n.addType(t,i),n.addF(t,o),n.addI(t,a),n.addS(t,s),n.addT(t,u),n.addG(t,c),n.addFloats(t,l),n.addInts(t,p),n.addStrings(t,f),n.addTensors(t,d),n.addGraphs(t,h),n.endAttribute(t)}}e.Attribute=n}(e.fbs||(e.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(e){!function(e){class n{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsGraph(t,e){return(e||new n).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsGraph(t,e){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(e||new n).__init(t.readInt32(t.position())+t.position(),t)}initializers(e,n){let r=this.bb.__offset(this.bb_pos,4);return r?(n||new t.experimental.fbs.Tensor).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos+r)+4*e),this.bb):null}initializersLength(){let t=this.bb.__offset(this.bb_pos,4);return t?this.bb.__vector_len(this.bb_pos+t):0}nodeArgs(e,n){let r=this.bb.__offset(this.bb_pos,6);return r?(n||new t.experimental.fbs.ValueInfo).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos+r)+4*e),this.bb):null}nodeArgsLength(){let t=this.bb.__offset(this.bb_pos,6);return t?this.bb.__vector_len(this.bb_pos+t):0}nodes(e,n){let r=this.bb.__offset(this.bb_pos,8);return r?(n||new t.experimental.fbs.Node).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos+r)+4*e),this.bb):null}nodesLength(){let t=this.bb.__offset(this.bb_pos,8);return t?this.bb.__vector_len(this.bb_pos+t):0}maxNodeIndex(){let t=this.bb.__offset(this.bb_pos,10);return t?this.bb.readUint32(this.bb_pos+t):0}nodeEdges(e,n){let r=this.bb.__offset(this.bb_pos,12);return r?(n||new t.experimental.fbs.NodeEdge).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos+r)+4*e),this.bb):null}nodeEdgesLength(){let t=this.bb.__offset(this.bb_pos,12);return t?this.bb.__vector_len(this.bb_pos+t):0}inputs(t,e){let n=this.bb.__offset(this.bb_pos,14);return n?this.bb.__string(this.bb.__vector(this.bb_pos+n)+4*t,e):null}inputsLength(){let t=this.bb.__offset(this.bb_pos,14);return t?this.bb.__vector_len(this.bb_pos+t):0}outputs(t,e){let n=this.bb.__offset(this.bb_pos,16);return n?this.bb.__string(this.bb.__vector(this.bb_pos+n)+4*t,e):null}outputsLength(){let t=this.bb.__offset(this.bb_pos,16);return t?this.bb.__vector_len(this.bb_pos+t):0}sparseInitializers(e,n){let r=this.bb.__offset(this.bb_pos,18);return r?(n||new t.experimental.fbs.SparseTensor).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos+r)+4*e),this.bb):null}sparseInitializersLength(){let t=this.bb.__offset(this.bb_pos,18);return t?this.bb.__vector_len(this.bb_pos+t):0}static startGraph(t){t.startObject(8)}static addInitializers(t,e){t.addFieldOffset(0,e,0)}static createInitializersVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addOffset(e[n]);return t.endVector()}static startInitializersVector(t,e){t.startVector(4,e,4)}static addNodeArgs(t,e){t.addFieldOffset(1,e,0)}static createNodeArgsVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addOffset(e[n]);return t.endVector()}static startNodeArgsVector(t,e){t.startVector(4,e,4)}static addNodes(t,e){t.addFieldOffset(2,e,0)}static createNodesVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addOffset(e[n]);return t.endVector()}static startNodesVector(t,e){t.startVector(4,e,4)}static addMaxNodeIndex(t,e){t.addFieldInt32(3,e,0)}static addNodeEdges(t,e){t.addFieldOffset(4,e,0)}static createNodeEdgesVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addOffset(e[n]);return t.endVector()}static startNodeEdgesVector(t,e){t.startVector(4,e,4)}static addInputs(t,e){t.addFieldOffset(5,e,0)}static createInputsVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addOffset(e[n]);return t.endVector()}static startInputsVector(t,e){t.startVector(4,e,4)}static addOutputs(t,e){t.addFieldOffset(6,e,0)}static createOutputsVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addOffset(e[n]);return t.endVector()}static startOutputsVector(t,e){t.startVector(4,e,4)}static addSparseInitializers(t,e){t.addFieldOffset(7,e,0)}static createSparseInitializersVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addOffset(e[n]);return t.endVector()}static startSparseInitializersVector(t,e){t.startVector(4,e,4)}static endGraph(t){return t.endObject()}static createGraph(t,e,r,i,o,a,s,u,c){return n.startGraph(t),n.addInitializers(t,e),n.addNodeArgs(t,r),n.addNodes(t,i),n.addMaxNodeIndex(t,o),n.addNodeEdges(t,a),n.addInputs(t,s),n.addOutputs(t,u),n.addSparseInitializers(t,c),n.endGraph(t)}}e.Graph=n}(e.fbs||(e.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(e){!function(e){class n{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsModel(t,e){return(e||new n).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsModel(t,e){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(e||new n).__init(t.readInt32(t.position())+t.position(),t)}irVersion(){let t=this.bb.__offset(this.bb_pos,4);return t?this.bb.readInt64(this.bb_pos+t):this.bb.createLong(0,0)}opsetImport(e,n){let r=this.bb.__offset(this.bb_pos,6);return r?(n||new t.experimental.fbs.OperatorSetId).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos+r)+4*e),this.bb):null}opsetImportLength(){let t=this.bb.__offset(this.bb_pos,6);return t?this.bb.__vector_len(this.bb_pos+t):0}producerName(t){let e=this.bb.__offset(this.bb_pos,8);return e?this.bb.__string(this.bb_pos+e,t):null}producerVersion(t){let e=this.bb.__offset(this.bb_pos,10);return e?this.bb.__string(this.bb_pos+e,t):null}domain(t){let e=this.bb.__offset(this.bb_pos,12);return e?this.bb.__string(this.bb_pos+e,t):null}modelVersion(){let t=this.bb.__offset(this.bb_pos,14);return t?this.bb.readInt64(this.bb_pos+t):this.bb.createLong(0,0)}docString(t){let e=this.bb.__offset(this.bb_pos,16);return e?this.bb.__string(this.bb_pos+e,t):null}graph(e){let n=this.bb.__offset(this.bb_pos,18);return n?(e||new t.experimental.fbs.Graph).__init(this.bb.__indirect(this.bb_pos+n),this.bb):null}graphDocString(t){let e=this.bb.__offset(this.bb_pos,20);return e?this.bb.__string(this.bb_pos+e,t):null}static startModel(t){t.startObject(9)}static addIrVersion(t,e){t.addFieldInt64(0,e,t.createLong(0,0))}static addOpsetImport(t,e){t.addFieldOffset(1,e,0)}static createOpsetImportVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addOffset(e[n]);return t.endVector()}static startOpsetImportVector(t,e){t.startVector(4,e,4)}static addProducerName(t,e){t.addFieldOffset(2,e,0)}static addProducerVersion(t,e){t.addFieldOffset(3,e,0)}static addDomain(t,e){t.addFieldOffset(4,e,0)}static addModelVersion(t,e){t.addFieldInt64(5,e,t.createLong(0,0))}static addDocString(t,e){t.addFieldOffset(6,e,0)}static addGraph(t,e){t.addFieldOffset(7,e,0)}static addGraphDocString(t,e){t.addFieldOffset(8,e,0)}static endModel(t){return t.endObject()}static createModel(t,e,r,i,o,a,s,u,c,l){return n.startModel(t),n.addIrVersion(t,e),n.addOpsetImport(t,r),n.addProducerName(t,i),n.addProducerVersion(t,o),n.addDomain(t,a),n.addModelVersion(t,s),n.addDocString(t,u),n.addGraph(t,c),n.addGraphDocString(t,l),n.endModel(t)}}e.Model=n}(e.fbs||(e.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(t){!function(t){class e{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsKernelCreateInfos(t,n){return(n||new e).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsKernelCreateInfos(t,n){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(n||new e).__init(t.readInt32(t.position())+t.position(),t)}nodeIndices(t){let e=this.bb.__offset(this.bb_pos,4);return e?this.bb.readUint32(this.bb.__vector(this.bb_pos+e)+4*t):0}nodeIndicesLength(){let t=this.bb.__offset(this.bb_pos,4);return t?this.bb.__vector_len(this.bb_pos+t):0}nodeIndicesArray(){let t=this.bb.__offset(this.bb_pos,4);return t?new Uint32Array(this.bb.bytes().buffer,this.bb.bytes().byteOffset+this.bb.__vector(this.bb_pos+t),this.bb.__vector_len(this.bb_pos+t)):null}kernelDefHashes(t){let e=this.bb.__offset(this.bb_pos,6);return e?this.bb.readUint64(this.bb.__vector(this.bb_pos+e)+8*t):this.bb.createLong(0,0)}kernelDefHashesLength(){let t=this.bb.__offset(this.bb_pos,6);return t?this.bb.__vector_len(this.bb_pos+t):0}static startKernelCreateInfos(t){t.startObject(2)}static addNodeIndices(t,e){t.addFieldOffset(0,e,0)}static createNodeIndicesVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addInt32(e[n]);return t.endVector()}static startNodeIndicesVector(t,e){t.startVector(4,e,4)}static addKernelDefHashes(t,e){t.addFieldOffset(1,e,0)}static createKernelDefHashesVector(t,e){t.startVector(8,e.length,8);for(let n=e.length-1;n>=0;n--)t.addInt64(e[n]);return t.endVector()}static startKernelDefHashesVector(t,e){t.startVector(8,e,8)}static endKernelCreateInfos(t){return t.endObject()}static createKernelCreateInfos(t,n,r){return e.startKernelCreateInfos(t),e.addNodeIndices(t,n),e.addKernelDefHashes(t,r),e.endKernelCreateInfos(t)}}t.KernelCreateInfos=e}(t.fbs||(t.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(e){!function(e){class n{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsSubGraphSessionState(t,e){return(e||new n).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsSubGraphSessionState(t,e){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(e||new n).__init(t.readInt32(t.position())+t.position(),t)}graphId(t){let e=this.bb.__offset(this.bb_pos,4);return e?this.bb.__string(this.bb_pos+e,t):null}sessionState(e){let n=this.bb.__offset(this.bb_pos,6);return n?(e||new t.experimental.fbs.SessionState).__init(this.bb.__indirect(this.bb_pos+n),this.bb):null}static startSubGraphSessionState(t){t.startObject(2)}static addGraphId(t,e){t.addFieldOffset(0,e,0)}static addSessionState(t,e){t.addFieldOffset(1,e,0)}static endSubGraphSessionState(t){let e=t.endObject();return t.requiredField(e,4),e}static createSubGraphSessionState(t,e,r){return n.startSubGraphSessionState(t),n.addGraphId(t,e),n.addSessionState(t,r),n.endSubGraphSessionState(t)}}e.SubGraphSessionState=n}(e.fbs||(e.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(e){!function(e){class n{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsSessionState(t,e){return(e||new n).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsSessionState(t,e){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(e||new n).__init(t.readInt32(t.position())+t.position(),t)}kernels(e){let n=this.bb.__offset(this.bb_pos,4);return n?(e||new t.experimental.fbs.KernelCreateInfos).__init(this.bb.__indirect(this.bb_pos+n),this.bb):null}subGraphSessionStates(e,n){let r=this.bb.__offset(this.bb_pos,6);return r?(n||new t.experimental.fbs.SubGraphSessionState).__init(this.bb.__indirect(this.bb.__vector(this.bb_pos+r)+4*e),this.bb):null}subGraphSessionStatesLength(){let t=this.bb.__offset(this.bb_pos,6);return t?this.bb.__vector_len(this.bb_pos+t):0}static startSessionState(t){t.startObject(2)}static addKernels(t,e){t.addFieldOffset(0,e,0)}static addSubGraphSessionStates(t,e){t.addFieldOffset(1,e,0)}static createSubGraphSessionStatesVector(t,e){t.startVector(4,e.length,4);for(let n=e.length-1;n>=0;n--)t.addOffset(e[n]);return t.endVector()}static startSubGraphSessionStatesVector(t,e){t.startVector(4,e,4)}static endSessionState(t){return t.endObject()}static createSessionState(t,e,r){return n.startSessionState(t),n.addKernels(t,e),n.addSubGraphSessionStates(t,r),n.endSessionState(t)}}e.SessionState=n}(e.fbs||(e.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={})),function(t){!function(e){!function(e){class n{constructor(){this.bb=null,this.bb_pos=0}__init(t,e){return this.bb_pos=t,this.bb=e,this}static getRootAsInferenceSession(t,e){return(e||new n).__init(t.readInt32(t.position())+t.position(),t)}static getSizePrefixedRootAsInferenceSession(t,e){return t.setPosition(t.position()+r.flatbuffers.SIZE_PREFIX_LENGTH),(e||new n).__init(t.readInt32(t.position())+t.position(),t)}static bufferHasIdentifier(t){return t.__has_identifier("ORTM")}ortVersion(t){let e=this.bb.__offset(this.bb_pos,4);return e?this.bb.__string(this.bb_pos+e,t):null}model(e){let n=this.bb.__offset(this.bb_pos,6);return n?(e||new t.experimental.fbs.Model).__init(this.bb.__indirect(this.bb_pos+n),this.bb):null}sessionState(e){let n=this.bb.__offset(this.bb_pos,8);return n?(e||new t.experimental.fbs.SessionState).__init(this.bb.__indirect(this.bb_pos+n),this.bb):null}static startInferenceSession(t){t.startObject(3)}static addOrtVersion(t,e){t.addFieldOffset(0,e,0)}static addModel(t,e){t.addFieldOffset(1,e,0)}static addSessionState(t,e){t.addFieldOffset(2,e,0)}static endInferenceSession(t){return t.endObject()}static finishInferenceSessionBuffer(t,e){t.finish(e,"ORTM")}static finishSizePrefixedInferenceSessionBuffer(t,e){t.finish(e,"ORTM",!0)}static createInferenceSession(t,e,r,i){return n.startInferenceSession(t),n.addOrtVersion(t,e),n.addModel(t,r),n.addSessionState(t,i),n.endInferenceSession(t)}}e.InferenceSession=n}(e.fbs||(e.fbs={}))}(t.experimental||(t.experimental={}))}(e.onnxruntime||(e.onnxruntime={}))},7448:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.OnnxjsSessionHandler=void 0;const r=n(8453),i=n(9162);e.OnnxjsSessionHandler=class{constructor(t){this.session=t,this.inputNames=this.session.inputNames,this.outputNames=this.session.outputNames}async dispose(){}async run(t,e,n){const o=new Map;for(const e in t)if(Object.hasOwnProperty.call(t,e)){const n=t[e];o.set(e,new i.Tensor(n.dims,n.type,void 0,void 0,n.data))}const a=await this.session.run(o),s={};return a.forEach(((t,e)=>{s[e]=new r.Tensor(t.type,t.data,t.dims)})),s}startProfiling(){this.session.startProfiling()}endProfiling(){this.session.endProfiling()}}},6919:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.Session=void 0;const r=n(7067),i=n(1296),o=n(7091),a=n(1036),s=n(6231),u=n(2644);e.Session=class{constructor(t={}){this._initialized=!1,this.backendHint=t.backendHint,this.profiler=s.Profiler.create(t.profiler),this.context={profiler:this.profiler,graphInputTypes:[],graphInputDims:[]}}get inputNames(){return this._model.graph.getInputNames()}get outputNames(){return this._model.graph.getOutputNames()}startProfiling(){this.profiler.start()}endProfiling(){this.profiler.stop()}async loadModel(t,e,n){await this.profiler.event("session","Session.loadModel",(async()=>{const a=await(0,o.resolveBackend)(this.backendHint);if(this.sessionHandler=a.createSessionHandler(this.context),this._model=new u.Model,"string"==typeof t){const e=t.endsWith(".ort");if("undefined"==typeof fetch){const n=await(0,i.promisify)(r.readFile)(t);this.initialize(n,e)}else{const n=await fetch(t),r=await n.arrayBuffer();this.initialize(new Uint8Array(r),e)}}else if(ArrayBuffer.isView(t))this.initialize(t);else{const r=new Uint8Array(t,e||0,n||t.byteLength);this.initialize(r)}}))}initialize(t,e){if(this._initialized)throw new Error("already initialized");this.profiler.event("session","Session.initialize",(()=>{const n=this.sessionHandler.transformGraph?this.sessionHandler:void 0;this._model.load(t,n,e),this.sessionHandler.onGraphInitialized&&this.sessionHandler.onGraphInitialized(this._model.graph),this.initializeOps(this._model.graph),this._executionPlan=new a.ExecutionPlan(this._model.graph,this._ops,this.profiler)})),this._initialized=!0}async run(t){if(!this._initialized)throw new Error("session not initialized yet");return this.profiler.event("session","Session.run",(async()=>{const e=this.normalizeAndValidateInputs(t),n=await this._executionPlan.execute(this.sessionHandler,e);return this.createOutput(n)}))}normalizeAndValidateInputs(t){const e=this._model.graph.getInputNames();if(Array.isArray(t)){if(t.length!==e.length)throw new Error(`incorrect input array length: expected ${e.length} but got ${t.length}`)}else{if(t.size!==e.length)throw new Error(`incorrect input map size: expected ${e.length} but got ${t.size}`);const n=new Array(t.size);let r=0;for(let i=0;i<e.length;++i){const o=t.get(e[i]);if(!o)throw new Error(`missing input tensor for: '${name}'`);n[r++]=o}t=n}if(this.context.graphInputTypes&&0!==this.context.graphInputTypes.length&&this.context.graphInputDims&&0!==this.context.graphInputDims.length)this.validateInputTensorDims(this.context.graphInputDims,t,!1);else{const e=this._model.graph.getInputIndices(),n=this._model.graph.getValues(),r=new Array(e.length);for(let i=0;i<e.length;++i){const o=n[e[i]];r[i]=o.type.shape.dims,this.context.graphInputTypes.push(o.type.tensorType),this.context.graphInputDims.push(t[i].dims)}this.validateInputTensorDims(r,t,!0)}return this.validateInputTensorTypes(this.context.graphInputTypes,t),t}validateInputTensorTypes(t,e){for(let n=0;n<e.length;n++){const r=t[n],i=e[n].type;if(r!==i)throw new Error(`input tensor[${n}] check failed: expected type '${r}' but got ${i}`)}}validateInputTensorDims(t,e,n){for(let r=0;r<e.length;r++){const i=t[r],o=e[r].dims;if(!this.compareTensorDims(i,o,n))throw new Error(`input tensor[${r}] check failed: expected shape '[${i.join(",")}]' but got [${o.join(",")}]`)}}compareTensorDims(t,e,n){if(t.length!==e.length)return!1;for(let r=0;r<t.length;++r)if(t[r]!==e[r]&&(!n||0!==t[r]))return!1;return!0}createOutput(t){const e=this._model.graph.getOutputNames();if(t.length!==e.length)throw new Error("expected number of outputs do not match number of generated outputs");const n=new Map;for(let r=0;r<e.length;++r)n.set(e[r],t[r]);return n}initializeOps(t){const e=t.getNodes();this._ops=new Array(e.length);for(let n=0;n<e.length;n++)this._ops[n]=this.sessionHandler.resolve(e[n],this._model.opsets,t)}}},9162:function(t,e,n){"use strict";var r=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.Tensor=void 0;const i=n(3442),o=r(n(3720)),a=n(1446),s=n(9395),u=n(2517);var c=s.onnxruntime.experimental.fbs;class l{get data(){if(void 0===this.cache){const t=this.dataProvider(this.dataId);if(t.length!==this.size)throw new Error("Length of data provided by the Data Provider is inconsistent with the dims of this Tensor.");this.cache=t}return this.cache}get stringData(){if("string"!==this.type)throw new TypeError("data type is not string");return this.data}get integerData(){switch(this.type){case"uint8":case"int8":case"uint16":case"int16":case"int32":case"uint32":case"bool":return this.data;default:throw new TypeError("data type is not integer (uint8, int8, uint16, int16, int32, uint32, bool)")}}get floatData(){switch(this.type){case"float32":case"float64":return this.data;default:throw new TypeError("data type is not float (float32, float64)")}}get numberData(){if("string"!==this.type)return this.data;throw new TypeError("type cannot be non-number (string)")}get(t){return this.data[u.ShapeUtil.indicesToOffset(t,this.strides)]}set(t,e){this.data[u.ShapeUtil.indicesToOffset(t,this.strides)]=e}async getData(){return void 0===this.cache&&(this.cache=await this.asyncDataProvider(this.dataId)),this.cache}get strides(){return this._strides||(this._strides=u.ShapeUtil.computeStrides(this.dims)),this._strides}constructor(t,e,n,r,o,a=i.Guid.create()){this.dims=t,this.type=e,this.dataProvider=n,this.asyncDataProvider=r,this.cache=o,this.dataId=a,this.size=u.ShapeUtil.validateDimsAndCalcSize(t);const s=this.size,c=void 0===n&&void 0===r&&void 0===o;if(void 0!==o&&o.length!==s)throw new RangeError("Input dims doesn't match data length.");if("string"===e){if(!(void 0===o||Array.isArray(o)&&o.every((t=>"string"==typeof t))))throw new TypeError("cache should be a string array");c&&(this.cache=new Array(s))}else{if(void 0!==o){const t=f(e);if(!(o instanceof t))throw new TypeError(`cache should be type ${t.name}`)}if(c){const t=new ArrayBuffer(s*function(t){switch(t){case"bool":case"int8":case"uint8":return 1;case"int16":case"uint16":return 2;case"int32":case"uint32":case"float32":return 4;case"float64":return 8;default:throw new Error(`cannot calculate sizeof() on type ${t}`)}}(e));this.cache=function(t,e){return new(f(e))(t)}(t,e)}}}static fromProto(t){if(!t)throw new Error("cannot construct Value from an empty tensor");const e=u.ProtoUtil.tensorDataTypeFromProto(t.dataType),n=u.ProtoUtil.tensorDimsFromProto(t.dims),r=new l(n,e);if("string"===e)t.stringData.forEach(((t,e)=>{r.data[e]=(0,u.decodeUtf8String)(t)}));else if(t.rawData&&"number"==typeof t.rawData.byteLength&&t.rawData.byteLength>0){const e=r.data,n=new DataView(t.rawData.buffer,t.rawData.byteOffset,t.rawData.byteLength),i=p(t.dataType),o=t.rawData.byteLength/i;if(t.rawData.byteLength%i!=0)throw new Error("invalid buffer length");if(e.length!==o)throw new Error("buffer length mismatch");for(let r=0;r<o;r++){const o=h(n,t.dataType,r*i);e[r]=o}}else{let e;switch(t.dataType){case a.onnx.TensorProto.DataType.FLOAT:e=t.floatData;break;case a.onnx.TensorProto.DataType.INT32:case a.onnx.TensorProto.DataType.INT16:case a.onnx.TensorProto.DataType.UINT16:case a.onnx.TensorProto.DataType.INT8:case a.onnx.TensorProto.DataType.UINT8:case a.onnx.TensorProto.DataType.BOOL:e=t.int32Data;break;case a.onnx.TensorProto.DataType.INT64:e=t.int64Data;break;case a.onnx.TensorProto.DataType.DOUBLE:e=t.doubleData;break;case a.onnx.TensorProto.DataType.UINT32:case a.onnx.TensorProto.DataType.UINT64:e=t.uint64Data;break;default:throw new Error("unspecific error")}if(null==e)throw new Error("failed to populate data from a tensorproto value");const n=r.data;if(n.length!==e.length)throw new Error("array length mismatch");for(let r=0;r<e.length;r++){const i=e[r];o.default.isLong(i)?n[r]=d(i,t.dataType):n[r]=i}}return r}static fromData(t,e,n){return new l(e,n,void 0,void 0,t)}static fromOrtTensor(t){if(!t)throw new Error("cannot construct Value from an empty tensor");const e=u.ProtoUtil.tensorDimsFromORTFormat(t),n=u.ProtoUtil.tensorDataTypeFromProto(t.dataType()),r=new l(e,n);if("string"===n)for(let e=0;e<t.stringDataLength();e++)r.data[e]=t.stringData(e);else if(t.rawDataArray()&&"number"==typeof t.rawDataLength()&&t.rawDataLength()>0){const e=r.data,n=new DataView(t.rawDataArray().buffer,t.rawDataArray().byteOffset,t.rawDataLength()),i=p(t.dataType()),o=t.rawDataLength()/i;if(t.rawDataLength()%i!=0)throw new Error("invalid buffer length");if(e.length!==o)throw new Error("buffer length mismatch");for(let r=0;r<o;r++){const o=h(n,t.dataType(),r*i);e[r]=o}}return r}}function p(t){switch(t){case a.onnx.TensorProto.DataType.UINT8:case a.onnx.TensorProto.DataType.INT8:case a.onnx.TensorProto.DataType.BOOL:return 1;case a.onnx.TensorProto.DataType.UINT16:case a.onnx.TensorProto.DataType.INT16:return 2;case a.onnx.TensorProto.DataType.FLOAT:case a.onnx.TensorProto.DataType.INT32:case a.onnx.TensorProto.DataType.UINT32:return 4;case a.onnx.TensorProto.DataType.INT64:case a.onnx.TensorProto.DataType.DOUBLE:case a.onnx.TensorProto.DataType.UINT64:return 8;default:throw new Error(`cannot calculate sizeof() on type ${a.onnx.TensorProto.DataType[t]}`)}}function f(t){switch(t){case"bool":case"uint8":return Uint8Array;case"int8":return Int8Array;case"int16":return Int16Array;case"uint16":return Uint16Array;case"int32":return Int32Array;case"uint32":return Uint32Array;case"float32":return Float32Array;case"float64":return Float64Array;default:throw new Error("unspecified error")}}function d(t,e){if(e===a.onnx.TensorProto.DataType.INT64||e===c.TensorDataType.INT64){if(t.greaterThanOrEqual(2147483648)||t.lessThan(-2147483648))throw new TypeError("int64 is not supported")}else{if(e!==a.onnx.TensorProto.DataType.UINT32&&e!==c.TensorDataType.UINT32&&e!==a.onnx.TensorProto.DataType.UINT64&&e!==c.TensorDataType.UINT64)throw new TypeError(`not a LONG type: ${a.onnx.TensorProto.DataType[e]}`);if(t.greaterThanOrEqual(4294967296)||t.lessThan(0))throw new TypeError("uint64 is not supported")}return t.toNumber()}function h(t,e,n){switch(e){case a.onnx.TensorProto.DataType.BOOL:case a.onnx.TensorProto.DataType.UINT8:return t.getUint8(n);case a.onnx.TensorProto.DataType.INT8:return t.getInt8(n);case a.onnx.TensorProto.DataType.UINT16:return t.getUint16(n,!0);case a.onnx.TensorProto.DataType.INT16:return t.getInt16(n,!0);case a.onnx.TensorProto.DataType.FLOAT:return t.getFloat32(n,!0);case a.onnx.TensorProto.DataType.INT32:return t.getInt32(n,!0);case a.onnx.TensorProto.DataType.UINT32:return t.getUint32(n,!0);case a.onnx.TensorProto.DataType.INT64:return d(o.default.fromBits(t.getUint32(n,!0),t.getUint32(n+4,!0),!1),e);case a.onnx.TensorProto.DataType.DOUBLE:return t.getFloat64(n,!0);case a.onnx.TensorProto.DataType.UINT64:return d(o.default.fromBits(t.getUint32(n,!0),t.getUint32(n+4,!0),!0),e);default:throw new Error(`cannot read from DataView for type ${a.onnx.TensorProto.DataType[e]}`)}}e.Tensor=l},2517:function(t,e,n){"use strict";var r=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.decodeUtf8String=e.MAX_CLIP=e.MIN_CLIP=e.PoolConvUtil=e.ReduceUtil=e.SplitUtil=e.MathUtil=e.ShapeUtil=e.LongUtil=e.ProtoUtil=e.GemmUtil=e.arrayCopyHelper=e.BroadcastUtil=e.MatMulUtil=e.ArrayUtil=e.assert=e.checkInputsShape=void 0;const i=n(5686),o=r(n(3720)),a=n(1446),s=n(9162);e.checkInputsShape=function(t,...e){if(!t||t.length!==e.length)return!1;for(let n=0;n<t.length;n++)if(!t[n].dims||t[n].dims.length!==e[n])return!1;return!0},e.assert=function(t,e){if(!t)throw new Error("string"==typeof e?e:e())},e.ArrayUtil=class{static arraysEqual(t,e){if(t.length!==e.length)return!1;for(let n=0;n<t.length;n++)if(t[n]!==e[n])return!1;return!0}};class u{static preprocessInputShapes(t,e){return[1===t.length?[1,t[0]]:t,1===e.length?[e[0],1]:e]}static postprocessOutputShape(t,e,n){1===e&&t.splice(t.length-2,1),1===n&&t.pop()}static calcMatMulShape(t,e){return t[1]!==e[0]?void 0:[t[0],e[1]]}}e.MatMulUtil=u;class c{static calcShape(t,e,n=!1){const r=t.length,i=e.length;if(0===r)return e;if(0===i)return t;const o=Math.max(t.length,e.length),a=new Array(o);if(n){if(r<2||i<2)return;const n=u.calcMatMulShape([t[r-2],t[r-1]],[e[i-2],e[i-1]]);if(void 0===n)return;[a[o-2],a[o-1]]=n}for(let s=n?3:1;s<=o;s++){const n=r-s<0?1:t[r-s],u=i-s<0?1:e[i-s];if(n!==u&&n>1&&u>1)return;a[o-s]=Math.max(n,u)}return a}static index(t,e){const n=new Array(e.length);return c.fillIndex(t,e,n),n}static fillIndex(t,e,n){const r=t.length-e.length;for(let i=0;i<e.length;i++)n[i]=t[r+i]%e[i]}static calc(t,e,n,r,i){const o=c.calcShape(t.dims,e.dims);if(o){if(r&&!f.areEqual(o,t.dims))return;const a=f.size(o),u=r?t:new s.Tensor(o,i||t.type);if(0===o.length)u.set([],n(t.get([]),e.get([])));else{const r=new Array(o.length),i=new Array(t.dims.length),s=new Array(e.dims.length);let l,p=0,f=0,d=!1,h=!1;0===t.dims.length&&(p=t.get([]),d=!0),0===e.dims.length&&(f=e.get([]),h=!0);for(let g=0;g<a;g++){l=g;for(let t=o.length-1;t>=0;t--)r[t]=l%o[t],l=Math.floor(l/o[t]);d||(c.fillIndex(r,t.dims,i),p=t.get(i)),h||(c.fillIndex(r,e.dims,s),f=e.get(s)),u.set(r,n(p,f))}}return u}}static isValidBroadcast(t,e){const n=t.length,r=e.length;if(n>r)return!1;for(let i=1;i<=n;i++)if(1!==t[n-i]&&t[n-i]!==e[r-i])return!1;return!0}static getBroadcastDims(t,e){const n=t.length,r=[];for(let i=0;i<n;i++){const o=n-1-i,a=t[o]||1;(e[e.length-1-i]||1)>1&&1===a&&r.unshift(o)}return r}}e.BroadcastUtil=c,e.arrayCopyHelper=function(t,e,n,r,i){if(r<0||r>=e.length)throw new Error("sourceIndex out of bounds");if(n<0||n>=t.length)throw new Error("targetIndex out of bounds");if(r+i>e.length)throw new Error("source indices to be copied are outside bounds");if(n+i>t.length)throw new Error("target array is too small to hold result");for(let o=0;o<i;o++)t[n+o]=e[r+o]},e.GemmUtil=class{static getShapeOfGemmResult(t,e,n,r,i){if(2!==t.length||2!==n.length)throw new Error("shape need to be of size 2");let o,a,s;e?(o=t[1],a=t[0]):(o=t[0],a=t[1]);let u=-1;if(r?(s=n[0],u=1):(s=n[1],u=0),n[u]!==a)throw new Error("dimension mismatch");if(o<=0||s<=0||a<=0)throw new Error("invalid shape specified");if(i&&!c.isValidBroadcast(i,[o,s]))throw new Error("gemm: invalid bias shape for broadcast");return[o,s,a]}};class l{static tensorDataTypeFromProto(t){switch(t){case a.onnx.TensorProto.DataType.INT8:return"int8";case a.onnx.TensorProto.DataType.UINT8:return"uint8";case a.onnx.TensorProto.DataType.BOOL:return"bool";case a.onnx.TensorProto.DataType.INT16:return"int16";case a.onnx.TensorProto.DataType.UINT16:return"uint16";case a.onnx.TensorProto.DataType.INT32:return"int32";case a.onnx.TensorProto.DataType.UINT32:return"uint32";case a.onnx.TensorProto.DataType.FLOAT:return"float32";case a.onnx.TensorProto.DataType.DOUBLE:return"float64";case a.onnx.TensorProto.DataType.STRING:return"string";case a.onnx.TensorProto.DataType.INT64:return"int32";case a.onnx.TensorProto.DataType.UINT64:return"uint32";default:throw new Error(`unsupported data type: ${a.onnx.TensorProto.DataType[t]}`)}}static tensorDataTypeStringToEnum(t){switch(t){case"int8":return a.onnx.TensorProto.DataType.INT8;case"uint8":return a.onnx.TensorProto.DataType.UINT8;case"bool":return a.onnx.TensorProto.DataType.BOOL;case"int16":return a.onnx.TensorProto.DataType.INT16;case"uint16":return a.onnx.TensorProto.DataType.UINT16;case"int32":return a.onnx.TensorProto.DataType.INT32;case"uint32":return a.onnx.TensorProto.DataType.UINT32;case"float32":return a.onnx.TensorProto.DataType.FLOAT;case"float64":return a.onnx.TensorProto.DataType.DOUBLE;case"string":return a.onnx.TensorProto.DataType.STRING;case"int64":return a.onnx.TensorProto.DataType.INT64;case"uint64":return a.onnx.TensorProto.DataType.UINT64;default:throw new Error(`unsupported data type: ${t}`)}}static tensorDimsFromProto(t){return t.map((t=>o.default.isLong(t)?t.toNumber():t))}static tensorValueTypeFromProto(t){return{tensorType:l.tensorDataTypeFromProto(t.elemType),shape:{dims:l.tensorDimsFromProto(t.shape.dim.map((t=>t.dimValue)))}}}static tensorDimsFromORTFormat(t){const e=[];for(let n=0;n<t.dimsLength();n++)e.push(p.longToNumber(t.dims(n)));return e}static tensorAttributesFromORTFormat(t){const e=[];for(let n=0;n<t.attributesLength();n++)e.push(t.attributes(n));return e}}e.ProtoUtil=l;class p{static longToNumber(t,e){return o.default.isLong(t)?t.toNumber():t instanceof i.flatbuffers.Long?o.default.fromValue({low:t.low,high:t.high,unsigned:null!=e&&e}).toNumber():t}static isLong(t){return o.default.isLong(t)||t instanceof i.flatbuffers.Long}}e.LongUtil=p;class f{static size(t){return f.getSizeFromDimensionRange(t,0,t.length)}static sizeFromDimension(t,e){if(e<0||e>t.length)throw new Error(`invalid dimension of ${e} for sizeFromDimension as Tensor has ${t.length} dimensions.`);return f.getSizeFromDimensionRange(t,e,t.length)}static sizeToDimension(t,e){if(e<0||e>t.length)throw new Error(`invalid dimension of ${e} for sizeToDimension as Tensor has ${t.length} dimensions.`);return f.getSizeFromDimensionRange(t,0,e)}static getSizeFromDimensionRange(t,e,n){let r=1;for(let i=e;i<n;i++){if(t[i]<=0)throw new Error("cannot get valid size from specified dimension range. Most likely the range contains 0 or negative values in them.");r*=t[i]}return r}static computeStrides(t){const e=t.length;if(0===e)return[];if(1===e)return[1];const n=new Array(e);n[e-1]=1,n[e-2]=t[e-1];for(let r=e-3;r>=0;--r)n[r]=n[r+1]*t[r+1];return n}static transpose(t){return t.slice().reverse()}static indicesToOffset(t,e,n){void 0===n&&(n=t.length);let r=0;for(let i=0;i<n;++i)r+=e[i]*t[i];return r}static offsetToIndices(t,e){const n=e.length;if(0===n)return[];if(1===n)return[t*e[0]];const r=new Array(e.length);for(let n=0;n<r.length-1;++n)r[n]=Math.floor(t/e[n]),t-=r[n]*e[n];return r[r.length-1]=t,r}static normalizeAxis(t,e){if(t<-e&&t>=e)throw new Error("unsupported axis for this operation.");return t<0?t+e:t}static normalizeAxes(t,e){return t.map((t=>this.normalizeAxis(t,e)))}static incrementIndex(t,e,n){if(0===e.length||0===t.length)throw new Error("Index incrementing unsupported for scalar Tensor");if(void 0===n)n=e.length;else if(n<=0||n>e.length)throw new Error("Incorrect axis to increment on");for(let r=n-1;r>=0&&(t[r]++,!(t[r]<e[r]));--r)t[r]=0}static calculateReshapedDims(t,e){if(0===e.length){if(0===t.length||1===f.size(t))return[];throw new Error("cannot reshape to a scalar Tensor")}const n=e.length,r=new Array(n);let i=-1,o=1;for(let a=0;a<n;a++){if(e[a]<-1)throw new Error("a dimension in shape hints cannot be less than -1");if(-1===e[a]){if(-1!==i)throw new Error("at most one dimension in shape hints can be -1");i=a}else{if(0===e[a]){if(a>=t.length)throw new Error("the dimension with value zero exceeds the dimension size of the input tensor");r[a]=t[a]}else r[a]=e[a];o*=r[a]}}const a=f.size(t);if(-1!==i){if(a%o!=0)throw new Error(`the input tensor cannot be reshaped to the requested shape. Input shape: [${t}] Output shape: [${e}]`);r[i]=a/o}else if(o!==a)throw new Error("reshapedDims and originalDims don't have matching sizes");return r}static sortBasedOnPerm(t,e){return e?e.map((e=>t[e])):t.slice().reverse()}static padShape(t,e){const n=t.length;return t.map(((t,r)=>t+e[r]+e[r+n]))}static areEqual(t,e){return t.length===e.length&&t.every(((t,n)=>t===e[n]))}static validateDimsAndCalcSize(t){if(t.length>6)throw new TypeError("Only rank 0 to 6 is supported for tensor shape.");let e=1;for(const n of t){if(!Number.isInteger(n))throw new TypeError(`Invalid shape: ${n} is not an integer`);if(n<0||n>2147483647)throw new TypeError(`Invalid shape: length ${n} is not allowed`);e*=n}return e}static flattenShape(t,e){e<0&&(e+=t.length);const n=t.reduce(((t,e)=>t*e),1),r=t.slice(e).reduce(((t,e)=>t*e),1);return[n/r,r]}static squeezeShape(t,e){const n=new Array;e=f.normalizeAxes(e,t.length);for(let r=0;r<t.length;r++){const i=e.indexOf(r)>=0;if(i&&1!==t[r])throw new Error("squeeze an axis of size different than 1");(0===e.length&&t[r]>1||e.length>0&&!i)&&n.push(t[r])}return n}static unsqueezeShape(t,e){const n=new Array(t.length+e.length);n.fill(0);for(let t=0;t<e.length;t++){const r=f.normalizeAxis(e[t],n.length);if(r>=n.length)throw new Error("'axes' has an out of range axis");if(0!==n[r])throw new Error("'axes' has a duplicate axis");n[r]=1}let r=0;for(let e=0;e<n.length;e++)0===n[e]&&(n[e]=t[r++]);if(r!==t.length)throw new Error("the unsqueezed dimension could not be established");return n}}e.ShapeUtil=f,e.MathUtil=class{static sqr(t,e,n,r,i){if(r<0||r>=e.length)throw new Error("sourceIndex out of bounds");if(n<0||n>=t.length)throw new Error("targetIndex out of bounds");if(r+i>e.length)throw new Error("source indices to be copied are outside bounds");if(n+i>t.length)throw new Error("target array is too small to hold result");for(let o=0;o<i;o++)t[n+o]+=Math.pow(e[r+o],2)}static axpy(t,e,n,r,i,o){if(r<0||r>=e.length)throw new Error("sourceIndex out of bounds");if(n<0||n>=t.length)throw new Error("targetIndex out of bounds");if(r+i>e.length)throw new Error("source indices to be copied are outside bounds");if(n+i>t.length)throw new Error("target array is too small to hold result");for(let a=0;a<i;a++)t[n+a]+=o*e[r+a]}static powx(t,e,n,r,i,o){if(r<0||r>=e.length)throw new Error("sourceIndex out of bounds");if(n<0||n>=t.length)throw new Error("targetIndex out of bounds");if(r+i>e.length)throw new Error("source indices to be copied are outside bounds");if(n+i>t.length)throw new Error("target array is too small to hold result");for(let a=0;a<i;a++)t[n+a]=Math.pow(e[r+a],o)}static mul(t,e,n,r,i){if(r<0||r>=e.length)throw new Error("sourceIndex out of bounds");if(n<0||n>=t.length)throw new Error("targetIndex out of bounds");if(r+i>e.length)throw new Error("source indices to be copied are outside bounds");if(n+i>t.length)throw new Error("target array is too small to hold result");for(let o=0;o<i;o++)t[n+o]=e[r+o]*t[n+o]}};class d{static splitShape(t,e,n,r){if(0===n.length){if(!r)throw new Error("need to know number of outputs when the 'split' attribute is not specified");d.determineSplit(t[e],r,n)}const i=[],o=[0];for(let r=0;r<n.length;++r){0!==r&&o.push(o[r-1]+n[r-1]);const a=t.slice();a[e]=n[r],i.push(a)}return[i,o]}static determineSplit(t,e,n){if(t%e!=0)throw new Error("cannot split tensor to equal sized parts");for(let r=0;r<e;++r)n.push(t/e)}}e.SplitUtil=d;class h{static calcReduce(t,e,n,r,i){const o=t.dims.slice(0);0===e.length&&o.forEach(((t,n)=>e.push(n)));const a=h.calcReduceShape(o,e,!0),u=f.size(a),l=new s.Tensor(a,t.type),p=f.computeStrides(a),d=f.computeStrides(o),g=new Array(o.length);for(let n=0;n<u;n++){const a=f.offsetToIndices(n,p);c.fillIndex(a,o,g),l.set(a,h.calcReduceByAxis(t.numberData,e,o,0,f.indicesToOffset(g,d),r,i))}return n?l:new s.Tensor(h.calcReduceShape(o,e,n),l.type,void 0,void 0,l.data,l.dataId)}static calcReduceByAxis(t,e,n,r,i,o,a){let s=0;if(r>=e.length)return o(t[i]);const u=e[r],c=u>=n.length?1:f.size(n.slice(u+1));for(let l=0;l<n[u];l++)s=0===l?h.calcReduceByAxis(t,e,n,r+1,i,o,a):a(s,h.calcReduceByAxis(t,e,n,r+1,i,o,a)),i+=c;return s}static calcReduceShape(t,e,n){const r=t.slice();for(let t=0;t<e.length;t++)r[e[t]]=n?1:0;return r.filter((t=>0!==t))}}e.ReduceUtil=h;class g{static adjustPoolAttributes(t,e,n,r,i,o){if(!t&&n.length!==e.length-2)throw new Error("length of specified kernel shapes should be 2 less than length of input dimensions");if(t)for(let t=0;t<e.length-2;t++)t>=n.length?n.push(e[t+2]):n[t]=e[t+2];for(let t=0;t<n.length;t++)if(t<r.length){if(r[t]<0)throw new Error("strides should be greater than or equal to 1")}else r.push(1);for(let t=0;t<n.length;t++)if(t<i.length){if(i[t]<0)throw new Error("dilations should be greater than or equal to 1")}else i.push(1);for(let t=0;t<2*n.length;t++)if(t<o.length){if(o[t]<0)throw new Error("pad should be greater than or equal to 1")}else o.push(0);for(let t=0;t<n.length;t++){if(n[t]<=0)throw new Error("kernel shapes need to be greater than 0");if(o[t]>=n[t]||o[t+n.length]>=n[t])throw new Error("pads should be smaller than kernel")}}static adjustPadsBasedOnAutoPad(t,e,n,r,i,o){if(o){if(i.length!==2*(t.length-2))throw new Error("length of pads should be twice the length of data dimensions");if(e.length!==t.length-2)throw new Error("length of strides should be the length of data dimensions");if(r.length!==t.length-2)throw new Error("length of kernel shapes should be the length of data dimensions");for(let a=0;a<t.length-2;a++)g.adjustPadAndReturnShape(t[a+2],e[a],n[a],r[a],i,a,a+t.length-2,o)}}static computePoolOutputShape(t,e,n,r,i,o,a){if(e.length<=0)throw new Error("input shape must be of size greater than 0");const s=[e[0],e[1]];return g.computeShapeHelper(t,e,s,n,r,i,o,a),s}static computeConvOutputShape(t,e,n,r,i,o,a){if(t.length<=0||e.length<=0)throw new Error("invalid input tensor dims or invalid filter tensor dims");const s=[t[0],e[0]];return g.computeShapeHelper(!1,t,s,n,r,i,o,a),s}static computeShapeHelper(t,e,n,r,i,o,a,s){if(t)for(let t=0;t<e.length-2;t++)n.push(1);else for(let t=0;t<e.length-2;t++)n.push(g.adjustPadAndReturnShape(e[t+2],r[t],i[t],o[t],a,t,t+e.length-2,s))}static adjustPadAndReturnShape(t,e,n,r,i,o,a,s){const u=n*(r-1)+1;if(!s||"NOTSET"===s)return Math.floor((t+i[o]+i[a]-u)/e+1);switch(s){case"VALID":return i[o]=0,i[a]=0,Math.floor((t-u)/e+1);case"SAME_LOWER":case"SAME_UPPER":if(1!==n)throw new Error("Dilation not supported for SAME_UPPER or SAME_LOWER");{const n=((t+e-1)/e-1)*e+r-t;return i[o]="SAME_LOWER"===s?Math.floor((n+1)/2):Math.floor(n/2),i[a]=n-i[o],Math.floor((t+n-r)/e+1)}default:throw new Error("Unsupported AutoPad type")}}}e.PoolConvUtil=g,e.MIN_CLIP=-34028234663852886e22,e.MAX_CLIP=34028234663852886e22,e.decodeUtf8String=function(t){return(new TextDecoder).decode(t)}},7967:(t,e)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.iterateExtraOptions=void 0,e.iterateExtraOptions=(t,n,r,i)=>{if("object"==typeof t&&null!==t){if(r.has(t))throw new Error("Circular reference in options");r.add(t)}Object.entries(t).forEach((([t,o])=>{const a=n?n+t:t;if("object"==typeof o)(0,e.iterateExtraOptions)(o,a+".",r,i);else if("string"==typeof o||"number"==typeof o)i(a,o.toString());else{if("boolean"!=typeof o)throw new Error("Can't handle extra config type: "+typeof o);i(a,o?"1":"0")}}))}},2157:function(t,e,n){"use strict";var r,i=this&&this.__createBinding||(Object.create?function(t,e,n,r){void 0===r&&(r=n);var i=Object.getOwnPropertyDescriptor(e,n);i&&!("get"in i?!e.__esModule:i.writable||i.configurable)||(i={enumerable:!0,get:function(){return e[n]}}),Object.defineProperty(t,r,i)}:function(t,e,n,r){void 0===r&&(r=n),t[r]=e[n]}),o=this&&this.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),a=this&&this.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)"default"!==n&&Object.prototype.hasOwnProperty.call(t,n)&&i(e,t,n);return o(e,t),e};Object.defineProperty(e,"__esModule",{value:!0}),e.endProfiling=e.run=e.releaseSession=e.createSession=e.createSessionFinalize=e.createSessionAllocate=e.initOrt=e.initWasm=void 0;const s=n(8453),u=a(n(349)),c=n(6361),l=()=>!!s.env.wasm.proxy&&"undefined"!=typeof document;let p,f,d,h=!1,g=!1,b=!1;const m=[],y=[],_=[],v=[],w=[],x=[],T=()=>{if(h||!g||b||!p)throw new Error("worker not ready")},S=t=>{switch(t.data.type){case"init-wasm":h=!1,t.data.err?(b=!0,f[1](t.data.err)):(g=!0,f[0]());break;case"init-ort":t.data.err?d[1](t.data.err):d[0]();break;case"create_allocate":t.data.err?m.shift()[1](t.data.err):m.shift()[0](t.data.out);break;case"create_finalize":t.data.err?y.shift()[1](t.data.err):y.shift()[0](t.data.out);break;case"create":t.data.err?_.shift()[1](t.data.err):_.shift()[0](t.data.out);break;case"release":t.data.err?v.shift()[1](t.data.err):v.shift()[0]();break;case"run":t.data.err?w.shift()[1](t.data.err):w.shift()[0](t.data.out);break;case"end-profiling":t.data.err?x.shift()[1](t.data.err):x.shift()[0]()}},O="undefined"!=typeof document?null===(r=null===document||void 0===document?void 0:document.currentScript)||void 0===r?void 0:r.src:void 0;e.initWasm=async()=>{if(l()){if(g)return;if(h)throw new Error("multiple calls to 'initWasm()' detected.");if(b)throw new Error("previous call to 'initWasm()' failed.");return h=!0,void 0===s.env.wasm.wasmPaths&&O&&0!==O.indexOf("blob:")&&(s.env.wasm.wasmPaths=O.substr(0,+O.lastIndexOf("/")+1)),new Promise(((t,e)=>{null==p||p.terminate(),p=n(9710).Z(),p.onmessage=S,f=[t,e];const r={type:"init-wasm",in:s.env.wasm};p.postMessage(r)}))}return(0,c.initializeWebAssembly)(s.env.wasm)},e.initOrt=async(t,e)=>{if(l())return T(),new Promise(((n,r)=>{d=[n,r];const i={type:"init-ort",in:{numThreads:t,loggingLevel:e}};p.postMessage(i)}));u.initOrt(t,e)},e.createSessionAllocate=async t=>l()?(T(),new Promise(((e,n)=>{m.push([e,n]);const r={type:"create_allocate",in:{model:t}};p.postMessage(r,[t.buffer])}))):u.createSessionAllocate(t),e.createSessionFinalize=async(t,e)=>l()?(T(),new Promise(((n,r)=>{y.push([n,r]);const i={type:"create_finalize",in:{modeldata:t,options:e}};p.postMessage(i)}))):u.createSessionFinalize(t,e),e.createSession=async(t,e)=>l()?(T(),new Promise(((n,r)=>{_.push([n,r]);const i={type:"create",in:{model:t,options:e}};p.postMessage(i,[t.buffer])}))):u.createSession(t,e),e.releaseSession=async t=>{if(l())return T(),new Promise(((e,n)=>{v.push([e,n]);const r={type:"release",in:t};p.postMessage(r)}));u.releaseSession(t)},e.run=async(t,e,n,r,i)=>l()?(T(),new Promise(((o,a)=>{w.push([o,a]);const s={type:"run",in:{sessionId:t,inputIndices:e,inputs:n,outputIndices:r,options:i}};p.postMessage(s,u.extractTransferableBuffers(n))}))):u.run(t,e,n,r,i),e.endProfiling=async t=>{if(l())return T(),new Promise(((e,n)=>{x.push([e,n]);const r={type:"end-profiling",in:t};p.postMessage(r)}));u.endProfiling(t)}},586:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.setRunOptions=void 0;const r=n(7967),i=n(4983),o=n(6361);e.setRunOptions=t=>{const e=(0,o.getInstance)();let n=0;const a=[],s=t||{};try{if(void 0===(null==t?void 0:t.logSeverityLevel))s.logSeverityLevel=2;else if("number"!=typeof t.logSeverityLevel||!Number.isInteger(t.logSeverityLevel)||t.logSeverityLevel<0||t.logSeverityLevel>4)throw new Error(`log serverity level is not valid: ${t.logSeverityLevel}`);if(void 0===(null==t?void 0:t.logVerbosityLevel))s.logVerbosityLevel=0;else if("number"!=typeof t.logVerbosityLevel||!Number.isInteger(t.logVerbosityLevel))throw new Error(`log verbosity level is not valid: ${t.logVerbosityLevel}`);void 0===(null==t?void 0:t.terminate)&&(s.terminate=!1);let o=0;if(void 0!==(null==t?void 0:t.tag)&&(o=(0,i.allocWasmString)(t.tag,a)),n=e._OrtCreateRunOptions(s.logSeverityLevel,s.logVerbosityLevel,!!s.terminate,o),0===n)throw new Error("Can't create run options");return void 0!==(null==t?void 0:t.extra)&&(0,r.iterateExtraOptions)(t.extra,"",new WeakSet,((t,r)=>{const o=(0,i.allocWasmString)(t,a),s=(0,i.allocWasmString)(r,a);if(0!==e._OrtAddRunConfigEntry(n,o,s))throw new Error(`Can't set a run config entry: ${t} - ${r}`)})),[n,a]}catch(t){throw 0!==n&&e._OrtReleaseRunOptions(n),a.forEach(e._free),t}}},2306:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.OnnxruntimeWebAssemblySessionHandler=void 0;const r=n(2806),i=n(8453),o=n(2850),a=n(2157);let s;e.OnnxruntimeWebAssemblySessionHandler=class{async createSessionAllocate(t){const e=await fetch(t),n=await e.arrayBuffer();return(0,a.createSessionAllocate)(new Uint8Array(n))}async loadModel(t,e){if(s||(await(0,a.initOrt)(i.env.wasm.numThreads,(t=>{switch(t){case"verbose":return 0;case"info":return 1;case"warning":return 2;case"error":return 3;case"fatal":return 4;default:throw new Error(`unsupported logging level: ${t}`)}})(i.env.logLevel)),s=!0),"string"==typeof t)if("undefined"==typeof fetch){const n=await(0,o.promisify)(r.readFile)(t);[this.sessionId,this.inputNames,this.outputNames]=await(0,a.createSession)(n,e)}else{const n=await this.createSessionAllocate(t);[this.sessionId,this.inputNames,this.outputNames]=await(0,a.createSessionFinalize)(n,e)}else[this.sessionId,this.inputNames,this.outputNames]=await(0,a.createSession)(t,e)}async dispose(){return(0,a.releaseSession)(this.sessionId)}async run(t,e,n){const r=[],o=[];Object.entries(t).forEach((t=>{const e=t[0],n=t[1],i=this.inputNames.indexOf(e);if(-1===i)throw new Error(`invalid input '${e}'`);r.push(n),o.push(i)}));const s=[];Object.entries(e).forEach((t=>{const e=t[0],n=this.outputNames.indexOf(e);if(-1===n)throw new Error(`invalid output '${e}'`);s.push(n)}));const u=await(0,a.run)(this.sessionId,o,r.map((t=>[t.type,t.dims,t.data])),s,n),c={};for(let t=0;t<u.length;t++)c[this.outputNames[s[t]]]=new i.Tensor(u[t][0],u[t][2],u[t][1]);return c}startProfiling(){}endProfiling(){(0,a.endProfiling)(this.sessionId)}}},4919:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.setSessionOptions=void 0;const r=n(7967),i=n(4983),o=n(6361);e.setSessionOptions=t=>{const e=(0,o.getInstance)();let n=0;const a=[],s=t||{};(t=>{t.extra||(t.extra={}),t.extra.session||(t.extra.session={});const e=t.extra.session;e.use_ort_model_bytes_directly||(e.use_ort_model_bytes_directly="1")})(s);try{void 0===(null==t?void 0:t.graphOptimizationLevel)&&(s.graphOptimizationLevel="all");const u=(t=>{switch(t){case"disabled":return 0;case"basic":return 1;case"extended":return 2;case"all":return 99;default:throw new Error(`unsupported graph optimization level: ${t}`)}})(s.graphOptimizationLevel);void 0===(null==t?void 0:t.enableCpuMemArena)&&(s.enableCpuMemArena=!0),void 0===(null==t?void 0:t.enableMemPattern)&&(s.enableMemPattern=!0),void 0===(null==t?void 0:t.executionMode)&&(s.executionMode="sequential");const c=(t=>{switch(t){case"sequential":return 0;case"parallel":return 1;default:throw new Error(`unsupported execution mode: ${t}`)}})(s.executionMode);let l=0;if(void 0!==(null==t?void 0:t.logId)&&(l=(0,i.allocWasmString)(t.logId,a)),void 0===(null==t?void 0:t.logSeverityLevel))s.logSeverityLevel=2;else if("number"!=typeof t.logSeverityLevel||!Number.isInteger(t.logSeverityLevel)||t.logSeverityLevel<0||t.logSeverityLevel>4)throw new Error(`log serverity level is not valid: ${t.logSeverityLevel}`);if(void 0===(null==t?void 0:t.logVerbosityLevel))s.logVerbosityLevel=0;else if("number"!=typeof t.logVerbosityLevel||!Number.isInteger(t.logVerbosityLevel))throw new Error(`log verbosity level is not valid: ${t.logVerbosityLevel}`);if(void 0===(null==t?void 0:t.enableProfiling)&&(s.enableProfiling=!1),n=e._OrtCreateSessionOptions(u,!!s.enableCpuMemArena,!!s.enableMemPattern,c,!!s.enableProfiling,0,l,s.logSeverityLevel,s.logVerbosityLevel),0===n)throw new Error("Can't create session options");return(null==t?void 0:t.executionProviders)&&((t,e,n)=>{for(const r of e){let e="string"==typeof r?r:r.name;switch(e){case"xnnpack":e="XNNPACK";break;case"wasm":case"cpu":continue;default:throw new Error(`not supported EP: ${e}`)}const a=(0,i.allocWasmString)(e,n);if(0!==(0,o.getInstance)()._OrtAppendExecutionProvider(t,a))throw new Error(`Can't append execution provider: ${e}`)}})(n,t.executionProviders,a),void 0!==(null==t?void 0:t.extra)&&(0,r.iterateExtraOptions)(t.extra,"",new WeakSet,((t,r)=>{const o=(0,i.allocWasmString)(t,a),s=(0,i.allocWasmString)(r,a);if(0!==e._OrtAddSessionConfigEntry(n,o,s))throw new Error(`Can't set a session config entry: ${t} - ${r}`)})),[n,a]}catch(t){throw 0!==n&&e._OrtReleaseSessionOptions(n),a.forEach(e._free),t}}},4983:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.allocWasmString=void 0;const r=n(6361);e.allocWasmString=(t,e)=>{const n=(0,r.getInstance)(),i=n.lengthBytesUTF8(t)+1,o=n._malloc(i);return n.stringToUTF8(t,o,i),e.push(o),o}},349:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.extractTransferableBuffers=e.endProfiling=e.run=e.releaseSession=e.createSession=e.createSessionFinalize=e.createSessionAllocate=e.initOrt=void 0;const r=n(586),i=n(4919),o=n(4983),a=n(6361);e.initOrt=(t,e)=>{const n=(0,a.getInstance)()._OrtInit(t,e);if(0!==n)throw new Error(`Can't initialize onnxruntime. error code = ${n}`)};const s=new Map;e.createSessionAllocate=t=>{const e=(0,a.getInstance)(),n=e._malloc(t.byteLength);return e.HEAPU8.set(t,n),[n,t.byteLength]},e.createSessionFinalize=(t,e)=>{const n=(0,a.getInstance)();let r=0,o=0,u=[];try{if([o,u]=(0,i.setSessionOptions)(e),r=n._OrtCreateSession(t[0],t[1],o),0===r)throw new Error("Can't create a session")}finally{n._free(t[0]),n._OrtReleaseSessionOptions(o),u.forEach(n._free)}const c=n._OrtGetInputCount(r),l=n._OrtGetOutputCount(r),p=[],f=[],d=[],h=[];for(let t=0;t<c;t++){const e=n._OrtGetInputName(r,t);if(0===e)throw new Error("Can't get an input name");f.push(e),p.push(n.UTF8ToString(e))}for(let t=0;t<l;t++){const e=n._OrtGetOutputName(r,t);if(0===e)throw new Error("Can't get an output name");h.push(e),d.push(n.UTF8ToString(e))}return s.set(r,[r,f,h]),[r,p,d]},e.createSession=(t,n)=>{const r=(0,e.createSessionAllocate)(t);return(0,e.createSessionFinalize)(r,n)},e.releaseSession=t=>{const e=(0,a.getInstance)(),n=s.get(t);if(!n)throw new Error("invalid session id");const r=n[0],i=n[1],o=n[2];i.forEach(e._OrtFree),o.forEach(e._OrtFree),e._OrtReleaseSession(r),s.delete(t)};const u=t=>{switch(t){case"int8":return 3;case"uint8":return 2;case"bool":return 9;case"int16":return 5;case"uint16":return 4;case"int32":return 6;case"uint32":return 12;case"float32":return 1;case"float64":return 11;case"string":return 8;case"int64":return 7;case"uint64":return 13;default:throw new Error(`unsupported data type: ${t}`)}},c=t=>{switch(t){case 3:return"int8";case 2:return"uint8";case 9:return"bool";case 5:return"int16";case 4:return"uint16";case 6:return"int32";case 12:return"uint32";case 1:return"float32";case 11:return"float64";case 8:return"string";case 7:return"int64";case 13:return"uint64";default:throw new Error(`unsupported data type: ${t}`)}},l=t=>{switch(t){case"float32":return Float32Array;case"uint8":case"bool":return Uint8Array;case"int8":return Int8Array;case"uint16":return Uint16Array;case"int16":return Int16Array;case"int32":return Int32Array;case"float64":return Float64Array;case"uint32":return Uint32Array;case"int64":return BigInt64Array;case"uint64":return BigUint64Array;default:throw new Error(`unsupported type: ${t}`)}};e.run=(t,e,n,i,p)=>{const f=(0,a.getInstance)(),d=s.get(t);if(!d)throw new Error("invalid session id");const h=d[0],g=d[1],b=d[2],m=e.length,y=i.length;let _=0,v=[];const w=[],x=[];try{[_,v]=(0,r.setRunOptions)(p);for(let t=0;t<m;t++){const e=n[t][0],r=n[t][1],i=n[t][2];let a,s;if(Array.isArray(i)){s=4*i.length,a=f._malloc(s),x.push(a);let t=a/4;for(let e=0;e<i.length;e++){if("string"!=typeof i[e])throw new TypeError(`tensor data at index ${e} is not a string`);f.HEAPU32[t++]=(0,o.allocWasmString)(i[e],x)}}else s=i.byteLength,a=f._malloc(s),x.push(a),f.HEAPU8.set(new Uint8Array(i.buffer,i.byteOffset,s),a);const c=f.stackSave(),l=f.stackAlloc(4*r.length);try{let t=l/4;r.forEach((e=>f.HEAP32[t++]=e));const n=f._OrtCreateTensor(u(e),a,s,l,r.length);if(0===n)throw new Error("Can't create a tensor");w.push(n)}finally{f.stackRestore(c)}}const t=f.stackSave(),a=f.stackAlloc(4*m),s=f.stackAlloc(4*m),d=f.stackAlloc(4*y),T=f.stackAlloc(4*y);try{let n=a/4,r=s/4,o=d/4,u=T/4;for(let t=0;t<m;t++)f.HEAPU32[n++]=w[t],f.HEAPU32[r++]=g[e[t]];for(let t=0;t<y;t++)f.HEAPU32[o++]=0,f.HEAPU32[u++]=b[i[t]];let p=f._OrtRun(h,s,a,m,T,y,d,_);const v=[];if(0===p)for(let t=0;t<y;t++){const e=f.HEAPU32[d/4+t],n=f.stackSave(),r=f.stackAlloc(16);let i,o=0;try{if(p=f._OrtGetTensorData(e,r,r+4,r+8,r+12),0!==p)throw new Error(`Can't access output tensor data. error code = ${p}`);let t=r/4;const a=f.HEAPU32[t++];o=f.HEAPU32[t++];const s=f.HEAPU32[t++],u=f.HEAPU32[t++],d=[];for(let t=0;t<u;t++)d.push(f.HEAPU32[s/4+t]);f._OrtFree(s);const h=0===d.length?1:d.reduce(((t,e)=>t*e));if(i=c(a),"string"===i){const t=[];let e=o/4;for(let n=0;n<h;n++){const r=f.HEAPU32[e++],i=n===h-1?void 0:f.HEAPU32[e]-r;t.push(f.UTF8ToString(r,i))}v.push([i,d,t])}else{const t=new(l(i))(h);new Uint8Array(t.buffer,t.byteOffset,t.byteLength).set(f.HEAPU8.subarray(o,o+t.byteLength)),v.push([i,d,t])}}finally{f.stackRestore(n),"string"===i&&o&&f._free(o),f._OrtReleaseTensor(e)}}if(0===p)return v;throw new Error(`failed to call OrtRun(). error code = ${p}.`)}finally{f.stackRestore(t)}}finally{w.forEach(f._OrtReleaseTensor),x.forEach(f._free),f._OrtReleaseRunOptions(_),v.forEach(f._free)}},e.endProfiling=t=>{const e=(0,a.getInstance)(),n=s.get(t);if(!n)throw new Error("invalid session id");const r=n[0],i=e._OrtEndProfiling(r);if(0===i)throw new Error("Can't get an profile file name");e._OrtFree(i)},e.extractTransferableBuffers=t=>{const e=[];for(const n of t){const t=n[2];!Array.isArray(t)&&t.buffer&&e.push(t.buffer)}return e}},6361:function(t,e,n){"use strict";var r=this&&this.__createBinding||(Object.create?function(t,e,n,r){void 0===r&&(r=n);var i=Object.getOwnPropertyDescriptor(e,n);i&&!("get"in i?!e.__esModule:i.writable||i.configurable)||(i={enumerable:!0,get:function(){return e[n]}}),Object.defineProperty(t,r,i)}:function(t,e,n,r){void 0===r&&(r=n),t[r]=e[n]}),i=this&&this.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),o=this&&this.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)"default"!==n&&Object.prototype.hasOwnProperty.call(t,n)&&r(e,t,n);return i(e,t),e},a=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.dispose=e.getInstance=e.initializeWebAssembly=void 0;const s=o(n(6449)),u=a(n(932)),c=n(3474);let l,p=!1,f=!1,d=!1;const h=(t,e)=>e?t?"ort-wasm-simd-threaded.wasm":"ort-wasm-threaded.wasm":t?"ort-wasm-simd.wasm":"ort-wasm.wasm";e.initializeWebAssembly=async t=>{if(p)return Promise.resolve();if(f)throw new Error("multiple calls to 'initializeWebAssembly()' detected.");if(d)throw new Error("previous call to 'initializeWebAssembly()' failed.");f=!0;const e=t.initTimeout,r=t.numThreads,i=t.simd,o=r>1&&(()=>{try{return"undefined"!=typeof SharedArrayBuffer&&("undefined"!=typeof MessageChannel&&(new MessageChannel).port1.postMessage(new SharedArrayBuffer(1)),WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,5,4,1,3,1,1,10,11,1,9,0,65,0,254,16,2,0,26,11])))}catch(t){return!1}})(),a=i&&(()=>{try{return WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,10,30,1,28,0,65,0,253,15,253,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,253,186,1,26,11]))}catch(t){return!1}})(),g="string"==typeof t.wasmPaths?t.wasmPaths:void 0,b=h(!1,o),m=h(a,o),y="object"==typeof t.wasmPaths?t.wasmPaths[m]:void 0;let _=!1;const v=[];if(e>0&&v.push(new Promise((t=>{setTimeout((()=>{_=!0,t()}),e)}))),v.push(new Promise(((t,e)=>{const r=o?c:u.default,i={locateFile:(t,e)=>o&&t.endsWith(".worker.js")&&"undefined"!=typeof Blob?URL.createObjectURL(new Blob([n(4154)],{type:"text/javascript"})):t===b?null!=y?y:(null!=g?g:e)+m:e+t};if(o)if("undefined"==typeof Blob)i.mainScriptUrlOrBlob=s.join("/","ort-wasm-threaded.js");else{const t=`var ortWasmThreaded=(function(){var _scriptDir;return ${r.toString()}})();`;i.mainScriptUrlOrBlob=new Blob([t],{type:"text/javascript"})}r(i).then((e=>{f=!1,p=!0,l=e,t()}),(t=>{f=!1,d=!0,e(t)}))}))),await Promise.race(v),_)throw new Error(`WebAssembly backend initializing failed due to timeout: ${e}ms`)},e.getInstance=()=>{if(p&&l)return l;throw new Error("WebAssembly is not initialized yet.")},e.dispose=()=>{var t;!p||f||d||(f=!0,null===(t=l.PThread)||void 0===t||t.terminateAllThreads(),l=void 0,f=!1,p=!1,d=!0)}},9710:(t,e,n)=>{"use strict";n.d(e,{Z:()=>o});var r=n(477),i=n.n(r);function o(){return i()('/*!\n* ONNX Runtime Web v1.14.0\n* Copyright (c) Microsoft Corporation. All rights reserved.\n* Licensed under the MIT License.\n*/\n(()=>{var t={474:(t,e,n)=>{var _scriptDir,r=(_scriptDir=(_scriptDir="undefined"!=typeof document&&document.currentScript?document.currentScript.src:void 0)||"/index.js",function(t){function e(){return j.buffer!=D&&N(j.buffer),P}function r(){return j.buffer!=D&&N(j.buffer),U}function a(){return j.buffer!=D&&N(j.buffer),F}function i(){return j.buffer!=D&&N(j.buffer),I}function o(){return j.buffer!=D&&N(j.buffer),W}var u,c,s;t=t||{},u||(u=void 0!==t?t:{}),u.ready=new Promise((function(t,e){c=t,s=e}));var l,f,p,h,d,y,b=Object.assign({},u),m="./this.program",g=(t,e)=>{throw e},v="object"==typeof window,w="function"==typeof importScripts,_="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node,O=u.ENVIRONMENT_IS_PTHREAD||!1,A="";function S(t){return u.locateFile?u.locateFile(t,A):A+t}if(_){let e;A=w?n(908).dirname(A)+"/":"//",y=()=>{d||(h=n(384),d=n(908))},l=function(t,e){return y(),t=d.normalize(t),h.readFileSync(t,e?void 0:"utf8")},p=t=>((t=l(t,!0)).buffer||(t=new Uint8Array(t)),t),f=(t,e,n)=>{y(),t=d.normalize(t),h.readFile(t,(function(t,r){t?n(t):e(r.buffer)}))},1<process.argv.length&&(m=process.argv[1].replace(/\\\\/g,"/")),process.argv.slice(2),process.on("uncaughtException",(function(t){if(!(t instanceof ct))throw t})),process.on("unhandledRejection",(function(t){throw t})),g=(t,e)=>{if(Q())throw process.exitCode=t,e;e instanceof ct||x("exiting due to exception: "+e),process.exit(t)},u.inspect=function(){return"[Emscripten Module object]"};try{e=n(925)}catch(t){throw console.error(\'The "worker_threads" module is not supported in this node.js build - perhaps a newer version is needed?\'),t}n.g.Worker=e.Worker}else(v||w)&&(w?A=self.location.href:"undefined"!=typeof document&&document.currentScript&&(A=document.currentScript.src),_scriptDir&&(A=_scriptDir),A=0!==A.indexOf("blob:")?A.substr(0,A.replace(/[?#].*/,"").lastIndexOf("/")+1):"",_||(l=t=>{var e=new XMLHttpRequest;return e.open("GET",t,!1),e.send(null),e.responseText},w&&(p=t=>{var e=new XMLHttpRequest;return e.open("GET",t,!1),e.responseType="arraybuffer",e.send(null),new Uint8Array(e.response)}),f=(t,e,n)=>{var r=new XMLHttpRequest;r.open("GET",t,!0),r.responseType="arraybuffer",r.onload=()=>{200==r.status||0==r.status&&r.response?e(r.response):n()},r.onerror=n,r.send(null)}));_&&"undefined"==typeof performance&&(n.g.performance=n(953).performance);var T=console.log.bind(console),E=console.warn.bind(console);_&&(y(),T=t=>h.writeSync(1,t+"\\n"),E=t=>h.writeSync(2,t+"\\n"));var M,C=u.print||T,x=u.printErr||E;Object.assign(u,b),b=null,u.thisProgram&&(m=u.thisProgram),u.quit&&(g=u.quit),u.wasmBinary&&(M=u.wasmBinary);var R=u.noExitRuntime||!1;"object"!=typeof WebAssembly&&at("no native wasm support detected");var j,k,D,P,U,F,I,W,H=!1,L="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0;function z(t,e,n){var r=(e>>>=0)+n;for(n=e;t[n]&&!(n>=r);)++n;if(16<n-e&&t.buffer&&L)return L.decode(t.buffer instanceof SharedArrayBuffer?t.slice(e,n):t.subarray(e,n));for(r="";e<n;){var a=t[e++];if(128&a){var i=63&t[e++];if(192==(224&a))r+=String.fromCharCode((31&a)<<6|i);else{var o=63&t[e++];65536>(a=224==(240&a)?(15&a)<<12|i<<6|o:(7&a)<<18|i<<12|o<<6|63&t[e++])?r+=String.fromCharCode(a):(a-=65536,r+=String.fromCharCode(55296|a>>10,56320|1023&a))}}else r+=String.fromCharCode(a)}return r}function Y(t,e){return(t>>>=0)?z(r(),t,e):""}function B(t,e,n,r){if(!(0<r))return 0;var a=n>>>=0;r=n+r-1;for(var i=0;i<t.length;++i){var o=t.charCodeAt(i);if(55296<=o&&57343>=o&&(o=65536+((1023&o)<<10)|1023&t.charCodeAt(++i)),127>=o){if(n>=r)break;e[n++>>>0]=o}else{if(2047>=o){if(n+1>=r)break;e[n++>>>0]=192|o>>6}else{if(65535>=o){if(n+2>=r)break;e[n++>>>0]=224|o>>12}else{if(n+3>=r)break;e[n++>>>0]=240|o>>18,e[n++>>>0]=128|o>>12&63}e[n++>>>0]=128|o>>6&63}e[n++>>>0]=128|63&o}}return e[n>>>0]=0,n-a}function G(t){for(var e=0,n=0;n<t.length;++n){var r=t.charCodeAt(n);127>=r?e++:2047>=r?e+=2:55296<=r&&57343>=r?(e+=4,++n):e+=3}return e}function N(t){D=t,u.HEAP8=P=new Int8Array(t),u.HEAP16=new Int16Array(t),u.HEAP32=F=new Int32Array(t),u.HEAPU8=U=new Uint8Array(t),u.HEAPU16=new Uint16Array(t),u.HEAPU32=I=new Uint32Array(t),u.HEAPF32=new Float32Array(t),u.HEAPF64=W=new Float64Array(t)}O&&(D=u.buffer);var V=u.INITIAL_MEMORY||16777216;if(O)j=u.wasmMemory,D=u.buffer;else if(u.wasmMemory)j=u.wasmMemory;else if(!((j=new WebAssembly.Memory({initial:V/65536,maximum:65536,shared:!0})).buffer instanceof SharedArrayBuffer))throw x("requested a shared WebAssembly.Memory but the returned buffer is not a SharedArrayBuffer, indicating that while the browser has SharedArrayBuffer it does not have WebAssembly threads support - you may need to set a flag"),_&&console.log("(on node you may need: --experimental-wasm-threads --experimental-wasm-bulk-memory and also use a recent version)"),Error("bad memory");j&&(D=j.buffer),V=D.byteLength,N(D);var $,q=[],X=[],J=[],Z=[];function Q(){return R||!1}function K(){var t=u.preRun.shift();q.unshift(t)}var tt,et=0,nt=null,rt=null;function at(t){throw O?postMessage({cmd:"onAbort",arg:t}):u.onAbort&&u.onAbort(t),x(t="Aborted("+t+")"),H=!0,t=new WebAssembly.RuntimeError(t+". Build with -sASSERTIONS for more info."),s(t),t}function it(){return tt.startsWith("data:application/octet-stream;base64,")}function ot(){var t=tt;try{if(t==tt&&M)return new Uint8Array(M);if(p)return p(t);throw"both async and sync fetching of the wasm failed"}catch(t){at(t)}}tt="ort-wasm-threaded.wasm",it()||(tt=S(tt));var ut={};function ct(t){this.name="ExitStatus",this.message="Program terminated with exit("+t+")",this.status=t}function st(t){(t=ht.Vb[t])||at(),ht.mc(t)}function lt(t){var e=ht.Cc();if(!e)return 6;ht.ac.push(e),ht.Vb[t.Ub]=e,e.Ub=t.Ub;var n={cmd:"run",start_routine:t.Ic,arg:t.zc,pthread_ptr:t.Ub};return e.$b=()=>{n.time=performance.now(),e.postMessage(n,t.Nc)},e.loaded&&(e.$b(),delete e.$b),0}function ft(t){if(O)return $t(1,1,t);Q()||(ht.oc(),u.onExit&&u.onExit(t),H=!0),g(t,new ct(t))}function pt(t,e){if(!e&&O)throw bt(t),"unwind";Q()||O||(me(),dt(J),be(0),re[1].length&&ae(1,10),re[2].length&&ae(2,10),ht.oc()),ft(t)}var ht={Yb:[],ac:[],qc:[],Vb:{},fc:function(){O&&ht.Ec()},Pc:function(){},Ec:function(){ht.receiveObjectTransfer=ht.Gc,ht.threadInitTLS=ht.pc,ht.setExitStatus=ht.nc,R=!1},nc:function(){},oc:function(){for(var t of Object.values(ht.Vb))ht.mc(t);for(t of ht.Yb)t.terminate();ht.Yb=[]},mc:function(t){var e=t.Ub;delete ht.Vb[e],ht.Yb.push(t),ht.ac.splice(ht.ac.indexOf(t),1),t.Ub=0,Oe(e)},Gc:function(){},pc:function(){ht.qc.forEach((t=>t()))},Fc:function(t,e){t.onmessage=n=>{var r=(n=n.data).cmd;if(t.Ub&&(ht.Bc=t.Ub),n.targetThread&&n.targetThread!=he()){var a=ht.Vb[n.Qc];a?a.postMessage(n,n.transferList):x(\'Internal error! Worker sent a message "\'+r+\'" to target pthread \'+n.targetThread+", but that thread no longer exists!")}else"processProxyingQueue"===r?zt(n.queue):"spawnThread"===r?lt(n):"cleanupThread"===r?st(n.thread):"killThread"===r?(n=n.thread,r=ht.Vb[n],delete ht.Vb[n],r.terminate(),Oe(n),ht.ac.splice(ht.ac.indexOf(r),1),r.Ub=0):"cancelThread"===r?ht.Vb[n.thread].postMessage({cmd:"cancel"}):"loaded"===r?(t.loaded=!0,e&&e(t),t.$b&&(t.$b(),delete t.$b)):"print"===r?C("Thread "+n.threadId+": "+n.text):"printErr"===r?x("Thread "+n.threadId+": "+n.text):"alert"===r?alert("Thread "+n.threadId+": "+n.text):"setimmediate"===n.target?t.postMessage(n):"onAbort"===r?u.onAbort&&u.onAbort(n.arg):r&&x("worker sent an unknown command "+r);ht.Bc=void 0},t.onerror=t=>{throw x("worker sent an error! "+t.filename+":"+t.lineno+": "+t.message),t},_&&(t.on("message",(function(e){t.onmessage({data:e})})),t.on("error",(function(e){t.onerror(e)})),t.on("detachedExit",(function(){}))),t.postMessage({cmd:"load",urlOrBlob:u.mainScriptUrlOrBlob||_scriptDir,wasmMemory:j,wasmModule:k})},yc:function(){var t=S("ort-wasm-threaded.worker.js");ht.Yb.push(new Worker(t))},Cc:function(){return 0==ht.Yb.length&&(ht.yc(),ht.Fc(ht.Yb[0])),ht.Yb.pop()}};function dt(t){for(;0<t.length;)t.shift()(u)}function yt(t){var e=Ee();return t=t(),Me(e),t}function bt(t){if(O)return $t(2,0,t);try{pt(t)}catch(t){t instanceof ct||"unwind"==t||g(1,t)}}u.PThread=ht,u.establishStackSpace=function(){var t=he(),e=a()[t+44>>2>>>0];t=a()[t+48>>2>>>0],Te(e,e-t),Me(e)};var mt=[];function gt(t){var e=mt[t];return e||(t>=mt.length&&(mt.length=t+1),mt[t]=e=$.get(t)),e}u.invokeEntryPoint=function(t,e){t=gt(t)(e),Q()?ht.nc(t):Ae(t)};var vt,wt,_t=[],Ot=0,At=0;function St(t){this.Zb=t,this.Sb=t-24,this.xc=function(t){i()[this.Sb+4>>2>>>0]=t},this.bc=function(){return i()[this.Sb+4>>2>>>0]},this.wc=function(t){i()[this.Sb+8>>2>>>0]=t},this.Dc=function(){return i()[this.Sb+8>>2>>>0]},this.rc=function(){a()[this.Sb>>2>>>0]=0},this.hc=function(t){t=t?1:0,e()[this.Sb+12>>0>>>0]=t},this.uc=function(){return 0!=e()[this.Sb+12>>0>>>0]},this.ic=function(t){t=t?1:0,e()[this.Sb+13>>0>>>0]=t},this.kc=function(){return 0!=e()[this.Sb+13>>0>>>0]},this.fc=function(t,e){this.cc(0),this.xc(t),this.wc(e),this.rc(),this.hc(!1),this.ic(!1)},this.sc=function(){Atomics.add(a(),this.Sb>>2,1)},this.Hc=function(){return 1===Atomics.sub(a(),this.Sb>>2,1)},this.cc=function(t){i()[this.Sb+16>>2>>>0]=t},this.tc=function(){return i()[this.Sb+16>>2>>>0]},this.vc=function(){if(Re(this.bc()))return i()[this.Zb>>2>>>0];var t=this.tc();return 0!==t?t:this.Zb}}function Tt(t){return ye(new St(t).Sb)}function Et(t,e,n,r){return O?$t(3,1,t,e,n,r):Mt(t,e,n,r)}function Mt(t,e,n,r){if("undefined"==typeof SharedArrayBuffer)return x("Current environment does not support SharedArrayBuffer, pthreads are not available!"),6;var a=[];return O&&0===a.length?Et(t,e,n,r):(t={Ic:n,Ub:t,zc:r,Nc:a},O?(t.Oc="spawnThread",postMessage(t,a),0):lt(t))}function Ct(t,e,n){return O?$t(4,1,t,e,n):0}function xt(t,e){if(O)return $t(5,1,t,e)}function Rt(t,e){if(O)return $t(6,1,t,e)}function jt(t,e,n){if(O)return $t(7,1,t,e,n)}function kt(t,e,n){return O?$t(8,1,t,e,n):0}function Dt(t,e){if(O)return $t(9,1,t,e)}function Pt(t,e,n){if(O)return $t(10,1,t,e,n)}function Ut(t,e,n,r){if(O)return $t(11,1,t,e,n,r)}function Ft(t,e,n,r){if(O)return $t(12,1,t,e,n,r)}function It(t,e,n,r){if(O)return $t(13,1,t,e,n,r)}function Wt(t){if(O)return $t(14,1,t)}function Ht(t,e){if(O)return $t(15,1,t,e)}function Lt(t,e,n){if(O)return $t(16,1,t,e,n)}function zt(t){Atomics.store(a(),t>>2,1),he()&&_e(t),Atomics.compareExchange(a(),t>>2,1,0)}function Yt(t){return i()[t>>>2]+4294967296*a()[t+4>>>2]}function Bt(t,e,n,r,a,i){return O?$t(17,1,t,e,n,r,a,i):-52}function Gt(t,e,n,r,a,i){if(O)return $t(18,1,t,e,n,r,a,i)}function Nt(t){var n=G(t)+1,r=de(n);return r&&B(t,e(),r,n),r}function Vt(t,e,n){function r(t){return(t=t.toTimeString().match(/\\(([A-Za-z ]+)\\)$/))?t[1]:"GMT"}if(O)return $t(19,1,t,e,n);var o=(new Date).getFullYear(),u=new Date(o,0,1),c=new Date(o,6,1);o=u.getTimezoneOffset();var s=c.getTimezoneOffset(),l=Math.max(o,s);a()[t>>2>>>0]=60*l,a()[e>>2>>>0]=Number(o!=s),t=r(u),e=r(c),t=Nt(t),e=Nt(e),s<o?(i()[n>>2>>>0]=t,i()[n+4>>2>>>0]=e):(i()[n>>2>>>0]=e,i()[n+4>>2>>>0]=t)}function $t(t,e){var n=arguments.length-2,r=arguments;return yt((()=>{for(var a=Ce(8*n),i=a>>3,u=0;u<n;u++){var c=r[2+u];o()[i+u>>>0]=c}return we(t,n,a,e)}))}u.executeNotifiedProxyingQueue=zt,wt=_?()=>{var t=process.hrtime();return 1e3*t[0]+t[1]/1e6}:O?()=>performance.now()-u.__performance_now_clock_drift:()=>performance.now();var qt,Xt=[],Jt={};function Zt(){if(!qt){var t,e={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:m||"./this.program"};for(t in Jt)void 0===Jt[t]?delete e[t]:e[t]=Jt[t];var n=[];for(t in e)n.push(t+"="+e[t]);qt=n}return qt}function Qt(t,n){if(O)return $t(20,1,t,n);var r=0;return Zt().forEach((function(a,o){var u=n+r;for(o=i()[t+4*o>>2>>>0]=u,u=0;u<a.length;++u)e()[o++>>0>>>0]=a.charCodeAt(u);e()[o>>0>>>0]=0,r+=a.length+1})),0}function Kt(t,e){if(O)return $t(21,1,t,e);var n=Zt();i()[t>>2>>>0]=n.length;var r=0;return n.forEach((function(t){r+=t.length+1})),i()[e>>2>>>0]=r,0}function te(t){return O?$t(22,1,t):52}function ee(t,e,n,r){return O?$t(23,1,t,e,n,r):52}function ne(t,e,n,r,a){return O?$t(24,1,t,e,n,r,a):70}var re=[null,[],[]];function ae(t,e){var n=re[t];0===e||10===e?((1===t?C:x)(z(n,0)),n.length=0):n.push(e)}function ie(t,e,n,a){if(O)return $t(25,1,t,e,n,a);for(var o=0,u=0;u<n;u++){var c=i()[e>>2>>>0],s=i()[e+4>>2>>>0];e+=8;for(var l=0;l<s;l++)ae(t,r()[c+l>>>0]);o+=s}return i()[a>>2>>>0]=o,0}var oe=0;function ue(t){return 0==t%4&&(0!=t%100||0==t%400)}var ce=[31,29,31,30,31,30,31,31,30,31,30,31],se=[31,28,31,30,31,30,31,31,30,31,30,31];function le(t,n,r,i){function o(t,e,n){for(t="number"==typeof t?t.toString():t||"";t.length<e;)t=n[0]+t;return t}function u(t,e){return o(t,e,"0")}function c(t,e){function n(t){return 0>t?-1:0<t?1:0}var r;return 0===(r=n(t.getFullYear()-e.getFullYear()))&&0===(r=n(t.getMonth()-e.getMonth()))&&(r=n(t.getDate()-e.getDate())),r}function s(t){switch(t.getDay()){case 0:return new Date(t.getFullYear()-1,11,29);case 1:return t;case 2:return new Date(t.getFullYear(),0,3);case 3:return new Date(t.getFullYear(),0,2);case 4:return new Date(t.getFullYear(),0,1);case 5:return new Date(t.getFullYear()-1,11,31);case 6:return new Date(t.getFullYear()-1,11,30)}}function l(t){var e=t.Wb;for(t=new Date(new Date(t.Xb+1900,0,1).getTime());0<e;){var n=t.getMonth(),r=(ue(t.getFullYear())?ce:se)[n];if(!(e>r-t.getDate())){t.setDate(t.getDate()+e);break}e-=r-t.getDate()+1,t.setDate(1),11>n?t.setMonth(n+1):(t.setMonth(0),t.setFullYear(t.getFullYear()+1))}return n=new Date(t.getFullYear()+1,0,4),e=s(new Date(t.getFullYear(),0,4)),n=s(n),0>=c(e,t)?0>=c(n,t)?t.getFullYear()+1:t.getFullYear():t.getFullYear()-1}var f=a()[i+40>>2>>>0];for(var p in i={Lc:a()[i>>2>>>0],Kc:a()[i+4>>2>>>0],dc:a()[i+8>>2>>>0],jc:a()[i+12>>2>>>0],ec:a()[i+16>>2>>>0],Xb:a()[i+20>>2>>>0],Tb:a()[i+24>>2>>>0],Wb:a()[i+28>>2>>>0],Rc:a()[i+32>>2>>>0],Jc:a()[i+36>>2>>>0],Mc:f?Y(f):""},r=Y(r),f={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"})r=r.replace(new RegExp(p,"g"),f[p]);var h="Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),d="January February March April May June July August September October November December".split(" ");for(p in f={"%a":function(t){return h[t.Tb].substring(0,3)},"%A":function(t){return h[t.Tb]},"%b":function(t){return d[t.ec].substring(0,3)},"%B":function(t){return d[t.ec]},"%C":function(t){return u((t.Xb+1900)/100|0,2)},"%d":function(t){return u(t.jc,2)},"%e":function(t){return o(t.jc,2," ")},"%g":function(t){return l(t).toString().substring(2)},"%G":function(t){return l(t)},"%H":function(t){return u(t.dc,2)},"%I":function(t){return 0==(t=t.dc)?t=12:12<t&&(t-=12),u(t,2)},"%j":function(t){for(var e=0,n=0;n<=t.ec-1;e+=(ue(t.Xb+1900)?ce:se)[n++]);return u(t.jc+e,3)},"%m":function(t){return u(t.ec+1,2)},"%M":function(t){return u(t.Kc,2)},"%n":function(){return"\\n"},"%p":function(t){return 0<=t.dc&&12>t.dc?"AM":"PM"},"%S":function(t){return u(t.Lc,2)},"%t":function(){return"\\t"},"%u":function(t){return t.Tb||7},"%U":function(t){return u(Math.floor((t.Wb+7-t.Tb)/7),2)},"%V":function(t){var e=Math.floor((t.Wb+7-(t.Tb+6)%7)/7);if(2>=(t.Tb+371-t.Wb-2)%7&&e++,e)53==e&&(4==(n=(t.Tb+371-t.Wb)%7)||3==n&&ue(t.Xb)||(e=1));else{e=52;var n=(t.Tb+7-t.Wb-1)%7;(4==n||5==n&&ue(t.Xb%400-1))&&e++}return u(e,2)},"%w":function(t){return t.Tb},"%W":function(t){return u(Math.floor((t.Wb+7-(t.Tb+6)%7)/7),2)},"%y":function(t){return(t.Xb+1900).toString().substring(2)},"%Y":function(t){return t.Xb+1900},"%z":function(t){var e=0<=(t=t.Jc);return t=Math.abs(t)/60,(e?"+":"-")+String("0000"+(t/60*100+t%60)).slice(-4)},"%Z":function(t){return t.Mc},"%%":function(){return"%"}},r=r.replace(/%%/g,"\\0\\0"),f)r.includes(p)&&(r=r.replace(new RegExp(p,"g"),f[p](i)));return p=function(t){var e=Array(G(t)+1);return B(t,e,0,e.length),e}(r=r.replace(/\\0\\0/g,"%")),p.length>n?0:(function(t,n){e().set(t,n>>>0)}(p,t),p.length-1)}ht.fc();var fe=[null,ft,bt,Et,Ct,xt,Rt,jt,kt,Dt,Pt,Ut,Ft,It,Wt,Ht,Lt,Bt,Gt,Vt,Qt,Kt,te,ee,ne,ie],pe={b:function(t){return de(t+24)+24},n:function(t){return(t=new St(t)).uc()||(t.hc(!0),Ot--),t.ic(!1),_t.push(t),t.sc(),t.vc()},ma:function(t){throw x("Unexpected exception thrown, this is not properly supported - aborting"),H=!0,t},x:function(){Se(0);var t=_t.pop();if(t.Hc()&&!t.kc()){var e=t.Dc();e&&gt(e)(t.Zb),Tt(t.Zb)}At=0},e:function(){var t=At;if(!t)return oe=0;var e=new St(t);e.cc(t);var n=e.bc();if(!n)return oe=0,t;for(var r=Array.prototype.slice.call(arguments),a=0;a<r.length;a++){var i=r[a];if(0===i||i===n)break;if(xe(i,n,e.Sb+16))return oe=i,t}return oe=n,t},l:function(){var t=At;if(!t)return oe=0;var e=new St(t);e.cc(t);var n=e.bc();if(!n)return oe=0,t;for(var r=Array.prototype.slice.call(arguments),a=0;a<r.length;a++){var i=r[a];if(0===i||i===n)break;if(xe(i,n,e.Sb+16))return oe=i,t}return oe=n,t},h:function(){var t=At;if(!t)return oe=0;var e=new St(t);e.cc(t);var n=e.bc();if(!n)return oe=0,t;for(var r=Array.prototype.slice.call(arguments),a=0;a<r.length;a++){var i=r[a];if(0===i||i===n)break;if(xe(i,n,e.Sb+16))return oe=i,t}return oe=n,t},t:Tt,M:function(){var t=_t.pop();t||at("no exception to throw");var e=t.Zb;throw t.kc()||(_t.push(t),t.ic(!0),t.hc(!1),Ot++),At=e,e},c:function(t,e,n){throw new St(t).fc(e,n),At=t,Ot++,t},pa:function(){return Ot},Fa:function(t){ge(t,!w,1,!v),ht.pc()},T:function(t){O?postMessage({cmd:"cleanupThread",thread:t}):st(t)},xa:Mt,j:function(t){throw At||(At=t),t},H:Ct,Ma:xt,ua:Rt,wa:jt,oa:kt,Ka:Dt,Ca:Pt,Ja:Ut,V:Ft,va:It,sa:Wt,La:Ht,ta:Lt,Ta:function(){},X:function(){at("To use dlopen, you need enable dynamic linking, see https://github.com/emscripten-core/emscripten/wiki/Linking")},Ua:function(){at("To use dlopen, you need enable dynamic linking, see https://github.com/emscripten-core/emscripten/wiki/Linking")},W:function(){return Date.now()},ya:function(){return 2097152},Oa:function(){return!0},za:function(t,e,n,r){if(t==e)setTimeout((()=>zt(r)));else if(O)postMessage({targetThread:t,cmd:"processProxyingQueue",queue:r});else{if(!(t=ht.Vb[t]))return;t.postMessage({cmd:"processProxyingQueue",queue:r})}return 1},Ea:function(){return-1},Pa:function(t,e){t=new Date(1e3*Yt(t)),a()[e>>2>>>0]=t.getUTCSeconds(),a()[e+4>>2>>>0]=t.getUTCMinutes(),a()[e+8>>2>>>0]=t.getUTCHours(),a()[e+12>>2>>>0]=t.getUTCDate(),a()[e+16>>2>>>0]=t.getUTCMonth(),a()[e+20>>2>>>0]=t.getUTCFullYear()-1900,a()[e+24>>2>>>0]=t.getUTCDay(),t=(t.getTime()-Date.UTC(t.getUTCFullYear(),0,1,0,0,0,0))/864e5|0,a()[e+28>>2>>>0]=t},Qa:function(t,e){t=new Date(1e3*Yt(t)),a()[e>>2>>>0]=t.getSeconds(),a()[e+4>>2>>>0]=t.getMinutes(),a()[e+8>>2>>>0]=t.getHours(),a()[e+12>>2>>>0]=t.getDate(),a()[e+16>>2>>>0]=t.getMonth(),a()[e+20>>2>>>0]=t.getFullYear()-1900,a()[e+24>>2>>>0]=t.getDay();var n=new Date(t.getFullYear(),0,1),r=(t.getTime()-n.getTime())/864e5|0;a()[e+28>>2>>>0]=r,a()[e+36>>2>>>0]=-60*t.getTimezoneOffset(),r=new Date(t.getFullYear(),6,1).getTimezoneOffset(),t=0|(r!=(n=n.getTimezoneOffset())&&t.getTimezoneOffset()==Math.min(n,r)),a()[e+32>>2>>>0]=t},Ra:function(t){var e=new Date(a()[t+20>>2>>>0]+1900,a()[t+16>>2>>>0],a()[t+12>>2>>>0],a()[t+8>>2>>>0],a()[t+4>>2>>>0],a()[t>>2>>>0],0),n=a()[t+32>>2>>>0],r=e.getTimezoneOffset(),i=new Date(e.getFullYear(),0,1),o=new Date(e.getFullYear(),6,1).getTimezoneOffset(),u=i.getTimezoneOffset(),c=Math.min(u,o);return 0>n?a()[t+32>>2>>>0]=Number(o!=u&&c==r):0<n!=(c==r)&&(o=Math.max(u,o),e.setTime(e.getTime()+6e4*((0<n?c:o)-r))),a()[t+24>>2>>>0]=e.getDay(),n=(e.getTime()-i.getTime())/864e5|0,a()[t+28>>2>>>0]=n,a()[t>>2>>>0]=e.getSeconds(),a()[t+4>>2>>>0]=e.getMinutes(),a()[t+8>>2>>>0]=e.getHours(),a()[t+12>>2>>>0]=e.getDate(),a()[t+16>>2>>>0]=e.getMonth(),e.getTime()/1e3|0},Aa:Bt,Ba:Gt,Sa:function t(e,n,r){t.Ac||(t.Ac=!0,Vt(e,n,r))},y:function(){at("")},U:function(){if(!_&&!w){var t="Blocking on the main thread is very dangerous, see https://emscripten.org/docs/porting/pthreads.html#blocking-on-the-main-browser-thread";vt||(vt={}),vt[t]||(vt[t]=1,_&&(t="warning: "+t),x(t))}},ra:function(){return 4294901760},B:wt,Ia:function(t,e,n){r().copyWithin(t>>>0,e>>>0,e+n>>>0)},F:function(){return _?n(993).cpus().length:navigator.hardwareConcurrency},Da:function(t,e,n){Xt.length=e,n>>=3;for(var r=0;r<e;r++)Xt[r]=o()[n+r>>>0];return(0>t?ut[-t-1]:fe[t]).apply(null,Xt)},qa:function(t){var e=r().length;if((t>>>=0)<=e||4294901760<t)return!1;for(var n=1;4>=n;n*=2){var a=e*(1+.2/n);a=Math.min(a,t+100663296);var i=Math;a=Math.max(t,a),i=i.min.call(i,4294901760,a+(65536-a%65536)%65536);t:{try{j.grow(i-D.byteLength+65535>>>16),N(j.buffer);var o=1;break t}catch(t){}o=void 0}if(o)return!0}return!1},Na:function(){throw"unwind"},Ga:Qt,Ha:Kt,J:pt,I:te,S:ee,ga:ne,R:ie,d:function(){return oe},na:function t(r,a){t.lc||(t.lc=function(){if("object"==typeof crypto&&"function"==typeof crypto.getRandomValues){var t=new Uint8Array(1);return()=>(crypto.getRandomValues(t),t[0])}if(_)try{var e=n(Object(function(){var t=new Error("Cannot find module \'crypto\'");throw t.code="MODULE_NOT_FOUND",t}()));return()=>e.randomBytes(1)[0]}catch(t){}return()=>at("randomDevice")}());for(var i=0;i<a;i++)e()[r+i>>0>>>0]=t.lc();return 0},ia:function(t,e,n){var r=Ee();try{return gt(t)(e,n)}catch(t){if(Me(r),t!==t+0)throw t;Se(1,0)}},ja:function(t,e,n){var r=Ee();try{return gt(t)(e,n)}catch(t){if(Me(r),t!==t+0)throw t;Se(1,0)}},K:function(t){var e=Ee();try{return gt(t)()}catch(t){if(Me(e),t!==t+0)throw t;Se(1,0)}},f:function(t,e){var n=Ee();try{return gt(t)(e)}catch(t){if(Me(n),t!==t+0)throw t;Se(1,0)}},P:function(t,e,n){var r=Ee();try{return gt(t)(e,n)}catch(t){if(Me(r),t!==t+0)throw t;Se(1,0)}},Q:function(t,e,n){var r=Ee();try{return gt(t)(e,n)}catch(t){if(Me(r),t!==t+0)throw t;Se(1,0)}},k:function(t,e,n){var r=Ee();try{return gt(t)(e,n)}catch(t){if(Me(r),t!==t+0)throw t;Se(1,0)}},p:function(t,e,n,r){var a=Ee();try{return gt(t)(e,n,r)}catch(t){if(Me(a),t!==t+0)throw t;Se(1,0)}},q:function(t,e,n,r,a){var i=Ee();try{return gt(t)(e,n,r,a)}catch(t){if(Me(i),t!==t+0)throw t;Se(1,0)}},N:function(t,e,n,r,a,i){var o=Ee();try{return gt(t)(e,n,r,a,i)}catch(t){if(Me(o),t!==t+0)throw t;Se(1,0)}},s:function(t,e,n,r,a,i){var o=Ee();try{return gt(t)(e,n,r,a,i)}catch(t){if(Me(o),t!==t+0)throw t;Se(1,0)}},w:function(t,e,n,r,a,i,o){var u=Ee();try{return gt(t)(e,n,r,a,i,o)}catch(t){if(Me(u),t!==t+0)throw t;Se(1,0)}},L:function(t,e,n,r,a,i,o,u){var c=Ee();try{return gt(t)(e,n,r,a,i,o,u)}catch(t){if(Me(c),t!==t+0)throw t;Se(1,0)}},E:function(t,e,n,r,a,i,o,u,c,s,l,f){var p=Ee();try{return gt(t)(e,n,r,a,i,o,u,c,s,l,f)}catch(t){if(Me(p),t!==t+0)throw t;Se(1,0)}},aa:function(t,e,n,r,a,i,o,u){var c=Ee();try{return He(t,e,n,r,a,i,o,u)}catch(t){if(Me(c),t!==t+0)throw t;Se(1,0)}},_:function(t,e,n,r,a,i,o){var u=Ee();try{return ke(t,e,n,r,a,i,o)}catch(t){if(Me(u),t!==t+0)throw t;Se(1,0)}},Z:function(t,e,n,r,a){var i=Ee();try{return Le(t,e,n,r,a)}catch(t){if(Me(i),t!==t+0)throw t;Se(1,0)}},ca:function(t,e,n,r){var a=Ee();try{return Ie(t,e,n,r)}catch(t){if(Me(a),t!==t+0)throw t;Se(1,0)}},$:function(t){var e=Ee();try{return je(t)}catch(t){if(Me(e),t!==t+0)throw t;Se(1,0)}},ba:function(t,e){var n=Ee();try{return We(t,e)}catch(t){if(Me(n),t!==t+0)throw t;Se(1,0)}},Y:function(t,e,n){var r=Ee();try{return De(t,e,n)}catch(t){if(Me(r),t!==t+0)throw t;Se(1,0)}},g:function(t){var e=Ee();try{gt(t)()}catch(t){if(Me(e),t!==t+0)throw t;Se(1,0)}},r:function(t,e){var n=Ee();try{gt(t)(e)}catch(t){if(Me(n),t!==t+0)throw t;Se(1,0)}},i:function(t,e,n){var r=Ee();try{gt(t)(e,n)}catch(t){if(Me(r),t!==t+0)throw t;Se(1,0)}},ha:function(t,e,n,r){var a=Ee();try{gt(t)(e,n,r)}catch(t){if(Me(a),t!==t+0)throw t;Se(1,0)}},m:function(t,e,n,r){var a=Ee();try{gt(t)(e,n,r)}catch(t){if(Me(a),t!==t+0)throw t;Se(1,0)}},v:function(t,e,n,r,a){var i=Ee();try{gt(t)(e,n,r,a)}catch(t){if(Me(i),t!==t+0)throw t;Se(1,0)}},u:function(t,e,n,r,a,i){var o=Ee();try{gt(t)(e,n,r,a,i)}catch(t){if(Me(o),t!==t+0)throw t;Se(1,0)}},O:function(t,e,n,r,a,i,o){var u=Ee();try{gt(t)(e,n,r,a,i,o)}catch(t){if(Me(u),t!==t+0)throw t;Se(1,0)}},A:function(t,e,n,r,a,i,o,u){var c=Ee();try{gt(t)(e,n,r,a,i,o,u)}catch(t){if(Me(c),t!==t+0)throw t;Se(1,0)}},ka:function(t,e,n,r,a,i,o,u,c){var s=Ee();try{gt(t)(e,n,r,a,i,o,u,c)}catch(t){if(Me(s),t!==t+0)throw t;Se(1,0)}},C:function(t,e,n,r,a,i,o,u,c,s,l){var f=Ee();try{gt(t)(e,n,r,a,i,o,u,c,s,l)}catch(t){if(Me(f),t!==t+0)throw t;Se(1,0)}},D:function(t,e,n,r,a,i,o,u,c,s,l,f,p,h,d,y){var b=Ee();try{gt(t)(e,n,r,a,i,o,u,c,s,l,f,p,h,d,y)}catch(t){if(Me(b),t!==t+0)throw t;Se(1,0)}},fa:function(t,e,n,r,a,i,o,u){var c=Ee();try{Pe(t,e,n,r,a,i,o,u)}catch(t){if(Me(c),t!==t+0)throw t;Se(1,0)}},da:function(t,e,n,r,a,i,o,u,c,s,l,f){var p=Ee();try{Fe(t,e,n,r,a,i,o,u,c,s,l,f)}catch(t){if(Me(p),t!==t+0)throw t;Se(1,0)}},ea:function(t,e,n,r,a,i){var o=Ee();try{Ue(t,e,n,r,a,i)}catch(t){if(Me(o),t!==t+0)throw t;Se(1,0)}},o:function(t){return t},a:j||u.wasmMemory,G:function(t){oe=t},la:le,z:function(t,e,n,r){return le(t,e,n,r)}};!function(){function t(t,e){u.asm=t.exports,ht.qc.push(u.asm.sb),$=u.asm.ub,X.unshift(u.asm.Va),k=e,O||(et--,u.monitorRunDependencies&&u.monitorRunDependencies(et),0==et&&(null!==nt&&(clearInterval(nt),nt=null),rt&&(t=rt,rt=null,t())))}function e(e){t(e.instance,e.module)}function n(t){return function(){if(!M&&(v||w)){if("function"==typeof fetch&&!tt.startsWith("file://"))return fetch(tt,{credentials:"same-origin"}).then((function(t){if(!t.ok)throw"failed to load wasm binary file at \'"+tt+"\'";return t.arrayBuffer()})).catch((function(){return ot()}));if(f)return new Promise((function(t,e){f(tt,(function(e){t(new Uint8Array(e))}),e)}))}return Promise.resolve().then((function(){return ot()}))}().then((function(t){return WebAssembly.instantiate(t,r)})).then((function(t){return t})).then(t,(function(t){x("failed to asynchronously prepare wasm: "+t),at(t)}))}var r={a:pe};if(O||(et++,u.monitorRunDependencies&&u.monitorRunDependencies(et)),u.instantiateWasm)try{return u.instantiateWasm(r,t)}catch(t){return x("Module.instantiateWasm callback failed with error: "+t),!1}(M||"function"!=typeof WebAssembly.instantiateStreaming||it()||tt.startsWith("file://")||_||"function"!=typeof fetch?n(e):fetch(tt,{credentials:"same-origin"}).then((function(t){return WebAssembly.instantiateStreaming(t,r).then(e,(function(t){return x("wasm streaming compile failed: "+t),x("falling back to ArrayBuffer instantiation"),n(e)}))}))).catch(s)}(),u.___wasm_call_ctors=function(){return(u.___wasm_call_ctors=u.asm.Va).apply(null,arguments)},u._OrtInit=function(){return(u._OrtInit=u.asm.Wa).apply(null,arguments)},u._OrtCreateSessionOptions=function(){return(u._OrtCreateSessionOptions=u.asm.Xa).apply(null,arguments)},u._OrtAppendExecutionProvider=function(){return(u._OrtAppendExecutionProvider=u.asm.Ya).apply(null,arguments)},u._OrtAddSessionConfigEntry=function(){return(u._OrtAddSessionConfigEntry=u.asm.Za).apply(null,arguments)},u._OrtReleaseSessionOptions=function(){return(u._OrtReleaseSessionOptions=u.asm._a).apply(null,arguments)},u._OrtCreateSession=function(){return(u._OrtCreateSession=u.asm.$a).apply(null,arguments)},u._OrtReleaseSession=function(){return(u._OrtReleaseSession=u.asm.ab).apply(null,arguments)},u._OrtGetInputCount=function(){return(u._OrtGetInputCount=u.asm.bb).apply(null,arguments)},u._OrtGetOutputCount=function(){return(u._OrtGetOutputCount=u.asm.cb).apply(null,arguments)},u._OrtGetInputName=function(){return(u._OrtGetInputName=u.asm.db).apply(null,arguments)},u._OrtGetOutputName=function(){return(u._OrtGetOutputName=u.asm.eb).apply(null,arguments)},u._OrtFree=function(){return(u._OrtFree=u.asm.fb).apply(null,arguments)},u._OrtCreateTensor=function(){return(u._OrtCreateTensor=u.asm.gb).apply(null,arguments)},u._OrtGetTensorData=function(){return(u._OrtGetTensorData=u.asm.hb).apply(null,arguments)},u._OrtReleaseTensor=function(){return(u._OrtReleaseTensor=u.asm.ib).apply(null,arguments)},u._OrtCreateRunOptions=function(){return(u._OrtCreateRunOptions=u.asm.jb).apply(null,arguments)},u._OrtAddRunConfigEntry=function(){return(u._OrtAddRunConfigEntry=u.asm.kb).apply(null,arguments)},u._OrtReleaseRunOptions=function(){return(u._OrtReleaseRunOptions=u.asm.lb).apply(null,arguments)},u._OrtRun=function(){return(u._OrtRun=u.asm.mb).apply(null,arguments)},u._OrtEndProfiling=function(){return(u._OrtEndProfiling=u.asm.nb).apply(null,arguments)};var he=u._pthread_self=function(){return(he=u._pthread_self=u.asm.ob).apply(null,arguments)},de=u._malloc=function(){return(de=u._malloc=u.asm.pb).apply(null,arguments)},ye=u._free=function(){return(ye=u._free=u.asm.qb).apply(null,arguments)},be=u._fflush=function(){return(be=u._fflush=u.asm.rb).apply(null,arguments)};u.__emscripten_tls_init=function(){return(u.__emscripten_tls_init=u.asm.sb).apply(null,arguments)};var me=u.___funcs_on_exit=function(){return(me=u.___funcs_on_exit=u.asm.tb).apply(null,arguments)},ge=u.__emscripten_thread_init=function(){return(ge=u.__emscripten_thread_init=u.asm.vb).apply(null,arguments)};u.__emscripten_thread_crashed=function(){return(u.__emscripten_thread_crashed=u.asm.wb).apply(null,arguments)};var ve,we=u._emscripten_run_in_main_runtime_thread_js=function(){return(we=u._emscripten_run_in_main_runtime_thread_js=u.asm.xb).apply(null,arguments)},_e=u.__emscripten_proxy_execute_task_queue=function(){return(_e=u.__emscripten_proxy_execute_task_queue=u.asm.yb).apply(null,arguments)},Oe=u.__emscripten_thread_free_data=function(){return(Oe=u.__emscripten_thread_free_data=u.asm.zb).apply(null,arguments)},Ae=u.__emscripten_thread_exit=function(){return(Ae=u.__emscripten_thread_exit=u.asm.Ab).apply(null,arguments)},Se=u._setThrew=function(){return(Se=u._setThrew=u.asm.Bb).apply(null,arguments)},Te=u._emscripten_stack_set_limits=function(){return(Te=u._emscripten_stack_set_limits=u.asm.Cb).apply(null,arguments)},Ee=u.stackSave=function(){return(Ee=u.stackSave=u.asm.Db).apply(null,arguments)},Me=u.stackRestore=function(){return(Me=u.stackRestore=u.asm.Eb).apply(null,arguments)},Ce=u.stackAlloc=function(){return(Ce=u.stackAlloc=u.asm.Fb).apply(null,arguments)},xe=u.___cxa_can_catch=function(){return(xe=u.___cxa_can_catch=u.asm.Gb).apply(null,arguments)},Re=u.___cxa_is_pointer_type=function(){return(Re=u.___cxa_is_pointer_type=u.asm.Hb).apply(null,arguments)},je=u.dynCall_j=function(){return(je=u.dynCall_j=u.asm.Ib).apply(null,arguments)},ke=u.dynCall_iiiiij=function(){return(ke=u.dynCall_iiiiij=u.asm.Jb).apply(null,arguments)},De=u.dynCall_jii=function(){return(De=u.dynCall_jii=u.asm.Kb).apply(null,arguments)},Pe=u.dynCall_viiiiij=function(){return(Pe=u.dynCall_viiiiij=u.asm.Lb).apply(null,arguments)},Ue=u.dynCall_vjji=function(){return(Ue=u.dynCall_vjji=u.asm.Mb).apply(null,arguments)},Fe=u.dynCall_viiijjjii=function(){return(Fe=u.dynCall_viiijjjii=u.asm.Nb).apply(null,arguments)},Ie=u.dynCall_iij=function(){return(Ie=u.dynCall_iij=u.asm.Ob).apply(null,arguments)},We=u.dynCall_ji=function(){return(We=u.dynCall_ji=u.asm.Pb).apply(null,arguments)},He=u.dynCall_iiiiiij=function(){return(He=u.dynCall_iiiiiij=u.asm.Qb).apply(null,arguments)},Le=u.dynCall_iiij=function(){return(Le=u.dynCall_iiij=u.asm.Rb).apply(null,arguments)};function ze(){function t(){if(!ve&&(ve=!0,u.calledRun=!0,!H)&&(O||dt(X),c(u),u.onRuntimeInitialized&&u.onRuntimeInitialized(),!O)){if(u.postRun)for("function"==typeof u.postRun&&(u.postRun=[u.postRun]);u.postRun.length;){var t=u.postRun.shift();Z.unshift(t)}dt(Z)}}if(!(0<et))if(O)c(u),O||dt(X),postMessage({cmd:"loaded"});else{if(u.preRun)for("function"==typeof u.preRun&&(u.preRun=[u.preRun]);u.preRun.length;)K();dt(q),0<et||(u.setStatus?(u.setStatus("Running..."),setTimeout((function(){setTimeout((function(){u.setStatus("")}),1),t()}),1)):t())}}if(u.UTF8ToString=Y,u.stringToUTF8=function(t,e,n){return B(t,r(),e,n)},u.lengthBytesUTF8=G,u.keepRuntimeAlive=Q,u.wasmMemory=j,u.stackSave=Ee,u.stackRestore=Me,u.stackAlloc=Ce,u.ExitStatus=ct,u.PThread=ht,rt=function t(){ve||ze(),ve||(rt=t)},u.preInit)for("function"==typeof u.preInit&&(u.preInit=[u.preInit]);0<u.preInit.length;)u.preInit.pop()();return ze(),t.ready});t.exports=r},932:(t,e,n)=>{var _scriptDir,r=(_scriptDir=(_scriptDir="undefined"!=typeof document&&document.currentScript?document.currentScript.src:void 0)||"/index.js",function(t){var e,r,a;t=t||{},e||(e=void 0!==t?t:{}),e.ready=new Promise((function(t,e){r=t,a=e}));var i,o,u,c,s,l,f=Object.assign({},e),p="./this.program",h=(t,e)=>{throw e},d="object"==typeof window,y="function"==typeof importScripts,b="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node,m="";b?(m=y?n(908).dirname(m)+"/":"//",l=()=>{s||(c=n(384),s=n(908))},i=function(t,e){return l(),t=s.normalize(t),c.readFileSync(t,e?void 0:"utf8")},u=t=>((t=i(t,!0)).buffer||(t=new Uint8Array(t)),t),o=(t,e,n)=>{l(),t=s.normalize(t),c.readFile(t,(function(t,r){t?n(t):e(r.buffer)}))},1<process.argv.length&&(p=process.argv[1].replace(/\\\\/g,"/")),process.argv.slice(2),process.on("uncaughtException",(function(t){if(!(t instanceof J))throw t})),process.on("unhandledRejection",(function(t){throw t})),h=(t,e)=>{if(_||0<L)throw process.exitCode=t,e;e instanceof J||w("exiting due to exception: "+e),process.exit(t)},e.inspect=function(){return"[Emscripten Module object]"}):(d||y)&&(y?m=self.location.href:"undefined"!=typeof document&&document.currentScript&&(m=document.currentScript.src),_scriptDir&&(m=_scriptDir),m=0!==m.indexOf("blob:")?m.substr(0,m.replace(/[?#].*/,"").lastIndexOf("/")+1):"",i=t=>{var e=new XMLHttpRequest;return e.open("GET",t,!1),e.send(null),e.responseText},y&&(u=t=>{var e=new XMLHttpRequest;return e.open("GET",t,!1),e.responseType="arraybuffer",e.send(null),new Uint8Array(e.response)}),o=(t,e,n)=>{var r=new XMLHttpRequest;r.open("GET",t,!0),r.responseType="arraybuffer",r.onload=()=>{200==r.status||0==r.status&&r.response?e(r.response):n()},r.onerror=n,r.send(null)});var g,v=e.print||console.log.bind(console),w=e.printErr||console.warn.bind(console);Object.assign(e,f),f=null,e.thisProgram&&(p=e.thisProgram),e.quit&&(h=e.quit),e.wasmBinary&&(g=e.wasmBinary);var _=e.noExitRuntime||!1;"object"!=typeof WebAssembly&&V("no native wasm support detected");var O,A,S,T,E,M,C=!1,x="undefined"!=typeof TextDecoder?new TextDecoder("utf8"):void 0;function R(t,e,n){var r=(e>>>=0)+n;for(n=e;t[n]&&!(n>=r);)++n;if(16<n-e&&t.buffer&&x)return x.decode(t.subarray(e,n));for(r="";e<n;){var a=t[e++];if(128&a){var i=63&t[e++];if(192==(224&a))r+=String.fromCharCode((31&a)<<6|i);else{var o=63&t[e++];65536>(a=224==(240&a)?(15&a)<<12|i<<6|o:(7&a)<<18|i<<12|o<<6|63&t[e++])?r+=String.fromCharCode(a):(a-=65536,r+=String.fromCharCode(55296|a>>10,56320|1023&a))}}else r+=String.fromCharCode(a)}return r}function j(t,e){return(t>>>=0)?R(T,t,e):""}function k(t,e,n,r){if(!(0<r))return 0;var a=n>>>=0;r=n+r-1;for(var i=0;i<t.length;++i){var o=t.charCodeAt(i);if(55296<=o&&57343>=o&&(o=65536+((1023&o)<<10)|1023&t.charCodeAt(++i)),127>=o){if(n>=r)break;e[n++>>>0]=o}else{if(2047>=o){if(n+1>=r)break;e[n++>>>0]=192|o>>6}else{if(65535>=o){if(n+2>=r)break;e[n++>>>0]=224|o>>12}else{if(n+3>=r)break;e[n++>>>0]=240|o>>18,e[n++>>>0]=128|o>>12&63}e[n++>>>0]=128|o>>6&63}e[n++>>>0]=128|63&o}}return e[n>>>0]=0,n-a}function D(t){for(var e=0,n=0;n<t.length;++n){var r=t.charCodeAt(n);127>=r?e++:2047>=r?e+=2:55296<=r&&57343>=r?(e+=4,++n):e+=3}return e}function P(){var t=O.buffer;A=t,e.HEAP8=S=new Int8Array(t),e.HEAP16=new Int16Array(t),e.HEAP32=E=new Int32Array(t),e.HEAPU8=T=new Uint8Array(t),e.HEAPU16=new Uint16Array(t),e.HEAPU32=M=new Uint32Array(t),e.HEAPF32=new Float32Array(t),e.HEAPF64=new Float64Array(t)}var U,F=[],I=[],W=[],H=[],L=0;function z(){var t=e.preRun.shift();F.unshift(t)}var Y,B=0,G=null,N=null;function V(t){throw e.onAbort&&e.onAbort(t),w(t="Aborted("+t+")"),C=!0,t=new WebAssembly.RuntimeError(t+". Build with -sASSERTIONS for more info."),a(t),t}function $(){return Y.startsWith("data:application/octet-stream;base64,")}if(Y="ort-wasm.wasm",!$()){var q=Y;Y=e.locateFile?e.locateFile(q,m):m+q}function X(){var t=Y;try{if(t==Y&&g)return new Uint8Array(g);if(u)return u(t);throw"both async and sync fetching of the wasm failed"}catch(t){V(t)}}function J(t){this.name="ExitStatus",this.message="Program terminated with exit("+t+")",this.status=t}function Z(t){for(;0<t.length;)t.shift()(e)}var Q=[],K=0,tt=0;function et(t){this.Db=t,this.zb=t-24,this.Ub=function(t){M[this.zb+4>>2>>>0]=t},this.Eb=function(){return M[this.zb+4>>2>>>0]},this.Sb=function(t){M[this.zb+8>>2>>>0]=t},this.Wb=function(){return M[this.zb+8>>2>>>0]},this.Tb=function(){E[this.zb>>2>>>0]=0},this.Ib=function(t){S[this.zb+12>>0>>>0]=t?1:0},this.Pb=function(){return 0!=S[this.zb+12>>0>>>0]},this.Jb=function(t){S[this.zb+13>>0>>>0]=t?1:0},this.Lb=function(){return 0!=S[this.zb+13>>0>>>0]},this.Rb=function(t,e){this.Fb(0),this.Ub(t),this.Sb(e),this.Tb(),this.Ib(!1),this.Jb(!1)},this.Nb=function(){E[this.zb>>2>>>0]+=1},this.Xb=function(){var t=E[this.zb>>2>>>0];return E[this.zb>>2>>>0]=t-1,1===t},this.Fb=function(t){M[this.zb+16>>2>>>0]=t},this.Ob=function(){return M[this.zb+16>>2>>>0]},this.Qb=function(){if(Mt(this.Eb()))return M[this.Db>>2>>>0];var t=this.Ob();return 0!==t?t:this.Db}}function nt(t){return vt(new et(t).zb)}var rt=[];function at(t){var e=rt[t];return e||(t>=rt.length&&(rt.length=t+1),rt[t]=e=U.get(t)),e}function it(t){var e=D(t)+1,n=gt(e);return n&&k(t,S,n,e),n}var ot={};function ut(){if(!ct){var t,e={USER:"web_user",LOGNAME:"web_user",PATH:"/",PWD:"/",HOME:"/home/web_user",LANG:("object"==typeof navigator&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8",_:p||"./this.program"};for(t in ot)void 0===ot[t]?delete e[t]:e[t]=ot[t];var n=[];for(t in e)n.push(t+"="+e[t]);ct=n}return ct}var ct,st=[null,[],[]];function lt(t,e){var n=st[t];0===e||10===e?((1===t?v:w)(R(n,0)),n.length=0):n.push(e)}var ft=0;function pt(t){return 0==t%4&&(0!=t%100||0==t%400)}var ht=[31,29,31,30,31,30,31,31,30,31,30,31],dt=[31,28,31,30,31,30,31,31,30,31,30,31];function yt(t,e,n,r){function a(t,e,n){for(t="number"==typeof t?t.toString():t||"";t.length<e;)t=n[0]+t;return t}function i(t,e){return a(t,e,"0")}function o(t,e){function n(t){return 0>t?-1:0<t?1:0}var r;return 0===(r=n(t.getFullYear()-e.getFullYear()))&&0===(r=n(t.getMonth()-e.getMonth()))&&(r=n(t.getDate()-e.getDate())),r}function u(t){switch(t.getDay()){case 0:return new Date(t.getFullYear()-1,11,29);case 1:return t;case 2:return new Date(t.getFullYear(),0,3);case 3:return new Date(t.getFullYear(),0,2);case 4:return new Date(t.getFullYear(),0,1);case 5:return new Date(t.getFullYear()-1,11,31);case 6:return new Date(t.getFullYear()-1,11,30)}}function c(t){var e=t.Bb;for(t=new Date(new Date(t.Cb+1900,0,1).getTime());0<e;){var n=t.getMonth(),r=(pt(t.getFullYear())?ht:dt)[n];if(!(e>r-t.getDate())){t.setDate(t.getDate()+e);break}e-=r-t.getDate()+1,t.setDate(1),11>n?t.setMonth(n+1):(t.setMonth(0),t.setFullYear(t.getFullYear()+1))}return n=new Date(t.getFullYear()+1,0,4),e=u(new Date(t.getFullYear(),0,4)),n=u(n),0>=o(e,t)?0>=o(n,t)?t.getFullYear()+1:t.getFullYear():t.getFullYear()-1}var s=E[r+40>>2>>>0];for(var l in r={$b:E[r>>2>>>0],Zb:E[r+4>>2>>>0],Gb:E[r+8>>2>>>0],Kb:E[r+12>>2>>>0],Hb:E[r+16>>2>>>0],Cb:E[r+20>>2>>>0],Ab:E[r+24>>2>>>0],Bb:E[r+28>>2>>>0],bc:E[r+32>>2>>>0],Yb:E[r+36>>2>>>0],ac:s?j(s):""},n=j(n),s={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"})n=n.replace(new RegExp(l,"g"),s[l]);var f="Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),p="January February March April May June July August September October November December".split(" ");for(l in s={"%a":function(t){return f[t.Ab].substring(0,3)},"%A":function(t){return f[t.Ab]},"%b":function(t){return p[t.Hb].substring(0,3)},"%B":function(t){return p[t.Hb]},"%C":function(t){return i((t.Cb+1900)/100|0,2)},"%d":function(t){return i(t.Kb,2)},"%e":function(t){return a(t.Kb,2," ")},"%g":function(t){return c(t).toString().substring(2)},"%G":function(t){return c(t)},"%H":function(t){return i(t.Gb,2)},"%I":function(t){return 0==(t=t.Gb)?t=12:12<t&&(t-=12),i(t,2)},"%j":function(t){for(var e=0,n=0;n<=t.Hb-1;e+=(pt(t.Cb+1900)?ht:dt)[n++]);return i(t.Kb+e,3)},"%m":function(t){return i(t.Hb+1,2)},"%M":function(t){return i(t.Zb,2)},"%n":function(){return"\\n"},"%p":function(t){return 0<=t.Gb&&12>t.Gb?"AM":"PM"},"%S":function(t){return i(t.$b,2)},"%t":function(){return"\\t"},"%u":function(t){return t.Ab||7},"%U":function(t){return i(Math.floor((t.Bb+7-t.Ab)/7),2)},"%V":function(t){var e=Math.floor((t.Bb+7-(t.Ab+6)%7)/7);if(2>=(t.Ab+371-t.Bb-2)%7&&e++,e)53==e&&(4==(n=(t.Ab+371-t.Bb)%7)||3==n&&pt(t.Cb)||(e=1));else{e=52;var n=(t.Ab+7-t.Bb-1)%7;(4==n||5==n&&pt(t.Cb%400-1))&&e++}return i(e,2)},"%w":function(t){return t.Ab},"%W":function(t){return i(Math.floor((t.Bb+7-(t.Ab+6)%7)/7),2)},"%y":function(t){return(t.Cb+1900).toString().substring(2)},"%Y":function(t){return t.Cb+1900},"%z":function(t){var e=0<=(t=t.Yb);return t=Math.abs(t)/60,(e?"+":"-")+String("0000"+(t/60*100+t%60)).slice(-4)},"%Z":function(t){return t.ac},"%%":function(){return"%"}},n=n.replace(/%%/g,"\\0\\0"),s)n.includes(l)&&(n=n.replace(new RegExp(l,"g"),s[l](r)));return l=function(t){var e=Array(D(t)+1);return k(t,e,0,e.length),e}(n=n.replace(/\\0\\0/g,"%")),l.length>e?0:(S.set(l,t>>>0),l.length-1)}var bt={a:function(t){return gt(t+24)+24},m:function(t){return(t=new et(t)).Pb()||(t.Ib(!0),K--),t.Jb(!1),Q.push(t),t.Nb(),t.Qb()},ia:function(t){throw w("Unexpected exception thrown, this is not properly supported - aborting"),C=!0,t},w:function(){Ot(0);var t=Q.pop();if(t.Xb()&&!t.Lb()){var e=t.Wb();e&&at(e)(t.Db),nt(t.Db)}tt=0},d:function(){var t=tt;if(!t)return ft=0;var e=new et(t);e.Fb(t);var n=e.Eb();if(!n)return ft=0,t;for(var r=Array.prototype.slice.call(arguments),a=0;a<r.length;a++){var i=r[a];if(0===i||i===n)break;if(Et(i,n,e.zb+16))return ft=i,t}return ft=n,t},k:function(){var t=tt;if(!t)return ft=0;var e=new et(t);e.Fb(t);var n=e.Eb();if(!n)return ft=0,t;for(var r=Array.prototype.slice.call(arguments),a=0;a<r.length;a++){var i=r[a];if(0===i||i===n)break;if(Et(i,n,e.zb+16))return ft=i,t}return ft=n,t},g:function(){var t=tt;if(!t)return ft=0;var e=new et(t);e.Fb(t);var n=e.Eb();if(!n)return ft=0,t;for(var r=Array.prototype.slice.call(arguments),a=0;a<r.length;a++){var i=r[a];if(0===i||i===n)break;if(Et(i,n,e.zb+16))return ft=i,t}return ft=n,t},s:nt,L:function(){var t=Q.pop();t||V("no exception to throw");var e=t.Db;throw t.Lb()||(Q.push(t),t.Jb(!0),t.Ib(!1),K++),tt=e,e},b:function(t,e,n){throw new et(t).Rb(e,n),tt=t,K++,t},la:function(){return K},i:function(t){throw tt||(tt=t),t},H:function(){return 0},Ba:function(){},pa:function(){},ra:function(){},ka:function(){return 0},za:function(){},ua:function(){},ya:function(){},R:function(){},qa:function(){},na:function(){},Aa:function(){},oa:function(){},Ha:function(){},Ja:function(){V("To use dlopen, you need enable dynamic linking, see https://github.com/emscripten-core/emscripten/wiki/Linking")},Ia:function(){V("To use dlopen, you need enable dynamic linking, see https://github.com/emscripten-core/emscripten/wiki/Linking")},S:function(){return Date.now()},Ca:function(){return!0},Da:function(t,e){t=new Date(1e3*(M[t>>>2]+4294967296*E[t+4>>>2])),E[e>>2>>>0]=t.getUTCSeconds(),E[e+4>>2>>>0]=t.getUTCMinutes(),E[e+8>>2>>>0]=t.getUTCHours(),E[e+12>>2>>>0]=t.getUTCDate(),E[e+16>>2>>>0]=t.getUTCMonth(),E[e+20>>2>>>0]=t.getUTCFullYear()-1900,E[e+24>>2>>>0]=t.getUTCDay(),E[e+28>>2>>>0]=(t.getTime()-Date.UTC(t.getUTCFullYear(),0,1,0,0,0,0))/864e5|0},Ea:function(t,e){t=new Date(1e3*(M[t>>>2]+4294967296*E[t+4>>>2])),E[e>>2>>>0]=t.getSeconds(),E[e+4>>2>>>0]=t.getMinutes(),E[e+8>>2>>>0]=t.getHours(),E[e+12>>2>>>0]=t.getDate(),E[e+16>>2>>>0]=t.getMonth(),E[e+20>>2>>>0]=t.getFullYear()-1900,E[e+24>>2>>>0]=t.getDay();var n=new Date(t.getFullYear(),0,1);E[e+28>>2>>>0]=(t.getTime()-n.getTime())/864e5|0,E[e+36>>2>>>0]=-60*t.getTimezoneOffset();var r=new Date(t.getFullYear(),6,1).getTimezoneOffset();n=n.getTimezoneOffset(),E[e+32>>2>>>0]=0|(r!=n&&t.getTimezoneOffset()==Math.min(n,r))},Fa:function(t){var e=new Date(E[t+20>>2>>>0]+1900,E[t+16>>2>>>0],E[t+12>>2>>>0],E[t+8>>2>>>0],E[t+4>>2>>>0],E[t>>2>>>0],0),n=E[t+32>>2>>>0],r=e.getTimezoneOffset(),a=new Date(e.getFullYear(),0,1),i=new Date(e.getFullYear(),6,1).getTimezoneOffset(),o=a.getTimezoneOffset(),u=Math.min(o,i);return 0>n?E[t+32>>2>>>0]=Number(i!=o&&u==r):0<n!=(u==r)&&(i=Math.max(o,i),e.setTime(e.getTime()+6e4*((0<n?u:i)-r))),E[t+24>>2>>>0]=e.getDay(),E[t+28>>2>>>0]=(e.getTime()-a.getTime())/864e5|0,E[t>>2>>>0]=e.getSeconds(),E[t+4>>2>>>0]=e.getMinutes(),E[t+8>>2>>>0]=e.getHours(),E[t+12>>2>>>0]=e.getDate(),E[t+16>>2>>>0]=e.getMonth(),e.getTime()/1e3|0},sa:function(){return-52},ta:function(){},Ga:function t(e,n,r){t.Vb||(t.Vb=!0,function(t,e,n){function r(t){return(t=t.toTimeString().match(/\\(([A-Za-z ]+)\\)$/))?t[1]:"GMT"}var a=(new Date).getFullYear(),i=new Date(a,0,1),o=new Date(a,6,1);a=i.getTimezoneOffset();var u=o.getTimezoneOffset();E[t>>2>>>0]=60*Math.max(a,u),E[e>>2>>>0]=Number(a!=u),t=r(i),e=r(o),t=it(t),e=it(e),u<a?(M[n>>2>>>0]=t,M[n+4>>2>>>0]=e):(M[n>>2>>>0]=e,M[n+4>>2>>>0]=t)}(e,n,r))},B:function(){V("")},ma:function(){return 4294901760},I:b?()=>{var t=process.hrtime();return 1e3*t[0]+t[1]/1e6}:()=>performance.now(),xa:function(t,e,n){T.copyWithin(t>>>0,e>>>0,e+n>>>0)},G:function(t){var e=T.length;if(4294901760<(t>>>=0))return!1;for(var n=1;4>=n;n*=2){var r=e*(1+.2/n);r=Math.min(r,t+100663296);var a=Math;r=Math.max(t,r),a=a.min.call(a,4294901760,r+(65536-r%65536)%65536);t:{try{O.grow(a-A.byteLength+65535>>>16),P();var i=1;break t}catch(t){}i=void 0}if(i)return!0}return!1},va:function(t,e){var n=0;return ut().forEach((function(r,a){var i=e+n;for(a=M[t+4*a>>2>>>0]=i,i=0;i<r.length;++i)S[a++>>0>>>0]=r.charCodeAt(i);S[a>>0>>>0]=0,n+=r.length+1})),0},wa:function(t,e){var n=ut();M[t>>2>>>0]=n.length;var r=0;return n.forEach((function(t){r+=t.length+1})),M[e>>2>>>0]=r,0},ba:function(t){_||0<L||(_t(),Z(W),wt(0),st[1].length&&lt(1,10),st[2].length&&lt(2,10)),_||0<L||(e.onExit&&e.onExit(t),C=!0),h(t,new J(t))},E:function(){return 52},Q:function(){return 52},ca:function(){return 70},P:function(t,e,n,r){for(var a=0,i=0;i<n;i++){var o=M[e>>2>>>0],u=M[e+4>>2>>>0];e+=8;for(var c=0;c<u;c++)lt(t,T[o+c>>>0]);a+=u}return M[r>>2>>>0]=a,0},c:function(){return ft},ja:function t(e,r){t.Mb||(t.Mb=function(){if("object"==typeof crypto&&"function"==typeof crypto.getRandomValues){var t=new Uint8Array(1);return()=>(crypto.getRandomValues(t),t[0])}if(b)try{var e=n(Object(function(){var t=new Error("Cannot find module \'crypto\'");throw t.code="MODULE_NOT_FOUND",t}()));return()=>e.randomBytes(1)[0]}catch(t){}return()=>V("randomDevice")}());for(var a=0;a<r;a++)S[e+a>>0>>>0]=t.Mb();return 0},ea:function(t,e,n){var r=At();try{return at(t)(e,n)}catch(t){if(St(r),t!==t+0)throw t;Ot(1,0)}},fa:function(t,e,n){var r=At();try{return at(t)(e,n)}catch(t){if(St(r),t!==t+0)throw t;Ot(1,0)}},J:function(t){var e=At();try{return at(t)()}catch(t){if(St(e),t!==t+0)throw t;Ot(1,0)}},e:function(t,e){var n=At();try{return at(t)(e)}catch(t){if(St(n),t!==t+0)throw t;Ot(1,0)}},N:function(t,e,n){var r=At();try{return at(t)(e,n)}catch(t){if(St(r),t!==t+0)throw t;Ot(1,0)}},O:function(t,e,n){var r=At();try{return at(t)(e,n)}catch(t){if(St(r),t!==t+0)throw t;Ot(1,0)}},j:function(t,e,n){var r=At();try{return at(t)(e,n)}catch(t){if(St(r),t!==t+0)throw t;Ot(1,0)}},o:function(t,e,n,r){var a=At();try{return at(t)(e,n,r)}catch(t){if(St(a),t!==t+0)throw t;Ot(1,0)}},p:function(t,e,n,r,a){var i=At();try{return at(t)(e,n,r,a)}catch(t){if(St(i),t!==t+0)throw t;Ot(1,0)}},M:function(t,e,n,r,a,i){var o=At();try{return at(t)(e,n,r,a,i)}catch(t){if(St(o),t!==t+0)throw t;Ot(1,0)}},r:function(t,e,n,r,a,i){var o=At();try{return at(t)(e,n,r,a,i)}catch(t){if(St(o),t!==t+0)throw t;Ot(1,0)}},v:function(t,e,n,r,a,i,o){var u=At();try{return at(t)(e,n,r,a,i,o)}catch(t){if(St(u),t!==t+0)throw t;Ot(1,0)}},K:function(t,e,n,r,a,i,o,u){var c=At();try{return at(t)(e,n,r,a,i,o,u)}catch(t){if(St(c),t!==t+0)throw t;Ot(1,0)}},D:function(t,e,n,r,a,i,o,u,c,s,l,f){var p=At();try{return at(t)(e,n,r,a,i,o,u,c,s,l,f)}catch(t){if(St(p),t!==t+0)throw t;Ot(1,0)}},X:function(t,e,n,r,a,i,o,u){var c=At();try{return Ft(t,e,n,r,a,i,o,u)}catch(t){if(St(c),t!==t+0)throw t;Ot(1,0)}},V:function(t,e,n,r,a,i,o){var u=At();try{return xt(t,e,n,r,a,i,o)}catch(t){if(St(u),t!==t+0)throw t;Ot(1,0)}},U:function(t,e,n,r,a){var i=At();try{return It(t,e,n,r,a)}catch(t){if(St(i),t!==t+0)throw t;Ot(1,0)}},Z:function(t,e,n,r){var a=At();try{return Pt(t,e,n,r)}catch(t){if(St(a),t!==t+0)throw t;Ot(1,0)}},W:function(t){var e=At();try{return Ct(t)}catch(t){if(St(e),t!==t+0)throw t;Ot(1,0)}},Y:function(t,e){var n=At();try{return Ut(t,e)}catch(t){if(St(n),t!==t+0)throw t;Ot(1,0)}},T:function(t,e,n){var r=At();try{return Rt(t,e,n)}catch(t){if(St(r),t!==t+0)throw t;Ot(1,0)}},f:function(t){var e=At();try{at(t)()}catch(t){if(St(e),t!==t+0)throw t;Ot(1,0)}},q:function(t,e){var n=At();try{at(t)(e)}catch(t){if(St(n),t!==t+0)throw t;Ot(1,0)}},h:function(t,e,n){var r=At();try{at(t)(e,n)}catch(t){if(St(r),t!==t+0)throw t;Ot(1,0)}},da:function(t,e,n,r){var a=At();try{at(t)(e,n,r)}catch(t){if(St(a),t!==t+0)throw t;Ot(1,0)}},l:function(t,e,n,r){var a=At();try{at(t)(e,n,r)}catch(t){if(St(a),t!==t+0)throw t;Ot(1,0)}},t:function(t,e,n,r,a){var i=At();try{at(t)(e,n,r,a)}catch(t){if(St(i),t!==t+0)throw t;Ot(1,0)}},u:function(t,e,n,r,a,i){var o=At();try{at(t)(e,n,r,a,i)}catch(t){if(St(o),t!==t+0)throw t;Ot(1,0)}},x:function(t,e,n,r,a,i,o){var u=At();try{at(t)(e,n,r,a,i,o)}catch(t){if(St(u),t!==t+0)throw t;Ot(1,0)}},z:function(t,e,n,r,a,i,o,u){var c=At();try{at(t)(e,n,r,a,i,o,u)}catch(t){if(St(c),t!==t+0)throw t;Ot(1,0)}},ga:function(t,e,n,r,a,i,o,u,c){var s=At();try{at(t)(e,n,r,a,i,o,u,c)}catch(t){if(St(s),t!==t+0)throw t;Ot(1,0)}},A:function(t,e,n,r,a,i,o,u,c,s,l){var f=At();try{at(t)(e,n,r,a,i,o,u,c,s,l)}catch(t){if(St(f),t!==t+0)throw t;Ot(1,0)}},C:function(t,e,n,r,a,i,o,u,c,s,l,f,p,h,d,y){var b=At();try{at(t)(e,n,r,a,i,o,u,c,s,l,f,p,h,d,y)}catch(t){if(St(b),t!==t+0)throw t;Ot(1,0)}},aa:function(t,e,n,r,a,i,o,u){var c=At();try{jt(t,e,n,r,a,i,o,u)}catch(t){if(St(c),t!==t+0)throw t;Ot(1,0)}},_:function(t,e,n,r,a,i,o,u,c,s,l,f){var p=At();try{Dt(t,e,n,r,a,i,o,u,c,s,l,f)}catch(t){if(St(p),t!==t+0)throw t;Ot(1,0)}},$:function(t,e,n,r,a,i){var o=At();try{kt(t,e,n,r,a,i)}catch(t){if(St(o),t!==t+0)throw t;Ot(1,0)}},n:function(t){return t},F:function(t){ft=t},ha:yt,y:function(t,e,n,r){return yt(t,e,n,r)}};!function(){function t(t){e.asm=t.exports,O=e.asm.Ka,P(),U=e.asm.ib,I.unshift(e.asm.La),B--,e.monitorRunDependencies&&e.monitorRunDependencies(B),0==B&&(null!==G&&(clearInterval(G),G=null),N&&(t=N,N=null,t()))}function n(e){t(e.instance)}function r(t){return function(){if(!g&&(d||y)){if("function"==typeof fetch&&!Y.startsWith("file://"))return fetch(Y,{credentials:"same-origin"}).then((function(t){if(!t.ok)throw"failed to load wasm binary file at \'"+Y+"\'";return t.arrayBuffer()})).catch((function(){return X()}));if(o)return new Promise((function(t,e){o(Y,(function(e){t(new Uint8Array(e))}),e)}))}return Promise.resolve().then((function(){return X()}))}().then((function(t){return WebAssembly.instantiate(t,i)})).then((function(t){return t})).then(t,(function(t){w("failed to asynchronously prepare wasm: "+t),V(t)}))}var i={a:bt};if(B++,e.monitorRunDependencies&&e.monitorRunDependencies(B),e.instantiateWasm)try{return e.instantiateWasm(i,t)}catch(t){return w("Module.instantiateWasm callback failed with error: "+t),!1}(g||"function"!=typeof WebAssembly.instantiateStreaming||$()||Y.startsWith("file://")||b||"function"!=typeof fetch?r(n):fetch(Y,{credentials:"same-origin"}).then((function(t){return WebAssembly.instantiateStreaming(t,i).then(n,(function(t){return w("wasm streaming compile failed: "+t),w("falling back to ArrayBuffer instantiation"),r(n)}))}))).catch(a)}(),e.___wasm_call_ctors=function(){return(e.___wasm_call_ctors=e.asm.La).apply(null,arguments)},e._OrtInit=function(){return(e._OrtInit=e.asm.Ma).apply(null,arguments)},e._OrtCreateSessionOptions=function(){return(e._OrtCreateSessionOptions=e.asm.Na).apply(null,arguments)},e._OrtAppendExecutionProvider=function(){return(e._OrtAppendExecutionProvider=e.asm.Oa).apply(null,arguments)},e._OrtAddSessionConfigEntry=function(){return(e._OrtAddSessionConfigEntry=e.asm.Pa).apply(null,arguments)},e._OrtReleaseSessionOptions=function(){return(e._OrtReleaseSessionOptions=e.asm.Qa).apply(null,arguments)},e._OrtCreateSession=function(){return(e._OrtCreateSession=e.asm.Ra).apply(null,arguments)},e._OrtReleaseSession=function(){return(e._OrtReleaseSession=e.asm.Sa).apply(null,arguments)},e._OrtGetInputCount=function(){return(e._OrtGetInputCount=e.asm.Ta).apply(null,arguments)},e._OrtGetOutputCount=function(){return(e._OrtGetOutputCount=e.asm.Ua).apply(null,arguments)},e._OrtGetInputName=function(){return(e._OrtGetInputName=e.asm.Va).apply(null,arguments)},e._OrtGetOutputName=function(){return(e._OrtGetOutputName=e.asm.Wa).apply(null,arguments)},e._OrtFree=function(){return(e._OrtFree=e.asm.Xa).apply(null,arguments)},e._OrtCreateTensor=function(){return(e._OrtCreateTensor=e.asm.Ya).apply(null,arguments)},e._OrtGetTensorData=function(){return(e._OrtGetTensorData=e.asm.Za).apply(null,arguments)},e._OrtReleaseTensor=function(){return(e._OrtReleaseTensor=e.asm._a).apply(null,arguments)},e._OrtCreateRunOptions=function(){return(e._OrtCreateRunOptions=e.asm.$a).apply(null,arguments)},e._OrtAddRunConfigEntry=function(){return(e._OrtAddRunConfigEntry=e.asm.ab).apply(null,arguments)},e._OrtReleaseRunOptions=function(){return(e._OrtReleaseRunOptions=e.asm.bb).apply(null,arguments)},e._OrtRun=function(){return(e._OrtRun=e.asm.cb).apply(null,arguments)},e._OrtEndProfiling=function(){return(e._OrtEndProfiling=e.asm.db).apply(null,arguments)};var mt,gt=e._malloc=function(){return(gt=e._malloc=e.asm.eb).apply(null,arguments)},vt=e._free=function(){return(vt=e._free=e.asm.fb).apply(null,arguments)},wt=e._fflush=function(){return(wt=e._fflush=e.asm.gb).apply(null,arguments)},_t=e.___funcs_on_exit=function(){return(_t=e.___funcs_on_exit=e.asm.hb).apply(null,arguments)},Ot=e._setThrew=function(){return(Ot=e._setThrew=e.asm.jb).apply(null,arguments)},At=e.stackSave=function(){return(At=e.stackSave=e.asm.kb).apply(null,arguments)},St=e.stackRestore=function(){return(St=e.stackRestore=e.asm.lb).apply(null,arguments)},Tt=e.stackAlloc=function(){return(Tt=e.stackAlloc=e.asm.mb).apply(null,arguments)},Et=e.___cxa_can_catch=function(){return(Et=e.___cxa_can_catch=e.asm.nb).apply(null,arguments)},Mt=e.___cxa_is_pointer_type=function(){return(Mt=e.___cxa_is_pointer_type=e.asm.ob).apply(null,arguments)},Ct=e.dynCall_j=function(){return(Ct=e.dynCall_j=e.asm.pb).apply(null,arguments)},xt=e.dynCall_iiiiij=function(){return(xt=e.dynCall_iiiiij=e.asm.qb).apply(null,arguments)},Rt=e.dynCall_jii=function(){return(Rt=e.dynCall_jii=e.asm.rb).apply(null,arguments)},jt=e.dynCall_viiiiij=function(){return(jt=e.dynCall_viiiiij=e.asm.sb).apply(null,arguments)},kt=e.dynCall_vjji=function(){return(kt=e.dynCall_vjji=e.asm.tb).apply(null,arguments)},Dt=e.dynCall_viiijjjii=function(){return(Dt=e.dynCall_viiijjjii=e.asm.ub).apply(null,arguments)},Pt=e.dynCall_iij=function(){return(Pt=e.dynCall_iij=e.asm.vb).apply(null,arguments)},Ut=e.dynCall_ji=function(){return(Ut=e.dynCall_ji=e.asm.wb).apply(null,arguments)},Ft=e.dynCall_iiiiiij=function(){return(Ft=e.dynCall_iiiiiij=e.asm.xb).apply(null,arguments)},It=e.dynCall_iiij=function(){return(It=e.dynCall_iiij=e.asm.yb).apply(null,arguments)};function Wt(){function t(){if(!mt&&(mt=!0,e.calledRun=!0,!C)){if(Z(I),r(e),e.onRuntimeInitialized&&e.onRuntimeInitialized(),e.postRun)for("function"==typeof e.postRun&&(e.postRun=[e.postRun]);e.postRun.length;){var t=e.postRun.shift();H.unshift(t)}Z(H)}}if(!(0<B)){if(e.preRun)for("function"==typeof e.preRun&&(e.preRun=[e.preRun]);e.preRun.length;)z();Z(F),0<B||(e.setStatus?(e.setStatus("Running..."),setTimeout((function(){setTimeout((function(){e.setStatus("")}),1),t()}),1)):t())}}if(e.UTF8ToString=j,e.stringToUTF8=function(t,e,n){return k(t,T,e,n)},e.lengthBytesUTF8=D,e.stackSave=At,e.stackRestore=St,e.stackAlloc=Tt,N=function t(){mt||Wt(),mt||(N=t)},e.preInit)for("function"==typeof e.preInit&&(e.preInit=[e.preInit]);0<e.preInit.length;)e.preInit.pop()();return Wt(),t.ready});t.exports=r},967:(t,e)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.iterateExtraOptions=void 0,e.iterateExtraOptions=(t,n,r,a)=>{if("object"==typeof t&&null!==t){if(r.has(t))throw new Error("Circular reference in options");r.add(t)}Object.entries(t).forEach((([t,i])=>{const o=n?n+t:t;if("object"==typeof i)(0,e.iterateExtraOptions)(i,o+".",r,a);else if("string"==typeof i||"number"==typeof i)a(o,i.toString());else{if("boolean"!=typeof i)throw new Error("Can\'t handle extra config type: "+typeof i);a(o,i?"1":"0")}}))}},586:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.setRunOptions=void 0;const r=n(967),a=n(983),i=n(361);e.setRunOptions=t=>{const e=(0,i.getInstance)();let n=0;const o=[],u=t||{};try{if(void 0===(null==t?void 0:t.logSeverityLevel))u.logSeverityLevel=2;else if("number"!=typeof t.logSeverityLevel||!Number.isInteger(t.logSeverityLevel)||t.logSeverityLevel<0||t.logSeverityLevel>4)throw new Error(`log serverity level is not valid: ${t.logSeverityLevel}`);if(void 0===(null==t?void 0:t.logVerbosityLevel))u.logVerbosityLevel=0;else if("number"!=typeof t.logVerbosityLevel||!Number.isInteger(t.logVerbosityLevel))throw new Error(`log verbosity level is not valid: ${t.logVerbosityLevel}`);void 0===(null==t?void 0:t.terminate)&&(u.terminate=!1);let i=0;if(void 0!==(null==t?void 0:t.tag)&&(i=(0,a.allocWasmString)(t.tag,o)),n=e._OrtCreateRunOptions(u.logSeverityLevel,u.logVerbosityLevel,!!u.terminate,i),0===n)throw new Error("Can\'t create run options");return void 0!==(null==t?void 0:t.extra)&&(0,r.iterateExtraOptions)(t.extra,"",new WeakSet,((t,r)=>{const i=(0,a.allocWasmString)(t,o),u=(0,a.allocWasmString)(r,o);if(0!==e._OrtAddRunConfigEntry(n,i,u))throw new Error(`Can\'t set a run config entry: ${t} - ${r}`)})),[n,o]}catch(t){throw 0!==n&&e._OrtReleaseRunOptions(n),o.forEach(e._free),t}}},919:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.setSessionOptions=void 0;const r=n(967),a=n(983),i=n(361);e.setSessionOptions=t=>{const e=(0,i.getInstance)();let n=0;const o=[],u=t||{};(t=>{t.extra||(t.extra={}),t.extra.session||(t.extra.session={});const e=t.extra.session;e.use_ort_model_bytes_directly||(e.use_ort_model_bytes_directly="1")})(u);try{void 0===(null==t?void 0:t.graphOptimizationLevel)&&(u.graphOptimizationLevel="all");const c=(t=>{switch(t){case"disabled":return 0;case"basic":return 1;case"extended":return 2;case"all":return 99;default:throw new Error(`unsupported graph optimization level: ${t}`)}})(u.graphOptimizationLevel);void 0===(null==t?void 0:t.enableCpuMemArena)&&(u.enableCpuMemArena=!0),void 0===(null==t?void 0:t.enableMemPattern)&&(u.enableMemPattern=!0),void 0===(null==t?void 0:t.executionMode)&&(u.executionMode="sequential");const s=(t=>{switch(t){case"sequential":return 0;case"parallel":return 1;default:throw new Error(`unsupported execution mode: ${t}`)}})(u.executionMode);let l=0;if(void 0!==(null==t?void 0:t.logId)&&(l=(0,a.allocWasmString)(t.logId,o)),void 0===(null==t?void 0:t.logSeverityLevel))u.logSeverityLevel=2;else if("number"!=typeof t.logSeverityLevel||!Number.isInteger(t.logSeverityLevel)||t.logSeverityLevel<0||t.logSeverityLevel>4)throw new Error(`log serverity level is not valid: ${t.logSeverityLevel}`);if(void 0===(null==t?void 0:t.logVerbosityLevel))u.logVerbosityLevel=0;else if("number"!=typeof t.logVerbosityLevel||!Number.isInteger(t.logVerbosityLevel))throw new Error(`log verbosity level is not valid: ${t.logVerbosityLevel}`);if(void 0===(null==t?void 0:t.enableProfiling)&&(u.enableProfiling=!1),n=e._OrtCreateSessionOptions(c,!!u.enableCpuMemArena,!!u.enableMemPattern,s,!!u.enableProfiling,0,l,u.logSeverityLevel,u.logVerbosityLevel),0===n)throw new Error("Can\'t create session options");return(null==t?void 0:t.executionProviders)&&((t,e,n)=>{for(const r of e){let e="string"==typeof r?r:r.name;switch(e){case"xnnpack":e="XNNPACK";break;case"wasm":case"cpu":continue;default:throw new Error(`not supported EP: ${e}`)}const o=(0,a.allocWasmString)(e,n);if(0!==(0,i.getInstance)()._OrtAppendExecutionProvider(t,o))throw new Error(`Can\'t append execution provider: ${e}`)}})(n,t.executionProviders,o),void 0!==(null==t?void 0:t.extra)&&(0,r.iterateExtraOptions)(t.extra,"",new WeakSet,((t,r)=>{const i=(0,a.allocWasmString)(t,o),u=(0,a.allocWasmString)(r,o);if(0!==e._OrtAddSessionConfigEntry(n,i,u))throw new Error(`Can\'t set a session config entry: ${t} - ${r}`)})),[n,o]}catch(t){throw 0!==n&&e._OrtReleaseSessionOptions(n),o.forEach(e._free),t}}},983:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.allocWasmString=void 0;const r=n(361);e.allocWasmString=(t,e)=>{const n=(0,r.getInstance)(),a=n.lengthBytesUTF8(t)+1,i=n._malloc(a);return n.stringToUTF8(t,i,a),e.push(i),i}},349:(t,e,n)=>{"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.extractTransferableBuffers=e.endProfiling=e.run=e.releaseSession=e.createSession=e.createSessionFinalize=e.createSessionAllocate=e.initOrt=void 0;const r=n(586),a=n(919),i=n(983),o=n(361);e.initOrt=(t,e)=>{const n=(0,o.getInstance)()._OrtInit(t,e);if(0!==n)throw new Error(`Can\'t initialize onnxruntime. error code = ${n}`)};const u=new Map;e.createSessionAllocate=t=>{const e=(0,o.getInstance)(),n=e._malloc(t.byteLength);return e.HEAPU8.set(t,n),[n,t.byteLength]},e.createSessionFinalize=(t,e)=>{const n=(0,o.getInstance)();let r=0,i=0,c=[];try{if([i,c]=(0,a.setSessionOptions)(e),r=n._OrtCreateSession(t[0],t[1],i),0===r)throw new Error("Can\'t create a session")}finally{n._free(t[0]),n._OrtReleaseSessionOptions(i),c.forEach(n._free)}const s=n._OrtGetInputCount(r),l=n._OrtGetOutputCount(r),f=[],p=[],h=[],d=[];for(let t=0;t<s;t++){const e=n._OrtGetInputName(r,t);if(0===e)throw new Error("Can\'t get an input name");p.push(e),f.push(n.UTF8ToString(e))}for(let t=0;t<l;t++){const e=n._OrtGetOutputName(r,t);if(0===e)throw new Error("Can\'t get an output name");d.push(e),h.push(n.UTF8ToString(e))}return u.set(r,[r,p,d]),[r,f,h]},e.createSession=(t,n)=>{const r=(0,e.createSessionAllocate)(t);return(0,e.createSessionFinalize)(r,n)},e.releaseSession=t=>{const e=(0,o.getInstance)(),n=u.get(t);if(!n)throw new Error("invalid session id");const r=n[0],a=n[1],i=n[2];a.forEach(e._OrtFree),i.forEach(e._OrtFree),e._OrtReleaseSession(r),u.delete(t)};const c=t=>{switch(t){case"int8":return 3;case"uint8":return 2;case"bool":return 9;case"int16":return 5;case"uint16":return 4;case"int32":return 6;case"uint32":return 12;case"float32":return 1;case"float64":return 11;case"string":return 8;case"int64":return 7;case"uint64":return 13;default:throw new Error(`unsupported data type: ${t}`)}},s=t=>{switch(t){case 3:return"int8";case 2:return"uint8";case 9:return"bool";case 5:return"int16";case 4:return"uint16";case 6:return"int32";case 12:return"uint32";case 1:return"float32";case 11:return"float64";case 8:return"string";case 7:return"int64";case 13:return"uint64";default:throw new Error(`unsupported data type: ${t}`)}},l=t=>{switch(t){case"float32":return Float32Array;case"uint8":case"bool":return Uint8Array;case"int8":return Int8Array;case"uint16":return Uint16Array;case"int16":return Int16Array;case"int32":return Int32Array;case"float64":return Float64Array;case"uint32":return Uint32Array;case"int64":return BigInt64Array;case"uint64":return BigUint64Array;default:throw new Error(`unsupported type: ${t}`)}};e.run=(t,e,n,a,f)=>{const p=(0,o.getInstance)(),h=u.get(t);if(!h)throw new Error("invalid session id");const d=h[0],y=h[1],b=h[2],m=e.length,g=a.length;let v=0,w=[];const _=[],O=[];try{[v,w]=(0,r.setRunOptions)(f);for(let t=0;t<m;t++){const e=n[t][0],r=n[t][1],a=n[t][2];let o,u;if(Array.isArray(a)){u=4*a.length,o=p._malloc(u),O.push(o);let t=o/4;for(let e=0;e<a.length;e++){if("string"!=typeof a[e])throw new TypeError(`tensor data at index ${e} is not a string`);p.HEAPU32[t++]=(0,i.allocWasmString)(a[e],O)}}else u=a.byteLength,o=p._malloc(u),O.push(o),p.HEAPU8.set(new Uint8Array(a.buffer,a.byteOffset,u),o);const s=p.stackSave(),l=p.stackAlloc(4*r.length);try{let t=l/4;r.forEach((e=>p.HEAP32[t++]=e));const n=p._OrtCreateTensor(c(e),o,u,l,r.length);if(0===n)throw new Error("Can\'t create a tensor");_.push(n)}finally{p.stackRestore(s)}}const t=p.stackSave(),o=p.stackAlloc(4*m),u=p.stackAlloc(4*m),h=p.stackAlloc(4*g),A=p.stackAlloc(4*g);try{let n=o/4,r=u/4,i=h/4,c=A/4;for(let t=0;t<m;t++)p.HEAPU32[n++]=_[t],p.HEAPU32[r++]=y[e[t]];for(let t=0;t<g;t++)p.HEAPU32[i++]=0,p.HEAPU32[c++]=b[a[t]];let f=p._OrtRun(d,u,o,m,A,g,h,v);const w=[];if(0===f)for(let t=0;t<g;t++){const e=p.HEAPU32[h/4+t],n=p.stackSave(),r=p.stackAlloc(16);let a,i=0;try{if(f=p._OrtGetTensorData(e,r,r+4,r+8,r+12),0!==f)throw new Error(`Can\'t access output tensor data. error code = ${f}`);let t=r/4;const o=p.HEAPU32[t++];i=p.HEAPU32[t++];const u=p.HEAPU32[t++],c=p.HEAPU32[t++],h=[];for(let t=0;t<c;t++)h.push(p.HEAPU32[u/4+t]);p._OrtFree(u);const d=0===h.length?1:h.reduce(((t,e)=>t*e));if(a=s(o),"string"===a){const t=[];let e=i/4;for(let n=0;n<d;n++){const r=p.HEAPU32[e++],a=n===d-1?void 0:p.HEAPU32[e]-r;t.push(p.UTF8ToString(r,a))}w.push([a,h,t])}else{const t=new(l(a))(d);new Uint8Array(t.buffer,t.byteOffset,t.byteLength).set(p.HEAPU8.subarray(i,i+t.byteLength)),w.push([a,h,t])}}finally{p.stackRestore(n),"string"===a&&i&&p._free(i),p._OrtReleaseTensor(e)}}if(0===f)return w;throw new Error(`failed to call OrtRun(). error code = ${f}.`)}finally{p.stackRestore(t)}}finally{_.forEach(p._OrtReleaseTensor),O.forEach(p._free),p._OrtReleaseRunOptions(v),w.forEach(p._free)}},e.endProfiling=t=>{const e=(0,o.getInstance)(),n=u.get(t);if(!n)throw new Error("invalid session id");const r=n[0],a=e._OrtEndProfiling(r);if(0===a)throw new Error("Can\'t get an profile file name");e._OrtFree(a)},e.extractTransferableBuffers=t=>{const e=[];for(const n of t){const t=n[2];!Array.isArray(t)&&t.buffer&&e.push(t.buffer)}return e}},361:function(t,e,n){"use strict";var r=this&&this.__createBinding||(Object.create?function(t,e,n,r){void 0===r&&(r=n);var a=Object.getOwnPropertyDescriptor(e,n);a&&!("get"in a?!e.__esModule:a.writable||a.configurable)||(a={enumerable:!0,get:function(){return e[n]}}),Object.defineProperty(t,r,a)}:function(t,e,n,r){void 0===r&&(r=n),t[r]=e[n]}),a=this&&this.__setModuleDefault||(Object.create?function(t,e){Object.defineProperty(t,"default",{enumerable:!0,value:e})}:function(t,e){t.default=e}),i=this&&this.__importStar||function(t){if(t&&t.__esModule)return t;var e={};if(null!=t)for(var n in t)"default"!==n&&Object.prototype.hasOwnProperty.call(t,n)&&r(e,t,n);return a(e,t),e},o=this&&this.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(e,"__esModule",{value:!0}),e.dispose=e.getInstance=e.initializeWebAssembly=void 0;const u=i(n(449)),c=o(n(932)),s=n(474);let l,f=!1,p=!1,h=!1;const d=(t,e)=>e?t?"ort-wasm-simd-threaded.wasm":"ort-wasm-threaded.wasm":t?"ort-wasm-simd.wasm":"ort-wasm.wasm";e.initializeWebAssembly=async t=>{if(f)return Promise.resolve();if(p)throw new Error("multiple calls to \'initializeWebAssembly()\' detected.");if(h)throw new Error("previous call to \'initializeWebAssembly()\' failed.");p=!0;const e=t.initTimeout,r=t.numThreads,a=t.simd,i=r>1&&(()=>{try{return"undefined"!=typeof SharedArrayBuffer&&("undefined"!=typeof MessageChannel&&(new MessageChannel).port1.postMessage(new SharedArrayBuffer(1)),WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,5,4,1,3,1,1,10,11,1,9,0,65,0,254,16,2,0,26,11])))}catch(t){return!1}})(),o=a&&(()=>{try{return WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,10,30,1,28,0,65,0,253,15,253,12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,253,186,1,26,11]))}catch(t){return!1}})(),y="string"==typeof t.wasmPaths?t.wasmPaths:void 0,b=d(!1,i),m=d(o,i),g="object"==typeof t.wasmPaths?t.wasmPaths[m]:void 0;let v=!1;const w=[];if(e>0&&w.push(new Promise((t=>{setTimeout((()=>{v=!0,t()}),e)}))),w.push(new Promise(((t,e)=>{const r=i?s:c.default,a={locateFile:(t,e)=>i&&t.endsWith(".worker.js")&&"undefined"!=typeof Blob?URL.createObjectURL(new Blob([n(154)],{type:"text/javascript"})):t===b?null!=g?g:(null!=y?y:e)+m:e+t};if(i)if("undefined"==typeof Blob)a.mainScriptUrlOrBlob=u.join("/","ort-wasm-threaded.js");else{const t=`var ortWasmThreaded=(function(){var _scriptDir;return ${r.toString()}})();`;a.mainScriptUrlOrBlob=new Blob([t],{type:"text/javascript"})}r(a).then((e=>{p=!1,f=!0,l=e,t()}),(t=>{p=!1,h=!0,e(t)}))}))),await Promise.race(w),v)throw new Error(`WebAssembly backend initializing failed due to timeout: ${e}ms`)},e.getInstance=()=>{if(f&&l)return l;throw new Error("WebAssembly is not initialized yet.")},e.dispose=()=>{var t;!f||p||h||(p=!0,null===(t=l.PThread)||void 0===t||t.terminateAllThreads(),l=void 0,p=!1,f=!1,h=!0)}},154:t=>{"use strict";t.exports=\'"use strict";var e={},t="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node;if(t){var r=require("worker_threads"),a=r.parentPort;a.on("message",(e=>onmessage({data:e})));var o=require("fs");Object.assign(global,{self:global,require:require,Module:e,location:{href:__filename},Worker:r.Worker,importScripts:function(e){(0,eval)(o.readFileSync(e,"utf8"))},postMessage:function(e){a.postMessage(e)},performance:global.performance||{now:function(){return Date.now()}}})}var s=!1,n=[],i=function(){var e=Array.prototype.slice.call(arguments).join(" ");t?o.writeSync(2,e+"\\\\n"):console.error(e)};self.alert=function(){var t=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:t,threadId:e._pthread_self()})},e.instantiateWasm=(t,r)=>{var a=new WebAssembly.Instance(e.wasmModule,t);return r(a),e.wasmModule=null,a.exports},self.onunhandledrejection=e=>{throw e.reason??e},self.onmessage=t=>{try{if("load"===t.data.cmd){if(e.wasmModule=t.data.wasmModule,e.wasmMemory=t.data.wasmMemory,e.buffer=e.wasmMemory.buffer,e.ENVIRONMENT_IS_PTHREAD=!0,"string"==typeof t.data.urlOrBlob)importScripts(t.data.urlOrBlob);else{var r=URL.createObjectURL(t.data.urlOrBlob);importScripts(r),URL.revokeObjectURL(r)}ortWasmThreaded(e).then((function(t){e=t}))}else if("run"===t.data.cmd){e.__performance_now_clock_drift=performance.now()-t.data.time,e.__emscripten_thread_init(t.data.pthread_ptr,0,0,1),e.establishStackSpace(),e.PThread.receiveObjectTransfer(t.data),e.PThread.threadInitTLS(),s||(n.forEach((t=>{e.executeNotifiedProxyingQueue(t)})),n=[],s=!0);try{e.invokeEntryPoint(t.data.start_routine,t.data.arg)}catch(t){if("unwind"!=t){if(!(t instanceof e.ExitStatus))throw t;e.keepRuntimeAlive()||e.__emscripten_thread_exit(t.status)}}}else"cancel"===t.data.cmd?e._pthread_self()&&e.__emscripten_thread_exit(-1):"setimmediate"===t.data.target||("processProxyingQueue"===t.data.cmd?s?e.executeNotifiedProxyingQueue(t.data.queue):n.push(t.data.queue):(i("worker.js received unknown command "+t.data.cmd),i(t.data)))}catch(t){throw i("worker.js onmessage() captured an uncaught exception: "+t),t&&t.stack&&i(t.stack),e.__emscripten_thread_crashed&&e.__emscripten_thread_crashed(),t}};\\n\'},384:()=>{},993:()=>{},908:()=>{},953:()=>{},925:()=>{},449:()=>{}},e={};function n(r){var a=e[r];if(void 0!==a)return a.exports;var i=e[r]={exports:{}};return t[r].call(i.exports,i,i.exports,n),i.exports}n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),(()=>{"use strict";const t=n(349),e=n(361);self.onmessage=n=>{switch(n.data.type){case"init-wasm":(0,e.initializeWebAssembly)(n.data.in).then((()=>postMessage({type:"init-wasm"})),(t=>postMessage({type:"init-wasm",err:t})));break;case"init-ort":try{const{numThreads:e,loggingLevel:r}=n.data.in;(0,t.initOrt)(e,r),postMessage({type:"init-ort"})}catch(t){postMessage({type:"init-ort",err:t})}break;case"create_allocate":try{const{model:e}=n.data.in,r=(0,t.createSessionAllocate)(e);postMessage({type:"create_allocate",out:r})}catch(t){postMessage({type:"create_allocate",err:t})}break;case"create_finalize":try{const{modeldata:e,options:r}=n.data.in,a=(0,t.createSessionFinalize)(e,r);postMessage({type:"create_finalize",out:a})}catch(t){postMessage({type:"create_finalize",err:t})}break;case"create":try{const{model:e,options:r}=n.data.in,a=(0,t.createSession)(e,r);postMessage({type:"create",out:a})}catch(t){postMessage({type:"create",err:t})}break;case"release":try{const e=n.data.in;(0,t.releaseSession)(e),postMessage({type:"release"})}catch(t){postMessage({type:"release",err:t})}break;case"run":try{const{sessionId:e,inputIndices:r,inputs:a,outputIndices:i,options:o}=n.data.in,u=(0,t.run)(e,r,a,i,o);postMessage({type:"run",out:u},(0,t.extractTransferableBuffers)(u))}catch(t){postMessage({type:"run",err:t})}break;case"end-profiling":try{const e=n.data.in;(0,t.endProfiling)(e),postMessage({type:"end-profiling"})}catch(t){postMessage({type:"end-profiling",err:t})}}}})()})();\n',"Worker",void 0,void 0)}},477:t=>{"use strict";t.exports=function(t,e,n,r){var i=self||window;try{try{var o;try{o=new i.Blob([t])}catch(e){(o=new(i.BlobBuilder||i.WebKitBlobBuilder||i.MozBlobBuilder||i.MSBlobBuilder)).append(t),o=o.getBlob()}var a=i.URL||i.webkitURL,s=a.createObjectURL(o),u=new i[e](s,n);return a.revokeObjectURL(s),u}catch(r){return new i[e]("data:application/javascript,".concat(encodeURIComponent(t)),n)}}catch(t){if(!r)throw Error("Inline worker is not supported");return new i[e](r,n)}}},4154:t=>{"use strict";t.exports='"use strict";var e={},t="object"==typeof process&&"object"==typeof process.versions&&"string"==typeof process.versions.node;if(t){var r=require("worker_threads"),a=r.parentPort;a.on("message",(e=>onmessage({data:e})));var o=require("fs");Object.assign(global,{self:global,require:require,Module:e,location:{href:__filename},Worker:r.Worker,importScripts:function(e){(0,eval)(o.readFileSync(e,"utf8"))},postMessage:function(e){a.postMessage(e)},performance:global.performance||{now:function(){return Date.now()}}})}var s=!1,n=[],i=function(){var e=Array.prototype.slice.call(arguments).join(" ");t?o.writeSync(2,e+"\\n"):console.error(e)};self.alert=function(){var t=Array.prototype.slice.call(arguments).join(" ");postMessage({cmd:"alert",text:t,threadId:e._pthread_self()})},e.instantiateWasm=(t,r)=>{var a=new WebAssembly.Instance(e.wasmModule,t);return r(a),e.wasmModule=null,a.exports},self.onunhandledrejection=e=>{throw e.reason??e},self.onmessage=t=>{try{if("load"===t.data.cmd){if(e.wasmModule=t.data.wasmModule,e.wasmMemory=t.data.wasmMemory,e.buffer=e.wasmMemory.buffer,e.ENVIRONMENT_IS_PTHREAD=!0,"string"==typeof t.data.urlOrBlob)importScripts(t.data.urlOrBlob);else{var r=URL.createObjectURL(t.data.urlOrBlob);importScripts(r),URL.revokeObjectURL(r)}ortWasmThreaded(e).then((function(t){e=t}))}else if("run"===t.data.cmd){e.__performance_now_clock_drift=performance.now()-t.data.time,e.__emscripten_thread_init(t.data.pthread_ptr,0,0,1),e.establishStackSpace(),e.PThread.receiveObjectTransfer(t.data),e.PThread.threadInitTLS(),s||(n.forEach((t=>{e.executeNotifiedProxyingQueue(t)})),n=[],s=!0);try{e.invokeEntryPoint(t.data.start_routine,t.data.arg)}catch(t){if("unwind"!=t){if(!(t instanceof e.ExitStatus))throw t;e.keepRuntimeAlive()||e.__emscripten_thread_exit(t.status)}}}else"cancel"===t.data.cmd?e._pthread_self()&&e.__emscripten_thread_exit(-1):"setimmediate"===t.data.target||("processProxyingQueue"===t.data.cmd?s?e.executeNotifiedProxyingQueue(t.data.queue):n.push(t.data.queue):(i("worker.js received unknown command "+t.data.cmd),i(t.data)))}catch(t){throw i("worker.js onmessage() captured an uncaught exception: "+t),t&&t.stack&&i(t.stack),e.__emscripten_thread_crashed&&e.__emscripten_thread_crashed(),t}};\n'},7067:()=>{},1296:()=>{},1384:()=>{},3993:()=>{},908:()=>{},6953:()=>{},9925:()=>{},2806:()=>{},6449:()=>{},2850:()=>{},5381:()=>{},5686:(t,e,n)=>{"use strict";n.r(e),n.d(e,{flatbuffers:()=>r});var r={};r.Offset,r.Table,r.SIZEOF_SHORT=2,r.SIZEOF_INT=4,r.FILE_IDENTIFIER_LENGTH=4,r.SIZE_PREFIX_LENGTH=4,r.Encoding={UTF8_BYTES:1,UTF16_STRING:2},r.int32=new Int32Array(2),r.float32=new Float32Array(r.int32.buffer),r.float64=new Float64Array(r.int32.buffer),r.isLittleEndian=1===new Uint16Array(new Uint8Array([1,0]).buffer)[0],r.Long=function(t,e){this.low=0|t,this.high=0|e},r.Long.create=function(t,e){return 0==t&&0==e?r.Long.ZERO:new r.Long(t,e)},r.Long.prototype.toFloat64=function(){return(this.low>>>0)+4294967296*this.high},r.Long.prototype.equals=function(t){return this.low==t.low&&this.high==t.high},r.Long.ZERO=new r.Long(0,0),r.Builder=function(t){if(t)e=t;else var e=1024;this.bb=r.ByteBuffer.allocate(e),this.space=e,this.minalign=1,this.vtable=null,this.vtable_in_use=0,this.isNested=!1,this.object_start=0,this.vtables=[],this.vector_num_elems=0,this.force_defaults=!1},r.Builder.prototype.clear=function(){this.bb.clear(),this.space=this.bb.capacity(),this.minalign=1,this.vtable=null,this.vtable_in_use=0,this.isNested=!1,this.object_start=0,this.vtables=[],this.vector_num_elems=0,this.force_defaults=!1},r.Builder.prototype.forceDefaults=function(t){this.force_defaults=t},r.Builder.prototype.dataBuffer=function(){return this.bb},r.Builder.prototype.asUint8Array=function(){return this.bb.bytes().subarray(this.bb.position(),this.bb.position()+this.offset())},r.Builder.prototype.prep=function(t,e){t>this.minalign&&(this.minalign=t);for(var n=1+~(this.bb.capacity()-this.space+e)&t-1;this.space<n+t+e;){var i=this.bb.capacity();this.bb=r.Builder.growByteBuffer(this.bb),this.space+=this.bb.capacity()-i}this.pad(n)},r.Builder.prototype.pad=function(t){for(var e=0;e<t;e++)this.bb.writeInt8(--this.space,0)},r.Builder.prototype.writeInt8=function(t){this.bb.writeInt8(this.space-=1,t)},r.Builder.prototype.writeInt16=function(t){this.bb.writeInt16(this.space-=2,t)},r.Builder.prototype.writeInt32=function(t){this.bb.writeInt32(this.space-=4,t)},r.Builder.prototype.writeInt64=function(t){this.bb.writeInt64(this.space-=8,t)},r.Builder.prototype.writeFloat32=function(t){this.bb.writeFloat32(this.space-=4,t)},r.Builder.prototype.writeFloat64=function(t){this.bb.writeFloat64(this.space-=8,t)},r.Builder.prototype.addInt8=function(t){this.prep(1,0),this.writeInt8(t)},r.Builder.prototype.addInt16=function(t){this.prep(2,0),this.writeInt16(t)},r.Builder.prototype.addInt32=function(t){this.prep(4,0),this.writeInt32(t)},r.Builder.prototype.addInt64=function(t){this.prep(8,0),this.writeInt64(t)},r.Builder.prototype.addFloat32=function(t){this.prep(4,0),this.writeFloat32(t)},r.Builder.prototype.addFloat64=function(t){this.prep(8,0),this.writeFloat64(t)},r.Builder.prototype.addFieldInt8=function(t,e,n){(this.force_defaults||e!=n)&&(this.addInt8(e),this.slot(t))},r.Builder.prototype.addFieldInt16=function(t,e,n){(this.force_defaults||e!=n)&&(this.addInt16(e),this.slot(t))},r.Builder.prototype.addFieldInt32=function(t,e,n){(this.force_defaults||e!=n)&&(this.addInt32(e),this.slot(t))},r.Builder.prototype.addFieldInt64=function(t,e,n){!this.force_defaults&&e.equals(n)||(this.addInt64(e),this.slot(t))},r.Builder.prototype.addFieldFloat32=function(t,e,n){(this.force_defaults||e!=n)&&(this.addFloat32(e),this.slot(t))},r.Builder.prototype.addFieldFloat64=function(t,e,n){(this.force_defaults||e!=n)&&(this.addFloat64(e),this.slot(t))},r.Builder.prototype.addFieldOffset=function(t,e,n){(this.force_defaults||e!=n)&&(this.addOffset(e),this.slot(t))},r.Builder.prototype.addFieldStruct=function(t,e,n){e!=n&&(this.nested(e),this.slot(t))},r.Builder.prototype.nested=function(t){if(t!=this.offset())throw new Error("FlatBuffers: struct must be serialized inline.")},r.Builder.prototype.notNested=function(){if(this.isNested)throw new Error("FlatBuffers: object serialization must not be nested.")},r.Builder.prototype.slot=function(t){this.vtable[t]=this.offset()},r.Builder.prototype.offset=function(){return this.bb.capacity()-this.space},r.Builder.growByteBuffer=function(t){var e=t.capacity();if(3221225472&e)throw new Error("FlatBuffers: cannot grow buffer beyond 2 gigabytes.");var n=e<<1,i=r.ByteBuffer.allocate(n);return i.setPosition(n-e),i.bytes().set(t.bytes(),n-e),i},r.Builder.prototype.addOffset=function(t){this.prep(r.SIZEOF_INT,0),this.writeInt32(this.offset()-t+r.SIZEOF_INT)},r.Builder.prototype.startObject=function(t){this.notNested(),null==this.vtable&&(this.vtable=[]),this.vtable_in_use=t;for(var e=0;e<t;e++)this.vtable[e]=0;this.isNested=!0,this.object_start=this.offset()},r.Builder.prototype.endObject=function(){if(null==this.vtable||!this.isNested)throw new Error("FlatBuffers: endObject called without startObject");this.addInt32(0);for(var t=this.offset(),e=this.vtable_in_use-1;e>=0&&0==this.vtable[e];e--);for(var n=e+1;e>=0;e--)this.addInt16(0!=this.vtable[e]?t-this.vtable[e]:0);this.addInt16(t-this.object_start);var i=(n+2)*r.SIZEOF_SHORT;this.addInt16(i);var o=0,a=this.space;t:for(e=0;e<this.vtables.length;e++){var s=this.bb.capacity()-this.vtables[e];if(i==this.bb.readInt16(s)){for(var u=r.SIZEOF_SHORT;u<i;u+=r.SIZEOF_SHORT)if(this.bb.readInt16(a+u)!=this.bb.readInt16(s+u))continue t;o=this.vtables[e];break}}return o?(this.space=this.bb.capacity()-t,this.bb.writeInt32(this.space,o-t)):(this.vtables.push(this.offset()),this.bb.writeInt32(this.bb.capacity()-t,this.offset()-t)),this.isNested=!1,t},r.Builder.prototype.finish=function(t,e,n){var i=n?r.SIZE_PREFIX_LENGTH:0;if(e){var o=e;if(this.prep(this.minalign,r.SIZEOF_INT+r.FILE_IDENTIFIER_LENGTH+i),o.length!=r.FILE_IDENTIFIER_LENGTH)throw new Error("FlatBuffers: file identifier must be length "+r.FILE_IDENTIFIER_LENGTH);for(var a=r.FILE_IDENTIFIER_LENGTH-1;a>=0;a--)this.writeInt8(o.charCodeAt(a))}this.prep(this.minalign,r.SIZEOF_INT+i),this.addOffset(t),i&&this.addInt32(this.bb.capacity()-this.space),this.bb.setPosition(this.space)},r.Builder.prototype.finishSizePrefixed=function(t,e){this.finish(t,e,!0)},r.Builder.prototype.requiredField=function(t,e){var n=this.bb.capacity()-t,r=n-this.bb.readInt32(n);if(0==this.bb.readInt16(r+e))throw new Error("FlatBuffers: field "+e+" must be set")},r.Builder.prototype.startVector=function(t,e,n){this.notNested(),this.vector_num_elems=e,this.prep(r.SIZEOF_INT,t*e),this.prep(n,t*e)},r.Builder.prototype.endVector=function(){return this.writeInt32(this.vector_num_elems),this.offset()},r.Builder.prototype.createString=function(t){if(t instanceof Uint8Array)var e=t;else{e=[];for(var n=0;n<t.length;){var r,i=t.charCodeAt(n++);(r=i<55296||i>=56320?i:(i<<10)+t.charCodeAt(n++)+-56613888)<128?e.push(r):(r<2048?e.push(r>>6&31|192):(r<65536?e.push(r>>12&15|224):e.push(r>>18&7|240,r>>12&63|128),e.push(r>>6&63|128)),e.push(63&r|128))}}this.addInt8(0),this.startVector(1,e.length,1),this.bb.setPosition(this.space-=e.length),n=0;for(var o=this.space,a=this.bb.bytes();n<e.length;n++)a[o++]=e[n];return this.endVector()},r.Builder.prototype.createLong=function(t,e){return r.Long.create(t,e)},r.ByteBuffer=function(t){this.bytes_=t,this.position_=0},r.ByteBuffer.allocate=function(t){return new r.ByteBuffer(new Uint8Array(t))},r.ByteBuffer.prototype.clear=function(){this.position_=0},r.ByteBuffer.prototype.bytes=function(){return this.bytes_},r.ByteBuffer.prototype.position=function(){return this.position_},r.ByteBuffer.prototype.setPosition=function(t){this.position_=t},r.ByteBuffer.prototype.capacity=function(){return this.bytes_.length},r.ByteBuffer.prototype.readInt8=function(t){return this.readUint8(t)<<24>>24},r.ByteBuffer.prototype.readUint8=function(t){return this.bytes_[t]},r.ByteBuffer.prototype.readInt16=function(t){return this.readUint16(t)<<16>>16},r.ByteBuffer.prototype.readUint16=function(t){return this.bytes_[t]|this.bytes_[t+1]<<8},r.ByteBuffer.prototype.readInt32=function(t){return this.bytes_[t]|this.bytes_[t+1]<<8|this.bytes_[t+2]<<16|this.bytes_[t+3]<<24},r.ByteBuffer.prototype.readUint32=function(t){return this.readInt32(t)>>>0},r.ByteBuffer.prototype.readInt64=function(t){return new r.Long(this.readInt32(t),this.readInt32(t+4))},r.ByteBuffer.prototype.readUint64=function(t){return new r.Long(this.readUint32(t),this.readUint32(t+4))},r.ByteBuffer.prototype.readFloat32=function(t){return r.int32[0]=this.readInt32(t),r.float32[0]},r.ByteBuffer.prototype.readFloat64=function(t){return r.int32[r.isLittleEndian?0:1]=this.readInt32(t),r.int32[r.isLittleEndian?1:0]=this.readInt32(t+4),r.float64[0]},r.ByteBuffer.prototype.writeInt8=function(t,e){this.bytes_[t]=e},r.ByteBuffer.prototype.writeUint8=function(t,e){this.bytes_[t]=e},r.ByteBuffer.prototype.writeInt16=function(t,e){this.bytes_[t]=e,this.bytes_[t+1]=e>>8},r.ByteBuffer.prototype.writeUint16=function(t,e){this.bytes_[t]=e,this.bytes_[t+1]=e>>8},r.ByteBuffer.prototype.writeInt32=function(t,e){this.bytes_[t]=e,this.bytes_[t+1]=e>>8,this.bytes_[t+2]=e>>16,this.bytes_[t+3]=e>>24},r.ByteBuffer.prototype.writeUint32=function(t,e){this.bytes_[t]=e,this.bytes_[t+1]=e>>8,this.bytes_[t+2]=e>>16,this.bytes_[t+3]=e>>24},r.ByteBuffer.prototype.writeInt64=function(t,e){this.writeInt32(t,e.low),this.writeInt32(t+4,e.high)},r.ByteBuffer.prototype.writeUint64=function(t,e){this.writeUint32(t,e.low),this.writeUint32(t+4,e.high)},r.ByteBuffer.prototype.writeFloat32=function(t,e){r.float32[0]=e,this.writeInt32(t,r.int32[0])},r.ByteBuffer.prototype.writeFloat64=function(t,e){r.float64[0]=e,this.writeInt32(t,r.int32[r.isLittleEndian?0:1]),this.writeInt32(t+4,r.int32[r.isLittleEndian?1:0])},r.ByteBuffer.prototype.getBufferIdentifier=function(){if(this.bytes_.length<this.position_+r.SIZEOF_INT+r.FILE_IDENTIFIER_LENGTH)throw new Error("FlatBuffers: ByteBuffer is too short to contain an identifier.");for(var t="",e=0;e<r.FILE_IDENTIFIER_LENGTH;e++)t+=String.fromCharCode(this.readInt8(this.position_+r.SIZEOF_INT+e));return t},r.ByteBuffer.prototype.__offset=function(t,e){var n=t-this.readInt32(t);return e<this.readInt16(n)?this.readInt16(n+e):0},r.ByteBuffer.prototype.__union=function(t,e){return t.bb_pos=e+this.readInt32(e),t.bb=this,t},r.ByteBuffer.prototype.__string=function(t,e){t+=this.readInt32(t);var n=this.readInt32(t),i="",o=0;if(t+=r.SIZEOF_INT,e===r.Encoding.UTF8_BYTES)return this.bytes_.subarray(t,t+n);for(;o<n;){var a,s=this.readUint8(t+o++);if(s<192)a=s;else{var u=this.readUint8(t+o++);if(s<224)a=(31&s)<<6|63&u;else{var c=this.readUint8(t+o++);a=s<240?(15&s)<<12|(63&u)<<6|63&c:(7&s)<<18|(63&u)<<12|(63&c)<<6|63&this.readUint8(t+o++)}}a<65536?i+=String.fromCharCode(a):(a-=65536,i+=String.fromCharCode(55296+(a>>10),56320+(1023&a)))}return i},r.ByteBuffer.prototype.__indirect=function(t){return t+this.readInt32(t)},r.ByteBuffer.prototype.__vector=function(t){return t+this.readInt32(t)+r.SIZEOF_INT},r.ByteBuffer.prototype.__vector_len=function(t){return this.readInt32(t+this.readInt32(t))},r.ByteBuffer.prototype.__has_identifier=function(t){if(t.length!=r.FILE_IDENTIFIER_LENGTH)throw new Error("FlatBuffers: file identifier must be length "+r.FILE_IDENTIFIER_LENGTH);for(var e=0;e<r.FILE_IDENTIFIER_LENGTH;e++)if(t.charCodeAt(e)!=this.readInt8(this.position_+r.SIZEOF_INT+e))return!1;return!0},r.ByteBuffer.prototype.createLong=function(t,e){return r.Long.create(t,e)}}},__webpack_module_cache__={};function __webpack_require__(t){var e=__webpack_module_cache__[t];if(void 0!==e)return e.exports;var n=__webpack_module_cache__[t]={exports:{}};return __webpack_modules__[t].call(n.exports,n,n.exports,__webpack_require__),n.exports}__webpack_require__.n=t=>{var e=t&&t.__esModule?()=>t.default:()=>t;return __webpack_require__.d(e,{a:e}),e},__webpack_require__.d=(t,e)=>{for(var n in e)__webpack_require__.o(e,n)&&!__webpack_require__.o(t,n)&&Object.defineProperty(t,n,{enumerable:!0,get:e[n]})},__webpack_require__.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(t){if("object"==typeof window)return window}}(),__webpack_require__.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),__webpack_require__.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})};var __webpack_exports__=__webpack_require__(6018);return __webpack_exports__})()));
//# sourceMappingURL=ort.min.js.mapPK
!<���pXpX0chrome/toolkit/content/global/ml/transformers.jsimport*as e from"chrome://global/content/ml/ort.js";var t={"onnxruntime-web":
/*!****************************************************!*\
  !*** external "chrome://global/content/ml/ort.js" ***!
  \****************************************************/t=>{t.exports=e},"?7a2c":
/*!********************!*\
  !*** fs (ignored) ***!
  \********************/()=>{},"?a42a":
/*!**********************!*\
  !*** path (ignored) ***!
  \**********************/()=>{},"?2b25":
/*!***********************!*\
  !*** sharp (ignored) ***!
  \***********************/()=>{},"?e65c":
/*!****************************!*\
  !*** stream/web (ignored) ***!
  \****************************/()=>{},"?569f":
/*!********************!*\
  !*** fs (ignored) ***!
  \********************/()=>{},"?3f59":
/*!**********************!*\
  !*** path (ignored) ***!
  \**********************/()=>{},"?154a":
/*!*********************!*\
  !*** url (ignored) ***!
  \*********************/()=>{},"./node_modules/@huggingface/jinja/dist/index.js":
/*!*******************************************************!*\
  !*** ./node_modules/@huggingface/jinja/dist/index.js ***!
  \*******************************************************/(e,t,s)=>{s.r(t),s.d(t,{Environment:()=>X,Interpreter:()=>Q,Template:()=>Y,parse:()=>z,tokenize:()=>d});var o=Object.freeze({Text:"Text",NumericLiteral:"NumericLiteral",BooleanLiteral:"BooleanLiteral",StringLiteral:"StringLiteral",Identifier:"Identifier",Equals:"Equals",OpenParen:"OpenParen",CloseParen:"CloseParen",OpenStatement:"OpenStatement",CloseStatement:"CloseStatement",OpenExpression:"OpenExpression",CloseExpression:"CloseExpression",OpenSquareBracket:"OpenSquareBracket",CloseSquareBracket:"CloseSquareBracket",OpenCurlyBracket:"OpenCurlyBracket",CloseCurlyBracket:"CloseCurlyBracket",Comma:"Comma",Dot:"Dot",Colon:"Colon",Pipe:"Pipe",CallOperator:"CallOperator",AdditiveBinaryOperator:"AdditiveBinaryOperator",MultiplicativeBinaryOperator:"MultiplicativeBinaryOperator",ComparisonBinaryOperator:"ComparisonBinaryOperator",UnaryOperator:"UnaryOperator",Set:"Set",If:"If",For:"For",In:"In",Is:"Is",NotIn:"NotIn",Else:"Else",EndIf:"EndIf",ElseIf:"ElseIf",EndFor:"EndFor",And:"And",Or:"Or",Not:"UnaryOperator"}),n=Object.freeze({set:o.Set,for:o.For,in:o.In,is:o.Is,if:o.If,else:o.Else,endif:o.EndIf,elif:o.ElseIf,endfor:o.EndFor,and:o.And,or:o.Or,not:o.Not,"not in":o.NotIn,true:o.BooleanLiteral,false:o.BooleanLiteral}),r=class{constructor(e,t){this.value=e,this.type=t}};function a(e){return/\w/.test(e)}function i(e){return/[0-9]/.test(e)}var l=[["{%",o.OpenStatement],["%}",o.CloseStatement],["{{",o.OpenExpression],["}}",o.CloseExpression],["(",o.OpenParen],[")",o.CloseParen],["{",o.OpenCurlyBracket],["}",o.CloseCurlyBracket],["[",o.OpenSquareBracket],["]",o.CloseSquareBracket],[",",o.Comma],[".",o.Dot],[":",o.Colon],["|",o.Pipe],["<=",o.ComparisonBinaryOperator],[">=",o.ComparisonBinaryOperator],["==",o.ComparisonBinaryOperator],["!=",o.ComparisonBinaryOperator],["<",o.ComparisonBinaryOperator],[">",o.ComparisonBinaryOperator],["+",o.AdditiveBinaryOperator],["-",o.AdditiveBinaryOperator],["*",o.MultiplicativeBinaryOperator],["/",o.MultiplicativeBinaryOperator],["%",o.MultiplicativeBinaryOperator],["=",o.Equals]],c=new Map([["n","\n"],["t","\t"],["r","\r"],["b","\b"],["f","\f"],["v","\v"],["'","'"],['"','"'],["\\","\\"]]);function d(e,t={}){const s=[],d=function(e,t={}){return e.endsWith("\n")&&(e=e.slice(0,-1)),e=e.replace(/{#.*?#}/gs,"{##}"),t.lstrip_blocks&&(e=e.replace(/^[ \t]*({[#%])/gm,"$1")),t.trim_blocks&&(e=e.replace(/([#%]})\n/g,"$1")),e.replace(/{##}/g,"").replace(/-%}\s*/g,"%}").replace(/\s*{%-/g,"{%").replace(/-}}\s*/g,"}}").replace(/\s*{{-/g,"{{")}(e,t);let u=0;const h=e=>{let t="";for(;e(d[u]);)if("\\"!==d[u]){if(t+=d[u++],u>=d.length)throw new SyntaxError("Unexpected end of input")}else{if(++u,u>=d.length)throw new SyntaxError("Unexpected end of input");const e=d[u++],s=c.get(e);if(void 0===s)throw new SyntaxError(`Unexpected escaped character: ${e}`);t+=s}return t};e:for(;u<d.length;){const e=s.at(-1)?.type;if(void 0===e||e===o.CloseStatement||e===o.CloseExpression){let e="";for(;u<d.length&&("{"!==d[u]||"%"!==d[u+1]&&"{"!==d[u+1]);)e+=d[u++];if(e.length>0){s.push(new r(e,o.Text));continue}}h((e=>/\s/.test(e)));const t=d[u];if("-"===t||"+"===t){const e=s.at(-1)?.type;if(e===o.Text||void 0===e)throw new SyntaxError(`Unexpected character: ${t}`);switch(e){case o.Identifier:case o.NumericLiteral:case o.BooleanLiteral:case o.StringLiteral:case o.CloseParen:case o.CloseSquareBracket:break;default:{++u;const e=h(i);s.push(new r(`${t}${e}`,e.length>0?o.NumericLiteral:o.UnaryOperator));continue}}}for(const[e,t]of l){if(d.slice(u,u+e.length)===e){s.push(new r(e,t)),u+=e.length;continue e}}if("'"!==t&&'"'!==t)if(i(t)){const e=h(i);s.push(new r(e,o.NumericLiteral))}else{if(!a(t))throw new SyntaxError(`Unexpected character: ${t}`);{const e=h(a),t=Object.hasOwn(n,e)?n[e]:o.Identifier;t===o.In&&s.at(-1)?.type===o.Not?(s.pop(),s.push(new r("not in",o.NotIn))):s.push(new r(e,t))}}else{++u;const e=h((e=>e!==t));s.push(new r(e,o.StringLiteral)),++u}}return s}var u=class{type="Statement"},h=class extends u{constructor(e){super(),this.body=e}type="Program"},p=class extends u{constructor(e,t,s){super(),this.test=e,this.body=t,this.alternate=s}type="If"},_=class extends u{constructor(e,t,s){super(),this.loopvar=e,this.iterable=t,this.body=s}type="For"},m=class extends u{constructor(e,t){super(),this.assignee=e,this.value=t}type="Set"},f=class extends u{type="Expression"},g=class extends f{constructor(e,t,s){super(),this.object=e,this.property=t,this.computed=s}type="MemberExpression"},M=class extends f{constructor(e,t){super(),this.callee=e,this.args=t}type="CallExpression"},w=class extends f{constructor(e){super(),this.value=e}type="Identifier"},T=class extends f{constructor(e){super(),this.value=e}type="Literal"},k=class extends T{type="NumericLiteral"},b=class extends T{type="StringLiteral"},x=class extends T{type="BooleanLiteral"},y=class extends T{type="ArrayLiteral"},F=class extends T{type="TupleLiteral"},C=class extends T{type="ObjectLiteral"},P=class extends f{constructor(e,t,s){super(),this.operator=e,this.left=t,this.right=s}type="BinaryExpression"},v=class extends f{constructor(e,t){super(),this.operand=e,this.filter=t}type="FilterExpression"},S=class extends f{constructor(e,t,s){super(),this.operand=e,this.negate=t,this.test=s}type="TestExpression"},A=class extends f{constructor(e,t){super(),this.operator=e,this.argument=t}type="UnaryExpression"},L=class extends f{constructor(e=void 0,t=void 0,s=void 0){super(),this.start=e,this.stop=t,this.step=s}type="SliceExpression"},E=class extends f{constructor(e,t){super(),this.key=e,this.value=t}type="KeywordArgumentExpression"};function z(e){const t=new h([]);let s=0;function n(t,o){const n=e[s++];if(!n||n.type!==t)throw new Error(`Parser Error: ${o}. ${n.type} !== ${t}.`);return n}function r(){switch(e[s].type){case o.Text:return new b(n(o.Text,"Expected text token").value);case o.OpenStatement:return function(){let t;switch(n(o.OpenStatement,"Expected opening statement token"),e[s].type){case o.Set:++s,t=l(),n(o.CloseStatement,"Expected closing statement token");break;case o.If:++s,t=c(),n(o.OpenStatement,"Expected {% token"),n(o.EndIf,"Expected endif token"),n(o.CloseStatement,"Expected %} token");break;case o.For:++s,t=function(){const e=d(!0);if(!(e instanceof w||e instanceof F))throw new SyntaxError(`Expected identifier/tuple for the loop variable, got ${e.type} instead`);n(o.In,"Expected `in` keyword following loop variable");const t=u();n(o.CloseStatement,"Expected closing statement token");const s=[];for(;a(o.OpenStatement,o.EndFor);)s.push(r());return new _(e,t,s)}(),n(o.OpenStatement,"Expected {% token"),n(o.EndFor,"Expected endfor token"),n(o.CloseStatement,"Expected %} token");break;default:throw new SyntaxError(`Unknown statement type: ${e[s].type}`)}return t}();case o.OpenExpression:return function(){n(o.OpenExpression,"Expected opening expression token");const e=u();return n(o.CloseExpression,"Expected closing expression token"),e}();default:throw new SyntaxError(`Unexpected token type: ${e[s].type}`)}}function a(...t){return s+t.length<=e.length&&t.some(((t,o)=>t!==e[s+o].type))}function i(...t){return s+t.length<=e.length&&t.every(((t,o)=>t===e[s+o].type))}function l(){const e=u();if(i(o.Equals)){++s;const t=l();return new m(e,t)}return e}function c(){const t=u();n(o.CloseStatement,"Expected closing statement token");const a=[],l=[];for(;e[s]?.type!==o.OpenStatement||e[s+1]?.type!==o.ElseIf&&e[s+1]?.type!==o.Else&&e[s+1]?.type!==o.EndIf;)a.push(r());if(e[s]?.type===o.OpenStatement&&e[s+1]?.type!==o.EndIf)if(++s,i(o.ElseIf))n(o.ElseIf,"Expected elseif token"),l.push(c());else for(n(o.Else,"Expected else token"),n(o.CloseStatement,"Expected closing statement token");e[s]?.type!==o.OpenStatement||e[s+1]?.type!==o.EndIf;)l.push(r());return new p(t,a,l)}function d(e=!1){const t=e?q:u,n=[t()],r=i(o.Comma);for(;r&&(++s,n.push(t()),i(o.Comma)););return r?new F(n):n[0]}function u(){return function(){const e=f();if(i(o.If)){++s;const t=f();n(o.Else,"Expected else token");const r=f();return new p(t,[e],[r])}return e}()}function f(){let t=T();for(;i(o.Or);){const o=e[s];++s;const n=T();t=new P(o,t,n)}return t}function T(){let t=z();for(;i(o.And);){const o=e[s];++s;const n=z();t=new P(o,t,n)}return t}function z(){let t;for(;i(o.Not);){const o=e[s];++s;const n=z();t=new A(o,n)}return t??function(){let t=B();for(;i(o.ComparisonBinaryOperator)||i(o.In)||i(o.NotIn);){const o=e[s];++s;const n=B();t=new P(o,t,n)}return t}()}function B(){let t=N();for(;i(o.AdditiveBinaryOperator);){const o=e[s];++s;const n=N();t=new P(o,t,n)}return t}function I(){const t=function(){let t=q();for(;i(o.Dot)||i(o.OpenSquareBracket);){const r=e[s];let a;++s;const i=r.type!==o.Dot;if(i)a=D(),n(o.CloseSquareBracket,"Expected closing square bracket");else if(a=q(),"Identifier"!==a.type)throw new SyntaxError("Expected identifier following dot operator");t=new g(t,a,i)}return t}();return i(o.OpenParen)?O(t):t}function O(e){let t=new M(e,function(){n(o.OpenParen,"Expected opening parenthesis for arguments list");const e=function(){const e=[];for(;!i(o.CloseParen);){let t=u();if(i(o.Equals)){if(++s,!(t instanceof w))throw new SyntaxError("Expected identifier for keyword argument");const e=u();t=new E(t,e)}e.push(t),i(o.Comma)&&++s}return e}();return n(o.CloseParen,"Expected closing parenthesis for arguments list"),e}());return i(o.OpenParen)&&(t=O(t)),t}function D(){const e=[];let t=!1;for(;!i(o.CloseSquareBracket);)i(o.Colon)?(e.push(void 0),++s,t=!0):(e.push(u()),i(o.Colon)&&(++s,t=!0));if(0===e.length)throw new SyntaxError("Expected at least one argument for member/slice expression");if(t){if(e.length>3)throw new SyntaxError("Expected 0-3 arguments for slice expression");return new L(...e)}return e[0]}function N(){let t=V();for(;i(o.MultiplicativeBinaryOperator);){const o=e[s];++s;const n=V();t=new P(o,t,n)}return t}function V(){let e=function(){let e=I();for(;i(o.Pipe);){++s;let t=q();if(!(t instanceof w))throw new SyntaxError("Expected identifier for the filter");i(o.OpenParen)&&(t=O(t)),e=new v(e,t)}return e}();for(;i(o.Is);){++s;const t=i(o.Not);t&&++s;let n=q();if(n instanceof x&&(n=new w(n.value.toString())),!(n instanceof w))throw new SyntaxError("Expected identifier for the test");e=new S(e,t,n)}return e}function q(){const t=e[s];switch(t.type){case o.NumericLiteral:return++s,new k(Number(t.value));case o.StringLiteral:return++s,new b(t.value);case o.BooleanLiteral:return++s,new x("true"===t.value);case o.Identifier:return++s,new w(t.value);case o.OpenParen:{++s;const t=d();if(e[s].type!==o.CloseParen)throw new SyntaxError(`Expected closing parenthesis, got ${e[s].type} instead`);return++s,t}case o.OpenSquareBracket:{++s;const e=[];for(;!i(o.CloseSquareBracket);)e.push(u()),i(o.Comma)&&++s;return++s,new y(e)}case o.OpenCurlyBracket:{++s;const e=new Map;for(;!i(o.CloseCurlyBracket);){const t=u();n(o.Colon,"Expected colon between key and value in object literal");const r=u();e.set(t,r),i(o.Comma)&&++s}return++s,new C(e)}default:throw new SyntaxError(`Unexpected token: ${t.type}`)}}for(;s<e.length;)t.body.push(r());return t}function B(e,t,s=1){void 0===t&&(t=e,e=0);const o=[];for(let n=e;n<t;n+=s)o.push(n);return o}function I(e,t,s,o=1){const n=Math.sign(o);n>=0?(t=(t??=0)<0?Math.max(e.length+t,0):Math.min(t,e.length),s=(s??=e.length)<0?Math.max(e.length+s,0):Math.min(s,e.length)):(t=(t??=e.length-1)<0?Math.max(e.length+t,-1):Math.min(t,e.length-1),s=(s??=-1)<-1?Math.max(e.length+s,-1):Math.min(s,e.length-1));const r=[];for(let a=t;n*a<n*s;a+=o)r.push(e[a]);return r}function O(e){return e.replace(/\b\w/g,(e=>e.toUpperCase()))}var D=class{type="RuntimeValue";value;builtins=new Map;constructor(e=void 0){this.value=e}__bool__(){return new q(!!this.value)}},N=class extends D{type="NumericValue"},V=class extends D{type="StringValue";builtins=new Map([["upper",new W((()=>new V(this.value.toUpperCase())))],["lower",new W((()=>new V(this.value.toLowerCase())))],["strip",new W((()=>new V(this.value.trim())))],["title",new W((()=>new V(O(this.value))))],["length",new N(this.value.length)]])},q=class extends D{type="BooleanValue"},j=class extends D{type="ObjectValue";__bool__(){return new q(this.value.size>0)}builtins=new Map([["get",new W((([e,t])=>{if(!(e instanceof V))throw new Error(`Object key must be a string: got ${e.type}`);return this.value.get(e.value)??t??new $}))],["items",new W((()=>new R(Array.from(this.value.entries()).map((([e,t])=>new R([new V(e),t]))))))]])},R=class extends D{type="ArrayValue";builtins=new Map([["length",new N(this.value.length)]]);__bool__(){return new q(this.value.length>0)}},G=class extends R{type="TupleValue"},W=class extends D{type="FunctionValue"},$=class extends D{type="NullValue"},U=class extends D{type="UndefinedValue"},X=class{constructor(e){this.parent=e}variables=new Map([["namespace",new W((e=>{if(0===e.length)return new j(new Map);if(1!==e.length||!(e[0]instanceof j))throw new Error("`namespace` expects either zero arguments or a single object argument");return e[0]}))]]);tests=new Map([["boolean",e=>"BooleanValue"===e.type],["callable",e=>e instanceof W],["odd",e=>{if("NumericValue"!==e.type)throw new Error(`Cannot apply test "odd" to type: ${e.type}`);return e.value%2!=0}],["even",e=>{if("NumericValue"!==e.type)throw new Error(`Cannot apply test "even" to type: ${e.type}`);return e.value%2==0}],["false",e=>"BooleanValue"===e.type&&!e.value],["true",e=>"BooleanValue"===e.type&&e.value],["number",e=>"NumericValue"===e.type],["integer",e=>"NumericValue"===e.type&&Number.isInteger(e.value)],["iterable",e=>e instanceof R||e instanceof V],["lower",e=>{const t=e.value;return"StringValue"===e.type&&t===t.toLowerCase()}],["upper",e=>{const t=e.value;return"StringValue"===e.type&&t===t.toUpperCase()}],["none",e=>"NullValue"===e.type],["defined",e=>"UndefinedValue"!==e.type],["undefined",e=>"UndefinedValue"===e.type],["equalto",(e,t)=>e.value===t.value]]);set(e,t){return this.declareVariable(e,H(t))}declareVariable(e,t){if(this.variables.has(e))throw new SyntaxError(`Variable already declared: ${e}`);return this.variables.set(e,t),t}setVariable(e,t){return this.variables.set(e,t),t}resolve(e){if(this.variables.has(e))return this;if(this.parent)return this.parent.resolve(e);throw new Error(`Unknown variable: ${e}`)}lookupVariable(e){try{return this.resolve(e).variables.get(e)??new U}catch{return new U}}},Q=class{global;constructor(e){this.global=e??new X}run(e){return this.evaluate(e,this.global)}evaluateBinaryExpression(e,t){const s=this.evaluate(e.left,t);switch(e.operator.value){case"and":return s.__bool__().value?this.evaluate(e.right,t):s;case"or":return s.__bool__().value?s:this.evaluate(e.right,t)}const o=this.evaluate(e.right,t);switch(e.operator.value){case"==":return new q(s.value==o.value);case"!=":return new q(s.value!=o.value)}if(s instanceof U||o instanceof U)throw new Error("Cannot perform operation on undefined values");if(s instanceof $||o instanceof $)throw new Error("Cannot perform operation on null values");if(s instanceof N&&o instanceof N)switch(e.operator.value){case"+":return new N(s.value+o.value);case"-":return new N(s.value-o.value);case"*":return new N(s.value*o.value);case"/":return new N(s.value/o.value);case"%":return new N(s.value%o.value);case"<":return new q(s.value<o.value);case">":return new q(s.value>o.value);case">=":return new q(s.value>=o.value);case"<=":return new q(s.value<=o.value)}else if(s instanceof R&&o instanceof R){if("+"===e.operator.value)return new R(s.value.concat(o.value))}else if(o instanceof R){const t=void 0!==o.value.find((e=>e.value===s.value));switch(e.operator.value){case"in":return new q(t);case"not in":return new q(!t)}}if((s instanceof V||o instanceof V)&&"+"===e.operator.value)return new V(s.value.toString()+o.value.toString());if(s instanceof V&&o instanceof V)switch(e.operator.value){case"in":return new q(o.value.includes(s.value));case"not in":return new q(!o.value.includes(s.value))}if(s instanceof V&&o instanceof j)switch(e.operator.value){case"in":return new q(o.value.has(s.value));case"not in":return new q(!o.value.has(s.value))}throw new SyntaxError(`Unknown operator "${e.operator.value}" between ${s.type} and ${o.type}`)}evaluateFilterExpression(e,t){const s=this.evaluate(e.operand,t);if("Identifier"===e.filter.type){const t=e.filter;if(s instanceof R)switch(t.value){case"list":return s;case"first":return s.value[0];case"last":return s.value[s.value.length-1];case"length":return new N(s.value.length);case"reverse":return new R(s.value.reverse());case"sort":return new R(s.value.sort(((e,t)=>{if(e.type!==t.type)throw new Error(`Cannot compare different types: ${e.type} and ${t.type}`);switch(e.type){case"NumericValue":return e.value-t.value;case"StringValue":return e.value.localeCompare(t.value);default:throw new Error(`Cannot compare type: ${e.type}`)}})));default:throw new Error(`Unknown ArrayValue filter: ${t.value}`)}else if(s instanceof V)switch(t.value){case"length":return new N(s.value.length);case"upper":return new V(s.value.toUpperCase());case"lower":return new V(s.value.toLowerCase());case"title":return new V(O(s.value));case"capitalize":return new V(s.value.charAt(0).toUpperCase()+s.value.slice(1));case"trim":return new V(s.value.trim());default:throw new Error(`Unknown StringValue filter: ${t.value}`)}else{if(s instanceof N){if("abs"===t.value)return new N(Math.abs(s.value));throw new Error(`Unknown NumericValue filter: ${t.value}`)}if(s instanceof j)switch(t.value){case"items":return new R(Array.from(s.value.entries()).map((([e,t])=>new R([new V(e),t]))));case"length":return new N(s.value.size);default:throw new Error(`Unknown ObjectValue filter: ${t.value}`)}}throw new Error(`Cannot apply filter "${t.value}" to type: ${s.type}`)}if("CallExpression"===e.filter.type){const o=e.filter;if("Identifier"!==o.callee.type)throw new Error(`Unknown filter: ${o.callee.type}`);const n=o.callee.value;if(s instanceof R){if("selectattr"===n){if(s.value.some((e=>!(e instanceof j))))throw new Error("`selectattr` can only be applied to array of objects");if(o.args.some((e=>"StringLiteral"!==e.type)))throw new Error("arguments of `selectattr` must be strings");const[e,n,r]=o.args.map((e=>this.evaluate(e,t)));let a;if(n){const e=t.tests.get(n.value);if(!e)throw new Error(`Unknown test: ${n.value}`);a=e}else a=(...e)=>e[0].__bool__().value;const i=s.value.filter((t=>{const s=t.value.get(e.value);return!!s&&a(s,r)}));return new R(i)}throw new Error(`Unknown ArrayValue filter: ${n}`)}throw new Error(`Cannot apply filter "${n}" to type: ${s.type}`)}throw new Error(`Unknown filter: ${e.filter.type}`)}evaluateTestExpression(e,t){const s=this.evaluate(e.operand,t),o=t.tests.get(e.test.value);if(!o)throw new Error(`Unknown test: ${e.test.value}`);const n=o(s);return new q(e.negate?!n:n)}evaluateUnaryExpression(e,t){const s=this.evaluate(e.argument,t);if("not"===e.operator.value)return new q(!s.value);throw new SyntaxError(`Unknown operator: ${e.operator.value}`)}evalProgram(e,t){return this.evaluateBlock(e.body,t)}evaluateBlock(e,t){let s="";for(const o of e){const e=this.evaluate(o,t);"NullValue"!==e.type&&"UndefinedValue"!==e.type&&(s+=e.value)}return new V(s)}evaluateIdentifier(e,t){return t.lookupVariable(e.value)}evaluateCallExpression(e,t){const s=[],o=new Map;for(const n of e.args)if("KeywordArgumentExpression"===n.type){const e=n;o.set(e.key.value,this.evaluate(e.value,t))}else s.push(this.evaluate(n,t));o.size>0&&s.push(new j(o));const n=this.evaluate(e.callee,t);if("FunctionValue"!==n.type)throw new Error(`Cannot call something that is not a function: got ${n.type}`);return n.value(s,t)}evaluateSliceExpression(e,t,s){if(!(e instanceof R||e instanceof V))throw new Error("Slice object must be an array or string");const o=this.evaluate(t.start,s),n=this.evaluate(t.stop,s),r=this.evaluate(t.step,s);if(!(o instanceof N||o instanceof U))throw new Error("Slice start must be numeric or undefined");if(!(n instanceof N||n instanceof U))throw new Error("Slice stop must be numeric or undefined");if(!(r instanceof N||r instanceof U))throw new Error("Slice step must be numeric or undefined");return e instanceof R?new R(I(e.value,o.value,n.value,r.value)):new V(I(Array.from(e.value),o.value,n.value,r.value).join(""))}evaluateMemberExpression(e,t){const s=this.evaluate(e.object,t);let o,n;if(e.computed){if("SliceExpression"===e.property.type)return this.evaluateSliceExpression(s,e.property,t);o=this.evaluate(e.property,t)}else o=new V(e.property.value);if(s instanceof j){if(!(o instanceof V))throw new Error(`Cannot access property with non-string: got ${o.type}`);n=s.value.get(o.value)??s.builtins.get(o.value)}else if(s instanceof R||s instanceof V)if(o instanceof N)n=s.value.at(o.value),s instanceof V&&(n=new V(s.value.at(o.value)));else{if(!(o instanceof V))throw new Error(`Cannot access property with non-string/non-number: got ${o.type}`);n=s.builtins.get(o.value)}else{if(!(o instanceof V))throw new Error(`Cannot access property with non-string: got ${o.type}`);n=s.builtins.get(o.value)}return n instanceof D?n:new U}evaluateSet(e,t){const s=this.evaluate(e.value,t);if("Identifier"===e.assignee.type){const o=e.assignee.value;t.setVariable(o,s)}else{if("MemberExpression"!==e.assignee.type)throw new Error(`Invalid LHS inside assignment expression: ${JSON.stringify(e.assignee)}`);{const o=e.assignee,n=this.evaluate(o.object,t);if(!(n instanceof j))throw new Error("Cannot assign to member of non-object");if("Identifier"!==o.property.type)throw new Error("Cannot assign to member with non-identifier property");n.value.set(o.property.value,s)}}return new $}evaluateIf(e,t){const s=this.evaluate(e.test,t);return this.evaluateBlock(s.__bool__().value?e.body:e.alternate,t)}evaluateFor(e,t){const s=new X(t),o=this.evaluate(e.iterable,s);if(!(o instanceof R))throw new Error(`Expected iterable type in for loop: got ${o.type}`);let n="";for(let t=0;t<o.value.length;++t){const r=new Map([["index",new N(t+1)],["index0",new N(t)],["revindex",new N(o.value.length-t)],["revindex0",new N(o.value.length-t-1)],["first",new q(0===t)],["last",new q(t===o.value.length-1)],["length",new N(o.value.length)],["previtem",t>0?o.value[t-1]:new U],["nextitem",t<o.value.length-1?o.value[t+1]:new U]]);s.setVariable("loop",new j(r));const a=o.value[t];if("Identifier"===e.loopvar.type)s.setVariable(e.loopvar.value,a);else if("TupleLiteral"===e.loopvar.type){const t=e.loopvar;if("ArrayValue"!==a.type)throw new Error(`Cannot unpack non-iterable type: ${a.type}`);const o=a;if(t.value.length!==o.value.length)throw new Error(`Too ${t.value.length>o.value.length?"few":"many"} items to unpack`);for(let e=0;e<t.value.length;++e){if("Identifier"!==t.value[e].type)throw new Error(`Cannot unpack non-identifier type: ${t.value[e].type}`);s.setVariable(t.value[e].value,o.value[e])}}n+=this.evaluateBlock(e.body,s).value}return new V(n)}evaluate(e,t){if(void 0===e)return new U;switch(e.type){case"Program":return this.evalProgram(e,t);case"Set":return this.evaluateSet(e,t);case"If":return this.evaluateIf(e,t);case"For":return this.evaluateFor(e,t);case"NumericLiteral":return new N(Number(e.value));case"StringLiteral":return new V(e.value);case"BooleanLiteral":return new q(e.value);case"ArrayLiteral":return new R(e.value.map((e=>this.evaluate(e,t))));case"TupleLiteral":return new G(e.value.map((e=>this.evaluate(e,t))));case"ObjectLiteral":{const s=new Map;for(const[o,n]of e.value){const e=this.evaluate(o,t);if(!(e instanceof V))throw new Error(`Object keys must be strings: got ${e.type}`);s.set(e.value,this.evaluate(n,t))}return new j(s)}case"Identifier":return this.evaluateIdentifier(e,t);case"CallExpression":return this.evaluateCallExpression(e,t);case"MemberExpression":return this.evaluateMemberExpression(e,t);case"UnaryExpression":return this.evaluateUnaryExpression(e,t);case"BinaryExpression":return this.evaluateBinaryExpression(e,t);case"FilterExpression":return this.evaluateFilterExpression(e,t);case"TestExpression":return this.evaluateTestExpression(e,t);default:throw new SyntaxError(`Unknown node type: ${e.type}`)}}};function H(e){switch(typeof e){case"number":return new N(e);case"string":return new V(e);case"boolean":return new q(e);case"object":return null===e?new $:Array.isArray(e)?new R(e.map(H)):new j(new Map(Object.entries(e).map((([e,t])=>[e,H(t)]))));case"function":return new W(((t,s)=>H(e(...t.map((e=>e.value)))??null)));default:throw new Error(`Cannot convert to runtime value: ${e}`)}}var Y=class{parsed;constructor(e){const t=d(e,{lstrip_blocks:!0,trim_blocks:!0});this.parsed=z(t)}render(e){const t=new X;t.set("false",!1),t.set("true",!0),t.set("raise_exception",(e=>{throw new Error(e)})),t.set("range",B);for(const[s,o]of Object.entries(e))t.set(s,o);return new Q(t).run(this.parsed).value}}},"./src/backends/onnx.js":
/*!******************************!*\
  !*** ./src/backends/onnx.js ***!
  \******************************/(e,t,s)=>{s.r(t),s.d(t,{ONNX:()=>n,executionProviders:()=>r});s(/*! onnxruntime-web */"onnxruntime-web");const o=ort;let n;const r=["wasm"];if("undefined"!=typeof process&&"node"===process?.release?.name)n=null.default??null,r.unshift("cpu");else{n=o.default??o;"undefined"!=typeof navigator&&/iP(hone|od|ad).+16_4.+AppleWebKit/.test(navigator.userAgent)&&(n.env.wasm.simd=!1)}},"./src/configs.js":
/*!************************!*\
  !*** ./src/configs.js ***!
  \************************/(e,t,s)=>{s.r(t),s.d(t,{AutoConfig:()=>r,PretrainedConfig:()=>n});var o=s(/*! ./utils/hub.js */"./src/utils/hub.js");class n{constructor(e){this.model_type=null,this.is_encoder_decoder=!1,Object.assign(this,e)}static async from_pretrained(e,{progress_callback:t=null,config:s=null,cache_dir:n=null,local_files_only:r=!1,revision:a="main"}={}){let i=s??await async function(e,t){return await(0,o.getModelJSON)(e,"config.json",!0,t)}(e,{progress_callback:t,config:s,cache_dir:n,local_files_only:r,revision:a});return new this(i)}}class r{static async from_pretrained(...e){return n.from_pretrained(...e)}}},"./src/env.js":
/*!********************!*\
  !*** ./src/env.js ***!
  \********************/(e,t,s)=>{s.r(t),s.d(t,{env:()=>g});var o=s(/*! fs */"?569f"),n=s(/*! path */"?3f59"),r=s(/*! url */"?154a"),a=s(/*! ./backends/onnx.js */"./src/backends/onnx.js");const{env:i}=a.ONNX,l="2.16.1",c="undefined"!=typeof self&&"caches"in self,d=!M(o),u=!M(n),h=d&&u,p=h?n.dirname(n.dirname(r.fileURLToPath("file:///Users/tarekziade/Dev/mozilla-central/toolkit/components/ml/vendor/tmp/transformers.js/src/env.js"))):"./",_=h?n.join(p,"/.cache/"):null,m="/models/",f=h?n.join(p,m):m;i?.wasm&&(i.wasm.wasmPaths=h?n.join(p,"/dist/"):`https://cdn.jsdelivr.net/npm/@xenova/transformers@${l}/dist/`);const g={backends:{onnx:i,tfjs:{}},__dirname:p,version:l,allowRemoteModels:!0,remoteHost:"https://huggingface.co/",remotePathTemplate:"{model}/resolve/{revision}/",allowLocalModels:!0,localModelPath:f,useFS:d,useBrowserCache:c,useFSCache:d,cacheDir:_,useCustomCache:!1,customCache:null};function M(e){return 0===Object.keys(e).length}},"./src/models.js":
/*!***********************!*\
  !*** ./src/models.js ***!
  \***********************/(e,t,s)=>{s.r(t),s.d(t,{ASTForAudioClassification:()=>Kt,ASTModel:()=>Zt,ASTPreTrainedModel:()=>Jt,AlbertForMaskedLM:()=>lt,AlbertForQuestionAnswering:()=>it,AlbertForSequenceClassification:()=>at,AlbertModel:()=>rt,AlbertPreTrainedModel:()=>nt,AutoModel:()=>ta,AutoModelForAudioClassification:()=>wa,AutoModelForAudioFrameClassification:()=>ka,AutoModelForCTC:()=>Ma,AutoModelForCausalLM:()=>la,AutoModelForDepthEstimation:()=>Fa,AutoModelForDocumentQuestionAnswering:()=>ba,AutoModelForImageClassification:()=>ha,AutoModelForImageFeatureExtraction:()=>Ca,AutoModelForImageMatting:()=>xa,AutoModelForImageSegmentation:()=>pa,AutoModelForImageToImage:()=>ya,AutoModelForMaskGeneration:()=>ga,AutoModelForMaskedLM:()=>ca,AutoModelForObjectDetection:()=>ma,AutoModelForQuestionAnswering:()=>da,AutoModelForSemanticSegmentation:()=>_a,AutoModelForSeq2SeqLM:()=>na,AutoModelForSequenceClassification:()=>sa,AutoModelForSpeechSeq2Seq:()=>ra,AutoModelForTextToSpectrogram:()=>aa,AutoModelForTextToWaveform:()=>ia,AutoModelForTokenClassification:()=>oa,AutoModelForVision2Seq:()=>ua,AutoModelForXVector:()=>Ta,AutoModelForZeroShotObjectDetection:()=>fa,BartForConditionalGeneration:()=>Tt,BartForSequenceClassification:()=>kt,BartModel:()=>wt,BartPretrainedModel:()=>Mt,BaseModelOutput:()=>q,BeitForImageClassification:()=>fo,BeitModel:()=>mo,BeitPreTrainedModel:()=>_o,BertForMaskedLM:()=>G,BertForQuestionAnswering:()=>U,BertForSequenceClassification:()=>W,BertForTokenClassification:()=>$,BertModel:()=>R,BertPreTrainedModel:()=>j,BlenderbotForConditionalGeneration:()=>St,BlenderbotModel:()=>vt,BlenderbotPreTrainedModel:()=>Pt,BlenderbotSmallForConditionalGeneration:()=>Et,BlenderbotSmallModel:()=>Lt,BlenderbotSmallPreTrainedModel:()=>At,BloomForCausalLM:()=>Us,BloomModel:()=>$s,BloomPreTrainedModel:()=>Ws,CLIPModel:()=>rs,CLIPPreTrainedModel:()=>ns,CLIPSegForImageSegmentation:()=>fs,CLIPSegModel:()=>ms,CLIPSegPreTrainedModel:()=>_s,CLIPTextModelWithProjection:()=>as,CLIPVisionModelWithProjection:()=>is,CamembertForMaskedLM:()=>me,CamembertForQuestionAnswering:()=>Me,CamembertForSequenceClassification:()=>fe,CamembertForTokenClassification:()=>ge,CamembertModel:()=>_e,CamembertPreTrainedModel:()=>pe,CausalLMOutput:()=>za,CausalLMOutputWithPast:()=>Ba,ChineseCLIPModel:()=>ps,ChineseCLIPPreTrainedModel:()=>hs,ClapAudioModelWithProjection:()=>ur,ClapModel:()=>cr,ClapPreTrainedModel:()=>lr,ClapTextModelWithProjection:()=>dr,CodeGenForCausalLM:()=>Bs,CodeGenModel:()=>zs,CodeGenPreTrainedModel:()=>Es,ConvBertForMaskedLM:()=>oe,ConvBertForQuestionAnswering:()=>ae,ConvBertForSequenceClassification:()=>ne,ConvBertForTokenClassification:()=>re,ConvBertModel:()=>se,ConvBertPreTrainedModel:()=>te,ConvNextForImageClassification:()=>Jo,ConvNextModel:()=>Yo,ConvNextPreTrainedModel:()=>Ho,ConvNextV2ForImageClassification:()=>en,ConvNextV2Model:()=>Ko,ConvNextV2PreTrainedModel:()=>Zo,DPTForDepthEstimation:()=>jo,DPTModel:()=>qo,DPTPreTrainedModel:()=>Vo,DebertaForMaskedLM:()=>ke,DebertaForQuestionAnswering:()=>ye,DebertaForSequenceClassification:()=>be,DebertaForTokenClassification:()=>xe,DebertaModel:()=>Te,DebertaPreTrainedModel:()=>we,DebertaV2ForMaskedLM:()=>Pe,DebertaV2ForQuestionAnswering:()=>Ae,DebertaV2ForSequenceClassification:()=>ve,DebertaV2ForTokenClassification:()=>Se,DebertaV2Model:()=>Ce,DebertaV2PreTrainedModel:()=>Fe,DeiTForImageClassification:()=>So,DeiTModel:()=>vo,DeiTPreTrainedModel:()=>Po,DepthAnythingForDepthEstimation:()=>Go,DepthAnythingPreTrainedModel:()=>Ro,DetrForObjectDetection:()=>wo,DetrForSegmentation:()=>To,DetrModel:()=>Mo,DetrObjectDetectionOutput:()=>ko,DetrPreTrainedModel:()=>go,DetrSegmentationOutput:()=>bo,Dinov2ForImageClassification:()=>on,Dinov2Model:()=>sn,Dinov2PreTrainedModel:()=>tn,DistilBertForMaskedLM:()=>Oe,DistilBertForQuestionAnswering:()=>Ie,DistilBertForSequenceClassification:()=>ze,DistilBertForTokenClassification:()=>Be,DistilBertModel:()=>Ee,DistilBertPreTrainedModel:()=>Le,DonutSwinModel:()=>Qo,DonutSwinPreTrainedModel:()=>Xo,EfficientNetForImageClassification:()=>xr,EfficientNetModel:()=>br,EfficientNetPreTrainedModel:()=>kr,ElectraForMaskedLM:()=>ce,ElectraForQuestionAnswering:()=>he,ElectraForSequenceClassification:()=>de,ElectraForTokenClassification:()=>ue,ElectraModel:()=>le,ElectraPreTrainedModel:()=>ie,EsmForMaskedLM:()=>Ve,EsmForSequenceClassification:()=>qe,EsmForTokenClassification:()=>je,EsmModel:()=>Ne,EsmPreTrainedModel:()=>De,FalconForCausalLM:()=>ir,FalconModel:()=>ar,FalconPreTrainedModel:()=>rr,GLPNForDepthEstimation:()=>Uo,GLPNModel:()=>$o,GLPNPreTrainedModel:()=>Wo,GPT2LMHeadModel:()=>ws,GPT2Model:()=>Ms,GPT2PreTrainedModel:()=>gs,GPTBigCodeForCausalLM:()=>Ls,GPTBigCodeModel:()=>As,GPTBigCodePreTrainedModel:()=>Ss,GPTJForCausalLM:()=>vs,GPTJModel:()=>Ps,GPTJPreTrainedModel:()=>Cs,GPTNeoForCausalLM:()=>bs,GPTNeoModel:()=>ks,GPTNeoPreTrainedModel:()=>Ts,GPTNeoXForCausalLM:()=>Fs,GPTNeoXModel:()=>ys,GPTNeoXPreTrainedModel:()=>xs,HubertForCTC:()=>Nn,HubertForSequenceClassification:()=>Vn,HubertModel:()=>Dn,HubertPreTrainedModel:()=>On,ImageMattingOutput:()=>Ia,LlamaForCausalLM:()=>Ds,LlamaModel:()=>Os,LlamaPreTrainedModel:()=>Is,LongT5ForConditionalGeneration:()=>_t,LongT5Model:()=>pt,LongT5PreTrainedModel:()=>ht,M2M100ForConditionalGeneration:()=>gn,M2M100Model:()=>fn,M2M100PreTrainedModel:()=>mn,MBartForCausalLM:()=>Ct,MBartForConditionalGeneration:()=>yt,MBartForSequenceClassification:()=>Ft,MBartModel:()=>xt,MBartPreTrainedModel:()=>bt,MPNetForMaskedLM:()=>He,MPNetForQuestionAnswering:()=>Ze,MPNetForSequenceClassification:()=>Ye,MPNetForTokenClassification:()=>Je,MPNetModel:()=>Qe,MPNetPreTrainedModel:()=>Xe,MT5ForConditionalGeneration:()=>gt,MT5Model:()=>ft,MT5PreTrainedModel:()=>mt,MarianMTModel:()=>_n,MarianModel:()=>pn,MarianPreTrainedModel:()=>hn,MaskedLMOutput:()=>La,MistralForCausalLM:()=>tr,MistralModel:()=>er,MistralPreTrainedModel:()=>Kn,MobileBertForMaskedLM:()=>We,MobileBertForQuestionAnswering:()=>Ue,MobileBertForSequenceClassification:()=>$e,MobileBertModel:()=>Ge,MobileBertPreTrainedModel:()=>Re,MobileViTForImageClassification:()=>ao,MobileViTModel:()=>ro,MobileViTPreTrainedModel:()=>no,ModelOutput:()=>V,MptForCausalLM:()=>Hs,MptModel:()=>Qs,MptPreTrainedModel:()=>Xs,NomicBertModel:()=>Q,NomicBertPreTrainedModel:()=>X,OPTForCausalLM:()=>Zs,OPTModel:()=>Js,OPTPreTrainedModel:()=>Ys,OwlViTForObjectDetection:()=>co,OwlViTModel:()=>lo,OwlViTPreTrainedModel:()=>io,Owlv2ForObjectDetection:()=>po,Owlv2Model:()=>ho,Owlv2PreTrainedModel:()=>uo,PhiForCausalLM:()=>Gs,PhiModel:()=>Rs,PhiPreTrainedModel:()=>js,PreTrainedModel:()=>N,PretrainedMixin:()=>yr,QuestionAnsweringModelOutput:()=>Ea,Qwen2ForCausalLM:()=>qs,Qwen2Model:()=>Vs,Qwen2PreTrainedModel:()=>Ns,ResNetForImageClassification:()=>Eo,ResNetModel:()=>Lo,ResNetPreTrainedModel:()=>Ao,RoFormerForMaskedLM:()=>J,RoFormerForQuestionAnswering:()=>ee,RoFormerForSequenceClassification:()=>Z,RoFormerForTokenClassification:()=>K,RoFormerModel:()=>Y,RoFormerPreTrainedModel:()=>H,RobertaForMaskedLM:()=>It,RobertaForQuestionAnswering:()=>Nt,RobertaForSequenceClassification:()=>Ot,RobertaForTokenClassification:()=>Dt,RobertaModel:()=>Bt,RobertaPreTrainedModel:()=>zt,SamImageSegmentationOutput:()=>un,SamModel:()=>dn,SamPreTrainedModel:()=>cn,SegformerForImageClassification:()=>fr,SegformerForSemanticSegmentation:()=>gr,SegformerModel:()=>mr,SegformerPreTrainedModel:()=>_r,Seq2SeqLMOutput:()=>Pa,SequenceClassifierOutput:()=>va,SiglipModel:()=>cs,SiglipPreTrainedModel:()=>ls,SiglipTextModel:()=>ds,SiglipVisionModel:()=>us,SpeechT5ForSpeechToText:()=>Qn,SpeechT5ForTextToSpeech:()=>Hn,SpeechT5HifiGan:()=>Yn,SpeechT5Model:()=>Xn,SpeechT5PreTrainedModel:()=>Un,SqueezeBertForMaskedLM:()=>tt,SqueezeBertForQuestionAnswering:()=>ot,SqueezeBertForSequenceClassification:()=>st,SqueezeBertModel:()=>et,SqueezeBertPreTrainedModel:()=>Ke,StableLmForCausalLM:()=>Tr,StableLmModel:()=>wr,StableLmPreTrainedModel:()=>Mr,Starcoder2ForCausalLM:()=>nr,Starcoder2Model:()=>or,Starcoder2PreTrainedModel:()=>sr,Swin2SRForImageSuperResolution:()=>No,Swin2SRModel:()=>Do,Swin2SRPreTrainedModel:()=>Oo,SwinForImageClassification:()=>Io,SwinModel:()=>Bo,SwinPreTrainedModel:()=>zo,T5ForConditionalGeneration:()=>ut,T5Model:()=>dt,T5PreTrainedModel:()=>ct,TableTransformerForObjectDetection:()=>Fo,TableTransformerModel:()=>yo,TableTransformerObjectDetectionOutput:()=>Co,TableTransformerPreTrainedModel:()=>xo,TokenClassifierOutput:()=>Aa,TrOCRForCausalLM:()=>Zn,TrOCRPreTrainedModel:()=>Jn,UniSpeechForCTC:()=>Fn,UniSpeechForSequenceClassification:()=>Cn,UniSpeechModel:()=>yn,UniSpeechPreTrainedModel:()=>xn,UniSpeechSatForAudioFrameClassification:()=>Ln,UniSpeechSatForCTC:()=>Sn,UniSpeechSatForSequenceClassification:()=>An,UniSpeechSatModel:()=>vn,UniSpeechSatPreTrainedModel:()=>Pn,ViTForImageClassification:()=>to,ViTModel:()=>eo,ViTPreTrainedModel:()=>Ks,VisionEncoderDecoderModel:()=>os,VitMatteForImageMatting:()=>oo,VitMattePreTrainedModel:()=>so,VitsModel:()=>pr,VitsModelOutput:()=>Oa,VitsPreTrainedModel:()=>hr,Wav2Vec2BertForCTC:()=>Bn,Wav2Vec2BertForSequenceClassification:()=>In,Wav2Vec2BertModel:()=>zn,Wav2Vec2BertPreTrainedModel:()=>En,Wav2Vec2ForAudioFrameClassification:()=>bn,Wav2Vec2ForCTC:()=>Tn,Wav2Vec2ForSequenceClassification:()=>kn,Wav2Vec2Model:()=>wn,Wav2Vec2PreTrainedModel:()=>Mn,WavLMForAudioFrameClassification:()=>$n,WavLMForCTC:()=>Rn,WavLMForSequenceClassification:()=>Gn,WavLMForXVector:()=>Wn,WavLMModel:()=>jn,WavLMPreTrainedModel:()=>qn,WhisperForConditionalGeneration:()=>ss,WhisperModel:()=>ts,WhisperPreTrainedModel:()=>es,XLMForQuestionAnswering:()=>Wt,XLMForSequenceClassification:()=>Rt,XLMForTokenClassification:()=>Gt,XLMModel:()=>qt,XLMPreTrainedModel:()=>Vt,XLMRobertaForMaskedLM:()=>Xt,XLMRobertaForQuestionAnswering:()=>Yt,XLMRobertaForSequenceClassification:()=>Qt,XLMRobertaForTokenClassification:()=>Ht,XLMRobertaModel:()=>Ut,XLMRobertaPreTrainedModel:()=>$t,XLMWithLMHeadModel:()=>jt,XVectorOutput:()=>Sa,YolosForObjectDetection:()=>an,YolosModel:()=>rn,YolosObjectDetectionOutput:()=>ln,YolosPreTrainedModel:()=>nn});var o=s(/*! ./configs.js */"./src/configs.js"),n=s(/*! ./utils/core.js */"./src/utils/core.js"),r=s(/*! ./utils/hub.js */"./src/utils/hub.js"),a=s(/*! ./utils/generation.js */"./src/utils/generation.js"),i=s(/*! ./utils/tensor.js */"./src/utils/tensor.js"),l=s(/*! ./backends/onnx.js */"./src/backends/onnx.js"),c=s(/*! ./transformers.js */"./src/transformers.js");const{InferenceSession:d,Tensor:u,env:h}=l.ONNX,p=0,_=1,m=2,f=3,g=4,M=5,w=new Map,T=new Map,k=new Map;async function b(e,t,s){let o=`onnx/${t}${s.quantized?"_quantized":""}.onnx`,n=await(0,r.getModelFile)(e,o,!0,s);try{return await d.create(n,{executionProviders:l.executionProviders})}catch(e){if(1===l.executionProviders.length&&"wasm"===l.executionProviders[0])throw e;return console.warn(e),console.warn("Something went wrong during model construction (most likely a missing operation). Using `wasm` as a fallback. "),await d.create(n,{executionProviders:["wasm"]})}}async function x(e,t){const s=function(e,t){const s=Object.create(null),o=[];for(const n of e.inputNames){const e=t[n];e instanceof i.Tensor?s[n]=h.wasm.proxy?e.clone():e:o.push(n)}if(o.length>0)throw new Error(`An error occurred during model execution: "Missing the following inputs: ${o.join(", ")}.`);const n=Object.keys(t).length,r=e.inputNames.length;if(n>r){let s=Object.keys(t).filter((t=>!e.inputNames.includes(t)));console.warn(`WARNING: Too many inputs were provided (${n} > ${r}). The following inputs will be ignored: "${s.join(", ")}".`)}return s}(e,t);try{let t=await e.run(s);return t=y(t),t}catch(e){throw console.error(`An error occurred during model execution: "${e}".`),console.error("Inputs given to model:",s),e}}function y(e){for(let t in e)e[t]instanceof u?e[t]=new i.Tensor(e[t]):"object"==typeof e[t]&&y(e[t]);return e}function F(e){if(e instanceof i.Tensor)return e;if(0===e.length)throw Error("items must be non-empty");if(Array.isArray(e[0])){if(e.some((t=>t.length!==e[0].length)))throw Error("Unable to create tensor, you should probably activate truncation and/or padding with 'padding=True' and/or 'truncation=True' to have batched tensors with the same length.");return new i.Tensor("int64",BigInt64Array.from(e.flat().map((e=>BigInt(e)))),[e.length,e[0].length])}return new i.Tensor("int64",BigInt64Array.from(e.map((e=>BigInt(e)))),[1,e.length])}function C(e,t){let s=e.config.pad_token_id??null,o=e.config.eos_token_id??null;(0,n.isIntegralNumber)(o)&&(o=[o]);let r=-1!==t.indexOf(s),a=null===o||!o.includes(s);if(r&&a){let e=BigInt64Array.from(t.data.map((e=>e!=s)));return new i.Tensor("int64",e,t.dims)}return(0,i.ones_like)(t)}function P(e,t,s){if(!e.inputNames.includes("position_ids"))return;const o=new BigInt64Array(t.attention_mask.data.length);for(let e=0;e<t.attention_mask.dims[0];++e){let s=e*t.attention_mask.dims[1],n=BigInt(0);for(let e=0;e<t.attention_mask.dims[1];++e){const r=s+e;0n===t.attention_mask.data[r]?o[r]=BigInt(1):(o[r]=n,n+=t.attention_mask.data[r])}}t.position_ids=new i.Tensor("int64",o,t.attention_mask.dims),s&&(t.position_ids=t.position_ids.slice(null,-1).unsqueeze_(-1))}function v(e){return new i.Tensor("bool",[e],[1])}async function S(e,t){let{encoder_outputs:s,past_key_values:o}=t;s||(s=(await z(e,t)).last_hidden_state);let n={input_ids:t.decoder_input_ids,encoder_hidden_states:s};const r=!!o;e.decoder_merged_session.inputNames.includes("use_cache_branch")&&(n.use_cache_branch=v(r)),e.decoder_merged_session.inputNames.includes("encoder_attention_mask")&&(n.encoder_attention_mask=t.attention_mask),P(e.decoder_merged_session,n,r),e.addPastKeyValues(n,o);const a=await x(e.decoder_merged_session,n);let i=a.logits;o=e.getPastKeyValues(a,o);const l=e.getAttentions(a);return new Pa({logits:i,past_key_values:o,encoder_outputs:s,...l})}function A(e,t,s,o){let n=[],r=0;const a=e.requires_attention_mask??!0;let l=s.decoder_input_ids??s.decoder_start_token_id??s.bos_token_id??s.eos_token_id;l instanceof i.Tensor?l=l.tolist().flat():Array.isArray(l)||(l=[l]);for(let s of t){s.dims=[1,...s.dims];let t={inputs:s,encoder_outputs:null,prev_model_outputs:null,output_token_ids:l,done:!1,score:0,id:r++};a&&(t.attention_mask=C(e,s)),n.push(t)}return n}async function L(e,t){const s=e.main_input_name;let o=t.output_token_ids;t.prev_model_outputs&&(o=o.slice(-1));let n={[s]:t.inputs,decoder_input_ids:F(o),encoder_outputs:t.encoder_outputs,past_key_values:t.prev_model_outputs?.past_key_values};t.attention_mask&&(n.attention_mask=t.attention_mask);let r=await e.forward(n);return t.prev_model_outputs=r,t.encoder_outputs=r.encoder_outputs,r}function E(e,t){e.output_token_ids=[...e.output_token_ids,t]}async function z(e,t){const s=Object.create(null);for(const o of e.session.inputNames)s[o]=t[o];return e.session.inputNames.includes("token_type_ids")&&!s.token_type_ids&&(s.token_type_ids=new i.Tensor("int64",new BigInt64Array(s.input_ids.data.length),s.input_ids.dims)),await x(e.session,s)}async function B(e,t){let{input_ids:s,past_key_values:o,attention_mask:n}=t,r={input_ids:s,attention_mask:n??C(e,s)};const a=!!o;e.session.inputNames.includes("use_cache_branch")&&(r.use_cache_branch=v(a)),P(e.session,r,a),e.addPastKeyValues(r,o);let i=await x(e.session,r),l=i.logits;return o=e.getPastKeyValues(i,o),{logits:l,past_key_values:o}}function I(e,t,s,o,n){let r=[],a=0;for(let s of t){let t,i=s.tolist().map(Number);s.dims=[1,...s.dims],n?(t=n[a],t.dims=[1,...t.dims]):t=C(e,s);let l={input:s,model_input_ids:s,attention_mask:t,prev_model_outputs:null,output_token_ids:i,num_output_tokens:o,done:!1,score:0,id:a++};r.push(l)}return r}async function O(e,t){let s=new BigInt64Array(t.output_token_ids.length).fill(1n),o={input_ids:t.model_input_ids,attention_mask:new i.Tensor("int64",s,[1,s.length]),past_key_values:t.prev_model_outputs?.past_key_values},n=await e.forward(o);return t.prev_model_outputs=n,n}function D(e,t){e.output_token_ids=[...e.output_token_ids,t],e.model_input_ids=new i.Tensor("int64",[BigInt(t)],[1,1])}class N extends n.Callable{main_input_name="input_ids";constructor(e,t){super(),this.config=e,this.session=t;const s=k.get(this.constructor),o=w.get(s);this.can_generate=!1,this._runBeam=null,this._getStartBeams=null,this._updateBeam=null,this._forward=null,o===g?(this.can_generate=!0,this._runBeam=O,this._getStartBeams=I,this._updateBeam=D,this._forward=B):o===m||o===f?(this.can_generate=!0,this._runBeam=L,this._getStartBeams=A,this._updateBeam=E,this._forward=S):this._forward=z}async dispose(){const e=[];for(let t of Object.keys(this)){const s=this[t];s instanceof d&&e.push(s.handler.dispose())}return await Promise.all(e)}static async from_pretrained(e,{quantized:t=!0,progress_callback:s=null,config:n=null,cache_dir:a=null,local_files_only:i=!1,revision:l="main",model_file_name:c=null}={}){let d={quantized:t,progress_callback:s,config:n,cache_dir:a,local_files_only:i,revision:l,model_file_name:c};const u=k.get(this),h=w.get(u);let T;return h===g?T=await Promise.all([o.AutoConfig.from_pretrained(e,d),b(e,d.model_file_name??"decoder_model_merged",d),(0,r.getModelJSON)(e,"generation_config.json",!1,d)]):h===m||h===f?T=await Promise.all([o.AutoConfig.from_pretrained(e,d),b(e,"encoder_model",d),b(e,"decoder_model_merged",d),(0,r.getModelJSON)(e,"generation_config.json",!1,d)]):h===M?T=await Promise.all([o.AutoConfig.from_pretrained(e,d),b(e,"vision_encoder",d),b(e,"prompt_encoder_mask_decoder",d)]):h===_?T=await Promise.all([o.AutoConfig.from_pretrained(e,d),b(e,"encoder_model",d),b(e,"decoder_model_merged",d)]):(h!==p&&console.warn(`Model type for '${u??n?.model_type}' not found, assuming encoder-only architecture. Please report this at https://github.com/xenova/transformers.js/issues/new/choose.`),T=await Promise.all([o.AutoConfig.from_pretrained(e,d),b(e,d.model_file_name??"model",d)])),new this(...T)}async _call(e){return await this.forward(e)}async forward(e){return await this._forward(this,e)}_get_logits_processor(e,t,s=null){const o=new a.LogitsProcessorList;if(null!==e.repetition_penalty&&1!==e.repetition_penalty&&o.push(new a.RepetitionPenaltyLogitsProcessor(e.repetition_penalty)),null!==e.no_repeat_ngram_size&&e.no_repeat_ngram_size>0&&o.push(new a.NoRepeatNGramLogitsProcessor(e.no_repeat_ngram_size)),null!==e.bad_words_ids&&o.push(new a.NoBadWordsLogitsProcessor(e.bad_words_ids,e.eos_token_id)),null!==e.min_length&&null!==e.eos_token_id&&e.min_length>0&&o.push(new a.MinLengthLogitsProcessor(e.min_length,e.eos_token_id)),null!==e.min_new_tokens&&null!==e.eos_token_id&&e.min_new_tokens>0&&o.push(new a.MinNewTokensLengthLogitsProcessor(t,e.min_new_tokens,e.eos_token_id)),null!==e.forced_bos_token_id&&o.push(new a.ForcedBOSTokenLogitsProcessor(e.forced_bos_token_id)),null!==e.forced_eos_token_id&&o.push(new a.ForcedEOSTokenLogitsProcessor(e.max_length,e.forced_eos_token_id)),null!==e.begin_suppress_tokens){let s=t>1||null===e.forced_bos_token_id?t:t+1;null!==e.forced_decoder_ids&&(s+=e.forced_decoder_ids[e.forced_decoder_ids.length-1][0]),o.push(new a.SuppressTokensAtBeginLogitsProcessor(e.begin_suppress_tokens,s))}return null!==e.forced_decoder_ids&&o.push(new a.ForceTokensLogitsProcessor(e.forced_decoder_ids)),null!==s&&o.extend(s),o}_get_generation_config(e){let t=new a.GenerationConfig(this.config);return"generation_config"in this&&Object.assign(t,this.generation_config),null!==e&&Object.assign(t,e),t}async generate(e,t=null,s=null,{inputs_attention_mask:o=null}={}){if(!this.can_generate){let e=`The current model class (${k.get(this.constructor)}) is not compatible with \`.generate()\`, as it doesn't have a language model head.`;const t=this.config.model_type,s=Br.get(t)??zr.get(t)??vr.get(t)??Dr.get(t);throw s&&(e+=` Please use the following class instead: '${s[0]}'`),Error(e)}if(!(e instanceof i.Tensor||(0,n.isTypedArray)(e)||Array.isArray(e)))throw Error(`\`inputs\` must be a Tensor, TypedArray, or Array, but is "${e.constructor.name}".`);let r;if(this.config.is_encoder_decoder)r=0;else if(r=e instanceof i.Tensor?e.dims.at(-1):e.length,0===r)throw Error("Must supply a non-empty array of input token ids.");t=this._get_generation_config(t),s=s??new a.LogitsProcessorList,s=this._get_logits_processor(t,r,s);let l=t.eos_token_id;null===l||Array.isArray(l)||(l=[l]);let c=1;const d=c+(t.max_new_tokens??1/0),u=Number.isInteger(t.max_length)&&null===(t.max_new_tokens??null);let h=a.Sampler.getSampler(t),p=this.getStartBeams(e,t,c,o);for(;p.some((e=>!e.done))&&c<d;){let e=[];for(let o of p){if(o.done){e.push(o);continue}if(u&&o.output_token_ids.length>=t.max_length){o.done=!0,e.push(o);continue}let n=await this.runBeam(o);t.output_attentions&&this.addAttentionsToBeam(o,n),t.output_scores;let r=n.logits.slice(null,-1,null);s(o.output_token_ids,r);let a=h(r);for(let[t,s]of a){let n={...o};this.updateBeam(n,t),n.score+=s,l&&l.includes(t)&&(n.done=!0),e.push(n)}}++c,e=this.groupBeams(e).map((e=>e.sort(((e,t)=>t.score-e.score)).slice(0,t.num_beams))),p=e.flat(),t.callback_function&&t.callback_function(p)}const _=this.groupBeams(p),m=e=>_.map((s=>t.num_return_sequences>1?s.slice(0,t.num_return_sequences).map((t=>t[e])):[s[0][e]])).flat(),f=m("output_token_ids");if(t.return_dict_in_generate){return{sequences:f,decoder_attentions:m("decoder_attentions"),cross_attentions:m("cross_attentions")}}return f}addAttentionsToBeam(e,t){if(this.config.is_encoder_decoder){if(!t.cross_attentions||0===t.cross_attentions.length)throw Error("`output_attentions` is true, but the model did not produce cross-attentions. This is most likely because the model was not exported with `output_attentions=True`.");e.cross_attentions||(e.cross_attentions=[]),e.cross_attentions.push(t.cross_attentions)}if(!t.decoder_attentions||0===t.decoder_attentions.length)throw Error("`output_attentions` is true, but the model did not produce decoder-attentions. This is most likely because the model was not exported with `output_attentions=True`.");e.decoder_attentions||(e.decoder_attentions=[]),e.decoder_attentions.push(t.decoder_attentions)}groupBeams(e){const t=Object.create(null);for(const s of e)void 0===t[s.id]?t[s.id]=[s]:t[s.id].push(s);return Object.values(t)}getPastKeyValues(e,t){const s=Object.create(null);for(const o in e)if(o.startsWith("present")){let n=o.replace("present","past_key_values");t&&o.includes("encoder")?s[n]=t[n]:s[n]=e[o]}return s}getAttentions(e){const t=Object.create(null);for(const s of["cross_attentions","decoder_attentions"]){const o=[];for(const t in e)if(t.startsWith(s)){o[t.split(".").pop()]=e[t]}t[s]=o}return t}addPastKeyValues(e,t){if(t)Object.assign(e,t);else{const t=1;if(this.config.is_encoder_decoder&&(this.add_encoder_pkv??1)){let s=[t,this.num_encoder_heads,0,this.encoder_dim_kv],o=[t,this.num_decoder_heads,0,this.decoder_dim_kv];for(let t=0;t<this.num_decoder_layers;++t)e[`past_key_values.${t}.encoder.key`]=new i.Tensor("float32",[],s),e[`past_key_values.${t}.encoder.value`]=new i.Tensor("float32",[],s),e[`past_key_values.${t}.decoder.key`]=new i.Tensor("float32",[],o),e[`past_key_values.${t}.decoder.value`]=new i.Tensor("float32",[],o)}else if("falcon"===this.config.model_type){let s=[t*this.num_heads,0,this.dim_kv];for(let t=0;t<this.num_layers;++t)e[`past_key_values.${t}.key`]=new i.Tensor("float32",[],s),e[`past_key_values.${t}.value`]=new i.Tensor("float32",[],s)}else if(this.config.multi_query){let s=[t*this.num_heads,0,2*this.dim_kv];for(let t=0;t<this.num_layers;++t)e[`past_key_values.${t}.key_value`]=new i.Tensor("float32",[],s)}else if("bloom"===this.config.model_type){let s=[t*this.num_heads,this.dim_kv,0],o=[t*this.num_heads,0,this.dim_kv];for(let t=0;t<this.num_layers;++t)e[`past_key_values.${t}.key`]=new i.Tensor("float32",[],s),e[`past_key_values.${t}.value`]=new i.Tensor("float32",[],o)}else{let s=[t,this.num_heads,0,this.dim_kv];for(let t=0;t<this.num_layers;++t)e[`past_key_values.${t}.key`]=new i.Tensor("float32",[],s),e[`past_key_values.${t}.value`]=new i.Tensor("float32",[],s)}}}getStartBeams(e,t,s,o){return this._getStartBeams(this,e,t,s,o)}async runBeam(e){return await this._runBeam(this,e)}updateBeam(e,t){return this._updateBeam(e,t)}}class V{}class q extends V{constructor({last_hidden_state:e,hidden_states:t=null,attentions:s=null}){super(),this.last_hidden_state=e,this.hidden_states=t,this.attentions=s}}class j extends N{}class R extends j{}class G extends j{async _call(e){return new La(await super._call(e))}}class W extends j{async _call(e){return new va(await super._call(e))}}class $ extends j{async _call(e){return new Aa(await super._call(e))}}class U extends j{async _call(e){return new Ea(await super._call(e))}}class X extends N{}class Q extends X{}class H extends N{}class Y extends H{}class J extends H{async _call(e){return new La(await super._call(e))}}class Z extends H{async _call(e){return new va(await super._call(e))}}class K extends H{async _call(e){return new Aa(await super._call(e))}}class ee extends H{async _call(e){return new Ea(await super._call(e))}}class te extends N{}class se extends te{}class oe extends te{async _call(e){return new La(await super._call(e))}}class ne extends te{async _call(e){return new va(await super._call(e))}}class re extends te{async _call(e){return new Aa(await super._call(e))}}class ae extends te{async _call(e){return new Ea(await super._call(e))}}class ie extends N{}class le extends ie{}class ce extends ie{async _call(e){return new La(await super._call(e))}}class de extends ie{async _call(e){return new va(await super._call(e))}}class ue extends ie{async _call(e){return new Aa(await super._call(e))}}class he extends ie{async _call(e){return new Ea(await super._call(e))}}class pe extends N{}class _e extends pe{}class me extends pe{async _call(e){return new La(await super._call(e))}}class fe extends pe{async _call(e){return new va(await super._call(e))}}class ge extends pe{async _call(e){return new Aa(await super._call(e))}}class Me extends pe{async _call(e){return new Ea(await super._call(e))}}class we extends N{}class Te extends we{}class ke extends we{async _call(e){return new La(await super._call(e))}}class be extends we{async _call(e){return new va(await super._call(e))}}class xe extends we{async _call(e){return new Aa(await super._call(e))}}class ye extends we{async _call(e){return new Ea(await super._call(e))}}class Fe extends N{}class Ce extends Fe{}class Pe extends Fe{async _call(e){return new La(await super._call(e))}}class ve extends Fe{async _call(e){return new va(await super._call(e))}}class Se extends Fe{async _call(e){return new Aa(await super._call(e))}}class Ae extends Fe{async _call(e){return new Ea(await super._call(e))}}class Le extends N{}class Ee extends Le{}class ze extends Le{async _call(e){return new va(await super._call(e))}}class Be extends Le{async _call(e){return new Aa(await super._call(e))}}class Ie extends Le{async _call(e){return new Ea(await super._call(e))}}class Oe extends Le{async _call(e){return new La(await super._call(e))}}class De extends N{}class Ne extends De{}class Ve extends De{async _call(e){return new La(await super._call(e))}}class qe extends De{async _call(e){return new va(await super._call(e))}}class je extends De{async _call(e){return new Aa(await super._call(e))}}class Re extends N{}class Ge extends Re{}class We extends Re{async _call(e){return new La(await super._call(e))}}class $e extends Re{async _call(e){return new va(await super._call(e))}}class Ue extends Re{async _call(e){return new Ea(await super._call(e))}}class Xe extends N{}class Qe extends Xe{}class He extends Xe{async _call(e){return new La(await super._call(e))}}class Ye extends Xe{async _call(e){return new va(await super._call(e))}}class Je extends Xe{async _call(e){return new Aa(await super._call(e))}}class Ze extends Xe{async _call(e){return new Ea(await super._call(e))}}class Ke extends N{}class et extends Ke{}class tt extends Ke{async _call(e){return new La(await super._call(e))}}class st extends Ke{async _call(e){return new va(await super._call(e))}}class ot extends Ke{async _call(e){return new Ea(await super._call(e))}}class nt extends N{}class rt extends nt{}class at extends nt{async _call(e){return new va(await super._call(e))}}class it extends nt{async _call(e){return new Ea(await super._call(e))}}class lt extends nt{async _call(e){return new La(await super._call(e))}}class ct extends N{}class dt extends ct{}class ut extends ct{constructor(e,t,s,o){super(e,t),this.decoder_merged_session=s,this.generation_config=o,this.num_decoder_layers=this.config.num_decoder_layers,this.num_decoder_heads=this.config.num_heads,this.decoder_dim_kv=this.config.d_kv,this.num_encoder_layers=this.config.num_layers,this.num_encoder_heads=this.config.num_heads,this.encoder_dim_kv=this.config.d_kv}}class ht extends N{}class pt extends ht{}class _t extends ht{constructor(e,t,s,o){super(e,t),this.decoder_merged_session=s,this.generation_config=o,this.num_decoder_layers=this.config.num_decoder_layers,this.num_decoder_heads=this.config.num_heads,this.decoder_dim_kv=this.config.d_kv,this.num_encoder_layers=this.config.num_layers,this.num_encoder_heads=this.config.num_heads,this.encoder_dim_kv=this.config.d_kv}}class mt extends N{}class ft extends mt{}class gt extends mt{constructor(e,t,s,o){super(e,t),this.decoder_merged_session=s,this.generation_config=o,this.num_decoder_layers=this.config.num_decoder_layers,this.num_decoder_heads=this.config.num_heads,this.decoder_dim_kv=this.config.d_kv,this.num_encoder_layers=this.config.num_layers,this.num_encoder_heads=this.config.num_heads,this.encoder_dim_kv=this.config.d_kv}}class Mt extends N{}class wt extends Mt{}class Tt extends Mt{constructor(e,t,s,o){super(e,t),this.decoder_merged_session=s,this.generation_config=o,this.num_decoder_layers=this.config.decoder_layers,this.num_decoder_heads=this.config.decoder_attention_heads,this.decoder_dim_kv=this.config.d_model/this.num_decoder_heads,this.num_encoder_layers=this.config.encoder_layers,this.num_encoder_heads=this.config.encoder_attention_heads,this.encoder_dim_kv=this.config.d_model/this.num_encoder_heads}}class kt extends Mt{async _call(e){return new va(await super._call(e))}}class bt extends N{}class xt extends bt{}class yt extends bt{constructor(e,t,s,o){super(e,t),this.decoder_merged_session=s,this.generation_config=o,this.num_decoder_layers=this.config.decoder_layers,this.num_decoder_heads=this.config.decoder_attention_heads,this.decoder_dim_kv=this.config.d_model/this.num_decoder_heads,this.num_encoder_layers=this.config.encoder_layers,this.num_encoder_heads=this.config.encoder_attention_heads,this.encoder_dim_kv=this.config.d_model/this.num_encoder_heads}}class Ft extends bt{async _call(e){return new va(await super._call(e))}}class Ct extends bt{constructor(e,t,s){super(e,t),this.generation_config=s,this.num_decoder_layers=this.config.decoder_layers,this.num_decoder_heads=this.config.decoder_attention_heads,this.decoder_dim_kv=this.config.d_model/this.num_decoder_heads,this.num_encoder_layers=this.config.encoder_layers,this.num_encoder_heads=this.config.encoder_attention_heads,this.encoder_dim_kv=this.config.d_model/this.num_encoder_heads}}class Pt extends N{}class vt extends Pt{}class St extends Pt{constructor(e,t,s,o){super(e,t),this.decoder_merged_session=s,this.generation_config=o,this.num_decoder_layers=this.config.decoder_layers,this.num_decoder_heads=this.config.decoder_attention_heads,this.decoder_dim_kv=this.config.d_model/this.num_decoder_heads,this.num_encoder_layers=this.config.encoder_layers,this.num_encoder_heads=this.config.encoder_attention_heads,this.encoder_dim_kv=this.config.d_model/this.num_encoder_heads}}class At extends N{}class Lt extends At{}class Et extends At{constructor(e,t,s,o){super(e,t),this.decoder_merged_session=s,this.generation_config=o,this.num_decoder_layers=this.config.decoder_layers,this.num_decoder_heads=this.config.decoder_attention_heads,this.decoder_dim_kv=this.config.d_model/this.num_decoder_heads,this.num_encoder_layers=this.config.encoder_layers,this.num_encoder_heads=this.config.encoder_attention_heads,this.encoder_dim_kv=this.config.d_model/this.num_encoder_heads}}class zt extends N{}class Bt extends zt{}class It extends zt{async _call(e){return new La(await super._call(e))}}class Ot extends zt{async _call(e){return new va(await super._call(e))}}class Dt extends zt{async _call(e){return new Aa(await super._call(e))}}class Nt extends zt{async _call(e){return new Ea(await super._call(e))}}class Vt extends N{}class qt extends Vt{}class jt extends Vt{async _call(e){return new La(await super._call(e))}}class Rt extends Vt{async _call(e){return new va(await super._call(e))}}class Gt extends Vt{async _call(e){return new Aa(await super._call(e))}}class Wt extends Vt{async _call(e){return new Ea(await super._call(e))}}class $t extends N{}class Ut extends $t{}class Xt extends $t{async _call(e){return new La(await super._call(e))}}class Qt extends $t{async _call(e){return new va(await super._call(e))}}class Ht extends $t{async _call(e){return new Aa(await super._call(e))}}class Yt extends $t{async _call(e){return new Ea(await super._call(e))}}class Jt extends N{}class Zt extends Jt{}class Kt extends Jt{}class es extends N{}class ts extends es{}class ss extends es{requires_attention_mask=!1;main_input_name="input_features";constructor(e,t,s,o){super(e,t),this.decoder_merged_session=s,this.generation_config=o,this.num_decoder_layers=this.config.decoder_layers,this.num_decoder_heads=this.config.decoder_attention_heads,this.decoder_dim_kv=this.config.d_model/this.num_decoder_heads,this.num_encoder_layers=this.config.encoder_layers,this.num_encoder_heads=this.config.encoder_attention_heads,this.encoder_dim_kv=this.config.d_model/this.num_encoder_heads}async generate(e,t=null,s=null){if(t=this._get_generation_config(t),t.return_timestamps??=!1,t.return_timestamps&&(s=[new a.WhisperTimeStampLogitsProcessor(t)]),t.return_token_timestamps&&(t.output_attentions=!0,t.return_dict_in_generate=!0,"translate"===t.task&&console.warn("Token-level timestamps may not be reliable for task 'translate'."),!t.alignment_heads))throw new Error("Model generation config has no `alignment_heads`, token-level timestamps not available. See https://gist.github.com/hollance/42e32852f24243b748ae6bc1f985b13a on how to add this property to the generation config.");const o=await super.generate(e,t,s);return t.return_token_timestamps&&t.alignment_heads&&(o.token_timestamps=this._extract_token_timestamps(o,t.alignment_heads,t.num_frames)),o}_extract_token_timestamps(e,t,s=null,o=.02){if(!e.cross_attentions)throw new Error("Model outputs must contain cross attentions to extract timestamps. This is most likely because the model was not exported with `output_attentions=True`.");let r=this.config.median_filter_width;void 0===r&&(console.warn("Model config has no `median_filter_width`, using default value of 7."),r=7);const a=e.cross_attentions.map((e=>{let o=Array.from({length:this.config.decoder_layers},((t,s)=>(0,i.cat)(e.map((e=>e[s])),2))),n=(0,i.stack)(t.map((([e,t])=>s?o[e].slice(null,t,null,[0,s]):o[e].slice(null,t))));n=n.transpose(1,0,2,3);let[a,l]=(0,i.std_mean)(n,-2,0,!0),d=n.clone();for(let e=0;e<d.dims[0];++e){let t=d[e];for(let s=0;s<t.dims[0];++s){let o=t[s];const n=a[e][s][0],i=l[e][s][0];for(let e=0;e<o.dims[0];++e){let t=o[e];for(let e=0;e<t.data.length;++e)t.data[e]=(t.data[e]-i.data[e])/n.data[e];t.data.set((0,c.medianFilter)(t.data,r))}}}return(0,i.mean)(d,1)})),l=[e.sequences.length,e.sequences[0].length],d=new i.Tensor("float32",new Float32Array(l[0]*l[1]),l);for(let e=0;e<l[0];++e){const t=a[e].neg().squeeze_(0);let[s,r]=(0,i.dynamicTimeWarping)(t),l=Array.from({length:s.length-1},((e,t)=>s[t+1]-s[t])),c=(0,n.mergeArrays)([1],l).map((e=>!!e)),u=[];for(let e=0;e<c.length;++e)c[e]&&u.push(r[e]*o);d[e].data.set(u,1)}return d}}class os extends N{main_input_name="pixel_values";constructor(e,t,s,o){super(e,t),this.decoder_merged_session=s,this.generation_config=o;const n=this.config.encoder,r=this.config.decoder,a=n.model_type;(Fr.get(a)??Cr.get(a))||console.warn(`Model type for encoder '${a}' not found, assuming encoder-only architecture. Please report this at https://github.com/xenova/transformers.js/issues/new/choose.`);const i=Br.get(r.model_type);if(!i)throw new Error(`Unable to construct \`VisionEncoderDecoder\` due to unsupported decoder: "${this.config.decoder.model_type}"`);const l=new(0,i[1])(r,s,o);this.add_encoder_pkv="num_decoder_layers"in l,this.add_encoder_pkv?(this.num_decoder_layers=l.num_decoder_layers,this.num_decoder_heads=l.num_decoder_heads,this.decoder_dim_kv=l.decoder_dim_kv,this.num_encoder_layers=l.num_encoder_layers,this.num_encoder_heads=l.num_encoder_heads,this.encoder_dim_kv=l.encoder_dim_kv):(this.num_layers=l.num_layers,this.num_heads=l.num_heads,this.dim_kv=l.dim_kv)}}class ns extends N{}class rs extends ns{}class as extends ns{static async from_pretrained(e,t={}){return t.model_file_name??="text_model",super.from_pretrained(e,t)}}class is extends ns{static async from_pretrained(e,t={}){return t.model_file_name??="vision_model",super.from_pretrained(e,t)}}class ls extends N{}class cs extends ls{}class ds extends ls{static async from_pretrained(e,t={}){return t.model_file_name??="text_model",super.from_pretrained(e,t)}}class us extends ns{static async from_pretrained(e,t={}){return t.model_file_name??="vision_model",super.from_pretrained(e,t)}}class hs extends N{}class ps extends hs{}class _s extends N{}class ms extends _s{}class fs extends _s{}class gs extends N{constructor(e,t,s){super(e,t),this.generation_config=s,this.config.pad_token_id=this.config.eos_token_id,this.num_heads=this.config.n_head,this.num_layers=this.config.n_layer,this.dim_kv=this.config.n_embd/this.num_heads}}class Ms extends gs{}class ws extends gs{}class Ts extends N{constructor(e,t,s){super(e,t),this.generation_config=s,this.config.pad_token_id=this.config.eos_token_id,this.num_heads=this.config.num_heads,this.num_layers=this.config.num_layers,this.dim_kv=this.config.hidden_size/this.num_heads}}class ks extends Ts{}class bs extends Ts{}class xs extends N{constructor(e,t,s){super(e,t),this.generation_config=s,this.config.pad_token_id=this.config.eos_token_id,this.num_heads=this.config.num_attention_heads,this.num_layers=this.config.num_hidden_layers,this.dim_kv=this.config.hidden_size/this.num_heads}}class ys extends xs{}class Fs extends xs{}class Cs extends N{constructor(e,t,s){super(e,t),this.generation_config=s,this.config.pad_token_id=this.config.eos_token_id,this.num_heads=this.config.n_head,this.num_layers=this.config.n_layer,this.dim_kv=this.config.n_embd/this.num_heads}}class Ps extends Cs{}class vs extends Cs{}class Ss extends N{constructor(e,t,s){super(e,t),this.generation_config=s,this.config.pad_token_id=this.config.eos_token_id,this.num_heads=this.config.n_head,this.num_layers=this.config.n_layer,this.dim_kv=this.config.n_embd/this.num_heads}}class As extends Ss{}class Ls extends Ss{}class Es extends N{constructor(e,t,s){super(e,t),this.generation_config=s,this.config.pad_token_id=this.config.eos_token_id,this.num_heads=this.config.n_head,this.num_layers=this.config.n_layer,this.dim_kv=this.config.n_embd/this.num_heads}}class zs extends Es{}class Bs extends Es{}class Is extends N{constructor(e,t,s){super(e,t),this.generation_config=s,this.config.pad_token_id=this.config.eos_token_id,this.num_heads=this.config.num_key_value_heads??this.config.num_attention_heads,this.num_layers=this.config.num_hidden_layers,this.dim_kv=this.config.hidden_size/this.config.num_attention_heads}}class Os extends Is{}class Ds extends Is{}class Ns extends N{constructor(e,t,s){super(e,t),this.generation_config=s,this.config.pad_token_id=this.config.eos_token_id,this.num_heads=this.config.num_key_value_heads??this.config.num_attention_heads,this.num_layers=this.config.num_hidden_layers,this.dim_kv=this.config.hidden_size/this.config.num_attention_heads}}class Vs extends Ns{}class qs extends Ns{}class js extends N{constructor(e,t,s){super(e,t),this.generation_config=s,this.config.pad_token_id=this.config.eos_token_id,this.num_heads=this.config.num_attention_heads,this.num_layers=this.config.num_hidden_layers,this.dim_kv=this.config.hidden_size/this.num_heads}}class Rs extends js{}class Gs extends js{}class Ws extends N{constructor(e,t,s){super(e,t),this.generation_config=s,this.config.pad_token_id=this.config.eos_token_id,this.num_heads=this.config.n_head,this.num_layers=this.config.n_layer,this.dim_kv=this.config.hidden_size/this.num_heads}}class $s extends Ws{}class Us extends Ws{}class Xs extends N{constructor(e,t,s){super(e,t),this.generation_config=s,this.config.pad_token_id=this.config.eos_token_id,this.num_heads=this.config.n_heads,this.num_layers=this.config.n_layers,this.dim_kv=this.config.d_model/this.num_heads}}class Qs extends Xs{}class Hs extends Xs{}class Ys extends N{constructor(e,t,s){super(e,t),this.generation_config=s,this.config.pad_token_id=this.config.eos_token_id,this.num_heads=this.config.num_attention_heads,this.num_layers=this.config.num_hidden_layers,this.dim_kv=this.config.hidden_size/this.num_heads}}class Js extends Ys{}class Zs extends Ys{}class Ks extends N{}class eo extends Ks{}class to extends Ks{async _call(e){return new va(await super._call(e))}}class so extends N{}class oo extends so{async _call(e){return new Ia(await super._call(e))}}class no extends N{}class ro extends no{}class ao extends no{async _call(e){return new va(await super._call(e))}}class io extends N{}class lo extends io{}class co extends io{}class uo extends N{}class ho extends uo{}class po extends uo{}class _o extends N{}class mo extends _o{}class fo extends _o{async _call(e){return new va(await super._call(e))}}class go extends N{}class Mo extends go{}class wo extends go{async _call(e){return new ko(await super._call(e))}}class To extends go{async _call(e){return new bo(await super._call(e))}}class ko extends V{constructor({logits:e,pred_boxes:t}){super(),this.logits=e,this.pred_boxes=t}}class bo extends V{constructor({logits:e,pred_boxes:t,pred_masks:s}){super(),this.logits=e,this.pred_boxes=t,this.pred_masks=s}}class xo extends N{}class yo extends xo{}class Fo extends xo{async _call(e){return new Co(await super._call(e))}}class Co extends ko{}class Po extends N{}class vo extends Po{}class So extends Po{async _call(e){return new va(await super._call(e))}}class Ao extends N{}class Lo extends Ao{}class Eo extends Ao{async _call(e){return new va(await super._call(e))}}class zo extends N{}class Bo extends zo{}class Io extends zo{async _call(e){return new va(await super._call(e))}}class Oo extends N{}class Do extends Oo{}class No extends Oo{}class Vo extends N{}class qo extends Vo{}class jo extends Vo{}class Ro extends N{}class Go extends Ro{}class Wo extends N{}class $o extends Wo{}class Uo extends Wo{}class Xo extends N{}class Qo extends Xo{}class Ho extends N{}class Yo extends Ho{}class Jo extends Ho{async _call(e){return new va(await super._call(e))}}class Zo extends N{}class Ko extends Zo{}class en extends Zo{async _call(e){return new va(await super._call(e))}}class tn extends N{}class sn extends tn{}class on extends tn{async _call(e){return new va(await super._call(e))}}class nn extends N{}class rn extends nn{}class an extends nn{async _call(e){return new ln(await super._call(e))}}class ln extends V{constructor({logits:e,pred_boxes:t}){super(),this.logits=e,this.pred_boxes=t}}class cn extends N{}class dn extends cn{constructor(e,t,s){super(e,t),this.prompt_encoder_mask_decoder=s}async get_image_embeddings({pixel_values:e}){return await z(this,{pixel_values:e})}async forward(e){if(e.image_embeddings&&e.image_positional_embeddings||(e={...e,...await this.get_image_embeddings(e)}),!e.input_labels){const t=e.input_points.dims.slice(0,-1),s=t.reduce(((e,t)=>e*t),1);e.input_labels=new i.Tensor("int64",new BigInt64Array(s).fill(1n),t)}return await x(this.prompt_encoder_mask_decoder,{input_points:e.input_points,input_labels:e.input_labels,image_embeddings:e.image_embeddings,image_positional_embeddings:e.image_positional_embeddings})}async _call(e){return new un(await super._call(e))}}class un extends V{constructor({iou_scores:e,pred_masks:t}){super(),this.iou_scores=e,this.pred_masks=t}}class hn extends N{}class pn extends hn{}class _n extends hn{constructor(e,t,s,o){super(e,t),this.decoder_merged_session=s,this.generation_config=o,this.num_decoder_layers=this.config.decoder_layers,this.num_decoder_heads=this.config.decoder_attention_heads,this.decoder_dim_kv=this.config.d_model/this.num_decoder_heads,this.num_encoder_layers=this.config.encoder_layers,this.num_encoder_heads=this.config.encoder_attention_heads,this.encoder_dim_kv=this.config.d_model/this.num_encoder_heads}}class mn extends N{}class fn extends mn{}class gn extends mn{constructor(e,t,s,o){super(e,t),this.decoder_merged_session=s,this.generation_config=o,this.num_decoder_layers=this.config.decoder_layers,this.num_decoder_heads=this.config.decoder_attention_heads,this.decoder_dim_kv=this.config.d_model/this.num_decoder_heads,this.num_encoder_layers=this.config.encoder_layers,this.num_encoder_heads=this.config.encoder_attention_heads,this.encoder_dim_kv=this.config.d_model/this.num_encoder_heads}}class Mn extends N{}class wn extends Mn{}class Tn extends Mn{async _call(e){return new za(await super._call(e))}}class kn extends Mn{async _call(e){return new va(await super._call(e))}}class bn extends Mn{async _call(e){return new Aa(await super._call(e))}}class xn extends N{}class yn extends xn{}class Fn extends xn{async _call(e){return new za(await super._call(e))}}class Cn extends xn{async _call(e){return new va(await super._call(e))}}class Pn extends N{}class vn extends Pn{}class Sn extends Pn{async _call(e){return new za(await super._call(e))}}class An extends Pn{async _call(e){return new va(await super._call(e))}}class Ln extends Pn{async _call(e){return new Aa(await super._call(e))}}class En extends N{}class zn extends En{}class Bn extends En{async _call(e){return new za(await super._call(e))}}class In extends En{async _call(e){return new va(await super._call(e))}}class On extends N{}class Dn extends Mn{}class Nn extends Mn{async _call(e){return new za(await super._call(e))}}class Vn extends Mn{async _call(e){return new va(await super._call(e))}}class qn extends N{}class jn extends qn{}class Rn extends qn{async _call(e){return new za(await super._call(e))}}class Gn extends qn{async _call(e){return new va(await super._call(e))}}class Wn extends qn{async _call(e){return new Sa(await super._call(e))}}class $n extends qn{async _call(e){return new Aa(await super._call(e))}}class Un extends N{}class Xn extends Un{}class Qn extends Un{}class Hn extends Un{constructor(e,t,s,o){super(e,t),this.decoder_merged_session=s,this.generation_config=o,this.num_decoder_layers=this.config.decoder_layers,this.num_decoder_heads=this.config.decoder_attention_heads,this.decoder_dim_kv=this.config.hidden_size/this.num_decoder_heads,this.num_encoder_layers=this.config.encoder_layers,this.num_encoder_heads=this.config.encoder_attention_heads,this.encoder_dim_kv=this.config.hidden_size/this.num_encoder_heads}async generate_speech(e,t,{threshold:s=.5,minlenratio:o=0,maxlenratio:n=20,vocoder:r=null}={}){const a={input_ids:e},{encoder_outputs:l,encoder_attention_mask:c}=await z(this,a),d=l.dims[1]/this.config.reduction_factor,u=Math.floor(d*n),h=Math.floor(d*o),p=this.config.num_mel_bins;let _=[],m=null,f=null,g=0;for(;;){++g;const e=v(!!f);let o;o=f?f.output_sequence_out:new i.Tensor("float32",new Float32Array(p),[1,1,p]);let n={use_cache_branch:e,output_sequence:o,encoder_attention_mask:c,speaker_embeddings:t,encoder_hidden_states:l};this.addPastKeyValues(n,m),f=await x(this.decoder_merged_session,n),m=this.getPastKeyValues(f,m);const{prob:r,spectrum:a}=f;if(_.push(a),g>=h&&(Array.from(r.data).filter((e=>e>=s)).length>0||g>=u))break}const M=(0,i.cat)(_),{waveform:w}=await x(r.session,{spectrogram:M});return{spectrogram:M,waveform:w}}}class Yn extends N{main_input_name="spectrogram"}class Jn extends N{constructor(e,t,s){super(e,t),this.generation_config=s,this.config.pad_token_id=this.config.eos_token_id,this.num_encoder_layers=this.num_decoder_layers=this.config.decoder_layers,this.num_encoder_heads=this.num_decoder_heads=this.config.decoder_attention_heads,this.encoder_dim_kv=this.decoder_dim_kv=this.config.d_model/this.num_decoder_heads}}class Zn extends Jn{}class Kn extends N{constructor(e,t,s){super(e,t),this.generation_config=s,this.config.pad_token_id=this.config.eos_token_id,this.num_heads=this.config.num_key_value_heads,this.num_layers=this.config.num_hidden_layers,this.dim_kv=this.config.hidden_size/this.config.num_attention_heads}}class er extends Kn{}class tr extends Kn{}class sr extends N{constructor(e,t,s){super(e,t),this.generation_config=s,this.config.pad_token_id=this.config.eos_token_id,this.num_heads=this.config.num_key_value_heads,this.num_layers=this.config.num_hidden_layers,this.dim_kv=this.config.hidden_size/this.config.num_attention_heads}}class or extends sr{}class nr extends sr{}class rr extends N{constructor(e,t,s){super(e,t),this.generation_config=s,this.config.pad_token_id=this.config.eos_token_id,this.num_heads=this.config.num_attention_heads,this.num_layers=this.config.num_hidden_layers,this.dim_kv=this.config.hidden_size/this.config.num_attention_heads}}class ar extends rr{}class ir extends rr{}class lr extends N{}class cr extends lr{}class dr extends lr{static async from_pretrained(e,t={}){return t.model_file_name??="text_model",super.from_pretrained(e,t)}}class ur extends lr{static async from_pretrained(e,t={}){return t.model_file_name??="audio_model",super.from_pretrained(e,t)}}class hr extends N{}class pr extends hr{async _call(e){return new Oa(await super._call(e))}}class _r extends N{}class mr extends _r{}class fr extends _r{}class gr extends _r{}class Mr extends N{constructor(e,t,s){super(e,t),this.generation_config=s,this.config.pad_token_id=this.config.eos_token_id,this.num_heads=this.config.num_attention_heads,this.num_layers=this.config.num_hidden_layers,this.dim_kv=this.config.hidden_size/this.num_heads}}class wr extends Mr{}class Tr extends Mr{}class kr extends N{}class br extends kr{}class xr extends kr{async _call(e){return new va(await super._call(e))}}class yr{static MODEL_CLASS_MAPPINGS=null;static BASE_IF_FAIL=!1;static async from_pretrained(e,{quantized:t=!0,progress_callback:s=null,config:n=null,cache_dir:r=null,local_files_only:a=!1,revision:i="main",model_file_name:l=null}={}){let c={quantized:t,progress_callback:s,config:n,cache_dir:r,local_files_only:a,revision:i,model_file_name:l};if(n=await o.AutoConfig.from_pretrained(e,c),c.config||(c.config=n),!this.MODEL_CLASS_MAPPINGS)throw new Error("`MODEL_CLASS_MAPPINGS` not implemented for this type of `AutoClass`: "+this.name);for(let t of this.MODEL_CLASS_MAPPINGS){const s=t.get(n.model_type);if(s)return await s[1].from_pretrained(e,c)}if(this.BASE_IF_FAIL)return console.warn(`Unknown model class "${n.model_type}", attempting to construct from base class.`),await N.from_pretrained(e,c);throw Error(`Unsupported model type: ${n.model_type}`)}}const Fr=new Map([["bert",["BertModel",R]],["nomic_bert",["NomicBertModel",Q]],["roformer",["RoFormerModel",Y]],["electra",["ElectraModel",le]],["esm",["EsmModel",Ne]],["convbert",["ConvBertModel",se]],["camembert",["CamembertModel",_e]],["deberta",["DebertaModel",Te]],["deberta-v2",["DebertaV2Model",Ce]],["mpnet",["MPNetModel",Qe]],["albert",["AlbertModel",rt]],["distilbert",["DistilBertModel",Ee]],["roberta",["RobertaModel",Bt]],["xlm",["XLMModel",qt]],["xlm-roberta",["XLMRobertaModel",Ut]],["clap",["ClapModel",cr]],["clip",["CLIPModel",rs]],["clipseg",["CLIPSegModel",ms]],["chinese_clip",["ChineseCLIPModel",ps]],["siglip",["SiglipModel",cs]],["mobilebert",["MobileBertModel",Ge]],["squeezebert",["SqueezeBertModel",et]],["wav2vec2",["Wav2Vec2Model",wn]],["wav2vec2-bert",["Wav2Vec2BertModel",zn]],["unispeech",["UniSpeechModel",yn]],["unispeech-sat",["UniSpeechSatModel",vn]],["hubert",["HubertModel",Dn]],["wavlm",["WavLMModel",jn]],["audio-spectrogram-transformer",["ASTModel",Zt]],["vits",["VitsModel",pr]],["detr",["DetrModel",Mo]],["table-transformer",["TableTransformerModel",yo]],["vit",["ViTModel",eo]],["mobilevit",["MobileViTModel",ro]],["owlvit",["OwlViTModel",lo]],["owlv2",["Owlv2Model",ho]],["beit",["BeitModel",mo]],["deit",["DeiTModel",vo]],["convnext",["ConvNextModel",Yo]],["convnextv2",["ConvNextV2Model",Ko]],["dinov2",["Dinov2Model",sn]],["resnet",["ResNetModel",Lo]],["swin",["SwinModel",Bo]],["swin2sr",["Swin2SRModel",Do]],["donut-swin",["DonutSwinModel",Qo]],["yolos",["YolosModel",rn]],["dpt",["DPTModel",qo]],["glpn",["GLPNModel",$o]],["hifigan",["SpeechT5HifiGan",Yn]],["efficientnet",["EfficientNetModel",br]]]),Cr=new Map([["t5",["T5Model",dt]],["longt5",["LongT5Model",pt]],["mt5",["MT5Model",ft]],["bart",["BartModel",wt]],["mbart",["MBartModel",xt]],["marian",["MarianModel",pn]],["whisper",["WhisperModel",ts]],["m2m_100",["M2M100Model",fn]],["blenderbot",["BlenderbotModel",vt]],["blenderbot-small",["BlenderbotSmallModel",Lt]]]),Pr=new Map([["bloom",["BloomModel",$s]],["gpt2",["GPT2Model",Ms]],["gptj",["GPTJModel",Ps]],["gpt_bigcode",["GPTBigCodeModel",As]],["gpt_neo",["GPTNeoModel",ks]],["gpt_neox",["GPTNeoXModel",ys]],["codegen",["CodeGenModel",zs]],["llama",["LlamaModel",Os]],["qwen2",["Qwen2Model",Vs]],["phi",["PhiModel",Rs]],["mpt",["MptModel",Qs]],["opt",["OPTModel",Js]],["mistral",["MistralModel",er]],["starcoder2",["Starcoder2Model",or]],["falcon",["FalconModel",ar]]]),vr=new Map([["speecht5",["SpeechT5ForSpeechToText",Qn]],["whisper",["WhisperForConditionalGeneration",ss]]]),Sr=new Map([["speecht5",["SpeechT5ForTextToSpeech",Hn]]]),Ar=new Map([["vits",["VitsModel",pr]]]),Lr=new Map([["bert",["BertForSequenceClassification",W]],["roformer",["RoFormerForSequenceClassification",Z]],["electra",["ElectraForSequenceClassification",de]],["esm",["EsmForSequenceClassification",qe]],["convbert",["ConvBertForSequenceClassification",ne]],["camembert",["CamembertForSequenceClassification",fe]],["deberta",["DebertaForSequenceClassification",be]],["deberta-v2",["DebertaV2ForSequenceClassification",ve]],["mpnet",["MPNetForSequenceClassification",Ye]],["albert",["AlbertForSequenceClassification",at]],["distilbert",["DistilBertForSequenceClassification",ze]],["roberta",["RobertaForSequenceClassification",Ot]],["xlm",["XLMForSequenceClassification",Rt]],["xlm-roberta",["XLMRobertaForSequenceClassification",Qt]],["bart",["BartForSequenceClassification",kt]],["mbart",["MBartForSequenceClassification",Ft]],["mobilebert",["MobileBertForSequenceClassification",$e]],["squeezebert",["SqueezeBertForSequenceClassification",st]]]),Er=new Map([["bert",["BertForTokenClassification",$]],["roformer",["RoFormerForTokenClassification",K]],["electra",["ElectraForTokenClassification",ue]],["esm",["EsmForTokenClassification",je]],["convbert",["ConvBertForTokenClassification",re]],["camembert",["CamembertForTokenClassification",ge]],["deberta",["DebertaForTokenClassification",xe]],["deberta-v2",["DebertaV2ForTokenClassification",Se]],["mpnet",["MPNetForTokenClassification",Je]],["distilbert",["DistilBertForTokenClassification",Be]],["roberta",["RobertaForTokenClassification",Dt]],["xlm",["XLMForTokenClassification",Gt]],["xlm-roberta",["XLMRobertaForTokenClassification",Ht]]]),zr=new Map([["t5",["T5ForConditionalGeneration",ut]],["longt5",["LongT5ForConditionalGeneration",_t]],["mt5",["MT5ForConditionalGeneration",gt]],["bart",["BartForConditionalGeneration",Tt]],["mbart",["MBartForConditionalGeneration",yt]],["marian",["MarianMTModel",_n]],["m2m_100",["M2M100ForConditionalGeneration",gn]],["blenderbot",["BlenderbotForConditionalGeneration",St]],["blenderbot-small",["BlenderbotSmallForConditionalGeneration",Et]]]),Br=new Map([["bloom",["BloomForCausalLM",Us]],["gpt2",["GPT2LMHeadModel",ws]],["gptj",["GPTJForCausalLM",vs]],["gpt_bigcode",["GPTBigCodeForCausalLM",Ls]],["gpt_neo",["GPTNeoForCausalLM",bs]],["gpt_neox",["GPTNeoXForCausalLM",Fs]],["codegen",["CodeGenForCausalLM",Bs]],["llama",["LlamaForCausalLM",Ds]],["qwen2",["Qwen2ForCausalLM",qs]],["phi",["PhiForCausalLM",Gs]],["mpt",["MptForCausalLM",Hs]],["opt",["OPTForCausalLM",Zs]],["mbart",["MBartForCausalLM",Ct]],["mistral",["MistralForCausalLM",tr]],["starcoder2",["Starcoder2ForCausalLM",nr]],["falcon",["FalconForCausalLM",ir]],["trocr",["TrOCRForCausalLM",Zn]],["stablelm",["StableLmForCausalLM",Tr]]]),Ir=new Map([["bert",["BertForMaskedLM",G]],["roformer",["RoFormerForMaskedLM",J]],["electra",["ElectraForMaskedLM",ce]],["esm",["EsmForMaskedLM",Ve]],["convbert",["ConvBertForMaskedLM",oe]],["camembert",["CamembertForMaskedLM",me]],["deberta",["DebertaForMaskedLM",ke]],["deberta-v2",["DebertaV2ForMaskedLM",Pe]],["mpnet",["MPNetForMaskedLM",He]],["albert",["AlbertForMaskedLM",lt]],["distilbert",["DistilBertForMaskedLM",Oe]],["roberta",["RobertaForMaskedLM",It]],["xlm",["XLMWithLMHeadModel",jt]],["xlm-roberta",["XLMRobertaForMaskedLM",Xt]],["mobilebert",["MobileBertForMaskedLM",We]],["squeezebert",["SqueezeBertForMaskedLM",tt]]]),Or=new Map([["bert",["BertForQuestionAnswering",U]],["roformer",["RoFormerForQuestionAnswering",ee]],["electra",["ElectraForQuestionAnswering",he]],["convbert",["ConvBertForQuestionAnswering",ae]],["camembert",["CamembertForQuestionAnswering",Me]],["deberta",["DebertaForQuestionAnswering",ye]],["deberta-v2",["DebertaV2ForQuestionAnswering",Ae]],["mpnet",["MPNetForQuestionAnswering",Ze]],["albert",["AlbertForQuestionAnswering",it]],["distilbert",["DistilBertForQuestionAnswering",Ie]],["roberta",["RobertaForQuestionAnswering",Nt]],["xlm",["XLMForQuestionAnswering",Wt]],["xlm-roberta",["XLMRobertaForQuestionAnswering",Yt]],["mobilebert",["MobileBertForQuestionAnswering",Ue]],["squeezebert",["SqueezeBertForQuestionAnswering",ot]]]),Dr=new Map([["vision-encoder-decoder",["VisionEncoderDecoderModel",os]]]),Nr=new Map([["vision-encoder-decoder",["VisionEncoderDecoderModel",os]]]),Vr=new Map([["vit",["ViTForImageClassification",to]],["mobilevit",["MobileViTForImageClassification",ao]],["beit",["BeitForImageClassification",fo]],["deit",["DeiTForImageClassification",So]],["convnext",["ConvNextForImageClassification",Jo]],["convnextv2",["ConvNextV2ForImageClassification",en]],["dinov2",["Dinov2ForImageClassification",on]],["resnet",["ResNetForImageClassification",Eo]],["swin",["SwinForImageClassification",Io]],["segformer",["SegformerForImageClassification",fr]],["efficientnet",["EfficientNetForImageClassification",xr]]]),qr=new Map([["detr",["DetrForObjectDetection",wo]],["table-transformer",["TableTransformerForObjectDetection",Fo]],["yolos",["YolosForObjectDetection",an]]]),jr=new Map([["owlvit",["OwlViTForObjectDetection",co]],["owlv2",["Owlv2ForObjectDetection",po]]]),Rr=new Map([["detr",["DetrForSegmentation",To]],["clipseg",["CLIPSegForImageSegmentation",fs]]]),Gr=new Map([["segformer",["SegformerForSemanticSegmentation",gr]]]),Wr=new Map([["sam",["SamModel",dn]]]),$r=new Map([["wav2vec2",["Wav2Vec2ForCTC",Tn]],["wav2vec2-bert",["Wav2Vec2BertForCTC",Bn]],["unispeech",["UniSpeechForCTC",Fn]],["unispeech-sat",["UniSpeechSatForCTC",Sn]],["wavlm",["WavLMForCTC",Rn]],["hubert",["HubertForCTC",Nn]]]),Ur=new Map([["wav2vec2",["Wav2Vec2ForSequenceClassification",kn]],["wav2vec2-bert",["Wav2Vec2BertForSequenceClassification",In]],["unispeech",["UniSpeechForSequenceClassification",Cn]],["unispeech-sat",["UniSpeechSatForSequenceClassification",An]],["wavlm",["WavLMForSequenceClassification",Gn]],["hubert",["HubertForSequenceClassification",Vn]],["audio-spectrogram-transformer",["ASTForAudioClassification",Kt]]]),Xr=new Map([["wavlm",["WavLMForXVector",Wn]]]),Qr=new Map([["unispeech-sat",["UniSpeechSatForAudioFrameClassification",Ln]],["wavlm",["WavLMForAudioFrameClassification",$n]],["wav2vec2",["Wav2Vec2ForAudioFrameClassification",bn]]]),Hr=new Map([["vitmatte",["VitMatteForImageMatting",oo]]]),Yr=new Map([["swin2sr",["Swin2SRForImageSuperResolution",No]]]),Jr=new Map([["dpt",["DPTForDepthEstimation",jo]],["depth_anything",["DepthAnythingForDepthEstimation",Go]],["glpn",["GLPNForDepthEstimation",Uo]]]),Zr=new Map([["clip",["CLIPVisionModelWithProjection",is]],["siglip",["SiglipVisionModel",us]]]),Kr=[[Fr,p],[Cr,_],[Pr,g],[Lr,p],[Er,p],[zr,m],[vr,m],[Br,g],[Ir,p],[Or,p],[Dr,f],[Vr,p],[Rr,p],[Gr,p],[Hr,p],[Yr,p],[Jr,p],[qr,p],[jr,p],[Wr,M],[$r,p],[Ur,p],[Sr,m],[Ar,p],[Xr,p],[Qr,p],[Zr,p]];for(const[e,t]of Kr)for(const[s,o]of e.values())w.set(s,t),k.set(o,s),T.set(s,o);const ea=[["CLIPTextModelWithProjection",as,p],["SiglipTextModel",ds,p],["ClapTextModelWithProjection",dr,p],["ClapAudioModelWithProjection",ur,p]];for(const[e,t,s]of ea)w.set(e,s),k.set(t,e),T.set(e,t);class ta extends yr{static MODEL_CLASS_MAPPINGS=Kr.map((e=>e[0]));static BASE_IF_FAIL=!0}class sa extends yr{static MODEL_CLASS_MAPPINGS=[Lr]}class oa extends yr{static MODEL_CLASS_MAPPINGS=[Er]}class na extends yr{static MODEL_CLASS_MAPPINGS=[zr]}class ra extends yr{static MODEL_CLASS_MAPPINGS=[vr]}class aa extends yr{static MODEL_CLASS_MAPPINGS=[Sr]}class ia extends yr{static MODEL_CLASS_MAPPINGS=[Ar]}class la extends yr{static MODEL_CLASS_MAPPINGS=[Br]}class ca extends yr{static MODEL_CLASS_MAPPINGS=[Ir]}class da extends yr{static MODEL_CLASS_MAPPINGS=[Or]}class ua extends yr{static MODEL_CLASS_MAPPINGS=[Dr]}class ha extends yr{static MODEL_CLASS_MAPPINGS=[Vr]}class pa extends yr{static MODEL_CLASS_MAPPINGS=[Rr]}class _a extends yr{static MODEL_CLASS_MAPPINGS=[Gr]}class ma extends yr{static MODEL_CLASS_MAPPINGS=[qr]}class fa extends yr{static MODEL_CLASS_MAPPINGS=[jr]}class ga extends yr{static MODEL_CLASS_MAPPINGS=[Wr]}class Ma extends yr{static MODEL_CLASS_MAPPINGS=[$r]}class wa extends yr{static MODEL_CLASS_MAPPINGS=[Ur]}class Ta extends yr{static MODEL_CLASS_MAPPINGS=[Xr]}class ka extends yr{static MODEL_CLASS_MAPPINGS=[Qr]}class ba extends yr{static MODEL_CLASS_MAPPINGS=[Nr]}class xa extends yr{static MODEL_CLASS_MAPPINGS=[Hr]}class ya extends yr{static MODEL_CLASS_MAPPINGS=[Yr]}class Fa extends yr{static MODEL_CLASS_MAPPINGS=[Jr]}class Ca extends yr{static MODEL_CLASS_MAPPINGS=[Zr]}class Pa extends V{constructor({logits:e,past_key_values:t,encoder_outputs:s,decoder_attentions:o=null,cross_attentions:n=null}){super(),this.logits=e,this.past_key_values=t,this.encoder_outputs=s,this.decoder_attentions=o,this.cross_attentions=n}}class va extends V{constructor({logits:e}){super(),this.logits=e}}class Sa extends V{constructor({logits:e,embeddings:t}){super(),this.logits=e,this.embeddings=t}}class Aa extends V{constructor({logits:e}){super(),this.logits=e}}class La extends V{constructor({logits:e}){super(),this.logits=e}}class Ea extends V{constructor({start_logits:e,end_logits:t}){super(),this.start_logits=e,this.end_logits=t}}class za extends V{constructor({logits:e}){super(),this.logits=e}}class Ba extends V{constructor({logits:e,past_key_values:t}){super(),this.logits=e,this.past_key_values=t}}class Ia extends V{constructor({alphas:e}){super(),this.alphas=e}}class Oa extends V{constructor({waveform:e,spectrogram:t}){super(),this.waveform=e,this.spectrogram=t}}},"./src/pipelines.js":
/*!**************************!*\
  !*** ./src/pipelines.js ***!
  \**************************/(e,t,s)=>{s.r(t),s.d(t,{AudioClassificationPipeline:()=>C,AutomaticSpeechRecognitionPipeline:()=>v,DepthEstimationPipeline:()=>N,DocumentQuestionAnsweringPipeline:()=>I,FeatureExtractionPipeline:()=>y,FillMaskPipeline:()=>M,ImageClassificationPipeline:()=>A,ImageFeatureExtractionPipeline:()=>F,ImageSegmentationPipeline:()=>L,ImageToImagePipeline:()=>D,ImageToTextPipeline:()=>S,ObjectDetectionPipeline:()=>z,Pipeline:()=>_,QuestionAnsweringPipeline:()=>g,SummarizationPipeline:()=>T,Text2TextGenerationPipeline:()=>w,TextClassificationPipeline:()=>m,TextGenerationPipeline:()=>b,TextToAudioPipeline:()=>O,TokenClassificationPipeline:()=>f,TranslationPipeline:()=>k,ZeroShotAudioClassificationPipeline:()=>P,ZeroShotClassificationPipeline:()=>x,ZeroShotImageClassificationPipeline:()=>E,ZeroShotObjectDetectionPipeline:()=>B,pipeline:()=>j});var o=s(/*! ./tokenizers.js */"./src/tokenizers.js"),n=s(/*! ./models.js */"./src/models.js"),r=s(/*! ./processors.js */"./src/processors.js"),a=s(/*! ./utils/core.js */"./src/utils/core.js"),i=s(/*! ./utils/maths.js */"./src/utils/maths.js"),l=s(/*! ./utils/audio.js */"./src/utils/audio.js"),c=s(/*! ./utils/tensor.js */"./src/utils/tensor.js"),d=s(/*! ./utils/image.js */"./src/utils/image.js");async function u(e){return Array.isArray(e)||(e=[e]),await Promise.all(e.map((e=>d.RawImage.read(e))))}async function h(e,t){return Array.isArray(e)||(e=[e]),await Promise.all(e.map((e=>"string"==typeof e||e instanceof URL?(0,l.read_audio)(e,t):e instanceof Float64Array?new Float32Array(e):e)))}function p(e,t){t&&(e=e.map((e=>0|e)));const[s,o,n,r]=e;return{xmin:s,ymin:o,xmax:n,ymax:r}}class _ extends a.Callable{constructor({task:e,model:t,tokenizer:s=null,processor:o=null}){super(),this.task=e,this.model=t,this.tokenizer=s,this.processor=o}async dispose(){await this.model.dispose()}}class m extends _{constructor(e){super(e)}async _call(e,{topk:t=1}={}){const s=this.tokenizer(e,{padding:!0,truncation:!0}),o=await this.model(s),n="multi_label_classification"===this.model.config.problem_type?e=>e.sigmoid().data:e=>(0,i.softmax)(e.data),r=this.model.config.id2label,a=[];for(const e of o.logits){const s=n(e),o=(0,i.getTopItems)(s,t).map((e=>({label:r[e[0]],score:e[1]})));1===t?a.push(...o):a.push(o)}return Array.isArray(e)||1===t?a:a[0]}}class f extends _{constructor(e){super(e)}async _call(e,{ignore_labels:t=["O"]}={}){const s=Array.isArray(e),o=this.tokenizer(s?e:[e],{padding:!0,truncation:!0}),n=(await this.model(o)).logits,r=this.model.config.id2label,a=[];for(let e=0;e<n.dims[0];++e){const s=o.input_ids[e],l=n[e],c=[];for(let e=0;e<l.dims[0];++e){const o=l[e],n=(0,i.max)(o.data)[1],a=r?r[n]:`LABEL_${n}`;if(t.includes(a))continue;const d=this.tokenizer.decode([s[e].item()],{skip_special_tokens:!0});if(""===d)continue;const u=(0,i.softmax)(o.data);c.push({entity:a,score:u[n],index:e,word:d,start:null,end:null})}a.push(c)}return s?a:a[0]}}class g extends _{constructor(e){super(e)}async _call(e,t,{topk:s=1}={}){const o=this.tokenizer(e,{text_pair:t,padding:!0,truncation:!0}),n=await this.model(o),r=[];for(let e=0;e<n.start_logits.dims[0];++e){const t=o.input_ids[e],l=t.indexOf(this.tokenizer.sep_token_id),c=Array.from((0,i.softmax)(n.start_logits[e].data)).map(((e,t)=>[e,t])).filter((e=>e[1]>l)),d=Array.from((0,i.softmax)(n.end_logits[e].data)).map(((e,t)=>[e,t])).filter((e=>e[1]>l)),u=(0,a.product)(c,d).filter((e=>e[0][1]<=e[1][1])).map((e=>[e[0][1],e[1][1],e[0][0]*e[1][0]])).sort(((e,t)=>t[2]-e[2]));for(let e=0;e<Math.min(u.length,s);++e){const[s,o,n]=u[e],a=[...t].slice(s,o+1),i=this.tokenizer.decode(a,{skip_special_tokens:!0});r.push({answer:i,score:n})}}return 1===s?r[0]:r}}class M extends _{constructor(e){super(e)}async _call(e,{topk:t=5}={}){const s=this.tokenizer(e,{padding:!0,truncation:!0}),o=await this.model(s),n=[];for(let e=0;e<s.input_ids.dims[0];++e){const r=s.input_ids[e],a=r.indexOf(this.tokenizer.mask_token_id);if(-1===a)throw Error(`Mask token (${this.tokenizer.mask_token}) not found in text.`);const l=o.logits[e][a],c=(0,i.getTopItems)((0,i.softmax)(l.data),t);n.push(c.map((e=>{const t=[...r];return t[a]=e[0],{score:e[1],token:e[0],token_str:this.tokenizer.model.vocab[e[0]],sequence:this.tokenizer.decode(t,{skip_special_tokens:!0})}})))}return Array.isArray(e)?n:n[0]}}class w extends _{_key="generated_text";constructor(e){super(e)}async _call(e,t={}){Array.isArray(e)||(e=[e]),this.model.config.prefix&&(e=e.map((e=>this.model.config.prefix+e)));const s=this.model.config.task_specific_params;s&&s[this.task]&&s[this.task].prefix&&(e=e.map((e=>s[this.task].prefix+e)));const o=this.tokenizer,n={padding:!0,truncation:!0};let r;r=this instanceof k&&"_build_translation_inputs"in o?o._build_translation_inputs(e,n,t).input_ids:o(e,n).input_ids;const a=await this.model.generate(r,t);return o.batch_decode(a,{skip_special_tokens:!0}).map((e=>({[this._key]:e})))}}class T extends w{_key="summary_text";constructor(e){super(e)}}class k extends w{_key="translation_text";constructor(e){super(e)}}class b extends _{constructor(e){super(e)}async _call(e,t={}){const s=Array.isArray(e);s||(e=[e]);const o=t.add_special_tokens??!1;this.tokenizer.padding_side="left";const{input_ids:n,attention_mask:r}=this.tokenizer(e,{add_special_tokens:o,padding:!0,truncation:!0}),a=await this.model.generate(n,t,null,{inputs_attention_mask:r}),i=this.tokenizer.batch_decode(a,{skip_special_tokens:!0}),l=Array.from({length:e.length},(e=>[]));for(let t=0;t<i.length;++t){l[Math.floor(t/a.length*e.length)].push({generated_text:i[t]})}return s||1!==l.length?l:l[0]}}class x extends _{constructor(e){super(e),this.label2id=Object.fromEntries(Object.entries(this.model.config.label2id).map((([e,t])=>[e.toLowerCase(),t]))),this.entailment_id=this.label2id.entailment,void 0===this.entailment_id&&(console.warn("Could not find 'entailment' in label2id mapping. Using 2 as entailment_id."),this.entailment_id=2),this.contradiction_id=this.label2id.contradiction??this.label2id.not_entailment,void 0===this.contradiction_id&&(console.warn("Could not find 'contradiction' in label2id mapping. Using 0 as contradiction_id."),this.contradiction_id=0)}async _call(e,t,{hypothesis_template:s="This example is {}.",multi_label:o=!1}={}){const n=Array.isArray(e);n||(e=[e]),Array.isArray(t)||(t=[t]);const r=t.map((e=>s.replace("{}",e))),a=o||1===t.length,l=[];for(const s of e){const e=[];for(const t of r){const o=this.tokenizer(s,{text_pair:t,padding:!0,truncation:!0}),n=await this.model(o);a?e.push([n.logits.data[this.contradiction_id],n.logits.data[this.entailment_id]]):e.push(n.logits.data[this.entailment_id])}const o=(a?e.map((e=>(0,i.softmax)(e)[1])):(0,i.softmax)(e)).map(((e,t)=>[e,t])).sort(((e,t)=>t[0]-e[0]));l.push({sequence:s,labels:o.map((e=>t[e[1]])),scores:o.map((e=>e[0]))})}return n?l:l[0]}}class y extends _{constructor(e){super(e)}async _call(e,{pooling:t="none",normalize:s=!1}={}){const o=this.tokenizer(e,{padding:!0,truncation:!0}),n=await this.model(o);let r=n.last_hidden_state??n.logits;if("none"===t);else if("mean"===t)r=(0,c.mean_pooling)(r,o.attention_mask);else{if("cls"!==t)throw Error(`Pooling method '${t}' not supported.`);r=r.slice(null,0)}return s&&(r=r.normalize(2,-1)),r}}class F extends _{constructor(e){super(e)}async _call(e,{pool:t=null}={}){const s=await u(e),{pixel_values:o}=await this.processor(s),n=await this.model({pixel_values:o});let r;if(t){if(!("pooler_output"in n))throw Error("No pooled output was returned. Make sure the model has a 'pooler' layer when using the 'pool' option.");r=n.pooler_output}else r=n.last_hidden_state??n.logits??n.image_embeds;return r}}class C extends _{constructor(e){super(e)}async _call(e,{topk:t=null}={}){const s=!Array.isArray(e),o=this.processor.feature_extractor.config.sampling_rate,n=await h(e,o),r=this.model.config.id2label,a=[];for(const e of n){const s=await this.processor(e),o=(await this.model(s)).logits[0],n=(0,i.getTopItems)((0,i.softmax)(o.data),t).map((e=>({label:r[e[0]],score:e[1]})));1===t?a.push(...n):a.push(n)}return s&&1!==t?a[0]:a}}class P extends _{constructor(e){super(e)}async _call(e,t,{hypothesis_template:s="This is a sound of {}."}={}){const o=!Array.isArray(e);o&&(e=[e]);const n=t.map((e=>s.replace("{}",e))),r=this.tokenizer(n,{padding:!0,truncation:!0}),a=this.processor.feature_extractor.config.sampling_rate,l=await h(e,a),c=[];for(const e of l){const s=await this.processor(e),o=await this.model({...r,...s}),n=(0,i.softmax)(o.logits_per_audio.data);c.push([...n].map(((e,s)=>({score:e,label:t[s]}))))}return o?c[0]:c}}class v extends _{constructor(e){super(e)}async _call(e,t={}){switch(this.model.config.model_type){case"whisper":return this._call_whisper(e,t);case"wav2vec2":case"wav2vec2-bert":case"unispeech":case"unispeech-sat":case"hubert":return this._call_wav2vec2(e,t);default:throw new Error(`AutomaticSpeechRecognitionPipeline does not support model type '${this.model.config.model_type}'.`)}}async _call_wav2vec2(e,t={}){t.language&&console.warn('`language` parameter is not yet supported for `wav2vec2` models, defaulting to "English".'),t.task&&console.warn('`task` parameter is not yet supported for `wav2vec2` models, defaulting to "transcribe".');const s=!Array.isArray(e);s&&(e=[e]);const o=this.processor.feature_extractor.config.sampling_rate,n=await h(e,o),r=[];for(const e of n){const t=await this.processor(e),s=(await this.model(t)).logits[0],o=[];for(const e of s)o.push((0,i.max)(e.data)[1]);const n=this.tokenizer.decode(o);r.push({text:n})}return s?r[0]:r}async _call_whisper(e,t={}){const s=t.return_timestamps??!1,o=t.chunk_length_s??0,n=t.chunk_callback??null,r=t.force_full_sequences??!1;let l=t.stride_length_s??null;"word"===s&&(t.return_token_timestamps=!0);const c=(0,a.pop)(t,"language",null),d=(0,a.pop)(t,"task",null);if(c||d||s){if(t.forced_decoder_ids)throw new Error("Cannot specify `language`/`task`/`return_timestamps` and `forced_decoder_ids` at the same time.");const e=this.tokenizer.get_decoder_prompt_ids({language:c,task:d,no_timestamps:!s});e.length>0&&(t.forced_decoder_ids=e)}const u=!Array.isArray(e);u&&(e=[e]);const p=this.processor.feature_extractor.config.chunk_length/this.model.config.max_source_positions,_=this.processor.feature_extractor.config.hop_length,m=this.processor.feature_extractor.config.sampling_rate,f=await h(e,m),g=[];for(const e of f){let a=[];if(o>0){if(null===l)l=o/6;else if(o<=l)throw Error("`chunk_length_s` must be larger than `stride_length_s`.");const t=m*o,s=m*l,n=t-2*s;let r=0;for(;r<e.length;){const o=e.subarray(r,r+t),i=await this.processor(o),l=0===r,c=r+n>=e.length;a.push({stride:[o.length,l?0:s,c?0:s],input_features:i.input_features,is_last:c}),r+=n}}else a=[{stride:[e.length,0,0],input_features:(await this.processor(e)).input_features,is_last:!0}];for(const e of a){t.num_frames=Math.floor(e.stride[0]/_);const o=await this.model.generate(e.input_features,t);"word"===s?(e.tokens=o.sequences[0],e.token_timestamps=o.token_timestamps.tolist()[0].map((e=>(0,i.round)(e,2)))):e.tokens=o[0],e.stride=e.stride.map((e=>e/m)),null!==n&&n(e)}const[c,d]=this.tokenizer._decode_asr(a,{time_precision:p,return_timestamps:s,force_full_sequences:r});g.push({text:c,...d})}return u?g[0]:g}}class S extends _{constructor(e){super(e)}async _call(e,t={}){const s=Array.isArray(e),o=await u(e),{pixel_values:n}=await this.processor(o),r=[];for(const e of n){e.dims=[1,...e.dims];const s=await this.model.generate(e,t),o=this.tokenizer.batch_decode(s,{skip_special_tokens:!0}).map((e=>({generated_text:e.trim()})));r.push(o)}return s?r:r[0]}}class A extends _{constructor(e){super(e)}async _call(e,{topk:t=1}={}){const s=Array.isArray(e),o=await u(e),{pixel_values:n}=await this.processor(o),r=await this.model({pixel_values:n}),a=this.model.config.id2label,l=[];for(const e of r.logits){const s=(0,i.getTopItems)((0,i.softmax)(e.data),t).map((e=>({label:a[e[0]],score:e[1]})));1===t?l.push(...s):l.push(s)}return s||1===t?l:l[0]}}class L extends _{constructor(e){super(e),this.subtasks_mapping={panoptic:"post_process_panoptic_segmentation",instance:"post_process_instance_segmentation",semantic:"post_process_semantic_segmentation"}}async _call(e,{threshold:t=.5,mask_threshold:s=.5,overlap_mask_area_threshold:o=.8,label_ids_to_fuse:n=null,target_sizes:r=null,subtask:a=null}={}){if(Array.isArray(e)&&1!==e.length)throw Error("Image segmentation pipeline currently only supports a batch size of 1.");const i=await u(e),l=i.map((e=>[e.height,e.width])),{pixel_values:c,pixel_mask:h}=await this.processor(i),p=await this.model({pixel_values:c,pixel_mask:h});let _=null;if(null!==a)_=this.subtasks_mapping[a];else for(let[e,t]of Object.entries(this.subtasks_mapping))if(t in this.processor.feature_extractor){_=this.processor.feature_extractor[t].bind(this.processor.feature_extractor),a=e;break}const m=this.model.config.id2label,f=[];if("panoptic"===a||"instance"===a){const e=_(p,t,s,o,n,r??l)[0],a=e.segmentation;for(const t of e.segments_info){const e=new Uint8ClampedArray(a.data.length);for(let s=0;s<a.data.length;++s)a.data[s]===t.id&&(e[s]=255);const s=new d.RawImage(e,a.dims[1],a.dims[0],1);f.push({score:t.score,label:m[t.label_id],mask:s})}}else{if("semantic"!==a)throw Error(`Subtask ${a} not supported.`);{const{segmentation:e,labels:t}=_(p,r??l)[0];for(const s of t){const t=new Uint8ClampedArray(e.data.length);for(let o=0;o<e.data.length;++o)e.data[o]===s&&(t[o]=255);const o=new d.RawImage(t,e.dims[1],e.dims[0],1);f.push({score:null,label:m[s],mask:o})}}}return f}}class E extends _{constructor(e){super(e)}async _call(e,t,{hypothesis_template:s="This is a photo of {}"}={}){const o=Array.isArray(e),n=await u(e),r=t.map((e=>s.replace("{}",e))),a=this.tokenizer(r,{padding:"siglip"!==this.model.config.model_type||"max_length",truncation:!0}),{pixel_values:l}=await this.processor(n),c=await this.model({...a,pixel_values:l}),d="siglip"===this.model.config.model_type?e=>e.sigmoid().data:e=>(0,i.softmax)(e.data),h=[];for(const e of c.logits_per_image){const s=[...d(e)].map(((e,s)=>({score:e,label:t[s]})));s.sort(((e,t)=>t.score-e.score)),h.push(s)}return o?h:h[0]}}class z extends _{constructor(e){super(e)}async _call(e,{threshold:t=.9,percentage:s=!1}={}){const o=Array.isArray(e);if(o&&1!==e.length)throw Error("Object detection pipeline currently only supports a batch size of 1.");const n=await u(e),r=s?null:n.map((e=>[e.height,e.width])),{pixel_values:a,pixel_mask:i}=await this.processor(n),l=await this.model({pixel_values:a,pixel_mask:i}),c=this.processor.feature_extractor.post_process_object_detection(l,t,r),d=this.model.config.id2label,h=c.map((e=>e.boxes.map(((t,o)=>({score:e.scores[o],label:d[e.classes[o]],box:p(t,!s)})))));return o?h:h[0]}}class B extends _{constructor(e){super(e)}async _call(e,t,{threshold:s=.1,topk:o=null,percentage:n=!1}={}){const r=Array.isArray(e),a=await u(e),i=this.tokenizer(t,{padding:!0,truncation:!0}),l=await this.processor(a),c=[];for(let e=0;e<a.length;++e){const r=a[e],d=n?null:[[r.height,r.width]],u=l.pixel_values[e].unsqueeze_(0),h=await this.model({...i,pixel_values:u}),_=this.processor.feature_extractor.post_process_object_detection(h,s,d,!0)[0];let m=_.boxes.map(((e,s)=>({score:_.scores[s],label:t[_.classes[s]],box:p(e,!n)}))).sort(((e,t)=>t.score-e.score));null!==o&&(m=m.slice(0,o)),c.push(m)}return r?c:c[0]}}class I extends _{constructor(e){super(e)}async _call(e,t,s={}){const o=(await u(e))[0],{pixel_values:n}=await this.processor(o),r=`<s_docvqa><s_question>${t}</s_question><s_answer>`,a=this.tokenizer(r,{add_special_tokens:!1,padding:!0,truncation:!0}).input_ids,i=await this.model.generate(n,{...s,decoder_input_ids:a,max_length:this.model.config.decoder.max_position_embeddings}),l=this.tokenizer.batch_decode(i)[0].match(/<s_answer>(.*?)<\/s_answer>/);let c=null;return l&&l.length>=2&&(c=l[1].trim()),[{answer:c}]}}class O extends _{DEFAULT_VOCODER_ID="Xenova/speecht5_hifigan";constructor(e){super(e),this.vocoder=e.vocoder??null}async _call(e,{speaker_embeddings:t=null}={}){return this.processor?this._call_text_to_spectrogram(e,{speaker_embeddings:t}):this._call_text_to_waveform(e)}async _call_text_to_waveform(e){const t=this.tokenizer(e,{padding:!0,truncation:!0}),{waveform:s}=await this.model(t),o=this.model.config.sampling_rate;return{audio:s.data,sampling_rate:o}}async _call_text_to_spectrogram(e,{speaker_embeddings:t}){if(this.vocoder||(console.log("No vocoder specified, using default HifiGan vocoder."),this.vocoder=await n.AutoModel.from_pretrained(this.DEFAULT_VOCODER_ID,{quantized:!1})),("string"==typeof t||t instanceof URL)&&(t=new Float32Array(await(await fetch(t)).arrayBuffer())),t instanceof Float32Array)t=new c.Tensor("float32",t,[1,t.length]);else if(!(t instanceof c.Tensor))throw new Error("Speaker embeddings must be a `Tensor`, `Float32Array`, `string`, or `URL`.");const{input_ids:s}=this.tokenizer(e,{padding:!0,truncation:!0}),{waveform:o}=await this.model.generate_speech(s,t,{vocoder:this.vocoder}),r=this.processor.feature_extractor.config.sampling_rate;return{audio:o.data,sampling_rate:r}}}class D extends _{constructor(e){super(e)}async _call(e){const t=await u(e),s=await this.processor(t),o=await this.model(s),n=[];for(const e of o.reconstruction){const t=e.squeeze().clamp_(0,1).mul_(255).round_().to("uint8");n.push(d.RawImage.fromTensor(t))}return n.length>1?n:n[0]}}class N extends _{constructor(e){super(e)}async _call(e){const t=await u(e),s=await this.processor(t),{predicted_depth:o}=await this.model(s),n=[];for(let e=0;e<t.length;++e){const s=(0,c.interpolate)(o[e],t[e].size.reverse(),"bilinear",!1),r=s.mul_(255/(0,i.max)(s.data)[0]).to("uint8");n.push({predicted_depth:o[e],depth:d.RawImage.fromTensor(r)})}return n.length>1?n:n[0]}}const V=Object.freeze({"text-classification":{tokenizer:o.AutoTokenizer,pipeline:m,model:n.AutoModelForSequenceClassification,default:{model:"Xenova/distilbert-base-uncased-finetuned-sst-2-english"},type:"text"},"token-classification":{tokenizer:o.AutoTokenizer,pipeline:f,model:n.AutoModelForTokenClassification,default:{model:"Xenova/bert-base-multilingual-cased-ner-hrl"},type:"text"},"question-answering":{tokenizer:o.AutoTokenizer,pipeline:g,model:n.AutoModelForQuestionAnswering,default:{model:"Xenova/distilbert-base-cased-distilled-squad"},type:"text"},"fill-mask":{tokenizer:o.AutoTokenizer,pipeline:M,model:n.AutoModelForMaskedLM,default:{model:"Xenova/bert-base-uncased"},type:"text"},summarization:{tokenizer:o.AutoTokenizer,pipeline:T,model:n.AutoModelForSeq2SeqLM,default:{model:"Xenova/distilbart-cnn-6-6"},type:"text"},translation:{tokenizer:o.AutoTokenizer,pipeline:k,model:n.AutoModelForSeq2SeqLM,default:{model:"Xenova/t5-small"},type:"text"},"text2text-generation":{tokenizer:o.AutoTokenizer,pipeline:w,model:n.AutoModelForSeq2SeqLM,default:{model:"Xenova/flan-t5-small"},type:"text"},"text-generation":{tokenizer:o.AutoTokenizer,pipeline:b,model:n.AutoModelForCausalLM,default:{model:"Xenova/gpt2"},type:"text"},"zero-shot-classification":{tokenizer:o.AutoTokenizer,pipeline:x,model:n.AutoModelForSequenceClassification,default:{model:"Xenova/distilbert-base-uncased-mnli"},type:"text"},"audio-classification":{pipeline:C,model:n.AutoModelForAudioClassification,processor:r.AutoProcessor,default:{model:"Xenova/wav2vec2-base-superb-ks"},type:"audio"},"zero-shot-audio-classification":{tokenizer:o.AutoTokenizer,pipeline:P,model:n.AutoModel,processor:r.AutoProcessor,default:{model:"Xenova/clap-htsat-unfused"},type:"multimodal"},"automatic-speech-recognition":{tokenizer:o.AutoTokenizer,pipeline:v,model:[n.AutoModelForSpeechSeq2Seq,n.AutoModelForCTC],processor:r.AutoProcessor,default:{model:"Xenova/whisper-tiny.en"},type:"multimodal"},"text-to-audio":{tokenizer:o.AutoTokenizer,pipeline:O,model:[n.AutoModelForTextToWaveform,n.AutoModelForTextToSpectrogram],processor:[r.AutoProcessor,null],default:{model:"Xenova/speecht5_tts"},type:"text"},"image-to-text":{tokenizer:o.AutoTokenizer,pipeline:S,model:n.AutoModelForVision2Seq,processor:r.AutoProcessor,default:{model:"Xenova/vit-gpt2-image-captioning"},type:"multimodal"},"image-classification":{pipeline:A,model:n.AutoModelForImageClassification,processor:r.AutoProcessor,default:{model:"Xenova/vit-base-patch16-224"},type:"multimodal"},"image-segmentation":{pipeline:L,model:[n.AutoModelForImageSegmentation,n.AutoModelForSemanticSegmentation],processor:r.AutoProcessor,default:{model:"Xenova/detr-resnet-50-panoptic"},type:"multimodal"},"zero-shot-image-classification":{tokenizer:o.AutoTokenizer,pipeline:E,model:n.AutoModel,processor:r.AutoProcessor,default:{model:"Xenova/clip-vit-base-patch32"},type:"multimodal"},"object-detection":{pipeline:z,model:n.AutoModelForObjectDetection,processor:r.AutoProcessor,default:{model:"Xenova/detr-resnet-50"},type:"multimodal"},"zero-shot-object-detection":{tokenizer:o.AutoTokenizer,pipeline:B,model:n.AutoModelForZeroShotObjectDetection,processor:r.AutoProcessor,default:{model:"Xenova/owlvit-base-patch32"},type:"multimodal"},"document-question-answering":{tokenizer:o.AutoTokenizer,pipeline:I,model:n.AutoModelForDocumentQuestionAnswering,processor:r.AutoProcessor,default:{model:"Xenova/donut-base-finetuned-docvqa"},type:"multimodal"},"image-to-image":{pipeline:D,model:n.AutoModelForImageToImage,processor:r.AutoProcessor,default:{model:"Xenova/swin2SR-classical-sr-x2-64"},type:"image"},"depth-estimation":{pipeline:N,model:n.AutoModelForDepthEstimation,processor:r.AutoProcessor,default:{model:"Xenova/dpt-large"},type:"image"},"feature-extraction":{tokenizer:o.AutoTokenizer,pipeline:y,model:n.AutoModel,default:{model:"Xenova/all-MiniLM-L6-v2"},type:"text"},"image-feature-extraction":{processor:r.AutoProcessor,pipeline:F,model:[n.AutoModelForImageFeatureExtraction,n.AutoModel],default:{model:"Xenova/vit-base-patch16-224-in21k"},type:"image"}}),q=Object.freeze({"sentiment-analysis":"text-classification",ner:"token-classification",asr:"automatic-speech-recognition","text-to-speech":"text-to-audio",embeddings:"feature-extraction"});async function j(e,t=null,{quantized:s=!0,progress_callback:o=null,config:n=null,cache_dir:r=null,local_files_only:i=!1,revision:l="main"}={}){e=q[e]??e;const c=V[e.split("_",1)[0]];if(!c)throw Error(`Unsupported pipeline: ${e}. Must be one of [${Object.keys(V)}]`);t||(t=c.default.model,console.log(`No model specified. Using default model: "${t}".`));const d={quantized:s,progress_callback:o,config:n,cache_dir:r,local_files_only:i,revision:l},u=new Map([["tokenizer",c.tokenizer],["model",c.model],["processor",c.processor]]),h=await async function(e,t,s){const o=Object.create(null),n=[];for(let[r,a]of e.entries()){if(!a)continue;let e;e=Array.isArray(a)?new Promise((async(e,o)=>{let n;for(let o of a){if(null===o)return void e(null);try{return void e(await o.from_pretrained(t,s))}catch(e){n=e}}o(n)})):a.from_pretrained(t,s),o[r]=e,n.push(e)}await Promise.all(n);for(let[e,t]of Object.entries(o))o[e]=await t;return o}(u,t,d);h.task=e,(0,a.dispatchCallback)(o,{status:"ready",task:e,model:t});return new(0,c.pipeline)(h)}},"./src/processors.js":
/*!***************************!*\
  !*** ./src/processors.js ***!
  \***************************/(e,t,s)=>{s.r(t),s.d(t,{ASTFeatureExtractor:()=>G,AutoProcessor:()=>Z,BeitFeatureExtractor:()=>E,BitImageProcessor:()=>M,CLIPFeatureExtractor:()=>T,ChineseCLIPFeatureExtractor:()=>k,ClapFeatureExtractor:()=>W,ConvNextFeatureExtractor:()=>x,ConvNextImageProcessor:()=>y,DPTFeatureExtractor:()=>f,DPTImageProcessor:()=>g,DeiTFeatureExtractor:()=>L,DetrFeatureExtractor:()=>I,DonutFeatureExtractor:()=>z,EfficientNetImageProcessor:()=>P,FeatureExtractor:()=>p,GLPNFeatureExtractor:()=>w,ImageFeatureExtractor:()=>_,MobileViTFeatureExtractor:()=>v,NougatImageProcessor:()=>B,OwlViTFeatureExtractor:()=>S,OwlViTProcessor:()=>J,Owlv2ImageProcessor:()=>A,Processor:()=>U,SamImageProcessor:()=>D,SamProcessor:()=>X,SeamlessM4TFeatureExtractor:()=>R,SegformerFeatureExtractor:()=>m,SiglipImageProcessor:()=>b,SpeechT5FeatureExtractor:()=>$,SpeechT5Processor:()=>Y,Swin2SRImageProcessor:()=>N,ViTFeatureExtractor:()=>F,ViTImageProcessor:()=>C,VitMatteImageProcessor:()=>V,Wav2Vec2FeatureExtractor:()=>j,Wav2Vec2ProcessorWithLM:()=>H,WhisperFeatureExtractor:()=>q,WhisperProcessor:()=>Q,YolosFeatureExtractor:()=>O});var o=s(/*! ./utils/core.js */"./src/utils/core.js"),n=s(/*! ./utils/hub.js */"./src/utils/hub.js"),r=s(/*! ./utils/maths.js */"./src/utils/maths.js"),a=s(/*! ./utils/tensor.js */"./src/utils/tensor.js"),i=(s(/*! ./utils/image.js */"./src/utils/image.js"),s(/*! ./utils/audio.js */"./src/utils/audio.js"));function l([e,t,s,o]){return[e-s/2,t-o/2,e+s/2,t+o/2]}function c(e,t=.5,s=null,o=!1){const n=e.logits,a=e.pred_boxes,[i,c,d]=n.dims;if(null!==s&&s.length!==i)throw Error("Make sure that you pass in as many target sizes as the batch dimension of the logits");let u=[];for(let e=0;e<i;++e){let i=null!==s?s[e]:null,h={boxes:[],classes:[],scores:[]},p=n[e],_=a[e];for(let e=0;e<c;++e){let s,n=p[e],a=[];if(o){s=n.sigmoid().data;for(let e=0;e<s.length;++e)s[e]>t&&a.push(e)}else{let e=(0,r.max)(n.data)[1];if(e===d-1)continue;a.push(e),s=(0,r.softmax)(n.data)}for(const t of a){let o=_[e].data;o=l(o),null!==i&&(o=o.map(((e,t)=>e*i[(t+1)%2]))),h.boxes.push(o),h.classes.push(t),h.scores.push(s[t])}}u.push(h)}return u}function d(e,t){if(!(e instanceof Float32Array||e instanceof Float64Array))throw new Error(`${t} expects input to be a Float32Array or a Float64Array, but got ${e?.constructor?.name??typeof e} instead. If using the feature extractor directly, remember to use \`read_audio(url, sampling_rate)\` to obtain the raw audio data of the file/url.`)}function u(e,t,s=0,o=null){const n=e/t;let a=(0,r.bankers_round)(n)*t;return null!==o&&a>o&&(a=Math.floor(n)*t),a<s&&(a=Math.ceil(n)*t),a}function h([e,t],s){return[Math.max(Math.floor(e/s),1)*s,Math.max(Math.floor(t/s),1)*s]}class p extends o.Callable{constructor(e){super(),this.config=e}}class _ extends p{constructor(e){super(e),this.image_mean=this.config.image_mean??this.config.mean,this.image_std=this.config.image_std??this.config.std,this.resample=this.config.resample??2,this.do_rescale=this.config.do_rescale??!0,this.rescale_factor=this.config.rescale_factor??1/255,this.do_normalize=this.config.do_normalize,this.do_resize=this.config.do_resize,this.do_thumbnail=this.config.do_thumbnail,this.size=this.config.size,this.size_divisibility=this.config.size_divisibility??this.config.size_divisor,this.do_center_crop=this.config.do_center_crop,this.crop_size=this.config.crop_size,this.do_convert_rgb=this.config.do_convert_rgb??!0,this.do_crop_margin=this.config.do_crop_margin,this.pad_size=this.config.pad_size,this.do_pad=this.config.do_pad,this.do_pad&&!this.pad_size&&this.size&&void 0!==this.size.width&&void 0!==this.size.height&&(this.pad_size=this.size)}async thumbnail(e,t,s=2){const o=e.height,n=e.width,r=t.height,a=t.width;let i=Math.min(o,r),l=Math.min(n,a);return i===o&&l===n?e:(o>n?l=Math.floor(n*i/o):n>o&&(i=Math.floor(o*l/n)),await e.resize(l,i,{resample:s}))}async crop_margin(e,t=200){const s=e.clone().grayscale(),o=(0,r.min)(s.data)[0],n=(0,r.max)(s.data)[0]-o;if(0===n)return e;const a=t/255;let i=s.width,l=s.height,c=0,d=0;for(let e=0;e<s.height;++e){const t=e*s.width;for(let r=0;r<s.width;++r)(s.data[t+r]-o)/n<a&&(i=Math.min(i,r),l=Math.min(l,e),c=Math.max(c,r),d=Math.max(d,e))}return e=await e.crop([i,l,c,d])}pad_image(e,t,s,{mode:n="constant",center:r=!1,constant_values:a=0}={}){const[i,l,c]=t;let d,u;if("number"==typeof s?(d=s,u=s):(d=s.width,u=s.height),d!==l||u!==i){const s=new Float32Array(d*u*c);if(Array.isArray(a))for(let e=0;e<s.length;++e)s[e]=a[e%c];else 0!==a&&s.fill(a);const[h,p]=r?[Math.floor((d-l)/2),Math.floor((u-i)/2)]:[0,0];for(let t=0;t<i;++t){const o=(t+p)*d,n=t*l;for(let t=0;t<l;++t){const r=(o+t+h)*c,a=(n+t)*c;for(let t=0;t<c;++t)s[r+t]=e[a+t]}}if("symmetric"===n){if(r)throw new Error("`center` padding is not supported when `mode` is set to `symmetric`.");const t=i-1,n=l-1;for(let r=0;r<u;++r){const a=r*d,u=(0,o.calculateReflectOffset)(r,t)*l;for(let t=0;t<d;++t){if(r<i&&t<l)continue;const d=(a+t)*c,h=(u+(0,o.calculateReflectOffset)(t,n))*c;for(let t=0;t<c;++t)s[d+t]=e[h+t]}}}e=s,t=[u,d,c]}return[e,t]}rescale(e){for(let t=0;t<e.length;++t)e[t]=this.rescale_factor*e[t]}get_resize_output_image_size(e,t){const[s,o]=e.size;let n,r;if(this.do_thumbnail){const{height:e,width:s}=t;n=Math.min(e,s)}else Number.isInteger(t)?(n=t,r=this.config.max_size??n):void 0!==t&&(n=t.shortest_edge,r=t.longest_edge);if(void 0!==n||void 0!==r){const e=void 0===n?1:Math.max(n/s,n/o),t=s*e,a=o*e,i=void 0===r?1:Math.min(r/t,r/a);let l=Math.floor(Number((t*i).toFixed(2))),c=Math.floor(Number((a*i).toFixed(2)));return void 0!==this.size_divisibility&&([l,c]=h([l,c],this.size_divisibility)),[l,c]}if(void 0!==t&&void 0!==t.width&&void 0!==t.height){let e=t.width,n=t.height;if(this.config.keep_aspect_ratio&&this.config.ensure_multiple_of){let t=n/o,r=e/s;Math.abs(1-r)<Math.abs(1-t)?t=r:r=t,n=u(t*o,this.config.ensure_multiple_of),e=u(r*s,this.config.ensure_multiple_of)}return[e,n]}if(void 0!==this.size_divisibility)return h([s,o],this.size_divisibility);throw new Error(`Could not resize image due to unsupported \`this.size\` option in config: ${JSON.stringify(t)}`)}async resize(e){const[t,s]=this.get_resize_output_image_size(e,this.size);return await e.resize(t,s,{resample:this.resample})}async preprocess(e,{do_normalize:t=null,do_pad:s=null,do_convert_rgb:o=null,do_convert_grayscale:n=null}={}){this.do_crop_margin&&(e=await this.crop_margin(e));const[r,i]=e.size;if(o??this.do_convert_rgb?e=e.rgb():n&&(e=e.grayscale()),this.do_resize&&(e=await this.resize(e)),this.do_thumbnail&&(e=await this.thumbnail(e,this.size,this.resample)),this.do_center_crop){let t,s;Number.isInteger(this.crop_size)?(t=this.crop_size,s=this.crop_size):(t=this.crop_size.width,s=this.crop_size.height),e=await e.center_crop(t,s)}const l=[e.height,e.width];let c=Float32Array.from(e.data),d=[e.height,e.width,e.channels];if(this.do_rescale&&this.rescale(c),t??this.do_normalize){let t=this.image_mean;Array.isArray(this.image_mean)||(t=new Array(e.channels).fill(t));let s=this.image_std;if(Array.isArray(this.image_std)||(s=new Array(e.channels).fill(t)),t.length!==e.channels||s.length!==e.channels)throw new Error(`When set to arrays, the length of \`image_mean\` (${t.length}) and \`image_std\` (${s.length}) must match the number of channels in the image (${e.channels}).`);for(let o=0;o<c.length;o+=e.channels)for(let n=0;n<e.channels;++n)c[o+n]=(c[o+n]-t[n])/s[n]}if(s??this.do_pad)if(this.pad_size){const t=this.pad_image(c,[e.height,e.width,e.channels],this.pad_size);[c,d]=t}else if(this.size_divisibility){const[e,t]=h([d[1],d[0]],this.size_divisibility);[c,d]=this.pad_image(c,d,{width:e,height:t})}return{original_size:[i,r],reshaped_input_size:l,pixel_values:new a.Tensor("float32",c,d).permute(2,0,1)}}async _call(e,...t){Array.isArray(e)||(e=[e]);const s=await Promise.all(e.map((e=>this.preprocess(e))));return{pixel_values:(0,a.stack)(s.map((e=>e.pixel_values)),0),original_sizes:s.map((e=>e.original_size)),reshaped_input_sizes:s.map((e=>e.reshaped_input_size))}}}class m extends _{post_process_semantic_segmentation(e,t=null){const s=e.logits,o=s.dims[0];if(null!==t&&t.length!==o)throw Error("Make sure that you pass in as many target sizes as the batch dimension of the logits");const n=[];for(let e=0;e<o;++e){const o=null!==t?t[e]:null;let r=s[e];null!==o&&(r=(0,a.interpolate)(r,o,"bilinear",!1));const[i,l]=o??r.dims.slice(-2),c=new a.Tensor("int32",new Int32Array(i*l),[i,l]),d=r[0].data;for(let e=1;e<r.dims[0];++e){const t=r[e].data;for(let s=0;s<t.length;++s)t[s]>d[s]&&(d[s]=t[s],c.data[s]=e)}const u=new Array(r.dims[0]),h=c.data;for(let e=0;e<h.length;++e){const t=h[e];u[t]=t}const p=u.filter((e=>void 0!==e));n.push({segmentation:c,labels:p})}return n}}class f extends _{}class g extends f{}class M extends _{}class w extends _{}class T extends _{}class k extends _{}class b extends _{}class x extends _{constructor(e){super(e),this.crop_pct=this.config.crop_pct??.875}async resize(e){const t=this.size?.shortest_edge;if(void 0===t)throw new Error("Size dictionary must contain 'shortest_edge' key.");if(t<384){const s=Math.floor(t/this.crop_pct),[o,n]=this.get_resize_output_image_size(e,{shortest_edge:s});e=await e.resize(o,n,{resample:this.resample}),e=await e.center_crop(t,t)}else e=await e.resize(t,t,{resample:this.resample});return e}}class y extends x{}class F extends _{}class C extends _{}class P extends _{constructor(e){super(e),this.include_top=this.config.include_top??!0,this.include_top&&(this.image_std=this.image_std.map((e=>e*e)))}}class v extends _{}class S extends _{post_process_object_detection(...e){return c(...e)}}class A extends S{}class L extends _{}class E extends _{}class z extends _{pad_image(e,t,s,o={}){const[n,r,a]=t;let i=this.image_mean;Array.isArray(this.image_mean)||(i=new Array(a).fill(i));let l=this.image_std;Array.isArray(l)||(l=new Array(a).fill(i));const c=i.map(((e,t)=>-e/l[t]));return super.pad_image(e,t,s,{center:!0,constant_values:c,...o})}}class B extends z{}class I extends _{async _call(e){const t=await super._call(e),s=[t.pixel_values.dims[0],64,64],o=new a.Tensor("int64",new BigInt64Array(s.reduce(((e,t)=>e*t))).fill(1n),s);return{...t,pixel_mask:o}}post_process_object_detection(...e){return c(...e)}remove_low_and_no_objects(e,t,s,o){let n=[],a=[],i=[];for(let l=0;l<e.dims[0];++l){let c=e[l],d=t[l],u=(0,r.max)(c.data)[1];if(u===o)continue;let h=(0,r.softmax)(c.data)[u];h>s&&(n.push(d),a.push(h),i.push(u))}return[n,a,i]}check_segment_validity(e,t,s,o=.5,n=.8){let r=[],a=0,i=0;for(let n=0;n<e.length;++n)e[n]===s&&(r.push(n),++a),t[s].data[n]>=o&&++i;let l=a>0&&i>0;if(l){l=a/i>n}return[l,r]}compute_segments(e,t,s,o,n,r=null,i=null){let[l,c]=i??e[0].dims,d=new a.Tensor("int32",new Int32Array(l*c),[l,c]),u=[];if(null!==i)for(let t=0;t<e.length;++t)e[t]=(0,a.interpolate)(e[t],i,"bilinear",!1);let h=new Int32Array(e[0].data.length),p=new Float32Array(e[0].data.length);for(let s=0;s<e.length;++s){let o=t[s];for(let t=0;t<e[s].data.length;++t)e[s].data[t]*=o,e[s].data[t]>p[t]&&(h[t]=s,p[t]=e[s].data[t])}let _=0;for(let r=0;r<s.length;++r){let a=s[r],[i,l]=this.check_segment_validity(h,e,r,o,n);if(i){++_;for(let e of l)d.data[e]=_;u.push({id:_,label_id:a,score:t[r]})}}return[d,u]}post_process_panoptic_segmentation(e,t=.5,s=.5,o=.8,n=null,r=null){null===n&&(console.warn("`label_ids_to_fuse` unset. No instance will be fused."),n=new Set);const i=e.logits,l=e.pred_masks.sigmoid();let[c,d,u]=i.dims;if(u-=1,null!==r&&r.length!==c)throw Error("Make sure that you pass in as many target sizes as the batch dimension of the logits");let h=[];for(let e=0;e<c;++e){let c=null!==r?r[e]:null,d=i[e],p=l[e],[_,m,f]=this.remove_low_and_no_objects(d,p,t,u);if(0===f.length){let[e,t]=c??p.dims.slice(-2),s=new a.Tensor("int32",new Int32Array(e*t).fill(-1),[e,t]);h.push({segmentation:s,segments_info:[]});continue}let[g,M]=this.compute_segments(_,m,f,s,o,n,c);h.push({segmentation:g,segments_info:M})}return h}post_process_instance_segmentation(){throw Error("Not implemented yet")}}class O extends _{post_process_object_detection(...e){return c(...e)}}class D extends _{reshape_input_points(e,t,s){e=structuredClone(e);let n=(0,o.calculateDimensions)(e);if(3===n.length)n=[1,...n],e=[e];else if(4!==n.length)throw Error("The input_points must be a 4D tensor of shape `batch_size`, `point_batch_size`, `nb_points_per_image`, `2`.");for(let o=0;o<e.length;++o){let n=t[o],r=s[o],a=[r[0]/n[0],r[1]/n[1]];for(let t=0;t<e[o].length;++t)for(let s=0;s<e[o][t].length;++s)for(let n=0;n<e[o][t][s].length;++n)e[o][t][s][n]*=a[n]}return new a.Tensor("float32",Float32Array.from(e.flat(1/0)),n)}add_input_labels(e,t){let s=(0,o.calculateDimensions)(e);if(2===s.length)s=[1,...s],e=[e];else if(3!==s.length)throw Error("The input_points must be a 4D tensor of shape `batch_size`, `point_batch_size`, `nb_points_per_image`, `2`.");if(s.some(((e,s)=>e!==t.dims[s])))throw Error(`The first ${s.length} dimensions of 'input_points' and 'input_labels' must be the same.`);return new a.Tensor("int64",e.flat(1/0).map(BigInt),s)}async _call(e,t=null,s=null){const o=await super._call(e);if(t&&(o.input_points=this.reshape_input_points(t,o.original_sizes,o.reshaped_input_sizes)),s){if(!o.input_points)throw Error("`input_points` must be provided if `input_labels` are provided.");o.input_labels=this.add_input_labels(s,o.input_points)}return o}post_process_masks(e,t,s,{mask_threshold:o=0,binarize:n=!0,pad_size:r=null}={}){const i=[],l=[(r=r??this.pad_size).height,r.width];for(let r=0;r<t.length;++r){const c=t[r],d=s[r],u=e[r],h=[];for(let e=0;e<u.dims[0];++e){const t=u[e];let s=(0,a.interpolate)(t,l,"bilinear",!1);if(s=s.slice(null,[0,d[0]],[0,d[1]]),s=(0,a.interpolate)(s,c,"bilinear",!1),n){const e=new Uint8Array(s.data.length);for(let t=0;t<s.data.length;++t)s.data[t]>o&&(e[t]=1);s=new a.Tensor("bool",e,s.dims)}h.push(s)}i.push((0,a.stack)(h))}return i}}class N extends _{pad_image(e,t,s,o={}){const[n,r,a]=t;return super.pad_image(e,t,{width:r+(s-r%s)%s,height:n+(s-n%s)%s},{mode:"symmetric",center:!1,constant_values:-1,...o})}}class V extends _{async _call(e,t){Array.isArray(e)||(e=[e]),Array.isArray(t)||(t=[t]);const s=await Promise.all(e.map((e=>this.preprocess(e)))),o=await Promise.all(t.map((e=>this.preprocess(e,{do_normalize:!1,do_convert_rgb:!1,do_convert_grayscale:!0}))));return{pixel_values:(0,a.stack)(s.map(((e,t)=>(0,a.cat)([e.pixel_values,o[t].pixel_values],0))),0),original_sizes:s.map((e=>e.original_size)),reshaped_input_sizes:s.map((e=>e.reshaped_input_size))}}}class q extends p{constructor(e){super(e),this.config.mel_filters??=(0,i.mel_filter_bank)(Math.floor(1+this.config.n_fft/2),this.config.feature_size,0,8e3,this.config.sampling_rate,"slaney","slaney"),this.window=(0,i.window_function)(this.config.n_fft,"hann")}_extract_fbank_features(e){const{data:t,dims:s}=(0,i.spectrogram)(e,this.window,this.config.n_fft,this.config.hop_length,{power:2,mel_filters:this.config.mel_filters,log_mel:"log10",max_num_frames:this.config.nb_max_frames}),o=(0,r.max)(t)[0];for(let e=0;e<t.length;++e)t[e]=(Math.max(t[e],o-8)+4)/4;return{data:t,dims:s}}async _call(e){let t;d(e,"WhisperFeatureExtractor"),e.length>this.config.n_samples?(console.warn("Attempting to extract features for audio longer than 30 seconds. If using a pipeline to extract transcript from a long audio clip, remember to specify `chunk_length_s` and/or `stride_length_s`."),t=e.slice(0,this.config.n_samples)):(t=new Float32Array(this.config.n_samples),t.set(e));const{data:s,dims:o}=this._extract_fbank_features(t);return{input_features:new a.Tensor("float32",s,[1,...o])}}}class j extends p{_zero_mean_unit_var_norm(e){const t=e.reduce(((e,t)=>e+t),0)/e.length,s=e.reduce(((e,s)=>e+(s-t)**2),0)/e.length;return e.map((e=>(e-t)/Math.sqrt(s+1e-7)))}async _call(e){d(e,"Wav2Vec2FeatureExtractor"),e instanceof Float64Array&&(e=new Float32Array(e));let t=e;this.config.do_normalize&&(t=this._zero_mean_unit_var_norm(t));const s=[1,t.length];return{input_values:new a.Tensor("float32",t,s),attention_mask:new a.Tensor("int64",new BigInt64Array(t.length).fill(1n),s)}}}class R extends p{constructor(e){super(e);const t=this.config.sampling_rate,s=(0,i.mel_filter_bank)(256,this.config.num_mel_bins,20,Math.floor(t/2),t,null,"kaldi",!0);for(let e=0;e<s.length;++e)s[e].push(0);this.mel_filters=s,this.window=(0,i.window_function)(400,"povey",{periodic:!1})}_extract_fbank_features(e,t){return e=e.map((e=>32768*e)),(0,i.spectrogram)(e,this.window,400,160,{fft_length:512,power:2,center:!1,preemphasis:.97,mel_filters:this.mel_filters,log_mel:"log",mel_floor:1.192092955078125e-7,remove_dc_offset:!0,max_num_frames:t,transpose:!0})}async _call(e,{padding:t=!0,pad_to_multiple_of:s=2,do_normalize_per_mel_bins:o=!0,return_attention_mask:n=!0}={}){d(e,"SeamlessM4TFeatureExtractor");let r,i=this._extract_fbank_features(e,this.config.max_length);if(o){const[e,t]=i.dims;for(let s=0;s<t;++s){let o=0;for(let n=0;n<e;++n)o+=i.data[n*t+s];const n=o/e;let r=0;for(let o=0;o<e;++o)r+=(i.data[o*t+s]-n)**2;r/=e-1;const a=Math.sqrt(r+1e-7);for(let o=0;o<e;++o){const e=o*t+s;i.data[e]=(i.data[e]-n)/a}}}if(t){const[e,t]=i.dims,o=e%s;if(o>0){const s=new Float32Array(t*(e+o));s.set(i.data),s.fill(this.config.padding_value,i.data.length);const l=e+o;i={data:s,dims:[l,t]},n&&(r=new a.Tensor("int64",new BigInt64Array(l),[1,l]),r.data.fill(1n,0,e))}}const[l,c]=i.dims,u=this.config.stride;if(0!==l%u)throw new Error(`The number of frames (${l}) must be a multiple of the stride (${u}).`);const h=new a.Tensor("float32",i.data,i.dims).view(1,Math.floor(l/u),c*u),p={input_features:h};if(n){const e=h.dims[1],t=new a.Tensor("int64",new BigInt64Array(e),[1,e]);if(r)for(let e=1,s=0;e<l;e+=u,++s)t.data[s]=r.data[e];else t.data.fill(1n);p.attention_mask=t}return p}}class G extends p{constructor(e){super(e);const t=this.config.sampling_rate,s=(0,i.mel_filter_bank)(256,this.config.num_mel_bins,20,Math.floor(t/2),t,null,"kaldi",!0);for(let e=0;e<s.length;++e)s[e].push(0);this.mel_filters=s,this.window=(0,i.window_function)(400,"hann",{periodic:!1}),this.mean=this.config.mean,this.std=this.config.std}_extract_fbank_features(e,t){return(0,i.spectrogram)(e,this.window,400,160,{fft_length:512,power:2,center:!1,preemphasis:.97,mel_filters:this.mel_filters,log_mel:"log",mel_floor:1.192092955078125e-7,remove_dc_offset:!0,max_num_frames:t,transpose:!0})}async _call(e){d(e,"ASTFeatureExtractor");const t=this._extract_fbank_features(e,this.config.max_length);if(this.config.do_normalize){const e=2*this.std;for(let s=0;s<t.data.length;++s)t.data[s]=(t.data[s]-this.mean)/e}return{input_values:new a.Tensor("float32",t.data,[1,...t.dims])}}}class W extends p{constructor(e){super(e),this.mel_filters=(0,i.mel_filter_bank)(this.config.nb_frequency_bins,this.config.feature_size,this.config.frequency_min,this.config.frequency_max,this.config.sampling_rate,null,"htk"),this.mel_filters_slaney=(0,i.mel_filter_bank)(this.config.nb_frequency_bins,this.config.feature_size,this.config.frequency_min,this.config.frequency_max,this.config.sampling_rate,"slaney","slaney"),this.window=(0,i.window_function)(this.config.fft_window_size,"hann")}_get_input_mel(e,t,s,o){let n,r=!1;const a=e.length-t;if(a>0){if("rand_trunc"!==s)throw new Error(`Truncation strategy "${s}" not implemented`);{r=!0;const s=Math.floor(Math.random()*(a+1));e=e.subarray(s,s+t),n=this._extract_fbank_features(e,this.mel_filters_slaney,this.config.nb_max_samples),n.dims=[1,...n.dims]}}else{if(a<0){let s=new Float64Array(t);if(s.set(e),"repeat"===o)for(let o=e.length;o<t;o+=e.length)s.set(e.subarray(0,Math.min(e.length,t-o)),o);else if("repeatpad"===o)for(let t=e.length;t<-a;t+=e.length)s.set(e,t);e=s}if("fusion"===s)throw new Error(`Truncation strategy "${s}" not implemented`);n=this._extract_fbank_features(e,this.mel_filters_slaney,this.config.nb_max_samples),n.dims=[1,...n.dims]}return{...n,longer:r}}_extract_fbank_features(e,t,s=null){return(0,i.spectrogram)(e,this.window,this.config.fft_window_size,this.config.hop_length,{power:2,mel_filters:t,log_mel:"dB",max_num_frames:s,do_pad:!1,transpose:!0})}async _call(e,{max_length:t=null}={}){d(e,"ClapFeatureExtractor");const s=this._get_input_mel(e,t??this.config.nb_max_samples,this.config.truncation,this.config.padding);return{input_features:new a.Tensor("float32",s.data,[1,...s.dims])}}}class $ extends p{}class U extends o.Callable{constructor(e){super(),this.feature_extractor=e}async _call(e,...t){return await this.feature_extractor(e,...t)}}class X extends U{async _call(...e){return await this.feature_extractor(...e)}post_process_masks(...e){return this.feature_extractor.post_process_masks(...e)}reshape_input_points(...e){return this.feature_extractor.reshape_input_points(...e)}}class Q extends U{async _call(e){return await this.feature_extractor(e)}}class H extends U{async _call(e){return await this.feature_extractor(e)}}class Y extends U{async _call(e){return await this.feature_extractor(e)}}class J extends U{}class Z{static FEATURE_EXTRACTOR_CLASS_MAPPING={ImageFeatureExtractor:_,WhisperFeatureExtractor:q,ViTFeatureExtractor:F,MobileViTFeatureExtractor:v,OwlViTFeatureExtractor:S,Owlv2ImageProcessor:A,CLIPFeatureExtractor:T,ChineseCLIPFeatureExtractor:k,SiglipImageProcessor:b,ConvNextFeatureExtractor:x,ConvNextImageProcessor:y,SegformerFeatureExtractor:m,BitImageProcessor:M,DPTImageProcessor:g,DPTFeatureExtractor:f,GLPNFeatureExtractor:w,BeitFeatureExtractor:E,DeiTFeatureExtractor:L,DetrFeatureExtractor:I,YolosFeatureExtractor:O,DonutFeatureExtractor:z,NougatImageProcessor:B,EfficientNetImageProcessor:P,ViTImageProcessor:C,VitMatteImageProcessor:V,SamImageProcessor:D,Swin2SRImageProcessor:N,Wav2Vec2FeatureExtractor:j,SeamlessM4TFeatureExtractor:R,SpeechT5FeatureExtractor:$,ASTFeatureExtractor:G,ClapFeatureExtractor:W};static PROCESSOR_CLASS_MAPPING={WhisperProcessor:Q,Wav2Vec2ProcessorWithLM:H,SamProcessor:X,SpeechT5Processor:Y,OwlViTProcessor:J};static async from_pretrained(e,{progress_callback:t=null,config:s=null,cache_dir:o=null,local_files_only:r=!1,revision:a="main"}={}){let i=s??await(0,n.getModelJSON)(e,"preprocessor_config.json",!0,{progress_callback:t,config:s,cache_dir:o,local_files_only:r,revision:a}),l=i.feature_extractor_type??i.image_processor_type,c=this.FEATURE_EXTRACTOR_CLASS_MAPPING[l];if(!c){if(void 0===i.size)throw new Error(`Unknown Feature Extractor type: ${l}`);console.warn(`Feature extractor type "${l}" not found, assuming ImageFeatureExtractor due to size parameter in config.`),c=_}return new(this.PROCESSOR_CLASS_MAPPING[i.processor_class]??U)(new c(i))}}},"./src/tokenizers.js":
/*!***************************!*\
  !*** ./src/tokenizers.js ***!
  \***************************/(e,t,s)=>{s.r(t),s.d(t,{AlbertTokenizer:()=>fe,AutoTokenizer:()=>dt,BartTokenizer:()=>Ae,BertTokenizer:()=>me,BlenderbotSmallTokenizer:()=>rt,BlenderbotTokenizer:()=>nt,BloomTokenizer:()=>Be,CLIPTokenizer:()=>et,CamembertTokenizer:()=>Fe,CodeGenTokenizer:()=>Ke,CodeLlamaTokenizer:()=>De,CohereTokenizer:()=>ct,ConvBertTokenizer:()=>be,DebertaTokenizer:()=>we,DebertaV2Tokenizer:()=>Te,DistilBertTokenizer:()=>ye,ElectraTokenizer:()=>Pe,EsmTokenizer:()=>Re,FalconTokenizer:()=>qe,GPT2Tokenizer:()=>Se,GPTNeoXTokenizer:()=>je,GemmaTokenizer:()=>We,Grok1Tokenizer:()=>$e,HerbertTokenizer:()=>ke,LlamaTokenizer:()=>Oe,M2M100Tokenizer:()=>Qe,MBart50Tokenizer:()=>Ee,MBartTokenizer:()=>Le,MPNetTokenizer:()=>Ve,MarianTokenizer:()=>st,MobileBertTokenizer:()=>ge,NllbTokenizer:()=>Xe,NougatTokenizer:()=>it,PreTrainedTokenizer:()=>_e,Qwen2Tokenizer:()=>Ge,RoFormerTokenizer:()=>xe,RobertaTokenizer:()=>ze,SiglipTokenizer:()=>tt,SpeechT5Tokenizer:()=>at,SqueezeBertTokenizer:()=>Me,T5Tokenizer:()=>ve,TokenizerModel:()=>M,VitsTokenizer:()=>lt,Wav2Vec2CTCTokenizer:()=>ot,WhisperTokenizer:()=>Ze,XLMRobertaTokenizer:()=>Ne,XLMTokenizer:()=>Ce});var o=s(/*! ./utils/core.js */"./src/utils/core.js"),n=s(/*! ./utils/hub.js */"./src/utils/hub.js"),r=s(/*! ./utils/maths.js */"./src/utils/maths.js"),a=s(/*! ./utils/tensor.js */"./src/utils/tensor.js"),i=s(/*! ./utils/data-structures.js */"./src/utils/data-structures.js"),l=s(/*! @huggingface/jinja */"./node_modules/@huggingface/jinja/dist/index.js");async function c(e,t){const s=await Promise.all([(0,n.getModelJSON)(e,"tokenizer.json",!0,t),(0,n.getModelJSON)(e,"tokenizer_config.json",!0,t)]);return null!==t.legacy&&(s[1].legacy=t.legacy),s}function d(e,t=!0){if(void 0!==e.Regex){let t=e.Regex.replace(/\\([#&~])/g,"$1");for(const[e,s]of f)t=t.replaceAll(e,s);return new RegExp(t,"gu")}if(void 0!==e.String){const s=(0,o.escapeRegExp)(e.String);return new RegExp(t?s:`(${s})`,"gu")}return console.warn("Unknown pattern type:",e),null}function u(e){return new Map(Object.entries(e))}function h(e){const t=e.dims;switch(t.length){case 1:return e.tolist();case 2:if(1!==t[0])throw new Error("Unable to decode tensor with `batch size !== 1`. Use `tokenizer.batch_decode(...)` for batched inputs.");return e.tolist()[0];default:throw new Error(`Expected tensor to have 1-2 dimensions, got ${t.length}.`)}}function p(e){return e.replace(/ \./g,".").replace(/ \?/g,"?").replace(/ \!/g,"!").replace(/ ,/g,",").replace(/ \' /g,"'").replace(/ n\'t/g,"n't").replace(/ \'m/g,"'m").replace(/ \'s/g,"'s").replace(/ \'ve/g,"'ve").replace(/ \'re/g,"'re")}function _(e){return e.replace(/[\u0300-\u036f]/g,"")}const m="\\p{P}\\u0021-\\u002F\\u003A-\\u0040\\u005B-\\u0060\\u007B-\\u007E",f=new Map([["(?i:'s|'t|'re|'ve|'m|'ll|'d)","(?:'([sS]|[tT]|[rR][eE]|[vV][eE]|[mM]|[lL][lL]|[dD]))"]]);class g{constructor(e){this.content=e.content,this.id=e.id,this.single_word=e.single_word??!1,this.lstrip=e.lstrip??!1,this.rstrip=e.rstrip??!1,this.special=e.special??!1,this.normalized=e.normalized??null}}class M extends o.Callable{constructor(e){super(),this.config=e,this.vocab=[],this.tokens_to_ids=new Map,this.unk_token_id=void 0,this.unk_token=void 0,this.end_of_word_suffix=void 0,this.fuse_unk=this.config.fuse_unk??!1}static fromConfig(e,...t){switch(e.type){case"WordPiece":return new w(e);case"Unigram":return new T(e,...t);case"BPE":return new x(e);default:if(e.vocab)return new y(e,...t);throw new Error(`Unknown TokenizerModel type: ${e.type}`)}}_call(e){let t=this.encode(e);return this.fuse_unk&&(t=function(e,t,s){const o=[];let n=0;for(;n<e.length;)if(o.push(e[n]),(s.get(e[n])??t)===t)for(;n<e.length&&(s.get(e[n])??t)===t;)++n;else++n;return o}(t,this.unk_token_id,this.tokens_to_ids)),t}encode(e){throw Error("encode should be implemented in subclass.")}convert_tokens_to_ids(e){return e.map((e=>this.tokens_to_ids.get(e)??this.unk_token_id))}convert_ids_to_tokens(e){return e.map((e=>this.vocab[e]??this.unk_token))}}class w extends M{constructor(e){super(e),this.tokens_to_ids=u(e.vocab),this.unk_token_id=this.tokens_to_ids.get(e.unk_token),this.unk_token=e.unk_token,this.max_input_chars_per_word=e.max_input_chars_per_word??100,this.vocab=new Array(this.tokens_to_ids.size);for(const[e,t]of this.tokens_to_ids)this.vocab[t]=e}encode(e){const t=[];for(const s of e){const e=[...s];if(e.length>this.max_input_chars_per_word){t.push(this.unk_token);continue}let o=!1,n=0;const r=[];for(;n<e.length;){let t=e.length,s=null;for(;n<t;){let o=e.slice(n,t).join("");if(n>0&&(o=this.config.continuing_subword_prefix+o),this.tokens_to_ids.has(o)){s=o;break}--t}if(null===s){o=!0;break}r.push(s),n=t}o?t.push(this.unk_token):t.push(...r)}return t}}class T extends M{constructor(e,t){super(e);const s=e.vocab.length;this.vocab=new Array(s),this.scores=new Array(s);for(let t=0;t<s;++t){const s=e.vocab[t];this.vocab[t]=s[0],this.scores[t]=s[1]}this.unk_token_id=e.unk_id,this.unk_token=this.vocab[e.unk_id],this.tokens_to_ids=new Map(this.vocab.map(((e,t)=>[e,t]))),this.bosToken=" ",this.bosTokenId=this.tokens_to_ids.get(this.bosToken),this.eosToken=t.eos_token,this.eosTokenId=this.tokens_to_ids.get(this.eosToken),this.unkToken=this.vocab[this.unk_token_id],this.minScore=(0,r.min)(this.scores)[0],this.unkScore=this.minScore-10,this.scores[this.unk_token_id]=this.unkScore,this.trie=new i.CharTrie,this.trie.extend(this.vocab),this.fuse_unk=!0}populateNodes(e){const t=e.sentence,s=t.length;let o=0;for(;o<s;){const s=1;let n=!1;const r=[];for(let a of this.trie.commonPrefixSearch(t.slice(o))){r.push(a);const t=this.tokens_to_ids.get(a),i=this.scores[t],l=a.length;e.insert(o,l,i,t),n||l!==s||(n=!0)}n||e.insert(o,s,this.unkScore,this.unk_token_id),o+=s}}tokenize(e){const t=new i.TokenLattice(e,this.bosTokenId,this.eosTokenId);return this.populateNodes(t),t.tokens()}encode(e){const t=[];for(const s of e){const e=this.tokenize(s);t.push(...e)}return t}}const k=(()=>{const e=[...Array.from({length:"~".charCodeAt(0)-"!".charCodeAt(0)+1},((e,t)=>t+"!".charCodeAt(0))),...Array.from({length:"¬".charCodeAt(0)-"¡".charCodeAt(0)+1},((e,t)=>t+"¡".charCodeAt(0))),...Array.from({length:"ÿ".charCodeAt(0)-"®".charCodeAt(0)+1},((e,t)=>t+"®".charCodeAt(0)))],t=e.slice();let s=0;for(let o=0;o<256;++o)e.includes(o)||(e.push(o),t.push(256+s),s+=1);const o=t.map((e=>String.fromCharCode(e)));return Object.fromEntries(e.map(((e,t)=>[e,o[t]])))})(),b=(0,o.reverseDictionary)(k);class x extends M{constructor(e){super(e),this.BPE_SPLIT_TOKEN=" ",this.tokens_to_ids=u(e.vocab),this.unk_token_id=this.tokens_to_ids.get(e.unk_token),this.unk_token=e.unk_token,this.vocab=new Array(this.tokens_to_ids.size);for(const[e,t]of this.tokens_to_ids)this.vocab[t]=e;this.bpe_ranks=new Map(e.merges.map(((e,t)=>[e,t]))),this.merges=e.merges.map((e=>e.split(this.BPE_SPLIT_TOKEN))),this.end_of_word_suffix=e.end_of_word_suffix,this.continuing_subword_suffix=e.continuing_subword_suffix??null,this.byte_fallback=this.config.byte_fallback??!1,this.byte_fallback&&(this.text_encoder=new TextEncoder),this.cache=new Map}bpe(e){if(0===e.length)return[];const t=this.cache.get(e);if(void 0!==t)return t;const s=Array.from(e);this.end_of_word_suffix&&(s[s.length-1]+=this.end_of_word_suffix);let o=[];if(s.length>1){const e=new i.PriorityQueue(((e,t)=>e.score<t.score));let t={token:s[0],bias:0,prev:null,next:null},n=t;for(let t=1;t<s.length;++t){const o={bias:t/s.length,token:s[t],prev:n,next:null};n.next=o,this._add_node(e,n),n=o}for(;!e.isEmpty();){const s=e.pop();if(s.deleted||!s.next||s.next.deleted)continue;if(s.deleted=!0,s.next.deleted=!0,s.prev){const e={...s.prev};s.prev.deleted=!0,s.prev=e,e.prev?e.prev.next=e:t=e}const o={token:s.token+s.next.token,bias:s.bias,prev:s.prev,next:s.next.next};o.prev?(o.prev.next=o,this._add_node(e,o.prev)):t=o,o.next&&(o.next.prev=o,this._add_node(e,o))}for(let e=t;null!==e;e=e.next)o.push(e.token)}else o=s;if(this.continuing_subword_suffix)for(let e=0;e<o.length-1;++e)o[e]+=this.continuing_subword_suffix;return this.cache.set(e,o),o}_add_node(e,t){const s=this.bpe_ranks.get(t.token+this.BPE_SPLIT_TOKEN+t.next.token);void 0!==s&&(t.score=s+t.bias,e.push(t))}encode(e){const t=[];for(const s of e){const e=this.bpe(s);for(const s of e)this.tokens_to_ids.has(s)?t.push(s):this.byte_fallback?t.push(...Array.from(this.text_encoder.encode(s)).map((e=>`<0x${e.toString(16).toUpperCase().padStart(2,"0")}>`))):t.push(this.unk_token)}return t}}class y extends M{constructor(e,t){super(e),this.tokens_to_ids=u(t.target_lang?e.vocab[t.target_lang]:e.vocab),this.bos_token=t.bos_token,this.bos_token_id=this.tokens_to_ids.get(this.bos_token),this.eos_token=t.eos_token,this.eos_token_id=this.tokens_to_ids.get(this.eos_token),this.pad_token=t.pad_token,this.pad_token_id=this.tokens_to_ids.get(this.pad_token),this.unk_token=t.unk_token,this.unk_token_id=this.tokens_to_ids.get(this.unk_token),this.vocab=new Array(this.tokens_to_ids.size);for(const[e,t]of this.tokens_to_ids)this.vocab[t]=e}encode(e){return e}}class F extends o.Callable{constructor(e){super(),this.config=e}static fromConfig(e){if(null===e)return null;switch(e.type){case"BertNormalizer":return new I(e);case"Precompiled":return new ae(e);case"Sequence":return new B(e);case"Replace":return new C(e);case"NFC":return new P(e);case"NFKC":return new v(e);case"NFKD":return new S(e);case"Strip":return new A(e);case"StripAccents":return new L(e);case"Lowercase":return new E(e);case"Prepend":return new z(e);default:throw new Error(`Unknown Normalizer type: ${e.type}`)}}normalize(e){throw Error("normalize should be implemented in subclass.")}_call(e){return this.normalize(e)}}class C extends F{normalize(e){const t=d(this.config.pattern);return null===t?e:e.replaceAll(t,this.config.content)}}class P extends F{normalize(e){return e=e.normalize("NFC")}}class v extends F{normalize(e){return e=e.normalize("NFKC")}}class S extends F{normalize(e){return e=e.normalize("NFKD")}}class A extends F{normalize(e){return this.config.strip_left&&this.config.strip_right?e=e.trim():(this.config.strip_left&&(e=e.trimStart()),this.config.strip_right&&(e=e.trimEnd())),e}}class L extends F{normalize(e){return e=_(e)}}class E extends F{normalize(e){return e=e.toLowerCase()}}class z extends F{normalize(e){return e=this.config.prepend+e}}class B extends F{constructor(e){super(e),this.normalizers=e.normalizers.map((e=>F.fromConfig(e)))}normalize(e){return this.normalizers.reduce(((e,t)=>t.normalize(e)),e)}}class I extends F{_tokenize_chinese_chars(e){const t=[];for(let s=0;s<e.length;++s){const o=e[s],n=o.charCodeAt(0);this._is_chinese_char(n)?(t.push(" "),t.push(o),t.push(" ")):t.push(o)}return t.join("")}_is_chinese_char(e){return e>=19968&&e<=40959||e>=13312&&e<=19903||e>=131072&&e<=173791||e>=173824&&e<=177983||e>=177984&&e<=178207||e>=178208&&e<=183983||e>=63744&&e<=64255||e>=194560&&e<=195103}stripAccents(e){return e.normalize("NFD").replace(/[\u0300-\u036f]/g,"")}_is_control(e){switch(e){case"\t":case"\n":case"\r":return!1;default:return/^\p{Cc}|\p{Cf}|\p{Co}|\p{Cs}$/u.test(e)}}_clean_text(e){const t=[];for(const s of e){const e=s.charCodeAt(0);0===e||65533===e||this._is_control(s)||(/^\s$/.test(s)?t.push(" "):t.push(s))}return t.join("")}normalize(e){return this.config.clean_text&&(e=this._clean_text(e)),this.config.handle_chinese_chars&&(e=this._tokenize_chinese_chars(e)),this.config.lowercase?(e=e.toLowerCase(),!1!==this.config.strip_accents&&(e=this.stripAccents(e))):this.config.strip_accents&&(e=this.stripAccents(e)),e}}class O extends o.Callable{static fromConfig(e){if(null===e)return null;switch(e.type){case"BertPreTokenizer":return new D(e);case"Sequence":return new ie(e);case"Whitespace":return new le(e);case"WhitespaceSplit":return new ce(e);case"Metaspace":return new ne(e);case"ByteLevel":return new N(e);case"Split":return new V(e);case"Punctuation":return new q(e);case"Digits":return new j(e);case"Replace":return new de(e);default:throw new Error(`Unknown PreTokenizer type: ${e.type}`)}}pre_tokenize_text(e,t){throw Error("pre_tokenize_text should be implemented in subclass.")}pre_tokenize(e,t){return(Array.isArray(e)?e.map((e=>this.pre_tokenize_text(e,t))):this.pre_tokenize_text(e,t)).flat()}_call(e,t){return this.pre_tokenize(e,t)}}class D extends O{constructor(e){super(),this.pattern=new RegExp(`[^\\s${m}]+|[${m}]`,"gu")}pre_tokenize_text(e,t){return e.trim().match(this.pattern)||[]}}class N extends O{constructor(e){super(),this.config=e,this.add_prefix_space=this.config.add_prefix_space,this.trim_offsets=this.config.trim_offsets,this.use_regex=this.config.use_regex??!0,this.pattern=/'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+/gu,this.byte_encoder=k,this.text_encoder=new TextEncoder}pre_tokenize_text(e,t){this.add_prefix_space&&!e.startsWith(" ")&&(e=" "+e);return(this.use_regex?e.match(this.pattern)||[]:[e]).map((e=>Array.from(this.text_encoder.encode(e),(e=>this.byte_encoder[e])).join("")))}}class V extends O{constructor(e){super(),this.config=e,this.pattern=d(this.config.pattern,this.config.invert)}pre_tokenize_text(e,t){return null===this.pattern?[]:this.config.invert?e.match(this.pattern)||[]:function(e,t){const s=[];let o=0;for(const n of e.matchAll(t)){const t=n[0];o<n.index&&s.push(e.slice(o,n.index)),t.length>0&&s.push(t),o=n.index+t.length}return o<e.length&&s.push(e.slice(o)),s}(e,this.pattern)}}class q extends O{constructor(e){super(),this.config=e,this.pattern=new RegExp(`[^${m}]+|[${m}]+`,"gu")}pre_tokenize_text(e,t){return e.match(this.pattern)||[]}}class j extends O{constructor(e){super(),this.config=e;const t="[^\\d]+|\\d"+(this.config.individual_digits?"":"+");this.pattern=new RegExp(t,"gu")}pre_tokenize_text(e,t){return e.match(this.pattern)||[]}}class R extends o.Callable{constructor(e){super(),this.config=e}static fromConfig(e){if(null===e)return null;switch(e.type){case"TemplateProcessing":return new $(e);case"ByteLevel":return new U(e);case"RobertaProcessing":return new W(e);case"BertProcessing":return new G(e);default:throw new Error(`Unknown PostProcessor type: ${e.type}`)}}post_process(e,...t){throw Error("post_process should be implemented in subclass.")}_call(e,...t){return this.post_process(e,...t)}}class G extends R{constructor(e){super(e),this.cls=e.cls[0],this.sep=e.sep[0]}post_process(e,t=null,{add_special_tokens:s=!0}={}){s&&(e=(0,o.mergeArrays)([this.cls],e,[this.sep]));let n=new Array(e.length).fill(0);if(null!==t){const r=s&&this instanceof W?[this.sep]:[],a=s?[this.sep]:[];e=(0,o.mergeArrays)(e,r,t,a),n=(0,o.mergeArrays)(n,new Array(t.length+r.length+a.length).fill(1))}return{tokens:e,token_type_ids:n}}}class W extends G{}class $ extends R{constructor(e){super(e),this.single=e.single,this.pair=e.pair}post_process(e,t=null,{add_special_tokens:s=!0}={}){const n=null===t?this.single:this.pair;let r=[],a=[];for(const i of n)"SpecialToken"in i?s&&(r.push(i.SpecialToken.id),a.push(i.SpecialToken.type_id)):"Sequence"in i&&("A"===i.Sequence.id?(r=(0,o.mergeArrays)(r,e),a=(0,o.mergeArrays)(a,new Array(e.length).fill(i.Sequence.type_id))):"B"===i.Sequence.id&&(r=(0,o.mergeArrays)(r,t),a=(0,o.mergeArrays)(a,new Array(t.length).fill(i.Sequence.type_id))));return{tokens:r,token_type_ids:a}}}class U extends R{post_process(e,t=null){return t&&(e=(0,o.mergeArrays)(e,t)),{tokens:e}}}class X extends o.Callable{constructor(e){super(),this.config=e,this.added_tokens=[],this.end_of_word_suffix=null,this.trim_offsets=e.trim_offsets}static fromConfig(e){if(null===e)return null;switch(e.type){case"WordPiece":return new Z(e);case"Metaspace":return new re(e);case"ByteLevel":return new K(e);case"Replace":return new Q(e);case"ByteFallback":return new H(e);case"Fuse":return new Y(e);case"Strip":return new J(e);case"Sequence":return new te(e);case"CTC":return new ee(e);case"BPEDecoder":return new se(e);default:throw new Error(`Unknown Decoder type: ${e.type}`)}}_call(e){return this.decode(e)}decode(e){return this.decode_chain(e).join("")}decode_chain(e){throw Error("`decode_chain` should be implemented in subclass.")}}class Q extends X{decode_chain(e){const t=d(this.config.pattern);return null===t?e:e.map((e=>e.replaceAll(t,this.config.content)))}}class H extends X{constructor(e){super(e),this.text_decoder=new TextDecoder}decode_chain(e){const t=[];let s=[];for(const o of e){let e=null;if(6===o.length&&o.startsWith("<0x")&&o.endsWith(">")){const t=parseInt(o.slice(3,5),16);isNaN(t)||(e=t)}if(null!==e)s.push(e);else{if(s.length>0){const e=this.text_decoder.decode(Uint8Array.from(s));t.push(e),s=[]}t.push(o)}}if(s.length>0){const e=this.text_decoder.decode(Uint8Array.from(s));t.push(e),s=[]}return t}}class Y extends X{decode_chain(e){return[e.join("")]}}class J extends X{constructor(e){super(e),this.content=this.config.content,this.start=this.config.start,this.stop=this.config.stop}decode_chain(e){return e.map((e=>{let t=0;for(let s=0;s<this.start&&e[s]===this.content;++s)t=s+1;let s=e.length;for(let t=0;t<this.stop;++t){const o=e.length-t-1;if(e[o]!==this.content)break;s=o}return e.slice(t,s)}))}}class Z extends X{constructor(e){super(e),this.cleanup=e.cleanup}decode_chain(e){return e.map(((e,t)=>(0!==t&&(e=e.startsWith(this.config.prefix)?e.replace(this.config.prefix,""):" "+e),this.cleanup&&(e=p(e)),e)))}}class K extends X{constructor(e){super(e),this.byte_decoder=b,this.text_decoder=new TextDecoder("utf-8",{fatal:!1,ignoreBOM:!0}),this.end_of_word_suffix=null}convert_tokens_to_string(e){const t=e.join(""),s=new Uint8Array([...t].map((e=>this.byte_decoder[e])));return this.text_decoder.decode(s)}decode_chain(e){const t=[];let s=[];for(const o of e)void 0!==this.added_tokens.find((e=>e.content===o))?(s.length>0&&(t.push(this.convert_tokens_to_string(s)),s=[]),t.push(o)):s.push(o);return s.length>0&&t.push(this.convert_tokens_to_string(s)),t}}class ee extends X{constructor(e){super(e),this.pad_token=this.config.pad_token,this.word_delimiter_token=this.config.word_delimiter_token,this.cleanup=this.config.cleanup}convert_tokens_to_string(e){if(0===e.length)return"";const t=[e[0]];for(let s=1;s<e.length;++s)e[s]!==t.at(-1)&&t.push(e[s]);let s=t.filter((e=>e!==this.pad_token)).join("");return this.cleanup&&(s=p(s).replaceAll(this.word_delimiter_token," ").trim()),s}decode_chain(e){return[this.convert_tokens_to_string(e)]}}class te extends X{constructor(e){super(e),this.decoders=e.decoders.map((e=>X.fromConfig(e)))}decode_chain(e){return this.decoders.reduce(((e,t)=>t.decode_chain(e)),e)}}class se extends X{constructor(e){super(e),this.suffix=this.config.suffix}decode_chain(e){return e.map(((t,s)=>t.replaceAll(this.suffix,s===e.length-1?"":" ")))}}class oe extends X{decode_chain(e){let t="";for(let s=1;s<e.length;s+=2)t+=e[s];return[t]}}class ne extends O{constructor(e){super(),this.addPrefixSpace=e.add_prefix_space,this.replacement=e.replacement,this.strRep=e.str_rep||this.replacement,this.prepend_scheme=e.prepend_scheme??"always"}pre_tokenize_text(e,{section_index:t}={}){let s=e.replaceAll(" ",this.strRep);return this.addPrefixSpace&&!s.startsWith(this.replacement)&&("always"===this.prepend_scheme||"first"===this.prepend_scheme&&0===t)&&(s=this.strRep+s),[s]}}class re extends X{constructor(e){super(e),this.addPrefixSpace=e.add_prefix_space,this.replacement=e.replacement}decode_chain(e){const t=[];for(let s=0;s<e.length;++s){let o=e[s].replaceAll(this.replacement," ");this.addPrefixSpace&&0==s&&o.startsWith(" ")&&(o=o.substring(1)),t.push(o)}return t}}class ae extends F{constructor(e){super(e),this.charsmap=e.precompiled_charsmap}normalize(e){if((e=(e=e.replace(/[\u0001-\u0008\u000B\u000E-\u001F\u007F\u008F\u009F]/gm,"")).replace(/[\u0009\u000A\u000C\u000D\u1680\u200B\u200C\u200E\u200F\u2028\u2029\u2581\uFEFF\uFFFD]/gm," ")).includes("~")){const t=e.split("~");e=t.map((e=>e.normalize("NFKC"))).join("~")}else e=e.normalize("NFKC");return e}}class ie extends O{constructor(e){super(),this.tokenizers=e.pretokenizers.map((e=>O.fromConfig(e)))}pre_tokenize_text(e,t){return this.tokenizers.reduce(((e,s)=>s.pre_tokenize(e,t)),[e])}}class le extends O{constructor(e){super()}pre_tokenize_text(e,t){return e.match(/\w+|[^\w\s]+/g)||[]}}class ce extends O{constructor(e){super()}pre_tokenize_text(e,t){return function(e){return e.match(/\S+/g)||[]}(e)}}class de extends O{constructor(e){super(),this.config=e,this.pattern=d(this.config.pattern),this.content=this.config.content}pre_tokenize_text(e,t){return null===this.pattern?[e]:[e.replaceAll(this.pattern,this.config.content)]}}const ue=["bos_token","eos_token","unk_token","sep_token","pad_token","cls_token","mask_token"];function he(e,t,s,n){for(const r of Object.keys(e)){const a=t-e[r].length,i=s(r),l=new Array(a).fill(i);e[r]="right"===n?(0,o.mergeArrays)(e[r],l):(0,o.mergeArrays)(l,e[r])}}function pe(e,t){for(const s of Object.keys(e))e[s].length=t}class _e extends o.Callable{return_token_type_ids=!1;_default_chat_template="{% for message in messages %}{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant\n' }}{% endif %}";constructor(e,t){super(),this._tokenizer_config=t,this.normalizer=F.fromConfig(e.normalizer),this.pre_tokenizer=O.fromConfig(e.pre_tokenizer),this.model=M.fromConfig(e.model,t),this.post_processor=R.fromConfig(e.post_processor),this.decoder=X.fromConfig(e.decoder),this.special_tokens=[],this.all_special_ids=[],this.added_tokens=[];for(const t of e.added_tokens){const e=new g(t);this.added_tokens.push(e),this.model.tokens_to_ids.set(e.content,e.id),this.model.vocab[e.id]=e.content,e.special&&(this.special_tokens.push(e.content),this.all_special_ids.push(e.id))}if(this.additional_special_tokens=t.additional_special_tokens??[],this.special_tokens.push(...this.additional_special_tokens),this.special_tokens=[...new Set(this.special_tokens)],this.decoder&&(this.decoder.added_tokens=this.added_tokens,this.decoder.end_of_word_suffix=this.model.end_of_word_suffix),this.added_tokens_regex=this.added_tokens.length>0?new RegExp(this.added_tokens.map((e=>`${e.lstrip?"\\s*":""}(${(0,o.escapeRegExp)(e.content)})${e.rstrip?"\\s*":""}`)).join("|")):null,this.mask_token=this.getToken("mask_token"),this.mask_token_id=this.model.tokens_to_ids.get(this.mask_token),this.pad_token=this.getToken("pad_token","eos_token"),this.pad_token_id=this.model.tokens_to_ids.get(this.pad_token),this.sep_token=this.getToken("sep_token"),this.sep_token_id=this.model.tokens_to_ids.get(this.sep_token),this.unk_token=this.getToken("unk_token"),this.unk_token_id=this.model.tokens_to_ids.get(this.unk_token),this.model_max_length=t.model_max_length,this.remove_space=t.remove_space,this.clean_up_tokenization_spaces=t.clean_up_tokenization_spaces??!0,this.do_lowercase_and_remove_accent=t.do_lowercase_and_remove_accent??!1,this.padding_side="right",this.legacy=!1,this.chat_template=t.chat_template??null,Array.isArray(this.chat_template)){const e=Object.create(null);for(const{name:t,template:s}of this.chat_template){if("string"!=typeof t||"string"!=typeof s)throw new Error('Chat template must be a list of objects with "name" and "template" properties');e[t]=s}this.chat_template=e}this._compiled_template_cache=new Map}getToken(...e){for(const t of e){const e=this._tokenizer_config[t];if(e){if("object"==typeof e){if("AddedToken"===e.__type)return e.content;throw Error(`Unknown token: ${e}`)}return e}}return null}static async from_pretrained(e,{progress_callback:t=null,config:s=null,cache_dir:o=null,local_files_only:n=!1,revision:r="main",legacy:a=null}={}){return new this(...await c(e,{progress_callback:t,config:s,cache_dir:o,local_files_only:n,revision:r,legacy:a}))}_call(e,{text_pair:t=null,add_special_tokens:s=!0,padding:o=!1,truncation:n=null,max_length:i=null,return_tensor:l=!0}={}){const c=Array.isArray(e);let d;if(c){if(0===e.length)throw Error("text array must be non-empty");if(null!==t){if(!Array.isArray(t))throw Error("text_pair must also be an array");if(e.length!==t.length)throw Error("text and text_pair must have the same length");d=e.map(((e,o)=>this._encode_plus(e,t[o],{add_special_tokens:s})))}else d=e.map((e=>this._encode_plus(e,null,{add_special_tokens:s})))}else{if(null===e)throw Error("text may not be null");if(Array.isArray(t))throw Error("When specifying `text_pair`, since `text` is a string, `text_pair` must also be a string (i.e., not an array).");d=[this._encode_plus(e,t,{add_special_tokens:s})]}if(null===i?i="max_length"===o?this.model_max_length:(0,r.max)(d.map((e=>e.input_ids.length)))[0]:n||console.warn("Truncation was not explicitly activated but `max_length` is provided a specific value, please use `truncation=true` to explicitly truncate examples to max length."),i=Math.min(i,this.model_max_length),o||n)for(let e=0;e<d.length;++e)d[e].input_ids.length!==i&&(d[e].input_ids.length>i?n&&pe(d[e],i):o&&he(d[e],i,(e=>"input_ids"===e?this.pad_token_id:0),this.padding_side));const u={};if(l){if((!o||!n)&&d.some((e=>{for(const t of Object.keys(e))if(e[t].length!==d[0][t]?.length)return!0;return!1})))throw Error("Unable to create tensor, you should probably activate truncation and/or padding with 'padding=true' and 'truncation=true' to have batched tensors with the same length.");const e=[d.length,d[0].input_ids.length];for(const t of Object.keys(d[0]))u[t]=new a.Tensor("int64",BigInt64Array.from(d.flatMap((e=>e[t])).map(BigInt)),e)}else{for(const e of Object.keys(d[0]))u[e]=d.map((t=>t[e]));if(!c)for(const e of Object.keys(u))u[e]=u[e][0]}return u}_encode_text(e){if(null===e)return null;const t=(this.added_tokens_regex?e.split(this.added_tokens_regex).filter((e=>e)):[e]).map(((e,t)=>{if(void 0!==this.added_tokens.find((t=>t.content===e)))return e;{if(!0===this.remove_space&&(e=e.trim().split(/\s+/).join(" ")),this.do_lowercase_and_remove_accent&&(e=function(e){return _(e.toLowerCase())}(e)),null!==this.normalizer&&(e=this.normalizer(e)),0===e.length)return[];const s=null!==this.pre_tokenizer?this.pre_tokenizer(e,{section_index:t}):[e];return this.model(s)}})).flat();return t}_encode_plus(e,t=null,{add_special_tokens:s=!0}={}){const n=this._encode_text(e),r=this._encode_text(t),a=this.post_processor?this.post_processor(n,r,{add_special_tokens:s}):{tokens:(0,o.mergeArrays)(n??[],r??[])},i=this.model.convert_tokens_to_ids(a.tokens),l={input_ids:i,attention_mask:new Array(i.length).fill(1)};return this.return_token_type_ids&&a.token_type_ids&&(l.token_type_ids=a.token_type_ids),l}encode(e,t=null,{add_special_tokens:s=!0}={}){const{input_ids:o}=this._encode_plus(e,t,{add_special_tokens:s});return o}batch_decode(e,t={}){return e instanceof a.Tensor&&(e=e.tolist()),e.map((e=>this.decode(e,t)))}decode(e,t={}){if(e instanceof a.Tensor&&(e=h(e)),!Array.isArray(e)||0===e.length||!(0,o.isIntegralNumber)(e[0]))throw Error("token_ids must be a non-empty array of integers.");return this.decode_single(e,t)}decode_single(e,{skip_special_tokens:t=!1,clean_up_tokenization_spaces:s=null}){let o=this.model.convert_ids_to_tokens(e);t&&(o=o.filter((e=>!this.special_tokens.includes(e))));let n=this.decoder?this.decoder(o):o.join(" ");return this.decoder&&this.decoder.end_of_word_suffix&&(n=n.replaceAll(this.decoder.end_of_word_suffix," "),t&&(n=n.trim())),(s??this.clean_up_tokenization_spaces)&&(n=p(n)),n}get default_chat_template(){return this._warned_about_chat_template||(console.warn("No chat template is defined for this tokenizer - using a default chat template that implements the ChatML format. If the default is not appropriate for your model, please set `tokenizer.chat_template` to an appropriate template. See https://huggingface.co/docs/transformers/main/chat_templating for more information."),this._warned_about_chat_template=!0),this._default_chat_template}apply_chat_template(e,{chat_template:t=null,add_generation_prompt:s=!1,tokenize:o=!0,padding:n=!1,truncation:r=!1,max_length:a=null,return_tensor:i=!0,tokenizer_kwargs:c={},...d}={}){if(this.chat_template&&"object"==typeof this.chat_template||null===this.chat_template&&this.default_chat_template&&"object"==typeof this.default_chat_template){const e=this.chat_template??this.default_chat_template;if(null!==t&&Object.hasOwn(e,t))t=e[t];else if(null===t&&"default"in e)t=e.default;else if(null===t)throw Error(`This model has multiple chat templates with no default specified! Please either pass a chat template or the name of the template you wish to use to the 'chat_template' argument. Available template names are ${Object.keys(e).sort()}.`)}else t??=this.chat_template??this.default_chat_template;if("string"!=typeof t)throw Error("chat_template must be a string, but got "+typeof t);let u=this._compiled_template_cache.get(t);void 0===u&&(u=new l.Template(t),this._compiled_template_cache.set(t,u));const h=Object.create(null);for(const e of ue){const t=this.getToken(e);t&&(h[e]=t)}const p=u.render({messages:e,add_generation_prompt:s,...h,...d});return o?this._call(p,{add_special_tokens:!1,padding:n,truncation:r,max_length:a,return_tensor:i,...c}).input_ids:p}}class me extends _e{return_token_type_ids=!0}class fe extends _e{return_token_type_ids=!0}class ge extends _e{return_token_type_ids=!0}class Me extends _e{return_token_type_ids=!0}class we extends _e{return_token_type_ids=!0}class Te extends _e{return_token_type_ids=!0}class ke extends _e{return_token_type_ids=!0}class be extends _e{return_token_type_ids=!0}class xe extends _e{return_token_type_ids=!0}class ye extends _e{}class Fe extends _e{}class Ce extends _e{return_token_type_ids=!0;constructor(e,t){super(e,t),console.warn('WARNING: `XLMTokenizer` is not yet supported by Hugging Face\'s "fast" tokenizers library. Therefore, you may experience slightly inaccurate results.')}}class Pe extends _e{return_token_type_ids=!0}class ve extends _e{}class Se extends _e{_default_chat_template='{% for message in messages %}" "{{ message.content }}{{ eos_token }}" "{% endfor %}'}class Ae extends _e{}class Le extends _e{constructor(e,t){super(e,t),this.languageRegex=/^[a-z]{2}_[A-Z]{2}$/,this.language_codes=this.special_tokens.filter((e=>this.languageRegex.test(e))),this.lang_to_token=e=>e}_build_translation_inputs(e,t,s){return Ue(this,e,t,s)}}class Ee extends Le{}class ze extends _e{}class Be extends Se{constructor(e,t){const s=".,!?…。,、।۔،",o=e.pre_tokenizer?.pretokenizers[0]?.pattern;o&&o.Regex===` ?[^(\\s|[${s}])]+`&&(o.Regex=` ?[^\\s${s}]+`),super(e,t)}}const Ie="▁";class Oe extends _e{_default_chat_template="{% if messages[0]['role'] == 'system' %}{% set loop_messages = messages[1:] %}{% set system_message = messages[0]['content'] %}{% elif USE_DEFAULT_PROMPT == true and not '<<SYS>>' in messages[0]['content'] %}{% set loop_messages = messages %}{% set system_message = 'DEFAULT_SYSTEM_MESSAGE' %}{% else %}{% set loop_messages = messages %}{% set system_message = false %}{% endif %}{% for message in loop_messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if loop.index0 == 0 and system_message != false %}{% set content = '<<SYS>>\n' + system_message + '\n<</SYS>>\n\n' + message['content'] %}{% else %}{% set content = message['content'] %}{% endif %}{% if message['role'] == 'user' %}{{ bos_token + '[INST] ' + content.strip() + ' [/INST]' }}{% elif message['role'] == 'system' %}{{ '<<SYS>>\n' + content.strip() + '\n<</SYS>>\n\n' }}{% elif message['role'] == 'assistant' %}{{ ' '  + content.strip() + ' ' + eos_token }}{% endif %}{% endfor %}";DEFAULT_SYSTEM_PROMPT="You are a helpful, respectful and honest assistant. Always answer as helpfully as possible, while being safe. Your answers should not include any harmful, unethical, racist, sexist, toxic, dangerous, or illegal content. Please ensure that your responses are socially unbiased and positive in nature.\n\nIf a question does not make any sense, or is not factually coherent, explain why instead of answering something not correct. If you don't know the answer to a question, please don't share false information.";constructor(e,t){super(e,t),this.use_default_system_prompt=t.use_default_system_prompt??!1,this.legacy=t.legacy??!0,this.legacy||(this.normalizer=null,this.pre_tokenizer=new ne({replacement:Ie,add_prefix_space:!0,prepend_scheme:"first"}))}_encode_text(e){if(null===e)return null;if(this.legacy||0===e.length)return super._encode_text(e);let t=super._encode_text(Ie+e.replaceAll(Ie," "));return t.length>1&&t[0]===Ie&&this.special_tokens.includes(t[1])&&(t=t.slice(1)),t}get default_chat_template(){return super.default_chat_template.replaceAll("USE_DEFAULT_PROMPT",this.use_default_system_prompt?"true":"false").replaceAll("DEFAULT_SYSTEM_MESSAGE",this.DEFAULT_SYSTEM_PROMPT.replaceAll("\n","\\n").replaceAll("'","\\'"))}}class De extends Oe{}class Ne extends _e{}class Ve extends _e{}class qe extends _e{}class je extends _e{}class Re extends _e{}class Ge extends _e{}class We extends _e{_default_chat_template="{% if messages[0]['role'] == 'system' %}{{ raise_exception('System role not supported') }}{% endif %}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if (message['role'] == 'assistant') %}{% set role = 'model' %}{% else %}{% set role = message['role'] %}{% endif %}{{ '<start_of_turn>' + role + '\n' + message['content'] | trim + '<end_of_turn>\n' }}{% endfor %}{% if add_generation_prompt %}{{'<start_of_turn>model\n'}}{% endif %}"}class $e extends _e{}function Ue(e,t,s,o){if(!("language_codes"in e)||!Array.isArray(e.language_codes))throw new Error("Tokenizer must have `language_codes` attribute set and it should be an array of language ids.");if(!("languageRegex"in e&&e.languageRegex instanceof RegExp))throw new Error("Tokenizer must have `languageRegex` attribute set and it should be a regular expression.");if(!("lang_to_token"in e)||"function"!=typeof e.lang_to_token)throw new Error("Tokenizer must have `lang_to_token` attribute set and it should be a function.");const n=o.src_lang,r=o.tgt_lang;if(!e.language_codes.includes(r))throw new Error(`Target language code "${r}" is not valid. Must be one of: {${e.language_codes.join(", ")}}`);if(void 0!==n){if(!e.language_codes.includes(n))throw new Error(`Source language code "${n}" is not valid. Must be one of: {${e.language_codes.join(", ")}}`);for(const t of e.post_processor.config.single)if("SpecialToken"in t&&e.languageRegex.test(t.SpecialToken.id)){t.SpecialToken.id=e.lang_to_token(n);break}}return o.forced_bos_token_id=e.model.convert_tokens_to_ids([e.lang_to_token(r)])[0],e._call(t,s)}class Xe extends _e{constructor(e,t){super(e,t),this.languageRegex=/^[a-z]{3}_[A-Z][a-z]{3}$/,this.language_codes=this.special_tokens.filter((e=>this.languageRegex.test(e))),this.lang_to_token=e=>e}_build_translation_inputs(e,t,s){return Ue(this,e,t,s)}}class Qe extends _e{constructor(e,t){super(e,t),this.languageRegex=/^__[a-z]{2,3}__$/,this.language_codes=this.special_tokens.filter((e=>this.languageRegex.test(e))).map((e=>e.slice(2,-2))),this.lang_to_token=e=>`__${e}__`}_build_translation_inputs(e,t,s){return Ue(this,e,t,s)}}const He=[["en","english"],["zh","chinese"],["de","german"],["es","spanish"],["ru","russian"],["ko","korean"],["fr","french"],["ja","japanese"],["pt","portuguese"],["tr","turkish"],["pl","polish"],["ca","catalan"],["nl","dutch"],["ar","arabic"],["sv","swedish"],["it","italian"],["id","indonesian"],["hi","hindi"],["fi","finnish"],["vi","vietnamese"],["he","hebrew"],["uk","ukrainian"],["el","greek"],["ms","malay"],["cs","czech"],["ro","romanian"],["da","danish"],["hu","hungarian"],["ta","tamil"],["no","norwegian"],["th","thai"],["ur","urdu"],["hr","croatian"],["bg","bulgarian"],["lt","lithuanian"],["la","latin"],["mi","maori"],["ml","malayalam"],["cy","welsh"],["sk","slovak"],["te","telugu"],["fa","persian"],["lv","latvian"],["bn","bengali"],["sr","serbian"],["az","azerbaijani"],["sl","slovenian"],["kn","kannada"],["et","estonian"],["mk","macedonian"],["br","breton"],["eu","basque"],["is","icelandic"],["hy","armenian"],["ne","nepali"],["mn","mongolian"],["bs","bosnian"],["kk","kazakh"],["sq","albanian"],["sw","swahili"],["gl","galician"],["mr","marathi"],["pa","punjabi"],["si","sinhala"],["km","khmer"],["sn","shona"],["yo","yoruba"],["so","somali"],["af","afrikaans"],["oc","occitan"],["ka","georgian"],["be","belarusian"],["tg","tajik"],["sd","sindhi"],["gu","gujarati"],["am","amharic"],["yi","yiddish"],["lo","lao"],["uz","uzbek"],["fo","faroese"],["ht","haitian creole"],["ps","pashto"],["tk","turkmen"],["nn","nynorsk"],["mt","maltese"],["sa","sanskrit"],["lb","luxembourgish"],["my","myanmar"],["bo","tibetan"],["tl","tagalog"],["mg","malagasy"],["as","assamese"],["tt","tatar"],["haw","hawaiian"],["ln","lingala"],["ha","hausa"],["ba","bashkir"],["jw","javanese"],["su","sundanese"]],Ye=new Map(He),Je=new Map([...He.map((([e,t])=>[t,e])),["burmese","my"],["valencian","ca"],["flemish","nl"],["haitian","ht"],["letzeburgesch","lb"],["pushto","ps"],["panjabi","pa"],["moldavian","ro"],["moldovan","ro"],["sinhalese","si"],["castilian","es"]]);class Ze extends _e{_default_chat_template='{% for message in messages %}" "{{ message.content }}{{ eos_token }}" "{% endfor %}';_decode_asr(e,{return_timestamps:t=!1,return_language:s=!1,time_precision:o=null,force_full_sequences:n=!0}={}){if(null===o)throw Error("Must specify time_precision");let a=null;const i="word"===t;function l(){return{language:a,timestamp:[null,null],text:""}}const c=[];let d=l(),u=0;const h=this.model.convert_tokens_to_ids(["<|notimestamps|>"])[0]+1;let p=[],_=[],m=!1,f=null;const g=new Set(this.all_special_ids);for(const s of e){const e=s.tokens,n=i?s.token_timestamps:null;let M=null,w=h;if("stride"in s){const[t,n,r]=s.stride;if(u-=n,f=t-r,n&&(w=n/o+h),r)for(let t=e.length-1;t>=0;--t){const s=e[t];if(s>=h){if(null!==M&&(s-h)*o<f)break;M=s}}}let T=[],k=[];for(let s=0;s<e.length;++s){const f=e[s];if(g.has(f)){const e=this.decode([f]),s=Ye.get(e.slice(2,-2));if(void 0!==s){if(null!==a&&s!==a&&!t){p.push(T);const e=this.findLongestCommonSequence(p)[0],t=this.decode(e);d.text=t,c.push(d),p=[],T=[],d=l()}a=d.language=s}}else if(f>=h){const e=(f-h)*o+u,t=(0,r.round)(e,2);if(null!==M&&f>=M)m=!0;else if(m||p.length>0&&f<w)m=!1;else if(null===d.timestamp[0])d.timestamp[0]=t;else if(t===d.timestamp[0]);else{d.timestamp[1]=t,p.push(T),i&&_.push(k);const[e,s]=this.findLongestCommonSequence(p,_),o=this.decode(e);d.text=o,i&&(d.words=this.collateWordTimestamps(e,s,a)),c.push(d),p=[],T=[],_=[],k=[],d=l()}}else if(T.push(f),i){let e,t=(0,r.round)(n[s]+u,2);e=s+1<n.length?(0,r.round)(n[s+1]+u,2):null,k.push([t,e])}}if("stride"in s){const[e,t,o]=s.stride;u+=e-o}T.length>0?(p.push(T),i&&_.push(k)):p.every((e=>0===e.length))&&(d=l(),p=[],T=[],_=[],k=[])}if(p.length>0){if(n&&t)throw new Error("Whisper did not predict an ending timestamp, which can happen if audio is cut off in the middle of a word. Also make sure WhisperTimeStampLogitsProcessor was used during generation.");const[e,s]=this.findLongestCommonSequence(p,_),o=this.decode(e);d.text=o,i&&(d.words=this.collateWordTimestamps(e,s,a)),c.push(d)}let M=Object.create(null);const w=c.map((e=>e.text)).join("");if(t||s){for(let e=0;e<c.length;++e){const o=c[e];t||delete o.timestamp,s||delete o.language}if(i){const e=[];for(const t of c)for(const s of t.words)e.push(s);M={chunks:e}}else M={chunks:c}}return[w,M]}findLongestCommonSequence(e,t=null){let s=e[0],o=s.length,n=[];const r=Array.isArray(t)&&t.length>0;let a=r?[]:null,i=r?t[0]:null;for(let l=1;l<e.length;++l){const c=e[l];let d=0,u=[o,o,0,0];const h=c.length;for(let e=1;e<o+h;++e){const t=e/1e4,n=Math.max(0,o-e),r=Math.min(o,o+h-e),a=s.slice(n,r),i=Math.max(0,e-o),l=Math.min(h,e),p=c.slice(i,l);if(a.length!==p.length)throw new Error("There is a bug within whisper `decode_asr` function, please report it. Dropping to prevent bad inference.");const _=a.filter(((e,t)=>e===p[t])).length,m=_/e+t;_>1&&m>d&&(d=m,u=[n,r,i,l])}const[p,_,m,f]=u,g=Math.floor((_+p)/2),M=Math.floor((f+m)/2);n.push(...s.slice(0,g)),s=c.slice(M),o=s.length,r&&(a.push(...i.slice(0,g)),i=t[l].slice(M))}return n.push(...s),r?(a.push(...i),[n,a]):[n,[]]}collateWordTimestamps(e,t,s){const[o,n,r]=this.combineTokensIntoWords(e,s),a=[];for(let e=0;e<o.length;++e){const s=r[e];a.push({text:o[e],timestamp:[t[s.at(0)][0],t[s.at(-1)][1]]})}return a}combineTokensIntoWords(e,t,s="\"'“¡¿([{-",o="\"'.。,,!!??::”)]}、"){let n,r,a;return["chinese","japanese","thai","lao","myanmar"].includes(t=t??"english")?[n,r,a]=this.splitTokensOnUnicode(e):[n,r,a]=this.splitTokensOnSpaces(e),this.mergePunctuations(n,r,a,s,o)}decode(e,t){let s;return t&&t.decode_with_timestamps?(e instanceof a.Tensor&&(e=h(e)),s=this.decodeWithTimestamps(e,t)):s=super.decode(e,t),s}decodeWithTimestamps(e,t){const s=t?.time_precision??.02,o=Array.from(this.all_special_ids).at(-1)+1;let n=[[]];for(const t of e)if(t>=o){const e=(0,r.round)((t-o)*s,2);n.push(`<|${e}|>`),n.push([])}else n[n.length-1].push(t);return n=n.map((e=>"string"==typeof e?e:super.decode(e,t))),n.join("")}splitTokensOnUnicode(e){const t=this.decode(e,{decode_with_timestamps:!0}),s=[],o=[],n=[];let r=[],a=[],i=0;for(let l=0;l<e.length;++l){const c=e[l];r.push(c),a.push(l);const d=this.decode(r,{decode_with_timestamps:!0});d.includes("�")&&"�"!==t[i+d.indexOf("�")]||(s.push(d),o.push(r),n.push(a),r=[],a=[],i+=d.length)}return[s,o,n]}splitTokensOnSpaces(e){const[t,s,o]=this.splitTokensOnUnicode(e),n=[],r=[],a=[],i=new RegExp(`^[${m}]$`,"gu");for(let e=0;e<t.length;++e){const l=t[e],c=s[e],d=o[e],u=c[0]>=this.model.tokens_to_ids.get("<|endoftext|>"),h=l.startsWith(" "),p=l.trim(),_=i.test(p);if(u||h||_||0===n.length)n.push(l),r.push(c),a.push(d);else{const e=n.length-1;n[e]+=l,r[e].push(...c),a[e].push(...d)}}return[n,r,a]}mergePunctuations(e,t,s,n,r){const a=structuredClone(e),i=structuredClone(t),l=structuredClone(s);let c=a.length-2,d=a.length-1;for(;c>=0;)a[c].startsWith(" ")&&n.includes(a[c].trim())?(a[d]=a[c]+a[d],i[d]=(0,o.mergeArrays)(i[c],i[d]),l[d]=(0,o.mergeArrays)(l[c],l[d]),a[c]="",i[c]=[],l[c]=[]):d=c,--c;for(c=0,d=1;d<a.length;)!a[c].endsWith(" ")&&r.includes(a[d])?(a[c]+=a[d],i[c]=(0,o.mergeArrays)(i[c],i[d]),l[c]=(0,o.mergeArrays)(l[c],l[d]),a[d]="",i[d]=[],l[d]=[]):c=d,++d;return[a.filter((e=>e)),i.filter((e=>e.length>0)),l.filter((e=>e.length>0))]}get_decoder_prompt_ids({language:e=null,task:t=null,no_timestamps:s=!0}={}){const o=[];if(e){e=e.toLowerCase();let t=Je.get(e);if(void 0===t){if(!Ye.has(e)){const t=2===e.length?Ye.keys():Ye.values();throw new Error(`Language "${e}" is not supported. Must be one of: ${JSON.stringify(t)}`)}t=e}const s=this.model.tokens_to_ids.get(`<|${t}|>`);if(void 0===s)throw new Error(`Unable to find language "${t}" in model vocabulary. Please report this issue at https://github.com/xenova/transformers.js/issues/new/choose.`);o.push(s)}else o.push(null);if(t){if("transcribe"!==(t=t.toLowerCase())&&"translate"!==t)throw new Error(`Task "${t}" is not supported. Must be one of: ["transcribe", "translate"]`);const e=this.model.tokens_to_ids.get(`<|${t}|>`);if(void 0===e)throw new Error(`Unable to find task "${t}" in model vocabulary. Please report this issue at https://github.com/xenova/transformers.js/issues/new/choose.`);o.push(e)}else o.push(null);if(s){const e=this.model.tokens_to_ids.get("<|notimestamps|>");if(void 0===e)throw new Error('Unable to find "<|notimestamps|>" in model vocabulary. Please report this issue at https://github.com/xenova/transformers.js/issues/new/choose.');o.push(e)}return o.map(((e,t)=>[t+1,e])).filter((e=>null!==e[1]))}}class Ke extends _e{}class et extends _e{}class tt extends _e{}class st extends _e{constructor(e,t){super(e,t),this.languageRegex=/^(>>\w+<<)\s*/g,this.supported_language_codes=this.model.vocab.filter((e=>this.languageRegex.test(e))),console.warn('WARNING: `MarianTokenizer` is not yet supported by Hugging Face\'s "fast" tokenizers library. Therefore, you may experience slightly inaccurate results.')}_encode_text(e){if(null===e)return null;const[t,...s]=e.trim().split(this.languageRegex);if(0===s.length)return super._encode_text(t);if(2===s.length){const[e,t]=s;return this.supported_language_codes.includes(e)||console.warn(`Unsupported language code "${e}" detected, which may lead to unexpected behavior. Should be one of: ${JSON.stringify(this.supported_language_codes)}`),(0,o.mergeArrays)([e],super._encode_text(t))}}}class ot extends _e{}class nt extends _e{_default_chat_template="{% for message in messages %}{% if message['role'] == 'user' %}{{ ' ' }}{% endif %}{{ message['content'] }}{% if not loop.last %}{{ '  ' }}{% endif %}{% endfor %}{{ eos_token }}"}class rt extends nt{}class at extends _e{}class it extends _e{}class lt extends _e{constructor(e,t){super(e,t),this.decoder=new oe({})}}class ct extends _e{}class dt{static TOKENIZER_CLASS_MAPPING={T5Tokenizer:ve,DistilBertTokenizer:ye,CamembertTokenizer:Fe,DebertaTokenizer:we,DebertaV2Tokenizer:Te,BertTokenizer:me,HerbertTokenizer:ke,ConvBertTokenizer:be,RoFormerTokenizer:xe,XLMTokenizer:Ce,ElectraTokenizer:Pe,MobileBertTokenizer:ge,SqueezeBertTokenizer:Me,AlbertTokenizer:fe,GPT2Tokenizer:Se,BartTokenizer:Ae,MBartTokenizer:Le,MBart50Tokenizer:Ee,RobertaTokenizer:ze,WhisperTokenizer:Ze,CodeGenTokenizer:Ke,CLIPTokenizer:et,SiglipTokenizer:tt,MarianTokenizer:st,BloomTokenizer:Be,NllbTokenizer:Xe,M2M100Tokenizer:Qe,LlamaTokenizer:Oe,CodeLlamaTokenizer:De,XLMRobertaTokenizer:Ne,MPNetTokenizer:Ve,FalconTokenizer:qe,GPTNeoXTokenizer:je,EsmTokenizer:Re,Wav2Vec2CTCTokenizer:ot,BlenderbotTokenizer:nt,BlenderbotSmallTokenizer:rt,SpeechT5Tokenizer:at,NougatTokenizer:it,VitsTokenizer:lt,Qwen2Tokenizer:Ge,GemmaTokenizer:We,Grok1Tokenizer:$e,CohereTokenizer:ct,PreTrainedTokenizer:_e};static async from_pretrained(e,{quantized:t=!0,progress_callback:s=null,config:o=null,cache_dir:n=null,local_files_only:r=!1,revision:a="main",legacy:i=null}={}){const[l,d]=await c(e,{quantized:t,progress_callback:s,config:o,cache_dir:n,local_files_only:r,revision:a,legacy:i}),u=d.tokenizer_class?.replace(/Fast$/,"")??"PreTrainedTokenizer";let h=this.TOKENIZER_CLASS_MAPPING[u];return h||(console.warn(`Unknown tokenizer class "${u}", attempting to construct from base class.`),h=_e),new h(l,d)}}},"./src/transformers.js":
/*!*****************************!*\
  !*** ./src/transformers.js ***!
  \*****************************/(e,t,s)=>{s.r(t),s.d(t,{ASTFeatureExtractor:()=>i.ASTFeatureExtractor,ASTForAudioClassification:()=>r.ASTForAudioClassification,ASTModel:()=>r.ASTModel,ASTPreTrainedModel:()=>r.ASTPreTrainedModel,AlbertForMaskedLM:()=>r.AlbertForMaskedLM,AlbertForQuestionAnswering:()=>r.AlbertForQuestionAnswering,AlbertForSequenceClassification:()=>r.AlbertForSequenceClassification,AlbertModel:()=>r.AlbertModel,AlbertPreTrainedModel:()=>r.AlbertPreTrainedModel,AlbertTokenizer:()=>a.AlbertTokenizer,AudioClassificationPipeline:()=>o.AudioClassificationPipeline,AutoConfig:()=>l.AutoConfig,AutoModel:()=>r.AutoModel,AutoModelForAudioClassification:()=>r.AutoModelForAudioClassification,AutoModelForAudioFrameClassification:()=>r.AutoModelForAudioFrameClassification,AutoModelForCTC:()=>r.AutoModelForCTC,AutoModelForCausalLM:()=>r.AutoModelForCausalLM,AutoModelForDepthEstimation:()=>r.AutoModelForDepthEstimation,AutoModelForDocumentQuestionAnswering:()=>r.AutoModelForDocumentQuestionAnswering,AutoModelForImageClassification:()=>r.AutoModelForImageClassification,AutoModelForImageFeatureExtraction:()=>r.AutoModelForImageFeatureExtraction,AutoModelForImageMatting:()=>r.AutoModelForImageMatting,AutoModelForImageSegmentation:()=>r.AutoModelForImageSegmentation,AutoModelForImageToImage:()=>r.AutoModelForImageToImage,AutoModelForMaskGeneration:()=>r.AutoModelForMaskGeneration,AutoModelForMaskedLM:()=>r.AutoModelForMaskedLM,AutoModelForObjectDetection:()=>r.AutoModelForObjectDetection,AutoModelForQuestionAnswering:()=>r.AutoModelForQuestionAnswering,AutoModelForSemanticSegmentation:()=>r.AutoModelForSemanticSegmentation,AutoModelForSeq2SeqLM:()=>r.AutoModelForSeq2SeqLM,AutoModelForSequenceClassification:()=>r.AutoModelForSequenceClassification,AutoModelForSpeechSeq2Seq:()=>r.AutoModelForSpeechSeq2Seq,AutoModelForTextToSpectrogram:()=>r.AutoModelForTextToSpectrogram,AutoModelForTextToWaveform:()=>r.AutoModelForTextToWaveform,AutoModelForTokenClassification:()=>r.AutoModelForTokenClassification,AutoModelForVision2Seq:()=>r.AutoModelForVision2Seq,AutoModelForXVector:()=>r.AutoModelForXVector,AutoModelForZeroShotObjectDetection:()=>r.AutoModelForZeroShotObjectDetection,AutoProcessor:()=>i.AutoProcessor,AutoTokenizer:()=>a.AutoTokenizer,AutomaticSpeechRecognitionPipeline:()=>o.AutomaticSpeechRecognitionPipeline,BartForConditionalGeneration:()=>r.BartForConditionalGeneration,BartForSequenceClassification:()=>r.BartForSequenceClassification,BartModel:()=>r.BartModel,BartPretrainedModel:()=>r.BartPretrainedModel,BartTokenizer:()=>a.BartTokenizer,BaseModelOutput:()=>r.BaseModelOutput,BeitFeatureExtractor:()=>i.BeitFeatureExtractor,BeitForImageClassification:()=>r.BeitForImageClassification,BeitModel:()=>r.BeitModel,BeitPreTrainedModel:()=>r.BeitPreTrainedModel,BertForMaskedLM:()=>r.BertForMaskedLM,BertForQuestionAnswering:()=>r.BertForQuestionAnswering,BertForSequenceClassification:()=>r.BertForSequenceClassification,BertForTokenClassification:()=>r.BertForTokenClassification,BertModel:()=>r.BertModel,BertPreTrainedModel:()=>r.BertPreTrainedModel,BertTokenizer:()=>a.BertTokenizer,BitImageProcessor:()=>i.BitImageProcessor,BlenderbotForConditionalGeneration:()=>r.BlenderbotForConditionalGeneration,BlenderbotModel:()=>r.BlenderbotModel,BlenderbotPreTrainedModel:()=>r.BlenderbotPreTrainedModel,BlenderbotSmallForConditionalGeneration:()=>r.BlenderbotSmallForConditionalGeneration,BlenderbotSmallModel:()=>r.BlenderbotSmallModel,BlenderbotSmallPreTrainedModel:()=>r.BlenderbotSmallPreTrainedModel,BlenderbotSmallTokenizer:()=>a.BlenderbotSmallTokenizer,BlenderbotTokenizer:()=>a.BlenderbotTokenizer,BloomForCausalLM:()=>r.BloomForCausalLM,BloomModel:()=>r.BloomModel,BloomPreTrainedModel:()=>r.BloomPreTrainedModel,BloomTokenizer:()=>a.BloomTokenizer,CLIPFeatureExtractor:()=>i.CLIPFeatureExtractor,CLIPModel:()=>r.CLIPModel,CLIPPreTrainedModel:()=>r.CLIPPreTrainedModel,CLIPSegForImageSegmentation:()=>r.CLIPSegForImageSegmentation,CLIPSegModel:()=>r.CLIPSegModel,CLIPSegPreTrainedModel:()=>r.CLIPSegPreTrainedModel,CLIPTextModelWithProjection:()=>r.CLIPTextModelWithProjection,CLIPTokenizer:()=>a.CLIPTokenizer,CLIPVisionModelWithProjection:()=>r.CLIPVisionModelWithProjection,CamembertForMaskedLM:()=>r.CamembertForMaskedLM,CamembertForQuestionAnswering:()=>r.CamembertForQuestionAnswering,CamembertForSequenceClassification:()=>r.CamembertForSequenceClassification,CamembertForTokenClassification:()=>r.CamembertForTokenClassification,CamembertModel:()=>r.CamembertModel,CamembertPreTrainedModel:()=>r.CamembertPreTrainedModel,CamembertTokenizer:()=>a.CamembertTokenizer,CausalLMOutput:()=>r.CausalLMOutput,CausalLMOutputWithPast:()=>r.CausalLMOutputWithPast,ChineseCLIPFeatureExtractor:()=>i.ChineseCLIPFeatureExtractor,ChineseCLIPModel:()=>r.ChineseCLIPModel,ChineseCLIPPreTrainedModel:()=>r.ChineseCLIPPreTrainedModel,ClapAudioModelWithProjection:()=>r.ClapAudioModelWithProjection,ClapFeatureExtractor:()=>i.ClapFeatureExtractor,ClapModel:()=>r.ClapModel,ClapPreTrainedModel:()=>r.ClapPreTrainedModel,ClapTextModelWithProjection:()=>r.ClapTextModelWithProjection,CodeGenForCausalLM:()=>r.CodeGenForCausalLM,CodeGenModel:()=>r.CodeGenModel,CodeGenPreTrainedModel:()=>r.CodeGenPreTrainedModel,CodeGenTokenizer:()=>a.CodeGenTokenizer,CodeLlamaTokenizer:()=>a.CodeLlamaTokenizer,CohereTokenizer:()=>a.CohereTokenizer,ConvBertForMaskedLM:()=>r.ConvBertForMaskedLM,ConvBertForQuestionAnswering:()=>r.ConvBertForQuestionAnswering,ConvBertForSequenceClassification:()=>r.ConvBertForSequenceClassification,ConvBertForTokenClassification:()=>r.ConvBertForTokenClassification,ConvBertModel:()=>r.ConvBertModel,ConvBertPreTrainedModel:()=>r.ConvBertPreTrainedModel,ConvBertTokenizer:()=>a.ConvBertTokenizer,ConvNextFeatureExtractor:()=>i.ConvNextFeatureExtractor,ConvNextForImageClassification:()=>r.ConvNextForImageClassification,ConvNextImageProcessor:()=>i.ConvNextImageProcessor,ConvNextModel:()=>r.ConvNextModel,ConvNextPreTrainedModel:()=>r.ConvNextPreTrainedModel,ConvNextV2ForImageClassification:()=>r.ConvNextV2ForImageClassification,ConvNextV2Model:()=>r.ConvNextV2Model,ConvNextV2PreTrainedModel:()=>r.ConvNextV2PreTrainedModel,DPTFeatureExtractor:()=>i.DPTFeatureExtractor,DPTForDepthEstimation:()=>r.DPTForDepthEstimation,DPTImageProcessor:()=>i.DPTImageProcessor,DPTModel:()=>r.DPTModel,DPTPreTrainedModel:()=>r.DPTPreTrainedModel,DebertaForMaskedLM:()=>r.DebertaForMaskedLM,DebertaForQuestionAnswering:()=>r.DebertaForQuestionAnswering,DebertaForSequenceClassification:()=>r.DebertaForSequenceClassification,DebertaForTokenClassification:()=>r.DebertaForTokenClassification,DebertaModel:()=>r.DebertaModel,DebertaPreTrainedModel:()=>r.DebertaPreTrainedModel,DebertaTokenizer:()=>a.DebertaTokenizer,DebertaV2ForMaskedLM:()=>r.DebertaV2ForMaskedLM,DebertaV2ForQuestionAnswering:()=>r.DebertaV2ForQuestionAnswering,DebertaV2ForSequenceClassification:()=>r.DebertaV2ForSequenceClassification,DebertaV2ForTokenClassification:()=>r.DebertaV2ForTokenClassification,DebertaV2Model:()=>r.DebertaV2Model,DebertaV2PreTrainedModel:()=>r.DebertaV2PreTrainedModel,DebertaV2Tokenizer:()=>a.DebertaV2Tokenizer,DeiTFeatureExtractor:()=>i.DeiTFeatureExtractor,DeiTForImageClassification:()=>r.DeiTForImageClassification,DeiTModel:()=>r.DeiTModel,DeiTPreTrainedModel:()=>r.DeiTPreTrainedModel,DepthAnythingForDepthEstimation:()=>r.DepthAnythingForDepthEstimation,DepthAnythingPreTrainedModel:()=>r.DepthAnythingPreTrainedModel,DepthEstimationPipeline:()=>o.DepthEstimationPipeline,DetrFeatureExtractor:()=>i.DetrFeatureExtractor,DetrForObjectDetection:()=>r.DetrForObjectDetection,DetrForSegmentation:()=>r.DetrForSegmentation,DetrModel:()=>r.DetrModel,DetrObjectDetectionOutput:()=>r.DetrObjectDetectionOutput,DetrPreTrainedModel:()=>r.DetrPreTrainedModel,DetrSegmentationOutput:()=>r.DetrSegmentationOutput,Dinov2ForImageClassification:()=>r.Dinov2ForImageClassification,Dinov2Model:()=>r.Dinov2Model,Dinov2PreTrainedModel:()=>r.Dinov2PreTrainedModel,DistilBertForMaskedLM:()=>r.DistilBertForMaskedLM,DistilBertForQuestionAnswering:()=>r.DistilBertForQuestionAnswering,DistilBertForSequenceClassification:()=>r.DistilBertForSequenceClassification,DistilBertForTokenClassification:()=>r.DistilBertForTokenClassification,DistilBertModel:()=>r.DistilBertModel,DistilBertPreTrainedModel:()=>r.DistilBertPreTrainedModel,DistilBertTokenizer:()=>a.DistilBertTokenizer,DocumentQuestionAnsweringPipeline:()=>o.DocumentQuestionAnsweringPipeline,DonutFeatureExtractor:()=>i.DonutFeatureExtractor,DonutSwinModel:()=>r.DonutSwinModel,DonutSwinPreTrainedModel:()=>r.DonutSwinPreTrainedModel,EfficientNetForImageClassification:()=>r.EfficientNetForImageClassification,EfficientNetImageProcessor:()=>i.EfficientNetImageProcessor,EfficientNetModel:()=>r.EfficientNetModel,EfficientNetPreTrainedModel:()=>r.EfficientNetPreTrainedModel,ElectraForMaskedLM:()=>r.ElectraForMaskedLM,ElectraForQuestionAnswering:()=>r.ElectraForQuestionAnswering,ElectraForSequenceClassification:()=>r.ElectraForSequenceClassification,ElectraForTokenClassification:()=>r.ElectraForTokenClassification,ElectraModel:()=>r.ElectraModel,ElectraPreTrainedModel:()=>r.ElectraPreTrainedModel,ElectraTokenizer:()=>a.ElectraTokenizer,EsmForMaskedLM:()=>r.EsmForMaskedLM,EsmForSequenceClassification:()=>r.EsmForSequenceClassification,EsmForTokenClassification:()=>r.EsmForTokenClassification,EsmModel:()=>r.EsmModel,EsmPreTrainedModel:()=>r.EsmPreTrainedModel,EsmTokenizer:()=>a.EsmTokenizer,FFT:()=>h.FFT,FalconForCausalLM:()=>r.FalconForCausalLM,FalconModel:()=>r.FalconModel,FalconPreTrainedModel:()=>r.FalconPreTrainedModel,FalconTokenizer:()=>a.FalconTokenizer,FeatureExtractionPipeline:()=>o.FeatureExtractionPipeline,FeatureExtractor:()=>i.FeatureExtractor,FillMaskPipeline:()=>o.FillMaskPipeline,GLPNFeatureExtractor:()=>i.GLPNFeatureExtractor,GLPNForDepthEstimation:()=>r.GLPNForDepthEstimation,GLPNModel:()=>r.GLPNModel,GLPNPreTrainedModel:()=>r.GLPNPreTrainedModel,GPT2LMHeadModel:()=>r.GPT2LMHeadModel,GPT2Model:()=>r.GPT2Model,GPT2PreTrainedModel:()=>r.GPT2PreTrainedModel,GPT2Tokenizer:()=>a.GPT2Tokenizer,GPTBigCodeForCausalLM:()=>r.GPTBigCodeForCausalLM,GPTBigCodeModel:()=>r.GPTBigCodeModel,GPTBigCodePreTrainedModel:()=>r.GPTBigCodePreTrainedModel,GPTJForCausalLM:()=>r.GPTJForCausalLM,GPTJModel:()=>r.GPTJModel,GPTJPreTrainedModel:()=>r.GPTJPreTrainedModel,GPTNeoForCausalLM:()=>r.GPTNeoForCausalLM,GPTNeoModel:()=>r.GPTNeoModel,GPTNeoPreTrainedModel:()=>r.GPTNeoPreTrainedModel,GPTNeoXForCausalLM:()=>r.GPTNeoXForCausalLM,GPTNeoXModel:()=>r.GPTNeoXModel,GPTNeoXPreTrainedModel:()=>r.GPTNeoXPreTrainedModel,GPTNeoXTokenizer:()=>a.GPTNeoXTokenizer,GemmaTokenizer:()=>a.GemmaTokenizer,Grok1Tokenizer:()=>a.Grok1Tokenizer,HerbertTokenizer:()=>a.HerbertTokenizer,HubertForCTC:()=>r.HubertForCTC,HubertForSequenceClassification:()=>r.HubertForSequenceClassification,HubertModel:()=>r.HubertModel,HubertPreTrainedModel:()=>r.HubertPreTrainedModel,ImageClassificationPipeline:()=>o.ImageClassificationPipeline,ImageFeatureExtractionPipeline:()=>o.ImageFeatureExtractionPipeline,ImageFeatureExtractor:()=>i.ImageFeatureExtractor,ImageMattingOutput:()=>r.ImageMattingOutput,ImageSegmentationPipeline:()=>o.ImageSegmentationPipeline,ImageToImagePipeline:()=>o.ImageToImagePipeline,ImageToTextPipeline:()=>o.ImageToTextPipeline,LlamaForCausalLM:()=>r.LlamaForCausalLM,LlamaModel:()=>r.LlamaModel,LlamaPreTrainedModel:()=>r.LlamaPreTrainedModel,LlamaTokenizer:()=>a.LlamaTokenizer,LongT5ForConditionalGeneration:()=>r.LongT5ForConditionalGeneration,LongT5Model:()=>r.LongT5Model,LongT5PreTrainedModel:()=>r.LongT5PreTrainedModel,M2M100ForConditionalGeneration:()=>r.M2M100ForConditionalGeneration,M2M100Model:()=>r.M2M100Model,M2M100PreTrainedModel:()=>r.M2M100PreTrainedModel,M2M100Tokenizer:()=>a.M2M100Tokenizer,MBart50Tokenizer:()=>a.MBart50Tokenizer,MBartForCausalLM:()=>r.MBartForCausalLM,MBartForConditionalGeneration:()=>r.MBartForConditionalGeneration,MBartForSequenceClassification:()=>r.MBartForSequenceClassification,MBartModel:()=>r.MBartModel,MBartPreTrainedModel:()=>r.MBartPreTrainedModel,MBartTokenizer:()=>a.MBartTokenizer,MPNetForMaskedLM:()=>r.MPNetForMaskedLM,MPNetForQuestionAnswering:()=>r.MPNetForQuestionAnswering,MPNetForSequenceClassification:()=>r.MPNetForSequenceClassification,MPNetForTokenClassification:()=>r.MPNetForTokenClassification,MPNetModel:()=>r.MPNetModel,MPNetPreTrainedModel:()=>r.MPNetPreTrainedModel,MPNetTokenizer:()=>a.MPNetTokenizer,MT5ForConditionalGeneration:()=>r.MT5ForConditionalGeneration,MT5Model:()=>r.MT5Model,MT5PreTrainedModel:()=>r.MT5PreTrainedModel,MarianMTModel:()=>r.MarianMTModel,MarianModel:()=>r.MarianModel,MarianPreTrainedModel:()=>r.MarianPreTrainedModel,MarianTokenizer:()=>a.MarianTokenizer,MaskedLMOutput:()=>r.MaskedLMOutput,MistralForCausalLM:()=>r.MistralForCausalLM,MistralModel:()=>r.MistralModel,MistralPreTrainedModel:()=>r.MistralPreTrainedModel,MobileBertForMaskedLM:()=>r.MobileBertForMaskedLM,MobileBertForQuestionAnswering:()=>r.MobileBertForQuestionAnswering,MobileBertForSequenceClassification:()=>r.MobileBertForSequenceClassification,MobileBertModel:()=>r.MobileBertModel,MobileBertPreTrainedModel:()=>r.MobileBertPreTrainedModel,MobileBertTokenizer:()=>a.MobileBertTokenizer,MobileViTFeatureExtractor:()=>i.MobileViTFeatureExtractor,MobileViTForImageClassification:()=>r.MobileViTForImageClassification,MobileViTModel:()=>r.MobileViTModel,MobileViTPreTrainedModel:()=>r.MobileViTPreTrainedModel,ModelOutput:()=>r.ModelOutput,MptForCausalLM:()=>r.MptForCausalLM,MptModel:()=>r.MptModel,MptPreTrainedModel:()=>r.MptPreTrainedModel,NllbTokenizer:()=>a.NllbTokenizer,NomicBertModel:()=>r.NomicBertModel,NomicBertPreTrainedModel:()=>r.NomicBertPreTrainedModel,NougatImageProcessor:()=>i.NougatImageProcessor,NougatTokenizer:()=>a.NougatTokenizer,OPTForCausalLM:()=>r.OPTForCausalLM,OPTModel:()=>r.OPTModel,OPTPreTrainedModel:()=>r.OPTPreTrainedModel,ObjectDetectionPipeline:()=>o.ObjectDetectionPipeline,OwlViTFeatureExtractor:()=>i.OwlViTFeatureExtractor,OwlViTForObjectDetection:()=>r.OwlViTForObjectDetection,OwlViTModel:()=>r.OwlViTModel,OwlViTPreTrainedModel:()=>r.OwlViTPreTrainedModel,OwlViTProcessor:()=>i.OwlViTProcessor,Owlv2ForObjectDetection:()=>r.Owlv2ForObjectDetection,Owlv2ImageProcessor:()=>i.Owlv2ImageProcessor,Owlv2Model:()=>r.Owlv2Model,Owlv2PreTrainedModel:()=>r.Owlv2PreTrainedModel,PhiForCausalLM:()=>r.PhiForCausalLM,PhiModel:()=>r.PhiModel,PhiPreTrainedModel:()=>r.PhiPreTrainedModel,Pipeline:()=>o.Pipeline,PreTrainedModel:()=>r.PreTrainedModel,PreTrainedTokenizer:()=>a.PreTrainedTokenizer,PretrainedConfig:()=>l.PretrainedConfig,PretrainedMixin:()=>r.PretrainedMixin,Processor:()=>i.Processor,QuestionAnsweringModelOutput:()=>r.QuestionAnsweringModelOutput,QuestionAnsweringPipeline:()=>o.QuestionAnsweringPipeline,Qwen2ForCausalLM:()=>r.Qwen2ForCausalLM,Qwen2Model:()=>r.Qwen2Model,Qwen2PreTrainedModel:()=>r.Qwen2PreTrainedModel,Qwen2Tokenizer:()=>a.Qwen2Tokenizer,RawImage:()=>d.RawImage,ResNetForImageClassification:()=>r.ResNetForImageClassification,ResNetModel:()=>r.ResNetModel,ResNetPreTrainedModel:()=>r.ResNetPreTrainedModel,RoFormerForMaskedLM:()=>r.RoFormerForMaskedLM,RoFormerForQuestionAnswering:()=>r.RoFormerForQuestionAnswering,RoFormerForSequenceClassification:()=>r.RoFormerForSequenceClassification,RoFormerForTokenClassification:()=>r.RoFormerForTokenClassification,RoFormerModel:()=>r.RoFormerModel,RoFormerPreTrainedModel:()=>r.RoFormerPreTrainedModel,RoFormerTokenizer:()=>a.RoFormerTokenizer,RobertaForMaskedLM:()=>r.RobertaForMaskedLM,RobertaForQuestionAnswering:()=>r.RobertaForQuestionAnswering,RobertaForSequenceClassification:()=>r.RobertaForSequenceClassification,RobertaForTokenClassification:()=>r.RobertaForTokenClassification,RobertaModel:()=>r.RobertaModel,RobertaPreTrainedModel:()=>r.RobertaPreTrainedModel,RobertaTokenizer:()=>a.RobertaTokenizer,SamImageProcessor:()=>i.SamImageProcessor,SamImageSegmentationOutput:()=>r.SamImageSegmentationOutput,SamModel:()=>r.SamModel,SamPreTrainedModel:()=>r.SamPreTrainedModel,SamProcessor:()=>i.SamProcessor,SeamlessM4TFeatureExtractor:()=>i.SeamlessM4TFeatureExtractor,SegformerFeatureExtractor:()=>i.SegformerFeatureExtractor,SegformerForImageClassification:()=>r.SegformerForImageClassification,SegformerForSemanticSegmentation:()=>r.SegformerForSemanticSegmentation,SegformerModel:()=>r.SegformerModel,SegformerPreTrainedModel:()=>r.SegformerPreTrainedModel,Seq2SeqLMOutput:()=>r.Seq2SeqLMOutput,SequenceClassifierOutput:()=>r.SequenceClassifierOutput,SiglipImageProcessor:()=>i.SiglipImageProcessor,SiglipModel:()=>r.SiglipModel,SiglipPreTrainedModel:()=>r.SiglipPreTrainedModel,SiglipTextModel:()=>r.SiglipTextModel,SiglipTokenizer:()=>a.SiglipTokenizer,SiglipVisionModel:()=>r.SiglipVisionModel,SpeechT5FeatureExtractor:()=>i.SpeechT5FeatureExtractor,SpeechT5ForSpeechToText:()=>r.SpeechT5ForSpeechToText,SpeechT5ForTextToSpeech:()=>r.SpeechT5ForTextToSpeech,SpeechT5HifiGan:()=>r.SpeechT5HifiGan,SpeechT5Model:()=>r.SpeechT5Model,SpeechT5PreTrainedModel:()=>r.SpeechT5PreTrainedModel,SpeechT5Processor:()=>i.SpeechT5Processor,SpeechT5Tokenizer:()=>a.SpeechT5Tokenizer,SqueezeBertForMaskedLM:()=>r.SqueezeBertForMaskedLM,SqueezeBertForQuestionAnswering:()=>r.SqueezeBertForQuestionAnswering,SqueezeBertForSequenceClassification:()=>r.SqueezeBertForSequenceClassification,SqueezeBertModel:()=>r.SqueezeBertModel,SqueezeBertPreTrainedModel:()=>r.SqueezeBertPreTrainedModel,SqueezeBertTokenizer:()=>a.SqueezeBertTokenizer,StableLmForCausalLM:()=>r.StableLmForCausalLM,StableLmModel:()=>r.StableLmModel,StableLmPreTrainedModel:()=>r.StableLmPreTrainedModel,Starcoder2ForCausalLM:()=>r.Starcoder2ForCausalLM,Starcoder2Model:()=>r.Starcoder2Model,Starcoder2PreTrainedModel:()=>r.Starcoder2PreTrainedModel,SummarizationPipeline:()=>o.SummarizationPipeline,Swin2SRForImageSuperResolution:()=>r.Swin2SRForImageSuperResolution,Swin2SRImageProcessor:()=>i.Swin2SRImageProcessor,Swin2SRModel:()=>r.Swin2SRModel,Swin2SRPreTrainedModel:()=>r.Swin2SRPreTrainedModel,SwinForImageClassification:()=>r.SwinForImageClassification,SwinModel:()=>r.SwinModel,SwinPreTrainedModel:()=>r.SwinPreTrainedModel,T5ForConditionalGeneration:()=>r.T5ForConditionalGeneration,T5Model:()=>r.T5Model,T5PreTrainedModel:()=>r.T5PreTrainedModel,T5Tokenizer:()=>a.T5Tokenizer,TableTransformerForObjectDetection:()=>r.TableTransformerForObjectDetection,TableTransformerModel:()=>r.TableTransformerModel,TableTransformerObjectDetectionOutput:()=>r.TableTransformerObjectDetectionOutput,TableTransformerPreTrainedModel:()=>r.TableTransformerPreTrainedModel,Tensor:()=>u.Tensor,Text2TextGenerationPipeline:()=>o.Text2TextGenerationPipeline,TextClassificationPipeline:()=>o.TextClassificationPipeline,TextGenerationPipeline:()=>o.TextGenerationPipeline,TextToAudioPipeline:()=>o.TextToAudioPipeline,TokenClassificationPipeline:()=>o.TokenClassificationPipeline,TokenClassifierOutput:()=>r.TokenClassifierOutput,TokenizerModel:()=>a.TokenizerModel,TrOCRForCausalLM:()=>r.TrOCRForCausalLM,TrOCRPreTrainedModel:()=>r.TrOCRPreTrainedModel,TranslationPipeline:()=>o.TranslationPipeline,UniSpeechForCTC:()=>r.UniSpeechForCTC,UniSpeechForSequenceClassification:()=>r.UniSpeechForSequenceClassification,UniSpeechModel:()=>r.UniSpeechModel,UniSpeechPreTrainedModel:()=>r.UniSpeechPreTrainedModel,UniSpeechSatForAudioFrameClassification:()=>r.UniSpeechSatForAudioFrameClassification,UniSpeechSatForCTC:()=>r.UniSpeechSatForCTC,UniSpeechSatForSequenceClassification:()=>r.UniSpeechSatForSequenceClassification,UniSpeechSatModel:()=>r.UniSpeechSatModel,UniSpeechSatPreTrainedModel:()=>r.UniSpeechSatPreTrainedModel,ViTFeatureExtractor:()=>i.ViTFeatureExtractor,ViTForImageClassification:()=>r.ViTForImageClassification,ViTImageProcessor:()=>i.ViTImageProcessor,ViTModel:()=>r.ViTModel,ViTPreTrainedModel:()=>r.ViTPreTrainedModel,VisionEncoderDecoderModel:()=>r.VisionEncoderDecoderModel,VitMatteForImageMatting:()=>r.VitMatteForImageMatting,VitMatteImageProcessor:()=>i.VitMatteImageProcessor,VitMattePreTrainedModel:()=>r.VitMattePreTrainedModel,VitsModel:()=>r.VitsModel,VitsModelOutput:()=>r.VitsModelOutput,VitsPreTrainedModel:()=>r.VitsPreTrainedModel,VitsTokenizer:()=>a.VitsTokenizer,Wav2Vec2BertForCTC:()=>r.Wav2Vec2BertForCTC,Wav2Vec2BertForSequenceClassification:()=>r.Wav2Vec2BertForSequenceClassification,Wav2Vec2BertModel:()=>r.Wav2Vec2BertModel,Wav2Vec2BertPreTrainedModel:()=>r.Wav2Vec2BertPreTrainedModel,Wav2Vec2CTCTokenizer:()=>a.Wav2Vec2CTCTokenizer,Wav2Vec2FeatureExtractor:()=>i.Wav2Vec2FeatureExtractor,Wav2Vec2ForAudioFrameClassification:()=>r.Wav2Vec2ForAudioFrameClassification,Wav2Vec2ForCTC:()=>r.Wav2Vec2ForCTC,Wav2Vec2ForSequenceClassification:()=>r.Wav2Vec2ForSequenceClassification,Wav2Vec2Model:()=>r.Wav2Vec2Model,Wav2Vec2PreTrainedModel:()=>r.Wav2Vec2PreTrainedModel,Wav2Vec2ProcessorWithLM:()=>i.Wav2Vec2ProcessorWithLM,WavLMForAudioFrameClassification:()=>r.WavLMForAudioFrameClassification,WavLMForCTC:()=>r.WavLMForCTC,WavLMForSequenceClassification:()=>r.WavLMForSequenceClassification,WavLMForXVector:()=>r.WavLMForXVector,WavLMModel:()=>r.WavLMModel,WavLMPreTrainedModel:()=>r.WavLMPreTrainedModel,WhisperFeatureExtractor:()=>i.WhisperFeatureExtractor,WhisperForConditionalGeneration:()=>r.WhisperForConditionalGeneration,WhisperModel:()=>r.WhisperModel,WhisperPreTrainedModel:()=>r.WhisperPreTrainedModel,WhisperProcessor:()=>i.WhisperProcessor,WhisperTokenizer:()=>a.WhisperTokenizer,XLMForQuestionAnswering:()=>r.XLMForQuestionAnswering,XLMForSequenceClassification:()=>r.XLMForSequenceClassification,XLMForTokenClassification:()=>r.XLMForTokenClassification,XLMModel:()=>r.XLMModel,XLMPreTrainedModel:()=>r.XLMPreTrainedModel,XLMRobertaForMaskedLM:()=>r.XLMRobertaForMaskedLM,XLMRobertaForQuestionAnswering:()=>r.XLMRobertaForQuestionAnswering,XLMRobertaForSequenceClassification:()=>r.XLMRobertaForSequenceClassification,XLMRobertaForTokenClassification:()=>r.XLMRobertaForTokenClassification,XLMRobertaModel:()=>r.XLMRobertaModel,XLMRobertaPreTrainedModel:()=>r.XLMRobertaPreTrainedModel,XLMRobertaTokenizer:()=>a.XLMRobertaTokenizer,XLMTokenizer:()=>a.XLMTokenizer,XLMWithLMHeadModel:()=>r.XLMWithLMHeadModel,XVectorOutput:()=>r.XVectorOutput,YolosFeatureExtractor:()=>i.YolosFeatureExtractor,YolosForObjectDetection:()=>r.YolosForObjectDetection,YolosModel:()=>r.YolosModel,YolosObjectDetectionOutput:()=>r.YolosObjectDetectionOutput,YolosPreTrainedModel:()=>r.YolosPreTrainedModel,ZeroShotAudioClassificationPipeline:()=>o.ZeroShotAudioClassificationPipeline,ZeroShotClassificationPipeline:()=>o.ZeroShotClassificationPipeline,ZeroShotImageClassificationPipeline:()=>o.ZeroShotImageClassificationPipeline,ZeroShotObjectDetectionPipeline:()=>o.ZeroShotObjectDetectionPipeline,bankers_round:()=>h.bankers_round,cat:()=>u.cat,cos_sim:()=>h.cos_sim,dot:()=>h.dot,dynamicTimeWarping:()=>u.dynamicTimeWarping,env:()=>n.env,getTopItems:()=>h.getTopItems,hanning:()=>c.hanning,interpolate:()=>u.interpolate,interpolate_data:()=>h.interpolate_data,layer_norm:()=>u.layer_norm,log_softmax:()=>h.log_softmax,magnitude:()=>h.magnitude,max:()=>h.max,mean:()=>u.mean,mean_pooling:()=>u.mean_pooling,medianFilter:()=>h.medianFilter,mel_filter_bank:()=>c.mel_filter_bank,min:()=>h.min,ones:()=>u.ones,ones_like:()=>u.ones_like,permute:()=>u.permute,permute_data:()=>h.permute_data,pipeline:()=>o.pipeline,read_audio:()=>c.read_audio,round:()=>h.round,softmax:()=>h.softmax,spectrogram:()=>c.spectrogram,stack:()=>u.stack,std_mean:()=>u.std_mean,window_function:()=>c.window_function});var o=s(/*! ./pipelines.js */"./src/pipelines.js"),n=s(/*! ./env.js */"./src/env.js"),r=s(/*! ./models.js */"./src/models.js"),a=s(/*! ./tokenizers.js */"./src/tokenizers.js"),i=s(/*! ./processors.js */"./src/processors.js"),l=s(/*! ./configs.js */"./src/configs.js"),c=s(/*! ./utils/audio.js */"./src/utils/audio.js"),d=s(/*! ./utils/image.js */"./src/utils/image.js"),u=s(/*! ./utils/tensor.js */"./src/utils/tensor.js"),h=s(/*! ./utils/maths.js */"./src/utils/maths.js")},"./src/utils/audio.js":
/*!****************************!*\
  !*** ./src/utils/audio.js ***!
  \****************************/(e,t,s)=>{s.r(t),s.d(t,{hanning:()=>i,mel_filter_bank:()=>h,read_audio:()=>a,spectrogram:()=>_,window_function:()=>m});var o=s(/*! ./hub.js */"./src/utils/hub.js"),n=s(/*! ./maths.js */"./src/utils/maths.js"),r=s(/*! ./core.js */"./src/utils/core.js");async function a(e,t){if("undefined"==typeof AudioContext)throw Error("Unable to load audio from path/URL since `AudioContext` is not available in your environment. Instead, audio data should be passed directly to the pipeline/processor. For more information and some example code, see https://huggingface.co/docs/transformers.js/guides/node-audio-processing.");const s=await(await(0,o.getFile)(e)).arrayBuffer(),n=new AudioContext({sampleRate:t});void 0===t&&console.warn(`No sampling rate provided, using default of ${n.sampleRate}Hz.`);const r=await n.decodeAudioData(s);let a;if(2===r.numberOfChannels){const e=Math.sqrt(2),t=r.getChannelData(0),s=r.getChannelData(1);a=new Float32Array(t.length);for(let o=0;o<r.length;++o)a[o]=e*(t[o]+s[o])/2}else a=r.getChannelData(0);return a}function i(e){if(e<1)return new Float64Array;if(1===e)return new Float64Array([1]);const t=e-1,s=Math.PI/t,o=new Float64Array(e);for(let n=0;n<e;++n){const e=2*n-t;o[n]=.5+.5*Math.cos(s*e)}return o}const l={htk:e=>2595*Math.log10(1+e/700),kaldi:e=>1127*Math.log(1+e/700),slaney:(e,t=1e3,s=15,o=27/Math.log(6.4))=>e>=t?s+Math.log(e/t)*o:3*e/200};function c(e,t="htk"){const s=l[t];if(!s)throw new Error('mel_scale should be one of "htk", "slaney" or "kaldi".');return"number"==typeof e?s(e):e.map((e=>s(e)))}const d={htk:e=>700*(10**(e/2595)-1),kaldi:e=>700*(Math.exp(e/1127)-1),slaney:(e,t=1e3,s=15,o=Math.log(6.4)/27)=>e>=s?t*Math.exp(o*(e-s)):200*e/3};function u(e,t,s){const o=(t-e)/(s-1);return Float64Array.from({length:s},((t,s)=>e+o*s))}function h(e,t,s,o,n,r=null,a="htk",i=!1){if(null!==r&&"slaney"!==r)throw new Error('norm must be one of null or "slaney"');const l=u(c(s,a),c(o,a),t+2);let h,p=function(e,t="htk"){const s=d[t];if(!s)throw new Error('mel_scale should be one of "htk", "slaney" or "kaldi".');return"number"==typeof e?s(e):e.map((e=>s(e)))}(l,a);if(i){const t=n/(2*e);h=c(Float64Array.from({length:e},((e,s)=>s*t)),a),p=l}else h=u(0,Math.floor(n/2),e);const _=function(e,t){const s=Float64Array.from({length:t.length-1},((e,s)=>t[s+1]-t[s])),o=Array.from({length:e.length},(()=>new Array(t.length)));for(let s=0;s<e.length;++s){const n=o[s];for(let o=0;o<t.length;++o)n[o]=t[o]-e[s]}const n=t.length-2,r=Array.from({length:n},(()=>new Array(e.length)));for(let t=0;t<e.length;++t){const e=o[t];for(let o=0;o<n;++o){const n=-e[o]/s[o],a=e[o+2]/s[o+1];r[o][t]=Math.max(0,Math.min(n,a))}}return r}(h,p);if(null!==r&&"slaney"===r)for(let s=0;s<t;++s){const t=_[s],o=2/(p[s+2]-p[s]);for(let s=0;s<e;++s)t[s]*=o}return _}function p(e,t,s,o,r){if(s<=0)throw new Error("reference must be greater than zero");if(o<=0)throw new Error("min_value must be greater than zero");s=Math.max(o,s);const a=Math.log10(s);for(let s=0;s<e.length;++s)e[s]=t*Math.log10(Math.max(o,e[s])-a);if(null!==r){if(r<=0)throw new Error("db_range must be greater than zero");const t=(0,n.max)(e)[0]-r;for(let s=0;s<e.length;++s)e[s]=Math.max(e[s],t)}return e}function _(e,t,s,o,{fft_length:a=null,power:i=1,center:l=!0,pad_mode:c="reflect",onesided:d=!0,preemphasis:u=null,mel_filters:h=null,mel_floor:_=1e-10,log_mel:m=null,reference:f=1,min_value:g=1e-10,db_range:M=null,remove_dc_offset:w=null,max_num_frames:T=null,do_pad:k=!0,transpose:b=!1}={}){const x=t.length;if(null===a&&(a=s),s>a)throw Error(`frame_length (${s}) may not be larger than fft_length (${a})`);if(x!==s)throw new Error(`Length of the window (${x}) must equal frame_length (${s})`);if(o<=0)throw new Error("hop_length must be greater than zero");if(l){if("reflect"!==c)throw new Error(`pad_mode="${c}" not implemented yet.`);const t=Math.floor((a-1)/2)+1;e=function(e,t,s){const o=new e.constructor(e.length+t+s),n=e.length-1;for(let s=0;s<e.length;++s)o[t+s]=e[s];for(let s=1;s<=t;++s)o[t-s]=e[(0,r.calculateReflectOffset)(s,n)];for(let a=1;a<=s;++a)o[n+t+a]=e[(0,r.calculateReflectOffset)(n-a,n)];return o}(e,t,t)}const y=Math.floor(1+Math.floor((e.length-s)/o)),F=d?Math.floor(a/2)+1:a;let C=y,P=y;null!==T&&(T>y?k&&(P=T):P=C=T);const v=new n.FFT(a),S=new Float64Array(a),A=new Float64Array(v.outputBufferSize),L=new Array(C);for(let n=0;n<C;++n){const r=n*o;for(let t=0;t<s;++t)S[t]=e[r+t];if(w){let e=0;for(let t=0;t<s;++t)e+=S[t];const t=e/s;for(let e=0;e<s;++e)S[e]-=t}if(null!==u){for(let e=s-1;e>=1;--e)S[e]-=u*S[e-1];S[0]*=1-u}for(let e=0;e<t.length;++e)S[e]*=t[e];v.realTransform(A,S);const a=new Array(F);for(let e=0;e<a.length;++e){const t=e<<1;a[e]=A[t]**2+A[t+1]**2}L[n]=a}if(null!==i&&2!==i){const e=2/i;for(let t=0;t<L.length;++t){const s=L[t];for(let t=0;t<s.length;++t)s[t]**=e}}const E=h.length,z=new Float32Array(E*P),B=b?[P,E]:[E,P];for(let e=0;e<E;++e){const t=h[e];for(let s=0;s<C;++s){const o=L[s];let n=0;for(let e=0;e<F;++e)n+=t[e]*o[e];z[b?s*E+e:e*C+s]=Math.max(_,n)}}if(null!==i&&null!==m){const e=Math.min(z.length,C*E);switch(m){case"log":for(let t=0;t<e;++t)z[t]=Math.log(z[t]);break;case"log10":for(let t=0;t<e;++t)z[t]=Math.log10(z[t]);break;case"dB":if(1===i)!function(e,t=1,s=1e-5,o=null){p(e,20,t,s,o)}(z,f,g,M);else{if(2!==i)throw new Error(`Cannot use log_mel option '${m}' with power ${i}`);!function(e,t=1,s=1e-10,o=null){p(e,10,t,s,o)}(z,f,g,M)}break;default:throw new Error(`log_mel must be one of null, 'log', 'log10' or 'dB'. Got '${m}'`)}}return{data:z,dims:B}}function m(e,t,{periodic:s=!0,frame_length:o=null,center:n=!0}={}){const r=s?e+1:e;let a;switch(t){case"boxcar":a=new Float64Array(r).fill(1);break;case"hann":case"hann_window":a=i(r);break;case"povey":a=i(r).map((e=>Math.pow(e,.85)));break;default:throw new Error(`Unknown window type ${t}.`)}if(s&&(a=a.subarray(0,e)),null===o)return a;if(e>o)throw new Error(`Length of the window (${e}) may not be larger than frame_length (${o})`);return a}},"./src/utils/core.js":
/*!***************************!*\
  !*** ./src/utils/core.js ***!
  \***************************/(e,t,s)=>{function o(e,t){e&&e(t)}function n(e){return Object.fromEntries(Object.entries(e).map((([e,t])=>[t,e])))}function r(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}s.r(t),s.d(t,{Callable:()=>a,calculateDimensions:()=>d,calculateReflectOffset:()=>_,dispatchCallback:()=>o,escapeRegExp:()=>r,exists:()=>c,isIntegralNumber:()=>l,isTypedArray:()=>i,mergeArrays:()=>h,pop:()=>u,product:()=>p,reverseDictionary:()=>n});const a=class{constructor(){let e=function(...t){return e._call(...t)};return Object.setPrototypeOf(e,new.target.prototype)}_call(...e){throw Error("Must implement _call method in subclass")}};function i(e){return"TypedArray"===e?.prototype?.__proto__?.constructor?.name}function l(e){return Number.isInteger(e)||"bigint"==typeof e}function c(e){return null!=e}function d(e){const t=[];let s=e;for(;Array.isArray(s);)t.push(s.length),s=s[0];return t}function u(e,t,s=void 0){const o=e[t];if(void 0!==o)return delete e[t],o;if(void 0===s)throw Error(`Key ${t} does not exist in object.`);return s}function h(...e){return Array.prototype.concat.apply([],e)}function p(...e){return e.reduce(((e,t)=>e.flatMap((e=>t.map((t=>[e,t]))))))}function _(e,t){return Math.abs((e+t)%(2*t)-t)}},"./src/utils/data-structures.js":
/*!**************************************!*\
  !*** ./src/utils/data-structures.js ***!
  \**************************************/(e,t,s)=>{s.r(t),s.d(t,{CharTrie:()=>n,PriorityQueue:()=>o,TokenLattice:()=>a});class o{constructor(e=((e,t)=>e>t)){this._heap=[],this._comparator=e}get size(){return this._heap.length}isEmpty(){return 0===this.size}peek(){return this._heap[0]}push(...e){return this.extend(e)}extend(e){for(const t of e)this._heap.push(t),this._siftUp();return this.size}pop(){const e=this.peek(),t=this.size-1;return t>0&&this._swap(0,t),this._heap.pop(),this._siftDown(),e}replace(e){const t=this.peek();return this._heap[0]=e,this._siftDown(),t}_parent(e){return(e+1>>>1)-1}_left(e){return 1+(e<<1)}_right(e){return e+1<<1}_greater(e,t){return this._comparator(this._heap[e],this._heap[t])}_swap(e,t){const s=this._heap[e];this._heap[e]=this._heap[t],this._heap[t]=s}_siftUp(){let e=this.size-1;for(;e>0&&this._greater(e,this._parent(e));)this._swap(e,this._parent(e)),e=this._parent(e)}_siftDown(){let e=0;for(;this._left(e)<this.size&&this._greater(this._left(e),e)||this._right(e)<this.size&&this._greater(this._right(e),e);){const t=this._right(e)<this.size&&this._greater(this._right(e),this._left(e))?this._right(e):this._left(e);this._swap(e,t),e=t}}}class n{constructor(){this.root=r.default()}extend(e){for(let t of e)this.push(t)}push(e){let t=this.root;for(let s of e){let e=t.children.get(s);void 0===e&&(e=r.default(),t.children.set(s,e)),t=e}t.isLeaf=!0}*commonPrefixSearch(e){let t=this.root,s="";for(let o=0;o<e.length&&void 0!==t;++o){const n=e[o];s+=n,t=t.children.get(n),void 0!==t&&t.isLeaf&&(yield s)}}}class r{constructor(e,t){this.isLeaf=e,this.children=t}static default(){return new r(!1,new Map)}}class a{constructor(e,t,s){this.sentence=e,this.len=e.length,this.bosTokenId=t,this.eosTokenId=s,this.nodes=[],this.beginNodes=Array.from({length:this.len+1},(()=>[])),this.endNodes=Array.from({length:this.len+1},(()=>[]));const o=new i(this.bosTokenId,0,0,0,0),n=new i(this.eosTokenId,1,this.len,0,0);this.nodes.push(o.clone()),this.nodes.push(n.clone()),this.beginNodes[this.len].push(n),this.endNodes[0].push(o)}insert(e,t,s,o){const n=this.nodes.length,r=new i(o,n,e,t,s);this.beginNodes[e].push(r),this.endNodes[e+t].push(r),this.nodes.push(r)}viterbi(){const e=this.len;let t=0;for(;t<=e;){if(0==this.beginNodes[t].length)return[];for(let e of this.beginNodes[t]){e.prev=null;let s=0,o=null;for(let n of this.endNodes[t]){const t=n.backtraceScore+e.score;(null===o||t>s)&&(o=n.clone(),s=t)}if(null===o)return[];e.prev=o,e.backtraceScore=s}++t}const s=[],o=this.beginNodes[e][0].prev;if(null===o)return[];let n=o.clone();for(;null!==n.prev;){s.push(n.clone());const e=n.clone();n=e.prev.clone()}return s.reverse(),s}piece(e){return this.sentence.slice(e.pos,e.pos+e.length)}tokens(){return this.viterbi().map((e=>this.piece(e)))}tokenIds(){return this.viterbi().map((e=>e.tokenId))}}class i{constructor(e,t,s,o,n){this.tokenId=e,this.nodeId=t,this.pos=s,this.length=o,this.score=n,this.prev=null,this.backtraceScore=0}clone(){const e=new i(this.tokenId,this.nodeId,this.pos,this.length,this.score);return e.prev=this.prev,e.backtraceScore=this.backtraceScore,e}}},"./src/utils/generation.js":
/*!*********************************!*\
  !*** ./src/utils/generation.js ***!
  \*********************************/(e,t,s)=>{s.r(t),s.d(t,{ForceTokensLogitsProcessor:()=>i,ForcedBOSTokenLogitsProcessor:()=>l,ForcedEOSTokenLogitsProcessor:()=>c,GenerationConfig:()=>g,LogitsProcessor:()=>a,LogitsProcessorList:()=>r,MinLengthLogitsProcessor:()=>_,MinNewTokensLengthLogitsProcessor:()=>m,NoBadWordsLogitsProcessor:()=>f,NoRepeatNGramLogitsProcessor:()=>h,RepetitionPenaltyLogitsProcessor:()=>p,Sampler:()=>M,SuppressTokensAtBeginLogitsProcessor:()=>d,WhisperTimeStampLogitsProcessor:()=>u});s(/*! ./tensor.js */"./src/utils/tensor.js");var o=s(/*! ./core.js */"./src/utils/core.js"),n=s(/*! ./maths.js */"./src/utils/maths.js");class r extends o.Callable{constructor(){super(),this.processors=[]}push(e){this.processors.push(e)}extend(e){this.processors.push(...e)}_call(e,t){for(let s of t)this.processors.forEach((t=>t(e,s)))}[Symbol.iterator](){return this.processors.values()}}class a extends o.Callable{_call(e,t){throw Error("`_call` should be implemented in a subclass")}}class i extends a{constructor(e){super(),this.force_token_map=Object.fromEntries(e??[])}_call(e,t){let s=this.force_token_map[e.length];return(0,o.exists)(s)&&(t.data.fill(-1/0),t.data[s]=0),t}}class l extends a{constructor(e){super(),this.bos_token_id=e}_call(e,t){return 1===e.length&&(t.data.fill(-1/0),t.data[this.bos_token_id]=0),t}}class c extends a{constructor(e,t){super(),this.max_length=e,this.forced_eos_token_id=t}_call(e,t){}}class d extends a{constructor(e,t){super(),this.begin_suppress_tokens=e,this.begin_index=t}_call(e,t){if(e.length===this.begin_index)for(let e of this.begin_suppress_tokens)t.data[e]=-1/0;return t}}class u extends a{constructor(e){super(),this.eos_token_id=e.eos_token_id,this.no_timestamps_token_id=e.no_timestamps_token_id,this.timestamp_begin=this.no_timestamps_token_id+1,this.begin_index=(e.forced_decoder_ids||[]).length+2,e.forced_decoder_ids.slice(-1)[0][1]===this.no_timestamps_token_id&&(this.begin_index-=1),this.max_initial_timestamp_index=e.max_initial_timestamp_index}_call(e,t){const s=t.data;if(s[this.no_timestamps_token_id]=-1/0,e.length===this.begin_index-1)return s.fill(-1/0),s[this.timestamp_begin]=0,t;const o=e.slice(this.begin_index),r=o.length>=1&&o[o.length-1]>=this.timestamp_begin,a=o.length<2||o[o.length-2]>=this.timestamp_begin;if(r&&(a?s.subarray(this.timestamp_begin).fill(-1/0):s.subarray(0,this.eos_token_id).fill(-1/0)),e.length===this.begin_index&&null!==this.max_initial_timestamp_index){const e=this.timestamp_begin+this.max_initial_timestamp_index;s.subarray(e+1).fill(-1/0)}const i=(0,n.log_softmax)(s);return Math.log(i.subarray(this.timestamp_begin).map(Math.exp).reduce(((e,t)=>e+t)))>(0,n.max)(i.subarray(0,this.timestamp_begin))[0]&&s.subarray(0,this.timestamp_begin).fill(-1/0),t}}class h extends a{constructor(e){super(),this.no_repeat_ngram_size=e}getNgrams(e){const t=e.length,s=[];for(let o=0;o<t+1-this.no_repeat_ngram_size;++o){const t=[];for(let s=0;s<this.no_repeat_ngram_size;++s)t.push(e[o+s]);s.push(t)}const o=new Map;for(const e of s){const t=e.slice(0,e.length-1),s=JSON.stringify(t),n=o.get(s)??[];n.push(e[e.length-1]),o.set(s,n)}return o}getGeneratedNgrams(e,t){const s=t.slice(t.length+1-this.no_repeat_ngram_size,t.length);return e.get(JSON.stringify(s))??[]}calcBannedNgramTokens(e){const t=[];if(e.length+1<this.no_repeat_ngram_size)return t;{const t=this.getNgrams(e);return this.getGeneratedNgrams(t,e)}}_call(e,t){const s=this.calcBannedNgramTokens(e);for(const e of s)t.data[e]=-1/0;return t}}class p extends a{constructor(e){super(),this.penalty=e}_call(e,t){for(const s of e)t.data[s]<0?t.data[s]*=this.penalty:t.data[s]/=this.penalty;return t}}class _ extends a{constructor(e,t){super(),this.min_length=e,this.eos_token_id=Array.isArray(t)?t:[t]}_call(e,t){if(e.length<this.min_length)for(const e of this.eos_token_id)t.data[e]=-1/0;return t}}class m extends a{constructor(e,t,s){super(),this.prompt_length_to_skip=e,this.min_new_tokens=t,this.eos_token_id=Array.isArray(s)?s:[s]}_call(e,t){if(e.length-this.prompt_length_to_skip<this.min_new_tokens)for(const e of this.eos_token_id)t.data[e]=-1/0;return t}}class f extends a{constructor(e,t){super(),this.bad_words_ids=e,this.eos_token_id=Array.isArray(t)?t:[t]}_call(e,t){for(const s of this.bad_words_ids){let o=!0;for(let t=1;t<=s.length-1&&s.length<e.length;++t)if(s.at(-t-1)!==e.at(-t)){o=!1;break}o&&(t.data[s.at(-1)]=-1/0)}return t}}const g=class{constructor(e={}){this.max_length=e.max_length??20,this.max_new_tokens=e.max_new_tokens??null,this.min_length=e.min_length??0,this.min_new_tokens=e.min_new_tokens??null,this.early_stopping=e.early_stopping??!1,this.max_time=e.max_time??null,this.do_sample=e.do_sample??!1,this.num_beams=e.num_beams??1,this.num_beam_groups=e.num_beam_groups??1,this.penalty_alpha=e.penalty_alpha??null,this.use_cache=e.use_cache??!0,this.temperature=e.temperature??1,this.top_k=e.top_k??50,this.top_p=e.top_p??1,this.typical_p=e.typical_p??1,this.epsilon_cutoff=e.epsilon_cutoff??0,this.eta_cutoff=e.eta_cutoff??0,this.diversity_penalty=e.diversity_penalty??0,this.repetition_penalty=e.repetition_penalty??1,this.encoder_repetition_penalty=e.encoder_repetition_penalty??1,this.length_penalty=e.length_penalty??1,this.no_repeat_ngram_size=e.no_repeat_ngram_size??0,this.bad_words_ids=e.bad_words_ids??null,this.force_words_ids=e.force_words_ids??null,this.renormalize_logits=e.renormalize_logits??!1,this.constraints=e.constraints??null,this.forced_bos_token_id=e.forced_bos_token_id??null,this.forced_eos_token_id=e.forced_eos_token_id??null,this.remove_invalid_values=e.remove_invalid_values??!1,this.exponential_decay_length_penalty=e.exponential_decay_length_penalty??null,this.suppress_tokens=e.suppress_tokens??null,this.begin_suppress_tokens=e.begin_suppress_tokens??null,this.forced_decoder_ids=e.forced_decoder_ids??null,this.num_return_sequences=e.num_return_sequences??1,this.output_attentions=e.output_attentions??!1,this.output_hidden_states=e.output_hidden_states??!1,this.output_scores=e.output_scores??!1,this.return_dict_in_generate=e.return_dict_in_generate??!1,this.pad_token_id=e.pad_token_id??null,this.bos_token_id=e.bos_token_id??null,this.eos_token_id=e.eos_token_id??null,this.encoder_no_repeat_ngram_size=e.encoder_no_repeat_ngram_size??0,this.decoder_start_token_id=e.decoder_start_token_id??null,this.generation_kwargs=e.generation_kwargs??{}}};class M extends o.Callable{constructor(e){super(),this.generation_config=e}_call(e,t=-1){return this.sample(e,t)}sample(e,t){throw Error("sample should be implemented in subclasses.")}getLogits(e,t){let s=e.dims.at(-1),o=e.data;if(-1===t)o=o.slice(-s);else{let e=t*s;o=o.slice(e,e+s)}return this.generation_config.temperature>0&&(o=o.map((e=>e/this.generation_config.temperature))),o}randomSelect(e){let t=e.reduce(((e,t)=>e+t),0),s=Math.random()*t;for(let t=0;t<e.length;++t)if(s-=e[t],s<=0)return t;return 0}static getSampler(e){if(e.do_sample)return new T(e);if(e.num_beams>1)return new k(e);if(e.num_return_sequences>1)throw Error(`num_return_sequences has to be 1 when doing greedy search, but is ${e.num_return_sequences}.`);return new w(e)}}class w extends M{sample(e,t=-1){let s=this.getLogits(e,t);return[[(0,n.max)(s)[1],0]]}}class T extends M{sample(e,t=-1){let s=e.dims.at(-1);this.generation_config.top_k>0&&(s=Math.min(this.generation_config.top_k,s));const o=this.getLogits(e,t),r=(0,n.getTopItems)(o,s),a=(0,n.softmax)(r.map((e=>e[1])));return Array.from({length:this.generation_config.num_beams},(()=>{const e=this.randomSelect(a);return[r[e][0],Math.log(a[e])]}))}}class k extends M{sample(e,t=-1){let s=e.dims.at(-1);this.generation_config.top_k>0&&(s=Math.min(this.generation_config.top_k,s));const o=this.getLogits(e,t),r=(0,n.getTopItems)(o,s),a=(0,n.softmax)(r.map((e=>e[1])));return Array.from({length:this.generation_config.num_beams},((e,t)=>[r[t][0],Math.log(a[t])]))}}},"./src/utils/hub.js":
/*!**************************!*\
  !*** ./src/utils/hub.js ***!
  \**************************/(e,t,s)=>{s.r(t),s.d(t,{getFile:()=>d,getModelFile:()=>p,getModelJSON:()=>_});var o=s(/*! fs */"?7a2c"),n=s(/*! path */"?a42a"),r=s(/*! stream/web */"?e65c"),a=s(/*! ../env.js */"./src/env.js"),i=s(/*! ./core.js */"./src/utils/core.js");globalThis.ReadableStream||(globalThis.ReadableStream=r.ReadableStream);class l{_CONTENT_TYPE_MAP={txt:"text/plain",html:"text/html",css:"text/css",js:"text/javascript",json:"application/json",png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif"};constructor(e){if(this.filePath=e,this.headers=new Headers,this.exists=o.existsSync(e),this.exists){this.status=200,this.statusText="OK";let t=o.statSync(e);this.headers.set("content-length",t.size.toString()),this.updateContentType();let s=this;this.body=new ReadableStream({start(e){s.arrayBuffer().then((t=>{e.enqueue(new Uint8Array(t)),e.close()}))}})}else this.status=404,this.statusText="Not Found",this.body=null}updateContentType(){const e=this.filePath.toString().split(".").pop().toLowerCase();this.headers.set("content-type",this._CONTENT_TYPE_MAP[e]??"application/octet-stream")}clone(){let e=new l(this.filePath);return e.exists=this.exists,e.status=this.status,e.statusText=this.statusText,e.headers=new Headers(this.headers),e}async arrayBuffer(){return(await o.promises.readFile(this.filePath)).buffer}async blob(){const e=await o.promises.readFile(this.filePath);return new Blob([e],{type:this.headers.get("content-type")})}async text(){return await o.promises.readFile(this.filePath,"utf8")}async json(){return JSON.parse(await this.text())}}function c(e,t=null){let s;try{s=new URL(e)}catch(e){return!1}return!(t&&!t.includes(s.hostname))&&("http:"===s.protocol||"https:"===s.protocol)}async function d(e){if(a.env.useFS&&!c(e))return new l(e);if("undefined"!=typeof process&&"node"===process?.release?.name){const t=!!process.env?.TESTING_REMOTELY,s=a.env.version,o=new Headers;o.set("User-Agent",`transformers.js/${s}; is_ci/${t};`);if(c(e,["huggingface.co","hf.co"])){const e=process.env?.HF_TOKEN??process.env?.HF_ACCESS_TOKEN;e&&o.set("Authorization",`Bearer ${e}`)}return fetch(e,{headers:o})}return fetch(e)}const u={400:"Bad request error occurred while trying to load file",401:"Unauthorized access to file",403:"Forbidden access to file",404:"Could not locate file",408:"Request timeout error occurred while trying to load file",500:"Internal server error error occurred while trying to load file",502:"Bad gateway error occurred while trying to load file",503:"Service unavailable error occurred while trying to load file",504:"Gateway timeout error occurred while trying to load file"};class h{constructor(e){this.path=e}async match(e){let t=n.join(this.path,e),s=new l(t);return s.exists?s:void 0}async put(e,t){const s=Buffer.from(await t.arrayBuffer());let r=n.join(this.path,e);try{await o.promises.mkdir(n.dirname(r),{recursive:!0}),await o.promises.writeFile(r,s)}catch(e){console.warn("An error occurred while writing the file to cache:",e)}}}async function p(e,t,s=!0,o={}){if(!a.env.allowLocalModels){if(o.local_files_only)throw Error("Invalid configuration detected: local models are disabled (`env.allowLocalModels=false`) but you have requested to only use local models (`local_files_only=true`).");if(!a.env.allowRemoteModels)throw Error("Invalid configuration detected: both local and remote models are disabled. Fix by setting `env.allowLocalModels` or `env.allowRemoteModels` to `true`.")}let n;if((0,i.dispatchCallback)(o.progress_callback,{status:"initiate",name:e,file:t}),!n&&a.env.useBrowserCache){if("undefined"==typeof caches)throw Error("Browser cache is not available in this environment.");try{n=await caches.open("transformers-cache")}catch(e){console.warn("An error occurred while opening the browser cache:",e)}}if(!n&&a.env.useFSCache&&(n=new h(o.cache_dir??a.env.cacheDir)),!n&&a.env.useCustomCache){if(!a.env.customCache)throw Error("`env.useCustomCache=true`, but `env.customCache` is not defined.");if(!a.env.customCache.match||!a.env.customCache.put)throw new Error("`env.customCache` must be an object which implements the `match` and `put` functions of the Web Cache API. For more information, see https://developer.mozilla.org/en-US/docs/Web/API/Cache");n=a.env.customCache}const r=o.revision??"main";let l,p,_=m(e,t),f=m(a.env.localModelPath,_),g=m(a.env.remoteHost,a.env.remotePathTemplate.replaceAll("{model}",e).replaceAll("{revision}",encodeURIComponent(r)),t),M="main"===r?_:m(e,r,t),w=n instanceof h?M:g,T=!1;n&&(p=await async function(e,...t){for(let s of t)try{let t=await e.match(s);if(t)return t}catch(e){continue}}(n,f,w));const k=void 0!==p;if(void 0===p){if(a.env.allowLocalModels){if(c(_)){if(o.local_files_only)throw new Error(`\`local_files_only=true\`, but attempted to load a remote file from: ${_}.`);if(!a.env.allowRemoteModels)throw new Error(`\`env.allowRemoteModels=false\`, but attempted to load a remote file from: ${_}.`)}else try{p=await d(f),l=f}catch(e){console.warn(`Unable to load from local path "${f}": "${e}"`)}}if(void 0===p||404===p.status){if(o.local_files_only||!a.env.allowRemoteModels){if(s)throw Error(`\`local_files_only=true\` or \`env.allowRemoteModels=false\` and file was not found locally at "${f}".`);return null}if(p=await d(g),200!==p.status)return function(e,t,s){if(!s)return null;const o=u[e]??`Error (${e}) occurred while trying to load file`;throw Error(`${o}: "${t}".`)}(p.status,g,s);l=w}T=n&&"undefined"!=typeof Response&&p instanceof Response&&200===p.status}(0,i.dispatchCallback)(o.progress_callback,{status:"download",name:e,file:t});const b={status:"progress",name:e,file:t};let x;return o.progress_callback?k&&"undefined"!=typeof navigator&&/firefox/i.test(navigator.userAgent)?(x=new Uint8Array(await p.arrayBuffer()),(0,i.dispatchCallback)(o.progress_callback,{...b,progress:100,loaded:x.length,total:x.length})):x=await async function(e,t){const s=e.headers.get("Content-Length");null===s&&console.warn("Unable to determine content-length from response headers. Will expand buffer when needed.");let o=parseInt(s??"0"),n=new Uint8Array(o),r=0;const a=e.body.getReader();async function i(){const{done:e,value:s}=await a.read();if(e)return;let l=r+s.length;if(l>o){o=l;let e=new Uint8Array(o);e.set(n),n=e}n.set(s,r),r=l;return t({progress:r/o*100,loaded:r,total:o}),i()}return await i(),n}(p,(e=>{(0,i.dispatchCallback)(o.progress_callback,{...b,...e})})):x=new Uint8Array(await p.arrayBuffer()),T&&l&&void 0===await n.match(l)&&await n.put(l,new Response(x,{headers:p.headers})).catch((e=>{console.warn(`Unable to add response to browser cache: ${e}.`)})),(0,i.dispatchCallback)(o.progress_callback,{status:"done",name:e,file:t}),x}async function _(e,t,s=!0,o={}){let n=await p(e,t,s,o);if(null===n)return{};let r=new TextDecoder("utf-8").decode(n);return JSON.parse(r)}function m(...e){return(e=e.map(((t,s)=>(s&&(t=t.replace(new RegExp("^/"),"")),s!==e.length-1&&(t=t.replace(new RegExp("/$"),"")),t)))).join("/")}},"./src/utils/image.js":
/*!****************************!*\
  !*** ./src/utils/image.js ***!
  \****************************/(e,t,s)=>{s.r(t),s.d(t,{RawImage:()=>_});var o=s(/*! ./hub.js */"./src/utils/hub.js"),n=s(/*! ../env.js */"./src/env.js"),r=s(/*! ./tensor.js */"./src/utils/tensor.js"),a=s(/*! sharp */"?2b25");const i="undefined"!=typeof self,l=i&&"DedicatedWorkerGlobalScope"===self.constructor.name;let c,d,u;if(i)c=(e,t)=>{if(!self.OffscreenCanvas)throw new Error("OffscreenCanvas not supported by this browser.");return new self.OffscreenCanvas(e,t)},u=self.createImageBitmap,d=self.ImageData;else{if(!a)throw new Error("Unable to load image processing library.");u=async e=>{const t=(await e.metadata()).channels;let{data:s,info:o}=await e.raw().toBuffer({resolveWithObject:!0});const n=new _(new Uint8ClampedArray(s),o.width,o.height,o.channels);return void 0!==t&&t!==o.channels&&n.convert(t),n}}const h={0:"nearest",1:"lanczos",2:"bilinear",3:"bicubic",4:"box",5:"hamming"},p=new Map([["png","image/png"],["jpg","image/jpeg"],["jpeg","image/jpeg"],["gif","image/gif"]]);class _{constructor(e,t,s,o){this.data=e,this.width=t,this.height=s,this.channels=o}get size(){return[this.width,this.height]}static async read(e){if(e instanceof _)return e;if("string"==typeof e||e instanceof URL)return await this.fromURL(e);throw new Error("Unsupported input type: "+typeof e)}static async fromURL(e){let t=await(0,o.getFile)(e);if(200!==t.status)throw new Error(`Unable to read image from "${e}" (${t.status} ${t.statusText})`);let s=await t.blob();return this.fromBlob(s)}static async fromBlob(e){if(i){let t=await u(e);const s=c(t.width,t.height).getContext("2d");return s.drawImage(t,0,0),new this(s.getImageData(0,0,t.width,t.height).data,t.width,t.height,4)}{let t=a(await e.arrayBuffer());return await u(t)}}static fromTensor(e,t="CHW"){if(3!==e.dims.length)throw new Error(`Tensor should have 3 dimensions, but has ${e.dims.length} dimensions.`);if("CHW"===t)e=e.transpose(1,2,0);else if("HWC"!==t)throw new Error(`Unsupported channel format: ${t}`);if(!(e.data instanceof Uint8ClampedArray||e.data instanceof Uint8Array))throw new Error(`Unsupported tensor type: ${e.type}`);switch(e.dims[2]){case 1:case 2:case 3:case 4:return new _(e.data,e.dims[1],e.dims[0],e.dims[2]);default:throw new Error(`Unsupported number of channels: ${e.dims[2]}`)}}grayscale(){if(1===this.channels)return this;let e=new Uint8ClampedArray(this.width*this.height*1);switch(this.channels){case 3:case 4:for(let t=0,s=0;t<this.data.length;t+=this.channels){const o=this.data[t],n=this.data[t+1],r=this.data[t+2];e[s++]=Math.round(.2989*o+.587*n+.114*r)}break;default:throw new Error(`Conversion failed due to unsupported number of channels: ${this.channels}`)}return this._update(e,this.width,this.height,1)}rgb(){if(3===this.channels)return this;let e=new Uint8ClampedArray(this.width*this.height*3);switch(this.channels){case 1:for(let t=0,s=0;t<this.data.length;++t)e[s++]=this.data[t],e[s++]=this.data[t],e[s++]=this.data[t];break;case 4:for(let t=0,s=0;t<this.data.length;t+=4)e[s++]=this.data[t],e[s++]=this.data[t+1],e[s++]=this.data[t+2];break;default:throw new Error(`Conversion failed due to unsupported number of channels: ${this.channels}`)}return this._update(e,this.width,this.height,3)}rgba(){if(4===this.channels)return this;let e=new Uint8ClampedArray(this.width*this.height*4);switch(this.channels){case 1:for(let t=0,s=0;t<this.data.length;++t)e[s++]=this.data[t],e[s++]=this.data[t],e[s++]=this.data[t],e[s++]=255;break;case 3:for(let t=0,s=0;t<this.data.length;t+=3)e[s++]=this.data[t],e[s++]=this.data[t+1],e[s++]=this.data[t+2],e[s++]=255;break;default:throw new Error(`Conversion failed due to unsupported number of channels: ${this.channels}`)}return this._update(e,this.width,this.height,4)}async resize(e,t,{resample:s=2}={}){let o=h[s]??s;if(i){let s=this.channels,o=this.toCanvas();const n=c(e,t).getContext("2d");return n.drawImage(o,0,0,e,t),new _(n.getImageData(0,0,e,t).data,e,t,4).convert(s)}{let s=this.toSharp();switch(o){case"box":case"hamming":"box"!==o&&"hamming"!==o||(console.warn(`Resampling method ${o} is not yet supported. Using bilinear instead.`),o="bilinear");case"nearest":case"bilinear":case"bicubic":s=s.affine([e/this.width,0,0,t/this.height],{interpolator:o});break;case"lanczos":s=s.resize({width:e,height:t,fit:"fill",kernel:"lanczos3"});break;default:throw new Error(`Resampling method ${o} is not supported.`)}return await u(s)}}async pad([e,t,s,o]){if(e=Math.max(e,0),t=Math.max(t,0),s=Math.max(s,0),o=Math.max(o,0),0===e&&0===t&&0===s&&0===o)return this;if(i){let n=this.channels,r=this.toCanvas(),a=this.width+e+t,i=this.height+s+o;const l=c(a,i).getContext("2d");return l.drawImage(r,0,0,this.width,this.height,e,s,a,i),new _(l.getImageData(0,0,a,i).data,a,i,4).convert(n)}{let n=this.toSharp().extend({left:e,right:t,top:s,bottom:o});return await u(n)}}async crop([e,t,s,o]){if(e=Math.max(e,0),t=Math.max(t,0),s=Math.min(s,this.width-1),o=Math.min(o,this.height-1),0===e&&0===t&&s===this.width-1&&o===this.height-1)return this;const n=s-e+1,r=o-t+1;if(i){const s=this.channels,o=this.toCanvas(),a=c(n,r).getContext("2d");a.drawImage(o,e,t,n,r,0,0,n,r);return new _(a.getImageData(0,0,n,r).data,n,r,4).convert(s)}{const s=this.toSharp().extract({left:e,top:t,width:n,height:r});return await u(s)}}async center_crop(e,t){if(this.width===e&&this.height===t)return this;let s=(this.width-e)/2,o=(this.height-t)/2;if(i){let n=this.channels,r=this.toCanvas();const a=c(e,t).getContext("2d");let i=0,l=0,d=0,u=0;return s>=0?i=s:d=-s,o>=0?l=o:u=-o,a.drawImage(r,i,l,e,t,d,u,e,t),new _(a.getImageData(0,0,e,t).data,e,t,4).convert(n)}{let n=this.toSharp();if(s>=0&&o>=0)n=n.extract({left:Math.floor(s),top:Math.floor(o),width:e,height:t});else if(s<=0&&o<=0){let r=Math.floor(-o),a=Math.floor(-s);n=n.extend({top:r,left:a,right:e-this.width-a,bottom:t-this.height-r})}else{let r=[0,0],a=0;o<0?(r[0]=Math.floor(-o),r[1]=t-this.height-r[0]):a=Math.floor(o);let i=[0,0],l=0;s<0?(i[0]=Math.floor(-s),i[1]=e-this.width-i[0]):l=Math.floor(s),n=n.extend({top:r[0],bottom:r[1],left:i[0],right:i[1]}).extract({left:l,top:a,width:e,height:t})}return await u(n)}}async toBlob(e="image/png",t=1){if(!i)throw new Error("toBlob() is only supported in browser environments.");const s=this.toCanvas();return await s.convertToBlob({type:e,quality:t})}toTensor(e="CHW"){let t=new r.Tensor("uint8",new Uint8Array(this.data),[this.height,this.width,this.channels]);if("HWC"===e);else{if("CHW"!==e)throw new Error(`Unsupported channel format: ${e}`);t=t.permute(2,0,1)}return t}toCanvas(){if(!i)throw new Error("toCanvas() is only supported in browser environments.");let e=this.clone().rgba(),t=c(e.width,e.height),s=new d(e.data,e.width,e.height);return t.getContext("2d").putImageData(s,0,0),t}_update(e,t,s,o=null){return this.data=e,this.width=t,this.height=s,null!==o&&(this.channels=o),this}clone(){return new _(this.data.slice(),this.width,this.height,this.channels)}convert(e){if(this.channels===e)return this;switch(e){case 1:this.grayscale();break;case 3:this.rgb();break;case 4:this.rgba();break;default:throw new Error(`Conversion failed due to unsupported number of channels: ${this.channels}`)}return this}async save(e){if(!i){if(n.env.useFS){const t=this.toSharp();return await t.toFile(e)}throw new Error("Unable to save the image because filesystem is disabled in this environment.")}{if(l)throw new Error("Unable to save an image from a Web Worker.");const t=e.split(".").pop().toLowerCase(),s=p.get(t)??"image/png",o=await this.toBlob(s),n=URL.createObjectURL(o),r=document.createElement("a");r.href=n,r.download=e,r.click(),r.remove()}}toSharp(){if(i)throw new Error("toSharp() is only supported in server-side environments.");return a(this.data,{raw:{width:this.width,height:this.height,channels:this.channels}})}}},"./src/utils/maths.js":
/*!****************************!*\
  !*** ./src/utils/maths.js ***!
  \****************************/(e,t,s)=>{function o(e,[t,s,o],[n,r],a="bilinear",i=!1){const l=r/o,c=n/s,d=new e.constructor(n*r*t),u=s*o,h=n*r;for(let a=0;a<n;++a)for(let n=0;n<r;++n){const i=a*r+n,p=(n+.5)/l-.5,_=(a+.5)/c-.5;let m=Math.floor(p),f=Math.floor(_);const g=Math.min(m+1,o-1),M=Math.min(f+1,s-1);m=Math.max(m,0),f=Math.max(f,0);const w=p-m,T=_-f,k=(1-w)*(1-T),b=w*(1-T),x=(1-w)*T,y=w*T,F=f*o,C=M*o,P=F+m,v=F+g,S=C+m,A=C+g;for(let s=0;s<t;++s){const t=s*u;d[s*h+i]=k*e[t+P]+b*e[t+v]+x*e[t+S]+y*e[t+A]}}return d}function n(e,t,s){const o=new Array(s.length),n=new Array(s.length);for(let e=s.length-1,r=1;e>=0;--e)n[e]=r,o[e]=t[s[e]],r*=o[e];const r=s.map(((e,t)=>n[s.indexOf(t)])),a=new e.constructor(e.length);for(let s=0;s<e.length;++s){let o=0;for(let e=t.length-1,n=s;e>=0;--e)o+=n%t[e]*r[e],n=Math.floor(n/t[e]);a[o]=e[s]}return[a,o]}function r(e){const t=h(e)[0],s=e.map((e=>Math.exp(e-t))),o=s.reduce(((e,t)=>e+t),0);return s.map((e=>e/o))}function a(e){return r(e).map((e=>Math.log(e)))}function i(e,t){return e.reduce(((e,s,o)=>e+s*t[o]),0)}function l(e,t=0){return e=Array.from(e).map(((e,t)=>[t,e])).sort(((e,t)=>t[1]-e[1])),null!==t&&t>0&&(e=e.slice(0,t)),e}function c(e,t){return i(e,t)/(d(e)*d(t))}function d(e){return Math.sqrt(e.reduce(((e,t)=>e+t*t),0))}function u(e){if(0===e.length)throw Error("Array must not be empty");let t=e[0],s=0;for(let o=1;o<e.length;++o)e[o]<t&&(t=e[o],s=o);return[t,s]}function h(e){if(0===e.length)throw Error("Array must not be empty");let t=e[0],s=0;for(let o=1;o<e.length;++o)e[o]>t&&(t=e[o],s=o);return[Number(t),s]}function p(e){return e>0&&0==(e&e-1)}s.r(t),s.d(t,{FFT:()=>f,bankers_round:()=>w,cos_sim:()=>c,dot:()=>i,getTopItems:()=>l,interpolate_data:()=>o,log_softmax:()=>a,magnitude:()=>d,max:()=>h,medianFilter:()=>g,min:()=>u,permute_data:()=>n,round:()=>M,softmax:()=>r});class _{constructor(e){if(this.size=0|e,this.size<=1||!p(this.size))throw new Error("FFT size must be a power of two larger than 1");this._csize=e<<1,this.table=new Float64Array(2*this.size);for(let e=0;e<this.table.length;e+=2){const t=Math.PI*e/this.size;this.table[e]=Math.cos(t),this.table[e+1]=-Math.sin(t)}let t=0;for(let e=1;this.size>e;e<<=1)++t;this._width=t%2==0?t-1:t,this._bitrev=new Int32Array(1<<this._width);for(let e=0;e<this._bitrev.length;++e){this._bitrev[e]=0;for(let t=0;t<this._width;t+=2){const s=this._width-t-2;this._bitrev[e]|=(e>>>t&3)<<s}}}createComplexArray(){return new Float64Array(this._csize)}fromComplexArray(e,t){const s=t||new Array(e.length>>>1);for(let t=0;t<e.length;t+=2)s[t>>>1]=e[t];return s}toComplexArray(e,t){const s=t||this.createComplexArray();for(let t=0;t<s.length;t+=2)s[t]=e[t>>>1],s[t+1]=0;return s}completeSpectrum(e){const t=this._csize,s=t>>>1;for(let o=2;o<s;o+=2)e[t-o]=e[o],e[t-o+1]=-e[o+1]}transform(e,t){if(e===t)throw new Error("Input and output buffers must be different");this._transform4(e,t,1)}realTransform(e,t){if(e===t)throw new Error("Input and output buffers must be different");this._realTransform4(e,t,1)}inverseTransform(e,t){if(e===t)throw new Error("Input and output buffers must be different");this._transform4(e,t,-1);for(let t=0;t<e.length;++t)e[t]/=this.size}_transform4(e,t,s){const o=this._csize;let n,r,a=1<<this._width,i=o/a<<1;const l=this._bitrev;if(4===i)for(n=0,r=0;n<o;n+=i,++r){const s=l[r];this._singleTransform2(t,e,n,s,a)}else for(n=0,r=0;n<o;n+=i,++r){const o=l[r];this._singleTransform4(t,e,n,o,a,s)}for(a>>=2;a>=2;a>>=2){i=o/a<<1;const t=i>>>2;for(n=0;n<o;n+=i){const o=n+t-1;for(let r=n,i=0;r<o;r+=2,i+=a){const o=r,n=o+t,a=n+t,l=a+t,c=e[o],d=e[o+1],u=e[n],h=e[n+1],p=e[a],_=e[a+1],m=e[l],f=e[l+1],g=this.table[i],M=s*this.table[i+1],w=u*g-h*M,T=u*M+h*g,k=this.table[2*i],b=s*this.table[2*i+1],x=p*k-_*b,y=p*b+_*k,F=this.table[3*i],C=s*this.table[3*i+1],P=m*F-f*C,v=m*C+f*F,S=c+x,A=d+y,L=c-x,E=d-y,z=w+P,B=T+v,I=s*(w-P),O=s*(T-v);e[o]=S+z,e[o+1]=A+B,e[n]=L+O,e[n+1]=E-I,e[a]=S-z,e[a+1]=A-B,e[l]=L-O,e[l+1]=E+I}}}}_singleTransform2(e,t,s,o,n){const r=e[o],a=e[o+1],i=e[o+n],l=e[o+n+1];t[s]=r+i,t[s+1]=a+l,t[s+2]=r-i,t[s+3]=a-l}_singleTransform4(e,t,s,o,n,r){const a=2*n,i=3*n,l=e[o],c=e[o+1],d=e[o+n],u=e[o+n+1],h=e[o+a],p=e[o+a+1],_=e[o+i],m=e[o+i+1],f=l+h,g=c+p,M=l-h,w=c-p,T=d+_,k=u+m,b=r*(d-_),x=r*(u-m);t[s]=f+T,t[s+1]=g+k,t[s+2]=M+x,t[s+3]=w-b,t[s+4]=f-T,t[s+5]=g-k,t[s+6]=M-x,t[s+7]=w+b}_realTransform4(e,t,s){const o=this._csize;let n,r,a=1<<this._width,i=o/a<<1;const l=this._bitrev;if(4===i)for(n=0,r=0;n<o;n+=i,++r){const s=l[r];this._singleRealTransform2(t,e,n,s>>>1,a>>>1)}else for(n=0,r=0;n<o;n+=i,++r){const o=l[r];this._singleRealTransform4(t,e,n,o>>>1,a>>>1,s)}for(a>>=2;a>=2;a>>=2){i=o/a<<1;const t=i>>>2;for(n=0;n<o;n+=i){const o=n+t-1;for(let r=n,i=0;r<o;r+=2,i+=a){const o=r,n=o+t,a=n+t,l=a+t,c=e[o],d=e[o+1],u=e[n],h=e[n+1],p=e[a],_=e[a+1],m=e[l],f=e[l+1],g=this.table[i],M=s*this.table[i+1],w=u*g-h*M,T=u*M+h*g,k=this.table[2*i],b=s*this.table[2*i+1],x=p*k-_*b,y=p*b+_*k,F=this.table[3*i],C=s*this.table[3*i+1],P=m*F-f*C,v=m*C+f*F,S=c+x,A=d+y,L=c-x,E=d-y,z=w+P,B=T+v,I=s*(w-P),O=s*(T-v);e[o]=S+z,e[o+1]=A+B,e[n]=L+O,e[n+1]=E-I,e[a]=S-z,e[a+1]=A-B,e[l]=L-O,e[l+1]=E+I}}}}_singleRealTransform2(e,t,s,o,n){const r=e[o],a=e[o+n];t[s]=r+a,t[s+1]=0,t[s+2]=r-a,t[s+3]=0}_singleRealTransform4(e,t,s,o,n,r){const a=2*n,i=3*n,l=e[o],c=e[o+n],d=e[o+a],u=e[o+i],h=l+d,p=l-d,_=c+u,m=r*(c-u);t[s]=h+_,t[s+1]=0,t[s+2]=p,t[s+3]=-m,t[s+4]=h-_,t[s+5]=0,t[s+6]=p,t[s+7]=m}}class m{constructor(e){const t=2*(e-1),s=2*(2*e-1),o=2**Math.ceil(Math.log2(s));this.bufferSize=o,this._a=t;const n=new Float64Array(s),r=new Float64Array(o);this._chirpBuffer=new Float64Array(o),this._buffer1=new Float64Array(o),this._buffer2=new Float64Array(o),this._outBuffer1=new Float64Array(o),this._outBuffer2=new Float64Array(o);const a=-2*Math.PI/e,i=Math.cos(a),l=Math.sin(a);for(let t=0;t<s>>1;++t){const s=(t+1-e)**2/2,o=Math.sqrt(i**2+l**2)**s,a=s*Math.atan2(l,i),c=2*t;n[c]=o*Math.cos(a),n[c+1]=o*Math.sin(a),r[c]=n[c],r[c+1]=-n[c+1]}this._slicedChirpBuffer=n.subarray(t,s),this._f=new _(o>>1),this._f.transform(this._chirpBuffer,r)}_transform(e,t,s){const o=this._buffer1,n=this._buffer2,r=this._outBuffer1,a=this._outBuffer2,i=this._chirpBuffer,l=this._slicedChirpBuffer,c=this._a;if(s)for(let e=0;e<l.length;e+=2){const s=e+1,n=t[e>>1];o[e]=n*l[e],o[s]=n*l[s]}else for(let e=0;e<l.length;e+=2){const s=e+1;o[e]=t[e]*l[e]-t[s]*l[s],o[s]=t[e]*l[s]+t[s]*l[e]}this._f.transform(r,o);for(let e=0;e<i.length;e+=2){const t=e+1;n[e]=r[e]*i[e]-r[t]*i[t],n[t]=r[e]*i[t]+r[t]*i[e]}this._f.inverseTransform(a,n);for(let t=0;t<a.length;t+=2){const s=a[t+c],o=a[t+c+1],n=l[t],r=l[t+1];e[t]=s*n-o*r,e[t+1]=s*r+o*n}}transform(e,t){this._transform(e,t,!1)}realTransform(e,t){this._transform(e,t,!0)}}class f{constructor(e){this.fft_length=e,this.isPowerOfTwo=p(e),this.isPowerOfTwo?(this.fft=new _(e),this.outputBufferSize=2*e):(this.fft=new m(e),this.outputBufferSize=this.fft.bufferSize)}realTransform(e,t){this.fft.realTransform(e,t)}transform(e,t){this.fft.transform(e,t)}}function g(e,t){if(t%2==0||t<=0)throw new Error("Window size must be a positive odd number");const s=new e.constructor(e.length),o=new e.constructor(t),n=Math.floor(t/2);for(let t=0;t<e.length;++t){let r=0;for(let s=-n;s<=n;++s){let n=t+s;n<0?n=Math.abs(n):n>=e.length&&(n=2*(e.length-1)-n),o[r++]=e[n]}o.sort(),s[t]=o[n]}return s}function M(e,t){const s=Math.pow(10,t);return Math.round(e*s)/s}function w(e){const t=Math.round(e);return Math.abs(e)%1==.5?t%2==0?t:t-1:t}},"./src/utils/tensor.js":
/*!*****************************!*\
  !*** ./src/utils/tensor.js ***!
  \*****************************/(e,t,s)=>{s.r(t),s.d(t,{Tensor:()=>i,cat:()=>m,dynamicTimeWarping:()=>w,interpolate:()=>c,layer_norm:()=>u,mean:()=>M,mean_pooling:()=>d,ones:()=>T,ones_like:()=>k,permute:()=>l,stack:()=>f,std_mean:()=>g});var o=s(/*! ../backends/onnx.js */"./src/backends/onnx.js"),n=s(/*! ./maths.js */"./src/utils/maths.js");const r=Object.freeze({float32:Float32Array,float64:Float64Array,string:Array,int8:Int8Array,uint8:Uint8Array,int16:Int16Array,uint16:Uint16Array,int32:Int32Array,uint32:Uint32Array,int64:BigInt64Array,uint64:BigUint64Array,bool:Uint8Array}),a=o.ONNX.Tensor;class i{dims;type;data;size;constructor(...e){return e[0]instanceof a?Object.assign(this,e[0]):Object.assign(this,new a(e[0],e[1],e[2])),new Proxy(this,{get:(e,t)=>{if("string"==typeof t){let s=Number(t);if(Number.isInteger(s))return e._getitem(s)}return e[t]},set:(e,t,s)=>e[t]=s})}*[Symbol.iterator](){const[e,...t]=this.dims;if(t.length>0){const s=t.reduce(((e,t)=>e*t));for(let o=0;o<e;++o)yield this._subarray(o,s,t)}else yield*this.data}_getitem(e){const[t,...s]=this.dims;if(e=_(e,t),s.length>0){const t=s.reduce(((e,t)=>e*t));return this._subarray(e,t,s)}return new i(this.type,[this.data[e]],s)}indexOf(e){for(let t=0;t<this.data.length;++t)if(this.data[t]==e)return t;return-1}_subarray(e,t,s){const o=e*t,n=(e+1)*t,r="subarray"in this.data?this.data.subarray(o,n):this.data.slice(o,n);return new i(this.type,r,s)}item(){if(1!==this.data.length)throw new Error(`a Tensor with ${this.data.length} elements cannot be converted to Scalar`);return this.data[0]}tolist(){return function(e,t){const s=e.length,o=t.reduce(((e,t)=>e*t));if(s!==o)throw Error(`cannot reshape array of size ${s} into shape (${t})`);let n=e;for(let e=t.length-1;e>=0;e--)n=n.reduce(((s,o)=>{let n=s[s.length-1];return n.length<t[e]?n.push(o):s.push([o]),s}),[[]]);return n[0]}(this.data,this.dims)}sigmoid(){return this.clone().sigmoid_()}sigmoid_(){for(let e=0;e<this.data.length;++e)this.data[e]=1/(1+Math.exp(-this.data[e]));return this}mul(e){return this.clone().mul_(e)}mul_(e){for(let t=0;t<this.data.length;++t)this.data[t]*=e;return this}add(e){return this.clone().add_(e)}add_(e){for(let t=0;t<this.data.length;++t)this.data[t]+=e;return this}clone(){return new i(this.type,this.data.slice(),this.dims.slice())}slice(...e){let t=[],s=[];for(let o=0;o<this.dims.length;++o){let n=e[o];if(null==n)s.push([0,this.dims[o]]),t.push(this.dims[o]);else if("number"==typeof n)n=_(n,this.dims[o],o),s.push([n,n+1]);else{if(!Array.isArray(n)||2!==n.length)throw new Error(`Invalid slice: ${n}`);{if(n[0]>n[1])throw new Error(`Invalid slice: ${n}`);let e=[Math.max(n[0],0),Math.min(n[1],this.dims[o])];s.push(e),t.push(e[1]-e[0])}}}let o=s.map((([e,t])=>t-e)),n=o.reduce(((e,t)=>e*t)),r=new this.data.constructor(n);const a=this.stride();for(let e=0;e<n;++e){let t=0;for(let n=o.length-1,r=e;n>=0;--n){const e=o[n];t+=(r%e+s[n][0])*a[n],r=Math.floor(r/e)}r[e]=this.data[t]}return new i(this.type,r,t)}permute(...e){return l(this,e)}transpose(...e){return this.permute(...e)}sum(e=null,t=!1){return this.norm(1,e,t)}norm(e="fro",t=null,s=!1){if("fro"===e)e=2;else if("string"==typeof e)throw Error(`Unsupported norm: ${e}`);if(null===t){let t=this.data.reduce(((t,s)=>t+s**e),0)**(1/e);return new i(this.type,[t],[])}t=_(t,this.dims.length);const o=this.dims.slice();o[t]=1;const n=new this.data.constructor(this.data.length/this.dims[t]);for(let s=0;s<this.data.length;++s){let r=0;for(let e=this.dims.length-1,n=s,a=1;e>=0;--e){const s=this.dims[e];if(e!==t){r+=n%s*a,a*=o[e]}n=Math.floor(n/s)}n[r]+=this.data[s]**e}if(1!==e)for(let t=0;t<n.length;++t)n[t]=n[t]**(1/e);return s||o.splice(t,1),new i(this.type,n,o)}normalize_(e=2,t=1){t=_(t,this.dims.length);const s=this.norm(e,t,!0);for(let e=0;e<this.data.length;++e){let o=0;for(let s=this.dims.length-1,n=e,r=1;s>=0;--s){const e=this.dims[s];if(s!==t){o+=n%e*r,r*=this.dims[s]}n=Math.floor(n/e)}this.data[e]/=s.data[o]}return this}normalize(e=2,t=1){return this.clone().normalize_(e,t)}stride(){return function(e){const t=new Array(e.length);for(let s=e.length-1,o=1;s>=0;--s)t[s]=o,o*=e[s];return t}(this.dims)}squeeze(e=null){return new i(this.type,this.data,h(this.dims,e))}squeeze_(e=null){return this.dims=h(this.dims,e),this}unsqueeze(e=null){return new i(this.type,this.data,p(this.dims,e))}unsqueeze_(e=null){return this.dims=p(this.dims,e),this}flatten_(e=0,t=-1){t=(t+this.dims.length)%this.dims.length;let s=this.dims.slice(0,e),o=this.dims.slice(e,t+1),n=this.dims.slice(t+1);return this.dims=[...s,o.reduce(((e,t)=>e*t),1),...n],this}flatten(e=0,t=-1){return this.clone().flatten_(e,t)}view(...e){let t=-1;for(let s=0;s<e.length;++s)if(-1===e[s]){if(-1!==t)throw new Error("Only one dimension can be inferred");t=s}if(-1!==t){const s=e.reduce(((e,s,o)=>o!==t?e*s:e),1);e[t]=this.data.length/s}return new i(this.type,this.data,e)}neg_(){for(let e=0;e<this.data.length;++e)this.data[e]=-this.data[e];return this}neg(){return this.clone().neg_()}clamp_(e,t){for(let s=0;s<this.data.length;++s)this.data[s]=Math.min(Math.max(this.data[s],e),t);return this}clamp(e,t){return this.clone().clamp_(e,t)}round_(){for(let e=0;e<this.data.length;++e)this.data[e]=Math.round(this.data[e]);return this}round(){return this.clone().round_()}to(e){if(this.type===e)return this;if(!r.hasOwnProperty(e))throw new Error(`Unsupported type: ${e}`);return new i(e,r[e].from(this.data),this.dims)}}function l(e,t){const[s,o]=(0,n.permute_data)(e.data,e.dims,t);return new i(e.type,s,o)}function c(e,[t,s],o="bilinear",r=!1){const a=e.dims.at(-3)??1,l=e.dims.at(-2),c=e.dims.at(-1);let d=(0,n.interpolate_data)(e.data,[a,l,c],[t,s],o,r);return new i(e.type,d,[a,t,s])}function d(e,t){let s=[e.dims[0],e.dims[2]],o=new e.data.constructor(s[0]*s[1]),[n,r,a]=e.dims,l=0;for(let s=0;s<n;++s){let n=s*a*r;for(let i=0;i<a;++i){let c=0,d=0,u=s*r,h=n+i;for(let s=0;s<r;++s){let o=Number(t.data[u+s]);d+=o,c+=e.data[h+s*a]*o}let p=c/d;o[l++]=p}}return new i(e.type,o,s)}function u(e,t,{eps:s=1e-5}={}){if(2!==e.dims.length)throw new Error("`layer_norm` currently only supports 2D input.");const[o,n]=e.dims;if(1!==t.length&&t[0]!==n)throw new Error("`normalized_shape` must be a 1D array with shape `[input.dims[1]]`.");const[r,a]=g(e,1,0,!0),l=new e.data.constructor(e.data.length);for(let t=0;t<o;++t){const o=t*n;for(let i=0;i<n;++i){const n=o+i;l[n]=(e.data[n]-a.data[t])/(r.data[t]+s)}}return new i(e.type,l,e.dims)}function h(e,t){return e=e.slice(),null===t?e=e.filter((e=>1!==e)):"number"==typeof t?1===e[t]&&e.splice(t,1):Array.isArray(t)&&(e=e.filter(((e,s)=>1!==e||!t.includes(s)))),e}function p(e,t){return t=_(t,e.length+1),(e=e.slice()).splice(t,0,1),e}function _(e,t,s=null){if(e<-t||e>=t)throw new Error(`IndexError: index ${e} is out of bounds for dimension${null===s?"":" "+s} with size ${t}`);return e<0&&(e=(e%t+t)%t),e}function m(e,t=0){t=_(t,e[0].dims.length);const s=e[0].dims.slice();s[t]=e.reduce(((e,s)=>e+s.dims[t]),0);const o=s.reduce(((e,t)=>e*t),1),n=new e[0].data.constructor(o),r=e[0].type;if(0===t){let t=0;for(let s of e)n.set(s.data,t),t+=s.data.length}else{let o=0;for(let r=0;r<e.length;++r){let a=e[r];for(let e=0;e<a.data.length;++e){let r=0;for(let n=a.dims.length-1,i=e,l=1;n>=0;--n){const e=a.dims[n];let c=i%e;n===t&&(c+=o),r+=c*l,l*=s[n],i=Math.floor(i/e)}n[r]=a.data[e]}o+=a.dims[t]}}return new i(r,n,s)}function f(e,t=0){return m(e.map((e=>e.unsqueeze(t))),t)}function g(e,t=null,s=1,o=!1){if(null===t){const t=e.data.reduce(((e,t)=>e+t),0)/e.data.length,o=Math.sqrt(e.data.reduce(((e,s)=>e+(s-t)**2),0)/(e.data.length-s)),n=new i(e.type,[t],[]);return[new i(e.type,[o],[]),n]}const n=M(e,t=_(t,e.dims.length),o),r=e.dims.slice();r[t]=1;const a=new e.data.constructor(e.data.length/e.dims[t]);for(let s=0;s<e.data.length;++s){let o=0;for(let n=e.dims.length-1,a=s,i=1;n>=0;--n){const s=e.dims[n];if(n!==t){o+=a%s*i,i*=r[n]}a=Math.floor(a/s)}a[o]+=(e.data[s]-n.data[o])**2}for(let o=0;o<a.length;++o)a[o]=Math.sqrt(a[o]/(e.dims[t]-s));o||r.splice(t,1);return[new i(e.type,a,r),n]}function M(e,t=null,s=!1){if(null===t){let t=e.data.reduce(((e,t)=>e+t),0);return new i(e.type,[t/e.data.length],[])}t=_(t,e.dims.length);const o=e.dims.slice();o[t]=1;const n=new e.data.constructor(e.data.length/e.dims[t]);for(let s=0;s<e.data.length;++s){let r=0;for(let n=e.dims.length-1,a=s,i=1;n>=0;--n){const s=e.dims[n];if(n!==t){r+=a%s*i,i*=o[n]}a=Math.floor(a/s)}n[r]+=e.data[s]}if(1!==e.dims[t])for(let s=0;s<n.length;++s)n[s]=n[s]/e.dims[t];return s||o.splice(t,1),new i(e.type,n,o)}function w(e){const[t,s]=e.dims,o=[t+1,s+1],n=new i("float32",new Float32Array(o[0]*o[1]).fill(1/0),o),r=new i("float32",new Float32Array(o[0]*o[1]).fill(-1),o);n[0].data[0]=0;for(let o=1;o<s+1;++o)for(let s=1;s<t+1;++s){const t=n[s-1][o-1].item(),a=n[s-1][o].item(),i=n[s][o-1].item();let l,c;t<a&&t<i?(l=t,c=0):a<t&&a<i?(l=a,c=1):(l=i,c=2),n[s].data[o]=e[s-1][o-1].item()+l,r[s].data[o]=c}let a=t,l=s;r.data.fill(2,0,o[1]);for(let e=0;e<o[0];++e)r[e].data[0]=1;let c=[],d=[];for(;a>0||l>0;){c.push(a-1),d.push(l-1);switch(r[a][l].item()){case 0:--a,--l;break;case 1:--a;break;case 2:--l;break;default:throw new Error(`Internal error in dynamic time warping. Unexpected trace[${a}, ${l}]. Please file a bug report.`)}}return c.reverse(),d.reverse(),[c,d]}function T(e){const t=e.reduce(((e,t)=>e*t),1);return new i("int64",new BigInt64Array(t).fill(1n),e)}function k(e){return T(e.dims)}}},s={};function o(e){var n=s[e];if(void 0!==n)return n.exports;var r=s[e]={exports:{}};return t[e](r,r.exports,o),r.exports}o.d=(e,t)=>{for(var s in t)o.o(t,s)&&!o.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:t[s]})},o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var n=o("./src/transformers.js"),r=n.ASTFeatureExtractor,a=n.ASTForAudioClassification,i=n.ASTModel,l=n.ASTPreTrainedModel,c=n.AlbertForMaskedLM,d=n.AlbertForQuestionAnswering,u=n.AlbertForSequenceClassification,h=n.AlbertModel,p=n.AlbertPreTrainedModel,_=n.AlbertTokenizer,m=n.AudioClassificationPipeline,f=n.AutoConfig,g=n.AutoModel,M=n.AutoModelForAudioClassification,w=n.AutoModelForAudioFrameClassification,T=n.AutoModelForCTC,k=n.AutoModelForCausalLM,b=n.AutoModelForDepthEstimation,x=n.AutoModelForDocumentQuestionAnswering,y=n.AutoModelForImageClassification,F=n.AutoModelForImageFeatureExtraction,C=n.AutoModelForImageMatting,P=n.AutoModelForImageSegmentation,v=n.AutoModelForImageToImage,S=n.AutoModelForMaskGeneration,A=n.AutoModelForMaskedLM,L=n.AutoModelForObjectDetection,E=n.AutoModelForQuestionAnswering,z=n.AutoModelForSemanticSegmentation,B=n.AutoModelForSeq2SeqLM,I=n.AutoModelForSequenceClassification,O=n.AutoModelForSpeechSeq2Seq,D=n.AutoModelForTextToSpectrogram,N=n.AutoModelForTextToWaveform,V=n.AutoModelForTokenClassification,q=n.AutoModelForVision2Seq,j=n.AutoModelForXVector,R=n.AutoModelForZeroShotObjectDetection,G=n.AutoProcessor,W=n.AutoTokenizer,$=n.AutomaticSpeechRecognitionPipeline,U=n.BartForConditionalGeneration,X=n.BartForSequenceClassification,Q=n.BartModel,H=n.BartPretrainedModel,Y=n.BartTokenizer,J=n.BaseModelOutput,Z=n.BeitFeatureExtractor,K=n.BeitForImageClassification,ee=n.BeitModel,te=n.BeitPreTrainedModel,se=n.BertForMaskedLM,oe=n.BertForQuestionAnswering,ne=n.BertForSequenceClassification,re=n.BertForTokenClassification,ae=n.BertModel,ie=n.BertPreTrainedModel,le=n.BertTokenizer,ce=n.BitImageProcessor,de=n.BlenderbotForConditionalGeneration,ue=n.BlenderbotModel,he=n.BlenderbotPreTrainedModel,pe=n.BlenderbotSmallForConditionalGeneration,_e=n.BlenderbotSmallModel,me=n.BlenderbotSmallPreTrainedModel,fe=n.BlenderbotSmallTokenizer,ge=n.BlenderbotTokenizer,Me=n.BloomForCausalLM,we=n.BloomModel,Te=n.BloomPreTrainedModel,ke=n.BloomTokenizer,be=n.CLIPFeatureExtractor,xe=n.CLIPModel,ye=n.CLIPPreTrainedModel,Fe=n.CLIPSegForImageSegmentation,Ce=n.CLIPSegModel,Pe=n.CLIPSegPreTrainedModel,ve=n.CLIPTextModelWithProjection,Se=n.CLIPTokenizer,Ae=n.CLIPVisionModelWithProjection,Le=n.CamembertForMaskedLM,Ee=n.CamembertForQuestionAnswering,ze=n.CamembertForSequenceClassification,Be=n.CamembertForTokenClassification,Ie=n.CamembertModel,Oe=n.CamembertPreTrainedModel,De=n.CamembertTokenizer,Ne=n.CausalLMOutput,Ve=n.CausalLMOutputWithPast,qe=n.ChineseCLIPFeatureExtractor,je=n.ChineseCLIPModel,Re=n.ChineseCLIPPreTrainedModel,Ge=n.ClapAudioModelWithProjection,We=n.ClapFeatureExtractor,$e=n.ClapModel,Ue=n.ClapPreTrainedModel,Xe=n.ClapTextModelWithProjection,Qe=n.CodeGenForCausalLM,He=n.CodeGenModel,Ye=n.CodeGenPreTrainedModel,Je=n.CodeGenTokenizer,Ze=n.CodeLlamaTokenizer,Ke=n.CohereTokenizer,et=n.ConvBertForMaskedLM,tt=n.ConvBertForQuestionAnswering,st=n.ConvBertForSequenceClassification,ot=n.ConvBertForTokenClassification,nt=n.ConvBertModel,rt=n.ConvBertPreTrainedModel,at=n.ConvBertTokenizer,it=n.ConvNextFeatureExtractor,lt=n.ConvNextForImageClassification,ct=n.ConvNextImageProcessor,dt=n.ConvNextModel,ut=n.ConvNextPreTrainedModel,ht=n.ConvNextV2ForImageClassification,pt=n.ConvNextV2Model,_t=n.ConvNextV2PreTrainedModel,mt=n.DPTFeatureExtractor,ft=n.DPTForDepthEstimation,gt=n.DPTImageProcessor,Mt=n.DPTModel,wt=n.DPTPreTrainedModel,Tt=n.DebertaForMaskedLM,kt=n.DebertaForQuestionAnswering,bt=n.DebertaForSequenceClassification,xt=n.DebertaForTokenClassification,yt=n.DebertaModel,Ft=n.DebertaPreTrainedModel,Ct=n.DebertaTokenizer,Pt=n.DebertaV2ForMaskedLM,vt=n.DebertaV2ForQuestionAnswering,St=n.DebertaV2ForSequenceClassification,At=n.DebertaV2ForTokenClassification,Lt=n.DebertaV2Model,Et=n.DebertaV2PreTrainedModel,zt=n.DebertaV2Tokenizer,Bt=n.DeiTFeatureExtractor,It=n.DeiTForImageClassification,Ot=n.DeiTModel,Dt=n.DeiTPreTrainedModel,Nt=n.DepthAnythingForDepthEstimation,Vt=n.DepthAnythingPreTrainedModel,qt=n.DepthEstimationPipeline,jt=n.DetrFeatureExtractor,Rt=n.DetrForObjectDetection,Gt=n.DetrForSegmentation,Wt=n.DetrModel,$t=n.DetrObjectDetectionOutput,Ut=n.DetrPreTrainedModel,Xt=n.DetrSegmentationOutput,Qt=n.Dinov2ForImageClassification,Ht=n.Dinov2Model,Yt=n.Dinov2PreTrainedModel,Jt=n.DistilBertForMaskedLM,Zt=n.DistilBertForQuestionAnswering,Kt=n.DistilBertForSequenceClassification,es=n.DistilBertForTokenClassification,ts=n.DistilBertModel,ss=n.DistilBertPreTrainedModel,os=n.DistilBertTokenizer,ns=n.DocumentQuestionAnsweringPipeline,rs=n.DonutFeatureExtractor,as=n.DonutSwinModel,is=n.DonutSwinPreTrainedModel,ls=n.EfficientNetForImageClassification,cs=n.EfficientNetImageProcessor,ds=n.EfficientNetModel,us=n.EfficientNetPreTrainedModel,hs=n.ElectraForMaskedLM,ps=n.ElectraForQuestionAnswering,_s=n.ElectraForSequenceClassification,ms=n.ElectraForTokenClassification,fs=n.ElectraModel,gs=n.ElectraPreTrainedModel,Ms=n.ElectraTokenizer,ws=n.EsmForMaskedLM,Ts=n.EsmForSequenceClassification,ks=n.EsmForTokenClassification,bs=n.EsmModel,xs=n.EsmPreTrainedModel,ys=n.EsmTokenizer,Fs=n.FFT,Cs=n.FalconForCausalLM,Ps=n.FalconModel,vs=n.FalconPreTrainedModel,Ss=n.FalconTokenizer,As=n.FeatureExtractionPipeline,Ls=n.FeatureExtractor,Es=n.FillMaskPipeline,zs=n.GLPNFeatureExtractor,Bs=n.GLPNForDepthEstimation,Is=n.GLPNModel,Os=n.GLPNPreTrainedModel,Ds=n.GPT2LMHeadModel,Ns=n.GPT2Model,Vs=n.GPT2PreTrainedModel,qs=n.GPT2Tokenizer,js=n.GPTBigCodeForCausalLM,Rs=n.GPTBigCodeModel,Gs=n.GPTBigCodePreTrainedModel,Ws=n.GPTJForCausalLM,$s=n.GPTJModel,Us=n.GPTJPreTrainedModel,Xs=n.GPTNeoForCausalLM,Qs=n.GPTNeoModel,Hs=n.GPTNeoPreTrainedModel,Ys=n.GPTNeoXForCausalLM,Js=n.GPTNeoXModel,Zs=n.GPTNeoXPreTrainedModel,Ks=n.GPTNeoXTokenizer,eo=n.GemmaTokenizer,to=n.Grok1Tokenizer,so=n.HerbertTokenizer,oo=n.HubertForCTC,no=n.HubertForSequenceClassification,ro=n.HubertModel,ao=n.HubertPreTrainedModel,io=n.ImageClassificationPipeline,lo=n.ImageFeatureExtractionPipeline,co=n.ImageFeatureExtractor,uo=n.ImageMattingOutput,ho=n.ImageSegmentationPipeline,po=n.ImageToImagePipeline,_o=n.ImageToTextPipeline,mo=n.LlamaForCausalLM,fo=n.LlamaModel,go=n.LlamaPreTrainedModel,Mo=n.LlamaTokenizer,wo=n.LongT5ForConditionalGeneration,To=n.LongT5Model,ko=n.LongT5PreTrainedModel,bo=n.M2M100ForConditionalGeneration,xo=n.M2M100Model,yo=n.M2M100PreTrainedModel,Fo=n.M2M100Tokenizer,Co=n.MBart50Tokenizer,Po=n.MBartForCausalLM,vo=n.MBartForConditionalGeneration,So=n.MBartForSequenceClassification,Ao=n.MBartModel,Lo=n.MBartPreTrainedModel,Eo=n.MBartTokenizer,zo=n.MPNetForMaskedLM,Bo=n.MPNetForQuestionAnswering,Io=n.MPNetForSequenceClassification,Oo=n.MPNetForTokenClassification,Do=n.MPNetModel,No=n.MPNetPreTrainedModel,Vo=n.MPNetTokenizer,qo=n.MT5ForConditionalGeneration,jo=n.MT5Model,Ro=n.MT5PreTrainedModel,Go=n.MarianMTModel,Wo=n.MarianModel,$o=n.MarianPreTrainedModel,Uo=n.MarianTokenizer,Xo=n.MaskedLMOutput,Qo=n.MistralForCausalLM,Ho=n.MistralModel,Yo=n.MistralPreTrainedModel,Jo=n.MobileBertForMaskedLM,Zo=n.MobileBertForQuestionAnswering,Ko=n.MobileBertForSequenceClassification,en=n.MobileBertModel,tn=n.MobileBertPreTrainedModel,sn=n.MobileBertTokenizer,on=n.MobileViTFeatureExtractor,nn=n.MobileViTForImageClassification,rn=n.MobileViTModel,an=n.MobileViTPreTrainedModel,ln=n.ModelOutput,cn=n.MptForCausalLM,dn=n.MptModel,un=n.MptPreTrainedModel,hn=n.NllbTokenizer,pn=n.NomicBertModel,_n=n.NomicBertPreTrainedModel,mn=n.NougatImageProcessor,fn=n.NougatTokenizer,gn=n.OPTForCausalLM,Mn=n.OPTModel,wn=n.OPTPreTrainedModel,Tn=n.ObjectDetectionPipeline,kn=n.OwlViTFeatureExtractor,bn=n.OwlViTForObjectDetection,xn=n.OwlViTModel,yn=n.OwlViTPreTrainedModel,Fn=n.OwlViTProcessor,Cn=n.Owlv2ForObjectDetection,Pn=n.Owlv2ImageProcessor,vn=n.Owlv2Model,Sn=n.Owlv2PreTrainedModel,An=n.PhiForCausalLM,Ln=n.PhiModel,En=n.PhiPreTrainedModel,zn=n.Pipeline,Bn=n.PreTrainedModel,In=n.PreTrainedTokenizer,On=n.PretrainedConfig,Dn=n.PretrainedMixin,Nn=n.Processor,Vn=n.QuestionAnsweringModelOutput,qn=n.QuestionAnsweringPipeline,jn=n.Qwen2ForCausalLM,Rn=n.Qwen2Model,Gn=n.Qwen2PreTrainedModel,Wn=n.Qwen2Tokenizer,$n=n.RawImage,Un=n.ResNetForImageClassification,Xn=n.ResNetModel,Qn=n.ResNetPreTrainedModel,Hn=n.RoFormerForMaskedLM,Yn=n.RoFormerForQuestionAnswering,Jn=n.RoFormerForSequenceClassification,Zn=n.RoFormerForTokenClassification,Kn=n.RoFormerModel,er=n.RoFormerPreTrainedModel,tr=n.RoFormerTokenizer,sr=n.RobertaForMaskedLM,or=n.RobertaForQuestionAnswering,nr=n.RobertaForSequenceClassification,rr=n.RobertaForTokenClassification,ar=n.RobertaModel,ir=n.RobertaPreTrainedModel,lr=n.RobertaTokenizer,cr=n.SamImageProcessor,dr=n.SamImageSegmentationOutput,ur=n.SamModel,hr=n.SamPreTrainedModel,pr=n.SamProcessor,_r=n.SeamlessM4TFeatureExtractor,mr=n.SegformerFeatureExtractor,fr=n.SegformerForImageClassification,gr=n.SegformerForSemanticSegmentation,Mr=n.SegformerModel,wr=n.SegformerPreTrainedModel,Tr=n.Seq2SeqLMOutput,kr=n.SequenceClassifierOutput,br=n.SiglipImageProcessor,xr=n.SiglipModel,yr=n.SiglipPreTrainedModel,Fr=n.SiglipTextModel,Cr=n.SiglipTokenizer,Pr=n.SiglipVisionModel,vr=n.SpeechT5FeatureExtractor,Sr=n.SpeechT5ForSpeechToText,Ar=n.SpeechT5ForTextToSpeech,Lr=n.SpeechT5HifiGan,Er=n.SpeechT5Model,zr=n.SpeechT5PreTrainedModel,Br=n.SpeechT5Processor,Ir=n.SpeechT5Tokenizer,Or=n.SqueezeBertForMaskedLM,Dr=n.SqueezeBertForQuestionAnswering,Nr=n.SqueezeBertForSequenceClassification,Vr=n.SqueezeBertModel,qr=n.SqueezeBertPreTrainedModel,jr=n.SqueezeBertTokenizer,Rr=n.StableLmForCausalLM,Gr=n.StableLmModel,Wr=n.StableLmPreTrainedModel,$r=n.Starcoder2ForCausalLM,Ur=n.Starcoder2Model,Xr=n.Starcoder2PreTrainedModel,Qr=n.SummarizationPipeline,Hr=n.Swin2SRForImageSuperResolution,Yr=n.Swin2SRImageProcessor,Jr=n.Swin2SRModel,Zr=n.Swin2SRPreTrainedModel,Kr=n.SwinForImageClassification,ea=n.SwinModel,ta=n.SwinPreTrainedModel,sa=n.T5ForConditionalGeneration,oa=n.T5Model,na=n.T5PreTrainedModel,ra=n.T5Tokenizer,aa=n.TableTransformerForObjectDetection,ia=n.TableTransformerModel,la=n.TableTransformerObjectDetectionOutput,ca=n.TableTransformerPreTrainedModel,da=n.Tensor,ua=n.Text2TextGenerationPipeline,ha=n.TextClassificationPipeline,pa=n.TextGenerationPipeline,_a=n.TextToAudioPipeline,ma=n.TokenClassificationPipeline,fa=n.TokenClassifierOutput,ga=n.TokenizerModel,Ma=n.TrOCRForCausalLM,wa=n.TrOCRPreTrainedModel,Ta=n.TranslationPipeline,ka=n.UniSpeechForCTC,ba=n.UniSpeechForSequenceClassification,xa=n.UniSpeechModel,ya=n.UniSpeechPreTrainedModel,Fa=n.UniSpeechSatForAudioFrameClassification,Ca=n.UniSpeechSatForCTC,Pa=n.UniSpeechSatForSequenceClassification,va=n.UniSpeechSatModel,Sa=n.UniSpeechSatPreTrainedModel,Aa=n.ViTFeatureExtractor,La=n.ViTForImageClassification,Ea=n.ViTImageProcessor,za=n.ViTModel,Ba=n.ViTPreTrainedModel,Ia=n.VisionEncoderDecoderModel,Oa=n.VitMatteForImageMatting,Da=n.VitMatteImageProcessor,Na=n.VitMattePreTrainedModel,Va=n.VitsModel,qa=n.VitsModelOutput,ja=n.VitsPreTrainedModel,Ra=n.VitsTokenizer,Ga=n.Wav2Vec2BertForCTC,Wa=n.Wav2Vec2BertForSequenceClassification,$a=n.Wav2Vec2BertModel,Ua=n.Wav2Vec2BertPreTrainedModel,Xa=n.Wav2Vec2CTCTokenizer,Qa=n.Wav2Vec2FeatureExtractor,Ha=n.Wav2Vec2ForAudioFrameClassification,Ya=n.Wav2Vec2ForCTC,Ja=n.Wav2Vec2ForSequenceClassification,Za=n.Wav2Vec2Model,Ka=n.Wav2Vec2PreTrainedModel,ei=n.Wav2Vec2ProcessorWithLM,ti=n.WavLMForAudioFrameClassification,si=n.WavLMForCTC,oi=n.WavLMForSequenceClassification,ni=n.WavLMForXVector,ri=n.WavLMModel,ai=n.WavLMPreTrainedModel,ii=n.WhisperFeatureExtractor,li=n.WhisperForConditionalGeneration,ci=n.WhisperModel,di=n.WhisperPreTrainedModel,ui=n.WhisperProcessor,hi=n.WhisperTokenizer,pi=n.XLMForQuestionAnswering,_i=n.XLMForSequenceClassification,mi=n.XLMForTokenClassification,fi=n.XLMModel,gi=n.XLMPreTrainedModel,Mi=n.XLMRobertaForMaskedLM,wi=n.XLMRobertaForQuestionAnswering,Ti=n.XLMRobertaForSequenceClassification,ki=n.XLMRobertaForTokenClassification,bi=n.XLMRobertaModel,xi=n.XLMRobertaPreTrainedModel,yi=n.XLMRobertaTokenizer,Fi=n.XLMTokenizer,Ci=n.XLMWithLMHeadModel,Pi=n.XVectorOutput,vi=n.YolosFeatureExtractor,Si=n.YolosForObjectDetection,Ai=n.YolosModel,Li=n.YolosObjectDetectionOutput,Ei=n.YolosPreTrainedModel,zi=n.ZeroShotAudioClassificationPipeline,Bi=n.ZeroShotClassificationPipeline,Ii=n.ZeroShotImageClassificationPipeline,Oi=n.ZeroShotObjectDetectionPipeline,Di=n.bankers_round,Ni=n.cat,Vi=n.cos_sim,qi=n.dot,ji=n.dynamicTimeWarping,Ri=n.env,Gi=n.getTopItems,Wi=n.hanning,$i=n.interpolate,Ui=n.interpolate_data,Xi=n.layer_norm,Qi=n.log_softmax,Hi=n.magnitude,Yi=n.max,Ji=n.mean,Zi=n.mean_pooling,Ki=n.medianFilter,el=n.mel_filter_bank,tl=n.min,sl=n.ones,ol=n.ones_like,nl=n.permute,rl=n.permute_data,al=n.pipeline,il=n.read_audio,ll=n.round,cl=n.softmax,dl=n.spectrogram,ul=n.stack,hl=n.std_mean,pl=n.window_function;export{r as ASTFeatureExtractor,a as ASTForAudioClassification,i as ASTModel,l as ASTPreTrainedModel,c as AlbertForMaskedLM,d as AlbertForQuestionAnswering,u as AlbertForSequenceClassification,h as AlbertModel,p as AlbertPreTrainedModel,_ as AlbertTokenizer,m as AudioClassificationPipeline,f as AutoConfig,g as AutoModel,M as AutoModelForAudioClassification,w as AutoModelForAudioFrameClassification,T as AutoModelForCTC,k as AutoModelForCausalLM,b as AutoModelForDepthEstimation,x as AutoModelForDocumentQuestionAnswering,y as AutoModelForImageClassification,F as AutoModelForImageFeatureExtraction,C as AutoModelForImageMatting,P as AutoModelForImageSegmentation,v as AutoModelForImageToImage,S as AutoModelForMaskGeneration,A as AutoModelForMaskedLM,L as AutoModelForObjectDetection,E as AutoModelForQuestionAnswering,z as AutoModelForSemanticSegmentation,B as AutoModelForSeq2SeqLM,I as AutoModelForSequenceClassification,O as AutoModelForSpeechSeq2Seq,D as AutoModelForTextToSpectrogram,N as AutoModelForTextToWaveform,V as AutoModelForTokenClassification,q as AutoModelForVision2Seq,j as AutoModelForXVector,R as AutoModelForZeroShotObjectDetection,G as AutoProcessor,W as AutoTokenizer,$ as AutomaticSpeechRecognitionPipeline,U as BartForConditionalGeneration,X as BartForSequenceClassification,Q as BartModel,H as BartPretrainedModel,Y as BartTokenizer,J as BaseModelOutput,Z as BeitFeatureExtractor,K as BeitForImageClassification,ee as BeitModel,te as BeitPreTrainedModel,se as BertForMaskedLM,oe as BertForQuestionAnswering,ne as BertForSequenceClassification,re as BertForTokenClassification,ae as BertModel,ie as BertPreTrainedModel,le as BertTokenizer,ce as BitImageProcessor,de as BlenderbotForConditionalGeneration,ue as BlenderbotModel,he as BlenderbotPreTrainedModel,pe as BlenderbotSmallForConditionalGeneration,_e as BlenderbotSmallModel,me as BlenderbotSmallPreTrainedModel,fe as BlenderbotSmallTokenizer,ge as BlenderbotTokenizer,Me as BloomForCausalLM,we as BloomModel,Te as BloomPreTrainedModel,ke as BloomTokenizer,be as CLIPFeatureExtractor,xe as CLIPModel,ye as CLIPPreTrainedModel,Fe as CLIPSegForImageSegmentation,Ce as CLIPSegModel,Pe as CLIPSegPreTrainedModel,ve as CLIPTextModelWithProjection,Se as CLIPTokenizer,Ae as CLIPVisionModelWithProjection,Le as CamembertForMaskedLM,Ee as CamembertForQuestionAnswering,ze as CamembertForSequenceClassification,Be as CamembertForTokenClassification,Ie as CamembertModel,Oe as CamembertPreTrainedModel,De as CamembertTokenizer,Ne as CausalLMOutput,Ve as CausalLMOutputWithPast,qe as ChineseCLIPFeatureExtractor,je as ChineseCLIPModel,Re as ChineseCLIPPreTrainedModel,Ge as ClapAudioModelWithProjection,We as ClapFeatureExtractor,$e as ClapModel,Ue as ClapPreTrainedModel,Xe as ClapTextModelWithProjection,Qe as CodeGenForCausalLM,He as CodeGenModel,Ye as CodeGenPreTrainedModel,Je as CodeGenTokenizer,Ze as CodeLlamaTokenizer,Ke as CohereTokenizer,et as ConvBertForMaskedLM,tt as ConvBertForQuestionAnswering,st as ConvBertForSequenceClassification,ot as ConvBertForTokenClassification,nt as ConvBertModel,rt as ConvBertPreTrainedModel,at as ConvBertTokenizer,it as ConvNextFeatureExtractor,lt as ConvNextForImageClassification,ct as ConvNextImageProcessor,dt as ConvNextModel,ut as ConvNextPreTrainedModel,ht as ConvNextV2ForImageClassification,pt as ConvNextV2Model,_t as ConvNextV2PreTrainedModel,mt as DPTFeatureExtractor,ft as DPTForDepthEstimation,gt as DPTImageProcessor,Mt as DPTModel,wt as DPTPreTrainedModel,Tt as DebertaForMaskedLM,kt as DebertaForQuestionAnswering,bt as DebertaForSequenceClassification,xt as DebertaForTokenClassification,yt as DebertaModel,Ft as DebertaPreTrainedModel,Ct as DebertaTokenizer,Pt as DebertaV2ForMaskedLM,vt as DebertaV2ForQuestionAnswering,St as DebertaV2ForSequenceClassification,At as DebertaV2ForTokenClassification,Lt as DebertaV2Model,Et as DebertaV2PreTrainedModel,zt as DebertaV2Tokenizer,Bt as DeiTFeatureExtractor,It as DeiTForImageClassification,Ot as DeiTModel,Dt as DeiTPreTrainedModel,Nt as DepthAnythingForDepthEstimation,Vt as DepthAnythingPreTrainedModel,qt as DepthEstimationPipeline,jt as DetrFeatureExtractor,Rt as DetrForObjectDetection,Gt as DetrForSegmentation,Wt as DetrModel,$t as DetrObjectDetectionOutput,Ut as DetrPreTrainedModel,Xt as DetrSegmentationOutput,Qt as Dinov2ForImageClassification,Ht as Dinov2Model,Yt as Dinov2PreTrainedModel,Jt as DistilBertForMaskedLM,Zt as DistilBertForQuestionAnswering,Kt as DistilBertForSequenceClassification,es as DistilBertForTokenClassification,ts as DistilBertModel,ss as DistilBertPreTrainedModel,os as DistilBertTokenizer,ns as DocumentQuestionAnsweringPipeline,rs as DonutFeatureExtractor,as as DonutSwinModel,is as DonutSwinPreTrainedModel,ls as EfficientNetForImageClassification,cs as EfficientNetImageProcessor,ds as EfficientNetModel,us as EfficientNetPreTrainedModel,hs as ElectraForMaskedLM,ps as ElectraForQuestionAnswering,_s as ElectraForSequenceClassification,ms as ElectraForTokenClassification,fs as ElectraModel,gs as ElectraPreTrainedModel,Ms as ElectraTokenizer,ws as EsmForMaskedLM,Ts as EsmForSequenceClassification,ks as EsmForTokenClassification,bs as EsmModel,xs as EsmPreTrainedModel,ys as EsmTokenizer,Fs as FFT,Cs as FalconForCausalLM,Ps as FalconModel,vs as FalconPreTrainedModel,Ss as FalconTokenizer,As as FeatureExtractionPipeline,Ls as FeatureExtractor,Es as FillMaskPipeline,zs as GLPNFeatureExtractor,Bs as GLPNForDepthEstimation,Is as GLPNModel,Os as GLPNPreTrainedModel,Ds as GPT2LMHeadModel,Ns as GPT2Model,Vs as GPT2PreTrainedModel,qs as GPT2Tokenizer,js as GPTBigCodeForCausalLM,Rs as GPTBigCodeModel,Gs as GPTBigCodePreTrainedModel,Ws as GPTJForCausalLM,$s as GPTJModel,Us as GPTJPreTrainedModel,Xs as GPTNeoForCausalLM,Qs as GPTNeoModel,Hs as GPTNeoPreTrainedModel,Ys as GPTNeoXForCausalLM,Js as GPTNeoXModel,Zs as GPTNeoXPreTrainedModel,Ks as GPTNeoXTokenizer,eo as GemmaTokenizer,to as Grok1Tokenizer,so as HerbertTokenizer,oo as HubertForCTC,no as HubertForSequenceClassification,ro as HubertModel,ao as HubertPreTrainedModel,io as ImageClassificationPipeline,lo as ImageFeatureExtractionPipeline,co as ImageFeatureExtractor,uo as ImageMattingOutput,ho as ImageSegmentationPipeline,po as ImageToImagePipeline,_o as ImageToTextPipeline,mo as LlamaForCausalLM,fo as LlamaModel,go as LlamaPreTrainedModel,Mo as LlamaTokenizer,wo as LongT5ForConditionalGeneration,To as LongT5Model,ko as LongT5PreTrainedModel,bo as M2M100ForConditionalGeneration,xo as M2M100Model,yo as M2M100PreTrainedModel,Fo as M2M100Tokenizer,Co as MBart50Tokenizer,Po as MBartForCausalLM,vo as MBartForConditionalGeneration,So as MBartForSequenceClassification,Ao as MBartModel,Lo as MBartPreTrainedModel,Eo as MBartTokenizer,zo as MPNetForMaskedLM,Bo as MPNetForQuestionAnswering,Io as MPNetForSequenceClassification,Oo as MPNetForTokenClassification,Do as MPNetModel,No as MPNetPreTrainedModel,Vo as MPNetTokenizer,qo as MT5ForConditionalGeneration,jo as MT5Model,Ro as MT5PreTrainedModel,Go as MarianMTModel,Wo as MarianModel,$o as MarianPreTrainedModel,Uo as MarianTokenizer,Xo as MaskedLMOutput,Qo as MistralForCausalLM,Ho as MistralModel,Yo as MistralPreTrainedModel,Jo as MobileBertForMaskedLM,Zo as MobileBertForQuestionAnswering,Ko as MobileBertForSequenceClassification,en as MobileBertModel,tn as MobileBertPreTrainedModel,sn as MobileBertTokenizer,on as MobileViTFeatureExtractor,nn as MobileViTForImageClassification,rn as MobileViTModel,an as MobileViTPreTrainedModel,ln as ModelOutput,cn as MptForCausalLM,dn as MptModel,un as MptPreTrainedModel,hn as NllbTokenizer,pn as NomicBertModel,_n as NomicBertPreTrainedModel,mn as NougatImageProcessor,fn as NougatTokenizer,gn as OPTForCausalLM,Mn as OPTModel,wn as OPTPreTrainedModel,Tn as ObjectDetectionPipeline,kn as OwlViTFeatureExtractor,bn as OwlViTForObjectDetection,xn as OwlViTModel,yn as OwlViTPreTrainedModel,Fn as OwlViTProcessor,Cn as Owlv2ForObjectDetection,Pn as Owlv2ImageProcessor,vn as Owlv2Model,Sn as Owlv2PreTrainedModel,An as PhiForCausalLM,Ln as PhiModel,En as PhiPreTrainedModel,zn as Pipeline,Bn as PreTrainedModel,In as PreTrainedTokenizer,On as PretrainedConfig,Dn as PretrainedMixin,Nn as Processor,Vn as QuestionAnsweringModelOutput,qn as QuestionAnsweringPipeline,jn as Qwen2ForCausalLM,Rn as Qwen2Model,Gn as Qwen2PreTrainedModel,Wn as Qwen2Tokenizer,$n as RawImage,Un as ResNetForImageClassification,Xn as ResNetModel,Qn as ResNetPreTrainedModel,Hn as RoFormerForMaskedLM,Yn as RoFormerForQuestionAnswering,Jn as RoFormerForSequenceClassification,Zn as RoFormerForTokenClassification,Kn as RoFormerModel,er as RoFormerPreTrainedModel,tr as RoFormerTokenizer,sr as RobertaForMaskedLM,or as RobertaForQuestionAnswering,nr as RobertaForSequenceClassification,rr as RobertaForTokenClassification,ar as RobertaModel,ir as RobertaPreTrainedModel,lr as RobertaTokenizer,cr as SamImageProcessor,dr as SamImageSegmentationOutput,ur as SamModel,hr as SamPreTrainedModel,pr as SamProcessor,_r as SeamlessM4TFeatureExtractor,mr as SegformerFeatureExtractor,fr as SegformerForImageClassification,gr as SegformerForSemanticSegmentation,Mr as SegformerModel,wr as SegformerPreTrainedModel,Tr as Seq2SeqLMOutput,kr as SequenceClassifierOutput,br as SiglipImageProcessor,xr as SiglipModel,yr as SiglipPreTrainedModel,Fr as SiglipTextModel,Cr as SiglipTokenizer,Pr as SiglipVisionModel,vr as SpeechT5FeatureExtractor,Sr as SpeechT5ForSpeechToText,Ar as SpeechT5ForTextToSpeech,Lr as SpeechT5HifiGan,Er as SpeechT5Model,zr as SpeechT5PreTrainedModel,Br as SpeechT5Processor,Ir as SpeechT5Tokenizer,Or as SqueezeBertForMaskedLM,Dr as SqueezeBertForQuestionAnswering,Nr as SqueezeBertForSequenceClassification,Vr as SqueezeBertModel,qr as SqueezeBertPreTrainedModel,jr as SqueezeBertTokenizer,Rr as StableLmForCausalLM,Gr as StableLmModel,Wr as StableLmPreTrainedModel,$r as Starcoder2ForCausalLM,Ur as Starcoder2Model,Xr as Starcoder2PreTrainedModel,Qr as SummarizationPipeline,Hr as Swin2SRForImageSuperResolution,Yr as Swin2SRImageProcessor,Jr as Swin2SRModel,Zr as Swin2SRPreTrainedModel,Kr as SwinForImageClassification,ea as SwinModel,ta as SwinPreTrainedModel,sa as T5ForConditionalGeneration,oa as T5Model,na as T5PreTrainedModel,ra as T5Tokenizer,aa as TableTransformerForObjectDetection,ia as TableTransformerModel,la as TableTransformerObjectDetectionOutput,ca as TableTransformerPreTrainedModel,da as Tensor,ua as Text2TextGenerationPipeline,ha as TextClassificationPipeline,pa as TextGenerationPipeline,_a as TextToAudioPipeline,ma as TokenClassificationPipeline,fa as TokenClassifierOutput,ga as TokenizerModel,Ma as TrOCRForCausalLM,wa as TrOCRPreTrainedModel,Ta as TranslationPipeline,ka as UniSpeechForCTC,ba as UniSpeechForSequenceClassification,xa as UniSpeechModel,ya as UniSpeechPreTrainedModel,Fa as UniSpeechSatForAudioFrameClassification,Ca as UniSpeechSatForCTC,Pa as UniSpeechSatForSequenceClassification,va as UniSpeechSatModel,Sa as UniSpeechSatPreTrainedModel,Aa as ViTFeatureExtractor,La as ViTForImageClassification,Ea as ViTImageProcessor,za as ViTModel,Ba as ViTPreTrainedModel,Ia as VisionEncoderDecoderModel,Oa as VitMatteForImageMatting,Da as VitMatteImageProcessor,Na as VitMattePreTrainedModel,Va as VitsModel,qa as VitsModelOutput,ja as VitsPreTrainedModel,Ra as VitsTokenizer,Ga as Wav2Vec2BertForCTC,Wa as Wav2Vec2BertForSequenceClassification,$a as Wav2Vec2BertModel,Ua as Wav2Vec2BertPreTrainedModel,Xa as Wav2Vec2CTCTokenizer,Qa as Wav2Vec2FeatureExtractor,Ha as Wav2Vec2ForAudioFrameClassification,Ya as Wav2Vec2ForCTC,Ja as Wav2Vec2ForSequenceClassification,Za as Wav2Vec2Model,Ka as Wav2Vec2PreTrainedModel,ei as Wav2Vec2ProcessorWithLM,ti as WavLMForAudioFrameClassification,si as WavLMForCTC,oi as WavLMForSequenceClassification,ni as WavLMForXVector,ri as WavLMModel,ai as WavLMPreTrainedModel,ii as WhisperFeatureExtractor,li as WhisperForConditionalGeneration,ci as WhisperModel,di as WhisperPreTrainedModel,ui as WhisperProcessor,hi as WhisperTokenizer,pi as XLMForQuestionAnswering,_i as XLMForSequenceClassification,mi as XLMForTokenClassification,fi as XLMModel,gi as XLMPreTrainedModel,Mi as XLMRobertaForMaskedLM,wi as XLMRobertaForQuestionAnswering,Ti as XLMRobertaForSequenceClassification,ki as XLMRobertaForTokenClassification,bi as XLMRobertaModel,xi as XLMRobertaPreTrainedModel,yi as XLMRobertaTokenizer,Fi as XLMTokenizer,Ci as XLMWithLMHeadModel,Pi as XVectorOutput,vi as YolosFeatureExtractor,Si as YolosForObjectDetection,Ai as YolosModel,Li as YolosObjectDetectionOutput,Ei as YolosPreTrainedModel,zi as ZeroShotAudioClassificationPipeline,Bi as ZeroShotClassificationPipeline,Ii as ZeroShotImageClassificationPipeline,Oi as ZeroShotObjectDetectionPipeline,Di as bankers_round,Ni as cat,Vi as cos_sim,qi as dot,ji as dynamicTimeWarping,Ri as env,Gi as getTopItems,Wi as hanning,$i as interpolate,Ui as interpolate_data,Xi as layer_norm,Qi as log_softmax,Hi as magnitude,Yi as max,Ji as mean,Zi as mean_pooling,Ki as medianFilter,el as mel_filter_bank,tl as min,sl as ones,ol as ones_like,nl as permute,rl as permute_data,al as pipeline,il as read_audio,ll as round,cl as softmax,dl as spectrogram,ul as stack,hl as std_mean,pl as window_function};
//# sourceMappingURL=transformers.min.js.mapPK
!<.D--*chrome/toolkit/content/global/mozilla.html<!DOCTYPE html>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<html>
  <head>
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src 'none'; style-src chrome:; object-src 'none'"
    />
    <meta charset="utf-8" />
    <title data-l10n-id="about-mozilla-title-6-27"></title>
    <link rel="stylesheet" href="chrome://global/content/aboutMozilla.css" />
    <link rel="localization" href="toolkit/about/aboutMozilla.ftl" />
  </head>

  <body>
    <section>
      <p id="moztext" data-l10n-id="about-mozilla-quote-6-27"></p>
      <p id="from" data-l10n-id="about-mozilla-from-6-27"></p>
    </section>
  </body>
</html>
PK
!<�Ѥ�K/K/<chrome/toolkit/content/global/neterror/aboutNetErrorCodes.jsconst KNOWN_ERROR_MESSAGE_IDS = new Set([
  "psmerr-ssl-disabled",
  "psmerr-ssl2-disabled",
  "psmerr-hostreusedissuerandserial",
  "ssl-error-export-only-server",
  "ssl-error-us-only-server",
  "ssl-error-no-cypher-overlap",
  "ssl-error-no-certificate",
  "ssl-error-bad-certificate",
  "ssl-error-bad-client",
  "ssl-error-bad-server",
  "ssl-error-unsupported-certificate-type",
  "ssl-error-unsupported-version",
  "ssl-error-wrong-certificate",
  "ssl-error-bad-cert-domain",
  "ssl-error-post-warning",
  "ssl-error-ssl2-disabled",
  "ssl-error-bad-mac-read",
  "ssl-error-bad-mac-alert",
  "ssl-error-bad-cert-alert",
  "ssl-error-revoked-cert-alert",
  "ssl-error-expired-cert-alert",
  "ssl-error-ssl-disabled",
  "ssl-error-fortezza-pqg",
  "ssl-error-unknown-cipher-suite",
  "ssl-error-no-ciphers-supported",
  "ssl-error-bad-block-padding",
  "ssl-error-rx-record-too-long",
  "ssl-error-tx-record-too-long",
  "ssl-error-rx-malformed-hello-request",
  "ssl-error-rx-malformed-client-hello",
  "ssl-error-rx-malformed-server-hello",
  "ssl-error-rx-malformed-certificate",
  "ssl-error-rx-malformed-server-key-exch",
  "ssl-error-rx-malformed-cert-request",
  "ssl-error-rx-malformed-hello-done",
  "ssl-error-rx-malformed-cert-verify",
  "ssl-error-rx-malformed-client-key-exch",
  "ssl-error-rx-malformed-finished",
  "ssl-error-rx-malformed-change-cipher",
  "ssl-error-rx-malformed-alert",
  "ssl-error-rx-malformed-handshake",
  "ssl-error-rx-malformed-application-data",
  "ssl-error-rx-unexpected-hello-request",
  "ssl-error-rx-unexpected-client-hello",
  "ssl-error-rx-unexpected-server-hello",
  "ssl-error-rx-unexpected-certificate",
  "ssl-error-rx-unexpected-server-key-exch",
  "ssl-error-rx-unexpected-cert-request",
  "ssl-error-rx-unexpected-hello-done",
  "ssl-error-rx-unexpected-cert-verify",
  "ssl-error-rx-unexpected-client-key-exch",
  "ssl-error-rx-unexpected-finished",
  "ssl-error-rx-unexpected-change-cipher",
  "ssl-error-rx-unexpected-alert",
  "ssl-error-rx-unexpected-handshake",
  "ssl-error-rx-unexpected-application-data",
  "ssl-error-rx-unknown-record-type",
  "ssl-error-rx-unknown-handshake",
  "ssl-error-rx-unknown-alert",
  "ssl-error-close-notify-alert",
  "ssl-error-handshake-unexpected-alert",
  "ssl-error-decompression-failure-alert",
  "ssl-error-handshake-failure-alert",
  "ssl-error-illegal-parameter-alert",
  "ssl-error-unsupported-cert-alert",
  "ssl-error-certificate-unknown-alert",
  "ssl-error-generate-random-failure",
  "ssl-error-sign-hashes-failure",
  "ssl-error-extract-public-key-failure",
  "ssl-error-server-key-exchange-failure",
  "ssl-error-client-key-exchange-failure",
  "ssl-error-encryption-failure",
  "ssl-error-decryption-failure",
  "ssl-error-socket-write-failure",
  "ssl-error-md5-digest-failure",
  "ssl-error-sha-digest-failure",
  "ssl-error-mac-computation-failure",
  "ssl-error-sym-key-context-failure",
  "ssl-error-sym-key-unwrap-failure",
  "ssl-error-pub-key-size-limit-exceeded",
  "ssl-error-iv-param-failure",
  "ssl-error-init-cipher-suite-failure",
  "ssl-error-session-key-gen-failure",
  "ssl-error-no-server-key-for-alg",
  "ssl-error-token-insertion-removal",
  "ssl-error-token-slot-not-found",
  "ssl-error-no-compression-overlap",
  "ssl-error-handshake-not-completed",
  "ssl-error-bad-handshake-hash-value",
  "ssl-error-cert-kea-mismatch",
  "ssl-error-no-trusted-ssl-client-ca",
  "ssl-error-session-not-found",
  "ssl-error-decryption-failed-alert",
  "ssl-error-record-overflow-alert",
  "ssl-error-unknown-ca-alert",
  "ssl-error-access-denied-alert",
  "ssl-error-decode-error-alert",
  "ssl-error-decrypt-error-alert",
  "ssl-error-export-restriction-alert",
  "ssl-error-protocol-version-alert",
  "ssl-error-insufficient-security-alert",
  "ssl-error-internal-error-alert",
  "ssl-error-user-canceled-alert",
  "ssl-error-no-renegotiation-alert",
  "ssl-error-server-cache-not-configured",
  "ssl-error-unsupported-extension-alert",
  "ssl-error-certificate-unobtainable-alert",
  "ssl-error-unrecognized-name-alert",
  "ssl-error-bad-cert-status-response-alert",
  "ssl-error-bad-cert-hash-value-alert",
  "ssl-error-rx-unexpected-new-session-ticket",
  "ssl-error-rx-malformed-new-session-ticket",
  "ssl-error-decompression-failure",
  "ssl-error-renegotiation-not-allowed",
  "ssl-error-unsafe-negotiation",
  "ssl-error-rx-unexpected-uncompressed-record",
  "ssl-error-weak-server-ephemeral-dh-key",
  "ssl-error-next-protocol-data-invalid",
  "ssl-error-feature-not-supported-for-ssl2",
  "ssl-error-feature-not-supported-for-servers",
  "ssl-error-feature-not-supported-for-clients",
  "ssl-error-invalid-version-range",
  "ssl-error-cipher-disallowed-for-version",
  "ssl-error-rx-malformed-hello-verify-request",
  "ssl-error-rx-unexpected-hello-verify-request",
  "ssl-error-feature-not-supported-for-version",
  "ssl-error-rx-unexpected-cert-status",
  "ssl-error-unsupported-hash-algorithm",
  "ssl-error-digest-failure",
  "ssl-error-incorrect-signature-algorithm",
  "ssl-error-next-protocol-no-callback",
  "ssl-error-next-protocol-no-protocol",
  "ssl-error-inappropriate-fallback-alert",
  "ssl-error-weak-server-cert-key",
  "ssl-error-rx-short-dtls-read",
  "ssl-error-no-supported-signature-algorithm",
  "ssl-error-unsupported-signature-algorithm",
  "ssl-error-missing-extended-master-secret",
  "ssl-error-unexpected-extended-master-secret",
  "sec-error-io",
  "sec-error-library-failure",
  "sec-error-bad-data",
  "sec-error-output-len",
  "sec-error-input-len",
  "sec-error-invalid-args",
  "sec-error-invalid-algorithm",
  "sec-error-invalid-ava",
  "sec-error-invalid-time",
  "sec-error-bad-der",
  "sec-error-bad-signature",
  "sec-error-expired-certificate",
  "sec-error-revoked-certificate",
  "sec-error-unknown-issuer",
  "sec-error-bad-key",
  "sec-error-bad-password",
  "sec-error-retry-password",
  "sec-error-no-nodelock",
  "sec-error-bad-database",
  "sec-error-no-memory",
  "sec-error-untrusted-issuer",
  "sec-error-untrusted-cert",
  "sec-error-duplicate-cert",
  "sec-error-duplicate-cert-name",
  "sec-error-adding-cert",
  "sec-error-filing-key",
  "sec-error-no-key",
  "sec-error-cert-valid",
  "sec-error-cert-not-valid",
  "sec-error-cert-no-response",
  "sec-error-expired-issuer-certificate",
  "sec-error-crl-expired",
  "sec-error-crl-bad-signature",
  "sec-error-crl-invalid",
  "sec-error-extension-value-invalid",
  "sec-error-extension-not-found",
  "sec-error-ca-cert-invalid",
  "sec-error-path-len-constraint-invalid",
  "sec-error-cert-usages-invalid",
  "sec-internal-only",
  "sec-error-invalid-key",
  "sec-error-unknown-critical-extension",
  "sec-error-old-crl",
  "sec-error-no-email-cert",
  "sec-error-no-recipient-certs-query",
  "sec-error-not-a-recipient",
  "sec-error-pkcs7-keyalg-mismatch",
  "sec-error-pkcs7-bad-signature",
  "sec-error-unsupported-keyalg",
  "sec-error-decryption-disallowed",
  "sec-error-no-krl",
  "sec-error-krl-expired",
  "sec-error-krl-bad-signature",
  "sec-error-revoked-key",
  "sec-error-krl-invalid",
  "sec-error-need-random",
  "sec-error-no-module",
  "sec-error-no-token",
  "sec-error-read-only",
  "sec-error-no-slot-selected",
  "sec-error-cert-nickname-collision",
  "sec-error-key-nickname-collision",
  "sec-error-safe-not-created",
  "sec-error-baggage-not-created",
  "sec-error-bad-export-algorithm",
  "sec-error-exporting-certificates",
  "sec-error-importing-certificates",
  "sec-error-pkcs12-decoding-pfx",
  "sec-error-pkcs12-invalid-mac",
  "sec-error-pkcs12-unsupported-mac-algorithm",
  "sec-error-pkcs12-unsupported-transport-mode",
  "sec-error-pkcs12-corrupt-pfx-structure",
  "sec-error-pkcs12-unsupported-pbe-algorithm",
  "sec-error-pkcs12-unsupported-version",
  "sec-error-pkcs12-privacy-password-incorrect",
  "sec-error-pkcs12-cert-collision",
  "sec-error-user-cancelled",
  "sec-error-pkcs12-duplicate-data",
  "sec-error-message-send-aborted",
  "sec-error-inadequate-key-usage",
  "sec-error-inadequate-cert-type",
  "sec-error-cert-addr-mismatch",
  "sec-error-pkcs12-unable-to-import-key",
  "sec-error-pkcs12-importing-cert-chain",
  "sec-error-pkcs12-unable-to-locate-object-by-name",
  "sec-error-pkcs12-unable-to-export-key",
  "sec-error-pkcs12-unable-to-write",
  "sec-error-pkcs12-unable-to-read",
  "sec-error-pkcs12-key-database-not-initialized",
  "sec-error-keygen-fail",
  "sec-error-invalid-password",
  "sec-error-retry-old-password",
  "sec-error-bad-nickname",
  "sec-error-not-fortezza-issuer",
  "sec-error-cannot-move-sensitive-key",
  "sec-error-js-invalid-module-name",
  "sec-error-js-invalid-dll",
  "sec-error-js-add-mod-failure",
  "sec-error-js-del-mod-failure",
  "sec-error-old-krl",
  "sec-error-ckl-conflict",
  "sec-error-cert-not-in-name-space",
  "sec-error-krl-not-yet-valid",
  "sec-error-crl-not-yet-valid",
  "sec-error-unknown-cert",
  "sec-error-unknown-signer",
  "sec-error-cert-bad-access-location",
  "sec-error-ocsp-unknown-response-type",
  "sec-error-ocsp-bad-http-response",
  "sec-error-ocsp-malformed-request",
  "sec-error-ocsp-server-error",
  "sec-error-ocsp-try-server-later",
  "sec-error-ocsp-request-needs-sig",
  "sec-error-ocsp-unauthorized-request",
  "sec-error-ocsp-unknown-response-status",
  "sec-error-ocsp-unknown-cert",
  "sec-error-ocsp-not-enabled",
  "sec-error-ocsp-no-default-responder",
  "sec-error-ocsp-malformed-response",
  "sec-error-ocsp-unauthorized-response",
  "sec-error-ocsp-future-response",
  "sec-error-ocsp-old-response",
  "sec-error-digest-not-found",
  "sec-error-unsupported-message-type",
  "sec-error-module-stuck",
  "sec-error-bad-template",
  "sec-error-crl-not-found",
  "sec-error-reused-issuer-and-serial",
  "sec-error-busy",
  "sec-error-extra-input",
  "sec-error-unsupported-elliptic-curve",
  "sec-error-unsupported-ec-point-form",
  "sec-error-unrecognized-oid",
  "sec-error-ocsp-invalid-signing-cert",
  "sec-error-revoked-certificate-crl",
  "sec-error-revoked-certificate-ocsp",
  "sec-error-crl-invalid-version",
  "sec-error-crl-v1-critical-extension",
  "sec-error-crl-unknown-critical-extension",
  "sec-error-unknown-object-type",
  "sec-error-incompatible-pkcs11",
  "sec-error-no-event",
  "sec-error-crl-already-exists",
  "sec-error-not-initialized",
  "sec-error-token-not-logged-in",
  "sec-error-ocsp-responder-cert-invalid",
  "sec-error-ocsp-bad-signature",
  "sec-error-out-of-search-limits",
  "sec-error-invalid-policy-mapping",
  "sec-error-policy-validation-failed",
  "sec-error-unknown-aia-location-type",
  "sec-error-bad-http-response",
  "sec-error-bad-ldap-response",
  "sec-error-failed-to-encode-data",
  "sec-error-bad-info-access-location",
  "sec-error-libpkix-internal",
  "sec-error-pkcs11-general-error",
  "sec-error-pkcs11-function-failed",
  "sec-error-pkcs11-device-error",
  "sec-error-bad-info-access-method",
  "sec-error-crl-import-failed",
  "sec-error-expired-password",
  "sec-error-locked-password",
  "sec-error-unknown-pkcs11-error",
  "sec-error-bad-crl-dp-url",
  "sec-error-cert-signature-algorithm-disabled",
  "mozilla-pkix-error-key-pinning-failure",
  "mozilla-pkix-error-ca-cert-used-as-end-entity",
  "mozilla-pkix-error-inadequate-key-size",
  "mozilla-pkix-error-v1-cert-used-as-ca",
  "mozilla-pkix-error-not-yet-valid-certificate",
  "mozilla-pkix-error-not-yet-valid-issuer-certificate",
  "mozilla-pkix-error-signature-algorithm-mismatch",
  "mozilla-pkix-error-ocsp-response-for-cert-missing",
  "mozilla-pkix-error-validity-too-long",
  "mozilla-pkix-error-required-tls-feature-missing",
  "mozilla-pkix-error-invalid-integer-encoding",
  "mozilla-pkix-error-empty-issuer-name",
  "mozilla-pkix-error-additional-policy-constraint-failed",
  "mozilla-pkix-error-self-signed-cert",
  "xp-java-remove-principal-error",
  "xp-java-delete-privilege-error",
  "xp-java-cert-not-exists-error",
  "xp-sec-fortezza-bad-card",
  "xp-sec-fortezza-no-card",
  "xp-sec-fortezza-none-selected",
  "xp-sec-fortezza-more-info",
  "xp-sec-fortezza-person-not-found",
  "xp-sec-fortezza-no-more-info",
  "xp-sec-fortezza-bad-pin",
  "xp-sec-fortezza-person-error",
]);
PK
!<�fԑ � Nchrome/toolkit/content/global/neterror/supportpages/connection-not-secure.html<!DOCTYPE html>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
  - License, v. 2.0. If a copy of the MPL was not distributed with this
  - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html>
  <head>
    <meta
      http-equiv="Content-Security-Policy"
      content="connect-src https:; default-src chrome:; object-src 'none'"
    />
    <meta name="referrer" content="no-referrer" />
    <meta charset="UTF-8" />
    <link
      rel="stylesheet"
      type="text/css"
      href="chrome://global/skin/offlineSupportPages.css"
    />
    <link
      rel="icon"
      type="image/png"
      id="favicon"
      href="chrome://branding/content/icon32.png"
    />
    <title>Secure connection failed and Firefox did not connect</title>
  </head>
  <body>
    <div id="offlineSupportContainer">
      <h1>Secure connection failed and Firefox did not connect</h1>
      <p>
        This article explains why you may see a
        <em>Secure Connection Failed</em> or a
        <em>Did Not Connect: Potential Security Issue</em> error page and what
        you can do.
      </p>
      <div id="toc">
        <h2>Table of Contents</h2>
        <ul>
          <li class="toclevel-1">
            <a href="#w_secure-connection-cannot-be-established"
              ><span class="tocnumber">1</span>
              <span class="toctext"
                >Secure connection cannot be established</span
              ></a
            >
            <ul>
              <li class="toclevel-2">
                <a href="#w_secure-connection-failed"
                  ><span class="tocnumber">1.1</span>
                  <span class="toctext">Secure Connection Failed</span></a
                >
              </li>
              <li class="toclevel-2">
                <a href="#w_did-not-connect-potential-security-issue">
                  <span class="tocnumber">1.2</span>
                  <span class="toctext"
                    >Did Not Connect: Potential Security Issue</span
                  ></a
                >
              </li>
            </ul>
          </li>
          <li class="toclevel-1">
            <a href="#w_website-issues"
              ><span class="tocnumber">2</span>
              <span class="toctext">Website issues</span></a
            >
            <ul>
              <li class="toclevel-2">
                <a href="#w_tls-version-unsupported"
                  ><span class="tocnumber">2.1</span>
                  <span class="toctext">TLS version unsupported</span></a
                >
              </li>
              <li class="toclevel-2">
                <a href="#w_hsts-required"
                  ><span class="tocnumber">2.2</span>
                  <span class="toctext">HSTS required</span></a
                >
              </li>
            </ul>
          </li>
          <li class="toclevel-1">
            <a href="#w_security-software-conflict"
              ><span class="tocnumber">3</span>
              <span class="toctext">Security software conflict</span></a
            >
          </li>
          <li class="toclevel-1">
            <a href="#w_incorrect-system-clock"
              ><span class="tocnumber">4</span>
              <span class="toctext">Incorrect system clock</span></a
            >
          </li>
          <li class="toclevel-1">
            <a href="#w_other-secure-connection-issues"
              ><span class="tocnumber">5</span>
              <span class="toctext">Other secure connection issues</span></a
            >
          </li>
        </ul>
      </div>
      <h1 id="w_secure-connection-cannot-be-established">
        Secure connection cannot be established
      </h1>
      <p>
        When a website that requires a secure (<strong>https</strong>)
        connection tries to secure communication with your computer, Firefox
        cross-checks this attempt to make sure that the website certificate and
        the connection method are actually secure. If Firefox cannot establish a
        secure connection, it will display an error page.
      </p>
      <h2 id="w_secure-connection-failed">Secure Connection Failed</h2>
      <p>
        A <em>Secure Connection Failed</em> error page will include a
        description of the error, an option to report the error to Mozilla and a
        <span class="button">Try Again</span> button. There is no option to add
        a security exception to bypass this type of error.
      </p>
      <p></p>
      <p>The error page will also include the following information:</p>
      <ul>
        <li>
          <em
            >The page you are trying to view cannot be shown because the
            authenticity of the received data could not be verified.</em
          >
        </li>
        <li>
          <em
            >Please contact the website owners to inform them of this
            problem.</em
          >
        </li>
      </ul>
      <h2 id="w_did-not-connect-potential-security-issue">
        Did Not Connect: Potential Security Issue
      </h2>
      <p>
        Certain secure connection failures will result in a
        <em>Did Not Connect: Potential Security Issue</em> error page.
      </p>
      <p></p>
      <p>
        The error page will include a description of the potential security
        threat, an option to report the error to Mozilla and an
        <span class="button">Advanced…</span> button to view the error code and
        other technical details. There is no option to add a security exception
        to visit the website.
      </p>
      <h1 id="w_website-issues">Website issues</h1>
      <h2 id="w_tls-version-unsupported">TLS version unsupported</h2>
      <p>
        Some websites try using outdated (no longer secure) Transport Layer
        Security(<em>TLS</em>) mechanisms in an attempt to secure your
        connection. Firefox protects you by preventing navigation to such sites
        if there is a problem in securely establishing a connection. Contact the
        owners of the website and ask them to update their TLS version to a
        version that is still current and still secure.
      </p>
      <p>
        Starting in Firefox version 74, the minimum TLS version allowed by
        default is TLS 1.2. Websites that don't support TLS version 1.2 or
        higher will display a <em>Secure Connection Failed</em> error page with
        Error code: SSL_ERROR_UNSUPPORTED_VERSION and a message that
        <em
          >This website might not support the TLS 1.2 protocol, which is the
          minimum version supported by Firefox.</em
        >
        The error page may also include a button,
        <span class="button">Enable TLS 1.0 and 1.1</span> that will allow you
        to override the minimum TLS requirement; however, Mozilla plans to
        remove this option and permanently disable TLS 1.0 and 1.1 in a future
        version of Firefox.
      </p>
      <h2 id="w_hsts-required">HSTS required</h2>
      <p>
        Other websites may require HTTP Strict Transport Security (HSTS) and
        will not allow access with an insecure connection.
      </p>
      <h1 id="w_security-software-conflict">Security software conflict</h1>
      <p>
        Many security products use a feature that intercepts secure connections
        by default. This can produce connection errors or warnings on secure
        websites. If you see secure connection errors on multiple secure
        websites, updating your security product or modifying its settings may
        resolve the issue.
      </p>
      <p>
        <span class="for" data-for="win8,win10">
          Alternatively, you can uninstall third-party security software and use
          Windows Defender, the built-in antivirus on Windows 8 and Windows 10.
        </span>
      </p>
      <p></p>
      <h1 id="w_incorrect-system-clock">Incorrect system clock</h1>
      <p>
        Firefox uses certificates on secure websites to ensure that your
        information is being sent to the intended recipient and can't be read by
        eavesdroppers. An incorrect system date can cause Firefox to detect that
        the website's security certificate is expired or invalid. Make sure your
        computer is set to the correct date, time and time zone.
      </p>
    </div>
  </body>
</html>
PK
!<���Fv*v*Dchrome/toolkit/content/global/neterror/supportpages/time-errors.html<!DOCTYPE html>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
  - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html>
  <head>
    <meta
      http-equiv="Content-Security-Policy"
      content="connect-src https:; default-src chrome:; object-src 'none'"
    />
    <meta name="referrer" content="no-referrer" />
    <meta charset="UTF-8" />
    <link
      rel="stylesheet"
      type="text/css"
      href="chrome://global/skin/offlineSupportPages.css"
    />
    <link
      rel="icon"
      type="image/png"
      id="favicon"
      href="chrome://branding/content/icon32.png"
    />
    <title>How to troubleshoot time related errors on secure websites</title>
  </head>
  <body>
    <div id="offlineSupportContainer">
      <h1>How to troubleshoot time related errors on secure websites</h1>
      <p>
        Certificates for secure websites (the address begins with
        <strong>https://</strong>) are valid only for a certain period of time.
        If a website presents a certificate with validity dates that don't match
        the date on your computer's clock, Firefox can't verify that it is
        secure and will show you an error page.
      </p>
      <p>
        Such issues can often be fixed by setting the correct date, time and
        time zone on your computer system. If this does not solve the problem,
        it could be caused by other issues, such as a misconfigured web server
        or an expired certificate.
      </p>
      <div id="toc">
        <h2>Table of Contents</h2>
        <ul>
          <li class="toclevel-1">
            <a href="#w_list-of-time-related-error-codes-you-may-encounter">
              <span class="tocnumber">1</span>
              <span class="toctext"
                >List of time-related error codes you may encounter</span
              >
            </a>
          </li>
          <li class="toclevel-1">
            <a href="#w_set-your-system-clock-to-the-correct-time">
              <span class="tocnumber">2</span>
              <span class="toctext"
                >Set your system clock to the correct time</span
              >
            </a>
          </li>
          <li class="toclevel-1">
            <a href="#w_contact-the-website-owner">
              <span class="tocnumber">3</span>
              <span class="toctext">Contact the website owner</span>
            </a>
          </li>
          <li class="toclevel-1">
            <a href="#w_bypass-the-warning">
              <span class="tocnumber">4</span>
              <span class="toctext">Bypass the warning</span>
            </a>
          </li>
        </ul>
      </div>
      <h1 id="w_list-of-time-related-error-codes-you-may-encounter">
        List of time-related error codes you may encounter
      </h1>
      <div class="for" data-for="fx66">
        <div class="note">
          <strong>Note:</strong> A <em>Your Computer Clock is Wrong</em> error
          page almost certainly means that your computer's clock is set to the
          wrong date. Some time-related errors will show a
          <em>Warning: Potential Security Risk Ahead</em> error page. For other
          time-related errors, you'll get a <em>Secure Connection Failed</em> or
          <em>Did Not Connect: Potential Security Issue</em> error page.
        </div>
        <p>
          <span class="for" data-for="=fx66"></span>
          <span class="for" data-for="fx67"></span>
        </p>
        <p>
          Click
          <span class="for" data-for="not fx67">
            <span class="button">More Information</span> or
            <span class="button">Advanced…</span>, depending on the error page,
          </span>
          <span class="for" data-for="fx67">
            <span class="button">Advanced…</span> on the error page</span
          >
          to view the error code. One of the following error codes will indicate
          that the secure connection couldn't be established due to a
          time-related error:
        </p>
      </div>
      <div class="for" data-for="not fx66">
        <div class="note">
          <strong>Note:</strong> If you get a
          <em>Your connection is not secure</em> error page, click the
          <span class="button">Advanced</span> button to view the error code and
          other details. A <em>Secure Connection Failed</em> error page may also
          indicate a time-related error.
        </div>
        <p>
          One of the following error codes will indicate that the secure
          connection couldn't be established due to a time-related error:
        </p>
      </div>
      <p>
        <sub>SEC_ERROR_EXPIRED_CERTIFICATE</sub><br />
        <sub>SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE</sub><br />
        <sub>SEC_ERROR_OCSP_FUTURE_RESPONSE</sub><br />
        <sub>SEC_ERROR_OCSP_OLD_RESPONSE</sub><br />
        <sub>MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE</sub><br />
        <sub>MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE</sub>
      </p>
      <p>
        The text on the error page will warn you when Firefox detects that your
        system date and time is probably wrong and will also show the date and
        time currently set in your system. If the clock settings are incorrect
        you should set it to the right time<span class="for" data-for="win,mac">
          as explained below</span
        >. Even if the displayed time settings seem to be correct, you should
        make sure that the time zone settings of your system match your current
        location.
      </p>
      <h1 id="w_set-your-system-clock-to-the-correct-time">
        Set your system clock to the correct time
      </h1>
      <p>
        Time-related errors on secure websites caused by a skewed system clock
        can be resolved by setting your correct date, time and time zone<span
          class="for"
          data-for="mac"
          >:</span
        ><span class="for" data-for="win,linux">.</span>
        <span class="for" data-for="win">
          Change your date and time settings from the clock on the Windows
          taskbar or follow these instructions:</span
        >
      </p>
      <div class="for" data-for="win10">
        <h2>If you are on Windows 10:</h2>
        <ol>
          <li>
            Click the Windows Start button or press the Windows key<span
              class="key"
            ></span
            >.
          </li>
          <li>In the Start menu, select<span class="menu">Settings</span>.</li>
          <li>
            In Settings, select<span class="menu">Time &amp; language</span>.
          </li>
          <li>
            In the<span class="menu">Date &amp; time</span> section you can
            review the current date and time settings. To change your settings
            click on <span class="button">Change</span> below
            <span class="menu">Change date and time</span> or expand the
            <span class="menu">Time zone</span> dropdown menu.
            <div class="note">
              If your system is set to manage the time and time zone
              automatically, you cannot make manual changes.
            </div>
          </li>
          <li>If you are done with your changes, close the Settings window.</li>
        </ol>
      </div>
      <div class="for" data-for="mac">
        <h2>If you are on Mac OS:</h2>
        <ol>
          <li>
            Click the Apple menu and select
            <span class="menu">System Preferences</span>.
          </li>
          <li>
            In the System Preferences window, click on
            <strong>Date &amp; Time</strong>.
          </li>
          <li>
            The panel that opens shows the current date and time settings. In
            order to adjust them, disable
            <span class="pref">Set date and time automatically</span>, manually
            enter the date and time and click
            <span class="button">Save</span> to confirm your changes.
          </li>
          <li>
            In order to review your time zone settings, click on the
            <strong>Time Zone</strong> tab. In order to adjust your time zone,
            disable
            <span class="pref"
              >Set time zone automatically using current location</span
            >, click onto your approximate location in the map and select the
            city closest to you in the dropdown panel.
          </li>
          <li>
            If you are done with your changes, close the Date &amp; Time window.
          </li>
        </ol>
      </div>
      <div class="note">
        <strong>Note:</strong> If the clock on your device constantly resets
        after you power it off, this might indicate that the battery cell that
        runs the real-time clock is getting low or is empty. Please consult your
        manufacturer's manual on how to replace the CMOS battery.
      </div>
      <h1 id="w_contact-the-website-owner">Contact the website owner</h1>
      <p>
        If you get a time related error on a secure website and you have already
        checked the correct settings of your system’s clock, please contact the
        owner of the website which you can’t access and inform them of the
        problem. The website owner might need to renew the expired certificate,
        for example.
      </p>
      <div class="for" data-for="not fx66">
        <h1 id="w_bypass-the-warning">Bypass the warning</h1>
        <div class="warning">
          <strong>Warning:</strong> You should never bypass the warning for a
          legitimate major website or sites where financial transactions take
          place – in this case an invalid certificate can indicate that your
          connection is compromised by a third party.
        </div>
        <p>
          If you see a <em>Your connection is not secure</em> warning page and
          the website allows it, you can add an exception to be able to visit
          the site, despite the fact that the certificate is not trusted by
          default:
        </p>
        <ol>
          <li>
            On the warning page, click <span class="button">Advanced</span>.
          </li>
          <li>
            Click <span class="button">Add Exception…</span>. The
            <em>Add Security Exception</em> dialog will appear.
          </li>
          <li>
            Read the text describing the problems with the website. You can
            click <span class="button">View…</span>
            to closer inspect the untrusted certificate.
          </li>
          <li>
            Click <span class="button">Confirm Security Exception</span> if you
            are sure you want to trust the site.
          </li>
        </ol>
      </div>
    </div>
  </body>
</html>
PK
!<��1��*chrome/toolkit/content/global/notfound.wavRIFF�WAVEfmt D�D�datab�����������~~}}||{{zyyxxwvuttsrqqpponnnnmmmmmmmnnoppqqrstvwxxy{|}}~���������������������������������������������������������������������������������~~}}||{{zzzyyyyyyyzzzzzzzzzzzyyxxwwvutssrqponmkjiihhggggghhijkmnprsuwy{}���������������������������������������������~|zywutsrqponnmllkkkkjiiiihhgggffeddccbbbaaaaaaabccdefghijlmnprsuwyz|~����������������������������������������������������������������������}zxurpmkhfdb`^\[ZXWVVUUTTTTTUUVVWXYZ[[\]_`abccdefhijjklmmmnoppqqqqrrssssstttuuvvvwwxxyyyyzz{{{|||}}}~~���������������������������������������������������������������������������~ytokfa]XTOKGC@=:7410.-+**)))))**+-.02357:<?ADGIMPSVY\`cfhlorvy|��������������������������������������������������������������������������������������������������|yuqlhd_[VRMHC?:61-)%"	

#',05:@FLRY_elry������������������������������������������������������������������~~~~~~~}|zyxvtrpnligda_\YWTRPNLKIHGFEEEEEEFFGGHIIJKLNOPQRSTTUVWWXXYYZZ[[[\\\\]]^^__`abcdefghjlnprtvy{~����������������������������������������������������������������������������yslf_YSMGA<72.*&#  #&),.259=@DHKORVZ]adhknruy|������������������������������������������������������������������������������������|yvrokgd`]ZVSOLIEC@=:87520.,)(&%$"!"#$%&(+-/258;?BGKNRW]bglqw}����������������������������������������������������������������������~~���PK
!<��
����8chrome/toolkit/content/global/pictureinpicture/player.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { PictureInPicture } = ChromeUtils.importESModule(
  "resource://gre/modules/PictureInPicture.sys.mjs"
);
const { ShortcutUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/ShortcutUtils.sys.mjs"
);
const { DeferredTask } = ChromeUtils.importESModule(
  "resource://gre/modules/DeferredTask.sys.mjs"
);
const { AppConstants } = ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);

const AUDIO_TOGGLE_ENABLED_PREF =
  "media.videocontrols.picture-in-picture.audio-toggle.enabled";
const KEYBOARD_CONTROLS_ENABLED_PREF =
  "media.videocontrols.picture-in-picture.keyboard-controls.enabled";
const CAPTIONS_ENABLED_PREF =
  "media.videocontrols.picture-in-picture.display-text-tracks.enabled";
const CAPTIONS_TOGGLE_ENABLED_PREF =
  "media.videocontrols.picture-in-picture.display-text-tracks.toggle.enabled";
const TEXT_TRACK_FONT_SIZE_PREF =
  "media.videocontrols.picture-in-picture.display-text-tracks.size";
const IMPROVED_CONTROLS_ENABLED_PREF =
  "media.videocontrols.picture-in-picture.improved-video-controls.enabled";
const SEETHROUGH_MODE_ENABLED_PREF =
  "media.videocontrols.picture-in-picture.seethrough-mode.enabled";

// Time to fade the Picture-in-Picture video controls after first opening.
const CONTROLS_FADE_TIMEOUT_MS = 3000;
const RESIZE_DEBOUNCE_RATE_MS = 500;

/**
Quadrants!
* 2 | 1
* 3 | 4
*/
const TOP_RIGHT_QUADRANT = 1;
const TOP_LEFT_QUADRANT = 2;
const BOTTOM_LEFT_QUADRANT = 3;
const BOTTOM_RIGHT_QUADRANT = 4;

/**
 * Public function to be called from PictureInPicture.sys.mjs. This is the main
 * entrypoint for initializing the player window.
 *
 * @param {Number} id
 *   A unique numeric ID for the window, used for Telemetry Events.
 * @param {WindowGlobalParent} wgp
 *   The WindowGlobalParent that is hosting the originating video.
 * @param {ContentDOMReference} videoRef
 *    A reference to the video element that a Picture-in-Picture window
 *    is being created for
 */
function setupPlayer(id, wgp, videoRef, autoFocus) {
  Player.init(id, wgp, videoRef, autoFocus);
}

/**
 * Public function to be called from PictureInPicture.sys.mjs. This update the
 * controls based on whether or not the video is playing.
 *
 * @param {Boolean} isPlaying
 *   True if the Picture-in-Picture video is playing.
 */
function setIsPlayingState(isPlaying) {
  Player.isPlaying = isPlaying;
}

/**
 * Public function to be called from PictureInPicture.sys.mjs. This update the
 * controls based on whether or not the video is muted.
 *
 * @param {Boolean} isMuted
 *   True if the Picture-in-Picture video is muted.
 */
function setIsMutedState(isMuted) {
  Player.isMuted = isMuted;
}

/**
 * Function to resize and reposition the PiP window
 * @param {Object} rect
 *   An object containing `left`, `top`, `width`, and `height` for the PiP
 *   window
 */
function resizeToVideo(rect) {
  Player.resizeToVideo(rect);
}

/**
 * Returns an object containing `left`, `top`, `width`, and `height` of the
 * PiP window before entering fullscreen. Will be null if the PiP window is
 * not in fullscreen.
 */
function getDeferredResize() {
  return Player.deferredResize;
}

function enableSubtitlesButton() {
  Player.enableSubtitlesButton();
}

function disableSubtitlesButton() {
  Player.disableSubtitlesButton();
}

function setScrubberPosition(position) {
  Player.setScrubberPosition(position);
}

function setTimestamp(timeString) {
  Player.setTimestamp(timeString);
}

function setVolume(volume) {
  Player.setVolume(volume);
}

function closeFromForeground() {
  Player.closeFromForeground();
}

/**
 * The Player object handles initializing the player, holds state, and handles
 * events for updating state.
 */
let Player = {
  _isInitialized: false,
  WINDOW_EVENTS: [
    "click",
    "contextmenu",
    "dblclick",
    "keydown",
    "mouseup",
    "mousemove",
    "MozDOMFullscreen:Entered",
    "MozDOMFullscreen:Exited",
    "resize",
    "unload",
    "draggableregionleftmousedown",
  ],
  actor: null,
  /**
   * Used for resizing Telemetry to avoid recording an event for every resize
   * event. Instead, we wait until RESIZE_DEBOUNCE_RATE_MS has passed since the
   * last resize event before recording.
   */
  resizeDebouncer: null,
  /**
   * Used for Telemetry to identify the window.
   */
  id: -1,

  /**
   * When set to a non-null value, a timer is scheduled to hide the controls
   * after CONTROLS_FADE_TIMEOUT_MS milliseconds.
   */
  showingTimeout: null,

  /**
   * Used to determine old window location when mouseup-ed for corner
   * snapping drag vector calculation
   */
  oldMouseUpWindowX: window.screenX,
  oldMouseUpWindowY: window.screenY,

  /**
   * Used to determine if hovering the mouse cursor over the pip window or not.
   * Gets updated whenever a new hover state is detected.
   */
  isCurrentHover: false,

  /**
   * Store the size and position of the window before entering fullscreen and
   * use this to correctly position the window when exiting fullscreen
   */
  deferredResize: null,

  /**
   * Initializes the player browser, and sets up the initial state.
   *
   * @param {Number} id
   *   A unique numeric ID for the window, used for Telemetry Events.
   * @param {WindowGlobalParent} wgp
   *   The WindowGlobalParent that is hosting the originating video.
   * @param {ContentDOMReference} videoRef
   *   A reference to the video element that a Picture-in-Picture window
   *   is being created for
   * @param {boolean} autoFocus
   *   Autofocus the PiP window
   */
  init(id, wgp, videoRef, autoFocus) {
    this.id = id;

    // State for whether or not we are adjusting the time via the scrubber
    this.scrubbing = false;

    let holder = document.querySelector(".player-holder");
    let browser = document.getElementById("browser");
    browser.remove();

    browser.setAttribute("nodefaultsrc", "true");

    this.setupTooltip("close", "pictureinpicture-close-btn", "closeShortcut");
    let strId = this.isFullscreen
      ? `pictureinpicture-exit-fullscreen-btn2`
      : `pictureinpicture-fullscreen-btn2`;
    this.setupTooltip("fullscreen", strId, "fullscreenToggleShortcut");

    // Set the specific remoteType and browsingContextGroupID to use for the
    // initial about:blank load. The combination of these two properties will
    // ensure that the browser loads in the same process as our originating
    // browser.
    browser.setAttribute("remoteType", wgp.domProcess.remoteType);
    browser.setAttribute(
      "initialBrowsingContextGroupId",
      wgp.browsingContext.group.id
    );
    holder.appendChild(browser);

    this.actor =
      browser.browsingContext.currentWindowGlobal.getActor("PictureInPicture");
    this.actor.sendAsyncMessage("PictureInPicture:SetupPlayer", {
      videoRef,
    });

    PictureInPicture.weakPipToWin.set(this.actor, window);

    for (let eventType of this.WINDOW_EVENTS) {
      addEventListener(eventType, this);
    }

    this.controls.addEventListener("mouseleave", () => {
      this.onMouseLeave();
    });
    this.controls.addEventListener("mouseenter", () => {
      this.onMouseEnter();
    });

    this.scrubber.addEventListener("input", event => {
      this.handleScrubbing(event);
    });
    this.scrubber.addEventListener("change", event => {
      this.handleScrubbingDone(event);
    });

    this.audioScrubber.addEventListener("input", event => {
      this.audioScrubbing = true;
      this.handleAudioScrubbing(event.target.value);
    });
    this.audioScrubber.addEventListener("change", () => {
      this.audioScrubbing = false;
    });
    this.audioScrubber.addEventListener("pointerdown", () => {
      if (this.isMuted) {
        this.audioScrubber.max = 1;
      }
    });

    for (let radio of document.querySelectorAll(
      'input[type=radio][name="cc-size"]'
    )) {
      radio.addEventListener("change", event => {
        this.onSubtitleChange(event.target.id);
      });
    }

    document
      .querySelector("#subtitles-toggle")
      .addEventListener("change", () => {
        this.onToggleChange();
      });

    // If the content process hosting the video crashes, let's
    // just close the window for now.
    browser.addEventListener("oop-browser-crashed", this);

    this.revealControls(false);

    if (Services.prefs.getBoolPref(AUDIO_TOGGLE_ENABLED_PREF, false)) {
      const audioButton = document.getElementById("audio");
      audioButton.hidden = false;

      const audioScrubber = document.getElementById("audio-scrubber");
      audioScrubber.hidden = false;
    }

    if (Services.prefs.getBoolPref(CAPTIONS_ENABLED_PREF, false)) {
      this.closedCaptionButton.hidden = false;
    }

    if (Services.prefs.getBoolPref(IMPROVED_CONTROLS_ENABLED_PREF, false)) {
      const fullscreenButton = document.getElementById("fullscreen");
      fullscreenButton.hidden = false;

      const seekBackwardButton = document.getElementById("seekBackward");
      seekBackwardButton.hidden = false;

      const seekForwardButton = document.getElementById("seekForward");
      seekForwardButton.hidden = false;

      this.scrubber.hidden = false;
      this.timestamp.hidden = false;

      const controlsBottomGradient = document.getElementById(
        "controls-bottom-gradient"
      );
      controlsBottomGradient.hidden = false;
    }

    this.alignEndControlsButtonTooltips();

    this.resizeDebouncer = new DeferredTask(() => {
      this.alignEndControlsButtonTooltips();
      this.recordEvent("resize", {
        width: window.outerWidth.toString(),
        height: window.outerHeight.toString(),
      });
    }, RESIZE_DEBOUNCE_RATE_MS);

    this.computeAndSetMinimumSize(window.outerWidth, window.outerHeight);

    // alwaysontop windows are not focused by default, so we have to do it
    // ourselves. We use requestAnimationFrame since we have to wait until the
    // window is visible before it can focus.
    if (autoFocus) {
      window.requestAnimationFrame(() => {
        window.focus();
      });
    }

    let fontSize = Services.prefs.getCharPref(
      TEXT_TRACK_FONT_SIZE_PREF,
      "medium"
    );

    // fallback to medium if the pref value is not a valid option
    if (fontSize === "small" || fontSize === "large") {
      document.querySelector(`#${fontSize}`).checked = "true";
    } else {
      document.querySelector("#medium").checked = "true";
    }

    // In see-through mode the PiP window is made semi-transparent on hover.
    if (Services.prefs.getBoolPref(SEETHROUGH_MODE_ENABLED_PREF, false)) {
      document.documentElement.classList.add("seethrough-mode");
    }

    this._isInitialized = true;
  },

  uninit() {
    this.resizeDebouncer.disarm();
    PictureInPicture.unload(window, this.actor);
  },

  setupTooltip(elId, l10nId, shortcutId) {
    const el = document.getElementById(elId);
    const shortcut = document.getElementById(shortcutId);
    let l10nObj = shortcut
      ? { shortcut: ShortcutUtils.prettifyShortcut(shortcut) }
      : {};
    document.l10n.setAttributes(el, l10nId, l10nObj);
  },

  handleEvent(event) {
    switch (event.type) {
      case "click": {
        // Don't run onClick if middle or right click is pressed respectively
        if (event.button !== 1 && event.button !== 2) {
          this.onClick(event);
          this.controls.removeAttribute("keying");
        }
        break;
      }

      case "contextmenu": {
        event.preventDefault();
        break;
      }

      case "dblclick": {
        this.onDblClick(event);
        break;
      }

      case "keydown": {
        if (event.keyCode == KeyEvent.DOM_VK_TAB) {
          this.controls.setAttribute("keying", true);
          this.showVideoControls();
        } else if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) {
          let isSettingsPanelInFocus = this.settingsPanel.contains(
            document.activeElement
          );

          event.preventDefault();

          if (!this.settingsPanel.classList.contains("hide")) {
            // If the subtitles settings panel is open, let the ESC key close it
            this.toggleSubtitlesSettingsPanel({ forceHide: true });
            if (isSettingsPanelInFocus) {
              document.getElementById("closed-caption").focus();
            }
          } else if (this.isFullscreen) {
            // We handle the ESC key, in fullscreen modus as intent to leave only the fullscreen mode
            document.exitFullscreen();
          } else {
            // We handle the ESC key, as an intent to leave the picture-in-picture modus
            this.onClose();
          }
        } else if (
          Services.prefs.getBoolPref(KEYBOARD_CONTROLS_ENABLED_PREF, false) &&
          (event.keyCode != KeyEvent.DOM_VK_SPACE || !event.target.id)
        ) {
          // Pressing "space" fires a "keydown" event which can also trigger a control
          // button's "click" event. Handle the "keydown" event only when the event did
          // not originate from a control button and it is not a "space" keypress.
          this.onKeyDown(event);
        }

        break;
      }

      case "mouseup": {
        this.onMouseUp(event);
        break;
      }

      case "mousemove": {
        this.onMouseMove();
        break;
      }

      // Normally, the DOMFullscreenParent / DOMFullscreenChild actors
      // would take care of firing the `fullscreen-painted` notification,
      // however, those actors are only ever instantiated when a <browser>
      // is fullscreened, and not a <body> element in a parent-process
      // chrome privileged DOM window.
      //
      // Rather than trying to re-engineer JSWindowActors to be re-usable for
      // this edge-case, we do the work of firing fullscreen-painted when
      // transitioning in and out of fullscreen ourselves here.
      case "MozDOMFullscreen:Entered":
      // Intentional fall-through
      case "MozDOMFullscreen:Exited": {
        let { lastTransactionId } = window.windowUtils;
        window.addEventListener("MozAfterPaint", function onPainted(event) {
          if (event.transactionId > lastTransactionId) {
            window.removeEventListener("MozAfterPaint", onPainted);
            Services.obs.notifyObservers(window, "fullscreen-painted");
          }
        });

        // If we are exiting fullscreen we want to resize the window to the
        // stored size and position
        if (this.deferredResize && event.type === "MozDOMFullscreen:Exited") {
          this.resizeToVideo(this.deferredResize);
          this.deferredResize = null;
        }

        // Sets the title for fullscreen button when PIP is in Enter Fullscreen mode and Exit Fullscreen mode
        let strId = this.isFullscreen
          ? `pictureinpicture-exit-fullscreen-btn2`
          : `pictureinpicture-fullscreen-btn2`;
        this.setupTooltip("fullscreen", strId, "fullscreenToggleShortcut");

        window.focus();

        if (this.isFullscreen) {
          this.actor.sendAsyncMessage("PictureInPicture:EnterFullscreen", {
            isFullscreen: true,
            isVideoControlsShowing: null,
            playerBottomControlsDOMRect: null,
          });
        } else {
          this.actor.sendAsyncMessage("PictureInPicture:ExitFullscreen", {
            isFullscreen: this.isFullscreen,
            isVideoControlsShowing:
              !!this.controls.getAttribute("showing") ||
              !!this.controls.getAttribute("keying"),
            playerBottomControlsDOMRect:
              this.controlsBottom.getBoundingClientRect(),
          });
        }
        // The subtitles settings panel gets selected when entering/exiting fullscreen even though
        // user-select is set to none. I don't know why this happens or how to prevent so we just
        // remove the selection when fullscreen is entered/exited.
        let selection = window.getSelection();
        selection.removeAllRanges();
        break;
      }

      case "oop-browser-crashed": {
        this.closePipWindow({ reason: "browser-crash" });
        break;
      }

      case "resize": {
        this.onResize(event);
        break;
      }

      case "unload": {
        this.uninit();
        break;
      }

      case "draggableregionleftmousedown": {
        this.toggleSubtitlesSettingsPanel({ forceHide: true });
        break;
      }
    }
  },

  /**
   * This function handles when the scrubber is being scrubbed by the mouse
   * because if we get an input event from the keyboard, onKeyDown will set
   * this.preventNextInputEvent to true.
   * This function is called by input events on the scrubber
   * @param {Event} event The input event
   */
  handleScrubbing(event) {
    // When using the keyboard to scrub, we get both a keydown and an input
    // event. The input event is fired after the keydown and we have already
    // handled the keydown event in onKeyDown so we set preventNextInputEvent
    // to true in onKeyDown as to not set the current time twice.
    if (this.preventNextInputEvent) {
      this.preventNextInputEvent = false;
      return;
    }
    if (!this.scrubbing) {
      this.wasPlaying = this.isPlaying;
      if (this.isPlaying) {
        this.actor.sendAsyncMessage("PictureInPicture:Pause");
      }
      this.scrubbing = true;
    }
    let scrubberPosition = this.getScrubberPositionFromEvent(event);
    this.setVideoTime(scrubberPosition);
  },

  /**
   * This function handles setting the scrubbing state to false and playing
   * the video if we paused it before scrubbing.
   * @param {Event} event The change event
   */
  handleScrubbingDone(event) {
    if (!this.scrubbing) {
      return;
    }
    let scrubberPosition = this.getScrubberPositionFromEvent(event);
    this.setVideoTime(scrubberPosition);
    if (this.wasPlaying) {
      this.actor.sendAsyncMessage("PictureInPicture:Play");
    }
    this.scrubbing = false;
  },

  /**
   * Set the volume on the video and unmute if the video was muted.
   * If the volume is changed via the keyboard, onKeyDown will set
   * this.preventNextInputEvent to true.
   * @param {Number} volume A number between 0 and 1 that represents the volume
   */
  handleAudioScrubbing(volume) {
    // When using the keyboard to adjust the volume, we get both a keydown and
    // an input event. The input event is fired after the keydown event and we
    // have already handled the keydown event in onKeyDown so we set
    // preventNextInputEvent to true in onKeyDown as to not set the volume twice.
    if (this.preventNextInputEvent) {
      this.preventNextInputEvent = false;
      return;
    }

    if (this.isMuted) {
      this.isMuted = false;
      this.actor.sendAsyncMessage("PictureInPicture:Unmute");
    }

    if (volume == 0) {
      this.actor.sendAsyncMessage("PictureInPicture:Mute");
    }

    this.actor.sendAsyncMessage("PictureInPicture:SetVolume", {
      volume,
    });
  },

  getScrubberPositionFromEvent(event) {
    return event.target.value;
  },

  setVideoTime(scrubberPosition) {
    let wasPlaying = this.scrubbing ? this.wasPlaying : this.isPlaying;
    this.setScrubberPosition(scrubberPosition);
    this.actor.sendAsyncMessage("PictureInPicture:SetVideoTime", {
      scrubberPosition,
      wasPlaying,
    });
  },

  setScrubberPosition(value) {
    this.scrubber.value = value;
    this.scrubber.hidden = value === undefined;

    // Also hide the seek buttons when we hide the scrubber
    this.seekBackward.hidden = value === undefined;
    this.seekForward.hidden = value === undefined;
  },

  setTimestamp(timestamp) {
    this.timestamp.textContent = timestamp;
    this.timestamp.hidden = timestamp === undefined;
  },

  setVolume(volume) {
    if (volume < Number.EPSILON) {
      this.actor.sendAsyncMessage("PictureInPicture:Mute");
    }

    this.audioScrubber.value = volume;
  },

  closePipWindow(closeData) {
    // Set the subtitles font size prefs
    Services.prefs.setBoolPref(
      CAPTIONS_TOGGLE_ENABLED_PREF,
      document.querySelector("#subtitles-toggle").checked
    );
    for (let radio of document.querySelectorAll(
      'input[type=radio][name="cc-size"]'
    )) {
      if (radio.checked) {
        Services.prefs.setCharPref(TEXT_TRACK_FONT_SIZE_PREF, radio.id);
        break;
      }
    }
    const { reason } = closeData;
    PictureInPicture.closeSinglePipWindow({ reason, actorRef: this.actor });
  },

  onDblClick(event) {
    if (event.target.id == "controls") {
      this.fullscreenModeToggle();
      event.preventDefault();
    }
  },

  onClick(event) {
    switch (event.target.id) {
      case "audio": {
        this.toggleMute();
        break;
      }

      case "close": {
        this.onClose();
        break;
      }

      case "playpause": {
        if (!this.isPlaying) {
          this.actor.sendAsyncMessage("PictureInPicture:Play");
          this.revealControls(false);
        } else {
          this.actor.sendAsyncMessage("PictureInPicture:Pause");
          this.revealControls(true);
        }

        break;
      }

      case "seekBackward": {
        this.actor.sendAsyncMessage("PictureInPicture:SeekBackward");
        break;
      }

      case "seekForward": {
        this.actor.sendAsyncMessage("PictureInPicture:SeekForward");
        break;
      }

      case "unpip": {
        PictureInPicture.focusTabAndClosePip(window, this.actor);
        break;
      }

      case "closed-caption": {
        let options = {};
        if (event.inputSource == MouseEvent.MOZ_SOURCE_KEYBOARD) {
          options.isKeyboard = true;
        }
        this.toggleSubtitlesSettingsPanel(options);
        // Early return to prevent hiding the panel below
        return;
      }

      case "fullscreen": {
        this.fullscreenModeToggle();
        this.recordEvent("fullscreen", {
          enter: (!this.isFullscreen).toString(),
        });
        break;
      }

      case "font-size-selection-radio-small": {
        document.getElementById("small").click();
        break;
      }

      case "font-size-selection-radio-medium": {
        document.getElementById("medium").click();
        break;
      }

      case "font-size-selection-radio-large": {
        document.getElementById("large").click();
        break;
      }
    }
    // If the click came from a element that is not inside the subtitles settings panel
    // then we want to hide the panel
    if (!this.settingsPanel.contains(event.target)) {
      this.toggleSubtitlesSettingsPanel({ forceHide: true });
    }
  },

  /**
   * Function to toggle the visibility of the subtitles settings panel
   * @param {Object} options [optional] Object containing options for the function
   *   - forceHide: true to force hide the subtitles settings panel
   *   - isKeyboard: true if the subtitles button was activated using the keyboard
   *     to show or hide the subtitles settings panel
   */
  toggleSubtitlesSettingsPanel(options) {
    let settingsPanelVisible = !this.settingsPanel.classList.contains("hide");
    if (options?.forceHide || settingsPanelVisible) {
      this.settingsPanel.classList.add("hide");
      this.closedCaptionButton.setAttribute("aria-expanded", false);
      this.controls.removeAttribute("donthide");

      if (
        this.controls.getAttribute("keying") ||
        this.isCurrentHover ||
        this.controls.getAttribute("showing")
      ) {
        return;
      }

      this.hideVideoControls();
    } else {
      this.settingsPanel.classList.remove("hide");
      this.closedCaptionButton.setAttribute("aria-expanded", true);
      this.controls.setAttribute("donthide", true);
      this.showVideoControls();

      if (options?.isKeyboard) {
        document.querySelector("#subtitles-toggle").focus();
      }
    }
  },

  onClose() {
    this.actor.sendAsyncMessage("PictureInPicture:Pause", {
      reason: "pip-closed",
    });
    this.closePipWindow({ reason: "closeButton" });
  },

  closeFromForeground() {
    PictureInPicture.closeSinglePipWindow({
      reason: "foregrounded",
      actorRef: this.actor,
    });
  },

  fullscreenModeToggle() {
    if (this.isFullscreen) {
      document.exitFullscreen();
    } else {
      this.deferredResize = {
        left: window.screenX,
        top: window.screenY,
        width: window.innerWidth,
        height: window.innerHeight,
      };
      document.body.requestFullscreen();
    }
  },

  /**
   * Toggle the mute state of the video
   */
  toggleMute() {
    if (this.isMuted) {
      // We unmute in handleAudioScrubbing so no need to also do it here
      this.audioScrubber.max = 1;
      this.handleAudioScrubbing(this.lastVolume ?? 1);
    } else {
      this.lastVolume = this.audioScrubber.value;
      this.actor.sendAsyncMessage("PictureInPicture:Mute");
    }
  },

  resizeToVideo(rect) {
    if (this.isFullscreen) {
      // We store the size and position because resizing the PiP window
      // while fullscreened will cause issues
      this.deferredResize = rect;
    } else {
      let { left, top, width, height } = rect;
      window.resizeTo(width, height);
      window.moveTo(left, top);
    }
  },

  onKeyDown(event) {
    // We don't want to send a keydown event if the event target was one of the
    // font sizes in the settings panel
    if (
      event.target.parentElement?.parentElement?.classList?.contains(
        "font-size-selection"
      )
    ) {
      return;
    }

    let eventKeys = {
      altKey: event.altKey,
      shiftKey: event.shiftKey,
      metaKey: event.metaKey,
      ctrlKey: event.ctrlKey,
      keyCode: event.keyCode,
    };

    // If the up or down arrow is pressed while the scrubber is focused then we
    // want to hijack these keydown events to act as left or right arrows
    // respectively to correctly seek the video.
    if (
      event.target.id === "scrubber" &&
      event.keyCode === window.KeyEvent.DOM_VK_UP
    ) {
      eventKeys.keyCode = window.KeyEvent.DOM_VK_RIGHT;
    } else if (
      event.target.id === "scrubber" &&
      event.keyCode === window.KeyEvent.DOM_VK_DOWN
    ) {
      eventKeys.keyCode = window.KeyEvent.DOM_VK_LEFT;
    }

    // If the left or right arrow is pressed while the audio scrubber is focused
    // then we want to hijack these keydown events to act as up or down arrows
    // respectively to correctly change the volume.
    if (
      event.target.id === "audio-scrubber" &&
      event.keyCode === window.KeyEvent.DOM_VK_RIGHT
    ) {
      eventKeys.keyCode = window.KeyEvent.DOM_VK_UP;
    } else if (
      event.target.id === "audio-scrubber" &&
      event.keyCode === window.KeyEvent.DOM_VK_LEFT
    ) {
      eventKeys.keyCode = window.KeyEvent.DOM_VK_DOWN;
    }

    // If the keydown event was one of the arrow keys and the scrubber or the
    // audio scrubber was focused then we want to prevent the subsequent input
    // event from overwriting the keydown event.
    if (
      event.target.id === "audio-scrubber" ||
      (event.target.id === "scrubber" &&
        [
          window.KeyEvent.DOM_VK_LEFT,
          window.KeyEvent.DOM_VK_RIGHT,
          window.KeyEvent.DOM_VK_UP,
          window.KeyEvent.DOM_VK_DOWN,
        ].includes(event.keyCode))
    ) {
      this.preventNextInputEvent = true;
    }

    this.actor.sendAsyncMessage("PictureInPicture:KeyDown", eventKeys);
  },

  onSubtitleChange(size) {
    Services.prefs.setCharPref(TEXT_TRACK_FONT_SIZE_PREF, size);

    this.actor.sendAsyncMessage("PictureInPicture:ChangeFontSizeTextTracks");
  },

  onToggleChange() {
    // The subtitles toggle has been click in the settings panel so we toggle
    // the overlay above the font sizes and send a message to toggle the
    // visibility of the subtitles and set the toggle pref
    document
      .querySelector(".font-size-selection")
      .classList.toggle("font-size-overlay");
    this.actor.sendAsyncMessage("PictureInPicture:ToggleTextTracks");

    this.captionsToggleEnabled = !this.captionsToggleEnabled;
    Services.prefs.setBoolPref(
      CAPTIONS_TOGGLE_ENABLED_PREF,
      this.captionsToggleEnabled
    );
  },

  /**
   * PiP Corner Snapping Helper Function
   * Determines the quadrant the PiP window is currently in.
   */
  determineCurrentQuadrant() {
    // Determine center coordinates of window.
    let windowCenterX = window.screenX + window.innerWidth / 2;
    let windowCenterY = window.screenY + window.innerHeight / 2;
    let quadrant = null;
    let halfWidth = window.screen.availLeft + window.screen.availWidth / 2;
    let halfHeight = window.screen.availTop + window.screen.availHeight / 2;

    let leftHalf = windowCenterX < halfWidth;
    let rightHalf = windowCenterX > halfWidth;
    let topHalf = windowCenterY < halfHeight;
    let bottomHalf = windowCenterY > halfHeight;

    if (leftHalf && topHalf) {
      quadrant = TOP_LEFT_QUADRANT;
    } else if (rightHalf && topHalf) {
      quadrant = TOP_RIGHT_QUADRANT;
    } else if (leftHalf && bottomHalf) {
      quadrant = BOTTOM_LEFT_QUADRANT;
    } else if (rightHalf && bottomHalf) {
      quadrant = BOTTOM_RIGHT_QUADRANT;
    }
    return quadrant;
  },

  /**
   * Helper function to actually move/snap the PiP window.
   * Moves the PiP window to the top right.
   */
  moveToTopRight() {
    window.moveTo(
      window.screen.availLeft + window.screen.availWidth - window.innerWidth,
      window.screen.availTop
    );
  },

  /**
   * Moves the PiP window to the top left.
   */
  moveToTopLeft() {
    window.moveTo(window.screen.availLeft, window.screen.availTop);
  },

  /**
   * Moves the PiP window to the bottom right.
   */
  moveToBottomRight() {
    window.moveTo(
      window.screen.availLeft + window.screen.availWidth - window.innerWidth,
      window.screen.availTop + window.screen.availHeight - window.innerHeight
    );
  },

  /**
   * Moves the PiP window to the bottom left.
   */
  moveToBottomLeft() {
    window.moveTo(
      window.screen.availLeft,
      window.screen.availTop + window.screen.availHeight - window.innerHeight
    );
  },

  /**
   * Uses the PiP window's change in position to determine which direction
   * the window has been moved in.
   */
  determineDirectionDragged() {
    // Determine change in window location.
    let deltaX = this.oldMouseUpWindowX - window.screenX;
    let deltaY = this.oldMouseUpWindowY - window.screenY;
    let dragDirection = "";

    if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX < 0) {
      dragDirection = "draggedRight";
    } else if (Math.abs(deltaX) > Math.abs(deltaY) && deltaX > 0) {
      dragDirection = "draggedLeft";
    } else if (Math.abs(deltaX) < Math.abs(deltaY) && deltaY < 0) {
      dragDirection = "draggedDown";
    } else if (Math.abs(deltaX) < Math.abs(deltaY) && deltaY > 0) {
      dragDirection = "draggedUp";
    }
    return dragDirection;
  },

  /**
   * Event handler for "mouseup" events on the PiP window.
   *
   * @param {Event} event
   *  Event context details
   */
  onMouseUp(event) {
    // Corner snapping changes start here.
    // Check if metakey pressed and macOS
    let quadrant = this.determineCurrentQuadrant();
    let dragAction = this.determineDirectionDragged();

    if (
      ((event.ctrlKey && AppConstants.platform !== "macosx") ||
        (event.metaKey && AppConstants.platform === "macosx")) &&
      dragAction
    ) {
      // Moving logic based on current quadrant and direction of drag.
      switch (quadrant) {
        case TOP_RIGHT_QUADRANT:
          switch (dragAction) {
            case "draggedRight":
              this.moveToTopRight();
              break;
            case "draggedLeft":
              this.moveToTopLeft();
              break;
            case "draggedDown":
              this.moveToBottomRight();
              break;
            case "draggedUp":
              this.moveToTopRight();
              break;
          }
          break;
        case TOP_LEFT_QUADRANT:
          switch (dragAction) {
            case "draggedRight":
              this.moveToTopRight();
              break;
            case "draggedLeft":
              this.moveToTopLeft();
              break;
            case "draggedDown":
              this.moveToBottomLeft();
              break;
            case "draggedUp":
              this.moveToTopLeft();
              break;
          }
          break;
        case BOTTOM_LEFT_QUADRANT:
          switch (dragAction) {
            case "draggedRight":
              this.moveToBottomRight();
              break;
            case "draggedLeft":
              this.moveToBottomLeft();
              break;
            case "draggedDown":
              this.moveToBottomLeft();
              break;
            case "draggedUp":
              this.moveToTopLeft();
              break;
          }
          break;
        case BOTTOM_RIGHT_QUADRANT:
          switch (dragAction) {
            case "draggedRight":
              this.moveToBottomRight();
              break;
            case "draggedLeft":
              this.moveToBottomLeft();
              break;
            case "draggedDown":
              this.moveToBottomRight();
              break;
            case "draggedUp":
              this.moveToTopRight();
              break;
          }
          break;
      } // Switch close.
    } // Metakey close.
    this.oldMouseUpWindowX = window.screenX;
    this.oldMouseUpWindowY = window.screenY;
  },

  /**
   * Event handler for mousemove the PiP Window
   */
  onMouseMove() {
    if (this.isFullscreen) {
      this.revealControls(false);
    }
  },

  onMouseEnter() {
    if (!this.isFullscreen) {
      this.isCurrentHover = true;
      this.showVideoControls();
    }
  },

  onMouseLeave() {
    if (!this.isFullscreen) {
      this.isCurrentHover = false;
      if (
        !this.controls.getAttribute("showing") &&
        !this.controls.getAttribute("keying") &&
        !this.controls.getAttribute("donthide")
      ) {
        this.hideVideoControls();
      }
    }
  },

  enableSubtitlesButton() {
    this.closedCaptionButton.disabled = false;

    this.alignEndControlsButtonTooltips();
    this.captionsToggleEnabled = true;
    // If the CAPTIONS_TOGGLE_ENABLED_PREF pref is false then we will click
    // the UI toggle to change the toggle to unchecked. This will call
    // onToggleChange where this.captionsToggleEnabled will be updated
    if (!Services.prefs.getBoolPref(CAPTIONS_TOGGLE_ENABLED_PREF, true)) {
      document.querySelector("#subtitles-toggle").click();
    }
  },

  disableSubtitlesButton() {
    this.closedCaptionButton.disabled = true;

    this.alignEndControlsButtonTooltips();
  },

  /**
   * Sets focus state inline end tooltip for rightmost playback controls
   */
  alignEndControlsButtonTooltips() {
    let audioBtn = document.getElementById("audio");
    let width = window.outerWidth;

    if (300 < width && width <= 400) {
      audioBtn.classList.replace("center-tooltip", "inline-end-tooltip");
    } else {
      audioBtn.classList.replace("inline-end-tooltip", "center-tooltip");
    }
  },

  /**
   * Event handler for resizing the PiP Window
   *
   * @param {Event} event
   *  Event context data object
   */
  onResize() {
    this.toggleSubtitlesSettingsPanel({ forceHide: true });
    this.resizeDebouncer.disarm();
    this.resizeDebouncer.arm();
  },

  /**
   * Event handler for user issued commands
   *
   * @param {Event} event
   *  Event context data object
   */
  onCommand() {
    this.closePipWindow({ reason: "shortcut" });
  },

  get controls() {
    delete this.controls;
    return (this.controls = document.getElementById("controls"));
  },

  get scrubber() {
    delete this.scrubber;
    return (this.scrubber = document.getElementById("scrubber"));
  },

  get audioScrubber() {
    delete this.audioScrubber;
    return (this.audioScrubber = document.getElementById("audio-scrubber"));
  },

  get timestamp() {
    delete this.timestamp;
    return (this.timestamp = document.getElementById("timestamp"));
  },

  get controlsBottom() {
    delete this.controlsBottom;
    return (this.controlsBottom = document.getElementById("controls-bottom"));
  },

  get seekBackward() {
    delete this.seekBackward;
    return (this.seekBackward = document.getElementById("seekBackward"));
  },

  get seekForward() {
    delete this.seekForward;
    return (this.seekForward = document.getElementById("seekForward"));
  },

  get closedCaptionButton() {
    delete this.closedCaptionButton;
    return (this.closedCaptionButton =
      document.getElementById("closed-caption"));
  },

  get settingsPanel() {
    delete this.settingsPanel;
    return (this.settingsPanel = document.getElementById("settings"));
  },

  _isPlaying: false,
  /**
   * GET isPlaying returns true if the video is currently playing.
   *
   * SET isPlaying to true if the video is playing, false otherwise. This will
   * update the internal state and displayed controls.
   *
   * @type {Boolean}
   */
  get isPlaying() {
    return this._isPlaying;
  },

  set isPlaying(isPlaying) {
    this._isPlaying = isPlaying;
    this.controls.classList.toggle("playing", isPlaying);
    let strId = isPlaying
      ? `pictureinpicture-pause-btn`
      : `pictureinpicture-play-btn`;
    this.setupTooltip("playpause", strId);

    if (
      !this._isInitialized ||
      this.isCurrentHover ||
      this.controls.getAttribute("keying")
    ) {
      return;
    }

    if (!isPlaying) {
      this.revealControls(true);
    } else {
      this.revealControls(false);
    }
  },

  _isMuted: false,
  /**
   * GET isMuted returns true if the video is currently muted.
   *
   * SET isMuted to true if the video is muted, false otherwise. This will
   * update the internal state and displayed controls.
   *
   * @type {Boolean}
   */
  get isMuted() {
    return this._isMuted;
  },

  set isMuted(isMuted) {
    this._isMuted = isMuted;
    if (!isMuted) {
      this.audioScrubber.max = 1;
    } else if (!this.audioScrubbing) {
      this.audioScrubber.max = 0;
    }
    this.controls.classList.toggle("muted", isMuted);
    let strId = isMuted
      ? `pictureinpicture-unmute-btn`
      : `pictureinpicture-mute-btn`;
    let shortcutId = isMuted ? "unMuteShortcut" : "muteShortcut";
    this.setupTooltip("audio", strId, shortcutId);
  },

  /**
   * GET isFullscreen returns true if the video is running in fullscreen mode
   *
   * @returns {boolean}
   */
  get isFullscreen() {
    return document.fullscreenElement == document.body;
  },

  /**
   * Used for recording telemetry in Picture-in-Picture.
   *
   * @param {string} type
   *   The type of PiP event being recorded.
   * @param {object} args
   *   The data to pass to telemetry when the event is recorded.
   */
  recordEvent(type, args) {
    Services.telemetry.recordEvent(
      "pictureinpicture",
      type,
      "player",
      this.id,
      args
    );
  },

  /**
   * Send a message to PiPChild to adjust the subtitles position
   * so that subtitles are visible when showing video controls.
   */
  showVideoControls() {
    // offsetParent returns null when the element or any ancestor has display: none
    // See https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetParent
    this.actor.sendAsyncMessage("PictureInPicture:ShowVideoControls", {
      isFullscreen: this.isFullscreen,
      isVideoControlsShowing: true,
      playerBottomControlsDOMRect: this.controlsBottom.getBoundingClientRect(),
      isScrubberShowing: !!this.scrubber.offsetParent,
    });
  },

  /**
   * Send a message to PiPChild to adjust the subtitles position
   * so that subtitles take up remaining space when hiding video controls.
   */
  hideVideoControls() {
    this.actor.sendAsyncMessage("PictureInPicture:HideVideoControls", {
      isFullscreen: this.isFullscreen,
      isVideoControlsShowing: false,
      playerBottomControlsDOMRect: null,
    });
  },

  /**
   * Makes the player controls visible.
   *
   * @param {Boolean} revealIndefinitely
   *   If false, this will hide the controls again after
   *   CONTROLS_FADE_TIMEOUT_MS milliseconds has passed. If true, the controls
   *   will remain visible until revealControls is called again with
   *   revealIndefinitely set to false.
   */
  revealControls(revealIndefinitely) {
    clearTimeout(this.showingTimeout);
    this.showingTimeout = null;

    this.controls.setAttribute("showing", true);

    if (!this.isFullscreen) {
      // revealControls() is called everytime we hover over fullscreen pip window.
      // Only communicate with pipchild when not in fullscreen mode for performance reasons.
      this.showVideoControls();
    }

    if (!revealIndefinitely) {
      this.showingTimeout = setTimeout(() => {
        const isHoverOverControlItem = this.controls.querySelector(
          ".control-item:hover"
        );
        if (this.isFullscreen && isHoverOverControlItem) {
          return;
        }
        this.controls.removeAttribute("showing");

        if (
          !this.isFullscreen &&
          !this.isCurrentHover &&
          !this.controls.getAttribute("keying") &&
          !this.controls.getAttribute("donthide")
        ) {
          this.hideVideoControls();
        }
      }, CONTROLS_FADE_TIMEOUT_MS);
    }
  },

  /**
   * Given a width and height for a video, computes the minimum dimensions for
   * the player window, and then sets them on the root element.
   *
   * This is currently only used on Linux GTK, where the OS doesn't already
   * impose a minimum window size. For other platforms, this function is a
   * no-op.
   *
   * @param {Number} width
   *   The width of the video being played.
   * @param {Number} height
   *   The height of the video being played.
   */
  computeAndSetMinimumSize(width, height) {
    if (!AppConstants.MOZ_WIDGET_GTK) {
      return;
    }

    // Using inspection, these seem to be the right minimums for each dimension
    // so that the controls don't get too crowded.
    const MIN_WIDTH = 120;
    const MIN_HEIGHT = 80;

    let resultWidth = width;
    let resultHeight = height;
    let aspectRatio = width / height;

    // Take the smaller of the two dimensions, and set it to the minimum.
    // Then calculate the other dimension using the aspect ratio to get
    // both minimums.
    if (width < height) {
      resultWidth = MIN_WIDTH;
      resultHeight = Math.round(MIN_WIDTH / aspectRatio);
    } else {
      resultHeight = MIN_HEIGHT;
      resultWidth = Math.round(MIN_HEIGHT * aspectRatio);
    }

    document.documentElement.style.minWidth = resultWidth + "px";
    document.documentElement.style.minHeight = resultHeight + "px";
  },
};
PK
!<mg�;chrome/toolkit/content/global/pictureinpicture/player.xhtml<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
      windowtype="Toolkit:PictureInPicture"
      macnativefullscreen="true"
      chromemargin="0,0,0,0"
      scrolling="false"
      >
  <head>
    <meta charset="utf-8"/>
    <link rel="stylesheet" href="chrome://global/skin/pictureinpicture/player.css"/>
    <link rel="localization" href="toolkit/pictureinpicture/pictureinpicture.ftl"/>
    <link rel="localization" href="browser/browserSets.ftl"/>
    <script src="chrome://global/content/pictureinpicture/player.js"></script>
    <title data-l10n-id="pictureinpicture-player-title"></title>
  </head>

  <body>
    <xul:commandset>
      <xul:command id="View:PictureInPicture" oncommand="Player.onCommand(event);"/>
      <xul:command id="View:Fullscreen" oncommand="Player.fullscreenModeToggle(event);"/>
    </xul:commandset>

    <xul:keyset>
      <xul:key id="closeShortcut" key="W" modifiers="accel"/>
      <xul:key id="muteShortcut" key="↓" modifiers="accel"/>
      <xul:key id="unMuteShortcut" key="↑" modifiers="accel"/>
      <xul:key data-l10n-id="picture-in-picture-toggle-shortcut" command="View:PictureInPicture" modifiers="accel,shift"/>
      <xul:key data-l10n-id="picture-in-picture-toggle-shortcut-alt" command="View:PictureInPicture" modifiers="accel,shift"/>
      <xul:key id="fullscreenToggleShortcut" data-l10n-id="pictureinpicture-toggle-fullscreen-shortcut" command="View:Fullscreen"/>
    </xul:keyset>

    <div class="player-holder">
      <xul:browser type="content" primary="true" remote="true" remoteType="web" id="browser" tabindex="-1"></xul:browser>
    </div>
    <div id="controls" dir="ltr">
      <button id="close"
      class="control-item control-button tooltip-under-controls" data-l10n-attrs="tooltip"
      tabindex="10"
      />
      <button id="unpip"
      class="control-item control-button tooltip-under-controls" data-l10n-id="pictureinpicture-unpip-btn" data-l10n-attrs="tooltip"
      tabindex="9"
      />
    <div id="controls-bottom-gradient" class="control-item"></div>
    <div id="controls-bottom">
      <div class="controls-bottom-upper">
          <div class="scrubber-no-drag">
            <input id="scrubber" class="control-item" min="0" max="1" step=".001" type="range" tabindex="11" hidden="true"/>
          </div>
      </div>
      <div class="controls-bottom-lower">
        <div class="start-controls">
          <div id="timestamp" class="control-item" hidden="true"></div>
        </div>
        <div class="center-controls">
          <button id="seekBackward" class="control-item control-button tooltip-over-controls center-tooltip" hidden="true" data-l10n-id="pictureinpicture-seekbackward-btn" data-l10n-attrs="tooltip" tabindex="12"></button>
          <button id="playpause" class="control-item control-button tooltip-over-controls center-tooltip" tabindex="1"
                  data-l10n-id="pictureinpicture-pause-btn" data-l10n-attrs="tooltip"/>
          <button id="seekForward" class="control-item control-button tooltip-over-controls center-tooltip" hidden="true" data-l10n-id="pictureinpicture-seekforward-btn" data-l10n-attrs="tooltip" tabindex="2"></button>
        </div>
        <div class="end-controls">
          <button id="audio" class="control-item control-button tooltip-over-controls center-tooltip" hidden="true" data-l10n-attrs="tooltip" tabindex="3"/>
          <input id="audio-scrubber" class="control-item" min="0" max="1" step=".001" type="range" tabindex="4" hidden="true"/>
          <button id="closed-caption" class="control-item control-button tooltip-over-controls center-tooltip closed-caption" hidden="true" disabled="true" data-l10n-id="pictureinpicture-subtitles-btn" data-l10n-attrs="tooltip" tabindex="5"></button>
          <div id="settings" class="hide panel">
            <fieldset class="box panel-fieldset">
              <legend class="a11y-only panel-legend" data-l10n-id="pictureinpicture-subtitles-panel-accessible"></legend>
              <div class="subtitle-grid">
                <label id="subtitles-toggle-label" data-l10n-id="pictureinpicture-subtitles-label" class="bold" for="subtitles-toggle"></label>
                <label class="switch">
                  <input id="subtitles-toggle" type="checkbox" tabindex="6" checked=""/>
                  <span class="slider" role="presentation"></span>
                </label>
              </div>
              <div class="grey-line"></div>
              <fieldset class="font-size-selection panel-fieldset">
                <legend data-l10n-id="pictureinpicture-font-size-label" class="bold panel-legend"></legend>
                <div id="font-size-selection-radio-small" class="font-size-selection-radio">
                  <input id="small" type="radio" name="cc-size" tabindex="7"/>
                  <label data-l10n-id="pictureinpicture-font-size-small" for="small"></label>
                </div>
                <div id="font-size-selection-radio-medium" class="font-size-selection-radio">
                  <input id="medium" type="radio" name="cc-size" tabindex="7"/>
                  <label data-l10n-id="pictureinpicture-font-size-medium" for="medium"></label>
                </div>
                <div id="font-size-selection-radio-large" class="font-size-selection-radio">
                  <input id="large" type="radio" name="cc-size" tabindex="7"/>
                  <label data-l10n-id="pictureinpicture-font-size-large" for="large"></label>
                </div>
              </fieldset>
            </fieldset>
            <div class="arrow"></div>
          </div>
          <button id="fullscreen" class="control-item control-button tooltip-over-controls inline-end-tooltip" hidden="true" data-l10n-attrs="tooltip" tabindex="8"></button>
        </div>
      </div>
    </div>
  </div>
  </body>
</html>
PK
!<��\�L�L4chrome/toolkit/content/global/preferencesBindings.js/* - This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this file,
   - You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// We attach Preferences to the window object so other contexts (tests, JSMs)
// have access to it.
const Preferences = (window.Preferences = (function () {
  const { EventEmitter } = ChromeUtils.importESModule(
    "resource://gre/modules/EventEmitter.sys.mjs"
  );

  const lazy = {};
  ChromeUtils.defineESModuleGetters(lazy, {
    DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  });

  function getElementsByAttribute(name, value) {
    // If we needed to defend against arbitrary values, we would escape
    // double quotes (") and escape characters (\) in them, i.e.:
    //   ${value.replace(/["\\]/g, '\\$&')}
    return value
      ? document.querySelectorAll(`[${name}="${value}"]`)
      : document.querySelectorAll(`[${name}]`);
  }

  const domContentLoadedPromise = new Promise(resolve => {
    window.addEventListener("DOMContentLoaded", resolve, {
      capture: true,
      once: true,
    });
  });

  const Preferences = {
    _all: {},

    _add(prefInfo) {
      if (this._all[prefInfo.id]) {
        throw new Error(`preference with id '${prefInfo.id}' already added`);
      }
      const pref = new Preference(prefInfo);
      this._all[pref.id] = pref;
      domContentLoadedPromise.then(() => {
        if (!this.updateQueued) {
          pref.updateElements();
        }
      });
      return pref;
    },

    add(prefInfo) {
      const pref = this._add(prefInfo);
      return pref;
    },

    addAll(prefInfos) {
      prefInfos.map(prefInfo => this._add(prefInfo));
    },

    get(id) {
      return this._all[id] || null;
    },

    getAll() {
      return Object.values(this._all);
    },

    defaultBranch: Services.prefs.getDefaultBranch(""),

    get type() {
      return document.documentElement.getAttribute("type") || "";
    },

    get instantApply() {
      // The about:preferences page forces instantApply.
      // TODO: Remove forceEnableInstantApply in favor of always applying in a
      // parent and never applying in a child (bug 1775386).
      if (this._instantApplyForceEnabled) {
        return true;
      }

      // Dialogs of type="child" are never instantApply.
      return this.type !== "child";
    },

    _instantApplyForceEnabled: false,

    // Override the computed value of instantApply for this window.
    forceEnableInstantApply() {
      this._instantApplyForceEnabled = true;
    },

    observe(subject, topic, data) {
      const pref = this._all[data];
      if (pref) {
        pref.value = pref.valueFromPreferences;
      }
    },

    updateQueued: false,

    queueUpdateOfAllElements() {
      if (this.updateQueued) {
        return;
      }

      this.updateQueued = true;

      Services.tm.dispatchToMainThread(() => {
        let startTime = performance.now();

        const elements = getElementsByAttribute("preference");
        for (const element of elements) {
          const id = element.getAttribute("preference");
          let preference = this.get(id);
          if (!preference) {
            console.error(`Missing preference for ID ${id}`);
            continue;
          }

          preference.setElementValue(element);
        }

        ChromeUtils.addProfilerMarker(
          "Preferences",
          { startTime },
          `updateAllElements: ${elements.length} preferences updated`
        );

        this.updateQueued = false;
      });
    },

    onUnload() {
      Services.prefs.removeObserver("", this);
    },

    QueryInterface: ChromeUtils.generateQI(["nsITimerCallback", "nsIObserver"]),

    _deferredValueUpdateElements: new Set(),

    writePreferences(aFlushToDisk) {
      // Write all values to preferences.
      if (this._deferredValueUpdateElements.size) {
        this._finalizeDeferredElements();
      }

      const preferences = Preferences.getAll();
      for (const preference of preferences) {
        preference.batching = true;
        preference.valueFromPreferences = preference.value;
        preference.batching = false;
      }
      if (aFlushToDisk) {
        Services.prefs.savePrefFile(null);
      }
    },

    getPreferenceElement(aStartElement) {
      let temp = aStartElement;
      while (
        temp &&
        temp.nodeType == Node.ELEMENT_NODE &&
        !temp.hasAttribute("preference")
      ) {
        temp = temp.parentNode;
      }
      return temp && temp.nodeType == Node.ELEMENT_NODE ? temp : aStartElement;
    },

    _deferredValueUpdate(aElement) {
      delete aElement._deferredValueUpdateTask;
      const prefID = aElement.getAttribute("preference");
      const preference = Preferences.get(prefID);
      const prefVal = preference.getElementValue(aElement);
      preference.value = prefVal;
      this._deferredValueUpdateElements.delete(aElement);
    },

    _finalizeDeferredElements() {
      for (const el of this._deferredValueUpdateElements) {
        if (el._deferredValueUpdateTask) {
          el._deferredValueUpdateTask.finalize();
        }
      }
    },

    userChangedValue(aElement) {
      const element = this.getPreferenceElement(aElement);
      if (element.hasAttribute("preference")) {
        if (element.getAttribute("delayprefsave") != "true") {
          const preference = Preferences.get(
            element.getAttribute("preference")
          );
          const prefVal = preference.getElementValue(element);
          preference.value = prefVal;
        } else {
          if (!element._deferredValueUpdateTask) {
            element._deferredValueUpdateTask = new lazy.DeferredTask(
              this._deferredValueUpdate.bind(this, element),
              1000
            );
            this._deferredValueUpdateElements.add(element);
          } else {
            // Each time the preference is changed, restart the delay.
            element._deferredValueUpdateTask.disarm();
          }
          element._deferredValueUpdateTask.arm();
        }
      }
    },

    onCommand(event) {
      // This "command" event handler tracks changes made to preferences by
      // the user in this window.
      if (event.sourceEvent) {
        event = event.sourceEvent;
      }
      this.userChangedValue(event.target);
    },

    onChange(event) {
      // This "change" event handler tracks changes made to preferences by
      // the user in this window.
      this.userChangedValue(event.target);
    },

    onInput(event) {
      // This "input" event handler tracks changes made to preferences by
      // the user in this window.
      this.userChangedValue(event.target);
    },

    _fireEvent(aEventName, aTarget) {
      try {
        const event = new CustomEvent(aEventName, {
          bubbles: true,
          cancelable: true,
        });
        return aTarget.dispatchEvent(event);
      } catch (e) {
        console.error(e);
      }
      return false;
    },

    onDialogAccept(event) {
      let dialog = document.querySelector("dialog");
      if (!this._fireEvent("beforeaccept", dialog)) {
        event.preventDefault();
        return false;
      }
      this.writePreferences(true);
      return true;
    },

    close(event) {
      if (Preferences.instantApply) {
        window.close();
      }
      event.stopPropagation();
      event.preventDefault();
    },

    handleEvent(event) {
      switch (event.type) {
        case "toggle":
        case "change":
          return this.onChange(event);
        case "command":
          return this.onCommand(event);
        case "dialogaccept":
          return this.onDialogAccept(event);
        case "input":
          return this.onInput(event);
        case "unload":
          return this.onUnload(event);
        default:
          return undefined;
      }
    },

    _syncFromPrefListeners: new WeakMap(),
    _syncToPrefListeners: new WeakMap(),

    addSyncFromPrefListener(aElement, callback) {
      this._syncFromPrefListeners.set(aElement, callback);
      if (this.updateQueued) {
        return;
      }
      // Make sure elements are updated correctly with the listener attached.
      let elementPref = aElement.getAttribute("preference");
      if (elementPref) {
        let pref = this.get(elementPref);
        if (pref) {
          pref.updateElements();
        }
      }
    },

    addSyncToPrefListener(aElement, callback) {
      this._syncToPrefListeners.set(aElement, callback);
      if (this.updateQueued) {
        return;
      }
      // Make sure elements are updated correctly with the listener attached.
      let elementPref = aElement.getAttribute("preference");
      if (elementPref) {
        let pref = this.get(elementPref);
        if (pref) {
          pref.updateElements();
        }
      }
    },

    removeSyncFromPrefListener(aElement) {
      this._syncFromPrefListeners.delete(aElement);
    },

    removeSyncToPrefListener(aElement) {
      this._syncToPrefListeners.delete(aElement);
    },
  };

  Services.prefs.addObserver("", Preferences);
  window.addEventListener("toggle", Preferences);
  window.addEventListener("change", Preferences);
  window.addEventListener("command", Preferences);
  window.addEventListener("dialogaccept", Preferences);
  window.addEventListener("input", Preferences);
  window.addEventListener("select", Preferences);
  window.addEventListener("unload", Preferences, { once: true });

  class Preference extends EventEmitter {
    constructor({ id, type, inverted }) {
      super();
      this.on("change", this.onChange.bind(this));

      this._value = null;
      this.readonly = false;
      this._useDefault = false;
      this.batching = false;

      this.id = id;
      this.type = type;
      this.inverted = !!inverted;

      // In non-instant apply mode, we must try and use the last saved state
      // from any previous opens of a child dialog instead of the value from
      // preferences, to pick up any edits a user may have made.

      if (
        Preferences.type == "child" &&
        window.opener &&
        window.opener.Preferences &&
        window.opener.document.nodePrincipal.isSystemPrincipal
      ) {
        // Try to find the preference in the parent window.
        const preference = window.opener.Preferences.get(this.id);

        // Don't use the value setter here, we don't want updateElements to be
        // prematurely fired.
        this._value = preference ? preference.value : this.valueFromPreferences;
      } else {
        this._value = this.valueFromPreferences;
      }
    }

    reset() {
      // defer reset until preference update
      this.value = undefined;
    }

    _reportUnknownType() {
      const msg = `Preference with id=${this.id} has unknown type ${this.type}.`;
      Services.console.logStringMessage(msg);
    }

    setElementValue(aElement) {
      if (this.locked) {
        aElement.disabled = true;
      }
      if (aElement.labels?.length) {
        for (let label of aElement.labels) {
          label.toggleAttribute("disabled", this.locked);
        }
      }

      if (!this.isElementEditable(aElement)) {
        return;
      }

      let rv = undefined;

      if (Preferences._syncFromPrefListeners.has(aElement)) {
        rv = Preferences._syncFromPrefListeners.get(aElement)(aElement);
      }
      let val = rv;
      if (val === undefined) {
        val = Preferences.instantApply ? this.valueFromPreferences : this.value;
      }
      // if the preference is marked for reset, show default value in UI
      if (val === undefined) {
        val = this.defaultValue;
      }

      /**
       * Initialize a UI element property with a value. Handles the case
       * where an element has not yet had a XBL binding attached for it and
       * the property setter does not yet exist by setting the same attribute
       * on the XUL element using DOM apis and assuming the element's
       * constructor or property getters appropriately handle this state.
       */
      function setValue(element, attribute, value) {
        if (attribute in element) {
          element[attribute] = value;
        } else if (attribute === "checked" || attribute === "pressed") {
          // The "checked" attribute can't simply be set to the specified value;
          // it has to be set if the value is true and removed if the value
          // is false in order to be interpreted correctly by the element.
          if (value) {
            // In theory we can set it to anything; however xbl implementation
            // of `checkbox` only works with "true".
            element.setAttribute(attribute, "true");
          } else {
            element.removeAttribute(attribute);
          }
        } else {
          element.setAttribute(attribute, value);
        }
      }
      if (
        aElement.localName == "checkbox" ||
        aElement.localName == "moz-checkbox" ||
        (aElement.localName == "input" && aElement.type == "checkbox")
      ) {
        setValue(aElement, "checked", val);
      } else if (aElement.localName == "moz-toggle") {
        setValue(aElement, "pressed", val);
      } else {
        setValue(aElement, "value", val);
      }
    }

    getElementValue(aElement) {
      if (Preferences._syncToPrefListeners.has(aElement)) {
        try {
          const rv = Preferences._syncToPrefListeners.get(aElement)(aElement);
          if (rv !== undefined) {
            return rv;
          }
        } catch (e) {
          console.error(e);
        }
      }

      /**
       * Read the value of an attribute from an element, assuming the
       * attribute is a property on the element's node API. If the property
       * is not present in the API, then assume its value is contained in
       * an attribute, as is the case before a binding has been attached.
       */
      function getValue(element, attribute) {
        if (attribute in element) {
          return element[attribute];
        }
        return element.getAttribute(attribute);
      }
      let value;
      if (
        aElement.localName == "checkbox" ||
        aElement.localName == "moz-checkbox" ||
        (aElement.localName == "input" && aElement.type == "checkbox")
      ) {
        value = getValue(aElement, "checked");
      } else if (aElement.localName == "moz-toggle") {
        value = getValue(aElement, "pressed");
      } else {
        value = getValue(aElement, "value");
      }

      switch (this.type) {
        case "int":
          return parseInt(value, 10) || 0;
        case "bool":
          return typeof value == "boolean" ? value : value == "true";
      }
      return value;
    }

    isElementEditable(aElement) {
      switch (aElement.localName) {
        case "checkbox":
        case "input":
        case "radiogroup":
        case "textarea":
        case "menulist":
        case "moz-toggle":
        case "moz-checkbox":
          return true;
      }
      return false;
    }

    updateElements() {
      let startTime = performance.now();

      if (!this.id) {
        return;
      }

      const elements = getElementsByAttribute("preference", this.id);
      for (const element of elements) {
        this.setElementValue(element);
      }

      ChromeUtils.addProfilerMarker(
        "Preferences",
        { startTime, captureStack: true },
        `updateElements for ${this.id}`
      );
    }

    onChange() {
      this.updateElements();
    }

    get value() {
      return this._value;
    }

    set value(val) {
      if (this.value !== val) {
        this._value = val;
        if (Preferences.instantApply) {
          this.valueFromPreferences = val;
        }
        this.emit("change");
      }
    }

    get locked() {
      return Services.prefs.prefIsLocked(this.id);
    }

    updateControlDisabledState(val) {
      if (!this.id) {
        return;
      }

      val = val || this.locked;

      const elements = getElementsByAttribute("preference", this.id);
      for (const element of elements) {
        element.disabled = val;

        const labels = getElementsByAttribute("control", element.id);
        for (const label of labels) {
          label.disabled = val;
        }
      }
    }

    get hasUserValue() {
      return (
        Services.prefs.prefHasUserValue(this.id) && this.value !== undefined
      );
    }

    get defaultValue() {
      this._useDefault = true;
      const val = this.valueFromPreferences;
      this._useDefault = false;
      return val;
    }

    get _branch() {
      return this._useDefault ? Preferences.defaultBranch : Services.prefs;
    }

    get valueFromPreferences() {
      try {
        // Force a resync of value with preferences.
        switch (this.type) {
          case "int":
            return this._branch.getIntPref(this.id);
          case "bool": {
            const val = this._branch.getBoolPref(this.id);
            return this.inverted ? !val : val;
          }
          case "wstring":
            return this._branch.getComplexValue(
              this.id,
              Ci.nsIPrefLocalizedString
            ).data;
          case "string":
          case "unichar":
            return this._branch.getStringPref(this.id);
          case "fontname": {
            const family = this._branch.getStringPref(this.id);
            const fontEnumerator = Cc[
              "@mozilla.org/gfx/fontenumerator;1"
            ].createInstance(Ci.nsIFontEnumerator);
            return fontEnumerator.getStandardFamilyName(family);
          }
          case "file": {
            const f = this._branch.getComplexValue(this.id, Ci.nsIFile);
            return f;
          }
          default:
            this._reportUnknownType();
        }
      } catch (e) {}
      return null;
    }

    set valueFromPreferences(val) {
      // Exit early if nothing to do.
      if (this.readonly || this.valueFromPreferences == val) {
        return;
      }

      // The special value undefined means 'reset preference to default'.
      if (val === undefined) {
        Services.prefs.clearUserPref(this.id);
        return;
      }

      // Force a resync of preferences with value.
      switch (this.type) {
        case "int":
          Services.prefs.setIntPref(this.id, val);
          break;
        case "bool":
          Services.prefs.setBoolPref(this.id, this.inverted ? !val : val);
          break;
        case "wstring": {
          const pls = Cc["@mozilla.org/pref-localizedstring;1"].createInstance(
            Ci.nsIPrefLocalizedString
          );
          pls.data = val;
          Services.prefs.setComplexValue(
            this.id,
            Ci.nsIPrefLocalizedString,
            pls
          );
          break;
        }
        case "string":
        case "unichar":
        case "fontname":
          Services.prefs.setStringPref(this.id, val);
          break;
        case "file": {
          let lf;
          if (typeof val == "string") {
            lf = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
            lf.persistentDescriptor = val;
            if (!lf.exists()) {
              lf.initWithPath(val);
            }
          } else {
            lf = val.QueryInterface(Ci.nsIFile);
          }
          Services.prefs.setComplexValue(this.id, Ci.nsIFile, lf);
          break;
        }
        default:
          this._reportUnknownType();
      }
      if (!this.batching) {
        Services.prefs.savePrefFile(null);
      }
    }
  }

  return Preferences;
})());
PK
!<UJw��'chrome/toolkit/content/global/print.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:root {
  font: menu;
}

:root,
body {
  height: 100vh;
}

body {
  display: flex;
  flex-direction: column;
  justify-content: flex-start;
  overflow: hidden;
}

*[hidden] {
  display: none !important;
}

.section-block {
  margin: var(--space-large);

  .block-label {
    display: block;
    margin-bottom: var(--space-small);
  }
}

.row {
  display: flex;
  flex-direction: column;
  width: 100%;
  min-height: 1.8em;
  margin-block: var(--space-xxsmall);

  &.cols-2 {
    flex-direction: row;
    align-items: center;

    #scale &,
    #more-settings-options > & {
      /* The margin-block of the checkboxes/radiobuttons
         already provide the needed vertical spacing. */
      margin-block: 0;
    }
  }
}

hr {
  margin: 0 var(--space-small);
}

.header-container {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
  flex: 0 1 auto;
  padding: var(--space-small) var(--space-large);

  > h2 {
    margin: 0;
    line-height: 1;
  }
}

#sheet-count {
  font: message-box;
  padding-block: var(--space-xsmall);
  margin: 0;

  &[loading],
  body[invalid] & {
    visibility: hidden;
  }
}

#print {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  justify-content: flex-start;
  overflow: hidden;

  body[loading] & {
    visibility: hidden;
  }
}

.body-container {
  flex: 1 1 auto;
  overflow: auto;
}

select {
  margin: 0;
  width: 100%;

  &:not([size], [multiple])[iconic] {
    padding-inline-start: calc(8px + 16px + 4px); /* spacing before image + image width + spacing after image */
    background-size: auto 12px, 16px;
    background-position: right 19px center, left 8px center;

    &:dir(rtl) {
      background-position-x: left 19px, right 8px;
    }
  }
}

#printer-picker {
  background-image: url("chrome://global/skin/icons/arrow-down-12.svg"), url("chrome://global/skin/icons/print.svg");

  &[output="pdf"] {
    background-image: url("chrome://global/skin/icons/arrow-down-12.svg"), url("chrome://global/skin/icons/page-portrait.svg");
  }
}

input:is([type="number"], [type="text"]) {
  padding: var(--space-xxsmall);
  padding-inline-start: var(--space-small);
}

input[type="number"] {
  box-sizing: content-box;
  min-height: unset;
  padding: 0;
  padding-inline-start: var(--space-small);
  margin: 0;
  height: 1.5em;
  width: 4em;
}

input:is([type="checkbox"], [type="radio"]):disabled + label,
input[type="radio"]:disabled + span > label {
  opacity: 0.5;
}

#custom-range {
  margin: 0;
  margin-top: var(--space-xsmall);
  width: 100%;
}

#percent-scale {
  margin-inline-start: var(--space-xsmall);

  &:disabled {
    /* Let clicks on the disabled input select the radio button */
    pointer-events: none;
  }
}

.toggle-group-label {
  padding: var(--space-xsmall) var(--space-small);

  #landscape + &::before {
    content: url("chrome://global/skin/icons/page-landscape.svg");
  }

  #portrait + &::before {
    content: url("chrome://global/skin/icons/page-portrait.svg");
  }
}

#more-settings {
  > summary {
    list-style: none;
    display: flex;
    cursor: pointer;
    align-items: center;

    > .twisty {
      background-image: url("chrome://global/skin/icons/arrow-down-12.svg");
      background-repeat: no-repeat;
      background-position: center;
      -moz-context-properties: fill;
      fill: currentColor;
      width: 12px;
      height: 12px;
    }

    > .label {
      flex-grow: 1;
    }
  }

  &[open] > summary > .twisty {
    background-image: url("chrome://global/skin/icons/arrow-up-12.svg");
  }
}

.vertical-margins,
.horizontal-margins {
  display: flex;
  gap: var(--space-large);
  margin-block: var(--space-xsmall);

  > .margin-pair {
    width: calc(50% - (2 * var(--space-xsmall))); /* Half minus the gap */

    > .margin-descriptor {
      display: block;
      text-align: center;
      margin-top: var(--space-xxsmall);
    }

    > .margin-input {
      width: 100%;
      box-sizing: border-box;
    }
  }
}

.horizontal-margins:dir(rtl) {
  /* Swap between the left and right margin inputs to match their position
   * in the form to their physical location they represent. */
  flex-direction: row-reverse;
}

#open-dialog-link {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-xsmall);

  &::after {
    display: block;
    flex-shrink: 0;
    content: "";
    background: url(chrome://global/skin/icons/open-in-new.svg) no-repeat center;
    background-size: 12px;
    -moz-context-properties: fill;
    fill: currentColor;
    width: 12px;
    height: 12px;
  }

  &:dir(rtl)::after {
    transform: scaleX(-1);
  }
}

.footer-container {
  flex: 0 1 auto;
}

#print-progress {
  background-image: url("chrome://global/skin/icons/loading.svg");
  background-repeat: no-repeat;
  background-size: var(--size-item-small);
  background-position: left center;
  -moz-context-properties: fill;
  fill: var(--in-content-accent-color);
  padding-inline-start: calc(var(--size-item-small) + var(--space-xsmall));

  &:dir(rtl) {
    background-position-x: right;
  }
}

.error-message {
  color: var(--text-color-error);
  margin-top: var(--space-xsmall);
}
PK
!<�\��G�G(chrome/toolkit/content/global/print.html<!DOCTYPE html>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this file,
   - You can obtain one at http://mozilla.org/MPL/2.0/. -->
<html>
  <head>
    <meta charset="utf-8" />
    <title data-l10n-id="printui-title"></title>
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:;img-src data:; object-src 'none'"
    />

    <link rel="localization" href="toolkit/printing/printUI.ftl" />

    <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
    <link rel="stylesheet" href="chrome://global/content/toggle-group.css" />
    <link rel="stylesheet" href="chrome://global/content/print.css" />
    <script defer src="chrome://global/content/print.js"></script>
    <script
      type="module"
      src="chrome://global/content/elements/moz-button-group.mjs"
    ></script>
  </head>

  <body loading rendering>
    <template id="page-range-template">
      <select
        id="range-picker"
        name="page-range-type"
        data-l10n-id="printui-page-range-picker"
        is="setting-select"
      >
        <option
          value="all"
          selected
          data-l10n-id="printui-page-range-all"
        ></option>
        <option
          value="current"
          data-l10n-id="printui-page-range-current"
        ></option>
        <option value="odd" data-l10n-id="printui-page-range-odd"></option>
        <option
          value="even"
          name="even"
          data-l10n-id="printui-page-range-even"
        ></option>
        <option
          value="custom"
          data-l10n-id="printui-page-range-custom"
        ></option>
      </select>
      <input
        id="custom-range"
        type="text"
        disabled
        hidden
        data-l10n-id="printui-page-custom-range-input"
        aria-errormessage="error-invalid-range error-invalid-start-range-overflow"
      />
      <p
        id="error-invalid-range"
        hidden
        data-l10n-id="printui-error-invalid-range"
        class="error-message"
        role="alert"
        data-l10n-args='{ "numPages": 1 }'
      ></p>
      <p
        id="error-invalid-start-range-overflow"
        hidden
        data-l10n-id="printui-error-invalid-start-overflow"
        class="error-message"
        role="alert"
      ></p>
    </template>

    <template id="orientation-template">
      <input
        type="radio"
        name="orientation"
        id="portrait"
        value="0"
        checked
        class="toggle-group-input"
      />
      <label
        for="portrait"
        data-l10n-id="printui-portrait"
        class="toggle-group-label toggle-group-label-iconic"
      ></label>
      <input
        type="radio"
        name="orientation"
        id="landscape"
        value="1"
        class="toggle-group-input"
      />
      <label
        for="landscape"
        data-l10n-id="printui-landscape"
        class="toggle-group-label toggle-group-label-iconic"
      ></label>
    </template>

    <template id="twisty-summary-template">
      <span class="label"></span>
      <span class="twisty"></span>
    </template>

    <template id="scale-template">
      <div role="radiogroup" aria-labelledby="scale-label">
        <label class="row cols-2">
          <input
            type="radio"
            name="scale-choice"
            id="fit-choice"
            value="fit"
            checked
          />
          <span
            data-l10n-id="printui-scale-fit-to-page-width"
            class="col"
          ></span>
        </label>
        <label class="row cols-2" for="percent-scale-choice">
          <input type="radio" name="scale-choice" id="percent-scale-choice" />
          <span class="col">
            <span
              id="percent-scale-label"
              data-l10n-id="printui-scale-pcent"
            ></span>
            <!-- Note that here and elsewhere, we're setting aria-errormessage
                 attributes to a list of all possible errors. The a11y APIs
                 will filter this down to visible items only. -->
            <input
              id="percent-scale"
              is="setting-number"
              min="10"
              max="200"
              step="1"
              size="6"
              aria-labelledby="percent-scale-label"
              aria-errormessage="error-invalid-scale"
              disabled
              required
            />
          </span>
        </label>
        <p
          id="error-invalid-scale"
          hidden
          data-l10n-id="printui-error-invalid-scale"
          class="error-message"
          role="alert"
        ></p>
      </div>
    </template>

    <template id="copy-template">
      <input
        id="copies-count"
        is="setting-number"
        data-setting-name="numCopies"
        min="1"
        max="10000"
        class="copy-count-input"
        aria-errormessage="error-invalid-copies"
        required
      />
      <p
        id="error-invalid-copies"
        hidden
        data-l10n-id="printui-error-invalid-copies"
        class="error-message"
        role="alert"
      ></p>
    </template>

    <template id="margins-template">
      <label
        for="margins-picker"
        class="block-label"
        data-l10n-id="printui-margins"
      ></label>
      <select
        is="margins-select"
        id="margins-picker"
        name="margin-type"
        data-setting-name="margins"
      >
        <option value="default" data-l10n-id="printui-margins-default"></option>
        <option value="minimum" data-l10n-id="printui-margins-min"></option>
        <option value="none" data-l10n-id="printui-margins-none"></option>
        <option
          value="custom"
          data-unit-prefix-l10n-id="printui-margins-custom-"
        ></option>
      </select>
      <div id="custom-margins" class="margin-group" role="group" hidden>
        <div class="vertical-margins">
          <div class="margin-pair">
            <input
              is="setting-number"
              id="custom-margin-top"
              class="margin-input"
              aria-describedby="margins-custom-margin-top-desc"
              min="0"
              step="0.01"
              required
            />
            <label
              for="custom-margin-top"
              class="margin-descriptor"
              data-l10n-id="printui-margins-custom-top"
            ></label>
            <label
              hidden
              id="margins-custom-margin-top-desc"
              data-unit-prefix-l10n-id="printui-margins-custom-top-"
            ></label>
          </div>
          <div class="margin-pair">
            <input
              is="setting-number"
              id="custom-margin-bottom"
              class="margin-input"
              aria-describedby="margins-custom-margin-bottom-desc"
              min="0"
              step="0.01"
              required
            />
            <label
              for="custom-margin-bottom"
              class="margin-descriptor"
              data-l10n-id="printui-margins-custom-bottom"
            ></label>
            <label
              hidden
              id="margins-custom-margin-bottom-desc"
              data-unit-prefix-l10n-id="printui-margins-custom-bottom-"
            ></label>
          </div>
        </div>
        <div class="horizontal-margins">
          <div class="margin-pair">
            <input
              is="setting-number"
              id="custom-margin-left"
              class="margin-input"
              aria-describedby="margins-custom-margin-left-desc"
              min="0"
              step="0.01"
              required
            />
            <label
              for="custom-margin-left"
              class="margin-descriptor"
              data-l10n-id="printui-margins-custom-left"
            ></label>
            <label
              hidden
              id="margins-custom-margin-left-desc"
              data-unit-prefix-l10n-id="printui-margins-custom-left-"
            ></label>
          </div>
          <div class="margin-pair">
            <input
              is="setting-number"
              id="custom-margin-right"
              class="margin-input"
              aria-describedby="margins-custom-margin-right-desc"
              min="0"
              step="0.01"
              required
            />
            <label
              for="custom-margin-right"
              class="margin-descriptor"
              data-l10n-id="printui-margins-custom-right"
            ></label>
            <label
              hidden
              id="margins-custom-margin-right-desc"
              data-unit-prefix-l10n-id="printui-margins-custom-right-"
            ></label>
          </div>
        </div>
        <p
          id="error-invalid-margin"
          hidden
          data-l10n-id="printui-error-invalid-margin"
          class="error-message"
          role="alert"
        ></p>
      </div>
    </template>

    <header class="header-container" role="none">
      <h2 data-l10n-id="printui-title"></h2>
      <div aria-live="off">
        <p
          id="sheet-count"
          is="page-count"
          data-l10n-id="printui-sheets-count"
          data-l10n-args='{ "sheetCount": 0 }'
          loading
        ></p>
      </div>
    </header>

    <hr />

    <form id="print" is="print-form" aria-labelledby="page-header">
      <section class="body-container">
        <section id="destination" class="section-block">
          <label
            for="printer-picker"
            class="block-label"
            data-l10n-id="printui-destination-label"
          ></label>
          <select
            is="destination-picker"
            id="printer-picker"
            data-setting-name="printerName"
            iconic
          ></select>
        </section>
        <section id="settings">
          <section id="copies" class="section-block">
            <label
              for="copies-count"
              class="block-label"
              data-l10n-id="printui-copies-label"
            ></label>
            <copy-count-input></copy-count-input>
          </section>

          <section id="orientation" class="section-block">
            <label
              id="orientation-label"
              class="block-label"
              data-l10n-id="printui-orientation"
            ></label>
            <div
              is="orientation-input"
              class="toggle-group"
              role="radiogroup"
              aria-labelledby="orientation-label"
            ></div>
          </section>

          <section id="pages" class="section-block">
            <label
              for="range-picker"
              class="block-label"
              data-l10n-id="printui-page-range-label"
            ></label>
            <div id="page-range-input" is="page-range-input"></div>
          </section>

          <section id="color-mode" class="section-block">
            <label
              for="color-mode-picker"
              class="block-label"
              data-l10n-id="printui-color-mode-label"
            ></label>
            <select
              is="color-mode-select"
              id="color-mode-picker"
              data-setting-name="printInColor"
            >
              <option
                value="color"
                selected
                data-l10n-id="printui-color-mode-color"
              ></option>
              <option value="bw" data-l10n-id="printui-color-mode-bw"></option>
            </select>
          </section>

          <hr />

          <details id="more-settings" class="twisty">
            <summary
              class="block-label section-block"
              is="twisty-summary"
              data-open-l10n-id="printui-less-settings"
              data-closed-l10n-id="printui-more-settings"
            ></summary>

            <section id="paper-size" class="section-block">
              <label
                for="paper-size-picker"
                class="block-label"
                data-l10n-id="printui-paper-size-label"
              ></label>
              <select
                is="paper-size-select"
                id="paper-size-picker"
                data-setting-name="paperId"
              ></select>
            </section>

            <section id="scale" class="section-block">
              <label
                id="scale-label"
                class="block-label"
                data-l10n-id="printui-scale"
              ></label>
              <scale-input></scale-input>
            </section>

            <section id="pages-per-sheet" class="section-block">
              <label
                id="pages-per-sheet-label"
                for="pages-per-sheet-picker"
                class="block-label"
                data-l10n-id="printui-pages-per-sheet"
              ></label>
              <select
                is="setting-select"
                id="pages-per-sheet-picker"
                data-setting-name="numPagesPerSheet"
              >
                <option value="1">1</option>
                <option value="2">2</option>
                <option value="4">4</option>
                <option value="6">6</option>
                <option value="9">9</option>
                <option value="16">16</option>
              </select>
            </section>

            <section id="margins" class="section-block">
              <div id="margins-select" is="margins-select"></div>
            </section>

            <section id="two-sided-printing" class="section-block">
              <label
                class="block-label"
                for="duplex-select"
                data-l10n-id="printui-two-sided-printing"
              ></label>
              <select
                is="setting-select"
                id="duplex-select"
                name="duplex-type"
                data-setting-name="printDuplex"
              >
                <option
                  value="off"
                  data-l10n-id="printui-two-sided-printing-off"
                  selected
                ></option>
                <option
                  value="long-edge"
                  data-l10n-id="printui-two-sided-printing-long-edge"
                ></option>
                <option
                  value="short-edge"
                  data-l10n-id="printui-two-sided-printing-short-edge"
                ></option>
              </select>
            </section>

            <section id="source-version-section" class="section-block">
              <label
                id="source-version-label"
                data-l10n-id="printui-source-label"
              ></label>
              <div role="radiogroup" aria-labelledby="source-version-label">
                <label id="source-version-source" class="row cols-2">
                  <input
                    is="setting-radio"
                    name="source-version"
                    value="source"
                    id="source-version-source-radio"
                    data-setting-name="sourceVersion"
                    checked
                  />
                  <span data-l10n-id="printui-source-radio"></span>
                </label>
                <label id="source-version-selection" class="row cols-2">
                  <input
                    is="setting-radio"
                    name="source-version"
                    value="selection"
                    id="source-version-selection-radio"
                    data-setting-name="sourceVersion"
                  />
                  <span data-l10n-id="printui-selection-radio"></span>
                </label>
                <label id="source-version-simplified" class="row cols-2">
                  <input
                    is="setting-radio"
                    name="source-version"
                    value="simplified"
                    id="source-version-simplified-radio"
                    data-setting-name="sourceVersion"
                  />
                  <span data-l10n-id="printui-simplify-page-radio"></span>
                </label>
              </div>
            </section>

            <section id="more-settings-options" class="section-block">
              <label class="block-label" data-l10n-id="printui-options"></label>
              <label id="headers-footers" class="row cols-2">
                <input
                  is="setting-checkbox"
                  id="headers-footers-enabled"
                  data-setting-name="printFootersHeaders"
                />
                <span data-l10n-id="printui-headers-footers-checkbox"></span>
              </label>
              <label id="backgrounds" class="row cols-2">
                <input
                  is="print-backgrounds"
                  id="backgrounds-enabled"
                  data-setting-name="printBackgrounds"
                />
                <span data-l10n-id="printui-backgrounds-checkbox"></span>
              </label>
            </section>
          </details>
        </section>

        <section id="system-print" class="section-block">
          <a
            href="#"
            id="open-dialog-link"
            data-l10n-id="printui-system-dialog-link"
          ></a>
        </section>
      </section>

      <hr />

      <footer class="footer-container" id="print-footer" role="none">
        <p
          id="print-progress"
          class="section-block"
          data-l10n-id="printui-print-progress-indicator"
          hidden
        ></p>
        <moz-button-group id="button-container" class="section-block">
          <button
            id="print-button"
            class="primary"
            showfocus
            name="print"
            data-l10n-id="printui-primary-button"
            is="print-button"
            type="submit"
          ></button>
          <button
            id="cancel-button"
            name="cancel"
            data-l10n-id="printui-cancel-button"
            data-close-l10n-id="printui-close-button"
            data-cancel-l10n-id="printui-cancel-button"
            type="button"
            is="cancel-button"
          ></button>
        </moz-button-group>
      </footer>
    </form>
  </body>
</html>
PK
!<�0��c�c&chrome/toolkit/content/global/print.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { PrintUtils, Services, AppConstants } =
  window.docShell.chromeEventHandler.ownerGlobal;

ChromeUtils.defineESModuleGetters(this, {
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  DownloadPaths: "resource://gre/modules/DownloadPaths.sys.mjs",
});

const PDF_JS_URI = "resource://pdf.js/web/viewer.html";
const INPUT_DELAY_MS = Cu.isInAutomation ? 100 : 500;
const MM_PER_POINT = 25.4 / 72;
const INCHES_PER_POINT = 1 / 72;
const INCHES_PER_MM = 1 / 25.4;
const ourBrowser = window.docShell.chromeEventHandler;
const PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
  Ci.nsIPrintSettingsService
);

var logger = (function () {
  const getMaxLogLevel = () =>
    Services.prefs.getBoolPref("print.debug", false) ? "all" : "warn";

  let { ConsoleAPI } = ChromeUtils.importESModule(
    "resource://gre/modules/Console.sys.mjs"
  );
  // Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
  let _logger = new ConsoleAPI({
    prefix: "printUI",
    maxLogLevel: getMaxLogLevel(),
  });

  function onPrefChange() {
    if (_logger) {
      _logger.maxLogLevel = getMaxLogLevel();
    }
  }
  // Watch for pref changes and the maxLogLevel for the logger
  Services.prefs.addObserver("print.debug", onPrefChange);
  window.addEventListener("unload", () => {
    Services.prefs.removeObserver("print.debug", onPrefChange);
  });
  return _logger;
})();

function serializeSettings(settings) {
  let re = /^(k[A-Z]|resolution)/; // accessing settings.resolution throws an exception?
  let types = new Set(["string", "boolean", "number", "undefined"]);
  let nameValues = {};
  for (let key in settings) {
    try {
      if (!re.test(key) && types.has(typeof settings[key])) {
        nameValues[key] = settings[key];
      }
    } catch (e) {
      logger.warn("Exception accessing setting: ", key, e);
    }
  }
  return JSON.stringify(nameValues, null, 2);
}

let printPending = false;
let deferredTasks = [];
function createDeferredTask(fn, timeout) {
  let task = new DeferredTask(fn, timeout);
  deferredTasks.push(task);
  return task;
}

function cancelDeferredTasks() {
  for (let task of deferredTasks) {
    task.disarm();
  }
  PrintEventHandler._updatePrintPreviewTask?.disarm();
  deferredTasks = [];
}

document.addEventListener(
  "DOMContentLoaded",
  () => {
    const dialogBox = ourBrowser.closest(".dialogBox");
    if (!dialogBox) {
      return;
    }

    window._initialized = PrintEventHandler.init().catch(e => console.error(e));
    ourBrowser.setAttribute("flex", "0");
    ourBrowser.setAttribute("constrainpopups", "false");
    ourBrowser.classList.add("printSettingsBrowser");
    dialogBox.classList.add("printDialogBox");
  },
  { once: true }
);

window.addEventListener("dialogclosing", () => {
  cancelDeferredTasks();
});

window.addEventListener(
  "unload",
  () => {
    document.textContent = "";
  },
  { once: true }
);

var PrintEventHandler = {
  settings: null,
  defaultSettings: null,
  allPaperSizes: {},
  previewIsEmpty: false,
  _delayedChanges: {},
  _userChangedSettings: {},
  settingFlags: {
    margins: Ci.nsIPrintSettings.kInitSaveMargins,
    customMargins: Ci.nsIPrintSettings.kInitSaveMargins,
    orientation: Ci.nsIPrintSettings.kInitSaveOrientation,
    paperId:
      Ci.nsIPrintSettings.kInitSavePaperSize |
      Ci.nsIPrintSettings.kInitSaveUnwriteableMargins,
    printInColor: Ci.nsIPrintSettings.kInitSaveInColor,
    scaling: Ci.nsIPrintSettings.kInitSaveScaling,
    shrinkToFit: Ci.nsIPrintSettings.kInitSaveShrinkToFit,
    printDuplex: Ci.nsIPrintSettings.kInitSaveDuplex,
    printFootersHeaders:
      Ci.nsIPrintSettings.kInitSaveHeaderLeft |
      Ci.nsIPrintSettings.kInitSaveHeaderCenter |
      Ci.nsIPrintSettings.kInitSaveHeaderRight |
      Ci.nsIPrintSettings.kInitSaveFooterLeft |
      Ci.nsIPrintSettings.kInitSaveFooterCenter |
      Ci.nsIPrintSettings.kInitSaveFooterRight,
    printBackgrounds:
      Ci.nsIPrintSettings.kInitSaveBGColors |
      Ci.nsIPrintSettings.kInitSaveBGImages,
  },

  topContentTitle: null,
  topCurrentURI: null,
  activeContentTitle: null,
  activeCurrentURI: null,

  get activeURI() {
    return this.viewSettings.sourceVersion == "selection"
      ? this.activeCurrentURI
      : this.topCurrentURI;
  },
  get activeTitle() {
    return this.viewSettings.sourceVersion == "selection"
      ? this.activeContentTitle
      : this.topContentTitle;
  },

  // These settings do not have an associated pref value or flag, but
  // changing them requires us to update the print preview.
  _nonFlaggedUpdatePreviewSettings: new Set([
    "pageRanges",
    "numPagesPerSheet",
    "sourceVersion",
  ]),
  _noPreviewUpdateSettings: new Set(["numCopies", "printDuplex"]),

  async init() {
    Services.telemetry.scalarAdd("printing.preview_opened_tm", 1);

    this.printPreviewEl =
      ourBrowser.parentElement.querySelector("print-preview");

    // Do not keep a reference to source browser, it may mutate after printing
    // is initiated and the print preview clone must be a snapshot from the
    // time that the print was started.
    let sourceBrowsingContext = this.printPreviewEl.getSourceBrowsingContext();

    let args = window.arguments[0];
    this.printFrameOnly = args.getProperty("printFrameOnly");
    this.printSelectionOnly = args.getProperty("printSelectionOnly");
    this.isArticle = args.getProperty("isArticle");
    this.hasSelection = await PrintUtils.checkForSelection(
      sourceBrowsingContext
    );

    let sourcePrincipal =
      sourceBrowsingContext.currentWindowGlobal.documentPrincipal;
    let sourceIsPdf =
      !sourcePrincipal.isNullPrincipal && sourcePrincipal.spec == PDF_JS_URI;
    this.activeContentTitle =
      sourceBrowsingContext.currentWindowContext.documentTitle;
    this.activeCurrentURI =
      sourceBrowsingContext.currentWindowContext.documentURI.spec;
    let topWindowContext = sourceBrowsingContext.top.currentWindowContext;
    this.topContentTitle = topWindowContext.documentTitle;
    this.topCurrentURI = topWindowContext.documentURI.spec;
    this.isReader = this.topCurrentURI.startsWith("about:reader");

    let canSimplify = !this.isReader && this.isArticle;
    if (!this.hasSelection && !canSimplify) {
      document.getElementById("source-version-section").hidden = true;
    } else {
      document.getElementById("source-version-selection").hidden =
        !this.hasSelection;
      document.getElementById("source-version-simplified").hidden =
        !canSimplify;
    }

    // We don't need the sourceBrowsingContext anymore, get rid of it.
    sourceBrowsingContext = undefined;

    this.printProgressIndicator = document.getElementById("print-progress");
    this.printForm = document.getElementById("print");
    if (sourceIsPdf) {
      this.printForm.removeNonPdfSettings();
    }

    // Let the dialog appear before doing any potential main thread work.
    await ourBrowser._dialogReady;

    // First check the available destinations to ensure we get settings for an
    // accessible printer.
    let destinations,
      defaultSystemPrinter,
      fallbackPaperList,
      selectedPrinter,
      printersByName;
    try {
      ({
        destinations,
        defaultSystemPrinter,
        fallbackPaperList,
        selectedPrinter,
        printersByName,
      } = await this.getPrintDestinations());
    } catch (e) {
      this.reportPrintingError("PRINT_DESTINATIONS");
      throw e;
    }
    PrintSettingsViewProxy.availablePrinters = printersByName;
    PrintSettingsViewProxy.fallbackPaperList = fallbackPaperList;
    PrintSettingsViewProxy.defaultSystemPrinter = defaultSystemPrinter;
    PrintSettingsViewProxy._sourceVersion =
      this.hasSelection && this.printSelectionOnly ? "selection" : "source";

    logger.debug("availablePrinters: ", Object.keys(printersByName));
    logger.debug("defaultSystemPrinter: ", defaultSystemPrinter);

    document.addEventListener("print", async () => {
      let cancelButton = document.getElementById("cancel-button");
      document.l10n.setAttributes(
        cancelButton,
        cancelButton.dataset.closeL10nId
      );
      let didPrint = await this.print();
      if (!didPrint) {
        // Re-enable elements of the form if the user cancels saving or
        // if a deferred task rendered the page invalid.
        this.printForm.enable();
      }
      // Reset the cancel button regardless of the outcome.
      document.l10n.setAttributes(
        cancelButton,
        cancelButton.dataset.cancelL10nId
      );
    });
    this._createDelayedSettingsChangeTask();
    document.addEventListener("update-print-settings", e => {
      this.handleSettingsChange(e.detail);
    });
    document.addEventListener("cancel-print-settings", e => {
      this._delayedSettingsChangeTask.disarm();
      for (let setting of Object.keys(e.detail)) {
        delete this._delayedChanges[setting];
      }
    });
    document.addEventListener("cancel-print", () => this.cancelPrint());
    document.addEventListener("open-system-dialog", async () => {
      // This file in only used if pref print.always_print_silent is false, so
      // no need to check that here.

      // Hide the dialog box before opening system dialog
      // We cannot close the window yet because the browsing context for the
      // print preview browser is needed to print the page.
      let sourceBrowser =
        this.printPreviewEl.getSourceBrowsingContext().top.embedderElement;
      let dialogBoxManager =
        PrintUtils.getTabDialogBox(sourceBrowser).getTabDialogManager();
      dialogBoxManager.hideDialog(sourceBrowser);

      // Use our settings to prepopulate the system dialog.
      // The system print dialog won't recognize our internal save-to-pdf
      // pseudo-printer.  We need to pass it a settings object from any
      // system recognized printer.
      let settings =
        this.settings.printerName == PrintUtils.SAVE_TO_PDF_PRINTER
          ? PrintUtils.getPrintSettings(this.viewSettings.defaultSystemPrinter)
          : this.settings.clone();
      // We set the title so that if the user chooses save-to-PDF from the
      // system dialog the title will be used to generate the prepopulated
      // filename in the file picker.
      settings.title = this.activeTitle;
      Services.telemetry.scalarAdd("printing.dialog_opened_via_preview_tm", 1);
      const doPrint = await this._showPrintDialog(
        window,
        this.hasSelection,
        settings
      );
      if (!doPrint) {
        Services.telemetry.scalarAdd(
          "printing.dialog_via_preview_cancelled_tm",
          1
        );
        window.close();
        return;
      }
      await this.print(settings);
    });

    let originalError;
    const printersByPriority = [
      selectedPrinter.value,
      ...Object.getOwnPropertyNames(printersByName).filter(
        name => name != selectedPrinter.value
      ),
    ];

    // Try to update settings, falling back to any available printer
    for (const printerName of printersByPriority) {
      try {
        let settingsToChange = await this.refreshSettings(printerName);
        await this.updateSettings(settingsToChange, true);
        originalError = null;
        break;
      } catch (e) {
        if (!originalError) {
          originalError = e;
          // Report on how often fetching the last used printer settings fails.
          this.reportPrintingError("PRINTER_SETTINGS_LAST_USED");
        }
      }
    }

    // Only throw original error if no fallback was possible
    if (originalError) {
      this.reportPrintingError("PRINTER_SETTINGS");
      throw originalError;
    }

    let initialPreviewDone = this._updatePrintPreview();

    // Use a DeferredTask for updating the preview. This will ensure that we
    // only have one update running at a time.
    this._createUpdatePrintPreviewTask(initialPreviewDone);

    document.dispatchEvent(
      new CustomEvent("available-destinations", {
        detail: destinations,
      })
    );

    document.dispatchEvent(
      new CustomEvent("print-settings", {
        detail: this.viewSettings,
      })
    );

    document.body.removeAttribute("loading");

    await new Promise(resolve => window.requestAnimationFrame(resolve));

    // Now that we're showing the form, select the destination select.
    document.getElementById("printer-picker").focus({ focusVisible: true });

    await initialPreviewDone;
  },

  async print(systemDialogSettings) {
    // Disable the form when a print is in progress
    this.printForm.disable();

    if (Object.keys(this._delayedChanges).length) {
      // Make sure any pending changes get saved.
      let task = this._delayedSettingsChangeTask;
      this._createDelayedSettingsChangeTask();
      await task.finalize();
    }

    if (this.settings.pageRanges.length) {
      // Finish any running previews to verify the range is still valid.
      let task = this._updatePrintPreviewTask;
      this._createUpdatePrintPreviewTask();
      await task.finalize();
    }

    if (!this.printForm.checkValidity() || this.previewIsEmpty) {
      return false;
    }

    let settings = systemDialogSettings || this.settings;

    if (settings.printerName == PrintUtils.SAVE_TO_PDF_PRINTER) {
      try {
        settings.toFileName = await pickFileName(
          this.activeTitle,
          this.activeURI
        );
      } catch (e) {
        return false;
      }
    }

    await window._initialized;

    // This seems like it should be handled automatically but it isn't.
    PSSVC.maybeSaveLastUsedPrinterNameToPrefs(settings.printerName);

    try {
      // We'll provide our own progress indicator.
      let l10nId =
        settings.printerName == PrintUtils.SAVE_TO_PDF_PRINTER
          ? "printui-print-progress-indicator-saving"
          : "printui-print-progress-indicator";
      document.l10n.setAttributes(this.printProgressIndicator, l10nId);
      this.printProgressIndicator.hidden = false;
      let bc = this.printPreviewEl.currentBrowsingContext;
      await this._doPrint(bc, settings);
    } catch (e) {
      console.error(e);
    }

    if (settings.printerName == PrintUtils.SAVE_TO_PDF_PRINTER) {
      // Clear the file name from the preference value since it may potentially
      // contain sensitive information from the page title (Bug 1675965)
      let prefName =
        "print.printer_" +
        settings.printerName.replace(/ /g, "_") +
        ".print_to_filename";
      Services.prefs.clearUserPref(prefName);
    }

    window.close();
    return true;
  },

  /**
   * Prints the window. This method has been abstracted into a helper for
   * testing purposes.
   */
  _doPrint(aBrowsingContext, aSettings) {
    return aBrowsingContext.print(aSettings);
  },

  cancelPrint() {
    Services.telemetry.scalarAdd("printing.preview_cancelled_tm", 1);
    window.close();
  },

  async refreshSettings(printerName) {
    this.currentPrinterName = printerName;
    let currentPrinter;
    try {
      currentPrinter = await PrintSettingsViewProxy.resolvePropertiesForPrinter(
        printerName
      );
    } catch (e) {
      this.reportPrintingError("PRINTER_PROPERTIES");
      throw e;
    }
    if (this.currentPrinterName != printerName) {
      // Refresh settings could take a while, if the destination has changed
      // then we don't want to update the settings after all.
      return {};
    }

    this.settings = currentPrinter.settings;
    this.defaultSettings = currentPrinter.defaultSettings;

    this.settings.printSelectionOnly = this.printSelectionOnly;

    logger.debug("currentPrinter name: ", printerName);
    logger.debug("settings:", serializeSettings(this.settings));

    // Some settings are only used by the UI
    // assigning new values should update the underlying settings
    this.viewSettings = new Proxy(this.settings, PrintSettingsViewProxy);
    return this.getSettingsToUpdate();
  },

  getSettingsToUpdate() {
    // Get the previously-changed settings we want to try to use on this printer
    let settingsToUpdate = Object.assign({}, this._userChangedSettings);

    // Ensure the color option is correct, if either of the supportsX flags are
    // false then the user cannot change the value through the UI.
    if (!this.viewSettings.supportsColor) {
      settingsToUpdate.printInColor = false;
    } else if (!this.viewSettings.supportsMonochrome) {
      settingsToUpdate.printInColor = true;
    }

    if (settingsToUpdate.sourceVersion == "simplified") {
      if (this.viewSettings.printBackgrounds) {
        // Remember that this was true before so it gets restored if the
        // format is changed to something else.
        this._userChangedSettings.printBackgrounds = true;
      }
      // Backgrounds are removed in simplified mode and this setting changes
      // the output subtly to be less legible.
      settingsToUpdate.printBackgrounds = false;
    }

    if (
      settingsToUpdate.printInColor != this._userChangedSettings.printInColor
    ) {
      delete this._userChangedSettings.printInColor;
    }

    // See if the paperId needs to change.
    let paperId = settingsToUpdate.paperId || this.viewSettings.paperId;

    logger.debug("Using paperId: ", paperId);
    logger.debug(
      "Available paper sizes: ",
      PrintSettingsViewProxy.availablePaperSizes
    );
    let matchedPaper =
      paperId && PrintSettingsViewProxy.availablePaperSizes[paperId];
    if (!matchedPaper) {
      let paperWidth, paperHeight, paperSizeUnit;
      if (settingsToUpdate.paperId) {
        // The user changed paperId in this instance and session,
        // We should have details on the paper size from the previous printer
        paperId = settingsToUpdate.paperId;
        let cachedPaperWrapper = this.allPaperSizes[paperId];
        // for the purposes of finding a best-size match, we'll use mm
        paperWidth = cachedPaperWrapper.paper.width * MM_PER_POINT;
        paperHeight = cachedPaperWrapper.paper.height * MM_PER_POINT;
        paperSizeUnit = PrintEventHandler.settings.kPaperSizeMillimeters;
      } else {
        paperId = this.viewSettings.paperId;
        logger.debug(
          "No paperId or matchedPaper, get a new default from viewSettings:",
          paperId
        );
        paperWidth = this.viewSettings.paperWidth;
        paperHeight = this.viewSettings.paperHeight;
        paperSizeUnit = this.viewSettings.paperSizeUnit;
      }
      matchedPaper = PrintSettingsViewProxy.getBestPaperMatch(
        paperWidth,
        paperHeight,
        paperSizeUnit
      );
    }
    if (!matchedPaper) {
      // We didn't find a good match. Take the first paper size
      matchedPaper = Object.values(
        PrintSettingsViewProxy.availablePaperSizes
      )[0];
      delete this._userChangedSettings.paperId;
    }
    if (matchedPaper.id !== paperId) {
      // The exact paper id doesn't exist for this printer
      logger.log(
        `Requested paperId: "${paperId}" missing on this printer, using: ${matchedPaper.id} instead`
      );
      delete this._userChangedSettings.paperId;
    }
    // Always write paper details back to settings
    settingsToUpdate.paperId = matchedPaper.id;

    return settingsToUpdate;
  },

  _createDelayedSettingsChangeTask() {
    this._delayedSettingsChangeTask = createDeferredTask(async () => {
      if (Object.keys(this._delayedChanges).length) {
        let changes = this._delayedChanges;
        this._delayedChanges = {};
        await this.onUserSettingsChange(changes);
      }
    }, INPUT_DELAY_MS);
  },

  _createUpdatePrintPreviewTask(initialPreviewDone = null) {
    this._updatePrintPreviewTask = new DeferredTask(async () => {
      await initialPreviewDone;
      await this._updatePrintPreview();
      document.dispatchEvent(new CustomEvent("preview-updated"));
    }, 0);
  },

  _scheduleDelayedSettingsChange(changes) {
    Object.assign(this._delayedChanges, changes);
    this._delayedSettingsChangeTask.disarm();
    this._delayedSettingsChangeTask.arm();
  },

  handleSettingsChange(changedSettings = {}) {
    let delayedChanges = {};
    let instantChanges = {};
    for (let [setting, value] of Object.entries(changedSettings)) {
      switch (setting) {
        case "pageRanges":
        case "scaling":
          delayedChanges[setting] = value;
          break;
        case "customMargins":
          delete this._delayedChanges.margins;
          changedSettings.margins == "custom"
            ? (delayedChanges[setting] = value)
            : (instantChanges[setting] = value);
          break;
        default:
          instantChanges[setting] = value;
          break;
      }
    }
    if (Object.keys(delayedChanges).length) {
      this._scheduleDelayedSettingsChange(delayedChanges);
    }
    if (Object.keys(instantChanges).length) {
      this.onUserSettingsChange(instantChanges);
    }
  },

  async onUserSettingsChange(changedSettings = {}) {
    let previewableChange = false;
    for (let [setting, value] of Object.entries(changedSettings)) {
      Services.telemetry.keyedScalarAdd(
        "printing.settings_changed",
        setting,
        1
      );
      // Update the list of user-changed settings, which we attempt to maintain
      // across printer changes.
      this._userChangedSettings[setting] = value;
      if (!this._noPreviewUpdateSettings.has(setting)) {
        previewableChange = true;
      }
    }
    if (changedSettings.printerName) {
      logger.debug(
        "onUserSettingsChange, changing to printerName:",
        changedSettings.printerName
      );
      this.printForm.printerChanging = true;
      this.printForm.disable(el => el.id != "printer-picker");
      let { printerName } = changedSettings;
      // Treat a printerName change separately, because it involves a settings
      // object switch and we don't want to set the new name on the old settings.
      changedSettings = await this.refreshSettings(printerName);
      if (printerName != this.currentPrinterName) {
        // Don't continue this update if the printer changed again.
        return;
      }
      this.printForm.printerChanging = false;
      this.printForm.enable();
    } else {
      changedSettings = this.getSettingsToUpdate();
    }

    let shouldPreviewUpdate =
      (await this.updateSettings(
        changedSettings,
        !!changedSettings.printerName
      )) && previewableChange;

    if (shouldPreviewUpdate && !printPending) {
      // We do not need to arm the preview task if the user has already printed
      // and finalized any deferred tasks.
      this.updatePrintPreview();
    }
    document.dispatchEvent(
      new CustomEvent("print-settings", {
        detail: this.viewSettings,
      })
    );
  },

  async updateSettings(changedSettings = {}, printerChanged = false) {
    let updatePreviewWithoutFlag = false;
    let flags = 0;
    logger.debug("updateSettings ", changedSettings, printerChanged);

    if (printerChanged || changedSettings.paperId) {
      // The paper's margin properties are async,
      // so resolve those now before we update the settings
      try {
        let paperWrapper = await PrintSettingsViewProxy.fetchPaperMargins(
          changedSettings.paperId || this.viewSettings.paperId
        );

        // See if we also need to change the custom margin values

        let paperHeightInInches = paperWrapper.paper.height * INCHES_PER_POINT;
        let paperWidthInInches = paperWrapper.paper.width * INCHES_PER_POINT;
        let height =
          (changedSettings.orientation || this.viewSettings.orientation) == 0
            ? paperHeightInInches
            : paperWidthInInches;
        let width =
          (changedSettings.orientation || this.viewSettings.orientation) == 0
            ? paperWidthInInches
            : paperHeightInInches;

        function verticalMarginsInvalid(margins) {
          return (
            parseFloat(margins.marginTop) + parseFloat(margins.marginBottom) >
            height -
              paperWrapper.unwriteableMarginTop -
              paperWrapper.unwriteableMarginBottom
          );
        }

        function horizontalMarginsInvalid(margins) {
          return (
            parseFloat(margins.marginRight) + parseFloat(margins.marginLeft) >
            width -
              paperWrapper.unwriteableMarginRight -
              paperWrapper.unwriteableMarginLeft
          );
        }

        let unwriteableMarginsInvalid = false;
        if (
          verticalMarginsInvalid(this.viewSettings.customMargins) ||
          this.viewSettings.customMargins.marginTop < 0 ||
          this.viewSettings.customMargins.marginBottom < 0
        ) {
          let { marginTop, marginBottom } = this.viewSettings.defaultMargins;
          if (verticalMarginsInvalid(this.viewSettings.defaultMargins)) {
            let marginsNone = this.getMarginPresets("none");
            marginTop = marginsNone.marginTop;
            marginBottom = marginsNone.marginBottom;
            unwriteableMarginsInvalid = true;
          }
          changedSettings.marginTop = changedSettings.customMarginTop =
            marginTop;
          changedSettings.marginBottom = changedSettings.customMarginBottom =
            marginBottom;
          delete this._userChangedSettings.customMargins;
        }

        if (
          horizontalMarginsInvalid(this.viewSettings.customMargins) ||
          this.viewSettings.customMargins.marginLeft < 0 ||
          this.viewSettings.customMargins.marginRight < 0
        ) {
          let { marginLeft, marginRight } = this.viewSettings.defaultMargins;
          if (horizontalMarginsInvalid(this.viewSettings.defaultMargins)) {
            let marginsNone = this.getMarginPresets("none");
            marginLeft = marginsNone.marginLeft;
            marginRight = marginsNone.marginRight;
            unwriteableMarginsInvalid = true;
          }
          changedSettings.marginLeft = changedSettings.customMarginLeft =
            marginLeft;
          changedSettings.marginRight = changedSettings.customMarginRight =
            marginRight;
          delete this._userChangedSettings.customMargins;
        }

        if (unwriteableMarginsInvalid) {
          changedSettings.ignoreUnwriteableMargins = true;
        }
      } catch (e) {
        this.reportPrintingError("PAPER_MARGINS");
        throw e;
      }
    }

    for (let [setting, value] of Object.entries(changedSettings)) {
      // Always write paper changes back to settings as pref-derived values could be bad
      if (
        this.viewSettings[setting] != value ||
        (printerChanged && setting == "paperId")
      ) {
        if (setting == "pageRanges") {
          // The page range is kept as an array. If the user switches between all
          // and custom with no specified range input (which is represented as an
          // empty array), we do not want to send an update.
          if (!this.viewSettings[setting].length && !value.length) {
            continue;
          }
        }
        this.viewSettings[setting] = value;

        if (
          setting in this.settingFlags &&
          setting in this._userChangedSettings
        ) {
          flags |= this.settingFlags[setting];
        }
        updatePreviewWithoutFlag |=
          this._nonFlaggedUpdatePreviewSettings.has(setting);
      }
    }

    let shouldPreviewUpdate =
      flags || printerChanged || updatePreviewWithoutFlag;
    logger.debug(
      "updateSettings, calculated flags:",
      flags,
      "shouldPreviewUpdate:",
      shouldPreviewUpdate
    );
    if (flags) {
      this.saveSettingsToPrefs(flags);
    }
    return shouldPreviewUpdate;
  },

  saveSettingsToPrefs(flags) {
    PSSVC.maybeSavePrintSettingsToPrefs(this.settings, flags);
  },

  /**
   * Queue a task to update the print preview. It will start immediately or when
   * the in progress update completes.
   */
  async updatePrintPreview() {
    // Make sure the rendering state is set so we don't visibly update the
    // sheet count with incomplete data.
    this._updatePrintPreviewTask.arm();
  },

  /**
   * Creates a print preview or refreshes the preview with new settings when omitted.
   *
   * @return {Promise} Resolves when the preview has been updated.
   */
  async _updatePrintPreview() {
    let { settings } = this;

    let totalPageCount, sheetCount, isEmpty, orientation, pageWidth, pageHeight;
    try {
      // This resolves with a PrintPreviewSuccessInfo dictionary.
      let { sourceVersion } = this.viewSettings;
      let sourceURI = this.activeURI;
      this._lastPrintPreviewSettings = settings;
      ({
        totalPageCount,
        sheetCount,
        isEmpty,
        orientation,
        pageWidth,
        pageHeight,
      } = await this.printPreviewEl.printPreview(settings, {
        sourceVersion,
        sourceURI,
      }));
    } catch (e) {
      this.reportPrintingError("PRINT_PREVIEW");
      console.error(e);
      throw e;
    }

    // If there is a set orientation, update the settings to use it. In this
    // case, the document will already have used this orientation to create
    // the print preview.
    if (orientation != "unspecified") {
      const kIPrintSettings = Ci.nsIPrintSettings;
      settings.orientation =
        orientation == "landscape"
          ? kIPrintSettings.kLandscapeOrientation
          : kIPrintSettings.kPortraitOrientation;
      document.dispatchEvent(new CustomEvent("hide-orientation"));
    }

    // If the page size is set, check whether we should use it as our paper size.
    let isUsingPageRuleSizeAsPaperSize =
      settings.usePageRuleSizeAsPaperSize &&
      pageWidth !== null &&
      pageHeight !== null;
    if (isUsingPageRuleSizeAsPaperSize) {
      // We canonically represent paper sizes using the width/height of a portrait-oriented sheet,
      // with landscape-orientation applied as a supplemental rotation.
      // If the page-size is landscape oriented, we flip the pageWidth / pageHeight here
      // in order to pass a canonical representation into the paper-size settings.
      if (orientation == "landscape") {
        [pageHeight, pageWidth] = [pageWidth, pageHeight];
      }

      let matchedPaper = PrintSettingsViewProxy.getBestPaperMatch(
        pageWidth,
        pageHeight,
        settings.kPaperSizeInches
      );
      if (matchedPaper) {
        settings.paperId = matchedPaper.id;
      }

      settings.paperWidth = pageWidth;
      settings.paperHeight = pageHeight;
      settings.paperSizeUnit = settings.kPaperSizeInches;
      document.dispatchEvent(new CustomEvent("hide-paper-size"));
    }

    this.previewIsEmpty = isEmpty;
    // If the preview is empty, we know our range is greater than the number of pages.
    // We have to send a pageRange update to display a non-empty page.
    if (this.previewIsEmpty) {
      this.viewSettings.pageRanges = [];
      this.updatePrintPreview();
    }

    document.dispatchEvent(
      new CustomEvent("page-count", {
        detail: { sheetCount, totalPages: totalPageCount },
      })
    );
  },

  async getPrintDestinations() {
    const printerList = Cc["@mozilla.org/gfx/printerlist;1"].createInstance(
      Ci.nsIPrinterList
    );
    let printers;

    if (Cu.isInAutomation) {
      printers = window._mockPrinters || [];
    } else {
      try {
        printers = await printerList.printers;
      } catch (e) {
        this.reportPrintingError("PRINTER_LIST");
        throw e;
      }
    }

    let fallbackPaperList;
    try {
      fallbackPaperList = await printerList.fallbackPaperList;
    } catch (e) {
      this.reportPrintingError("FALLBACK_PAPER_LIST");
      throw e;
    }

    let lastUsedPrinterName;
    try {
      lastUsedPrinterName = PSSVC.lastUsedPrinterName;
    } catch (e) {
      this.reportPrintingError("LAST_USED_PRINTER");
      throw e;
    }
    const defaultPrinterName = printerList.systemDefaultPrinterName;
    const printersByName = {};

    let lastUsedPrinter;
    let defaultSystemPrinter;

    let saveToPdfPrinter = {
      nameId: "printui-destination-pdf-label",
      value: PrintUtils.SAVE_TO_PDF_PRINTER,
    };
    printersByName[PrintUtils.SAVE_TO_PDF_PRINTER] = {
      supportsColor: true,
      supportsMonochrome: false,
      name: PrintUtils.SAVE_TO_PDF_PRINTER,
    };

    if (lastUsedPrinterName == PrintUtils.SAVE_TO_PDF_PRINTER) {
      lastUsedPrinter = saveToPdfPrinter;
    }

    let destinations = [
      saveToPdfPrinter,
      ...printers.map(printer => {
        printer.QueryInterface(Ci.nsIPrinter);
        const { name } = printer;
        printersByName[printer.name] = { printer };
        const destination = { name, value: name };

        if (name == lastUsedPrinterName) {
          lastUsedPrinter = destination;
        }
        if (name == defaultPrinterName) {
          defaultSystemPrinter = destination;
        }

        return destination;
      }),
    ];

    let selectedPrinter =
      lastUsedPrinter || defaultSystemPrinter || saveToPdfPrinter;

    return {
      destinations,
      fallbackPaperList,
      selectedPrinter,
      printersByName,
      defaultSystemPrinter,
    };
  },

  getMarginPresets(marginSize, paperWrapper) {
    switch (marginSize) {
      case "minimum": {
        let marginSource = paperWrapper || this.defaultSettings;
        return {
          marginTop: marginSource.unwriteableMarginTop,
          marginRight: marginSource.unwriteableMarginRight,
          marginBottom: marginSource.unwriteableMarginBottom,
          marginLeft: marginSource.unwriteableMarginLeft,
        };
      }
      case "none":
        return {
          marginTop: 0,
          marginLeft: 0,
          marginBottom: 0,
          marginRight: 0,
        };
      case "custom":
        return {
          marginTop:
            PrintSettingsViewProxy._lastCustomMarginValues.marginTop ??
            this.settings.marginTop,
          marginBottom:
            PrintSettingsViewProxy._lastCustomMarginValues.marginBottom ??
            this.settings.marginBottom,
          marginLeft:
            PrintSettingsViewProxy._lastCustomMarginValues.marginLeft ??
            this.settings.marginLeft,
          marginRight:
            PrintSettingsViewProxy._lastCustomMarginValues.marginRight ??
            this.settings.marginRight,
        };
      default: {
        let minimum = this.getMarginPresets("minimum", paperWrapper);
        return {
          marginTop: !isNaN(minimum.marginTop)
            ? Math.max(minimum.marginTop, this.defaultSettings.marginTop)
            : this.defaultSettings.marginTop,
          marginRight: !isNaN(minimum.marginRight)
            ? Math.max(minimum.marginRight, this.defaultSettings.marginRight)
            : this.defaultSettings.marginRight,
          marginBottom: !isNaN(minimum.marginBottom)
            ? Math.max(minimum.marginBottom, this.defaultSettings.marginBottom)
            : this.defaultSettings.marginBottom,
          marginLeft: !isNaN(minimum.marginLeft)
            ? Math.max(minimum.marginLeft, this.defaultSettings.marginLeft)
            : this.defaultSettings.marginLeft,
        };
      }
    }
  },

  reportPrintingError(aMessage) {
    logger.debug("reportPrintingError:", aMessage);
    Services.telemetry.keyedScalarAdd("printing.error", aMessage, 1);
  },

  /**
   * Shows the system dialog. This method has been abstracted into a helper for
   * testing purposes. The showPrintDialog() call blocks until the dialog is
   * closed, so we mark it as async to allow us to reject from the test.
   */
  async _showPrintDialog(aWindow, aHaveSelection, aSettings) {
    return PrintUtils.handleSystemPrintDialog(
      aWindow,
      aHaveSelection,
      aSettings
    );
  },
};

var PrintSettingsViewProxy = {
  get defaultHeadersAndFooterValues() {
    const defaultBranch = Services.prefs.getDefaultBranch("");
    let settingValues = {};
    for (let [name, pref] of Object.entries(this.headerFooterSettingsPrefs)) {
      settingValues[name] = defaultBranch.getStringPref(pref);
    }
    // We only need to retrieve these defaults once and they will not change
    Object.defineProperty(this, "defaultHeadersAndFooterValues", {
      value: settingValues,
    });
    return settingValues;
  },

  headerFooterSettingsPrefs: {
    footerStrCenter: "print.print_footercenter",
    footerStrLeft: "print.print_footerleft",
    footerStrRight: "print.print_footerright",
    headerStrCenter: "print.print_headercenter",
    headerStrLeft: "print.print_headerleft",
    headerStrRight: "print.print_headerright",
  },

  // Custom margins are not saved by a pref, so we need to keep track of them
  // in order to save the value.
  _lastCustomMarginValues: {
    marginTop: null,
    marginBottom: null,
    marginLeft: null,
    marginRight: null,
  },

  // This list was taken from nsDeviceContextSpecWin.cpp which records telemetry on print target type
  knownSaveToFilePrinters: new Set([
    "Microsoft Print to PDF",
    "Adobe PDF",
    "Bullzip PDF Printer",
    "CutePDF Writer",
    "doPDF",
    "Foxit Reader PDF Printer",
    "Nitro PDF Creator",
    "novaPDF",
    "PDF-XChange",
    "PDF24 PDF",
    "PDFCreator",
    "PrimoPDF",
    "Soda PDF",
    "Solid PDF Creator",
    "Universal Document Converter",
    "Microsoft XPS Document Writer",
  ]),

  getBestPaperMatch(paperWidth, paperHeight, paperSizeUnit) {
    let paperSizes = Object.values(this.availablePaperSizes);
    if (!(paperWidth && paperHeight)) {
      return null;
    }
    // first try to match on the paper dimensions using the current units
    let unitsPerPoint;
    let altUnitsPerPoint;
    if (paperSizeUnit == PrintEventHandler.settings.kPaperSizeMillimeters) {
      unitsPerPoint = MM_PER_POINT;
      altUnitsPerPoint = INCHES_PER_POINT;
    } else {
      unitsPerPoint = INCHES_PER_POINT;
      altUnitsPerPoint = MM_PER_POINT;
    }
    // equality to 1pt.
    const equal = (a, b) => Math.abs(a - b) < 1;
    const findMatch = (widthPts, heightPts) =>
      paperSizes.find(paperWrapper => {
        // the dimensions on the nsIPaper object are in points
        let result =
          equal(widthPts, paperWrapper.paper.width) &&
          equal(heightPts, paperWrapper.paper.height);
        return result;
      });
    // Look for a paper with matching dimensions, using the current printer's
    // paper size unit, then the alternate unit
    let matchedPaper =
      findMatch(paperWidth / unitsPerPoint, paperHeight / unitsPerPoint) ||
      findMatch(paperWidth / altUnitsPerPoint, paperHeight / altUnitsPerPoint);

    if (matchedPaper) {
      return matchedPaper;
    }
    return null;
  },

  async fetchPaperMargins(paperId) {
    // resolve any async and computed properties we need on the paper
    let paperWrapper = this.availablePaperSizes[paperId];
    if (!paperWrapper) {
      throw new Error("Can't fetchPaperMargins: " + paperId);
    }
    if (paperWrapper._resolved) {
      // We've already resolved and calculated these values
      return paperWrapper;
    }
    let margins;
    try {
      margins = await paperWrapper.paper.unwriteableMargin;
    } catch (e) {
      this.reportPrintingError("UNWRITEABLE_MARGIN");
      throw e;
    }
    margins.QueryInterface(Ci.nsIPaperMargin);

    // margin dimensions are given on the paper in points, setting values need to be in inches
    paperWrapper.unwriteableMarginTop = margins.top * INCHES_PER_POINT;
    paperWrapper.unwriteableMarginRight = margins.right * INCHES_PER_POINT;
    paperWrapper.unwriteableMarginBottom = margins.bottom * INCHES_PER_POINT;
    paperWrapper.unwriteableMarginLeft = margins.left * INCHES_PER_POINT;
    // No need to re-resolve static properties
    paperWrapper._resolved = true;
    return paperWrapper;
  },

  async resolvePropertiesForPrinter(printerName) {
    // resolve any async properties we need on the printer
    let printerInfo = this.availablePrinters[printerName];
    if (printerInfo._resolved) {
      // Store a convenience reference
      this.availablePaperSizes = printerInfo.availablePaperSizes;
      return printerInfo;
    }

    // Await the async printer data.
    if (printerInfo.printer) {
      let basePrinterInfo;
      try {
        [
          printerInfo.supportsDuplex,
          printerInfo.supportsColor,
          printerInfo.supportsMonochrome,
          basePrinterInfo,
        ] = await Promise.all([
          printerInfo.printer.supportsDuplex,
          printerInfo.printer.supportsColor,
          printerInfo.printer.supportsMonochrome,
          printerInfo.printer.printerInfo,
        ]);
      } catch (e) {
        this.reportPrintingError("PRINTER_SETTINGS");
        throw e;
      }
      basePrinterInfo.QueryInterface(Ci.nsIPrinterInfo);
      basePrinterInfo.defaultSettings.QueryInterface(Ci.nsIPrintSettings);

      printerInfo.paperList = basePrinterInfo.paperList;
      printerInfo.defaultSettings = basePrinterInfo.defaultSettings;
    } else if (printerName == PrintUtils.SAVE_TO_PDF_PRINTER) {
      // The Mozilla PDF pseudo-printer has no actual nsIPrinter implementation
      printerInfo.defaultSettings = PSSVC.createNewPrintSettings();
      printerInfo.defaultSettings.printerName = printerName;
      printerInfo.defaultSettings.toFileName = "";
      printerInfo.defaultSettings.outputFormat =
        Ci.nsIPrintSettings.kOutputFormatPDF;
      printerInfo.defaultSettings.outputDestination =
        Ci.nsIPrintSettings.kOutputDestinationFile;
      printerInfo.defaultSettings.usePageRuleSizeAsPaperSize =
        Services.prefs.getBoolPref(
          "print.save_as_pdf.use_page_rule_size_as_paper_size.enabled",
          false
        );
      printerInfo.paperList = this.fallbackPaperList;
    }
    printerInfo.settings = printerInfo.defaultSettings.clone();
    // Apply any previously persisted user values
    // Don't apply kInitSavePrintToFile though, that should only be true for
    // the PDF printer.
    printerInfo.settings.outputDestination =
      printerName == PrintUtils.SAVE_TO_PDF_PRINTER
        ? Ci.nsIPrintSettings.kOutputDestinationFile
        : Ci.nsIPrintSettings.kOutputDestinationPrinter;
    let flags =
      printerInfo.settings.kInitSaveAll ^
      printerInfo.settings.kInitSavePrintToFile;
    PSSVC.initPrintSettingsFromPrefs(printerInfo.settings, true, flags);
    // We set `isInitializedFromPrinter` to make sure that that's set on the
    // SAVE_TO_PDF_PRINTER settings.  The naming is poor, but that tells the
    // platform code that the settings object is complete.
    printerInfo.settings.isInitializedFromPrinter = true;

    printerInfo.settings.toFileName = "";

    // prepare the available paper sizes for this printer
    if (!printerInfo.paperList?.length) {
      logger.warn(
        "Printer has empty paperList: ",
        printerInfo.printer.id,
        "using fallbackPaperList"
      );
      printerInfo.paperList = this.fallbackPaperList;
    }
    // don't trust the settings to provide valid paperSizeUnit values
    let sizeUnit =
      printerInfo.settings.paperSizeUnit ==
      printerInfo.settings.kPaperSizeMillimeters
        ? printerInfo.settings.kPaperSizeMillimeters
        : printerInfo.settings.kPaperSizeInches;
    let papersById = (printerInfo.availablePaperSizes = {});
    // Store a convenience reference
    this.availablePaperSizes = papersById;

    for (let paper of printerInfo.paperList) {
      paper.QueryInterface(Ci.nsIPaper);
      // Bug 1662239: I'm seeing multiple duplicate entries for each paper size
      // so ensure we have one entry per name
      if (!papersById[paper.id]) {
        papersById[paper.id] = {
          paper,
          id: paper.id,
          name: paper.name,
          // XXXsfoster: Eventually we want to get the unit from the nsIPaper object
          sizeUnit,
        };
      }
    }
    // Update our cache of all the paper sizes by name
    Object.assign(PrintEventHandler.allPaperSizes, papersById);

    // The printer properties don't change, mark this as resolved for next time
    printerInfo._resolved = true;
    logger.debug("Resolved printerInfo:", printerInfo);
    return printerInfo;
  },

  get(target, name) {
    switch (name) {
      case "currentPaper": {
        let paperId = this.get(target, "paperId");
        return paperId && this.availablePaperSizes[paperId];
      }

      case "marginPresets":
        let paperWrapper = this.get(target, "currentPaper");
        return {
          none: PrintEventHandler.getMarginPresets("none", paperWrapper),
          minimum: PrintEventHandler.getMarginPresets("minimum", paperWrapper),
          default: PrintEventHandler.getMarginPresets("default", paperWrapper),
          custom: PrintEventHandler.getMarginPresets("custom", paperWrapper),
        };

      case "marginOptions": {
        let allMarginPresets = this.get(target, "marginPresets");
        let uniqueMargins = new Set();
        let marginsEnabled = {};
        for (let name of ["none", "default", "minimum", "custom"]) {
          let { marginTop, marginLeft, marginBottom, marginRight } =
            allMarginPresets[name];
          let key = [marginTop, marginLeft, marginBottom, marginRight].join(
            ","
          );
          // Custom margins are initialized to default margins
          marginsEnabled[name] = !uniqueMargins.has(key) || name == "custom";
          uniqueMargins.add(key);
        }
        return marginsEnabled;
      }

      case "margins":
        let marginSettings = {
          marginTop: target.marginTop,
          marginLeft: target.marginLeft,
          marginBottom: target.marginBottom,
          marginRight: target.marginRight,
        };
        // see if they match the none, minimum, or default margin values
        let allMarginPresets = this.get(target, "marginPresets");
        const marginsMatch = function (lhs, rhs) {
          return Object.keys(marginSettings).every(
            name => lhs[name].toFixed(2) == rhs[name].toFixed(2)
          );
        };
        const potentialPresets = (function () {
          let presets = [];
          const minimumIsNone = marginsMatch(
            allMarginPresets.none,
            allMarginPresets.minimum
          );
          // We only attempt to match the serialized values against the "none"
          // preset if the unwriteable margins are being ignored or are zero.
          if (target.ignoreUnwriteableMargins || minimumIsNone) {
            presets.push("none");
          }
          if (!minimumIsNone) {
            presets.push("minimum");
          }
          presets.push("default");
          return presets;
        })();
        for (let presetName of potentialPresets) {
          let marginPresets = allMarginPresets[presetName];
          if (marginsMatch(marginSettings, marginPresets)) {
            return presetName;
          }
        }

        // Fall back to custom for other values
        return "custom";

      case "defaultMargins":
        return PrintEventHandler.getMarginPresets(
          "default",
          this.get(target, "currentPaper")
        );

      case "customMargins":
        return PrintEventHandler.getMarginPresets(
          "custom",
          this.get(target, "currentPaper")
        );

      case "paperSizes":
        return Object.values(this.availablePaperSizes)
          .sort((a, b) => a.name.localeCompare(b.name))
          .map(paper => {
            return {
              name: paper.name,
              value: paper.id,
            };
          });

      case "supportsDuplex":
        return this.availablePrinters[target.printerName].supportsDuplex;

      case "printDuplex":
        switch (target.duplex) {
          case Ci.nsIPrintSettings.kDuplexNone:
            break;
          case Ci.nsIPrintSettings.kDuplexFlipOnLongEdge:
            return "long-edge";
          case Ci.nsIPrintSettings.kDuplexFlipOnShortEdge:
            return "short-edge";
          default:
            logger.warn("Unexpected duplex value: ", target.duplex);
        }
        return "off";
      case "printBackgrounds":
        return target.printBGImages || target.printBGColors;

      case "printFootersHeaders":
        // if any of the footer and headers settings have a non-empty string value
        // we consider that "enabled"
        return Object.keys(this.headerFooterSettingsPrefs).some(
          name => !!target[name]
        );

      case "supportsColor":
        return this.availablePrinters[target.printerName].supportsColor;

      case "willSaveToFile":
        return (
          target.outputFormat == Ci.nsIPrintSettings.kOutputFormatPDF ||
          this.knownSaveToFilePrinters.has(target.printerName)
        );
      case "supportsMonochrome":
        return this.availablePrinters[target.printerName].supportsMonochrome;
      case "defaultSystemPrinter":
        return (
          this.defaultSystemPrinter?.value ||
          Object.getOwnPropertyNames(this.availablePrinters).find(
            name => name != PrintUtils.SAVE_TO_PDF_PRINTER
          )
        );

      case "numCopies":
        return this.get(target, "willSaveToFile") ? 1 : target.numCopies;
      case "sourceVersion":
        return this._sourceVersion;
    }
    return target[name];
  },

  set(target, name, value) {
    switch (name) {
      case "margins":
        if (!["default", "minimum", "none", "custom"].includes(value)) {
          logger.warn("Unexpected margin preset name: ", value);
          value = "default";
        }
        let paperWrapper = this.get(target, "currentPaper");
        let marginPresets = PrintEventHandler.getMarginPresets(
          value,
          paperWrapper
        );
        for (let [settingName, presetValue] of Object.entries(marginPresets)) {
          target[settingName] = presetValue;
        }
        target.honorPageRuleMargins = value == "default";
        target.ignoreUnwriteableMargins = value == "none";
        break;

      case "paperId": {
        let paperId = value;
        let paperWrapper = this.availablePaperSizes[paperId];
        // Dimensions on the paper object are in pts.
        // We convert to the printer's specified unit when updating settings
        let unitsPerPoint =
          paperWrapper.sizeUnit == target.kPaperSizeMillimeters
            ? MM_PER_POINT
            : INCHES_PER_POINT;
        // paperWidth and paperHeight are calculated values that we always treat as suspect and
        // re-calculate whenever the paperId changes
        target.paperSizeUnit = paperWrapper.sizeUnit;
        target.paperWidth = paperWrapper.paper.width * unitsPerPoint;
        target.paperHeight = paperWrapper.paper.height * unitsPerPoint;
        // Unwriteable margins were pre-calculated from their async values when the paper size
        // was selected. They are always in inches
        target.unwriteableMarginTop = paperWrapper.unwriteableMarginTop;
        target.unwriteableMarginRight = paperWrapper.unwriteableMarginRight;
        target.unwriteableMarginBottom = paperWrapper.unwriteableMarginBottom;
        target.unwriteableMarginLeft = paperWrapper.unwriteableMarginLeft;
        target.paperId = paperWrapper.paper.id;
        // pull new margin values for the new paper size
        this.set(target, "margins", this.get(target, "margins"));
        break;
      }

      case "printerName":
        // Can't set printerName, settings objects belong to a specific printer.
        break;

      case "printBackgrounds":
        target.printBGImages = value;
        target.printBGColors = value;
        break;

      case "printDuplex": {
        let duplex = (function () {
          switch (value) {
            case "off":
              break;
            case "long-edge":
              return Ci.nsIPrintSettings.kDuplexFlipOnLongEdge;
            case "short-edge":
              return Ci.nsIPrintSettings.kDuplexFlipOnShortEdge;
            default:
              logger.warn("Unexpected duplex name: ", value);
          }
          return Ci.nsIPrintSettings.kDuplexNone;
        })();

        target.duplex = duplex;
        break;
      }

      case "printFootersHeaders":
        // To disable header & footers, set them all to empty.
        // To enable, restore default values for each of the header & footer settings.
        for (let [settingName, defaultValue] of Object.entries(
          this.defaultHeadersAndFooterValues
        )) {
          target[settingName] = value ? defaultValue : "";
        }
        break;

      case "customMargins":
        if (value != null) {
          for (let [settingName, newVal] of Object.entries(value)) {
            target[settingName] = newVal;
            this._lastCustomMarginValues[settingName] = newVal;
          }
        }
        break;

      case "customMarginTop":
      case "customMarginBottom":
      case "customMarginLeft":
      case "customMarginRight":
        let customMarginName = "margin" + name.substring(12);
        this.set(
          target,
          "customMargins",
          Object.assign({}, this.get(target, "customMargins"), {
            [customMarginName]: value,
          })
        );
        break;

      case "sourceVersion":
        this._sourceVersion = value;
        this.set(target, "printSelectionOnly", value == "selection");
        if (value == "simplified") {
          this.set(target, "printBackgrounds", false);
        }
        break;

      default:
        target[name] = value;
    }
  },
};

/*
 * Custom elements ----------------------------------------------------
 */

function PrintUIControlMixin(superClass) {
  return class PrintUIControl extends superClass {
    connectedCallback() {
      this.setAttribute("autocomplete", "off");
      this.initialize();
      this.render();
    }

    initialize() {
      if (this._initialized) {
        return;
      }
      this._initialized = true;
      if (this.templateId) {
        let template = this.ownerDocument.getElementById(this.templateId);
        let templateContent = template.content;
        this.appendChild(templateContent.cloneNode(true));
      }

      document.addEventListener("print-settings", ({ detail: settings }) => {
        this.update(settings);
      });

      this.addEventListener("input", this);
    }

    render() {}

    update() {}

    dispatchSettingsChange(changedSettings) {
      this.dispatchEvent(
        new CustomEvent("update-print-settings", {
          bubbles: true,
          detail: changedSettings,
        })
      );
    }

    cancelSettingsChange(changedSettings) {
      this.dispatchEvent(
        new CustomEvent("cancel-print-settings", {
          bubbles: true,
          detail: changedSettings,
        })
      );
    }

    handleEvent() {}
  };
}

class PrintUIForm extends PrintUIControlMixin(HTMLFormElement) {
  initialize() {
    super.initialize();

    this.addEventListener("submit", this);
    this.addEventListener("click", this);
    this.addEventListener("revalidate", this);

    this._printerDestination = this.querySelector("#destination");
    this.printButton = this.querySelector("#print-button");
  }

  removeNonPdfSettings() {
    let selectors = ["#backgrounds", "#source-version-selection"];
    for (let selector of selectors) {
      this.querySelector(selector).remove();
    }
    let moreSettings = this.querySelector("#more-settings-options");
    if (moreSettings.children.length <= 1) {
      moreSettings.remove();
    }
  }

  requestPrint() {
    this.requestSubmit(this.printButton);
  }

  update(settings) {
    // If there are no default system printers available and we are not on mac,
    // we should hide the system dialog because it won't be populated with
    // the correct settings. Mac and Gtk support save to pdf functionality
    // in the native dialog, so it can be shown regardless.
    this.querySelector("#system-print").hidden =
      AppConstants.platform === "win" && !settings.defaultSystemPrinter;

    this.querySelector("#two-sided-printing").hidden = !settings.supportsDuplex;
  }

  enable() {
    let isValid = this.checkValidity();
    document.body.toggleAttribute("invalid", !isValid);
    if (isValid) {
      for (let element of this.elements) {
        if (!element.hasAttribute("disallowed")) {
          element.disabled = false;
        }
      }
      // aria-describedby will usually cause the first value to be reported.
      // Unfortunately, screen readers don't pick up description changes from
      // dialogs, so we must use a live region. To avoid double reporting of
      // the first value, we don't set aria-live initially. We only set it for
      // subsequent updates.
      // aria-live is set on the parent because sheetCount itself might be
      // hidden and then shown, and updates are only reported for live
      // regions that were already visible.
      document
        .querySelector("#sheet-count")
        .parentNode.setAttribute("aria-live", "polite");
    } else {
      // Find the invalid element
      let invalidElement;
      for (let element of this.elements) {
        if (!element.checkValidity()) {
          invalidElement = element;
          break;
        }
      }
      let section = invalidElement.closest(".section-block");
      document.body.toggleAttribute("invalid", !isValid);
      // We're hiding the sheet count and aria-describedby includes the
      // content of hidden elements, so remove aria-describedby.
      document.body.removeAttribute("aria-describedby");
      for (let element of this.elements) {
        // If we're valid, enable all inputs.
        // Otherwise, disable the valid inputs other than the cancel button and the elements
        // in the invalid section.
        element.disabled =
          element.hasAttribute("disallowed") ||
          (!isValid &&
            element.validity.valid &&
            element.name != "cancel" &&
            element.closest(".section-block") != this._printerDestination &&
            element.closest(".section-block") != section);
      }
    }
  }

  disable(filterFn) {
    for (let element of this.elements) {
      if (filterFn && !filterFn(element)) {
        continue;
      }
      element.disabled = element.name != "cancel";
    }
  }

  handleEvent(e) {
    if (e.target.id == "open-dialog-link") {
      this.dispatchEvent(new Event("open-system-dialog", { bubbles: true }));
      return;
    }

    if (e.type == "submit") {
      e.preventDefault();
      if (e.submitter.name == "print" && this.checkValidity()) {
        this.dispatchEvent(new Event("print", { bubbles: true }));
      }
    } else if (
      (e.type == "input" || e.type == "revalidate") &&
      !this.printerChanging
    ) {
      this.enable();
    }
  }
}
customElements.define("print-form", PrintUIForm, { extends: "form" });

class PrintSettingSelect extends PrintUIControlMixin(HTMLSelectElement) {
  initialize() {
    super.initialize();
    this.addEventListener("keypress", this);
  }

  connectedCallback() {
    this.settingName = this.dataset.settingName;
    super.connectedCallback();
  }

  setOptions(optionValues = []) {
    this.textContent = "";
    for (let optionData of optionValues) {
      let opt = new Option(
        optionData.name,
        "value" in optionData ? optionData.value : optionData.name
      );
      if (optionData.nameId) {
        document.l10n.setAttributes(opt, optionData.nameId);
      }
      // option selectedness is set via update() and assignment to this.value
      this.options.add(opt);
    }
  }

  update(settings) {
    if (this.settingName) {
      this.value = settings[this.settingName];
    }
  }

  handleEvent(e) {
    if (e.type == "input" && this.settingName) {
      this.dispatchSettingsChange({
        [this.settingName]: e.target.value,
      });
    } else if (e.type == "keypress") {
      if (
        e.key == "Enter" &&
        (!e.metaKey || AppConstants.platform == "macosx")
      ) {
        this.form.requestPrint();
      }
    }
  }
}
customElements.define("setting-select", PrintSettingSelect, {
  extends: "select",
});

class PrintSettingNumber extends PrintUIControlMixin(HTMLInputElement) {
  initialize() {
    super.initialize();
    this.addEventListener("beforeinput", e => this.preventWhitespaceEntry(e));
    this.addEventListener("paste", e => this.pasteWithoutWhitespace(e));
  }

  connectedCallback() {
    this.type = "number";
    this.settingName = this.dataset.settingName;
    super.connectedCallback();
  }

  update(settings) {
    if (this.settingName) {
      this.value = settings[this.settingName];
    }
  }

  preventWhitespaceEntry(e) {
    if (e.data && !e.data.trim().length) {
      e.preventDefault();
    }
  }

  pasteWithoutWhitespace(e) {
    // Prevent original value from being pasted
    e.preventDefault();

    // Manually update input's value with sanitized clipboard data
    let paste = (e.clipboardData || window.clipboardData)
      .getData("text")
      .trim();
    this.value = paste;
  }

  handleEvent(e) {
    switch (e.type) {
      case "input":
        if (this.settingName && this.checkValidity()) {
          this.dispatchSettingsChange({
            [this.settingName]: this.value,
          });
        }
        break;
    }
  }
}
customElements.define("setting-number", PrintSettingNumber, {
  extends: "input",
});

class PrintSettingCheckbox extends PrintUIControlMixin(HTMLInputElement) {
  connectedCallback() {
    this.type = "checkbox";
    this.settingName = this.dataset.settingName;
    super.connectedCallback();
  }

  update(settings) {
    this.checked = settings[this.settingName];
  }

  handleEvent() {
    this.dispatchSettingsChange({
      [this.settingName]: this.checked,
    });
  }
}
customElements.define("setting-checkbox", PrintSettingCheckbox, {
  extends: "input",
});

class PrintSettingRadio extends PrintUIControlMixin(HTMLInputElement) {
  connectedCallback() {
    this.type = "radio";
    this.settingName = this.dataset.settingName;
    super.connectedCallback();
  }

  update(settings) {
    this.checked = settings[this.settingName] == this.value;
  }

  handleEvent() {
    this.dispatchSettingsChange({
      [this.settingName]: this.value,
    });
  }
}
customElements.define("setting-radio", PrintSettingRadio, {
  extends: "input",
});

class DestinationPicker extends PrintSettingSelect {
  initialize() {
    super.initialize();
    document.addEventListener("available-destinations", this);
  }

  update(settings) {
    super.update(settings);
    let isPdf = settings.outputFormat == Ci.nsIPrintSettings.kOutputFormatPDF;
    this.setAttribute("output", isPdf ? "pdf" : "paper");
  }

  handleEvent(e) {
    super.handleEvent(e);

    if (e.type == "available-destinations") {
      this.setOptions(e.detail);
    }
  }
}
customElements.define("destination-picker", DestinationPicker, {
  extends: "select",
});

class ColorModePicker extends PrintSettingSelect {
  update(settings) {
    this.value = settings[this.settingName] ? "color" : "bw";
    let canSwitch = settings.supportsColor && settings.supportsMonochrome;
    if (this.disablePicker != canSwitch) {
      this.toggleAttribute("disallowed", !canSwitch);
      this.disabled = !canSwitch;
    }
    this.disablePicker = canSwitch;
  }

  handleEvent(e) {
    if (e.type == "input") {
      // turn our string value into the expected boolean
      this.dispatchSettingsChange({
        [this.settingName]: this.value == "color",
      });
    }
  }
}
customElements.define("color-mode-select", ColorModePicker, {
  extends: "select",
});

class PaperSizePicker extends PrintSettingSelect {
  initialize() {
    super.initialize();
    this._printerName = null;
    this._section = this.closest(".section-block");
    document.addEventListener("hide-paper-size", this);
  }

  update(settings) {
    if (settings.printerName !== this._printerName) {
      this._printerName = settings.printerName;
      this.setOptions(settings.paperSizes);
    }
    this.value = settings.paperId;

    // Unhide the paper-size picker, if we've stopped using the page size as paper-size.
    if (this._section.hidden && !settings.usePageRuleSizeAsPaperSize) {
      this._section.hidden = false;
    }
  }

  handleEvent(e) {
    super.handleEvent(e);
    const { type } = e;
    if (type == "hide-paper-size") {
      this._section.hidden = true;
    }
  }
}
customElements.define("paper-size-select", PaperSizePicker, {
  extends: "select",
});

class OrientationInput extends PrintUIControlMixin(HTMLElement) {
  initialize() {
    super.initialize();
    document.addEventListener("hide-orientation", this);
  }

  get templateId() {
    return "orientation-template";
  }

  update(settings) {
    for (let input of this.querySelectorAll("input")) {
      input.checked = settings.orientation == input.value;
    }
  }

  handleEvent(e) {
    if (e.type == "hide-orientation") {
      document.getElementById("orientation").hidden = true;
      return;
    }
    this.dispatchSettingsChange({
      orientation: e.target.value,
    });
  }
}
customElements.define("orientation-input", OrientationInput);

class CopiesInput extends PrintUIControlMixin(HTMLElement) {
  get templateId() {
    return "copy-template";
  }

  initialize() {
    super.initialize();
    this._copiesSection = this.closest(".section-block");
    this._copiesInput = this.querySelector("#copies-count");
    this._copiesError = this.querySelector("#error-invalid-copies");
  }

  update(settings) {
    this._copiesSection.hidden = settings.willSaveToFile;
    this._copiesError.hidden = true;
  }

  handleEvent() {
    this._copiesError.hidden = this._copiesInput.checkValidity();
  }
}
customElements.define("copy-count-input", CopiesInput);

class ScaleInput extends PrintUIControlMixin(HTMLElement) {
  get templateId() {
    return "scale-template";
  }

  initialize() {
    super.initialize();

    this._percentScale = this.querySelector("#percent-scale");
    this._shrinkToFitChoice = this.querySelector("#fit-choice");
    this._scaleChoice = this.querySelector("#percent-scale-choice");
    this._scaleError = this.querySelector("#error-invalid-scale");
  }

  updateScale() {
    this.dispatchSettingsChange({
      scaling: Number(this._percentScale.value / 100),
    });
  }

  update(settings) {
    let { scaling, shrinkToFit, printerName } = settings;
    this._shrinkToFitChoice.checked = shrinkToFit;
    this._scaleChoice.checked = !shrinkToFit;
    if (this.disableScale != shrinkToFit) {
      this._percentScale.disabled = shrinkToFit;
      this._percentScale.toggleAttribute("disallowed", shrinkToFit);
    }
    this.disableScale = shrinkToFit;
    if (!this.printerName) {
      this.printerName = printerName;
    }

    // If the user had an invalid input and switches back to "fit to page",
    // we repopulate the scale field with the stored, valid scaling value.
    let isValid = this._percentScale.checkValidity();
    if (
      !this._percentScale.value ||
      (this._shrinkToFitChoice.checked && !isValid) ||
      (this.printerName != printerName && !isValid)
    ) {
      // Only allow whole numbers. 0.14 * 100 would have decimal places, etc.
      this._percentScale.value = parseInt(scaling * 100, 10);
      this.printerName = printerName;
      if (!isValid) {
        this.dispatchEvent(new Event("revalidate", { bubbles: true }));
        this._scaleError.hidden = true;
      }
    }
  }

  handleEvent(e) {
    if (e.target == this._shrinkToFitChoice || e.target == this._scaleChoice) {
      if (!this._percentScale.checkValidity()) {
        this._percentScale.value = 100;
      }
      let scale =
        e.target == this._shrinkToFitChoice
          ? 1
          : Number(this._percentScale.value / 100);
      this.dispatchSettingsChange({
        shrinkToFit: this._shrinkToFitChoice.checked,
        scaling: scale,
      });
      this._scaleError.hidden = true;
    } else if (e.type == "input") {
      if (this._percentScale.checkValidity()) {
        this.updateScale();
      }
    }

    window.clearTimeout(this.showErrorTimeoutId);
    if (this._percentScale.validity.valid) {
      this._scaleError.hidden = true;
    } else {
      this.cancelSettingsChange({ scaling: true });
      this.showErrorTimeoutId = window.setTimeout(() => {
        this._scaleError.hidden = false;
      }, INPUT_DELAY_MS);
    }
  }
}
customElements.define("scale-input", ScaleInput);

class PageRangeInput extends PrintUIControlMixin(HTMLElement) {
  initialize() {
    super.initialize();

    this._rangeInput = this.querySelector("#custom-range");
    this._rangeInput.title = "";
    this._rangePicker = this.querySelector("#range-picker");
    this._rangePickerEvenOption = this._rangePicker.namedItem("even");
    this._rangeError = this.querySelector("#error-invalid-range");
    this._startRangeOverflowError = this.querySelector(
      "#error-invalid-start-range-overflow"
    );

    this._pagesSet = new Set();

    this.addEventListener("keypress", this);
    this.addEventListener("paste", this);
    document.addEventListener("page-count", this);
  }

  get templateId() {
    return "page-range-template";
  }

  updatePageRange() {
    let isCustom = this._rangePicker.value == "custom";
    let isCurrent = this._rangePicker.value == "current";

    if (!isCurrent) {
      this._currentPage = null;
    }

    if (isCustom) {
      this.validateRangeInput();
    } else if (isCurrent) {
      this._currentPage = this._rangeInput.value =
        this._currentPage || this.getCurrentVisiblePageNumber();
      this.validateRangeInput();
    } else {
      this._pagesSet.clear();

      if (this._rangePicker.value == "odd") {
        for (let i = 1; i <= this._numPages; i += 2) {
          this._pagesSet.add(i);
        }
      } else if (this._rangePicker.value == "even") {
        for (let i = 2; i <= this._numPages; i += 2) {
          this._pagesSet.add(i);
        }
      }

      if (!this._rangeInput.checkValidity()) {
        this._rangeInput.setCustomValidity("");
        this._rangeInput.value = "";
      }
    }

    this.dispatchEvent(new Event("revalidate", { bubbles: true }));

    document.l10n.setAttributes(
      this._rangeError,
      "printui-error-invalid-range",
      {
        numPages: this._numPages,
      }
    );

    // If it's valid, update the page range and hide the error messages.
    // Otherwise, set the appropriate error message
    if (this._rangeInput.validity.valid || !isCustom) {
      window.clearTimeout(this.showErrorTimeoutId);
      this._startRangeOverflowError.hidden = this._rangeError.hidden = true;
    } else {
      this._rangeInput.focus();
    }
  }

  dispatchPageRange(shouldCancel = true) {
    window.clearTimeout(this.showErrorTimeoutId);
    if (
      this._rangeInput.validity.valid ||
      this._rangePicker.value != "custom"
    ) {
      this.dispatchSettingsChange({
        pageRanges: this.formatPageRange(),
      });
    } else {
      if (shouldCancel) {
        this.cancelSettingsChange({ pageRanges: true });
      }
      this.showErrorTimeoutId = window.setTimeout(() => {
        this._rangeError.hidden =
          this._rangeInput.validationMessage != "invalid";
        this._startRangeOverflowError.hidden =
          this._rangeInput.validationMessage != "startRangeOverflow";
      }, INPUT_DELAY_MS);
    }
  }

  // The platform expects pageRanges to be an array of
  // ranges represented by ints.
  // Ex: Printing pages 1-3 would return [1,3]
  // Ex: Printing page 1 would return [1,1]
  // Ex: Printing pages 1-2,4 would return [1,2,4,4]
  formatPageRange() {
    if (
      this._pagesSet.size == 0 ||
      (this._rangePicker.value == "custom" && this._rangeInput.value == "") ||
      this._rangePicker.value == "all"
    ) {
      // Show all pages.
      return [];
    }
    let pages = Array.from(this._pagesSet).sort((a, b) => a - b);

    let formattedRanges = [];
    let startRange = pages[0];
    let endRange = pages[0];
    formattedRanges.push(startRange);

    for (let i = 1; i < pages.length; i++) {
      let currentPage = pages[i - 1];
      let nextPage = pages[i];
      if (nextPage > currentPage + 1) {
        formattedRanges.push(endRange);
        startRange = endRange = nextPage;
        formattedRanges.push(startRange);
      } else {
        endRange = nextPage;
      }
    }
    formattedRanges.push(endRange);

    return formattedRanges;
  }

  update(settings) {
    let { pageRanges, printerName } = settings;

    this.toggleAttribute("all-pages", !pageRanges.length);
    if (!this.printerName) {
      this.printerName = printerName;
    }

    let isValid = this._rangeInput.checkValidity();

    if (this.printerName != printerName && !isValid) {
      this.printerName = printerName;
      this._rangeInput.value = "";
      this.updatePageRange();
      this.dispatchPageRange();
    }
  }

  handleKeypress(e) {
    let char = String.fromCharCode(e.charCode);
    let acceptedChar = char.match(/^[0-9,-]$/);
    if (!acceptedChar && !char.match("\x00") && !e.ctrlKey && !e.metaKey) {
      e.preventDefault();
    }
  }

  handlePaste(e) {
    let paste = (e.clipboardData || window.clipboardData)
      .getData("text")
      .trim();
    if (!paste.match(/^[0-9,-]*$/)) {
      e.preventDefault();
    }
  }

  // This method has been abstracted into a helper for testing purposes
  _validateRangeInput(value, numPages) {
    this._pagesSet.clear();
    var ranges = value.split(",");

    for (let range of ranges) {
      let rangeParts = range.split("-");
      if (rangeParts.length > 2) {
        this._rangeInput.setCustomValidity("invalid");
        this._rangeInput.title = "";
        this._pagesSet.clear();
        return;
      }
      let startRange = parseInt(rangeParts[0], 10);
      let endRange = parseInt(
        rangeParts.length == 2 ? rangeParts[1] : rangeParts[0],
        10
      );

      if (isNaN(startRange) && isNaN(endRange)) {
        continue;
      }

      // If the startRange was not specified, then we infer this
      // to be 1.
      if (isNaN(startRange) && rangeParts[0] == "") {
        startRange = 1;
      }
      // If the end range was not specified, then we infer this
      // to be the total number of pages.
      if (isNaN(endRange) && rangeParts[1] == "") {
        endRange = numPages;
      }

      // Check the range for errors
      if (endRange < startRange) {
        this._rangeInput.setCustomValidity("startRangeOverflow");
        this._pagesSet.clear();
        return;
      } else if (
        startRange > numPages ||
        endRange > numPages ||
        startRange == 0
      ) {
        this._rangeInput.setCustomValidity("invalid");
        this._rangeInput.title = "";
        this._pagesSet.clear();
        return;
      }

      for (let i = startRange; i <= endRange; i++) {
        this._pagesSet.add(i);
      }
    }

    this._rangeInput.setCustomValidity("");
  }

  validateRangeInput() {
    let value = ["custom", "current"].includes(this._rangePicker.value)
      ? this._rangeInput.value
      : "";
    this._validateRangeInput(value, this._numPages);
  }

  getCurrentVisiblePageNumber() {
    let pageNum = parseInt(
      PrintEventHandler.printPreviewEl.lastPreviewBrowser.getAttribute(
        "current-page"
      )
    );
    return isNaN(pageNum) ? 1 : pageNum;
  }

  handleEvent(e) {
    if (e.type == "keypress") {
      if (e.target == this._rangeInput) {
        this.handleKeypress(e);
      }
      return;
    }

    if (e.type === "paste" && e.target == this._rangeInput) {
      this.handlePaste(e);
      return;
    }

    if (e.type == "page-count") {
      let { totalPages } = e.detail;
      // This means we have already handled the page count event
      // and do not need to dispatch another event.
      if (this._numPages == totalPages) {
        return;
      }

      this._numPages = totalPages;
      this._rangeInput.disabled = false;
      this._rangePickerEvenOption.disabled = this._numPages < 2;

      let prevPages = Array.from(this._pagesSet);
      this.updatePageRange();
      if (
        prevPages.length != this._pagesSet.size ||
        !prevPages.every(page => this._pagesSet.has(page))
      ) {
        // If the calculated set of pages has changed then we need to dispatch
        // a new pageRanges setting :(
        // Ideally this would be resolved in the settings code since it should
        // only happen for the "N-" case where pages N through the end of the
        // document are in the range.
        this.dispatchPageRange(false);
      }

      return;
    }

    if (e.target == this._rangePicker) {
      this._rangeInput.hidden = e.target.value != "custom";
      this.updatePageRange();
      this.dispatchPageRange();
      if (!this._rangeInput.hidden) {
        this._rangeInput.select();
      }
    } else if (e.target == this._rangeInput) {
      this._rangeInput.focus();
      if (this._numPages) {
        this.updatePageRange();
        this.dispatchPageRange();
      }
    }
  }
}
customElements.define("page-range-input", PageRangeInput);

class MarginsPicker extends PrintUIControlMixin(HTMLElement) {
  initialize() {
    super.initialize();

    this._marginPicker = this.querySelector("#margins-picker");
    this._customTopMargin = this.querySelector("#custom-margin-top");
    this._customBottomMargin = this.querySelector("#custom-margin-bottom");
    this._customLeftMargin = this.querySelector("#custom-margin-left");
    this._customRightMargin = this.querySelector("#custom-margin-right");
    this._marginError = this.querySelector("#error-invalid-margin");
    this._sizeUnit = null;
    this._toInchesMultiplier = 1;
  }

  get templateId() {
    return "margins-template";
  }

  updateCustomMargins() {
    let newMargins = {
      marginTop: this.toInchValue(this._customTopMargin.value),
      marginBottom: this.toInchValue(this._customBottomMargin.value),
      marginLeft: this.toInchValue(this._customLeftMargin.value),
      marginRight: this.toInchValue(this._customRightMargin.value),
    };

    this.dispatchSettingsChange({
      margins: "custom",
      customMargins: newMargins,
    });
    this._marginError.hidden = true;
  }

  updateMaxValues() {
    let maxWidth = this.toCurrentUnitValue(this._maxWidth);
    let maxHeight = this.toCurrentUnitValue(this._maxHeight);
    this._customTopMargin.max = this.formatMaxAttr(
      maxHeight - this._customBottomMargin.value
    );
    this._customBottomMargin.max = this.formatMaxAttr(
      maxHeight - this._customTopMargin.value
    );
    this._customLeftMargin.max = this.formatMaxAttr(
      maxWidth - this._customRightMargin.value
    );
    this._customRightMargin.max = this.formatMaxAttr(
      maxWidth - this._customLeftMargin.value
    );
  }

  truncateTwoDecimals(val) {
    if (val.split(".")[1].length > 2) {
      let dotIndex = val.indexOf(".");
      return val.slice(0, dotIndex + 3);
    }
    return val;
  }

  formatMaxAttr(val) {
    const strVal = val.toString();
    if (strVal.includes(".")) {
      return this.truncateTwoDecimals(strVal);
    }
    return val;
  }

  formatMargin(target) {
    if (target.value.includes(".")) {
      target.value = this.truncateTwoDecimals(target.value);
    }
  }

  toCurrentUnitValue(val) {
    if (typeof val == "string") {
      val = parseFloat(val);
    }
    return val / this._toInchesMultiplier;
  }

  toInchValue(val) {
    if (typeof val == "string") {
      val = parseFloat(val);
    }
    return val * this._toInchesMultiplier;
  }

  setAllMarginValues(settings) {
    this._customTopMargin.value = this.toCurrentUnitValue(
      settings.customMargins.marginTop
    ).toFixed(2);
    this._customBottomMargin.value = this.toCurrentUnitValue(
      settings.customMargins.marginBottom
    ).toFixed(2);
    this._customLeftMargin.value = this.toCurrentUnitValue(
      settings.customMargins.marginLeft
    ).toFixed(2);
    this._customRightMargin.value = this.toCurrentUnitValue(
      settings.customMargins.marginRight
    ).toFixed(2);
  }

  update(settings) {
    // Re-evaluate which margin options should be enabled whenever the printer or paper changes
    this._toInchesMultiplier =
      settings.paperSizeUnit == settings.kPaperSizeMillimeters
        ? INCHES_PER_MM
        : 1;
    if (
      settings.paperId !== this._paperId ||
      settings.printerName !== this._printerName ||
      settings.orientation !== this._orientation
    ) {
      let enabledMargins = settings.marginOptions;
      for (let option of this._marginPicker.options) {
        option.hidden = !enabledMargins[option.value];
      }
      this._paperId = settings.paperId;
      this._printerName = settings.printerName;
      this._orientation = settings.orientation;

      // Paper dimensions are in the paperSizeUnit. As the margin values are in inches
      // we'll normalize to that when storing max dimensions
      let height =
        this._orientation == 0 ? settings.paperHeight : settings.paperWidth;
      let width =
        this._orientation == 0 ? settings.paperWidth : settings.paperHeight;
      let heightInches =
        Math.round(this._toInchesMultiplier * height * 100) / 100;
      let widthInches =
        Math.round(this._toInchesMultiplier * width * 100) / 100;

      this._maxHeight =
        heightInches -
        settings.unwriteableMarginTop -
        settings.unwriteableMarginBottom;
      this._maxWidth =
        widthInches -
        settings.unwriteableMarginLeft -
        settings.unwriteableMarginRight;

      // The values in custom fields should be initialized to custom margin values
      // and must be overriden if they are no longer valid.
      this.setAllMarginValues(settings);
      this.updateMaxValues();
      this.dispatchEvent(new Event("revalidate", { bubbles: true }));
      this._marginError.hidden = true;
    }

    if (settings.paperSizeUnit !== this._sizeUnit) {
      this._sizeUnit = settings.paperSizeUnit;
      let unitStr =
        this._sizeUnit == settings.kPaperSizeMillimeters ? "mm" : "inches";
      for (let elem of this.querySelectorAll("[data-unit-prefix-l10n-id]")) {
        let l10nId = elem.getAttribute("data-unit-prefix-l10n-id") + unitStr;
        document.l10n.setAttributes(elem, l10nId);
      }
    }

    // We need to ensure we don't override the value if the value should be custom.
    if (this._marginPicker.value != "custom") {
      // Reset the custom margin values if they are not valid and revalidate the form
      if (
        !this._customTopMargin.checkValidity() ||
        !this._customBottomMargin.checkValidity() ||
        !this._customLeftMargin.checkValidity() ||
        !this._customRightMargin.checkValidity()
      ) {
        window.clearTimeout(this.showErrorTimeoutId);
        this.setAllMarginValues(settings);
        this.updateMaxValues();
        this.dispatchEvent(new Event("revalidate", { bubbles: true }));
        this._marginError.hidden = true;
      }
      if (settings.margins == "custom") {
        // Ensure that we display the custom margin boxes
        this.querySelector(".margin-group").hidden = false;
      }
      this._marginPicker.value = settings.margins;
    }
  }

  handleEvent(e) {
    if (e.target == this._marginPicker) {
      let customMargin = e.target.value == "custom";
      this.querySelector(".margin-group").hidden = !customMargin;
      if (customMargin) {
        // Update the custom margin values to ensure consistency
        this.updateCustomMargins();
        return;
      }

      this.dispatchSettingsChange({
        margins: e.target.value,
        customMargins: null,
      });
    }

    if (
      e.target == this._customTopMargin ||
      e.target == this._customBottomMargin ||
      e.target == this._customLeftMargin ||
      e.target == this._customRightMargin
    ) {
      if (e.target.checkValidity()) {
        this.updateMaxValues();
      }
      if (
        this._customTopMargin.validity.valid &&
        this._customBottomMargin.validity.valid &&
        this._customLeftMargin.validity.valid &&
        this._customRightMargin.validity.valid
      ) {
        this.formatMargin(e.target);
        this.updateCustomMargins();
      } else if (e.target.validity.stepMismatch) {
        // If this is the third digit after the decimal point, we should
        // truncate the string.
        this.formatMargin(e.target);
      }
    }

    window.clearTimeout(this.showErrorTimeoutId);
    if (
      this._customTopMargin.validity.valid &&
      this._customBottomMargin.validity.valid &&
      this._customLeftMargin.validity.valid &&
      this._customRightMargin.validity.valid
    ) {
      this._marginError.hidden = true;
    } else {
      this.cancelSettingsChange({ customMargins: true, margins: true });
      this.showErrorTimeoutId = window.setTimeout(() => {
        this._marginError.hidden = false;
      }, INPUT_DELAY_MS);
    }
  }
}
customElements.define("margins-select", MarginsPicker);

class TwistySummary extends PrintUIControlMixin(HTMLElement) {
  get isOpen() {
    return this.closest("details")?.hasAttribute("open");
  }

  get templateId() {
    return "twisty-summary-template";
  }

  initialize() {
    if (this._initialized) {
      return;
    }
    super.initialize();
    this.label = this.querySelector(".label");

    this.addEventListener("click", this);
    let shouldOpen = Services.prefs.getBoolPref(
      "print.more-settings.open",
      false
    );
    this.closest("details").open = shouldOpen;
    this.updateSummary(shouldOpen);
  }

  handleEvent() {
    let willOpen = !this.isOpen;
    Services.prefs.setBoolPref("print.more-settings.open", willOpen);
    this.updateSummary(willOpen);
  }

  updateSummary(open) {
    document.l10n.setAttributes(
      this.label,
      open
        ? this.getAttribute("data-open-l10n-id")
        : this.getAttribute("data-closed-l10n-id")
    );
  }
}
customElements.define("twisty-summary", TwistySummary);

class PageCount extends PrintUIControlMixin(HTMLElement) {
  initialize() {
    super.initialize();
    document.addEventListener("page-count", this);
  }

  update(settings) {
    this.numCopies = settings.numCopies;
    this.duplex = settings.duplex;
    this.outputDestination = settings.outputDestination;
    this.render();
  }

  render() {
    if (!this.numCopies || !this.sheetCount) {
      return;
    }

    let sheetCount = this.sheetCount;

    // When printing to a printer (not to a file) update
    // the sheet count to account for duplex printing.
    if (
      this.outputDestination == Ci.nsIPrintSettings.kOutputDestinationPrinter &&
      this.duplex != Ci.nsIPrintSettings.kDuplexNone
    ) {
      sheetCount = Math.ceil(sheetCount / 2);
    }

    sheetCount *= this.numCopies;

    document.l10n.setAttributes(this, "printui-sheets-count", {
      sheetCount,
    });

    // The loading attribute must be removed on first render
    if (this.hasAttribute("loading")) {
      this.removeAttribute("loading");
    }

    if (this.id) {
      // We're showing the sheet count, so let it describe the dialog.
      document.body.setAttribute("aria-describedby", this.id);
    }
  }

  handleEvent(e) {
    this.sheetCount = e.detail.sheetCount;
    this.render();
  }
}
customElements.define("page-count", PageCount);

class PrintBackgrounds extends PrintSettingCheckbox {
  update(settings) {
    super.update(settings);
    let isSimplified = settings.sourceVersion == "simplified";
    this.disabled = isSimplified;
    this.toggleAttribute("disallowed", isSimplified);
    this.checked = !isSimplified && settings.printBackgrounds;
  }
}
customElements.define("print-backgrounds", PrintBackgrounds, {
  extends: "input",
});

class PrintButton extends PrintUIControlMixin(HTMLButtonElement) {
  update(settings) {
    let l10nId =
      settings.printerName == PrintUtils.SAVE_TO_PDF_PRINTER
        ? "printui-primary-button-save"
        : "printui-primary-button";
    document.l10n.setAttributes(this, l10nId);
  }
}
customElements.define("print-button", PrintButton, { extends: "button" });

class CancelButton extends HTMLButtonElement {
  constructor() {
    super();
    this.addEventListener("click", () => {
      this.dispatchEvent(new Event("cancel-print", { bubbles: true }));
    });
  }
}
customElements.define("cancel-button", CancelButton, { extends: "button" });

async function pickFileName(contentTitle, currentURI) {
  let picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
  let [title] = await document.l10n.formatMessages([
    { id: "printui-save-to-pdf-title" },
  ]);
  title = title.value;

  let filename;
  if (contentTitle != "") {
    filename = contentTitle;
  } else {
    let url = new URL(currentURI);
    let path = decodeURIComponent(url.pathname);
    path = path.replace(/\/$/, "");
    filename = path.split("/").pop();
    if (filename == "") {
      filename = url.hostname;
    }
  }
  if (!filename.endsWith(".pdf")) {
    // macOS and linux don't set the extension based on the default extension.
    // Windows won't add the extension a second time, fortunately.
    // If it already ends with .pdf though, adding it again isn't needed.
    filename += ".pdf";
  }
  filename = DownloadPaths.sanitize(filename);

  picker.init(
    window.docShell.chromeEventHandler.ownerGlobal.browsingContext,
    title,
    Ci.nsIFilePicker.modeSave
  );
  picker.appendFilter("PDF", "*.pdf");
  picker.defaultExtension = "pdf";
  picker.defaultString = filename;

  let retval = await new Promise(resolve => picker.open(resolve));

  if (retval == 1) {
    throw new Error({ reason: "cancelled" });
  } else {
    // OK clicked (retval == 0) or replace confirmed (retval == 2)

    // Workaround: When trying to replace an existing file that is open in another application (i.e. a locked file),
    // the print progress listener is never called. This workaround ensures that a correct status is always returned.
    try {
      let fstream = Cc[
        "@mozilla.org/network/file-output-stream;1"
      ].createInstance(Ci.nsIFileOutputStream);
      fstream.init(picker.file, 0x2a, 0o666, 0); // ioflags = write|create|truncate, file permissions = rw-rw-rw-
      fstream.close();

      // Remove the file to reduce the likelihood of the user opening an empty or damaged fle when the
      // preview is loading
      await IOUtils.remove(picker.file.path);
    } catch (e) {
      throw new Error({ reason: retval == 0 ? "not_saved" : "not_replaced" });
    }
  }

  return picker.file.path;
}
PK
!<[�|��1chrome/toolkit/content/global/printPagination.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:host {
  /* in-content/common.css variables */
  --blue-50: #0a84ff;
  --grey-90-a10: rgba(12, 12, 13, 0.1);
  --shadow-30: 0 4px 16px var(--grey-90-a10);
  --border-active-shadow: var(--blue-50);
}

:host {
  display: block;
  position: absolute;
  bottom: 24px;
  inset-inline-start: 50%;
  translate: -50%;
}
:host(:-moz-locale-dir(rtl)) {
  translate: 50%;
}

.container {
  margin-inline: auto;
  align-items: center;
  display: flex;
  justify-content: center;
  box-shadow: var(--shadow-30);
  color: var(--toolbar-color);
  background-color: var(--toolbar-bgcolor);
  border-radius: 6px;
  border-style: none;
}
.container::before {
  content: "";
  display: block;
  position: absolute;
  inset: 0;
  z-index: -1;
  background-color: ButtonFace;
  border-radius: 6px;
}

.toolbarButton,
.toolbarCenter {
  align-self: stretch;
  flex: 0 0 auto;
  padding: var(--toolbarbutton-outer-padding);
  border: none;
  border-inline-end: 1px solid ThreeDShadow;
  border-block: 1px solid ThreeDShadow;
  color: inherit;
  background-color: transparent;
  min-width: calc(2 * var(--toolbarbutton-inner-padding) + 16px);
  min-height: calc(2 * var(--toolbarbutton-inner-padding) + 16px);
}
.startItem {
  border-inline-start: 1px solid ThreeDShadow;
  border-start-start-radius: 6px;
  border-end-start-radius: 6px;
}
.endItem {
  border-start-end-radius: 6px;
  border-end-end-radius: 6px;
}

.toolbarButton::after {
  content: "";
  display: inline-block;
  width: 16px;
  height: 16px;
  vertical-align: text-bottom;
  text-align: center;
  background-repeat: no-repeat;
  background-position: center center;
  background-size: 12px;
  -moz-context-properties: fill, fill-opacity;
  fill: var(--toolbarbutton-icon-fill);
}

.toolbarButton:hover {
  background-color: var(--toolbarbutton-hover-background);
}
.toolbarButton:hover:active {
  background-color: var(--toolbarbutton-active-background);
}
.toolbarButton::-moz-focus-inner {
  border: none;
}
.toolbarButton:focus {
  z-index: 1;
}

.toolbarButton:-moz-focusring {
  outline: 2px solid var(--border-active-shadow);
}

.toolbarCenter {
  flex-shrink: 0;
  /* 3 chars + (3px border + 1px padding) on both sides */
  min-width: calc(8px + 3ch);
  padding: 0 32px;
  display: flex;
  align-items: center;
  justify-content: center;
}

#navigateHome::after,
#navigateEnd::after {
  background-image: url("chrome://global/skin/icons/chevron.svg");
}

#navigatePrevious::after,
#navigateNext::after {
  background-image: url("chrome://global/skin/icons/arrow-left.svg");
}

#navigatePrevious:-moz-locale-dir(rtl)::after,
#navigateNext:-moz-locale-dir(ltr)::after {
  background-image: url("chrome://global/skin/icons/arrow-right.svg");
}

#navigateEnd:-moz-locale-dir(rtl)::after,
#navigateHome:-moz-locale-dir(ltr)::after {
  transform: scaleX(-1);
}

/* progressively hide the navigation buttons when the print preview is too narrow to fit */
@media (max-width: 550px) {
  #navigatePrevious,
  #navigateNext,
  #navigateEnd,
  #navigateHome {
    display: none;
  }
  .toolbarCenter {
    border-inline-start: 1px solid ThreeDShadow;
    border-radius: 6px;
  }
}
PK
!<�v���.chrome/toolkit/content/global/printPreview.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

.printSettingsBrowser {
  width: 250px !important;
}

.previewStack {
  background-color: light-dark(#f9f9fa, #2a2a2e);
  color: light-dark(#0c0c0d, rgb(249, 249, 250));
}

.previewRendering {
  background-repeat: no-repeat;
  background-size: 60px 60px;
  background-position: center center;
  display: flex;
  align-items: center;
  justify-content: center;

  .previewStack:not([rendering=true]) > & {
    visibility: hidden;
  }
}

.printPreviewBrowser {
  visibility: collapse;

  .previewStack[previewtype="source"] > &[previewtype="source"],
  .previewStack[previewtype="selection"] > &[previewtype="selection"],
  .previewStack[previewtype="simplified"] > &[previewtype="simplified"] {
    visibility: inherit;
  }

  .previewStack[rendering=true] > & {
    opacity: 0;
  }
}

.print-pending-label {
  margin-top: 110px;
  font-size: large;
}

printpreview-pagination {
  opacity: 0;
}
printpreview-pagination:focus-within,
.previewStack:hover printpreview-pagination {
  opacity: 1;
}
.previewStack[rendering=true] printpreview-pagination {
  opacity: 0;
}

@media (prefers-reduced-motion: no-preference) {
  .previewRendering {
    background-image: url("chrome://global/skin/icons/pendingpaint.png");
  }

  .printPreviewBrowser {
    transition: opacity 60ms;

    .previewStack[rendering=true] > & {
      transition: opacity 1ms 250ms;
    }
  }

  printpreview-pagination {
    transition: opacity 100ms 500ms;

    &:focus-within,
    .previewStack:hover & {
      transition: opacity 100ms;
    }
  }
}
PK
!<��jLL7chrome/toolkit/content/global/printPreviewPagination.js// This file is loaded into the browser window scope.
/* eslint-env mozilla/browser-window */

// -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*-

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

customElements.define(
  "printpreview-pagination",
  class PrintPreviewPagination extends HTMLElement {
    static get markup() {
      return `
      <html:link rel="stylesheet" href="chrome://global/content/printPagination.css" />
      <html:div class="container">
        <html:button id="navigateHome" class="toolbarButton startItem" data-l10n-id="printpreview-homearrow-button"></html:button>
        <html:button id="navigatePrevious" class="toolbarButton" data-l10n-id="printpreview-previousarrow-button"></html:button>
        <html:div class="toolbarCenter"><html:span id="sheetIndicator" data-l10n-id="printpreview-sheet-of-sheets" data-l10n-args='{ "sheetNum": 1, "sheetCount": 1 }'></html:span></html:div>
        <html:button id="navigateNext" class="toolbarButton" data-l10n-id="printpreview-nextarrow-button"></html:button>
        <html:button id="navigateEnd" class="toolbarButton endItem" data-l10n-id="printpreview-endarrow-button"></html:button>
      </html:div>
      `;
    }

    static get defaultProperties() {
      return {
        currentPage: 1,
        sheetCount: 1,
      };
    }

    get previewBrowser() {
      return this._previewBrowser;
    }

    set previewBrowser(aBrowser) {
      this._previewBrowser = aBrowser;
    }

    observePreviewBrowser(browser) {
      if (browser == this.previewBrowser || !this.isConnected) {
        return;
      }
      this.previewBrowser = browser;
      this.mutationObserver.disconnect();
      this.mutationObserver.observe(browser, {
        attributes: ["current-page", "sheet-count"],
      });
      this.updateFromBrowser();
    }

    connectedCallback() {
      MozXULElement.insertFTLIfNeeded("toolkit/printing/printPreview.ftl");

      const shadowRoot = this.attachShadow({ mode: "open" });
      document.l10n.connectRoot(shadowRoot);

      let fragment = MozXULElement.parseXULToFragment(this.constructor.markup);
      this.shadowRoot.append(fragment);

      this.elements = {
        sheetIndicator: shadowRoot.querySelector("#sheetIndicator"),
        homeButton: shadowRoot.querySelector("#navigateHome"),
        previousButton: shadowRoot.querySelector("#navigatePrevious"),
        nextButton: shadowRoot.querySelector("#navigateNext"),
        endButton: shadowRoot.querySelector("#navigateEnd"),
      };

      this.shadowRoot.addEventListener("click", this);

      this.mutationObserver = new MutationObserver(() =>
        this.updateFromBrowser()
      );

      // Initial render with some default values
      // We'll be updated with real values when available
      this.update(this.constructor.defaultProperties);
    }

    disconnectedCallback() {
      document.l10n.disconnectRoot(this.shadowRoot);
      this.shadowRoot.textContent = "";
      this.mutationObserver?.disconnect();
      delete this.mutationObserver;
      this.currentPreviewBrowserObserver?.disconnect();
      delete this.currentPreviewBrowserObserver;
    }

    handleEvent(event) {
      if (event.type == "click" && event.button != 0) {
        return;
      }
      event.stopPropagation();

      switch (event.target) {
        case this.elements.homeButton:
          this.navigate(0, 0, "home");
          break;
        case this.elements.previousButton:
          this.navigate(-1, 0, 0);
          break;
        case this.elements.nextButton:
          this.navigate(1, 0, 0);
          break;
        case this.elements.endButton:
          this.navigate(0, 0, "end");
          break;
      }
    }

    navigate(aDirection, aPageNum, aHomeOrEnd) {
      const nsIWebBrowserPrint = Ci.nsIWebBrowserPrint;
      let targetNum;
      let navType;
      // we use only one of aHomeOrEnd, aDirection, or aPageNum
      if (aHomeOrEnd) {
        // We're going to either the very first page ("home"), or the
        // very last page ("end").
        if (aHomeOrEnd == "home") {
          targetNum = 1;
          navType = nsIWebBrowserPrint.PRINTPREVIEW_HOME;
        } else {
          targetNum = this.sheetCount;
          navType = nsIWebBrowserPrint.PRINTPREVIEW_END;
        }
      } else if (aPageNum) {
        // We're going to a specific page (aPageNum)
        targetNum = Math.min(Math.max(1, aPageNum), this.sheetCount);
        navType = nsIWebBrowserPrint.PRINTPREVIEW_GOTO_PAGENUM;
      } else {
        // aDirection is either +1 or -1, and allows us to increment
        // or decrement our currently viewed page.
        targetNum = Math.min(
          Math.max(1, this.currentSheet + aDirection),
          this.sheetCount
        );
        navType = nsIWebBrowserPrint.PRINTPREVIEW_GOTO_PAGENUM;
      }

      // Preemptively update our own state, rather than waiting for the message from the child process
      // This allows subsequent clicks of next/back to advance 1 page per click if possible
      // and keeps the UI feeling more responsive
      this.update({ currentPage: targetNum });

      this.previewBrowser.sendMessageToActor(
        "Printing:Preview:Navigate",
        {
          navType,
          pageNum: targetNum,
        },
        "Printing"
      );
    }

    update(data = {}) {
      if (data.sheetCount) {
        this.sheetCount = data.sheetCount;
      }
      if (data.currentPage) {
        this.currentSheet = data.currentPage;
      }
      document.l10n.setAttributes(
        this.elements.sheetIndicator,
        this.elements.sheetIndicator.dataset.l10nId,
        {
          sheetNum: this.currentSheet,
          sheetCount: this.sheetCount,
        }
      );
    }

    updateFromBrowser() {
      let sheetCount = parseInt(
        this.previewBrowser.getAttribute("sheet-count"),
        10
      );
      let currentPage = parseInt(
        this.previewBrowser.getAttribute("current-page"),
        10
      );
      this.update({ sheetCount, currentPage });
    }
  }
);
PK
!<����KpKp+chrome/toolkit/content/global/printUtils.js// This file is loaded into the browser window scope.
/* eslint-env mozilla/browser-window */

// -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*-

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * PrintUtils is a utility for front-end code to trigger common print
 * operations (printing, show print preview, show page settings).
 *
 * Unfortunately, likely due to inconsistencies in how different operating
 * systems do printing natively, our XPCOM-level printing interfaces
 * are a bit confusing and the method by which we do something basic
 * like printing a page is quite circuitous.
 *
 * To compound that, we need to support remote browsers, and that means
 * kicking off the print jobs in the content process. This means we send
 * messages back and forth to that process via the Printing actor.
 *
 * This also means that <xul:browser>'s that hope to use PrintUtils must have
 * their type attribute set to "content".
 *
 * Messages sent:
 *
 *   Printing:Preview:Enter
 *     This message is sent to put content into print preview mode. We pass
 *     the content window of the browser we're showing the preview of, and
 *     the target of the message is the browser that we'll be showing the
 *     preview in.
 *
 *   Printing:Preview:Exit
 *     This message is sent to take content out of print preview mode.
 */

XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "SHOW_PAGE_SETUP_MENU",
  "print.show_page_setup_menu",
  false
);

XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "PRINT_ALWAYS_SILENT",
  "print.always_print_silent",
  false
);

XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "PREFER_SYSTEM_DIALOG",
  "print.prefer_system_dialog",
  false
);

ChromeUtils.defineESModuleGetters(this, {
  PromptUtils: "resource://gre/modules/PromptUtils.sys.mjs",
});

var PrintUtils = {
  SAVE_TO_PDF_PRINTER: "Mozilla Save to PDF",

  get _bundle() {
    delete this._bundle;
    return (this._bundle = Services.strings.createBundle(
      "chrome://global/locale/printing.properties"
    ));
  },

  async checkForSelection(browsingContext) {
    try {
      let sourceActor =
        browsingContext.currentWindowGlobal.getActor("PrintingSelection");
      // Need the await for the try to trigger...
      return await sourceActor.sendQuery("PrintingSelection:HasSelection", {});
    } catch (e) {
      console.error(e);
    }
    return false;
  },

  /**
   * Updates the hidden state of the "Page Setup" menu items in the File menu,
   * depending on the value of the `print.show_page_setup_menu` pref.
   * Note: not all platforms have a "Page Setup" menu item (or Page Setup
   * window).
   */
  updatePrintSetupMenuHiddenState() {
    let pageSetupMenuItem = document.getElementById("menu_printSetup");
    if (pageSetupMenuItem) {
      pageSetupMenuItem.hidden = !SHOW_PAGE_SETUP_MENU;
    }
  },

  /**
   * Shows the page setup dialog, and saves any settings changed in
   * that dialog if print.save_print_settings is set to true.
   *
   * @return true on success, false on failure
   */
  showPageSetup() {
    let printSettings = this.getPrintSettings();
    // If we come directly from the Page Setup menu, the hack in
    // _enterPrintPreview will not have been invoked to set the last used
    // printer name. For the reasons outlined at that hack, we want that set
    // here too.
    let PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
      Ci.nsIPrintSettingsService
    );
    if (!PSSVC.lastUsedPrinterName) {
      if (printSettings.printerName) {
        PSSVC.maybeSaveLastUsedPrinterNameToPrefs(printSettings.printerName);
        PSSVC.maybeSavePrintSettingsToPrefs(
          printSettings,
          Ci.nsIPrintSettings.kInitSaveAll
        );
      }
    }
    try {
      var PRINTDIALOGSVC = Cc[
        "@mozilla.org/widget/printdialog-service;1"
      ].getService(Ci.nsIPrintDialogService);
      PRINTDIALOGSVC.showPageSetupDialog(window, printSettings, null);
    } catch (e) {
      dump("showPageSetup " + e + "\n");
      return false;
    }
    return true;
  },

  /**
   * This call exists in a separate method so it can be easily overridden where
   * `gBrowser` doesn't exist (e.g. Thunderbird).
   *
   * @see getTabDialogBox in tabbrowser.js
   */
  getTabDialogBox(sourceBrowser) {
    return gBrowser.getTabDialogBox(sourceBrowser);
  },

  getPreviewBrowser(sourceBrowser) {
    let dialogBox = this.getTabDialogBox(sourceBrowser);
    for (let dialog of dialogBox.getTabDialogManager()._dialogs) {
      let browser = dialog._box.querySelector(".printPreviewBrowser");
      if (browser) {
        return browser;
      }
    }
    return null;
  },

  /**
   * Opens the tab modal version of the print UI for the current tab.
   *
   * @param aBrowsingContext
   *        The BrowsingContext of the window to print.
   * @param aExistingPreviewBrowser
   *        An existing browser created for printing from window.print().
   * @param aPrintSelectionOnly
   *        Whether to print only the active selection of the given browsing
   *        context.
   * @param aPrintFrameOnly
   *        Whether to print the selected frame only
   * @return promise resolving when the dialog is open, rejected if the preview
   *         fails.
   */
  _openTabModalPrint(
    aBrowsingContext,
    aOpenWindowInfo,
    aPrintSelectionOnly,
    aPrintFrameOnly
  ) {
    let sourceBrowser = aBrowsingContext.top.embedderElement;
    let previewBrowser = this.getPreviewBrowser(sourceBrowser);
    if (previewBrowser) {
      // Don't open another dialog if we're already printing.
      //
      // XXX This can be racy can't it? getPreviewBrowser looks at browser that
      // we set up after opening the dialog. But I guess worst case we just
      // open two dialogs so...
      throw new Error("Tab-modal print UI already open");
    }

    // Create the print preview dialog.
    let args = PromptUtils.objectToPropBag({
      printSelectionOnly: !!aPrintSelectionOnly,
      isArticle: sourceBrowser.isArticle,
      printFrameOnly: !!aPrintFrameOnly,
    });
    let dialogBox = this.getTabDialogBox(sourceBrowser);
    let { closedPromise, dialog } = dialogBox.open(
      `chrome://global/content/print.html`,
      { features: "resizable=no", sizeTo: "available" },
      args
    );
    closedPromise.catch(e => {
      console.error(e);
    });

    let settingsBrowser = dialog._frame;
    let printPreview = new PrintPreview({
      sourceBrowsingContext: aBrowsingContext,
      settingsBrowser,
      topBrowsingContext: aBrowsingContext.top,
      activeBrowsingContext: aBrowsingContext,
      openWindowInfo: aOpenWindowInfo,
      printFrameOnly: aPrintFrameOnly,
    });
    // This will create the source browser in connectedCallback() if we sent
    // openWindowInfo. Otherwise the browser will be null.
    settingsBrowser.parentElement.insertBefore(printPreview, settingsBrowser);
    return printPreview.sourceBrowser;
  },

  /**
   * Initialize a print, this will open the tab modal UI if it is enabled or
   * defer to the native dialog/silent print.
   *
   * @param aBrowsingContext
   *        The BrowsingContext of the window to print.
   *        Note that the browsing context could belong to a subframe of the
   *        tab that called window.print, or similar shenanigans.
   * @param aOptions
   *        {windowDotPrintOpenWindowInfo}
   *                              Non-null if this call comes from window.print().
   *                              This is the nsIOpenWindowInfo object that has to
   *                              be passed down to createBrowser in order for the
   *                              static clone that has been cretaed in the child
   *                              process to be linked to the browser it creates
   *                              in the parent process.
   *        {printSelectionOnly}  Whether to print only the active selection of
   *                              the given browsing context.
   *        {printFrameOnly}      Whether to print the selected frame.
   */
  startPrintWindow(aBrowsingContext, aOptions) {
    // At most, one of these is set.
    let { printSelectionOnly, printFrameOnly, windowDotPrintOpenWindowInfo } =
      aOptions || {};

    if (
      windowDotPrintOpenWindowInfo &&
      !windowDotPrintOpenWindowInfo.isForWindowDotPrint
    ) {
      throw new Error("Only expect openWindowInfo for window.print()");
    }

    let browsingContext = aBrowsingContext;
    if (printSelectionOnly) {
      // Ensure that we use the window with focus/selection if the context menu
      // (from which 'Print selection' was selected) happens to have been opened
      // over a different frame.
      let focusedBc = Services.focus.focusedContentBrowsingContext;
      if (
        focusedBc &&
        focusedBc.top.embedderElement == browsingContext.top.embedderElement
      ) {
        browsingContext = focusedBc;
      }
    }

    if (!PRINT_ALWAYS_SILENT && !PREFER_SYSTEM_DIALOG) {
      return this._openTabModalPrint(
        browsingContext,
        windowDotPrintOpenWindowInfo,
        printSelectionOnly,
        printFrameOnly
      );
    }

    const useSystemDialog = PREFER_SYSTEM_DIALOG && !PRINT_ALWAYS_SILENT;

    let browser = null;
    if (windowDotPrintOpenWindowInfo) {
      // When we're called by handleStaticCloneCreatedForPrint(), we must
      // return this browser.
      browser = this.createParentBrowserForStaticClone(
        browsingContext,
        windowDotPrintOpenWindowInfo
      );
      browsingContext = browser.browsingContext;
    }

    // This code is wrapped in an async function so that we can await the async
    // functions that it calls.
    async function makePrintSettingsAndInvokePrint() {
      let settings = PrintUtils.getPrintSettings(
        /*aPrinterName*/ "",
        /*aDefaultsOnly*/ false,
        /*aAllowPseudoPrinter*/ !useSystemDialog
      );
      settings.printSelectionOnly = printSelectionOnly;
      if (
        settings.outputDestination ==
          Ci.nsIPrintSettings.kOutputDestinationFile &&
        !settings.toFileName
      ) {
        // TODO(bug 1748004): We should consider generating the file name
        // from the document's title as we do in print.js's pickFileName
        // (including using DownloadPaths.sanitize!).
        // For now, the following is for consistency with the behavior
        // prior to bug 1669149 part 3.
        let dest = undefined;
        try {
          dest = Services.dirsvc.get("CurWorkD", Ci.nsIFile).path;
        } catch (e) {}
        if (!dest) {
          dest = Services.dirsvc.get("Home", Ci.nsIFile).path;
        }
        settings.toFileName = PathUtils.join(dest, "mozilla.pdf");
      }

      if (useSystemDialog) {
        const hasSelection = await PrintUtils.checkForSelection(
          browsingContext
        );

        // Prompt the user to choose a printer and make any desired print
        // settings changes.
        let doPrint = false;
        try {
          doPrint = await PrintUtils.handleSystemPrintDialog(
            browsingContext.topChromeWindow,
            hasSelection,
            settings
          );
          if (!doPrint) {
            return;
          }
        } finally {
          // Clean up browser if we aren't going to use it.
          if (!doPrint && browser) {
            browser.remove();
          }
        }
      }

      // At some point we should handle the Promise that this returns (at
      // least report rejection to telemetry).
      browsingContext.print(settings);
    }

    // We need to return to the event loop before calling
    // makePrintSettingsAndInvokePrint() if we were called for `window.print()`.
    // That's because if that function synchronously calls `browser.remove()`
    // or `browsingContext.print()` before we return `browser`, the nested
    // event loop that is being spun in the content process under the
    // OpenInternal call in nsGlobalWindowOuter::Print will still be active.
    // In the case of `browser.remove()`, nsGlobalWindowOuter::Print would then
    // get unhappy once OpenInternal does return since it will fail to return
    // a BrowsingContext. In the case of `browsingContext.print()`, we would
    // re-enter nsGlobalWindowOuter::Print under the nested event loop and
    // printing would then fail since the outer nsGlobalWindowOuter::Print call
    // wouldn't yet have created the static clone.
    setTimeout(makePrintSettingsAndInvokePrint, 0);

    return browser;
  },

  togglePrintPreview(aBrowsingContext) {
    let dialogBox = this.getTabDialogBox(aBrowsingContext.top.embedderElement);
    let dialogs = dialogBox.getTabDialogManager().dialogs;
    let previewDialog = dialogs.find(d =>
      d._box.querySelector(".printSettingsBrowser")
    );
    if (previewDialog) {
      previewDialog.close();
      return;
    }
    this.startPrintWindow(aBrowsingContext);
  },

  /**
   * Called when a content process has created a new BrowsingContext for a
   * static clone of a document that is to be printed, but we do NOT yet have a
   * CanonicalBrowsingContext counterpart in the parent process. This only
   * happens in the following cases:
   *
   *   - content script invoked window.print() in the content process, or:
   *   - silent printing is enabled, and UI code previously invoked
   *     startPrintWindow which called BrowsingContext.print(), and we're now
   *     being called back by the content process to parent the static clone.
   *
   * In the latter case we only need to create the CanonicalBrowsingContext,
   * link it to it's content process counterpart, and inserted it into
   * the document tree; the print in the content process has already been
   * initiated.
   *
   * In the former case we additionally need to check if we should open the
   * tab modal print UI (if not silent printing), obtain a valid
   * nsIPrintSettings object, and tell the content process to initiate the
   * print with this settings object.
   */
  handleStaticCloneCreatedForPrint(aOpenWindowInfo) {
    let browsingContext = aOpenWindowInfo.parent;
    if (aOpenWindowInfo.isForWindowDotPrint) {
      return this.startPrintWindow(browsingContext, {
        windowDotPrintOpenWindowInfo: aOpenWindowInfo,
      });
    }
    return this.createParentBrowserForStaticClone(
      browsingContext,
      aOpenWindowInfo
    );
  },

  createParentBrowserForStaticClone(aBrowsingContext, aOpenWindowInfo) {
    // XXX This code is only called when silent printing, so we're really
    // abusing PrintPreview here. See bug 1768020.
    let printPreview = new PrintPreview({
      sourceBrowsingContext: aBrowsingContext,
      openWindowInfo: aOpenWindowInfo,
    });
    let browser = printPreview.createPreviewBrowser("source");
    document.documentElement.append(browser);
    return browser;
  },

  // "private" methods and members. Don't use them.

  _getErrorCodeForNSResult(nsresult) {
    const MSG_CODES = [
      "GFX_PRINTER_NO_PRINTER_AVAILABLE",
      "GFX_PRINTER_NAME_NOT_FOUND",
      "GFX_PRINTER_COULD_NOT_OPEN_FILE",
      "GFX_PRINTER_STARTDOC",
      "GFX_PRINTER_ENDDOC",
      "GFX_PRINTER_STARTPAGE",
      "GFX_PRINTER_DOC_IS_BUSY",
      "ABORT",
      "NOT_AVAILABLE",
      "NOT_IMPLEMENTED",
      "OUT_OF_MEMORY",
      "UNEXPECTED",
    ];

    for (let code of MSG_CODES) {
      let nsErrorResult = "NS_ERROR_" + code;
      if (Cr[nsErrorResult] == nsresult) {
        return code;
      }
    }

    // PERR_FAILURE is the catch-all error message if we've gotten one that
    // we don't recognize.
    return "FAILURE";
  },

  _displayPrintingError(nsresult, isPrinting, browser) {
    // The nsresults from a printing error are mapped to strings that have
    // similar names to the errors themselves. For example, for error
    // NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE, the name of the string
    // for the error message is: PERR_GFX_PRINTER_NO_PRINTER_AVAILABLE. What's
    // more, if we're in the process of doing a print preview, it's possible
    // that there are strings specific for print preview for these errors -
    // if so, the names of those strings have _PP as a suffix. It's possible
    // that no print preview specific strings exist, in which case it is fine
    // to fall back to the original string name.
    let msgName = "PERR_" + this._getErrorCodeForNSResult(nsresult);
    let msg, title;
    if (!isPrinting) {
      // Try first with _PP suffix.
      let ppMsgName = msgName + "_PP";
      try {
        msg = this._bundle.GetStringFromName(ppMsgName);
      } catch (e) {
        // We allow localizers to not have the print preview error string,
        // and just fall back to the printing error string.
      }
    }

    if (!msg) {
      msg = this._bundle.GetStringFromName(msgName);
    }

    title = this._bundle.GetStringFromName(
      isPrinting
        ? "print_error_dialog_title"
        : "printpreview_error_dialog_title"
    );

    Services.prompt.asyncAlert(
      browser.browsingContext,
      Services.prompt.MODAL_TYPE_TAB,
      title,
      msg
    );

    Services.telemetry.keyedScalarAdd(
      "printing.error",
      this._getErrorCodeForNSResult(nsresult),
      1
    );
  },

  getPrintSettings(aPrinterName, aDefaultsOnly, aAllowPseudoPrinter = true) {
    var printSettings;
    try {
      var PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
        Ci.nsIPrintSettingsService
      );

      function isValidPrinterName(aPrinterName) {
        return (
          aPrinterName &&
          (aAllowPseudoPrinter ||
            aPrinterName != PrintUtils.SAVE_TO_PDF_PRINTER)
        );
      }

      // We must not try to print using an nsIPrintSettings without a printer
      // name set.
      const printerName = (function () {
        if (isValidPrinterName(aPrinterName)) {
          return aPrinterName;
        }
        if (isValidPrinterName(PSSVC.lastUsedPrinterName)) {
          return PSSVC.lastUsedPrinterName;
        }
        return Cc["@mozilla.org/gfx/printerlist;1"].getService(
          Ci.nsIPrinterList
        ).systemDefaultPrinterName;
      })();

      printSettings = PSSVC.createNewPrintSettings();
      printSettings.printerName = printerName;

      // First get any defaults from the printer. We want to skip this for Save
      // to PDF since it isn't a real printer and will throw.
      if (printSettings.printerName != this.SAVE_TO_PDF_PRINTER) {
        PSSVC.initPrintSettingsFromPrinter(
          printSettings.printerName,
          printSettings
        );
      }

      if (!aDefaultsOnly) {
        // Apply any settings that have been saved for this printer.
        PSSVC.initPrintSettingsFromPrefs(
          printSettings,
          true,
          printSettings.kInitSaveAll
        );
      }
    } catch (e) {
      console.error("PrintUtils.getPrintSettings failed:", e);
    }
    return printSettings;
  },

  // Show the system print dialog, saving modified preferences.
  // Returns true if the user clicked print (Not cancel).
  async handleSystemPrintDialog(aWindow, aHasSelection, aSettings) {
    // Prompt the user to choose a printer and make any desired print
    // settings changes.
    try {
      const svc = Cc["@mozilla.org/widget/printdialog-service;1"].getService(
        Ci.nsIPrintDialogService
      );
      await svc.showPrintDialog(aWindow, aHasSelection, aSettings);
    } catch (e) {
      if (e.result == Cr.NS_ERROR_ABORT) {
        return false;
      }
      throw e;
    }

    // Update the saved last used printer name and print settings:
    var PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
      Ci.nsIPrintSettingsService
    );
    PSSVC.maybeSaveLastUsedPrinterNameToPrefs(aSettings.printerName);
    PSSVC.maybeSavePrintSettingsToPrefs(
      aSettings,
      Ci.nsIPrintSettings.kPrintDialogPersistSettings
    );
    return true;
  },
};

/**
 * This class implements a custom element that contains a nested <browser>
 * element. When the user asks to print a document, we create an instance of
 * this class and ask the platform code to create a static clone of the
 * document (a snapshot that won't change due to script running, etc.) in the
 * contained <browser> element.
 *
 * To display a print preview to the user, an instance of this element is added
 * to the tab-modal print preview dialog. As the user changes print preview
 * settings, we may actually end up with multiple instances: one for a preview
 * of the original document, one for a preview of the focused frame, and one
 * for the selected text.
 *
 * To print without displaying a print preview, an instance of this class is
 * appended, hidden, to the end of the top-level chrome browser's document.
 */
class PrintPreview extends MozElements.BaseControl {
  constructor({
    sourceBrowsingContext,
    settingsBrowser,
    topBrowsingContext,
    activeBrowsingContext,
    openWindowInfo,
    printFrameOnly,
  }) {
    super();
    this.sourceBrowsingContext = sourceBrowsingContext;
    this.settingsBrowser = settingsBrowser;
    this.topBrowsingContext = topBrowsingContext;
    this.activeBrowsingContext = activeBrowsingContext;
    this.openWindowInfo = openWindowInfo;
    this.printFrameOnly = printFrameOnly;

    this.printSelectionOnly = false;
    this.simplifyPage = false;
    this.sourceBrowser = null;
    this.selectionBrowser = null;
    this.simplifiedBrowser = null;
    this.lastPreviewBrowser = null;
  }

  connectedCallback() {
    if (this.childElementCount > 0) {
      return;
    }
    this.setAttribute("flex", "1");
    this.append(
      MozXULElement.parseXULToFragment(`
        <stack class="previewStack" rendering="true" flex="1" previewtype="primary">
          <vbox class="previewRendering" flex="1">
            <h1 class="print-pending-label" data-l10n-id="printui-loading"></h1>
          </vbox>
          <html:printpreview-pagination class="printPreviewNavigation"></html:printpreview-pagination>
        </stack>
        <html:link rel="stylesheet" href="chrome://global/content/printPreview.css"/>
    `)
    );
    this.stack = this.firstElementChild;
    this.paginator = this.querySelector("printpreview-pagination");

    if (this.openWindowInfo) {
      // For window.print() we need a browser right away for the contents to be
      // cloned into, create it now.
      this.createPreviewBrowser("source");
    }
  }

  disconnectedCallback() {
    this.exitPrintPreview();
  }

  getSourceBrowsingContext() {
    if (this.openWindowInfo) {
      // If openWindowInfo is set this was for window.print() and the source
      // contents have already been cloned into the preview browser.
      return this.sourceBrowser.browsingContext;
    }
    return this.sourceBrowsingContext;
  }

  get currentBrowsingContext() {
    return this.lastPreviewBrowser.browsingContext;
  }

  exitPrintPreview() {
    this.sourceBrowser?.frameLoader?.exitPrintPreview();
    this.simplifiedBrowser?.frameLoader?.exitPrintPreview();
    this.selectionBrowser?.frameLoader?.exitPrintPreview();

    this.textContent = "";
  }

  async printPreview(settings, { sourceVersion, sourceURI }) {
    this.stack.setAttribute("rendering", true);

    let result = await this._printPreview(settings, {
      sourceVersion,
      sourceURI,
    });

    let browser = this.lastPreviewBrowser;
    this.stack.setAttribute("previewtype", browser.getAttribute("previewtype"));
    browser.setAttribute("sheet-count", result.sheetCount);
    // The view resets to the top of the document on update bug 1686737.
    browser.setAttribute("current-page", 1);
    this.paginator.observePreviewBrowser(browser);
    await document.l10n.translateElements([browser]);

    this.stack.removeAttribute("rendering");

    return result;
  }

  async _printPreview(settings, { sourceVersion, sourceURI }) {
    let printSelectionOnly = sourceVersion == "selection";
    let simplifyPage = sourceVersion == "simplified";
    let selectionTypeBrowser;
    let previewBrowser;

    // Select the existing preview browser elements, these could be null.
    if (printSelectionOnly) {
      selectionTypeBrowser = this.selectionBrowser;
      previewBrowser = this.selectionBrowser;
    } else {
      selectionTypeBrowser = this.sourceBrowser;
      previewBrowser = simplifyPage
        ? this.simplifiedBrowser
        : this.sourceBrowser;
    }

    settings.docURL = sourceURI;

    if (previewBrowser) {
      this.lastPreviewBrowser = previewBrowser;
      if (this.openWindowInfo) {
        // We only want to use openWindowInfo for the window.print() browser,
        // we can get rid of it now.
        this.openWindowInfo = null;
      }
      // This browser has been rendered already, just update it.
      return previewBrowser.frameLoader.printPreview(settings, null);
    }

    if (!selectionTypeBrowser) {
      // Need to create a non-simplified browser.
      selectionTypeBrowser = this.createPreviewBrowser(
        simplifyPage ? "source" : sourceVersion
      );
      let browsingContext =
        printSelectionOnly || this.printFrameOnly
          ? this.activeBrowsingContext
          : this.topBrowsingContext;
      let result = await selectionTypeBrowser.frameLoader.printPreview(
        settings,
        browsingContext
      );
      // If this isn't simplified then we're done.
      if (!simplifyPage) {
        this.lastPreviewBrowser = selectionTypeBrowser;
        return result;
      }
    }

    // We have the base selection/primary browser but need to simplify.
    previewBrowser = this.createPreviewBrowser(sourceVersion);
    await previewBrowser.browsingContext.currentWindowGlobal
      .getActor("Printing")
      .sendQuery("Printing:Preview:ParseDocument", {
        URL: sourceURI,
        windowID:
          selectionTypeBrowser.browsingContext.currentWindowGlobal
            .outerWindowId,
      });

    // We've parsed a simplified version into the preview browser. Convert that to
    // a print preview as usual.
    this.lastPreviewBrowser = previewBrowser;
    return previewBrowser.frameLoader.printPreview(
      settings,
      previewBrowser.browsingContext
    );
  }

  createPreviewBrowser(sourceVersion) {
    let browser = document.createXULElement("browser");
    let browsingContext =
      sourceVersion == "selection" ||
      this.printFrameOnly ||
      (sourceVersion == "source" && this.openWindowInfo)
        ? this.sourceBrowsingContext
        : this.sourceBrowsingContext.top;
    if (sourceVersion == "source" && this.openWindowInfo) {
      browser.openWindowInfo = this.openWindowInfo;
    } else {
      let userContextId = browsingContext.originAttributes.userContextId;
      if (userContextId) {
        browser.setAttribute("usercontextid", userContextId);
      }
      browser.setAttribute(
        "initialBrowsingContextGroupId",
        browsingContext.group.id
      );
    }
    browser.setAttribute("type", "content");
    let remoteType = browsingContext.currentRemoteType;
    if (remoteType) {
      browser.setAttribute("remoteType", remoteType);
      browser.setAttribute("remote", "true");
    }
    // When the print process finishes, we get closed by
    // nsDocumentViewer::OnDonePrinting, or by the print preview code.
    //
    // When that happens, we should remove us from the DOM if connected.
    browser.addEventListener("DOMWindowClose", function (e) {
      if (this.isConnected) {
        this.remove();
      }
      e.stopPropagation();
      e.preventDefault();
    });

    if (this.settingsBrowser) {
      browser.addEventListener("contextmenu", function (e) {
        e.preventDefault();
      });

      browser.setAttribute("previewtype", sourceVersion);
      browser.classList.add("printPreviewBrowser");
      browser.setAttribute("flex", "1");
      browser.setAttribute("printpreview", "true");
      browser.setAttribute("nodefaultsrc", "true");
      document.l10n.setAttributes(browser, "printui-preview-label");

      this.stack.insertBefore(browser, this.paginator);

      if (sourceVersion == "source") {
        this.sourceBrowser = browser;
      } else if (sourceVersion == "selection") {
        this.selectionBrowser = browser;
      } else if (sourceVersion == "simplified") {
        this.simplifiedBrowser = browser;
      }
    } else {
      browser.style.visibility = "collapse";
    }
    return browser;
  }
}
customElements.define("print-preview", PrintPreview);
PK
!<�K�Bs*s*5chrome/toolkit/content/global/reader/aboutReader.html<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this file,
   - You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html>
<html>
  <head>
    <title id="reader-title"></title>
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:; img-src data: *; media-src *; object-src 'none'"
    />
    <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
    <meta name="viewport" content="width=device-width; user-scalable=0" />
    <link
      rel="stylesheet"
      href="chrome://global/skin/design-system/tokens-brand.css"
    />
    <link
      rel="stylesheet"
      href="chrome://global/skin/aboutReader.css"
      type="text/css"
    />
    <link rel="localization" href="toolkit/about/aboutReader.ftl" />
    <link rel="localization" href="toolkit/branding/brandings.ftl" />
    <script
      type="module"
      src="chrome://global/content/reader/moz-slider.mjs"
    ></script>
    <script
      type="module"
      src="chrome://global/content/reader/color-input.mjs"
    ></script>
    <script
      type="module"
      src="chrome://global/content/elements/named-deck.js"
    ></script>
  </head>

  <body>
    <div class="top-anchor"></div>

    <div id="toolbar" class="toolbar-container">
      <div class="toolbar reader-toolbar">
        <div class="reader-controls">
          <button
            class="close-button toolbar-button"
            aria-labelledby="toolbar-close"
            data-telemetry-id="reader-close"
          >
            <span
              class="hover-label"
              id="toolbar-close"
              data-l10n-id="about-reader-toolbar-close"
            ></span>
          </button>
          <ul
            class="dropdown improved-style-dropdown"
            id="improved-text-menu"
            hidden="true"
          >
            <li>
              <button
                class="dropdown-toggle toolbar-button improved-style-button"
                aria-labelledby="toolbar-text-layout-controls"
                data-telemetry-id="reader-text-layout-controls"
              >
                <span
                  class="hover-label"
                  id="toolbar-text-layout-controls"
                  data-l10n-id="about-reader-toolbar-text-layout-controls"
                ></span>
              </button>
            </li>
            <li class="dropdown-popup" id="text-layout-controls" tabindex="-1">
              <h2
                data-l10n-id="about-reader-text-header"
                id="about-reader-text-header"
              ></h2>
              <div id="text-size-controls">
                <span
                  data-l10n-id="about-reader-text-size-label"
                  id="about-reader-text-size-label"
                ></span>
                <div class="text-size-buttons buttonrow">
                  <button
                    class="text-size-minus-button"
                    data-l10n-id="about-reader-toolbar-minus"
                    data-telemetry-id="text-size-minus-button"
                  ></button>
                  <button
                    class="text-size-plus-button"
                    data-l10n-id="about-reader-toolbar-plus"
                    data-telemetry-id="text-size-plus-button"
                  ></button>
                </div>
              </div>
              <div id="font-controls">
                <div id="font-type-dropdown" class="dropdown-selector">
                  <label
                    for="font-type-selector"
                    data-l10n-id="about-reader-font-type-selector-label"
                  ></label>
                  <select
                    name="font-type-selector"
                    id="font-type-selector"
                    data-telemetry-id="font-type-selector"
                  ></select>
                </div>
                <div id="font-weight-dropdown" class="dropdown-selector">
                  <label
                    for="font-weight-selector"
                    data-l10n-id="about-reader-font-weight-selector-label"
                  ></label>
                  <select
                    name="font-weight-selector"
                    id="font-weight-selector"
                    data-telemetry-id="font-weight-selector"
                  ></select>
                </div>
              </div>
              <hr />
              <h2
                data-l10n-id="about-reader-layout-header"
                id="about-reader-layout-header"
              ></h2>
              <div id="content-width-slider" class="slider-container"></div>
              <div id="line-spacing-slider" class="slider-container"></div>
              <hr />
              <details id="about-reader-advanced-layout">
                <summary class="accordion-header">
                  <h2
                    data-l10n-id="about-reader-advanced-layout-header"
                    id="about-reader-advanced-layout-header"
                  ></h2>
                  <span class="chevron-icon"></span>
                </summary>
                <div
                  id="character-spacing-slider"
                  class="slider-container"
                ></div>
                <div id="word-spacing-slider" class="slider-container"></div>
                <label
                  for="text-alignment-buttons"
                  data-l10n-id="about-reader-text-alignment-label"
                ></label>
                <div
                  class="text-alignment-buttons radiorow"
                  id="text-alignment-buttons"
                ></div>
                <button
                  class="text-layout-reset-button reset-button"
                  data-l10n-id="about-reader-reset-button"
                ></button>
              </details>
            </li>
          </ul>
          <ul class="dropdown style-dropdown" id="regular-text-menu">
            <li>
              <button
                class="dropdown-toggle toolbar-button style-button"
                aria-labelledby="toolbar-type-controls"
                data-telemetry-id="reader-type-controls"
              >
                <span
                  class="hover-label"
                  id="toolbar-type-controls"
                  data-l10n-id="about-reader-toolbar-type-controls"
                ></span>
              </button>
            </li>
            <li class="dropdown-popup">
              <div class="font-type-buttons radiorow"></div>
              <div class="font-size-buttons buttonrow">
                <button
                  class="minus-button"
                  data-l10n-id="about-reader-toolbar-minus"
                ></button>
                <span class="font-size-value"></span>
                <button
                  class="plus-button"
                  data-l10n-id="about-reader-toolbar-plus"
                ></button>
              </div>
              <div class="content-width-buttons buttonrow">
                <button
                  class="content-width-minus-button"
                  data-l10n-id="about-reader-toolbar-contentwidthminus"
                ></button>
                <span class="content-width-value"></span>
                <button
                  class="content-width-plus-button"
                  data-l10n-id="about-reader-toolbar-contentwidthplus"
                ></button>
              </div>
              <div class="line-height-buttons buttonrow">
                <button
                  class="line-height-minus-button"
                  data-l10n-id="about-reader-toolbar-lineheightminus"
                ></button>
                <span class="line-height-value"></span>
                <button
                  class="line-height-plus-button"
                  data-l10n-id="about-reader-toolbar-lineheightplus"
                ></button>
              </div>
              <div
                class="color-scheme-buttons radiorow"
                id="regular-color-scheme"
                hidden="false"
              ></div>
            </li>
          </ul>
          <ul
            class="dropdown colors-dropdown"
            id="custom-colors-color-scheme"
            hidden="true"
          >
            <li>
              <button
                class="dropdown-toggle toolbar-button colors-button"
                aria-labelledby="toolbar-color-controls"
                data-telemetry-id="reader-color-controls"
              >
                <span
                  class="hover-label"
                  id="toolbar-color-controls"
                  data-l10n-id="about-reader-toolbar-theme-controls"
                ></span>
              </button>
            </li>
            <li class="dropdown-popup" id="color-controls">
              <h2
                data-l10n-id="about-reader-colors-menu-header"
                id="about-reader-colors-menu-header"
              ></h2>
              <button-group aria-labelledby="about-reader-colors-menu-header">
                <button
                  is="named-deck-button"
                  deck="tabs-deck"
                  name="fxtheme"
                  data-l10n-id="about-reader-fxtheme-tab"
                  data-telemetry-id="colors-menu-default-tab"
                ></button>
                <button
                  is="named-deck-button"
                  deck="tabs-deck"
                  name="customtheme"
                  data-l10n-id="about-reader-customtheme-tab"
                  data-telemetry-id="colors-menu-custom-tab"
                ></button>
              </button-group>
              <named-deck id="tabs-deck" is-tabbed>
                <div
                  name="fxtheme"
                  class="colors-menu-color-scheme-buttons radiorow"
                ></div>
                <div name="customtheme">
                  <ul class="custom-colors-selection"></ul>
                  <button
                    class="custom-colors-reset-button reset-button"
                    data-l10n-id="about-reader-reset-button"
                  ></button>
                </div>
              </named-deck>
            </li>
          </ul>
        </div>
      </div>
    </div>

    <div class="container">
      <div class="header reader-header">
        <a class="domain reader-domain"></a>
        <div class="domain-border"></div>
        <h1 class="reader-title"></h1>
        <div class="credits reader-credits"></div>
        <div class="meta-data">
          <div class="reader-estimated-time"></div>
        </div>
      </div>

      <hr />

      <div class="content">
        <div class="moz-reader-content"></div>
      </div>

      <div>
        <div class="reader-message"></div>
      </div>
      <div aria-owns="toolbar"></div>
    </div>
  </body>
</html>
PK
!<x){VV4chrome/toolkit/content/global/reader/color-input.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

.color-input-container {
  display: flex;
  position: relative;
  justify-content: flex-start;
  align-items: center;
  gap: var(--space-small);
  min-height: 46px;
  border: 1px solid rgba(0, 0, 0, 0.2);
  padding: 0 var(--space-small);
  border-radius: var(--border-radius-small);
}

.color-input-container:hover {
  background-color: var(--toolbar-button-background-hover);
}

#color-swatch:focus-visible {
  outline: none;
}

.color-input-container:focus-within {
  outline: 2px solid var(--primary-color);
  outline-offset: var(--focus-outline-offset);
}

.icon-container {
  display: flex;
  margin-inline: auto var(--space-xsmall);
}

#color-swatch {
  appearance: none;
  width: 34px;
  height: 34px;
  background-color: transparent;
  border: none;
  cursor: pointer;
}

#color-swatch::-moz-color-swatch {
  border-radius: var(--border-radius-circle);
  border: 1px solid rgba(0, 0, 0, 0.25);
}
PK
!</�P4chrome/toolkit/content/global/reader/color-input.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { html } from "chrome://global/content/vendor/lit.all.mjs";
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";

/**
 * @tagname color-input
 * @property {string} color - The initial color value as a hex code.
 * @property {string} propName - The property that the color input sets.
 * @property {string} l10nId - l10nId for label text.
 */
export default class ColorInput extends MozLitElement {
  static properties = {
    color: { type: String },
    propName: { type: String, attribute: "prop-name" },
    l10nId: { type: String, attribute: "data-l10n-id" },
  };

  static queries = {
    inputEl: "#color-swatch",
  };

  handleColorInput(event) {
    this.color = event.target.value;
    this.dispatchEvent(
      new CustomEvent("color-picked", {
        detail: this.color,
      })
    );
  }

  /* Function to launch color picker when the user clicks anywhere in the container. */
  handleClick(event) {
    // If the user directly clicks the color swatch, no need to propagate click.
    if (event.target.matches("input")) {
      return;
    }
    this.inputEl.click();
  }

  render() {
    return html`
      <link
        rel="stylesheet"
        href="chrome://global/content/reader/color-input.css"
      />
      <div class="color-input-container" @click="${this.handleClick}">
        <input
          type="color"
          name="${this.propName}"
          .value="${this.color}"
          id="color-swatch"
          @input="${this.handleColorInput}"
        />
        <label for="color-swatch" data-l10n-id=${this.l10nId}></label>
        <div class="icon-container">
          <img
            class="icon"
            role="presentation"
            src="chrome://global/skin/icons/edit-outline.svg"
          />
        </div>
      </div>
    `;
  }
}
customElements.define("color-input", ColorInput);
PK
!<��n-��3chrome/toolkit/content/global/reader/moz-slider.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

.container {
  display: grid;
  grid-template-areas: "title title title "
                       ".     icon  slider";
  grid-template-columns: 0 min-content 1fr;
  align-items: center;
  gap: var(--space-large);
}

.slider-label {
  grid-area: title;
}

.icon-container {
  grid-area: icon;
  align-self: flex-start;

  > .icon {
    -moz-context-properties: fill;
    fill: var(--popup-foreground);
    height: 20px;
    width: 20px;
  }
}

.slider-container {
  grid-area: slider;
}

input {
  margin: 0;
  width: 100%;
}

datalist {
  display: flex;
  justify-content: space-between;
  white-space: nowrap;
  font-size: var(--font-size-small);

  &.no-tick-labels {
    display: none;
  }
}
PK
!<QQKK3chrome/toolkit/content/global/reader/moz-slider.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { html } from "chrome://global/content/vendor/lit.all.mjs";
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";

/**
 * A range slider component that can be used to select a value.
 *
 * @tagname moz-slider
 * @property {number} min - The minimum value of the slider.
 * @property {number} max - The maximum value of the slider.
 * @property {number} value - The initial value of the slider.
 * @property {number} ticks - The number of tick marks to display under the slider.
 * @property {Array<string>} tickLabels - A list containing the tick label strings.
 * @property {string} label - The label text for the slider.
 * @property {string} sliderIcon  - The url of the slider icon.
 */

export default class MozSlider extends MozLitElement {
  static properties = {
    min: { type: Number },
    max: { type: Number },
    value: { type: Number },
    ticks: { type: Number },
    tickLabels: { type: Array, attribute: "tick-labels" },
    label: { type: String },
    sliderIcon: { type: String, attribute: "slider-icon" },
  };

  static get queries() {
    return {
      tickMarks: "datalist",
      sliderTrack: "#inputSlider",
    };
  }

  constructor() {
    super();
    this.ticks = 0;
    this.tickLabels = [];
    this.sliderIcon = "";
  }

  getStepSize() {
    const stepSize = (this.max - this.min) / (this.ticks - 1);
    return stepSize;
  }

  setupIcon() {
    if (this.sliderIcon) {
      return html`<div class="icon-container">
        <img class="icon" role="presentation" src=${this.sliderIcon} />
      </div> `;
    }
    return "";
  }

  ticksTemplate() {
    if (this.ticks > 0) {
      let tickList = [];
      let value = this.min;
      let stepSize = this.getStepSize();
      let className = "";
      for (let i = 0; i < this.ticks; i++) {
        let optionId = "";
        let label = "";
        if (this.tickLabels.length) {
          if (i == 0) {
            optionId = "inline-start-label";
            label = this.tickLabels[0];
          } else if (i == this.ticks - 1) {
            optionId = "inline-end-label";
            label = this.tickLabels[1];
          }
        } else {
          className = "no-tick-labels";
        }
        tickList.push(
          html`<option
            id=${optionId}
            value=${parseFloat(value).toFixed(2)}
            label=${label}
          ></option>`
        );
        value += stepSize;
      }
      return html`
        <datalist id="slider-ticks" class=${className}>${tickList}</datalist>
      `;
    }
    return "";
  }

  handleChange(event) {
    this.value = event.target.value;
    this.dispatchEvent(
      new CustomEvent("slider-changed", {
        detail: this.value,
      })
    );
  }

  render() {
    return html`
      <link
        rel="stylesheet"
        href="chrome://global/content/reader/moz-slider.css"
      />
      <div class="container">
        <label class="slider-label" for="inputSlider">${this.label}</label>
        ${this.setupIcon()}
        <div class="slider-container">
          <input
            id="inputSlider"
            max=${this.max}
            min=${this.min}
            step=${this.getStepSize()}
            type="range"
            .value=${this.value}
            list="slider-ticks"
            @change=${this.handleChange}
          />
          ${this.ticksTemplate()}
        </div>
      </div>
    `;
  }
}
customElements.define("moz-slider", MozSlider);
PK
!<@��###.chrome/toolkit/content/global/resetProfile.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

#resetProfileProgressDialog {
  padding: 10px;
}

#infoTitle {
  font-weight: 600;
}
PK
!<�G4u		-chrome/toolkit/content/global/resetProfile.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

document.addEventListener("dialogaccept", onResetProfileAccepted);
document
  .getElementById("refreshProfileLearnMore")
  .addEventListener("click", e => {
    e.preventDefault();
    let retVals = window.arguments[0];
    retVals.learnMore = true;
    window.close();
  });

document.addEventListener("DOMContentLoaded", function () {
  document
    .getElementById("resetProfileDialog")
    .getButton("accept")
    .classList.add("danger-button");
});

function onResetProfileAccepted() {
  let retVals = window.arguments[0];
  retVals.reset = true;
}
PK
!<.f�Z��0chrome/toolkit/content/global/resetProfile.xhtml<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<window
  id="resetProfileDialogWindow"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
  xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  aria-describedby="infoBody"
>
  <dialog
    id="resetProfileDialog"
    buttons="accept,cancel"
    defaultButton="accept"
    buttonidaccept="refresh-profile-dialog-button"
  >
    <!-- The empty onload event handler is a hack to get the accept button text applied by Fluent. -->

    <linkset>
      <html:link rel="stylesheet" href="chrome://global/skin/global.css" />
      <html:link
        rel="stylesheet"
        href="chrome://global/content/commonDialog.css"
      />
      <html:link
        rel="stylesheet"
        href="chrome://global/skin/commonDialog.css"
      />
      <html:link
        rel="stylesheet"
        href="chrome://global/content/resetProfile.css"
      />

      <html:link rel="localization" href="branding/brand.ftl" />
      <html:link rel="localization" href="toolkit/global/resetProfile.ftl" />
    </linkset>

    <div xmlns="http://www.w3.org/1999/xhtml" id="dialogGrid">
      <div class="dialogRow" id="infoRow">
        <div id="iconContainer">
          <xul:image id="infoIcon" />
        </div>
        <div id="infoContainer">
          <xul:description
            id="infoTitle"
            data-l10n-id="refresh-profile-dialog-title"
          />
          <xul:description
            id="infoBody"
            context="contentAreaContextMenu"
            noinitialfocus="true"
            data-l10n-id="refresh-profile-dialog-description"
          />
          <xul:description id="learnMoreDescription"
            ><a
              id="refreshProfileLearnMore"
              data-l10n-id="refresh-profile-learn-more"
              href=""
            ></a
          ></xul:description>
        </div>
      </div>
    </div>

    <script src="chrome://global/content/resetProfile.js" />
  </dialog>
</window>
PK
!<���<��8chrome/toolkit/content/global/resetProfileProgress.xhtml<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window>

<window
  id="resetProfileProgressDialog"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
  data-l10n-id="refresh-profile-progress"
  style="min-width: 30em"
>
  <vbox>
    <linkset>
      <html:link
        rel="stylesheet"
        href="chrome://global/content/resetProfile.css"
      />
      <html:link rel="stylesheet" href="chrome://global/skin/global.css" />

      <html:link rel="localization" href="branding/brand.ftl" />
      <html:link rel="localization" href="toolkit/global/resetProfile.ftl" />
    </linkset>

    <description
      data-l10n-id="refresh-profile-progress-description"
    ></description>
    <html:progress />
  </vbox>
</window>
PK
!<�r�o##.chrome/toolkit/content/global/selectDialog.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:root {
  min-width: 29em;
}

#selectContainer {
  max-width: 45em;
  margin: 5px;
}
PK
!<�3e���-chrome/toolkit/content/global/selectDialog.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// Defined in dialog.xml.
/* globals centerWindowOnScreen:false, moveToAlertPosition:false */

var propBag, listBox, args;

function onDCL() {
  propBag = window.arguments[0]
    .QueryInterface(Ci.nsIWritablePropertyBag2)
    .QueryInterface(Ci.nsIWritablePropertyBag);

  // Convert to a JS object
  let args = {};
  for (let prop of propBag.enumerator) {
    args[prop.name] = prop.value;
  }

  let promptType = propBag.getProperty("promptType");
  if (promptType != "select") {
    console.error("selectDialog opened for unknown type: ", promptType);
    window.close();
  }

  // Default to canceled.
  propBag.setProperty("ok", false);

  document.title = propBag.getProperty("title");

  let text = propBag.getProperty("text");
  let element = document.getElementById("info.txt");
  element.appendChild(document.createTextNode(text));

  let items = propBag.getProperty("list");
  listBox = document.getElementById("list");

  for (let i = 0; i < items.length; i++) {
    let str = items[i];
    if (str == "") {
      str = "<>";
    }
    listBox.appendItem(str);
    listBox.getItemAtIndex(i).addEventListener("dblclick", dialogDoubleClick);
  }
  listBox.selectedIndex = 0;
}

function onLoad() {
  listBox.focus();

  document.addEventListener("dialogaccept", dialogOK);
  // resize the window to the content
  window.sizeToContent();

  // Move to the right location
  moveToAlertPosition();
  centerWindowOnScreen();

  // play sound
  try {
    if (!args.openedWithTabDialog) {
      Cc["@mozilla.org/sound;1"]
        .getService(Ci.nsISound)
        .playEventSound(Ci.nsISound.EVENT_SELECT_DIALOG_OPEN);
    }
  } catch (e) {}

  Services.obs.notifyObservers(window, "select-dialog-loaded");
}

function dialogOK() {
  propBag.setProperty("selected", listBox.selectedIndex);
  propBag.setProperty("ok", true);
}

function dialogDoubleClick() {
  dialogOK();
  window.close();
}

document.addEventListener("DOMContentLoaded", onDCL);
window.addEventListener("load", onLoad, { once: true });
PK
!<�xx0chrome/toolkit/content/global/selectDialog.xhtml<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window>

<window
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
>
  <linkset>
    <html:link rel="stylesheet" href="chrome://global/skin/global.css" />
    <html:link
      rel="stylesheet"
      href="chrome://global/content/selectDialog.css"
    />
  </linkset>

  <dialog>
    <script src="chrome://global/content/selectDialog.js" />
    <keyset id="dialogKeys" />
    <vbox id="selectContainer">
      <description id="info.txt" />
      <vbox>
        <richlistbox id="list" class="theme-listbox" style="height: 8em" />
      </vbox>
    </vbox>
  </dialog>
</window>
PK
!<��))8chrome/toolkit/content/global/shopping/ProductConfig.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

let { XPCOMUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/XPCOMUtils.sys.mjs"
);

const ANALYSIS_RESPONSE_SCHEMA =
  "chrome://global/content/shopping/analysis_response.schema.json";
const ANALYSIS_REQUEST_SCHEMA =
  "chrome://global/content/shopping/analysis_request.schema.json";

const ANALYZE_RESPONSE_SCHEMA =
  "chrome://global/content/shopping/analyze_response.schema.json";
const ANALYZE_REQUEST_SCHEMA =
  "chrome://global/content/shopping/analyze_request.schema.json";

const ANALYSIS_STATUS_RESPONSE_SCHEMA =
  "chrome://global/content/shopping/analysis_status_response.schema.json";
const ANALYSIS_STATUS_REQUEST_SCHEMA =
  "chrome://global/content/shopping/analysis_status_request.schema.json";

const RECOMMENDATIONS_RESPONSE_SCHEMA =
  "chrome://global/content/shopping/recommendations_response.schema.json";
const RECOMMENDATIONS_REQUEST_SCHEMA =
  "chrome://global/content/shopping/recommendations_request.schema.json";

const ATTRIBUTION_RESPONSE_SCHEMA =
  "chrome://global/content/shopping/attribution_response.schema.json";
const ATTRIBUTION_REQUEST_SCHEMA =
  "chrome://global/content/shopping/attribution_request.schema.json";

const REPORTING_RESPONSE_SCHEMA =
  "chrome://global/content/shopping/reporting_response.schema.json";
const REPORTING_REQUEST_SCHEMA =
  "chrome://global/content/shopping/reporting_request.schema.json";

// Allow switching to the stage or test environments by changing the
// "toolkit.shopping.environment" pref from "prod" to "stage" or "test".
const lazy = {};
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "env",
  "toolkit.shopping.environment",
  "prod",
  null,
  // If the pref is set to an unexpected string value, "prod" will be used.
  prefValue =>
    ["prod", "stage", "test"].includes(prefValue) ? prefValue : "prod"
);

// prettier-ignore
const Environments = {
  prod: {
    ANALYSIS_API:        "https://trustwerty.com/api/v2/fx/analysis",
    ANALYSIS_STATUS_API: "https://trustwerty.com/api/v1/fx/analysis_status",
    ANALYZE_API:         "https://trustwerty.com/api/v1/fx/analyze",
    ATTRIBUTION_API:     "https://pe.fakespot.com/api/v1/fx/events",
    RECOMMENDATIONS_API: "https://a.fakespot.com/v2/fx/sp_search",
    REPORTING_API:       "https://trustwerty.com/api/v1/fx/report",
  },
  stage: {
    ANALYSIS_API:        "https://staging.trustwerty.com/api/v2/fx/analysis",
    ANALYSIS_STATUS_API: "https://staging.trustwerty.com/api/v1/fx/analysis_status",
    ANALYZE_API:         "https://staging.trustwerty.com/api/v1/fx/analyze",
    ATTRIBUTION_API:     "https://staging-partner-ads.fakespot.io/api/v1/fx/events",
    RECOMMENDATIONS_API: "https://staging-affiliates.fakespot.io/v2/fx/sp_search",
    REPORTING_API:       "https://staging.trustwerty.com/api/v1/fx/report",
  },
  test: {
    ANALYSIS_API:        "https://example.com/browser/toolkit/components/shopping/test/browser/analysis.sjs",
    ANALYSIS_STATUS_API: "https://example.com/browser/toolkit/components/shopping/test/browser/analysis_status.sjs",
    ANALYZE_API:         "https://example.com/browser/toolkit/components/shopping/test/browser/analyze.sjs",
    ATTRIBUTION_API:     "https://example.com/browser/toolkit/components/shopping/test/browser/attribution.sjs",
    RECOMMENDATIONS_API: "https://example.com/browser/toolkit/components/shopping/test/browser/recommendations.sjs",
    REPORTING_API:       "https://example.com/browser/toolkit/components/shopping/test/browser/reporting.sjs",
  },
};

const ShoppingEnvironment = {
  get ANALYSIS_API() {
    return Environments[lazy.env].ANALYSIS_API;
  },
  get ANALYSIS_STATUS_API() {
    return Environments[lazy.env].ANALYSIS_STATUS_API;
  },
  get ANALYZE_API() {
    return Environments[lazy.env].ANALYZE_API;
  },
  get ATTRIBUTION_API() {
    return Environments[lazy.env].ATTRIBUTION_API;
  },
  get RECOMMENDATIONS_API() {
    return Environments[lazy.env].RECOMMENDATIONS_API;
  },
  get REPORTING_API() {
    return Environments[lazy.env].REPORTING_API;
  },
};

const ProductConfig = {
  amazon: {
    productIdFromURLRegex:
      /(?:[\/]|$|%2F)(?<productId>[A-Z0-9]{10})(?:[\/]|$|\#|\?|%2F)/,
    validTLDs: ["com", "de", "fr"],
  },
  walmart: {
    productIdFromURLRegex:
      /\/ip\/(?:[A-Za-z0-9-]{1,320}\/)?(?<productId>[0-9]{3,13})/,
    validTLDs: ["com"],
  },
  bestbuy: {
    productIdFromURLRegex: /\/(?<productId>\d+\.p)/,
    validTLDs: ["com"],
  },
};

if (lazy.env == "test") {
  // Also allow example.com to allow for testing.
  ProductConfig.example = ProductConfig.amazon;
}

export {
  ANALYSIS_RESPONSE_SCHEMA,
  ANALYSIS_REQUEST_SCHEMA,
  ANALYZE_RESPONSE_SCHEMA,
  ANALYZE_REQUEST_SCHEMA,
  ANALYSIS_STATUS_RESPONSE_SCHEMA,
  ANALYSIS_STATUS_REQUEST_SCHEMA,
  RECOMMENDATIONS_RESPONSE_SCHEMA,
  RECOMMENDATIONS_REQUEST_SCHEMA,
  ATTRIBUTION_RESPONSE_SCHEMA,
  ATTRIBUTION_REQUEST_SCHEMA,
  REPORTING_RESPONSE_SCHEMA,
  REPORTING_REQUEST_SCHEMA,
  ProductConfig,
  ShoppingEnvironment,
};
PK
!<	�LD22?chrome/toolkit/content/global/shopping/ProductValidator.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { JsonSchema } from "resource://gre/modules/JsonSchema.sys.mjs";

let schemas = {};

/**
 * Validate JSON result from the shopping API.
 *
 * @param {object} json
 *  JSON object from the API request.
 * @param {string} SchemaURL
 *  URL string for the schema to validate with.
 * @param {boolean} logErrors
 *  Should invalid JSON log out the errors.
 * @returns {boolean} result
 *  If the JSON is valid or not.
 */
async function validate(json, SchemaURL, logErrors) {
  if (!schemas[SchemaURL]) {
    schemas[SchemaURL] = await fetch(SchemaURL).then(rsp => rsp.json());
  }

  let result = JsonSchema.validate(json, schemas[SchemaURL]);
  let { errors } = result;
  if (!result.valid && logErrors) {
    console.error(`Invalid result: ${JSON.stringify(errors, undefined, 2)}`);
  }
  return result.valid;
}

export const ProductValidator = {
  validate,
};
PK
!<��C�mSmS:chrome/toolkit/content/global/shopping/ShoppingProduct.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import {
  ANALYSIS_RESPONSE_SCHEMA,
  ANALYSIS_REQUEST_SCHEMA,
  ANALYZE_RESPONSE_SCHEMA,
  ANALYZE_REQUEST_SCHEMA,
  ANALYSIS_STATUS_RESPONSE_SCHEMA,
  ANALYSIS_STATUS_REQUEST_SCHEMA,
  RECOMMENDATIONS_RESPONSE_SCHEMA,
  RECOMMENDATIONS_REQUEST_SCHEMA,
  ATTRIBUTION_RESPONSE_SCHEMA,
  ATTRIBUTION_REQUEST_SCHEMA,
  REPORTING_RESPONSE_SCHEMA,
  REPORTING_REQUEST_SCHEMA,
  ProductConfig,
  ShoppingEnvironment,
} from "chrome://global/content/shopping/ProductConfig.mjs";

let { EventEmitter } = ChromeUtils.importESModule(
  "resource://gre/modules/EventEmitter.sys.mjs"
);

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  ObliviousHTTP: "resource://gre/modules/ObliviousHTTP.sys.mjs",
  ProductValidator: "chrome://global/content/shopping/ProductValidator.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

const API_RETRIES = 3;
const API_RETRY_TIMEOUT = 100;
const API_POLL_ATTEMPTS = 260;
const API_POLL_INITIAL_WAIT = 1000;
const API_POLL_WAIT = 1000;

/**
 * @typedef {object} Product
 *  A parsed product for a URL
 * @property {number} id
 *  The product id of the product.
 * @property {string} host
 *  The host of a product url (without www)
 * @property {string} tld
 *  The top level domain of a URL
 * @property {string} sitename
 *  The name of a website (without TLD or subdomains)
 * @property {boolean} valid
 *  If the product is valid or not
 */

/**
 * Class for working with the products shopping API,
 * with helpers for parsing the product from a url
 * and querying the shopping API for information on it.
 *
 * @example
 * let product = new ShoppingProduct(url);
 * if (product.isProduct()) {
 *  let analysis = await product.requestAnalysis();
 *  let recommendations = await product.requestRecommendations();
 * }
 * @example
 * if (!isProductURL(url)) {
 *  return;
 * }
 * let product = new ShoppingProduct(url);
 * let analysis = await product.requestAnalysis();
 * let recommendations = await product.requestRecommendations();
 */
export class ShoppingProduct extends EventEmitter {
  /**
   * Creates a product.
   *
   * @param {URL} url
   *  URL to get the product info from.
   * @param {object} [options]
   * @param {boolean} [options.allowValidationFailure=true]
   *  Should validation failures be allowed or return null
   */
  constructor(url, options = { allowValidationFailure: true }) {
    super();

    this.allowValidationFailure = !!options.allowValidationFailure;

    this._abortController = new AbortController();

    if (url instanceof Ci.nsIURI) {
      url = URL.fromURI(url);
    }

    if (url && URL.isInstance(url)) {
      let product = this.constructor.fromURL(url);
      this.assignProduct(product);
    }
  }

  /**
   * Gets a Product from a URL.
   *
   * @param {URL} url
   *  URL to find a product in.
   * @returns {Product}
   */
  static fromURL(url) {
    let host, sitename, tld;
    let result = { valid: false };

    if (!url || !URL.isInstance(url)) {
      return result;
    }

    // Lowercase hostname and remove www.
    host = url.hostname.replace(/^www\./i, "");
    result.host = host;

    // Get host TLD
    try {
      tld = Services.eTLD.getPublicSuffixFromHost(host);
    } catch (_) {
      return result;
    }
    if (!tld.length) {
      return result;
    }

    // Remove tld and the preceding period to get the sitename
    sitename = host.slice(0, -(tld.length + 1));

    // Check if sitename is one the API has products for
    let siteConfig = ProductConfig[sitename];
    if (!siteConfig) {
      return result;
    }
    result.sitename = sitename;

    // Check if API has products for this TLD
    if (!siteConfig.validTLDs.includes(tld)) {
      return result;
    }
    result.tld = tld;

    // Try to find a product id from the pathname.
    let found = url.pathname.match(siteConfig.productIdFromURLRegex);
    if (!found?.groups) {
      return result;
    }

    let { productId } = found.groups;
    if (!productId) {
      return result;
    }
    result.id = productId;

    // Mark product as valid and complete.
    result.valid = true;

    return result;
  }

  /**
   * Check if a Product is a valid product.
   *
   * @param {Product} product
   *  Product to check.
   * @returns {boolean}
   */
  static isProduct(product) {
    return !!(
      product &&
      product.valid &&
      product.id &&
      product.host &&
      product.sitename &&
      product.tld
    );
  }

  /**
   * Check if a the instances product is a valid product.
   *
   * @returns {boolean}
   */
  isProduct() {
    return this.constructor.isProduct(this.product);
  }

  /**
   * Assign a product to this instance.
   */
  assignProduct(product) {
    if (this.constructor.isProduct(product)) {
      this.product = product;
    }
  }

  /**
   * Request analysis for a product from the API.
   *
   * @param {Product} product
   *  Product to request for (defaults to the instances product).
   * @param {object} options
   *  Override default API url and schema.
   * @returns {object} result
   *  Parsed JSON API result or null.
   */
  async requestAnalysis(
    product = this.product,
    options = {
      url: ShoppingEnvironment.ANALYSIS_API,
      requestSchema: ANALYSIS_REQUEST_SCHEMA,
      responseSchema: ANALYSIS_RESPONSE_SCHEMA,
    }
  ) {
    if (!product) {
      return null;
    }

    let requestOptions = {
      product_id: product.id,
      website: product.host,
    };

    let { url, requestSchema, responseSchema } = options;
    let { allowValidationFailure } = this;

    let result = await ShoppingProduct.request(url, requestOptions, {
      requestSchema,
      responseSchema,
      allowValidationFailure,
    });

    return result;
  }

  /**
   * Request recommended related products from the API.
   * Currently only provides recommendations for Amazon products,
   * which may be paid ads.
   *
   * @param {Product} product
   *  Product to request for (defaults to the instances product).
   * @param {object} options
   *  Override default API url and schema.
   * @returns {object} result
   *  Parsed JSON API result or null.
   */
  async requestRecommendations(
    product = this.product,
    options = {
      url: ShoppingEnvironment.RECOMMENDATIONS_API,
      requestSchema: RECOMMENDATIONS_REQUEST_SCHEMA,
      responseSchema: RECOMMENDATIONS_RESPONSE_SCHEMA,
    }
  ) {
    if (!product) {
      return null;
    }

    let requestOptions = {
      product_id: product.id,
      website: product.host,
    };
    let { url, requestSchema, responseSchema } = options;
    let { allowValidationFailure } = this;
    let result = await ShoppingProduct.request(url, requestOptions, {
      requestSchema,
      responseSchema,
      allowValidationFailure,
    });

    for (let ad of result) {
      ad.image_blob = await ShoppingProduct.requestImageBlob(ad.image_url, {
        signal: this._abortController.signal,
      });
    }

    return result;
  }

  /**
   * Request method for shopping API.
   *
   * @param {string} apiURL
   *  URL string for the API to request.
   * @param {object} bodyObj
   *  What to send to the API.
   * @param {object} [options]
   *  Options for validation and retries.
   * @param {string} [options.requestSchema]
   *  URL string for the JSON Schema to validated the request.
   * @param {string} [options.responseSchema]
   *  URL string for the JSON Schema to validated the response.
   * @param {int} [options.failCount]
   *  Current number of failures for this request.
   * @param {int} [options.maxRetries=API_RETRIES]
   *  Max number of allowed failures.
   * @param {int} [options.retryTimeout=API_RETRY_TIMEOUT]
   *  Minimum time to wait.
   * @param {AbortSignal} [options.signal]
   *  Signal to check if the request needs to be aborted.
   * @param {boolean} [options.allowValidationFailure=true]
   *  Should validation failures be allowed.
   * @returns {object} result
   *  Parsed JSON API result or null.
   */
  static async request(apiURL, bodyObj = {}, options = {}) {
    let {
      requestSchema,
      responseSchema,
      failCount = 0,
      maxRetries = API_RETRIES,
      retryTimeout = API_RETRY_TIMEOUT,
      signal = new AbortController().signal,
      allowValidationFailure = true,
    } = options;

    if (signal.aborted) {
      return null;
    }

    if (bodyObj && requestSchema) {
      let validRequest = await lazy.ProductValidator.validate(
        bodyObj,
        requestSchema,
        allowValidationFailure
      );
      if (!validRequest) {
        Glean?.shoppingProduct?.invalidRequest.record();
        if (!allowValidationFailure) {
          return null;
        }
      }
    }

    let requestOptions = {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      body: JSON.stringify(bodyObj),
      signal,
      abortCallback() {
        Glean?.shoppingProduct?.requestAborted.record();
      },
    };

    let requestPromise;
    let ohttpRelayURL = Services.prefs.getStringPref(
      "toolkit.shopping.ohttpRelayURL",
      ""
    );
    let ohttpConfigURL = Services.prefs.getStringPref(
      "toolkit.shopping.ohttpConfigURL",
      ""
    );
    if (ohttpRelayURL && ohttpConfigURL) {
      let config = await lazy.ObliviousHTTP.getOHTTPConfig(ohttpConfigURL);
      // In the time it took to fetch the OHTTP config, we might have been
      // aborted...
      if (signal.aborted) {
        Glean?.shoppingProduct?.requestAborted.record();
        return null;
      }
      if (!config) {
        Glean?.shoppingProduct?.invalidOhttpConfig.record();
        console.error(
          new Error(
            "OHTTP was configured for shopping but we couldn't get a valid config."
          )
        );
        return null;
      }
      requestPromise = lazy.ObliviousHTTP.ohttpRequest(
        ohttpRelayURL,
        config,
        apiURL,
        requestOptions
      );
    } else {
      requestPromise = fetch(apiURL, requestOptions);
    }

    let result;
    let responseOk;
    let responseStatus;
    try {
      let response = await requestPromise;
      responseOk = response.ok;
      responseStatus = response.status;
      result = await response.json();

      if (responseOk && responseSchema) {
        let validResponse = await lazy.ProductValidator.validate(
          result,
          responseSchema,
          allowValidationFailure
        );
        if (!validResponse) {
          Glean?.shoppingProduct?.invalidResponse.record();
          if (!allowValidationFailure) {
            return null;
          }
        }
      }
    } catch (error) {
      Glean?.shoppingProduct?.requestError.record();
      console.error(error);
    }

    if (!responseOk && responseStatus < 500) {
      Glean?.shoppingProduct?.requestFailure.record();
    }

    // Retry 500 errors.
    if (!responseOk && responseStatus >= 500) {
      failCount++;

      Glean?.shoppingProduct?.serverFailure.record();

      // Make sure we still want to retry
      if (failCount > maxRetries) {
        Glean?.shoppingProduct?.requestRetriesFailed.record();
        return null;
      }

      Glean?.shoppingProduct?.requestRetried.record();
      // Wait for a back off timeout base on the number of failures.
      let backOff = retryTimeout * Math.pow(2, failCount - 1);

      await new Promise(resolve => lazy.setTimeout(resolve, backOff));

      // Try the request again.
      options.failCount = failCount;
      result = await ShoppingProduct.request(apiURL, bodyObj, options);
    }

    return result;
  }

  /**
   * Requests an image for a recommended product.
   *
   * @param {string} imageUrl
   * @returns {blob} A blob of the image
   */
  static async requestImageBlob(imageUrl, options = {}) {
    let { signal = new AbortController().signal } = options;
    let ohttpRelayURL = Services.prefs.getStringPref(
      "toolkit.shopping.ohttpRelayURL",
      ""
    );
    let ohttpConfigURL = Services.prefs.getStringPref(
      "toolkit.shopping.ohttpConfigURL",
      ""
    );

    let imgRequestPromise;
    if (ohttpRelayURL && ohttpConfigURL) {
      let config = await lazy.ObliviousHTTP.getOHTTPConfig(ohttpConfigURL);
      if (!config) {
        Glean?.shoppingProduct?.invalidOhttpConfig.record();
        console.error(
          new Error(
            "OHTTP was configured for shopping but we couldn't get a valid config."
          )
        );
        return null;
      }

      let imgRequestOptions = {
        signal,
        headers: {
          Accept: "image/jpeg",
          "Content-Type": "image/jpeg",
        },
        abortCallback() {
          Glean?.shoppingProduct?.requestAborted.record();
        },
      };

      imgRequestPromise = lazy.ObliviousHTTP.ohttpRequest(
        ohttpRelayURL,
        config,
        imageUrl,
        imgRequestOptions
      );
    } else {
      imgRequestPromise = fetch(imageUrl);
    }

    let imgResult;
    try {
      let response = await imgRequestPromise;
      imgResult = await response.blob();
    } catch (error) {
      console.error(error);
    }

    return imgResult;
  }

  /**
   * Poll Analysis Status API until an analysis has finished.
   *
   * After an initial wait keep checking the api for results,
   * until we have reached a maximum of tries.
   *
   * Passes all arguments to requestAnalysisCreationStatus().
   *
   * @example
   *  let analysis;
   *  let { status } = await product.pollForAnalysisCompleted();
   *  // Check if analysis has finished
   *  if(status != "pending" && status != "in_progress") {
   *    // Get the new analysis
   *    analysis = await product.requestAnalysis();
   *  }
   *
   * @example
   * // Check the current status
   * let { status } = await product.requestAnalysisCreationStatus();
   * if(status == "pending" && status == "in_progress") {
   *    // Start polling without the initial timeout if the analysis
   *    // is already in progress.
   *    await product.pollForAnalysisCompleted({
   *      pollInitialWait: analysisStatus == "in_progress" ? 0 : undefined,
   *    });
   * }
   * @param {object} options
   *  Override default API url and schema.
   * @returns {object} result
   *  Parsed JSON API result or null.
   */
  async pollForAnalysisCompleted(options) {
    let pollCount = 0;
    let initialWait = options?.pollInitialWait || API_POLL_INITIAL_WAIT;
    let pollTimeout = options?.pollTimeout || API_POLL_WAIT;
    let pollAttempts = options?.pollAttempts || API_POLL_ATTEMPTS;
    let isFinished = false;
    let result;

    while (!isFinished && pollCount < pollAttempts) {
      if (this._abortController.signal.aborted) {
        Glean?.shoppingProduct?.requestAborted.record();
        return null;
      }
      let backOff = pollCount == 0 ? initialWait : pollTimeout;
      if (backOff) {
        await new Promise(resolve => lazy.setTimeout(resolve, backOff));
      }
      try {
        result = await this.requestAnalysisCreationStatus(undefined, options);
        if (result?.progress) {
          this.emit("analysis-progress", result.progress);
        }
        isFinished =
          result &&
          result.status != "pending" &&
          result.status != "in_progress";
      } catch (error) {
        console.error(error);
        return null;
      }
      pollCount++;
    }
    return result;
  }

  /**
   * Request that the API creates an analysis for a product.
   *
   * Once the processing status indicates that analyzing is complete,
   * the new analysis data that can be requested with `requestAnalysis`.
   *
   * If the product is currently being analyzed, this will return a
   * status of "in_progress" and not trigger a reanalyzing the product.
   *
   * @param {Product} product
   *  Product to request for (defaults to the instances product).
   * @param {object} options
   *  Override default API url and schema.
   * @returns {object} result
   *  Parsed JSON API result or null.
   */
  async requestCreateAnalysis(product = this.product, options = {}) {
    let url = options?.url || ShoppingEnvironment.ANALYZE_API;
    let requestSchema = options?.requestSchema || ANALYZE_REQUEST_SCHEMA;
    let responseSchema = options?.responseSchema || ANALYZE_RESPONSE_SCHEMA;
    let signal = options?.signal || this._abortController.signal;
    let allowValidationFailure = this.allowValidationFailure;

    if (!product) {
      return null;
    }

    let requestOptions = {
      product_id: product.id,
      website: product.host,
    };

    let result = await ShoppingProduct.request(url, requestOptions, {
      requestSchema,
      responseSchema,
      signal,
      allowValidationFailure,
    });

    return result;
  }

  /**
   * Check the status of creating an analysis for a product.
   *
   * API returns a progress of 0-100 complete and the processing status.
   *
   * @param {Product} product
   *  Product to request for (defaults to the instances product).
   * @param {object} options
   *  Override default API url and schema.
   * @returns {object} result
   *  Parsed JSON API result or null.
   */
  async requestAnalysisCreationStatus(product = this.product, options = {}) {
    let url = options?.url || ShoppingEnvironment.ANALYSIS_STATUS_API;
    let requestSchema =
      options?.requestSchema || ANALYSIS_STATUS_REQUEST_SCHEMA;
    let responseSchema =
      options?.responseSchema || ANALYSIS_STATUS_RESPONSE_SCHEMA;
    let signal = options?.signal || this._abortController.signal;
    let allowValidationFailure = this.allowValidationFailure;

    if (!product) {
      return null;
    }

    let requestOptions = {
      product_id: product.id,
      website: product.host,
    };

    let result = await ShoppingProduct.request(url, requestOptions, {
      requestSchema,
      responseSchema,
      signal,
      allowValidationFailure,
    });

    return result;
  }

  /**
   * Send an event to the Ad Attribution API
   *
   * @param {string} eventName
   *  Event name options are:
   *  - "impression"
   *  - "click"
   * @param {string} aid
   *  The aid (Ad ID) from the recommendation.
   * @param {string} [source]
   *  Source of the event
   * @param {object} [options]
   *  Override default API url and schema.
   * @returns {object} result
   *  Parsed JSON API result or null.
   */
  static async sendAttributionEvent(
    eventName,
    aid,
    source = "firefox_sidebar",
    options = {}
  ) {
    let {
      url = ShoppingEnvironment.ATTRIBUTION_API,
      requestSchema = ATTRIBUTION_REQUEST_SCHEMA,
      responseSchema = ATTRIBUTION_RESPONSE_SCHEMA,
      signal = new AbortController().signal,
      allowValidationFailure = true,
    } = options;

    if (!eventName) {
      throw new Error("An event name is required.");
    }
    if (!aid) {
      throw new Error("An Ad ID is required.");
    }

    let requestBody = {
      event_source: source,
    };

    switch (eventName) {
      case "impression":
        requestBody.event_name = "trusted_deals_impression";
        requestBody.aidvs = [aid];
        break;
      case "click":
        requestBody.event_name = "trusted_deals_link_clicked";
        requestBody.aid = aid;
        break;
      case "placement":
        requestBody.event_name = "trusted_deals_placement";
        requestBody.aidvs = [aid];
        break;
      default:
        throw new Error(`"${eventName}" is not a valid event name`);
    }

    let result = await ShoppingProduct.request(url, requestBody, {
      requestSchema,
      responseSchema,
      signal,
      allowValidationFailure,
    });

    return result;
  }

  /**
   * Send a report that a product is back in stock.
   *
   * @param {Product} product
   *  Product to request for (defaults to the instances product).
   * @param {object} options
   *  Override default API url and schema.
   * @returns {object} result
   *  Parsed JSON API result or null.
   */
  async sendReport(product = this.product, options = {}) {
    if (!product) {
      return null;
    }

    let url = options?.url || ShoppingEnvironment.REPORTING_API;
    let requestSchema = options?.requestSchema || REPORTING_REQUEST_SCHEMA;
    let responseSchema = options?.responseSchema || REPORTING_RESPONSE_SCHEMA;
    let signal = options?.signal || this._abortController.signal;
    let allowValidationFailure = this.allowValidationFailure;

    let requestOptions = {
      product_id: product.id,
      website: product.host,
    };

    let result = await ShoppingProduct.request(url, requestOptions, {
      requestSchema,
      responseSchema,
      signal,
      allowValidationFailure,
    });

    return result;
  }

  uninit() {
    this._abortController.abort();
    this.product = null;
  }
}

/**
 * Check if a URL is a valid product.
 *
 * @param {URL | nsIURI } url
 *  URL to check.
 * @returns {boolean}
 */
export function isProductURL(url) {
  if (url instanceof Ci.nsIURI) {
    url = URL.fromURI(url);
  }
  if (!URL.isInstance(url)) {
    return false;
  }
  let productInfo = ShoppingProduct.fromURL(url);
  return ShoppingProduct.isProduct(productInfo);
}
PK
!<�4<�DDCchrome/toolkit/content/global/shopping/analysis_request.schema.json{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "chrome://global/content/shopping/analysis_request.schema.json",
  "title": "Product request",
  "type": "object",
  "properties": {
    "product_id": {
      "description": "Product identifier (ASIN / SKU).",
      "type": "string",
      "examples": ["B07W59LRL9", "5200904.p", "1752657021"]
    },
    "website": {
      "description": "Product websites.",
      "type": "string",
      "examples": ["amazon.com", "amazon.ca", "bestbuy.com", "walmart.com"]
    }
  },
  "required": ["product_id", "website"]
}
PK
!<�� Dchrome/toolkit/content/global/shopping/analysis_response.schema.json{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "chrome://global/content/shopping/analysis_response.schema.json",
  "title": "Product",
  "description": "A product analysis result",
  "type": "object",
  "properties": {
    "product_id": {
      "description": "Product identifier (ASIN / SKU).",
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "examples": ["B07W59LRL9", "5200904.p", "1752657021"]
    },
    "grade": {
      "description": "Reliability grade for the product's reviews.",
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "examples": ["A", "B", "C", "D", "F"]
    },
    "adjusted_rating": {
      "description": "Product rating adjusted to exclude untrusted reviews.",
      "anyOf": [
        {
          "type": "number"
        },
        {
          "type": "null"
        }
      ],
      "examples": [4.7, null]
    },
    "needs_analysis": {
      "description": "Boolean indicating if the analysis is stale.",
      "type": "boolean"
    },
    "page_not_supported": {
      "description": "Boolean indicating if current product page is supported or not.",
      "type": "boolean"
    },
    "not_enough_reviews": {
      "description": "Boolean indicating if the product has enough reviews to analyze.",
      "type": "boolean"
    },
    "last_analysis_time": {
      "description": "Integer indicating last analysis time since 1970-01-01 00:00:00 +0000",
      "type": "number"
    },
    "deleted_product": {
      "description": "Boolean indicating if product is marked as deleted by website in Fakespot database",
      "type": "boolean"
    },
    "deleted_product_reported": {
      "description": "Boolean indicating if product marked as deleted has already been reported as in stock by a user",
      "type": "boolean"
    },
    "highlights": {
      "description": "Object containing highlights for Amazon product.",
      "type": "object",
      "properties": {
        "quality": {
          "description": "Highlights related to quality.",
          "type": "object",
          "$ref": "#/$defs/highlights"
        },
        "price": {
          "description": "Highlights related to price.",
          "type": "object",
          "$ref": "#/$defs/highlights"
        },
        "shipping": {
          "description": "Highlights related to shipping.",
          "type": "object",
          "$ref": "#/$defs/highlights"
        },
        "packaging/appearance": {
          "description": "Highlights related to packaging or appearance.",
          "type": "object",
          "$ref": "#/$defs/highlights"
        },
        "competitiveness": {
          "description": "Highlights related to competitiveness.",
          "type": "object",
          "$ref": "#/$defs/highlights"
        }
      }
    }
  },
  "$defs": {
    "highlights": {
      "description": "Possibly empty array of highlights.",
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  }
}
PK
!<����SSJchrome/toolkit/content/global/shopping/analysis_status_request.schema.json{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "chrome://global/content/shopping/analysis_status_request.schema.json",
  "title": "Analysis Status request",
  "type": "object",
  "properties": {
    "product_id": {
      "description": "Product identifier (ASIN / SKU).",
      "type": "string",
      "examples": ["B07W59LRL9", "5200904.p", "1752657021"]
    },
    "website": {
      "description": "Product websites.",
      "type": "string",
      "examples": ["amazon.com", "amazon.ca", "bestbuy.com", "walmart.com"]
    }
  },
  "required": ["product_id", "website"]
}
PK
!<� ��Kchrome/toolkit/content/global/shopping/analysis_status_response.schema.json{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "chrome://global/content/shopping/analysis_status_response.schema.json",
  "title": "Analysis Status response",
  "type": "object",
  "properties": {
    "status": {
      "description": "Current Analysis status",
      "type": "string",
      "examples": [
        "not_found",
        "pending",
        "in_progress",
        "completed",
        "not_analyzable",
        "unprocessable",
        "page_not_supported",
        "not_enough_reviews",
        "stale"
      ]
    },
    "progress": {
      "description": "Current Analysis progress 0.0..100.0",
      "type": "number",
      "examples": [10.0]
    }
  },
  "required": ["status", "progress"]
}
PK
!<���yCCBchrome/toolkit/content/global/shopping/analyze_request.schema.json{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "chrome://global/content/shopping/analyze_request.schema.json",
  "title": "Analyze request",
  "type": "object",
  "properties": {
    "product_id": {
      "description": "Product identifier (ASIN / SKU).",
      "type": "string",
      "examples": ["B07W59LRL9", "5200904.p", "1752657021"]
    },
    "website": {
      "description": "Product websites.",
      "type": "string",
      "examples": ["amazon.com", "amazon.ca", "bestbuy.com", "walmart.com"]
    }
  },
  "required": ["product_id", "website"]
}
PK
!<�.�Cchrome/toolkit/content/global/shopping/analyze_response.schema.json{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "chrome://global/content/shopping/analyze_response.schema.json",
  "title": "Analyze response",
  "type": "object",
  "properties": {
    "status": {
      "description": "Current Analysis status",
      "type": "string",
      "examples": [
        "pending",
        "in_progress",
        "completed",
        "not_analyzable",
        "unprocessable",
        "page_not_supported",
        "not_enough_reviews"
      ]
    }
  },
  "required": ["status"]
}
PK
!<4��Fchrome/toolkit/content/global/shopping/attribution_request.schema.json{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "chrome://global/content/shopping/events_request.schema.json",
  "title": "Ad events request",
  "type": "object",
  "properties": {
    "event_name": {
      "description": "Event name string",
      "type": "string",
      "examples": [
        "trusted_deals_impression",
        "trusted_deals_link_clicked",
        "trusted_deals_placement"
      ]
    },
    "event_source": {
      "description": "Where event was dispatched from",
      "type": "string",
      "examples": ["firefox_sidebar"]
    },
    "aidvs": {
      "description": "Ad identifiers for impression",
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "aid": {
      "description": "Ad identifier for clicks",
      "type": "string"
    },
    "properties": {
      "description": "Extra properties",
      "type": "object"
    }
  },
  "required": ["event_name", "event_source"]
}
PK
!<(�A�@@Gchrome/toolkit/content/global/shopping/attribution_response.schema.json{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "chrome://global/content/shopping/events_response.schema.json",
  "title": "Ad events response",
  "type": "object",
  "additionalProperties": {
    "anyOf": [
      {
        "type": "string"
      },
      {
        "type": "null"
      }
    ]
  }
}
PK
!<4ΥSSJchrome/toolkit/content/global/shopping/recommendations_request.schema.json{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "chrome://global/content/shopping/recommendations_request.schema.json",
  "title": "Recommendations request",
  "type": "object",
  "properties": {
    "product_id": {
      "description": "Product identifier (ASIN / SKU).",
      "type": "string",
      "examples": ["B07W59LRL9", "5200904.p", "1752657021"]
    },
    "website": {
      "description": "Product websites.",
      "type": "string",
      "examples": ["amazon.com", "amazon.ca", "bestbuy.com", "walmart.com"]
    }
  },
  "required": ["product_id", "website"]
}
PK
!<����Kchrome/toolkit/content/global/shopping/recommendations_response.schema.json{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "chrome://global/content/shopping/recommendations_response.schema.json",
  "title": "Recommendations",
  "description": "Recommendations for a product",
  "type": "array",
  "items": {
    "$ref": "#/$defs/recommendation"
  },
  "$defs": {
    "recommendation": {
      "type": "object",
      "properties": {
        "name": {
          "type": "string"
        },
        "url": {
          "type": "string"
        },
        "image_url": {
          "type": "string"
        },
        "price": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ]
        },
        "currency": {
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "examples": ["USD"]
        },
        "grade": {
          "description": "Reliability grade for the product's reviews.",
          "anyOf": [
            {
              "type": "string"
            },
            {
              "type": "null"
            }
          ],
          "examples": ["A", "B", "C", "D", "F"]
        },
        "adjusted_rating": {
          "type": "number"
        },
        "analysis_url": {
          "type": "string"
        },
        "sponsored": {
          "type": "boolean"
        },
        "aid": {
          "type": "string"
        }
      }
    }
  }
}
PK
!<hL��RRDchrome/toolkit/content/global/shopping/reporting_request.schema.json{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "chrome://global/content/shopping/reporting_request.schema.json",
  "title": "Report back in stock request",
  "type": "object",
  "properties": {
    "product_id": {
      "description": "Product identifier (ASIN / SKU).",
      "type": "string",
      "examples": ["B07W59LRL9", "5200904.p", "1752657021"]
    },
    "website": {
      "description": "Product websites.",
      "type": "string",
      "examples": ["amazon.com", "amazon.ca", "bestbuy.com", "walmart.com"]
    }
  },
  "required": ["product_id", "website"]
}
PK
!<��F��Echrome/toolkit/content/global/shopping/reporting_response.schema.json{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "chrome://global/content/shopping/reporting_request.schema.json",
  "title": "Report back in stock response",
  "type": "object",
  "properties": {
    "message": {
      "description": "String indicating current state",
      "type": "string",
      "examples": ["report created", "already reported", "not deleted"]
    }
  }
}
PK
!<������.chrome/toolkit/content/global/simplifyMode.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* This file defines specific rules for print preview when using simplify mode.
 * Some of these rules (styling for title and author on the header element)
 * already exist in aboutReader.css, however, we decoupled it from the original
 * file so we don't need to load a bunch of extra queries that will not take
 * effect when using the simplify page checkbox. */
:root {
  --font-size: 16px;
  --content-width: 30em;
  --line-height: 1.6em;
}

body {
  padding: 0px;
}

.container {
  max-width: 100%;
  font-family: Georgia, "Times New Roman", serif;
}

.header > h1 {
  font-size: 1.6em;
  line-height: 1.25em;
  margin: 30px 0;
}

.header > .credits {
  font-size: 0.9em;
  line-height: 1.48em;
  margin: 0 0 30px 0;
  font-style: italic;
}
PK
!<�uP((2chrome/toolkit/content/global/third_party/d3/d3.js!function() {
  var d3 = {
    version: "3.5.17"
  };
  var d3_arraySlice = [].slice, d3_array = function(list) {
    return d3_arraySlice.call(list);
  };
  var d3_document = this.document;
  function d3_documentElement(node) {
    return node && (node.ownerDocument || node.document || node).documentElement;
  }
  function d3_window(node) {
    return node && (node.ownerDocument && node.ownerDocument.defaultView || node.document && node || node.defaultView);
  }
  if (d3_document) {
    try {
      d3_array(d3_document.documentElement.childNodes)[0].nodeType;
    } catch (e) {
      d3_array = function(list) {
        var i = list.length, array = new Array(i);
        while (i--) array[i] = list[i];
        return array;
      };
    }
  }
  if (!Date.now) Date.now = function() {
    return +new Date();
  };
  if (d3_document) {
    try {
      d3_document.createElement("DIV").style.setProperty("opacity", 0, "");
    } catch (error) {
      var d3_element_prototype = this.Element.prototype, d3_element_setAttribute = d3_element_prototype.setAttribute, d3_element_setAttributeNS = d3_element_prototype.setAttributeNS, d3_style_prototype = this.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty;
      d3_element_prototype.setAttribute = function(name, value) {
        d3_element_setAttribute.call(this, name, value + "");
      };
      d3_element_prototype.setAttributeNS = function(space, local, value) {
        d3_element_setAttributeNS.call(this, space, local, value + "");
      };
      d3_style_prototype.setProperty = function(name, value, priority) {
        d3_style_setProperty.call(this, name, value + "", priority);
      };
    }
  }
  d3.ascending = d3_ascending;
  function d3_ascending(a, b) {
    return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
  }
  d3.descending = function(a, b) {
    return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN;
  };
  d3.min = function(array, f) {
    var i = -1, n = array.length, a, b;
    if (arguments.length === 1) {
      while (++i < n) if ((b = array[i]) != null && b >= b) {
        a = b;
        break;
      }
      while (++i < n) if ((b = array[i]) != null && a > b) a = b;
    } else {
      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) {
        a = b;
        break;
      }
      while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b;
    }
    return a;
  };
  d3.max = function(array, f) {
    var i = -1, n = array.length, a, b;
    if (arguments.length === 1) {
      while (++i < n) if ((b = array[i]) != null && b >= b) {
        a = b;
        break;
      }
      while (++i < n) if ((b = array[i]) != null && b > a) a = b;
    } else {
      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) {
        a = b;
        break;
      }
      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b;
    }
    return a;
  };
  d3.extent = function(array, f) {
    var i = -1, n = array.length, a, b, c;
    if (arguments.length === 1) {
      while (++i < n) if ((b = array[i]) != null && b >= b) {
        a = c = b;
        break;
      }
      while (++i < n) if ((b = array[i]) != null) {
        if (a > b) a = b;
        if (c < b) c = b;
      }
    } else {
      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) {
        a = c = b;
        break;
      }
      while (++i < n) if ((b = f.call(array, array[i], i)) != null) {
        if (a > b) a = b;
        if (c < b) c = b;
      }
    }
    return [ a, c ];
  };
  function d3_number(x) {
    return x === null ? NaN : +x;
  }
  function d3_numeric(x) {
    return !isNaN(x);
  }
  d3.sum = function(array, f) {
    var s = 0, n = array.length, a, i = -1;
    if (arguments.length === 1) {
      while (++i < n) if (d3_numeric(a = +array[i])) s += a;
    } else {
      while (++i < n) if (d3_numeric(a = +f.call(array, array[i], i))) s += a;
    }
    return s;
  };
  d3.mean = function(array, f) {
    var s = 0, n = array.length, a, i = -1, j = n;
    if (arguments.length === 1) {
      while (++i < n) if (d3_numeric(a = d3_number(array[i]))) s += a; else --j;
    } else {
      while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) s += a; else --j;
    }
    if (j) return s / j;
  };
  d3.quantile = function(values, p) {
    var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h;
    return e ? v + e * (values[h] - v) : v;
  };
  d3.median = function(array, f) {
    var numbers = [], n = array.length, a, i = -1;
    if (arguments.length === 1) {
      while (++i < n) if (d3_numeric(a = d3_number(array[i]))) numbers.push(a);
    } else {
      while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) numbers.push(a);
    }
    if (numbers.length) return d3.quantile(numbers.sort(d3_ascending), .5);
  };
  d3.variance = function(array, f) {
    var n = array.length, m = 0, a, d, s = 0, i = -1, j = 0;
    if (arguments.length === 1) {
      while (++i < n) {
        if (d3_numeric(a = d3_number(array[i]))) {
          d = a - m;
          m += d / ++j;
          s += d * (a - m);
        }
      }
    } else {
      while (++i < n) {
        if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) {
          d = a - m;
          m += d / ++j;
          s += d * (a - m);
        }
      }
    }
    if (j > 1) return s / (j - 1);
  };
  d3.deviation = function() {
    var v = d3.variance.apply(this, arguments);
    return v ? Math.sqrt(v) : v;
  };
  function d3_bisector(compare) {
    return {
      left: function(a, x, lo, hi) {
        if (arguments.length < 3) lo = 0;
        if (arguments.length < 4) hi = a.length;
        while (lo < hi) {
          var mid = lo + hi >>> 1;
          if (compare(a[mid], x) < 0) lo = mid + 1; else hi = mid;
        }
        return lo;
      },
      right: function(a, x, lo, hi) {
        if (arguments.length < 3) lo = 0;
        if (arguments.length < 4) hi = a.length;
        while (lo < hi) {
          var mid = lo + hi >>> 1;
          if (compare(a[mid], x) > 0) hi = mid; else lo = mid + 1;
        }
        return lo;
      }
    };
  }
  var d3_bisect = d3_bisector(d3_ascending);
  d3.bisectLeft = d3_bisect.left;
  d3.bisect = d3.bisectRight = d3_bisect.right;
  d3.bisector = function(f) {
    return d3_bisector(f.length === 1 ? function(d, x) {
      return d3_ascending(f(d), x);
    } : f);
  };
  d3.shuffle = function(array, i0, i1) {
    if ((m = arguments.length) < 3) {
      i1 = array.length;
      if (m < 2) i0 = 0;
    }
    var m = i1 - i0, t, i;
    while (m) {
      i = Math.random() * m-- | 0;
      t = array[m + i0], array[m + i0] = array[i + i0], array[i + i0] = t;
    }
    return array;
  };
  d3.permute = function(array, indexes) {
    var i = indexes.length, permutes = new Array(i);
    while (i--) permutes[i] = array[indexes[i]];
    return permutes;
  };
  d3.pairs = function(array) {
    var i = 0, n = array.length - 1, p0, p1 = array[0], pairs = new Array(n < 0 ? 0 : n);
    while (i < n) pairs[i] = [ p0 = p1, p1 = array[++i] ];
    return pairs;
  };
  d3.transpose = function(matrix) {
    if (!(n = matrix.length)) return [];
    for (var i = -1, m = d3.min(matrix, d3_transposeLength), transpose = new Array(m); ++i < m; ) {
      for (var j = -1, n, row = transpose[i] = new Array(n); ++j < n; ) {
        row[j] = matrix[j][i];
      }
    }
    return transpose;
  };
  function d3_transposeLength(d) {
    return d.length;
  }
  d3.zip = function() {
    return d3.transpose(arguments);
  };
  d3.keys = function(map) {
    var keys = [];
    for (var key in map) keys.push(key);
    return keys;
  };
  d3.values = function(map) {
    var values = [];
    for (var key in map) values.push(map[key]);
    return values;
  };
  d3.entries = function(map) {
    var entries = [];
    for (var key in map) entries.push({
      key: key,
      value: map[key]
    });
    return entries;
  };
  d3.merge = function(arrays) {
    var n = arrays.length, m, i = -1, j = 0, merged, array;
    while (++i < n) j += arrays[i].length;
    merged = new Array(j);
    while (--n >= 0) {
      array = arrays[n];
      m = array.length;
      while (--m >= 0) {
        merged[--j] = array[m];
      }
    }
    return merged;
  };
  var abs = Math.abs;
  d3.range = function(start, stop, step) {
    if (arguments.length < 3) {
      step = 1;
      if (arguments.length < 2) {
        stop = start;
        start = 0;
      }
    }
    if ((stop - start) / step === Infinity) throw new Error("infinite range");
    var range = [], k = d3_range_integerScale(abs(step)), i = -1, j;
    start *= k, stop *= k, step *= k;
    if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k);
    return range;
  };
  function d3_range_integerScale(x) {
    var k = 1;
    while (x * k % 1) k *= 10;
    return k;
  }
  function d3_class(ctor, properties) {
    for (var key in properties) {
      Object.defineProperty(ctor.prototype, key, {
        value: properties[key],
        enumerable: false
      });
    }
  }
  d3.map = function(object, f) {
    var map = new d3_Map();
    if (object instanceof d3_Map) {
      object.forEach(function(key, value) {
        map.set(key, value);
      });
    } else if (Array.isArray(object)) {
      var i = -1, n = object.length, o;
      if (arguments.length === 1) while (++i < n) map.set(i, object[i]); else while (++i < n) map.set(f.call(object, o = object[i], i), o);
    } else {
      for (var key in object) map.set(key, object[key]);
    }
    return map;
  };
  function d3_Map() {
    this._ = Object.create(null);
  }
  var d3_map_proto = "__proto__", d3_map_zero = "\x00";
  d3_class(d3_Map, {
    has: d3_map_has,
    get: function(key) {
      return this._[d3_map_escape(key)];
    },
    set: function(key, value) {
      return this._[d3_map_escape(key)] = value;
    },
    remove: d3_map_remove,
    keys: d3_map_keys,
    values: function() {
      var values = [];
      for (var key in this._) values.push(this._[key]);
      return values;
    },
    entries: function() {
      var entries = [];
      for (var key in this._) entries.push({
        key: d3_map_unescape(key),
        value: this._[key]
      });
      return entries;
    },
    size: d3_map_size,
    empty: d3_map_empty,
    forEach: function(f) {
      for (var key in this._) f.call(this, d3_map_unescape(key), this._[key]);
    }
  });
  function d3_map_escape(key) {
    return (key += "") === d3_map_proto || key[0] === d3_map_zero ? d3_map_zero + key : key;
  }
  function d3_map_unescape(key) {
    return (key += "")[0] === d3_map_zero ? key.slice(1) : key;
  }
  function d3_map_has(key) {
    return d3_map_escape(key) in this._;
  }
  function d3_map_remove(key) {
    return (key = d3_map_escape(key)) in this._ && delete this._[key];
  }
  function d3_map_keys() {
    var keys = [];
    for (var key in this._) keys.push(d3_map_unescape(key));
    return keys;
  }
  function d3_map_size() {
    var size = 0;
    for (var key in this._) ++size;
    return size;
  }
  function d3_map_empty() {
    for (var key in this._) return false;
    return true;
  }
  d3.nest = function() {
    var nest = {}, keys = [], sortKeys = [], sortValues, rollup;
    function map(mapType, array, depth) {
      if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array;
      var i = -1, n = array.length, key = keys[depth++], keyValue, object, setter, valuesByKey = new d3_Map(), values;
      while (++i < n) {
        if (values = valuesByKey.get(keyValue = key(object = array[i]))) {
          values.push(object);
        } else {
          valuesByKey.set(keyValue, [ object ]);
        }
      }
      if (mapType) {
        object = mapType();
        setter = function(keyValue, values) {
          object.set(keyValue, map(mapType, values, depth));
        };
      } else {
        object = {};
        setter = function(keyValue, values) {
          object[keyValue] = map(mapType, values, depth);
        };
      }
      valuesByKey.forEach(setter);
      return object;
    }
    function entries(map, depth) {
      if (depth >= keys.length) return map;
      var array = [], sortKey = sortKeys[depth++];
      map.forEach(function(key, keyMap) {
        array.push({
          key: key,
          values: entries(keyMap, depth)
        });
      });
      return sortKey ? array.sort(function(a, b) {
        return sortKey(a.key, b.key);
      }) : array;
    }
    nest.map = function(array, mapType) {
      return map(mapType, array, 0);
    };
    nest.entries = function(array) {
      return entries(map(d3.map, array, 0), 0);
    };
    nest.key = function(d) {
      keys.push(d);
      return nest;
    };
    nest.sortKeys = function(order) {
      sortKeys[keys.length - 1] = order;
      return nest;
    };
    nest.sortValues = function(order) {
      sortValues = order;
      return nest;
    };
    nest.rollup = function(f) {
      rollup = f;
      return nest;
    };
    return nest;
  };
  d3.set = function(array) {
    var set = new d3_Set();
    if (array) for (var i = 0, n = array.length; i < n; ++i) set.add(array[i]);
    return set;
  };
  function d3_Set() {
    this._ = Object.create(null);
  }
  d3_class(d3_Set, {
    has: d3_map_has,
    add: function(key) {
      this._[d3_map_escape(key += "")] = true;
      return key;
    },
    remove: d3_map_remove,
    values: d3_map_keys,
    size: d3_map_size,
    empty: d3_map_empty,
    forEach: function(f) {
      for (var key in this._) f.call(this, d3_map_unescape(key));
    }
  });
  d3.behavior = {};
  function d3_identity(d) {
    return d;
  }
  d3.rebind = function(target, source) {
    var i = 1, n = arguments.length, method;
    while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]);
    return target;
  };
  function d3_rebind(target, source, method) {
    return function() {
      var value = method.apply(source, arguments);
      return value === source ? target : value;
    };
  }
  function d3_vendorSymbol(object, name) {
    if (name in object) return name;
    name = name.charAt(0).toUpperCase() + name.slice(1);
    for (var i = 0, n = d3_vendorPrefixes.length; i < n; ++i) {
      var prefixName = d3_vendorPrefixes[i] + name;
      if (prefixName in object) return prefixName;
    }
  }
  var d3_vendorPrefixes = [ "webkit", "ms", "moz", "Moz", "o", "O" ];
  function d3_noop() {}
  d3.dispatch = function() {
    var dispatch = new d3_dispatch(), i = -1, n = arguments.length;
    while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
    return dispatch;
  };
  function d3_dispatch() {}
  d3_dispatch.prototype.on = function(type, listener) {
    var i = type.indexOf("."), name = "";
    if (i >= 0) {
      name = type.slice(i + 1);
      type = type.slice(0, i);
    }
    if (type) return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener);
    if (arguments.length === 2) {
      if (listener == null) for (type in this) {
        if (this.hasOwnProperty(type)) this[type].on(name, null);
      }
      return this;
    }
  };
  function d3_dispatch_event(dispatch) {
    var listeners = [], listenerByName = new d3_Map();
    function event() {
      var z = listeners, i = -1, n = z.length, l;
      while (++i < n) if (l = z[i].on) l.apply(this, arguments);
      return dispatch;
    }
    event.on = function(name, listener) {
      var l = listenerByName.get(name), i;
      if (arguments.length < 2) return l && l.on;
      if (l) {
        l.on = null;
        listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1));
        listenerByName.remove(name);
      }
      if (listener) listeners.push(listenerByName.set(name, {
        on: listener
      }));
      return dispatch;
    };
    return event;
  }
  d3.event = null;
  function d3_eventPreventDefault() {
    d3.event.preventDefault();
  }
  function d3_eventSource() {
    var e = d3.event, s;
    while (s = e.sourceEvent) e = s;
    return e;
  }
  function d3_eventDispatch(target) {
    var dispatch = new d3_dispatch(), i = 0, n = arguments.length;
    while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
    dispatch.of = function(thiz, argumentz) {
      return function(e1) {
        try {
          var e0 = e1.sourceEvent = d3.event;
          e1.target = target;
          d3.event = e1;
          dispatch[e1.type].apply(thiz, argumentz);
        } finally {
          d3.event = e0;
        }
      };
    };
    return dispatch;
  }
  d3.requote = function(s) {
    return s.replace(d3_requote_re, "\\$&");
  };
  var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
  var d3_subclass = {}.__proto__ ? function(object, prototype) {
    object.__proto__ = prototype;
  } : function(object, prototype) {
    for (var property in prototype) object[property] = prototype[property];
  };
  function d3_selection(groups) {
    d3_subclass(groups, d3_selectionPrototype);
    return groups;
  }
  var d3_select = function(s, n) {
    return n.querySelector(s);
  }, d3_selectAll = function(s, n) {
    return n.querySelectorAll(s);
  }, d3_selectMatches = function(n, s) {
    var d3_selectMatcher = n.matches || n[d3_vendorSymbol(n, "matchesSelector")];
    d3_selectMatches = function(n, s) {
      return d3_selectMatcher.call(n, s);
    };
    return d3_selectMatches(n, s);
  };
  if (typeof Sizzle === "function") {
    d3_select = function(s, n) {
      return Sizzle(s, n)[0] || null;
    };
    d3_selectAll = Sizzle;
    d3_selectMatches = Sizzle.matchesSelector;
  }
  d3.selection = function() {
    return d3.select(d3_document.documentElement);
  };
  var d3_selectionPrototype = d3.selection.prototype = [];
  d3_selectionPrototype.select = function(selector) {
    var subgroups = [], subgroup, subnode, group, node;
    selector = d3_selection_selector(selector);
    for (var j = -1, m = this.length; ++j < m; ) {
      subgroups.push(subgroup = []);
      subgroup.parentNode = (group = this[j]).parentNode;
      for (var i = -1, n = group.length; ++i < n; ) {
        if (node = group[i]) {
          subgroup.push(subnode = selector.call(node, node.__data__, i, j));
          if (subnode && "__data__" in node) subnode.__data__ = node.__data__;
        } else {
          subgroup.push(null);
        }
      }
    }
    return d3_selection(subgroups);
  };
  function d3_selection_selector(selector) {
    return typeof selector === "function" ? selector : function() {
      return d3_select(selector, this);
    };
  }
  d3_selectionPrototype.selectAll = function(selector) {
    var subgroups = [], subgroup, node;
    selector = d3_selection_selectorAll(selector);
    for (var j = -1, m = this.length; ++j < m; ) {
      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
        if (node = group[i]) {
          subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i, j)));
          subgroup.parentNode = node;
        }
      }
    }
    return d3_selection(subgroups);
  };
  function d3_selection_selectorAll(selector) {
    return typeof selector === "function" ? selector : function() {
      return d3_selectAll(selector, this);
    };
  }
  var d3_nsXhtml = "http://www.w3.org/1999/xhtml";
  var d3_nsPrefix = {
    svg: "http://www.w3.org/2000/svg",
    xhtml: d3_nsXhtml,
    xlink: "http://www.w3.org/1999/xlink",
    xml: "http://www.w3.org/XML/1998/namespace",
    xmlns: "http://www.w3.org/2000/xmlns/"
  };
  d3.ns = {
    prefix: d3_nsPrefix,
    qualify: function(name) {
      var i = name.indexOf(":"), prefix = name;
      if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1);
      return d3_nsPrefix.hasOwnProperty(prefix) ? {
        space: d3_nsPrefix[prefix],
        local: name
      } : name;
    }
  };
  d3_selectionPrototype.attr = function(name, value) {
    if (arguments.length < 2) {
      if (typeof name === "string") {
        var node = this.node();
        name = d3.ns.qualify(name);
        return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name);
      }
      for (value in name) this.each(d3_selection_attr(value, name[value]));
      return this;
    }
    return this.each(d3_selection_attr(name, value));
  };
  function d3_selection_attr(name, value) {
    name = d3.ns.qualify(name);
    function attrNull() {
      this.removeAttribute(name);
    }
    function attrNullNS() {
      this.removeAttributeNS(name.space, name.local);
    }
    function attrConstant() {
      this.setAttribute(name, value);
    }
    function attrConstantNS() {
      this.setAttributeNS(name.space, name.local, value);
    }
    function attrFunction() {
      var x = value.apply(this, arguments);
      if (x == null) this.removeAttribute(name); else this.setAttribute(name, x);
    }
    function attrFunctionNS() {
      var x = value.apply(this, arguments);
      if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x);
    }
    return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant;
  }
  function d3_collapse(s) {
    return s.trim().replace(/\s+/g, " ");
  }
  d3_selectionPrototype.classed = function(name, value) {
    if (arguments.length < 2) {
      if (typeof name === "string") {
        var node = this.node(), n = (name = d3_selection_classes(name)).length, i = -1;
        if (value = node.classList) {
          while (++i < n) if (!value.contains(name[i])) return false;
        } else {
          value = node.getAttribute("class");
          while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false;
        }
        return true;
      }
      for (value in name) this.each(d3_selection_classed(value, name[value]));
      return this;
    }
    return this.each(d3_selection_classed(name, value));
  };
  function d3_selection_classedRe(name) {
    return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g");
  }
  function d3_selection_classes(name) {
    return (name + "").trim().split(/^|\s+/);
  }
  function d3_selection_classed(name, value) {
    name = d3_selection_classes(name).map(d3_selection_classedName);
    var n = name.length;
    function classedConstant() {
      var i = -1;
      while (++i < n) name[i](this, value);
    }
    function classedFunction() {
      var i = -1, x = value.apply(this, arguments);
      while (++i < n) name[i](this, x);
    }
    return typeof value === "function" ? classedFunction : classedConstant;
  }
  function d3_selection_classedName(name) {
    var re = d3_selection_classedRe(name);
    return function(node, value) {
      if (c = node.classList) return value ? c.add(name) : c.remove(name);
      var c = node.getAttribute("class") || "";
      if (value) {
        re.lastIndex = 0;
        if (!re.test(c)) node.setAttribute("class", d3_collapse(c + " " + name));
      } else {
        node.setAttribute("class", d3_collapse(c.replace(re, " ")));
      }
    };
  }
  d3_selectionPrototype.style = function(name, value, priority) {
    var n = arguments.length;
    if (n < 3) {
      if (typeof name !== "string") {
        if (n < 2) value = "";
        for (priority in name) this.each(d3_selection_style(priority, name[priority], value));
        return this;
      }
      if (n < 2) {
        var node = this.node();
        return d3_window(node).getComputedStyle(node, null).getPropertyValue(name);
      }
      priority = "";
    }
    return this.each(d3_selection_style(name, value, priority));
  };
  function d3_selection_style(name, value, priority) {
    function styleNull() {
      this.style.removeProperty(name);
    }
    function styleConstant() {
      this.style.setProperty(name, value, priority);
    }
    function styleFunction() {
      var x = value.apply(this, arguments);
      if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority);
    }
    return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant;
  }
  d3_selectionPrototype.property = function(name, value) {
    if (arguments.length < 2) {
      if (typeof name === "string") return this.node()[name];
      for (value in name) this.each(d3_selection_property(value, name[value]));
      return this;
    }
    return this.each(d3_selection_property(name, value));
  };
  function d3_selection_property(name, value) {
    function propertyNull() {
      delete this[name];
    }
    function propertyConstant() {
      this[name] = value;
    }
    function propertyFunction() {
      var x = value.apply(this, arguments);
      if (x == null) delete this[name]; else this[name] = x;
    }
    return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant;
  }
  d3_selectionPrototype.text = function(value) {
    return arguments.length ? this.each(typeof value === "function" ? function() {
      var v = value.apply(this, arguments);
      this.textContent = v == null ? "" : v;
    } : value == null ? function() {
      this.textContent = "";
    } : function() {
      this.textContent = value;
    }) : this.node().textContent;
  };
  d3_selectionPrototype.html = function(value) {
    return arguments.length ? this.each(typeof value === "function" ? function() {
      var v = value.apply(this, arguments);
      this.innerHTML = v == null ? "" : v;
    } : value == null ? function() {
      this.innerHTML = "";
    } : function() {
      this.innerHTML = value;
    }) : this.node().innerHTML;
  };
  d3_selectionPrototype.append = function(name) {
    name = d3_selection_creator(name);
    return this.select(function() {
      return this.appendChild(name.apply(this, arguments));
    });
  };
  function d3_selection_creator(name) {
    function create() {
      var document = this.ownerDocument, namespace = this.namespaceURI;
      return namespace === d3_nsXhtml && document.documentElement.namespaceURI === d3_nsXhtml ? document.createElement(name) : document.createElementNS(namespace, name);
    }
    function createNS() {
      return this.ownerDocument.createElementNS(name.space, name.local);
    }
    return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? createNS : create;
  }
  d3_selectionPrototype.insert = function(name, before) {
    name = d3_selection_creator(name);
    before = d3_selection_selector(before);
    return this.select(function() {
      return this.insertBefore(name.apply(this, arguments), before.apply(this, arguments) || null);
    });
  };
  d3_selectionPrototype.remove = function() {
    return this.each(d3_selectionRemove);
  };
  function d3_selectionRemove() {
    var parent = this.parentNode;
    if (parent) parent.removeChild(this);
  }
  d3_selectionPrototype.data = function(value, key) {
    var i = -1, n = this.length, group, node;
    if (!arguments.length) {
      value = new Array(n = (group = this[0]).length);
      while (++i < n) {
        if (node = group[i]) {
          value[i] = node.__data__;
        }
      }
      return value;
    }
    function bind(group, groupData) {
      var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData;
      if (key) {
        var nodeByKeyValue = new d3_Map(), keyValues = new Array(n), keyValue;
        for (i = -1; ++i < n; ) {
          if (node = group[i]) {
            if (nodeByKeyValue.has(keyValue = key.call(node, node.__data__, i))) {
              exitNodes[i] = node;
            } else {
              nodeByKeyValue.set(keyValue, node);
            }
            keyValues[i] = keyValue;
          }
        }
        for (i = -1; ++i < m; ) {
          if (!(node = nodeByKeyValue.get(keyValue = key.call(groupData, nodeData = groupData[i], i)))) {
            enterNodes[i] = d3_selection_dataNode(nodeData);
          } else if (node !== true) {
            updateNodes[i] = node;
            node.__data__ = nodeData;
          }
          nodeByKeyValue.set(keyValue, true);
        }
        for (i = -1; ++i < n; ) {
          if (i in keyValues && nodeByKeyValue.get(keyValues[i]) !== true) {
            exitNodes[i] = group[i];
          }
        }
      } else {
        for (i = -1; ++i < n0; ) {
          node = group[i];
          nodeData = groupData[i];
          if (node) {
            node.__data__ = nodeData;
            updateNodes[i] = node;
          } else {
            enterNodes[i] = d3_selection_dataNode(nodeData);
          }
        }
        for (;i < m; ++i) {
          enterNodes[i] = d3_selection_dataNode(groupData[i]);
        }
        for (;i < n; ++i) {
          exitNodes[i] = group[i];
        }
      }
      enterNodes.update = updateNodes;
      enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode;
      enter.push(enterNodes);
      update.push(updateNodes);
      exit.push(exitNodes);
    }
    var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]);
    if (typeof value === "function") {
      while (++i < n) {
        bind(group = this[i], value.call(group, group.parentNode.__data__, i));
      }
    } else {
      while (++i < n) {
        bind(group = this[i], value);
      }
    }
    update.enter = function() {
      return enter;
    };
    update.exit = function() {
      return exit;
    };
    return update;
  };
  function d3_selection_dataNode(data) {
    return {
      __data__: data
    };
  }
  d3_selectionPrototype.datum = function(value) {
    return arguments.length ? this.property("__data__", value) : this.property("__data__");
  };
  d3_selectionPrototype.filter = function(filter) {
    var subgroups = [], subgroup, group, node;
    if (typeof filter !== "function") filter = d3_selection_filter(filter);
    for (var j = 0, m = this.length; j < m; j++) {
      subgroups.push(subgroup = []);
      subgroup.parentNode = (group = this[j]).parentNode;
      for (var i = 0, n = group.length; i < n; i++) {
        if ((node = group[i]) && filter.call(node, node.__data__, i, j)) {
          subgroup.push(node);
        }
      }
    }
    return d3_selection(subgroups);
  };
  function d3_selection_filter(selector) {
    return function() {
      return d3_selectMatches(this, selector);
    };
  }
  d3_selectionPrototype.order = function() {
    for (var j = -1, m = this.length; ++j < m; ) {
      for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) {
        if (node = group[i]) {
          if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next);
          next = node;
        }
      }
    }
    return this;
  };
  d3_selectionPrototype.sort = function(comparator) {
    comparator = d3_selection_sortComparator.apply(this, arguments);
    for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator);
    return this.order();
  };
  function d3_selection_sortComparator(comparator) {
    if (!arguments.length) comparator = d3_ascending;
    return function(a, b) {
      return a && b ? comparator(a.__data__, b.__data__) : !a - !b;
    };
  }
  d3_selectionPrototype.each = function(callback) {
    return d3_selection_each(this, function(node, i, j) {
      callback.call(node, node.__data__, i, j);
    });
  };
  function d3_selection_each(groups, callback) {
    for (var j = 0, m = groups.length; j < m; j++) {
      for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) {
        if (node = group[i]) callback(node, i, j);
      }
    }
    return groups;
  }
  d3_selectionPrototype.call = function(callback) {
    var args = d3_array(arguments);
    callback.apply(args[0] = this, args);
    return this;
  };
  d3_selectionPrototype.empty = function() {
    return !this.node();
  };
  d3_selectionPrototype.node = function() {
    for (var j = 0, m = this.length; j < m; j++) {
      for (var group = this[j], i = 0, n = group.length; i < n; i++) {
        var node = group[i];
        if (node) return node;
      }
    }
    return null;
  };
  d3_selectionPrototype.size = function() {
    var n = 0;
    d3_selection_each(this, function() {
      ++n;
    });
    return n;
  };
  function d3_selection_enter(selection) {
    d3_subclass(selection, d3_selection_enterPrototype);
    return selection;
  }
  var d3_selection_enterPrototype = [];
  d3.selection.enter = d3_selection_enter;
  d3.selection.enter.prototype = d3_selection_enterPrototype;
  d3_selection_enterPrototype.append = d3_selectionPrototype.append;
  d3_selection_enterPrototype.empty = d3_selectionPrototype.empty;
  d3_selection_enterPrototype.node = d3_selectionPrototype.node;
  d3_selection_enterPrototype.call = d3_selectionPrototype.call;
  d3_selection_enterPrototype.size = d3_selectionPrototype.size;
  d3_selection_enterPrototype.select = function(selector) {
    var subgroups = [], subgroup, subnode, upgroup, group, node;
    for (var j = -1, m = this.length; ++j < m; ) {
      upgroup = (group = this[j]).update;
      subgroups.push(subgroup = []);
      subgroup.parentNode = group.parentNode;
      for (var i = -1, n = group.length; ++i < n; ) {
        if (node = group[i]) {
          subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i, j));
          subnode.__data__ = node.__data__;
        } else {
          subgroup.push(null);
        }
      }
    }
    return d3_selection(subgroups);
  };
  d3_selection_enterPrototype.insert = function(name, before) {
    if (arguments.length < 2) before = d3_selection_enterInsertBefore(this);
    return d3_selectionPrototype.insert.call(this, name, before);
  };
  function d3_selection_enterInsertBefore(enter) {
    var i0, j0;
    return function(d, i, j) {
      var group = enter[j].update, n = group.length, node;
      if (j != j0) j0 = j, i0 = 0;
      if (i >= i0) i0 = i + 1;
      while (!(node = group[i0]) && ++i0 < n) ;
      return node;
    };
  }
  d3.select = function(node) {
    var group;
    if (typeof node === "string") {
      group = [ d3_select(node, d3_document) ];
      group.parentNode = d3_document.documentElement;
    } else {
      group = [ node ];
      group.parentNode = d3_documentElement(node);
    }
    return d3_selection([ group ]);
  };
  d3.selectAll = function(nodes) {
    var group;
    if (typeof nodes === "string") {
      group = d3_array(d3_selectAll(nodes, d3_document));
      group.parentNode = d3_document.documentElement;
    } else {
      group = d3_array(nodes);
      group.parentNode = null;
    }
    return d3_selection([ group ]);
  };
  d3_selectionPrototype.on = function(type, listener, capture) {
    var n = arguments.length;
    if (n < 3) {
      if (typeof type !== "string") {
        if (n < 2) listener = false;
        for (capture in type) this.each(d3_selection_on(capture, type[capture], listener));
        return this;
      }
      if (n < 2) return (n = this.node()["__on" + type]) && n._;
      capture = false;
    }
    return this.each(d3_selection_on(type, listener, capture));
  };
  function d3_selection_on(type, listener, capture) {
    var name = "__on" + type, i = type.indexOf("."), wrap = d3_selection_onListener;
    if (i > 0) type = type.slice(0, i);
    var filter = d3_selection_onFilters.get(type);
    if (filter) type = filter, wrap = d3_selection_onFilter;
    function onRemove() {
      var l = this[name];
      if (l) {
        this.removeEventListener(type, l, l.$);
        delete this[name];
      }
    }
    function onAdd() {
      var l = wrap(listener, d3_array(arguments));
      onRemove.call(this);
      this.addEventListener(type, this[name] = l, l.$ = capture);
      l._ = listener;
    }
    function removeAll() {
      var re = new RegExp("^__on([^.]+)" + d3.requote(type) + "$"), match;
      for (var name in this) {
        if (match = name.match(re)) {
          var l = this[name];
          this.removeEventListener(match[1], l, l.$);
          delete this[name];
        }
      }
    }
    return i ? listener ? onAdd : onRemove : listener ? d3_noop : removeAll;
  }
  var d3_selection_onFilters = d3.map({
    mouseenter: "mouseover",
    mouseleave: "mouseout"
  });
  if (d3_document) {
    d3_selection_onFilters.forEach(function(k) {
      if ("on" + k in d3_document) d3_selection_onFilters.remove(k);
    });
  }
  function d3_selection_onListener(listener, argumentz) {
    return function(e) {
      var o = d3.event;
      d3.event = e;
      argumentz[0] = this.__data__;
      try {
        listener.apply(this, argumentz);
      } finally {
        d3.event = o;
      }
    };
  }
  function d3_selection_onFilter(listener, argumentz) {
    var l = d3_selection_onListener(listener, argumentz);
    return function(e) {
      var target = this, related = e.relatedTarget;
      if (!related || related !== target && !(related.compareDocumentPosition(target) & 8)) {
        l.call(target, e);
      }
    };
  }
  var d3_event_dragSelect, d3_event_dragId = 0;
  function d3_event_dragSuppress(node) {
    var name = ".dragsuppress-" + ++d3_event_dragId, click = "click" + name, w = d3.select(d3_window(node)).on("touchmove" + name, d3_eventPreventDefault).on("dragstart" + name, d3_eventPreventDefault).on("selectstart" + name, d3_eventPreventDefault);
    if (d3_event_dragSelect == null) {
      d3_event_dragSelect = "onselectstart" in node ? false : d3_vendorSymbol(node.style, "userSelect");
    }
    if (d3_event_dragSelect) {
      var style = d3_documentElement(node).style, select = style[d3_event_dragSelect];
      style[d3_event_dragSelect] = "none";
    }
    return function(suppressClick) {
      w.on(name, null);
      if (d3_event_dragSelect) style[d3_event_dragSelect] = select;
      if (suppressClick) {
        var off = function() {
          w.on(click, null);
        };
        w.on(click, function() {
          d3_eventPreventDefault();
          off();
        }, true);
        setTimeout(off, 0);
      }
    };
  }
  d3.mouse = function(container) {
    return d3_mousePoint(container, d3_eventSource());
  };
  var d3_mouse_bug44083 = this.navigator && /WebKit/.test(this.navigator.userAgent) ? -1 : 0;
  function d3_mousePoint(container, e) {
    if (e.changedTouches) e = e.changedTouches[0];
    var svg = container.ownerSVGElement || container;
    if (svg.createSVGPoint) {
      var point = svg.createSVGPoint();
      if (d3_mouse_bug44083 < 0) {
        var window = d3_window(container);
        if (window.scrollX || window.scrollY) {
          svg = d3.select("body").append("svg").style({
            position: "absolute",
            top: 0,
            left: 0,
            margin: 0,
            padding: 0,
            border: "none"
          }, "important");
          var ctm = svg[0][0].getScreenCTM();
          d3_mouse_bug44083 = !(ctm.f || ctm.e);
          svg.remove();
        }
      }
      if (d3_mouse_bug44083) point.x = e.pageX, point.y = e.pageY; else point.x = e.clientX, 
      point.y = e.clientY;
      point = point.matrixTransform(container.getScreenCTM().inverse());
      return [ point.x, point.y ];
    }
    var rect = container.getBoundingClientRect();
    return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ];
  }
  d3.touch = function(container, touches, identifier) {
    if (arguments.length < 3) identifier = touches, touches = d3_eventSource().changedTouches;
    if (touches) for (var i = 0, n = touches.length, touch; i < n; ++i) {
      if ((touch = touches[i]).identifier === identifier) {
        return d3_mousePoint(container, touch);
      }
    }
  };
  d3.behavior.drag = function() {
    var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null, mousedown = dragstart(d3_noop, d3.mouse, d3_window, "mousemove", "mouseup"), touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_identity, "touchmove", "touchend");
    function drag() {
      this.on("mousedown.drag", mousedown).on("touchstart.drag", touchstart);
    }
    function dragstart(id, position, subject, move, end) {
      return function() {
        var that = this, target = d3.event.target.correspondingElement || d3.event.target, parent = that.parentNode, dispatch = event.of(that, arguments), dragged = 0, dragId = id(), dragName = ".drag" + (dragId == null ? "" : "-" + dragId), dragOffset, dragSubject = d3.select(subject(target)).on(move + dragName, moved).on(end + dragName, ended), dragRestore = d3_event_dragSuppress(target), position0 = position(parent, dragId);
        if (origin) {
          dragOffset = origin.apply(that, arguments);
          dragOffset = [ dragOffset.x - position0[0], dragOffset.y - position0[1] ];
        } else {
          dragOffset = [ 0, 0 ];
        }
        dispatch({
          type: "dragstart"
        });
        function moved() {
          var position1 = position(parent, dragId), dx, dy;
          if (!position1) return;
          dx = position1[0] - position0[0];
          dy = position1[1] - position0[1];
          dragged |= dx | dy;
          position0 = position1;
          dispatch({
            type: "drag",
            x: position1[0] + dragOffset[0],
            y: position1[1] + dragOffset[1],
            dx: dx,
            dy: dy
          });
        }
        function ended() {
          if (!position(parent, dragId)) return;
          dragSubject.on(move + dragName, null).on(end + dragName, null);
          dragRestore(dragged);
          dispatch({
            type: "dragend"
          });
        }
      };
    }
    drag.origin = function(x) {
      if (!arguments.length) return origin;
      origin = x;
      return drag;
    };
    return d3.rebind(drag, event, "on");
  };
  function d3_behavior_dragTouchId() {
    return d3.event.changedTouches[0].identifier;
  }
  d3.touches = function(container, touches) {
    if (arguments.length < 2) touches = d3_eventSource().touches;
    return touches ? d3_array(touches).map(function(touch) {
      var point = d3_mousePoint(container, touch);
      point.identifier = touch.identifier;
      return point;
    }) : [];
  };
  var ε = 1e-6, ε2 = ε * ε, π = Math.PI, τ = 2 * π, τε = τ - ε, halfπ = π / 2, d3_radians = π / 180, d3_degrees = 180 / π;
  function d3_sgn(x) {
    return x > 0 ? 1 : x < 0 ? -1 : 0;
  }
  function d3_cross2d(a, b, c) {
    return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
  }
  function d3_acos(x) {
    return x > 1 ? 0 : x < -1 ? π : Math.acos(x);
  }
  function d3_asin(x) {
    return x > 1 ? halfπ : x < -1 ? -halfπ : Math.asin(x);
  }
  function d3_sinh(x) {
    return ((x = Math.exp(x)) - 1 / x) / 2;
  }
  function d3_cosh(x) {
    return ((x = Math.exp(x)) + 1 / x) / 2;
  }
  function d3_tanh(x) {
    return ((x = Math.exp(2 * x)) - 1) / (x + 1);
  }
  function d3_haversin(x) {
    return (x = Math.sin(x / 2)) * x;
  }
  var ρ = Math.SQRT2, ρ2 = 2, ρ4 = 4;
  d3.interpolateZoom = function(p0, p1) {
    var ux0 = p0[0], uy0 = p0[1], w0 = p0[2], ux1 = p1[0], uy1 = p1[1], w1 = p1[2], dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, i, S;
    if (d2 < ε2) {
      S = Math.log(w1 / w0) / ρ;
      i = function(t) {
        return [ ux0 + t * dx, uy0 + t * dy, w0 * Math.exp(ρ * t * S) ];
      };
    } else {
      var d1 = Math.sqrt(d2), b0 = (w1 * w1 - w0 * w0 + ρ4 * d2) / (2 * w0 * ρ2 * d1), b1 = (w1 * w1 - w0 * w0 - ρ4 * d2) / (2 * w1 * ρ2 * d1), r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0), r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1);
      S = (r1 - r0) / ρ;
      i = function(t) {
        var s = t * S, coshr0 = d3_cosh(r0), u = w0 / (ρ2 * d1) * (coshr0 * d3_tanh(ρ * s + r0) - d3_sinh(r0));
        return [ ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / d3_cosh(ρ * s + r0) ];
      };
    }
    i.duration = S * 1e3;
    return i;
  };
  d3.behavior.zoom = function() {
    var view = {
      x: 0,
      y: 0,
      k: 1
    }, translate0, center0, center, size = [ 960, 500 ], scaleExtent = d3_behavior_zoomInfinity, duration = 250, zooming = 0, mousedown = "mousedown.zoom", mousemove = "mousemove.zoom", mouseup = "mouseup.zoom", mousewheelTimer, touchstart = "touchstart.zoom", touchtime, event = d3_eventDispatch(zoom, "zoomstart", "zoom", "zoomend"), x0, x1, y0, y1;
    if (!d3_behavior_zoomWheel) {
      d3_behavior_zoomWheel = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() {
        return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1);
      }, "wheel") : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() {
        return d3.event.wheelDelta;
      }, "mousewheel") : (d3_behavior_zoomDelta = function() {
        return -d3.event.detail;
      }, "MozMousePixelScroll");
    }
    function zoom(g) {
      g.on(mousedown, mousedowned).on(d3_behavior_zoomWheel + ".zoom", mousewheeled).on("dblclick.zoom", dblclicked).on(touchstart, touchstarted);
    }
    zoom.event = function(g) {
      g.each(function() {
        var dispatch = event.of(this, arguments), view1 = view;
        if (d3_transitionInheritId) {
          d3.select(this).transition().each("start.zoom", function() {
            view = this.__chart__ || {
              x: 0,
              y: 0,
              k: 1
            };
            zoomstarted(dispatch);
          }).tween("zoom:zoom", function() {
            var dx = size[0], dy = size[1], cx = center0 ? center0[0] : dx / 2, cy = center0 ? center0[1] : dy / 2, i = d3.interpolateZoom([ (cx - view.x) / view.k, (cy - view.y) / view.k, dx / view.k ], [ (cx - view1.x) / view1.k, (cy - view1.y) / view1.k, dx / view1.k ]);
            return function(t) {
              var l = i(t), k = dx / l[2];
              this.__chart__ = view = {
                x: cx - l[0] * k,
                y: cy - l[1] * k,
                k: k
              };
              zoomed(dispatch);
            };
          }).each("interrupt.zoom", function() {
            zoomended(dispatch);
          }).each("end.zoom", function() {
            zoomended(dispatch);
          });
        } else {
          this.__chart__ = view;
          zoomstarted(dispatch);
          zoomed(dispatch);
          zoomended(dispatch);
        }
      });
    };
    zoom.translate = function(_) {
      if (!arguments.length) return [ view.x, view.y ];
      view = {
        x: +_[0],
        y: +_[1],
        k: view.k
      };
      rescale();
      return zoom;
    };
    zoom.scale = function(_) {
      if (!arguments.length) return view.k;
      view = {
        x: view.x,
        y: view.y,
        k: null
      };
      scaleTo(+_);
      rescale();
      return zoom;
    };
    zoom.scaleExtent = function(_) {
      if (!arguments.length) return scaleExtent;
      scaleExtent = _ == null ? d3_behavior_zoomInfinity : [ +_[0], +_[1] ];
      return zoom;
    };
    zoom.center = function(_) {
      if (!arguments.length) return center;
      center = _ && [ +_[0], +_[1] ];
      return zoom;
    };
    zoom.size = function(_) {
      if (!arguments.length) return size;
      size = _ && [ +_[0], +_[1] ];
      return zoom;
    };
    zoom.duration = function(_) {
      if (!arguments.length) return duration;
      duration = +_;
      return zoom;
    };
    zoom.x = function(z) {
      if (!arguments.length) return x1;
      x1 = z;
      x0 = z.copy();
      view = {
        x: 0,
        y: 0,
        k: 1
      };
      return zoom;
    };
    zoom.y = function(z) {
      if (!arguments.length) return y1;
      y1 = z;
      y0 = z.copy();
      view = {
        x: 0,
        y: 0,
        k: 1
      };
      return zoom;
    };
    function location(p) {
      return [ (p[0] - view.x) / view.k, (p[1] - view.y) / view.k ];
    }
    function point(l) {
      return [ l[0] * view.k + view.x, l[1] * view.k + view.y ];
    }
    function scaleTo(s) {
      view.k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s));
    }
    function translateTo(p, l) {
      l = point(l);
      view.x += p[0] - l[0];
      view.y += p[1] - l[1];
    }
    function zoomTo(that, p, l, k) {
      that.__chart__ = {
        x: view.x,
        y: view.y,
        k: view.k
      };
      scaleTo(Math.pow(2, k));
      translateTo(center0 = p, l);
      that = d3.select(that);
      if (duration > 0) that = that.transition().duration(duration);
      that.call(zoom.event);
    }
    function rescale() {
      if (x1) x1.domain(x0.range().map(function(x) {
        return (x - view.x) / view.k;
      }).map(x0.invert));
      if (y1) y1.domain(y0.range().map(function(y) {
        return (y - view.y) / view.k;
      }).map(y0.invert));
    }
    function zoomstarted(dispatch) {
      if (!zooming++) dispatch({
        type: "zoomstart"
      });
    }
    function zoomed(dispatch) {
      rescale();
      dispatch({
        type: "zoom",
        scale: view.k,
        translate: [ view.x, view.y ]
      });
    }
    function zoomended(dispatch) {
      if (!--zooming) dispatch({
        type: "zoomend"
      }), center0 = null;
    }
    function mousedowned() {
      var that = this, dispatch = event.of(that, arguments), dragged = 0, subject = d3.select(d3_window(that)).on(mousemove, moved).on(mouseup, ended), location0 = location(d3.mouse(that)), dragRestore = d3_event_dragSuppress(that);
      d3_selection_interrupt.call(that);
      zoomstarted(dispatch);
      function moved() {
        dragged = 1;
        translateTo(d3.mouse(that), location0);
        zoomed(dispatch);
      }
      function ended() {
        subject.on(mousemove, null).on(mouseup, null);
        dragRestore(dragged);
        zoomended(dispatch);
      }
    }
    function touchstarted() {
      var that = this, dispatch = event.of(that, arguments), locations0 = {}, distance0 = 0, scale0, zoomName = ".zoom-" + d3.event.changedTouches[0].identifier, touchmove = "touchmove" + zoomName, touchend = "touchend" + zoomName, targets = [], subject = d3.select(that), dragRestore = d3_event_dragSuppress(that);
      started();
      zoomstarted(dispatch);
      subject.on(mousedown, null).on(touchstart, started);
      function relocate() {
        var touches = d3.touches(that);
        scale0 = view.k;
        touches.forEach(function(t) {
          if (t.identifier in locations0) locations0[t.identifier] = location(t);
        });
        return touches;
      }
      function started() {
        var target = d3.event.target;
        d3.select(target).on(touchmove, moved).on(touchend, ended);
        targets.push(target);
        var changed = d3.event.changedTouches;
        for (var i = 0, n = changed.length; i < n; ++i) {
          locations0[changed[i].identifier] = null;
        }
        var touches = relocate(), now = Date.now();
        if (touches.length === 1) {
          if (now - touchtime < 500) {
            var p = touches[0];
            zoomTo(that, p, locations0[p.identifier], Math.floor(Math.log(view.k) / Math.LN2) + 1);
            d3_eventPreventDefault();
          }
          touchtime = now;
        } else if (touches.length > 1) {
          var p = touches[0], q = touches[1], dx = p[0] - q[0], dy = p[1] - q[1];
          distance0 = dx * dx + dy * dy;
        }
      }
      function moved() {
        var touches = d3.touches(that), p0, l0, p1, l1;
        d3_selection_interrupt.call(that);
        for (var i = 0, n = touches.length; i < n; ++i, l1 = null) {
          p1 = touches[i];
          if (l1 = locations0[p1.identifier]) {
            if (l0) break;
            p0 = p1, l0 = l1;
          }
        }
        if (l1) {
          var distance1 = (distance1 = p1[0] - p0[0]) * distance1 + (distance1 = p1[1] - p0[1]) * distance1, scale1 = distance0 && Math.sqrt(distance1 / distance0);
          p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ];
          l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ];
          scaleTo(scale1 * scale0);
        }
        touchtime = null;
        translateTo(p0, l0);
        zoomed(dispatch);
      }
      function ended() {
        if (d3.event.touches.length) {
          var changed = d3.event.changedTouches;
          for (var i = 0, n = changed.length; i < n; ++i) {
            delete locations0[changed[i].identifier];
          }
          for (var identifier in locations0) {
            return void relocate();
          }
        }
        d3.selectAll(targets).on(zoomName, null);
        subject.on(mousedown, mousedowned).on(touchstart, touchstarted);
        dragRestore();
        zoomended(dispatch);
      }
    }
    function mousewheeled() {
      var dispatch = event.of(this, arguments);
      if (mousewheelTimer) clearTimeout(mousewheelTimer); else d3_selection_interrupt.call(this), 
      translate0 = location(center0 = center || d3.mouse(this)), zoomstarted(dispatch);
      mousewheelTimer = setTimeout(function() {
        mousewheelTimer = null;
        zoomended(dispatch);
      }, 50);
      d3_eventPreventDefault();
      scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * view.k);
      translateTo(center0, translate0);
      zoomed(dispatch);
    }
    function dblclicked() {
      var p = d3.mouse(this), k = Math.log(view.k) / Math.LN2;
      zoomTo(this, p, location(p), d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1);
    }
    return d3.rebind(zoom, event, "on");
  };
  var d3_behavior_zoomInfinity = [ 0, Infinity ], d3_behavior_zoomDelta, d3_behavior_zoomWheel;
  d3.color = d3_color;
  function d3_color() {}
  d3_color.prototype.toString = function() {
    return this.rgb() + "";
  };
  d3.hsl = d3_hsl;
  function d3_hsl(h, s, l) {
    return this instanceof d3_hsl ? void (this.h = +h, this.s = +s, this.l = +l) : arguments.length < 2 ? h instanceof d3_hsl ? new d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : new d3_hsl(h, s, l);
  }
  var d3_hslPrototype = d3_hsl.prototype = new d3_color();
  d3_hslPrototype.brighter = function(k) {
    k = Math.pow(.7, arguments.length ? k : 1);
    return new d3_hsl(this.h, this.s, this.l / k);
  };
  d3_hslPrototype.darker = function(k) {
    k = Math.pow(.7, arguments.length ? k : 1);
    return new d3_hsl(this.h, this.s, k * this.l);
  };
  d3_hslPrototype.rgb = function() {
    return d3_hsl_rgb(this.h, this.s, this.l);
  };
  function d3_hsl_rgb(h, s, l) {
    var m1, m2;
    h = isNaN(h) ? 0 : (h %= 360) < 0 ? h + 360 : h;
    s = isNaN(s) ? 0 : s < 0 ? 0 : s > 1 ? 1 : s;
    l = l < 0 ? 0 : l > 1 ? 1 : l;
    m2 = l <= .5 ? l * (1 + s) : l + s - l * s;
    m1 = 2 * l - m2;
    function v(h) {
      if (h > 360) h -= 360; else if (h < 0) h += 360;
      if (h < 60) return m1 + (m2 - m1) * h / 60;
      if (h < 180) return m2;
      if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60;
      return m1;
    }
    function vv(h) {
      return Math.round(v(h) * 255);
    }
    return new d3_rgb(vv(h + 120), vv(h), vv(h - 120));
  }
  d3.hcl = d3_hcl;
  function d3_hcl(h, c, l) {
    return this instanceof d3_hcl ? void (this.h = +h, this.c = +c, this.l = +l) : arguments.length < 2 ? h instanceof d3_hcl ? new d3_hcl(h.h, h.c, h.l) : h instanceof d3_lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : new d3_hcl(h, c, l);
  }
  var d3_hclPrototype = d3_hcl.prototype = new d3_color();
  d3_hclPrototype.brighter = function(k) {
    return new d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)));
  };
  d3_hclPrototype.darker = function(k) {
    return new d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)));
  };
  d3_hclPrototype.rgb = function() {
    return d3_hcl_lab(this.h, this.c, this.l).rgb();
  };
  function d3_hcl_lab(h, c, l) {
    if (isNaN(h)) h = 0;
    if (isNaN(c)) c = 0;
    return new d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c);
  }
  d3.lab = d3_lab;
  function d3_lab(l, a, b) {
    return this instanceof d3_lab ? void (this.l = +l, this.a = +a, this.b = +b) : arguments.length < 2 ? l instanceof d3_lab ? new d3_lab(l.l, l.a, l.b) : l instanceof d3_hcl ? d3_hcl_lab(l.h, l.c, l.l) : d3_rgb_lab((l = d3_rgb(l)).r, l.g, l.b) : new d3_lab(l, a, b);
  }
  var d3_lab_K = 18;
  var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883;
  var d3_labPrototype = d3_lab.prototype = new d3_color();
  d3_labPrototype.brighter = function(k) {
    return new d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
  };
  d3_labPrototype.darker = function(k) {
    return new d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
  };
  d3_labPrototype.rgb = function() {
    return d3_lab_rgb(this.l, this.a, this.b);
  };
  function d3_lab_rgb(l, a, b) {
    var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200;
    x = d3_lab_xyz(x) * d3_lab_X;
    y = d3_lab_xyz(y) * d3_lab_Y;
    z = d3_lab_xyz(z) * d3_lab_Z;
    return new d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z));
  }
  function d3_lab_hcl(l, a, b) {
    return l > 0 ? new d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l) : new d3_hcl(NaN, NaN, l);
  }
  function d3_lab_xyz(x) {
    return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037;
  }
  function d3_xyz_lab(x) {
    return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29;
  }
  function d3_xyz_rgb(r) {
    return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055));
  }
  d3.rgb = d3_rgb;
  function d3_rgb(r, g, b) {
    return this instanceof d3_rgb ? void (this.r = ~~r, this.g = ~~g, this.b = ~~b) : arguments.length < 2 ? r instanceof d3_rgb ? new d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : new d3_rgb(r, g, b);
  }
  function d3_rgbNumber(value) {
    return new d3_rgb(value >> 16, value >> 8 & 255, value & 255);
  }
  function d3_rgbString(value) {
    return d3_rgbNumber(value) + "";
  }
  var d3_rgbPrototype = d3_rgb.prototype = new d3_color();
  d3_rgbPrototype.brighter = function(k) {
    k = Math.pow(.7, arguments.length ? k : 1);
    var r = this.r, g = this.g, b = this.b, i = 30;
    if (!r && !g && !b) return new d3_rgb(i, i, i);
    if (r && r < i) r = i;
    if (g && g < i) g = i;
    if (b && b < i) b = i;
    return new d3_rgb(Math.min(255, r / k), Math.min(255, g / k), Math.min(255, b / k));
  };
  d3_rgbPrototype.darker = function(k) {
    k = Math.pow(.7, arguments.length ? k : 1);
    return new d3_rgb(k * this.r, k * this.g, k * this.b);
  };
  d3_rgbPrototype.hsl = function() {
    return d3_rgb_hsl(this.r, this.g, this.b);
  };
  d3_rgbPrototype.toString = function() {
    return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b);
  };
  function d3_rgb_hex(v) {
    return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16);
  }
  function d3_rgb_parse(format, rgb, hsl) {
    var r = 0, g = 0, b = 0, m1, m2, color;
    m1 = /([a-z]+)\((.*)\)/.exec(format = format.toLowerCase());
    if (m1) {
      m2 = m1[2].split(",");
      switch (m1[1]) {
       case "hsl":
        {
          return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100);
        }

       case "rgb":
        {
          return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2]));
        }
      }
    }
    if (color = d3_rgb_names.get(format)) {
      return rgb(color.r, color.g, color.b);
    }
    if (format != null && format.charAt(0) === "#" && !isNaN(color = parseInt(format.slice(1), 16))) {
      if (format.length === 4) {
        r = (color & 3840) >> 4;
        r = r >> 4 | r;
        g = color & 240;
        g = g >> 4 | g;
        b = color & 15;
        b = b << 4 | b;
      } else if (format.length === 7) {
        r = (color & 16711680) >> 16;
        g = (color & 65280) >> 8;
        b = color & 255;
      }
    }
    return rgb(r, g, b);
  }
  function d3_rgb_hsl(r, g, b) {
    var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2;
    if (d) {
      s = l < .5 ? d / (max + min) : d / (2 - max - min);
      if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4;
      h *= 60;
    } else {
      h = NaN;
      s = l > 0 && l < 1 ? 0 : h;
    }
    return new d3_hsl(h, s, l);
  }
  function d3_rgb_lab(r, g, b) {
    r = d3_rgb_xyz(r);
    g = d3_rgb_xyz(g);
    b = d3_rgb_xyz(b);
    var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z);
    return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z));
  }
  function d3_rgb_xyz(r) {
    return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4);
  }
  function d3_rgb_parseNumber(c) {
    var f = parseFloat(c);
    return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f;
  }
  var d3_rgb_names = d3.map({
    aliceblue: 15792383,
    antiquewhite: 16444375,
    aqua: 65535,
    aquamarine: 8388564,
    azure: 15794175,
    beige: 16119260,
    bisque: 16770244,
    black: 0,
    blanchedalmond: 16772045,
    blue: 255,
    blueviolet: 9055202,
    brown: 10824234,
    burlywood: 14596231,
    cadetblue: 6266528,
    chartreuse: 8388352,
    chocolate: 13789470,
    coral: 16744272,
    cornflowerblue: 6591981,
    cornsilk: 16775388,
    crimson: 14423100,
    cyan: 65535,
    darkblue: 139,
    darkcyan: 35723,
    darkgoldenrod: 12092939,
    darkgray: 11119017,
    darkgreen: 25600,
    darkgrey: 11119017,
    darkkhaki: 12433259,
    darkmagenta: 9109643,
    darkolivegreen: 5597999,
    darkorange: 16747520,
    darkorchid: 10040012,
    darkred: 9109504,
    darksalmon: 15308410,
    darkseagreen: 9419919,
    darkslateblue: 4734347,
    darkslategray: 3100495,
    darkslategrey: 3100495,
    darkturquoise: 52945,
    darkviolet: 9699539,
    deeppink: 16716947,
    deepskyblue: 49151,
    dimgray: 6908265,
    dimgrey: 6908265,
    dodgerblue: 2003199,
    firebrick: 11674146,
    floralwhite: 16775920,
    forestgreen: 2263842,
    fuchsia: 16711935,
    gainsboro: 14474460,
    ghostwhite: 16316671,
    gold: 16766720,
    goldenrod: 14329120,
    gray: 8421504,
    green: 32768,
    greenyellow: 11403055,
    grey: 8421504,
    honeydew: 15794160,
    hotpink: 16738740,
    indianred: 13458524,
    indigo: 4915330,
    ivory: 16777200,
    khaki: 15787660,
    lavender: 15132410,
    lavenderblush: 16773365,
    lawngreen: 8190976,
    lemonchiffon: 16775885,
    lightblue: 11393254,
    lightcoral: 15761536,
    lightcyan: 14745599,
    lightgoldenrodyellow: 16448210,
    lightgray: 13882323,
    lightgreen: 9498256,
    lightgrey: 13882323,
    lightpink: 16758465,
    lightsalmon: 16752762,
    lightseagreen: 2142890,
    lightskyblue: 8900346,
    lightslategray: 7833753,
    lightslategrey: 7833753,
    lightsteelblue: 11584734,
    lightyellow: 16777184,
    lime: 65280,
    limegreen: 3329330,
    linen: 16445670,
    magenta: 16711935,
    maroon: 8388608,
    mediumaquamarine: 6737322,
    mediumblue: 205,
    mediumorchid: 12211667,
    mediumpurple: 9662683,
    mediumseagreen: 3978097,
    mediumslateblue: 8087790,
    mediumspringgreen: 64154,
    mediumturquoise: 4772300,
    mediumvioletred: 13047173,
    midnightblue: 1644912,
    mintcream: 16121850,
    mistyrose: 16770273,
    moccasin: 16770229,
    navajowhite: 16768685,
    navy: 128,
    oldlace: 16643558,
    olive: 8421376,
    olivedrab: 7048739,
    orange: 16753920,
    orangered: 16729344,
    orchid: 14315734,
    palegoldenrod: 15657130,
    palegreen: 10025880,
    paleturquoise: 11529966,
    palevioletred: 14381203,
    papayawhip: 16773077,
    peachpuff: 16767673,
    peru: 13468991,
    pink: 16761035,
    plum: 14524637,
    powderblue: 11591910,
    purple: 8388736,
    rebeccapurple: 6697881,
    red: 16711680,
    rosybrown: 12357519,
    royalblue: 4286945,
    saddlebrown: 9127187,
    salmon: 16416882,
    sandybrown: 16032864,
    seagreen: 3050327,
    seashell: 16774638,
    sienna: 10506797,
    silver: 12632256,
    skyblue: 8900331,
    slateblue: 6970061,
    slategray: 7372944,
    slategrey: 7372944,
    snow: 16775930,
    springgreen: 65407,
    steelblue: 4620980,
    tan: 13808780,
    teal: 32896,
    thistle: 14204888,
    tomato: 16737095,
    turquoise: 4251856,
    violet: 15631086,
    wheat: 16113331,
    white: 16777215,
    whitesmoke: 16119285,
    yellow: 16776960,
    yellowgreen: 10145074
  });
  d3_rgb_names.forEach(function(key, value) {
    d3_rgb_names.set(key, d3_rgbNumber(value));
  });
  function d3_functor(v) {
    return typeof v === "function" ? v : function() {
      return v;
    };
  }
  d3.functor = d3_functor;
  d3.xhr = d3_xhrType(d3_identity);
  function d3_xhrType(response) {
    return function(url, mimeType, callback) {
      if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, 
      mimeType = null;
      return d3_xhr(url, mimeType, response, callback);
    };
  }
  function d3_xhr(url, mimeType, response, callback) {
    var xhr = {}, dispatch = d3.dispatch("beforesend", "progress", "load", "error"), headers = {}, request = new XMLHttpRequest(), responseType = null;
    if (this.XDomainRequest && !("withCredentials" in request) && /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest();
    "onload" in request ? request.onload = request.onerror = respond : request.onreadystatechange = function() {
      request.readyState > 3 && respond();
    };
    function respond() {
      var status = request.status, result;
      if (!status && d3_xhrHasResponse(request) || status >= 200 && status < 300 || status === 304) {
        try {
          result = response.call(xhr, request);
        } catch (e) {
          dispatch.error.call(xhr, e);
          return;
        }
        dispatch.load.call(xhr, result);
      } else {
        dispatch.error.call(xhr, request);
      }
    }
    request.onprogress = function(event) {
      var o = d3.event;
      d3.event = event;
      try {
        dispatch.progress.call(xhr, request);
      } finally {
        d3.event = o;
      }
    };
    xhr.header = function(name, value) {
      name = (name + "").toLowerCase();
      if (arguments.length < 2) return headers[name];
      if (value == null) delete headers[name]; else headers[name] = value + "";
      return xhr;
    };
    xhr.mimeType = function(value) {
      if (!arguments.length) return mimeType;
      mimeType = value == null ? null : value + "";
      return xhr;
    };
    xhr.responseType = function(value) {
      if (!arguments.length) return responseType;
      responseType = value;
      return xhr;
    };
    xhr.response = function(value) {
      response = value;
      return xhr;
    };
    [ "get", "post" ].forEach(function(method) {
      xhr[method] = function() {
        return xhr.send.apply(xhr, [ method ].concat(d3_array(arguments)));
      };
    });
    xhr.send = function(method, data, callback) {
      if (arguments.length === 2 && typeof data === "function") callback = data, data = null;
      request.open(method, url, true);
      if (mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*";
      if (request.setRequestHeader) for (var name in headers) request.setRequestHeader(name, headers[name]);
      if (mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType);
      if (responseType != null) request.responseType = responseType;
      if (callback != null) xhr.on("error", callback).on("load", function(request) {
        callback(null, request);
      });
      dispatch.beforesend.call(xhr, request);
      request.send(data == null ? null : data);
      return xhr;
    };
    xhr.abort = function() {
      request.abort();
      return xhr;
    };
    d3.rebind(xhr, dispatch, "on");
    return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback));
  }
  function d3_xhr_fixCallback(callback) {
    return callback.length === 1 ? function(error, request) {
      callback(error == null ? request : null);
    } : callback;
  }
  function d3_xhrHasResponse(request) {
    var type = request.responseType;
    return type && type !== "text" ? request.response : request.responseText;
  }
  d3.dsv = function(delimiter, mimeType) {
    var reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0);
    function dsv(url, row, callback) {
      if (arguments.length < 3) callback = row, row = null;
      var xhr = d3_xhr(url, mimeType, row == null ? response : typedResponse(row), callback);
      xhr.row = function(_) {
        return arguments.length ? xhr.response((row = _) == null ? response : typedResponse(_)) : row;
      };
      return xhr;
    }
    function response(request) {
      return dsv.parse(request.responseText);
    }
    function typedResponse(f) {
      return function(request) {
        return dsv.parse(request.responseText, f);
      };
    }
    dsv.parse = function(text, f) {
      var o;
      return dsv.parseRows(text, function(row, i) {
        if (o) return o(row, i - 1);
        var a = new Function("d", "return {" + row.map(function(name, i) {
          return JSON.stringify(name) + ": d[" + i + "]";
        }).join(",") + "}");
        o = f ? function(row, i) {
          return f(a(row), i);
        } : a;
      });
    };
    dsv.parseRows = function(text, f) {
      var EOL = {}, EOF = {}, rows = [], N = text.length, I = 0, n = 0, t, eol;
      function token() {
        if (I >= N) return EOF;
        if (eol) return eol = false, EOL;
        var j = I;
        if (text.charCodeAt(j) === 34) {
          var i = j;
          while (i++ < N) {
            if (text.charCodeAt(i) === 34) {
              if (text.charCodeAt(i + 1) !== 34) break;
              ++i;
            }
          }
          I = i + 2;
          var c = text.charCodeAt(i + 1);
          if (c === 13) {
            eol = true;
            if (text.charCodeAt(i + 2) === 10) ++I;
          } else if (c === 10) {
            eol = true;
          }
          return text.slice(j + 1, i).replace(/""/g, '"');
        }
        while (I < N) {
          var c = text.charCodeAt(I++), k = 1;
          if (c === 10) eol = true; else if (c === 13) {
            eol = true;
            if (text.charCodeAt(I) === 10) ++I, ++k;
          } else if (c !== delimiterCode) continue;
          return text.slice(j, I - k);
        }
        return text.slice(j);
      }
      while ((t = token()) !== EOF) {
        var a = [];
        while (t !== EOL && t !== EOF) {
          a.push(t);
          t = token();
        }
        if (f && (a = f(a, n++)) == null) continue;
        rows.push(a);
      }
      return rows;
    };
    dsv.format = function(rows) {
      if (Array.isArray(rows[0])) return dsv.formatRows(rows);
      var fieldSet = new d3_Set(), fields = [];
      rows.forEach(function(row) {
        for (var field in row) {
          if (!fieldSet.has(field)) {
            fields.push(fieldSet.add(field));
          }
        }
      });
      return [ fields.map(formatValue).join(delimiter) ].concat(rows.map(function(row) {
        return fields.map(function(field) {
          return formatValue(row[field]);
        }).join(delimiter);
      })).join("\n");
    };
    dsv.formatRows = function(rows) {
      return rows.map(formatRow).join("\n");
    };
    function formatRow(row) {
      return row.map(formatValue).join(delimiter);
    }
    function formatValue(text) {
      return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text;
    }
    return dsv;
  };
  d3.csv = d3.dsv(",", "text/csv");
  d3.tsv = d3.dsv("	", "text/tab-separated-values");
  var d3_timer_queueHead, d3_timer_queueTail, d3_timer_interval, d3_timer_timeout, d3_timer_frame = this[d3_vendorSymbol(this, "requestAnimationFrame")] || function(callback) {
    setTimeout(callback, 17);
  };
  d3.timer = function() {
    d3_timer.apply(this, arguments);
  };
  function d3_timer(callback, delay, then) {
    var n = arguments.length;
    if (n < 2) delay = 0;
    if (n < 3) then = Date.now();
    var time = then + delay, timer = {
      c: callback,
      t: time,
      n: null
    };
    if (d3_timer_queueTail) d3_timer_queueTail.n = timer; else d3_timer_queueHead = timer;
    d3_timer_queueTail = timer;
    if (!d3_timer_interval) {
      d3_timer_timeout = clearTimeout(d3_timer_timeout);
      d3_timer_interval = 1;
      d3_timer_frame(d3_timer_step);
    }
    return timer;
  }
  function d3_timer_step() {
    var now = d3_timer_mark(), delay = d3_timer_sweep() - now;
    if (delay > 24) {
      if (isFinite(delay)) {
        clearTimeout(d3_timer_timeout);
        d3_timer_timeout = setTimeout(d3_timer_step, delay);
      }
      d3_timer_interval = 0;
    } else {
      d3_timer_interval = 1;
      d3_timer_frame(d3_timer_step);
    }
  }
  d3.timer.flush = function() {
    d3_timer_mark();
    d3_timer_sweep();
  };
  function d3_timer_mark() {
    var now = Date.now(), timer = d3_timer_queueHead;
    while (timer) {
      if (now >= timer.t && timer.c(now - timer.t)) timer.c = null;
      timer = timer.n;
    }
    return now;
  }
  function d3_timer_sweep() {
    var t0, t1 = d3_timer_queueHead, time = Infinity;
    while (t1) {
      if (t1.c) {
        if (t1.t < time) time = t1.t;
        t1 = (t0 = t1).n;
      } else {
        t1 = t0 ? t0.n = t1.n : d3_timer_queueHead = t1.n;
      }
    }
    d3_timer_queueTail = t0;
    return time;
  }
  function d3_format_precision(x, p) {
    return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1);
  }
  d3.round = function(x, n) {
    return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x);
  };
  var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix);
  d3.formatPrefix = function(value, precision) {
    var i = 0;
    if (value = +value) {
      if (value < 0) value *= -1;
      if (precision) value = d3.round(value, d3_format_precision(value, precision));
      i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10);
      i = Math.max(-24, Math.min(24, Math.floor((i - 1) / 3) * 3));
    }
    return d3_formatPrefixes[8 + i / 3];
  };
  function d3_formatPrefix(d, i) {
    var k = Math.pow(10, abs(8 - i) * 3);
    return {
      scale: i > 8 ? function(d) {
        return d / k;
      } : function(d) {
        return d * k;
      },
      symbol: d
    };
  }
  function d3_locale_numberFormat(locale) {
    var locale_decimal = locale.decimal, locale_thousands = locale.thousands, locale_grouping = locale.grouping, locale_currency = locale.currency, formatGroup = locale_grouping && locale_thousands ? function(value, width) {
      var i = value.length, t = [], j = 0, g = locale_grouping[0], length = 0;
      while (i > 0 && g > 0) {
        if (length + g + 1 > width) g = Math.max(1, width - length);
        t.push(value.substring(i -= g, i + g));
        if ((length += g + 1) > width) break;
        g = locale_grouping[j = (j + 1) % locale_grouping.length];
      }
      return t.reverse().join(locale_thousands);
    } : d3_identity;
    return function(specifier) {
      var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "-", symbol = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, prefix = "", suffix = "", integer = false, exponent = true;
      if (precision) precision = +precision.substring(1);
      if (zfill || fill === "0" && align === "=") {
        zfill = fill = "0";
        align = "=";
      }
      switch (type) {
       case "n":
        comma = true;
        type = "g";
        break;

       case "%":
        scale = 100;
        suffix = "%";
        type = "f";
        break;

       case "p":
        scale = 100;
        suffix = "%";
        type = "r";
        break;

       case "b":
       case "o":
       case "x":
       case "X":
        if (symbol === "#") prefix = "0" + type.toLowerCase();

       case "c":
        exponent = false;

       case "d":
        integer = true;
        precision = 0;
        break;

       case "s":
        scale = -1;
        type = "r";
        break;
      }
      if (symbol === "$") prefix = locale_currency[0], suffix = locale_currency[1];
      if (type == "r" && !precision) type = "g";
      if (precision != null) {
        if (type == "g") precision = Math.max(1, Math.min(21, precision)); else if (type == "e" || type == "f") precision = Math.max(0, Math.min(20, precision));
      }
      type = d3_format_types.get(type) || d3_format_typeDefault;
      var zcomma = zfill && comma;
      return function(value) {
        var fullSuffix = suffix;
        if (integer && value % 1) return "";
        var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign === "-" ? "" : sign;
        if (scale < 0) {
          var unit = d3.formatPrefix(value, precision);
          value = unit.scale(value);
          fullSuffix = unit.symbol + suffix;
        } else {
          value *= scale;
        }
        value = type(value, precision);
        var i = value.lastIndexOf("."), before, after;
        if (i < 0) {
          var j = exponent ? value.lastIndexOf("e") : -1;
          if (j < 0) before = value, after = ""; else before = value.substring(0, j), after = value.substring(j);
        } else {
          before = value.substring(0, i);
          after = locale_decimal + value.substring(i + 1);
        }
        if (!zfill && comma) before = formatGroup(before, Infinity);
        var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : "";
        if (zcomma) before = formatGroup(padding + before, padding.length ? width - after.length : Infinity);
        negative += prefix;
        value = before + after;
        return (align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + fullSuffix;
      };
    };
  }
  var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i;
  var d3_format_types = d3.map({
    b: function(x) {
      return x.toString(2);
    },
    c: function(x) {
      return String.fromCharCode(x);
    },
    o: function(x) {
      return x.toString(8);
    },
    x: function(x) {
      return x.toString(16);
    },
    X: function(x) {
      return x.toString(16).toUpperCase();
    },
    g: function(x, p) {
      return x.toPrecision(p);
    },
    e: function(x, p) {
      return x.toExponential(p);
    },
    f: function(x, p) {
      return x.toFixed(p);
    },
    r: function(x, p) {
      return (x = d3.round(x, d3_format_precision(x, p))).toFixed(Math.max(0, Math.min(20, d3_format_precision(x * (1 + 1e-15), p))));
    }
  });
  function d3_format_typeDefault(x) {
    return x + "";
  }
  var d3_time = d3.time = {}, d3_date = Date;
  function d3_date_utc() {
    this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]);
  }
  d3_date_utc.prototype = {
    getDate: function() {
      return this._.getUTCDate();
    },
    getDay: function() {
      return this._.getUTCDay();
    },
    getFullYear: function() {
      return this._.getUTCFullYear();
    },
    getHours: function() {
      return this._.getUTCHours();
    },
    getMilliseconds: function() {
      return this._.getUTCMilliseconds();
    },
    getMinutes: function() {
      return this._.getUTCMinutes();
    },
    getMonth: function() {
      return this._.getUTCMonth();
    },
    getSeconds: function() {
      return this._.getUTCSeconds();
    },
    getTime: function() {
      return this._.getTime();
    },
    getTimezoneOffset: function() {
      return 0;
    },
    valueOf: function() {
      return this._.valueOf();
    },
    setDate: function() {
      d3_time_prototype.setUTCDate.apply(this._, arguments);
    },
    setDay: function() {
      d3_time_prototype.setUTCDay.apply(this._, arguments);
    },
    setFullYear: function() {
      d3_time_prototype.setUTCFullYear.apply(this._, arguments);
    },
    setHours: function() {
      d3_time_prototype.setUTCHours.apply(this._, arguments);
    },
    setMilliseconds: function() {
      d3_time_prototype.setUTCMilliseconds.apply(this._, arguments);
    },
    setMinutes: function() {
      d3_time_prototype.setUTCMinutes.apply(this._, arguments);
    },
    setMonth: function() {
      d3_time_prototype.setUTCMonth.apply(this._, arguments);
    },
    setSeconds: function() {
      d3_time_prototype.setUTCSeconds.apply(this._, arguments);
    },
    setTime: function() {
      d3_time_prototype.setTime.apply(this._, arguments);
    }
  };
  var d3_time_prototype = Date.prototype;
  function d3_time_interval(local, step, number) {
    function round(date) {
      var d0 = local(date), d1 = offset(d0, 1);
      return date - d0 < d1 - date ? d0 : d1;
    }
    function ceil(date) {
      step(date = local(new d3_date(date - 1)), 1);
      return date;
    }
    function offset(date, k) {
      step(date = new d3_date(+date), k);
      return date;
    }
    function range(t0, t1, dt) {
      var time = ceil(t0), times = [];
      if (dt > 1) {
        while (time < t1) {
          if (!(number(time) % dt)) times.push(new Date(+time));
          step(time, 1);
        }
      } else {
        while (time < t1) times.push(new Date(+time)), step(time, 1);
      }
      return times;
    }
    function range_utc(t0, t1, dt) {
      try {
        d3_date = d3_date_utc;
        var utc = new d3_date_utc();
        utc._ = t0;
        return range(utc, t1, dt);
      } finally {
        d3_date = Date;
      }
    }
    local.floor = local;
    local.round = round;
    local.ceil = ceil;
    local.offset = offset;
    local.range = range;
    var utc = local.utc = d3_time_interval_utc(local);
    utc.floor = utc;
    utc.round = d3_time_interval_utc(round);
    utc.ceil = d3_time_interval_utc(ceil);
    utc.offset = d3_time_interval_utc(offset);
    utc.range = range_utc;
    return local;
  }
  function d3_time_interval_utc(method) {
    return function(date, k) {
      try {
        d3_date = d3_date_utc;
        var utc = new d3_date_utc();
        utc._ = date;
        return method(utc, k)._;
      } finally {
        d3_date = Date;
      }
    };
  }
  d3_time.year = d3_time_interval(function(date) {
    date = d3_time.day(date);
    date.setMonth(0, 1);
    return date;
  }, function(date, offset) {
    date.setFullYear(date.getFullYear() + offset);
  }, function(date) {
    return date.getFullYear();
  });
  d3_time.years = d3_time.year.range;
  d3_time.years.utc = d3_time.year.utc.range;
  d3_time.day = d3_time_interval(function(date) {
    var day = new d3_date(2e3, 0);
    day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
    return day;
  }, function(date, offset) {
    date.setDate(date.getDate() + offset);
  }, function(date) {
    return date.getDate() - 1;
  });
  d3_time.days = d3_time.day.range;
  d3_time.days.utc = d3_time.day.utc.range;
  d3_time.dayOfYear = function(date) {
    var year = d3_time.year(date);
    return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5);
  };
  [ "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" ].forEach(function(day, i) {
    i = 7 - i;
    var interval = d3_time[day] = d3_time_interval(function(date) {
      (date = d3_time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7);
      return date;
    }, function(date, offset) {
      date.setDate(date.getDate() + Math.floor(offset) * 7);
    }, function(date) {
      var day = d3_time.year(date).getDay();
      return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i);
    });
    d3_time[day + "s"] = interval.range;
    d3_time[day + "s"].utc = interval.utc.range;
    d3_time[day + "OfYear"] = function(date) {
      var day = d3_time.year(date).getDay();
      return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7);
    };
  });
  d3_time.week = d3_time.sunday;
  d3_time.weeks = d3_time.sunday.range;
  d3_time.weeks.utc = d3_time.sunday.utc.range;
  d3_time.weekOfYear = d3_time.sundayOfYear;
  function d3_locale_timeFormat(locale) {
    var locale_dateTime = locale.dateTime, locale_date = locale.date, locale_time = locale.time, locale_periods = locale.periods, locale_days = locale.days, locale_shortDays = locale.shortDays, locale_months = locale.months, locale_shortMonths = locale.shortMonths;
    function d3_time_format(template) {
      var n = template.length;
      function format(date) {
        var string = [], i = -1, j = 0, c, p, f;
        while (++i < n) {
          if (template.charCodeAt(i) === 37) {
            string.push(template.slice(j, i));
            if ((p = d3_time_formatPads[c = template.charAt(++i)]) != null) c = template.charAt(++i);
            if (f = d3_time_formats[c]) c = f(date, p == null ? c === "e" ? " " : "0" : p);
            string.push(c);
            j = i + 1;
          }
        }
        string.push(template.slice(j, i));
        return string.join("");
      }
      format.parse = function(string) {
        var d = {
          y: 1900,
          m: 0,
          d: 1,
          H: 0,
          M: 0,
          S: 0,
          L: 0,
          Z: null
        }, i = d3_time_parse(d, template, string, 0);
        if (i != string.length) return null;
        if ("p" in d) d.H = d.H % 12 + d.p * 12;
        var localZ = d.Z != null && d3_date !== d3_date_utc, date = new (localZ ? d3_date_utc : d3_date)();
        if ("j" in d) date.setFullYear(d.y, 0, d.j); else if ("W" in d || "U" in d) {
          if (!("w" in d)) d.w = "W" in d ? 1 : 0;
          date.setFullYear(d.y, 0, 1);
          date.setFullYear(d.y, 0, "W" in d ? (d.w + 6) % 7 + d.W * 7 - (date.getDay() + 5) % 7 : d.w + d.U * 7 - (date.getDay() + 6) % 7);
        } else date.setFullYear(d.y, d.m, d.d);
        date.setHours(d.H + (d.Z / 100 | 0), d.M + d.Z % 100, d.S, d.L);
        return localZ ? date._ : date;
      };
      format.toString = function() {
        return template;
      };
      return format;
    }
    function d3_time_parse(date, template, string, j) {
      var c, p, t, i = 0, n = template.length, m = string.length;
      while (i < n) {
        if (j >= m) return -1;
        c = template.charCodeAt(i++);
        if (c === 37) {
          t = template.charAt(i++);
          p = d3_time_parsers[t in d3_time_formatPads ? template.charAt(i++) : t];
          if (!p || (j = p(date, string, j)) < 0) return -1;
        } else if (c != string.charCodeAt(j++)) {
          return -1;
        }
      }
      return j;
    }
    d3_time_format.utc = function(template) {
      var local = d3_time_format(template);
      function format(date) {
        try {
          d3_date = d3_date_utc;
          var utc = new d3_date();
          utc._ = date;
          return local(utc);
        } finally {
          d3_date = Date;
        }
      }
      format.parse = function(string) {
        try {
          d3_date = d3_date_utc;
          var date = local.parse(string);
          return date && date._;
        } finally {
          d3_date = Date;
        }
      };
      format.toString = local.toString;
      return format;
    };
    d3_time_format.multi = d3_time_format.utc.multi = d3_time_formatMulti;
    var d3_time_periodLookup = d3.map(), d3_time_dayRe = d3_time_formatRe(locale_days), d3_time_dayLookup = d3_time_formatLookup(locale_days), d3_time_dayAbbrevRe = d3_time_formatRe(locale_shortDays), d3_time_dayAbbrevLookup = d3_time_formatLookup(locale_shortDays), d3_time_monthRe = d3_time_formatRe(locale_months), d3_time_monthLookup = d3_time_formatLookup(locale_months), d3_time_monthAbbrevRe = d3_time_formatRe(locale_shortMonths), d3_time_monthAbbrevLookup = d3_time_formatLookup(locale_shortMonths);
    locale_periods.forEach(function(p, i) {
      d3_time_periodLookup.set(p.toLowerCase(), i);
    });
    var d3_time_formats = {
      a: function(d) {
        return locale_shortDays[d.getDay()];
      },
      A: function(d) {
        return locale_days[d.getDay()];
      },
      b: function(d) {
        return locale_shortMonths[d.getMonth()];
      },
      B: function(d) {
        return locale_months[d.getMonth()];
      },
      c: d3_time_format(locale_dateTime),
      d: function(d, p) {
        return d3_time_formatPad(d.getDate(), p, 2);
      },
      e: function(d, p) {
        return d3_time_formatPad(d.getDate(), p, 2);
      },
      H: function(d, p) {
        return d3_time_formatPad(d.getHours(), p, 2);
      },
      I: function(d, p) {
        return d3_time_formatPad(d.getHours() % 12 || 12, p, 2);
      },
      j: function(d, p) {
        return d3_time_formatPad(1 + d3_time.dayOfYear(d), p, 3);
      },
      L: function(d, p) {
        return d3_time_formatPad(d.getMilliseconds(), p, 3);
      },
      m: function(d, p) {
        return d3_time_formatPad(d.getMonth() + 1, p, 2);
      },
      M: function(d, p) {
        return d3_time_formatPad(d.getMinutes(), p, 2);
      },
      p: function(d) {
        return locale_periods[+(d.getHours() >= 12)];
      },
      S: function(d, p) {
        return d3_time_formatPad(d.getSeconds(), p, 2);
      },
      U: function(d, p) {
        return d3_time_formatPad(d3_time.sundayOfYear(d), p, 2);
      },
      w: function(d) {
        return d.getDay();
      },
      W: function(d, p) {
        return d3_time_formatPad(d3_time.mondayOfYear(d), p, 2);
      },
      x: d3_time_format(locale_date),
      X: d3_time_format(locale_time),
      y: function(d, p) {
        return d3_time_formatPad(d.getFullYear() % 100, p, 2);
      },
      Y: function(d, p) {
        return d3_time_formatPad(d.getFullYear() % 1e4, p, 4);
      },
      Z: d3_time_zone,
      "%": function() {
        return "%";
      }
    };
    var d3_time_parsers = {
      a: d3_time_parseWeekdayAbbrev,
      A: d3_time_parseWeekday,
      b: d3_time_parseMonthAbbrev,
      B: d3_time_parseMonth,
      c: d3_time_parseLocaleFull,
      d: d3_time_parseDay,
      e: d3_time_parseDay,
      H: d3_time_parseHour24,
      I: d3_time_parseHour24,
      j: d3_time_parseDayOfYear,
      L: d3_time_parseMilliseconds,
      m: d3_time_parseMonthNumber,
      M: d3_time_parseMinutes,
      p: d3_time_parseAmPm,
      S: d3_time_parseSeconds,
      U: d3_time_parseWeekNumberSunday,
      w: d3_time_parseWeekdayNumber,
      W: d3_time_parseWeekNumberMonday,
      x: d3_time_parseLocaleDate,
      X: d3_time_parseLocaleTime,
      y: d3_time_parseYear,
      Y: d3_time_parseFullYear,
      Z: d3_time_parseZone,
      "%": d3_time_parseLiteralPercent
    };
    function d3_time_parseWeekdayAbbrev(date, string, i) {
      d3_time_dayAbbrevRe.lastIndex = 0;
      var n = d3_time_dayAbbrevRe.exec(string.slice(i));
      return n ? (date.w = d3_time_dayAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
    }
    function d3_time_parseWeekday(date, string, i) {
      d3_time_dayRe.lastIndex = 0;
      var n = d3_time_dayRe.exec(string.slice(i));
      return n ? (date.w = d3_time_dayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
    }
    function d3_time_parseMonthAbbrev(date, string, i) {
      d3_time_monthAbbrevRe.lastIndex = 0;
      var n = d3_time_monthAbbrevRe.exec(string.slice(i));
      return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
    }
    function d3_time_parseMonth(date, string, i) {
      d3_time_monthRe.lastIndex = 0;
      var n = d3_time_monthRe.exec(string.slice(i));
      return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
    }
    function d3_time_parseLocaleFull(date, string, i) {
      return d3_time_parse(date, d3_time_formats.c.toString(), string, i);
    }
    function d3_time_parseLocaleDate(date, string, i) {
      return d3_time_parse(date, d3_time_formats.x.toString(), string, i);
    }
    function d3_time_parseLocaleTime(date, string, i) {
      return d3_time_parse(date, d3_time_formats.X.toString(), string, i);
    }
    function d3_time_parseAmPm(date, string, i) {
      var n = d3_time_periodLookup.get(string.slice(i, i += 2).toLowerCase());
      return n == null ? -1 : (date.p = n, i);
    }
    return d3_time_format;
  }
  var d3_time_formatPads = {
    "-": "",
    _: " ",
    "0": "0"
  }, d3_time_numberRe = /^\s*\d+/, d3_time_percentRe = /^%/;
  function d3_time_formatPad(value, fill, width) {
    var sign = value < 0 ? "-" : "", string = (sign ? -value : value) + "", length = string.length;
    return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string);
  }
  function d3_time_formatRe(names) {
    return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i");
  }
  function d3_time_formatLookup(names) {
    var map = new d3_Map(), i = -1, n = names.length;
    while (++i < n) map.set(names[i].toLowerCase(), i);
    return map;
  }
  function d3_time_parseWeekdayNumber(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 1));
    return n ? (date.w = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseWeekNumberSunday(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i));
    return n ? (date.U = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseWeekNumberMonday(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i));
    return n ? (date.W = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseFullYear(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 4));
    return n ? (date.y = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseYear(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
    return n ? (date.y = d3_time_expandYear(+n[0]), i + n[0].length) : -1;
  }
  function d3_time_parseZone(date, string, i) {
    return /^[+-]\d{4}$/.test(string = string.slice(i, i + 5)) ? (date.Z = -string, 
    i + 5) : -1;
  }
  function d3_time_expandYear(d) {
    return d + (d > 68 ? 1900 : 2e3);
  }
  function d3_time_parseMonthNumber(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
    return n ? (date.m = n[0] - 1, i + n[0].length) : -1;
  }
  function d3_time_parseDay(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
    return n ? (date.d = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseDayOfYear(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 3));
    return n ? (date.j = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseHour24(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
    return n ? (date.H = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseMinutes(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
    return n ? (date.M = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseSeconds(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
    return n ? (date.S = +n[0], i + n[0].length) : -1;
  }
  function d3_time_parseMilliseconds(date, string, i) {
    d3_time_numberRe.lastIndex = 0;
    var n = d3_time_numberRe.exec(string.slice(i, i + 3));
    return n ? (date.L = +n[0], i + n[0].length) : -1;
  }
  function d3_time_zone(d) {
    var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = abs(z) / 60 | 0, zm = abs(z) % 60;
    return zs + d3_time_formatPad(zh, "0", 2) + d3_time_formatPad(zm, "0", 2);
  }
  function d3_time_parseLiteralPercent(date, string, i) {
    d3_time_percentRe.lastIndex = 0;
    var n = d3_time_percentRe.exec(string.slice(i, i + 1));
    return n ? i + n[0].length : -1;
  }
  function d3_time_formatMulti(formats) {
    var n = formats.length, i = -1;
    while (++i < n) formats[i][0] = this(formats[i][0]);
    return function(date) {
      var i = 0, f = formats[i];
      while (!f[1](date)) f = formats[++i];
      return f[0](date);
    };
  }
  d3.locale = function(locale) {
    return {
      numberFormat: d3_locale_numberFormat(locale),
      timeFormat: d3_locale_timeFormat(locale)
    };
  };
  var d3_locale_enUS = d3.locale({
    decimal: ".",
    thousands: ",",
    grouping: [ 3 ],
    currency: [ "$", "" ],
    dateTime: "%a %b %e %X %Y",
    date: "%m/%d/%Y",
    time: "%H:%M:%S",
    periods: [ "AM", "PM" ],
    days: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ],
    shortDays: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ],
    months: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ],
    shortMonths: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]
  });
  d3.format = d3_locale_enUS.numberFormat;
  d3.geo = {};
  function d3_adder() {}
  d3_adder.prototype = {
    s: 0,
    t: 0,
    add: function(y) {
      d3_adderSum(y, this.t, d3_adderTemp);
      d3_adderSum(d3_adderTemp.s, this.s, this);
      if (this.s) this.t += d3_adderTemp.t; else this.s = d3_adderTemp.t;
    },
    reset: function() {
      this.s = this.t = 0;
    },
    valueOf: function() {
      return this.s;
    }
  };
  var d3_adderTemp = new d3_adder();
  function d3_adderSum(a, b, o) {
    var x = o.s = a + b, bv = x - a, av = x - bv;
    o.t = a - av + (b - bv);
  }
  d3.geo.stream = function(object, listener) {
    if (object && d3_geo_streamObjectType.hasOwnProperty(object.type)) {
      d3_geo_streamObjectType[object.type](object, listener);
    } else {
      d3_geo_streamGeometry(object, listener);
    }
  };
  function d3_geo_streamGeometry(geometry, listener) {
    if (geometry && d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) {
      d3_geo_streamGeometryType[geometry.type](geometry, listener);
    }
  }
  var d3_geo_streamObjectType = {
    Feature: function(feature, listener) {
      d3_geo_streamGeometry(feature.geometry, listener);
    },
    FeatureCollection: function(object, listener) {
      var features = object.features, i = -1, n = features.length;
      while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener);
    }
  };
  var d3_geo_streamGeometryType = {
    Sphere: function(object, listener) {
      listener.sphere();
    },
    Point: function(object, listener) {
      object = object.coordinates;
      listener.point(object[0], object[1], object[2]);
    },
    MultiPoint: function(object, listener) {
      var coordinates = object.coordinates, i = -1, n = coordinates.length;
      while (++i < n) object = coordinates[i], listener.point(object[0], object[1], object[2]);
    },
    LineString: function(object, listener) {
      d3_geo_streamLine(object.coordinates, listener, 0);
    },
    MultiLineString: function(object, listener) {
      var coordinates = object.coordinates, i = -1, n = coordinates.length;
      while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0);
    },
    Polygon: function(object, listener) {
      d3_geo_streamPolygon(object.coordinates, listener);
    },
    MultiPolygon: function(object, listener) {
      var coordinates = object.coordinates, i = -1, n = coordinates.length;
      while (++i < n) d3_geo_streamPolygon(coordinates[i], listener);
    },
    GeometryCollection: function(object, listener) {
      var geometries = object.geometries, i = -1, n = geometries.length;
      while (++i < n) d3_geo_streamGeometry(geometries[i], listener);
    }
  };
  function d3_geo_streamLine(coordinates, listener, closed) {
    var i = -1, n = coordinates.length - closed, coordinate;
    listener.lineStart();
    while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1], coordinate[2]);
    listener.lineEnd();
  }
  function d3_geo_streamPolygon(coordinates, listener) {
    var i = -1, n = coordinates.length;
    listener.polygonStart();
    while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1);
    listener.polygonEnd();
  }
  d3.geo.area = function(object) {
    d3_geo_areaSum = 0;
    d3.geo.stream(object, d3_geo_area);
    return d3_geo_areaSum;
  };
  var d3_geo_areaSum, d3_geo_areaRingSum = new d3_adder();
  var d3_geo_area = {
    sphere: function() {
      d3_geo_areaSum += 4 * π;
    },
    point: d3_noop,
    lineStart: d3_noop,
    lineEnd: d3_noop,
    polygonStart: function() {
      d3_geo_areaRingSum.reset();
      d3_geo_area.lineStart = d3_geo_areaRingStart;
    },
    polygonEnd: function() {
      var area = 2 * d3_geo_areaRingSum;
      d3_geo_areaSum += area < 0 ? 4 * π + area : area;
      d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop;
    }
  };
  function d3_geo_areaRingStart() {
    var λ00, φ00, λ0, cosφ0, sinφ0;
    d3_geo_area.point = function(λ, φ) {
      d3_geo_area.point = nextPoint;
      λ0 = (λ00 = λ) * d3_radians, cosφ0 = Math.cos(φ = (φ00 = φ) * d3_radians / 2 + π / 4), 
      sinφ0 = Math.sin(φ);
    };
    function nextPoint(λ, φ) {
      λ *= d3_radians;
      φ = φ * d3_radians / 2 + π / 4;
      var dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, cosφ = Math.cos(φ), sinφ = Math.sin(φ), k = sinφ0 * sinφ, u = cosφ0 * cosφ + k * Math.cos(adλ), v = k * sdλ * Math.sin(adλ);
      d3_geo_areaRingSum.add(Math.atan2(v, u));
      λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ;
    }
    d3_geo_area.lineEnd = function() {
      nextPoint(λ00, φ00);
    };
  }
  function d3_geo_cartesian(spherical) {
    var λ = spherical[0], φ = spherical[1], cosφ = Math.cos(φ);
    return [ cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ) ];
  }
  function d3_geo_cartesianDot(a, b) {
    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
  }
  function d3_geo_cartesianCross(a, b) {
    return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ];
  }
  function d3_geo_cartesianAdd(a, b) {
    a[0] += b[0];
    a[1] += b[1];
    a[2] += b[2];
  }
  function d3_geo_cartesianScale(vector, k) {
    return [ vector[0] * k, vector[1] * k, vector[2] * k ];
  }
  function d3_geo_cartesianNormalize(d) {
    var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
    d[0] /= l;
    d[1] /= l;
    d[2] /= l;
  }
  function d3_geo_spherical(cartesian) {
    return [ Math.atan2(cartesian[1], cartesian[0]), d3_asin(cartesian[2]) ];
  }
  function d3_geo_sphericalEqual(a, b) {
    return abs(a[0] - b[0]) < ε && abs(a[1] - b[1]) < ε;
  }
  d3.geo.bounds = function() {
    var λ0, φ0, λ1, φ1, λ_, λ__, φ__, p0, dλSum, ranges, range;
    var bound = {
      point: point,
      lineStart: lineStart,
      lineEnd: lineEnd,
      polygonStart: function() {
        bound.point = ringPoint;
        bound.lineStart = ringStart;
        bound.lineEnd = ringEnd;
        dλSum = 0;
        d3_geo_area.polygonStart();
      },
      polygonEnd: function() {
        d3_geo_area.polygonEnd();
        bound.point = point;
        bound.lineStart = lineStart;
        bound.lineEnd = lineEnd;
        if (d3_geo_areaRingSum < 0) λ0 = -(λ1 = 180), φ0 = -(φ1 = 90); else if (dλSum > ε) φ1 = 90; else if (dλSum < -ε) φ0 = -90;
        range[0] = λ0, range[1] = λ1;
      }
    };
    function point(λ, φ) {
      ranges.push(range = [ λ0 = λ, λ1 = λ ]);
      if (φ < φ0) φ0 = φ;
      if (φ > φ1) φ1 = φ;
    }
    function linePoint(λ, φ) {
      var p = d3_geo_cartesian([ λ * d3_radians, φ * d3_radians ]);
      if (p0) {
        var normal = d3_geo_cartesianCross(p0, p), equatorial = [ normal[1], -normal[0], 0 ], inflection = d3_geo_cartesianCross(equatorial, normal);
        d3_geo_cartesianNormalize(inflection);
        inflection = d3_geo_spherical(inflection);
        var dλ = λ - λ_, s = dλ > 0 ? 1 : -1, λi = inflection[0] * d3_degrees * s, antimeridian = abs(dλ) > 180;
        if (antimeridian ^ (s * λ_ < λi && λi < s * λ)) {
          var φi = inflection[1] * d3_degrees;
          if (φi > φ1) φ1 = φi;
        } else if (λi = (λi + 360) % 360 - 180, antimeridian ^ (s * λ_ < λi && λi < s * λ)) {
          var φi = -inflection[1] * d3_degrees;
          if (φi < φ0) φ0 = φi;
        } else {
          if (φ < φ0) φ0 = φ;
          if (φ > φ1) φ1 = φ;
        }
        if (antimeridian) {
          if (λ < λ_) {
            if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ;
          } else {
            if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ;
          }
        } else {
          if (λ1 >= λ0) {
            if (λ < λ0) λ0 = λ;
            if (λ > λ1) λ1 = λ;
          } else {
            if (λ > λ_) {
              if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ;
            } else {
              if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ;
            }
          }
        }
      } else {
        point(λ, φ);
      }
      p0 = p, λ_ = λ;
    }
    function lineStart() {
      bound.point = linePoint;
    }
    function lineEnd() {
      range[0] = λ0, range[1] = λ1;
      bound.point = point;
      p0 = null;
    }
    function ringPoint(λ, φ) {
      if (p0) {
        var dλ = λ - λ_;
        dλSum += abs(dλ) > 180 ? dλ + (dλ > 0 ? 360 : -360) : dλ;
      } else λ__ = λ, φ__ = φ;
      d3_geo_area.point(λ, φ);
      linePoint(λ, φ);
    }
    function ringStart() {
      d3_geo_area.lineStart();
    }
    function ringEnd() {
      ringPoint(λ__, φ__);
      d3_geo_area.lineEnd();
      if (abs(dλSum) > ε) λ0 = -(λ1 = 180);
      range[0] = λ0, range[1] = λ1;
      p0 = null;
    }
    function angle(λ0, λ1) {
      return (λ1 -= λ0) < 0 ? λ1 + 360 : λ1;
    }
    function compareRanges(a, b) {
      return a[0] - b[0];
    }
    function withinRange(x, range) {
      return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x;
    }
    return function(feature) {
      φ1 = λ1 = -(λ0 = φ0 = Infinity);
      ranges = [];
      d3.geo.stream(feature, bound);
      var n = ranges.length;
      if (n) {
        ranges.sort(compareRanges);
        for (var i = 1, a = ranges[0], b, merged = [ a ]; i < n; ++i) {
          b = ranges[i];
          if (withinRange(b[0], a) || withinRange(b[1], a)) {
            if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1];
            if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0];
          } else {
            merged.push(a = b);
          }
        }
        var best = -Infinity, dλ;
        for (var n = merged.length - 1, i = 0, a = merged[n], b; i <= n; a = b, ++i) {
          b = merged[i];
          if ((dλ = angle(a[1], b[0])) > best) best = dλ, λ0 = b[0], λ1 = a[1];
        }
      }
      ranges = range = null;
      return λ0 === Infinity || φ0 === Infinity ? [ [ NaN, NaN ], [ NaN, NaN ] ] : [ [ λ0, φ0 ], [ λ1, φ1 ] ];
    };
  }();
  d3.geo.centroid = function(object) {
    d3_geo_centroidW0 = d3_geo_centroidW1 = d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0;
    d3.geo.stream(object, d3_geo_centroid);
    var x = d3_geo_centroidX2, y = d3_geo_centroidY2, z = d3_geo_centroidZ2, m = x * x + y * y + z * z;
    if (m < ε2) {
      x = d3_geo_centroidX1, y = d3_geo_centroidY1, z = d3_geo_centroidZ1;
      if (d3_geo_centroidW1 < ε) x = d3_geo_centroidX0, y = d3_geo_centroidY0, z = d3_geo_centroidZ0;
      m = x * x + y * y + z * z;
      if (m < ε2) return [ NaN, NaN ];
    }
    return [ Math.atan2(y, x) * d3_degrees, d3_asin(z / Math.sqrt(m)) * d3_degrees ];
  };
  var d3_geo_centroidW0, d3_geo_centroidW1, d3_geo_centroidX0, d3_geo_centroidY0, d3_geo_centroidZ0, d3_geo_centroidX1, d3_geo_centroidY1, d3_geo_centroidZ1, d3_geo_centroidX2, d3_geo_centroidY2, d3_geo_centroidZ2;
  var d3_geo_centroid = {
    sphere: d3_noop,
    point: d3_geo_centroidPoint,
    lineStart: d3_geo_centroidLineStart,
    lineEnd: d3_geo_centroidLineEnd,
    polygonStart: function() {
      d3_geo_centroid.lineStart = d3_geo_centroidRingStart;
    },
    polygonEnd: function() {
      d3_geo_centroid.lineStart = d3_geo_centroidLineStart;
    }
  };
  function d3_geo_centroidPoint(λ, φ) {
    λ *= d3_radians;
    var cosφ = Math.cos(φ *= d3_radians);
    d3_geo_centroidPointXYZ(cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ));
  }
  function d3_geo_centroidPointXYZ(x, y, z) {
    ++d3_geo_centroidW0;
    d3_geo_centroidX0 += (x - d3_geo_centroidX0) / d3_geo_centroidW0;
    d3_geo_centroidY0 += (y - d3_geo_centroidY0) / d3_geo_centroidW0;
    d3_geo_centroidZ0 += (z - d3_geo_centroidZ0) / d3_geo_centroidW0;
  }
  function d3_geo_centroidLineStart() {
    var x0, y0, z0;
    d3_geo_centroid.point = function(λ, φ) {
      λ *= d3_radians;
      var cosφ = Math.cos(φ *= d3_radians);
      x0 = cosφ * Math.cos(λ);
      y0 = cosφ * Math.sin(λ);
      z0 = Math.sin(φ);
      d3_geo_centroid.point = nextPoint;
      d3_geo_centroidPointXYZ(x0, y0, z0);
    };
    function nextPoint(λ, φ) {
      λ *= d3_radians;
      var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), w = Math.atan2(Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z);
      d3_geo_centroidW1 += w;
      d3_geo_centroidX1 += w * (x0 + (x0 = x));
      d3_geo_centroidY1 += w * (y0 + (y0 = y));
      d3_geo_centroidZ1 += w * (z0 + (z0 = z));
      d3_geo_centroidPointXYZ(x0, y0, z0);
    }
  }
  function d3_geo_centroidLineEnd() {
    d3_geo_centroid.point = d3_geo_centroidPoint;
  }
  function d3_geo_centroidRingStart() {
    var λ00, φ00, x0, y0, z0;
    d3_geo_centroid.point = function(λ, φ) {
      λ00 = λ, φ00 = φ;
      d3_geo_centroid.point = nextPoint;
      λ *= d3_radians;
      var cosφ = Math.cos(φ *= d3_radians);
      x0 = cosφ * Math.cos(λ);
      y0 = cosφ * Math.sin(λ);
      z0 = Math.sin(φ);
      d3_geo_centroidPointXYZ(x0, y0, z0);
    };
    d3_geo_centroid.lineEnd = function() {
      nextPoint(λ00, φ00);
      d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd;
      d3_geo_centroid.point = d3_geo_centroidPoint;
    };
    function nextPoint(λ, φ) {
      λ *= d3_radians;
      var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), cx = y0 * z - z0 * y, cy = z0 * x - x0 * z, cz = x0 * y - y0 * x, m = Math.sqrt(cx * cx + cy * cy + cz * cz), u = x0 * x + y0 * y + z0 * z, v = m && -d3_acos(u) / m, w = Math.atan2(m, u);
      d3_geo_centroidX2 += v * cx;
      d3_geo_centroidY2 += v * cy;
      d3_geo_centroidZ2 += v * cz;
      d3_geo_centroidW1 += w;
      d3_geo_centroidX1 += w * (x0 + (x0 = x));
      d3_geo_centroidY1 += w * (y0 + (y0 = y));
      d3_geo_centroidZ1 += w * (z0 + (z0 = z));
      d3_geo_centroidPointXYZ(x0, y0, z0);
    }
  }
  function d3_geo_compose(a, b) {
    function compose(x, y) {
      return x = a(x, y), b(x[0], x[1]);
    }
    if (a.invert && b.invert) compose.invert = function(x, y) {
      return x = b.invert(x, y), x && a.invert(x[0], x[1]);
    };
    return compose;
  }
  function d3_true() {
    return true;
  }
  function d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener) {
    var subject = [], clip = [];
    segments.forEach(function(segment) {
      if ((n = segment.length - 1) <= 0) return;
      var n, p0 = segment[0], p1 = segment[n];
      if (d3_geo_sphericalEqual(p0, p1)) {
        listener.lineStart();
        for (var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]);
        listener.lineEnd();
        return;
      }
      var a = new d3_geo_clipPolygonIntersection(p0, segment, null, true), b = new d3_geo_clipPolygonIntersection(p0, null, a, false);
      a.o = b;
      subject.push(a);
      clip.push(b);
      a = new d3_geo_clipPolygonIntersection(p1, segment, null, false);
      b = new d3_geo_clipPolygonIntersection(p1, null, a, true);
      a.o = b;
      subject.push(a);
      clip.push(b);
    });
    clip.sort(compare);
    d3_geo_clipPolygonLinkCircular(subject);
    d3_geo_clipPolygonLinkCircular(clip);
    if (!subject.length) return;
    for (var i = 0, entry = clipStartInside, n = clip.length; i < n; ++i) {
      clip[i].e = entry = !entry;
    }
    var start = subject[0], points, point;
    while (1) {
      var current = start, isSubject = true;
      while (current.v) if ((current = current.n) === start) return;
      points = current.z;
      listener.lineStart();
      do {
        current.v = current.o.v = true;
        if (current.e) {
          if (isSubject) {
            for (var i = 0, n = points.length; i < n; ++i) listener.point((point = points[i])[0], point[1]);
          } else {
            interpolate(current.x, current.n.x, 1, listener);
          }
          current = current.n;
        } else {
          if (isSubject) {
            points = current.p.z;
            for (var i = points.length - 1; i >= 0; --i) listener.point((point = points[i])[0], point[1]);
          } else {
            interpolate(current.x, current.p.x, -1, listener);
          }
          current = current.p;
        }
        current = current.o;
        points = current.z;
        isSubject = !isSubject;
      } while (!current.v);
      listener.lineEnd();
    }
  }
  function d3_geo_clipPolygonLinkCircular(array) {
    if (!(n = array.length)) return;
    var n, i = 0, a = array[0], b;
    while (++i < n) {
      a.n = b = array[i];
      b.p = a;
      a = b;
    }
    a.n = b = array[0];
    b.p = a;
  }
  function d3_geo_clipPolygonIntersection(point, points, other, entry) {
    this.x = point;
    this.z = points;
    this.o = other;
    this.e = entry;
    this.v = false;
    this.n = this.p = null;
  }
  function d3_geo_clip(pointVisible, clipLine, interpolate, clipStart) {
    return function(rotate, listener) {
      var line = clipLine(listener), rotatedClipStart = rotate.invert(clipStart[0], clipStart[1]);
      var clip = {
        point: point,
        lineStart: lineStart,
        lineEnd: lineEnd,
        polygonStart: function() {
          clip.point = pointRing;
          clip.lineStart = ringStart;
          clip.lineEnd = ringEnd;
          segments = [];
          polygon = [];
        },
        polygonEnd: function() {
          clip.point = point;
          clip.lineStart = lineStart;
          clip.lineEnd = lineEnd;
          segments = d3.merge(segments);
          var clipStartInside = d3_geo_pointInPolygon(rotatedClipStart, polygon);
          if (segments.length) {
            if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
            d3_geo_clipPolygon(segments, d3_geo_clipSort, clipStartInside, interpolate, listener);
          } else if (clipStartInside) {
            if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
            listener.lineStart();
            interpolate(null, null, 1, listener);
            listener.lineEnd();
          }
          if (polygonStarted) listener.polygonEnd(), polygonStarted = false;
          segments = polygon = null;
        },
        sphere: function() {
          listener.polygonStart();
          listener.lineStart();
          interpolate(null, null, 1, listener);
          listener.lineEnd();
          listener.polygonEnd();
        }
      };
      function point(λ, φ) {
        var point = rotate(λ, φ);
        if (pointVisible(λ = point[0], φ = point[1])) listener.point(λ, φ);
      }
      function pointLine(λ, φ) {
        var point = rotate(λ, φ);
        line.point(point[0], point[1]);
      }
      function lineStart() {
        clip.point = pointLine;
        line.lineStart();
      }
      function lineEnd() {
        clip.point = point;
        line.lineEnd();
      }
      var segments;
      var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), polygonStarted = false, polygon, ring;
      function pointRing(λ, φ) {
        ring.push([ λ, φ ]);
        var point = rotate(λ, φ);
        ringListener.point(point[0], point[1]);
      }
      function ringStart() {
        ringListener.lineStart();
        ring = [];
      }
      function ringEnd() {
        pointRing(ring[0][0], ring[0][1]);
        ringListener.lineEnd();
        var clean = ringListener.clean(), ringSegments = buffer.buffer(), segment, n = ringSegments.length;
        ring.pop();
        polygon.push(ring);
        ring = null;
        if (!n) return;
        if (clean & 1) {
          segment = ringSegments[0];
          var n = segment.length - 1, i = -1, point;
          if (n > 0) {
            if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
            listener.lineStart();
            while (++i < n) listener.point((point = segment[i])[0], point[1]);
            listener.lineEnd();
          }
          return;
        }
        if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));
        segments.push(ringSegments.filter(d3_geo_clipSegmentLength1));
      }
      return clip;
    };
  }
  function d3_geo_clipSegmentLength1(segment) {
    return segment.length > 1;
  }
  function d3_geo_clipBufferListener() {
    var lines = [], line;
    return {
      lineStart: function() {
        lines.push(line = []);
      },
      point: function(λ, φ) {
        line.push([ λ, φ ]);
      },
      lineEnd: d3_noop,
      buffer: function() {
        var buffer = lines;
        lines = [];
        line = null;
        return buffer;
      },
      rejoin: function() {
        if (lines.length > 1) lines.push(lines.pop().concat(lines.shift()));
      }
    };
  }
  function d3_geo_clipSort(a, b) {
    return ((a = a.x)[0] < 0 ? a[1] - halfπ - ε : halfπ - a[1]) - ((b = b.x)[0] < 0 ? b[1] - halfπ - ε : halfπ - b[1]);
  }
  var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate, [ -π, -π / 2 ]);
  function d3_geo_clipAntimeridianLine(listener) {
    var λ0 = NaN, φ0 = NaN, sλ0 = NaN, clean;
    return {
      lineStart: function() {
        listener.lineStart();
        clean = 1;
      },
      point: function(λ1, φ1) {
        var sλ1 = λ1 > 0 ? π : -π, dλ = abs(λ1 - λ0);
        if (abs(dλ - π) < ε) {
          listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? halfπ : -halfπ);
          listener.point(sλ0, φ0);
          listener.lineEnd();
          listener.lineStart();
          listener.point(sλ1, φ0);
          listener.point(λ1, φ0);
          clean = 0;
        } else if (sλ0 !== sλ1 && dλ >= π) {
          if (abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε;
          if (abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε;
          φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1);
          listener.point(sλ0, φ0);
          listener.lineEnd();
          listener.lineStart();
          listener.point(sλ1, φ0);
          clean = 0;
        }
        listener.point(λ0 = λ1, φ0 = φ1);
        sλ0 = sλ1;
      },
      lineEnd: function() {
        listener.lineEnd();
        λ0 = φ0 = NaN;
      },
      clean: function() {
        return 2 - clean;
      }
    };
  }
  function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) {
    var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1);
    return abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2;
  }
  function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) {
    var φ;
    if (from == null) {
      φ = direction * halfπ;
      listener.point(-π, φ);
      listener.point(0, φ);
      listener.point(π, φ);
      listener.point(π, 0);
      listener.point(π, -φ);
      listener.point(0, -φ);
      listener.point(-π, -φ);
      listener.point(-π, 0);
      listener.point(-π, φ);
    } else if (abs(from[0] - to[0]) > ε) {
      var s = from[0] < to[0] ? π : -π;
      φ = direction * s / 2;
      listener.point(-s, φ);
      listener.point(0, φ);
      listener.point(s, φ);
    } else {
      listener.point(to[0], to[1]);
    }
  }
  function d3_geo_pointInPolygon(point, polygon) {
    var meridian = point[0], parallel = point[1], meridianNormal = [ Math.sin(meridian), -Math.cos(meridian), 0 ], polarAngle = 0, winding = 0;
    d3_geo_areaRingSum.reset();
    for (var i = 0, n = polygon.length; i < n; ++i) {
      var ring = polygon[i], m = ring.length;
      if (!m) continue;
      var point0 = ring[0], λ0 = point0[0], φ0 = point0[1] / 2 + π / 4, sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), j = 1;
      while (true) {
        if (j === m) j = 0;
        point = ring[j];
        var λ = point[0], φ = point[1] / 2 + π / 4, sinφ = Math.sin(φ), cosφ = Math.cos(φ), dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, antimeridian = adλ > π, k = sinφ0 * sinφ;
        d3_geo_areaRingSum.add(Math.atan2(k * sdλ * Math.sin(adλ), cosφ0 * cosφ + k * Math.cos(adλ)));
        polarAngle += antimeridian ? dλ + sdλ * τ : dλ;
        if (antimeridian ^ λ0 >= meridian ^ λ >= meridian) {
          var arc = d3_geo_cartesianCross(d3_geo_cartesian(point0), d3_geo_cartesian(point));
          d3_geo_cartesianNormalize(arc);
          var intersection = d3_geo_cartesianCross(meridianNormal, arc);
          d3_geo_cartesianNormalize(intersection);
          var φarc = (antimeridian ^ dλ >= 0 ? -1 : 1) * d3_asin(intersection[2]);
          if (parallel > φarc || parallel === φarc && (arc[0] || arc[1])) {
            winding += antimeridian ^ dλ >= 0 ? 1 : -1;
          }
        }
        if (!j++) break;
        λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ, point0 = point;
      }
    }
    return (polarAngle < -ε || polarAngle < ε && d3_geo_areaRingSum < -ε) ^ winding & 1;
  }
  function d3_geo_clipCircle(radius) {
    var cr = Math.cos(radius), smallRadius = cr > 0, notHemisphere = abs(cr) > ε, interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians);
    return d3_geo_clip(visible, clipLine, interpolate, smallRadius ? [ 0, -radius ] : [ -π, radius - π ]);
    function visible(λ, φ) {
      return Math.cos(λ) * Math.cos(φ) > cr;
    }
    function clipLine(listener) {
      var point0, c0, v0, v00, clean;
      return {
        lineStart: function() {
          v00 = v0 = false;
          clean = 1;
        },
        point: function(λ, φ) {
          var point1 = [ λ, φ ], point2, v = visible(λ, φ), c = smallRadius ? v ? 0 : code(λ, φ) : v ? code(λ + (λ < 0 ? π : -π), φ) : 0;
          if (!point0 && (v00 = v0 = v)) listener.lineStart();
          if (v !== v0) {
            point2 = intersect(point0, point1);
            if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) {
              point1[0] += ε;
              point1[1] += ε;
              v = visible(point1[0], point1[1]);
            }
          }
          if (v !== v0) {
            clean = 0;
            if (v) {
              listener.lineStart();
              point2 = intersect(point1, point0);
              listener.point(point2[0], point2[1]);
            } else {
              point2 = intersect(point0, point1);
              listener.point(point2[0], point2[1]);
              listener.lineEnd();
            }
            point0 = point2;
          } else if (notHemisphere && point0 && smallRadius ^ v) {
            var t;
            if (!(c & c0) && (t = intersect(point1, point0, true))) {
              clean = 0;
              if (smallRadius) {
                listener.lineStart();
                listener.point(t[0][0], t[0][1]);
                listener.point(t[1][0], t[1][1]);
                listener.lineEnd();
              } else {
                listener.point(t[1][0], t[1][1]);
                listener.lineEnd();
                listener.lineStart();
                listener.point(t[0][0], t[0][1]);
              }
            }
          }
          if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) {
            listener.point(point1[0], point1[1]);
          }
          point0 = point1, v0 = v, c0 = c;
        },
        lineEnd: function() {
          if (v0) listener.lineEnd();
          point0 = null;
        },
        clean: function() {
          return clean | (v00 && v0) << 1;
        }
      };
    }
    function intersect(a, b, two) {
      var pa = d3_geo_cartesian(a), pb = d3_geo_cartesian(b);
      var n1 = [ 1, 0, 0 ], n2 = d3_geo_cartesianCross(pa, pb), n2n2 = d3_geo_cartesianDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2;
      if (!determinant) return !two && a;
      var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_cartesianCross(n1, n2), A = d3_geo_cartesianScale(n1, c1), B = d3_geo_cartesianScale(n2, c2);
      d3_geo_cartesianAdd(A, B);
      var u = n1xn2, w = d3_geo_cartesianDot(A, u), uu = d3_geo_cartesianDot(u, u), t2 = w * w - uu * (d3_geo_cartesianDot(A, A) - 1);
      if (t2 < 0) return;
      var t = Math.sqrt(t2), q = d3_geo_cartesianScale(u, (-w - t) / uu);
      d3_geo_cartesianAdd(q, A);
      q = d3_geo_spherical(q);
      if (!two) return q;
      var λ0 = a[0], λ1 = b[0], φ0 = a[1], φ1 = b[1], z;
      if (λ1 < λ0) z = λ0, λ0 = λ1, λ1 = z;
      var δλ = λ1 - λ0, polar = abs(δλ - π) < ε, meridian = polar || δλ < ε;
      if (!polar && φ1 < φ0) z = φ0, φ0 = φ1, φ1 = z;
      if (meridian ? polar ? φ0 + φ1 > 0 ^ q[1] < (abs(q[0] - λ0) < ε ? φ0 : φ1) : φ0 <= q[1] && q[1] <= φ1 : δλ > π ^ (λ0 <= q[0] && q[0] <= λ1)) {
        var q1 = d3_geo_cartesianScale(u, (-w + t) / uu);
        d3_geo_cartesianAdd(q1, A);
        return [ q, d3_geo_spherical(q1) ];
      }
    }
    function code(λ, φ) {
      var r = smallRadius ? radius : π - radius, code = 0;
      if (λ < -r) code |= 1; else if (λ > r) code |= 2;
      if (φ < -r) code |= 4; else if (φ > r) code |= 8;
      return code;
    }
  }
  function d3_geom_clipLine(x0, y0, x1, y1) {
    return function(line) {
      var a = line.a, b = line.b, ax = a.x, ay = a.y, bx = b.x, by = b.y, t0 = 0, t1 = 1, dx = bx - ax, dy = by - ay, r;
      r = x0 - ax;
      if (!dx && r > 0) return;
      r /= dx;
      if (dx < 0) {
        if (r < t0) return;
        if (r < t1) t1 = r;
      } else if (dx > 0) {
        if (r > t1) return;
        if (r > t0) t0 = r;
      }
      r = x1 - ax;
      if (!dx && r < 0) return;
      r /= dx;
      if (dx < 0) {
        if (r > t1) return;
        if (r > t0) t0 = r;
      } else if (dx > 0) {
        if (r < t0) return;
        if (r < t1) t1 = r;
      }
      r = y0 - ay;
      if (!dy && r > 0) return;
      r /= dy;
      if (dy < 0) {
        if (r < t0) return;
        if (r < t1) t1 = r;
      } else if (dy > 0) {
        if (r > t1) return;
        if (r > t0) t0 = r;
      }
      r = y1 - ay;
      if (!dy && r < 0) return;
      r /= dy;
      if (dy < 0) {
        if (r > t1) return;
        if (r > t0) t0 = r;
      } else if (dy > 0) {
        if (r < t0) return;
        if (r < t1) t1 = r;
      }
      if (t0 > 0) line.a = {
        x: ax + t0 * dx,
        y: ay + t0 * dy
      };
      if (t1 < 1) line.b = {
        x: ax + t1 * dx,
        y: ay + t1 * dy
      };
      return line;
    };
  }
  var d3_geo_clipExtentMAX = 1e9;
  d3.geo.clipExtent = function() {
    var x0, y0, x1, y1, stream, clip, clipExtent = {
      stream: function(output) {
        if (stream) stream.valid = false;
        stream = clip(output);
        stream.valid = true;
        return stream;
      },
      extent: function(_) {
        if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ];
        clip = d3_geo_clipExtent(x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]);
        if (stream) stream.valid = false, stream = null;
        return clipExtent;
      }
    };
    return clipExtent.extent([ [ 0, 0 ], [ 960, 500 ] ]);
  };
  function d3_geo_clipExtent(x0, y0, x1, y1) {
    return function(listener) {
      var listener_ = listener, bufferListener = d3_geo_clipBufferListener(), clipLine = d3_geom_clipLine(x0, y0, x1, y1), segments, polygon, ring;
      var clip = {
        point: point,
        lineStart: lineStart,
        lineEnd: lineEnd,
        polygonStart: function() {
          listener = bufferListener;
          segments = [];
          polygon = [];
          clean = true;
        },
        polygonEnd: function() {
          listener = listener_;
          segments = d3.merge(segments);
          var clipStartInside = insidePolygon([ x0, y1 ]), inside = clean && clipStartInside, visible = segments.length;
          if (inside || visible) {
            listener.polygonStart();
            if (inside) {
              listener.lineStart();
              interpolate(null, null, 1, listener);
              listener.lineEnd();
            }
            if (visible) {
              d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener);
            }
            listener.polygonEnd();
          }
          segments = polygon = ring = null;
        }
      };
      function insidePolygon(p) {
        var wn = 0, n = polygon.length, y = p[1];
        for (var i = 0; i < n; ++i) {
          for (var j = 1, v = polygon[i], m = v.length, a = v[0], b; j < m; ++j) {
            b = v[j];
            if (a[1] <= y) {
              if (b[1] > y && d3_cross2d(a, b, p) > 0) ++wn;
            } else {
              if (b[1] <= y && d3_cross2d(a, b, p) < 0) --wn;
            }
            a = b;
          }
        }
        return wn !== 0;
      }
      function interpolate(from, to, direction, listener) {
        var a = 0, a1 = 0;
        if (from == null || (a = corner(from, direction)) !== (a1 = corner(to, direction)) || comparePoints(from, to) < 0 ^ direction > 0) {
          do {
            listener.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0);
          } while ((a = (a + direction + 4) % 4) !== a1);
        } else {
          listener.point(to[0], to[1]);
        }
      }
      function pointVisible(x, y) {
        return x0 <= x && x <= x1 && y0 <= y && y <= y1;
      }
      function point(x, y) {
        if (pointVisible(x, y)) listener.point(x, y);
      }
      var x__, y__, v__, x_, y_, v_, first, clean;
      function lineStart() {
        clip.point = linePoint;
        if (polygon) polygon.push(ring = []);
        first = true;
        v_ = false;
        x_ = y_ = NaN;
      }
      function lineEnd() {
        if (segments) {
          linePoint(x__, y__);
          if (v__ && v_) bufferListener.rejoin();
          segments.push(bufferListener.buffer());
        }
        clip.point = point;
        if (v_) listener.lineEnd();
      }
      function linePoint(x, y) {
        x = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, x));
        y = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, y));
        var v = pointVisible(x, y);
        if (polygon) ring.push([ x, y ]);
        if (first) {
          x__ = x, y__ = y, v__ = v;
          first = false;
          if (v) {
            listener.lineStart();
            listener.point(x, y);
          }
        } else {
          if (v && v_) listener.point(x, y); else {
            var l = {
              a: {
                x: x_,
                y: y_
              },
              b: {
                x: x,
                y: y
              }
            };
            if (clipLine(l)) {
              if (!v_) {
                listener.lineStart();
                listener.point(l.a.x, l.a.y);
              }
              listener.point(l.b.x, l.b.y);
              if (!v) listener.lineEnd();
              clean = false;
            } else if (v) {
              listener.lineStart();
              listener.point(x, y);
              clean = false;
            }
          }
        }
        x_ = x, y_ = y, v_ = v;
      }
      return clip;
    };
    function corner(p, direction) {
      return abs(p[0] - x0) < ε ? direction > 0 ? 0 : 3 : abs(p[0] - x1) < ε ? direction > 0 ? 2 : 1 : abs(p[1] - y0) < ε ? direction > 0 ? 1 : 0 : direction > 0 ? 3 : 2;
    }
    function compare(a, b) {
      return comparePoints(a.x, b.x);
    }
    function comparePoints(a, b) {
      var ca = corner(a, 1), cb = corner(b, 1);
      return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0];
    }
  }
  function d3_geo_conic(projectAt) {
    var φ0 = 0, φ1 = π / 3, m = d3_geo_projectionMutator(projectAt), p = m(φ0, φ1);
    p.parallels = function(_) {
      if (!arguments.length) return [ φ0 / π * 180, φ1 / π * 180 ];
      return m(φ0 = _[0] * π / 180, φ1 = _[1] * π / 180);
    };
    return p;
  }
  function d3_geo_conicEqualArea(φ0, φ1) {
    var sinφ0 = Math.sin(φ0), n = (sinφ0 + Math.sin(φ1)) / 2, C = 1 + sinφ0 * (2 * n - sinφ0), ρ0 = Math.sqrt(C) / n;
    function forward(λ, φ) {
      var ρ = Math.sqrt(C - 2 * n * Math.sin(φ)) / n;
      return [ ρ * Math.sin(λ *= n), ρ0 - ρ * Math.cos(λ) ];
    }
    forward.invert = function(x, y) {
      var ρ0_y = ρ0 - y;
      return [ Math.atan2(x, ρ0_y) / n, d3_asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n)) ];
    };
    return forward;
  }
  (d3.geo.conicEqualArea = function() {
    return d3_geo_conic(d3_geo_conicEqualArea);
  }).raw = d3_geo_conicEqualArea;
  d3.geo.albers = function() {
    return d3.geo.conicEqualArea().rotate([ 96, 0 ]).center([ -.6, 38.7 ]).parallels([ 29.5, 45.5 ]).scale(1070);
  };
  d3.geo.albersUsa = function() {
    var lower48 = d3.geo.albers();
    var alaska = d3.geo.conicEqualArea().rotate([ 154, 0 ]).center([ -2, 58.5 ]).parallels([ 55, 65 ]);
    var hawaii = d3.geo.conicEqualArea().rotate([ 157, 0 ]).center([ -3, 19.9 ]).parallels([ 8, 18 ]);
    var point, pointStream = {
      point: function(x, y) {
        point = [ x, y ];
      }
    }, lower48Point, alaskaPoint, hawaiiPoint;
    function albersUsa(coordinates) {
      var x = coordinates[0], y = coordinates[1];
      point = null;
      (lower48Point(x, y), point) || (alaskaPoint(x, y), point) || hawaiiPoint(x, y);
      return point;
    }
    albersUsa.invert = function(coordinates) {
      var k = lower48.scale(), t = lower48.translate(), x = (coordinates[0] - t[0]) / k, y = (coordinates[1] - t[1]) / k;
      return (y >= .12 && y < .234 && x >= -.425 && x < -.214 ? alaska : y >= .166 && y < .234 && x >= -.214 && x < -.115 ? hawaii : lower48).invert(coordinates);
    };
    albersUsa.stream = function(stream) {
      var lower48Stream = lower48.stream(stream), alaskaStream = alaska.stream(stream), hawaiiStream = hawaii.stream(stream);
      return {
        point: function(x, y) {
          lower48Stream.point(x, y);
          alaskaStream.point(x, y);
          hawaiiStream.point(x, y);
        },
        sphere: function() {
          lower48Stream.sphere();
          alaskaStream.sphere();
          hawaiiStream.sphere();
        },
        lineStart: function() {
          lower48Stream.lineStart();
          alaskaStream.lineStart();
          hawaiiStream.lineStart();
        },
        lineEnd: function() {
          lower48Stream.lineEnd();
          alaskaStream.lineEnd();
          hawaiiStream.lineEnd();
        },
        polygonStart: function() {
          lower48Stream.polygonStart();
          alaskaStream.polygonStart();
          hawaiiStream.polygonStart();
        },
        polygonEnd: function() {
          lower48Stream.polygonEnd();
          alaskaStream.polygonEnd();
          hawaiiStream.polygonEnd();
        }
      };
    };
    albersUsa.precision = function(_) {
      if (!arguments.length) return lower48.precision();
      lower48.precision(_);
      alaska.precision(_);
      hawaii.precision(_);
      return albersUsa;
    };
    albersUsa.scale = function(_) {
      if (!arguments.length) return lower48.scale();
      lower48.scale(_);
      alaska.scale(_ * .35);
      hawaii.scale(_);
      return albersUsa.translate(lower48.translate());
    };
    albersUsa.translate = function(_) {
      if (!arguments.length) return lower48.translate();
      var k = lower48.scale(), x = +_[0], y = +_[1];
      lower48Point = lower48.translate(_).clipExtent([ [ x - .455 * k, y - .238 * k ], [ x + .455 * k, y + .238 * k ] ]).stream(pointStream).point;
      alaskaPoint = alaska.translate([ x - .307 * k, y + .201 * k ]).clipExtent([ [ x - .425 * k + ε, y + .12 * k + ε ], [ x - .214 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point;
      hawaiiPoint = hawaii.translate([ x - .205 * k, y + .212 * k ]).clipExtent([ [ x - .214 * k + ε, y + .166 * k + ε ], [ x - .115 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point;
      return albersUsa;
    };
    return albersUsa.scale(1070);
  };
  var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = {
    point: d3_noop,
    lineStart: d3_noop,
    lineEnd: d3_noop,
    polygonStart: function() {
      d3_geo_pathAreaPolygon = 0;
      d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart;
    },
    polygonEnd: function() {
      d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop;
      d3_geo_pathAreaSum += abs(d3_geo_pathAreaPolygon / 2);
    }
  };
  function d3_geo_pathAreaRingStart() {
    var x00, y00, x0, y0;
    d3_geo_pathArea.point = function(x, y) {
      d3_geo_pathArea.point = nextPoint;
      x00 = x0 = x, y00 = y0 = y;
    };
    function nextPoint(x, y) {
      d3_geo_pathAreaPolygon += y0 * x - x0 * y;
      x0 = x, y0 = y;
    }
    d3_geo_pathArea.lineEnd = function() {
      nextPoint(x00, y00);
    };
  }
  var d3_geo_pathBoundsX0, d3_geo_pathBoundsY0, d3_geo_pathBoundsX1, d3_geo_pathBoundsY1;
  var d3_geo_pathBounds = {
    point: d3_geo_pathBoundsPoint,
    lineStart: d3_noop,
    lineEnd: d3_noop,
    polygonStart: d3_noop,
    polygonEnd: d3_noop
  };
  function d3_geo_pathBoundsPoint(x, y) {
    if (x < d3_geo_pathBoundsX0) d3_geo_pathBoundsX0 = x;
    if (x > d3_geo_pathBoundsX1) d3_geo_pathBoundsX1 = x;
    if (y < d3_geo_pathBoundsY0) d3_geo_pathBoundsY0 = y;
    if (y > d3_geo_pathBoundsY1) d3_geo_pathBoundsY1 = y;
  }
  function d3_geo_pathBuffer() {
    var pointCircle = d3_geo_pathBufferCircle(4.5), buffer = [];
    var stream = {
      point: point,
      lineStart: function() {
        stream.point = pointLineStart;
      },
      lineEnd: lineEnd,
      polygonStart: function() {
        stream.lineEnd = lineEndPolygon;
      },
      polygonEnd: function() {
        stream.lineEnd = lineEnd;
        stream.point = point;
      },
      pointRadius: function(_) {
        pointCircle = d3_geo_pathBufferCircle(_);
        return stream;
      },
      result: function() {
        if (buffer.length) {
          var result = buffer.join("");
          buffer = [];
          return result;
        }
      }
    };
    function point(x, y) {
      buffer.push("M", x, ",", y, pointCircle);
    }
    function pointLineStart(x, y) {
      buffer.push("M", x, ",", y);
      stream.point = pointLine;
    }
    function pointLine(x, y) {
      buffer.push("L", x, ",", y);
    }
    function lineEnd() {
      stream.point = point;
    }
    function lineEndPolygon() {
      buffer.push("Z");
    }
    return stream;
  }
  function d3_geo_pathBufferCircle(radius) {
    return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + 2 * radius + "z";
  }
  var d3_geo_pathCentroid = {
    point: d3_geo_pathCentroidPoint,
    lineStart: d3_geo_pathCentroidLineStart,
    lineEnd: d3_geo_pathCentroidLineEnd,
    polygonStart: function() {
      d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart;
    },
    polygonEnd: function() {
      d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
      d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart;
      d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd;
    }
  };
  function d3_geo_pathCentroidPoint(x, y) {
    d3_geo_centroidX0 += x;
    d3_geo_centroidY0 += y;
    ++d3_geo_centroidZ0;
  }
  function d3_geo_pathCentroidLineStart() {
    var x0, y0;
    d3_geo_pathCentroid.point = function(x, y) {
      d3_geo_pathCentroid.point = nextPoint;
      d3_geo_pathCentroidPoint(x0 = x, y0 = y);
    };
    function nextPoint(x, y) {
      var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy);
      d3_geo_centroidX1 += z * (x0 + x) / 2;
      d3_geo_centroidY1 += z * (y0 + y) / 2;
      d3_geo_centroidZ1 += z;
      d3_geo_pathCentroidPoint(x0 = x, y0 = y);
    }
  }
  function d3_geo_pathCentroidLineEnd() {
    d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
  }
  function d3_geo_pathCentroidRingStart() {
    var x00, y00, x0, y0;
    d3_geo_pathCentroid.point = function(x, y) {
      d3_geo_pathCentroid.point = nextPoint;
      d3_geo_pathCentroidPoint(x00 = x0 = x, y00 = y0 = y);
    };
    function nextPoint(x, y) {
      var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy);
      d3_geo_centroidX1 += z * (x0 + x) / 2;
      d3_geo_centroidY1 += z * (y0 + y) / 2;
      d3_geo_centroidZ1 += z;
      z = y0 * x - x0 * y;
      d3_geo_centroidX2 += z * (x0 + x);
      d3_geo_centroidY2 += z * (y0 + y);
      d3_geo_centroidZ2 += z * 3;
      d3_geo_pathCentroidPoint(x0 = x, y0 = y);
    }
    d3_geo_pathCentroid.lineEnd = function() {
      nextPoint(x00, y00);
    };
  }
  function d3_geo_pathContext(context) {
    var pointRadius = 4.5;
    var stream = {
      point: point,
      lineStart: function() {
        stream.point = pointLineStart;
      },
      lineEnd: lineEnd,
      polygonStart: function() {
        stream.lineEnd = lineEndPolygon;
      },
      polygonEnd: function() {
        stream.lineEnd = lineEnd;
        stream.point = point;
      },
      pointRadius: function(_) {
        pointRadius = _;
        return stream;
      },
      result: d3_noop
    };
    function point(x, y) {
      context.moveTo(x + pointRadius, y);
      context.arc(x, y, pointRadius, 0, τ);
    }
    function pointLineStart(x, y) {
      context.moveTo(x, y);
      stream.point = pointLine;
    }
    function pointLine(x, y) {
      context.lineTo(x, y);
    }
    function lineEnd() {
      stream.point = point;
    }
    function lineEndPolygon() {
      context.closePath();
    }
    return stream;
  }
  function d3_geo_resample(project) {
    var δ2 = .5, cosMinDistance = Math.cos(30 * d3_radians), maxDepth = 16;
    function resample(stream) {
      return (maxDepth ? resampleRecursive : resampleNone)(stream);
    }
    function resampleNone(stream) {
      return d3_geo_transformPoint(stream, function(x, y) {
        x = project(x, y);
        stream.point(x[0], x[1]);
      });
    }
    function resampleRecursive(stream) {
      var λ00, φ00, x00, y00, a00, b00, c00, λ0, x0, y0, a0, b0, c0;
      var resample = {
        point: point,
        lineStart: lineStart,
        lineEnd: lineEnd,
        polygonStart: function() {
          stream.polygonStart();
          resample.lineStart = ringStart;
        },
        polygonEnd: function() {
          stream.polygonEnd();
          resample.lineStart = lineStart;
        }
      };
      function point(x, y) {
        x = project(x, y);
        stream.point(x[0], x[1]);
      }
      function lineStart() {
        x0 = NaN;
        resample.point = linePoint;
        stream.lineStart();
      }
      function linePoint(λ, φ) {
        var c = d3_geo_cartesian([ λ, φ ]), p = project(λ, φ);
        resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream);
        stream.point(x0, y0);
      }
      function lineEnd() {
        resample.point = point;
        stream.lineEnd();
      }
      function ringStart() {
        lineStart();
        resample.point = ringPoint;
        resample.lineEnd = ringEnd;
      }
      function ringPoint(λ, φ) {
        linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0;
        resample.point = linePoint;
      }
      function ringEnd() {
        resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream);
        resample.lineEnd = lineEnd;
        lineEnd();
      }
      return resample;
    }
    function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) {
      var dx = x1 - x0, dy = y1 - y0, d2 = dx * dx + dy * dy;
      if (d2 > 4 * δ2 && depth--) {
        var a = a0 + a1, b = b0 + b1, c = c0 + c1, m = Math.sqrt(a * a + b * b + c * c), φ2 = Math.asin(c /= m), λ2 = abs(abs(c) - 1) < ε || abs(λ0 - λ1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a), p = project(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x2 - x0, dy2 = y2 - y0, dz = dy * dx2 - dx * dy2;
        if (dz * dz / d2 > δ2 || abs((dx * dx2 + dy * dy2) / d2 - .5) > .3 || a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) {
          resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream);
          stream.point(x2, y2);
          resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream);
        }
      }
    }
    resample.precision = function(_) {
      if (!arguments.length) return Math.sqrt(δ2);
      maxDepth = (δ2 = _ * _) > 0 && 16;
      return resample;
    };
    return resample;
  }
  d3.geo.path = function() {
    var pointRadius = 4.5, projection, context, projectStream, contextStream, cacheStream;
    function path(object) {
      if (object) {
        if (typeof pointRadius === "function") contextStream.pointRadius(+pointRadius.apply(this, arguments));
        if (!cacheStream || !cacheStream.valid) cacheStream = projectStream(contextStream);
        d3.geo.stream(object, cacheStream);
      }
      return contextStream.result();
    }
    path.area = function(object) {
      d3_geo_pathAreaSum = 0;
      d3.geo.stream(object, projectStream(d3_geo_pathArea));
      return d3_geo_pathAreaSum;
    };
    path.centroid = function(object) {
      d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0;
      d3.geo.stream(object, projectStream(d3_geo_pathCentroid));
      return d3_geo_centroidZ2 ? [ d3_geo_centroidX2 / d3_geo_centroidZ2, d3_geo_centroidY2 / d3_geo_centroidZ2 ] : d3_geo_centroidZ1 ? [ d3_geo_centroidX1 / d3_geo_centroidZ1, d3_geo_centroidY1 / d3_geo_centroidZ1 ] : d3_geo_centroidZ0 ? [ d3_geo_centroidX0 / d3_geo_centroidZ0, d3_geo_centroidY0 / d3_geo_centroidZ0 ] : [ NaN, NaN ];
    };
    path.bounds = function(object) {
      d3_geo_pathBoundsX1 = d3_geo_pathBoundsY1 = -(d3_geo_pathBoundsX0 = d3_geo_pathBoundsY0 = Infinity);
      d3.geo.stream(object, projectStream(d3_geo_pathBounds));
      return [ [ d3_geo_pathBoundsX0, d3_geo_pathBoundsY0 ], [ d3_geo_pathBoundsX1, d3_geo_pathBoundsY1 ] ];
    };
    path.projection = function(_) {
      if (!arguments.length) return projection;
      projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity;
      return reset();
    };
    path.context = function(_) {
      if (!arguments.length) return context;
      contextStream = (context = _) == null ? new d3_geo_pathBuffer() : new d3_geo_pathContext(_);
      if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius);
      return reset();
    };
    path.pointRadius = function(_) {
      if (!arguments.length) return pointRadius;
      pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_);
      return path;
    };
    function reset() {
      cacheStream = null;
      return path;
    }
    return path.projection(d3.geo.albersUsa()).context(null);
  };
  function d3_geo_pathProjectStream(project) {
    var resample = d3_geo_resample(function(x, y) {
      return project([ x * d3_degrees, y * d3_degrees ]);
    });
    return function(stream) {
      return d3_geo_projectionRadians(resample(stream));
    };
  }
  d3.geo.transform = function(methods) {
    return {
      stream: function(stream) {
        var transform = new d3_geo_transform(stream);
        for (var k in methods) transform[k] = methods[k];
        return transform;
      }
    };
  };
  function d3_geo_transform(stream) {
    this.stream = stream;
  }
  d3_geo_transform.prototype = {
    point: function(x, y) {
      this.stream.point(x, y);
    },
    sphere: function() {
      this.stream.sphere();
    },
    lineStart: function() {
      this.stream.lineStart();
    },
    lineEnd: function() {
      this.stream.lineEnd();
    },
    polygonStart: function() {
      this.stream.polygonStart();
    },
    polygonEnd: function() {
      this.stream.polygonEnd();
    }
  };
  function d3_geo_transformPoint(stream, point) {
    return {
      point: point,
      sphere: function() {
        stream.sphere();
      },
      lineStart: function() {
        stream.lineStart();
      },
      lineEnd: function() {
        stream.lineEnd();
      },
      polygonStart: function() {
        stream.polygonStart();
      },
      polygonEnd: function() {
        stream.polygonEnd();
      }
    };
  }
  d3.geo.projection = d3_geo_projection;
  d3.geo.projectionMutator = d3_geo_projectionMutator;
  function d3_geo_projection(project) {
    return d3_geo_projectionMutator(function() {
      return project;
    })();
  }
  function d3_geo_projectionMutator(projectAt) {
    var project, rotate, projectRotate, projectResample = d3_geo_resample(function(x, y) {
      x = project(x, y);
      return [ x[0] * k + δx, δy - x[1] * k ];
    }), k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx, δy, preclip = d3_geo_clipAntimeridian, postclip = d3_identity, clipAngle = null, clipExtent = null, stream;
    function projection(point) {
      point = projectRotate(point[0] * d3_radians, point[1] * d3_radians);
      return [ point[0] * k + δx, δy - point[1] * k ];
    }
    function invert(point) {
      point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k);
      return point && [ point[0] * d3_degrees, point[1] * d3_degrees ];
    }
    projection.stream = function(output) {
      if (stream) stream.valid = false;
      stream = d3_geo_projectionRadians(preclip(rotate, projectResample(postclip(output))));
      stream.valid = true;
      return stream;
    };
    projection.clipAngle = function(_) {
      if (!arguments.length) return clipAngle;
      preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians);
      return invalidate();
    };
    projection.clipExtent = function(_) {
      if (!arguments.length) return clipExtent;
      clipExtent = _;
      postclip = _ ? d3_geo_clipExtent(_[0][0], _[0][1], _[1][0], _[1][1]) : d3_identity;
      return invalidate();
    };
    projection.scale = function(_) {
      if (!arguments.length) return k;
      k = +_;
      return reset();
    };
    projection.translate = function(_) {
      if (!arguments.length) return [ x, y ];
      x = +_[0];
      y = +_[1];
      return reset();
    };
    projection.center = function(_) {
      if (!arguments.length) return [ λ * d3_degrees, φ * d3_degrees ];
      λ = _[0] % 360 * d3_radians;
      φ = _[1] % 360 * d3_radians;
      return reset();
    };
    projection.rotate = function(_) {
      if (!arguments.length) return [ δλ * d3_degrees, δφ * d3_degrees, δγ * d3_degrees ];
      δλ = _[0] % 360 * d3_radians;
      δφ = _[1] % 360 * d3_radians;
      δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0;
      return reset();
    };
    d3.rebind(projection, projectResample, "precision");
    function reset() {
      projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project);
      var center = project(λ, φ);
      δx = x - center[0] * k;
      δy = y + center[1] * k;
      return invalidate();
    }
    function invalidate() {
      if (stream) stream.valid = false, stream = null;
      return projection;
    }
    return function() {
      project = projectAt.apply(this, arguments);
      projection.invert = project.invert && invert;
      return reset();
    };
  }
  function d3_geo_projectionRadians(stream) {
    return d3_geo_transformPoint(stream, function(x, y) {
      stream.point(x * d3_radians, y * d3_radians);
    });
  }
  function d3_geo_equirectangular(λ, φ) {
    return [ λ, φ ];
  }
  (d3.geo.equirectangular = function() {
    return d3_geo_projection(d3_geo_equirectangular);
  }).raw = d3_geo_equirectangular.invert = d3_geo_equirectangular;
  d3.geo.rotation = function(rotate) {
    rotate = d3_geo_rotation(rotate[0] % 360 * d3_radians, rotate[1] * d3_radians, rotate.length > 2 ? rotate[2] * d3_radians : 0);
    function forward(coordinates) {
      coordinates = rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
      return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
    }
    forward.invert = function(coordinates) {
      coordinates = rotate.invert(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
      return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
    };
    return forward;
  };
  function d3_geo_identityRotation(λ, φ) {
    return [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ];
  }
  d3_geo_identityRotation.invert = d3_geo_equirectangular;
  function d3_geo_rotation(δλ, δφ, δγ) {
    return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_identityRotation;
  }
  function d3_geo_forwardRotationλ(δλ) {
    return function(λ, φ) {
      return λ += δλ, [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ];
    };
  }
  function d3_geo_rotationλ(δλ) {
    var rotation = d3_geo_forwardRotationλ(δλ);
    rotation.invert = d3_geo_forwardRotationλ(-δλ);
    return rotation;
  }
  function d3_geo_rotationφγ(δφ, δγ) {
    var cosδφ = Math.cos(δφ), sinδφ = Math.sin(δφ), cosδγ = Math.cos(δγ), sinδγ = Math.sin(δγ);
    function rotation(λ, φ) {
      var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδφ + x * sinδφ;
      return [ Math.atan2(y * cosδγ - k * sinδγ, x * cosδφ - z * sinδφ), d3_asin(k * cosδγ + y * sinδγ) ];
    }
    rotation.invert = function(λ, φ) {
      var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδγ - y * sinδγ;
      return [ Math.atan2(y * cosδγ + z * sinδγ, x * cosδφ + k * sinδφ), d3_asin(k * cosδφ - x * sinδφ) ];
    };
    return rotation;
  }
  d3.geo.circle = function() {
    var origin = [ 0, 0 ], angle, precision = 6, interpolate;
    function circle() {
      var center = typeof origin === "function" ? origin.apply(this, arguments) : origin, rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert, ring = [];
      interpolate(null, null, 1, {
        point: function(x, y) {
          ring.push(x = rotate(x, y));
          x[0] *= d3_degrees, x[1] *= d3_degrees;
        }
      });
      return {
        type: "Polygon",
        coordinates: [ ring ]
      };
    }
    circle.origin = function(x) {
      if (!arguments.length) return origin;
      origin = x;
      return circle;
    };
    circle.angle = function(x) {
      if (!arguments.length) return angle;
      interpolate = d3_geo_circleInterpolate((angle = +x) * d3_radians, precision * d3_radians);
      return circle;
    };
    circle.precision = function(_) {
      if (!arguments.length) return precision;
      interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians);
      return circle;
    };
    return circle.angle(90);
  };
  function d3_geo_circleInterpolate(radius, precision) {
    var cr = Math.cos(radius), sr = Math.sin(radius);
    return function(from, to, direction, listener) {
      var step = direction * precision;
      if (from != null) {
        from = d3_geo_circleAngle(cr, from);
        to = d3_geo_circleAngle(cr, to);
        if (direction > 0 ? from < to : from > to) from += direction * τ;
      } else {
        from = radius + direction * τ;
        to = radius - .5 * step;
      }
      for (var point, t = from; direction > 0 ? t > to : t < to; t -= step) {
        listener.point((point = d3_geo_spherical([ cr, -sr * Math.cos(t), -sr * Math.sin(t) ]))[0], point[1]);
      }
    };
  }
  function d3_geo_circleAngle(cr, point) {
    var a = d3_geo_cartesian(point);
    a[0] -= cr;
    d3_geo_cartesianNormalize(a);
    var angle = d3_acos(-a[1]);
    return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI);
  }
  d3.geo.distance = function(a, b) {
    var Δλ = (b[0] - a[0]) * d3_radians, φ0 = a[1] * d3_radians, φ1 = b[1] * d3_radians, sinΔλ = Math.sin(Δλ), cosΔλ = Math.cos(Δλ), sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), sinφ1 = Math.sin(φ1), cosφ1 = Math.cos(φ1), t;
    return Math.atan2(Math.sqrt((t = cosφ1 * sinΔλ) * t + (t = cosφ0 * sinφ1 - sinφ0 * cosφ1 * cosΔλ) * t), sinφ0 * sinφ1 + cosφ0 * cosφ1 * cosΔλ);
  };
  d3.geo.graticule = function() {
    var x1, x0, X1, X0, y1, y0, Y1, Y0, dx = 10, dy = dx, DX = 90, DY = 360, x, y, X, Y, precision = 2.5;
    function graticule() {
      return {
        type: "MultiLineString",
        coordinates: lines()
      };
    }
    function lines() {
      return d3.range(Math.ceil(X0 / DX) * DX, X1, DX).map(X).concat(d3.range(Math.ceil(Y0 / DY) * DY, Y1, DY).map(Y)).concat(d3.range(Math.ceil(x0 / dx) * dx, x1, dx).filter(function(x) {
        return abs(x % DX) > ε;
      }).map(x)).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).filter(function(y) {
        return abs(y % DY) > ε;
      }).map(y));
    }
    graticule.lines = function() {
      return lines().map(function(coordinates) {
        return {
          type: "LineString",
          coordinates: coordinates
        };
      });
    };
    graticule.outline = function() {
      return {
        type: "Polygon",
        coordinates: [ X(X0).concat(Y(Y1).slice(1), X(X1).reverse().slice(1), Y(Y0).reverse().slice(1)) ]
      };
    };
    graticule.extent = function(_) {
      if (!arguments.length) return graticule.minorExtent();
      return graticule.majorExtent(_).minorExtent(_);
    };
    graticule.majorExtent = function(_) {
      if (!arguments.length) return [ [ X0, Y0 ], [ X1, Y1 ] ];
      X0 = +_[0][0], X1 = +_[1][0];
      Y0 = +_[0][1], Y1 = +_[1][1];
      if (X0 > X1) _ = X0, X0 = X1, X1 = _;
      if (Y0 > Y1) _ = Y0, Y0 = Y1, Y1 = _;
      return graticule.precision(precision);
    };
    graticule.minorExtent = function(_) {
      if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ];
      x0 = +_[0][0], x1 = +_[1][0];
      y0 = +_[0][1], y1 = +_[1][1];
      if (x0 > x1) _ = x0, x0 = x1, x1 = _;
      if (y0 > y1) _ = y0, y0 = y1, y1 = _;
      return graticule.precision(precision);
    };
    graticule.step = function(_) {
      if (!arguments.length) return graticule.minorStep();
      return graticule.majorStep(_).minorStep(_);
    };
    graticule.majorStep = function(_) {
      if (!arguments.length) return [ DX, DY ];
      DX = +_[0], DY = +_[1];
      return graticule;
    };
    graticule.minorStep = function(_) {
      if (!arguments.length) return [ dx, dy ];
      dx = +_[0], dy = +_[1];
      return graticule;
    };
    graticule.precision = function(_) {
      if (!arguments.length) return precision;
      precision = +_;
      x = d3_geo_graticuleX(y0, y1, 90);
      y = d3_geo_graticuleY(x0, x1, precision);
      X = d3_geo_graticuleX(Y0, Y1, 90);
      Y = d3_geo_graticuleY(X0, X1, precision);
      return graticule;
    };
    return graticule.majorExtent([ [ -180, -90 + ε ], [ 180, 90 - ε ] ]).minorExtent([ [ -180, -80 - ε ], [ 180, 80 + ε ] ]);
  };
  function d3_geo_graticuleX(y0, y1, dy) {
    var y = d3.range(y0, y1 - ε, dy).concat(y1);
    return function(x) {
      return y.map(function(y) {
        return [ x, y ];
      });
    };
  }
  function d3_geo_graticuleY(x0, x1, dx) {
    var x = d3.range(x0, x1 - ε, dx).concat(x1);
    return function(y) {
      return x.map(function(x) {
        return [ x, y ];
      });
    };
  }
  function d3_source(d) {
    return d.source;
  }
  function d3_target(d) {
    return d.target;
  }
  d3.geo.greatArc = function() {
    var source = d3_source, source_, target = d3_target, target_;
    function greatArc() {
      return {
        type: "LineString",
        coordinates: [ source_ || source.apply(this, arguments), target_ || target.apply(this, arguments) ]
      };
    }
    greatArc.distance = function() {
      return d3.geo.distance(source_ || source.apply(this, arguments), target_ || target.apply(this, arguments));
    };
    greatArc.source = function(_) {
      if (!arguments.length) return source;
      source = _, source_ = typeof _ === "function" ? null : _;
      return greatArc;
    };
    greatArc.target = function(_) {
      if (!arguments.length) return target;
      target = _, target_ = typeof _ === "function" ? null : _;
      return greatArc;
    };
    greatArc.precision = function() {
      return arguments.length ? greatArc : 0;
    };
    return greatArc;
  };
  d3.geo.interpolate = function(source, target) {
    return d3_geo_interpolate(source[0] * d3_radians, source[1] * d3_radians, target[0] * d3_radians, target[1] * d3_radians);
  };
  function d3_geo_interpolate(x0, y0, x1, y1) {
    var cy0 = Math.cos(y0), sy0 = Math.sin(y0), cy1 = Math.cos(y1), sy1 = Math.sin(y1), kx0 = cy0 * Math.cos(x0), ky0 = cy0 * Math.sin(x0), kx1 = cy1 * Math.cos(x1), ky1 = cy1 * Math.sin(x1), d = 2 * Math.asin(Math.sqrt(d3_haversin(y1 - y0) + cy0 * cy1 * d3_haversin(x1 - x0))), k = 1 / Math.sin(d);
    var interpolate = d ? function(t) {
      var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1;
      return [ Math.atan2(y, x) * d3_degrees, Math.atan2(z, Math.sqrt(x * x + y * y)) * d3_degrees ];
    } : function() {
      return [ x0 * d3_degrees, y0 * d3_degrees ];
    };
    interpolate.distance = d;
    return interpolate;
  }
  d3.geo.length = function(object) {
    d3_geo_lengthSum = 0;
    d3.geo.stream(object, d3_geo_length);
    return d3_geo_lengthSum;
  };
  var d3_geo_lengthSum;
  var d3_geo_length = {
    sphere: d3_noop,
    point: d3_noop,
    lineStart: d3_geo_lengthLineStart,
    lineEnd: d3_noop,
    polygonStart: d3_noop,
    polygonEnd: d3_noop
  };
  function d3_geo_lengthLineStart() {
    var λ0, sinφ0, cosφ0;
    d3_geo_length.point = function(λ, φ) {
      λ0 = λ * d3_radians, sinφ0 = Math.sin(φ *= d3_radians), cosφ0 = Math.cos(φ);
      d3_geo_length.point = nextPoint;
    };
    d3_geo_length.lineEnd = function() {
      d3_geo_length.point = d3_geo_length.lineEnd = d3_noop;
    };
    function nextPoint(λ, φ) {
      var sinφ = Math.sin(φ *= d3_radians), cosφ = Math.cos(φ), t = abs((λ *= d3_radians) - λ0), cosΔλ = Math.cos(t);
      d3_geo_lengthSum += Math.atan2(Math.sqrt((t = cosφ * Math.sin(t)) * t + (t = cosφ0 * sinφ - sinφ0 * cosφ * cosΔλ) * t), sinφ0 * sinφ + cosφ0 * cosφ * cosΔλ);
      λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ;
    }
  }
  function d3_geo_azimuthal(scale, angle) {
    function azimuthal(λ, φ) {
      var cosλ = Math.cos(λ), cosφ = Math.cos(φ), k = scale(cosλ * cosφ);
      return [ k * cosφ * Math.sin(λ), k * Math.sin(φ) ];
    }
    azimuthal.invert = function(x, y) {
      var ρ = Math.sqrt(x * x + y * y), c = angle(ρ), sinc = Math.sin(c), cosc = Math.cos(c);
      return [ Math.atan2(x * sinc, ρ * cosc), Math.asin(ρ && y * sinc / ρ) ];
    };
    return azimuthal;
  }
  var d3_geo_azimuthalEqualArea = d3_geo_azimuthal(function(cosλcosφ) {
    return Math.sqrt(2 / (1 + cosλcosφ));
  }, function(ρ) {
    return 2 * Math.asin(ρ / 2);
  });
  (d3.geo.azimuthalEqualArea = function() {
    return d3_geo_projection(d3_geo_azimuthalEqualArea);
  }).raw = d3_geo_azimuthalEqualArea;
  var d3_geo_azimuthalEquidistant = d3_geo_azimuthal(function(cosλcosφ) {
    var c = Math.acos(cosλcosφ);
    return c && c / Math.sin(c);
  }, d3_identity);
  (d3.geo.azimuthalEquidistant = function() {
    return d3_geo_projection(d3_geo_azimuthalEquidistant);
  }).raw = d3_geo_azimuthalEquidistant;
  function d3_geo_conicConformal(φ0, φ1) {
    var cosφ0 = Math.cos(φ0), t = function(φ) {
      return Math.tan(π / 4 + φ / 2);
    }, n = φ0 === φ1 ? Math.sin(φ0) : Math.log(cosφ0 / Math.cos(φ1)) / Math.log(t(φ1) / t(φ0)), F = cosφ0 * Math.pow(t(φ0), n) / n;
    if (!n) return d3_geo_mercator;
    function forward(λ, φ) {
      if (F > 0) {
        if (φ < -halfπ + ε) φ = -halfπ + ε;
      } else {
        if (φ > halfπ - ε) φ = halfπ - ε;
      }
      var ρ = F / Math.pow(t(φ), n);
      return [ ρ * Math.sin(n * λ), F - ρ * Math.cos(n * λ) ];
    }
    forward.invert = function(x, y) {
      var ρ0_y = F - y, ρ = d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y);
      return [ Math.atan2(x, ρ0_y) / n, 2 * Math.atan(Math.pow(F / ρ, 1 / n)) - halfπ ];
    };
    return forward;
  }
  (d3.geo.conicConformal = function() {
    return d3_geo_conic(d3_geo_conicConformal);
  }).raw = d3_geo_conicConformal;
  function d3_geo_conicEquidistant(φ0, φ1) {
    var cosφ0 = Math.cos(φ0), n = φ0 === φ1 ? Math.sin(φ0) : (cosφ0 - Math.cos(φ1)) / (φ1 - φ0), G = cosφ0 / n + φ0;
    if (abs(n) < ε) return d3_geo_equirectangular;
    function forward(λ, φ) {
      var ρ = G - φ;
      return [ ρ * Math.sin(n * λ), G - ρ * Math.cos(n * λ) ];
    }
    forward.invert = function(x, y) {
      var ρ0_y = G - y;
      return [ Math.atan2(x, ρ0_y) / n, G - d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y) ];
    };
    return forward;
  }
  (d3.geo.conicEquidistant = function() {
    return d3_geo_conic(d3_geo_conicEquidistant);
  }).raw = d3_geo_conicEquidistant;
  var d3_geo_gnomonic = d3_geo_azimuthal(function(cosλcosφ) {
    return 1 / cosλcosφ;
  }, Math.atan);
  (d3.geo.gnomonic = function() {
    return d3_geo_projection(d3_geo_gnomonic);
  }).raw = d3_geo_gnomonic;
  function d3_geo_mercator(λ, φ) {
    return [ λ, Math.log(Math.tan(π / 4 + φ / 2)) ];
  }
  d3_geo_mercator.invert = function(x, y) {
    return [ x, 2 * Math.atan(Math.exp(y)) - halfπ ];
  };
  function d3_geo_mercatorProjection(project) {
    var m = d3_geo_projection(project), scale = m.scale, translate = m.translate, clipExtent = m.clipExtent, clipAuto;
    m.scale = function() {
      var v = scale.apply(m, arguments);
      return v === m ? clipAuto ? m.clipExtent(null) : m : v;
    };
    m.translate = function() {
      var v = translate.apply(m, arguments);
      return v === m ? clipAuto ? m.clipExtent(null) : m : v;
    };
    m.clipExtent = function(_) {
      var v = clipExtent.apply(m, arguments);
      if (v === m) {
        if (clipAuto = _ == null) {
          var k = π * scale(), t = translate();
          clipExtent([ [ t[0] - k, t[1] - k ], [ t[0] + k, t[1] + k ] ]);
        }
      } else if (clipAuto) {
        v = null;
      }
      return v;
    };
    return m.clipExtent(null);
  }
  (d3.geo.mercator = function() {
    return d3_geo_mercatorProjection(d3_geo_mercator);
  }).raw = d3_geo_mercator;
  var d3_geo_orthographic = d3_geo_azimuthal(function() {
    return 1;
  }, Math.asin);
  (d3.geo.orthographic = function() {
    return d3_geo_projection(d3_geo_orthographic);
  }).raw = d3_geo_orthographic;
  var d3_geo_stereographic = d3_geo_azimuthal(function(cosλcosφ) {
    return 1 / (1 + cosλcosφ);
  }, function(ρ) {
    return 2 * Math.atan(ρ);
  });
  (d3.geo.stereographic = function() {
    return d3_geo_projection(d3_geo_stereographic);
  }).raw = d3_geo_stereographic;
  function d3_geo_transverseMercator(λ, φ) {
    return [ Math.log(Math.tan(π / 4 + φ / 2)), -λ ];
  }
  d3_geo_transverseMercator.invert = function(x, y) {
    return [ -y, 2 * Math.atan(Math.exp(x)) - halfπ ];
  };
  (d3.geo.transverseMercator = function() {
    var projection = d3_geo_mercatorProjection(d3_geo_transverseMercator), center = projection.center, rotate = projection.rotate;
    projection.center = function(_) {
      return _ ? center([ -_[1], _[0] ]) : (_ = center(), [ _[1], -_[0] ]);
    };
    projection.rotate = function(_) {
      return _ ? rotate([ _[0], _[1], _.length > 2 ? _[2] + 90 : 90 ]) : (_ = rotate(), 
      [ _[0], _[1], _[2] - 90 ]);
    };
    return rotate([ 0, 0, 90 ]);
  }).raw = d3_geo_transverseMercator;
  d3.geom = {};
  function d3_geom_pointX(d) {
    return d[0];
  }
  function d3_geom_pointY(d) {
    return d[1];
  }
  d3.geom.hull = function(vertices) {
    var x = d3_geom_pointX, y = d3_geom_pointY;
    if (arguments.length) return hull(vertices);
    function hull(data) {
      if (data.length < 3) return [];
      var fx = d3_functor(x), fy = d3_functor(y), i, n = data.length, points = [], flippedPoints = [];
      for (i = 0; i < n; i++) {
        points.push([ +fx.call(this, data[i], i), +fy.call(this, data[i], i), i ]);
      }
      points.sort(d3_geom_hullOrder);
      for (i = 0; i < n; i++) flippedPoints.push([ points[i][0], -points[i][1] ]);
      var upper = d3_geom_hullUpper(points), lower = d3_geom_hullUpper(flippedPoints);
      var skipLeft = lower[0] === upper[0], skipRight = lower[lower.length - 1] === upper[upper.length - 1], polygon = [];
      for (i = upper.length - 1; i >= 0; --i) polygon.push(data[points[upper[i]][2]]);
      for (i = +skipLeft; i < lower.length - skipRight; ++i) polygon.push(data[points[lower[i]][2]]);
      return polygon;
    }
    hull.x = function(_) {
      return arguments.length ? (x = _, hull) : x;
    };
    hull.y = function(_) {
      return arguments.length ? (y = _, hull) : y;
    };
    return hull;
  };
  function d3_geom_hullUpper(points) {
    var n = points.length, hull = [ 0, 1 ], hs = 2;
    for (var i = 2; i < n; i++) {
      while (hs > 1 && d3_cross2d(points[hull[hs - 2]], points[hull[hs - 1]], points[i]) <= 0) --hs;
      hull[hs++] = i;
    }
    return hull.slice(0, hs);
  }
  function d3_geom_hullOrder(a, b) {
    return a[0] - b[0] || a[1] - b[1];
  }
  d3.geom.polygon = function(coordinates) {
    d3_subclass(coordinates, d3_geom_polygonPrototype);
    return coordinates;
  };
  var d3_geom_polygonPrototype = d3.geom.polygon.prototype = [];
  d3_geom_polygonPrototype.area = function() {
    var i = -1, n = this.length, a, b = this[n - 1], area = 0;
    while (++i < n) {
      a = b;
      b = this[i];
      area += a[1] * b[0] - a[0] * b[1];
    }
    return area * .5;
  };
  d3_geom_polygonPrototype.centroid = function(k) {
    var i = -1, n = this.length, x = 0, y = 0, a, b = this[n - 1], c;
    if (!arguments.length) k = -1 / (6 * this.area());
    while (++i < n) {
      a = b;
      b = this[i];
      c = a[0] * b[1] - b[0] * a[1];
      x += (a[0] + b[0]) * c;
      y += (a[1] + b[1]) * c;
    }
    return [ x * k, y * k ];
  };
  d3_geom_polygonPrototype.clip = function(subject) {
    var input, closed = d3_geom_polygonClosed(subject), i = -1, n = this.length - d3_geom_polygonClosed(this), j, m, a = this[n - 1], b, c, d;
    while (++i < n) {
      input = subject.slice();
      subject.length = 0;
      b = this[i];
      c = input[(m = input.length - closed) - 1];
      j = -1;
      while (++j < m) {
        d = input[j];
        if (d3_geom_polygonInside(d, a, b)) {
          if (!d3_geom_polygonInside(c, a, b)) {
            subject.push(d3_geom_polygonIntersect(c, d, a, b));
          }
          subject.push(d);
        } else if (d3_geom_polygonInside(c, a, b)) {
          subject.push(d3_geom_polygonIntersect(c, d, a, b));
        }
        c = d;
      }
      if (closed) subject.push(subject[0]);
      a = b;
    }
    return subject;
  };
  function d3_geom_polygonInside(p, a, b) {
    return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]);
  }
  function d3_geom_polygonIntersect(c, d, a, b) {
    var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21);
    return [ x1 + ua * x21, y1 + ua * y21 ];
  }
  function d3_geom_polygonClosed(coordinates) {
    var a = coordinates[0], b = coordinates[coordinates.length - 1];
    return !(a[0] - b[0] || a[1] - b[1]);
  }
  var d3_geom_voronoiEdges, d3_geom_voronoiCells, d3_geom_voronoiBeaches, d3_geom_voronoiBeachPool = [], d3_geom_voronoiFirstCircle, d3_geom_voronoiCircles, d3_geom_voronoiCirclePool = [];
  function d3_geom_voronoiBeach() {
    d3_geom_voronoiRedBlackNode(this);
    this.edge = this.site = this.circle = null;
  }
  function d3_geom_voronoiCreateBeach(site) {
    var beach = d3_geom_voronoiBeachPool.pop() || new d3_geom_voronoiBeach();
    beach.site = site;
    return beach;
  }
  function d3_geom_voronoiDetachBeach(beach) {
    d3_geom_voronoiDetachCircle(beach);
    d3_geom_voronoiBeaches.remove(beach);
    d3_geom_voronoiBeachPool.push(beach);
    d3_geom_voronoiRedBlackNode(beach);
  }
  function d3_geom_voronoiRemoveBeach(beach) {
    var circle = beach.circle, x = circle.x, y = circle.cy, vertex = {
      x: x,
      y: y
    }, previous = beach.P, next = beach.N, disappearing = [ beach ];
    d3_geom_voronoiDetachBeach(beach);
    var lArc = previous;
    while (lArc.circle && abs(x - lArc.circle.x) < ε && abs(y - lArc.circle.cy) < ε) {
      previous = lArc.P;
      disappearing.unshift(lArc);
      d3_geom_voronoiDetachBeach(lArc);
      lArc = previous;
    }
    disappearing.unshift(lArc);
    d3_geom_voronoiDetachCircle(lArc);
    var rArc = next;
    while (rArc.circle && abs(x - rArc.circle.x) < ε && abs(y - rArc.circle.cy) < ε) {
      next = rArc.N;
      disappearing.push(rArc);
      d3_geom_voronoiDetachBeach(rArc);
      rArc = next;
    }
    disappearing.push(rArc);
    d3_geom_voronoiDetachCircle(rArc);
    var nArcs = disappearing.length, iArc;
    for (iArc = 1; iArc < nArcs; ++iArc) {
      rArc = disappearing[iArc];
      lArc = disappearing[iArc - 1];
      d3_geom_voronoiSetEdgeEnd(rArc.edge, lArc.site, rArc.site, vertex);
    }
    lArc = disappearing[0];
    rArc = disappearing[nArcs - 1];
    rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, rArc.site, null, vertex);
    d3_geom_voronoiAttachCircle(lArc);
    d3_geom_voronoiAttachCircle(rArc);
  }
  function d3_geom_voronoiAddBeach(site) {
    var x = site.x, directrix = site.y, lArc, rArc, dxl, dxr, node = d3_geom_voronoiBeaches._;
    while (node) {
      dxl = d3_geom_voronoiLeftBreakPoint(node, directrix) - x;
      if (dxl > ε) node = node.L; else {
        dxr = x - d3_geom_voronoiRightBreakPoint(node, directrix);
        if (dxr > ε) {
          if (!node.R) {
            lArc = node;
            break;
          }
          node = node.R;
        } else {
          if (dxl > -ε) {
            lArc = node.P;
            rArc = node;
          } else if (dxr > -ε) {
            lArc = node;
            rArc = node.N;
          } else {
            lArc = rArc = node;
          }
          break;
        }
      }
    }
    var newArc = d3_geom_voronoiCreateBeach(site);
    d3_geom_voronoiBeaches.insert(lArc, newArc);
    if (!lArc && !rArc) return;
    if (lArc === rArc) {
      d3_geom_voronoiDetachCircle(lArc);
      rArc = d3_geom_voronoiCreateBeach(lArc.site);
      d3_geom_voronoiBeaches.insert(newArc, rArc);
      newArc.edge = rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site);
      d3_geom_voronoiAttachCircle(lArc);
      d3_geom_voronoiAttachCircle(rArc);
      return;
    }
    if (!rArc) {
      newArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site);
      return;
    }
    d3_geom_voronoiDetachCircle(lArc);
    d3_geom_voronoiDetachCircle(rArc);
    var lSite = lArc.site, ax = lSite.x, ay = lSite.y, bx = site.x - ax, by = site.y - ay, rSite = rArc.site, cx = rSite.x - ax, cy = rSite.y - ay, d = 2 * (bx * cy - by * cx), hb = bx * bx + by * by, hc = cx * cx + cy * cy, vertex = {
      x: (cy * hb - by * hc) / d + ax,
      y: (bx * hc - cx * hb) / d + ay
    };
    d3_geom_voronoiSetEdgeEnd(rArc.edge, lSite, rSite, vertex);
    newArc.edge = d3_geom_voronoiCreateEdge(lSite, site, null, vertex);
    rArc.edge = d3_geom_voronoiCreateEdge(site, rSite, null, vertex);
    d3_geom_voronoiAttachCircle(lArc);
    d3_geom_voronoiAttachCircle(rArc);
  }
  function d3_geom_voronoiLeftBreakPoint(arc, directrix) {
    var site = arc.site, rfocx = site.x, rfocy = site.y, pby2 = rfocy - directrix;
    if (!pby2) return rfocx;
    var lArc = arc.P;
    if (!lArc) return -Infinity;
    site = lArc.site;
    var lfocx = site.x, lfocy = site.y, plby2 = lfocy - directrix;
    if (!plby2) return lfocx;
    var hl = lfocx - rfocx, aby2 = 1 / pby2 - 1 / plby2, b = hl / plby2;
    if (aby2) return (-b + Math.sqrt(b * b - 2 * aby2 * (hl * hl / (-2 * plby2) - lfocy + plby2 / 2 + rfocy - pby2 / 2))) / aby2 + rfocx;
    return (rfocx + lfocx) / 2;
  }
  function d3_geom_voronoiRightBreakPoint(arc, directrix) {
    var rArc = arc.N;
    if (rArc) return d3_geom_voronoiLeftBreakPoint(rArc, directrix);
    var site = arc.site;
    return site.y === directrix ? site.x : Infinity;
  }
  function d3_geom_voronoiCell(site) {
    this.site = site;
    this.edges = [];
  }
  d3_geom_voronoiCell.prototype.prepare = function() {
    var halfEdges = this.edges, iHalfEdge = halfEdges.length, edge;
    while (iHalfEdge--) {
      edge = halfEdges[iHalfEdge].edge;
      if (!edge.b || !edge.a) halfEdges.splice(iHalfEdge, 1);
    }
    halfEdges.sort(d3_geom_voronoiHalfEdgeOrder);
    return halfEdges.length;
  };
  function d3_geom_voronoiCloseCells(extent) {
    var x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], x2, y2, x3, y3, cells = d3_geom_voronoiCells, iCell = cells.length, cell, iHalfEdge, halfEdges, nHalfEdges, start, end;
    while (iCell--) {
      cell = cells[iCell];
      if (!cell || !cell.prepare()) continue;
      halfEdges = cell.edges;
      nHalfEdges = halfEdges.length;
      iHalfEdge = 0;
      while (iHalfEdge < nHalfEdges) {
        end = halfEdges[iHalfEdge].end(), x3 = end.x, y3 = end.y;
        start = halfEdges[++iHalfEdge % nHalfEdges].start(), x2 = start.x, y2 = start.y;
        if (abs(x3 - x2) > ε || abs(y3 - y2) > ε) {
          halfEdges.splice(iHalfEdge, 0, new d3_geom_voronoiHalfEdge(d3_geom_voronoiCreateBorderEdge(cell.site, end, abs(x3 - x0) < ε && y1 - y3 > ε ? {
            x: x0,
            y: abs(x2 - x0) < ε ? y2 : y1
          } : abs(y3 - y1) < ε && x1 - x3 > ε ? {
            x: abs(y2 - y1) < ε ? x2 : x1,
            y: y1
          } : abs(x3 - x1) < ε && y3 - y0 > ε ? {
            x: x1,
            y: abs(x2 - x1) < ε ? y2 : y0
          } : abs(y3 - y0) < ε && x3 - x0 > ε ? {
            x: abs(y2 - y0) < ε ? x2 : x0,
            y: y0
          } : null), cell.site, null));
          ++nHalfEdges;
        }
      }
    }
  }
  function d3_geom_voronoiHalfEdgeOrder(a, b) {
    return b.angle - a.angle;
  }
  function d3_geom_voronoiCircle() {
    d3_geom_voronoiRedBlackNode(this);
    this.x = this.y = this.arc = this.site = this.cy = null;
  }
  function d3_geom_voronoiAttachCircle(arc) {
    var lArc = arc.P, rArc = arc.N;
    if (!lArc || !rArc) return;
    var lSite = lArc.site, cSite = arc.site, rSite = rArc.site;
    if (lSite === rSite) return;
    var bx = cSite.x, by = cSite.y, ax = lSite.x - bx, ay = lSite.y - by, cx = rSite.x - bx, cy = rSite.y - by;
    var d = 2 * (ax * cy - ay * cx);
    if (d >= -ε2) return;
    var ha = ax * ax + ay * ay, hc = cx * cx + cy * cy, x = (cy * ha - ay * hc) / d, y = (ax * hc - cx * ha) / d, cy = y + by;
    var circle = d3_geom_voronoiCirclePool.pop() || new d3_geom_voronoiCircle();
    circle.arc = arc;
    circle.site = cSite;
    circle.x = x + bx;
    circle.y = cy + Math.sqrt(x * x + y * y);
    circle.cy = cy;
    arc.circle = circle;
    var before = null, node = d3_geom_voronoiCircles._;
    while (node) {
      if (circle.y < node.y || circle.y === node.y && circle.x <= node.x) {
        if (node.L) node = node.L; else {
          before = node.P;
          break;
        }
      } else {
        if (node.R) node = node.R; else {
          before = node;
          break;
        }
      }
    }
    d3_geom_voronoiCircles.insert(before, circle);
    if (!before) d3_geom_voronoiFirstCircle = circle;
  }
  function d3_geom_voronoiDetachCircle(arc) {
    var circle = arc.circle;
    if (circle) {
      if (!circle.P) d3_geom_voronoiFirstCircle = circle.N;
      d3_geom_voronoiCircles.remove(circle);
      d3_geom_voronoiCirclePool.push(circle);
      d3_geom_voronoiRedBlackNode(circle);
      arc.circle = null;
    }
  }
  function d3_geom_voronoiClipEdges(extent) {
    var edges = d3_geom_voronoiEdges, clip = d3_geom_clipLine(extent[0][0], extent[0][1], extent[1][0], extent[1][1]), i = edges.length, e;
    while (i--) {
      e = edges[i];
      if (!d3_geom_voronoiConnectEdge(e, extent) || !clip(e) || abs(e.a.x - e.b.x) < ε && abs(e.a.y - e.b.y) < ε) {
        e.a = e.b = null;
        edges.splice(i, 1);
      }
    }
  }
  function d3_geom_voronoiConnectEdge(edge, extent) {
    var vb = edge.b;
    if (vb) return true;
    var va = edge.a, x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], lSite = edge.l, rSite = edge.r, lx = lSite.x, ly = lSite.y, rx = rSite.x, ry = rSite.y, fx = (lx + rx) / 2, fy = (ly + ry) / 2, fm, fb;
    if (ry === ly) {
      if (fx < x0 || fx >= x1) return;
      if (lx > rx) {
        if (!va) va = {
          x: fx,
          y: y0
        }; else if (va.y >= y1) return;
        vb = {
          x: fx,
          y: y1
        };
      } else {
        if (!va) va = {
          x: fx,
          y: y1
        }; else if (va.y < y0) return;
        vb = {
          x: fx,
          y: y0
        };
      }
    } else {
      fm = (lx - rx) / (ry - ly);
      fb = fy - fm * fx;
      if (fm < -1 || fm > 1) {
        if (lx > rx) {
          if (!va) va = {
            x: (y0 - fb) / fm,
            y: y0
          }; else if (va.y >= y1) return;
          vb = {
            x: (y1 - fb) / fm,
            y: y1
          };
        } else {
          if (!va) va = {
            x: (y1 - fb) / fm,
            y: y1
          }; else if (va.y < y0) return;
          vb = {
            x: (y0 - fb) / fm,
            y: y0
          };
        }
      } else {
        if (ly < ry) {
          if (!va) va = {
            x: x0,
            y: fm * x0 + fb
          }; else if (va.x >= x1) return;
          vb = {
            x: x1,
            y: fm * x1 + fb
          };
        } else {
          if (!va) va = {
            x: x1,
            y: fm * x1 + fb
          }; else if (va.x < x0) return;
          vb = {
            x: x0,
            y: fm * x0 + fb
          };
        }
      }
    }
    edge.a = va;
    edge.b = vb;
    return true;
  }
  function d3_geom_voronoiEdge(lSite, rSite) {
    this.l = lSite;
    this.r = rSite;
    this.a = this.b = null;
  }
  function d3_geom_voronoiCreateEdge(lSite, rSite, va, vb) {
    var edge = new d3_geom_voronoiEdge(lSite, rSite);
    d3_geom_voronoiEdges.push(edge);
    if (va) d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, va);
    if (vb) d3_geom_voronoiSetEdgeEnd(edge, rSite, lSite, vb);
    d3_geom_voronoiCells[lSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, lSite, rSite));
    d3_geom_voronoiCells[rSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, rSite, lSite));
    return edge;
  }
  function d3_geom_voronoiCreateBorderEdge(lSite, va, vb) {
    var edge = new d3_geom_voronoiEdge(lSite, null);
    edge.a = va;
    edge.b = vb;
    d3_geom_voronoiEdges.push(edge);
    return edge;
  }
  function d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, vertex) {
    if (!edge.a && !edge.b) {
      edge.a = vertex;
      edge.l = lSite;
      edge.r = rSite;
    } else if (edge.l === rSite) {
      edge.b = vertex;
    } else {
      edge.a = vertex;
    }
  }
  function d3_geom_voronoiHalfEdge(edge, lSite, rSite) {
    var va = edge.a, vb = edge.b;
    this.edge = edge;
    this.site = lSite;
    this.angle = rSite ? Math.atan2(rSite.y - lSite.y, rSite.x - lSite.x) : edge.l === lSite ? Math.atan2(vb.x - va.x, va.y - vb.y) : Math.atan2(va.x - vb.x, vb.y - va.y);
  }
  d3_geom_voronoiHalfEdge.prototype = {
    start: function() {
      return this.edge.l === this.site ? this.edge.a : this.edge.b;
    },
    end: function() {
      return this.edge.l === this.site ? this.edge.b : this.edge.a;
    }
  };
  function d3_geom_voronoiRedBlackTree() {
    this._ = null;
  }
  function d3_geom_voronoiRedBlackNode(node) {
    node.U = node.C = node.L = node.R = node.P = node.N = null;
  }
  d3_geom_voronoiRedBlackTree.prototype = {
    insert: function(after, node) {
      var parent, grandpa, uncle;
      if (after) {
        node.P = after;
        node.N = after.N;
        if (after.N) after.N.P = node;
        after.N = node;
        if (after.R) {
          after = after.R;
          while (after.L) after = after.L;
          after.L = node;
        } else {
          after.R = node;
        }
        parent = after;
      } else if (this._) {
        after = d3_geom_voronoiRedBlackFirst(this._);
        node.P = null;
        node.N = after;
        after.P = after.L = node;
        parent = after;
      } else {
        node.P = node.N = null;
        this._ = node;
        parent = null;
      }
      node.L = node.R = null;
      node.U = parent;
      node.C = true;
      after = node;
      while (parent && parent.C) {
        grandpa = parent.U;
        if (parent === grandpa.L) {
          uncle = grandpa.R;
          if (uncle && uncle.C) {
            parent.C = uncle.C = false;
            grandpa.C = true;
            after = grandpa;
          } else {
            if (after === parent.R) {
              d3_geom_voronoiRedBlackRotateLeft(this, parent);
              after = parent;
              parent = after.U;
            }
            parent.C = false;
            grandpa.C = true;
            d3_geom_voronoiRedBlackRotateRight(this, grandpa);
          }
        } else {
          uncle = grandpa.L;
          if (uncle && uncle.C) {
            parent.C = uncle.C = false;
            grandpa.C = true;
            after = grandpa;
          } else {
            if (after === parent.L) {
              d3_geom_voronoiRedBlackRotateRight(this, parent);
              after = parent;
              parent = after.U;
            }
            parent.C = false;
            grandpa.C = true;
            d3_geom_voronoiRedBlackRotateLeft(this, grandpa);
          }
        }
        parent = after.U;
      }
      this._.C = false;
    },
    remove: function(node) {
      if (node.N) node.N.P = node.P;
      if (node.P) node.P.N = node.N;
      node.N = node.P = null;
      var parent = node.U, sibling, left = node.L, right = node.R, next, red;
      if (!left) next = right; else if (!right) next = left; else next = d3_geom_voronoiRedBlackFirst(right);
      if (parent) {
        if (parent.L === node) parent.L = next; else parent.R = next;
      } else {
        this._ = next;
      }
      if (left && right) {
        red = next.C;
        next.C = node.C;
        next.L = left;
        left.U = next;
        if (next !== right) {
          parent = next.U;
          next.U = node.U;
          node = next.R;
          parent.L = node;
          next.R = right;
          right.U = next;
        } else {
          next.U = parent;
          parent = next;
          node = next.R;
        }
      } else {
        red = node.C;
        node = next;
      }
      if (node) node.U = parent;
      if (red) return;
      if (node && node.C) {
        node.C = false;
        return;
      }
      do {
        if (node === this._) break;
        if (node === parent.L) {
          sibling = parent.R;
          if (sibling.C) {
            sibling.C = false;
            parent.C = true;
            d3_geom_voronoiRedBlackRotateLeft(this, parent);
            sibling = parent.R;
          }
          if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) {
            if (!sibling.R || !sibling.R.C) {
              sibling.L.C = false;
              sibling.C = true;
              d3_geom_voronoiRedBlackRotateRight(this, sibling);
              sibling = parent.R;
            }
            sibling.C = parent.C;
            parent.C = sibling.R.C = false;
            d3_geom_voronoiRedBlackRotateLeft(this, parent);
            node = this._;
            break;
          }
        } else {
          sibling = parent.L;
          if (sibling.C) {
            sibling.C = false;
            parent.C = true;
            d3_geom_voronoiRedBlackRotateRight(this, parent);
            sibling = parent.L;
          }
          if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) {
            if (!sibling.L || !sibling.L.C) {
              sibling.R.C = false;
              sibling.C = true;
              d3_geom_voronoiRedBlackRotateLeft(this, sibling);
              sibling = parent.L;
            }
            sibling.C = parent.C;
            parent.C = sibling.L.C = false;
            d3_geom_voronoiRedBlackRotateRight(this, parent);
            node = this._;
            break;
          }
        }
        sibling.C = true;
        node = parent;
        parent = parent.U;
      } while (!node.C);
      if (node) node.C = false;
    }
  };
  function d3_geom_voronoiRedBlackRotateLeft(tree, node) {
    var p = node, q = node.R, parent = p.U;
    if (parent) {
      if (parent.L === p) parent.L = q; else parent.R = q;
    } else {
      tree._ = q;
    }
    q.U = parent;
    p.U = q;
    p.R = q.L;
    if (p.R) p.R.U = p;
    q.L = p;
  }
  function d3_geom_voronoiRedBlackRotateRight(tree, node) {
    var p = node, q = node.L, parent = p.U;
    if (parent) {
      if (parent.L === p) parent.L = q; else parent.R = q;
    } else {
      tree._ = q;
    }
    q.U = parent;
    p.U = q;
    p.L = q.R;
    if (p.L) p.L.U = p;
    q.R = p;
  }
  function d3_geom_voronoiRedBlackFirst(node) {
    while (node.L) node = node.L;
    return node;
  }
  function d3_geom_voronoi(sites, bbox) {
    var site = sites.sort(d3_geom_voronoiVertexOrder).pop(), x0, y0, circle;
    d3_geom_voronoiEdges = [];
    d3_geom_voronoiCells = new Array(sites.length);
    d3_geom_voronoiBeaches = new d3_geom_voronoiRedBlackTree();
    d3_geom_voronoiCircles = new d3_geom_voronoiRedBlackTree();
    while (true) {
      circle = d3_geom_voronoiFirstCircle;
      if (site && (!circle || site.y < circle.y || site.y === circle.y && site.x < circle.x)) {
        if (site.x !== x0 || site.y !== y0) {
          d3_geom_voronoiCells[site.i] = new d3_geom_voronoiCell(site);
          d3_geom_voronoiAddBeach(site);
          x0 = site.x, y0 = site.y;
        }
        site = sites.pop();
      } else if (circle) {
        d3_geom_voronoiRemoveBeach(circle.arc);
      } else {
        break;
      }
    }
    if (bbox) d3_geom_voronoiClipEdges(bbox), d3_geom_voronoiCloseCells(bbox);
    var diagram = {
      cells: d3_geom_voronoiCells,
      edges: d3_geom_voronoiEdges
    };
    d3_geom_voronoiBeaches = d3_geom_voronoiCircles = d3_geom_voronoiEdges = d3_geom_voronoiCells = null;
    return diagram;
  }
  function d3_geom_voronoiVertexOrder(a, b) {
    return b.y - a.y || b.x - a.x;
  }
  d3.geom.voronoi = function(points) {
    var x = d3_geom_pointX, y = d3_geom_pointY, fx = x, fy = y, clipExtent = d3_geom_voronoiClipExtent;
    if (points) return voronoi(points);
    function voronoi(data) {
      var polygons = new Array(data.length), x0 = clipExtent[0][0], y0 = clipExtent[0][1], x1 = clipExtent[1][0], y1 = clipExtent[1][1];
      d3_geom_voronoi(sites(data), clipExtent).cells.forEach(function(cell, i) {
        var edges = cell.edges, site = cell.site, polygon = polygons[i] = edges.length ? edges.map(function(e) {
          var s = e.start();
          return [ s.x, s.y ];
        }) : site.x >= x0 && site.x <= x1 && site.y >= y0 && site.y <= y1 ? [ [ x0, y1 ], [ x1, y1 ], [ x1, y0 ], [ x0, y0 ] ] : [];
        polygon.point = data[i];
      });
      return polygons;
    }
    function sites(data) {
      return data.map(function(d, i) {
        return {
          x: Math.round(fx(d, i) / ε) * ε,
          y: Math.round(fy(d, i) / ε) * ε,
          i: i
        };
      });
    }
    voronoi.links = function(data) {
      return d3_geom_voronoi(sites(data)).edges.filter(function(edge) {
        return edge.l && edge.r;
      }).map(function(edge) {
        return {
          source: data[edge.l.i],
          target: data[edge.r.i]
        };
      });
    };
    voronoi.triangles = function(data) {
      var triangles = [];
      d3_geom_voronoi(sites(data)).cells.forEach(function(cell, i) {
        var site = cell.site, edges = cell.edges.sort(d3_geom_voronoiHalfEdgeOrder), j = -1, m = edges.length, e0, s0, e1 = edges[m - 1].edge, s1 = e1.l === site ? e1.r : e1.l;
        while (++j < m) {
          e0 = e1;
          s0 = s1;
          e1 = edges[j].edge;
          s1 = e1.l === site ? e1.r : e1.l;
          if (i < s0.i && i < s1.i && d3_geom_voronoiTriangleArea(site, s0, s1) < 0) {
            triangles.push([ data[i], data[s0.i], data[s1.i] ]);
          }
        }
      });
      return triangles;
    };
    voronoi.x = function(_) {
      return arguments.length ? (fx = d3_functor(x = _), voronoi) : x;
    };
    voronoi.y = function(_) {
      return arguments.length ? (fy = d3_functor(y = _), voronoi) : y;
    };
    voronoi.clipExtent = function(_) {
      if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent;
      clipExtent = _ == null ? d3_geom_voronoiClipExtent : _;
      return voronoi;
    };
    voronoi.size = function(_) {
      if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent && clipExtent[1];
      return voronoi.clipExtent(_ && [ [ 0, 0 ], _ ]);
    };
    return voronoi;
  };
  var d3_geom_voronoiClipExtent = [ [ -1e6, -1e6 ], [ 1e6, 1e6 ] ];
  function d3_geom_voronoiTriangleArea(a, b, c) {
    return (a.x - c.x) * (b.y - a.y) - (a.x - b.x) * (c.y - a.y);
  }
  d3.geom.delaunay = function(vertices) {
    return d3.geom.voronoi().triangles(vertices);
  };
  d3.geom.quadtree = function(points, x1, y1, x2, y2) {
    var x = d3_geom_pointX, y = d3_geom_pointY, compat;
    if (compat = arguments.length) {
      x = d3_geom_quadtreeCompatX;
      y = d3_geom_quadtreeCompatY;
      if (compat === 3) {
        y2 = y1;
        x2 = x1;
        y1 = x1 = 0;
      }
      return quadtree(points);
    }
    function quadtree(data) {
      var d, fx = d3_functor(x), fy = d3_functor(y), xs, ys, i, n, x1_, y1_, x2_, y2_;
      if (x1 != null) {
        x1_ = x1, y1_ = y1, x2_ = x2, y2_ = y2;
      } else {
        x2_ = y2_ = -(x1_ = y1_ = Infinity);
        xs = [], ys = [];
        n = data.length;
        if (compat) for (i = 0; i < n; ++i) {
          d = data[i];
          if (d.x < x1_) x1_ = d.x;
          if (d.y < y1_) y1_ = d.y;
          if (d.x > x2_) x2_ = d.x;
          if (d.y > y2_) y2_ = d.y;
          xs.push(d.x);
          ys.push(d.y);
        } else for (i = 0; i < n; ++i) {
          var x_ = +fx(d = data[i], i), y_ = +fy(d, i);
          if (x_ < x1_) x1_ = x_;
          if (y_ < y1_) y1_ = y_;
          if (x_ > x2_) x2_ = x_;
          if (y_ > y2_) y2_ = y_;
          xs.push(x_);
          ys.push(y_);
        }
      }
      var dx = x2_ - x1_, dy = y2_ - y1_;
      if (dx > dy) y2_ = y1_ + dx; else x2_ = x1_ + dy;
      function insert(n, d, x, y, x1, y1, x2, y2) {
        if (isNaN(x) || isNaN(y)) return;
        if (n.leaf) {
          var nx = n.x, ny = n.y;
          if (nx != null) {
            if (abs(nx - x) + abs(ny - y) < .01) {
              insertChild(n, d, x, y, x1, y1, x2, y2);
            } else {
              var nPoint = n.point;
              n.x = n.y = n.point = null;
              insertChild(n, nPoint, nx, ny, x1, y1, x2, y2);
              insertChild(n, d, x, y, x1, y1, x2, y2);
            }
          } else {
            n.x = x, n.y = y, n.point = d;
          }
        } else {
          insertChild(n, d, x, y, x1, y1, x2, y2);
        }
      }
      function insertChild(n, d, x, y, x1, y1, x2, y2) {
        var xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym, i = below << 1 | right;
        n.leaf = false;
        n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode());
        if (right) x1 = xm; else x2 = xm;
        if (below) y1 = ym; else y2 = ym;
        insert(n, d, x, y, x1, y1, x2, y2);
      }
      var root = d3_geom_quadtreeNode();
      root.add = function(d) {
        insert(root, d, +fx(d, ++i), +fy(d, i), x1_, y1_, x2_, y2_);
      };
      root.visit = function(f) {
        d3_geom_quadtreeVisit(f, root, x1_, y1_, x2_, y2_);
      };
      root.find = function(point) {
        return d3_geom_quadtreeFind(root, point[0], point[1], x1_, y1_, x2_, y2_);
      };
      i = -1;
      if (x1 == null) {
        while (++i < n) {
          insert(root, data[i], xs[i], ys[i], x1_, y1_, x2_, y2_);
        }
        --i;
      } else data.forEach(root.add);
      xs = ys = data = d = null;
      return root;
    }
    quadtree.x = function(_) {
      return arguments.length ? (x = _, quadtree) : x;
    };
    quadtree.y = function(_) {
      return arguments.length ? (y = _, quadtree) : y;
    };
    quadtree.extent = function(_) {
      if (!arguments.length) return x1 == null ? null : [ [ x1, y1 ], [ x2, y2 ] ];
      if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = +_[0][0], y1 = +_[0][1], x2 = +_[1][0], 
      y2 = +_[1][1];
      return quadtree;
    };
    quadtree.size = function(_) {
      if (!arguments.length) return x1 == null ? null : [ x2 - x1, y2 - y1 ];
      if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = y1 = 0, x2 = +_[0], y2 = +_[1];
      return quadtree;
    };
    return quadtree;
  };
  function d3_geom_quadtreeCompatX(d) {
    return d.x;
  }
  function d3_geom_quadtreeCompatY(d) {
    return d.y;
  }
  function d3_geom_quadtreeNode() {
    return {
      leaf: true,
      nodes: [],
      point: null,
      x: null,
      y: null
    };
  }
  function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) {
    if (!f(node, x1, y1, x2, y2)) {
      var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes;
      if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy);
      if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy);
      if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2);
      if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2);
    }
  }
  function d3_geom_quadtreeFind(root, x, y, x0, y0, x3, y3) {
    var minDistance2 = Infinity, closestPoint;
    (function find(node, x1, y1, x2, y2) {
      if (x1 > x3 || y1 > y3 || x2 < x0 || y2 < y0) return;
      if (point = node.point) {
        var point, dx = x - node.x, dy = y - node.y, distance2 = dx * dx + dy * dy;
        if (distance2 < minDistance2) {
          var distance = Math.sqrt(minDistance2 = distance2);
          x0 = x - distance, y0 = y - distance;
          x3 = x + distance, y3 = y + distance;
          closestPoint = point;
        }
      }
      var children = node.nodes, xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym;
      for (var i = below << 1 | right, j = i + 4; i < j; ++i) {
        if (node = children[i & 3]) switch (i & 3) {
         case 0:
          find(node, x1, y1, xm, ym);
          break;

         case 1:
          find(node, xm, y1, x2, ym);
          break;

         case 2:
          find(node, x1, ym, xm, y2);
          break;

         case 3:
          find(node, xm, ym, x2, y2);
          break;
        }
      }
    })(root, x0, y0, x3, y3);
    return closestPoint;
  }
  d3.interpolateRgb = d3_interpolateRgb;
  function d3_interpolateRgb(a, b) {
    a = d3.rgb(a);
    b = d3.rgb(b);
    var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab;
    return function(t) {
      return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t));
    };
  }
  d3.interpolateObject = d3_interpolateObject;
  function d3_interpolateObject(a, b) {
    var i = {}, c = {}, k;
    for (k in a) {
      if (k in b) {
        i[k] = d3_interpolate(a[k], b[k]);
      } else {
        c[k] = a[k];
      }
    }
    for (k in b) {
      if (!(k in a)) {
        c[k] = b[k];
      }
    }
    return function(t) {
      for (k in i) c[k] = i[k](t);
      return c;
    };
  }
  d3.interpolateNumber = d3_interpolateNumber;
  function d3_interpolateNumber(a, b) {
    a = +a, b = +b;
    return function(t) {
      return a * (1 - t) + b * t;
    };
  }
  d3.interpolateString = d3_interpolateString;
  function d3_interpolateString(a, b) {
    var bi = d3_interpolate_numberA.lastIndex = d3_interpolate_numberB.lastIndex = 0, am, bm, bs, i = -1, s = [], q = [];
    a = a + "", b = b + "";
    while ((am = d3_interpolate_numberA.exec(a)) && (bm = d3_interpolate_numberB.exec(b))) {
      if ((bs = bm.index) > bi) {
        bs = b.slice(bi, bs);
        if (s[i]) s[i] += bs; else s[++i] = bs;
      }
      if ((am = am[0]) === (bm = bm[0])) {
        if (s[i]) s[i] += bm; else s[++i] = bm;
      } else {
        s[++i] = null;
        q.push({
          i: i,
          x: d3_interpolateNumber(am, bm)
        });
      }
      bi = d3_interpolate_numberB.lastIndex;
    }
    if (bi < b.length) {
      bs = b.slice(bi);
      if (s[i]) s[i] += bs; else s[++i] = bs;
    }
    return s.length < 2 ? q[0] ? (b = q[0].x, function(t) {
      return b(t) + "";
    }) : function() {
      return b;
    } : (b = q.length, function(t) {
      for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t);
      return s.join("");
    });
  }
  var d3_interpolate_numberA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g, d3_interpolate_numberB = new RegExp(d3_interpolate_numberA.source, "g");
  d3.interpolate = d3_interpolate;
  function d3_interpolate(a, b) {
    var i = d3.interpolators.length, f;
    while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ;
    return f;
  }
  d3.interpolators = [ function(a, b) {
    var t = typeof b;
    return (t === "string" ? d3_rgb_names.has(b.toLowerCase()) || /^(#|rgb\(|hsl\()/i.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_color ? d3_interpolateRgb : Array.isArray(b) ? d3_interpolateArray : t === "object" && isNaN(b) ? d3_interpolateObject : d3_interpolateNumber)(a, b);
  } ];
  d3.interpolateArray = d3_interpolateArray;
  function d3_interpolateArray(a, b) {
    var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i;
    for (i = 0; i < n0; ++i) x.push(d3_interpolate(a[i], b[i]));
    for (;i < na; ++i) c[i] = a[i];
    for (;i < nb; ++i) c[i] = b[i];
    return function(t) {
      for (i = 0; i < n0; ++i) c[i] = x[i](t);
      return c;
    };
  }
  var d3_ease_default = function() {
    return d3_identity;
  };
  var d3_ease = d3.map({
    linear: d3_ease_default,
    poly: d3_ease_poly,
    quad: function() {
      return d3_ease_quad;
    },
    cubic: function() {
      return d3_ease_cubic;
    },
    sin: function() {
      return d3_ease_sin;
    },
    exp: function() {
      return d3_ease_exp;
    },
    circle: function() {
      return d3_ease_circle;
    },
    elastic: d3_ease_elastic,
    back: d3_ease_back,
    bounce: function() {
      return d3_ease_bounce;
    }
  });
  var d3_ease_mode = d3.map({
    "in": d3_identity,
    out: d3_ease_reverse,
    "in-out": d3_ease_reflect,
    "out-in": function(f) {
      return d3_ease_reflect(d3_ease_reverse(f));
    }
  });
  d3.ease = function(name) {
    var i = name.indexOf("-"), t = i >= 0 ? name.slice(0, i) : name, m = i >= 0 ? name.slice(i + 1) : "in";
    t = d3_ease.get(t) || d3_ease_default;
    m = d3_ease_mode.get(m) || d3_identity;
    return d3_ease_clamp(m(t.apply(null, d3_arraySlice.call(arguments, 1))));
  };
  function d3_ease_clamp(f) {
    return function(t) {
      return t <= 0 ? 0 : t >= 1 ? 1 : f(t);
    };
  }
  function d3_ease_reverse(f) {
    return function(t) {
      return 1 - f(1 - t);
    };
  }
  function d3_ease_reflect(f) {
    return function(t) {
      return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t));
    };
  }
  function d3_ease_quad(t) {
    return t * t;
  }
  function d3_ease_cubic(t) {
    return t * t * t;
  }
  function d3_ease_cubicInOut(t) {
    if (t <= 0) return 0;
    if (t >= 1) return 1;
    var t2 = t * t, t3 = t2 * t;
    return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75);
  }
  function d3_ease_poly(e) {
    return function(t) {
      return Math.pow(t, e);
    };
  }
  function d3_ease_sin(t) {
    return 1 - Math.cos(t * halfπ);
  }
  function d3_ease_exp(t) {
    return Math.pow(2, 10 * (t - 1));
  }
  function d3_ease_circle(t) {
    return 1 - Math.sqrt(1 - t * t);
  }
  function d3_ease_elastic(a, p) {
    var s;
    if (arguments.length < 2) p = .45;
    if (arguments.length) s = p / τ * Math.asin(1 / a); else a = 1, s = p / 4;
    return function(t) {
      return 1 + a * Math.pow(2, -10 * t) * Math.sin((t - s) * τ / p);
    };
  }
  function d3_ease_back(s) {
    if (!s) s = 1.70158;
    return function(t) {
      return t * t * ((s + 1) * t - s);
    };
  }
  function d3_ease_bounce(t) {
    return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375;
  }
  d3.interpolateHcl = d3_interpolateHcl;
  function d3_interpolateHcl(a, b) {
    a = d3.hcl(a);
    b = d3.hcl(b);
    var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al;
    if (isNaN(bc)) bc = 0, ac = isNaN(ac) ? b.c : ac;
    if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360;
    return function(t) {
      return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + "";
    };
  }
  d3.interpolateHsl = d3_interpolateHsl;
  function d3_interpolateHsl(a, b) {
    a = d3.hsl(a);
    b = d3.hsl(b);
    var ah = a.h, as = a.s, al = a.l, bh = b.h - ah, bs = b.s - as, bl = b.l - al;
    if (isNaN(bs)) bs = 0, as = isNaN(as) ? b.s : as;
    if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360;
    return function(t) {
      return d3_hsl_rgb(ah + bh * t, as + bs * t, al + bl * t) + "";
    };
  }
  d3.interpolateLab = d3_interpolateLab;
  function d3_interpolateLab(a, b) {
    a = d3.lab(a);
    b = d3.lab(b);
    var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab;
    return function(t) {
      return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + "";
    };
  }
  d3.interpolateRound = d3_interpolateRound;
  function d3_interpolateRound(a, b) {
    b -= a;
    return function(t) {
      return Math.round(a + b * t);
    };
  }
  d3.transform = function(string) {
    var g = d3_document.createElementNS(d3.ns.prefix.svg, "g");
    return (d3.transform = function(string) {
      if (string != null) {
        g.setAttribute("transform", string);
        var t = g.transform.baseVal.consolidate();
      }
      return new d3_transform(t ? t.matrix : d3_transformIdentity);
    })(string);
  };
  function d3_transform(m) {
    var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0;
    if (r0[0] * r1[1] < r1[0] * r0[1]) {
      r0[0] *= -1;
      r0[1] *= -1;
      kx *= -1;
      kz *= -1;
    }
    this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_degrees;
    this.translate = [ m.e, m.f ];
    this.scale = [ kx, ky ];
    this.skew = ky ? Math.atan2(kz, ky) * d3_degrees : 0;
  }
  d3_transform.prototype.toString = function() {
    return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")";
  };
  function d3_transformDot(a, b) {
    return a[0] * b[0] + a[1] * b[1];
  }
  function d3_transformNormalize(a) {
    var k = Math.sqrt(d3_transformDot(a, a));
    if (k) {
      a[0] /= k;
      a[1] /= k;
    }
    return k;
  }
  function d3_transformCombine(a, b, k) {
    a[0] += k * b[0];
    a[1] += k * b[1];
    return a;
  }
  var d3_transformIdentity = {
    a: 1,
    b: 0,
    c: 0,
    d: 1,
    e: 0,
    f: 0
  };
  d3.interpolateTransform = d3_interpolateTransform;
  function d3_interpolateTransformPop(s) {
    return s.length ? s.pop() + "," : "";
  }
  function d3_interpolateTranslate(ta, tb, s, q) {
    if (ta[0] !== tb[0] || ta[1] !== tb[1]) {
      var i = s.push("translate(", null, ",", null, ")");
      q.push({
        i: i - 4,
        x: d3_interpolateNumber(ta[0], tb[0])
      }, {
        i: i - 2,
        x: d3_interpolateNumber(ta[1], tb[1])
      });
    } else if (tb[0] || tb[1]) {
      s.push("translate(" + tb + ")");
    }
  }
  function d3_interpolateRotate(ra, rb, s, q) {
    if (ra !== rb) {
      if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360;
      q.push({
        i: s.push(d3_interpolateTransformPop(s) + "rotate(", null, ")") - 2,
        x: d3_interpolateNumber(ra, rb)
      });
    } else if (rb) {
      s.push(d3_interpolateTransformPop(s) + "rotate(" + rb + ")");
    }
  }
  function d3_interpolateSkew(wa, wb, s, q) {
    if (wa !== wb) {
      q.push({
        i: s.push(d3_interpolateTransformPop(s) + "skewX(", null, ")") - 2,
        x: d3_interpolateNumber(wa, wb)
      });
    } else if (wb) {
      s.push(d3_interpolateTransformPop(s) + "skewX(" + wb + ")");
    }
  }
  function d3_interpolateScale(ka, kb, s, q) {
    if (ka[0] !== kb[0] || ka[1] !== kb[1]) {
      var i = s.push(d3_interpolateTransformPop(s) + "scale(", null, ",", null, ")");
      q.push({
        i: i - 4,
        x: d3_interpolateNumber(ka[0], kb[0])
      }, {
        i: i - 2,
        x: d3_interpolateNumber(ka[1], kb[1])
      });
    } else if (kb[0] !== 1 || kb[1] !== 1) {
      s.push(d3_interpolateTransformPop(s) + "scale(" + kb + ")");
    }
  }
  function d3_interpolateTransform(a, b) {
    var s = [], q = [];
    a = d3.transform(a), b = d3.transform(b);
    d3_interpolateTranslate(a.translate, b.translate, s, q);
    d3_interpolateRotate(a.rotate, b.rotate, s, q);
    d3_interpolateSkew(a.skew, b.skew, s, q);
    d3_interpolateScale(a.scale, b.scale, s, q);
    a = b = null;
    return function(t) {
      var i = -1, n = q.length, o;
      while (++i < n) s[(o = q[i]).i] = o.x(t);
      return s.join("");
    };
  }
  function d3_uninterpolateNumber(a, b) {
    b = (b -= a = +a) || 1 / b;
    return function(x) {
      return (x - a) / b;
    };
  }
  function d3_uninterpolateClamp(a, b) {
    b = (b -= a = +a) || 1 / b;
    return function(x) {
      return Math.max(0, Math.min(1, (x - a) / b));
    };
  }
  d3.layout = {};
  d3.layout.bundle = function() {
    return function(links) {
      var paths = [], i = -1, n = links.length;
      while (++i < n) paths.push(d3_layout_bundlePath(links[i]));
      return paths;
    };
  };
  function d3_layout_bundlePath(link) {
    var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ];
    while (start !== lca) {
      start = start.parent;
      points.push(start);
    }
    var k = points.length;
    while (end !== lca) {
      points.splice(k, 0, end);
      end = end.parent;
    }
    return points;
  }
  function d3_layout_bundleAncestors(node) {
    var ancestors = [], parent = node.parent;
    while (parent != null) {
      ancestors.push(node);
      node = parent;
      parent = parent.parent;
    }
    ancestors.push(node);
    return ancestors;
  }
  function d3_layout_bundleLeastCommonAncestor(a, b) {
    if (a === b) return a;
    var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null;
    while (aNode === bNode) {
      sharedNode = aNode;
      aNode = aNodes.pop();
      bNode = bNodes.pop();
    }
    return sharedNode;
  }
  d3.layout.chord = function() {
    var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords;
    function relayout() {
      var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j;
      chords = [];
      groups = [];
      k = 0, i = -1;
      while (++i < n) {
        x = 0, j = -1;
        while (++j < n) {
          x += matrix[i][j];
        }
        groupSums.push(x);
        subgroupIndex.push(d3.range(n));
        k += x;
      }
      if (sortGroups) {
        groupIndex.sort(function(a, b) {
          return sortGroups(groupSums[a], groupSums[b]);
        });
      }
      if (sortSubgroups) {
        subgroupIndex.forEach(function(d, i) {
          d.sort(function(a, b) {
            return sortSubgroups(matrix[i][a], matrix[i][b]);
          });
        });
      }
      k = (τ - padding * n) / k;
      x = 0, i = -1;
      while (++i < n) {
        x0 = x, j = -1;
        while (++j < n) {
          var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k;
          subgroups[di + "-" + dj] = {
            index: di,
            subindex: dj,
            startAngle: a0,
            endAngle: a1,
            value: v
          };
        }
        groups[di] = {
          index: di,
          startAngle: x0,
          endAngle: x,
          value: groupSums[di]
        };
        x += padding;
      }
      i = -1;
      while (++i < n) {
        j = i - 1;
        while (++j < n) {
          var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i];
          if (source.value || target.value) {
            chords.push(source.value < target.value ? {
              source: target,
              target: source
            } : {
              source: source,
              target: target
            });
          }
        }
      }
      if (sortChords) resort();
    }
    function resort() {
      chords.sort(function(a, b) {
        return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2);
      });
    }
    chord.matrix = function(x) {
      if (!arguments.length) return matrix;
      n = (matrix = x) && matrix.length;
      chords = groups = null;
      return chord;
    };
    chord.padding = function(x) {
      if (!arguments.length) return padding;
      padding = x;
      chords = groups = null;
      return chord;
    };
    chord.sortGroups = function(x) {
      if (!arguments.length) return sortGroups;
      sortGroups = x;
      chords = groups = null;
      return chord;
    };
    chord.sortSubgroups = function(x) {
      if (!arguments.length) return sortSubgroups;
      sortSubgroups = x;
      chords = null;
      return chord;
    };
    chord.sortChords = function(x) {
      if (!arguments.length) return sortChords;
      sortChords = x;
      if (chords) resort();
      return chord;
    };
    chord.chords = function() {
      if (!chords) relayout();
      return chords;
    };
    chord.groups = function() {
      if (!groups) relayout();
      return groups;
    };
    return chord;
  };
  d3.layout.force = function() {
    var force = {}, event = d3.dispatch("start", "tick", "end"), timer, size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, chargeDistance2 = d3_layout_forceChargeDistance2, gravity = .1, theta2 = .64, nodes = [], links = [], distances, strengths, charges;
    function repulse(node) {
      return function(quad, x1, _, x2) {
        if (quad.point !== node) {
          var dx = quad.cx - node.x, dy = quad.cy - node.y, dw = x2 - x1, dn = dx * dx + dy * dy;
          if (dw * dw / theta2 < dn) {
            if (dn < chargeDistance2) {
              var k = quad.charge / dn;
              node.px -= dx * k;
              node.py -= dy * k;
            }
            return true;
          }
          if (quad.point && dn && dn < chargeDistance2) {
            var k = quad.pointCharge / dn;
            node.px -= dx * k;
            node.py -= dy * k;
          }
        }
        return !quad.charge;
      };
    }
    force.tick = function() {
      if ((alpha *= .99) < .005) {
        timer = null;
        event.end({
          type: "end",
          alpha: alpha = 0
        });
        return true;
      }
      var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y;
      for (i = 0; i < m; ++i) {
        o = links[i];
        s = o.source;
        t = o.target;
        x = t.x - s.x;
        y = t.y - s.y;
        if (l = x * x + y * y) {
          l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l;
          x *= l;
          y *= l;
          t.x -= x * (k = s.weight + t.weight ? s.weight / (s.weight + t.weight) : .5);
          t.y -= y * k;
          s.x += x * (k = 1 - k);
          s.y += y * k;
        }
      }
      if (k = alpha * gravity) {
        x = size[0] / 2;
        y = size[1] / 2;
        i = -1;
        if (k) while (++i < n) {
          o = nodes[i];
          o.x += (x - o.x) * k;
          o.y += (y - o.y) * k;
        }
      }
      if (charge) {
        d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges);
        i = -1;
        while (++i < n) {
          if (!(o = nodes[i]).fixed) {
            q.visit(repulse(o));
          }
        }
      }
      i = -1;
      while (++i < n) {
        o = nodes[i];
        if (o.fixed) {
          o.x = o.px;
          o.y = o.py;
        } else {
          o.x -= (o.px - (o.px = o.x)) * friction;
          o.y -= (o.py - (o.py = o.y)) * friction;
        }
      }
      event.tick({
        type: "tick",
        alpha: alpha
      });
    };
    force.nodes = function(x) {
      if (!arguments.length) return nodes;
      nodes = x;
      return force;
    };
    force.links = function(x) {
      if (!arguments.length) return links;
      links = x;
      return force;
    };
    force.size = function(x) {
      if (!arguments.length) return size;
      size = x;
      return force;
    };
    force.linkDistance = function(x) {
      if (!arguments.length) return linkDistance;
      linkDistance = typeof x === "function" ? x : +x;
      return force;
    };
    force.distance = force.linkDistance;
    force.linkStrength = function(x) {
      if (!arguments.length) return linkStrength;
      linkStrength = typeof x === "function" ? x : +x;
      return force;
    };
    force.friction = function(x) {
      if (!arguments.length) return friction;
      friction = +x;
      return force;
    };
    force.charge = function(x) {
      if (!arguments.length) return charge;
      charge = typeof x === "function" ? x : +x;
      return force;
    };
    force.chargeDistance = function(x) {
      if (!arguments.length) return Math.sqrt(chargeDistance2);
      chargeDistance2 = x * x;
      return force;
    };
    force.gravity = function(x) {
      if (!arguments.length) return gravity;
      gravity = +x;
      return force;
    };
    force.theta = function(x) {
      if (!arguments.length) return Math.sqrt(theta2);
      theta2 = x * x;
      return force;
    };
    force.alpha = function(x) {
      if (!arguments.length) return alpha;
      x = +x;
      if (alpha) {
        if (x > 0) {
          alpha = x;
        } else {
          timer.c = null, timer.t = NaN, timer = null;
          event.end({
            type: "end",
            alpha: alpha = 0
          });
        }
      } else if (x > 0) {
        event.start({
          type: "start",
          alpha: alpha = x
        });
        timer = d3_timer(force.tick);
      }
      return force;
    };
    force.start = function() {
      var i, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o;
      for (i = 0; i < n; ++i) {
        (o = nodes[i]).index = i;
        o.weight = 0;
      }
      for (i = 0; i < m; ++i) {
        o = links[i];
        if (typeof o.source == "number") o.source = nodes[o.source];
        if (typeof o.target == "number") o.target = nodes[o.target];
        ++o.source.weight;
        ++o.target.weight;
      }
      for (i = 0; i < n; ++i) {
        o = nodes[i];
        if (isNaN(o.x)) o.x = position("x", w);
        if (isNaN(o.y)) o.y = position("y", h);
        if (isNaN(o.px)) o.px = o.x;
        if (isNaN(o.py)) o.py = o.y;
      }
      distances = [];
      if (typeof linkDistance === "function") for (i = 0; i < m; ++i) distances[i] = +linkDistance.call(this, links[i], i); else for (i = 0; i < m; ++i) distances[i] = linkDistance;
      strengths = [];
      if (typeof linkStrength === "function") for (i = 0; i < m; ++i) strengths[i] = +linkStrength.call(this, links[i], i); else for (i = 0; i < m; ++i) strengths[i] = linkStrength;
      charges = [];
      if (typeof charge === "function") for (i = 0; i < n; ++i) charges[i] = +charge.call(this, nodes[i], i); else for (i = 0; i < n; ++i) charges[i] = charge;
      function position(dimension, size) {
        if (!neighbors) {
          neighbors = new Array(n);
          for (j = 0; j < n; ++j) {
            neighbors[j] = [];
          }
          for (j = 0; j < m; ++j) {
            var o = links[j];
            neighbors[o.source.index].push(o.target);
            neighbors[o.target.index].push(o.source);
          }
        }
        var candidates = neighbors[i], j = -1, l = candidates.length, x;
        while (++j < l) if (!isNaN(x = candidates[j][dimension])) return x;
        return Math.random() * size;
      }
      return force.resume();
    };
    force.resume = function() {
      return force.alpha(.1);
    };
    force.stop = function() {
      return force.alpha(0);
    };
    force.drag = function() {
      if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart.force", d3_layout_forceDragstart).on("drag.force", dragmove).on("dragend.force", d3_layout_forceDragend);
      if (!arguments.length) return drag;
      this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag);
    };
    function dragmove(d) {
      d.px = d3.event.x, d.py = d3.event.y;
      force.resume();
    }
    return d3.rebind(force, event, "on");
  };
  function d3_layout_forceDragstart(d) {
    d.fixed |= 2;
  }
  function d3_layout_forceDragend(d) {
    d.fixed &= ~6;
  }
  function d3_layout_forceMouseover(d) {
    d.fixed |= 4;
    d.px = d.x, d.py = d.y;
  }
  function d3_layout_forceMouseout(d) {
    d.fixed &= ~4;
  }
  function d3_layout_forceAccumulate(quad, alpha, charges) {
    var cx = 0, cy = 0;
    quad.charge = 0;
    if (!quad.leaf) {
      var nodes = quad.nodes, n = nodes.length, i = -1, c;
      while (++i < n) {
        c = nodes[i];
        if (c == null) continue;
        d3_layout_forceAccumulate(c, alpha, charges);
        quad.charge += c.charge;
        cx += c.charge * c.cx;
        cy += c.charge * c.cy;
      }
    }
    if (quad.point) {
      if (!quad.leaf) {
        quad.point.x += Math.random() - .5;
        quad.point.y += Math.random() - .5;
      }
      var k = alpha * charges[quad.point.index];
      quad.charge += quad.pointCharge = k;
      cx += k * quad.point.x;
      cy += k * quad.point.y;
    }
    quad.cx = cx / quad.charge;
    quad.cy = cy / quad.charge;
  }
  var d3_layout_forceLinkDistance = 20, d3_layout_forceLinkStrength = 1, d3_layout_forceChargeDistance2 = Infinity;
  d3.layout.hierarchy = function() {
    var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue;
    function hierarchy(root) {
      var stack = [ root ], nodes = [], node;
      root.depth = 0;
      while ((node = stack.pop()) != null) {
        nodes.push(node);
        if ((childs = children.call(hierarchy, node, node.depth)) && (n = childs.length)) {
          var n, childs, child;
          while (--n >= 0) {
            stack.push(child = childs[n]);
            child.parent = node;
            child.depth = node.depth + 1;
          }
          if (value) node.value = 0;
          node.children = childs;
        } else {
          if (value) node.value = +value.call(hierarchy, node, node.depth) || 0;
          delete node.children;
        }
      }
      d3_layout_hierarchyVisitAfter(root, function(node) {
        var childs, parent;
        if (sort && (childs = node.children)) childs.sort(sort);
        if (value && (parent = node.parent)) parent.value += node.value;
      });
      return nodes;
    }
    hierarchy.sort = function(x) {
      if (!arguments.length) return sort;
      sort = x;
      return hierarchy;
    };
    hierarchy.children = function(x) {
      if (!arguments.length) return children;
      children = x;
      return hierarchy;
    };
    hierarchy.value = function(x) {
      if (!arguments.length) return value;
      value = x;
      return hierarchy;
    };
    hierarchy.revalue = function(root) {
      if (value) {
        d3_layout_hierarchyVisitBefore(root, function(node) {
          if (node.children) node.value = 0;
        });
        d3_layout_hierarchyVisitAfter(root, function(node) {
          var parent;
          if (!node.children) node.value = +value.call(hierarchy, node, node.depth) || 0;
          if (parent = node.parent) parent.value += node.value;
        });
      }
      return root;
    };
    return hierarchy;
  };
  function d3_layout_hierarchyRebind(object, hierarchy) {
    d3.rebind(object, hierarchy, "sort", "children", "value");
    object.nodes = object;
    object.links = d3_layout_hierarchyLinks;
    return object;
  }
  function d3_layout_hierarchyVisitBefore(node, callback) {
    var nodes = [ node ];
    while ((node = nodes.pop()) != null) {
      callback(node);
      if ((children = node.children) && (n = children.length)) {
        var n, children;
        while (--n >= 0) nodes.push(children[n]);
      }
    }
  }
  function d3_layout_hierarchyVisitAfter(node, callback) {
    var nodes = [ node ], nodes2 = [];
    while ((node = nodes.pop()) != null) {
      nodes2.push(node);
      if ((children = node.children) && (n = children.length)) {
        var i = -1, n, children;
        while (++i < n) nodes.push(children[i]);
      }
    }
    while ((node = nodes2.pop()) != null) {
      callback(node);
    }
  }
  function d3_layout_hierarchyChildren(d) {
    return d.children;
  }
  function d3_layout_hierarchyValue(d) {
    return d.value;
  }
  function d3_layout_hierarchySort(a, b) {
    return b.value - a.value;
  }
  function d3_layout_hierarchyLinks(nodes) {
    return d3.merge(nodes.map(function(parent) {
      return (parent.children || []).map(function(child) {
        return {
          source: parent,
          target: child
        };
      });
    }));
  }
  d3.layout.partition = function() {
    var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ];
    function position(node, x, dx, dy) {
      var children = node.children;
      node.x = x;
      node.y = node.depth * dy;
      node.dx = dx;
      node.dy = dy;
      if (children && (n = children.length)) {
        var i = -1, n, c, d;
        dx = node.value ? dx / node.value : 0;
        while (++i < n) {
          position(c = children[i], x, d = c.value * dx, dy);
          x += d;
        }
      }
    }
    function depth(node) {
      var children = node.children, d = 0;
      if (children && (n = children.length)) {
        var i = -1, n;
        while (++i < n) d = Math.max(d, depth(children[i]));
      }
      return 1 + d;
    }
    function partition(d, i) {
      var nodes = hierarchy.call(this, d, i);
      position(nodes[0], 0, size[0], size[1] / depth(nodes[0]));
      return nodes;
    }
    partition.size = function(x) {
      if (!arguments.length) return size;
      size = x;
      return partition;
    };
    return d3_layout_hierarchyRebind(partition, hierarchy);
  };
  d3.layout.pie = function() {
    var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = τ, padAngle = 0;
    function pie(data) {
      var n = data.length, values = data.map(function(d, i) {
        return +value.call(pie, d, i);
      }), a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle), da = (typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a, p = Math.min(Math.abs(da) / n, +(typeof padAngle === "function" ? padAngle.apply(this, arguments) : padAngle)), pa = p * (da < 0 ? -1 : 1), sum = d3.sum(values), k = sum ? (da - n * pa) / sum : 0, index = d3.range(n), arcs = [], v;
      if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) {
        return values[j] - values[i];
      } : function(i, j) {
        return sort(data[i], data[j]);
      });
      index.forEach(function(i) {
        arcs[i] = {
          data: data[i],
          value: v = values[i],
          startAngle: a,
          endAngle: a += v * k + pa,
          padAngle: p
        };
      });
      return arcs;
    }
    pie.value = function(_) {
      if (!arguments.length) return value;
      value = _;
      return pie;
    };
    pie.sort = function(_) {
      if (!arguments.length) return sort;
      sort = _;
      return pie;
    };
    pie.startAngle = function(_) {
      if (!arguments.length) return startAngle;
      startAngle = _;
      return pie;
    };
    pie.endAngle = function(_) {
      if (!arguments.length) return endAngle;
      endAngle = _;
      return pie;
    };
    pie.padAngle = function(_) {
      if (!arguments.length) return padAngle;
      padAngle = _;
      return pie;
    };
    return pie;
  };
  var d3_layout_pieSortByValue = {};
  d3.layout.stack = function() {
    var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY;
    function stack(data, index) {
      if (!(n = data.length)) return data;
      var series = data.map(function(d, i) {
        return values.call(stack, d, i);
      });
      var points = series.map(function(d) {
        return d.map(function(v, i) {
          return [ x.call(stack, v, i), y.call(stack, v, i) ];
        });
      });
      var orders = order.call(stack, points, index);
      series = d3.permute(series, orders);
      points = d3.permute(points, orders);
      var offsets = offset.call(stack, points, index);
      var m = series[0].length, n, i, j, o;
      for (j = 0; j < m; ++j) {
        out.call(stack, series[0][j], o = offsets[j], points[0][j][1]);
        for (i = 1; i < n; ++i) {
          out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]);
        }
      }
      return data;
    }
    stack.values = function(x) {
      if (!arguments.length) return values;
      values = x;
      return stack;
    };
    stack.order = function(x) {
      if (!arguments.length) return order;
      order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault;
      return stack;
    };
    stack.offset = function(x) {
      if (!arguments.length) return offset;
      offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero;
      return stack;
    };
    stack.x = function(z) {
      if (!arguments.length) return x;
      x = z;
      return stack;
    };
    stack.y = function(z) {
      if (!arguments.length) return y;
      y = z;
      return stack;
    };
    stack.out = function(z) {
      if (!arguments.length) return out;
      out = z;
      return stack;
    };
    return stack;
  };
  function d3_layout_stackX(d) {
    return d.x;
  }
  function d3_layout_stackY(d) {
    return d.y;
  }
  function d3_layout_stackOut(d, y0, y) {
    d.y0 = y0;
    d.y = y;
  }
  var d3_layout_stackOrders = d3.map({
    "inside-out": function(data) {
      var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) {
        return max[a] - max[b];
      }), top = 0, bottom = 0, tops = [], bottoms = [];
      for (i = 0; i < n; ++i) {
        j = index[i];
        if (top < bottom) {
          top += sums[j];
          tops.push(j);
        } else {
          bottom += sums[j];
          bottoms.push(j);
        }
      }
      return bottoms.reverse().concat(tops);
    },
    reverse: function(data) {
      return d3.range(data.length).reverse();
    },
    "default": d3_layout_stackOrderDefault
  });
  var d3_layout_stackOffsets = d3.map({
    silhouette: function(data) {
      var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = [];
      for (j = 0; j < m; ++j) {
        for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
        if (o > max) max = o;
        sums.push(o);
      }
      for (j = 0; j < m; ++j) {
        y0[j] = (max - sums[j]) / 2;
      }
      return y0;
    },
    wiggle: function(data) {
      var n = data.length, x = data[0], m = x.length, i, j, k, s1, s2, s3, dx, o, o0, y0 = [];
      y0[0] = o = o0 = 0;
      for (j = 1; j < m; ++j) {
        for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1];
        for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) {
          for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) {
            s3 += (data[k][j][1] - data[k][j - 1][1]) / dx;
          }
          s2 += s3 * data[i][j][1];
        }
        y0[j] = o -= s1 ? s2 / s1 * dx : 0;
        if (o < o0) o0 = o;
      }
      for (j = 0; j < m; ++j) y0[j] -= o0;
      return y0;
    },
    expand: function(data) {
      var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = [];
      for (j = 0; j < m; ++j) {
        for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
        if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k;
      }
      for (j = 0; j < m; ++j) y0[j] = 0;
      return y0;
    },
    zero: d3_layout_stackOffsetZero
  });
  function d3_layout_stackOrderDefault(data) {
    return d3.range(data.length);
  }
  function d3_layout_stackOffsetZero(data) {
    var j = -1, m = data[0].length, y0 = [];
    while (++j < m) y0[j] = 0;
    return y0;
  }
  function d3_layout_stackMaxIndex(array) {
    var i = 1, j = 0, v = array[0][1], k, n = array.length;
    for (;i < n; ++i) {
      if ((k = array[i][1]) > v) {
        j = i;
        v = k;
      }
    }
    return j;
  }
  function d3_layout_stackReduceSum(d) {
    return d.reduce(d3_layout_stackSum, 0);
  }
  function d3_layout_stackSum(p, d) {
    return p + d[1];
  }
  d3.layout.histogram = function() {
    var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges;
    function histogram(data, i) {
      var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x;
      while (++i < m) {
        bin = bins[i] = [];
        bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]);
        bin.y = 0;
      }
      if (m > 0) {
        i = -1;
        while (++i < n) {
          x = values[i];
          if (x >= range[0] && x <= range[1]) {
            bin = bins[d3.bisect(thresholds, x, 1, m) - 1];
            bin.y += k;
            bin.push(data[i]);
          }
        }
      }
      return bins;
    }
    histogram.value = function(x) {
      if (!arguments.length) return valuer;
      valuer = x;
      return histogram;
    };
    histogram.range = function(x) {
      if (!arguments.length) return ranger;
      ranger = d3_functor(x);
      return histogram;
    };
    histogram.bins = function(x) {
      if (!arguments.length) return binner;
      binner = typeof x === "number" ? function(range) {
        return d3_layout_histogramBinFixed(range, x);
      } : d3_functor(x);
      return histogram;
    };
    histogram.frequency = function(x) {
      if (!arguments.length) return frequency;
      frequency = !!x;
      return histogram;
    };
    return histogram;
  };
  function d3_layout_histogramBinSturges(range, values) {
    return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1));
  }
  function d3_layout_histogramBinFixed(range, n) {
    var x = -1, b = +range[0], m = (range[1] - b) / n, f = [];
    while (++x <= n) f[x] = m * x + b;
    return f;
  }
  function d3_layout_histogramRange(values) {
    return [ d3.min(values), d3.max(values) ];
  }
  d3.layout.pack = function() {
    var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ], radius;
    function pack(d, i) {
      var nodes = hierarchy.call(this, d, i), root = nodes[0], w = size[0], h = size[1], r = radius == null ? Math.sqrt : typeof radius === "function" ? radius : function() {
        return radius;
      };
      root.x = root.y = 0;
      d3_layout_hierarchyVisitAfter(root, function(d) {
        d.r = +r(d.value);
      });
      d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings);
      if (padding) {
        var dr = padding * (radius ? 1 : Math.max(2 * root.r / w, 2 * root.r / h)) / 2;
        d3_layout_hierarchyVisitAfter(root, function(d) {
          d.r += dr;
        });
        d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings);
        d3_layout_hierarchyVisitAfter(root, function(d) {
          d.r -= dr;
        });
      }
      d3_layout_packTransform(root, w / 2, h / 2, radius ? 1 : 1 / Math.max(2 * root.r / w, 2 * root.r / h));
      return nodes;
    }
    pack.size = function(_) {
      if (!arguments.length) return size;
      size = _;
      return pack;
    };
    pack.radius = function(_) {
      if (!arguments.length) return radius;
      radius = _ == null || typeof _ === "function" ? _ : +_;
      return pack;
    };
    pack.padding = function(_) {
      if (!arguments.length) return padding;
      padding = +_;
      return pack;
    };
    return d3_layout_hierarchyRebind(pack, hierarchy);
  };
  function d3_layout_packSort(a, b) {
    return a.value - b.value;
  }
  function d3_layout_packInsert(a, b) {
    var c = a._pack_next;
    a._pack_next = b;
    b._pack_prev = a;
    b._pack_next = c;
    c._pack_prev = b;
  }
  function d3_layout_packSplice(a, b) {
    a._pack_next = b;
    b._pack_prev = a;
  }
  function d3_layout_packIntersects(a, b) {
    var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r;
    return .999 * dr * dr > dx * dx + dy * dy;
  }
  function d3_layout_packSiblings(node) {
    if (!(nodes = node.children) || !(n = nodes.length)) return;
    var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n;
    function bound(node) {
      xMin = Math.min(node.x - node.r, xMin);
      xMax = Math.max(node.x + node.r, xMax);
      yMin = Math.min(node.y - node.r, yMin);
      yMax = Math.max(node.y + node.r, yMax);
    }
    nodes.forEach(d3_layout_packLink);
    a = nodes[0];
    a.x = -a.r;
    a.y = 0;
    bound(a);
    if (n > 1) {
      b = nodes[1];
      b.x = b.r;
      b.y = 0;
      bound(b);
      if (n > 2) {
        c = nodes[2];
        d3_layout_packPlace(a, b, c);
        bound(c);
        d3_layout_packInsert(a, c);
        a._pack_prev = c;
        d3_layout_packInsert(c, b);
        b = a._pack_next;
        for (i = 3; i < n; i++) {
          d3_layout_packPlace(a, b, c = nodes[i]);
          var isect = 0, s1 = 1, s2 = 1;
          for (j = b._pack_next; j !== b; j = j._pack_next, s1++) {
            if (d3_layout_packIntersects(j, c)) {
              isect = 1;
              break;
            }
          }
          if (isect == 1) {
            for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) {
              if (d3_layout_packIntersects(k, c)) {
                break;
              }
            }
          }
          if (isect) {
            if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b);
            i--;
          } else {
            d3_layout_packInsert(a, c);
            b = c;
            bound(c);
          }
        }
      }
    }
    var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0;
    for (i = 0; i < n; i++) {
      c = nodes[i];
      c.x -= cx;
      c.y -= cy;
      cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y));
    }
    node.r = cr;
    nodes.forEach(d3_layout_packUnlink);
  }
  function d3_layout_packLink(node) {
    node._pack_next = node._pack_prev = node;
  }
  function d3_layout_packUnlink(node) {
    delete node._pack_next;
    delete node._pack_prev;
  }
  function d3_layout_packTransform(node, x, y, k) {
    var children = node.children;
    node.x = x += k * node.x;
    node.y = y += k * node.y;
    node.r *= k;
    if (children) {
      var i = -1, n = children.length;
      while (++i < n) d3_layout_packTransform(children[i], x, y, k);
    }
  }
  function d3_layout_packPlace(a, b, c) {
    var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y;
    if (db && (dx || dy)) {
      var da = b.r + c.r, dc = dx * dx + dy * dy;
      da *= da;
      db *= db;
      var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc);
      c.x = a.x + x * dx + y * dy;
      c.y = a.y + x * dy - y * dx;
    } else {
      c.x = a.x + db;
      c.y = a.y;
    }
  }
  d3.layout.tree = function() {
    var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = null;
    function tree(d, i) {
      var nodes = hierarchy.call(this, d, i), root0 = nodes[0], root1 = wrapTree(root0);
      d3_layout_hierarchyVisitAfter(root1, firstWalk), root1.parent.m = -root1.z;
      d3_layout_hierarchyVisitBefore(root1, secondWalk);
      if (nodeSize) d3_layout_hierarchyVisitBefore(root0, sizeNode); else {
        var left = root0, right = root0, bottom = root0;
        d3_layout_hierarchyVisitBefore(root0, function(node) {
          if (node.x < left.x) left = node;
          if (node.x > right.x) right = node;
          if (node.depth > bottom.depth) bottom = node;
        });
        var tx = separation(left, right) / 2 - left.x, kx = size[0] / (right.x + separation(right, left) / 2 + tx), ky = size[1] / (bottom.depth || 1);
        d3_layout_hierarchyVisitBefore(root0, function(node) {
          node.x = (node.x + tx) * kx;
          node.y = node.depth * ky;
        });
      }
      return nodes;
    }
    function wrapTree(root0) {
      var root1 = {
        A: null,
        children: [ root0 ]
      }, queue = [ root1 ], node1;
      while ((node1 = queue.pop()) != null) {
        for (var children = node1.children, child, i = 0, n = children.length; i < n; ++i) {
          queue.push((children[i] = child = {
            _: children[i],
            parent: node1,
            children: (child = children[i].children) && child.slice() || [],
            A: null,
            a: null,
            z: 0,
            m: 0,
            c: 0,
            s: 0,
            t: null,
            i: i
          }).a = child);
        }
      }
      return root1.children[0];
    }
    function firstWalk(v) {
      var children = v.children, siblings = v.parent.children, w = v.i ? siblings[v.i - 1] : null;
      if (children.length) {
        d3_layout_treeShift(v);
        var midpoint = (children[0].z + children[children.length - 1].z) / 2;
        if (w) {
          v.z = w.z + separation(v._, w._);
          v.m = v.z - midpoint;
        } else {
          v.z = midpoint;
        }
      } else if (w) {
        v.z = w.z + separation(v._, w._);
      }
      v.parent.A = apportion(v, w, v.parent.A || siblings[0]);
    }
    function secondWalk(v) {
      v._.x = v.z + v.parent.m;
      v.m += v.parent.m;
    }
    function apportion(v, w, ancestor) {
      if (w) {
        var vip = v, vop = v, vim = w, vom = vip.parent.children[0], sip = vip.m, sop = vop.m, sim = vim.m, som = vom.m, shift;
        while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) {
          vom = d3_layout_treeLeft(vom);
          vop = d3_layout_treeRight(vop);
          vop.a = v;
          shift = vim.z + sim - vip.z - sip + separation(vim._, vip._);
          if (shift > 0) {
            d3_layout_treeMove(d3_layout_treeAncestor(vim, v, ancestor), v, shift);
            sip += shift;
            sop += shift;
          }
          sim += vim.m;
          sip += vip.m;
          som += vom.m;
          sop += vop.m;
        }
        if (vim && !d3_layout_treeRight(vop)) {
          vop.t = vim;
          vop.m += sim - sop;
        }
        if (vip && !d3_layout_treeLeft(vom)) {
          vom.t = vip;
          vom.m += sip - som;
          ancestor = v;
        }
      }
      return ancestor;
    }
    function sizeNode(node) {
      node.x *= size[0];
      node.y = node.depth * size[1];
    }
    tree.separation = function(x) {
      if (!arguments.length) return separation;
      separation = x;
      return tree;
    };
    tree.size = function(x) {
      if (!arguments.length) return nodeSize ? null : size;
      nodeSize = (size = x) == null ? sizeNode : null;
      return tree;
    };
    tree.nodeSize = function(x) {
      if (!arguments.length) return nodeSize ? size : null;
      nodeSize = (size = x) == null ? null : sizeNode;
      return tree;
    };
    return d3_layout_hierarchyRebind(tree, hierarchy);
  };
  function d3_layout_treeSeparation(a, b) {
    return a.parent == b.parent ? 1 : 2;
  }
  function d3_layout_treeLeft(v) {
    var children = v.children;
    return children.length ? children[0] : v.t;
  }
  function d3_layout_treeRight(v) {
    var children = v.children, n;
    return (n = children.length) ? children[n - 1] : v.t;
  }
  function d3_layout_treeMove(wm, wp, shift) {
    var change = shift / (wp.i - wm.i);
    wp.c -= change;
    wp.s += shift;
    wm.c += change;
    wp.z += shift;
    wp.m += shift;
  }
  function d3_layout_treeShift(v) {
    var shift = 0, change = 0, children = v.children, i = children.length, w;
    while (--i >= 0) {
      w = children[i];
      w.z += shift;
      w.m += shift;
      shift += w.s + (change += w.c);
    }
  }
  function d3_layout_treeAncestor(vim, v, ancestor) {
    return vim.a.parent === v.parent ? vim.a : ancestor;
  }
  d3.layout.cluster = function() {
    var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = false;
    function cluster(d, i) {
      var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0;
      d3_layout_hierarchyVisitAfter(root, function(node) {
        var children = node.children;
        if (children && children.length) {
          node.x = d3_layout_clusterX(children);
          node.y = d3_layout_clusterY(children);
        } else {
          node.x = previousNode ? x += separation(node, previousNode) : 0;
          node.y = 0;
          previousNode = node;
        }
      });
      var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2;
      d3_layout_hierarchyVisitAfter(root, nodeSize ? function(node) {
        node.x = (node.x - root.x) * size[0];
        node.y = (root.y - node.y) * size[1];
      } : function(node) {
        node.x = (node.x - x0) / (x1 - x0) * size[0];
        node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1];
      });
      return nodes;
    }
    cluster.separation = function(x) {
      if (!arguments.length) return separation;
      separation = x;
      return cluster;
    };
    cluster.size = function(x) {
      if (!arguments.length) return nodeSize ? null : size;
      nodeSize = (size = x) == null;
      return cluster;
    };
    cluster.nodeSize = function(x) {
      if (!arguments.length) return nodeSize ? size : null;
      nodeSize = (size = x) != null;
      return cluster;
    };
    return d3_layout_hierarchyRebind(cluster, hierarchy);
  };
  function d3_layout_clusterY(children) {
    return 1 + d3.max(children, function(child) {
      return child.y;
    });
  }
  function d3_layout_clusterX(children) {
    return children.reduce(function(x, child) {
      return x + child.x;
    }, 0) / children.length;
  }
  function d3_layout_clusterLeft(node) {
    var children = node.children;
    return children && children.length ? d3_layout_clusterLeft(children[0]) : node;
  }
  function d3_layout_clusterRight(node) {
    var children = node.children, n;
    return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node;
  }
  d3.layout.treemap = function() {
    var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, mode = "squarify", ratio = .5 * (1 + Math.sqrt(5));
    function scale(children, k) {
      var i = -1, n = children.length, child, area;
      while (++i < n) {
        area = (child = children[i]).value * (k < 0 ? 0 : k);
        child.area = isNaN(area) || area <= 0 ? 0 : area;
      }
    }
    function squarify(node) {
      var children = node.children;
      if (children && children.length) {
        var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy), n;
        scale(remaining, rect.dx * rect.dy / node.value);
        row.area = 0;
        while ((n = remaining.length) > 0) {
          row.push(child = remaining[n - 1]);
          row.area += child.area;
          if (mode !== "squarify" || (score = worst(row, u)) <= best) {
            remaining.pop();
            best = score;
          } else {
            row.area -= row.pop().area;
            position(row, u, rect, false);
            u = Math.min(rect.dx, rect.dy);
            row.length = row.area = 0;
            best = Infinity;
          }
        }
        if (row.length) {
          position(row, u, rect, true);
          row.length = row.area = 0;
        }
        children.forEach(squarify);
      }
    }
    function stickify(node) {
      var children = node.children;
      if (children && children.length) {
        var rect = pad(node), remaining = children.slice(), child, row = [];
        scale(remaining, rect.dx * rect.dy / node.value);
        row.area = 0;
        while (child = remaining.pop()) {
          row.push(child);
          row.area += child.area;
          if (child.z != null) {
            position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length);
            row.length = row.area = 0;
          }
        }
        children.forEach(stickify);
      }
    }
    function worst(row, u) {
      var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length;
      while (++i < n) {
        if (!(r = row[i].area)) continue;
        if (r < rmin) rmin = r;
        if (r > rmax) rmax = r;
      }
      s *= s;
      u *= u;
      return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity;
    }
    function position(row, u, rect, flush) {
      var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o;
      if (u == rect.dx) {
        if (flush || v > rect.dy) v = rect.dy;
        while (++i < n) {
          o = row[i];
          o.x = x;
          o.y = y;
          o.dy = v;
          x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0);
        }
        o.z = true;
        o.dx += rect.x + rect.dx - x;
        rect.y += v;
        rect.dy -= v;
      } else {
        if (flush || v > rect.dx) v = rect.dx;
        while (++i < n) {
          o = row[i];
          o.x = x;
          o.y = y;
          o.dx = v;
          y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0);
        }
        o.z = false;
        o.dy += rect.y + rect.dy - y;
        rect.x += v;
        rect.dx -= v;
      }
    }
    function treemap(d) {
      var nodes = stickies || hierarchy(d), root = nodes[0];
      root.x = root.y = 0;
      if (root.value) root.dx = size[0], root.dy = size[1]; else root.dx = root.dy = 0;
      if (stickies) hierarchy.revalue(root);
      scale([ root ], root.dx * root.dy / root.value);
      (stickies ? stickify : squarify)(root);
      if (sticky) stickies = nodes;
      return nodes;
    }
    treemap.size = function(x) {
      if (!arguments.length) return size;
      size = x;
      return treemap;
    };
    treemap.padding = function(x) {
      if (!arguments.length) return padding;
      function padFunction(node) {
        var p = x.call(treemap, node, node.depth);
        return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p);
      }
      function padConstant(node) {
        return d3_layout_treemapPad(node, x);
      }
      var type;
      pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ], 
      padConstant) : padConstant;
      return treemap;
    };
    treemap.round = function(x) {
      if (!arguments.length) return round != Number;
      round = x ? Math.round : Number;
      return treemap;
    };
    treemap.sticky = function(x) {
      if (!arguments.length) return sticky;
      sticky = x;
      stickies = null;
      return treemap;
    };
    treemap.ratio = function(x) {
      if (!arguments.length) return ratio;
      ratio = x;
      return treemap;
    };
    treemap.mode = function(x) {
      if (!arguments.length) return mode;
      mode = x + "";
      return treemap;
    };
    return d3_layout_hierarchyRebind(treemap, hierarchy);
  };
  function d3_layout_treemapPadNull(node) {
    return {
      x: node.x,
      y: node.y,
      dx: node.dx,
      dy: node.dy
    };
  }
  function d3_layout_treemapPad(node, padding) {
    var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2];
    if (dx < 0) {
      x += dx / 2;
      dx = 0;
    }
    if (dy < 0) {
      y += dy / 2;
      dy = 0;
    }
    return {
      x: x,
      y: y,
      dx: dx,
      dy: dy
    };
  }
  d3.random = {
    normal: function(µ, σ) {
      var n = arguments.length;
      if (n < 2) σ = 1;
      if (n < 1) µ = 0;
      return function() {
        var x, y, r;
        do {
          x = Math.random() * 2 - 1;
          y = Math.random() * 2 - 1;
          r = x * x + y * y;
        } while (!r || r > 1);
        return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r);
      };
    },
    logNormal: function() {
      var random = d3.random.normal.apply(d3, arguments);
      return function() {
        return Math.exp(random());
      };
    },
    bates: function(m) {
      var random = d3.random.irwinHall(m);
      return function() {
        return random() / m;
      };
    },
    irwinHall: function(m) {
      return function() {
        for (var s = 0, j = 0; j < m; j++) s += Math.random();
        return s;
      };
    }
  };
  d3.scale = {};
  function d3_scaleExtent(domain) {
    var start = domain[0], stop = domain[domain.length - 1];
    return start < stop ? [ start, stop ] : [ stop, start ];
  }
  function d3_scaleRange(scale) {
    return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range());
  }
  function d3_scale_bilinear(domain, range, uninterpolate, interpolate) {
    var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]);
    return function(x) {
      return i(u(x));
    };
  }
  function d3_scale_nice(domain, nice) {
    var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx;
    if (x1 < x0) {
      dx = i0, i0 = i1, i1 = dx;
      dx = x0, x0 = x1, x1 = dx;
    }
    domain[i0] = nice.floor(x0);
    domain[i1] = nice.ceil(x1);
    return domain;
  }
  function d3_scale_niceStep(step) {
    return step ? {
      floor: function(x) {
        return Math.floor(x / step) * step;
      },
      ceil: function(x) {
        return Math.ceil(x / step) * step;
      }
    } : d3_scale_niceIdentity;
  }
  var d3_scale_niceIdentity = {
    floor: d3_identity,
    ceil: d3_identity
  };
  function d3_scale_polylinear(domain, range, uninterpolate, interpolate) {
    var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1;
    if (domain[k] < domain[0]) {
      domain = domain.slice().reverse();
      range = range.slice().reverse();
    }
    while (++j <= k) {
      u.push(uninterpolate(domain[j - 1], domain[j]));
      i.push(interpolate(range[j - 1], range[j]));
    }
    return function(x) {
      var j = d3.bisect(domain, x, 1, k) - 1;
      return i[j](u[j](x));
    };
  }
  d3.scale.linear = function() {
    return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3_interpolate, false);
  };
  function d3_scale_linear(domain, range, interpolate, clamp) {
    var output, input;
    function rescale() {
      var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber;
      output = linear(domain, range, uninterpolate, interpolate);
      input = linear(range, domain, uninterpolate, d3_interpolate);
      return scale;
    }
    function scale(x) {
      return output(x);
    }
    scale.invert = function(y) {
      return input(y);
    };
    scale.domain = function(x) {
      if (!arguments.length) return domain;
      domain = x.map(Number);
      return rescale();
    };
    scale.range = function(x) {
      if (!arguments.length) return range;
      range = x;
      return rescale();
    };
    scale.rangeRound = function(x) {
      return scale.range(x).interpolate(d3_interpolateRound);
    };
    scale.clamp = function(x) {
      if (!arguments.length) return clamp;
      clamp = x;
      return rescale();
    };
    scale.interpolate = function(x) {
      if (!arguments.length) return interpolate;
      interpolate = x;
      return rescale();
    };
    scale.ticks = function(m) {
      return d3_scale_linearTicks(domain, m);
    };
    scale.tickFormat = function(m, format) {
      return d3_scale_linearTickFormat(domain, m, format);
    };
    scale.nice = function(m) {
      d3_scale_linearNice(domain, m);
      return rescale();
    };
    scale.copy = function() {
      return d3_scale_linear(domain, range, interpolate, clamp);
    };
    return rescale();
  }
  function d3_scale_linearRebind(scale, linear) {
    return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp");
  }
  function d3_scale_linearNice(domain, m) {
    d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2]));
    d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2]));
    return domain;
  }
  function d3_scale_linearTickRange(domain, m) {
    if (m == null) m = 10;
    var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step;
    if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2;
    extent[0] = Math.ceil(extent[0] / step) * step;
    extent[1] = Math.floor(extent[1] / step) * step + step * .5;
    extent[2] = step;
    return extent;
  }
  function d3_scale_linearTicks(domain, m) {
    return d3.range.apply(d3, d3_scale_linearTickRange(domain, m));
  }
  function d3_scale_linearTickFormat(domain, m, format) {
    var range = d3_scale_linearTickRange(domain, m);
    if (format) {
      var match = d3_format_re.exec(format);
      match.shift();
      if (match[8] === "s") {
        var prefix = d3.formatPrefix(Math.max(abs(range[0]), abs(range[1])));
        if (!match[7]) match[7] = "." + d3_scale_linearPrecision(prefix.scale(range[2]));
        match[8] = "f";
        format = d3.format(match.join(""));
        return function(d) {
          return format(prefix.scale(d)) + prefix.symbol;
        };
      }
      if (!match[7]) match[7] = "." + d3_scale_linearFormatPrecision(match[8], range);
      format = match.join("");
    } else {
      format = ",." + d3_scale_linearPrecision(range[2]) + "f";
    }
    return d3.format(format);
  }
  var d3_scale_linearFormatSignificant = {
    s: 1,
    g: 1,
    p: 1,
    r: 1,
    e: 1
  };
  function d3_scale_linearPrecision(value) {
    return -Math.floor(Math.log(value) / Math.LN10 + .01);
  }
  function d3_scale_linearFormatPrecision(type, range) {
    var p = d3_scale_linearPrecision(range[2]);
    return type in d3_scale_linearFormatSignificant ? Math.abs(p - d3_scale_linearPrecision(Math.max(abs(range[0]), abs(range[1])))) + +(type !== "e") : p - (type === "%") * 2;
  }
  d3.scale.log = function() {
    return d3_scale_log(d3.scale.linear().domain([ 0, 1 ]), 10, true, [ 1, 10 ]);
  };
  function d3_scale_log(linear, base, positive, domain) {
    function log(x) {
      return (positive ? Math.log(x < 0 ? 0 : x) : -Math.log(x > 0 ? 0 : -x)) / Math.log(base);
    }
    function pow(x) {
      return positive ? Math.pow(base, x) : -Math.pow(base, -x);
    }
    function scale(x) {
      return linear(log(x));
    }
    scale.invert = function(x) {
      return pow(linear.invert(x));
    };
    scale.domain = function(x) {
      if (!arguments.length) return domain;
      positive = x[0] >= 0;
      linear.domain((domain = x.map(Number)).map(log));
      return scale;
    };
    scale.base = function(_) {
      if (!arguments.length) return base;
      base = +_;
      linear.domain(domain.map(log));
      return scale;
    };
    scale.nice = function() {
      var niced = d3_scale_nice(domain.map(log), positive ? Math : d3_scale_logNiceNegative);
      linear.domain(niced);
      domain = niced.map(pow);
      return scale;
    };
    scale.ticks = function() {
      var extent = d3_scaleExtent(domain), ticks = [], u = extent[0], v = extent[1], i = Math.floor(log(u)), j = Math.ceil(log(v)), n = base % 1 ? 2 : base;
      if (isFinite(j - i)) {
        if (positive) {
          for (;i < j; i++) for (var k = 1; k < n; k++) ticks.push(pow(i) * k);
          ticks.push(pow(i));
        } else {
          ticks.push(pow(i));
          for (;i++ < j; ) for (var k = n - 1; k > 0; k--) ticks.push(pow(i) * k);
        }
        for (i = 0; ticks[i] < u; i++) {}
        for (j = ticks.length; ticks[j - 1] > v; j--) {}
        ticks = ticks.slice(i, j);
      }
      return ticks;
    };
    scale.tickFormat = function(n, format) {
      if (!arguments.length) return d3_scale_logFormat;
      if (arguments.length < 2) format = d3_scale_logFormat; else if (typeof format !== "function") format = d3.format(format);
      var k = Math.max(1, base * n / scale.ticks().length);
      return function(d) {
        var i = d / pow(Math.round(log(d)));
        if (i * base < base - .5) i *= base;
        return i <= k ? format(d) : "";
      };
    };
    scale.copy = function() {
      return d3_scale_log(linear.copy(), base, positive, domain);
    };
    return d3_scale_linearRebind(scale, linear);
  }
  var d3_scale_logFormat = d3.format(".0e"), d3_scale_logNiceNegative = {
    floor: function(x) {
      return -Math.ceil(-x);
    },
    ceil: function(x) {
      return -Math.floor(-x);
    }
  };
  d3.scale.pow = function() {
    return d3_scale_pow(d3.scale.linear(), 1, [ 0, 1 ]);
  };
  function d3_scale_pow(linear, exponent, domain) {
    var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent);
    function scale(x) {
      return linear(powp(x));
    }
    scale.invert = function(x) {
      return powb(linear.invert(x));
    };
    scale.domain = function(x) {
      if (!arguments.length) return domain;
      linear.domain((domain = x.map(Number)).map(powp));
      return scale;
    };
    scale.ticks = function(m) {
      return d3_scale_linearTicks(domain, m);
    };
    scale.tickFormat = function(m, format) {
      return d3_scale_linearTickFormat(domain, m, format);
    };
    scale.nice = function(m) {
      return scale.domain(d3_scale_linearNice(domain, m));
    };
    scale.exponent = function(x) {
      if (!arguments.length) return exponent;
      powp = d3_scale_powPow(exponent = x);
      powb = d3_scale_powPow(1 / exponent);
      linear.domain(domain.map(powp));
      return scale;
    };
    scale.copy = function() {
      return d3_scale_pow(linear.copy(), exponent, domain);
    };
    return d3_scale_linearRebind(scale, linear);
  }
  function d3_scale_powPow(e) {
    return function(x) {
      return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e);
    };
  }
  d3.scale.sqrt = function() {
    return d3.scale.pow().exponent(.5);
  };
  d3.scale.ordinal = function() {
    return d3_scale_ordinal([], {
      t: "range",
      a: [ [] ]
    });
  };
  function d3_scale_ordinal(domain, ranger) {
    var index, range, rangeBand;
    function scale(x) {
      return range[((index.get(x) || (ranger.t === "range" ? index.set(x, domain.push(x)) : NaN)) - 1) % range.length];
    }
    function steps(start, step) {
      return d3.range(domain.length).map(function(i) {
        return start + step * i;
      });
    }
    scale.domain = function(x) {
      if (!arguments.length) return domain;
      domain = [];
      index = new d3_Map();
      var i = -1, n = x.length, xi;
      while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi));
      return scale[ranger.t].apply(scale, ranger.a);
    };
    scale.range = function(x) {
      if (!arguments.length) return range;
      range = x;
      rangeBand = 0;
      ranger = {
        t: "range",
        a: arguments
      };
      return scale;
    };
    scale.rangePoints = function(x, padding) {
      if (arguments.length < 2) padding = 0;
      var start = x[0], stop = x[1], step = domain.length < 2 ? (start = (start + stop) / 2, 
      0) : (stop - start) / (domain.length - 1 + padding);
      range = steps(start + step * padding / 2, step);
      rangeBand = 0;
      ranger = {
        t: "rangePoints",
        a: arguments
      };
      return scale;
    };
    scale.rangeRoundPoints = function(x, padding) {
      if (arguments.length < 2) padding = 0;
      var start = x[0], stop = x[1], step = domain.length < 2 ? (start = stop = Math.round((start + stop) / 2), 
      0) : (stop - start) / (domain.length - 1 + padding) | 0;
      range = steps(start + Math.round(step * padding / 2 + (stop - start - (domain.length - 1 + padding) * step) / 2), step);
      rangeBand = 0;
      ranger = {
        t: "rangeRoundPoints",
        a: arguments
      };
      return scale;
    };
    scale.rangeBands = function(x, padding, outerPadding) {
      if (arguments.length < 2) padding = 0;
      if (arguments.length < 3) outerPadding = padding;
      var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding);
      range = steps(start + step * outerPadding, step);
      if (reverse) range.reverse();
      rangeBand = step * (1 - padding);
      ranger = {
        t: "rangeBands",
        a: arguments
      };
      return scale;
    };
    scale.rangeRoundBands = function(x, padding, outerPadding) {
      if (arguments.length < 2) padding = 0;
      if (arguments.length < 3) outerPadding = padding;
      var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding));
      range = steps(start + Math.round((stop - start - (domain.length - padding) * step) / 2), step);
      if (reverse) range.reverse();
      rangeBand = Math.round(step * (1 - padding));
      ranger = {
        t: "rangeRoundBands",
        a: arguments
      };
      return scale;
    };
    scale.rangeBand = function() {
      return rangeBand;
    };
    scale.rangeExtent = function() {
      return d3_scaleExtent(ranger.a[0]);
    };
    scale.copy = function() {
      return d3_scale_ordinal(domain, ranger);
    };
    return scale.domain(domain);
  }
  d3.scale.category10 = function() {
    return d3.scale.ordinal().range(d3_category10);
  };
  d3.scale.category20 = function() {
    return d3.scale.ordinal().range(d3_category20);
  };
  d3.scale.category20b = function() {
    return d3.scale.ordinal().range(d3_category20b);
  };
  d3.scale.category20c = function() {
    return d3.scale.ordinal().range(d3_category20c);
  };
  var d3_category10 = [ 2062260, 16744206, 2924588, 14034728, 9725885, 9197131, 14907330, 8355711, 12369186, 1556175 ].map(d3_rgbString);
  var d3_category20 = [ 2062260, 11454440, 16744206, 16759672, 2924588, 10018698, 14034728, 16750742, 9725885, 12955861, 9197131, 12885140, 14907330, 16234194, 8355711, 13092807, 12369186, 14408589, 1556175, 10410725 ].map(d3_rgbString);
  var d3_category20b = [ 3750777, 5395619, 7040719, 10264286, 6519097, 9216594, 11915115, 13556636, 9202993, 12426809, 15186514, 15190932, 8666169, 11356490, 14049643, 15177372, 8077683, 10834324, 13528509, 14589654 ].map(d3_rgbString);
  var d3_category20c = [ 3244733, 7057110, 10406625, 13032431, 15095053, 16616764, 16625259, 16634018, 3253076, 7652470, 10607003, 13101504, 7695281, 10394312, 12369372, 14342891, 6513507, 9868950, 12434877, 14277081 ].map(d3_rgbString);
  d3.scale.quantile = function() {
    return d3_scale_quantile([], []);
  };
  function d3_scale_quantile(domain, range) {
    var thresholds;
    function rescale() {
      var k = 0, q = range.length;
      thresholds = [];
      while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q);
      return scale;
    }
    function scale(x) {
      if (!isNaN(x = +x)) return range[d3.bisect(thresholds, x)];
    }
    scale.domain = function(x) {
      if (!arguments.length) return domain;
      domain = x.map(d3_number).filter(d3_numeric).sort(d3_ascending);
      return rescale();
    };
    scale.range = function(x) {
      if (!arguments.length) return range;
      range = x;
      return rescale();
    };
    scale.quantiles = function() {
      return thresholds;
    };
    scale.invertExtent = function(y) {
      y = range.indexOf(y);
      return y < 0 ? [ NaN, NaN ] : [ y > 0 ? thresholds[y - 1] : domain[0], y < thresholds.length ? thresholds[y] : domain[domain.length - 1] ];
    };
    scale.copy = function() {
      return d3_scale_quantile(domain, range);
    };
    return rescale();
  }
  d3.scale.quantize = function() {
    return d3_scale_quantize(0, 1, [ 0, 1 ]);
  };
  function d3_scale_quantize(x0, x1, range) {
    var kx, i;
    function scale(x) {
      return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))];
    }
    function rescale() {
      kx = range.length / (x1 - x0);
      i = range.length - 1;
      return scale;
    }
    scale.domain = function(x) {
      if (!arguments.length) return [ x0, x1 ];
      x0 = +x[0];
      x1 = +x[x.length - 1];
      return rescale();
    };
    scale.range = function(x) {
      if (!arguments.length) return range;
      range = x;
      return rescale();
    };
    scale.invertExtent = function(y) {
      y = range.indexOf(y);
      y = y < 0 ? NaN : y / kx + x0;
      return [ y, y + 1 / kx ];
    };
    scale.copy = function() {
      return d3_scale_quantize(x0, x1, range);
    };
    return rescale();
  }
  d3.scale.threshold = function() {
    return d3_scale_threshold([ .5 ], [ 0, 1 ]);
  };
  function d3_scale_threshold(domain, range) {
    function scale(x) {
      if (x <= x) return range[d3.bisect(domain, x)];
    }
    scale.domain = function(_) {
      if (!arguments.length) return domain;
      domain = _;
      return scale;
    };
    scale.range = function(_) {
      if (!arguments.length) return range;
      range = _;
      return scale;
    };
    scale.invertExtent = function(y) {
      y = range.indexOf(y);
      return [ domain[y - 1], domain[y] ];
    };
    scale.copy = function() {
      return d3_scale_threshold(domain, range);
    };
    return scale;
  }
  d3.scale.identity = function() {
    return d3_scale_identity([ 0, 1 ]);
  };
  function d3_scale_identity(domain) {
    function identity(x) {
      return +x;
    }
    identity.invert = identity;
    identity.domain = identity.range = function(x) {
      if (!arguments.length) return domain;
      domain = x.map(identity);
      return identity;
    };
    identity.ticks = function(m) {
      return d3_scale_linearTicks(domain, m);
    };
    identity.tickFormat = function(m, format) {
      return d3_scale_linearTickFormat(domain, m, format);
    };
    identity.copy = function() {
      return d3_scale_identity(domain);
    };
    return identity;
  }
  d3.svg = {};
  function d3_zero() {
    return 0;
  }
  d3.svg.arc = function() {
    var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, cornerRadius = d3_zero, padRadius = d3_svg_arcAuto, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle, padAngle = d3_svg_arcPadAngle;
    function arc() {
      var r0 = Math.max(0, +innerRadius.apply(this, arguments)), r1 = Math.max(0, +outerRadius.apply(this, arguments)), a0 = startAngle.apply(this, arguments) - halfπ, a1 = endAngle.apply(this, arguments) - halfπ, da = Math.abs(a1 - a0), cw = a0 > a1 ? 0 : 1;
      if (r1 < r0) rc = r1, r1 = r0, r0 = rc;
      if (da >= τε) return circleSegment(r1, cw) + (r0 ? circleSegment(r0, 1 - cw) : "") + "Z";
      var rc, cr, rp, ap, p0 = 0, p1 = 0, x0, y0, x1, y1, x2, y2, x3, y3, path = [];
      if (ap = (+padAngle.apply(this, arguments) || 0) / 2) {
        rp = padRadius === d3_svg_arcAuto ? Math.sqrt(r0 * r0 + r1 * r1) : +padRadius.apply(this, arguments);
        if (!cw) p1 *= -1;
        if (r1) p1 = d3_asin(rp / r1 * Math.sin(ap));
        if (r0) p0 = d3_asin(rp / r0 * Math.sin(ap));
      }
      if (r1) {
        x0 = r1 * Math.cos(a0 + p1);
        y0 = r1 * Math.sin(a0 + p1);
        x1 = r1 * Math.cos(a1 - p1);
        y1 = r1 * Math.sin(a1 - p1);
        var l1 = Math.abs(a1 - a0 - 2 * p1) <= π ? 0 : 1;
        if (p1 && d3_svg_arcSweep(x0, y0, x1, y1) === cw ^ l1) {
          var h1 = (a0 + a1) / 2;
          x0 = r1 * Math.cos(h1);
          y0 = r1 * Math.sin(h1);
          x1 = y1 = null;
        }
      } else {
        x0 = y0 = 0;
      }
      if (r0) {
        x2 = r0 * Math.cos(a1 - p0);
        y2 = r0 * Math.sin(a1 - p0);
        x3 = r0 * Math.cos(a0 + p0);
        y3 = r0 * Math.sin(a0 + p0);
        var l0 = Math.abs(a0 - a1 + 2 * p0) <= π ? 0 : 1;
        if (p0 && d3_svg_arcSweep(x2, y2, x3, y3) === 1 - cw ^ l0) {
          var h0 = (a0 + a1) / 2;
          x2 = r0 * Math.cos(h0);
          y2 = r0 * Math.sin(h0);
          x3 = y3 = null;
        }
      } else {
        x2 = y2 = 0;
      }
      if (da > ε && (rc = Math.min(Math.abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments))) > .001) {
        cr = r0 < r1 ^ cw ? 0 : 1;
        var rc1 = rc, rc0 = rc;
        if (da < π) {
          var oc = x3 == null ? [ x2, y2 ] : x1 == null ? [ x0, y0 ] : d3_geom_polygonIntersect([ x0, y0 ], [ x3, y3 ], [ x1, y1 ], [ x2, y2 ]), ax = x0 - oc[0], ay = y0 - oc[1], bx = x1 - oc[0], by = y1 - oc[1], kc = 1 / Math.sin(Math.acos((ax * bx + ay * by) / (Math.sqrt(ax * ax + ay * ay) * Math.sqrt(bx * bx + by * by))) / 2), lc = Math.sqrt(oc[0] * oc[0] + oc[1] * oc[1]);
          rc0 = Math.min(rc, (r0 - lc) / (kc - 1));
          rc1 = Math.min(rc, (r1 - lc) / (kc + 1));
        }
        if (x1 != null) {
          var t30 = d3_svg_arcCornerTangents(x3 == null ? [ x2, y2 ] : [ x3, y3 ], [ x0, y0 ], r1, rc1, cw), t12 = d3_svg_arcCornerTangents([ x1, y1 ], [ x2, y2 ], r1, rc1, cw);
          if (rc === rc1) {
            path.push("M", t30[0], "A", rc1, ",", rc1, " 0 0,", cr, " ", t30[1], "A", r1, ",", r1, " 0 ", 1 - cw ^ d3_svg_arcSweep(t30[1][0], t30[1][1], t12[1][0], t12[1][1]), ",", cw, " ", t12[1], "A", rc1, ",", rc1, " 0 0,", cr, " ", t12[0]);
          } else {
            path.push("M", t30[0], "A", rc1, ",", rc1, " 0 1,", cr, " ", t12[0]);
          }
        } else {
          path.push("M", x0, ",", y0);
        }
        if (x3 != null) {
          var t03 = d3_svg_arcCornerTangents([ x0, y0 ], [ x3, y3 ], r0, -rc0, cw), t21 = d3_svg_arcCornerTangents([ x2, y2 ], x1 == null ? [ x0, y0 ] : [ x1, y1 ], r0, -rc0, cw);
          if (rc === rc0) {
            path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t21[1], "A", r0, ",", r0, " 0 ", cw ^ d3_svg_arcSweep(t21[1][0], t21[1][1], t03[1][0], t03[1][1]), ",", 1 - cw, " ", t03[1], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]);
          } else {
            path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]);
          }
        } else {
          path.push("L", x2, ",", y2);
        }
      } else {
        path.push("M", x0, ",", y0);
        if (x1 != null) path.push("A", r1, ",", r1, " 0 ", l1, ",", cw, " ", x1, ",", y1);
        path.push("L", x2, ",", y2);
        if (x3 != null) path.push("A", r0, ",", r0, " 0 ", l0, ",", 1 - cw, " ", x3, ",", y3);
      }
      path.push("Z");
      return path.join("");
    }
    function circleSegment(r1, cw) {
      return "M0," + r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + -r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + r1;
    }
    arc.innerRadius = function(v) {
      if (!arguments.length) return innerRadius;
      innerRadius = d3_functor(v);
      return arc;
    };
    arc.outerRadius = function(v) {
      if (!arguments.length) return outerRadius;
      outerRadius = d3_functor(v);
      return arc;
    };
    arc.cornerRadius = function(v) {
      if (!arguments.length) return cornerRadius;
      cornerRadius = d3_functor(v);
      return arc;
    };
    arc.padRadius = function(v) {
      if (!arguments.length) return padRadius;
      padRadius = v == d3_svg_arcAuto ? d3_svg_arcAuto : d3_functor(v);
      return arc;
    };
    arc.startAngle = function(v) {
      if (!arguments.length) return startAngle;
      startAngle = d3_functor(v);
      return arc;
    };
    arc.endAngle = function(v) {
      if (!arguments.length) return endAngle;
      endAngle = d3_functor(v);
      return arc;
    };
    arc.padAngle = function(v) {
      if (!arguments.length) return padAngle;
      padAngle = d3_functor(v);
      return arc;
    };
    arc.centroid = function() {
      var r = (+innerRadius.apply(this, arguments) + +outerRadius.apply(this, arguments)) / 2, a = (+startAngle.apply(this, arguments) + +endAngle.apply(this, arguments)) / 2 - halfπ;
      return [ Math.cos(a) * r, Math.sin(a) * r ];
    };
    return arc;
  };
  var d3_svg_arcAuto = "auto";
  function d3_svg_arcInnerRadius(d) {
    return d.innerRadius;
  }
  function d3_svg_arcOuterRadius(d) {
    return d.outerRadius;
  }
  function d3_svg_arcStartAngle(d) {
    return d.startAngle;
  }
  function d3_svg_arcEndAngle(d) {
    return d.endAngle;
  }
  function d3_svg_arcPadAngle(d) {
    return d && d.padAngle;
  }
  function d3_svg_arcSweep(x0, y0, x1, y1) {
    return (x0 - x1) * y0 - (y0 - y1) * x0 > 0 ? 0 : 1;
  }
  function d3_svg_arcCornerTangents(p0, p1, r1, rc, cw) {
    var x01 = p0[0] - p1[0], y01 = p0[1] - p1[1], lo = (cw ? rc : -rc) / Math.sqrt(x01 * x01 + y01 * y01), ox = lo * y01, oy = -lo * x01, x1 = p0[0] + ox, y1 = p0[1] + oy, x2 = p1[0] + ox, y2 = p1[1] + oy, x3 = (x1 + x2) / 2, y3 = (y1 + y2) / 2, dx = x2 - x1, dy = y2 - y1, d2 = dx * dx + dy * dy, r = r1 - rc, D = x1 * y2 - x2 * y1, d = (dy < 0 ? -1 : 1) * Math.sqrt(Math.max(0, r * r * d2 - D * D)), cx0 = (D * dy - dx * d) / d2, cy0 = (-D * dx - dy * d) / d2, cx1 = (D * dy + dx * d) / d2, cy1 = (-D * dx + dy * d) / d2, dx0 = cx0 - x3, dy0 = cy0 - y3, dx1 = cx1 - x3, dy1 = cy1 - y3;
    if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) cx0 = cx1, cy0 = cy1;
    return [ [ cx0 - ox, cy0 - oy ], [ cx0 * r1 / r, cy0 * r1 / r ] ];
  }
  function d3_svg_line(projection) {
    var x = d3_geom_pointX, y = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7;
    function line(data) {
      var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y);
      function segment() {
        segments.push("M", interpolate(projection(points), tension));
      }
      while (++i < n) {
        if (defined.call(this, d = data[i], i)) {
          points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]);
        } else if (points.length) {
          segment();
          points = [];
        }
      }
      if (points.length) segment();
      return segments.length ? segments.join("") : null;
    }
    line.x = function(_) {
      if (!arguments.length) return x;
      x = _;
      return line;
    };
    line.y = function(_) {
      if (!arguments.length) return y;
      y = _;
      return line;
    };
    line.defined = function(_) {
      if (!arguments.length) return defined;
      defined = _;
      return line;
    };
    line.interpolate = function(_) {
      if (!arguments.length) return interpolateKey;
      if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
      return line;
    };
    line.tension = function(_) {
      if (!arguments.length) return tension;
      tension = _;
      return line;
    };
    return line;
  }
  d3.svg.line = function() {
    return d3_svg_line(d3_identity);
  };
  var d3_svg_lineInterpolators = d3.map({
    linear: d3_svg_lineLinear,
    "linear-closed": d3_svg_lineLinearClosed,
    step: d3_svg_lineStep,
    "step-before": d3_svg_lineStepBefore,
    "step-after": d3_svg_lineStepAfter,
    basis: d3_svg_lineBasis,
    "basis-open": d3_svg_lineBasisOpen,
    "basis-closed": d3_svg_lineBasisClosed,
    bundle: d3_svg_lineBundle,
    cardinal: d3_svg_lineCardinal,
    "cardinal-open": d3_svg_lineCardinalOpen,
    "cardinal-closed": d3_svg_lineCardinalClosed,
    monotone: d3_svg_lineMonotone
  });
  d3_svg_lineInterpolators.forEach(function(key, value) {
    value.key = key;
    value.closed = /-closed$/.test(key);
  });
  function d3_svg_lineLinear(points) {
    return points.length > 1 ? points.join("L") : points + "Z";
  }
  function d3_svg_lineLinearClosed(points) {
    return points.join("L") + "Z";
  }
  function d3_svg_lineStep(points) {
    var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
    while (++i < n) path.push("H", (p[0] + (p = points[i])[0]) / 2, "V", p[1]);
    if (n > 1) path.push("H", p[0]);
    return path.join("");
  }
  function d3_svg_lineStepBefore(points) {
    var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
    while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]);
    return path.join("");
  }
  function d3_svg_lineStepAfter(points) {
    var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
    while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]);
    return path.join("");
  }
  function d3_svg_lineCardinalOpen(points, tension) {
    return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, -1), d3_svg_lineCardinalTangents(points, tension));
  }
  function d3_svg_lineCardinalClosed(points, tension) {
    return points.length < 3 ? d3_svg_lineLinearClosed(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), 
    points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension));
  }
  function d3_svg_lineCardinal(points, tension) {
    return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension));
  }
  function d3_svg_lineHermite(points, tangents) {
    if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) {
      return d3_svg_lineLinear(points);
    }
    var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1;
    if (quad) {
      path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1];
      p0 = points[1];
      pi = 2;
    }
    if (tangents.length > 1) {
      t = tangents[1];
      p = points[pi];
      pi++;
      path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
      for (var i = 2; i < tangents.length; i++, pi++) {
        p = points[pi];
        t = tangents[i];
        path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
      }
    }
    if (quad) {
      var lp = points[pi];
      path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1];
    }
    return path;
  }
  function d3_svg_lineCardinalTangents(points, tension) {
    var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length;
    while (++i < n) {
      p0 = p1;
      p1 = p2;
      p2 = points[i];
      tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]);
    }
    return tangents;
  }
  function d3_svg_lineBasis(points) {
    if (points.length < 3) return d3_svg_lineLinear(points);
    var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0, "L", d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ];
    points.push(points[n - 1]);
    while (++i <= n) {
      pi = points[i];
      px.shift();
      px.push(pi[0]);
      py.shift();
      py.push(pi[1]);
      d3_svg_lineBasisBezier(path, px, py);
    }
    points.pop();
    path.push("L", pi);
    return path.join("");
  }
  function d3_svg_lineBasisOpen(points) {
    if (points.length < 4) return d3_svg_lineLinear(points);
    var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ];
    while (++i < 3) {
      pi = points[i];
      px.push(pi[0]);
      py.push(pi[1]);
    }
    path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py));
    --i;
    while (++i < n) {
      pi = points[i];
      px.shift();
      px.push(pi[0]);
      py.shift();
      py.push(pi[1]);
      d3_svg_lineBasisBezier(path, px, py);
    }
    return path.join("");
  }
  function d3_svg_lineBasisClosed(points) {
    var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = [];
    while (++i < 4) {
      pi = points[i % n];
      px.push(pi[0]);
      py.push(pi[1]);
    }
    path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ];
    --i;
    while (++i < m) {
      pi = points[i % n];
      px.shift();
      px.push(pi[0]);
      py.shift();
      py.push(pi[1]);
      d3_svg_lineBasisBezier(path, px, py);
    }
    return path.join("");
  }
  function d3_svg_lineBundle(points, tension) {
    var n = points.length - 1;
    if (n) {
      var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t;
      while (++i <= n) {
        p = points[i];
        t = i / n;
        p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx);
        p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy);
      }
    }
    return d3_svg_lineBasis(points);
  }
  function d3_svg_lineDot4(a, b) {
    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
  }
  var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ];
  function d3_svg_lineBasisBezier(path, x, y) {
    path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y));
  }
  function d3_svg_lineSlope(p0, p1) {
    return (p1[1] - p0[1]) / (p1[0] - p0[0]);
  }
  function d3_svg_lineFiniteDifferences(points) {
    var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1);
    while (++i < j) {
      m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2;
    }
    m[i] = d;
    return m;
  }
  function d3_svg_lineMonotoneTangents(points) {
    var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1;
    while (++i < j) {
      d = d3_svg_lineSlope(points[i], points[i + 1]);
      if (abs(d) < ε) {
        m[i] = m[i + 1] = 0;
      } else {
        a = m[i] / d;
        b = m[i + 1] / d;
        s = a * a + b * b;
        if (s > 9) {
          s = d * 3 / Math.sqrt(s);
          m[i] = s * a;
          m[i + 1] = s * b;
        }
      }
    }
    i = -1;
    while (++i <= j) {
      s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i]));
      tangents.push([ s || 0, m[i] * s || 0 ]);
    }
    return tangents;
  }
  function d3_svg_lineMonotone(points) {
    return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points));
  }
  d3.svg.line.radial = function() {
    var line = d3_svg_line(d3_svg_lineRadial);
    line.radius = line.x, delete line.x;
    line.angle = line.y, delete line.y;
    return line;
  };
  function d3_svg_lineRadial(points) {
    var point, i = -1, n = points.length, r, a;
    while (++i < n) {
      point = points[i];
      r = point[0];
      a = point[1] - halfπ;
      point[0] = r * Math.cos(a);
      point[1] = r * Math.sin(a);
    }
    return points;
  }
  function d3_svg_area(projection) {
    var x0 = d3_geom_pointX, x1 = d3_geom_pointX, y0 = 0, y1 = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7;
    function area(data) {
      var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() {
        return x;
      } : d3_functor(x1), fy1 = y0 === y1 ? function() {
        return y;
      } : d3_functor(y1), x, y;
      function segment() {
        segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z");
      }
      while (++i < n) {
        if (defined.call(this, d = data[i], i)) {
          points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]);
          points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]);
        } else if (points0.length) {
          segment();
          points0 = [];
          points1 = [];
        }
      }
      if (points0.length) segment();
      return segments.length ? segments.join("") : null;
    }
    area.x = function(_) {
      if (!arguments.length) return x1;
      x0 = x1 = _;
      return area;
    };
    area.x0 = function(_) {
      if (!arguments.length) return x0;
      x0 = _;
      return area;
    };
    area.x1 = function(_) {
      if (!arguments.length) return x1;
      x1 = _;
      return area;
    };
    area.y = function(_) {
      if (!arguments.length) return y1;
      y0 = y1 = _;
      return area;
    };
    area.y0 = function(_) {
      if (!arguments.length) return y0;
      y0 = _;
      return area;
    };
    area.y1 = function(_) {
      if (!arguments.length) return y1;
      y1 = _;
      return area;
    };
    area.defined = function(_) {
      if (!arguments.length) return defined;
      defined = _;
      return area;
    };
    area.interpolate = function(_) {
      if (!arguments.length) return interpolateKey;
      if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
      interpolateReverse = interpolate.reverse || interpolate;
      L = interpolate.closed ? "M" : "L";
      return area;
    };
    area.tension = function(_) {
      if (!arguments.length) return tension;
      tension = _;
      return area;
    };
    return area;
  }
  d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter;
  d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore;
  d3.svg.area = function() {
    return d3_svg_area(d3_identity);
  };
  d3.svg.area.radial = function() {
    var area = d3_svg_area(d3_svg_lineRadial);
    area.radius = area.x, delete area.x;
    area.innerRadius = area.x0, delete area.x0;
    area.outerRadius = area.x1, delete area.x1;
    area.angle = area.y, delete area.y;
    area.startAngle = area.y0, delete area.y0;
    area.endAngle = area.y1, delete area.y1;
    return area;
  };
  d3.svg.chord = function() {
    var source = d3_source, target = d3_target, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle;
    function chord(d, i) {
      var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i);
      return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z";
    }
    function subgroup(self, f, d, i) {
      var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) - halfπ, a1 = endAngle.call(self, subgroup, i) - halfπ;
      return {
        r: r,
        a0: a0,
        a1: a1,
        p0: [ r * Math.cos(a0), r * Math.sin(a0) ],
        p1: [ r * Math.cos(a1), r * Math.sin(a1) ]
      };
    }
    function equals(a, b) {
      return a.a0 == b.a0 && a.a1 == b.a1;
    }
    function arc(r, p, a) {
      return "A" + r + "," + r + " 0 " + +(a > π) + ",1 " + p;
    }
    function curve(r0, p0, r1, p1) {
      return "Q 0,0 " + p1;
    }
    chord.radius = function(v) {
      if (!arguments.length) return radius;
      radius = d3_functor(v);
      return chord;
    };
    chord.source = function(v) {
      if (!arguments.length) return source;
      source = d3_functor(v);
      return chord;
    };
    chord.target = function(v) {
      if (!arguments.length) return target;
      target = d3_functor(v);
      return chord;
    };
    chord.startAngle = function(v) {
      if (!arguments.length) return startAngle;
      startAngle = d3_functor(v);
      return chord;
    };
    chord.endAngle = function(v) {
      if (!arguments.length) return endAngle;
      endAngle = d3_functor(v);
      return chord;
    };
    return chord;
  };
  function d3_svg_chordRadius(d) {
    return d.radius;
  }
  d3.svg.diagonal = function() {
    var source = d3_source, target = d3_target, projection = d3_svg_diagonalProjection;
    function diagonal(d, i) {
      var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, {
        x: p0.x,
        y: m
      }, {
        x: p3.x,
        y: m
      }, p3 ];
      p = p.map(projection);
      return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3];
    }
    diagonal.source = function(x) {
      if (!arguments.length) return source;
      source = d3_functor(x);
      return diagonal;
    };
    diagonal.target = function(x) {
      if (!arguments.length) return target;
      target = d3_functor(x);
      return diagonal;
    };
    diagonal.projection = function(x) {
      if (!arguments.length) return projection;
      projection = x;
      return diagonal;
    };
    return diagonal;
  };
  function d3_svg_diagonalProjection(d) {
    return [ d.x, d.y ];
  }
  d3.svg.diagonal.radial = function() {
    var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection;
    diagonal.projection = function(x) {
      return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection;
    };
    return diagonal;
  };
  function d3_svg_diagonalRadialProjection(projection) {
    return function() {
      var d = projection.apply(this, arguments), r = d[0], a = d[1] - halfπ;
      return [ r * Math.cos(a), r * Math.sin(a) ];
    };
  }
  d3.svg.symbol = function() {
    var type = d3_svg_symbolType, size = d3_svg_symbolSize;
    function symbol(d, i) {
      return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i));
    }
    symbol.type = function(x) {
      if (!arguments.length) return type;
      type = d3_functor(x);
      return symbol;
    };
    symbol.size = function(x) {
      if (!arguments.length) return size;
      size = d3_functor(x);
      return symbol;
    };
    return symbol;
  };
  function d3_svg_symbolSize() {
    return 64;
  }
  function d3_svg_symbolType() {
    return "circle";
  }
  function d3_svg_symbolCircle(size) {
    var r = Math.sqrt(size / π);
    return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z";
  }
  var d3_svg_symbols = d3.map({
    circle: d3_svg_symbolCircle,
    cross: function(size) {
      var r = Math.sqrt(size / 5) / 2;
      return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z";
    },
    diamond: function(size) {
      var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30;
      return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z";
    },
    square: function(size) {
      var r = Math.sqrt(size) / 2;
      return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z";
    },
    "triangle-down": function(size) {
      var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2;
      return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z";
    },
    "triangle-up": function(size) {
      var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2;
      return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z";
    }
  });
  d3.svg.symbolTypes = d3_svg_symbols.keys();
  var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * d3_radians);
  d3_selectionPrototype.transition = function(name) {
    var id = d3_transitionInheritId || ++d3_transitionId, ns = d3_transitionNamespace(name), subgroups = [], subgroup, node, transition = d3_transitionInherit || {
      time: Date.now(),
      ease: d3_ease_cubicInOut,
      delay: 0,
      duration: 250
    };
    for (var j = -1, m = this.length; ++j < m; ) {
      subgroups.push(subgroup = []);
      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
        if (node = group[i]) d3_transitionNode(node, i, ns, id, transition);
        subgroup.push(node);
      }
    }
    return d3_transition(subgroups, ns, id);
  };
  d3_selectionPrototype.interrupt = function(name) {
    return this.each(name == null ? d3_selection_interrupt : d3_selection_interruptNS(d3_transitionNamespace(name)));
  };
  var d3_selection_interrupt = d3_selection_interruptNS(d3_transitionNamespace());
  function d3_selection_interruptNS(ns) {
    return function() {
      var lock, activeId, active;
      if ((lock = this[ns]) && (active = lock[activeId = lock.active])) {
        active.timer.c = null;
        active.timer.t = NaN;
        if (--lock.count) delete lock[activeId]; else delete this[ns];
        lock.active += .5;
        active.event && active.event.interrupt.call(this, this.__data__, active.index);
      }
    };
  }
  function d3_transition(groups, ns, id) {
    d3_subclass(groups, d3_transitionPrototype);
    groups.namespace = ns;
    groups.id = id;
    return groups;
  }
  var d3_transitionPrototype = [], d3_transitionId = 0, d3_transitionInheritId, d3_transitionInherit;
  d3_transitionPrototype.call = d3_selectionPrototype.call;
  d3_transitionPrototype.empty = d3_selectionPrototype.empty;
  d3_transitionPrototype.node = d3_selectionPrototype.node;
  d3_transitionPrototype.size = d3_selectionPrototype.size;
  d3.transition = function(selection, name) {
    return selection && selection.transition ? d3_transitionInheritId ? selection.transition(name) : selection : d3.selection().transition(selection);
  };
  d3.transition.prototype = d3_transitionPrototype;
  d3_transitionPrototype.select = function(selector) {
    var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnode, node;
    selector = d3_selection_selector(selector);
    for (var j = -1, m = this.length; ++j < m; ) {
      subgroups.push(subgroup = []);
      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
        if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i, j))) {
          if ("__data__" in node) subnode.__data__ = node.__data__;
          d3_transitionNode(subnode, i, ns, id, node[ns][id]);
          subgroup.push(subnode);
        } else {
          subgroup.push(null);
        }
      }
    }
    return d3_transition(subgroups, ns, id);
  };
  d3_transitionPrototype.selectAll = function(selector) {
    var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnodes, node, subnode, transition;
    selector = d3_selection_selectorAll(selector);
    for (var j = -1, m = this.length; ++j < m; ) {
      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
        if (node = group[i]) {
          transition = node[ns][id];
          subnodes = selector.call(node, node.__data__, i, j);
          subgroups.push(subgroup = []);
          for (var k = -1, o = subnodes.length; ++k < o; ) {
            if (subnode = subnodes[k]) d3_transitionNode(subnode, k, ns, id, transition);
            subgroup.push(subnode);
          }
        }
      }
    }
    return d3_transition(subgroups, ns, id);
  };
  d3_transitionPrototype.filter = function(filter) {
    var subgroups = [], subgroup, group, node;
    if (typeof filter !== "function") filter = d3_selection_filter(filter);
    for (var j = 0, m = this.length; j < m; j++) {
      subgroups.push(subgroup = []);
      for (var group = this[j], i = 0, n = group.length; i < n; i++) {
        if ((node = group[i]) && filter.call(node, node.__data__, i, j)) {
          subgroup.push(node);
        }
      }
    }
    return d3_transition(subgroups, this.namespace, this.id);
  };
  d3_transitionPrototype.tween = function(name, tween) {
    var id = this.id, ns = this.namespace;
    if (arguments.length < 2) return this.node()[ns][id].tween.get(name);
    return d3_selection_each(this, tween == null ? function(node) {
      node[ns][id].tween.remove(name);
    } : function(node) {
      node[ns][id].tween.set(name, tween);
    });
  };
  function d3_transition_tween(groups, name, value, tween) {
    var id = groups.id, ns = groups.namespace;
    return d3_selection_each(groups, typeof value === "function" ? function(node, i, j) {
      node[ns][id].tween.set(name, tween(value.call(node, node.__data__, i, j)));
    } : (value = tween(value), function(node) {
      node[ns][id].tween.set(name, value);
    }));
  }
  d3_transitionPrototype.attr = function(nameNS, value) {
    if (arguments.length < 2) {
      for (value in nameNS) this.attr(value, nameNS[value]);
      return this;
    }
    var interpolate = nameNS == "transform" ? d3_interpolateTransform : d3_interpolate, name = d3.ns.qualify(nameNS);
    function attrNull() {
      this.removeAttribute(name);
    }
    function attrNullNS() {
      this.removeAttributeNS(name.space, name.local);
    }
    function attrTween(b) {
      return b == null ? attrNull : (b += "", function() {
        var a = this.getAttribute(name), i;
        return a !== b && (i = interpolate(a, b), function(t) {
          this.setAttribute(name, i(t));
        });
      });
    }
    function attrTweenNS(b) {
      return b == null ? attrNullNS : (b += "", function() {
        var a = this.getAttributeNS(name.space, name.local), i;
        return a !== b && (i = interpolate(a, b), function(t) {
          this.setAttributeNS(name.space, name.local, i(t));
        });
      });
    }
    return d3_transition_tween(this, "attr." + nameNS, value, name.local ? attrTweenNS : attrTween);
  };
  d3_transitionPrototype.attrTween = function(nameNS, tween) {
    var name = d3.ns.qualify(nameNS);
    function attrTween(d, i) {
      var f = tween.call(this, d, i, this.getAttribute(name));
      return f && function(t) {
        this.setAttribute(name, f(t));
      };
    }
    function attrTweenNS(d, i) {
      var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local));
      return f && function(t) {
        this.setAttributeNS(name.space, name.local, f(t));
      };
    }
    return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween);
  };
  d3_transitionPrototype.style = function(name, value, priority) {
    var n = arguments.length;
    if (n < 3) {
      if (typeof name !== "string") {
        if (n < 2) value = "";
        for (priority in name) this.style(priority, name[priority], value);
        return this;
      }
      priority = "";
    }
    function styleNull() {
      this.style.removeProperty(name);
    }
    function styleString(b) {
      return b == null ? styleNull : (b += "", function() {
        var a = d3_window(this).getComputedStyle(this, null).getPropertyValue(name), i;
        return a !== b && (i = d3_interpolate(a, b), function(t) {
          this.style.setProperty(name, i(t), priority);
        });
      });
    }
    return d3_transition_tween(this, "style." + name, value, styleString);
  };
  d3_transitionPrototype.styleTween = function(name, tween, priority) {
    if (arguments.length < 3) priority = "";
    function styleTween(d, i) {
      var f = tween.call(this, d, i, d3_window(this).getComputedStyle(this, null).getPropertyValue(name));
      return f && function(t) {
        this.style.setProperty(name, f(t), priority);
      };
    }
    return this.tween("style." + name, styleTween);
  };
  d3_transitionPrototype.text = function(value) {
    return d3_transition_tween(this, "text", value, d3_transition_text);
  };
  function d3_transition_text(b) {
    if (b == null) b = "";
    return function() {
      this.textContent = b;
    };
  }
  d3_transitionPrototype.remove = function() {
    var ns = this.namespace;
    return this.each("end.transition", function() {
      var p;
      if (this[ns].count < 2 && (p = this.parentNode)) p.removeChild(this);
    });
  };
  d3_transitionPrototype.ease = function(value) {
    var id = this.id, ns = this.namespace;
    if (arguments.length < 1) return this.node()[ns][id].ease;
    if (typeof value !== "function") value = d3.ease.apply(d3, arguments);
    return d3_selection_each(this, function(node) {
      node[ns][id].ease = value;
    });
  };
  d3_transitionPrototype.delay = function(value) {
    var id = this.id, ns = this.namespace;
    if (arguments.length < 1) return this.node()[ns][id].delay;
    return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
      node[ns][id].delay = +value.call(node, node.__data__, i, j);
    } : (value = +value, function(node) {
      node[ns][id].delay = value;
    }));
  };
  d3_transitionPrototype.duration = function(value) {
    var id = this.id, ns = this.namespace;
    if (arguments.length < 1) return this.node()[ns][id].duration;
    return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
      node[ns][id].duration = Math.max(1, value.call(node, node.__data__, i, j));
    } : (value = Math.max(1, value), function(node) {
      node[ns][id].duration = value;
    }));
  };
  d3_transitionPrototype.each = function(type, listener) {
    var id = this.id, ns = this.namespace;
    if (arguments.length < 2) {
      var inherit = d3_transitionInherit, inheritId = d3_transitionInheritId;
      try {
        d3_transitionInheritId = id;
        d3_selection_each(this, function(node, i, j) {
          d3_transitionInherit = node[ns][id];
          type.call(node, node.__data__, i, j);
        });
      } finally {
        d3_transitionInherit = inherit;
        d3_transitionInheritId = inheritId;
      }
    } else {
      d3_selection_each(this, function(node) {
        var transition = node[ns][id];
        (transition.event || (transition.event = d3.dispatch("start", "end", "interrupt"))).on(type, listener);
      });
    }
    return this;
  };
  d3_transitionPrototype.transition = function() {
    var id0 = this.id, id1 = ++d3_transitionId, ns = this.namespace, subgroups = [], subgroup, group, node, transition;
    for (var j = 0, m = this.length; j < m; j++) {
      subgroups.push(subgroup = []);
      for (var group = this[j], i = 0, n = group.length; i < n; i++) {
        if (node = group[i]) {
          transition = node[ns][id0];
          d3_transitionNode(node, i, ns, id1, {
            time: transition.time,
            ease: transition.ease,
            delay: transition.delay + transition.duration,
            duration: transition.duration
          });
        }
        subgroup.push(node);
      }
    }
    return d3_transition(subgroups, ns, id1);
  };
  function d3_transitionNamespace(name) {
    return name == null ? "__transition__" : "__transition_" + name + "__";
  }
  function d3_transitionNode(node, i, ns, id, inherit) {
    var lock = node[ns] || (node[ns] = {
      active: 0,
      count: 0
    }), transition = lock[id], time, timer, duration, ease, tweens;
    function schedule(elapsed) {
      var delay = transition.delay;
      timer.t = delay + time;
      if (delay <= elapsed) return start(elapsed - delay);
      timer.c = start;
    }
    function start(elapsed) {
      var activeId = lock.active, active = lock[activeId];
      if (active) {
        active.timer.c = null;
        active.timer.t = NaN;
        --lock.count;
        delete lock[activeId];
        active.event && active.event.interrupt.call(node, node.__data__, active.index);
      }
      for (var cancelId in lock) {
        if (+cancelId < id) {
          var cancel = lock[cancelId];
          cancel.timer.c = null;
          cancel.timer.t = NaN;
          --lock.count;
          delete lock[cancelId];
        }
      }
      timer.c = tick;
      d3_timer(function() {
        if (timer.c && tick(elapsed || 1)) {
          timer.c = null;
          timer.t = NaN;
        }
        return 1;
      }, 0, time);
      lock.active = id;
      transition.event && transition.event.start.call(node, node.__data__, i);
      tweens = [];
      transition.tween.forEach(function(key, value) {
        if (value = value.call(node, node.__data__, i)) {
          tweens.push(value);
        }
      });
      ease = transition.ease;
      duration = transition.duration;
    }
    function tick(elapsed) {
      var t = elapsed / duration, e = ease(t), n = tweens.length;
      while (n > 0) {
        tweens[--n].call(node, e);
      }
      if (t >= 1) {
        transition.event && transition.event.end.call(node, node.__data__, i);
        if (--lock.count) delete lock[id]; else delete node[ns];
        return 1;
      }
    }
    if (!transition) {
      time = inherit.time;
      timer = d3_timer(schedule, 0, time);
      transition = lock[id] = {
        tween: new d3_Map(),
        time: time,
        timer: timer,
        delay: inherit.delay,
        duration: inherit.duration,
        ease: inherit.ease,
        index: i
      };
      inherit = null;
      ++lock.count;
    }
  }
  d3.svg.axis = function() {
    var scale = d3.scale.linear(), orient = d3_svg_axisDefaultOrient, innerTickSize = 6, outerTickSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_;
    function axis(g) {
      g.each(function() {
        var g = d3.select(this);
        var scale0 = this.__chart__ || scale, scale1 = this.__chart__ = scale.copy();
        var ticks = tickValues == null ? scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain() : tickValues, tickFormat = tickFormat_ == null ? scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity : tickFormat_, tick = g.selectAll(".tick").data(ticks, scale1), tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε), tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(), tickUpdate = d3.transition(tick.order()).style("opacity", 1), tickSpacing = Math.max(innerTickSize, 0) + tickPadding, tickTransform;
        var range = d3_scaleRange(scale1), path = g.selectAll(".domain").data([ 0 ]), pathUpdate = (path.enter().append("path").attr("class", "domain"), 
        d3.transition(path));
        tickEnter.append("line");
        tickEnter.append("text");
        var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"), sign = orient === "top" || orient === "left" ? -1 : 1, x1, x2, y1, y2;
        if (orient === "bottom" || orient === "top") {
          tickTransform = d3_svg_axisX, x1 = "x", y1 = "y", x2 = "x2", y2 = "y2";
          text.attr("dy", sign < 0 ? "0em" : ".71em").style("text-anchor", "middle");
          pathUpdate.attr("d", "M" + range[0] + "," + sign * outerTickSize + "V0H" + range[1] + "V" + sign * outerTickSize);
        } else {
          tickTransform = d3_svg_axisY, x1 = "y", y1 = "x", x2 = "y2", y2 = "x2";
          text.attr("dy", ".32em").style("text-anchor", sign < 0 ? "end" : "start");
          pathUpdate.attr("d", "M" + sign * outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + sign * outerTickSize);
        }
        lineEnter.attr(y2, sign * innerTickSize);
        textEnter.attr(y1, sign * tickSpacing);
        lineUpdate.attr(x2, 0).attr(y2, sign * innerTickSize);
        textUpdate.attr(x1, 0).attr(y1, sign * tickSpacing);
        if (scale1.rangeBand) {
          var x = scale1, dx = x.rangeBand() / 2;
          scale0 = scale1 = function(d) {
            return x(d) + dx;
          };
        } else if (scale0.rangeBand) {
          scale0 = scale1;
        } else {
          tickExit.call(tickTransform, scale1, scale0);
        }
        tickEnter.call(tickTransform, scale0, scale1);
        tickUpdate.call(tickTransform, scale1, scale1);
      });
    }
    axis.scale = function(x) {
      if (!arguments.length) return scale;
      scale = x;
      return axis;
    };
    axis.orient = function(x) {
      if (!arguments.length) return orient;
      orient = x in d3_svg_axisOrients ? x + "" : d3_svg_axisDefaultOrient;
      return axis;
    };
    axis.ticks = function() {
      if (!arguments.length) return tickArguments_;
      tickArguments_ = d3_array(arguments);
      return axis;
    };
    axis.tickValues = function(x) {
      if (!arguments.length) return tickValues;
      tickValues = x;
      return axis;
    };
    axis.tickFormat = function(x) {
      if (!arguments.length) return tickFormat_;
      tickFormat_ = x;
      return axis;
    };
    axis.tickSize = function(x) {
      var n = arguments.length;
      if (!n) return innerTickSize;
      innerTickSize = +x;
      outerTickSize = +arguments[n - 1];
      return axis;
    };
    axis.innerTickSize = function(x) {
      if (!arguments.length) return innerTickSize;
      innerTickSize = +x;
      return axis;
    };
    axis.outerTickSize = function(x) {
      if (!arguments.length) return outerTickSize;
      outerTickSize = +x;
      return axis;
    };
    axis.tickPadding = function(x) {
      if (!arguments.length) return tickPadding;
      tickPadding = +x;
      return axis;
    };
    axis.tickSubdivide = function() {
      return arguments.length && axis;
    };
    return axis;
  };
  var d3_svg_axisDefaultOrient = "bottom", d3_svg_axisOrients = {
    top: 1,
    right: 1,
    bottom: 1,
    left: 1
  };
  function d3_svg_axisX(selection, x0, x1) {
    selection.attr("transform", function(d) {
      var v0 = x0(d);
      return "translate(" + (isFinite(v0) ? v0 : x1(d)) + ",0)";
    });
  }
  function d3_svg_axisY(selection, y0, y1) {
    selection.attr("transform", function(d) {
      var v0 = y0(d);
      return "translate(0," + (isFinite(v0) ? v0 : y1(d)) + ")";
    });
  }
  d3.svg.brush = function() {
    var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, xExtent = [ 0, 0 ], yExtent = [ 0, 0 ], xExtentDomain, yExtentDomain, xClamp = true, yClamp = true, resizes = d3_svg_brushResizes[0];
    function brush(g) {
      g.each(function() {
        var g = d3.select(this).style("pointer-events", "all").style("-webkit-tap-highlight-color", "rgba(0,0,0,0)").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart);
        var background = g.selectAll(".background").data([ 0 ]);
        background.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair");
        g.selectAll(".extent").data([ 0 ]).enter().append("rect").attr("class", "extent").style("cursor", "move");
        var resize = g.selectAll(".resize").data(resizes, d3_identity);
        resize.exit().remove();
        resize.enter().append("g").attr("class", function(d) {
          return "resize " + d;
        }).style("cursor", function(d) {
          return d3_svg_brushCursor[d];
        }).append("rect").attr("x", function(d) {
          return /[ew]$/.test(d) ? -3 : null;
        }).attr("y", function(d) {
          return /^[ns]/.test(d) ? -3 : null;
        }).attr("width", 6).attr("height", 6).style("visibility", "hidden");
        resize.style("display", brush.empty() ? "none" : null);
        var gUpdate = d3.transition(g), backgroundUpdate = d3.transition(background), range;
        if (x) {
          range = d3_scaleRange(x);
          backgroundUpdate.attr("x", range[0]).attr("width", range[1] - range[0]);
          redrawX(gUpdate);
        }
        if (y) {
          range = d3_scaleRange(y);
          backgroundUpdate.attr("y", range[0]).attr("height", range[1] - range[0]);
          redrawY(gUpdate);
        }
        redraw(gUpdate);
      });
    }
    brush.event = function(g) {
      g.each(function() {
        var event_ = event.of(this, arguments), extent1 = {
          x: xExtent,
          y: yExtent,
          i: xExtentDomain,
          j: yExtentDomain
        }, extent0 = this.__chart__ || extent1;
        this.__chart__ = extent1;
        if (d3_transitionInheritId) {
          d3.select(this).transition().each("start.brush", function() {
            xExtentDomain = extent0.i;
            yExtentDomain = extent0.j;
            xExtent = extent0.x;
            yExtent = extent0.y;
            event_({
              type: "brushstart"
            });
          }).tween("brush:brush", function() {
            var xi = d3_interpolateArray(xExtent, extent1.x), yi = d3_interpolateArray(yExtent, extent1.y);
            xExtentDomain = yExtentDomain = null;
            return function(t) {
              xExtent = extent1.x = xi(t);
              yExtent = extent1.y = yi(t);
              event_({
                type: "brush",
                mode: "resize"
              });
            };
          }).each("end.brush", function() {
            xExtentDomain = extent1.i;
            yExtentDomain = extent1.j;
            event_({
              type: "brush",
              mode: "resize"
            });
            event_({
              type: "brushend"
            });
          });
        } else {
          event_({
            type: "brushstart"
          });
          event_({
            type: "brush",
            mode: "resize"
          });
          event_({
            type: "brushend"
          });
        }
      });
    };
    function redraw(g) {
      g.selectAll(".resize").attr("transform", function(d) {
        return "translate(" + xExtent[+/e$/.test(d)] + "," + yExtent[+/^s/.test(d)] + ")";
      });
    }
    function redrawX(g) {
      g.select(".extent").attr("x", xExtent[0]);
      g.selectAll(".extent,.n>rect,.s>rect").attr("width", xExtent[1] - xExtent[0]);
    }
    function redrawY(g) {
      g.select(".extent").attr("y", yExtent[0]);
      g.selectAll(".extent,.e>rect,.w>rect").attr("height", yExtent[1] - yExtent[0]);
    }
    function brushstart() {
      var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), dragRestore = d3_event_dragSuppress(target), center, origin = d3.mouse(target), offset;
      var w = d3.select(d3_window(target)).on("keydown.brush", keydown).on("keyup.brush", keyup);
      if (d3.event.changedTouches) {
        w.on("touchmove.brush", brushmove).on("touchend.brush", brushend);
      } else {
        w.on("mousemove.brush", brushmove).on("mouseup.brush", brushend);
      }
      g.interrupt().selectAll("*").interrupt();
      if (dragging) {
        origin[0] = xExtent[0] - origin[0];
        origin[1] = yExtent[0] - origin[1];
      } else if (resizing) {
        var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing);
        offset = [ xExtent[1 - ex] - origin[0], yExtent[1 - ey] - origin[1] ];
        origin[0] = xExtent[ex];
        origin[1] = yExtent[ey];
      } else if (d3.event.altKey) center = origin.slice();
      g.style("pointer-events", "none").selectAll(".resize").style("display", null);
      d3.select("body").style("cursor", eventTarget.style("cursor"));
      event_({
        type: "brushstart"
      });
      brushmove();
      function keydown() {
        if (d3.event.keyCode == 32) {
          if (!dragging) {
            center = null;
            origin[0] -= xExtent[1];
            origin[1] -= yExtent[1];
            dragging = 2;
          }
          d3_eventPreventDefault();
        }
      }
      function keyup() {
        if (d3.event.keyCode == 32 && dragging == 2) {
          origin[0] += xExtent[1];
          origin[1] += yExtent[1];
          dragging = 0;
          d3_eventPreventDefault();
        }
      }
      function brushmove() {
        var point = d3.mouse(target), moved = false;
        if (offset) {
          point[0] += offset[0];
          point[1] += offset[1];
        }
        if (!dragging) {
          if (d3.event.altKey) {
            if (!center) center = [ (xExtent[0] + xExtent[1]) / 2, (yExtent[0] + yExtent[1]) / 2 ];
            origin[0] = xExtent[+(point[0] < center[0])];
            origin[1] = yExtent[+(point[1] < center[1])];
          } else center = null;
        }
        if (resizingX && move1(point, x, 0)) {
          redrawX(g);
          moved = true;
        }
        if (resizingY && move1(point, y, 1)) {
          redrawY(g);
          moved = true;
        }
        if (moved) {
          redraw(g);
          event_({
            type: "brush",
            mode: dragging ? "move" : "resize"
          });
        }
      }
      function move1(point, scale, i) {
        var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], extent = i ? yExtent : xExtent, size = extent[1] - extent[0], min, max;
        if (dragging) {
          r0 -= position;
          r1 -= size + position;
        }
        min = (i ? yClamp : xClamp) ? Math.max(r0, Math.min(r1, point[i])) : point[i];
        if (dragging) {
          max = (min += position) + size;
        } else {
          if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min));
          if (position < min) {
            max = min;
            min = position;
          } else {
            max = position;
          }
        }
        if (extent[0] != min || extent[1] != max) {
          if (i) yExtentDomain = null; else xExtentDomain = null;
          extent[0] = min;
          extent[1] = max;
          return true;
        }
      }
      function brushend() {
        brushmove();
        g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null);
        d3.select("body").style("cursor", null);
        w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null);
        dragRestore();
        event_({
          type: "brushend"
        });
      }
    }
    brush.x = function(z) {
      if (!arguments.length) return x;
      x = z;
      resizes = d3_svg_brushResizes[!x << 1 | !y];
      return brush;
    };
    brush.y = function(z) {
      if (!arguments.length) return y;
      y = z;
      resizes = d3_svg_brushResizes[!x << 1 | !y];
      return brush;
    };
    brush.clamp = function(z) {
      if (!arguments.length) return x && y ? [ xClamp, yClamp ] : x ? xClamp : y ? yClamp : null;
      if (x && y) xClamp = !!z[0], yClamp = !!z[1]; else if (x) xClamp = !!z; else if (y) yClamp = !!z;
      return brush;
    };
    brush.extent = function(z) {
      var x0, x1, y0, y1, t;
      if (!arguments.length) {
        if (x) {
          if (xExtentDomain) {
            x0 = xExtentDomain[0], x1 = xExtentDomain[1];
          } else {
            x0 = xExtent[0], x1 = xExtent[1];
            if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1);
            if (x1 < x0) t = x0, x0 = x1, x1 = t;
          }
        }
        if (y) {
          if (yExtentDomain) {
            y0 = yExtentDomain[0], y1 = yExtentDomain[1];
          } else {
            y0 = yExtent[0], y1 = yExtent[1];
            if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1);
            if (y1 < y0) t = y0, y0 = y1, y1 = t;
          }
        }
        return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ];
      }
      if (x) {
        x0 = z[0], x1 = z[1];
        if (y) x0 = x0[0], x1 = x1[0];
        xExtentDomain = [ x0, x1 ];
        if (x.invert) x0 = x(x0), x1 = x(x1);
        if (x1 < x0) t = x0, x0 = x1, x1 = t;
        if (x0 != xExtent[0] || x1 != xExtent[1]) xExtent = [ x0, x1 ];
      }
      if (y) {
        y0 = z[0], y1 = z[1];
        if (x) y0 = y0[1], y1 = y1[1];
        yExtentDomain = [ y0, y1 ];
        if (y.invert) y0 = y(y0), y1 = y(y1);
        if (y1 < y0) t = y0, y0 = y1, y1 = t;
        if (y0 != yExtent[0] || y1 != yExtent[1]) yExtent = [ y0, y1 ];
      }
      return brush;
    };
    brush.clear = function() {
      if (!brush.empty()) {
        xExtent = [ 0, 0 ], yExtent = [ 0, 0 ];
        xExtentDomain = yExtentDomain = null;
      }
      return brush;
    };
    brush.empty = function() {
      return !!x && xExtent[0] == xExtent[1] || !!y && yExtent[0] == yExtent[1];
    };
    return d3.rebind(brush, event, "on");
  };
  var d3_svg_brushCursor = {
    n: "ns-resize",
    e: "ew-resize",
    s: "ns-resize",
    w: "ew-resize",
    nw: "nwse-resize",
    ne: "nesw-resize",
    se: "nwse-resize",
    sw: "nesw-resize"
  };
  var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ];
  var d3_time_format = d3_time.format = d3_locale_enUS.timeFormat;
  var d3_time_formatUtc = d3_time_format.utc;
  var d3_time_formatIso = d3_time_formatUtc("%Y-%m-%dT%H:%M:%S.%LZ");
  d3_time_format.iso = Date.prototype.toISOString && +new Date("2000-01-01T00:00:00.000Z") ? d3_time_formatIsoNative : d3_time_formatIso;
  function d3_time_formatIsoNative(date) {
    return date.toISOString();
  }
  d3_time_formatIsoNative.parse = function(string) {
    var date = new Date(string);
    return isNaN(date) ? null : date;
  };
  d3_time_formatIsoNative.toString = d3_time_formatIso.toString;
  d3_time.second = d3_time_interval(function(date) {
    return new d3_date(Math.floor(date / 1e3) * 1e3);
  }, function(date, offset) {
    date.setTime(date.getTime() + Math.floor(offset) * 1e3);
  }, function(date) {
    return date.getSeconds();
  });
  d3_time.seconds = d3_time.second.range;
  d3_time.seconds.utc = d3_time.second.utc.range;
  d3_time.minute = d3_time_interval(function(date) {
    return new d3_date(Math.floor(date / 6e4) * 6e4);
  }, function(date, offset) {
    date.setTime(date.getTime() + Math.floor(offset) * 6e4);
  }, function(date) {
    return date.getMinutes();
  });
  d3_time.minutes = d3_time.minute.range;
  d3_time.minutes.utc = d3_time.minute.utc.range;
  d3_time.hour = d3_time_interval(function(date) {
    var timezone = date.getTimezoneOffset() / 60;
    return new d3_date((Math.floor(date / 36e5 - timezone) + timezone) * 36e5);
  }, function(date, offset) {
    date.setTime(date.getTime() + Math.floor(offset) * 36e5);
  }, function(date) {
    return date.getHours();
  });
  d3_time.hours = d3_time.hour.range;
  d3_time.hours.utc = d3_time.hour.utc.range;
  d3_time.month = d3_time_interval(function(date) {
    date = d3_time.day(date);
    date.setDate(1);
    return date;
  }, function(date, offset) {
    date.setMonth(date.getMonth() + offset);
  }, function(date) {
    return date.getMonth();
  });
  d3_time.months = d3_time.month.range;
  d3_time.months.utc = d3_time.month.utc.range;
  function d3_time_scale(linear, methods, format) {
    function scale(x) {
      return linear(x);
    }
    scale.invert = function(x) {
      return d3_time_scaleDate(linear.invert(x));
    };
    scale.domain = function(x) {
      if (!arguments.length) return linear.domain().map(d3_time_scaleDate);
      linear.domain(x);
      return scale;
    };
    function tickMethod(extent, count) {
      var span = extent[1] - extent[0], target = span / count, i = d3.bisect(d3_time_scaleSteps, target);
      return i == d3_time_scaleSteps.length ? [ methods.year, d3_scale_linearTickRange(extent.map(function(d) {
        return d / 31536e6;
      }), count)[2] ] : !i ? [ d3_time_scaleMilliseconds, d3_scale_linearTickRange(extent, count)[2] ] : methods[target / d3_time_scaleSteps[i - 1] < d3_time_scaleSteps[i] / target ? i - 1 : i];
    }
    scale.nice = function(interval, skip) {
      var domain = scale.domain(), extent = d3_scaleExtent(domain), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" && tickMethod(extent, interval);
      if (method) interval = method[0], skip = method[1];
      function skipped(date) {
        return !isNaN(date) && !interval.range(date, d3_time_scaleDate(+date + 1), skip).length;
      }
      return scale.domain(d3_scale_nice(domain, skip > 1 ? {
        floor: function(date) {
          while (skipped(date = interval.floor(date))) date = d3_time_scaleDate(date - 1);
          return date;
        },
        ceil: function(date) {
          while (skipped(date = interval.ceil(date))) date = d3_time_scaleDate(+date + 1);
          return date;
        }
      } : interval));
    };
    scale.ticks = function(interval, skip) {
      var extent = d3_scaleExtent(scale.domain()), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" ? tickMethod(extent, interval) : !interval.range && [ {
        range: interval
      }, skip ];
      if (method) interval = method[0], skip = method[1];
      return interval.range(extent[0], d3_time_scaleDate(+extent[1] + 1), skip < 1 ? 1 : skip);
    };
    scale.tickFormat = function() {
      return format;
    };
    scale.copy = function() {
      return d3_time_scale(linear.copy(), methods, format);
    };
    return d3_scale_linearRebind(scale, linear);
  }
  function d3_time_scaleDate(t) {
    return new Date(t);
  }
  var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ];
  var d3_time_scaleLocalMethods = [ [ d3_time.second, 1 ], [ d3_time.second, 5 ], [ d3_time.second, 15 ], [ d3_time.second, 30 ], [ d3_time.minute, 1 ], [ d3_time.minute, 5 ], [ d3_time.minute, 15 ], [ d3_time.minute, 30 ], [ d3_time.hour, 1 ], [ d3_time.hour, 3 ], [ d3_time.hour, 6 ], [ d3_time.hour, 12 ], [ d3_time.day, 1 ], [ d3_time.day, 2 ], [ d3_time.week, 1 ], [ d3_time.month, 1 ], [ d3_time.month, 3 ], [ d3_time.year, 1 ] ];
  var d3_time_scaleLocalFormat = d3_time_format.multi([ [ ".%L", function(d) {
    return d.getMilliseconds();
  } ], [ ":%S", function(d) {
    return d.getSeconds();
  } ], [ "%I:%M", function(d) {
    return d.getMinutes();
  } ], [ "%I %p", function(d) {
    return d.getHours();
  } ], [ "%a %d", function(d) {
    return d.getDay() && d.getDate() != 1;
  } ], [ "%b %d", function(d) {
    return d.getDate() != 1;
  } ], [ "%B", function(d) {
    return d.getMonth();
  } ], [ "%Y", d3_true ] ]);
  var d3_time_scaleMilliseconds = {
    range: function(start, stop, step) {
      return d3.range(Math.ceil(start / step) * step, +stop, step).map(d3_time_scaleDate);
    },
    floor: d3_identity,
    ceil: d3_identity
  };
  d3_time_scaleLocalMethods.year = d3_time.year;
  d3_time.scale = function() {
    return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat);
  };
  var d3_time_scaleUtcMethods = d3_time_scaleLocalMethods.map(function(m) {
    return [ m[0].utc, m[1] ];
  });
  var d3_time_scaleUtcFormat = d3_time_formatUtc.multi([ [ ".%L", function(d) {
    return d.getUTCMilliseconds();
  } ], [ ":%S", function(d) {
    return d.getUTCSeconds();
  } ], [ "%I:%M", function(d) {
    return d.getUTCMinutes();
  } ], [ "%I %p", function(d) {
    return d.getUTCHours();
  } ], [ "%a %d", function(d) {
    return d.getUTCDay() && d.getUTCDate() != 1;
  } ], [ "%b %d", function(d) {
    return d.getUTCDate() != 1;
  } ], [ "%B", function(d) {
    return d.getUTCMonth();
  } ], [ "%Y", d3_true ] ]);
  d3_time_scaleUtcMethods.year = d3_time.year.utc;
  d3_time.scale.utc = function() {
    return d3_time_scale(d3.scale.linear(), d3_time_scaleUtcMethods, d3_time_scaleUtcFormat);
  };
  d3.text = d3_xhrType(function(request) {
    return request.responseText;
  });
  d3.json = function(url, callback) {
    return d3_xhr(url, "application/json", d3_json, callback);
  };
  function d3_json(request) {
    return JSON.parse(request.responseText);
  }
  d3.html = function(url, callback) {
    return d3_xhr(url, "text/html", d3_html, callback);
  };
  function d3_html(request) {
    var range = d3_document.createRange();
    range.selectNode(d3_document.body);
    return range.createContextualFragment(request.responseText);
  }
  d3.xml = d3_xhrType(function(request) {
    return request.responseXML;
  });
  if (typeof define === "function" && define.amd) this.d3 = d3, define(d3); else if (typeof module === "object" && module.exports) module.exports = d3; else this.d3 = d3;
}();PK
!<����.chrome/toolkit/content/global/timepicker.xhtml<?xml version="1.0" encoding="UTF-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this file,
   - You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html [ <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
%htmlDTD; ]>
<html
  xmlns="http://www.w3.org/1999/xhtml"
  xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
>
  <head>
    <title>Time Picker</title>
    <link
      rel="stylesheet"
      href="chrome://global/skin/datetimeinputpickers.css"
    />
    <script src="chrome://global/content/bindings/timekeeper.js"></script>
    <script src="chrome://global/content/bindings/spinner.js"></script>
    <script src="chrome://global/content/bindings/timepicker.js"></script>
  </head>
  <body>
    <div id="time-picker"></div>
    <template id="spinner-template">
      <div class="spinner-container">
        <button class="up" />
        <div class="spinner"></div>
        <button class="down" />
      </div>
    </template>
    <script>
      /* import-globals-from widgets/timepicker.js */
      // Create a TimePicker instance and prepare to be
      // initialized by the "TimePickerInit" event from timepicker.xml
      new TimePicker(document.getElementById("time-picker"));
    </script>
  </body>
</html>
PK
!<"�Y3		.chrome/toolkit/content/global/toggle-group.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * A radiogroup styled to hide the radio buttons
 * and present tab-like labels as buttons
 */

.toggle-group {
  display: inline-flex;
  min-height: var(--button-min-height-small);
}

.toggle-group-label {
  display: inline-flex;
  align-items: center;
  margin: 0;
  padding: 6px 10px;
  background-color: var(--in-content-button-background);
}

.toggle-group-input {
  position: absolute;
  inset-inline-start: -100px;
}

.toggle-group-label-iconic::before {
  width: var(--size-item-small);
  height: var(--size-item-small);
  margin-inline-end: 5px;
  -moz-context-properties: fill;
  fill: currentColor;
}

.toggle-group-label:first-of-type {
  border-start-start-radius: var(--border-radius-small);
  border-end-start-radius: var(--border-radius-small);
}

.toggle-group-label:last-of-type {
  border-start-end-radius: var(--border-radius-small);
  border-end-end-radius: var(--border-radius-small);
}

.toggle-group-input:disabled + .toggle-group-label {
  opacity: 0.4;
}

.toggle-group-input:enabled + .toggle-group-label:hover {
  background-color: var(--in-content-button-background-hover);
  color: var(--in-content-button-text-color-hover);
}

.toggle-group-input:enabled + .toggle-group-label:hover:active {
  background-color: var(--in-content-button-background-active);
  color: var(--in-content-button-text-color-active);
}

.toggle-group-input:focus-visible + .toggle-group-label {
  outline: var(--in-content-focus-outline);
  outline-offset: var(--in-content-focus-outline-offset);
  z-index: 1;
}

.toggle-group-input:checked + .toggle-group-label {
  background-color: var(--in-content-primary-button-background);
  color: var(--in-content-primary-button-text-color);
}

.toggle-group-input:enabled:checked + .toggle-group-label:hover {
  background-color: var(--in-content-primary-button-background-hover);
  color: var(--in-content-primary-button-text-color-hover);
}

.toggle-group-input:enabled:checked + .toggle-group-label:hover:active {
  background-color: var(--in-content-primary-button-background-active);
  color: var(--in-content-primary-button-text-color-active);
}
PK
!<���:�#�#9chrome/toolkit/content/global/translations/Translator.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * This class manages the communications to the translations engine via MessagePort.
 */
export class Translator {
  /**
   * The port through with to communicate with the Translations Engine.
   *
   * @type {MessagePort}
   */
  #port;

  /**
   * True if the current #port is closed, otherwise false.
   *
   * @type {boolean}
   */
  #portClosed = true;

  /**
   * A promise that resolves when the Translator has successfully established communication with
   * the translations engine, or rejects if communication was not successfully established.
   *
   * @type {Promise<void>}
   */
  #ready = Promise.reject;

  /**
   * The BCP-47 language tag for the from-language.
   *
   * @type {string}
   */
  #fromLanguage;

  /**
   * The BCP-47 language tag for the to-language.
   *
   * @type {string}
   */
  #toLanguage;

  /**
   * The callback function to request a new port, provided at construction time
   * by the caller.
   *
   * @type {Function}
   */
  #requestTranslationsPort;

  /**
   * An id for each message sent. This is used to match up the request and response.
   *
   * @type {number}
   */
  #nextMessageId = 0;

  /**
   * Tie together a message id to a resolved response.
   *
   * @type {Map<number, TranslationRequest>}
   */
  #requests = new Map();

  /**
   * Initializes a new Translator.
   *
   * Prefer using the Translator.create() function.
   *
   * @see Translator.create
   *
   * @param {string} fromLanguage - The BCP-47 from-language tag.
   * @param {string} toLanguage - The BCP-47 to-language tag.
   * @param {Function} requestTranslationsPort - A callback function to request a new MessagePort.
   */
  constructor(fromLanguage, toLanguage, requestTranslationsPort) {
    this.#fromLanguage = fromLanguage;
    this.#toLanguage = toLanguage;
    this.#requestTranslationsPort = requestTranslationsPort;
  }

  /**
   * @returns {Promise<void>} A promise that indicates if the Translator is ready to translate.
   */
  get ready() {
    return this.#ready;
  }

  /**
   * @returns {boolean} True if the translation port is closed, false otherwise.
   */
  get portClosed() {
    return this.#portClosed;
  }

  /**
   * @returns {string} The BCP-47 language tag of the from-language.
   */
  get fromLanguage() {
    return this.#fromLanguage;
  }

  /**
   * @returns {string} The BCP-47 language tag of the to-language.
   */
  get toLanguage() {
    return this.#toLanguage;
  }

  /**
   * Opens up a port and creates a new translator.
   *
   * @param {string} fromLanguage - The BCP-47 language tag of the from-language.
   * @param {string} toLanguage - The BCP-47 language tag of the to-language.
   * @param {object} data - Data for creating a translator.
   * @param {Function} [data.requestTranslationsPort]
   *  - A function to request a translations port for communication with the Translations engine.
   *    This is required in all cases except if allowSameLanguage is true and the fromLanguage
   *    is the same as the toLanguage.
   * @param {boolean} [data.allowSameLanguage]
   *  - Whether to allow or disallow the creation of a PassthroughTranslator in the event
   *    that the fromLanguage and the toLanguage are the same language.
   *
   * @returns {Promise<Translator | PassthroughTranslator>}
   */
  static async create(
    fromLanguage,
    toLanguage,
    { requestTranslationsPort, allowSameLanguage }
  ) {
    if (!fromLanguage || !toLanguage) {
      throw new Error(
        "Attempt to create Translator with missing language tags."
      );
    }

    if (fromLanguage === toLanguage) {
      if (!allowSameLanguage) {
        throw new Error("Attempt to create disallowed PassthroughTranslator");
      }
      return new PassthroughTranslator(fromLanguage, toLanguage);
    }

    if (!requestTranslationsPort) {
      throw new Error(
        "Attempt to create Translator without a requestTranslationsPort function"
      );
    }

    const translator = new Translator(
      fromLanguage,
      toLanguage,
      requestTranslationsPort
    );
    await translator.#createNewPortIfClosed();

    return translator;
  }

  /**
   * Creates a new translation port if the current one is closed.
   *
   * @returns {Promise<void>} - Whether the Translator is ready to translate.
   */
  async #createNewPortIfClosed() {
    if (!this.#portClosed) {
      return;
    }

    this.#port = await this.#requestTranslationsPort(
      this.#fromLanguage,
      this.#toLanguage
    );
    this.#portClosed = false;

    // Create a promise that will be resolved when the engine is ready.
    const { promise, resolve, reject } = Promise.withResolvers();

    // Match up a response on the port to message that was sent.
    this.#port.onmessage = ({ data }) => {
      switch (data.type) {
        case "TranslationsPort:TranslationResponse": {
          const { targetText, messageId } = data;
          // A request may not match match a messageId if there is a race during the pausing
          // and discarding of the queue.
          this.#requests.get(messageId)?.resolve(targetText);
          break;
        }
        case "TranslationsPort:GetEngineStatusResponse": {
          if (data.status === "ready") {
            resolve();
          } else {
            this.#portClosed = true;
            reject();
          }
          break;
        }
        case "TranslationsPort:EngineTerminated": {
          this.#portClosed = true;
          break;
        }
        default:
          break;
      }
    };

    this.#ready = promise;
    this.#port.postMessage({ type: "TranslationsPort:GetEngineStatusRequest" });
  }

  /**
   * Send a request to translate text to the Translations Engine. If it returns `null`
   * then the request is stale. A rejection means there was an error in the translation.
   * This request may be queued.
   *
   * @param {string} sourceText
   * @returns {Promise<string>}
   */
  async translate(sourceText, isHTML = false) {
    await this.#createNewPortIfClosed();
    await this.#ready;

    const { promise, resolve, reject } = Promise.withResolvers();
    const messageId = this.#nextMessageId++;

    // Store the "resolve" for the promise. It will be matched back up with the
    // `messageId` in #handlePortMessage.
    this.#requests.set(messageId, {
      sourceText,
      isHTML,
      resolve,
      reject,
    });
    this.#port.postMessage({
      type: "TranslationsPort:TranslationRequest",
      messageId,
      sourceText,
      isHTML,
    });

    return promise;
  }

  /**
   * Close the port and remove any pending or queued requests.
   */
  destroy() {
    this.#port.close();
    this.#portClosed = true;
    this.#ready = Promise.reject;
  }
}

/**
 * The PassthroughTranslator class mimics the same API as the Translator class,
 * but it does not create any message ports for actual translation. This class
 * may only be constructed with the same fromLanguage and toLanguage value, and
 * instead of translating, it just passes through the source text as the translated
 * text.
 *
 * The Translator class may return a PassthroughTranslator instance if the fromLanguage
 * and toLanguage passed to the create() method are the same.
 *
 * @see Translator.create
 */
class PassthroughTranslator {
  /**
   * The BCP-47 language tag for the from-language and the to-language.
   *
   * @type {string}
   */
  #language;

  /**
   * @returns {Promise<void>} A promise that indicates if the Translator is ready to translate.
   */
  get ready() {
    return Promise.resolve;
  }

  /**
   * @returns {boolean} Always false for PassthroughTranslator because there is no port.
   */
  get portClosed() {
    return false;
  }

  /**
   * @returns {string} The BCP-47 language tag of the from-language.
   */
  get fromLanguage() {
    return this.#language;
  }

  /**
   * @returns {string} The BCP-47 language tag of the to-language.
   */
  get toLanguage() {
    return this.#language;
  }

  /**
   * Initializes a new PassthroughTranslator.
   *
   * Prefer using the Translator.create() function.
   *
   * @see Translator.create
   *
   * @param {string} fromLanguage - The BCP-47 from-language tag.
   * @param {string} toLanguage - The BCP-47 to-language tag.
   */
  constructor(fromLanguage, toLanguage) {
    if (fromLanguage !== toLanguage) {
      throw new Error(
        "Attempt to create PassthroughTranslator with different fromLanguage and toLanguage."
      );
    }
    this.#language = fromLanguage;
  }

  /**
   * Passes through the source text as if it was translated.
   *
   * @returns {Promise<string>}
   */
  async translate(sourceText) {
    return Promise.resolve(sourceText);
  }

  /**
   * There is nothing to destroy in the PassthroughTranslator class.
   * This function is implemented to maintain the same API surface as
   * the Translator class.
   *
   * @see Translator
   */
  destroy() {}
}
PK
!<E�Q�MxMxAchrome/toolkit/content/global/translations/bergamot-translator.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

function loadBergamot(Module) {
  var BERGAMOT_VERSION_FULL = "v0.4.5+05a8778";
  null;

  var Module = typeof Module != "undefined" ? Module : {};

  var moduleOverrides = Object.assign({}, Module);

  var arguments_ = [];

  var thisProgram = "./this.program";

  var quit_ = (status, toThrow) => {
    throw toThrow;
  };

  var ENVIRONMENT_IS_WEB = typeof window == "object";

  var ENVIRONMENT_IS_WORKER = typeof importScripts == "function";

  var ENVIRONMENT_IS_NODE =
    typeof process == "object" &&
    typeof process.versions == "object" &&
    typeof process.versions.node == "string";

  var scriptDirectory = "";

  function locateFile(path) {
    if (Module.locateFile) {
      return Module.locateFile(path, scriptDirectory);
    }
    return scriptDirectory + path;
  }

  var read_, readAsync, readBinary, setWindowTitle;

  if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
    if (ENVIRONMENT_IS_WORKER) {
      scriptDirectory = self.location.href;
    } else if (typeof document != "undefined" && document.currentScript) {
      scriptDirectory = document.currentScript.src;
    }
    if (scriptDirectory.indexOf("blob:") !== 0) {
      scriptDirectory = scriptDirectory.substr(
        0,
        scriptDirectory.replace(/[?#].*/, "").lastIndexOf("/") + 1
      );
    } else {
      scriptDirectory = "";
    }
    {
      read_ = url => {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", url, false);
        xhr.send(null);
        return xhr.responseText;
      };
      if (ENVIRONMENT_IS_WORKER) {
        readBinary = url => {
          var xhr = new XMLHttpRequest();
          xhr.open("GET", url, false);
          xhr.responseType = "arraybuffer";
          xhr.send(null);
          return new Uint8Array(xhr.response);
        };
      }
      readAsync = (url, onload, onerror) => {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", url, true);
        xhr.responseType = "arraybuffer";
        xhr.onload = () => {
          if (xhr.status == 200 || (xhr.status == 0 && xhr.response)) {
            onload(xhr.response);
            return;
          }
          onerror();
        };
        xhr.onerror = onerror;
        xhr.send(null);
      };
    }
    setWindowTitle = title => (document.title = title);
  } else {
  }

  var out = Module.print || console.log.bind(console);

  var err = Module.printErr || console.warn.bind(console);

  Object.assign(Module, moduleOverrides);

  moduleOverrides = null;

  if (Module.arguments) {
    arguments_ = Module.arguments;
  }

  if (Module.thisProgram) {
    thisProgram = Module.thisProgram;
  }

  if (Module.quit) {
    quit_ = Module.quit;
  }

  var tempRet0 = 0;

  var setTempRet0 = value => {
    tempRet0 = value;
  };

  var wasmBinary;

  if (Module.wasmBinary) {
    wasmBinary = Module.wasmBinary;
  }

  var noExitRuntime = Module.noExitRuntime || true;

  if (typeof WebAssembly != "object") {
    abort("no native wasm support detected");
  }

  var wasmMemory;

  var ABORT = false;

  var EXITSTATUS;

  function assert(condition, text) {
    if (!condition) {
      abort(text);
    }
  }

  var UTF8Decoder =
    typeof TextDecoder != "undefined" ? new TextDecoder("utf8") : undefined;

  function UTF8ArrayToString(heapOrArray, idx, maxBytesToRead) {
    var endIdx = idx + maxBytesToRead;
    var endPtr = idx;
    while (heapOrArray[endPtr] && !(endPtr >= endIdx)) {
      ++endPtr;
    }
    if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) {
      return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr));
    }
    var str = "";
    while (idx < endPtr) {
      var u0 = heapOrArray[idx++];
      if (!(u0 & 128)) {
        str += String.fromCharCode(u0);
        continue;
      }
      var u1 = heapOrArray[idx++] & 63;
      if ((u0 & 224) == 192) {
        str += String.fromCharCode(((u0 & 31) << 6) | u1);
        continue;
      }
      var u2 = heapOrArray[idx++] & 63;
      if ((u0 & 240) == 224) {
        u0 = ((u0 & 15) << 12) | (u1 << 6) | u2;
      } else {
        u0 =
          ((u0 & 7) << 18) | (u1 << 12) | (u2 << 6) | (heapOrArray[idx++] & 63);
      }
      if (u0 < 65536) {
        str += String.fromCharCode(u0);
      } else {
        var ch = u0 - 65536;
        str += String.fromCharCode(55296 | (ch >> 10), 56320 | (ch & 1023));
      }
    }

    return str;
  }

  function UTF8ToString(ptr, maxBytesToRead) {
    return ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : "";
  }

  function stringToUTF8Array(str, heap, outIdx, maxBytesToWrite) {
    if (!(maxBytesToWrite > 0)) {
      return 0;
    }
    var startIdx = outIdx;
    var endIdx = outIdx + maxBytesToWrite - 1;
    for (var i = 0; i < str.length; ++i) {
      var u = str.charCodeAt(i);
      if (u >= 55296 && u <= 57343) {
        var u1 = str.charCodeAt(++i);
        u = (65536 + ((u & 1023) << 10)) | (u1 & 1023);
      }
      if (u <= 127) {
        if (outIdx >= endIdx) {
          break;
        }
        heap[outIdx++] = u;
      } else if (u <= 2047) {
        if (outIdx + 1 >= endIdx) {
          break;
        }
        heap[outIdx++] = 192 | (u >> 6);
        heap[outIdx++] = 128 | (u & 63);
      } else if (u <= 65535) {
        if (outIdx + 2 >= endIdx) {
          break;
        }
        heap[outIdx++] = 224 | (u >> 12);
        heap[outIdx++] = 128 | ((u >> 6) & 63);
        heap[outIdx++] = 128 | (u & 63);
      } else {
        if (outIdx + 3 >= endIdx) {
          break;
        }
        heap[outIdx++] = 240 | (u >> 18);
        heap[outIdx++] = 128 | ((u >> 12) & 63);
        heap[outIdx++] = 128 | ((u >> 6) & 63);
        heap[outIdx++] = 128 | (u & 63);
      }
    }
    heap[outIdx] = 0;
    return outIdx - startIdx;
  }

  function stringToUTF8(str, outPtr, maxBytesToWrite) {
    return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite);
  }

  function lengthBytesUTF8(str) {
    var len = 0;
    for (var i = 0; i < str.length; ++i) {
      var u = str.charCodeAt(i);
      if (u >= 55296 && u <= 57343) {
        u = (65536 + ((u & 1023) << 10)) | (str.charCodeAt(++i) & 1023);
      }
      if (u <= 127) {
        ++len;
      } else if (u <= 2047) {
        len += 2;
      } else if (u <= 65535) {
        len += 3;
      } else {
        len += 4;
      }
    }
    return len;
  }

  var UTF16Decoder =
    typeof TextDecoder != "undefined" ? new TextDecoder("utf-16le") : undefined;

  function UTF16ToString(ptr, maxBytesToRead) {
    var endPtr = ptr;
    var idx = endPtr >> 1;
    var maxIdx = idx + maxBytesToRead / 2;
    while (!(idx >= maxIdx) && HEAPU16[idx]) {
      ++idx;
    }
    endPtr = idx << 1;
    if (endPtr - ptr > 32 && UTF16Decoder) {
      return UTF16Decoder.decode(HEAPU8.subarray(ptr, endPtr));
    }
    var str = "";
    for (var i = 0; !(i >= maxBytesToRead / 2); ++i) {
      var codeUnit = HEAP16[(ptr + i * 2) >> 1];
      if (codeUnit == 0) {
        break;
      }
      str += String.fromCharCode(codeUnit);
    }
    return str;
  }

  function stringToUTF16(str, outPtr, maxBytesToWrite) {
    if (maxBytesToWrite === undefined) {
      maxBytesToWrite = 2147483647;
    }
    if (maxBytesToWrite < 2) {
      return 0;
    }
    maxBytesToWrite -= 2;
    var startPtr = outPtr;
    var numCharsToWrite =
      maxBytesToWrite < str.length * 2 ? maxBytesToWrite / 2 : str.length;
    for (var i = 0; i < numCharsToWrite; ++i) {
      var codeUnit = str.charCodeAt(i);
      HEAP16[outPtr >> 1] = codeUnit;
      outPtr += 2;
    }
    HEAP16[outPtr >> 1] = 0;
    return outPtr - startPtr;
  }

  function lengthBytesUTF16(str) {
    return str.length * 2;
  }

  function UTF32ToString(ptr, maxBytesToRead) {
    var i = 0;
    var str = "";
    while (!(i >= maxBytesToRead / 4)) {
      var utf32 = HEAP32[(ptr + i * 4) >> 2];
      if (utf32 == 0) {
        break;
      }
      ++i;
      if (utf32 >= 65536) {
        var ch = utf32 - 65536;
        str += String.fromCharCode(55296 | (ch >> 10), 56320 | (ch & 1023));
      } else {
        str += String.fromCharCode(utf32);
      }
    }
    return str;
  }

  function stringToUTF32(str, outPtr, maxBytesToWrite) {
    if (maxBytesToWrite === undefined) {
      maxBytesToWrite = 2147483647;
    }
    if (maxBytesToWrite < 4) {
      return 0;
    }
    var startPtr = outPtr;
    var endPtr = startPtr + maxBytesToWrite - 4;
    for (var i = 0; i < str.length; ++i) {
      var codeUnit = str.charCodeAt(i);
      if (codeUnit >= 55296 && codeUnit <= 57343) {
        var trailSurrogate = str.charCodeAt(++i);
        codeUnit =
          (65536 + ((codeUnit & 1023) << 10)) | (trailSurrogate & 1023);
      }
      HEAP32[outPtr >> 2] = codeUnit;
      outPtr += 4;
      if (outPtr + 4 > endPtr) {
        break;
      }
    }
    HEAP32[outPtr >> 2] = 0;
    return outPtr - startPtr;
  }

  function lengthBytesUTF32(str) {
    var len = 0;
    for (var i = 0; i < str.length; ++i) {
      var codeUnit = str.charCodeAt(i);
      if (codeUnit >= 55296 && codeUnit <= 57343) {
        ++i;
      }
      len += 4;
    }
    return len;
  }

  function allocateUTF8(str) {
    var size = lengthBytesUTF8(str) + 1;
    var ret = _malloc(size);
    if (ret) {
      stringToUTF8Array(str, HEAP8, ret, size);
    }
    return ret;
  }

  function writeArrayToMemory(array, buffer) {
    HEAP8.set(array, buffer);
  }

  function writeAsciiToMemory(str, buffer, dontAddNull) {
    for (var i = 0; i < str.length; ++i) {
      HEAP8[buffer++ >> 0] = str.charCodeAt(i);
    }
    if (!dontAddNull) {
      HEAP8[buffer >> 0] = 0;
    }
  }

  var buffer, HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32, HEAPF32, HEAPF64;

  function updateGlobalBufferAndViews(buf) {
    const mb = (buf.byteLength / 1_000_000).toFixed();
    Module.print(`Growing wasm buffer to ${mb}MB (${buf.byteLength} bytes).`);

    buffer = buf;
    Module.HEAP8 = HEAP8 = new Int8Array(buf);
    Module.HEAP16 = HEAP16 = new Int16Array(buf);
    Module.HEAP32 = HEAP32 = new Int32Array(buf);
    Module.HEAPU8 = HEAPU8 = new Uint8Array(buf);
    Module.HEAPU16 = HEAPU16 = new Uint16Array(buf);
    Module.HEAPU32 = HEAPU32 = new Uint32Array(buf);
    Module.HEAPF32 = HEAPF32 = new Float32Array(buf);
    Module.HEAPF64 = HEAPF64 = new Float64Array(buf);
  }

  var INITIAL_MEMORY = Module.INITIAL_MEMORY || 16777216;

  if (Module.wasmMemory) {
    wasmMemory = Module.wasmMemory;
  } else {
    wasmMemory = new WebAssembly.Memory({
      initial: INITIAL_MEMORY / 65536,
      maximum: 2147483648 / 65536,
    });
  }

  if (wasmMemory) {
    buffer = wasmMemory.buffer;
  }

  INITIAL_MEMORY = buffer.byteLength;

  updateGlobalBufferAndViews(buffer);

  var wasmTable;

  var __ATPRERUN__ = [];

  var __ATINIT__ = [];

  var __ATPOSTRUN__ = [];

  var runtimeInitialized = false;

  function keepRuntimeAlive() {
    return noExitRuntime;
  }

  function preRun() {
    if (Module.preRun) {
      if (typeof Module.preRun == "function") {
        Module.preRun = [Module.preRun];
      }
      while (Module.preRun.length) {
        addOnPreRun(Module.preRun.shift());
      }
    }
    callRuntimeCallbacks(__ATPRERUN__);
  }

  function initRuntime() {
    runtimeInitialized = true;
    callRuntimeCallbacks(__ATINIT__);
  }

  function postRun() {
    if (Module.postRun) {
      if (typeof Module.postRun == "function") {
        Module.postRun = [Module.postRun];
      }
      while (Module.postRun.length) {
        addOnPostRun(Module.postRun.shift());
      }
    }
    callRuntimeCallbacks(__ATPOSTRUN__);
  }

  function addOnPreRun(cb) {
    __ATPRERUN__.unshift(cb);
  }

  function addOnInit(cb) {
    __ATINIT__.unshift(cb);
  }

  function addOnPostRun(cb) {
    __ATPOSTRUN__.unshift(cb);
  }

  var runDependencies = 0;

  var runDependencyWatcher = null;

  var dependenciesFulfilled = null;

  function addRunDependency(id) {
    runDependencies++;
    if (Module.monitorRunDependencies) {
      Module.monitorRunDependencies(runDependencies);
    }
  }

  function removeRunDependency(id) {
    runDependencies--;
    if (Module.monitorRunDependencies) {
      Module.monitorRunDependencies(runDependencies);
    }
    if (runDependencies == 0) {
      if (runDependencyWatcher !== null) {
        clearInterval(runDependencyWatcher);
        runDependencyWatcher = null;
      }
      if (dependenciesFulfilled) {
        var callback = dependenciesFulfilled;
        dependenciesFulfilled = null;
        callback();
      }
    }
  }

  Module.preloadedImages = {};

  Module.preloadedAudios = {};

  function abort(what) {
    {
      if (Module.onAbort) {
        Module.onAbort(what);
      }
    }
    what = "Aborted(" + what + ")";
    err(what);
    ABORT = true;
    EXITSTATUS = 1;
    what += ". Build with -s ASSERTIONS=1 for more info.";
    var e = new WebAssembly.RuntimeError(what);
    throw e;
  }

  var dataURIPrefix = "data:application/octet-stream;base64,";

  function isDataURI(filename) {
    return filename.startsWith(dataURIPrefix);
  }

  var wasmBinaryFile;

  wasmBinaryFile = "bergamot-translator-worker.wasm";

  if (!isDataURI(wasmBinaryFile)) {
    wasmBinaryFile = locateFile(wasmBinaryFile);
  }

  function getBinary(file) {
    try {
      if (file == wasmBinaryFile && wasmBinary) {
        return new Uint8Array(wasmBinary);
      }
      if (readBinary) {
        return readBinary(file);
      }
      throw "both async and sync fetching of the wasm failed";
    } catch (err) {
      abort(err);
    }
  }

  function getBinaryPromise() {
    if (!wasmBinary && (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER)) {
      if (typeof fetch == "function") {
        return fetch(wasmBinaryFile, {
          credentials: "same-origin",
        })
          .then(function (response) {
            if (!response.ok) {
              throw (
                "failed to load wasm binary file at '" + wasmBinaryFile + "'"
              );
            }
            return response.arrayBuffer();
          })
          .catch(function () {
            return getBinary(wasmBinaryFile);
          });
      }
    }
    return Promise.resolve().then(function () {
      return getBinary(wasmBinaryFile);
    });
  }

  function createWasm() {
    var info = {
      env: asmLibraryArg,
      wasm_gemm: createWasmGemm(),
      wasi_snapshot_preview1: asmLibraryArg,
    };
    function receiveInstance(instance, module) {
      var exports = instance.exports;
      Module.asm = exports;
      wasmTable = Module.asm.__indirect_function_table;
      addOnInit(Module.asm.__wasm_call_ctors);
      exportAsmFunctions(exports);
      removeRunDependency("wasm-instantiate");
    }
    addRunDependency("wasm-instantiate");
    function receiveInstantiationResult(result) {
      receiveInstance(result.instance);
    }
    function instantiateArrayBuffer(receiver) {
      return getBinaryPromise()
        .then(function (binary) {
          return WebAssembly.instantiate(binary, info);
        })
        .then(function (instance) {
          return instance;
        })
        .then(receiver, function (reason) {
          err("failed to asynchronously prepare wasm: " + reason);
          abort(reason);
        });
    }
    function instantiateAsync() {
      if (
        !wasmBinary &&
        typeof WebAssembly.instantiateStreaming == "function" &&
        !isDataURI(wasmBinaryFile) &&
        typeof fetch == "function"
      ) {
        return fetch(wasmBinaryFile, {
          credentials: "same-origin",
        }).then(function (response) {
          var result = WebAssembly.instantiateStreaming(response, info);
          return result.then(receiveInstantiationResult, function (reason) {
            err("wasm streaming compile failed: " + reason);
            err("falling back to ArrayBuffer instantiation");
            return instantiateArrayBuffer(receiveInstantiationResult);
          });
        });
      }
      return instantiateArrayBuffer(receiveInstantiationResult);
    }
    if (Module.instantiateWasm) {
      try {
        var exports = Module.instantiateWasm(info, receiveInstance);
        return exports;
      } catch (e) {
        err("Module.instantiateWasm callback failed with error: " + e);
        return false;
      }
    }
    instantiateAsync();
    return {};
  }

  function callRuntimeCallbacks(callbacks) {
    while (callbacks.length) {
      var callback = callbacks.shift();
      if (typeof callback == "function") {
        callback(Module);
        continue;
      }
      var func = callback.func;
      if (typeof func == "number") {
        if (callback.arg === undefined) {
          getWasmTableEntry(func)();
        } else {
          getWasmTableEntry(func)(callback.arg);
        }
      } else {
        func(callback.arg === undefined ? null : callback.arg);
      }
    }
  }

  function asmjsMangle(x) {
    var unmangledSymbols = ["stackAlloc", "stackSave", "stackRestore"];
    return x.indexOf("dynCall_") == 0 || unmangledSymbols.includes(x)
      ? x
      : "_" + x;
  }

  function exportAsmFunctions(asm) {
    var global_object = this;
    for (var __exportedFunc in asm) {
      var jsname = asmjsMangle(__exportedFunc);
      global_object[jsname] = Module[jsname] = asm[__exportedFunc];
    }
  }

  var wasmTableMirror = [];

  function getWasmTableEntry(funcPtr) {
    var func = wasmTableMirror[funcPtr];
    if (!func) {
      if (funcPtr >= wasmTableMirror.length) {
        wasmTableMirror.length = funcPtr + 1;
      }
      wasmTableMirror[funcPtr] = func = wasmTable.get(funcPtr);
    }
    return func;
  }

  function ___assert_fail(condition, filename, line, func) {
    abort(
      "Assertion failed: " +
        UTF8ToString(condition) +
        ", at: " +
        [
          filename ? UTF8ToString(filename) : "unknown filename",
          line,
          func ? UTF8ToString(func) : "unknown function",
        ]
    );
  }

  function ___cxa_allocate_exception(size) {
    return _malloc(size + 16) + 16;
  }

  var exceptionCaught = [];

  var exceptionLast = 0;

  var uncaughtExceptionCount = 0;

  function ___cxa_rethrow() {
    var catchInfo = exceptionCaught.pop();
    if (!catchInfo) {
      abort("no exception to throw");
    }
    var info = catchInfo.get_exception_info();
    var ptr = catchInfo.get_base_ptr();
    if (!info.get_rethrown()) {
      exceptionCaught.push(catchInfo);
      info.set_rethrown(true);
      info.set_caught(false);
      uncaughtExceptionCount++;
    } else {
      catchInfo.free();
    }
    exceptionLast = ptr;
    throw ptr;
  }

  function ExceptionInfo(excPtr) {
    this.excPtr = excPtr;
    this.ptr = excPtr - 16;
    this.set_type = function (type) {
      HEAP32[(this.ptr + 4) >> 2] = type;
    };
    this.get_type = function () {
      return HEAP32[(this.ptr + 4) >> 2];
    };
    this.set_destructor = function (destructor) {
      HEAP32[(this.ptr + 8) >> 2] = destructor;
    };
    this.get_destructor = function () {
      return HEAP32[(this.ptr + 8) >> 2];
    };
    this.set_refcount = function (refcount) {
      HEAP32[this.ptr >> 2] = refcount;
    };
    this.set_caught = function (caught) {
      caught = caught ? 1 : 0;
      HEAP8[(this.ptr + 12) >> 0] = caught;
    };
    this.get_caught = function () {
      return HEAP8[(this.ptr + 12) >> 0] != 0;
    };
    this.set_rethrown = function (rethrown) {
      rethrown = rethrown ? 1 : 0;
      HEAP8[(this.ptr + 13) >> 0] = rethrown;
    };
    this.get_rethrown = function () {
      return HEAP8[(this.ptr + 13) >> 0] != 0;
    };
    this.init = function (type, destructor) {
      this.set_type(type);
      this.set_destructor(destructor);
      this.set_refcount(0);
      this.set_caught(false);
      this.set_rethrown(false);
    };
    this.add_ref = function () {
      var value = HEAP32[this.ptr >> 2];
      HEAP32[this.ptr >> 2] = value + 1;
    };
    this.release_ref = function () {
      var prev = HEAP32[this.ptr >> 2];
      HEAP32[this.ptr >> 2] = prev - 1;
      return prev === 1;
    };
  }

  function ___cxa_throw(ptr, type, destructor) {
    var info = new ExceptionInfo(ptr);
    info.init(type, destructor);
    exceptionLast = ptr;
    uncaughtExceptionCount++;
    throw ptr;
  }

  var SYSCALLS = {
    buffers: [null, [], []],
    printChar(stream, curr) {
      var buffer = SYSCALLS.buffers[stream];
      if (curr === 0 || curr === 10) {
        (stream === 1 ? out : err)(UTF8ArrayToString(buffer, 0));
        buffer.length = 0;
      } else {
        buffer.push(curr);
      }
    },
    varargs: undefined,
    get() {
      SYSCALLS.varargs += 4;
      var ret = HEAP32[(SYSCALLS.varargs - 4) >> 2];
      return ret;
    },
    getStr(ptr) {
      var ret = UTF8ToString(ptr);
      return ret;
    },
    get64(low, high) {
      return low;
    },
  };

  function ___syscall_faccessat(dirfd, path, amode, flags) {
    path = SYSCALLS.getStr(path);
    path = SYSCALLS.calculateAt(dirfd, path);
    return SYSCALLS.doAccess(path, amode);
  }

  function ___syscall_fcntl64(fd, cmd, varargs) {
    SYSCALLS.varargs = varargs;
    return 0;
  }

  function ___syscall_fstat64(fd, buf) {}

  function ___syscall_getcwd(buf, size) {}

  function ___syscall_ioctl(fd, op, varargs) {
    SYSCALLS.varargs = varargs;
    return 0;
  }

  function ___syscall_lstat64(path, buf) {}

  function ___syscall_newfstatat(dirfd, path, buf, flags) {}

  function ___syscall_openat(dirfd, path, flags, varargs) {
    SYSCALLS.varargs = varargs;
  }

  function ___syscall_renameat(olddirfd, oldpath, newdirfd, newpath) {}

  function ___syscall_rmdir(path) {}

  function ___syscall_stat64(path, buf) {}

  function ___syscall_unlinkat(dirfd, path, flags) {}

  var structRegistrations = {};

  function runDestructors(destructors) {
    while (destructors.length) {
      var ptr = destructors.pop();
      var del = destructors.pop();
      del(ptr);
    }
  }

  function simpleReadValueFromPointer(pointer) {
    return this.fromWireType(HEAPU32[pointer >> 2]);
  }

  var awaitingDependencies = {};

  var registeredTypes = {};

  var typeDependencies = {};

  var char_0 = 48;

  var char_9 = 57;

  function makeLegalFunctionName(name) {
    if (undefined === name) {
      return "_unknown";
    }
    name = name.replace(/[^a-zA-Z0-9_]/g, "$");
    var f = name.charCodeAt(0);
    if (f >= char_0 && f <= char_9) {
      return "_" + name;
    }
    return name;
  }

  function createNamedFunction(name, body) {
    name = makeLegalFunctionName(name);
    return function () {
      null;
      return body.apply(this, arguments);
    };
  }

  function extendError(baseErrorType, errorName) {
    var errorClass = createNamedFunction(errorName, function (message) {
      this.name = errorName;
      this.message = message;
      var stack = new Error(message).stack;
      if (stack !== undefined) {
        this.stack =
          this.toString() + "\n" + stack.replace(/^Error(:[^\n]*)?\n/, "");
      }
    });
    errorClass.prototype = Object.create(baseErrorType.prototype);
    errorClass.prototype.constructor = errorClass;
    errorClass.prototype.toString = function () {
      if (this.message === undefined) {
        return this.name;
      }
      return this.name + ": " + this.message;
    };
    return errorClass;
  }

  var InternalError = undefined;

  function throwInternalError(message) {
    throw new InternalError(message);
  }

  function whenDependentTypesAreResolved(
    myTypes,
    dependentTypes,
    getTypeConverters
  ) {
    myTypes.forEach(function (type) {
      typeDependencies[type] = dependentTypes;
    });
    function onComplete(typeConverters) {
      var myTypeConverters = getTypeConverters(typeConverters);
      if (myTypeConverters.length !== myTypes.length) {
        throwInternalError("Mismatched type converter count");
      }
      for (var i = 0; i < myTypes.length; ++i) {
        registerType(myTypes[i], myTypeConverters[i]);
      }
    }
    var typeConverters = new Array(dependentTypes.length);
    var unregisteredTypes = [];
    var registered = 0;
    dependentTypes.forEach((dt, i) => {
      if (registeredTypes.hasOwnProperty(dt)) {
        typeConverters[i] = registeredTypes[dt];
      } else {
        unregisteredTypes.push(dt);
        if (!awaitingDependencies.hasOwnProperty(dt)) {
          awaitingDependencies[dt] = [];
        }
        awaitingDependencies[dt].push(() => {
          typeConverters[i] = registeredTypes[dt];
          ++registered;
          if (registered === unregisteredTypes.length) {
            onComplete(typeConverters);
          }
        });
      }
    });
    if (0 === unregisteredTypes.length) {
      onComplete(typeConverters);
    }
  }

  function __embind_finalize_value_object(structType) {
    var reg = structRegistrations[structType];
    delete structRegistrations[structType];
    var rawConstructor = reg.rawConstructor;
    var rawDestructor = reg.rawDestructor;
    var fieldRecords = reg.fields;
    var fieldTypes = fieldRecords
      .map(field => field.getterReturnType)
      .concat(fieldRecords.map(field => field.setterArgumentType));
    whenDependentTypesAreResolved([structType], fieldTypes, fieldTypes => {
      var fields = {};
      fieldRecords.forEach((field, i) => {
        var fieldName = field.fieldName;
        var getterReturnType = fieldTypes[i];
        var getter = field.getter;
        var getterContext = field.getterContext;
        var setterArgumentType = fieldTypes[i + fieldRecords.length];
        var setter = field.setter;
        var setterContext = field.setterContext;
        fields[fieldName] = {
          read: ptr => {
            return getterReturnType.fromWireType(getter(getterContext, ptr));
          },
          write: (ptr, o) => {
            var destructors = [];
            setter(
              setterContext,
              ptr,
              setterArgumentType.toWireType(destructors, o)
            );
            runDestructors(destructors);
          },
        };
      });
      return [
        {
          name: reg.name,
          fromWireType: function (ptr) {
            var rv = {};
            for (var i in fields) {
              rv[i] = fields[i].read(ptr);
            }
            rawDestructor(ptr);
            return rv;
          },
          toWireType: function (destructors, o) {
            for (var fieldName in fields) {
              if (!(fieldName in o)) {
                throw new TypeError('Missing field:  "' + fieldName + '"');
              }
            }
            var ptr = rawConstructor();
            for (fieldName in fields) {
              fields[fieldName].write(ptr, o[fieldName]);
            }
            if (destructors !== null) {
              destructors.push(rawDestructor, ptr);
            }
            return ptr;
          },
          argPackAdvance: 8,
          readValueFromPointer: simpleReadValueFromPointer,
          destructorFunction: rawDestructor,
        },
      ];
    });
  }

  function __embind_register_bigint(
    primitiveType,
    name,
    size,
    minRange,
    maxRange
  ) {}

  function getShiftFromSize(size) {
    switch (size) {
      case 1:
        return 0;

      case 2:
        return 1;

      case 4:
        return 2;

      case 8:
        return 3;

      default:
        throw new TypeError("Unknown type size: " + size);
    }
  }

  function embind_init_charCodes() {
    var codes = new Array(256);
    for (var i = 0; i < 256; ++i) {
      codes[i] = String.fromCharCode(i);
    }
    embind_charCodes = codes;
  }

  var embind_charCodes = undefined;

  function readLatin1String(ptr) {
    var ret = "";
    var c = ptr;
    while (HEAPU8[c]) {
      ret += embind_charCodes[HEAPU8[c++]];
    }
    return ret;
  }

  var BindingError = undefined;

  function throwBindingError(message) {
    throw new BindingError(message);
  }

  function registerType(rawType, registeredInstance, options = {}) {
    if (!("argPackAdvance" in registeredInstance)) {
      throw new TypeError(
        "registerType registeredInstance requires argPackAdvance"
      );
    }
    var name = registeredInstance.name;
    if (!rawType) {
      throwBindingError(
        'type "' + name + '" must have a positive integer typeid pointer'
      );
    }
    if (registeredTypes.hasOwnProperty(rawType)) {
      if (options.ignoreDuplicateRegistrations) {
        return;
      }
      throwBindingError("Cannot register type '" + name + "' twice");
    }
    registeredTypes[rawType] = registeredInstance;
    delete typeDependencies[rawType];
    if (awaitingDependencies.hasOwnProperty(rawType)) {
      var callbacks = awaitingDependencies[rawType];
      delete awaitingDependencies[rawType];
      callbacks.forEach(cb => cb());
    }
  }

  function __embind_register_bool(rawType, name, size, trueValue, falseValue) {
    var shift = getShiftFromSize(size);
    name = readLatin1String(name);
    registerType(rawType, {
      name,
      fromWireType: function (wt) {
        return !!wt;
      },
      toWireType: function (destructors, o) {
        return o ? trueValue : falseValue;
      },
      argPackAdvance: 8,
      readValueFromPointer: function (pointer) {
        var heap;
        if (size === 1) {
          heap = HEAP8;
        } else if (size === 2) {
          heap = HEAP16;
        } else if (size === 4) {
          heap = HEAP32;
        } else {
          throw new TypeError("Unknown boolean type size: " + name);
        }
        return this.fromWireType(heap[pointer >> shift]);
      },
      destructorFunction: null,
    });
  }

  function ClassHandle_isAliasOf(other) {
    if (!(this instanceof ClassHandle)) {
      return false;
    }
    if (!(other instanceof ClassHandle)) {
      return false;
    }
    var leftClass = this.$$.ptrType.registeredClass;
    var left = this.$$.ptr;
    var rightClass = other.$$.ptrType.registeredClass;
    var right = other.$$.ptr;
    while (leftClass.baseClass) {
      left = leftClass.upcast(left);
      leftClass = leftClass.baseClass;
    }
    while (rightClass.baseClass) {
      right = rightClass.upcast(right);
      rightClass = rightClass.baseClass;
    }
    return leftClass === rightClass && left === right;
  }

  function shallowCopyInternalPointer(o) {
    return {
      count: o.count,
      deleteScheduled: o.deleteScheduled,
      preservePointerOnDelete: o.preservePointerOnDelete,
      ptr: o.ptr,
      ptrType: o.ptrType,
      smartPtr: o.smartPtr,
      smartPtrType: o.smartPtrType,
    };
  }

  function throwInstanceAlreadyDeleted(obj) {
    function getInstanceTypeName(handle) {
      return handle.$$.ptrType.registeredClass.name;
    }
    throwBindingError(getInstanceTypeName(obj) + " instance already deleted");
  }

  var finalizationRegistry = false;

  function detachFinalizer(handle) {}

  function runDestructor($$) {
    if ($$.smartPtr) {
      $$.smartPtrType.rawDestructor($$.smartPtr);
    } else {
      $$.ptrType.registeredClass.rawDestructor($$.ptr);
    }
  }

  function releaseClassHandle($$) {
    $$.count.value -= 1;
    var toDelete = 0 === $$.count.value;
    if (toDelete) {
      runDestructor($$);
    }
  }

  function downcastPointer(ptr, ptrClass, desiredClass) {
    if (ptrClass === desiredClass) {
      return ptr;
    }
    if (undefined === desiredClass.baseClass) {
      return null;
    }
    var rv = downcastPointer(ptr, ptrClass, desiredClass.baseClass);
    if (rv === null) {
      return null;
    }
    return desiredClass.downcast(rv);
  }

  var registeredPointers = {};

  function getInheritedInstanceCount() {
    return Object.keys(registeredInstances).length;
  }

  function getLiveInheritedInstances() {
    var rv = [];
    for (var k in registeredInstances) {
      if (registeredInstances.hasOwnProperty(k)) {
        rv.push(registeredInstances[k]);
      }
    }
    return rv;
  }

  var deletionQueue = [];

  function flushPendingDeletes() {
    while (deletionQueue.length) {
      var obj = deletionQueue.pop();
      obj.$$.deleteScheduled = false;
      obj.delete();
    }
  }

  var delayFunction = undefined;

  function setDelayFunction(fn) {
    delayFunction = fn;
    if (deletionQueue.length && delayFunction) {
      delayFunction(flushPendingDeletes);
    }
  }

  function init_embind() {
    Module.getInheritedInstanceCount = getInheritedInstanceCount;
    Module.getLiveInheritedInstances = getLiveInheritedInstances;
    Module.flushPendingDeletes = flushPendingDeletes;
    Module.setDelayFunction = setDelayFunction;
  }

  var registeredInstances = {};

  function getBasestPointer(class_, ptr) {
    if (ptr === undefined) {
      throwBindingError("ptr should not be undefined");
    }
    while (class_.baseClass) {
      ptr = class_.upcast(ptr);
      class_ = class_.baseClass;
    }
    return ptr;
  }

  function getInheritedInstance(class_, ptr) {
    ptr = getBasestPointer(class_, ptr);
    return registeredInstances[ptr];
  }

  function makeClassHandle(prototype, record) {
    if (!record.ptrType || !record.ptr) {
      throwInternalError("makeClassHandle requires ptr and ptrType");
    }
    var hasSmartPtrType = !!record.smartPtrType;
    var hasSmartPtr = !!record.smartPtr;
    if (hasSmartPtrType !== hasSmartPtr) {
      throwInternalError("Both smartPtrType and smartPtr must be specified");
    }
    record.count = {
      value: 1,
    };
    return attachFinalizer(
      Object.create(prototype, {
        $$: {
          value: record,
        },
      })
    );
  }

  function RegisteredPointer_fromWireType(ptr) {
    var rawPointer = this.getPointee(ptr);
    if (!rawPointer) {
      this.destructor(ptr);
      return null;
    }
    var registeredInstance = getInheritedInstance(
      this.registeredClass,
      rawPointer
    );
    if (undefined !== registeredInstance) {
      if (0 === registeredInstance.$$.count.value) {
        registeredInstance.$$.ptr = rawPointer;
        registeredInstance.$$.smartPtr = ptr;
        return registeredInstance.clone();
      }
      var rv = registeredInstance.clone();
      this.destructor(ptr);
      return rv;
    }
    function makeDefaultHandle() {
      if (this.isSmartPointer) {
        return makeClassHandle(this.registeredClass.instancePrototype, {
          ptrType: this.pointeeType,
          ptr: rawPointer,
          smartPtrType: this,
          smartPtr: ptr,
        });
      }
      return makeClassHandle(this.registeredClass.instancePrototype, {
        ptrType: this,
        ptr,
      });
    }
    var actualType = this.registeredClass.getActualType(rawPointer);
    var registeredPointerRecord = registeredPointers[actualType];
    if (!registeredPointerRecord) {
      return makeDefaultHandle.call(this);
    }
    var toType;
    if (this.isConst) {
      toType = registeredPointerRecord.constPointerType;
    } else {
      toType = registeredPointerRecord.pointerType;
    }
    var dp = downcastPointer(
      rawPointer,
      this.registeredClass,
      toType.registeredClass
    );
    if (dp === null) {
      return makeDefaultHandle.call(this);
    }
    if (this.isSmartPointer) {
      return makeClassHandle(toType.registeredClass.instancePrototype, {
        ptrType: toType,
        ptr: dp,
        smartPtrType: this,
        smartPtr: ptr,
      });
    }
    return makeClassHandle(toType.registeredClass.instancePrototype, {
      ptrType: toType,
      ptr: dp,
    });
  }

  function attachFinalizer(handle) {
    if ("undefined" === typeof FinalizationRegistry) {
      attachFinalizer = handle => handle;
      return handle;
    }
    finalizationRegistry = new FinalizationRegistry(info => {
      releaseClassHandle(info.$$);
    });
    attachFinalizer = handle => {
      var $$ = handle.$$;
      var hasSmartPtr = !!$$.smartPtr;
      if (hasSmartPtr) {
        var info = {
          $$,
        };
        finalizationRegistry.register(handle, info, handle);
      }
      return handle;
    };
    detachFinalizer = handle => finalizationRegistry.unregister(handle);
    return attachFinalizer(handle);
  }

  function ClassHandle_clone() {
    if (!this.$$.ptr) {
      throwInstanceAlreadyDeleted(this);
    }
    if (this.$$.preservePointerOnDelete) {
      this.$$.count.value += 1;
      return this;
    }
    var clone = attachFinalizer(
      Object.create(Object.getPrototypeOf(this), {
        $$: {
          value: shallowCopyInternalPointer(this.$$),
        },
      })
    );
    clone.$$.count.value += 1;
    clone.$$.deleteScheduled = false;
    return clone;
  }

  function ClassHandle_delete() {
    if (!this.$$.ptr) {
      throwInstanceAlreadyDeleted(this);
    }
    if (this.$$.deleteScheduled && !this.$$.preservePointerOnDelete) {
      throwBindingError("Object already scheduled for deletion");
    }
    detachFinalizer(this);
    releaseClassHandle(this.$$);
    if (!this.$$.preservePointerOnDelete) {
      this.$$.smartPtr = undefined;
      this.$$.ptr = undefined;
    }
  }

  function ClassHandle_isDeleted() {
    return !this.$$.ptr;
  }

  function ClassHandle_deleteLater() {
    if (!this.$$.ptr) {
      throwInstanceAlreadyDeleted(this);
    }
    if (this.$$.deleteScheduled && !this.$$.preservePointerOnDelete) {
      throwBindingError("Object already scheduled for deletion");
    }
    deletionQueue.push(this);
    if (deletionQueue.length === 1 && delayFunction) {
      delayFunction(flushPendingDeletes);
    }
    this.$$.deleteScheduled = true;
    return this;
  }

  function init_ClassHandle() {
    ClassHandle.prototype.isAliasOf = ClassHandle_isAliasOf;
    ClassHandle.prototype.clone = ClassHandle_clone;
    ClassHandle.prototype.delete = ClassHandle_delete;
    ClassHandle.prototype.isDeleted = ClassHandle_isDeleted;
    ClassHandle.prototype.deleteLater = ClassHandle_deleteLater;
  }

  function ClassHandle() {}

  function ensureOverloadTable(proto, methodName, humanName) {
    if (undefined === proto[methodName].overloadTable) {
      var prevFunc = proto[methodName];
      proto[methodName] = function () {
        if (!proto[methodName].overloadTable.hasOwnProperty(arguments.length)) {
          throwBindingError(
            "Function '" +
              humanName +
              "' called with an invalid number of arguments (" +
              arguments.length +
              ") - expects one of (" +
              proto[methodName].overloadTable +
              ")!"
          );
        }
        return proto[methodName].overloadTable[arguments.length].apply(
          this,
          arguments
        );
      };
      proto[methodName].overloadTable = [];
      proto[methodName].overloadTable[prevFunc.argCount] = prevFunc;
    }
  }

  function exposePublicSymbol(name, value, numArguments) {
    if (Module.hasOwnProperty(name)) {
      if (
        undefined === numArguments ||
        (undefined !== Module[name].overloadTable &&
          undefined !== Module[name].overloadTable[numArguments])
      ) {
        throwBindingError("Cannot register public name '" + name + "' twice");
      }
      ensureOverloadTable(Module, name, name);
      if (Module.hasOwnProperty(numArguments)) {
        throwBindingError(
          "Cannot register multiple overloads of a function with the same number of arguments (" +
            numArguments +
            ")!"
        );
      }
      Module[name].overloadTable[numArguments] = value;
    } else {
      Module[name] = value;
      if (undefined !== numArguments) {
        Module[name].numArguments = numArguments;
      }
    }
  }

  function RegisteredClass(
    name,
    constructor,
    instancePrototype,
    rawDestructor,
    baseClass,
    getActualType,
    upcast,
    downcast
  ) {
    this.name = name;
    this.constructor = constructor;
    this.instancePrototype = instancePrototype;
    this.rawDestructor = rawDestructor;
    this.baseClass = baseClass;
    this.getActualType = getActualType;
    this.upcast = upcast;
    this.downcast = downcast;
    this.pureVirtualFunctions = [];
  }

  function upcastPointer(ptr, ptrClass, desiredClass) {
    while (ptrClass !== desiredClass) {
      if (!ptrClass.upcast) {
        throwBindingError(
          "Expected null or instance of " +
            desiredClass.name +
            ", got an instance of " +
            ptrClass.name
        );
      }
      ptr = ptrClass.upcast(ptr);
      ptrClass = ptrClass.baseClass;
    }
    return ptr;
  }

  function constNoSmartPtrRawPointerToWireType(destructors, handle) {
    if (handle === null) {
      if (this.isReference) {
        throwBindingError("null is not a valid " + this.name);
      }
      return 0;
    }
    if (!handle.$$) {
      throwBindingError(
        'Cannot pass "' + _embind_repr(handle) + '" as a ' + this.name
      );
    }
    if (!handle.$$.ptr) {
      throwBindingError(
        "Cannot pass deleted object as a pointer of type " + this.name
      );
    }
    var handleClass = handle.$$.ptrType.registeredClass;
    var ptr = upcastPointer(handle.$$.ptr, handleClass, this.registeredClass);
    return ptr;
  }

  function genericPointerToWireType(destructors, handle) {
    var ptr;
    if (handle === null) {
      if (this.isReference) {
        throwBindingError("null is not a valid " + this.name);
      }
      if (this.isSmartPointer) {
        ptr = this.rawConstructor();
        if (destructors !== null) {
          destructors.push(this.rawDestructor, ptr);
        }
        return ptr;
      }
      return 0;
    }
    if (!handle.$$) {
      throwBindingError(
        'Cannot pass "' + _embind_repr(handle) + '" as a ' + this.name
      );
    }
    if (!handle.$$.ptr) {
      throwBindingError(
        "Cannot pass deleted object as a pointer of type " + this.name
      );
    }
    if (!this.isConst && handle.$$.ptrType.isConst) {
      throwBindingError(
        "Cannot convert argument of type " +
          (handle.$$.smartPtrType
            ? handle.$$.smartPtrType.name
            : handle.$$.ptrType.name) +
          " to parameter type " +
          this.name
      );
    }
    var handleClass = handle.$$.ptrType.registeredClass;
    ptr = upcastPointer(handle.$$.ptr, handleClass, this.registeredClass);
    if (this.isSmartPointer) {
      if (undefined === handle.$$.smartPtr) {
        throwBindingError("Passing raw pointer to smart pointer is illegal");
      }
      switch (this.sharingPolicy) {
        case 0:
          if (handle.$$.smartPtrType === this) {
            ptr = handle.$$.smartPtr;
          } else {
            throwBindingError(
              "Cannot convert argument of type " +
                (handle.$$.smartPtrType
                  ? handle.$$.smartPtrType.name
                  : handle.$$.ptrType.name) +
                " to parameter type " +
                this.name
            );
          }
          break;

        case 1:
          ptr = handle.$$.smartPtr;
          break;

        case 2:
          if (handle.$$.smartPtrType === this) {
            ptr = handle.$$.smartPtr;
          } else {
            var clonedHandle = handle.clone();
            ptr = this.rawShare(
              ptr,
              Emval.toHandle(function () {
                clonedHandle.delete();
              })
            );
            if (destructors !== null) {
              destructors.push(this.rawDestructor, ptr);
            }
          }
          break;

        default:
          throwBindingError("Unsupporting sharing policy");
      }
    }
    return ptr;
  }

  function nonConstNoSmartPtrRawPointerToWireType(destructors, handle) {
    if (handle === null) {
      if (this.isReference) {
        throwBindingError("null is not a valid " + this.name);
      }
      return 0;
    }
    if (!handle.$$) {
      throwBindingError(
        'Cannot pass "' + _embind_repr(handle) + '" as a ' + this.name
      );
    }
    if (!handle.$$.ptr) {
      throwBindingError(
        "Cannot pass deleted object as a pointer of type " + this.name
      );
    }
    if (handle.$$.ptrType.isConst) {
      throwBindingError(
        "Cannot convert argument of type " +
          handle.$$.ptrType.name +
          " to parameter type " +
          this.name
      );
    }
    var handleClass = handle.$$.ptrType.registeredClass;
    var ptr = upcastPointer(handle.$$.ptr, handleClass, this.registeredClass);
    return ptr;
  }

  function RegisteredPointer_getPointee(ptr) {
    if (this.rawGetPointee) {
      ptr = this.rawGetPointee(ptr);
    }
    return ptr;
  }

  function RegisteredPointer_destructor(ptr) {
    if (this.rawDestructor) {
      this.rawDestructor(ptr);
    }
  }

  function RegisteredPointer_deleteObject(handle) {
    if (handle !== null) {
      handle.delete();
    }
  }

  function init_RegisteredPointer() {
    RegisteredPointer.prototype.getPointee = RegisteredPointer_getPointee;
    RegisteredPointer.prototype.destructor = RegisteredPointer_destructor;
    RegisteredPointer.prototype.argPackAdvance = 8;
    RegisteredPointer.prototype.readValueFromPointer =
      simpleReadValueFromPointer;
    RegisteredPointer.prototype.deleteObject = RegisteredPointer_deleteObject;
    RegisteredPointer.prototype.fromWireType = RegisteredPointer_fromWireType;
  }

  function RegisteredPointer(
    name,
    registeredClass,
    isReference,
    isConst,
    isSmartPointer,
    pointeeType,
    sharingPolicy,
    rawGetPointee,
    rawConstructor,
    rawShare,
    rawDestructor
  ) {
    this.name = name;
    this.registeredClass = registeredClass;
    this.isReference = isReference;
    this.isConst = isConst;
    this.isSmartPointer = isSmartPointer;
    this.pointeeType = pointeeType;
    this.sharingPolicy = sharingPolicy;
    this.rawGetPointee = rawGetPointee;
    this.rawConstructor = rawConstructor;
    this.rawShare = rawShare;
    this.rawDestructor = rawDestructor;
    if (!isSmartPointer && registeredClass.baseClass === undefined) {
      if (isConst) {
        this.toWireType = constNoSmartPtrRawPointerToWireType;
        this.destructorFunction = null;
      } else {
        this.toWireType = nonConstNoSmartPtrRawPointerToWireType;
        this.destructorFunction = null;
      }
    } else {
      this.toWireType = genericPointerToWireType;
    }
  }

  function replacePublicSymbol(name, value, numArguments) {
    if (!Module.hasOwnProperty(name)) {
      throwInternalError("Replacing nonexistant public symbol");
    }
    if (
      undefined !== Module[name].overloadTable &&
      undefined !== numArguments
    ) {
      Module[name].overloadTable[numArguments] = value;
    } else {
      Module[name] = value;
      Module[name].argCount = numArguments;
    }
  }

  function dynCallLegacy(sig, ptr, args) {
    var f = Module["dynCall_" + sig];
    return args && args.length
      ? f.apply(null, [ptr].concat(args))
      : f.call(null, ptr);
  }

  function dynCall(sig, ptr, args) {
    if (sig.includes("j")) {
      return dynCallLegacy(sig, ptr, args);
    }
    return getWasmTableEntry(ptr).apply(null, args);
  }

  function getDynCaller(sig, ptr) {
    var argCache = [];
    return function () {
      argCache.length = 0;
      Object.assign(argCache, arguments);
      return dynCall(sig, ptr, argCache);
    };
  }

  function embind__requireFunction(signature, rawFunction) {
    signature = readLatin1String(signature);
    function makeDynCaller() {
      if (signature.includes("j")) {
        return getDynCaller(signature, rawFunction);
      }
      return getWasmTableEntry(rawFunction);
    }
    var fp = makeDynCaller();
    if (typeof fp != "function") {
      throwBindingError(
        "unknown function pointer with signature " +
          signature +
          ": " +
          rawFunction
      );
    }
    return fp;
  }

  var UnboundTypeError = undefined;

  function getTypeName(type) {
    var ptr = ___getTypeName(type);
    var rv = readLatin1String(ptr);
    _free(ptr);
    return rv;
  }

  function throwUnboundTypeError(message, types) {
    var unboundTypes = [];
    var seen = {};
    function visit(type) {
      if (seen[type]) {
        return;
      }
      if (registeredTypes[type]) {
        return;
      }
      if (typeDependencies[type]) {
        typeDependencies[type].forEach(visit);
        return;
      }
      unboundTypes.push(type);
      seen[type] = true;
    }
    types.forEach(visit);
    throw new UnboundTypeError(
      message + ": " + unboundTypes.map(getTypeName).join([", "])
    );
  }

  function __embind_register_class(
    rawType,
    rawPointerType,
    rawConstPointerType,
    baseClassRawType,
    getActualTypeSignature,
    getActualType,
    upcastSignature,
    upcast,
    downcastSignature,
    downcast,
    name,
    destructorSignature,
    rawDestructor
  ) {
    name = readLatin1String(name);
    getActualType = embind__requireFunction(
      getActualTypeSignature,
      getActualType
    );
    if (upcast) {
      upcast = embind__requireFunction(upcastSignature, upcast);
    }
    if (downcast) {
      downcast = embind__requireFunction(downcastSignature, downcast);
    }
    rawDestructor = embind__requireFunction(destructorSignature, rawDestructor);
    var legalFunctionName = makeLegalFunctionName(name);
    exposePublicSymbol(legalFunctionName, function () {
      throwUnboundTypeError(
        "Cannot construct " + name + " due to unbound types",
        [baseClassRawType]
      );
    });
    whenDependentTypesAreResolved(
      [rawType, rawPointerType, rawConstPointerType],
      baseClassRawType ? [baseClassRawType] : [],
      function (base) {
        base = base[0];
        var baseClass;
        var basePrototype;
        if (baseClassRawType) {
          baseClass = base.registeredClass;
          basePrototype = baseClass.instancePrototype;
        } else {
          basePrototype = ClassHandle.prototype;
        }
        var constructor = createNamedFunction(legalFunctionName, function () {
          if (Object.getPrototypeOf(this) !== instancePrototype) {
            throw new BindingError("Use 'new' to construct " + name);
          }
          if (undefined === registeredClass.constructor_body) {
            throw new BindingError(name + " has no accessible constructor");
          }
          var body = registeredClass.constructor_body[arguments.length];
          if (undefined === body) {
            throw new BindingError(
              "Tried to invoke ctor of " +
                name +
                " with invalid number of parameters (" +
                arguments.length +
                ") - expected (" +
                Object.keys(registeredClass.constructor_body).toString() +
                ") parameters instead!"
            );
          }
          return body.apply(this, arguments);
        });
        var instancePrototype = Object.create(basePrototype, {
          constructor: {
            value: constructor,
          },
        });
        constructor.prototype = instancePrototype;
        var registeredClass = new RegisteredClass(
          name,
          constructor,
          instancePrototype,
          rawDestructor,
          baseClass,
          getActualType,
          upcast,
          downcast
        );
        var referenceConverter = new RegisteredPointer(
          name,
          registeredClass,
          true,
          false,
          false
        );
        var pointerConverter = new RegisteredPointer(
          name + "*",
          registeredClass,
          false,
          false,
          false
        );
        var constPointerConverter = new RegisteredPointer(
          name + " const*",
          registeredClass,
          false,
          true,
          false
        );
        registeredPointers[rawType] = {
          pointerType: pointerConverter,
          constPointerType: constPointerConverter,
        };
        replacePublicSymbol(legalFunctionName, constructor);
        return [referenceConverter, pointerConverter, constPointerConverter];
      }
    );
  }

  function heap32VectorToArray(count, firstElement) {
    var array = [];
    for (var i = 0; i < count; i++) {
      array.push(HEAP32[(firstElement >> 2) + i]);
    }
    return array;
  }

  function __embind_register_class_constructor(
    rawClassType,
    argCount,
    rawArgTypesAddr,
    invokerSignature,
    invoker,
    rawConstructor
  ) {
    assert(argCount > 0);
    var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr);
    invoker = embind__requireFunction(invokerSignature, invoker);
    whenDependentTypesAreResolved([], [rawClassType], function (classType) {
      classType = classType[0];
      var humanName = "constructor " + classType.name;
      if (undefined === classType.registeredClass.constructor_body) {
        classType.registeredClass.constructor_body = [];
      }
      if (
        undefined !== classType.registeredClass.constructor_body[argCount - 1]
      ) {
        throw new BindingError(
          "Cannot register multiple constructors with identical number of parameters (" +
            (argCount - 1) +
            ") for class '" +
            classType.name +
            "'! Overload resolution is currently only performed using the parameter count, not actual type info!"
        );
      }
      classType.registeredClass.constructor_body[argCount - 1] = () => {
        throwUnboundTypeError(
          "Cannot construct " + classType.name + " due to unbound types",
          rawArgTypes
        );
      };
      whenDependentTypesAreResolved([], rawArgTypes, function (argTypes) {
        argTypes.splice(1, 0, null);
        classType.registeredClass.constructor_body[argCount - 1] =
          craftInvokerFunction(
            humanName,
            argTypes,
            null,
            invoker,
            rawConstructor
          );
        return [];
      });
      return [];
    });
  }

  function craftInvokerFunction(
    humanName,
    argTypes,
    classType,
    cppInvokerFunc,
    cppTargetFunc
  ) {
    var argCount = argTypes.length;
    if (argCount < 2) {
      throwBindingError(
        "argTypes array size mismatch! Must at least get return value and 'this' types!"
      );
    }
    var isClassMethodFunc = argTypes[1] !== null && classType !== null;
    var needsDestructorStack = false;
    for (var i = 1; i < argTypes.length; ++i) {
      if (
        argTypes[i] !== null &&
        argTypes[i].destructorFunction === undefined
      ) {
        needsDestructorStack = true;
        break;
      }
    }
    var returns = argTypes[0].name !== "void";
    var expectedArgCount = argCount - 2;
    var argsWired = new Array(expectedArgCount);
    var invokerFuncArgs = [];
    var destructors = [];
    return function () {
      if (arguments.length !== expectedArgCount) {
        throwBindingError(
          "function " +
            humanName +
            " called with " +
            arguments.length +
            " arguments, expected " +
            expectedArgCount +
            " args!"
        );
      }
      destructors.length = 0;
      var thisWired;
      invokerFuncArgs.length = isClassMethodFunc ? 2 : 1;
      invokerFuncArgs[0] = cppTargetFunc;
      if (isClassMethodFunc) {
        thisWired = argTypes[1].toWireType(destructors, this);
        invokerFuncArgs[1] = thisWired;
      }
      for (var i = 0; i < expectedArgCount; ++i) {
        argsWired[i] = argTypes[i + 2].toWireType(destructors, arguments[i]);
        invokerFuncArgs.push(argsWired[i]);
      }
      var rv = cppInvokerFunc.apply(null, invokerFuncArgs);
      function onDone(rv) {
        if (needsDestructorStack) {
          runDestructors(destructors);
        } else {
          for (var i = isClassMethodFunc ? 1 : 2; i < argTypes.length; i++) {
            var param = i === 1 ? thisWired : argsWired[i - 2];
            if (argTypes[i].destructorFunction !== null) {
              argTypes[i].destructorFunction(param);
            }
          }
        }
        if (returns) {
          return argTypes[0].fromWireType(rv);
        }
      }
      return onDone(rv);
    };
  }

  function __embind_register_class_function(
    rawClassType,
    methodName,
    argCount,
    rawArgTypesAddr,
    invokerSignature,
    rawInvoker,
    context,
    isPureVirtual
  ) {
    var rawArgTypes = heap32VectorToArray(argCount, rawArgTypesAddr);
    methodName = readLatin1String(methodName);
    rawInvoker = embind__requireFunction(invokerSignature, rawInvoker);
    whenDependentTypesAreResolved([], [rawClassType], function (classType) {
      classType = classType[0];
      var humanName = classType.name + "." + methodName;
      if (methodName.startsWith("@@")) {
        methodName = Symbol[methodName.substring(2)];
      }
      if (isPureVirtual) {
        classType.registeredClass.pureVirtualFunctions.push(methodName);
      }
      function unboundTypesHandler() {
        throwUnboundTypeError(
          "Cannot call " + humanName + " due to unbound types",
          rawArgTypes
        );
      }
      var proto = classType.registeredClass.instancePrototype;
      var method = proto[methodName];
      if (
        undefined === method ||
        (undefined === method.overloadTable &&
          method.className !== classType.name &&
          method.argCount === argCount - 2)
      ) {
        unboundTypesHandler.argCount = argCount - 2;
        unboundTypesHandler.className = classType.name;
        proto[methodName] = unboundTypesHandler;
      } else {
        ensureOverloadTable(proto, methodName, humanName);
        proto[methodName].overloadTable[argCount - 2] = unboundTypesHandler;
      }
      whenDependentTypesAreResolved([], rawArgTypes, function (argTypes) {
        var memberFunction = craftInvokerFunction(
          humanName,
          argTypes,
          classType,
          rawInvoker,
          context
        );
        if (undefined === proto[methodName].overloadTable) {
          memberFunction.argCount = argCount - 2;
          proto[methodName] = memberFunction;
        } else {
          proto[methodName].overloadTable[argCount - 2] = memberFunction;
        }
        return [];
      });
      return [];
    });
  }

  var emval_free_list = [];

  var emval_handle_array = [
    {},
    {
      value: undefined,
    },
    {
      value: null,
    },
    {
      value: true,
    },
    {
      value: false,
    },
  ];

  function __emval_decref(handle) {
    if (handle > 4 && 0 === --emval_handle_array[handle].refcount) {
      emval_handle_array[handle] = undefined;
      emval_free_list.push(handle);
    }
  }

  function count_emval_handles() {
    var count = 0;
    for (var i = 5; i < emval_handle_array.length; ++i) {
      if (emval_handle_array[i] !== undefined) {
        ++count;
      }
    }
    return count;
  }

  function get_first_emval() {
    for (var i = 5; i < emval_handle_array.length; ++i) {
      if (emval_handle_array[i] !== undefined) {
        return emval_handle_array[i];
      }
    }
    return null;
  }

  function init_emval() {
    Module.count_emval_handles = count_emval_handles;
    Module.get_first_emval = get_first_emval;
  }

  var Emval = {
    toValue: handle => {
      if (!handle) {
        throwBindingError("Cannot use deleted val. handle = " + handle);
      }
      return emval_handle_array[handle].value;
    },
    toHandle: value => {
      switch (value) {
        case undefined:
          return 1;

        case null:
          return 2;

        case true:
          return 3;

        case false:
          return 4;

        default: {
          var handle = emval_free_list.length
            ? emval_free_list.pop()
            : emval_handle_array.length;
          emval_handle_array[handle] = {
            refcount: 1,
            value,
          };
          return handle;
        }
      }
    },
  };

  function __embind_register_emval(rawType, name) {
    name = readLatin1String(name);
    registerType(rawType, {
      name,
      fromWireType: function (handle) {
        var rv = Emval.toValue(handle);
        __emval_decref(handle);
        return rv;
      },
      toWireType: function (destructors, value) {
        return Emval.toHandle(value);
      },
      argPackAdvance: 8,
      readValueFromPointer: simpleReadValueFromPointer,
      destructorFunction: null,
    });
  }

  function _embind_repr(v) {
    if (v === null) {
      return "null";
    }
    var t = typeof v;
    if (t === "object" || t === "array" || t === "function") {
      return v.toString();
    }
    return "" + v;
  }

  function floatReadValueFromPointer(name, shift) {
    switch (shift) {
      case 2:
        return function (pointer) {
          return this.fromWireType(HEAPF32[pointer >> 2]);
        };

      case 3:
        return function (pointer) {
          return this.fromWireType(HEAPF64[pointer >> 3]);
        };

      default:
        throw new TypeError("Unknown float type: " + name);
    }
  }

  function __embind_register_float(rawType, name, size) {
    var shift = getShiftFromSize(size);
    name = readLatin1String(name);
    registerType(rawType, {
      name,
      fromWireType: function (value) {
        return value;
      },
      toWireType: function (destructors, value) {
        return value;
      },
      argPackAdvance: 8,
      readValueFromPointer: floatReadValueFromPointer(name, shift),
      destructorFunction: null,
    });
  }

  function integerReadValueFromPointer(name, shift, signed) {
    switch (shift) {
      case 0:
        return signed
          ? function readS8FromPointer(pointer) {
              return HEAP8[pointer];
            }
          : function readU8FromPointer(pointer) {
              return HEAPU8[pointer];
            };

      case 1:
        return signed
          ? function readS16FromPointer(pointer) {
              return HEAP16[pointer >> 1];
            }
          : function readU16FromPointer(pointer) {
              return HEAPU16[pointer >> 1];
            };

      case 2:
        return signed
          ? function readS32FromPointer(pointer) {
              return HEAP32[pointer >> 2];
            }
          : function readU32FromPointer(pointer) {
              return HEAPU32[pointer >> 2];
            };

      default:
        throw new TypeError("Unknown integer type: " + name);
    }
  }

  function __embind_register_integer(
    primitiveType,
    name,
    size,
    minRange,
    maxRange
  ) {
    name = readLatin1String(name);
    if (maxRange === -1) {
      maxRange = 4294967295;
    }
    var shift = getShiftFromSize(size);
    var fromWireType = value => value;
    if (minRange === 0) {
      var bitshift = 32 - 8 * size;
      fromWireType = value => (value << bitshift) >>> bitshift;
    }
    var isUnsignedType = name.includes("unsigned");
    var checkAssertions = (value, toTypeName) => {};
    var toWireType;
    if (isUnsignedType) {
      toWireType = function (destructors, value) {
        checkAssertions(value, this.name);
        return value >>> 0;
      };
    } else {
      toWireType = function (destructors, value) {
        checkAssertions(value, this.name);
        return value;
      };
    }
    registerType(primitiveType, {
      name,
      fromWireType: fromWireType,
      toWireType: toWireType,
      argPackAdvance: 8,
      readValueFromPointer: integerReadValueFromPointer(
        name,
        shift,
        minRange !== 0
      ),
      destructorFunction: null,
    });
  }

  function __embind_register_memory_view(rawType, dataTypeIndex, name) {
    var typeMapping = [
      Int8Array,
      Uint8Array,
      Int16Array,
      Uint16Array,
      Int32Array,
      Uint32Array,
      Float32Array,
      Float64Array,
    ];
    var TA = typeMapping[dataTypeIndex];
    function decodeMemoryView(handle) {
      handle = handle >> 2;
      var heap = HEAPU32;
      var size = heap[handle];
      var data = heap[handle + 1];
      return new TA(buffer, data, size);
    }
    name = readLatin1String(name);
    registerType(
      rawType,
      {
        name,
        fromWireType: decodeMemoryView,
        argPackAdvance: 8,
        readValueFromPointer: decodeMemoryView,
      },
      {
        ignoreDuplicateRegistrations: true,
      }
    );
  }

  function __embind_register_smart_ptr(
    rawType,
    rawPointeeType,
    name,
    sharingPolicy,
    getPointeeSignature,
    rawGetPointee,
    constructorSignature,
    rawConstructor,
    shareSignature,
    rawShare,
    destructorSignature,
    rawDestructor
  ) {
    name = readLatin1String(name);
    rawGetPointee = embind__requireFunction(getPointeeSignature, rawGetPointee);
    rawConstructor = embind__requireFunction(
      constructorSignature,
      rawConstructor
    );
    rawShare = embind__requireFunction(shareSignature, rawShare);
    rawDestructor = embind__requireFunction(destructorSignature, rawDestructor);
    whenDependentTypesAreResolved(
      [rawType],
      [rawPointeeType],
      function (pointeeType) {
        pointeeType = pointeeType[0];
        var registeredPointer = new RegisteredPointer(
          name,
          pointeeType.registeredClass,
          false,
          false,
          true,
          pointeeType,
          sharingPolicy,
          rawGetPointee,
          rawConstructor,
          rawShare,
          rawDestructor
        );
        return [registeredPointer];
      }
    );
  }

  function __embind_register_std_string(rawType, name) {
    name = readLatin1String(name);
    var stdStringIsUTF8 = name === "std::string";
    registerType(rawType, {
      name,
      fromWireType: function (value) {
        var length = HEAPU32[value >> 2];
        var str;
        if (stdStringIsUTF8) {
          var decodeStartPtr = value + 4;
          for (var i = 0; i <= length; ++i) {
            var currentBytePtr = value + 4 + i;
            if (i == length || HEAPU8[currentBytePtr] == 0) {
              var maxRead = currentBytePtr - decodeStartPtr;
              var stringSegment = UTF8ToString(decodeStartPtr, maxRead);
              if (str === undefined) {
                str = stringSegment;
              } else {
                str += String.fromCharCode(0);
                str += stringSegment;
              }
              decodeStartPtr = currentBytePtr + 1;
            }
          }
        } else {
          var a = new Array(length);
          for (var i = 0; i < length; ++i) {
            a[i] = String.fromCharCode(HEAPU8[value + 4 + i]);
          }
          str = a.join("");
        }
        _free(value);
        return str;
      },
      toWireType: function (destructors, value) {
        if (value instanceof ArrayBuffer) {
          value = new Uint8Array(value);
        }
        var getLength;
        var valueIsOfTypeString = typeof value == "string";
        if (
          !(
            valueIsOfTypeString ||
            value instanceof Uint8Array ||
            value instanceof Uint8ClampedArray ||
            value instanceof Int8Array
          )
        ) {
          throwBindingError("Cannot pass non-string to std::string");
        }
        if (stdStringIsUTF8 && valueIsOfTypeString) {
          getLength = () => lengthBytesUTF8(value);
        } else {
          getLength = () => value.length;
        }
        var length = getLength();
        var ptr = _malloc(4 + length + 1);
        HEAPU32[ptr >> 2] = length;
        if (stdStringIsUTF8 && valueIsOfTypeString) {
          stringToUTF8(value, ptr + 4, length + 1);
        } else if (valueIsOfTypeString) {
          for (var i = 0; i < length; ++i) {
            var charCode = value.charCodeAt(i);
            if (charCode > 255) {
              _free(ptr);
              throwBindingError(
                "String has UTF-16 code units that do not fit in 8 bits"
              );
            }
            HEAPU8[ptr + 4 + i] = charCode;
          }
        } else {
          for (var i = 0; i < length; ++i) {
            HEAPU8[ptr + 4 + i] = value[i];
          }
        }
        if (destructors !== null) {
          destructors.push(_free, ptr);
        }
        return ptr;
      },
      argPackAdvance: 8,
      readValueFromPointer: simpleReadValueFromPointer,
      destructorFunction(ptr) {
        _free(ptr);
      },
    });
  }

  function __embind_register_std_wstring(rawType, charSize, name) {
    name = readLatin1String(name);
    var decodeString, encodeString, getHeap, lengthBytesUTF, shift;
    if (charSize === 2) {
      decodeString = UTF16ToString;
      encodeString = stringToUTF16;
      lengthBytesUTF = lengthBytesUTF16;
      getHeap = () => HEAPU16;
      shift = 1;
    } else if (charSize === 4) {
      decodeString = UTF32ToString;
      encodeString = stringToUTF32;
      lengthBytesUTF = lengthBytesUTF32;
      getHeap = () => HEAPU32;
      shift = 2;
    }
    registerType(rawType, {
      name,
      fromWireType: function (value) {
        var length = HEAPU32[value >> 2];
        var HEAP = getHeap();
        var str;
        var decodeStartPtr = value + 4;
        for (var i = 0; i <= length; ++i) {
          var currentBytePtr = value + 4 + i * charSize;
          if (i == length || HEAP[currentBytePtr >> shift] == 0) {
            var maxReadBytes = currentBytePtr - decodeStartPtr;
            var stringSegment = decodeString(decodeStartPtr, maxReadBytes);
            if (str === undefined) {
              str = stringSegment;
            } else {
              str += String.fromCharCode(0);
              str += stringSegment;
            }
            decodeStartPtr = currentBytePtr + charSize;
          }
        }
        _free(value);
        return str;
      },
      toWireType: function (destructors, value) {
        if (!(typeof value == "string")) {
          throwBindingError(
            "Cannot pass non-string to C++ string type " + name
          );
        }
        var length = lengthBytesUTF(value);
        var ptr = _malloc(4 + length + charSize);
        HEAPU32[ptr >> 2] = length >> shift;
        encodeString(value, ptr + 4, length + charSize);
        if (destructors !== null) {
          destructors.push(_free, ptr);
        }
        return ptr;
      },
      argPackAdvance: 8,
      readValueFromPointer: simpleReadValueFromPointer,
      destructorFunction(ptr) {
        _free(ptr);
      },
    });
  }

  function __embind_register_value_object(
    rawType,
    name,
    constructorSignature,
    rawConstructor,
    destructorSignature,
    rawDestructor
  ) {
    structRegistrations[rawType] = {
      name: readLatin1String(name),
      rawConstructor: embind__requireFunction(
        constructorSignature,
        rawConstructor
      ),
      rawDestructor: embind__requireFunction(
        destructorSignature,
        rawDestructor
      ),
      fields: [],
    };
  }

  function __embind_register_value_object_field(
    structType,
    fieldName,
    getterReturnType,
    getterSignature,
    getter,
    getterContext,
    setterArgumentType,
    setterSignature,
    setter,
    setterContext
  ) {
    structRegistrations[structType].fields.push({
      fieldName: readLatin1String(fieldName),
      getterReturnType,
      getter: embind__requireFunction(getterSignature, getter),
      getterContext,
      setterArgumentType,
      setter: embind__requireFunction(setterSignature, setter),
      setterContext,
    });
  }

  function __embind_register_void(rawType, name) {
    name = readLatin1String(name);
    registerType(rawType, {
      isVoid: true,
      name,
      argPackAdvance: 0,
      fromWireType: function () {
        return undefined;
      },
      toWireType: function (destructors, o) {
        return undefined;
      },
    });
  }

  function __emscripten_date_now() {
    return Date.now();
  }

  var nowIsMonotonic = true;

  function __emscripten_get_now_is_monotonic() {
    return nowIsMonotonic;
  }

  function requireRegisteredType(rawType, humanName) {
    var impl = registeredTypes[rawType];
    if (undefined === impl) {
      throwBindingError(
        humanName + " has unknown type " + getTypeName(rawType)
      );
    }
    return impl;
  }

  function __emval_lookupTypes(argCount, argTypes) {
    var a = new Array(argCount);
    for (var i = 0; i < argCount; ++i) {
      a[i] = requireRegisteredType(
        HEAP32[(argTypes >> 2) + i],
        "parameter " + i
      );
    }
    return a;
  }

  function __emval_call(handle, argCount, argTypes, argv) {
    handle = Emval.toValue(handle);
    var types = __emval_lookupTypes(argCount, argTypes);
    var args = new Array(argCount);
    for (var i = 0; i < argCount; ++i) {
      var type = types[i];
      args[i] = type.readValueFromPointer(argv);
      argv += type.argPackAdvance;
    }
    var rv = handle.apply(undefined, args);
    return Emval.toHandle(rv);
  }

  function __emval_incref(handle) {
    if (handle > 4) {
      emval_handle_array[handle].refcount += 1;
    }
  }

  function __emval_take_value(type, argv) {
    type = requireRegisteredType(type, "_emval_take_value");
    var v = type.readValueFromPointer(argv);
    return Emval.toHandle(v);
  }

  function __localtime_js(time, tmPtr) {
    var date = new Date(HEAP32[time >> 2] * 1e3);
    HEAP32[tmPtr >> 2] = date.getSeconds();
    HEAP32[(tmPtr + 4) >> 2] = date.getMinutes();
    HEAP32[(tmPtr + 8) >> 2] = date.getHours();
    HEAP32[(tmPtr + 12) >> 2] = date.getDate();
    HEAP32[(tmPtr + 16) >> 2] = date.getMonth();
    HEAP32[(tmPtr + 20) >> 2] = date.getFullYear() - 1900;
    HEAP32[(tmPtr + 24) >> 2] = date.getDay();
    var start = new Date(date.getFullYear(), 0, 1);
    var yday = ((date.getTime() - start.getTime()) / (1e3 * 60 * 60 * 24)) | 0;
    HEAP32[(tmPtr + 28) >> 2] = yday;
    HEAP32[(tmPtr + 36) >> 2] = -(date.getTimezoneOffset() * 60);
    var summerOffset = new Date(date.getFullYear(), 6, 1).getTimezoneOffset();
    var winterOffset = start.getTimezoneOffset();
    var dst =
      (summerOffset != winterOffset &&
        date.getTimezoneOffset() == Math.min(winterOffset, summerOffset)) | 0;
    HEAP32[(tmPtr + 32) >> 2] = dst;
  }

  function __mmap_js(addr, len, prot, flags, fd, off, allocated, builtin) {
    return -52;
  }

  function __munmap_js(addr, len, prot, flags, fd, offset) {}

  function _tzset_impl(timezone, daylight, tzname) {
    var currentYear = new Date().getFullYear();
    var winter = new Date(currentYear, 0, 1);
    var summer = new Date(currentYear, 6, 1);
    var winterOffset = winter.getTimezoneOffset();
    var summerOffset = summer.getTimezoneOffset();
    var stdTimezoneOffset = Math.max(winterOffset, summerOffset);
    HEAP32[timezone >> 2] = stdTimezoneOffset * 60;
    HEAP32[daylight >> 2] = Number(winterOffset != summerOffset);
    function extractZone(date) {
      var match = date.toTimeString().match(/\(([A-Za-z ]+)\)$/);
      return match ? match[1] : "GMT";
    }
    var winterName = extractZone(winter);
    var summerName = extractZone(summer);
    var winterNamePtr = allocateUTF8(winterName);
    var summerNamePtr = allocateUTF8(summerName);
    if (summerOffset < winterOffset) {
      HEAP32[tzname >> 2] = winterNamePtr;
      HEAP32[(tzname + 4) >> 2] = summerNamePtr;
    } else {
      HEAP32[tzname >> 2] = summerNamePtr;
      HEAP32[(tzname + 4) >> 2] = winterNamePtr;
    }
  }

  function __tzset_js(timezone, daylight, tzname) {
    if (__tzset_js.called) {
      return;
    }
    __tzset_js.called = true;
    _tzset_impl(timezone, daylight, tzname);
  }

  function _abort() {
    abort("");
  }

  function _emscripten_get_heap_max() {
    return 2147483648;
  }

  var _emscripten_get_now;

  _emscripten_get_now = () => performance.now();

  function _emscripten_memcpy_big(dest, src, num) {
    HEAPU8.copyWithin(dest, src, src + num);
  }

  function emscripten_realloc_buffer(size) {
    try {
      wasmMemory.grow((size - buffer.byteLength + 65535) >>> 16);
      updateGlobalBufferAndViews(wasmMemory.buffer);
      return 1;
    } catch (e) {}
  }

  function _emscripten_resize_heap(requestedSize) {
    var oldSize = HEAPU8.length;
    requestedSize = requestedSize >>> 0;
    var maxHeapSize = _emscripten_get_heap_max();
    if (requestedSize > maxHeapSize) {
      return false;
    }
    let alignUp = (x, multiple) => x + ((multiple - (x % multiple)) % multiple);
    for (var cutDown = 1; cutDown <= 4; cutDown *= 2) {
      var overGrownHeapSize = oldSize * (1 + 0.2 / cutDown);
      overGrownHeapSize = Math.min(
        overGrownHeapSize,
        requestedSize + 100663296
      );
      var newSize = Math.min(
        maxHeapSize,
        alignUp(Math.max(requestedSize, overGrownHeapSize), 65536)
      );
      var replacement = emscripten_realloc_buffer(newSize);
      if (replacement) {
        return true;
      }
    }
    return false;
  }

  var ENV = {};

  function getExecutableName() {
    return thisProgram || "./this.program";
  }

  function getEnvStrings() {
    if (!getEnvStrings.strings) {
      var lang =
        (
          (typeof navigator == "object" &&
            navigator.languages &&
            navigator.languages[0]) ||
          "C"
        ).replace("-", "_") + ".UTF-8";
      var env = {
        USER: "web_user",
        LOGNAME: "web_user",
        PATH: "/",
        PWD: "/",
        HOME: "/home/web_user",
        LANG: lang,
        _: getExecutableName(),
      };
      for (var x in ENV) {
        if (ENV[x] === undefined) {
          delete env[x];
        } else {
          env[x] = ENV[x];
        }
      }
      var strings = [];
      for (var x in env) {
        strings.push(x + "=" + env[x]);
      }
      getEnvStrings.strings = strings;
    }
    return getEnvStrings.strings;
  }

  function _environ_get(__environ, environ_buf) {
    var bufSize = 0;
    getEnvStrings().forEach(function (string, i) {
      var ptr = environ_buf + bufSize;
      HEAP32[(__environ + i * 4) >> 2] = ptr;
      writeAsciiToMemory(string, ptr);
      bufSize += string.length + 1;
    });
    return 0;
  }

  function _environ_sizes_get(penviron_count, penviron_buf_size) {
    var strings = getEnvStrings();
    HEAP32[penviron_count >> 2] = strings.length;
    var bufSize = 0;
    strings.forEach(function (string) {
      bufSize += string.length + 1;
    });
    HEAP32[penviron_buf_size >> 2] = bufSize;
    return 0;
  }

  function _exit(status) {
    exit(status);
  }

  function _fd_close(fd) {
    return 0;
  }

  function _fd_read(fd, iov, iovcnt, pnum) {
    var stream = SYSCALLS.getStreamFromFD(fd);
    var num = SYSCALLS.doReadv(stream, iov, iovcnt);
    HEAP32[pnum >> 2] = num;
    return 0;
  }

  function _fd_seek(fd, offset_low, offset_high, whence, newOffset) {}

  function _fd_write(fd, iov, iovcnt, pnum) {
    var num = 0;
    for (var i = 0; i < iovcnt; i++) {
      var ptr = HEAP32[iov >> 2];
      var len = HEAP32[(iov + 4) >> 2];
      iov += 8;
      for (var j = 0; j < len; j++) {
        SYSCALLS.printChar(fd, HEAPU8[ptr + j]);
      }
      num += len;
    }
    HEAP32[pnum >> 2] = num;
    return 0;
  }

  function getRandomDevice() {
    if (
      typeof crypto == "object" &&
      typeof crypto.getRandomValues == "function"
    ) {
      var randomBuffer = new Uint8Array(1);
      return function () {
        crypto.getRandomValues(randomBuffer);
        return randomBuffer[0];
      };
    }
    return function () {
      abort("randomDevice");
    };
  }

  function _getentropy(buffer, size) {
    if (!_getentropy.randomDevice) {
      _getentropy.randomDevice = getRandomDevice();
    }
    for (var i = 0; i < size; i++) {
      HEAP8[(buffer + i) >> 0] = _getentropy.randomDevice();
    }
    return 0;
  }

  function _pclose() {
    err("missing function: pclose");
    abort(-1);
  }

  function _setTempRet0(val) {
    setTempRet0(val);
  }

  function __isLeapYear(year) {
    return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
  }

  function __arraySum(array, index) {
    var sum = 0;
    for (var i = 0; i <= index; sum += array[i++]) {}
    return sum;
  }

  var __MONTH_DAYS_LEAP = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

  var __MONTH_DAYS_REGULAR = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

  function __addDays(date, days) {
    var newDate = new Date(date.getTime());
    while (days > 0) {
      var leap = __isLeapYear(newDate.getFullYear());
      var currentMonth = newDate.getMonth();
      var daysInCurrentMonth = (
        leap ? __MONTH_DAYS_LEAP : __MONTH_DAYS_REGULAR
      )[currentMonth];
      if (days > daysInCurrentMonth - newDate.getDate()) {
        days -= daysInCurrentMonth - newDate.getDate() + 1;
        newDate.setDate(1);
        if (currentMonth < 11) {
          newDate.setMonth(currentMonth + 1);
        } else {
          newDate.setMonth(0);
          newDate.setFullYear(newDate.getFullYear() + 1);
        }
      } else {
        newDate.setDate(newDate.getDate() + days);
        return newDate;
      }
    }
    return newDate;
  }

  function _strftime(s, maxsize, format, tm) {
    var tm_zone = HEAP32[(tm + 40) >> 2];
    var date = {
      tm_sec: HEAP32[tm >> 2],
      tm_min: HEAP32[(tm + 4) >> 2],
      tm_hour: HEAP32[(tm + 8) >> 2],
      tm_mday: HEAP32[(tm + 12) >> 2],
      tm_mon: HEAP32[(tm + 16) >> 2],
      tm_year: HEAP32[(tm + 20) >> 2],
      tm_wday: HEAP32[(tm + 24) >> 2],
      tm_yday: HEAP32[(tm + 28) >> 2],
      tm_isdst: HEAP32[(tm + 32) >> 2],
      tm_gmtoff: HEAP32[(tm + 36) >> 2],
      tm_zone: tm_zone ? UTF8ToString(tm_zone) : "",
    };
    var pattern = UTF8ToString(format);
    var EXPANSION_RULES_1 = {
      "%c": "%a %b %d %H:%M:%S %Y",
      "%D": "%m/%d/%y",
      "%F": "%Y-%m-%d",
      "%h": "%b",
      "%r": "%I:%M:%S %p",
      "%R": "%H:%M",
      "%T": "%H:%M:%S",
      "%x": "%m/%d/%y",
      "%X": "%H:%M:%S",
      "%Ec": "%c",
      "%EC": "%C",
      "%Ex": "%m/%d/%y",
      "%EX": "%H:%M:%S",
      "%Ey": "%y",
      "%EY": "%Y",
      "%Od": "%d",
      "%Oe": "%e",
      "%OH": "%H",
      "%OI": "%I",
      "%Om": "%m",
      "%OM": "%M",
      "%OS": "%S",
      "%Ou": "%u",
      "%OU": "%U",
      "%OV": "%V",
      "%Ow": "%w",
      "%OW": "%W",
      "%Oy": "%y",
    };
    for (var rule in EXPANSION_RULES_1) {
      pattern = pattern.replace(new RegExp(rule, "g"), EXPANSION_RULES_1[rule]);
    }
    var WEEKDAYS = [
      "Sunday",
      "Monday",
      "Tuesday",
      "Wednesday",
      "Thursday",
      "Friday",
      "Saturday",
    ];
    var MONTHS = [
      "January",
      "February",
      "March",
      "April",
      "May",
      "June",
      "July",
      "August",
      "September",
      "October",
      "November",
      "December",
    ];
    function leadingSomething(value, digits, character) {
      var str = typeof value == "number" ? value.toString() : value || "";
      while (str.length < digits) {
        str = character[0] + str;
      }
      return str;
    }
    function leadingNulls(value, digits) {
      return leadingSomething(value, digits, "0");
    }
    function compareByDay(date1, date2) {
      function sgn(value) {
        return value < 0 ? -1 : value > 0 ? 1 : 0;
      }
      var compare;
      if ((compare = sgn(date1.getFullYear() - date2.getFullYear())) === 0) {
        if ((compare = sgn(date1.getMonth() - date2.getMonth())) === 0) {
          compare = sgn(date1.getDate() - date2.getDate());
        }
      }
      return compare;
    }
    function getFirstWeekStartDate(janFourth) {
      switch (janFourth.getDay()) {
        case 0:
          return new Date(janFourth.getFullYear() - 1, 11, 29);

        case 1:
          return janFourth;

        case 2:
          return new Date(janFourth.getFullYear(), 0, 3);

        case 3:
          return new Date(janFourth.getFullYear(), 0, 2);

        case 4:
          return new Date(janFourth.getFullYear(), 0, 1);

        case 5:
          return new Date(janFourth.getFullYear() - 1, 11, 31);

        case 6:
          return new Date(janFourth.getFullYear() - 1, 11, 30);
      }
    }
    function getWeekBasedYear(date) {
      var thisDate = __addDays(
        new Date(date.tm_year + 1900, 0, 1),
        date.tm_yday
      );
      var janFourthThisYear = new Date(thisDate.getFullYear(), 0, 4);
      var janFourthNextYear = new Date(thisDate.getFullYear() + 1, 0, 4);
      var firstWeekStartThisYear = getFirstWeekStartDate(janFourthThisYear);
      var firstWeekStartNextYear = getFirstWeekStartDate(janFourthNextYear);
      if (compareByDay(firstWeekStartThisYear, thisDate) <= 0) {
        if (compareByDay(firstWeekStartNextYear, thisDate) <= 0) {
          return thisDate.getFullYear() + 1;
        }
        return thisDate.getFullYear();
      }
      return thisDate.getFullYear() - 1;
    }
    var EXPANSION_RULES_2 = {
      "%a": function (date) {
        return WEEKDAYS[date.tm_wday].substring(0, 3);
      },
      "%A": function (date) {
        return WEEKDAYS[date.tm_wday];
      },
      "%b": function (date) {
        return MONTHS[date.tm_mon].substring(0, 3);
      },
      "%B": function (date) {
        return MONTHS[date.tm_mon];
      },
      "%C": function (date) {
        var year = date.tm_year + 1900;
        return leadingNulls((year / 100) | 0, 2);
      },
      "%d": function (date) {
        return leadingNulls(date.tm_mday, 2);
      },
      "%e": function (date) {
        return leadingSomething(date.tm_mday, 2, " ");
      },
      "%g": function (date) {
        return getWeekBasedYear(date).toString().substring(2);
      },
      "%G": function (date) {
        return getWeekBasedYear(date);
      },
      "%H": function (date) {
        return leadingNulls(date.tm_hour, 2);
      },
      "%I": function (date) {
        var twelveHour = date.tm_hour;
        if (twelveHour == 0) {
          twelveHour = 12;
        } else if (twelveHour > 12) {
          twelveHour -= 12;
        }
        return leadingNulls(twelveHour, 2);
      },
      "%j": function (date) {
        return leadingNulls(
          date.tm_mday +
            __arraySum(
              __isLeapYear(date.tm_year + 1900)
                ? __MONTH_DAYS_LEAP
                : __MONTH_DAYS_REGULAR,
              date.tm_mon - 1
            ),
          3
        );
      },
      "%m": function (date) {
        return leadingNulls(date.tm_mon + 1, 2);
      },
      "%M": function (date) {
        return leadingNulls(date.tm_min, 2);
      },
      "%n": function () {
        return "\n";
      },
      "%p": function (date) {
        if (date.tm_hour >= 0 && date.tm_hour < 12) {
          return "AM";
        }
        return "PM";
      },
      "%S": function (date) {
        return leadingNulls(date.tm_sec, 2);
      },
      "%t": function () {
        return "\t";
      },
      "%u": function (date) {
        return date.tm_wday || 7;
      },
      "%U": function (date) {
        var days = date.tm_yday + 7 - date.tm_wday;
        return leadingNulls(Math.floor(days / 7), 2);
      },
      "%V": function (date) {
        var val = Math.floor((date.tm_yday + 7 - ((date.tm_wday + 6) % 7)) / 7);
        if ((date.tm_wday + 371 - date.tm_yday - 2) % 7 <= 2) {
          val++;
        }
        if (!val) {
          val = 52;
          var dec31 = (date.tm_wday + 7 - date.tm_yday - 1) % 7;
          if (
            dec31 == 4 ||
            (dec31 == 5 && __isLeapYear((date.tm_year % 400) - 1))
          ) {
            val++;
          }
        } else if (val == 53) {
          var jan1 = (date.tm_wday + 371 - date.tm_yday) % 7;
          if (jan1 != 4 && (jan1 != 3 || !__isLeapYear(date.tm_year))) {
            val = 1;
          }
        }
        return leadingNulls(val, 2);
      },
      "%w": function (date) {
        return date.tm_wday;
      },
      "%W": function (date) {
        var days = date.tm_yday + 7 - ((date.tm_wday + 6) % 7);
        return leadingNulls(Math.floor(days / 7), 2);
      },
      "%y": function (date) {
        return (date.tm_year + 1900).toString().substring(2);
      },
      "%Y": function (date) {
        return date.tm_year + 1900;
      },
      "%z": function (date) {
        var off = date.tm_gmtoff;
        var ahead = off >= 0;
        off = Math.abs(off) / 60;
        off = (off / 60) * 100 + (off % 60);
        return (ahead ? "+" : "-") + String("0000" + off).slice(-4);
      },
      "%Z": function (date) {
        return date.tm_zone;
      },
      "%%": function () {
        return "%";
      },
    };
    pattern = pattern.replace(/%%/g, "\0\0");
    for (var rule in EXPANSION_RULES_2) {
      if (pattern.includes(rule)) {
        pattern = pattern.replace(
          new RegExp(rule, "g"),
          EXPANSION_RULES_2[rule](date)
        );
      }
    }
    pattern = pattern.replace(/\0\0/g, "%");
    var bytes = intArrayFromString(pattern, false);
    if (bytes.length > maxsize) {
      return 0;
    }
    writeArrayToMemory(bytes, s);
    return bytes.length - 1;
  }

  function _strftime_l(s, maxsize, format, tm) {
    return _strftime(s, maxsize, format, tm);
  }

  InternalError = Module.InternalError = extendError(Error, "InternalError");

  embind_init_charCodes();

  BindingError = Module.BindingError = extendError(Error, "BindingError");

  init_ClassHandle();

  init_embind();

  init_RegisteredPointer();

  UnboundTypeError = Module.UnboundTypeError = extendError(
    Error,
    "UnboundTypeError"
  );

  init_emval();

  function intArrayFromString(stringy, dontAddNull, length) {
    var len = length > 0 ? length : lengthBytesUTF8(stringy) + 1;
    var u8array = new Array(len);
    var numBytesWritten = stringToUTF8Array(
      stringy,
      u8array,
      0,
      u8array.length
    );
    if (dontAddNull) {
      u8array.length = numBytesWritten;
    }
    return u8array;
  }

  var asmLibraryArg = {
    __assert_fail: ___assert_fail,
    __cxa_allocate_exception: ___cxa_allocate_exception,
    __cxa_rethrow: ___cxa_rethrow,
    __cxa_throw: ___cxa_throw,
    __syscall_faccessat: ___syscall_faccessat,
    __syscall_fcntl64: ___syscall_fcntl64,
    __syscall_fstat64: ___syscall_fstat64,
    __syscall_getcwd: ___syscall_getcwd,
    __syscall_ioctl: ___syscall_ioctl,
    __syscall_lstat64: ___syscall_lstat64,
    __syscall_newfstatat: ___syscall_newfstatat,
    __syscall_openat: ___syscall_openat,
    __syscall_renameat: ___syscall_renameat,
    __syscall_rmdir: ___syscall_rmdir,
    __syscall_stat64: ___syscall_stat64,
    __syscall_unlinkat: ___syscall_unlinkat,
    _embind_finalize_value_object: __embind_finalize_value_object,
    _embind_register_bigint: __embind_register_bigint,
    _embind_register_bool: __embind_register_bool,
    _embind_register_class: __embind_register_class,
    _embind_register_class_constructor: __embind_register_class_constructor,
    _embind_register_class_function: __embind_register_class_function,
    _embind_register_emval: __embind_register_emval,
    _embind_register_float: __embind_register_float,
    _embind_register_integer: __embind_register_integer,
    _embind_register_memory_view: __embind_register_memory_view,
    _embind_register_smart_ptr: __embind_register_smart_ptr,
    _embind_register_std_string: __embind_register_std_string,
    _embind_register_std_wstring: __embind_register_std_wstring,
    _embind_register_value_object: __embind_register_value_object,
    _embind_register_value_object_field: __embind_register_value_object_field,
    _embind_register_void: __embind_register_void,
    _emscripten_date_now: __emscripten_date_now,
    _emscripten_get_now_is_monotonic: __emscripten_get_now_is_monotonic,
    _emval_call: __emval_call,
    _emval_decref: __emval_decref,
    _emval_incref: __emval_incref,
    _emval_take_value: __emval_take_value,
    _localtime_js: __localtime_js,
    _mmap_js: __mmap_js,
    _munmap_js: __munmap_js,
    _tzset_js: __tzset_js,
    abort: _abort,
    emscripten_get_heap_max: _emscripten_get_heap_max,
    emscripten_get_now: _emscripten_get_now,
    emscripten_memcpy_big: _emscripten_memcpy_big,
    emscripten_resize_heap: _emscripten_resize_heap,
    environ_get: _environ_get,
    environ_sizes_get: _environ_sizes_get,
    exit: _exit,
    fd_close: _fd_close,
    fd_read: _fd_read,
    fd_seek: _fd_seek,
    fd_write: _fd_write,
    getentropy: _getentropy,
    memory: wasmMemory,
    pclose: _pclose,
    setTempRet0: _setTempRet0,
    strftime_l: _strftime_l,
  };

  var asm = createWasm();

  var calledRun;

  function ExitStatus(status) {
    this.name = "ExitStatus";
    this.message = "Program terminated with exit(" + status + ")";
    this.status = status;
  }

  dependenciesFulfilled = function runCaller() {
    if (!calledRun) {
      run();
    }
    if (!calledRun) {
      dependenciesFulfilled = runCaller;
    }
  };

  function run(args) {
    args = args || arguments_;
    if (runDependencies > 0) {
      return;
    }
    preRun();
    if (runDependencies > 0) {
      return;
    }
    function doRun() {
      if (calledRun) {
        return;
      }
      calledRun = true;
      Module.calledRun = true;
      if (ABORT) {
        return;
      }
      initRuntime();
      if (Module.onRuntimeInitialized) {
        Module.onRuntimeInitialized();
      }
      postRun();
    }
    if (Module.setStatus) {
      Module.setStatus("Running...");
      setTimeout(function () {
        setTimeout(function () {
          Module.setStatus("");
        }, 1);
        doRun();
      }, 1);
    } else {
      doRun();
    }
  }

  Module.run = run;

  function exit(status, implicit) {
    EXITSTATUS = status;
    procExit(status);
  }

  function procExit(code) {
    EXITSTATUS = code;
    if (!keepRuntimeAlive()) {
      if (Module.onExit) {
        Module.onExit(code);
      }
      ABORT = true;
    }
    quit_(code, new ExitStatus(code));
  }

  if (Module.preInit) {
    if (typeof Module.preInit == "function") {
      Module.preInit = [Module.preInit];
    }
    while (Module.preInit.length) {
      Module.preInit.pop()();
    }
  }

  run();

  /* Use an optimized gemm implementation if available, otherwise use the fallback
   * implementation.
   */
  function createWasmGemm() {
    // A map of expected gemm function to the corresponding fallback gemm function names.
    const GEMM_TO_FALLBACK_FUNCTIONS_MAP = {
      int8_prepare_a: "int8PrepareAFallback",
      int8_prepare_b: "int8PrepareBFallback",
      int8_prepare_b_from_transposed: "int8PrepareBFromTransposedFallback",
      int8_prepare_b_from_quantized_transposed:
        "int8PrepareBFromQuantizedTransposedFallback",
      int8_prepare_bias: "int8PrepareBiasFallback",
      int8_multiply_and_add_bias: "int8MultiplyAndAddBiasFallback",
      int8_select_columns_of_b: "int8SelectColumnsOfBFallback",
    };

    // Name of the optimized gemm implementation.
    const OPTIMIZED_GEMM = "mozIntGemm";

    const optimizedGemmModule = WebAssembly[OPTIMIZED_GEMM];
    if (!optimizedGemmModule) {
      return fallbackGemm(GEMM_TO_FALLBACK_FUNCTIONS_MAP);
    }

    const optimizedGemmModuleExports = new WebAssembly.Instance(
      optimizedGemmModule(),
      { "": { memory: wasmMemory } }
    ).exports;
    for (let key in GEMM_TO_FALLBACK_FUNCTIONS_MAP) {
      if (!optimizedGemmModuleExports[key]) {
        return fallbackGemm(GEMM_TO_FALLBACK_FUNCTIONS_MAP);
      }
    }
    Module.print(`Using optimized gemm (${OPTIMIZED_GEMM}) implementation`);
    return optimizedGemmModuleExports;
  }

  // Return the fallback gemm implementation.
  function fallbackGemm(gemmToFallbackFunctionsMap) {
    // The fallback gemm implementation
    const FALLBACK_GEMM = "asm";

    let fallbackGemmModuleExports = {};
    for (let key in gemmToFallbackFunctionsMap) {
      fallbackGemmModuleExports[key] = (...a) =>
        Module[FALLBACK_GEMM][gemmToFallbackFunctionsMap[key]](...a);
    }
    Module.print(`Using fallback gemm implementation`);
    return fallbackGemmModuleExports;
  }
  return Module;
}
PK
!<m�~KF	F	Hchrome/toolkit/content/global/translations/translations-document.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "console", () => {
  return console.createInstance({
    maxLogLevelPref: "browser.translations.logLevel",
    prefix: "Translations",
  });
});

/**
 * Map the NodeFilter enums that are used by the TreeWalker into enums that make
 * sense for determining the status of the nodes for the TranslationsDocument process.
 * This aligns the meanings of the filtering for the translations process.
 */
const NodeStatus = {
  // This node is ready to translate as is.
  READY_TO_TRANSLATE: NodeFilter.FILTER_ACCEPT,

  // This node is a shadow host and needs to be subdivided further.
  SHADOW_HOST: NodeFilter.FILTER_ACCEPT,

  // This node contains too many block elements and needs to be subdivided further.
  SUBDIVIDE_FURTHER: NodeFilter.FILTER_SKIP,

  // This node should not be considered for translation.
  NOT_TRANSLATABLE: NodeFilter.FILTER_REJECT,
};

/**
 * @typedef {import("../translations").NodeVisibility} NodeVisibility
 * @typedef {(message: string) => Promise<string>} TranslationFunction
 */

/**
 * Create a translation cache with a limit. It implements a "least recently used" strategy
 * to remove old translations. After `#cacheExpirationMS` the cache will be emptied.
 * This cache is owned statically by the TranslationsChild. This means that it will be
 * re-used on page reloads if the origin of the site does not change.
 */
export class LRUCache {
  /** @type {Map<string, string>} */
  #htmlCache = new Map();
  /** @type {Map<string, string>} */
  #textCache = new Map();
  /** @type {string} */
  #fromLanguage;
  /** @type {string} */
  #toLanguage;

  /**
   * This limit is used twice, once for Text translations, and once for HTML translations.
   */
  #cacheLimit = 5_000;

  /**
   * This cache will self-destruct after 10 minutes.
   */
  #cacheExpirationMS = 10 * 60_000;

  /**
   * @param {string} fromLanguage
   * @param {string} toLanguage
   */
  constructor(fromLanguage, toLanguage) {
    this.#fromLanguage = fromLanguage;
    this.#toLanguage = toLanguage;
  }

  /**
   * @param {boolean} isHTML
   * @returns {boolean}
   */
  #getCache(isHTML) {
    return isHTML ? this.#htmlCache : this.#textCache;
  }

  /**
   * Get a translation if it exists from the cache, and move it to the end of the cache
   * to keep it alive longer.
   *
   * @param {string} sourceString
   * @param {boolean} isHTML
   * @returns {string}
   */
  get(sourceString, isHTML) {
    const cache = this.#getCache(isHTML);
    const targetString = cache.get(sourceString);

    if (targetString === undefined) {
      return undefined;
    }

    // Maps are ordered, move this item to the end of the list so it will stay
    // alive longer.
    cache.delete(sourceString);
    cache.set(sourceString, targetString);

    this.keepAlive();

    return targetString;
  }

  /**
   * @param {string} sourceString
   * @param {string} targetString
   * @param {boolean} isHTML
   */
  set(sourceString, targetString, isHTML) {
    const cache = this.#getCache(isHTML);
    if (cache.size === this.#cacheLimit) {
      // If the cache is at the limit, get the least recently used translation and
      // remove it. This works since Maps have keys ordered by insertion order.
      const key = cache.keys().next().value;
      cache.delete(key);
    }
    cache.set(sourceString, targetString);
    this.keepAlive();
  }

  /**
   * @param {string} fromLanguage
   * @param {string} toLanguage
   */
  matches(fromLanguage, toLanguage) {
    return (
      this.#fromLanguage === fromLanguage && this.#toLanguage === toLanguage
    );
  }

  /**
   * @type {number}
   */
  #timeoutId = 0;

  #pendingKeepAlive = false;

  /**
   * Clear out the cache on a timer.
   */
  keepAlive() {
    if (this.#timeoutId) {
      lazy.clearTimeout(this.#timeoutId);
    }
    if (!this.#pendingKeepAlive) {
      // Rather than continuously creating new functions in a tight loop, only schedule
      // one keepAlive timeout on the next tick.
      this.#pendingKeepAlive = true;

      lazy.setTimeout(() => {
        this.#pendingKeepAlive = false;
        this.#timeoutId = lazy.setTimeout(() => {
          this.#htmlCache = new Map();
          this.#textCache = new Map();
        }, this.#cacheExpirationMS);
      }, 0);
    }
  }
}

/**
 * How often the DOM is updated with translations, in milliseconds.
 */
const DOM_UPDATE_INTERVAL_MS = 50;

/**
 * These tags are excluded from translation.
 */
const EXCLUDED_TAGS = new Set([
  // The following are elements that semantically should not be translated.
  "CODE",
  "KBD",
  "SAMP",
  "VAR",
  "ACRONYM",

  // The following are deprecated tags.
  "DIR",
  "APPLET",

  // The following are embedded elements, and are not supported (yet).
  "MATH",
  "EMBED",
  "OBJECT",
  "IFRAME",

  // This is an SVG tag that can contain arbitrary XML, ignore it.
  "METADATA",

  // These are elements that are treated as opaque by Firefox which causes their
  // innerHTML property to be just the raw text node behind it. Any text that is sent as
  // HTML must be valid, and there is no guarantee that the innerHTML is valid.
  "NOSCRIPT",
  "NOEMBED",
  "NOFRAMES",

  // The title is handled separately, and a HEAD tag should not be considered.
  "HEAD",

  // These are not user-visible tags.
  "STYLE",
  "SCRIPT",
  "TEMPLATE",

  // Textarea elements contain user content, which should not be translated.
  "TEXTAREA",
]);

/**
 * Attributes to be translated, a tuple of the tag name and the element. If the attribute
 * is not particular to an element, leave it as an empty string.
 *
 * @type {Array<[string, string]>}
 */
const TRANSLATABLE_ATTRIBUTES = [
  ["", "aria-brailledescription"],
  ["", "aria-braillelabel"],
  ["", "aria-description"],
  ["", "aria-label"],
  ["", "aria-placeholder"],
  ["", "aria-roledescription"],
  ["", "aria-valuetext"],
  ["", "placeholder"],
  ["", "title"],
  ["IMG", "alt"],
  ["INPUT", "value"],
  ["TRACK", "label"],
];

/**
 * Selectors to get all the attributes.
 * e.g. "[title]", "[placeholder]", "input[value]"
 */
const TRANSLATABLE_ATTRIBUTES_SELECTOR = TRANSLATABLE_ATTRIBUTES.map(
  ([tagName, attribute]) => `${tagName}[${attribute}]`
);

/**
 * Options used by the mutation observer
 */
const MUTATION_OBSERVER_OPTIONS = {
  characterData: true,
  childList: true,
  subtree: true,
  attributes: true,
  attributeFilter: TRANSLATABLE_ATTRIBUTES.map(
    ([_tagName, attribute]) => attribute
  ),
};

/**
 * This class manages the process of translating the DOM from one language to another.
 * A translateHTML and a translateText function are injected into the constructor. This
 * class is responsible for subdividing a Node into small enough pieces to where it
 * contains a reasonable amount of text and inline elements for the translations engine
 * to translate. Once a node has been identified as a small enough chunk, its innerHTML
 * is read, and sent for translation. The async translation result comes back as an HTML
 * string. The DOM node is updated with the new text and potentially changed DOM ordering.
 *
 * This class also handles mutations of the DOM and will translate nodes as they are added
 * to the page, or the when the node's text is changed by content scripts.
 */
export class TranslationsDocument {
  /**
   * The BCP 47 language tag that is used on the page.
   *
    @type {string} */
  documentLanguage;

  /**
   * The timeout between the first translation received and the call to update the DOM
   * with translations.
   */
  #updateTimeout = null;
  #attributeUpdateTimeout = null;

  /**
   * The nodes that need translations. They are queued when the document tree is walked,
   * and then they are dispatched for translation based on their visibility. The viewport
   * nodes are given the highest priority.
   *
   * @type {Map<Node, NodeVisibility>}
   */
  #queuedNodes = new Map();

  /**
   * The nodes that need Attribute translations. They are queued when the document tree is walked,
   * and then they are dispatched for translation based on their visibility. The viewport
   * nodes are given the highest priority.
   *
   * @type  {Map<Node, { attributeList: string[], visibility: NodeVisibility }>}
   */
  #queuedAttributeNodes = new Map();

  /**
   * The count of how many pending translations have been sent to the translations
   * engine.
   */
  #pendingTranslationsCount = 0;

  /**
   * The list of nodes that need updating with the translated HTML. These are batched
   * into an update.
   *
   * @type {Set<{ node: Node, translatedHTML: string }>}
   */
  #nodesWithTranslatedHTML = new Set();

  /**
   * The list of nodes that need updating with the translated Attribute HTML. These are batched
   * into an update.
   *
   * @type {Set<{ node: Node, translation: string, attribute: string }>}
   */
  #nodesWithTranslatedAttributes = new Set();

  /**
   * The set of nodes that have been subdivided and processed for translation. They
   * should not be submitted again unless their contents have been changed.
   *
   * @type {WeakSet<Node>}
   */
  #processedNodes = new WeakSet();

  /**
   * All root elements we're trying to translate. This should be the `document.body`
   * and the the `title` element.
   *
   * @type {Set<Node>}
   */
  #rootNodes = new Set();

  /**
   * This promise gets resolved when the initial viewport translations are done.
   * This is a key user-visible performance metric. It represents what the user
   * actually sees.
   *
   * @type {Promise<void> | null}
   */
  viewportTranslated = null;

  isDestroyed = false;

  /**
   * This boolean indicates whether the first visible DOM translation change is about to occur.
   *
   * @type {boolean}
   */
  hasFirstVisibleChange = false;

  /**
   * Construct a new TranslationsDocument. It is tied to a specific Document and cannot
   * be re-used. The translation functions are injected since this class shouldn't
   * manage the life cycle of the translations engines.
   *
   * @param {Document} document
   * @param {string} documentLanguage - The BCP 47 tag of the source language.
   * @param {string} toLanguage - The BCP 47 tag of the destination language.
   * @param {number} innerWindowId - This is used for better profiler marker reporting.
   * @param {MessagePort} port - The port to the translations engine.
   * @param {() => void} requestNewPort - Used when an engine times out and a new
   *                                      translation request comes in.
   * @param {() => void} reportVisibleChange - Used to report to the actor that the first visible change
   *                                          for a translation is about to occur.
   * @param {number} translationsStart
   * @param {() => number} now
   * @param {LRUCache} translationsCache
   */
  constructor(
    document,
    documentLanguage,
    toLanguage,
    innerWindowId,
    port,
    requestNewPort,
    reportVisibleChange,
    translationsStart,
    now,
    translationsCache
  ) {
    /**
     * The language of the document. If elements are found that do not match this language,
     * then they are skipped.
     *
     * @type {string}
     */
    this.documentLanguage = documentLanguage;
    if (documentLanguage.length !== 2) {
      throw new Error(
        "Expected the document language to be a valid 2 letter BCP 47 language tag: " +
          documentLanguage
      );
    }
    if (toLanguage.length !== 2) {
      throw new Error(
        "Expected the destination language to be a valid 2 letter BCP 47 language tag: " +
          toLanguage
      );
    }

    /** @type {QueuedTranslator} */
    this.translator = new QueuedTranslator(
      port,
      requestNewPort,
      reportVisibleChange
    );

    /** @type {number} */
    this.innerWindowId = innerWindowId;

    /** @type {DOMParser} */
    this.domParser = new document.ownerGlobal.DOMParser();

    /** @type {Document} */
    this.document = document;

    /** @type {LRUCache} */
    this.translationsCache = translationsCache;

    /** @type {() => void} */
    this.actorReportFirstVisibleChange = reportVisibleChange;

    /**
     * This selector runs to find child nodes that should be excluded. It should be
     * basically the same implementation of `isExcludedNode`, but as a selector.
     *
     * @type {string}
     */
    this.excludedNodeSelector = [
      // Use: [lang|=value] to match language codes.
      //
      // Per: https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors
      //
      // The elements with an attribute name of attr whose value can be exactly
      // value or can begin with value immediately followed by a hyphen, - (U+002D).
      // It is often used for language subcode matches.
      `[lang]:not([lang|="${this.documentLanguage}"])`,
      `[translate=no]`,
      `.notranslate`,
      `[contenteditable="true"]`,
      `[contenteditable=""]`,
      [...EXCLUDED_TAGS].join(","),
    ].join(",");

    this.observer = new document.ownerGlobal.MutationObserver(mutationsList => {
      for (const mutation of mutationsList) {
        switch (mutation.type) {
          case "childList":
            for (const node of mutation.addedNodes) {
              this.#processedNodes.delete(node);
              this.subdivideNodeForTranslations(node);
              if (node.nodeType === Node.ELEMENT_NODE) {
                this.translateAttributes(node);
              }
            }
            break;
          case "characterData":
            this.#processedNodes.delete(mutation);
            this.subdivideNodeForTranslations(mutation.target);
            break;
          case "attributes":
            if (
              isAttributeTranslatable(mutation.target, mutation.attributeName)
            ) {
              this.queueAttributeNodeForTranslation(mutation.target, [
                mutation.attributeName,
              ]);
              this.dispatchQueuedAttributeTranslations();
            }
            break;
          default:
            break;
        }
      }
    });

    this.document.addEventListener(
      "visibilitychange",
      this.handleVisibilityChange
    );

    const addRootElements = () => {
      this.addRootElement(document.querySelector("title"));
      this.addRootElement(document.body, true /* reportWordsInViewport */);
    };

    if (document.body) {
      addRootElements();
    } else {
      // The TranslationsDocument was invoked before the DOM was ready, wait for
      // it to be loaded.
      document.addEventListener("DOMContentLoaded", addRootElements);
    }

    this.viewportTranslated?.then(() => {
      ChromeUtils.addProfilerMarker(
        "TranslationsChild",
        { innerWindowId, startTime: now() },
        "Viewport translations"
      );
      ChromeUtils.addProfilerMarker(
        "TranslationsChild",
        { innerWindowId, startTime: translationsStart },
        "Time to first translation"
      );
    });

    document.documentElement.lang = toLanguage;

    lazy.console.log(
      "Beginning to translate.",
      // The defaultView may not be there on tests.
      document.defaultView?.location.href
    );
  }

  /**
   * Queue a node for translation of attributes.
   *
   * @param {Node} node
   * @param {Array<string>} attributeList
   */
  queueAttributeNodeForTranslation(node, attributeList) {
    /** @type {NodeVisibility} */
    let visibility = "out-of-viewport";
    if (isNodeHidden(node)) {
      visibility = "hidden";
    } else if (isNodeInViewport(node)) {
      visibility = "in-viewport";
    }
    this.#queuedAttributeNodes.set(node, { attributeList, visibility });
  }

  /**
   * Start and stop the translator as the page is shown. For instance, this will
   * transition into "hidden" when the user tabs away from a document.
   */
  handleVisibilityChange = () => {
    if (this.document.visibilityState === "visible") {
      this.translator.showPage();
    } else {
      ChromeUtils.addProfilerMarker(
        "Translations",
        { innerWindowId: this.innerWindowId },
        "Pausing translations and discarding the port"
      );
      this.translator.hidePage();
    }
  };

  /**
   * Remove any dangling event handlers.
   */
  destroy() {
    this.isDestroyed = true;
    this.translator.destroy();
    this.stopMutationObserver();
    this.document.removeEventListener(
      "visibilitychange",
      this.handleVisibilityChange
    );
  }

  /**
   * Helper function for adding a new root to the mutation
   * observer.
   *
   * @param {Node} root
   */
  observeNewRoot(root) {
    this.#rootNodes.add(root);
    this.observer.observe(root, MUTATION_OBSERVER_OPTIONS);
  }

  /**
   * This function finds all sub shadow trees of node and
   * add the ShadowRoot of those subtrees to the mutation
   * observer.
   */
  addShadowRootsToObserver(node) {
    const nodeIterator = node.ownerDocument.createTreeWalker(
      node,
      NodeFilter.SHOW_ELEMENT,
      function (node) {
        return node.openOrClosedShadowRoot
          ? NodeFilter.FILTER_ACCEPT
          : NodeFilter.FILTER_SKIP;
      }
    );
    let currentNode;
    while ((currentNode = nodeIterator.nextNode())) {
      // Only shadow hosts are accepted nodes
      const shadowRoot = currentNode.openOrClosedShadowRoot;
      this.observeNewRoot(shadowRoot);
      this.addShadowRootsToObserver(shadowRoot);
    }
  }

  /**
   * Add a new element to start translating. This root is tracked for mutations and
   * kept up to date with translations. This will be the body element and title tag
   * for the document.
   *
   * @param {Element} [node]
   */
  addRootElement(node) {
    if (!node) {
      return;
    }

    if (node.nodeType !== Node.ELEMENT_NODE) {
      // This node is not an element, do not add it.
      return;
    }

    if (this.#rootNodes.has(node)) {
      // Exclude nodes that are already targetted.
      return;
    }

    this.#rootNodes.add(node);

    let viewportNodeTranslations = this.subdivideNodeForTranslations(node);
    let viewportAttributeTranslations = this.translateAttributes(node);

    if (!this.viewportTranslated) {
      this.viewportTranslated = Promise.allSettled([
        ...(viewportNodeTranslations ?? []),
        ...(viewportAttributeTranslations ?? []),
      ]);
    }

    this.observer.observe(node, MUTATION_OBSERVER_OPTIONS);
    this.addShadowRootsToObserver(node);
  }

  /**
   * Add qualified nodes to queueNodeForTranslation by recursively walk
   * through the DOM tree of node, including elements in Shadow DOM.
   *
   * @param {Element} [node]
   */
  processSubdivide(node) {
    const nodeIterator = node.ownerDocument.createTreeWalker(
      node,
      NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT,
      this.determineTranslationStatusForUnprocessedNodes
    );

    // This iterator will contain each node that has been subdivided enough to
    // be translated.
    let currentNode;
    while ((currentNode = nodeIterator.nextNode())) {
      const shadowRoot = currentNode.openOrClosedShadowRoot;
      if (shadowRoot) {
        this.processSubdivide(shadowRoot);
      } else {
        this.queueNodeForTranslation(currentNode);
      }
    }
  }

  /**
   * Start walking down through a node's subtree and decide which nodes to queue for
   * translation. This first node could be the root nodes of the DOM, such as the
   * document body, or the title element, or it could be a mutation target.
   *
   * The nodes go through a process of subdivision until an appropriate sized chunk
   * of inline text can be found.
   *
   * @param {Node} node
   */
  subdivideNodeForTranslations(node) {
    if (!this.#rootNodes.has(node)) {
      // This is a non-root node, which means it came from a mutation observer.
      // This new node could be a host element for shadow tree
      const shadowRoot = node.openOrClosedShadowRoot;
      if (shadowRoot && !this.#rootNodes.has(shadowRoot)) {
        this.observeNewRoot(shadowRoot);
      } else {
        // Ensure that it is a valid node to translate by checking all of its ancestors.
        for (let parent of getAncestorsIterator(node)) {
          // Parent is ShadowRoot. We can stop here since this is
          // the top ancestor of the shadow tree.
          if (parent.containingShadowRoot == parent) {
            break;
          }
          if (
            this.determineTranslationStatus(parent) ===
            NodeStatus.NOT_TRANSLATABLE
          ) {
            return;
          }
        }
      }
    }

    switch (this.determineTranslationStatusForUnprocessedNodes(node)) {
      case NodeStatus.NOT_TRANSLATABLE:
        // This node is rejected as it shouldn't be translated.
        return;

      // SHADOW_HOST and READY_TO_TRANSLATE both map to FILTER_ACCEPT
      case NodeStatus.SHADOW_HOST:
      case NodeStatus.READY_TO_TRANSLATE: {
        const shadowRoot = node.openOrClosedShadowRoot;
        if (shadowRoot) {
          this.processSubdivide(shadowRoot);
        } else {
          // This node is ready for translating, and doesn't need to be subdivided. There
          // is no reason to run the TreeWalker, it can be directly submitted for
          // translation.
          this.queueNodeForTranslation(node);
        }
        break;
      }

      case NodeStatus.SUBDIVIDE_FURTHER:
        // This node may be translatable, but it needs to be subdivided into smaller
        // pieces. Create a TreeWalker to walk the subtree, and find the subtrees/nodes
        // that contain enough inline elements to send to be translated.
        this.processSubdivide(node);
        break;
    }

    if (node.nodeName === "BODY") {
      this.reportWordsInViewport();
    }
    this.dispatchQueuedTranslations();
  }

  /**
   * Get all the nodes which have selected attributes
   * from the node/document and queue them.
   * Call the translate function on these nodes
   *
   * @param {Node} node
   * @returns {Array<Promise<void>> | null}
   */
  translateAttributes(node) {
    const attributeList = getTranslatableAttributes(node);
    if (attributeList.length) {
      // Queue the root node if it has any attributes
      // Because querySelectorAll searches only child nodes.
      this.queueAttributeNodeForTranslation(node, attributeList);
    }
    // Get all attributes in child nodes at once
    const nodesWithTranslatableAttributes = node.querySelectorAll(
      TRANSLATABLE_ATTRIBUTES_SELECTOR
    );
    for (const node of nodesWithTranslatableAttributes) {
      const attributeList = getTranslatableAttributes(node);
      this.queueAttributeNodeForTranslation(node, attributeList);
    }
    return this.dispatchQueuedAttributeTranslations();
  }

  /**
   * Test whether this is an element we do not want to translate. These are things like
   * <code> elements, elements with a different "lang" attribute, and elements that
   * have a `translate=no` attribute.
   *
   * @param {Node} node
   */
  isExcludedNode(node) {
    // Property access be expensive, so destructure required properties so they are
    // not accessed multiple times.
    const { nodeType } = node;

    if (nodeType === Node.TEXT_NODE) {
      // Text nodes are never excluded.
      return false;
    }
    if (nodeType !== Node.ELEMENT_NODE) {
      // Only elements and and text nodes should be considered.
      return true;
    }

    const { nodeName } = node;

    if (
      EXCLUDED_TAGS.has(
        // SVG tags can be lowercased, so ensure everything is uppercased.
        nodeName.toUpperCase()
      )
    ) {
      // This is an excluded tag.
      return true;
    }

    if (!this.matchesDocumentLanguage(node)) {
      // Exclude nodes that don't match the fromLanguage.
      return true;
    }

    if (node.getAttribute("translate") === "no") {
      // This element has a translate="no" attribute.
      // https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/translate
      return true;
    }

    if (node.classList.contains("notranslate")) {
      // Google Translate skips translations if the classList contains "notranslate"
      // https://cloud.google.com/translate/troubleshooting
      return true;
    }

    if (node.isContentEditable) {
      // This field is editable, and so exclude it similar to the way that form input
      // fields are excluded.
      return true;
    }

    return false;
  }

  /**
   * Runs `determineTranslationStatus`, but only on unprocessed nodes.
   *
   * @param {Node} node
   * @returns {number} - One of the NodeStatus values.
   */
  determineTranslationStatusForUnprocessedNodes = node => {
    if (this.#processedNodes.has(node)) {
      // Skip nodes that have already been processed.
      return NodeStatus.NOT_TRANSLATABLE;
    }

    return this.determineTranslationStatus(node);
  };

  /**
   * Determines if a node should be submitted for translation, not translatable, or if
   * it should be subdivided further. It doesn't check if the node has already been
   * processed.
   *
   * The return result works as a TreeWalker NodeFilter as well.
   *
   * @param {Node} node
   * @returns {number} - One of the `NodeStatus` values. See that object
   *   for documentation. These values match the filters for the TreeWalker.
   *   These values also work as a `NodeFilter` value.
   */
  determineTranslationStatus(node) {
    if (node.openOrClosedShadowRoot) {
      return NodeStatus.SHADOW_HOST;
    }

    if (isNodeQueued(node, this.#queuedNodes)) {
      // This node or its parent was already queued, reject it.
      return NodeStatus.NOT_TRANSLATABLE;
    }

    if (this.isExcludedNode(node)) {
      // This is an explicitly excluded node.
      return NodeStatus.NOT_TRANSLATABLE;
    }

    if (node.textContent.trim().length === 0) {
      // Do not use subtrees that are empty of text. This textContent call is fairly
      // expensive.
      return !node.hasChildNodes()
        ? NodeStatus.NOT_TRANSLATABLE
        : NodeStatus.SUBDIVIDE_FURTHER;
    }

    if (nodeNeedsSubdividing(node)) {
      // Skip this node, and dig deeper into its tree to cut off smaller pieces
      // to translate. It is presumed to be a wrapper of block elements.
      return NodeStatus.SUBDIVIDE_FURTHER;
    }

    if (
      containsExcludedNode(node, this.excludedNodeSelector) &&
      !hasTextNodes(node)
    ) {
      // Skip this node, and dig deeper into its tree to cut off smaller pieces
      // to translate.
      return NodeStatus.SUBDIVIDE_FURTHER;
    }

    // This node can be treated as entire block to submit for translation.
    return NodeStatus.READY_TO_TRANSLATE;
  }

  /**
   * Queue a node for translation.
   *
   * @param {Node} node
   */
  queueNodeForTranslation(node) {
    /** @type {NodeVisibility} */
    let visibility = "out-of-viewport";
    if (isNodeHidden(node)) {
      visibility = "hidden";
    } else if (isNodeInViewport(node)) {
      visibility = "in-viewport";
    }

    this.#queuedNodes.set(node, visibility);
  }

  /**
   * Submit the translations giving priority to nodes in the viewport.
   *
   * @returns {Array<Promise<void>> | null}
   */
  dispatchQueuedTranslations() {
    let inViewportCounts = 0;
    let outOfViewportCounts = 0;
    let hiddenCounts = 0;

    let inViewportTranslations = null;
    if (!this.viewportTranslated) {
      inViewportTranslations = [];
    }

    for (const [node, visibility] of this.#queuedNodes) {
      if (visibility === "in-viewport") {
        inViewportCounts++;
        const promise = this.submitTranslation(node);
        if (inViewportTranslations) {
          inViewportTranslations.push(promise);
        }
      }
    }
    for (const [node, visibility] of this.#queuedNodes) {
      if (visibility === "out-of-viewport") {
        outOfViewportCounts++;
        this.submitTranslation(node);
      }
    }
    for (const [node, visibility] of this.#queuedNodes) {
      if (visibility === "hidden") {
        hiddenCounts++;
        this.submitTranslation(node);
      }
    }

    ChromeUtils.addProfilerMarker(
      "Translations",
      { innerWindowId: this.innerWindowId },
      `Translate ${this.#queuedNodes.size} nodes.\n\n` +
        `In viewport: ${inViewportCounts}\n` +
        `Out of viewport: ${outOfViewportCounts}\n` +
        `Hidden: ${hiddenCounts}\n`
    );

    this.#queuedNodes.clear();
    return inViewportTranslations;
  }

  /**
   * Submit the Attribute translations giving priority to nodes in the viewport.
   *
   * @returns {Array<Promise<void>> | null}
   */
  dispatchQueuedAttributeTranslations() {
    let inViewportCounts = 0;
    let outOfViewportCounts = 0;
    let hiddenCounts = 0;

    let inViewportTranslations = null;
    if (!this.viewportTranslated) {
      inViewportTranslations = [];
    }
    // Submit the nodes with attrbutes to be translated.
    for (const [node, { attributeList, visibility }] of this
      .#queuedAttributeNodes) {
      if (visibility === "in-viewport") {
        inViewportCounts++;
        const promise = this.submitAttributeTranslation(node, attributeList);
        if (inViewportTranslations) {
          inViewportTranslations.push(promise);
        }
      }
    }
    for (const [node, { attributeList, visibility }] of this
      .#queuedAttributeNodes) {
      if (visibility === "out-of-viewport") {
        outOfViewportCounts++;
        this.submitAttributeTranslation(node, attributeList);
      }
    }
    for (const [node, { attributeList, visibility }] of this
      .#queuedAttributeNodes) {
      if (visibility === "hidden") {
        hiddenCounts++;
        this.submitAttributeTranslation(node, attributeList);
      }
    }

    ChromeUtils.addProfilerMarker(
      "Attribute Translations",
      { innerWindowId: this.innerWindowId },
      `Attribute Translate ${this.#queuedAttributeNodes.size} nodes.\n\n` +
        `In viewport: ${inViewportCounts}\n` +
        `Out of viewport: ${outOfViewportCounts}\n` +
        `Hidden: ${hiddenCounts}\n`
    );

    this.#queuedAttributeNodes.clear();

    return inViewportTranslations;
  }

  /**
   * Submit a node for Attribute translation to the translations engine.
   *
   * @param {Node} node
   * @returns {Promise<void>}
   */
  async submitAttributeTranslation(node, attributeList) {
    if (node.nodeType === Node.ELEMENT_NODE) {
      for (const attribute of attributeList) {
        const text = node.getAttribute(attribute);

        if (text.trim().length === 0) {
          continue;
        }
        const translation = await this.maybeTranslate(
          node,
          text,
          false /*isHTML*/
        );
        if (translation != null) {
          this.scheduleNodeUpdateWithTranslationAttribute(
            node,
            translation,
            attribute
          );
        }
      }
    }
  }

  /**
   * Schedule a node to be updated with a translation.
   *
   * @param {Node} node
   * @param {string} translation
   */
  scheduleNodeUpdateWithTranslationAttribute(node, translation, attribute) {
    // Add the nodes to be populated with the next translation update.
    this.#nodesWithTranslatedAttributes.add({
      node,
      translation,
      attribute,
    });

    if (this.#pendingTranslationsCount === 0) {
      // No translations are pending, update the node.
      this.updateNodesWithTranslationsAttributes();
    } else if (!this.#attributeUpdateTimeout) {
      // Schedule an update.
      this.#attributeUpdateTimeout = lazy.setTimeout(
        this.updateNodesWithTranslationsAttributes.bind(this),
        DOM_UPDATE_INTERVAL_MS
      );
    } else {
      // An update has been previously scheduled, do nothing here.
    }
  }

  /**
   * This is called every `DOM_UPDATE_INTERVAL_MS` ms with translations
   * for attributes in the nodes.
   *
   * This function is called asynchronously, so nodes may already be dead. Before
   * accessing a node make sure and run `Cu.isDeadWrapper` to check that it is alive.
   */
  updateNodesWithTranslationsAttributes() {
    // Stop the mutations so that the updates won't trigger observations.

    this.pauseMutationObserverAndRun(() => {
      for (const { node, translation, attribute } of this
        .#nodesWithTranslatedAttributes) {
        if (Cu.isDeadWrapper(node)) {
          // The node is no longer alive.
          ChromeUtils.addProfilerMarker(
            "Translations",
            { innerWindowId: this.innerWindowId },
            "Node is no long alive."
          );
          continue;
        }
        // Update the attribute of the node with translated attribute
        if (attribute) {
          node.setAttribute(attribute, translation);
        }
      }
      this.#nodesWithTranslatedAttributes.clear();
      this.#attributeUpdateTimeout = null;
    });
  }

  /**
   * Record how many words were in the viewport, as this is the most important
   * user-visible translation content.
   */
  reportWordsInViewport() {
    if (
      // This promise gets created for the first dispatchQueuedTranslations
      this.viewportTranslated ||
      this.#queuedNodes.size === 0
    ) {
      return;
    }

    // TODO(Bug 1814195) - Add telemetry.
    // TODO(Bug 1820618) - This whitespace regex will not work in CJK-like languages.
    // This requires a segmenter for a proper implementation.

    const whitespace = /\s+/;
    let wordCount = 0;
    for (const [node, visibility] of this.#queuedNodes) {
      if (visibility === "in-viewport") {
        wordCount += node.textContent.trim().split(whitespace).length;
      }
    }

    const message = wordCount + " words are in the viewport.";
    lazy.console.log(message);
    ChromeUtils.addProfilerMarker(
      "Translations",
      { innerWindowId: this.innerWindowId },
      message
    );
  }

  /**
   * Submit a node for translation to the translations engine.
   *
   * @param {Node} node
   * @returns {Promise<void>}
   */
  async submitTranslation(node) {
    // Give each element an id that gets passed through the translation so it can be
    // reunited later on.
    if (node.nodeType === Node.ELEMENT_NODE) {
      node.querySelectorAll("*").forEach((el, i) => {
        el.dataset.mozTranslationsId = i;
      });
    }

    /** @type {string} */
    let text;
    /** @type {boolean} */
    let isHTML;

    if (node.nodeType === Node.ELEMENT_NODE) {
      text = node.innerHTML;
      isHTML = true;
    } else {
      text = node.textContent;
      isHTML = false;
    }

    if (text.trim().length === 0) {
      return;
    }

    // Mark this node as not to be translated again unless the contents are changed
    // (which the observer will pick up on)
    this.#processedNodes.add(node);
    const translatedHTML = await this.maybeTranslate(node, text, isHTML);
    if (translatedHTML != null) {
      this.scheduleNodeUpdateWithTranslation(node, translatedHTML);
    }
  }

  /**
   * A single function to update pendingTranslationsCount while
   * calling the translate function
   *
   * @param {Node} node
   * @param {string} text
   * @property {boolean} isHTML
   * @returns {Promise<string | null>}
   */
  async maybeTranslate(node, text, isHTML) {
    this.#pendingTranslationsCount++;
    try {
      let translation = this.translationsCache.get(text, isHTML);
      if (translation === undefined) {
        translation = await this.translator.translate(node, text, isHTML);
        this.translationsCache.set(text, translation, isHTML);
      } else if (!this.hasFirstVisibleChange) {
        this.hasFirstVisibleChange = true;
        this.actorReportFirstVisibleChange();
      }
      return translation;
    } catch (error) {
      lazy.console.log("Translation failed", error);
    } finally {
      this.#pendingTranslationsCount--;
    }
    return null;
  }

  /**
   * Start the mutation observer, for instance after applying the translations to the DOM.
   */
  startMutationObserver() {
    if (Cu.isDeadWrapper(this.observer)) {
      // This observer is no longer alive.
      return;
    }
    for (const node of this.#rootNodes) {
      if (Cu.isDeadWrapper(node)) {
        // This node is no longer alive.
        continue;
      }
      this.observer.observe(node, MUTATION_OBSERVER_OPTIONS);
    }
  }

  /**
   * Stop the mutation observer, for instance to apply the translations to the DOM.
   */
  stopMutationObserver() {
    // Was the window already destroyed?
    if (!Cu.isDeadWrapper(this.observer)) {
      this.observer.disconnect();
    }
  }

  /**
   * This is called every `DOM_UPDATE_INTERVAL_MS` ms with translations for nodes.
   *
   * This function is called asynchronously, so nodes may already be dead. Before
   * accessing a node make sure and run `Cu.isDeadWrapper` to check that it is alive.
   */
  updateNodesWithTranslations() {
    // Stop the mutations so that the updates won't trigger observations.
    this.pauseMutationObserverAndRun(() => {
      for (const { node, translatedHTML } of this.#nodesWithTranslatedHTML) {
        if (Cu.isDeadWrapper(node)) {
          // The node is no longer alive.
          ChromeUtils.addProfilerMarker(
            "Translations",
            { innerWindowId: this.innerWindowId },
            "Node is no long alive."
          );
          continue;
        }
        switch (node.nodeType) {
          case Node.TEXT_NODE: {
            if (translatedHTML.trim().length !== 0) {
              // Only update the node if there is new text.
              node.textContent = translatedHTML;
            }
            break;
          }
          case Node.ELEMENT_NODE: {
            // TODO (Bug 1820625) - This is slow compared to the original implementation
            // in the addon which set the innerHTML directly. We can't set the innerHTML
            // here, but perhaps there is another way to get back some of the performance.
            const translationsDocument = this.domParser.parseFromString(
              `<!DOCTYPE html><div>${translatedHTML}</div>`,
              "text/html"
            );
            updateElement(translationsDocument, node);
            break;
          }
        }
      }

      this.#nodesWithTranslatedHTML.clear();
      this.#updateTimeout = null;
    });
  }

  /**
   * Stop the mutations so that the updates of the translations
   * in the nodes won't trigger observations.
   *
   * @param {Function} run The function to update translations
   */
  pauseMutationObserverAndRun(run) {
    this.stopMutationObserver();
    run();
    this.startMutationObserver();
  }

  /**
   * Schedule a node to be updated with a translation.
   *
   * @param {Node} node
   * @param {string} translatedHTML
   */
  scheduleNodeUpdateWithTranslation(node, translatedHTML) {
    // Add the nodes to be populated with the next translation update.
    this.#nodesWithTranslatedHTML.add({ node, translatedHTML });

    if (this.#pendingTranslationsCount === 0) {
      // No translations are pending, update the node.
      this.updateNodesWithTranslations();
    } else if (!this.#updateTimeout) {
      // Schedule an update.
      this.#updateTimeout = lazy.setTimeout(
        this.updateNodesWithTranslations.bind(this),
        DOM_UPDATE_INTERVAL_MS
      );
    } else {
      // An update has been previously scheduled, do nothing here.
    }
  }

  /**
   * Check to see if a language matches the document language.
   *
   * @param {Node} node
   */
  matchesDocumentLanguage(node) {
    if (!node.lang) {
      // No `lang` was present, so assume it matches the language.
      return true;
    }

    // First, cheaply check if language tags match, without canonicalizing.
    if (langTagsMatch(this.documentLanguage, node.lang)) {
      return true;
    }

    try {
      // Make sure the local is in the canonical form, and check again. This function
      // throws, so don't trust that the language tags are formatting correctly.
      const [language] = Intl.getCanonicalLocales(node.lang);

      return langTagsMatch(this.documentLanguage, language);
    } catch (_error) {
      return false;
    }
  }
}

/**
 * Get the list of attributes that need to be translated
 * in a given node.
 *
 * @returns {Array<string>}
 */
function getTranslatableAttributes(node) {
  if (node.nodeType !== Node.ELEMENT_NODE) {
    return [];
  }
  const attributes = [];
  for (const [tagName, attribute] of TRANSLATABLE_ATTRIBUTES) {
    if (tagName && node.tagName !== tagName) {
      // The tagName does not match.
      continue;
    }
    if (node.hasAttribute(attribute)) {
      attributes.push(attribute);
    }
  }
  return attributes;
}

/**
 * This function needs to be fairly fast since it's used on many nodes when iterating
 * over the DOM to find nodes to translate.
 *
 * @param {Text | HTMLElement} node
 */
function isNodeHidden(node) {
  /** @type {HTMLElement} */
  const element = getElementForStyle(node);
  if (!element) {
    throw new Error("Unable to find the Element to compute the style for node");
  }

  // This flushes the style, which is a performance cost.
  const style = element.ownerGlobal.getComputedStyle(element);
  return style.display === "none" || style.visibility === "hidden";
}

/**
 * This function cheaply checks that language tags match.
 *
 * @param {string} knownLanguage
 * @param {string} otherLanguage
 */
function langTagsMatch(knownLanguage, otherLanguage) {
  if (knownLanguage === otherLanguage) {
    // A simple direct match.
    return true;
  }
  if (knownLanguage.length !== 2) {
    throw new Error("Expected the knownLanguage to be of length 2.");
  }
  // Check if the language tags part match, e.g. "en" and "en-US".
  return (
    knownLanguage[0] === otherLanguage[0] &&
    knownLanguage[1] === otherLanguage[1] &&
    otherLanguage[2] === "-"
  );
}

/**
 * This function returns the correct element to determine the
 * style of node.
 *
 * @param {Node} node
  @returns {HTMLElement} */
function getElementForStyle(node) {
  if (node.nodeType != Node.TEXT_NODE) {
    return node;
  }

  if (node.parentElement) {
    return node.parentElement;
  }

  // For cases like text node where its parent is ShadowRoot,
  // we'd like to use flattenedTreeParentNode
  if (node.flattenedTreeParentNode) {
    return node.flattenedTreeParentNode;
  }

  // If the text node is not connected or doesn't have a frame.
  return null;
}

/**
 * This function runs when walking the DOM, which means it is a hot function. It runs
 * fairly fast even though it is computing the bounding box. This is all done in a tight
 * loop, and it is done on mutations. Care should be taken with reflows caused by
 * getBoundingClientRect, as this is a common performance issue.
 *
 * The following are the counts of how often this is run on a news site:
 *
 * Given:
 *  1573 DOM nodes
 *  504 Text nodes
 *  1069 Elements
 *
 * There were:
 *  209 calls to get this funcion.
 *
 * @param {Node} node
 */
function isNodeInViewport(node) {
  const window = node.ownerGlobal;
  const document = node.ownerDocument;

  /** @type {HTMLElement} */
  const element = getElementForStyle(node);
  if (!element) {
    throw new Error("Unable to find the Element to compute the style for node");
  }

  const rect = element.getBoundingClientRect();
  return (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <=
      (window.innerHeight || document.documentElement.clientHeight) &&
    rect.right <= (window.innerWidth || document.documentElement.clientWidth)
  );
}

/**
 * Actually perform the update of the element with the translated node. This step
 * will detach all of the "live" nodes, and match them up in the correct order as provided
 * by the translations engine.
 *
 * @param {Document} translationsDocument
 * @param {Element} element
 * @returns {void}
 */
function updateElement(translationsDocument, element) {
  // This text should have the same layout as the target, but it's not completely
  // guaranteed since the content page could change at any time, and the translation process is async.
  //
  // The document has the following structure:
  //
  // <html>
  //   <head>
  //   <body>{translated content}</body>
  // </html>

  const originalHTML = element.innerHTML;

  /**
   * The Set of translation IDs for nodes that have been cloned.
   *
   * @type {Set<number>}
   */
  const clonedNodes = new Set();

  merge(element, translationsDocument.body.firstChild);

  /**
   * Merge the live tree with the translated tree by re-using elements from the live tree.
   *
   * @param {Node} liveTree
   * @param {Node} translatedTree
   */
  function merge(liveTree, translatedTree) {
    /** @type {Map<number, Element>} */
    const liveElementsById = new Map();

    /** @type {Array<Text>} */
    const liveTextNodes = [];

    // Remove all the nodes from the liveTree, and categorize them by Text node or
    // Element node.
    let node;
    while ((node = liveTree.firstChild)) {
      node.remove();

      if (node.nodeType === Node.ELEMENT_NODE) {
        liveElementsById.set(node.dataset.mozTranslationsId, node);
      } else if (node.nodeType === Node.TEXT_NODE) {
        liveTextNodes.push(node);
      }
    }

    // The translated tree dictates the order.
    const translatedNodes = [...translatedTree.childNodes];
    for (
      let translatedIndex = 0;
      translatedIndex < translatedNodes.length;
      translatedIndex++
    ) {
      const translatedNode = translatedNodes[translatedIndex];

      if (translatedNode.nodeType === Node.TEXT_NODE) {
        // Copy the translated text to the original Text node and re-append it.
        let liveTextNode = liveTextNodes.shift();

        if (liveTextNode) {
          liveTextNode.data = translatedNode.data;
        } else {
          liveTextNode = translatedNode;
        }

        liveTree.appendChild(liveTextNode);
      } else if (translatedNode.nodeType === Node.ELEMENT_NODE) {
        const translationsId = translatedNode.dataset.mozTranslationsId;
        // Element nodes try to use the already existing DOM nodes.

        // Find the element in the live tree that matches the one in the translated tree.
        let liveElement = liveElementsById.get(translationsId);

        if (!liveElement) {
          lazy.console.warn("Could not find a corresponding live element", {
            path: createNodePath(translatedNode, translationsDocument.body),
            translationsId,
            liveElementsById,
            translatedNode,
          });
          continue;
        }

        // Has this element already been added to the list? Then duplicate it and re-add
        // it as a clone. The Translations Engine can sometimes duplicate HTML.
        if (liveElement.parentNode) {
          liveElement = liveElement.cloneNode(true /* deep clone */);
          clonedNodes.add(translationsId);
          lazy.console.warn(
            "Cloning a node because it was already inserted earlier",
            {
              path: createNodePath(translatedNode, translationsDocument.body),
              translatedNode,
              liveElement,
            }
          );
        }

        if (isNodeTextEmpty(translatedNode)) {
          // The original node had text, but the one that came out of translation
          // didn't have any text. This scenario might be caused by one of two causes:
          //
          //   1) The element was duplicated by translation but then not given text
          //      content. This happens on Wikipedia articles for example.
          //
          //   2) The translator messed up and could not translate the text. This
          //      happens on YouTube in the language selector. In that case, having the
          //      original text is much better than no text at all.
          //
          // To make sure it is case 1 and not case 2 check whether this is the only occurrence.
          for (let i = 0; i < translatedNodes.length; i++) {
            if (translatedIndex === i) {
              // This is the current node, not a sibling.
              continue;
            }
            const sibling = translatedNodes[i];
            if (
              // Only consider other element nodes.
              sibling.nodeType === Node.ELEMENT_NODE &&
              // If the sibling's translationsId matches, then use the sibling's
              // node instead.
              translationsId === sibling.dataset.mozTranslationsId
            ) {
              // This is case 1 from above. Remove this element's original text nodes,
              // since a sibling text node now has all of the text nodes.
              removeTextNodes(liveElement);
            }
          }

          // Report this issue to the console.
          lazy.console.warn(
            "The translated element has no text even though the original did.",
            {
              path: createNodePath(translatedNode, translationsDocument.body),
              translatedNode,
              liveElement,
            }
          );
        } else if (!isNodeTextEmpty(liveElement)) {
          // There are still text nodes to find and update, recursively merge.
          merge(liveElement, translatedNode);
        }

        // Put the live node back in the live branch. But now t has been synced with the
        // translated text and order.
        liveTree.appendChild(liveElement);
      }
    }

    const unhandledElements = [...liveElementsById].filter(
      ([, element]) => !element.parentNode
    );

    for (node of liveTree.querySelectorAll("*")) {
      // Clean-up the translation ids.
      delete node.dataset.mozTranslationsId;
    }

    if (unhandledElements.length) {
      lazy.console.warn(
        `${createNodePath(
          translatedTree,
          translationsDocument.body
        )} Not all nodes unified`,
        {
          unhandledElements,
          clonedNodes,
          originalHTML,
          translatedHTML: translationsDocument.body.innerHTML,
          liveTree: liveTree.outerHTML,
          translatedTree: translatedTree.outerHTML,
        }
      );
    }
  }
}

/**
 * For debug purposes, compute a string path to an element.
 *
 * e.g. "div/div#header/p.bold.string/a"
 *
 * @param {Node} node
 * @param {Node | null} root
 */
function createNodePath(node, root) {
  if (root === null) {
    root = node.ownerDocument.body;
  }
  let path =
    node.parentNode && node.parentNode !== root
      ? createNodePath(node.parentNode)
      : "";
  path += `/${node.nodeName}`;
  if (node.id) {
    path += `#${node.id}`;
  } else if (node.className) {
    for (const className of node.classList) {
      path += "." + className;
    }
  }
  return path;
}

/**
 * @param {Node} node
 * @returns {boolean}
 */
function isNodeTextEmpty(node) {
  if ("innerText" in node) {
    return node.innerText.trim().length === 0;
  }
  if (node.nodeType === Node.TEXT_NODE && node.nodeValue) {
    return node.nodeValue.trim().length === 0;
  }
  return true;
}

/**
 * @param {Node} node
 */
function removeTextNodes(node) {
  for (const child of node.childNodes) {
    switch (child.nodeType) {
      case Node.TEXT_NODE:
        node.removeChild(child);
        break;
      case Node.ELEMENT_NODE:
        removeTextNodes(child);
        break;
      default:
        break;
    }
  }
}

/**
 * Test whether any of the direct child text nodes of are non-whitespace
 * text nodes.
 *
 * For example:
 *   - `<p>test</p>`: yes
 *   - `<p> </p>`: no
 *   - `<p><b>test</b></p>`: no
 *
 * @param {Node} node
 * @returns {boolean}
 */
function hasTextNodes(node) {
  if (node.nodeType !== Node.ELEMENT_NODE) {
    // Only check element nodes.
    return false;
  }

  for (const child of node.childNodes) {
    if (child.nodeType === Node.TEXT_NODE) {
      if (child.textContent.trim() === "") {
        // This is just whitespace.
        continue;
      }
      // A text node with content was found.
      return true;
    }
  }

  // No text nodes were found.
  return false;
}

/**
 * Like `isExcludedNode` but looks at the full subtree. Used to see whether
 * we can submit a subtree, or whether we should split it into smaller
 * branches first to try to exclude more of the non-translatable content.
 *
 * @param {Node} node
 * @param {string} excludedNodeSelector
 * @returns {boolean}
 */
function containsExcludedNode(node, excludedNodeSelector) {
  return (
    node.nodeType === Node.ELEMENT_NODE &&
    node.querySelector(excludedNodeSelector)
  );
}

/**
 * Check if this node has already been queued to be translated. This can be because
 * the node is itself is queued, or its parent node is queued.
 *
 * @param {Node} node
 * @param {Map<Node, any>} queuedNodes
 * @returns {boolean}
 */
function isNodeQueued(node, queuedNodes) {
  if (queuedNodes.has(node)) {
    return true;
  }

  // If the immediate parent is the body, it is allowed.
  if (node.parentNode === node.ownerDocument.body) {
    return false;
  }

  // Accessing the parentNode is expensive here according to performance profilling. This
  // is due to XrayWrappers. Minimize reading attributes by storing a reference to the
  // `parentNode` in a named variable, rather than re-accessing it.
  let parentNode;
  let lastNode = node;
  while ((parentNode = lastNode.parentNode)) {
    if (queuedNodes.has(parentNode)) {
      return parentNode;
    }
    lastNode = parentNode;
  }

  return false;
}

/**
 * Reads the elements computed style and determines if the element is a block-like
 * element or not. Every element that lays out like a block should be sent in as one
 * cohesive unit to be translated.
 *
 * @param {Element} element
 */
function getIsBlockLike(element) {
  const win = element.ownerGlobal;
  if (element.namespaceURI === "http://www.w3.org/2000/svg") {
    // SVG elements will report as inline, but there is no block layout in SVG.
    // Treat every SVG element as being block so that every node will be subdivided.
    return true;
  }
  const { display } = win.getComputedStyle(element);
  return display !== "inline" && display !== "none";
}

/**
 * Determine if this element is an inline element or a block element. Inline elements
 * should be sent as a contiguous chunk of text, while block elements should be further
 * subdivided before sending them in for translation.
 *
 * @param {Node} node
 * @returns {boolean}
 */
function nodeNeedsSubdividing(node) {
  if (node.nodeType === Node.TEXT_NODE) {
    // Text nodes are fully subdivided.
    return false;
  }

  if (!getIsBlockLike(node)) {
    // This element is inline, or not displayed.
    return false;
  }

  for (let child of node.childNodes) {
    switch (child.nodeType) {
      case Node.TEXT_NODE:
        // Keep checking for more inline or text nodes.
        continue;
      case Node.ELEMENT_NODE: {
        if (getIsBlockLike(child)) {
          // This node is a block node, so it needs further subdividing.
          return true;
        }
        // Keep checking for more inline or text nodes.
        continue;
      }
      default:
        return true;
    }
  }
  return false;
}

/**
 * Returns an iterator of a node's ancestors.
 *
 * @param {Node} node
 * @returns {Generator<ParentNode>}
 */
function* getAncestorsIterator(node) {
  const document = node.ownerDocument;
  for (
    let parent = node.parentNode;
    parent && parent !== document.documentElement;
    parent = parent.parentNode
  ) {
    yield parent;
  }
}

/**
 * This contains all of the information needed to perform a translation request.
 *
 * @typedef {object} TranslationRequest
 * @property {Node} node
 * @property {string} sourceText
 * @property {boolean} isHTML
 * @property {Function} resolve
 * @property {Function} reject
 */

/**
 * When a page is hidden, mutations may occur in the DOM. It doesn't make sense to
 * translate those elements while the page is hidden, especially as it may bring
 * a translations engine back to life, which can be quite expensive. Queue those
 * messages here.
 */
class QueuedTranslator {
  /**
   * @type {MessagePort | null}
   */
  #port = null;

  /**
   * @type {() => void}
   */
  #actorRequestNewPort;

  /**
   * Send a message to the actor that the first visible DOM translation change is about to occur.
   *
   * @type {() => void}
   */
  #actorReportFirstVisibleChange;

  /**
   * An id for each message sent. This is used to match up the request and response.
   */
  #nextMessageId = 0;

  /**
   * Tie together a message id to a resolved response.
   *
   * @type {Map<number, TranslationRequest>}
   */
  #requests = new Map();

  /**
   * If the translations are paused, they are queued here. This Map is ordered by
   * from oldest to newest requests with stale requests being removed.
   *
   * @type {Map<Node, TranslationRequest>}
   */
  #queue = new Map();

  /**
   * @type {"uninitialized" | "ready" | "error" | "closed"}
   */
  engineStatus = "uninitialized";

  /**
   * @param {MessagePort} port
   * @param {() => void} actorRequestNewPort
   * @param {() => void} actorReportFirstVisibleChange
   */
  constructor(port, actorRequestNewPort, actorReportFirstVisibleChange) {
    this.#actorRequestNewPort = actorRequestNewPort;
    this.#actorReportFirstVisibleChange = actorReportFirstVisibleChange;

    this.acquirePort(port);
  }

  /**
   * When an engine gets closed while still in use, a new one will need to be requested.
   *
   * @type {{ promise: Promise<void>, resolve: Function, reject: Function } | null}
   */
  #portRequest = null;

  /**
   * Keep track if the page is shown or hidden. When the page is hidden, no translations
   * will be posted to the translations engine.
   */
  #isPageShown = true;

  /**
   * Note when a new port is being requested so we don't re-request it.
   */
  async showPage() {
    this.#isPageShown = true;
    if (this.#port) {
      throw new Error(
        "Attempting to show the page when there is already port available"
      );
    }

    let portRequestPromise;
    if (this.#portRequest) {
      // It is possible that the page is being re-shown while a port request is still pending.
      // If that is the case, then we should continue to wait for the pending port.
      portRequestPromise = this.#portRequest.promise;
    } else if (this.#queue.size) {
      // There are queued translations, request a new port. After the port is retrieved
      // the pending queue will be processed.
      portRequestPromise = this.#requestNewPort();
    }

    try {
      await portRequestPromise;
    } catch {
      // Failed to retrieve the port after re-showing a page, which will be reported as an error in the panel UI.
      // At this point it is up to the user to determine the next step from the UI.
    }
  }

  /**
   * Hide the page, and move any outstanding translation requests to a queue.
   */
  async hidePage() {
    this.#isPageShown = false;

    if (this.#portRequest) {
      // It is possible that the page is being hidden while a port request is still pending.
      // If that is the case, then we should wait for the port to resolve so that any pending
      // translations can be properly moved to the queue, ready to resume when the page is re-shown.
      try {
        await this.#portRequest.promise;
      } catch {
        // Failed to retrieve the port after hiding the page. At this point it is up to the user to
        // determine the next step from the UI if they return to the page that was hidden.
      }
    }

    if (this.#requests.size) {
      lazy.console.log(
        "Pausing translations with pending translation requests."
      );
      this.#moveRequestsToQueue();
    }

    this.discardPort();
  }

  /**
   * Request a new port. The port will come in via `acquirePort`, and then resolved
   * through the `this.#portRequest.resolve`.
   *
   * @returns {Promise<void>}
   */
  #requestNewPort() {
    if (this.#portRequest) {
      // A port was already requested.
      return this.#portRequest.promise;
    }

    const portRequest = { promise: null, resolve: null, reject: null };
    portRequest.promise = new Promise((resolve, reject) => {
      portRequest.resolve = resolve;
      portRequest.reject = reject;
    });

    this.#portRequest = portRequest;

    // Send a request through the actor for a new port. The request response will
    // trigger the method `QueuedTranslator.prototype.acquirePort`
    this.#actorRequestNewPort();

    this.#portRequest.promise
      .then(
        () => {
          if (portRequest === this.#portRequest) {
            this.#portRequest = null;
          }

          // Resume the queued translations.
          if (this.#queue.size) {
            lazy.console.log(
              `Resuming ${
                this.#queue.size
              } translations from the pending translation queue.`
            );

            const oldQueue = this.#queue;
            this.#queue = new Map();
            this.#repostTranslations(oldQueue);
          }
        },
        error => {
          lazy.console.error(error);
        }
      )
      .finally(() => {
        if (portRequest === this.#portRequest) {
          this.#portRequest = null;
        }
      });

    return portRequest.promise;
  }

  /**
   * Send a request to translate text to the Translations Engine. If it returns `null`
   * then the request is stale. A rejection means there was an error in the translation.
   * This request may be queued.
   *
   * @param {Node} node
   * @param {string} sourceText
   * @param {boolean} isHTML
   */
  async translate(node, sourceText, isHTML) {
    if (this.#isPageShown && !this.#port) {
      try {
        await this.#requestNewPort();
      } catch {}
    }

    // At this point we don't know if the page is still shown, or if the attempt
    // to get a port was successful so check again.

    if (!this.#isPageShown || !this.#port) {
      // Queue the request while the page isn't shown.
      return new Promise((resolve, reject) => {
        const previousRequest = this.#queue.get(node);
        if (previousRequest) {
          // Previous requests get resolved as null, as this new one will replace it.
          previousRequest.resolve(null);
          // Delete the entry so that the order of the queue is maintained. The
          // new request will be put on the end.
          this.#queue.delete(node);
        }

        // This Promises's resolve and reject will be chained after the translation
        // request. For now add it to the queue along with the other arguments.
        this.#queue.set(node, { node, sourceText, isHTML, resolve, reject });
      });
    }

    return this.#postTranslationRequest(node, sourceText, isHTML);
  }

  /**
   * Posts the translation to the translations engine through the MessagePort.
   *
   * @param {Node} node
   * @param {string} sourceText
   * @param {boolean} isHTML
   * @returns {{ translateText: TranslationFunction, translateHTML: TranslationFunction}}
   */
  #postTranslationRequest(node, sourceText, isHTML) {
    return new Promise((resolve, reject) => {
      const messageId = this.#nextMessageId++;
      // Store the "resolve" for the promise. It will be matched back up with the
      // `messageId` in #handlePortMessage.
      this.#requests.set(messageId, {
        node,
        sourceText,
        isHTML,
        resolve,
        reject,
      });
      this.#port.postMessage({
        type: "TranslationsPort:TranslationRequest",
        messageId,
        sourceText,
        isHTML,
      });
    });
  }

  /**
   * Close the port and move any pending translations onto a queue.
   */
  discardPort() {
    if (this.#port) {
      this.#port.postMessage({ type: "TranslationsPort:DiscardTranslations" });
      this.#port.close();
      this.#port = null;
      this.#portRequest = null;
    }
    this.#moveRequestsToQueue();
    this.engineStatus = "uninitialized";
  }

  /**
   * Move any unfulfilled requests to the queue so they can be sent again when
   * the page is active again.
   */
  #moveRequestsToQueue() {
    if (this.#requests.size) {
      for (const request of this.#requests.values()) {
        this.#queue.set(request.node, request);
      }
      this.#requests = new Map();
    }
  }

  /**
   * Acquires a port, checks on the engine status, and then starts or resumes
   * translations.
   *
   * @param {MessagePort} port
   */
  acquirePort(port) {
    if (this.#port) {
      if (this.engineStatus === "ready") {
        lazy.console.error(
          "Received a new translation port while one already existed."
        );
      }
      this.discardPort();
    }

    this.#port = port;

    const portRequest = this.#portRequest;

    // Match up a response on the port to message that was sent.
    port.onmessage = ({ data }) => {
      switch (data.type) {
        case "TranslationsPort:TranslationResponse": {
          if (!this.hasFirstVisibleChange) {
            this.hasFirstVisibleChange = true;
            this.#actorReportFirstVisibleChange();
          }
          const { targetText, messageId } = data;
          // A request may not match match a messageId if there is a race during the pausing
          // and discarding of the queue.
          this.#requests.get(messageId)?.resolve(targetText);
          this.#requests.delete(messageId);
          break;
        }
        case "TranslationsPort:GetEngineStatusResponse": {
          if (portRequest) {
            const { resolve, reject } = portRequest;
            if (data.status === "ready") {
              resolve();
            } else {
              reject(new Error("The engine failed to load."));
            }
          }
          this.engineStatus = data.status;
          break;
        }
        case "TranslationsPort:EngineTerminated": {
          // The engine was terminated, and if a translation is needed a new port
          // will need to be requested.
          this.engineStatus = "closed";
          this.discardPort();
          if (this.#queue.size && this.#isPageShown) {
            this.#requestNewPort();
          }
          break;
        }
        default:
          lazy.console.error("Unknown translations port message: " + data.type);
          break;
      }
    };

    port.postMessage({ type: "TranslationsPort:GetEngineStatusRequest" });
  }

  /**
   * Re-send a list of translation requests.
   *
   * @param {Map<any, TranslationRequest>} mappedRequests
   *  This is either the this.#queue or this.#requests.
   */
  #repostTranslations(mappedRequests) {
    for (const value of mappedRequests.values()) {
      const { node, sourceText, isHTML, resolve, reject } = value;
      if (Cu.isDeadWrapper(node)) {
        // If the node is dead, resolve without any text. Do not reject as that
        // will be treated as an error.
        resolve(null);
      } else {
        this.#postTranslationRequest(node, sourceText, isHTML).then(
          resolve,
          reject
        );
      }
    }
  }

  /**
   * Close the port and remove any pending or queued requests.
   */
  destroy() {
    this.#port.close();
    this.#requests = new Map();
    this.#queue = new Map();
  }
}

/**
 * @param {Element} element
 * @param {string} attribute
 */
function isAttributeTranslatable(element, attribute) {
  for (const [tagName, translatableAttribute] of TRANSLATABLE_ATTRIBUTES) {
    if (
      (!tagName || tagName === element.tagName) &&
      attribute === translatableAttribute
    ) {
      return true;
    }
  }
  return false;
}
PK
!<�Og��Cchrome/toolkit/content/global/translations/translations-engine.html<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome: resource:; object-src 'none'"
    />
    <!-- Run the translations engine in its own singleton unprivileged content process. -->
    <script
      type="module"
      src="chrome://global/content/translations/translations-engine.sys.mjs"
    ></script>
  </head>
  <body></body>
</html>
PK
!<�UJk�M�MFchrome/toolkit/content/global/translations/translations-engine.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-env browser */
/* globals TE_addProfilerMarker, TE_getLogLevel, TE_log, TE_logError, TE_getLogLevel,
           TE_destroyEngineProcess, TE_requestEnginePayload, TE_reportEngineStatus,
           TE_resolveForceShutdown */

/**
 * This file lives in the translation engine's process and is in charge of managing the
 * lifecycle of the translations engines. This process is a singleton Web Content
 * process that can be created and destroyed as needed.
 *
 * The goal of the code in this file is to be as unprivileged as possible, which should
 * unlock Bug 1813789, which will make this file fully unprivileged.
 *
 * Each translation needs an engine for that specific translation pair. This engine is
 * kept around as long as the CACHE_TIMEOUT_MS, after this if some keepAlive event does
 * not happen, the engine is destroyed. An engine may be destroyed even when a page is
 * still open and may need translations in the future. This is handled gracefully by
 * creating new engines and MessagePorts on the fly.
 *
 * The engine communicates directly with the content page via a MessagePort. Each end
 * of the port is transfered from the parent process to the content process, and this
 * engine process. This port is transitory, and may be closed at any time. Only when a
 * translation has been requested once (which is initiated by the parent process) can
 * the content process re-request translation ports. This ensures a rogue content process
 * only has the capabilities to perform tasks that the parent process has given it.
 *
 * The messaging flow can get a little convoluted to handle all of the correctness cases,
 * but ideally communication passes through the message port as much as possible. There
 * are many scenarios such as:
 *
 *  - Translation pages becoming idle
 *  - Tab changing causing "pageshow" and "pagehide" visibility changes
 *  - Translation actor destruction (this can happen long after the page has been
 *                                   navigated away from, but is still alive in the
 *                                   page history)
 *  - Error states
 *  - Engine Process being graceful shut down (no engines left)
 *  - Engine Process being killed by the OS.
 *
 * The following is a diagram that attempts to illustrate the structure of the processes
 * and the communication channels that exist between them.
 *
 * ┌─────────────────────────────────────────────────────────────┐
 * │ PARENT PROCESS                                              │
 * │                                                             │
 * │  [TranslationsParent]  ←────→  [TranslationsEngineParent]   │
 * │                  ↑                                    ↑     │
 * └──────────────────│────────────────────────────────────│─────┘
 *                    │ JSWindowActor IPC calls            │ JSWindowActor IPC calls
 *                    │                                    │
 * ┌──────────────────│────────┐                     ┌─────│─────────────────────────────┐
 * │ CONTENT PROCESS  │        │                     │     │    ENGINE PROCESS           │
 * │                  │        │                     │     ↓                             │
 * │  [french.html]   │        │                     │ [TranslationsEngineChild]         │
 * │        ↕         ↓        │                     │            ↕                      │
 * │  [TranslationsChild]      │                     │ [translations-engine.html]        │
 * │  └──TranslationsDocument  │                     │    ├── "fr to en" engine          │
 * │     └──port1     « ═══════════ MessageChannel ════ » │   └── port2                  │
 * │                           │                     │    └── "de to en" engine (idle)   │
 * └───────────────────────────┘                     └───────────────────────────────────┘
 */

// How long the cache remains alive between uses, in milliseconds. In automation the
// engine is manually created and destroyed to avoid timing issues.
const CACHE_TIMEOUT_MS = 15_000;

/**
 * @typedef {import("./translations-document.sys.mjs").TranslationsDocument} TranslationsDocument
 * @typedef {import("../translations.js").TranslationsEnginePayload} TranslationsEnginePayload
 */

/**
 * The TranslationsEngine encapsulates the logic for translating messages. It can
 * only be set up for a single language translation pair. In order to change languages
 * a new engine should be constructed.
 *
 * The actual work for the translations happens in a worker. This class manages
 * instantiating and messaging the worker.
 *
 * Keep unused engines around in the TranslationsEngine.#cachedEngine cache in case
 * page navigation happens and we can re-use previous engines. The engines are very
 * heavy-weight, so get rid of them after a timeout. Once all are destroyed the
 * TranslationsEngineParent is notified that it can be destroyed.
 */
export class TranslationsEngine {
  /**
   * Maps a language pair key to a cached engine. Engines are kept around for a timeout
   * before they are removed so that they can be re-used during navigation.
   *
   * @type {Map<string, Promise<TranslationsEngine>>}
   */
  static #cachedEngines = new Map();

  /** @type {TimeoutID | null} */
  #keepAliveTimeout = null;

  /** @type {Worker} */
  #worker;

  /**
   * Multiple messages can be sent before a response is received. This ID is used to keep
   * track of the messages. It is incremented on every use.
   */
  #messageId = 0;

  /**
   * Returns a getter function that will create a translations engine on the first
   * call, and then return the cached one. After a timeout when the engine hasn't
   * been used, it is destroyed.
   *
   * @param {string} fromLanguage
   * @param {string} toLanguage
   * @param {number} innerWindowId
   * @returns {Promise<TranslationsEngine>}
   */
  static getOrCreate(fromLanguage, toLanguage, innerWindowId) {
    const languagePairKey = getLanguagePairKey(fromLanguage, toLanguage);
    let enginePromise = TranslationsEngine.#cachedEngines.get(languagePairKey);

    if (enginePromise) {
      return enginePromise;
    }

    TE_log(`Creating a new engine for "${fromLanguage}" to "${toLanguage}".`);

    // A new engine needs to be created.
    enginePromise = TranslationsEngine.create(
      fromLanguage,
      toLanguage,
      innerWindowId
    );

    TranslationsEngine.#cachedEngines.set(languagePairKey, enginePromise);

    enginePromise.catch(error => {
      TE_logError(
        `The engine failed to load for translating "${fromLanguage}" to "${toLanguage}". Removing it from the cache.`,
        error
      );
      // Remove the engine if it fails to initialize.
      TranslationsEngine.#removeEngineFromCache(languagePairKey);
    });

    return enginePromise;
  }

  /**
   * Removes the engine, and if it's the last, call the process to destroy itself.
   *
   * @param {string} languagePairKey
   * @param {boolean} force - On forced shutdowns, it's not necessary to notify the
   *                          parent process.
   */
  static #removeEngineFromCache(languagePairKey, force) {
    TranslationsEngine.#cachedEngines.delete(languagePairKey);
    if (TranslationsEngine.#cachedEngines.size === 0 && !force) {
      TE_log("The last engine was removed, destroying this process.");
      TE_destroyEngineProcess();
    }
  }

  /**
   * Create a TranslationsEngine and bypass the cache.
   *
   * @param {string} fromLanguage
   * @param {string} toLanguage
   * @param {number} innerWindowId
   * @returns {Promise<TranslationsEngine>}
   */
  static async create(fromLanguage, toLanguage, innerWindowId) {
    const startTime = performance.now();

    const engine = new TranslationsEngine(
      fromLanguage,
      toLanguage,
      await TE_requestEnginePayload(fromLanguage, toLanguage)
    );

    await engine.isReady;

    TE_addProfilerMarker({
      startTime,
      message: `Translations engine loaded for "${fromLanguage}" to "${toLanguage}"`,
      innerWindowId,
    });

    return engine;
  }

  /**
   * Signal to the engines that they are being forced to shutdown.
   */
  static forceShutdown() {
    return Promise.allSettled(
      [...TranslationsEngine.#cachedEngines].map(
        async ([langPair, enginePromise]) => {
          TE_log(`Force shutdown of the engine "${langPair}"`);
          const engine = await enginePromise;
          engine.terminate(true /* force */);
        }
      )
    );
  }

  /**
   * Terminates the engine and its worker after a timeout.
   *
   * @param {boolean} force
   */
  terminate = (force = false) => {
    const message = `Terminating translations engine "${this.languagePairKey}".`;
    TE_addProfilerMarker({ message });
    TE_log(message);
    this.#worker.terminate();
    this.#worker = null;
    if (this.#keepAliveTimeout) {
      clearTimeout(this.#keepAliveTimeout);
    }
    for (const [innerWindowId, data] of ports) {
      const { fromLanguage, toLanguage, port } = data;
      if (
        fromLanguage === this.fromLanguage &&
        toLanguage === this.toLanguage
      ) {
        // This port is still active but being closed.
        ports.delete(innerWindowId);
        port.postMessage({ type: "TranslationsPort:EngineTerminated" });
        port.close();
      }
    }
    TranslationsEngine.#removeEngineFromCache(this.languagePairKey, force);
  };

  /**
   * The worker needs to be shutdown after some amount of time of not being used.
   */
  keepAlive() {
    if (this.#keepAliveTimeout) {
      // Clear any previous timeout.
      clearTimeout(this.#keepAliveTimeout);
    }
    // In automated tests, the engine is manually destroyed.
    if (!Cu.isInAutomation) {
      this.#keepAliveTimeout = setTimeout(this.terminate, CACHE_TIMEOUT_MS);
    }
  }

  /**
   * Construct and initialize the worker.
   *
   * @param {string} fromLanguage
   * @param {string} toLanguage
   * @param {TranslationsEnginePayload} enginePayload - If there is no engine payload
   *   then the engine will be mocked. This allows this class to be used in tests.
   */
  constructor(fromLanguage, toLanguage, enginePayload) {
    /** @type {string} */
    this.fromLanguage = fromLanguage;
    /** @type {string} */
    this.toLanguage = toLanguage;
    this.languagePairKey = getLanguagePairKey(fromLanguage, toLanguage);
    this.#worker = new Worker(
      "chrome://global/content/translations/translations-engine.worker.js"
    );

    /** @type {Promise<void>} */
    this.isReady = new Promise((resolve, reject) => {
      const onMessage = ({ data }) => {
        TE_log("Received initialization message", data);
        if (data.type === "initialization-success") {
          resolve();
        } else if (data.type === "initialization-error") {
          reject(data.error);
        }
        this.#worker.removeEventListener("message", onMessage);
      };
      this.#worker.addEventListener("message", onMessage);

      // Schedule the first timeout for keeping the engine alive.
      this.keepAlive();
    });

    // Make sure the ArrayBuffers are transferred, not cloned.
    // https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects
    const transferables = [];
    if (enginePayload) {
      transferables.push(enginePayload.bergamotWasmArrayBuffer);
      for (const files of enginePayload.languageModelFiles) {
        for (const { buffer } of Object.values(files)) {
          transferables.push(buffer);
        }
      }
    }

    this.#worker.postMessage(
      {
        type: "initialize",
        fromLanguage,
        toLanguage,
        enginePayload,
        messageId: this.#messageId++,
        logLevel: TE_getLogLevel(),
      },
      transferables
    );
  }

  /**
   * The implementation for translation. Use translateText or translateHTML for the
   * public API.
   *
   * @param {string} sourceText
   * @param {boolean} isHTML
   * @param {number} innerWindowId
   * @returns {Promise<string[]>}
   */
  translate(sourceText, isHTML, innerWindowId) {
    this.keepAlive();

    const messageId = this.#messageId++;

    return new Promise((resolve, reject) => {
      const onMessage = ({ data }) => {
        if (
          data.type === "translations-discarded" &&
          data.innerWindowId === innerWindowId
        ) {
          // The page was unloaded, and we no longer need to listen for a response.
          this.#worker.removeEventListener("message", onMessage);
          return;
        }

        if (data.messageId !== messageId) {
          // Multiple translation requests can be sent before a response is received.
          // Ensure that the response received here is the correct one.
          return;
        }

        if (data.type === "translation-response") {
          // Also keep the translation alive after getting a result, as many translations
          // can queue up at once, and then it can take minutes to resolve them all.
          this.keepAlive();
          resolve(data.targetText);
        }
        if (data.type === "translation-error") {
          reject(data.error);
        }
        this.#worker.removeEventListener("message", onMessage);
      };

      this.#worker.addEventListener("message", onMessage);

      this.#worker.postMessage({
        type: "translation-request",
        isHTML,
        sourceText,
        messageId,
        innerWindowId,
      });
    });
  }

  /**
   * Applies a function only if a cached engine exists.
   *
   * @param {string} fromLanguage
   * @param {string} toLanguage
   * @param {(engine: TranslationsEngine) => void} fn
   */
  static withCachedEngine(fromLanguage, toLanguage, fn) {
    const engine = TranslationsEngine.#cachedEngines.get(
      getLanguagePairKey(fromLanguage, toLanguage)
    );

    if (engine) {
      engine.then(fn).catch(() => {});
    }
  }

  /**
   * Stop processing the translation queue. All in-progress messages will be discarded.
   *
   * @param {number} innerWindowId
   */
  discardTranslationQueue(innerWindowId) {
    this.#worker.postMessage({
      type: "discard-translation-queue",
      innerWindowId,
    });
  }

  /**
   * Pause or resume the translations from a cached engine.
   *
   * @param {boolean} pause
   * @param {string} fromLanguage
   * @param {string} toLanguage
   * @param {number} innerWindowId
   */
  static pause(pause, fromLanguage, toLanguage, innerWindowId) {
    TranslationsEngine.withCachedEngine(fromLanguage, toLanguage, engine => {
      engine.pause(pause, innerWindowId);
    });
  }
}

/**
 * Creates a lookup key that is unique to each fromLanguage-toLanguage pair.
 *
 * @param {string} fromLanguage
 * @param {string} toLanguage
 * @returns {string}
 */
function getLanguagePairKey(fromLanguage, toLanguage) {
  return `${fromLanguage},${toLanguage}`;
}

/**
 * Maps the innerWindowId to the port.
 *
 * @type {Map<number, { fromLanguage: string, toLanguage: string, port: MessagePort }>}
 */
const ports = new Map();

/**
 * Listen to the port to the content process for incoming messages, and pass
 * them to the TranslationsEngine manager. The other end of the port is held
 * in the content process by the TranslationsDocument.
 *
 * @param {string} fromLanguage
 * @param {string} toLanguage
 * @param {number} innerWindowId
 * @param {MessagePort} port
 */
function listenForPortMessages(fromLanguage, toLanguage, innerWindowId, port) {
  async function handleMessage({ data }) {
    switch (data.type) {
      case "TranslationsPort:GetEngineStatusRequest": {
        // This message gets sent first before the translation queue is processed.
        // The engine is most likely to fail on the initial invocation. Any failure
        // past the first one is not reported to the UI.
        TranslationsEngine.getOrCreate(
          fromLanguage,
          toLanguage,
          innerWindowId
        ).then(
          () => {
            TE_log("The engine is ready for translations.", {
              innerWindowId,
            });
            TE_reportEngineStatus(innerWindowId, "ready");
            port.postMessage({
              type: "TranslationsPort:GetEngineStatusResponse",
              status: "ready",
            });
          },
          () => {
            TE_reportEngineStatus(innerWindowId, "error");
            port.postMessage({
              type: "TranslationsPort:GetEngineStatusResponse",
              status: "error",
            });
            // After an error no more translation requests will be sent. Go ahead
            // and close the port.
            port.close();
            ports.delete(innerWindowId);
          }
        );
        break;
      }
      case "TranslationsPort:TranslationRequest": {
        const { sourceText, isHTML, messageId } = data;
        const engine = await TranslationsEngine.getOrCreate(
          fromLanguage,
          toLanguage,
          innerWindowId
        );
        const targetText = await engine.translate(
          sourceText,
          isHTML,
          innerWindowId
        );
        port.postMessage({
          type: "TranslationsPort:TranslationResponse",
          messageId,
          targetText,
        });
        break;
      }
      case "TranslationsPort:DiscardTranslations": {
        discardTranslations(innerWindowId);
        break;
      }
      default:
        TE_logError("Unknown translations port message: " + data.type);
        break;
    }
  }

  if (port.onmessage) {
    TE_logError(
      new Error("The MessagePort onmessage handler was already present.")
    );
  }

  port.onmessage = event => {
    handleMessage(event).catch(error => TE_logError(error));
  };
}

/**
 * Discards the queue and removes the port.
 *
 * @param {number} innerWindowId
 */
function discardTranslations(innerWindowId) {
  TE_log("Discarding translations, innerWindowId:", innerWindowId);

  const portData = ports.get(innerWindowId);
  if (portData) {
    const { port, fromLanguage, toLanguage } = portData;
    port.close();
    ports.delete(innerWindowId);

    TranslationsEngine.withCachedEngine(fromLanguage, toLanguage, engine => {
      engine.discardTranslationQueue(innerWindowId);
    });
  }
}

/**
 * Listen for events coming from the TranslationsEngine actor.
 */
window.addEventListener("message", ({ data }) => {
  switch (data.type) {
    case "StartTranslation": {
      const { fromLanguage, toLanguage, innerWindowId, port } = data;
      TE_log("Starting translation", innerWindowId);
      listenForPortMessages(fromLanguage, toLanguage, innerWindowId, port);
      ports.set(innerWindowId, { port, fromLanguage, toLanguage });
      break;
    }
    case "DiscardTranslations": {
      const { innerWindowId } = data;
      discardTranslations(innerWindowId);
      break;
    }
    case "ForceShutdown": {
      TranslationsEngine.forceShutdown().then(() => {
        TE_resolveForceShutdown();
      });
      break;
    }
    default:
      throw new Error("Unknown TranslationsEngineChromeToContent event.");
  }
});
PK
!</�t_+V+VHchrome/toolkit/content/global/translations/translations-engine.worker.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/**
 * @typedef {import("../translations").Bergamot} Bergamot
 * @typedef {import("../translations").LanguageTranslationModelFiles} LanguageTranslationModelFiles
 */

/* global loadBergamot */
importScripts("chrome://global/content/translations/bergamot-translator.js");

// Respect the preference "browser.translations.logLevel".
let _loggingLevel = "Error";
function log(...args) {
  if (_loggingLevel !== "Error" && _loggingLevel !== "Warn") {
    console.log("Translations:", ...args);
  }
}
function trace(...args) {
  if (_loggingLevel === "Trace" || _loggingLevel === "All") {
    console.log("Translations:", ...args);
  }
}

// Throw Promise rejection errors so that they are visible in the console.
self.addEventListener("unhandledrejection", event => {
  throw event.reason;
});

/**
 * The alignment for each file type, file type strings should be same as in the
 * model registry.
 */
const MODEL_FILE_ALIGNMENTS = {
  model: 256,
  lex: 64,
  vocab: 64,
  qualityModel: 64,
  srcvocab: 64,
  trgvocab: 64,
};

/**
 * Initialize the engine, and get it ready to handle translation requests.
 * The "initialize" message must be received before any other message handling
 * requests will be processed.
 */
addEventListener("message", handleInitializationMessage);

async function handleInitializationMessage({ data }) {
  const startTime = performance.now();
  if (data.type !== "initialize") {
    console.error(
      "The TranslationEngine worker received a message before it was initialized."
    );
    return;
  }

  try {
    const { fromLanguage, toLanguage, enginePayload, logLevel, innerWindowId } =
      data;

    if (!fromLanguage) {
      throw new Error('Worker initialization missing "fromLanguage"');
    }
    if (!toLanguage) {
      throw new Error('Worker initialization missing "toLanguage"');
    }

    if (logLevel) {
      // Respect the "browser.translations.logLevel" preference.
      _loggingLevel = logLevel;
    }

    let engine;
    if (enginePayload.isMocked) {
      // The engine is testing mode, and no Bergamot wasm is available.
      engine = new MockedEngine(fromLanguage, toLanguage);
    } else {
      const { bergamotWasmArrayBuffer, languageModelFiles } = enginePayload;
      const bergamot = await BergamotUtils.initializeWasm(
        bergamotWasmArrayBuffer
      );
      engine = new Engine(
        fromLanguage,
        toLanguage,
        bergamot,
        languageModelFiles
      );
    }

    ChromeUtils.addProfilerMarker(
      "TranslationsWorker",
      { startTime, innerWindowId },
      "Translations engine loaded."
    );

    handleMessages(engine);
    postMessage({ type: "initialization-success" });
  } catch (error) {
    console.error(error);
    postMessage({ type: "initialization-error", error: error?.message });
  }

  removeEventListener("message", handleInitializationMessage);
}

/**
 * Sets up the message handling for the worker.
 *
 * @param {Engine | MockedEngine} engine
 */
function handleMessages(engine) {
  let discardPromise;
  addEventListener("message", async ({ data }) => {
    try {
      if (data.type === "initialize") {
        throw new Error("The Translations engine must not be re-initialized.");
      }
      if (data.type === "translation-request") {
        // Only show these messages when "All" logging is on, since there are so many
        // of them.
        trace("Received message", data);
      } else {
        log("Received message", data);
      }

      switch (data.type) {
        case "translation-request": {
          const { sourceText, messageId, isHTML, innerWindowId } = data;
          if (discardPromise) {
            // Wait for messages to be discarded if there are any.
            await discardPromise;
          }
          try {
            // Add a translation to the work queue, and when it returns, post the message
            // back. The translation may never return if the translations are discarded
            // before it have time to be run. In this case this await is just never
            // resolved, and the postMessage is never run.
            const targetText = await engine.translate(
              sourceText,
              isHTML,
              innerWindowId
            );

            // This logging level can be very verbose and slow, so only do it under the
            // "Trace" level, which is the most verbose. Set the logging level to "Info" to avoid
            // these, and get all of the other logs.
            trace("Translation complete", {
              sourceText,
              targetText,
              isHTML,
              innerWindowId,
            });

            postMessage({
              type: "translation-response",
              targetText,
              messageId,
            });
          } catch (error) {
            console.error(error);
            let message = "An error occurred in the engine worker.";
            if (typeof error?.message === "string") {
              message = error.message;
            }
            let stack = "(no stack)";
            if (typeof error?.stack === "string") {
              stack = error.stack;
            }
            postMessage({
              type: "translation-error",
              error: { message, stack },
              messageId,
              innerWindowId,
            });
          }
          break;
        }
        case "discard-translation-queue": {
          ChromeUtils.addProfilerMarker(
            "TranslationsWorker",
            { innerWindowId: data.innerWindowId },
            "Translations discard requested"
          );

          discardPromise = engine.discardTranslations(data.innerWindowId);
          await discardPromise;
          discardPromise = null;

          // Signal to the "message" listeners in the main thread to stop listening.
          postMessage({
            type: "translations-discarded",
          });
          break;
        }
        default:
          console.warn("Unknown message type:", data.type);
      }
    } catch (error) {
      // Ensure the unexpected errors are surfaced in the console.
      console.error(error);
    }
  });
}

/**
 * The Engine is created once for a language pair. The initialization process copies the
 * ArrayBuffers for the language buffers from JS-managed ArrayBuffers, to aligned
 * internal memory for the wasm heap.
 *
 * After this the ArrayBuffers are discarded and GC'd. This file should be managed
 * from the TranslationsEngine class on the main thread.
 *
 * This class starts listening for messages only after the Bergamot engine has been
 * fully initialized.
 */
class Engine {
  /**
   * @param {string} fromLanguage
   * @param {string} toLanguage
   * @param {Bergamot} bergamot
   * @param {Array<LanguageTranslationModelFiles>} languageTranslationModelFiles
   */
  constructor(
    fromLanguage,
    toLanguage,
    bergamot,
    languageTranslationModelFiles
  ) {
    /** @type {string} */
    this.fromLanguage = fromLanguage;
    /** @type {string} */
    this.toLanguage = toLanguage;
    /** @type {Bergamot} */
    this.bergamot = bergamot;
    /** @type {Bergamot["TranslationModel"][]} */
    this.languageTranslationModels = languageTranslationModelFiles.map(
      languageTranslationModelFiles =>
        BergamotUtils.constructSingleTranslationModel(
          bergamot,
          languageTranslationModelFiles
        )
    );

    /** @type {Bergamot["BlockingService"]} */
    this.translationService = new bergamot.BlockingService({
      // Caching is disabled (see https://github.com/mozilla/firefox-translations/issues/288)
      cacheSize: 0,
    });
  }

  /**
   * Run the translation models to perform a batch of message translations. The
   * promise is rejected when the sync version of this function throws an error.
   * This function creates an async interface over the synchronous translation
   * mechanism. This allows other microtasks such as message handling to still work
   * even though the translations are CPU-intensive.
   *
   * @param {string} sourceText
   * @param {boolean} isHTML
   * @param {number} innerWindowId - This is required
   *
   * @returns {Promise<string>}sourceText
   */
  translate(sourceText, isHTML, innerWindowId) {
    return this.#getWorkQueue(innerWindowId).runTask(() =>
      this.#syncTranslate(sourceText, isHTML, innerWindowId)
    );
  }

  /**
   * Map each innerWindowId to its own WorkQueue. This makes it easy to shut down
   * an entire queue of work when the page is unloaded.
   *
   * @type {Map<number, WorkQueue>}
   */
  #workQueues = new Map();

  /**
   * Get or create a `WorkQueue` that is unique to an `innerWindowId`.
   *
   * @param {number} innerWindowId
   * @returns {WorkQueue}
   */
  #getWorkQueue(innerWindowId) {
    let workQueue = this.#workQueues.get(innerWindowId);
    if (workQueue) {
      return workQueue;
    }
    workQueue = new WorkQueue(innerWindowId);
    this.#workQueues.set(innerWindowId, workQueue);
    return workQueue;
  }

  /**
   * Cancels any in-progress translations by removing the work queue.
   *
   * @param {number} innerWindowId
   */
  discardTranslations(innerWindowId) {
    let workQueue = this.#workQueues.get(innerWindowId);
    if (workQueue) {
      workQueue.cancelWork();
      this.#workQueues.delete(innerWindowId);
    }
  }

  /**
   * Run the translation models to perform a translation. This
   * blocks the worker thread until it is completed.
   *
   * @param {string} sourceText
   * @param {boolean} isHTML
   * @param {number} innerWindowId
   * @returns {string}
   */
  #syncTranslate(sourceText, isHTML, innerWindowId) {
    const startTime = performance.now();
    let response;
    sourceText = sourceText.trim();
    const { messages, options } = BergamotUtils.getTranslationArgs(
      this.bergamot,
      sourceText,
      isHTML
    );
    try {
      if (messages.size() === 0) {
        return [];
      }

      /** @type {Bergamot["VectorResponse"]} */
      let responses;

      if (this.languageTranslationModels.length === 1) {
        responses = this.translationService.translate(
          this.languageTranslationModels[0],
          messages,
          options
        );
      } else if (this.languageTranslationModels.length === 2) {
        responses = this.translationService.translateViaPivoting(
          this.languageTranslationModels[0],
          this.languageTranslationModels[1],
          messages,
          options
        );
      } else {
        throw new Error(
          "Too many models were provided to the translation worker."
        );
      }

      // Report on the time it took to do this translation.
      ChromeUtils.addProfilerMarker(
        "TranslationsWorker",
        { startTime, innerWindowId },
        `Translated ${sourceText.length} code units.`
      );

      const targetText = responses.get(0).getTranslatedText();
      return targetText;
    } finally {
      // Free up any memory that was allocated. This will always run.
      messages?.delete();
      options?.delete();
      response?.delete();
    }
  }
}

/**
 * Static utilities to help work with the Bergamot wasm module.
 */
class BergamotUtils {
  /**
   * Construct a single translation model.
   *
   * @param {Bergamot} bergamot
   * @param {LanguageTranslationModelFiles} languageTranslationModelFiles
   * @returns {Bergamot["TranslationModel"]}
   */
  static constructSingleTranslationModel(
    bergamot,
    languageTranslationModelFiles
  ) {
    log(`Constructing translation model.`);

    const { model, lex, vocab, qualityModel, srcvocab, trgvocab } =
      BergamotUtils.allocateModelMemory(
        bergamot,
        languageTranslationModelFiles
      );

    // Transform the bytes to mb, like "10.2mb"
    const getMemory = memory => `${Math.floor(memory.size() / 100_000) / 10}mb`;

    let memoryLog = `Model memory sizes in wasm heap:`;
    memoryLog += `\n  Model: ${getMemory(model)}`;
    memoryLog += `\n  Shortlist: ${getMemory(lex)}`;

    // Set up the vocab list, which could either be a single "vocab" model, or a
    // "srcvocab" and "trgvocab" pair.
    const vocabList = new bergamot.AlignedMemoryList();

    if (vocab) {
      vocabList.push_back(vocab);
      memoryLog += `\n  Vocab: ${getMemory(vocab)}`;
    } else if (srcvocab && trgvocab) {
      vocabList.push_back(srcvocab);
      vocabList.push_back(trgvocab);
      memoryLog += `\n  Src Vocab: ${getMemory(srcvocab)}`;
      memoryLog += `\n  Trg Vocab: ${getMemory(trgvocab)}`;
    } else {
      throw new Error("Vocabulary key is not found.");
    }

    if (qualityModel) {
      memoryLog += `\n  QualityModel: ${getMemory(qualityModel)}\n`;
    }

    const config = BergamotUtils.generateTextConfig({
      "beam-size": "1",
      normalize: "1.0",
      "word-penalty": "0",
      "max-length-break": "128",
      "mini-batch-words": "1024",
      workspace: "128",
      "max-length-factor": "2.0",
      "skip-cost": (!qualityModel).toString(),
      "cpu-threads": "0",
      quiet: "true",
      "quiet-translation": "true",
      "gemm-precision":
        languageTranslationModelFiles.model.record.name.endsWith("intgemm8.bin")
          ? "int8shiftAll"
          : "int8shiftAlphaAll",
      alignment: "soft",
    });

    log(`Bergamot translation model config: ${config}`);
    log(memoryLog);

    return new bergamot.TranslationModel(
      config,
      model,
      lex,
      vocabList,
      qualityModel ?? null
    );
  }

  /**
   * The models must be placed in aligned memory that the Bergamot wasm module has access
   * to. This function copies over the model blobs into this memory space.
   *
   * @param {Bergamot} bergamot
   * @param {LanguageTranslationModelFiles} languageTranslationModelFiles
   * @returns {LanguageTranslationModelFilesAligned}
   */
  static allocateModelMemory(bergamot, languageTranslationModelFiles) {
    /** @type {LanguageTranslationModelFilesAligned} */
    const results = {};

    for (const [fileType, file] of Object.entries(
      languageTranslationModelFiles
    )) {
      const alignment = MODEL_FILE_ALIGNMENTS[fileType];
      if (!alignment) {
        throw new Error(`Unknown file type: "${fileType}"`);
      }

      const alignedMemory = new bergamot.AlignedMemory(
        file.buffer.byteLength,
        alignment
      );

      alignedMemory.getByteArrayView().set(new Uint8Array(file.buffer));

      results[fileType] = alignedMemory;
    }

    return results;
  }

  /**
   * Initialize the Bergamot translation engine. It is a wasm compiled version of the
   * Marian translation software. The wasm is delivered remotely to cut down on binary size.
   *
   * https://github.com/mozilla/bergamot-translator/
   *
   * @param {ArrayBuffer} wasmBinary
   * @returns {Promise<Bergamot>}
   */
  static initializeWasm(wasmBinary) {
    return new Promise((resolve, reject) => {
      /** @type {number} */
      let start = performance.now();

      /** @type {Bergamot} */
      const bergamot = loadBergamot({
        // This is the amount of memory that a simple run of Bergamot uses, in bytes.
        INITIAL_MEMORY: 234_291_200,
        print: log,
        onAbort() {
          reject(new Error("Error loading Bergamot wasm module."));
        },
        onRuntimeInitialized: async () => {
          const duration = performance.now() - start;
          log(
            `Bergamot wasm runtime initialized in ${duration / 1000} seconds.`
          );
          // Await at least one microtask so that the captured `bergamot` variable is
          // fully initialized.
          await Promise.resolve();
          resolve(bergamot);
        },
        wasmBinary,
      });
    });
  }

  /**
   * Maps the Bergamot Vector to a JS array
   *
   * @param {Bergamot["Vector"]} vector
   * @param {Function} fn
   * @returns {Array}
   */
  static mapVector(vector, fn) {
    const result = [];
    for (let index = 0; index < vector.size(); index++) {
      result.push(fn(vector.get(index), index));
    }
    return result;
  }

  /**
   * Generate a config for the Marian translation service. It requires specific whitespace.
   *
   * https://marian-nmt.github.io/docs/cmd/marian-decoder/
   *
   * @param {Record<string, string>} config
   * @returns {string}
   */
  static generateTextConfig(config) {
    const indent = "            ";
    let result = "\n";

    for (const [key, value] of Object.entries(config)) {
      result += `${indent}${key}: ${value}\n`;
    }

    return result + indent;
  }

  /**
   * Do any cleaning steps for the text that are required before sending it into
   * the translation engine.
   *
   * @param {string} sourceText
   * @returns {string}
   */
  static cleanText(sourceText) {
    // Whitespace at the beginning or end can confuse translations.
    sourceText = sourceText.trim();

    // Remove any soft hyphens, as they will break tokenization.
    sourceText = sourceText.replaceAll("\u00AD", "");

    return sourceText;
  }

  /**
   * JS objects need to be translated into wasm objects to configure the translation engine.
   *
   * @param {Bergamot} bergamot
   * @param {string[]} sourceText
   * @returns {{ messages: Bergamot["VectorString"], options: Bergamot["VectorResponseOptions"] }}
   */
  static getTranslationArgs(bergamot, sourceText, isHTML) {
    const messages = new bergamot.VectorString();
    const options = new bergamot.VectorResponseOptions();
    sourceText = BergamotUtils.cleanText(sourceText);

    // Empty paragraphs break the translation.
    if (sourceText) {
      messages.push_back(sourceText);
      options.push_back({
        qualityScores: false,
        alignment: true,
        html: isHTML,
      });
    }

    return { messages, options };
  }
}

/**
 * For testing purposes, provide a fully mocked engine. This allows for easy integration
 * testing of the UI, without having to rely on downloading remote models and remote
 * wasm binaries.
 */
class MockedEngine {
  /**
   * @param {string} fromLanguage
   * @param {string} toLanguage
   */
  constructor(fromLanguage, toLanguage) {
    /** @type {string} */
    this.fromLanguage = fromLanguage;
    /** @type {string} */
    this.toLanguage = toLanguage;
  }

  /**
   * Create a fake translation of the text.
   *
   * @param {string} sourceText
   * @param {bool} isHTML
   * @returns {string}
   */
  translate(sourceText, isHTML) {
    // Note when an HTML translations is requested.
    let html = isHTML ? ", html" : "";
    const targetText = sourceText.toUpperCase();
    return `${targetText} [${this.fromLanguage} to ${this.toLanguage}${html}]`;
  }

  discardTranslations() {}
}

/**
 * This class takes tasks that may block the thread's event loop, and has them yield
 * after a time budget via setTimeout calls to allow other code to execute.
 */
class WorkQueue {
  #TIME_BUDGET = 100; // ms
  #RUN_IMMEDIATELY_COUNT = 20;

  /** @type {Array<{task: Function, resolve: Function}>} */
  #tasks = [];
  #isRunning = false;
  #isWorkCancelled = false;
  #runImmediately = this.#RUN_IMMEDIATELY_COUNT;

  /**
   * @param {number} innerWindowId
   */
  constructor(innerWindowId) {
    this.innerWindowId = innerWindowId;
  }

  /**
   * Run the task and return the result.
   *
   * @template {any} T
   * @param {() => T} task
   * @returns {Promise<T>}
   */
  runTask(task) {
    if (this.#runImmediately > 0) {
      // Run the first N translations immediately, most likely these are the user-visible
      // translations on the page, as they are sent in first. The setTimeout of 0 can
      // still delay the translations noticeably.
      this.#runImmediately--;
      return Promise.resolve(task());
    }
    return new Promise((resolve, reject) => {
      this.#tasks.push({ task, resolve, reject });
      this.#run().catch(error => console.error(error));
    });
  }

  /**
   * The internal run function.
   */
  async #run() {
    if (this.#isRunning) {
      // The work queue is already running.
      return;
    }

    this.#isRunning = true;

    // Measure the timeout
    let lastTimeout = null;

    let tasksInBatch = 0;
    const addProfilerMarker = () => {
      ChromeUtils.addProfilerMarker(
        "TranslationsWorker WorkQueue",
        { startTime: lastTimeout, innerWindowId: this.innerWindowId },
        `WorkQueue processed ${tasksInBatch} tasks`
      );
    };

    while (this.#tasks.length !== 0) {
      if (this.#isWorkCancelled) {
        // The work was already cancelled.
        break;
      }
      const now = performance.now();

      if (lastTimeout === null) {
        lastTimeout = now;
        // Allow other work to get on the queue.
        await new Promise(resolve => setTimeout(resolve, 0));
      } else if (now - lastTimeout > this.#TIME_BUDGET) {
        // Perform a timeout with no effective wait. This clears the current
        // promise queue from the event loop.
        await new Promise(resolve => setTimeout(resolve, 0));
        addProfilerMarker();
        lastTimeout = performance.now();
      }

      // Check this between every `await`.
      if (this.#isWorkCancelled || !this.#tasks.length) {
        break;
      }

      tasksInBatch++;
      const { task, resolve, reject } = this.#tasks.shift();
      try {
        const result = await task();

        // Check this between every `await`.
        if (this.#isWorkCancelled) {
          break;
        }
        // The work is done, resolve the original task.
        resolve(result);
      } catch (error) {
        reject(error);
      }
    }
    addProfilerMarker();
    this.#isRunning = false;
  }

  async cancelWork() {
    this.#isWorkCancelled = true;
    this.#tasks = [];
    await new Promise(resolve => setTimeout(resolve, 0));
    this.#isWorkCancelled = false;
  }
}
PK
!<�G�h��;chrome/toolkit/content/global/translations/translations.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:root {
  /* Provide defaults for when this page is viewed in "toolkit". */
  background-color: var(--in-content-page-background, #fff);
  color: var(--in-content-page-color, #15141a);

  /* Provide backup values for some of the variables used in "browser" so that the styles
     look nice by default in "toolkit". */
  --AT-box-background: var(--in-content-box-background, #fff);
  --AT-box-border-color: var(--in-content-box-border-color, #9e9ea0);
  --AT-box-info-background: var(--in-content-box-info-background, #f0f0f4);

  /* Variables used in the page layout */
  --AT-page-margin: 20px;
  --AT-input-padding: 20px;
  /* This is somewhat arbitrary, but works well for the current design. If the computed
     header height changes, this will need to be adjusted. */
  --AT-header-height: 156px;
  --AT-input-height: calc(min(400px, calc(100vh - var(--AT-header-height))));
  --AT-select-arrow-inset: 5px;
}

body {
  display: flex;
  justify-content: center;
  align-items: center;
  inset: 0;
  position: absolute;
  visibility: hidden;
  flex-direction: column;
}

.about-translations-header {
  display: flex;
}

.about-translations-header > * {
  flex: 1;
  display: flex;
  max-width: 50%;
}

.about-translations-header-start {
  justify-content: start;
}

.about-translations-header-end {
  justify-content: end;
}

/* Increase the selector specificity to override the base `select` styles. */
select.about-translations-select {
  position: relative;
  padding-inline: 10px 20px;
  padding-block: 0px;
  min-width: 50%;
  margin: 5px;
  background-position: right var(--AT-select-arrow-inset) center;
}

select.about-translations-select:dir(rtl) {
  background-position-x: left var(--AT-select-arrow-inset);
}

.about-translations-contents {
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  width: calc(100% - var(--AT-page-margin) * 2);
  max-width: 1200px;
  background-color: var(--AT-box-background);
  border: 1px solid var(--AT-box-border-color);
  border-radius: 4px;
}

.about-translations-input {
  display: flex;
  width: 100%;
  border-top: 1px solid var(--AT-box-border-color);
}

.about-translations-input-start {
  border-inline-end: 1px solid var(--AT-box-border-color);
}

.about-translations-input > * {
  position: relative;
  width: 50%;
}

.about-translations-input-textarea {
  /* Override user's dragging of the textarea width. */
  width: 100% !important;
  height: var(--AT-input-height);
  box-sizing: border-box;
  margin: 0;
  padding: var(--AT-input-padding);
  border: 0;
}

.about-translations-input-results-blank {
  opacity: 0.7;
}

.about-translations-input-results {
  position: absolute;
  inset: 0;
  padding: var(--AT-input-padding);
  box-sizing: border-box;
  overflow-y: scroll;
}

.about-translations-info {
  display: none;
  padding: 10px;
  background-color: var(--AT-box-info-background);
  border-radius: 4px;
  margin-bottom: var(--AT-input-padding);
}

.about-translations-info-message {
  flex: 1;
  align-self: center;
}

.about-translations-info-icon {
  width: 16px;
  height: 16px;
  margin: 10px;
  background-image: url('chrome://global/skin/icons/info.svg');
  -moz-context-properties: fill;
  fill: currentColor;
}

@media (max-width: 700px) {
  :root {
    --AT-page-margin: 10px;
  }
  h1 {
    margin-top: 15px;
  }
  body {
    padding-bottom: var(--AT-page-margin);
  }
  .about-translations-input {
    flex-direction: column;
    flex: 1;
  }
  .about-translations-input-textarea,
  .about-translations-input {
    font-size: 16px;
  }
  .about-translations-input > * {
    width: 100%;
    flex: 1;
  }
  .about-translations-input-end {
    border-top: 1px solid var(--AT-box-border-color);
  }
  .about-translations-input-textarea {
    height: 100%;
  }
  .about-translations-contents {
    flex: 1;
  }
}
PK
!<�a��<chrome/toolkit/content/global/translations/translations.html<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="Content-Security-Policy" content="default-src chrome:; object-src 'none'">
    <meta name="color-scheme" content="light dark">
    <meta name="viewport" content="width=device-width" />
    <title data-l10n-id="about-translations-title"></title>
    <link rel="stylesheet" href="chrome://global/skin/global.css">
    <link rel="stylesheet" href="chrome://global/skin/in-content/common.css">
    <link rel="stylesheet" href="chrome://global/content/translations/translations.css">
    <link rel="localization" href="toolkit/branding/brandings.ftl"/>
    <link rel="localization" href="locales-preview/aboutTranslations.ftl"/>
    <script type="module" src="chrome://global/content/translations/translations.mjs"></script>
  </head>
  <body>
    <h1 data-l10n-id="about-translations-header"></h1>
    <main class="about-translations-contents">

      <header class="about-translations-header">
        <div class="about-translations-header-start">
          <select
            class="about-translations-select"
            id="language-from"
            disabled>
            <option data-l10n-id="about-translations-detect" value="detect"></option>
          </select>
        </div>
        <div class="about-translations-header-end">
          <select
            class="about-translations-select"
            id="language-to"
            disabled>
            <option data-l10n-id="about-translations-select" value=""></option>
          </select>
        </div>
      </header>

      <main class="about-translations-input">
        <div class="about-translations-input-start">
          <textarea
            class="about-translations-input-textarea"
            data-l10n-id="about-translations-textarea"
            id="translation-from"
            ></textarea>
        </div>
        <div class="about-translations-input-end">
          <div
            class="about-translations-input-results about-translations-input-results-blank"
            id="translation-to-blank">
            <div class="about-translations-info" id="translation-info">
              <div class="about-translations-info-icon"></div>
              <div class="about-translations-info-message" id="translation-info-message"></div>
            </div>
            <div id="translation-results-placeholder" data-l10n-id="about-translations-results-placeholder"></div>
          </div>
          <div
            class="about-translations-input-results"
            id="translation-to">
          </div>
        </div>
      </main>

    </div>
  </body>
</html>
PK
!<\�fITIT;chrome/toolkit/content/global/translations/translations.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// The following globals are injected via the AboutTranslationsChild actor.
// translations.mjs is running in an unprivileged context, and these injected functions
// allow for the page to get access to additional privileged features.

/* global AT_getSupportedLanguages, AT_log, AT_getScriptDirection,
   AT_logError, AT_createTranslationsPort, AT_isHtmlTranslation,
   AT_isTranslationEngineSupported, AT_identifyLanguage */

import { Translator } from "chrome://global/content/translations/Translator.mjs";

// Allow tests to override this value so that they can run faster.
// This is the delay in milliseconds.
window.DEBOUNCE_DELAY = 200;
// Allow tests to test the debounce behavior by counting debounce runs.
window.DEBOUNCE_RUN_COUNT = 0;

const l10nIds = {
  resultsPlaceholder: "about-translations-results-placeholder",
  translatingMessage: "about-translations-translating-message",
};

/**
 * @typedef {import("../translations").SupportedLanguages} SupportedLanguages
 */

/**
 * The model and controller for initializing about:translations.
 */
class TranslationsState {
  /**
   * This class is responsible for all UI updated.
   *
   * @type {TranslationsUI}
   */
  ui;

  /**
   * The language to translate from, in the form of a BCP 47 language tag,
   * e.g. "en" or "fr".
   *
   * @type {string}
   */
  fromLanguage = "";

  /**
   * The language to translate to, in the form of a BCP 47 language tag,
   * e.g. "en" or "fr".
   *
   * @type {string}
   */
  toLanguage = "";

  /**
   * The message to translate, cached so that it can be determined if the text
   * needs to be re-translated.
   *
   * @type {string}
   */
  messageToTranslate = "";

  /**
   * Only send one translation in at a time to the worker.
   *
   * @type {Promise<string[]>}
   */
  translationRequest = Promise.resolve([]);

  /**
   * The translator is only valid for a single language pair, and needs
   * to be recreated if the language pair changes.
   *
   * @type {null | Translator}
   */
  translator = null;

  /**
   * @param {boolean} isSupported
   */
  constructor(isSupported) {
    /**
     * Is the engine supported by the device?
     *
     * @type {boolean}
     */
    this.isTranslationEngineSupported = isSupported;

    /**
     * @type {SupportedLanguages}
     */
    this.supportedLanguages = isSupported
      ? AT_getSupportedLanguages()
      : Promise.resolve([]);

    this.ui = new TranslationsUI(this);
    this.ui.setup();

    // Set the UI as ready after all of the state promises have settled.
    this.supportedLanguages
      .then(() => {
        this.ui.setAsReady();
      })
      .catch(error => {
        AT_logError("Failed to load the supported languages", error);
      });
  }

  /**
   * Identifies the human language in which the message is written and returns
   * the BCP 47 language tag of the language it is determined to be.
   *
   * e.g. "en" for English.
   *
   * @param {string} message
   */
  async identifyLanguage(message) {
    const start = performance.now();
    const { langTag, confidence } = await AT_identifyLanguage(message);
    const duration = performance.now() - start;
    AT_log(
      `[ ${langTag}(${(confidence * 100).toFixed(2)}%) ]`,
      `Source language identified in ${duration / 1000} seconds`
    );
    return langTag;
  }

  /**
   * Only request a translation when it's ready.
   */
  maybeRequestTranslation = debounce({
    /**
     * Debounce the translation requests so that the worker doesn't fire for every
     * single keyboard input, but instead the keyboard events are ignored until
     * there is a short break, or enough events have happened that it's worth sending
     * in a new translation request.
     */
    onDebounce: async () => {
      // The contents of "this" can change between async steps, store a local variable
      // binding of these values.
      const { fromLanguage, toLanguage, messageToTranslate, translator } = this;

      if (!this.isTranslationEngineSupported) {
        // Never translate when the engine isn't supported.
        return;
      }

      if (!fromLanguage || !toLanguage || !messageToTranslate || !translator) {
        // Not everything is set for translation.
        this.ui.updateTranslation("");
        return;
      }

      // Ensure the previous translation has finished so that only the latest
      // translation goes through.
      await this.translationRequest;

      if (
        // Check if the current configuration has changed and if this is stale. If so
        // then skip this request, as there is already a newer request with more up to
        // date information.
        this.translator !== translator ||
        this.fromLanguage !== fromLanguage ||
        this.toLanguage !== toLanguage ||
        this.messageToTranslate !== messageToTranslate
      ) {
        return;
      }

      const start = performance.now();
      this.translationRequest = this.translator.translate(
        messageToTranslate,
        AT_isHtmlTranslation()
      );
      this.ui.setResultPlaceholderTextContent(l10nIds.translatingMessage);
      const translation = await this.translationRequest;
      this.ui.setResultPlaceholderTextContent(l10nIds.resultsPlaceholder);

      // The measure events will show up in the Firefox Profiler.
      performance.measure(
        `Translations: Translate "${this.fromLanguage}" to "${this.toLanguage}" with ${messageToTranslate.length} characters.`,
        {
          start,
          end: performance.now(),
        }
      );

      this.ui.updateTranslation(translation);
      const duration = performance.now() - start;
      AT_log(`Translation done in ${duration / 1000} seconds`);
    },

    // Mark the events so that they show up in the Firefox Profiler. This makes it handy
    // to visualize the debouncing behavior.
    doEveryTime: () => {
      performance.mark(
        `Translations: input changed to ${this.messageToTranslate.length} characters`
      );
    },
  });

  /**
   * Any time a language pair is changed, a new Translator needs to be created.
   */
  async maybeCreateNewTranslator() {
    // If we may need to re-building the worker, the old translation is no longer valid.
    this.ui.updateTranslation("");

    // These are cases in which it wouldn't make sense or be possible to load any translations models.
    if (
      // If fromLanguage or toLanguage are unpopulated we cannot load anything.
      !this.fromLanguage ||
      !this.toLanguage ||
      // If fromLanguage's value is "detect", rather than a BCP 47 language tag, then no language
      // has been detected yet.
      this.fromLanguage === "detect" ||
      // If fromLanguage and toLanguage are the same, this means that the detected language
      // is the same as the toLanguage, and we do not want to translate from one language to itself.
      this.fromLanguage === this.toLanguage
    ) {
      if (this.translator) {
        // The engine is no longer needed.
        this.translator.destroy();
        this.translator = null;
      }
      return;
    }

    const start = performance.now();
    AT_log(
      `Creating a new translator for "${this.fromLanguage}" to "${this.toLanguage}"`
    );

    const translationPortPromise = (fromLanguage, toLanguage) => {
      const { promise, resolve } = Promise.withResolvers();

      const getResponse = ({ data }) => {
        if (
          data.type == "GetTranslationsPort" &&
          data.fromLanguage === fromLanguage &&
          data.toLanguage === toLanguage
        ) {
          window.removeEventListener("message", getResponse);
          resolve(data.port);
        }
      };

      window.addEventListener("message", getResponse);
      AT_createTranslationsPort(fromLanguage, toLanguage);

      return promise;
    };

    try {
      const translatorPromise = Translator.create(
        this.fromLanguage,
        this.toLanguage,
        {
          allowSameLanguage: false,
          requestTranslationsPort: translationPortPromise,
        }
      );
      const duration = performance.now() - start;

      // Signal to tests that the translator was created so they can exit.
      window.postMessage("translator-ready");
      AT_log(`Created a new Translator in ${duration / 1000} seconds`);

      this.translator = await translatorPromise;
      this.maybeRequestTranslation();
    } catch (error) {
      this.ui.showInfo("about-translations-engine-error");
      this.ui.setResultPlaceholderTextContent(l10nIds.resultsPlaceholder);
      AT_logError("Failed to get the Translations worker", error);
    }
  }

  /**
   * Updates the fromLanguage to match the detected language only if the
   * about-translations-detect option is selected in the language-from dropdown.
   *
   * If the new fromLanguage is different than the previous fromLanguage this
   * may update the UI to display the new language and may rebuild the translations
   * worker if there is a valid selected target language.
   */
  async maybeUpdateDetectedLanguage() {
    if (!this.ui.detectOptionIsSelected() || this.messageToTranslate === "") {
      // If we are not detecting languages or if the message has been cleared
      // we should ensure that the UI is not displaying a detected language
      // and there is no need to run any language detection.
      this.ui.setDetectOptionTextContent("");
      return;
    }

    const [langTag, supportedLanguages] = await Promise.all([
      this.identifyLanguage(this.messageToTranslate),
      this.supportedLanguages,
    ]);

    // Only update the language if the detected language matches
    // one of our supported languages.
    const entry = supportedLanguages.fromLanguages.find(
      ({ langTag: existingTag }) => existingTag === langTag
    );
    if (entry) {
      const { displayName } = entry;
      await this.setFromLanguage(langTag);
      this.ui.setDetectOptionTextContent(displayName);
    }
  }

  /**
   * @param {string} lang
   */
  async setFromLanguage(lang) {
    if (lang !== this.fromLanguage) {
      this.fromLanguage = lang;
      await this.maybeCreateNewTranslator();
    }
  }

  /**
   * @param {string} lang
   */
  setToLanguage(lang) {
    if (lang !== this.toLanguage) {
      this.toLanguage = lang;
      this.maybeCreateNewTranslator();
    }
  }

  /**
   * @param {string} message
   */
  async setMessageToTranslate(message) {
    if (message !== this.messageToTranslate) {
      this.messageToTranslate = message;
      await this.maybeUpdateDetectedLanguage();
      this.maybeRequestTranslation();
    }
  }
}

/**
 *
 */
class TranslationsUI {
  /** @type {HTMLSelectElement} */
  languageFrom = document.getElementById("language-from");
  /** @type {HTMLSelectElement} */
  languageTo = document.getElementById("language-to");
  /** @type {HTMLTextAreaElement} */
  translationFrom = document.getElementById("translation-from");
  /** @type {HTMLDivElement} */
  translationTo = document.getElementById("translation-to");
  /** @type {HTMLDivElement} */
  translationToBlank = document.getElementById("translation-to-blank");
  /** @type {HTMLDivElement} */
  translationInfo = document.getElementById("translation-info");
  /** @type {HTMLDivElement} */
  translationInfoMessage = document.getElementById("translation-info-message");
  /** @type {HTMLDivElement} */
  translationResultsPlaceholder = document.getElementById(
    "translation-results-placeholder"
  );
  /** @type {TranslationsState} */
  state;

  /**
   * The detect-language option element. We want to maintain a handle to this so that
   * we can dynamically update its display text to include the detected language.
   *
   * @type {HTMLOptionElement}
   */
  #detectOption;

  /**
   * @param {TranslationsState} state
   */
  constructor(state) {
    this.state = state;
    this.translationTo.style.visibility = "visible";
    this.#detectOption = document.querySelector('option[value="detect"]');
  }

  /**
   * Do the initial setup.
   */
  setup() {
    if (!this.state.isTranslationEngineSupported) {
      this.showInfo("about-translations-no-support");
      this.disableUI();
      return;
    }
    this.setupDropdowns();
    this.setupTextarea();
  }

  /**
   * Signals that the UI is ready, for tests.
   */
  setAsReady() {
    document.body.setAttribute("ready", "");
  }

  /**
   * Once the models have been synced from remote settings, populate them with the display
   * names of the languages.
   */
  async setupDropdowns() {
    const supportedLanguages = await this.state.supportedLanguages;

    // Update the DOM elements with the display names.
    for (const { langTag, displayName } of supportedLanguages.toLanguages) {
      const option = document.createElement("option");
      option.value = langTag;
      option.text = displayName;
      this.languageTo.add(option);
    }

    for (const { langTag, displayName } of supportedLanguages.fromLanguages) {
      const option = document.createElement("option");
      option.value = langTag;
      option.text = displayName;
      this.languageFrom.add(option);
    }

    // Enable the controls.
    this.languageFrom.disabled = false;
    this.languageTo.disabled = false;

    // Focus the language dropdowns if they are empty.
    if (this.languageFrom.value == "") {
      this.languageFrom.focus();
    } else if (this.languageTo.value == "") {
      this.languageTo.focus();
    }

    this.state.setFromLanguage(this.languageFrom.value);
    this.state.setToLanguage(this.languageTo.value);
    this.updateOnLanguageChange();

    this.languageFrom.addEventListener("input", () => {
      this.state.setFromLanguage(this.languageFrom.value);
      this.updateOnLanguageChange();
    });

    this.languageTo.addEventListener("input", () => {
      this.state.setToLanguage(this.languageTo.value);
      this.updateOnLanguageChange();
      this.translationTo.setAttribute("lang", this.languageTo.value);
    });
  }

  /**
   * Show an info message to the user.
   *
   * @param {string} l10nId
   */
  showInfo(l10nId) {
    document.l10n.setAttributes(this.translationInfoMessage, l10nId);
    this.translationInfo.style.display = "flex";
  }

  /**
   * Hides the info UI.
   */
  hideInfo() {
    this.translationInfo.style.display = "none";
  }

  /**
   * Returns true if about-translations-detect is the currently
   * selected option in the language-from dropdown, otherwise false.
   *
   * @returns {boolean}
   */
  detectOptionIsSelected() {
    return this.languageFrom.value === "detect";
  }

  /**
   * Sets the textContent of the about-translations-detect option in the
   * language-from dropdown to include the detected language's display name.
   *
   * @param {string} displayName
   */
  setDetectOptionTextContent(displayName) {
    // Set the text to the fluent value that takes an arg to display the language name.
    if (displayName) {
      document.l10n.setAttributes(
        this.#detectOption,
        "about-translations-detect-lang",
        { language: displayName }
      );
    } else {
      // Reset the text to the fluent value that does not display any language name.
      document.l10n.setAttributes(
        this.#detectOption,
        "about-translations-detect"
      );
    }
  }

  /**
   * Sets the translation result placeholder text based on the l10n id provided
   *
   * @param {string} l10nId
   */
  setResultPlaceholderTextContent(l10nId) {
    document.l10n.setAttributes(this.translationResultsPlaceholder, l10nId);
  }

  /**
   * React to language changes.
   */
  updateOnLanguageChange() {
    this.#updateDropdownLanguages();
    this.#updateMessageDirections();
  }

  /**
   * You cant translate from one language to another language. Hide the options
   * if this is the case.
   */
  #updateDropdownLanguages() {
    for (const option of this.languageFrom.options) {
      option.hidden = false;
    }
    for (const option of this.languageTo.options) {
      option.hidden = false;
    }
    if (this.state.toLanguage) {
      const option = this.languageFrom.querySelector(
        `[value=${this.state.toLanguage}]`
      );
      if (option) {
        option.hidden = true;
      }
    }
    if (this.state.fromLanguage) {
      const option = this.languageTo.querySelector(
        `[value=${this.state.fromLanguage}]`
      );
      if (option) {
        option.hidden = true;
      }
    }
    this.state.maybeUpdateDetectedLanguage();
  }

  /**
   * Define the direction of the language message text, otherwise it might not display
   * correctly. For instance English in an RTL UI would display incorrectly like so:
   *
   * LTR text in LTR UI:
   *
   * ┌──────────────────────────────────────────────┐
   * │ This is in English.                          │
   * └──────────────────────────────────────────────┘
   *
   * LTR text in RTL UI:
   * ┌──────────────────────────────────────────────┐
   * │                          .This is in English │
   * └──────────────────────────────────────────────┘
   *
   * LTR text in RTL UI, but in an LTR container:
   * ┌──────────────────────────────────────────────┐
   * │ This is in English.                          │
   * └──────────────────────────────────────────────┘
   *
   * The effects are similar, but reversed for RTL text in an LTR UI.
   */
  #updateMessageDirections() {
    if (this.state.toLanguage) {
      this.translationTo.setAttribute(
        "dir",
        AT_getScriptDirection(this.state.toLanguage)
      );
    } else {
      this.translationTo.removeAttribute("dir");
    }
    if (this.state.fromLanguage) {
      this.translationFrom.setAttribute(
        "dir",
        AT_getScriptDirection(this.state.fromLanguage)
      );
    } else {
      this.translationFrom.removeAttribute("dir");
    }
  }

  setupTextarea() {
    this.state.setMessageToTranslate(this.translationFrom.value);
    this.translationFrom.addEventListener("input", () => {
      this.state.setMessageToTranslate(this.translationFrom.value);
    });
  }

  disableUI() {
    this.translationFrom.disabled = true;
    this.languageFrom.disabled = true;
    this.languageTo.disabled = true;
  }

  /**
   * @param {string} message
   */
  updateTranslation(message) {
    this.translationTo.innerText = message;
    if (message) {
      this.translationTo.style.visibility = "visible";
      this.translationToBlank.style.visibility = "hidden";
      this.hideInfo();
    } else {
      this.translationTo.style.visibility = "hidden";
      this.translationToBlank.style.visibility = "visible";
    }
  }
}

/**
 * Listen for events coming from the AboutTranslations actor.
 */
window.addEventListener("AboutTranslationsChromeToContent", ({ detail }) => {
  switch (detail.type) {
    case "enable": {
      // While the feature is in development, hide the feature behind a pref. See the
      // "browser.translations.enable" pref in modules/libpref/init/all.js and Bug 971044
      // for the status of enabling this project.
      if (window.translationsState) {
        throw new Error("about:translations was already initialized.");
      }
      AT_isTranslationEngineSupported().then(isSupported => {
        window.translationsState = new TranslationsState(isSupported);
      });
      document.body.style.visibility = "visible";
      break;
    }
    default:
      throw new Error("Unknown AboutTranslationsChromeToContent event.");
  }
});

/**
 * Debounce a function so that it is only called after some wait time with no activity.
 * This is good for grouping text entry via keyboard.
 *
 * @param {object} settings
 * @param {Function} settings.onDebounce
 * @param {Function} settings.doEveryTime
 * @returns {Function}
 */
function debounce({ onDebounce, doEveryTime }) {
  /** @type {number | null} */
  let timeoutId = null;
  let lastDispatch = null;

  return (...args) => {
    doEveryTime(...args);

    const now = Date.now();
    if (lastDispatch === null) {
      // This is the first call to the function.
      lastDispatch = now;
    }

    const timeLeft = lastDispatch + window.DEBOUNCE_DELAY - now;

    // Always discard the old timeout, either the function will run, or a new
    // timer will be scheduled.
    clearTimeout(timeoutId);

    if (timeLeft <= 0) {
      // It's been long enough to go ahead and call the function.
      timeoutId = null;
      lastDispatch = null;
      window.DEBOUNCE_RUN_COUNT += 1;
      onDebounce(...args);
      return;
    }

    // Re-set the timeout with the current time left.
    clearTimeout(timeoutId);

    timeoutId = setTimeout(() => {
      // Timeout ended, call the function.
      timeoutId = null;
      lastDispatch = null;
      window.DEBOUNCE_RUN_COUNT += 1;
      onDebounce(...args);
    }, timeLeft);
  };
}
PK
!<�&���*chrome/toolkit/content/global/treeUtils.js// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

var gTreeUtils = {
  deleteAll(aTree, aView, aItems, aDeletedItems) {
    for (var i = 0; i < aItems.length; ++i) {
      aDeletedItems.push(aItems[i]);
    }
    aItems.splice(0, aItems.length);
    var oldCount = aView.rowCount;
    aView._rowCount = 0;
    aTree.rowCountChanged(0, -oldCount);
  },

  deleteSelectedItems(aTree, aView, aItems, aDeletedItems) {
    var selection = aTree.view.selection;
    selection.selectEventsSuppressed = true;

    var rc = selection.getRangeCount();
    for (var i = 0; i < rc; ++i) {
      var min = {};
      var max = {};
      selection.getRangeAt(i, min, max);
      for (let j = min.value; j <= max.value; ++j) {
        aDeletedItems.push(aItems[j]);
        aItems[j] = null;
      }
    }

    var nextSelection = 0;
    for (i = 0; i < aItems.length; ++i) {
      if (!aItems[i]) {
        let j = i;
        while (j < aItems.length && !aItems[j]) {
          ++j;
        }
        aItems.splice(i, j - i);
        nextSelection = j < aView.rowCount ? j - 1 : j - 2;
        aView._rowCount -= j - i;
        aTree.rowCountChanged(i, i - j);
      }
    }

    if (aItems.length) {
      selection.select(nextSelection);
      aTree.ensureRowIsVisible(nextSelection);
      aTree.focus();
    }
    selection.selectEventsSuppressed = false;
  },

  sort(
    aTree,
    aView,
    aDataSet,
    aColumn,
    aComparator,
    aLastSortColumn,
    aLastSortAscending
  ) {
    var ascending = aColumn == aLastSortColumn ? !aLastSortAscending : true;
    if (!aDataSet.length) {
      return ascending;
    }

    var sortFunction = null;
    if (aComparator) {
      sortFunction = function (a, b) {
        return aComparator(a[aColumn], b[aColumn]);
      };
    }
    aDataSet.sort(sortFunction);
    if (!ascending) {
      aDataSet.reverse();
    }

    aTree.view.selection.clearSelection();
    aTree.view.selection.select(0);
    aTree.invalidate();
    aTree.ensureRowIsVisible(0);

    return ascending;
  },
};
PK
!<՟�����>chrome/toolkit/content/global/usercharacteristics/gl-matrix.js/*!
@fileoverview gl-matrix - High performance matrix and vector operations
@author Brandon Jones
@author Colin MacKenzie IV
@version 2.7.0

Copyright (c) 2015-2018, Brandon Jones, Colin MacKenzie IV.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/
!function(t,n){if("object"==typeof exports&&"object"==typeof module)module.exports=n();else if("function"==typeof define&&define.amd)define([],n);else{var r=n();for(var a in r)("object"==typeof exports?exports:t)[a]=r[a]}}("undefined"!=typeof self?self:this,function(){return function(t){var n={};function r(a){if(n[a])return n[a].exports;var e=n[a]={i:a,l:!1,exports:{}};return t[a].call(e.exports,e,e.exports,r),e.l=!0,e.exports}return r.m=t,r.c=n,r.d=function(t,n,a){r.o(t,n)||Object.defineProperty(t,n,{enumerable:!0,get:a})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,n){if(1&n&&(t=r(t)),8&n)return t;if(4&n&&"object"==typeof t&&t&&t.__esModule)return t;var a=Object.create(null);if(r.r(a),Object.defineProperty(a,"default",{enumerable:!0,value:t}),2&n&&"string"!=typeof t)for(var e in t)r.d(a,e,function(n){return t[n]}.bind(null,e));return a},r.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(n,"a",n),n},r.o=function(t,n){return Object.prototype.hasOwnProperty.call(t,n)},r.p="",r(r.s=10)}([function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.setMatrixArrayType=function(t){n.ARRAY_TYPE=t},n.toRadian=function(t){return t*e},n.equals=function(t,n){return Math.abs(t-n)<=a*Math.max(1,Math.abs(t),Math.abs(n))};var a=n.EPSILON=1e-6;n.ARRAY_TYPE="undefined"!=typeof Float32Array?Float32Array:Array,n.RANDOM=Math.random;var e=Math.PI/180},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.forEach=n.sqrLen=n.len=n.sqrDist=n.dist=n.div=n.mul=n.sub=void 0,n.create=e,n.clone=function(t){var n=new a.ARRAY_TYPE(4);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n[3]=t[3],n},n.fromValues=function(t,n,r,e){var u=new a.ARRAY_TYPE(4);return u[0]=t,u[1]=n,u[2]=r,u[3]=e,u},n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t},n.set=function(t,n,r,a,e){return t[0]=n,t[1]=r,t[2]=a,t[3]=e,t},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t[3]=n[3]+r[3],t},n.subtract=u,n.multiply=o,n.divide=i,n.ceil=function(t,n){return t[0]=Math.ceil(n[0]),t[1]=Math.ceil(n[1]),t[2]=Math.ceil(n[2]),t[3]=Math.ceil(n[3]),t},n.floor=function(t,n){return t[0]=Math.floor(n[0]),t[1]=Math.floor(n[1]),t[2]=Math.floor(n[2]),t[3]=Math.floor(n[3]),t},n.min=function(t,n,r){return t[0]=Math.min(n[0],r[0]),t[1]=Math.min(n[1],r[1]),t[2]=Math.min(n[2],r[2]),t[3]=Math.min(n[3],r[3]),t},n.max=function(t,n,r){return t[0]=Math.max(n[0],r[0]),t[1]=Math.max(n[1],r[1]),t[2]=Math.max(n[2],r[2]),t[3]=Math.max(n[3],r[3]),t},n.round=function(t,n){return t[0]=Math.round(n[0]),t[1]=Math.round(n[1]),t[2]=Math.round(n[2]),t[3]=Math.round(n[3]),t},n.scale=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=n[3]*r,t},n.scaleAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t[3]=n[3]+r[3]*a,t},n.distance=s,n.squaredDistance=c,n.length=f,n.squaredLength=M,n.negate=function(t,n){return t[0]=-n[0],t[1]=-n[1],t[2]=-n[2],t[3]=-n[3],t},n.inverse=function(t,n){return t[0]=1/n[0],t[1]=1/n[1],t[2]=1/n[2],t[3]=1/n[3],t},n.normalize=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r*r+a*a+e*e+u*u;o>0&&(o=1/Math.sqrt(o),t[0]=r*o,t[1]=a*o,t[2]=e*o,t[3]=u*o);return t},n.dot=function(t,n){return t[0]*n[0]+t[1]*n[1]+t[2]*n[2]+t[3]*n[3]},n.lerp=function(t,n,r,a){var e=n[0],u=n[1],o=n[2],i=n[3];return t[0]=e+a*(r[0]-e),t[1]=u+a*(r[1]-u),t[2]=o+a*(r[2]-o),t[3]=i+a*(r[3]-i),t},n.random=function(t,n){var r,e,u,o,i,s;n=n||1;do{r=2*a.RANDOM()-1,e=2*a.RANDOM()-1,i=r*r+e*e}while(i>=1);do{u=2*a.RANDOM()-1,o=2*a.RANDOM()-1,s=u*u+o*o}while(s>=1);var c=Math.sqrt((1-i)/s);return t[0]=n*r,t[1]=n*e,t[2]=n*u*c,t[3]=n*o*c,t},n.transformMat4=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3];return t[0]=r[0]*a+r[4]*e+r[8]*u+r[12]*o,t[1]=r[1]*a+r[5]*e+r[9]*u+r[13]*o,t[2]=r[2]*a+r[6]*e+r[10]*u+r[14]*o,t[3]=r[3]*a+r[7]*e+r[11]*u+r[15]*o,t},n.transformQuat=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=r[0],i=r[1],s=r[2],c=r[3],f=c*a+i*u-s*e,M=c*e+s*a-o*u,h=c*u+o*e-i*a,l=-o*a-i*e-s*u;return t[0]=f*c+l*-o+M*-s-h*-i,t[1]=M*c+l*-i+h*-o-f*-s,t[2]=h*c+l*-s+f*-i-M*-o,t[3]=n[3],t},n.str=function(t){return"vec4("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+")"},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=t[3],i=n[0],s=n[1],c=n[2],f=n[3];return Math.abs(r-i)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(i))&&Math.abs(e-s)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(s))&&Math.abs(u-c)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(c))&&Math.abs(o-f)<=a.EPSILON*Math.max(1,Math.abs(o),Math.abs(f))};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(){var t=new a.ARRAY_TYPE(4);return a.ARRAY_TYPE!=Float32Array&&(t[0]=0,t[1]=0,t[2]=0,t[3]=0),t}function u(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t[3]=n[3]-r[3],t}function o(t,n,r){return t[0]=n[0]*r[0],t[1]=n[1]*r[1],t[2]=n[2]*r[2],t[3]=n[3]*r[3],t}function i(t,n,r){return t[0]=n[0]/r[0],t[1]=n[1]/r[1],t[2]=n[2]/r[2],t[3]=n[3]/r[3],t}function s(t,n){var r=n[0]-t[0],a=n[1]-t[1],e=n[2]-t[2],u=n[3]-t[3];return Math.sqrt(r*r+a*a+e*e+u*u)}function c(t,n){var r=n[0]-t[0],a=n[1]-t[1],e=n[2]-t[2],u=n[3]-t[3];return r*r+a*a+e*e+u*u}function f(t){var n=t[0],r=t[1],a=t[2],e=t[3];return Math.sqrt(n*n+r*r+a*a+e*e)}function M(t){var n=t[0],r=t[1],a=t[2],e=t[3];return n*n+r*r+a*a+e*e}n.sub=u,n.mul=o,n.div=i,n.dist=s,n.sqrDist=c,n.len=f,n.sqrLen=M,n.forEach=function(){var t=e();return function(n,r,a,e,u,o){var i=void 0,s=void 0;for(r||(r=4),a||(a=0),s=e?Math.min(e*r+a,n.length):n.length,i=a;i<s;i+=r)t[0]=n[i],t[1]=n[i+1],t[2]=n[i+2],t[3]=n[i+3],u(t,t,o),n[i]=t[0],n[i+1]=t[1],n[i+2]=t[2],n[i+3]=t[3];return n}}()},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.forEach=n.sqrLen=n.len=n.sqrDist=n.dist=n.div=n.mul=n.sub=void 0,n.create=e,n.clone=function(t){var n=new a.ARRAY_TYPE(3);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n},n.length=u,n.fromValues=o,n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t},n.set=function(t,n,r,a){return t[0]=n,t[1]=r,t[2]=a,t},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t},n.subtract=i,n.multiply=s,n.divide=c,n.ceil=function(t,n){return t[0]=Math.ceil(n[0]),t[1]=Math.ceil(n[1]),t[2]=Math.ceil(n[2]),t},n.floor=function(t,n){return t[0]=Math.floor(n[0]),t[1]=Math.floor(n[1]),t[2]=Math.floor(n[2]),t},n.min=function(t,n,r){return t[0]=Math.min(n[0],r[0]),t[1]=Math.min(n[1],r[1]),t[2]=Math.min(n[2],r[2]),t},n.max=function(t,n,r){return t[0]=Math.max(n[0],r[0]),t[1]=Math.max(n[1],r[1]),t[2]=Math.max(n[2],r[2]),t},n.round=function(t,n){return t[0]=Math.round(n[0]),t[1]=Math.round(n[1]),t[2]=Math.round(n[2]),t},n.scale=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t},n.scaleAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t},n.distance=f,n.squaredDistance=M,n.squaredLength=h,n.negate=function(t,n){return t[0]=-n[0],t[1]=-n[1],t[2]=-n[2],t},n.inverse=function(t,n){return t[0]=1/n[0],t[1]=1/n[1],t[2]=1/n[2],t},n.normalize=l,n.dot=v,n.cross=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=r[0],i=r[1],s=r[2];return t[0]=e*s-u*i,t[1]=u*o-a*s,t[2]=a*i-e*o,t},n.lerp=function(t,n,r,a){var e=n[0],u=n[1],o=n[2];return t[0]=e+a*(r[0]-e),t[1]=u+a*(r[1]-u),t[2]=o+a*(r[2]-o),t},n.hermite=function(t,n,r,a,e,u){var o=u*u,i=o*(2*u-3)+1,s=o*(u-2)+u,c=o*(u-1),f=o*(3-2*u);return t[0]=n[0]*i+r[0]*s+a[0]*c+e[0]*f,t[1]=n[1]*i+r[1]*s+a[1]*c+e[1]*f,t[2]=n[2]*i+r[2]*s+a[2]*c+e[2]*f,t},n.bezier=function(t,n,r,a,e,u){var o=1-u,i=o*o,s=u*u,c=i*o,f=3*u*i,M=3*s*o,h=s*u;return t[0]=n[0]*c+r[0]*f+a[0]*M+e[0]*h,t[1]=n[1]*c+r[1]*f+a[1]*M+e[1]*h,t[2]=n[2]*c+r[2]*f+a[2]*M+e[2]*h,t},n.random=function(t,n){n=n||1;var r=2*a.RANDOM()*Math.PI,e=2*a.RANDOM()-1,u=Math.sqrt(1-e*e)*n;return t[0]=Math.cos(r)*u,t[1]=Math.sin(r)*u,t[2]=e*n,t},n.transformMat4=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=r[3]*a+r[7]*e+r[11]*u+r[15];return o=o||1,t[0]=(r[0]*a+r[4]*e+r[8]*u+r[12])/o,t[1]=(r[1]*a+r[5]*e+r[9]*u+r[13])/o,t[2]=(r[2]*a+r[6]*e+r[10]*u+r[14])/o,t},n.transformMat3=function(t,n,r){var a=n[0],e=n[1],u=n[2];return t[0]=a*r[0]+e*r[3]+u*r[6],t[1]=a*r[1]+e*r[4]+u*r[7],t[2]=a*r[2]+e*r[5]+u*r[8],t},n.transformQuat=function(t,n,r){var a=r[0],e=r[1],u=r[2],o=r[3],i=n[0],s=n[1],c=n[2],f=e*c-u*s,M=u*i-a*c,h=a*s-e*i,l=e*h-u*M,v=u*f-a*h,d=a*M-e*f,b=2*o;return f*=b,M*=b,h*=b,l*=2,v*=2,d*=2,t[0]=i+f+l,t[1]=s+M+v,t[2]=c+h+d,t},n.rotateX=function(t,n,r,a){var e=[],u=[];return e[0]=n[0]-r[0],e[1]=n[1]-r[1],e[2]=n[2]-r[2],u[0]=e[0],u[1]=e[1]*Math.cos(a)-e[2]*Math.sin(a),u[2]=e[1]*Math.sin(a)+e[2]*Math.cos(a),t[0]=u[0]+r[0],t[1]=u[1]+r[1],t[2]=u[2]+r[2],t},n.rotateY=function(t,n,r,a){var e=[],u=[];return e[0]=n[0]-r[0],e[1]=n[1]-r[1],e[2]=n[2]-r[2],u[0]=e[2]*Math.sin(a)+e[0]*Math.cos(a),u[1]=e[1],u[2]=e[2]*Math.cos(a)-e[0]*Math.sin(a),t[0]=u[0]+r[0],t[1]=u[1]+r[1],t[2]=u[2]+r[2],t},n.rotateZ=function(t,n,r,a){var e=[],u=[];return e[0]=n[0]-r[0],e[1]=n[1]-r[1],e[2]=n[2]-r[2],u[0]=e[0]*Math.cos(a)-e[1]*Math.sin(a),u[1]=e[0]*Math.sin(a)+e[1]*Math.cos(a),u[2]=e[2],t[0]=u[0]+r[0],t[1]=u[1]+r[1],t[2]=u[2]+r[2],t},n.angle=function(t,n){var r=o(t[0],t[1],t[2]),a=o(n[0],n[1],n[2]);l(r,r),l(a,a);var e=v(r,a);return e>1?0:e<-1?Math.PI:Math.acos(e)},n.str=function(t){return"vec3("+t[0]+", "+t[1]+", "+t[2]+")"},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=n[0],i=n[1],s=n[2];return Math.abs(r-o)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(o))&&Math.abs(e-i)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(i))&&Math.abs(u-s)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(s))};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(){var t=new a.ARRAY_TYPE(3);return a.ARRAY_TYPE!=Float32Array&&(t[0]=0,t[1]=0,t[2]=0),t}function u(t){var n=t[0],r=t[1],a=t[2];return Math.sqrt(n*n+r*r+a*a)}function o(t,n,r){var e=new a.ARRAY_TYPE(3);return e[0]=t,e[1]=n,e[2]=r,e}function i(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t}function s(t,n,r){return t[0]=n[0]*r[0],t[1]=n[1]*r[1],t[2]=n[2]*r[2],t}function c(t,n,r){return t[0]=n[0]/r[0],t[1]=n[1]/r[1],t[2]=n[2]/r[2],t}function f(t,n){var r=n[0]-t[0],a=n[1]-t[1],e=n[2]-t[2];return Math.sqrt(r*r+a*a+e*e)}function M(t,n){var r=n[0]-t[0],a=n[1]-t[1],e=n[2]-t[2];return r*r+a*a+e*e}function h(t){var n=t[0],r=t[1],a=t[2];return n*n+r*r+a*a}function l(t,n){var r=n[0],a=n[1],e=n[2],u=r*r+a*a+e*e;return u>0&&(u=1/Math.sqrt(u),t[0]=n[0]*u,t[1]=n[1]*u,t[2]=n[2]*u),t}function v(t,n){return t[0]*n[0]+t[1]*n[1]+t[2]*n[2]}n.sub=i,n.mul=s,n.div=c,n.dist=f,n.sqrDist=M,n.len=u,n.sqrLen=h,n.forEach=function(){var t=e();return function(n,r,a,e,u,o){var i=void 0,s=void 0;for(r||(r=3),a||(a=0),s=e?Math.min(e*r+a,n.length):n.length,i=a;i<s;i+=r)t[0]=n[i],t[1]=n[i+1],t[2]=n[i+2],u(t,t,o),n[i]=t[0],n[i+1]=t[1],n[i+2]=t[2];return n}}()},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.setAxes=n.sqlerp=n.rotationTo=n.equals=n.exactEquals=n.normalize=n.sqrLen=n.squaredLength=n.len=n.length=n.lerp=n.dot=n.scale=n.mul=n.add=n.set=n.copy=n.fromValues=n.clone=void 0,n.create=s,n.identity=function(t){return t[0]=0,t[1]=0,t[2]=0,t[3]=1,t},n.setAxisAngle=c,n.getAxisAngle=function(t,n){var r=2*Math.acos(n[3]),e=Math.sin(r/2);e>a.EPSILON?(t[0]=n[0]/e,t[1]=n[1]/e,t[2]=n[2]/e):(t[0]=1,t[1]=0,t[2]=0);return r},n.multiply=f,n.rotateX=function(t,n,r){r*=.5;var a=n[0],e=n[1],u=n[2],o=n[3],i=Math.sin(r),s=Math.cos(r);return t[0]=a*s+o*i,t[1]=e*s+u*i,t[2]=u*s-e*i,t[3]=o*s-a*i,t},n.rotateY=function(t,n,r){r*=.5;var a=n[0],e=n[1],u=n[2],o=n[3],i=Math.sin(r),s=Math.cos(r);return t[0]=a*s-u*i,t[1]=e*s+o*i,t[2]=u*s+a*i,t[3]=o*s-e*i,t},n.rotateZ=function(t,n,r){r*=.5;var a=n[0],e=n[1],u=n[2],o=n[3],i=Math.sin(r),s=Math.cos(r);return t[0]=a*s+e*i,t[1]=e*s-a*i,t[2]=u*s+o*i,t[3]=o*s-u*i,t},n.calculateW=function(t,n){var r=n[0],a=n[1],e=n[2];return t[0]=r,t[1]=a,t[2]=e,t[3]=Math.sqrt(Math.abs(1-r*r-a*a-e*e)),t},n.slerp=M,n.random=function(t){var n=a.RANDOM(),r=a.RANDOM(),e=a.RANDOM(),u=Math.sqrt(1-n),o=Math.sqrt(n);return t[0]=u*Math.sin(2*Math.PI*r),t[1]=u*Math.cos(2*Math.PI*r),t[2]=o*Math.sin(2*Math.PI*e),t[3]=o*Math.cos(2*Math.PI*e),t},n.invert=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r*r+a*a+e*e+u*u,i=o?1/o:0;return t[0]=-r*i,t[1]=-a*i,t[2]=-e*i,t[3]=u*i,t},n.conjugate=function(t,n){return t[0]=-n[0],t[1]=-n[1],t[2]=-n[2],t[3]=n[3],t},n.fromMat3=h,n.fromEuler=function(t,n,r,a){var e=.5*Math.PI/180;n*=e,r*=e,a*=e;var u=Math.sin(n),o=Math.cos(n),i=Math.sin(r),s=Math.cos(r),c=Math.sin(a),f=Math.cos(a);return t[0]=u*s*f-o*i*c,t[1]=o*i*f+u*s*c,t[2]=o*s*c-u*i*f,t[3]=o*s*f+u*i*c,t},n.str=function(t){return"quat("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+")"};var a=i(r(0)),e=i(r(5)),u=i(r(2)),o=i(r(1));function i(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}function s(){var t=new a.ARRAY_TYPE(4);return a.ARRAY_TYPE!=Float32Array&&(t[0]=0,t[1]=0,t[2]=0),t[3]=1,t}function c(t,n,r){r*=.5;var a=Math.sin(r);return t[0]=a*n[0],t[1]=a*n[1],t[2]=a*n[2],t[3]=Math.cos(r),t}function f(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=r[0],s=r[1],c=r[2],f=r[3];return t[0]=a*f+o*i+e*c-u*s,t[1]=e*f+o*s+u*i-a*c,t[2]=u*f+o*c+a*s-e*i,t[3]=o*f-a*i-e*s-u*c,t}function M(t,n,r,e){var u=n[0],o=n[1],i=n[2],s=n[3],c=r[0],f=r[1],M=r[2],h=r[3],l=void 0,v=void 0,d=void 0,b=void 0,m=void 0;return(v=u*c+o*f+i*M+s*h)<0&&(v=-v,c=-c,f=-f,M=-M,h=-h),1-v>a.EPSILON?(l=Math.acos(v),d=Math.sin(l),b=Math.sin((1-e)*l)/d,m=Math.sin(e*l)/d):(b=1-e,m=e),t[0]=b*u+m*c,t[1]=b*o+m*f,t[2]=b*i+m*M,t[3]=b*s+m*h,t}function h(t,n){var r=n[0]+n[4]+n[8],a=void 0;if(r>0)a=Math.sqrt(r+1),t[3]=.5*a,a=.5/a,t[0]=(n[5]-n[7])*a,t[1]=(n[6]-n[2])*a,t[2]=(n[1]-n[3])*a;else{var e=0;n[4]>n[0]&&(e=1),n[8]>n[3*e+e]&&(e=2);var u=(e+1)%3,o=(e+2)%3;a=Math.sqrt(n[3*e+e]-n[3*u+u]-n[3*o+o]+1),t[e]=.5*a,a=.5/a,t[3]=(n[3*u+o]-n[3*o+u])*a,t[u]=(n[3*u+e]+n[3*e+u])*a,t[o]=(n[3*o+e]+n[3*e+o])*a}return t}n.clone=o.clone,n.fromValues=o.fromValues,n.copy=o.copy,n.set=o.set,n.add=o.add,n.mul=f,n.scale=o.scale,n.dot=o.dot,n.lerp=o.lerp;var l=n.length=o.length,v=(n.len=l,n.squaredLength=o.squaredLength),d=(n.sqrLen=v,n.normalize=o.normalize);n.exactEquals=o.exactEquals,n.equals=o.equals,n.rotationTo=function(){var t=u.create(),n=u.fromValues(1,0,0),r=u.fromValues(0,1,0);return function(a,e,o){var i=u.dot(e,o);return i<-.999999?(u.cross(t,n,e),u.len(t)<1e-6&&u.cross(t,r,e),u.normalize(t,t),c(a,t,Math.PI),a):i>.999999?(a[0]=0,a[1]=0,a[2]=0,a[3]=1,a):(u.cross(t,e,o),a[0]=t[0],a[1]=t[1],a[2]=t[2],a[3]=1+i,d(a,a))}}(),n.sqlerp=function(){var t=s(),n=s();return function(r,a,e,u,o,i){return M(t,a,o,i),M(n,e,u,i),M(r,t,n,2*i*(1-i)),r}}(),n.setAxes=function(){var t=e.create();return function(n,r,a,e){return t[0]=a[0],t[3]=a[1],t[6]=a[2],t[1]=e[0],t[4]=e[1],t[7]=e[2],t[2]=-r[0],t[5]=-r[1],t[8]=-r[2],d(n,h(n,t))}}()},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.sub=n.mul=void 0,n.create=function(){var t=new a.ARRAY_TYPE(16);a.ARRAY_TYPE!=Float32Array&&(t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[11]=0,t[12]=0,t[13]=0,t[14]=0);return t[0]=1,t[5]=1,t[10]=1,t[15]=1,t},n.clone=function(t){var n=new a.ARRAY_TYPE(16);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n[3]=t[3],n[4]=t[4],n[5]=t[5],n[6]=t[6],n[7]=t[7],n[8]=t[8],n[9]=t[9],n[10]=t[10],n[11]=t[11],n[12]=t[12],n[13]=t[13],n[14]=t[14],n[15]=t[15],n},n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[4]=n[4],t[5]=n[5],t[6]=n[6],t[7]=n[7],t[8]=n[8],t[9]=n[9],t[10]=n[10],t[11]=n[11],t[12]=n[12],t[13]=n[13],t[14]=n[14],t[15]=n[15],t},n.fromValues=function(t,n,r,e,u,o,i,s,c,f,M,h,l,v,d,b){var m=new a.ARRAY_TYPE(16);return m[0]=t,m[1]=n,m[2]=r,m[3]=e,m[4]=u,m[5]=o,m[6]=i,m[7]=s,m[8]=c,m[9]=f,m[10]=M,m[11]=h,m[12]=l,m[13]=v,m[14]=d,m[15]=b,m},n.set=function(t,n,r,a,e,u,o,i,s,c,f,M,h,l,v,d,b){return t[0]=n,t[1]=r,t[2]=a,t[3]=e,t[4]=u,t[5]=o,t[6]=i,t[7]=s,t[8]=c,t[9]=f,t[10]=M,t[11]=h,t[12]=l,t[13]=v,t[14]=d,t[15]=b,t},n.identity=e,n.transpose=function(t,n){if(t===n){var r=n[1],a=n[2],e=n[3],u=n[6],o=n[7],i=n[11];t[1]=n[4],t[2]=n[8],t[3]=n[12],t[4]=r,t[6]=n[9],t[7]=n[13],t[8]=a,t[9]=u,t[11]=n[14],t[12]=e,t[13]=o,t[14]=i}else t[0]=n[0],t[1]=n[4],t[2]=n[8],t[3]=n[12],t[4]=n[1],t[5]=n[5],t[6]=n[9],t[7]=n[13],t[8]=n[2],t[9]=n[6],t[10]=n[10],t[11]=n[14],t[12]=n[3],t[13]=n[7],t[14]=n[11],t[15]=n[15];return t},n.invert=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=n[6],c=n[7],f=n[8],M=n[9],h=n[10],l=n[11],v=n[12],d=n[13],b=n[14],m=n[15],p=r*i-a*o,P=r*s-e*o,A=r*c-u*o,E=a*s-e*i,O=a*c-u*i,R=e*c-u*s,y=f*d-M*v,q=f*b-h*v,x=f*m-l*v,_=M*b-h*d,Y=M*m-l*d,L=h*m-l*b,S=p*L-P*Y+A*_+E*x-O*q+R*y;if(!S)return null;return S=1/S,t[0]=(i*L-s*Y+c*_)*S,t[1]=(e*Y-a*L-u*_)*S,t[2]=(d*R-b*O+m*E)*S,t[3]=(h*O-M*R-l*E)*S,t[4]=(s*x-o*L-c*q)*S,t[5]=(r*L-e*x+u*q)*S,t[6]=(b*A-v*R-m*P)*S,t[7]=(f*R-h*A+l*P)*S,t[8]=(o*Y-i*x+c*y)*S,t[9]=(a*x-r*Y-u*y)*S,t[10]=(v*O-d*A+m*p)*S,t[11]=(M*A-f*O-l*p)*S,t[12]=(i*q-o*_-s*y)*S,t[13]=(r*_-a*q+e*y)*S,t[14]=(d*P-v*E-b*p)*S,t[15]=(f*E-M*P+h*p)*S,t},n.adjoint=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=n[6],c=n[7],f=n[8],M=n[9],h=n[10],l=n[11],v=n[12],d=n[13],b=n[14],m=n[15];return t[0]=i*(h*m-l*b)-M*(s*m-c*b)+d*(s*l-c*h),t[1]=-(a*(h*m-l*b)-M*(e*m-u*b)+d*(e*l-u*h)),t[2]=a*(s*m-c*b)-i*(e*m-u*b)+d*(e*c-u*s),t[3]=-(a*(s*l-c*h)-i*(e*l-u*h)+M*(e*c-u*s)),t[4]=-(o*(h*m-l*b)-f*(s*m-c*b)+v*(s*l-c*h)),t[5]=r*(h*m-l*b)-f*(e*m-u*b)+v*(e*l-u*h),t[6]=-(r*(s*m-c*b)-o*(e*m-u*b)+v*(e*c-u*s)),t[7]=r*(s*l-c*h)-o*(e*l-u*h)+f*(e*c-u*s),t[8]=o*(M*m-l*d)-f*(i*m-c*d)+v*(i*l-c*M),t[9]=-(r*(M*m-l*d)-f*(a*m-u*d)+v*(a*l-u*M)),t[10]=r*(i*m-c*d)-o*(a*m-u*d)+v*(a*c-u*i),t[11]=-(r*(i*l-c*M)-o*(a*l-u*M)+f*(a*c-u*i)),t[12]=-(o*(M*b-h*d)-f*(i*b-s*d)+v*(i*h-s*M)),t[13]=r*(M*b-h*d)-f*(a*b-e*d)+v*(a*h-e*M),t[14]=-(r*(i*b-s*d)-o*(a*b-e*d)+v*(a*s-e*i)),t[15]=r*(i*h-s*M)-o*(a*h-e*M)+f*(a*s-e*i),t},n.determinant=function(t){var n=t[0],r=t[1],a=t[2],e=t[3],u=t[4],o=t[5],i=t[6],s=t[7],c=t[8],f=t[9],M=t[10],h=t[11],l=t[12],v=t[13],d=t[14],b=t[15];return(n*o-r*u)*(M*b-h*d)-(n*i-a*u)*(f*b-h*v)+(n*s-e*u)*(f*d-M*v)+(r*i-a*o)*(c*b-h*l)-(r*s-e*o)*(c*d-M*l)+(a*s-e*i)*(c*v-f*l)},n.multiply=u,n.translate=function(t,n,r){var a=r[0],e=r[1],u=r[2],o=void 0,i=void 0,s=void 0,c=void 0,f=void 0,M=void 0,h=void 0,l=void 0,v=void 0,d=void 0,b=void 0,m=void 0;n===t?(t[12]=n[0]*a+n[4]*e+n[8]*u+n[12],t[13]=n[1]*a+n[5]*e+n[9]*u+n[13],t[14]=n[2]*a+n[6]*e+n[10]*u+n[14],t[15]=n[3]*a+n[7]*e+n[11]*u+n[15]):(o=n[0],i=n[1],s=n[2],c=n[3],f=n[4],M=n[5],h=n[6],l=n[7],v=n[8],d=n[9],b=n[10],m=n[11],t[0]=o,t[1]=i,t[2]=s,t[3]=c,t[4]=f,t[5]=M,t[6]=h,t[7]=l,t[8]=v,t[9]=d,t[10]=b,t[11]=m,t[12]=o*a+f*e+v*u+n[12],t[13]=i*a+M*e+d*u+n[13],t[14]=s*a+h*e+b*u+n[14],t[15]=c*a+l*e+m*u+n[15]);return t},n.scale=function(t,n,r){var a=r[0],e=r[1],u=r[2];return t[0]=n[0]*a,t[1]=n[1]*a,t[2]=n[2]*a,t[3]=n[3]*a,t[4]=n[4]*e,t[5]=n[5]*e,t[6]=n[6]*e,t[7]=n[7]*e,t[8]=n[8]*u,t[9]=n[9]*u,t[10]=n[10]*u,t[11]=n[11]*u,t[12]=n[12],t[13]=n[13],t[14]=n[14],t[15]=n[15],t},n.rotate=function(t,n,r,e){var u=e[0],o=e[1],i=e[2],s=Math.sqrt(u*u+o*o+i*i),c=void 0,f=void 0,M=void 0,h=void 0,l=void 0,v=void 0,d=void 0,b=void 0,m=void 0,p=void 0,P=void 0,A=void 0,E=void 0,O=void 0,R=void 0,y=void 0,q=void 0,x=void 0,_=void 0,Y=void 0,L=void 0,S=void 0,w=void 0,I=void 0;if(s<a.EPSILON)return null;u*=s=1/s,o*=s,i*=s,c=Math.sin(r),f=Math.cos(r),M=1-f,h=n[0],l=n[1],v=n[2],d=n[3],b=n[4],m=n[5],p=n[6],P=n[7],A=n[8],E=n[9],O=n[10],R=n[11],y=u*u*M+f,q=o*u*M+i*c,x=i*u*M-o*c,_=u*o*M-i*c,Y=o*o*M+f,L=i*o*M+u*c,S=u*i*M+o*c,w=o*i*M-u*c,I=i*i*M+f,t[0]=h*y+b*q+A*x,t[1]=l*y+m*q+E*x,t[2]=v*y+p*q+O*x,t[3]=d*y+P*q+R*x,t[4]=h*_+b*Y+A*L,t[5]=l*_+m*Y+E*L,t[6]=v*_+p*Y+O*L,t[7]=d*_+P*Y+R*L,t[8]=h*S+b*w+A*I,t[9]=l*S+m*w+E*I,t[10]=v*S+p*w+O*I,t[11]=d*S+P*w+R*I,n!==t&&(t[12]=n[12],t[13]=n[13],t[14]=n[14],t[15]=n[15]);return t},n.rotateX=function(t,n,r){var a=Math.sin(r),e=Math.cos(r),u=n[4],o=n[5],i=n[6],s=n[7],c=n[8],f=n[9],M=n[10],h=n[11];n!==t&&(t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[12]=n[12],t[13]=n[13],t[14]=n[14],t[15]=n[15]);return t[4]=u*e+c*a,t[5]=o*e+f*a,t[6]=i*e+M*a,t[7]=s*e+h*a,t[8]=c*e-u*a,t[9]=f*e-o*a,t[10]=M*e-i*a,t[11]=h*e-s*a,t},n.rotateY=function(t,n,r){var a=Math.sin(r),e=Math.cos(r),u=n[0],o=n[1],i=n[2],s=n[3],c=n[8],f=n[9],M=n[10],h=n[11];n!==t&&(t[4]=n[4],t[5]=n[5],t[6]=n[6],t[7]=n[7],t[12]=n[12],t[13]=n[13],t[14]=n[14],t[15]=n[15]);return t[0]=u*e-c*a,t[1]=o*e-f*a,t[2]=i*e-M*a,t[3]=s*e-h*a,t[8]=u*a+c*e,t[9]=o*a+f*e,t[10]=i*a+M*e,t[11]=s*a+h*e,t},n.rotateZ=function(t,n,r){var a=Math.sin(r),e=Math.cos(r),u=n[0],o=n[1],i=n[2],s=n[3],c=n[4],f=n[5],M=n[6],h=n[7];n!==t&&(t[8]=n[8],t[9]=n[9],t[10]=n[10],t[11]=n[11],t[12]=n[12],t[13]=n[13],t[14]=n[14],t[15]=n[15]);return t[0]=u*e+c*a,t[1]=o*e+f*a,t[2]=i*e+M*a,t[3]=s*e+h*a,t[4]=c*e-u*a,t[5]=f*e-o*a,t[6]=M*e-i*a,t[7]=h*e-s*a,t},n.fromTranslation=function(t,n){return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=1,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=1,t[11]=0,t[12]=n[0],t[13]=n[1],t[14]=n[2],t[15]=1,t},n.fromScaling=function(t,n){return t[0]=n[0],t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=n[1],t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=n[2],t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t},n.fromRotation=function(t,n,r){var e=r[0],u=r[1],o=r[2],i=Math.sqrt(e*e+u*u+o*o),s=void 0,c=void 0,f=void 0;if(i<a.EPSILON)return null;return e*=i=1/i,u*=i,o*=i,s=Math.sin(n),c=Math.cos(n),f=1-c,t[0]=e*e*f+c,t[1]=u*e*f+o*s,t[2]=o*e*f-u*s,t[3]=0,t[4]=e*u*f-o*s,t[5]=u*u*f+c,t[6]=o*u*f+e*s,t[7]=0,t[8]=e*o*f+u*s,t[9]=u*o*f-e*s,t[10]=o*o*f+c,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t},n.fromXRotation=function(t,n){var r=Math.sin(n),a=Math.cos(n);return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=a,t[6]=r,t[7]=0,t[8]=0,t[9]=-r,t[10]=a,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t},n.fromYRotation=function(t,n){var r=Math.sin(n),a=Math.cos(n);return t[0]=a,t[1]=0,t[2]=-r,t[3]=0,t[4]=0,t[5]=1,t[6]=0,t[7]=0,t[8]=r,t[9]=0,t[10]=a,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t},n.fromZRotation=function(t,n){var r=Math.sin(n),a=Math.cos(n);return t[0]=a,t[1]=r,t[2]=0,t[3]=0,t[4]=-r,t[5]=a,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=1,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t},n.fromRotationTranslation=o,n.fromQuat2=function(t,n){var r=new a.ARRAY_TYPE(3),e=-n[0],u=-n[1],i=-n[2],s=n[3],c=n[4],f=n[5],M=n[6],h=n[7],l=e*e+u*u+i*i+s*s;l>0?(r[0]=2*(c*s+h*e+f*i-M*u)/l,r[1]=2*(f*s+h*u+M*e-c*i)/l,r[2]=2*(M*s+h*i+c*u-f*e)/l):(r[0]=2*(c*s+h*e+f*i-M*u),r[1]=2*(f*s+h*u+M*e-c*i),r[2]=2*(M*s+h*i+c*u-f*e));return o(t,n,r),t},n.getTranslation=function(t,n){return t[0]=n[12],t[1]=n[13],t[2]=n[14],t},n.getScaling=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[4],o=n[5],i=n[6],s=n[8],c=n[9],f=n[10];return t[0]=Math.sqrt(r*r+a*a+e*e),t[1]=Math.sqrt(u*u+o*o+i*i),t[2]=Math.sqrt(s*s+c*c+f*f),t},n.getRotation=function(t,n){var r=n[0]+n[5]+n[10],a=0;r>0?(a=2*Math.sqrt(r+1),t[3]=.25*a,t[0]=(n[6]-n[9])/a,t[1]=(n[8]-n[2])/a,t[2]=(n[1]-n[4])/a):n[0]>n[5]&&n[0]>n[10]?(a=2*Math.sqrt(1+n[0]-n[5]-n[10]),t[3]=(n[6]-n[9])/a,t[0]=.25*a,t[1]=(n[1]+n[4])/a,t[2]=(n[8]+n[2])/a):n[5]>n[10]?(a=2*Math.sqrt(1+n[5]-n[0]-n[10]),t[3]=(n[8]-n[2])/a,t[0]=(n[1]+n[4])/a,t[1]=.25*a,t[2]=(n[6]+n[9])/a):(a=2*Math.sqrt(1+n[10]-n[0]-n[5]),t[3]=(n[1]-n[4])/a,t[0]=(n[8]+n[2])/a,t[1]=(n[6]+n[9])/a,t[2]=.25*a);return t},n.fromRotationTranslationScale=function(t,n,r,a){var e=n[0],u=n[1],o=n[2],i=n[3],s=e+e,c=u+u,f=o+o,M=e*s,h=e*c,l=e*f,v=u*c,d=u*f,b=o*f,m=i*s,p=i*c,P=i*f,A=a[0],E=a[1],O=a[2];return t[0]=(1-(v+b))*A,t[1]=(h+P)*A,t[2]=(l-p)*A,t[3]=0,t[4]=(h-P)*E,t[5]=(1-(M+b))*E,t[6]=(d+m)*E,t[7]=0,t[8]=(l+p)*O,t[9]=(d-m)*O,t[10]=(1-(M+v))*O,t[11]=0,t[12]=r[0],t[13]=r[1],t[14]=r[2],t[15]=1,t},n.fromRotationTranslationScaleOrigin=function(t,n,r,a,e){var u=n[0],o=n[1],i=n[2],s=n[3],c=u+u,f=o+o,M=i+i,h=u*c,l=u*f,v=u*M,d=o*f,b=o*M,m=i*M,p=s*c,P=s*f,A=s*M,E=a[0],O=a[1],R=a[2],y=e[0],q=e[1],x=e[2],_=(1-(d+m))*E,Y=(l+A)*E,L=(v-P)*E,S=(l-A)*O,w=(1-(h+m))*O,I=(b+p)*O,N=(v+P)*R,g=(b-p)*R,T=(1-(h+d))*R;return t[0]=_,t[1]=Y,t[2]=L,t[3]=0,t[4]=S,t[5]=w,t[6]=I,t[7]=0,t[8]=N,t[9]=g,t[10]=T,t[11]=0,t[12]=r[0]+y-(_*y+S*q+N*x),t[13]=r[1]+q-(Y*y+w*q+g*x),t[14]=r[2]+x-(L*y+I*q+T*x),t[15]=1,t},n.fromQuat=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r+r,i=a+a,s=e+e,c=r*o,f=a*o,M=a*i,h=e*o,l=e*i,v=e*s,d=u*o,b=u*i,m=u*s;return t[0]=1-M-v,t[1]=f+m,t[2]=h-b,t[3]=0,t[4]=f-m,t[5]=1-c-v,t[6]=l+d,t[7]=0,t[8]=h+b,t[9]=l-d,t[10]=1-c-M,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t},n.frustum=function(t,n,r,a,e,u,o){var i=1/(r-n),s=1/(e-a),c=1/(u-o);return t[0]=2*u*i,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=2*u*s,t[6]=0,t[7]=0,t[8]=(r+n)*i,t[9]=(e+a)*s,t[10]=(o+u)*c,t[11]=-1,t[12]=0,t[13]=0,t[14]=o*u*2*c,t[15]=0,t},n.perspective=function(t,n,r,a,e){var u=1/Math.tan(n/2),o=void 0;t[0]=u/r,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=u,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[11]=-1,t[12]=0,t[13]=0,t[15]=0,null!=e&&e!==1/0?(o=1/(a-e),t[10]=(e+a)*o,t[14]=2*e*a*o):(t[10]=-1,t[14]=-2*a);return t},n.perspectiveFromFieldOfView=function(t,n,r,a){var e=Math.tan(n.upDegrees*Math.PI/180),u=Math.tan(n.downDegrees*Math.PI/180),o=Math.tan(n.leftDegrees*Math.PI/180),i=Math.tan(n.rightDegrees*Math.PI/180),s=2/(o+i),c=2/(e+u);return t[0]=s,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=c,t[6]=0,t[7]=0,t[8]=-(o-i)*s*.5,t[9]=(e-u)*c*.5,t[10]=a/(r-a),t[11]=-1,t[12]=0,t[13]=0,t[14]=a*r/(r-a),t[15]=0,t},n.ortho=function(t,n,r,a,e,u,o){var i=1/(n-r),s=1/(a-e),c=1/(u-o);return t[0]=-2*i,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=-2*s,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=2*c,t[11]=0,t[12]=(n+r)*i,t[13]=(e+a)*s,t[14]=(o+u)*c,t[15]=1,t},n.lookAt=function(t,n,r,u){var o=void 0,i=void 0,s=void 0,c=void 0,f=void 0,M=void 0,h=void 0,l=void 0,v=void 0,d=void 0,b=n[0],m=n[1],p=n[2],P=u[0],A=u[1],E=u[2],O=r[0],R=r[1],y=r[2];if(Math.abs(b-O)<a.EPSILON&&Math.abs(m-R)<a.EPSILON&&Math.abs(p-y)<a.EPSILON)return e(t);h=b-O,l=m-R,v=p-y,d=1/Math.sqrt(h*h+l*l+v*v),o=A*(v*=d)-E*(l*=d),i=E*(h*=d)-P*v,s=P*l-A*h,(d=Math.sqrt(o*o+i*i+s*s))?(o*=d=1/d,i*=d,s*=d):(o=0,i=0,s=0);c=l*s-v*i,f=v*o-h*s,M=h*i-l*o,(d=Math.sqrt(c*c+f*f+M*M))?(c*=d=1/d,f*=d,M*=d):(c=0,f=0,M=0);return t[0]=o,t[1]=c,t[2]=h,t[3]=0,t[4]=i,t[5]=f,t[6]=l,t[7]=0,t[8]=s,t[9]=M,t[10]=v,t[11]=0,t[12]=-(o*b+i*m+s*p),t[13]=-(c*b+f*m+M*p),t[14]=-(h*b+l*m+v*p),t[15]=1,t},n.targetTo=function(t,n,r,a){var e=n[0],u=n[1],o=n[2],i=a[0],s=a[1],c=a[2],f=e-r[0],M=u-r[1],h=o-r[2],l=f*f+M*M+h*h;l>0&&(l=1/Math.sqrt(l),f*=l,M*=l,h*=l);var v=s*h-c*M,d=c*f-i*h,b=i*M-s*f;(l=v*v+d*d+b*b)>0&&(l=1/Math.sqrt(l),v*=l,d*=l,b*=l);return t[0]=v,t[1]=d,t[2]=b,t[3]=0,t[4]=M*b-h*d,t[5]=h*v-f*b,t[6]=f*d-M*v,t[7]=0,t[8]=f,t[9]=M,t[10]=h,t[11]=0,t[12]=e,t[13]=u,t[14]=o,t[15]=1,t},n.str=function(t){return"mat4("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+", "+t[4]+", "+t[5]+", "+t[6]+", "+t[7]+", "+t[8]+", "+t[9]+", "+t[10]+", "+t[11]+", "+t[12]+", "+t[13]+", "+t[14]+", "+t[15]+")"},n.frob=function(t){return Math.sqrt(Math.pow(t[0],2)+Math.pow(t[1],2)+Math.pow(t[2],2)+Math.pow(t[3],2)+Math.pow(t[4],2)+Math.pow(t[5],2)+Math.pow(t[6],2)+Math.pow(t[7],2)+Math.pow(t[8],2)+Math.pow(t[9],2)+Math.pow(t[10],2)+Math.pow(t[11],2)+Math.pow(t[12],2)+Math.pow(t[13],2)+Math.pow(t[14],2)+Math.pow(t[15],2))},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t[3]=n[3]+r[3],t[4]=n[4]+r[4],t[5]=n[5]+r[5],t[6]=n[6]+r[6],t[7]=n[7]+r[7],t[8]=n[8]+r[8],t[9]=n[9]+r[9],t[10]=n[10]+r[10],t[11]=n[11]+r[11],t[12]=n[12]+r[12],t[13]=n[13]+r[13],t[14]=n[14]+r[14],t[15]=n[15]+r[15],t},n.subtract=i,n.multiplyScalar=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=n[3]*r,t[4]=n[4]*r,t[5]=n[5]*r,t[6]=n[6]*r,t[7]=n[7]*r,t[8]=n[8]*r,t[9]=n[9]*r,t[10]=n[10]*r,t[11]=n[11]*r,t[12]=n[12]*r,t[13]=n[13]*r,t[14]=n[14]*r,t[15]=n[15]*r,t},n.multiplyScalarAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t[3]=n[3]+r[3]*a,t[4]=n[4]+r[4]*a,t[5]=n[5]+r[5]*a,t[6]=n[6]+r[6]*a,t[7]=n[7]+r[7]*a,t[8]=n[8]+r[8]*a,t[9]=n[9]+r[9]*a,t[10]=n[10]+r[10]*a,t[11]=n[11]+r[11]*a,t[12]=n[12]+r[12]*a,t[13]=n[13]+r[13]*a,t[14]=n[14]+r[14]*a,t[15]=n[15]+r[15]*a,t},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]&&t[4]===n[4]&&t[5]===n[5]&&t[6]===n[6]&&t[7]===n[7]&&t[8]===n[8]&&t[9]===n[9]&&t[10]===n[10]&&t[11]===n[11]&&t[12]===n[12]&&t[13]===n[13]&&t[14]===n[14]&&t[15]===n[15]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=t[3],i=t[4],s=t[5],c=t[6],f=t[7],M=t[8],h=t[9],l=t[10],v=t[11],d=t[12],b=t[13],m=t[14],p=t[15],P=n[0],A=n[1],E=n[2],O=n[3],R=n[4],y=n[5],q=n[6],x=n[7],_=n[8],Y=n[9],L=n[10],S=n[11],w=n[12],I=n[13],N=n[14],g=n[15];return Math.abs(r-P)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(P))&&Math.abs(e-A)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(A))&&Math.abs(u-E)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(E))&&Math.abs(o-O)<=a.EPSILON*Math.max(1,Math.abs(o),Math.abs(O))&&Math.abs(i-R)<=a.EPSILON*Math.max(1,Math.abs(i),Math.abs(R))&&Math.abs(s-y)<=a.EPSILON*Math.max(1,Math.abs(s),Math.abs(y))&&Math.abs(c-q)<=a.EPSILON*Math.max(1,Math.abs(c),Math.abs(q))&&Math.abs(f-x)<=a.EPSILON*Math.max(1,Math.abs(f),Math.abs(x))&&Math.abs(M-_)<=a.EPSILON*Math.max(1,Math.abs(M),Math.abs(_))&&Math.abs(h-Y)<=a.EPSILON*Math.max(1,Math.abs(h),Math.abs(Y))&&Math.abs(l-L)<=a.EPSILON*Math.max(1,Math.abs(l),Math.abs(L))&&Math.abs(v-S)<=a.EPSILON*Math.max(1,Math.abs(v),Math.abs(S))&&Math.abs(d-w)<=a.EPSILON*Math.max(1,Math.abs(d),Math.abs(w))&&Math.abs(b-I)<=a.EPSILON*Math.max(1,Math.abs(b),Math.abs(I))&&Math.abs(m-N)<=a.EPSILON*Math.max(1,Math.abs(m),Math.abs(N))&&Math.abs(p-g)<=a.EPSILON*Math.max(1,Math.abs(p),Math.abs(g))};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=1,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=1,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t}function u(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=n[6],f=n[7],M=n[8],h=n[9],l=n[10],v=n[11],d=n[12],b=n[13],m=n[14],p=n[15],P=r[0],A=r[1],E=r[2],O=r[3];return t[0]=P*a+A*i+E*M+O*d,t[1]=P*e+A*s+E*h+O*b,t[2]=P*u+A*c+E*l+O*m,t[3]=P*o+A*f+E*v+O*p,P=r[4],A=r[5],E=r[6],O=r[7],t[4]=P*a+A*i+E*M+O*d,t[5]=P*e+A*s+E*h+O*b,t[6]=P*u+A*c+E*l+O*m,t[7]=P*o+A*f+E*v+O*p,P=r[8],A=r[9],E=r[10],O=r[11],t[8]=P*a+A*i+E*M+O*d,t[9]=P*e+A*s+E*h+O*b,t[10]=P*u+A*c+E*l+O*m,t[11]=P*o+A*f+E*v+O*p,P=r[12],A=r[13],E=r[14],O=r[15],t[12]=P*a+A*i+E*M+O*d,t[13]=P*e+A*s+E*h+O*b,t[14]=P*u+A*c+E*l+O*m,t[15]=P*o+A*f+E*v+O*p,t}function o(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=a+a,s=e+e,c=u+u,f=a*i,M=a*s,h=a*c,l=e*s,v=e*c,d=u*c,b=o*i,m=o*s,p=o*c;return t[0]=1-(l+d),t[1]=M+p,t[2]=h-m,t[3]=0,t[4]=M-p,t[5]=1-(f+d),t[6]=v+b,t[7]=0,t[8]=h+m,t[9]=v-b,t[10]=1-(f+l),t[11]=0,t[12]=r[0],t[13]=r[1],t[14]=r[2],t[15]=1,t}function i(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t[3]=n[3]-r[3],t[4]=n[4]-r[4],t[5]=n[5]-r[5],t[6]=n[6]-r[6],t[7]=n[7]-r[7],t[8]=n[8]-r[8],t[9]=n[9]-r[9],t[10]=n[10]-r[10],t[11]=n[11]-r[11],t[12]=n[12]-r[12],t[13]=n[13]-r[13],t[14]=n[14]-r[14],t[15]=n[15]-r[15],t}n.mul=u,n.sub=i},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.sub=n.mul=void 0,n.create=function(){var t=new a.ARRAY_TYPE(9);a.ARRAY_TYPE!=Float32Array&&(t[1]=0,t[2]=0,t[3]=0,t[5]=0,t[6]=0,t[7]=0);return t[0]=1,t[4]=1,t[8]=1,t},n.fromMat4=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[4],t[4]=n[5],t[5]=n[6],t[6]=n[8],t[7]=n[9],t[8]=n[10],t},n.clone=function(t){var n=new a.ARRAY_TYPE(9);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n[3]=t[3],n[4]=t[4],n[5]=t[5],n[6]=t[6],n[7]=t[7],n[8]=t[8],n},n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[4]=n[4],t[5]=n[5],t[6]=n[6],t[7]=n[7],t[8]=n[8],t},n.fromValues=function(t,n,r,e,u,o,i,s,c){var f=new a.ARRAY_TYPE(9);return f[0]=t,f[1]=n,f[2]=r,f[3]=e,f[4]=u,f[5]=o,f[6]=i,f[7]=s,f[8]=c,f},n.set=function(t,n,r,a,e,u,o,i,s,c){return t[0]=n,t[1]=r,t[2]=a,t[3]=e,t[4]=u,t[5]=o,t[6]=i,t[7]=s,t[8]=c,t},n.identity=function(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=1,t[5]=0,t[6]=0,t[7]=0,t[8]=1,t},n.transpose=function(t,n){if(t===n){var r=n[1],a=n[2],e=n[5];t[1]=n[3],t[2]=n[6],t[3]=r,t[5]=n[7],t[6]=a,t[7]=e}else t[0]=n[0],t[1]=n[3],t[2]=n[6],t[3]=n[1],t[4]=n[4],t[5]=n[7],t[6]=n[2],t[7]=n[5],t[8]=n[8];return t},n.invert=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=n[6],c=n[7],f=n[8],M=f*o-i*c,h=-f*u+i*s,l=c*u-o*s,v=r*M+a*h+e*l;if(!v)return null;return v=1/v,t[0]=M*v,t[1]=(-f*a+e*c)*v,t[2]=(i*a-e*o)*v,t[3]=h*v,t[4]=(f*r-e*s)*v,t[5]=(-i*r+e*u)*v,t[6]=l*v,t[7]=(-c*r+a*s)*v,t[8]=(o*r-a*u)*v,t},n.adjoint=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=n[6],c=n[7],f=n[8];return t[0]=o*f-i*c,t[1]=e*c-a*f,t[2]=a*i-e*o,t[3]=i*s-u*f,t[4]=r*f-e*s,t[5]=e*u-r*i,t[6]=u*c-o*s,t[7]=a*s-r*c,t[8]=r*o-a*u,t},n.determinant=function(t){var n=t[0],r=t[1],a=t[2],e=t[3],u=t[4],o=t[5],i=t[6],s=t[7],c=t[8];return n*(c*u-o*s)+r*(-c*e+o*i)+a*(s*e-u*i)},n.multiply=e,n.translate=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=n[6],f=n[7],M=n[8],h=r[0],l=r[1];return t[0]=a,t[1]=e,t[2]=u,t[3]=o,t[4]=i,t[5]=s,t[6]=h*a+l*o+c,t[7]=h*e+l*i+f,t[8]=h*u+l*s+M,t},n.rotate=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=n[6],f=n[7],M=n[8],h=Math.sin(r),l=Math.cos(r);return t[0]=l*a+h*o,t[1]=l*e+h*i,t[2]=l*u+h*s,t[3]=l*o-h*a,t[4]=l*i-h*e,t[5]=l*s-h*u,t[6]=c,t[7]=f,t[8]=M,t},n.scale=function(t,n,r){var a=r[0],e=r[1];return t[0]=a*n[0],t[1]=a*n[1],t[2]=a*n[2],t[3]=e*n[3],t[4]=e*n[4],t[5]=e*n[5],t[6]=n[6],t[7]=n[7],t[8]=n[8],t},n.fromTranslation=function(t,n){return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=1,t[5]=0,t[6]=n[0],t[7]=n[1],t[8]=1,t},n.fromRotation=function(t,n){var r=Math.sin(n),a=Math.cos(n);return t[0]=a,t[1]=r,t[2]=0,t[3]=-r,t[4]=a,t[5]=0,t[6]=0,t[7]=0,t[8]=1,t},n.fromScaling=function(t,n){return t[0]=n[0],t[1]=0,t[2]=0,t[3]=0,t[4]=n[1],t[5]=0,t[6]=0,t[7]=0,t[8]=1,t},n.fromMat2d=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=0,t[3]=n[2],t[4]=n[3],t[5]=0,t[6]=n[4],t[7]=n[5],t[8]=1,t},n.fromQuat=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r+r,i=a+a,s=e+e,c=r*o,f=a*o,M=a*i,h=e*o,l=e*i,v=e*s,d=u*o,b=u*i,m=u*s;return t[0]=1-M-v,t[3]=f-m,t[6]=h+b,t[1]=f+m,t[4]=1-c-v,t[7]=l-d,t[2]=h-b,t[5]=l+d,t[8]=1-c-M,t},n.normalFromMat4=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=n[6],c=n[7],f=n[8],M=n[9],h=n[10],l=n[11],v=n[12],d=n[13],b=n[14],m=n[15],p=r*i-a*o,P=r*s-e*o,A=r*c-u*o,E=a*s-e*i,O=a*c-u*i,R=e*c-u*s,y=f*d-M*v,q=f*b-h*v,x=f*m-l*v,_=M*b-h*d,Y=M*m-l*d,L=h*m-l*b,S=p*L-P*Y+A*_+E*x-O*q+R*y;if(!S)return null;return S=1/S,t[0]=(i*L-s*Y+c*_)*S,t[1]=(s*x-o*L-c*q)*S,t[2]=(o*Y-i*x+c*y)*S,t[3]=(e*Y-a*L-u*_)*S,t[4]=(r*L-e*x+u*q)*S,t[5]=(a*x-r*Y-u*y)*S,t[6]=(d*R-b*O+m*E)*S,t[7]=(b*A-v*R-m*P)*S,t[8]=(v*O-d*A+m*p)*S,t},n.projection=function(t,n,r){return t[0]=2/n,t[1]=0,t[2]=0,t[3]=0,t[4]=-2/r,t[5]=0,t[6]=-1,t[7]=1,t[8]=1,t},n.str=function(t){return"mat3("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+", "+t[4]+", "+t[5]+", "+t[6]+", "+t[7]+", "+t[8]+")"},n.frob=function(t){return Math.sqrt(Math.pow(t[0],2)+Math.pow(t[1],2)+Math.pow(t[2],2)+Math.pow(t[3],2)+Math.pow(t[4],2)+Math.pow(t[5],2)+Math.pow(t[6],2)+Math.pow(t[7],2)+Math.pow(t[8],2))},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t[3]=n[3]+r[3],t[4]=n[4]+r[4],t[5]=n[5]+r[5],t[6]=n[6]+r[6],t[7]=n[7]+r[7],t[8]=n[8]+r[8],t},n.subtract=u,n.multiplyScalar=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=n[3]*r,t[4]=n[4]*r,t[5]=n[5]*r,t[6]=n[6]*r,t[7]=n[7]*r,t[8]=n[8]*r,t},n.multiplyScalarAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t[3]=n[3]+r[3]*a,t[4]=n[4]+r[4]*a,t[5]=n[5]+r[5]*a,t[6]=n[6]+r[6]*a,t[7]=n[7]+r[7]*a,t[8]=n[8]+r[8]*a,t},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]&&t[4]===n[4]&&t[5]===n[5]&&t[6]===n[6]&&t[7]===n[7]&&t[8]===n[8]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=t[3],i=t[4],s=t[5],c=t[6],f=t[7],M=t[8],h=n[0],l=n[1],v=n[2],d=n[3],b=n[4],m=n[5],p=n[6],P=n[7],A=n[8];return Math.abs(r-h)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(h))&&Math.abs(e-l)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(l))&&Math.abs(u-v)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(v))&&Math.abs(o-d)<=a.EPSILON*Math.max(1,Math.abs(o),Math.abs(d))&&Math.abs(i-b)<=a.EPSILON*Math.max(1,Math.abs(i),Math.abs(b))&&Math.abs(s-m)<=a.EPSILON*Math.max(1,Math.abs(s),Math.abs(m))&&Math.abs(c-p)<=a.EPSILON*Math.max(1,Math.abs(c),Math.abs(p))&&Math.abs(f-P)<=a.EPSILON*Math.max(1,Math.abs(f),Math.abs(P))&&Math.abs(M-A)<=a.EPSILON*Math.max(1,Math.abs(M),Math.abs(A))};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=n[6],f=n[7],M=n[8],h=r[0],l=r[1],v=r[2],d=r[3],b=r[4],m=r[5],p=r[6],P=r[7],A=r[8];return t[0]=h*a+l*o+v*c,t[1]=h*e+l*i+v*f,t[2]=h*u+l*s+v*M,t[3]=d*a+b*o+m*c,t[4]=d*e+b*i+m*f,t[5]=d*u+b*s+m*M,t[6]=p*a+P*o+A*c,t[7]=p*e+P*i+A*f,t[8]=p*u+P*s+A*M,t}function u(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t[3]=n[3]-r[3],t[4]=n[4]-r[4],t[5]=n[5]-r[5],t[6]=n[6]-r[6],t[7]=n[7]-r[7],t[8]=n[8]-r[8],t}n.mul=e,n.sub=u},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.forEach=n.sqrLen=n.sqrDist=n.dist=n.div=n.mul=n.sub=n.len=void 0,n.create=e,n.clone=function(t){var n=new a.ARRAY_TYPE(2);return n[0]=t[0],n[1]=t[1],n},n.fromValues=function(t,n){var r=new a.ARRAY_TYPE(2);return r[0]=t,r[1]=n,r},n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t},n.set=function(t,n,r){return t[0]=n,t[1]=r,t},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t},n.subtract=u,n.multiply=o,n.divide=i,n.ceil=function(t,n){return t[0]=Math.ceil(n[0]),t[1]=Math.ceil(n[1]),t},n.floor=function(t,n){return t[0]=Math.floor(n[0]),t[1]=Math.floor(n[1]),t},n.min=function(t,n,r){return t[0]=Math.min(n[0],r[0]),t[1]=Math.min(n[1],r[1]),t},n.max=function(t,n,r){return t[0]=Math.max(n[0],r[0]),t[1]=Math.max(n[1],r[1]),t},n.round=function(t,n){return t[0]=Math.round(n[0]),t[1]=Math.round(n[1]),t},n.scale=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t},n.scaleAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t},n.distance=s,n.squaredDistance=c,n.length=f,n.squaredLength=M,n.negate=function(t,n){return t[0]=-n[0],t[1]=-n[1],t},n.inverse=function(t,n){return t[0]=1/n[0],t[1]=1/n[1],t},n.normalize=function(t,n){var r=n[0],a=n[1],e=r*r+a*a;e>0&&(e=1/Math.sqrt(e),t[0]=n[0]*e,t[1]=n[1]*e);return t},n.dot=function(t,n){return t[0]*n[0]+t[1]*n[1]},n.cross=function(t,n,r){var a=n[0]*r[1]-n[1]*r[0];return t[0]=t[1]=0,t[2]=a,t},n.lerp=function(t,n,r,a){var e=n[0],u=n[1];return t[0]=e+a*(r[0]-e),t[1]=u+a*(r[1]-u),t},n.random=function(t,n){n=n||1;var r=2*a.RANDOM()*Math.PI;return t[0]=Math.cos(r)*n,t[1]=Math.sin(r)*n,t},n.transformMat2=function(t,n,r){var a=n[0],e=n[1];return t[0]=r[0]*a+r[2]*e,t[1]=r[1]*a+r[3]*e,t},n.transformMat2d=function(t,n,r){var a=n[0],e=n[1];return t[0]=r[0]*a+r[2]*e+r[4],t[1]=r[1]*a+r[3]*e+r[5],t},n.transformMat3=function(t,n,r){var a=n[0],e=n[1];return t[0]=r[0]*a+r[3]*e+r[6],t[1]=r[1]*a+r[4]*e+r[7],t},n.transformMat4=function(t,n,r){var a=n[0],e=n[1];return t[0]=r[0]*a+r[4]*e+r[12],t[1]=r[1]*a+r[5]*e+r[13],t},n.rotate=function(t,n,r,a){var e=n[0]-r[0],u=n[1]-r[1],o=Math.sin(a),i=Math.cos(a);return t[0]=e*i-u*o+r[0],t[1]=e*o+u*i+r[1],t},n.angle=function(t,n){var r=t[0],a=t[1],e=n[0],u=n[1],o=r*r+a*a;o>0&&(o=1/Math.sqrt(o));var i=e*e+u*u;i>0&&(i=1/Math.sqrt(i));var s=(r*e+a*u)*o*i;return s>1?0:s<-1?Math.PI:Math.acos(s)},n.str=function(t){return"vec2("+t[0]+", "+t[1]+")"},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]},n.equals=function(t,n){var r=t[0],e=t[1],u=n[0],o=n[1];return Math.abs(r-u)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(u))&&Math.abs(e-o)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(o))};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(){var t=new a.ARRAY_TYPE(2);return a.ARRAY_TYPE!=Float32Array&&(t[0]=0,t[1]=0),t}function u(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t}function o(t,n,r){return t[0]=n[0]*r[0],t[1]=n[1]*r[1],t}function i(t,n,r){return t[0]=n[0]/r[0],t[1]=n[1]/r[1],t}function s(t,n){var r=n[0]-t[0],a=n[1]-t[1];return Math.sqrt(r*r+a*a)}function c(t,n){var r=n[0]-t[0],a=n[1]-t[1];return r*r+a*a}function f(t){var n=t[0],r=t[1];return Math.sqrt(n*n+r*r)}function M(t){var n=t[0],r=t[1];return n*n+r*r}n.len=f,n.sub=u,n.mul=o,n.div=i,n.dist=s,n.sqrDist=c,n.sqrLen=M,n.forEach=function(){var t=e();return function(n,r,a,e,u,o){var i=void 0,s=void 0;for(r||(r=2),a||(a=0),s=e?Math.min(e*r+a,n.length):n.length,i=a;i<s;i+=r)t[0]=n[i],t[1]=n[i+1],u(t,t,o),n[i]=t[0],n[i+1]=t[1];return n}}()},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.sqrLen=n.squaredLength=n.len=n.length=n.dot=n.mul=n.setReal=n.getReal=void 0,n.create=function(){var t=new a.ARRAY_TYPE(8);a.ARRAY_TYPE!=Float32Array&&(t[0]=0,t[1]=0,t[2]=0,t[4]=0,t[5]=0,t[6]=0,t[7]=0);return t[3]=1,t},n.clone=function(t){var n=new a.ARRAY_TYPE(8);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n[3]=t[3],n[4]=t[4],n[5]=t[5],n[6]=t[6],n[7]=t[7],n},n.fromValues=function(t,n,r,e,u,o,i,s){var c=new a.ARRAY_TYPE(8);return c[0]=t,c[1]=n,c[2]=r,c[3]=e,c[4]=u,c[5]=o,c[6]=i,c[7]=s,c},n.fromRotationTranslationValues=function(t,n,r,e,u,o,i){var s=new a.ARRAY_TYPE(8);s[0]=t,s[1]=n,s[2]=r,s[3]=e;var c=.5*u,f=.5*o,M=.5*i;return s[4]=c*e+f*r-M*n,s[5]=f*e+M*t-c*r,s[6]=M*e+c*n-f*t,s[7]=-c*t-f*n-M*r,s},n.fromRotationTranslation=i,n.fromTranslation=function(t,n){return t[0]=0,t[1]=0,t[2]=0,t[3]=1,t[4]=.5*n[0],t[5]=.5*n[1],t[6]=.5*n[2],t[7]=0,t},n.fromRotation=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[4]=0,t[5]=0,t[6]=0,t[7]=0,t},n.fromMat4=function(t,n){var r=e.create();u.getRotation(r,n);var o=new a.ARRAY_TYPE(3);return u.getTranslation(o,n),i(t,r,o),t},n.copy=s,n.identity=function(t){return t[0]=0,t[1]=0,t[2]=0,t[3]=1,t[4]=0,t[5]=0,t[6]=0,t[7]=0,t},n.set=function(t,n,r,a,e,u,o,i,s){return t[0]=n,t[1]=r,t[2]=a,t[3]=e,t[4]=u,t[5]=o,t[6]=i,t[7]=s,t},n.getDual=function(t,n){return t[0]=n[4],t[1]=n[5],t[2]=n[6],t[3]=n[7],t},n.setDual=function(t,n){return t[4]=n[0],t[5]=n[1],t[6]=n[2],t[7]=n[3],t},n.getTranslation=function(t,n){var r=n[4],a=n[5],e=n[6],u=n[7],o=-n[0],i=-n[1],s=-n[2],c=n[3];return t[0]=2*(r*c+u*o+a*s-e*i),t[1]=2*(a*c+u*i+e*o-r*s),t[2]=2*(e*c+u*s+r*i-a*o),t},n.translate=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=.5*r[0],s=.5*r[1],c=.5*r[2],f=n[4],M=n[5],h=n[6],l=n[7];return t[0]=a,t[1]=e,t[2]=u,t[3]=o,t[4]=o*i+e*c-u*s+f,t[5]=o*s+u*i-a*c+M,t[6]=o*c+a*s-e*i+h,t[7]=-a*i-e*s-u*c+l,t},n.rotateX=function(t,n,r){var a=-n[0],u=-n[1],o=-n[2],i=n[3],s=n[4],c=n[5],f=n[6],M=n[7],h=s*i+M*a+c*o-f*u,l=c*i+M*u+f*a-s*o,v=f*i+M*o+s*u-c*a,d=M*i-s*a-c*u-f*o;return e.rotateX(t,n,r),a=t[0],u=t[1],o=t[2],i=t[3],t[4]=h*i+d*a+l*o-v*u,t[5]=l*i+d*u+v*a-h*o,t[6]=v*i+d*o+h*u-l*a,t[7]=d*i-h*a-l*u-v*o,t},n.rotateY=function(t,n,r){var a=-n[0],u=-n[1],o=-n[2],i=n[3],s=n[4],c=n[5],f=n[6],M=n[7],h=s*i+M*a+c*o-f*u,l=c*i+M*u+f*a-s*o,v=f*i+M*o+s*u-c*a,d=M*i-s*a-c*u-f*o;return e.rotateY(t,n,r),a=t[0],u=t[1],o=t[2],i=t[3],t[4]=h*i+d*a+l*o-v*u,t[5]=l*i+d*u+v*a-h*o,t[6]=v*i+d*o+h*u-l*a,t[7]=d*i-h*a-l*u-v*o,t},n.rotateZ=function(t,n,r){var a=-n[0],u=-n[1],o=-n[2],i=n[3],s=n[4],c=n[5],f=n[6],M=n[7],h=s*i+M*a+c*o-f*u,l=c*i+M*u+f*a-s*o,v=f*i+M*o+s*u-c*a,d=M*i-s*a-c*u-f*o;return e.rotateZ(t,n,r),a=t[0],u=t[1],o=t[2],i=t[3],t[4]=h*i+d*a+l*o-v*u,t[5]=l*i+d*u+v*a-h*o,t[6]=v*i+d*o+h*u-l*a,t[7]=d*i-h*a-l*u-v*o,t},n.rotateByQuatAppend=function(t,n,r){var a=r[0],e=r[1],u=r[2],o=r[3],i=n[0],s=n[1],c=n[2],f=n[3];return t[0]=i*o+f*a+s*u-c*e,t[1]=s*o+f*e+c*a-i*u,t[2]=c*o+f*u+i*e-s*a,t[3]=f*o-i*a-s*e-c*u,i=n[4],s=n[5],c=n[6],f=n[7],t[4]=i*o+f*a+s*u-c*e,t[5]=s*o+f*e+c*a-i*u,t[6]=c*o+f*u+i*e-s*a,t[7]=f*o-i*a-s*e-c*u,t},n.rotateByQuatPrepend=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=r[0],s=r[1],c=r[2],f=r[3];return t[0]=a*f+o*i+e*c-u*s,t[1]=e*f+o*s+u*i-a*c,t[2]=u*f+o*c+a*s-e*i,t[3]=o*f-a*i-e*s-u*c,i=r[4],s=r[5],c=r[6],f=r[7],t[4]=a*f+o*i+e*c-u*s,t[5]=e*f+o*s+u*i-a*c,t[6]=u*f+o*c+a*s-e*i,t[7]=o*f-a*i-e*s-u*c,t},n.rotateAroundAxis=function(t,n,r,e){if(Math.abs(e)<a.EPSILON)return s(t,n);var u=Math.sqrt(r[0]*r[0]+r[1]*r[1]+r[2]*r[2]);e*=.5;var o=Math.sin(e),i=o*r[0]/u,c=o*r[1]/u,f=o*r[2]/u,M=Math.cos(e),h=n[0],l=n[1],v=n[2],d=n[3];t[0]=h*M+d*i+l*f-v*c,t[1]=l*M+d*c+v*i-h*f,t[2]=v*M+d*f+h*c-l*i,t[3]=d*M-h*i-l*c-v*f;var b=n[4],m=n[5],p=n[6],P=n[7];return t[4]=b*M+P*i+m*f-p*c,t[5]=m*M+P*c+p*i-b*f,t[6]=p*M+P*f+b*c-m*i,t[7]=P*M-b*i-m*c-p*f,t},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t[3]=n[3]+r[3],t[4]=n[4]+r[4],t[5]=n[5]+r[5],t[6]=n[6]+r[6],t[7]=n[7]+r[7],t},n.multiply=c,n.scale=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=n[3]*r,t[4]=n[4]*r,t[5]=n[5]*r,t[6]=n[6]*r,t[7]=n[7]*r,t},n.lerp=function(t,n,r,a){var e=1-a;f(n,r)<0&&(a=-a);return t[0]=n[0]*e+r[0]*a,t[1]=n[1]*e+r[1]*a,t[2]=n[2]*e+r[2]*a,t[3]=n[3]*e+r[3]*a,t[4]=n[4]*e+r[4]*a,t[5]=n[5]*e+r[5]*a,t[6]=n[6]*e+r[6]*a,t[7]=n[7]*e+r[7]*a,t},n.invert=function(t,n){var r=h(n);return t[0]=-n[0]/r,t[1]=-n[1]/r,t[2]=-n[2]/r,t[3]=n[3]/r,t[4]=-n[4]/r,t[5]=-n[5]/r,t[6]=-n[6]/r,t[7]=n[7]/r,t},n.conjugate=function(t,n){return t[0]=-n[0],t[1]=-n[1],t[2]=-n[2],t[3]=n[3],t[4]=-n[4],t[5]=-n[5],t[6]=-n[6],t[7]=n[7],t},n.normalize=function(t,n){var r=h(n);if(r>0){r=Math.sqrt(r);var a=n[0]/r,e=n[1]/r,u=n[2]/r,o=n[3]/r,i=n[4],s=n[5],c=n[6],f=n[7],M=a*i+e*s+u*c+o*f;t[0]=a,t[1]=e,t[2]=u,t[3]=o,t[4]=(i-a*M)/r,t[5]=(s-e*M)/r,t[6]=(c-u*M)/r,t[7]=(f-o*M)/r}return t},n.str=function(t){return"quat2("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+", "+t[4]+", "+t[5]+", "+t[6]+", "+t[7]+")"},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]&&t[4]===n[4]&&t[5]===n[5]&&t[6]===n[6]&&t[7]===n[7]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=t[3],i=t[4],s=t[5],c=t[6],f=t[7],M=n[0],h=n[1],l=n[2],v=n[3],d=n[4],b=n[5],m=n[6],p=n[7];return Math.abs(r-M)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(M))&&Math.abs(e-h)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(h))&&Math.abs(u-l)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(l))&&Math.abs(o-v)<=a.EPSILON*Math.max(1,Math.abs(o),Math.abs(v))&&Math.abs(i-d)<=a.EPSILON*Math.max(1,Math.abs(i),Math.abs(d))&&Math.abs(s-b)<=a.EPSILON*Math.max(1,Math.abs(s),Math.abs(b))&&Math.abs(c-m)<=a.EPSILON*Math.max(1,Math.abs(c),Math.abs(m))&&Math.abs(f-p)<=a.EPSILON*Math.max(1,Math.abs(f),Math.abs(p))};var a=o(r(0)),e=o(r(3)),u=o(r(4));function o(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}function i(t,n,r){var a=.5*r[0],e=.5*r[1],u=.5*r[2],o=n[0],i=n[1],s=n[2],c=n[3];return t[0]=o,t[1]=i,t[2]=s,t[3]=c,t[4]=a*c+e*s-u*i,t[5]=e*c+u*o-a*s,t[6]=u*c+a*i-e*o,t[7]=-a*o-e*i-u*s,t}function s(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[4]=n[4],t[5]=n[5],t[6]=n[6],t[7]=n[7],t}n.getReal=e.copy;n.setReal=e.copy;function c(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=r[4],s=r[5],c=r[6],f=r[7],M=n[4],h=n[5],l=n[6],v=n[7],d=r[0],b=r[1],m=r[2],p=r[3];return t[0]=a*p+o*d+e*m-u*b,t[1]=e*p+o*b+u*d-a*m,t[2]=u*p+o*m+a*b-e*d,t[3]=o*p-a*d-e*b-u*m,t[4]=a*f+o*i+e*c-u*s+M*p+v*d+h*m-l*b,t[5]=e*f+o*s+u*i-a*c+h*p+v*b+l*d-M*m,t[6]=u*f+o*c+a*s-e*i+l*p+v*m+M*b-h*d,t[7]=o*f-a*i-e*s-u*c+v*p-M*d-h*b-l*m,t}n.mul=c;var f=n.dot=e.dot;var M=n.length=e.length,h=(n.len=M,n.squaredLength=e.squaredLength);n.sqrLen=h},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.sub=n.mul=void 0,n.create=function(){var t=new a.ARRAY_TYPE(6);a.ARRAY_TYPE!=Float32Array&&(t[1]=0,t[2]=0,t[4]=0,t[5]=0);return t[0]=1,t[3]=1,t},n.clone=function(t){var n=new a.ARRAY_TYPE(6);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n[3]=t[3],n[4]=t[4],n[5]=t[5],n},n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t[4]=n[4],t[5]=n[5],t},n.identity=function(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t[4]=0,t[5]=0,t},n.fromValues=function(t,n,r,e,u,o){var i=new a.ARRAY_TYPE(6);return i[0]=t,i[1]=n,i[2]=r,i[3]=e,i[4]=u,i[5]=o,i},n.set=function(t,n,r,a,e,u,o){return t[0]=n,t[1]=r,t[2]=a,t[3]=e,t[4]=u,t[5]=o,t},n.invert=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=n[4],i=n[5],s=r*u-a*e;if(!s)return null;return s=1/s,t[0]=u*s,t[1]=-a*s,t[2]=-e*s,t[3]=r*s,t[4]=(e*i-u*o)*s,t[5]=(a*o-r*i)*s,t},n.determinant=function(t){return t[0]*t[3]-t[1]*t[2]},n.multiply=e,n.rotate=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=Math.sin(r),f=Math.cos(r);return t[0]=a*f+u*c,t[1]=e*f+o*c,t[2]=a*-c+u*f,t[3]=e*-c+o*f,t[4]=i,t[5]=s,t},n.scale=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=r[0],f=r[1];return t[0]=a*c,t[1]=e*c,t[2]=u*f,t[3]=o*f,t[4]=i,t[5]=s,t},n.translate=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=r[0],f=r[1];return t[0]=a,t[1]=e,t[2]=u,t[3]=o,t[4]=a*c+u*f+i,t[5]=e*c+o*f+s,t},n.fromRotation=function(t,n){var r=Math.sin(n),a=Math.cos(n);return t[0]=a,t[1]=r,t[2]=-r,t[3]=a,t[4]=0,t[5]=0,t},n.fromScaling=function(t,n){return t[0]=n[0],t[1]=0,t[2]=0,t[3]=n[1],t[4]=0,t[5]=0,t},n.fromTranslation=function(t,n){return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t[4]=n[0],t[5]=n[1],t},n.str=function(t){return"mat2d("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+", "+t[4]+", "+t[5]+")"},n.frob=function(t){return Math.sqrt(Math.pow(t[0],2)+Math.pow(t[1],2)+Math.pow(t[2],2)+Math.pow(t[3],2)+Math.pow(t[4],2)+Math.pow(t[5],2)+1)},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t[3]=n[3]+r[3],t[4]=n[4]+r[4],t[5]=n[5]+r[5],t},n.subtract=u,n.multiplyScalar=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=n[3]*r,t[4]=n[4]*r,t[5]=n[5]*r,t},n.multiplyScalarAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t[3]=n[3]+r[3]*a,t[4]=n[4]+r[4]*a,t[5]=n[5]+r[5]*a,t},n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]&&t[4]===n[4]&&t[5]===n[5]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=t[3],i=t[4],s=t[5],c=n[0],f=n[1],M=n[2],h=n[3],l=n[4],v=n[5];return Math.abs(r-c)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(c))&&Math.abs(e-f)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(f))&&Math.abs(u-M)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(M))&&Math.abs(o-h)<=a.EPSILON*Math.max(1,Math.abs(o),Math.abs(h))&&Math.abs(i-l)<=a.EPSILON*Math.max(1,Math.abs(i),Math.abs(l))&&Math.abs(s-v)<=a.EPSILON*Math.max(1,Math.abs(s),Math.abs(v))};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=n[4],s=n[5],c=r[0],f=r[1],M=r[2],h=r[3],l=r[4],v=r[5];return t[0]=a*c+u*f,t[1]=e*c+o*f,t[2]=a*M+u*h,t[3]=e*M+o*h,t[4]=a*l+u*v+i,t[5]=e*l+o*v+s,t}function u(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t[3]=n[3]-r[3],t[4]=n[4]-r[4],t[5]=n[5]-r[5],t}n.mul=e,n.sub=u},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.sub=n.mul=void 0,n.create=function(){var t=new a.ARRAY_TYPE(4);a.ARRAY_TYPE!=Float32Array&&(t[1]=0,t[2]=0);return t[0]=1,t[3]=1,t},n.clone=function(t){var n=new a.ARRAY_TYPE(4);return n[0]=t[0],n[1]=t[1],n[2]=t[2],n[3]=t[3],n},n.copy=function(t,n){return t[0]=n[0],t[1]=n[1],t[2]=n[2],t[3]=n[3],t},n.identity=function(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t},n.fromValues=function(t,n,r,e){var u=new a.ARRAY_TYPE(4);return u[0]=t,u[1]=n,u[2]=r,u[3]=e,u},n.set=function(t,n,r,a,e){return t[0]=n,t[1]=r,t[2]=a,t[3]=e,t},n.transpose=function(t,n){if(t===n){var r=n[1];t[1]=n[2],t[2]=r}else t[0]=n[0],t[1]=n[2],t[2]=n[1],t[3]=n[3];return t},n.invert=function(t,n){var r=n[0],a=n[1],e=n[2],u=n[3],o=r*u-e*a;if(!o)return null;return o=1/o,t[0]=u*o,t[1]=-a*o,t[2]=-e*o,t[3]=r*o,t},n.adjoint=function(t,n){var r=n[0];return t[0]=n[3],t[1]=-n[1],t[2]=-n[2],t[3]=r,t},n.determinant=function(t){return t[0]*t[3]-t[2]*t[1]},n.multiply=e,n.rotate=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=Math.sin(r),s=Math.cos(r);return t[0]=a*s+u*i,t[1]=e*s+o*i,t[2]=a*-i+u*s,t[3]=e*-i+o*s,t},n.scale=function(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=r[0],s=r[1];return t[0]=a*i,t[1]=e*i,t[2]=u*s,t[3]=o*s,t},n.fromRotation=function(t,n){var r=Math.sin(n),a=Math.cos(n);return t[0]=a,t[1]=r,t[2]=-r,t[3]=a,t},n.fromScaling=function(t,n){return t[0]=n[0],t[1]=0,t[2]=0,t[3]=n[1],t},n.str=function(t){return"mat2("+t[0]+", "+t[1]+", "+t[2]+", "+t[3]+")"},n.frob=function(t){return Math.sqrt(Math.pow(t[0],2)+Math.pow(t[1],2)+Math.pow(t[2],2)+Math.pow(t[3],2))},n.LDU=function(t,n,r,a){return t[2]=a[2]/a[0],r[0]=a[0],r[1]=a[1],r[3]=a[3]-t[2]*r[1],[t,n,r]},n.add=function(t,n,r){return t[0]=n[0]+r[0],t[1]=n[1]+r[1],t[2]=n[2]+r[2],t[3]=n[3]+r[3],t},n.subtract=u,n.exactEquals=function(t,n){return t[0]===n[0]&&t[1]===n[1]&&t[2]===n[2]&&t[3]===n[3]},n.equals=function(t,n){var r=t[0],e=t[1],u=t[2],o=t[3],i=n[0],s=n[1],c=n[2],f=n[3];return Math.abs(r-i)<=a.EPSILON*Math.max(1,Math.abs(r),Math.abs(i))&&Math.abs(e-s)<=a.EPSILON*Math.max(1,Math.abs(e),Math.abs(s))&&Math.abs(u-c)<=a.EPSILON*Math.max(1,Math.abs(u),Math.abs(c))&&Math.abs(o-f)<=a.EPSILON*Math.max(1,Math.abs(o),Math.abs(f))},n.multiplyScalar=function(t,n,r){return t[0]=n[0]*r,t[1]=n[1]*r,t[2]=n[2]*r,t[3]=n[3]*r,t},n.multiplyScalarAndAdd=function(t,n,r,a){return t[0]=n[0]+r[0]*a,t[1]=n[1]+r[1]*a,t[2]=n[2]+r[2]*a,t[3]=n[3]+r[3]*a,t};var a=function(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}(r(0));function e(t,n,r){var a=n[0],e=n[1],u=n[2],o=n[3],i=r[0],s=r[1],c=r[2],f=r[3];return t[0]=a*i+u*s,t[1]=e*i+o*s,t[2]=a*c+u*f,t[3]=e*c+o*f,t}function u(t,n,r){return t[0]=n[0]-r[0],t[1]=n[1]-r[1],t[2]=n[2]-r[2],t[3]=n[3]-r[3],t}n.mul=e,n.sub=u},function(t,n,r){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.vec4=n.vec3=n.vec2=n.quat2=n.quat=n.mat4=n.mat3=n.mat2d=n.mat2=n.glMatrix=void 0;var a=l(r(0)),e=l(r(9)),u=l(r(8)),o=l(r(5)),i=l(r(4)),s=l(r(3)),c=l(r(7)),f=l(r(6)),M=l(r(2)),h=l(r(1));function l(t){if(t&&t.__esModule)return t;var n={};if(null!=t)for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(n[r]=t[r]);return n.default=t,n}n.glMatrix=a,n.mat2=e,n.mat2d=u,n.mat3=o,n.mat4=i,n.quat=s,n.quat2=c,n.vec2=f,n.vec3=M,n.vec4=h}])});PK
!<}�oA��;chrome/toolkit/content/global/usercharacteristics/ssdeep.js/*!
The MIT License (MIT)

Copyright (c) 2015 Hu Wenshuo

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

// Trimmed down version of ssdeep.js from https://github.com/cloudtracer/ssdeep.js

(function () {
  var ssdeep = {};
  this.ssdeep = ssdeep;

  var HASH_PRIME = 16777619;
  var HASH_INIT = 671226215;
  var ROLLING_WINDOW = 7;
  var MAX_LENGTH = 64; // Max individual hash length in characters
  var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

  /*
   * Add integers, wrapping at 2^32. This uses 16-bit operations internally
   * to work around bugs in some JS interpreters.
   */
  function safe_add(x, y) {
    var lsw = (x & 0xffff) + (y & 0xffff);
    var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
    return (msw << 16) | (lsw & 0xffff);
  }

  /*
      1000 0000
      1000 0000
      0000 0001
    */

  function safe_multiply(x, y) {
    /*
  			a = a00 + a16
  			b = b00 + b16
  			a*b = (a00 + a16)(b00 + b16)
  				= a00b00 + a00b16 + a16b00 + a16b16

  			a16b16 overflows the 32bits
  		 */
    var xlsw = x & 0xffff;
    var xmsw = (x >> 16) + (xlsw >> 16);
    var ylsw = y & 0xffff;
    var ymsw = (y >> 16) + (ylsw >> 16);
    var a16 = xmsw;
    var a00 = xlsw;
    var b16 = ymsw;
    var b00 = ylsw;
    var c16, c00;
    c00 = a00 * b00;
    c16 = c00 >>> 16;

    c16 += a16 * b00;
    c16 &= 0xffff; // Not required but improves performance
    c16 += a00 * b16;

    xlsw = c00 & 0xffff;
    xmsw = c16 & 0xffff;

    return (xmsw << 16) | (xlsw & 0xffff);
  }

  //FNV-1 hash
  function fnv(h, c) {
    return (safe_multiply(h, HASH_PRIME) ^ c) >>> 0;
  }

  function RollHash() {
    this.rolling_window = new Array(ROLLING_WINDOW);
    this.h1 = 0;
    this.h2 = 0;
    this.h3 = 0;
    this.n = 0;
  }
  RollHash.prototype.update = function (c) {
    this.h2 = safe_add(this.h2, -this.h1);
    var mut = ROLLING_WINDOW * c;
    this.h2 = safe_add(this.h2, mut) >>> 0;
    this.h1 = safe_add(this.h1, c);

    var val = this.rolling_window[this.n % ROLLING_WINDOW] || 0;
    this.h1 = safe_add(this.h1, -val) >>> 0;
    this.rolling_window[this.n % ROLLING_WINDOW] = c;
    this.n++;

    this.h3 = this.h3 << 5;
    this.h3 = (this.h3 ^ c) >>> 0;
  };
  RollHash.prototype.sum = function () {
    return (this.h1 + this.h2 + this.h3) >>> 0;
  };

  function piecewiseHash(bytes, triggerValue) {
    var signatures = ["", "", triggerValue];
    if (bytes.length === 0) {
      return signatures;
    }
    var h1 = HASH_INIT;
    var h2 = HASH_INIT;
    var rh = new RollHash();
    //console.log(triggerValue)
    for (var i = 0, len = bytes.length; i < len; i++) {
      var thisByte = bytes[i];

      h1 = fnv(h1, thisByte);
      h2 = fnv(h2, thisByte);

      rh.update(thisByte);

      if (
        signatures[0].length < MAX_LENGTH - 1 &&
        rh.sum() % triggerValue === triggerValue - 1
      ) {
        signatures[0] += B64.charAt(h1 & 63);
        h1 = HASH_INIT;
      }
      if (
        signatures[1].length < MAX_LENGTH / 2 - 1 &&
        rh.sum() % (triggerValue * 2) === triggerValue * 2 - 1
      ) {
        signatures[1] += B64.charAt(h2 & 63);
        h2 = HASH_INIT;
      }
    }
    signatures[0] += B64.charAt(h1 & 63);
    signatures[1] += B64.charAt(h2 & 63);
    return signatures;
  }

  function digest(bytes) {
    var bi = 3;
    while (bi * MAX_LENGTH < bytes.length) {
      bi *= 2;
    }

    var signatures;
    do {
      signatures = piecewiseHash(bytes, bi);
      bi = ~~(bi / 2);
    } while (bi > 3 && signatures[0].length < MAX_LENGTH / 2);

    return signatures[2] + ":" + signatures[0] + ":" + signatures[1];
  }

  ssdeep.digest = function (data) {
    if (typeof data === "string") {
      data = new TextEncoder().encode(data);
    }
    return digest(data);
  };
})();
PK
!<]��Ichrome/toolkit/content/global/usercharacteristics/usercharacteristics.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

.testcanvas {
  border: 1px solid #2600ff;
}
PK
!<��B=KcKcJchrome/toolkit/content/global/usercharacteristics/usercharacteristics.html<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at https://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src data: resource: chrome:; style-src-elem chrome:; object-src 'none'; script-src chrome:"
    />
    <title>about:fingerprintingprotection</title>

    <link
      href="chrome://global/content/usercharacteristics/usercharacteristics.css"
      rel="stylesheet"
      type="text/css"
    />
  </head>
  <body>
    <div id="test_canvases">
      <h2>Test Canvases</h2>
      <div>
        <h3>Canvas 1</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas1"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input type="text" name="canvas1data" id="canvas1data" readonly />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>Canvas 2</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas2"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input type="text" name="canvas2data" id="canvas2data" readonly />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>Canvas 3</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas3"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input type="text" name="canvas3data" id="canvas3data" readonly />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>Canvas 4</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas4"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input type="text" name="canvas4data" id="canvas4data" readonly />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>Canvas 5</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas5"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input type="text" name="canvas5data" id="canvas5data" readonly />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>Canvas 6</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas6"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input type="text" name="canvas6data" id="canvas6data" readonly />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>Canvas 7</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas7"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input type="text" name="canvas7data" id="canvas7data" readonly />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>Canvas 8</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas8"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input type="text" name="canvas8data" id="canvas8data" readonly />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>Canvas 9</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas9"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input type="text" name="canvas9data" id="canvas9data" readonly />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>Canvas 10</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas10"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input
                type="text"
                name="canvas10data"
                id="canvas10data"
                readonly
              />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>WebGL Canvas</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="glcanvas"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input
                type="text"
                name="glcanvasdata"
                id="glcanvasdata"
                readonly
              />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>FingerprintJS Canvas 1 (Text)</h3>
        <canvas
          class="testcanvas"
          width="240"
          height="60"
          id="fingerprintjscanvas1"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input
                type="text"
                name="fingerprintjscanvas1data"
                id="fingerprintjscanvas1data"
                readonly
              />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>FingerprintJS Canvas 2 (Geometry)</h3>
        <canvas
          class="testcanvas"
          width="122"
          height="110"
          id="fingerprintjscanvas2"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input
                type="text"
                name="fingerprintjscanvas2data"
                id="fingerprintjscanvas2data"
                readonly
              />
            </td>
          </tr>
        </table>
      </div>
    </div>
    <div id="test_canvases">
      <h2>Test Canvases Software</h2>
      <div>
        <h3>Canvas 1</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas1software"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input type="text" name="canvas1data" id="canvas1data" readonly />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>Canvas 2</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas2software"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input type="text" name="canvas2data" id="canvas2data" readonly />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>Canvas 3</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas3software"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input type="text" name="canvas3data" id="canvas3data" readonly />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>Canvas 4</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas4software"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input type="text" name="canvas4data" id="canvas4data" readonly />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>Canvas 5</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas5software"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input type="text" name="canvas5data" id="canvas5data" readonly />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>Canvas 6</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas6software"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input type="text" name="canvas6data" id="canvas6data" readonly />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>Canvas 7</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas7software"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input type="text" name="canvas7data" id="canvas7data" readonly />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>Canvas 8</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas8software"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input type="text" name="canvas8data" id="canvas8data" readonly />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>Canvas 9</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas9software"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input type="text" name="canvas9data" id="canvas9data" readonly />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>Canvas 10</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="canvas10software"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input
                type="text"
                name="canvas10data"
                id="canvas10data"
                readonly
              />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>WebGL Canvas</h3>
        <canvas
          class="testcanvas"
          width="250"
          height="250"
          id="glcanvassoftware"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input
                type="text"
                name="glcanvasdata"
                id="glcanvasdata"
                readonly
              />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>FingerprintJS Canvas 1 (Text)</h3>
        <canvas
          class="testcanvas"
          width="240"
          height="60"
          id="fingerprintjscanvas1software"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input
                type="text"
                name="fingerprintjscanvas1data"
                id="fingerprintjscanvas1data"
                readonly
              />
            </td>
          </tr>
        </table>
      </div>
      <div>
        <h3>FingerprintJS Canvas 2 (Geometry)</h3>
        <canvas
          class="testcanvas"
          width="122"
          height="110"
          id="fingerprintjscanvas2software"
        ></canvas>
        <table>
          <tr>
            <td>ImageData:</td>
            <td>
              <input
                type="text"
                name="fingerprintjscanvas2data"
                id="fingerprintjscanvas2data"
                readonly
              />
            </td>
          </tr>
        </table>
      </div>
    </div>

    <!--
      MathML Width Tests. Last 10 examples from the MathML Torture Test.
      https://github.com/mdn/archived-content/blob/b0aa15f42b0b53e790dc175ae8914b61460593a8/files/en-us/mozilla/mathml_project/mathml_torture_test/raw.html
     -->
    <div>
      <math id="1">
        <mrow>
          <munder>
            <mo>∑</mo>
            <mrow>
              <mi>p</mi>
              <mtext>&nbsp;prime</mtext>
            </mrow>
          </munder>
          <mi>f</mi>
          <mo stretchy="false">(</mo>
          <mi>p</mi>
          <mo stretchy="false">)</mo>
          <mo>=</mo>
          <msub>
            <mo stretchy="false">∫</mo>
            <mrow>
              <mi>t</mi>
              <mo>&gt;</mo>
              <mn>1</mn>
            </mrow>
          </msub>
          <mi>f</mi>
          <mo stretchy="false">(</mo>
          <mi>t</mi>
          <mo stretchy="false">)</mo>
          <mspace width="thinmathspace"></mspace>
          <mi>d</mi>
          <mi>π</mi>
          <mo stretchy="false">(</mo>
          <mi>t</mi>
          <mo stretchy="false">)</mo>
        </mrow>
      </math>
      <math id="2">
        <mrow>
          <mo stretchy="false">{</mo>
          <munder>
            <mrow>
              <mover>
                <mrow>
                  <mpadded width="0em">
                    <mphantom>
                      <mo>(</mo>
                    </mphantom>
                  </mpadded>
                  <mi>a</mi>
                  <mo>,</mo>
                  <mo>...</mo>
                  <mo>,</mo>
                  <mi>a</mi>
                </mrow>
                <mover>
                  <mo>⏞</mo>
                  <mrow>
                    <mi>k</mi>
                    <mtext>&nbsp;</mtext>
                    <mi>a</mi>
                    <mtext>'s</mtext>
                  </mrow>
                </mover>
              </mover>
              <mo>,</mo>
              <mover>
                <mrow>
                  <mpadded width="0em">
                    <mphantom>
                      <mo>(</mo>
                    </mphantom>
                  </mpadded>
                  <mi>b</mi>
                  <mo>,</mo>
                  <mo>...</mo>
                  <mo>,</mo>
                  <mi>b</mi>
                </mrow>
                <mover>
                  <mo>⏞</mo>
                  <mrow>
                    <mi>ℓ</mi>
                    <mtext>&nbsp;</mtext>
                    <mi>b</mi>
                    <mtext>'s</mtext>
                  </mrow>
                </mover>
              </mover>
            </mrow>
            <munder>
              <mo>⏟</mo>
              <mrow>
                <mi>k</mi>
                <mo>+</mo>
                <mi>ℓ</mi>
                <mtext>&nbsp;elements</mtext>
              </mrow>
            </munder>
          </munder>
          <mo stretchy="false">}</mo>
        </mrow>
      </math>
      <math id="3">
        <mrow>
          <mo>(</mo>
          <mtable>
            <mtr>
              <mtd columnalign="center">
                <mrow>
                  <mo>(</mo>
                  <mtable>
                    <mtr>
                      <mtd columnalign="center">
                        <mi>a</mi>
                      </mtd>
                      <mtd columnalign="center">
                        <mi>b</mi>
                      </mtd>
                    </mtr>
                    <mtr>
                      <mtd columnalign="center">
                        <mi>c</mi>
                      </mtd>
                      <mtd columnalign="center">
                        <mi>d</mi>
                      </mtd>
                    </mtr>
                  </mtable>
                  <mo>)</mo>
                </mrow>
              </mtd>
              <mtd columnalign="center">
                <mrow>
                  <mo>(</mo>
                  <mtable>
                    <mtr>
                      <mtd columnalign="center">
                        <mi>e</mi>
                      </mtd>
                      <mtd columnalign="center">
                        <mi>f</mi>
                      </mtd>
                    </mtr>
                    <mtr>
                      <mtd columnalign="center">
                        <mi>g</mi>
                      </mtd>
                      <mtd columnalign="center">
                        <mi>h</mi>
                      </mtd>
                    </mtr>
                  </mtable>
                  <mo>)</mo>
                </mrow>
              </mtd>
            </mtr>
            <mtr>
              <mtd columnalign="center">
                <mn>0</mn>
              </mtd>
              <mtd columnalign="center">
                <mrow>
                  <mo>(</mo>
                  <mtable>
                    <mtr>
                      <mtd columnalign="center">
                        <mi>i</mi>
                      </mtd>
                      <mtd columnalign="center">
                        <mi>j</mi>
                      </mtd>
                    </mtr>
                    <mtr>
                      <mtd columnalign="center">
                        <mi>k</mi>
                      </mtd>
                      <mtd columnalign="center">
                        <mi>l</mi>
                      </mtd>
                    </mtr>
                  </mtable>
                  <mo>)</mo>
                </mrow>
              </mtd>
            </mtr>
          </mtable>
          <mo>)</mo>
        </mrow>
      </math>
      <math id="4">
        <mrow>
          <mi>det</mi>
          <mo>|</mo>
          <mtable>
            <mtr>
              <mtd columnalign="center">
                <msub>
                  <mi>c</mi>
                  <mn>0</mn>
                </msub>
              </mtd>
              <mtd columnalign="center">
                <msub>
                  <mi>c</mi>
                  <mn>1</mn>
                </msub>
              </mtd>
              <mtd columnalign="center">
                <msub>
                  <mi>c</mi>
                  <mn>2</mn>
                </msub>
              </mtd>
              <mtd columnalign="center">
                <mo>…</mo>
              </mtd>
              <mtd columnalign="center">
                <msub>
                  <mi>c</mi>
                  <mi>n</mi>
                </msub>
              </mtd>
            </mtr>
            <mtr>
              <mtd columnalign="center">
                <msub>
                  <mi>c</mi>
                  <mn>1</mn>
                </msub>
              </mtd>
              <mtd columnalign="center">
                <msub>
                  <mi>c</mi>
                  <mn>2</mn>
                </msub>
              </mtd>
              <mtd columnalign="center">
                <msub>
                  <mi>c</mi>
                  <mn>3</mn>
                </msub>
              </mtd>
              <mtd columnalign="center">
                <mo>…</mo>
              </mtd>
              <mtd columnalign="center">
                <msub>
                  <mi>c</mi>
                  <mrow>
                    <mi>n</mi>
                    <mo>+</mo>
                    <mn>1</mn>
                  </mrow>
                </msub>
              </mtd>
            </mtr>
            <mtr>
              <mtd columnalign="center">
                <msub>
                  <mi>c</mi>
                  <mn>2</mn>
                </msub>
              </mtd>
              <mtd columnalign="center">
                <msub>
                  <mi>c</mi>
                  <mn>3</mn>
                </msub>
              </mtd>
              <mtd columnalign="center">
                <msub>
                  <mi>c</mi>
                  <mn>4</mn>
                </msub>
              </mtd>
              <mtd columnalign="center">
                <mo>…</mo>
              </mtd>
              <mtd columnalign="center">
                <msub>
                  <mi>c</mi>
                  <mrow>
                    <mi>n</mi>
                    <mo>+</mo>
                    <mn>2</mn>
                  </mrow>
                </msub>
              </mtd>
            </mtr>
            <mtr>
              <mtd columnalign="center">
                <mo>⋮</mo>
              </mtd>
              <mtd columnalign="center">
                <mo>⋮</mo>
              </mtd>
              <mtd columnalign="center">
                <mo>⋮</mo>
              </mtd>
              <mtd columnalign="center"> </mtd>
              <mtd columnalign="center">
                <mo>⋮</mo>
              </mtd>
            </mtr>
            <mtr>
              <mtd columnalign="center">
                <msub>
                  <mi>c</mi>
                  <mi>n</mi>
                </msub>
              </mtd>
              <mtd columnalign="center">
                <msub>
                  <mi>c</mi>
                  <mrow>
                    <mi>n</mi>
                    <mo>+</mo>
                    <mn>1</mn>
                  </mrow>
                </msub>
              </mtd>
              <mtd columnalign="center">
                <msub>
                  <mi>c</mi>
                  <mrow>
                    <mi>n</mi>
                    <mo>+</mo>
                    <mn>2</mn>
                  </mrow>
                </msub>
              </mtd>
              <mtd columnalign="center">
                <mo>…</mo>
              </mtd>
              <mtd columnalign="center">
                <msub>
                  <mi>c</mi>
                  <mrow>
                    <mn>2</mn>
                    <mi>n</mi>
                  </mrow>
                </msub>
              </mtd>
            </mtr>
          </mtable>
          <mo>|</mo>
          <mo>&gt;</mo>
          <mn>0</mn>
        </mrow>
      </math>
      <math id="5">
        <msub>
          <mi>y</mi>
          <msub>
            <mi>x</mi>
            <mn>2</mn>
          </msub>
        </msub>
      </math>
      <math id="6">
        <mrow>
          <msubsup>
            <mi>x</mi>
            <mn>92</mn>
            <mn>31415</mn>
          </msubsup>
          <mo>+</mo>
          <mi>π</mi>
        </mrow>
      </math>
      <math id="7">
        <msubsup>
          <mi>x</mi>
          <msubsup>
            <mi>y</mi>
            <mi>b</mi>
            <mi>a</mi>
          </msubsup>
          <msubsup>
            <mi>z</mi>
            <mi>c</mi>
            <mi>d</mi>
          </msubsup>
        </msubsup>
      </math>
      <math id="8">
        <msubsup>
          <mi>y</mi>
          <mn>3</mn>
          <mo>‴</mo>
        </msubsup>
      </math>
      <math id="9" xmlns="http://www.w3.org/1998/Math/MathML">
        <mrow>
          <munder>
            <mo lspace="0em" rspace="0em">lim</mo>
            <mrow>
              <mi>n</mi>
              <mo stretchy="false">→</mo>
              <mo>+</mo>
              <mn>∞</mn>
            </mrow>
          </munder>
          <mfrac>
            <msqrt>
              <mrow>
                <mn>2</mn>
                <mi>π</mi>
                <mi>n</mi>
              </mrow>
            </msqrt>
            <mrow>
              <mi>n</mi>
              <mo>!</mo>
            </mrow>
          </mfrac>
          <msup>
            <mrow>
              <mo>(</mo>
              <mfrac>
                <mi>n</mi>
                <mi>e</mi>
              </mfrac>
              <mo>)</mo>
            </mrow>
            <mi>n</mi>
          </msup>
        </mrow>
        <mo>=</mo>
        <mn>1</mn>
      </math>
      <math id="10" xmlns="http://www.w3.org/1998/Math/MathML">
        <mrow>
          <mrow>
            <mo lspace="0em" rspace="0em">det</mo>
            <mo stretchy="false">(</mo>
            <mi>A</mi>
            <mo stretchy="false">)</mo>
          </mrow>
          <mo>=</mo>
          <munder>
            <mo>∑</mo>
            <mrow>
              <mi>σ</mi>
              <mo>∊</mo>
              <msub>
                <mi>S</mi>
                <mi>n</mi>
              </msub>
            </mrow>
          </munder>
          <mrow>
            <mi>ϵ</mi>
            <mo stretchy="false">(</mo>
            <mi>σ</mi>
            <mo stretchy="false">)</mo>
          </mrow>
          <mrow>
            <munderover>
              <mo>∏</mo>
              <mrow>
                <mi>i</mi>
                <mo>=</mo>
                <mn>1</mn>
              </mrow>
              <mi>n</mi>
            </munderover>
            <msub>
              <mi>a</mi>
              <mrow>
                <mi>i</mi>
                <mo>,</mo>
                <msub>
                  <mi>σ</mi>
                  <mi>i</mi>
                </msub>
              </mrow>
            </msub>
          </mrow>
        </mrow>
      </math>
    </div>

    <script src="chrome://global/content/usercharacteristics/gl-matrix.js"></script>
    <script src="chrome://global/content/usercharacteristics/ssdeep.js"></script>
    <script src="chrome://global/content/usercharacteristics/usercharacteristics.js"></script>
  </body>
</html>
PK
!<'�K�+�+Hchrome/toolkit/content/global/usercharacteristics/usercharacteristics.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

// Defined by gl-matrix.js
/* global mat4 */

// Defined by ssdeep.js
/* global ssdeep */

// =============================================================
// Utility Functions

var debugMsgs = [];
function debug(...args) {
  let msg = "";
  if (!args.length) {
    debugMsgs.push("");
    return;
  }

  let stringify = o => {
    if (typeof o == "string") {
      return o;
    }
    return JSON.stringify(o);
  };

  let stringifiedArgs = args.map(stringify);
  msg += stringifiedArgs.join(" ");
  debugMsgs.push(msg);

  // Also echo it locally
  /* eslint-disable-next-line no-console */
  console.log(msg);
}

async function sha1(message) {
  const msgUint8 = new TextEncoder().encode(message);
  const hashBuffer = await window.crypto.subtle.digest("SHA-1", msgUint8);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  const hashHex = hashArray.map(b => b.toString(16).padStart(2, "0")).join("");
  return hashHex;
}

async function stringifyError(error) {
  if (error instanceof Error) {
    const stack = (error.stack ?? "").replaceAll(
      /@chrome.+?usercharacteristics.js:/g,
      ""
    );
    return `${error.toString()} ${stack}`;
  }
  // A hacky attempt to extract as much as info from error
  const errStr = await (async () => {
    const asStr = await (async () => error.toString())().catch(() => "");
    const asJson = await (async () => JSON.stringify(error))().catch(() => "");
    return asStr.length > asJson.len ? asStr : asJson;
  })();
  return errStr;
}

function sample(array, count) {
  const range = array.length - 1;
  if (range <= count) {
    return array;
  }

  const samples = [];
  const step = Math.floor(range / count);
  for (let i = 0; i < range; i += step) {
    samples.push(array[i]);
  }
  return samples;
}

function mean(array) {
  if (array.length === 0) {
    return 0;
  }
  return array.reduce((a, b) => a + b) / array.length;
}

function standardDeviation(array) {
  const m = mean(array);
  return Math.sqrt(mean(array.map(x => Math.pow(x - m, 2))));
}

// Returns the number of decimal places num has. Useful for
// collecting precision of values reported by the hardware.
function decimalPlaces(num) {
  // Omit - sign if num is negative.
  const str = num >= 0 ? num.toString() : num.toString().substr(1);
  // Handle scientific notation numbers such as 1e-15.
  const dashI = str.indexOf("-");
  if (dashI !== -1) {
    return +str.substr(dashI + 1);
  }

  // Handle numbers separated by . such as 1.0000015
  const dotI = str.indexOf(".");
  if (dotI !== -1) {
    return str.length - dotI - 1;
  }

  // Handle numbers separated by , such as 1,0000015
  const commaI = str.indexOf(",");
  if (commaI !== -1) {
    return str.length - commaI - 1;
  }

  return 0;
}

function timeoutPromise(promise, ms) {
  return new Promise((resolve, reject) => {
    const timeoutId = setTimeout(() => {
      reject(new Error("TIMEOUT"));
    }, ms);

    promise.then(
      value => {
        clearTimeout(timeoutId);
        resolve(value);
      },
      error => {
        clearTimeout(timeoutId);
        reject(error);
      }
    );
  });
}

// ==============================================================
// Regular Canvases

function populateTestCanvases(contextOptions = {}) {
  const data = {};
  const kImageBlob =
    "data:content/type;base64,/9j/7QCGUGhvdG9zaG9wIDMuMAA4QklNBAQAAAAAAGkcAVoAAxslRxwBAAACAAQcAgAAAgAEHALmAEdodHRwczovL2ZsaWNrci5jb20vZS9YNlAxaHFJOCUyQmNIdDFqOEV6c3lnb3d0SSUyRnBRNEJpeThjRnYyOU9ybk1mVSUzRBwCAAACAAQA/+AAEEpGSUYAAQIAAAEAAQAA/+ICsElDQ19QUk9GSUxFAAEBAAACoGxjbXMEMAAAbW50clJHQiBYWVogB+gABQADAA4AIAAoYWNzcEFQUEwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y1sY21zAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANZGVzYwAAASAAAABAY3BydAAAAWAAAAA2d3RwdAAAAZgAAAAUY2hhZAAAAawAAAAsclhZWgAAAdgAAAAUYlhZWgAAAewAAAAUZ1hZWgAAAgAAAAAUclRSQwAAAhQAAAAgZ1RSQwAAAhQAAAAgYlRSQwAAAhQAAAAgY2hybQAAAjQAAAAkZG1uZAAAAlgAAAAkZG1kZAAAAnwAAAAkbWx1YwAAAAAAAAABAAAADGVuVVMAAAAkAAAAHABHAEkATQBQACAAYgB1AGkAbAB0AC0AaQBuACAAcwBSAEcAQm1sdWMAAAAAAAAAAQAAAAxlblVTAAAAGgAAABwAUAB1AGIAbABpAGMAIABEAG8AbQBhAGkAbgAAWFlaIAAAAAAAAPbWAAEAAAAA0y1zZjMyAAAAAAABDEIAAAXe///zJQAAB5MAAP2Q///7of///aIAAAPcAADAblhZWiAAAAAAAABvoAAAOPUAAAOQWFlaIAAAAAAAACSfAAAPhAAAtsRYWVogAAAAAAAAYpcAALeHAAAY2XBhcmEAAAAAAAMAAAACZmYAAPKnAAANWQAAE9AAAApbY2hybQAAAAAAAwAAAACj1wAAVHwAAEzNAACZmgAAJmcAAA9cbWx1YwAAAAAAAAABAAAADGVuVVMAAAAIAAAAHABHAEkATQBQbWx1YwAAAAAAAAABAAAADGVuVVMAAAAIAAAAHABzAFIARwBC/9sAQwADAgIDAgIDAwMDBAMDBAUIBQUEBAUKBwcGCAwKDAwLCgsLDQ4SEA0OEQ4LCxAWEBETFBUVFQwPFxgWFBgSFBUU/9sAQwEDBAQFBAUJBQUJFA0LDRQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQU/8AAEQgA4QGQAwARAAERAQIRAf/EAB0AAAICAgMBAAAAAAAAAAAAAAQFAgMBBgAHCAn/xABDEAACAQIEBAQFAgMGBQQBBQABAgMEEQAFEiEGEzFBByJRYQgUMnGBI5EVQlIJM6GxwdEkNGLh8BZDcoIXJTVEkvH/xAAZAQEBAQEBAQAAAAAAAAAAAAAAAQIDBAX/xAAlEQEBAAICAwACAwEBAQEAAAAAAQIRITEDEkFRYQQicRMUMjP/2gAMAwAAARECEQA/APoVNDEaKeLl6VljZDpNiLgi49Dv1xsL/lc+oMqoocnqKDPKqBYIpTm8rUzOi6RI+tFYNIVDECygta5AwDyTNqeSVommVJ1FzBKQrqL26ffvjXCE3ET0tImXV1bWx5fRQVkbmqdwpV7NpAvtYjUGvsFudrXxnX4VJ+K5M00f+n6amzeIsqy1k1TyqeJT/PqCsZNtwqA3v1HXFn7DVqypaLlxxQyMr6TKs4WNtt9zuD1FrYm00T5zlmY5xV5W1XJRwZdSVHzMlPDIZnqCoIRGOkALchjbc6QMLdNcRirymkqneeakaaRl0WeUmPT6FOhGJEW0mUAKZAx1PuLG4t2xQUaV41O2t/UjEvABeOY6leFZYj9SSKGB/BwGEgiWBuXTwU4G9kTTv+MXYHlof4pEqSqSD0semIA+BskpDnOdcics0NQEkQDo2kGxP2IOIHGd8Y0+T5h8nFQS1zJ/fPHYBP8AfF9jSU9dTZhTI7Rl1PmCN09r4m9gcwmr/TUHz9bYBjLE8cNjZTax27YUDUsYL9ATbDoXoyurlXvpNvscOxKKAlbtbV2IwohNVx5enNnjdUBCiQC4N8QcSokjqJSZ+Yj2KIEtpH+uLsQmrSHa8KNbZXYbg4utjkdfHSQ3Y636kDDqiqfNObENI0ve+xxNfgC089c9WJIZHhcftiBtDRvJJrqJtUjd2NrnGpwLXpXjkJJIBHQ98ZFbvHGRdrH0vgBIQ719QBJ5diFtihkouo7fbAWoGii0qWaId33ONXaBeYoDmJQSTvb1xhS2OWojMjxbkne/+mKKllkmXQsAjubm3c4QMKbLggbUbORfbFmxj5FDJdr7H98Ngs08bLbTYdbDD/RhIyHJFrehGJJsYmV1N9W3phZoAV2YukTJHcP0BGECzL6usJ0zksPXFDtlM0a8lgGPf2xPom9L5LOdeNXcoW1OXtIDbcX6YW7CfNclURxWQh1cEYB0k4MSIydOuCDokRU88YQH0xFERlVG3TEC2lm5tJG0jIHN1v2uDY/4g40B4nZZJTrJRjZbdLetrd8NAmfN5yhi/RsLajJGGbT2F8TQ6e8Vsnp+JuN+EIeK6ta/hPml6XJND8qqziNhLTtPYW5ccUU7KGOln2IY6bWWzhZdO1abMKmvcEkhbG1ug/HbGUMIINUnmBK26kYdgmZxT07cuxbooYEi/a9u2FHFLAjmMqEAX0/Tf2vhAdy9IIvdr72x0Zqgxx0sB0AqguzNLIW37m56DGK0oGuR1YCE02m5kL2YntpHcYyK5kBXSQSD0GLsVxzRZdGWlYKo7k2xRpvB1dxDwxnPF2Z1dOlVktdmhqKWTUOeE0IpuvQJceWxJtuetsLpe2+ZNHl/EEs1VPrp2LXZCtr4yFWfVdJSVfKpiFU7C564I1Tj/jh/Dzw/4i4jgjiqqrLaKSojp5pBGkjKt1Uk9LnG5qLJu6eefhO+PHOvHfOa3h7iLhWly/MYlMsdVl8rNG6/0aX31D2J29MS3G9N5YWTb1zRy89VK3OroANxiua+pKRFYVYlk7ehxnsULPIrEg6QOoxYg/5ylpqcyvKCyi+gjV/hgpYKtpJ3coIYG3Vybs3/ANe2IOIIpnKhmUk6ipONb0B6sDVpEZtewVdycZowISHIt5vfCBjkdKJ6sIoKKPVr3wg2qfKIGjBkhWUruobsfXFoR1Lspa97jb2xANCiBrqiq5NyTvfEFNRPprnGkL5BdsBAZnyjY20D0xYG1JxNl4QxySL0uNv88KFFRXQuskkALN0UL3OIoxqV/lgSCotvb1wQJZdJAsgA6k4ojRLNcSSEt7k3JGLrQtqK3oqgk3wgklRIHJe2/bAUNmTJWFCLgC98NC5qoGMlGLsTcg9hhqjGgSqAoG/+GHQnT0axgqBc+/bEFghCuFBIPoMNggJbuTjfqm3Dsem2M1VM9MJ1swuO2JqwUSUSG3bF2OSIIotj7bnCCyFSF3OwHbChRw/lbZRw/leX87mfLQLDrl3MhFzqv136+uKCtDrOV21WDEeo9v2wA1RSRPEyFZDEx3ErBzf8iw/bC9IBzXh5pq7Jq4FVakqi5Tcl43jaNgANifMDc7AXP3itgpKFI2DbWH07Ww77BPOQT8q5DlSwGk2IHv0/GH+CiWpJdlYlVJGnQCD+T3wotJDR3AJZvTcYgzCkgXdha/vi7B1LLGjHmabdPN0xqpCWlzGWeB2rXgV+cyRCJGACgm3Xr9xjEVfJMxQtyzJpGwG18NAOoi500ErwKHW+zqGAuPXDoLM6ymStW8rF/IU5bElSp6i2IMSZDTU5pquGnnGYUyCOJjUFYtB6gjv+Rgu0vlGeRnNiR6bgfvghJxBw9DnNFV0tVGs1PUKyyRSeYMCLWsdsaWccvmB4k8PZt8LfjnlHFFMr1PB81XzYlhBULGSytC7dn03I9bY4dV9PD1z8evr6MeDfizQ+IGSl6KpV1iKAMp8wVkDLcdjY2t6g49Hx83KetdlLDIYmUSyHVez33HuL4jIujjeVynUKouSOpwAmY5YpmVyxRh3BtibFuV0SVMbOAbg2IJucUWNRPDKS8XNTuCxAOMimOXQJIoXMSRi3LIJN+1m9MXQGWCY1NRJJVvPG5GmIooEQA3AI3NzvvgLHrZ8uRpofrX6SfXDYAbjDO1dpaqYU9LHd5GJ7W6Ww2NjyjPMvz/LTVU1XG8aKGlLeUp9wemLoV1VQvLUwuJUbcOhuP3xAommlFYNDkB4yrat+hFsQYt5gtyxOLAFUZDT1Mzs6FyTfY2xCcGCqPlPl21xQvYNoPmA++CmDVogFoquSSIAKEmHQet8UkV6ixsg1K3Y9MRBFIJW1a3ACmwt0xoXFRzFJsL98BNolFze+JsKwg+dkAslx1ONJ8TeTl+VNrbX9cP2CqdgYx5Tf2wq9mEa6V++EnGxwoCb739cZk2JKtjuTjpJpHA9mIB39MZyuxIt5APTGseYdI2vhqTs2BzRhBEszECJTdr4z0ohXUxK/8lr3xkU0sLcmEtoL2BuVvp26j/f3xoKeG81lzPiHimGo0JJldbFRxC1mEL0sEwY72Id3kIPWy2PTEDatCpUxhIJmkdSTUKgKLboGN+9zbb1w4QnqGrpMyhrJAqxwI8ahYvMA5QGx7XsL+wwVsVPTIiqpOop0J/0xYlE83kUM7xxGWo0tykcEK722F/T3GFoHjqq5KJJcyoqejqHGnTC5e47WB3xnelAPPJFHGlOYqNAwvriLgr3AAIsffDYkEDa5isjtINJOortv09OvXrh2K6sRyIbLIouBYsz2xQunmhy+spjmda6pKCtNcM67bsCADptsbn39MTsNmjX5vUlNIVKXesVRoPohN7k9+lsBdE0MLs8kmkDrfsMXWwPmVXl9ZEnyNWlQW7L2/GJ0ENX/ABOOpVFW0ABuT1v6jDYIo4ZVH6rEnrcnEFNfIBVwwr5xMjlm6aQtrW29TjQ6A+ILw8l4p4SzPJsyopc14dr4OTLJSJzJ8vIN46jljeQIwBYruBc22JxjPH2nDv4c/XKV5d+BTxXlpPFHN+FczqFFRWxEwBD+lI9P5Tp99IbfvjGEuPD0eaY5X2j6R5TXmeNQXW1h5SDe+Ou3gN6OpRanl6kuEvIdXm67HT6dd8UWV+WNmcZWNydW2wtiC/I8nl4auat1ljbpy+o++LBjNMx+brqb5GSB4Ff9VJBYlbdj0vgKK1qIabPHrIuPNv8AjC8dBXNXilkQ8mFUldYxoFmZzfr2tbEBCymJCRH3+rr+LYoW1aTFGJWN1NtSuL7X3xNA2GlyqkotNLk1S8cwCzSTOLvb7YokhkeNY0gEEaiwRTtjIqqqdl06lY27KcBGCMNYpe/QnFgLhy9y8lt0AHmJ74aBtXRUGXZWaqrnWJVG5PT2w6CN4WkqGILSxsgUQlRp++FWGsAkgjOsWAHS3TBF9MonQMoZV/ot198UVVEMrXVdrbXO22AHlDxAF21H0BxpFVNGtTWgOxUBL39d+mMqNqII4lZlQyEAnSMZFGQy1VXRxzTUzUkh6wuQSv5GNd0Nmv8A/wCYl2IcxlLXswHfCC0OrDY7+mN27RXPIUKhbISepxjSpAuHG4I9BizcF7kMoASzdzjbIHMKIV0BjaxUkEg9NsZ1y05SNzIiNOnl+UriWCunUxIq6l0gWVel7bYDWuCqSpOc8aZrPURLNmeYxxDL0F2pDTQ8hS0gJB5iLHIAF2BG7X2btG1zR9DqQFfMdfS3e/8AvibA8oE7RCml1mbcBGDBx1uvW9utxtihlG6sAqowl9L+bGkrEtM0q6SSpG+xsRhxQK1A3N1vI0x/qZyT9sY18VXVTpTiaKnWmerA1sJ5eXFGNhqdt7D/ABNtsa1oFohdBbRI1h/dny39r72w0IyRQpOkEk8cdU6mRYTIAxA629cQJJKmfLs6Sq58MVIkLQl5hqCyvIgXrsuwIuepYD7xfg+WkqQXlkuzNv0tc4coDlhH8UgEscpaSJ43UuwVQLMLC9r7kE/YYow9CiFwI2hRhb9I6WUex7HGRaulQsMLTyRoBaSd9TN9ziiBdHJWPzafqJ7f6YtCDimslpZcnFJRy5hmD1q8uCKy/pWtOxc+VAsTM2+7aQo3IxRXmE9JFI/zDRoUBuCLgbXNz0xKPl18XnhlmPw/+PFLxnw5MseXZxUPmuWzQkWgqRYywmx6HVqHYhj6YxHrl9o9/eBvihB4pcL0WbUkQELZdRVckoDJ+rMjl4wGG+ho2BKkj7EEYsefKaduwSyOhMJVpANh5QT7aj0xpg3y+olpHXU/KNrlSdWn2uMAyzWvoq+CGKeUF5XEccZ2uw32t7fjbAIcvoaWs5hppxUIGKswvYH2PQ4gHnyOOKouI9TDcP1t+cOwTT0/LVdUccxI21gHf84sBMMb8oXGhzuRe9vY4UD1VIWp2KsFCfUChOr2xIBmzPNYamODlD5cA3ZWsR6AjDYL+Xkkhi0zsuqZRKzklgl97WGFgbS0gZNl0xj1wgFieKN1+nQP8MIM5XUUpgzIPUx6o6pwLN2IBt+L4oSVFDmVZWqUmD0ytcIf9exwoYmtioIiXjnmkDiNjHETY/7YyCYIZnRxLIJVJJFlsbemLsWUiLLEQlwFJUG/XGpN8otMTsLFrDE5VRLSbFrBmA2A74oEkoeY2jzIQQ2odjibBKa0kVGIUtspv1xAakPk67/fGpEQkkAbQD5yLgHEUFLM6TItwQLlrdMBfBVRgathf0w6FVTIJGXS4a/QAXxRZTXhUg6r++HQIgmKoFMnMbuxFicNppyqmihgeV9QVAWIUXJxbqnJDkFRNm8j5rqMdHKoEEB2aw6sw9TjFimNAnOpopWBQ6NgwsQMUDx0kEPFJqUiCTVVHy3lUka1iYaQ46MRzCFIFwNQNxptAfUFJCLsEvcBWAOr7+33xQqrKlRU00chSKDmBXePy6AQQtgo2BNgbW2wDmnEjlHhkTkICGBhuX9LG/l3+98UEa6lltTNTq+29Xq5dve25+22LWQsNK9HSpA9ZJW1ABMlS4G5JJ8tgPKL2AO9gLknfGGmdUZkanEvNKAMUZR0PcjoL4ouDAqLf5Y13E+hqmo5bcxqaCYqthLIPMo9sYquv/EdTxfkOZcEUzPDV8RUk1BJVhmiGXwOhElRrFruq7xqpuX0XIFziStRs1DleU5bUSVKRZjLXSxxwvXVOZSvJMkYspcE6S3ckAEknF2ytImj+TvWNUxqNMkpiId2IsAAD5bncnfYW2vcXYYRwGZBfv2OMgtcoqI4BJHH+nbvjWgmnuJyXQCS2nbbb7dO/XDWgqrE5mcUDy6HhBcQqFsUm5becm+40awAB1Nze4tRcqztN5JHBXyjf/IYDoz41/BR/F3wQzeWkoxVcQZRGcwoiI9Ur6PM6KfUqG277jvjN726+O86ec/7NvxvgSWfw0zSU655HrMmkcDSF06pIbncWbUwBv8AW1rY1xY35Zp775RWUMgUEn6r7WxHnH5ZKagxiZDG5O6MwO9/UGx/GKG7ZfNCgeGZQx6iM3X7b4mwjOfww1C5fPVJlGbctpkoZ2TU0QdV5i22KlnQX7FgD1wk3yrlNTTCWmdaiSnhhJvTRqpM+4te/QdfQ4kQ6MZkI1NZiL2UdMNiDxGRNJd1VtmZGGofbbriiRoVlGhuZLALACV97D1tbEB8WWrJCzLqLDoO2KEdT8xSy6ri46W6YgGbP5akaf1ZHY6SVZeWtvTvc98XsTA5vXa/W+CKIOF6cyGdS4Z21sQxIY9L4ithpORRRqjCyD+k4sqB6iOKul1QyvZf6TYYigq2trIkEcSKoH8zdTiwH5XI0UEXPFrDzG1r++KC8yzzJmQR09Wj1PTlI3mH3GFoCFTzNxsSehFrYlF8IAJ81zgLDIrrJGjAS6drr0v0xbZUVU8q8ldLiQjYkHqR1w3pUZisrXKkH2xLdgN0CO5IO4tt1tijK0umzITY/wCOAuoZZS8Qjg1u7FQZfKB98SUMqkywECUKrEfSvTDYojXmXLAJa+7bDFnPNSqBUJUxCSBlmQm2pTcYXStc4RmlSiqo55jLNHWzL9OnQuolQB7AjfCwbLEzNArlGBZQbHY4gDE8r1DLJEI3QkRuDfUlgfxvsft74aE3qEY20cxul7Wt7YgClpPnK2gDmNgsut4i3ZdwwGxJU229CSdhiwNJ6OKZoVlzGry+KJy4NLezm1rMB1HXrtihik60yFk1PGP52HXFT6SmqMss7NUrLEz6lBjCcoW6XHX1ucYUXTNHzrSSM4XzGMt0Hrihbw7xL/G8rzKoIhPyFfU5fopo2MheJuhjO4JDLYX3BB8uqwW8GmwNQGaAcwk3UEkrpF/z0+2FvA1+lp0rap63Lqpmi5L0UisNOgiRXFlZdVzvve1iOt8ZBCUfLbbzkdScXsDPSTPmtFMJRHTw8xrKLMzlSoB/6bMx9bgdr4sDCGflapGGrQpLNvpAAuT0viUQq6WhSqbOJq+pFW6rohiZgjADbYGxGHIE57TqzyqVc9FuCCPwcXmhdLSmtzHzGOWnp41cKV3SYlhqvfpo6beu56YglK/yiSOtJNVsg1mOGzM242XUQP8AHFDd6mhMLESrMOYISg893/oI9bHp7+mCx8Z/Fylr/hw+JniSo4RqVyWsyTNXrMtOhJRCJF1aAjAhktIVse1x2xwudwzlfTwwx8mF2+hPwi+PDePvgw9dKsOV8TUFTJSZlMacSwrIzmRGii16iDG1gGIAKkXNsejKfY+blNXTtPOOKq3hOWnjlFTxJl1ZUsS9HlyQS5dESLFjrtMASdgoe29zYnE2jZhUVqsaeN1nS/lkUgr7WscNoJheopzI7TsZ5kSJyFAFlJNh3tdjtfsPTE5BUL00M0ZqpEDPfReTzv3OkE7kYotp2ojVGq+UjWq0GL5iY3lCE303B2B62xehOqkTT5FGvsLgXxAIKiqSRrESL/Tp2t6DAQlgzrMpzPS1dPT5bHbXGj6ZQe92v09rfnAZSqae5eUmMkWuoUAf/K/fEBv8GTlmyWXqQBthqgGWJSSqkoegNthig2kWDTGEQSGG6qWckqbet+uAzVRrLGQ2w6jT0xNCCmrp6No8rgjqa19k5guo9SbW6DCgOuq66ipymaRxUtWoYmWJboB1BIubfk4fQXBLzaRFkBmZkGpltY7Yugs/hFNl9XHOIhGsptrt5tROwP36ffEDkoXA8p1D17YoXzDRUDUVU3A+q1ziopqa3X/wRqXjnnR+VYWawG5U+ovfBU8ujTLKWOEeVUQKotvtiXQKWreW5AGnTa6/UD7dsQSlISCSaSyqqksWPbFF9HzJaOO5COVHmK3/AMMUUVXzsMnMpiFZQbM3S+MgLLjmLOZcwnM0o/bF0Dp6yUEMgDMNgCLjAKs9zWvfL6nkCOKfRpTQgUAna9h3F74C7K8sSlpo4IJCUUaS3Vie5J9Tih3FHJFAizsvMO1kbUAfS/f0vhrgVyQ2RgEDA/1E7fY9jjNlA8UEvMPNCWuSNLX8t9r3tvgFPHi0mX8DZ5mc4qCcrpJcxiNGrNKkkKmQMApBYeWxUHzAle+HzYd5VmNTm+V0ss2VikqDEhlhE6ctJNILIGNibNcbjtvi/AFWZnncOVyQo1BQVzqxSa5njhN/KGUhdZtsSDbvhehOKrSb+8iXWPqMF9APsxAv+2EEczNU+UVsNBzWzN4XFOYbalcg6SC22x3/ABh0GHD6qaOhhVJKflQqphk6qbea5ub3Nze5ve98TQjnmcUOX1IoI80ip8yZdSUxbzNe9rbEC9vvhsJaPKsxbMmqqqWZhcBnTSWdR0RgRuBfbvhoX55mrZbk89QqPPJTLz3EaaSyoQzjc9dAY9d7WHW2IuhQKTmGSGTVTFeZGdxrDC6np6G+/rgiFVTx1GgOocqyuFLEWYG4Nx6HfFgDeB0lZmLSuT9Ja/53xQRFT6w7mMxshIGvqR6i3Y4QCSUkbyTTRs8FZLCKf5lDcogYkeU+U2LMRcbEn3GJ0MtRkowu2+5ZyCT7m3/bGgnruEKbMuc80Mckkmg88NaXUn0PrHm1L2N7226bYz0PHX9oX8M1VxoMi4/4fWlgzSOVcrzl6upSnpuQQeTUySNZU0sNDE2HnTuN8V6fHndaeWfgu8Z63we8VqGolZjwznciUVZDIg0PKvnj3J8hGo3b0Y9cdMcuPVM8eNvrhTVuZR0kU1P8mssz3MKyCeJ499wdip/7+2Mz9uCynp6eE3qKaGFnBXTHrWJR7KD27Wt98U2upDWwxUYnp46qCIhBXUchWBVtpBdHJYMSehLAEg6icVB41CVNEXMvcczlhuWLXvfqo2t97YonGod/MCV7EDBF6PDAokmYKgNgwAJUk2v9vX0wUQ8JdPK1wR06398AEkMGkPLNHTDTdjULosPe/T84DMbw1OXxSxqxjmQOElXZkO6kgjuLHcdxiWACSNnnlabVWRyKqGKeVygC9LKDYdevU4fA8o445o4xI6c1tlXZSbDoL+gxYiUs8FEXeSMmMeZtI3P/AHxOlLct4ooc/pqd6GCopyw81PUIFeMg2Ibt77dsTQsnmhqomaGcSoGKMYz0I6jbACxU6CUpGwMjLrsTuw6b4sB5RYKT9CCSa+wRDsD7nsPfAA5nBDW081HUWqqSZTHJGxNiD1F+v5G4w7oMpcxpstBppIqhI1UsjCNmRB6F+5/xxdp2JaKOqY64wSN726YKT55QfMxoALGKRJomBI0yKbqf+3fEGUnmq1Cz30+qfViCySqRH1RxhX0hQ5uTa/cDAYzSuLTUVPdBHUSFWTQWZyBqAXsPpN74ug5hmqZSkVDTw1NQW/UjnkI2/wCm2KiVRURa3hkjanmX6kfr98S1XBEgW+2LqaRRLCC/UAD3xFVVVPFIoWwL3DYg5ABRwvFE5gkkO8pTWfvbGoG0ETl0UFgWBtZbgfc9saRZWJJRUutaKavkDAGKC2ux7gEgG2M2mgE+korRsWLD+ZSv+B3GM7VqnESRTyJHmMvLySTy1Sq2kS7ECNmuNKsWFz30hf5sKHE3FT5xGYzEKfkuVUItrj1A/wDOmH6BUcFNKsa8xeYwuAxGprdbDqcX9I5PGtIhLSFUPqbWxIquWnLqpV2RgbhwbEYbCfLHzCiy6ZZJ2mq1nmEkhIIF5GKqDYCwQp0wFUudGOSST+HzVdY4CCpq3X9O3cBdj+w6YithyaOvq6UMTG5Zhq1nTpX1FhucNoFqaVYanQWUuCGtICVIJ3GKFeT1mXU/E03DlNLUSTZdlcFXM8+prpLNLHEBIxuxHIkv6ALc3NsQbAXp55OUHUSEBgoPmte17ffbFgy0Bhb6Dc9Ay2tgKiDpuTY/0ja2AqR1VrNY23IO2KLFAbVpgVAdywsCx6b+vQYor0hW0Wux73w0F/F2XZbXcKZnSZ5T0dRw7PC8Wax1tzF8mykTFhY3AXr6de2M1rG+t2+J/wAQ/gjmPgF4v5/wfUhkhglFRldYG5nNpHJMEgbbUwGx/wCpCMJOdvRctzb6qfCH4n03i34QxZjRyxVk2X18+WVMgVoQGSzKwQjUutXVwCNg2+4x0zn2PNZq6dpBKgRKlUkQqDtMIXLJq9iQDbvvjCIy0gqI2ikVhGGV1eOQqVYEMCCLFSCB64lBqONIAGrfop6YoIDuo/Tcjf6Tvb/viiyO7yrzGV79Aw64aRjh3NZJaWSeVg5mndo1QDQkQOlVBHW+ksT6tYbAYijOJauGfLYYKmhjrZ55o4afUQrJqYXYFtrKLkjqQLd8BRV1cPKnqJ6l+Ut3eVyBYDe5NsNrJvh1jwZ8TPhj4i8ZTcL8PcRRVubRKXUrC6Qz2vqEUjACS1r+W+2+4vh21cLO2+V1NLU5lQmLmEamZWjawBCkkt6i2w73wYPoKQtRnmMht5QL/tf1xewFFBypLLTwwqNjIHuJDb+VRva/c2xn/BKR3EgDRhkC31h1UA36aet+98QUKvPzJyyR+SFVi0kgqHJ139b6Ft6WONhoUQRpHcKCLAE2v9sQBCMJCsrm8Rk0oVNwd7enX2wF0eb/AMPcgswQn6Qe+AJoKWet5tQI2WMkkXNj7bYoGzGVImWORH89wxGxXbYkH9sYCwx85HEMrLe67bEe+N/tBlLShUPMsGPvvbDpQOf1MCVWSUUbD5uorV5IV7WCIzyPbuoQG4/6lxQZLLzNWiNgoHmkDgb+luuIBkvKxBdmKi4ubnE19Fa5hURVEkJS+lQyliN73/a1sAxiqCY0LBg5/m7DAXan20xLdjcn198QWMjlh+oym97KoN/bCAl6gkaWY39jjVqIU/EVJlBMdVUSK5Bs7nYfnE+KDqq+araV5mEsKmyOCSQvvcdcQdVeO8YruCIcr/hTZoc2zfLctjhFSadFaSsi/UeRRdQgUvcd1A74zlyOxZl+arZZncoGcsNXpfG/2A6PN8szXNRT5hSRt8gS8bVNISXa1l0OOhFzjIYQywxsEipooYQdXLRAVb7g/wDm2LsV19aFG0i6nOlNewuegGG9gimiLKkaOI4QCWiZN2Ym9we2ArmpkRyZNOk/vh2EVf4hVmUzNluSmGoq2teOVTcL3Aa1r298S3SwwyqrlqlEtY55gFyt7i+CKckgqM048zx5lnpaGmgpUig+YWRKl7SMZABvEo16WT+dkVu291tW6VEDfLsjxK0b21BlDbYukJIX0zWgaQREM/KlJbTcjoW8w3vsTbfa22J2IZlPBS0slTK6JFCut2kHlUDe5tfp640NHyHxT4a4ny2rzPJc6yzM1EInmioa2Kpkp1At+oqMdNrdD3O+N6Rr4+Jngalp62SDiPLsxlpE1SwQVCq4vaxZT5gN7XtscXUWyxfwr8QHDXFpUQZpRwTvu0bVSl1JvsB6C3udsX1Zb5UZpR1GR1kfzMU9PLSyxsIzrVlaMgi4PofXvjNix5x+MPwEy7xm8BqHOMpoGHE/CmWfPZZ8iFLTQCJWkpSDYMhVdQtuGW4B1EFY64ZfK8G/Db8RWaeBviFkVXSZzXUXB9RWRtnVBE2qKSNrI0xS3mZFsRbey2xMMva+tdvNjjJ/V9ixnFBxBl1PW5dUQ5hQVcQkp6qlkvFPGw8rxsOoI3BBxLNPKGerp4wkJEokFgLqSpFv6vXbviDACSyaC8RnH/sRygudr7gHbY97DpvgDKGlljgTynW7BmSZxdQeoFrjboB098BfV5lBl0MhSGqnquUWjjWlZgHOyK97Wu1hv64gopcvXL8upaN2MlRBEqNKwsHe3mO2wF79BgFuYVqUNSlVMjTQ0VRBTszMQsQqJERmuTpuFYbC/wBQ6XxqTa9vKXxg/Ezm/CfDGb8LZFnZqM+zjMGy2BMrprT0cEbKszR2sSWe8ABuWkWW2yNbh5M9TU7fQ/i+H3y9suJCX4NPhdruBc3/APWvEkQoM+qacwwZYmlxSRNYlSx6PYAG3uD1tieP2+uX8jLG5X16e3YbmnJBTmhToWXodvX/AGx3eNONGacS8u8i7CwttgDoWhyrLTU5nMQsQUPIIbsWJsPKgPf0GJ0K4aqkr6Vq2Fy9LGW1ao2V109fKRf/AAwooc/LztWICwkhVXCjcKpLKbH/AOR298ILVq2c2blmUC/lsSB627YAXMs0NKaVFhFRWTSrHHCXC3uDdmP8iqoZibb6bC5IwE3kkU82NTLKBuFHX7A4oXQ8cV+Q06UjwVUyBdpp2Es2om+lmHUC+xxLdAqLM6nOF+YnAMbb36k4di0Rr/Kukvc6F2v7m3TACVccoRzurdN74chRT5pPS8SikcKlHJQFkkkB1NUCQArGegtGWLdSbr2BxqdFO0YSJfdSfXDek0K5SLtfynewxFV06ctyqxvKruWJlfUwv6E9h2HbDoHBdZAT6gNxbEESzU0bOQTvYWPTAFiTSqn+b2xQmpJDGjEuZCRexO+JoGRR01QqyzKJD2LC+IAaFeKFzBpH+XbLma3ywhtpX1D9b4gWcTRvn2UVdAEpGdpFePWC4R0dXRvZgVuPQgYXkHioWR35WoBDptKbt+ca38DIZzGaMwmmUOdi4UXOEAsZLqQIhZtgR1HviC0UrvDy42MkgUqpZQHJt69j9sA3y/K1kgikuCzKALb3OEAldl2qQrINPt0wCpc5y+DMZcnSkcShfNVcjVGrdbavUjF3BiyxagYijA+VwQQ49Rbp+cJQ3yFUirHqKpijFRGtwAdIJN/yTgHk2Y5U0kqQSKJ2A1kE9sAiEaVZmmvuNvMcB5l+MrIONeNPBuqh4KknpM7p5kqTSU84SWojFw0aOG2azE6b2YXU4VqafNrJ/DLiHJOC6bjnJ8xqPnqGR4c2paSGanq8mlLNGI52srKWCtcGwAK3PmGOV80xusuNu8wuU3FPDmRSSyPGoUVGhpZpZI2Zxe5vp6k7hNKkbEE3sTj1emOuK89zu+TClyWvj0VDPFRxRtyvOTcsADtckbi1wrdRYb2GNzx75Jny2HKPFDizgNKiny/Oa+ip5Ry5II6hpYZVYWKNGwv9J2uCe1++MTc7a43w9W+E/wAbCyVMWW8c0pjqYv0I8zyy4jVdIFpae5uLLfUuw309xiWs/eHlv4rvASDwwziPinhP5PNPDzOKh3pazL5zMKWdyX+Xk38oANoyOqix8w34Z77j3eO45TWTevhb+Nep8HOHcr4V4hoJK/hOlqgKWeJrz0cbuGmjN7B08zuq7FTYXIOOnj8uOf8AW9uXl/j5Yf2nT3V4feNvAvi9PWpktTTZtW0TgfLyiRJCn1CQIbXF1vcXtYH0x0uP15XYVE8659TmPIY6aimp2Vq+KRQxAKnde67gdb3tjmNr+Q5Z0rEF2uLnEAtbLKIArOg+WfmKzgNy3tYWINwbE7HtigOngnqQFjDOVG+i+r8Ys19Hk744PH+Hwy4Ep+GqKWKfiDiBp5XgqWVvlKO7ETNpFiNYiA/qKMDexOJeI9Ph8ftduo/g98C+IeNZ6PxJ4zE7xJzBlUU2nmSo9rSlrkou7gKAHIdrlQfN55hq2vR5fN/X0j3HCJaGANBGnMHVZN7/AOG5v622x1jwG1FPNUiGWZFEsa2LWBCtYXYDsT7fbGmTMTJ8sUeR0RxYuraGI76WHQ4A2ihkkSJKVmNPFZFSdxK7W6Bj1P8AngGUyTTkySiOmIF9MAKDDVTZVWRtNGOW63bo53wVz5ePT5iARte1jgBJp3a6KyhT2Pf8YoIjRhpV1JNrXI6+/t9sBZGi3Fk2vftvidiiZkdzzOZLeQPEkqACLa3ltue/X1wgws8ENMZy6cntZuu9uv3xKB2nqppJA4Swb9NY1N1Fh9Vybm9/bpigTNKU/I1DrHG9ZHG7QCXyqJNBCm/bc9fS+APpIoUSGMF2IUAPIwLtYDc+pxaDEpbk8uHmkm10JNvv6YdjEVTGlXJSMbzoquwuDpDFrX9PpP7YzYLZJg3lEYufRj09bjFAclRHFmyLIh/UgOl7kjysLi17X8wIPXY74aEqDM5Xp42rIhSTG+qFTzVO9hZ7L2senfATkgVIxYWv6YbC9YGhjYSycqPV5ST1v0GAulnzSOJ6Rcxkjp3FtF9gPv1xAvp6AQSqib4aB0dFJZtK2JNyb3xaCY6Hzli4aMbEKLNf3wEqyGGNf03UsOwa5GJoBapBusjA9rHGtJsl/iFVFPz6ahRHimazQtps4upY79SO+MVTCmzCR2kmrppDJIAGuxstulh0GAKjq5JIHgikHKfckL57ffFgIp6FkCra4te5xoAVmqbiCmyv5loGnpJpzyyBIwUovlv0sXG9vTGdgcRVGWxvAkUkzMqqs7m7s1rEvYdem+IE0XC/ELvIq5rUwrPfmcu3foBfpYY1NI5xJkLUGViGqnYnYXX6ie1vTfviq+a/EfFmefDB8RueyVwrpeG86VZKhXdy8sBvuhB0ymJi9huGDBSRYW8f8nw/9ent8Hl9e28cfeF+XvkK8U8LmOXKallaopzKssSF7NqQ6wbAspa9rEnYkEYx/F/k+uf/AC8jv/I/jy4e+LSKWnXMIFjLq8F+m4KqAemrqpOk2sOvUY+/J9fC3bdAs5h+bVeYk8c7SGOSUOkBYKnZBfUdyb3YFRbr1mWLpK0LPMnOUUCtQRkAIZ+dTtqjeMgbKvYahckHrY6R38ueOrt2lgnJvF6Th/KMx4Xz6gbOOFq4fL5hl8M/LLsGus0TFTy5kOkqxHUWNwTjhqumOWrt13x7wvxJkORUObvzsy4IqZ5Y8tzWnZWpy4azRyBf7mbYXVwpa111LY4xPHJfb69d8ntNKPD/AMSM34V4gy/MMpq3oszppVkp6hGIZGHofT19QN9sejG6eTOfh9bfA/xnoPFnw/yTPZoNowyVo54jWjrF0o66NWqzlrq1iLML4X8uetO0Rm2YZQvydHaVkuNMxuTv7be34xEER5hmrsDLoUEhpOXEqSD7kddvzhoefPit+K//APFki8J8KLRZjxBrjqM2ilqigpoSPLC9jqu50syruE2213Ey4m3bx4e1eOPCLwk4g+KHxgrK/PK75mkicz5nWmUSajfVHSoobUq2JA0iyKOxOOGP95uvX5Mp4sfWPpxwhkFTlOXU1C1N+lAixRJEURVQCyqASLACwt/njTw72e0kIqKNJZIOS73BQsHK2JHUG3UHFZqSQhSNNiL7qDa+NoLicoLC3tp3wFomSoEZlp6SqMbalMtOoZW7Ne25t3OAR8ZT0+W8OZjNAs1G0toWkWrYhGmcQh0VzpUqZdQW25UD0tNkMAXgEUXMdxCBGSTctpAUEn12vt1wgPhiMukBVEQWxVQb37e1vXFotSjYMCSVX9/8sRGTAbqutUYfSSPKPxgqVNVpLpWVtFYEDNAbcxASQD1+kkGx729bjFFdajSAEoWvsLdR98QCtVlIXZBpmA8pLmxt1H56ffF/0RMzmHQgqaVpAGJIUSx3/wD7AHt3wE5A7VCCSMFJbhHDA2NxsVAv3+rp9sAQj08hChtexNwCF29+mIB5nHLMZmkK2DSfIzNE1wb7MRe18UVxUbjM62ulDmWrSONmUjXoQNpXUB6u5v6th+gckgIJCutjsshH/hxABUwTVpiCVUlOFk1uSNRK73Uex2+wGAkZZbXcAbdbjcfv/higkvMEVtm9RbfBC1Z6qqz7zxRtTQoGiLi93N73HsLfviKJrJyZLPpDd+37YgoiqDHUKQAT74sDOmr1B3TSvqcUYj4czASPmBrw1LJcmEWP2tjMATUkMcjOnVjdr7G+EFsg0x2Fha7avQDGrQq4Wjkehqqypuk9ZVSzhHbVpjvpQAdBdVBsO5PfGJyDI8qnq5HdmBg7A7be+LoTjRYrLCrrIQfOFuq/c4v+BvTxmVbiQsO5O2INezx6nLOLcpr4YYHWSnmopHeP9RblXSzf03Rrj10+mJ0HVJUmrkGqPzDew3vjXYozzj3LcggbXqM6/wDtqpJ32GLtHVnH3iBWmoo4qSi+bqquTQis1lQdSzHrb7b4tmh5K/tBPC6WXg/IONOUkdXBN8tUoi7cl1ADA23AkAuT0DgbbYxbpvF5w8D/ABurPDudKPMitfkVnUxzmVzBqXTdFVwGXZbqeo6EEb+Tzfxsc/7Y9vf4v5Pr/XLp2VxBVUDgZxkjR1mTu6ySiGR3SkB+o62HMChibBuxF8er+N/Jy/8Az8kcPP8Ax5/9+O9lFFVSUUD1BeSPWXOimuVIBJ3ULa5uCCNxvfY4+ha8EmuEKNY8wy2okkhNSrh5pkl/VOru2mw8oUFu2ncWAO2bJY01Hi3gJa6aTMIbTOJBHLKYhd3ckarm36ewurHrqGoWF/PnONuuNalwjxrUeHOc5lQS0pzPhjM/0s1ySfeOpisASL9HHY/bc7HHPcsdJxy0Divhpco4qlGWVMU+WVAE9G+sKeU63UNbuBsbdwftjMt6rr67ntK9sfAtxFmUbcQcN0UNNUGrhhqGq6tFEUT2dCjkkeUhC3Y+UbY1JZxXPPXx72yTLc2NNFFmbZXNG3kmqKaVi8o3vZLmxO3fHXTg6Q+Kf4naLwopIeGOFpIhxtVxf87O3NOWUwYHmS6h5mZlYLGRva/Trda5dcMd3l896HKeJPGPilcn4cgqcwzKvqXmq66pa/MZj555WAvu2r16gAC2OWWXtxXrtxwm31J+F/4fMl+H/gh8opnWqzWqYS5lm4jAlqZB2F72jXcKlza5PUk4a1w8OWXtd13FUvToLkohtYEi5/3xmpCqntBUVIpzKtO7F5YGQv8ArE3LhjaykW2FxfCFXPUurEG5PZFAvjp6ovQGUAGURMbESAXt7kDqPbE0Ks44ny/J6qCns8sshFzHEzBR3JIBsPviBZSZxR8UfxFKd6moEYVTLJSvFDY6tJhLgByCty4N1bTa22IGNJTzJDbmjlxganfdrAd/c98UN5jMtK0EVV/D+YCGqeRzLbdCLXH3G/uMQYgqLgR09MlNSoPKsQsAPsTf1OApmzOAUUco0TCewhU6k1k72+kkWG5uNu9sAozqIR55kNfTs9M9NWCmkanp2mFRTSqytFIVBKoJDHKGOytGL2DMcVd/DqpKTOdf02tp6ea/Xr7enfriIqNLGwJjZHa1jv8ATiiccKwxhtLyk90Gr829MAozX+IxZ7k9TTWal/WoqlDuAjoXjkA7MskarfuJCPSwNGXmC0kl7HfUSCbDt2JxKKJ41lSNmjISNxJzENhGw3ux22tt3G/5xBYkrKzxMwUav5dmt7kd+u+NDNUsJ1VDhRylIaY7aF6kb9umEC/mVtTXBYFlhoIwDLUzxaGnJBtHEps2kbFnYDpZb3JUCJHSKakeWcRBJDGsZl5aTXB8un+dthb84geLCAu53wC+ppnlq1aI/QDcYQCzxiRvMrcy+4OJexVoVZACLMOmLBfrAjZd2HXGkco6mZTIGZEprbEP57/bGFViaHnM0Epldvqub2+2ICJad5KGUhJGk0MFWNbm9tsUAcN5euX5Fl1HpJ5EKxsSTcEDvfvgG884ijESEEt6dsX/AEqdHREjdiFve3S+GvwCah/k4rqFt9sBrQzePOM0kjakkmhp3BjnK2XVbewO9x6++IBc9aqWiqOWJKIN5EkEoDOCOoA3GGqNEbJcxzSpvU1Ms6sRrkI3QdzfDQcRB8vl5kUMcojAC84arf74662w1DxOyhfE/h6v4azqI1NHXQyIALKIFO3k7ahe43HQb4xcWsctdPlB4gcEZt4ZcY5jw/m8f/GUcliyAaZEO6SLuRZhvt3uO2Jjb1XTU2CyjiKvy2Ysk0scTg6lEhAYHr9jvjOUlvK+1nDsjh7i356L5cpC+sKjEjcjvfoO/wC/fHbHLXFcspe2yxZrR0tBpgMyyKqnXGwAU6iCdQY6rg2vtsSOlsem3WO3KTd5UHM6bQTHUPEtiSsalnYW82lrbAi4KspG9+2PNdzp6cXW3FOWU8kEs0JSMM2tI4/II1vZRYkXa3YA7E7+U445XmbaaNFn/wAtNFA+hkjLFBICdN9ypF/pJF9iLEm3U46Sa5WV2x4AcdV3C/G+WVMeYtHHU1MaVPKGlCh8mkX9Aftjp3Wd/Ht3xf8AHGn8IeDIno3Rs8zJilHGl0YHSbvfZQBcW97emGWUkMcPavnpxbnlS4ZXqpq/MswfXVSMTreTb+brjhv8u+WNw6fRj4JfDzJ+EfC6jrqTL1pM6zEH5upc6p5hqJXzHotiAFUAd7XOE/EcLuvSKT3jMC5o2WyRkMJWgEnMt/Kbj/Y425ihmomv+rq7Hy21e+LoEQvcBmfSvYDcjFF6TLoGysehexA/Y4bHHeOK7Rrv19vvii7Kny2sBerf5eZdtbDqPtjGwNW5lGZEo6GdnpIz1Ld/Yfvi80HxaKhNLXdSLdRYjE1RDKcxiqKivoY35j5c8cEjtuCzRrIFB/mIR0v6EjGQwMLqyNECXjOqzdz9z0wCmhSKkpflVnmr6qEPzJJpSzIXYyBXa+q1mCgbnSo9MQGU9M8qoxBCqb2DEAH1Hc40MU1M1LTpEQY4okCAhyxsNgSWJJNrbnc4mxx49ADc0aet16/n1wAwMokQuyKP5rKWVhv06WPTDQrpJ5hOYm1T6E1iocgljqN1KjcWFrG1rfbCUHCZbfqKbj21W/OKKanLoc3ppqWe8cdQhjaSFjdL7ag1tiNj0PTvidAbhjMKvOcky+pzSCKlzVogtXFEbolQt1lA9tYb7Yv+BlUU6Qssj3qGUrZdiVuRuL/v+MQSEkd3dYAsd/025gJc28xt1Xf73wGXoXqgjCBVksSplUN9yL98BaahI4yztbbriQDR5rBPtGdwbYgHqKnW4ZGIIxrW+RVC2qca1DXP1YbDowwU1K8zW2FgPU4vxGu1lGZCzEeZ/wCQbacZVGgytMvS4Fl6kX64QPxxRlWXUZM7lZhtsOmLboKcpjNc0khlDmVy509N+gxkNJIRSONahSPUYvYGhzeRc5+XNFKaYQmR6tQOWGv9P3thvnQsr+KcozVGhpJdUqHQyny2OGx5o+LrOeO8l4LE3A1XLRojNJmD0qjnmIDblnqDfrbfGcuJw3hJby6F+Hf4vsyzfOqfh/javaaU2jhr6w6WLg2CSE23PQHueuM4+TXFenPwzUsexuEs/gzukzFhEAtLXSQDUx1HTY2I9PNjtMt8vJZpZmGZBEkSPyyEEAkbDG5WbGv8DnMHzvOVzcQmZ2T5QRMSPlwtiTfuWJvb2xNpp138T/wu5Z4y5NFmNA1LlfFNGrLSTSIVjqb78qQqL2J6NvY9uuOeU301jddvmVxJwjm3Cme1mUZzSS5bX0jlKimmFmQj/MehGx7XxJI3+1NPXDK5uZBUPqZfMquSGNuo/B3H+eN3rSb2ajihqiNYhMFia4ENxbpfYdsPbKzVWSCaLMOVOlR8w8o1AtE0uhrkbWIve1/cb4XhLzSniPiN56GZ/wDiIA4MZQkMsiFg2m+22pRsBa6jHOz2staluPTrSqaSeoZydJHmF9v2x1m2dt14Pqnd6RDUanjcSLpG+pSCL/8AnbHWLp2T4s+LGfca1tDWZiivDlEawIVUPDCZQwLENezMVP2K7WOPPnZMtPThLQHgpwgnHviVl6Vp05VCGlrJ2a3KjA+r3u1hboQTfbGZPtPJ+H0FyXxy4MTJ81+XzDLa7K8iWOOupI3A5CyPojEaKbnz2A0XsbeuLbJXOY74eePFX4/8+qc/kpeDFiyrLIToWaqi51S5AsS19l9hYn3xuZ77az8XrCXh/wCNnxWpaiGrlmpc3o3I1U89GoSQA7gMgBU+tjjpOXC6jv8A4Q+PvKMwOX0+c8M12RLUTaJ6xXNRT00YF2eyqZHA9NOw3JxdTel9Lrcbpm3xweHmWVNOkS8QVlFLf/j4MolECWvubjWQbXFl3BGJcdJ6UpzP+0A8NKXLYpY5M5qp5yVFPS5eTOgBsS6sQoPcC5J2vbEPSuvpf7SDLKVXL8FZ5OVJEZlkgjLrfys9ja5FiQosO1+uFsjX/Ohc4/tKslgpac5ZwrVTVr6eZEXSJE3IYa2Jv2s1rb+g3xM41/ysrrziL+0N8WOIc8nTg6lpcsohIRBTGgWulsG6O1iCx2FhYbbY45ea74en/wA+Mx3a9v8Awr+IfGPiF4bxZtx3kUGRcSTykOkAK/MwqqiKZ0P0OV2KgkWA6dB1mUym3gymq7tRZJWCanttc98VFkogjIp46qCTMNtETOGdQTuWUG4W1zia/ALnWCF1VqqNGdtEYdrF/t64v6QNVBUUqWDg7XI2xm8VQCIsBdgNOo7C/XFGGbntvcMOl164ToLuIKOU5bMYDHBUpGbVCJaVItSmXSw3BKK1uouF2NrYX8g7LXirEhmpWaeKZVkisCfKwBUm+9iCP3xRZUpLCG0qkxBOyMt3b+m97L+cQUUcbUkkg5UbiWd3Zkbz+be57HpbY+h9cAbTiKR+W8aOFOwswHqPqAuftcYC1jSqkVQIKineRghgmgKupJt032369MAPXws8obk00pKGNnmQsyof5V7AHe9xvgLqugE8ek7D0xLwFUeVfKA8sXud8NCyKlaRjqBUDrjV10Ba6uWgiITzSE9T2xnYDk5+YyCV3YsLFVv5Rh2G9HTVMwuqiSU9FJsTi64B1QjKgjMXKfoQ+++J0EOa5ClYCJiCpG4HfFs2OULzcOxA0VOlQoP0P0wvAYwZu2aVSx1v6MjjUIx0/fE2OZeYaieopnYrIp8wVtmHbECnNamipa/k09G00ifWy7BRigDN6CKtsFsEYXOoYvA6B8XfhT4c43y6urKHIkizoqSlfQWSZD6lNhIPVevpvjlcNu2HkuLy74H/ABBcS+CPilWcN8eVdSmUVU6QVT1isWpLKUjnUHfRbSTf+Ue2Eby/tzHvjafSzSCW4AvceY27ev4xubnLz1bTUyDN4ZlULNFGwZv+k9v3F8aQyaqmlgmaSZT5v0lVbaVt1v64zsdG+Onw65L4zZC71UjpxPDGIqTOCCGXe+hgCA8Z/pPTqpB65/bUr53+MXgxxR4M52tBn9H8vqUtBVwjXSzp6pLt5h0KkAi/5x0l2Wfh1nJNHHIeepVgbq3Qgjocav5OolHnJFksSV/mG23p/wCb4aIX5jWs8KESIsAJ0xl7t13JXe25vfv13xrRSo1PNRFiLlj/AHgPY32/Fv8AXAdh8O5VPRJSzyDSbbKAbgA283+H74ntJw7TG3lHiPiKekro5aKUwVIsqzKouijqo7bk73HYepxLJl23M7j/APLYOEIedlNTeskpxUxNHUctyolXrpNuxt06dPfEynGmPa27pnLm2XUOT0WS5ZlMlRmEk0Zp6lmbmRyEMroABazXW/svphqSftZa2LLvhV8ROOa6eSSgoMjqEKq8uZy8nWRZVClVOraxuCb2BxxmNm7XfPOZSNjyX4RPGHJ6yb+BjI81khZoZYYs1SNjcBgQsgW31AAk7EEdsdcbZrTllMb2WZL4TeLucS0y0fCeZvWrPIr6YIysTIWjcMCxClWv13Gx6b4ZZW5bdcLMJZWeKPDzxwosxgp6jg3iN6iIhY2hoxIPLuLSIxUgW23xPfKzWzK4ceorhv4WvGvxKlq6qooE4cmjVWE2fVHyZnDNdgoUMzEEAm4BJxJdfXLLybvTaqT+z08TM0SEVvEuRqWOl0jmlmKr2tqRAe56jGds+z0R4S/2f3h/wbTxNxTSvxjmbi3zFSG+VWwB3hU2Rr3AJY3HucVnLO16LyrgjK+HcuWkybLKHLY4lIhEFMqoht10i1+3Xrh6yufsecJ5KmR5bTQxu7cuMBnf6nPdj7+wxths1PWF4tUaEHcB3BGr3HtgJ0OQZLTVU1aZUizadjMVULzHsALX6kYQWQ1LCV7OqE7DmqCfxfpgMSK6zIg0mPSdT6jqB/1HXAcWGJmcLrDqotKyeU3H8pPW2HwUwzrOqyovPiKgK7AqSe91IFsSKCzxZZcmqVWcU/MZI5GIN+Wzqrqtt9RUsF9yML+EM6iWOggWFNdNHYJHJTLqMNuhA32AFsXYpObUjKKVa5qxVIHzMsYjMrsTsehJHuBftfEFfL5co8xa5sNA6H3xrYzmMpam0KoSSTyoZwdF79SRcgWvjIh8wkVXGWOt5bpEGYljYX2ubkADFgNgaplkBkk1DcFAqgHpubi9x7Hvvi8g6XMoUQCQAn74nSAZKuBhs4xFKczz1UheCmGqUj6rdMBrVLl88kg5zs5vc6j3xJBtmVU4jj8wuRsCcb6QzSC8ZfcEdx1xFYkj8oaRmc9i3bEA7wq7hutsIqTxeQBVFz2t1xUVzCWGmKNHHdupAuR9sApFHTQVpqypMxTRdW7fbE0B3jjSvg5MbursWqH7IuEAOaZnTVuZ8miUmGPYv2Jxfod5ZFaEAi326nCjy98cnwj1PibwzDxbwnS6+J8rDmShVkUV1OxuyKCB+opuw3sQWFrkYlx/Dphl8eROA/ilz7hPgaPhPN8piz2noH/4KraulpauntbSutQWOm1lYWZdgbhdJ1jrKcLlhY758L/ju4dr4eTxxR1eS5lH+mtfTxc6CqHZnCC8b2tey6CbkaB5RfVix6a4N8Q+EuPYNXD3EGWZxqW/LpapHkHqDHfUD9x3FsZ9ah1NBaVARp3uFIxio07x4q8ryjwm4nzHOqGHMMvpKGSSSCWNXDm1lB1dPMRv1GFaj4n53UKK2S0YjW9yEuQP3x3nMa02fw18NuIPFesmpOH6eOpmplV5mllESIpNrlj1tuxABIVWY7DGbdIJ8WPCjN/Cqv8A4dnYBzBkDMEDaN9yBcAm2359MMcplw1628tb4H4eGZZl+puiLqeMN5tJ/mI66fe3+eOk4c9vbPg3w1wB/Af4NnU8GY1VVKEp5GtpjBTllQT0N2Ld7EL6Y8P8nw55WZYV9X+P5MJNZOveOvhlFPTHMKCoQCVpI4KSQEyyMhXUNh5TuWDNbUNhuDfXjuWX9fw5+XxyTcdVT0NVklNLRuDEbHY/537jY473dePXreSrhXN66kqK2X5uqo4lj0vLSymOQC/QEEfb84vbU4bHWeJ+d8HRVVNwxxvnlbDVqsdQ9TIxI3OoLrZinYXBF9R32xrLWtRcbe61ng/iHijJq05pkOa1yVKtqfTUMS2/Q774uM4c8suXvP4afipm46pv4Fxq9FBWom1VKscUkz3FhI22ojqGvvYjrjnnisr1TSGnq4EkRlkLDZkIZWHrf0xysN7cnpUiKqLI2oWkI1AexHvjN4XjQqnnqgskEdTBRyyjSk5cBV+1+/thdoOy+WuyWJsvzuc1Up/Uhn/rT0Yja4P+GNxnVGvxFSwMySXDgXKDqB6kdsbZ9V+T5ostPE0Y0pYGNr3Onsb/ALYrI+sqJ3jMqyKG1KSzdhqFyfxfEoJZIJVSeOWKdXXUkiWYEHurYIJp+TLYFyfUMQxU/cYqraeNY9VmNgdtWJsUUDPPTaJFBQO2mRvrIubHEBTwyxC6zMx/qcXwC6viFerQTykRErsmzXBDXv23GALZNWorIxHe/Q/bDQoGWrzNcTclu9+pxRBRJT1saPpdHBGoj6GA6/Yj/wA3wgtZU57WWS5UaqhtPKO58lr6r9+lt+uADzEmlmy+eMhpVqUjI5eq0cnka39O+k+llN/aB4tSsgIL3UKCrFhpvffYea/323xoLs9j5UBlt9PpjAXUtHLNHzGGlSL4smxmhoFn1yXBIO+EHI00ZlGum6sN8W8UbBEioRcbYm+QxStjgjugsw7EY1QFJWNWykkWuPTGBTyLdTbFFkCkkdG98O6LqmmKbsL+4xq/pCuqp49LHocQIK5mp45mV2EdvMFxFLcky4CTmWJ5h1fjFGyyOaaMMAVGJRrfEUueSTcyCqdoQthD/KPfE5+LHj3xf+Ayh8ROJq3iDK8+k4aqqvVNNRxUCNTGY7lgEKlNR3awNySepOMyevTtfLbJK6Szb4F/F3KLfwifKeIKUsVuaxaZwLdSsoAA/wDtf2x1meU4Y3O2j534LeJnB83Ozjw8zMfLsF+fy0CYLboVkhZvYXG3ri+86ScdD+EPHPjHgiraDIuOM4yk0sthlmbOZo45EurIY5NQA7FNgbdL2tvUvQ7f4r+MGq8RfCvN+EuK8spjW1sYSPOsnm5UTDWrDnU0gO1gQSjk9DoxnLD8N4Sy7eEuKcvqKXMJkqTHpHm5kO6sPbt29fbGcb+GssbJuvqf8LngLmng74GUOV1uRVFPn1Y38Xmq4WjneKpkjA0PH5ZAgjtEyqH21EX2OMZ7ycJw6U8cs98PfEvxAy7ITU/PSCnkoK2SdlBux1BEckBXWS519Lk9MeHzTKTeHcfT/jyb1n9eWYaHNcjb+C1rSqaOZtVGqiMLMEWMvpAB1FI0uf5rAm974+j4svfGWPJ5fH6Z2GeW5nU0VagkYBh+orodaswNwwB2Prj0yuH7jufw18Qn4iraPJeJakpSVRWEZlqOuFtAjU37/wAq373N8cPJh6T3w7enx+X2vrk5xjwKk+URUdRSqMyhjFqoNfWmkFRf03Jt27Yxjnj5JuV3viuM3Xm/jDhuromqDGCEvZ7dvTFcrPw2nw1qvDxKGKTi3IM4zKpqoXjkkgrUjjV1ewaFPK2vZQ1y31Gy9scst74axx4XZ3S5FwZn2V5xwzVyZxk0sRapo5oATFJY6gVa2pd7Xbcbm3pvx5WXVc/J4567hnT8UZZ/FKWuFOMvMKLURNFEdpAwKMCejAgHqftj0bjjJp2VU/EtxZlCpV5Z4nVlVOvnNNNTKInv/Jy1jAHodx02NscsvXXDt6yTSip+MzxLzWmYf+o8ry5YVUCP5TS0hN7sCW1fy779wLEdMySuVxuJC/xkeI9NCYTn9LUMzlmMlFHIpFhYBbC297kk3uNhbe+s0hgPjD8VOMKyLKMiqFhqalxFHQ0FMs7yHa4IYE2O24tb1xwvHTrNTtvHgV8NPijLxTHxTn1VXZDHTyieVJqjmVVWA3mjbzHSrC4JNzY7DGfq5543HUfQXI2jp4VgSMRxKgCWOwHoMdnkPEnJddD7+jLcN9/XF7BNHBEyfoiJIiT+lTKFiBvvYdt8UMqalVZiBuVFyBt/jigw0YlBL3FgQDfGexNEip1+qwUXJJ7YASnrqSth+Yp6taiJz5WB229MOhmOjjlD/qBmJ1EL1H4w0Mco0i+RS6dbX6YoEnqDUXLxvpNgCLaR/ricgetlrlSJKWNXCurFZXsWW/mA2626dN7bjGhNKlatmDRszXKkOtitjuN8AwFBFUppdSyBgeu1x0P3HbCciilpo6yKNZLyPTy6XkW6Byp2JAtcHuOl8QU1sXzGdtLU1aplwS4jB6nHPJuQJFmnzVXLy78geVR0uMWWs04jkhWMcqLlg7n3ONVAiknMkAFr98Nh0ig2A3OLqaBC0apYyNpuemHQlOtNHH5XGrtY4bC2a4BJPk9RjIIiVY0BX6euOn7T9BK2pniBKyEg9Ft0xlSacyvc3IviXgByxPJCYux6374QH0kKwBVba+wxpDKCCCoV45D5SNiTiVWp1HE1G2Y1dDHUCWSBtJCi9sSXYHm5ktjGdm6knAcnkhhVYyfN3PYYaGY4EcAkXB2222xR1R4m/Ch4X+JSmTMeFKeizB2LGvyhjRzXPUnl2Vv/ALKcP8WWx584i/s4qekl08P8a1UquTpp8zolOgdrujbjt9N8S+1dcfJJ3Dzwb/s+q7Is9yHNuNs4yvN4sizD5iky2mLzQSozs5ModFCqCEYRAMC5dmJvpxzxwuOVy/LWfk956k/x1fE1U8E00nhtwvIMqm0EZgKWXaNG35SWPlBB3UWCg2AA2xu43JJJjOXz94ebMs04ooaelV5qqeoQQpETr1lgBpI3H/a+FxkjrPJ+XeHiFk8k1bVjM6ejp+JcumFDmVDDzA2pVCiRQPLoayuGTYagLb2x5/Bl6X1+O/kn/TGZOvOc3J0sxIitYgAntv8AbscfSl28NZmzGWKFGjbQ6m6NuQSOx9B74WuUtldq8C+JMs2Ry5JmztVU2pKiGaWS80DIAByydrW2sduuOX/nlvtjw9H/AK7J6ZAaqpkzymrIDRtmTyRS1NRVPQJTpAEYBQkiyEykgnUGUEWBW24xjPH11y6Y5zLpnwt4IhgramjzKipq7K62dGCVEavHpPlaxPQ6tPTfYg2x5fLl1p38fMtT8X/Bqq8M5xnmWRiXhqeRdbElmpJOyuD1QnYN+D6l4898Vzz64KfCXhym4r41y7KK2ieoy6sdoeWps6A3IIYeh6H2x2yy4ceXej/AvkPE9dU/wvirM8nkLsFgq6JJ4wL7fqKyEj7i+OGPkvTVnPDcMi/s0OHJ8rQZtx1XT16ylpGoqVIUdD0VA+sggdydzf2tv3vxi7Mcv/s6uAMomvmGZZ9m0SEXZpo4dYPqFW/5DDGt2saehfDvwe4X8OYEj4a4foaenewqJbXlawsCxYlm263OIb123yWhgCPE4VVkUrttcEb41phGlo44IYkSzQqABv6Y1JpDWB0tYrpscaQxjDyC5SzDoAQdvXbFB2WyLGWGgRr2QXOH7Ac1emZZuKGWklieELPHK5sPqt0H279cTeycGdVQU0yhpJSJX8qqAbHvvhoCrSuJAZCkgAsAAAP2GIJSJUwlpFYL5bXHW3pjXAIhBaNS51g2Num+G4L+WvUgXOLrf0DSwaFLAlu5PbEAK5ZyQ7693OrT139sNiM9XW0cJSm0NI3aQHp6ffDYKo5ilOqyxhLdQg2JxRrEXDsPMMr1EshP8jNcD8Y52bXZzl9HGjJGENj0IxvidIcSQpHHbrjNC27fxFCQAe2NBsp0N03xJwMyzNKNDamHSwxewOMlkkIYBlQdN8TQzmHkopQDY6Ta+LsU5TUSNQRrJu3TE2CKqIaNzthsLjTMbhTfF2KJEETWNmI9D0wFMjc2oiGkhRvc4gsqFaWN0RihO2odsAgpchpsrklFPCA8h1SSEbsffEk0D6jKIaimAN1ZPMLG2LQnra2ipmSGQ6pu6oLnEgMkmSFY1XvvY40JGoDOPLipFUoWPmSEgX9cB1H8SnxDUXgT4fyVEUiycSV6tHldKbHzW3mZeuhf8TYeuM3jp0wxt5fI/i+qq89ra7N8xnkqq+rmMs08zednY3N/c74mOWuHquGsdvYXwp/DY/APCtL4icSUn/6zmEIlyykmG9NCekjDs7DoOw++MeTPUcMea0z4uG4elz7J62AyU3Frrpq5YT5HhA/T5g/rFrAj+U2PQEY8e7xXpnHTqJ6GozR6uWGGGOqjs70fOT9RWPWLsw9gSf2x1nk/58ZN5eOeSbxKoqepSq5akQ1Uh5Yj1jZibab3sPzt698er/HgvHFBRZu1DJqZtbI2wKXFrbN16g9sblsctGmX8WVuUSgRVc0S2MsSqdShjYbC/kJ9bdh98c8v7cN4X1bvwJxEuZ5hS5ZSw1AaomKyyQW/ULElw47dR5uo0g9L48flwnb14ZfHrrwb+W8UuAfl8wp0zGFtUEnOW6yIGIBI9wAceayxu5Tqtu4O+GnhDw9zOuzzJKORcylUrCk0pkEBYWPL/p/22xrbn3XbfD+UwJlsVJUvrrIkszJsTi6jNprTAQlWBcDp5xi9OZq1Qs+lSi2A+pt746dpVlPRRQ2awLeq43IyJngR4GkZzHpBIY9tuuNAXh+AT5dDK5CyzeazgKW97e+CD5IzCSUAHawxYio1dYZEEUzUwVgWCoCHHob9MA4iqmkYXa5ve4xpNBsqz2pzSrrhpHyjTaVqH3dwotsfTrYYy0dTlWpA0TyRAfynyk/9sKiqKZViFlJPcnDYKE0RjDEkIe5GKLkZXW6i49cIMGEMRcalG4BxfUclkWMKvQubAD1xBVMgIGsa7dD74gCroy8WkPy1DAkWvcX6YovgaKQsxUR6PpYsDqH27YCqOijUXsbnEF5BC+QbjpgKZa2UDQ8dj2xABSTc7N11Hp2xRsWOkm+UFUcsSuodRf8AqOM39KY1c9PHGQ0qqbbC+MjWZhFVa1L3UemIobKs1ppQ8JujxtYahi8JRNVUgxsUYNb0w0F4qSdz5fthsQLIqMRGeYTucBVyzIw3thrkFpAzgaR9ji2AOsWWIm69MAorZppYSqNYna4xKFIg/g9NJWSKJJBsNQucTX0W0UM9aRUTAqGFwMagbBIkdADuexxaNN8aPFLh/wAH+CqniDPS8iReWGlhF5KiT+VF6fk9hfEpJt8suOfFl/FfxHreLcycyZjJMWhhnFkp4FHkjT+UKBfbubk7knFmWvj2TGab98OPw2VHiXxO/E3FFDOOHYVlngglBJzCUgFSx2sg1h7jqFtjF/LGeftxHuLMqz+IU1FTTnW0KrGxAsDYAf6Y4ZcpjNPmf8RVbJxH4h8W16qyxQ5g8UIU3GlBp/YW/wAcd5JMduuO8stQw+GDwabxgqs6o6nMqrK8vooeZTzxoJeTUv8AQdJtcetiCR0IOMZ2fXOZWZK/Gzwsz/w24rfL88p5aqdlM0WaRl3SvjLP+oHCgawdmU6Wv1B6nt49a7c8+eXWBPNKjUURvNZxb/w747zmOSyKKzMpJVdRRzoJCjqDe1jff7WHrjO9Lp2bwOY+GMozOWrpGeuqoFSnjsOZqu6g3G+kBibdSQvbp58sp7PRjxy90/CvwtW8PeHeTzVaRwmoj1PG4swB+kkdj7dsee81PrvzLqGJa1vmkVVX+7ZTs3vi+paaz5VRRzCoiA1d8a9YzaGloy6pJrZipuFUbYumbWVp72LAgY1IzRKRNGyupsv9JxoqjP3qTlMvy6hpNgFPQ79DiVIvpjLDCkaxoQoGkEbp9saQQCQdRs3exxYOSW0M5F2tcC1regOMhTlyHOwmYzVxeAqY2oYxpjVgbNfuTtjUDmCXTCsMSBEXYKosAMAXAz8zUSZSPpW+2Aa0q8xLsBb0XGRZUQNJSuuoouxFsa2DqWGSeMEJY2uR6Y1P2jjA9xbEvPSh5oRMyEgkodQINiMZFNBWPI0qTqUkjcghha47H9sUWLVQyqX5bxi9v1VK3+18BAh5DGyxiNr3Opb7en3xQTGVkQMvQ4z2MGDe4NsBRJG7NcnYYgVUtOYs0dyL3xoPUmXTc26d8al0lB11U0a6ozc4z3dq0+vnqqrMDKZmFttIO2Jqh3l83JhsWBcjv2w+BzBNTiARpCsj92A3wCuop2E5exQN/L2wkoLpoUIubE++LYJ1NMYo9TkEnoBiygNyEgZrXZdwMTsCQ5pK0XMvZvS+AqGZyVb6XW3rgJBICSLjbcjAJ8+qQ9BIkEYmYmwF+mHwBpmdTHBFCYgGA3tgDaQMLSyA4I8s/wBoHwfXcS8D5TmNMWnSjqrmCMavKVILH7f64zl1t0wunjjwB4VyKr8Q6QZ/TfM0i6v0WUOjm1hqB9Ln82xbZJy622zh9I8prqOop6KmyuEQUippVYxpCLYCw/AH7Y5ZWZdMTgyl4djD3XuepxnS7fLjxbEVLxrxhloLRtBm1TpNydXnOxx6MNWarpMrjeHqn4D8ly/I/B3OuIKyRIObmTR3Y7BY0W1/yxxnOMatpl8Umd5V4h+Ff8Uyh1rjkFZ8zOIG3MLDTJ69PKfxjnjZLvbpMeLHjCroqbOavWEPPlIJsy6JL38wuL3O3ft67475XXTMwdr+BfgRD4q1SRwZpCZaKoAr6WN7ui2uNLDYtsbrew2362z/ANGbNPZ3D/w7cJZDNFM2RUdRUQm/Oli1M21v5une/uceS73tre5p2FNT6DGo8q2sWOEZWxVDoFEktlQdR6Y67Qbw/V1QFSkzcyDXeKTrcHtixmnM9ctBCrMpIOEQVDUJVwiRV7dMdGe10UYbqDp9u2BtxoRIpVdx6+uAnFC0Z2vvjXxF/KjQjVuT798QZRJRzElh0EjyN2OHNAOVZGaKEwxoFVmLNYdSTcnF6Dymy7SgJAA7+uKhjHQwIg0uodugvviiFDRfKalDFlJLEt1vjMi2jGiEiFBexFjbbG9TTOxL2iplSSc0y3HnT6sc720qqqxnJUXZR3ta+NbAvOaQEKtr7a/TDYpWiAlVyxmkKhTI1gTb2GJeRRnWloIqaaQ3nYIgDWII3BH7YUNaGK6qJXBIG5O18WdAxaCKnhCvZSBtjIXVEvKewsRi0VS1Kouo4yFFHVNU1z+XbsfXGoG0UYe97X98O+BVNl2q+lvxhdwDpkKsSx3OHIqnynTt2vthoZphLRyaVQkd8TQOKGQamv8AY4ojDCQx22PbEt2MVzjlhemABMkaxPfdrfti9QLKaNHVim+5xNiTqIzcC59sUUx0+uU7bd98QAcQSw0jQQU8Z5znrhRdR0DqjSTbk9zgLGXnxtEBa+1xgNQzTJIJI6xa1WqU0lFSTcEHE4jU/TzBU/CbXN4gycRUdSlJl7S80UsS2t7Y45Y2119+NPQvD9EmSZfFT8uzqNyBiyac7TuRnK80EkDfTi6Nvnl8ZngrmHCnHtXxXlNK0uUZu3zUwUE8moYnWPsdiPvhOHXGyupeDvFLiHKcjqeGYal6XKaxw80XYuBbVftt1xqzcdJZLt70+Gzwfp4vDCnnro+eM2hY1EEo2dHBBBHoQcee4l8m7t5F+IDwWzTwD4s+XMZquFqsu+X1ZXUNF7mJz2ZelvSxx3wzutVrcG/CJ4lHgvxpy6OkCtl2eOKGop2YKGJuUcE9Cpv99xjpbK55cx9IU454YrK16CLiDK2r1XUaYVaGQD3F8cbI5j5YoammEiOrK3cb4zoDqaaFSkyqyEWN8bicpZTTQUMRhp2vASSu97e2LrY2ONYKinEcy3A6XxqVz2xDIKbUkagqOmKi6KpYptt6jATZydw2hb9sFXxzxyHQrXY4Iu+VtuX+1sXoWLzWsHclV6XxYGNNC1VHojYIx72xag6lpXpE5UjBmB6jCTlVksAhGrkoxPRiNx9sLqJGNtrb/fDapqyqp2Oq/TGozUG85XXEkyKb2ftjOUWISTqnQC57Dth7KoR9ey+XfbGRMMyMSdxbsMINXq6zl+IWXpMBJFNQyGNOvLdXF29rhrYTvY3FwrpsPwcdEa1W8RVdXIDumOe1W0ImqXu7G33xATUweUkHcYoU0LyR1jBRtfFGzQ6eWCNvXD9iYYHvjVuwJV1705IjF/fGd0C00stTNdz+MQMtNkBwAOYTiKHUWKi+FC+HN5GjLorMo7kdcQBVfEctSdAhN8NiXPHyLs66XI6HF+DGWIVpgCDvvgLJUscUX08O3XrhrYUVSRLXsXIJHTEEpq9dIUG/2wEqYFrm9vvgAq2k+YdnfZF3IxNCOVUPzUcj7GPoBiiubIVEhbSDiAarpSsehVAOFC+t4YoM8oJaPMqeOrglUq8UqhgQR6HBdup4/hA8ODxJNmIyblqXDrTrIeUp6/T7+mIu9u78vy2DLKRIKeNY40UKiKLAAegxjStd8SfDDI/FThibI+IKMVdDKQ9g2l43HRlYbgjE0syr53+O3wWcT+GFZNX8PVBznIR+pG2nTUQj+lgOtv6h/hhNzt2mcrz7WUGd5RKXqqSqpZemtkZPvvjpPVi1uPBXxDeIfBKJFlPEtfHTrsIJn5qD7Br4tk0y7HX4pPFTjOm/h+X8yaoYWMtPT3Y+/tjjeHTG4/XrD4WI/EODIJqjjB5J2eW8cU58yj3wxytjFk+PSBneWNXRAvtjrK50RExDb9cVlepBa5AwFjsqC/8AKPXAJuE87OeZlmUywmOkiflxM4sXt1P2viwbM78wqu+3viAqK72HTtjU4SmFMjQsuna43OCiRKWrlQm403xZeQWzA2Bv+ca4vKbVSMB9sZtlo4BhiVx28pvv7YVIoYCPewUYw0grADYEA4oxJPFAjSO9hb1wGreH85zWlfMKwL/EeZJE9x5kUOdK/tY4g24y6wQu3vjdy4RrrWmICEW63xntTrL4xHFY9cP9FtRECt74BIo0Ver3xfoZyMOUSLjbqMUYoplA87b974gJ5STm4H7YmtjK0yxbgb4WaEJGNwBiBZmTl3RSmpR1wA87s+lIk0KOuAjFSAEs1icWCrMw3y4VF3JtiC2kjkiiQHpbFgtGln3xRZLdYzp2NtsasSNQnpWp8xZ3ctrPTtjmphFCHPT841oHQwntgJVNOnykoY2BU4CnIqTl0Ngbi+2IC6gctbkiww6AFQiTpdLAnviAT5Ucy17kYCaUhR7EbHvgJOio4G5OH7VXNBNWHlxG2Md9L01/i/hWfMMolhm8wAvviWLKUVPA2R5zlNJTZjltNVRquhkmiVgQRbfbDSNLX4ZOAqecTRcNUBYbXMXb/fC3St14e8Msh4dh/wCByqmpxb/24lW37DE0UyyfL7VcxhLBAbW7YTFNtk+WOkA2x0k0LorxruB73xUSJSR/INj2wRXmNG1TSGIMUv1Pt6YKpp4xSQogUIp9MXtBmtowN7jrhoF0+apGQHBte4wE4+MaZ6ySnlhlh09HK7NgGFNnVNJIdEoL+nfANBUO8fMK+W3Y74WmiGtz9IZhHZ3PXSoviLouXjGveokEWVzGNBszOoviwNYs+rJ4CWp4oCQLF3vhbtAzVdQ1UGapWUW+hRYDEUYaqSSMhm0gi1/TGogdYEjtYXPqxvi61NptlmY1iSIQslrNYW1D3xnW1MfmjYB10kb3HTF0OUuVJDEnc+mEFrRtC2x69sQD1VTKBpINvW2IF1CzPW2YbXxoPTTh1tvbDkQNBGLXFhiDklZDSrpBucWXQrizHUx1mwxOwVHIjrsQcBRU0/MHlxQA0DxncdfTEGKpBy7RN57b4AfJpFqaowTnVIB0wija6nWCTSuwOLpAgjsbDriC5gQljcH3xrSEdXSGavU22HfEUctKF6C2AkY2CkA2OJoA5glTNSypG25FgTi7F2QU09NRBKj6vbDqAisBcaRuPfGQulp3jFxtgKMuWRp3LDUemC1c9aUnMTJuO+G/iLVi5l2bBUoYWik1R31Ya0Ac1NfW19PTx25B3kNuuM3e14HVGWJFEFWPce2LoCR5fIzkkWX0xLLTaU8Jige3YHF6ibB5HTGCF2ItrN98IUylp5pFHKAuMVFnIMiKHXzDAZSnEW3TFEnhNr3/ABiCPyYmUq/T2xdCiqj5BRet+mAy1KWIsemKOVUzNGoMakr3wgC5Z1mSLyuepGJRfZqpVd80njMe5jB2b2xm/lelfN1klSV9+5xZybVCVjLy99+98VB8VDIyA21L74UEwUqUxuVN++AY09JFX+Vn0AYoLMEFI1g+vbvvi/pA9VoWO4Wz/UCMP2MrUvNTI5XS7bYqmkThlB3tjmLo4kmkFyLYC2to0ih1XBHvijXaBkNft64sD8IbarXGOnTPYee7Cwxztm2iWroyr3N/bDiiI8q9cPVNiaWUR9ATc7nC9KP+YWx9euIBTV824CE4dgaWNmJ8tvtjVQjMcuX5p82CdJ7Yx01DKGvbMJGcjbCXYZwxhYtdt+pvjSMTVayLbR+cTYWsjPNdV2wBCWGxxRk0xkFwMUC1lI0CA3074lmgVDvEm+rbriCNRHdCRsfXEC2S8pte5xV6TiaOmIRba2xERmprvqaxJOAn8rJLYKwA6YCqVaugjbSokB2Bw3oKIs4qKTMFWWLVf+ne2M700afxxJ3MdrN6HDe1HUy8xbkde2NMo1VICpX17YAeKkKeQbWwRIxzQiyN+MKIQF9bFiT7YA0WewtY+uKI8sqSeowES5jO3fDYxyRKQZRdgbjFgs5DEEjYYAaaMHykYUBOnLvY2B7YgqEIZx3wBsVClgWPmOKMmhVJVYbgdwMUM1lkiiBp4hNIe2Jf0CZKjmU4NXByZfbD/QKqjYjFgsCljYdcVEuQ4BJUkAY1amlccTyKXQErjG2jeIWjX7YwOSu0YuoucAtq80qJRoOwxd7FWW05actfcb3wg2CGqZV0WsDjUolKikAgftiXkDSQCS1xviQCVtJ+gSBuPTDYApyyCwv13w2DrhLlrEEYf4JRSwQwnfzenrh0I08bTAv0W+AXZhSfNO0Y29hiUSpKZKeIRnaw64Ah5SiFFNx64uuBTr9sBOGoEQOwN/XF2IIyuxOJOxGtzMUyhY9z6DF2KGeStiCvcDDsUUEkkVW0JvpG3tiQG1c3LjO19sWhK878pio37Yy12toITI4lY+YYJRLNz6jTc/jA0LWFlUC5/IwQFm7ziPRGT0wWAMroJIJBPUgv7EYa0bOpaCmqRrjUB/W2ChJ2enACkg9MT6aWUwmJ1Obg4rKVzHUX6g9cUXsbbnr6YghdG36HBpNAL3BwZTCfviiJgudxa2NWUYmXlhWOwJtjPQsYLovffF0KmAIuRcYBbmAI06VuL72xKMR0pCX1WOL2CoKFmILuSDgLGkUymFCPL1wEoJpqFxNA1m9D0xBXLmlXmVSzzsoA/kAwVdSSM6EEEWOLKyOgjMpG1tPfFB62A2GIqikilpxLqZWiY7KOoxASPpxBztgFVR9b4CzKv70/Y4ob4DPbG/gxjEFdZ/yz/wDxwvYU0v0tiDk31N+MaiUK38uI0eU3/JjD6hWn/M4glL/eNizsV9jiDi4sFMnRvviCEXfBQH/8xvvgU3hxuIpi/wCcfE+jlZ/dNiUKG+h/tiL8GZV9BwROk/5x/wA4LezI4IGqPrGAn/IuLRYv92mIoGv+v84KIg/uh9sEqE31jGojjYlGF6fnEEo/rOAuHQ4sBZ/u0/GN/QLX/wDLRfcYxe1Vv1TC9Ix64gGm/ux98BlMbF0HfECyj/8A3Op+/wDriQNewwgFX/mD+MAYnTFx7DKn/uxi0ExdWxn6Mt3wo//Z";

  // Canvas 1 is just a box, this is a basic check that we can draw, it does
  // not attempt any fingerprinting.
  const suffix = contextOptions.forceSoftwareRendering ? "software" : "";
  var canvas1 = document.getElementById("canvas1" + suffix);
  var c1 = canvas1.getContext("2d", contextOptions);
  c1.fillStyle = "orange";
  c1.fillRect(100, 100, 50, 50);
  data["canvasdata1" + suffix] = sha1(canvas1.toDataURL());

  // Canvas 2 is a polygon with lines, this fingerprints a little via
  // floating point rounding.
  var canvas2 = document.getElementById("canvas2" + suffix);
  var c2 = canvas2.getContext("2d", contextOptions);
  c2.fillStyle = "blue";
  c2.beginPath();
  c2.moveTo(50, 50);
  c2.lineTo(200, 200);
  c2.lineTo(175, 100);
  c2.closePath();
  c2.fill();
  c2.strokeStyle = "red";
  c2.lineWidth = 5;
  c2.stroke();
  data["canvasdata2" + suffix] = sha1(canvas2.toDataURL());

  // Canvas 3 renders an image at a reduced resolution, this also
  // fingerprints via floating point rounding.
  var canvas3 = document.getElementById("canvas3" + suffix);
  var c3 = canvas3.getContext("2d", contextOptions);
  data["canvasdata3" + suffix] = new Promise((resolve, reject) => {
    const image = new Image();
    // CC Public Domain - https://www.flickr.com/photos/birds_and_critters/53695948491/
    image.src = kImageBlob;
    image.onload = () => {
      c3.drawImage(image, 0, 0, canvas3.width, canvas3.height);
      sha1(canvas3.toDataURL()).then(resolve);
    };
    image.onerror = e => {
      reject(e);
    };
  });

  // Canvas 4 renders two rotated semi-transparent boxes.
  var canvas4 = document.getElementById("canvas4" + suffix);
  var c4 = canvas4.getContext("2d", contextOptions);
  c4.fillStyle = "orange";
  c4.globalAlpha = 0.5;
  c4.translate(100, 100);
  c4.rotate((45.0 * Math.PI) / 180.0);
  c4.fillRect(0, 0, 50, 50);
  c4.rotate((-15.0 * Math.PI) / 180.0);
  c4.fillRect(0, 0, 50, 50);
  data["canvasdata4" + suffix] = sha1(canvas4.toDataURL());

  // Canvas 5 renders text with a local font the user might have in a pretty standard configuration
  var canvas5 = document.getElementById("canvas5" + suffix);
  var c5 = canvas5.getContext("2d", contextOptions);
  c5.fillStyle = "green";
  c5.font = "italic 30px Georgia";
  c5.fillText("The quick brown", 15, 100);
  c5.fillText("fox jumps over", 15, 150);
  c5.fillText("the lazy dog", 15, 200);
  data["canvasdata5" + suffix] = sha1(canvas5.toDataURL());

  // Canvas 6 renders text with a local font the user might have but translated, rotated, and with a blurred shadow
  var canvas6 = document.getElementById("canvas6" + suffix);
  var c6 = canvas6.getContext("2d", contextOptions);
  c6.fillStyle = "green";
  c6.translate(10, 100);
  c6.rotate((45.0 * Math.PI) / 180.0);
  c6.shadowColor = "blue";
  c6.shadowBlur = 50;
  c6.font = "italic 40px Georgia";
  c6.fillText("The quick", 0, 0);
  data["canvasdata6" + suffix] = sha1(canvas6.toDataURL());

  // Canvas 7 renders text with a system font.
  var canvas7 = document.getElementById("canvas7" + suffix);
  var c7 = canvas7.getContext("2d", contextOptions);
  c7.fillStyle = "green";
  c7.font = "italic 30px system-ui";
  c7.fillText("The quick brown", 15, 100);
  c7.fillText("fox jumps over", 15, 150);
  c7.fillText("the lazy dog", 15, 200);
  data["canvasdata7" + suffix] = sha1(canvas7.toDataURL());

  // Canvas 8 renders text with a system font.
  var canvas8 = document.getElementById("canvas8" + suffix);
  var c8 = canvas8.getContext("2d", contextOptions);
  c8.fillStyle = "green";
  c8.translate(10, 100);
  c8.rotate((45.0 * Math.PI) / 180.0);
  c8.shadowColor = "blue";
  c8.shadowBlur = 50;
  c8.font = "italic 40px system-ui";
  c8.fillText("The quick", 0, 0);
  data["canvasdata8" + suffix] = sha1(canvas8.toDataURL());

  // Canvas 9 renders text with a supplied font.
  var canvas9 = document.getElementById("canvas9" + suffix);
  var c9 = canvas9.getContext("2d", contextOptions);
  c9.fillStyle = "green";
  c9.font = "italic 30px LocalFiraSans";
  c9.fillText("The quick brown", 15, 100);
  c9.fillText("fox jumps over", 15, 150);
  c9.fillText("the lazy dog", 15, 200);
  data["canvasdata9" + suffix] = sha1(canvas9.toDataURL());

  // Canvas 10 renders text with a supplied font.
  var canvas10 = document.getElementById("canvas10" + suffix);
  var c10 = canvas10.getContext("2d", contextOptions);
  c10.fillStyle = "green";
  c10.translate(10, 100);
  c10.rotate((45.0 * Math.PI) / 180.0);
  c10.shadowColor = "blue";
  c10.shadowBlur = 50;
  c10.font = "italic 40px LocalFiraSans";
  c10.fillText("The quick", 0, 0);
  data["canvasdata10" + suffix] = sha1(canvas10.toDataURL());

  return data;
}

// =======================================================================
// WebGL Canvases

function populateWebGLCanvases(contextOptions = {}) {
  // The following WebGL code came from https://github.com/mdn/dom-examples/blob/4f305d21de796432dac2e9f2961591e4b7f913c0/webgl-examples/tutorial/sample3/webgl-demo.js
  // with some minor modifications

  const data = {};
  const suffix = contextOptions.forceSoftwareRendering ? "software" : "";

  // --------------------------------------------------------------------
  // initBuffers
  //
  // Initialize the buffers we'll need. For this demo, we just
  // have one object -- a simple two-dimensional square.
  //
  function initBuffers(gl) {
    // Create a buffer for the square's positions.

    const positionBuffer = gl.createBuffer();

    // Select the positionBuffer as the one to apply buffer
    // operations to from here out.

    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

    // Now create an array of positions for the square.

    const positions = [1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0];

    // Now pass the list of positions into WebGL to build the
    // shape. We do this by creating a Float32Array from the
    // JavaScript array, then use it to fill the current buffer.

    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

    // Now set up the colors for the vertices

    var colors = [
      1.0,
      1.0,
      1.0,
      1.0, // white
      1.0,
      0.0,
      0.0,
      1.0, // red
      0.0,
      1.0,
      0.0,
      1.0, // green
      0.0,
      0.0,
      1.0,
      1.0, // blue
    ];

    const colorBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);

    return {
      position: positionBuffer,
      color: colorBuffer,
    };
  }

  // --------------------------------------------------------------------
  // Draw the scene.
  function drawScene(gl, programInfo, buffers) {
    gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black, fully opaque
    gl.clearDepth(1.0); // Clear everything
    gl.enable(gl.DEPTH_TEST); // Enable depth testing
    gl.depthFunc(gl.LEQUAL); // Near things obscure far things

    // Clear the canvas before we start drawing on it.

    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

    // Create a perspective matrix, a special matrix that is
    // used to simulate the distortion of perspective in a camera.
    // Our field of view is 45 degrees, with a width/height
    // ratio that matches the display size of the canvas
    // and we only want to see objects between 0.1 units
    // and 100 units away from the camera.

    const fieldOfView = (45 * Math.PI) / 180; // in radians
    const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
    const zNear = 0.1;
    const zFar = 100.0;
    const projectionMatrix = mat4.create();

    // note: glmatrix.js always has the first argument
    // as the destination to receive the result.
    mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);

    // Set the drawing position to the "identity" point, which is
    // the center of the scene.
    const modelViewMatrix = mat4.create();

    var squareRotation = 1.0;

    // Now move the drawing position a bit to where we want to
    // start drawing the square.

    mat4.translate(
      modelViewMatrix, // destination matrix
      modelViewMatrix, // matrix to translate
      [-0.0, 0.0, -6.0]
    ); // amount to translate
    mat4.rotate(
      modelViewMatrix, // destination matrix
      modelViewMatrix, // matrix to rotate
      squareRotation, // amount to rotate in radians
      [0, 0, 1]
    ); // axis to rotate around

    // Tell WebGL how to pull out the positions from the position
    // buffer into the vertexPosition attribute
    {
      const numComponents = 2;
      const type = gl.FLOAT;
      const normalize = false;
      const stride = 0;
      const offset = 0;
      gl.bindBuffer(gl.ARRAY_BUFFER, buffers.position);
      gl.vertexAttribPointer(
        programInfo.attribLocations.vertexPosition,
        numComponents,
        type,
        normalize,
        stride,
        offset
      );
      gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition);
    }

    // Tell WebGL how to pull out the colors from the color buffer
    // into the vertexColor attribute.
    {
      const numComponents = 4;
      const type = gl.FLOAT;
      const normalize = false;
      const stride = 0;
      const offset = 0;
      gl.bindBuffer(gl.ARRAY_BUFFER, buffers.color);
      gl.vertexAttribPointer(
        programInfo.attribLocations.vertexColor,
        numComponents,
        type,
        normalize,
        stride,
        offset
      );
      gl.enableVertexAttribArray(programInfo.attribLocations.vertexColor);
    }

    // Tell WebGL to use our program when drawing

    gl.useProgram(programInfo.program);

    // Set the shader uniforms

    gl.uniformMatrix4fv(
      programInfo.uniformLocations.projectionMatrix,
      false,
      projectionMatrix
    );
    gl.uniformMatrix4fv(
      programInfo.uniformLocations.modelViewMatrix,
      false,
      modelViewMatrix
    );

    {
      const offset = 0;
      const vertexCount = 4;
      gl.drawArrays(gl.TRIANGLE_STRIP, offset, vertexCount);
    }
  }

  // --------------------------------------------------------------------
  // Initialize a shader program, so WebGL knows how to draw our data
  function initShaderProgram(gl, vsSource, fsSource) {
    const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
    const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);

    // Create the shader program

    const shaderProgram = gl.createProgram();
    gl.attachShader(shaderProgram, vertexShader);
    gl.attachShader(shaderProgram, fragmentShader);
    gl.linkProgram(shaderProgram);

    // If creating the shader program failed, alert

    if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
      alert(
        "Unable to initialize the shader program: " +
          gl.getProgramInfoLog(shaderProgram)
      );
      return null;
    }

    return shaderProgram;
  }

  // --------------------------------------------------------------------
  //
  // creates a shader of the given type, uploads the source and
  // compiles it.
  //
  function loadShader(gl, type, source) {
    const shader = gl.createShader(type);

    // Send the source to the shader object
    gl.shaderSource(shader, source);

    // Compile the shader program
    gl.compileShader(shader);

    // See if it compiled successfully
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
      alert(
        "An error occurred compiling the shaders: " +
          gl.getShaderInfoLog(shader)
      );
      gl.deleteShader(shader);
      return null;
    }

    return shader;
  }

  // --------------------------------------------------------------------
  const canvas = document.getElementById("glcanvas" + suffix);
  const gl = canvas.getContext("webgl", contextOptions);

  // If we don't have a GL context, give up now

  if (!gl) {
    alert(
      "Unable to initialize WebGL. Your browser or machine may not support it."
    );
    return {};
  }

  // Vertex shader program

  const vsSource = `
    attribute vec4 aVertexPosition;
    attribute vec4 aVertexColor;

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    varying lowp vec4 vColor;

    void main(void) {
      gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
      vColor = aVertexColor;
    }
  `;

  // Fragment shader program

  const fsSource = `
    varying lowp vec4 vColor;

    void main(void) {
      gl_FragColor = vColor;
    }
  `;

  // Initialize a shader program; this is where all the lighting
  // for the vertices and so forth is established.
  const shaderProgram = initShaderProgram(gl, vsSource, fsSource);

  // Collect all the info needed to use the shader program.
  // Look up which attributes our shader program is using
  // for aVertexPosition, aVevrtexColor and also
  // look up uniform locations.
  const programInfo = {
    program: shaderProgram,
    attribLocations: {
      vertexPosition: gl.getAttribLocation(shaderProgram, "aVertexPosition"),
      vertexColor: gl.getAttribLocation(shaderProgram, "aVertexColor"),
    },
    uniformLocations: {
      projectionMatrix: gl.getUniformLocation(
        shaderProgram,
        "uProjectionMatrix"
      ),
      modelViewMatrix: gl.getUniformLocation(shaderProgram, "uModelViewMatrix"),
    },
  };

  // Here's where we call the routine that builds all the
  // objects we'll be drawing.
  const buffers = initBuffers(gl);

  // Draw the scene
  drawScene(gl, programInfo, buffers);

  // Write to the fields
  data["canvasdata11Webgl" + suffix] = sha1(canvas.toDataURL());

  return data;
}

// ==============================================================
// Fingerprint JS Canvases
function populateFingerprintJSCanvases(contextOptions = {}) {
  const data = {};
  const suffix = contextOptions.forceSoftwareRendering ? "software" : "";

  // fingerprintjs
  // Their fingerprinting code went to the BSL license from MIT in
  // https://github.com/fingerprintjs/fingerprintjs/commit/572fd98f9e4f27b4e854137ea0d53231b3b4eb6e
  // So use the version of the code in the parent commit which is still MIT
  // https://github.com/fingerprintjs/fingerprintjs/blob/aca79b37f7956eee58018e4a317a2bdf8be62d0f/src/sources/canvas.ts

  function renderTextImage(canvas, context) {
    context.textBaseline = "alphabetic";
    context.fillStyle = "#f60";
    context.fillRect(100, 1, 62, 20);

    context.fillStyle = "#069";
    // It's important to use explicit built-in fonts in order to exclude the affect of font preferences
    // (there is a separate entropy source for them).
    context.font = '11pt "Times New Roman"';
    // The choice of emojis has a gigantic impact on rendering performance (especially in FF).
    // Some newer emojis cause it to slow down 50-200 times.
    // There must be no text to the right of the emoji, see https://github.com/fingerprintjs/fingerprintjs/issues/574
    // A bare emoji shouldn't be used because the canvas will change depending on the script encoding:
    // https://github.com/fingerprintjs/fingerprintjs/issues/66
    // Escape sequence shouldn't be used too because Terser will turn it into a bare unicode.
    const printedText = `Cwm fjordbank gly ${
      String.fromCharCode(55357, 56835) /* 😃 */
    }`;
    context.fillText(printedText, 2, 15);
    context.fillStyle = "rgba(102, 204, 0, 0.2)";
    context.font = "18pt Arial";
    context.fillText(printedText, 4, 45);
  }

  function renderGeometryImage(canvas, context) {
    // Canvas blending
    // https://web.archive.org/web/20170826194121/http://blogs.adobe.com/webplatform/2013/01/28/blending-features-in-canvas/
    // http://jsfiddle.net/NDYV8/16/
    context.globalCompositeOperation = "multiply";
    for (const [color, x, y] of [
      ["#f2f", 40, 40],
      ["#2ff", 80, 40],
      ["#ff2", 60, 80],
    ]) {
      context.fillStyle = color;
      context.beginPath();
      context.arc(x, y, 40, 0, Math.PI * 2, true);
      context.closePath();
      context.fill();
    }

    // Canvas winding
    // https://web.archive.org/web/20130913061632/http://blogs.adobe.com/webplatform/2013/01/30/winding-rules-in-canvas/
    // http://jsfiddle.net/NDYV8/19/
    context.fillStyle = "#f9c";
    context.arc(60, 60, 60, 0, Math.PI * 2, true);
    context.arc(60, 60, 20, 0, Math.PI * 2, true);
    context.fill("evenodd");
  }

  const canvas1 = document.getElementById("fingerprintjscanvas1" + suffix);
  const context1 = canvas1.getContext("2d", contextOptions);
  renderTextImage(canvas1, context1);
  data["canvasdata12Fingerprintjs1" + suffix] = sha1(canvas1.toDataURL());

  const canvas2 = document.getElementById("fingerprintjscanvas2" + suffix);
  const context2 = canvas2.getContext("2d", contextOptions);
  renderGeometryImage(canvas2, context2);
  data["canvasdata13Fingerprintjs2" + suffix] = sha1(canvas2.toDataURL());

  return data;
}

// ==============================================================
// Speech Synthesis Voices
async function populateVoiceList() {
  // Replace long prefixes with short ones to reduce the size of the output.
  const uriPrefixes = [
    [/(?:urn:)?moz-tts:.*?:/, "#m:"],
    [/com\.apple\.speech\.synthesis\.voice\./, "#as:"],
    [/com\.apple\.voice\.compact./, "#ac:"],
    [/com\.apple\.eloquence\./, "#ap:"],
    // Populate with more prefixes as needed.
  ];

  function trimVoiceURI(uri) {
    for (const [re, replacement] of uriPrefixes) {
      uri = uri.replace(re, replacement);
    }
    return uri;
  }

  async function stringifyVoices(voices) {
    voices = voices
      .map(voice => ({
        voiceURI: trimVoiceURI(voice.voiceURI),
        default: voice.default,
        localService: voice.localService,
      }))
      .sort((a, b) => a.voiceURI.localeCompare(b.voiceURI));

    const [localServices, nonLocalServices] = voices.reduce(
      (acc, voice) => {
        if (voice.localService) {
          acc[0].push(voice.voiceURI);
        } else {
          acc[1].push(voice.voiceURI);
        }
        return acc;
      },
      [[], []]
    );
    const defaultVoice = voices.find(voice => voice.default);

    voices = voices.map(voice => voice.voiceURI);

    return JSON.stringify({
      count: voices.length,
      localServices: localServices.length,
      defaultVoice: defaultVoice ? defaultVoice.voiceURI : null,
      samples: sample(voices, 5),
      sha1: await sha1(voices.join("|")),
      allHash: ssdeep.digest(voices.join("|")),
      localHash: ssdeep.digest(localServices.join("|")),
      nonLocalHash: ssdeep.digest(nonLocalServices.join("|")),
    });
  }

  function fetchVoices() {
    const promise = new Promise(resolve => {
      speechSynthesis.addEventListener("voiceschanged", function () {
        resolve(speechSynthesis.getVoices());
      });

      if (speechSynthesis.getVoices().length !== 0) {
        resolve(speechSynthesis.getVoices());
      }
    });

    const timeout = new Promise(resolve => {
      setTimeout(() => {
        resolve([]);
      }, 5000);
    });

    return Promise.race([promise, timeout]);
  }

  return {
    voices: fetchVoices().then(stringifyVoices),
  };
}

async function populateMediaCapabilities() {
  // Decoding: MP4 and WEBM are PDM dependant, while the other types are not, so for MP4 and WEBM we manually check for mimetypes.
  // We also don't make an extra check for media-source as both file and media-source end up calling the same code path except for
  // some prefs that block some mime types but we collect them.
  // Encoding: It isn't dependant on hardware, so we just skip it, but collect media.encoder.webm.enabled pref.
  const mimeTypes = [
    // WEBM
    "video/webm; codecs=vp9",
    "video/webm; codecs=vp8",
    "video/webm; codecs=av1",
    // MP4
    "video/mp4; codecs=vp9",
    "video/mp4; codecs=vp8",
    "video/mp4; codecs=hev1.1.0.L30.b0",
    "video/mp4; codecs=avc1.42000A",
  ];

  const videoConfig = {
    type: "file",
    video: {
      width: 1280,
      height: 720,
      bitrate: 10000,
      framerate: 30,
    },
  };

  // Generates a list of h264 codecs, then checks if they are supported.
  // Returns the highest supported level for each profile.
  async function h264CodecsSupported() {
    // Generate hex values for x.0, x.1, x.2 for x in [4, 6]
    const levels = [...Array(3).keys()]
      .map(i => [
        ((i + 4) * 10).toString(16),
        ((i + 4) * 10 + 1).toString(16),
        ((i + 4) * 10 + 2).toString(16),
      ])
      .flat();

    // Contains profiles without levels. They will be added
    // later in the loop.
    const profiles = ["avc1.4200", "avc1.4d00", "avc1.6e00", "avc1.7a00"];

    const supportLevels = {};
    for (const profile of profiles) {
      for (const level of levels) {
        const mimeType = `video/mp4; codecs=${profile}${level}`;
        videoConfig.video.contentType = mimeType;
        const capability = await navigator.mediaCapabilities.decodingInfo(
          videoConfig
        );

        if (capability.supported) {
          supportLevels[profile] = level;
        }
      }
    }

    return supportLevels;
  }

  async function getCapabilities() {
    const capabilities = {
      unsupported: [],
      notSmooth: [],
      notPowerEfficient: [],
      h264: await h264CodecsSupported(),
    };

    for (const mime of mimeTypes) {
      videoConfig.video.contentType = mime;
      const capability = await navigator.mediaCapabilities.decodingInfo(
        videoConfig
      );
      const shortMime = mime.split("=")[1];
      if (!capability.supported) {
        capabilities.unsupported.push(shortMime);
      } else {
        if (!capability.smooth) {
          capabilities.notSmooth.push(shortMime);
        }
        if (!capability.powerEfficient) {
          capabilities.notPowerEfficient.push(shortMime);
        }
      }
    }

    return JSON.stringify(capabilities);
  }

  return {
    mediaCapabilities: getCapabilities(),
  };
}

async function populateAudioFingerprint() {
  // Trimmed down version of https://github.com/fingerprintjs/fingerprintjs/blob/c463ca034747df80d95cc96a0a9c686d8cd001a5/src/sources/audio.ts
  // At that time, fingerprintjs was licensed with MIT.
  const hashFromIndex = 4500;
  const hashToIndex = 5000;
  const context = new window.OfflineAudioContext(1, hashToIndex, 44100);

  const oscillator = context.createOscillator();
  oscillator.type = "triangle";
  oscillator.frequency.value = 10000;

  const compressor = context.createDynamicsCompressor();
  compressor.threshold.value = -50;
  compressor.knee.value = 40;
  compressor.ratio.value = 12;
  compressor.attack.value = 0;
  compressor.release.value = 0.25;

  oscillator.connect(compressor);
  compressor.connect(context.destination);
  oscillator.start(0);

  const [renderPromise, finishRendering] = startRenderingAudio(context);
  const fingerprintPromise = renderPromise.then(
    buffer => getHash(buffer.getChannelData(0).subarray(hashFromIndex)),
    error => {
      if (error === "TIMEOUT" || error.name === "SUSPENDED") {
        return "TIMEOUT";
      }
      throw error;
    }
  );

  /**
   * Starts rendering the audio context.
   * When the returned function is called, the render process starts finishing.
   */
  function startRenderingAudio(context) {
    const renderTryMaxCount = 3;
    const renderRetryDelay = 500;
    const runningMaxAwaitTime = 500;
    const runningSufficientTime = 5000;
    let finalize = () => undefined;

    const resultPromise = new Promise((resolve, reject) => {
      let isFinalized = false;
      let renderTryCount = 0;
      let startedRunningAt = 0;

      context.oncomplete = event => resolve(event.renderedBuffer);

      const startRunningTimeout = () => {
        setTimeout(
          () => reject("TIMEMOUT"),
          Math.min(
            runningMaxAwaitTime,
            startedRunningAt + runningSufficientTime - Date.now()
          )
        );
      };

      const tryRender = () => {
        try {
          context.startRendering();

          switch (context.state) {
            case "running":
              startedRunningAt = Date.now();
              if (isFinalized) {
                startRunningTimeout();
              }
              break;

            // Sometimes the audio context doesn't start after calling `startRendering` (in addition to the cases where
            // audio context doesn't start at all). A known case is starting an audio context when the browser tab is in
            // background on iPhone. Retries usually help in this case.
            case "suspended":
              // The audio context can reject starting until the tab is in foreground. Long fingerprint duration
              // in background isn't a problem, therefore the retry attempts don't count in background. It can lead to
              // a situation when a fingerprint takes very long time and finishes successfully. FYI, the audio context
              // can be suspended when `document.hidden === false` and start running after a retry.
              if (!document.hidden) {
                renderTryCount++;
              }
              if (isFinalized && renderTryCount >= renderTryMaxCount) {
                reject("SUSPENDED");
              } else {
                setTimeout(tryRender, renderRetryDelay);
              }
              break;
          }
        } catch (error) {
          reject(error);
        }
      };

      tryRender();

      finalize = () => {
        if (!isFinalized) {
          isFinalized = true;
          if (startedRunningAt > 0) {
            startRunningTimeout();
          }
        }
      };
    });

    return [resultPromise, finalize];
  }

  function getHash(signal) {
    let hash = 0;
    for (let i = 0; i < signal.length; ++i) {
      hash += Math.abs(signal[i]);
    }
    // 10e13 is the maximum safe number we can use.
    // 10e14 is over Number.MAX_SAFE_INTEGER, techinically it isn't but
    // 35.x * 10e14 is over Number.MAX_SAFE_INTEGER. We are losing one digit
    // of precision but it should hopefully be enough.
    return hash * 10e13;
  }

  finishRendering();

  return {
    audioFingerprint: fingerprintPromise,
  };
}

async function populateCSSQueries() {
  return {
    monochrome: matchMedia("(monochrome)").matches,
  };
}

async function populateNavigatorProperties() {
  return {
    oscpu: navigator.oscpu,
    pdfViewer: navigator.pdfViewerEnabled,
    platform: navigator.platform,
  };
}

async function populatePointerInfo() {
  const capabilities = {
    None: 0,
    Coarse: 1 << 0,
    Fine: 1 << 1,
  };

  const q = {
    isCoarse: matchMedia("(pointer: coarse)").matches,
    isFine: matchMedia("(pointer: fine)").matches,
    isAnyCoarse: matchMedia("(any-pointer: coarse)").matches,
    isAnyFine: matchMedia("(any-pointer: fine)").matches,
  };

  // Pointer media query matches for primary pointer. So, it can be
  // only one of coarse/fine/none.
  let pointerType;
  if (q.isCoarse) {
    pointerType = capabilities.Coarse;
  } else {
    pointerType = q.isFine ? capabilities.Fine : capabilities.None;
  }

  // Any-pointer media query matches for any pointer available. So, it
  // can be both coarse and fine value, be one of them, or none.
  const anyPointerType =
    (q.isAnyCoarse && capabilities.Coarse) | (q.isAnyFine && capabilities.Fine);

  return {
    pointerType,
    anyPointerType,
  };
}

async function populateICEFoundations() {
  // ICE Foundations timeout on CI, so we skip them for automation.
  if (window.location.hash === "#automation") {
    debug("Skipping ICE Foundations for automation");
    return {};
  }

  function getFoundationsAndLatencies() {
    const { promise, resolve, reject } = Promise.withResolvers();

    // With no other peers, we wouldn't get prflx candidates.
    // Relay type of candidates require a turn server.
    // So, we'll only get host and srflx candidates.
    const result = {
      hostLatencies: [],
      hostFoundations: [],
      srflxLatencies: [],
      srflxFoundations: [],
    };

    let lastTime;
    function calculateLatency() {
      const now = window.performance.now();
      const latency = window.performance.now() - lastTime;
      lastTime = now;
      return latency;
    }

    const pc = new RTCPeerConnection({
      iceServers: [{ urls: ["stun:stun.l.google.com:19302"] }],
    });
    pc.onicecandidate = e => {
      const latency = calculateLatency();
      if (e.candidate && e.candidate.candidate !== "") {
        result[e.candidate.type + "Latencies"].push(latency);
        result[e.candidate.type + "Foundations"].push(e.candidate.foundation);
      }
    };
    pc.onicegatheringstatechange = () => {
      if (pc.iceGatheringState !== "complete") {
        return;
      }
      pc.close();
      resolve(result);
    };

    pc.createOffer({ offerToReceiveAudio: 1 })
      .then(desc => {
        pc.setLocalDescription(desc);
        lastTime = window.performance.now();
      })
      .catch(reject);

    return promise;
  }

  // Run get candidates multiple times to see if foundation order changes
  // and calculate standard deviation of latencies
  const allLatencies = {
    srflx: [],
    host: [],
  };
  const allFoundations = {
    srflx: {},
    host: {},
  };
  for (let i = 0; i < 10; i++) {
    const result = await getFoundationsAndLatencies();
    const hostFoundations = result.hostFoundations.join("");
    const srflxFoundations = result.srflxFoundations.join("");

    allLatencies.host.push(result.hostLatencies);
    allLatencies.srflx.push(result.srflxLatencies);

    if (hostFoundations) {
      allFoundations.host[hostFoundations] =
        (allFoundations.host[hostFoundations] ?? 0) + 1;
    }
    if (srflxFoundations) {
      allFoundations.srflx[srflxFoundations] =
        (allFoundations.srflx[srflxFoundations] ?? 0) + 1;
    }
  }

  const sdLatencies = {
    host: [],
    srflx: [],
  };
  for (let i = 0; i < (allLatencies.host?.[0]?.length ?? 0); i++) {
    sdLatencies.host.push(standardDeviation(allLatencies.host.map(a => a[i])));
  }
  for (let i = 0; i < (allLatencies.srflx?.[0]?.length ?? 0); i++) {
    sdLatencies.srflx.push(
      standardDeviation(allLatencies.srflx.map(a => a[i]))
    );
  }

  return {
    iceFoundations: JSON.stringify({
      uniqueHostOrder: Object.keys(allFoundations.host).length,
      uniqueSrflxOrder: Object.keys(allFoundations.srflx).length,
      sdLatencies,
    }),
  };
}

async function populateSensorInfo() {
  const { promise, resolve } = Promise.withResolvers();

  const events = {
    devicemotion: 0,
    deviceorientation: 0,
    deviceorientationabsolute: 0,
  };
  const results = {
    frequency: { ...events },
    decPlaces: { ...events },
  };

  const eventCounter = { ...events };
  const eventDecPlaces = { ...events };
  const eventStarts = { ...events };

  const processEvent = eventName => e => {
    eventCounter[eventName] += 1;

    // Weird behaviour for devicemotion event, probably a bug.
    // First devicemotion event has accelerationIncludingGravity but not acceleration.
    const property =
      e.acceleration?.x || e.alpha || e.accelerationIncludingGravity?.x;
    const decPlaces = decimalPlaces(property);
    eventDecPlaces[eventName] =
      eventDecPlaces[eventName] > decPlaces
        ? eventDecPlaces[eventName]
        : decPlaces;
  };
  const processResult = eventName => {
    const elapsed = (window.performance.now() - eventStarts[eventName]) / 1000;
    results.frequency[eventName] = Math.round(
      eventCounter[eventName] / elapsed
    );
    results.decPlaces[eventName] = eventDecPlaces[eventName];
  };

  for (const eventName in events) {
    eventStarts[eventName] = window.performance.now();
    window.addEventListener(eventName, processEvent(eventName));
    setTimeout(() => processResult(eventName), 10 * 1000);
  }

  // A whole extra second to process results
  setTimeout(
    () =>
      resolve({
        motionDecimals: results.decPlaces.devicemotion,
        orientationDecimals: results.decPlaces.deviceorientation,
        orientationabsDecimals: results.decPlaces.deviceorientationabsolute,
        motionFreq: results.frequency.devicemotion,
        orientationFreq: results.frequency.deviceorientation,
        orientationabsFreq: results.frequency.deviceorientationabsolute,
      }),
    11 * 1000
  );

  return promise;
}

async function populateMathML() {
  // We only collect width of the math elements.
  // FPJS reports that height of elements fluctuates.
  // https://github.com/fingerprintjs/fingerprintjs/blob/143479cba3d4bfd6f2cd773c61c26e8e74a70c06/src/sources/font_preferences.ts#L128-L132
  // We use getBoundingClientRect().width and not offsetWidth as math elements don't have a offsetWidth property.
  const mathElements = [...document.querySelectorAll("math[id]")];

  return mathElements.reduce((acc, el) => {
    // We multiply by 10^15 to include the decimal part.
    acc["mathml" + el.id] = el.getBoundingClientRect().width * 10 ** 15;
    return acc;
  }, {});
}

async function populateAudioDeviceProperties() {
  const ctx = new AudioContext();
  await ctx.resume();

  // Give firefox some time to calculate latency
  await new Promise(resolve => setTimeout(resolve, 2000));

  // All the other properties (min/max decibels, smoothingTimeConstant,
  // fftSize, frequencyBinCount, baseLatency) are hardcoded.
  return {
    audioFrames: ctx.outputLatency * ctx.sampleRate,
    audioRate: ctx.sampleRate,
    audioChannels: ctx.destination.maxChannelCount,
  };
}

// A helper function to generate an array of asynchronous functions to populate
// canvases using both software and hardware rendering.
function getCanvasSources() {
  const canvasSources = [
    populateTestCanvases,
    populateWebGLCanvases,
    populateFingerprintJSCanvases,
  ];

  // Create a source with both software and hardware rendering
  return canvasSources
    .map(source => {
      const functions = [
        async () => source({ forceSoftwareRendering: true }),
        async () => source({ forceSoftwareRendering: false }),
      ];

      // Using () => {} renames the function, so we rename them again.
      // This is needed for error collection.
      Object.defineProperty(functions[0], "name", {
        value: source.name + "Software",
      });
      Object.defineProperty(functions[1], "name", {
        value: source.name,
      });
      return functions;
    })
    .flat();
}

// =======================================================================
// Setup & Populating

/* Pick any local font, we just don't want to needlessly increase binary size */
const LocalFiraSans = new FontFace(
  "LocalFiraSans",
  "url('chrome://pocket/content/panels/fonts/FiraSans-Regular.woff') format('woff')"
);

if (document.readyState === "loading") {
  window.addEventListener("load", startPopulating);
} else {
  startPopulating();
}

async function startPopulating() {
  const errors = [];

  await LocalFiraSans.load()
    .then(font => document.fonts.add(font))
    .catch(async e => {
      // Fail silently
      errors.push(`LocalFiraSans: ${await stringifyError(e)}`);
    });

  // Data contains key: (Promise<any> | any) pairs. The keys are identifiers
  // for the data and the values are either a promise that returns a value,
  // or a value. Promises are awaited and values are resolved immediately.
  const data = {};
  const sources = [
    ...getCanvasSources(),
    populateVoiceList,
    populateMediaCapabilities,
    populateAudioFingerprint,
    populatePointerInfo,
    populateICEFoundations,
    populateSensorInfo,
    populateMathML,
    populateCSSQueries,
    populateNavigatorProperties,
    populateAudioDeviceProperties,
  ];
  // Catches errors in promise-creating functions. E.g. if populateVoiceList
  // throws an error before returning any of its `key: (Promise<any> | any)`
  // pairs, we catch it here. This also catches non-async function errors
  for (const source of sources) {
    try {
      Object.assign(data, await timeoutPromise(source(), 5 * 60 * 1000));
    } catch (error) {
      errors.push(`${source.name}: ${await stringifyError(error)}`);
    }
  }

  debug("Awaiting", Object.keys(data).length, "data promises.");
  await Promise.allSettled(Object.values(data));

  debug("Sizes of extractions:");
  const output = {};
  for (const key in data) {
    try {
      output[key] = await data[key];
      debug(key, output[key].length);
    } catch (e) {
      debug("Promise rejected for", key, "Error:", e);
      errors.push(`${key}: ${await stringifyError(e)}`);
    }
  }
  output.jsErrors = JSON.stringify(errors);

  document.dispatchEvent(
    new CustomEvent("UserCharacteristicsDataDone", {
      bubbles: true,
      detail: {
        debug: debugMsgs,
        output,
      },
    })
  );
}
PK
!<W7V����0chrome/toolkit/content/global/vendor/lit.all.mjs/**
 * @license
 * Copyright 2019 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
const NODE_MODE$1 = false;
const global$2 = window;
/**
 * Whether the current browser supports `adoptedStyleSheets`.
 */
const supportsAdoptingStyleSheets = global$2.ShadowRoot &&
    (global$2.ShadyCSS === undefined || global$2.ShadyCSS.nativeShadow) &&
    'adoptedStyleSheets' in Document.prototype &&
    'replace' in CSSStyleSheet.prototype;
const constructionToken = Symbol();
const cssTagCache = new WeakMap();
/**
 * A container for a string of CSS text, that may be used to create a CSSStyleSheet.
 *
 * CSSResult is the return value of `css`-tagged template literals and
 * `unsafeCSS()`. In order to ensure that CSSResults are only created via the
 * `css` tag and `unsafeCSS()`, CSSResult cannot be constructed directly.
 */
class CSSResult {
    constructor(cssText, strings, safeToken) {
        // This property needs to remain unminified.
        this['_$cssResult$'] = true;
        if (safeToken !== constructionToken) {
            throw new Error('CSSResult is not constructable. Use `unsafeCSS` or `css` instead.');
        }
        this.cssText = cssText;
        this._strings = strings;
    }
    // This is a getter so that it's lazy. In practice, this means stylesheets
    // are not created until the first element instance is made.
    get styleSheet() {
        // If `supportsAdoptingStyleSheets` is true then we assume CSSStyleSheet is
        // constructable.
        let styleSheet = this._styleSheet;
        const strings = this._strings;
        if (supportsAdoptingStyleSheets && styleSheet === undefined) {
            const cacheable = strings !== undefined && strings.length === 1;
            if (cacheable) {
                styleSheet = cssTagCache.get(strings);
            }
            if (styleSheet === undefined) {
                (this._styleSheet = styleSheet = new CSSStyleSheet()).replaceSync(this.cssText);
                if (cacheable) {
                    cssTagCache.set(strings, styleSheet);
                }
            }
        }
        return styleSheet;
    }
    toString() {
        return this.cssText;
    }
}
const textFromCSSResult = (value) => {
    // This property needs to remain unminified.
    if (value['_$cssResult$'] === true) {
        return value.cssText;
    }
    else if (typeof value === 'number') {
        return value;
    }
    else {
        throw new Error(`Value passed to 'css' function must be a 'css' function result: ` +
            `${value}. Use 'unsafeCSS' to pass non-literal values, but take care ` +
            `to ensure page security.`);
    }
};
/**
 * Wrap a value for interpolation in a {@linkcode css} tagged template literal.
 *
 * This is unsafe because untrusted CSS text can be used to phone home
 * or exfiltrate data to an attacker controlled site. Take care to only use
 * this with trusted input.
 */
const unsafeCSS = (value) => new CSSResult(typeof value === 'string' ? value : String(value), undefined, constructionToken);
/**
 * A template literal tag which can be used with LitElement's
 * {@linkcode LitElement.styles} property to set element styles.
 *
 * For security reasons, only literal string values and number may be used in
 * embedded expressions. To incorporate non-literal values {@linkcode unsafeCSS}
 * may be used inside an expression.
 */
const css = (strings, ...values) => {
    const cssText = strings.length === 1
        ? strings[0]
        : values.reduce((acc, v, idx) => acc + textFromCSSResult(v) + strings[idx + 1], strings[0]);
    return new CSSResult(cssText, strings, constructionToken);
};
/**
 * Applies the given styles to a `shadowRoot`. When Shadow DOM is
 * available but `adoptedStyleSheets` is not, styles are appended to the
 * `shadowRoot` to [mimic spec behavior](https://wicg.github.io/construct-stylesheets/#using-constructed-stylesheets).
 * Note, when shimming is used, any styles that are subsequently placed into
 * the shadowRoot should be placed *before* any shimmed adopted styles. This
 * will match spec behavior that gives adopted sheets precedence over styles in
 * shadowRoot.
 */
const adoptStyles = (renderRoot, styles) => {
    if (supportsAdoptingStyleSheets) {
        renderRoot.adoptedStyleSheets = styles.map((s) => s instanceof CSSStyleSheet ? s : s.styleSheet);
    }
    else {
        styles.forEach((s) => {
            const style = document.createElement('style');
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            const nonce = global$2['litNonce'];
            if (nonce !== undefined) {
                style.setAttribute('nonce', nonce);
            }
            style.textContent = s.cssText;
            renderRoot.appendChild(style);
        });
    }
};
const cssResultFromStyleSheet = (sheet) => {
    let cssText = '';
    for (const rule of sheet.cssRules) {
        cssText += rule.cssText;
    }
    return unsafeCSS(cssText);
};
const getCompatibleStyle = supportsAdoptingStyleSheets ||
    (NODE_MODE$1 )
    ? (s) => s
    : (s) => s instanceof CSSStyleSheet ? cssResultFromStyleSheet(s) : s;

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
var _d$1;
var _e;
const global$1 = window;
const trustedTypes$1 = global$1
    .trustedTypes;
// Temporary workaround for https://crbug.com/993268
// Currently, any attribute starting with "on" is considered to be a
// TrustedScript source. Such boolean attributes must be set to the equivalent
// trusted emptyScript value.
const emptyStringForBooleanAttribute$1 = trustedTypes$1
    ? trustedTypes$1.emptyScript
    : '';
const polyfillSupport$2 = global$1.reactiveElementPolyfillSupport;
/*
 * When using Closure Compiler, JSCompiler_renameProperty(property, object) is
 * replaced at compile time by the munged name for object[property]. We cannot
 * alias this function, so we have to use a small shim that has the same
 * behavior when not compiling.
 */
/*@__INLINE__*/
const JSCompiler_renameProperty = (prop, _obj) => prop;
const defaultConverter = {
    toAttribute(value, type) {
        switch (type) {
            case Boolean:
                value = value ? emptyStringForBooleanAttribute$1 : null;
                break;
            case Object:
            case Array:
                // if the value is `null` or `undefined` pass this through
                // to allow removing/no change behavior.
                value = value == null ? value : JSON.stringify(value);
                break;
        }
        return value;
    },
    fromAttribute(value, type) {
        let fromValue = value;
        switch (type) {
            case Boolean:
                fromValue = value !== null;
                break;
            case Number:
                fromValue = value === null ? null : Number(value);
                break;
            case Object:
            case Array:
                // Do *not* generate exception when invalid JSON is set as elements
                // don't normally complain on being mis-configured.
                // TODO(sorvell): Do generate exception in *dev mode*.
                try {
                    // Assert to adhere to Bazel's "must type assert JSON parse" rule.
                    fromValue = JSON.parse(value);
                }
                catch (e) {
                    fromValue = null;
                }
                break;
        }
        return fromValue;
    },
};
/**
 * Change function that returns true if `value` is different from `oldValue`.
 * This method is used as the default for a property's `hasChanged` function.
 */
const notEqual = (value, old) => {
    // This ensures (old==NaN, value==NaN) always returns false
    return old !== value && (old === old || value === value);
};
const defaultPropertyDeclaration = {
    attribute: true,
    type: String,
    converter: defaultConverter,
    reflect: false,
    hasChanged: notEqual,
};
/**
 * The Closure JS Compiler doesn't currently have good support for static
 * property semantics where "this" is dynamic (e.g.
 * https://github.com/google/closure-compiler/issues/3177 and others) so we use
 * this hack to bypass any rewriting by the compiler.
 */
const finalized = 'finalized';
/**
 * Base element class which manages element properties and attributes. When
 * properties change, the `update` method is asynchronously called. This method
 * should be supplied by subclassers to render updates as desired.
 * @noInheritDoc
 */
class ReactiveElement extends HTMLElement {
    constructor() {
        super();
        this.__instanceProperties = new Map();
        /**
         * True if there is a pending update as a result of calling `requestUpdate()`.
         * Should only be read.
         * @category updates
         */
        this.isUpdatePending = false;
        /**
         * Is set to `true` after the first update. The element code cannot assume
         * that `renderRoot` exists before the element `hasUpdated`.
         * @category updates
         */
        this.hasUpdated = false;
        /**
         * Name of currently reflecting property
         */
        this.__reflectingProperty = null;
        this._initialize();
    }
    /**
     * Adds an initializer function to the class that is called during instance
     * construction.
     *
     * This is useful for code that runs against a `ReactiveElement`
     * subclass, such as a decorator, that needs to do work for each
     * instance, such as setting up a `ReactiveController`.
     *
     * ```ts
     * const myDecorator = (target: typeof ReactiveElement, key: string) => {
     *   target.addInitializer((instance: ReactiveElement) => {
     *     // This is run during construction of the element
     *     new MyController(instance);
     *   });
     * }
     * ```
     *
     * Decorating a field will then cause each instance to run an initializer
     * that adds a controller:
     *
     * ```ts
     * class MyElement extends LitElement {
     *   @myDecorator foo;
     * }
     * ```
     *
     * Initializers are stored per-constructor. Adding an initializer to a
     * subclass does not add it to a superclass. Since initializers are run in
     * constructors, initializers will run in order of the class hierarchy,
     * starting with superclasses and progressing to the instance's class.
     *
     * @nocollapse
     */
    static addInitializer(initializer) {
        var _a;
        this.finalize();
        ((_a = this._initializers) !== null && _a !== void 0 ? _a : (this._initializers = [])).push(initializer);
    }
    /**
     * Returns a list of attributes corresponding to the registered properties.
     * @nocollapse
     * @category attributes
     */
    static get observedAttributes() {
        // note: piggy backing on this to ensure we're finalized.
        this.finalize();
        const attributes = [];
        // Use forEach so this works even if for/of loops are compiled to for loops
        // expecting arrays
        this.elementProperties.forEach((v, p) => {
            const attr = this.__attributeNameForProperty(p, v);
            if (attr !== undefined) {
                this.__attributeToPropertyMap.set(attr, p);
                attributes.push(attr);
            }
        });
        return attributes;
    }
    /**
     * Creates a property accessor on the element prototype if one does not exist
     * and stores a {@linkcode PropertyDeclaration} for the property with the
     * given options. The property setter calls the property's `hasChanged`
     * property option or uses a strict identity check to determine whether or not
     * to request an update.
     *
     * This method may be overridden to customize properties; however,
     * when doing so, it's important to call `super.createProperty` to ensure
     * the property is setup correctly. This method calls
     * `getPropertyDescriptor` internally to get a descriptor to install.
     * To customize what properties do when they are get or set, override
     * `getPropertyDescriptor`. To customize the options for a property,
     * implement `createProperty` like this:
     *
     * ```ts
     * static createProperty(name, options) {
     *   options = Object.assign(options, {myOption: true});
     *   super.createProperty(name, options);
     * }
     * ```
     *
     * @nocollapse
     * @category properties
     */
    static createProperty(name, options = defaultPropertyDeclaration) {
        // if this is a state property, force the attribute to false.
        if (options.state) {
            // Cast as any since this is readonly.
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            options.attribute = false;
        }
        // Note, since this can be called by the `@property` decorator which
        // is called before `finalize`, we ensure finalization has been kicked off.
        this.finalize();
        this.elementProperties.set(name, options);
        // Do not generate an accessor if the prototype already has one, since
        // it would be lost otherwise and that would never be the user's intention;
        // Instead, we expect users to call `requestUpdate` themselves from
        // user-defined accessors. Note that if the super has an accessor we will
        // still overwrite it
        if (!options.noAccessor && !this.prototype.hasOwnProperty(name)) {
            const key = typeof name === 'symbol' ? Symbol() : `__${name}`;
            const descriptor = this.getPropertyDescriptor(name, key, options);
            if (descriptor !== undefined) {
                Object.defineProperty(this.prototype, name, descriptor);
            }
        }
    }
    /**
     * Returns a property descriptor to be defined on the given named property.
     * If no descriptor is returned, the property will not become an accessor.
     * For example,
     *
     * ```ts
     * class MyElement extends LitElement {
     *   static getPropertyDescriptor(name, key, options) {
     *     const defaultDescriptor =
     *         super.getPropertyDescriptor(name, key, options);
     *     const setter = defaultDescriptor.set;
     *     return {
     *       get: defaultDescriptor.get,
     *       set(value) {
     *         setter.call(this, value);
     *         // custom action.
     *       },
     *       configurable: true,
     *       enumerable: true
     *     }
     *   }
     * }
     * ```
     *
     * @nocollapse
     * @category properties
     */
    static getPropertyDescriptor(name, key, options) {
        return {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            get() {
                return this[key];
            },
            set(value) {
                const oldValue = this[name];
                this[key] = value;
                this.requestUpdate(name, oldValue, options);
            },
            configurable: true,
            enumerable: true,
        };
    }
    /**
     * Returns the property options associated with the given property.
     * These options are defined with a `PropertyDeclaration` via the `properties`
     * object or the `@property` decorator and are registered in
     * `createProperty(...)`.
     *
     * Note, this method should be considered "final" and not overridden. To
     * customize the options for a given property, override
     * {@linkcode createProperty}.
     *
     * @nocollapse
     * @final
     * @category properties
     */
    static getPropertyOptions(name) {
        return this.elementProperties.get(name) || defaultPropertyDeclaration;
    }
    /**
     * Creates property accessors for registered properties, sets up element
     * styling, and ensures any superclasses are also finalized. Returns true if
     * the element was finalized.
     * @nocollapse
     */
    static finalize() {
        if (this.hasOwnProperty(finalized)) {
            return false;
        }
        this[finalized] = true;
        // finalize any superclasses
        const superCtor = Object.getPrototypeOf(this);
        superCtor.finalize();
        // Create own set of initializers for this class if any exist on the
        // superclass and copy them down. Note, for a small perf boost, avoid
        // creating initializers unless needed.
        if (superCtor._initializers !== undefined) {
            this._initializers = [...superCtor._initializers];
        }
        this.elementProperties = new Map(superCtor.elementProperties);
        // initialize Map populated in observedAttributes
        this.__attributeToPropertyMap = new Map();
        // make any properties
        // Note, only process "own" properties since this element will inherit
        // any properties defined on the superClass, and finalization ensures
        // the entire prototype chain is finalized.
        if (this.hasOwnProperty(JSCompiler_renameProperty('properties'))) {
            const props = this.properties;
            // support symbols in properties (IE11 does not support this)
            const propKeys = [
                ...Object.getOwnPropertyNames(props),
                ...Object.getOwnPropertySymbols(props),
            ];
            // This for/of is ok because propKeys is an array
            for (const p of propKeys) {
                // note, use of `any` is due to TypeScript lack of support for symbol in
                // index types
                // eslint-disable-next-line @typescript-eslint/no-explicit-any
                this.createProperty(p, props[p]);
            }
        }
        this.elementStyles = this.finalizeStyles(this.styles);
        return true;
    }
    /**
     * Takes the styles the user supplied via the `static styles` property and
     * returns the array of styles to apply to the element.
     * Override this method to integrate into a style management system.
     *
     * Styles are deduplicated preserving the _last_ instance in the list. This
     * is a performance optimization to avoid duplicated styles that can occur
     * especially when composing via subclassing. The last item is kept to try
     * to preserve the cascade order with the assumption that it's most important
     * that last added styles override previous styles.
     *
     * @nocollapse
     * @category styles
     */
    static finalizeStyles(styles) {
        const elementStyles = [];
        if (Array.isArray(styles)) {
            // Dedupe the flattened array in reverse order to preserve the last items.
            // Casting to Array<unknown> works around TS error that
            // appears to come from trying to flatten a type CSSResultArray.
            const set = new Set(styles.flat(Infinity).reverse());
            // Then preserve original order by adding the set items in reverse order.
            for (const s of set) {
                elementStyles.unshift(getCompatibleStyle(s));
            }
        }
        else if (styles !== undefined) {
            elementStyles.push(getCompatibleStyle(styles));
        }
        return elementStyles;
    }
    /**
     * Returns the property name for the given attribute `name`.
     * @nocollapse
     */
    static __attributeNameForProperty(name, options) {
        const attribute = options.attribute;
        return attribute === false
            ? undefined
            : typeof attribute === 'string'
                ? attribute
                : typeof name === 'string'
                    ? name.toLowerCase()
                    : undefined;
    }
    /**
     * Internal only override point for customizing work done when elements
     * are constructed.
     *
     * @internal
     */
    _initialize() {
        var _a;
        this.__updatePromise = new Promise((res) => (this.enableUpdating = res));
        this._$changedProperties = new Map();
        this.__saveInstanceProperties();
        // ensures first update will be caught by an early access of
        // `updateComplete`
        this.requestUpdate();
        (_a = this.constructor._initializers) === null || _a === void 0 ? void 0 : _a.forEach((i) => i(this));
    }
    /**
     * Registers a `ReactiveController` to participate in the element's reactive
     * update cycle. The element automatically calls into any registered
     * controllers during its lifecycle callbacks.
     *
     * If the element is connected when `addController()` is called, the
     * controller's `hostConnected()` callback will be immediately called.
     * @category controllers
     */
    addController(controller) {
        var _a, _b;
        ((_a = this.__controllers) !== null && _a !== void 0 ? _a : (this.__controllers = [])).push(controller);
        // If a controller is added after the element has been connected,
        // call hostConnected. Note, re-using existence of `renderRoot` here
        // (which is set in connectedCallback) to avoid the need to track a
        // first connected state.
        if (this.renderRoot !== undefined && this.isConnected) {
            (_b = controller.hostConnected) === null || _b === void 0 ? void 0 : _b.call(controller);
        }
    }
    /**
     * Removes a `ReactiveController` from the element.
     * @category controllers
     */
    removeController(controller) {
        var _a;
        // Note, if the indexOf is -1, the >>> will flip the sign which makes the
        // splice do nothing.
        (_a = this.__controllers) === null || _a === void 0 ? void 0 : _a.splice(this.__controllers.indexOf(controller) >>> 0, 1);
    }
    /**
     * Fixes any properties set on the instance before upgrade time.
     * Otherwise these would shadow the accessor and break these properties.
     * The properties are stored in a Map which is played back after the
     * constructor runs. Note, on very old versions of Safari (<=9) or Chrome
     * (<=41), properties created for native platform properties like (`id` or
     * `name`) may not have default values set in the element constructor. On
     * these browsers native properties appear on instances and therefore their
     * default value will overwrite any element default (e.g. if the element sets
     * this.id = 'id' in the constructor, the 'id' will become '' since this is
     * the native platform default).
     */
    __saveInstanceProperties() {
        // Use forEach so this works even if for/of loops are compiled to for loops
        // expecting arrays
        this.constructor.elementProperties.forEach((_v, p) => {
            if (this.hasOwnProperty(p)) {
                this.__instanceProperties.set(p, this[p]);
                delete this[p];
            }
        });
    }
    /**
     * Returns the node into which the element should render and by default
     * creates and returns an open shadowRoot. Implement to customize where the
     * element's DOM is rendered. For example, to render into the element's
     * childNodes, return `this`.
     *
     * @return Returns a node into which to render.
     * @category rendering
     */
    createRenderRoot() {
        var _a;
        const renderRoot = (_a = this.shadowRoot) !== null && _a !== void 0 ? _a : this.attachShadow(this.constructor.shadowRootOptions);
        adoptStyles(renderRoot, this.constructor.elementStyles);
        return renderRoot;
    }
    /**
     * On first connection, creates the element's renderRoot, sets up
     * element styling, and enables updating.
     * @category lifecycle
     */
    connectedCallback() {
        var _a;
        // create renderRoot before first update.
        if (this.renderRoot === undefined) {
            this.renderRoot = this.createRenderRoot();
        }
        this.enableUpdating(true);
        (_a = this.__controllers) === null || _a === void 0 ? void 0 : _a.forEach((c) => { var _a; return (_a = c.hostConnected) === null || _a === void 0 ? void 0 : _a.call(c); });
    }
    /**
     * Note, this method should be considered final and not overridden. It is
     * overridden on the element instance with a function that triggers the first
     * update.
     * @category updates
     */
    enableUpdating(_requestedUpdate) { }
    /**
     * Allows for `super.disconnectedCallback()` in extensions while
     * reserving the possibility of making non-breaking feature additions
     * when disconnecting at some point in the future.
     * @category lifecycle
     */
    disconnectedCallback() {
        var _a;
        (_a = this.__controllers) === null || _a === void 0 ? void 0 : _a.forEach((c) => { var _a; return (_a = c.hostDisconnected) === null || _a === void 0 ? void 0 : _a.call(c); });
    }
    /**
     * Synchronizes property values when attributes change.
     *
     * Specifically, when an attribute is set, the corresponding property is set.
     * You should rarely need to implement this callback. If this method is
     * overridden, `super.attributeChangedCallback(name, _old, value)` must be
     * called.
     *
     * See [using the lifecycle callbacks](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#using_the_lifecycle_callbacks)
     * on MDN for more information about the `attributeChangedCallback`.
     * @category attributes
     */
    attributeChangedCallback(name, _old, value) {
        this._$attributeToProperty(name, value);
    }
    __propertyToAttribute(name, value, options = defaultPropertyDeclaration) {
        var _a;
        const attr = this.constructor.__attributeNameForProperty(name, options);
        if (attr !== undefined && options.reflect === true) {
            const converter = ((_a = options.converter) === null || _a === void 0 ? void 0 : _a.toAttribute) !==
                undefined
                ? options.converter
                : defaultConverter;
            const attrValue = converter.toAttribute(value, options.type);
            // Track if the property is being reflected to avoid
            // setting the property again via `attributeChangedCallback`. Note:
            // 1. this takes advantage of the fact that the callback is synchronous.
            // 2. will behave incorrectly if multiple attributes are in the reaction
            // stack at time of calling. However, since we process attributes
            // in `update` this should not be possible (or an extreme corner case
            // that we'd like to discover).
            // mark state reflecting
            this.__reflectingProperty = name;
            if (attrValue == null) {
                this.removeAttribute(attr);
            }
            else {
                this.setAttribute(attr, attrValue);
            }
            // mark state not reflecting
            this.__reflectingProperty = null;
        }
    }
    /** @internal */
    _$attributeToProperty(name, value) {
        var _a;
        const ctor = this.constructor;
        // Note, hint this as an `AttributeMap` so closure clearly understands
        // the type; it has issues with tracking types through statics
        const propName = ctor.__attributeToPropertyMap.get(name);
        // Use tracking info to avoid reflecting a property value to an attribute
        // if it was just set because the attribute changed.
        if (propName !== undefined && this.__reflectingProperty !== propName) {
            const options = ctor.getPropertyOptions(propName);
            const converter = typeof options.converter === 'function'
                ? { fromAttribute: options.converter }
                : ((_a = options.converter) === null || _a === void 0 ? void 0 : _a.fromAttribute) !== undefined
                    ? options.converter
                    : defaultConverter;
            // mark state reflecting
            this.__reflectingProperty = propName;
            this[propName] = converter.fromAttribute(value, options.type
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            );
            // mark state not reflecting
            this.__reflectingProperty = null;
        }
    }
    /**
     * Requests an update which is processed asynchronously. This should be called
     * when an element should update based on some state not triggered by setting
     * a reactive property. In this case, pass no arguments. It should also be
     * called when manually implementing a property setter. In this case, pass the
     * property `name` and `oldValue` to ensure that any configured property
     * options are honored.
     *
     * @param name name of requesting property
     * @param oldValue old value of requesting property
     * @param options property options to use instead of the previously
     *     configured options
     * @category updates
     */
    requestUpdate(name, oldValue, options) {
        let shouldRequestUpdate = true;
        // If we have a property key, perform property update steps.
        if (name !== undefined) {
            options =
                options ||
                    this.constructor.getPropertyOptions(name);
            const hasChanged = options.hasChanged || notEqual;
            if (hasChanged(this[name], oldValue)) {
                if (!this._$changedProperties.has(name)) {
                    this._$changedProperties.set(name, oldValue);
                }
                // Add to reflecting properties set.
                // Note, it's important that every change has a chance to add the
                // property to `_reflectingProperties`. This ensures setting
                // attribute + property reflects correctly.
                if (options.reflect === true && this.__reflectingProperty !== name) {
                    if (this.__reflectingProperties === undefined) {
                        this.__reflectingProperties = new Map();
                    }
                    this.__reflectingProperties.set(name, options);
                }
            }
            else {
                // Abort the request if the property should not be considered changed.
                shouldRequestUpdate = false;
            }
        }
        if (!this.isUpdatePending && shouldRequestUpdate) {
            this.__updatePromise = this.__enqueueUpdate();
        }
        // Note, since this no longer returns a promise, in dev mode we return a
        // thenable which warns if it's called.
        return undefined;
    }
    /**
     * Sets up the element to asynchronously update.
     */
    async __enqueueUpdate() {
        this.isUpdatePending = true;
        try {
            // Ensure any previous update has resolved before updating.
            // This `await` also ensures that property changes are batched.
            await this.__updatePromise;
        }
        catch (e) {
            // Refire any previous errors async so they do not disrupt the update
            // cycle. Errors are refired so developers have a chance to observe
            // them, and this can be done by implementing
            // `window.onunhandledrejection`.
            Promise.reject(e);
        }
        const result = this.scheduleUpdate();
        // If `scheduleUpdate` returns a Promise, we await it. This is done to
        // enable coordinating updates with a scheduler. Note, the result is
        // checked to avoid delaying an additional microtask unless we need to.
        if (result != null) {
            await result;
        }
        return !this.isUpdatePending;
    }
    /**
     * Schedules an element update. You can override this method to change the
     * timing of updates by returning a Promise. The update will await the
     * returned Promise, and you should resolve the Promise to allow the update
     * to proceed. If this method is overridden, `super.scheduleUpdate()`
     * must be called.
     *
     * For instance, to schedule updates to occur just before the next frame:
     *
     * ```ts
     * override protected async scheduleUpdate(): Promise<unknown> {
     *   await new Promise((resolve) => requestAnimationFrame(() => resolve()));
     *   super.scheduleUpdate();
     * }
     * ```
     * @category updates
     */
    scheduleUpdate() {
        return this.performUpdate();
    }
    /**
     * Performs an element update. Note, if an exception is thrown during the
     * update, `firstUpdated` and `updated` will not be called.
     *
     * Call `performUpdate()` to immediately process a pending update. This should
     * generally not be needed, but it can be done in rare cases when you need to
     * update synchronously.
     *
     * Note: To ensure `performUpdate()` synchronously completes a pending update,
     * it should not be overridden. In LitElement 2.x it was suggested to override
     * `performUpdate()` to also customizing update scheduling. Instead, you should now
     * override `scheduleUpdate()`. For backwards compatibility with LitElement 2.x,
     * scheduling updates via `performUpdate()` continues to work, but will make
     * also calling `performUpdate()` to synchronously process updates difficult.
     *
     * @category updates
     */
    performUpdate() {
        var _b;
        // Abort any update if one is not pending when this is called.
        // This can happen if `performUpdate` is called early to "flush"
        // the update.
        if (!this.isUpdatePending) {
            return;
        }
        // create renderRoot before first update.
        if (!this.hasUpdated) ;
        // Mixin instance properties once, if they exist.
        if (this.__instanceProperties) {
            // Use forEach so this works even if for/of loops are compiled to for loops
            // expecting arrays
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            this.__instanceProperties.forEach((v, p) => (this[p] = v));
            this.__instanceProperties = undefined;
        }
        let shouldUpdate = false;
        const changedProperties = this._$changedProperties;
        try {
            shouldUpdate = this.shouldUpdate(changedProperties);
            if (shouldUpdate) {
                this.willUpdate(changedProperties);
                (_b = this.__controllers) === null || _b === void 0 ? void 0 : _b.forEach((c) => { var _a; return (_a = c.hostUpdate) === null || _a === void 0 ? void 0 : _a.call(c); });
                this.update(changedProperties);
            }
            else {
                this.__markUpdated();
            }
        }
        catch (e) {
            // Prevent `firstUpdated` and `updated` from running when there's an
            // update exception.
            shouldUpdate = false;
            // Ensure element can accept additional updates after an exception.
            this.__markUpdated();
            throw e;
        }
        // The update is no longer considered pending and further updates are now allowed.
        if (shouldUpdate) {
            this._$didUpdate(changedProperties);
        }
    }
    /**
     * Invoked before `update()` to compute values needed during the update.
     *
     * Implement `willUpdate` to compute property values that depend on other
     * properties and are used in the rest of the update process.
     *
     * ```ts
     * willUpdate(changedProperties) {
     *   // only need to check changed properties for an expensive computation.
     *   if (changedProperties.has('firstName') || changedProperties.has('lastName')) {
     *     this.sha = computeSHA(`${this.firstName} ${this.lastName}`);
     *   }
     * }
     *
     * render() {
     *   return html`SHA: ${this.sha}`;
     * }
     * ```
     *
     * @category updates
     */
    willUpdate(_changedProperties) { }
    // Note, this is an override point for polyfill-support.
    // @internal
    _$didUpdate(changedProperties) {
        var _a;
        (_a = this.__controllers) === null || _a === void 0 ? void 0 : _a.forEach((c) => { var _a; return (_a = c.hostUpdated) === null || _a === void 0 ? void 0 : _a.call(c); });
        if (!this.hasUpdated) {
            this.hasUpdated = true;
            this.firstUpdated(changedProperties);
        }
        this.updated(changedProperties);
    }
    __markUpdated() {
        this._$changedProperties = new Map();
        this.isUpdatePending = false;
    }
    /**
     * Returns a Promise that resolves when the element has completed updating.
     * The Promise value is a boolean that is `true` if the element completed the
     * update without triggering another update. The Promise result is `false` if
     * a property was set inside `updated()`. If the Promise is rejected, an
     * exception was thrown during the update.
     *
     * To await additional asynchronous work, override the `getUpdateComplete`
     * method. For example, it is sometimes useful to await a rendered element
     * before fulfilling this Promise. To do this, first await
     * `super.getUpdateComplete()`, then any subsequent state.
     *
     * @return A promise of a boolean that resolves to true if the update completed
     *     without triggering another update.
     * @category updates
     */
    get updateComplete() {
        return this.getUpdateComplete();
    }
    /**
     * Override point for the `updateComplete` promise.
     *
     * It is not safe to override the `updateComplete` getter directly due to a
     * limitation in TypeScript which means it is not possible to call a
     * superclass getter (e.g. `super.updateComplete.then(...)`) when the target
     * language is ES5 (https://github.com/microsoft/TypeScript/issues/338).
     * This method should be overridden instead. For example:
     *
     * ```ts
     * class MyElement extends LitElement {
     *   override async getUpdateComplete() {
     *     const result = await super.getUpdateComplete();
     *     await this._myChild.updateComplete;
     *     return result;
     *   }
     * }
     * ```
     *
     * @return A promise of a boolean that resolves to true if the update completed
     *     without triggering another update.
     * @category updates
     */
    getUpdateComplete() {
        return this.__updatePromise;
    }
    /**
     * Controls whether or not `update()` should be called when the element requests
     * an update. By default, this method always returns `true`, but this can be
     * customized to control when to update.
     *
     * @param _changedProperties Map of changed properties with old values
     * @category updates
     */
    shouldUpdate(_changedProperties) {
        return true;
    }
    /**
     * Updates the element. This method reflects property values to attributes.
     * It can be overridden to render and keep updated element DOM.
     * Setting properties inside this method will *not* trigger
     * another update.
     *
     * @param _changedProperties Map of changed properties with old values
     * @category updates
     */
    update(_changedProperties) {
        if (this.__reflectingProperties !== undefined) {
            // Use forEach so this works even if for/of loops are compiled to for
            // loops expecting arrays
            this.__reflectingProperties.forEach((v, k) => this.__propertyToAttribute(k, this[k], v));
            this.__reflectingProperties = undefined;
        }
        this.__markUpdated();
    }
    /**
     * Invoked whenever the element is updated. Implement to perform
     * post-updating tasks via DOM APIs, for example, focusing an element.
     *
     * Setting properties inside this method will trigger the element to update
     * again after this update cycle completes.
     *
     * @param _changedProperties Map of changed properties with old values
     * @category updates
     */
    updated(_changedProperties) { }
    /**
     * Invoked when the element is first updated. Implement to perform one time
     * work on the element after update.
     *
     * ```ts
     * firstUpdated() {
     *   this.renderRoot.getElementById('my-text-area').focus();
     * }
     * ```
     *
     * Setting properties inside this method will trigger the element to update
     * again after this update cycle completes.
     *
     * @param _changedProperties Map of changed properties with old values
     * @category updates
     */
    firstUpdated(_changedProperties) { }
}
_e = finalized;
/**
 * Marks class as having finished creating properties.
 */
ReactiveElement[_e] = true;
/**
 * Memoized list of all element properties, including any superclass properties.
 * Created lazily on user subclasses when finalizing the class.
 * @nocollapse
 * @category properties
 */
ReactiveElement.elementProperties = new Map();
/**
 * Memoized list of all element styles.
 * Created lazily on user subclasses when finalizing the class.
 * @nocollapse
 * @category styles
 */
ReactiveElement.elementStyles = [];
/**
 * Options used when calling `attachShadow`. Set this property to customize
 * the options for the shadowRoot; for example, to create a closed
 * shadowRoot: `{mode: 'closed'}`.
 *
 * Note, these options are used in `createRenderRoot`. If this method
 * is customized, options should be respected if possible.
 * @nocollapse
 * @category rendering
 */
ReactiveElement.shadowRootOptions = { mode: 'open' };
// Apply polyfills if available
polyfillSupport$2 === null || polyfillSupport$2 === void 0 ? void 0 : polyfillSupport$2({ ReactiveElement });
// IMPORTANT: do not change the property name or the assignment expression.
// This line will be used in regexes to search for ReactiveElement usage.
((_d$1 = global$1.reactiveElementVersions) !== null && _d$1 !== void 0 ? _d$1 : (global$1.reactiveElementVersions = [])).push('1.5.0');

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
var _d;
// Use window for browser builds because IE11 doesn't have globalThis.
const global = window;
const __moz_domParser = new DOMParser();
const wrap$1 = (node) => node;
const trustedTypes = global.trustedTypes;
/**
 * Our TrustedTypePolicy for HTML which is declared using the html template
 * tag function.
 *
 * That HTML is a developer-authored constant, and is parsed with innerHTML
 * before any untrusted expressions have been mixed in. Therefor it is
 * considered safe by construction.
 */
const policy = trustedTypes
    ? trustedTypes.createPolicy('lit-html', {
        createHTML: (s) => s,
    })
    : undefined;
// Added to an attribute name to mark the attribute as bound so we can find
// it easily.
const boundAttributeSuffix = '$lit$';
// This marker is used in many syntactic positions in HTML, so it must be
// a valid element name and attribute name. We don't support dynamic names (yet)
// but this at least ensures that the parse tree is closer to the template
// intention.
const marker = `lit$${String(Math.random()).slice(9)}$`;
// String used to tell if a comment is a marker comment
const markerMatch = '?' + marker;
// Text used to insert a comment marker node. We use processing instruction
// syntax because it's slightly smaller, but parses as a comment node.
const nodeMarker = `<${markerMatch}>`;
const d = document;
// Creates a dynamic marker. We never have to search for these in the DOM.
const createMarker$1 = (v = '') => d.createComment(v);
const isPrimitive$1 = (value) => value === null || (typeof value != 'object' && typeof value != 'function');
const isArray = Array.isArray;
const isIterable = (value) => isArray(value) ||
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    typeof (value === null || value === void 0 ? void 0 : value[Symbol.iterator]) === 'function';
const SPACE_CHAR = `[ \t\n\f\r]`;
const ATTR_VALUE_CHAR = `[^ \t\n\f\r"'\`<>=]`;
const NAME_CHAR = `[^\\s"'>=/]`;
// These regexes represent the five parsing states that we care about in the
// Template's HTML scanner. They match the *end* of the state they're named
// after.
// Depending on the match, we transition to a new state. If there's no match,
// we stay in the same state.
// Note that the regexes are stateful. We utilize lastIndex and sync it
// across the multiple regexes used. In addition to the five regexes below
// we also dynamically create a regex to find the matching end tags for raw
// text elements.
/**
 * End of text is: `<` followed by:
 *   (comment start) or (tag) or (dynamic tag binding)
 */
const textEndRegex = /<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g;
const COMMENT_START = 1;
const TAG_NAME = 2;
const DYNAMIC_TAG_NAME = 3;
const commentEndRegex = /-->/g;
/**
 * Comments not started with <!--, like </{, can be ended by a single `>`
 */
const comment2EndRegex = />/g;
/**
 * The tagEnd regex matches the end of the "inside an opening" tag syntax
 * position. It either matches a `>`, an attribute-like sequence, or the end
 * of the string after a space (attribute-name position ending).
 *
 * See attributes in the HTML spec:
 * https://www.w3.org/TR/html5/syntax.html#elements-attributes
 *
 * " \t\n\f\r" are HTML space characters:
 * https://infra.spec.whatwg.org/#ascii-whitespace
 *
 * So an attribute is:
 *  * The name: any character except a whitespace character, ("), ('), ">",
 *    "=", or "/". Note: this is different from the HTML spec which also excludes control characters.
 *  * Followed by zero or more space characters
 *  * Followed by "="
 *  * Followed by zero or more space characters
 *  * Followed by:
 *    * Any character except space, ('), ("), "<", ">", "=", (`), or
 *    * (") then any non-("), or
 *    * (') then any non-(')
 */
const tagEndRegex = new RegExp(`>|${SPACE_CHAR}(?:(${NAME_CHAR}+)(${SPACE_CHAR}*=${SPACE_CHAR}*(?:${ATTR_VALUE_CHAR}|("|')|))|$)`, 'g');
const ENTIRE_MATCH = 0;
const ATTRIBUTE_NAME = 1;
const SPACES_AND_EQUALS = 2;
const QUOTE_CHAR = 3;
const singleQuoteAttrEndRegex = /'/g;
const doubleQuoteAttrEndRegex = /"/g;
/**
 * Matches the raw text elements.
 *
 * Comments are not parsed within raw text elements, so we need to search their
 * text content for marker strings.
 */
const rawTextElement = /^(?:script|style|textarea|title)$/i;
/** TemplateResult types */
const HTML_RESULT$1 = 1;
const SVG_RESULT$1 = 2;
// TemplatePart types
// IMPORTANT: these must match the values in PartType
const ATTRIBUTE_PART = 1;
const CHILD_PART = 2;
const PROPERTY_PART = 3;
const BOOLEAN_ATTRIBUTE_PART = 4;
const EVENT_PART = 5;
const ELEMENT_PART = 6;
const COMMENT_PART = 7;
/**
 * Generates a template literal tag function that returns a TemplateResult with
 * the given result type.
 */
const tag = (type) => (strings, ...values) => {
    return {
        // This property needs to remain unminified.
        ['_$litType$']: type,
        strings,
        values,
    };
};
/**
 * Interprets a template literal as an HTML template that can efficiently
 * render to and update a container.
 *
 * ```ts
 * const header = (title: string) => html`<h1>${title}</h1>`;
 * ```
 *
 * The `html` tag returns a description of the DOM to render as a value. It is
 * lazy, meaning no work is done until the template is rendered. When rendering,
 * if a template comes from the same expression as a previously rendered result,
 * it's efficiently updated instead of replaced.
 */
const html$1 = tag(HTML_RESULT$1);
/**
 * Interprets a template literal as an SVG fragment that can efficiently
 * render to and update a container.
 *
 * ```ts
 * const rect = svg`<rect width="10" height="10"></rect>`;
 *
 * const myImage = html`
 *   <svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg">
 *     ${rect}
 *   </svg>`;
 * ```
 *
 * The `svg` *tag function* should only be used for SVG fragments, or elements
 * that would be contained **inside** an `<svg>` HTML element. A common error is
 * placing an `<svg>` *element* in a template tagged with the `svg` tag
 * function. The `<svg>` element is an HTML element and should be used within a
 * template tagged with the {@linkcode html} tag function.
 *
 * In LitElement usage, it's invalid to return an SVG fragment from the
 * `render()` method, as the SVG fragment will be contained within the element's
 * shadow root and thus cannot be used within an `<svg>` HTML element.
 */
const svg$1 = tag(SVG_RESULT$1);
/**
 * A sentinel value that signals that a value was handled by a directive and
 * should not be written to the DOM.
 */
const noChange = Symbol.for('lit-noChange');
/**
 * A sentinel value that signals a ChildPart to fully clear its content.
 *
 * ```ts
 * const button = html`${
 *  user.isAdmin
 *    ? html`<button>DELETE</button>`
 *    : nothing
 * }`;
 * ```
 *
 * Prefer using `nothing` over other falsy values as it provides a consistent
 * behavior between various expression binding contexts.
 *
 * In child expressions, `undefined`, `null`, `''`, and `nothing` all behave the
 * same and render no nodes. In attribute expressions, `nothing` _removes_ the
 * attribute, while `undefined` and `null` will render an empty string. In
 * property expressions `nothing` becomes `undefined`.
 */
const nothing = Symbol.for('lit-nothing');
/**
 * The cache of prepared templates, keyed by the tagged TemplateStringsArray
 * and _not_ accounting for the specific template tag used. This means that
 * template tags cannot be dynamic - the must statically be one of html, svg,
 * or attr. This restriction simplifies the cache lookup, which is on the hot
 * path for rendering.
 */
const templateCache = new WeakMap();
const walker = d.createTreeWalker(d, 129 /* NodeFilter.SHOW_{ELEMENT|COMMENT} */, null, false);
/**
 * Returns an HTML string for the given TemplateStringsArray and result type
 * (HTML or SVG), along with the case-sensitive bound attribute names in
 * template order. The HTML contains comment markers denoting the `ChildPart`s
 * and suffixes on bound attributes denoting the `AttributeParts`.
 *
 * @param strings template strings array
 * @param type HTML or SVG
 * @return Array containing `[html, attrNames]` (array returned for terseness,
 *     to avoid object fields since this code is shared with non-minified SSR
 *     code)
 */
const getTemplateHtml = (strings, type) => {
    // Insert makers into the template HTML to represent the position of
    // bindings. The following code scans the template strings to determine the
    // syntactic position of the bindings. They can be in text position, where
    // we insert an HTML comment, attribute value position, where we insert a
    // sentinel string and re-write the attribute name, or inside a tag where
    // we insert the sentinel string.
    const l = strings.length - 1;
    // Stores the case-sensitive bound attribute names in the order of their
    // parts. ElementParts are also reflected in this array as undefined
    // rather than a string, to disambiguate from attribute bindings.
    const attrNames = [];
    let html = type === SVG_RESULT$1 ? '<svg>' : '';
    // When we're inside a raw text tag (not it's text content), the regex
    // will still be tagRegex so we can find attributes, but will switch to
    // this regex when the tag ends.
    let rawTextEndRegex;
    // The current parsing state, represented as a reference to one of the
    // regexes
    let regex = textEndRegex;
    for (let i = 0; i < l; i++) {
        const s = strings[i];
        // The index of the end of the last attribute name. When this is
        // positive at end of a string, it means we're in an attribute value
        // position and need to rewrite the attribute name.
        // We also use a special value of -2 to indicate that we encountered
        // the end of a string in attribute name position.
        let attrNameEndIndex = -1;
        let attrName;
        let lastIndex = 0;
        let match;
        // The conditions in this loop handle the current parse state, and the
        // assignments to the `regex` variable are the state transitions.
        while (lastIndex < s.length) {
            // Make sure we start searching from where we previously left off
            regex.lastIndex = lastIndex;
            match = regex.exec(s);
            if (match === null) {
                break;
            }
            lastIndex = regex.lastIndex;
            if (regex === textEndRegex) {
                if (match[COMMENT_START] === '!--') {
                    regex = commentEndRegex;
                }
                else if (match[COMMENT_START] !== undefined) {
                    // We started a weird comment, like </{
                    regex = comment2EndRegex;
                }
                else if (match[TAG_NAME] !== undefined) {
                    if (rawTextElement.test(match[TAG_NAME])) {
                        // Record if we encounter a raw-text element. We'll switch to
                        // this regex at the end of the tag.
                        rawTextEndRegex = new RegExp(`</${match[TAG_NAME]}`, 'g');
                    }
                    regex = tagEndRegex;
                }
                else if (match[DYNAMIC_TAG_NAME] !== undefined) {
                    regex = tagEndRegex;
                }
            }
            else if (regex === tagEndRegex) {
                if (match[ENTIRE_MATCH] === '>') {
                    // End of a tag. If we had started a raw-text element, use that
                    // regex
                    regex = rawTextEndRegex !== null && rawTextEndRegex !== void 0 ? rawTextEndRegex : textEndRegex;
                    // We may be ending an unquoted attribute value, so make sure we
                    // clear any pending attrNameEndIndex
                    attrNameEndIndex = -1;
                }
                else if (match[ATTRIBUTE_NAME] === undefined) {
                    // Attribute name position
                    attrNameEndIndex = -2;
                }
                else {
                    attrNameEndIndex = regex.lastIndex - match[SPACES_AND_EQUALS].length;
                    attrName = match[ATTRIBUTE_NAME];
                    regex =
                        match[QUOTE_CHAR] === undefined
                            ? tagEndRegex
                            : match[QUOTE_CHAR] === '"'
                                ? doubleQuoteAttrEndRegex
                                : singleQuoteAttrEndRegex;
                }
            }
            else if (regex === doubleQuoteAttrEndRegex ||
                regex === singleQuoteAttrEndRegex) {
                regex = tagEndRegex;
            }
            else if (regex === commentEndRegex || regex === comment2EndRegex) {
                regex = textEndRegex;
            }
            else {
                // Not one of the five state regexes, so it must be the dynamically
                // created raw text regex and we're at the close of that element.
                regex = tagEndRegex;
                rawTextEndRegex = undefined;
            }
        }
        // We have four cases:
        //  1. We're in text position, and not in a raw text element
        //     (regex === textEndRegex): insert a comment marker.
        //  2. We have a non-negative attrNameEndIndex which means we need to
        //     rewrite the attribute name to add a bound attribute suffix.
        //  3. We're at the non-first binding in a multi-binding attribute, use a
        //     plain marker.
        //  4. We're somewhere else inside the tag. If we're in attribute name
        //     position (attrNameEndIndex === -2), add a sequential suffix to
        //     generate a unique attribute name.
        // Detect a binding next to self-closing tag end and insert a space to
        // separate the marker from the tag end:
        const end = regex === tagEndRegex && strings[i + 1].startsWith('/>') ? ' ' : '';
        html +=
            regex === textEndRegex
                ? s + nodeMarker
                : attrNameEndIndex >= 0
                    ? (attrNames.push(attrName),
                        s.slice(0, attrNameEndIndex) +
                            boundAttributeSuffix +
                            s.slice(attrNameEndIndex)) +
                        marker +
                        end
                    : s +
                        marker +
                        (attrNameEndIndex === -2 ? (attrNames.push(undefined), i) : end);
    }
    const htmlResult = html + (strings[l] || '<?>') + (type === SVG_RESULT$1 ? '</svg>' : '');
    // A security check to prevent spoofing of Lit template results.
    // In the future, we may be able to replace this with Array.isTemplateObject,
    // though we might need to make that check inside of the html and svg
    // functions, because precompiled templates don't come in as
    // TemplateStringArray objects.
    if (!Array.isArray(strings) || !strings.hasOwnProperty('raw')) {
        let message = 'invalid template strings array';
        throw new Error(message);
    }
    // Returned as an array for terseness
    return [
        policy !== undefined
            ? policy.createHTML(htmlResult)
            : htmlResult,
        attrNames,
    ];
};
class Template {
    constructor(
    // This property needs to remain unminified.
    { strings, ['_$litType$']: type }, options) {
        /** @internal */
        this.parts = [];
        let node;
        let nodeIndex = 0;
        let attrNameIndex = 0;
        const partCount = strings.length - 1;
        const parts = this.parts;
        // Create template element
        const [html, attrNames] = getTemplateHtml(strings, type);
        this.el = Template.createElement(html, options);
        walker.currentNode = this.el.content;
        // Reparent SVG nodes into template root
        if (type === SVG_RESULT$1) {
            const content = this.el.content;
            const svgElement = content.firstChild;
            svgElement.remove();
            content.append(...svgElement.childNodes);
        }
        // Walk the template to find binding markers and create TemplateParts
        while ((node = walker.nextNode()) !== null && parts.length < partCount) {
            if (node.nodeType === 1) {
                // TODO (justinfagnani): for attempted dynamic tag names, we don't
                // increment the bindingIndex, and it'll be off by 1 in the element
                // and off by two after it.
                if (node.hasAttributes()) {
                    // We defer removing bound attributes because on IE we might not be
                    // iterating attributes in their template order, and would sometimes
                    // remove an attribute that we still need to create a part for.
                    const attrsToRemove = [];
                    for (const name of node.getAttributeNames()) {
                        // `name` is the name of the attribute we're iterating over, but not
                        // _neccessarily_ the name of the attribute we will create a part
                        // for. They can be different in browsers that don't iterate on
                        // attributes in source order. In that case the attrNames array
                        // contains the attribute name we'll process next. We only need the
                        // attribute name here to know if we should process a bound attribute
                        // on this element.
                        if (name.endsWith(boundAttributeSuffix) ||
                            name.startsWith(marker)) {
                            const realName = attrNames[attrNameIndex++];
                            attrsToRemove.push(name);
                            if (realName !== undefined) {
                                // Lowercase for case-sensitive SVG attributes like viewBox
                                const value = node.getAttribute(realName.toLowerCase() + boundAttributeSuffix);
                                const statics = value.split(marker);
                                const m = /([.?@])?(.*)/.exec(realName);
                                parts.push({
                                    type: ATTRIBUTE_PART,
                                    index: nodeIndex,
                                    name: m[2],
                                    strings: statics,
                                    ctor: m[1] === '.'
                                        ? PropertyPart
                                        : m[1] === '?'
                                            ? BooleanAttributePart
                                            : m[1] === '@'
                                                ? EventPart
                                                : AttributePart,
                                });
                            }
                            else {
                                parts.push({
                                    type: ELEMENT_PART,
                                    index: nodeIndex,
                                });
                            }
                        }
                    }
                    for (const name of attrsToRemove) {
                        node.removeAttribute(name);
                    }
                }
                // TODO (justinfagnani): benchmark the regex against testing for each
                // of the 3 raw text element names.
                if (rawTextElement.test(node.tagName)) {
                    // For raw text elements we need to split the text content on
                    // markers, create a Text node for each segment, and create
                    // a TemplatePart for each marker.
                    const strings = node.textContent.split(marker);
                    const lastIndex = strings.length - 1;
                    if (lastIndex > 0) {
                        node.textContent = trustedTypes
                            ? trustedTypes.emptyScript
                            : '';
                        // Generate a new text node for each literal section
                        // These nodes are also used as the markers for node parts
                        // We can't use empty text nodes as markers because they're
                        // normalized when cloning in IE (could simplify when
                        // IE is no longer supported)
                        for (let i = 0; i < lastIndex; i++) {
                            node.append(strings[i], createMarker$1());
                            // Walk past the marker node we just added
                            walker.nextNode();
                            parts.push({ type: CHILD_PART, index: ++nodeIndex });
                        }
                        // Note because this marker is added after the walker's current
                        // node, it will be walked to in the outer loop (and ignored), so
                        // we don't need to adjust nodeIndex here
                        node.append(strings[lastIndex], createMarker$1());
                    }
                }
            }
            else if (node.nodeType === 8) {
                const data = node.data;
                if (data === markerMatch) {
                    parts.push({ type: CHILD_PART, index: nodeIndex });
                }
                else {
                    let i = -1;
                    while ((i = node.data.indexOf(marker, i + 1)) !== -1) {
                        // Comment node has a binding marker inside, make an inactive part
                        // The binding won't work, but subsequent bindings will
                        parts.push({ type: COMMENT_PART, index: nodeIndex });
                        // Move to the end of the match
                        i += marker.length - 1;
                    }
                }
            }
            nodeIndex++;
        }
    }
    // Overridden via `litHtmlPolyfillSupport` to provide platform support.
    /** @nocollapse */
    static createElement(html, _options) {
        const doc = __moz_domParser.parseFromString(`<template>${html}</template>`, 'text/html');
        return document.importNode(doc.querySelector('template'), true);
    }
}
function resolveDirective(part, value, parent = part, attributeIndex) {
    var _a, _b, _c;
    var _d;
    // Bail early if the value is explicitly noChange. Note, this means any
    // nested directive is still attached and is not run.
    if (value === noChange) {
        return value;
    }
    let currentDirective = attributeIndex !== undefined
        ? (_a = parent.__directives) === null || _a === void 0 ? void 0 : _a[attributeIndex]
        : parent.__directive;
    const nextDirectiveConstructor = isPrimitive$1(value)
        ? undefined
        : // This property needs to remain unminified.
            value['_$litDirective$'];
    if ((currentDirective === null || currentDirective === void 0 ? void 0 : currentDirective.constructor) !== nextDirectiveConstructor) {
        // This property needs to remain unminified.
        (_b = currentDirective === null || currentDirective === void 0 ? void 0 : currentDirective['_$notifyDirectiveConnectionChanged']) === null || _b === void 0 ? void 0 : _b.call(currentDirective, false);
        if (nextDirectiveConstructor === undefined) {
            currentDirective = undefined;
        }
        else {
            currentDirective = new nextDirectiveConstructor(part);
            currentDirective._$initialize(part, parent, attributeIndex);
        }
        if (attributeIndex !== undefined) {
            ((_c = (_d = parent).__directives) !== null && _c !== void 0 ? _c : (_d.__directives = []))[attributeIndex] =
                currentDirective;
        }
        else {
            parent.__directive = currentDirective;
        }
    }
    if (currentDirective !== undefined) {
        value = resolveDirective(part, currentDirective._$resolve(part, value.values), currentDirective, attributeIndex);
    }
    return value;
}
/**
 * An updateable instance of a Template. Holds references to the Parts used to
 * update the template instance.
 */
class TemplateInstance {
    constructor(template, parent) {
        /** @internal */
        this._parts = [];
        /** @internal */
        this._$disconnectableChildren = undefined;
        this._$template = template;
        this._$parent = parent;
    }
    // Called by ChildPart parentNode getter
    get parentNode() {
        return this._$parent.parentNode;
    }
    // See comment in Disconnectable interface for why this is a getter
    get _$isConnected() {
        return this._$parent._$isConnected;
    }
    // This method is separate from the constructor because we need to return a
    // DocumentFragment and we don't want to hold onto it with an instance field.
    _clone(options) {
        var _a;
        const { el: { content }, parts: parts, } = this._$template;
        const fragment = ((_a = options === null || options === void 0 ? void 0 : options.creationScope) !== null && _a !== void 0 ? _a : d).importNode(content, true);
        walker.currentNode = fragment;
        let node = walker.nextNode();
        let nodeIndex = 0;
        let partIndex = 0;
        let templatePart = parts[0];
        while (templatePart !== undefined) {
            if (nodeIndex === templatePart.index) {
                let part;
                if (templatePart.type === CHILD_PART) {
                    part = new ChildPart$1(node, node.nextSibling, this, options);
                }
                else if (templatePart.type === ATTRIBUTE_PART) {
                    part = new templatePart.ctor(node, templatePart.name, templatePart.strings, this, options);
                }
                else if (templatePart.type === ELEMENT_PART) {
                    part = new ElementPart(node, this, options);
                }
                this._parts.push(part);
                templatePart = parts[++partIndex];
            }
            if (nodeIndex !== (templatePart === null || templatePart === void 0 ? void 0 : templatePart.index)) {
                node = walker.nextNode();
                nodeIndex++;
            }
        }
        return fragment;
    }
    _update(values) {
        let i = 0;
        for (const part of this._parts) {
            if (part !== undefined) {
                if (part.strings !== undefined) {
                    part._$setValue(values, part, i);
                    // The number of values the part consumes is part.strings.length - 1
                    // since values are in between template spans. We increment i by 1
                    // later in the loop, so increment it by part.strings.length - 2 here
                    i += part.strings.length - 2;
                }
                else {
                    part._$setValue(values[i]);
                }
            }
            i++;
        }
    }
}
class ChildPart$1 {
    constructor(startNode, endNode, parent, options) {
        var _a;
        this.type = CHILD_PART;
        this._$committedValue = nothing;
        // The following fields will be patched onto ChildParts when required by
        // AsyncDirective
        /** @internal */
        this._$disconnectableChildren = undefined;
        this._$startNode = startNode;
        this._$endNode = endNode;
        this._$parent = parent;
        this.options = options;
        // Note __isConnected is only ever accessed on RootParts (i.e. when there is
        // no _$parent); the value on a non-root-part is "don't care", but checking
        // for parent would be more code
        this.__isConnected = (_a = options === null || options === void 0 ? void 0 : options.isConnected) !== null && _a !== void 0 ? _a : true;
    }
    // See comment in Disconnectable interface for why this is a getter
    get _$isConnected() {
        var _a, _b;
        // ChildParts that are not at the root should always be created with a
        // parent; only RootChildNode's won't, so they return the local isConnected
        // state
        return (_b = (_a = this._$parent) === null || _a === void 0 ? void 0 : _a._$isConnected) !== null && _b !== void 0 ? _b : this.__isConnected;
    }
    /**
     * The parent node into which the part renders its content.
     *
     * A ChildPart's content consists of a range of adjacent child nodes of
     * `.parentNode`, possibly bordered by 'marker nodes' (`.startNode` and
     * `.endNode`).
     *
     * - If both `.startNode` and `.endNode` are non-null, then the part's content
     * consists of all siblings between `.startNode` and `.endNode`, exclusively.
     *
     * - If `.startNode` is non-null but `.endNode` is null, then the part's
     * content consists of all siblings following `.startNode`, up to and
     * including the last child of `.parentNode`. If `.endNode` is non-null, then
     * `.startNode` will always be non-null.
     *
     * - If both `.endNode` and `.startNode` are null, then the part's content
     * consists of all child nodes of `.parentNode`.
     */
    get parentNode() {
        let parentNode = wrap$1(this._$startNode).parentNode;
        const parent = this._$parent;
        if (parent !== undefined &&
            parentNode.nodeType === 11 /* Node.DOCUMENT_FRAGMENT */) {
            // If the parentNode is a DocumentFragment, it may be because the DOM is
            // still in the cloned fragment during initial render; if so, get the real
            // parentNode the part will be committed into by asking the parent.
            parentNode = parent.parentNode;
        }
        return parentNode;
    }
    /**
     * The part's leading marker node, if any. See `.parentNode` for more
     * information.
     */
    get startNode() {
        return this._$startNode;
    }
    /**
     * The part's trailing marker node, if any. See `.parentNode` for more
     * information.
     */
    get endNode() {
        return this._$endNode;
    }
    _$setValue(value, directiveParent = this) {
        value = resolveDirective(this, value, directiveParent);
        if (isPrimitive$1(value)) {
            // Non-rendering child values. It's important that these do not render
            // empty text nodes to avoid issues with preventing default <slot>
            // fallback content.
            if (value === nothing || value == null || value === '') {
                if (this._$committedValue !== nothing) {
                    this._$clear();
                }
                this._$committedValue = nothing;
            }
            else if (value !== this._$committedValue && value !== noChange) {
                this._commitText(value);
            }
            // This property needs to remain unminified.
        }
        else if (value['_$litType$'] !== undefined) {
            this._commitTemplateResult(value);
        }
        else if (value.nodeType !== undefined) {
            this._commitNode(value);
        }
        else if (isIterable(value)) {
            this._commitIterable(value);
        }
        else {
            // Fallback, will render the string representation
            this._commitText(value);
        }
    }
    _insert(node, ref = this._$endNode) {
        return wrap$1(wrap$1(this._$startNode).parentNode).insertBefore(node, ref);
    }
    _commitNode(value) {
        if (this._$committedValue !== value) {
            this._$clear();
            this._$committedValue = this._insert(value);
        }
    }
    _commitText(value) {
        // If the committed value is a primitive it means we called _commitText on
        // the previous render, and we know that this._$startNode.nextSibling is a
        // Text node. We can now just replace the text content (.data) of the node.
        if (this._$committedValue !== nothing &&
            isPrimitive$1(this._$committedValue)) {
            const node = wrap$1(this._$startNode).nextSibling;
            node.data = value;
        }
        else {
            {
                this._commitNode(d.createTextNode(value));
            }
        }
        this._$committedValue = value;
    }
    _commitTemplateResult(result) {
        var _a;
        // This property needs to remain unminified.
        const { values, ['_$litType$']: type } = result;
        // If $litType$ is a number, result is a plain TemplateResult and we get
        // the template from the template cache. If not, result is a
        // CompiledTemplateResult and _$litType$ is a CompiledTemplate and we need
        // to create the <template> element the first time we see it.
        const template = typeof type === 'number'
            ? this._$getTemplate(result)
            : (type.el === undefined &&
                (type.el = Template.createElement(type.h, this.options)),
                type);
        if (((_a = this._$committedValue) === null || _a === void 0 ? void 0 : _a._$template) === template) {
            this._$committedValue._update(values);
        }
        else {
            const instance = new TemplateInstance(template, this);
            const fragment = instance._clone(this.options);
            instance._update(values);
            this._commitNode(fragment);
            this._$committedValue = instance;
        }
    }
    // Overridden via `litHtmlPolyfillSupport` to provide platform support.
    /** @internal */
    _$getTemplate(result) {
        let template = templateCache.get(result.strings);
        if (template === undefined) {
            templateCache.set(result.strings, (template = new Template(result)));
        }
        return template;
    }
    _commitIterable(value) {
        // For an Iterable, we create a new InstancePart per item, then set its
        // value to the item. This is a little bit of overhead for every item in
        // an Iterable, but it lets us recurse easily and efficiently update Arrays
        // of TemplateResults that will be commonly returned from expressions like:
        // array.map((i) => html`${i}`), by reusing existing TemplateInstances.
        // If value is an array, then the previous render was of an
        // iterable and value will contain the ChildParts from the previous
        // render. If value is not an array, clear this part and make a new
        // array for ChildParts.
        if (!isArray(this._$committedValue)) {
            this._$committedValue = [];
            this._$clear();
        }
        // Lets us keep track of how many items we stamped so we can clear leftover
        // items from a previous render
        const itemParts = this._$committedValue;
        let partIndex = 0;
        let itemPart;
        for (const item of value) {
            if (partIndex === itemParts.length) {
                // If no existing part, create a new one
                // TODO (justinfagnani): test perf impact of always creating two parts
                // instead of sharing parts between nodes
                // https://github.com/lit/lit/issues/1266
                itemParts.push((itemPart = new ChildPart$1(this._insert(createMarker$1()), this._insert(createMarker$1()), this, this.options)));
            }
            else {
                // Reuse an existing part
                itemPart = itemParts[partIndex];
            }
            itemPart._$setValue(item);
            partIndex++;
        }
        if (partIndex < itemParts.length) {
            // itemParts always have end nodes
            this._$clear(itemPart && wrap$1(itemPart._$endNode).nextSibling, partIndex);
            // Truncate the parts array so _value reflects the current state
            itemParts.length = partIndex;
        }
    }
    /**
     * Removes the nodes contained within this Part from the DOM.
     *
     * @param start Start node to clear from, for clearing a subset of the part's
     *     DOM (used when truncating iterables)
     * @param from  When `start` is specified, the index within the iterable from
     *     which ChildParts are being removed, used for disconnecting directives in
     *     those Parts.
     *
     * @internal
     */
    _$clear(start = wrap$1(this._$startNode).nextSibling, from) {
        var _a;
        (_a = this._$notifyConnectionChanged) === null || _a === void 0 ? void 0 : _a.call(this, false, true, from);
        while (start && start !== this._$endNode) {
            const n = wrap$1(start).nextSibling;
            wrap$1(start).remove();
            start = n;
        }
    }
    /**
     * Implementation of RootPart's `isConnected`. Note that this metod
     * should only be called on `RootPart`s (the `ChildPart` returned from a
     * top-level `render()` call). It has no effect on non-root ChildParts.
     * @param isConnected Whether to set
     * @internal
     */
    setConnected(isConnected) {
        var _a;
        if (this._$parent === undefined) {
            this.__isConnected = isConnected;
            (_a = this._$notifyConnectionChanged) === null || _a === void 0 ? void 0 : _a.call(this, isConnected);
        }
    }
}
class AttributePart {
    constructor(element, name, strings, parent, options) {
        this.type = ATTRIBUTE_PART;
        /** @internal */
        this._$committedValue = nothing;
        /** @internal */
        this._$disconnectableChildren = undefined;
        this.element = element;
        this.name = name;
        this._$parent = parent;
        this.options = options;
        if (strings.length > 2 || strings[0] !== '' || strings[1] !== '') {
            this._$committedValue = new Array(strings.length - 1).fill(new String());
            this.strings = strings;
        }
        else {
            this._$committedValue = nothing;
        }
    }
    get tagName() {
        return this.element.tagName;
    }
    // See comment in Disconnectable interface for why this is a getter
    get _$isConnected() {
        return this._$parent._$isConnected;
    }
    /**
     * Sets the value of this part by resolving the value from possibly multiple
     * values and static strings and committing it to the DOM.
     * If this part is single-valued, `this._strings` will be undefined, and the
     * method will be called with a single value argument. If this part is
     * multi-value, `this._strings` will be defined, and the method is called
     * with the value array of the part's owning TemplateInstance, and an offset
     * into the value array from which the values should be read.
     * This method is overloaded this way to eliminate short-lived array slices
     * of the template instance values, and allow a fast-path for single-valued
     * parts.
     *
     * @param value The part value, or an array of values for multi-valued parts
     * @param valueIndex the index to start reading values from. `undefined` for
     *   single-valued parts
     * @param noCommit causes the part to not commit its value to the DOM. Used
     *   in hydration to prime attribute parts with their first-rendered value,
     *   but not set the attribute, and in SSR to no-op the DOM operation and
     *   capture the value for serialization.
     *
     * @internal
     */
    _$setValue(value, directiveParent = this, valueIndex, noCommit) {
        const strings = this.strings;
        // Whether any of the values has changed, for dirty-checking
        let change = false;
        if (strings === undefined) {
            // Single-value binding case
            value = resolveDirective(this, value, directiveParent, 0);
            change =
                !isPrimitive$1(value) ||
                    (value !== this._$committedValue && value !== noChange);
            if (change) {
                this._$committedValue = value;
            }
        }
        else {
            // Interpolation case
            const values = value;
            value = strings[0];
            let i, v;
            for (i = 0; i < strings.length - 1; i++) {
                v = resolveDirective(this, values[valueIndex + i], directiveParent, i);
                if (v === noChange) {
                    // If the user-provided value is `noChange`, use the previous value
                    v = this._$committedValue[i];
                }
                change || (change = !isPrimitive$1(v) || v !== this._$committedValue[i]);
                if (v === nothing) {
                    value = nothing;
                }
                else if (value !== nothing) {
                    value += (v !== null && v !== void 0 ? v : '') + strings[i + 1];
                }
                // We always record each value, even if one is `nothing`, for future
                // change detection.
                this._$committedValue[i] = v;
            }
        }
        if (change && !noCommit) {
            this._commitValue(value);
        }
    }
    /** @internal */
    _commitValue(value) {
        if (value === nothing) {
            wrap$1(this.element).removeAttribute(this.name);
        }
        else {
            wrap$1(this.element).setAttribute(this.name, (value !== null && value !== void 0 ? value : ''));
        }
    }
}
class PropertyPart extends AttributePart {
    constructor() {
        super(...arguments);
        this.type = PROPERTY_PART;
    }
    /** @internal */
    _commitValue(value) {
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        this.element[this.name] = value === nothing ? undefined : value;
    }
}
// Temporary workaround for https://crbug.com/993268
// Currently, any attribute starting with "on" is considered to be a
// TrustedScript source. Such boolean attributes must be set to the equivalent
// trusted emptyScript value.
const emptyStringForBooleanAttribute = trustedTypes
    ? trustedTypes.emptyScript
    : '';
class BooleanAttributePart extends AttributePart {
    constructor() {
        super(...arguments);
        this.type = BOOLEAN_ATTRIBUTE_PART;
    }
    /** @internal */
    _commitValue(value) {
        if (value && value !== nothing) {
            wrap$1(this.element).setAttribute(this.name, emptyStringForBooleanAttribute);
        }
        else {
            wrap$1(this.element).removeAttribute(this.name);
        }
    }
}
class EventPart extends AttributePart {
    constructor(element, name, strings, parent, options) {
        super(element, name, strings, parent, options);
        this.type = EVENT_PART;
    }
    // EventPart does not use the base _$setValue/_resolveValue implementation
    // since the dirty checking is more complex
    /** @internal */
    _$setValue(newListener, directiveParent = this) {
        var _a;
        newListener =
            (_a = resolveDirective(this, newListener, directiveParent, 0)) !== null && _a !== void 0 ? _a : nothing;
        if (newListener === noChange) {
            return;
        }
        const oldListener = this._$committedValue;
        // If the new value is nothing or any options change we have to remove the
        // part as a listener.
        const shouldRemoveListener = (newListener === nothing && oldListener !== nothing) ||
            newListener.capture !==
                oldListener.capture ||
            newListener.once !==
                oldListener.once ||
            newListener.passive !==
                oldListener.passive;
        // If the new value is not nothing and we removed the listener, we have
        // to add the part as a listener.
        const shouldAddListener = newListener !== nothing &&
            (oldListener === nothing || shouldRemoveListener);
        if (shouldRemoveListener) {
            this.element.removeEventListener(this.name, this, oldListener);
        }
        if (shouldAddListener) {
            // Beware: IE11 and Chrome 41 don't like using the listener as the
            // options object. Figure out how to deal w/ this in IE11 - maybe
            // patch addEventListener?
            this.element.addEventListener(this.name, this, newListener);
        }
        this._$committedValue = newListener;
    }
    handleEvent(event) {
        var _a, _b;
        if (typeof this._$committedValue === 'function') {
            this._$committedValue.call((_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.host) !== null && _b !== void 0 ? _b : this.element, event);
        }
        else {
            this._$committedValue.handleEvent(event);
        }
    }
}
class ElementPart {
    constructor(element, parent, options) {
        this.element = element;
        this.type = ELEMENT_PART;
        /** @internal */
        this._$disconnectableChildren = undefined;
        this._$parent = parent;
        this.options = options;
    }
    // See comment in Disconnectable interface for why this is a getter
    get _$isConnected() {
        return this._$parent._$isConnected;
    }
    _$setValue(value) {
        resolveDirective(this, value);
    }
}
/**
 * END USERS SHOULD NOT RELY ON THIS OBJECT.
 *
 * Private exports for use by other Lit packages, not intended for use by
 * external users.
 *
 * We currently do not make a mangled rollup build of the lit-ssr code. In order
 * to keep a number of (otherwise private) top-level exports  mangled in the
 * client side code, we export a _$LH object containing those members (or
 * helper methods for accessing private fields of those members), and then
 * re-export them for use in lit-ssr. This keeps lit-ssr agnostic to whether the
 * client-side code is being used in `dev` mode or `prod` mode.
 *
 * This has a unique name, to disambiguate it from private exports in
 * lit-element, which re-exports all of lit-html.
 *
 * @private
 */
const _$LH = {
    // Used in lit-ssr
    _boundAttributeSuffix: boundAttributeSuffix,
    _marker: marker,
    _markerMatch: markerMatch,
    _HTML_RESULT: HTML_RESULT$1,
    _getTemplateHtml: getTemplateHtml,
    // Used in hydrate
    _TemplateInstance: TemplateInstance,
    _isIterable: isIterable,
    _resolveDirective: resolveDirective,
    // Used in tests and private-ssr-support
    _ChildPart: ChildPart$1,
    _AttributePart: AttributePart,
    _BooleanAttributePart: BooleanAttributePart,
    _EventPart: EventPart,
    _PropertyPart: PropertyPart,
    _ElementPart: ElementPart,
};
// Apply polyfills if available
const polyfillSupport$1 = global.litHtmlPolyfillSupport;
polyfillSupport$1 === null || polyfillSupport$1 === void 0 ? void 0 : polyfillSupport$1(Template, ChildPart$1);
// IMPORTANT: do not change the property name or the assignment expression.
// This line will be used in regexes to search for lit-html usage.
((_d = global.litHtmlVersions) !== null && _d !== void 0 ? _d : (global.litHtmlVersions = [])).push('2.5.0');
/**
 * Renders a value, usually a lit-html TemplateResult, to the container.
 *
 * This example renders the text "Hello, Zoe!" inside a paragraph tag, appending
 * it to the container `document.body`.
 *
 * ```js
 * import {html, render} from 'lit';
 *
 * const name = "Zoe";
 * render(html`<p>Hello, ${name}!</p>`, document.body);
 * ```
 *
 * @param value Any [renderable
 *   value](https://lit.dev/docs/templates/expressions/#child-expressions),
 *   typically a {@linkcode TemplateResult} created by evaluating a template tag
 *   like {@linkcode html} or {@linkcode svg}.
 * @param container A DOM container to render to. The first render will append
 *   the rendered value to the container, and subsequent renders will
 *   efficiently update the rendered value if the same result type was
 *   previously rendered there.
 * @param options See {@linkcode RenderOptions} for options documentation.
 * @see
 * {@link https://lit.dev/docs/libraries/standalone-templates/#rendering-lit-html-templates| Rendering Lit HTML Templates}
 */
const render = (value, container, options) => {
    var _a, _b;
    const partOwnerNode = (_a = options === null || options === void 0 ? void 0 : options.renderBefore) !== null && _a !== void 0 ? _a : container;
    // This property needs to remain unminified.
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    let part = partOwnerNode['_$litPart$'];
    if (part === undefined) {
        const endNode = (_b = options === null || options === void 0 ? void 0 : options.renderBefore) !== null && _b !== void 0 ? _b : null;
        // This property needs to remain unminified.
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        partOwnerNode['_$litPart$'] = part = new ChildPart$1(container.insertBefore(createMarker$1(), endNode), endNode, undefined, options !== null && options !== void 0 ? options : {});
    }
    part._$setValue(value);
    return part;
};

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
var _b, _c;
// For backwards compatibility export ReactiveElement as UpdatingElement. Note,
// IE transpilation requires exporting like this.
const UpdatingElement = ReactiveElement;
/**
 * Base element class that manages element properties and attributes, and
 * renders a lit-html template.
 *
 * To define a component, subclass `LitElement` and implement a
 * `render` method to provide the component's template. Define properties
 * using the {@linkcode LitElement.properties properties} property or the
 * {@linkcode property} decorator.
 */
class LitElement extends ReactiveElement {
    constructor() {
        super(...arguments);
        /**
         * @category rendering
         */
        this.renderOptions = { host: this };
        this.__childPart = undefined;
    }
    /**
     * @category rendering
     */
    createRenderRoot() {
        var _a;
        var _b;
        const renderRoot = super.createRenderRoot();
        // When adoptedStyleSheets are shimmed, they are inserted into the
        // shadowRoot by createRenderRoot. Adjust the renderBefore node so that
        // any styles in Lit content render before adoptedStyleSheets. This is
        // important so that adoptedStyleSheets have precedence over styles in
        // the shadowRoot.
        (_a = (_b = this.renderOptions).renderBefore) !== null && _a !== void 0 ? _a : (_b.renderBefore = renderRoot.firstChild);
        return renderRoot;
    }
    /**
     * Updates the element. This method reflects property values to attributes
     * and calls `render` to render DOM via lit-html. Setting properties inside
     * this method will *not* trigger another update.
     * @param changedProperties Map of changed properties with old values
     * @category updates
     */
    update(changedProperties) {
        // Setting properties in `render` should not trigger an update. Since
        // updates are allowed after super.update, it's important to call `render`
        // before that.
        const value = this.render();
        if (!this.hasUpdated) {
            this.renderOptions.isConnected = this.isConnected;
        }
        super.update(changedProperties);
        this.__childPart = render(value, this.renderRoot, this.renderOptions);
    }
    /**
     * Invoked when the component is added to the document's DOM.
     *
     * In `connectedCallback()` you should setup tasks that should only occur when
     * the element is connected to the document. The most common of these is
     * adding event listeners to nodes external to the element, like a keydown
     * event handler added to the window.
     *
     * ```ts
     * connectedCallback() {
     *   super.connectedCallback();
     *   addEventListener('keydown', this._handleKeydown);
     * }
     * ```
     *
     * Typically, anything done in `connectedCallback()` should be undone when the
     * element is disconnected, in `disconnectedCallback()`.
     *
     * @category lifecycle
     */
    connectedCallback() {
        var _a;
        super.connectedCallback();
        (_a = this.__childPart) === null || _a === void 0 ? void 0 : _a.setConnected(true);
    }
    /**
     * Invoked when the component is removed from the document's DOM.
     *
     * This callback is the main signal to the element that it may no longer be
     * used. `disconnectedCallback()` should ensure that nothing is holding a
     * reference to the element (such as event listeners added to nodes external
     * to the element), so that it is free to be garbage collected.
     *
     * ```ts
     * disconnectedCallback() {
     *   super.disconnectedCallback();
     *   window.removeEventListener('keydown', this._handleKeydown);
     * }
     * ```
     *
     * An element may be re-connected after being disconnected.
     *
     * @category lifecycle
     */
    disconnectedCallback() {
        var _a;
        super.disconnectedCallback();
        (_a = this.__childPart) === null || _a === void 0 ? void 0 : _a.setConnected(false);
    }
    /**
     * Invoked on each update to perform rendering tasks. This method may return
     * any value renderable by lit-html's `ChildPart` - typically a
     * `TemplateResult`. Setting properties inside this method will *not* trigger
     * the element to update.
     * @category rendering
     */
    render() {
        return noChange;
    }
}
/**
 * Ensure this class is marked as `finalized` as an optimization ensuring
 * it will not needlessly try to `finalize`.
 *
 * Note this property name is a string to prevent breaking Closure JS Compiler
 * optimizations. See @lit/reactive-element for more information.
 */
LitElement['finalized'] = true;
// This property needs to remain unminified.
LitElement['_$litElement$'] = true;
// Install hydration if available
(_b = globalThis.litElementHydrateSupport) === null || _b === void 0 ? void 0 : _b.call(globalThis, { LitElement });
// Apply polyfills if available
const polyfillSupport = globalThis.litElementPolyfillSupport;
polyfillSupport === null || polyfillSupport === void 0 ? void 0 : polyfillSupport({ LitElement });
/**
 * END USERS SHOULD NOT RELY ON THIS OBJECT.
 *
 * Private exports for use by other Lit packages, not intended for use by
 * external users.
 *
 * We currently do not make a mangled rollup build of the lit-ssr code. In order
 * to keep a number of (otherwise private) top-level exports  mangled in the
 * client side code, we export a _$LE object containing those members (or
 * helper methods for accessing private fields of those members), and then
 * re-export them for use in lit-ssr. This keeps lit-ssr agnostic to whether the
 * client-side code is being used in `dev` mode or `prod` mode.
 *
 * This has a unique name, to disambiguate it from private exports in
 * lit-html, since this module re-exports all of lit-html.
 *
 * @private
 */
const _$LE = {
    _$attributeToProperty: (el, name, value) => {
        // eslint-disable-next-line
        el._$attributeToProperty(name, value);
    },
    // eslint-disable-next-line
    _$changedProperties: (el) => el._$changedProperties,
};
// IMPORTANT: do not change the property name or the assignment expression.
// This line will be used in regexes to search for LitElement usage.
((_c = globalThis.litElementVersions) !== null && _c !== void 0 ? _c : (globalThis.litElementVersions = [])).push('3.2.2');

/**
 * @license
 * Copyright 2022 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
/**
 * @fileoverview
 *
 * This file exports a boolean const whose value will depend on what environment
 * the module is being imported from.
 */
const NODE_MODE = false;
/**
 * A boolean that will be `true` in server environments like Node, and `false`
 * in browser environments. Note that your server environment or toolchain must
 * support the `"node"` export condition for this to be `true`.
 *
 * This can be used when authoring components to change behavior based on
 * whether or not the component is executing in an SSR context.
 */
const isServer = NODE_MODE;

/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
const { _ChildPart: ChildPart } = _$LH;
const wrap = (node) => node;
/**
 * Tests if a value is a primitive value.
 *
 * See https://tc39.github.io/ecma262/#sec-typeof-operator
 */
const isPrimitive = (value) => value === null || (typeof value != 'object' && typeof value != 'function');
const TemplateResultType = {
    HTML: 1,
    SVG: 2,
};
/**
 * Tests if a value is a TemplateResult.
 */
const isTemplateResult = (value, type) => type === undefined
    ? // This property needs to remain unminified.
        (value === null || value === void 0 ? void 0 : value['_$litType$']) !== undefined
    : (value === null || value === void 0 ? void 0 : value['_$litType$']) === type;
/**
 * Tests if a value is a DirectiveResult.
 */
const isDirectiveResult = (value) => 
// This property needs to remain unminified.
(value === null || value === void 0 ? void 0 : value['_$litDirective$']) !== undefined;
/**
 * Retrieves the Directive class for a DirectiveResult
 */
const getDirectiveClass = (value) => 
// This property needs to remain unminified.
value === null || value === void 0 ? void 0 : value['_$litDirective$'];
/**
 * Tests whether a part has only a single-expression with no strings to
 * interpolate between.
 *
 * Only AttributePart and PropertyPart can have multiple expressions.
 * Multi-expression parts have a `strings` property and single-expression
 * parts do not.
 */
const isSingleExpression = (part) => part.strings === undefined;
const createMarker = () => document.createComment('');
/**
 * Inserts a ChildPart into the given container ChildPart's DOM, either at the
 * end of the container ChildPart, or before the optional `refPart`.
 *
 * This does not add the part to the containerPart's committed value. That must
 * be done by callers.
 *
 * @param containerPart Part within which to add the new ChildPart
 * @param refPart Part before which to add the new ChildPart; when omitted the
 *     part added to the end of the `containerPart`
 * @param part Part to insert, or undefined to create a new part
 */
const insertPart = (containerPart, refPart, part) => {
    var _a;
    const container = wrap(containerPart._$startNode).parentNode;
    const refNode = refPart === undefined ? containerPart._$endNode : refPart._$startNode;
    if (part === undefined) {
        const startNode = wrap(container).insertBefore(createMarker(), refNode);
        const endNode = wrap(container).insertBefore(createMarker(), refNode);
        part = new ChildPart(startNode, endNode, containerPart, containerPart.options);
    }
    else {
        const endNode = wrap(part._$endNode).nextSibling;
        const oldParent = part._$parent;
        const parentChanged = oldParent !== containerPart;
        if (parentChanged) {
            (_a = part._$reparentDisconnectables) === null || _a === void 0 ? void 0 : _a.call(part, containerPart);
            // Note that although `_$reparentDisconnectables` updates the part's
            // `_$parent` reference after unlinking from its current parent, that
            // method only exists if Disconnectables are present, so we need to
            // unconditionally set it here
            part._$parent = containerPart;
            // Since the _$isConnected getter is somewhat costly, only
            // read it once we know the subtree has directives that need
            // to be notified
            let newConnectionState;
            if (part._$notifyConnectionChanged !== undefined &&
                (newConnectionState = containerPart._$isConnected) !==
                    oldParent._$isConnected) {
                part._$notifyConnectionChanged(newConnectionState);
            }
        }
        if (endNode !== refNode || parentChanged) {
            let start = part._$startNode;
            while (start !== endNode) {
                const n = wrap(start).nextSibling;
                wrap(container).insertBefore(start, refNode);
                start = n;
            }
        }
    }
    return part;
};
/**
 * Sets the value of a Part.
 *
 * Note that this should only be used to set/update the value of user-created
 * parts (i.e. those created using `insertPart`); it should not be used
 * by directives to set the value of the directive's container part. Directives
 * should return a value from `update`/`render` to update their part state.
 *
 * For directives that require setting their part value asynchronously, they
 * should extend `AsyncDirective` and call `this.setValue()`.
 *
 * @param part Part to set
 * @param value Value to set
 * @param index For `AttributePart`s, the index to set
 * @param directiveParent Used internally; should not be set by user
 */
const setChildPartValue = (part, value, directiveParent = part) => {
    part._$setValue(value, directiveParent);
    return part;
};
// A sentinal value that can never appear as a part value except when set by
// live(). Used to force a dirty-check to fail and cause a re-render.
const RESET_VALUE = {};
/**
 * Sets the committed value of a ChildPart directly without triggering the
 * commit stage of the part.
 *
 * This is useful in cases where a directive needs to update the part such
 * that the next update detects a value change or not. When value is omitted,
 * the next update will be guaranteed to be detected as a change.
 *
 * @param part
 * @param value
 */
const setCommittedValue = (part, value = RESET_VALUE) => (part._$committedValue = value);
/**
 * Returns the committed value of a ChildPart.
 *
 * The committed value is used for change detection and efficient updates of
 * the part. It can differ from the value set by the template or directive in
 * cases where the template value is transformed before being commited.
 *
 * - `TemplateResult`s are committed as a `TemplateInstance`
 * - Iterables are committed as `Array<ChildPart>`
 * - All other types are committed as the template value or value returned or
 *   set by a directive.
 *
 * @param part
 */
const getCommittedValue = (part) => part._$committedValue;
/**
 * Removes a ChildPart from the DOM, including any of its content.
 *
 * @param part The Part to remove
 */
const removePart = (part) => {
    var _a;
    (_a = part._$notifyConnectionChanged) === null || _a === void 0 ? void 0 : _a.call(part, false, true);
    let start = part._$startNode;
    const end = wrap(part._$endNode).nextSibling;
    while (start !== end) {
        const n = wrap(start).nextSibling;
        wrap(start).remove();
        start = n;
    }
};
const clearPart = (part) => {
    part._$clear();
};

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
const PartType = {
    ATTRIBUTE: 1,
    CHILD: 2,
    PROPERTY: 3,
    BOOLEAN_ATTRIBUTE: 4,
    EVENT: 5,
    ELEMENT: 6,
};
/**
 * Creates a user-facing directive function from a Directive class. This
 * function has the same parameters as the directive's render() method.
 */
const directive = (c) => (...values) => ({
    // This property needs to remain unminified.
    ['_$litDirective$']: c,
    values,
});
/**
 * Base class for creating custom directives. Users should extend this class,
 * implement `render` and/or `update`, and then pass their subclass to
 * `directive`.
 */
class Directive {
    constructor(_partInfo) { }
    // See comment in Disconnectable interface for why this is a getter
    get _$isConnected() {
        return this._$parent._$isConnected;
    }
    /** @internal */
    _$initialize(part, parent, attributeIndex) {
        this.__part = part;
        this._$parent = parent;
        this.__attributeIndex = attributeIndex;
    }
    /** @internal */
    _$resolve(part, props) {
        return this.update(part, props);
    }
    update(_part, props) {
        return this.render(...props);
    }
}

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
/**
 * Recursively walks down the tree of Parts/TemplateInstances/Directives to set
 * the connected state of directives and run `disconnected`/ `reconnected`
 * callbacks.
 *
 * @return True if there were children to disconnect; false otherwise
 */
const notifyChildrenConnectedChanged = (parent, isConnected) => {
    var _a, _b;
    const children = parent._$disconnectableChildren;
    if (children === undefined) {
        return false;
    }
    for (const obj of children) {
        // The existence of `_$notifyDirectiveConnectionChanged` is used as a "brand" to
        // disambiguate AsyncDirectives from other DisconnectableChildren
        // (as opposed to using an instanceof check to know when to call it); the
        // redundancy of "Directive" in the API name is to avoid conflicting with
        // `_$notifyConnectionChanged`, which exists `ChildParts` which are also in
        // this list
        // Disconnect Directive (and any nested directives contained within)
        // This property needs to remain unminified.
        (_b = (_a = obj)['_$notifyDirectiveConnectionChanged']) === null || _b === void 0 ? void 0 : _b.call(_a, isConnected, false);
        // Disconnect Part/TemplateInstance
        notifyChildrenConnectedChanged(obj, isConnected);
    }
    return true;
};
/**
 * Removes the given child from its parent list of disconnectable children, and
 * if the parent list becomes empty as a result, removes the parent from its
 * parent, and so forth up the tree when that causes subsequent parent lists to
 * become empty.
 */
const removeDisconnectableFromParent = (obj) => {
    let parent, children;
    do {
        if ((parent = obj._$parent) === undefined) {
            break;
        }
        children = parent._$disconnectableChildren;
        children.delete(obj);
        obj = parent;
    } while ((children === null || children === void 0 ? void 0 : children.size) === 0);
};
const addDisconnectableToParent = (obj) => {
    // Climb the parent tree, creating a sparse tree of children needing
    // disconnection
    for (let parent; (parent = obj._$parent); obj = parent) {
        let children = parent._$disconnectableChildren;
        if (children === undefined) {
            parent._$disconnectableChildren = children = new Set();
        }
        else if (children.has(obj)) {
            // Once we've reached a parent that already contains this child, we
            // can short-circuit
            break;
        }
        children.add(obj);
        installDisconnectAPI(parent);
    }
};
/**
 * Changes the parent reference of the ChildPart, and updates the sparse tree of
 * Disconnectable children accordingly.
 *
 * Note, this method will be patched onto ChildPart instances and called from
 * the core code when parts are moved between different parents.
 */
function reparentDisconnectables(newParent) {
    if (this._$disconnectableChildren !== undefined) {
        removeDisconnectableFromParent(this);
        this._$parent = newParent;
        addDisconnectableToParent(this);
    }
    else {
        this._$parent = newParent;
    }
}
/**
 * Sets the connected state on any directives contained within the committed
 * value of this part (i.e. within a TemplateInstance or iterable of
 * ChildParts) and runs their `disconnected`/`reconnected`s, as well as within
 * any directives stored on the ChildPart (when `valueOnly` is false).
 *
 * `isClearingValue` should be passed as `true` on a top-level part that is
 * clearing itself, and not as a result of recursively disconnecting directives
 * as part of a `clear` operation higher up the tree. This both ensures that any
 * directive on this ChildPart that produced a value that caused the clear
 * operation is not disconnected, and also serves as a performance optimization
 * to avoid needless bookkeeping when a subtree is going away; when clearing a
 * subtree, only the top-most part need to remove itself from the parent.
 *
 * `fromPartIndex` is passed only in the case of a partial `_clear` running as a
 * result of truncating an iterable.
 *
 * Note, this method will be patched onto ChildPart instances and called from the
 * core code when parts are cleared or the connection state is changed by the
 * user.
 */
function notifyChildPartConnectedChanged(isConnected, isClearingValue = false, fromPartIndex = 0) {
    const value = this._$committedValue;
    const children = this._$disconnectableChildren;
    if (children === undefined || children.size === 0) {
        return;
    }
    if (isClearingValue) {
        if (Array.isArray(value)) {
            // Iterable case: Any ChildParts created by the iterable should be
            // disconnected and removed from this ChildPart's disconnectable
            // children (starting at `fromPartIndex` in the case of truncation)
            for (let i = fromPartIndex; i < value.length; i++) {
                notifyChildrenConnectedChanged(value[i], false);
                removeDisconnectableFromParent(value[i]);
            }
        }
        else if (value != null) {
            // TemplateInstance case: If the value has disconnectable children (will
            // only be in the case that it is a TemplateInstance), we disconnect it
            // and remove it from this ChildPart's disconnectable children
            notifyChildrenConnectedChanged(value, false);
            removeDisconnectableFromParent(value);
        }
    }
    else {
        notifyChildrenConnectedChanged(this, isConnected);
    }
}
/**
 * Patches disconnection API onto ChildParts.
 */
const installDisconnectAPI = (obj) => {
    var _a, _b;
    var _c, _d;
    if (obj.type == PartType.CHILD) {
        (_a = (_c = obj)._$notifyConnectionChanged) !== null && _a !== void 0 ? _a : (_c._$notifyConnectionChanged = notifyChildPartConnectedChanged);
        (_b = (_d = obj)._$reparentDisconnectables) !== null && _b !== void 0 ? _b : (_d._$reparentDisconnectables = reparentDisconnectables);
    }
};
/**
 * An abstract `Directive` base class whose `disconnected` method will be
 * called when the part containing the directive is cleared as a result of
 * re-rendering, or when the user calls `part.setConnected(false)` on
 * a part that was previously rendered containing the directive (as happens
 * when e.g. a LitElement disconnects from the DOM).
 *
 * If `part.setConnected(true)` is subsequently called on a
 * containing part, the directive's `reconnected` method will be called prior
 * to its next `update`/`render` callbacks. When implementing `disconnected`,
 * `reconnected` should also be implemented to be compatible with reconnection.
 *
 * Note that updates may occur while the directive is disconnected. As such,
 * directives should generally check the `this.isConnected` flag during
 * render/update to determine whether it is safe to subscribe to resources
 * that may prevent garbage collection.
 */
class AsyncDirective extends Directive {
    constructor() {
        super(...arguments);
        // @internal
        this._$disconnectableChildren = undefined;
    }
    /**
     * Initialize the part with internal fields
     * @param part
     * @param parent
     * @param attributeIndex
     */
    _$initialize(part, parent, attributeIndex) {
        super._$initialize(part, parent, attributeIndex);
        addDisconnectableToParent(this);
        this.isConnected = part._$isConnected;
    }
    // This property needs to remain unminified.
    /**
     * Called from the core code when a directive is going away from a part (in
     * which case `shouldRemoveFromParent` should be true), and from the
     * `setChildrenConnected` helper function when recursively changing the
     * connection state of a tree (in which case `shouldRemoveFromParent` should
     * be false).
     *
     * @param isConnected
     * @param isClearingDirective - True when the directive itself is being
     *     removed; false when the tree is being disconnected
     * @internal
     */
    ['_$notifyDirectiveConnectionChanged'](isConnected, isClearingDirective = true) {
        var _a, _b;
        if (isConnected !== this.isConnected) {
            this.isConnected = isConnected;
            if (isConnected) {
                (_a = this.reconnected) === null || _a === void 0 ? void 0 : _a.call(this);
            }
            else {
                (_b = this.disconnected) === null || _b === void 0 ? void 0 : _b.call(this);
            }
        }
        if (isClearingDirective) {
            notifyChildrenConnectedChanged(this, isConnected);
            removeDisconnectableFromParent(this);
        }
    }
    /**
     * Sets the value of the directive's Part outside the normal `update`/`render`
     * lifecycle of a directive.
     *
     * This method should not be called synchronously from a directive's `update`
     * or `render`.
     *
     * @param directive The directive to update
     * @param value The value to set
     */
    setValue(value) {
        if (isSingleExpression(this.__part)) {
            this.__part._$setValue(value, this);
        }
        else {
            const newValues = [...this.__part._$committedValue];
            newValues[this.__attributeIndex] = value;
            this.__part._$setValue(newValues, this, 0);
        }
    }
    /**
     * User callbacks for implementing logic to release any resources/subscriptions
     * that may have been retained by this directive. Since directives may also be
     * re-connected, `reconnected` should also be implemented to restore the
     * working state of the directive prior to the next render.
     */
    disconnected() { }
    reconnected() { }
}

/**
 * @license
 * Copyright 2021 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
// Note, this module is not included in package exports so that it's private to
// our first-party directives. If it ends up being useful, we can open it up and
// export it.
/**
 * Helper to iterate an AsyncIterable in its own closure.
 * @param iterable The iterable to iterate
 * @param callback The callback to call for each value. If the callback returns
 * `false`, the loop will be broken.
 */
const forAwaitOf = async (iterable, callback) => {
    for await (const v of iterable) {
        if ((await callback(v)) === false) {
            return;
        }
    }
};
/**
 * Holds a reference to an instance that can be disconnected and reconnected,
 * so that a closure over the ref (e.g. in a then function to a promise) does
 * not strongly hold a ref to the instance. Approximates a WeakRef but must
 * be manually connected & disconnected to the backing instance.
 */
class PseudoWeakRef {
    constructor(ref) {
        this._ref = ref;
    }
    /**
     * Disassociates the ref with the backing instance.
     */
    disconnect() {
        this._ref = undefined;
    }
    /**
     * Reassociates the ref with the backing instance.
     */
    reconnect(ref) {
        this._ref = ref;
    }
    /**
     * Retrieves the backing instance (will be undefined when disconnected)
     */
    deref() {
        return this._ref;
    }
}
/**
 * A helper to pause and resume waiting on a condition in an async function
 */
class Pauser {
    constructor() {
        this._promise = undefined;
        this._resolve = undefined;
    }
    /**
     * When paused, returns a promise to be awaited; when unpaused, returns
     * undefined. Note that in the microtask between the pauser being resumed
     * an an await of this promise resolving, the pauser could be paused again,
     * hence callers should check the promise in a loop when awaiting.
     * @returns A promise to be awaited when paused or undefined
     */
    get() {
        return this._promise;
    }
    /**
     * Creates a promise to be awaited
     */
    pause() {
        var _a;
        (_a = this._promise) !== null && _a !== void 0 ? _a : (this._promise = new Promise((resolve) => (this._resolve = resolve)));
    }
    /**
     * Resolves the promise which may be awaited
     */
    resume() {
        var _a;
        (_a = this._resolve) === null || _a === void 0 ? void 0 : _a.call(this);
        this._promise = this._resolve = undefined;
    }
}

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
class AsyncReplaceDirective extends AsyncDirective {
    constructor() {
        super(...arguments);
        this.__weakThis = new PseudoWeakRef(this);
        this.__pauser = new Pauser();
    }
    // @ts-expect-error value not used, but we want a nice parameter for docs
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    render(value, _mapper) {
        return noChange;
    }
    update(_part, [value, mapper]) {
        // If our initial render occurs while disconnected, ensure that the pauser
        // and weakThis are in the disconnected state
        if (!this.isConnected) {
            this.disconnected();
        }
        // If we've already set up this particular iterable, we don't need
        // to do anything.
        if (value === this.__value) {
            return;
        }
        this.__value = value;
        let i = 0;
        const { __weakThis: weakThis, __pauser: pauser } = this;
        // Note, the callback avoids closing over `this` so that the directive
        // can be gc'ed before the promise resolves; instead `this` is retrieved
        // from `weakThis`, which can break the hard reference in the closure when
        // the directive disconnects
        forAwaitOf(value, async (v) => {
            // The while loop here handles the case that the connection state
            // thrashes, causing the pauser to resume and then get re-paused
            while (pauser.get()) {
                await pauser.get();
            }
            // If the callback gets here and there is no `this`, it means that the
            // directive has been disconnected and garbage collected and we don't
            // need to do anything else
            const _this = weakThis.deref();
            if (_this !== undefined) {
                // Check to make sure that value is the still the current value of
                // the part, and if not bail because a new value owns this part
                if (_this.__value !== value) {
                    return false;
                }
                // As a convenience, because functional-programming-style
                // transforms of iterables and async iterables requires a library,
                // we accept a mapper function. This is especially convenient for
                // rendering a template for each item.
                if (mapper !== undefined) {
                    v = mapper(v, i);
                }
                _this.commitValue(v, i);
                i++;
            }
            return true;
        });
        return noChange;
    }
    // Override point for AsyncAppend to append rather than replace
    commitValue(value, _index) {
        this.setValue(value);
    }
    disconnected() {
        this.__weakThis.disconnect();
        this.__pauser.pause();
    }
    reconnected() {
        this.__weakThis.reconnect(this);
        this.__pauser.resume();
    }
}
/**
 * A directive that renders the items of an async iterable[1], replacing
 * previous values with new values, so that only one value is ever rendered
 * at a time. This directive may be used in any expression type.
 *
 * Async iterables are objects with a `[Symbol.asyncIterator]` method, which
 * returns an iterator who's `next()` method returns a Promise. When a new
 * value is available, the Promise resolves and the value is rendered to the
 * Part controlled by the directive. If another value other than this
 * directive has been set on the Part, the iterable will no longer be listened
 * to and new values won't be written to the Part.
 *
 * [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
 *
 * @param value An async iterable
 * @param mapper An optional function that maps from (value, index) to another
 *     value. Useful for generating templates for each item in the iterable.
 */
const asyncReplace = directive(AsyncReplaceDirective);

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
class AsyncAppendDirective extends AsyncReplaceDirective {
    // Override AsyncReplace to narrow the allowed part type to ChildPart only
    constructor(partInfo) {
        super(partInfo);
        if (partInfo.type !== PartType.CHILD) {
            throw new Error('asyncAppend can only be used in child expressions');
        }
    }
    // Override AsyncReplace to save the part since we need to append into it
    update(part, params) {
        this.__childPart = part;
        return super.update(part, params);
    }
    // Override AsyncReplace to append rather than replace
    commitValue(value, index) {
        // When we get the first value, clear the part. This lets the
        // previous value display until we can replace it.
        if (index === 0) {
            clearPart(this.__childPart);
        }
        // Create and insert a new part and set its value to the next value
        const newPart = insertPart(this.__childPart);
        setChildPartValue(newPart, value);
    }
}
/**
 * A directive that renders the items of an async iterable[1], appending new
 * values after previous values, similar to the built-in support for iterables.
 * This directive is usable only in child expressions.
 *
 * Async iterables are objects with a [Symbol.asyncIterator] method, which
 * returns an iterator who's `next()` method returns a Promise. When a new
 * value is available, the Promise resolves and the value is appended to the
 * Part controlled by the directive. If another value other than this
 * directive has been set on the Part, the iterable will no longer be listened
 * to and new values won't be written to the Part.
 *
 * [1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of
 *
 * @param value An async iterable
 * @param mapper An optional function that maps from (value, index) to another
 *     value. Useful for generating templates for each item in the iterable.
 */
const asyncAppend = directive(AsyncAppendDirective);

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
class CacheDirective extends Directive {
    constructor(partInfo) {
        super(partInfo);
        this._templateCache = new WeakMap();
    }
    render(v) {
        // Return an array of the value to induce lit-html to create a ChildPart
        // for the value that we can move into the cache.
        return [v];
    }
    update(containerPart, [v]) {
        // If the previous value is a TemplateResult and the new value is not,
        // or is a different Template as the previous value, move the child part
        // into the cache.
        if (isTemplateResult(this._value) &&
            (!isTemplateResult(v) || this._value.strings !== v.strings)) {
            // This is always an array because we return [v] in render()
            const partValue = getCommittedValue(containerPart);
            const childPart = partValue.pop();
            let cachedContainerPart = this._templateCache.get(this._value.strings);
            if (cachedContainerPart === undefined) {
                const fragment = document.createDocumentFragment();
                cachedContainerPart = render(nothing, fragment);
                cachedContainerPart.setConnected(false);
                this._templateCache.set(this._value.strings, cachedContainerPart);
            }
            // Move into cache
            setCommittedValue(cachedContainerPart, [childPart]);
            insertPart(cachedContainerPart, undefined, childPart);
        }
        // If the new value is a TemplateResult and the previous value is not,
        // or is a different Template as the previous value, restore the child
        // part from the cache.
        if (isTemplateResult(v)) {
            if (!isTemplateResult(this._value) || this._value.strings !== v.strings) {
                const cachedContainerPart = this._templateCache.get(v.strings);
                if (cachedContainerPart !== undefined) {
                    // Move the cached part back into the container part value
                    const partValue = getCommittedValue(cachedContainerPart);
                    const cachedPart = partValue.pop();
                    // Move cached part back into DOM
                    clearPart(containerPart);
                    insertPart(containerPart, undefined, cachedPart);
                    setCommittedValue(containerPart, [cachedPart]);
                }
            }
            this._value = v;
        }
        else {
            this._value = undefined;
        }
        return this.render(v);
    }
}
/**
 * Enables fast switching between multiple templates by caching the DOM nodes
 * and TemplateInstances produced by the templates.
 *
 * Example:
 *
 * ```js
 * let checked = false;
 *
 * html`
 *   ${cache(checked ? html`input is checked` : html`input is not checked`)}
 * `
 * ```
 */
const cache = directive(CacheDirective);

/**
 * @license
 * Copyright 2021 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
/**
 * Chooses and evaluates a template function from a list based on matching
 * the given `value` to a case.
 *
 * Cases are structured as `[caseValue, func]`. `value` is matched to
 * `caseValue` by strict equality. The first match is selected. Case values
 * can be of any type including primitives, objects, and symbols.
 *
 * This is similar to a switch statement, but as an expression and without
 * fallthrough.
 *
 * @example
 *
 * ```ts
 * render() {
 *   return html`
 *     ${choose(this.section, [
 *       ['home', () => html`<h1>Home</h1>`],
 *       ['about', () => html`<h1>About</h1>`]
 *     ],
 *     () => html`<h1>Error</h1>`)}
 *   `;
 * }
 * ```
 */
const choose = (value, cases, defaultCase) => {
    for (const c of cases) {
        const caseValue = c[0];
        if (caseValue === value) {
            const fn = c[1];
            return fn();
        }
    }
    return defaultCase === null || defaultCase === void 0 ? void 0 : defaultCase();
};

/**
 * @license
 * Copyright 2018 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
class ClassMapDirective extends Directive {
    constructor(partInfo) {
        var _a;
        super(partInfo);
        if (partInfo.type !== PartType.ATTRIBUTE ||
            partInfo.name !== 'class' ||
            ((_a = partInfo.strings) === null || _a === void 0 ? void 0 : _a.length) > 2) {
            throw new Error('`classMap()` can only be used in the `class` attribute ' +
                'and must be the only part in the attribute.');
        }
    }
    render(classInfo) {
        // Add spaces to ensure separation from static classes
        return (' ' +
            Object.keys(classInfo)
                .filter((key) => classInfo[key])
                .join(' ') +
            ' ');
    }
    update(part, [classInfo]) {
        var _a, _b;
        // Remember dynamic classes on the first render
        if (this._previousClasses === undefined) {
            this._previousClasses = new Set();
            if (part.strings !== undefined) {
                this._staticClasses = new Set(part.strings
                    .join(' ')
                    .split(/\s/)
                    .filter((s) => s !== ''));
            }
            for (const name in classInfo) {
                if (classInfo[name] && !((_a = this._staticClasses) === null || _a === void 0 ? void 0 : _a.has(name))) {
                    this._previousClasses.add(name);
                }
            }
            return this.render(classInfo);
        }
        const classList = part.element.classList;
        // Remove old classes that no longer apply
        // We use forEach() instead of for-of so that we don't require down-level
        // iteration.
        this._previousClasses.forEach((name) => {
            if (!(name in classInfo)) {
                classList.remove(name);
                this._previousClasses.delete(name);
            }
        });
        // Add or remove classes based on their classMap value
        for (const name in classInfo) {
            // We explicitly want a loose truthy check of `value` because it seems
            // more convenient that '' and 0 are skipped.
            const value = !!classInfo[name];
            if (value !== this._previousClasses.has(name) &&
                !((_b = this._staticClasses) === null || _b === void 0 ? void 0 : _b.has(name))) {
                if (value) {
                    classList.add(name);
                    this._previousClasses.add(name);
                }
                else {
                    classList.remove(name);
                    this._previousClasses.delete(name);
                }
            }
        }
        return noChange;
    }
}
/**
 * A directive that applies dynamic CSS classes.
 *
 * This must be used in the `class` attribute and must be the only part used in
 * the attribute. It takes each property in the `classInfo` argument and adds
 * the property name to the element's `classList` if the property value is
 * truthy; if the property value is falsey, the property name is removed from
 * the element's `class`.
 *
 * For example `{foo: bar}` applies the class `foo` if the value of `bar` is
 * truthy.
 *
 * @param classInfo
 */
const classMap = directive(ClassMapDirective);

/**
 * @license
 * Copyright 2018 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
// A sentinal that indicates guard() hasn't rendered anything yet
const initialValue = {};
class GuardDirective extends Directive {
    constructor() {
        super(...arguments);
        this._previousValue = initialValue;
    }
    render(_value, f) {
        return f();
    }
    update(_part, [value, f]) {
        if (Array.isArray(value)) {
            // Dirty-check arrays by item
            if (Array.isArray(this._previousValue) &&
                this._previousValue.length === value.length &&
                value.every((v, i) => v === this._previousValue[i])) {
                return noChange;
            }
        }
        else if (this._previousValue === value) {
            // Dirty-check non-arrays by identity
            return noChange;
        }
        // Copy the value if it's an array so that if it's mutated we don't forget
        // what the previous values were.
        this._previousValue = Array.isArray(value) ? Array.from(value) : value;
        const r = this.render(value, f);
        return r;
    }
}
/**
 * Prevents re-render of a template function until a single value or an array of
 * values changes.
 *
 * Values are checked against previous values with strict equality (`===`), and
 * so the check won't detect nested property changes inside objects or arrays.
 * Arrays values have each item checked against the previous value at the same
 * index with strict equality. Nested arrays are also checked only by strict
 * equality.
 *
 * Example:
 *
 * ```js
 * html`
 *   <div>
 *     ${guard([user.id, company.id], () => html`...`)}
 *   </div>
 * `
 * ```
 *
 * In this case, the template only rerenders if either `user.id` or `company.id`
 * changes.
 *
 * guard() is useful with immutable data patterns, by preventing expensive work
 * until data updates.
 *
 * Example:
 *
 * ```js
 * html`
 *   <div>
 *     ${guard([immutableItems], () => immutableItems.map(i => html`${i}`))}
 *   </div>
 * `
 * ```
 *
 * In this case, items are mapped over only when the array reference changes.
 *
 * @param value the value to check before re-rendering
 * @param f the template function
 */
const guard = directive(GuardDirective);

/**
 * @license
 * Copyright 2018 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
/**
 * For AttributeParts, sets the attribute if the value is defined and removes
 * the attribute if the value is undefined.
 *
 * For other part types, this directive is a no-op.
 */
const ifDefined = (value) => value !== null && value !== void 0 ? value : nothing;

/**
 * @license
 * Copyright 2021 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
function* join(items, joiner) {
    const isFunction = typeof joiner === 'function';
    if (items !== undefined) {
        let i = -1;
        for (const value of items) {
            if (i > -1) {
                yield isFunction ? joiner(i) : joiner;
            }
            i++;
            yield value;
        }
    }
}

/**
 * @license
 * Copyright 2021 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
class Keyed extends Directive {
    constructor() {
        super(...arguments);
        this.key = nothing;
    }
    render(k, v) {
        this.key = k;
        return v;
    }
    update(part, [k, v]) {
        if (k !== this.key) {
            // Clear the part before returning a value. The one-arg form of
            // setCommittedValue sets the value to a sentinel which forces a
            // commit the next render.
            setCommittedValue(part);
            this.key = k;
        }
        return v;
    }
}
/**
 * Associates a renderable value with a unique key. When the key changes, the
 * previous DOM is removed and disposed before rendering the next value, even
 * if the value - such as a template - is the same.
 *
 * This is useful for forcing re-renders of stateful components, or working
 * with code that expects new data to generate new HTML elements, such as some
 * animation techniques.
 */
const keyed = directive(Keyed);

/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
class LiveDirective extends Directive {
    constructor(partInfo) {
        super(partInfo);
        if (!(partInfo.type === PartType.PROPERTY ||
            partInfo.type === PartType.ATTRIBUTE ||
            partInfo.type === PartType.BOOLEAN_ATTRIBUTE)) {
            throw new Error('The `live` directive is not allowed on child or event bindings');
        }
        if (!isSingleExpression(partInfo)) {
            throw new Error('`live` bindings can only contain a single expression');
        }
    }
    render(value) {
        return value;
    }
    update(part, [value]) {
        if (value === noChange || value === nothing) {
            return value;
        }
        const element = part.element;
        const name = part.name;
        if (part.type === PartType.PROPERTY) {
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            if (value === element[name]) {
                return noChange;
            }
        }
        else if (part.type === PartType.BOOLEAN_ATTRIBUTE) {
            if (!!value === element.hasAttribute(name)) {
                return noChange;
            }
        }
        else if (part.type === PartType.ATTRIBUTE) {
            if (element.getAttribute(name) === String(value)) {
                return noChange;
            }
        }
        // Resets the part's value, causing its dirty-check to fail so that it
        // always sets the value.
        setCommittedValue(part);
        return value;
    }
}
/**
 * Checks binding values against live DOM values, instead of previously bound
 * values, when determining whether to update the value.
 *
 * This is useful for cases where the DOM value may change from outside of
 * lit-html, such as with a binding to an `<input>` element's `value` property,
 * a content editable elements text, or to a custom element that changes it's
 * own properties or attributes.
 *
 * In these cases if the DOM value changes, but the value set through lit-html
 * bindings hasn't, lit-html won't know to update the DOM value and will leave
 * it alone. If this is not what you want--if you want to overwrite the DOM
 * value with the bound value no matter what--use the `live()` directive:
 *
 * ```js
 * html`<input .value=${live(x)}>`
 * ```
 *
 * `live()` performs a strict equality check against the live DOM value, and if
 * the new value is equal to the live value, does nothing. This means that
 * `live()` should not be used when the binding will cause a type conversion. If
 * you use `live()` with an attribute binding, make sure that only strings are
 * passed in, or the binding will update every render.
 */
const live = directive(LiveDirective);

/**
 * @license
 * Copyright 2021 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
/**
 * Returns an iterable containing the result of calling `f(value)` on each
 * value in `items`.
 *
 * @example
 *
 * ```ts
 * render() {
 *   return html`
 *     <ul>
 *       ${map(items, (i) => html`<li>${i}</li>`)}
 *     </ul>
 *   `;
 * }
 * ```
 */
function* map(items, f) {
    if (items !== undefined) {
        let i = 0;
        for (const value of items) {
            yield f(value, i++);
        }
    }
}

/**
 * @license
 * Copyright 2021 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
function* range(startOrEnd, end, step = 1) {
    const start = end === undefined ? 0 : startOrEnd;
    end !== null && end !== void 0 ? end : (end = startOrEnd);
    for (let i = start; step > 0 ? i < end : end < i; i += step) {
        yield i;
    }
}

/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
/**
 * Creates a new Ref object, which is container for a reference to an element.
 */
const createRef = () => new Ref();
/**
 * An object that holds a ref value.
 */
class Ref {
}
// When callbacks are used for refs, this map tracks the last value the callback
// was called with, for ensuring a directive doesn't clear the ref if the ref
// has already been rendered to a new spot. It is double-keyed on both the
// context (`options.host`) and the callback, since we auto-bind class methods
// to `options.host`.
const lastElementForContextAndCallback = new WeakMap();
class RefDirective extends AsyncDirective {
    render(_ref) {
        return nothing;
    }
    update(part, [ref]) {
        var _a;
        const refChanged = ref !== this._ref;
        if (refChanged && this._ref !== undefined) {
            // The ref passed to the directive has changed;
            // unset the previous ref's value
            this._updateRefValue(undefined);
        }
        if (refChanged || this._lastElementForRef !== this._element) {
            // We either got a new ref or this is the first render;
            // store the ref/element & update the ref value
            this._ref = ref;
            this._context = (_a = part.options) === null || _a === void 0 ? void 0 : _a.host;
            this._updateRefValue((this._element = part.element));
        }
        return nothing;
    }
    _updateRefValue(element) {
        var _a;
        if (typeof this._ref === 'function') {
            // If the current ref was called with a previous value, call with
            // `undefined`; We do this to ensure callbacks are called in a consistent
            // way regardless of whether a ref might be moving up in the tree (in
            // which case it would otherwise be called with the new value before the
            // previous one unsets it) and down in the tree (where it would be unset
            // before being set). Note that element lookup is keyed by
            // both the context and the callback, since we allow passing unbound
            // functions that are called on options.host, and we want to treat
            // these as unique "instances" of a function.
            const context = (_a = this._context) !== null && _a !== void 0 ? _a : globalThis;
            let lastElementForCallback = lastElementForContextAndCallback.get(context);
            if (lastElementForCallback === undefined) {
                lastElementForCallback = new WeakMap();
                lastElementForContextAndCallback.set(context, lastElementForCallback);
            }
            if (lastElementForCallback.get(this._ref) !== undefined) {
                this._ref.call(this._context, undefined);
            }
            lastElementForCallback.set(this._ref, element);
            // Call the ref with the new element value
            if (element !== undefined) {
                this._ref.call(this._context, element);
            }
        }
        else {
            this._ref.value = element;
        }
    }
    get _lastElementForRef() {
        var _a, _b, _c;
        return typeof this._ref === 'function'
            ? (_b = lastElementForContextAndCallback
                .get((_a = this._context) !== null && _a !== void 0 ? _a : globalThis)) === null || _b === void 0 ? void 0 : _b.get(this._ref)
            : (_c = this._ref) === null || _c === void 0 ? void 0 : _c.value;
    }
    disconnected() {
        // Only clear the box if our element is still the one in it (i.e. another
        // directive instance hasn't rendered its element to it before us); that
        // only happens in the event of the directive being cleared (not via manual
        // disconnection)
        if (this._lastElementForRef === this._element) {
            this._updateRefValue(undefined);
        }
    }
    reconnected() {
        // If we were manually disconnected, we can safely put our element back in
        // the box, since no rendering could have occurred to change its state
        this._updateRefValue(this._element);
    }
}
/**
 * Sets the value of a Ref object or calls a ref callback with the element it's
 * bound to.
 *
 * A Ref object acts as a container for a reference to an element. A ref
 * callback is a function that takes an element as its only argument.
 *
 * The ref directive sets the value of the Ref object or calls the ref callback
 * during rendering, if the referenced element changed.
 *
 * Note: If a ref callback is rendered to a different element position or is
 * removed in a subsequent render, it will first be called with `undefined`,
 * followed by another call with the new element it was rendered to (if any).
 *
 * ```js
 * // Using Ref object
 * const inputRef = createRef();
 * render(html`<input ${ref(inputRef)}>`, container);
 * inputRef.value.focus();
 *
 * // Using callback
 * const callback = (inputElement) => inputElement.focus();
 * render(html`<input ${ref(callback)}>`, container);
 * ```
 */
const ref = directive(RefDirective);

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
// Helper for generating a map of array item to its index over a subset
// of an array (used to lazily generate `newKeyToIndexMap` and
// `oldKeyToIndexMap`)
const generateMap = (list, start, end) => {
    const map = new Map();
    for (let i = start; i <= end; i++) {
        map.set(list[i], i);
    }
    return map;
};
class RepeatDirective extends Directive {
    constructor(partInfo) {
        super(partInfo);
        if (partInfo.type !== PartType.CHILD) {
            throw new Error('repeat() can only be used in text expressions');
        }
    }
    _getValuesAndKeys(items, keyFnOrTemplate, template) {
        let keyFn;
        if (template === undefined) {
            template = keyFnOrTemplate;
        }
        else if (keyFnOrTemplate !== undefined) {
            keyFn = keyFnOrTemplate;
        }
        const keys = [];
        const values = [];
        let index = 0;
        for (const item of items) {
            keys[index] = keyFn ? keyFn(item, index) : index;
            values[index] = template(item, index);
            index++;
        }
        return {
            values,
            keys,
        };
    }
    render(items, keyFnOrTemplate, template) {
        return this._getValuesAndKeys(items, keyFnOrTemplate, template).values;
    }
    update(containerPart, [items, keyFnOrTemplate, template]) {
        var _a;
        // Old part & key lists are retrieved from the last update (which may
        // be primed by hydration)
        const oldParts = getCommittedValue(containerPart);
        const { values: newValues, keys: newKeys } = this._getValuesAndKeys(items, keyFnOrTemplate, template);
        // We check that oldParts, the committed value, is an Array as an
        // indicator that the previous value came from a repeat() call. If
        // oldParts is not an Array then this is the first render and we return
        // an array for lit-html's array handling to render, and remember the
        // keys.
        if (!Array.isArray(oldParts)) {
            this._itemKeys = newKeys;
            return newValues;
        }
        // In SSR hydration it's possible for oldParts to be an arrray but for us
        // to not have item keys because the update() hasn't run yet. We set the
        // keys to an empty array. This will cause all oldKey/newKey comparisons
        // to fail and execution to fall to the last nested brach below which
        // reuses the oldPart.
        const oldKeys = ((_a = this._itemKeys) !== null && _a !== void 0 ? _a : (this._itemKeys = []));
        // New part list will be built up as we go (either reused from
        // old parts or created for new keys in this update). This is
        // saved in the above cache at the end of the update.
        const newParts = [];
        // Maps from key to index for current and previous update; these
        // are generated lazily only when needed as a performance
        // optimization, since they are only required for multiple
        // non-contiguous changes in the list, which are less common.
        let newKeyToIndexMap;
        let oldKeyToIndexMap;
        // Head and tail pointers to old parts and new values
        let oldHead = 0;
        let oldTail = oldParts.length - 1;
        let newHead = 0;
        let newTail = newValues.length - 1;
        // Overview of O(n) reconciliation algorithm (general approach
        // based on ideas found in ivi, vue, snabbdom, etc.):
        //
        // * We start with the list of old parts and new values (and
        //   arrays of their respective keys), head/tail pointers into
        //   each, and we build up the new list of parts by updating
        //   (and when needed, moving) old parts or creating new ones.
        //   The initial scenario might look like this (for brevity of
        //   the diagrams, the numbers in the array reflect keys
        //   associated with the old parts or new values, although keys
        //   and parts/values are actually stored in parallel arrays
        //   indexed using the same head/tail pointers):
        //
        //      oldHead v                 v oldTail
        //   oldKeys:  [0, 1, 2, 3, 4, 5, 6]
        //   newParts: [ ,  ,  ,  ,  ,  ,  ]
        //   newKeys:  [0, 2, 1, 4, 3, 7, 6] <- reflects the user's new
        //                                      item order
        //      newHead ^                 ^ newTail
        //
        // * Iterate old & new lists from both sides, updating,
        //   swapping, or removing parts at the head/tail locations
        //   until neither head nor tail can move.
        //
        // * Example below: keys at head pointers match, so update old
        //   part 0 in-place (no need to move it) and record part 0 in
        //   the `newParts` list. The last thing we do is advance the
        //   `oldHead` and `newHead` pointers (will be reflected in the
        //   next diagram).
        //
        //      oldHead v                 v oldTail
        //   oldKeys:  [0, 1, 2, 3, 4, 5, 6]
        //   newParts: [0,  ,  ,  ,  ,  ,  ] <- heads matched: update 0
        //   newKeys:  [0, 2, 1, 4, 3, 7, 6]    and advance both oldHead
        //                                      & newHead
        //      newHead ^                 ^ newTail
        //
        // * Example below: head pointers don't match, but tail
        //   pointers do, so update part 6 in place (no need to move
        //   it), and record part 6 in the `newParts` list. Last,
        //   advance the `oldTail` and `oldHead` pointers.
        //
        //         oldHead v              v oldTail
        //   oldKeys:  [0, 1, 2, 3, 4, 5, 6]
        //   newParts: [0,  ,  ,  ,  ,  , 6] <- tails matched: update 6
        //   newKeys:  [0, 2, 1, 4, 3, 7, 6]    and advance both oldTail
        //                                      & newTail
        //         newHead ^              ^ newTail
        //
        // * If neither head nor tail match; next check if one of the
        //   old head/tail items was removed. We first need to generate
        //   the reverse map of new keys to index (`newKeyToIndexMap`),
        //   which is done once lazily as a performance optimization,
        //   since we only hit this case if multiple non-contiguous
        //   changes were made. Note that for contiguous removal
        //   anywhere in the list, the head and tails would advance
        //   from either end and pass each other before we get to this
        //   case and removals would be handled in the final while loop
        //   without needing to generate the map.
        //
        // * Example below: The key at `oldTail` was removed (no longer
        //   in the `newKeyToIndexMap`), so remove that part from the
        //   DOM and advance just the `oldTail` pointer.
        //
        //         oldHead v           v oldTail
        //   oldKeys:  [0, 1, 2, 3, 4, 5, 6]
        //   newParts: [0,  ,  ,  ,  ,  , 6] <- 5 not in new map: remove
        //   newKeys:  [0, 2, 1, 4, 3, 7, 6]    5 and advance oldTail
        //         newHead ^           ^ newTail
        //
        // * Once head and tail cannot move, any mismatches are due to
        //   either new or moved items; if a new key is in the previous
        //   "old key to old index" map, move the old part to the new
        //   location, otherwise create and insert a new part. Note
        //   that when moving an old part we null its position in the
        //   oldParts array if it lies between the head and tail so we
        //   know to skip it when the pointers get there.
        //
        // * Example below: neither head nor tail match, and neither
        //   were removed; so find the `newHead` key in the
        //   `oldKeyToIndexMap`, and move that old part's DOM into the
        //   next head position (before `oldParts[oldHead]`). Last,
        //   null the part in the `oldPart` array since it was
        //   somewhere in the remaining oldParts still to be scanned
        //   (between the head and tail pointers) so that we know to
        //   skip that old part on future iterations.
        //
        //         oldHead v        v oldTail
        //   oldKeys:  [0, 1, -, 3, 4, 5, 6]
        //   newParts: [0, 2,  ,  ,  ,  , 6] <- stuck: update & move 2
        //   newKeys:  [0, 2, 1, 4, 3, 7, 6]    into place and advance
        //                                      newHead
        //         newHead ^           ^ newTail
        //
        // * Note that for moves/insertions like the one above, a part
        //   inserted at the head pointer is inserted before the
        //   current `oldParts[oldHead]`, and a part inserted at the
        //   tail pointer is inserted before `newParts[newTail+1]`. The
        //   seeming asymmetry lies in the fact that new parts are
        //   moved into place outside in, so to the right of the head
        //   pointer are old parts, and to the right of the tail
        //   pointer are new parts.
        //
        // * We always restart back from the top of the algorithm,
        //   allowing matching and simple updates in place to
        //   continue...
        //
        // * Example below: the head pointers once again match, so
        //   simply update part 1 and record it in the `newParts`
        //   array.  Last, advance both head pointers.
        //
        //         oldHead v        v oldTail
        //   oldKeys:  [0, 1, -, 3, 4, 5, 6]
        //   newParts: [0, 2, 1,  ,  ,  , 6] <- heads matched: update 1
        //   newKeys:  [0, 2, 1, 4, 3, 7, 6]    and advance both oldHead
        //                                      & newHead
        //            newHead ^        ^ newTail
        //
        // * As mentioned above, items that were moved as a result of
        //   being stuck (the final else clause in the code below) are
        //   marked with null, so we always advance old pointers over
        //   these so we're comparing the next actual old value on
        //   either end.
        //
        // * Example below: `oldHead` is null (already placed in
        //   newParts), so advance `oldHead`.
        //
        //            oldHead v     v oldTail
        //   oldKeys:  [0, 1, -, 3, 4, 5, 6] <- old head already used:
        //   newParts: [0, 2, 1,  ,  ,  , 6]    advance oldHead
        //   newKeys:  [0, 2, 1, 4, 3, 7, 6]
        //               newHead ^     ^ newTail
        //
        // * Note it's not critical to mark old parts as null when they
        //   are moved from head to tail or tail to head, since they
        //   will be outside the pointer range and never visited again.
        //
        // * Example below: Here the old tail key matches the new head
        //   key, so the part at the `oldTail` position and move its
        //   DOM to the new head position (before `oldParts[oldHead]`).
        //   Last, advance `oldTail` and `newHead` pointers.
        //
        //               oldHead v  v oldTail
        //   oldKeys:  [0, 1, -, 3, 4, 5, 6]
        //   newParts: [0, 2, 1, 4,  ,  , 6] <- old tail matches new
        //   newKeys:  [0, 2, 1, 4, 3, 7, 6]   head: update & move 4,
        //                                     advance oldTail & newHead
        //               newHead ^     ^ newTail
        //
        // * Example below: Old and new head keys match, so update the
        //   old head part in place, and advance the `oldHead` and
        //   `newHead` pointers.
        //
        //               oldHead v oldTail
        //   oldKeys:  [0, 1, -, 3, 4, 5, 6]
        //   newParts: [0, 2, 1, 4, 3,   ,6] <- heads match: update 3
        //   newKeys:  [0, 2, 1, 4, 3, 7, 6]    and advance oldHead &
        //                                      newHead
        //                  newHead ^  ^ newTail
        //
        // * Once the new or old pointers move past each other then all
        //   we have left is additions (if old list exhausted) or
        //   removals (if new list exhausted). Those are handled in the
        //   final while loops at the end.
        //
        // * Example below: `oldHead` exceeded `oldTail`, so we're done
        //   with the main loop.  Create the remaining part and insert
        //   it at the new head position, and the update is complete.
        //
        //                   (oldHead > oldTail)
        //   oldKeys:  [0, 1, -, 3, 4, 5, 6]
        //   newParts: [0, 2, 1, 4, 3, 7 ,6] <- create and insert 7
        //   newKeys:  [0, 2, 1, 4, 3, 7, 6]
        //                     newHead ^ newTail
        //
        // * Note that the order of the if/else clauses is not
        //   important to the algorithm, as long as the null checks
        //   come first (to ensure we're always working on valid old
        //   parts) and that the final else clause comes last (since
        //   that's where the expensive moves occur). The order of
        //   remaining clauses is is just a simple guess at which cases
        //   will be most common.
        //
        // * Note, we could calculate the longest
        //   increasing subsequence (LIS) of old items in new position,
        //   and only move those not in the LIS set. However that costs
        //   O(nlogn) time and adds a bit more code, and only helps
        //   make rare types of mutations require fewer moves. The
        //   above handles removes, adds, reversal, swaps, and single
        //   moves of contiguous items in linear time, in the minimum
        //   number of moves. As the number of multiple moves where LIS
        //   might help approaches a random shuffle, the LIS
        //   optimization becomes less helpful, so it seems not worth
        //   the code at this point. Could reconsider if a compelling
        //   case arises.
        while (oldHead <= oldTail && newHead <= newTail) {
            if (oldParts[oldHead] === null) {
                // `null` means old part at head has already been used
                // below; skip
                oldHead++;
            }
            else if (oldParts[oldTail] === null) {
                // `null` means old part at tail has already been used
                // below; skip
                oldTail--;
            }
            else if (oldKeys[oldHead] === newKeys[newHead]) {
                // Old head matches new head; update in place
                newParts[newHead] = setChildPartValue(oldParts[oldHead], newValues[newHead]);
                oldHead++;
                newHead++;
            }
            else if (oldKeys[oldTail] === newKeys[newTail]) {
                // Old tail matches new tail; update in place
                newParts[newTail] = setChildPartValue(oldParts[oldTail], newValues[newTail]);
                oldTail--;
                newTail--;
            }
            else if (oldKeys[oldHead] === newKeys[newTail]) {
                // Old head matches new tail; update and move to new tail
                newParts[newTail] = setChildPartValue(oldParts[oldHead], newValues[newTail]);
                insertPart(containerPart, newParts[newTail + 1], oldParts[oldHead]);
                oldHead++;
                newTail--;
            }
            else if (oldKeys[oldTail] === newKeys[newHead]) {
                // Old tail matches new head; update and move to new head
                newParts[newHead] = setChildPartValue(oldParts[oldTail], newValues[newHead]);
                insertPart(containerPart, oldParts[oldHead], oldParts[oldTail]);
                oldTail--;
                newHead++;
            }
            else {
                if (newKeyToIndexMap === undefined) {
                    // Lazily generate key-to-index maps, used for removals &
                    // moves below
                    newKeyToIndexMap = generateMap(newKeys, newHead, newTail);
                    oldKeyToIndexMap = generateMap(oldKeys, oldHead, oldTail);
                }
                if (!newKeyToIndexMap.has(oldKeys[oldHead])) {
                    // Old head is no longer in new list; remove
                    removePart(oldParts[oldHead]);
                    oldHead++;
                }
                else if (!newKeyToIndexMap.has(oldKeys[oldTail])) {
                    // Old tail is no longer in new list; remove
                    removePart(oldParts[oldTail]);
                    oldTail--;
                }
                else {
                    // Any mismatches at this point are due to additions or
                    // moves; see if we have an old part we can reuse and move
                    // into place
                    const oldIndex = oldKeyToIndexMap.get(newKeys[newHead]);
                    const oldPart = oldIndex !== undefined ? oldParts[oldIndex] : null;
                    if (oldPart === null) {
                        // No old part for this value; create a new one and
                        // insert it
                        const newPart = insertPart(containerPart, oldParts[oldHead]);
                        setChildPartValue(newPart, newValues[newHead]);
                        newParts[newHead] = newPart;
                    }
                    else {
                        // Reuse old part
                        newParts[newHead] = setChildPartValue(oldPart, newValues[newHead]);
                        insertPart(containerPart, oldParts[oldHead], oldPart);
                        // This marks the old part as having been used, so that
                        // it will be skipped in the first two checks above
                        oldParts[oldIndex] = null;
                    }
                    newHead++;
                }
            }
        }
        // Add parts for any remaining new values
        while (newHead <= newTail) {
            // For all remaining additions, we insert before last new
            // tail, since old pointers are no longer valid
            const newPart = insertPart(containerPart, newParts[newTail + 1]);
            setChildPartValue(newPart, newValues[newHead]);
            newParts[newHead++] = newPart;
        }
        // Remove any remaining unused old parts
        while (oldHead <= oldTail) {
            const oldPart = oldParts[oldHead++];
            if (oldPart !== null) {
                removePart(oldPart);
            }
        }
        // Save order of new parts for next round
        this._itemKeys = newKeys;
        // Directly set part value, bypassing it's dirty-checking
        setCommittedValue(containerPart, newParts);
        return noChange;
    }
}
/**
 * A directive that repeats a series of values (usually `TemplateResults`)
 * generated from an iterable, and updates those items efficiently when the
 * iterable changes based on user-provided `keys` associated with each item.
 *
 * Note that if a `keyFn` is provided, strict key-to-DOM mapping is maintained,
 * meaning previous DOM for a given key is moved into the new position if
 * needed, and DOM will never be reused with values for different keys (new DOM
 * will always be created for new keys). This is generally the most efficient
 * way to use `repeat` since it performs minimum unnecessary work for insertions
 * and removals.
 *
 * The `keyFn` takes two parameters, the item and its index, and returns a unique key value.
 *
 * ```js
 * html`
 *   <ol>
 *     ${repeat(this.items, (item) => item.id, (item, index) => {
 *       return html`<li>${index}: ${item.name}</li>`;
 *     })}
 *   </ol>
 * `
 * ```
 *
 * **Important**: If providing a `keyFn`, keys *must* be unique for all items in a
 * given call to `repeat`. The behavior when two or more items have the same key
 * is undefined.
 *
 * If no `keyFn` is provided, this directive will perform similar to mapping
 * items to values, and DOM will be reused against potentially different items.
 */
const repeat = directive(RepeatDirective);

/**
 * @license
 * Copyright 2018 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
class StyleMapDirective extends Directive {
    constructor(partInfo) {
        var _a;
        super(partInfo);
        if (partInfo.type !== PartType.ATTRIBUTE ||
            partInfo.name !== 'style' ||
            ((_a = partInfo.strings) === null || _a === void 0 ? void 0 : _a.length) > 2) {
            throw new Error('The `styleMap` directive must be used in the `style` attribute ' +
                'and must be the only part in the attribute.');
        }
    }
    render(styleInfo) {
        return Object.keys(styleInfo).reduce((style, prop) => {
            return style + prop.slice(0, 0);
        }, '');
    }
    update(part, [styleInfo]) {
        const { style } = part.element;
        if (this._previousStyleProperties === undefined) {
            this._previousStyleProperties = new Set();
        }
        // Remove old properties that no longer exist in styleInfo
        // We use forEach() instead of for-of so that re don't require down-level
        // iteration.
        this._previousStyleProperties.forEach((name) => {
            // If the name isn't in styleInfo or it's null/undefined
            if (styleInfo[name] == null) {
                this._previousStyleProperties.delete(name);
                if (name.includes('-')) {
                    style.removeProperty(name);
                }
                else {
                    // Note reset using empty string (vs null) as IE11 does not always
                    // reset via null (https://developer.mozilla.org/en-US/docs/Web/API/ElementCSSInlineStyle/style#setting_styles)
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    style[name] = '';
                }
            }
        });
        // Add or update properties
        for (const name in styleInfo) {
            const value = styleInfo[name];
            if (value != null) {
                this._previousStyleProperties.add(name);
                if (name.includes('-')) {
                    style.setProperty(name, value);
                }
                else {
                    // eslint-disable-next-line @typescript-eslint/no-explicit-any
                    style[name] = value;
                }
            }
        }
        return noChange;
    }
}
/**
 * A directive that applies CSS properties to an element.
 *
 * `styleMap` can only be used in the `style` attribute and must be the only
 * expression in the attribute. It takes the property names in the
 * {@link StyleInfo styleInfo} object and adds the property values as CSS
 * properties. Property names with dashes (`-`) are assumed to be valid CSS
 * property names and set on the element's style object using `setProperty()`.
 * Names without dashes are assumed to be camelCased JavaScript property names
 * and set on the element's style object using property assignment, allowing the
 * style object to translate JavaScript-style names to CSS property names.
 *
 * For example `styleMap({backgroundColor: 'red', 'border-top': '5px', '--size':
 * '0'})` sets the `background-color`, `border-top` and `--size` properties.
 *
 * @param styleInfo
 * @see {@link https://lit.dev/docs/templates/directives/#stylemap styleMap code samples on Lit.dev}
 */
const styleMap = directive(StyleMapDirective);

/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
class TemplateContentDirective extends Directive {
    constructor(partInfo) {
        super(partInfo);
        if (partInfo.type !== PartType.CHILD) {
            throw new Error('templateContent can only be used in child bindings');
        }
    }
    render(template) {
        if (this._previousTemplate === template) {
            return noChange;
        }
        this._previousTemplate = template;
        return document.importNode(template.content, true);
    }
}
/**
 * Renders the content of a template element as HTML.
 *
 * Note, the template should be developer controlled and not user controlled.
 * Rendering a user-controlled template with this directive
 * could lead to cross-site-scripting vulnerabilities.
 */
const templateContent = directive(TemplateContentDirective);

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
const HTML_RESULT = 1;
class UnsafeHTMLDirective extends Directive {
    constructor(partInfo) {
        super(partInfo);
        this._value = nothing;
        if (partInfo.type !== PartType.CHILD) {
            throw new Error(`${this.constructor.directiveName}() can only be used in child bindings`);
        }
    }
    render(value) {
        if (value === nothing || value == null) {
            this._templateResult = undefined;
            return (this._value = value);
        }
        if (value === noChange) {
            return value;
        }
        if (typeof value != 'string') {
            throw new Error(`${this.constructor.directiveName}() called with a non-string value`);
        }
        if (value === this._value) {
            return this._templateResult;
        }
        this._value = value;
        const strings = [value];
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        strings.raw = strings;
        // WARNING: impersonating a TemplateResult like this is extremely
        // dangerous. Third-party directives should not do this.
        return (this._templateResult = {
            // Cast to a known set of integers that satisfy ResultType so that we
            // don't have to export ResultType and possibly encourage this pattern.
            // This property needs to remain unminified.
            ['_$litType$']: this.constructor
                .resultType,
            strings,
            values: [],
        });
    }
}
UnsafeHTMLDirective.directiveName = 'unsafeHTML';
UnsafeHTMLDirective.resultType = HTML_RESULT;
/**
 * Renders the result as HTML, rather than text.
 *
 * The values `undefined`, `null`, and `nothing`, will all result in no content
 * (empty string) being rendered.
 *
 * Note, this is unsafe to use with any user-provided input that hasn't been
 * sanitized or escaped, as it may lead to cross-site-scripting
 * vulnerabilities.
 */
const unsafeHTML = directive(UnsafeHTMLDirective);

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
const SVG_RESULT = 2;
class UnsafeSVGDirective extends UnsafeHTMLDirective {
}
UnsafeSVGDirective.directiveName = 'unsafeSVG';
UnsafeSVGDirective.resultType = SVG_RESULT;
/**
 * Renders the result as SVG, rather than text.
 *
 * The values `undefined`, `null`, and `nothing`, will all result in no content
 * (empty string) being rendered.
 *
 * Note, this is unsafe to use with any user-provided input that hasn't been
 * sanitized or escaped, as it may lead to cross-site-scripting
 * vulnerabilities.
 */
const unsafeSVG = directive(UnsafeSVGDirective);

/**
 * @license
 * Copyright 2017 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
const isPromise = (x) => {
    return !isPrimitive(x) && typeof x.then === 'function';
};
// Effectively infinity, but a SMI.
const _infinity = 0x3fffffff;
class UntilDirective extends AsyncDirective {
    constructor() {
        super(...arguments);
        this.__lastRenderedIndex = _infinity;
        this.__values = [];
        this.__weakThis = new PseudoWeakRef(this);
        this.__pauser = new Pauser();
    }
    render(...args) {
        var _a;
        return (_a = args.find((x) => !isPromise(x))) !== null && _a !== void 0 ? _a : noChange;
    }
    update(_part, args) {
        const previousValues = this.__values;
        let previousLength = previousValues.length;
        this.__values = args;
        const weakThis = this.__weakThis;
        const pauser = this.__pauser;
        // If our initial render occurs while disconnected, ensure that the pauser
        // and weakThis are in the disconnected state
        if (!this.isConnected) {
            this.disconnected();
        }
        for (let i = 0; i < args.length; i++) {
            // If we've rendered a higher-priority value already, stop.
            if (i > this.__lastRenderedIndex) {
                break;
            }
            const value = args[i];
            // Render non-Promise values immediately
            if (!isPromise(value)) {
                this.__lastRenderedIndex = i;
                // Since a lower-priority value will never overwrite a higher-priority
                // synchronous value, we can stop processing now.
                return value;
            }
            // If this is a Promise we've already handled, skip it.
            if (i < previousLength && value === previousValues[i]) {
                continue;
            }
            // We have a Promise that we haven't seen before, so priorities may have
            // changed. Forget what we rendered before.
            this.__lastRenderedIndex = _infinity;
            previousLength = 0;
            // Note, the callback avoids closing over `this` so that the directive
            // can be gc'ed before the promise resolves; instead `this` is retrieved
            // from `weakThis`, which can break the hard reference in the closure when
            // the directive disconnects
            Promise.resolve(value).then(async (result) => {
                // If we're disconnected, wait until we're (maybe) reconnected
                // The while loop here handles the case that the connection state
                // thrashes, causing the pauser to resume and then get re-paused
                while (pauser.get()) {
                    await pauser.get();
                }
                // If the callback gets here and there is no `this`, it means that the
                // directive has been disconnected and garbage collected and we don't
                // need to do anything else
                const _this = weakThis.deref();
                if (_this !== undefined) {
                    const index = _this.__values.indexOf(value);
                    // If state.values doesn't contain the value, we've re-rendered without
                    // the value, so don't render it. Then, only render if the value is
                    // higher-priority than what's already been rendered.
                    if (index > -1 && index < _this.__lastRenderedIndex) {
                        _this.__lastRenderedIndex = index;
                        _this.setValue(result);
                    }
                }
            });
        }
        return noChange;
    }
    disconnected() {
        this.__weakThis.disconnect();
        this.__pauser.pause();
    }
    reconnected() {
        this.__weakThis.reconnect(this);
        this.__pauser.resume();
    }
}
/**
 * Renders one of a series of values, including Promises, to a Part.
 *
 * Values are rendered in priority order, with the first argument having the
 * highest priority and the last argument having the lowest priority. If a
 * value is a Promise, low-priority values will be rendered until it resolves.
 *
 * The priority of values can be used to create placeholder content for async
 * data. For example, a Promise with pending content can be the first,
 * highest-priority, argument, and a non_promise loading indicator template can
 * be used as the second, lower-priority, argument. The loading indicator will
 * render immediately, and the primary content will render when the Promise
 * resolves.
 *
 * Example:
 *
 * ```js
 * const content = fetch('./content.txt').then(r => r.text());
 * html`${until(content, html`<span>Loading...</span>`)}`
 * ```
 */
const until = directive(UntilDirective);
/**
 * The type of the class that powers this directive. Necessary for naming the
 * directive's return type.
 */
// export type {UntilDirective};

/**
 * @license
 * Copyright 2021 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
function when(condition, trueCase, falseCase) {
    return condition ? trueCase() : falseCase === null || falseCase === void 0 ? void 0 : falseCase();
}

/**
 * @license
 * Copyright 2020 Google LLC
 * SPDX-License-Identifier: BSD-3-Clause
 */
/**
 * Prevents JSON injection attacks.
 *
 * The goals of this brand:
 *   1) fast to check
 *   2) code is small on the wire
 *   3) multiple versions of Lit in a single page will all produce mutually
 *      interoperable StaticValues
 *   4) normal JSON.parse (without an unusual reviver) can not produce a
 *      StaticValue
 *
 * Symbols satisfy (1), (2), and (4). We use Symbol.for to satisfy (3), but
 * we don't care about the key, so we break ties via (2) and use the empty
 * string.
 */
const brand = Symbol.for('');
/** Safely extracts the string part of a StaticValue. */
const unwrapStaticValue = (value) => {
    if ((value === null || value === void 0 ? void 0 : value.r) !== brand) {
        return undefined;
    }
    return value === null || value === void 0 ? void 0 : value['_$litStatic$'];
};
/**
 * Wraps a string so that it behaves like part of the static template
 * strings instead of a dynamic value.
 *
 * Users must take care to ensure that adding the static string to the template
 * results in well-formed HTML, or else templates may break unexpectedly.
 *
 * Note that this function is unsafe to use on untrusted content, as it will be
 * directly parsed into HTML. Do not pass user input to this function
 * without sanitizing it.
 *
 * Static values can be changed, but they will cause a complete re-render
 * since they effectively create a new template.
 */
const unsafeStatic = (value) => ({
    ['_$litStatic$']: value,
    r: brand,
});
const textFromStatic = (value) => {
    if (value['_$litStatic$'] !== undefined) {
        return value['_$litStatic$'];
    }
    else {
        throw new Error(`Value passed to 'literal' function must be a 'literal' result: ${value}. Use 'unsafeStatic' to pass non-literal values, but
            take care to ensure page security.`);
    }
};
/**
 * Tags a string literal so that it behaves like part of the static template
 * strings instead of a dynamic value.
 *
 * The only values that may be used in template expressions are other tagged
 * `literal` results or `unsafeStatic` values (note that untrusted content
 * should never be passed to `unsafeStatic`).
 *
 * Users must take care to ensure that adding the static string to the template
 * results in well-formed HTML, or else templates may break unexpectedly.
 *
 * Static values can be changed, but they will cause a complete re-render since
 * they effectively create a new template.
 */
const literal = (strings, ...values) => ({
    ['_$litStatic$']: values.reduce((acc, v, idx) => acc + textFromStatic(v) + strings[idx + 1], strings[0]),
    r: brand,
});
const stringsCache = new Map();
/**
 * Wraps a lit-html template tag (`html` or `svg`) to add static value support.
 */
const withStatic = (coreTag) => (strings, ...values) => {
    const l = values.length;
    let staticValue;
    let dynamicValue;
    const staticStrings = [];
    const dynamicValues = [];
    let i = 0;
    let hasStatics = false;
    let s;
    while (i < l) {
        s = strings[i];
        // Collect any unsafeStatic values, and their following template strings
        // so that we treat a run of template strings and unsafe static values as
        // a single template string.
        while (i < l &&
            ((dynamicValue = values[i]),
                (staticValue = unwrapStaticValue(dynamicValue))) !== undefined) {
            s += staticValue + strings[++i];
            hasStatics = true;
        }
        dynamicValues.push(dynamicValue);
        staticStrings.push(s);
        i++;
    }
    // If the last value isn't static (which would have consumed the last
    // string), then we need to add the last string.
    if (i === l) {
        staticStrings.push(strings[l]);
    }
    if (hasStatics) {
        const key = staticStrings.join('$$lit$$');
        strings = stringsCache.get(key);
        if (strings === undefined) {
            // Beware: in general this pattern is unsafe, and doing so may bypass
            // lit's security checks and allow an attacker to execute arbitrary
            // code and inject arbitrary content.
            // eslint-disable-next-line @typescript-eslint/no-explicit-any
            staticStrings.raw = staticStrings;
            stringsCache.set(key, (strings = staticStrings));
        }
        values = dynamicValues;
    }
    return coreTag(strings, ...values);
};
/**
 * Interprets a template literal as an HTML template that can efficiently
 * render to and update a container.
 *
 * Includes static value support from `lit-html/static.js`.
 */
const html = withStatic(html$1);
/**
 * Interprets a template literal as an SVG template that can efficiently
 * render to and update a container.
 *
 * Includes static value support from `lit-html/static.js`.
 */
const svg = withStatic(svg$1);

export { AsyncDirective, AsyncReplaceDirective, CSSResult, Directive, LitElement, PartType, ReactiveElement, TemplateResultType, UnsafeHTMLDirective, UntilDirective, UpdatingElement, _$LE, _$LH, adoptStyles, asyncAppend, asyncReplace, cache, choose, classMap, clearPart, createRef, css, defaultConverter, directive, getCommittedValue, getCompatibleStyle, getDirectiveClass, guard, html$1 as html, ifDefined, insertPart, isDirectiveResult, isPrimitive, isServer, isSingleExpression, isTemplateResult, join, keyed, literal, live, map, noChange, notEqual, nothing, range, ref, removePart, render, repeat, setChildPartValue, setCommittedValue, html as staticHtml, svg as staticSvg, styleMap, supportsAdoptingStyleSheets, svg$1 as svg, templateContent, unsafeCSS, unsafeHTML, unsafeSVG, unsafeStatic, until, when, withStatic };
PK
!<f;Xf�7�70chrome/toolkit/content/global/viewSourceUtils.js// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * To keep the global namespace safe, don't define global variables and
 * functions in this file.
 *
 * This file silently depends on contentAreaUtils.js for getDefaultFileName
 */

ChromeUtils.defineESModuleGetters(this, {
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

var gViewSourceUtils = {
  mnsIWebBrowserPersist: Ci.nsIWebBrowserPersist,
  mnsIWebProgress: Ci.nsIWebProgress,
  mnsIWebPageDescriptor: Ci.nsIWebPageDescriptor,

  // Get the ViewSource actor for a browsing context.
  getViewSourceActor(aBrowsingContext) {
    return aBrowsingContext.currentWindowGlobal.getActor("ViewSource");
  },

  /**
   * Get the ViewSourcePage actor.
   * @param object An object with `browsingContext` field
   */
  getPageActor({ browsingContext }) {
    return browsingContext.currentWindowGlobal.getActor("ViewSourcePage");
  },

  /**
   * Opens the view source window.
   *
   * @param aArgs (required)
   *        This Object can include the following properties:
   *
   *        URL (required):
   *          A string URL for the page we'd like to view the source of.
   *        browser (optional):
   *          The browser containing the document that we would like to view the
   *          source of. This is required if outerWindowID is passed.
   *        outerWindowID (optional):
   *          The outerWindowID of the content window containing the document that
   *          we want to view the source of. Pass this if you want to attempt to
   *          load the document source out of the network cache.
   *        lineNumber (optional):
   *          The line number to focus on once the source is loaded.
   */
  async viewSource(aArgs) {
    // Check if external view source is enabled.  If so, try it.  If it fails,
    // fallback to internal view source.
    if (Services.prefs.getBoolPref("view_source.editor.external")) {
      try {
        await this.openInExternalEditor(aArgs);
        return;
      } catch (data) {}
    }
    // Try existing browsers first.
    let browserWin = Services.wm.getMostRecentWindow("navigator:browser");
    if (browserWin && browserWin.BrowserCommands.viewSourceOfDocument) {
      browserWin.BrowserCommands.viewSourceOfDocument(aArgs);
      return;
    }
    // No browser window created yet, try to create one.
    let utils = this;
    Services.ww.registerNotification(function onOpen(win, topic) {
      if (
        win.document.documentURI !== "about:blank" ||
        topic !== "domwindowopened"
      ) {
        return;
      }
      Services.ww.unregisterNotification(onOpen);
      win.addEventListener(
        "load",
        () => {
          aArgs.viewSourceBrowser = win.gBrowser.selectedTab.linkedBrowser;
          utils.viewSourceInBrowser(aArgs);
        },
        { once: true }
      );
    });
    window.top.openWebLinkIn("about:blank", "current");
  },

  /**
   * Displays view source in the provided <browser>.  This allows for non-window
   * display methods, such as a tab from Firefox.
   *
   * @param aArgs
   *        An object with the following properties:
   *
   *        URL (required):
   *          A string URL for the page we'd like to view the source of.
   *        viewSourceBrowser (required):
   *          The browser to display the view source in.
   *        browser (optional):
   *          The browser containing the document that we would like to view the
   *          source of. This is required if outerWindowID is passed.
   *        outerWindowID (optional):
   *          The outerWindowID of the content window containing the document that
   *          we want to view the source of. Pass this if you want to attempt to
   *          load the document source out of the network cache.
   *        lineNumber (optional):
   *          The line number to focus on once the source is loaded.
   */
  viewSourceInBrowser({
    URL,
    viewSourceBrowser,
    browser,
    outerWindowID,
    lineNumber,
  }) {
    if (!URL) {
      throw new Error("Must supply a URL when opening view source.");
    }

    if (browser) {
      // If we're dealing with a remote browser, then the browser
      // for view source needs to be remote as well.
      if (viewSourceBrowser.remoteType != browser.remoteType) {
        // In this base case, where we are handed a <browser> someone else is
        // managing, we don't know for sure that it's safe to toggle remoteness.
        // For view source in a window, this is overridden to actually do the
        // flip if needed.
        throw new Error("View source browser's remoteness mismatch");
      }
    } else if (outerWindowID) {
      throw new Error("Must supply the browser if passing the outerWindowID");
    }

    let viewSourceActor = this.getViewSourceActor(
      viewSourceBrowser.browsingContext
    );
    viewSourceActor.sendAsyncMessage("ViewSource:LoadSource", {
      URL,
      outerWindowID,
      lineNumber,
    });
  },

  /**
   * Displays view source for a selection from some document in the provided
   * <browser>.  This allows for non-window display methods, such as a tab from
   * Firefox.
   *
   * @param aBrowsingContext:
   *        The child browsing context containing the document to view the source of.
   * @param aGetBrowserFn
   *        A function that will return a browser to open the source in.
   */
  async viewPartialSourceInBrowser(aBrowsingContext, aGetBrowserFn) {
    let sourceActor = this.getViewSourceActor(aBrowsingContext);
    if (sourceActor) {
      let data = await sourceActor.sendQuery("ViewSource:GetSelection", {});

      let targetActor = this.getViewSourceActor(
        aGetBrowserFn().browsingContext
      );
      targetActor.sendAsyncMessage("ViewSource:LoadSourceWithSelection", data);
    }
  },

  buildEditorArgs(aPath, aLineNumber) {
    // Determine the command line arguments to pass to the editor.
    // We currently support a %LINE% placeholder which is set to the passed
    // line number (or to 0 if there's none)
    var editorArgs = [];
    var args = Services.prefs.getCharPref("view_source.editor.args");
    if (args) {
      args = args.replace("%LINE%", aLineNumber || "0");
      // add the arguments to the array (keeping quoted strings intact)
      const argumentRE = /"([^"]+)"|(\S+)/g;
      while (argumentRE.test(args)) {
        editorArgs.push(RegExp.$1 || RegExp.$2);
      }
    }
    editorArgs.push(aPath);
    return editorArgs;
  },

  /**
   * Opens an external editor with the view source content.
   *
   * @param aArgs (required)
   *        This Object can include the following properties:
   *
   *        URL (required):
   *          A string URL for the page we'd like to view the source of.
   *        browser (optional):
   *          The browser containing the document that we would like to view the
   *          source of. This is required if outerWindowID is passed.
   *        outerWindowID (optional):
   *          The outerWindowID of the content window containing the document that
   *          we want to view the source of. Pass this if you want to attempt to
   *          load the document source out of the network cache.
   *        lineNumber (optional):
   *          The line number to focus on once the source is loaded.
   *
   * @return Promise
   *        The promise will be resolved or rejected based on whether the
   *        external editor attempt was successful.  Either way, the data object
   *        is passed along as well.
   */
  openInExternalEditor(aArgs) {
    return new Promise((resolve, reject) => {
      let data;
      let { URL, browser, lineNumber } = aArgs;
      data = {
        url: URL,
        lineNumber,
        isPrivate: false,
      };
      if (browser) {
        data.doc = {
          characterSet: browser.characterSet,
          contentType: browser.documentContentType,
          title: browser.contentTitle,
          cookieJarSettings: browser.cookieJarSettings,
        };
        data.isPrivate = PrivateBrowsingUtils.isBrowserPrivate(browser);
      }

      try {
        var editor = this.getExternalViewSourceEditor();
        if (!editor) {
          reject(data);
          return;
        }

        // make a uri
        var charset = data.doc ? data.doc.characterSet : null;
        var uri = Services.io.newURI(data.url, charset);
        data.uri = uri;

        var path;
        var contentType = data.doc ? data.doc.contentType : null;
        var cookieJarSettings = data.doc ? data.doc.cookieJarSettings : null;
        if (uri.scheme == "file") {
          // it's a local file; we can open it directly
          path = uri.QueryInterface(Ci.nsIFileURL).file.path;

          var editorArgs = this.buildEditorArgs(path, data.lineNumber);
          editor.runw(false, editorArgs, editorArgs.length);
          resolve(data);
        } else {
          // set up the progress listener with what we know so far
          this.viewSourceProgressListener.contentLoaded = false;
          this.viewSourceProgressListener.editor = editor;
          this.viewSourceProgressListener.resolve = resolve;
          this.viewSourceProgressListener.reject = reject;
          this.viewSourceProgressListener.data = data;

          // without a page descriptor, loadPage has no chance of working. download the file.
          var file = this.getTemporaryFile(uri, data.doc, contentType);
          this.viewSourceProgressListener.file = file;

          var webBrowserPersist = Cc[
            "@mozilla.org/embedding/browser/nsWebBrowserPersist;1"
          ].createInstance(this.mnsIWebBrowserPersist);
          // the default setting is to not decode. we need to decode.
          webBrowserPersist.persistFlags =
            this.mnsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
          webBrowserPersist.progressListener = this.viewSourceProgressListener;
          let ssm = Services.scriptSecurityManager;
          let principal = ssm.createContentPrincipal(
            data.uri,
            browser.contentPrincipal.originAttributes
          );
          webBrowserPersist.saveURI(
            uri,
            principal,
            null,
            null,
            cookieJarSettings,
            null,
            null,
            file,
            Ci.nsIContentPolicy.TYPE_SAVEAS_DOWNLOAD,
            data.isPrivate
          );

          let helperService = Cc[
            "@mozilla.org/uriloader/external-helper-app-service;1"
          ].getService(Ci.nsPIExternalAppLauncher);
          if (data.isPrivate) {
            // register the file to be deleted when possible
            helperService.deleteTemporaryPrivateFileWhenPossible(file);
          } else {
            // register the file to be deleted on app exit
            helperService.deleteTemporaryFileOnExit(file);
          }
        }
      } catch (ex) {
        // we failed loading it with the external editor.
        console.error(ex);
        reject(data);
      }
    });
  },

  // Returns nsIProcess of the external view source editor or null
  getExternalViewSourceEditor() {
    try {
      let viewSourceAppPath = Services.prefs.getComplexValue(
        "view_source.editor.path",
        Ci.nsIFile
      );
      let editor = Cc["@mozilla.org/process/util;1"].createInstance(
        Ci.nsIProcess
      );
      editor.init(viewSourceAppPath);

      return editor;
    } catch (ex) {
      console.error(ex);
    }

    return null;
  },

  viewSourceProgressListener: {
    mnsIWebProgressListener: Ci.nsIWebProgressListener,

    QueryInterface: ChromeUtils.generateQI([
      "nsIWebProgressListener",
      "nsISupportsWeakReference",
    ]),

    destroy() {
      this.editor = null;
      this.resolve = null;
      this.reject = null;
      this.data = null;
      this.file = null;
    },

    // This listener is used both for tracking the progress of an HTML parse
    // in one case and for tracking the progress of nsIWebBrowserPersist in
    // another case.
    onStateChange(aProgress, aRequest, aFlag, aStatus) {
      // once it's done loading...
      if (aFlag & this.mnsIWebProgressListener.STATE_STOP && aStatus == 0) {
        // We aren't waiting for the parser. Instead, we are waiting for
        // an nsIWebBrowserPersist.
        this.onContentLoaded();
      }
      return 0;
    },

    onContentLoaded() {
      // The progress listener may call this multiple times, so be sure we only
      // run once.
      if (this.contentLoaded) {
        return;
      }
      try {
        if (!this.file) {
          throw new Error("View-source progress listener should have a file!");
        }

        var editorArgs = gViewSourceUtils.buildEditorArgs(
          this.file.path,
          this.data.lineNumber
        );
        this.editor.runw(false, editorArgs, editorArgs.length);

        this.contentLoaded = true;
        this.resolve(this.data);
      } catch (ex) {
        // we failed loading it with the external editor.
        console.error(ex);
        this.reject(this.data);
      } finally {
        this.destroy();
      }
    },

    editor: null,
    resolve: null,
    reject: null,
    data: null,
    file: null,
  },

  // returns an nsIFile for the passed document in the system temp directory
  getTemporaryFile(aURI, aDocument, aContentType) {
    // include contentAreaUtils.js in our own context when we first need it
    if (!this._caUtils) {
      this._caUtils = {};
      Services.scriptloader.loadSubScript(
        "chrome://global/content/contentAreaUtils.js",
        this._caUtils
      );
    }

    var fileName = this._caUtils.getDefaultFileName(
      null,
      aURI,
      aDocument,
      null
    );

    const mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
    fileName = mimeService.validateFileNameForSaving(
      fileName,
      aContentType,
      mimeService.VALIDATE_DEFAULT
    );

    var tempFile = Services.dirsvc.get("TmpD", Ci.nsIFile);
    tempFile.append(fileName);
    return tempFile;
  },
};
PK
!<��t%��'chrome/toolkit/content/global/win.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this file,
   - You can obtain one at http://mozilla.org/MPL/2.0/. -->

<window id="win" />
PK
!<98�~~4chrome/toolkit/content/global/xml/XMLPrettyPrint.css@charset "UTF-8";
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("resource://content-accessible/viewsource.css");

:host {
  color-scheme: light dark;
}

#header {
  background-color: #ccc;
  border-bottom: 3px solid black;
  padding: 0.5em;
  margin-bottom: 1em;
}

@media (prefers-color-scheme: dark) {
  #header {
    background-color: #333;
    border-color: #555;
  }
}

#tree,
.expandable-children {
  margin-inline-start: 1em;
}

.expandable-body {
  display: inline-block;
}

.expandable-body[open] {
  display: block;
}

.expandable-opening {
  list-style: '+' outside;
}

[open] > .expandable-opening {
  list-style-type: '−';
}

.expandable-opening::marker {
  cursor: pointer;
  padding-inline-end: 2px;
  /* Don't want to inherit the styling from pi and comment elements */
  color: buttontext;
  font: initial;
}

.comment {
  font-family: monospace;
  white-space: pre;
}

.space-default {
  white-space: initial;
}

.space-preserve {
  white-space: pre-wrap;
}
PK
!<��G�EE4chrome/toolkit/content/global/xml/XMLPrettyPrint.xsl<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE overlay>

<xsl:stylesheet version="1.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns="http://www.w3.org/1999/xhtml">

  <xsl:output method="xml"/>

  <xsl:template match="/">
    <div id="top">
      <link rel="stylesheet" href="chrome://global/content/xml/XMLPrettyPrint.css"/>
      <div id="header">
        <p data-l10n-id="xml-nostylesheet"></p>
      </div>
      <main id="tree" class="highlight">
        <xsl:apply-templates/>
      </main>
    </div>
  </xsl:template>

  <xsl:template match="*">
    <div>
      <xsl:text>&lt;</xsl:text>
      <span class="start-tag"><xsl:value-of select="name(.)"/></span>
      <xsl:apply-templates select="@*"/>
      <xsl:text>/&gt;</xsl:text>
    </div>
  </xsl:template>

  <xsl:template match="*[node()]">
    <div><xsl:apply-templates mode="space" select="@xml:space"/>
      <xsl:text>&lt;</xsl:text>
      <span class="start-tag"><xsl:value-of select="name(.)"/></span>
      <xsl:apply-templates select="@*"/>
      <xsl:text>&gt;</xsl:text>

      <span class="text"><xsl:value-of select="."/></span>

      <xsl:text>&lt;/</xsl:text>
      <span class="end-tag"><xsl:value-of select="name(.)"/></span>
      <xsl:text>&gt;</xsl:text>
    </div>
  </xsl:template>

  <xsl:template match="*[* or processing-instruction() or comment() or string-length(.) &gt; 50]">
    <div><xsl:apply-templates mode="space" select="@xml:space"/>
      <details open="" class="expandable-body">
        <summary class="expandable-opening">
          <xsl:text>&lt;</xsl:text>
          <span class="start-tag"><xsl:value-of select="name(.)"/></span>
          <xsl:apply-templates select="@*"/>
          <xsl:text>&gt;</xsl:text>
        </summary>

        <div class="expandable-children"><xsl:apply-templates/></div>

      </details>
      <span class="expandable-closing">
        <xsl:text>&lt;/</xsl:text>
        <span class="end-tag"><xsl:value-of select="name(.)"/></span>
        <xsl:text>&gt;</xsl:text>
      </span>
    </div>
  </xsl:template>

  <xsl:template match="@xml:space[string() = 'default']" mode="space">
    <xsl:attribute name="class">space-default</xsl:attribute>
  </xsl:template>
  <xsl:template match="@xml:space[string() = 'preserve']" mode="space">
    <xsl:attribute name="class">space-preserve</xsl:attribute>
  </xsl:template>

  <xsl:template match="@*">
    <xsl:text> </xsl:text>
    <span class="attribute-name"><xsl:value-of select="name(.)"/></span>
    <xsl:text>=</xsl:text>
    <span class="attribute-value">"<xsl:value-of select="."/>"</span>
  </xsl:template>

  <xsl:template match="text()">
    <xsl:if test="normalize-space(.)">
      <xsl:value-of select="."/>
    </xsl:if>
  </xsl:template>

  <xsl:template match="processing-instruction()">
    <div class="pi">
      <xsl:text>&lt;?</xsl:text>
      <xsl:value-of select="name(.)"/>
      <xsl:text> </xsl:text>
      <xsl:value-of select="."/>
      <xsl:text>?&gt;</xsl:text>
    </div>
  </xsl:template>

  <xsl:template match="processing-instruction()[string-length(.) &gt; 50]">
    <div class="pi">
      <details open="" class="expandable-body">
        <summary class="expandable-opening">
          <xsl:text>&lt;?</xsl:text>
          <xsl:value-of select="name(.)"/>
        </summary>
        <div class="expandable-children"><xsl:value-of select="."/></div>
      </details>
      <span class="expandable-closing">
        <xsl:text>?&gt;</xsl:text>
      </span>
    </div>
  </xsl:template>

  <xsl:template match="comment()">
    <div class="comment">
      <xsl:text>&lt;!--</xsl:text>
      <xsl:value-of select="."/>
      <xsl:text>--&gt;</xsl:text>
    </div>
  </xsl:template>

  <xsl:template match="comment()[string-length(.) &gt; 50]">
    <div class="comment">
      <details open="" class="expandable-body">
        <summary class="expandable-opening">
          <xsl:text>&lt;!--</xsl:text>
        </summary>
        <div class="expandable-children">
          <xsl:value-of select="."/>
        </div>
      </details>
      <span class="expandable-closing">
        <xsl:text>--&gt;</xsl:text>
      </span>
    </div>
  </xsl:template>

</xsl:stylesheet>
PK
!<��!��Achrome/toolkit/content/mozapps/downloads/unknownContentType.xhtml<?xml version="1.0"?>

<window id="unknownContentTypeWindow"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        xmlns:html="http://www.w3.org/1999/xhtml"
        onload="dialog.initDialog();" onunload="if (dialog) dialog.onCancel();"
        style="min-width: 34em;"
        screenX="" screenY=""
        persist="screenX screenY"
        aria-describedby="intro location whichIs type from source unknownPrompt">
<linkset>
  <html:link rel="stylesheet" href="chrome://global/skin/global.css" />
  <html:link
    rel="stylesheet"
    href="chrome://mozapps/skin/downloads/unknownContentType.css"
  />

  <html:link rel="localization" href="branding/brand.ftl"/>
  <html:link rel="localization" href="toolkit/global/unknownContentType.ftl"/>
</linkset>
<dialog id="unknownContentType">

  <stringbundle id="strings" src="chrome://mozapps/locale/downloads/unknownContentType.properties"/>

  <script src="chrome://global/content/globalOverlay.js"/>
  <script src="chrome://global/content/editMenuOverlay.js"/>

  <vbox flex="1" id="container">
    <description id="intro" data-l10n-id="unknowncontenttype-intro"></description>
    <separator class="thin"/>
    <hbox align="start" class="small-indent">
      <image id="contentTypeImage"/>
      <vbox flex="1">
        <description id="location" crop="start" flex="1"/>
        <separator class="thin"/>
        <hbox align="center">
          <label id="whichIs" data-l10n-id="unknowncontenttype-which-is"/>
          <label id="type" tabindex="0" noinitialfocus="true"/>
        </hbox>
        <hbox align="center">
          <label data-l10n-id="unknowncontenttype-from" id="from"/>
          <description id="source" crop="start" flex="1"/>
        </hbox>
      </vbox>
    </hbox>

    <separator class="thin"/>

    <hbox align="center" id="basicBox" collapsed="true">
      <label id="unknownPrompt" data-l10n-id="unknowncontenttype-prompt" flex="1"/>
    </hbox>

    <vbox flex="1" id="normalBox">
      <separator/>
      <label control="mode" class="header" data-l10n-id="unknowncontenttype-action-question"/>
      <radiogroup id="mode" class="small-indent">
        <radio id="handleInternally" hidden="true" data-l10n-id="unknowncontenttype-handleinternally"/>

        <hbox>
          <radio id="open" data-l10n-id="unknowncontenttype-open-with"/>
          <deck id="modeDeck" flex="1">
            <hbox id="openHandlerBox" flex="1" align="center">
              <menulist id="openHandler" flex="1" native="true">
                <menupopup id="openHandlerPopup" oncommand="dialog.openHandlerCommand();">
                  <menuitem id="defaultHandler" default="true" crop="end"/>
                  <menuitem id="otherHandler" hidden="true" crop="start"/>
                  <menuseparator/>
                  <menuitem id="choose" data-l10n-id="unknowncontenttype-other"/>
                </menupopup>
              </menulist>
            </hbox>
            <hbox flex="1" align="center">
              <button id="chooseButton" oncommand="dialog.chooseApp();"
                      data-l10n-id="unknowncontenttype-choose-handler"/>
            </hbox>
          </deck>
        </hbox>

        <radio id="save" data-l10n-id="unknowncontenttype-save-file"/>
      </radiogroup>
      <separator class="thin"/>
      <hbox class="small-indent">
        <checkbox id="rememberChoice" data-l10n-id="unknowncontenttype-remember-choice"
                  oncommand="dialog.toggleRememberChoice(event.target);"
                  native="true"/>
      </hbox>

      <separator/>

      <description id="settingsChange" hidden="true" data-l10n-id="unknowncontenttype-settingschange"/>

      <separator class="thin"/>
    </vbox>
  </vbox>
</dialog>
</window>
PK
!<@�T??>chrome/toolkit/content/mozapps/extensions/OpenH264-license.txt-------------------------------------------------------
About The Cisco-Provided Binary of OpenH264 Video Codec
-------------------------------------------------------

Cisco provides this program under the terms of the BSD license.  

Additionally, this binary is licensed under Cisco’s AVC/H.264 Patent Portfolio License from MPEG LA, at no cost to you, provided that the requirements and conditions shown below in the AVC/H.264 Patent Portfolio sections are met.  

As with all AVC/H.264 codecs, you may also obtain your own patent license from MPEG LA or from the individual patent owners, or proceed at your own risk.  Your rights from Cisco under the BSD license are not affected by this choice.  

For more information on the OpenH264 binary licensing, please see the OpenH264 FAQ found at http://www.openh264.org/faq.html#binary 

A corresponding source code to this binary program is available under the same BSD terms, which can be found at http://www.openh264.org

-----------
BSD License
-----------

Copyright © 2014 Cisco Systems, Inc.

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

-----------------------------------------
AVC/H.264 Patent Portfolio License Notice
-----------------------------------------

The binary form of this Software is distributed by Cisco under the AVC/H.264 Patent Portfolio License from MPEG LA, and is subject to the following requirements, which may or may not be applicable to your use of this software: 

THIS PRODUCT IS LICENSED UNDER THE AVC PATENT PORTFOLIO LICENSE FOR THE PERSONAL USE OF A CONSUMER OR OTHER USES IN WHICH IT DOES NOT RECEIVE REMUNERATION TO (i) ENCODE VIDEO IN COMPLIANCE WITH THE AVC STANDARD (“AVC VIDEO”) AND/OR (ii) DECODE AVC VIDEO THAT WAS ENCODED BY A CONSUMER ENGAGED IN A PERSONAL ACTIVITY AND/OR WAS OBTAINED FROM A VIDEO PROVIDER LICENSED TO PROVIDE AVC VIDEO.  NO LICENSE IS GRANTED OR SHALL BE IMPLIED FOR ANY OTHER USE.  ADDITIONAL INFORMATION MAY BE OBTAINED FROM MPEG LA, L.L.C. SEE HTTP://WWW.MPEGLA.COM

Accordingly, please be advised that content providers and broadcasters using AVC/H.264 in their service may be required to obtain a separate use license from MPEG LA, referred to as "(b) sublicenses" in the SUMMARY OF AVC/H.264 LICENSE TERMS from MPEG LA found at http://www.openh264.org/mpegla

---------------------------------------------
AVC/H.264 Patent Portfolio License Conditions
---------------------------------------------

In addition, the Cisco-provided binary of this Software is licensed under Cisco's license from MPEG LA only if the following conditions are met:

1. The Cisco-provided binary is separately downloaded to an end user’s device, and not integrated into or combined with third party software prior to being downloaded to the end user’s device;

2. The end user must have the ability to control (e.g., to enable, disable, or re-enable) the use of the Cisco-provided binary;

3. Third party software, in the location where end users can control the use of the Cisco-provided binary, must display the following text:

       "OpenH264 Video Codec provided by Cisco Systems, Inc."

4.  Any third-party software that makes use of the Cisco-provided binary must reproduce all of the above text, as well as this last condition, in the EULA and/or in another location where licensing information is to be presented to the end user.  
 


                          v1.0
PK
!<Ɍ���A�A9chrome/toolkit/content/mozapps/extensions/aboutaddons.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:root {
  --addon-icon-size: 32px;
  --main-margin-start: 28px;
  --section-width: 664px;
  --sidebar-width: var(--in-content-sidebar-width);
  --z-index-sticky-container: 5;
  --z-index-popup: 10;
}

@media (max-width: 830px) {
  :root {
    --main-margin-start: 16px;
    /* Maintain a main margin so card shadows don't overlap the sidebar. */
    --sidebar-width: calc(var(--in-content-sidebar-width) - var(--main-margin-start));
  }
}

*|*[hidden] {
  display: none !important;
}

body {
  cursor: default;
  /* The page starts to look really bad lower than this. */
  min-width: 500px;
}

h1 {
  font-size: var(--font-size-xlarge);
}

h2 {
  font-size: var(--font-size-large);
}

#full {
  display: grid;
  grid-template-columns: var(--sidebar-width) 1fr;
}

#sidebar {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  margin: 0;
  overflow: hidden auto;
}

@media (prefers-reduced-motion) {
  /* Setting border-inline-end on #sidebar makes it a focusable element */
  #sidebar::after {
    content: "";
    width: 1px;
    height: 100%;
    background-color: var(--in-content-border-color);
    top: 0;
    inset-inline-end: 0;
    position: absolute;
  }
}

#categories {
  display: flex;
  flex-direction: column;
  padding-inline-end: 4px; /* Leave space for the button focus styles. */
}

.category {
  display: grid;
  grid-template-columns: 1fr auto;
  margin-block: 0;
  align-items: center;
  font-weight: normal;
}

.category[badge-count]::after {
  display: inline-block;
  min-width: 20px;
  background-color: var(--in-content-accent-color);
  color: var(--in-content-primary-button-text-color);
  font-weight: bold;
  /* Use a large border-radius to get semi-circles on the sides. */
  border-radius: 1000px;
  padding: 2px 6px;
  content: attr(badge-count);
  text-align: center;
  margin-inline-start: 8px;
  grid-column: 2;
}

.category[name="discover"] {
  background-image: url("chrome://mozapps/skin/extensions/category-discover.svg");
}
.category[name="locale"] {
  background-image: url("chrome://mozapps/skin/extensions/category-languages.svg");
}
.category[name="extension"] {
  background-image: url("chrome://mozapps/skin/extensions/category-extensions.svg");
}
.category[name="theme"] {
  background-image: url("chrome://mozapps/skin/extensions/category-themes.svg");
}
.category[name="plugin"] {
  background-image: url("chrome://mozapps/skin/extensions/category-plugins.svg");
}
.category[name="dictionary"] {
  background-image: url("chrome://mozapps/skin/extensions/category-dictionaries.svg");
}
.category[name="available-updates"] {
  background-image: url("chrome://mozapps/skin/extensions/category-available.svg");
}
.category[name="recent-updates"] {
  background-image: url("chrome://mozapps/skin/extensions/category-recent.svg");
}
.category[name="sitepermission"] {
  background-image: url("chrome://mozapps/skin/extensions/category-sitepermission.svg");
}

.sticky-container {
  background: var(--in-content-page-background);
  width: 100%;
  position: sticky;
  top: 0;
  z-index: var(--z-index-sticky-container);
}

.main-search {
  background: var(--in-content-page-background);
  display: flex;
  justify-content: flex-end;
  align-items: center;
  padding-inline-start: 28px;
  padding-top: 20px;
  padding-bottom: 30px;
  max-width: var(--section-width);
}

search-addons > search-textbox {
  margin: 0;
  width: 20em;
  min-height: 32px;
}

.search-label {
  margin-inline-end: 8px;
}

.main-heading {
  background: var(--in-content-page-background);
  display: flex;
  margin-inline-start: var(--main-margin-start);
  padding-bottom: 16px;
  max-width: var(--section-width);
}

.spacer {
  flex-grow: 1;
}

#updates-message {
  display: flex;
  align-items: center;
  margin-inline-end: 8px;
}

.back-button {
  margin-inline-end: 16px;
}

/* Plugins aren't yet disabled by safemode (bug 342333),
   so don't show that warning when viewing plugins. */
#page-header[current-param="plugin"] moz-message-bar[warning-type="safe-mode"] {
  display: none;
}

#main {
  margin-inline-start: var(--main-margin-start);
  margin-bottom: 28px;
  max-width: var(--section-width);
}

global-warnings {
  margin-inline-start: var(--main-margin-start);
  max-width: var(--section-width);
}

/* The margin between message bars. */
message-bar-stack > * {
  margin-bottom: 8px;
}

/* List sections */

.list-section-heading {
  margin-bottom: 16px;
}

.list-section-subheading {
  font-size: 0.9em;
  font-weight: 400;
  margin-block-start: 0.5em;
}

.section {
  margin-bottom: 32px;
}

/* Add-on cards */

.addon.card {
  margin-bottom: 16px;
  transition: opacity 150ms, box-shadow 150ms;
}

addon-list:not([type="theme"]) addon-card:not([expanded]):not([panelopen]) > .addon.card[active="false"]:not(:focus-within):not(:hover) {
  opacity: 0.6;
}

.addon.card:hover {
  box-shadow: var(--card-shadow);
}

addon-card:not([expanded]) > .addon.card:hover {
  box-shadow: var(--card-shadow-hover);
  cursor: pointer;
}

addon-card[expanded] .addon.card {
  padding-bottom: 0;
}

.addon-card-collapsed {
  display: flex;
}

addon-list addon-card > .addon.card {
  user-select: none;
}

.addon-card-message,
.update-postponed-bar {
  border-top-left-radius: 0;
  border-top-right-radius: 0;
  margin: 8px calc(var(--card-padding) * -1) calc(var(--card-padding) * -1);
}

addon-card[expanded] .addon-card-message,
addon-card[expanded] .update-postponed-bar {
  border-radius: 0;
  margin-bottom: 0;
}

addon-card[expanded] .update-postponed-bar + .addon-card-message {
  /* Remove margin between the two message bars when they are both
   * visible in the detail view */
  margin-top: 0px;
}

.update-postponed-bar + .addon-card-message:not([hidden]) {
  /* Prevent the small overlapping between the two message bars
   * when they are both visible at the same time one after the
   * other on the same addon card */
  margin-top: 12px;
}

/* Theme preview image. */
.card-heading-image {
  /* If the width, height or aspect ratio changes, don't forget to update the
   * getScreenshotUrlForAddon function in aboutaddons.js */
  width: var(--section-width);
  /* Adjust height so that the image preserves the aspect ratio from AMO.
   * For details, see https://bugzilla.mozilla.org/show_bug.cgi?id=1546123 */
  height: calc(var(--section-width) * 92 / 680);
  object-fit: cover;
}

.card-heading-icon {
  flex-shrink: 0;
  width: var(--addon-icon-size);
  height: var(--addon-icon-size);
  margin-inline-end: 16px;
  -moz-context-properties: fill;
  fill: currentColor;
}

.card-contents {
  word-break: break-word;
  flex-grow: 1;
  display: flex;
  flex-direction: column;
}

.addon-name-container {
  /* Subtract the top line-height so the text and icon align at the top. */
  margin-top: -3px;
  display: flex;
  align-items: center;
}

.addon-name {
  font-size: 16px;
  font-weight: var(--font-weight-bold);
  line-height: 22px;
  margin: 0;
  margin-inline-end: 8px;
}

.addon-name-link,
.addon-name-link:hover {
  color: var(--in-content-text-color);
  text-decoration: none;
}

.addon-name-link:-moz-focusring {
  /* Since the parent is overflow:hidden to ellipsize the regular outline is hidden. */
  outline-offset: -1px;
  outline-width: 1px;
}

.addon-badge {
  display: inline-block;
  margin-inline-end: 8px;
  width: 22px;
  height: 22px;
  background-repeat: no-repeat;
  background-position: center;
  flex-shrink: 0;
  border-radius: 11px;
  -moz-context-properties: fill;
  fill: #fff;
}

.addon-badge-private-browsing-allowed {
  background-image: url("chrome://global/skin/icons/indicator-private-browsing.svg");
}

.addon-badge-recommended {
  background-color: var(--orange-50);
  background-image: url("chrome://mozapps/skin/extensions/recommended.svg");
}

.addon-badge-line {
  background-color: #fff;
  background-image: url("chrome://mozapps/skin/extensions/line.svg");
  background-size: 16px;
  border-radius: 10px;
  border: 1px solid #CFCFD8;
  width: 20px;
  height: 20px;
}

.addon-badge-verified {
  background-color: var(--green-70);
  background-image: url("chrome://global/skin/icons/check.svg");
}

.theme-enable-button {
  min-width: auto;
  font-size: var(--font-size-small);
  min-height: auto;
  height: 24px;
  margin: 0;
  padding: 0 8px;
  font-weight: normal;
}

.addon-description {
  font-size: 14px;
  line-height: 20px;
  color: var(--text-color-deemphasized);
  font-weight: 400;
}

/* Prevent the content from wrapping unless expanded. */
addon-card:not([expanded]) .card-contents {
  /* We're hiding the content when it's too long, so we need to define the
   * width. As long as this is less than the width of its parent it works. */
  width: 1px;
  white-space: nowrap;
}

/* Ellipsize if the content is too long. */
addon-card:not([expanded]) .addon-name,
addon-card:not([expanded]) .addon-description {
  text-overflow: ellipsis;
  overflow-x: hidden;
}

.page-options-menu {
  align-self: center;
}

.page-options-menu > .more-options-button {
  background-image: url("chrome://global/skin/icons/settings.svg");
  width: 32px;
  height: 32px;
}

/* Recommended add-ons on list views */
.recommended-heading {
  margin-bottom: 24px;
  margin-top: 48px;
}

/* Discopane extensions to the add-on card */

recommended-addon-card .addon-description:not(:empty) {
  margin-top: 0.5em;
}

.disco-card-head {
  flex-grow: 1;
  display: flex;
  flex-direction: column;
}

.disco-addon-name {
  font-size: inherit;
  font-weight: normal;
  line-height: normal;
  margin: 0;
}

.disco-addon-author {
  font-size: 12px;
  font-weight: normal;
}

.disco-description-statistics {
  margin-top: 1em;
  display: grid;
  grid-template-columns: repeat(2, max-content);
  grid-column-gap: 2em;
  align-items: center;
}

.disco-cta-button {
  font-size: 14px;
  flex-shrink: 0;
  flex-grow: 0;
  align-self: baseline;
  margin-inline-end: 0;
}

.discopane-notice {
  margin: 24px 0;
}

.view-footer {
  text-align: center;
}

.view-footer-item {
  margin-top: 30px;
}

.privacy-policy-link {
  font-size: small;
}

.theme-recommendation {
  text-align: start;
}

addon-details {
  color: var(--text-color-deemphasized);
}

.addon-detail-description-wrapper {
  margin: 16px 0;
}

.addon-detail-description-collapse .addon-detail-description {
  max-height: 20rem;
  overflow: hidden;
}

/* Include button to beat out .button-link which is below this */
button.addon-detail-description-toggle {
  display: flex;
  align-items: center;
  margin-top: 8px;
  font-weight: normal;
  gap: 4px;
}

.addon-detail-description-toggle::after {
  content: "";
  display: block;
  background-image: url("chrome://global/skin/icons/arrow-up-12.svg");
  background-repeat: no-repeat;
  background-position: center;
  -moz-context-properties: fill;
  fill: currentColor;
  width: 12px;
  height: 12px;
}

.addon-detail-description-collapse .addon-detail-description-toggle::after {
  transform: scaleY(-1);
}

.addon-detail-contribute {
  display: flex;
  padding: var(--card-padding);
  border: 1px solid var(--in-content-box-border-color);
  border-radius: 4px;
  margin-bottom: var(--card-padding);
  flex-direction: column;
}

.addon-detail-contribute > label {
  font-style: italic;
}

.addon-detail-contribute-button {
  -moz-context-properties: fill;
  fill: currentColor;
  background-image: url("chrome://global/skin/icons/heart.svg");
  background-repeat: no-repeat;
  background-position: 8px;
  padding-inline-start: 28px;
  margin-top: var(--card-padding);
  margin-bottom: 0;
  align-self: flex-end;
}

.addon-detail-contribute-button:dir(rtl) {
  background-position-x: right 8px;
}

.addon-detail-sitepermissions,
.addon-detail-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-top: 1px solid var(--in-content-border-color);
  margin: 0 calc(var(--card-padding) * -1);
  padding: var(--card-padding);
  color: var(--in-content-text-color);
}

.addon-detail-row.addon-detail-help-row {
  display: block;
  color: var(--text-color-deemphasized);
  padding-top: 4px;
  padding-bottom: var(--card-padding);
  border: none;
}

.addon-detail-row-has-help {
  padding-bottom: 0;
}

.addon-detail-row input[type="checkbox"] {
  margin: 0;
}

.addon-detail-actions,
.addon-detail-rating {
  display: flex;
}

.addon-detail-actions {
  gap: 20px;
}

.addon-detail-actions > label {
  flex-wrap: wrap;
}

.addon-detail-rating > a {
  margin-inline-start: 8px;
}

.more-options-button {
  min-width: auto;
  min-height: auto;
  width: 24px;
  height: 24px;
  margin: 0;
  margin-inline-start: 8px;
  -moz-context-properties: fill;
  fill: currentColor;
  background-image: url("chrome://global/skin/icons/more.svg");
  background-repeat: no-repeat;
  background-position: center center;
  /* Get the -badged ::after element in the right spot. */
  padding: 1px;
  display: flex;
  justify-content: flex-end;
}

.more-options-button-badged::after {
  content: "";
  display: block;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background-color: var(--in-content-accent-color);;
}

panel-item[action="remove"]::part(button) {
  background-image: url("chrome://global/skin/icons/delete.svg");
}

panel-item[action="install-update"]::part(button) {
  background-image: url("chrome://global/skin/icons/update-icon.svg");
}

panel-item[action="report"]::part(button) {
  background-image: url(chrome://global/skin/icons/warning.svg);
}

.hide-amo-link .amo-link-container {
  display: none;
}

.button-link {
  min-height: auto;
  background: none !important;
  padding: 0;
  margin: 0;
  color: var(--link-color) !important;
  cursor: pointer;
  border: none;
}

.button-link:hover {
  color: var(--link-color-hover) !important;
  text-decoration: underline;
}

.button-link:active {
  color: var(--link-color-active) !important;
  text-decoration: none;
}

.addon-inline-options {
  width: 100%;
  background-color: Canvas;
  margin-block: 4px;
  /*
   * Makes sure the browser minimal height is going to be the same as when
   * this browser element was wrapper in a stack and a min-height was necessary
   * for the prompts to fit inside the browser element.
   * That stack element has been removed as part of Bug 1881055, but keeping
   * the min-height unchanged to avoid potential regressions in the short term.
   */
  min-height: 250px;
}

addon-permissions-list > .addon-detail-row {
  border-top: none;
}

.addon-permissions-list {
  list-style-type: none;
  margin: 0;
  padding-inline-start: 8px;
}

.addon-permissions-list > li {
  border: none;
  padding-block: 4px;
  padding-inline-start: 2rem;
  background-image: none;
  background-position: 0 center;
  background-size: 1.6rem 1.6rem;
  background-repeat: no-repeat;
  word-break: break-all;
}

.addon-permissions-list > li:dir(rtl) {
  background-position-x: right 0;
}

/* using a list-style-image prevents aligning the image */
.addon-permissions-list > li.permission-checked {
  background-image: url("chrome://global/skin/icons/check.svg");
  -moz-context-properties: fill;
  fill: var(--green-60);
}

.permission-header {
  font-size: 1em;
}

.tab-group {
  display: block;
  margin-top: 8px;
  /* Pull the buttons flush with the side of the card */
  margin-inline: calc(var(--card-padding) * -1);
  border-bottom: 1px solid var(--in-content-border-color);
  border-top: 1px solid var(--in-content-border-color);
  font-size: 0;
  line-height: 0;
}

button.tab-button {
  appearance: none;
  border-inline: none;
  border-block: 2px solid transparent;
  border-radius: 0;
  background: transparent;
  font-size: 14px;
  line-height: 20px;
  margin: 0;
  padding: 4px 16px;
}

button.tab-button:hover {
  border-top-color: var(--in-content-box-border-color);
}

button.tab-button[selected],
button.tab-button[selected]:hover {
  border-top-color: currentColor;
  color: var(--in-content-accent-color);
}

@media (prefers-contrast) {
  button.tab-button[selected],
  button.tab-button[selected]:hover {
    color: var(--in-content-primary-button-text-color);
    background-color: var(--in-content-primary-button-background);
  }
}

button.tab-button:-moz-focusring {
  outline-offset: -2px;
}

.tab-group[last-input-type="mouse"] > button.tab-button:-moz-focusring {
  outline: none;
  box-shadow: none;
}

section:not(:empty) ~ #empty-addons-message {
  display: none;
}

@media (max-width: 830px) {
  .category[badge-count]::after {
    content: "";
    display: block;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    min-width: auto;
    padding: 0;
    /* move the badged dot into the top-end (right in ltr, left in rtl) corner. */
    margin-top: -20px;
  }
}

.permission-header > .addon-sitepermissions-host {
  font-weight: bolder;
}
PK
!<6���GgGg:chrome/toolkit/content/mozapps/extensions/aboutaddons.html<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this file,
   - You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE html>
<html>
  <head>
    <title data-l10n-id="addons-page-title"></title>

    <!-- Bug 1571346 Remove 'unsafe-inline' from style-src within about:addons -->
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:; style-src chrome: 'unsafe-inline'; img-src chrome: file: jar: https: http:; connect-src chrome: data: https: http:; object-src 'none'"
    />
    <meta name="color-scheme" content="light dark" />
    <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
    <link
      rel="stylesheet"
      href="chrome://mozapps/content/extensions/aboutaddons.css"
    />
    <link
      rel="stylesheet"
      href="chrome://mozapps/content/extensions/shortcuts.css"
    />

    <link
      rel="shortcut icon"
      href="chrome://mozapps/skin/extensions/extension.svg"
    />

    <link rel="localization" href="branding/brand.ftl" />
    <link rel="localization" href="toolkit/about/aboutAddons.ftl" />

    <!-- Defer scripts so all the templates are loaded by the time they run. -->
    <script
      defer
      src="chrome://mozapps/content/extensions/aboutaddonsCommon.js"
    ></script>
    <script
      defer
      src="chrome://mozapps/content/extensions/abuse-reports.js"
    ></script>
    <script
      defer
      src="chrome://mozapps/content/extensions/shortcuts.js"
    ></script>
    <script
      defer
      src="chrome://mozapps/content/extensions/drag-drop-addon-installer.js"
    ></script>
    <script
      defer
      src="chrome://mozapps/content/extensions/view-controller.js"
    ></script>
    <script
      defer
      src="chrome://mozapps/content/extensions/aboutaddons.js"
    ></script>
    <script
      type="module"
      src="chrome://global/content/elements/moz-message-bar.mjs"
    ></script>
    <script
      type="module"
      src="chrome://global/content/elements/moz-toggle.mjs"
    ></script>
    <script
      type="module"
      src="chrome://global/content/elements/moz-support-link.mjs"
    ></script>
    <script
      type="module"
      src="chrome://global/content/elements/moz-five-star.mjs"
    ></script>
  </head>
  <body>
    <drag-drop-addon-installer></drag-drop-addon-installer>
    <div id="full">
      <div id="sidebar">
        <categories-box id="categories" orientation="vertical">
          <button
            is="discover-button"
            viewid="addons://discover/"
            class="category"
            role="tab"
            name="discover"
          ></button>
          <button
            is="category-button"
            viewid="addons://list/extension"
            class="category"
            role="tab"
            name="extension"
          ></button>
          <button
            is="category-button"
            viewid="addons://list/theme"
            class="category"
            role="tab"
            name="theme"
          ></button>
          <button
            is="category-button"
            viewid="addons://list/plugin"
            class="category"
            role="tab"
            name="plugin"
          ></button>
          <button
            is="category-button"
            viewid="addons://list/dictionary"
            class="category"
            role="tab"
            name="dictionary"
            hidden
            default-hidden
          ></button>
          <button
            is="category-button"
            viewid="addons://list/locale"
            class="category"
            role="tab"
            name="locale"
            hidden
            default-hidden
          ></button>
          <button
            is="category-button"
            viewid="addons://list/sitepermission"
            class="category"
            role="tab"
            name="sitepermission"
            hidden
            default-hidden
          ></button>
          <button
            is="category-button"
            viewid="addons://updates/available"
            class="category"
            role="tab"
            name="available-updates"
            hidden
            default-hidden
          ></button>
          <button
            is="category-button"
            viewid="addons://updates/recent"
            class="category"
            role="tab"
            name="recent-updates"
            hidden
            default-hidden
          ></button>
        </categories-box>
        <div class="spacer"></div>
        <sidebar-footer></sidebar-footer>
      </div>
      <div id="content">
        <addon-page-header
          id="page-header"
          page-options-id="page-options"
        ></addon-page-header>
        <addon-page-options id="page-options"></addon-page-options>

        <div id="main"></div>
      </div>
    </div>

    <proxy-context-menu id="contentAreaContextMenu"></proxy-context-menu>

    <template name="addon-page-header">
      <div class="sticky-container">
        <div class="main-search">
          <label
            for="search-addons"
            class="search-label"
            data-l10n-id="default-heading-search-label"
          ></label>
          <search-addons></search-addons>
        </div>
        <div class="main-heading">
          <button
            class="back-button"
            action="go-back"
            data-l10n-id="header-back-button"
            hidden
          ></button>
          <h1 class="header-name"></h1>
          <div class="spacer"></div>
          <addon-updates-message
            id="updates-message"
            hidden
          ></addon-updates-message>
          <div class="page-options-menu">
            <button
              class="more-options-button"
              action="page-options"
              aria-haspopup="menu"
              aria-expanded="false"
              data-l10n-id="addon-page-options-button"
            ></button>
          </div>
        </div>
      </div>
      <global-warnings></global-warnings>
    </template>

    <template name="addon-page-options">
      <panel-list>
        <panel-item
          action="check-for-updates"
          data-l10n-id="addon-updates-check-for-updates"
          data-l10n-attrs="accesskey"
        ></panel-item>
        <panel-item
          action="view-recent-updates"
          data-l10n-id="addon-updates-view-updates"
          data-l10n-attrs="accesskey"
        ></panel-item>
        <hr />
        <panel-item
          action="install-from-file"
          data-l10n-id="addon-install-from-file"
          data-l10n-attrs="accesskey"
        ></panel-item>
        <panel-item
          action="debug-addons"
          data-l10n-id="addon-open-about-debugging"
          data-l10n-attrs="accesskey"
        ></panel-item>
        <hr />
        <panel-item
          action="set-update-automatically"
          data-l10n-id="addon-updates-update-addons-automatically"
          data-l10n-attrs="accesskey"
        ></panel-item>
        <panel-item
          action="reset-update-states"
          data-l10n-attrs="accesskey"
        ></panel-item>
        <hr />
        <panel-item
          action="manage-shortcuts"
          data-l10n-id="addon-manage-extensions-shortcuts"
          data-l10n-attrs="accesskey"
        ></panel-item>
      </panel-list>
    </template>

    <template name="addon-options">
      <panel-list>
        <panel-item
          data-l10n-id="remove-addon-button"
          action="remove"
        ></panel-item>
        <panel-item
          data-l10n-id="install-update-button"
          action="install-update"
          badged
        ></panel-item>
        <panel-item
          data-l10n-id="preferences-addon-button"
          action="preferences"
        ></panel-item>
        <hr />
        <panel-item
          data-l10n-id="report-addon-button"
          action="report"
        ></panel-item>
        <hr />
        <panel-item
          data-l10n-id="manage-addon-button"
          action="expand"
        ></panel-item>
      </panel-list>
    </template>

    <template name="plugin-options">
      <panel-list>
        <panel-item
          data-l10n-id="always-activate-button"
          action="always-activate"
        ></panel-item>
        <panel-item
          data-l10n-id="never-activate-button"
          action="never-activate"
        ></panel-item>
        <hr />
        <panel-item
          data-l10n-id="preferences-addon-button"
          action="preferences"
        ></panel-item>
        <hr />
        <panel-item
          data-l10n-id="manage-addon-button"
          action="expand"
        ></panel-item>
      </panel-list>
    </template>

    <template name="addon-permissions-list">
      <div class="addon-permissions-required" hidden>
        <h2
          class="permission-header"
          data-l10n-id="addon-permissions-required"
        ></h2>
        <ul class="addon-permissions-list"></ul>
      </div>
      <div class="addon-permissions-optional" hidden>
        <h2
          class="permission-header"
          data-l10n-id="addon-permissions-optional"
        ></h2>
        <ul class="addon-permissions-list"></ul>
      </div>
      <div
        class="addon-detail-row addon-permissions-empty"
        data-l10n-id="addon-permissions-empty"
        hidden
      ></div>
      <div class="addon-detail-row">
        <a
          is="moz-support-link"
          support-page="extension-permissions"
          data-l10n-id="addon-permissions-learnmore"
        ></a>
      </div>
    </template>

    <template name="addon-sitepermissions-list">
      <div class="addon-permissions-required" hidden>
        <h2
          class="permission-header"
          data-l10n-id="addon-sitepermissions-required"
        >
          <span
            data-l10n-name="hostname"
            class="addon-sitepermissions-host"
          ></span>
        </h2>
        <ul class="addon-permissions-list"></ul>
      </div>
    </template>

    <template name="card">
      <div class="card addon">
        <img class="card-heading-image" role="presentation" />
        <div class="addon-card-collapsed">
          <img class="card-heading-icon addon-icon" alt="" />
          <div class="card-contents">
            <div class="addon-name-container">
              <a
                class="addon-badge addon-badge-recommended"
                is="moz-support-link"
                support-page="add-on-badges"
                utm-content="promoted-addon-badge"
                data-l10n-id="addon-badge-recommended2"
                hidden
              >
              </a>
              <a
                class="addon-badge addon-badge-line"
                is="moz-support-link"
                support-page="add-on-badges"
                utm-content="promoted-addon-badge"
                data-l10n-id="addon-badge-line3"
                hidden
              >
              </a>
              <a
                class="addon-badge addon-badge-verified"
                is="moz-support-link"
                support-page="add-on-badges"
                utm-content="promoted-addon-badge"
                data-l10n-id="addon-badge-verified2"
                hidden
              >
              </a>
              <a
                class="addon-badge addon-badge-private-browsing-allowed"
                is="moz-support-link"
                support-page="extensions-pb"
                data-l10n-id="addon-badge-private-browsing-allowed2"
                hidden
              >
              </a>
              <div class="spacer"></div>
              <button
                class="theme-enable-button"
                action="toggle-disabled"
                hidden
              ></button>
              <moz-toggle
                class="extension-enable-button"
                action="toggle-disabled"
                data-l10n-id="extension-enable-addon-button-label"
                hidden
              ></moz-toggle>
              <button
                class="more-options-button"
                action="more-options"
                data-l10n-id="addon-options-button"
                aria-haspopup="menu"
                aria-expanded="false"
              ></button>
            </div>
            <!-- This ends up in the tab order when the ellipsis happens, but it isn't necessary. -->
            <span class="addon-description" tabindex="-1"></span>
          </div>
        </div>
        <moz-message-bar
          class="update-postponed-bar"
          data-l10n-id="install-postponed-message2"
          data-l10n-attrs="message"
          align="center"
          hidden
        >
          <button
            slot="actions"
            action="install-postponed"
            data-l10n-id="install-postponed-button"
          ></button>
        </moz-message-bar>
        <moz-message-bar class="addon-card-message" align="center" hidden>
          <button action="link"></button>
        </moz-message-bar>
      </div>
    </template>

    <template name="addon-name-container-in-disco-card">
      <div class="disco-card-head">
        <h3 class="disco-addon-name"></h3>
        <span class="disco-addon-author"
          ><a data-l10n-name="author" target="_blank"></a
        ></span>
      </div>
      <button class="disco-cta-button" action="install-addon"></button>
      <button
        class="disco-cta-button"
        data-l10n-id="manage-addon-button"
        action="manage-addon"
      ></button>
    </template>

    <template name="addon-description-in-disco-card">
      <div>
        <span class="disco-description-main"></span>
      </div>
      <div class="disco-description-statistics">
        <moz-five-star></moz-five-star>
        <span class="disco-user-count"></span>
      </div>
    </template>

    <template name="addon-details">
      <button-group class="tab-group">
        <button
          is="named-deck-button"
          deck="details-deck"
          name="details"
          data-l10n-id="details-addon-button"
          class="tab-button ghost-button"
        ></button>
        <button
          is="named-deck-button"
          deck="details-deck"
          name="preferences"
          data-l10n-id="preferences-addon-button"
          class="tab-button ghost-button"
        ></button>
        <button
          is="named-deck-button"
          deck="details-deck"
          name="permissions"
          data-l10n-id="permissions-addon-button"
          class="tab-button ghost-button"
        ></button>
        <button
          is="named-deck-button"
          deck="details-deck"
          name="release-notes"
          data-l10n-id="release-notes-addon-button"
          class="tab-button ghost-button"
        ></button>
      </button-group>
      <named-deck id="details-deck" is-tabbed>
        <section name="details">
          <div class="addon-detail-description-wrapper">
            <div class="addon-detail-description"></div>
            <button
              class="button-link addon-detail-description-toggle"
              data-l10n-id="addon-detail-description-expand"
              hidden
            ></button>
          </div>
          <div class="addon-detail-contribute">
            <label data-l10n-id="detail-contributions-description"></label>
            <button
              class="addon-detail-contribute-button"
              action="contribute"
              data-l10n-id="detail-contributions-button"
              data-l10n-attrs="accesskey"
            ></button>
          </div>
          <div class="addon-detail-sitepermissions">
            <addon-sitepermissions-list></addon-sitepermissions-list>
          </div>
          <div
            class="addon-detail-row addon-detail-row-updates"
            role="group"
            data-l10n-id="addon-detail-group-label-updates"
          >
            <span data-l10n-id="addon-detail-updates-label"></span>
            <div class="addon-detail-actions">
              <button
                class="button-link"
                data-l10n-id="addon-detail-update-check-label"
                action="update-check"
                hidden
              ></button>
              <label class="radio-container-with-text">
                <input type="radio" name="autoupdate" value="1" />
                <span data-l10n-id="addon-detail-updates-radio-default"></span>
              </label>
              <label class="radio-container-with-text">
                <input type="radio" name="autoupdate" value="2" />
                <span data-l10n-id="addon-detail-updates-radio-on"></span>
              </label>
              <label class="radio-container-with-text">
                <input type="radio" name="autoupdate" value="0" />
                <span data-l10n-id="addon-detail-updates-radio-off"></span>
              </label>
            </div>
          </div>
          <div
            class="addon-detail-row addon-detail-row-has-help addon-detail-row-private-browsing"
            role="group"
            data-l10n-id="addon-detail-group-label-private-browsing"
            hidden
          >
            <span data-l10n-id="detail-private-browsing-label"></span>
            <div class="addon-detail-actions">
              <label class="radio-container-with-text">
                <input type="radio" name="private-browsing" value="1" />
                <span data-l10n-id="addon-detail-private-browsing-allow"></span>
              </label>
              <label class="radio-container-with-text">
                <input type="radio" name="private-browsing" value="0" />
                <span
                  data-l10n-id="addon-detail-private-browsing-disallow"
                ></span>
              </label>
            </div>
          </div>
          <div
            class="addon-detail-row addon-detail-help-row"
            data-l10n-id="addon-detail-private-browsing-help"
            hidden
          >
            <a
              is="moz-support-link"
              support-page="extensions-pb"
              data-l10n-name="learn-more"
            ></a>
          </div>
          <div
            class="addon-detail-row addon-detail-row-has-help addon-detail-row-private-browsing-disallowed"
            hidden
          >
            <label data-l10n-id="detail-private-disallowed-label"></label>
          </div>
          <div
            class="addon-detail-row addon-detail-help-row"
            data-l10n-id="detail-private-disallowed-description2"
            hidden
          >
            <a
              is="moz-support-link"
              data-l10n-name="learn-more"
              support-page="extensions-pb"
            ></a>
          </div>
          <div
            class="addon-detail-row addon-detail-row-has-help addon-detail-row-private-browsing-required"
            hidden
          >
            <label
              class="learn-more-label-link"
              data-l10n-id="detail-private-required-label"
            ></label>
          </div>
          <div
            class="addon-detail-row addon-detail-help-row"
            data-l10n-id="detail-private-required-description2"
            hidden
          >
            <a
              is="moz-support-link"
              data-l10n-name="learn-more"
              support-page="extensions-pb"
            ></a>
          </div>
          <div
            class="addon-detail-row addon-detail-row-has-help addon-detail-row-quarantined-domains"
            role="group"
            data-l10n-id="addon-detail-group-label-quarantined-domains"
            hidden
          >
            <span data-l10n-id="addon-detail-quarantined-domains-label"></span>
            <div class="addon-detail-actions">
              <label class="radio-container-with-text">
                <input
                  type="radio"
                  name="quarantined-domains-user-allowed"
                  value="1"
                />
                <span
                  data-l10n-id="addon-detail-quarantined-domains-allow"
                ></span>
              </label>
              <label class="radio-container-with-text">
                <input
                  type="radio"
                  name="quarantined-domains-user-allowed"
                  value="0"
                />
                <span
                  data-l10n-id="addon-detail-quarantined-domains-disallow"
                ></span>
              </label>
            </div>
          </div>
          <div class="addon-detail-row addon-detail-help-row" hidden>
            <span data-l10n-id="addon-detail-quarantined-domains-help"></span>
            <a is="moz-support-link" support-page="quarantined-domains"></a>
          </div>
          <div class="addon-detail-row addon-detail-row-author">
            <label data-l10n-id="addon-detail-author-label"></label>
            <a target="_blank"></a>
          </div>
          <div class="addon-detail-row addon-detail-row-version">
            <label data-l10n-id="addon-detail-version-label"></label>
          </div>
          <div class="addon-detail-row addon-detail-row-lastUpdated">
            <label data-l10n-id="addon-detail-last-updated-label"></label>
          </div>
          <div class="addon-detail-row addon-detail-row-homepage">
            <label data-l10n-id="addon-detail-homepage-label"></label>
            <!-- URLs should always be displayed as LTR. -->
            <a target="_blank" dir="ltr"></a>
          </div>
          <div class="addon-detail-row addon-detail-row-rating">
            <label data-l10n-id="addon-detail-rating-label"></label>
            <div class="addon-detail-rating">
              <moz-five-star></moz-five-star>
              <a target="_blank"></a>
            </div>
          </div>
        </section>
        <inline-options-browser name="preferences"></inline-options-browser>
        <addon-permissions-list name="permissions"></addon-permissions-list>
        <update-release-notes name="release-notes"></update-release-notes>
      </named-deck>
    </template>

    <template name="taar-notice">
      <moz-message-bar
        class="discopane-notice"
        data-l10n-id="discopane-notice-recommendations2"
        data-l10n-attrs="message"
        dismissable
      >
        <a
          is="moz-support-link"
          support-page="personalized-addons"
          data-l10n-id="discopane-notice-learn-more"
          action="notice-learn-more"
          slot="support-link"
        ></a>
      </moz-message-bar>
    </template>

    <template name="recommended-footer">
      <div class="amo-link-container view-footer-item">
        <button
          class="primary"
          action="open-amo"
          data-l10n-id="find-more-addons"
        ></button>
      </div>
      <div class="view-footer-item">
        <a
          class="privacy-policy-link"
          data-l10n-id="privacy-policy"
          target="_blank"
        ></a>
      </div>
    </template>

    <template name="discopane">
      <header>
        <p>
          <span data-l10n-id="discopane-intro">
            <a
              class="discopane-intro-learn-more-link"
              is="moz-support-link"
              support-page="recommended-extensions-program"
              data-l10n-name="learn-more-trigger"
            >
            </a>
          </span>
        </p>
      </header>
      <taar-notice></taar-notice>
      <recommended-addon-list></recommended-addon-list>
      <footer is="recommended-footer" class="view-footer"></footer>
    </template>

    <template name="recommended-extensions-section">
      <h2
        data-l10n-id="recommended-extensions-heading"
        class="header-name recommended-heading"
      ></h2>
      <taar-notice></taar-notice>
      <recommended-addon-list
        type="extension"
        hide-installed
      ></recommended-addon-list>
      <footer is="recommended-footer" class="view-footer"></footer>
    </template>

    <template name="recommended-themes-footer">
      <p data-l10n-id="recommended-theme-1" class="theme-recommendation">
        <a data-l10n-name="link" target="_blank"></a>
      </p>
      <div class="amo-link-container view-footer-item">
        <button
          class="primary"
          action="open-amo"
          data-l10n-id="find-more-themes"
        ></button>
      </div>
    </template>

    <template name="recommended-themes-section">
      <h2
        data-l10n-id="recommended-themes-heading"
        class="header-name recommended-heading"
      ></h2>
      <recommended-addon-list
        type="theme"
        hide-installed
      ></recommended-addon-list>
      <footer is="recommended-themes-footer" class="view-footer"></footer>
    </template>

    <template id="shortcut-view">
      <div class="error-message">
        <img
          class="error-message-icon"
          src="chrome://global/skin/arrow/panelarrow-vertical.svg"
        />
        <div class="error-message-label"></div>
      </div>
      <message-bar-stack
        id="duplicate-warning-messages"
        reverse
        max-message-bar-count="5"
      >
      </message-bar-stack>
    </template>

    <template id="shortcut-card-template">
      <div class="card shortcut">
        <div class="card-heading">
          <img class="card-heading-icon addon-icon" />
          <h2 class="addon-name"></h2>
        </div>
      </div>
    </template>

    <template id="shortcut-row-template">
      <div class="shortcut-row">
        <label class="shortcut-label"></label>
        <input
          class="shortcut-input"
          data-l10n-id="shortcuts-input"
          type="text"
          readonly
        />
        <button
          class="shortcut-remove-button ghost-button"
          data-l10n-id="shortcuts-remove-button"
          data-l10n-attrs="aria-label"
        ></button>
      </div>
    </template>

    <template id="expand-row-template">
      <div class="expand-row">
        <button class="expand-button"></button>
      </div>
    </template>

    <template id="shortcuts-no-addons">
      <div data-l10n-id="shortcuts-no-addons"></div>
    </template>

    <template id="shortcuts-no-commands-template">
      <div data-l10n-id="shortcuts-no-commands"></div>
      <ul class="shortcuts-no-commands-list"></ul>
    </template>
  </body>
</html>
PK
!<t�����8chrome/toolkit/content/mozapps/extensions/aboutaddons.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint max-len: ["error", 80] */
/* import-globals-from aboutaddonsCommon.js */
/* import-globals-from abuse-reports.js */
/* import-globals-from view-controller.js */
/* global windowRoot */

"use strict";

ChromeUtils.defineESModuleGetters(this, {
  AMBrowserExtensionsImport: "resource://gre/modules/AddonManager.sys.mjs",
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  AddonRepository: "resource://gre/modules/addons/AddonRepository.sys.mjs",
  BuiltInThemes: "resource:///modules/BuiltInThemes.sys.mjs",
  ClientID: "resource://gre/modules/ClientID.sys.mjs",
  DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
  E10SUtils: "resource://gre/modules/E10SUtils.sys.mjs",
  ExtensionCommon: "resource://gre/modules/ExtensionCommon.sys.mjs",
  ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs",
  ExtensionPermissions: "resource://gre/modules/ExtensionPermissions.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "manifestV3enabled",
  "extensions.manifestV3.enabled"
);

XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "SUPPORT_URL",
  "app.support.baseURL",
  "",
  null,
  val => Services.urlFormatter.formatURL(val)
);

XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "XPINSTALL_ENABLED",
  "xpinstall.enabled",
  true
);

const UPDATES_RECENT_TIMESPAN = 2 * 24 * 3600000; // 2 days (in milliseconds)

XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "ABUSE_REPORT_ENABLED",
  "extensions.abuseReport.enabled",
  false
);
XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "LIST_RECOMMENDATIONS_ENABLED",
  "extensions.htmlaboutaddons.recommendations.enabled",
  false
);

const PLUGIN_ICON_URL = "chrome://global/skin/icons/plugin.svg";
const EXTENSION_ICON_URL =
  "chrome://mozapps/skin/extensions/extensionGeneric.svg";

const PERMISSION_MASKS = {
  enable: AddonManager.PERM_CAN_ENABLE,
  "always-activate": AddonManager.PERM_CAN_ENABLE,
  disable: AddonManager.PERM_CAN_DISABLE,
  "never-activate": AddonManager.PERM_CAN_DISABLE,
  uninstall: AddonManager.PERM_CAN_UNINSTALL,
  upgrade: AddonManager.PERM_CAN_UPGRADE,
  "change-privatebrowsing": AddonManager.PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS,
};

const PREF_DISCOVERY_API_URL = "extensions.getAddons.discovery.api_url";
const PREF_THEME_RECOMMENDATION_URL =
  "extensions.recommendations.themeRecommendationUrl";
const PREF_RECOMMENDATION_HIDE_NOTICE = "extensions.recommendations.hideNotice";
const PREF_PRIVACY_POLICY_URL = "extensions.recommendations.privacyPolicyUrl";
const PREF_RECOMMENDATION_ENABLED = "browser.discovery.enabled";
const PREF_TELEMETRY_ENABLED = "datareporting.healthreport.uploadEnabled";
const PRIVATE_BROWSING_PERM_NAME = "internal:privateBrowsingAllowed";
const PRIVATE_BROWSING_PERMS = {
  permissions: [PRIVATE_BROWSING_PERM_NAME],
  origins: [],
};

const L10N_ID_MAPPING = {
  "theme-disabled-heading": "theme-disabled-heading2",
};

function getL10nIdMapping(id) {
  return L10N_ID_MAPPING[id] || id;
}

function shouldSkipAnimations() {
  return (
    document.body.hasAttribute("skip-animations") ||
    window.matchMedia("(prefers-reduced-motion: reduce)").matches
  );
}

function callListeners(name, args, listeners) {
  for (let listener of listeners) {
    try {
      if (name in listener) {
        listener[name](...args);
      }
    } catch (e) {
      Cu.reportError(e);
    }
  }
}

function getUpdateInstall(addon) {
  return (
    // Install object for a pending update.
    addon.updateInstall ||
    // Install object for a postponed upgrade (only for extensions,
    // because is the only addon type that can postpone their own
    // updates).
    (addon.type === "extension" &&
      addon.pendingUpgrade &&
      addon.pendingUpgrade.install)
  );
}

function isManualUpdate(install) {
  let isManual =
    install.existingAddon &&
    !AddonManager.shouldAutoUpdate(install.existingAddon);
  let isExtension =
    install.existingAddon && install.existingAddon.type == "extension";
  return (
    (isManual && isInState(install, "available")) ||
    (isExtension && isInState(install, "postponed"))
  );
}

const AddonManagerListenerHandler = {
  listeners: new Set(),

  addListener(listener) {
    this.listeners.add(listener);
  },

  removeListener(listener) {
    this.listeners.delete(listener);
  },

  delegateEvent(name, args) {
    callListeners(name, args, this.listeners);
  },

  startup() {
    this._listener = new Proxy(
      {},
      {
        has: () => true,
        get:
          (_, name) =>
          (...args) =>
            this.delegateEvent(name, args),
      }
    );
    AddonManager.addAddonListener(this._listener);
    AddonManager.addInstallListener(this._listener);
    AddonManager.addManagerListener(this._listener);
    this._permissionHandler = (type, data) => {
      if (type == "change-permissions") {
        this.delegateEvent("onChangePermissions", [data]);
      }
    };
    ExtensionPermissions.addListener(this._permissionHandler);
  },

  shutdown() {
    AddonManager.removeAddonListener(this._listener);
    AddonManager.removeInstallListener(this._listener);
    AddonManager.removeManagerListener(this._listener);
    ExtensionPermissions.removeListener(this._permissionHandler);
  },
};

/**
 * This object wires the AddonManager event listeners into addon-card and
 * addon-details elements rather than needing to add/remove listeners all the
 * time as the view changes.
 */
const AddonCardListenerHandler = new Proxy(
  {},
  {
    has: () => true,
    get(_, name) {
      return (...args) => {
        let elements = [];
        let addonId;

        // We expect args[0] to be of type:
        // - AddonInstall, on AddonManager install events
        // - AddonWrapper, on AddonManager addon events
        // - undefined, on AddonManager manage events
        if (args[0]) {
          addonId =
            args[0].addon?.id ||
            args[0].existingAddon?.id ||
            args[0].extensionId ||
            args[0].id;
        }

        if (addonId) {
          let cardSelector = `addon-card[addon-id="${addonId}"]`;
          elements = document.querySelectorAll(
            `${cardSelector}, ${cardSelector} addon-details`
          );
        } else if (name == "onUpdateModeChanged") {
          elements = document.querySelectorAll("addon-card");
        }

        callListeners(name, args, elements);
      };
    },
  }
);
AddonManagerListenerHandler.addListener(AddonCardListenerHandler);

function isAbuseReportSupported(addon) {
  return (
    ABUSE_REPORT_ENABLED &&
    AbuseReporter.isSupportedAddonType(addon.type) &&
    !(addon.isBuiltin || addon.isSystem)
  );
}

async function isAllowedInPrivateBrowsing(addon) {
  // Use the Promise directly so this function stays sync for the other case.
  let perms = await ExtensionPermissions.get(addon.id);
  return perms.permissions.includes(PRIVATE_BROWSING_PERM_NAME);
}

function hasPermission(addon, permission) {
  return !!(addon.permissions & PERMISSION_MASKS[permission]);
}

function isInState(install, state) {
  return install.state == AddonManager["STATE_" + state.toUpperCase()];
}

async function getAddonMessageInfo(addon) {
  const { name } = addon;
  const { STATE_BLOCKED, STATE_SOFTBLOCKED } = Ci.nsIBlocklistService;

  if (addon.blocklistState === STATE_BLOCKED) {
    return {
      linkUrl: await addon.getBlocklistURL(),
      linkId: "details-notification-blocked-link",
      messageId: "details-notification-blocked2",
      messageArgs: { name },
      type: "error",
    };
  } else if (isDisabledUnsigned(addon)) {
    return {
      linkUrl: SUPPORT_URL + "unsigned-addons",
      linkId: "details-notification-unsigned-and-disabled-link",
      messageId: "details-notification-unsigned-and-disabled2",
      messageArgs: { name },
      type: "error",
    };
  } else if (
    !addon.isCompatible &&
    (AddonManager.checkCompatibility ||
      addon.blocklistState !== STATE_SOFTBLOCKED)
  ) {
    return {
      messageId: "details-notification-incompatible2",
      messageArgs: { name, version: Services.appinfo.version },
      type: "error",
    };
  } else if (!isCorrectlySigned(addon)) {
    return {
      linkUrl: SUPPORT_URL + "unsigned-addons",
      linkId: "details-notification-unsigned-link",
      messageId: "details-notification-unsigned2",
      messageArgs: { name },
      type: "warning",
    };
  } else if (addon.blocklistState === STATE_SOFTBLOCKED) {
    return {
      linkUrl: await addon.getBlocklistURL(),
      linkId: "details-notification-softblocked-link",
      messageId: "details-notification-softblocked2",
      messageArgs: { name },
      type: "warning",
    };
  } else if (addon.isGMPlugin && !addon.isInstalled && addon.isActive) {
    return {
      messageId: "details-notification-gmp-pending2",
      messageArgs: { name },
      type: "warning",
    };
  }
  return {};
}

function checkForUpdate(addon) {
  return new Promise(resolve => {
    let listener = {
      onUpdateAvailable(addon, install) {
        if (AddonManager.shouldAutoUpdate(addon)) {
          // Make sure that an update handler is attached to all the install
          // objects when updated xpis are going to be installed automatically.
          attachUpdateHandler(install);

          let failed = () => {
            detachUpdateHandler(install);
            install.removeListener(updateListener);
            resolve({ installed: false, pending: false, found: true });
          };
          let updateListener = {
            onDownloadFailed: failed,
            onInstallCancelled: failed,
            onInstallFailed: failed,
            onInstallEnded: () => {
              detachUpdateHandler(install);
              install.removeListener(updateListener);
              resolve({ installed: true, pending: false, found: true });
            },
            onInstallPostponed: () => {
              detachUpdateHandler(install);
              install.removeListener(updateListener);
              resolve({ installed: false, pending: true, found: true });
            },
          };
          install.addListener(updateListener);
          install.install();
        } else {
          resolve({ installed: false, pending: true, found: true });
        }
      },
      onNoUpdateAvailable() {
        resolve({ found: false });
      },
    };
    addon.findUpdates(listener, AddonManager.UPDATE_WHEN_USER_REQUESTED);
  });
}

async function checkForUpdates() {
  let addons = await AddonManager.getAddonsByTypes(null);
  addons = addons.filter(addon => hasPermission(addon, "upgrade"));
  let updates = await Promise.all(addons.map(addon => checkForUpdate(addon)));
  gViewController.notifyEMUpdateCheckFinished();
  return updates.reduce(
    (counts, update) => ({
      installed: counts.installed + (update.installed ? 1 : 0),
      pending: counts.pending + (update.pending ? 1 : 0),
      found: counts.found + (update.found ? 1 : 0),
    }),
    { installed: 0, pending: 0, found: 0 }
  );
}

// Don't change how we handle this while the page is open.
const INLINE_OPTIONS_ENABLED = Services.prefs.getBoolPref(
  "extensions.htmlaboutaddons.inline-options.enabled"
);
const OPTIONS_TYPE_MAP = {
  [AddonManager.OPTIONS_TYPE_TAB]: "tab",
  [AddonManager.OPTIONS_TYPE_INLINE_BROWSER]: INLINE_OPTIONS_ENABLED
    ? "inline"
    : "tab",
};

// Check if an add-on has the provided options type, accounting for the pref
// to disable inline options.
function getOptionsType(addon) {
  return OPTIONS_TYPE_MAP[addon.optionsType];
}

// Check whether the options page can be loaded in the current browser window.
async function isAddonOptionsUIAllowed(addon) {
  if (addon.type !== "extension" || !getOptionsType(addon)) {
    // Themes never have options pages.
    // Some plugins have preference pages, and they can always be shown.
    // Extensions do not need to be checked if they do not have options pages.
    return true;
  }
  if (!PrivateBrowsingUtils.isContentWindowPrivate(window)) {
    return true;
  }
  if (addon.incognito === "not_allowed") {
    return false;
  }
  // The current page is in a private browsing window, and the add-on does not
  // have the permission to access private browsing windows. Block access.
  return (
    // Note: This function is async because isAllowedInPrivateBrowsing is async.
    isAllowedInPrivateBrowsing(addon)
  );
}

let _templates = {};

/**
 * Import a template from the main document.
 */
function importTemplate(name) {
  if (!_templates.hasOwnProperty(name)) {
    _templates[name] = document.querySelector(`template[name="${name}"]`);
  }
  let template = _templates[name];
  if (template) {
    return document.importNode(template.content, true);
  }
  throw new Error(`Unknown template: ${name}`);
}

function nl2br(text) {
  let frag = document.createDocumentFragment();
  let hasAppended = false;
  for (let part of text.split("\n")) {
    if (hasAppended) {
      frag.appendChild(document.createElement("br"));
    }
    frag.appendChild(new Text(part));
    hasAppended = true;
  }
  return frag;
}

/**
 * Select the screeenshot to display above an add-on card.
 *
 * @param {AddonWrapper|DiscoAddonWrapper} addon
 * @returns {string|null}
 *          The URL of the best fitting screenshot, if any.
 */
function getScreenshotUrlForAddon(addon) {
  if (addon.id == "default-theme@mozilla.org") {
    return "chrome://mozapps/content/extensions/default-theme/preview.svg";
  }
  const builtInThemePreview = BuiltInThemes.previewForBuiltInThemeId(addon.id);
  if (builtInThemePreview) {
    return builtInThemePreview;
  }

  let { screenshots } = addon;
  if (!screenshots || !screenshots.length) {
    return null;
  }

  // The image size is defined at .card-heading-image in aboutaddons.css, and
  // is based on the aspect ratio for a 680x92 image. Use the image if possible,
  // and otherwise fall back to the first image and hope for the best.
  let screenshot = screenshots.find(s => s.width === 680 && s.height === 92);
  if (!screenshot) {
    console.warn(`Did not find screenshot with desired size for ${addon.id}.`);
    screenshot = screenshots[0];
  }
  return screenshot.url;
}

/**
 * Adds UTM parameters to a given URL, if it is an AMO URL.
 *
 * @param {string} contentAttribute
 *        Identifies the part of the UI with which the link is associated.
 * @param {string} url
 * @returns {string}
 *          The url with UTM parameters if it is an AMO URL.
 *          Otherwise the url in unmodified form.
 */
function formatUTMParams(contentAttribute, url) {
  let parsedUrl = new URL(url);
  let domain = `.${parsedUrl.hostname}`;
  if (
    !domain.endsWith(".mozilla.org") &&
    // For testing: addons-dev.allizom.org and addons.allizom.org
    !domain.endsWith(".allizom.org")
  ) {
    return url;
  }

  parsedUrl.searchParams.set("utm_source", "firefox-browser");
  parsedUrl.searchParams.set("utm_medium", "firefox-browser");
  parsedUrl.searchParams.set("utm_content", contentAttribute);
  return parsedUrl.href;
}

// A wrapper around an item from the "results" array from AMO's discovery API.
// See https://addons-server.readthedocs.io/en/latest/topics/api/discovery.html
class DiscoAddonWrapper {
  /**
   * @param {object} details
   *        An item in the "results" array from AMO's discovery API.
   */
  constructor(details) {
    // Reuse AddonRepository._parseAddon to have the AMO response parsing logic
    // in one place.
    let repositoryAddon = AddonRepository._parseAddon(details.addon);

    // Note: Any property used by RecommendedAddonCard should appear here.
    // The property names and values should have the same semantics as
    // AddonWrapper, to ease the reuse of helper functions in this file.
    this.id = repositoryAddon.id;
    this.type = repositoryAddon.type;
    this.name = repositoryAddon.name;
    this.screenshots = repositoryAddon.screenshots;
    this.sourceURI = repositoryAddon.sourceURI;
    this.creator = repositoryAddon.creator;
    this.averageRating = repositoryAddon.averageRating;

    this.dailyUsers = details.addon.average_daily_users;

    this.editorialDescription = details.description_text;
    this.iconURL = details.addon.icon_url;
    this.amoListingUrl = details.addon.url;

    this.taarRecommended = details.is_recommendation;
  }
}

/**
 * A helper to retrieve the list of recommended add-ons via AMO's discovery API.
 */
var DiscoveryAPI = {
  // Map<boolean, Promise> Promises from fetching the API results with or
  // without a client ID. The `false` (no client ID) case could actually
  // have been fetched with a client ID. See getResults() for more info.
  _resultPromises: new Map(),

  /**
   * Fetch the list of recommended add-ons. The results are cached.
   *
   * Pending requests are coalesced, so there is only one request at any given
   * time. If a request fails, the pending promises are rejected, but a new
   * call will result in a new request. A succesful response is cached for the
   * lifetime of the document.
   *
   * @param {boolean} preferClientId
   *                  A boolean indicating a preference for using a client ID.
   *                  This will not overwrite the user preference but will
   *                  avoid sending a client ID if no request has been made yet.
   * @returns {Promise<DiscoAddonWrapper[]>}
   */
  async getResults(preferClientId = true) {
    // Allow a caller to set preferClientId to false, but not true if discovery
    // is disabled.
    preferClientId = preferClientId && this.clientIdDiscoveryEnabled;

    // Reuse a request for this preference first.
    let resultPromise =
      this._resultPromises.get(preferClientId) ||
      // If the client ID isn't preferred, we can still reuse a request with the
      // client ID.
      (!preferClientId && this._resultPromises.get(true));

    if (resultPromise) {
      return resultPromise;
    }

    // Nothing is prepared for this preference, make a new request.
    resultPromise = this._fetchRecommendedAddons(preferClientId).catch(e => {
      // Delete the pending promise, so _fetchRecommendedAddons can be
      // called again at the next property access.
      this._resultPromises.delete(preferClientId);
      Cu.reportError(e);
      throw e;
    });

    // Store the new result for the preference.
    this._resultPromises.set(preferClientId, resultPromise);

    return resultPromise;
  },

  get clientIdDiscoveryEnabled() {
    // These prefs match Discovery.sys.mjs for enabling clientId cookies.
    return (
      Services.prefs.getBoolPref(PREF_RECOMMENDATION_ENABLED, false) &&
      Services.prefs.getBoolPref(PREF_TELEMETRY_ENABLED, false) &&
      !PrivateBrowsingUtils.isContentWindowPrivate(window)
    );
  },

  async _fetchRecommendedAddons(useClientId) {
    let discoveryApiUrl = new URL(
      Services.urlFormatter.formatURLPref(PREF_DISCOVERY_API_URL)
    );

    if (useClientId) {
      let clientId = await ClientID.getClientIdHash();
      discoveryApiUrl.searchParams.set("telemetry-client-id", clientId);
    }
    let res = await fetch(discoveryApiUrl.href, {
      credentials: "omit",
    });
    if (!res.ok) {
      throw new Error(`Failed to fetch recommended add-ons, ${res.status}`);
    }
    let { results } = await res.json();
    return results.map(details => new DiscoAddonWrapper(details));
  },
};

class SearchAddons extends HTMLElement {
  connectedCallback() {
    if (this.childElementCount === 0) {
      this.input = document.createXULElement("search-textbox");
      this.input.setAttribute("searchbutton", true);
      this.input.setAttribute("maxlength", 100);
      this.input.setAttribute("data-l10n-attrs", "placeholder");
      document.l10n.setAttributes(this.input, "addons-heading-search-input");
      this.append(this.input);
    }
    this.input.addEventListener("command", this);
  }

  disconnectedCallback() {
    this.input.removeEventListener("command", this);
  }

  handleEvent(e) {
    if (e.type === "command") {
      this.searchAddons(this.value);
    }
  }

  get value() {
    return this.input.value;
  }

  searchAddons(query) {
    if (query.length === 0) {
      return;
    }

    let url = formatUTMParams(
      "addons-manager-search",
      AddonRepository.getSearchURL(query)
    );

    let browser = getBrowserElement();
    let chromewin = browser.ownerGlobal;
    chromewin.openWebLinkIn(url, "tab");
  }
}
customElements.define("search-addons", SearchAddons);

class MessageBarStackElement extends HTMLElement {
  constructor() {
    super();
    this._observer = null;
    const shadowRoot = this.attachShadow({ mode: "open" });
    shadowRoot.append(this.constructor.template.content.cloneNode(true));
  }

  connectedCallback() {
    // Close any message bar that should be allowed based on the
    // maximum number of message bars.
    this.closeMessageBars();

    // Observe mutations to close older bars when new ones have been
    // added.
    this._observer = new MutationObserver(() => {
      this._observer.disconnect();
      this.closeMessageBars();
      this._observer.observe(this, { childList: true });
    });
    this._observer.observe(this, { childList: true });
  }

  disconnectedCallback() {
    this._observer.disconnect();
    this._observer = null;
  }

  closeMessageBars() {
    const { maxMessageBarCount } = this;
    if (maxMessageBarCount > 1) {
      // Remove the older message bars if the stack reached the
      // maximum number of message bars allowed.
      while (this.childElementCount > maxMessageBarCount) {
        this.firstElementChild.remove();
      }
    }
  }

  get maxMessageBarCount() {
    return parseInt(this.getAttribute("max-message-bar-count"), 10);
  }

  static get template() {
    const template = document.createElement("template");

    const style = document.createElement("style");
    // Render the stack in the reverse order if the stack has the
    // reverse attribute set.
    style.textContent = `
      :host {
        display: block;
      }
      :host([reverse]) > slot {
        display: flex;
        flex-direction: column-reverse;
      }
    `;
    template.content.append(style);
    template.content.append(document.createElement("slot"));

    Object.defineProperty(this, "template", {
      value: template,
    });

    return template;
  }
}

customElements.define("message-bar-stack", MessageBarStackElement);

class GlobalWarnings extends MessageBarStackElement {
  constructor() {
    super();
    // This won't change at runtime, but we'll want to fake it in tests.
    this.inSafeMode = Services.appinfo.inSafeMode;
    this.globalWarning = null;
  }

  connectedCallback() {
    this.refresh();
    this.addEventListener("click", this);
    AddonManagerListenerHandler.addListener(this);
  }

  disconnectedCallback() {
    this.removeEventListener("click", this);
    AddonManagerListenerHandler.removeListener(this);
  }

  refresh() {
    if (this.inSafeMode) {
      this.setWarning("safe-mode");
    } else if (
      AddonManager.checkUpdateSecurityDefault &&
      !AddonManager.checkUpdateSecurity
    ) {
      this.setWarning("update-security", { action: true });
    } else if (!AddonManager.checkCompatibility) {
      this.setWarning("check-compatibility", { action: true });
    } else if (AMBrowserExtensionsImport.canCompleteOrCancelInstalls) {
      this.setWarning("imported-addons", { action: true });
    } else {
      this.removeWarning();
    }
  }

  setWarning(type, opts) {
    if (
      this.globalWarning &&
      this.globalWarning.getAttribute("warning-type") !== type
    ) {
      this.removeWarning();
    }
    if (!this.globalWarning) {
      this.globalWarning = document.createElement("moz-message-bar");
      this.globalWarning.setAttribute("warning-type", type);
      let { messageId, buttonId } = this.getGlobalWarningL10nIds(type);
      document.l10n.setAttributes(this.globalWarning, messageId);
      this.globalWarning.setAttribute("data-l10n-attrs", "message");
      if (opts && opts.action) {
        let button = document.createElement("button");
        document.l10n.setAttributes(button, buttonId);
        button.setAttribute("action", type);
        button.setAttribute("slot", "actions");
        this.globalWarning.appendChild(button);
      }
      this.appendChild(this.globalWarning);
    }
  }

  getGlobalWarningL10nIds(type) {
    const WARNING_TYPE_TO_L10NID_MAPPING = {
      "safe-mode": {
        messageId: "extensions-warning-safe-mode2",
      },
      "update-security": {
        messageId: "extensions-warning-update-security2",
        buttonId: "extensions-warning-update-security-button",
      },
      "check-compatibility": {
        messageId: "extensions-warning-check-compatibility2",
        buttonId: "extensions-warning-check-compatibility-button",
      },
      "imported-addons": {
        messageId: "extensions-warning-imported-addons2",
        buttonId: "extensions-warning-imported-addons-button",
      },
    };

    return WARNING_TYPE_TO_L10NID_MAPPING[type];
  }

  removeWarning() {
    if (this.globalWarning) {
      this.globalWarning.remove();
      this.globalWarning = null;
    }
  }

  handleEvent(e) {
    if (e.type === "click") {
      switch (e.target.getAttribute("action")) {
        case "update-security":
          AddonManager.checkUpdateSecurity = true;
          break;
        case "check-compatibility":
          AddonManager.checkCompatibility = true;
          break;
        case "imported-addons":
          AMBrowserExtensionsImport.completeInstalls();
          break;
      }
    }
  }

  /**
   * AddonManager listener events.
   */

  onCompatibilityModeChanged() {
    this.refresh();
  }

  onCheckUpdateSecurityChanged() {
    this.refresh();
  }

  onBrowserExtensionsImportChanged() {
    this.refresh();
  }
}
customElements.define("global-warnings", GlobalWarnings);

class AddonPageHeader extends HTMLElement {
  connectedCallback() {
    if (this.childElementCount === 0) {
      this.appendChild(importTemplate("addon-page-header"));
      this.heading = this.querySelector(".header-name");
      this.backButton = this.querySelector(".back-button");
      this.pageOptionsMenuButton = this.querySelector(
        '[action="page-options"]'
      );
      // The addon-page-options element is outside of this element since this is
      // position: sticky and that would break the positioning of the menu.
      this.pageOptionsMenu = document.getElementById(
        this.getAttribute("page-options-id")
      );
    }
    document.addEventListener("view-selected", this);
    this.addEventListener("click", this);
    this.addEventListener("mousedown", this);
    // Use capture since the event is actually triggered on the internal
    // panel-list and it doesn't bubble.
    this.pageOptionsMenu.addEventListener("shown", this, true);
    this.pageOptionsMenu.addEventListener("hidden", this, true);
  }

  disconnectedCallback() {
    document.removeEventListener("view-selected", this);
    this.removeEventListener("click", this);
    this.removeEventListener("mousedown", this);
    this.pageOptionsMenu.removeEventListener("shown", this, true);
    this.pageOptionsMenu.removeEventListener("hidden", this, true);
  }

  setViewInfo({ type, param }) {
    this.setAttribute("current-view", type);
    this.setAttribute("current-param", param);
    let viewType = type === "list" ? param : type;
    this.setAttribute("type", viewType);

    this.heading.hidden = viewType === "detail";
    this.backButton.hidden = viewType !== "detail" && viewType !== "shortcuts";

    this.backButton.disabled = !history.state?.previousView;

    if (viewType !== "detail") {
      document.l10n.setAttributes(this.heading, `${viewType}-heading`);
    }
  }

  handleEvent(e) {
    let { backButton, pageOptionsMenu, pageOptionsMenuButton } = this;
    if (e.type === "click") {
      switch (e.target) {
        case backButton:
          window.history.back();
          break;
        case pageOptionsMenuButton:
          if (e.inputSource == MouseEvent.MOZ_SOURCE_KEYBOARD) {
            this.pageOptionsMenu.toggle(e);
          }
          break;
      }
    } else if (
      e.type == "mousedown" &&
      e.target == pageOptionsMenuButton &&
      e.button == 0
    ) {
      this.pageOptionsMenu.toggle(e);
    } else if (
      e.target == pageOptionsMenu.panel &&
      (e.type == "shown" || e.type == "hidden")
    ) {
      this.pageOptionsMenuButton.setAttribute(
        "aria-expanded",
        this.pageOptionsMenu.open
      );
    } else if (e.target == document && e.type == "view-selected") {
      const { type, param } = e.detail;
      this.setViewInfo({ type, param });
    }
  }
}
customElements.define("addon-page-header", AddonPageHeader);

class AddonUpdatesMessage extends HTMLElement {
  static get observedAttributes() {
    return ["state"];
  }

  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    let style = document.createElement("style");
    style.textContent = `
      @import "chrome://global/skin/in-content/common.css";
      button {
        margin: 0;
      }
    `;
    this.message = document.createElement("span");
    this.message.hidden = true;
    this.button = document.createElement("button");
    this.button.addEventListener("click", e => {
      if (e.button === 0) {
        gViewController.loadView("updates/available");
      }
    });
    this.button.hidden = true;
    this.shadowRoot.append(style, this.message, this.button);
  }

  connectedCallback() {
    document.l10n.connectRoot(this.shadowRoot);
    document.l10n.translateFragment(this.shadowRoot);
  }

  disconnectedCallback() {
    document.l10n.disconnectRoot(this.shadowRoot);
  }

  attributeChangedCallback(name, oldVal, newVal) {
    if (name === "state" && oldVal !== newVal) {
      let l10nId = `addon-updates-${newVal}`;
      switch (newVal) {
        case "updating":
        case "installed":
        case "none-found":
          this.button.hidden = true;
          this.message.hidden = false;
          document.l10n.setAttributes(this.message, l10nId);
          break;
        case "manual-updates-found":
          this.message.hidden = true;
          this.button.hidden = false;
          document.l10n.setAttributes(this.button, l10nId);
          break;
      }
    }
  }

  set state(val) {
    this.setAttribute("state", val);
  }
}
customElements.define("addon-updates-message", AddonUpdatesMessage);

class AddonPageOptions extends HTMLElement {
  connectedCallback() {
    if (this.childElementCount === 0) {
      this.render();
    }
    this.addEventListener("click", this);
    this.panel.addEventListener("showing", this);
    AddonManagerListenerHandler.addListener(this);
  }

  disconnectedCallback() {
    this.removeEventListener("click", this);
    this.panel.removeEventListener("showing", this);
    AddonManagerListenerHandler.removeListener(this);
  }

  toggle(...args) {
    return this.panel.toggle(...args);
  }

  get open() {
    return this.panel.open;
  }

  render() {
    this.appendChild(importTemplate("addon-page-options"));
    this.panel = this.querySelector("panel-list");
    this.installFromFile = this.querySelector('[action="install-from-file"]');
    this.toggleUpdatesEl = this.querySelector(
      '[action="set-update-automatically"]'
    );
    this.resetUpdatesEl = this.querySelector('[action="reset-update-states"]');
    this.onUpdateModeChanged();
  }

  async handleEvent(e) {
    if (e.type === "click") {
      e.target.disabled = true;
      try {
        await this.onClick(e);
      } finally {
        e.target.disabled = false;
      }
    } else if (e.type === "showing") {
      this.installFromFile.hidden = !XPINSTALL_ENABLED;
    }
  }

  async onClick(e) {
    switch (e.target.getAttribute("action")) {
      case "check-for-updates":
        await this.checkForUpdates();
        break;
      case "view-recent-updates":
        gViewController.loadView("updates/recent");
        break;
      case "install-from-file":
        if (XPINSTALL_ENABLED) {
          installAddonsFromFilePicker();
        }
        break;
      case "debug-addons":
        this.openAboutDebugging();
        break;
      case "set-update-automatically":
        await this.toggleAutomaticUpdates();
        break;
      case "reset-update-states":
        await this.resetAutomaticUpdates();
        break;
      case "manage-shortcuts":
        gViewController.loadView("shortcuts/shortcuts");
        break;
    }
  }

  async checkForUpdates() {
    let message = document.getElementById("updates-message");
    message.state = "updating";
    message.hidden = false;
    let { installed, pending } = await checkForUpdates();
    if (pending > 0) {
      message.state = "manual-updates-found";
    } else if (installed > 0) {
      message.state = "installed";
    } else {
      message.state = "none-found";
    }
  }

  openAboutDebugging() {
    let mainWindow = window.windowRoot.ownerGlobal;
    if ("switchToTabHavingURI" in mainWindow) {
      let principal = Services.scriptSecurityManager.getSystemPrincipal();
      mainWindow.switchToTabHavingURI(
        `about:debugging#/runtime/this-firefox`,
        true,
        {
          ignoreFragment: "whenComparing",
          triggeringPrincipal: principal,
        }
      );
    }
  }

  automaticUpdatesEnabled() {
    return AddonManager.updateEnabled && AddonManager.autoUpdateDefault;
  }

  toggleAutomaticUpdates() {
    if (!this.automaticUpdatesEnabled()) {
      // One or both of the prefs is false, i.e. the checkbox is not
      // checked. Now toggle both to true. If the user wants us to
      // auto-update add-ons, we also need to auto-check for updates.
      AddonManager.updateEnabled = true;
      AddonManager.autoUpdateDefault = true;
    } else {
      // Both prefs are true, i.e. the checkbox is checked.
      // Toggle the auto pref to false, but don't touch the enabled check.
      AddonManager.autoUpdateDefault = false;
    }
  }

  async resetAutomaticUpdates() {
    let addons = await AddonManager.getAllAddons();
    for (let addon of addons) {
      if ("applyBackgroundUpdates" in addon) {
        addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
      }
    }
  }

  /**
   * AddonManager listener events.
   */

  onUpdateModeChanged() {
    let updatesEnabled = this.automaticUpdatesEnabled();
    this.toggleUpdatesEl.checked = updatesEnabled;
    let resetType = updatesEnabled ? "automatic" : "manual";
    let resetStringId = `addon-updates-reset-updates-to-${resetType}`;
    document.l10n.setAttributes(this.resetUpdatesEl, resetStringId);
  }
}
customElements.define("addon-page-options", AddonPageOptions);

class CategoryButton extends HTMLButtonElement {
  connectedCallback() {
    if (this.childElementCount != 0) {
      return;
    }

    // Make sure the aria-selected attribute is set correctly.
    this.selected = this.hasAttribute("selected");

    document.l10n.setAttributes(this, `addon-category-${this.name}-title`);

    let text = document.createElement("span");
    text.classList.add("category-name");
    document.l10n.setAttributes(text, `addon-category-${this.name}`);

    this.append(text);
  }

  load() {
    gViewController.loadView(this.viewId);
  }

  get isVisible() {
    // Make a category button visible only if the related addon type is
    // supported by the AddonManager Providers actually registered to
    // the AddonManager.
    return AddonManager.hasAddonType(this.name);
  }

  get badgeCount() {
    return parseInt(this.getAttribute("badge-count"), 10) || 0;
  }

  set badgeCount(val) {
    let count = parseInt(val, 10);
    if (count) {
      this.setAttribute("badge-count", count);
    } else {
      this.removeAttribute("badge-count");
    }
  }

  get selected() {
    return this.hasAttribute("selected");
  }

  set selected(val) {
    this.toggleAttribute("selected", !!val);
    this.setAttribute("aria-selected", !!val);
  }

  get name() {
    return this.getAttribute("name");
  }

  get viewId() {
    return this.getAttribute("viewid");
  }

  // Just setting the hidden attribute isn't enough in case the category gets
  // hidden while about:addons is closed since it could be the last active view
  // which will unhide the button when it gets selected.
  get defaultHidden() {
    return this.hasAttribute("default-hidden");
  }
}
customElements.define("category-button", CategoryButton, { extends: "button" });

class DiscoverButton extends CategoryButton {
  get isVisible() {
    return isDiscoverEnabled();
  }
}
customElements.define("discover-button", DiscoverButton, { extends: "button" });

// Create the button-group element so it gets loaded.
document.createElement("button-group");
class CategoriesBox extends customElements.get("button-group") {
  constructor() {
    super();
    // This will resolve when the initial category states have been set from
    // our cached prefs. This is intended for use in testing to verify that we
    // are caching the previous state.
    this.promiseRendered = new Promise(resolve => {
      this._resolveRendered = resolve;
    });
  }

  handleEvent(e) {
    if (e.target == document && e.type == "view-selected") {
      const { type, param } = e.detail;
      this.select(`addons://${type}/${param}`);
      return;
    }

    if (e.target == this && e.type == "button-group:key-selected") {
      this.activeChild.load();
      return;
    }

    if (e.type == "click") {
      const button = e.target.closest("[viewid]");
      if (button) {
        button.load();
        return;
      }
    }

    // Forward the unhandled events to the button-group custom element.
    super.handleEvent(e);
  }

  disconnectedCallback() {
    document.removeEventListener("view-selected", this);
    this.removeEventListener("button-group:key-selected", this);
    this.removeEventListener("click", this);
    AddonManagerListenerHandler.removeListener(this);
    super.disconnectedCallback();
  }

  async initialize() {
    let hiddenTypes = new Set([]);

    for (let button of this.children) {
      let { defaultHidden, name } = button;
      button.hidden =
        !button.isVisible || (defaultHidden && this.shouldHideCategory(name));

      if (defaultHidden && AddonManager.hasAddonType(name)) {
        hiddenTypes.add(name);
      }
    }

    let hiddenUpdated;
    if (hiddenTypes.size) {
      hiddenUpdated = this.updateHiddenCategories(Array.from(hiddenTypes));
    }

    this.updateAvailableCount();

    document.addEventListener("view-selected", this);
    this.addEventListener("button-group:key-selected", this);
    this.addEventListener("click", this);
    AddonManagerListenerHandler.addListener(this);

    this._resolveRendered();
    await hiddenUpdated;
  }

  shouldHideCategory(name) {
    return Services.prefs.getBoolPref(`extensions.ui.${name}.hidden`, true);
  }

  setShouldHideCategory(name, hide) {
    Services.prefs.setBoolPref(`extensions.ui.${name}.hidden`, hide);
  }

  getButtonByName(name) {
    return this.querySelector(`[name="${name}"]`);
  }

  get selectedChild() {
    return this._selectedChild;
  }

  set selectedChild(node) {
    if (node && this.contains(node)) {
      if (this._selectedChild) {
        this._selectedChild.selected = false;
      }
      this._selectedChild = node;
      this._selectedChild.selected = true;
    }
  }

  select(viewId) {
    let button = this.querySelector(`[viewid="${viewId}"]`);
    if (button) {
      this.activeChild = button;
      this.selectedChild = button;
      button.hidden = false;
      Services.prefs.setStringPref(PREF_UI_LASTCATEGORY, viewId);
    }
  }

  selectType(type) {
    this.select(`addons://list/${type}`);
  }

  onInstalled(addon) {
    let button = this.getButtonByName(addon.type);
    if (button) {
      button.hidden = false;
      this.setShouldHideCategory(addon.type, false);
    }
    this.updateAvailableCount();
  }

  onInstallStarted(install) {
    this.onInstalled(install);
  }

  onNewInstall() {
    this.updateAvailableCount();
  }

  onInstallPostponed() {
    this.updateAvailableCount();
  }

  onInstallCancelled() {
    this.updateAvailableCount();
  }

  async updateAvailableCount() {
    let installs = await AddonManager.getAllInstalls();
    var count = installs.filter(install => {
      return isManualUpdate(install) && !install.installed;
    }).length;
    let availableButton = this.getButtonByName("available-updates");
    availableButton.hidden = !availableButton.selected && count == 0;
    availableButton.badgeCount = count;
  }

  async updateHiddenCategories(types) {
    let hiddenTypes = new Set(types);
    let getAddons = AddonManager.getAddonsByTypes(types);
    let getInstalls = AddonManager.getInstallsByTypes(types);

    for (let addon of await getAddons) {
      if (addon.hidden) {
        continue;
      }

      this.onInstalled(addon);
      hiddenTypes.delete(addon.type);

      if (!hiddenTypes.size) {
        return;
      }
    }

    for (let install of await getInstalls) {
      if (
        install.existingAddon ||
        install.state == AddonManager.STATE_AVAILABLE
      ) {
        continue;
      }

      this.onInstalled(install);
      hiddenTypes.delete(install.type);

      if (!hiddenTypes.size) {
        return;
      }
    }

    for (let type of hiddenTypes) {
      let button = this.getButtonByName(type);
      if (button.selected) {
        // Cancel the load if this view should be hidden.
        gViewController.resetState();
      }
      this.setShouldHideCategory(type, true);
      button.hidden = true;
    }
  }
}
customElements.define("categories-box", CategoriesBox);

class SidebarFooter extends HTMLElement {
  connectedCallback() {
    let list = document.createElement("ul");
    list.classList.add("sidebar-footer-list");

    let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
    let prefsItem = this.createItem({
      icon: "chrome://global/skin/icons/settings.svg",
      createLinkElement: () => {
        let link = document.createElement("a");
        link.href = "about:preferences";
        link.id = "preferencesButton";
        return link;
      },
      titleL10nId: "sidebar-settings-button-title",
      labelL10nId: "addons-settings-button",
      onClick: e => {
        e.preventDefault();
        let hasAboutSettings = windowRoot.ownerGlobal.switchToTabHavingURI(
          "about:settings",
          false,
          {
            ignoreFragment: "whenComparing",
          }
        );
        if (!hasAboutSettings) {
          windowRoot.ownerGlobal.switchToTabHavingURI(
            "about:preferences",
            true,
            {
              ignoreFragment: "whenComparing",
              triggeringPrincipal: systemPrincipal,
            }
          );
        }
      },
    });

    let supportItem = this.createItem({
      icon: "chrome://global/skin/icons/help.svg",
      createLinkElement: () => {
        let link = document.createElement("a", { is: "moz-support-link" });
        link.setAttribute("support-page", "addons-help");
        link.id = "help-button";
        return link;
      },
      titleL10nId: "sidebar-help-button-title",
      labelL10nId: "help-button",
    });

    list.append(prefsItem, supportItem);
    this.append(list);
  }

  createItem({ onClick, titleL10nId, labelL10nId, icon, createLinkElement }) {
    let listItem = document.createElement("li");

    let link = createLinkElement();
    link.classList.add("sidebar-footer-link");
    link.addEventListener("click", onClick);
    document.l10n.setAttributes(link, titleL10nId);

    let img = document.createElement("img");
    img.src = icon;
    img.className = "sidebar-footer-icon";

    let label = document.createElement("span");
    label.className = "sidebar-footer-label";
    document.l10n.setAttributes(label, labelL10nId);

    link.append(img, label);
    listItem.append(link);
    return listItem;
  }
}
customElements.define("sidebar-footer", SidebarFooter, { extends: "footer" });

class AddonOptions extends HTMLElement {
  connectedCallback() {
    if (!this.children.length) {
      this.render();
    }
  }

  get panel() {
    return this.querySelector("panel-list");
  }

  updateSeparatorsVisibility() {
    let lastSeparator;
    let elWasVisible = false;

    // Collect the panel-list children that are not already hidden.
    const children = Array.from(this.panel.children).filter(el => !el.hidden);

    for (let child of children) {
      if (child.localName == "hr") {
        child.hidden = !elWasVisible;
        if (!child.hidden) {
          lastSeparator = child;
        }
        elWasVisible = false;
      } else {
        elWasVisible = true;
      }
    }
    if (!elWasVisible && lastSeparator) {
      lastSeparator.hidden = true;
    }
  }

  get template() {
    return "addon-options";
  }

  render() {
    this.appendChild(importTemplate(this.template));
  }

  setElementState(el, card, addon, updateInstall) {
    switch (el.getAttribute("action")) {
      case "remove":
        if (hasPermission(addon, "uninstall")) {
          // Regular add-on that can be uninstalled.
          el.disabled = false;
          el.hidden = false;
          document.l10n.setAttributes(el, "remove-addon-button");
        } else if (addon.isBuiltin) {
          // Likely the built-in themes, can't be removed, that's fine.
          el.hidden = true;
        } else {
          // Likely sideloaded, mention that it can't be removed with a link.
          el.hidden = false;
          el.disabled = true;
          if (!el.querySelector('[slot="support-link"]')) {
            let link = document.createElement("a", { is: "moz-support-link" });
            link.setAttribute("data-l10n-name", "link");
            link.setAttribute("support-page", "cant-remove-addon");
            link.setAttribute("slot", "support-link");
            el.appendChild(link);
            document.l10n.setAttributes(el, "remove-addon-disabled-button");
          }
        }
        break;
      case "report":
        el.hidden = !isAbuseReportSupported(addon);
        break;
      case "install-update":
        el.hidden = !updateInstall;
        break;
      case "expand":
        el.hidden = card.expanded;
        break;
      case "preferences":
        el.hidden =
          getOptionsType(addon) !== "tab" &&
          (getOptionsType(addon) !== "inline" || card.expanded);
        if (!el.hidden) {
          isAddonOptionsUIAllowed(addon).then(allowed => {
            el.hidden = !allowed;
          });
        }
        break;
    }
  }

  update(card, addon, updateInstall) {
    for (let el of this.items) {
      this.setElementState(el, card, addon, updateInstall);
    }

    // Update the separators visibility based on the updated visibility
    // of the actions in the panel-list.
    this.updateSeparatorsVisibility();
  }

  get items() {
    return this.querySelectorAll("panel-item");
  }

  get visibleItems() {
    return Array.from(this.items).filter(item => !item.hidden);
  }
}
customElements.define("addon-options", AddonOptions);

class PluginOptions extends AddonOptions {
  get template() {
    return "plugin-options";
  }

  setElementState(el, card, addon) {
    const userDisabledStates = {
      "always-activate": false,
      "never-activate": true,
    };
    const action = el.getAttribute("action");
    if (action in userDisabledStates) {
      let userDisabled = userDisabledStates[action];
      el.checked = addon.userDisabled === userDisabled;
      el.disabled = !(el.checked || hasPermission(addon, action));
    } else {
      super.setElementState(el, card, addon);
    }
  }
}
customElements.define("plugin-options", PluginOptions);

class ProxyContextMenu extends HTMLElement {
  openPopupAtScreen(...args) {
    // prettier-ignore
    const parentContextMenuPopup =
      windowRoot.ownerGlobal.document.getElementById("contentAreaContextMenu");
    return parentContextMenuPopup.openPopupAtScreen(...args);
  }
}
customElements.define("proxy-context-menu", ProxyContextMenu);

class InlineOptionsBrowser extends HTMLElement {
  constructor() {
    super();
    // Force the options_ui remote browser to recompute window.mozInnerScreenX
    // and window.mozInnerScreenY when the "addon details" page has been
    // scrolled (See Bug 1390445 for rationale).
    // Also force a repaint to fix an issue where the click location was
    // getting out of sync (see bug 1548687).
    this.updatePositionTask = new DeferredTask(() => {
      if (this.browser && this.browser.isRemoteBrowser) {
        // Select boxes can appear in the wrong spot after scrolling, this will
        // clear that up. Bug 1390445.
        this.browser.frameLoader.requestUpdatePosition();
      }
    }, 100);

    this._embedderElement = null;
    this._promiseDisconnected = new Promise(
      resolve => (this._resolveDisconnected = resolve)
    );
  }

  connectedCallback() {
    window.addEventListener("scroll", this, true);
    const { embedderElement } = top.browsingContext;
    this._embedderElement = embedderElement;
    embedderElement.addEventListener("FullZoomChange", this);
    embedderElement.addEventListener("TextZoomChange", this);
  }

  disconnectedCallback() {
    this._resolveDisconnected();
    window.removeEventListener("scroll", this, true);
    this._embedderElement?.removeEventListener("FullZoomChange", this);
    this._embedderElement?.removeEventListener("TextZoomChange", this);
    this._embedderElement = null;
  }

  handleEvent(e) {
    switch (e.type) {
      case "scroll":
        return this.updatePositionTask.arm();
      case "FullZoomChange":
      case "TextZoomChange":
        return this.maybeUpdateZoom();
    }
    return undefined;
  }

  maybeUpdateZoom() {
    let bc = this.browser?.browsingContext;
    let topBc = top.browsingContext;
    if (!bc || !topBc) {
      return;
    }
    // Use the same full-zoom as our top window.
    bc.fullZoom = topBc.fullZoom;
    bc.textZoom = topBc.textZoom;
  }

  setAddon(addon) {
    this.addon = addon;
  }

  destroyBrowser() {
    this.textContent = "";
  }

  ensureBrowserCreated() {
    if (this.childElementCount === 0) {
      this.render();
    }
  }

  async render() {
    let { addon } = this;
    if (!addon) {
      throw new Error("addon required to create inline options");
    }

    let browser = document.createXULElement("browser");
    browser.setAttribute("type", "content");
    browser.setAttribute("disableglobalhistory", "true");
    browser.setAttribute("messagemanagergroup", "webext-browsers");
    browser.setAttribute("id", "addon-inline-options");
    browser.setAttribute("class", "addon-inline-options");
    browser.setAttribute("transparent", "true");
    browser.setAttribute("forcemessagemanager", "true");
    browser.setAttribute("autocompletepopup", "PopupAutoComplete");

    let { optionsURL, optionsBrowserStyle } = addon;
    if (addon.isWebExtension) {
      let policy = ExtensionParent.WebExtensionPolicy.getByID(addon.id);
      browser.setAttribute(
        "initialBrowsingContextGroupId",
        policy.browsingContextGroupId
      );
    }

    let readyPromise;
    let remoteSubframes = window.docShell.QueryInterface(
      Ci.nsILoadContext
    ).useRemoteSubframes;
    // For now originAttributes have no effect, which will change if the
    // optionsURL becomes anything but moz-extension* or we start considering
    // OA for extensions.
    var oa = E10SUtils.predictOriginAttributes({ browser });
    let loadRemote = E10SUtils.canLoadURIInRemoteType(
      optionsURL,
      remoteSubframes,
      E10SUtils.EXTENSION_REMOTE_TYPE,
      oa
    );
    if (loadRemote) {
      browser.setAttribute("remote", "true");
      browser.setAttribute("remoteType", E10SUtils.EXTENSION_REMOTE_TYPE);

      readyPromise = promiseEvent("XULFrameLoaderCreated", browser);
    } else {
      readyPromise = promiseEvent("load", browser, true);
    }

    this.appendChild(browser);
    this.browser = browser;

    // Force bindings to apply synchronously.
    browser.clientTop;

    await readyPromise;

    this.maybeUpdateZoom();

    if (!browser.messageManager) {
      // If the browser.messageManager is undefined, the browser element has
      // been removed from the document in the meantime (e.g. due to a rapid
      // sequence of addon reload), return null.
      return;
    }

    ExtensionParent.apiManager.emit("extension-browser-inserted", browser);

    await new Promise(resolve => {
      let messageListener = {
        receiveMessage({ name, data }) {
          if (name === "Extension:BrowserResized") {
            browser.style.height = `${data.height}px`;
          } else if (name === "Extension:BrowserContentLoaded") {
            resolve();
          }
        },
      };

      let mm = browser.messageManager;

      if (!mm) {
        // If the browser.messageManager is undefined, the browser element has
        // been removed from the document in the meantime (e.g. due to a rapid
        // sequence of addon reload), return null.
        resolve();
        return;
      }

      mm.loadFrameScript(
        "chrome://extensions/content/ext-browser-content.js",
        false,
        true
      );
      mm.addMessageListener("Extension:BrowserContentLoaded", messageListener);
      mm.addMessageListener("Extension:BrowserResized", messageListener);

      let browserOptions = {
        fixedWidth: true,
        isInline: true,
      };

      if (optionsBrowserStyle) {
        // aboutaddons.js is not used on Android. extension.css is included in
        // Firefox desktop and Thunderbird.
        // eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
        browserOptions.stylesheets = ["chrome://browser/content/extension.css"];
      }

      mm.sendAsyncMessage("Extension:InitBrowser", browserOptions);

      if (browser.isConnectedAndReady) {
        this.fixupAndLoadURIString(optionsURL);
      } else {
        // browser custom element does opt-in the delayConnectedCallback
        // behavior (see connectedCallback in the custom element definition
        // from browser-custom-element.js) and so calling browser.loadURI
        // would fail if the about:addons document is not yet fully loaded.
        Promise.race([
          promiseEvent("DOMContentLoaded", document),
          this._promiseDisconnected,
        ]).then(() => {
          this.fixupAndLoadURIString(optionsURL);
        });
      }
    });
  }

  fixupAndLoadURIString(uriString) {
    if (!this.browser || !this.browser.isConnectedAndReady) {
      throw new Error("Fail to loadURI");
    }

    this.browser.fixupAndLoadURIString(uriString, {
      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
    });
  }
}
customElements.define("inline-options-browser", InlineOptionsBrowser);

class UpdateReleaseNotes extends HTMLElement {
  connectedCallback() {
    this.addEventListener("click", this);
  }

  disconnectedCallback() {
    this.removeEventListener("click", this);
  }

  handleEvent(e) {
    // We used to strip links, but ParserUtils.parseFragment() leaves them in,
    // so just make sure we open them using the null principal in a new tab.
    if (e.type == "click" && e.target.localName == "a" && e.target.href) {
      e.preventDefault();
      e.stopPropagation();
      windowRoot.ownerGlobal.openWebLinkIn(e.target.href, "tab");
    }
  }

  async loadForUri(uri) {
    // Can't load the release notes without a URL to load.
    if (!uri || !uri.spec) {
      this.setErrorMessage();
      this.dispatchEvent(new CustomEvent("release-notes-error"));
      return;
    }

    // Don't try to load for the same update a second time.
    if (this.url == uri.spec) {
      this.dispatchEvent(new CustomEvent("release-notes-cached"));
      return;
    }

    // Store the URL to skip the network if loaded again.
    this.url = uri.spec;

    // Set the loading message before hitting the network.
    this.setLoadingMessage();
    this.dispatchEvent(new CustomEvent("release-notes-loading"));

    try {
      // loadReleaseNotes will fetch and sanitize the release notes.
      let fragment = await loadReleaseNotes(uri);
      this.textContent = "";
      this.appendChild(fragment);
      this.dispatchEvent(new CustomEvent("release-notes-loaded"));
    } catch (e) {
      this.setErrorMessage();
      this.dispatchEvent(new CustomEvent("release-notes-error"));
    }
  }

  setMessage(id) {
    this.textContent = "";
    let message = document.createElement("p");
    document.l10n.setAttributes(message, id);
    this.appendChild(message);
  }

  setLoadingMessage() {
    this.setMessage("release-notes-loading");
  }

  setErrorMessage() {
    this.setMessage("release-notes-error");
  }
}
customElements.define("update-release-notes", UpdateReleaseNotes);

class AddonPermissionsList extends HTMLElement {
  setAddon(addon) {
    this.addon = addon;
    this.render();
  }

  async render() {
    let empty = { origins: [], permissions: [] };
    let requiredPerms = { ...(this.addon.userPermissions ?? empty) };
    let optionalPerms = { ...(this.addon.optionalPermissions ?? empty) };
    let grantedPerms = await ExtensionPermissions.get(this.addon.id);

    if (manifestV3enabled) {
      // If optional permissions include <all_urls>, extension can request and
      // be granted permission for individual sites not listed in the manifest.
      // Include them as well in the optional origins list.
      let origins = [
        ...(this.addon.optionalOriginsNormalized ?? []),
        ...grantedPerms.origins.filter(o => !requiredPerms.origins.includes(o)),
      ];
      optionalPerms.origins = [...new Set(origins)];
    }

    let permissions = Extension.formatPermissionStrings(
      {
        permissions: requiredPerms,
        optionalPermissions: optionalPerms,
      },
      { buildOptionalOrigins: manifestV3enabled }
    );
    let optionalEntries = [
      ...Object.entries(permissions.optionalPermissions),
      ...Object.entries(permissions.optionalOrigins),
    ];

    this.textContent = "";
    let frag = importTemplate("addon-permissions-list");

    if (permissions.msgs.length) {
      let section = frag.querySelector(".addon-permissions-required");
      section.hidden = false;
      let list = section.querySelector(".addon-permissions-list");

      for (let msg of permissions.msgs) {
        let item = document.createElement("li");
        item.classList.add("permission-info", "permission-checked");
        item.appendChild(document.createTextNode(msg));
        list.appendChild(item);
      }
    }

    if (optionalEntries.length) {
      let section = frag.querySelector(".addon-permissions-optional");
      section.hidden = false;
      let list = section.querySelector(".addon-permissions-list");

      for (let id = 0; id < optionalEntries.length; id++) {
        let [perm, msg] = optionalEntries[id];

        let type = "permission";
        if (permissions.optionalOrigins[perm]) {
          type = "origin";
        }
        let item = document.createElement("li");
        item.classList.add("permission-info");

        let toggle = document.createElement("moz-toggle");
        toggle.setAttribute("label", msg);
        toggle.id = `permission-${id}`;
        toggle.setAttribute("permission-type", type);

        let checked =
          grantedPerms.permissions.includes(perm) ||
          grantedPerms.origins.includes(perm);

        // If this is one of the "all sites" permissions
        if (Extension.isAllSitesPermission(perm)) {
          // mark it as checked if ANY of the "all sites" permission is granted.
          checked = await AddonCard.optionalAllSitesGranted(this.addon.id);
          toggle.toggleAttribute("permission-all-sites", true);
        }

        toggle.pressed = checked;
        item.classList.toggle("permission-checked", checked);

        toggle.setAttribute("permission-key", perm);
        toggle.setAttribute("action", "toggle-permission");
        item.appendChild(toggle);
        list.appendChild(item);
      }
    }
    if (!permissions.msgs.length && !optionalEntries.length) {
      let row = frag.querySelector(".addon-permissions-empty");
      row.hidden = false;
    }

    this.appendChild(frag);
  }
}
customElements.define("addon-permissions-list", AddonPermissionsList);

class AddonSitePermissionsList extends HTMLElement {
  setAddon(addon) {
    this.addon = addon;
    this.render();
  }

  async render() {
    let permissions = Extension.formatPermissionStrings({
      sitePermissions: this.addon.sitePermissions,
      siteOrigin: this.addon.siteOrigin,
    });

    this.textContent = "";
    let frag = importTemplate("addon-sitepermissions-list");

    if (permissions.msgs.length) {
      let section = frag.querySelector(".addon-permissions-required");
      section.hidden = false;
      let list = section.querySelector(".addon-permissions-list");
      let header = section.querySelector(".permission-header");
      document.l10n.setAttributes(header, "addon-sitepermissions-required", {
        hostname: new URL(this.addon.siteOrigin).hostname,
      });

      for (let msg of permissions.msgs) {
        let item = document.createElement("li");
        item.classList.add("permission-info", "permission-checked");
        item.appendChild(document.createTextNode(msg));
        list.appendChild(item);
      }
    }

    this.appendChild(frag);
  }
}
customElements.define("addon-sitepermissions-list", AddonSitePermissionsList);

class AddonDetails extends HTMLElement {
  connectedCallback() {
    if (!this.children.length) {
      this.render();
    }
    this.deck.addEventListener("view-changed", this);
    this.descriptionShowMoreButton.addEventListener("click", this);
  }

  disconnectedCallback() {
    this.inlineOptions.destroyBrowser();
    this.deck.removeEventListener("view-changed", this);
    this.descriptionShowMoreButton.removeEventListener("click", this);
  }

  handleEvent(e) {
    if (e.type == "view-changed" && e.target == this.deck) {
      switch (this.deck.selectedViewName) {
        case "release-notes":
          let releaseNotes = this.querySelector("update-release-notes");
          let uri = this.releaseNotesUri;
          if (uri) {
            releaseNotes.loadForUri(uri);
          }
          break;
        case "preferences":
          if (getOptionsType(this.addon) == "inline") {
            this.inlineOptions.ensureBrowserCreated();
          }
          break;
      }

      // When a details view is rendered again, the default details view is
      // unconditionally shown. So if any other tab is selected, do not save
      // the current scroll offset, but start at the top of the page instead.
      ScrollOffsets.canRestore = this.deck.selectedViewName === "details";
    } else if (
      e.type == "click" &&
      e.target == this.descriptionShowMoreButton
    ) {
      this.toggleDescription();
    }
  }

  onInstalled() {
    let policy = WebExtensionPolicy.getByID(this.addon.id);
    let extension = policy && policy.extension;
    if (extension && extension.startupReason === "ADDON_UPGRADE") {
      // Ensure the options browser is recreated when a new version starts.
      this.extensionShutdown();
      this.extensionStartup();
    }
  }

  onDisabled() {
    this.extensionShutdown();
  }

  onEnabled() {
    this.extensionStartup();
  }

  extensionShutdown() {
    this.inlineOptions.destroyBrowser();
  }

  extensionStartup() {
    if (this.deck.selectedViewName === "preferences") {
      this.inlineOptions.ensureBrowserCreated();
    }
  }

  toggleDescription() {
    this.descriptionCollapsed = !this.descriptionCollapsed;

    this.descriptionWrapper.classList.toggle(
      "addon-detail-description-collapse",
      this.descriptionCollapsed
    );

    this.descriptionShowMoreButton.hidden = false;
    document.l10n.setAttributes(
      this.descriptionShowMoreButton,
      this.descriptionCollapsed
        ? "addon-detail-description-expand"
        : "addon-detail-description-collapse"
    );
  }

  get releaseNotesUri() {
    let { releaseNotesURI } = getUpdateInstall(this.addon) || this.addon;
    return releaseNotesURI;
  }

  setAddon(addon) {
    this.addon = addon;
  }

  update() {
    let { addon } = this;

    // Hide tab buttons that won't have any content.
    let getButtonByName = name =>
      this.tabGroup.querySelector(`[name="${name}"]`);
    let permsBtn = getButtonByName("permissions");
    permsBtn.hidden = addon.type != "extension";
    let notesBtn = getButtonByName("release-notes");
    notesBtn.hidden = !this.releaseNotesUri;
    let prefsBtn = getButtonByName("preferences");
    prefsBtn.hidden = getOptionsType(addon) !== "inline";
    if (prefsBtn.hidden) {
      if (this.deck.selectedViewName === "preferences") {
        this.deck.selectedViewName = "details";
      }
    } else {
      isAddonOptionsUIAllowed(addon).then(allowed => {
        prefsBtn.hidden = !allowed;
      });
    }

    // Hide the tab group if "details" is the only visible button.
    let tabGroupButtons = this.tabGroup.querySelectorAll(".tab-button");
    this.tabGroup.hidden = Array.from(tabGroupButtons).every(button => {
      return button.name == "details" || button.hidden;
    });

    // Show the update check button if necessary. The button might not exist if
    // the add-on doesn't support updates.
    let updateButton = this.querySelector('[action="update-check"]');
    if (updateButton) {
      updateButton.hidden =
        this.addon.updateInstall || AddonManager.shouldAutoUpdate(this.addon);
    }

    // Set the value for auto updates.
    let inputs = this.querySelectorAll(".addon-detail-row-updates input");
    for (let input of inputs) {
      input.checked = input.value == addon.applyBackgroundUpdates;
    }
  }

  renderDescription(addon) {
    this.descriptionWrapper = this.querySelector(
      ".addon-detail-description-wrapper"
    );
    this.descriptionContents = this.querySelector(".addon-detail-description");
    this.descriptionShowMoreButton = this.querySelector(
      ".addon-detail-description-toggle"
    );

    if (addon.getFullDescription) {
      this.descriptionContents.appendChild(addon.getFullDescription(document));
    } else if (addon.fullDescription) {
      this.descriptionContents.appendChild(nl2br(addon.fullDescription));
    }

    this.descriptionCollapsed = false;

    requestAnimationFrame(() => {
      const remSize = parseFloat(
        getComputedStyle(document.documentElement).fontSize
      );
      const { height } = this.descriptionContents.getBoundingClientRect();

      // collapse description if there are too many lines,i.e. height > (20 rem)
      if (height > 20 * remSize) {
        this.toggleDescription();
      }
    });
  }

  updateQuarantinedDomainsUserAllowed() {
    const { addon } = this;
    let quarantinedDomainsUserAllowedRow = this.querySelector(
      ".addon-detail-row-quarantined-domains"
    );
    if (addon.canChangeQuarantineIgnored) {
      quarantinedDomainsUserAllowedRow.hidden = false;
      quarantinedDomainsUserAllowedRow.nextElementSibling.hidden = false;
      quarantinedDomainsUserAllowedRow.querySelector(
        `[value="${addon.quarantineIgnoredByUser ? 1 : 0}"]`
      ).checked = true;
    } else {
      quarantinedDomainsUserAllowedRow.hidden = true;
      quarantinedDomainsUserAllowedRow.nextElementSibling.hidden = true;
    }
  }

  async render() {
    let { addon } = this;
    if (!addon) {
      throw new Error("addon-details must be initialized by setAddon");
    }

    this.textContent = "";
    this.appendChild(importTemplate("addon-details"));

    this.deck = this.querySelector("named-deck");
    this.tabGroup = this.querySelector(".tab-group");

    // Set the add-on for the permissions section.
    this.permissionsList = this.querySelector("addon-permissions-list");
    this.permissionsList.setAddon(addon);

    // Set the add-on for the sitepermissions section.
    this.sitePermissionsList = this.querySelector("addon-sitepermissions-list");
    if (addon.type == "sitepermission") {
      this.sitePermissionsList.setAddon(addon);
    }
    this.querySelector(".addon-detail-sitepermissions").hidden =
      addon.type !== "sitepermission";

    // Set the add-on for the preferences section.
    this.inlineOptions = this.querySelector("inline-options-browser");
    this.inlineOptions.setAddon(addon);

    // Full description.
    this.renderDescription(addon);
    this.querySelector(".addon-detail-contribute").hidden =
      !addon.contributionURL;
    this.querySelector(".addon-detail-row-updates").hidden = !hasPermission(
      addon,
      "upgrade"
    );

    if (addon.type != "extension") {
      // Don't show any private browsing related section for non-extension
      // addon types, because not relevant or they are either always allowed
      // (e.g. static themes).
      //
      // TODO(Bug 1799090): introduce ad-hoc UI for "sitepermission" addon type.
    } else if (addon.incognito == "not_allowed") {
      let pbRowNotAllowed = this.querySelector(
        ".addon-detail-row-private-browsing-disallowed"
      );
      pbRowNotAllowed.hidden = false;
      pbRowNotAllowed.nextElementSibling.hidden = false;
    } else if (!hasPermission(addon, "change-privatebrowsing")) {
      let pbRowRequired = this.querySelector(
        ".addon-detail-row-private-browsing-required"
      );
      pbRowRequired.hidden = false;
      pbRowRequired.nextElementSibling.hidden = false;
    } else {
      let pbRow = this.querySelector(".addon-detail-row-private-browsing");
      pbRow.hidden = false;
      pbRow.nextElementSibling.hidden = false;
      let isAllowed = await isAllowedInPrivateBrowsing(addon);
      pbRow.querySelector(`[value="${isAllowed ? 1 : 0}"]`).checked = true;
    }

    this.updateQuarantinedDomainsUserAllowed();

    // Author.
    let creatorRow = this.querySelector(".addon-detail-row-author");
    if (addon.creator) {
      let link = creatorRow.querySelector("a");
      link.hidden = !addon.creator.url;
      if (link.hidden) {
        creatorRow.appendChild(new Text(addon.creator.name));
      } else {
        link.href = formatUTMParams(
          "addons-manager-user-profile-link",
          addon.creator.url
        );
        link.target = "_blank";
        link.textContent = addon.creator.name;
      }
    } else {
      creatorRow.hidden = true;
    }

    // Version. Don't show a version for LWTs.
    let version = this.querySelector(".addon-detail-row-version");
    if (addon.version && !/@personas\.mozilla\.org/.test(addon.id)) {
      version.appendChild(new Text(addon.version));
    } else {
      version.hidden = true;
    }

    // Last updated.
    let updateDate = this.querySelector(".addon-detail-row-lastUpdated");
    if (addon.updateDate) {
      let lastUpdated = addon.updateDate.toLocaleDateString(undefined, {
        year: "numeric",
        month: "long",
        day: "numeric",
      });
      updateDate.appendChild(new Text(lastUpdated));
    } else {
      updateDate.hidden = true;
    }

    // Homepage.
    let homepageRow = this.querySelector(".addon-detail-row-homepage");
    if (addon.homepageURL) {
      let homepageURL = homepageRow.querySelector("a");
      homepageURL.href = addon.homepageURL;
      homepageURL.textContent = addon.homepageURL;
    } else {
      homepageRow.hidden = true;
    }

    // Rating.
    let ratingRow = this.querySelector(".addon-detail-row-rating");
    if (addon.reviewURL) {
      ratingRow.querySelector("moz-five-star").rating = addon.averageRating;
      let reviews = ratingRow.querySelector("a");
      reviews.href = formatUTMParams(
        "addons-manager-reviews-link",
        addon.reviewURL
      );
      document.l10n.setAttributes(reviews, "addon-detail-reviews-link", {
        numberOfReviews: addon.reviewCount,
      });
    } else {
      ratingRow.hidden = true;
    }

    this.update();
  }

  showPrefs() {
    if (getOptionsType(this.addon) == "inline") {
      this.deck.selectedViewName = "preferences";
      this.inlineOptions.ensureBrowserCreated();
    }
  }
}
customElements.define("addon-details", AddonDetails);

/**
 * A card component for managing an add-on. It should be initialized by setting
 * the add-on with `setAddon()` before being connected to the document.
 *
 *    let card = document.createElement("addon-card");
 *    card.setAddon(addon);
 *    document.body.appendChild(card);
 */
class AddonCard extends HTMLElement {
  connectedCallback() {
    // If we've already rendered we can just update, otherwise render.
    if (this.children.length) {
      this.update();
    } else {
      this.render();
    }
    this.registerListeners();
  }

  disconnectedCallback() {
    this.removeListeners();
  }

  get expanded() {
    return this.hasAttribute("expanded");
  }

  set expanded(val) {
    if (val) {
      this.setAttribute("expanded", "true");
    } else {
      this.removeAttribute("expanded");
    }
  }

  get updateInstall() {
    return this._updateInstall;
  }

  set updateInstall(install) {
    this._updateInstall = install;
    if (this.children.length) {
      this.update();
    }
  }

  get reloading() {
    return this.hasAttribute("reloading");
  }

  set reloading(val) {
    this.toggleAttribute("reloading", val);
  }

  /**
   * Set the add-on for this card. The card will be populated based on the
   * add-on when it is connected to the DOM.
   *
   * @param {AddonWrapper} addon The add-on to use.
   */
  setAddon(addon) {
    this.addon = addon;
    let install = getUpdateInstall(addon);
    if (
      install &&
      (isInState(install, "available") || isInState(install, "postponed"))
    ) {
      this.updateInstall = install;
    } else {
      this.updateInstall = null;
    }
    if (this.children.length) {
      this.render();
    }
  }

  async setAddonPermission(permission, type, action) {
    let { addon } = this;
    let perms = { origins: [], permissions: [] };

    if (!["add", "remove"].includes(action)) {
      throw new Error("invalid action for permission change");
    }

    if (type === "permission") {
      perms.permissions = [permission];
    } else if (type === "origin") {
      perms.origins = [permission];
    } else {
      throw new Error("unknown permission type changed");
    }

    let normalized = ExtensionPermissions.normalizeOptional(
      perms,
      addon.optionalPermissions
    );

    let policy = WebExtensionPolicy.getByID(addon.id);
    ExtensionPermissions[action](addon.id, normalized, policy?.extension);
  }

  async handleEvent(e) {
    let { addon } = this;
    let action = e.target.getAttribute("action");

    if (e.type == "click") {
      switch (action) {
        case "toggle-disabled":
          // Keep the checked state the same until the add-on's state changes.
          e.target.checked = !addon.userDisabled;
          if (addon.userDisabled) {
            if (shouldShowPermissionsPrompt(addon)) {
              await showPermissionsPrompt(addon);
            } else {
              await addon.enable();
            }
          } else {
            await addon.disable();
          }
          break;
        case "always-activate":
          addon.userDisabled = false;
          break;
        case "never-activate":
          addon.userDisabled = true;
          break;
        case "update-check": {
          let { found } = await checkForUpdate(addon);
          if (!found) {
            this.sendEvent("no-update");
          }
          break;
        }
        case "install-postponed": {
          const { updateInstall } = this;
          if (updateInstall && isInState(updateInstall, "postponed")) {
            updateInstall.continuePostponedInstall();
          }
          break;
        }
        case "install-update":
          // Make sure that an update handler is attached to the install object
          // before starting the update installation (otherwise the user would
          // not be prompted for the new permissions requested if necessary),
          // and also make sure that a prompt handler attached from a closed
          // about:addons tab is replaced by the one attached by the currently
          // active about:addons tab.
          attachUpdateHandler(this.updateInstall);
          this.updateInstall.install().then(
            () => {
              detachUpdateHandler(this.updateInstall);
              // The card will update with the new add-on when it gets
              // installed.
              this.sendEvent("update-installed");
            },
            () => {
              detachUpdateHandler(this.updateInstall);
              // Update our state if the install is cancelled.
              this.update();
              this.sendEvent("update-cancelled");
            }
          );
          // Clear the install since it will be removed from the global list of
          // available updates (whether it succeeds or fails).
          this.updateInstall = null;
          break;
        case "contribute":
          windowRoot.ownerGlobal.openWebLinkIn(addon.contributionURL, "tab");
          break;
        case "preferences":
          if (getOptionsType(addon) == "tab") {
            openOptionsInTab(addon.optionsURL);
          } else if (getOptionsType(addon) == "inline") {
            gViewController.loadView(`detail/${this.addon.id}/preferences`);
          }
          break;
        case "remove":
          {
            this.panel.hide();
            if (!hasPermission(addon, "uninstall")) {
              this.sendEvent("remove-disabled");
              return;
            }
            let { BrowserAddonUI } = windowRoot.ownerGlobal;
            let { remove, report } = await BrowserAddonUI.promptRemoveExtension(
              addon
            );
            if (remove) {
              await addon.uninstall(true);
              this.sendEvent("remove");
              if (report) {
                openAbuseReport({
                  addonId: addon.id,
                  reportEntryPoint: "uninstall",
                });
              }
            } else {
              this.sendEvent("remove-cancelled");
            }
          }
          break;
        case "expand":
          gViewController.loadView(`detail/${this.addon.id}`);
          break;
        case "more-options":
          // Open panel on click from the keyboard.
          if (e.inputSource == MouseEvent.MOZ_SOURCE_KEYBOARD) {
            this.panel.toggle(e);
          }
          break;
        case "report":
          this.panel.hide();
          openAbuseReport({ addonId: addon.id, reportEntryPoint: "menu" });
          break;
        case "link":
          if (e.target.getAttribute("url")) {
            windowRoot.ownerGlobal.openWebLinkIn(
              e.target.getAttribute("url"),
              "tab"
            );
          }
          break;
        default:
          // Handle a click on the card itself.
          if (
            !this.expanded &&
            (e.target === this.addonNameEl || !e.target.closest("a"))
          ) {
            e.preventDefault();
            gViewController.loadView(`detail/${this.addon.id}`);
          }
          break;
      }
    } else if (e.type == "toggle" && action == "toggle-permission") {
      let permission = e.target.getAttribute("permission-key");
      let type = e.target.getAttribute("permission-type");
      let fname = e.target.pressed ? "add" : "remove";
      this.setAddonPermission(permission, type, fname);
    } else if (e.type == "change") {
      let { name } = e.target;
      switch (name) {
        case "autoupdate": {
          addon.applyBackgroundUpdates = e.target.value;
          break;
        }
        case "private-browsing": {
          let policy = WebExtensionPolicy.getByID(addon.id);
          let extension = policy && policy.extension;

          if (e.target.value == "1") {
            await ExtensionPermissions.add(
              addon.id,
              PRIVATE_BROWSING_PERMS,
              extension
            );
          } else {
            await ExtensionPermissions.remove(
              addon.id,
              PRIVATE_BROWSING_PERMS,
              extension
            );
          }
          // Reload the extension if it is already enabled. This ensures any
          // change on the private browsing permission is properly handled.
          if (addon.isActive) {
            this.reloading = true;
            // Reloading will trigger an enable and update the card.
            addon.reload();
          } else {
            // Update the card if the add-on isn't active.
            this.update();
          }
          break;
        }
        case "quarantined-domains-user-allowed": {
          addon.quarantineIgnoredByUser = e.target.value == "1";
          break;
        }
      }
    } else if (e.type == "mousedown") {
      // Open panel on mousedown when the mouse is used.
      if (action == "more-options" && e.button == 0) {
        this.panel.toggle(e);
      }
    } else if (e.type === "shown" || e.type === "hidden") {
      let panelOpen = e.type === "shown";
      // The card will be dimmed if it's disabled, but when the panel is open
      // that should be reverted so the menu items can be easily read.
      this.toggleAttribute("panelopen", panelOpen);
      this.optionsButton.setAttribute("aria-expanded", panelOpen);
    }
  }

  get panel() {
    return this.card.querySelector("panel-list");
  }

  get postponedMessageBar() {
    return this.card.querySelector(".update-postponed-bar");
  }

  registerListeners() {
    this.addEventListener("change", this);
    this.addEventListener("click", this);
    this.addEventListener("mousedown", this);
    this.addEventListener("toggle", this);
    this.panel.addEventListener("shown", this);
    this.panel.addEventListener("hidden", this);
  }

  removeListeners() {
    this.removeEventListener("change", this);
    this.removeEventListener("click", this);
    this.removeEventListener("mousedown", this);
    this.removeEventListener("toggle", this);
    this.panel.removeEventListener("shown", this);
    this.panel.removeEventListener("hidden", this);
  }

  /**
   * Update the card's contents based on the previously set add-on. This should
   * be called if there has been a change to the add-on.
   */
  update() {
    let { addon, card } = this;

    card.setAttribute("active", addon.isActive);

    // Set the icon or theme preview.
    let iconEl = card.querySelector(".addon-icon");
    let preview = card.querySelector(".card-heading-image");
    if (addon.type == "theme") {
      iconEl.hidden = true;
      let screenshotUrl = getScreenshotUrlForAddon(addon);
      if (screenshotUrl) {
        preview.src = screenshotUrl;
      }
      preview.hidden = !screenshotUrl;
    } else {
      preview.hidden = true;
      iconEl.hidden = false;
      if (addon.type == "plugin") {
        iconEl.src = PLUGIN_ICON_URL;
      } else {
        iconEl.src =
          AddonManager.getPreferredIconURL(addon, 32, window) ||
          EXTENSION_ICON_URL;
      }
    }

    // Update the name.
    let name = this.addonNameEl;
    let setDisabledStyle = !(addon.isActive || addon.type === "theme");
    if (!setDisabledStyle) {
      name.textContent = addon.name;
      name.removeAttribute("data-l10n-id");
    } else {
      document.l10n.setAttributes(name, "addon-name-disabled", {
        name: addon.name,
      });
    }
    name.title = `${addon.name} ${addon.version}`;

    let toggleDisabledButton = card.querySelector('[action="toggle-disabled"]');
    if (toggleDisabledButton) {
      let toggleDisabledAction = addon.userDisabled ? "enable" : "disable";
      toggleDisabledButton.hidden = !hasPermission(addon, toggleDisabledAction);
      if (addon.type === "theme") {
        document.l10n.setAttributes(
          toggleDisabledButton,
          `${toggleDisabledAction}-addon-button`
        );
      } else if (
        addon.type === "extension" ||
        addon.type === "sitepermission"
      ) {
        toggleDisabledButton.pressed = !addon.userDisabled;
      }
    }

    // Set the items in the more options menu.
    this.options.update(this, addon, this.updateInstall);

    // Badge the more options button if there's an update.
    let moreOptionsButton = card.querySelector(".more-options-button");
    moreOptionsButton.classList.toggle(
      "more-options-button-badged",
      !!(this.updateInstall && isInState(this.updateInstall, "available"))
    );

    // Postponed update addon card message bar.
    const hasPostponedInstall =
      this.updateInstall && isInState(this.updateInstall, "postponed");
    this.postponedMessageBar.hidden = !hasPostponedInstall;

    // Hide the more options button if it's empty.
    moreOptionsButton.hidden = this.options.visibleItems.length === 0;

    // Ensure all badges are initially hidden.
    for (let node of card.querySelectorAll(".addon-badge")) {
      node.hidden = true;
    }

    // Set the private browsing badge visibility.
    // TODO: We don't show the badge for SitePermsAddon for now, but this should
    // be handled in Bug 1799090.
    if (addon.incognito != "not_allowed" && addon.type == "extension") {
      // Keep update synchronous, the badge can appear later.
      isAllowedInPrivateBrowsing(addon).then(isAllowed => {
        card.querySelector(".addon-badge-private-browsing-allowed").hidden =
          !isAllowed;
      });
    }

    // Show the recommended badges if needed.
    // Plugins don't have recommendationStates, so ensure a default.
    let states = addon.recommendationStates || [];
    for (let badgeName of states) {
      let badge = card.querySelector(`.addon-badge-${badgeName}`);
      if (badge) {
        badge.hidden = false;
      }
    }

    // Update description.
    card.querySelector(".addon-description").textContent = addon.description;

    this.updateMessage();

    // Update the details if they're shown.
    if (this.details) {
      this.details.update();
    }

    this.sendEvent("update");
  }

  async updateMessage() {
    const messageBar = this.card.querySelector(".addon-card-message");

    const {
      linkUrl,
      linkId,
      messageId,
      messageArgs,
      type = "",
    } = await getAddonMessageInfo(this.addon);

    if (messageId) {
      document.l10n.pauseObserving();
      document.l10n.setAttributes(messageBar, messageId, messageArgs);
      messageBar.setAttribute("data-l10n-attrs", "message");

      const link = messageBar.querySelector("button");
      if (linkUrl) {
        document.l10n.setAttributes(link, linkId);
        link.setAttribute("url", linkUrl);
        link.setAttribute("slot", "actions");
        link.hidden = false;
      } else {
        link.removeAttribute("slot");
        link.hidden = true;
      }

      document.l10n.resumeObserving();
      await document.l10n.translateFragment(messageBar);
      messageBar.setAttribute("type", type);
      messageBar.hidden = false;
    } else {
      messageBar.hidden = true;
    }
  }

  showPrefs() {
    this.details.showPrefs();
  }

  expand() {
    if (!this.children.length) {
      this.expanded = true;
    } else {
      throw new Error("expand() is only supported before render()");
    }
  }

  render() {
    this.textContent = "";

    let { addon } = this;
    if (!addon) {
      throw new Error("addon-card must be initialized with setAddon()");
    }

    this.setAttribute("addon-id", addon.id);

    this.card = importTemplate("card").firstElementChild;
    let headingId = ExtensionCommon.makeWidgetId(`${addon.id}-heading`);
    this.card.setAttribute("aria-labelledby", headingId);

    // Remove the toggle-disabled button(s) based on type.
    if (addon.type != "theme") {
      this.card.querySelector(".theme-enable-button").remove();
    }
    if (addon.type != "extension" && addon.type != "sitepermission") {
      this.card.querySelector(".extension-enable-button").remove();
    }

    let nameContainer = this.card.querySelector(".addon-name-container");
    let headingLevel = this.expanded ? "h1" : "h3";
    let nameHeading = document.createElement(headingLevel);
    nameHeading.classList.add("addon-name");
    nameHeading.id = headingId;
    if (!this.expanded) {
      let name = document.createElement("a");
      name.classList.add("addon-name-link");
      name.href = `addons://detail/${addon.id}`;
      nameHeading.appendChild(name);
      this.addonNameEl = name;
    } else {
      this.addonNameEl = nameHeading;
    }
    nameContainer.prepend(nameHeading);

    let panelType = addon.type == "plugin" ? "plugin-options" : "addon-options";
    this.options = document.createElement(panelType);
    this.options.render();
    this.card.appendChild(this.options);
    this.optionsButton = this.card.querySelector(".more-options-button");

    // Set the contents.
    this.update();

    let doneRenderPromise = Promise.resolve();
    if (this.expanded) {
      if (!this.details) {
        this.details = document.createElement("addon-details");
      }
      this.details.setAddon(this.addon);
      doneRenderPromise = this.details.render();

      // If we're re-rendering we still need to append the details since the
      // entire card was emptied at the beginning of the render.
      this.card.appendChild(this.details);
    }

    this.appendChild(this.card);

    if (this.expanded) {
      requestAnimationFrame(() => this.optionsButton.focus());
    }

    // Return the promise of details rendering to wait on in DetailView.
    return doneRenderPromise;
  }

  sendEvent(name, detail) {
    this.dispatchEvent(new CustomEvent(name, { detail }));
  }

  /**
   * AddonManager listener events.
   */

  onNewInstall(install) {
    this.updateInstall = install;
    this.sendEvent("update-found");
  }

  onInstallEnded(install) {
    this.setAddon(install.addon);
  }

  onInstallPostponed(install) {
    this.updateInstall = install;
    this.sendEvent("update-postponed");
  }

  onDisabled() {
    if (!this.reloading) {
      this.update();
    }
  }

  onEnabled() {
    this.reloading = false;
    this.update();
  }

  onInstalled() {
    // When a temporary addon is reloaded, onInstalled is triggered instead of
    // onEnabled.
    this.reloading = false;
    this.update();
  }

  onUninstalling() {
    // Dispatch a remove event, the DetailView is listening for this to get us
    // back to the list view when the current add-on is removed.
    this.sendEvent("remove");
  }

  onUpdateModeChanged() {
    this.update();
  }

  onPropertyChanged(addon, changed) {
    if (this.details && changed.includes("applyBackgroundUpdates")) {
      this.details.update();
    } else if (addon.type == "plugin" && changed.includes("userDisabled")) {
      this.update();
    }

    if (this.details && changed.includes("quarantineIgnoredByUser")) {
      this.details.updateQuarantinedDomainsUserAllowed();
    }
  }

  /* Extension Permission change listener */
  async onChangePermissions(data) {
    let perms = data.added || data.removed;
    let hasAllSites = false;
    for (let permission of perms.permissions.concat(perms.origins)) {
      if (Extension.isAllSitesPermission(permission)) {
        hasAllSites = true;
        continue;
      }
      let target = document.querySelector(`[permission-key="${permission}"]`);
      let checked = !data.removed;
      if (target) {
        target.closest("li").classList.toggle("permission-checked", checked);
        target.pressed = checked;
      }
    }
    if (hasAllSites) {
      // special-case for finding the all-sites target by attribute.
      let target = document.querySelector("[permission-all-sites]");
      let checked = await AddonCard.optionalAllSitesGranted(this.addon.id);
      target.closest("li").classList.toggle("permission-checked", checked);
      target.pressed = checked;
    }
  }

  // Only covers optional_permissions in MV2 and all host permissions in MV3.
  static async optionalAllSitesGranted(addonId) {
    let granted = await ExtensionPermissions.get(addonId);
    return granted.origins.some(perm => Extension.isAllSitesPermission(perm));
  }
}
customElements.define("addon-card", AddonCard);

/**
 * A child element of `<recommended-addon-list>`. It should be initialized
 * by calling `setDiscoAddon()` first. Call `setAddon(addon)` if it has been
 * installed, and call `setAddon(null)` upon uninstall.
 *
 *    let discoAddon = new DiscoAddonWrapper({ ... });
 *    let card = document.createElement("recommended-addon-card");
 *    card.setDiscoAddon(discoAddon);
 *    document.body.appendChild(card);
 *
 *    AddonManager.getAddonsByID(discoAddon.id)
 *      .then(addon => card.setAddon(addon));
 */
class RecommendedAddonCard extends HTMLElement {
  /**
   * @param {DiscoAddonWrapper} addon
   *        The details of the add-on that should be rendered in the card.
   */
  setDiscoAddon(addon) {
    this.addonId = addon.id;

    // Save the information so we can install.
    this.discoAddon = addon;

    let card = importTemplate("card").firstElementChild;
    let heading = card.querySelector(".addon-name-container");
    heading.textContent = "";
    heading.append(importTemplate("addon-name-container-in-disco-card"));

    this.setCardContent(card, addon);
    if (addon.type != "theme") {
      card
        .querySelector(".addon-description")
        .append(importTemplate("addon-description-in-disco-card"));
      this.setCardDescription(card, addon);
    }
    this.registerButtons(card, addon);

    this.textContent = "";
    this.append(card);

    // We initially assume that the add-on is not installed.
    this.setAddon(null);
  }

  /**
   * Fills in all static parts of the card.
   *
   * @param {HTMLElement} card
   *        The primary content of this card.
   * @param {DiscoAddonWrapper} addon
   */
  setCardContent(card, addon) {
    // Set the icon.
    if (addon.type == "theme") {
      card.querySelector(".addon-icon").hidden = true;
    } else {
      card.querySelector(".addon-icon").src = AddonManager.getPreferredIconURL(
        addon,
        32,
        window
      );
    }

    // Set the theme preview.
    let preview = card.querySelector(".card-heading-image");
    if (addon.type == "theme") {
      let screenshotUrl = getScreenshotUrlForAddon(addon);
      if (screenshotUrl) {
        preview.src = screenshotUrl;
        preview.hidden = false;
      }
    } else {
      preview.hidden = true;
    }

    // Set the name.
    card.querySelector(".disco-addon-name").textContent = addon.name;

    // Set the author name and link to AMO.
    if (addon.creator) {
      let authorInfo = card.querySelector(".disco-addon-author");
      document.l10n.setAttributes(authorInfo, "created-by-author", {
        author: addon.creator.name,
      });
      // This is intentionally a link to the add-on listing instead of the
      // author page, because the add-on listing provides more relevant info.
      authorInfo.querySelector("a").href = formatUTMParams(
        "discopane-entry-link",
        addon.amoListingUrl
      );
      authorInfo.hidden = false;
    }
  }

  setCardDescription(card, addon) {
    // Set the description. Note that this is the editorial description, not
    // the add-on's original description that would normally appear on a card.
    card.querySelector(".disco-description-main").textContent =
      addon.editorialDescription;

    let hasStats = false;
    if (addon.averageRating) {
      hasStats = true;
      card.querySelector("moz-five-star").rating = addon.averageRating;
    } else {
      card.querySelector("moz-five-star").hidden = true;
    }

    if (addon.dailyUsers) {
      hasStats = true;
      let userCountElem = card.querySelector(".disco-user-count");
      document.l10n.setAttributes(userCountElem, "user-count", {
        dailyUsers: addon.dailyUsers,
      });
    }

    card.querySelector(".disco-description-statistics").hidden = !hasStats;
  }

  registerButtons(card, addon) {
    let installButton = card.querySelector("[action='install-addon']");
    if (addon.type == "theme") {
      document.l10n.setAttributes(installButton, "install-theme-button");
    } else {
      document.l10n.setAttributes(installButton, "install-extension-button");
    }

    this.addEventListener("click", this);
  }

  handleEvent(event) {
    let action = event.target.getAttribute("action");
    switch (action) {
      case "install-addon":
        this.installDiscoAddon();
        break;
      case "manage-addon":
        gViewController.loadView(`detail/${this.addonId}`);
        break;
    }
  }

  async installDiscoAddon() {
    let addon = this.discoAddon;
    let url = addon.sourceURI.spec;
    let install = await AddonManager.getInstallForURL(url, {
      name: addon.name,
      telemetryInfo: {
        source: "disco",
        taarRecommended: addon.taarRecommended,
      },
    });
    // We are hosted in a <browser> in about:addons, but we can just use the
    // main tab's browser since all of it is using the system principal.
    let browser = window.docShell.chromeEventHandler;
    AddonManager.installAddonFromWebpage(
      "application/x-xpinstall",
      browser,
      Services.scriptSecurityManager.getSystemPrincipal(),
      install
    );
  }

  /**
   * @param {AddonWrapper|null} addon
   *        The add-on that has been installed; null if it has been removed.
   */
  setAddon(addon) {
    let card = this.firstElementChild;
    card.querySelector("[action='install-addon']").hidden = !!addon;
    card.querySelector("[action='manage-addon']").hidden = !addon;

    this.dispatchEvent(new CustomEvent("disco-card-updated")); // For testing.
  }
}
customElements.define("recommended-addon-card", RecommendedAddonCard);

/**
 * A list view for add-ons of a certain type. It should be initialized with the
 * type of add-on to render and have section data set before being connected to
 * the document.
 *
 *    let list = document.createElement("addon-list");
 *    list.type = "plugin";
 *    list.setSections([{
 *      headingId: "plugin-section-heading",
 *      filterFn: addon => !addon.isSystem,
 *    }]);
 *    document.body.appendChild(list);
 */
class AddonList extends HTMLElement {
  constructor() {
    super();
    this.sections = [];
    this.pendingUninstallAddons = new Set();
    this._addonsToUpdate = new Set();
    this._userFocusListenersAdded = false;
  }

  async connectedCallback() {
    // Register the listener and get the add-ons, these operations should
    // happpen as close to each other as possible.
    this.registerListener();
    // Don't render again if we were rendered prior to being inserted.
    if (!this.children.length) {
      // Render the initial view.
      this.render();
    }
  }

  disconnectedCallback() {
    // Remove content and stop listening until this is connected again.
    this.textContent = "";
    this.removeListener();

    // Process any pending uninstall related to this list.
    for (const addon of this.pendingUninstallAddons) {
      if (isPending(addon, "uninstall")) {
        addon.uninstall();
      }
    }
    this.pendingUninstallAddons.clear();
  }

  /**
   * Configure the sections in the list.
   *
   * @param {object[]} sections
   *        The options for the section. Each entry in the array should have:
   *          headingId: The fluent id for the section's heading.
   *          filterFn: A function that determines if an add-on belongs in
   *                    the section.
   */
  setSections(sections) {
    this.sections = sections.map(section => Object.assign({}, section));
  }

  /**
   * Set the add-on type for this list. This will be used to filter the add-ons
   * that are displayed.
   *
   * @param {string} val The type to filter on.
   */
  set type(val) {
    this.setAttribute("type", val);
  }

  get type() {
    return this.getAttribute("type");
  }

  getSection(index) {
    return this.sections[index].node;
  }

  getCards(section) {
    return section.querySelectorAll("addon-card");
  }

  getCard(addon) {
    return this.querySelector(`addon-card[addon-id="${addon.id}"]`);
  }

  getPendingUninstallBar(addon) {
    return this.querySelector(`moz-message-bar[addon-id="${addon.id}"]`);
  }

  sortByFn(aAddon, bAddon) {
    return aAddon.name.localeCompare(bAddon.name);
  }

  async getAddons() {
    if (!this.type) {
      throw new Error(`type must be set to find add-ons`);
    }

    // Find everything matching our type, null will find all types.
    let type = this.type == "all" ? null : [this.type];
    let addons = await AddonManager.getAddonsByTypes(type);

    if (type == "theme") {
      await BuiltInThemes.ensureBuiltInThemes();
    }

    // Put the add-ons into the sections, an add-on goes in the first section
    // that it matches the filterFn for. It might not go in any section.
    let sectionedAddons = this.sections.map(() => []);
    for (let addon of addons) {
      let index = this.sections.findIndex(({ filterFn }) => filterFn(addon));
      if (index != -1) {
        sectionedAddons[index].push(addon);
      } else if (isPending(addon, "uninstall")) {
        // A second tab may be opened on "about:addons" (or Firefox may
        // have crashed) while there are still "pending uninstall" add-ons.
        // Ensure to list them in the pendingUninstall message-bar-stack
        // when the AddonList is initially rendered.
        this.pendingUninstallAddons.add(addon);
      }
    }

    // Sort the add-ons in each section.
    for (let [index, section] of sectionedAddons.entries()) {
      let sortByFn = this.sections[index].sortByFn || this.sortByFn;
      section.sort(sortByFn);
    }

    return sectionedAddons;
  }

  createPendingUninstallStack() {
    const stack = document.createElement("message-bar-stack");
    stack.setAttribute("class", "pending-uninstall");
    stack.setAttribute("reverse", "");
    return stack;
  }

  addPendingUninstallBar(addon) {
    const stack = this.pendingUninstallStack;
    const mb = document.createElement("moz-message-bar");
    mb.setAttribute("addon-id", addon.id);
    mb.setAttribute("type", "info");

    const undo = document.createElement("button");
    undo.setAttribute("action", "undo");
    undo.addEventListener("click", () => {
      addon.cancelUninstall();
    });
    undo.setAttribute("slot", "actions");

    document.l10n.setAttributes(mb, "pending-uninstall-description2", {
      addon: addon.name,
    });
    mb.setAttribute("data-l10n-attrs", "message");
    document.l10n.setAttributes(undo, "pending-uninstall-undo-button");

    mb.appendChild(undo);
    stack.append(mb);
  }

  removePendingUninstallBar(addon) {
    const messagebar = this.getPendingUninstallBar(addon);
    if (messagebar) {
      messagebar.remove();
    }
  }

  createSectionHeading(headingIndex) {
    let { headingId, subheadingId } = this.sections[headingIndex];
    let frag = document.createDocumentFragment();
    let heading = document.createElement("h2");
    heading.classList.add("list-section-heading");
    document.l10n.setAttributes(heading, headingId);
    frag.append(heading);

    if (subheadingId) {
      heading.className = "header-name";
      let subheading = document.createElement("h3");
      subheading.classList.add("list-section-subheading");
      document.l10n.setAttributes(subheading, subheadingId);
      frag.append(subheading);
    }

    return frag;
  }

  createEmptyListMessage() {
    let emptyMessage = "list-empty-get-extensions-message";
    let linkPref = "extensions.getAddons.link.url";

    if (this.sections && this.sections.length) {
      if (this.sections[0].headingId == "locale-enabled-heading") {
        emptyMessage = "list-empty-get-language-packs-message";
        linkPref = "browser.dictionaries.download.url";
      } else if (this.sections[0].headingId == "dictionary-enabled-heading") {
        emptyMessage = "list-empty-get-dictionaries-message";
        linkPref = "browser.dictionaries.download.url";
      }
    }

    let messageContainer = document.createElement("p");
    messageContainer.id = "empty-addons-message";
    let a = document.createElement("a");
    a.href = Services.urlFormatter.formatURLPref(linkPref);
    a.setAttribute("target", "_blank");
    a.setAttribute("data-l10n-name", "get-extensions");
    document.l10n.setAttributes(messageContainer, emptyMessage, {
      domain: a.hostname,
    });
    messageContainer.appendChild(a);
    return messageContainer;
  }

  updateSectionIfEmpty(section) {
    // The header is added before any add-on cards, so if there's only one
    // child then it's the header. In that case we should empty out the section.
    if (section.children.length == 1) {
      section.textContent = "";
    }
  }

  insertCardInto(card, sectionIndex) {
    let section = this.getSection(sectionIndex);
    let sectionCards = this.getCards(section);

    // If this is the first card in the section, create the heading.
    if (!sectionCards.length) {
      section.appendChild(this.createSectionHeading(sectionIndex));
    }

    // Find where to insert the card.
    let insertBefore = Array.from(sectionCards).find(
      otherCard => this.sortByFn(card.addon, otherCard.addon) < 0
    );
    // This will append if insertBefore is null.
    section.insertBefore(card, insertBefore || null);
  }

  addAddon(addon) {
    // Only insert add-ons of the right type.
    if (addon.type != this.type && this.type != "all") {
      this.sendEvent("skip-add", "type-mismatch");
      return;
    }

    let insertSection = this._addonSectionIndex(addon);

    // Don't add the add-on if it doesn't go in a section.
    if (insertSection == -1) {
      return;
    }

    // Create and insert the card.
    let card = document.createElement("addon-card");
    card.setAddon(addon);
    this.insertCardInto(card, insertSection);
    this.sendEvent("add", { id: addon.id });
  }

  sendEvent(name, detail) {
    this.dispatchEvent(new CustomEvent(name, { detail }));
  }

  removeAddon(addon) {
    let card = this.getCard(addon);
    if (card) {
      let section = card.parentNode;
      card.remove();
      this.updateSectionIfEmpty(section);
      this.sendEvent("remove", { id: addon.id });
    }
  }

  updateAddon(addon) {
    if (!this.getCard(addon)) {
      // Try to add the add-on right away.
      this.addAddon(addon);
    } else if (this._addonSectionIndex(addon) == -1) {
      // Try to remove the add-on right away.
      this._updateAddon(addon);
    } else if (this.isUserFocused) {
      // Queue up a change for when the focus is cleared.
      this.updateLater(addon);
    } else {
      // Not currently focused, make the change now.
      this.withCardAnimation(() => this._updateAddon(addon));
    }
  }

  updateLater(addon) {
    this._addonsToUpdate.add(addon);
    this._addUserFocusListeners();
  }

  _addUserFocusListeners() {
    if (this._userFocusListenersAdded) {
      return;
    }

    this._userFocusListenersAdded = true;
    this.addEventListener("mouseleave", this);
    this.addEventListener("hidden", this, true);
    this.addEventListener("focusout", this);
  }

  _removeUserFocusListeners() {
    if (!this._userFocusListenersAdded) {
      return;
    }

    this.removeEventListener("mouseleave", this);
    this.removeEventListener("hidden", this, true);
    this.removeEventListener("focusout", this);
    this._userFocusListenersAdded = false;
  }

  get hasMenuOpen() {
    return !!this.querySelector("panel-list[open]");
  }

  get isUserFocused() {
    return this.matches(":hover, :focus-within") || this.hasMenuOpen;
  }

  update() {
    if (this._addonsToUpdate.size) {
      this.withCardAnimation(() => {
        for (let addon of this._addonsToUpdate) {
          this._updateAddon(addon);
        }
        this._addonsToUpdate = new Set();
      });
    }
  }

  _getChildCoords() {
    let results = new Map();
    for (let child of this.querySelectorAll("addon-card")) {
      results.set(child, child.getBoundingClientRect());
    }
    return results;
  }

  withCardAnimation(changeFn) {
    if (shouldSkipAnimations()) {
      changeFn();
      return;
    }

    let origChildCoords = this._getChildCoords();

    changeFn();

    let newChildCoords = this._getChildCoords();
    let cards = this.querySelectorAll("addon-card");
    let transitionCards = [];
    for (let card of cards) {
      let orig = origChildCoords.get(card);
      let moved = newChildCoords.get(card);
      let changeY = moved.y - (orig || moved).y;
      let cardEl = card.firstElementChild;

      if (changeY != 0) {
        cardEl.style.transform = `translateY(${changeY * -1}px)`;
        transitionCards.push(card);
      }
    }
    requestAnimationFrame(() => {
      for (let card of transitionCards) {
        card.firstElementChild.style.transition = "transform 125ms";
      }

      requestAnimationFrame(() => {
        for (let card of transitionCards) {
          let cardEl = card.firstElementChild;
          cardEl.style.transform = "";
          cardEl.addEventListener("transitionend", function handler(e) {
            if (e.target == cardEl && e.propertyName == "transform") {
              cardEl.style.transition = "";
              cardEl.removeEventListener("transitionend", handler);
            }
          });
        }
      });
    });
  }

  _addonSectionIndex(addon) {
    return this.sections.findIndex(s => s.filterFn(addon));
  }

  _updateAddon(addon) {
    let card = this.getCard(addon);
    if (card) {
      let sectionIndex = this._addonSectionIndex(addon);
      if (sectionIndex != -1) {
        // Move the card, if needed. This will allow an animation between
        // page sections and provides clearer events for testing.
        if (card.parentNode.getAttribute("section") != sectionIndex) {
          let { activeElement } = document;
          let refocus = card.contains(activeElement);
          let oldSection = card.parentNode;
          this.insertCardInto(card, sectionIndex);
          this.updateSectionIfEmpty(oldSection);
          if (refocus) {
            activeElement.focus();
          }
          this.sendEvent("move", { id: addon.id });
        }
      } else {
        this.removeAddon(addon);
      }
    }
  }

  renderSection(addons, index) {
    const { sectionClass } = this.sections[index];

    let section = document.createElement("section");
    section.setAttribute("section", index);
    if (sectionClass) {
      section.setAttribute("class", sectionClass);
    }

    // Render the heading and add-ons if there are any.
    if (addons.length) {
      section.appendChild(this.createSectionHeading(index));
    }

    for (let addon of addons) {
      let card = document.createElement("addon-card");
      card.setAddon(addon);
      card.render();
      section.appendChild(card);
    }

    return section;
  }

  async render() {
    this.textContent = "";

    let sectionedAddons = await this.getAddons();

    let frag = document.createDocumentFragment();

    // Render the pending uninstall message-bar-stack.
    this.pendingUninstallStack = this.createPendingUninstallStack();
    for (let addon of this.pendingUninstallAddons) {
      this.addPendingUninstallBar(addon);
    }
    frag.appendChild(this.pendingUninstallStack);

    // Render the sections.
    for (let i = 0; i < sectionedAddons.length; i++) {
      this.sections[i].node = this.renderSection(sectionedAddons[i], i);
      frag.appendChild(this.sections[i].node);
    }

    // Render the placeholder that is shown when all sections are empty.
    // This call is after rendering the sections, because its visibility
    // is controlled through the general sibling combinator relative to
    // the sections (section ~).
    let message = this.createEmptyListMessage();
    frag.appendChild(message);

    // Make sure fluent has set all the strings before we render. This will
    // avoid the height changing as strings go from 0 height to having text.
    await document.l10n.translateFragment(frag);
    this.appendChild(frag);
  }

  registerListener() {
    AddonManagerListenerHandler.addListener(this);
  }

  removeListener() {
    AddonManagerListenerHandler.removeListener(this);
  }

  handleEvent(e) {
    if (!this.isUserFocused || (e.type == "mouseleave" && !this.hasMenuOpen)) {
      this._removeUserFocusListeners();
      this.update();
    }
  }

  /**
   * AddonManager listener events.
   */

  onOperationCancelled(addon) {
    if (
      this.pendingUninstallAddons.has(addon) &&
      !isPending(addon, "uninstall")
    ) {
      this.pendingUninstallAddons.delete(addon);
      this.removePendingUninstallBar(addon);
    }
    this.updateAddon(addon);
  }

  onEnabled(addon) {
    this.updateAddon(addon);
  }

  onDisabled(addon) {
    this.updateAddon(addon);
  }

  onUninstalling(addon) {
    if (
      isPending(addon, "uninstall") &&
      (this.type === "all" || addon.type === this.type)
    ) {
      this.pendingUninstallAddons.add(addon);
      this.addPendingUninstallBar(addon);
      this.updateAddon(addon);
    }
  }

  onInstalled(addon) {
    if (this.querySelector(`addon-card[addon-id="${addon.id}"]`)) {
      return;
    }
    this.addAddon(addon);
  }

  onUninstalled(addon) {
    this.pendingUninstallAddons.delete(addon);
    this.removePendingUninstallBar(addon);
    this.removeAddon(addon);
  }
}
customElements.define("addon-list", AddonList);

class RecommendedAddonList extends HTMLElement {
  connectedCallback() {
    if (this.isConnected) {
      this.loadCardsIfNeeded();
      this.updateCardsWithAddonManager();
    }
    AddonManagerListenerHandler.addListener(this);
  }

  disconnectedCallback() {
    AddonManagerListenerHandler.removeListener(this);
  }

  get type() {
    return this.getAttribute("type");
  }

  /**
   * Set the add-on type for this list. This will be used to filter the add-ons
   * that are displayed.
   *
   * Must be set prior to the first render.
   *
   * @param {string} val The type to filter on.
   */
  set type(val) {
    this.setAttribute("type", val);
  }

  get hideInstalled() {
    return this.hasAttribute("hide-installed");
  }

  /**
   * Set whether installed add-ons should be hidden from the list. If false,
   * installed add-ons will be shown with a "Manage" button, otherwise they
   * will be hidden.
   *
   * Must be set prior to the first render.
   *
   * @param {boolean} val Whether to show installed add-ons.
   */
  set hideInstalled(val) {
    this.toggleAttribute("hide-installed", val);
  }

  getCardById(addonId) {
    for (let card of this.children) {
      if (card.addonId === addonId) {
        return card;
      }
    }
    return null;
  }

  setAddonForCard(card, addon) {
    card.setAddon(addon);

    let wasHidden = card.hidden;
    card.hidden = this.hideInstalled && addon;

    if (wasHidden != card.hidden) {
      let eventName = card.hidden ? "card-hidden" : "card-shown";
      this.dispatchEvent(new CustomEvent(eventName, { detail: { card } }));
    }
  }

  /**
   * Whether the client ID should be preferred. This is disabled for themes
   * since they don't use the telemetry data and don't show the TAAR notice.
   */
  get preferClientId() {
    return !this.type || this.type == "extension";
  }

  async updateCardsWithAddonManager() {
    let cards = Array.from(this.children);
    let addonIds = cards.map(card => card.addonId);
    let addons = await AddonManager.getAddonsByIDs(addonIds);
    for (let [i, card] of cards.entries()) {
      let addon = addons[i];
      this.setAddonForCard(card, addon);
      if (addon) {
        // Already installed, move card to end.
        this.append(card);
      }
    }
  }

  async loadCardsIfNeeded() {
    // Use promise as guard. Also used by tests to detect when load completes.
    if (!this.cardsReady) {
      this.cardsReady = this._loadCards();
    }
    return this.cardsReady;
  }

  async _loadCards() {
    let recommendedAddons;
    try {
      recommendedAddons = await DiscoveryAPI.getResults(this.preferClientId);
    } catch (e) {
      return;
    }

    let frag = document.createDocumentFragment();
    for (let addon of recommendedAddons) {
      if (this.type && addon.type != this.type) {
        continue;
      }
      let card = document.createElement("recommended-addon-card");
      card.setDiscoAddon(addon);
      frag.append(card);
    }
    this.append(frag);
    await this.updateCardsWithAddonManager();
  }

  /**
   * AddonManager listener events.
   */

  onInstalled(addon) {
    let card = this.getCardById(addon.id);
    if (card) {
      this.setAddonForCard(card, addon);
    }
  }

  onUninstalled(addon) {
    let card = this.getCardById(addon.id);
    if (card) {
      this.setAddonForCard(card, null);
    }
  }
}
customElements.define("recommended-addon-list", RecommendedAddonList);

class TaarMessageBar extends HTMLElement {
  connectedCallback() {
    this.hidden =
      Services.prefs.getBoolPref(PREF_RECOMMENDATION_HIDE_NOTICE, false) ||
      !DiscoveryAPI.clientIdDiscoveryEnabled;
    if (this.childElementCount == 0 && !this.hidden) {
      this.appendChild(importTemplate("taar-notice"));
      this.addEventListener("click", this);
      this.messageBar = this.querySelector("moz-message-bar");
      this.messageBar.addEventListener("message-bar:user-dismissed", this);
    }
  }

  handleEvent(e) {
    if (e.type == "message-bar:user-dismissed") {
      Services.prefs.setBoolPref(PREF_RECOMMENDATION_HIDE_NOTICE, true);
    }
  }
}
customElements.define("taar-notice", TaarMessageBar);

class RecommendedFooter extends HTMLElement {
  connectedCallback() {
    if (this.childElementCount == 0) {
      this.appendChild(importTemplate("recommended-footer"));
      this.querySelector(".privacy-policy-link").href =
        Services.prefs.getStringPref(PREF_PRIVACY_POLICY_URL);
      this.addEventListener("click", this);
    }
  }

  handleEvent(event) {
    let action = event.target.getAttribute("action");
    switch (action) {
      case "open-amo":
        openAmoInTab(this);
        break;
    }
  }
}
customElements.define("recommended-footer", RecommendedFooter, {
  extends: "footer",
});

class RecommendedThemesFooter extends HTMLElement {
  connectedCallback() {
    if (this.childElementCount == 0) {
      this.appendChild(importTemplate("recommended-themes-footer"));
      let themeRecommendationRow = this.querySelector(".theme-recommendation");
      let themeRecommendationUrl = Services.prefs.getStringPref(
        PREF_THEME_RECOMMENDATION_URL
      );
      if (themeRecommendationUrl) {
        themeRecommendationRow.querySelector("a").href = themeRecommendationUrl;
      }
      themeRecommendationRow.hidden = !themeRecommendationUrl;
      this.addEventListener("click", this);
    }
  }

  handleEvent(event) {
    let action = event.target.getAttribute("action");
    switch (action) {
      case "open-amo":
        openAmoInTab(this, "themes");
        break;
    }
  }
}
customElements.define("recommended-themes-footer", RecommendedThemesFooter, {
  extends: "footer",
});

/**
 * This element will handle showing recommendations with a
 * <recommended-addon-list> and a <footer>. The footer will be hidden until
 * the <recommended-addon-list> is done making its request so the footer
 * doesn't move around.
 *
 * Subclass this element to use it and define a `template` property to pull
 * the template from. Expected template:
 *
 * <h1>My extra content can go here.</h1>
 * <p>It can be anything but a footer or recommended-addon-list.</p>
 * <recommended-addon-list></recommended-addon-list>
 * <footer>My custom footer</footer>
 */
class RecommendedSection extends HTMLElement {
  connectedCallback() {
    if (this.childElementCount == 0) {
      this.render();
    }
  }

  get list() {
    return this.querySelector("recommended-addon-list");
  }

  get footer() {
    return this.querySelector("footer");
  }

  render() {
    this.appendChild(importTemplate(this.template));

    // Hide footer until the cards are loaded, to prevent the content from
    // suddenly shifting when the user attempts to interact with it.
    let { footer } = this;
    footer.hidden = true;
    this.list.loadCardsIfNeeded().finally(() => {
      footer.hidden = false;
    });
  }
}

class RecommendedExtensionsSection extends RecommendedSection {
  get template() {
    return "recommended-extensions-section";
  }
}
customElements.define(
  "recommended-extensions-section",
  RecommendedExtensionsSection
);

class RecommendedThemesSection extends RecommendedSection {
  get template() {
    return "recommended-themes-section";
  }
}
customElements.define("recommended-themes-section", RecommendedThemesSection);

class DiscoveryPane extends RecommendedSection {
  get template() {
    return "discopane";
  }
}
customElements.define("discovery-pane", DiscoveryPane);

// Define views
gViewController.defineView("list", async type => {
  if (!AddonManager.hasAddonType(type)) {
    return null;
  }

  let frag = document.createDocumentFragment();
  let list = document.createElement("addon-list");
  list.type = type;

  let sections = [
    {
      headingId: type + "-enabled-heading",
      sectionClass: `${type}-enabled-section`,
      filterFn: addon =>
        !addon.hidden && addon.isActive && !isPending(addon, "uninstall"),
    },
  ];

  const disabledAddonsFilterFn = addon =>
    !addon.hidden && !addon.isActive && !isPending(addon, "uninstall");

  sections.push({
    headingId: getL10nIdMapping(`${type}-disabled-heading`),
    sectionClass: `${type}-disabled-section`,
    filterFn: disabledAddonsFilterFn,
  });

  list.setSections(sections);
  frag.appendChild(list);

  // Show recommendations for themes and extensions.
  if (
    LIST_RECOMMENDATIONS_ENABLED &&
    (type == "extension" || type == "theme")
  ) {
    let elementName =
      type == "extension"
        ? "recommended-extensions-section"
        : "recommended-themes-section";
    let recommendations = document.createElement(elementName);
    // Start loading the recommendations. This can finish after the view load
    // event is sent.
    recommendations.render();
    frag.appendChild(recommendations);
  }

  await list.render();

  return frag;
});

gViewController.defineView("detail", async param => {
  let [id, selectedTab] = param.split("/");
  let addon = await AddonManager.getAddonByID(id);

  if (!addon) {
    return null;
  }

  let card = document.createElement("addon-card");

  // Ensure the category for this add-on type is selected.
  document.querySelector("categories-box").selectType(addon.type);

  // Go back to the list view when the add-on is removed.
  card.addEventListener("remove", () =>
    gViewController.loadView(`list/${addon.type}`)
  );

  card.setAddon(addon);
  card.expand();
  await card.render();
  if (selectedTab === "preferences" && (await isAddonOptionsUIAllowed(addon))) {
    card.showPrefs();
  }

  return card;
});

gViewController.defineView("updates", async param => {
  let list = document.createElement("addon-list");
  list.type = "all";
  if (param == "available") {
    list.setSections([
      {
        headingId: "available-updates-heading",
        filterFn: addon => {
          // Filter the addons visible in the updates view using the same
          // criteria that is being used to compute the counter on the
          // available updates category button badge.
          const install = getUpdateInstall(addon);
          return install && isManualUpdate(install) && !install.installed;
        },
      },
    ]);
  } else if (param == "recent") {
    list.sortByFn = (a, b) => {
      if (a.updateDate > b.updateDate) {
        return -1;
      }
      if (a.updateDate < b.updateDate) {
        return 1;
      }
      return 0;
    };
    let updateLimit = new Date() - UPDATES_RECENT_TIMESPAN;
    list.setSections([
      {
        headingId: "recent-updates-heading",
        filterFn: addon =>
          !addon.hidden && addon.updateDate && addon.updateDate > updateLimit,
      },
    ]);
  } else {
    throw new Error(`Unknown updates view ${param}`);
  }

  await list.render();
  return list;
});

gViewController.defineView("discover", async () => {
  let discopane = document.createElement("discovery-pane");
  discopane.render();
  await document.l10n.translateFragment(discopane);
  return discopane;
});

gViewController.defineView("shortcuts", async () => {
  // Force the extension category to be selected, in the case of a reload,
  // restart, or if the view was opened from another category's page.
  document.querySelector("categories-box").selectType("extension");

  let view = document.createElement("addon-shortcuts");
  await view.render();
  await document.l10n.translateFragment(view);
  return view;
});

/**
 * @param {Element} el The button element.
 */
function openAmoInTab(el, path) {
  let amoUrl = Services.urlFormatter.formatURLPref(
    "extensions.getAddons.link.url"
  );

  if (path) {
    amoUrl += path;
  }

  amoUrl = formatUTMParams("find-more-link-bottom", amoUrl);
  windowRoot.ownerGlobal.openTrustedLinkIn(amoUrl, "tab");
}

/**
 * Called when about:addons is loaded.
 */
async function initialize() {
  window.addEventListener(
    "unload",
    () => {
      // Clear out the document so the disconnectedCallback will trigger
      // properly and all of the custom elements can cleanup.
      document.body.textContent = "";
      AddonManagerListenerHandler.shutdown();
    },
    { once: true }
  );

  // Init UI and view management
  gViewController.initialize(document.getElementById("main"));

  document.querySelector("categories-box").initialize();
  AddonManagerListenerHandler.startup();

  // browser.js may call loadView here if it expects an EM-loaded notification
  gViewController.notifyEMLoaded();

  // Select an initial view if no listener has set one so far
  if (!gViewController.currentViewId) {
    if (history.state) {
      // If there is a history state to restore then use that
      await gViewController.renderState(history.state);
    } else {
      // Fallback to the last category or first valid category view otherwise.
      await gViewController.loadView(
        Services.prefs.getStringPref(
          PREF_UI_LASTCATEGORY,
          gViewController.defaultViewId
        )
      );
    }
  }
}

window.promiseInitialized = new Promise(resolve => {
  window.addEventListener(
    "load",
    () => {
      initialize().then(resolve);
    },
    { once: true }
  );
});
PK
!<�s�8ee>chrome/toolkit/content/mozapps/extensions/aboutaddonsCommon.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint max-len: ["error", 80] */

"use strict";

/* exported attachUpdateHandler, detachUpdateHandler,
 *          getBrowserElement, installAddonsFromFilePicker,
 *          isCorrectlySigned, isDisabledUnsigned, isDiscoverEnabled,
 *          isPending, loadReleaseNotes, openOptionsInTab, promiseEvent,
 *          shouldShowPermissionsPrompt, showPermissionsPrompt,
 *          PREF_UI_LASTCATEGORY */

const { AddonSettings } = ChromeUtils.importESModule(
  "resource://gre/modules/addons/AddonSettings.sys.mjs"
);
var { XPCOMUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/XPCOMUtils.sys.mjs"
);

const HTML_NS = "http://www.w3.org/1999/xhtml";

ChromeUtils.defineESModuleGetters(this, {
  Extension: "resource://gre/modules/Extension.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "XPINSTALL_ENABLED",
  "xpinstall.enabled",
  true
);

const PREF_DISCOVER_ENABLED = "extensions.getAddons.showPane";
const PREF_UI_LASTCATEGORY = "extensions.ui.lastCategory";

function isDiscoverEnabled() {
  try {
    if (!Services.prefs.getBoolPref(PREF_DISCOVER_ENABLED)) {
      return false;
    }
  } catch (e) {}

  if (!XPINSTALL_ENABLED) {
    return false;
  }

  return true;
}

function getBrowserElement() {
  return window.docShell.chromeEventHandler;
}

function promiseEvent(event, target, capture = false) {
  return new Promise(resolve => {
    target.addEventListener(event, resolve, { capture, once: true });
  });
}

function installPromptHandler(info) {
  const install = this;

  let oldPerms = info.existingAddon.userPermissions;
  if (!oldPerms) {
    // Updating from a legacy add-on, let it proceed
    return Promise.resolve();
  }

  let newPerms = info.addon.userPermissions;

  let difference = Extension.comparePermissions(oldPerms, newPerms);

  // If there are no new permissions, just proceed
  if (!difference.origins.length && !difference.permissions.length) {
    return Promise.resolve();
  }

  return new Promise((resolve, reject) => {
    let subject = {
      wrappedJSObject: {
        target: getBrowserElement(),
        info: {
          type: "update",
          addon: info.addon,
          icon: info.addon.iconURL,
          // Reference to the related AddonInstall object (used in
          // AMTelemetry to link the recorded event to the other events from
          // the same install flow).
          install,
          permissions: difference,
          resolve,
          reject,
        },
      },
    };
    Services.obs.notifyObservers(subject, "webextension-permission-prompt");
  });
}

function attachUpdateHandler(install) {
  install.promptHandler = installPromptHandler;
}

function detachUpdateHandler(install) {
  if (install?.promptHandler === installPromptHandler) {
    install.promptHandler = null;
  }
}

async function loadReleaseNotes(uri) {
  const res = await fetch(uri.spec, { credentials: "omit" });

  if (!res.ok) {
    throw new Error("Error loading release notes");
  }

  // Load the content.
  const text = await res.text();

  // Setup the content sanitizer.
  const ParserUtils = Cc["@mozilla.org/parserutils;1"].getService(
    Ci.nsIParserUtils
  );
  const flags =
    ParserUtils.SanitizerDropMedia |
    ParserUtils.SanitizerDropNonCSSPresentation |
    ParserUtils.SanitizerDropForms;

  // Sanitize and parse the content to a fragment.
  const context = document.createElementNS(HTML_NS, "div");
  return ParserUtils.parseFragment(text, flags, false, uri, context);
}

function openOptionsInTab(optionsURL) {
  let mainWindow = window.windowRoot.ownerGlobal;
  if ("switchToTabHavingURI" in mainWindow) {
    mainWindow.switchToTabHavingURI(optionsURL, true, {
      relatedToCurrent: true,
      triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
    });
    return true;
  }
  return false;
}

function shouldShowPermissionsPrompt(addon) {
  if (!addon.isWebExtension || addon.seen) {
    return false;
  }

  let perms = addon.installPermissions;
  return perms?.origins.length || perms?.permissions.length;
}

function showPermissionsPrompt(addon) {
  return new Promise(resolve => {
    const permissions = addon.installPermissions;
    const target = getBrowserElement();

    const onAddonEnabled = () => {
      // The user has just enabled a sideloaded extension, if the permission
      // can be changed for the extension, show the post-install panel to
      // give the user that opportunity.
      if (
        addon.permissions & AddonManager.PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS
      ) {
        Services.obs.notifyObservers(
          { addon, target },
          "webextension-install-notify"
        );
      }
      resolve();
    };

    const subject = {
      wrappedJSObject: {
        target,
        info: {
          type: "sideload",
          addon,
          icon: addon.iconURL,
          permissions,
          resolve() {
            addon.markAsSeen();
            addon.enable().then(onAddonEnabled);
          },
          reject() {
            // Ignore a cancelled permission prompt.
          },
        },
      },
    };
    Services.obs.notifyObservers(subject, "webextension-permission-prompt");
  });
}

function isCorrectlySigned(addon) {
  // Add-ons without an "isCorrectlySigned" property are correctly signed as
  // they aren't the correct type for signing.
  return addon.isCorrectlySigned !== false;
}

function isDisabledUnsigned(addon) {
  let signingRequired =
    addon.type == "locale"
      ? AddonSettings.LANGPACKS_REQUIRE_SIGNING
      : AddonSettings.REQUIRE_SIGNING;
  return signingRequired && !isCorrectlySigned(addon);
}

function isPending(addon, action) {
  const amAction = AddonManager["PENDING_" + action.toUpperCase()];
  return !!(addon.pendingOperations & amAction);
}

async function installAddonsFromFilePicker() {
  let [dialogTitle, filterName] = await document.l10n.formatMessages([
    { id: "addon-install-from-file-dialog-title" },
    { id: "addon-install-from-file-filter-name" },
  ]);
  const nsIFilePicker = Ci.nsIFilePicker;
  var fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
  fp.init(
    window.browsingContext,
    dialogTitle.value,
    nsIFilePicker.modeOpenMultiple
  );
  try {
    fp.appendFilter(filterName.value, "*.xpi;*.jar;*.zip");
    fp.appendFilters(nsIFilePicker.filterAll);
  } catch (e) {}

  return new Promise(resolve => {
    fp.open(async result => {
      if (result != nsIFilePicker.returnOK) {
        return;
      }

      let installTelemetryInfo = {
        source: "about:addons",
        method: "install-from-file",
      };

      let browser = getBrowserElement();
      let installs = [];
      for (let file of fp.files) {
        let install = await AddonManager.getInstallForFile(
          file,
          null,
          installTelemetryInfo
        );
        AddonManager.installAddonFromAOM(
          browser,
          document.documentURIObject,
          install
        );
        installs.push(install);
      }
      resolve(installs);
    });
  });
}
PK
!<��:chrome/toolkit/content/mozapps/extensions/abuse-reports.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint max-len: ["error", 80] */
/* import-globals-from aboutaddonsCommon.js */
/* exported AbuseReporter, openAbuseReport */
/* global windowRoot */

/**
 * This script is part of the HTML about:addons page and it provides some
 * helpers used for abuse reports.
 */

const { AbuseReporter } = ChromeUtils.importESModule(
  "resource://gre/modules/AbuseReporter.sys.mjs"
);

async function openAbuseReport({ addonId }) {
  // TODO: `reportEntryPoint` is also passed to this function but we aren't
  // using it currently. Maybe we should?

  const amoUrl = AbuseReporter.getAMOFormURL({ addonId });
  windowRoot.ownerGlobal.openTrustedLinkIn(amoUrl, "tab", {
    // Make sure the newly open tab is going to be focused, independently
    // from general user prefs.
    forceForeground: true,
  });
}

window.openAbuseReport = openAbuseReport;
PK
!<�`@?\\@chrome/toolkit/content/mozapps/extensions/default-theme/icon.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg width="63" height="63" viewBox="0 0 63 63" fill="none" xmlns="http://www.w3.org/2000/svg">
  <path d="M53.8717 54C66.0976 41.5959 66.0425 21.6295 53.7065 9.29348C41.3705 -3.04253 21.4041 -3.09758 9 9.12833L53.8717 54Z" fill="#23222B"/>
  <path d="M9.12833 9C-3.09758 21.4041 -3.04253 41.3705 9.29348 53.7065C21.6295 66.0425 41.5959 66.0976 54 53.8717L9.12833 9Z" fill="#F0F0F4"/>
</svg>
PK
!<������Echrome/toolkit/content/mozapps/extensions/default-theme/manifest.json{
  "manifest_version": 2,

  "browser_specific_settings": {
    "gecko": {
      "id": "default-theme@mozilla.org"
    }
  },

  "name": "System theme — auto",
  "description": "Follow the operating system setting for buttons, menus, and windows.",
  "author": "Mozilla",
  "version": "1.3",

  "icons": { "32": "icon.svg" },

  "theme": {},

  "dark_theme": {
    "colors": {
      "tab_background_text": "#fbfbfe",
      "tab_selected": "rgb(66,65,77)",
      "tab_text": "rgb(251,251,254)",
      "icons": "rgb(251,251,254)",
      "frame": "#1c1b22",
      "popup": "rgb(66,65,77)",
      "popup_text": "rgb(251,251,254)",
      "popup_border": "rgb(82,82,94)",
      "popup_highlight": "rgb(43,42,51)",
      "tab_line": "transparent",
      "toolbar": "#2b2a33",
      "toolbar_top_separator": "transparent",
      "toolbar_bottom_separator": "hsl(240, 5%, 5%)",
      "toolbar_field": "rgb(28,27,34)",
      "toolbar_field_border": "transparent",
      "toolbar_field_text": "rgb(251,251,254)",
      "toolbar_field_focus": "rgb(66,65,77)",
      "toolbar_text": "rgb(251, 251, 254)",
      "ntp_background": "rgb(43, 42, 51)",
      "ntp_text": "rgb(251, 251, 254)",
      "sidebar": "#38383D",
      "sidebar_text": "rgb(249, 249, 250)",
      "sidebar_border": "rgba(255, 255, 255, 0.1)",
      "button": "rgb(43,42,51)",
      "button_hover": "rgb(82,82,94)",
      "button_active": "rgb(91,91,102)",
      "button_primary": "rgb(0, 221, 255)",
      "button_primary_hover": "rgb(128, 235, 255)",
      "button_primary_active": "rgb(170, 242, 255)",
      "button_primary_color": "rgb(43, 42, 51)",
      "input_background": "#42414D",
      "input_color": "rgb(251,251,254)",
      "urlbar_popup_separator": "rgb(82,82,94)",
      "tab_icon_overlay_stroke": "rgb(66,65,77)",
      "tab_icon_overlay_fill": "rgb(251,251,254)"
    },
    "properties": {
      "panel_hover": "color-mix(in srgb, currentColor 9%, transparent)",
      "panel_active": "color-mix(in srgb, currentColor 14%, transparent)",
      "panel_active_darker": "color-mix(in srgb, currentColor 25%, transparent)",
      "toolbar_field_icon_opacity": "1",
      "zap_gradient": "linear-gradient(90deg, #9059FF 0%, #FF4AA2 52.08%, #FFBD4F 100%)"
    }
  },

  "theme_experiment": {
    "colors": {
      "button": "--button-bgcolor",
      "button_hover": "--button-hover-bgcolor",
      "button_active": "--button-active-bgcolor",
      "button_primary": "--button-primary-bgcolor",
      "button_primary_hover": "--button-primary-hover-bgcolor",
      "button_primary_active": "--button-primary-active-bgcolor",
      "button_primary_color": "--button-primary-color",
      "input_background": "--input-bgcolor",
      "input_color": "--input-color",
      "urlbar_popup_separator": "--urlbarView-separator-color",
      "tab_icon_overlay_stroke": "--tab-icon-overlay-stroke",
      "tab_icon_overlay_fill": "--tab-icon-overlay-fill"
    },
    "properties": {
      "panel_hover": "--panel-item-hover-bgcolor",
      "panel_active": "--arrowpanel-dimmed-further",
      "panel_active_darker": "--panel-item-active-bgcolor",
      "toolbar_field_icon_opacity": "--urlbar-icon-fill-opacity",
      "zap_gradient": "--panel-separator-zap-gradient"
    }
  }
}
PK
!<-<n��Cchrome/toolkit/content/mozapps/extensions/default-theme/preview.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg width="680" height="92" fill="none" xmlns="http://www.w3.org/2000/svg">
  <rect width="680" height="92" fill="#F0F0F4" />
  <g filter="url(#filter0_dd)">
    <rect x="28" y="5" width="166" height="34" rx="4" fill="white" />
  </g>
  <rect x="51" y="20" width="121" height="4" rx="2" fill="#15141A" />
  <rect x="221" y="20" width="121" height="4" rx="2" fill="#15141A" />
  <rect y="44" width="680" height="48" fill="#F9F9FB" />
  <circle cx="24" cy="68" r="6.25" stroke="#5B5B66" stroke-width="1.5" />
  <circle cx="60" cy="68" r="6.25" stroke="#5B5B66" stroke-width="1.5" />
  <rect x="114" y="52" width="488" height="32" rx="4" fill="#F0F0F4" />
  <circle cx="130" cy="68" r="6.25" stroke="#5B5B66" stroke-width="1.5" />
  <rect x="146" y="66" width="308" height="4" rx="2" fill="#5B5B66" />
  <mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="680" height="92">
    <path d="M680 92V0H334C321.85 0 312 9.84974 312 22C312 34.1503 302.15 44 290 44H252C238.745 44 228 54.7452 228 68C228 81.2548 217.255 92 204 92H680Z" fill="#C4C4C4" />
  </mask>
  <g mask="url(#mask0)">
    <rect width="680" height="92" fill="#1C1B22" />
    <rect x="221" y="20" width="121" height="4" rx="2" fill="#B8B7BB" />
    <rect y="44" width="680" height="48" fill="#2B2A33" />
    <line x1="663" y1="73.75" x2="649" y2="73.75" stroke="#FBFBFE" stroke-width="1.5" />
    <line x1="663" y1="67.75" x2="649" y2="67.75" stroke="#FBFBFE" stroke-width="1.5" />
    <line x1="663" y1="61.75" x2="649" y2="61.75" stroke="#FBFBFE" stroke-width="1.5" />
    <rect x="114" y="52" width="488" height="32" rx="4" fill="#1C1B22" />
    <rect x="146" y="66" width="308" height="4" rx="2" fill="white" />
  </g>
  <defs>
    <filter id="filter0_dd" x="24" y="1" width="174" height="42" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
      <feFlood flood-opacity="0" result="BackgroundImageFix" />
      <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
      <feOffset />
      <feGaussianBlur stdDeviation="2" />
      <feColorMatrix type="matrix" values="0 0 0 0 0.501961 0 0 0 0 0.501961 0 0 0 0 0.556863 0 0 0 0.5 0" />
      <feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow" />
      <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" />
      <feOffset />
      <feGaussianBlur stdDeviation="0.5" />
      <feColorMatrix type="matrix" values="0 0 0 0 0.501961 0 0 0 0 0.501961 0 0 0 0 0.556863 0 0 0 0.9 0" />
      <feBlend mode="normal" in2="effect1_dropShadow" result="effect2_dropShadow" />
      <feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow" result="shape" />
    </filter>
  </defs>
</svg>
PK
!<�
�>>Fchrome/toolkit/content/mozapps/extensions/drag-drop-addon-installer.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from aboutaddonsCommon.js */

"use strict";

class DragDropAddonInstaller extends HTMLElement {
  connectedCallback() {
    window.addEventListener("drop", this);
  }

  disconnectedCallback() {
    window.removeEventListener("drop", this);
  }

  canInstallFromEvent(e) {
    let types = e.dataTransfer.types;
    return (
      types.includes("text/uri-list") ||
      types.includes("text/x-moz-url") ||
      types.includes("application/x-moz-file")
    );
  }

  handleEvent(e) {
    if (!XPINSTALL_ENABLED) {
      // Nothing to do if we can't install add-ons.
      return;
    }

    if (e.type == "drop" && this.canInstallFromEvent(e)) {
      this.onDrop(e);
    }
  }

  async onDrop(e) {
    e.preventDefault();

    let dataTransfer = e.dataTransfer;
    let browser = getBrowserElement();
    let urls = [];

    // Convert every dropped item into a url, without this should be sync.
    for (let i = 0; i < dataTransfer.mozItemCount; i++) {
      let url = dataTransfer.mozGetDataAt("text/uri-list", i);
      if (!url) {
        url = dataTransfer.mozGetDataAt("text/x-moz-url", i);
      }
      if (url) {
        url = url.split("\n")[0];
      } else {
        let file = dataTransfer.mozGetDataAt("application/x-moz-file", i);
        if (file) {
          url = Services.io.newFileURI(file).spec;
        }
      }

      if (url) {
        urls.push(url);
      }
    }

    // Install the add-ons, the await clears the event data so we do this last.
    for (let url of urls) {
      let install = await AddonManager.getInstallForURL(url, {
        telemetryInfo: {
          source: "about:addons",
          method: "drag-and-drop",
        },
      });

      AddonManager.installAddonFromAOM(
        browser,
        document.documentURIObject,
        install
      );
    }
  }
}
customElements.define("drag-drop-addon-installer", DragDropAddonInstaller);
PK
!<rF�
�
�
7chrome/toolkit/content/mozapps/extensions/shortcuts.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

.shortcut.card {
  margin-bottom: 16px;
}

.shortcut.card:first-of-type {
  margin-top: 8px;
}

.shortcut.card:hover {
  box-shadow: var(--card-shadow);
}

.shortcut.card .card-heading-icon {
  width: 24px;
  height: 24px;
  margin-inline-end: 16px;
  -moz-context-properties: fill;
  fill: currentColor;
}

.card-heading {
  display: flex;
  font-weight: 600;
}

.shortcuts-empty-label {
  margin-top: 16px;
}

.shortcut-row {
  display: flex;
  align-items: center;
  margin-top: 10px;
}

.shortcut.card:not([expanded]) > .shortcut-row[hide-before-expand] {
  display: none;
}

.shortcut-label {
  flex-grow: 1;
}

.shortcut-remove-button {
  background-image: url("chrome://global/skin/icons/delete.svg");
  background-position: center;
  background-repeat: no-repeat;
  -moz-context-properties: fill;
  fill: currentColor;
  min-width: 32px;
}

.shortcut-input[shortcut=""] + .shortcut-remove-button {
  visibility: hidden;
}

.expand-row {
  display: flex;
  justify-content: center;
}

.expand-button {
  margin: 8px 0 0;
}

.expand-button[warning]:not(:focus) {
  outline: 2px solid var(--yellow-60);
  outline-offset: -1px;
  box-shadow: 0 0 0 4px var(--yellow-60-a30);
}

.shortcut-input {
  /* Shortcuts should always be left-to-right. */
  direction: ltr;
  text-align: match-parent;
}

.extension-heading {
  display: flex;
}

.error-message {
  --error-background: var(--red-60);
  --warning-background: var(--yellow-50);
  --warning-text-color: var(--yellow-90);
  color: white;
  display: flex;
  flex-direction: column;
  position: absolute;
  visibility: hidden;
}

.error-message-icon {
  margin-inline-start: 10px;
  width: 14px;
  height: 8px;
  fill: var(--error-background);
  stroke: var(--error-background);
  -moz-context-properties: fill, stroke;
}

.error-message[type="warning"] > .error-message-icon {
  fill: var(--warning-background);
  stroke: var(--warning-background);
}

.error-message-label {
  background-color: var(--error-background);
  border-radius: 2px;
  margin: 0;
  margin-inline-end: 8px;
  max-width: 300px;
  padding: 5px 10px;
  word-wrap: break-word;
}

.error-message[type="warning"] > .error-message-label {
  background-color: var(--warning-background);
  color: var(--warning-text-color);
}

.error-message-arrow {
  background-color: var(--error-background);
  content: "";
  max-height: 8px;
  width: 8px;
  transform: translate(4px, -6px) rotate(45deg);
  position: absolute;
}

/* The margin between message bars. */
message-bar-stack > * {
  margin-bottom: 8px;
}
PK
!<�	��E�E6chrome/toolkit/content/mozapps/extensions/shortcuts.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from aboutaddonsCommon.js */

"use strict";

const { AppConstants } = ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);

ChromeUtils.defineESModuleGetters(this, {
  ExtensionShortcutKeyMap: "resource://gre/modules/ExtensionShortcuts.sys.mjs",
  ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
});

{
  const FALLBACK_ICON = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
  const COLLAPSE_OPTIONS = {
    limit: 5, // We only want to show 5 when collapsed.
    allowOver: 1, // Avoid collapsing to hide 1 row.
  };

  let templatesLoaded = false;
  let shortcutKeyMap = new ExtensionShortcutKeyMap();
  const templates = {};

  function loadTemplates() {
    if (templatesLoaded) {
      return;
    }
    templatesLoaded = true;

    templates.view = document.getElementById("shortcut-view");
    templates.card = document.getElementById("shortcut-card-template");
    templates.row = document.getElementById("shortcut-row-template");
    templates.noAddons = document.getElementById("shortcuts-no-addons");
    templates.expandRow = document.getElementById("expand-row-template");
    templates.noShortcutAddons = document.getElementById(
      "shortcuts-no-commands-template"
    );
  }

  function extensionForAddonId(id) {
    let policy = WebExtensionPolicy.getByID(id);
    return policy && policy.extension;
  }

  let builtInNames = new Map([
    ["_execute_action", "shortcuts-browserAction2"],
    ["_execute_browser_action", "shortcuts-browserAction2"],
    ["_execute_page_action", "shortcuts-pageAction"],
    ["_execute_sidebar_action", "shortcuts-sidebarAction"],
  ]);
  let getCommandDescriptionId = command => {
    if (!command.description && builtInNames.has(command.name)) {
      return builtInNames.get(command.name);
    }
    return null;
  };

  const _functionKeys = [
    "F1",
    "F2",
    "F3",
    "F4",
    "F5",
    "F6",
    "F7",
    "F8",
    "F9",
    "F10",
    "F11",
    "F12",
  ];
  const functionKeys = new Set(_functionKeys);
  const validKeys = new Set([
    "Home",
    "End",
    "PageUp",
    "PageDown",
    "Insert",
    "Delete",
    "0",
    "1",
    "2",
    "3",
    "4",
    "5",
    "6",
    "7",
    "8",
    "9",
    ..._functionKeys,
    "MediaNextTrack",
    "MediaPlayPause",
    "MediaPrevTrack",
    "MediaStop",
    "A",
    "B",
    "C",
    "D",
    "E",
    "F",
    "G",
    "H",
    "I",
    "J",
    "K",
    "L",
    "M",
    "N",
    "O",
    "P",
    "Q",
    "R",
    "S",
    "T",
    "U",
    "V",
    "W",
    "X",
    "Y",
    "Z",
    "Up",
    "Down",
    "Left",
    "Right",
    "Comma",
    "Period",
    "Space",
  ]);

  /**
   * Trim a valid prefix from an event string.
   *
   *     "Digit3" ~> "3"
   *     "ArrowUp" ~> "Up"
   *     "W" ~> "W"
   *
   * @param {string} string The input string.
   * @returns {string} The trimmed string, or unchanged.
   */
  function trimPrefix(string) {
    return string.replace(/^(?:Digit|Numpad|Arrow)/, "");
  }

  const remapKeys = {
    ",": "Comma",
    ".": "Period",
    " ": "Space",
  };
  /**
   * Map special keys to their shortcut name.
   *
   *     "," ~> "Comma"
   *     " " ~> "Space"
   *
   * @param {string} string The input string.
   * @returns {string} The remapped string, or unchanged.
   */
  function remapKey(string) {
    if (remapKeys.hasOwnProperty(string)) {
      return remapKeys[string];
    }
    return string;
  }

  const keyOptions = [
    e => String.fromCharCode(e.which), // A letter?
    e => e.code.toUpperCase(), // A letter.
    e => trimPrefix(e.code), // Digit3, ArrowUp, Numpad9.
    e => trimPrefix(e.key), // Digit3, ArrowUp, Numpad9.
    e => remapKey(e.key), // Comma, Period, Space.
  ];
  /**
   * Map a DOM event to a shortcut string character.
   *
   * For example:
   *
   *    "a" ~> "A"
   *    "Digit3" ~> "3"
   *    "," ~> "Comma"
   *
   * @param {object} event A KeyboardEvent.
   * @returns {string} A string corresponding to the pressed key.
   */
  function getStringForEvent(event) {
    for (let option of keyOptions) {
      let value = option(event);
      if (validKeys.has(value)) {
        return value;
      }
    }

    return "";
  }

  function getShortcutValue(shortcut) {
    if (!shortcut) {
      // Ensure the shortcut is a string, even if it is unset.
      return null;
    }

    let modifiers = shortcut.split("+");
    let key = modifiers.pop();

    if (modifiers.length) {
      let modifiersAttribute = ShortcutUtils.getModifiersAttribute(modifiers);
      let displayString =
        ShortcutUtils.getModifierString(modifiersAttribute) + key;
      return displayString;
    }

    if (functionKeys.has(key)) {
      return key;
    }

    return null;
  }

  let error;

  function setError(...args) {
    setInputMessage("error", ...args);
  }

  function setWarning(...args) {
    setInputMessage("warning", ...args);
  }

  function setInputMessage(type, input, messageId, args) {
    let { x, y, height, right } = input.getBoundingClientRect();
    error.style.top = `${y + window.scrollY + height - 5}px`;

    if (document.dir == "ltr") {
      error.style.left = `${x}px`;
      error.style.right = null;
    } else {
      error.style.right = `${document.documentElement.clientWidth - right}px`;
      error.style.left = null;
    }

    error.setAttribute("type", type);
    document.l10n.setAttributes(
      error.querySelector(".error-message-label"),
      messageId,
      args
    );
    error.style.visibility = "visible";
  }

  function inputBlurred(e) {
    error.style.visibility = "hidden";
    e.target.value = getShortcutValue(e.target.getAttribute("shortcut"));
  }

  function onFocus(e) {
    e.target.value = "";

    let warning = e.target.getAttribute("warning");
    if (warning) {
      setWarning(e.target, warning);
    }
  }

  function getShortcutForEvent(e) {
    let modifierMap;

    if (AppConstants.platform == "macosx") {
      modifierMap = {
        MacCtrl: e.ctrlKey,
        Alt: e.altKey,
        Command: e.metaKey,
        Shift: e.shiftKey,
      };
    } else {
      modifierMap = {
        Ctrl: e.ctrlKey,
        Alt: e.altKey,
        Shift: e.shiftKey,
      };
    }

    return Object.entries(modifierMap)
      .filter(([, isDown]) => isDown)
      .map(([key]) => key)
      .concat(getStringForEvent(e))
      .join("+");
  }

  async function buildDuplicateShortcutsMap(addons) {
    await shortcutKeyMap.buildForAddonIds(addons.map(addon => addon.id));
  }

  function recordShortcut(shortcut, addonName, commandName) {
    shortcutKeyMap.recordShortcut(shortcut, addonName, commandName);
  }

  function removeShortcut(shortcut, addonName, commandName) {
    shortcutKeyMap.removeShortcut(shortcut, addonName, commandName);
  }

  function getAddonName(shortcut) {
    return shortcutKeyMap.getFirstAddonName(shortcut);
  }

  function setDuplicateWarnings() {
    let warningHolder = document.getElementById("duplicate-warning-messages");
    clearWarnings(warningHolder);
    for (let [shortcut, addons] of shortcutKeyMap) {
      if (addons.size > 1) {
        warningHolder.appendChild(createDuplicateWarningBar(shortcut));
        markDuplicates(shortcut);
      }
    }
  }

  function clearWarnings(warningHolder) {
    warningHolder.textContent = "";
    let inputs = document.querySelectorAll(".shortcut-input[warning]");
    for (let input of inputs) {
      input.removeAttribute("warning");
      let row = input.closest(".shortcut-row");
      if (row.hasAttribute("hide-before-expand")) {
        row
          .closest(".card")
          .querySelector(".expand-button")
          .removeAttribute("warning");
      }
    }
  }

  function createDuplicateWarningBar(shortcut) {
    let messagebar = document.createElement("moz-message-bar");
    messagebar.setAttribute("type", "warning");

    document.l10n.setAttributes(
      messagebar,
      "shortcuts-duplicate-warning-message2",
      { shortcut }
    );
    messagebar.setAttribute("data-l10n-attrs", "message");

    return messagebar;
  }

  function markDuplicates(shortcut) {
    let inputs = document.querySelectorAll(
      `.shortcut-input[shortcut="${shortcut}"]`
    );
    for (let input of inputs) {
      input.setAttribute("warning", "shortcuts-duplicate");
      let row = input.closest(".shortcut-row");
      if (row.hasAttribute("hide-before-expand")) {
        row
          .closest(".card")
          .querySelector(".expand-button")
          .setAttribute("warning", "shortcuts-duplicate");
      }
    }
  }

  function onShortcutChange(e) {
    let input = e.target;

    if (e.key == "Escape") {
      input.blur();
      return;
    }

    if (e.key == "Tab") {
      return;
    }

    if (!e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey) {
      if (e.key == "Delete" || e.key == "Backspace") {
        // Avoid triggering back-navigation.
        e.preventDefault();
        assignShortcutToInput(input, "");
        return;
      }
    }

    e.preventDefault();
    e.stopPropagation();

    // Some system actions aren't in the keyset, handle them independantly.
    if (ShortcutUtils.getSystemActionForEvent(e)) {
      e.defaultCancelled = true;
      setError(input, "shortcuts-system");
      return;
    }

    let shortcutString = getShortcutForEvent(e);
    input.value = getShortcutValue(shortcutString);

    if (e.type == "keyup" || !shortcutString.length) {
      return;
    }

    let validation = ShortcutUtils.validate(shortcutString);
    switch (validation) {
      case ShortcutUtils.IS_VALID:
        // Show an error if this is already a system shortcut.
        let chromeWindow = window.windowRoot.ownerGlobal;
        if (ShortcutUtils.isSystem(chromeWindow, shortcutString)) {
          setError(input, "shortcuts-system");
          break;
        }

        // Check if shortcut is already assigned.
        if (shortcutKeyMap.has(shortcutString)) {
          setError(input, "shortcuts-exists", {
            addon: getAddonName(shortcutString),
          });
        } else {
          // Update the shortcut if it isn't reserved or assigned.
          assignShortcutToInput(input, shortcutString);
        }
        break;
      case ShortcutUtils.MODIFIER_REQUIRED:
        if (AppConstants.platform == "macosx") {
          setError(input, "shortcuts-modifier-mac");
        } else {
          setError(input, "shortcuts-modifier-other");
        }
        break;
      case ShortcutUtils.INVALID_COMBINATION:
        setError(input, "shortcuts-invalid");
        break;
      case ShortcutUtils.INVALID_KEY:
        setError(input, "shortcuts-letter");
        break;
    }
  }

  function onShortcutRemove(e) {
    let removeButton = e.target;
    let input = removeButton.parentNode.querySelector(".shortcut-input");
    if (input.getAttribute("shortcut")) {
      input.value = "";
      assignShortcutToInput(input, "");
    }
  }

  function assignShortcutToInput(input, shortcutString) {
    let addonId = input.closest(".card").getAttribute("addon-id");
    let extension = extensionForAddonId(addonId);

    let oldShortcut = input.getAttribute("shortcut");
    let addonName = input.closest(".card").getAttribute("addon-name");
    let commandName = input.getAttribute("name");

    removeShortcut(oldShortcut, addonName, commandName);
    recordShortcut(shortcutString, addonName, commandName);

    // This is async, but we're not awaiting it to keep the handler sync.
    extension.shortcuts.updateCommand({
      name: commandName,
      shortcut: shortcutString,
    });
    input.setAttribute("shortcut", shortcutString);
    input.blur();
    setDuplicateWarnings();
  }

  function renderNoShortcutAddons(addons) {
    let fragment = document.importNode(
      templates.noShortcutAddons.content,
      true
    );
    let list = fragment.querySelector(".shortcuts-no-commands-list");
    for (let addon of addons) {
      let addonItem = document.createElement("li");
      addonItem.textContent = addon.name;
      addonItem.setAttribute("addon-id", addon.id);
      list.appendChild(addonItem);
    }

    return fragment;
  }

  async function renderAddons(addons) {
    let frag = document.createDocumentFragment();
    let noShortcutAddons = [];

    await buildDuplicateShortcutsMap(addons);

    let isDuplicate = command => {
      if (command.shortcut) {
        let dupes = shortcutKeyMap.get(command.shortcut);
        return dupes.size > 1;
      }
      return false;
    };

    for (let addon of addons) {
      let extension = extensionForAddonId(addon.id);

      // Skip this extension if it isn't a webextension.
      if (!extension) {
        continue;
      }

      if (extension.shortcuts) {
        let card = document.importNode(
          templates.card.content,
          true
        ).firstElementChild;
        let icon = AddonManager.getPreferredIconURL(addon, 24, window);
        card.setAttribute("addon-id", addon.id);
        card.setAttribute("addon-name", addon.name);
        card.querySelector(".addon-icon").src = icon || FALLBACK_ICON;
        card.querySelector(".addon-name").textContent = addon.name;

        let commands = await extension.shortcuts.allCommands();

        // Sort the commands so the ones with shortcuts are at the top.
        commands.sort((a, b) => {
          if (isDuplicate(a) && isDuplicate(b)) {
            return 0;
          }
          if (isDuplicate(a)) {
            return -1;
          }
          if (isDuplicate(b)) {
            return 1;
          }
          // Boolean compare the shortcuts to see if they're both set or unset.
          if (!a.shortcut == !b.shortcut) {
            return 0;
          }
          if (a.shortcut) {
            return -1;
          }
          return 1;
        });

        let { limit, allowOver } = COLLAPSE_OPTIONS;
        let willHideCommands = commands.length > limit + allowOver;
        let firstHiddenInput;

        for (let i = 0; i < commands.length; i++) {
          let command = commands[i];

          let row = document.importNode(
            templates.row.content,
            true
          ).firstElementChild;

          if (willHideCommands && i >= limit) {
            row.setAttribute("hide-before-expand", "true");
          }

          let label = row.querySelector(".shortcut-label");
          let descriptionId = getCommandDescriptionId(command);
          if (descriptionId) {
            document.l10n.setAttributes(label, descriptionId);
          } else {
            label.textContent = command.description || command.name;
          }
          let input = row.querySelector(".shortcut-input");
          input.value = getShortcutValue(command.shortcut);
          input.setAttribute("name", command.name);
          input.setAttribute("shortcut", command.shortcut);
          input.addEventListener("keydown", onShortcutChange);
          input.addEventListener("keyup", onShortcutChange);
          input.addEventListener("blur", inputBlurred);
          input.addEventListener("focus", onFocus);

          let removeButton = row.querySelector(".shortcut-remove-button");
          removeButton.addEventListener("click", onShortcutRemove);

          if (willHideCommands && i == limit) {
            firstHiddenInput = input;
          }

          card.appendChild(row);
        }

        // Add an expand button, if needed.
        if (willHideCommands) {
          let row = document.importNode(templates.expandRow.content, true);
          let button = row.querySelector(".expand-button");
          let numberToShow = commands.length - limit;
          let setLabel = type => {
            document.l10n.setAttributes(
              button,
              `shortcuts-card-${type}-button`,
              {
                numberToShow,
              }
            );
          };

          setLabel("expand");
          button.addEventListener("click", event => {
            let expanded = card.hasAttribute("expanded");
            if (expanded) {
              card.removeAttribute("expanded");
              setLabel("expand");
            } else {
              card.setAttribute("expanded", "true");
              setLabel("collapse");
              // If this as a keyboard event then focus the next input.
              if (event.inputSource == MouseEvent.MOZ_SOURCE_KEYBOARD) {
                firstHiddenInput.focus();
              }
            }
          });
          card.appendChild(row);
        }

        frag.appendChild(card);
      } else if (!addon.hidden) {
        noShortcutAddons.push({ id: addon.id, name: addon.name });
      }
    }

    if (noShortcutAddons.length) {
      frag.appendChild(renderNoShortcutAddons(noShortcutAddons));
    }

    return frag;
  }

  class AddonShortcuts extends HTMLElement {
    connectedCallback() {
      setDuplicateWarnings();
    }

    disconnectedCallback() {
      error = null;
    }

    async render() {
      loadTemplates();
      let allAddons = await AddonManager.getAddonsByTypes(["extension"]);
      let addons = allAddons
        .filter(addon => addon.isActive)
        .sort((a, b) => a.name.localeCompare(b.name));
      let frag;

      if (addons.length) {
        frag = await renderAddons(addons);
      } else {
        frag = document.importNode(templates.noAddons.content, true);
      }

      this.textContent = "";
      this.appendChild(document.importNode(templates.view.content, true));
      error = this.querySelector(".error-message");
      this.appendChild(frag);
    }
  }
  customElements.define("addon-shortcuts", AddonShortcuts);
}
PK
!<a.N��<chrome/toolkit/content/mozapps/extensions/view-controller.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

/* import-globals-from /toolkit/content/customElements.js */
/* import-globals-from aboutaddonsCommon.js */
/* exported loadView */

// Used by external callers to load a specific view into the manager
function loadView(viewId) {
  if (!gViewController.readyForLoadView) {
    throw new Error("loadView called before about:addons is initialized");
  }
  gViewController.loadView(viewId);
}

/**
 * Helper for saving and restoring the scroll offsets when a previously loaded
 * view is accessed again.
 */
var ScrollOffsets = {
  _key: null,
  _offsets: new Map(),
  canRestore: true,

  setView(historyEntryId) {
    this._key = historyEntryId;
    this.canRestore = true;
  },

  getPosition() {
    if (!this.canRestore) {
      return { top: 0, left: 0 };
    }
    let { scrollTop: top, scrollLeft: left } = document.documentElement;
    return { top, left };
  },

  save() {
    if (this._key) {
      this._offsets.set(this._key, this.getPosition());
    }
  },

  restore() {
    let { top = 0, left = 0 } = this._offsets.get(this._key) || {};
    window.scrollTo({ top, left, behavior: "auto" });
  },
};

var gViewController = {
  currentViewId: null,
  readyForLoadView: false,
  get defaultViewId() {
    if (!isDiscoverEnabled()) {
      return "addons://list/extension";
    }
    return "addons://discover/";
  },
  isLoading: true,
  // All historyEntryId values must be unique within one session, because the
  // IDs are used to map history entries to page state. It is not possible to
  // see whether a historyEntryId was used in history entries before this page
  // was loaded, so start counting from a random value to avoid collisions.
  // This is used for scroll offsets in aboutaddons.js
  nextHistoryEntryId: Math.floor(Math.random() * 2 ** 32),
  views: {},

  initialize(container) {
    this.container = container;

    window.addEventListener("popstate", this);
    window.addEventListener("unload", this, { once: true });
    Services.obs.addObserver(this, "EM-ping");
  },

  handleEvent(e) {
    if (e.type == "popstate") {
      this.renderState(e.state);
      return;
    }

    if (e.type == "unload") {
      Services.obs.removeObserver(this, "EM-ping");
      // eslint-disable-next-line no-useless-return
      return;
    }
  },

  observe(subject, topic) {
    if (topic == "EM-ping") {
      this.readyForLoadView = true;
      Services.obs.notifyObservers(window, "EM-pong");
    }
  },

  notifyEMLoaded() {
    this.readyForLoadView = true;
    Services.obs.notifyObservers(window, "EM-loaded");
  },

  notifyEMUpdateCheckFinished() {
    // Notify the observer about a completed update check (currently only used in tests).
    Services.obs.notifyObservers(null, "EM-update-check-finished");
  },

  defineView(viewName, renderFunction) {
    if (this.views[viewName]) {
      throw new Error(
        `about:addons view ${viewName} should not be defined twice`
      );
    }
    this.views[viewName] = renderFunction;
  },

  parseViewId(viewId) {
    const matchRegex = /^addons:\/\/([^\/]+)\/(.*)$/;
    const [, viewType, viewParam] = viewId.match(matchRegex) || [];
    return { type: viewType, param: decodeURIComponent(viewParam) };
  },

  loadView(viewId, replace = false) {
    viewId = viewId.startsWith("addons://") ? viewId : `addons://${viewId}`;
    if (viewId == this.currentViewId) {
      return Promise.resolve();
    }

    // Always rewrite history state instead of pushing incorrect state for initial load.
    replace = replace || !this.currentViewId;

    const state = {
      view: viewId,
      previousView: replace ? null : this.currentViewId,
      historyEntryId: ++this.nextHistoryEntryId,
    };
    if (replace) {
      history.replaceState(state, "");
    } else {
      history.pushState(state, "");
    }
    return this.renderState(state);
  },

  async renderState(state) {
    let { param, type } = this.parseViewId(state.view);

    if (!type || this.views[type] == null) {
      console.warn(`No view for ${type} ${param}, switching to default`);
      this.resetState();
      return;
    }

    ScrollOffsets.save();
    ScrollOffsets.setView(state.historyEntryId);

    this.currentViewId = state.view;
    this.isLoading = true;

    // Perform tasks before view load
    document.dispatchEvent(
      new CustomEvent("view-selected", {
        detail: { id: state.view, param, type },
      })
    );

    // Render the fragment
    this.container.setAttribute("current-view", type);
    let fragment = await this.views[type](param);

    // Clear and append the fragment
    if (fragment) {
      this.container.textContent = "";
      this.container.append(fragment);

      // Most content has been rendered at this point. The only exception are
      // recommendations in the discovery pane and extension/theme list, because
      // they rely on remote data. If loaded before, then these may be rendered
      // within one tick, so wait a full frame before restoring scroll offsets.
      await new Promise(resolve => {
        window.requestAnimationFrame(() => {
          window.requestAnimationFrame(async () => {
            // Ensure all our content is translated.
            if (document.hasPendingL10nMutations) {
              await new Promise(r => {
                document.addEventListener("L10nMutationsFinished", r, {
                  once: true,
                });
              });
            }
            ScrollOffsets.restore();
            resolve();
          });
        });
      });
    } else {
      // Reset to default view if no given content
      this.resetState();
      return;
    }

    this.isLoading = false;

    document.dispatchEvent(new CustomEvent("view-loaded"));
  },

  resetState() {
    return this.loadView(this.defaultViewId, true);
  },
};
PK
!<��0�.�.5chrome/toolkit/content/mozapps/handling/appChooser.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { PrivateBrowsingUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/PrivateBrowsingUtils.sys.mjs"
);
const { EnableDelayHelper } = ChromeUtils.importESModule(
  "resource://gre/modules/PromptUtils.sys.mjs"
);

class MozHandler extends window.MozElements.MozRichlistitem {
  static get markup() {
    return `
    <vbox pack="center">
      <html:img alt="" height="32" width="32" loading="lazy" />
    </vbox>
    <vbox flex="1">
      <label class="name"/>
      <label class="description"/>
    </vbox>
    `;
  }

  connectedCallback() {
    this.textContent = "";
    this.appendChild(this.constructor.fragment);
    this.initializeAttributeInheritance();
  }

  static get inheritedAttributes() {
    return {
      img: "src=image,disabled",
      ".name": "value=name,disabled",
      ".description": "value=description,disabled",
    };
  }

  get label() {
    return `${this.getAttribute("name")} ${this.getAttribute("description")}`;
  }
}

customElements.define("mozapps-handler", MozHandler, {
  extends: "richlistitem",
});

window.addEventListener("DOMContentLoaded", () => dialog.initialize(), {
  once: true,
});

let dialog = {
  /**
   * This function initializes the content of the dialog.
   */
  initialize() {
    let args = window.arguments[0].wrappedJSObject || window.arguments[0];
    let { handler, outArgs, usePrivateBrowsing, enableButtonDelay } = args;

    this._handlerInfo = handler.QueryInterface(Ci.nsIHandlerInfo);
    this._outArgs = outArgs;

    this.isPrivate =
      usePrivateBrowsing ||
      (window.opener && PrivateBrowsingUtils.isWindowPrivate(window.opener));

    this._dialog = document.querySelector("dialog");
    this._itemChoose = document.getElementById("item-choose");
    this._rememberCheck = document.getElementById("remember");

    // Register event listener for the checkbox hint.
    this._rememberCheck.addEventListener("change", () => this.onCheck());

    document.addEventListener("dialogaccept", () => {
      this.onAccept();
    });

    // UI is ready, lets populate our list
    this.populateList();

    this.initL10n();

    if (enableButtonDelay) {
      this._delayHelper = new EnableDelayHelper({
        disableDialog: () => {
          this._acceptBtnDisabled = true;
          this.updateAcceptButton();
        },
        enableDialog: () => {
          this._acceptBtnDisabled = false;
          this.updateAcceptButton();
        },
        focusTarget: window,
      });
    }
  },

  initL10n() {
    let rememberLabel = document.getElementById("remember-label");
    document.l10n.setAttributes(rememberLabel, "chooser-dialog-remember", {
      scheme: this._handlerInfo.type,
    });

    let description = document.getElementById("description");
    document.l10n.setAttributes(description, "chooser-dialog-description", {
      scheme: this._handlerInfo.type,
    });
  },

  /**
   * Populates the list that a user can choose from.
   */
  populateList: function populateList() {
    var items = document.getElementById("items");
    var possibleHandlers = this._handlerInfo.possibleApplicationHandlers;
    var preferredHandler = this._handlerInfo.preferredApplicationHandler;
    for (let i = possibleHandlers.length - 1; i >= 0; --i) {
      let app = possibleHandlers.queryElementAt(i, Ci.nsIHandlerApp);
      let elm = document.createXULElement("richlistitem", {
        is: "mozapps-handler",
      });
      elm.setAttribute("name", app.name);
      elm.obj = app;

      // We defer loading the favicon so it doesn't delay load. The dialog is
      // opened in a SubDialog which will only show on window load.
      if (app instanceof Ci.nsILocalHandlerApp) {
        // See if we have an nsILocalHandlerApp and set the icon
        let uri = Services.io.newFileURI(app.executable);
        elm.setAttribute("image", "moz-icon://" + uri.spec + "?size=32");
      } else if (app instanceof Ci.nsIWebHandlerApp) {
        let uri = Services.io.newURI(app.uriTemplate);
        if (/^https?$/.test(uri.scheme)) {
          // Unfortunately we can't use the favicon service to get the favicon,
          // because the service looks for a record with the exact URL we give
          // it, and users won't have such records for URLs they don't visit,
          // and users won't visit the handler's URL template, they'll only
          // visit URLs derived from that template (i.e. with %s in the template
          // replaced by the URL of the content being handled).
          elm.setAttribute("image", uri.prePath + "/favicon.ico");
        }
        elm.setAttribute("description", uri.prePath);

        // Check for extensions needing private browsing access before
        // creating UI elements.
        if (this.isPrivate) {
          let policy = WebExtensionPolicy.getByURI(uri);
          if (policy && !policy.privateBrowsingAllowed) {
            elm.setAttribute("disabled", true);
            this.getPrivateBrowsingDisabledLabel().then(label => {
              elm.setAttribute("description", label);
            });
            if (app == preferredHandler) {
              preferredHandler = null;
            }
          }
        }
      } else if (app instanceof Ci.nsIDBusHandlerApp) {
        elm.setAttribute("description", app.method);
      } else if (!(app instanceof Ci.nsIGIOMimeApp)) {
        // We support GIO application handler, but no action required there
        throw new Error("unknown handler type");
      }

      items.insertBefore(elm, this._itemChoose);
      if (preferredHandler && app == preferredHandler) {
        this.selectedItem = elm;
      }
    }

    if (this._handlerInfo.hasDefaultHandler) {
      let elm = document.createXULElement("richlistitem", {
        is: "mozapps-handler",
      });
      elm.id = "os-default-handler";
      elm.setAttribute("name", this._handlerInfo.defaultDescription);

      items.insertBefore(elm, items.firstChild);
      if (
        this._handlerInfo.preferredAction == Ci.nsIHandlerInfo.useSystemDefault
      ) {
        this.selectedItem = elm;
      }
    }

    // Add gio handlers
    if (Cc["@mozilla.org/gio-service;1"]) {
      let gIOSvc = Cc["@mozilla.org/gio-service;1"].getService(
        Ci.nsIGIOService
      );
      var gioApps = gIOSvc.getAppsForURIScheme(this._handlerInfo.type);
      for (let handler of gioApps.enumerate(Ci.nsIHandlerApp)) {
        // OS handler share the same name, it's most likely the same app, skipping...
        if (handler.name == this._handlerInfo.defaultDescription) {
          continue;
        }
        // Check if the handler is already in possibleHandlers
        let appAlreadyInHandlers = false;
        for (let i = possibleHandlers.length - 1; i >= 0; --i) {
          let app = possibleHandlers.queryElementAt(i, Ci.nsIHandlerApp);
          // nsGIOMimeApp::Equals is able to compare with nsILocalHandlerApp
          if (handler.equals(app)) {
            appAlreadyInHandlers = true;
            break;
          }
        }
        if (!appAlreadyInHandlers) {
          let elm = document.createXULElement("richlistitem", {
            is: "mozapps-handler",
          });
          elm.setAttribute("name", handler.name);
          elm.obj = handler;
          items.insertBefore(elm, this._itemChoose);
        }
      }
    }

    items.ensureSelectedElementIsVisible();
  },

  /**
   * Brings up a filepicker and allows a user to choose an application.
   */
  async chooseApplication() {
    let title = await this.getChooseAppWindowTitle();

    var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
    fp.init(window.browsingContext, title, Ci.nsIFilePicker.modeOpen);
    fp.appendFilters(Ci.nsIFilePicker.filterApps);

    fp.open(rv => {
      if (rv == Ci.nsIFilePicker.returnOK && fp.file) {
        let uri = Services.io.newFileURI(fp.file);

        let handlerApp = Cc[
          "@mozilla.org/uriloader/local-handler-app;1"
        ].createInstance(Ci.nsILocalHandlerApp);
        handlerApp.executable = fp.file;

        // if this application is already in the list, select it and don't add it again
        let parent = document.getElementById("items");
        for (let i = 0; i < parent.childNodes.length; ++i) {
          let elm = parent.childNodes[i];
          if (
            elm.obj instanceof Ci.nsILocalHandlerApp &&
            elm.obj.equals(handlerApp)
          ) {
            parent.selectedItem = elm;
            parent.ensureSelectedElementIsVisible();
            return;
          }
        }

        let elm = document.createXULElement("richlistitem", {
          is: "mozapps-handler",
        });
        elm.setAttribute("name", fp.file.leafName);
        elm.setAttribute("image", "moz-icon://" + uri.spec + "?size=32");
        elm.obj = handlerApp;

        parent.selectedItem = parent.insertBefore(elm, parent.firstChild);
        parent.ensureSelectedElementIsVisible();
      }
    });
  },

  /**
   * Function called when the OK button is pressed.
   */
  onAccept() {
    this.updateHandlerData(this._rememberCheck.checked);
    this._outArgs.setProperty("openHandler", true);
  },

  /**
   * Determines if the accept button should be disabled or not
   */
  updateAcceptButton() {
    this._dialog.setAttribute(
      "buttondisabledaccept",
      this._acceptBtnDisabled || this._itemChoose.selected
    );
  },

  /**
   * Update the handler info to reflect the user choice.
   * @param {boolean} skipAsk - Whether we should persist the application
   * choice and skip asking next time.
   */
  updateHandlerData(skipAsk) {
    // We need to make sure that the default is properly set now
    if (this.selectedItem.obj) {
      // default OS handler doesn't have this property
      this._outArgs.setProperty(
        "preferredAction",
        Ci.nsIHandlerInfo.useHelperApp
      );
      this._outArgs.setProperty(
        "preferredApplicationHandler",
        this.selectedItem.obj
      );
    } else {
      this._outArgs.setProperty(
        "preferredAction",
        Ci.nsIHandlerInfo.useSystemDefault
      );
    }
    this._outArgs.setProperty("alwaysAskBeforeHandling", !skipAsk);
  },

  /**
   * Updates the UI based on the checkbox being checked or not.
   */
  onCheck() {
    if (document.getElementById("remember").checked) {
      document.getElementById("remember-text").setAttribute("visible", "true");
    } else {
      document.getElementById("remember-text").removeAttribute("visible");
    }
  },

  /**
   * Function called when the user double clicks on an item of the list
   */
  onDblClick: function onDblClick() {
    if (this.selectedItem == this._itemChoose) {
      this.chooseApplication();
    } else {
      this._dialog.acceptDialog();
    }
  },

  // Getters / Setters

  /**
   * Returns/sets the selected element in the richlistbox
   */
  get selectedItem() {
    return document.getElementById("items").selectedItem;
  },
  set selectedItem(aItem) {
    document.getElementById("items").selectedItem = aItem;
  },

  /**
   *  Lazy l10n getter for the title of the app chooser window
   */
  async getChooseAppWindowTitle() {
    if (!this._chooseAppWindowTitle) {
      this._chooseAppWindowTitle = await document.l10n.formatValues([
        "choose-other-app-window-title",
      ]);
    }
    return this._chooseAppWindowTitle;
  },

  /**
   * Lazy l10n getter for handler menu items which are disabled due to private
   * browsing.
   */
  async getPrivateBrowsingDisabledLabel() {
    if (!this._privateBrowsingDisabledLabel) {
      this._privateBrowsingDisabledLabel = await document.l10n.formatValues([
        "choose-dialog-privatebrowsing-disabled",
      ]);
    }
    return this._privateBrowsingDisabledLabel;
  },
};
PK
!<W޲E��8chrome/toolkit/content/mozapps/handling/appChooser.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window>

<window
  persist="width height screenX screenY"
  aria-describedby="description-text"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
  data-l10n-id="chooser-window"
  data-l10n-attrs="style"
>
  <dialog
    id="handling"
    buttons="accept,cancel"
    defaultButton="none"
    data-l10n-id="chooser-dialog"
    data-l10n-attrs="buttonlabelaccept, buttonaccesskeyaccept"
  >
    <linkset>
      <html:link rel="stylesheet" href="chrome://global/skin/global.css" />
      <html:link
        rel="stylesheet"
        href="chrome://mozapps/content/handling/handler.css"
      />
      <html:link
        rel="stylesheet"
        href="chrome://mozapps/skin/handling/handling.css"
      />

      <html:link rel="localization" href="branding/brand.ftl" />
      <html:link rel="localization" href="toolkit/global/handlerDialog.ftl" />
    </linkset>

    <script
      src="chrome://mozapps/content/handling/appChooser.js"
      type="application/javascript"
    />

    <description id="description" />

    <vbox id="chooser" flex="1">
      <richlistbox
        id="items"
        flex="1"
        ondblclick="dialog.onDblClick();"
        onselect="dialog.updateAcceptButton();"
      >
        <richlistitem id="item-choose" orient="horizontal" selected="true">
          <label data-l10n-id="choose-other-app-description" flex="1" />
          <button
            oncommand="dialog.chooseApplication();"
            data-l10n-id="choose-app-btn"
          />
        </richlistitem>
      </richlistbox>
    </vbox>

    <vbox id="rememberContainer">
      <html:label class="toggle-container-with-text">
        <html:input type="checkbox" id="remember" />
        <html:span id="remember-label" />
      </html:label>
      <description
        id="remember-text"
        data-l10n-id="chooser-dialog-remember-extra"
      />
    </vbox>
  </dialog>
</window>
PK
!<�Y3chrome/toolkit/content/mozapps/handling/handler.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace html "http://www.w3.org/1999/xhtml";

#description {
  font-weight: bold;
}

#remember-text:not([visible]) {
  visibility: hidden;
}

dialog {
  padding: 16px calc(16px - 4px);
}

#items,
label, description {
  margin: 0;
}

#items label {
  margin-inline: 4px;
}

#description,
#description-box,
#rememberContainer,
#chooser {
  margin: 0 4px 16px;
}

#chooser img:is(:-moz-broken, :not([src])) {
  visibility: hidden;
}

/* avoid double inline margins when #description is nested: */
#description-box > #description {
  margin-inline: 0;
}

/* Parent selector to win on specificity against common.css */
#rememberContainer > .toggle-container-with-text {
  align-items: baseline;
  color: var(--text-color-deemphasized);
}

.toggle-container-with-text > html|input[type="checkbox"] {
  margin-inline-end: 8px;
  /* Ensure the checkbox is properly aligned with the text: */
  translate: 0 calc(1px + max(60% - .6em, 0px));
}

#rememberContainer:not([hidden]) {
  /* Ensure we don't get sized to the smallest child when the checkbox text wraps. */
  display: block;
}
PK
!<�,���;chrome/toolkit/content/mozapps/handling/permissionDialog.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { EnableDelayHelper } = ChromeUtils.importESModule(
  "resource://gre/modules/PromptUtils.sys.mjs"
);

let dialog = {
  /**
   * This function initializes the content of the dialog.
   */
  initialize() {
    let args = window.arguments[0].wrappedJSObject || window.arguments[0];
    let {
      handler,
      principal,
      outArgs,
      canPersistPermission,
      preferredHandlerName,
      browsingContext,
    } = args;

    this._handlerInfo = handler.QueryInterface(Ci.nsIHandlerInfo);
    this._principal = principal?.QueryInterface(Ci.nsIPrincipal);
    this._addonPolicy =
      this._principal?.addonPolicy ?? this._principal?.contentScriptAddonPolicy;
    this._browsingContext = browsingContext;
    this._outArgs = outArgs.QueryInterface(Ci.nsIWritablePropertyBag);
    this._preferredHandlerName = preferredHandlerName;

    this._dialog = document.querySelector("dialog");
    this._checkRemember = document.getElementById("remember");
    this._checkRememberContainer = document.getElementById("rememberContainer");

    if (!canPersistPermission) {
      this._checkRememberContainer.hidden = true;
    }

    let changeAppLink = document.getElementById("change-app");

    // allow the user to choose another application if they wish,
    // but don't offer this if the protocol was opened via
    // system principal (URLbar) and there's a preferred handler
    if (this._preferredHandlerName && !this._principal?.isSystemPrincipal) {
      changeAppLink.hidden = false;

      changeAppLink.addEventListener("click", () => this.onChangeApp());
    }
    document.addEventListener("dialogaccept", () => this.onAccept());
    this.initL10n();

    this._delayHelper = new EnableDelayHelper({
      disableDialog: () => {
        this._dialog.setAttribute("buttondisabledaccept", true);
      },
      enableDialog: () => {
        this._dialog.setAttribute("buttondisabledaccept", false);
      },
      focusTarget: window,
    });
  },

  /**
   * Checks whether the principal that triggered this dialog is top level
   * (not embedded in a frame).
   * @returns {boolean} - true if principal is top level, false otherwise.
   * If the triggering principal is null this method always returns false.
   */
  triggeringPrincipalIsTop() {
    if (!this._principal) {
      return false;
    }

    let topContentPrincipal =
      this._browsingContext?.top.embedderElement?.contentPrincipal;
    if (!topContentPrincipal) {
      return false;
    }
    return this._principal.equals(topContentPrincipal);
  },

  /**
   * Determines the l10n ID to use for the dialog description, depending on
   * the triggering principal and the preferred application handler.
   */
  get l10nDescriptionId() {
    if (this._addonPolicy) {
      if (this._preferredHandlerName) {
        return "permission-dialog-description-extension-app";
      }
      return "permission-dialog-description-extension";
    }

    if (this._principal?.schemeIs("file")) {
      if (this._preferredHandlerName) {
        return "permission-dialog-description-file-app";
      }
      return "permission-dialog-description-file";
    }

    if (this._principal?.isSystemPrincipal && this._preferredHandlerName) {
      return "permission-dialog-description-system-app";
    }

    if (this._principal?.isSystemPrincipal && !this._preferredHandlerName) {
      return "permission-dialog-description-system-noapp";
    }

    // We only show the website address if the request didn't come from the top
    // level frame. If we can't get a host to display, fall back to the copy
    // without host.
    if (!this.triggeringPrincipalIsTop() && this.displayPrePath) {
      if (this._preferredHandlerName) {
        return "permission-dialog-description-host-app";
      }
      return "permission-dialog-description-host";
    }

    if (this._preferredHandlerName) {
      return "permission-dialog-description-app";
    }

    return "permission-dialog-description";
  },

  /**
   * Determines the l10n ID to use for the "remember permission" checkbox,
   * depending on the triggering principal and the preferred application
   * handler.
   */
  get l10nCheckboxId() {
    if (!this._principal) {
      return null;
    }

    if (this._addonPolicy) {
      return "permission-dialog-remember-extension";
    }
    if (this._principal.schemeIs("file")) {
      return "permission-dialog-remember-file";
    }
    return "permission-dialog-remember";
  },

  /**
   * Computes the prePath to show in the prompt. It's the prePath of the site
   * that wants to navigate to the external protocol.
   * @returns {string|null} - prePath to show, or null if we can't derive an
   * exposable prePath from the triggering principal.
   */
  get displayPrePath() {
    if (!this._principal) {
      return null;
    }

    // NullPrincipals don't expose a meaningful prePath. Instead use the
    // precursorPrincipal, which the NullPrincipal was derived from.
    if (this._principal.isNullPrincipal) {
      return this._principal.precursorPrincipal?.exposablePrePath;
    }

    return this._principal?.exposablePrePath;
  },

  initL10n() {
    // The UI labels depend on whether we will show the application chooser next
    // or directly open the assigned protocol handler.

    // Fluent id for dialog accept button
    let idAcceptButton;
    let acceptButton = this._dialog.getButton("accept");

    if (this._preferredHandlerName) {
      idAcceptButton = "permission-dialog-btn-open-link";
    } else {
      idAcceptButton = "permission-dialog-btn-choose-app";

      let descriptionExtra = document.getElementById("description-extra");
      descriptionExtra.hidden = false;
      acceptButton.addEventListener("click", () => this.onChangeApp());
    }
    document.l10n.setAttributes(acceptButton, idAcceptButton);

    let description = document.getElementById("description");

    let host = this.displayPrePath;
    let scheme = this._handlerInfo.type;

    document.l10n.setAttributes(description, this.l10nDescriptionId, {
      host,
      scheme,
      extension: this._addonPolicy?.name,
      appName: this._preferredHandlerName,
    });

    if (!this._checkRememberContainer.hidden) {
      let checkboxLabel = document.getElementById("remember-label");
      document.l10n.setAttributes(checkboxLabel, this.l10nCheckboxId, {
        host,
        scheme,
      });
    }
  },

  onAccept() {
    this._outArgs.setProperty("remember", this._checkRemember.checked);
    this._outArgs.setProperty("granted", true);
  },

  onChangeApp() {
    this._outArgs.setProperty("resetHandlerChoice", true);

    // We can't call the dialogs accept handler here. If the accept button is
    // still disabled, it will prevent closing.
    this.onAccept();
    window.close();
  },
};

window.addEventListener("DOMContentLoaded", () => dialog.initialize(), {
  once: true,
});
PK
!<9|�OO>chrome/toolkit/content/mozapps/handling/permissionDialog.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window>

<window
  aria-describedby="description"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
>
  <dialog
    buttons="accept,cancel"
    defaultButton="none"
    data-l10n-attrs="buttonlabelaccept, buttonaccesskeyaccept"
  >
    <linkset>
      <html:link rel="stylesheet" href="chrome://global/skin/global.css" />
      <html:link
        rel="stylesheet"
        href="chrome://mozapps/content/handling/handler.css"
      />

      <html:link rel="localization" href="toolkit/global/handlerDialog.ftl" />
    </linkset>

    <script
      src="chrome://mozapps/content/handling/permissionDialog.js"
      type="application/javascript"
    />

    <vbox id="description-box">
      <description id="description"></description>
      <label
        id="change-app"
        hidden="true"
        is="text-link"
        data-l10n-id="permission-dialog-set-change-app-link"
      ></label>
      <description
        id="description-extra"
        hidden="true"
        data-l10n-id="permission-dialog-unset-description"
      >
      </description>
    </vbox>

    <vbox id="rememberContainer">
      <html:label class="toggle-container-with-text">
        <html:input type="checkbox" id="remember" />
        <html:span id="remember-label" />
      </html:label>
    </vbox>
  </dialog>
</window>
PK
!<jp��6chrome/toolkit/content/mozapps/preferences/changemp.js// -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*-

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const nsPK11TokenDB = "@mozilla.org/security/pk11tokendb;1";
const nsIPK11TokenDB = Ci.nsIPK11TokenDB;
const nsIDialogParamBlock = Ci.nsIDialogParamBlock;
const nsPKCS11ModuleDB = "@mozilla.org/security/pkcs11moduledb;1";
const nsIPKCS11ModuleDB = Ci.nsIPKCS11ModuleDB;
const nsIPKCS11Slot = Ci.nsIPKCS11Slot;
const nsIPK11Token = Ci.nsIPK11Token;

var params;
var pw1;

function init() {
  pw1 = document.getElementById("pw1");

  process();
  document.addEventListener("dialogaccept", setPassword);
}

function process() {
  // If the token is unitialized, don't use the old password box.
  // Otherwise, do.

  let tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"].getService(
    Ci.nsIPK11TokenDB
  );
  let token = tokenDB.getInternalKeyToken();
  if (token) {
    let oldpwbox = document.getElementById("oldpw");
    let msgBox = document.getElementById("message");
    if ((token.needsLogin() && token.needsUserInit) || !token.needsLogin()) {
      oldpwbox.hidden = true;
      msgBox.hidden = false;

      if (!token.needsLogin()) {
        oldpwbox.setAttribute("inited", "empty");
      } else {
        oldpwbox.setAttribute("inited", "true");
      }

      // Select first password field
      document.getElementById("pw1").focus();
    } else {
      // Select old password field
      oldpwbox.hidden = false;
      msgBox.hidden = true;
      oldpwbox.setAttribute("inited", "false");
      oldpwbox.focus();
    }
  }

  if (
    !token.hasPassword &&
    !Services.policies.isAllowed("removeMasterPassword")
  ) {
    document.getElementById("admin").hidden = false;
  }

  if (params) {
    // Return value 0 means "canceled"
    params.SetInt(1, 0);
  }

  checkPasswords();
}

async function createAlert(titleL10nId, messageL10nId) {
  const [title, message] = await document.l10n.formatValues([
    { id: titleL10nId },
    { id: messageL10nId },
  ]);
  Services.prompt.alert(window, title, message);
}

function setPassword() {
  var pk11db = Cc[nsPK11TokenDB].getService(nsIPK11TokenDB);
  var token = pk11db.getInternalKeyToken();

  var oldpwbox = document.getElementById("oldpw");
  var initpw = oldpwbox.getAttribute("inited");

  if (initpw == "false" || initpw == "empty") {
    try {
      var oldpw = "";
      var passok = 0;

      if (initpw == "empty") {
        passok = 1;
      } else {
        oldpw = oldpwbox.value;
        passok = token.checkPassword(oldpw);
      }

      if (passok) {
        if (initpw == "empty" && pw1.value == "") {
          // This makes no sense that we arrive here,
          // we reached a case that should have been prevented by checkPasswords.
        } else {
          if (pw1.value == "") {
            var secmoddb = Cc[nsPKCS11ModuleDB].getService(nsIPKCS11ModuleDB);
            if (secmoddb.isFIPSEnabled) {
              // empty passwords are not allowed in FIPS mode
              createAlert(
                "pw-change-failed-title",
                "pp-change2empty-in-fips-mode"
              );
              passok = 0;
            }
          }
          if (passok) {
            token.changePassword(oldpw, pw1.value);
            if (pw1.value == "") {
              createAlert("pw-change-success-title", "settings-pp-erased-ok");
            } else {
              createAlert("pw-change-success-title", "pp-change-ok");
            }
          }
        }
      } else {
        oldpwbox.focus();
        oldpwbox.setAttribute("value", "");
        createAlert("pw-change-failed-title", "incorrect-pp");
      }
    } catch (e) {
      console.error(e);
      createAlert("pw-change-failed-title", "failed-pp-change");
    }
  } else {
    token.initPassword(pw1.value);
    if (pw1.value == "") {
      createAlert("pw-change-success-title", "settings-pp-not-wanted");
    }
  }
}

function setPasswordStrength() {
  // Here is how we weigh the quality of the password
  // number of characters
  // numbers
  // non-alpha-numeric chars
  // upper and lower case characters

  var pw = document.getElementById("pw1").value;

  // length of the password
  var pwlength = pw.length;
  if (pwlength > 5) {
    pwlength = 5;
  }

  // use of numbers in the password
  var numnumeric = pw.replace(/[0-9]/g, "");
  var numeric = pw.length - numnumeric.length;
  if (numeric > 3) {
    numeric = 3;
  }

  // use of symbols in the password
  var symbols = pw.replace(/\W/g, "");
  var numsymbols = pw.length - symbols.length;
  if (numsymbols > 3) {
    numsymbols = 3;
  }

  // use of uppercase in the password
  var numupper = pw.replace(/[A-Z]/g, "");
  var upper = pw.length - numupper.length;
  if (upper > 3) {
    upper = 3;
  }

  var pwstrength =
    pwlength * 10 - 20 + numeric * 10 + numsymbols * 15 + upper * 10;

  // make sure we're give a value between 0 and 100
  if (pwstrength < 0) {
    pwstrength = 0;
  }

  if (pwstrength > 100) {
    pwstrength = 100;
  }

  var mymeter = document.getElementById("pwmeter");
  mymeter.value = pwstrength;
}

function checkPasswords() {
  var pw1 = document.getElementById("pw1").value;
  var pw2 = document.getElementById("pw2").value;
  var ok = document.getElementById("changemp").getButton("accept");

  var oldpwbox = document.getElementById("oldpw");
  if (oldpwbox) {
    var initpw = oldpwbox.getAttribute("inited");

    if (initpw == "empty" && pw1 == "") {
      // The token has already been initialized, therefore this dialog
      // was called with the intention to change the password.
      // The token currently uses an empty password.
      // We will not allow changing the password from empty to empty.
      ok.setAttribute("disabled", "true");
      return;
    }
  }

  if (
    pw1 == pw2 &&
    (pw1 != "" || Services.policies.isAllowed("removeMasterPassword"))
  ) {
    ok.setAttribute("disabled", "false");
  } else {
    ok.setAttribute("disabled", "true");
  }
}
PK
!<����9chrome/toolkit/content/mozapps/preferences/changemp.xhtml<?xml version="1.0"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<window
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
  style="min-width: 40em"
  onload="init()"
  data-l10n-id="primary-password-dialog"
>
  <dialog id="changemp">
    <script src="chrome://mozapps/content/preferences/changemp.js" />

    <linkset>
      <html:link rel="stylesheet" href="chrome://global/skin/global.css" />

      <html:link rel="localization" href="branding/brand.ftl" />
      <html:link
        rel="localization"
        href="toolkit/preferences/preferences.ftl"
      />
    </linkset>

    <description
      id="admin"
      class="header"
      data-l10n-id="primary-password-required-by-policy"
      hidden="true"
    ></description>
    <description
      control="pw1"
      data-l10n-id="primary-password-description"
    ></description>

    <vbox>
      <hbox>
        <label
          flex="1"
          control="oldpw"
          data-l10n-id="set-password-old-password"
        ></label>
        <html:input id="oldpw" type="password" />
        <!-- This textbox is inserted as a workaround to the fact that making the 'type'
            & 'disabled' property of the 'oldpw' textbox toggle between ['password' &
            'false'] and ['text' & 'true'] - as would be necessary if the menu has more
            than one tokens, some initialized and some not - does not work properly. So,
            either the textbox 'oldpw' or the textbox 'message' would be displayed,
            depending on the state of the token selected
      -->
        <html:input
          type="text"
          data-l10n-attrs="value"
          data-l10n-id="password-not-set"
          id="message"
          disabled="true"
        />
      </hbox>
      <hbox>
        <label
          flex="1"
          control="pw1"
          data-l10n-id="set-password-new-password"
        ></label>
        <html:input
          id="pw1"
          type="password"
          oninput="setPasswordStrength(); checkPasswords();"
        />
      </hbox>
      <hbox>
        <label
          flex="1"
          control="pw2"
          data-l10n-id="set-password-reenter-password"
        ></label>
        <html:input id="pw2" type="password" oninput="checkPasswords();" />
      </hbox>
    </vbox>

    <html:label
      for="pwmeter"
      style="display: flex"
      data-l10n-id="set-password-meter"
    ></html:label>
    <html:progress id="pwmeter" value="0" max="100" />

    <description
      control="pw2"
      class="header"
      data-l10n-id="primary-password-warning"
    ></description>
  </dialog>
</window>
PK
!<�؎9%%9chrome/toolkit/content/mozapps/preferences/fontbuilder.js// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* import-globals-from ../../content/preferencesBindings.js */

var FontBuilder = {
  _enumerator: null,
  get enumerator() {
    if (!this._enumerator) {
      this._enumerator = Cc["@mozilla.org/gfx/fontenumerator;1"].createInstance(
        Ci.nsIFontEnumerator
      );
    }
    return this._enumerator;
  },

  _allFonts: null,
  _langGroupSupported: false,
  async buildFontList(aLanguage, aFontType, aMenuList) {
    // Remove the original <menupopup>
    if (aMenuList.menupopup) {
      aMenuList.menupopup.remove();
    }

    let defaultFont = null;
    // Load Font Lists
    let fonts = await this.enumerator.EnumerateFontsAsync(aLanguage, aFontType);
    if (fonts.length) {
      defaultFont = this.enumerator.getDefaultFont(aLanguage, aFontType);
    } else {
      fonts = await this.enumerator.EnumerateFontsAsync(aLanguage, "");
      if (fonts.length) {
        defaultFont = this.enumerator.getDefaultFont(aLanguage, "");
      }
    }

    if (!this._allFonts) {
      this._allFonts = await this.enumerator.EnumerateAllFontsAsync({});
    }

    // Build the UI for the Default Font and Fonts for this CSS type.
    const popup = document.createXULElement("menupopup");
    let separator;
    if (fonts.length) {
      let menuitem = document.createXULElement("menuitem");
      if (defaultFont) {
        document.l10n.setAttributes(menuitem, "fonts-label-default", {
          name: defaultFont,
        });
      } else {
        document.l10n.setAttributes(menuitem, "fonts-label-default-unnamed");
      }
      menuitem.setAttribute("value", ""); // Default Font has a blank value
      popup.appendChild(menuitem);

      separator = document.createXULElement("menuseparator");
      popup.appendChild(separator);

      for (let font of fonts) {
        menuitem = document.createXULElement("menuitem");
        menuitem.setAttribute("value", font);
        menuitem.setAttribute("label", font);
        popup.appendChild(menuitem);
      }
    }

    // Build the UI for the remaining fonts.
    if (this._allFonts.length > fonts.length) {
      this._langGroupSupported = true;
      // Both lists are sorted, and the Fonts-By-Type list is a subset of the
      // All-Fonts list, so walk both lists side-by-side, skipping values we've
      // already created menu items for.
      let builtItem = separator ? separator.nextSibling : popup.firstChild;
      let builtItemValue = builtItem ? builtItem.getAttribute("value") : null;

      separator = document.createXULElement("menuseparator");
      popup.appendChild(separator);

      for (let font of this._allFonts) {
        if (font != builtItemValue) {
          const menuitem = document.createXULElement("menuitem");
          menuitem.setAttribute("value", font);
          menuitem.setAttribute("label", font);
          popup.appendChild(menuitem);
        } else {
          builtItem = builtItem.nextSibling;
          builtItemValue = builtItem ? builtItem.getAttribute("value") : null;
        }
      }
    }
    aMenuList.appendChild(popup);
  },

  readFontSelection(aElement) {
    // Determine the appropriate value to select, for the following cases:
    // - there is no setting
    // - the font selected by the user is no longer present (e.g. deleted from
    //   fonts folder)
    const preference = Preferences.get(aElement.getAttribute("preference"));
    if (preference.value) {
      const fontItems = aElement.getElementsByAttribute(
        "value",
        preference.value
      );

      // There is a setting that actually is in the list. Respect it.
      if (fontItems.length) {
        return undefined;
      }
    }

    // Otherwise, use "default" font of current system which is computed
    // with "font.name-list.*".  If "font.name.*" is empty string, it means
    // "default".  So, return empty string in this case.
    return "";
  },
};
PK
!<�)Z�6chrome/toolkit/content/mozapps/preferences/removemp.js// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

var gRemovePasswordDialog = {
  _token: null,
  _okButton: null,
  _password: null,
  init() {
    this._okButton = document.getElementById("removemp").getButton("accept");
    document.l10n.setAttributes(this._okButton, "pw-remove-button");

    this._password = document.getElementById("password");

    var pk11db = Cc["@mozilla.org/security/pk11tokendb;1"].getService(
      Ci.nsIPK11TokenDB
    );
    this._token = pk11db.getInternalKeyToken();

    // Initialize the enabled state of the Remove button by checking the
    // initial value of the password ("" should be incorrect).
    this.validateInput();
    document.addEventListener("dialogaccept", function () {
      gRemovePasswordDialog.removePassword();
    });
  },

  validateInput() {
    this._okButton.disabled = !this._token.checkPassword(this._password.value);
  },

  async createAlert(titleL10nId, messageL10nId) {
    const [title, message] = await document.l10n.formatValues([
      { id: titleL10nId },
      { id: messageL10nId },
    ]);
    Services.prompt.alert(window, title, message);
  },

  removePassword() {
    if (this._token.checkPassword(this._password.value)) {
      this._token.changePassword(this._password.value, "");
      this.createAlert("pw-change-success-title", "settings-pp-erased-ok");
    } else {
      this._password.value = "";
      this._password.focus();
      this.createAlert("pw-change-failed-title", "incorrect-pp");
    }
  },
};
PK
!<^��	9chrome/toolkit/content/mozapps/preferences/removemp.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<window
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
  style="min-width: 35em"
  onload="gRemovePasswordDialog.init()"
  data-l10n-id="remove-primary-password"
>
  <dialog id="removemp">
    <script src="chrome://mozapps/content/preferences/removemp.js" />

    <linkset>
      <html:link rel="stylesheet" href="chrome://global/skin/global.css" />

      <html:link rel="localization" href="branding/brand.ftl" />
      <html:link
        rel="localization"
        href="toolkit/preferences/preferences.ftl"
      />
    </linkset>

    <vbox id="warnings">
      <description
        data-l10n-id="remove-primary-password-warning1"
      ></description>
      <description
        class="header"
        data-l10n-id="remove-primary-password-warning2"
      ></description>
    </vbox>

    <separator class="thin" />

    <groupbox>
      <label data-l10n-id="remove-info" />

      <hbox align="center">
        <label control="password" data-l10n-id="remove-password-old-password" />
        <html:input
          id="password"
          type="password"
          oninput="gRemovePasswordDialog.validateInput();"
          aria-describedby="warnings"
        />
      </hbox>
    </groupbox>

    <separator />
  </dialog>
</window>
PK
!<����OO=chrome/toolkit/content/mozapps/profile/createProfileWizard.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const C = Cc;
const I = Ci;

const { AppConstants } = ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);

const ToolkitProfileService = "@mozilla.org/toolkit/profile-service;1";

var gProfileService;
var gProfileManagerBundle;

var gDefaultProfileParent;

// The directory where the profile will be created.
var gProfileRoot;

// Text node to display the location and name of the profile to create.
var gProfileDisplay;

// Called once when the wizard is opened.
function initWizard() {
  try {
    gProfileService = C[ToolkitProfileService].getService(
      I.nsIToolkitProfileService
    );
    gProfileManagerBundle = document.getElementById("bundle_profileManager");

    gDefaultProfileParent = Services.dirsvc.get("DefProfRt", I.nsIFile);

    // Initialize the profile location display.
    gProfileDisplay = document.getElementById("profileDisplay").firstChild;
    document.addEventListener("wizardfinish", onFinish);
    document
      .getElementById("explanation")
      .addEventListener("pageshow", enableNextButton);
    document
      .getElementById("createProfile")
      .addEventListener("pageshow", initSecondWizardPage);
    setDisplayToDefaultFolder();
  } catch (e) {
    window.close();
    throw e;
  }
}

// Called every time the second wizard page is displayed.
function initSecondWizardPage() {
  var profileName = document.getElementById("profileName");
  profileName.select();
  profileName.focus();

  // Initialize profile name validation.
  checkCurrentInput(profileName.value);
}

const kSaltTable = [
  "a",
  "b",
  "c",
  "d",
  "e",
  "f",
  "g",
  "h",
  "i",
  "j",
  "k",
  "l",
  "m",
  "n",
  "o",
  "p",
  "q",
  "r",
  "s",
  "t",
  "u",
  "v",
  "w",
  "x",
  "y",
  "z",
  "1",
  "2",
  "3",
  "4",
  "5",
  "6",
  "7",
  "8",
  "9",
  "0",
];

var kSaltString = "";
for (var i = 0; i < 8; ++i) {
  kSaltString += kSaltTable[Math.floor(Math.random() * kSaltTable.length)];
}

function saltName(aName) {
  return kSaltString + "." + aName;
}

function setDisplayToDefaultFolder() {
  var defaultProfileDir = gDefaultProfileParent.clone();
  defaultProfileDir.append(
    saltName(document.getElementById("profileName").value)
  );
  gProfileRoot = defaultProfileDir;
  document.getElementById("useDefault").disabled = true;
}

function updateProfileDisplay() {
  gProfileDisplay.data = gProfileRoot.path;
}

// Invoke a folder selection dialog for choosing the directory of profile storage.
function chooseProfileFolder() {
  var newProfileRoot;

  var dirChooser = C["@mozilla.org/filepicker;1"].createInstance(
    I.nsIFilePicker
  );
  dirChooser.init(
    window.browsingContext,
    gProfileManagerBundle.getString("chooseFolder"),
    I.nsIFilePicker.modeGetFolder
  );
  dirChooser.appendFilters(I.nsIFilePicker.filterAll);

  // default to the Profiles folder
  dirChooser.displayDirectory = gDefaultProfileParent;

  dirChooser.open(() => {
    newProfileRoot = dirChooser.file;

    // Disable the "Default Folder..." button when the default profile folder
    // was selected manually in the File Picker.
    document.getElementById("useDefault").disabled =
      newProfileRoot.parent.equals(gDefaultProfileParent);

    gProfileRoot = newProfileRoot;
    updateProfileDisplay();
  });
}

// Checks the current user input for validity and triggers an error message accordingly.
function checkCurrentInput(currentInput) {
  let wizard = document.querySelector("wizard");
  var finishButton = wizard.getButton("finish");
  var finishText = document.getElementById("finishText");
  var canAdvance;

  var errorMessage = checkProfileName(currentInput);

  if (!errorMessage) {
    finishText.className = "";
    if (AppConstants.platform == "macosx") {
      finishText.firstChild.data = gProfileManagerBundle.getString(
        "profileFinishTextMac"
      );
    } else {
      finishText.firstChild.data =
        gProfileManagerBundle.getString("profileFinishText");
    }
    canAdvance = true;
  } else {
    finishText.className = "error";
    finishText.firstChild.data = errorMessage;
    canAdvance = false;
  }

  wizard.canAdvance = canAdvance;
  finishButton.disabled = !canAdvance;

  updateProfileDisplay();

  return canAdvance;
}

function updateProfileName(aNewName) {
  if (checkCurrentInput(aNewName)) {
    gProfileRoot.leafName = saltName(aNewName);
    updateProfileDisplay();
  }
}

// Checks whether the given string is a valid profile name.
// Returns an error message describing the error in the name or "" when it's valid.
function checkProfileName(profileNameToCheck) {
  // Check for emtpy profile name.
  if (!/\S/.test(profileNameToCheck)) {
    return gProfileManagerBundle.getString("profileNameEmpty");
  }

  // Check whether all characters in the profile name are allowed.
  if (/([\\*:?<>|\/\"])/.test(profileNameToCheck)) {
    return gProfileManagerBundle.getFormattedString("invalidChar", [RegExp.$1]);
  }

  // Check whether a profile with the same name already exists.
  if (profileExists(profileNameToCheck)) {
    return gProfileManagerBundle.getString("profileExists");
  }

  // profileNameToCheck is valid.
  return "";
}

function profileExists(aName) {
  for (let profile of gProfileService.profiles) {
    if (profile.name.toLowerCase() == aName.toLowerCase()) {
      return true;
    }
  }

  return false;
}

// Called when the first wizard page is shown.
function enableNextButton() {
  document.querySelector("wizard").canAdvance = true;
}

function onFinish(event) {
  var profileName = document.getElementById("profileName").value;
  var profile;

  // Create profile named profileName in profileRoot.
  try {
    profile = gProfileService.createProfile(gProfileRoot, profileName);
  } catch (e) {
    var profileCreationFailed = gProfileManagerBundle.getString(
      "profileCreationFailed"
    );
    var profileCreationFailedTitle = gProfileManagerBundle.getString(
      "profileCreationFailedTitle"
    );
    Services.prompt.alert(
      window,
      profileCreationFailedTitle,
      profileCreationFailed + "\n" + e
    );

    event.preventDefault();
    return;
  }

  if (window.arguments && window.arguments[1]) {
    // Add new profile to the list in the Profile Manager.
    window.arguments[1].CreateProfile(profile);
  } else {
    // Use the newly created Profile.
    var profileLock = profile.lock(null);

    var dialogParams = window.arguments[0].QueryInterface(
      I.nsIDialogParamBlock
    );
    dialogParams.objects.insertElementAt(profileLock, 0);
  }
}
PK
!<�j�@chrome/toolkit/content/mozapps/profile/createProfileWizard.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE wizard>

<window
  id="createProfileWizard"
  data-l10n-id="create-profile-window2"
  xmlns:html="http://www.w3.org/1999/xhtml"
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  onload="initWizard();"
  data-l10n-attrs="style"
>
  <linkset>
    <html:link rel="stylesheet" href="chrome://global/skin/global.css" />

    <html:link rel="localization" href="branding/brand.ftl" />
    <html:link
      rel="localization"
      href="toolkit/global/createProfileWizard.ftl"
    />
  </linkset>

  <script src="chrome://global/content/customElements.js" />
  <script src="chrome://global/content/globalOverlay.js" />
  <script src="chrome://global/content/editMenuOverlay.js" />
  <script src="chrome://mozapps/content/profile/createProfileWizard.js" />

  <wizard>
    <stringbundle
      id="bundle_profileManager"
      src="chrome://mozapps/locale/profile/profileSelection.properties"
    />

    <wizardpage
      id="explanation"
      data-header-label-id="create-profile-first-page-header2"
    >
      <description data-l10n-id="profile-creation-explanation-1"></description>
      <description data-l10n-id="profile-creation-explanation-2"></description>
      <description data-l10n-id="profile-creation-explanation-3"></description>
      <spacer flex="1" />
      <description data-l10n-id="profile-creation-explanation-4"></description>
    </wizardpage>

    <wizardpage
      id="createProfile"
      data-header-label-id="create-profile-last-page-header2"
    >
      <description data-l10n-id="profile-creation-intro"></description>

      <label data-l10n-id="profile-prompt" control="ProfileName"></label>
      <html:input
        id="profileName"
        data-l10n-id="profile-default-name"
        data-l10n-attrs="value"
        oninput="updateProfileName(this.value);"
      />

      <separator />

      <description data-l10n-id="profile-directory-explanation"></description>

      <vbox class="indent" flex="1" style="overflow: auto">
        <description id="profileDisplay">*</description>
      </vbox>

      <hbox>
        <button
          data-l10n-id="create-profile-choose-folder"
          oncommand="chooseProfileFolder();"
        />

        <button
          id="useDefault"
          data-l10n-id="create-profile-use-default"
          oncommand="setDisplayToDefaultFolder(); updateProfileDisplay();"
          disabled="true"
        />
      </hbox>

      <separator />

      <description id="finishText">*</description>
    </wizardpage>
  </wizard>
</window>
PK
!<d���((:chrome/toolkit/content/mozapps/profile/profileDowngrade.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

let gParams;

const { AppConstants } = ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);

function init() {
  /*
   * The C++ code passes a dialog param block using its integers as in and out
   * arguments for this UI. The following are the uses of the integers:
   *
   *  0: A set of flags from nsIToolkitProfileService.downgradeUIFlags.
   *  1: A return argument, one of nsIToolkitProfileService.downgradeUIChoice.
   */
  gParams = window.arguments[0].QueryInterface(Ci.nsIDialogParamBlock);
  if (AppConstants.MOZ_SERVICES_SYNC) {
    let hasSync = gParams.GetInt(0) & Ci.nsIToolkitProfileService.hasSync;

    document.getElementById("sync").hidden = !hasSync;
    document.getElementById("nosync").hidden = hasSync;
  }

  document.addEventListener("dialogextra1", createProfile);
  document.addEventListener("dialogaccept", quit);
  document.addEventListener("dialogcancel", quit);
}

function quit() {
  gParams.SetInt(1, Ci.nsIToolkitProfileService.quit);
}

function createProfile() {
  gParams.SetInt(1, Ci.nsIToolkitProfileService.createNewProfile);
  window.close();
}
PK
!<��;=chrome/toolkit/content/mozapps/profile/profileDowngrade.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window>

<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        xmlns:html="http://www.w3.org/1999/xhtml"
        prefwidth="min-width"
        data-l10n-id="profiledowngrade-window2"
        data-l10n-attrs="title,style"
        onload="init()">
<linkset>
  <html:link rel="stylesheet" href="chrome://global/skin/global.css" />
  <html:link
    rel="stylesheet"
    href="chrome://mozapps/skin/profileDowngrade.css"
  />

  <html:link rel="localization" href="branding/brand.ftl"/>
  <html:link rel="localization" href="toolkit/global/profileDowngrade.ftl"/>
</linkset>
<dialog buttons="accept,extra1" buttonpack="end"
        buttonidextra1="profiledowngrade-window-create"
        buttonidaccept="profiledowngrade-quit">

  <script src="profileDowngrade.js"/>
  <script src="chrome://global/content/customElements.js"/>

  <hbox flex="1" align="start">
    <image id="info" role="presentation"/>
    <vbox flex="1">
      <description data-l10n-id="profiledowngrade-nosync"></description>
    </vbox>
  </hbox>

</dialog>
</window>
PK
!<' 
�i%i%:chrome/toolkit/content/mozapps/profile/profileSelection.js/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { AppConstants } = ChromeUtils.importESModule(
  "resource://gre/modules/AppConstants.sys.mjs"
);

const C = Cc;
const I = Ci;

const ToolkitProfileService = "@mozilla.org/toolkit/profile-service;1";

const fluentStrings = new Localization([
  "branding/brand.ftl",
  "toolkit/global/profileSelection.ftl",
]);

var gDialogParams;
var gProfileManagerBundle;
var gBrandBundle;
var gProfileService;
var gNeedsFlush = false;

function getFluentString(str) {
  return fluentStrings.formatValue(str);
}

function startup() {
  try {
    gDialogParams = window.arguments[0].QueryInterface(I.nsIDialogParamBlock);

    gProfileService = C[ToolkitProfileService].getService(
      I.nsIToolkitProfileService
    );

    gProfileManagerBundle = document.getElementById("bundle_profileManager");
    gBrandBundle = document.getElementById("bundle_brand");

    document.getElementById("profileWindow").centerWindowOnScreen();

    var profilesElement = document.getElementById("profiles");

    for (let profile of gProfileService.profiles.entries(I.nsIToolkitProfile)) {
      var listitem = profilesElement.appendItem(profile.name, "");

      var tooltiptext = gProfileManagerBundle.getFormattedString(
        "profileTooltip",
        [profile.name, profile.rootDir.path]
      );
      listitem.setAttribute("tooltiptext", tooltiptext);
      listitem.profile = profile;
      try {
        if (profile === gProfileService.defaultProfile) {
          setTimeout(
            function (a) {
              profilesElement.ensureElementIsVisible(a);
              profilesElement.selectItem(a);
            },
            0,
            listitem
          );
        }
      } catch (e) {}
    }

    var autoSelectLastProfile = document.getElementById(
      "autoSelectLastProfile"
    );
    autoSelectLastProfile.checked = gProfileService.startWithLastProfile;
    profilesElement.focus();
  } catch (e) {
    window.close();
    throw e;
  }
  document.addEventListener("dialogaccept", acceptDialog);
  document.addEventListener("dialogcancel", exitDialog);
}

async function flush(cancelled) {
  updateStartupPrefs();

  gDialogParams.SetInt(
    1,
    document.getElementById("offlineState").checked ? 1 : 0
  );

  if (gNeedsFlush) {
    try {
      gProfileService.flush();
    } catch (e) {
      let appName = gBrandBundle.getString("brandShortName");

      let title = gProfileManagerBundle.getString("flushFailTitle");
      let restartButton = gProfileManagerBundle.getFormattedString(
        "flushFailRestartButton",
        [appName]
      );
      let exitButton = gProfileManagerBundle.getString("flushFailExitButton");

      let message;
      if (e.result == undefined) {
        message = await getFluentString("profile-selection-conflict-message");
      } else {
        message = gProfileManagerBundle.getString("flushFailMessage");
      }

      const PS = Ci.nsIPromptService;
      let result = Services.prompt.confirmEx(
        window,
        title,
        message,
        PS.BUTTON_POS_0 * PS.BUTTON_TITLE_IS_STRING +
          PS.BUTTON_POS_1 * PS.BUTTON_TITLE_IS_STRING,
        restartButton,
        exitButton,
        null,
        null,
        {}
      );

      gDialogParams.SetInt(
        0,
        result == 0
          ? Ci.nsIToolkitProfileService.restart
          : Ci.nsIToolkitProfileService.exit
      );
      return;
    }
    gNeedsFlush = false;
  }

  gDialogParams.SetInt(
    0,
    cancelled
      ? Ci.nsIToolkitProfileService.exit
      : Ci.nsIToolkitProfileService.launchWithProfile
  );
}

function acceptDialog(event) {
  var appName = gBrandBundle.getString("brandShortName");

  var profilesElement = document.getElementById("profiles");
  var selectedProfile = profilesElement.selectedItem;
  if (!selectedProfile) {
    var pleaseSelectTitle =
      gProfileManagerBundle.getString("pleaseSelectTitle");
    var pleaseSelect = gProfileManagerBundle.getFormattedString(
      "pleaseSelect",
      [appName]
    );
    Services.prompt.alert(window, pleaseSelectTitle, pleaseSelect);
    event.preventDefault();
    return;
  }

  gDialogParams.objects.insertElementAt(selectedProfile.profile.rootDir, 0);
  gDialogParams.objects.insertElementAt(selectedProfile.profile.localDir, 1);

  if (gProfileService.defaultProfile != selectedProfile.profile) {
    try {
      gProfileService.defaultProfile = selectedProfile.profile;
      gNeedsFlush = true;
    } catch (e) {
      // This can happen on dev-edition. We'll still restart with the selected
      // profile based on the lock's directories.
    }
  }
  flush(false);
}

function exitDialog() {
  flush(true);
}

function updateStartupPrefs() {
  var autoSelectLastProfile = document.getElementById("autoSelectLastProfile");
  if (gProfileService.startWithLastProfile != autoSelectLastProfile.checked) {
    gProfileService.startWithLastProfile = autoSelectLastProfile.checked;
    gNeedsFlush = true;
  }
}

// handle key event on listboxes
function onProfilesKey(aEvent) {
  switch (aEvent.keyCode) {
    case KeyEvent.DOM_VK_BACK_SPACE:
      if (AppConstants.platform != "macosx") {
        break;
      }
    // fall through
    case KeyEvent.DOM_VK_DELETE:
      ConfirmDelete();
      break;
    case KeyEvent.DOM_VK_F2:
      RenameProfile();
      break;
  }
}

function onProfilesDblClick(aEvent) {
  if (aEvent.target.closest("richlistitem")) {
    document.getElementById("profileWindow").acceptDialog();
  }
}

// invoke the createProfile Wizard
function CreateProfileWizard() {
  window.openDialog(
    "chrome://mozapps/content/profile/createProfileWizard.xhtml",
    "",
    "centerscreen,chrome,modal,titlebar",
    gProfileService,
    { CreateProfile }
  );
}

/**
 * Called from createProfileWizard to update the display.
 */
function CreateProfile(aProfile) {
  var profilesElement = document.getElementById("profiles");

  var listitem = profilesElement.appendItem(aProfile.name, "");

  var tooltiptext = gProfileManagerBundle.getFormattedString("profileTooltip", [
    aProfile.name,
    aProfile.rootDir.path,
  ]);
  listitem.setAttribute("tooltiptext", tooltiptext);
  listitem.profile = aProfile;

  profilesElement.ensureElementIsVisible(listitem);
  profilesElement.selectItem(listitem);

  gNeedsFlush = true;
}

// rename the selected profile
function RenameProfile() {
  var profilesElement = document.getElementById("profiles");
  var selectedItem = profilesElement.selectedItem;
  if (!selectedItem) {
    return false;
  }

  var selectedProfile = selectedItem.profile;

  var oldName = selectedProfile.name;
  var newName = { value: oldName };

  var dialogTitle = gProfileManagerBundle.getString("renameProfileTitle");
  var msg = gProfileManagerBundle.getFormattedString("renameProfilePrompt", [
    oldName,
  ]);

  if (
    Services.prompt.prompt(window, dialogTitle, msg, newName, null, {
      value: 0,
    })
  ) {
    newName = newName.value;

    // User hasn't changed the profile name. Treat as if cancel was pressed.
    if (newName == oldName) {
      return false;
    }

    try {
      selectedProfile.name = newName;
      gNeedsFlush = true;
    } catch (e) {
      var alTitle = gProfileManagerBundle.getString("profileNameInvalidTitle");
      var alMsg = gProfileManagerBundle.getFormattedString(
        "profileNameInvalid",
        [newName]
      );
      Services.prompt.alert(window, alTitle, alMsg);
      return false;
    }

    selectedItem.firstChild.setAttribute("value", newName);
    var tiptext = gProfileManagerBundle.getFormattedString("profileTooltip", [
      newName,
      selectedProfile.rootDir.path,
    ]);
    selectedItem.setAttribute("tooltiptext", tiptext);

    return true;
  }

  return false;
}

function ConfirmDelete() {
  var profileList = document.getElementById("profiles");

  var selectedItem = profileList.selectedItem;
  if (!selectedItem) {
    return false;
  }

  var selectedProfile = selectedItem.profile;
  var deleteFiles = false;

  if (selectedProfile.rootDir.exists()) {
    var dialogTitle = gProfileManagerBundle.getString("deleteTitle");
    var dialogText = gProfileManagerBundle.getFormattedString(
      "deleteProfileConfirm",
      [selectedProfile.rootDir.path]
    );

    var buttonPressed = Services.prompt.confirmEx(
      window,
      dialogTitle,
      dialogText,
      Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
        Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1 +
        Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_2,
      gProfileManagerBundle.getString("dontDeleteFiles"),
      null,
      gProfileManagerBundle.getString("deleteFiles"),
      null,
      { value: 0 }
    );
    if (buttonPressed == 1) {
      return false;
    }

    if (buttonPressed == 2) {
      deleteFiles = true;
    }
  }

  try {
    selectedProfile.remove(deleteFiles);
    gNeedsFlush = true;
  } catch (e) {
    let title = gProfileManagerBundle.getString("profileDeletionFailedTitle");
    let msg = gProfileManagerBundle.getString("profileDeletionFailed");
    Services.prompt.alert(window, title, msg);

    return true;
  }

  profileList.removeChild(selectedItem);
  if (profileList.firstChild != undefined) {
    profileList.selectItem(profileList.firstChild);
  }

  return true;
}
PK
!<)U��
�
=chrome/toolkit/content/mozapps/profile/profileSelection.xhtml<?xml version="1.0"?>
<!-- -*- Mode: SGML; indent-tabs-mode: nil; -*- -->
<!--

 This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window>

<window
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
  class="non-resizable"
  data-l10n-id="profile-selection-window"
  orient="vertical"
  prefwidth="min-width"
  style="min-width: 30em"
  onload="startup();"
>
  <dialog
    id="profileWindow"
    buttons="accept,cancel"
    buttonidaccept="profile-selection-button-accept"
    buttonidcancel="profile-selection-button-cancel"
  >
    <linkset>
      <html:link
        rel="stylesheet"
        href="chrome://mozapps/skin/profileSelection.css"
      />

      <html:link rel="localization" href="branding/brand.ftl" />
      <html:link
        rel="localization"
        href="toolkit/global/profileSelection.ftl"
      />
    </linkset>

    <script src="chrome://global/content/customElements.js" />

    <stringbundle
      id="bundle_profileManager"
      src="chrome://mozapps/locale/profile/profileSelection.properties"
    />
    <stringbundle
      id="bundle_brand"
      src="chrome://branding/locale/brand.properties"
    />

    <script src="chrome://mozapps/content/profile/profileSelection.js" />

    <description
      class="label"
      data-l10n-id="profile-manager-description"
    ></description>

    <separator class="thin" />

    <hbox class="indent">
      <vbox id="managebuttons">
        <button
          id="newbutton"
          data-l10n-id="profile-selection-new-button"
          oncommand="CreateProfileWizard();"
        />
        <button
          id="renbutton"
          data-l10n-id="profile-selection-rename-button"
          oncommand="RenameProfile();"
        />
        <button
          id="delbutton"
          data-l10n-id="profile-selection-delete-button"
          oncommand="ConfirmDelete();"
        />
      </vbox>

      <vbox id="profilesContainer">
        <richlistbox
          id="profiles"
          class="theme-listbox"
          seltype="single"
          ondblclick="onProfilesDblClick(event)"
          onkeypress="onProfilesKey(event);"
        >
        </richlistbox>

        <!-- Bug 257777 -->
        <checkbox
          id="offlineState"
          data-l10n-id="profile-manager-work-offline"
          native="true"
        />

        <checkbox
          id="autoSelectLastProfile"
          data-l10n-id="profile-manager-use-selected"
          native="true"
        />
      </vbox>
    </hbox>
  </dialog>
</window>
PK
!<̅P�n&n&/chrome/toolkit/featuregates/FeatureGate.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  // This is an unfortunate exception where we depend on ASRouter because
  // Nimbus has this dependency.
  // This implementation is written in a way where it will avoid requiring
  // this module if it's not available.
  ASRouterTargeting:
    // eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
    "resource:///modules/asrouter/ASRouterTargeting.sys.mjs",
  FeatureGateImplementation:
    "resource://featuregates/FeatureGateImplementation.sys.mjs",
  ExperimentManager: "resource://nimbus/lib/ExperimentManager.sys.mjs",
  TargetingContext: "resource://messaging-system/targeting/Targeting.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "gFeatureDefinitionsPromise", async () => {
  const url = "resource://featuregates/feature_definitions.json";
  return fetchFeatureDefinitions(url);
});

const kCustomTargeting = {
  // For default values, although something like `channel == 'nightly'` kinda
  // works, local builds don't have that update channel set in that way so it
  // doesn't, and then tests fail because the defaults for the FeatureGate
  // do not match the default value in the prefs code.
  // We may in future want other things from AppConstants here, too.
  nightly_build: AppConstants.NIGHTLY_BUILD,
  thunderbird: AppConstants.MOZ_APP_NAME == "thunderbird",
};

ChromeUtils.defineLazyGetter(lazy, "defaultContexts", () => {
  let ASRouterEnv = {};
  try {
    ASRouterEnv = lazy.ASRouterTargeting.Environment;
  } catch (ex) {
    // No ASRouter; just keep going.
  }
  return [
    kCustomTargeting,
    lazy.ExperimentManager.createTargetingContext(),
    ASRouterEnv,
  ];
});

function getCombinedContext(...contexts) {
  let combined = lazy.TargetingContext.combineContexts(
    ...lazy.defaultContexts,
    ...contexts
  );
  return new lazy.TargetingContext(combined, {
    source: "featuregate",
  });
}

async function fetchFeatureDefinitions(url) {
  const res = await fetch(url);
  let definitionsJson = await res.json();
  return new Map(Object.entries(definitionsJson));
}

async function buildFeatureGateImplementation(definition) {
  const targetValueKeys = ["defaultValue", "isPublic"];
  for (const key of targetValueKeys) {
    definition[key] = await FeatureGate.evaluateJexlValue(
      definition[key + "Jexl"]
    );
  }
  return new lazy.FeatureGateImplementation(definition);
}

let featureGatePrefObserver = {
  onChange() {
    FeatureGate.annotateCrashReporter();
  },
  // Ignore onEnable and onDisable since onChange is called in both cases.
  onEnable() {},
  onDisable() {},
};

const kFeatureGateCache = new Map();

/** A high level control for turning features on and off. */
export class FeatureGate {
  /*
   * This is structured as a class with static methods to that sphinx-js can
   * easily document it. This constructor is required for sphinx-js to detect
   * this class for documentation.
   */

  constructor() {}

  /**
   * Constructs a feature gate object that is defined in ``Features.toml``.
   * This is the primary way to create a ``FeatureGate``.
   *
   * @param {string} id The ID of the feature's definition in `Features.toml`.
   * @param {string} testDefinitionsUrl A URL from which definitions can be fetched. Only use this in tests.
   * @throws If the ``id`` passed is not defined in ``Features.toml``.
   */
  static async fromId(id, testDefinitionsUrl = undefined) {
    let featureDefinitions;
    if (testDefinitionsUrl) {
      featureDefinitions = await fetchFeatureDefinitions(testDefinitionsUrl);
    } else {
      featureDefinitions = await lazy.gFeatureDefinitionsPromise;
    }

    if (!featureDefinitions.has(id)) {
      throw new Error(
        `Unknown feature id ${id}. Features must be defined in toolkit/components/featuregates/Features.toml`
      );
    }

    // Make a copy of the definition, since we are about to modify it
    return buildFeatureGateImplementation({ ...featureDefinitions.get(id) });
  }

  /**
   * Constructs feature gate objects for each of the definitions in ``Features.toml``.
   * @param {string} testDefinitionsUrl A URL from which definitions can be fetched. Only use this in tests.
   */
  static async all(testDefinitionsUrl = undefined) {
    let featureDefinitions;
    if (testDefinitionsUrl) {
      featureDefinitions = await fetchFeatureDefinitions(testDefinitionsUrl);
    } else {
      featureDefinitions = await lazy.gFeatureDefinitionsPromise;
    }

    let definitions = [];
    for (let definition of featureDefinitions.values()) {
      // Make a copy of the definition, since we are about to modify it
      definitions[definitions.length] = await buildFeatureGateImplementation(
        Object.assign({}, definition)
      );
    }
    return definitions;
  }

  static async observePrefChangesForCrashReportAnnotation(
    testDefinitionsUrl = undefined
  ) {
    let featureDefinitions = await FeatureGate.all(testDefinitionsUrl);

    for (let definition of featureDefinitions.values()) {
      FeatureGate.addObserver(
        definition.id,
        featureGatePrefObserver,
        testDefinitionsUrl
      );
    }
  }

  static async annotateCrashReporter() {
    if (!Services.appinfo.crashReporterEnabled) {
      return;
    }
    let features = await FeatureGate.all();
    let enabledFeatures = [];
    for (let feature of features) {
      if (await feature.getValue()) {
        enabledFeatures.push(feature.preference);
      }
    }
    Services.appinfo.annotateCrashReport(
      "ExperimentalFeatures",
      enabledFeatures.join(",")
    );
  }

  /**
   * Add an observer for a feature gate by ID. If the feature is of type
   * boolean and currently enabled, `onEnable` will be called.
   *
   * The underlying feature gate instance will be shared with all other callers
   * of this function, and share an observer.
   *
   * @param {string} id The ID of the feature's definition in `Features.toml`.
   * @param {object} observer Functions to be called when the feature changes.
   *        All observer functions are optional.
   * @param {Function()} [observer.onEnable] Called when the feature becomes enabled.
   * @param {Function()} [observer.onDisable] Called when the feature becomes disabled.
   * @param {Function(newValue)} [observer.onChange] Called when the
   *        feature's state changes to any value. The new value will be passed to the
   *        function.
   * @param {string} testDefinitionsUrl A URL from which definitions can be fetched. Only use this in tests.
   * @returns {Promise<boolean>} The current value of the feature.
   */
  static async addObserver(id, observer, testDefinitionsUrl = undefined) {
    if (!kFeatureGateCache.has(id)) {
      kFeatureGateCache.set(
        id,
        await FeatureGate.fromId(id, testDefinitionsUrl)
      );
    }
    const feature = kFeatureGateCache.get(id);
    return feature.addObserver(observer);
  }

  /**
   * Remove an observer of changes from this feature
   * @param {string} id The ID of the feature's definition in `Features.toml`.
   * @param observer Then observer that was passed to addObserver to remove.
   */
  static async removeObserver(id, observer) {
    let feature = kFeatureGateCache.get(id);
    if (!feature) {
      return;
    }
    feature.removeObserver(observer);
    if (feature._observers.size === 0) {
      kFeatureGateCache.delete(id);
    }
  }

  /**
   * Get the current value of this feature gate. Implementors should avoid
   * storing the result to avoid missing changes to the feature's value.
   * Consider using :func:`addObserver` if it is necessary to store the value
   * of the feature.
   *
   * @async
   * @param {string} id The ID of the feature's definition in `Features.toml`.
   * @returns {Promise<boolean>} A promise for the value associated with this feature.
   */
  static async getValue(id, testDefinitionsUrl = undefined) {
    let feature = kFeatureGateCache.get(id);
    if (!feature) {
      feature = await FeatureGate.fromId(id, testDefinitionsUrl);
    }
    return feature.getValue();
  }

  /**
   * An alias of `getValue` for boolean typed feature gates.
   *
   * @async
   * @param {string} id The ID of the feature's definition in `Features.toml`.
   * @returns {Promise<boolean>} A promise for the value associated with this feature.
   * @throws {Error} If the feature is not a boolean.
   */
  static async isEnabled(id, testDefinitionsUrl = undefined) {
    let feature = kFeatureGateCache.get(id);
    if (!feature) {
      feature = await FeatureGate.fromId(id, testDefinitionsUrl);
    }
    return feature.isEnabled();
  }

  /**
   * Take a jexl expression and evaluate it against the standard Nimbus
   * context, extended with some additional properties defined in
   * kCustomTargeting.
   *
   * @param {String} jexlExpression The expression to evaluate.
   * @param {Object[]?} additionalContexts Any additional context properties
   *                                    that should be taken into account.
   *
   * @returns {Promise<boolean>} Resolves to either true or false if successful,
   *          or null if there was some problem with the jexl expression (which
   *          will also log an error to the console).
   */
  static async evaluateJexlValue(jexlExpression, ...additionalContexts) {
    let result = null;
    let context = getCombinedContext(...additionalContexts);
    try {
      result = !!(await context.evalWithDefault(jexlExpression));
    } catch (ex) {
      console.error(ex);
    }
    return result;
  }
}
PK
!<"���bb=chrome/toolkit/featuregates/FeatureGateImplementation.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/** An individual feature gate that can be re-used for more advanced usage. */
export class FeatureGateImplementation {
  // Note that the following comment is *not* a jsdoc. Making it a jsdoc would
  // makes sphinx-js expose it to users. This feature shouldn't be used by
  // users, and so should not be in the docs. Sphinx-js does not respect the
  // @private marker on a constructor (https://github.com/erikrose/sphinx-js/issues/71).
  /*
   * This constructor should only be used directly in tests.
   * ``FeatureGate.fromId`` should be used instead for most cases.
   *
   * @private
   *
   * @param {object} definition Description of the feature gate.
   * @param {string} definition.id
   * @param {string} definition.title
   * @param {string} definition.description
   * @param {string} definition.descriptionLinks
   * @param {boolean} definition.restartRequired
   * @param {string} definition.type
   * @param {string} definition.preference
   * @param {string} definition.defaultValue
   * @param {object} definition.isPublic
   * @param {object} definition.bugNumbers
   */
  constructor(definition) {
    this._definition = definition;
    this._observers = new Set();
  }

  // The below are all getters instead of direct access to make it easy to provide JSDocs.

  /**
   * A short string used to refer to this feature in code.
   * @type string
   */
  get id() {
    return this._definition.id;
  }

  /**
   * A Fluent string ID that will resolve to some text to identify this feature's group to users.
   * @type string
   */
  get group() {
    return this._definition.group;
  }

  /**
   * A Fluent string ID that will resolve to some text to identify this feature to users.
   * @type string
   */
  get title() {
    return this._definition.title;
  }

  /**
   * A Fluent string ID that will resolve to a longer string to show to users that explains the feature.
   * @type string
   */
  get description() {
    return this._definition.description;
  }

  get descriptionLinks() {
    return this._definition.descriptionLinks;
  }

  /**
   * Whether this feature requires a browser restart to take effect after toggling.
   * @type boolean
   */
  get restartRequired() {
    return this._definition.restartRequired;
  }

  /**
   * The type of feature. Currently only booleans are supported. This may be
   * richer than JS types in the future, such as enum values.
   * @type string
   */
  get type() {
    return this._definition.type;
  }

  /**
   * The name of the preference that stores the value of this feature.
   *
   * This preference should not be read directly, but instead its values should
   * be accessed via FeatureGate#addObserver or FeatureGate#getValue. This
   * property is provided for backwards compatibility.
   *
   * @type string
   */
  get preference() {
    return this._definition.preference;
  }

  /**
   * The default value for the feature gate for this update channel.
   * @type boolean
   */
  get defaultValue() {
    return this._definition.defaultValue;
  }

  /**
   * If this feature should be exposed to users in an advanced settings panel
   * for this build of Firefox.
   *
   * @type boolean
   */
  get isPublic() {
    return this._definition.isPublic;
  }

  /**
   * Bug numbers associated with this feature.
   * @type Array<number>
   */
  get bugNumbers() {
    return this._definition.bugNumbers;
  }

  /**
   * Get the current value of this feature gate. Implementors should avoid
   * storing the result to avoid missing changes to the feature's value.
   * Consider using :func:`addObserver` if it is necessary to store the value
   * of the feature.
   *
   * @async
   * @returns {Promise<boolean>} A promise for the value associated with this feature.
   */
  // Note that this is async for potential future use of a storage backend besides preferences.
  async getValue() {
    return Services.prefs.getBoolPref(this.preference, this.defaultValue);
  }

  /**
   * An alias of `getValue` for boolean typed feature gates.
   *
   * @async
   * @returns {Promise<boolean>} A promise for the value associated with this feature.
   * @throws {Error} If the feature is not a boolean.
   */
  // Note that this is async for potential future use of a storage backend besides preferences.
  async isEnabled() {
    if (this.type !== "boolean") {
      throw new Error(
        `Tried to call isEnabled when type is not boolean (it is ${this.type})`
      );
    }
    return this.getValue();
  }

  /**
   * Add an observer for changes to this feature. When the observer is added,
   * `onChange` will asynchronously be called with the current value of the
   * preference. If the feature is of type boolean and currently enabled,
   * `onEnable` will additionally be called.
   *
   * @param {object} observer Functions to be called when the feature changes.
   *        All observer functions are optional.
   * @param {Function()} [observer.onEnable] Called when the feature becomes enabled.
   * @param {Function()} [observer.onDisable] Called when the feature becomes disabled.
   * @param {Function(newValue: boolean)} [observer.onChange] Called when the
   *        feature's state changes to any value. The new value will be passed to the
   *        function.
   * @returns {Promise<boolean>} The current value of the feature.
   */
  async addObserver(observer) {
    if (this._observers.size === 0) {
      Services.prefs.addObserver(this.preference, this);
    }

    this._observers.add(observer);

    if (this.type === "boolean" && (await this.isEnabled())) {
      this._callObserverMethod(observer, "onEnable");
    }
    // onDisable should not be called, because features should be assumed
    // disabled until onEnabled is called for the first time.

    return this.getValue();
  }

  /**
   * Remove an observer of changes from this feature
   * @param observer The observer that was passed to addObserver to remove.
   */
  removeObserver(observer) {
    this._observers.delete(observer);
    if (this._observers.size === 0) {
      Services.prefs.removeObserver(this.preference, this);
    }
  }

  /**
   * Removes all observers from this instance of the feature gate.
   */
  removeAllObservers() {
    if (this._observers.size > 0) {
      this._observers.clear();
      Services.prefs.removeObserver(this.preference, this);
    }
  }

  _callObserverMethod(observer, method, ...args) {
    if (method in observer) {
      try {
        observer[method](...args);
      } catch (err) {
        console.error(err);
      }
    }
  }

  /**
   * Observes changes to the preference storing the enabled state of the
   * feature. The observer is dynamically added only when observer have been
   * added.
   * @private
   */
  async observe(aSubject, aTopic, aData) {
    if (aTopic === "nsPref:changed" && aData === this.preference) {
      const value = await this.getValue();
      for (const observer of this._observers) {
        this._callObserverMethod(observer, "onChange", value);

        if (value) {
          this._callObserverMethod(observer, "onEnable");
        } else {
          this._callObserverMethod(observer, "onDisable");
        }
      }
    } else {
      console.error(
        new Error(`Unexpected event observed: ${aSubject}, ${aTopic}, ${aData}`)
      );
    }
  }
}
PK
!<}��(��2chrome/toolkit/res/accessiblecaret-normal@1.5x.png�PNG


IHDR36jl��tIDATx�x�Z�3�ت���L��m۶m۶Qsm۶m+����t���̾o�}����_�e��0�"Lf.Uxq�)��r:�b�_�:3��-�@@�6X͔��eb��m&�!��	#Tj�K�'î�t�}6p��hJ���0�|�0	O?���͆�E�}k,�"��`H�� E�>�R��g���l�[���?f��]-2z���ٛ��?|�{����G�#fк;�)��W�&�*d�b$I��	S� �R�`Dw�	]������=���>D�?��jr�s���(�P�)��`A)4��QG�"BV\]�$���[j�M֔��
Y~i���o�+R�p�ҁ��*��(s�sO4�ټ2�Δ�
�u`��?�^`�D��-@�O�=�I|��c�
f0�`_E@T���F���T/�<���B�
^|~�"(��ߊ��F��E�e(� �c�����5U��b6eh>¥Zg~a�r�գx�wי�g��L�&�l��{|գ嘂��5	J���X�e��-_!D�����B݇<�����/Q�ϔ��2�$�3aAJ�=<�
+�cu�o�<}�¹�
�VI���ӫӌ����V��O��5�#�0��.� �pnnA�Ξ�zH�# �:Lm�lu�0.�m&T��Έ���I�2��<)�٬Cb�p#v�u
Zp�9��"��`jg.(B�����׺}�01�������6���SՎ�I����R�Ş�Ƕ�a�?xq���\��nD)un`�Z�W�)�-�c�~�֠�J����h�U2�yź���f
�Ȕ���C+��W3�����5�w�ZN`B�ĹJ��t��O���Go�Z� �r5g�Y���s^Cs�36x��{��ЅR\&M��[�›�
Y�݇��+�G��ch�`h�M�
�f#Fb��}���_z�g|�
ր��?�������ѫ��%����Z�_��l5~��Vk�$��J�d��)��u�3H�C7Dz�B�+�l�e�B5z����h�-��X���~���������[�~��7~‡#g a?���:O�g��5����(6��&���R����7߇MB���t�	�K.<c�b��S�5���t��%�����
�c	�S��Y�����g�#�A�`�������9�UȊ+}z,�"�����	�lyH�t�(�24��Kv�0�)��O�X�z��ޠ��t��?}����6+�swPT.����3M	�)�k���Z��
���7� d�W���<[���?��1ڊj��fG�����5�e�m�:�
��~��f�y�r��C@��r`�]����q��S2M�Z
*UIEND�B`�PK
!<ݪ���0chrome/toolkit/res/accessiblecaret-normal@1x.png�PNG


IHDR"$����IDATx̗��L�3������$�ͳm۶m۶mۥ������ S{�zq��<7��&�7]�dђ�k"�2%������5�T[e��B��|l�Og
�vt��h-��}ǖT����E�^�m�P����eD��m��(~�]���C���
���$ ��"���B͹g���fkz
���K|d�f$����;x��|+B�Ki~@$f?����^�˷�slN�H�y�!^�����O��l��	a��ZgB�ge�@�9�7KŶfzS��U�1�GB�~���f�-B���VnO���чJ��0[��P�`���f�uO���qs�ֿ���T��G�m<��3�)�O�i�7�`6$ f[FBp�E�U�_���&�Wd��,j�3����Y�|��F�����!z��&�
`�	��?pDb
$�����ؒ�G^�)j��!b��=�w�;�YO�9�Yy�F�ȃb���V��l�.G草�^�W
�@��b����ph"�>�j�c1v�R�9
B*�2�%@=��W~@�`���D�<�B�������Oo�d�ލ�Joq�4
�����W���f��K@�F���^��PvW./ �M�ȷ��^��]Q���b��#���2�*��2n��Hk����"�d�����ܱ����N񺿫v�V'$� �uO�,�H�]�f�=_ߦ#V����%l�B�X�[OA$��?u[p�^�L�9 ��{
P`�Y;!
iܰ��ūH��w{�k�/��ݹ�U~�Ǚ;%6㼑Q�~�g/�s9!��W��9���}��n�1;�Y���:n��D�6w�b�cBAd�Xא j�Eyp\s��pc��s�պ,1�?[��PX�6�����X��x�X��X
��|nd��z��Q��:�^��Ki�IEND�B`�PK
!<�j�~3chrome/toolkit/res/accessiblecaret-normal@2.25x.png�PNG


IHDRMQ�+P�IDATx�t$K�kcl�Nz&X۶m۶�`m�~�mkm[��=;����ٮs�5�/U���"�҇�(�K"4�M����<PCB�g�w�Z��q#Oc0y
˽�)*4�s�\:�d�L��Ac�W��c��$lV�dg�9�����F#���M$�ݖOp���:�C¾C
wMؽ���N��,�M�3�;�N!0V�%Wh��̕"��Y*�*~���#����9��CU�M��_�><�7����4T��q�-�[l������˷L��C�����I�,y��D,~y���<���&��s�O��ޡQ&#�Q)��!\h��H�$�~,G�5Iz�儽��	
�Xz�چ�L�Rr�%��'�S�C��…��ޖۏ#���UZ9��`���۵s��+���/�[MtM��\#�m��<Y�g�����ы=4d
x�~�o1$����Ob7|��Yt�a����M�.:K������B�
O$��/p�@�,g�i��a��~ېU�?��{B�.����EL.����
��?K��4fC�O(�^�T5�/S�����⋏	�m����e�	ͯD*>4߈d;�����I/\�[{�G�v�g���ue���6`)�5(4O����-���?{�~��?нoU��础9�6x���Q�A�4�J#W�5kc�R���>H�
���n׷((h^�0i���;
β	�3�ރhI��j��4OC�N�x~b�%�$8&�0SQ������*���!I/��+,h�d|
��_h�nӫ&�J/2l�^΢�6,�Z�AC�f�~�N����k�a�q��k���4���������%�F#��*�	��G���ٚ�'�:�Ϝ1��S�Ф^�$lN�p&�.	8�b[���=Ro?�yC�P���"�T�C��䠮�xΡ�{�mG���اoz�^�M�=g��Z������t�#���bOSw�$2�O��7>
4y�Rү��M�9��fQU�]T.�1�DŽ
KH%�oB��9���=g��T�
^�B��q
2���m8�0�T[,���N����N�sMm�^�����_x<i�̬tn�j��Z#(h��-���N�[�X)8h%�h�̜M�\*�u��X^RLKz�&�
]����w=	=�PnOT���Z��4L��y���� �aqw��v���e~1�Sb7w����I{����J�:r�{��
&�r䟡P���/*%�Kh���n�)E�W9PI/ݠ����= .�ɕ�ub����?>�7�3��Ґ�/0:z��b���0݋]����+ˇ�����|��54ߘ	@���|�F<u��1k?�ős�_�;v2�$����Y���
���������й0��^�w� 'ܚ�~7B�|jclK�_�a�K����c�坡�i�,W����h�_R��]��u�ۜ�L��MU�Vð=�ש!HQ�R���;h��X����H%��>�f1v���בs��t	=��ςBH���d�$��i�z�ž�|F�������K$2�!��z��t����r�Y|o�����G/�;��}F&�ˠ'4\������5�Æ�'
�=<�X���	Mݮ�	!� _����Aj�Dln����B/������`�*���oyۢ:aLy��{OoP$��	���]h._���sUGPvQL�Q�?���fd�R�D��$��HVRH�^�|B��=g�N�`?��Нil���	:��	V�{�⟏���(���8	Luu|!	����b9H	c
�.i	o�Fڏ�4v�0M�M��7��31�\�N��,�E8
se?�L�����m��)c�﵁����_���sl�dŀ*���D&kV�-@i7`;PK�]e��AF�?������]GMҹ�W�.�x�/�=���Dh"4Q"4�M�&B%B���Dh"4Q�U'�i�>IEND�B`�PK
!<����FF0chrome/toolkit/res/accessiblecaret-normal@2x.png�PNG


IHDRDG���
IDATx��cK�s�tڶc���z�m۶m��3϶������Z�]��ɝt���&�|s�ԩS�.���I�	�B!��H�K-�p)ޢ�#��gs"ɺGn9�1_�^g��?#�����~�lE�t��r�Sv����(Dӫ��B|�qb�b����_�=�ꭊH�>B(ꏻq?s��5�gg2Nj�&V2x�m�s��E-�P�7N����Ş������wݤ�𘞝S�":��<}���Mkb��$!��ߙ9q����{
�&���t7��ᇾ����d ��<���'�e{�H�?ꎸ���
\��$��BVw��{����)��K�\2��ʿ$��<y��/��^�BfV
�D�aT����[������)w]�"�d�	�</W|7L�5��oV2�̜���o�~���>;"�dȚ��|���%Ǯ��!�xn3ǯa���p<�u�=7K!$C7\Mg<p�ئ�s�K�ӿ1L/^��k���#��jk)�B�k���~����q��߶�T�&��xy����G��nZKq�ZM
Qk<�Yk<M"���x�	��HΥ��Ȩ��(��M��P��"~c<���o��۔�^�L�	������p��&�[�S�ky�Wfb��1eӢƓ��R��u�B����z�$�6x���a�l�X�V5���>���B�}B���#�9Ȝ�n#V6)HԘ����e'I��#�b�㇦&�]�#��e+���m�1əB2
{n]����3h�釯>�G���i��fB�d4�
�
��E��e���Y�O^$PR�����V��5�$^��\<p��c:������݇���yйg
��t��%�"���΁�?�@�8FH��y� 1�E��~!d�����3ǯf1J��c�uP���$BT �]��$q��w�ۮy��_��+B���ۆP}���ź.��OK4 �k�IL����-9v�Rj:
^�b���iOS�.!������p�9a
�pg	�<����z�$�]B�l�O9/5q=���a�`<�y���+�]BT��j�Ӯ¹��:���1�v	р�Y˅B�5�b�rד.2��!N�2�x
�#�]���'�9������Ĩ%�|�=0V0��!��C��3����:���W��������v�2%Zy���ͯ%��xe��kzec�����¬T7�r�H�"���~�z�so��(����P��zN�����������ڍ���9
c,����˸A=�!4E��1B��
��SK�z�0ұ��A��y�=���1m���k��]O�c�e@M��6��e��#���j����wZq�0��������ԂDŎ�\��>��޵k˶<�<�)@�)�=�bЂS��m��8-c�ULW~���^�x��vP�tw�ݠ�z�ޡ�>��������]7�c��bW�%*���%�	ļ�>'��ϧ�v'��(o�d�{>��h�1$A#�r�N�tP��g~b�9�ǿ�oNZg_���*~
�dD�D�1���Ţ�ɳ]�S���e�����5���Mɶ�d$p��1�1�u���+�g�APFc�2}B��T��>Zy�~���4������eɓ��T��;�p�yw����!;�~��R�ز�~��ډ��c�8:�Xy�W�%<��,~��_��������E��<���>P*dd�:�E��Ah&䒤}��r=�V��;g�QQn�Mf��輜�(b�"���1�c<:���?_�r�ӯ�4�I���{&h%�22+DH�A!�a�
�{5-'W�z��)Ok����B��_ Z~��%�<�ͯ�n�蹯N�c
��������=������J8��T�pnQ3lv�a�x8�ON�F���;'��<U�+8�?v���ID������]
�Qhׂ6����b01��D@+��-�bs��䓜
�Z�K�
PB�1�=�l��(�YM����W4��f!!;�XD���#'$'$'$'$'$'$'$'$'�q�	t��\IkIEND�B`�PK
!<A�G��5chrome/toolkit/res/accessiblecaret-tilt-left@1.5x.png�PNG


IHDR36jl��jIDATx��#��+v2�c2\۶m۶m۶m{w��m�W�Mϵow������U)5B�5�a�n�a~�d�@EͳH]�&��}@?f/("�`��
���:,��24'�3/�i�i0N9���4�$�
�g���	�"!��T�4������-�5+��\�P�t%L_Џ�M��.�u��2/U��N�g�G�S@A����)j�p�| P[��C�M�0O?��0v�.��;/�+��C��w�0^��ʔ>ہa�a�.�x�e^zy����%���Q,s��g_j�q���2�B�g�����:/uz�uᕗ���c�Sb[r�8�=#!m�Fψ9��F5�#��o-i3��m��%����ږ�"���Q�~ |l�����Ʒ�_v{�A�/h�F�X��J�Y�|e�̏b X�̨�k7� �`$�~ ��.s��!Ϙ�ɮ���D<�U�h�xBd�սcw~e����y�V[�3�Pa��trn�@c�3��'�<'9Un�q#�H!�����o_a���*f�+=z�v���;'0�Bǚ���q
B��3	�8k�X�*5X��u���`U�&~!�5{L�a41y��_���cf?!ޥ���ƫP��q�If��\q;���c��
#�*@���C�N�"�=��MoH�Dw�`Djw�<��m[|��.��.��g]�!3�Y�k=��6;�]��+\��!��LV���؃�yG*"9������2�����ڒ-�M�"ڙ�ɾ܅0�XϞ⌓��0�VD�scJ~�s��`h����@eΚ�5�&��PS�m�;.M�뢫)�(����,WyJ}B�q"<����`�45�ܹS���fo&$2Yp˱3��?��h��]&q�蒊5���"	�pƢ��=w���H<�
�%IF����6Ķ�#Q�7��9���S���ح�	f5N����x�l��8��t��a���pF���M�ID��'p���M�M(�癷����avd�
}���r�a(?��F�/�TM 7��C��g�5z�/
���&F�cL��@8���A�����O�E�
�`��U0�D� ���zY�$?pny��65���>\���&�D��x\	�r���j!m&oŦ0#TV=�َ�Y�����A_��$����'n���W7`�[�_6�@$NRY�v
n=i�q��G��}a�^:deo��p1��0fߍ�f�V+��mp*�`�IP� &
�
ڸB��|��V㷄v���*�Մ��z�U��t��[�X�b��CiQ�(*�D��6��Pj����d�%C)Q���-E	�N���ax���ax���ax�����}$�IEND�B`�PK
!<a����3chrome/toolkit/res/accessiblecaret-tilt-left@1x.png�PNG


IHDR"$����IDATxԖ�,9�kl�6ƞg۶m۶m۶m۶m�le��NϪ�i#_�[��������Ц��k�>Sq�x
����s)8� ��BL�~�[�
���e�fHl;<�o�6E.�u趢�q���b-� �\S�%�{ "�J��}���;�A� ��dpϺh�o:��s��7��2��Ѥ�	!y��.]AyR�=��X��3ܳ���,����{�J��}��n�̽>�9��,�����)7~$)6�'�q��j^ Jkj���]�>��aϢ;�=n�ij�.��uo�o�#�{�x���.}H�x�ܿ�9���w4�A�	N�mԾ��e�>��?�
��v����qϹ�F��P	0�ȢM`wx0��ié$�;����`I��k�SvE��v02�賖��9��s�Y��$w]��j�	$��ީ�v���,'�X

�O�xl��� �S��8��T:�>[i'B����b���iFc��}�_��.����6r�U�bk�Q�\��׼�4�Q{�@�Q��0c
�V>}�]� ( �a�/q�%
\����.�V>�XGVn��E��פ��;�54}c�
HK�:��f+��X+�A�w��"ɝ���	D��[,�6K��-��Ŀ��k8l�p$`���ڝ�*��7T+�cN)���2fa��"�͈f��{��W�V�s��@3�t�<�R$|Ah�\Ԟ�Ml�v_�V3����A�D7zN�R C��_��n��J��J~/P� ?�>�pD�V�mc��D'��P1ҍf�؏�~kN��2X(Sf������~��9�rU1�=]˰�
�GV�2#�r��h�I�M��R9͎$���w��}�����3(�ba �F'��],@���D��==J�CF2�Q���@��-@9IEND�B`�PK
!<�A<,,6chrome/toolkit/res/accessiblecaret-tilt-left@2.25x.png�PNG


IHDRMQ�+P�IDATx��$�@�M��c���m۶͵m۶mc�YU]H��3~�ͩ��9�`��w��U%�$�����V�Zik���8�I��q=��GT?�O�q\�/8�֭?Lv�,?����Y`"�v�v�P��/���
!����x��lR�f�����{��?�w�ޔ����M����'}�����>!�;s߿w��k�_[=ҚAÀ�<�7az!Jg7��X�(t�&̲�-�k�4���^��Q�J1����6�Jo=�?���w�c#��+�:?�_��r�@1��T�($*����:�fS�6��@tՕ�5�ӏ�2��Tƅ#��N��?���7���f�ޤ��N] “`�[(�w�@��[M�W���D ��k��)�?7�wG���2�@�"�y�?'�%`و��h�
��F��K�Em���A��Z�5�`�s���Q��g^��)-ETFO����~��o�q?`y���NJ/X�O#Xm m�E����Ka��T?��xǥ�$N�>(�g̟�����Ų"��O��+��/x}��Uy������)-=m.�;|��
��5��ME�k
MQy4{�ˀ q������=�����JC�J�ǥ���壷\x������Uǜ�;�i���`�D�P��*~ϒ�Jq 1�bam��u�1q�`��"jB����?1�gf憬�vwZ���z@�du�U��`b��6��t��x�0Ί B��8,+Ż�\h"���䏶���dG�k$S�#�1ʭ������-��S5$՚	�#��!m�G��mo��_%ф~QѠ�����o��ZTI4�!`/m��?��:w3S��xm��8c�e� �5$�h����ݲ�G��À�t�KD�i�4qRo�R�u�D$��ҝ��6TZn�f�
ƐxT�2��nۄtC�Yn���'��f�j�f@��ζd���	�KyNKG��:~�>́��Y��sf5&<��`;���,"�x�Y����J3!���H��Q�h*ՈƬ45�FQ�Q�KSyS4��

��
���k%�H����_j����D�ᾨ8�'�Ks `��*��~i��ý�c�޶���$XD�R�x��7�UC��;�,����-��x���mT86�H8:X���#�?��>���J+?x�-c����m!��6#7_p����J3��P���nE,��"�K4:��t��6~r[�����+����b�Z��K�����Q����Jw]q��^��*V*KT)U.=�5a1Ҁz����ڊG{,7���]�`�#N�]�iam��{.<�05���ک<q�Ҿ3�9L����s��ˏ?-.��2Y����������WX�'��/ԯ-�:��Ԗ=��n�h�07�86���w���D���~߲V���]%�}�E���c��/�9u������=q��Kw����=K�܎I �/S��a����V����7����8�#@>=c�Ϭ�c�������8��֖�,��=}�ӻO�q'T5�T���^H���)���gm;|���
�7S� ����#]�먡+N8X
��K{!Yq������/L��/���2s2"��Q�o�M��`�������v?���ˀ.@�U�L�:�l3�S?�La��|05yVgN�z
�h�+��
���ʁ@0�[�<q�#Cמq����_�Q�0��֤�,�Lv�'n��~83o�m�s7��p��oC,�%֞h�>���u0�:Qi8��Z�WYt���\tY��oV@�Ќ�^����V`bv�V�ě�7r;�̍�KM��fg��X��3q�pO��b�p\�v�+����8�80�*o��K{aئ�,�Z�ra��)�,/���EƯ�¡�!U*@�YWē�9�^���K}R����KsH6��ho`���bM��б� �Q�+��I�&MҤI�&
iҤI��4iiҤI#���!��IEND�B`�PK
!<,�9��3chrome/toolkit/res/accessiblecaret-tilt-left@2x.png�PNG


IHDRDG����IDATx��d=�o�m[e7V�5�m۶m۶m�vk��#{S����^���9���Kr�B�I�D"�Dp �k�%Bt���
�
m�1���i���S�6���`�
L�n�i�������Ǯ�s�@?�,覞��㠛ttSNإ�v��f�>p�2�m}x�X\L?�?�=���c5,�2j�����ِ
�+��?�?��j6ݴSݬ�3���i��ꑻ@��eh�᪠��*��%ͷm�ymY�J��~�tf�X7d�<�+
�7��/~g�w��i����#v��i���8���/�
;��|L��x������SN�{���t��Kl�3��n}F�˓B��ޏ�nS@^�W�(���0낏j����;כ��;�2MK���J�ަ��s8cn�9�:���Q�Y�]W�����$n�kbY�� RB2ò��m�}�C��l)���/�OD���Ҍ;0;s϶1��'�3����甂��J���1|���nyJ�÷��:��1���w��j�殖�)���2�|u!0
$�n�ɀ�	 �
i�^�4�@p��~L�_��w��1$����w�M���+*�2D5|�E��s��@B���#~�l�!p�A@oF��O[	
A��2�.v˳4��T!`p�ú�1	m<lH�S�N ��s�Տ����{���_T��$���{�z�9������c�\�����	$�N�"��<�������0�|���_+���a��=l	����wPg'L�J' o0�d���)�� 
�P��d�,��`җ&�+By⤲�X�e�B��˪��8�F���;
�@�
�O����Ҟ��oͯ��(!���Θ�2�z:���oB��h�+�g�R7��\�ꇌI&X:$8XCy����M9��,s@l��Dt�=�g A�����ce�A �$��e��	
C�v�I ��G�X��_ ^A�IGv2�d��]�kr'w�V߶s��reX뉓x"�ɥam&������ �
n0�+[B�F��^0�Ѩ9�oUK��Y[s�\fBw.�;�(U:���=��T÷����܌�����J�$��-ްOܮ����*پd<�R�6����駟��B	�z<����$R��	VB��n�)Ԗ�w�*�hNg��@d�^f��=��`@bw�"Q���Dc�����OE��yi]��/<1@O�8E�9W+4)*п|�qX��O@i�Os��x�8��6*2�V�6ܟ�ؽN�W�9l�҉_馣�)CC@����}��Xk�����it�<�+�b,D���f�ʯt����0h�rq"��V�mSAW���*} %G�@h��"ݬ��U
�|ي����-�2� ⶿$4��}�w���+���6n���/8[��G'F�t����ь�M5�Xn��]��	(�w-�p.���9���X<�qȸ�f�m�S��PL:��Tx����4f�O
m6j7z�����go�6�w7G�*�)�R�-���%�
k9~�V<3p�I��^K!�M8f�Y~����F�׻A��]K�僊��&sr�I���������
 8�s(���Z�r(��ݶ�! �!D "Q"�D"��@D "�D"��@D "Q=�y���IEND�B`�PK
!<#�G���6chrome/toolkit/res/accessiblecaret-tilt-right@1.5x.png�PNG


IHDR36jl��gIDATx���H���������m۶m۶m���ѳm���鬽�d����KU�us�PJ�3`rK�X�&�}����/.vm��Q�X28���}Q�����ĺ�3b��!���X�|�]��	�-��ؗf��,��eے��7��b]�4�	i2��S�����"$6G�F��g�=µ�*un���<��aۂ��Ev��uP�7]Y|�a����>&��_��t�}y�,GK�%��^@|`��#3�7lid��c���R��Խ�um�J�N?�7��cy�|x�nT϶���"�a��m��mK�g_��ʾ"{'h��Z۲�q���5i��3�Q��N4�4P�%�0�pSצ�ϝ�/S纋(l%��tͻ���/k���l�s�87\Y�uvՠ]�:wQ�Œ�ˆU��Q�$츏’��pMнM��F��֗(�⹃��+^�O��,}�ʉG���K[�B��7�[�1��k�'��؇a�x�
��e��?�[e��i[����v�3����it���Eb1w0R�`_㔣v=��S�gϓ�a�Ɩ��\�`�HT��+����r����B�\Z�	FH��s��R�a ۠�^�����:n`+���|�{�g��L]B�`N`�;N��t�5/08$�4�t���Ht��8~��ݏy�A�j�o��7�U,Zs�Mk[��AȒ���KkΟTFZ%Z_{��5-r�t����l�F�U1N�T���֞?ս��5��k<�\�p�Ǫ8wA�!0�=G�B�w\�x��0[o�T=����Ɩ/�s���.^��U��G�8W�>��ޘ�w���d������]VFYS�M�X�����':��Ϛ1�����<(2F���:i��;'2�`<^ЈN��d
!")�0�Ej��I�5&��?��kjH+ߞ�tFj����ާ�\�؝�*�.�1s�X��`��889u��}�hP��cG0Xz2���g�#��K�qF��!��\�`(���
�*y�����`;���}JY,�)�����W�� צ��(@�6��b|A[OB�����"��5�2�ܚ�|N�� 0&g��c@R�C
֧��
��+��/0�k��Ϩci�u�B��7 H�Y��w�GS7�ˬ]�B����|�I�/��d��Э��X���:g�2�X��}��
Թ�զĮ�'o1O=~
���J�m�`�ɷ^[�~p3����{��"�4
�?�Dn�`�@D$J�������x��3�D�Zt�y�^�?f�>���J[��ũh&̹���)H˸��Jc��VPH���
�3Ǔ����YV0����0���#�0�#�0�#�|�͌}����IEND�B`�PK
!<N?xO��4chrome/toolkit/res/accessiblecaret-tilt-right@1x.png�PNG


IHDR"$����IDATx���HG���>3��m۶m۶m۶m۶��;ﭝ���wj�d~ss�o��SDm9�,c���&�S�wŷ��l�6!��{�Y�E��d�v�_x��@t�%�3�b�?A��Ő�@M�I��L�~mƢ���SN��m2��w�{|�I�6pc���(�Fo$�HL�����Њ��\*��ׂ���>`����ܓ�ouO=3�7��P�������{-_�[pk�w����)�����0�Ҧ-��y� u0?(*�f�Q���01�?�%��<�{{�\��҇8��5�Ϳ����~�w�c��
{*�"mڂŽ����/�O��G�"����1�/}��1roY���!&g��2�j�㜀�`��C���4w(d`���9s�5�pb�5�����_�,}�]�,����e)�UD	"��Rn�8���^�E��V2��V>Ǯ	G��iF(I�/
4��_������-]�Rn�R+fVLj�W ��E��̪/
9�����"f��=�C%X��!g�2"�!���}趋!Y|3+����SH�Қي��^+���#_O�g��K��]�H,g+���;pl_���,��Sl��c��[-����+�'xs��^{k�Y�\��5�ђkAp�1
���{�}�np	�}�m7)u6�"� ��#w_J��;�,�Lw�UތU�R��ˢmE\�^n��[#�׀�6�/aHjKz �+BJ!�ĔH�pO`�2o�nI�&2��	�x��	��]T�L�B&��s��q))�"�1O�A־�O�|���&d����c�aeZN�̅~|&B%BJ$PYnI$]��:N��y��H�aJg��LY>�~&B.�uQ�0q��k���-¢���kD?Z"��_���E��O�@����IEND�B`�PK
!<��
7chrome/toolkit/res/accessiblecaret-tilt-right@2.25x.png�PNG


IHDRMQ�+P�IDATx�t,��k�
7wc'kG�m۶m�׶m۶�X׶�U���<sz��u�?�kJ]��_�I@�4M@���$�2^����S��Ck��W���g�R�bKn��Є#���g`�\���:pw[�$W�����J�t_�^��w���oP%��T�j�u�����gx�mS����B֫�!��&(�n�5b�WC�=o��'�?i�?h�x�|�����G�I���O}us��bͮRHCX�G�?U�ew�M;�!0b'Ch�?��!$V4����g��0����7Xlw�rj>�'��t�)1�_��	O9N� �'Yь�,8f�	���3��~�s����ה����C5su\jBiQ���r���j���+�}6?�����3��?�;�����uo"X����3/@+�1:��5��OC��������H��Yx�	��s��ݾAU�m���PkQ�}+���j}�� \OR����Wo+�e���2��)�<-������P�v
��d[���~aUh�a%4� �����0R�높j��A�.V#C�ۍ:0烞�ߎ��!��*�Ј[��m�&������\`���w=������lw�
d�B���?��h����]*@Sx!z,���Ÿ'?a̴s
��<G�y��U���e���T�y�F��q��1���e�igX`X�(����#4CƋ�|�K�TeZ�#��� �ޠI��<t��g����8ʲXv5�y���8k��{�ae�Ƌ(t؛O{G��4s���iَ�H>�0l��v4�я.��)6��v�
E���aϺ��H�����h9s��"��'X�{�^�Z��Ōi<C#Q-��yUL�J�h)���C#
k�	�ԇ?z�h�f��ESO1!
�L�r���i�Rޡ�(Ç&�J����=-�\�JQS_4�<K����T�fH�wy�m9Bcޡ��f�c�/���Rx�����ޓW��b��K�ʶ,��
�@�~�ӫ8<��r��=4~_h.ڴ�8Y��=��G�e�-0j7���lv;1�ƫM��a�����X�U82
icmj@��B�]І�՘���-UO�~���ѻ�0'w�
CA�%^O���gtz��&�>�Ws=v�1TI)��4
 ���{Zg�S�ִxI'��=i��2o��'����� ��AҨV��ڕ��~�f�=����]��~d�A3��6��hHx�Gp��v5�6����_���=�0��
��Pc4[U�F�O�(��V����I����UB*}M��b�n�VE-
�Y��
�]���e\@#3d�nǸv�袖��ۘ,땖}��
4I�7�?�}OJ��t��n���WQ�
42]���h+Ó�qtY��4ٰ���W���Ҵ���x���\@���O�O�d��8�F����G=B��ʵ[=��aq���s=<Cӣ|�/5�|�n�Tɨ������Q�u{(��CQ�km&����
�_F����m�,�2ZC�A�Д'I��'�B���R$@�%�Ύ�ˍٮ'#�_�&hdT���'��V��̅i��$��eH��|�c:�}�VzX4A#�P�<�s���G4��;��#rz�� ����+�p$�\fD#4��%b��"���ũ�*�?I�\x�1�q�?�~J�)�t�#i��e�ތg�$'�?9(�MiFT&꒸��>�z��lg�y��Ȟ!�sqڷ�	+�P��,��ųY��@���O�O����Ĕ������
��7*���Y�J��o��{{P�����b�ZG���X_պ;/���tu�/S�f��ni꣟LL��6fG��P���?�ߴ�:	���K�t�>)�6CZ�}�L��rr�]�����O�"�c���o	��$b�HF�ƣ'�)<PO�����&$�	h��&�		h��&�	hB��&�	h���&�	h��&$�	h�w�a�j���IEND�B`�PK
!<�?���4chrome/toolkit/res/accessiblecaret-tilt-right@2x.png�PNG


IHDRDG����IDATx��#]�o�V8�M&�8�JZ�߶5�m۶m۶m۶Y����Z���?���ƪ'��-��@�d�W 2�D�Y�n��➁[��f|d�?A�o��� ���IDr��z�!�1G�w�N��-W6���3x;��M|��أ$0�8	L8IO�;�����OJ��?|E���I��GL?�"Z�oHJ����ey��L �o���so5���D�A�=�Hw6����$4���248�6�M�ȇ��}�6��XK�6?[+vu�R�$g�r���~Δ�ŏx����s<��{�o�I֪��Da����fv�	w�
��qܢ��6��}�*�90�Xy�� ��W��9\�}#0�xL���$�y����UC�=�f\·��<7�"���9n���_ÿ|+`7�����x�8�L:ͧM>��n����-����|�������ZK
1~W�h�lQF�؜]�M_��ۚ�(mZ.�?�Ju>_�쟄�����W�u��u;�zIn:����j�ҭ=`!���'8�&�օ��������w��{�����g^�̬����r\�Q�E�]��G�	�Pgs�d.y�V�ϛL�i
�2�?/��B��⎵j�X��V��)8� �H����E���}ސ��K����Kϐ9�g^�����Z���@H��|��=n�eI�P�M_x�wv^4J���:���{���H
�uM�-�Z*�
=�� @ Z�F�_n��ID�T�Ь]`�r���/�~#
�Ɗ&�:3@ �X<��a�U| �7\���nq3�0YU��P��@L?�|5�}�E�B3E�f}���@����ξ�� ���P�	��3F��ҝ
q3��[F���ZQ���B/��2h�B���^T IM�NA�g�e�3x�Z�Β(*��ff��gʁ
��&jsgo4z�.3`�r�^-����?�+���Ptv]2U�Ӊ�2���7Ɨ���L������
�Z)*����8�1�C�
�%O���O+�^�)��.��E/�T��s/ґ����ӹY��2�ƉxG�%k�qF�@Tq���d�˜gbH���%o��|+�D������+� �2�{��:�5�B���pD՜��F��Aw�?��&�Q\��-�,O_�L2 ��窃��4ɁP��{��N�]�ð�)*���4~<���2!$�	 �0�&�E�˜��q�Т��?L?��C))YB�l��r�М���6pY%��K��ci�
� �%d7~[�7��qp���[��{�-�0�	!N��`	55���^��H��B�b�u��B��
������T�OqWk@�P�zJ�����C��X������i���H�N�߫��&j�0
D�>:(K�q�hǏ����t��K���>���;�/?�U1�A��*\�ZdRh��!u�V��;|�EڢS84M�!���4P�>����y(t뭊������W�L�����p���@�[Lސ�~�v*�]��V��"×�FǤ��1"�+Kɀ�p"�W��TP8�� N��**�ED�C���V���Ư�ߏ5(,O���B%"�?"��Ȓ��@d 2�D"���@d 2�D"���#԰y���GIEND�B`�PK
!<Y�:�ll4chrome/toolkit/res/autofill/FormAutofill.ios.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs";

FormAutofill.defineLogGetter = (_scope, _logPrefix) => ({
  // TODO: Bug 1828405. Explore how logging should be handled.
  // Maybe it makes more sense to do it on swift side and have JS just send messages.
  info: () => {},
  error: () => {},
  warn: () => {},
  debug: () => {},
});

export { FormAutofill };
export default FormAutofill;
PK
!<޶�t((0chrome/toolkit/res/autofill/FormAutofill.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { Region } from "resource://gre/modules/Region.sys.mjs";
import { AddressMetaDataLoader } from "resource://gre/modules/shared/AddressMetaDataLoader.sys.mjs";

const AUTOFILL_ADDRESSES_AVAILABLE_PREF =
  "extensions.formautofill.addresses.supported";
// This pref should be refactored after the migration of the old bool pref
const AUTOFILL_CREDITCARDS_AVAILABLE_PREF =
  "extensions.formautofill.creditCards.supported";
const BROWSER_SEARCH_REGION_PREF = "browser.search.region";
const CREDITCARDS_AUTOFILL_SUPPORTED_COUNTRIES_PREF =
  "extensions.formautofill.creditCards.supportedCountries";
const ENABLED_AUTOFILL_ADDRESSES_PREF =
  "extensions.formautofill.addresses.enabled";
const ENABLED_AUTOFILL_ADDRESSES_CAPTURE_PREF =
  "extensions.formautofill.addresses.capture.enabled";
const ENABLED_AUTOFILL_ADDRESSES_CAPTURE_REQUIRED_FIELDS_PREF =
  "extensions.formautofill.addresses.capture.requiredFields";
const ENABLED_AUTOFILL_ADDRESSES_SUPPORTED_COUNTRIES_PREF =
  "extensions.formautofill.addresses.supportedCountries";
const ENABLED_AUTOFILL_CREDITCARDS_PREF =
  "extensions.formautofill.creditCards.enabled";
const AUTOFILL_CREDITCARDS_REAUTH_PREF =
  "extensions.formautofill.creditCards.reauth.optout";
const AUTOFILL_CREDITCARDS_HIDE_UI_PREF =
  "extensions.formautofill.creditCards.hideui";
const FORM_AUTOFILL_SUPPORT_RTL_PREF = "extensions.formautofill.supportRTL";
const AUTOFILL_CREDITCARDS_AUTOCOMPLETE_OFF_PREF =
  "extensions.formautofill.creditCards.ignoreAutocompleteOff";
const AUTOFILL_ADDRESSES_AUTOCOMPLETE_OFF_PREF =
  "extensions.formautofill.addresses.ignoreAutocompleteOff";
const ENABLED_AUTOFILL_CAPTURE_ON_FORM_REMOVAL_PREF =
  "extensions.formautofill.heuristics.captureOnFormRemoval";
const ENABLED_AUTOFILL_CAPTURE_ON_PAGE_NAVIGATION_PREF =
  "extensions.formautofill.heuristics.captureOnPageNavigation";

export const FormAutofill = {
  ENABLED_AUTOFILL_ADDRESSES_PREF,
  ENABLED_AUTOFILL_ADDRESSES_CAPTURE_PREF,
  ENABLED_AUTOFILL_CAPTURE_ON_FORM_REMOVAL_PREF,
  ENABLED_AUTOFILL_CAPTURE_ON_PAGE_NAVIGATION_PREF,
  ENABLED_AUTOFILL_CREDITCARDS_PREF,
  AUTOFILL_CREDITCARDS_REAUTH_PREF,
  AUTOFILL_CREDITCARDS_AUTOCOMPLETE_OFF_PREF,
  AUTOFILL_ADDRESSES_AUTOCOMPLETE_OFF_PREF,

  _region: null,

  get DEFAULT_REGION() {
    return this._region || Region.home || "US";
  },

  set DEFAULT_REGION(region) {
    this._region = region;
  },

  /**
   * Determines if an autofill feature should be enabled based on the "available"
   * and "supportedCountries" parameters.
   *
   * @param {string} available Available can be one of the following: "on", "detect", "off".
   * "on" forces the particular Form Autofill feature on, while "detect" utilizes the supported countries
   * to see if the feature should be available.
   * @param {string[]} supportedCountries
   * @returns {boolean} `true` if autofill feature is supported in the current browser search region
   */
  _isSupportedRegion(available, supportedCountries) {
    if (available == "on") {
      return true;
    } else if (available == "detect") {
      if (!FormAutofill.supportRTL && Services.locale.isAppLocaleRTL) {
        return false;
      }

      return supportedCountries.includes(FormAutofill.browserSearchRegion);
    }
    return false;
  },
  isAutofillAddressesAvailableInCountry(country) {
    return FormAutofill._addressAutofillSupportedCountries.includes(
      country.toUpperCase()
    );
  },
  get isAutofillEnabled() {
    return this.isAutofillAddressesEnabled || this.isAutofillCreditCardsEnabled;
  },
  /**
   * Determines if the credit card autofill feature is available to use in the browser.
   * If the feature is not available, then there are no user facing ways to enable it.
   *
   * @returns {boolean} `true` if credit card autofill is available
   */
  get isAutofillCreditCardsAvailable() {
    return this._isSupportedRegion(
      FormAutofill._isAutofillCreditCardsAvailable,
      FormAutofill._creditCardAutofillSupportedCountries
    );
  },
  /**
   * Determines if the address autofill feature is available to use in the browser.
   * If the feature is not available, then there are no user facing ways to enable it.
   * Two conditions must be met for the autofill feature to be considered available:
   *   1. Address autofill support is confirmed when:
   *      - `extensions.formautofill.addresses.supported` is set to `on`.
   *      - The user is located in a region supported by the feature
   *        (`extensions.formautofill.creditCards.supportedCountries`).
   *   2. Address autofill is enabled through a Nimbus experiment:
   *      - The experiment pref `extensions.formautofill.addresses.experiments.enabled` is set to true.
   *
   * @returns {boolean} `true` if address autofill is available
   */
  get isAutofillAddressesAvailable() {
    const isUserInSupportedRegion = this._isSupportedRegion(
      FormAutofill._isAutofillAddressesAvailable,
      FormAutofill._addressAutofillSupportedCountries
    );
    return (
      isUserInSupportedRegion ||
      FormAutofill._isAutofillAddressesAvailableInExperiment
    );
  },
  /**
   * Determines if the user has enabled or disabled credit card autofill.
   *
   * @returns {boolean} `true` if credit card autofill is enabled
   */
  get isAutofillCreditCardsEnabled() {
    return (
      this.isAutofillCreditCardsAvailable &&
      FormAutofill._isAutofillCreditCardsEnabled
    );
  },
  /**
   * Determines if credit card autofill is locked by policy.
   *
   * @returns {boolean} `true` if credit card autofill is locked
   */
  get isAutofillCreditCardsLocked() {
    return Services.prefs.prefIsLocked(ENABLED_AUTOFILL_CREDITCARDS_PREF);
  },
  /**
   * Determines if the user has enabled or disabled address autofill.
   *
   * @returns {boolean} `true` if address autofill is enabled
   */
  get isAutofillAddressesEnabled() {
    return (
      this.isAutofillAddressesAvailable &&
      FormAutofill._isAutofillAddressesEnabled
    );
  },
  /**
   * Determines if address autofill is locked by policy.
   *
   * @returns {boolean} `true` if address autofill is locked
   */
  get isAutofillAddressesLocked() {
    return Services.prefs.prefIsLocked(ENABLED_AUTOFILL_ADDRESSES_PREF);
  },

  defineLogGetter(scope, logPrefix) {
    // A logging helper for debug logging to avoid creating Console objects
    // or triggering expensive JS -> C++ calls when debug logging is not
    // enabled.
    //
    // Console objects, even natively-implemented ones, can consume a lot of
    // memory, and since this code may run in every content process, that
    // memory can add up quickly. And, even when debug-level messages are
    // being ignored, console.debug() calls can be expensive.
    //
    // This helper avoids both of those problems by never touching the
    // console object unless debug logging is enabled.
    scope.debug = function debug() {
      if (FormAutofill.logLevel.toLowerCase() == "debug") {
        this.log.debug(...arguments);
      }
    };

    let { ConsoleAPI } = ChromeUtils.importESModule(
      "resource://gre/modules/Console.sys.mjs"
    );
    return new ConsoleAPI({
      maxLogLevelPref: "extensions.formautofill.loglevel",
      prefix: logPrefix,
    });
  },
};

// TODO: Bug 1747284. Use Region.home instead of reading "browser.serach.region"
// by default. However, Region.home doesn't observe preference change at this point,
// we should also fix that issue.
XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofill,
  "browserSearchRegion",
  BROWSER_SEARCH_REGION_PREF,
  FormAutofill.DEFAULT_REGION
);
XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofill,
  "logLevel",
  "extensions.formautofill.loglevel",
  "Warn"
);

XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofill,
  "_isAutofillAddressesAvailable",
  AUTOFILL_ADDRESSES_AVAILABLE_PREF
);
XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofill,
  "_isAutofillAddressesEnabled",
  ENABLED_AUTOFILL_ADDRESSES_PREF
);
XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofill,
  "isAutofillAddressesCaptureEnabled",
  ENABLED_AUTOFILL_ADDRESSES_CAPTURE_PREF
);
XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofill,
  "_isAutofillCreditCardsAvailable",
  AUTOFILL_CREDITCARDS_AVAILABLE_PREF
);
XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofill,
  "_isAutofillCreditCardsEnabled",
  ENABLED_AUTOFILL_CREDITCARDS_PREF
);
XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofill,
  "isAutofillCreditCardsHideUI",
  AUTOFILL_CREDITCARDS_HIDE_UI_PREF
);
XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofill,
  "_addressAutofillSupportedCountries",
  ENABLED_AUTOFILL_ADDRESSES_SUPPORTED_COUNTRIES_PREF,
  null,
  val => val.split(",")
);
XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofill,
  "_creditCardAutofillSupportedCountries",
  CREDITCARDS_AUTOFILL_SUPPORTED_COUNTRIES_PREF,
  null,
  null,
  val => val.split(",")
);
XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofill,
  "supportRTL",
  FORM_AUTOFILL_SUPPORT_RTL_PREF
);
XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofill,
  "creditCardsAutocompleteOff",
  AUTOFILL_CREDITCARDS_AUTOCOMPLETE_OFF_PREF
);
XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofill,
  "addressesAutocompleteOff",
  AUTOFILL_ADDRESSES_AUTOCOMPLETE_OFF_PREF
);
XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofill,
  "captureOnFormRemoval",
  ENABLED_AUTOFILL_CAPTURE_ON_FORM_REMOVAL_PREF
);
XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofill,
  "captureOnPageNavigation",
  ENABLED_AUTOFILL_CAPTURE_ON_PAGE_NAVIGATION_PREF
);
XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofill,
  "addressCaptureRequiredFields",
  ENABLED_AUTOFILL_ADDRESSES_CAPTURE_REQUIRED_FIELDS_PREF,
  null,
  null,
  val => val?.split(",").filter(v => !!v)
);

XPCOMUtils.defineLazyPreferenceGetter(
  FormAutofill,
  "_isAutofillAddressesAvailableInExperiment",
  "extensions.formautofill.addresses.experiments.enabled"
);

ChromeUtils.defineLazyGetter(FormAutofill, "countries", () =>
  AddressMetaDataLoader.getCountries()
);
PK
!<5�RXGG9chrome/toolkit/res/autofill/FormAutofillChild.ios.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* eslint-disable no-undef,mozilla/balanced-listeners */
import { AddressRecord } from "resource://gre/modules/shared/AddressRecord.sys.mjs";
import { FormAutofillUtils } from "resource://gre/modules/shared/FormAutofillUtils.sys.mjs";
import { FormStateManager } from "resource://gre/modules/shared/FormStateManager.sys.mjs";
import { CreditCardRecord } from "resource://gre/modules/shared/CreditCardRecord.sys.mjs";
import {
  FormAutofillAddressSection,
  FormAutofillCreditCardSection,
  FormAutofillSection,
} from "resource://gre/modules/shared/FormAutofillSection.sys.mjs";

export class FormAutofillChild {
  /**
   * Creates an instance of FormAutofillChild.
   *
   * @param {object} callbacks - An object containing callback functions.
   * @param {object} callbacks.address - Callbacks related to addresses.
   * @param {Function} callbacks.address.autofill - Function called to autofill address fields.
   * @param {Function} callbacks.address.submit - Function called on address form submission.
   * @param {object} callbacks.creditCard - Callbacks related to credit cards.
   * @param {Function} callbacks.creditCard.autofill - Function called to autofill credit card fields.
   * @param {Function} callbacks.creditCard.submit - Function called on credit card form submission.
   */
  constructor(callbacks) {
    this.onFocusIn = this.onFocusIn.bind(this);
    this.onSubmit = this.onSubmit.bind(this);

    this.callbacks = callbacks;

    this.fieldDetailsManager = new FormStateManager();

    try {
      document.addEventListener("focusin", this.onFocusIn);
      document.addEventListener("submit", this.onSubmit);
    } catch {
      // We don't have `document` when running in xpcshell-test
    }
  }

  transformToFieldNamesWithValues(details) {
    return details?.reduce(
      (acc, field) => ({
        ...acc,
        [field.fieldName]: field.element.value,
      }),
      {}
    );
  }

  _doIdentifyAutofillFields(element) {
    if (this.#focusedElement == element) {
      return;
    }
    this.#focusedElement = element;

    if (!FormAutofillUtils.isCreditCardOrAddressFieldType(element)) {
      return;
    }

    // Find the autofill handler for this form and identify all the fields.
    const { handler, newFieldsIdentified } =
      this.fieldDetailsManager.identifyAutofillFields(element);

    // If we found newly identified fields, run section classification heuristic
    if (newFieldsIdentified) {
      this.#sections = FormAutofillSection.classifySections(
        handler.fieldDetails
      );
    }
  }

  #focusedElement = null;

  // This is a cache contains the classified section for the active form.
  #sections = null;

  get activeSection() {
    const elementId = this.activeFieldDetail?.elementId;
    return this.#sections?.find(section =>
      section.getFieldDetailByElementId(elementId)
    );
  }

  // active field detail only exists if we identified its field name
  get activeFieldDetail() {
    return this.activeHandler?.getFieldDetailByElement(this.#focusedElement);
  }

  get activeHandler() {
    return this.fieldDetailsManager.getFormHandler(this.#focusedElement);
  }

  onFocusIn(evt) {
    const element = evt.target;

    this._doIdentifyAutofillFields(element);

    // Only ping swift if current field is either a cc or address field
    if (!this.activeFieldDetail) {
      return;
    }

    const fieldNamesWithValues = this.transformToFieldNamesWithValues(
      this.activeSection.fieldDetails
    );

    if (FormAutofillUtils.isAddressField(this.activeFieldDetail.fieldName)) {
      this.callbacks.address.autofill(fieldNamesWithValues);
    } else if (
      FormAutofillUtils.isCreditCardField(this.activeFieldDetail.fieldName)
    ) {
      // Normalize record format so we always get a consistent
      // credit card record format: {cc-number, cc-name, cc-exp-month, cc-exp-year}
      CreditCardRecord.normalizeFields(fieldNamesWithValues);
      this.callbacks.creditCard.autofill(fieldNamesWithValues);
    }
  }

  onSubmit(_event) {
    if (!this.activeHandler) {
      return;
    }

    // Get filled value for the form
    const formFilledData = this.activeHandler.collectFormFilledData();

    // Should reference `_onFormSubmit` in `FormAutofillParent.sys.mjs`
    const creditCard = [];

    for (const section of this.#sections) {
      const secRecord = section.createRecord(formFilledData);
      if (!secRecord) {
        continue;
      }

      if (section instanceof FormAutofillAddressSection) {
        // TODO(FXSP-133 Phase 3): Support address capture
        // this.callbacks.address.submit();
        continue;
      } else if (section instanceof FormAutofillCreditCardSection) {
        creditCard.push(secRecord);
      } else {
        throw new Error("Unknown section type");
      }
    }

    if (creditCard.length) {
      // Normalize record format so we always get a consistent
      // credit card record format: {cc-number, cc-name, cc-exp-month, cc-exp-year}
      const creditCardRecords = creditCard.map(entry => {
        CreditCardRecord.normalizeFields(entry.record);
        return entry.record;
      });
      this.callbacks.creditCard.submit(creditCardRecords);
    }
  }

  fillFormFields(payload) {
    // In iOS, we have access only to valid fields (https://github.com/mozilla/application-services/blob/9054db4bb5031881550ceab3448665ef6499a706/components/autofill/src/autofill.udl#L59-L76) for an address;
    // all additional data must be computed. On Desktop, computed fields are handled in FormAutofillStorageBase.sys.mjs at the time of saving. Ideally, we should centralize
    // all transformations, computations, and normalization processes within AddressRecord.sys.mjs to maintain a unified implementation across both platforms.
    // This will be addressed in FXCM-810, aiming to simplify our data representation for both credit cards and addresses.

    if (FormAutofillUtils.isAddressField(this.activeFieldDetail?.fieldName)) {
      AddressRecord.computeFields(payload);
    }

    this.activeHandler.fillFields(
      FormAutofillUtils.getElementIdentifier(this.#focusedElement),
      this.activeSection.fieldDetails.map(f => f.elementId),
      payload
    );
  }
}

export default FormAutofillChild;
PK
!<����I�I5chrome/toolkit/res/autofill/FormAutofillChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddressResult: "resource://autofill/ProfileAutoCompleteResult.sys.mjs",
  AutofillTelemetry: "resource://gre/modules/shared/AutofillTelemetry.sys.mjs",
  CreditCardResult: "resource://autofill/ProfileAutoCompleteResult.sys.mjs",
  GenericAutocompleteItem: "resource://gre/modules/FillHelpers.sys.mjs",
  InsecurePasswordUtils: "resource://gre/modules/InsecurePasswordUtils.sys.mjs",
  FormAutofill: "resource://autofill/FormAutofill.sys.mjs",
  FormAutofillContent: "resource://autofill/FormAutofillContent.sys.mjs",
  FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
  FormScenarios: "resource://gre/modules/FormScenarios.sys.mjs",
  FormStateManager: "resource://gre/modules/shared/FormStateManager.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
  FORM_SUBMISSION_REASON: "resource://gre/actors/FormHandlerChild.sys.mjs",
});

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "DELEGATE_AUTOCOMPLETE",
  "toolkit.autocomplete.delegate",
  false
);

/**
 * Handles content's interactions for the frame.
 */
export class FormAutofillChild extends JSWindowActorChild {
  constructor() {
    super();

    this.log = lazy.FormAutofill.defineLogGetter(this, "FormAutofillChild");
    this.debug("init");

    this._nextHandleElements = [];
    this._hasDOMContentLoadedHandler = false;

    this._hasRegisteredPageHide = new Set();
    /**
     * @type {FormAutofillFieldDetailsManager} handling state management of current forms and handlers.
     */
    this._fieldDetailsManager = new lazy.FormStateManager(
      this.onFilledModified.bind(this)
    );

    /**
     * Tracks whether the last form submission was triggered by a form submit event,
     * if so we'll ignore the page navigation that follows
     */
    this.isFollowingSubmitEvent = false;
  }

  /**
   * Identifies and marks each autofill field
   */
  identifyAutofillFields() {
    if (
      lazy.DELEGATE_AUTOCOMPLETE ||
      !lazy.FormAutofillContent.savedFieldNames
    ) {
      this.debug("identifyAutofillFields: savedFieldNames are not known yet");

      // Init can be asynchronous because we don't need anything from the parent
      // at this point.
      this.sendAsyncMessage("FormAutofill:InitStorage");
    }

    for (const element of this._nextHandleElements) {
      this.debug(
        `identifyAutofillFields: ${element.ownerDocument.location?.hostname}`
      );

      const { handler, newFieldsIdentified } =
        this._fieldDetailsManager.identifyAutofillFields(element);

      // Bail out if there is nothing changed since last time we identified this element
      // or there is no interested fields.
      if (!newFieldsIdentified || !handler.fieldDetails.length) {
        continue;
      }

      const fieldDetails = handler.fieldDetails;
      // Inform the autocomplete controller these fields are autofillable.

      // Bug 1905040. This is only a temporarily workaround for now to skip marking address fields
      // autocompletable whenever we detect an address field. We only mark address field when
      // it is a valid address section (This is done in the parent)
      const fields = new Set(
        fieldDetails
          .map(f => f.fieldName)
          .filter(fieldName => lazy.FormAutofillUtils.isAddressField(fieldName))
      );
      const validAddressSection =
        fields.size >= lazy.FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD;

      for (const fieldDetail of fieldDetails) {
        if (
          !validAddressSection &&
          lazy.FormAutofillUtils.isAddressField(fieldDetail.fieldName)
        ) {
          continue;
        }
        this.#markAsAutofillField(fieldDetail.element);
      }

      // Notify the parent when we detect autofillable fields.
      this.sendAsyncMessage(
        "FormAutofill:FieldsDetected",
        fieldDetails.map(detail => detail.toVanillaObject())
      );

      this.manager
        .getActor("FormHandler")
        .registerFormSubmissionInterest(this, {
          includesFormRemoval: lazy.FormAutofill.captureOnFormRemoval,
          includesPageNavigation: lazy.FormAutofill.captureOnPageNavigation,
        });

      // TODO (Bug 1901486): Integrate pagehide to FormHandler.
      if (!this._hasRegisteredPageHide.has(handler)) {
        this.registerPageHide(handler);
        this._hasRegisteredPageHide.add(true);
      }
    }

    if (
      this._nextHandleElements.includes(lazy.FormAutofillContent.focusedInput)
    ) {
      this.#showCreditCardPopupIfEmpty(lazy.FormAutofillContent.focusedInput);
    }

    this._nextHandleElements = [];

    // This is for testing purpose only which sends a notification to indicate that the
    // form has been identified, and ready to open popup.
    this.sendAsyncMessage("FormAutofill:FieldsIdentified");
  }

  #showCreditCardPopupIfEmpty(element) {
    if (element.value?.length !== 0) {
      this.debug(`Not opening popup because field is not empty.`);
      return;
    }

    const handler = this._fieldDetailsManager.getFormHandler(element);
    const fieldName =
      handler?.getFieldDetailByElement(element)?.fieldName ?? "";
    if (fieldName.startsWith("cc-") || AppConstants.platform === "android") {
      lazy.FormAutofillContent.showPopup();
    }
  }

  /**
   * We received a form-submission-detected event because
   * the page was navigated.
   */
  onPageNavigation() {
    if (!lazy.FormAutofill.captureOnPageNavigation) {
      return;
    }

    if (this.isFollowingSubmitEvent) {
      // The next page navigation should be handled as form submission again
      this.isFollowingSubmitEvent = false;
      return;
    }
    let weakIdentifiedForms = ChromeUtils.nondeterministicGetWeakMapKeys(
      this._fieldDetailsManager._formsDetails
    );
    const formSubmissionReason = lazy.FORM_SUBMISSION_REASON.PAGE_NAVIGATION;

    for (const form of weakIdentifiedForms) {
      // Disconnected forms are captured by the form removal heuristic
      if (!form.isConnected) {
        continue;
      }
      this.formSubmitted(form, formSubmissionReason);
    }
  }

  /**
   * We received a form-submission-detected event because
   * a form was removed from the DOM after a successful
   * xhr/fetch request
   *
   * @param {Event} form form to be submitted
   */
  onFormRemoval(form) {
    if (!lazy.FormAutofill.captureOnFormRemoval) {
      return;
    }

    const formSubmissionReason =
      lazy.FORM_SUBMISSION_REASON.FORM_REMOVAL_AFTER_FETCH;
    this.formSubmitted(form, formSubmissionReason);
    this.manager.getActor("FormHandler").unregisterFormRemovalInterest(this);
  }

  registerPageHide(handler) {
    // Check whether the section is in an <iframe>; and, if so,
    // watch for the <iframe> to pagehide.
    if (this.browsingContext != this.browsingContext.top) {
      this.debug(
        "Address/Credit card form is in an iframe -- watching for pagehide",
        handler.fieldDetails
      );
      handler.window.addEventListener(
        "pagehide",
        () => {
          this.debug("Credit card subframe is pagehiding", handler.form);

          const reason = lazy.FORM_SUBMISSION_REASON.IFRAME_PAGEHIDE;
          this.formSubmitted(handler.form, reason, handler);
          this._hasRegisteredPageHide.delete(handler);
        },
        { once: true }
      );
    }
  }

  shouldIgnoreFormAutofillEvent(event) {
    let nodePrincipal = event.target.nodePrincipal;
    return nodePrincipal.isSystemPrincipal || nodePrincipal.schemeIs("about");
  }

  handleEvent(evt) {
    if (!evt.isTrusted) {
      return;
    }
    if (this.shouldIgnoreFormAutofillEvent(evt)) {
      return;
    }

    if (!this.windowContext) {
      // !this.windowContext must not be null, because we need the
      // windowContext and/or docShell to (un)register form submission listeners
      return;
    }

    switch (evt.type) {
      case "focusin": {
        if (lazy.FormAutofill.isAutofillEnabled) {
          this.onFocusIn(evt);
        }
        break;
      }
      case "form-submission-detected": {
        if (lazy.FormAutofill.isAutofillEnabled) {
          const formElement = evt.detail.form;
          const formSubmissionReason = evt.detail.reason;
          this.onFormSubmission(formElement, formSubmissionReason);
        }
        break;
      }

      default: {
        throw new Error("Unexpected event type");
      }
    }
  }

  onFocusIn(evt) {
    const element = evt.target;
    if (!lazy.FormAutofillUtils.isCreditCardOrAddressFieldType(element)) {
      return;
    }

    this.#showCreditCardPopupIfEmpty(element);

    this._nextHandleElements.push(element);
    const doc = element.ownerDocument;
    if (doc.readyState === "loading") {
      // For auto-focused input, we might receive focus event before document becomes ready.
      // When this happens, run field identification after receiving `DOMContentLoaded` event
      if (!this._hasDOMContentLoadedHandler) {
        this._hasDOMContentLoadedHandler = true;
        doc.addEventListener(
          "DOMContentLoaded",
          () => this.identifyAutofillFields(),
          { once: true }
        );
      }
      return;
    }

    this.identifyAutofillFields();
  }

  /**
   * Handle form-submission-detected event (dispatched by FormHandlerChild)
   *
   * Depending on the heuristic that detected the form submission,
   * the form that is submitted is retrieved differently
   *
   * @param {HTMLFormElement} form that is being submitted
   * @param {string} reason heuristic that detected the form submission
   *                        (see FormHandlerChild.FORM_SUBMISSION_REASON)
   */
  onFormSubmission(form, reason) {
    switch (reason) {
      case lazy.FORM_SUBMISSION_REASON.PAGE_NAVIGATION:
        this.onPageNavigation();
        break;
      case lazy.FORM_SUBMISSION_REASON.FORM_SUBMIT_EVENT:
        this.formSubmitted(form, reason);
        break;
      case lazy.FORM_SUBMISSION_REASON.FORM_REMOVAL_AFTER_FETCH:
        this.onFormRemoval(form);
        break;
    }
  }

  async receiveMessage(message) {
    if (!lazy.FormAutofill.isAutofillEnabled) {
      return false;
    }

    switch (message.name) {
      case "FormAutofill:FillFields": {
        const { focusedElementId, ids, profile } = message.data;
        const result = await this.fillFields(focusedElementId, ids, profile);

        // Return the autofilled result to the parent. The result
        // is used by both tests and telemetry.
        return result;
      }
      case "FormAutofill:ClearFilledFields": {
        const ids = message.data;
        const handler = this.#getHandlerByElementId(ids[0]);
        handler?.clearFilledFields(ids);
        break;
      }
      case "FormAutofill:PreviewFields": {
        const { ids, profile } = message.data;
        const handler = this.#getHandlerByElementId(ids[0]);
        handler?.previewFields(ids, profile);
        break;
      }
      case "FormAutofill:ClearPreviewedFields": {
        const { ids } = message.data;
        const handler = this.#getHandlerByElementId(ids[0]);
        handler?.clearPreviewedFields(ids);
        break;
      }
    }
    return true;
  }

  /**
   * Handle a form submission and early return when:
   * 1. In private browsing mode.
   * 2. Could not map any autofill handler by form element.
   * 3. Number of filled fields is less than autofill threshold
   *
   * @param {HTMLElement} formElement Root element which receives submit event.
   * @param {string} formSubmissionReason Reason for invoking the form submission
   *                 (see options for FORM_SUBMISSION_REASON in FormAutofillUtils))
   * @param {object} handler FormAutofillHander, if known by caller
   */
  formSubmitted(formElement, formSubmissionReason, handler = undefined) {
    this.debug(`Handling form submission - infered by ${formSubmissionReason}`);

    lazy.AutofillTelemetry.recordFormSubmissionHeuristicCount(
      formSubmissionReason
    );

    if (!lazy.FormAutofill.isAutofillEnabled) {
      this.debug("Form Autofill is disabled");
      return;
    }

    // The `domWin` truthiness test is used by unit tests to bypass this check.
    const domWin = formElement.ownerGlobal;
    if (domWin && lazy.PrivateBrowsingUtils.isContentWindowPrivate(domWin)) {
      this.debug("Ignoring submission in a private window");
      return;
    }

    handler = handler || this._fieldDetailsManager.getFormHandler(formElement);
    if (!handler) {
      this.debug("Form element could not map to an existing handler");
      return;
    }

    const formFilledData = handler.collectFormFilledData();
    if (!formFilledData) {
      this.debug("Form handler could not obtain filled data");
      return;
    }

    // After a form submit event follows (most likely) a page navigation, so we set this flag
    // to not handle the following one as form submission in order to avoid re-submitting the same form.
    // Ideally, we should keep a record of the last submitted form details and based on that we
    // should decide if we want to submit a form (bug 1895437)
    this.isFollowingSubmitEvent = true;

    this.sendAsyncMessage("FormAutofill:OnFormSubmit", {
      rootElementId: handler.rootElementId,
      formFilledData,
    });
  }

  /**
   * This is called by FormAutofillHandler
   */
  onFilledModified(fieldDetail, previousState, newState) {
    const element = fieldDetail.element;
    if (HTMLInputElement.isInstance(element)) {
      // If the user manually blanks a credit card field, then
      // we want the popup to be activated.
      if (
        lazy.FormAutofillUtils.isCreditCardField(fieldDetail.fieldName) &&
        element.value === ""
      ) {
        lazy.FormAutofillContent.showPopup();
      }
    }

    if (
      previousState == lazy.FormAutofillUtils.FIELD_STATES.AUTO_FILLED &&
      newState == lazy.FormAutofillUtils.FIELD_STATES.NORMAL
    ) {
      this.sendAsyncMessage(
        "FormAutofill:FieldFilledModified",
        fieldDetail.elementId
      );
    }
  }

  async fillFields(focusedElementId, elementIds, profile) {
    let result = new Map();
    try {
      Services.obs.notifyObservers(null, "autofill-fill-starting");
      const handler = this.#getHandlerByElementId(elementIds[0]);
      await handler.fillFields(focusedElementId, elementIds, profile);

      // Return the autofilled result to the parent. The result
      // is used by both tests and telemetry.
      result = handler.collectFormFilledData();

      Services.obs.notifyObservers(null, "autofill-fill-complete");
    } catch {}

    return result;
  }

  #markAsAutofillField(element) {
    // Since Form Autofill popup is only for input element, any non-Input
    // element should be excluded here.
    if (!HTMLInputElement.isInstance(element)) {
      return;
    }

    this.manager
      .getActor("AutoComplete")
      ?.markAsAutoCompletableField(element, this);
  }

  get actorName() {
    return "FormAutofill";
  }

  /**
   * Get the search options when searching for autocomplete entries in the parent
   *
   * @param {HTMLInputElement} input - The input element to search for autocomplete entries
   * @returns {object} the search options for the input
   */
  getAutoCompleteSearchOption(input) {
    const fieldDetail = this._fieldDetailsManager
      .getFormHandler(input)
      ?.getFieldDetailByElement(input);

    const scenarioName = lazy.FormScenarios.detect({ input }).signUpForm
      ? "SignUpFormScenario"
      : "";
    return {
      fieldName: fieldDetail?.fieldName,
      elementId: fieldDetail?.elementId,
      scenarioName,
    };
  }

  /**
   * Ask the provider whether it might have autocomplete entry to show
   * for the given input.
   *
   * @param {HTMLInputElement} input - The input element to search for autocomplete entries
   * @returns {boolean} true if we shold search for autocomplete entries
   */
  shouldSearchForAutoComplete(input) {
    const fieldDetail = this._fieldDetailsManager
      .getFormHandler(input)
      ?.getFieldDetailByElement(input);
    if (!fieldDetail) {
      return false;
    }
    const fieldName = fieldDetail.fieldName;
    const isAddressField = lazy.FormAutofillUtils.isAddressField(fieldName);
    const searchPermitted = isAddressField
      ? lazy.FormAutofill.isAutofillAddressesEnabled
      : lazy.FormAutofill.isAutofillCreditCardsEnabled;
    // If the specified autofill feature is pref off, do not search
    if (!searchPermitted) {
      return false;
    }

    // No profile can fill the currently-focused input.
    if (!lazy.FormAutofillContent.savedFieldNames.has(fieldName)) {
      return false;
    }

    return true;
  }

  /**
   * Convert the search result to autocomplete results
   *
   * @param {string} searchString - The string to search for
   * @param {HTMLInputElement} input - The input element to search for autocomplete entries
   * @param {Array<object>} records - autocomplete records
   * @returns {AutocompleteResult}
   */
  searchResultToAutoCompleteResult(searchString, input, records) {
    if (!records) {
      return null;
    }

    const handler = this._fieldDetailsManager.getFormHandler(input);
    const fieldDetail = handler?.getFieldDetailByElement(input);
    if (!fieldDetail) {
      return null;
    }

    const adaptedRecords = handler.getAdaptedProfiles(records.records);
    const isSecure = lazy.InsecurePasswordUtils.isFormSecure(handler.form);
    const isInputAutofilled =
      input.autofillState == lazy.FormAutofillUtils.FIELD_STATES.AUTO_FILLED;

    const AutocompleteResult = lazy.FormAutofillUtils.isAddressField(
      fieldDetail.fieldName
    )
      ? lazy.AddressResult
      : lazy.CreditCardResult;

    const acResult = new AutocompleteResult(
      searchString,
      fieldDetail,
      records.allFieldNames,
      adaptedRecords,
      { isSecure, isInputAutofilled }
    );

    const externalEntries = records.externalEntries;

    acResult.externalEntries.push(
      ...externalEntries.map(
        entry =>
          new lazy.GenericAutocompleteItem(
            entry.image,
            entry.title,
            entry.subtitle,
            entry.fillMessageName,
            entry.fillMessageData
          )
      )
    );

    return acResult;
  }

  #getHandlerByElementId(elementId) {
    const element = lazy.FormAutofillUtils.getElementByIdentifier(elementId);
    return this._fieldDetailsManager.getFormHandler(element);
  }
}
PK
!<[Ml�QQ7chrome/toolkit/res/autofill/FormAutofillContent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Form Autofill content process module.
 */

/* eslint-disable no-use-before-define */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  FormAutofill: "resource://autofill/FormAutofill.sys.mjs",
});

const formFillController = Cc[
  "@mozilla.org/satchel/form-fill-controller;1"
].getService(Ci.nsIFormFillController);

/**
 * Handles content's interactions for the process.
 *
 */
export var FormAutofillContent = {
  /**
   * @type {Set} Set of the fields with usable values in any saved profile.
   */
  get savedFieldNames() {
    return Services.cpmm.sharedData.get("FormAutofill:savedFieldNames");
  },

  get focusedInput() {
    return formFillController.focusedInput;
  },

  /**
   * @type {boolean} Flag indicating whether a focus action requiring
   * the popup to be active is pending.
   */
  _popupPending: false,

  init() {
    this.log = lazy.FormAutofill.defineLogGetter(this, "FormAutofillContent");
    this.debug("init");

    // eslint-disable-next-line mozilla/balanced-listeners
    Services.cpmm.sharedData.addEventListener("change", this);
  },

  showPopup() {
    if (Services.cpmm.sharedData.get("FormAutofill:enabled")) {
      this.debug("updateActiveElement: opening pop up");
      formFillController.showPopup();
    } else {
      this.debug(
        "updateActiveElement: Deferring pop-up until Autofill is ready"
      );
      this._popupPending = true;
    }
  },

  handleEvent(evt) {
    switch (evt.type) {
      case "change": {
        if (!evt.changedKeys.includes("FormAutofill:enabled")) {
          return;
        }
        if (Services.cpmm.sharedData.get("FormAutofill:enabled")) {
          if (this._popupPending) {
            this._popupPending = false;
            this.debug("handleEvent: Opening deferred popup");
            formFillController.showPopup();
          }
        }
        break;
      }
    }
  },
};

FormAutofillContent.init();
PK
!<�ip��l�l6chrome/toolkit/res/autofill/FormAutofillParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Implements a service used to access storage and communicate with content.
 *
 * A "fields" array is used to communicate with FormAutofillChild. Each item
 * represents a single input field in the content page as well as its
 * @autocomplete properties. The schema is as below. Please refer to
 * FormAutofillChild.js for more details.
 *
 * [
 *   {
 *     section,
 *     addressType,
 *     contactType,
 *     fieldName,
 *     value,
 *     index
 *   },
 *   {
 *     // ...
 *   }
 * ]
 */

// We expose a singleton from this module. Some tests may import the
// constructor via a backstage pass.
import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs";
import { FormAutofillUtils } from "resource://gre/modules/shared/FormAutofillUtils.sys.mjs";

const { FIELD_STATES } = FormAutofillUtils;

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddressComponent: "resource://gre/modules/shared/AddressComponent.sys.mjs",
  // eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
  FormAutofillAddressSection:
    "resource://gre/modules/shared/FormAutofillSection.sys.mjs",
  FormAutofillCreditCardSection:
    "resource://gre/modules/shared/FormAutofillSection.sys.mjs",
  FormAutofillSection:
    "resource://gre/modules/shared/FormAutofillSection.sys.mjs",
  FormAutofillPreferences:
    "resource://autofill/FormAutofillPreferences.sys.mjs",
  FormAutofillPrompter: "resource://autofill/FormAutofillPrompter.sys.mjs",
  FirefoxRelay: "resource://gre/modules/FirefoxRelay.sys.mjs",
  LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
  OSKeyStore: "resource://gre/modules/OSKeyStore.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "log", () =>
  FormAutofill.defineLogGetter(lazy, "FormAutofillParent")
);

const { ENABLED_AUTOFILL_ADDRESSES_PREF, ENABLED_AUTOFILL_CREDITCARDS_PREF } =
  FormAutofill;

const { ADDRESSES_COLLECTION_NAME, CREDITCARDS_COLLECTION_NAME } =
  FormAutofillUtils;

let gMessageObservers = new Set();

export let FormAutofillStatus = {
  _initialized: false,

  /**
   * Cache of the Form Autofill status (considering preferences and storage).
   */
  _active: null,

  /**
   * Initializes observers and registers the message handler.
   */
  init() {
    if (this._initialized) {
      return;
    }
    this._initialized = true;

    Services.obs.addObserver(this, "privacy-pane-loaded");

    // Observing the pref and storage changes
    Services.prefs.addObserver(ENABLED_AUTOFILL_ADDRESSES_PREF, this);
    Services.obs.addObserver(this, "formautofill-storage-changed");

    // Only listen to credit card related preference if it is available
    if (FormAutofill.isAutofillCreditCardsAvailable) {
      Services.prefs.addObserver(ENABLED_AUTOFILL_CREDITCARDS_PREF, this);
    }

    Services.telemetry.setEventRecordingEnabled("creditcard", true);
    Services.telemetry.setEventRecordingEnabled("address", true);
  },

  /**
   * Uninitializes FormAutofillStatus. This is for testing only.
   *
   * @private
   */
  uninit() {
    lazy.gFormAutofillStorage._saveImmediately();

    if (!this._initialized) {
      return;
    }
    this._initialized = false;

    this._active = null;

    Services.obs.removeObserver(this, "privacy-pane-loaded");
    Services.prefs.removeObserver(ENABLED_AUTOFILL_ADDRESSES_PREF, this);
    Services.wm.removeListener(this);

    if (FormAutofill.isAutofillCreditCardsAvailable) {
      Services.prefs.removeObserver(ENABLED_AUTOFILL_CREDITCARDS_PREF, this);
    }
  },

  get formAutofillStorage() {
    return lazy.gFormAutofillStorage;
  },

  /**
   * Broadcast the status to frames when the form autofill status changes.
   */
  onStatusChanged() {
    lazy.log.debug("onStatusChanged: Status changed to", this._active);
    Services.ppmm.sharedData.set("FormAutofill:enabled", this._active);
    // Sync autofill enabled to make sure the value is up-to-date
    // no matter when the new content process is initialized.
    Services.ppmm.sharedData.flush();
  },

  /**
   * Query preference and storage status to determine the overall status of the
   * form autofill feature.
   *
   * @returns {boolean} whether form autofill is active (enabled and has data)
   */
  computeStatus() {
    const savedFieldNames = Services.ppmm.sharedData.get(
      "FormAutofill:savedFieldNames"
    );

    return (
      (Services.prefs.getBoolPref(ENABLED_AUTOFILL_ADDRESSES_PREF) ||
        Services.prefs.getBoolPref(ENABLED_AUTOFILL_CREDITCARDS_PREF)) &&
      savedFieldNames &&
      savedFieldNames.size > 0
    );
  },

  /**
   * Update the status and trigger onStatusChanged, if necessary.
   */
  updateStatus() {
    lazy.log.debug("updateStatus");
    let wasActive = this._active;
    this._active = this.computeStatus();
    if (this._active !== wasActive) {
      this.onStatusChanged();
    }
  },

  async updateSavedFieldNames() {
    lazy.log.debug("updateSavedFieldNames");

    let savedFieldNames;
    const addressNames =
      await lazy.gFormAutofillStorage.addresses.getSavedFieldNames();

    // Don't access the credit cards store unless it is enabled.
    if (FormAutofill.isAutofillCreditCardsAvailable) {
      const creditCardNames =
        await lazy.gFormAutofillStorage.creditCards.getSavedFieldNames();
      savedFieldNames = new Set([...addressNames, ...creditCardNames]);
    } else {
      savedFieldNames = addressNames;
    }

    Services.ppmm.sharedData.set(
      "FormAutofill:savedFieldNames",
      savedFieldNames
    );
    Services.ppmm.sharedData.flush();

    this.updateStatus();
  },

  async observe(subject, topic, data) {
    lazy.log.debug("observe:", topic, "with data:", data);
    switch (topic) {
      case "privacy-pane-loaded": {
        let formAutofillPreferences = new lazy.FormAutofillPreferences();
        let document = subject.document;
        let prefFragment = formAutofillPreferences.init(document);
        let formAutofillGroupBox = document.getElementById(
          "formAutofillGroupBox"
        );
        formAutofillGroupBox.appendChild(prefFragment);
        break;
      }

      case "nsPref:changed": {
        // Observe pref changes and update _active cache if status is changed.
        this.updateStatus();
        break;
      }

      case "formautofill-storage-changed": {
        // Early exit if only metadata is changed
        if (data == "notifyUsed") {
          break;
        }

        await this.updateSavedFieldNames();
        break;
      }

      default: {
        throw new Error(
          `FormAutofillStatus: Unexpected topic observed: ${topic}`
        );
      }
    }
  },
};

// Lazily load the storage JSM to avoid disk I/O until absolutely needed.
// Once storage is loaded we need to update saved field names and inform content processes.
ChromeUtils.defineLazyGetter(lazy, "gFormAutofillStorage", () => {
  let { formAutofillStorage } = ChromeUtils.importESModule(
    "resource://autofill/FormAutofillStorage.sys.mjs"
  );
  lazy.log.debug("Loading formAutofillStorage");

  formAutofillStorage.initialize().then(() => {
    // Update the saved field names to compute the status and update child processes.
    FormAutofillStatus.updateSavedFieldNames();
  });

  return formAutofillStorage;
});

export class FormAutofillParent extends JSWindowActorParent {
  // An array of section that are found in this form
  sectionsByRootId = {};

  constructor() {
    super();
    FormAutofillStatus.init();
  }

  static addMessageObserver(observer) {
    gMessageObservers.add(observer);
  }

  static removeMessageObserver(observer) {
    gMessageObservers.delete(observer);
  }

  /**
   * Handles the message coming from FormAutofillChild.
   *
   * @param   {object} message
   * @param   {string} message.name The name of the message.
   * @param   {object} message.data The data of the message.
   */
  async receiveMessage({ name, data }) {
    switch (name) {
      case "FormAutofill:InitStorage": {
        await lazy.gFormAutofillStorage.initialize();
        await FormAutofillStatus.updateSavedFieldNames();
        break;
      }
      case "FormAutofill:GetRecords": {
        const records = await FormAutofillParent.getRecords(data);
        return { records };
      }
      case "FormAutofill:OnFormSubmit": {
        const { rootElementId, formFilledData } = data;
        this.notifyMessageObservers("onFormSubmitted", data);
        await this._onFormSubmit(rootElementId, formFilledData);
        break;
      }
      case "FormAutofill:UpdateWarningMessage":
        this.notifyMessageObservers("updateWarningNote", data);
        break;

      case "FormAutofill:FieldsDetected":
        this.onFormDetected(data);
        break;
      case "FormAutofill:FieldsIdentified":
        this.notifyMessageObservers("fieldsIdentified", data);
        break;
      case "FormAutofill:FieldFilledModified": {
        this.onFieldFilledModified(data);
        break;
      }

      // The remaining Save and Remove messages are invoked only by tests.
      case "FormAutofill:SaveAddress": {
        if (data.guid) {
          await lazy.gFormAutofillStorage.addresses.update(
            data.guid,
            data.address
          );
        } else {
          await lazy.gFormAutofillStorage.addresses.add(data.address);
        }
        break;
      }
      case "FormAutofill:SaveCreditCard": {
        // Setting the first parameter of OSKeyStore.ensurLoggedIn as false
        // since this case only called in tests. Also the reason why we're not calling FormAutofill.verifyUserOSAuth.
        if (!(await lazy.OSKeyStore.ensureLoggedIn(false)).authenticated) {
          lazy.log.warn("User canceled encryption login");
          return undefined;
        }
        await lazy.gFormAutofillStorage.creditCards.add(data.creditcard);
        break;
      }
      case "FormAutofill:RemoveAddresses": {
        data.guids.forEach(guid =>
          lazy.gFormAutofillStorage.addresses.remove(guid)
        );
        break;
      }
      case "FormAutofill:RemoveCreditCards": {
        data.guids.forEach(guid =>
          lazy.gFormAutofillStorage.creditCards.remove(guid)
        );
        break;
      }
    }

    return undefined;
  }

  get formOrigin() {
    return lazy.LoginHelper.getLoginOrigin(
      this.manager.documentPrincipal?.originNoSuffix
    );
  }

  onFormDetected(fields) {
    if (!fields?.length) {
      return;
    }

    const sections = lazy.FormAutofillSection.classifySections(fields);

    // This function is not only called when a form is detected,
    // but also called when the elements in a form are changed, which means we would
    // treat the "updated" section as a new detected section.
    sections.forEach(section => section.onDetected());

    const rootElementId = fields[0].rootElementId;
    this.sectionsByRootId[rootElementId] = sections;
  }

  notifyMessageObservers(callbackName, data) {
    for (let observer of gMessageObservers) {
      try {
        if (callbackName in observer) {
          observer[callbackName](
            data,
            this.manager.browsingContext.topChromeWindow
          );
        }
      } catch (ex) {
        console.error(ex);
      }
    }
  }

  /**
   * Get the records from profile store and return results back to content
   * process. It will decrypt the credit card number and append
   * "cc-number-decrypted" to each record if OSKeyStore isn't set.
   *
   * This is static as a unit test calls this.
   *
   * @param  {object} data
   * @param  {string} data.searchString
   *         The typed string for filtering out the matched records.
   * @param  {string} data.collectionName
   *         The name used to specify which collection to retrieve records.
   * @param  {string} data.fieldName
   *         The field name to search. If not specified, return all records in
   *         the collection
   */
  static async getRecords({ searchString, collectionName, fieldName }) {
    // Derive the collection name from field name if it doesn't exist
    collectionName ||=
      FormAutofillUtils.getCollectionNameFromFieldName(fieldName);

    const collection = lazy.gFormAutofillStorage[collectionName];
    if (!collection) {
      return [];
    }

    const records = await collection.getAll();
    if (!fieldName || !records.length) {
      return records;
    }

    // We don't filter "cc-number"
    if (collectionName == CREDITCARDS_COLLECTION_NAME) {
      if (fieldName == "cc-number") {
        return records.filter(record => !!record["cc-number"]);
      }
    }

    const lcSearchString = searchString.toLowerCase();
    return records.filter(record => {
      const fieldValue = record[fieldName];
      if (!fieldValue) {
        return false;
      }

      if (
        collectionName == ADDRESSES_COLLECTION_NAME &&
        record.country &&
        !FormAutofill.isAutofillAddressesAvailableInCountry(record.country)
      ) {
        // Address autofill isn't supported for the record's country so we don't
        // want to attempt to potentially incorrectly fill the address fields.
        return false;
      }

      return (
        !lcSearchString ||
        String(fieldValue).toLowerCase().startsWith(lcSearchString)
      );
    });
  }

  /*
   * Capture-related functions
   */

  async _onAddressSubmit(address, browser) {
    const storage = lazy.gFormAutofillStorage.addresses;

    // Make sure record is normalized before comparing with records in the storage
    try {
      storage._normalizeRecord(address.record);
    } catch (_e) {
      return false;
    }

    const newAddress = new lazy.AddressComponent(
      address.record,
      // Invalid address fields in the address form will not be captured.
      { ignoreInvalid: true }
    );

    // Exams all stored record to determine whether to show the prompt or not.
    let mergeableFields = [];
    let preserveFields = [];
    let oldRecord = {};

    for (const record of await storage.getAll()) {
      const savedAddress = new lazy.AddressComponent(record);
      // filter invalid field
      const result = newAddress.compare(savedAddress);

      // If any of the fields in the new address are different from the corresponding fields
      // in the saved address, the two addresses are considered different. For example, if
      // the name, email, country are the same but the street address is different, the two
      // addresses are not considered the same.
      if (Object.values(result).includes("different")) {
        continue;
      }

      // If none of the fields in the new address are mergeable, the new address is considered
      // a duplicate of a local address. Therefore, we don't need to capture this address.
      const fields = Object.entries(result)
        .filter(v => ["superset", "similar"].includes(v[1]))
        .map(v => v[0]);
      if (!fields.length) {
        lazy.log.debug(
          "A duplicated address record is found, do not show the prompt"
        );
        storage.notifyUsed(record.guid);
        return false;
      }

      // If the new address is neither a duplicate of the saved address nor a different address.
      // There must be at least one field we can merge, show the update doorhanger
      lazy.log.debug(
        "A mergeable address record is found, show the update prompt"
      );

      // If one record has fewer mergeable fields compared to another, it suggests greater similarity
      // to the merged record. In such cases, we opt for the record with the fewest mergeable fields.
      // TODO: Bug 1830841. Add a testcase
      if (!mergeableFields.length || mergeableFields > fields.length) {
        mergeableFields = fields;
        preserveFields = Object.entries(result)
          .filter(v => ["same", "subset"].includes(v[1]))
          .map(v => v[0]);
        oldRecord = record;
      }
    }

    // Find a mergeable old record, construct the new record by only copying mergeable fields
    // from the new address.
    let newRecord = {};
    if (mergeableFields.length) {
      // TODO: This is only temporarily, should be removed after Bug 1836438 is fixed
      if (mergeableFields.includes("name")) {
        mergeableFields.push("given-name", "additional-name", "family-name");
      }
      mergeableFields.forEach(f => {
        if (f in newAddress.record) {
          newRecord[f] = newAddress.record[f];
        }
      });

      if (preserveFields.includes("name")) {
        preserveFields.push("given-name", "additional-name", "family-name");
      }
      preserveFields.forEach(f => {
        if (f in oldRecord) {
          newRecord[f] = oldRecord[f];
        }
      });
    } else {
      newRecord = newAddress.record;
    }

    if (!this._shouldShowSaveAddressPrompt(newAddress.record)) {
      return false;
    }

    return async () => {
      await lazy.FormAutofillPrompter.promptToSaveAddress(
        browser,
        storage,
        address.flowId,
        { oldRecord, newRecord }
      );
    };
  }

  async _onCreditCardSubmit(creditCard, browser) {
    const storage = lazy.gFormAutofillStorage.creditCards;

    // Make sure record is normalized before comparing with records in the storage
    try {
      storage._normalizeRecord(creditCard.record);
    } catch (_e) {
      return false;
    }

    // If the record alreay exists in the storage, don't bother showing the prompt
    const matchRecord = (
      await storage.getMatchRecords(creditCard.record).next()
    ).value;
    if (matchRecord) {
      storage.notifyUsed(matchRecord.guid);
      return false;
    }

    // Suppress the pending doorhanger from showing up if user disabled credit card in previous doorhanger.
    if (!FormAutofill.isAutofillCreditCardsEnabled) {
      return false;
    }

    // Overwrite the guid if there is a duplicate
    const duplicateRecord =
      (await storage.getDuplicateRecords(creditCard.record).next()).value ?? {};

    return async () => {
      await lazy.FormAutofillPrompter.promptToSaveCreditCard(
        browser,
        storage,
        creditCard.flowId,
        { oldRecord: duplicateRecord, newRecord: creditCard.record }
      );
    };
  }

  async _onFormSubmit(rootElementId, formFilledData) {
    const browser = this.manager.browsingContext.top.embedderElement;
    if (!browser) {
      return;
    }

    const sections = this.sectionsByRootId[rootElementId];
    if (!sections) {
      return;
    }

    const address = [];
    const creditCard = [];

    for (const section of sections) {
      const secRecord = section.createRecord(formFilledData);
      if (!secRecord) {
        continue;
      }

      if (section instanceof lazy.FormAutofillAddressSection) {
        address.push(secRecord);
      } else if (section instanceof lazy.FormAutofillCreditCardSection) {
        creditCard.push(secRecord);
      } else {
        throw new Error("Unknown section type");
      }

      // Used for telemetry
      section.onSubmitted(formFilledData);
    }

    // Transmit the telemetry immediately in the meantime form submitted, and handle these pending
    // doorhangers at a later.
    await Promise.all(
      [
        await Promise.all(
          address.map(addrRecord => this._onAddressSubmit(addrRecord, browser))
        ),
        await Promise.all(
          creditCard.map(ccRecord =>
            this._onCreditCardSubmit(ccRecord, browser)
          )
        ),
      ]
        .map(pendingDoorhangers => {
          return pendingDoorhangers.filter(
            pendingDoorhanger =>
              !!pendingDoorhanger && typeof pendingDoorhanger == "function"
          );
        })
        .map(pendingDoorhangers =>
          (async () => {
            for (const showDoorhanger of pendingDoorhangers) {
              await showDoorhanger();
            }
          })()
        )
    );
  }

  _shouldShowSaveAddressPrompt(record) {
    if (!FormAutofill.isAutofillAddressesCaptureEnabled) {
      return false;
    }

    // Do not save address for regions that we don't support
    if (
      FormAutofill._isAutofillAddressesAvailable == "detect" &&
      !FormAutofill.isAutofillAddressesAvailableInCountry(record.country)
    ) {
      lazy.log.debug(
        `Do not show the address capture prompt for unsupported regions - ${record.country}`
      );
      return false;
    }

    // Display the address capture doorhanger only when the submitted form contains all
    // the required fields. This approach is implemented to prevent excessive prompting.
    let requiredFields = FormAutofill.addressCaptureRequiredFields;
    requiredFields ??=
      FormAutofillUtils.getFormFormat(record.country).countryRequiredFields ??
      [];

    if (!requiredFields.every(field => field in record)) {
      lazy.log.debug(
        "Do not show the address capture prompt when the submitted form doesn't contain all the required fields"
      );
      return false;
    }

    return true;
  }

  /*
   * AutoComplete-related functions
   */

  /**
   * Retrieves autocomplete entries for a given search string and data context.
   *
   * @param {string} searchString
   *                 The search string used to filter autocomplete entries.
   * @param {object} options
   * @param {string} options.fieldName
   *                 The name of the field for which autocomplete entries are being fetched.
   * @param {string} options.elementId
   *                 The id of the element for which we are searching for an autocomplete entry.
   * @param {string} options.scenarioName
   *                 The scenario name used in the autocomplete operation to fetch external entries.
   * @returns {Promise<object>} A promise that resolves to an object containing two properties: `records` and `externalEntries`.
   *         `records` is an array of autofill records from the form's internal data, sorted by `timeLastUsed`.
   *         `externalEntries` is an array of external autocomplete items fetched based on the scenario.
   *         `allFieldNames` is an array containing all the matched field name found in this section.
   */
  async searchAutoCompleteEntries(searchString, options) {
    const { fieldName, elementId, scenarioName } = options;

    const section = this.getSectionByElementId(elementId);
    if (!section.isValidSection() || !section.isEnabled()) {
      return null;
    }

    const relayPromise = lazy.FirefoxRelay.autocompleteItemsAsync({
      formOrigin: this.formOrigin,
      scenarioName,
      hasInput: !!searchString?.length,
    });

    // Retrieve information for the autocomplete entry
    const recordsPromise = FormAutofillParent.getRecords({
      searchString,
      fieldName,
    });

    const [records, externalEntries] = await Promise.all([
      recordsPromise,
      relayPromise,
    ]);

    // Sort addresses by timeLastUsed for showing the lastest used address at top.
    records.sort((a, b) => b.timeLastUsed - a.timeLastUsed);
    return { records, externalEntries, allFieldNames: section.allFieldNames };
  }

  /**
   * This function is called when an autocomplete entry that is provided by
   * formautofill is selected by the user.
   */
  async onAutoCompleteEntrySelected(message, data) {
    switch (message) {
      case "FormAutofill:OpenPreferences": {
        const win = lazy.BrowserWindowTracker.getTopWindow();
        win.openPreferences("privacy-form-autofill");
        break;
      }

      case "FormAutofill:ClearForm": {
        this.clearForm(data.focusElementId);
        break;
      }

      case "FormAutofill:FillForm": {
        this.autofillFields(data.focusElementId, data.profile);
        break;
      }

      default: {
        lazy.log.debug("Unsupported autocomplete message:", message);
        break;
      }
    }
  }

  onAutoCompletePopupOpened(elementId) {
    const section = this.getSectionByElementId(elementId);
    section?.onPopupOpened(elementId);
  }

  onAutoCompleteEntryClearPreview(message, data) {
    this.previewFields(data.focusElementId, null);
  }

  onAutoCompleteEntryHovered(message, data) {
    if (message == "FormAutofill:FillForm") {
      this.previewFields(data.focusElementId, data.profile);
    } else {
      // Make sure the preview is cleared when users select an entry
      // that doesn't support preview.
      this.previewFields(data.focusElementId, null);
    }
  }

  clearForm(elementId) {
    const section = this.getSectionByElementId(elementId);

    section.onCleared(elementId);

    const ids = section.fieldDetails.map(detail => detail.elementId);
    this.sendAsyncMessage("FormAutofill:ClearFilledFields", ids);
  }

  async previewFields(elementId, profile) {
    const section = this.getSectionByElementId(elementId);
    if (!(await section.preparePreviewProfile(profile))) {
      lazy.log.debug("profile cannot be previewed");
      return false;
    }

    const ids = section.fieldDetails.map(detail => detail.elementId);

    try {
      if (profile) {
        await this.sendQuery("FormAutofill:PreviewFields", { ids, profile });
      } else {
        await this.sendQuery("FormAutofill:ClearPreviewedFields", { ids });
      }
    } catch (e) {
      console.error("There was an error previewing: ", e.message);
    }

    // For testing only
    Services.obs.notifyObservers(null, "formautofill-preview-complete");
    return true;
  }

  async autofillFields(elementId, profile) {
    const section = this.getSectionByElementId(elementId);
    if (!(await section.prepareFillingProfile(profile))) {
      lazy.log.debug("profile cannot be filled");
      return;
    }

    const ids = section.fieldDetails.map(detail => detail.elementId);

    let filledResult = new Map();
    try {
      filledResult = await this.sendQuery("FormAutofill:FillFields", {
        focusedElementId: elementId,
        ids,
        profile,
      });

      this.filledResult = this.filledResult ?? new Map();
      filledResult.forEach((value, key) => this.filledResult.set(key, value));

      section.onFilled(filledResult);
    } catch (e) {
      console.error("There was an error autofilling: ", e.message);
    }

    // eslint-disable-next-line consistent-return
    return filledResult;
  }

  onFieldFilledModified(elementId) {
    if (!this.filledResult?.get(elementId)) {
      return;
    }

    this.filledResult.get(elementId).filledState = FIELD_STATES.NORMAL;

    const section = this.getSectionByElementId(elementId);

    // For telemetry
    section?.onFilledModified(elementId);

    // Restore <select> fields to their initial state once we know
    // that the user intends to manually clear the filled form.
    const fieldDetails = section.fieldDetails;
    const selects = fieldDetails.filter(field => field.tagName == "SELECT");
    if (selects.length) {
      const inputs = fieldDetails.filter(field => field.tagName == "INPUT");
      if (
        inputs.every(
          field =>
            this.filledResult.get(field.elementId).filledState ==
            FIELD_STATES.NORMAL
        )
      ) {
        const ids = selects.map(field => field.elementId);
        this.sendAsyncMessage("FormAutofill:ClearFilledFields", ids);
      }
    }
  }

  getSectionByElementId(elementId) {
    for (const sections of Object.values(this.sectionsByRootId)) {
      const section = sections.find(s =>
        s.getFieldDetailByElementId(elementId)
      );
      if (section) {
        return section;
      }
    }
    return null;
  }

  // For testing
  getSections() {
    return Object.values(this.sectionsByRootId).flat();
  }
}
PK
!<Z����8�8;chrome/toolkit/res/autofill/FormAutofillPreferences.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Injects the form autofill section into about:preferences.
 */

// Add addresses enabled flag in telemetry environment for recording the number of
// users who disable/enable the address autofill feature.
const BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties";
const MANAGE_ADDRESSES_URL =
  "chrome://formautofill/content/manageAddresses.xhtml";
const MANAGE_CREDITCARDS_URL =
  "chrome://formautofill/content/manageCreditCards.xhtml";

import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs";
import { FormAutofillUtils } from "resource://gre/modules/shared/FormAutofillUtils.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  OSKeyStore: "resource://gre/modules/OSKeyStore.sys.mjs",
});
ChromeUtils.defineLazyGetter(
  lazy,
  "l10n",
  () =>
    new Localization(
      ["branding/brand.ftl", "browser/preferences/preferences.ftl"],
      true
    )
);

const {
  ENABLED_AUTOFILL_ADDRESSES_PREF,
  ENABLED_AUTOFILL_CREDITCARDS_PREF,
  AUTOFILL_CREDITCARDS_REAUTH_PREF,
} = FormAutofill;
const {
  MANAGE_ADDRESSES_L10N_IDS,
  EDIT_ADDRESS_L10N_IDS,
  MANAGE_CREDITCARDS_L10N_IDS,
  EDIT_CREDITCARD_L10N_IDS,
} = FormAutofillUtils;
// Add credit card enabled flag in telemetry environment for recording the number of
// users who disable/enable the credit card autofill feature.

const HTML_NS = "http://www.w3.org/1999/xhtml";

export function FormAutofillPreferences() {
  this.bundle = Services.strings.createBundle(BUNDLE_URI);
}

FormAutofillPreferences.prototype = {
  /**
   * Create the Form Autofill preference group.
   *
   * @param   {HTMLDocument} document
   * @returns {XULElement}
   */
  init(document) {
    this.createPreferenceGroup(document);
    this.attachEventListeners();

    return this.refs.formAutofillFragment;
  },

  /**
   * Remove event listeners and the preference group.
   */
  uninit() {
    this.detachEventListeners();
    this.refs.formAutofillGroup.remove();
  },

  /**
   * Create Form Autofill preference group
   *
   * @param  {HTMLDocument} document
   */
  createPreferenceGroup(document) {
    let formAutofillFragment = document.createDocumentFragment();
    let formAutofillGroupBoxLabel = document.createXULElement("label");
    let formAutofillGroupBoxLabelHeading = document.createElementNS(
      HTML_NS,
      "h2"
    );
    let formAutofillGroup = document.createXULElement("vbox");
    // Wrappers are used to properly compute the search tooltip positions
    // let savedAddressesBtnWrapper = document.createXULElement("hbox");
    // let savedCreditCardsBtnWrapper = document.createXULElement("hbox");
    this.refs = {};
    this.refs.formAutofillGroup = formAutofillGroup;
    this.refs.formAutofillFragment = formAutofillFragment;

    formAutofillGroupBoxLabel.appendChild(formAutofillGroupBoxLabelHeading);
    formAutofillFragment.appendChild(formAutofillGroupBoxLabel);
    formAutofillFragment.appendChild(formAutofillGroup);

    let showAddressUI = FormAutofill.isAutofillAddressesAvailable;
    let showCreditCardUI = FormAutofill.isAutofillCreditCardsAvailable;

    if (!showAddressUI && !showCreditCardUI) {
      return;
    }

    formAutofillGroupBoxLabelHeading.textContent = lazy.l10n.formatValueSync(
      "pane-privacy-autofill-header"
    );

    if (showAddressUI) {
      let savedAddressesBtnWrapper = document.createXULElement("hbox");
      let addressAutofill = document.createXULElement("hbox");
      let addressAutofillCheckboxGroup = document.createXULElement("hbox");
      let addressAutofillCheckbox = document.createXULElement("checkbox");
      let addressAutofillLearnMore = document.createElement("a", {
        is: "moz-support-link",
      });
      let savedAddressesBtn = document.createXULElement("button", {
        is: "highlightable-button",
      });
      savedAddressesBtn.className = "accessory-button";
      addressAutofillCheckbox.className = "tail-with-learn-more";

      formAutofillGroup.id = "formAutofillGroup";
      addressAutofill.id = "addressAutofill";
      addressAutofillLearnMore.id = "addressAutofillLearnMore";

      addressAutofill.setAttribute("data-subcategory", "address-autofill");
      addressAutofillCheckbox.setAttribute(
        "label",
        lazy.l10n.formatValueSync("autofill-addresses-checkbox")
      );
      savedAddressesBtn.setAttribute(
        "label",
        lazy.l10n.formatValueSync("autofill-saved-addresses-button")
      );
      // Align the start to keep the savedAddressesBtn as original size
      // when addressAutofillCheckboxGroup's height is changed by a longer l10n string
      savedAddressesBtnWrapper.setAttribute("align", "start");

      addressAutofillLearnMore.setAttribute(
        "support-page",
        "autofill-card-address"
      );

      // Add preferences search support
      savedAddressesBtn.setAttribute(
        "search-l10n-ids",
        MANAGE_ADDRESSES_L10N_IDS.concat(EDIT_ADDRESS_L10N_IDS).join(",")
      );

      // Manually set the checked state
      if (FormAutofill.isAutofillAddressesEnabled) {
        addressAutofillCheckbox.setAttribute("checked", true);
      }
      if (FormAutofill.isAutofillAddressesLocked) {
        addressAutofillCheckbox.disabled = true;
      }

      addressAutofillCheckboxGroup.setAttribute("align", "center");
      addressAutofillCheckboxGroup.setAttribute("flex", "1");

      formAutofillGroup.appendChild(addressAutofill);
      addressAutofill.appendChild(addressAutofillCheckboxGroup);
      addressAutofillCheckboxGroup.appendChild(addressAutofillCheckbox);
      addressAutofillCheckboxGroup.appendChild(addressAutofillLearnMore);
      addressAutofill.appendChild(savedAddressesBtnWrapper);
      savedAddressesBtnWrapper.appendChild(savedAddressesBtn);

      this.refs.formAutofillFragment = formAutofillFragment;
      this.refs.addressAutofillCheckbox = addressAutofillCheckbox;
      this.refs.savedAddressesBtn = savedAddressesBtn;
    }

    if (showCreditCardUI) {
      let savedCreditCardsBtnWrapper = document.createXULElement("hbox");
      let creditCardAutofill = document.createXULElement("hbox");
      let creditCardAutofillCheckboxGroup = document.createXULElement("hbox");
      let creditCardAutofillCheckbox = document.createXULElement("checkbox");
      let creditCardAutofillLearnMore = document.createElement("a", {
        is: "moz-support-link",
      });
      let savedCreditCardsBtn = document.createXULElement("button", {
        is: "highlightable-button",
      });
      savedCreditCardsBtn.className = "accessory-button";
      creditCardAutofillCheckbox.className = "tail-with-learn-more";

      creditCardAutofill.id = "creditCardAutofill";
      creditCardAutofillLearnMore.id = "creditCardAutofillLearnMore";

      creditCardAutofill.setAttribute(
        "data-subcategory",
        "credit-card-autofill"
      );
      creditCardAutofillCheckbox.setAttribute(
        "label",
        lazy.l10n.formatValueSync("autofill-payment-methods-checkbox-message")
      );

      savedCreditCardsBtn.setAttribute(
        "label",
        lazy.l10n.formatValueSync("autofill-saved-payment-methods-button")
      );
      // Align the start to keep the savedCreditCardsBtn as original size
      // when creditCardAutofillCheckboxGroup's height is changed by a longer l10n string
      savedCreditCardsBtnWrapper.setAttribute("align", "start");

      creditCardAutofillLearnMore.setAttribute(
        "support-page",
        "credit-card-autofill"
      );

      let creditCardsAutofillDescription =
        document.createXULElement("description");

      creditCardsAutofillDescription.setAttribute("flex", "1");
      creditCardsAutofillDescription.className = "indent tip-caption";
      creditCardsAutofillDescription.setAttribute("data-l10n-attrs", "hidden");
      creditCardsAutofillDescription.setAttribute(
        "data-l10n-id",
        "autofill-payment-methods-checkbox-submessage"
      );

      // Add preferences search support
      savedCreditCardsBtn.setAttribute(
        "search-l10n-ids",
        MANAGE_CREDITCARDS_L10N_IDS.concat(EDIT_CREDITCARD_L10N_IDS).join(",")
      );

      // Manually set the checked state
      if (FormAutofill.isAutofillCreditCardsEnabled) {
        creditCardAutofillCheckbox.setAttribute("checked", true);
      }
      if (FormAutofill.isAutofillCreditCardsLocked) {
        creditCardAutofillCheckbox.disabled = true;
      }

      creditCardAutofillCheckboxGroup.setAttribute("align", "center");
      creditCardAutofillCheckboxGroup.setAttribute("flex", "1");

      formAutofillGroup.appendChild(creditCardAutofill);
      creditCardAutofill.appendChild(creditCardAutofillCheckboxGroup);
      creditCardAutofillCheckboxGroup.appendChild(creditCardAutofillCheckbox);
      creditCardAutofillCheckboxGroup.appendChild(creditCardAutofillLearnMore);
      creditCardAutofill.appendChild(savedCreditCardsBtnWrapper);
      savedCreditCardsBtnWrapper.appendChild(savedCreditCardsBtn);
      formAutofillGroup.appendChild(creditCardsAutofillDescription);

      this.refs.creditCardAutofillCheckbox = creditCardAutofillCheckbox;
      this.refs.savedCreditCardsBtn = savedCreditCardsBtn;

      if (
        lazy.OSKeyStore.canReauth() &&
        !Services.prefs.getBoolPref("security.nocertdb", false)
      ) {
        let reauth = document.createXULElement("hbox");
        let reauthCheckboxGroup = document.createXULElement("hbox");
        let reauthCheckbox = document.createXULElement("checkbox");
        let reauthLearnMore = document.createElement("a", {
          is: "moz-support-link",
        });

        reauthCheckboxGroup.classList.add("indent");
        reauthCheckbox.classList.add("tail-with-learn-more");
        reauthCheckbox.disabled = !FormAutofill.isAutofillCreditCardsEnabled;

        reauth.id = "creditCardReauthenticate";
        reauthLearnMore.id = "creditCardReauthenticateLearnMore";

        reauth.setAttribute("data-subcategory", "reauth-credit-card-autofill");

        reauthCheckbox.setAttribute(
          "label",
          lazy.l10n.formatValueSync("autofill-reauth-payment-methods-checkbox")
        );

        // If target.checked is checked, enable OSAuth. Otherwise, reset the pref value.
        reauthCheckbox.setAttribute(
          "checked",
          FormAutofillUtils.getOSAuthEnabled(AUTOFILL_CREDITCARDS_REAUTH_PREF)
        );

        reauthLearnMore.setAttribute(
          "support-page",
          "credit-card-autofill#w_require-authentication-for-autofill"
        );

        reauthCheckboxGroup.setAttribute("align", "center");
        reauthCheckboxGroup.setAttribute("flex", "1");

        formAutofillGroup.appendChild(reauth);
        reauth.appendChild(reauthCheckboxGroup);
        reauthCheckboxGroup.appendChild(reauthCheckbox);
        reauthCheckboxGroup.appendChild(reauthLearnMore);
        this.refs.reauthCheckbox = reauthCheckbox;
      }
    }
  },

  /**
   * Handle events
   *
   * @param  {DOMEvent} event
   */
  async handleEvent(event) {
    switch (event.type) {
      case "command": {
        let target = event.target;

        if (target == this.refs.addressAutofillCheckbox) {
          // Set preference directly instead of relying on <Preference>
          Services.prefs.setBoolPref(
            ENABLED_AUTOFILL_ADDRESSES_PREF,
            target.checked
          );
        } else if (target == this.refs.creditCardAutofillCheckbox) {
          Services.prefs.setBoolPref(
            ENABLED_AUTOFILL_CREDITCARDS_PREF,
            target.checked
          );
          if (this.refs.reauthCheckbox) {
            this.refs.reauthCheckbox.disabled = !target.checked;
          }
        } else if (target == this.refs.reauthCheckbox) {
          if (!lazy.OSKeyStore.canReauth()) {
            break;
          }

          let messageText = await lazy.l10n.formatValueSync(
            "autofill-creditcard-os-dialog-message"
          );
          let captionText = await lazy.l10n.formatValueSync(
            "autofill-creditcard-os-auth-dialog-caption"
          );
          let win = target.ownerGlobal.docShell.chromeEventHandler.ownerGlobal;
          // Calling OSKeyStore.ensureLoggedIn() instead of FormAutofillUtils.verifyOSAuth()
          // since we want to authenticate user each time this stting is changed.
          let isAuthorized = (
            await lazy.OSKeyStore.ensureLoggedIn(
              messageText,
              captionText,
              win,
              false
            )
          ).authenticated;
          if (!isAuthorized) {
            target.checked = !target.checked;
            break;
          }

          // If target.checked is checked, enable OSAuth. Otherwise, reset the pref value.
          FormAutofillUtils.setOSAuthEnabled(
            AUTOFILL_CREDITCARDS_REAUTH_PREF,
            target.checked
          );
        } else if (target == this.refs.savedAddressesBtn) {
          target.ownerGlobal.gSubDialog.open(MANAGE_ADDRESSES_URL);
        } else if (target == this.refs.savedCreditCardsBtn) {
          target.ownerGlobal.gSubDialog.open(MANAGE_CREDITCARDS_URL);
        }
        break;
      }
      case "click": {
        let target = event.target;

        if (target == this.refs.addressAutofillCheckboxLabel) {
          let pref = FormAutofill.isAutofillAddressesEnabled;
          Services.prefs.setBoolPref(ENABLED_AUTOFILL_ADDRESSES_PREF, !pref);
          this.refs.addressAutofillCheckbox.checked = !pref;
        } else if (target == this.refs.creditCardAutofillCheckboxLabel) {
          let pref = FormAutofill.isAutofillCreditCardsEnabled;
          Services.prefs.setBoolPref(ENABLED_AUTOFILL_CREDITCARDS_PREF, !pref);
          this.refs.creditCardAutofillCheckbox.checked = !pref;
          this.refs.reauthCheckbox.disabled = pref;
        }
        break;
      }
    }
  },

  /**
   * Attach event listener
   */
  attachEventListeners() {
    this.refs.formAutofillGroup.addEventListener("command", this);
    this.refs.formAutofillGroup.addEventListener("click", this);
  },

  /**
   * Remove event listener
   */
  detachEventListeners() {
    this.refs.formAutofillGroup.removeEventListener("command", this);
    this.refs.formAutofillGroup.removeEventListener("click", this);
  },
};
PK
!<X{s@I�I�8chrome/toolkit/res/autofill/FormAutofillPrompter.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Implements doorhanger singleton that wraps up the PopupNotifications and handles
 * the doorhager UI for formautofill related features.
 */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs";
import { FormAutofillUtils } from "resource://gre/modules/shared/FormAutofillUtils.sys.mjs";

import { AutofillTelemetry } from "resource://gre/modules/shared/AutofillTelemetry.sys.mjs";
import { showConfirmation } from "resource://gre/modules/FillHelpers.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  CreditCard: "resource://gre/modules/CreditCard.sys.mjs",
  formAutofillStorage: "resource://autofill/FormAutofillStorage.sys.mjs",
  OSKeyStore: "resource://gre/modules/OSKeyStore.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "log", () =>
  FormAutofill.defineLogGetter(lazy, "FormAutofillPrompter")
);

const l10n = new Localization(
  [
    "browser/preferences/formAutofill.ftl",
    "toolkit/formautofill/formAutofill.ftl",
    "branding/brand.ftl",
  ],
  true
);

const { ENABLED_AUTOFILL_CREDITCARDS_PREF } = FormAutofill;

let CONTENT = {};

/**
 * `AutofillDoorhanger` provides a base for both address capture and credit card
 * capture doorhanger notifications. It handles the UI generation and logic
 * related to displaying the doorhanger,
 *
 * The UI data sourced from the `CONTENT` variable is used for rendering. Derived classes
 * should override the `render()` method to customize the layout.
 */
export class AutofillDoorhanger {
  /**
   * Constructs an instance of the `AutofillDoorhanger` class.
   *
   * @param {object} browser   The browser where the doorhanger will be displayed.
   * @param {object} oldRecord The old record that can be merged with the new record
   * @param {object} newRecord The new record submitted by users
   */
  static headerClass = "address-capture-header";
  static descriptionClass = "address-capture-description";
  static contentClass = "address-capture-content";
  static menuButtonId = "address-capture-menu-button";

  static preferenceURL = null;
  static learnMoreURL = null;

  constructor(browser, oldRecord, newRecord, flowId) {
    this.browser = browser;
    this.oldRecord = oldRecord ?? {};
    this.newRecord = newRecord;
    this.flowId = flowId;
  }

  get ui() {
    return CONTENT[this.constructor.name];
  }

  // PopupNotification appends a "-notification" suffix to the id to avoid
  // id conflict.
  get notificationId() {
    return this.ui.id + "-notification";
  }

  // The popup notification element
  get panel() {
    return this.browser.ownerDocument.getElementById(this.notificationId);
  }

  get doc() {
    return this.browser.ownerDocument;
  }

  get chromeWin() {
    return this.browser.ownerGlobal;
  }

  /*
   * An autofill doorhanger consists 3 parts - header, description, and content
   * The content part contains customized UI layout for this doorhanger
   */

  // The container of the header part
  static header(panel) {
    return panel.querySelector(`.${AutofillDoorhanger.headerClass}`);
  }
  get header() {
    return AutofillDoorhanger.header(this.panel);
  }

  // The container of the description part
  static description(panel) {
    return panel.querySelector(`.${AutofillDoorhanger.descriptionClass}`);
  }
  get description() {
    return AutofillDoorhanger.description(this.panel);
  }

  // The container of the content part
  static content(panel) {
    return panel.querySelector(`.${AutofillDoorhanger.contentClass}`);
  }
  get content() {
    return AutofillDoorhanger.content(this.panel);
  }

  static menuButton(panel) {
    return panel.querySelector(`#${AutofillDoorhanger.menuButtonId}`);
  }
  get menuButton() {
    return AutofillDoorhanger.menuButton(this.panel);
  }

  static menuPopup(panel) {
    return AutofillDoorhanger.menuButton(panel).querySelector(
      `.toolbar-menupopup`
    );
  }
  get menuPopup() {
    return AutofillDoorhanger.menuPopup(this.panel);
  }

  static preferenceButton(panel) {
    return AutofillDoorhanger.menuButton(panel).querySelector(
      `[data-l10n-id=address-capture-manage-address-button]`
    );
  }
  static learnMoreButton(panel) {
    return AutofillDoorhanger.menuButton(panel).querySelector(
      `[data-l10n-id=address-capture-learn-more-button]`
    );
  }

  get preferenceURL() {
    return this.constructor.preferenceURL;
  }
  get learnMoreURL() {
    return this.constructor.learnMoreURL;
  }

  onMenuItemClick(evt) {
    AutofillTelemetry.recordDoorhangerClicked(
      this.constructor.telemetryType,
      evt,
      this.constructor.telemetryObject,
      this.flowId
    );

    if (evt == "open-pref") {
      this.browser.ownerGlobal.openPreferences(this.preferenceURL);
    } else if (evt == "learn-more") {
      const url =
        Services.urlFormatter.formatURLPref("app.support.baseURL") +
        this.learnMoreURL;
      this.browser.ownerGlobal.openWebLinkIn(url, "tab", {
        relatedToCurrent: true,
      });
    }
  }

  // Build the doorhanger markup
  render() {
    this.renderHeader();

    this.renderDescription();

    // doorhanger specific content
    this.renderContent();
  }

  renderHeader() {
    // Render the header text
    const text = this.header.querySelector(`h1`);
    this.doc.l10n.setAttributes(text, this.ui.header.l10nId);

    // Render the menu button
    if (!this.ui.menu?.length || AutofillDoorhanger.menuButton(this.panel)) {
      return;
    }

    const button = this.doc.createElement("button");
    button.setAttribute("id", AutofillDoorhanger.menuButtonId);
    button.setAttribute("class", "address-capture-icon-button");
    this.doc.l10n.setAttributes(button, "address-capture-open-menu-button");

    const menupopup = this.doc.createXULElement("menupopup");
    menupopup.setAttribute("id", AutofillDoorhanger.menuButtonId);
    menupopup.setAttribute("class", "toolbar-menupopup");

    for (const [index, element] of this.ui.menu.entries()) {
      const menuitem = this.doc.createXULElement("menuitem");
      this.doc.l10n.setAttributes(menuitem, element.l10nId);
      /* eslint-disable mozilla/balanced-listeners */
      menuitem.addEventListener("command", event => {
        event.stopPropagation();
        this.onMenuItemClick(element.evt);
      });
      menupopup.appendChild(menuitem);

      if (index != this.ui.menu.length - 1) {
        menupopup.appendChild(this.doc.createXULElement("menuseparator"));
      }
    }

    button.appendChild(menupopup);
    /* eslint-disable mozilla/balanced-listeners */
    button.addEventListener("click", event => {
      event.stopPropagation();
      menupopup.openPopup(button, "after_start");
    });
    this.header.appendChild(button);
  }

  renderDescription() {
    if (this.ui.description?.l10nId) {
      const text = this.description.querySelector(`p`);
      this.doc.l10n.setAttributes(text, this.ui.description.l10nId);
      this.description?.setAttribute("style", "");
    } else {
      this.description?.setAttribute("style", "display:none");
    }
  }

  onEventCallback(state) {
    lazy.log.debug(`Doorhanger receives event callback: ${state}`);

    if (state == "showing") {
      this.render();
    }
  }

  async show() {
    AutofillTelemetry.recordDoorhangerShown(
      this.constructor.telemetryType,
      this.constructor.telemetryObject,
      this.flowId
    );

    let options = {
      ...this.ui.options,
      eventCallback: state => this.onEventCallback(state),
    };

    this.#setAnchor();

    return new Promise(resolve => {
      this.resolve = resolve;
      this.chromeWin.PopupNotifications.show(
        this.browser,
        this.ui.id,
        this.getNotificationHeader?.() ?? "",
        this.ui.anchor.id,
        ...this.#createActions(),
        options
      );
    });
  }

  /**
   * Closes the doorhanger with a given action.
   * This method is specifically intended for closing the doorhanger in scenarios
   * other than clicking the main or secondary buttons.
   */
  closeDoorhanger(action) {
    this.resolve(action);
    const notification = this.chromeWin.PopupNotifications.getNotification(
      this.ui.id,
      this.browser
    );
    if (notification) {
      this.chromeWin.PopupNotifications.remove(notification);
    }
  }

  /**
   * Create an image element for notification anchor if it doesn't already exist.
   */
  #setAnchor() {
    let anchor = this.doc.getElementById(this.ui.anchor.id);
    if (!anchor) {
      // Icon shown on URL bar
      anchor = this.doc.createXULElement("image");
      anchor.id = this.ui.anchor.id;
      anchor.setAttribute("src", this.ui.anchor.URL);
      anchor.classList.add("notification-anchor-icon");
      anchor.setAttribute("role", "button");
      anchor.setAttribute("tooltiptext", this.ui.anchor.tooltiptext);

      const popupBox = this.doc.getElementById("notification-popup-box");
      popupBox.appendChild(anchor);
    }
  }

  /**
   * Generate the main action and secondary actions from content parameters and
   * promise resolve.
   */
  #createActions() {
    function getLabelAndAccessKey(param) {
      const msg = l10n.formatMessagesSync([{ id: param.l10nId }])[0];
      return {
        label: msg.attributes.find(x => x.name == "label").value,
        accessKey: msg.attributes.find(x => x.name == "accessKey").value,
        dismiss: param.dismiss,
      };
    }

    const mainActionParams = this.ui.footer.mainAction;
    const secondaryActionParams = this.ui.footer.secondaryActions;

    const callback = () => {
      AutofillTelemetry.recordDoorhangerClicked(
        this.constructor.telemetryType,
        mainActionParams.callbackState,
        this.constructor.telemetryObject,
        this.flowId
      );

      this.resolve(mainActionParams.callbackState);
    };

    const mainAction = {
      ...getLabelAndAccessKey(mainActionParams),
      callback,
    };

    let secondaryActions = [];
    for (const params of secondaryActionParams) {
      const callback = () => {
        AutofillTelemetry.recordDoorhangerClicked(
          this.constructor.telemetryType,
          params.callbackState,
          this.constructor.telemetryObject,
          this.flowId
        );

        this.resolve(params.callbackState);
      };

      secondaryActions.push({
        ...getLabelAndAccessKey(params),
        callback,
      });
    }

    return [mainAction, secondaryActions];
  }
}

export class AddressSaveDoorhanger extends AutofillDoorhanger {
  static preferenceURL = "privacy-address-autofill";
  static learnMoreURL = "automatically-fill-your-address-web-forms";
  static editButtonId = "address-capture-edit-address-button";

  static telemetryType = AutofillTelemetry.ADDRESS;
  static telemetryObject = "capture_doorhanger";

  constructor(browser, oldRecord, newRecord, flowId) {
    super(browser, oldRecord, newRecord, flowId);
  }

  static editButton(panel) {
    return panel.querySelector(`#${AddressSaveDoorhanger.editButtonId}`);
  }
  get editButton() {
    return AddressSaveDoorhanger.editButton(this.panel);
  }

  /**
   * Formats a line by comparing the old and the new address field and returns an array of
   * <span> elements that represents the formatted line.
   *
   * @param {Array<Array<string>>} datalist An array of pairs, where each pair contains old and new data.
   * @param {boolean}              showDiff True to format the text line that highlight the diff part.
   *
   * @returns {Array<HTMLSpanElement>} An array of formatted text elements.
   */
  #formatLine(datalist, showDiff) {
    const createSpan = (text, style = null) => {
      let s;

      if (showDiff) {
        if (style == "remove") {
          s = this.doc.createElement("del");
          s.setAttribute("class", "address-update-text-diff-removed");
        } else if (style == "add") {
          s = this.doc.createElement("mark");
          s.setAttribute("class", "address-update-text-diff-added");
        } else {
          s = this.doc.createElement("span");
        }
      } else {
        s = this.doc.createElement("span");
      }
      s.textContent = text;
      return s;
    };

    let spans = [];
    let previousField;
    for (const [field, oldData, newData] of datalist) {
      if (!oldData && !newData) {
        continue;
      }

      // Always add a whitespace between field data that we put in the same line.
      // Ex. first-name: John, family-name: Doe becomes
      // "John Doe"
      if (spans.length) {
        if (previousField == "address-level2" && field == "address-level1") {
          spans.push(createSpan(", "));
        } else {
          spans.push(createSpan(" "));
        }
      }

      if (!oldData) {
        spans.push(createSpan(newData, "add"));
      } else if (!newData || oldData == newData) {
        // The same
        spans.push(createSpan(oldData));
      } else if (newData.startsWith(oldData)) {
        // Have the same prefix
        const diff = newData.slice(oldData.length).trim();
        spans.push(createSpan(newData.slice(0, newData.length - diff.length)));
        spans.push(createSpan(diff, "add"));
      } else if (newData.endsWith(oldData)) {
        // Have the same suffix
        const diff = newData.slice(0, newData.length - oldData.length).trim();
        spans.push(createSpan(diff, "add"));
        spans.push(createSpan(newData.slice(diff.length)));
      } else {
        spans.push(createSpan(oldData, "remove"));
        spans.push(createSpan(" "));
        spans.push(createSpan(newData, "add"));
      }

      previousField = field;
    }

    return spans;
  }

  #formatTextByAddressCategory(fieldName) {
    let data = [];
    switch (fieldName) {
      case "street-address":
        data = [
          [
            fieldName,
            FormAutofillUtils.toOneLineAddress(
              this.oldRecord["street-address"]
            ),
            FormAutofillUtils.toOneLineAddress(
              this.newRecord["street-address"]
            ),
          ],
        ];
        break;
      case "address":
        data = [
          [
            "address-level2",
            this.oldRecord["address-level2"],
            this.newRecord["address-level2"],
          ],
          [
            "address-level1",
            FormAutofillUtils.getAbbreviatedSubregionName(
              this.oldRecord["address-level1"],
              this.oldRecord.country
            ) || this.oldRecord["address-level1"],
            FormAutofillUtils.getAbbreviatedSubregionName(
              this.newRecord["address-level1"],
              this.newRecord.country
            ) || this.newRecord["address-level1"],
          ],
          [
            "postal-code",
            this.oldRecord["postal-code"],
            this.newRecord["postal-code"],
          ],
        ];
        break;
      case "name":
      case "country":
      case "tel":
      case "email":
      case "organization":
        data = [
          [fieldName, this.oldRecord[fieldName], this.newRecord[fieldName]],
        ];
        break;
    }

    const showDiff = !!Object.keys(this.oldRecord).length;
    return this.#formatLine(data, showDiff);
  }

  renderDescription() {
    if (lazy.formAutofillStorage.addresses.isEmpty()) {
      super.renderDescription();
    } else {
      this.description?.setAttribute("style", "display:none");
    }
  }

  renderContent() {
    this.content.replaceChildren();

    // Each section contains address fields that are grouped together while displaying
    // the doorhanger.
    for (const { imgClass, categories } of this.ui.content.sections) {
      // Add all the address fields that are in the same category
      let texts = [];
      categories.forEach(category => {
        const line = this.#formatTextByAddressCategory(category);
        if (line.length) {
          texts.push(line);
        }
      });

      // If there is no data for this section, just ignore it.
      if (!texts.length) {
        continue;
      }

      const section = this.doc.createElement("div");
      section.setAttribute("class", "address-save-update-row-container");

      // Add image icon for this section
      //const img = this.doc.createElement("img");
      const img = this.doc.createXULElement("image");
      img.setAttribute("class", imgClass);
      // ToDo: provide meaningful alt values (bug 1870155):
      img.setAttribute("alt", "");
      section.appendChild(img);

      // Each line is consisted of multiple <span> to form diff style texts
      const lineContainer = this.doc.createElement("div");
      for (const spans of texts) {
        const p = this.doc.createElement("p");
        spans.forEach(span => p.appendChild(span));
        lineContainer.appendChild(p);
      }
      section.appendChild(lineContainer);

      this.content.appendChild(section);

      // Put the edit address button in the first section
      if (!AddressSaveDoorhanger.editButton(this.panel)) {
        const button = this.doc.createElement("button");
        button.setAttribute("id", AddressSaveDoorhanger.editButtonId);
        button.setAttribute("class", "address-capture-icon-button");
        this.doc.l10n.setAttributes(
          button,
          "address-capture-edit-address-button"
        );

        // The element will be removed after the popup is closed
        /* eslint-disable mozilla/balanced-listeners */
        button.addEventListener("click", event => {
          event.stopPropagation();
          this.closeDoorhanger("edit-address");
        });
        section.appendChild(button);
      }
    }
  }

  // The record to be saved by this doorhanger
  recordToSave() {
    return this.newRecord;
  }
}

/**
 * Address Update doorhanger and Address Save doorhanger have the same implementation.
 * The only difference is UI.
 */
export class AddressUpdateDoorhanger extends AddressSaveDoorhanger {
  static telemetryObject = "update_doorhanger";
}

export class AddressEditDoorhanger extends AutofillDoorhanger {
  static telemetryType = AutofillTelemetry.ADDRESS;
  static telemetryObject = "edit_doorhanger";

  constructor(browser, record, flowId) {
    // Address edit dialog doesn't have "old" record
    super(browser, null, record, flowId);

    this.country = record.country || FormAutofill.DEFAULT_REGION;
  }

  // Address edit doorhanger changes layout according to the country
  #layout = null;
  get layout() {
    if (this.#layout?.country != this.country) {
      this.#layout = FormAutofillUtils.getFormFormat(this.country);
    }
    return this.#layout;
  }

  get country() {
    return this.newRecord.country;
  }

  set country(c) {
    if (this.newRecord.country == c) {
      return;
    }

    // `recordToSave` only contains the latest data the current country support.
    // For example, if a country doesn't have `address-level2`, `recordToSave`
    // will not have the address field.
    // `newRecord` is where we keep all the data regardless what the country is.
    // Merge `recordToSave` to `newRecord` before switching country to keep
    // `newRecord` update-to-date.
    this.newRecord = Object.assign(this.newRecord, this.recordToSave());

    // The layout of the address edit doorhanger should be changed when the
    // country is changed.
    this.#buildCountrySpecificAddressFields();
  }

  renderContent() {
    this.content.replaceChildren();

    this.#buildAddressFields(this.content, this.ui.content.fixedFields);

    this.#buildCountrySpecificAddressFields();
  }

  // Put address fields that should be in the same line together.
  // Determined by the `newLine` property that is defined in libaddressinput
  #buildAddressFields(container, fields) {
    const createRowContainer = () => {
      const div = this.doc.createElement("div");
      div.setAttribute("class", "address-edit-row-container");
      container.appendChild(div);
      return div;
    };

    let row = null;
    let createRow = true;
    for (const { fieldId, newLine } of fields) {
      if (createRow) {
        row = createRowContainer();
      }
      row.appendChild(this.#createInputField(fieldId));
      createRow = newLine;
    }
  }

  #buildCountrySpecificAddressFields() {
    const fixedFieldIds = this.ui.content.fixedFields.map(f => f.fieldId);
    let container = this.doc.getElementById(
      "country-specific-fields-container"
    );
    if (container) {
      // Country-specific fields might be rebuilt after users update the country
      // field, so if the container already exists, we remove all its childern and
      // then rebuild it.
      container.replaceChildren();
    } else {
      container = this.doc.createElement("div");
      container.setAttribute("id", "country-specific-fields-container");

      // Find where to insert country-specific fields
      const nth = fixedFieldIds.indexOf(
        this.ui.content.countrySpecificFieldsBefore
      );
      this.content.insertBefore(container, this.content.children[nth]);
    }

    this.#buildAddressFields(
      container,
      // Filter out fields that are always displayed
      this.layout.fieldsOrder.filter(f => !fixedFieldIds.includes(f.fieldId))
    );
  }

  #buildCountryMenupopup() {
    const menupopup = this.doc.createXULElement("menupopup");

    let menuitem = this.doc.createXULElement("menuitem");
    menuitem.setAttribute("value", "");
    menupopup.appendChild(menuitem);

    const countries = [...FormAutofill.countries.entries()].sort((e1, e2) =>
      e1[1].localeCompare(e2[1])
    );
    for (const [country] of countries) {
      const countryName = Services.intl.getRegionDisplayNames(undefined, [
        country.toLowerCase(),
      ]);
      menuitem = this.doc.createXULElement("menuitem");
      menuitem.setAttribute("label", countryName);
      menuitem.setAttribute("value", country);
      menupopup.appendChild(menuitem);
    }

    return menupopup;
  }

  #buildAddressLevel1Menupopup() {
    const menupopup = this.doc.createXULElement("menupopup");

    let menuitem = this.doc.createXULElement("menuitem");
    menuitem.setAttribute("value", "");
    menupopup.appendChild(menuitem);

    for (const [regionCode, regionName] of this.layout.addressLevel1Options) {
      menuitem = this.doc.createXULElement("menuitem");
      menuitem.setAttribute("label", regionCode);
      menuitem.setAttribute("value", regionName);
      menupopup.appendChild(menuitem);
    }

    return menupopup;
  }

  /**
   * Creates an input field with a label and attaches it to a container element.
   * The type of the input field is determined by the `fieldName`.
   *
   * @param {string} fieldName The name of the address field
   */
  #createInputField(fieldName) {
    const div = this.doc.createElement("div");
    div.setAttribute("class", "address-edit-input-container");

    const inputId = AddressEditDoorhanger.getInputId(fieldName);
    const label = this.doc.createElement("label");
    label.setAttribute("for", inputId);

    switch (fieldName) {
      case "address-level1":
        this.doc.l10n.setAttributes(label, this.layout.addressLevel1L10nId);
        break;
      case "address-level2":
        this.doc.l10n.setAttributes(label, this.layout.addressLevel2L10nId);
        break;
      case "address-level3":
        this.doc.l10n.setAttributes(label, this.layout.addressLevel3L10nId);
        break;
      case "postal-code":
        this.doc.l10n.setAttributes(label, this.layout.postalCodeL10nId);
        break;
      case "country":
        // workaround because `autofill-address-country` is already defined
        this.doc.l10n.setAttributes(
          label,
          `autofill-address-${fieldName}-only`
        );
        break;
      default:
        this.doc.l10n.setAttributes(label, `autofill-address-${fieldName}`);
        break;
    }
    div.appendChild(label);

    let input;
    let popup;
    if ("street-address".includes(fieldName)) {
      input = this.doc.createElement("textarea");
      input.setAttribute("rows", 3);
    } else if (fieldName == "country") {
      input = this.doc.createXULElement("menulist");
      popup = this.#buildCountryMenupopup();
      popup.addEventListener("popuphidden", e => e.stopPropagation());
      input.appendChild(popup);

      // The element will be removed after the popup is closed
      /* eslint-disable mozilla/balanced-listeners */
      input.addEventListener("command", event => {
        event.stopPropagation();
        this.country = input.selectedItem.value;
      });
    } else if (
      fieldName == "address-level1" &&
      this.layout.addressLevel1Options
    ) {
      input = this.doc.createXULElement("menulist");
      popup = this.#buildAddressLevel1Menupopup();
      popup.addEventListener("popuphidden", e => e.stopPropagation());
      input.appendChild(popup);
    } else {
      input = this.doc.createElement("input");
    }

    input.setAttribute("id", inputId);

    if (popup) {
      input.selectedItem =
        FormAutofillUtils.findAddressSelectOptionWithMenuPopup(
          popup,
          this.newRecord,
          fieldName
        );
    } else {
      input.value = this.newRecord[fieldName] ?? "";
    }

    div.appendChild(input);

    return div;
  }

  /*
   * This method generates a unique input ID using the field name of the address field.
   *
   * @param {string} fieldName The name of the address field
   */
  static getInputId(fieldName) {
    return `address-edit-${fieldName}-input`;
  }

  /*
   * Return a regular expression that matches the ID pattern generated by getInputId.
   */
  static #getInputIdMatchRegexp() {
    const regex = /^address-edit-(.+)-input$/;
    return regex;
  }

  /**
   * Collects data from all visible address field inputs within the doorhanger.
   * Since address fields may vary by country, only fields present for the
   * current country's address format are included in the record.
   */
  recordToSave() {
    let record = {};
    const regex = AddressEditDoorhanger.#getInputIdMatchRegexp();
    const elements = this.panel.querySelectorAll("input, textarea, menulist");
    for (const element of elements) {
      const match = element.id.match(regex);
      if (match && match[1]) {
        record[match[1]] = element.value;
      }
    }
    return record;
  }

  onEventCallback(state) {
    super.onEventCallback(state);

    // Close the edit address doorhanger when it has been dismissed.
    if (state == "dismissed") {
      this.closeDoorhanger("cancel");
    }
  }
}

export class CreditCardSaveDoorhanger extends AutofillDoorhanger {
  static contentClass = "credit-card-capture-content";

  static telemetryType = AutofillTelemetry.CREDIT_CARD;
  static telemetryObject = "capture_doorhanger";

  static spotlightURL = "about:preferences#privacy-credit-card-autofill";

  constructor(browser, oldRecord, newRecord, flowId) {
    super(browser, oldRecord, newRecord, flowId);
  }

  /**
   * We have not yet sync address and credit card design. After syncing,
   * we should be able to use the same "class"
   */
  static content(panel) {
    return panel.querySelector(`.${CreditCardSaveDoorhanger.contentClass}`);
  }
  get content() {
    return CreditCardSaveDoorhanger.content(this.panel);
  }

  addCheckboxListener() {
    if (!this.ui.options.checkbox) {
      return;
    }

    const { checkbox } = this.panel;
    if (checkbox && !checkbox.hidden) {
      checkbox.addEventListener("command", event => {
        let { secondaryButton, menubutton } =
          event.target.closest("popupnotification");
        let checked = event.target.checked;
        Services.prefs.setBoolPref("services.sync.engine.creditcards", checked);
        secondaryButton.disabled = checked;
        menubutton.disabled = checked;
        lazy.log.debug("Set creditCard sync to", checked);
      });
    }
  }

  removeCheckboxListener() {
    if (!this.ui.options.checkbox) {
      return;
    }

    const { checkbox } = this.panel;

    if (checkbox && !checkbox.hidden) {
      checkbox.removeEventListener(
        "command",
        this.ui.options.checkbox.callback
      );
    }
  }

  appendDescription() {
    const docFragment = this.doc.createDocumentFragment();

    const label = this.doc.createXULElement("label");
    this.doc.l10n.setAttributes(label, this.ui.description.l10nId);
    docFragment.appendChild(label);

    const descriptionWrapper = this.doc.createXULElement("hbox");
    descriptionWrapper.className = "desc-message-box";

    const number =
      this.newRecord["cc-number"] || this.newRecord["cc-number-decrypted"];
    const name = this.newRecord["cc-name"];
    const network = lazy.CreditCard.getType(number);

    const descriptionIcon = lazy.CreditCard.getCreditCardLogo(network);
    if (descriptionIcon) {
      const icon = this.doc.createXULElement("image");
      if (
        typeof descriptionIcon == "string" &&
        (descriptionIcon.includes("cc-logo") ||
          descriptionIcon.includes("icon-credit"))
      ) {
        icon.setAttribute("src", descriptionIcon);
      }
      descriptionWrapper.appendChild(icon);
    }

    const description = this.doc.createXULElement("description");
    description.textContent =
      `${lazy.CreditCard.getMaskedNumber(number)}` + (name ? `, ${name}` : ``);

    descriptionWrapper.appendChild(description);
    docFragment.appendChild(descriptionWrapper);

    this.content.appendChild(docFragment);
  }

  appendPrivacyPanelLink() {
    const privacyLinkElement = this.doc.createXULElement("label", {
      is: "text-link",
    });
    privacyLinkElement.setAttribute("useoriginprincipal", true);
    privacyLinkElement.setAttribute(
      "href",
      CreditCardSaveDoorhanger.spotlightURL ||
        "about:preferences#privacy-form-autofill"
    );

    const linkId = `autofill-options-link${
      AppConstants.platform == "macosx" ? "-osx" : ""
    }`;
    this.doc.l10n.setAttributes(privacyLinkElement, linkId);

    this.content.appendChild(privacyLinkElement);
  }

  // TODO: Currently, the header and description are unused. Align
  // these with the address doorhanger's implementation during
  // the credit card doorhanger redesign.
  getNotificationHeader() {
    return l10n.formatValueSync(this.ui.header.l10nId);
  }

  renderHeader() {
    // Not implement
  }

  renderDescription() {
    // Not implement
  }

  renderContent() {
    this.content.replaceChildren();

    this.appendDescription();

    this.appendPrivacyPanelLink();
  }

  onEventCallback(state) {
    super.onEventCallback(state);

    if (state == "removed" || state == "dismissed") {
      this.removeCheckboxListener();
    } else if (state == "shown") {
      this.addCheckboxListener();
    }
  }

  // The record to be saved by this doorhanger
  recordToSave() {
    return this.newRecord;
  }
}

export class CreditCardUpdateDoorhanger extends CreditCardSaveDoorhanger {
  static telemetryType = AutofillTelemetry.CREDIT_CARD;
  static telemetryObject = "update_doorhanger";

  constructor(browser, oldRecord, newRecord, flowId) {
    super(browser, oldRecord, newRecord, flowId);
  }
}

CONTENT = {
  [AddressSaveDoorhanger.name]: {
    id: "address-save-update",
    anchor: {
      id: "autofill-address-notification-icon",
      URL: "chrome://formautofill/content/formfill-anchor.svg",
      tooltiptext: l10n.formatValueSync("autofill-message-tooltip"),
    },
    header: {
      l10nId: "address-capture-save-doorhanger-header",
    },
    description: {
      l10nId: "address-capture-save-doorhanger-description",
    },
    menu: [
      {
        l10nId: "address-capture-manage-address-button",
        evt: "open-pref",
      },
      {
        l10nId: "address-capture-learn-more-button",
        evt: "learn-more",
      },
    ],
    content: {
      // We divide address data into two sections to display in the Address Save Doorhanger.
      sections: [
        {
          imgClass: "address-capture-img-address",
          categories: [
            "name",
            "organization",
            "street-address",
            "address",
            "country",
          ],
        },
        {
          imgClass: "address-capture-img-email",
          categories: ["email", "tel"],
        },
      ],
    },
    footer: {
      mainAction: {
        l10nId: "address-capture-save-button",
        callbackState: "create",
      },
      secondaryActions: [
        {
          l10nId: "address-capture-not-now-button",
          callbackState: "cancel",
        },
      ],
    },
    options: {
      autofocus: true,
      persistWhileVisible: true,
      hideClose: true,
    },
  },

  [AddressUpdateDoorhanger.name]: {
    id: "address-save-update",
    anchor: {
      id: "autofill-address-notification-icon",
      URL: "chrome://formautofill/content/formfill-anchor.svg",
      tooltiptext: l10n.formatValueSync("autofill-message-tooltip"),
    },
    header: {
      l10nId: "address-capture-update-doorhanger-header",
    },
    menu: [
      {
        l10nId: "address-capture-manage-address-button",
        evt: "open-pref",
      },
      {
        l10nId: "address-capture-learn-more-button",
        evt: "learn-more",
      },
    ],
    content: {
      // Addresses fields are categoried into two sections, each section
      // has its own icon
      sections: [
        {
          imgClass: "address-capture-img-address",
          categories: [
            "name",
            "organization",
            "street-address",
            "address",
            "country",
          ],
        },
        {
          imgClass: "address-capture-img-email",
          categories: ["email", "tel"],
        },
      ],
    },
    footer: {
      mainAction: {
        l10nId: "address-capture-update-button",
        callbackState: "update",
      },
      secondaryActions: [
        {
          l10nId: "address-capture-not-now-button",
          callbackState: "cancel",
        },
      ],
    },
    options: {
      autofocus: true,
      persistWhileVisible: true,
      hideClose: true,
    },
  },

  [AddressEditDoorhanger.name]: {
    id: "address-edit",
    anchor: {
      id: "autofill-address-notification-icon",
      URL: "chrome://formautofill/content/formfill-anchor.svg",
      tooltiptext: l10n.formatValueSync("autofill-message-tooltip"),
    },
    header: {
      l10nId: "address-capture-edit-doorhanger-header",
    },
    menu: null,
    content: {
      // We start by organizing the fields in a specific order:
      // name, organization, and country are fixed and come first.
      // These are followed by country-specific fields, which are
      // laid out differently for each country (as referenced from libaddressinput).
      // Finally, we place the telephone and email fields at the end.
      countrySpecificFieldsBefore: "tel",
      fixedFields: [
        { fieldId: "name", newLine: true },
        { fieldId: "organization", newLine: true },
        { fieldId: "country", newLine: true },
        { fieldId: "tel", newLine: false },
        { fieldId: "email", newLine: true },
      ],
    },
    footer: {
      mainAction: {
        l10nId: "address-capture-save-button",
        callbackState: "save",
      },
      secondaryActions: [
        {
          l10nId: "address-capture-cancel-button",
          callbackState: "cancel",
          dismiss: true,
        },
      ],
    },
    options: {
      autofocus: true,
      persistWhileVisible: true,
      hideClose: true,
    },
  },

  [CreditCardSaveDoorhanger.name]: {
    id: "credit-card-save-update",
    anchor: {
      id: "autofill-credit-card-notification-icon",
      URL: "chrome://formautofill/content/formfill-anchor.svg",
      tooltiptext: l10n.formatValueSync("autofill-message-tooltip"),
    },
    header: {
      l10nId: "credit-card-save-doorhanger-header",
    },
    description: {
      l10nId: "credit-card-save-doorhanger-description",
    },
    content: {},
    footer: {
      mainAction: {
        l10nId: "credit-card-capture-save-button",
        callbackState: "create",
      },
      secondaryActions: [
        {
          l10nId: "credit-card-capture-cancel-button",
          callbackState: "cancel",
        },
        {
          l10nId: "credit-card-capture-never-save-button",
          callbackState: "disable",
        },
      ],
    },
    options: {
      persistWhileVisible: true,
      popupIconURL: "chrome://formautofill/content/icon-credit-card.svg",
      hideClose: true,

      checkbox: {
        get checked() {
          return Services.prefs.getBoolPref("services.sync.engine.creditcards");
        },
        get label() {
          // Only set the label when the fallowing conditions existed:
          // - sync account is set
          // - credit card sync is disabled
          // - credit card sync is available
          // otherwise return null label to hide checkbox.
          return Services.prefs.prefHasUserValue("services.sync.username") &&
            !Services.prefs.getBoolPref("services.sync.engine.creditcards") &&
            Services.prefs.getBoolPref(
              "services.sync.engine.creditcards.available"
            )
            ? l10n.formatValueSync(
                "credit-card-doorhanger-credit-cards-sync-checkbox"
              )
            : null;
        },
      },
    },
  },

  [CreditCardUpdateDoorhanger.name]: {
    id: "credit-card-save-update",
    anchor: {
      id: "autofill-credit-card-notification-icon",
      URL: "chrome://formautofill/content/formfill-anchor.svg",
      tooltiptext: l10n.formatValueSync("autofill-message-tooltip"),
    },
    header: {
      l10nId: "credit-card-update-doorhanger-header",
    },
    description: {
      l10nId: "credit-card-update-doorhanger-description",
    },
    content: {},
    footer: {
      mainAction: {
        l10nId: "credit-card-capture-update-button",
        callbackState: "update",
      },
      secondaryActions: [
        {
          l10nId: "credit-card-capture-save-new-button",
          callbackState: "create",
        },
      ],
    },
    options: {
      persistWhileVisible: true,
      popupIconURL: "chrome://formautofill/content/icon-credit-card.svg",
      hideClose: true,
    },
  },
};

export let FormAutofillPrompter = {
  async promptToSaveCreditCard(
    browser,
    storage,
    flowId,
    { oldRecord, newRecord }
  ) {
    const showUpdateDoorhanger = !!Object.keys(oldRecord).length;

    const { ownerGlobal: win } = browser;
    win.MozXULElement.insertFTLIfNeeded(
      "toolkit/formautofill/formAutofill.ftl"
    );

    let action;
    const doorhanger = showUpdateDoorhanger
      ? new CreditCardUpdateDoorhanger(browser, oldRecord, newRecord, flowId)
      : new CreditCardSaveDoorhanger(browser, oldRecord, newRecord, flowId);
    action = await doorhanger.show();

    lazy.log.debug(`Doorhanger action is ${action}`);

    if (action == "cancel") {
      return;
    } else if (action == "disable") {
      Services.prefs.setBoolPref(ENABLED_AUTOFILL_CREDITCARDS_PREF, false);
      return;
    }

    if (!(await lazy.OSKeyStore.ensureLoggedIn(false)).authenticated) {
      lazy.log.warn("User canceled encryption login");
      return;
    }

    this._updateStorageAfterInteractWithPrompt(
      browser,
      storage,
      "credit-card",
      action == "update" ? oldRecord : null,
      doorhanger.recordToSave()
    );
  },

  /**
   * Show save or update address doorhanger
   *
   * @param {Element<browser>} browser  Browser to show the save/update address prompt
   * @param {object} storage Address storage
   * @param {string} flowId Unique GUID to record a series of the same user action
   * @param {object} options
   * @param {object} [options.oldRecord] Record to be merged
   * @param {object} [options.newRecord] Record with more information
   */
  async promptToSaveAddress(
    browser,
    storage,
    flowId,
    { oldRecord, newRecord }
  ) {
    const showUpdateDoorhanger = !!Object.keys(oldRecord).length;

    lazy.log.debug(
      `Show the ${showUpdateDoorhanger ? "update" : "save"} address doorhanger`
    );

    const { ownerGlobal: win } = browser;
    win.MozXULElement.insertFTLIfNeeded(
      "toolkit/formautofill/formAutofill.ftl"
    );
    // address-autofill-* are defined in browser/preferences now
    win.MozXULElement.insertFTLIfNeeded("browser/preferences/formAutofill.ftl");

    let doorhanger;
    let action;
    while (true) {
      doorhanger = showUpdateDoorhanger
        ? new AddressUpdateDoorhanger(browser, oldRecord, newRecord, flowId)
        : new AddressSaveDoorhanger(browser, oldRecord, newRecord, flowId);
      action = await doorhanger.show();

      if (action == "edit-address") {
        doorhanger = new AddressEditDoorhanger(
          browser,
          { ...oldRecord, ...newRecord },
          flowId
        );
        action = await doorhanger.show();

        // If users cancel the edit address doorhanger, show the save/update
        // doorhanger again.
        if (action == "cancel") {
          continue;
        }
      }

      break;
    }

    lazy.log.debug(`Doorhanger action is ${action}`);

    if (action == "cancel") {
      return;
    }

    this._updateStorageAfterInteractWithPrompt(
      browser,
      storage,
      "address",
      showUpdateDoorhanger ? oldRecord : null,
      doorhanger.recordToSave()
    );
  },

  // TODO: Simplify the code after integrating credit card prompt to use AutofillDoorhanger
  async _updateStorageAfterInteractWithPrompt(
    browser,
    storage,
    type,
    oldRecord,
    newRecord
  ) {
    let changedGUID = null;
    if (oldRecord) {
      changedGUID = oldRecord.guid;
      await storage.update(changedGUID, newRecord, true);
    } else {
      changedGUID = await storage.add(newRecord);
    }
    storage.notifyUsed(changedGUID);

    const hintId = `confirmation-hint-${type}-${
      oldRecord ? "updated" : "created"
    }`;
    showConfirmation(browser, hintId);
  },
};
PK
!<�~-~-4chrome/toolkit/res/autofill/FormAutofillSync.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import {
  Changeset,
  Store,
  SyncEngine,
  Tracker,
} from "resource://services-sync/engines.sys.mjs";
import { CryptoWrapper } from "resource://services-sync/record.sys.mjs";
import { Utils } from "resource://services-sync/util.sys.mjs";

import { SCORE_INCREMENT_XLARGE } from "resource://services-sync/constants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  Log: "resource://gre/modules/Log.sys.mjs",
  formAutofillStorage: "resource://autofill/FormAutofillStorage.sys.mjs",
});

// A helper to sanitize address and creditcard records suitable for logging.
export function sanitizeStorageObject(ob) {
  if (!ob) {
    return null;
  }
  const allowList = ["timeCreated", "timeLastUsed", "timeLastModified"];
  let result = {};
  for (let key of Object.keys(ob)) {
    let origVal = ob[key];
    if (allowList.includes(key)) {
      result[key] = origVal;
    } else if (typeof origVal == "string") {
      result[key] = "X".repeat(origVal.length);
    } else {
      result[key] = typeof origVal; // *shrug*
    }
  }
  return result;
}

export function AutofillRecord(collection, id) {
  CryptoWrapper.call(this, collection, id);
}

AutofillRecord.prototype = {
  toEntry() {
    return Object.assign(
      {
        guid: this.id,
      },
      this.entry
    );
  },

  fromEntry(entry) {
    this.id = entry.guid;
    this.entry = entry;
    // The GUID is already stored in record.id, so we nuke it from the entry
    // itself to save a tiny bit of space. The formAutofillStorage clones profiles,
    // so nuking in-place is OK.
    delete this.entry.guid;
  },

  cleartextToString() {
    // And a helper so logging a *Sync* record auto sanitizes.
    let record = this.cleartext;
    return JSON.stringify({ entry: sanitizeStorageObject(record.entry) });
  },
};
Object.setPrototypeOf(AutofillRecord.prototype, CryptoWrapper.prototype);

// Profile data is stored in the "entry" object of the record.
Utils.deferGetSet(AutofillRecord, "cleartext", ["entry"]);

function FormAutofillStore(name, engine) {
  Store.call(this, name, engine);
}

FormAutofillStore.prototype = {
  _subStorageName: null, // overridden below.
  _storage: null,

  get storage() {
    if (!this._storage) {
      this._storage = lazy.formAutofillStorage[this._subStorageName];
    }
    return this._storage;
  },

  async getAllIDs() {
    let result = {};
    for (let { guid } of await this.storage.getAll({ includeDeleted: true })) {
      result[guid] = true;
    }
    return result;
  },

  async changeItemID(oldID, newID) {
    this.storage.changeGUID(oldID, newID);
  },

  // Note: this function intentionally returns false in cases where we only have
  // a (local) tombstone - and formAutofillStorage.get() filters them for us.
  async itemExists(id) {
    return Boolean(await this.storage.get(id));
  },

  async applyIncoming(remoteRecord) {
    if (remoteRecord.deleted) {
      this._log.trace("Deleting record", remoteRecord);
      this.storage.remove(remoteRecord.id, { sourceSync: true });
      return;
    }

    // Records from the remote might come from an older device. To ensure that
    // remote records from older devices can still sync with the local records,
    // we migrate the remote records. This enables the merging of older records
    // with newer records.
    //
    // Currently, this migration is only used for converting `*-name` fields to `name` fields.
    // The migration process involves:
    // 1. Generating a `name` field so we don't assume the `name` field is empty, thereby
    //    avoiding erasing its value.
    // 2. Removing deprecated *-name fields from the remote record because the autofill storage
    //    does not expect to see those fields.
    this.storage.migrateRemoteRecord(remoteRecord.entry);

    if (await this.itemExists(remoteRecord.id)) {
      // We will never get a tombstone here, so we are updating a real record.
      await this._doUpdateRecord(remoteRecord);
      return;
    }

    // No matching local record. Try to dedupe a NEW local record.
    let localDupeID = await this.storage.findDuplicateGUID(
      remoteRecord.toEntry()
    );
    if (localDupeID) {
      this._log.trace(
        `Deduping local record ${localDupeID} to remote`,
        remoteRecord
      );
      // Change the local GUID to match the incoming record, then apply the
      // incoming record.
      await this.changeItemID(localDupeID, remoteRecord.id);
      await this._doUpdateRecord(remoteRecord);
      return;
    }

    // We didn't find a dupe, either, so must be a new record (or possibly
    // a non-deleted version of an item we have a tombstone for, which add()
    // handles for us.)
    this._log.trace("Add record", remoteRecord);
    let entry = remoteRecord.toEntry();
    await this.storage.add(entry, { sourceSync: true });
  },

  async createRecord(id, collection) {
    this._log.trace("Create record", id);
    let record = new AutofillRecord(collection, id);
    let entry = await this.storage.get(id, {
      rawData: true,
    });
    if (entry) {
      record.fromEntry(entry);
    } else {
      // We should consider getting a more authortative indication it's actually deleted.
      this._log.debug(
        `Failed to get autofill record with id "${id}", assuming deleted`
      );
      record.deleted = true;
    }
    return record;
  },

  async _doUpdateRecord(record) {
    this._log.trace("Updating record", record);

    let entry = record.toEntry();
    let { forkedGUID } = await this.storage.reconcile(entry);
    if (this._log.level <= lazy.Log.Level.Debug) {
      let forkedRecord = forkedGUID ? await this.storage.get(forkedGUID) : null;
      let reconciledRecord = await this.storage.get(record.id);
      this._log.debug("Updated local record", {
        forked: sanitizeStorageObject(forkedRecord),
        updated: sanitizeStorageObject(reconciledRecord),
      });
    }
  },

  // NOTE: Because we re-implement the incoming/reconcilliation logic we leave
  // the |create|, |remove| and |update| methods undefined - the base
  // implementation throws, which is what we want to happen so we can identify
  // any places they are "accidentally" called.
};
Object.setPrototypeOf(FormAutofillStore.prototype, Store.prototype);

function FormAutofillTracker(name, engine) {
  Tracker.call(this, name, engine);
}

FormAutofillTracker.prototype = {
  async observe(subject, topic, data) {
    if (topic != "formautofill-storage-changed") {
      return;
    }
    if (
      subject &&
      subject.wrappedJSObject &&
      subject.wrappedJSObject.sourceSync
    ) {
      return;
    }
    switch (data) {
      case "add":
      case "update":
      case "remove":
        this.score += SCORE_INCREMENT_XLARGE;
        break;
      default:
        this._log.debug("unrecognized autofill notification", data);
        break;
    }
  },

  onStart() {
    Services.obs.addObserver(this, "formautofill-storage-changed");
  },

  onStop() {
    Services.obs.removeObserver(this, "formautofill-storage-changed");
  },
};
Object.setPrototypeOf(FormAutofillTracker.prototype, Tracker.prototype);

// This uses the same conventions as BookmarkChangeset in
// services/sync/modules/engines/bookmarks.js. Specifically,
// - "synced" means the item has already been synced (or we have another reason
//   to ignore it), and should be ignored in most methods.
class AutofillChangeset extends Changeset {
  constructor() {
    super();
  }

  getModifiedTimestamp(_id) {
    throw new Error("Don't use timestamps to resolve autofill merge conflicts");
  }

  has(id) {
    let change = this.changes[id];
    if (change) {
      return !change.synced;
    }
    return false;
  }

  delete(id) {
    let change = this.changes[id];
    if (change) {
      // Mark the change as synced without removing it from the set. We do this
      // so that we can update FormAutofillStorage in `trackRemainingChanges`.
      change.synced = true;
    }
  }
}

function FormAutofillEngine(service, name) {
  SyncEngine.call(this, name, service);
}

FormAutofillEngine.prototype = {
  // the priority for this engine is == addons, so will happen after bookmarks
  // prefs and tabs, but before forms, history, etc.
  syncPriority: 5,

  // We don't use SyncEngine.initialize() for this, as we initialize even if
  // the engine is disabled, and we don't want to be the loader of
  // FormAutofillStorage in this case.
  async _syncStartup() {
    await lazy.formAutofillStorage.initialize();
    await SyncEngine.prototype._syncStartup.call(this);
  },

  // We handle reconciliation in the store, not the engine.
  async _reconcile() {
    return true;
  },

  emptyChangeset() {
    return new AutofillChangeset();
  },

  async _uploadOutgoing() {
    this._modified.replace(this._store.storage.pullSyncChanges());
    await SyncEngine.prototype._uploadOutgoing.call(this);
  },

  // Typically, engines populate the changeset before downloading records.
  // However, we handle conflict resolution in the store, so we can wait
  // to pull changes until we're ready to upload.
  async pullAllChanges() {
    return {};
  },

  async pullNewChanges() {
    return {};
  },

  async trackRemainingChanges() {
    this._store.storage.pushSyncChanges(this._modified.changes);
  },

  _deleteId(id) {
    this._noteDeletedId(id);
  },

  async _resetClient() {
    await lazy.formAutofillStorage.initialize();
    this._store.storage.resetSync();
    await this.resetLastSync(0);
  },

  async _wipeClient() {
    await lazy.formAutofillStorage.initialize();
    this._store.storage.removeAll({ sourceSync: true });
  },
};
Object.setPrototypeOf(FormAutofillEngine.prototype, SyncEngine.prototype);

// The concrete engines

function AddressesRecord(collection, id) {
  AutofillRecord.call(this, collection, id);
}

AddressesRecord.prototype = {
  _logName: "Sync.Record.Addresses",
};
Object.setPrototypeOf(AddressesRecord.prototype, AutofillRecord.prototype);

function AddressesStore(name, engine) {
  FormAutofillStore.call(this, name, engine);
}

AddressesStore.prototype = {
  _subStorageName: "addresses",
};
Object.setPrototypeOf(AddressesStore.prototype, FormAutofillStore.prototype);

export function AddressesEngine(service) {
  FormAutofillEngine.call(this, service, "Addresses");
}

AddressesEngine.prototype = {
  _trackerObj: FormAutofillTracker,
  _storeObj: AddressesStore,
  _recordObj: AddressesRecord,

  get prefName() {
    return "addresses";
  },
};
Object.setPrototypeOf(AddressesEngine.prototype, FormAutofillEngine.prototype);

function CreditCardsRecord(collection, id) {
  AutofillRecord.call(this, collection, id);
}

CreditCardsRecord.prototype = {
  _logName: "Sync.Record.CreditCards",
};
Object.setPrototypeOf(CreditCardsRecord.prototype, AutofillRecord.prototype);

function CreditCardsStore(name, engine) {
  FormAutofillStore.call(this, name, engine);
}

CreditCardsStore.prototype = {
  _subStorageName: "creditCards",
};
Object.setPrototypeOf(CreditCardsStore.prototype, FormAutofillStore.prototype);

export function CreditCardsEngine(service) {
  FormAutofillEngine.call(this, service, "CreditCards");
}

CreditCardsEngine.prototype = {
  _trackerObj: FormAutofillTracker,
  _storeObj: CreditCardsStore,
  _recordObj: CreditCardsRecord,
  get prefName() {
    return "creditcards";
  },
};
Object.setPrototypeOf(
  CreditCardsEngine.prototype,
  FormAutofillEngine.prototype
);
PK
!<���AA=chrome/toolkit/res/autofill/ProfileAutoCompleteResult.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  CreditCard: "resource://gre/modules/CreditCard.sys.mjs",
  FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
});

ChromeUtils.defineLazyGetter(
  lazy,
  "l10n",
  () => new Localization(["toolkit/formautofill/formAutofill.ftl"], true)
);

class ProfileAutoCompleteResult {
  externalEntries = [];

  constructor(
    searchString,
    focusedFieldDetail,
    allFieldNames,
    matchingProfiles,
    { resultCode = null, isSecure = true, isInputAutofilled = false }
  ) {
    // nsISupports
    this.QueryInterface = ChromeUtils.generateQI(["nsIAutoCompleteResult"]);

    // The user's query string
    this.searchString = searchString;
    // The field name of the focused input.
    this._focusedFieldName = focusedFieldDetail.fieldName;
    // The content dom reference id of the focused input.
    this._focusedElementId = focusedFieldDetail.elementId;
    // The matching profiles contains the information for filling forms.
    this._matchingProfiles = matchingProfiles;
    // The default item that should be entered if none is selected
    this.defaultIndex = 0;
    // The reason the search failed
    this.errorDescription = "";
    // The value used to determine whether the form is secure or not.
    this._isSecure = isSecure;
    // The value to indicate whether the focused input has been autofilled or not.
    this._isInputAutofilled = isInputAutofilled;
    // All fillable field names in the form including the field name of the currently-focused input.
    this._allFieldNames = [
      ...this._matchingProfiles.reduce((fieldSet, curProfile) => {
        for (let field of Object.keys(curProfile)) {
          fieldSet.add(field);
        }

        return fieldSet;
      }, new Set()),
    ].filter(field => allFieldNames.includes(field));

    // Force return success code if the focused field is auto-filled in order
    // to show clear form button popup.
    if (isInputAutofilled) {
      resultCode = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
    }
    // The result code of this result object.
    if (resultCode) {
      this.searchResult = resultCode;
    } else {
      this.searchResult = matchingProfiles.length
        ? Ci.nsIAutoCompleteResult.RESULT_SUCCESS
        : Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
    }

    // An array of primary and secondary labels for each profile.
    this._popupLabels = this._generateLabels(
      this._focusedFieldName,
      this._allFieldNames,
      this._matchingProfiles
    );
  }

  getAt(index) {
    for (const group of [this._popupLabels, this.externalEntries]) {
      if (index < group.length) {
        return group[index];
      }
      index -= group.length;
    }

    throw Components.Exception(
      "Index out of range.",
      Cr.NS_ERROR_ILLEGAL_VALUE
    );
  }

  /**
   * @returns {number} The number of results
   */
  get matchCount() {
    return this._popupLabels.length + this.externalEntries.length;
  }

  /**
   * Get the secondary label based on the focused field name and related field names
   * in the same form.
   *
   * @param   {string} _focusedFieldName The field name of the focused input
   * @param   {Array<object>} _allFieldNames The field names in the same section
   * @param   {object} _profile The profile providing the labels to show.
   * @returns {string} The secondary label
   */
  _getSecondaryLabel(_focusedFieldName, _allFieldNames, _profile) {
    return "";
  }

  _generateLabels(_focusedFieldName, _allFieldNames, _profiles) {}

  /**
   * Get the value of the result at the given index.
   *
   * Always return empty string for form autofill feature to suppress
   * AutoCompleteController from autofilling, as we'll populate the
   * fields on our own.
   *
   * @param   {number} index The index of the result requested
   * @returns {string} The result at the specified index
   */
  getValueAt(index) {
    this.getAt(index);
    return "";
  }

  getLabelAt(index) {
    const label = this.getAt(index);
    return typeof label == "string" ? label : label.primary;
  }

  /**
   * Retrieves a comment (metadata instance)
   *
   * @param   {number} index The index of the comment requested
   * @returns {string} The comment at the specified index
   */
  getCommentAt(index) {
    const item = this.getAt(index);
    if (item.style == "status") {
      return JSON.stringify(item);
    }

    const data = {
      fillMessageData: {
        focusElementId: this._focusedElementId,
      },
    };

    const type = this.getTypeOfIndex(index);
    switch (type) {
      case "clear":
        data.fillMessageName = "FormAutofill:ClearForm";
        break;
      case "manage":
        data.fillMessageName = "FormAutofill:OpenPreferences";
        break;
      case "insecure":
        data.noLearnMore = true;
        break;
      default: {
        if (item.comment) {
          return item.comment;
        }

        data.fillMessageName = "FormAutofill:FillForm";
        data.fillMessageData.profile = this._matchingProfiles[index];
        break;
      }
    }

    return JSON.stringify({ ...item, ...data });
  }

  /**
   * Retrieves a style hint specific to a particular index.
   *
   * @param   {number} index The index of the style hint requested
   * @returns {string} The style hint at the specified index
   */
  getStyleAt(index) {
    const itemStyle = this.getAt(index).style;
    if (itemStyle) {
      return itemStyle;
    }

    switch (this.getTypeOfIndex(index)) {
      case "manage":
        return "action";
      case "clear":
        return "action";
      case "insecure":
        return "insecureWarning";
      default:
        return "autofill";
    }
  }

  /**
   * Retrieves an image url.
   *
   * @param   {number} index The index of the image url requested
   * @returns {string} The image url at the specified index
   */
  getImageAt(index) {
    return this.getAt(index).image ?? "";
  }

  /**
   * Retrieves a result
   *
   * @param   {number} index The index of the result requested
   * @returns {string} The result at the specified index
   */
  getFinalCompleteValueAt(index) {
    return this.getValueAt(index);
  }

  /**
   * Returns true if the value at the given index is removable
   *
   * @param   {number}  _index The index of the result to remove
   * @returns {boolean} True if the value is removable
   */
  isRemovableAt(_index) {
    return false;
  }

  /**
   * Removes a result from the resultset
   *
   * @param {number} _index The index of the result to remove
   */
  removeValueAt(_index) {
    // There is no plan to support removing profiles via autocomplete.
  }

  /**
   * Returns a type string that identifies te type of row at the given index.
   *
   * @param   {number} index The index of the result requested
   * @returns {string} The type at the specified index
   */
  getTypeOfIndex(index) {
    if (this._isInputAutofilled && index == 0) {
      return "clear";
    }

    if (index == this._popupLabels.length - 1) {
      return "manage";
    }

    return "item";
  }
}

export class AddressResult extends ProfileAutoCompleteResult {
  _getSecondaryLabel(focusedFieldName, allFieldNames, profile) {
    // We group similar fields into the same field name so we won't pick another
    // field in the same group as the secondary label.
    const GROUP_FIELDS = {
      name: ["name", "given-name", "additional-name", "family-name"],
      "street-address": [
        "street-address",
        "address-line1",
        "address-line2",
        "address-line3",
      ],
      "country-name": ["country", "country-name"],
      tel: [
        "tel",
        "tel-country-code",
        "tel-national",
        "tel-area-code",
        "tel-local",
        "tel-local-prefix",
        "tel-local-suffix",
      ],
    };

    const secondaryLabelOrder = [
      "street-address", // Street address
      "name", // Full name
      "address-level3", // Townland / Neighborhood / Village
      "address-level2", // City/Town
      "organization", // Company or organization name
      "address-level1", // Province/State (Standardized code if possible)
      "country-name", // Country name
      "postal-code", // Postal code
      "tel", // Phone number
      "email", // Email address
    ];

    for (let field in GROUP_FIELDS) {
      if (GROUP_FIELDS[field].includes(focusedFieldName)) {
        focusedFieldName = field;
        break;
      }
    }

    for (const currentFieldName of secondaryLabelOrder) {
      if (focusedFieldName == currentFieldName || !profile[currentFieldName]) {
        continue;
      }

      let matching = GROUP_FIELDS[currentFieldName]
        ? allFieldNames.some(fieldName =>
            GROUP_FIELDS[currentFieldName].includes(fieldName)
          )
        : allFieldNames.includes(currentFieldName);

      if (matching) {
        if (
          currentFieldName == "street-address" &&
          profile["-moz-street-address-one-line"]
        ) {
          return profile["-moz-street-address-one-line"];
        }
        return profile[currentFieldName];
      }
    }

    return ""; // Nothing matched.
  }

  _generateLabels(focusedFieldName, allFieldNames, profiles) {
    const manageLabel = lazy.l10n.formatValueSync(
      "autofill-manage-addresses-label"
    );

    let footerItem = {
      primary: manageLabel,
      secondary: "",
    };

    if (this._isInputAutofilled) {
      const clearLabel = lazy.l10n.formatValueSync("autofill-clear-form-label");

      let labels = [
        {
          primary: clearLabel,
        },
      ];
      labels.push(footerItem);
      return labels;
    }

    let focusedCategory =
      lazy.FormAutofillUtils.getCategoryFromFieldName(focusedFieldName);

    // Skip results without a primary label.
    let labels = profiles
      .filter(profile => {
        return !!profile[focusedFieldName];
      })
      .map(profile => {
        let primaryLabel = profile[focusedFieldName];
        if (
          focusedFieldName == "street-address" &&
          profile["-moz-street-address-one-line"]
        ) {
          primaryLabel = profile["-moz-street-address-one-line"];
        }

        let profileFields = allFieldNames.filter(
          fieldName => !!profile[fieldName]
        );

        let categories =
          lazy.FormAutofillUtils.getCategoriesFromFieldNames(profileFields);
        let status = this.getStatusNote(categories, focusedCategory);
        let secondary = this._getSecondaryLabel(
          focusedFieldName,
          allFieldNames,
          profile
        );
        const ariaLabel = [primaryLabel, secondary, status]
          .filter(chunk => !!chunk) // Exclude empty chunks.
          .join(" ");
        return {
          primary: primaryLabel,
          secondary,
          status,
          ariaLabel,
        };
      });

    let allCategories =
      lazy.FormAutofillUtils.getCategoriesFromFieldNames(allFieldNames);

    if (allCategories && allCategories.length) {
      let statusItem = {
        primary: "",
        secondary: "",
        status: this.getStatusNote(allCategories, focusedCategory),
        style: "status",
      };
      labels.push(statusItem);
    }

    labels.push(footerItem);

    return labels;
  }

  getStatusNote(categories, focusedCategory) {
    if (!categories || !categories.length) {
      return "";
    }

    // If the length of categories is 1, that means all the fillable fields are in the same
    // category. We will change the way to inform user according to this flag. When the value
    // is true, we show "Also autofills ...", otherwise, show "Autofills ..." only.
    let hasExtraCategories = categories.length > 1;
    // Show the categories in certain order to conform with the spec.
    let orderedCategoryList = [
      "address",
      "name",
      "organization",
      "tel",
      "email",
    ];
    let showCategories = hasExtraCategories
      ? orderedCategoryList.filter(
          category =>
            categories.includes(category) && category != focusedCategory
        )
      : [orderedCategoryList.find(category => category == focusedCategory)];

    let formatter = new Intl.ListFormat(undefined, {
      style: "narrow",
    });

    let categoriesText = showCategories.map(category =>
      lazy.l10n.formatValueSync("autofill-category-" + category)
    );
    categoriesText = formatter.format(categoriesText);

    let statusTextTmplKey = hasExtraCategories
      ? "autofill-phishing-warningmessage-extracategory"
      : "autofill-phishing-warningmessage";
    return lazy.l10n.formatValueSync(statusTextTmplKey, {
      categories: categoriesText,
    });
  }
}

export class CreditCardResult extends ProfileAutoCompleteResult {
  _getSecondaryLabel(focusedFieldName, allFieldNames, profile) {
    const GROUP_FIELDS = {
      "cc-name": [
        "cc-name",
        "cc-given-name",
        "cc-additional-name",
        "cc-family-name",
      ],
      "cc-exp": ["cc-exp", "cc-exp-month", "cc-exp-year"],
    };

    const secondaryLabelOrder = [
      "cc-number", // Credit card number
      "cc-name", // Full name
      "cc-exp", // Expiration date
    ];

    for (let field in GROUP_FIELDS) {
      if (GROUP_FIELDS[field].includes(focusedFieldName)) {
        focusedFieldName = field;
        break;
      }
    }

    for (const currentFieldName of secondaryLabelOrder) {
      if (focusedFieldName == currentFieldName || !profile[currentFieldName]) {
        continue;
      }

      let matching = GROUP_FIELDS[currentFieldName]
        ? allFieldNames.some(fieldName =>
            GROUP_FIELDS[currentFieldName].includes(fieldName)
          )
        : allFieldNames.includes(currentFieldName);

      if (matching) {
        if (currentFieldName == "cc-number") {
          return lazy.CreditCard.formatMaskedNumber(profile[currentFieldName]);
        }
        return profile[currentFieldName];
      }
    }

    return ""; // Nothing matched.
  }

  _generateLabels(focusedFieldName, allFieldNames, profiles) {
    if (!this._isSecure) {
      let brandName =
        lazy.FormAutofillUtils.brandBundle.GetStringFromName("brandShortName");

      return [
        lazy.FormAutofillUtils.stringBundle.formatStringFromName(
          "insecureFieldWarningDescription",
          [brandName]
        ),
      ];
    }

    const manageLabel = lazy.l10n.formatValueSync(
      "autofill-manage-payment-methods-label"
    );

    let footerItem = {
      primary: manageLabel,
    };

    if (this._isInputAutofilled) {
      const clearLabel = lazy.l10n.formatValueSync("autofill-clear-form-label");

      let labels = [
        {
          primary: clearLabel,
        },
      ];
      labels.push(footerItem);
      return labels;
    }

    // Skip results without a primary label.
    let labels = profiles
      .filter(profile => {
        return !!profile[focusedFieldName];
      })
      .map(profile => {
        let primary = profile[focusedFieldName];

        if (focusedFieldName == "cc-number") {
          primary = lazy.CreditCard.formatMaskedNumber(primary);
        }
        const secondary = this._getSecondaryLabel(
          focusedFieldName,
          allFieldNames,
          profile
        );
        // The card type is displayed visually using an image. For a11y, we need
        // to expose it as text. We do this using aria-label. However,
        // aria-label overrides the text content, so we must include that also.
        const ccType = profile["cc-type"];
        const image = lazy.CreditCard.getCreditCardLogo(ccType);
        const ccTypeL10nId = lazy.CreditCard.getNetworkL10nId(ccType);
        const ccTypeName = ccTypeL10nId
          ? lazy.l10n.formatValueSync(ccTypeL10nId)
          : ccType ?? ""; // Unknown card type
        const ariaLabel = [
          ccTypeName,
          primary.toString().replaceAll("*", ""),
          secondary,
        ]
          .filter(chunk => !!chunk) // Exclude empty chunks.
          .join(" ");
        return {
          primary: primary.toString().replaceAll("*", "•"),
          secondary: secondary.toString().replaceAll("*", "•"),
          ariaLabel,
          image,
        };
      });

    labels.push(footerItem);

    return labels;
  }

  getTypeOfIndex(index) {
    if (!this._isSecure) {
      return "insecure";
    }

    return super.getTypeOfIndex(index);
  }
}
PK
!<��}T��#chrome/toolkit/res/broken-image.png�PNG


IHDR�agIDATx��
�@��o|Ծ�8�b 6����!����KDdE)p�2I��� �'��-}�$��X{�3fo�gȾX8�LX���t����cϧ��t�`�
>?R�	�IEND�B`�PK
!<���MBB6chrome/toolkit/res/messaging-system/lib/Logger.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ConsoleAPI } from "resource://gre/modules/Console.sys.mjs";

const LOGGING_PREF = "messaging-system.log";

export class Logger extends ConsoleAPI {
  constructor(name) {
    let consoleOptions = {
      prefix: name,
      maxLogLevel: Services.prefs.getCharPref(LOGGING_PREF, "warn"),
      maxLogLevelPref: LOGGING_PREF,
    };
    super(consoleOptions);
  }
}
PK
!<��ު))?chrome/toolkit/res/messaging-system/targeting/Targeting.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ASRouterTargeting:
    // eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
    "resource:///modules/asrouter/ASRouterTargeting.sys.mjs",
  ClientEnvironment: "resource://normandy/lib/ClientEnvironment.sys.mjs",
  ClientEnvironmentBase:
    "resource://gre/modules/components-utils/ClientEnvironment.sys.mjs",
  FilterExpressions:
    "resource://gre/modules/components-utils/FilterExpressions.sys.mjs",
  TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

const TARGETING_EVENT_CATEGORY = "messaging_experiments";
const TARGETING_EVENT_METHOD = "targeting";
const DEFAULT_TIMEOUT = 5000;
const ERROR_TYPES = {
  ATTRIBUTE_ERROR: "attribute_error",
  TIMEOUT: "attribute_timeout",
};

const TargetingEnvironment = {
  get locale() {
    return lazy.ASRouterTargeting.Environment.locale;
  },

  get localeLanguageCode() {
    return lazy.ASRouterTargeting.Environment.localeLanguageCode;
  },

  get region() {
    return lazy.ASRouterTargeting.Environment.region;
  },

  get userId() {
    return lazy.ClientEnvironment.userId;
  },

  get version() {
    return AppConstants.MOZ_APP_VERSION_DISPLAY;
  },

  get channel() {
    const { settings } = lazy.TelemetryEnvironment.currentEnvironment;
    return settings.update.channel;
  },

  get platform() {
    return AppConstants.platform;
  },

  get os() {
    return lazy.ClientEnvironmentBase.os;
  },
};

export class TargetingContext {
  #telemetrySource = null;

  constructor(customContext, options = { source: null }) {
    if (customContext) {
      this.ctx = new Proxy(customContext, {
        get: (customCtx, prop) => {
          if (prop in TargetingEnvironment) {
            return TargetingEnvironment[prop];
          }
          return customCtx[prop];
        },
      });
    } else {
      this.ctx = TargetingEnvironment;
    }

    // Used in telemetry to report where the targeting expression is coming from
    this.#telemetrySource = options.source;

    // Enable event recording
    Services.telemetry.setEventRecordingEnabled(TARGETING_EVENT_CATEGORY, true);
  }

  setTelemetrySource(source) {
    if (source) {
      this.#telemetrySource = source;
    }
  }

  _sendUndesiredEvent(eventData) {
    if (this.#telemetrySource) {
      Services.telemetry.recordEvent(
        TARGETING_EVENT_CATEGORY,
        TARGETING_EVENT_METHOD,
        eventData.event,
        eventData.value,
        { source: this.#telemetrySource }
      );
    } else {
      Services.telemetry.recordEvent(
        TARGETING_EVENT_CATEGORY,
        TARGETING_EVENT_METHOD,
        eventData.event,
        eventData.value
      );
    }
  }

  /**
   * Wrap each property of context[key] with a Proxy that captures errors and
   * timeouts
   *
   * @param {Object.<string, TargetingGetters> | TargetingGetters} context
   * @param {string} key Namespace value found in `context` param
   * @returns {TargetingGetters} Wrapped context where getter report errors and timeouts
   */
  createContextWithTimeout(context, key = null) {
    const timeoutDuration = key ? context[key].timeout : context.timeout;
    const logUndesiredEvent = (event, key, prop) => {
      const value = key ? `${key}.${prop}` : prop;
      this._sendUndesiredEvent({ event, value });
      console.error(`${event}: ${value}`);
    };

    return new Proxy(context, {
      get(target, prop) {
        // eslint-disable-next-line no-async-promise-executor
        return new Promise(async (resolve, reject) => {
          // Create timeout cb to record attribute resolution taking too long.
          let timeout = lazy.setTimeout(() => {
            logUndesiredEvent(ERROR_TYPES.TIMEOUT, key, prop);
            reject(
              new Error(
                `${prop} targeting getter timed out after ${
                  timeoutDuration || DEFAULT_TIMEOUT
                }ms`
              )
            );
          }, timeoutDuration || DEFAULT_TIMEOUT);

          try {
            resolve(await (key ? target[key][prop] : target[prop]));
          } catch (error) {
            logUndesiredEvent(ERROR_TYPES.ATTRIBUTE_ERROR, key, prop);
            reject(error);
            console.error(error);
          } finally {
            lazy.clearTimeout(timeout);
          }
        });
      },
    });
  }

  /**
   * Merge all evaluation contexts and wrap the getters with timeouts
   *
   * @param {Object.<string, TargetingGetters>[]} contexts
   * @returns {Object.<string, TargetingGetters>} Object that follows the pattern of `namespace: getters`
   */
  mergeEvaluationContexts(contexts) {
    let context = {};
    for (let c of contexts) {
      for (let envNamespace of Object.keys(c)) {
        // Take the provided context apart, replace it with a proxy
        context[envNamespace] = this.createContextWithTimeout(c, envNamespace);
      }
    }

    return context;
  }

  /**
   * Merge multiple TargetingGetters objects without accidentally evaluating
   *
   * @param {TargetingGetters[]} ...contexts
   * @returns {Proxy<TargetingGetters>}
   */
  static combineContexts(...contexts) {
    return new Proxy(
      {},
      {
        get(target, prop) {
          for (let context of contexts) {
            if (prop in context) {
              return context[prop];
            }
          }

          return null;
        },
      }
    );
  }

  /**
   * Evaluate JEXL expressions with default `TargetingEnvironment` and custom
   * provided targeting contexts
   *
   * @example
   * eval(
   *   "ctx.locale == 'en-US' && customCtx.foo == 42",
   *   { customCtx: { foo: 42 } }
   * ); // true
   *
   * @param {string} expression JEXL expression
   * @param {Object.<string, TargetingGetters>[]} ...contexts Additional custom context
   *        objects where the keys act as namespaces for the different getters
   *
   * @returns {promise} Evaluation result
   */
  eval(expression, ...contexts) {
    return lazy.FilterExpressions.eval(
      expression,
      this.mergeEvaluationContexts([{ ctx: this.ctx }, ...contexts])
    );
  }

  /**
   * Evaluate JEXL expressions with default provided targeting context
   *
   * @example
   * new TargetingContext({ bar: 42 });
   * evalWithDefault(
   *   "bar == 42",
   * ); // true
   *
   * @param {string} expression JEXL expression
   * @returns {promise} Evaluation result
   */
  evalWithDefault(expression) {
    return lazy.FilterExpressions.eval(
      expression,
      this.createContextWithTimeout(this.ctx)
    );
  }
}
PK
!<"4ޡI�I/chrome/toolkit/res/nimbus/ExperimentAPI.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ExperimentManager: "resource://nimbus/lib/ExperimentManager.sys.mjs",
  ExperimentStore: "resource://nimbus/lib/ExperimentStore.sys.mjs",
  FeatureManifest: "resource://nimbus/FeatureManifest.sys.mjs",
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
});

const IS_MAIN_PROCESS =
  Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT;

const COLLECTION_ID_PREF = "messaging-system.rsexperimentloader.collection_id";
const COLLECTION_ID_FALLBACK = "nimbus-desktop-experiments";
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "COLLECTION_ID",
  COLLECTION_ID_PREF,
  COLLECTION_ID_FALLBACK
);
const EXPOSURE_EVENT_CATEGORY = "normandy";
const EXPOSURE_EVENT_METHOD = "expose";
const EXPOSURE_EVENT_OBJECT = "nimbus_experiment";

function parseJSON(value) {
  if (value) {
    try {
      return JSON.parse(value);
    } catch (e) {
      console.error(e);
    }
  }
  return null;
}

function featuresCompat(branch) {
  if (!branch) {
    return [];
  }
  let { features } = branch;
  // In <=v1.5.0 of the Nimbus API, experiments had single feature
  if (!features) {
    features = [branch.feature];
  }

  return features;
}

function getBranchFeature(enrollment, targetFeatureId) {
  return featuresCompat(enrollment.branch).find(
    ({ featureId }) => featureId === targetFeatureId
  );
}

const experimentBranchAccessor = {
  get: (target, prop) => {
    // Offer an API where we can access `branch.feature.*`.
    // This is a useful shorthand that hides the fact that
    // even single-feature recipes are still represented
    // as an array with 1 item
    if (!(prop in target) && target.features) {
      return target.features.find(f => f.featureId === prop);
    } else if (target.feature?.featureId === prop) {
      // Backwards compatibility for version 1.6.2 and older
      return target.feature;
    }

    return target[prop];
  },
};

export const ExperimentAPI = {
  /**
   * @returns {Promise} Resolves when the API has synchronized to the main store
   */
  ready() {
    return this._store.ready();
  },

  /**
   * Returns an experiment, including all its metadata
   * Sends exposure event
   *
   * @param {{slug?: string, featureId?: string}} options slug = An experiment identifier
   * or feature = a stable identifier for a type of experiment
   * @returns {{slug: string, active: bool}} A matching experiment if one is found.
   */
  getExperiment({ slug, featureId } = {}) {
    if (!slug && !featureId) {
      throw new Error(
        "getExperiment(options) must include a slug or a feature."
      );
    }
    let experimentData;
    try {
      if (slug) {
        experimentData = this._store.get(slug);
      } else if (featureId) {
        experimentData = this._store.getExperimentForFeature(featureId);
      }
    } catch (e) {
      console.error(e);
    }
    if (experimentData) {
      return {
        slug: experimentData.slug,
        active: experimentData.active,
        branch: new Proxy(experimentData.branch, experimentBranchAccessor),
      };
    }

    return null;
  },

  /**
   * Used by getExperimentMetaData and getRolloutMetaData
   *
   * @param {{slug: string, featureId: string}} options Enrollment identifier
   * @param isRollout Is enrollment an experiment or a rollout
   * @returns {object} Enrollment metadata
   */
  getEnrollmentMetaData({ slug, featureId }, isRollout) {
    if (!slug && !featureId) {
      throw new Error(
        "getExperiment(options) must include a slug or a feature."
      );
    }

    let experimentData;
    try {
      if (slug) {
        experimentData = this._store.get(slug);
      } else if (featureId) {
        if (isRollout) {
          experimentData = this._store.getRolloutForFeature(featureId);
        } else {
          experimentData = this._store.getExperimentForFeature(featureId);
        }
      }
    } catch (e) {
      console.error(e);
    }
    if (experimentData) {
      return {
        slug: experimentData.slug,
        active: experimentData.active,
        branch: { slug: experimentData.branch.slug },
      };
    }

    return null;
  },

  /**
   * Return experiment slug its status and the enrolled branch slug
   * Does NOT send exposure event because you only have access to the slugs
   */
  getExperimentMetaData(options) {
    return this.getEnrollmentMetaData(options);
  },

  /**
   * Return rollout slug its status and the enrolled branch slug
   * Does NOT send exposure event because you only have access to the slugs
   */
  getRolloutMetaData(options) {
    return this.getEnrollmentMetaData(options, true);
  },

  /**
   * Return FeatureConfig from first active experiment where it can be found
   * @param {{slug: string, featureId: string }}
   * @returns {Branch | null}
   */
  getActiveBranch({ slug, featureId }) {
    let experiment = null;
    try {
      if (slug) {
        experiment = this._store.get(slug);
      } else if (featureId) {
        experiment = this._store.getExperimentForFeature(featureId);
      }
    } catch (e) {
      console.error(e);
    }

    if (!experiment) {
      return null;
    }

    // Default to null for feature-less experiments where we're only
    // interested in exposure.
    return experiment?.branch || null;
  },

  /**
   * Deregisters an event listener.
   * @param {string} eventName
   * @param {function} callback
   */
  off(eventName, callback) {
    this._store.off(eventName, callback);
  },

  /**
   * Returns the recipe for a given experiment slug
   *
   * This should noly be called from the main process.
   *
   * Note that the recipe is directly fetched from RemoteSettings, which has
   * all the recipe metadata available without relying on the `this._store`.
   * Therefore, calling this function does not require to call `this.ready()` first.
   *
   * @param slug {String} An experiment identifier
   * @returns {Recipe|undefined} A matching experiment recipe if one is found
   */
  async getRecipe(slug) {
    if (!IS_MAIN_PROCESS) {
      throw new Error(
        "getRecipe() should only be called from the main process"
      );
    }

    let recipe;

    try {
      [recipe] = await this._remoteSettingsClient.get({
        // Do not sync the RS store, let RemoteSettingsExperimentLoader do that
        syncIfEmpty: false,
        filters: { slug },
      });
    } catch (e) {
      // If an error occurs in .get(), an empty list is returned and the destructuring
      // assignment will throw.
      console.error(e);
      recipe = undefined;
    }

    return recipe;
  },

  /**
   * Returns all the branches for a given experiment slug
   *
   * This should only be called from the main process. Like `getRecipe()`,
   * calling this function does not require to call `this.ready()` first.
   *
   * @param slug {String} An experiment identifier
   * @returns {[Branches]|undefined} An array of branches for the given slug
   */
  async getAllBranches(slug) {
    if (!IS_MAIN_PROCESS) {
      throw new Error(
        "getAllBranches() should only be called from the main process"
      );
    }

    const recipe = await this.getRecipe(slug);
    return recipe?.branches.map(
      branch => new Proxy(branch, experimentBranchAccessor)
    );
  },

  recordExposureEvent({ featureId, experimentSlug, branchSlug }) {
    Services.telemetry.setEventRecordingEnabled(EXPOSURE_EVENT_CATEGORY, true);
    try {
      Services.telemetry.recordEvent(
        EXPOSURE_EVENT_CATEGORY,
        EXPOSURE_EVENT_METHOD,
        EXPOSURE_EVENT_OBJECT,
        experimentSlug,
        {
          branchSlug,
          featureId,
        }
      );
    } catch (e) {
      console.error(e);
    }
    Glean.nimbusEvents.exposure.record({
      experiment: experimentSlug,
      branch: branchSlug,
      feature_id: featureId,
    });
  },
};

/**
 * Singleton that holds lazy references to _ExperimentFeature instances
 * defined by the FeatureManifest
 */
export const NimbusFeatures = {};

for (let feature in lazy.FeatureManifest) {
  ChromeUtils.defineLazyGetter(NimbusFeatures, feature, () => {
    return new _ExperimentFeature(feature);
  });
}

export class _ExperimentFeature {
  constructor(featureId, manifest) {
    this.featureId = featureId;
    this.prefGetters = {};
    this.manifest = manifest || lazy.FeatureManifest[featureId];
    if (!this.manifest) {
      console.error(
        `No manifest entry for ${featureId}. Please add one to toolkit/components/nimbus/FeatureManifest.yaml`
      );
    }
    this._didSendExposureEvent = false;
    const variables = this.manifest?.variables || {};

    Object.keys(variables).forEach(key => {
      const { type, fallbackPref } = variables[key];
      if (fallbackPref) {
        XPCOMUtils.defineLazyPreferenceGetter(
          this.prefGetters,
          key,
          fallbackPref,
          null,
          () => {
            ExperimentAPI._store._emitFeatureUpdate(
              this.featureId,
              "pref-updated"
            );
          },
          type === "json" ? parseJSON : val => val
        );
      }
    });
  }

  getSetPrefName(variable) {
    const setPref = this.manifest?.variables?.[variable]?.setPref;

    return setPref?.pref ?? setPref ?? undefined;
  }

  getSetPref(variable) {
    return this.manifest?.variables?.[variable]?.setPref;
  }

  getFallbackPrefName(variable) {
    return this.manifest?.variables?.[variable]?.fallbackPref;
  }

  /**
   * Wait for ExperimentStore to load giving access to experiment features that
   * do not have a pref cache
   */
  ready() {
    return ExperimentAPI.ready();
  }

  /**
   * Lookup feature variables in experiments, rollouts, and fallback prefs.
   * @param {{defaultValues?: {[variableName: string]: any}}} options
   * @returns {{[variableName: string]: any}} The feature value
   */
  getAllVariables({ defaultValues = null } = {}) {
    let enrollment = null;
    try {
      enrollment = ExperimentAPI._store.getExperimentForFeature(this.featureId);
    } catch (e) {
      console.error(e);
    }
    let featureValue = this._getLocalizedValue(enrollment);

    if (typeof featureValue === "undefined") {
      try {
        enrollment = ExperimentAPI._store.getRolloutForFeature(this.featureId);
      } catch (e) {
        console.error(e);
      }
      featureValue = this._getLocalizedValue(enrollment);
    }

    return {
      ...this.prefGetters,
      ...defaultValues,
      ...featureValue,
    };
  }

  getVariable(variable) {
    if (!this.manifest?.variables?.[variable]) {
      // Only throw in nightly/tests
      if (Cu.isInAutomation || AppConstants.NIGHTLY_BUILD) {
        throw new Error(
          `Nimbus: Warning - variable "${variable}" is not defined in FeatureManifest.yaml`
        );
      }
    }

    // Next, check if an experiment is defined
    let enrollment = null;
    try {
      enrollment = ExperimentAPI._store.getExperimentForFeature(this.featureId);
    } catch (e) {
      console.error(e);
    }
    let value = this._getLocalizedValue(enrollment, variable);
    if (typeof value !== "undefined") {
      return value;
    }

    // Next, check for a rollout.
    try {
      enrollment = ExperimentAPI._store.getRolloutForFeature(this.featureId);
    } catch (e) {
      console.error(e);
    }
    value = this._getLocalizedValue(enrollment, variable);
    if (typeof value !== "undefined") {
      return value;
    }

    // Return the default preference value
    const prefName = this.getFallbackPrefName(variable);
    return prefName ? this.prefGetters[variable] : undefined;
  }

  getRollout() {
    let remoteConfig = ExperimentAPI._store.getRolloutForFeature(
      this.featureId
    );
    if (!remoteConfig) {
      return null;
    }

    if (remoteConfig.branch?.features) {
      return remoteConfig.branch?.features.find(
        f => f.featureId === this.featureId
      );
    }

    // This path is deprecated and will be removed in the future
    if (remoteConfig.branch?.feature) {
      return remoteConfig.branch.feature;
    }

    return null;
  }

  recordExposureEvent({ once = false } = {}) {
    if (once && this._didSendExposureEvent) {
      return;
    }

    let enrollmentData = ExperimentAPI.getExperimentMetaData({
      featureId: this.featureId,
    });
    if (!enrollmentData) {
      enrollmentData = ExperimentAPI.getRolloutMetaData({
        featureId: this.featureId,
      });
    }

    // Exposure only sent if user is enrolled in an experiment
    if (enrollmentData) {
      ExperimentAPI.recordExposureEvent({
        featureId: this.featureId,
        experimentSlug: enrollmentData.slug,
        branchSlug: enrollmentData.branch?.slug,
      });
      this._didSendExposureEvent = true;
    }
  }

  onUpdate(callback) {
    ExperimentAPI._store._onFeatureUpdate(this.featureId, callback);
  }

  offUpdate(callback) {
    ExperimentAPI._store._offFeatureUpdate(this.featureId, callback);
  }

  /**
   * The applications this feature applies to.
   *
   */
  get applications() {
    return this.manifest.applications ?? ["firefox-desktop"];
  }

  debug() {
    return {
      variables: this.getAllVariables(),
      experiment: ExperimentAPI.getExperimentMetaData({
        featureId: this.featureId,
      }),
      fallbackPrefs: Object.keys(this.prefGetters).map(prefName => [
        prefName,
        this.prefGetters[prefName],
      ]),
      rollouts: this.getRollout(),
    };
  }

  /**
   * Do recursive locale substitution on the values, if applicable.
   *
   * If there are no localizations provided, the value will be returned as-is.
   *
   * If the value is an object containing an $l10n key, its substitution will be
   * returned.
   *
   * Otherwise, the value will be recursively substituted.
   *
   * @param {unknown} values The values to perform substitutions upon.
   * @param {Record<string, string>} localizations The localization
   *        substitutions for a specific locale.
   * @param {Set<string>?} missingIds An optional set to collect all the IDs of
   *        all missing l10n entries.
   *
   * @returns {any} The values, potentially locale substituted.
   */
  static substituteLocalizations(
    values,
    localizations,
    missingIds = undefined
  ) {
    const result = _ExperimentFeature._substituteLocalizations(
      values,
      localizations,
      missingIds
    );

    if (missingIds?.size) {
      throw new ExperimentLocalizationError("l10n-missing-entry");
    }

    return result;
  }

  /**
   * The implementation of localization substitution.
   *
   * @param {unknown} values The values to perform substitutions upon.
   * @param {Record<string, string>} localizations The localization
   *        substitutions for a specific locale.
   * @param {Set<string>?} missingIds An optional set to collect all the IDs of
   *        all missing l10n entries.
   *
   * @returns {any} The values, potentially locale substituted.
   */
  static _substituteLocalizations(values, localizations, missingIds) {
    // If the recipe is not localized, we don't need to do anything.
    // Likewise, if the value we are attempting to localize is not an object,
    // there is nothing to localize.
    if (
      typeof localizations === "undefined" ||
      typeof values !== "object" ||
      values === null
    ) {
      return values;
    }

    if (Array.isArray(values)) {
      return values.map(value =>
        _ExperimentFeature._substituteLocalizations(
          value,
          localizations,
          missingIds
        )
      );
    }

    const substituted = Object.assign({}, values);

    for (const [key, value] of Object.entries(values)) {
      if (
        key === "$l10n" &&
        typeof value === "object" &&
        value !== null &&
        value?.id
      ) {
        if (!Object.hasOwn(localizations, value.id)) {
          if (missingIds) {
            missingIds.add(value.id);
            break;
          } else {
            throw new ExperimentLocalizationError("l10n-missing-entry");
          }
        }

        return localizations[value.id];
      }

      substituted[key] = _ExperimentFeature._substituteLocalizations(
        value,
        localizations,
        missingIds
      );
    }

    return substituted;
  }

  /**
   * Return a value (or all values) from an enrollment, potentially localized.
   *
   * @param {Enrollment} enrollment - The enrollment to query for the value or values.
   * @param {string?} variable - The name of the variable to query for. If not
   *                             provided, all variables will be returned.
   *
   * @returns {any} The value for the variable(s) in question.
   */
  _getLocalizedValue(enrollment, variable = undefined) {
    if (enrollment) {
      const locale = Services.locale.appLocaleAsBCP47;

      if (
        typeof enrollment.localizations === "object" &&
        enrollment.localizations !== null &&
        (typeof enrollment.localizations[locale] !== "object" ||
          enrollment.localizations[locale] === null)
      ) {
        ExperimentAPI._manager.unenroll(enrollment.slug, "l10n-missing-locale");
        return undefined;
      }

      const allValues = getBranchFeature(enrollment, this.featureId)?.value;
      const value =
        typeof variable === "undefined" ? allValues : allValues?.[variable];

      if (typeof value !== "undefined") {
        try {
          return _ExperimentFeature.substituteLocalizations(
            value,
            enrollment.localizations?.[locale]
          );
        } catch (e) {
          // This should never happen.
          if (e instanceof ExperimentLocalizationError) {
            ExperimentAPI._manager.unenroll(enrollment.slug, e.reason);
          } else {
            throw e;
          }
        }
      }
    }

    return undefined;
  }
}

ChromeUtils.defineLazyGetter(ExperimentAPI, "_manager", function () {
  return lazy.ExperimentManager;
});

ChromeUtils.defineLazyGetter(ExperimentAPI, "_store", function () {
  return IS_MAIN_PROCESS
    ? lazy.ExperimentManager.store
    : new lazy.ExperimentStore();
});

ChromeUtils.defineLazyGetter(
  ExperimentAPI,
  "_remoteSettingsClient",
  function () {
    return lazy.RemoteSettings(lazy.COLLECTION_ID);
  }
);

class ExperimentLocalizationError extends Error {
  constructor(reason) {
    super(`Localized experiment error (${reason})`);
    this.reason = reason;
  }
}
PK
!<_�]�]�1chrome/toolkit/res/nimbus/FeatureManifest.sys.mjs// This file was generated by generate_feature_manifest.py from FeatureManifest.yaml. DO NOT EDIT.
export const FeatureManifest = {"no-feature-firefox-desktop": {"description": "A dummy feature for experiments that target no feature.", "owner": "beth@mozilla.com", "applications": ["firefox-desktop", "firefox-desktop-background-task"], "hasExposure": false, "variables": {}}, "testFeature": {"description": "Test only feature", "owner": "beth@mozilla.com", "applications": ["firefox-desktop", "firefox-desktop-background-task"], "hasExposure": false, "isEarlyStartup": true, "variables": {"enabled": {"type": "boolean", "description": "Whether or not this feature is enabled"}, "testInt": {"type": "int", "fallbackPref": "nimbus.testing.testInt", "description": "Int pref used by platform API tests"}, "testSetString": {"type": "string", "setPref": {"branch": "user", "pref": "nimbus.testing.testSetString"}, "description": "A string pref set by Nimbus tests"}}}, "nimbus-qa-1": {"description": "A feature for testing pref-setting on the default branch.", "owner": "beth@mozilla.com", "hasExposure": false, "variables": {"value": {"type": "string", "setPref": {"branch": "default", "pref": "nimbus.qa.pref-1"}, "description": "The value to set for the pref."}}}, "nimbus-qa-2": {"description": "A feature for testing pref-setting on the user branch.", "owner": "beth@mozilla.com", "hasExposure": false, "variables": {"value": {"type": "string", "setPref": {"branch": "user", "pref": "nimbus.qa.pref-2"}, "description": "The value to set for the pref."}}}, "prefFlips": {"description": "Flip arbitrary prefs. Controlled by release management.", "owner": "beth@mozilla.com", "hasExposure": false, "variables": {"prefs": {"type": "json", "description": "The prefs to set."}}, "schema": {"uri": "resource://nimbus/schemas/PrefFlipsFeature.schema.json", "path": "toolkit/components/nimbus/schemas/PrefFlipsFeature.schema.json"}}, "search": {"description": "Search engine experimentation support and testing features.", "owner": "search-and-suggest-program@mozilla.com", "hasExposure": false, "variables": {"extraParams": {"type": "json", "description": "This allows extra parameters to be set for search engines requests including, where calls to the suggestions API, the search engine configuration defines those parameters.\nThe use of this field should be coordinated with the Search team.\nThe field value is an array of objects with key/value fields. For example:\n[\n  {\"key\": \"google_channel_row\", \"value\": \"foo\"}\n]\nThis is matched to a section in the search configuration:\n\"extraParams\": [\n  {\n    \"name\": \"channel\",\n    \"pref\": \"google_channel_row\",\n    \"condition\": \"pref\"\n  }\n],\nIn this case, the resulting URL for the appropriate search engine would have `&channel=foo` added to the URL when doing searches.\nIf the key is not referenced in the search configuration, then no parameter will be added. Only the search team can update the configuration."}, "richSuggestionsFeatureGate": {"type": "boolean", "setPref": {"branch": "default", "pref": "browser.urlbar.richSuggestions.featureGate"}, "description": "Feature gate that controls whether Rich Suggestions are enabled."}, "serpEventTelemetryCategorizationEnabled": {"type": "boolean", "setPref": {"branch": "default", "pref": "browser.search.serpEventTelemetryCategorization.enabled"}, "description": "Whether the Glean SERP event telemetry for SERP categorization is enabled."}, "trendingEnabled": {"type": "boolean", "setPref": {"branch": "default", "pref": "browser.urlbar.trending.featureGate"}, "description": "Feature gate that controls whether trending suggestions are enabled."}, "trendingRequireSearchMode": {"type": "boolean", "setPref": {"branch": "default", "pref": "browser.urlbar.trending.requireSearchMode"}, "description": "Controls whether trending suggestions are only shown in search mode or not."}, "trendingMaxResultsNoSearchMode": {"type": "int", "setPref": {"branch": "default", "pref": "browser.urlbar.trending.maxResultsNoSearchMode"}, "description": "The maximum number of trending results mode outside search mode."}, "newSearchConfigEnabled": {"type": "boolean", "setPref": {"branch": "user", "pref": "browser.search.newSearchConfig.enabled"}, "description": "Whether search-config-v2 is enabled for the user. This will only take effect when the user restarts. Obsolete. This is kept until all multi-feature roll-outs using it are ended to prevent un-enrollment."}, "targetExperiment": {"type": "string", "description": "The slug of the experiment someone is requesting enrollment and branch information for. (Note that the featureId needs to be set to \"search\".)"}}}, "searchConfiguration": {"description": "Search experimentation support for the engine configuration", "owner": "search-and-suggest-program@mozilla.com", "isEarlyStartup": true, "hasExposure": false, "variables": {"experiment": {"type": "string", "fallbackPref": "browser.search.experiment", "description": "Used to activate only matching configurations that contain the value in `experiment`"}, "seperatePrivateDefaultUIEnabled": {"type": "boolean", "description": "Whether the UI for the separate private default feature is enabled."}, "seperatePrivateDefaultUrlbarResultEnabled": {"type": "boolean", "description": "Whether the urlbar result for the separate private default is shown."}}}, "urlbar": {"description": "The Address Bar", "owner": "search-and-suggest-program@mozilla.com", "hasExposure": true, "exposureDescription": "The timing of the exposure event depends on the experiment, but generally the event is recorded once per app session when the user first encounters the UI of the experiment in which they're enrolled.", "variables": {"addonsFeatureGate": {"type": "boolean", "fallbackPref": "browser.urlbar.addons.featureGate", "description": "Feature gate that controls whether all aspects of the addons suggestion feature are exposed to the user."}, "addonsShowLessFrequentlyCap": {"type": "int", "description": "If defined and non-zero, this is the maximum number of times the user will be able to click the \"Show less frequently\" command for addon suggestions. If undefined or zero, the user will be able to click the command without any limit."}, "autoFillAdaptiveHistoryEnabled": {"type": "boolean", "fallbackPref": "browser.urlbar.autoFill.adaptiveHistory.enabled", "description": "Whether enabling adaptive history autofill."}, "autoFillAdaptiveHistoryMinCharsThreshold": {"type": "int", "fallbackPref": "browser.urlbar.autoFill.adaptiveHistory.minCharsThreshold", "description": "Minimum char length of the user's search string to trigger adaptive history autofill."}, "autoFillAdaptiveHistoryUseCountThreshold": {"type": "string", "description": "This value assumes float expression like \"0.47\". Threshold for use count of input history that we handle as adaptive history autofill. If the use count is this value or more, it will be a candidate."}, "experimentType": {"type": "string", "description": "The type of the experiment (or rollout). If \"best-match\", then the Nimbus exposure event will be recorded when the user first triggers a best match (or would have triggered a best match, for users in the control group). If empty, the event will be recorded when the user first triggers any type of Suggest suggestion.", "enum": ["best-match", ""]}, "fakespotFeatureGate": {"type": "boolean", "fallbackPref": "browser.urlbar.fakespot.featureGate", "description": "Feature gate that controls whether all aspects of the Fakespot suggestion feature are exposed to the user."}, "fakespotMinKeywordLength": {"type": "int", "description": "The minimum prefix length of a Fakespot keyword the user must type to trigger the suggestion."}, "fakespotShowLessFrequentlyCap": {"type": "int", "fallbackPref": "browser.urlbar.fakespot.showLessFrequentlyCap", "description": "If defined and non-zero, this is the maximum number of times the user will be able to click the \"Show less frequently\" command for Fakespot suggestions. If undefined or zero, the user will be able to click the command without any limit."}, "fakespotSuggestedIndex": {"type": "int", "fallbackPref": "browser.urlbar.fakespot.suggestedIndex", "description": "The index of Fakespot results within the Firefox Suggest section. A negative index is relative to the end of the section."}, "mdnFeatureGate": {"type": "boolean", "setPref": {"branch": "default", "pref": "browser.urlbar.mdn.featureGate"}, "description": "Feature gate that controls whether all aspects of the mdn suggestion feature are exposed to the user."}, "merinoClientVariants": {"type": "string", "fallbackPref": "browser.urlbar.merino.clientVariants", "description": "Comma separated list of client variants to report to the Merino server. May impact server behavior."}, "merinoEndpointURL": {"type": "string", "fallbackPref": "browser.urlbar.merino.endpointURL", "description": "The Merino endpoint URL, not including parameters. An empty string will cause Firefox not to fetch from Merino."}, "merinoProviders": {"type": "string", "fallbackPref": "browser.urlbar.merino.providers", "description": "Comma-separated list of providers to request from the Merino server. Merino will return suggestions only for these providers."}, "merinoTimeoutMs": {"type": "int", "fallbackPref": "browser.urlbar.merino.timeoutMs", "description": "Timeout for Merino fetches (ms)"}, "exposureResults": {"type": "string", "setPref": {"branch": "default", "pref": "browser.urlbar.exposureResults"}, "description": "Comma-separated list of result type combinations, that are used to determine if an exposure event should be fired."}, "showExposureResults": {"type": "boolean", "setPref": {"branch": "default", "pref": "browser.urlbar.showExposureResults"}, "description": "Boolean used to determine if the results defined in `exposureResults` should be shown in search results. Should be false for Control branch of an experiment."}, "pocketFeatureGate": {"type": "boolean", "fallbackPref": "browser.urlbar.pocket.featureGate", "description": "Feature gate that controls whether all aspects of the Pocket suggestions feature are exposed to the user."}, "pocketShowLessFrequentlyCap": {"type": "int", "description": "If defined and non-zero, this is the maximum number of times the user will be able to click the \"Show less frequently\" command for Pocket suggestions. If undefined or zero, the user will be able to click the command without any limit."}, "pocketSuggestIndex": {"type": "int", "description": "The group-relative suggestedIndex of Pocket suggestions within the Firefox Suggest section."}, "potentialExposureKeywords": {"type": "json", "fallbackPref": "browser.urlbar.potentialExposureKeywords", "description": "An array of keyword strings that will trigger the `urlbar-potential-exposure` ping when the user types one during a urlbar session."}, "quickSuggestAllowPositionInSuggestions": {"type": "boolean", "fallbackPref": "browser.urlbar.quicksuggest.allowPositionInSuggestions", "description": "Whether quick suggest results can be shown in position specified in the suggestions."}, "quickSuggestAmpTopPickCharThreshold": {"type": "int", "fallbackPref": "browser.urlbar.quicksuggest.ampTopPickCharThreshold", "description": "Character-count threshold (inclusive) for showing AMP suggestions as top picks. If an AMP suggestion is triggered by a keyword at least this many characters long, it will be shown as a top pick. When this variable is set, full keywords will also show AMP suggestions as top picks even if they have fewer characters than this threshold."}, "quickSuggestContextualOptInEnabled": {"type": "boolean", "setPref": {"branch": "default", "pref": "browser.urlbar.quicksuggest.contextualOptIn"}, "description": "Whether the Firefox Suggest contextual opt-in result is enabled. If true, this implicitly disables shouldShowOnboardingDialog."}, "quickSuggestContextualOptInSayHello": {"type": "boolean", "setPref": {"branch": "default", "pref": "browser.urlbar.quicksuggest.contextualOptIn.sayHello"}, "description": "Controls which variant of the copy is used for the Firefox Suggest contextual opt-in result."}, "quickSuggestContextualOptInTopPosition": {"type": "boolean", "setPref": {"branch": "default", "pref": "browser.urlbar.quicksuggest.contextualOptIn.topPosition"}, "description": "Controls whether the Firefox Suggest contextual opt-in result appears at the top of results or at the bottom, after one-off buttons."}, "quickSuggestDataCollectionEnabled": {"type": "boolean", "description": "Whether data collection should be enabled by default. If this variable is specified, it will override the value implied by the scenario. It will never override the user's local preference to disable (or enable) data collection, if the user has already toggled that preference."}, "quickSuggestEnabled": {"type": "boolean", "fallbackPref": "browser.urlbar.quicksuggest.enabled", "description": "Gate for the Firefox Suggest feature as a whole. If false, the Firefox Suggest preferences UI and Suggest suggestions will not be shown. If true, the preferences UI will be shown, and the user can turn suggestions on or off."}, "quickSuggestImpressionCapsSponsoredEnabled": {"type": "boolean", "fallbackPref": "browser.urlbar.quicksuggest.impressionCaps.sponsoredEnabled", "description": "Whether sponsored suggestions are subject to impression frequency caps. If false, sponsored suggestions can be shown an unlimited number of times over any given period. If true, sponsored suggestion impressions will be subject to the caps in the remote settings configuration."}, "quickSuggestImpressionCapsNonSponsoredEnabled": {"type": "boolean", "fallbackPref": "browser.urlbar.quicksuggest.impressionCaps.nonSponsoredEnabled", "description": "Whether non-sponsored suggestions are subject to impression frequency caps. If false, non-sponsored suggestions can be shown an unlimited number of times over any given period. If true, non-sponsored suggestion impressions will be subject to the caps in the remote settings configuration."}, "quickSuggestNonSponsoredEnabled": {"type": "boolean", "description": "Whether non-sponsored suggestions should be enabled by default. If this variable is specified, it will override the value implied by the scenario. It will never override the user's local preference to disable (or enable) non-sponsored suggestions, if the user has already toggled that preference."}, "quickSuggestNonSponsoredIndex": {"type": "int", "fallbackPref": "browser.urlbar.quicksuggest.nonSponsoredIndex", "description": "The index of non-sponsored QuickSuggest results within the general group. A negative index is relative to the end of the group"}, "quickSuggestOnboardingDialogVariation": {"type": "string", "description": "Specify the messages/UI variation for QuickSuggest onboarding dialog. This value is case insensitive."}, "quickSuggestRemoteSettingsDataType": {"type": "string", "description": "The `type` of the suggestions data in remote settings. If not specified, \"data\" is used."}, "quickSuggestRustEnabled": {"type": "boolean", "fallbackPref": "browser.urlbar.quicksuggest.rustEnabled", "description": "Whether Firefox Suggest will use the new Rust backend instead of the original JS backend."}, "quickSuggestScenario": {"type": "string", "description": "The Firefox Suggest scenario in which the user is enrolled", "enum": ["history", "offline", "online"]}, "quickSuggestScoreMap": {"type": "json", "description": "A JSON object that maps telemetry result types to suggestion scores. If a telemetry result type is present in this map, the client will use the corresponding score as the score for all suggestions of the type, overriding all other sources of scores for the type. In other words, the scores in this map will override scores that are set in remote settings and Merino as well as scores that are hardcoded in the client. Example entries: `\"amo\": 0.5`, `\"adm_sponsored\": 0.9`"}, "quickSuggestShouldShowOnboardingDialog": {"type": "boolean", "fallbackPref": "browser.urlbar.quicksuggest.shouldShowOnboardingDialog", "description": "Whether or not to show the QuickSuggest onboarding dialog"}, "quickSuggestShowOnboardingDialogAfterNRestarts": {"type": "int", "fallbackPref": "browser.urlbar.quicksuggest.showOnboardingDialogAfterNRestarts", "description": "Show QuickSuggest onboarding dialog after N browser restarts"}, "quickSuggestSponsoredEnabled": {"type": "boolean", "description": "Whether sponsored suggestions should be enabled by default. If this variable is specified, it will override the value implied by the scenario. It will never override the user's local preference to disable (or enable) sponsored suggestions, if the user has already toggled that preference."}, "quickSuggestSponsoredIndex": {"type": "int", "fallbackPref": "browser.urlbar.quicksuggest.sponsoredIndex", "description": "The index of sponsored Firefox Suggest results within the Firefox Suggest section when \"Show search suggestions ahead of browsing history in address bar results\" is checked. When not checked, the index is hardcoded as -1. Negative indexes are relative to the end of the section."}, "quickSuggestSponsoredPriority": {"type": "boolean", "fallbackPref": "browser.urlbar.quicksuggest.sponsoredPriority", "description": "Whether or not showing sponsored suggestion as priority. If this variable is true, the following things are processed. * \"Sponsored\" label is shown as the group label. * Change the suggested index to 1. * Handle as top pick."}, "recentSearchesFeatureGate": {"type": "boolean", "setPref": {"branch": "default", "pref": "browser.urlbar.recentsearches.featureGate"}, "description": "Gate for the recent searches feature."}, "recentSearchesMaxResults": {"type": "int", "setPref": {"branch": "default", "pref": "browser.urlbar.recentsearches.maxResults"}, "description": "The maximum number of recent searches to show."}, "recordNavigationalSuggestionTelemetry": {"type": "boolean", "description": "Whether to record navigational suggestion telemetry. Defaults to false."}, "showSearchTermsFeatureGate": {"type": "boolean", "fallbackPref": "browser.urlbar.showSearchTerms.featureGate", "description": "Gate for the show search terms feature. If false, the preference#search will not show the search terms feature checkbox, and search terms will never persist in the urlbar. If true, the preference checkbox will be shown on preferences#search, and the user can choose to persist search terms on or off in the urlbar."}, "weatherFeatureGate": {"type": "boolean", "fallbackPref": "browser.urlbar.weather.featureGate", "description": "Feature gate that controls whether all aspects of the weather suggestion feature are exposed to the user. See also `weatherKeywords` and `weatherKeywordsMinimumLength`. In summary: To enable the weather suggestion, set `weatherFeatureGate` to true, `weatherKeywords` to an array of full keyword strings, and `weatherKeywordsMinimumLength` to a non-zero integer. To disable the weather suggestion, leave out all weather-related variables."}, "weatherKeywords": {"type": "json", "description": "An array of full keyword strings that will trigger the weather suggestion when the user types them in the address bar. If absent or null, Firefox will fall back to the weather keywords defined in remote settings. If neither Nimbus nor remote settings defines any keywords, the weather suggestion will be disabled. See also `weatherKeywordsMinimumLength`."}, "weatherKeywordsMinimumLength": {"type": "int", "description": "If defined and non-zero, the weather suggestion will be triggered by typing any prefix of a full weather keyword when the prefix is at least `weatherKeywordsMinimumLength` characters long. If this variable is absent or zero, Firefox will fall back to the minimum length defined in remote settings. If neither Nimbus nor remote settings defines a minimum length, only full keywords will trigger the suggestion. See also `weatherKeywords`."}, "weatherKeywordsMinimumLengthCap": {"type": "int", "description": "If defined and non-zero, the user will not be able to increment the minimum keyword length beyond this value. e.g., if this value is 6, the current minimum length is 5, and the user clicks \"Show less frequently\", then the minimum length will be incremented to 6, the \"Show less frequently\" command will be hidden, and the user can continue to trigger the weather suggestion by typing 6 characters, but they will not be able to increment the minimum length any further. If this variable is absent or zero, Firefox will fall back to the cap defined in remote settings. If neither Nimbus nor remote settings defines a cap, no cap will be used, and the user will be able to increment the minimum length without any limit."}, "weatherSimpleUI": {"type": "boolean", "description": "If true, show the weather suggestion by simple UI edition as follows. * Remove the forcast text from the summary text."}, "yelpMinKeywordLength": {"type": "int", "description": "The minimum prefix length of a Yelp keyword the user must type to trigger the suggestion."}, "yelpFeatureGate": {"type": "boolean", "fallbackPref": "browser.urlbar.yelp.featureGate", "description": "Feature gate that controls whether all aspects of the Yelp suggestion feature are exposed to the user."}, "yelpShowLessFrequentlyCap": {"type": "int", "fallbackPref": "browser.urlbar.yelp.showLessFrequentlyCap", "description": "If defined and non-zero, this is the maximum number of times the user will be able to click the \"Show less frequently\" command for Yelp suggestions. If undefined or zero, the user will be able to click the command without any limit."}, "yelpSuggestNonPriorityIndex": {"type": "int", "description": "The group-relative suggestedIndex of Yelp suggestions within the Firefox Suggest section. Ignored when `yelpSuggestPriority` is true."}, "yelpSuggestPriority": {"type": "boolean", "fallbackPref": "browser.urlbar.yelp.priority", "description": "Whether or not showing yelp suggestion as priority. If this variable is true, the following things are processed. * Change the suggested index to 1. * Handle as top pick."}, "originsAlternativeEnable": {"description": "Use an alternative ranking algorithm for autofilling origins, that is mainly domains of Web pages. When the user types the beginning of an origin, we autofill the whole origin. Whether autofill happens depends on the ranking algorithm. Bookmarks are always autofilled anyway.", "type": "boolean", "setPref": {"branch": "user", "pref": "places.frecency.origins.alternative.featureGate"}}, "originsDaysCutOff": {"description": "The alternative ranking algorithm only considers pages visited in the last N days, where N is controlled by this variable.", "type": "int", "setPref": {"branch": "user", "pref": "places.frecency.origins.alternative.daysCutOff"}}, "pagesAlternativeEnable": {"description": "Use an alternative ranking algorithm for sorting history and bookmarks among the urlbar results.", "type": "boolean", "setPref": {"branch": "user", "pref": "places.frecency.pages.alternative.featureGate"}}, "pagesNumSampledVisits": {"description": "The number of recent visits to sample when calculating the ranking of a page. Examining all the visits would be expensive, so we only sample recent visits.", "type": "int", "setPref": {"branch": "user", "pref": "places.frecency.pages.alternative.numSampledVisits"}}, "pagesHalfLifeDays": {"description": "The number of days after which the ranking halves. This implements the \"recency\" part of the algorithm.", "type": "int", "setPref": {"branch": "user", "pref": "places.frecency.pages.alternative.halfLifeDays"}}, "pagesHighWeight": {"description": "The weight to use for the high importance bucket.", "type": "int", "setPref": {"branch": "user", "pref": "places.frecency.pages.alternative.highWeight"}}, "pagesMediumWeight": {"description": "The weight to use for the medium importance bucket.", "type": "int", "setPref": {"branch": "user", "pref": "places.frecency.pages.alternative.mediumWeight"}}, "pagesLowWeight": {"description": "The weight to use for the low importance bucket.", "type": "int", "setPref": {"branch": "user", "pref": "places.frecency.pages.alternative.lowWeight"}}}}, "aboutwelcome": {"description": "The about:welcome page", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "Exposure is sent once per browsing session when the about:welcome URL is first accessed.", "isEarlyStartup": true, "variables": {"enabled": {"type": "boolean", "fallbackPref": "browser.aboutwelcome.enabled", "description": "Should users see about:welcome? If this is false, users will see a regular new tab instead."}, "id": {"type": "string", "description": "Descriptive ID for the about:welcome content"}, "screens": {"type": "json", "fallbackPref": "browser.aboutwelcome.screens", "description": "Content to show in the onboarding flow"}, "languageMismatchEnabled": {"type": "boolean", "fallbackPref": "intl.multilingual.aboutWelcome.languageMismatchEnabled", "description": "Suggest to change the language on about:welcome when there is a mismatch with the OS."}, "transitions": {"type": "boolean", "description": "Enable transition effect between screens"}, "showModal": {"type": "boolean", "fallbackPref": "browser.aboutwelcome.showModal", "description": "Should users see window modal onboarding"}, "backdrop": {"type": "string", "fallbackPref": "browser.aboutwelcome.backdrop", "description": "Specify the color to be used to update the background color"}, "toolbarButtonEnabled": {"type": "boolean", "setPref": {"branch": "user", "pref": "browser.aboutwelcome.toolbarButtonEnabled"}, "description": "Should the return to about:welcome toolbar button be shown"}}}, "moreFromMozilla": {"description": "New page on about:preferences to suggest more Mozilla products", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "Exposure is sent once per browsing session when the about:preferences URL is first accessed.", "variables": {"enabled": {"type": "boolean", "fallbackPref": "browser.preferences.moreFromMozilla", "description": "Should users see the new more from Mozilla section."}, "template": {"type": "string", "fallbackPref": "browser.preferences.moreFromMozilla.template", "description": "UI template used to display Mozilla products. Possible values simple, advanced. Default is simple."}}}, "windowsLaunchOnLogin": {"description": "New checkbox in about:preferences startup section to start Firefox on Windows login", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "Exposure is sent once per browsing session when the about:preferences URL is first accessed.", "variables": {"enabled": {"type": "boolean", "setPref": {"branch": "default", "pref": "browser.startup.windowsLaunchOnLogin.enabled"}, "description": "Should users see the Windows launch on login checkbox."}}}, "firefoxBridge": {"description": "Controls for Firefox Bridge extension and extension registration", "owner": "install-update@mozilla.com", "hasExposure": false, "variables": {"enabled": {"type": "boolean", "setPref": {"branch": "default", "pref": "browser.firefoxbridge.enabled"}, "description": "Should Firefox Bridge be registered within firefoxBridgeNativeMessaging startup idle task for use with native messaging proxy."}}}, "abouthomecache": {"description": "The startup about:home cache.", "owner": "omc@mozilla.com, mconley@mozilla.com", "hasExposure": false, "variables": {"enabled": {"type": "boolean", "description": "Is the feature enabled?", "setPref": {"branch": "user", "pref": "browser.startup.homepage.abouthome_cache.enabled"}}}}, "newtab": {"description": "The about:newtab page", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "Exposure is sent once per browsing session when the first newtab page loads (either about:newtab or about:home).", "isEarlyStartup": true, "variables": {"newTheme": {"type": "boolean", "description": "Enable the new theme"}, "customizationMenuEnabled": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.customizationMenu.enabled", "description": "Enable the customization panel inside of the newtab"}, "prefsButtonIcon": {"type": "string", "description": "Icon url to use for the preferences button"}, "topSitesContileEnabled": {"type": "boolean", "fallbackPref": "browser.topsites.contile.enabled", "description": "Enable the Contile integration for Sponsored Top Sites"}, "topSitesUseAdditionalTilesFromContile": {"type": "boolean", "description": "Allow Contile to use additonal sponsored top sites"}}}, "newtabSpocsCache": {"description": "The about:newtab sponsored content cache.", "owner": "sdowne@mozilla.com", "hasExposure": false, "variables": {"spocsCacheTimeout": {"type": "int", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.discoverystream.spocs.cacheTimeout"}, "description": "Set sponsored content cache timeout in minutes."}, "spocsStartupCache": {"type": "boolean", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.discoverystream.spocs.startupCache.enabled"}, "description": "Controls if spocs should be included in startup cache."}}}, "newtabLayoutExperiment": {"description": "Change the default layout of new tab by adjusting sizes and spacing of elements.", "owner": "achurchwell@mozilla.com, sdowne@mozilla.com", "hasExposure": false, "variables": {"variantA": {"type": "boolean", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.newtabLayouts.variant-a"}, "description": "Variant A in layouts experiment."}, "variantB": {"type": "boolean", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.newtabLayouts.variant-b"}, "description": "Variant B in layouts experiment."}}}, "newtabSponsoredContent": {"description": "Change and control the sponsored content on newtab.", "owner": "sdowne@mozilla.com", "hasExposure": false, "variables": {"spocPositions": {"type": "string", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.discoverystream.spoc-positions"}, "description": "CSV string of spoc position indexes on newtab Pocket grid"}}}, "newtabTopicSelection": {"description": "the about:newtab topic selection experience.", "owner": "nbarrett@mozilla.com", "hasExposure": false, "variables": {"availableTopics": {"type": "string", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.discoverystream.topicSelection.topics"}, "description": "List of available topics to select for topic selection"}, "suggestedTopics": {"type": "string", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.discoverystream.topicSelection.suggestedTopics"}, "description": "List of pre-selected topics to display on first run of topic selection modal"}, "topicSelectionOnboarding": {"type": "boolean", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.discoverystream.topicSelection.onboarding.enabled"}, "description": "Turns on and off topic selection onbaording"}, "regionTopicsConfig": {"description": "A comma-separated list of regions that get topics selection by default.", "type": "string", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.discoverystream.topicSelection.region-topics-config"}}, "localeTopicsConfig": {"description": "A comma-separated list of locales that get topics selection by default.", "type": "string", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.discoverystream.topicSelection.locale-topics-config"}}, "regionTopicLabelConfig": {"description": "A comma-separated list of regions that get topic labels by default.", "type": "string", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.discoverystream.topicLabels.region-topic-label-config"}}, "localeTopicLabelConfig": {"description": "A comma-separated list of locales that get topic Labels by default.", "type": "string", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.discoverystream.topicLabels.locale-topic-label-config"}}}}, "pocketNewtab": {"description": "The Pocket section in newtab", "owner": "sdowne@mozilla.com", "hasExposure": false, "isEarlyStartup": true, "variables": {"spocTopsitesPositions": {"type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.spoc-topsites-positions", "description": "CSV string of spoc position indexes on newtab topsites section"}, "contileTopsitesPositions": {"type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.contile-topsites-positions", "description": "CSV string of contile position indexes on newtab topsites section"}, "spocAdTypes": {"type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.spocAdTypes", "description": "CSV string of data to set the spoc content."}, "spocZoneIds": {"type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.spocZoneIds", "description": "CSV string of data to set the spoc content."}, "spocTopsitesAdTypes": {"type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.spocTopsitesAdTypes", "description": "CSV string of data to set the spoc content."}, "spocTopsitesZoneIds": {"type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.spocTopsitesZoneIds", "description": "CSV string of data to set the spoc content."}, "spocTopsitesPlacementEnabled": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.spocTopsitesPlacement.enabled", "description": "Tuns on and off the sponsored topsites placement."}, "spocSiteId": {"type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.spocSiteId", "description": "String ID to set the spoc content."}, "widgetPositions": {"type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.widget-positions", "description": "CSV string of widget position indexes on newtab grid"}, "hybridLayout": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.hybridLayout.enabled", "description": "Enable compact cards on newtab grid only for specific breakpoints"}, "hideCardBackground": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.hideCardBackground.enabled", "description": "Removes Pocket card background and borders."}, "fourCardLayout": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.fourCardLayout.enabled", "description": "Enable four Pocket cards per row."}, "newFooterSection": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.newFooterSection.enabled", "description": "Enable an updated Pocket section topics footer"}, "saveToPocketCard": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.saveToPocketCard.enabled", "description": "A save to Pocket button inside the card, shown on the card thumbnail, on hover."}, "saveToPocketCardRegions": {"type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.saveToPocketCardRegions", "description": "CSV string of regions that support the save to Pocket button inside the card."}, "hideDescriptions": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.hideDescriptions.enabled", "description": "Hide or display descriptions for Pocket stories on newtab."}, "hideDescriptionsRegions": {"type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.hideDescriptionsRegions", "description": "CSV string of regions that hide descriptions for Pocket stories on newtab."}, "compactGrid": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.compactGrid.enabled", "description": "Reduce the number of pixels between the Pocket cards on newtab."}, "compactImages": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.compactImages.enabled", "description": "Reduce the height on Pocket card images on newtab."}, "imageGradient": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.imageGradient.enabled", "description": "Add a gradient to the bottom of Pocket card images on newtab to blend the image in with the card."}, "titleLines": {"type": "int", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.titleLines", "description": "Changes the maximum number of lines a title can be for Pocket cards on newtab."}, "descLines": {"type": "int", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.descLines", "description": "Changes the maximum number of lines a description can be for Pocket cards on newtab."}, "onboardingExperience": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.onboardingExperience.enabled", "description": "Enables an onboarding experience for Pocket section on newtab."}, "essentialReadsHeader": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.essentialReadsHeader.enabled", "description": "Updates the Pocket section header and title to say \"Today\u2019s Essential Reads\", moves the \"Recommended by Pocket\" header to the right side."}, "editorsPicksHeader": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.editorsPicksHeader.enabled", "description": "Updates the Pocket section header and title to say \"Editor\u2019s Picks\", if used with essentialReadsHeader, creates a second section 2 rows down for editorsPicksHeader."}, "recentSavesEnabled": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.recentSaves.enabled", "description": "Updates the Pocket section with a new header and 1 row of recently saved Pocket stories."}, "readTime": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.readTime.enabled", "description": "Displays an estimated read time for Pocket cards on newtab."}, "newSponsoredLabel": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.newSponsoredLabel.enabled", "description": "Updates the sponsored label position to below the image for Pocket cards on newtab."}, "sendToPocket": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.sendToPocket.enabled", "description": "Decides what to do when a logged out user click \"Save to Pocket\" from a Pocket card."}, "wallpapers": {"type": "boolean", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.newtabWallpapers.enabled"}, "description": "Turns on and off wallpaper support."}, "wallpapersV2": {"type": "boolean", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.newtabWallpapers.v2.enabled"}, "description": "Turns on and off updated wallpaper experience."}, "wallpapersHighlightEnabled": {"type": "boolean", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.newtabWallpapers.highlightEnabled"}, "description": "Turns on and off wallpaper feature highlight."}, "wallpaperHighlightHeaderText": {"type": "string", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.newtabWallpapers.highlightHeaderText"}, "description": "Changes the wallpaper feature highlight header"}, "wallpaperHighlightContentText": {"type": "string", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.newtabWallpapers.highlightContentText"}, "description": "Changes the wallpaper feature highlight content"}, "wallpaperHighlightCtaText": {"type": "string", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.newtabWallpapers.highlightCtaText"}, "description": "Changes the wallpaper feature highlight cta"}, "weatherLocationSearch": {"type": "boolean", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.weather.locationSearchEnabled"}, "description": "Turns on and off location search for newtab weather widget"}, "recsPersonalized": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.recs.personalized", "description": "Enables Pocket stories personalization."}, "spocsPersonalized": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.spocs.personalized", "description": "Enables Pocket sponsored content personalization."}, "discoveryStreamConfig": {"description": "A JSON blob of discovery stream configuration.", "type": "string", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.discoverystream.config"}}, "spocsEndpoint": {"description": "The URL for the spocs endpoint.", "type": "string", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.discoverystream.spocs-endpoint"}}, "spocsEndpointAllowlist": {"description": "Comma separated list of allowed endpoints for fetching spocs", "type": "string", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.discoverystream.endpoints"}}, "spocsClearEndpoint": {"description": "URL for deleting any server data when a user opts out of sponsored content", "type": "string", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.discoverystream.endpointSpocsClear"}}, "ctaButtonSponsors": {"description": "A CSV list of sponsors that should use a button CTA.", "type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.ctaButtonSponsors"}, "ctaButtonVariant": {"description": "Specifies which variant to use for any sponsors in ctaButtonSponsors", "type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.ctaButtonVariant"}, "spocMessageVariant": {"description": "Adds some message dialogs explainging sponsored content to the user", "type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.spocMessageVariant"}, "regionStoriesConfig": {"description": "A comma-separated list of region to get stories for.", "type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.region-stories-config"}, "regionBffConfig": {"type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.region-bff-config", "description": "A comma-separated list of regions to get stories from the recommendations BFF. Also requires region-stories-config."}, "merinoProviderEnabled": {"type": "boolean", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.merino-provider.enabled", "description": "Sets the recommendations provider service to Merino."}, "merinoProviderEndpoint": {"type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.merino-provider.endpoint", "description": "Merino endpoint to use for recommendations."}, "regionStoriesBlock": {"description": "A comma-separated list of regions that do not get stories, regardless of locale-list-config.", "type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.region-stories-block"}, "localeListConfig": {"description": "A comma-separated list of locales that get stories, regardless of region-stories-config.", "type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.locale-list-config"}, "regionSpocsConfig": {"description": "A comma-separated list of regions that get spocs by default.", "type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.region-spocs-config"}, "regionWeatherConfig": {"description": "A comma-separated list of regions that get weather by default.", "type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.region-weather-config"}, "localeWeatherConfig": {"description": "A comma-separated list of locales that weather widget supports.", "type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.locale-weather-config"}, "topSitesMaxSponsored": {"type": "int", "description": "The maximum number of sponsored Top Sites to be displayed"}, "topSitesContileMaxSponsored": {"type": "int", "description": "The maximum number of sponsored Top Sites used from Contile"}, "topSitesContileSovEnabled": {"description": "Enable the Share-of-Voice feature for Sponsored Topsites.", "type": "boolean", "fallbackPref": "browser.topsites.contile.sov.enabled"}, "pocketFeedParameters": {"type": "string", "fallbackPref": "browser.newtabpage.activity-stream.discoverystream.pocket-feed-parameters", "description": "Add parameters to Pocket feed URL."}, "merinoFeedExperiment": {"type": "boolean", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.discoverystream.merino-feed-experiment"}, "description": "Should we pass the experiment branch and slug to the Merino feed request."}, "thumbsUpDown": {"type": "boolean", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.discoverystream.thumbsUpDown.enabled"}, "description": "Display thumbs up and down buttons on recommended stories"}, "regionThumbsUpDownConfig": {"description": "Display thumbs up and down buttons on recommended stories", "type": "string", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.discoverystream.thumbsUpDown.region-thumbs-config"}}, "localeThumbsUpDownConfig": {"description": "A comma-separated list of locales that get thumbs up and down buttons by default.", "type": "string", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.discoverystream.thumbsUpDown.locale-thumbs-config"}}, "thumbsUpDownCompactLayout": {"type": "boolean", "setPref": {"branch": "user", "pref": "browser.newtabpage.activity-stream.discoverystream.thumbsUpDown.searchTopsitesCompact"}, "description": "Change the layout of the sections to be more compact to account for the added card height with the thumbs up/down enabled."}}}, "saveToPocket": {"description": "The save to Pocket feature", "owner": "sdowne@mozilla.com", "hasExposure": false, "variables": {"emailButton": {"type": "boolean", "setPref": {"branch": "user", "pref": "extensions.pocket.refresh.emailButton.enabled"}, "description": "Just for the new Pocket panels, enables the email signup button."}, "hideRecentSaves": {"type": "boolean", "setPref": {"branch": "user", "pref": "extensions.pocket.refresh.hideRecentSaves.enabled"}, "description": "Hides the recently saved section in the home panel."}, "bffRecentSaves": {"type": "boolean", "setPref": {"branch": "user", "pref": "extensions.pocket.bffRecentSaves"}, "description": "Use the new BFF Proxy Service instead of the legacy Pocket Service for Recent Saves"}, "bffApi": {"type": "string", "setPref": {"branch": "user", "pref": "extensions.pocket.bffApi"}, "description": "BFF Proxy Service domain"}, "oAuthConsumerKeyBff": {"type": "string", "setPref": {"branch": "user", "pref": "extensions.pocket.oAuthConsumerKeyBff"}, "description": "BFF Proxy Service OAuth Consumer Key"}}}, "password-autocomplete": {"description": "A special autocomplete UI for password fields.", "owner": "sgalich@mozilla.com", "hasExposure": false, "variables": {"directMigrateSingleProfile": {"type": "boolean", "description": "Enable direct migration?"}}}, "cm-csv-import": {"description": "Importing logins from CSV files", "owner": "issozi@mozilla.com", "hasExposure": false, "variables": {"csvImport": {"type": "boolean", "description": "Can show CSV Import in about:logins or Migration Wizard", "setPref": {"branch": "default", "pref": "signon.management.page.fileImport.enabled"}}}}, "address-autofill-feature": {"description": "Enabling address autofill feature", "owner": "issozi@mozilla.com", "hasExposure": false, "variables": {"status": {"type": "boolean", "setPref": {"branch": "default", "pref": "extensions.formautofill.addresses.experiments.enabled"}, "description": "If true, we will allow user to use address autofill"}}}, "shellService": {"description": "Interface with OS, e.g., pinning and set default", "owner": "desktop-integrations@mozilla.com", "hasExposure": false, "variables": {"disablePin": {"type": "boolean", "description": "Disable pin to taskbar feature"}, "disableStartMenuPin": {"type": "boolean", "setPref": {"branch": "user", "pref": "browser.shell.disableStartMenuPin"}, "description": "Disable pin to start menu feature"}, "setDefaultBrowserUserChoice": {"type": "boolean", "setPref": {"branch": "user", "pref": "browser.shell.setDefaultBrowserUserChoice"}, "description": "Should it set as default browser"}, "setDefaultBrowserUserChoiceRegRename": {"type": "boolean", "setPref": {"branch": "user", "pref": "browser.shell.setDefaultBrowserUserChoice.regRename"}, "description": "When setting default via UserChoice, rename the underlying registry key to prevent kennel driver registry locking."}, "setDefaultPDFHandler": {"type": "boolean", "fallbackPref": "browser.shell.setDefaultPDFHandler", "description": "Should setting it as the default browser set it as the default PDF handler."}, "setDefaultPDFHandlerOnlyReplaceBrowsers": {"type": "boolean", "fallbackPref": "browser.shell.setDefaultPDFHandler.onlyReplaceBrowsers", "description": "Should setting it as the default PDF handler only replace existing PDF handlers that are browsers, and not other PDF handlers such as Acrobat Reader or Nitro PDF."}, "setDefaultGuidanceNotifications": {"type": "boolean", "fallbackPref": "browser.shell.setDefaultGuidanceNotifications", "description": "Whether or not the user should see the guidance notifications when setting Firefox as their default browser."}}}, "upgradeDialog": {"description": "The dialog shown for major upgrades", "owner": "omc@mozilla.com", "hasExposure": false, "isEarlyStartup": true, "variables": {"enabled": {"type": "boolean", "fallbackPref": "browser.startup.upgradeDialog.enabled", "description": "Is the feature enabled?"}}}, "cfr": {"description": "A Firefox Messaging System message for the cfr message channel", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.", "schema": {"uri": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", "path": "browser/components/asrouter/content-src/schemas/MessagingExperiment.schema.json"}, "variables": {}}, "moments-page": {"description": "A Firefox Messaging System message for the moments-page message channel", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "\"Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.\"", "schema": {"uri": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", "path": "browser/components/asrouter/content-src/schemas/MessagingExperiment.schema.json"}, "variables": {}}, "infobar": {"description": "A Firefox Messaging system message for the infobar message channel", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "\"Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.\"", "schema": {"uri": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", "path": "browser/components/asrouter/content-src/schemas/MessagingExperiment.schema.json"}, "variables": {}}, "spotlight": {"description": "A Firefox Messaging System message for the spotlight message channel", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "\"Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.\"", "schema": {"uri": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", "path": "browser/components/asrouter/content-src/schemas/MessagingExperiment.schema.json"}, "variables": {}}, "featureCallout": {"description": "A Firefox Messaging System message for the Feature Callout message channel", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "\"Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.\"", "schema": {"uri": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", "path": "browser/components/asrouter/content-src/schemas/MessagingExperiment.schema.json"}, "variables": {}}, "fullPageTranslation": {"description": "This feature opens a popup panel to offer to translate a page.", "owner": "gtatum@mozilla.com", "hasExposure": false, "variables": {"boolean": {"description": "Set to true to enable the translations feature", "type": "boolean", "setPref": {"branch": "user", "pref": "browser.translations.enable"}}}}, "fullPageTranslationAutomaticPopup": {"description": "Controls whether the popup automatically shows for translations.", "owner": "gtatum@mozilla.com", "hasExposure": false, "variables": {"boolean": {"description": "Set to true to automatically popup, and false to only show the button.", "type": "boolean", "setPref": {"branch": "user", "pref": "browser.translations.automaticallyPopup"}}}}, "selectTranslation": {"description": "This feature enables the option to translate selected text from the context menu.", "owner": "enordin@mozilla.com", "hasExposure": false, "variables": {"enabled": {"description": "Set to true to enable the select-translations feature", "type": "boolean", "setPref": {"branch": "user", "pref": "browser.translations.select.enable"}}}}, "pdfjs": {"description": "The Firefox pdf reader.", "owner": "pdfjs-team@mozilla.com", "hasExposure": true, "exposureDescription": "Exposure is sent each time a pdf is displayed.", "variables": {"addHighlight": {"description": "Set to true to highlight some text or something else in an existing pdf.", "type": "boolean", "setPref": {"branch": "default", "pref": "pdfjs.enableHighlightEditor"}}, "enableAltText": {"description": "Set to true to enable alt text generation.", "type": "boolean", "setPref": {"branch": "default", "pref": "pdfjs.enableAltText"}}, "enableUpdatedAddImage": {"description": "Set to true to enable updated add image with alt text flow.", "type": "boolean", "setPref": {"branch": "default", "pref": "pdfjs.enableUpdatedAddImage"}}, "browserMlEnable": {"description": "Set to true to enable local inference engine", "type": "boolean", "setPref": {"branch": "default", "pref": "browser.ml.enable"}}}}, "fxms-message-1": {"description": "A Firefox Messaging System message", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "\"Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.\"", "schema": {"uri": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", "path": "browser/components/asrouter/content-src/schemas/MessagingExperiment.schema.json"}, "variables": {}}, "fxms-message-2": {"description": "Firefox Messaging System message 2", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "\"Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.\"", "schema": {"uri": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", "path": "browser/components/asrouter/content-src/schemas/MessagingExperiment.schema.json"}, "variables": {}}, "fxms-message-3": {"description": "Firefox Messaging System message 3", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "\"Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.\"", "schema": {"uri": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", "path": "browser/components/asrouter/content-src/schemas/MessagingExperiment.schema.json"}, "variables": {}}, "fxms-message-4": {"description": "Firefox Messaging System message 4", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "\"Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.\"", "schema": {"uri": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", "path": "browser/components/asrouter/content-src/schemas/MessagingExperiment.schema.json"}, "variables": {}}, "fxms-message-5": {"description": "Firefox Messaging System message 5", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "\"Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.\"", "schema": {"uri": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", "path": "browser/components/asrouter/content-src/schemas/MessagingExperiment.schema.json"}, "variables": {}}, "fxms-message-6": {"description": "Firefox Messaging System message 6", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "\"Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.\"", "schema": {"uri": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", "path": "browser/components/asrouter/content-src/schemas/MessagingExperiment.schema.json"}, "variables": {}}, "fxms-message-7": {"description": "Firefox Messaging System message 7", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "\"Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.\"", "schema": {"uri": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", "path": "browser/components/asrouter/content-src/schemas/MessagingExperiment.schema.json"}, "variables": {}}, "fxms-message-8": {"description": "Firefox Messaging System message 8", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "\"Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.\"", "schema": {"uri": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", "path": "browser/components/asrouter/content-src/schemas/MessagingExperiment.schema.json"}, "variables": {}}, "fxms-message-9": {"description": "Firefox Messaging System message 9", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "\"Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.\"", "schema": {"uri": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", "path": "browser/components/asrouter/content-src/schemas/MessagingExperiment.schema.json"}, "variables": {}}, "fxms-message-10": {"description": "Firefox Messaging System message 10", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "\"Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.\"", "schema": {"uri": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", "path": "browser/components/asrouter/content-src/schemas/MessagingExperiment.schema.json"}, "variables": {}}, "fxms-message-11": {"description": "Firefox Messaging System message 11", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "\"Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.\"", "schema": {"uri": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", "path": "browser/components/asrouter/content-src/schemas/MessagingExperiment.schema.json"}, "variables": {}}, "whatsNewPage": {"description": "A Firefox Messaging System message for the What's new page channel", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "\"Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.\"", "variables": {"overrideUrl": {"description": "URL of the What's new page", "type": "string", "setPref": {"branch": "user", "pref": "startup.homepage_override_url_nimbus"}}, "maxVersion": {"description": "Maximum Firefox update version", "type": "string", "setPref": {"branch": "user", "pref": "startup.homepage_override_nimbus_maxVersion"}}, "minVersion": {"description": "Minimum Firefox update version", "type": "string", "setPref": {"branch": "user", "pref": "startup.homepage_override_nimbus_minVersion"}}, "disableWNP": {"description": "Block all What's New Pages. Used to compare no-WNP control branches to WNP treatment branches.", "type": "boolean", "setPref": {"branch": "user", "pref": "startup.homepage_override_nimbus_disable_wnp"}}}}, "pbNewtab": {"description": "A Firefox Messaging System message for the pbNewtab message channel", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.", "schema": {"uri": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", "path": "browser/components/asrouter/content-src/schemas/MessagingExperiment.schema.json"}, "variables": {}}, "backgroundTaskMessage": {"description": "A Firefox Messaging System message for the background task message channel", "owner": "nalexander@mozilla.com", "applications": ["firefox-desktop-background-task"], "hasExposure": true, "exposureDescription": "Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.", "schema": {"uri": "chrome://browser/content/asrouter/schemas/BackgroundTaskMessagingExperiment.schema.json", "path": "browser/components/asrouter/content-src/schemas/BackgroundTaskMessagingExperiment.schema.json"}, "variables": {}}, "backgroundUpdateAutomaticRestart": {"description": "Whether to automatically restart when the background update task could make more progress.", "owner": "nalexander@mozilla.com", "applications": ["firefox-desktop-background-task"], "hasExposure": false, "variables": {"enabled": {"type": "boolean", "fallbackPref": "app.update.background.automaticRestartEnabled", "description": "When true, make the background update task restart when the final update state is `READY_FOR_RESTART`. Generally, this will finish applying a staged update, completing the update earlier than it otherwise would have been completed."}}}, "pictureinpicture": {"description": "Message for first time Picture-in-Picture users", "owner": "nbaumgardner@mozilla.com", "hasExposure": true, "exposureDescription": "Exposure is sent when a user hovers over a video and Picture-in-Picture has not been used before", "variables": {"title": {"type": "string", "description": "The title to be used for the PiP toggle"}, "message": {"type": "string", "description": "The message to be used in the PiP toggle"}, "showIconOnly": {"type": "boolean", "description": "Whether to show the first time PiP toggle or show the PiP icon only"}, "oldToggle": {"type": "boolean", "description": "Whether to show the control style (true) or variant style (false) for the first time PiP toggle"}, "displayDuration": {"type": "int", "description": "Duration of PiP first time toggle display in days before switching to PiP icon toggle"}}}, "glean": {"description": "The Glean data-control-plane feature within Firefox Desktop for controlling metric configuration", "owner": "glean-team@mozilla.com", "hasExposure": false, "variables": {"newtabPingEnabled": {"type": "boolean", "fallbackPref": "browser.newtabpage.ping.enabled", "description": "Whether to submit the 'newtab' ping"}, "gleanMetricConfiguration": {"type": "json", "description": "A map of metric base-identifiers to booleans representing the state of the 'enabled' flag for that metric.\nThis variable is intended for interacting with the Glean data-control-plane via the Server Knobs functionality\nto remotely configure metrics to be enabled or disabled.\n"}}}, "gleanInternalSdk": {"description": "The Glean internal SDK feature intended only for internal Glean Team use", "owner": "glean-team@mozilla.com", "hasExposure": false, "variables": {"finalInactive": {"type": "boolean", "description": "Enables FOG early shutdown pings when true", "setPref": {"branch": "user", "pref": "telemetry.glean.internal.finalInactive"}}, "gleanMetricConfiguration": {"type": "json", "description": "A map of metric base-identifiers to booleans representing the state of the 'enabled' flag for that metric.\nThis is not for public use! For data-control-plane use, please refer to the Glean documentation and use the\n`gleanMetricConfiguration` found in the `glean` feature for this.\n"}, "gleanMaxPingsPerMinute": {"type": "int", "description": "Maximum number of pings that can be sent in a 60 second interval", "setPref": {"branch": "user", "pref": "telemetry.glean.internal.maxPingsPerMinute"}}}}, "browserLowMemoryPrefs": {"description": "Prefs which control the browser's behaviour under low memory.", "owner": "haftandilian@mozilla.com", "hasExposure": false, "variables": {"lowMemoryResponseMask": {"description": "Control the response on macOS when under memory pressure.", "type": "int", "setPref": {"branch": "default", "pref": "browser.lowMemoryResponseMask"}}, "lowMemoryResponseOnWarn": {"description": "Controls which macOS memory-pressure levels trigger the browser low memory response.", "type": "boolean", "setPref": {"branch": "default", "pref": "browser.lowMemoryResponseOnWarn"}}, "tabsUnloadOnLowMemory": {"description": "Whether to unload tabs when available memory is running low.", "type": "boolean", "setPref": {"branch": "default", "pref": "browser.tabs.unloadOnLowMemory"}}}}, "scriptLoaderPrefs": {"description": "Prefs that control the script loader.", "owner": "npierron@mozilla.com", "hasExposure": false, "variables": {"delazificationStrategy": {"description": "Selects which parsing/delazification strategy should be used while parsing scripts off-main-thread. See DelazificationOption in CompileOptions.h for values.", "type": "int", "setPref": {"branch": "default", "pref": "dom.script_loader.delazification.strategy"}}}}, "echPrefs": {"description": "Prefs that control Encrypted Client Hello.", "owner": "djackson@mozilla.com", "hasExposure": false, "variables": {"tlsEnabled": {"description": "Whether to enable ECH for connections using TLS", "type": "boolean", "setPref": {"branch": "default", "pref": "network.dns.echconfig.enabled"}}, "h3Enabled": {"description": "Whether to enable ECH for connections using H3/QUIC", "type": "boolean", "setPref": {"branch": "default", "pref": "network.dns.http3_echconfig.enabled"}}, "forceWaitHttpsRR": {"description": "Whether to force waiting for HTTPS DNS records, which ECH requires.", "type": "boolean", "setPref": {"branch": "default", "pref": "network.dns.force_waiting_https_rr"}}, "insecureFallback": {"description": "Whether to fallback to non-ECH connections if all ECH RRs fail.", "type": "boolean", "setPref": {"branch": "default", "pref": "network.dns.echconfig.fallback_to_origin_when_all_failed"}}, "tlsGreaseProb": {"description": "Probability of GREASEing a TLS connection with ECH (0-100).", "type": "int", "setPref": {"branch": "default", "pref": "security.tls.ech.grease_probability"}}, "h3GreaseEnabled": {"description": "Whether to apply GREASE settings to H3/QUIC connections.", "type": "boolean", "setPref": {"branch": "default", "pref": "security.tls.ech.grease_http3"}}, "disableGreaseOnFallback": {"description": "Whether to disable GREASE when retrying a connection.", "type": "boolean", "setPref": {"branch": "default", "pref": "security.tls.ech.disable_grease_on_fallback"}}, "greasePaddingSize": {"description": "Assumed echConfig padding length for GREASE extensions (1-255).", "type": "int", "setPref": {"branch": "default", "pref": "security.tls.ech.grease_size"}}}}, "dohPrefs": {"description": "Prefs that control DNS over HTTPS.", "owner": "vgosu@mozilla.com", "hasExposure": false, "variables": {"trrMode": {"description": "Has a value of 2 for TRR first, 3 for TRR only, 0 for off.", "type": "int", "setPref": {"branch": "default", "pref": "network.trr.mode"}}, "trrUri": {"description": "The URL of the DNS over HTTPS endpoint", "type": "string", "setPref": {"branch": "default", "pref": "network.trr.uri"}}, "dohMode": {"description": "Same as trrMode, but set by the DoHController module.", "type": "int", "setPref": {"branch": "default", "pref": "doh-rollout.mode"}}, "dohUri": {"description": "Same as trrUri, but set by the DoHController module.", "type": "string", "setPref": {"branch": "default", "pref": "doh-rollout.uri"}}, "enableFallbackWarningPage": {"description": "Whether DoH fallback warning page will be displayed when DoH doesn't work in TRR first mode.", "type": "boolean", "setPref": {"branch": "default", "pref": "network.trr.display_fallback_warning"}}, "showFallbackCheckbox": {"description": "Whether the checkbox to enable the fallback warning page is displayed in the settings UI.", "type": "boolean", "setPref": {"branch": "default", "pref": "network.trr_ui.show_fallback_warning_option"}}, "nativeHTTPSRecords": {"description": "Whether we can perform native DNS HTTPS lookups", "type": "boolean", "setPref": {"branch": "default", "pref": "network.dns.native_https_query"}}}}, "dooh": {"description": "DNS over Oblivious HTTP", "owner": "vgosu@mozilla.com", "hasExposure": false, "variables": {"ohttpEnabled": {"description": "Whether to use Oblivious HTTP for the resolution", "type": "boolean", "setPref": {"branch": "default", "pref": "network.trr.use_ohttp"}}, "ohttpRelayUri": {"description": "The URL of the Oblivious HTTP relay", "type": "string", "setPref": {"branch": "default", "pref": "network.trr.ohttp.relay_uri"}}, "ohttpConfigUri": {"description": "The URL used to fetch the configuration of the Oblivious HTTP gateway", "type": "string", "setPref": {"branch": "default", "pref": "network.trr.ohttp.config_uri"}}, "ohttpUri": {"description": "The URL of the Oblivious DNS over HTTPS target resource", "type": "string", "setPref": {"branch": "default", "pref": "network.trr.ohttp.uri"}}}}, "networking": {"description": "Firefox Networking (Necko)", "owner": "vgosu@mozilla.com", "hasExposure": false, "variables": {"ehPreloadEnabled": {"description": "Whether Early Hints preload is enabled", "type": "boolean", "setPref": {"branch": "default", "pref": "network.early-hints.enabled"}}, "ehPreconnectEnabled": {"description": "Whether Early Hints preconnect is enabled", "type": "boolean", "setPref": {"branch": "default", "pref": "network.early-hints.preconnect.enabled"}}, "dnsMaxPriorityThreads": {"description": "The maximum number of high priority DNS threads that can be created.", "type": "int", "setPref": {"branch": "default", "pref": "network.dns.max_high_priority_threads"}}, "dnsMaxAnyPriorityThreads": {"description": "The maximum number of DNS threads that can be created to handle any priority DNS requests.", "type": "int", "setPref": {"branch": "default", "pref": "network.dns.max_any_priority_threads"}}, "preconnect": {"description": "Whether the rel=preconnect feature is enabled", "type": "boolean", "setPref": {"branch": "default", "pref": "network.preconnect"}}, "networkPredictor": {"description": "Whether the Necko predictor is enabled", "type": "boolean", "setPref": {"branch": "default", "pref": "network.predictor.enabled"}}, "http3CCalgorithm": {"description": "The congestion control algorithm with which to configure neqo. 0 for NewReno, 1 for Cubic", "type": "int", "setPref": {"branch": "default", "pref": "network.http.http3.cc_algorithm"}}, "sendOnDataFinished": {"description": "Whether we can send OnDataFinished in the content process", "type": "boolean", "setPref": {"branch": "default", "pref": "network.send_OnDataFinished"}}, "sendOnDataFinshedFromInputStreamPump": {"description": "Whether we can send OnDataFinished to the content process from InputStreamPump", "type": "boolean", "setPref": {"branch": "default", "pref": "network.send_OnDataFinished.nsInputStreamPump"}}, "sendOnDataFinishedToHtml5parser": {"description": "Whether we can send OnDataFinished to the html5parser in content process", "type": "boolean", "setPref": {"branch": "default", "pref": "network.send_OnDataFinished.html5parser"}}, "sendOnDataFinishedToCssLoader": {"description": "Whether we can send OnDataFinished to the cssLoader in content process", "type": "boolean", "setPref": {"branch": "default", "pref": "network.send_OnDataFinished.cssLoader"}}, "enableOffMainThreadStreamDecompression": {"description": "Whether to enable decompression of network streams off-main-thread", "type": "boolean", "setPref": {"branch": "default", "pref": "network.decompression_off_mainthread2"}}, "offMainThreadStreamDecompressionThreshold": {"description": "Minimum content length for off-main-thread decompression", "type": "int", "setPref": {"branch": "default", "pref": "network.decompression_off_mainthread_min_size"}}, "httpMaxConnections": {"description": "The maximum number of http connections.", "type": "int", "setPref": {"branch": "default", "pref": "network.http.max-connections"}}, "httpMaxPersistentConnectionsPerServer": {"description": "The maximum number of persistent connections to a server", "type": "int", "setPref": {"branch": "default", "pref": "network.http.max-persistent-connections-per-server"}}, "speculativeConnectionLimit": {"description": "The maximum number of half-open sockets allowed for speculative connections", "type": "int", "setPref": {"branch": "default", "pref": "network.http.speculative-parallel-limit"}}, "chipsPartitionLimitEnabled": {"description": "Whether we enforce CHIPS partition limit", "type": "boolean", "setPref": {"branch": "default", "pref": "network.cookie.chips.partitionLimitEnabled"}}, "chipsPartitionLimitDryRun": {"description": "Whether we actually perform purging/rejection, used to report telemetry without webcompat issues", "type": "boolean", "setPref": {"branch": "default", "pref": "network.cookie.chips.partitionLimitDryRun"}}, "chipsPartitionLimitByteCapacity": {"description": "The actual value of the CHIPS partition limit in bytes", "type": "boolean", "setPref": {"branch": "default", "pref": "network.cookie.chips.partitionLimitByteCapacity"}}}}, "networkPrioritization": {"description": "Network request prioritization", "owner": "vgosu@mozilla.com", "hasExposure": false, "variables": {"priorityHeader": {"description": "Whether to set the Priority header on each request.", "type": "boolean", "setPref": {"branch": "default", "pref": "network.http.priority_header.enabled"}}, "fetchPriority": {"description": "Whether fetch priority is enabled", "type": "boolean", "setPref": {"branch": "default", "pref": "network.fetchpriority.enabled"}}, "h3FetchPriority": {"description": "Whether to send HTTP3 priority frames", "type": "boolean", "setPref": {"branch": "default", "pref": "network.http.http3.priority"}}, "h3BackgroundTabDeprioritization": {"description": "Lowers the priority when the tab is in the background. Potential privacy concerns.", "type": "boolean", "setPref": {"branch": "default", "pref": "network.http.http3.send_background_tabs_deprioritization"}}, "documentPriorityIncremental": {"description": "Sets the incremental flag on the Priority header for document requests", "type": "boolean", "setPref": {"branch": "default", "pref": "dom.document_priority.incremental"}}, "imagePriorityIncremental": {"description": "Sets the incremental flag on the Priority header for image requests", "type": "boolean", "setPref": {"branch": "default", "pref": "image.priority.incremental"}}, "imageAdjustLayoutPriority": {"description": "Whether the network request priority should be adjusted according the layout and view frame position of each particular image.", "type": "boolean", "setPref": {"branch": "default", "pref": "image.layout_network_priority"}}, "adjustLinkPreloadScriptLow": {"description": "Priority adjustment low for link-preload-script", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.link-preload-script.low"}}, "adjustLinkPreloadScriptHigh": {"description": "Priority adjustment high for link-preload-script", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.link-preload-script.high"}}, "adjustLinkPreloadScriptAuto": {"description": "Priority adjustment auto for link-preload-script", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.link-preload-script.auto"}}, "adjustModuleScriptLow": {"description": "Priority adjustment low for module-script", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.module-script.low"}}, "adjustModuleScriptHigh": {"description": "Priority adjustment high for module-script", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.module-script.high"}}, "adjustModuleScriptAuto": {"description": "Priority adjustment auto for module-script", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.module-script.auto"}}, "adjustAsyncOrDeferScriptLow": {"description": "Priority adjustment low for async-or-defer-script", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.async-or-defer-script.low"}}, "adjustAsyncOrDeferScriptHigh": {"description": "Priority adjustment high for async-or-defer-script", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.async-or-defer-script.high"}}, "adjustAsyncOrDeferScriptAuto": {"description": "Priority adjustment auto for async-or-defer-script", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.async-or-defer-script.auto"}}, "adjustScriptInHeadLow": {"description": "Priority adjustment low for script-in-head", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.script-in-head.low"}}, "adjustScriptInHeadHigh": {"description": "Priority adjustment high for script-in-head", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.script-in-head.high"}}, "adjustScriptInHeadAuto": {"description": "Priority adjustment auto for script-in-head", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.script-in-head.auto"}}, "adjustOtherScriptLow": {"description": "Priority adjustment low for other-script", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.other-script.low"}}, "adjustOtherScriptHigh": {"description": "Priority adjustment high for other-script", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.other-script.high"}}, "adjustOtherScriptAuto": {"description": "Priority adjustment auto for other-script", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.other-script.auto"}}, "adjustLinkPreloadFontLow": {"description": "Priority adjustment low for link-preload-font", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.link-preload-font.low"}}, "adjustLinkPreloadFontHigh": {"description": "Priority adjustment high for link-preload-font", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.link-preload-font.high"}}, "adjustLinkPreloadFontAuto": {"description": "Priority adjustment auto for link-preload-font", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.link-preload-font.auto"}}, "adjustLinkPreloadFetchLow": {"description": "Priority adjustment low for link-preload-fetch", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.link-preload-fetch.low"}}, "adjustLinkPreloadFetchHigh": {"description": "Priority adjustment high for link-preload-fetch", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.link-preload-fetch.high"}}, "adjustLinkPreloadFetchAuto": {"description": "Priority adjustment auto for link-preload-fetch", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.link-preload-fetch.auto"}}, "adjustDeferredStyleLow": {"description": "Priority adjustment low for deferred-style", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.deferred-style.low"}}, "adjustDeferredStyleHigh": {"description": "Priority adjustment high for deferred-style", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.deferred-style.high"}}, "adjustDeferredStyleAuto": {"description": "Priority adjustment auto for deferred-style", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.deferred-style.auto"}}, "adjustLinkPreloadStyleLow": {"description": "Priority adjustment low for link-preload-style", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.link-preload-style.low"}}, "adjustLinkPreloadStyleHigh": {"description": "Priority adjustment high for link-preload-style", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.link-preload-style.high"}}, "adjustLinkPreloadStyleAuto": {"description": "Priority adjustment auto for link-preload-style", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.link-preload-style.auto"}}, "adjustNonDeferredStyleLow": {"description": "Priority adjustment low for non-deferred-style", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.non-deferred-style.low"}}, "adjustNonDeferredStyleHigh": {"description": "Priority adjustment high for non-deferred-style", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.non-deferred-style.high"}}, "adjustNonDeferredStyleAuto": {"description": "Priority adjustment auto for non-deferred-style", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.non-deferred-style.auto"}}, "adjustGlobalFetchApiLow": {"description": "Priority adjustment low for global-fetch-api", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.global-fetch-api.low"}}, "adjustGlobalFetchApiHigh": {"description": "Priority adjustment high for global-fetch-api", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.global-fetch-api.high"}}, "adjustGlobalFetchApiAuto": {"description": "Priority adjustment auto for global-fetch-api", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.global-fetch-api.auto"}}, "adjustImagesLow": {"description": "Priority adjustment low for images", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.images.low"}}, "adjustImagesHigh": {"description": "Priority adjustment high for images", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.images.high"}}, "adjustImagesAuto": {"description": "Priority adjustment auto for images", "type": "int", "setPref": {"branch": "default", "pref": "network.fetchpriority.adjustments.images.auto"}}}}, "networkingAuth": {"description": "Firefox Networking (Necko) Authentication", "owner": "vgosu@mozilla.com", "hasExposure": false, "variables": {"redirectForAuthRetriesEnabled": {"description": "Whether to enable redirects for auth retries", "type": "boolean", "setPref": {"branch": "default", "pref": "network.auth.use_redirect_for_retries"}}}}, "networkingDenyIpAddrAny": {"description": "Firefox Networking (Necko) Deny IP Address Any", "owner": "vgosu@mozilla.com", "hasExposure": false, "variables": {"denyIpAddrAny": {"description": "Whether to deny empty (0.0.0.0) IP addresses", "type": "boolean", "setPref": {"branch": "default", "pref": "network.socket.ip_addr_any.disabled"}}}}, "pingsender": {"description": "In-product usage of the pingsender telemetry reporter.", "owner": "nalexander@mozilla.com", "hasExposure": false, "variables": {"backgroundTaskEnabled": {"type": "boolean", "fallbackPref": "toolkit.telemetry.shutdownPingSender.backgroundtask.enabled", "description": "Whether to use the `pingsender` background task to send shutdown telemetry"}}}, "dapTelemetry": {"description": "DAP Telemetry", "owner": "simon@mozilla.com", "hasExposure": false, "variables": {"enabled": {"type": "boolean", "description": "Whether to automatically send DAP measurements."}, "task1Enabled": {"type": "boolean", "description": "Whether to send fake measurements for task 1."}, "task1TaskId": {"type": "string", "description": "The task ID to use for task 1 measurements."}, "visitCountingEnabled": {"type": "boolean", "description": "Whether to count visits to the provided list of URLs."}, "visitCountingExperimentList": {"type": "json", "description": "A list of experiments with URLs for which we want to count visits."}}}, "dapAggregators": {"description": "Aggregator server configuration to use for submitting DAP reports.", "owner": "tcampbell@mozilla.com", "hasExposure": false, "variables": {"leader_url": {"description": "The URL of the DAP Leader server where reports are submitted.", "type": "string", "setPref": {"branch": "default", "pref": "toolkit.telemetry.dap.leader.url"}}, "leader_hpke": {"description": "The base64-encode HPKE key to encrypt leader shares with.", "type": "string", "setPref": {"branch": "default", "pref": "toolkit.telemetry.dap.leader.hpke"}}, "helper_url": {"description": "The URL of the DAP Helper server.", "type": "string", "setPref": {"branch": "default", "pref": "toolkit.telemetry.dap.helper.url"}}, "helper_hpke": {"description": "The base64-encode HPKE key to encrypt helper shares with.", "type": "string", "setPref": {"branch": "default", "pref": "toolkit.telemetry.dap.helper.hpke"}}}}, "etpLevel2PBMPref": {"description": "The pref that controls the ETP level 2 list in the private browsing mode", "owner": "tihuang@mozilla.com", "hasExposure": false, "variables": {"enabled": {"description": "Whether to enable ETP level 2 list in the private browsing mode.", "type": "boolean", "setPref": {"branch": "default", "pref": "privacy.annotate_channels.strict_list.pbmode.enabled"}}}}, "fxaButtonVisibility": {"description": "Prefs to control the visibility of the Firefox Accounts toolbar button when not signed in.", "owner": "mconley@mozilla.com", "hasExposure": false, "variables": {"boolean": {"description": "True if the Firefox Accounts toolbar button should be visible when not signed in.", "type": "boolean", "setPref": {"branch": "user", "pref": "identity.fxaccounts.toolbar.defaultVisible"}}, "pxiToolbarEnabled": {"description": "True if we're enabling the PXI dropdown menu for the FxA toolbar button instead of taking the user straight to login", "type": "boolean", "setPref": {"branch": "user", "pref": "identity.fxaccounts.toolbar.pxiToolbarEnabled"}}, "monitorEnabled": {"description": "Toggle the Monitor CTA", "type": "boolean", "setPref": {"branch": "user", "pref": "identity.fxaccounts.toolbar.pxiToolbarEnabled.monitorEnabled"}}, "relayEnabled": {"description": "Toggle the Relay CTA", "type": "boolean", "setPref": {"branch": "user", "pref": "identity.fxaccounts.toolbar.pxiToolbarEnabled.relayEnabled"}}, "vpnEnabled": {"description": "Toggle the VPN CTA", "type": "boolean", "setPref": {"branch": "user", "pref": "identity.fxaccounts.toolbar.pxiToolbarEnabled.vpnEnabled"}}}}, "fxaClientAssociation": {"description": "Prefs to control the client association ping.", "owner": "mconley@mozilla.com", "hasExposure": false, "variables": {"pingEnabled": {"description": "True if the client association ping should be sent.", "type": "boolean", "setPref": {"branch": "user", "pref": "identity.fxaccounts.telemetry.clientAssociationPing.enabled"}}}}, "legacyHeartbeat": {"description": "Normandy Heartbeat exposed to Nimbus", "owner": "beth@mozilla.com", "hasExposure": false, "schema": {"uri": "resource://normandy/schemas/LegacyHeartbeat.schema.json", "path": "toolkit/components/normandy/schemas/LegacyHeartbeat.schema.json"}, "variables": {"survey": {"type": "json", "description": "The Heartbeat survey parameters."}}}, "queryStripping": {"description": "Query parameter stripping anti-tracking feature.", "owner": "pbz@mozilla.com", "hasExposure": false, "variables": {"enabledNormalBrowsing": {"type": "boolean", "setPref": {"branch": "default", "pref": "privacy.query_stripping.enabled"}, "description": "Enables / disables URL query string stripping in normal browsing mode."}, "enabledPrivateBrowsing": {"type": "boolean", "setPref": {"branch": "default", "pref": "privacy.query_stripping.enabled.pbmode"}, "description": "Enables / disables URL query string stripping in private browsing mode."}, "allowList": {"type": "string", "setPref": {"branch": "default", "pref": "privacy.query_stripping.allow_list"}, "description": "List of sites exempt from query stripping. This list will be merged with records coming from RemoteSettings."}, "stripList": {"type": "string", "setPref": {"branch": "default", "pref": "privacy.query_stripping.strip_list"}, "description": "List of query params to be stripped from URIs. This list will be merged with records coming from RemoteSettings."}}}, "fontvisibility": {"description": "Control Font Visibility in PBM", "owner": "tom@mozilla.com", "hasExposure": false, "variables": {"enabledETP": {"type": "int", "setPref": {"branch": "default", "pref": "layout.css.font-visibility.trackingprotection"}, "description": "Set the Font Visibility level when Enhanced Tracking Protection is enabled"}, "enabledStandard": {"type": "int", "setPref": {"branch": "default", "pref": "layout.css.font-visibility.standard"}, "description": "Set the Font Visibility level for normal browsing"}, "enabledPBM": {"type": "int", "setPref": {"branch": "default", "pref": "layout.css.font-visibility.private"}, "description": "Set the Font Visibility level for private browsing (will override ETP)"}}}, "fingerprintingProtection": {"description": "Control Fingerprinting Protection", "owner": "tihuang@mozilla.com", "hasExposure": false, "variables": {"enabledNormal": {"type": "boolean", "setPref": {"branch": "default", "pref": "privacy.fingerprintingProtection"}, "description": "Enables / disables fingerprinting protection in normal browsing mode."}, "enabledPrivate": {"type": "boolean", "setPref": {"branch": "default", "pref": "privacy.fingerprintingProtection.pbmode"}, "description": "Enables / disables fingerprinting protection in private browsing mode."}, "overrides": {"type": "string", "setPref": {"branch": "default", "pref": "privacy.fingerprintingProtection.overrides"}, "description": "The protection overrides to add or remove fingerprinting protection targets. Please check RFPTargets.inc for all supported targets."}, "fdlibm_math": {"type": "boolean", "setPref": {"branch": "default", "pref": "javascript.options.use_fdlibm_for_sin_cos_tan"}, "description": "Uses a different math backend for Math.sin/cos/tan in JavaScript that exposes less entropy"}}}, "userCharacteristics": {"description": "Control user characteristic data collection", "owner": "tihuang@mozilla.com", "hasExposure": false, "variables": {"currentVersion": {"type": "int", "setPref": {"branch": "user", "pref": "toolkit.telemetry.user_characteristics_ping.current_version"}, "description": "The current collection version of the user characteristics."}}}, "migrationWizard": {"description": "Prefs to control the Migration Wizard UI.", "owner": "mconley@mozilla.com", "hasExposure": false, "variables": {"showImportAll": {"description": "True if the \"Variant 2\" of the Migration Wizard browser / profile selection UI should be used. This is only meaningful in the new Migration Wizard.", "type": "boolean", "setPref": {"branch": "user", "pref": "browser.migrate.content-modal.import-all.enabled"}}, "showPreferencesEntrypoint": {"description": "True if an entrypoint to the migration wizard should be visible in about:preferences.", "type": "boolean", "setPref": {"branch": "user", "pref": "browser.migrate.preferences-entrypoint.enabled"}}, "aboutWelcomeBehavior": {"description": "When migration is kicked off from about:welcome, there are a few different behaviors that we want to test, controlled by a preference that is instrumented for Nimbus. The pref has the following possible states:\n\"autoclose\":\n  The user will be directed to the migration wizard in\n  about:preferences, but once the wizard is dismissed,\n  the tab will close.\n\n\"embedded\":\n  The migration wizard is embedded in about:welcome.\n\n\"standalone\":\n  The migration wizard will open in a new top-level content\n  window.\n\n\"default\" / other\n  The user will be directed to the migration wizard in\n  about:preferences. The tab will not close once the\n  user closes the wizard.", "type": "string", "setPref": {"branch": "user", "pref": "browser.migrate.content-modal.about-welcome-behavior"}}, "migrateExtensions": {"description": "True if importing extensions is enabled.", "type": "boolean", "setPref": {"branch": "user", "pref": "browser.migrate.chrome.extensions.enabled"}}, "chromeCanRequestPermissions": {"description": "True if Chrome-based browsers can request read permissions on platforms where the browser is restricted from reading the contents of a Chrome-based browser's user data directory. In practice, this is only relevant to the Linux platform when the browser is installed as a Snap package.", "type": "boolean", "setPref": {"branch": "user", "pref": "browser.migrate.chrome.get_permissions.enabled"}}}}, "mixedContentUpgrading": {"description": "Prefs to control whether we upgrade mixed passive content (images, audio, video) from http to https", "owner": "fbraun@mozilla.com", "hasExposure": false, "variables": {"enabled": {"description": "True if the mixed content upgrading pref is enabled", "type": "boolean", "setPref": {"branch": "default", "pref": "security.mixed_content.upgrade_display_content"}}, "image": {"description": "True if the mixed content upgrading is enabled for images", "type": "boolean", "setPref": {"branch": "default", "pref": "security.mixed_content.upgrade_display_content.image"}}, "audio": {"description": "True if the mixed content upgrading is enabled for audio", "type": "boolean", "setPref": {"branch": "default", "pref": "security.mixed_content.upgrade_display_content.audio"}}, "video": {"description": "True if the mixed content upgrading is enabled for videos", "type": "boolean", "setPref": {"branch": "default", "pref": "security.mixed_content.upgrade_display_content.video"}}}}, "gc": {"description": "Prefs that control gc heuristics.", "owner": "dpalmeiro@mozilla.com", "hasExposure": false, "variables": {"max_nursery_size": {"description": "Set the maximum size of the GC nursery, in kb.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.nursery.max_kb"}}, "min_nursery_size": {"description": "Set the minimum size of the GC nursery, in kb.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.nursery.min_kb"}}, "gc_allocation_threshold_mb": {"description": "Lower limit for collecting a zone, in MB.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_allocation_threshold_mb"}}, "gc_balanced_heap_limits": {"description": "Whether balanced heap limits are enabled.", "type": "boolean", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_balanced_heap_limits"}}, "gc_compacting": {"description": "Whether compacting GC is enabled.", "type": "boolean", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_compacting"}}, "gc_generational": {"description": "Whether generational GC is enabled.", "type": "boolean", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_generational"}}, "gc_heap_growth_factor": {"description": "Heap growth parameter for balanced heap limit calculation.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_heap_growth_factor"}}, "gc_helper_thread_ratio": {"description": "Number of threads to use for parallel GC work.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_helper_thread_ratio"}}, "gc_high_frequency_large_heap_growth": {"description": "Heap growth factor for large heaps in the high-frequency GC state.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_high_frequency_large_heap_growth"}}, "gc_high_frequency_small_heap_growth": {"description": "Heap growth factor for small heaps in the high-frequency GC state.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_high_frequency_small_heap_growth"}}, "gc_high_frequency_time_limit_ms": {"description": "GCs less than this far apart in milliseconds will be considered high-frequency GCs.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_high_frequency_time_limit_ms"}}, "gc_incremental": {"description": "Whether incremental GC is enabled. If not, GC will always run to completion.", "type": "boolean", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_incremental"}}, "incremental_weakmap": {"description": "Enable incremental weakmap marking.", "type": "boolean", "setPref": {"branch": "user", "pref": "javascript.options.mem.incremental_weakmap"}}, "gc_incremental_slice_ms": {"description": "Max milliseconds to spend in an incremental GC slice.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_incremental_slice_ms"}}, "gc_large_heap_incremental_limit": {"description": "Limit of how far over the incremental trigger threshold we allow the heap to grow before finishing a collection non-incrementally, for large heaps.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_large_heap_incremental_limit"}}, "gc_large_heap_size_min_mb": {"description": "Lower limit for classifying a heap as large, in MB.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_large_heap_size_min_mb"}}, "gc_low_frequency_heap_growth": {"description": "Heap growth factor for low frequency GCs.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_low_frequency_heap_growth"}}, "gc_malloc_threshold_base_mb": {"description": "Set the malloc threshold base value in MB.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_malloc_threshold_base_mb"}}, "gc_max_empty_chunk_count": {"description": "Do not keep more than this many unused chunks in the free chunk pool.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_max_empty_chunk_count"}}, "gc_max_helper_threads": {"description": "The maximum number of background threads to use for parallel GC work.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_max_helper_threads"}}, "gc_min_empty_chunk_count": {"description": "We try to keep at least this many unused chunks in the free chunk pool at all times, even after a shrinking GC.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_min_empty_chunk_count"}}, "gc_parallel_marking": {"description": "Enable parallel marking.", "type": "boolean", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_parallel_marking"}}, "gc_parallel_marking_threshold_mb": {"description": "The heap size above which to use parallel marking.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_parallel_marking_threshold_mb"}}, "gc_max_parallel_marking_threads": {"description": "The maximum number of threads to use for parallel marking, if enabled.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_max_parallel_marking_threads"}}, "gc_per_zone": {"description": "Whether per-zone GC is enabled. If not, all zones are collected every time.", "type": "boolean", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_per_zone"}}, "gc_small_heap_incremental_limit": {"description": "Limit of how far over the incremental trigger threshold we allow the heap to grow before finishing a collection non-incrementally, for small heaps.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_small_heap_incremental_limit"}}, "gc_small_heap_size_max_mb": {"description": "Upper limit for classifying a heap as small, in MB.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_small_heap_size_max_mb"}}, "gc_urgent_threshold_mb": {"description": "Set the urgent threshold, in MB.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.gc_urgent_threshold_mb"}}, "nursery_eager_collection_threshold_kb": {"description": "Set the eager collection threshold, in kb, for the nursery.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.nursery_eager_collection_threshold_kb"}}, "nursery_eager_collection_threshold_percent": {"description": "Set the eager collection percent threshold for the nursery.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.nursery_eager_collection_threshold_percent"}}, "nursery_eager_collection_timeout_ms": {"description": "Set the eager collection timeout, in ms, for the nursery.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.mem.nursery_eager_collection_timeout_ms"}}}}, "jsParallelParsing": {"description": "Pref to toggle JS parallel parsing.", "owner": "dpalmeiro@mozilla.com, nbp@mozilla.com", "hasExposure": false, "variables": {"enabled": {"description": "True to enable parallel parsing.", "type": "boolean", "setPref": {"branch": "user", "pref": "javascript.options.parallel_parsing"}}}}, "jitThresholds": {"description": "Prefs that control jit tier thresholds.", "owner": "dpalmeiro@mozilla.com, jdemooij@mozilla.com", "hasExposure": false, "variables": {"blinterp_threshold": {"description": "Set the threshold to enable blinterp compilation.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.blinterp.threshold"}}, "baseline_threshold": {"description": "Set the threshold to enable baseline compilation.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.baselinejit.threshold"}}, "ion_threshold": {"description": "Set the threshold to enable ion compilation.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.ion.threshold"}}, "ion_bailout_threshold": {"description": "Set the ion frequent bailout threshold.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.ion.frequent_bailout_threshold"}}, "ion_offthread_compilation": {"description": "True to enable offthread ion compilations.", "type": "boolean", "setPref": {"branch": "user", "pref": "javascript.options.ion.offthread_compilation"}}, "inlining_max_length": {"description": "Set the max bytecode length considered for inlining.", "type": "int", "setPref": {"branch": "user", "pref": "javascript.options.inlining_bytecode_max_length"}}}}, "jitHintsCache": {"description": "Pref to toggle the JIT hints cache.", "owner": "dpalmeiro@mozilla.com", "hasExposure": false, "variables": {"enabled": {"description": "True to enable the hints cache.", "type": "boolean", "setPref": {"branch": "user", "pref": "javascript.options.jithints"}}}}, "raceCacheWithNetwork": {"description": "Prefs to toggle the race cache with network.", "owner": "dpalmeiro@mozilla.com, acreskey@mozilla.com", "hasExposure": false, "variables": {"enabled": {"description": "True to enable the rcwn feature.", "type": "boolean", "setPref": {"branch": "default", "pref": "network.http.rcwn.enabled"}}}}, "shopping2023": {"description": "Prefs to control the 2023 shopping experiment.", "owner": "jhirsch@mozilla.com", "hasExposure": true, "exposureDescription": "The timing of the exposure event depends on the experiment, but generally the event is recorded when the user first encounters onboarding UI for the shopping feature.", "variables": {"enabled": {"description": "True if the experience is enabled (experimental treatment group)", "type": "boolean", "fallbackPref": "browser.shopping.experience2023.enabled"}, "control": {"description": "True if the experiment is enabled but experience is disabled (experimental control group)", "type": "boolean", "fallbackPref": "browser.shopping.experience2023.control"}, "adsEnabled": {"description": "True if showing recommended products is enabled", "type": "boolean", "setPref": {"branch": "default", "pref": "browser.shopping.experience2023.ads.enabled"}}, "adsExposure": {"description": "True if we want to record ad inventory for opted-in users, even if ads are disabled", "type": "boolean", "setPref": {"branch": "default", "pref": "browser.shopping.experience2023.ads.exposure"}}, "surveyEnabled": {"description": "True if showing survey is enabled", "type": "boolean", "fallbackPref": "browser.shopping.experience2023.survey.enabled"}, "autoOpenEnabled": {"description": "True if auto-open behavior for the sidebar is enabled", "type": "boolean", "setPref": {"branch": "default", "pref": "browser.shopping.experience2023.autoOpen.enabled"}}}}, "shoppingOHTTP": {"description": "Prefs to control the OHTTP URLs used for shopping.", "owner": "gijs@mozilla.com", "hasExposure": false, "variables": {"ohttpRelayURL": {"description": "What OHTTP relay URL to use", "type": "string", "setPref": {"branch": "default", "pref": "toolkit.shopping.ohttpRelayURL"}}, "ohttpConfigURL": {"description": "URL for the OHTTP config to use", "type": "string", "setPref": {"branch": "default", "pref": "toolkit.shopping.ohttpConfigURL"}}}}, "opaqueResponseBlocking": {"description": "Prefs to enable Opaque Response Blocking", "owner": "farre@mozilla.com", "hasExposure": true, "exposureDescription": "Exposure is sent when a response is blocked", "variables": {"enabled": {"description": "Whether ORB is enabled", "type": "boolean", "setPref": {"branch": "user", "pref": "browser.opaqueResponseBlocking"}}, "javascriptValidator": {"description": "Whether JavaScript validation for ORB is enabled", "type": "boolean", "setPref": {"branch": "user", "pref": "browser.opaqueResponseBlocking.javascriptValidator"}}, "filterFetchResponse": {"description": "Whether filtering of internal responses in the parent ORB is enabled", "type": "int", "setPref": {"branch": "user", "pref": "browser.opaqueResponseBlocking.filterFetchResponse"}}, "mediaExceptionsStrategy": {"description": "If we partially or wholly allow audio and video MIME types in conflict with spec.", "type": "int", "setPref": {"branch": "user", "pref": "browser.opaqueResponseBlocking.mediaExceptionsStrategy"}}}}, "updatePrompt": {"description": "Prefs to control content and behavior of update notifications", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "Exposure is sent at most once per browsing session when an update notification prompt is displayed.", "variables": {"showReleaseNotesLink": {"type": "boolean", "description": "If true, the \"Learn More\" link will be shown in the update prompt. If false or omitted, the link will only be shown for supported locales."}, "releaseNotesURL": {"type": "string", "fallbackPref": "app.releaseNotesURL.prompt", "description": "Template for the URL opened when the user clicks the \"Learn More\" link in the update prompt. If an empty string, the link will not be shown."}}}, "powerSaver": {"description": "Prefs to control power saving behaviors", "owner": "florian@mozilla.com", "hasExposure": false, "variables": {"reduceFrameRates": {"type": "int", "setPref": {"branch": "user", "pref": "gfx.display.max-frame-rate"}, "description": "Limit the number of frames displayed per second. If omitted, the refresh rate of the screen will be used."}, "mediaAutoPlay": {"type": "int", "setPref": {"branch": "user", "pref": "media.autoplay.default"}, "description": "Control if media is allowed to auto-play, with and without sound."}, "backgroundTimerMinTime": {"type": "int", "setPref": {"branch": "user", "pref": "dom.min_background_timeout_value"}, "description": "Limit how frequently timers are allowed to run in background tabs."}, "backgroundTimerRegenerationRate": {"type": "int", "setPref": {"branch": "user", "pref": "dom.timeout.background_budget_regeneration_rate"}, "description": "Limit how quickly the background tab timer budget regenerates."}}}, "backgroundUpdate": {"description": "Prefs to control aspects of the background update process.", "owner": "install-update@mozilla.com", "hasExposure": true, "exposureDescription": "The exposure event is sent when scheduling the background task and both the feature is enabled and the service registry key (Mozilla Maintenance Service) is *not* available for this installation. That is the first time the feature can impact Firefox behaviour and the user experience.", "variables": {"enableUpdatesForUnelevatedInstallations": {"description": "Allow the background update process to download and apply updates when the Mozilla Maintenance Service is unavailable but the installation directory can be written.", "type": "boolean", "setPref": {"branch": "user", "pref": "app.update.background.allowUpdatesForUnelevatedInstallations"}}}}, "bookmarks": {"description": "Prefs to control aspects of the bookmarks system.", "owner": "omc@mozilla.com", "hasExposure": false, "variables": {"enableBookmarksToolbar": {"type": "string", "setPref": {"branch": "user", "pref": "browser.toolbars.bookmarks.visibility"}, "description": "If the bookmarks toolbar should never, always, or only show on newtab."}}}, "cookieBannerHandling": {"description": "Automatically handle cookie banners on the user's behalf.", "owner": "pbz@mozilla.com", "hasExposure": false, "variables": {"modeNormalBrowsing": {"type": "int", "setPref": {"branch": "default", "pref": "cookiebanners.service.mode"}, "description": "Controls the cookie banner handling mode in normal browsing. Values: 0 - disabled, 1 - reject all, 2 - reject all with accept all fallback."}, "modePrivateBrowsing": {"type": "int", "setPref": {"branch": "default", "pref": "cookiebanners.service.mode.privateBrowsing"}, "description": "Controls the cookie banner handling mode in private browsing. Values: 0 - disabled, 1 - reject all, 2 - reject all with accept all fallback."}, "enableGlobalRules": {"type": "boolean", "setPref": {"branch": "default", "pref": "cookiebanners.service.enableGlobalRules"}, "description": "Enables use of global CookieBannerRules, which apply to all sites. This enables handling of CMPs across sites without the use of site-specific rules."}, "enableGlobalRulesSubFrames": {"type": "boolean", "setPref": {"branch": "default", "pref": "cookiebanners.service.enableGlobalRules.subFrames"}, "description": "Whether global rules are allowed to run in sub-frames. Running query selectors in every sub-frame may negatively impact performance, but is required for some CMPs."}, "enableDetectOnly": {"type": "boolean", "setPref": {"branch": "default", "pref": "cookiebanners.service.detectOnly"}, "description": "When set to true, cookie banners are detected and detection events are dispatched, but they will not be handled. This pref applies to both normal and private browsing windows."}, "enableFirefoxDesktopUI": {"type": "boolean", "setPref": {"branch": "default", "pref": "cookiebanners.ui.desktop.enabled"}, "description": "Enables the cookie banner desktop UI."}, "enablePromo": {"type": "boolean", "setPref": {"branch": "default", "pref": "browser.promo.cookiebanners.enabled"}, "description": "Enables the cookie banner promo in about:privatebrowsing."}, "enableDesktopFeatureCallout": {"type": "boolean", "setPref": {"branch": "default", "pref": "cookiebanners.ui.desktop.showCallout"}, "description": "Enables the cookie banner feature callout on desktop."}}}, "backgroundThreads": {"description": "Prefs to control MacOS thread priorities for power savings.", "owner": "kwright@mozilla.com", "hasExposure": false, "variables": {"use_low_power": {"description": "Use the MacOS QoS libraries to deprioritize select threads.", "type": "boolean", "setPref": {"branch": "user", "pref": "threads.use_low_power.enabled"}}, "lower_mainthread_priority_in_background": {"description": "When a browsing context is put in the background and isn't actively playing media, deprioritize its main thread.", "type": "boolean", "setPref": {"branch": "user", "pref": "threads.lower_mainthread_priority_in_background.enabled"}}}}, "reportBrokenSite": {"description": "The Report Broken Site feature", "owner": "twisniewski@mozilla.com", "hasExposure": false, "variables": {"enabled": {"type": "boolean", "setPref": {"branch": "user", "pref": "ui.new-webcompat-reporter.enabled"}, "description": "Whether Report Broken Site is enabled"}, "sendMoreInfo": {"type": "boolean", "setPref": {"branch": "user", "pref": "ui.new-webcompat-reporter.send-more-info-link"}, "description": "Whether Report Broken Site shows the send more info link directing users to webcompat.com (defaults to true for prerelease channels)"}, "reasonDropdown": {"type": "int", "setPref": {"branch": "user", "pref": "ui.new-webcompat-reporter.reason-dropdown"}, "description": "0 = do not show the \"reason\" dropdown 1 = show an optional \"reason\" dropdown 2 = show a required \"reason\" dropdown"}}}, "feltPrivacy": {"description": "Prefs for Felt Privacy v1 experiments", "owner": "cmeador@mozilla.com", "hasExposure": true, "exposureDescription": "Exposure when user opens a private browsing window.", "variables": {"feltPrivacy": {"type": "boolean", "setPref": {"branch": "default", "pref": "browser.privatebrowsing.felt-privacy-v1"}, "description": "When true, new styles and copy enabled on about:privatebrowsing. When true, a toggle for showing or hiding quick suggestions appears in about:preferences."}, "resetPBMAction": {"type": "boolean", "setPref": {"branch": "default", "pref": "browser.privatebrowsing.resetPBM.enabled"}, "description": "Enables the reset PBM feature button and confirmation panel."}}}, "phc": {"description": "Prefs to control the Probabalistic Heap Checker (PHC)", "owner": "pbone@mozilla.com", "hasExposure": false, "variables": {"phcEnabled": {"description": "Whether to enable PHC", "type": "boolean", "setPref": {"branch": "user", "pref": "memory.phc.enabled"}}, "phcMinRamMB": {"description": "The minimum amount of RAM required to enable PHC", "type": "int", "setPref": {"branch": "user", "pref": "memory.phc.min_ram_mb"}}, "phcAvgDelayFirst": {"description": "The delay before the first PHC allocation", "type": "int", "setPref": {"branch": "user", "pref": "memory.phc.avg_delay.first"}}, "phcAvgDelayNormal": {"description": "The delay between PHC allocations", "type": "int", "setPref": {"branch": "user", "pref": "memory.phc.avg_delay.normal"}}, "phcAvgDelayPageReuse": {"description": "The delay before reusing a PHC page", "type": "int", "setPref": {"branch": "user", "pref": "memory.phc.avg_delay.page_reuse"}}}}, "mailto": {"description": "Prefs to control aspects of the mailto handler", "owner": "install-update@mozilla.com", "hasExposure": true, "exposureDescription": "The exposure event is sent when a webmail site calls the registerProtocolHandler function and when users use mailto links in Firefox.", "variables": {"dualPrompt": {"type": "boolean", "description": "Can be used to toggle the entire feature on and off.", "fallbackPref": "browser.mailto.dualPrompt"}, "dualPrompt.onLocationChange": {"type": "boolean", "description": "Display a reminder prompt for known webmailers if the prompt was not dismissed before the next visit of that webmailer.", "fallbackPref": "browser.mailto.dualPrompt.onLocationChange"}, "dualPrompt.dismissXClickMinutes": {"type": "int", "description": "This pref controls after how many minutes the mailto prompt can be shown again, which has been dismissed by clicking the 'X' button before.", "fallbackPref": "browser.mailto.dualPrompt.dismissXClickMinutes"}, "dualPrompt.dismissNotNowMinutes": {"type": "int", "description": "This pref controls after how many minutes the mailto prompt can be shown again, which has been dismissed by clicking a 'not now' button on it.", "fallbackPref": "browser.mailto.dualPrompt.dismissNotNowMinutes"}}}, "nimbusIsReady": {"description": "A feature that provides the number of Nimbus is_ready events to send when Nimbus is ready.", "owner": "chumphreys@mozilla.com", "hasExposure": false, "applications": ["firefox-desktop"], "variables": {"eventCount": {"description": "The number of events that should be sent.", "type": "int"}}}, "nimbusTelemetry": {"description": "A feature that enables or disables Nimbus telemetry.", "owner": "chumphreys@mozilla.com", "hasExposure": false, "applications": ["firefox-desktop"], "variables": {"gleanMetricConfiguration": {"description": "A Glean metric configuration JSON blob.", "type": "json"}}}, "httpsFirst": {"description": "Prefs for HTTPS-First, which upgrades all top-level page loads to HTTPS and provides a automatic fallback to HTTP if the site isn't available via HTTPS.", "owner": "mjurgens@mozilla.com, seceng-telemetry@mozilla.com", "hasExposure": false, "variables": {"enabled": {"description": "Enable HTTPS-First", "type": "boolean", "setPref": {"branch": "default", "pref": "dom.security.https_first"}}, "enabledPbm": {"description": "Enable HTTPS-First in private browsing only", "type": "boolean", "setPref": {"branch": "default", "pref": "dom.security.https_first_pbm"}}, "enabledSchemeless": {"description": "Enables schemeless HTTPS-First, which will only apply HTTPS-First to address bar inputs without a scheme. This essentially makes HTTPS the default scheme in the address bar, while providing a fallback to HTTP.", "type": "boolean", "setPref": {"branch": "default", "pref": "dom.security.https_first_schemeless"}}, "backgroundTimerMs": {"description": "After a request gets upgraded to HTTPS, specifies the time after which a second HTTP request is fired to check if the site is available via HTTPS, but timing out via HTTPS. This also applies to HTTPS-Only, not just HTTPS-First.", "type": "int", "setPref": {"branch": "default", "pref": "dom.security.https_only_fire_http_request_background_timer_ms"}}}}, "contentRelevancy": {"description": "A feature for interest-based content relevance ranking and personalization for Firefox.", "owner": "disco-team@mozilla.com", "hasExposure": false, "variables": {"enabled": {"description": "Enable this feature", "type": "boolean", "fallbackPref": "toolkit.contentRelevancy.enabled"}, "maxInputUrls": {"description": "The maximum number of input URLs for interest classification", "type": "int"}, "minInputUrls": {"description": "The minimal number of input URLs for interest classification", "type": "int"}, "timerInterval": {"description": "The interval (in seconds) of the background update timer for the content relevancy manager", "type": "int", "setPref": {"branch": "user", "pref": "toolkit.contentRelevancy.timerInterval"}}, "ingestEnabled": {"description": "Enable the ingestion through the Rust component", "type": "boolean", "fallbackPref": "toolkit.contentRelevancy.ingestEnabled"}}}, "backupService": {"description": "Prefs to control the profile backup service", "owner": "mconley@mozilla.com", "hasExposure": false, "variables": {"enabled": {"type": "boolean", "setPref": {"branch": "default", "pref": "browser.backup.enabled"}, "description": "When true, the profile backup service will be initialized soon after startup."}, "prefsUIEnabled": {"type": "boolean", "setPref": {"branch": "default", "pref": "browser.backup.preferences.ui.enabled"}, "description": "When true, the section in about:preferences to control the backup feature is visible."}, "sqlitePagesPerStep": {"description": "The number of database pages to backup per step when backing up an SQLite database.", "type": "int", "setPref": {"branch": "user", "pref": "browser.backup.sqlite.pages_per_step"}}, "sqliteStepDelayMs": {"description": "The delay between SQLite database backup steps in milliseconds.", "type": "int", "setPref": {"branch": "user", "pref": "browser.backup.sqlite.step_delay_ms"}}, "idleThresholdSeconds": {"description": "The number of seconds of user idle time to wait for before considering to schedule a backup.", "type": "int", "setPref": {"branch": "user", "pref": "browser.backup.scheduled.idle-threshold-seconds"}}, "minTimeBetweenBackupsSeconds": {"description": "The minimum number of seconds since the last known backup that must pass before we might schedule a backup.", "type": "int", "setPref": {"branch": "user", "pref": "browser.backup.scheduled.minimum-time-between-backups-seconds"}}}}, "pqcrypto": {"description": "Prefs that control the use of post-quantum cryptography.", "owner": "jschanck@mozilla.com", "hasExposure": false, "variables": {"tlsEnableXyber": {"type": "boolean", "setPref": {"branch": "default", "pref": "security.tls.enable_kyber"}, "description": "Whether to enable Xyber768 for TLS."}, "h3EnableXyber": {"type": "boolean", "setPref": {"branch": "default", "pref": "network.http.http3.enable_kyber"}, "description": "Whether to enable Xyber768 for H3/QUIC."}, "sendP256": {"type": "boolean", "setPref": {"branch": "default", "pref": "security.tls.client_hello.send_p256_keyshare"}, "description": "Whether to send a P256 share in the client hello in QUIC and TLS."}}}, "dtlsWebRTC": {"description": "Pref that controls the use of DTLS.", "owner": "nkulatova@mozilla.com", "hasExposure": false, "variables": {"tlsVersionDTLS": {"type": "int", "setPref": {"branch": "default", "pref": "media.peerconnection.dtls.version.max"}, "description": "The maximum version of DTLS protocol used in WebRTC (770 = DTLS 1.0, 771 = DTLS 1.2, 772 = DTLS 1.3)."}}}, "certCompression": {"description": "Prefs that control the use of certificate compression decoders.", "owner": "anna.weine@mozilla.com", "hasExposure": false, "variables": {"tlsEnableZlib": {"type": "boolean", "setPref": {"branch": "default", "pref": "security.tls.enable_certificate_compression_zlib"}, "description": "Whether to enable Zlib for TLS certificate compression."}, "tlsEnableBrotli": {"type": "boolean", "setPref": {"branch": "default", "pref": "security.tls.enable_certificate_compression_brotli"}, "description": "Whether to enable Brotli for TLS certificate compression."}, "tlsEnableZstd": {"type": "boolean", "setPref": {"branch": "default", "pref": "security.tls.enable_certificate_compression_zstd"}, "description": "Whether to enable Zstd for TLS certificate compression."}}}, "bounceTrackingProtection": {"description": "Controls the Bounce Tracking Protection feature.", "owner": "pbz@mozilla.com", "hasExposure": false, "variables": {"enabled": {"type": "int", "setPref": {"branch": "default", "pref": "privacy.bounceTrackingProtection.mode"}, "description": "Mode to run the feature in. See nsIBounceTrackingProtection.idl for documentation."}}}, "remoteTabManagement": {"description": "Features that let users manage tabs on other devices that are connected to the same Mozilla account.\n", "owner": "skhamis@mozilla.com", "hasExposure": false, "variables": {"closeTabsEnabled": {"description": "When true, the user can close tabs on other devices connected to the same Mozilla account from the synced tabs menu.", "type": "boolean", "fallbackPref": "identity.fxaccounts.commands.remoteTabManagement.enabled"}}}, "crlite": {"description": "Prefs that control the use of CRLite", "owner": "jschanck@mozilla.com", "hasExposure": false, "variables": {"enabled": {"type": "boolean", "setPref": {"branch": "default", "pref": "security.remote_settings.crlite_filters.enabled"}, "description": "Whether CRLite artifacts will be downloaded."}, "channel": {"type": "string", "setPref": {"branch": "default", "pref": "security.pki.crlite_channel"}, "description": "The channel from which CRLite artifacts will be downloaded."}, "mode": {"type": "int", "setPref": {"branch": "default", "pref": "security.pki.crlite_mode"}, "description": "How CRLite results will be interpreted."}}}, "chatbot": {"description": "AI chatbot feature configuration", "owner": "elee@mozilla.com", "hasExposure": false, "variables": {"prefs": {"type": "json", "description": "The prefs to set under browser.ml.chat.* with object keys as the sub-pref and values objects indicating the desired branch and value defaulting to clearing the user branch value."}}}, "fxms_bmb_button": {"description": "A feature for the Firefox Messaging System Bookmarks Bar button surface", "owner": "omc@mozilla.com", "hasExposure": true, "exposureDescription": "Exposure is sent if the message is about to be shown after trigger and targeting conditions on the message matched.", "schema": {"uri": "chrome://browser/content/asrouter/schemas/MessagingExperiment.schema.json", "path": "browser/components/asrouter/content-src/schemas/MessagingExperiment.schema.json"}, "variables": {}}};PK
!<y�����7chrome/toolkit/res/nimbus/lib/ExperimentManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import {
  PrefFlipsFeature,
  REASON_PREFFLIPS_FAILED,
} from "resource://nimbus/lib/PrefFlipsFeature.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ClientEnvironment: "resource://normandy/lib/ClientEnvironment.sys.mjs",
  ClientID: "resource://gre/modules/ClientID.sys.mjs",
  ExperimentStore: "resource://nimbus/lib/ExperimentStore.sys.mjs",
  FirstStartup: "resource://gre/modules/FirstStartup.sys.mjs",
  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
  NormandyUtils: "resource://normandy/lib/NormandyUtils.sys.mjs",
  PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs",
  Sampling: "resource://gre/modules/components-utils/Sampling.sys.mjs",
  TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
  TelemetryEvents: "resource://normandy/lib/TelemetryEvents.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  const { Logger } = ChromeUtils.importESModule(
    "resource://messaging-system/lib/Logger.sys.mjs"
  );
  return new Logger("ExperimentManager");
});

const TELEMETRY_EVENT_OBJECT = "nimbus_experiment";
const TELEMETRY_EXPERIMENT_ACTIVE_PREFIX = "nimbus-";
const TELEMETRY_DEFAULT_EXPERIMENT_TYPE = "nimbus";

const UPLOAD_ENABLED_PREF = "datareporting.healthreport.uploadEnabled";
const STUDIES_OPT_OUT_PREF = "app.shield.optoutstudies.enabled";

const STUDIES_ENABLED_CHANGED = "nimbus:studies-enabled-changed";

const ENROLLMENT_STATUS = {
  ENROLLED: "Enrolled",
  NOT_ENROLLED: "NotEnrolled",
  DISQUALIFIED: "Disqualified",
  WAS_ENROLLED: "WasEnrolled",
  ERROR: "Error",
};

const ENROLLMENT_STATUS_REASONS = {
  QUALIFIED: "Qualified",
  OPT_IN: "OptIn",
  OPT_OUT: "OptOut",
  NOT_SELECTED: "NotSelected",
  NOT_TARGETED: "NotTargeted",
  ENROLLMENTS_PAUSED: "EnrollmentsPaused",
  FEATURE_CONFLICT: "FeatureConflict",
  ERROR: "Error",
};

function featuresCompat(branch) {
  if (!branch || (!branch.feature && !branch.features)) {
    return [];
  }
  let { features } = branch;
  // In <=v1.5.0 of the Nimbus API, experiments had single feature
  if (!features) {
    features = [branch.feature];
  }

  return features;
}

function getFeatureFromBranch(branch, featureId) {
  return featuresCompat(branch).find(
    featureConfig => featureConfig.featureId === featureId
  );
}

/**
 * A module for processes Experiment recipes, choosing and storing enrollment state,
 * and sending experiment-related Telemetry.
 */
export class _ExperimentManager {
  constructor({ id = "experimentmanager", store } = {}) {
    this.id = id;
    this.store = store || new lazy.ExperimentStore();
    this.sessions = new Map();
    // By default, no extra context.
    this.extraContext = {};
    Services.prefs.addObserver(UPLOAD_ENABLED_PREF, this);
    Services.prefs.addObserver(STUDIES_OPT_OUT_PREF, this);

    // A Map from pref names to pref observers and metadata. See
    // `_updatePrefObservers` for the full structure.
    this._prefs = new Map();
    // A Map from enrollment slugs to a Set of prefs that enrollment is setting
    // or would set (e.g., if the enrollment is a rollout and there wasn't an
    // active experiment already setting it).
    this._prefsBySlug = new Map();

    this._prefFlips = new PrefFlipsFeature({ manager: this });
  }

  get studiesEnabled() {
    return (
      Services.prefs.getBoolPref(UPLOAD_ENABLED_PREF, false) &&
      Services.prefs.getBoolPref(STUDIES_OPT_OUT_PREF, false) &&
      Services.policies.isAllowed("Shield")
    );
  }

  /**
   * Creates a targeting context with following filters:
   *
   *   * `activeExperiments`: an array of slugs of all the active experiments
   *   * `isFirstStartup`: a boolean indicating whether or not the current enrollment
   *      is performed during the first startup
   *
   * @returns {Object} A context object
   * @memberof _ExperimentManager
   */
  createTargetingContext() {
    let context = {
      ...this.extraContext,

      isFirstStartup: lazy.FirstStartup.state === lazy.FirstStartup.IN_PROGRESS,

      get currentDate() {
        return new Date();
      },
    };
    Object.defineProperty(context, "activeExperiments", {
      enumerable: true,
      get: async () => {
        await this.store.ready();
        return this.store.getAllActiveExperiments().map(exp => exp.slug);
      },
    });
    Object.defineProperty(context, "activeRollouts", {
      enumerable: true,
      get: async () => {
        await this.store.ready();
        return this.store.getAllActiveRollouts().map(rollout => rollout.slug);
      },
    });
    Object.defineProperty(context, "previousExperiments", {
      enumerable: true,
      get: async () => {
        await this.store.ready();
        return this.store
          .getAll()
          .filter(enrollment => !enrollment.active && !enrollment.isRollout)
          .map(exp => exp.slug);
      },
    });
    Object.defineProperty(context, "previousRollouts", {
      enumerable: true,
      get: async () => {
        await this.store.ready();
        return this.store
          .getAll()
          .filter(enrollment => !enrollment.active && enrollment.isRollout)
          .map(rollout => rollout.slug);
      },
    });
    Object.defineProperty(context, "enrollments", {
      enumerable: true,
      get: async () => {
        await this.store.ready();
        return this.store.getAll().map(enrollment => enrollment.slug);
      },
    });
    Object.defineProperty(context, "enrollmentsMap", {
      enumerable: true,
      get: async () => {
        await this.store.ready();
        return this.store.getAll().reduce((acc, enrollment) => {
          acc[enrollment.slug] = enrollment.branch.slug;
          return acc;
        }, {});
      },
    });
    return context;
  }

  /**
   * Runs on startup, including before first run.
   *
   * @param {object} extraContext extra targeting context provided by the
   * ambient environment.
   */
  async onStartup(extraContext = {}) {
    await this.store.init();
    this.extraContext = extraContext;

    const restoredExperiments = this.store.getAllActiveExperiments();
    const restoredRollouts = this.store.getAllActiveRollouts();

    for (const experiment of restoredExperiments) {
      this.setExperimentActive(experiment);
      if (this._restoreEnrollmentPrefs(experiment)) {
        this._updatePrefObservers(experiment);
      }
    }
    for (const rollout of restoredRollouts) {
      this.setExperimentActive(rollout);
      if (this._restoreEnrollmentPrefs(rollout)) {
        this._updatePrefObservers(rollout);
      }
    }

    this._prefFlips.init();

    this.observe();

    lazy.NimbusFeatures.nimbusTelemetry.onUpdate(() => {
      const cfg =
        lazy.NimbusFeatures.nimbusTelemetry.getVariable(
          "gleanMetricConfiguration"
        ) ?? {};
      Services.fog.applyServerKnobsConfig(JSON.stringify(cfg));
    });
  }

  /**
   * Runs every time a Recipe is updated or seen for the first time.
   * @param {RecipeArgs} recipe
   * @param {string} source
   */
  async onRecipe(recipe, source) {
    const { slug, isEnrollmentPaused } = recipe;

    if (!source) {
      throw new Error("When calling onRecipe, you must specify a source.");
    }

    if (!this.sessions.has(source)) {
      this.sessions.set(source, new Set());
    }
    this.sessions.get(source).add(slug);

    if (this.store.has(slug)) {
      await this.updateEnrollment(recipe, source);
    } else if (isEnrollmentPaused) {
      lazy.log.debug(`Enrollment is paused for "${slug}"`);
    } else if (!(await this.isInBucketAllocation(recipe.bucketConfig))) {
      lazy.log.debug("Client was not enrolled because of the bucket sampling");
    } else {
      await this.enroll(recipe, source);
    }
  }

  _checkUnseenEnrollments(
    enrollments,
    sourceToCheck,
    recipeMismatches,
    invalidRecipes,
    invalidBranches,
    invalidFeatures,
    missingLocale,
    missingL10nIds
  ) {
    for (const enrollment of enrollments) {
      const { slug, source, branch } = enrollment;
      if (sourceToCheck !== source) {
        continue;
      }
      const statusTelemetry = {
        slug,
        branch: branch.slug,
      };
      if (!this.sessions.get(source)?.has(slug)) {
        lazy.log.debug(`Stopping study for recipe ${slug}`);
        try {
          let reason;
          if (recipeMismatches.includes(slug)) {
            reason = "targeting-mismatch";
            statusTelemetry.status = ENROLLMENT_STATUS.DISQUALIFIED;
            statusTelemetry.reason = ENROLLMENT_STATUS_REASONS.NOT_TARGETED;
          } else if (invalidRecipes.includes(slug)) {
            reason = "invalid-recipe";
          } else if (invalidBranches.has(slug) || invalidFeatures.has(slug)) {
            reason = "invalid-branch";
          } else if (missingLocale.includes(slug)) {
            reason = "l10n-missing-locale";
          } else if (missingL10nIds.has(slug)) {
            reason = "l10n-missing-entry";
          } else {
            reason = "recipe-not-seen";
            statusTelemetry.status = ENROLLMENT_STATUS.WAS_ENROLLED;
            statusTelemetry.branch = branch.slug;
          }
          if (!statusTelemetry.status) {
            statusTelemetry.status = ENROLLMENT_STATUS.DISQUALIFIED;
            statusTelemetry.reason = ENROLLMENT_STATUS_REASONS.ERROR;
            statusTelemetry.error_string = reason;
          }
          this.unenroll(slug, reason);
        } catch (err) {
          console.error(err);
        }
      } else {
        statusTelemetry.status = ENROLLMENT_STATUS.ENROLLED;
        statusTelemetry.reason = ENROLLMENT_STATUS_REASONS.QUALIFIED;
      }
      this.sendEnrollmentStatusTelemetry(statusTelemetry);
    }
  }

  /**
   * Removes stored enrollments that were not seen after syncing with Remote Settings
   * Runs when the all recipes been processed during an update, including at first run.
   * @param {string} sourceToCheck
   * @param {object} options Extra context used in telemetry reporting
   * @param {string[]} options.recipeMismatches
   *         The list of experiments that do not match targeting.
   * @param {string[]} options.invalidRecipes
   *         The list of recipes that do not match
   * @param {Map<string, string[]>} options.invalidBranches
   *         A mapping of experiment slugs to a list of branches that failed
   *         feature validation.
   * @param {Map<string, string[]>} options.invalidFeatures
   *        The mapping of experiment slugs to a list of invalid feature IDs.
   * @param {string[]} options.missingLocale
   *        The list of experiment slugs missing an entry in the localization
   *        table for the current locale.
   * @param {Map<string, string[]>} options.missingL10nIds
   *        The mapping of experiment slugs to the IDs of localization entries
   *        missing from the current locale.
   * @param {string | null} options.locale
   *        The current locale.
   * @param {boolean} options.validationEnabled
   *        Whether or not schema validation was enabled.
   */
  onFinalize(
    sourceToCheck,
    {
      recipeMismatches = [],
      invalidRecipes = [],
      invalidBranches = new Map(),
      invalidFeatures = new Map(),
      missingLocale = [],
      missingL10nIds = new Map(),
      locale = null,
      validationEnabled = true,
    } = {}
  ) {
    if (!sourceToCheck) {
      throw new Error("When calling onFinalize, you must specify a source.");
    }
    const activeExperiments = this.store.getAllActiveExperiments();
    const activeRollouts = this.store.getAllActiveRollouts();
    this._checkUnseenEnrollments(
      activeExperiments,
      sourceToCheck,
      recipeMismatches,
      invalidRecipes,
      invalidBranches,
      invalidFeatures,
      missingLocale,
      missingL10nIds
    );
    this._checkUnseenEnrollments(
      activeRollouts,
      sourceToCheck,
      recipeMismatches,
      invalidRecipes,
      invalidBranches,
      invalidFeatures,
      missingLocale,
      missingL10nIds
    );

    // If schema validation is disabled, then we will never send these
    // validation failed telemetry events
    if (validationEnabled) {
      for (const slug of invalidRecipes) {
        this.sendValidationFailedTelemetry(slug, "invalid-recipe");
      }
      for (const [slug, branches] of invalidBranches.entries()) {
        for (const branch of branches) {
          this.sendValidationFailedTelemetry(slug, "invalid-branch", {
            branch,
          });
        }
      }
      for (const [slug, featureIds] of invalidFeatures.entries()) {
        for (const featureId of featureIds) {
          this.sendValidationFailedTelemetry(slug, "invalid-feature", {
            feature: featureId,
          });
        }
      }
    }

    if (locale) {
      for (const slug of missingLocale.values()) {
        this.sendValidationFailedTelemetry(slug, "l10n-missing-locale", {
          locale,
        });
      }

      for (const [slug, ids] of missingL10nIds.entries()) {
        this.sendValidationFailedTelemetry(slug, "l10n-missing-entry", {
          l10n_ids: ids.join(","),
          locale,
        });
      }
    }

    this.sessions.delete(sourceToCheck);
    this._originalDefaultValues = null;
  }

  /**
   * Determine userId based on bucketConfig.randomizationUnit;
   * either "normandy_id" or "group_id".
   *
   * @param {object} bucketConfig
   *
   */
  async getUserId(bucketConfig) {
    let id;
    if (bucketConfig.randomizationUnit === "normandy_id") {
      id = lazy.ClientEnvironment.userId;
    } else if (bucketConfig.randomizationUnit === "group_id") {
      id = await lazy.ClientID.getProfileGroupID();
    } else {
      // Others not currently supported.
      lazy.log.debug(
        `Invalid randomizationUnit: ${bucketConfig.randomizationUnit}`
      );
    }
    return id;
  }

  /**
   * Determine if this client falls into the bucketing specified in bucketConfig
   *
   * @param {object} bucketConfig
   * @param {string} bucketConfig.randomizationUnit
   *                 The randomization unit to use for bucketing. This must be
   *                 either "normandy_id" or "group_id".
   * @param {number} bucketConfig.start
   *                 The start of the bucketing range (inclusive).
   * @param {number} bucketConfig.count
   *                 The number of buckets in the range.
   * @param {number} bucketConfig.total
   *                 The total number of buckets.
   * @param {string} bucketConfig.namespace
   *                 A namespace used to seed the RNG used in the sampling
   *                 algorithm. Given an otherwise identical bucketConfig with
   *                 different namespaces, the client will fall into different a
   *                 different bucket.
   * @returns {Promise<boolean>}
   *          Whether or not the client falls into the bucketing range.
   */
  async isInBucketAllocation(bucketConfig) {
    if (!bucketConfig) {
      lazy.log.debug("Cannot enroll if recipe bucketConfig is not set.");
      return false;
    }

    const id = await this.getUserId(bucketConfig);
    if (!id) {
      return false;
    }

    return lazy.Sampling.bucketSample(
      [id, bucketConfig.namespace],
      bucketConfig.start,
      bucketConfig.count,
      bucketConfig.total
    );
  }

  /**
   * Start a new experiment by enrolling the users
   *
   * @param {RecipeArgs} recipe
   * @param {string} source
   * @param {object} options
   * @param {boolean} options.reenroll - Allow re-enrollment. Only allowed for rollouts.
   * @returns {Promise<Enrollment>} The experiment object stored in the data store
   * @rejects {Error}
   * @memberof _ExperimentManager
   */
  async enroll(recipe, source, { reenroll = false } = {}) {
    let { slug, branches, bucketConfig } = recipe;

    const enrollment = this.store.get(slug);

    if (
      enrollment &&
      (enrollment.isActive || !enrollment.isRollout || !reenroll)
    ) {
      this.sendFailureTelemetry("enrollFailed", slug, "name-conflict");
      throw new Error(`An experiment with the slug "${slug}" already exists.`);
    }

    let storeLookupByFeature = recipe.isRollout
      ? this.store.getRolloutForFeature.bind(this.store)
      : this.store.hasExperimentForFeature.bind(this.store);
    const userId = await this.getUserId(bucketConfig);
    const branch = await this.chooseBranch(slug, branches, userId);
    const features = featuresCompat(branch);
    for (let feature of features) {
      if (storeLookupByFeature(feature?.featureId)) {
        lazy.log.debug(
          `Skipping enrollment for "${slug}" because there is an existing ${
            recipe.isRollout ? "rollout" : "experiment"
          } for this feature.`
        );
        this.sendFailureTelemetry("enrollFailed", slug, "feature-conflict");

        return null;
      }
    }

    return this._enroll(recipe, branch, source);
  }

  _enroll(
    {
      slug,
      experimentType = TELEMETRY_DEFAULT_EXPERIMENT_TYPE,
      userFacingName,
      userFacingDescription,
      featureIds,
      isRollout,
      localizations,
    },
    branch,
    source,
    options = {}
  ) {
    const { prefs, prefsToSet } = this._getPrefsForBranch(branch, isRollout);
    const prefNames = new Set(prefs.map(entry => entry.name));

    // Unenroll in any conflicting prefFlips enrollments. Even though the
    // rollout does not have an effect, if it also *would* control any of the
    // same prefs, it would cause a conflict when it became active.
    const prefFlipEnrollments = [
      this.store.getRolloutForFeature(PrefFlipsFeature.FEATURE_ID),
      this.store.getExperimentForFeature(PrefFlipsFeature.FEATURE_ID),
    ].filter(enrollment => enrollment);

    for (const enrollment of prefFlipEnrollments) {
      const featureValue = getFeatureFromBranch(
        enrollment.branch,
        PrefFlipsFeature.FEATURE_ID
      ).value;

      for (const prefName of Object.keys(featureValue.prefs)) {
        if (prefNames.has(prefName)) {
          this._unenroll(enrollment, {
            reason: "prefFlips-conflict",
            conflictingSlug: slug,
          });
          break;
        }
      }
    }

    /** @type {Enrollment} */
    const experiment = {
      slug,
      branch,
      active: true,
      experimentType,
      source,
      userFacingName,
      userFacingDescription,
      lastSeen: new Date().toJSON(),
      featureIds,
      prefs,
    };

    if (localizations) {
      experiment.localizations = localizations;
    }

    if (typeof isRollout !== "undefined") {
      experiment.isRollout = isRollout;
    }

    // Tag this as a forced enrollment. This prevents all unenrolling unless
    // manually triggered from about:studies
    if (options.force) {
      experiment.force = true;
    }

    if (isRollout) {
      experiment.experimentType = "rollout";
      this.store.addEnrollment(experiment);
      this.setExperimentActive(experiment);
    } else {
      this.store.addEnrollment(experiment);
      this.setExperimentActive(experiment);
    }
    this.sendEnrollmentTelemetry(experiment);

    this._setEnrollmentPrefs(prefsToSet);
    this._updatePrefObservers(experiment);

    lazy.log.debug(
      `New ${isRollout ? "rollout" : "experiment"} started: ${slug}, ${
        branch.slug
      }`
    );

    return experiment;
  }

  forceEnroll(recipe, branch, source = "force-enrollment") {
    /**
     * If we happen to be enrolled in an experiment for the same feature
     * we need to unenroll from that experiment.
     * If the experiment has the same slug after unenrollment adding it to the
     * store will overwrite the initial experiment.
     */
    const features = featuresCompat(branch);
    for (let feature of features) {
      const isRollout = recipe.isRollout ?? false;
      let enrollment = isRollout
        ? this.store.getRolloutForFeature(feature?.featureId)
        : this.store.getExperimentForFeature(feature?.featureId);
      if (enrollment) {
        lazy.log.debug(
          `Existing ${
            isRollout ? "rollout" : "experiment"
          } found for the same feature ${feature.featureId}, unenrolling.`
        );

        this.unenroll(enrollment.slug, source);
      }
    }

    recipe.userFacingName = `${recipe.userFacingName} - Forced enrollment`;

    const slug = `optin-${recipe.slug}`;
    const enrollment = this._enroll(
      {
        ...recipe,
        slug,
      },
      branch,
      source,
      { force: true }
    );

    Services.obs.notifyObservers(null, "nimbus:enrollments-updated", slug);

    return enrollment;
  }

  /**
   * Update an enrollment that was already set
   *
   * @param {RecipeArgs} recipe
   * @returns {boolean} whether the enrollment is still active
   */
  async updateEnrollment(recipe, source) {
    /** @type Enrollment */
    const enrollment = this.store.get(recipe.slug);

    // Don't update experiments that were already unenrolled.
    if (enrollment.active === false && !recipe.isRollout) {
      lazy.log.debug(`Enrollment ${recipe.slug} has expired, aborting.`);
      return false;
    }

    if (recipe.isRollout) {
      if (!(await this.isInBucketAllocation(recipe.bucketConfig))) {
        lazy.log.debug(
          `No longer meet bucketing for "${recipe.slug}"; unenrolling...`
        );
        this.unenroll(recipe.slug, "bucketing");
        return false;
      } else if (
        !enrollment.active &&
        enrollment.unenrollReason !== "individual-opt-out"
      ) {
        lazy.log.debug(`Re-enrolling in rollout "${recipe.slug}`);
        return !!(await this.enroll(recipe, source, { reenroll: true }));
      }
    }

    // Stay in the same branch, don't re-sample every time.
    const branch = recipe.branches.find(
      branch => branch.slug === enrollment.branch.slug
    );

    if (!branch) {
      // Our branch has been removed. Unenroll.
      this.unenroll(recipe.slug, "branch-removed");
      return false;
    }

    return true;
  }

  /**
   * Stop an enrollment that is currently active
   *
   * @param {string} slug
   *        The slug of the enrollment to stop.
   * @param {string} reason
   *        An optional reason for the unenrollment.
   *
   *        This will be reported in telemetry.
   */
  unenroll(slug, reason = "unknown") {
    const enrollment = this.store.get(slug);
    if (!enrollment) {
      this.sendFailureTelemetry("unenrollFailed", slug, "does-not-exist");
      throw new Error(`Could not find an experiment with the slug "${slug}"`);
    }

    this._unenroll(enrollment, { reason });
  }

  /**
   * Stop an enrollment that is currently active.
   *
   * @param {Enrollment} enrollment
   *        The enrollment to end.
   *
   * @param {object} options
   * @param {string} options.reason
   *        An optional reason for the unenrollment.
   *
   *        This will be reported in telemetry.
   *
   * @param {object?} options.changedPref
   *        If the unenrollment was due to pref change, this will contain the
   *        information about the pref that changed.
   *
   * @param {string} options.changedPref.name
   *        The name of the pref that caused the unenrollment.
   *
   * @param {string} options.changedPref.branch
   *        The branch that was changed ("user" or "default").
   */
  _unenroll(
    enrollment,
    {
      reason = "unknown",
      changedPref = undefined,
      duringRestore = false,
      conflictingSlug = undefined,
      prefName = undefined,
      prefType = undefined,
    } = {}
  ) {
    const { slug } = enrollment;

    if (!enrollment.active) {
      this.sendFailureTelemetry("unenrollFailed", slug, "already-unenrolled");
      throw new Error(
        `Cannot stop experiment "${slug}" because it is already expired`
      );
    }

    lazy.TelemetryEnvironment.setExperimentInactive(slug);
    // We also need to set the experiment inactive in the Glean Experiment API
    Services.fog.setExperimentInactive(slug);
    this.store.updateExperiment(slug, {
      active: false,
      unenrollReason: reason,
    });

    lazy.TelemetryEvents.sendEvent(
      "unenroll",
      TELEMETRY_EVENT_OBJECT,
      slug,
      Object.assign(
        {
          reason,
          branch: enrollment.branch.slug,
        },
        typeof changedPref !== "undefined"
          ? { changedPref: changedPref.name }
          : {},
        typeof conflictingSlug !== "undefined" ? { conflictingSlug } : {},
        reason === REASON_PREFFLIPS_FAILED ? { prefType, prefName } : {}
      )
    );
    // Sent Glean event equivalent
    Glean.nimbusEvents.unenrollment.record(
      Object.assign(
        {
          experiment: slug,
          branch: enrollment.branch.slug,
          reason,
        },
        typeof changedPref !== "undefined"
          ? { changed_pref: changedPref.name }
          : {},
        typeof conflictingSlug !== "undefined"
          ? { conflicting_slug: conflictingSlug }
          : {},
        reason === REASON_PREFFLIPS_FAILED
          ? {
              pref_type: prefType,
              pref_name: prefName,
            }
          : {}
      )
    );

    this._unsetEnrollmentPrefs(enrollment, { changedPref, duringRestore });

    lazy.log.debug(`Recipe unenrolled: ${slug}`);
  }

  /**
   * Unenroll from all active studies if user opts out.
   */
  observe() {
    if (!this.studiesEnabled) {
      for (const { slug } of this.store.getAllActiveExperiments()) {
        this.unenroll(slug, "studies-opt-out");
      }
      for (const { slug } of this.store.getAllActiveRollouts()) {
        this.unenroll(slug, "studies-opt-out");
      }
    }

    Services.obs.notifyObservers(null, STUDIES_ENABLED_CHANGED);
  }

  /**
   * Send Telemetry for undesired event
   *
   * @param {string} eventName
   * @param {string} slug
   * @param {string} reason
   */
  sendFailureTelemetry(eventName, slug, reason) {
    lazy.TelemetryEvents.sendEvent(eventName, TELEMETRY_EVENT_OBJECT, slug, {
      reason,
    });
    if (eventName == "enrollFailed") {
      Glean.nimbusEvents.enrollFailed.record({
        experiment: slug,
        reason,
      });
    } else if (eventName == "unenrollFailed") {
      Glean.nimbusEvents.unenrollFailed.record({
        experiment: slug,
        reason,
      });
    }
  }

  sendValidationFailedTelemetry(slug, reason, extra) {
    lazy.TelemetryEvents.sendEvent(
      "validationFailed",
      TELEMETRY_EVENT_OBJECT,
      slug,
      {
        reason,
        ...extra,
      }
    );
    Glean.nimbusEvents.validationFailed.record({
      experiment: slug,
      reason,
      ...extra,
    });
  }

  /**
   *
   * @param {Enrollment} experiment
   */
  sendEnrollmentTelemetry({ slug, branch, experimentType }) {
    lazy.TelemetryEvents.sendEvent("enroll", TELEMETRY_EVENT_OBJECT, slug, {
      experimentType,
      branch: branch.slug,
    });
    Glean.nimbusEvents.enrollment.record({
      experiment: slug,
      branch: branch.slug,
      experiment_type: experimentType,
    });
  }

  /**
   *
   * @param {object} enrollmentStatus
   * @param {string} enrollmentStatus.slug
   * @param {string} enrollmentStatus.status
   * @param {string?} enrollmentStatus.reason
   * @param {string?} enrollmentStatus.branch
   * @param {string?} enrollmentStatus.error_string
   * @param {string?} enrollmentStatus.conflict_slug
   */
  sendEnrollmentStatusTelemetry({
    slug,
    status,
    reason,
    branch,
    error_string,
    conflict_slug,
  }) {
    Glean.nimbusEvents.enrollmentStatus.record({
      slug,
      status,
      reason,
      branch,
      error_string,
      conflict_slug,
    });
  }

  /**
   * Sets Telemetry when activating an experiment.
   *
   * @param {Enrollment} experiment
   */
  setExperimentActive(experiment) {
    lazy.TelemetryEnvironment.setExperimentActive(
      experiment.slug,
      experiment.branch.slug,
      {
        type: `${TELEMETRY_EXPERIMENT_ACTIVE_PREFIX}${experiment.experimentType}`,
      }
    );
    // Report the experiment to the Glean Experiment API
    Services.fog.setExperimentActive(experiment.slug, experiment.branch.slug, {
      type: `${TELEMETRY_EXPERIMENT_ACTIVE_PREFIX}${experiment.experimentType}`,
    });
  }

  /**
   * Generate Normandy UserId respective to a branch
   * for a given experiment.
   *
   * @param {string} slug
   * @param {Array<{slug: string; ratio: number}>} branches
   * @param {string} namespace
   * @param {number} start
   * @param {number} count
   * @param {number} total
   * @returns {Promise<{[branchName: string]: string}>} An object where
   * the keys are branch names and the values are user IDs that will enroll
   * a user for that particular branch. Also includes a `notInExperiment` value
   * that will not enroll the user in the experiment if not 100% enrollment.
   */
  async generateTestIds(recipe) {
    // Older recipe structure had bucket config values at the top level while
    // newer recipes group them into a bucketConfig object
    const { slug, branches, namespace, start, count, total } = {
      ...recipe,
      ...recipe.bucketConfig,
    };
    const branchValues = {};
    const includeNot = count < total;

    if (!slug || !namespace) {
      throw new Error(`slug, namespace not in expected format`);
    }

    if (!(start < total && count <= total)) {
      throw new Error("Must include start, count, and total as integers");
    }

    if (
      !Array.isArray(branches) ||
      branches.filter(branch => branch.slug && branch.ratio).length !==
        branches.length
    ) {
      throw new Error("branches parameter not in expected format");
    }

    while (Object.keys(branchValues).length < branches.length + includeNot) {
      const id = lazy.NormandyUtils.generateUuid();
      const enrolls = await lazy.Sampling.bucketSample(
        [id, namespace],
        start,
        count,
        total
      );
      // Does this id enroll the user in the experiment
      if (enrolls) {
        // Choose a random branch
        const { slug: pickedBranch } = await this.chooseBranch(
          slug,
          branches,
          id
        );

        if (!Object.keys(branchValues).includes(pickedBranch)) {
          branchValues[pickedBranch] = id;
          lazy.log.debug(`Found a value for "${pickedBranch}"`);
        }
      } else if (!branchValues.notInExperiment) {
        branchValues.notInExperiment = id;
      }
    }
    return branchValues;
  }

  /**
   * Choose a branch randomly.
   *
   * @param {string} slug
   * @param {Branch[]} branches
   * @param {string} userId
   * @returns {Promise<Branch>}
   * @memberof _ExperimentManager
   */
  async chooseBranch(slug, branches, userId = lazy.ClientEnvironment.userId) {
    const ratios = branches.map(({ ratio = 1 }) => ratio);

    // It's important that the input be:
    // - Unique per-user (no one is bucketed alike)
    // - Unique per-experiment (bucketing differs across multiple experiments)
    // - Differs from the input used for sampling the recipe (otherwise only
    //   branches that contain the same buckets as the recipe sampling will
    //   receive users)
    const input = `${this.id}-${userId}-${slug}-branch`;

    const index = await lazy.Sampling.ratioSample(input, ratios);
    return branches[index];
  }

  /**
   * Generate the list of prefs a recipe will set.
   *
   * @params {object} branch The recipe branch that will be enrolled.
   * @params {boolean} isRollout Whether or not this recipe is a rollout.
   *
   * @returns {object} An object with the following keys:
   *
   *                   `prefs`:
   *                        The full list of prefs that this recipe would set,
   *                        if there are no conflicts. This will include prefs
   *                        that, for example, will not be set because this
   *                        enrollment is a rollout and there is an active
   *                        experiment that set the same pref.
   *
   *                   `prefsToSet`:
   *                        Prefs that should be set once enrollment is
   *                        complete.
   */
  _getPrefsForBranch(branch, isRollout = false) {
    const prefs = [];
    const prefsToSet = [];

    const getConflictingEnrollment = this._makeEnrollmentCache(isRollout);

    for (const { featureId, value: featureValue } of featuresCompat(branch)) {
      const feature = lazy.NimbusFeatures[featureId];

      if (!feature) {
        continue;
      }

      // It is possible to enroll in both an experiment and a rollout, so we
      // need to check if we have another enrollment for the same feature.
      const conflictingEnrollment = getConflictingEnrollment(featureId);

      for (let [variable, value] of Object.entries(featureValue)) {
        const setPref = feature.getSetPref(variable);

        if (setPref) {
          const { pref: prefName, branch: prefBranch } = setPref;

          let originalValue;
          const conflictingPref = conflictingEnrollment?.prefs?.find(
            p => p.name === prefName
          );

          if (conflictingPref) {
            // If there is another enrollment that has already set the pref we
            // care about, we use its stored originalValue.
            originalValue = conflictingPref.originalValue;
          } else if (
            prefBranch === "user" &&
            !Services.prefs.prefHasUserValue(prefName)
          ) {
            // If there is a default value set, then attempting to read the user
            // branch would result in returning the default branch value.
            originalValue = null;
          } else {
            // If there is an active prefFlips experiment for this pref on this
            // branch, we must use its originalValue.
            const prefFlip = this._prefFlips._prefs.get(prefName);
            if (prefFlip?.branch === prefBranch) {
              originalValue = prefFlip.originalValue;
            } else {
              originalValue = lazy.PrefUtils.getPref(prefName, {
                branch: prefBranch,
              });
            }
          }

          prefs.push({
            name: prefName,
            branch: prefBranch,
            featureId,
            variable,
            originalValue,
          });

          // An experiment takes precedence if there is already a pref set.
          if (!isRollout || !conflictingPref) {
            if (
              lazy.NimbusFeatures[featureId].manifest.variables[variable]
                .type === "json"
            ) {
              value = JSON.stringify(value);
            }

            prefsToSet.push({
              name: prefName,
              value,
              prefBranch,
            });
          }
        }
      }
    }

    return { prefs, prefsToSet };
  }

  /**
   * Set a list of prefs from enrolling in an experiment or rollout.
   *
   * The ExperimentManager's pref observers will be disabled while setting each
   * pref so as not to accidentally unenroll an existing rollout that an
   * experiment would override.
   *
   * @param {object[]} prefsToSet
   *                   A list of objects containing the prefs to set.
   *
   *                   Each object has the following properties:
   *
   *                   * `name`: The name of the pref.
   *                   * `value`: The value of the pref.
   *                   * `prefBranch`: The branch to set the pref on (either "user" or "default").
   */
  _setEnrollmentPrefs(prefsToSet) {
    for (const { name, value, prefBranch } of prefsToSet) {
      const entry = this._prefs.get(name);

      // If another enrollment exists that has set this pref, temporarily
      // disable the pref observer so as not to cause unenrollment.
      if (entry) {
        entry.enrollmentChanging = true;
      }

      lazy.PrefUtils.setPref(name, value, { branch: prefBranch });

      if (entry) {
        entry.enrollmentChanging = false;
      }
    }
  }

  /**
   * Unset prefs set during this enrollment.
   *
   * If this enrollment is an experiment and there is an existing rollout that
   * would set a pref that was covered by this enrollment, the pref will be
   * updated to that rollout's value.
   *
   * Otherwise, it will be set to the original value from before the enrollment
   * began.
   *
   * @param {Enrollment} enrollment
   *        The enrollment that has ended.
   *
   * @param {object} options
   *
   * @param {object?} options.changedPref
   *        If provided, a changed pref that caused the unenrollment that
   *        triggered unsetting these prefs. This is provided as to not
   *        overwrite a changed pref with an original value.
   *
   * @param {string} options.changedPref.name
   *        The name of the changed pref.
   *
   * @param {string} options.changedPref.branch
   *        The branch that was changed ("user" or "default").
   *
   * @param {boolean} options.duringRestore
   *        The unenrollment was caused during restore.
   */
  _unsetEnrollmentPrefs(enrollment, { changedPref, duringRestore } = {}) {
    if (!enrollment.prefs?.length) {
      return;
    }

    const getConflictingEnrollment = this._makeEnrollmentCache(
      enrollment.isRollout
    );

    for (const pref of enrollment.prefs) {
      this._removePrefObserver(pref.name, enrollment.slug);

      if (
        changedPref?.name == pref.name &&
        changedPref.branch === pref.branch
      ) {
        // Resetting the original value would overwite the pref the user just
        // set. Skip it.
        continue;
      }

      let newValue = pref.originalValue;

      // If we are unenrolling from an experiment during a restore, we must
      // ignore any potential conflicting rollout in the store, because its
      // hasn't gone through `_restoreEnrollmentPrefs`, which might also cause
      // it to unenroll.
      //
      // Both enrollments will have the same `originalValue` stored, so it will
      // always be restored.
      if (!duringRestore || enrollment.isRollout) {
        const conflictingEnrollment = getConflictingEnrollment(pref.featureId);
        const conflictingPref = conflictingEnrollment?.prefs?.find(
          p => p.name === pref.name
        );

        if (conflictingPref) {
          if (enrollment.isRollout) {
            // If we are unenrolling from a rollout, we have an experiment that
            // has set the pref. Since experiments take priority, we do not unset
            // it.
            continue;
          } else {
            // If we are an unenrolling from an experiment, we have a rollout that would
            // set the same pref, so we update the pref to that value instead of
            // the original value.
            newValue = getFeatureFromBranch(
              conflictingEnrollment.branch,
              pref.featureId
            ).value[pref.variable];
          }
        }
      }

      // If another enrollment exists that has set this pref, temporarily
      // disable the pref observer so as not to cause unenrollment when we
      // update the pref to its value.
      const entry = this._prefs.get(pref.name);
      if (entry) {
        entry.enrollmentChanging = true;
      }

      lazy.PrefUtils.setPref(pref.name, newValue, {
        branch: pref.branch,
      });

      if (entry) {
        entry.enrollmentChanging = false;
      }
    }
  }

  /**
   * Restore the prefs set by an enrollment.
   *
   * @param {object} enrollment The enrollment.
   * @param {object} enrollment.branch The branch that was enrolled.
   * @param {object[]} enrollment.prefs The prefs that are set by the enrollment.
   * @param {object[]} enrollment.isRollout The prefs that are set by the enrollment.
   *
   * @returns {boolean} Whether the restore was successful. If false, the
   *                    enrollment has ended.
   */
  _restoreEnrollmentPrefs(enrollment) {
    const { branch, prefs = [], isRollout } = enrollment;

    if (!prefs?.length) {
      return false;
    }

    const featuresById = Object.assign(
      ...featuresCompat(branch).map(f => ({ [f.featureId]: f }))
    );

    for (const { name, featureId, variable } of prefs) {
      // If the feature no longer exists, unenroll.
      if (!Object.hasOwn(lazy.NimbusFeatures, featureId)) {
        this._unenroll(enrollment, {
          reason: "invalid-feature",
          duringRestore: true,
        });
        return false;
      }

      const variables = lazy.NimbusFeatures[featureId].manifest.variables;

      // If the feature is missing a variable that set a pref, unenroll.
      if (!Object.hasOwn(variables, variable)) {
        this._unenroll(enrollment, {
          reason: "pref-variable-missing",
          duringRestore: true,
        });
        return false;
      }

      const variableDef = variables[variable];

      // If the variable is no longer a pref-setting variable, unenroll.
      if (!Object.hasOwn(variableDef, "setPref")) {
        this._unenroll(enrollment, {
          reason: "pref-variable-no-longer",
          duringRestore: true,
        });
        return false;
      }

      // If the variable is setting a different preference, unenroll.
      const prefName =
        typeof variableDef.setPref === "object"
          ? variableDef.setPref.pref
          : variableDef.setPref;

      if (prefName !== name) {
        this._unenroll(enrollment, {
          reason: "pref-variable-changed",
          duringRestore: true,
        });
        return false;
      }
    }

    for (const { name, branch: prefBranch, featureId, variable } of prefs) {
      // User prefs are already persisted.
      if (prefBranch === "user") {
        continue;
      }

      // If we are a rollout, we need to check for an existing experiment that
      // has set the same pref. If so, we do not need to set the pref because
      // experiments take priority.
      if (isRollout) {
        const conflictingEnrollment =
          this.store.getExperimentForFeature(featureId);
        const conflictingPref = conflictingEnrollment?.prefs?.find(
          p => p.name === name
        );

        if (conflictingPref) {
          continue;
        }
      }

      let value = featuresById[featureId].value[variable];
      if (
        lazy.NimbusFeatures[featureId].manifest.variables[variable].type ===
        "json"
      ) {
        value = JSON.stringify(value);
      }

      if (prefBranch !== "user") {
        lazy.PrefUtils.setPref(name, value, { branch: prefBranch });
      }
    }

    return true;
  }

  /**
   * Make a cache to look up enrollments of the oppposite kind by feature ID.
   *
   * @param {boolean} isRollout Whether or not the current enrollment is a
   *                            rollout. If true, the cache will return
   *                            experiments. If false, the cache will return
   *                            rollouts.
   *
   * @returns {function} The cache, as a callable function.
   */
  _makeEnrollmentCache(isRollout) {
    const getOtherEnrollment = (
      isRollout
        ? this.store.getExperimentForFeature
        : this.store.getRolloutForFeature
    ).bind(this.store);

    const conflictingEnrollments = {};
    return featureId => {
      if (!Object.hasOwn(conflictingEnrollments, featureId)) {
        conflictingEnrollments[featureId] = getOtherEnrollment(featureId);
      }

      return conflictingEnrollments[featureId];
    };
  }

  /**
   * Update the set of observers with prefs set by the given enrollment.
   *
   * @param {Enrollment} enrollment
   *        The enrollment that is setting prefs.
   */
  _updatePrefObservers({ slug, prefs }) {
    if (!prefs?.length) {
      return;
    }

    for (const pref of prefs) {
      const { name } = pref;

      if (!this._prefs.has(name)) {
        const observer = (aSubject, aTopic, aData) => {
          // This observer will be called for changes to `name` as well as any
          // other pref that begins with `name.`, so we have to filter to
          // exactly the pref we care about.
          if (aData === name) {
            this._onExperimentPrefChanged(pref);
          }
        };
        const entry = {
          slugs: new Set([slug]),
          enrollmentChanging: false,
          observer,
        };

        Services.prefs.addObserver(name, observer);

        this._prefs.set(name, entry);
      } else {
        this._prefs.get(name).slugs.add(slug);
      }

      if (!this._prefsBySlug.has(slug)) {
        this._prefsBySlug.set(slug, new Set([name]));
      } else {
        this._prefsBySlug.get(slug).add(name);
      }
    }
  }

  /**
   * Remove an entry for the pref observer for the given pref and slug.
   *
   * If there are no more enrollments listening to a pref, the observer will be removed.
   *
   * This is called when an enrollment is ending.
   *
   * @param {string} name The name of the pref.
   * @param {string} slug The slug of the enrollment that is being unenrolled.
   */
  _removePrefObserver(name, slug) {
    // Update the pref observer that the current enrollment is no longer
    // involved in the pref.
    //
    // If no enrollments have a variable setting the pref, then we can remove
    // the observers.
    const entry = this._prefs.get(name);

    // If this is happening due to a pref change, the observers will already be removed.
    if (entry) {
      entry.slugs.delete(slug);
      if (entry.slugs.size == 0) {
        Services.prefs.removeObserver(name, entry.observer);
        this._prefs.delete(name);
      }
    }

    const bySlug = this._prefsBySlug.get(slug);
    if (bySlug) {
      bySlug.delete(name);
      if (bySlug.size == 0) {
        this._prefsBySlug.delete(slug);
      }
    }
  }

  /**
   * Handle a change to a pref set by enrollments by ending those enrollments.
   *
   * @param {object} pref
   *        Information about the pref that was changed.
   *
   * @param {string} pref.name
   *        The name of the pref that was changed.
   *
   * @param {string} pref.branch
   *        The branch enrollments set the pref on.
   *
   * @param {string} pref.featureId
   *        The feature ID of the feature containing the variable that set the
   *        pref.
   *
   * @param {string} pref.variable
   *        The variable in the given feature whose value determined the pref's
   *        value.
   */
  _onExperimentPrefChanged(pref) {
    const entry = this._prefs.get(pref.name);
    // If this was triggered while we are enrolling or unenrolling from an
    // experiment, then we don't want to unenroll from the rollout because the
    // experiment's value is taking precendence.
    //
    // Otherwise, all enrollments that set the variable corresponding to this
    // pref must be unenrolled.
    if (entry.enrollmentChanging) {
      return;
    }

    // Copy the `Set` into an `Array` because we modify the set later in
    // `_removePrefObserver` and we need to iterate over it multiple times.
    const slugs = Array.from(entry.slugs);

    // Remove all pref observers set by enrollments. We are potentially about
    // to set these prefs during unenrollment, so we don't want to trigger
    // them and cause nested unenrollments.
    for (const slug of slugs) {
      const toRemove = Array.from(this._prefsBySlug.get(slug) ?? []);
      for (const name of toRemove) {
        this._removePrefObserver(name, slug);
      }
    }

    // Unenroll from the rollout first to save calls to setPref.
    const enrollments = Array.from(slugs).map(slug => this.store.get(slug));

    // There is a maximum of two enrollments (one experiment and one rollout).
    if (enrollments.length == 2) {
      // Order enrollments so that we unenroll from the rollout first.
      if (!enrollments[0].isRollout) {
        enrollments.reverse();
      }
    }

    const feature = getFeatureFromBranch(
      enrollments.at(-1).branch,
      pref.featureId
    );

    const changedPref = {
      name: pref.name,
      branch: PrefFlipsFeature.determinePrefChangeBranch(
        pref.name,
        pref.branch,
        feature.value[pref.variable]
      ),
    };

    for (const enrollment of enrollments) {
      this._unenroll(enrollment, { reason: "changed-pref", changedPref });
    }
  }
}

export const ExperimentManager = new _ExperimentManager();
PK
!<�z$�::5chrome/toolkit/res/nimbus/lib/ExperimentStore.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { SharedDataMap } from "resource://nimbus/lib/SharedDataMap.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  FeatureManifest: "resource://nimbus/FeatureManifest.sys.mjs",
  PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs",
});

const IS_MAIN_PROCESS =
  Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT;

// This branch is used to store experiment data
const SYNC_DATA_PREF_BRANCH = "nimbus.syncdatastore.";
// This branch is used to store remote rollouts
const SYNC_DEFAULTS_PREF_BRANCH = "nimbus.syncdefaultsstore.";
let tryJSONParse = data => {
  try {
    return JSON.parse(data);
  } catch (e) {}

  return null;
};
ChromeUtils.defineLazyGetter(lazy, "syncDataStore", () => {
  let experimentsPrefBranch = Services.prefs.getBranch(SYNC_DATA_PREF_BRANCH);
  let defaultsPrefBranch = Services.prefs.getBranch(SYNC_DEFAULTS_PREF_BRANCH);
  return {
    _tryParsePrefValue(branch, pref) {
      try {
        return tryJSONParse(branch.getStringPref(pref, ""));
      } catch (e) {
        /* This is expected if we don't have anything stored */
      }

      return null;
    },
    _trySetPrefValue(branch, pref, value) {
      try {
        branch.setStringPref(pref, JSON.stringify(value));
      } catch (e) {
        console.error(e);
      }
    },
    _trySetTypedPrefValue(pref, value) {
      let variableType = typeof value;
      switch (variableType) {
        case "boolean":
          Services.prefs.setBoolPref(pref, value);
          break;
        case "number":
          Services.prefs.setIntPref(pref, value);
          break;
        case "string":
          Services.prefs.setStringPref(pref, value);
          break;
        case "object":
          Services.prefs.setStringPref(pref, JSON.stringify(value));
          break;
      }
    },
    _clearBranchChildValues(prefBranch) {
      const variablesBranch = Services.prefs.getBranch(prefBranch);
      const prefChildList = variablesBranch.getChildList("");
      for (let variable of prefChildList) {
        variablesBranch.clearUserPref(variable);
      }
    },
    /**
     * Given a branch pref returns all child prefs and values
     * { childPref: value }
     * where value is parsed to the appropriate type
     *
     * @returns {Object[]}
     */
    _getBranchChildValues(prefBranch, featureId) {
      const branch = Services.prefs.getBranch(prefBranch);
      const prefChildList = branch.getChildList("");
      let values = {};
      if (!prefChildList.length) {
        return null;
      }
      for (const childPref of prefChildList) {
        let prefName = `${prefBranch}${childPref}`;
        let value = lazy.PrefUtils.getPref(prefName);
        // Try to parse string values that could be stringified objects
        if (
          lazy.FeatureManifest[featureId]?.variables[childPref]?.type === "json"
        ) {
          let parsedValue = tryJSONParse(value);
          if (parsedValue) {
            value = parsedValue;
          }
        }
        values[childPref] = value;
      }

      return values;
    },
    get(featureId) {
      let metadata = this._tryParsePrefValue(experimentsPrefBranch, featureId);
      if (!metadata) {
        return null;
      }
      let prefBranch = `${SYNC_DATA_PREF_BRANCH}${featureId}.`;
      metadata.branch.feature.value = this._getBranchChildValues(
        prefBranch,
        featureId
      );

      return metadata;
    },
    getDefault(featureId) {
      let metadata = this._tryParsePrefValue(defaultsPrefBranch, featureId);
      if (!metadata) {
        return null;
      }
      let prefBranch = `${SYNC_DEFAULTS_PREF_BRANCH}${featureId}.`;
      metadata.branch.feature.value = this._getBranchChildValues(
        prefBranch,
        featureId
      );

      return metadata;
    },
    set(featureId, value) {
      /* If the enrollment branch has variables we store those separately
       * in pref branches of appropriate type:
       * { featureId: "foo", value: { enabled: true } }
       * gets stored as `${SYNC_DATA_PREF_BRANCH}foo.enabled=true`
       */
      if (value.branch?.feature?.value) {
        for (let variable of Object.keys(value.branch.feature.value)) {
          let prefName = `${SYNC_DATA_PREF_BRANCH}${featureId}.${variable}`;
          this._trySetTypedPrefValue(
            prefName,
            value.branch.feature.value[variable]
          );
        }
        this._trySetPrefValue(experimentsPrefBranch, featureId, {
          ...value,
          branch: {
            ...value.branch,
            feature: {
              ...value.branch.feature,
              value: null,
            },
          },
        });
      } else {
        this._trySetPrefValue(experimentsPrefBranch, featureId, value);
      }
    },
    setDefault(featureId, enrollment) {
      /* We store configuration variables separately in pref branches of
       * appropriate type:
       * (feature: "foo") { variables: { enabled: true } }
       * gets stored as `${SYNC_DEFAULTS_PREF_BRANCH}foo.enabled=true`
       */
      let { feature } = enrollment.branch;
      for (let variable of Object.keys(feature.value)) {
        let prefName = `${SYNC_DEFAULTS_PREF_BRANCH}${featureId}.${variable}`;
        this._trySetTypedPrefValue(prefName, feature.value[variable]);
      }
      this._trySetPrefValue(defaultsPrefBranch, featureId, {
        ...enrollment,
        branch: {
          ...enrollment.branch,
          feature: {
            ...enrollment.branch.feature,
            value: null,
          },
        },
      });
    },
    getAllDefaultBranches() {
      return defaultsPrefBranch.getChildList("").filter(
        // Filter out remote defaults variable prefs
        pref => !pref.includes(".")
      );
    },
    delete(featureId) {
      const prefBranch = `${SYNC_DATA_PREF_BRANCH}${featureId}.`;
      this._clearBranchChildValues(prefBranch);
      try {
        experimentsPrefBranch.clearUserPref(featureId);
      } catch (e) {}
    },
    deleteDefault(featureId) {
      let prefBranch = `${SYNC_DEFAULTS_PREF_BRANCH}${featureId}.`;
      this._clearBranchChildValues(prefBranch);
      try {
        defaultsPrefBranch.clearUserPref(featureId);
      } catch (e) {}
    },
  };
});

const DEFAULT_STORE_ID = "ExperimentStoreData";

/**
 * Returns all feature ids associated with the branch provided.
 * Fallback for when `featureIds` was not persisted to disk. Can be removed
 * after bug 1725240 has reached release.
 *
 * @param {Branch} branch
 * @returns {string[]}
 */
function getAllBranchFeatureIds(branch) {
  return featuresCompat(branch).map(f => f.featureId);
}

function featuresCompat(branch) {
  if (!branch || (!branch.feature && !branch.features)) {
    return [];
  }
  let { features } = branch;
  // In <=v1.5.0 of the Nimbus API, experiments had single feature
  if (!features) {
    features = [branch.feature];
  }

  return features;
}

export class ExperimentStore extends SharedDataMap {
  static SYNC_DATA_PREF_BRANCH = SYNC_DATA_PREF_BRANCH;
  static SYNC_DEFAULTS_PREF_BRANCH = SYNC_DEFAULTS_PREF_BRANCH;

  constructor(sharedDataKey, options = { isParent: IS_MAIN_PROCESS }) {
    super(sharedDataKey || DEFAULT_STORE_ID, options);
  }

  async init() {
    await super.init();

    this.getAllActiveExperiments().forEach(({ branch, featureIds }) => {
      (featureIds || getAllBranchFeatureIds(branch)).forEach(featureId =>
        this._emitFeatureUpdate(featureId, "feature-experiment-loaded")
      );
    });
    this.getAllActiveRollouts().forEach(({ featureIds }) => {
      featureIds.forEach(featureId =>
        this._emitFeatureUpdate(featureId, "feature-rollout-loaded")
      );
    });

    Services.tm.idleDispatchToMainThread(() => this._cleanupOldRecipes());
  }

  /**
   * Given a feature identifier, find an active experiment that matches that feature identifier.
   * This assumes, for now, that there is only one active experiment per feature per browser.
   * Does not activate the experiment (send an exposure event)
   *
   * @param {string} featureId
   * @returns {Enrollment|undefined} An active experiment if it exists
   * @memberof ExperimentStore
   */
  getExperimentForFeature(featureId) {
    return (
      this.getAllActiveExperiments().find(
        experiment =>
          experiment.featureIds?.includes(featureId) ||
          // Supports <v1.3.0, which was when .featureIds was added
          getAllBranchFeatureIds(experiment.branch).includes(featureId)
        // Default to the pref store if data is not yet ready
      ) || lazy.syncDataStore.get(featureId)
    );
  }

  /**
   * Check if an active experiment already exists for a feature.
   * Does not activate the experiment (send an exposure event)
   *
   * @param {string} featureId
   * @returns {boolean} Does an active experiment exist for that feature?
   * @memberof ExperimentStore
   */
  hasExperimentForFeature(featureId) {
    if (!featureId) {
      return false;
    }
    return !!this.getExperimentForFeature(featureId);
  }

  /**
   * @returns {Enrollment[]}
   */
  getAll() {
    let data = [];
    try {
      data = Object.values(this._data || {});
    } catch (e) {
      console.error(e);
    }

    return data;
  }

  /**
   * Returns all active experiments
   * @returns {Enrollment[]}
   */
  getAllActiveExperiments() {
    return this.getAll().filter(
      enrollment => enrollment.active && !enrollment.isRollout
    );
  }

  /**
   * Returns all active rollouts
   * @returns {Enrollment[]}
   */
  getAllActiveRollouts() {
    return this.getAll().filter(
      enrollment => enrollment.active && enrollment.isRollout
    );
  }

  /**
   * Query the store for the remote configuration of a feature
   * @param {string} featureId The feature we want to query for
   * @returns {{Rollout}|undefined} Remote defaults if available
   */
  getRolloutForFeature(featureId) {
    return (
      this.getAllActiveRollouts().find(r => r.featureIds.includes(featureId)) ||
      lazy.syncDataStore.getDefault(featureId)
    );
  }

  /**
   * Check if an active rollout already exists for a feature.
   * Does not active the experiment (send an exposure event).
   *
   * @param {string} featureId
   * @returns {boolean} Does an active rollout exist for that feature?
   */
  hasRolloutForFeature(featureId) {
    if (!featureId) {
      return false;
    }
    return !!this.getRolloutForFeature(featureId);
  }

  /**
   * Remove inactive enrollments older than 6 months
   */
  _cleanupOldRecipes() {
    // Roughly six months
    const threshold = 15552000000;
    const nowTimestamp = new Date().getTime();
    const recipesToRemove = this.getAll().filter(
      experiment =>
        !experiment.active &&
        // Flip the comparison here to catch scenarios in which lastSeen is
        // invalid or undefined. The result with be a comparison with NaN
        // which is always false
        !(nowTimestamp - new Date(experiment.lastSeen).getTime() < threshold)
    );
    this._removeEntriesByKeys(recipesToRemove.map(r => r.slug));
  }

  _emitUpdates(enrollment) {
    this.emit(`update:${enrollment.slug}`, enrollment);
    const featureIds =
      enrollment.featureIds || getAllBranchFeatureIds(enrollment.branch);
    const reason = enrollment.isRollout
      ? "rollout-updated"
      : "experiment-updated";

    for (const featureId of featureIds) {
      this._emitFeatureUpdate(featureId, reason);
    }
  }

  _emitFeatureUpdate(featureId, reason) {
    this.emit(`featureUpdate:${featureId}`, reason);
  }

  _onFeatureUpdate(featureId, callback) {
    if (this._isReady) {
      const hasExperiment = this.hasExperimentForFeature(featureId);
      if (hasExperiment || this.hasRolloutForFeature(featureId)) {
        callback(
          `featureUpdate:${featureId}`,
          hasExperiment ? "experiment-updated" : "rollout-updated"
        );
      }
    }

    this.on(`featureUpdate:${featureId}`, callback);
  }

  _offFeatureUpdate(featureId, callback) {
    this.off(`featureUpdate:${featureId}`, callback);
  }

  /**
   * Persists early startup experiments or rollouts
   * @param {Enrollment} enrollment Experiment or rollout
   */
  _updateSyncStore(enrollment) {
    let features = featuresCompat(enrollment.branch);
    for (let feature of features) {
      if (
        lazy.FeatureManifest[feature.featureId]?.isEarlyStartup ||
        feature.isEarlyStartup
      ) {
        if (!enrollment.active) {
          // Remove experiments on un-enroll, no need to check if it exists
          if (enrollment.isRollout) {
            lazy.syncDataStore.deleteDefault(feature.featureId);
          } else {
            lazy.syncDataStore.delete(feature.featureId);
          }
        } else {
          let updateEnrollmentSyncStore = enrollment.isRollout
            ? lazy.syncDataStore.setDefault.bind(lazy.syncDataStore)
            : lazy.syncDataStore.set.bind(lazy.syncDataStore);
          updateEnrollmentSyncStore(feature.featureId, {
            ...enrollment,
            branch: {
              ...enrollment.branch,
              feature,
              // Only store the early startup feature
              features: null,
            },
          });
        }
      }
    }
  }

  /**
   * Add an enrollment and notify listeners
   * @param {Enrollment} enrollment
   */
  addEnrollment(enrollment) {
    if (!enrollment || !enrollment.slug) {
      throw new Error(
        `Tried to add an experiment but it didn't have a .slug property.`
      );
    }

    this.set(enrollment.slug, enrollment);
    this._updateSyncStore(enrollment);
    this._emitUpdates(enrollment);
  }

  /**
   * Merge new properties into the properties of an existing experiment
   * @param {string} slug
   * @param {Partial<Enrollment>} newProperties
   */
  updateExperiment(slug, newProperties) {
    const oldProperties = this.get(slug);
    if (!oldProperties) {
      throw new Error(
        `Tried to update experiment ${slug} but it doesn't exist`
      );
    }
    const updatedExperiment = { ...oldProperties, ...newProperties };
    this.set(slug, updatedExperiment);
    this._updateSyncStore(updatedExperiment);
    this._emitUpdates(updatedExperiment);
  }

  /**
   * Test only helper for cleanup
   *
   * @param slugOrFeatureId Can be called with slug (which removes the SharedDataMap entry) or
   * with featureId which removes the SyncDataStore entry for the feature
   */
  _deleteForTests(slugOrFeatureId) {
    super._deleteForTests(slugOrFeatureId);
    lazy.syncDataStore.deleteDefault(slugOrFeatureId);
    lazy.syncDataStore.delete(slugOrFeatureId);
  }
}
PK
!<o�)�-�-6chrome/toolkit/res/nimbus/lib/PrefFlipsFeature.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
  PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs",
});

const FEATURE_ID = "prefFlips";
export const REASON_PREFFLIPS_FAILED = "prefFlips-failed";

export class PrefFlipsFeature {
  #initialized;
  #updating;

  static get FEATURE_ID() {
    return FEATURE_ID;
  }

  constructor({ manager }) {
    this.manager = manager;
    this._prefs = new Map();

    this.#initialized = false;
    this.#updating = false;
  }

  onFeatureUpdate() {
    if (this.#updating) {
      return;
    }

    this.#updating = true;

    const activeEnrollment =
      this.manager.store.getExperimentForFeature(PrefFlipsFeature.FEATURE_ID) ??
      this.manager.store.getRolloutForFeature(PrefFlipsFeature.FEATURE_ID);

    const prefs = lazy.NimbusFeatures[FEATURE_ID].getVariable("prefs") ?? {};

    try {
      for (const [pref, details] of this._prefs.entries()) {
        if (Object.hasOwn(prefs, pref)) {
          // The pref may have changed.
          const newDetails = prefs[pref];

          if (
            newDetails.branch !== details.branch ||
            newDetails.value !== details.value
          ) {
            this._updatePref({
              pref,
              branch: newDetails.branch,
              value: newDetails.value,
              slug: activeEnrollment.slug,
            });
          }
        } else {
          // The pref is no longer controlled by us.
          this._unregisterPref(pref);
        }
      }
    } catch (e) {
      if (e instanceof PrefFlipsFailedError) {
        this.#updating = false;

        this._unenrollForFailure(activeEnrollment, e.pref);
        return;
      }
    }

    try {
      for (const [pref, { branch, value }] of Object.entries(prefs)) {
        const known = this._prefs.get(pref);
        if (known) {
          // We have already processed this pref.
          continue;
        }

        const setPref = this.manager._prefs.get(pref);
        if (setPref) {
          const toUnenroll = Array.from(setPref.slugs.values()).map(slug =>
            this.manager.store.get(slug)
          );

          if (toUnenroll.length === 2 && !toUnenroll[0].isRollout) {
            toUnenroll.reverse();
          }

          for (const enrollment of toUnenroll) {
            this.manager._unenroll(enrollment, {
              reason: "prefFlips-conflict",
              conflictingSlug: activeEnrollment.slug,
            });
          }
        }

        this._registerPref({
          pref,
          branch,
          value,
          originalValue: lazy.PrefUtils.getPref(pref, { branch }),
          slug: activeEnrollment.slug,
        });
      }
    } catch (e) {
      this.#updating = false;

      this._unenrollForFailure(activeEnrollment, e.pref);
      return;
    }

    if (activeEnrollment) {
      // If this is new enrollment, we need to cache the original values of prefs
      // so they can be restored.
      if (!Object.hasOwn(activeEnrollment, "prefFlips")) {
        activeEnrollment.prefFlips = {};
      }

      activeEnrollment.prefFlips.originalValues = Object.fromEntries(
        Array.from(this._prefs.entries(), ([pref, { originalValue }]) => [
          pref,
          originalValue,
        ])
      );
    }

    this.#updating = false;
  }

  /**
   * Intialize the prefFlips feature.
   *
   * This will re-hydrate `this._prefs` from the active enrollment (if any) and
   * register any necessary pref observers.
   *
   * onFeatureUpdate will be called for any future feature changes.
   */
  init() {
    if (this.#initialized) {
      return;
    }

    const activeEnrollment =
      this.manager.store.getExperimentForFeature(FEATURE_ID) ??
      this.manager.store.getRolloutForFeature(FEATURE_ID);

    if (activeEnrollment?.prefFlips?.originalValues) {
      const featureValue = activeEnrollment.branch.features.find(
        fc => fc.featureId === FEATURE_ID
      ).value;
      try {
        for (const [pref, { branch, value }] of Object.entries(
          featureValue.prefs
        )) {
          this._registerPref({
            pref,
            branch,
            value,
            originalValue: activeEnrollment.prefFlips.originalValues[pref],
            slug: activeEnrollment.slug,
          });
        }
      } catch (e) {
        if (e instanceof PrefFlipsFailedError) {
          this._unenrollForFailure(activeEnrollment, e.pref);
          return;
        }
      }
    }

    lazy.NimbusFeatures.prefFlips.onUpdate((...args) =>
      this.onFeatureUpdate(...args)
    );

    this.#initialized = true;
  }

  _registerPref({ pref, branch, value, originalValue, slug }) {
    const observer = (_aSubject, _aTopic, aData) => {
      // This observer will be called for changes to `name` as well as any
      // other pref that begins with `name.`, so we have to filter to
      // exactly the pref we care about.
      if (aData === pref) {
        this._onPrefChanged(pref);
      }
    };

    // If we *just* unenrolled a setPref experiment for this pref on the default
    // branch, the pref will only be correctly restored if the pref had a value
    // on the default branch. Otherwise, it will be left as-is until restart.
    // This may result in us computing an incorrect originalValue, but (a) we
    // couldn't correct the problem even if we recorded the correct (i.e., null)
    // value and (b) the issue will resolve itself at next startup. This is
    // consistent with how setPref experiments work.
    const entry = {
      branch,
      originalValue,
      value: value ?? null,
      observer,
      slug,
    };

    try {
      lazy.PrefUtils.setPref(pref, value ?? null, { branch });
    } catch (e) {
      throw new PrefFlipsFailedError(pref);
    }

    Services.prefs.addObserver(pref, observer);
    this._prefs.set(pref, entry);
  }

  _updatePref({ pref, branch, value, slug }) {
    const entry = this._prefs.get(pref);
    if (!entry) {
      return;
    }

    Services.prefs.removeObserver(pref, entry.observer);

    let originalValue = entry.originalValue;
    if (entry.branch !== branch) {
      // Restore the value on the previous branch.
      //
      // Because we were able to set the pref, it must have the same type as the
      // originalValue, so this will also succeed.
      lazy.PrefUtils.setPref(pref, entry.originalValue, {
        branch: entry.branch,
      });

      originalValue = lazy.PrefUtils.getPref(pref, { branch });
    }

    Object.assign(entry, {
      branch,
      value,
      originalValue,
      slug,
    });

    try {
      lazy.PrefUtils.setPref(pref, value, { branch });
    } catch (e) {
      throw new PrefFlipsFailedError(pref);
    }
    Services.prefs.addObserver(pref, entry.observer);
  }

  _unregisterPref(pref) {
    const entry = this._prefs.get(pref);
    if (!entry) {
      return;
    }

    this._prefs.delete(pref);
    Services.prefs.removeObserver(pref, entry.observer);

    const { originalValue, branch } = entry;
    lazy.PrefUtils.setPref(pref, originalValue, { branch });
  }

  _onPrefChanged(pref) {
    if (this.#updating) {
      return;
    }

    if (this.manager._prefs.get(pref)?.enrollmentChanging) {
      return;
    }

    this.#updating = true;

    const entry = this._prefs.get(pref);
    if (!entry) {
      return;
    }

    this._prefs.delete(pref);
    Services.prefs.removeObserver(pref, entry.observer);

    const changedPref = {
      name: pref,
      branch: PrefFlipsFeature.determinePrefChangeBranch(
        pref,
        entry.branch,
        entry.value
      ),
    };

    // If there is both an experiment and a rollout that would both control the
    // same pref, we unenroll both because if we only unenrolled the experiment,
    // the rollout would clobber the pref change that just happened.
    const toUnenroll = this.manager.store.getAll().filter(enrollment => {
      if (!enrollment.active || !enrollment.featureIds.includes(FEATURE_ID)) {
        return false;
      }

      const featureValue = enrollment.branch.features.find(
        featureConfig => featureConfig.featureId === FEATURE_ID
      ).value;
      return Object.hasOwn(featureValue.prefs, pref);
    });

    // We have to restore every *other* pref controlled by these enrollments.
    const toRestore = new Set(
      toUnenroll.flatMap(enrollment =>
        Object.keys(
          enrollment.branch.features.find(
            featureConfig => featureConfig.featureId === FEATURE_ID
          ).value.prefs
        )
      )
    );
    toRestore.delete(pref);

    for (const prefToRestore of toRestore) {
      this._unregisterPref(prefToRestore);
    }

    // Unenrollment doesn't matter here like it does in ExperimentManager's
    // managed prefs because we've already restored prefs before unenrollment.
    for (const enrollment of toUnenroll) {
      this.manager._unenroll(enrollment, {
        reason: "changed-pref",
        changedPref,
      });
    }

    this.#updating = false;

    // If we've caused unenrollments, we need to recompute state.
    this.onFeatureUpdate();
  }

  static determinePrefChangeBranch(pref, expectedBranch, expectedValue) {
    // We want to know what branch was changed so we can know if we should
    // restore prefs (.e.,g if we have a pref set on the user branch and the
    // user branch changed, we do not want to then overwrite the user's choice).

    // This is not complicated if a pref simply changed. However, we must also
    // detect `nsIPrefBranch::clearUserPref()`, which wipes out the user branch
    // and leaves the default branch untouched. That is where this gets
    // complicated.

    if (Services.prefs.prefHasUserValue(pref)) {
      // If there is a user branch value, then the user branch changed, because
      // a change to the default branch wouldn't have triggered the observer.
      return "user";
    } else if (!Services.prefs.prefHasDefaultValue(pref)) {
      // If there is no user branch value *or* default branch avlue, then the
      // user branch must have been cleared because you cannot clear the default
      // branch.
      return "user";
    } else if (expectedBranch === "default") {
      const value = lazy.PrefUtils.getPref(pref, { branch: "default" });
      if (value === expectedValue) {
        // The pref we control was set on the default branch and still matches
        // the expected value. Therefore, the user branch must have been
        // cleared.
        return "user";
      }
      // The default value branch does not match the value we expect, so it
      // must have just changed.
      return "default";
    }
    return "user";
  }

  _unenrollForFailure(enrollment, pref) {
    const rawType = Services.prefs.getPrefType(pref);
    let prefType = "invalid";

    switch (rawType) {
      case Ci.nsIPrefBranch.PREF_BOOL:
        prefType = "bool";
        break;

      case Ci.nsIPrefBranch.PREF_STRING:
        prefType = "string";
        break;

      case Ci.nsIPrefBranch.PREF_INT:
        prefType = "int";
        break;
    }

    this.manager._unenroll(enrollment, {
      reason: REASON_PREFFLIPS_FAILED,
      prefName: pref,
      prefType,
    });
  }
}

/**
 * Thrown when the prefFlips feature fails to set a pref.
 */
class PrefFlipsFailedError extends Error {
  constructor(pref, value) {
    super(`The Nimbus prefFlips feature failed to set ${pref}=${value}`);
    this.pref = pref;
  }
}
PK
!<�*��b�bDchrome/toolkit/res/nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  _ExperimentFeature: "resource://nimbus/ExperimentAPI.sys.mjs",
  ASRouterTargeting:
    // eslint-disable-next-line mozilla/no-browser-refs-in-toolkit
    "resource:///modules/asrouter/ASRouterTargeting.sys.mjs",
  CleanupManager: "resource://normandy/lib/CleanupManager.sys.mjs",
  ExperimentManager: "resource://nimbus/lib/ExperimentManager.sys.mjs",
  JsonSchema: "resource://gre/modules/JsonSchema.sys.mjs",
  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
  TargetingContext: "resource://messaging-system/targeting/Targeting.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  const { Logger } = ChromeUtils.importESModule(
    "resource://messaging-system/lib/Logger.sys.mjs"
  );
  return new Logger("RSLoader");
});

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "timerManager",
  "@mozilla.org/updates/timer-manager;1",
  "nsIUpdateTimerManager"
);

const COLLECTION_ID_PREF = "messaging-system.rsexperimentloader.collection_id";
const COLLECTION_ID_FALLBACK = "nimbus-desktop-experiments";
const ENABLED_PREF = "messaging-system.rsexperimentloader.enabled";

const TIMER_NAME = "rs-experiment-loader-timer";
const TIMER_LAST_UPDATE_PREF = `app.update.lastUpdateTime.${TIMER_NAME}`;
// Use the same update interval as normandy
const RUN_INTERVAL_PREF = "app.normandy.run_interval_seconds";
const NIMBUS_DEBUG_PREF = "nimbus.debug";
const NIMBUS_VALIDATION_PREF = "nimbus.validation.enabled";
const NIMBUS_APPID_PREF = "nimbus.appId";

const STUDIES_ENABLED_CHANGED = "nimbus:studies-enabled-changed";

const SECURE_EXPERIMENTS_COLLECTION_ID = "nimbus-secure-experiments";

const EXPERIMENTS_COLLECTION = "experiments";
const SECURE_EXPERIMENTS_COLLECTION = "secureExperiments";

const RS_COLLECTION_OPTIONS = {
  [EXPERIMENTS_COLLECTION]: {
    disallowedFeatureIds: ["prefFlips"],
  },

  [SECURE_EXPERIMENTS_COLLECTION]: {
    allowedFeatureIds: ["prefFlips"],
  },
};

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "COLLECTION_ID",
  COLLECTION_ID_PREF,
  COLLECTION_ID_FALLBACK
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "NIMBUS_DEBUG",
  NIMBUS_DEBUG_PREF,
  false
);
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "APP_ID",
  NIMBUS_APPID_PREF,
  "firefox-desktop"
);

const SCHEMAS = {
  get NimbusExperiment() {
    return fetch("resource://nimbus/schemas/NimbusExperiment.schema.json", {
      credentials: "omit",
    })
      .then(rsp => rsp.json())
      .then(json => json.definitions.NimbusExperiment);
  },
};

export class _RemoteSettingsExperimentLoader {
  constructor() {
    // Has the timer been set?
    this._initialized = false;
    // Are we in the middle of updating recipes already?
    this._updating = false;

    // Make it possible to override for testing
    this.manager = lazy.ExperimentManager;

    this.remoteSettingsClients = {};
    ChromeUtils.defineLazyGetter(
      this.remoteSettingsClients,
      EXPERIMENTS_COLLECTION,
      () => {
        return lazy.RemoteSettings(lazy.COLLECTION_ID);
      }
    );
    ChromeUtils.defineLazyGetter(
      this.remoteSettingsClients,
      SECURE_EXPERIMENTS_COLLECTION,
      () => {
        return lazy.RemoteSettings(SECURE_EXPERIMENTS_COLLECTION_ID);
      }
    );

    Services.obs.addObserver(this, STUDIES_ENABLED_CHANGED);

    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "enabled",
      ENABLED_PREF,
      false,
      this.onEnabledPrefChange.bind(this)
    );

    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "intervalInSeconds",
      RUN_INTERVAL_PREF,
      21600,
      () => this.setTimer()
    );

    XPCOMUtils.defineLazyPreferenceGetter(
      this,
      "validationEnabled",
      NIMBUS_VALIDATION_PREF,
      true
    );
  }

  get studiesEnabled() {
    return this.manager.studiesEnabled;
  }

  /**
   * Initialize the loader, updating recipes from Remote Settings.
   *
   * @param {Object} options            additional options.
   * @param {bool}   options.forceSync  force Remote Settings to sync recipe collection
   *                                    before updating recipes; throw if sync fails.
   * @return {Promise}                  which resolves after initialization and recipes
   *                                    are updated.
   */
  async init(options = {}) {
    const { forceSync = false } = options;

    if (this._initialized || !this.enabled || !this.studiesEnabled) {
      return;
    }

    this.setTimer();
    lazy.CleanupManager.addCleanupHandler(() => this.uninit());
    this._initialized = true;

    await this.updateRecipes(undefined, { forceSync });
  }

  uninit() {
    if (!this._initialized) {
      return;
    }
    lazy.timerManager.unregisterTimer(TIMER_NAME);
    this._initialized = false;
    this._updating = false;
  }

  /**
   * Get all recipes from remote settings and update enrollments.
   *
   * @param {string} trigger - What caused the update to occur?
   * @param {object} options
   * @param {boolean}   options.forceSync - Force Remote Settings to sync recipe
   *                                     collection before updating recipes.
   */
  async updateRecipes(trigger, { forceSync = false } = {}) {
    if (this._updating || !this._initialized) {
      return;
    }

    this._updating = true;

    // Since this method is async, the enabled pref could change between await
    // points. We don't want to half validate experiments, so we cache this to
    // keep it consistent throughout updating.
    const validationEnabled = this.validationEnabled;

    let recipeValidator;

    if (validationEnabled) {
      recipeValidator = new lazy.JsonSchema.Validator(
        await SCHEMAS.NimbusExperiment
      );
    }

    lazy.log.debug(`Updating recipes with trigger "${trigger ?? ""}`);

    const recipes = [];
    let loadingError = false;

    const experiments = await this.getRecipesFromCollection({
      forceSync,
      client: this.remoteSettingsClients[EXPERIMENTS_COLLECTION],
      ...RS_COLLECTION_OPTIONS[EXPERIMENTS_COLLECTION],
    });

    if (experiments !== null) {
      recipes.push(...experiments);
    } else {
      loadingError = true;
    }

    const secureExperiments = await this.getRecipesFromCollection({
      forceSync,
      client: this.remoteSettingsClients[SECURE_EXPERIMENTS_COLLECTION],
      ...RS_COLLECTION_OPTIONS[SECURE_EXPERIMENTS_COLLECTION],
    });

    if (secureExperiments !== null) {
      recipes.push(...secureExperiments);
    } else {
      loadingError = true;
    }

    recipes.sort(
      (a, b) => new Date(a.publishedDate ?? 0) - new Date(b.publishedDate ?? 0)
    );

    const enrollmentsCtx = new EnrollmentsContext(
      this.manager,
      recipeValidator,
      { validationEnabled, shouldCheckTargeting: true }
    );

    if (recipes && !loadingError) {
      for (const recipe of recipes) {
        if (await enrollmentsCtx.checkRecipe(recipe)) {
          await this.manager.onRecipe(recipe, "rs-loader");
        }
      }

      lazy.log.debug(
        `${enrollmentsCtx.matches} recipes matched. Finalizing ExperimentManager.`
      );
      this.manager.onFinalize("rs-loader", enrollmentsCtx.getResults());
    }

    if (trigger !== "timer") {
      const lastUpdateTime = Math.round(Date.now() / 1000);
      Services.prefs.setIntPref(TIMER_LAST_UPDATE_PREF, lastUpdateTime);
    }

    Services.obs.notifyObservers(null, "nimbus:enrollments-updated");

    this._updating = false;

    this.recordIsReady();
  }

  /**
   * Return the recipes from a given collection.
   *
   * @param {object} options
   * @param {RemoteSettings} options.client
   *        The RemoteSettings client that will be used to fetch recipes.
   * @param {boolean} options.forceSync
   *        Force the RemoteSettings client to sync the collection before retrieving recipes.
   * @param {string[] | null} options.allowedFeatureIds
   *        If non-null, any recipe that uses a feature ID not in this list will
   *        be rejected.
   * @param {string[]} options.disallowedFeatureIds
   *        If a recipe uses any features in this list, it will be rejected.
   *
   * @returns {object[] | null}
   *          Recipes from the collection, filtered to match the allowed and
   *          disallowed feature IDs, or null if there was an error syncing the
   *          collection.
   */
  async getRecipesFromCollection({
    client,
    forceSync = false,
    allowedFeatureIds = null,
    disallowedFeatureIds = [],
  } = {}) {
    let recipes;
    try {
      recipes = await client.get({
        forceSync,
        emptyListFallback: false, // Throw instead of returning an empty list.
      });
      lazy.log.debug(
        `Got ${recipes.length} recipes from ${client.collectionName}`
      );
    } catch (e) {
      lazy.log.debug(
        `Error getting recipes from Remote Settings collection ${client.collectionName}`
      );
      console.error(e);

      return null;
    }

    return recipes.filter(recipe => {
      for (const featureId of recipe.featureIds) {
        if (allowedFeatureIds !== null) {
          if (!allowedFeatureIds.includes(featureId)) {
            lazy.log.warn(
              `Recipe ${recipe.slug} not returned from collection ${client.collectionName} because it contains feature ${featureId}, which is disallowed for that collection.`
            );
            return false;
          }
        }

        if (disallowedFeatureIds.includes(featureId)) {
          lazy.log.warn(
            `Recipe ${recipe.slug} not returned from collection ${client.collectionName} because it contains feature ${featureId}, which is disallowed for that collection.`
          );
          return false;
        }
      }

      return true;
    });
  }

  async optInToExperiment({
    slug,
    branch: branchSlug,
    collection,
    applyTargeting = false,
  }) {
    lazy.log.debug(`Attempting force enrollment with ${slug} / ${branchSlug}`);

    if (!lazy.NIMBUS_DEBUG) {
      lazy.log.debug(
        `Force enrollment only works when '${NIMBUS_DEBUG_PREF}' is enabled.`
      );
      // More generic error if no debug preference is on.
      throw new Error("Could not opt in.");
    }

    if (!this.studiesEnabled) {
      lazy.log.debug(
        "Force enrollment does not work when studies are disabled."
      );
      throw new Error("Could not opt in: studies are disabled.");
    }

    let recipes;
    try {
      recipes = await lazy
        .RemoteSettings(collection || lazy.COLLECTION_ID)
        .get({
          // Throw instead of returning an empty list.
          emptyListFallback: false,
        });
    } catch (e) {
      console.error(e);
      throw new Error("Error getting recipes from remote settings.");
    }

    const recipe = recipes.find(r => r.slug === slug);

    if (!recipe) {
      throw new Error(
        `Could not find experiment slug ${slug} in collection ${
          collection || lazy.COLLECTION_ID
        }.`
      );
    }

    const recipeValidator = new lazy.JsonSchema.Validator(
      await SCHEMAS.NimbusExperiment
    );
    const enrollmentsCtx = new EnrollmentsContext(
      this.manager,
      recipeValidator,
      {
        validationEnabled: this.validationEnabled,
        shouldCheckTargeting: applyTargeting,
      }
    );

    if (!(await enrollmentsCtx.checkRecipe(recipe))) {
      const results = enrollmentsCtx.getResults();

      if (results.recipeMismatches.length) {
        throw new Error(`Recipe ${recipe.slug} did not match targeting`);
      } else if (results.invalidRecipes.length) {
        console.error(`Recipe ${recipe.slug} did not match recipe schema`);
      } else if (results.invalidBranches.size) {
        // There will only be one entry becuase we only validated a single recipe.
        for (const branches of results.invalidBranches.values()) {
          for (const branch of branches) {
            console.error(
              `Recipe ${recipe.slug} failed feature validation for branch ${branch}`
            );
          }
        }
      } else if (results.invalidFeatures.length) {
        for (const featureIds of results.invalidFeatures.values()) {
          for (const featureId of featureIds) {
            console.error(
              `Recipe ${recipe.slug} references unknown feature ID ${featureId}`
            );
          }
        }
      }

      throw new Error(
        `Recipe ${recipe.slug} failed validation: ${JSON.stringify(results)}`
      );
    }

    let branch = recipe.branches.find(b => b.slug === branchSlug);
    if (!branch) {
      throw new Error(`Could not find branch slug ${branchSlug} in ${slug}.`);
    }

    await this.manager.forceEnroll(recipe, branch);
  }

  /**
   * Handles feature status based on feature pref and STUDIES_OPT_OUT_PREF.
   * Changing any of them to false will turn off any recipe fetching and
   * processing.
   */
  onEnabledPrefChange() {
    if (this._initialized && !(this.enabled && this.studiesEnabled)) {
      this.uninit();
    } else if (!this._initialized && this.enabled && this.studiesEnabled) {
      // If the feature pref is turned on then turn on recipe processing.
      // If the opt in pref is turned on then turn on recipe processing only if
      // the feature pref is also enabled.
      this.init();
    }
  }

  observe(aSubect, aTopic) {
    if (aTopic === STUDIES_ENABLED_CHANGED) {
      this.onEnabledPrefChange();
    }
  }

  /**
   * Sets a timer to update recipes every this.intervalInSeconds
   */
  setTimer() {
    if (this.intervalInSeconds === 0) {
      // Used in tests where we want to turn this mechanism off
      return;
    }
    // The callbacks will be called soon after the timer is registered
    lazy.timerManager.registerTimer(
      TIMER_NAME,
      () => this.updateRecipes("timer"),
      this.intervalInSeconds
    );
    lazy.log.debug("Registered update timer");
  }

  recordIsReady() {
    const eventCount =
      lazy.NimbusFeatures.nimbusIsReady.getVariable("eventCount") ?? 1;
    for (let i = 0; i < eventCount; i++) {
      Glean.nimbusEvents.isReady.record();
    }
  }
}

export class EnrollmentsContext {
  constructor(
    experimentManager,
    recipeValidator,
    { validationEnabled = true, shouldCheckTargeting = true } = {}
  ) {
    this.experimentManager = experimentManager;
    this.recipeValidator = recipeValidator;

    this.validationEnabled = validationEnabled;
    this.shouldCheckTargeting = shouldCheckTargeting;
    this.matches = 0;

    this.recipeMismatches = [];
    this.invalidRecipes = [];
    this.invalidBranches = new Map();
    this.invalidFeatures = new Map();
    this.validatorCache = {};
    this.missingLocale = [];
    this.missingL10nIds = new Map();

    this.locale = Services.locale.appLocaleAsBCP47;
  }

  getResults() {
    return {
      recipeMismatches: this.recipeMismatches,
      invalidRecipes: this.invalidRecipes,
      invalidBranches: this.invalidBranches,
      invalidFeatures: this.invalidFeatures,
      missingLocale: this.missingLocale,
      missingL10nIds: this.missingL10nIds,
      locale: this.locale,
      validationEnabled: this.validationEnabled,
    };
  }

  async checkRecipe(recipe) {
    if (recipe.appId !== "firefox-desktop") {
      // Skip over recipes not intended for desktop. Experimenter publishes
      // recipes into a collection per application (desktop goes to
      // `nimbus-desktop-experiments`) but all preview experiments share the
      // same collection (`nimbus-preview`).
      //
      // This is *not* the same as `lazy.APP_ID` which is used to
      // distinguish between desktop Firefox and the desktop background
      // updater.
      return false;
    }

    const validateFeatureSchemas =
      this.validationEnabled && !recipe.featureValidationOptOut;

    if (this.validationEnabled) {
      let validation = this.recipeValidator.validate(recipe);
      if (!validation.valid) {
        console.error(
          `Could not validate experiment recipe ${recipe.id}: ${JSON.stringify(
            validation.errors,
            null,
            2
          )}`
        );
        if (recipe.slug) {
          this.invalidRecipes.push(recipe.slug);
        }
        return false;
      }
    }

    const featureIds =
      recipe.featureIds ??
      recipe.branches
        .flatMap(branch => branch.features ?? [branch.feature])
        .map(featureDef => featureDef.featureId);

    let haveAllFeatures = true;

    for (const featureId of featureIds) {
      const feature = lazy.NimbusFeatures[featureId];

      // If validation is enabled, we want to catch this later in
      // _validateBranches to collect the correct stats for telemetry.
      if (!feature) {
        continue;
      }

      if (!feature.applications.includes(lazy.APP_ID)) {
        lazy.log.debug(
          `${recipe.id} uses feature ${featureId} which is not enabled for this application (${lazy.APP_ID}) -- skipping`
        );
        haveAllFeatures = false;
        break;
      }
    }

    if (!haveAllFeatures) {
      return false;
    }

    if (this.shouldCheckTargeting) {
      const match = await this.checkTargeting(recipe);

      if (match) {
        const type = recipe.isRollout ? "rollout" : "experiment";
        lazy.log.debug(`[${type}] ${recipe.id} matched targeting`);
      } else {
        lazy.log.debug(`${recipe.id} did not match due to targeting`);
        this.recipeMismatches.push(recipe.slug);
        return false;
      }
    }

    this.matches++;

    if (
      typeof recipe.localizations === "object" &&
      recipe.localizations !== null
    ) {
      if (
        typeof recipe.localizations[this.locale] !== "object" ||
        recipe.localizations[this.locale] === null
      ) {
        this.missingLocale.push(recipe.slug);
        lazy.log.debug(
          `${recipe.id} is localized but missing locale ${this.locale}`
        );
        return false;
      }
    }

    const result = await this._validateBranches(recipe, validateFeatureSchemas);
    if (!result.valid) {
      if (result.invalidBranchSlugs.length) {
        this.invalidBranches.set(recipe.slug, result.invalidBranchSlugs);
      }
      if (result.invalidFeatureIds.length) {
        this.invalidFeatures.set(recipe.slug, result.invalidFeatureIds);
      }
      if (result.missingL10nIds.length) {
        this.missingL10nIds.set(recipe.slug, result.missingL10nIds);
      }
      lazy.log.debug(`${recipe.id} did not validate`);
      return false;
    }

    return true;
  }

  async evaluateJexl(jexlString, customContext) {
    if (customContext && !customContext.experiment) {
      throw new Error(
        "Expected an .experiment property in second param of this function"
      );
    }

    if (!customContext.source) {
      throw new Error(
        "Expected a .source property that identifies which targeting expression is being evaluated."
      );
    }

    const context = lazy.TargetingContext.combineContexts(
      customContext,
      this.experimentManager.createTargetingContext(),
      lazy.ASRouterTargeting.Environment
    );

    lazy.log.debug("Testing targeting expression:", jexlString);
    const targetingContext = new lazy.TargetingContext(context, {
      source: customContext.source,
    });

    let result = null;
    try {
      result = await targetingContext.evalWithDefault(jexlString);
    } catch (e) {
      lazy.log.debug("Targeting failed because of an error", e);
      console.error(e);
    }
    return result;
  }

  /**
   * Checks targeting of a recipe if it is defined
   * @param {Recipe} recipe
   * @param {{[key: string]: any}} customContext A custom filter context
   * @returns {Promise<boolean>} Should we process the recipe?
   */
  async checkTargeting(recipe) {
    if (!recipe.targeting) {
      lazy.log.debug("No targeting for recipe, so it matches automatically");
      return true;
    }

    const result = await this.evaluateJexl(recipe.targeting, {
      experiment: recipe,
      source: recipe.slug,
    });

    return Boolean(result);
  }

  /**
   * Validate the branches of an experiment.
   *
   * @param {object} recipe The recipe object.
   * @param {boolean} validateSchema Whether to validate the feature values
   *        using JSON schemas.
   *
   * @returns {object} The lists of invalid branch slugs and invalid feature
   *                   IDs.
   */
  async _validateBranches({ id, branches, localizations }, validateSchema) {
    const invalidBranchSlugs = [];
    const invalidFeatureIds = new Set();
    const missingL10nIds = new Set();

    if (validateSchema || typeof localizations !== "undefined") {
      for (const [branchIdx, branch] of branches.entries()) {
        const features = branch.features ?? [branch.feature];
        for (const feature of features) {
          const { featureId, value } = feature;
          if (!lazy.NimbusFeatures[featureId]) {
            console.error(
              `Experiment ${id} has unknown featureId: ${featureId}`
            );

            invalidFeatureIds.add(featureId);
            continue;
          }

          let substitutedValue = value;

          if (localizations) {
            // We already know that we have a localization table for this locale
            // because we checked in `checkRecipe`.
            try {
              substitutedValue =
                lazy._ExperimentFeature.substituteLocalizations(
                  value,
                  localizations[Services.locale.appLocaleAsBCP47],
                  missingL10nIds
                );
            } catch (e) {
              if (e?.reason === "l10n-missing-entry") {
                // Skip validation because it *will* fail.
                continue;
              }
              throw e;
            }
          }

          if (validateSchema) {
            let validator;
            if (this.validatorCache[featureId]) {
              validator = this.validatorCache[featureId];
            } else if (lazy.NimbusFeatures[featureId].manifest.schema?.uri) {
              const uri = lazy.NimbusFeatures[featureId].manifest.schema.uri;
              try {
                const schema = await fetch(uri, {
                  credentials: "omit",
                }).then(rsp => rsp.json());

                validator = this.validatorCache[featureId] =
                  new lazy.JsonSchema.Validator(schema);
              } catch (e) {
                throw new Error(
                  `Could not fetch schema for feature ${featureId} at "${uri}": ${e}`
                );
              }
            } else {
              const schema = this._generateVariablesOnlySchema(
                lazy.NimbusFeatures[featureId]
              );
              validator = this.validatorCache[featureId] =
                new lazy.JsonSchema.Validator(schema);
            }

            const result = validator.validate(substitutedValue);
            if (!result.valid) {
              console.error(
                `Experiment ${id} branch ${branchIdx} feature ${featureId} does not validate: ${JSON.stringify(
                  result.errors,
                  undefined,
                  2
                )}`
              );
              invalidBranchSlugs.push(branch.slug);
            }
          }
        }
      }
    }

    return {
      invalidBranchSlugs,
      invalidFeatureIds: Array.from(invalidFeatureIds),
      missingL10nIds: Array.from(missingL10nIds),
      valid:
        invalidBranchSlugs.length === 0 &&
        invalidFeatureIds.size === 0 &&
        missingL10nIds.size === 0,
    };
  }

  _generateVariablesOnlySchema({ featureId, manifest }) {
    // See-also: https://github.com/mozilla/experimenter/blob/main/app/experimenter/features/__init__.py#L21-L64
    const schema = {
      $schema: "https://json-schema.org/draft/2019-09/schema",
      title: featureId,
      description: manifest.description,
      type: "object",
      properties: {},
      additionalProperties: true,
    };

    for (const [varName, desc] of Object.entries(manifest.variables)) {
      const prop = {};
      switch (desc.type) {
        case "boolean":
        case "string":
          prop.type = desc.type;
          break;

        case "int":
          prop.type = "integer";
          break;

        case "json":
          // NB: Don't set a type of json fields, since they can be of any type.
          break;

        default:
          // NB: Experimenter doesn't outright reject invalid types either.
          console.error(
            `Feature ID ${featureId} has variable ${varName} with invalid FML type: ${prop.type}`
          );
          break;
      }

      if (prop.type === "string" && !!desc.enum) {
        prop.enum = [...desc.enum];
      }

      schema.properties[varName] = prop;
    }

    return schema;
  }
}

export const RemoteSettingsExperimentLoader =
  new _RemoteSettingsExperimentLoader();
PK
!<;m����3chrome/toolkit/res/nimbus/lib/SharedDataMap.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { EventEmitter } from "resource://gre/modules/EventEmitter.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  JSONFile: "resource://gre/modules/JSONFile.sys.mjs",
});

const IS_MAIN_PROCESS =
  Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT;

export class SharedDataMap extends EventEmitter {
  constructor(sharedDataKey, options = { isParent: IS_MAIN_PROCESS }) {
    super();

    this._sharedDataKey = sharedDataKey;
    this._isParent = options.isParent;
    this._isReady = false;
    this._readyDeferred = Promise.withResolvers();
    this._data = null;

    if (this.isParent) {
      // Lazy-load JSON file that backs Storage instances.
      ChromeUtils.defineLazyGetter(this, "_store", () => {
        let path = options.path;
        let store = null;
        if (!path) {
          try {
            const profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile).path;
            path = PathUtils.join(profileDir, `${sharedDataKey}.json`);
          } catch (e) {
            console.error(e);
          }
        }
        try {
          store = new lazy.JSONFile({ path });
        } catch (e) {
          console.error(e);
        }
        return store;
      });
    } else {
      this._syncFromParent();
      Services.cpmm.sharedData.addEventListener("change", this);
    }
  }

  async init() {
    if (!this._isReady && this.isParent) {
      try {
        await this._store.load();
        this._data = this._store.data;
        this._syncToChildren({ flush: true });
        this._checkIfReady();
      } catch (e) {
        console.error(e);
      }
    }
  }

  get sharedDataKey() {
    return this._sharedDataKey;
  }

  get isParent() {
    return this._isParent;
  }

  ready() {
    return this._readyDeferred.promise;
  }

  get(key) {
    if (!this._data) {
      return null;
    }

    let entry = this._data[key];

    return entry;
  }

  set(key, value) {
    if (!this.isParent) {
      throw new Error(
        "Setting values from within a content process is not allowed"
      );
    }
    this._store.data[key] = value;
    this._store.saveSoon();
    this._syncToChildren();
    this._notifyUpdate();
  }

  /**
   * Replace the stored data with an updated filtered dataset for cleanup
   * purposes. We don't notify of update because we're only filtering out
   * old unused entries.
   *
   * @param {string[]} keysToRemove - list of keys to remove from the persistent store
   */
  _removeEntriesByKeys(keysToRemove) {
    if (!keysToRemove.length) {
      return;
    }
    for (let key of keysToRemove) {
      try {
        delete this._store.data[key];
      } catch (e) {
        // It's ok if this fails
      }
    }
    this._store.saveSoon();
  }

  // Only used in tests
  _deleteForTests(key) {
    if (!this.isParent) {
      throw new Error(
        "Setting values from within a content process is not allowed"
      );
    }
    if (this.has(key)) {
      delete this._store.data[key];
      this._store.saveSoon();
      this._syncToChildren();
      this._notifyUpdate();
    }
  }

  has(key) {
    return Boolean(this.get(key));
  }

  /**
   * Notify store listeners of updates
   * Called both from Main and Content process
   */
  _notifyUpdate(process = "parent") {
    for (let key of Object.keys(this._data || {})) {
      this.emit(`${process}-store-update:${key}`, this._data[key]);
    }
  }

  _syncToChildren({ flush = false } = {}) {
    Services.ppmm.sharedData.set(this.sharedDataKey, {
      ...this._data,
    });
    if (flush) {
      Services.ppmm.sharedData.flush();
    }
  }

  _syncFromParent() {
    this._data = Services.cpmm.sharedData.get(this.sharedDataKey);
    this._checkIfReady();
    this._notifyUpdate("child");
  }

  _checkIfReady() {
    if (!this._isReady && this._data) {
      this._isReady = true;
      this._readyDeferred.resolve();
    }
  }

  handleEvent(event) {
    if (event.type === "change") {
      if (event.changedKeys.includes(this.sharedDataKey)) {
        this._syncFromParent();
      }
    }
  }
}
PK
!<$�!�!>chrome/toolkit/res/nimbus/schemas/NimbusEnrollment.schema.json{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$ref": "#/definitions/NimbusEnrollment",
  "definitions": {
    "NimbusEnrollment": {
      "type": "object",
      "properties": {
        "slug": {
          "type": "string",
          "description": "Unique identifier for the experiment"
        },
        "userFacingName": {
          "type": "string",
          "description": "Public name of the experiment displayed on \"about:studies\""
        },
        "userFacingDescription": {
          "type": "string",
          "description": "Short public description of the experiment displayed on on \"about:studies\""
        },
        "isRollout": {
          "type": "boolean",
          "description": "When this property is set to true, treat this experiment as a rollout. Rollouts are currently handled as single-branch experiments separated from the bucketing namespace for normal experiments. See also: https://mozilla-hub.atlassian.net/browse/SDK-405"
        },
        "featureIds": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "A list of featureIds the experiment contains configurations for."
        },
        "branch": {
          "anyOf": [
            {
              "type": "object",
              "properties": {
                "slug": {
                  "type": "string",
                  "description": "Identifier for the branch"
                },
                "feature": {
                  "type": "object",
                  "properties": {
                    "featureId": {
                      "type": "string",
                      "description": "The identifier for the feature flag"
                    },
                    "value": {
                      "type": "object",
                      "additionalProperties": {},
                      "description": "Optional extra params for the feature (this should be validated against a schema)"
                    }
                  },
                  "required": ["featureId", "value"],
                  "description": "A single feature configuration"
                }
              },
              "required": ["slug", "feature"]
            },
            {
              "type": "object",
              "properties": {
                "slug": {
                  "type": "string",
                  "description": "Identifier for the branch"
                },
                "feature": {
                  "type": "object",
                  "properties": {
                    "featureId": {
                      "type": "string",
                      "const": "unused-feature-id-for-legacy-support"
                    },
                    "enabled": {
                      "type": "boolean",
                      "const": false
                    },
                    "value": {
                      "type": "object",
                      "additionalProperties": {}
                    }
                  },
                  "required": ["featureId", "enabled", "value"],
                  "description": "The feature key must be provided with valid values to prevent crashes if the DTO is encountered by Desktop clients earlier than version 95."
                },
                "features": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "featureId": {
                        "type": "string",
                        "description": "The identifier for the feature flag"
                      },
                      "value": {
                        "type": "object",
                        "additionalProperties": {},
                        "description": "Optional extra params for the feature (this should be validated against a schema)"
                      }
                    },
                    "required": ["featureId", "value"]
                  },
                  "description": "An array of feature configurations"
                }
              },
              "required": ["slug", "feature", "features"]
            },
            {
              "type": "object",
              "properties": {
                "slug": {
                  "type": "string",
                  "description": "Identifier for the branch"
                },
                "features": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "featureId": {
                        "type": "string",
                        "description": "The identifier for the feature flag"
                      },
                      "value": {
                        "type": "object",
                        "additionalProperties": {},
                        "description": "Optional extra params for the feature (this should be validated against a schema)"
                      }
                    },
                    "required": ["featureId", "value"]
                  },
                  "description": "An array of feature configurations"
                }
              },
              "required": ["slug", "features"]
            }
          ],
          "description": "Branch configuration for the experiment"
        },
        "experimentType": {
          "type": "string",
          "description": "What kind of experiment this enrollment corresponds to."
        },
        "active": {
          "type": "boolean",
          "description": "Whether or not the enrollment is active."
        },
        "lastSeen": {
          "type": "string",
          "format": "date-time",
          "description": "The last time the experiment was seen."
        },
        "force": {
          "type": "boolean",
          "description": "Whether or not this was a force enrollment."
        },
        "prefs": {
          "type": "array",
          "description": "Information about prefs set by this enrollment.",
          "items": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "description": "The name of the pref that was set."
              },
              "featureId": {
                "type": "string",
                "description": "The ID of the feature that owns the variable that set this pref."
              },
              "variable": {
                "type": "string",
                "description": "The variable that set this pref."
              },
              "branch": {
                "type": "string",
                "enum": ["user", "default"],
                "description": "The branch the pref was set on."
              },
              "originalValue": {
                "description": "The original value before the experiment."
              }
            },
            "additionalProperties": false
          }
        },
        "prefFlips": {
          "type": "object",
          "description": "Information stored on this enrollment by the prefFlips feature. Only present if an experiment uses the prefFlips feature.",
          "properties": {
            "originalValues": {
              "type": "object",
              "description": "Original values for prefs set by this experiment.",
              "patternProperties": {
                ".*": {
                  "description": "The original value of the pref, or null if the pref was not set.",
                  "type": ["string", "integer", "boolean", "null"]
                }
              }
            }
          }
        },
        "localizations": {
          "anyOf": [
            {
              "type": "object",
              "additionalProperties": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            },
            {
              "type": "null"
            }
          ],
          "description": "Per-locale localization substitutions.\n\nThe top level key is the locale (e.g., \"en-US\" or \"fr\"). Each entry is a mapping of string IDs to their localized equivalents.\n\nOnly supported on desktop."
        },
        "unenrollReason": {
          "type": "string",
          "description": "The reason for unenrollment. Only present when the enrollment is inactive."
        }
      },
      "required": [
        "slug",
        "userFacingName",
        "userFacingDescription",
        "branch",
        "active",
        "lastSeen"
      ],
      "description": "An enrollment in a Nimbus Experiment saved to disk"
    }
  }
}
PK
!<hY��7�7>chrome/toolkit/res/nimbus/schemas/NimbusExperiment.schema.json{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$ref": "#/definitions/NimbusExperiment",
  "definitions": {
    "NimbusExperiment": {
      "type": "object",
      "properties": {
        "schemaVersion": {
          "type": "string",
          "description": "Version of the NimbusExperiment schema this experiment refers to"
        },
        "slug": {
          "type": "string",
          "description": "Unique identifier for the experiment"
        },
        "id": {
          "type": "string",
          "description": "Unique identifier for the experiment. This is a duplicate of slug, but is a required field for all Remote Settings records."
        },
        "appName": {
          "type": "string",
          "description": "A slug identifying the targeted product for this experiment. It should be a lowercase_with_underscores name that is short and unambiguous and it should match the app_name found in https://probeinfo.telemetry.mozilla.org/glean/repositories. Examples are \"fenix\" or \"firefox_desktop\"."
        },
        "appId": {
          "type": "string",
          "description": "The platform identifier for the targeted app. The app's identifier exactly as it appears in the relevant app store listing (for relevant platforms) or in the app's Glean initialization call (for other platforms). Examples are \"org.mozilla.firefox_beta\" or \"firefox-desktop\"."
        },
        "channel": {
          "type": "string",
          "description": "A specific channel of an application such as \"nightly\", \"beta\", or \"release\""
        },
        "userFacingName": {
          "type": "string",
          "description": "Public name of the experiment displayed on \"about:studies\""
        },
        "userFacingDescription": {
          "type": "string",
          "description": "Short public description of the experiment displayed on on \"about:studies\""
        },
        "isEnrollmentPaused": {
          "type": "boolean",
          "description": "When this property is set to true, the the SDK should not enroll new users into the experiment that have not already been enrolled."
        },
        "isRollout": {
          "type": "boolean",
          "description": "When this property is set to true, treat this experiment as a rollout. Rollouts are currently handled as single-branch experiments separated from the bucketing namespace for normal experiments. See also: https://mozilla-hub.atlassian.net/browse/SDK-405"
        },
        "bucketConfig": {
          "type": "object",
          "properties": {
            "randomizationUnit": {
              "type": "string",
              "description": "A unique, stable identifier for the user used as an input to bucket hashing"
            },
            "namespace": {
              "type": "string",
              "description": "Additional inputs to the hashing function"
            },
            "start": {
              "type": "integer",
              "description": "Index of start of the range of buckets"
            },
            "count": {
              "type": "integer",
              "description": "Number of buckets to check"
            },
            "total": {
              "type": "integer",
              "description": "Total number of buckets. You can assume this will always be 10000.",
              "default": 10000
            }
          },
          "required": [
            "randomizationUnit",
            "namespace",
            "start",
            "count",
            "total"
          ],
          "description": "Bucketing configuration"
        },
        "outcomes": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "slug": {
                "type": "string",
                "description": "Identifier for the outcome"
              },
              "priority": {
                "type": "string",
                "description": "e.g. \"primary\" or \"secondary\""
              }
            },
            "required": [
              "slug",
              "priority"
            ]
          },
          "description": "A list of outcomes relevant to the experiment analysis."
        },
        "featureIds": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "A list of featureIds the experiment contains configurations for."
        },
        "branches": {
          "anyOf": [
            {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "slug": {
                    "type": "string",
                    "description": "Identifier for the branch"
                  },
                  "ratio": {
                    "type": "integer",
                    "description": "Relative ratio of population for the branch (e.g. if branch A=1 and branch B=3, branch A would get 25% of the population)",
                    "default": 1
                  },
                  "feature": {
                    "type": "object",
                    "properties": {
                      "featureId": {
                        "type": "string",
                        "description": "The identifier for the feature flag"
                      },
                      "value": {
                        "type": "object",
                        "additionalProperties": {},
                        "description": "Optional extra params for the feature (this should be validated against a schema)"
                      }
                    },
                    "required": [
                      "featureId",
                      "value"
                    ],
                    "description": "A single feature configuration"
                  }
                },
                "required": [
                  "slug",
                  "ratio",
                  "feature"
                ]
              }
            },
            {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "slug": {
                    "type": "string",
                    "description": "Identifier for the branch"
                  },
                  "ratio": {
                    "type": "integer",
                    "description": "Relative ratio of population for the branch (e.g. if branch A=1 and branch B=3, branch A would get 25% of the population)",
                    "default": 1
                  },
                  "feature": {
                    "type": "object",
                    "properties": {
                      "featureId": {
                        "type": "string",
                        "const": "unused-feature-id-for-legacy-support"
                      },
                      "enabled": {
                        "type": "boolean",
                        "const": false
                      },
                      "value": {
                        "type": "object",
                        "additionalProperties": {}
                      }
                    },
                    "required": [
                      "featureId",
                      "enabled",
                      "value"
                    ],
                    "description": "The feature key must be provided with valid values to prevent crashes if the DTO is encountered by Desktop clients earlier than version 95."
                  },
                  "features": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "featureId": {
                          "type": "string",
                          "description": "The identifier for the feature flag"
                        },
                        "value": {
                          "type": "object",
                          "additionalProperties": {},
                          "description": "Optional extra params for the feature (this should be validated against a schema)"
                        }
                      },
                      "required": [
                        "featureId",
                        "value"
                      ]
                    },
                    "description": "An array of feature configurations"
                  }
                },
                "required": [
                  "slug",
                  "ratio",
                  "feature",
                  "features"
                ]
              }
            },
            {
              "type": "array",
              "items": {
                "type": "object",
                "properties": {
                  "slug": {
                    "type": "string",
                    "description": "Identifier for the branch"
                  },
                  "ratio": {
                    "type": "integer",
                    "description": "Relative ratio of population for the branch (e.g. if branch A=1 and branch B=3, branch A would get 25% of the population)",
                    "default": 1
                  },
                  "features": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "featureId": {
                          "type": "string",
                          "description": "The identifier for the feature flag"
                        },
                        "value": {
                          "type": "object",
                          "additionalProperties": {},
                          "description": "Optional extra params for the feature (this should be validated against a schema)"
                        }
                      },
                      "required": [
                        "featureId",
                        "value"
                      ]
                    },
                    "description": "An array of feature configurations"
                  }
                },
                "required": [
                  "slug",
                  "ratio",
                  "features"
                ]
              }
            }
          ],
          "description": "Branch configuration for the experiment"
        },
        "targeting": {
          "type": [
            "string",
            "null"
          ],
          "description": "JEXL expression used to filter experiments based on locale, geo, etc."
        },
        "startDate": {
          "type": [
            "string",
            "null"
          ],
          "description": "Actual publish date of the experiment Note that this value is expected to be null in Remote Settings.",
          "format": "date"
        },
        "enrollmentEndDate": {
          "type": [
            "string",
            "null"
          ],
          "description": "Actual enrollment end date of the experiment. Note that this value is expected to be null in Remote Settings.",
          "format": "date"
        },
        "endDate": {
          "type": [
            "string",
            "null"
          ],
          "description": "Actual end date of the experiment. Note that this value is expected to be null in Remote Settings.",
          "format": "date"
        },
        "proposedDuration": {
          "type": "integer",
          "description": "Duration of the experiment from the start date in days. Note that this property is only used during the analysis phase (not by the SDK)"
        },
        "proposedEnrollment": {
          "type": "integer",
          "description": "This represents the number of days that we expect to enroll new users. Note that this property is only used during the analysis phase (not by the SDK)"
        },
        "referenceBranch": {
          "type": [
            "string",
            "null"
          ],
          "description": "The slug of the reference branch (that is, which branch we consider \"control\")"
        },
        "featureValidationOptOut": {
          "type": "boolean",
          "description": "Opt out of feature schema validation. Only supported on desktop."
        },
        "localizations": {
          "anyOf": [
            {
              "type": "object",
              "additionalProperties": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            },
            {
              "type": "null"
            }
          ],
          "description": "Per-locale localization substitutions.\n\nThe top level key is the locale (e.g., \"en-US\" or \"fr\"). Each entry is a mapping of string IDs to their localized equivalents.\n\nOnly supported on desktop."
        },
        "locales": {
          "anyOf": [
            {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            {
              "type": "null"
            }
          ],
          "description": "The list of locale codes (e.g., \"en-US\" or \"fr\") that this experiment is targeting.\n\nIf null, all locales are targeted."
        },
        "publishedDate": {
          "type": [
            "string",
            "null"
          ],
          "description": "The date that this experiment was first published to Remote Settings. Note that this value is expected to be present in Remote Settings.\n\nIf null, it has not yet been published.",
          "format": "date-time"
        }
      },
      "required": [
        "schemaVersion",
        "slug",
        "id",
        "appName",
        "appId",
        "channel",
        "userFacingName",
        "userFacingDescription",
        "isEnrollmentPaused",
        "bucketConfig",
        "branches",
        "startDate",
        "endDate",
        "proposedEnrollment",
        "referenceBranch"
      ],
      "description": "The experiment definition accessible to: 1. The Nimbus SDK via Remote Settings 2. Jetstream via the Experimenter API"
    }
  }
}
PK
!<s*Q>chrome/toolkit/res/nimbus/schemas/PrefFlipsFeature.schema.json{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "properties": {
    "prefs": {
      "description": "The prefs to set",
      "type": "object",
      "patternProperties": {
        ".*": {
          "type": "object",
          "properties": {
            "branch": {
              "description": "The branch the pref should be set on.",
              "type": "string",
              "enum": ["user", "default"]
            },
            "value": {
              "description": "The value of the pref. Set to null to clear prefs.",
              "type": ["string", "integer", "boolean", "null"]
            }
          },
          "required": ["branch", "value"],
          "allOf": [
            {
              "description": "Prevent clearing the default branch",
              "if": {
                "properties": {
                  "branch": {
                    "const": "default"
                  }
                }
              },
              "then": {
                "properties": {
                  "value": {
                    "type": ["string", "integer", "boolean"]
                  }
                }
              }
            }
          ]
        }
      }
    }
  },
  "required": ["prefs"],
  "additionalProperties": false
}
PK
!<�� "$"$,chrome/toolkit/res/normandy/Normandy.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Log } from "resource://gre/modules/Log.sys.mjs";
import { setTimeout } from "resource://gre/modules/Timer.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonRollouts: "resource://normandy/lib/AddonRollouts.sys.mjs",
  AddonStudies: "resource://normandy/lib/AddonStudies.sys.mjs",
  CleanupManager: "resource://normandy/lib/CleanupManager.sys.mjs",
  ExperimentManager: "resource://nimbus/lib/ExperimentManager.sys.mjs",
  LogManager: "resource://normandy/lib/LogManager.sys.mjs",
  NormandyMigrations: "resource://normandy/NormandyMigrations.sys.mjs",
  PreferenceExperiments:
    "resource://normandy/lib/PreferenceExperiments.sys.mjs",
  PreferenceRollouts: "resource://normandy/lib/PreferenceRollouts.sys.mjs",
  RecipeRunner: "resource://normandy/lib/RecipeRunner.sys.mjs",
  RemoteSettingsExperimentLoader:
    "resource://nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs",
  ShieldPreferences: "resource://normandy/lib/ShieldPreferences.sys.mjs",
  TelemetryEvents: "resource://normandy/lib/TelemetryEvents.sys.mjs",
});

const UI_AVAILABLE_NOTIFICATION = "sessionstore-windows-restored";
const BOOTSTRAP_LOGGER_NAME = "app.normandy.bootstrap";
const SHIELD_INIT_NOTIFICATION = "shield-init-complete";

const STARTUP_EXPERIMENT_PREFS_BRANCH = "app.normandy.startupExperimentPrefs.";
const STARTUP_ROLLOUT_PREFS_BRANCH = "app.normandy.startupRolloutPrefs.";
const PREF_LOGGING_LEVEL = "app.normandy.logging.level";

// Logging
const log = Log.repository.getLogger(BOOTSTRAP_LOGGER_NAME);
log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
log.level = Services.prefs.getIntPref(PREF_LOGGING_LEVEL, Log.Level.Warn);

export var Normandy = {
  studyPrefsChanged: {},
  rolloutPrefsChanged: {},
  defaultPrefsHaveBeenApplied: Promise.withResolvers(),
  uiAvailableNotificationObserved: Promise.withResolvers(),

  /** Initialization that needs to happen before the first paint on startup. */
  async init({ runAsync = true } = {}) {
    // It is important to register the listener for the UI before the first
    // await, to avoid missing it.
    Services.obs.addObserver(this, UI_AVAILABLE_NOTIFICATION);

    // It is important this happens before the first `await`. Note that this
    // also happens before migrations are applied.
    this.rolloutPrefsChanged = this.applyStartupPrefs(
      STARTUP_ROLLOUT_PREFS_BRANCH
    );
    this.studyPrefsChanged = this.applyStartupPrefs(
      STARTUP_EXPERIMENT_PREFS_BRANCH
    );
    this.defaultPrefsHaveBeenApplied.resolve();

    await lazy.NormandyMigrations.applyAll();

    // Wait for the UI to be ready, or time out after 5 minutes.
    if (runAsync) {
      await Promise.race([
        this.uiAvailableNotificationObserved.promise,
        new Promise(resolve => setTimeout(resolve, 5 * 60 * 1000)),
      ]);
    }

    // Remove observer for UI notifications. It will error if the notification
    // was already removed, which is fine.
    try {
      Services.obs.removeObserver(this, UI_AVAILABLE_NOTIFICATION);
    } catch (e) {}

    await this.finishInit();
  },

  async observe(subject, topic) {
    if (topic === UI_AVAILABLE_NOTIFICATION) {
      Services.obs.removeObserver(this, UI_AVAILABLE_NOTIFICATION);
      this.uiAvailableNotificationObserved.resolve();
    }
  },

  async finishInit() {
    try {
      lazy.TelemetryEvents.init();
    } catch (err) {
      log.error("Failed to initialize telemetry events:", err);
    }

    await lazy.PreferenceRollouts.recordOriginalValues(
      this.rolloutPrefsChanged
    );
    await lazy.PreferenceExperiments.recordOriginalValues(
      this.studyPrefsChanged
    );

    // Setup logging and listen for changes to logging prefs
    lazy.LogManager.configure(
      Services.prefs.getIntPref(PREF_LOGGING_LEVEL, Log.Level.Warn)
    );
    Services.prefs.addObserver(PREF_LOGGING_LEVEL, lazy.LogManager.configure);
    lazy.CleanupManager.addCleanupHandler(() =>
      Services.prefs.removeObserver(
        PREF_LOGGING_LEVEL,
        lazy.LogManager.configure
      )
    );

    try {
      await lazy.ExperimentManager.onStartup();
    } catch (err) {
      log.error("Failed to initialize ExperimentManager:", err);
    }

    try {
      await lazy.RemoteSettingsExperimentLoader.init();
    } catch (err) {
      log.error("Failed to initialize RemoteSettingsExperimentLoader:", err);
    }

    try {
      await lazy.AddonStudies.init();
    } catch (err) {
      log.error("Failed to initialize addon studies:", err);
    }

    try {
      await lazy.PreferenceRollouts.init();
    } catch (err) {
      log.error("Failed to initialize preference rollouts:", err);
    }

    try {
      await lazy.AddonRollouts.init();
    } catch (err) {
      log.error("Failed to initialize addon rollouts:", err);
    }

    try {
      await lazy.PreferenceExperiments.init();
    } catch (err) {
      log.error("Failed to initialize preference experiments:", err);
    }

    try {
      lazy.ShieldPreferences.init();
    } catch (err) {
      log.error("Failed to initialize preferences UI:", err);
    }

    await lazy.RecipeRunner.init();
    Services.obs.notifyObservers(null, SHIELD_INIT_NOTIFICATION);
  },

  async uninit() {
    await lazy.CleanupManager.cleanup();
    // Note that Service.pref.removeObserver and Service.obs.removeObserver have
    // oppositely ordered parameters.
    Services.prefs.removeObserver(
      PREF_LOGGING_LEVEL,
      lazy.LogManager.configure
    );

    try {
      Services.obs.removeObserver(this, UI_AVAILABLE_NOTIFICATION);
    } catch (e) {
      // topic must have already been removed or never added
    }
  },

  /**
   * Copy a preference subtree from one branch to another, being careful about
   * types, and return the values the target branch originally had. Prefs will
   * be read from the user branch and applied to the default branch.
   *
   * @param sourcePrefix
   *   The pref prefix to read prefs from.
   * @returns
   *   The original values that each pref had on the default branch.
   */
  applyStartupPrefs(sourcePrefix) {
    // Note that this is called before Normandy's migrations are applied. This
    // currently has no effect, but future changes should be careful to be
    // backwards compatible.
    const originalValues = {};
    const sourceBranch = Services.prefs.getBranch(sourcePrefix);
    const targetBranch = Services.prefs.getDefaultBranch("");

    for (const prefName of sourceBranch.getChildList("")) {
      const sourcePrefType = sourceBranch.getPrefType(prefName);
      const targetPrefType = targetBranch.getPrefType(prefName);

      if (
        targetPrefType !== Services.prefs.PREF_INVALID &&
        targetPrefType !== sourcePrefType
      ) {
        console.error(
          new Error(
            `Error setting startup pref ${prefName}; pref type does not match.`
          )
        );
        continue;
      }

      // record the value of the default branch before setting it
      try {
        switch (targetPrefType) {
          case Services.prefs.PREF_STRING: {
            originalValues[prefName] = targetBranch.getCharPref(prefName);
            break;
          }
          case Services.prefs.PREF_INT: {
            originalValues[prefName] = targetBranch.getIntPref(prefName);
            break;
          }
          case Services.prefs.PREF_BOOL: {
            originalValues[prefName] = targetBranch.getBoolPref(prefName);
            break;
          }
          case Services.prefs.PREF_INVALID: {
            originalValues[prefName] = null;
            break;
          }
          default: {
            // This should never happen
            log.error(
              `Error getting startup pref ${prefName}; unknown value type ${sourcePrefType}.`
            );
          }
        }
      } catch (e) {
        if (e.result === Cr.NS_ERROR_UNEXPECTED) {
          // There is a value for the pref on the user branch but not on the default branch. This is ok.
          originalValues[prefName] = null;
        } else {
          // Unexpected error, report it and move on
          console.error(e);
          continue;
        }
      }

      // now set the new default value
      switch (sourcePrefType) {
        case Services.prefs.PREF_STRING: {
          targetBranch.setCharPref(
            prefName,
            sourceBranch.getCharPref(prefName)
          );
          break;
        }
        case Services.prefs.PREF_INT: {
          targetBranch.setIntPref(prefName, sourceBranch.getIntPref(prefName));
          break;
        }
        case Services.prefs.PREF_BOOL: {
          targetBranch.setBoolPref(
            prefName,
            sourceBranch.getBoolPref(prefName)
          );
          break;
        }
        default: {
          // This should never happen.
          console.error(
            new Error(
              `Error getting startup pref ${prefName}; unexpected value type ${sourcePrefType}.`
            )
          );
        }
      }
    }

    return originalValues;
  },
};
PK
!<H=B�BB6chrome/toolkit/res/normandy/NormandyMigrations.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Log } from "resource://gre/modules/Log.sys.mjs";
import { AddonStudies } from "resource://normandy/lib/AddonStudies.sys.mjs";
import { PreferenceExperiments } from "resource://normandy/lib/PreferenceExperiments.sys.mjs";
import { RecipeRunner } from "resource://normandy/lib/RecipeRunner.sys.mjs";

const BOOTSTRAP_LOGGER_NAME = "app.normandy.bootstrap";

const PREF_PREFIX = "app.normandy";
const LEGACY_PREF_PREFIX = "extensions.shield-recipe-client";
const PREF_LOGGING_LEVEL = "app.normandy.logging.level";
const PREF_MIGRATIONS_APPLIED = "app.normandy.migrationsApplied";
const PREF_OPTOUTSTUDIES_ENABLED = "app.shield.optoutstudies.enabled";

// Logging
const log = Log.repository.getLogger(BOOTSTRAP_LOGGER_NAME);
log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
log.level = Services.prefs.getIntPref(PREF_LOGGING_LEVEL, Log.Level.Warn);

export const NormandyMigrations = {
  async applyAll() {
    let migrationsApplied = Services.prefs.getIntPref(
      PREF_MIGRATIONS_APPLIED,
      0
    );

    for (let i = migrationsApplied; i < this.migrations.length; i++) {
      await this.applyOne(i);
      migrationsApplied++;
      Services.prefs.setIntPref(PREF_MIGRATIONS_APPLIED, migrationsApplied);
    }
  },

  async applyOne(id) {
    const migration = this.migrations[id];
    log.debug(`Running Normandy migration ${migration.name}`);
    await migration();
  },

  migrations: [
    migrateShieldPrefs,
    migrateStudiesEnabledWithoutHealthReporting,
    AddonStudies.migrations
      .migration01AddonStudyFieldsToSlugAndUserFacingFields,
    PreferenceExperiments.migrations.migration01MoveExperiments,
    PreferenceExperiments.migrations.migration02MultiPreference,
    PreferenceExperiments.migrations.migration03AddActionName,
    PreferenceExperiments.migrations.migration04RenameNameToSlug,
    RecipeRunner.migrations.migration01RemoveOldRecipesCollection,
    AddonStudies.migrations.migration02RemoveOldAddonStudyAction,
    migrateRemoveLastBuildIdPref,
    PreferenceExperiments.migrations.migration05RemoveOldAction,
    PreferenceExperiments.migrations.migration06TrackOverriddenPrefs,
  ],
};

function migrateShieldPrefs() {
  const legacyBranch = Services.prefs.getBranch(LEGACY_PREF_PREFIX + ".");
  const newBranch = Services.prefs.getBranch(PREF_PREFIX + ".");

  for (const prefName of legacyBranch.getChildList("")) {
    const legacyPrefType = legacyBranch.getPrefType(prefName);
    const newPrefType = newBranch.getPrefType(prefName);

    // If new preference exists and is not the same as the legacy pref, skip it
    if (
      newPrefType !== Services.prefs.PREF_INVALID &&
      newPrefType !== legacyPrefType
    ) {
      log.error(
        `Error migrating normandy pref ${prefName}; pref type does not match.`
      );
      continue;
    }

    // Now move the value over. If it matches the default, this will be a no-op
    switch (legacyPrefType) {
      case Services.prefs.PREF_STRING:
        newBranch.setCharPref(prefName, legacyBranch.getCharPref(prefName));
        break;

      case Services.prefs.PREF_INT:
        newBranch.setIntPref(prefName, legacyBranch.getIntPref(prefName));
        break;

      case Services.prefs.PREF_BOOL:
        newBranch.setBoolPref(prefName, legacyBranch.getBoolPref(prefName));
        break;

      case Services.prefs.PREF_INVALID:
        // This should never happen.
        log.error(
          `Error migrating pref ${prefName}; pref type is invalid (${legacyPrefType}).`
        );
        break;

      default:
        // This should never happen either.
        log.error(
          `Error getting startup pref ${prefName}; unknown value type ${legacyPrefType}.`
        );
    }

    legacyBranch.clearUserPref(prefName);
  }
}

/**
 * Migration to handle moving the studies opt-out pref from under the health
 * report upload pref to an independent pref.
 *
 * If the pref was set to true and the health report upload pref was set
 * to true then the pref should stay true. Otherwise set it to false.
 */
function migrateStudiesEnabledWithoutHealthReporting() {
  const optOutStudiesEnabled = Services.prefs.getBoolPref(
    PREF_OPTOUTSTUDIES_ENABLED,
    false
  );
  const healthReportUploadEnabled = Services.prefs.getBoolPref(
    "datareporting.healthreport.uploadEnabled",
    false
  );
  Services.prefs.setBoolPref(
    PREF_OPTOUTSTUDIES_ENABLED,
    optOutStudiesEnabled && healthReportUploadEnabled
  );
}

/**
 * Tracking last build ID is now done by comparing Services.appinfo.appBuildID
 * and Services.appinfo.lastAppBuildID. Remove the manual tracking.
 */
function migrateRemoveLastBuildIdPref() {
  Services.prefs.clearUserPref("app.normandy.last_seen_buildid");
}
PK
!<��g	g	?chrome/toolkit/res/normandy/actions/AddonRollbackAction.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { BaseAction } from "resource://normandy/actions/BaseAction.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ActionSchemas: "resource://normandy/actions/schemas/index.sys.mjs",
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  AddonRollouts: "resource://normandy/lib/AddonRollouts.sys.mjs",
  TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
  TelemetryEvents: "resource://normandy/lib/TelemetryEvents.sys.mjs",
});

export class AddonRollbackAction extends BaseAction {
  get schema() {
    return lazy.ActionSchemas["addon-rollback"];
  }

  async _run(recipe) {
    const { rolloutSlug } = recipe.arguments;
    const rollout = await lazy.AddonRollouts.get(rolloutSlug);

    if (!rollout) {
      this.log.debug(`Rollback ${rolloutSlug} not applicable, skipping`);
      return;
    }

    switch (rollout.state) {
      case lazy.AddonRollouts.STATE_ACTIVE: {
        await lazy.AddonRollouts.update({
          ...rollout,
          state: lazy.AddonRollouts.STATE_ROLLED_BACK,
        });

        const addon = await lazy.AddonManager.getAddonByID(rollout.addonId);
        if (addon) {
          try {
            await addon.uninstall();
          } catch (err) {
            lazy.TelemetryEvents.sendEvent(
              "unenrollFailed",
              "addon_rollback",
              rolloutSlug,
              {
                reason: "uninstall-failed",
              }
            );
            throw err;
          }
        } else {
          this.log.warn(
            `Could not uninstall addon ${rollout.addonId} for rollback ${rolloutSlug}: it is not installed.`
          );
        }

        lazy.TelemetryEvents.sendEvent(
          "unenroll",
          "addon_rollback",
          rolloutSlug,
          {
            reason: "rollback",
          }
        );
        lazy.TelemetryEnvironment.setExperimentInactive(rolloutSlug);
        break;
      }

      case lazy.AddonRollouts.STATE_ROLLED_BACK: {
        return; // Do nothing
      }

      default: {
        throw new Error(
          `Unexpected state when rolling back ${rolloutSlug}: ${rollout.state}`
        );
      }
    }
  }
}
PK
!<�kD|��>chrome/toolkit/res/normandy/actions/AddonRolloutAction.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { BaseAction } from "resource://normandy/actions/BaseAction.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ActionSchemas: "resource://normandy/actions/schemas/index.sys.mjs",
  AddonRollouts: "resource://normandy/lib/AddonRollouts.sys.mjs",
  NormandyAddonManager: "resource://normandy/lib/NormandyAddonManager.sys.mjs",
  NormandyApi: "resource://normandy/lib/NormandyApi.sys.mjs",
  TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
  TelemetryEvents: "resource://normandy/lib/TelemetryEvents.sys.mjs",
});

class AddonRolloutError extends Error {
  /**
   * @param {string} slug
   * @param {object} extra Extra details to include when reporting the error to telemetry.
   * @param {string} extra.reason The specific reason for the failure.
   */
  constructor(slug, extra) {
    let message;
    let { reason } = extra;
    switch (reason) {
      case "conflict": {
        message = "an existing rollout already exists for this add-on";
        break;
      }
      case "addon-id-changed": {
        message = "upgrade add-on ID does not match installed add-on ID";
        break;
      }
      case "upgrade-required": {
        message = "a newer version of the add-on is already installed";
        break;
      }
      case "download-failure": {
        message = "the add-on failed to download";
        break;
      }
      case "metadata-mismatch": {
        message = "the server metadata does not match the downloaded add-on";
        break;
      }
      case "install-failure": {
        message = "the add-on failed to install";
        break;
      }
      default: {
        throw new Error(`Unexpected AddonRolloutError reason: ${reason}`);
      }
    }
    super(`Cannot install add-on for rollout (${slug}): ${message}.`);
    this.slug = slug;
    this.extra = extra;
  }
}

export class AddonRolloutAction extends BaseAction {
  get schema() {
    return lazy.ActionSchemas["addon-rollout"];
  }

  async _run(recipe) {
    const { extensionApiId, slug } = recipe.arguments;

    const existingRollout = await lazy.AddonRollouts.get(slug);
    const eventName = existingRollout ? "update" : "enroll";
    const extensionDetails = await lazy.NormandyApi.fetchExtensionDetails(
      extensionApiId
    );

    // Check if the existing rollout matches the current rollout
    if (
      existingRollout &&
      existingRollout.addonId === extensionDetails.extension_id
    ) {
      const versionCompare = Services.vc.compare(
        existingRollout.addonVersion,
        extensionDetails.version
      );

      if (versionCompare === 0) {
        return; // Do nothing
      }
    }

    const createError = (reason, extra = {}) => {
      return new AddonRolloutError(slug, {
        ...extra,
        reason,
      });
    };

    // Check for a conflict (addon already installed by another rollout)
    const activeRollouts = await lazy.AddonRollouts.getAllActive();
    const conflictingRollout = activeRollouts.find(
      rollout =>
        rollout.slug !== slug &&
        rollout.addonId === extensionDetails.extension_id
    );
    if (conflictingRollout) {
      const conflictError = createError("conflict", {
        addonId: conflictingRollout.addonId,
        conflictingSlug: conflictingRollout.slug,
      });
      this.reportError(conflictError, "enrollFailed");
      throw conflictError;
    }

    const onInstallStarted = (install, installDeferred) => {
      const existingAddon = install.existingAddon;

      if (existingRollout && existingRollout.addonId !== install.addon.id) {
        installDeferred.reject(createError("addon-id-changed"));
        return false; // cancel the upgrade, the add-on ID has changed
      }

      if (
        existingAddon &&
        Services.vc.compare(existingAddon.version, install.addon.version) > 0
      ) {
        installDeferred.reject(createError("upgrade-required"));
        return false; // cancel the installation, must be an upgrade
      }

      return true;
    };

    const applyNormandyChanges = async install => {
      const details = {
        addonId: install.addon.id,
        addonVersion: install.addon.version,
        extensionApiId,
        xpiUrl: extensionDetails.xpi,
        xpiHash: extensionDetails.hash,
        xpiHashAlgorithm: extensionDetails.hash_algorithm,
      };

      if (existingRollout) {
        await lazy.AddonRollouts.update({
          ...existingRollout,
          ...details,
        });
      } else {
        await lazy.AddonRollouts.add({
          recipeId: recipe.id,
          state: lazy.AddonRollouts.STATE_ACTIVE,
          slug,
          ...details,
        });
      }
    };

    const undoNormandyChanges = async () => {
      if (existingRollout) {
        await lazy.AddonRollouts.update(existingRollout);
      } else {
        await lazy.AddonRollouts.delete(recipe.id);
      }
    };

    const [installedId, installedVersion] =
      await lazy.NormandyAddonManager.downloadAndInstall({
        createError,
        extensionDetails,
        applyNormandyChanges,
        undoNormandyChanges,
        onInstallStarted,
        reportError: error => this.reportError(error, `${eventName}Failed`),
      });

    if (existingRollout) {
      this.log.debug(`Updated addon rollout ${slug}`);
    } else {
      this.log.debug(`Enrolled in addon rollout ${slug}`);
      lazy.TelemetryEnvironment.setExperimentActive(
        slug,
        lazy.AddonRollouts.STATE_ACTIVE,
        {
          type: "normandy-addonrollout",
        }
      );
    }

    // All done, report success to Telemetry
    lazy.TelemetryEvents.sendEvent(eventName, "addon_rollout", slug, {
      addonId: installedId,
      addonVersion: installedVersion,
    });
  }

  reportError(error, eventName) {
    if (error instanceof AddonRolloutError) {
      // One of our known errors. Report it nicely to telemetry
      lazy.TelemetryEvents.sendEvent(
        eventName,
        "addon_rollout",
        error.slug,
        error.extra
      );
    } else {
      /*
       * Some unknown error. Add some helpful details, and report it to
       * telemetry. The actual stack trace and error message could possibly
       * contain PII, so we don't include them here. Instead include some
       * information that should still be helpful, and is less likely to be
       * unsafe.
       */
      const safeErrorMessage = `${error.fileName}:${error.lineNumber}:${error.columnNumber} ${error.name}`;
      lazy.TelemetryEvents.sendEvent(eventName, "addon_rollout", error.slug, {
        reason: safeErrorMessage.slice(0, 80), // max length is 80 chars
      });
    }
  }
}
PK
!<��Vu)u)6chrome/toolkit/res/normandy/actions/BaseAction.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Uptake } from "resource://normandy/lib/Uptake.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  JsonSchemaValidator:
    "resource://gre/modules/components-utils/JsonSchemaValidator.sys.mjs",
  LogManager: "resource://normandy/lib/LogManager.sys.mjs",
});

/**
 * Base class for local actions.
 *
 * This should be subclassed. Subclasses must implement _run() for
 * per-recipe behavior, and may implement _preExecution and _finalize
 * for actions to be taken once before and after recipes are run.
 *
 * Other methods should be overridden with care, to maintain the life
 * cycle events and error reporting implemented by this class.
 */
export class BaseAction {
  constructor() {
    this.state = BaseAction.STATE_PREPARING;
    this.log = lazy.LogManager.getLogger(`action.${this.name}`);
    this.lastError = null;
  }

  /**
   * Be sure to run the _preExecution() hook once during its
   * lifecycle.
   *
   * This is not intended for overriding by subclasses.
   */
  _ensurePreExecution() {
    if (this.state !== BaseAction.STATE_PREPARING) {
      return;
    }

    try {
      this._preExecution();
      // if _preExecution changed the state, don't overwrite it
      if (this.state === BaseAction.STATE_PREPARING) {
        this.state = BaseAction.STATE_READY;
      }
    } catch (err) {
      // Sometimes err.message is editable. If it is, add helpful details.
      // Otherwise log the helpful details and move on.
      try {
        err.message = `Could not initialize action ${this.name}: ${err.message}`;
      } catch (_e) {
        this.log.error(
          `Could not initialize action ${this.name}, error follows.`
        );
      }
      this.fail(err);
    }
  }

  get schema() {
    return {
      type: "object",
      properties: {},
    };
  }

  /**
   * Disable the action for a non-error reason, such as the user opting out of
   * this type of action.
   */
  disable() {
    this.state = BaseAction.STATE_DISABLED;
  }

  fail(err) {
    switch (this.state) {
      case BaseAction.STATE_PREPARING: {
        Uptake.reportAction(this.name, Uptake.ACTION_PRE_EXECUTION_ERROR);
        break;
      }
      default: {
        console.error(new Error("BaseAction.fail() called at unexpected time"));
      }
    }
    this.state = BaseAction.STATE_FAILED;
    this.lastError = err;
    console.error(err);
  }

  // Gets the name of the action. Does not necessarily match the
  // server slug for the action.
  get name() {
    return this.constructor.name;
  }

  /**
   * Action specific pre-execution behavior should be implemented
   * here. It will be called once per execution session.
   */
  _preExecution() {
    // Does nothing, may be overridden
  }

  validateArguments(args, schema = this.schema) {
    let { valid, parsedValue: validated } = lazy.JsonSchemaValidator.validate(
      args,
      schema,
      {
        allowAdditionalProperties: true,
      }
    );
    if (!valid) {
      throw new Error(
        `Arguments do not match schema. arguments:\n${JSON.stringify(args)}\n` +
          `schema:\n${JSON.stringify(schema)}`
      );
    }
    return validated;
  }

  /**
   * Execute the per-recipe behavior of this action for a given
   * recipe.  Reports Uptake telemetry for the execution of the recipe.
   *
   * @param {Recipe} recipe
   * @param {BaseAction.suitability} suitability
   * @throws If this action has already been finalized.
   */
  async processRecipe(recipe, suitability) {
    if (!BaseAction.suitabilitySet.has(suitability)) {
      throw new Error(`Unknown recipe status ${suitability}`);
    }

    this._ensurePreExecution();

    if (this.state === BaseAction.STATE_FINALIZED) {
      throw new Error("Action has already been finalized");
    }

    if (this.state !== BaseAction.STATE_READY) {
      Uptake.reportRecipe(recipe, Uptake.RECIPE_ACTION_DISABLED);
      this.log.warn(
        `Skipping recipe ${recipe.name} because ${this.name} was disabled during preExecution.`
      );
      return;
    }

    let uptakeResult = BaseAction.suitabilityToUptakeStatus[suitability];
    if (!uptakeResult) {
      throw new Error(
        `Coding error, no uptake status for suitability ${suitability}`
      );
    }

    // If capabilties don't match, we can't even be sure that the arguments
    // should be valid. In that case don't try to validate them.
    if (suitability !== BaseAction.suitability.CAPABILITIES_MISMATCH) {
      try {
        recipe.arguments = this.validateArguments(recipe.arguments);
      } catch (error) {
        console.error(error);
        uptakeResult = Uptake.RECIPE_EXECUTION_ERROR;
        suitability = BaseAction.suitability.ARGUMENTS_INVALID;
      }
    }

    try {
      await this._processRecipe(recipe, suitability);
    } catch (err) {
      console.error(err);
      uptakeResult = Uptake.RECIPE_EXECUTION_ERROR;
    }
    Uptake.reportRecipe(recipe, uptakeResult);
  }

  /**
   * Action specific recipe behavior may be implemented here. It will be
   * executed once for each recipe that applies to this client.
   * The recipe will be passed as a parameter.
   *
   * @param {Recipe} recipe
   */
  async _run() {
    throw new Error("Not implemented");
  }

  /**
   * Action specific recipe behavior should be implemented here. It will be
   * executed once for every recipe currently published. The suitability of the
   * recipe will be passed, it will be one of the constants from
   * `BaseAction.suitability`.
   *
   * By default, this calls `_run()` for recipes with `status == FILTER_MATCH`,
   * and does nothing for all other recipes. It is invalid for an action to
   * override both `_run` and `_processRecipe`.
   *
   * @param {Recipe} recipe
   * @param {RecipeSuitability} suitability
   */
  async _processRecipe(recipe, suitability) {
    if (!suitability) {
      throw new Error("Suitability is undefined:", suitability);
    }
    if (suitability == BaseAction.suitability.FILTER_MATCH) {
      await this._run(recipe);
    }
  }

  /**
   * Finish an execution session. After this method is called, no
   * other methods may be called on this method, and all relevant
   * recipes will be assumed to have been seen.
   */
  async finalize(options) {
    // It's possible that no recipes used this action, so processRecipe()
    // was never called. In that case, we should ensure that we call
    // _preExecute() here.
    this._ensurePreExecution();

    let status;
    switch (this.state) {
      case BaseAction.STATE_FINALIZED: {
        throw new Error("Action has already been finalized");
      }
      case BaseAction.STATE_READY: {
        try {
          await this._finalize(options);
          status = Uptake.ACTION_SUCCESS;
        } catch (err) {
          status = Uptake.ACTION_POST_EXECUTION_ERROR;
          // Sometimes Error.message can be updated in place. This gives better messages when debugging errors.
          try {
            err.message = `Could not run postExecution hook for ${this.name}: ${err.message}`;
          } catch (err) {
            // Sometimes Error.message cannot be updated. Log a warning, and move on.
            this.log.debug(`Could not run postExecution hook for ${this.name}`);
          }

          this.lastError = err;
          console.error(err);
        }
        break;
      }
      case BaseAction.STATE_DISABLED: {
        this.log.debug(
          `Skipping post-execution hook for ${this.name} because it is disabled.`
        );
        status = Uptake.ACTION_SUCCESS;
        break;
      }
      case BaseAction.STATE_FAILED: {
        this.log.debug(
          `Skipping post-execution hook for ${this.name} because it failed during pre-execution.`
        );
        // Don't report a status. A status should have already been reported by this.fail().
        break;
      }
      default: {
        throw new Error(`Unexpected state during finalize: ${this.state}`);
      }
    }

    this.state = BaseAction.STATE_FINALIZED;
    if (status) {
      Uptake.reportAction(this.name, status);
    }
  }

  /**
   * Action specific post-execution behavior should be implemented
   * here. It will be executed once after all recipes have been
   * processed.
   */
  async _finalize(_options = {}) {
    // Does nothing, may be overridden
  }
}

BaseAction.STATE_PREPARING = "ACTION_PREPARING";
BaseAction.STATE_READY = "ACTION_READY";
BaseAction.STATE_DISABLED = "ACTION_DISABLED";
BaseAction.STATE_FAILED = "ACTION_FAILED";
BaseAction.STATE_FINALIZED = "ACTION_FINALIZED";

// Make sure to update the docs in ../docs/suitabilities.rst when changing this.
BaseAction.suitability = {
  /**
   * The recipe's signature is not valid. If any action is taken this recipe
   * should be treated with extreme suspicion.
   */
  SIGNATURE_ERROR: "RECIPE_SUITABILITY_SIGNATURE_ERROR",

  /**
   * The recipe requires capabilities that this recipe runner does not have.
   * Use caution when interacting with this recipe, as it may not match the
   * expected schema.
   */
  CAPABILITIES_MISMATCH: "RECIPE_SUITABILITY_CAPABILITIES_MISMATCH",

  /**
   * The recipe is suitable to execute in this client.
   */
  FILTER_MATCH: "RECIPE_SUITABILITY_FILTER_MATCH",

  /**
   * This client does not match the recipe's filter, but it is otherwise a
   * suitable recipe.
   */
  FILTER_MISMATCH: "RECIPE_SUITABILITY_FILTER_MISMATCH",

  /**
   * There was an error while evaluating the filter. It is unknown if this
   * client matches this filter. This may be temporary, due to network errors,
   * or permanent due to syntax errors.
   */
  FILTER_ERROR: "RECIPE_SUITABILITY_FILTER_ERROR",

  /**
   * The arguments of the recipe do not match the expected schema for the named
   * action.
   */
  ARGUMENTS_INVALID: "RECIPE_SUITABILITY_ARGUMENTS_INVALID",
};

BaseAction.suitabilitySet = new Set(Object.values(BaseAction.suitability));

BaseAction.suitabilityToUptakeStatus = {
  [BaseAction.suitability.SIGNATURE_ERROR]: Uptake.RECIPE_INVALID_SIGNATURE,
  [BaseAction.suitability.CAPABILITIES_MISMATCH]:
    Uptake.RECIPE_INCOMPATIBLE_CAPABILITIES,
  [BaseAction.suitability.FILTER_MATCH]: Uptake.RECIPE_SUCCESS,
  [BaseAction.suitability.FILTER_MISMATCH]: Uptake.RECIPE_DIDNT_MATCH_FILTER,
  [BaseAction.suitability.FILTER_ERROR]: Uptake.RECIPE_FILTER_BROKEN,
  [BaseAction.suitability.ARGUMENTS_INVALID]: Uptake.RECIPE_ARGUMENTS_INVALID,
};
PK
!<��HGG;chrome/toolkit/res/normandy/actions/BaseStudyAction.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { BaseAction } from "resource://normandy/actions/BaseAction.sys.mjs";

const OPT_OUT_STUDIES_ENABLED_PREF = "app.shield.optoutstudies.enabled";

/**
 * Base class for local study actions.
 *
 * This should be subclassed. Subclasses must implement _run() for
 * per-recipe behavior, and may implement _finalize for actions to be
 * taken once after recipes are run.
 *
 * For actions that need to be taken once before recipes are run
 * _preExecution may be overriden but the overridden method must
 * call the parent method to ensure the appropriate checks occur.
 *
 * Other methods should be overridden with care, to maintain the life
 * cycle events and error reporting implemented by this class.
 */
export class BaseStudyAction extends BaseAction {
  _preExecution() {
    if (!Services.policies.isAllowed("Shield")) {
      this.log.debug("Disabling Shield because it's blocked by policy.");
      this.disable();
    }

    if (!Services.prefs.getBoolPref(OPT_OUT_STUDIES_ENABLED_PREF, true)) {
      this.log.debug(
        "User has opted-out of opt-out experiments, disabling action."
      );
      this.disable();
    }
  }
}
PK
!<��__Dchrome/toolkit/res/normandy/actions/BranchedAddonStudyAction.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This action handles the life cycle of add-on based studies. Currently that
 * means installing the add-on the first time the recipe applies to this
 * client, updating the add-on to new versions if the recipe changes, and
 * uninstalling them when the recipe no longer applies.
 */

import { BaseStudyAction } from "resource://normandy/actions/BaseStudyAction.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ActionSchemas: "resource://normandy/actions/schemas/index.sys.mjs",
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  AddonStudies: "resource://normandy/lib/AddonStudies.sys.mjs",
  BaseAction: "resource://normandy/actions/BaseAction.sys.mjs",
  ClientEnvironment: "resource://normandy/lib/ClientEnvironment.sys.mjs",
  NormandyApi: "resource://normandy/lib/NormandyApi.sys.mjs",
  Sampling: "resource://gre/modules/components-utils/Sampling.sys.mjs",
  TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
  TelemetryEvents: "resource://normandy/lib/TelemetryEvents.sys.mjs",
});

class AddonStudyEnrollError extends Error {
  /**
   * @param {string} studyName
   * @param {object} extra Extra details to include when reporting the error to telemetry.
   * @param {string} extra.reason The specific reason for the failure.
   */
  constructor(studyName, extra) {
    let message;
    let { reason } = extra;
    switch (reason) {
      case "conflicting-addon-id": {
        message = "an add-on with this ID is already installed";
        break;
      }
      case "download-failure": {
        message = "the add-on failed to download";
        break;
      }
      case "metadata-mismatch": {
        message = "the server metadata does not match the downloaded add-on";
        break;
      }
      case "install-failure": {
        message = "the add-on failed to install";
        break;
      }
      default: {
        throw new Error(`Unexpected AddonStudyEnrollError reason: ${reason}`);
      }
    }
    super(`Cannot install study add-on for ${studyName}: ${message}.`);
    this.studyName = studyName;
    this.extra = extra;
  }
}

class AddonStudyUpdateError extends Error {
  /**
   * @param {string} studyName
   * @param {object} extra Extra details to include when reporting the error to telemetry.
   * @param {string} extra.reason The specific reason for the failure.
   */
  constructor(studyName, extra) {
    let message;
    let { reason } = extra;
    switch (reason) {
      case "addon-id-mismatch": {
        message = "new add-on ID does not match old add-on ID";
        break;
      }
      case "addon-does-not-exist": {
        message = "an add-on with this ID does not exist";
        break;
      }
      case "no-downgrade": {
        message = "the add-on was an older version than is installed";
        break;
      }
      case "metadata-mismatch": {
        message = "the server metadata does not match the downloaded add-on";
        break;
      }
      case "download-failure": {
        message = "the add-on failed to download";
        break;
      }
      case "install-failure": {
        message = "the add-on failed to install";
        break;
      }
      default: {
        throw new Error(`Unexpected AddonStudyUpdateError reason: ${reason}`);
      }
    }
    super(`Cannot update study add-on for ${studyName}: ${message}.`);
    this.studyName = studyName;
    this.extra = extra;
  }
}

export class BranchedAddonStudyAction extends BaseStudyAction {
  get schema() {
    return lazy.ActionSchemas["branched-addon-study"];
  }

  constructor() {
    super();
    this.seenRecipeIds = new Set();
  }

  async _run() {
    throw new Error("_run should not be called anymore");
  }

  /**
   * This hook is executed once for every recipe currently enabled on the
   * server. It is responsible for:
   *
   *   - Enrolling studies the first time they have a FILTER_MATCH suitability.
   *   - Updating studies that have changed and still have a FILTER_MATCH suitability.
   *   - Marking studies as having been seen in this session.
   *   - Unenrolling studies when they have permanent errors.
   *   - Unenrolling studies when temporary errors persist for too long.
   *
   * If the action fails to perform any of these tasks, it should throw to
   * properly report its status.
   */
  async _processRecipe(recipe, suitability) {
    this.seenRecipeIds.add(recipe.id);
    const study = await lazy.AddonStudies.get(recipe.id);

    switch (suitability) {
      case lazy.BaseAction.suitability.FILTER_MATCH: {
        if (!study) {
          await this.enroll(recipe);
        } else if (study.active) {
          await this.update(recipe, study);
        }
        break;
      }

      case lazy.BaseAction.suitability.SIGNATURE_ERROR: {
        await this._considerTemporaryError({
          study,
          reason: "signature-error",
        });
        break;
      }

      case lazy.BaseAction.suitability.FILTER_ERROR: {
        await this._considerTemporaryError({
          study,
          reason: "filter-error",
        });
        break;
      }

      case lazy.BaseAction.suitability.CAPABILITIES_MISMATCH: {
        if (study?.active) {
          await this.unenroll(recipe.id, "capability-mismatch");
        }
        break;
      }

      case lazy.BaseAction.suitability.FILTER_MISMATCH: {
        if (study?.active) {
          await this.unenroll(recipe.id, "filter-mismatch");
        }
        break;
      }

      case lazy.BaseAction.suitability.ARGUMENTS_INVALID: {
        if (study?.active) {
          await this.unenroll(recipe.id, "arguments-invalid");
        }
        break;
      }

      default: {
        throw new Error(`Unknown recipe suitability "${suitability}".`);
      }
    }
  }

  /**
   * This hook is executed once after all recipes that apply to this client
   * have been processed. It is responsible for unenrolling the client from any
   * studies that no longer apply, based on this.seenRecipeIds.
   */
  async _finalize({ noRecipes } = {}) {
    const activeStudies = await lazy.AddonStudies.getAllActive({
      branched: lazy.AddonStudies.FILTER_BRANCHED_ONLY,
    });

    if (noRecipes) {
      if (this.seenRecipeIds.size) {
        throw new BranchedAddonStudyAction.BadNoRecipesArg();
      }
      for (const study of activeStudies) {
        await this._considerTemporaryError({ study, reason: "no-recipes" });
      }
    } else {
      for (const study of activeStudies) {
        if (!this.seenRecipeIds.has(study.recipeId)) {
          this.log.debug(
            `Stopping branched add-on study for recipe ${study.recipeId}`
          );
          try {
            await this.unenroll(study.recipeId, "recipe-not-seen");
          } catch (err) {
            console.error(err);
          }
        }
      }
    }
  }

  /**
   * Download and install the addon for a given recipe
   *
   * @param recipe Object describing the study to enroll in.
   * @param extensionDetails Object describing the addon to be installed.
   * @param onInstallStarted A function that returns a callback for the install listener.
   * @param onComplete A callback function that is run on completion of the download.
   * @param onFailedInstall A callback function that is run if the installation fails.
   * @param errorClass The class of error to be thrown when exceptions occur.
   * @param reportError A function that reports errors to Telemetry.
   * @param [errorExtra] Optional, an object that will be merged into the
   *                     `extra` field of the error generated, if any.
   */
  async downloadAndInstall({
    recipe,
    extensionDetails,
    branchSlug,
    onInstallStarted,
    onComplete,
    onFailedInstall,
    errorClass,
    reportError,
    errorExtra = {},
  }) {
    const { slug } = recipe.arguments;
    const { hash, hash_algorithm } = extensionDetails;

    const downloadDeferred = Promise.withResolvers();
    const installDeferred = Promise.withResolvers();

    const install = await lazy.AddonManager.getInstallForURL(
      extensionDetails.xpi,
      {
        hash: `${hash_algorithm}:${hash}`,
        telemetryInfo: { source: "internal" },
      }
    );

    const listener = {
      onDownloadFailed() {
        downloadDeferred.reject(
          new errorClass(slug, {
            reason: "download-failure",
            branch: branchSlug,
            detail: lazy.AddonManager.errorToString(install.error),
            ...errorExtra,
          })
        );
      },

      onDownloadEnded() {
        downloadDeferred.resolve();
        return false; // temporarily pause installation for Normandy bookkeeping
      },

      onInstallFailed() {
        installDeferred.reject(
          new errorClass(slug, {
            reason: "install-failure",
            branch: branchSlug,
            detail: lazy.AddonManager.errorToString(install.error),
          })
        );
      },

      onInstallEnded() {
        installDeferred.resolve();
      },
    };

    listener.onInstallStarted = onInstallStarted(installDeferred);

    install.addListener(listener);

    // Download the add-on
    try {
      install.install();
      await downloadDeferred.promise;
    } catch (err) {
      reportError(err);
      install.removeListener(listener);
      throw err;
    }

    await onComplete(install, listener);

    // Finish paused installation
    try {
      install.install();
      await installDeferred.promise;
    } catch (err) {
      reportError(err);
      install.removeListener(listener);
      await onFailedInstall();
      throw err;
    }

    install.removeListener(listener);

    return [install.addon.id, install.addon.version];
  }

  async chooseBranch({ slug, branches }) {
    const ratios = branches.map(branch => branch.ratio);
    const userId = lazy.ClientEnvironment.userId;

    // It's important that the input be:
    // - Unique per-user (no one is bucketed alike)
    // - Unique per-experiment (bucketing differs across multiple experiments)
    // - Differs from the input used for sampling the recipe (otherwise only
    //   branches that contain the same buckets as the recipe sampling will
    //   receive users)
    const input = `${userId}-${slug}-addon-branch`;

    const index = await lazy.Sampling.ratioSample(input, ratios);
    return branches[index];
  }

  /**
   * Enroll in the study represented by the given recipe.
   * @param recipe Object describing the study to enroll in.
   * @param extensionDetails Object describing the addon to be installed.
   */
  async enroll(recipe) {
    // This function first downloads the add-on to get its metadata. Then it
    // uses that metadata to record a study in `AddonStudies`. Then, it finishes
    // installing the add-on, and finally sends telemetry. If any of these steps
    // fails, the previous ones are undone, as needed.
    //
    // This ordering is important because the only intermediate states we can be
    // in are:
    //   1. The add-on is only downloaded, in which case AddonManager will clean it up.
    //   2. The study has been recorded, in which case we will unenroll on next
    //      start up. The start up code will assume that the add-on was uninstalled
    //      while the browser was shutdown.
    //   3. After installation is complete, but before telemetry, in which case we
    //      lose an enroll event. This is acceptable.
    //
    // This way a shutdown, crash or unexpected error can't leave Normandy in a
    // long term inconsistent state. The main thing avoided is having a study
    // add-on installed but no record of it, which would leave it permanently
    // installed.

    if (recipe.arguments.isEnrollmentPaused) {
      // Recipe does not need anything done
      return;
    }

    const { slug, userFacingName, userFacingDescription } = recipe.arguments;
    const branch = await this.chooseBranch({
      slug: recipe.arguments.slug,
      branches: recipe.arguments.branches,
    });
    this.log.debug(`Enrolling in branch ${branch.slug}`);

    if (branch.extensionApiId === null) {
      const study = {
        recipeId: recipe.id,
        slug,
        userFacingName,
        userFacingDescription,
        branch: branch.slug,
        addonId: null,
        addonVersion: null,
        addonUrl: null,
        extensionApiId: null,
        extensionHash: null,
        extensionHashAlgorithm: null,
        active: true,
        studyStartDate: new Date(),
        studyEndDate: null,
        temporaryErrorDeadline: null,
      };

      try {
        await lazy.AddonStudies.add(study);
      } catch (err) {
        this.reportEnrollError(err);
        throw err;
      }

      // All done, report success to Telemetry
      lazy.TelemetryEvents.sendEvent("enroll", "addon_study", slug, {
        addonId: lazy.AddonStudies.NO_ADDON_MARKER,
        addonVersion: lazy.AddonStudies.NO_ADDON_MARKER,
        branch: branch.slug,
      });
    } else {
      const extensionDetails = await lazy.NormandyApi.fetchExtensionDetails(
        branch.extensionApiId
      );

      const onInstallStarted = installDeferred => cbInstall => {
        const versionMatches =
          cbInstall.addon.version === extensionDetails.version;
        const idMatches = cbInstall.addon.id === extensionDetails.extension_id;

        if (cbInstall.existingAddon) {
          installDeferred.reject(
            new AddonStudyEnrollError(slug, {
              reason: "conflicting-addon-id",
              branch: branch.slug,
            })
          );
          return false; // cancel the installation, no upgrades allowed
        } else if (!versionMatches || !idMatches) {
          installDeferred.reject(
            new AddonStudyEnrollError(slug, {
              branch: branch.slug,
              reason: "metadata-mismatch",
            })
          );
          return false; // cancel the installation, server metadata does not match downloaded add-on
        }
        return true;
      };

      let study;
      const onComplete = async (install, listener) => {
        study = {
          recipeId: recipe.id,
          slug,
          userFacingName,
          userFacingDescription,
          branch: branch.slug,
          addonId: install.addon.id,
          addonVersion: install.addon.version,
          addonUrl: extensionDetails.xpi,
          extensionApiId: branch.extensionApiId,
          extensionHash: extensionDetails.hash,
          extensionHashAlgorithm: extensionDetails.hash_algorithm,
          active: true,
          studyStartDate: new Date(),
          studyEndDate: null,
          temporaryErrorDeadline: null,
        };

        try {
          await lazy.AddonStudies.add(study);
        } catch (err) {
          this.reportEnrollError(err);
          install.removeListener(listener);
          install.cancel();
          throw err;
        }
      };

      const onFailedInstall = async () => {
        await lazy.AddonStudies.delete(recipe.id);
      };

      const [installedId, installedVersion] = await this.downloadAndInstall({
        recipe,
        branchSlug: branch.slug,
        extensionDetails,
        onInstallStarted,
        onComplete,
        onFailedInstall,
        errorClass: AddonStudyEnrollError,
        reportError: this.reportEnrollError,
      });

      // All done, report success to Telemetry
      lazy.TelemetryEvents.sendEvent("enroll", "addon_study", slug, {
        addonId: installedId,
        addonVersion: installedVersion,
        branch: branch.slug,
      });
    }

    lazy.TelemetryEnvironment.setExperimentActive(slug, branch.slug, {
      type: "normandy-addonstudy",
    });
  }

  /**
   * Update the study represented by the given recipe.
   * @param recipe Object describing the study to be updated.
   * @param extensionDetails Object describing the addon to be installed.
   */
  async update(recipe, study) {
    const { slug } = recipe.arguments;

    // Stay in the same branch, don't re-sample every time.
    const branch = recipe.arguments.branches.find(
      branch => branch.slug === study.branch
    );

    if (!branch) {
      // Our branch has been removed. Unenroll.
      await this.unenroll(recipe.id, "branch-removed");
      return;
    }

    // Since we saw a non-error suitability, clear the temporary error deadline.
    study.temporaryErrorDeadline = null;
    await lazy.AddonStudies.update(study);

    const extensionDetails = await lazy.NormandyApi.fetchExtensionDetails(
      branch.extensionApiId
    );

    let error;

    if (study.addonId && study.addonId !== extensionDetails.extension_id) {
      error = new AddonStudyUpdateError(slug, {
        branch: branch.slug,
        reason: "addon-id-mismatch",
      });
    }

    const versionCompare = Services.vc.compare(
      study.addonVersion,
      extensionDetails.version
    );
    if (versionCompare > 0) {
      error = new AddonStudyUpdateError(slug, {
        branch: branch.slug,
        reason: "no-downgrade",
      });
    } else if (versionCompare === 0) {
      return; // Unchanged, do nothing
    }

    if (error) {
      this.reportUpdateError(error);
      throw error;
    }

    const onInstallStarted = installDeferred => cbInstall => {
      const versionMatches =
        cbInstall.addon.version === extensionDetails.version;
      const idMatches = cbInstall.addon.id === extensionDetails.extension_id;

      if (!cbInstall.existingAddon) {
        installDeferred.reject(
          new AddonStudyUpdateError(slug, {
            branch: branch.slug,
            reason: "addon-does-not-exist",
          })
        );
        return false; // cancel the installation, must upgrade an existing add-on
      } else if (!versionMatches || !idMatches) {
        installDeferred.reject(
          new AddonStudyUpdateError(slug, {
            branch: branch.slug,
            reason: "metadata-mismatch",
          })
        );
        return false; // cancel the installation, server metadata do not match downloaded add-on
      }

      return true;
    };

    const onComplete = async (install, listener) => {
      try {
        await lazy.AddonStudies.update({
          ...study,
          addonVersion: install.addon.version,
          addonUrl: extensionDetails.xpi,
          extensionHash: extensionDetails.hash,
          extensionHashAlgorithm: extensionDetails.hash_algorithm,
          extensionApiId: branch.extensionApiId,
        });
      } catch (err) {
        this.reportUpdateError(err);
        install.removeListener(listener);
        install.cancel();
        throw err;
      }
    };

    const onFailedInstall = () => {
      lazy.AddonStudies.update(study);
    };

    const [installedId, installedVersion] = await this.downloadAndInstall({
      recipe,
      extensionDetails,
      branchSlug: branch.slug,
      onInstallStarted,
      onComplete,
      onFailedInstall,
      errorClass: AddonStudyUpdateError,
      reportError: this.reportUpdateError,
      errorExtra: {},
    });

    // All done, report success to Telemetry
    lazy.TelemetryEvents.sendEvent("update", "addon_study", slug, {
      addonId: installedId,
      addonVersion: installedVersion,
      branch: branch.slug,
    });
  }

  reportEnrollError(error) {
    if (error instanceof AddonStudyEnrollError) {
      // One of our known errors. Report it nicely to telemetry
      lazy.TelemetryEvents.sendEvent(
        "enrollFailed",
        "addon_study",
        error.studyName,
        error.extra
      );
    } else {
      /*
       * Some unknown error. Add some helpful details, and report it to
       * telemetry. The actual stack trace and error message could possibly
       * contain PII, so we don't include them here. Instead include some
       * information that should still be helpful, and is less likely to be
       * unsafe.
       */
      const safeErrorMessage = `${error.fileName}:${error.lineNumber}:${error.columnNumber} ${error.name}`;
      lazy.TelemetryEvents.sendEvent(
        "enrollFailed",
        "addon_study",
        error.studyName,
        {
          reason: safeErrorMessage.slice(0, 80), // max length is 80 chars
        }
      );
    }
  }

  reportUpdateError(error) {
    if (error instanceof AddonStudyUpdateError) {
      // One of our known errors. Report it nicely to telemetry
      lazy.TelemetryEvents.sendEvent(
        "updateFailed",
        "addon_study",
        error.studyName,
        error.extra
      );
    } else {
      /*
       * Some unknown error. Add some helpful details, and report it to
       * telemetry. The actual stack trace and error message could possibly
       * contain PII, so we don't include them here. Instead include some
       * information that should still be helpful, and is less likely to be
       * unsafe.
       */
      const safeErrorMessage = `${error.fileName}:${error.lineNumber}:${error.columnNumber} ${error.name}`;
      lazy.TelemetryEvents.sendEvent(
        "updateFailed",
        "addon_study",
        error.studyName,
        {
          reason: safeErrorMessage.slice(0, 80), // max length is 80 chars
        }
      );
    }
  }

  /**
   * Unenrolls the client from the study with a given recipe ID.
   * @param recipeId The recipe ID of an enrolled study
   * @param reason The reason for this unenrollment, to be used in Telemetry
   * @throws If the specified study does not exist, or if it is already inactive.
   */
  async unenroll(recipeId, reason = "unknown") {
    const study = await lazy.AddonStudies.get(recipeId);
    if (!study) {
      throw new Error(`No study found for recipe ${recipeId}.`);
    }
    if (!study.active) {
      throw new Error(
        `Cannot stop study for recipe ${recipeId}; it is already inactive.`
      );
    }

    await lazy.AddonStudies.markAsEnded(study, reason);

    // Study branches may indicate that no add-on should be installed, as a
    // form of control branch. In that case, `study.addonId` will be null (as
    // will the other add-on related fields). Only try to uninstall the add-on
    // if we expect one should be installed.
    if (study.addonId) {
      const addon = await lazy.AddonManager.getAddonByID(study.addonId);
      if (addon) {
        await addon.uninstall();
      } else {
        this.log.warn(
          `Could not uninstall addon ${study.addonId} for recipe ${study.recipeId}: it is not installed.`
        );
      }
    }
  }

  /**
   * Given that a temporary error has occured for a study, check if it
   * should be temporarily ignored, or if the deadline has passed. If the
   * deadline is passed, the study will be ended. If this is the first
   * temporary error, a deadline will be generated. Otherwise, nothing will
   * happen.
   *
   * If a temporary deadline exists but cannot be parsed, a new one will be
   * made.
   *
   * The deadline is 7 days from the first time that recipe failed, as
   * reckoned by the client's clock.
   *
   * @param {Object} args
   * @param {Study} args.study The enrolled study to potentially unenroll.
   * @param {String} args.reason If the study should end, the reason it is ending.
   */
  async _considerTemporaryError({ study, reason }) {
    if (!study?.active) {
      return;
    }

    let now = Date.now(); // milliseconds-since-epoch
    let day = 24 * 60 * 60 * 1000;
    let newDeadline = new Date(now + 7 * day);

    if (study.temporaryErrorDeadline) {
      // if deadline is an invalid date, set it to one week from now.
      if (isNaN(study.temporaryErrorDeadline)) {
        study.temporaryErrorDeadline = newDeadline;
        await lazy.AddonStudies.update(study);
        return;
      }

      if (now > study.temporaryErrorDeadline) {
        await this.unenroll(study.recipeId, reason);
      }
    } else {
      // there is no deadline, so set one
      study.temporaryErrorDeadline = newDeadline;
      await lazy.AddonStudies.update(study);
    }
  }
}

BranchedAddonStudyAction.BadNoRecipesArg = class extends Error {
  message = "noRecipes is true, but some recipes observed";
};
PK
!<Q:Ϳcc<chrome/toolkit/res/normandy/actions/ConsoleLogAction.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { BaseAction } from "resource://normandy/actions/BaseAction.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  ActionSchemas: "resource://normandy/actions/schemas/index.sys.mjs",
});

export class ConsoleLogAction extends BaseAction {
  get schema() {
    return lazy.ActionSchemas["console-log"];
  }

  async _run(recipe) {
    this.log.info(recipe.arguments.message);
  }
}
PK
!<2�+���Echrome/toolkit/res/normandy/actions/MessagingExperimentAction.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { BaseStudyAction } from "resource://normandy/actions/BaseStudyAction.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ActionSchemas: "resource://normandy/actions/schemas/index.sys.mjs",
  ExperimentManager: "resource://nimbus/lib/ExperimentManager.sys.mjs",
});

const RECIPE_SOURCE = "normandy";

export class MessagingExperimentAction extends BaseStudyAction {
  constructor() {
    super();
    this.manager = lazy.ExperimentManager;
  }
  get schema() {
    return lazy.ActionSchemas["messaging-experiment"];
  }

  async _run(recipe) {
    if (recipe.arguments) {
      await this.manager.onRecipe(recipe.arguments, RECIPE_SOURCE);
    }
  }

  async _finalize() {
    this.manager.onFinalize(RECIPE_SOURCE);
  }
}
PK
!<��(#%%Fchrome/toolkit/res/normandy/actions/PreferenceExperimentAction.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { BaseStudyAction } from "resource://normandy/actions/BaseStudyAction.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  ActionSchemas: "resource://normandy/actions/schemas/index.sys.mjs",
  BaseAction: "resource://normandy/actions/BaseAction.sys.mjs",
  ClientEnvironment: "resource://normandy/lib/ClientEnvironment.sys.mjs",
  PreferenceExperiments:
    "resource://normandy/lib/PreferenceExperiments.sys.mjs",
  Sampling: "resource://gre/modules/components-utils/Sampling.sys.mjs",
});

/**
 * Enrolls a user in a preference experiment, in which we assign the
 * user to an experiment branch and modify a preference temporarily to
 * measure how it affects Firefox via Telemetry.
 */
export class PreferenceExperimentAction extends BaseStudyAction {
  get schema() {
    return lazy.ActionSchemas["multi-preference-experiment"];
  }

  constructor() {
    super();
    this.seenExperimentSlugs = new Set();
  }

  async _processRecipe(recipe, suitability) {
    const {
      branches,
      isHighPopulation,
      isEnrollmentPaused,
      slug,
      userFacingName,
      userFacingDescription,
    } = recipe.arguments || {};

    let experiment;
    // Slug might not exist, because if suitability is ARGUMENTS_INVALID, the
    // arguments is not guaranteed to match the schema.
    if (slug) {
      this.seenExperimentSlugs.add(slug);

      try {
        experiment = await lazy.PreferenceExperiments.get(slug);
      } catch (err) {
        // This is probably that the experiment doesn't exist. If that's not the
        // case, re-throw the error.
        if (!(err instanceof lazy.PreferenceExperiments.NotFoundError)) {
          throw err;
        }
      }
    }

    switch (suitability) {
      case lazy.BaseAction.suitability.SIGNATURE_ERROR: {
        this._considerTemporaryError({ experiment, reason: "signature-error" });
        break;
      }

      case lazy.BaseAction.suitability.CAPABILITIES_MISMATCH: {
        if (experiment && !experiment.expired) {
          await lazy.PreferenceExperiments.stop(slug, {
            resetValue: true,
            reason: "capability-mismatch",
            caller:
              "PreferenceExperimentAction._processRecipe::capabilities_mismatch",
          });
        }
        break;
      }

      case lazy.BaseAction.suitability.FILTER_MATCH: {
        // If we're not in the experiment, try to enroll
        if (!experiment) {
          // Check all preferences that could be used by this experiment.
          // If there's already an active experiment that has set that preference, abort.
          const activeExperiments =
            await lazy.PreferenceExperiments.getAllActive();
          for (const branch of branches) {
            const conflictingPrefs = Object.keys(branch.preferences).filter(
              preferenceName => {
                return activeExperiments.some(exp =>
                  exp.preferences.hasOwnProperty(preferenceName)
                );
              }
            );
            if (conflictingPrefs.length) {
              throw new Error(
                `Experiment ${slug} ignored; another active experiment is already using the
            ${conflictingPrefs[0]} preference.`
              );
            }
          }

          // Determine if enrollment is currently paused for this experiment.
          if (isEnrollmentPaused) {
            this.log.debug(`Enrollment is paused for experiment "${slug}"`);
            return;
          }

          // Otherwise, enroll!
          const branch = await this.chooseBranch(slug, branches);
          const experimentType = isHighPopulation ? "exp-highpop" : "exp";
          await lazy.PreferenceExperiments.start({
            slug,
            actionName: this.name,
            branch: branch.slug,
            preferences: branch.preferences,
            experimentType,
            userFacingName,
            userFacingDescription,
          });
        } else if (experiment.expired) {
          this.log.debug(`Experiment ${slug} has expired, aborting.`);
        } else {
          experiment.temporaryErrorDeadline = null;
          await lazy.PreferenceExperiments.update(experiment);
          await lazy.PreferenceExperiments.markLastSeen(slug);
        }
        break;
      }

      case lazy.BaseAction.suitability.FILTER_MISMATCH: {
        if (experiment && !experiment.expired) {
          await lazy.PreferenceExperiments.stop(slug, {
            resetValue: true,
            reason: "filter-mismatch",
            caller:
              "PreferenceExperimentAction._processRecipe::filter_mismatch",
          });
        }
        break;
      }

      case lazy.BaseAction.suitability.FILTER_ERROR: {
        this._considerTemporaryError({ experiment, reason: "filter-error" });
        break;
      }

      case lazy.BaseAction.suitability.ARGUMENTS_INVALID: {
        if (experiment && !experiment.expired) {
          await lazy.PreferenceExperiments.stop(slug, {
            resetValue: true,
            reason: "arguments-invalid",
            caller:
              "PreferenceExperimentAction._processRecipe::arguments_invalid",
          });
        }
        break;
      }

      default: {
        throw new Error(`Unknown recipe suitability "${suitability}".`);
      }
    }
  }

  async _run() {
    throw new Error("_run shouldn't be called anymore");
  }

  async chooseBranch(slug, branches) {
    const ratios = branches.map(branch => branch.ratio);
    const userId = lazy.ClientEnvironment.userId;

    // It's important that the input be:
    // - Unique per-user (no one is bucketed alike)
    // - Unique per-experiment (bucketing differs across multiple experiments)
    // - Differs from the input used for sampling the recipe (otherwise only
    //   branches that contain the same buckets as the recipe sampling will
    //   receive users)
    const input = `${userId}-${slug}-branch`;

    const index = await lazy.Sampling.ratioSample(input, ratios);
    return branches[index];
  }

  /**
   * End any experiments which we didn't see during this session.
   * This is the "normal" way experiments end, as they are disabled on
   * the server and so we stop seeing them.  This can also happen if
   * the user doesn't match the filter any more.
   */
  async _finalize({ noRecipes } = {}) {
    const activeExperiments = await lazy.PreferenceExperiments.getAllActive();

    if (noRecipes && this.seenExperimentSlugs.size) {
      throw new PreferenceExperimentAction.BadNoRecipesArg();
    }

    return Promise.all(
      activeExperiments.map(experiment => {
        if (this.name != experiment.actionName) {
          // Another action is responsible for cleaning this one
          // up. Leave it alone.
          return null;
        }

        if (noRecipes) {
          return this._considerTemporaryError({
            experiment,
            reason: "no-recipes",
          });
        }

        if (this.seenExperimentSlugs.has(experiment.slug)) {
          return null;
        }

        return lazy.PreferenceExperiments.stop(experiment.slug, {
          resetValue: true,
          reason: "recipe-not-seen",
          caller: "PreferenceExperimentAction._finalize",
        }).catch(e => {
          this.log.warn(`Stopping experiment ${experiment.slug} failed: ${e}`);
        });
      })
    );
  }

  /**
   * Given that a temporary error has occurred for an experiment, check if it
   * should be temporarily ignored, or if the deadline has passed. If the
   * deadline is passed, the experiment will be ended. If this is the first
   * temporary error, a deadline will be generated. Otherwise, nothing will
   * happen.
   *
   * If a temporary deadline exists but cannot be parsed, a new one will be
   * made.
   *
   * The deadline is 7 days from the first time that recipe failed, as
   * reckoned by the client's clock.
   *
   * @param {Object} args
   * @param {Experiment} args.experiment The enrolled experiment to potentially unenroll.
   * @param {String} args.reason If the recipe should end, the reason it is ending.
   */
  async _considerTemporaryError({ experiment, reason }) {
    if (!experiment || experiment.expired) {
      return;
    }

    let now = Date.now(); // milliseconds-since-epoch
    let day = 24 * 60 * 60 * 1000;
    let newDeadline = new Date(now + 7 * day).toJSON();

    if (experiment.temporaryErrorDeadline) {
      let deadline = new Date(experiment.temporaryErrorDeadline);
      // if deadline is an invalid date, set it to one week from now.
      if (isNaN(deadline)) {
        experiment.temporaryErrorDeadline = newDeadline;
        await lazy.PreferenceExperiments.update(experiment);
        return;
      }

      if (now > deadline) {
        await lazy.PreferenceExperiments.stop(experiment.slug, {
          resetValue: true,
          reason,
          caller: "PreferenceExperimentAction._considerTemporaryFailure",
        });
      }
    } else {
      // there is no deadline, so set one
      experiment.temporaryErrorDeadline = newDeadline;
      await lazy.PreferenceExperiments.update(experiment);
    }
  }
}

PreferenceExperimentAction.BadNoRecipesArg = class extends Error {
  message = "noRecipes is true, but some recipes observed";
};
PK
!<e`����Dchrome/toolkit/res/normandy/actions/PreferenceRollbackAction.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { BaseAction } from "resource://normandy/actions/BaseAction.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  ActionSchemas: "resource://normandy/actions/schemas/index.sys.mjs",
  PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs",
  PreferenceRollouts: "resource://normandy/lib/PreferenceRollouts.sys.mjs",
  TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
  TelemetryEvents: "resource://normandy/lib/TelemetryEvents.sys.mjs",
});

export class PreferenceRollbackAction extends BaseAction {
  get schema() {
    return lazy.ActionSchemas["preference-rollback"];
  }

  async _run(recipe) {
    const { rolloutSlug } = recipe.arguments;
    const rollout = await lazy.PreferenceRollouts.get(rolloutSlug);

    if (lazy.PreferenceRollouts.GRADUATION_SET.has(rolloutSlug)) {
      // graduated rollouts can't be rolled back
      lazy.TelemetryEvents.sendEvent(
        "unenrollFailed",
        "preference_rollback",
        rolloutSlug,
        {
          reason: "in-graduation-set",
        }
      );
      throw new Error(
        `Cannot rollback rollout in graduation set "${rolloutSlug}".`
      );
    }

    if (!rollout) {
      this.log.debug(`Rollback ${rolloutSlug} not applicable, skipping`);
      return;
    }

    switch (rollout.state) {
      case lazy.PreferenceRollouts.STATE_ACTIVE: {
        this.log.info(`Rolling back ${rolloutSlug}`);
        rollout.state = lazy.PreferenceRollouts.STATE_ROLLED_BACK;
        for (const { preferenceName, previousValue } of rollout.preferences) {
          lazy.PrefUtils.setPref(preferenceName, previousValue, {
            branch: "default",
          });
        }
        await lazy.PreferenceRollouts.update(rollout);
        lazy.TelemetryEvents.sendEvent(
          "unenroll",
          "preference_rollback",
          rolloutSlug,
          {
            reason: "rollback",
          }
        );
        lazy.TelemetryEnvironment.setExperimentInactive(rolloutSlug);
        break;
      }
      case lazy.PreferenceRollouts.STATE_ROLLED_BACK: {
        // The rollout has already been rolled back, so nothing to do here.
        break;
      }
      case lazy.PreferenceRollouts.STATE_GRADUATED: {
        // graduated rollouts can't be rolled back
        lazy.TelemetryEvents.sendEvent(
          "unenrollFailed",
          "preference_rollback",
          rolloutSlug,
          {
            reason: "graduated",
          }
        );
        throw new Error(
          `Cannot rollback already graduated rollout ${rolloutSlug}`
        );
      }
      default: {
        throw new Error(
          `Unexpected state when rolling back ${rolloutSlug}: ${rollout.state}`
        );
      }
    }
  }

  async _finalize() {
    await lazy.PreferenceRollouts.saveStartupPrefs();
  }
}
PK
!<�8U� � Cchrome/toolkit/res/normandy/actions/PreferenceRolloutAction.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { BaseAction } from "resource://normandy/actions/BaseAction.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  ActionSchemas: "resource://normandy/actions/schemas/index.sys.mjs",
  PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs",
  PreferenceRollouts: "resource://normandy/lib/PreferenceRollouts.sys.mjs",
  TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
  TelemetryEvents: "resource://normandy/lib/TelemetryEvents.sys.mjs",
});

const PREFERENCE_TYPE_MAP = {
  boolean: Services.prefs.PREF_BOOL,
  string: Services.prefs.PREF_STRING,
  number: Services.prefs.PREF_INT,
};

export class PreferenceRolloutAction extends BaseAction {
  get schema() {
    return lazy.ActionSchemas["preference-rollout"];
  }

  async _run(recipe) {
    const args = recipe.arguments;

    // Check if the rollout is on the list of rollouts to stop applying.
    if (lazy.PreferenceRollouts.GRADUATION_SET.has(args.slug)) {
      this.log.debug(
        `Skipping rollout "${args.slug}" because it is in the graduation set.`
      );
      return;
    }

    // Determine which preferences are already being managed, to avoid
    // conflicts between recipes. This will throw if there is a problem.
    await this._verifyRolloutPrefs(args);

    const newRollout = {
      slug: args.slug,
      state: "active",
      preferences: args.preferences.map(({ preferenceName, value }) => ({
        preferenceName,
        value,
        previousValue: lazy.PrefUtils.getPref(preferenceName, {
          branch: "default",
        }),
      })),
    };

    const existingRollout = await lazy.PreferenceRollouts.get(args.slug);
    if (existingRollout) {
      const anyChanged = await this._updatePrefsForExistingRollout(
        existingRollout,
        newRollout
      );

      // If anything was different about the new rollout, write it to the db and send an event about it
      if (anyChanged) {
        await lazy.PreferenceRollouts.update(newRollout);
        lazy.TelemetryEvents.sendEvent(
          "update",
          "preference_rollout",
          args.slug,
          {
            previousState: existingRollout.state,
          }
        );

        switch (existingRollout.state) {
          case lazy.PreferenceRollouts.STATE_ACTIVE: {
            this.log.debug(`Updated preference rollout ${args.slug}`);
            break;
          }
          case lazy.PreferenceRollouts.STATE_GRADUATED: {
            this.log.debug(`Ungraduated preference rollout ${args.slug}`);
            lazy.TelemetryEnvironment.setExperimentActive(
              args.slug,
              newRollout.state,
              { type: "normandy-prefrollout" }
            );
            break;
          }
          default: {
            console.error(
              new Error(
                `Updated pref rollout in unexpected state: ${existingRollout.state}`
              )
            );
          }
        }
      } else {
        this.log.debug(`No updates to preference rollout ${args.slug}`);
      }
    } else {
      // new enrollment
      // Check if this rollout would be a no-op, which is not allowed.
      if (
        newRollout.preferences.every(
          ({ value, previousValue }) => value === previousValue
        )
      ) {
        lazy.TelemetryEvents.sendEvent(
          "enrollFailed",
          "preference_rollout",
          args.slug,
          { reason: "would-be-no-op" }
        );
        // Throw so that this recipe execution is marked as a failure
        throw new Error(
          `New rollout ${args.slug} does not change any preferences.`
        );
      }

      await lazy.PreferenceRollouts.add(newRollout);

      for (const { preferenceName, value } of args.preferences) {
        lazy.PrefUtils.setPref(preferenceName, value, { branch: "default" });
      }

      this.log.debug(`Enrolled in preference rollout ${args.slug}`);
      lazy.TelemetryEnvironment.setExperimentActive(
        args.slug,
        newRollout.state,
        {
          type: "normandy-prefrollout",
        }
      );
      lazy.TelemetryEvents.sendEvent(
        "enroll",
        "preference_rollout",
        args.slug,
        {}
      );
    }
  }

  /**
   * Check that all the preferences in a rollout are ok to set. This means 1) no
   * other rollout is managing them, and 2) they match the types of the builtin
   * values.
   * @param {PreferenceRollout} rollout The arguments from a rollout recipe.
   * @throws If the preferences are not valid, with details in the error message.
   */
  async _verifyRolloutPrefs({ slug, preferences }) {
    const existingManagedPrefs = new Set();
    for (const rollout of await lazy.PreferenceRollouts.getAllActive()) {
      if (rollout.slug === slug) {
        continue;
      }
      for (const prefSpec of rollout.preferences) {
        existingManagedPrefs.add(prefSpec.preferenceName);
      }
    }

    for (const prefSpec of preferences) {
      if (existingManagedPrefs.has(prefSpec.preferenceName)) {
        lazy.TelemetryEvents.sendEvent(
          "enrollFailed",
          "preference_rollout",
          slug,
          {
            reason: "conflict",
            preference: prefSpec.preferenceName,
          }
        );
        // Throw so that this recipe execution is marked as a failure
        throw new Error(
          `Cannot start rollout ${slug}. Preference ${prefSpec.preferenceName} is already managed.`
        );
      }
      const existingPrefType = Services.prefs.getPrefType(
        prefSpec.preferenceName
      );
      const rolloutPrefType = PREFERENCE_TYPE_MAP[typeof prefSpec.value];

      if (
        existingPrefType !== Services.prefs.PREF_INVALID &&
        existingPrefType !== rolloutPrefType
      ) {
        lazy.TelemetryEvents.sendEvent(
          "enrollFailed",
          "preference_rollout",
          slug,
          {
            reason: "invalid type",
            preference: prefSpec.preferenceName,
          }
        );
        // Throw so that this recipe execution is marked as a failure
        throw new Error(
          `Cannot start rollout "${slug}" on "${prefSpec.preferenceName}". ` +
            `Existing preference is of type ${existingPrefType}, but rollout ` +
            `specifies type ${rolloutPrefType}`
        );
      }
    }
  }

  async _updatePrefsForExistingRollout(existingRollout, newRollout) {
    let anyChanged = false;
    const oldPrefSpecs = new Map(
      existingRollout.preferences.map(p => [p.preferenceName, p])
    );
    const newPrefSpecs = new Map(
      newRollout.preferences.map(p => [p.preferenceName, p])
    );

    // Check for any preferences that no longer exist, and un-set them.
    for (const { preferenceName, previousValue } of oldPrefSpecs.values()) {
      if (!newPrefSpecs.has(preferenceName)) {
        this.log.debug(
          `updating ${existingRollout.slug}: ${preferenceName} no longer exists`
        );
        anyChanged = true;
        lazy.PrefUtils.setPref(preferenceName, previousValue, {
          branch: "default",
        });
      }
    }

    // Check for any preferences that are new and need added, or changed and need updated.
    for (const prefSpec of Object.values(newRollout.preferences)) {
      let oldValue = null;
      if (oldPrefSpecs.has(prefSpec.preferenceName)) {
        let oldPrefSpec = oldPrefSpecs.get(prefSpec.preferenceName);
        oldValue = oldPrefSpec.value;

        // Trust the old rollout for the values of `previousValue`, but don't
        // consider this a change, since it already matches the DB, and doesn't
        // have any other stateful effect.
        prefSpec.previousValue = oldPrefSpec.previousValue;
      }
      if (oldValue !== newPrefSpecs.get(prefSpec.preferenceName).value) {
        anyChanged = true;
        this.log.debug(
          `updating ${existingRollout.slug}: ${prefSpec.preferenceName} value changed from ${oldValue} to ${prefSpec.value}`
        );
        lazy.PrefUtils.setPref(prefSpec.preferenceName, prefSpec.value, {
          branch: "default",
        });
      }
    }
    return anyChanged;
  }

  async _finalize() {
    await lazy.PreferenceRollouts.saveStartupPrefs();
  }
}
PK
!<���U��?chrome/toolkit/res/normandy/actions/ShowHeartbeatAction.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { BaseAction } from "resource://normandy/actions/BaseAction.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  ActionSchemas: "resource://normandy/actions/schemas/index.sys.mjs",
  BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
  ClientEnvironment: "resource://normandy/lib/ClientEnvironment.sys.mjs",
  Heartbeat: "resource://normandy/lib/Heartbeat.sys.mjs",
  NormandyUtils: "resource://normandy/lib/NormandyUtils.sys.mjs",
  ShellService: "resource:///modules/ShellService.sys.mjs",
  Storage: "resource://normandy/lib/Storage.sys.mjs",
  UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "gAllRecipeStorage", function () {
  return new lazy.Storage("normandy-heartbeat");
});

const DAY_IN_MS = 24 * 60 * 60 * 1000;
const HEARTBEAT_THROTTLE = 1 * DAY_IN_MS;

export class ShowHeartbeatAction extends BaseAction {
  static Heartbeat = lazy.Heartbeat;

  static overrideHeartbeatForTests(newHeartbeat) {
    if (newHeartbeat) {
      this.Heartbeat = newHeartbeat;
    } else {
      this.Heartbeat = lazy.Heartbeat;
    }
  }

  get schema() {
    return lazy.ActionSchemas["show-heartbeat"];
  }

  async _run(recipe) {
    const {
      message,
      engagementButtonLabel,
      thanksMessage,
      learnMoreMessage,
      learnMoreUrl,
    } = recipe.arguments;

    const recipeStorage = new lazy.Storage(recipe.id);

    if (!(await this.shouldShow(recipeStorage, recipe))) {
      return;
    }

    this.log.debug(
      `Heartbeat for recipe ${recipe.id} showing prompt "${message}"`
    );
    const targetWindow = lazy.BrowserWindowTracker.getTopWindow();

    if (!targetWindow) {
      throw new Error("No window to show heartbeat in");
    }

    const heartbeat = new ShowHeartbeatAction.Heartbeat(targetWindow, {
      surveyId: this.generateSurveyId(recipe),
      message,
      engagementButtonLabel,
      thanksMessage,
      learnMoreMessage,
      learnMoreUrl,
      postAnswerUrl: await this.generatePostAnswerURL(recipe),
      flowId: lazy.NormandyUtils.generateUuid(),
      surveyVersion: recipe.revision_id,
    });

    heartbeat.eventEmitter.once(
      "Voted",
      this.updateLastInteraction.bind(this, recipeStorage)
    );
    heartbeat.eventEmitter.once(
      "Engaged",
      this.updateLastInteraction.bind(this, recipeStorage)
    );

    let now = Date.now();
    await Promise.all([
      lazy.gAllRecipeStorage.setItem("lastShown", now),
      recipeStorage.setItem("lastShown", now),
    ]);
  }

  async shouldShow(recipeStorage, recipe) {
    const { repeatOption, repeatEvery } = recipe.arguments;
    // Don't show any heartbeats to a user more than once per throttle period
    let lastShown = await lazy.gAllRecipeStorage.getItem("lastShown");
    if (lastShown) {
      const duration = new Date() - lastShown;
      if (duration < HEARTBEAT_THROTTLE) {
        // show the number of hours since the last heartbeat, with at most 1 decimal point.
        const hoursAgo = Math.floor(duration / 1000 / 60 / 6) / 10;
        this.log.debug(
          `A heartbeat was shown too recently (${hoursAgo} hours), skipping recipe ${recipe.id}.`
        );
        return false;
      }
    }

    switch (repeatOption) {
      case "once": {
        // Don't show if we've ever shown before
        if (await recipeStorage.getItem("lastShown")) {
          this.log.debug(
            `Heartbeat for "once" recipe ${recipe.id} has been shown before, skipping.`
          );
          return false;
        }
        break;
      }

      case "nag": {
        // Show a heartbeat again only if the user has not interacted with it before
        if (await recipeStorage.getItem("lastInteraction")) {
          this.log.debug(
            `Heartbeat for "nag" recipe ${recipe.id} has already been interacted with, skipping.`
          );
          return false;
        }
        break;
      }

      case "xdays": {
        // Show this heartbeat again if it  has been at least `repeatEvery` days since the last time it was shown.
        let lastShown = await lazy.gAllRecipeStorage.getItem("lastShown");
        if (lastShown) {
          lastShown = new Date(lastShown);
          const duration = new Date() - lastShown;
          if (duration < repeatEvery * DAY_IN_MS) {
            // show the number of hours since the last time this hearbeat was shown, with at most 1 decimal point.
            const hoursAgo = Math.floor(duration / 1000 / 60 / 6) / 10;
            this.log.debug(
              `Heartbeat for "xdays" recipe ${recipe.id} ran in the last ${repeatEvery} days, skipping. (${hoursAgo} hours ago)`
            );
            return false;
          }
        }
      }
    }

    return true;
  }

  /**
   * Returns a surveyId value. If recipe calls to include the Normandy client
   * ID, then the client ID is attached to the surveyId in the format
   * `${surveyId}::${userId}`.
   *
   * @return {String} Survey ID, possibly with user UUID
   */
  generateSurveyId(recipe) {
    const { includeTelemetryUUID, surveyId } = recipe.arguments;
    if (includeTelemetryUUID) {
      return `${surveyId}::${lazy.ClientEnvironment.userId}`;
    }
    return surveyId;
  }

  /**
   * Generate the appropriate post-answer URL for a recipe.
   * @param  recipe
   * @return {String} URL with post-answer query params
   */
  async generatePostAnswerURL(recipe) {
    const { postAnswerUrl, message, includeTelemetryUUID } = recipe.arguments;

    // Don`t bother with empty URLs.
    if (!postAnswerUrl) {
      return postAnswerUrl;
    }

    const userId = lazy.ClientEnvironment.userId;
    const searchEngine = (await Services.search.getDefault()).identifier;
    const args = {
      fxVersion: Services.appinfo.version,
      isDefaultBrowser: lazy.ShellService.isDefaultBrowser() ? 1 : 0,
      searchEngine,
      source: "heartbeat",
      // `surveyversion` used to be the version of the heartbeat action when it
      // was hosted on a server. Keeping it around for compatibility.
      surveyversion: Services.appinfo.version,
      syncSetup: Services.prefs.prefHasUserValue("services.sync.username")
        ? 1
        : 0,
      updateChannel: lazy.UpdateUtils.getUpdateChannel(false),
      utm_campaign: encodeURIComponent(message.replace(/\s+/g, "")),
      utm_medium: recipe.action,
      utm_source: "firefox",
    };
    if (includeTelemetryUUID) {
      args.userId = userId;
    }

    let url = new URL(postAnswerUrl);
    // create a URL object to append arguments to
    for (const [key, val] of Object.entries(args)) {
      if (!url.searchParams.has(key)) {
        url.searchParams.set(key, val);
      }
    }

    // return the address with encoded queries
    return url.toString();
  }

  updateLastInteraction(recipeStorage) {
    recipeStorage.setItem("lastInteraction", Date.now());
  }
}
PK
!<ӡ�e==9chrome/toolkit/res/normandy/actions/schemas/index.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

export const ActionSchemas = {
  "console-log": {
    $schema: "http://json-schema.org/draft-04/schema#",
    title: "Log a message to the console",
    type: "object",
    required: ["message"],
    properties: {
      message: {
        description: "Message to log to the console",
        type: "string",
        default: "",
      },
    },
  },

  "messaging-experiment": {
    $schema: "http://json-schema.org/draft-04/schema#",
    title: "Messaging Experiment",
    type: "object",
    required: ["slug", "branches", "isEnrollmentPaused"],
    properties: {
      slug: {
        description: "Unique identifier for this experiment",
        type: "string",
        pattern: "^[A-Za-z0-9\\-_]+$",
      },
      isEnrollmentPaused: {
        description: "If true, new users will not be enrolled in the study.",
        type: "boolean",
        default: true,
      },
      branches: {
        description: "List of experimental branches",
        type: "array",
        minItems: 1,
        items: {
          type: "object",
          required: ["slug", "value", "ratio", "groups"],
          properties: {
            slug: {
              description:
                "Unique identifier for this branch of the experiment.",
              type: "string",
              pattern: "^[A-Za-z0-9\\-_]+$",
            },
            value: {
              description: "Message content.",
              type: "object",
              properties: {},
            },
            ratio: {
              description:
                "Ratio of users who should be grouped into this branch.",
              type: "integer",
              minimum: 1,
            },
            groups: {
              description:
                "A list of experiment groups that can be used to exclude or select related experiments. May be empty.",
              type: "array",
              items: {
                type: "string",
                description: "Identifier of the group",
              },
            },
          },
        },
      },
    },
  },

  "preference-rollout": {
    $schema: "http://json-schema.org/draft-04/schema#",
    title: "Change preferences permanently",
    type: "object",
    required: ["slug", "preferences"],
    properties: {
      slug: {
        description:
          "Unique identifer for the rollout, used in telemetry and rollbacks",
        type: "string",
        pattern: "^[a-z0-9\\-_]+$",
      },
      preferences: {
        description: "The preferences to change, and their values",
        type: "array",
        minItems: 1,
        items: {
          type: "object",
          required: ["preferenceName", "value"],
          properties: {
            preferenceName: {
              description: "Full dotted-path of the preference being changed",
              type: "string",
            },
            value: {
              description: "Value to set the preference to",
              type: ["string", "integer", "boolean"],
            },
          },
        },
      },
    },
  },

  "preference-rollback": {
    $schema: "http://json-schema.org/draft-04/schema#",
    title: "Undo a preference rollout",
    type: "object",
    required: ["rolloutSlug"],
    properties: {
      rolloutSlug: {
        description: "Unique identifer for the rollout to undo",
        type: "string",
        pattern: "^[a-z0-9\\-_]+$",
      },
    },
  },

  "addon-study": {
    $schema: "http://json-schema.org/draft-04/schema#",
    title: "Enroll a user in an opt-out SHIELD study",
    type: "object",
    required: [
      "name",
      "description",
      "addonUrl",
      "extensionApiId",
      "isEnrollmentPaused",
    ],
    properties: {
      name: {
        description: "User-facing name of the study",
        type: "string",
        minLength: 1,
      },
      description: {
        description: "User-facing description of the study",
        type: "string",
        minLength: 1,
      },
      addonUrl: {
        description: "URL of the add-on XPI file",
        type: "string",
        format: "uri",
        minLength: 1,
      },
      extensionApiId: {
        description:
          "The record ID of the extension used for Normandy API calls.",
        type: "integer",
      },
      isEnrollmentPaused: {
        description: "If true, new users will not be enrolled in the study.",
        type: "boolean",
        default: true,
      },
    },
  },

  "addon-rollout": {
    $schema: "http://json-schema.org/draft-04/schema#",
    title: "Install add-on permanently",
    type: "object",
    required: ["extensionApiId", "slug"],
    properties: {
      extensionApiId: {
        description:
          "The record ID of the extension used for Normandy API calls.",
        type: "integer",
      },
      slug: {
        description:
          "Unique identifer for the rollout, used in telemetry and rollbacks.",
        type: "string",
        pattern: "^[a-z0-9\\-_]+$",
      },
    },
  },

  "addon-rollback": {
    $schema: "http://json-schema.org/draft-04/schema#",
    title: "Undo an add-on rollout",
    type: "object",
    required: ["rolloutSlug"],
    properties: {
      rolloutSlug: {
        description: "Unique identifer for the rollout to undo.",
        type: "string",
        pattern: "^[a-z0-9\\-_]+$",
      },
    },
  },

  "branched-addon-study": {
    $schema: "http://json-schema.org/draft-04/schema#",
    title: "Enroll a user in an add-on experiment, with managed branches",
    type: "object",
    required: [
      "slug",
      "userFacingName",
      "userFacingDescription",
      "branches",
      "isEnrollmentPaused",
    ],
    properties: {
      slug: {
        description: "Machine-readable identifier",
        type: "string",
        minLength: 1,
      },
      userFacingName: {
        description: "User-facing name of the study",
        type: "string",
        minLength: 1,
      },
      userFacingDescription: {
        description: "User-facing description of the study",
        type: "string",
        minLength: 1,
      },
      isEnrollmentPaused: {
        description: "If true, new users will not be enrolled in the study.",
        type: "boolean",
        default: true,
      },
      branches: {
        description: "List of experimental branches",
        type: "array",
        minItems: 1,
        items: {
          type: "object",
          required: ["slug", "ratio", "extensionApiId"],
          properties: {
            slug: {
              description:
                "Unique identifier for this branch of the experiment.",
              type: "string",
              pattern: "^[A-Za-z0-9\\-_]+$",
            },
            ratio: {
              description:
                "Ratio of users who should be grouped into this branch.",
              type: "integer",
              minimum: 1,
            },
            extensionApiId: {
              description:
                "The record ID of the add-on uploaded to the Normandy server. May be null, in which case no add-on will be installed.",
              type: ["number", "null"],
              default: null,
            },
          },
        },
      },
    },
  },

  "show-heartbeat": {
    $schema: "http://json-schema.org/draft-04/schema#",
    title: "Show a Heartbeat survey.",
    description: "This action shows a single survey.",

    type: "object",
    required: [
      "surveyId",
      "message",
      "thanksMessage",
      "postAnswerUrl",
      "learnMoreMessage",
      "learnMoreUrl",
    ],
    properties: {
      repeatOption: {
        type: "string",
        enum: ["once", "xdays", "nag"],
        description: "Determines how often a prompt is shown executes.",
        default: "once",
      },
      repeatEvery: {
        description:
          "For repeatOption=xdays, how often (in days) the prompt is displayed.",
        default: null,
        type: ["number", "null"],
      },
      includeTelemetryUUID: {
        type: "boolean",
        description: "Include unique user ID in post-answer-url and Telemetry",
        default: false,
      },
      surveyId: {
        type: "string",
        description: "Slug uniquely identifying this survey in telemetry",
      },
      message: {
        description: "Message to show to the user",
        type: "string",
      },
      engagementButtonLabel: {
        description:
          "Text for the engagement button. If specified, this button will be shown instead of rating stars.",
        default: null,
        type: ["string", "null"],
      },
      thanksMessage: {
        description:
          "Thanks message to show to the user after they've rated Firefox",
        type: "string",
      },
      postAnswerUrl: {
        description:
          "URL to redirect the user to after rating Firefox or clicking the engagement button",
        default: null,
        type: ["string", "null"],
      },
      learnMoreMessage: {
        description: "Message to show to the user to learn more",
        default: null,
        type: ["string", "null"],
      },
      learnMoreUrl: {
        description: "URL to show to the user when they click Learn More",
        default: null,
        type: ["string", "null"],
      },
    },
  },

  "multi-preference-experiment": {
    $schema: "http://json-schema.org/draft-04/schema#",
    title: "Run a feature experiment activated by a set of preferences.",
    type: "object",
    required: [
      "slug",
      "userFacingName",
      "userFacingDescription",
      "branches",
      "isEnrollmentPaused",
    ],
    properties: {
      slug: {
        description: "Unique identifier for this experiment",
        type: "string",
        pattern: "^[A-Za-z0-9\\-_]+$",
      },
      userFacingName: {
        description: "User-facing name of the experiment",
        type: "string",
        minLength: 1,
      },
      userFacingDescription: {
        description: "User-facing description of the experiment",
        type: "string",
        minLength: 1,
      },
      experimentDocumentUrl: {
        description: "URL of a document describing the experiment",
        type: "string",
        format: "uri",
        default: "",
      },
      isHighPopulation: {
        description:
          "Marks the preference experiment as a high population experiment, that should be excluded from certain types of telemetry",
        type: "boolean",
        default: "false",
      },
      isEnrollmentPaused: {
        description: "If true, new users will not be enrolled in the study.",
        type: "boolean",
        default: true,
      },
      branches: {
        description: "List of experimental branches",
        type: "array",
        minItems: 1,
        items: {
          type: "object",
          required: ["slug", "ratio", "preferences"],
          properties: {
            slug: {
              description:
                "Unique identifier for this branch of the experiment",
              type: "string",
              pattern: "^[A-Za-z0-9\\-_]+$",
            },
            ratio: {
              description:
                "Ratio of users who should be grouped into this branch",
              type: "integer",
              minimum: 1,
            },
            preferences: {
              description:
                "The set of preferences to be set if this branch is chosen",
              type: "object",
              patternProperties: {
                ".*": {
                  type: "object",
                  properties: {
                    preferenceType: {
                      description:
                        "Data type of the preference that controls this experiment",
                      type: "string",
                      enum: ["string", "integer", "boolean"],
                    },
                    preferenceBranchType: {
                      description:
                        "Controls whether the default or user value of the preference is modified",
                      type: "string",
                      enum: ["user", "default"],
                      default: "default",
                    },
                    preferenceValue: {
                      description:
                        "Value for this preference when this branch is chosen",
                      type: ["string", "number", "boolean"],
                    },
                  },
                  required: [
                    "preferenceType",
                    "preferenceBranchType",
                    "preferenceValue",
                  ],
                },
              },
            },
          },
        },
      },
    },
  },

  "single-preference-experiment": {
    $schema: "http://json-schema.org/draft-04/schema#",
    title: "Run a feature experiment activated by a preference.",
    type: "object",
    required: [
      "slug",
      "preferenceName",
      "preferenceType",
      "branches",
      "isEnrollmentPaused",
    ],
    properties: {
      slug: {
        description: "Unique identifier for this experiment",
        type: "string",
        pattern: "^[A-Za-z0-9\\-_]+$",
      },
      experimentDocumentUrl: {
        description: "URL of a document describing the experiment",
        type: "string",
        format: "uri",
        default: "",
      },
      preferenceName: {
        description:
          "Full dotted-path of the preference that controls this experiment",
        type: "string",
      },
      preferenceType: {
        description:
          "Data type of the preference that controls this experiment",
        type: "string",
        enum: ["string", "integer", "boolean"],
      },
      preferenceBranchType: {
        description:
          "Controls whether the default or user value of the preference is modified",
        type: "string",
        enum: ["user", "default"],
        default: "default",
      },
      isHighPopulation: {
        description:
          "Marks the preference experiment as a high population experiment, that should be excluded from certain types of telemetry",
        type: "boolean",
        default: "false",
      },
      isEnrollmentPaused: {
        description: "If true, new users will not be enrolled in the study.",
        type: "boolean",
        default: true,
      },
      branches: {
        description: "List of experimental branches",
        type: "array",
        minItems: 1,
        items: {
          type: "object",
          required: ["slug", "value", "ratio"],
          properties: {
            slug: {
              description:
                "Unique identifier for this branch of the experiment",
              type: "string",
              pattern: "^[A-Za-z0-9\\-_]+$",
            },
            value: {
              description: "Value to set the preference to for this branch",
              type: ["string", "number", "boolean"],
            },
            ratio: {
              description:
                "Ratio of users who should be grouped into this branch",
              type: "integer",
              minimum: 1,
            },
          },
        },
      },
    },
  },
};

// Legacy name used on Normandy server
ActionSchemas["opt-out-study"] = ActionSchemas["addon-study"];

// If running in Node.js, export the schemas.
if (typeof module !== "undefined") {
  /* globals module */
  module.exports = ActionSchemas;
}
PK
!<֢D��6chrome/toolkit/res/normandy/content/AboutPages.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonStudies: "resource://normandy/lib/AddonStudies.sys.mjs",
  BranchedAddonStudyAction:
    "resource://normandy/actions/BranchedAddonStudyAction.sys.mjs",
  ExperimentManager: "resource://nimbus/lib/ExperimentManager.sys.mjs",
  PreferenceExperiments:
    "resource://normandy/lib/PreferenceExperiments.sys.mjs",
  RecipeRunner: "resource://normandy/lib/RecipeRunner.sys.mjs",
  RemoteSettingsExperimentLoader:
    "resource://nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs",
});

const SHIELD_LEARN_MORE_URL_PREF = "app.normandy.shieldLearnMoreUrl";
XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "gOptOutStudiesEnabled",
  "app.shield.optoutstudies.enabled"
);

/**
 * Class for managing an about: page that Normandy provides. Adapted from
 * browser/components/pocket/content/AboutPocket.sys.mjs.
 *
 * @implements nsIFactory
 * @implements nsIAboutModule
 */
class AboutPage {
  constructor({ chromeUrl, aboutHost, classID, description, uriFlags }) {
    this.chromeUrl = chromeUrl;
    this.aboutHost = aboutHost;
    this.classID = Components.ID(classID);
    this.description = description;
    this.uriFlags = uriFlags;
  }

  getURIFlags() {
    return this.uriFlags;
  }

  newChannel(uri, loadInfo) {
    const newURI = Services.io.newURI(this.chromeUrl);
    const channel = Services.io.newChannelFromURIWithLoadInfo(newURI, loadInfo);
    channel.originalURI = uri;

    if (this.uriFlags & Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT) {
      const principal = Services.scriptSecurityManager.createContentPrincipal(
        uri,
        {}
      );
      channel.owner = principal;
    }
    return channel;
  }
}
AboutPage.prototype.QueryInterface = ChromeUtils.generateQI(["nsIAboutModule"]);

/**
 * The module exported by this file.
 */
export let AboutPages = {};

/**
 * The weak set that keeps track of which browsing contexts
 * have an about:studies page.
 */
let BrowsingContexts = new WeakSet();
/**
 * about:studies page for displaying in-progress and past Shield studies.
 * @type {AboutPage}
 * @implements {nsIMessageListener}
 */
ChromeUtils.defineLazyGetter(AboutPages, "aboutStudies", () => {
  const aboutStudies = new AboutPage({
    chromeUrl: "resource://normandy-content/about-studies/about-studies.html",
    aboutHost: "studies",
    classID: "{6ab96943-a163-482c-9622-4faedc0e827f}",
    description: "Shield Study Listing",
    uriFlags:
      Ci.nsIAboutModule.ALLOW_SCRIPT |
      Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT |
      Ci.nsIAboutModule.URI_MUST_LOAD_IN_CHILD |
      Ci.nsIAboutModule.IS_SECURE_CHROME_UI,
  });

  // Extra methods for about:study-specific behavior.
  Object.assign(aboutStudies, {
    getAddonStudyList() {
      return lazy.AddonStudies.getAll();
    },

    getPreferenceStudyList() {
      return lazy.PreferenceExperiments.getAll();
    },

    getMessagingSystemList() {
      return lazy.ExperimentManager.store.getAll();
    },

    async optInToExperiment(data) {
      try {
        await lazy.RemoteSettingsExperimentLoader.optInToExperiment(data);
        return {
          error: false,
          message: "Opt-in was successful.",
        };
      } catch (error) {
        return {
          error: true,
          message: error.message,
        };
      }
    },

    /** Add a browsing context to the weak set;
     * this weak set keeps track of all contexts
     * that are housing an about:studies page.
     */
    addToWeakSet(browsingContext) {
      BrowsingContexts.add(browsingContext);
    },
    /** Remove a browsing context to the weak set;
     * this weak set keeps track of all contexts
     * that are housing an about:studies page.
     */
    removeFromWeakSet(browsingContext) {
      BrowsingContexts.delete(browsingContext);
    },

    /**
     * Sends a message to every about:studies page,
     * by iterating over the BrowsingContexts weakset.
     * @param {string} message The message string to send to.
     * @param {object} data The data object to send.
     */
    _sendToAll(message, data) {
      ChromeUtils.nondeterministicGetWeakSetKeys(BrowsingContexts).forEach(
        browser =>
          browser.currentWindowGlobal
            .getActor("ShieldFrame")
            .sendAsyncMessage(message, data)
      );
    },

    /**
     * Get if studies are enabled. This has to be in the parent process,
     * since RecipeRunner is stateful, and can't be interacted with from
     * content processes safely.
     */
    async getStudiesEnabled() {
      await lazy.RecipeRunner.initializedPromise.promise;
      return lazy.RecipeRunner.enabled && lazy.gOptOutStudiesEnabled;
    },

    /**
     * Disable an active add-on study and remove its add-on.
     * @param {String} recipeId the id of the addon to remove
     * @param {String} reason the reason for removal
     */
    async removeAddonStudy(recipeId, reason) {
      try {
        const action = new lazy.BranchedAddonStudyAction();
        await action.unenroll(recipeId, reason);
      } catch (err) {
        // If the exception was that the study was already removed, that's ok.
        // If not, rethrow the error.
        if (!err.toString().includes("already inactive")) {
          throw err;
        }
      } finally {
        // Update any open tabs with the new study list now that it has changed,
        // even if the above failed.
        this.getAddonStudyList().then(list =>
          this._sendToAll("Shield:UpdateAddonStudyList", list)
        );
      }
    },

    /**
     * Disable an active preference study.
     * @param {String} experimentName the name of the experiment to remove
     * @param {String} reason the reason for removal
     */
    async removePreferenceStudy(experimentName, reason) {
      try {
        await lazy.PreferenceExperiments.stop(experimentName, {
          reason,
          caller: "AboutPages.removePreferenceStudy",
        });
      } catch (err) {
        // If the exception was that the study was already removed, that's ok.
        // If not, rethrow the error.
        if (!err.toString().includes("already expired")) {
          throw err;
        }
      } finally {
        // Update any open tabs with the new study list now that it has changed,
        // even if the above failed.
        this.getPreferenceStudyList().then(list =>
          this._sendToAll("Shield:UpdatePreferenceStudyList", list)
        );
      }
    },

    async removeMessagingSystemExperiment(slug, reason) {
      lazy.ExperimentManager.unenroll(slug, reason);
      this._sendToAll(
        "Shield:UpdateMessagingSystemExperimentList",
        lazy.ExperimentManager.store.getAll()
      );
    },

    openDataPreferences() {
      const browserWindow =
        Services.wm.getMostRecentWindow("navigator:browser");
      browserWindow.openPreferences("privacy-reports");
    },

    getShieldLearnMoreHref() {
      return Services.urlFormatter.formatURLPref(SHIELD_LEARN_MORE_URL_PREF);
    },
  });

  return aboutStudies;
});
PK
!<��]��<chrome/toolkit/res/normandy/content/ShieldFrameChild.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Listen for DOM events bubbling up from the about:studies page, and perform
 * privileged actions in response to them. If we need to do anything that the
 * content process can't handle (such as reading IndexedDB), we send a message
 * to the parent process and handle it there.
 */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AboutPages: "resource://normandy-content/AboutPages.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "gBrandBundle", function () {
  return Services.strings.createBundle(
    "chrome://branding/locale/brand.properties"
  );
});

ChromeUtils.defineLazyGetter(lazy, "gStringBundle", function () {
  return Services.strings.createBundle(
    "chrome://global/locale/aboutStudies.properties"
  );
});

/**
 * Listen for DOM events bubbling up from the about:studies page, and perform
 * privileged actions in response to them. If we need to do anything that the
 * content process can't handle (such as reading IndexedDB), we send a message
 * to the parent process and handle it there.
 */
export class ShieldFrameChild extends JSWindowActorChild {
  async handleEvent(event) {
    // On page show or page hide,
    // add this child to the WeakSet in AboutStudies.
    switch (event.type) {
      case "pageshow":
        this.sendAsyncMessage("Shield:AddToWeakSet");
        return;

      case "pagehide":
        this.sendAsyncMessage("Shield:RemoveFromWeakSet");
        return;
    }
    switch (event.detail.action) {
      // Actions that require the parent process
      case "GetRemoteValue:AddonStudyList":
        let addonStudies = await this.sendQuery("Shield:GetAddonStudyList");
        this.triggerPageCallback(
          "ReceiveRemoteValue:AddonStudyList",
          addonStudies
        );
        break;
      case "GetRemoteValue:PreferenceStudyList":
        let prefStudies = await this.sendQuery("Shield:GetPreferenceStudyList");
        this.triggerPageCallback(
          "ReceiveRemoteValue:PreferenceStudyList",
          prefStudies
        );
        break;
      case "GetRemoteValue:MessagingSystemList":
        let experiments = await this.sendQuery("Shield:GetMessagingSystemList");
        this.triggerPageCallback(
          "ReceiveRemoteValue:MessagingSystemList",
          experiments
        );
        break;
      case "RemoveAddonStudy":
        this.sendAsyncMessage("Shield:RemoveAddonStudy", event.detail.data);
        break;
      case "RemovePreferenceStudy":
        this.sendAsyncMessage(
          "Shield:RemovePreferenceStudy",
          event.detail.data
        );
        break;
      case "RemoveMessagingSystemExperiment":
        this.sendAsyncMessage(
          "Shield:RemoveMessagingSystemExperiment",
          event.detail.data
        );
        break;
      case "GetRemoteValue:StudiesEnabled":
        let studiesEnabled = await this.sendQuery("Shield:GetStudiesEnabled");
        this.triggerPageCallback(
          "ReceiveRemoteValue:StudiesEnabled",
          studiesEnabled
        );
        break;
      case "NavigateToDataPreferences":
        this.sendAsyncMessage("Shield:OpenDataPreferences");
        break;
      // Actions that can be performed in the content process
      case "GetRemoteValue:ShieldLearnMoreHref":
        this.triggerPageCallback(
          "ReceiveRemoteValue:ShieldLearnMoreHref",
          lazy.AboutPages.aboutStudies.getShieldLearnMoreHref()
        );
        break;
      case "GetRemoteValue:ShieldTranslations":
        const strings = {};
        for (let str of lazy.gStringBundle.getSimpleEnumeration()) {
          strings[str.key] = str.value;
        }
        const brandName = lazy.gBrandBundle.GetStringFromName("brandShortName");
        strings.enabledList = lazy.gStringBundle.formatStringFromName(
          "enabledList",
          [brandName]
        );

        this.triggerPageCallback(
          "ReceiveRemoteValue:ShieldTranslations",
          strings
        );
        break;
      case "ExperimentOptIn":
        const message = await this.sendQuery(
          "Shield:ExperimentOptIn",
          event.detail.data
        );
        this.triggerPageCallback("ReceiveRemoteValue:OptInMessage", message);
        break;
    }
  }

  receiveMessage(msg) {
    switch (msg.name) {
      case "Shield:UpdateAddonStudyList":
        this.triggerPageCallback("ReceiveRemoteValue:AddonStudyList", msg.data);
        break;
      case "Shield:UpdatePreferenceStudyList":
        this.triggerPageCallback(
          "ReceiveRemoteValue:PreferenceStudyList",
          msg.data
        );
        break;
      case "Shield:UpdateMessagingSystemExperimentList":
        this.triggerPageCallback(
          "ReceiveRemoteValue:MessagingSystemList",
          msg.data
        );
        break;
    }
  }
  /**
   * Trigger an event to communicate with the unprivileged about:studies page.
   * @param {String} type The type of event to trigger.
   * @param {Object} detail The data to pass along to the event.
   */
  triggerPageCallback(type, detail) {
    // Clone details and use the event class from the unprivileged context.
    const event = new this.document.defaultView.CustomEvent(type, {
      bubbles: true,
      detail: Cu.cloneInto(detail, this.document.defaultView),
    });
    this.document.dispatchEvent(event);
  }
}
PK
!<�EE=chrome/toolkit/res/normandy/content/ShieldFrameParent.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AboutPages: "resource://normandy-content/AboutPages.sys.mjs",
});

export class ShieldFrameParent extends JSWindowActorParent {
  async receiveMessage(msg) {
    let { aboutStudies } = lazy.AboutPages;
    switch (msg.name) {
      case "Shield:AddToWeakSet":
        aboutStudies.addToWeakSet(this.browsingContext);
        break;
      case "Shield:RemoveFromWeakSet":
        aboutStudies.removeFromWeakSet(this.browsingContext);
        break;
      case "Shield:GetAddonStudyList":
        return aboutStudies.getAddonStudyList();
      case "Shield:GetPreferenceStudyList":
        return aboutStudies.getPreferenceStudyList();
      case "Shield:GetMessagingSystemList":
        return aboutStudies.getMessagingSystemList();
      case "Shield:RemoveAddonStudy":
        aboutStudies.removeAddonStudy(msg.data.recipeId, msg.data.reason);
        break;
      case "Shield:RemovePreferenceStudy":
        aboutStudies.removePreferenceStudy(
          msg.data.experimentName,
          msg.data.reason
        );
        break;
      case "Shield:RemoveMessagingSystemExperiment":
        aboutStudies.removeMessagingSystemExperiment(
          msg.data.slug,
          msg.data.reason
        );
        break;
      case "Shield:OpenDataPreferences":
        aboutStudies.openDataPreferences();
        break;
      case "Shield:GetStudiesEnabled":
        return aboutStudies.getStudiesEnabled();
      case "Shield:ExperimentOptIn":
        return aboutStudies.optInToExperiment(msg.data);
    }

    return null;
  }
}
PK
!<�t��mmCchrome/toolkit/res/normandy/content/about-studies/about-studies.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:root {
  --icon-background-color-1: #0A84FF;
  --icon-background-color-2: #008EA4;
  --icon-background-color-3: #ED00B5;
  --icon-background-color-4: #058B00;
  --icon-background-color-5: #A47F00;
  --icon-background-color-6: #FF0039;
  --icon-background-disabled-color: #737373;
  --body-text-disabled-color: #737373;
  --study-status-active-color: #058B00;
  --study-status-disabled-color: #737373;
}

html,
body,
#app {
  height: 100%;
  width: 100%;
}

button > .button-box {
  padding-inline: 10px;
}

.about-studies-container {
  max-width: 960px;
  margin: 0 auto;
}

.info-box {
  margin-bottom: 10px;
  text-align: center;
}

.info-box-content {
  align-items: center;
  background: var(--in-content-box-info-background);
  border: 1px solid var(--in-content-border-color);
  display: inline-flex;
  padding: 10px 15px;
}

.info-box-content > * {
  margin-right: 10px;
}

.info-box-content > *:last-child {
  margin-right: 0;
}

.study-list {
  list-style-type: none;
  margin: 0;
  padding: 0;
}

.study {
  align-items: center;
  border-bottom: 1px solid var(--in-content-border-color);
  display: flex;
  flex-direction: row;
  padding: 10px;
}

.study.disabled {
  color: var(--body-text-disabled-color);
}

.study .study-status {
  color: var(--study-status-active-color);
  font-weight: bold;
}

.study.disabled .study-status {
  color: var(--study-status-disabled-color);
}

.study:last-child {
  border-bottom: none;
}

.study > * {
  margin-right: 15px;
}

.study > *:last-child {
  margin-right: 0;
}

.study-icon {
  color: #FFF;
  flex: 0 0 40px;
  font-size: 26px;
  height: 40px;
  line-height: 40px;
  text-align: center;
  text-transform: capitalize;
}

.study:nth-child(6n+0) .study-icon {
  background: var(--icon-background-color-1);
}

.study:nth-child(6n+1) .study-icon {
  background: var(--icon-background-color-2);
}

.study:nth-child(6n+2) .study-icon {
  background: var(--icon-background-color-3);
}

.study:nth-child(6n+3) .study-icon {
  background: var(--icon-background-color-4);
}

.study:nth-child(6n+4) .study-icon {
  background: var(--icon-background-color-5);
}

.study:nth-child(6n+5) .study-icon {
  background: var(--icon-background-color-6);
}

.study.disabled .study-icon {
  background: var(--icon-background-disabled-color);
}

.study-details {
  flex: 1;
  overflow: hidden;
}

.study-name {
  font-weight: bold;
}

.study-header {
  margin-bottom: .3em;
}

.study-header > * {
  margin-right: 5px;
}

.study-header > *:last-child {
  margin-right: 0;
}

.study-description code {
  background-color: rgb(128, 128, 128, 0.1);
  border-radius: 3px;
  box-sizing: border-box;
  color: var(--in-content-text-color);
  font-size: 85%;
  font-family: 'Fira Mono', 'mono', monospace;
  padding: .05em .4em;
}

.study-actions {
  flex: 0 0;
}

.opt-in-box {
  border-radius: 3px;
  padding: 10px;
  color: var(--study-status-active-color);
  border: 1px solid;
}

.opt-in-box.opt-in-error {
  color: var(--text-color-error);
}
PK
!<�98���Dchrome/toolkit/res/normandy/content/about-studies/about-studies.html<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="color-scheme" content="light dark" />
    <meta
      http-equiv="Content-Security-Policy"
      content="default-src chrome:; script-src resource:; style-src resource: chrome:; object-src 'none'"
    />
    <title>about:studies</title>
    <link rel="stylesheet" href="chrome://global/skin/global.css" />
    <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
    <link
      rel="stylesheet"
      href="resource://normandy-content/about-studies/about-studies.css"
    />
  </head>
  <body>
    <div id="app"></div>
    <script src="resource://normandy-vendor/React.js"></script>
    <script src="resource://normandy-vendor/ReactDOM.js"></script>
    <script src="resource://normandy-vendor/PropTypes.js"></script>
    <script src="resource://normandy-vendor/classnames.js"></script>
    <script src="resource://normandy-content/about-studies/about-studies.js"></script>
  </body>
</html>
PK
!<�~�\->->Bchrome/toolkit/res/normandy/content/about-studies/about-studies.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";
/* global classnames PropTypes React ReactDOM */

/**
 * Shorthand for creating elements (to avoid using a JSX preprocessor)
 */
const r = React.createElement;

/**
 * Dispatches a page event to the privileged frame script for this tab.
 * @param {String} action
 * @param {Object} data
 */
function sendPageEvent(action, data) {
  const event = new CustomEvent("ShieldPageEvent", {
    bubbles: true,
    detail: { action, data },
  });
  document.dispatchEvent(event);
}

function readOptinParams() {
  let searchParams = new URLSearchParams(new URL(location).search);
  return {
    slug: searchParams.get("optin_slug"),
    branch: searchParams.get("optin_branch"),
    collection: searchParams.get("optin_collection"),
    applyTargeting: !!searchParams.get("apply_targeting"),
  };
}

/**
 * Handle basic layout and routing within about:studies.
 */
class AboutStudies extends React.Component {
  constructor(props) {
    super(props);

    this.remoteValueNameMap = {
      AddonStudyList: "addonStudies",
      PreferenceStudyList: "prefStudies",
      MessagingSystemList: "experiments",
      ShieldLearnMoreHref: "learnMoreHref",
      StudiesEnabled: "studiesEnabled",
      ShieldTranslations: "translations",
    };

    this.state = {};
    for (const stateName of Object.values(this.remoteValueNameMap)) {
      this.state[stateName] = null;
    }
    this.state.optInMessage = false;
  }

  initializeData() {
    for (const remoteName of Object.keys(this.remoteValueNameMap)) {
      document.addEventListener(`ReceiveRemoteValue:${remoteName}`, this);
      sendPageEvent(`GetRemoteValue:${remoteName}`);
    }
  }

  componentWillMount() {
    let optinParams = readOptinParams();
    if (optinParams.branch && optinParams.slug) {
      const onOptIn = ({ detail: value }) => {
        this.setState({ optInMessage: value });
        this.initializeData();
        document.removeEventListener(
          `ReceiveRemoteValue:OptInMessage`,
          onOptIn
        );
      };
      document.addEventListener(`ReceiveRemoteValue:OptInMessage`, onOptIn);
      sendPageEvent(`ExperimentOptIn`, optinParams);
    } else {
      this.initializeData();
    }
  }

  componentWillUnmount() {
    for (const remoteName of Object.keys(this.remoteValueNameMap)) {
      document.removeEventListener(`ReceiveRemoteValue:${remoteName}`, this);
    }
  }

  /** Event handle to receive remote values from documentAddEventListener */
  handleEvent({ type, detail: value }) {
    const prefix = "ReceiveRemoteValue:";
    if (type.startsWith(prefix)) {
      const name = type.substring(prefix.length);
      this.setState({ [this.remoteValueNameMap[name]]: value });
    }
  }

  render() {
    const {
      translations,
      learnMoreHref,
      studiesEnabled,
      addonStudies,
      prefStudies,
      experiments,
      optInMessage,
    } = this.state;
    // Wait for all values to be loaded before rendering. Some of the values may
    // be falsey, so an explicit null check is needed.
    if (Object.values(this.state).some(v => v === null)) {
      return null;
    }

    return r(
      "div",
      { className: "about-studies-container main-content" },
      r(WhatsThisBox, { translations, learnMoreHref, studiesEnabled }),
      optInMessage && r(OptInBox, optInMessage),
      r(StudyList, {
        translations,
        addonStudies,
        prefStudies,
        experiments,
      })
    );
  }
}

/**
 * Explains the contents of the page, and offers a way to learn more and update preferences.
 */
class WhatsThisBox extends React.Component {
  handleUpdateClick() {
    sendPageEvent("NavigateToDataPreferences");
  }

  render() {
    const { learnMoreHref, studiesEnabled, translations } = this.props;

    return r(
      "div",
      { className: "info-box" },
      r(
        "div",
        { className: "info-box-content" },
        r(
          "span",
          {},
          studiesEnabled ? translations.enabledList : translations.disabledList
        ),
        r(
          "a",
          { id: "shield-studies-learn-more", href: learnMoreHref },
          translations.learnMore
        ),

        r(
          "button",
          {
            id: "shield-studies-update-preferences",
            onClick: this.handleUpdateClick,
          },
          r(
            "div",
            { className: "button-box" },
            navigator.platform.includes("Win")
              ? translations.updateButtonWin
              : translations.updateButtonUnix
          )
        )
      )
    );
  }
}
/**OptInMessage
 * Explains the contents of the page, and offers a way to learn more and update preferences.
 */
function OptInBox({ error, message }) {
  return r(
    "div",
    { className: "opt-in-box" + (error ? " opt-in-error" : "") },
    message
  );
}

/**
 * Shows a list of studies, with an option to end in-progress ones.
 */
class StudyList extends React.Component {
  render() {
    const { addonStudies, prefStudies, translations, experiments } = this.props;

    if (!addonStudies.length && !prefStudies.length && !experiments.length) {
      return r("p", { className: "study-list-info" }, translations.noStudies);
    }

    const activeStudies = [];
    const inactiveStudies = [];

    // Since we are modifying the study objects, it is polite to make copies
    for (const study of addonStudies) {
      const clonedStudy = Object.assign({}, study, {
        type: "addon",
        sortDate: study.studyStartDate,
      });
      if (study.active) {
        activeStudies.push(clonedStudy);
      } else {
        inactiveStudies.push(clonedStudy);
      }
    }

    for (const study of prefStudies) {
      const clonedStudy = Object.assign({}, study, {
        type: "pref",
        sortDate: new Date(study.lastSeen),
      });
      if (study.expired) {
        inactiveStudies.push(clonedStudy);
      } else {
        activeStudies.push(clonedStudy);
      }
    }

    for (const study of experiments) {
      const clonedStudy = Object.assign({}, study, {
        type: study.experimentType,
        sortDate: new Date(study.lastSeen),
      });
      if (!study.active) {
        inactiveStudies.push(clonedStudy);
      } else {
        activeStudies.push(clonedStudy);
      }
    }

    activeStudies.sort((a, b) => b.sortDate - a.sortDate);
    inactiveStudies.sort((a, b) => b.sortDate - a.sortDate);
    return r(
      "div",
      {},
      r("h2", {}, translations.activeStudiesList),
      r(
        "ul",
        { className: "study-list active-study-list" },
        activeStudies.map(study => {
          if (study.type === "addon") {
            return r(AddonStudyListItem, {
              key: study.slug,
              study,
              translations,
            });
          }
          if (study.type === "nimbus" || study.type === "rollout") {
            return r(MessagingSystemListItem, {
              key: study.slug,
              study,
              translations,
            });
          }
          if (study.type === "pref") {
            return r(PreferenceStudyListItem, {
              key: study.slug,
              study,
              translations,
            });
          }
          return null;
        })
      ),
      r("h2", {}, translations.completedStudiesList),
      r(
        "ul",
        { className: "study-list inactive-study-list" },
        inactiveStudies.map(study => {
          if (study.type === "addon") {
            return r(AddonStudyListItem, {
              key: study.slug,
              study,
              translations,
            });
          }
          if (
            study.type === "nimbus" ||
            study.type === "messaging_experiment" ||
            study.type === "rollout"
          ) {
            return r(MessagingSystemListItem, {
              key: study.slug,
              study,
              translations,
            });
          }
          if (study.type === "pref") {
            return r(PreferenceStudyListItem, {
              key: study.slug,
              study,
              translations,
            });
          }
          return null;
        })
      )
    );
  }
}
StudyList.propTypes = {
  addonStudies: PropTypes.array.isRequired,
  translations: PropTypes.object.isRequired,
};

class MessagingSystemListItem extends React.Component {
  constructor(props) {
    super(props);
    this.handleClickRemove = this.handleClickRemove.bind(this);
  }

  handleClickRemove() {
    sendPageEvent("RemoveMessagingSystemExperiment", {
      slug: this.props.study.slug,
      reason: "individual-opt-out",
    });
  }

  render() {
    const { study, translations } = this.props;
    const userFacingName = study.userFacingName || study.slug;
    const userFacingDescription =
      study.userFacingDescription || "Nimbus experiment.";
    return r(
      "li",
      {
        className: classnames("study nimbus", {
          disabled: !study.active,
        }),
        "data-study-slug": study.slug, // used to identify this row in tests
      },
      r("div", { className: "study-icon" }, userFacingName.slice(0, 1)),
      r(
        "div",
        { className: "study-details" },
        r(
          "div",
          { className: "study-header" },
          r("span", { className: "study-name" }, userFacingName),
          r("span", {}, "\u2022"), // &bullet;
          !study.isRollout && [
            r("span", { className: "study-branch-slug" }, study.branch.slug),
            r("span", {}, "\u2022"), // &bullet;
          ],
          r(
            "span",
            { className: "study-status" },
            study.active
              ? translations.activeStatus
              : translations.completeStatus
          )
        ),
        r("div", { className: "study-description" }, userFacingDescription)
      ),
      r(
        "div",
        { className: "study-actions" },
        study.active &&
          r(
            "button",
            { className: "remove-button", onClick: this.handleClickRemove },
            r("div", { className: "button-box" }, translations.removeButton)
          )
      )
    );
  }
}

/**
 * Details about an individual add-on study, with an option to end it if it is active.
 */
class AddonStudyListItem extends React.Component {
  constructor(props) {
    super(props);
    this.handleClickRemove = this.handleClickRemove.bind(this);
  }

  handleClickRemove() {
    sendPageEvent("RemoveAddonStudy", {
      recipeId: this.props.study.recipeId,
      reason: "individual-opt-out",
    });
  }

  render() {
    const { study, translations } = this.props;
    return r(
      "li",
      {
        className: classnames("study addon-study", { disabled: !study.active }),
        "data-study-slug": study.slug, // used to identify this row in tests
      },
      r(
        "div",
        { className: "study-icon" },
        study.userFacingName
          .replace(/-?add-?on-?/i, "")
          .replace(/-?study-?/i, "")
          .slice(0, 1)
      ),
      r(
        "div",
        { className: "study-details" },
        r(
          "div",
          { className: "study-header" },
          r("span", { className: "study-name" }, study.userFacingName),
          r("span", {}, "\u2022"), // &bullet;
          r(
            "span",
            { className: "study-status" },
            study.active
              ? translations.activeStatus
              : translations.completeStatus
          )
        ),
        r(
          "div",
          { className: "study-description" },
          study.userFacingDescription
        )
      ),
      r(
        "div",
        { className: "study-actions" },
        study.active &&
          r(
            "button",
            { className: "remove-button", onClick: this.handleClickRemove },
            r("div", { className: "button-box" }, translations.removeButton)
          )
      )
    );
  }
}
AddonStudyListItem.propTypes = {
  study: PropTypes.shape({
    recipeId: PropTypes.number.isRequired,
    slug: PropTypes.string.isRequired,
    userFacingName: PropTypes.string.isRequired,
    active: PropTypes.bool.isRequired,
    userFacingDescription: PropTypes.string.isRequired,
  }).isRequired,
  translations: PropTypes.object.isRequired,
};

/**
 * Details about an individual preference study, with an option to end it if it is active.
 */
class PreferenceStudyListItem extends React.Component {
  constructor(props) {
    super(props);
    this.handleClickRemove = this.handleClickRemove.bind(this);
  }

  handleClickRemove() {
    sendPageEvent("RemovePreferenceStudy", {
      experimentName: this.props.study.slug,
      reason: "individual-opt-out",
    });
  }

  render() {
    const { study, translations } = this.props;

    let iconLetter = (study.userFacingName || study.slug)
      .replace(/-?pref-?(flip|study)-?/, "")
      .replace(/-?study-?/, "")
      .slice(0, 1)
      .toUpperCase();

    let description = study.userFacingDescription;
    if (!description) {
      // Assume there is exactly one preference (old-style preference experiment).
      const [preferenceName, { preferenceValue }] = Object.entries(
        study.preferences
      )[0];
      // Sanitize the values by setting them as the text content of an element,
      // and then getting the HTML representation of that text. This will have the
      // browser safely sanitize them. Use outerHTML to also include the <code>
      // element in the string.
      const sanitizer = document.createElement("code");
      sanitizer.textContent = preferenceName;
      const sanitizedPreferenceName = sanitizer.outerHTML;
      sanitizer.textContent = preferenceValue;
      const sanitizedPreferenceValue = sanitizer.outerHTML;
      description = translations.preferenceStudyDescription
        .replace(/%(?:1\$)?S/, sanitizedPreferenceName)
        .replace(/%(?:2\$)?S/, sanitizedPreferenceValue);
    }

    return r(
      "li",
      {
        className: classnames("study pref-study", { disabled: study.expired }),
        "data-study-slug": study.slug, // used to identify this row in tests
      },
      r("div", { className: "study-icon" }, iconLetter),
      r(
        "div",
        { className: "study-details" },
        r(
          "div",
          { className: "study-header" },
          r(
            "span",
            { className: "study-name" },
            study.userFacingName || study.slug
          ),
          r("span", {}, "\u2022"), // &bullet;
          r(
            "span",
            { className: "study-status" },
            study.expired
              ? translations.completeStatus
              : translations.activeStatus
          )
        ),
        r("div", {
          className: "study-description",
          dangerouslySetInnerHTML: { __html: description },
        })
      ),
      r(
        "div",
        { className: "study-actions" },
        !study.expired &&
          r(
            "button",
            { className: "remove-button", onClick: this.handleClickRemove },
            r("div", { className: "button-box" }, translations.removeButton)
          )
      )
    );
  }
}
PreferenceStudyListItem.propTypes = {
  study: PropTypes.shape({
    slug: PropTypes.string.isRequired,
    userFacingName: PropTypes.string,
    userFacingDescription: PropTypes.string,
    expired: PropTypes.bool.isRequired,
    preferenceName: PropTypes.string.isRequired,
    preferenceValue: PropTypes.oneOf(
      PropTypes.string,
      PropTypes.bool,
      PropTypes.number
    ).isRequired,
  }).isRequired,
  translations: PropTypes.object.isRequired,
};

ReactDOM.render(r(AboutStudies), document.getElementById("app"));
PK
!<�dc��
�
6chrome/toolkit/res/normandy/lib/ActionsManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

import { LogManager } from "resource://normandy/lib/LogManager.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonRollbackAction:
    "resource://normandy/actions/AddonRollbackAction.sys.mjs",
  AddonRolloutAction: "resource://normandy/actions/AddonRolloutAction.sys.mjs",
  BaseAction: "resource://normandy/actions/BaseAction.sys.mjs",
  BranchedAddonStudyAction:
    "resource://normandy/actions/BranchedAddonStudyAction.sys.mjs",
  ConsoleLogAction: "resource://normandy/actions/ConsoleLogAction.sys.mjs",
  MessagingExperimentAction:
    "resource://normandy/actions/MessagingExperimentAction.sys.mjs",
  PreferenceExperimentAction:
    "resource://normandy/actions/PreferenceExperimentAction.sys.mjs",
  PreferenceRollbackAction:
    "resource://normandy/actions/PreferenceRollbackAction.sys.mjs",
  PreferenceRolloutAction:
    "resource://normandy/actions/PreferenceRolloutAction.sys.mjs",
  ShowHeartbeatAction:
    "resource://normandy/actions/ShowHeartbeatAction.sys.mjs",
  Uptake: "resource://normandy/lib/Uptake.sys.mjs",
});

const log = LogManager.getLogger("recipe-runner");

/**
 * A class to manage the actions that recipes can use in Normandy.
 */
export class ActionsManager {
  constructor() {
    this.finalized = false;

    this.localActions = {};
    for (const [name, Constructor] of Object.entries(
      ActionsManager.actionConstructors
    )) {
      this.localActions[name] = new Constructor();
    }
  }

  static actionConstructors = {
    "addon-rollback": lazy.AddonRollbackAction,
    "addon-rollout": lazy.AddonRolloutAction,
    "branched-addon-study": lazy.BranchedAddonStudyAction,
    "console-log": lazy.ConsoleLogAction,
    "messaging-experiment": lazy.MessagingExperimentAction,
    "multi-preference-experiment": lazy.PreferenceExperimentAction,
    "preference-rollback": lazy.PreferenceRollbackAction,
    "preference-rollout": lazy.PreferenceRolloutAction,
    "show-heartbeat": lazy.ShowHeartbeatAction,
  };

  static getCapabilities() {
    // Prefix each action name with "action." to turn it into a capability name.
    let capabilities = new Set();
    for (const actionName of Object.keys(ActionsManager.actionConstructors)) {
      capabilities.add(`action.${actionName}`);
    }
    return capabilities;
  }

  async processRecipe(recipe, suitability) {
    let actionName = recipe.action;

    if (actionName in this.localActions) {
      log.info(`Executing recipe "${recipe.name}" (action=${recipe.action})`);
      const action = this.localActions[actionName];
      await action.processRecipe(recipe, suitability);

      // If the recipe doesn't have matching capabilities, then a missing action
      // is expected. In this case, don't send an error
    } else if (
      suitability !== lazy.BaseAction.suitability.CAPABILITIES_MISMATCH
    ) {
      log.error(
        `Could not execute recipe ${recipe.name}:`,
        `Action ${recipe.action} is either missing or invalid.`
      );
      await lazy.Uptake.reportRecipe(recipe, lazy.Uptake.RECIPE_INVALID_ACTION);
    }
  }

  async finalize(options) {
    if (this.finalized) {
      throw new Error("ActionsManager has already been finalized");
    }
    this.finalized = true;

    // Finalize local actions
    for (const action of Object.values(this.localActions)) {
      action.finalize(options);
    }
  }
}
PK
!<�~Y���5chrome/toolkit/res/normandy/lib/AddonRollouts.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  IndexedDB: "resource://gre/modules/IndexedDB.sys.mjs",
  TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
});

/**
 * AddonRollouts store info about an active or expired addon rollouts.
 * @typedef {object} AddonRollout
 * @property {int} recipeId
 *   The ID of the recipe.
 * @property {string} slug
 *   Unique slug of the rollout.
 * @property {string} state
 *   The current state of the rollout: "active", or "rolled-back".
 *   Active means that Normandy is actively managing therollout. Rolled-back
 *   means that the rollout was previously active, but has been rolled back for
 *   this user.
 * @property {int} extensionApiId
 *   The ID used to look up the extension in Normandy's API.
 * @property {string} addonId
 *   The add-on ID for this particular rollout.
 * @property {string} addonVersion
 *   The rollout add-on version number
 * @property {string} xpiUrl
 *   URL that the add-on was installed from.
 * @property {string} xpiHash
 *   The hash of the XPI file.
 * @property {string} xpiHashAlgorithm
 *   The algorithm used to hash the XPI file.
 */

const DB_NAME = "normandy-addon-rollout";
const STORE_NAME = "addon-rollouts";
const DB_VERSION = 1;

/**
 * Create a new connection to the database.
 */
function openDatabase() {
  return lazy.IndexedDB.open(DB_NAME, DB_VERSION, db => {
    db.createObjectStore(STORE_NAME, {
      keyPath: "slug",
    });
  });
}

/**
 * Cache the database connection so that it is shared among multiple operations.
 */
let databasePromise;
function getDatabase() {
  if (!databasePromise) {
    databasePromise = openDatabase();
  }
  return databasePromise;
}

/**
 * Get a transaction for interacting with the rollout store.
 *
 * @param {IDBDatabase} db
 * @param {String} mode Either "readonly" or "readwrite"
 *
 * NOTE: Methods on the store returned by this function MUST be called
 * synchronously, otherwise the transaction with the store will expire.
 * This is why the helper takes a database as an argument; if we fetched the
 * database in the helper directly, the helper would be async and the
 * transaction would expire before methods on the store were called.
 */
function getStore(db, mode) {
  if (!mode) {
    throw new Error("mode is required");
  }
  return db.objectStore(STORE_NAME, mode);
}

export const AddonRollouts = {
  STATE_ACTIVE: "active",
  STATE_ROLLED_BACK: "rolled-back",

  async init() {
    for (const rollout of await this.getAllActive()) {
      lazy.TelemetryEnvironment.setExperimentActive(
        rollout.slug,
        rollout.state,
        {
          type: "normandy-addonrollout",
        }
      );
    }
  },

  /**
   * Add a new rollout
   * @param {AddonRollout} rollout
   */
  async add(rollout) {
    const db = await getDatabase();
    return getStore(db, "readwrite").add(rollout);
  },

  /**
   * Update an existing rollout
   * @param {AddonRollout} rollout
   * @throws If a matching rollout does not exist.
   */
  async update(rollout) {
    if (!(await this.has(rollout.slug))) {
      throw new Error(
        `Tried to update ${rollout.slug}, but it doesn't already exist.`
      );
    }
    const db = await getDatabase();
    return getStore(db, "readwrite").put(rollout);
  },

  /**
   * Update many existing rollouts. More efficient than calling `update` many
   * times in a row.
   * @param {Array<PreferenceRollout>} rollouts
   * @throws If any of the passed rollouts have a slug that doesn't exist in the database already.
   */
  async updateMany(rollouts) {
    // Don't touch the database if there is nothing to do
    if (!rollouts.length) {
      return;
    }

    // Both of the below operations use .map() instead of a normal loop becaues
    // once we get the object store, we can't let it expire by spinning the
    // event loop. This approach queues up all the interactions with the store
    // immediately, preventing it from expiring too soon.

    const db = await getDatabase();
    let store = await getStore(db, "readonly");
    await Promise.all(
      rollouts.map(async ({ slug }) => {
        let existingRollout = await store.get(slug);
        if (!existingRollout) {
          throw new Error(`Tried to update ${slug}, but it doesn't exist.`);
        }
      })
    );

    // awaiting spun the event loop, so the store is now invalid. Get a new
    // store. This is also a chance to get it in readwrite mode.
    store = await getStore(db, "readwrite");
    await Promise.all(rollouts.map(rollout => store.put(rollout)));
  },

  /**
   * Test whether there is a rollout in storage with the given slug.
   * @param {string} slug
   * @returns {Promise<boolean>}
   */
  async has(slug) {
    const db = await getDatabase();
    const rollout = await getStore(db, "readonly").get(slug);
    return !!rollout;
  },

  /**
   * Get a rollout by slug
   * @param {string} slug
   */
  async get(slug) {
    const db = await getDatabase();
    return getStore(db, "readonly").get(slug);
  },

  /** Get all rollouts in the database. */
  async getAll() {
    const db = await getDatabase();
    return getStore(db, "readonly").getAll();
  },

  /** Get all rollouts in the "active" state. */
  async getAllActive() {
    const rollouts = await this.getAll();
    return rollouts.filter(rollout => rollout.state === this.STATE_ACTIVE);
  },

  /**
   * Test wrapper that temporarily replaces the stored rollout data with fake
   * data for testing.
   */
  withTestMock() {
    return function (testFunction) {
      return async function inner(...args) {
        let db = await getDatabase();
        const oldData = await getStore(db, "readonly").getAll();
        await getStore(db, "readwrite").clear();
        try {
          await testFunction(...args);
        } finally {
          db = await getDatabase();
          await getStore(db, "readwrite").clear();
          const store = getStore(db, "readwrite");
          await Promise.all(oldData.map(d => store.add(d)));
        }
      };
    };
  },
};
PK
!<f>��9�94chrome/toolkit/res/normandy/lib/AddonStudies.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * @typedef {Object} Study
 * @property {Number} recipeId
 *   ID of the recipe that created the study. Used as the primary key of the
 *   study.
 * @property {Number} slug
 *   String code used to identify the study for use in Telemetry and logging.
 * @property {string} userFacingName
 *   Name of the study to show to the user
 * @property {string} userFacingDescription
 *   Description of the study and its intent.
 * @property {string} branch
 *   The branch the user is enrolled in
 * @property {boolean} active
 *   Is the study still running?
 * @property {string} addonId
 *   Add-on ID for this particular study.
 * @property {string} addonUrl
 *   URL that the study add-on was installed from.
 * @property {string} addonVersion
 *   Study add-on version number
 * @property {int} extensionApiId
 *   The ID used to look up the extension in Normandy's API.
 * @property {string} extensionHash
 *   The hash of the XPI file.
 * @property {string} extensionHashAlgorithm
 *   The algorithm used to hash the XPI file.
 * @property {Date} studyStartDate
 *   Date when the study was started.
 * @property {Date|null} studyEndDate
 *   Date when the study was ended.
 * @property {Date|null} temporaryErrorDeadline
 *   Date of when temporary errors with this experiment should no longer be
 *   considered temporary. After this point, further errors will result in
 *   unenrollment.
 */

import { LogManager } from "resource://normandy/lib/LogManager.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
  BranchedAddonStudyAction:
    "resource://normandy/actions/BranchedAddonStudyAction.sys.mjs",
  CleanupManager: "resource://normandy/lib/CleanupManager.sys.mjs",
  IndexedDB: "resource://gre/modules/IndexedDB.sys.mjs",
  TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
  TelemetryEvents: "resource://normandy/lib/TelemetryEvents.sys.mjs",
});

const DB_NAME = "shield";
const STORE_NAME = "addon-studies";
const VERSION_STORE_NAME = "addon-studies-version";
const DB_VERSION = 2;
const STUDY_ENDED_TOPIC = "shield-study-ended";
const log = LogManager.getLogger("addon-studies");

/**
 * Create a new connection to the database.
 */
function openDatabase() {
  return lazy.IndexedDB.open(DB_NAME, DB_VERSION, async (db, event) => {
    if (event.oldVersion < 1) {
      db.createObjectStore(STORE_NAME, {
        keyPath: "recipeId",
      });
    }

    if (event.oldVersion < 2) {
      db.createObjectStore(VERSION_STORE_NAME);
    }
  });
}

/**
 * Cache the database connection so that it is shared among multiple operations.
 */
let databasePromise;
async function getDatabase() {
  if (!databasePromise) {
    databasePromise = openDatabase();
  }
  return databasePromise;
}

/**
 * Get a transaction for interacting with the study store.
 *
 * @param {IDBDatabase} db
 * @param {String} mode Either "readonly" or "readwrite"
 *
 * NOTE: Methods on the store returned by this function MUST be called
 * synchronously, otherwise the transaction with the store will expire.
 * This is why the helper takes a database as an argument; if we fetched the
 * database in the helper directly, the helper would be async and the
 * transaction would expire before methods on the store were called.
 */
function getStore(db, mode) {
  if (!mode) {
    throw new Error("mode is required");
  }
  return db.objectStore(STORE_NAME, mode);
}

export var AddonStudies = {
  /**
   * Test wrapper that temporarily replaces the stored studies with the given
   * ones. The original stored studies are restored upon completion.
   *
   * This is defined here instead of in test code since it needs to access the
   * getDatabase, which we don't expose to avoid outside modules relying on the
   * type of storage used for studies.
   *
   * @param {Array} [addonStudies=[]]
   */
  withStudies(addonStudies = []) {
    return function wrapper(testFunction) {
      return async function wrappedTestFunction(args) {
        const oldStudies = await AddonStudies.getAll();
        let db = await getDatabase();
        await AddonStudies.clear();
        const store = getStore(db, "readwrite");
        await Promise.all(addonStudies.map(study => store.add(study)));

        try {
          await testFunction({ ...args, addonStudies });
        } finally {
          db = await getDatabase();
          await AddonStudies.clear();
          const store = getStore(db, "readwrite");
          await Promise.all(oldStudies.map(study => store.add(study)));
        }
      };
    };
  },

  async init() {
    for (const study of await this.getAllActive()) {
      // If an active study's add-on has been removed since we last ran, stop it.
      const addon = await lazy.AddonManager.getAddonByID(study.addonId);
      if (!addon) {
        await this.markAsEnded(study, "uninstalled-sideload");
        continue;
      }

      // Otherwise mark that study as active in Telemetry
      lazy.TelemetryEnvironment.setExperimentActive(study.slug, study.branch, {
        type: "normandy-addonstudy",
      });
    }

    // Listen for add-on uninstalls so we can stop the corresponding studies.
    lazy.AddonManager.addAddonListener(this);
    lazy.CleanupManager.addCleanupHandler(() => {
      lazy.AddonManager.removeAddonListener(this);
    });
  },

  /**
   * These migrations should only be called from `NormandyMigrations.sys.mjs` and
   * tests.
   */
  migrations: {
    /**
     * Change from "name" and "description" to "slug", "userFacingName",
     * and "userFacingDescription".
     */
    async migration01AddonStudyFieldsToSlugAndUserFacingFields() {
      const db = await getDatabase();
      const studies = await db.objectStore(STORE_NAME, "readonly").getAll();

      // If there are no studies, stop here to avoid opening the DB again.
      if (studies.length === 0) {
        return;
      }

      // Object stores expire after `await`, so this method accumulates a bunch of
      // promises, and then awaits them at the end.
      const writePromises = [];
      const objectStore = db.objectStore(STORE_NAME, "readwrite");

      for (const study of studies) {
        // use existing name as slug
        if (!study.slug) {
          study.slug = study.name;
        }

        // Rename `name` and `description` as `userFacingName` and `userFacingDescription`
        if (study.name && !study.userFacingName) {
          study.userFacingName = study.name;
        }
        delete study.name;
        if (study.description && !study.userFacingDescription) {
          study.userFacingDescription = study.description;
        }
        delete study.description;

        // Specify that existing recipes don't have branches
        if (!study.branch) {
          study.branch = AddonStudies.NO_BRANCHES_MARKER;
        }

        writePromises.push(objectStore.put(study));
      }

      await Promise.all(writePromises);
    },

    async migration02RemoveOldAddonStudyAction() {
      const studies = await AddonStudies.getAllActive({
        branched: AddonStudies.FILTER_NOT_BRANCHED,
      });
      if (!studies.length) {
        return;
      }
      const action = new lazy.BranchedAddonStudyAction();
      for (const study of studies) {
        try {
          await action.unenroll(
            study.recipeId,
            "migration-removing-unbranched-action"
          );
        } catch (e) {
          log.error(
            `Stopping add-on study ${study.slug} during migration failed: ${e}`
          );
        }
      }
    },
  },

  /**
   * If a study add-on is uninstalled, mark the study as having ended.
   * @param {Addon} addon
   */
  async onUninstalled(addon) {
    const activeStudies = (await this.getAll()).filter(study => study.active);
    const matchingStudy = activeStudies.find(
      study => study.addonId === addon.id
    );
    if (matchingStudy) {
      await this.markAsEnded(matchingStudy, "uninstalled");
    }
  },

  /**
   * Remove all stored studies.
   */
  async clear() {
    const db = await getDatabase();
    await getStore(db, "readwrite").clear();
  },

  /**
   * Test whether there is a study in storage for the given recipe ID.
   * @param {Number} recipeId
   * @returns {Boolean}
   */
  async has(recipeId) {
    const db = await getDatabase();
    const study = await getStore(db, "readonly").get(recipeId);
    return !!study;
  },

  /**
   * Fetch a study from storage.
   * @param {Number} recipeId
   * @return {Study} The requested study, or null if none with that ID exist.
   */
  async get(recipeId) {
    const db = await getDatabase();
    return getStore(db, "readonly").get(recipeId);
  },

  FILTER_BRANCHED_ONLY: Symbol("FILTER_BRANCHED_ONLY"),
  FILTER_NOT_BRANCHED: Symbol("FILTER_NOT_BRANCHED"),
  FILTER_ALL: Symbol("FILTER_ALL"),

  /**
   * Fetch all studies in storage.
   * @return {Array<Study>}
   */
  async getAll({ branched = AddonStudies.FILTER_ALL } = {}) {
    const db = await getDatabase();
    let results = await getStore(db, "readonly").getAll();

    if (branched == AddonStudies.FILTER_BRANCHED_ONLY) {
      results = results.filter(
        study => study.branch != AddonStudies.NO_BRANCHES_MARKER
      );
    } else if (branched == AddonStudies.FILTER_NOT_BRANCHED) {
      results = results.filter(
        study => study.branch == AddonStudies.NO_BRANCHES_MARKER
      );
    }
    return results;
  },

  /**
   * Fetch all studies in storage.
   * @return {Array<Study>}
   */
  async getAllActive(options) {
    return (await this.getAll(options)).filter(study => study.active);
  },

  /**
   * Add a study to storage.
   * @return {Promise<void, Error>} Resolves when the study is stored, or rejects with an error.
   */
  async add(study) {
    const db = await getDatabase();
    return getStore(db, "readwrite").add(study);
  },

  /**
   * Update a study in storage.
   * @return {Promise<void, Error>} Resolves when the study is updated, or rejects with an error.
   */
  async update(study) {
    const db = await getDatabase();
    return getStore(db, "readwrite").put(study);
  },

  /**
   * Update many existing studies. More efficient than calling `update` many
   * times in a row.
   * @param {Array<AddonStudy>} studies
   * @throws If any of the passed studies have a slug that doesn't exist in the database already.
   */
  async updateMany(studies) {
    // Don't touch the database if there is nothing to do
    if (!studies.length) {
      return;
    }

    // Both of the below operations use .map() instead of a normal loop becaues
    // once we get the object store, we can't let it expire by spinning the
    // event loop. This approach queues up all the interactions with the store
    // immediately, preventing it from expiring too soon.

    const db = await getDatabase();
    let store = await getStore(db, "readonly");
    await Promise.all(
      studies.map(async ({ recipeId }) => {
        let existingStudy = await store.get(recipeId);
        if (!existingStudy) {
          throw new Error(
            `Tried to update addon study ${recipeId}, but it doesn't exist.`
          );
        }
      })
    );

    // awaiting spun the event loop, so the store is now invalid. Get a new
    // store. This is also a chance to get it in readwrite mode.
    store = await getStore(db, "readwrite");
    await Promise.all(studies.map(study => store.put(study)));
  },

  /**
   * Remove a study from storage
   * @param recipeId The recipeId of the study to delete
   * @return {Promise<void, Error>} Resolves when the study is deleted, or rejects with an error.
   */
  async delete(recipeId) {
    const db = await getDatabase();
    return getStore(db, "readwrite").delete(recipeId);
  },

  /**
   * Mark a study object as having ended. Modifies the study in-place.
   * @param {IDBDatabase} db
   * @param {Study} study
   * @param {String} reason Why the study is ending.
   */
  async markAsEnded(study, reason = "unknown") {
    if (reason === "unknown") {
      log.warn(`Study ${study.slug} ending for unknown reason.`);
    }

    study.active = false;
    study.temporaryErrorDeadline = null;
    study.studyEndDate = new Date();
    const db = await getDatabase();
    await getStore(db, "readwrite").put(study);

    Services.obs.notifyObservers(study, STUDY_ENDED_TOPIC, `${study.recipeId}`);
    lazy.TelemetryEvents.sendEvent("unenroll", "addon_study", study.slug, {
      addonId: study.addonId || AddonStudies.NO_ADDON_MARKER,
      addonVersion: study.addonVersion || AddonStudies.NO_ADDON_MARKER,
      reason,
      branch: study.branch,
    });
    lazy.TelemetryEnvironment.setExperimentInactive(study.slug);

    await this.callUnenrollListeners(study.addonId, reason);
  },

  // Maps extension id -> Set(callbacks)
  _unenrollListeners: new Map(),

  /**
   * Register a callback to be invoked when a given study ends.
   *
   * @param {string} id         The extension id
   * @param {function} listener The callback
   */
  addUnenrollListener(id, listener) {
    let listeners = this._unenrollListeners.get(id);
    if (!listeners) {
      listeners = new Set();
      this._unenrollListeners.set(id, listeners);
    }
    listeners.add(listener);
  },

  /**
   * Unregister a callback to be invoked when a given study ends.
   *
   * @param {string} id         The extension id
   * @param {function} listener The callback
   */
  removeUnenrollListener(id, listener) {
    let listeners = this._unenrollListeners.get(id);
    if (listeners) {
      listeners.delete(listener);
    }
  },

  /**
   * Invoke the unenroll callback (if any) for the given extension
   *
   * @param {string} id The extension id
   * @param {string} reason Why the study is ending
   *
   * @returns {Promise} A Promise resolved after the unenroll listener
   *                    (if any) has finished its unenroll tasks.
   */
  async callUnenrollListeners(id, reason) {
    let callbacks = this._unenrollListeners.get(id) || [];

    async function callCallback(cb, reason) {
      try {
        await cb(reason);
      } catch (err) {
        console.error(err);
      }
    }

    let promises = [];
    for (let callback of callbacks) {
      promises.push(callCallback(callback, reason));
    }

    // Wait for all the promises to be settled. This won't throw even if some of
    // the listeners fail.
    await Promise.all(promises);
  },
};

AddonStudies.NO_BRANCHES_MARKER = "__NO_BRANCHES__";
AddonStudies.NO_ADDON_MARKER = "__NO_ADDON__";
PK
!<,h�/��6chrome/toolkit/res/normandy/lib/CleanupManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AsyncShutdown: "resource://gre/modules/AsyncShutdown.sys.mjs",
});

class CleanupManagerClass {
  constructor() {
    this.handlers = new Set();
    this.cleanupPromise = null;
  }

  addCleanupHandler(handler) {
    this.handlers.add(handler);
  }

  removeCleanupHandler(handler) {
    this.handlers.delete(handler);
  }

  async cleanup() {
    if (this.cleanupPromise === null) {
      this.cleanupPromise = (async () => {
        for (const handler of this.handlers) {
          try {
            await handler();
          } catch (ex) {
            console.error(ex);
          }
        }
      })();

      // Block shutdown to ensure any cleanup tasks that write data are
      // finished.
      lazy.AsyncShutdown.profileBeforeChange.addBlocker(
        "ShieldRecipeClient: Cleaning up",
        this.cleanupPromise
      );
    }

    return this.cleanupPromise;
  }
}

export var CleanupManager = new CleanupManagerClass();
PK
!<���
9chrome/toolkit/res/normandy/lib/ClientEnvironment.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { ClientEnvironmentBase } from "resource://gre/modules/components-utils/ClientEnvironment.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonRollouts: "resource://normandy/lib/AddonRollouts.sys.mjs",
  AddonStudies: "resource://normandy/lib/AddonStudies.sys.mjs",
  NormandyApi: "resource://normandy/lib/NormandyApi.sys.mjs",
  PreferenceExperiments:
    "resource://normandy/lib/PreferenceExperiments.sys.mjs",
  PreferenceRollouts: "resource://normandy/lib/PreferenceRollouts.sys.mjs",
});

// Cached API request for client attributes that are determined by the Normandy
// service.
let _classifyRequest = null;

export class ClientEnvironment extends ClientEnvironmentBase {
  /**
   * Fetches information about the client that is calculated on the server,
   * like geolocation and the current time.
   *
   * The server request is made lazily and is cached for the entire browser
   * session.
   */
  static async getClientClassification() {
    if (!_classifyRequest) {
      _classifyRequest = lazy.NormandyApi.classifyClient();
    }
    return _classifyRequest;
  }

  static clearClassifyCache() {
    _classifyRequest = null;
  }

  /**
   * Test wrapper that mocks the server request for classifying the client.
   * @param  {Object}   data          Fake server data to use
   * @param  {Function} testFunction  Test function to execute while mock data is in effect.
   */
  static withMockClassify(data, testFunction) {
    return async function inner() {
      const oldRequest = _classifyRequest;
      _classifyRequest = Promise.resolve(data);
      await testFunction();
      _classifyRequest = oldRequest;
    };
  }

  static get userId() {
    return ClientEnvironment.randomizationId;
  }

  static get country() {
    return (async () => {
      const { country } = await ClientEnvironment.getClientClassification();
      return country;
    })();
  }

  static get request_time() {
    return (async () => {
      const { request_time } =
        await ClientEnvironment.getClientClassification();
      return request_time;
    })();
  }

  static get experiments() {
    return (async () => {
      const names = { all: [], active: [], expired: [] };

      for (const {
        slug,
        expired,
      } of await lazy.PreferenceExperiments.getAll()) {
        names.all.push(slug);
        if (expired) {
          names.expired.push(slug);
        } else {
          names.active.push(slug);
        }
      }

      return names;
    })();
  }

  static get studies() {
    return (async () => {
      const rv = { pref: {}, addon: {} };
      for (const prefStudy of await lazy.PreferenceExperiments.getAll()) {
        rv.pref[prefStudy.slug] = prefStudy;
      }
      for (const addonStudy of await lazy.AddonStudies.getAll()) {
        rv.addon[addonStudy.slug] = addonStudy;
      }
      return rv;
    })();
  }

  static get rollouts() {
    return (async () => {
      const rv = { pref: {}, addon: {} };
      for (const prefRollout of await lazy.PreferenceRollouts.getAll()) {
        rv.pref[prefRollout.slug] = prefRollout;
      }
      for (const addonRollout of await lazy.AddonRollouts.getAll()) {
        rv.addon[addonRollout.slug] = addonRollout;
      }
      return rv;
    })();
  }

  static get isFirstRun() {
    return Services.prefs.getBoolPref("app.normandy.first_run", true);
  }
}
PK
!<FL"w��4chrome/toolkit/res/normandy/lib/EventEmitter.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { LogManager } from "resource://normandy/lib/LogManager.sys.mjs";

const log = LogManager.getLogger("event-emitter");

export var EventEmitter = function () {
  const listeners = {};

  return {
    emit(eventName, event) {
      // Fire events async
      Promise.resolve().then(() => {
        if (!(eventName in listeners)) {
          log.debug(
            `EventEmitter: Event fired with no listeners: ${eventName}`
          );
          return;
        }
        // Clone callbacks array to avoid problems with mutation while iterating
        const callbacks = Array.from(listeners[eventName]);
        for (const cb of callbacks) {
          // Clone event so it can't by modified by the handler
          let eventToPass = event;
          if (typeof event === "object") {
            eventToPass = Object.assign({}, event);
          }
          cb(eventToPass);
        }
      });
    },

    on(eventName, callback) {
      if (!(eventName in listeners)) {
        listeners[eventName] = [];
      }
      listeners[eventName].push(callback);
    },

    off(eventName, callback) {
      if (eventName in listeners) {
        const index = listeners[eventName].indexOf(callback);
        if (index !== -1) {
          listeners[eventName].splice(index, 1);
        }
      }
    },

    once(eventName, callback) {
      const inner = event => {
        callback(event);
        this.off(eventName, inner);
      };
      this.on(eventName, inner);
    },
  };
};
PK
!<��j1j11chrome/toolkit/res/normandy/lib/Heartbeat.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { TelemetryController } from "resource://gre/modules/TelemetryController.sys.mjs";
import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs";
import { CleanupManager } from "resource://normandy/lib/CleanupManager.sys.mjs";
import { EventEmitter } from "resource://normandy/lib/EventEmitter.sys.mjs";
import { LogManager } from "resource://normandy/lib/LogManager.sys.mjs";

const PREF_SURVEY_DURATION = "browser.uitour.surveyDuration";
const NOTIFICATION_TIME = 3000;
const HEARTBEAT_CSS_URI = Services.io.newURI(
  "resource://normandy/skin/shared/Heartbeat.css"
);
const log = LogManager.getLogger("heartbeat");
const windowsWithInjectedCss = new WeakSet();
let anyWindowsWithInjectedCss = false;

// Add cleanup handler for CSS injected into windows by Heartbeat
CleanupManager.addCleanupHandler(() => {
  if (anyWindowsWithInjectedCss) {
    for (let window of Services.wm.getEnumerator("navigator:browser")) {
      if (windowsWithInjectedCss.has(window)) {
        const utils = window.windowUtils;
        utils.removeSheet(HEARTBEAT_CSS_URI, window.AGENT_SHEET);
        windowsWithInjectedCss.delete(window);
      }
    }
  }
});

/**
 * Show the Heartbeat UI to request user feedback.
 *
 * @param chromeWindow
 *        The chrome window that the heartbeat notification is displayed in.
 * @param {Object} options Options object.
 * @param {String} options.message
 *        The message, or question, to display on the notification.
 * @param {String} options.thanksMessage
 *        The thank you message to display after user votes.
 * @param {String} options.flowId
 *        An identifier for this rating flow. Please note that this is only used to
 *        identify the notification box.
 * @param {String} [options.engagementButtonLabel=null]
 *        The text of the engagement button to use instead of stars. If this is null
 *        or invalid, rating stars are used.
 * @param {String} [options.learnMoreMessage=null]
 *        The label of the learn more link. No link will be shown if this is null.
 * @param {String} [options.learnMoreUrl=null]
 *        The learn more URL to open when clicking on the learn more link. No learn more
 *        will be shown if this is an invalid URL.
 * @param {String} [options.surveyId]
 *        An ID for the survey, reflected in the Telemetry ping.
 * @param {Number} [options.surveyVersion]
 *        Survey's version number, reflected in the Telemetry ping.
 * @param {boolean} [options.testing]
 *        Whether this is a test survey, reflected in the Telemetry ping.
 * @param {String} [options.postAnswerURL=null]
 *        The url to visit after the user answers the question.
 */
export var Heartbeat = class {
  constructor(chromeWindow, options) {
    if (typeof options.flowId !== "string") {
      throw new Error(
        `flowId must be a string, but got ${JSON.stringify(
          options.flowId
        )}, a ${typeof options.flowId}`
      );
    }

    if (!options.flowId) {
      throw new Error("flowId must not be an empty string");
    }

    if (typeof options.message !== "string") {
      throw new Error(
        `message must be a string, but got ${JSON.stringify(
          options.message
        )}, a ${typeof options.message}`
      );
    }

    if (!options.message) {
      throw new Error("message must not be an empty string");
    }

    if (options.postAnswerUrl) {
      options.postAnswerUrl = new URL(options.postAnswerUrl);
    } else {
      options.postAnswerUrl = null;
    }

    if (options.learnMoreUrl) {
      try {
        options.learnMoreUrl = new URL(options.learnMoreUrl);
      } catch (e) {
        options.learnMoreUrl = null;
      }
    }

    this.chromeWindow = chromeWindow;
    this.eventEmitter = new EventEmitter();
    this.options = options;
    this.surveyResults = {};
    this.buttons = [];

    if (!windowsWithInjectedCss.has(chromeWindow)) {
      windowsWithInjectedCss.add(chromeWindow);
      const utils = chromeWindow.windowUtils;
      utils.loadSheet(HEARTBEAT_CSS_URI, chromeWindow.AGENT_SHEET);
      anyWindowsWithInjectedCss = true;
    }

    // so event handlers are consistent
    this.handleWindowClosed = this.handleWindowClosed.bind(this);
    this.close = this.close.bind(this);

    // Add Learn More Link
    if (this.options.learnMoreMessage && this.options.learnMoreUrl) {
      this.buttons.push({
        link: this.options.learnMoreUrl.toString(),
        label: this.options.learnMoreMessage,
        callback: () => {
          this.maybeNotifyHeartbeat("LearnMore");
          return true;
        },
      });
    }

    if (this.options.engagementButtonLabel) {
      this.buttons.push({
        label: this.options.engagementButtonLabel,
        callback: () => {
          // Let the consumer know user engaged.
          this.maybeNotifyHeartbeat("Engaged");

          this.userEngaged({
            type: "button",
            flowId: this.options.flowId,
          });

          // Return true so that the notification bar doesn't close itself since
          // we have a thank you message to show.
          return true;
        },
      });
    }

    // Build the heartbeat stars
    if (!this.options.engagementButtonLabel) {
      const numStars = this.options.engagementButtonLabel ? 0 : 5;
      this.ratingContainer = this.chromeWindow.document.createElement("span");
      this.ratingContainer.id = "star-rating-container";

      for (let i = 0; i < numStars; i++) {
        // create a star rating element
        const ratingElement =
          this.chromeWindow.document.createXULElement("toolbarbutton");

        // style it
        const starIndex = numStars - i;
        ratingElement.className = "plain star-x";
        ratingElement.id = "star" + starIndex;
        ratingElement.setAttribute("data-score", starIndex);

        // Add the click handler
        ratingElement.addEventListener("click", ev => {
          const rating = parseInt(ev.target.getAttribute("data-score"));
          this.maybeNotifyHeartbeat("Voted", { score: rating });
          this.userEngaged({
            type: "stars",
            score: rating,
            flowId: this.options.flowId,
          });
        });

        this.ratingContainer.appendChild(ratingElement);
      }
    }

    this.notificationBox = this.chromeWindow.gNotificationBox;
    this.noticePromise = new Promise(resolve => {
      this.notificationBox
        .appendNotification(
          "heartbeat-" + this.options.flowId,
          {
            label: this.options.message,
            image: "resource://normandy/skin/shared/heartbeat-icon.svg",
            priority: this.notificationBox.PRIORITY_SYSTEM,
            eventCallback: eventType => {
              if (eventType !== "removed") {
                return;
              }
              this.maybeNotifyHeartbeat("NotificationClosed");
            },
          },
          this.buttons
        )
        .then(noticeEl => {
          noticeEl.classList.add("heartbeat");
          this.chromeWindow.requestAnimationFrame(() => {
            noticeEl.messageText.classList.add("heartbeat");
          });
          if (this.ratingContainer) {
            noticeEl.buttonContainer.append(this.ratingContainer);
          }
          resolve(noticeEl);
        }, resolve);
    });

    // Let the consumer know the notification was shown.
    this.maybeNotifyHeartbeat("NotificationOffered");
    this.chromeWindow.addEventListener(
      "SSWindowClosing",
      this.handleWindowClosed
    );

    const surveyDuration =
      Services.prefs.getIntPref(PREF_SURVEY_DURATION, 300) * 1000;
    this.surveyEndTimer = setTimeout(() => {
      this.maybeNotifyHeartbeat("SurveyExpired");
      this.close();
    }, surveyDuration);

    CleanupManager.addCleanupHandler(this.close);
  }

  maybeNotifyHeartbeat(name, data = {}) {
    if (this.pingSent) {
      log.warn(
        "Heartbeat event received after Telemetry ping sent. name:",
        name,
        "data:",
        data
      );
      return;
    }

    const timestamp = Date.now();
    let sendPing = false;
    let cleanup = false;

    const phases = {
      NotificationOffered: () => {
        this.surveyResults.flowId = this.options.flowId;
        this.surveyResults.offeredTS = timestamp;
      },
      LearnMore: () => {
        if (!this.surveyResults.learnMoreTS) {
          this.surveyResults.learnMoreTS = timestamp;
        }
      },
      Engaged: () => {
        this.surveyResults.engagedTS = timestamp;
      },
      Voted: () => {
        this.surveyResults.votedTS = timestamp;
        this.surveyResults.score = data.score;
      },
      SurveyExpired: () => {
        this.surveyResults.expiredTS = timestamp;
      },
      NotificationClosed: () => {
        this.surveyResults.closedTS = timestamp;
        cleanup = true;
        sendPing = true;
      },
      WindowClosed: () => {
        this.surveyResults.windowClosedTS = timestamp;
        cleanup = true;
        sendPing = true;
      },
      default: () => {
        log.error("Unrecognized Heartbeat event:", name);
      },
    };

    (phases[name] || phases.default)();

    data.timestamp = timestamp;
    data.flowId = this.options.flowId;
    this.eventEmitter.emit(name, data);

    if (sendPing) {
      // Send the ping to Telemetry
      const payload = Object.assign({ version: 1 }, this.surveyResults);
      for (const meta of ["surveyId", "surveyVersion", "testing"]) {
        if (this.options.hasOwnProperty(meta)) {
          payload[meta] = this.options[meta];
        }
      }

      log.debug("Sending telemetry");
      TelemetryController.submitExternalPing("heartbeat", payload, {
        addClientId: true,
        addEnvironment: true,
      });

      // only for testing
      this.eventEmitter.emit("TelemetrySent", payload);

      // Survey is complete, clear out the expiry timer & survey configuration
      this.endTimerIfPresent("surveyEndTimer");

      this.pingSent = true;
      this.surveyResults = null;
    }

    if (cleanup) {
      this.cleanup();
    }
  }

  userEngaged(engagementParams) {
    this.noticePromise.then(noticeEl => {
      // Make the heartbeat icon pulse twice
      noticeEl.label = this.options.thanksMessage;
      noticeEl.messageImage?.classList.remove("pulse-onshow");
      noticeEl.messageImage?.classList.add("pulse-twice");
      // Remove the custom contents of the buttons
      for (let button of noticeEl.buttonContainer.querySelectorAll("button")) {
        button.remove();
      }
    });

    // Remove the custom contents of the notice
    if (this.ratingContainer) {
      this.ratingContainer.remove();
    }

    // Open the engagement tab if we have a valid engagement URL.
    if (this.options.postAnswerUrl) {
      for (const key in engagementParams) {
        this.options.postAnswerUrl.searchParams.append(
          key,
          engagementParams[key]
        );
      }
      // Open the engagement URL in a new tab.
      let { gBrowser } = this.chromeWindow;
      gBrowser.selectedTab = gBrowser.addWebTab(
        this.options.postAnswerUrl.toString(),
        {
          triggeringPrincipal:
            Services.scriptSecurityManager.createNullPrincipal({}),
        }
      );
    }

    this.endTimerIfPresent("surveyEndTimer");

    this.engagementCloseTimer = setTimeout(
      () => this.close(),
      NOTIFICATION_TIME
    );
  }

  endTimerIfPresent(timerName) {
    if (this[timerName]) {
      clearTimeout(this[timerName]);
      this[timerName] = null;
    }
  }

  handleWindowClosed() {
    this.maybeNotifyHeartbeat("WindowClosed");
  }

  close() {
    this.noticePromise.then(noticeEl =>
      this.notificationBox.removeNotification(noticeEl)
    );
  }

  cleanup() {
    // Kill the timers which might call things after we've cleaned up:
    this.endTimerIfPresent("surveyEndTimer");
    this.endTimerIfPresent("engagementCloseTimer");
    // remove listeners
    this.chromeWindow.removeEventListener(
      "SSWindowClosing",
      this.handleWindowClosed
    );
    // remove references for garbage collection
    this.chromeWindow = null;
    this.notificationBox = null;
    this.noticePromise = null;
    this.ratingContainer = null;
    this.eventEmitter = null;
    // Ensure we don't re-enter and release the CleanupManager's reference to us:
    CleanupManager.removeCleanupHandler(this.close);
  }
};
PK
!<d��7chrome/toolkit/res/normandy/lib/LegacyHeartbeat.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs",
  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
});

const FEATURE_ID = "legacyHeartbeat";

/**
 * A bridge between Nimbus and Normandy's Heartbeat implementation.
 */
export const LegacyHeartbeat = {
  getHeartbeatRecipe() {
    const survey = lazy.NimbusFeatures.legacyHeartbeat.getVariable("survey");

    if (typeof survey == "undefined") {
      return null;
    }

    let isRollout = false;
    let enrollmentData = lazy.ExperimentAPI.getExperimentMetaData({
      featureId: FEATURE_ID,
    });

    if (!enrollmentData) {
      enrollmentData = lazy.ExperimentAPI.getRolloutMetaData({
        featureId: FEATURE_ID,
      });
      isRollout = true;
    }

    return {
      id: `nimbus:${enrollmentData.slug}`,
      name: `Nimbus legacyHeartbeat ${isRollout ? "rollout" : "experiment"} ${
        enrollmentData.slug
      }`,
      action: "show-heartbeat",
      arguments: survey,
      capabilities: ["action.show-heartbeat"],
      filter_expression: "true",
      use_only_baseline_capabilities: true,
      revision_id: "1", // Required for the Heartbeat telemetry ping.
    };
  },
};
PK
!<���WW2chrome/toolkit/res/normandy/lib/LogManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { Log } from "resource://gre/modules/Log.sys.mjs";

const ROOT_LOGGER_NAME = "app.normandy";
let rootLogger = null;

export var LogManager = {
  /**
   * Configure the root logger for the Recipe Client. Must be called at
   * least once before using any loggers created via getLogger.
   * @param {Number} loggingLevel
   *        Logging level to use as defined in Log.sys.mjs
   */
  configure(loggingLevel) {
    if (!rootLogger) {
      rootLogger = Log.repository.getLogger(ROOT_LOGGER_NAME);
      rootLogger.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
    }
    rootLogger.level = loggingLevel;
  },

  /**
   * Obtain a named logger with the recipe client logger as its parent.
   * @param {String} name
   *        Name of the logger to obtain.
   * @return {Logger}
   */
  getLogger(name) {
    return Log.repository.getLogger(`${ROOT_LOGGER_NAME}.${name}`);
  },
};
PK
!<j�l�VV<chrome/toolkit/res/normandy/lib/NormandyAddonManager.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
});

export const NormandyAddonManager = {
  async downloadAndInstall({
    createError,
    extensionDetails,
    applyNormandyChanges,
    undoNormandyChanges,
    onInstallStarted,
    reportError,
  }) {
    const { extension_id, hash, hash_algorithm, version, xpi } =
      extensionDetails;

    const downloadDeferred = Promise.withResolvers();
    const installDeferred = Promise.withResolvers();

    const install = await lazy.AddonManager.getInstallForURL(xpi, {
      hash: `${hash_algorithm}:${hash}`,
      telemetryInfo: { source: "internal" },
    });

    const listener = {
      onInstallStarted(cbInstall) {
        const versionMatches = cbInstall.addon.version === version;
        const idMatches = cbInstall.addon.id === extension_id;

        if (!versionMatches || !idMatches) {
          installDeferred.reject(createError("metadata-mismatch"));
          return false; // cancel the installation, server metadata does not match downloaded add-on
        }

        if (onInstallStarted) {
          return onInstallStarted(cbInstall, installDeferred);
        }

        return true;
      },

      onDownloadFailed() {
        downloadDeferred.reject(
          createError("download-failure", {
            detail: lazy.AddonManager.errorToString(install.error),
          })
        );
      },

      onDownloadEnded() {
        downloadDeferred.resolve();
        return false; // temporarily pause installation for Normandy bookkeeping
      },

      onInstallFailed() {
        installDeferred.reject(
          createError("install-failure", {
            detail: lazy.AddonManager.errorToString(install.error),
          })
        );
      },

      onInstallEnded() {
        installDeferred.resolve();
      },
    };

    install.addListener(listener);

    // Download the add-on
    try {
      install.install();
      await downloadDeferred.promise;
    } catch (err) {
      reportError(err);
      install.removeListener(listener);
      throw err;
    }

    // Complete any book-keeping
    try {
      await applyNormandyChanges(install);
    } catch (err) {
      reportError(err);
      install.removeListener(listener);
      install.cancel();
      throw err;
    }

    // Finish paused installation
    try {
      install.install();
      await installDeferred.promise;
    } catch (err) {
      reportError(err);
      install.removeListener(listener);
      await undoNormandyChanges();
      throw err;
    }

    install.removeListener(listener);

    return [install.addon.id, install.addon.version];
  },
};
PK
!<TȸŻ�3chrome/toolkit/res/normandy/lib/NormandyApi.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  CanonicalJSON: "resource://gre/modules/CanonicalJSON.sys.mjs",
});

const prefs = Services.prefs.getBranch("app.normandy.");

let indexPromise = null;

function getChainRootIdentifier() {
  const normandy_url = Services.prefs.getCharPref("app.normandy.api_url");
  if (normandy_url == "https://normandy.cdn.mozilla.net/api/v1") {
    return Ci.nsIContentSignatureVerifier.ContentSignatureProdRoot;
  }
  if (normandy_url.includes("stage.")) {
    return Ci.nsIContentSignatureVerifier.ContentSignatureStageRoot;
  }
  if (normandy_url.includes("dev.")) {
    return Ci.nsIContentSignatureVerifier.ContentSignatureDevRoot;
  }
  if (Services.env.exists("XPCSHELL_TEST_PROFILE_DIR")) {
    return Ci.nsIX509CertDB.AppXPCShellRoot;
  }
  return Ci.nsIContentSignatureVerifier.ContentSignatureLocalRoot;
}

export var NormandyApi = {
  InvalidSignatureError: class InvalidSignatureError extends Error {},

  clearIndexCache() {
    indexPromise = null;
  },

  get(endpoint, data) {
    const url = new URL(endpoint);
    if (data) {
      for (const key of Object.keys(data)) {
        url.searchParams.set(key, data[key]);
      }
    }
    return fetch(url.href, {
      method: "get",
      headers: { Accept: "application/json" },
      credentials: "omit",
    });
  },

  absolutify(url) {
    if (url.startsWith("http")) {
      return url;
    }
    const apiBase = prefs.getCharPref("api_url");
    const server = new URL(apiBase).origin;
    if (url.startsWith("/")) {
      return server + url;
    }
    throw new Error("Can't use relative urls");
  },

  async getApiUrl(name) {
    if (!indexPromise) {
      const apiBase = new URL(prefs.getCharPref("api_url"));
      if (!apiBase.pathname.endsWith("/")) {
        apiBase.pathname += "/";
      }
      indexPromise = this.get(apiBase.toString()).then(res => res.json());
    }
    const index = await indexPromise;
    if (!(name in index)) {
      throw new Error(`API endpoint with name "${name}" not found.`);
    }
    const url = index[name];
    return this.absolutify(url);
  },

  /**
   * Verify content signature, by serializing the specified `object` as
   * canonical JSON, and using the Normandy signer verifier to check that
   * it matches the signature specified in `signaturePayload`.
   *
   * If the the signature is not valid, an error is thrown. Otherwise this
   * function returns undefined.
   *
   * @param {object|String} data The object (or string) to be checked
   * @param {object} signaturePayload The signature information
   * @param {String} signaturePayload.x5u The certificate chain URL
   * @param {String} signaturePayload.signature base64 signature bytes
   * @param {String} type The object type (eg. `"recipe"`, `"action"`)
   * @returns {Promise<undefined>} If the signature is valid, this function returns without error
   * @throws {NormandyApi.InvalidSignatureError} if signature is invalid.
   */
  async verifyObjectSignature(data, signaturePayload, type) {
    const { signature, x5u } = signaturePayload;
    const certChainResponse = await this.get(this.absolutify(x5u));
    const certChain = await certChainResponse.text();
    const builtSignature = `p384ecdsa=${signature}`;

    const serialized =
      typeof data == "string" ? data : lazy.CanonicalJSON.stringify(data);

    const verifier = Cc[
      "@mozilla.org/security/contentsignatureverifier;1"
    ].createInstance(Ci.nsIContentSignatureVerifier);

    let valid;
    try {
      valid = await verifier.asyncVerifyContentSignature(
        serialized,
        builtSignature,
        certChain,
        "normandy.content-signature.mozilla.org",
        getChainRootIdentifier()
      );
    } catch (err) {
      throw new NormandyApi.InvalidSignatureError(
        `${type} signature validation failed: ${err}`
      );
    }

    if (!valid) {
      throw new NormandyApi.InvalidSignatureError(
        `${type} signature is not valid`
      );
    }
  },

  /**
   * Fetch metadata about this client determined by the server.
   * @return {object} Metadata specified by the server
   */
  async classifyClient() {
    const classifyClientUrl = await this.getApiUrl("classify-client");
    const response = await this.get(classifyClientUrl);
    const clientData = await response.json();
    clientData.request_time = new Date(clientData.request_time);
    return clientData;
  },

  /**
   * Fetch details for an extension from the server.
   * @param extensionId {integer} The ID of the extension to look up
   * @resolves {Object}
   */
  async fetchExtensionDetails(extensionId) {
    const baseUrl = await this.getApiUrl("extension-list");
    const extensionDetailsUrl = `${baseUrl}${extensionId}/`;
    const response = await this.get(extensionDetailsUrl);
    return response.json();
  },
};
PK
!<��8���5chrome/toolkit/res/normandy/lib/NormandyUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

export var NormandyUtils = {
  generateUuid() {
    // Generate a random UUID, convert it to a string, and slice the braces off the ends.
    return Services.uuid.generateUUID().toString().slice(1, -1);
  },
};
PK
!<E����1chrome/toolkit/res/normandy/lib/PrefUtils.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  LogManager: "resource://normandy/lib/LogManager.sys.mjs",
});

ChromeUtils.defineLazyGetter(lazy, "log", () => {
  return lazy.LogManager.getLogger("preference-experiments");
});

const kPrefBranches = {
  user: Services.prefs,
  default: Services.prefs.getDefaultBranch(""),
};

export var PrefUtils = {
  /**
   * Get a preference of any type from the named branch.
   * @param {string} pref
   * @param {object} [options]
   * @param {"default"|"user"} [options.branchName="user"] One of "default" or "user"
   * @param {string|boolean|integer|null} [options.defaultValue]
   *   The value to return if the preference does not exist. Defaults to null.
   */
  getPref(pref, { branch = "user", defaultValue = null } = {}) {
    const branchObj = kPrefBranches[branch];
    if (!branchObj) {
      throw new this.UnexpectedPreferenceBranch(
        `"${branch}" is not a valid preference branch`
      );
    }
    const type = branchObj.getPrefType(pref);

    try {
      switch (type) {
        case Services.prefs.PREF_BOOL: {
          return branchObj.getBoolPref(pref);
        }
        case Services.prefs.PREF_STRING: {
          return branchObj.getStringPref(pref);
        }
        case Services.prefs.PREF_INT: {
          return branchObj.getIntPref(pref);
        }
        case Services.prefs.PREF_INVALID: {
          return defaultValue;
        }
      }
    } catch (e) {
      if (branch === "default" && e.result === Cr.NS_ERROR_UNEXPECTED) {
        // There is a value for the pref on the user branch but not on the default branch. This is ok.
        return defaultValue;
      }
      // Unexpected error, re-throw it
      throw e;
    }

    // If `type` isn't any of the above, throw an error. Don't do this in a
    // default branch of switch so that error handling is easier.
    throw new TypeError(`Unknown preference type (${type}) for ${pref}.`);
  },

  /**
   * Set a preference on the named branch
   * @param {string} pref
   * @param {string|boolean|integer|null} value The value to set.
   * @param {object} options
   * @param {"user"|"default"} options.branchName The branch to make the change on.
   */
  setPref(pref, value, { branch = "user" } = {}) {
    if (value === null) {
      this.clearPref(pref, { branch });
      return;
    }
    const branchObj = kPrefBranches[branch];
    if (!branchObj) {
      throw new this.UnexpectedPreferenceBranch(
        `"${branch}" is not a valid preference branch`
      );
    }
    switch (typeof value) {
      case "boolean": {
        branchObj.setBoolPref(pref, value);
        break;
      }
      case "string": {
        branchObj.setStringPref(pref, value);
        break;
      }
      case "number": {
        branchObj.setIntPref(pref, value);
        break;
      }
      default: {
        throw new TypeError(
          `Unexpected value type (${typeof value}) for ${pref}.`
        );
      }
    }
  },

  /**
   * Remove a preference from a branch. Note that default branch preferences
   * cannot effectively be cleared. If "default" is passed for a branch, an
   * error will be logged and nothing else will happen.
   *
   * @param {string} pref
   * @param {object} options
   * @param {"user"|"default"} options.branchName The branch to clear
   */
  clearPref(pref, { branch = "user" } = {}) {
    if (branch === "user") {
      kPrefBranches.user.clearUserPref(pref);
    } else if (branch === "default") {
      lazy.log.warn(
        `Cannot reset pref ${pref} on the default branch. Pref will be cleared at next restart.`
      );
    } else {
      throw new this.UnexpectedPreferenceBranch(
        `"${branch}" is not a valid preference branch`
      );
    }
  },

  UnexpectedPreferenceType: class extends Error {},
  UnexpectedPreferenceBranch: class extends Error {},
};
PK
!<�?h�.�.�=chrome/toolkit/res/normandy/lib/PreferenceExperiments.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Preference Experiments temporarily change a preference to one of several test
 * values for the duration of the experiment. Telemetry packets are annotated to
 * show what experiments are active, and we use this data to measure the
 * effectiveness of the preference change.
 *
 * Info on active and past experiments is stored in a JSON file in the profile
 * folder.
 *
 * Active preference experiments are stopped if they aren't active on the recipe
 * server. They also expire if Firefox isn't able to contact the recipe server
 * after a period of time, as well as if the user modifies the preference during
 * an active experiment.
 */

/**
 * Experiments store info about an active or expired preference experiment.
 * @typedef {Object} Experiment
 * @property {string} slug
 *   A string uniquely identifying the experiment. Used for telemetry, and other
 *   machine-oriented use cases. Used as a display name if `userFacingName` is
 *   null.
 * @property {string|null} userFacingName
 *   A user-friendly name for the experiment. Null on old-style single-preference
 *   experiments, which do not have a userFacingName.
 * @property {string|null} userFacingDescription
 *   A user-friendly description of the experiment. Null on old-style
 *   single-preference experiments, which do not have a userFacingDescription.
 * @property {string} branch
 *   Experiment branch that the user was matched to
 * @property {boolean} expired
 *   If false, the experiment is active.
 *   ISO-formatted date string of when the experiment was last seen from the
 *   recipe server.
 * @property {string|null} temporaryErrorDeadline
 *   ISO-formatted date string of when temporary errors with this experiment
 *   should not longer be considered temporary. After this point, further errors
 *   will result in unenrollment.
 * @property {Object} preferences
 *   An object consisting of all the preferences that are set by this experiment.
 *   Keys are the name of each preference affected by this experiment. Values are
 *   Preference Objects, about which see below.
 * @property {string} experimentType
 *   The type to report to Telemetry's experiment marker API.
 * @property {string} actionName
 *   The action who knows about this experiment and is responsible for cleaning
 *   it up. This should correspond to the `name` of some BaseAction subclass.
 */

/**
 * Each Preference stores information about a preference that an
 * experiment sets.
 * @property {string|integer|boolean} preferenceValue
 *   Value to change the preference to during the experiment.
 * @property {string} preferenceType
 *   Type of the preference value being set.
 * @property {string|integer|boolean|undefined} previousPreferenceValue
 *   Value of the preference prior to the experiment, or undefined if it was
 *   unset.
 * @property {PreferenceBranchType} preferenceBranchType
 *   Controls how we modify the preference to affect the client.
 *
 *   If "default", when the experiment is active, the default value for the
 *   preference is modified on startup of the add-on. If "user", the user value
 *   for the preference is modified when the experiment starts, and is reset to
 *   its original value when the experiment ends.
 * @property {boolean} overridden
 *   Tracks if this preference has been changed away from the experimental value.
 */

import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { CleanupManager } from "resource://normandy/lib/CleanupManager.sys.mjs";
import { LogManager } from "resource://normandy/lib/LogManager.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  JSONFile: "resource://gre/modules/JSONFile.sys.mjs",
  PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs",
  TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
  TelemetryEvents: "resource://normandy/lib/TelemetryEvents.sys.mjs",
});

const EXPERIMENT_FILE = "shield-preference-experiments.json";
const STARTUP_EXPERIMENT_PREFS_BRANCH = "app.normandy.startupExperimentPrefs.";

const MAX_EXPERIMENT_TYPE_LENGTH = 20; // enforced by TelemetryEnvironment
const EXPERIMENT_TYPE_PREFIX = "normandy-";
const MAX_EXPERIMENT_SUBTYPE_LENGTH =
  MAX_EXPERIMENT_TYPE_LENGTH - EXPERIMENT_TYPE_PREFIX.length;

const PREFERENCE_TYPE_MAP = {
  boolean: Services.prefs.PREF_BOOL,
  string: Services.prefs.PREF_STRING,
  integer: Services.prefs.PREF_INT,
};

const UserPreferences = Services.prefs;
const DefaultPreferences = Services.prefs.getDefaultBranch("");

/**
 * Enum storing Preference modules for each type of preference branch.
 * @enum {Object}
 */
const PreferenceBranchType = {
  user: UserPreferences,
  default: DefaultPreferences,
};

/**
 * Asynchronously load the JSON file that stores experiment status in the profile.
 */
let gStorePromise;
function ensureStorage() {
  if (gStorePromise === undefined) {
    const path = PathUtils.join(
      Services.dirsvc.get("ProfD", Ci.nsIFile).path,
      EXPERIMENT_FILE
    );
    const storage = new lazy.JSONFile({ path });
    // `storage.load()` is defined as being infallible: It won't ever throw an
    // error. However, if there are are I/O errors, such as a corrupt, missing,
    // or unreadable file the data loaded will be an empty object. This can
    // happen ever after our migrations have run. If that happens, edit the
    // storage to match our expected schema before returning it to the rest of
    // the module.
    gStorePromise = storage.load().then(() => {
      if (!storage.data.experiments) {
        storage.data = { ...storage.data, experiments: {} };
      }
      return storage;
    });
  }
  return gStorePromise;
}

const log = LogManager.getLogger("preference-experiments");

// List of active preference observers. Cleaned up on shutdown.
let experimentObservers = new Map();
CleanupManager.addCleanupHandler(() =>
  PreferenceExperiments.stopAllObservers()
);

export var PreferenceExperiments = {
  /**
   * Update the the experiment storage with changes that happened during early startup.
   * @param {object} studyPrefsChanged Map from pref name to previous pref value
   */
  async recordOriginalValues(studyPrefsChanged) {
    const store = await ensureStorage();

    for (const experiment of Object.values(store.data.experiments)) {
      for (const [prefName, prefInfo] of Object.entries(
        experiment.preferences
      )) {
        if (studyPrefsChanged.hasOwnProperty(prefName)) {
          if (experiment.expired) {
            log.warn(
              "Expired preference experiment changed value during startup"
            );
          }
          if (prefInfo.preferenceBranch !== "default") {
            log.warn(
              "Non-default branch preference experiment changed value during startup"
            );
          }
          prefInfo.previousPreferenceValue = studyPrefsChanged[prefName];
        }
      }
    }

    // not calling store.saveSoon() because if the data doesn't get
    // written, it will get updated with fresher data next time the
    // browser starts.
  },

  /**
   * Set the default preference value for active experiments that use the
   * default preference branch.
   */
  async init() {
    CleanupManager.addCleanupHandler(() => this.saveStartupPrefs());

    for (const experiment of await this.getAllActive()) {
      // Check that the current value of the preference is still what we set it to
      for (const [preferenceName, spec] of Object.entries(
        experiment.preferences
      )) {
        if (
          !spec.overridden &&
          lazy.PrefUtils.getPref(preferenceName) !== spec.preferenceValue
        ) {
          // if not, record the difference
          await this.recordPrefChange({
            experiment,
            preferenceName,
            reason: "sideload",
          });
        }
      }

      // Notify Telemetry of experiments we're running, since they don't persist between restarts
      lazy.TelemetryEnvironment.setExperimentActive(
        experiment.slug,
        experiment.branch,
        {
          type: EXPERIMENT_TYPE_PREFIX + experiment.experimentType,
        }
      );

      // Watch for changes to the experiment's preference
      this.startObserver(experiment.slug, experiment.preferences);
    }
  },

  /**
   * Save in-progress, default-branch preference experiments in a sub-branch of
   * the normandy preferences. On startup, we read these to set the
   * experimental values.
   *
   * This is needed because the default branch does not persist between Firefox
   * restarts. To compensate for that, Normandy sets the default branch to the
   * experiment values again every startup. The values to set the preferences
   * to are stored in user-branch preferences because preferences have minimal
   * impact on the performance of startup.
   */
  async saveStartupPrefs() {
    const prefBranch = Services.prefs.getBranch(
      STARTUP_EXPERIMENT_PREFS_BRANCH
    );
    for (const pref of prefBranch.getChildList("")) {
      prefBranch.clearUserPref(pref);
    }

    // Only store prefs to set on the default branch.
    // Be careful not to store user branch prefs here, because this
    // would cause the default branch to match the user branch,
    // causing the user branch pref to get cleared.
    const allExperiments = await this.getAllActive();
    const defaultBranchPrefs = allExperiments
      .flatMap(exp => Object.entries(exp.preferences))
      .filter(
        ([, preferenceInfo]) =>
          preferenceInfo.preferenceBranchType === "default"
      );
    for (const [preferenceName, { preferenceValue }] of defaultBranchPrefs) {
      switch (typeof preferenceValue) {
        case "string":
          prefBranch.setCharPref(preferenceName, preferenceValue);
          break;

        case "number":
          prefBranch.setIntPref(preferenceName, preferenceValue);
          break;

        case "boolean":
          prefBranch.setBoolPref(preferenceName, preferenceValue);
          break;

        default:
          throw new Error(`Invalid preference type ${typeof preferenceValue}`);
      }
    }
  },

  /**
   * Test wrapper that temporarily replaces the stored experiment data with fake
   * data for testing.
   */
  withMockExperiments(prefExperiments = []) {
    return function wrapper(testFunction) {
      return async function wrappedTestFunction(args) {
        const experiments = {};

        for (const exp of prefExperiments) {
          if (exp.name) {
            throw new Error(
              "Preference experiments 'name' field has been replaced by 'slug' and 'userFacingName', please update."
            );
          }

          experiments[exp.slug] = exp;
        }
        const data = { experiments };

        const oldPromise = gStorePromise;
        gStorePromise = Promise.resolve({
          data,
          saveSoon() {},
        });
        const oldObservers = experimentObservers;
        experimentObservers = new Map();
        try {
          await testFunction({ ...args, prefExperiments });
        } finally {
          gStorePromise = oldPromise;
          PreferenceExperiments.stopAllObservers();
          experimentObservers = oldObservers;
        }
      };
    };
  },

  /**
   * Clear all stored data about active and past experiments.
   */
  async clearAllExperimentStorage() {
    const store = await ensureStorage();
    store.data = {
      experiments: {},
    };
    store.saveSoon();
  },

  /**
   * Start a new preference experiment.
   * @param {Object} experiment
   * @param {string} experiment.slug
   * @param {string} experiment.actionName  The action who knows about this
   *   experiment and is responsible for cleaning it up. This should
   *   correspond to the name of some BaseAction subclass.
   * @param {string} experiment.branch
   * @param {string} experiment.preferenceName
   * @param {string|integer|boolean} experiment.preferenceValue
   * @param {PreferenceBranchType} experiment.preferenceBranchType
   * @returns {Experiment} The experiment object stored in the data store
   * @rejects {Error}
   *   - If an experiment with the given name already exists
   *   - if an experiment for the given preference is active
   *   - If the given preferenceType does not match the existing stored preference
   */
  async start({
    name = null, // To check if old code is still using `name` instead of `slug`, and provide a nice error message
    slug,
    actionName,
    branch,
    preferences,
    experimentType = "exp",
    userFacingName = null,
    userFacingDescription = null,
  }) {
    if (name) {
      throw new Error(
        "Preference experiments 'name' field has been replaced by 'slug' and 'userFacingName', please update."
      );
    }

    log.debug(`PreferenceExperiments.start(${slug}, ${branch})`);

    const store = await ensureStorage();
    if (slug in store.data.experiments) {
      lazy.TelemetryEvents.sendEvent("enrollFailed", "preference_study", slug, {
        reason: "name-conflict",
      });
      throw new Error(
        `A preference experiment with the slug "${slug}" already exists.`
      );
    }

    const activeExperiments = Object.values(store.data.experiments).filter(
      e => !e.expired
    );
    const preferencesWithConflicts = Object.keys(preferences).filter(
      preferenceName => {
        return activeExperiments.some(e =>
          e.preferences.hasOwnProperty(preferenceName)
        );
      }
    );

    if (preferencesWithConflicts.length) {
      lazy.TelemetryEvents.sendEvent("enrollFailed", "preference_study", slug, {
        reason: "pref-conflict",
      });
      throw new Error(
        `Another preference experiment for the pref "${preferencesWithConflicts[0]}" is currently active.`
      );
    }

    if (experimentType.length > MAX_EXPERIMENT_SUBTYPE_LENGTH) {
      lazy.TelemetryEvents.sendEvent("enrollFailed", "preference_study", slug, {
        reason: "experiment-type-too-long",
      });
      throw new Error(
        `experimentType must be less than ${MAX_EXPERIMENT_SUBTYPE_LENGTH} characters. ` +
          `"${experimentType}" is ${experimentType.length} long.`
      );
    }

    // Sanity check each preference
    for (const [preferenceName, preferenceInfo] of Object.entries(
      preferences
    )) {
      // Ensure preferenceBranchType is set, using the default from
      // the schema. This also modifies the preferenceInfo for use in
      // the rest of the function.
      preferenceInfo.preferenceBranchType =
        preferenceInfo.preferenceBranchType || "default";
      const { preferenceBranchType, preferenceType } = preferenceInfo;
      if (
        !(preferenceBranchType === "user" || preferenceBranchType === "default")
      ) {
        lazy.TelemetryEvents.sendEvent(
          "enrollFailed",
          "preference_study",
          slug,
          {
            reason: "invalid-branch",
            prefBranch: preferenceBranchType.slice(0, 80),
          }
        );
        throw new Error(
          `Invalid value for preferenceBranchType: ${preferenceBranchType}`
        );
      }

      const prevPrefType = Services.prefs.getPrefType(preferenceName);
      const givenPrefType = PREFERENCE_TYPE_MAP[preferenceType];

      if (!preferenceType || !givenPrefType) {
        lazy.TelemetryEvents.sendEvent(
          "enrollFailed",
          "preference_study",
          slug,
          {
            reason: "invalid-type",
          }
        );
        throw new Error(
          `Invalid preferenceType provided (given "${preferenceType}")`
        );
      }

      if (
        prevPrefType !== Services.prefs.PREF_INVALID &&
        prevPrefType !== givenPrefType
      ) {
        lazy.TelemetryEvents.sendEvent(
          "enrollFailed",
          "preference_study",
          slug,
          {
            reason: "invalid-type",
          }
        );
        throw new Error(
          `Previous preference value is of type "${prevPrefType}", but was given ` +
            `"${givenPrefType}" (${preferenceType})`
        );
      }

      preferenceInfo.previousPreferenceValue = lazy.PrefUtils.getPref(
        preferenceName,
        { branch: preferenceBranchType }
      );
    }

    const alreadyOverriddenPrefs = new Set();
    for (const [preferenceName, preferenceInfo] of Object.entries(
      preferences
    )) {
      const { preferenceValue, preferenceBranchType } = preferenceInfo;

      if (preferenceBranchType === "default") {
        // Only set the pref if there is no user-branch value, because
        // changing the default-branch value to the same value as the
        // user-branch will effectively delete the user value.
        if (Services.prefs.prefHasUserValue(preferenceName)) {
          alreadyOverriddenPrefs.add(preferenceName);
        } else {
          lazy.PrefUtils.setPref(preferenceName, preferenceValue, {
            branch: preferenceBranchType,
          });
        }
      } else if (preferenceBranchType === "user") {
        // The original value was already backed up above.
        lazy.PrefUtils.setPref(preferenceName, preferenceValue, {
          branch: preferenceBranchType,
        });
      } else {
        log.error(`Unexpected preference branch type ${preferenceBranchType}`);
      }
    }
    PreferenceExperiments.startObserver(slug, preferences);

    /** @type {Experiment} */
    const experiment = {
      slug,
      actionName,
      branch,
      expired: false,
      lastSeen: new Date().toJSON(),
      preferences,
      experimentType,
      userFacingName,
      userFacingDescription,
    };

    store.data.experiments[slug] = experiment;
    store.saveSoon();

    // Record telemetry that the experiment started
    lazy.TelemetryEnvironment.setExperimentActive(slug, branch, {
      type: EXPERIMENT_TYPE_PREFIX + experimentType,
    });
    lazy.TelemetryEvents.sendEvent("enroll", "preference_study", slug, {
      experimentType,
      branch,
    });

    // Send events for any default branch preferences set that already had user
    // values overriding them.
    for (const preferenceName of alreadyOverriddenPrefs) {
      await this.recordPrefChange({
        experiment,
        preferenceName,
        reason: "onEnroll",
      });
    }
    await this.saveStartupPrefs();

    return experiment;
  },

  /**
   * Register a preference observer that stops an experiment when the user
   * modifies the preference.
   * @param {string} experimentSlug
   * @param {string} preferenceName
   * @param {string|integer|boolean} preferenceValue
   * @throws {Error}
   *   If an observer for the experiment is already active.
   */
  startObserver(experimentSlug, preferences) {
    log.debug(`PreferenceExperiments.startObserver(${experimentSlug})`);

    if (experimentObservers.has(experimentSlug)) {
      throw new Error(
        `An observer for the preference experiment ${experimentSlug} is already active.`
      );
    }

    const observerInfo = {
      preferences,
      observe(aSubject, aTopic, preferenceName) {
        const prefInfo = preferences[preferenceName];
        // if `preferenceName` is one of the experiment prefs but with more on
        // the end (ie, foo.bar vs foo.bar.baz) then this can be triggered for
        // changes we don't care about. Check for that.
        if (!prefInfo) {
          return;
        }
        const originalValue = prefInfo.preferenceValue;
        const newValue = lazy.PrefUtils.getPref(preferenceName);
        if (newValue !== originalValue) {
          PreferenceExperiments.recordPrefChange({
            experimentSlug,
            preferenceName,
            reason: "observer",
          });
          Services.prefs.removeObserver(preferenceName, observerInfo);
        }
      },
    };
    experimentObservers.set(experimentSlug, observerInfo);
    for (const [preferenceName, spec] of Object.entries(preferences)) {
      if (!spec.overridden) {
        Services.prefs.addObserver(preferenceName, observerInfo);
      }
    }
  },

  /**
   * Check if a preference observer is active for an experiment.
   * @param {string} experimentSlug
   * @return {Boolean}
   */
  hasObserver(experimentSlug) {
    log.debug(`PreferenceExperiments.hasObserver(${experimentSlug})`);
    return experimentObservers.has(experimentSlug);
  },

  /**
   * Disable a preference observer for an experiment.
   * @param {string} experimentSlug
   * @throws {Error}
   *   If there is no active observer for the experiment.
   */
  stopObserver(experimentSlug) {
    log.debug(`PreferenceExperiments.stopObserver(${experimentSlug})`);

    if (!experimentObservers.has(experimentSlug)) {
      throw new Error(
        `No observer for the preference experiment ${experimentSlug} found.`
      );
    }

    const observer = experimentObservers.get(experimentSlug);
    for (const preferenceName of Object.keys(observer.preferences)) {
      Services.prefs.removeObserver(preferenceName, observer);
    }
    experimentObservers.delete(experimentSlug);
  },

  /**
   * Disable all currently-active preference observers for experiments.
   */
  stopAllObservers() {
    log.debug("PreferenceExperiments.stopAllObservers()");
    for (const observer of experimentObservers.values()) {
      for (const preferenceName of Object.keys(observer.preferences)) {
        Services.prefs.removeObserver(preferenceName, observer);
      }
    }
    experimentObservers.clear();
  },

  /**
   * Update the timestamp storing when Normandy last sent a recipe for the
   * experiment.
   * @param {string} experimentSlug
   * @rejects {Error}
   *   If there is no stored experiment with the given slug.
   */
  async markLastSeen(experimentSlug) {
    log.debug(`PreferenceExperiments.markLastSeen(${experimentSlug})`);

    const store = await ensureStorage();
    if (!(experimentSlug in store.data.experiments)) {
      throw new Error(
        `Could not find a preference experiment with the slug "${experimentSlug}"`
      );
    }

    store.data.experiments[experimentSlug].lastSeen = new Date().toJSON();
    store.saveSoon();
  },

  /**
   * Called when an experimental pref has changed away from its experimental
   * value for the first time.
   *
   * One of `experiment` or `slug` must be passed.
   *
   * @param {object} options
   * @param {Experiment} [options.experiment]
   *   The experiment that had a pref change. If this is passed, slug is ignored.
   * @param {string} [options.slug]
   *   The slug of the experiment that had a pref change. This will be used to
   *   fetch an experiment if none was passed.
   * @param {string} options.preferenceName The preference changed.
   * @param {string} options.reason The reason the preference change was detected.
   */
  async recordPrefChange({
    experiment = null,
    experimentSlug = null,
    preferenceName,
    reason,
  }) {
    if (!experiment) {
      experiment = await PreferenceExperiments.get(experimentSlug);
    }
    let preferenceSpecification = experiment.preferences[preferenceName];
    if (!preferenceSpecification) {
      throw new PreferenceExperiments.InvalidPreferenceName(
        `Preference "${preferenceName}" is not a part of experiment "${experimentSlug}"`
      );
    }

    preferenceSpecification.overridden = true;
    await this.update(experiment);

    lazy.TelemetryEvents.sendEvent(
      "expPrefChanged",
      "preference_study",
      experiment.slug,
      {
        preferenceName,
        reason,
      }
    );
  },

  /**
   * Stop an active experiment, deactivate preference watchers, and optionally
   * reset the associated preference to its previous value.
   * @param {string} experimentSlug
   * @param {Object} options
   * @param {boolean} [options.resetValue = true]
   *   If true, reset the preference to its original value prior to
   *   the experiment. Optional, defaults to true.
   * @param {String} [options.reason = "unknown"]
   *   Reason that the experiment is ending. Optional, defaults to
   *   "unknown".
   * @rejects {Error}
   *   If there is no stored experiment with the given slug, or if the
   *   experiment has already expired.
   */
  async stop(
    experimentSlug,
    { resetValue = true, reason = "unknown", changedPref, caller } = {}
  ) {
    log.debug(
      `PreferenceExperiments.stop(${experimentSlug}, {resetValue: ${resetValue}, reason: ${reason}, changedPref: ${changedPref}, caller: ${caller}})`
    );
    if (reason === "unknown") {
      log.warn(`experiment ${experimentSlug} ending for unknown reason`);
    }

    const store = await ensureStorage();
    if (!(experimentSlug in store.data.experiments)) {
      lazy.TelemetryEvents.sendEvent(
        "unenrollFailed",
        "preference_study",
        experimentSlug,
        {
          reason: "does-not-exist",
          originalReason: reason,
          ...(changedPref ? { changedPref } : {}),
        }
      );
      throw new Error(
        `Could not find a preference experiment with the slug "${experimentSlug}"`
      );
    }

    const experiment = store.data.experiments[experimentSlug];
    if (experiment.expired) {
      const extra = {
        reason: "already-unenrolled",
        originalReason: reason,
      };
      if (changedPref) {
        extra.changedPref = changedPref;
      }
      if (caller && AppConstants.NIGHTLY_BUILD) {
        extra.caller = caller;
      }
      lazy.TelemetryEvents.sendEvent(
        "unenrollFailed",
        "preference_study",
        experimentSlug,
        extra
      );
      throw new Error(
        `Cannot stop preference experiment "${experimentSlug}" because it is already expired`
      );
    }

    if (PreferenceExperiments.hasObserver(experimentSlug)) {
      PreferenceExperiments.stopObserver(experimentSlug);
    }

    if (resetValue) {
      for (const [
        preferenceName,
        { previousPreferenceValue, preferenceBranchType, overridden },
      ] of Object.entries(experiment.preferences)) {
        // Overridden user prefs should keep their new value, even if that value
        // is the same as the experimental value, since it is the value the user
        // chose.
        if (overridden && preferenceBranchType === "user") {
          continue;
        }

        const preferences = PreferenceBranchType[preferenceBranchType];

        if (previousPreferenceValue !== null) {
          lazy.PrefUtils.setPref(preferenceName, previousPreferenceValue, {
            branch: preferenceBranchType,
          });
        } else if (preferenceBranchType === "user") {
          // Remove the "user set" value (which Shield set), but leave the default intact.
          preferences.clearUserPref(preferenceName);
        } else {
          log.warn(
            `Can't revert pref ${preferenceName} for experiment ${experimentSlug} ` +
              `because it had no default value. ` +
              `Preference will be reset at the next restart.`
          );
          // It would seem that Services.prefs.deleteBranch() could be used for
          // this, but in Normandy's case it does not work. See bug 1502410.
        }
      }
    }

    experiment.expired = true;
    if (experiment.temporaryErrorDeadline) {
      experiment.temporaryErrorDeadline = null;
    }
    await store.saveSoon();

    lazy.TelemetryEnvironment.setExperimentInactive(experimentSlug);
    lazy.TelemetryEvents.sendEvent(
      "unenroll",
      "preference_study",
      experimentSlug,
      {
        didResetValue: resetValue ? "true" : "false",
        branch: experiment.branch,
        reason,
        ...(changedPref ? { changedPref } : {}),
      }
    );
    await this.saveStartupPrefs();
    Services.obs.notifyObservers(
      null,
      "normandy:preference-experiment:stopped",
      experimentSlug
    );
  },

  /**
   * Clone an experiment using knowledge of its structure to avoid
   * having to serialize/deserialize it.
   *
   * We do this in places where return experiments so clients can't
   * accidentally mutate our data underneath us.
   */
  _cloneExperiment(experiment) {
    return {
      ...experiment,
      preferences: {
        ...experiment.preferences,
      },
    };
  },

  /**
   * Get the experiment object for the experiment.
   * @param {string} experimentSlug
   * @resolves {Experiment}
   * @rejects {Error}
   *   If no preference experiment exists with the given slug.
   */
  async get(experimentSlug) {
    log.debug(`PreferenceExperiments.get(${experimentSlug})`);
    const store = await ensureStorage();
    if (!(experimentSlug in store.data.experiments)) {
      throw new PreferenceExperiments.NotFoundError(
        `Could not find a preference experiment with the slug "${experimentSlug}"`
      );
    }

    return this._cloneExperiment(store.data.experiments[experimentSlug]);
  },

  /**
   * Get a list of all stored experiment objects.
   * @resolves {Experiment[]}
   */
  async getAll() {
    const store = await ensureStorage();
    return Object.values(store.data.experiments).map(experiment =>
      this._cloneExperiment(experiment)
    );
  },

  /**
   * Get a list of experiment objects for all active experiments.
   * @resolves {Experiment[]}
   */
  async getAllActive() {
    const store = await ensureStorage();
    return Object.values(store.data.experiments)
      .filter(e => !e.expired)
      .map(e => this._cloneExperiment(e));
  },

  /**
   * Check if an experiment exists with the given slug.
   * @param {string} experimentSlug
   * @resolves {boolean} True if the experiment exists, false if it doesn't.
   */
  async has(experimentSlug) {
    log.debug(`PreferenceExperiments.has(${experimentSlug})`);
    const store = await ensureStorage();
    return experimentSlug in store.data.experiments;
  },

  /**
   * Update an experiment in the data store. If an experiment with the given
   * slug is not already in the store, an error will be thrown.
   *
   * @param experiment {Experiment} The experiment to update
   * @param experiment.slug {String} The experiment must have a slug
   */
  async update(experiment) {
    const store = await ensureStorage();

    if (!(experiment.slug in store.data.experiments)) {
      throw new Error(
        `Could not update a preference experiment with the slug "${experiment.slug}"`
      );
    }

    store.data.experiments[experiment.slug] = experiment;
    store.saveSoon();
  },

  NotFoundError: class extends Error {},
  InvalidPreferenceName: class extends Error {},

  /**
   * These migrations should only be called from `NormandyMigrations.sys.mjs` and tests.
   */
  migrations: {
    /** Move experiments into a specific key. */
    async migration01MoveExperiments(storage = null) {
      if (storage === null) {
        storage = await ensureStorage();
      }
      if (Object.hasOwnProperty.call(storage.data, "experiments")) {
        return;
      }
      storage.data = {
        experiments: storage.data,
      };
      delete storage.data.experiments.__version;
      storage.saveSoon();
    },

    /** Migrate storage.data to multi-preference format */
    async migration02MultiPreference(storage = null) {
      if (storage === null) {
        storage = await ensureStorage();
      }

      const oldExperiments = storage.data.experiments;
      const v2Experiments = {};

      for (let [expName, oldExperiment] of Object.entries(oldExperiments)) {
        if (expName == "__version") {
          // A stray "__version" entry snuck in, likely from old migrations.
          // Ignore it and continue. It won't be propagated to future
          // migrations, since `v2Experiments` won't have it.
          continue;
        }
        if (oldExperiment.preferences) {
          // experiment is already migrated
          v2Experiments[expName] = oldExperiment;
          continue;
        }
        v2Experiments[expName] = {
          name: oldExperiment.name,
          branch: oldExperiment.branch,
          expired: oldExperiment.expired,
          lastSeen: oldExperiment.lastSeen,
          preferences: {
            [oldExperiment.preferenceName]: {
              preferenceBranchType: oldExperiment.preferenceBranchType,
              preferenceType: oldExperiment.preferenceType,
              preferenceValue: oldExperiment.preferenceValue,
              previousPreferenceValue: oldExperiment.previousPreferenceValue,
            },
          },
          experimentType: oldExperiment.experimentType,
        };
      }
      storage.data.experiments = v2Experiments;
      storage.saveSoon();
    },

    /** Add "actionName" field for experiments that don't have it. */
    async migration03AddActionName(storage = null) {
      if (storage === null) {
        storage = await ensureStorage();
      }

      for (const experiment of Object.values(storage.data.experiments)) {
        if (!experiment.actionName) {
          // Assume SinglePreferenceExperimentAction because as of this
          // writing, no multi-pref experiment recipe has launched.
          experiment.actionName = "SinglePreferenceExperimentAction";
        }
      }
      storage.saveSoon();
    },

    async migration04RenameNameToSlug(storage = null) {
      if (!storage) {
        storage = await ensureStorage();
      }
      // Rename "name" to "slug" to match the intended purpose of the field.
      for (const experiment of Object.values(storage.data.experiments)) {
        if (experiment.name && !experiment.slug) {
          experiment.slug = experiment.name;
          delete experiment.name;
        }
      }
      storage.saveSoon();
    },

    async migration05RemoveOldAction() {
      const experiments = await PreferenceExperiments.getAllActive();
      for (const experiment of experiments) {
        if (experiment.actionName == "SinglePreferenceExperimentAction") {
          try {
            await PreferenceExperiments.stop(experiment.slug, {
              resetValue: true,
              reason: "migration-removing-single-pref-action",
              caller: "migration05RemoveOldAction",
            });
          } catch (e) {
            log.error(
              `Stopping preference experiment ${experiment.slug} during migration failed: ${e}`
            );
          }
        }
      }
    },

    async migration06TrackOverriddenPrefs(storage = null) {
      if (!storage) {
        storage = await ensureStorage();
      }
      for (const experiment of Object.values(storage.data.experiments)) {
        for (const [preferenceName, specification] of Object.entries(
          experiment.preferences
        )) {
          if (specification.overridden !== undefined) {
            continue;
          }
          specification.overridden =
            lazy.PrefUtils.getPref(preferenceName) !==
            specification.preferenceValue;
        }
      }
      storage.saveSoon();
    },
  },
};
PK
!<�4���+�+:chrome/toolkit/res/normandy/lib/PreferenceRollouts.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { LogManager } from "resource://normandy/lib/LogManager.sys.mjs";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  CleanupManager: "resource://normandy/lib/CleanupManager.sys.mjs",
  IndexedDB: "resource://gre/modules/IndexedDB.sys.mjs",
  PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs",
  TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
  TelemetryEvents: "resource://normandy/lib/TelemetryEvents.sys.mjs",
});

const log = LogManager.getLogger("recipe-runner");

/**
 * PreferenceRollouts store info about an active or expired preference rollout.
 * @typedef {object} PreferenceRollout
 * @property {string} slug
 *   Unique slug of the experiment
 * @property {string} state
 *   The current state of the rollout: "active", "rolled-back", "graduated".
 *   Active means that Normandy is actively managing therollout. Rolled-back
 *   means that the rollout was previously active, but has been rolled back for
 *   this user. Graduated means that the built-in default now matches the
 *   rollout value, and so Normandy is no longer managing the preference.
 * @property {Array<PreferenceSpec>} preferences
 *   An array of preferences specifications involved in the rollout.
 */

/**
 * PreferenceSpec describe how a preference should change during a rollout.
 * @typedef {object} PreferenceSpec
 * @property {string} preferenceName
 *   The preference to modify.
 * @property {string} preferenceType
 *   Type of the preference being set.
 * @property {string|integer|boolean} value
 *   The value to change the preference to.
 * @property {string|integer|boolean} previousValue
 *   The value the preference would have on the default branch if this rollout
 *   were not active.
 */

const STARTUP_PREFS_BRANCH = "app.normandy.startupRolloutPrefs.";
const DB_NAME = "normandy-preference-rollout";
const STORE_NAME = "preference-rollouts";
const DB_VERSION = 1;

/**
 * Create a new connection to the database.
 */
function openDatabase() {
  return lazy.IndexedDB.open(DB_NAME, DB_VERSION, db => {
    db.createObjectStore(STORE_NAME, {
      keyPath: "slug",
    });
  });
}

/**
 * Cache the database connection so that it is shared among multiple operations.
 */
let databasePromise;
function getDatabase() {
  if (!databasePromise) {
    databasePromise = openDatabase();
  }
  return databasePromise;
}

/**
 * Get a transaction for interacting with the rollout store.
 *
 * @param {IDBDatabase} db
 * @param {String} mode Either "readonly" or "readwrite"
 *
 * NOTE: Methods on the store returned by this function MUST be called
 * synchronously, otherwise the transaction with the store will expire.
 * This is why the helper takes a database as an argument; if we fetched the
 * database in the helper directly, the helper would be async and the
 * transaction would expire before methods on the store were called.
 */
function getStore(db, mode) {
  if (!mode) {
    throw new Error("mode is required");
  }
  return db.objectStore(STORE_NAME, mode);
}

export var PreferenceRollouts = {
  STATE_ACTIVE: "active",
  STATE_ROLLED_BACK: "rolled-back",
  STATE_GRADUATED: "graduated",

  // A set of rollout slugs that are obsolete based on the code in this build of
  // Firefox. This may include things like the preference no longer being
  // applicable, or the feature changing in such a way that Normandy's automatic
  // graduation system cannot detect that the rollout should hand off to the
  // built-in code.
  GRADUATION_SET: new Set([
    "pref-webrender-intel-rollout-70-release",
    "bug-1703186-rollout-http3-support-release-88-89",
    "rollout-doh-nightly-rollout-to-all-us-desktop-users-nightly-74-80-bug-1613481",
    "rollout-doh-beta-rollout-to-all-us-desktop-users-v2-beta-74-80-bug-1613489",
    "rollout-doh-us-staged-rollout-to-all-us-desktop-users-release-73-77-bug-1586331",
    "bug-1648229-rollout-comcast-steering-rollout-release-78-80",
    "bug-1732206-rollout-fission-release-rollout-release-94-95",
    "bug-1745237-rollout-fission-beta-96-97-rollout-beta-96-97",
    "bug-1750601-rollout-doh-steering-in-canada-staggered-starting-for-release-97-98",
    "bug-1758988-rollout-doh-enablment-to-new-countries-staggered-st-release-98-100",
    "bug-1758818-rollout-enabling-doh-in-new-countries-staggered-sta-release-98-100",
  ]),

  /**
   * Update the rollout database with changes that happened during early startup.
   * @param {object} rolloutPrefsChanged Map from pref name to previous pref value
   */
  async recordOriginalValues(originalPreferences) {
    for (const rollout of await this.getAllActive()) {
      let shouldSaveRollout = false;

      // Count the number of preferences in this rollout that are now redundant.
      let prefMatchingDefaultCount = 0;

      for (const prefSpec of rollout.preferences) {
        const builtInDefault = originalPreferences[prefSpec.preferenceName];
        if (prefSpec.value === builtInDefault) {
          prefMatchingDefaultCount++;
        }
        // Store the current built-in default. That way, if the preference is
        // rolled back during the current session (ie, until the browser is
        // shut down), the correct value will be used.
        if (prefSpec.previousValue !== builtInDefault) {
          prefSpec.previousValue = builtInDefault;
          shouldSaveRollout = true;
        }
      }

      if (prefMatchingDefaultCount === rollout.preferences.length) {
        // Firefox's builtin defaults have caught up to the rollout, making all
        // of the rollout's changes redundant, so graduate the rollout.
        await this.graduate(rollout, "all-prefs-match");
        // `this.graduate` writes the rollout to the db, so we don't need to do it anymore.
        shouldSaveRollout = false;
      }

      if (shouldSaveRollout) {
        const db = await getDatabase();
        await getStore(db, "readwrite").put(rollout);
      }
    }
  },

  async init() {
    lazy.CleanupManager.addCleanupHandler(() => this.saveStartupPrefs());

    for (const rollout of await this.getAllActive()) {
      if (this.GRADUATION_SET.has(rollout.slug)) {
        await this.graduate(rollout, "in-graduation-set");
        continue;
      }
      lazy.TelemetryEnvironment.setExperimentActive(
        rollout.slug,
        rollout.state,
        {
          type: "normandy-prefrollout",
        }
      );
    }
  },

  /**
   * Test wrapper that temporarily replaces the stored rollout data with fake
   * data for testing.
   */
  withTestMock({
    graduationSet = new Set(),
    rollouts: prefRollouts = [],
  } = {}) {
    return testFunction => {
      return async args => {
        let db = await getDatabase();
        const oldData = await getStore(db, "readonly").getAll();
        await getStore(db, "readwrite").clear();
        await Promise.all(prefRollouts.map(r => this.add(r)));
        const oldGraduationSet = this.GRADUATION_SET;
        this.GRADUATION_SET = graduationSet;

        try {
          await testFunction({ ...args, prefRollouts });
        } finally {
          this.GRADUATION_SET = oldGraduationSet;
          db = await getDatabase();
          await getStore(db, "readwrite").clear();
          const store = getStore(db, "readwrite");
          await Promise.all(oldData.map(d => store.add(d)));
        }
      };
    };
  },

  /**
   * Add a new rollout
   * @param {PreferenceRollout} rollout
   */
  async add(rollout) {
    const db = await getDatabase();
    return getStore(db, "readwrite").add(rollout);
  },

  /**
   * Update an existing rollout
   * @param {PreferenceRollout} rollout
   * @throws If a matching rollout does not exist.
   */
  async update(rollout) {
    if (!(await this.has(rollout.slug))) {
      throw new Error(
        `Tried to update ${rollout.slug}, but it doesn't already exist.`
      );
    }
    const db = await getDatabase();
    return getStore(db, "readwrite").put(rollout);
  },

  /**
   * Update many existing rollouts. More efficient than calling `update` many
   * times in a row.
   * @param {Array<PreferenceRollout>} rollouts
   * @throws If any of the passed rollouts have a slug that doesn't exist in the database already.
   */
  async updateMany(rollouts) {
    // Don't touch the database if there is nothing to do
    if (!rollouts.length) {
      return;
    }

    // Both of the below operations use .map() instead of a normal loop becaues
    // once we get the object store, we can't let it expire by spinning the
    // event loop. This approach queues up all the interactions with the store
    // immediately, preventing it from expiring too soon.

    const db = await getDatabase();
    let store = await getStore(db, "readonly");
    await Promise.all(
      rollouts.map(async ({ slug }) => {
        let existingRollout = await store.get(slug);
        if (!existingRollout) {
          throw new Error(`Tried to update ${slug}, but it doesn't exist.`);
        }
      })
    );

    // awaiting spun the event loop, so the store is now invalid. Get a new
    // store. This is also a chance to get it in readwrite mode.
    store = await getStore(db, "readwrite");
    await Promise.all(rollouts.map(rollout => store.put(rollout)));
  },

  /**
   * Test whether there is a rollout in storage with the given slug.
   * @param {string} slug
   * @returns {boolean}
   */
  async has(slug) {
    const db = await getDatabase();
    const rollout = await getStore(db, "readonly").get(slug);
    return !!rollout;
  },

  /**
   * Get a rollout by slug
   * @param {string} slug
   */
  async get(slug) {
    const db = await getDatabase();
    return getStore(db, "readonly").get(slug);
  },

  /** Get all rollouts in the database. */
  async getAll() {
    const db = await getDatabase();
    return getStore(db, "readonly").getAll();
  },

  /** Get all rollouts in the "active" state. */
  async getAllActive() {
    const rollouts = await this.getAll();
    return rollouts.filter(rollout => rollout.state === this.STATE_ACTIVE);
  },

  /**
   * Save in-progress preference rollouts in a sub-branch of the normandy prefs.
   * On startup, we read these to set the rollout values.
   */
  async saveStartupPrefs() {
    const prefBranch = Services.prefs.getBranch(STARTUP_PREFS_BRANCH);
    for (const pref of prefBranch.getChildList("")) {
      prefBranch.clearUserPref(pref);
    }

    for (const rollout of await this.getAllActive()) {
      for (const prefSpec of rollout.preferences) {
        lazy.PrefUtils.setPref(
          STARTUP_PREFS_BRANCH + prefSpec.preferenceName,
          prefSpec.value
        );
      }
    }
  },

  async graduate(rollout, reason) {
    log.debug(`Graduating rollout: ${rollout.slug}`);
    rollout.state = this.STATE_GRADUATED;
    const db = await getDatabase();
    await getStore(db, "readwrite").put(rollout);
    lazy.TelemetryEvents.sendEvent(
      "graduate",
      "preference_rollout",
      rollout.slug,
      {
        reason,
      }
    );
  },
};
PK
!<��<aCUCU4chrome/toolkit/res/normandy/lib/RecipeRunner.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { LogManager } from "resource://normandy/lib/LogManager.sys.mjs";

const lazy = {};

XPCOMUtils.defineLazyServiceGetter(
  lazy,
  "timerManager",
  "@mozilla.org/updates/timer-manager;1",
  "nsIUpdateTimerManager"
);

ChromeUtils.defineESModuleGetters(lazy, {
  ActionsManager: "resource://normandy/lib/ActionsManager.sys.mjs",
  BaseAction: "resource://normandy/actions/BaseAction.sys.mjs",
  CleanupManager: "resource://normandy/lib/CleanupManager.sys.mjs",
  ClientEnvironment: "resource://normandy/lib/ClientEnvironment.sys.mjs",
  FilterExpressions:
    "resource://gre/modules/components-utils/FilterExpressions.sys.mjs",
  LegacyHeartbeat: "resource://normandy/lib/LegacyHeartbeat.sys.mjs",
  Normandy: "resource://normandy/Normandy.sys.mjs",
  NormandyApi: "resource://normandy/lib/NormandyApi.sys.mjs",
  RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
  RemoteSettingsClient:
    "resource://services-settings/RemoteSettingsClient.sys.mjs",
  Storage: "resource://normandy/lib/Storage.sys.mjs",
  TargetingContext: "resource://messaging-system/targeting/Targeting.sys.mjs",
  Uptake: "resource://normandy/lib/Uptake.sys.mjs",
  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

const log = LogManager.getLogger("recipe-runner");
const TIMER_NAME = "recipe-client-addon-run";
const REMOTE_SETTINGS_COLLECTION = "normandy-recipes-capabilities";
const PREF_CHANGED_TOPIC = "nsPref:changed";

const RUN_INTERVAL_PREF = "app.normandy.run_interval_seconds";
const FIRST_RUN_PREF = "app.normandy.first_run";
const SHIELD_ENABLED_PREF = "app.normandy.enabled";
const DEV_MODE_PREF = "app.normandy.dev_mode";
const API_URL_PREF = "app.normandy.api_url";
const LAZY_CLASSIFY_PREF = "app.normandy.experiments.lazy_classify";
const ONSYNC_SKEW_SEC_PREF = "app.normandy.onsync_skew_sec";

// Timer last update preference.
// see https://searchfox.org/mozilla-central/rev/11cfa0462/toolkit/components/timermanager/UpdateTimerManager.jsm#8
const TIMER_LAST_UPDATE_PREF = `app.update.lastUpdateTime.${TIMER_NAME}`;

const PREFS_TO_WATCH = [RUN_INTERVAL_PREF, SHIELD_ENABLED_PREF, API_URL_PREF];

ChromeUtils.defineLazyGetter(lazy, "gRemoteSettingsClient", () => {
  return lazy.RemoteSettings(REMOTE_SETTINGS_COLLECTION);
});

/**
 * cacheProxy returns an object Proxy that will memoize properties of the target.
 */
function cacheProxy(target) {
  const cache = new Map();
  return new Proxy(target, {
    get(target, prop) {
      if (!cache.has(prop)) {
        cache.set(prop, target[prop]);
      }
      return cache.get(prop);
    },
    set(target, prop, value) {
      cache.set(prop, value);
      return true;
    },
    has(target, prop) {
      return cache.has(prop) || prop in target;
    },
  });
}

export var RecipeRunner = {
  initializedPromise: Promise.withResolvers(),

  async init() {
    this.running = false;
    this.enabled = null;
    this.loadFromRemoteSettings = false;
    this._syncSkewTimeout = null;

    this.checkPrefs(); // sets this.enabled
    this.watchPrefs();
    this.setUpRemoteSettings();

    // Here "first run" means the first run this profile has ever done. This
    // preference is set to true at the end of this function, and never reset to
    // false.
    const firstRun = Services.prefs.getBoolPref(FIRST_RUN_PREF, true);

    // If we've seen a build ID from a previous run that doesn't match the
    // current build ID, run immediately. This is probably an upgrade or
    // downgrade, which may cause recipe eligibility to change.
    let hasNewBuildID =
      Services.appinfo.lastAppBuildID != null &&
      Services.appinfo.lastAppBuildID != Services.appinfo.appBuildID;

    // Dev mode is a mode used for development and QA that bypasses the normal
    // timer function of Normandy, to make testing more convenient.
    const devMode = Services.prefs.getBoolPref(DEV_MODE_PREF, false);

    if (this.enabled && (devMode || firstRun || hasNewBuildID)) {
      // In dev mode, if remote settings is enabled, force an immediate sync
      // before running. This ensures that the latest data is used for testing.
      // This is not needed for the first run case, because remote settings
      // already handles empty collections well.
      if (devMode) {
        await lazy.gRemoteSettingsClient.sync();
      }
      let trigger;
      if (devMode) {
        trigger = "devMode";
      } else if (firstRun) {
        trigger = "firstRun";
      } else if (hasNewBuildID) {
        trigger = "newBuildID";
      }

      await this.run({ trigger });
    }

    // Update the firstRun pref, to indicate that Normandy has run at least once
    // on this profile.
    if (firstRun) {
      Services.prefs.setBoolPref(FIRST_RUN_PREF, false);
    }

    this.initializedPromise.resolve();
  },

  enable() {
    if (this.enabled) {
      return;
    }
    this.registerTimer();
    this.enabled = true;
  },

  disable() {
    if (this.enabled) {
      this.unregisterTimer();
    }
    // this.enabled may be null, so always set it to false
    this.enabled = false;
  },

  /** Watch for prefs to change, and call this.observer when they do */
  watchPrefs() {
    for (const pref of PREFS_TO_WATCH) {
      Services.prefs.addObserver(pref, this);
    }

    lazy.CleanupManager.addCleanupHandler(this.unwatchPrefs.bind(this));
  },

  unwatchPrefs() {
    for (const pref of PREFS_TO_WATCH) {
      Services.prefs.removeObserver(pref, this);
    }
  },

  /** When prefs change, this is fired */
  observe(subject, topic, data) {
    switch (topic) {
      case PREF_CHANGED_TOPIC: {
        const prefName = data;

        switch (prefName) {
          case RUN_INTERVAL_PREF:
            this.updateRunInterval();
            break;

          // explicit fall-through
          case SHIELD_ENABLED_PREF:
          case API_URL_PREF:
            this.checkPrefs();
            break;

          default:
            log.debug(
              `Observer fired with unexpected pref change: ${prefName}`
            );
        }

        break;
      }
    }
  },

  checkPrefs() {
    if (!Services.prefs.getBoolPref(SHIELD_ENABLED_PREF, false)) {
      log.debug(
        `Disabling Shield because ${SHIELD_ENABLED_PREF} is set to false`
      );
      this.disable();
      return;
    }

    const apiUrl = Services.prefs.getCharPref(API_URL_PREF, "");
    if (!apiUrl) {
      log.warn(`Disabling Shield because ${API_URL_PREF} is not set.`);
      this.disable();
      return;
    }
    if (!apiUrl.startsWith("https://")) {
      log.warn(
        `Disabling Shield because ${API_URL_PREF} is not an HTTPS url: ${apiUrl}.`
      );
      this.disable();
      return;
    }

    log.debug(`Enabling Shield`);
    this.enable();
  },

  registerTimer() {
    this.updateRunInterval();
    lazy.CleanupManager.addCleanupHandler(() =>
      lazy.timerManager.unregisterTimer(TIMER_NAME)
    );
  },

  unregisterTimer() {
    lazy.timerManager.unregisterTimer(TIMER_NAME);
  },

  setUpRemoteSettings() {
    if (this._alreadySetUpRemoteSettings) {
      return;
    }
    this._alreadySetUpRemoteSettings = true;

    if (!this._onSync) {
      this._onSync = this.onSync.bind(this);
    }
    lazy.gRemoteSettingsClient.on("sync", this._onSync);

    lazy.CleanupManager.addCleanupHandler(() => {
      lazy.gRemoteSettingsClient.off("sync", this._onSync);
      this._alreadySetUpRemoteSettings = false;
    });
  },

  /** Called when our Remote Settings collection is updated */
  async onSync() {
    if (!this.enabled) {
      return;
    }

    // Delay the Normandy run by a random amount, determined by preference.
    // This helps alleviate server load, since we don't have a thundering
    // herd of users trying to update all at once.
    if (this._syncSkewTimeout) {
      lazy.clearTimeout(this._syncSkewTimeout);
    }
    let minSkewSec = 1; // this is primarily is to avoid race conditions in tests
    let maxSkewSec = Services.prefs.getIntPref(ONSYNC_SKEW_SEC_PREF, 0);
    if (maxSkewSec >= minSkewSec) {
      let skewMillis =
        (minSkewSec + Math.random() * (maxSkewSec - minSkewSec)) * 1000;
      log.debug(
        `Delaying on-sync Normandy run for ${Math.floor(
          skewMillis / 1000
        )} seconds`
      );
      this._syncSkewTimeout = lazy.setTimeout(
        () => this.run({ trigger: "sync" }),
        skewMillis
      );
    } else {
      log.debug(`Not skewing on-sync Normandy run`);
      await this.run({ trigger: "sync" });
    }
  },

  updateRunInterval() {
    // Run once every `runInterval` wall-clock seconds. This is managed by setting a "last ran"
    // timestamp, and running if it is more than `runInterval` seconds ago. Even with very short
    // intervals, the timer will only fire at most once every few minutes.
    const runInterval = Services.prefs.getIntPref(RUN_INTERVAL_PREF, 21600); // 6h
    lazy.timerManager.registerTimer(TIMER_NAME, () => this.run(), runInterval);
  },

  async run({ trigger = "timer" } = {}) {
    if (this.running) {
      // Do nothing if already running.
      return;
    }
    this.running = true;

    await lazy.Normandy.defaultPrefsHaveBeenApplied.promise;

    try {
      this.running = true;
      Services.obs.notifyObservers(null, "recipe-runner:start");

      if (this._syncSkewTimeout) {
        lazy.clearTimeout(this._syncSkewTimeout);
        this._syncSkewTimeout = null;
      }

      this.clearCaches();
      // Unless lazy classification is enabled, prep the classify cache.
      if (!Services.prefs.getBoolPref(LAZY_CLASSIFY_PREF, false)) {
        try {
          await lazy.ClientEnvironment.getClientClassification();
        } catch (err) {
          // Try to go on without this data; the filter expressions will
          // gracefully fail without this info if they need it.
        }
      }

      // Fetch recipes before execution in case we fail and exit early.
      let recipesAndSignatures;
      try {
        recipesAndSignatures = await lazy.gRemoteSettingsClient.get({
          // Do not return an empty list if an error occurs.
          emptyListFallback: false,
        });
      } catch (e) {
        await lazy.Uptake.reportRunner(lazy.Uptake.RUNNER_SERVER_ERROR);
        return;
      }

      const actionsManager = new lazy.ActionsManager();

      const legacyHeartbeat = lazy.LegacyHeartbeat.getHeartbeatRecipe();
      const noRecipes =
        !recipesAndSignatures.length && legacyHeartbeat === null;

      // Execute recipes, if we have any.
      if (noRecipes) {
        log.debug("No recipes to execute");
      } else {
        for (const { recipe, signature } of recipesAndSignatures) {
          let suitability = await this.getRecipeSuitability(recipe, signature);
          await actionsManager.processRecipe(recipe, suitability);
        }

        if (legacyHeartbeat !== null) {
          await actionsManager.processRecipe(
            legacyHeartbeat,
            lazy.BaseAction.suitability.FILTER_MATCH
          );
        }
      }

      await actionsManager.finalize({ noRecipes });

      await lazy.Uptake.reportRunner(lazy.Uptake.RUNNER_SUCCESS);
      Services.obs.notifyObservers(null, "recipe-runner:end");
    } finally {
      this.running = false;
      if (trigger != "timer") {
        // `run()` was executed outside the scheduled timer.
        // Update the last time it ran to make sure it is rescheduled later.
        const lastUpdateTime = Math.round(Date.now() / 1000);
        Services.prefs.setIntPref(TIMER_LAST_UPDATE_PREF, lastUpdateTime);
      }
    }
  },

  getFilterContext(recipe) {
    const environment = cacheProxy(lazy.ClientEnvironment);
    environment.recipe = {
      id: recipe.id,
      arguments: recipe.arguments,
    };
    return {
      env: environment,
      // Backwards compatibility -- see bug 1477255.
      normandy: environment,
    };
  },

  /**
   * Return the set of capabilities this runner has.
   *
   * This is used to pre-filter recipes that aren't compatible with this client.
   *
   * @returns {Set<String>} The capabilities supported by this client.
   */
  getCapabilities() {
    let capabilities = new Set([
      "capabilities-v1", // The initial version of the capabilities system.
    ]);

    // Get capabilities from ActionsManager.
    for (const actionCapability of lazy.ActionsManager.getCapabilities()) {
      capabilities.add(actionCapability);
    }

    // Add a capability for each transform available to JEXL.
    for (const transform of lazy.FilterExpressions.getAvailableTransforms()) {
      capabilities.add(`jexl.transform.${transform}`);
    }

    // Add two capabilities for each top level key available in the context: one
    // for the `normandy.` namespace, and another for the `env.` namespace.
    capabilities.add("jexl.context.env");
    capabilities.add("jexl.context.normandy");
    let env = lazy.ClientEnvironment;
    while (env && env.name) {
      // Walk up the class chain for ClientEnvironment, collecting applicable
      // properties as we go. Stop when we get to an unnamed object, which is
      // usually just a plain function is the super class of a class that doesn't
      // extend anything. Also stop if we get to an undefined object, just in
      // case.
      for (const [name, descriptor] of Object.entries(
        Object.getOwnPropertyDescriptors(env)
      )) {
        // All of the properties we are looking for are are static getters (so
        // will have a truthy `get` property) and are defined on the class, so
        // will be configurable
        if (descriptor.configurable && descriptor.get) {
          capabilities.add(`jexl.context.env.${name}`);
          capabilities.add(`jexl.context.normandy.${name}`);
        }
      }
      // Check for the next parent
      env = Object.getPrototypeOf(env);
    }

    return capabilities;
  },

  /**
   * Decide if a recipe is suitable to run, and returns a value from
   * `BaseAction.suitability`.
   *
   * This checks several things in order:
   *  - recipe signature
   *  - capabilities
   *  - filter expression
   *
   * If the provided signature does not match the provided recipe, then
   * `SIGNATURE_ERROR` is returned. Recipes with this suitability should not be
   * trusted. These recipes are included so that temporary signature errors on
   * the server can be handled intelligently by actions.
   *
   * Capabilities are a simple set of strings in the recipe. If the Normandy
   * client has all of the capabilities listed, then execution continues. If
   * not, then `CAPABILITY_MISMATCH` is returned. Recipes with this suitability
   * should be considered incompatible and treated with caution.
   *
   * If the capabilities check passes, then the filter expression is evaluated
   * against the current environment. The result of the expression is cast to a
   * boolean. If it is true, then `FILTER_MATCH` is returned. If not, then
   * `FILTER_MISMATCH` is returned.
   *
   * If there is an error while evaluating the recipe's filter, `FILTER_ERROR`
   * is returned instead.
   *
   * @param {object} recipe
   * @param {object} signature
   * @param {string} recipe.filter_expression The expression to evaluate against the environment.
   * @param {Set<String>} runnerCapabilities The capabilities provided by this runner.
   * @return {Promise<BaseAction.suitability>} The recipe's suitability
   */
  async getRecipeSuitability(recipe, signature) {
    let generator = this.getAllSuitabilities(recipe, signature);
    // For our purposes, only the first suitability matters, so pull the first
    // value out of the async generator. This additionally guarantees if we fail
    // a security or compatibility check, we won't continue to run other checks,
    // which is good for the general case of running recipes.
    let { value: suitability } = await generator.next();
    switch (suitability) {
      case lazy.BaseAction.suitability.SIGNATURE_ERROR: {
        await lazy.Uptake.reportRecipe(
          recipe,
          lazy.Uptake.RECIPE_INVALID_SIGNATURE
        );
        break;
      }

      case lazy.BaseAction.suitability.CAPABILITIES_MISMATCH: {
        await lazy.Uptake.reportRecipe(
          recipe,
          lazy.Uptake.RECIPE_INCOMPATIBLE_CAPABILITIES
        );
        break;
      }

      case lazy.BaseAction.suitability.FILTER_MATCH: {
        // No telemetry needs to be sent for this right now.
        break;
      }

      case lazy.BaseAction.suitability.FILTER_MISMATCH: {
        // This represents a terminal state for the given recipe, so
        // report its outcome. Others are reported when executed in
        // ActionsManager.
        await lazy.Uptake.reportRecipe(
          recipe,
          lazy.Uptake.RECIPE_DIDNT_MATCH_FILTER
        );
        break;
      }

      case lazy.BaseAction.suitability.FILTER_ERROR: {
        await lazy.Uptake.reportRecipe(
          recipe,
          lazy.Uptake.RECIPE_FILTER_BROKEN
        );
        break;
      }

      case lazy.BaseAction.suitability.ARGUMENTS_INVALID: {
        // This shouldn't ever occur, since the arguments schema is checked by
        // BaseAction itself.
        throw new Error(`Shouldn't get ${suitability} in RecipeRunner`);
      }

      default: {
        throw new Error(`Unexpected recipe suitability ${suitability}`);
      }
    }

    return suitability;
  },

  /**
   * Some uses cases, such as Normandy Devtools, want the status of all
   * suitabilities, not only the most important one. This checks the cases of
   * suitabilities in order from most blocking to least blocking. The first
   * yielded is the "primary" suitability to pass on to actions.
   *
   * If this function yields only [FILTER_MATCH], then the recipe fully matches
   * and should be executed. If any other statuses are yielded, then the recipe
   * should not be executed as normal.
   *
   * This is a generator so that the execution can be halted as needed. For
   * example, after receiving a signature error, a caller can stop advancing
   * the iterator to avoid exposing the browser to unneeded risk.
   */
  async *getAllSuitabilities(recipe, signature) {
    try {
      await lazy.NormandyApi.verifyObjectSignature(recipe, signature, "recipe");
    } catch (e) {
      yield lazy.BaseAction.suitability.SIGNATURE_ERROR;
    }

    const runnerCapabilities = this.getCapabilities();
    if (Array.isArray(recipe.capabilities)) {
      for (const recipeCapability of recipe.capabilities) {
        if (!runnerCapabilities.has(recipeCapability)) {
          log.debug(
            `Recipe "${recipe.name}" requires unknown capabilities. ` +
              `Recipe capabilities: ${JSON.stringify(recipe.capabilities)}. ` +
              `Local runner capabilities: ${JSON.stringify(
                Array.from(runnerCapabilities)
              )}`
          );
          yield lazy.BaseAction.suitability.CAPABILITIES_MISMATCH;
        }
      }
    }

    const context = this.getFilterContext(recipe);
    const targetingContext = new lazy.TargetingContext();
    try {
      if (await targetingContext.eval(recipe.filter_expression, context)) {
        yield lazy.BaseAction.suitability.FILTER_MATCH;
      } else {
        yield lazy.BaseAction.suitability.FILTER_MISMATCH;
      }
    } catch (err) {
      log.error(
        `Error checking filter for "${recipe.name}". Filter: [${recipe.filter_expression}]. Error: "${err}"`
      );
      yield lazy.BaseAction.suitability.FILTER_ERROR;
    }
  },

  /**
   * Clear all caches of systems used by RecipeRunner, in preparation
   * for a clean run.
   */
  clearCaches() {
    lazy.ClientEnvironment.clearClassifyCache();
    lazy.NormandyApi.clearIndexCache();
  },

  /**
   * Clear out cached state and fetch/execute recipes from the given
   * API url. This is used mainly by the mock-recipe-server JS that is
   * executed in the browser console.
   */
  async testRun(baseApiUrl) {
    const oldApiUrl = Services.prefs.getCharPref(API_URL_PREF, "");
    Services.prefs.setCharPref(API_URL_PREF, baseApiUrl);

    try {
      lazy.Storage.clearAllStorage();
      this.clearCaches();
      await this.run();
    } finally {
      Services.prefs.setCharPref(API_URL_PREF, oldApiUrl);
      this.clearCaches();
    }
  },

  /**
   * Offer a mechanism to get access to the lazily-instantiated
   * gRemoteSettingsClient, because if someone instantiates it
   * themselves, it won't have the options we provided in this module,
   * and it will prevent instantiation by this module later.
   *
   * This is only meant to be used in testing, where it is a
   * convenient hook to store data in the underlying remote-settings
   * collection.
   */
  get _remoteSettingsClientForTesting() {
    return lazy.gRemoteSettingsClient;
  },

  migrations: {
    /**
     * Delete the now-unused collection of recipes, since we are using the
     * "normandy-recipes-capabilities" collection now.
     */
    async migration01RemoveOldRecipesCollection() {
      // Don't bother to open IDB and clear on clean profiles.
      const lastCheckPref =
        "services.settings.main.normandy-recipes.last_check";
      if (Services.prefs.prefHasUserValue(lastCheckPref)) {
        // We instantiate a client, but it won't take part of sync.
        const client = new lazy.RemoteSettingsClient("normandy-recipes");
        await client.db.clear();
        Services.prefs.clearUserPref(lastCheckPref);
      }
    },
  },
};
PK
!<v0�

9chrome/toolkit/res/normandy/lib/ShieldPreferences.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  BranchedAddonStudyAction:
    "resource://normandy/actions/BranchedAddonStudyAction.sys.mjs",
  AddonStudies: "resource://normandy/lib/AddonStudies.sys.mjs",
  CleanupManager: "resource://normandy/lib/CleanupManager.sys.mjs",
  PreferenceExperiments:
    "resource://normandy/lib/PreferenceExperiments.sys.mjs",
});

const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed"; // from modules/libpref/nsIPrefBranch.idl
const PREF_OPT_OUT_STUDIES_ENABLED = "app.shield.optoutstudies.enabled";

/**
 * Handles Shield-specific preferences, including their UI.
 */
export var ShieldPreferences = {
  init() {
    // Watch for changes to the Opt-out pref
    Services.prefs.addObserver(PREF_OPT_OUT_STUDIES_ENABLED, this);

    lazy.CleanupManager.addCleanupHandler(() => {
      Services.prefs.removeObserver(PREF_OPT_OUT_STUDIES_ENABLED, this);
    });
  },

  observe(subject, topic, data) {
    switch (topic) {
      case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
        this.observePrefChange(data);
        break;
    }
  },

  async observePrefChange(prefName) {
    let prefValue;
    switch (prefName) {
      // If the opt-out pref changes to be false, disable all current studies.
      case PREF_OPT_OUT_STUDIES_ENABLED: {
        prefValue = Services.prefs.getBoolPref(PREF_OPT_OUT_STUDIES_ENABLED);
        if (!prefValue) {
          const action = new lazy.BranchedAddonStudyAction();
          const studyPromises = (await lazy.AddonStudies.getAll()).map(
            study => {
              if (!study.active) {
                return null;
              }
              return action.unenroll(study.recipeId, "general-opt-out");
            }
          );

          const experimentPromises = (
            await lazy.PreferenceExperiments.getAll()
          ).map(experiment => {
            if (experiment.expired) {
              return null;
            }
            return lazy.PreferenceExperiments.stop(experiment.slug, {
              reason: "general-opt-out",
              caller: "observePrefChange::general-opt-out",
            });
          });

          const allPromises = studyPromises
            .concat(experimentPromises)
            .map(p => p && p.catch(err => console.error(err)));
          await Promise.all(allPromises);
        }
        break;
      }
    }
  },
};
PK
!<>����/chrome/toolkit/res/normandy/lib/Storage.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  JSONFile: "resource://gre/modules/JSONFile.sys.mjs",
});

// Lazy-load JSON file that backs Storage instances.
ChromeUtils.defineLazyGetter(lazy, "lazyStore", async function () {
  const path = PathUtils.join(
    PathUtils.profileDir,
    "shield-recipe-client.json"
  );
  const store = new lazy.JSONFile({ path });
  await store.load();
  return store;
});

export var Storage = class {
  constructor(prefix) {
    this.prefix = prefix;
  }

  /**
   * Clear ALL storage data and save to the disk.
   */
  static async clearAllStorage() {
    const store = await lazy.lazyStore;
    store.data = {};
    store.saveSoon();
  }

  /**
   * Sets an item in the prefixed storage.
   * @returns {Promise}
   * @resolves With the stored value, or null.
   * @rejects Javascript exception.
   */
  async getItem(name) {
    const store = await lazy.lazyStore;
    const namespace = store.data[this.prefix] || {};
    return namespace[name] || null;
  }

  /**
   * Sets an item in the prefixed storage.
   * @returns {Promise}
   * @resolves When the operation is completed successfully
   * @rejects Javascript exception.
   */
  async setItem(name, value) {
    const store = await lazy.lazyStore;
    if (!(this.prefix in store.data)) {
      store.data[this.prefix] = {};
    }
    store.data[this.prefix][name] = value;
    store.saveSoon();
  }

  /**
   * Removes a single item from the prefixed storage.
   * @returns {Promise}
   * @resolves When the operation is completed successfully
   * @rejects Javascript exception.
   */
  async removeItem(name) {
    const store = await lazy.lazyStore;
    if (this.prefix in store.data) {
      delete store.data[this.prefix][name];
      store.saveSoon();
    }
  }

  /**
   * Clears all storage for the prefix.
   * @returns {Promise}
   * @resolves When the operation is completed successfully
   * @rejects Javascript exception.
   */
  async clear() {
    const store = await lazy.lazyStore;
    store.data[this.prefix] = {};
    store.saveSoon();
  }
};
PK
!<�&�/��7chrome/toolkit/res/normandy/lib/TelemetryEvents.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const TELEMETRY_CATEGORY = "normandy";

export const TelemetryEvents = {
  init() {
    Services.telemetry.setEventRecordingEnabled(TELEMETRY_CATEGORY, true);
  },

  sendEvent(method, object, value, extra) {
    for (const val of Object.values(extra)) {
      if (val == null) {
        throw new Error(
          "Extra parameters in telemetry events must not be null"
        );
      }
    }
    Services.telemetry.recordEvent(
      TELEMETRY_CATEGORY,
      method,
      object,
      value,
      extra
    );
  },
};
PK
!<�M�Dm	m	.chrome/toolkit/res/normandy/lib/Uptake.sys.mjs/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

import { UptakeTelemetry } from "resource://services-common/uptake-telemetry.sys.mjs";

const COMPONENT = "normandy";

export var Uptake = {
  // Action uptake
  ACTION_NETWORK_ERROR: UptakeTelemetry.STATUS.NETWORK_ERROR,
  ACTION_PRE_EXECUTION_ERROR: UptakeTelemetry.STATUS.CUSTOM_1_ERROR,
  ACTION_POST_EXECUTION_ERROR: UptakeTelemetry.STATUS.CUSTOM_2_ERROR,
  ACTION_SERVER_ERROR: UptakeTelemetry.STATUS.SERVER_ERROR,
  ACTION_SUCCESS: UptakeTelemetry.STATUS.SUCCESS,

  // Per-recipe uptake
  RECIPE_ACTION_DISABLED: UptakeTelemetry.STATUS.CUSTOM_1_ERROR,
  RECIPE_DIDNT_MATCH_FILTER: UptakeTelemetry.STATUS.BACKOFF,
  RECIPE_INCOMPATIBLE_CAPABILITIES: UptakeTelemetry.STATUS.BACKOFF,
  RECIPE_EXECUTION_ERROR: UptakeTelemetry.STATUS.APPLY_ERROR,
  RECIPE_FILTER_BROKEN: UptakeTelemetry.STATUS.CONTENT_ERROR,
  RECIPE_ARGUMENTS_INVALID: UptakeTelemetry.STATUS.CONTENT_ERROR,
  RECIPE_INVALID_ACTION: UptakeTelemetry.STATUS.DOWNLOAD_ERROR,
  RECIPE_SUCCESS: UptakeTelemetry.STATUS.SUCCESS,
  RECIPE_INVALID_SIGNATURE: UptakeTelemetry.STATUS.SIGNATURE_ERROR,

  // Uptake for the runner as a whole
  RUNNER_NETWORK_ERROR: UptakeTelemetry.STATUS.NETWORK_ERROR,
  RUNNER_SERVER_ERROR: UptakeTelemetry.STATUS.SERVER_ERROR,
  RUNNER_SUCCESS: UptakeTelemetry.STATUS.SUCCESS,

  async _report(status, source) {
    // Telemetry doesn't help us much with error detection, so do some here.
    if (!status) {
      throw new Error(
        `Uptake status is required (got "${JSON.stringify(status)}"`
      );
    }
    if (!source) {
      throw new Error(
        `Uptake source is required (got "${JSON.stringify(status)}`
      );
    }
    await UptakeTelemetry.report(COMPONENT, status, {
      source: `${COMPONENT}/${source}`,
    });
  },

  async reportRunner(status) {
    await Uptake._report(status, "runner");
  },

  async reportRecipe(recipe, status) {
    await Uptake._report(status, `recipe/${recipe.id}`);
    const revisionId = parseInt(recipe.revision_id, 10);
    Services.telemetry.keyedScalarSet(
      "normandy.recipe_freshness",
      recipe.id,
      revisionId
    );
  },

  async reportAction(actionName, status) {
    await Uptake._report(status, `action/${actionName}`);
  },
};
PK
!<@��	�	?chrome/toolkit/res/normandy/schemas/LegacyHeartbeat.schema.json{
  "$schema": "http://json-schema.org/draft-04/schema#",
  "title": "Legacy (Normandy) Heartbeat, via Nimbus",
  "description": "The schema for the Legacy Heartbeat Nimbus feature.",
  "type": "object",
  "properties": {
    "survey": {
      "$comment": "Hearbeat arguments are nested under survey to prevent simultaneous rollouts and experiments from overriding eachothers optional variables",
      "type": "object",
      "properties": {
        "repeatOption": {
          "type": "string",
          "enum": ["once", "xdays", "nag"],
          "description": "Determines how often a prompt is shown executes.",
          "default": "once"
        },
        "repeatEvery": {
          "description": "For repeatOption=xdays, how often (in days) the prompt is displayed.",
          "default": null,
          "type": ["number", "null"]
        },
        "includeTelemetryUUID": {
          "type": "boolean",
          "description": "Include unique user ID in post-answer-url and Telemetry",
          "default": false
        },
        "surveyId": {
          "description": "Slug uniquely identifying this survey in telemetry",
          "type": "string"
        },
        "message": {
          "description": "Message to show to the user",
          "type": "string"
        },
        "engagementButtonLabel": {
          "description": "Text for the engagement button. If specified, this button will be shown instead of rating stars.",
          "default": null,
          "type": ["string", "null"]
        },
        "thanksMessage": {
          "description": "Thanks message to show to the user after they've rated Firefox",
          "type": "string"
        },
        "postAnswerUrl": {
          "description": "URL to redirect the user to after rating Firefox or clicking the engagement button",
          "default": null,
          "type": ["string", "null"]
        },
        "learnMoreMessage": {
          "description": "Message to show to the user to learn more",
          "default": null,
          "type": ["string", "null"]
        },
        "learnMoreUrl": {
          "description": "URL to show to the user when they click Learn More",
          "default": null,
          "type": ["string", "null"]
        }
      },
      "required": [
        "surveyId",
        "message",
        "thanksMessage",
        "postAnswerUrl",
        "learnMoreMessage",
        "learnMoreUrl"
      ],
      "additionalProperties": false
    }
  },
  "required": ["survey"],
  "additionalProperties": false
}
PK
!<Y&�5chrome/toolkit/res/normandy/skin/shared/Heartbeat.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* Notification overrides for Heartbeat UI */

@keyframes pulse-onshow {
  0% {
    opacity: 0;
    transform: scale(1);
  }

  25% {
    opacity: 1;
    transform: scale(1.1);
  }

  50% {
    transform: scale(1);
  }

  75% {
    transform: scale(1.1);
  }

  100% {
    transform: scale(1);
  }
}

@keyframes pulse-twice {
  0% {
    transform: scale(1.1);
  }

  50% {
    transform: scale(0.8);
  }

  100% {
    transform: scale(1);
  }
}

.messageImage.heartbeat {
  /* Needed for the animation to not get clipped when pulsing. */
  margin-inline: 8px;
}

.messageImage.heartbeat.pulse-onshow {
  animation-duration: 1.5s;
  animation-iteration-count: 1;
  animation-name: pulse-onshow;
  animation-timing-function: cubic-bezier(0.7, 1.8, 0.9, 1.1);
}

.messageImage.heartbeat.pulse-twice {
  animation-duration: 1s;
  animation-iteration-count: 2;
  animation-name: pulse-twice;
  animation-timing-function: linear;
}

/* Learn More link styles */
.heartbeat > hbox > .text-link {
  margin-inline-start: 0 !important;
}

.heartbeat > hbox > .text-link:hover {
  text-decoration: none !important;
}

/* Heartbeat UI Rating Star Classes */
#star-rating-container {
  display: flex;
  margin-block: 4px;
}

#star-rating-container > #star5 {
  order: 5;
}

#star-rating-container > #star4 {
  order: 4;
}

#star-rating-container > #star3 {
  order: 3;
}

#star-rating-container > #star2 {
  order: 2;
}

#star-rating-container > .star-x {
  background: url("resource://normandy/skin/shared/heartbeat-star-off.svg");
  cursor: pointer;
  height: 16px;
  margin-inline-end: 4px !important; /* Overrides the margin-inline-end for all platforms defined in the .plain class */
  width: 16px;
}

#star-rating-container > .star-x:hover,
#star-rating-container > .star-x:hover ~ .star-x {
  background: url("resource://normandy/skin/shared/heartbeat-star-lit.svg");
}
PK
!<����
�
:chrome/toolkit/res/normandy/skin/shared/heartbeat-icon.svg<?xml version="1.0" encoding="UTF-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg width="288px" height="248px" viewBox="0 0 288 248" xmlns="http://www.w3.org/2000/svg">
  <path fill="#d74345" d="M144,248.571429 C141.214272,248.571429 138.857152,247.607152 136.928571,245.678571 L36.6428571,148.928571 C35.5714232,148.071424 34.0982237,146.678581 32.2232143,144.75 C30.3482049,142.821419 27.3750204,139.312525 23.3035714,134.223214 C19.2321225,129.133903 15.5893018,123.910741 12.375,118.553571 C9.16069821,113.196402 6.29465545,106.714324 3.77678571,99.1071429 C1.25891598,91.499962 0,84.1071788 0,76.9285714 C0,53.357025 6.80350339,34.9286379 20.4107143,21.6428571 C34.0179252,8.35707643 52.8213086,1.71428571 76.8214286,1.71428571 C83.4643189,1.71428571 90.2410369,2.86605991 97.1517857,5.16964286 C104.062535,7.4732258 110.491042,10.5803376 116.4375,14.4910714 C122.383958,18.4018053 127.499979,22.0714114 131.785714,25.5 C136.07145,28.9285886 140.142838,32.5714093 144,36.4285714 C147.857162,32.5714093 151.92855,28.9285886 156.214286,25.5 C160.500021,22.0714114 165.616042,18.4018053 171.5625,14.4910714 C177.508958,10.5803376 183.937465,7.4732258 190.848214,5.16964286 C197.758963,2.86605991 204.535681,1.71428571 211.178571,1.71428571 C235.178691,1.71428571 253.982075,8.35707643 267.589286,21.6428571 C281.196497,34.9286379 288,53.357025 288,76.9285714 C288,100.607261 275.732266,124.714163 251.196429,149.25 L151.071429,245.678571 C149.142847,247.607152 146.785728,248.571429 144,248.571429 L144,248.571429 Z" transform="translate(0,-1)"/>
  <g transform="translate(0,-0.29)">
    <mask id="mask" fill="#fff">
      <path d="M144,246.857143 C141.214272,246.857143 138.857152,245.892867 136.928571,243.964286 L36.6428571,147.214286 C35.5714232,146.357139 34.0982237,144.964295 32.2232143,143.035714 C30.3482049,141.107133 27.3750204,137.59824 23.3035714,132.508929 C19.2321225,127.419617 15.5893018,122.196455 12.375,116.839286 C9.16069821,111.482116 6.29465545,105.000038 3.77678571,97.3928571 C1.25891598,89.7856763 0,82.392893 0,75.2142857 C0,51.6427393 6.80350339,33.2143521 20.4107143,19.9285714 C34.0179252,6.64279071 52.8213086,0 76.8214286,0 C83.4643189,0 90.2410369,1.1517742 97.1517857,3.45535714 C104.062535,5.75894009 110.491042,8.86605187 116.4375,12.7767857 C122.383958,16.6875196 127.499979,20.3571257 131.785714,23.7857143 C136.07145,27.2143029 140.142838,30.8571236 144,34.7142857 C147.857162,30.8571236 151.92855,27.2143029 156.214286,23.7857143 C160.500021,20.3571257 165.616042,16.6875196 171.5625,12.7767857 C177.508958,8.86605187 183.937465,5.75894009 190.848214,3.45535714 C197.758963,1.1517742 204.535681,0 211.178571,0 C235.178691,0 253.982075,6.64279071 267.589286,19.9285714 C281.196497,33.2143521 288,51.6427393 288,75.2142857 C288,98.8929755 275.732266,122.999877 251.196429,147.535714 L151.071429,243.964286 C149.142847,245.892867 146.785728,246.857143 144,246.857143 L144,246.857143 Z"/>
    </mask>
    <path fill="none" stroke="#fff" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" mask="url(#mask)" d="M-166,115.135254 C-166,115.135254 0.595052083,115.135254 2.9765625,115.135254 L91.9101562,115.135254 L97.9638977,100.101562 L105.430695,115.135254 L114.893585,115.135254 L131.129913,189.53125 L148.161163,57 L165.348663,131.027344 L172.272491,115.135254 L250.84967,115.135254 L428.259813,115.135254"/>
  </g>
</svg>
PK
!<�o;!��>chrome/toolkit/res/normandy/skin/shared/heartbeat-star-lit.svg<?xml version="1.0" encoding="UTF-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="100%" height="100%">
  <path fill="#0095dd" d="M8,0C7.7,0,7.4,0.2,7.2,0.7l-2,4.1L0.9,5.5c-1,0.2-1.2,0.9-0.5,1.6l3.1,3.3l-0.7,4.6C2.7,15.6,3,16,3.4,16c0.2,0,0.4-0.1,0.6-0.2L8,13.7l3.9,2.1c0.2,0.1,0.5,0.2,0.6,0.2c0.5,0,0.8-0.4,0.7-1.1l-0.7-4.6l3.1-3.3c0.7-0.7,0.4-1.4-0.5-1.6l-4.3-0.7l-2-4.1C8.6,0.2,8.3,0,8,0L8,0z"/>
</svg>
PK
!<�����>chrome/toolkit/res/normandy/skin/shared/heartbeat-star-off.svg<?xml version="1.0" encoding="UTF-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="100%" height="100%">
  <path fill="#c0c0c0" d="M8,0C7.7,0,7.4,0.2,7.2,0.7l-2,4.1L0.9,5.5c-1,0.2-1.2,0.9-0.5,1.6l3.1,3.3l-0.7,4.6C2.7,15.6,3,16,3.4,16c0.2,0,0.4-0.1,0.6-0.2L8,13.7l3.9,2.1c0.2,0.1,0.5,0.2,0.6,0.2c0.5,0,0.8-0.4,0.7-1.1l-0.7-4.6l3.1-3.3c0.7-0.7,0.4-1.4-0.5-1.6l-4.3-0.7l-2-4.1C8.6,0.2,8.3,0,8,0L8,0z"/>
</svg>
PK
!<���Q�,�,5chrome/toolkit/res/normandy/vendor/LICENSE_THIRDPARTYfbjs@0.8.16 MIT
MIT License

Copyright (c) 2013-present, Facebook, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


react-dom@15.6.1 BSD-3-Clause
BSD License

For React software

Copyright (c) 2013-present, Facebook, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

 * Neither the name Facebook nor the names of its contributors may be used to
   endorse or promote products derived from this software without specific
   prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


object-assign@4.1.1 MIT
The MIT License (MIT)

Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


react@15.6.1 BSD-3-Clause
BSD License

For React software

Copyright (c) 2013-present, Facebook, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

 * Neither the name Facebook nor the names of its contributors may be used to
   endorse or promote products derived from this software without specific
   prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


prop-types@15.5.10 BSD-3-Clause
BSD License

For React software

Copyright (c) 2013-present, Facebook, Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

 * Neither the name Facebook nor the names of its contributors may be used to
   endorse or promote products derived from this software without specific
   prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

create-react-class@15.6.2 MIT
MIT License

Copyright (c) 2013-present, Facebook, Inc.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


mozjexl@1.1.5 MIT
Copyright for portions of mozJexl are held by TechnologyAdvice, 2015 as part of Jexl.
All other copyright for mozJexl are held by the Mozilla Foundation, 2017.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

process@0.11.10 MIT
(The MIT License)

Copyright (c) 2013 Roman Shtylman <shtylman@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


classnames@2.2.5 MIT
The MIT License (MIT)

Copyright (c) 2016 Jed Watson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
PK
!<�&(00/chrome/toolkit/res/normandy/vendor/PropTypes.js/* eslint-disable */this.PropTypes=function(a){function b(d){if(c[d])return c[d].exports;var e=c[d]={i:d,l:!1,exports:{}};return a[d].call(e.exports,e,e.exports,b),e.l=!0,e.exports}var c={};return b.m=a,b.c=c,b.d=function(a,c,d){b.o(a,c)||Object.defineProperty(a,c,{configurable:!1,enumerable:!0,get:d})},b.n=function(a){var c=a&&a.__esModule?function(){return a['default']}:function(){return a};return b.d(c,'a',c),c},b.o=function(a,b){return Object.prototype.hasOwnProperty.call(a,b)},b.p='',b(b.s=100)}({0:function(a){'use strict';var g=function(){};!1,a.exports=function(h,i,j,a,b,c,d,e){if(g(i),!h){var f;if(void 0===i)f=new Error('Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.');else{var k=[j,a,b,c,d,e],l=0;f=new Error(i.replace(/%s/g,function(){return k[l++]})),f.name='Invariant Violation'}throw f.framesToPop=1,f}}},100:function(a,b,c){a.exports=c(101)()},101:function(a,b,c){'use strict';var d=c(5),e=c(0),f=c(19);a.exports=function(){function a(a,b,c,d,g,h){h===f||e(!1,'Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types')}function b(){return a}a.isRequired=a;var c={array:a,bool:a,func:a,number:a,object:a,string:a,symbol:a,any:a,arrayOf:b,element:a,instanceOf:b,node:a,objectOf:b,oneOf:b,oneOfType:b,shape:b};return c.checkPropTypes=d,c.PropTypes=c,c}},19:function(a){'use strict';a.exports='SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED'},5:function(a){'use strict';function b(a){return function(){return a}}var c=function(){};c.thatReturns=b,c.thatReturnsFalse=b(!1),c.thatReturnsTrue=b(!0),c.thatReturnsNull=b(null),c.thatReturnsThis=function(){return this},c.thatReturnsArgument=function(a){return a},a.exports=c}});
PK
!<��3�U�U+chrome/toolkit/res/normandy/vendor/React.js/* eslint-disable */this.React=function(e){function t(o){if(n[o])return n[o].exports;var r=n[o]={i:o,l:!1,exports:{}};return e[o].call(r.exports,r,r.exports,t),r.l=!0,r.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,o){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:o})},t.n=function(e){var n=e&&e.__esModule?function(){return e['default']}:function(){return e};return t.d(n,'a',n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p='',t(t.s=102)}([function(e){'use strict';var t=function(){};!1,e.exports=function(n,o,r,a,i,p,s,e){if(t(o),!n){var d;if(void 0===o)d=new Error('Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.');else{var l=[r,a,i,p,s,e],u=0;d=new Error(o.replace(/%s/g,function(){return l[u++]})),d.name='Invariant Violation'}throw d.framesToPop=1,d}}},function(e,t,n){'use strict';var o=n(5);e.exports=o},,function(e){'use strict';/*
object-assign
(c) Sindre Sorhus
@license MIT
*/function t(e){if(null===e||e===void 0)throw new TypeError('Object.assign cannot be called with null or undefined');return Object(e)}var n=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;e.exports=function(){try{if(!Object.assign)return!1;var e=new String('abc');if(e[5]='de','5'===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;10>n;n++)t['_'+String.fromCharCode(n)]=n;var o=Object.getOwnPropertyNames(t).map(function(e){return t[e]});if('0123456789'!==o.join(''))return!1;var r={};return['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t'].forEach(function(e){r[e]=e}),'abcdefghijklmnopqrst'===Object.keys(Object.assign({},r)).join('')}catch(e){return!1}}()?Object.assign:function(e){for(var a,p,d=t(e),l=1;l<arguments.length;l++){for(var s in a=Object(arguments[l]),a)o.call(a,s)&&(d[s]=a[s]);if(n){p=n(a);for(var u=0;u<p.length;u++)r.call(a,p[u])&&(d[p[u]]=a[p[u]])}}return d}},,function(e){'use strict';function t(e){return function(){return e}}var n=function(){};n.thatReturns=t,n.thatReturnsFalse=t(!1),n.thatReturnsTrue=t(!0),n.thatReturnsNull=t(null),n.thatReturnsThis=function(){return this},n.thatReturnsArgument=function(e){return e},e.exports=n},,function(e,t,n){'use strict';function o(e){return e.ref!==void 0}function r(e){return e.key!==void 0}var a,i,p=n(3),s=n(8),d=n(1),l=n(22),u=Object.prototype.hasOwnProperty,c=n(23),m={key:!0,ref:!0,__self:!0,__source:!0},f=function(e,t,n,o,r,a,i){return!1,{$$typeof:c,type:e,key:t,ref:n,props:i,_owner:a}};f.createElement=function(e,t,n){var a,p={},d=null,l=null,c=null,y=null;if(null!=t)for(a in o(t)&&(l=t.ref),r(t)&&(d=''+t.key),c=void 0===t.__self?null:t.__self,y=void 0===t.__source?null:t.__source,t)u.call(t,a)&&!m.hasOwnProperty(a)&&(p[a]=t[a]);var h=arguments.length-2;if(1==h)p.children=n;else if(1<h){for(var g=Array(h),b=0;b<h;b++)g[b]=arguments[b+2];!1,p.children=g}if(e&&e.defaultProps){var i=e.defaultProps;for(a in i)void 0===p[a]&&(p[a]=i[a])}return f(e,d,l,c,y,s.current,p)},f.createFactory=function(e){var t=f.createElement.bind(null,e);return t.type=e,t},f.cloneAndReplaceKey=function(e,t){var n=f(e.type,t,e.ref,e._self,e._source,e._owner,e.props);return n},f.cloneElement=function(e,t,n){var a,d=p({},e.props),l=e.key,c=e.ref,y=e._self,h=e._source,g=e._owner;if(null!=t){o(t)&&(c=t.ref,g=s.current),r(t)&&(l=''+t.key);var b;for(a in e.type&&e.type.defaultProps&&(b=e.type.defaultProps),t)u.call(t,a)&&!m.hasOwnProperty(a)&&(d[a]=void 0===t[a]&&void 0!==b?b[a]:t[a])}var E=arguments.length-2;if(1==E)d.children=n;else if(1<E){for(var x=Array(E),P=0;P<E;P++)x[P]=arguments[P+2];d.children=x}return f(e.type,l,c,y,h,g,d)},f.isValidElement=function(e){return'object'==typeof e&&null!==e&&e.$$typeof===c},e.exports=f},function(e){'use strict';e.exports={current:null}},,function(e){'use strict';e.exports=function(e){for(var t=arguments.length-1,n='Minified React error #'+e+'; visit http://facebook.github.io/react/docs/error-decoder.html?invariant='+e,o=0;o<t;o++)n+='&args[]='+encodeURIComponent(arguments[o+1]);n+=' for the full message or use the non-minified dev environment for full errors and additional helpful warnings.';var r=new Error(n);throw r.name='Invariant Violation',r.framesToPop=1,r}},,,function(e,t,n){'use strict';var o=n(3),r=n(20),a=n(35),i=n(40),p=n(7),s=n(41),d=n(44),l=n(45),u=n(47),c=p.createElement,m=p.createFactory,f=p.cloneElement;var y={Children:{map:a.map,forEach:a.forEach,count:a.count,toArray:a.toArray,only:u},Component:r.Component,PureComponent:r.PureComponent,createElement:c,cloneElement:f,isValidElement:p.isValidElement,PropTypes:s,createClass:l,createFactory:m,createMixin:function(e){return e},DOM:i,version:d,__spread:o};e.exports=y},function(e){'use strict';!1,e.exports={}},,,,,function(e){'use strict';e.exports='SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED'},function(e,t,n){'use strict';function o(e,t,n){this.props=e,this.context=t,this.refs=l,this.updater=n||s}function r(e,t,n){this.props=e,this.context=t,this.refs=l,this.updater=n||s}function a(){}var i=n(10),p=n(3),s=n(21),d=n(22),l=n(14),u=n(0),c=n(34);o.prototype.isReactComponent={},o.prototype.setState=function(e,t){'object'==typeof e||'function'==typeof e||null==e?void 0:i('85'),this.updater.enqueueSetState(this,e),t&&this.updater.enqueueCallback(this,t,'setState')},o.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this),e&&this.updater.enqueueCallback(this,e,'forceUpdate')};a.prototype=o.prototype,r.prototype=new a,r.prototype.constructor=r,p(r.prototype,o.prototype),r.prototype.isPureReactComponent=!0,e.exports={Component:o,PureComponent:r}},function(e,t,n){'use strict';function o(){}var r=n(1);e.exports={isMounted:function(){return!1},enqueueCallback:function(){},enqueueForceUpdate:function(e){o(e,'forceUpdate')},enqueueReplaceState:function(e){o(e,'replaceState')},enqueueSetState:function(e){o(e,'setState')}}},function(e){'use strict';e.exports=!1},function(e){'use strict';var t='function'==typeof Symbol&&Symbol['for']&&Symbol['for']('react.element')||60103;e.exports=t},,,,,function(e,t,n){'use strict';var o=n(42);e.exports=function(e){return o(e,!1)}},,,,,,function(e){'use strict';e.exports=function(){}},function(e,t,n){'use strict';function o(e){return(''+e).replace(h,'$&/')}function r(e,t){this.func=e,this.context=t,this.count=0}function a(e,t){var n=e.func,o=e.context;n.call(o,t,e.count++)}function i(e,t,n,o){this.result=e,this.keyPrefix=t,this.func=n,this.context=o,this.count=0}function p(e,t,n){var r=e.result,a=e.keyPrefix,i=e.func,p=e.context,d=i.call(p,t,e.count++);Array.isArray(d)?s(d,r,n,c.thatReturnsArgument):null!=d&&(u.isValidElement(d)&&(d=u.cloneAndReplaceKey(d,a+(d.key&&(!t||t.key!==d.key)?o(d.key)+'/':'')+n)),r.push(d))}function s(e,t,n,r,a){var s='';null!=n&&(s=o(n)+'/');var d=i.getPooled(t,s,r,a);m(e,p,d),i.release(d)}function d(){return null}var l=n(36),u=n(7),c=n(5),m=n(37),f=l.twoArgumentPooler,y=l.fourArgumentPooler,h=/\/+/g;r.prototype.destructor=function(){this.func=null,this.context=null,this.count=0},l.addPoolingTo(r,f),i.prototype.destructor=function(){this.result=null,this.keyPrefix=null,this.func=null,this.context=null,this.count=0},l.addPoolingTo(i,y);e.exports={forEach:function(e,t,n){if(null==e)return e;var o=r.getPooled(t,n);m(e,a,o),r.release(o)},map:function(e,t,n){if(null==e)return e;var o=[];return s(e,o,null,t,n),o},mapIntoWithKeyPrefixInternal:s,count:function(e){return m(e,d,null)},toArray:function(e){var t=[];return s(e,t,null,c.thatReturnsArgument),t}}},function(e,t,n){'use strict';var o=n(10),r=n(0),a=function(e){var t=this;if(t.instancePool.length){var n=t.instancePool.pop();return t.call(n,e),n}return new t(e)},i=function(e){var t=this;e instanceof t?void 0:o('25'),e.destructor(),t.instancePool.length<t.poolSize&&t.instancePool.push(e)};e.exports={addPoolingTo:function(e,t){var n=e;return n.instancePool=[],n.getPooled=t||a,n.poolSize||(n.poolSize=10),n.release=i,n},oneArgumentPooler:a,twoArgumentPooler:function(e,t){var n=this;if(n.instancePool.length){var o=n.instancePool.pop();return n.call(o,e,t),o}return new n(e,t)},threeArgumentPooler:function(e,t,n){var o=this;if(o.instancePool.length){var r=o.instancePool.pop();return o.call(r,e,t,n),r}return new o(e,t,n)},fourArgumentPooler:function(e,t,n,o){var r=this;if(r.instancePool.length){var a=r.instancePool.pop();return r.call(a,e,t,n,o),a}return new r(e,t,n,o)}}},function(e,t,n){'use strict';function o(e,t){return e&&'object'==typeof e&&null!=e.key?l.escape(e.key):t.toString(36)}function r(e,t,n,d){var u=typeof e;if(('undefined'==u||'boolean'==u)&&(e=null),null===e||'string'==u||'number'==u||'object'==u&&e.$$typeof===p)return n(d,e,''===t?c+o(e,0):t),1;var f,y,h=0,g=''===t?c:t+m;if(Array.isArray(e))for(var b=0;b<e.length;b++)f=e[b],y=g+o(f,b),h+=r(f,y,n,d);else{var i=s(e);if(i){var E,x=i.call(e);if(i!==e.entries)for(var P=0;!(E=x.next()).done;)f=E.value,y=g+o(f,P++),h+=r(f,y,n,d);else for(var _;!(E=x.next()).done;)_=E.value,_&&(f=_[1],y=g+l.escape(_[0])+m+o(f,0),h+=r(f,y,n,d))}else if('object'==u){var N='',I=e+'';a('31','[object Object]'===I?'object with keys {'+Object.keys(e).join(', ')+'}':I,N)}}return h}var a=n(10),i=n(8),p=n(23),s=n(38),d=n(0),l=n(39),u=n(1),c='.',m=':';e.exports=function(e,t,n){return null==e?0:r(e,'',t,n)}},function(e){'use strict';var t='function'==typeof Symbol&&Symbol.iterator;e.exports=function(e){var n=e&&(t&&e[t]||e['@@iterator']);if('function'==typeof n)return n}},function(e){'use strict';e.exports={escape:function(e){var t=/[=:]/g,n={"=":'=0',":":'=2'},o=(''+e).replace(t,function(e){return n[e]});return'$'+o},unescape:function(e){var t=/(=0|=2)/g,n={"=0":'=',"=2":':'},o='.'===e[0]&&'$'===e[1]?e.substring(2):e.substring(1);return(''+o).replace(t,function(e){return n[e]})}}},function(e,t,n){'use strict';var o=n(7),r=o.createFactory;var a={a:r('a'),abbr:r('abbr'),address:r('address'),area:r('area'),article:r('article'),aside:r('aside'),audio:r('audio'),b:r('b'),base:r('base'),bdi:r('bdi'),bdo:r('bdo'),big:r('big'),blockquote:r('blockquote'),body:r('body'),br:r('br'),button:r('button'),canvas:r('canvas'),caption:r('caption'),cite:r('cite'),code:r('code'),col:r('col'),colgroup:r('colgroup'),data:r('data'),datalist:r('datalist'),dd:r('dd'),del:r('del'),details:r('details'),dfn:r('dfn'),dialog:r('dialog'),div:r('div'),dl:r('dl'),dt:r('dt'),em:r('em'),embed:r('embed'),fieldset:r('fieldset'),figcaption:r('figcaption'),figure:r('figure'),footer:r('footer'),form:r('form'),h1:r('h1'),h2:r('h2'),h3:r('h3'),h4:r('h4'),h5:r('h5'),h6:r('h6'),head:r('head'),header:r('header'),hgroup:r('hgroup'),hr:r('hr'),html:r('html'),i:r('i'),iframe:r('iframe'),img:r('img'),input:r('input'),ins:r('ins'),kbd:r('kbd'),keygen:r('keygen'),label:r('label'),legend:r('legend'),li:r('li'),link:r('link'),main:r('main'),map:r('map'),mark:r('mark'),menu:r('menu'),menuitem:r('menuitem'),meta:r('meta'),meter:r('meter'),nav:r('nav'),noscript:r('noscript'),object:r('object'),ol:r('ol'),optgroup:r('optgroup'),option:r('option'),output:r('output'),p:r('p'),param:r('param'),picture:r('picture'),pre:r('pre'),progress:r('progress'),q:r('q'),rp:r('rp'),rt:r('rt'),ruby:r('ruby'),s:r('s'),samp:r('samp'),script:r('script'),section:r('section'),select:r('select'),small:r('small'),source:r('source'),span:r('span'),strong:r('strong'),style:r('style'),sub:r('sub'),summary:r('summary'),sup:r('sup'),table:r('table'),tbody:r('tbody'),td:r('td'),textarea:r('textarea'),tfoot:r('tfoot'),th:r('th'),thead:r('thead'),time:r('time'),title:r('title'),tr:r('tr'),track:r('track'),u:r('u'),ul:r('ul'),var:r('var'),video:r('video'),wbr:r('wbr'),circle:r('circle'),clipPath:r('clipPath'),defs:r('defs'),ellipse:r('ellipse'),g:r('g'),image:r('image'),line:r('line'),linearGradient:r('linearGradient'),mask:r('mask'),path:r('path'),pattern:r('pattern'),polygon:r('polygon'),polyline:r('polyline'),radialGradient:r('radialGradient'),rect:r('rect'),stop:r('stop'),svg:r('svg'),text:r('text'),tspan:r('tspan')};e.exports=a},function(e,t,n){'use strict';var o=n(7),r=o.isValidElement,a=n(28);e.exports=a(r)},function(e,t,n){'use strict';var o=n(5),r=n(0),a=n(1),p=n(19),i=n(43);e.exports=function(e,t){function n(e){var t=e&&(b&&e[b]||e[E]);if('function'==typeof t)return t}function s(e,t){return e===t?0!==e||1/e==1/t:e!==e&&t!==t}function d(e){this.message=e,this.stack=''}function l(e){function n(n,o,a,i,s,l,u){if(i=i||x,l=l||a,u!==p)if(t)r(!1,'Calling PropTypes validators directly is not supported by the `prop-types` package. Use `PropTypes.checkPropTypes()` to call them. Read more at http://fb.me/use-check-prop-types');else;return null==o[a]?n?null===o[a]?new d('The '+s+' `'+l+'` is marked as required '+('in `'+i+'`, but its value is `null`.')):new d('The '+s+' `'+l+'` is marked as required in '+('`'+i+'`, but its value is `undefined`.')):null:e(o,a,i,s,l)}var o=n.bind(null,!1);return o.isRequired=n.bind(null,!0),o}function u(e){return l(function(t,n,o,r,a){var i=t[n],p=f(i);if(p!==e){var s=y(i);return new d('Invalid '+r+' `'+a+'` of type '+('`'+s+'` supplied to `'+o+'`, expected ')+('`'+e+'`.'))}return null})}function c(t){switch(typeof t){case'number':case'string':case'undefined':return!0;case'boolean':return!t;case'object':if(Array.isArray(t))return t.every(c);if(null===t||e(t))return!0;var o=n(t);if(o){var r,a=o.call(t);if(o!==t.entries){for(;!(r=a.next()).done;)if(!c(r.value))return!1;}else for(;!(r=a.next()).done;){var i=r.value;if(i&&!c(i[1]))return!1}}else return!1;return!0;default:return!1;}}function m(e,t){return'symbol'===e||'Symbol'===t['@@toStringTag']||'function'==typeof Symbol&&t instanceof Symbol}function f(e){var t=typeof e;return Array.isArray(e)?'array':e instanceof RegExp?'object':m(t,e)?'symbol':t}function y(e){if('undefined'==typeof e||null===e)return''+e;var t=f(e);if('object'===t){if(e instanceof Date)return'date';if(e instanceof RegExp)return'regexp'}return t}function h(e){var t=y(e);return'array'===t||'object'===t?'an '+t:'boolean'===t||'date'===t||'regexp'===t?'a '+t:t}function g(e){return e.constructor&&e.constructor.name?e.constructor.name:x}var b='function'==typeof Symbol&&Symbol.iterator,E='@@iterator',x='<<anonymous>>',P={array:u('array'),bool:u('boolean'),func:u('function'),number:u('number'),object:u('object'),string:u('string'),symbol:u('symbol'),any:function(){return l(o.thatReturnsNull)}(),arrayOf:function(e){return l(function(t,n,o,r,a){if('function'!=typeof e)return new d('Property `'+a+'` of component `'+o+'` has invalid PropType notation inside arrayOf.');var s=t[n];if(!Array.isArray(s)){var l=f(s);return new d('Invalid '+r+' `'+a+'` of type '+('`'+l+'` supplied to `'+o+'`, expected an array.'))}for(var u,c=0;c<s.length;c++)if(u=e(s,c,o,r,a+'['+c+']',p),u instanceof Error)return u;return null})},element:function(){return l(function(t,n,o,r,a){var i=t[n];if(!e(i)){var p=f(i);return new d('Invalid '+r+' `'+a+'` of type '+('`'+p+'` supplied to `'+o+'`, expected a single ReactElement.'))}return null})}(),instanceOf:function(e){return l(function(t,n,o,r,a){if(!(t[n]instanceof e)){var i=e.name||x,p=g(t[n]);return new d('Invalid '+r+' `'+a+'` of type '+('`'+p+'` supplied to `'+o+'`, expected ')+('instance of `'+i+'`.'))}return null})},node:function(){return l(function(e,t,n,o,r){return c(e[t])?null:new d('Invalid '+o+' `'+r+'` supplied to '+('`'+n+'`, expected a ReactNode.'))})}(),objectOf:function(e){return l(function(t,n,o,r,a){if('function'!=typeof e)return new d('Property `'+a+'` of component `'+o+'` has invalid PropType notation inside objectOf.');var i=t[n],s=f(i);if('object'!==s)return new d('Invalid '+r+' `'+a+'` of type '+('`'+s+'` supplied to `'+o+'`, expected an object.'));for(var l in i)if(i.hasOwnProperty(l)){var u=e(i,l,o,r,a+'.'+l,p);if(u instanceof Error)return u}return null})},oneOf:function(e){return Array.isArray(e)?l(function(t,n,o,r,a){for(var p=t[n],l=0;l<e.length;l++)if(s(p,e[l]))return null;var i=JSON.stringify(e);return new d('Invalid '+r+' `'+a+'` of value `'+p+'` '+('supplied to `'+o+'`, expected one of '+i+'.'))}):(void 0,o.thatReturnsNull)},oneOfType:function(e){if(!Array.isArray(e))return void 0,o.thatReturnsNull;for(var t,n=0;n<e.length;n++)if(t=e[n],'function'!=typeof t)return a(!1,'Invalid argument supplid to oneOfType. Expected an array of check functions, but received %s at index %s.',h(t),n),o.thatReturnsNull;return l(function(t,n,o,r,a){for(var s,l=0;l<e.length;l++)if(s=e[l],null==s(t,n,o,r,a,p))return null;return new d('Invalid '+r+' `'+a+'` supplied to '+('`'+o+'`.'))})},shape:function(e){return l(function(t,n,o,r,a){var i=t[n],s=f(i);if('object'!==s)return new d('Invalid '+r+' `'+a+'` of type `'+s+'` '+('supplied to `'+o+'`, expected `object`.'));for(var l in e){var u=e[l];if(u){var c=u(i,l,o,r,a+'.'+l,p);if(c)return c}}return null})}};return d.prototype=Error.prototype,P.checkPropTypes=i,P.PropTypes=P,P}},function(e){'use strict';e.exports=function(){}},function(e){'use strict';e.exports='15.6.1'},function(e,t,n){'use strict';var o=n(20),r=o.Component,a=n(7),i=a.isValidElement,p=n(21),s=n(46);e.exports=s(r,i,p)},function(e,t,n){'use strict';function o(e){return e}var r=n(3),a=n(14),i=n(0);var p,s='mixins';p={},e.exports=function(e,t,n){function p(e,t){var n=g.hasOwnProperty(t)?g[t]:null;P.hasOwnProperty(t)&&i('OVERRIDE_BASE'===n,'ReactClassInterface: You are attempting to override `%s` from your class specification. Ensure that your method names do not overlap with React methods.',t),e&&i('DEFINE_MANY'===n||'DEFINE_MANY_MERGED'===n,'ReactClassInterface: You are attempting to define `%s` on your component more than once. This conflict may be due to a mixin.',t)}function d(e,n){if(!n){return}i('function'!=typeof n,'ReactClass: You\'re attempting to use a component class or function as a mixin. Instead, just use a regular object.'),i(!t(n),'ReactClass: You\'re attempting to use a component as a mixin. Instead, just use a regular object.');var o=e.prototype,r=o.__reactAutoBindPairs;for(var a in n.hasOwnProperty(s)&&b.mixins(e,n.mixins),n)if(n.hasOwnProperty(a)&&a!=s){var d=n[a],l=o.hasOwnProperty(a);if(p(l,a),b.hasOwnProperty(a))b[a](e,d);else{var u=g.hasOwnProperty(a),f='function'==typeof d&&!u&&!l&&!1!==n.autobind;if(f)r.push(a,d),o[a]=d;else if(l){var y=g[a];i(u&&('DEFINE_MANY_MERGED'===y||'DEFINE_MANY'===y),'ReactClass: Unexpected spec policy %s for key %s when mixing in component specs.',y,a),'DEFINE_MANY_MERGED'===y?o[a]=c(o[a],d):'DEFINE_MANY'===y&&(o[a]=m(o[a],d))}else o[a]=d,!1}}}function l(e,t){if(t)for(var n in t){var o=t[n];if(t.hasOwnProperty(n)){i(!(n in b),'ReactClass: You are attempting to define a reserved property, `%s`, that shouldn\'t be on the "statics" key. Define it as an instance property instead; it will still be accessible on the constructor.',n);i(!(n in e),'ReactClass: You are attempting to define `%s` on your component more than once. This conflict may be due to a mixin.',n),e[n]=o}}}function u(e,t){for(var n in i(e&&t&&'object'==typeof e&&'object'==typeof t,'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.'),t)t.hasOwnProperty(n)&&(i(void 0===e[n],'mergeIntoWithNoDuplicateKeys(): Tried to merge two objects with the same key: `%s`. This conflict may be due to a mixin; in particular, this may be caused by two getInitialState() or getDefaultProps() methods returning objects with clashing keys.',n),e[n]=t[n]);return e}function c(e,t){return function(){var n=e.apply(this,arguments),o=t.apply(this,arguments);if(null==n)return o;if(null==o)return n;var r={};return u(r,n),u(r,o),r}}function m(e,t){return function(){e.apply(this,arguments),t.apply(this,arguments)}}function f(e,t){var n=t.bind(e);return n}function y(e){for(var t=e.__reactAutoBindPairs,n=0;n<t.length;n+=2){var o=t[n],r=t[n+1];e[o]=f(e,r)}}var h=[],g={mixins:'DEFINE_MANY',statics:'DEFINE_MANY',propTypes:'DEFINE_MANY',contextTypes:'DEFINE_MANY',childContextTypes:'DEFINE_MANY',getDefaultProps:'DEFINE_MANY_MERGED',getInitialState:'DEFINE_MANY_MERGED',getChildContext:'DEFINE_MANY_MERGED',render:'DEFINE_ONCE',componentWillMount:'DEFINE_MANY',componentDidMount:'DEFINE_MANY',componentWillReceiveProps:'DEFINE_MANY',shouldComponentUpdate:'DEFINE_ONCE',componentWillUpdate:'DEFINE_MANY',componentDidUpdate:'DEFINE_MANY',componentWillUnmount:'DEFINE_MANY',updateComponent:'OVERRIDE_BASE'},b={displayName:function(e,t){e.displayName=t},mixins:function(e,t){if(t)for(var n=0;n<t.length;n++)d(e,t[n])},childContextTypes:function(e,t){!1,e.childContextTypes=r({},e.childContextTypes,t)},contextTypes:function(e,t){!1,e.contextTypes=r({},e.contextTypes,t)},getDefaultProps:function(e,t){e.getDefaultProps=e.getDefaultProps?c(e.getDefaultProps,t):t},propTypes:function(e,t){!1,e.propTypes=r({},e.propTypes,t)},statics:function(e,t){l(e,t)},autobind:function(){}},E={componentDidMount:function(){this.__isMounted=!0}},x={componentWillUnmount:function(){this.__isMounted=!1}},P={replaceState:function(e,t){this.updater.enqueueReplaceState(this,e,t)},isMounted:function(){return!1,!!this.__isMounted}},_=function(){};return r(_.prototype,e.prototype,P),function(e){var t=o(function(e,o,r){!1,this.__reactAutoBindPairs.length&&y(this),this.props=e,this.context=o,this.refs=a,this.updater=r||n,this.state=null;var p=this.getInitialState?this.getInitialState():null;!1,i('object'==typeof p&&!Array.isArray(p),'%s.getInitialState(): must return an object or null',t.displayName||'ReactCompositeComponent'),this.state=p});for(var r in t.prototype=new _,t.prototype.constructor=t,t.prototype.__reactAutoBindPairs=[],h.forEach(d.bind(null,t)),d(t,E),d(t,e),d(t,x),t.getDefaultProps&&(t.defaultProps=t.getDefaultProps()),!1,i(t.prototype.render,'createClass(...): Class specification must implement a `render` method.'),!1,g)t.prototype[r]||(t.prototype[r]=null);return t}}},function(e,t,n){'use strict';var o=n(10),r=n(7),a=n(0);e.exports=function(e){return r.isValidElement(e)?void 0:o('143'),e}},,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,function(e,t,n){'use strict';e.exports=n(13)}]);
PK
!<��~�=�=.chrome/toolkit/res/normandy/vendor/ReactDOM.js/* eslint-disable */this.ReactDOM=function(e){function t(o){if(n[o])return n[o].exports;var a=n[o]={i:o,l:!1,exports:{}};return e[o].call(a.exports,a,a.exports,t),a.l=!0,a.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,o){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:o})},t.n=function(e){var n=e&&e.__esModule?function(){return e['default']}:function(){return e};return t.d(n,'a',n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p='',t(t.s=103)}([function(e){'use strict';var t=function(){};!1,e.exports=function(n,o,r,a,i,s,d,e){if(t(o),!n){var p;if(void 0===o)p=new Error('Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.');else{var l=[r,a,i,s,d,e],u=0;p=new Error(o.replace(/%s/g,function(){return l[u++]})),p.name='Invariant Violation'}throw p.framesToPop=1,p}}},function(e,t,n){'use strict';var o=n(5);e.exports=o},function(e){'use strict';e.exports=function(e){for(var t=arguments.length-1,n='Minified React error #'+e+'; visit http://facebook.github.io/react/docs/error-decoder.html?invariant='+e,o=0;o<t;o++)n+='&args[]='+encodeURIComponent(arguments[o+1]);n+=' for the full message or use the non-minified dev environment for full errors and additional helpful warnings.';var a=new Error(n);throw a.name='Invariant Violation',a.framesToPop=1,a}},function(e){'use strict';/*
object-assign
(c) Sindre Sorhus
@license MIT
*/function t(e){if(null===e||e===void 0)throw new TypeError('Object.assign cannot be called with null or undefined');return Object(e)}var n=Object.getOwnPropertySymbols,o=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable;e.exports=function(){try{if(!Object.assign)return!1;var e=new String('abc');if(e[5]='de','5'===Object.getOwnPropertyNames(e)[0])return!1;for(var t={},n=0;10>n;n++)t['_'+String.fromCharCode(n)]=n;var o=Object.getOwnPropertyNames(t).map(function(e){return t[e]});if('0123456789'!==o.join(''))return!1;var a={};return['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t'].forEach(function(e){a[e]=e}),'abcdefghijklmnopqrst'===Object.keys(Object.assign({},a)).join('')}catch(e){return!1}}()?Object.assign:function(e){for(var r=t(e),d=1,s,p;d<arguments.length;d++){for(var l in s=Object(arguments[d]),s)o.call(s,l)&&(r[l]=s[l]);if(n){p=n(s);for(var u=0;u<p.length;u++)a.call(s,p[u])&&(r[p[u]]=s[p[u]])}}return r}},function(e,t,n){'use strict';function o(e,t){return 1===e.nodeType&&e.getAttribute(c)===t+''||8===e.nodeType&&e.nodeValue===' react-text: '+t+' '||8===e.nodeType&&e.nodeValue===' react-empty: '+t+' '}function a(e){for(var t;t=e._renderedComponent;)e=t;return e}function r(e,t){var n=a(e);n._hostNode=t,t[h]=n}function i(e,t){if(!(e._flags&m.hasCachedChildNodes)){var n=e._renderedChildren,i=t.firstChild;outer:for(var d in n)if(n.hasOwnProperty(d)){var p=n[d],l=a(p)._domID;if(0!==l){for(;null!==i;i=i.nextSibling)if(o(i,l)){r(p,i);continue outer}s('32',l)}}e._flags|=m.hasCachedChildNodes}}function d(e){if(e[h])return e[h];for(var t=[];!e[h];)if(t.push(e),e.parentNode)e=e.parentNode;else return null;for(var n,o;e&&(o=e[h]);e=t.pop())n=o,t.length&&i(o,e);return n}var s=n(2),p=n(16),l=n(66),u=n(0),c=p.ID_ATTRIBUTE_NAME,m=l,h='__reactInternalInstance$'+Math.random().toString(36).slice(2);e.exports={getClosestInstanceFromNode:d,getInstanceFromNode:function(e){var t=d(e);return null!=t&&t._hostNode===e?t:null},getNodeFromInstance:function(e){if(void 0===e._hostNode?s('33'):void 0,e._hostNode)return e._hostNode;for(var t=[];!e._hostNode;)t.push(e),e._hostParent?void 0:s('34'),e=e._hostParent;for(;t.length;e=t.pop())i(e,e._hostNode);return e._hostNode},precacheChildNodes:i,precacheNode:r,uncacheNode:function(e){var t=e._hostNode;t&&(delete t[h],e._hostNode=null)}}},function(e){'use strict';function t(e){return function(){return e}}var n=function(){};n.thatReturns=t,n.thatReturnsFalse=t(!1),n.thatReturnsTrue=t(!0),n.thatReturnsNull=t(null),n.thatReturnsThis=function(){return this},n.thatReturnsArgument=function(e){return e},e.exports=n},function(e){'use strict';var t=!!('undefined'!=typeof window&&window.document&&window.document.createElement),n={canUseDOM:t,canUseWorkers:'undefined'!=typeof Worker,canUseEventListeners:t&&!!(window.addEventListener||window.attachEvent),canUseViewport:t&&!!window.screen,isInWorker:!t};e.exports=n},function(e,t,n){'use strict';function o(e){return e.ref!==void 0}function a(e){return e.key!==void 0}var r=n(3),d=n(8),i=n(1),s=n(22),p=Object.prototype.hasOwnProperty,l=n(23),u={key:!0,ref:!0,__self:!0,__source:!0},c=function(e,t,n,o,a,r,i){return!1,{$$typeof:l,type:e,key:t,ref:n,props:i,_owner:r}},m,h;c.createElement=function(e,t,n){var r={},s=null,l=null,m=null,h=null,g;if(null!=t)for(g in o(t)&&(l=t.ref),a(t)&&(s=''+t.key),m=void 0===t.__self?null:t.__self,h=void 0===t.__source?null:t.__source,t)p.call(t,g)&&!u.hasOwnProperty(g)&&(r[g]=t[g]);var f=arguments.length-2;if(1==f)r.children=n;else if(1<f){for(var y=Array(f),_=0;_<f;_++)y[_]=arguments[_+2];!1,r.children=y}if(e&&e.defaultProps){var i=e.defaultProps;for(g in i)void 0===r[g]&&(r[g]=i[g])}return c(e,s,l,m,h,d.current,r)},c.createFactory=function(e){var t=c.createElement.bind(null,e);return t.type=e,t},c.cloneAndReplaceKey=function(e,t){var n=c(e.type,t,e.ref,e._self,e._source,e._owner,e.props);return n},c.cloneElement=function(e,t,n){var s=r({},e.props),l=e.key,m=e.ref,h=e._self,g=e._source,f=e._owner,y;if(null!=t){o(t)&&(m=t.ref,f=d.current),a(t)&&(l=''+t.key);var _;for(y in e.type&&e.type.defaultProps&&(_=e.type.defaultProps),t)p.call(t,y)&&!u.hasOwnProperty(y)&&(s[y]=void 0===t[y]&&void 0!==_?_[y]:t[y])}var C=arguments.length-2;if(1==C)s.children=n;else if(1<C){for(var b=Array(C),E=0;E<C;E++)b[E]=arguments[E+2];s.children=b}return c(e.type,l,m,h,g,f,s)},c.isValidElement=function(e){return'object'==typeof e&&null!==e&&e.$$typeof===l},e.exports=c},function(e){'use strict';e.exports={current:null}},function(e){'use strict';e.exports={debugTool:null}},function(e){'use strict';e.exports=function(e){for(var t=arguments.length-1,n='Minified React error #'+e+'; visit http://facebook.github.io/react/docs/error-decoder.html?invariant='+e,o=0;o<t;o++)n+='&args[]='+encodeURIComponent(arguments[o+1]);n+=' for the full message or use the non-minified dev environment for full errors and additional helpful warnings.';var a=new Error(n);throw a.name='Invariant Violation',a.framesToPop=1,a}},function(e,t,n){'use strict';function o(){x.ReactReconcileTransaction&&E?void 0:s('123')}function a(){this.reinitializeTransaction(),this.dirtyComponentsLength=null,this.callbackQueue=l.getPooled(),this.reconcileTransaction=x.ReactReconcileTransaction.getPooled(!0)}function r(e,t){return e._mountOrder-t._mountOrder}function i(e){var t=e.dirtyComponentsLength;t===f.length?void 0:s('124',t,f.length),f.sort(r),y++;for(var n=0;n<t;n++){var o=f[n],a=o._pendingCallbacks;o._pendingCallbacks=null;var i;if(c.logTopLevelRenders){var d=o;o._currentElement.type.isReactTopLevelWrapper&&(d=o._renderedComponent),i='React update: '+d.getName(),console.time(i)}if(m.performUpdateIfNecessary(o,e.reconcileTransaction,y),i&&console.timeEnd(i),a)for(var p=0;p<a.length;p++)e.callbackQueue.enqueue(a[p],o.getPublicInstance())}}function d(e){return o(),E.isBatchingUpdates?void(f.push(e),null==e._updateBatchNumber&&(e._updateBatchNumber=y+1)):void E.batchedUpdates(d,e)}var s=n(2),p=n(3),l=n(70),u=n(15),c=n(71),m=n(17),h=n(29),g=n(0),f=[],y=0,_=l.getPooled(),C=!1,E=null,b=[{initialize:function(){this.dirtyComponentsLength=f.length},close:function(){this.dirtyComponentsLength===f.length?f.length=0:(f.splice(0,this.dirtyComponentsLength),v())}},{initialize:function(){this.callbackQueue.reset()},close:function(){this.callbackQueue.notifyAll()}}];p(a.prototype,h,{getTransactionWrappers:function(){return b},destructor:function(){this.dirtyComponentsLength=null,l.release(this.callbackQueue),this.callbackQueue=null,x.ReactReconcileTransaction.release(this.reconcileTransaction),this.reconcileTransaction=null},perform:function(e,t,n){return h.perform.call(this,this.reconcileTransaction.perform,this.reconcileTransaction,e,t,n)}}),u.addPoolingTo(a);var v=function(){for(;f.length||C;){if(f.length){var e=a.getPooled();e.perform(i,null,e),a.release(e)}if(C){C=!1;var t=_;_=l.getPooled(),t.notifyAll(),l.release(t)}}},x={ReactReconcileTransaction:null,batchedUpdates:function(t,n,a,r,i,d){return o(),E.batchedUpdates(t,n,a,r,i,d)},enqueueUpdate:d,flushBatchedUpdates:v,injection:{injectReconcileTransaction:function(e){e?void 0:s('126'),x.ReactReconcileTransaction=e},injectBatchingStrategy:function(e){e?void 0:s('127'),'function'==typeof e.batchedUpdates?void 0:s('128'),'boolean'==typeof e.isBatchingUpdates?void 0:s('129'),E=e}},asap:function(e,t){E.isBatchingUpdates?void 0:s('125'),_.enqueue(e,t),C=!0}};e.exports=x},function(e,t,n){'use strict';function o(e,t,n,o){!1,this.dispatchConfig=e,this._targetInst=t,this.nativeEvent=n;var a=this.constructor.Interface;for(var r in a)if(a.hasOwnProperty(r)){var d=a[r];d?this[r]=d(n):'target'==r?this.target=o:this[r]=n[r]}var s=null==n.defaultPrevented?!1===n.returnValue:n.defaultPrevented;return this.isDefaultPrevented=s?i.thatReturnsTrue:i.thatReturnsFalse,this.isPropagationStopped=i.thatReturnsFalse,this}var a=n(3),r=n(15),i=n(5),d=n(1),s='function'==typeof Proxy,p=['dispatchConfig','_targetInst','nativeEvent','isDefaultPrevented','isPropagationStopped','_dispatchListeners','_dispatchInstances'],l={type:null,target:null,currentTarget:i.thatReturnsNull,eventPhase:null,bubbles:null,cancelable:null,timeStamp:function(e){return e.timeStamp||Date.now()},defaultPrevented:null,isTrusted:null};a(o.prototype,{preventDefault:function(){this.defaultPrevented=!0;var e=this.nativeEvent;e&&(e.preventDefault?e.preventDefault():'unknown'!=typeof e.returnValue&&(e.returnValue=!1),this.isDefaultPrevented=i.thatReturnsTrue)},stopPropagation:function(){var e=this.nativeEvent;e&&(e.stopPropagation?e.stopPropagation():'unknown'!=typeof e.cancelBubble&&(e.cancelBubble=!0),this.isPropagationStopped=i.thatReturnsTrue)},persist:function(){this.isPersistent=i.thatReturnsTrue},isPersistent:i.thatReturnsFalse,destructor:function(){var e=this.constructor.Interface;for(var t in e)this[t]=null;for(var n=0;n<p.length;n++)this[p[n]]=null}}),o.Interface=l,!1,o.augmentClass=function(e,t){var n=this,o=function(){};o.prototype=n.prototype;var i=new o;a(i,e.prototype),e.prototype=i,e.prototype.constructor=e,e.Interface=a({},n.Interface,t),e.augmentClass=n.augmentClass,r.addPoolingTo(e,r.fourArgumentPooler)},r.addPoolingTo(o,r.fourArgumentPooler),e.exports=o},function(e,t,n){'use strict';var o=n(3),a=n(20),r=n(35),i=n(40),d=n(7),s=n(41),p=n(44),l=n(45),u=n(47),c=d.createElement,m=d.createFactory,h=d.cloneElement;var g={Children:{map:r.map,forEach:r.forEach,count:r.count,toArray:r.toArray,only:u},Component:a.Component,PureComponent:a.PureComponent,createElement:c,cloneElement:h,isValidElement:d.isValidElement,PropTypes:s,createClass:l,createFactory:m,createMixin:function(e){return e},DOM:i,version:p,__spread:o};e.exports=g},function(e){'use strict';!1,e.exports={}},function(e,t,n){'use strict';var o=n(2),a=n(0),r=function(e){var t=this;if(t.instancePool.length){var n=t.instancePool.pop();return t.call(n,e),n}return new t(e)},i=function(e){var t=this;e instanceof t?void 0:o('25'),e.destructor(),t.instancePool.length<t.poolSize&&t.instancePool.push(e)};e.exports={addPoolingTo:function(e,t){var n=e;return n.instancePool=[],n.getPooled=t||r,n.poolSize||(n.poolSize=10),n.release=i,n},oneArgumentPooler:r,twoArgumentPooler:function(e,t){var n=this;if(n.instancePool.length){var o=n.instancePool.pop();return n.call(o,e,t),o}return new n(e,t)},threeArgumentPooler:function(e,t,n){var o=this;if(o.instancePool.length){var a=o.instancePool.pop();return o.call(a,e,t,n),a}return new o(e,t,n)},fourArgumentPooler:function(e,t,n,o){var a=this;if(a.instancePool.length){var r=a.instancePool.pop();return a.call(r,e,t,n,o),r}return new a(e,t,n,o)}}},function(e,t,n){'use strict';function o(e,t){return(e&t)===t}var a=n(2),r=n(0),i={MUST_USE_PROPERTY:1,HAS_BOOLEAN_VALUE:4,HAS_NUMERIC_VALUE:8,HAS_POSITIVE_NUMERIC_VALUE:24,HAS_OVERLOADED_BOOLEAN_VALUE:32,injectDOMPropertyConfig:function(e){var t=i,n=e.Properties||{},r=e.DOMAttributeNamespaces||{},d=e.DOMAttributeNames||{},p=e.DOMPropertyNames||{},l=e.DOMMutationMethods||{};for(var u in e.isCustomAttribute&&s._isCustomAttributeFunctions.push(e.isCustomAttribute),n){s.properties.hasOwnProperty(u)?a('48',u):void 0;var c=u.toLowerCase(),m=n[u],h={attributeName:c,attributeNamespace:null,propertyName:u,mutationMethod:null,mustUseProperty:o(m,t.MUST_USE_PROPERTY),hasBooleanValue:o(m,t.HAS_BOOLEAN_VALUE),hasNumericValue:o(m,t.HAS_NUMERIC_VALUE),hasPositiveNumericValue:o(m,t.HAS_POSITIVE_NUMERIC_VALUE),hasOverloadedBooleanValue:o(m,t.HAS_OVERLOADED_BOOLEAN_VALUE)};if(1>=h.hasBooleanValue+h.hasNumericValue+h.hasOverloadedBooleanValue?void 0:a('50',u),!1,d.hasOwnProperty(u)){var g=d[u];h.attributeName=g,!1}r.hasOwnProperty(u)&&(h.attributeNamespace=r[u]),p.hasOwnProperty(u)&&(h.propertyName=p[u]),l.hasOwnProperty(u)&&(h.mutationMethod=l[u]),s.properties[u]=h}}},d=':A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD',s={ID_ATTRIBUTE_NAME:'data-reactid',ROOT_ATTRIBUTE_NAME:'data-reactroot',ATTRIBUTE_NAME_START_CHAR:d,ATTRIBUTE_NAME_CHAR:d+'\\-.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040',properties:{},getPossibleStandardName:null,_isCustomAttributeFunctions:[],isCustomAttribute:function(e){for(var t=0,n;t<s._isCustomAttributeFunctions.length;t++)if(n=s._isCustomAttributeFunctions[t],n(e))return!0;return!1},injection:i};e.exports=s},function(e,t,n){'use strict';function o(){a.attachRefs(this,this._currentElement)}var a=n(112),r=n(9),i=n(1);e.exports={mountComponent:function(e,t,n,a,r,i){var d=e.mountComponent(t,n,a,r,i);return e._currentElement&&null!=e._currentElement.ref&&t.getReactMountReady().enqueue(o,e),!1,d},getHostNode:function(e){return e.getHostNode()},unmountComponent:function(e,t){!1,a.detachRefs(e,e._currentElement),e.unmountComponent(t),!1},receiveComponent:function(e,t,n,r){var i=e._currentElement;if(t!==i||r!==e._context){var d=a.shouldUpdateRefs(i,t);d&&a.detachRefs(e,i),e.receiveComponent(t,n,r),d&&e._currentElement&&null!=e._currentElement.ref&&n.getReactMountReady().enqueue(o,e),!1}},performUpdateIfNecessary:function(e,t,n){return e._updateBatchNumber===n?void(!1,e.performUpdateIfNecessary(t),!1):void void 0}}},function(e,t,n){'use strict';function o(e){if(l){var t=e.node,n=e.children;if(n.length)for(var o=0;o<n.length;o++)u(t,n[o],null);else null==e.html?null!=e.text&&p(t,e.text):d(t,e.html)}}function a(){return this.node.nodeName}function r(e){return{node:e,children:[],html:null,text:null,toString:a}}var i=n(55),d=n(31),s=n(56),p=n(75),l='undefined'!=typeof document&&'number'==typeof document.documentMode||'undefined'!=typeof navigator&&'string'==typeof navigator.userAgent&&/\bEdge\/\d/.test(navigator.userAgent),u=s(function(e,t,n){t.node.nodeType===11||t.node.nodeType===1&&'object'===t.node.nodeName.toLowerCase()&&(null==t.node.namespaceURI||t.node.namespaceURI===i.html)?(o(t),e.insertBefore(t.node,n)):(e.insertBefore(t.node,n),o(t))});r.insertTreeBefore=u,r.replaceChildWithTree=function(e,t){e.parentNode.replaceChild(t.node,e),o(t)},r.queueChild=function(e,t){l?e.children.push(t):e.node.appendChild(t.node)},r.queueHTML=function(e,t){l?e.html=t:d(e.node,t)},r.queueText=function(e,t){l?e.text=t:p(e.node,t)},e.exports=r},function(e){'use strict';e.exports='SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED'},function(e,t,n){'use strict';function o(e,t,n){this.props=e,this.context=t,this.refs=l,this.updater=n||s}function a(e,t,n){this.props=e,this.context=t,this.refs=l,this.updater=n||s}function r(){}var i=n(10),d=n(3),s=n(21),p=n(22),l=n(14),u=n(0),c=n(34);o.prototype.isReactComponent={},o.prototype.setState=function(e,t){'object'==typeof e||'function'==typeof e||null==e?void 0:i('85'),this.updater.enqueueSetState(this,e),t&&this.updater.enqueueCallback(this,t,'setState')},o.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this),e&&this.updater.enqueueCallback(this,e,'forceUpdate')};r.prototype=o.prototype,a.prototype=new r,a.prototype.constructor=a,d(a.prototype,o.prototype),a.prototype.isPureReactComponent=!0,e.exports={Component:o,PureComponent:a}},function(e,t,n){'use strict';function o(){}var a=n(1);e.exports={isMounted:function(){return!1},enqueueCallback:function(){},enqueueForceUpdate:function(e){o(e,'forceUpdate')},enqueueReplaceState:function(e){o(e,'replaceState')},enqueueSetState:function(e){o(e,'setState')}}},function(e){'use strict';e.exports=!1},function(e){'use strict';var t='function'==typeof Symbol&&Symbol['for']&&Symbol['for']('react.element')||60103;e.exports=t},function(e,t,n){'use strict';function o(e,t,n){var o=t.dispatchConfig.phasedRegistrationNames[n];return h(e,o)}function a(e,t,n){var a=o(e,n,t);a&&(n._dispatchListeners=u(n._dispatchListeners,a),n._dispatchInstances=u(n._dispatchInstances,e))}function r(e){e&&e.dispatchConfig.phasedRegistrationNames&&l.traverseTwoPhase(e._targetInst,a,e)}function i(e){if(e&&e.dispatchConfig.phasedRegistrationNames){var t=e._targetInst,n=t?l.getParentInstance(t):null;l.traverseTwoPhase(n,a,e)}}function d(e,t,n){if(n&&n.dispatchConfig.registrationName){var o=n.dispatchConfig.registrationName,a=h(e,o);a&&(n._dispatchListeners=u(n._dispatchListeners,a),n._dispatchInstances=u(n._dispatchInstances,e))}}function s(e){e&&e.dispatchConfig.registrationName&&d(e._targetInst,null,e)}var p=n(25),l=n(49),u=n(67),c=n(68),m=n(1),h=p.getListener;e.exports={accumulateTwoPhaseDispatches:function(e){c(e,r)},accumulateTwoPhaseDispatchesSkipTarget:function(e){c(e,i)},accumulateDirectDispatches:function(e){c(e,s)},accumulateEnterLeaveDispatches:function(e,t,n,o){l.traverseEnterLeave(n,o,d,e,t)}}},function(e,t,n){'use strict';function o(e){return'button'===e||'input'===e||'select'===e||'textarea'===e}function a(e,t,n){return('onClick'===e||'onClickCapture'===e||'onDoubleClick'===e||'onDoubleClickCapture'===e||'onMouseDown'===e||'onMouseDownCapture'===e||'onMouseMove'===e||'onMouseMoveCapture'===e||'onMouseUp'===e||'onMouseUpCapture'===e)&&!!(n.disabled&&o(t))}var r=n(2),d=n(48),i=n(49),s=n(50),p=n(67),l=n(68),u=n(0),c={},m=null,h=function(e,t){e&&(i.executeDispatchesInOrder(e,t),!e.isPersistent()&&e.constructor.release(e))},g=function(t){return h(t,!0)},f=function(t){return h(t,!1)},y=function(e){return'.'+e._rootNodeID},_={injection:{injectEventPluginOrder:d.injectEventPluginOrder,injectEventPluginsByName:d.injectEventPluginsByName},putListener:function(e,t,n){'function'==typeof n?void 0:r('94',t,typeof n);var o=y(e),a=c[t]||(c[t]={});a[o]=n;var i=d.registrationNameModules[t];i&&i.didPutListener&&i.didPutListener(e,t,n)},getListener:function(e,t){var n=c[t];if(a(t,e._currentElement.type,e._currentElement.props))return null;var o=y(e);return n&&n[o]},deleteListener:function(e,t){var n=d.registrationNameModules[t];n&&n.willDeleteListener&&n.willDeleteListener(e,t);var o=c[t];if(o){var a=y(e);delete o[a]}},deleteAllListeners:function(e){var t=y(e);for(var n in c)if(c.hasOwnProperty(n)&&c[n][t]){var o=d.registrationNameModules[n];o&&o.willDeleteListener&&o.willDeleteListener(e,n),delete c[n][t]}},extractEvents:function(e,t,n,o){for(var a=d.plugins,r=0,i,s;r<a.length;r++)if(s=a[r],s){var l=s.extractEvents(e,t,n,o);l&&(i=p(i,l))}return i},enqueueEvents:function(e){e&&(m=p(m,e))},processEventQueue:function(e){var t=m;m=null,e?l(t,g):l(t,f),!m?void 0:r('95'),s.rethrowCaughtError()},__purge:function(){c={}},__getListenerBank:function(){return c}};e.exports=_},function(e,t,n){'use strict';function o(e,t,n,o){return a.call(this,e,t,n,o)}var a=n(12),r=n(51);a.augmentClass(o,{view:function(e){if(e.view)return e.view;var t=r(e);if(t.window===t)return t;var n=t.ownerDocument;return n?n.defaultView||n.parentWindow:window},detail:function(e){return e.detail||0}}),e.exports=o},function(e){'use strict';e.exports={remove:function(e){e._reactInternalInstance=void 0},get:function(e){return e._reactInternalInstance},has:function(e){return e._reactInternalInstance!==void 0},set:function(e,t){e._reactInternalInstance=t}}},function(e,t,n){'use strict';var o=n(42);e.exports=function(e){return o(e,!1)}},function(e,t,n){'use strict';var o=n(2),a=n(0),r={};e.exports={reinitializeTransaction:function(){this.transactionWrappers=this.getTransactionWrappers(),this.wrapperInitData?this.wrapperInitData.length=0:this.wrapperInitData=[],this._isInTransaction=!1},_isInTransaction:!1,getTransactionWrappers:null,isInTransaction:function(){return!!this._isInTransaction},perform:function(t,n,r,a,i,s,d,e){!this.isInTransaction()?void 0:o('27');var p,l;try{this._isInTransaction=!0,p=!0,this.initializeAll(0),l=t.call(n,r,a,i,s,d,e),p=!1}finally{try{if(p)try{this.closeAll(0)}catch(e){}else this.closeAll(0)}finally{this._isInTransaction=!1}}return l},initializeAll:function(e){for(var t=this.transactionWrappers,n=e,o;n<t.length;n++){o=t[n];try{this.wrapperInitData[n]=r,this.wrapperInitData[n]=o.initialize?o.initialize.call(this):null}finally{if(this.wrapperInitData[n]===r)try{this.initializeAll(n+1)}catch(e){}}}},closeAll:function(e){this.isInTransaction()?void 0:o('28');for(var t=this.transactionWrappers,n=e;n<t.length;n++){var a=t[n],i=this.wrapperInitData[n],d;try{d=!0,i!==r&&a.close&&a.close.call(this,i),d=!1}finally{if(d)try{this.closeAll(n+1)}catch(t){}}}this.wrapperInitData.length=0}}},function(e,t,n){'use strict';function o(e,t,n,o){return a.call(this,e,t,n,o)}var a=n(26),r=n(74),i=n(53);a.augmentClass(o,{screenX:null,screenY:null,clientX:null,clientY:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,getModifierState:i,button:function(e){var t=e.button;return'which'in e?t:2===t?2:4===t?1:0},buttons:null,relatedTarget:function(e){return e.relatedTarget||(e.fromElement===e.srcElement?e.toElement:e.fromElement)},pageX:function(e){return'pageX'in e?e.pageX:e.clientX+r.currentScrollLeft},pageY:function(e){return'pageY'in e?e.pageY:e.clientY+r.currentScrollTop}}),e.exports=o},function(e,t,n){'use strict';var o=n(6),a=n(55),r=/^[ \r\n\t\f]/,i=/<(!--|link|noscript|meta|script|style)[ \r\n\t\f\/>]/,d=n(56),s=d(function(e,t){if(e.namespaceURI===a.svg&&!('innerHTML'in e)){p=p||document.createElement('div'),p.innerHTML='<svg>'+t+'</svg>';for(var n=p.firstChild;n.firstChild;)e.appendChild(n.firstChild)}else e.innerHTML=t}),p;if(o.canUseDOM){var l=document.createElement('div');l.innerHTML=' ',''===l.innerHTML&&(s=function(e,t){if(e.parentNode&&e.parentNode.replaceChild(e,e),r.test(t)||'<'===t[0]&&i.test(t)){e.innerHTML='\uFEFF'+t;var n=e.firstChild;1===n.data.length?e.removeChild(n):n.deleteData(0,1)}else e.innerHTML=t}),l=null}e.exports=s},function(e){'use strict';function t(e){var t=''+e,o=n.exec(t);if(!o)return t;var a='',r=0,i=0,d;for(r=o.index;r<t.length;r++){switch(t.charCodeAt(r)){case 34:d='&quot;';break;case 38:d='&amp;';break;case 39:d='&#x27;';break;case 60:d='&lt;';break;case 62:d='&gt;';break;default:continue;}i!==r&&(a+=t.substring(i,r)),i=r+1,a+=d}return i===r?a:a+t.substring(i,r)}var n=/["'&<>]/;e.exports=function(e){return'boolean'==typeof e||'number'==typeof e?''+e:t(e)}},function(e,t,n){'use strict';function o(e){return Object.prototype.hasOwnProperty.call(e,h)||(e[h]=c++,l[e[h]]={}),l[e[h]]}var a=n(3),r=n(48),i=n(133),d=n(74),s=n(134),p=n(52),l={},u=!1,c=0,m={topAbort:'abort',topAnimationEnd:s('animationend')||'animationend',topAnimationIteration:s('animationiteration')||'animationiteration',topAnimationStart:s('animationstart')||'animationstart',topBlur:'blur',topCanPlay:'canplay',topCanPlayThrough:'canplaythrough',topChange:'change',topClick:'click',topCompositionEnd:'compositionend',topCompositionStart:'compositionstart',topCompositionUpdate:'compositionupdate',topContextMenu:'contextmenu',topCopy:'copy',topCut:'cut',topDoubleClick:'dblclick',topDrag:'drag',topDragEnd:'dragend',topDragEnter:'dragenter',topDragExit:'dragexit',topDragLeave:'dragleave',topDragOver:'dragover',topDragStart:'dragstart',topDrop:'drop',topDurationChange:'durationchange',topEmptied:'emptied',topEncrypted:'encrypted',topEnded:'ended',topError:'error',topFocus:'focus',topInput:'input',topKeyDown:'keydown',topKeyPress:'keypress',topKeyUp:'keyup',topLoadedData:'loadeddata',topLoadedMetadata:'loadedmetadata',topLoadStart:'loadstart',topMouseDown:'mousedown',topMouseMove:'mousemove',topMouseOut:'mouseout',topMouseOver:'mouseover',topMouseUp:'mouseup',topPaste:'paste',topPause:'pause',topPlay:'play',topPlaying:'playing',topProgress:'progress',topRateChange:'ratechange',topScroll:'scroll',topSeeked:'seeked',topSeeking:'seeking',topSelectionChange:'selectionchange',topStalled:'stalled',topSuspend:'suspend',topTextInput:'textInput',topTimeUpdate:'timeupdate',topTouchCancel:'touchcancel',topTouchEnd:'touchend',topTouchMove:'touchmove',topTouchStart:'touchstart',topTransitionEnd:s('transitionend')||'transitionend',topVolumeChange:'volumechange',topWaiting:'waiting',topWheel:'wheel'},h='_reactListenersID'+(Math.random()+'').slice(2),g=a({},i,{ReactEventListener:null,injection:{injectReactEventListener:function(e){e.setHandleTopLevel(g.handleTopLevel),g.ReactEventListener=e}},setEnabled:function(e){g.ReactEventListener&&g.ReactEventListener.setEnabled(e)},isEnabled:function(){return!!(g.ReactEventListener&&g.ReactEventListener.isEnabled())},listenTo:function(e,t){for(var n=t,a=o(n),d=r.registrationNameDependencies[e],s=0,i;s<d.length;s++)i=d[s],a.hasOwnProperty(i)&&a[i]||('topWheel'===i?p('wheel')?g.ReactEventListener.trapBubbledEvent('topWheel','wheel',n):p('mousewheel')?g.ReactEventListener.trapBubbledEvent('topWheel','mousewheel',n):g.ReactEventListener.trapBubbledEvent('topWheel','DOMMouseScroll',n):'topScroll'===i?p('scroll',!0)?g.ReactEventListener.trapCapturedEvent('topScroll','scroll',n):g.ReactEventListener.trapBubbledEvent('topScroll','scroll',g.ReactEventListener.WINDOW_HANDLE):'topFocus'===i||'topBlur'===i?(p('focus',!0)?(g.ReactEventListener.trapCapturedEvent('topFocus','focus',n),g.ReactEventListener.trapCapturedEvent('topBlur','blur',n)):p('focusin')&&(g.ReactEventListener.trapBubbledEvent('topFocus','focusin',n),g.ReactEventListener.trapBubbledEvent('topBlur','focusout',n)),a.topBlur=!0,a.topFocus=!0):m.hasOwnProperty(i)&&g.ReactEventListener.trapBubbledEvent(i,m[i],n),a[i]=!0)},trapBubbledEvent:function(e,t,n){return g.ReactEventListener.trapBubbledEvent(e,t,n)},trapCapturedEvent:function(e,t,n){return g.ReactEventListener.trapCapturedEvent(e,t,n)},supportsEventPageXY:function(){if(!document.createEvent)return!1;var e=document.createEvent('MouseEvent');return null!=e&&'pageX'in e},ensureScrollValueMonitoring:function(){if(void 0==f&&(f=g.supportsEventPageXY()),!f&&!u){var e=d.refreshScrollValues;g.ReactEventListener.monitorScrollValue(e),u=!0}}}),f;e.exports=g},function(e){'use strict';e.exports=function(){}},function(e,t,n){'use strict';function o(e){return(''+e).replace(f,'$&/')}function a(e,t){this.func=e,this.context=t,this.count=0}function r(e,t){var n=e.func,o=e.context;n.call(o,t,e.count++)}function i(e,t,n,o){this.result=e,this.keyPrefix=t,this.func=n,this.context=o,this.count=0}function d(e,t,n){var a=e.result,r=e.keyPrefix,i=e.func,d=e.context,p=i.call(d,t,e.count++);Array.isArray(p)?s(p,a,n,c.thatReturnsArgument):null!=p&&(u.isValidElement(p)&&(p=u.cloneAndReplaceKey(p,r+(p.key&&(!t||t.key!==p.key)?o(p.key)+'/':'')+n)),a.push(p))}function s(e,t,n,a,r){var s='';null!=n&&(s=o(n)+'/');var p=i.getPooled(t,s,a,r);m(e,d,p),i.release(p)}function p(){return null}var l=n(36),u=n(7),c=n(5),m=n(37),h=l.twoArgumentPooler,g=l.fourArgumentPooler,f=/\/+/g;a.prototype.destructor=function(){this.func=null,this.context=null,this.count=0},l.addPoolingTo(a,h),i.prototype.destructor=function(){this.result=null,this.keyPrefix=null,this.func=null,this.context=null,this.count=0},l.addPoolingTo(i,g);e.exports={forEach:function(e,t,n){if(null==e)return e;var o=a.getPooled(t,n);m(e,r,o),a.release(o)},map:function(e,t,n){if(null==e)return e;var o=[];return s(e,o,null,t,n),o},mapIntoWithKeyPrefixInternal:s,count:function(e){return m(e,p,null)},toArray:function(e){var t=[];return s(e,t,null,c.thatReturnsArgument),t}}},function(e,t,n){'use strict';var o=n(10),a=n(0),r=function(e){var t=this;if(t.instancePool.length){var n=t.instancePool.pop();return t.call(n,e),n}return new t(e)},i=function(e){var t=this;e instanceof t?void 0:o('25'),e.destructor(),t.instancePool.length<t.poolSize&&t.instancePool.push(e)};e.exports={addPoolingTo:function(e,t){var n=e;return n.instancePool=[],n.getPooled=t||r,n.poolSize||(n.poolSize=10),n.release=i,n},oneArgumentPooler:r,twoArgumentPooler:function(e,t){var n=this;if(n.instancePool.length){var o=n.instancePool.pop();return n.call(o,e,t),o}return new n(e,t)},threeArgumentPooler:function(e,t,n){var o=this;if(o.instancePool.length){var a=o.instancePool.pop();return o.call(a,e,t,n),a}return new o(e,t,n)},fourArgumentPooler:function(e,t,n,o){var a=this;if(a.instancePool.length){var r=a.instancePool.pop();return a.call(r,e,t,n,o),r}return new a(e,t,n,o)}}},function(e,t,n){'use strict';function o(e,t){return e&&'object'==typeof e&&null!=e.key?l.escape(e.key):t.toString(36)}function a(e,t,n,p){var u=typeof e;if(('undefined'==u||'boolean'==u)&&(e=null),null===e||'string'==u||'number'==u||'object'==u&&e.$$typeof===d)return n(p,e,''===t?c+o(e,0):t),1;var h=0,g=''===t?c:t+m,f,y;if(Array.isArray(e))for(var _=0;_<e.length;_++)f=e[_],y=g+o(f,_),h+=a(f,y,n,p);else{var i=s(e);if(i){var C=i.call(e),b;if(i!==e.entries)for(var E=0;!(b=C.next()).done;)f=b.value,y=g+o(f,E++),h+=a(f,y,n,p);else for(var v;!(b=C.next()).done;)v=b.value,v&&(f=v[1],y=g+l.escape(v[0])+m+o(f,0),h+=a(f,y,n,p))}else if('object'==u){var x='',N=e+'';r('31','[object Object]'===N?'object with keys {'+Object.keys(e).join(', ')+'}':N,x)}}return h}var r=n(10),i=n(8),d=n(23),s=n(38),p=n(0),l=n(39),u=n(1),c='.',m=':';e.exports=function(e,t,n){return null==e?0:a(e,'',t,n)}},function(e){'use strict';var t='function'==typeof Symbol&&Symbol.iterator;e.exports=function(e){var n=e&&(t&&e[t]||e['@@iterator']);if('function'==typeof n)return n}},function(e){'use strict';e.exports={escape:function(e){var t=/[=:]/g,n={"=":'=0',":":'=2'},o=(''+e).replace(t,function(e){return n[e]});return'$'+o},unescape:function(e){var t=/(=0|=2)/g,n={"=0":'=',"=2":':'},o='.'===e[0]&&'$'===e[1]?e.substring(2):e.substring(1);return(''+o).replace(t,function(e){return n[e]})}}},function(e,t,n){'use strict';var o=n(7),a=o.createFactory;var r={a:a('a'),abbr:a('abbr'),address:a('address'),area:a('area'),article:a('article'),aside:a('aside'),audio:a('audio'),b:a('b'),base:a('base'),bdi:a('bdi'),bdo:a('bdo'),big:a('big'),blockquote:a('blockquote'),body:a('body'),br:a('br'),button:a('button'),canvas:a('canvas'),caption:a('caption'),cite:a('cite'),code:a('code'),col:a('col'),colgroup:a('colgroup'),data:a('data'),datalist:a('datalist'),dd:a('dd'),del:a('del'),details:a('details'),dfn:a('dfn'),dialog:a('dialog'),div:a('div'),dl:a('dl'),dt:a('dt'),em:a('em'),embed:a('embed'),fieldset:a('fieldset'),figcaption:a('figcaption'),figure:a('figure'),footer:a('footer'),form:a('form'),h1:a('h1'),h2:a('h2'),h3:a('h3'),h4:a('h4'),h5:a('h5'),h6:a('h6'),head:a('head'),header:a('header'),hgroup:a('hgroup'),hr:a('hr'),html:a('html'),i:a('i'),iframe:a('iframe'),img:a('img'),input:a('input'),ins:a('ins'),kbd:a('kbd'),keygen:a('keygen'),label:a('label'),legend:a('legend'),li:a('li'),link:a('link'),main:a('main'),map:a('map'),mark:a('mark'),menu:a('menu'),menuitem:a('menuitem'),meta:a('meta'),meter:a('meter'),nav:a('nav'),noscript:a('noscript'),object:a('object'),ol:a('ol'),optgroup:a('optgroup'),option:a('option'),output:a('output'),p:a('p'),param:a('param'),picture:a('picture'),pre:a('pre'),progress:a('progress'),q:a('q'),rp:a('rp'),rt:a('rt'),ruby:a('ruby'),s:a('s'),samp:a('samp'),script:a('script'),section:a('section'),select:a('select'),small:a('small'),source:a('source'),span:a('span'),strong:a('strong'),style:a('style'),sub:a('sub'),summary:a('summary'),sup:a('sup'),table:a('table'),tbody:a('tbody'),td:a('td'),textarea:a('textarea'),tfoot:a('tfoot'),th:a('th'),thead:a('thead'),time:a('time'),title:a('title'),tr:a('tr'),track:a('track'),u:a('u'),ul:a('ul'),var:a('var'),video:a('video'),wbr:a('wbr'),circle:a('circle'),clipPath:a('clipPath'),defs:a('defs'),ellipse:a('ellipse'),g:a('g'),image:a('image'),line:a('line'),linearGradient:a('linearGradient'),mask:a('mask'),path:a('path'),pattern:a('pattern'),polygon:a('polygon'),polyline:a('polyline'),radialGradient:a('radialGradient'),rect:a('rect'),stop:a('stop'),svg:a('svg'),text:a('text'),tspan:a('tspan')};e.exports=r},function(e,t,n){'use strict';var o=n(7),a=o.isValidElement,r=n(28);e.exports=r(a)},function(e,t,n){'use strict';var o=n(5),a=n(0),r=n(1),d=n(19),i=n(43);e.exports=function(e,t){function n(e){var t=e&&(_&&e[_]||e[C]);if('function'==typeof t)return t}function s(e,t){return e===t?0!==e||1/e==1/t:e!==e&&t!==t}function p(e){this.message=e,this.stack=''}function l(e){function n(n,o,r,i,s,l,u){if(i=i||b,l=l||r,u!==d)if(t)a(!1,'Calling PropTypes validators directly is not supported by the `prop-types` package. Use `PropTypes.checkPropTypes()` to call them. Read more at http://fb.me/use-check-prop-types');else;return null==o[r]?n?null===o[r]?new p('The '+s+' `'+l+'` is marked as required '+('in `'+i+'`, but its value is `null`.')):new p('The '+s+' `'+l+'` is marked as required in '+('`'+i+'`, but its value is `undefined`.')):null:e(o,r,i,s,l)}var o=n.bind(null,!1);return o.isRequired=n.bind(null,!0),o}function u(e){return l(function(t,n,o,a,r){var i=t[n],d=h(i);if(d!==e){var s=g(i);return new p('Invalid '+a+' `'+r+'` of type '+('`'+s+'` supplied to `'+o+'`, expected ')+('`'+e+'`.'))}return null})}function c(t){switch(typeof t){case'number':case'string':case'undefined':return!0;case'boolean':return!t;case'object':if(Array.isArray(t))return t.every(c);if(null===t||e(t))return!0;var o=n(t);if(o){var a=o.call(t),r;if(o!==t.entries){for(;!(r=a.next()).done;)if(!c(r.value))return!1;}else for(;!(r=a.next()).done;){var i=r.value;if(i&&!c(i[1]))return!1}}else return!1;return!0;default:return!1;}}function m(e,t){return'symbol'===e||'Symbol'===t['@@toStringTag']||'function'==typeof Symbol&&t instanceof Symbol}function h(e){var t=typeof e;return Array.isArray(e)?'array':e instanceof RegExp?'object':m(t,e)?'symbol':t}function g(e){if('undefined'==typeof e||null===e)return''+e;var t=h(e);if('object'===t){if(e instanceof Date)return'date';if(e instanceof RegExp)return'regexp'}return t}function f(e){var t=g(e);return'array'===t||'object'===t?'an '+t:'boolean'===t||'date'===t||'regexp'===t?'a '+t:t}function y(e){return e.constructor&&e.constructor.name?e.constructor.name:b}var _='function'==typeof Symbol&&Symbol.iterator,C='@@iterator',b='<<anonymous>>',E={array:u('array'),bool:u('boolean'),func:u('function'),number:u('number'),object:u('object'),string:u('string'),symbol:u('symbol'),any:function(){return l(o.thatReturnsNull)}(),arrayOf:function(e){return l(function(t,n,o,a,r){if('function'!=typeof e)return new p('Property `'+r+'` of component `'+o+'` has invalid PropType notation inside arrayOf.');var s=t[n];if(!Array.isArray(s)){var l=h(s);return new p('Invalid '+a+' `'+r+'` of type '+('`'+l+'` supplied to `'+o+'`, expected an array.'))}for(var u=0,i;u<s.length;u++)if(i=e(s,u,o,a,r+'['+u+']',d),i instanceof Error)return i;return null})},element:function(){return l(function(t,n,o,a,r){var i=t[n];if(!e(i)){var d=h(i);return new p('Invalid '+a+' `'+r+'` of type '+('`'+d+'` supplied to `'+o+'`, expected a single ReactElement.'))}return null})}(),instanceOf:function(e){return l(function(t,n,o,a,r){if(!(t[n]instanceof e)){var i=e.name||b,d=y(t[n]);return new p('Invalid '+a+' `'+r+'` of type '+('`'+d+'` supplied to `'+o+'`, expected ')+('instance of `'+i+'`.'))}return null})},node:function(){return l(function(e,t,n,o,a){return c(e[t])?null:new p('Invalid '+o+' `'+a+'` supplied to '+('`'+n+'`, expected a ReactNode.'))})}(),objectOf:function(e){return l(function(t,n,o,a,r){if('function'!=typeof e)return new p('Property `'+r+'` of component `'+o+'` has invalid PropType notation inside objectOf.');var i=t[n],s=h(i);if('object'!==s)return new p('Invalid '+a+' `'+r+'` of type '+('`'+s+'` supplied to `'+o+'`, expected an object.'));for(var l in i)if(i.hasOwnProperty(l)){var u=e(i,l,o,a,r+'.'+l,d);if(u instanceof Error)return u}return null})},oneOf:function(e){return Array.isArray(e)?l(function(t,n,o,a,r){for(var d=t[n],l=0;l<e.length;l++)if(s(d,e[l]))return null;var i=JSON.stringify(e);return new p('Invalid '+a+' `'+r+'` of value `'+d+'` '+('supplied to `'+o+'`, expected one of '+i+'.'))}):(void 0,o.thatReturnsNull)},oneOfType:function(e){if(!Array.isArray(e))return void 0,o.thatReturnsNull;for(var t=0,n;t<e.length;t++)if(n=e[t],'function'!=typeof n)return r(!1,'Invalid argument supplid to oneOfType. Expected an array of check functions, but received %s at index %s.',f(n),t),o.thatReturnsNull;return l(function(t,n,o,a,r){for(var s=0,i;s<e.length;s++)if(i=e[s],null==i(t,n,o,a,r,d))return null;return new p('Invalid '+a+' `'+r+'` supplied to '+('`'+o+'`.'))})},shape:function(e){return l(function(t,n,o,a,r){var i=t[n],s=h(i);if('object'!==s)return new p('Invalid '+a+' `'+r+'` of type `'+s+'` '+('supplied to `'+o+'`, expected `object`.'));for(var l in e){var u=e[l];if(u){var c=u(i,l,o,a,r+'.'+l,d);if(c)return c}}return null})}};return p.prototype=Error.prototype,E.checkPropTypes=i,E.PropTypes=E,E}},function(e){'use strict';e.exports=function(){}},function(e){'use strict';e.exports='15.6.1'},function(e,t,n){'use strict';var o=n(20),a=o.Component,r=n(7),i=r.isValidElement,d=n(21),s=n(46);e.exports=s(a,i,d)},function(e,t,n){'use strict';function o(e){return e}var a=n(3),r=n(14),i=n(0);var d='mixins',s;s={},e.exports=function(e,t,n){function s(e,t){var n=y.hasOwnProperty(t)?y[t]:null;E.hasOwnProperty(t)&&i('OVERRIDE_BASE'===n,'ReactClassInterface: You are attempting to override `%s` from your class specification. Ensure that your method names do not overlap with React methods.',t),e&&i('DEFINE_MANY'===n||'DEFINE_MANY_MERGED'===n,'ReactClassInterface: You are attempting to define `%s` on your component more than once. This conflict may be due to a mixin.',t)}function p(e,n){if(!n){return}i('function'!=typeof n,'ReactClass: You\'re attempting to use a component class or function as a mixin. Instead, just use a regular object.'),i(!t(n),'ReactClass: You\'re attempting to use a component as a mixin. Instead, just use a regular object.');var o=e.prototype,a=o.__reactAutoBindPairs;for(var r in n.hasOwnProperty(d)&&_.mixins(e,n.mixins),n)if(n.hasOwnProperty(r)&&r!=d){var p=n[r],l=o.hasOwnProperty(r);if(s(l,r),_.hasOwnProperty(r))_[r](e,p);else{var u=y.hasOwnProperty(r),h='function'==typeof p&&!u&&!l&&!1!==n.autobind;if(h)a.push(r,p),o[r]=p;else if(l){var g=y[r];i(u&&('DEFINE_MANY_MERGED'===g||'DEFINE_MANY'===g),'ReactClass: Unexpected spec policy %s for key %s when mixing in component specs.',g,r),'DEFINE_MANY_MERGED'===g?o[r]=c(o[r],p):'DEFINE_MANY'===g&&(o[r]=m(o[r],p))}else o[r]=p,!1}}}function l(e,t){if(t)for(var n in t){var o=t[n];if(t.hasOwnProperty(n)){i(!(n in _),'ReactClass: You are attempting to define a reserved property, `%s`, that shouldn\'t be on the "statics" key. Define it as an instance property instead; it will still be accessible on the constructor.',n);i(!(n in e),'ReactClass: You are attempting to define `%s` on your component more than once. This conflict may be due to a mixin.',n),e[n]=o}}}function u(e,t){for(var n in i(e&&t&&'object'==typeof e&&'object'==typeof t,'mergeIntoWithNoDuplicateKeys(): Cannot merge non-objects.'),t)t.hasOwnProperty(n)&&(i(void 0===e[n],'mergeIntoWithNoDuplicateKeys(): Tried to merge two objects with the same key: `%s`. This conflict may be due to a mixin; in particular, this may be caused by two getInitialState() or getDefaultProps() methods returning objects with clashing keys.',n),e[n]=t[n]);return e}function c(e,t){return function(){var n=e.apply(this,arguments),o=t.apply(this,arguments);if(null==n)return o;if(null==o)return n;var a={};return u(a,n),u(a,o),a}}function m(e,t){return function(){e.apply(this,arguments),t.apply(this,arguments)}}function h(e,t){var n=t.bind(e);return n}function g(e){for(var t=e.__reactAutoBindPairs,n=0;n<t.length;n+=2){var o=t[n],a=t[n+1];e[o]=h(e,a)}}var f=[],y={mixins:'DEFINE_MANY',statics:'DEFINE_MANY',propTypes:'DEFINE_MANY',contextTypes:'DEFINE_MANY',childContextTypes:'DEFINE_MANY',getDefaultProps:'DEFINE_MANY_MERGED',getInitialState:'DEFINE_MANY_MERGED',getChildContext:'DEFINE_MANY_MERGED',render:'DEFINE_ONCE',componentWillMount:'DEFINE_MANY',componentDidMount:'DEFINE_MANY',componentWillReceiveProps:'DEFINE_MANY',shouldComponentUpdate:'DEFINE_ONCE',componentWillUpdate:'DEFINE_MANY',componentDidUpdate:'DEFINE_MANY',componentWillUnmount:'DEFINE_MANY',updateComponent:'OVERRIDE_BASE'},_={displayName:function(e,t){e.displayName=t},mixins:function(e,t){if(t)for(var n=0;n<t.length;n++)p(e,t[n])},childContextTypes:function(e,t){!1,e.childContextTypes=a({},e.childContextTypes,t)},contextTypes:function(e,t){!1,e.contextTypes=a({},e.contextTypes,t)},getDefaultProps:function(e,t){e.getDefaultProps=e.getDefaultProps?c(e.getDefaultProps,t):t},propTypes:function(e,t){!1,e.propTypes=a({},e.propTypes,t)},statics:function(e,t){l(e,t)},autobind:function(){}},C={componentDidMount:function(){this.__isMounted=!0}},b={componentWillUnmount:function(){this.__isMounted=!1}},E={replaceState:function(e,t){this.updater.enqueueReplaceState(this,e,t)},isMounted:function(){return!1,!!this.__isMounted}},v=function(){};return a(v.prototype,e.prototype,E),function(e){var t=o(function(e,o,a){!1,this.__reactAutoBindPairs.length&&g(this),this.props=e,this.context=o,this.refs=r,this.updater=a||n,this.state=null;var d=this.getInitialState?this.getInitialState():null;!1,i('object'==typeof d&&!Array.isArray(d),'%s.getInitialState(): must return an object or null',t.displayName||'ReactCompositeComponent'),this.state=d});for(var a in t.prototype=new v,t.prototype.constructor=t,t.prototype.__reactAutoBindPairs=[],f.forEach(p.bind(null,t)),p(t,C),p(t,e),p(t,b),t.getDefaultProps&&(t.defaultProps=t.getDefaultProps()),!1,i(t.prototype.render,'createClass(...): Class specification must implement a `render` method.'),!1,y)t.prototype[a]||(t.prototype[a]=null);return t}}},function(e,t,n){'use strict';var o=n(10),a=n(7),r=n(0);e.exports=function(e){return a.isValidElement(e)?void 0:o('143'),e}},function(e,t,n){'use strict';function o(){if(s)for(var e in p){var t=p[e],n=s.indexOf(e);if(-1<n?void 0:i('96',e),!l.plugins[n]){t.extractEvents?void 0:i('97',e),l.plugins[n]=t;var o=t.eventTypes;for(var r in o)a(o[r],t,r)?void 0:i('98',r,e)}}}function a(e,t,n){!l.eventNameDispatchConfigs.hasOwnProperty(n)?void 0:i('99',n),l.eventNameDispatchConfigs[n]=e;var o=e.phasedRegistrationNames;if(o){for(var a in o)if(o.hasOwnProperty(a)){var d=o[a];r(d,t,n)}return!0}return!!e.registrationName&&(r(e.registrationName,t,n),!0)}function r(e,t,n){!l.registrationNameModules[e]?void 0:i('100',e),l.registrationNameModules[e]=t,l.registrationNameDependencies[e]=t.eventTypes[n].dependencies}var i=n(2),d=n(0),s=null,p={},l={plugins:[],eventNameDispatchConfigs:{},registrationNameModules:{},registrationNameDependencies:{},possibleRegistrationNames:null,injectEventPluginOrder:function(e){!s?void 0:i('101'),s=Array.prototype.slice.call(e),o()},injectEventPluginsByName:function(e){var t=!1;for(var n in e)if(e.hasOwnProperty(n)){var a=e[n];p.hasOwnProperty(n)&&p[n]===a||(p[n]?i('102',n):void 0,p[n]=a,t=!0)}t&&o()},getPluginModuleForEvent:function(e){var t=e.dispatchConfig;if(t.registrationName)return l.registrationNameModules[t.registrationName]||null;if(void 0!==t.phasedRegistrationNames){var n=t.phasedRegistrationNames;for(var o in n)if(n.hasOwnProperty(o)){var a=l.registrationNameModules[n[o]];if(a)return a}}return null},_resetEventPlugins:function(){for(var e in s=null,p)p.hasOwnProperty(e)&&delete p[e];l.plugins.length=0;var t=l.eventNameDispatchConfigs;for(var n in t)t.hasOwnProperty(n)&&delete t[n];var o=l.registrationNameModules;for(var a in o)o.hasOwnProperty(a)&&delete o[a]}};e.exports=l},function(e,t,n){'use strict';function o(e,t,n,o){var a=e.type||'unknown-event';e.currentTarget=u.getNodeFromInstance(o),t?i.invokeGuardedCallbackWithCatch(a,n,e):i.invokeGuardedCallback(a,n,e),e.currentTarget=null}function a(e){var t=e._dispatchListeners,n=e._dispatchInstances;if(!1,Array.isArray(t)){for(var o=0;o<t.length&&!e.isPropagationStopped();o++)if(t[o](e,n[o]))return n[o];}else if(t&&t(e,n))return n;return null}var r=n(2),i=n(50),d=n(0),s=n(1),p,l;var u={isEndish:function(e){return'topMouseUp'===e||'topTouchEnd'===e||'topTouchCancel'===e},isMoveish:function(e){return'topMouseMove'===e||'topTouchMove'===e},isStartish:function(e){return'topMouseDown'===e||'topTouchStart'===e},executeDirectDispatch:function(e){var t=e._dispatchListeners,n=e._dispatchInstances;Array.isArray(t)?r('103'):void 0,e.currentTarget=t?u.getNodeFromInstance(n):null;var o=t?t(e):null;return e.currentTarget=null,e._dispatchListeners=null,e._dispatchInstances=null,o},executeDispatchesInOrder:function(e,t){var n=e._dispatchListeners,a=e._dispatchInstances;if(!1,Array.isArray(n))for(var r=0;r<n.length&&!e.isPropagationStopped();r++)o(e,t,n[r],a[r]);else n&&o(e,t,n,a);e._dispatchListeners=null,e._dispatchInstances=null},executeDispatchesInOrderStopAtTrue:function(e){var t=a(e);return e._dispatchInstances=null,e._dispatchListeners=null,t},hasDispatches:function(e){return!!e._dispatchListeners},getInstanceFromNode:function(e){return p.getInstanceFromNode(e)},getNodeFromInstance:function(e){return p.getNodeFromInstance(e)},isAncestor:function(e,t){return l.isAncestor(e,t)},getLowestCommonAncestor:function(e,t){return l.getLowestCommonAncestor(e,t)},getParentInstance:function(e){return l.getParentInstance(e)},traverseTwoPhase:function(e,t,n){return l.traverseTwoPhase(e,t,n)},traverseEnterLeave:function(e,t,n,o,a){return l.traverseEnterLeave(e,t,n,o,a)},injection:{injectComponentTree:function(e){p=e,!1},injectTreeTraversal:function(e){l=e,!1}}};e.exports=u},function(e){'use strict';function t(e,t,o){try{t(o)}catch(e){null==n&&(n=e)}}var n=null;e.exports={invokeGuardedCallback:t,invokeGuardedCallbackWithCatch:t,rethrowCaughtError:function(){if(n){var e=n;throw n=null,e}}}},function(e){'use strict';e.exports=function(e){var t=e.target||e.srcElement||window;return t.correspondingUseElement&&(t=t.correspondingUseElement),3===t.nodeType?t.parentNode:t}},function(e,t,n){'use strict';var o=n(6),a;/**
 * Checks if an event is supported in the current execution environment.
 *
 * NOTE: This will not work correctly for non-generic events such as `change`,
 * `reset`, `load`, `error`, and `select`.
 *
 * Borrows from Modernizr.
 *
 * @param {string} eventNameSuffix Event name, e.g. "click".
 * @param {?boolean} capture Check if the capture phase is supported.
 * @return {boolean} True if the event is supported.
 * @internal
 * @license Modernizr 3.0.0pre (Custom Build) | MIT
 */o.canUseDOM&&(a=document.implementation&&document.implementation.hasFeature&&!0!==document.implementation.hasFeature('','')),e.exports=function(e,t){if(!o.canUseDOM||t&&!('addEventListener'in document))return!1;var n='on'+e,r=n in document;if(!r){var i=document.createElement('div');i.setAttribute(n,'return;'),r='function'==typeof i[n]}return!r&&a&&'wheel'===e&&(r=document.implementation.hasFeature('Events.wheel','3.0')),r}},function(e){'use strict';function t(e){var t=this,o=t.nativeEvent;if(o.getModifierState)return o.getModifierState(e);var a=n[e];return!!a&&!!o[a]}var n={Alt:'altKey',Control:'ctrlKey',Meta:'metaKey',Shift:'shiftKey'};e.exports=function(){return t}},function(e,t,n){'use strict';function o(e,t){return Array.isArray(t)&&(t=t[1]),t?t.nextSibling:e.firstChild}function a(e,t,n){p.insertTreeBefore(e,t,n)}function r(e,t,n){Array.isArray(t)?d(e,t[0],t[1],n):f(e,t,n)}function i(e,t){if(Array.isArray(t)){var n=t[1];t=t[0],s(e,t,n),e.removeChild(n)}e.removeChild(t)}function d(e,t,n,o){for(var a=t,r;r=a.nextSibling,f(e,a,o),a!==n;)a=r}function s(e,t,n){for(;;){var o=t.nextSibling;if(o===n)break;else e.removeChild(o)}}var p=n(18),l=n(118),u=n(4),c=n(9),m=n(56),h=n(31),g=n(75),f=m(function(e,t,n){e.insertBefore(t,n)}),y=l.dangerouslyReplaceNodeWithMarkup;e.exports={dangerouslyReplaceNodeWithMarkup:y,replaceDelimitedText:function(e,t,n){var o=e.parentNode,a=e.nextSibling;a===t?n&&f(o,document.createTextNode(n),a):n?(g(a,n),s(o,a,t)):s(o,e,t),!1},processUpdates:function(e,t){for(var n=0,d;n<t.length;n++)switch(d=t[n],d.type){case'INSERT_MARKUP':a(e,d.content,o(e,d.afterNode)),!1;break;case'MOVE_EXISTING':r(e,d.fromNode,o(e,d.afterNode)),!1;break;case'SET_MARKUP':h(e,d.content),!1;break;case'TEXT_CONTENT':g(e,d.content),!1;break;case'REMOVE_NODE':i(e,d.fromNode),!1;}}}},function(e){'use strict';e.exports={html:'http://www.w3.org/1999/xhtml',mathml:'http://www.w3.org/1998/Math/MathML',svg:'http://www.w3.org/2000/svg'}},function(e){'use strict';e.exports=function(e){return'undefined'!=typeof MSApp&&MSApp.execUnsafeLocalFunction?function(t,n,o,a){MSApp.execUnsafeLocalFunction(function(){return e(t,n,o,a)})}:e}},function(e,t,n){'use strict';function o(e){null==e.checkedLink||null==e.valueLink?void 0:d('87')}function a(e){o(e),null==e.value&&null==e.onChange?void 0:d('88')}function r(e){o(e),null==e.checked&&null==e.onChange?void 0:d('89')}function i(e){if(e){var t=e.getName();if(t)return' Check the render method of `'+t+'`.'}return''}var d=n(2),s=n(136),p=n(28),l=n(13),u=p(l.isValidElement),c=n(0),m=n(1),h={button:!0,checkbox:!0,image:!0,hidden:!0,radio:!0,reset:!0,submit:!0},g={value:function(e,t){return!e[t]||h[e.type]||e.onChange||e.readOnly||e.disabled?null:new Error('You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.')},checked:function(e,t){return!e[t]||e.onChange||e.readOnly||e.disabled?null:new Error('You provided a `checked` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultChecked`. Otherwise, set either `onChange` or `readOnly`.')},onChange:u.func},f={};e.exports={checkPropTypes:function(e,t,n){for(var o in g){if(g.hasOwnProperty(o))var a=g[o](t,o,e,'prop',null,s);if(a instanceof Error&&!(a.message in f)){f[a.message]=!0;var r=i(n);void 0}}},getValue:function(e){return e.valueLink?(a(e),e.valueLink.value):e.value},getChecked:function(e){return e.checkedLink?(r(e),e.checkedLink.value):e.checked},executeOnChange:function(e,t){return e.valueLink?(a(e),e.valueLink.requestChange(t.target.value)):e.checkedLink?(r(e),e.checkedLink.requestChange(t.target.checked)):e.onChange?e.onChange.call(void 0,t):void 0}}},function(e,t,n){'use strict';var o=n(2),a=n(0),r=!1,i={replaceNodeWithMarkup:null,processChildrenUpdates:null,injection:{injectEnvironment:function(e){!r?void 0:o('104'),i.replaceNodeWithMarkup=e.replaceNodeWithMarkup,i.processChildrenUpdates=e.processChildrenUpdates,r=!0}}};e.exports=i},function(e){'use strict';function t(e,t){return e===t?0!==e||0!==t||1/e==1/t:e!==e&&t!==t}var n=Object.prototype.hasOwnProperty;e.exports=function(e,o){if(t(e,o))return!0;if('object'!=typeof e||null===e||'object'!=typeof o||null===o)return!1;var a=Object.keys(e),r=Object.keys(o);if(a.length!==r.length)return!1;for(var d=0;d<a.length;d++)if(!n.call(o,a[d])||!t(e[a[d]],o[a[d]]))return!1;return!0}},function(e){'use strict';e.exports=function(e,t){var n=null===e||!1===e,o=null===t||!1===t;if(n||o)return n==o;var a=typeof e,r=typeof t;return'string'==a||'number'==a?'string'==r||'number'==r:'object'==r&&e.type===t.type&&e.key===t.key}},function(e){'use strict';e.exports={escape:function(e){var t=/[=:]/g,n={"=":'=0',":":'=2'},o=(''+e).replace(t,function(e){return n[e]});return'$'+o},unescape:function(e){var t=/(=0|=2)/g,n={"=0":'=',"=2":':'},o='.'===e[0]&&'$'===e[1]?e.substring(2):e.substring(1);return(''+o).replace(t,function(e){return n[e]})}}},function(e,t,n){'use strict';function o(e){l.enqueueUpdate(e)}function a(e){var t=typeof e;if('object'!=t)return t;var n=e.constructor&&e.constructor.name||t,o=Object.keys(e);return 0<o.length&&20>o.length?n+' (keys: '+o.join(', ')+')':n}function r(e){var t=s.get(e);if(!t){return null}return!1,t}var i=n(2),d=n(8),s=n(27),p=n(9),l=n(11),u=n(0),c=n(1),m={isMounted:function(e){var t=s.get(e);return!!t&&!!t._renderedComponent},enqueueCallback:function(e,t,n){m.validateCallback(t,n);var a=r(e);return a?void(a._pendingCallbacks?a._pendingCallbacks.push(t):a._pendingCallbacks=[t],o(a)):null},enqueueCallbackInternal:function(e,t){e._pendingCallbacks?e._pendingCallbacks.push(t):e._pendingCallbacks=[t],o(e)},enqueueForceUpdate:function(e){var t=r(e,'forceUpdate');t&&(t._pendingForceUpdate=!0,o(t))},enqueueReplaceState:function(e,t,n){var a=r(e,'replaceState');a&&(a._pendingStateQueue=[t],a._pendingReplaceState=!0,n!==void 0&&null!==n&&(m.validateCallback(n,'replaceState'),a._pendingCallbacks?a._pendingCallbacks.push(n):a._pendingCallbacks=[n]),o(a))},enqueueSetState:function(e,t){var n=r(e,'setState');if(n){var a=n._pendingStateQueue||(n._pendingStateQueue=[]);a.push(t),o(n)}},enqueueElementInternal:function(e,t,n){e._pendingElement=t,e._context=n,o(e)},validateCallback:function(e,t){!e||'function'==typeof e?void 0:i('122',t,a(e))}};e.exports=m},function(e,t,n){'use strict';var o=n(3),a=n(5),r=n(1);e.exports=a},function(e){'use strict';e.exports=function(e){var t=e.keyCode,n;return'charCode'in e?(n=e.charCode,0===n&&13===t&&(n=13)):n=t,32<=n||13===n?n:0}},,function(e){'use strict';e.exports={hasCachedChildNodes:1}},function(e,t,n){'use strict';var o=n(2),a=n(0);e.exports=function(e,t){return null==t?o('30'):void 0,null==e?t:Array.isArray(e)?Array.isArray(t)?(e.push.apply(e,t),e):(e.push(t),e):Array.isArray(t)?[e].concat(t):[e,t]}},function(e){'use strict';e.exports=function(e,t,n){Array.isArray(e)?e.forEach(t,n):e&&t.call(n,e)}},function(e,t,n){'use strict';var o=n(6),a=null;e.exports=function(){return!a&&o.canUseDOM&&(a='textContent'in document.documentElement?'textContent':'innerText'),a}},function(e,t,n){'use strict';function o(e,t){if(!(e instanceof t))throw new TypeError('Cannot call a class as a function')}var a=n(2),r=n(15),i=n(0),d=function(){function e(t){o(this,e),this._callbacks=null,this._contexts=null,this._arg=t}return e.prototype.enqueue=function(e,t){this._callbacks=this._callbacks||[],this._callbacks.push(e),this._contexts=this._contexts||[],this._contexts.push(t)},e.prototype.notifyAll=function(){var e=this._callbacks,t=this._contexts,n=this._arg;if(e&&t){e.length===t.length?void 0:a('24'),this._callbacks=null,this._contexts=null;for(var o=0;o<e.length;o++)e[o].call(t[o],n);e.length=0,t.length=0}},e.prototype.checkpoint=function(){return this._callbacks?this._callbacks.length:0},e.prototype.rollback=function(e){this._callbacks&&this._contexts&&(this._callbacks.length=e,this._contexts.length=e)},e.prototype.reset=function(){this._callbacks=null,this._contexts=null},e.prototype.destructor=function(){this.reset()},e}();e.exports=r.addPoolingTo(d)},function(e){'use strict';e.exports={logTopLevelRenders:!1}},function(e,t,n){'use strict';function o(e){var t=e.type,n=e.nodeName;return n&&'input'===n.toLowerCase()&&('checkbox'===t||'radio'===t)}function a(e){return e._wrapperState.valueTracker}function r(e,t){e._wrapperState.valueTracker=t}function i(e){delete e._wrapperState.valueTracker}function d(e){var t;return e&&(t=o(e)?''+e.checked:e.value),t}var s=n(4),p={_getTrackerFromNode:function(e){return a(s.getInstanceFromNode(e))},track:function(e){if(!a(e)){var t=s.getNodeFromInstance(e),n=o(t)?'checked':'value',d=Object.getOwnPropertyDescriptor(t.constructor.prototype,n),p=''+t[n];t.hasOwnProperty(n)||'function'!=typeof d.get||'function'!=typeof d.set||(Object.defineProperty(t,n,{enumerable:d.enumerable,configurable:!0,get:function(){return d.get.call(this)},set:function(e){p=''+e,d.set.call(this,e)}}),r(e,{getValue:function(){return p},setValue:function(e){p=''+e},stopTracking:function(){i(e),delete t[n]}}))}},updateValueIfChanged:function(e){if(!e)return!1;var t=a(e);if(!t)return p.track(e),!0;var n=t.getValue(),o=d(s.getNodeFromInstance(e));return o!==n&&(t.setValue(o),!0)},stopTracking:function(e){var t=a(e);t&&t.stopTracking()}};e.exports=p},function(e){'use strict';var t={color:!0,date:!0,datetime:!0,"datetime-local":!0,email:!0,month:!0,number:!0,password:!0,range:!0,search:!0,tel:!0,text:!0,time:!0,url:!0,week:!0};e.exports=function(e){var n=e&&e.nodeName&&e.nodeName.toLowerCase();return'input'===n?!!t[e.type]:!('textarea'!==n)}},function(e){'use strict';var t={currentScrollLeft:0,currentScrollTop:0,refreshScrollValues:function(e){t.currentScrollLeft=e.x,t.currentScrollTop=e.y}};e.exports=t},function(e,t,n){'use strict';var o=n(6),a=n(32),r=n(31),i=function(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&3===n.nodeType)return void(n.nodeValue=t)}e.textContent=t};o.canUseDOM&&!('textContent'in document.documentElement)&&(i=function(e,t){return 3===e.nodeType?void(e.nodeValue=t):void r(e,a(t))}),e.exports=i},function(e){'use strict';e.exports=function(e){try{e.focus()}catch(t){}}},function(e){'use strict';function t(e,t){return e+t.charAt(0).toUpperCase()+t.substring(1)}var n={animationIterationCount:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},o=['Webkit','ms','Moz','O'];Object.keys(n).forEach(function(e){o.forEach(function(o){n[t(o,e)]=n[e]})});e.exports={isUnitlessNumber:n,shorthandPropertyExpansions:{background:{backgroundAttachment:!0,backgroundColor:!0,backgroundImage:!0,backgroundPositionX:!0,backgroundPositionY:!0,backgroundRepeat:!0},backgroundPosition:{backgroundPositionX:!0,backgroundPositionY:!0},border:{borderWidth:!0,borderStyle:!0,borderColor:!0},borderBottom:{borderBottomWidth:!0,borderBottomStyle:!0,borderBottomColor:!0},borderLeft:{borderLeftWidth:!0,borderLeftStyle:!0,borderLeftColor:!0},borderRight:{borderRightWidth:!0,borderRightStyle:!0,borderRightColor:!0},borderTop:{borderTopWidth:!0,borderTopStyle:!0,borderTopColor:!0},font:{fontStyle:!0,fontVariant:!0,fontWeight:!0,fontSize:!0,lineHeight:!0,fontFamily:!0},outline:{outlineWidth:!0,outlineStyle:!0,outlineColor:!0}}}},function(e,t,n){'use strict';function o(e){return!!c.hasOwnProperty(e)||!u.hasOwnProperty(e)&&(l.test(e)?(c[e]=!0,!0):(u[e]=!0,void 0,!1))}function a(e,t){return null==t||e.hasBooleanValue&&!t||e.hasNumericValue&&isNaN(t)||e.hasPositiveNumericValue&&1>t||e.hasOverloadedBooleanValue&&!1===t}var r=n(16),i=n(4),d=n(9),s=n(132),p=n(1),l=new RegExp('^['+r.ATTRIBUTE_NAME_START_CHAR+']['+r.ATTRIBUTE_NAME_CHAR+']*$'),u={},c={},m={createMarkupForID:function(e){return r.ID_ATTRIBUTE_NAME+'='+s(e)},setAttributeForID:function(e,t){e.setAttribute(r.ID_ATTRIBUTE_NAME,t)},createMarkupForRoot:function(){return r.ROOT_ATTRIBUTE_NAME+'=""'},setAttributeForRoot:function(e){e.setAttribute(r.ROOT_ATTRIBUTE_NAME,'')},createMarkupForProperty:function(e,t){var n=r.properties.hasOwnProperty(e)?r.properties[e]:null;if(n){if(a(n,t))return'';var o=n.attributeName;return n.hasBooleanValue||n.hasOverloadedBooleanValue&&!0===t?o+'=""':o+'='+s(t)}return r.isCustomAttribute(e)?null==t?'':e+'='+s(t):null},createMarkupForCustomAttribute:function(e,t){return o(e)&&null!=t?e+'='+s(t):''},setValueForProperty:function(e,t,n){var o=r.properties.hasOwnProperty(t)?r.properties[t]:null;if(o){var i=o.mutationMethod;if(i)i(e,n);else{if(a(o,n))return void this.deleteValueForProperty(e,t);if(o.mustUseProperty)e[o.propertyName]=n;else{var d=o.attributeName,s=o.attributeNamespace;s?e.setAttributeNS(s,d,''+n):o.hasBooleanValue||o.hasOverloadedBooleanValue&&!0===n?e.setAttribute(d,''):e.setAttribute(d,''+n)}}}else if(r.isCustomAttribute(t))return void m.setValueForAttribute(e,t,n)},setValueForAttribute:function(e,t,n){if(o(t)){null==n?e.removeAttribute(t):e.setAttribute(t,''+n)}},deleteValueForAttribute:function(e,t){e.removeAttribute(t),!1},deleteValueForProperty:function(e,t){var n=r.properties.hasOwnProperty(t)?r.properties[t]:null;if(n){var o=n.mutationMethod;if(o)o(e,void 0);else if(n.mustUseProperty){var a=n.propertyName;e[a]=!n.hasBooleanValue&&''}else e.removeAttribute(n.attributeName)}else r.isCustomAttribute(t)&&e.removeAttribute(t)}};e.exports=m},function(e,t,n){'use strict';function o(){if(this._rootNodeID&&this._wrapperState.pendingUpdate){this._wrapperState.pendingUpdate=!1;var e=this._currentElement.props,t=d.getValue(e);null!=t&&a(this,!!e.multiple,t)}}function a(e,t,n){var o=s.getNodeFromInstance(e).options,a,r;if(t){for(a={},r=0;r<n.length;r++)a[''+n[r]]=!0;for(r=0;r<o.length;r++){var i=a.hasOwnProperty(o[r].value);o[r].selected!==i&&(o[r].selected=i)}}else{for(a=''+n,r=0;r<o.length;r++)if(o[r].value===a)return void(o[r].selected=!0);o.length&&(o[0].selected=!0)}}function r(e){var t=this._currentElement.props,n=d.executeOnChange(t,e);return this._rootNodeID&&(this._wrapperState.pendingUpdate=!0),p.asap(o,this),n}var i=n(3),d=n(57),s=n(4),p=n(11),l=n(1),u=!1,c=!1,m=['value','defaultValue'];e.exports={getHostProps:function(e,t){return i({},t,{onChange:e._wrapperState.onChange,value:void 0})},mountWrapper:function(e,t){var n=d.getValue(t);e._wrapperState={pendingUpdate:!1,initialValue:null==n?t.defaultValue:n,listeners:null,onChange:r.bind(e),wasMultiple:!!t.multiple},t.value===void 0||t.defaultValue===void 0||c||(void 0,c=!0)},getSelectValueContext:function(e){return e._wrapperState.initialValue},postUpdateWrapper:function(e){var t=e._currentElement.props;e._wrapperState.initialValue=void 0;var n=e._wrapperState.wasMultiple;e._wrapperState.wasMultiple=!!t.multiple;var o=d.getValue(t);null==o?n!==!!t.multiple&&(null==t.defaultValue?a(e,!!t.multiple,t.multiple?[]:''):a(e,!!t.multiple,t.defaultValue)):(e._wrapperState.pendingUpdate=!1,a(e,!!t.multiple,o))}}},function(e){function t(){throw new Error('setTimeout has not been defined')}function n(){throw new Error('clearTimeout has not been defined')}function o(e){if(l===setTimeout)return setTimeout(e,0);if((l===t||!l)&&setTimeout)return l=setTimeout,setTimeout(e,0);try{return l(e,0)}catch(t){try{return l.call(null,e,0)}catch(t){return l.call(this,e,0)}}}function a(e){if(u===clearTimeout)return clearTimeout(e);if((u===n||!u)&&clearTimeout)return u=clearTimeout,clearTimeout(e);try{return u(e)}catch(t){try{return u.call(null,e)}catch(t){return u.call(this,e)}}}function r(){m&&g&&(m=!1,g.length?c=g.concat(c):h=-1,c.length&&d())}function d(){if(!m){var e=o(r);m=!0;for(var t=c.length;t;){for(g=c,c=[];++h<t;)g&&g[h].run();h=-1,t=c.length}g=null,m=!1,a(e)}}function s(e,t){this.fun=e,this.array=t}function i(){}var p=e.exports={},l,u;(function(){try{l='function'==typeof setTimeout?setTimeout:t}catch(n){l=t}try{u='function'==typeof clearTimeout?clearTimeout:n}catch(t){u=n}})();var c=[],m=!1,h=-1,g;p.nextTick=function(e){var t=Array(arguments.length-1);if(1<arguments.length)for(var n=1;n<arguments.length;n++)t[n-1]=arguments[n];c.push(new s(e,t)),1!==c.length||m||o(d)},s.prototype.run=function(){this.fun.apply(null,this.array)},p.title='browser',p.browser=!0,p.env={},p.argv=[],p.version='',p.versions={},p.on=i,p.addListener=i,p.once=i,p.off=i,p.removeListener=i,p.removeAllListeners=i,p.emit=i,p.prependListener=i,p.prependOnceListener=i,p.listeners=function(){return[]},p.binding=function(){throw new Error('process.binding is not supported')},p.cwd=function(){return'/'},p.chdir=function(){throw new Error('process.chdir is not supported')},p.umask=function(){return 0}},function(e,t,n){'use strict';function o(e){if(e){var t=e.getName();if(t)return' Check the render method of `'+t+'`.'}return''}function a(e){return'function'==typeof e&&'undefined'!=typeof e.prototype&&'function'==typeof e.prototype.mountComponent&&'function'==typeof e.prototype.receiveComponent}function r(e){var t;if(null===e||!1===e)t=p.create(r);else if('object'==typeof e){var n=e,d=n.type;if('function'!=typeof d&&'string'!=typeof d){var s='';!1,s+=o(n._owner),i('130',null==d?d:typeof d,s)}'string'==typeof n.type?t=l.createInternalComponent(n):a(n.type)?(t=new n.type(n),!t.getHostNode&&(t.getHostNode=t.getNativeNode)):t=new h(n)}else'string'==typeof e||'number'==typeof e?t=l.createInstanceForText(e):i('131',typeof e);return!1,t._mountIndex=0,t._mountImage=null,!1,!1,t}var i=n(2),d=n(3),s=n(141),p=n(83),l=n(84),u=n(142),c=n(0),m=n(1),h=function(e){this.construct(e)};d(h.prototype,s,{_instantiateReactComponent:r}),e.exports=r},function(e,t,n){'use strict';var o=n(2),a=n(13),r=n(0),i={HOST:0,COMPOSITE:1,EMPTY:2,getType:function(e){if(null===e||!1===e)return i.EMPTY;return a.isValidElement(e)?'function'==typeof e.type?i.COMPOSITE:i.HOST:void o('26',e)}};e.exports=i},function(e){'use strict';var t={create:function(e){return n(e)}},n;t.injection={injectEmptyComponentFactory:function(e){n=e}},e.exports=t},function(e,t,n){'use strict';var o=n(2),a=n(0),r=null,i=null;e.exports={createInternalComponent:function(e){return r?void 0:o('111',e.type),new r(e)},createInstanceForText:function(e){return new i(e)},isTextComponent:function(e){return e instanceof i},injection:{injectGenericComponentClass:function(e){r=e},injectTextComponentClass:function(e){i=e}}}},function(e,t,n){'use strict';function o(e,t){return e&&'object'==typeof e&&null!=e.key?l.escape(e.key):t.toString(36)}function a(e,t,n,p){var u=typeof e;if(('undefined'==u||'boolean'==u)&&(e=null),null===e||'string'==u||'number'==u||'object'==u&&e.$$typeof===d)return n(p,e,''===t?c+o(e,0):t),1;var h=0,g=''===t?c:t+m,f,y;if(Array.isArray(e))for(var _=0;_<e.length;_++)f=e[_],y=g+o(f,_),h+=a(f,y,n,p);else{var i=s(e);if(i){var C=i.call(e),b;if(i!==e.entries)for(var E=0;!(b=C.next()).done;)f=b.value,y=g+o(f,E++),h+=a(f,y,n,p);else for(var v;!(b=C.next()).done;)v=b.value,v&&(f=v[1],y=g+l.escape(v[0])+m+o(f,0),h+=a(f,y,n,p))}else if('object'==u){var x='',N=e+'';r('31','[object Object]'===N?'object with keys {'+Object.keys(e).join(', ')+'}':N,x)}}return h}var r=n(2),i=n(8),d=n(143),s=n(144),p=n(0),l=n(61),u=n(1),c='.',m=':';e.exports=function(e,t,n){return null==e?0:a(e,'',t,n)}},function(e,t,n){'use strict';function o(e){var t=Function.prototype.toString,n=Object.prototype.hasOwnProperty,o=RegExp('^'+t.call(n).replace(/[\\^$.*+?()[\]{}|]/g,'\\$&').replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,'$1.*?')+'$');try{var a=t.call(e);return o.test(a)}catch(e){return!1}}function a(e){var t=h(e);if(t){var n=t.childIDs;g(e),n.forEach(a)}}function r(e,t,n){return'\n    in '+(e||'Unknown')+(t?' (at '+t.fileName.replace(/^.*[\\\/]/,'')+':'+t.lineNumber+')':n?' (created by '+n+')':'')}function i(e){return null==e?'#empty':'string'==typeof e||'number'==typeof e?'#text':'string'==typeof e.type?e.type:e.type.displayName||e.type.name||'Unknown'}function d(e){var t=P.getDisplayName(e),n=P.getElement(e),o=P.getOwnerID(e),a;return o&&(a=P.getDisplayName(o)),void 0,r(t,n&&n._source,a)}var s=n(10),p=n(8),l=n(0),u=n(1),c='function'==typeof Array.from&&'function'==typeof Map&&o(Map)&&null!=Map.prototype&&'function'==typeof Map.prototype.keys&&o(Map.prototype.keys)&&'function'==typeof Set&&o(Set)&&null!=Set.prototype&&'function'==typeof Set.prototype.keys&&o(Set.prototype.keys),m,h,g,f,y,_,C;if(c){var b=new Map,E=new Set;m=function(e,t){b.set(e,t)},h=function(e){return b.get(e)},g=function(e){b['delete'](e)},f=function(){return Array.from(b.keys())},y=function(e){E.add(e)},_=function(e){E['delete'](e)},C=function(){return Array.from(E.keys())}}else{var v={},x={},N=function(e){return'.'+e},T=function(e){return parseInt(e.substr(1),10)};m=function(e,t){var n=N(e);v[n]=t},h=function(e){var t=N(e);return v[t]},g=function(e){var t=N(e);delete v[t]},f=function(){return Object.keys(v).map(T)},y=function(e){var t=N(e);x[t]=!0},_=function(e){var t=N(e);delete x[t]},C=function(){return Object.keys(x).map(T)}}var k=[],P={onSetChildren:function(e,t){var n=h(e);n?void 0:s('144'),n.childIDs=t;for(var o=0;o<t.length;o++){var a=t[o],r=h(a);r?void 0:s('140'),null!=r.childIDs||'object'!=typeof r.element||null==r.element?void 0:s('141'),r.isMounted?void 0:s('71'),null==r.parentID&&(r.parentID=e),r.parentID===e?void 0:s('142',a,r.parentID,e)}},onBeforeMountComponent:function(e,t,n){m(e,{element:t,parentID:n,text:null,childIDs:[],isMounted:!1,updateCount:0})},onBeforeUpdateComponent:function(e,t){var n=h(e);n&&n.isMounted&&(n.element=t)},onMountComponent:function(e){var t=h(e);t?void 0:s('144'),t.isMounted=!0;var n=0===t.parentID;n&&y(e)},onUpdateComponent:function(e){var t=h(e);t&&t.isMounted&&t.updateCount++},onUnmountComponent:function(e){var t=h(e);if(t){t.isMounted=!1;var n=0===t.parentID;n&&_(e)}k.push(e)},purgeUnmountedComponents:function(){if(!P._preventPurging){for(var e=0,t;e<k.length;e++)t=k[e],a(t);k.length=0}},isMounted:function(e){var t=h(e);return!!t&&t.isMounted},getCurrentStackAddendum:function(e){var t='';if(e){var n=i(e),o=e._owner;t+=r(n,e._source,o&&o.getName())}var a=p.current,d=a&&a._debugID;return t+=P.getStackAddendumByID(d),t},getStackAddendumByID:function(e){for(var t='';e;)t+=d(e),e=P.getParentID(e);return t},getChildIDs:function(e){var t=h(e);return t?t.childIDs:[]},getDisplayName:function(e){var t=P.getElement(e);return t?i(t):null},getElement:function(e){var t=h(e);return t?t.element:null},getOwnerID:function(e){var t=P.getElement(e);return t&&t._owner?t._owner._debugID:null},getParentID:function(e){var t=h(e);return t?t.parentID:null},getSource:function(e){var t=h(e),n=t?t.element:null,o=null==n?null:n._source;return o},getText:function(e){var t=P.getElement(e);return'string'==typeof t?t:'number'==typeof t?''+t:null},getUpdateCount:function(e){var t=h(e);return t?t.updateCount:0},getRootIDs:C,getRegisteredIDs:f,pushNonStandardWarningStack:function(e,t){if('function'==typeof console.reactStack){var n=[],o=p.current,a=o&&o._debugID;try{for(e&&n.push({name:a?P.getDisplayName(a):null,fileName:t?t.fileName:null,lineNumber:t?t.lineNumber:null});a;){var r=P.getElement(a),i=P.getParentID(a),d=P.getOwnerID(a),s=d?P.getDisplayName(d):null,l=r&&r._source;n.push({name:s,fileName:l?l.fileName:null,lineNumber:l?l.lineNumber:null}),a=i}}catch(e){}console.reactStack(n)}},popNonStandardWarningStack:function(){'function'!=typeof console.reactStackEnd||console.reactStackEnd()}};e.exports=P},function(e,t,n){'use strict';var o=n(5);e.exports={listen:function(e,t,n){return e.addEventListener?(e.addEventListener(t,n,!1),{remove:function(){e.removeEventListener(t,n,!1)}}):e.attachEvent?(e.attachEvent('on'+t,n),{remove:function(){e.detachEvent('on'+t,n)}}):void 0},capture:function(e,t,n){return e.addEventListener?(e.addEventListener(t,n,!0),{remove:function(){e.removeEventListener(t,n,!0)}}):(!1,{remove:o})},registerDefault:function(){}}},function(e,t,n){'use strict';function o(e){return r(document.documentElement,e)}var a=n(156),r=n(158),i=n(76),d=n(89),s={hasSelectionCapabilities:function(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&('input'===t&&'text'===e.type||'textarea'===t||'true'===e.contentEditable)},getSelectionInformation:function(){var e=d();return{focusedElem:e,selectionRange:s.hasSelectionCapabilities(e)?s.getSelection(e):null}},restoreSelection:function(e){var t=d(),n=e.focusedElem,a=e.selectionRange;t!==n&&o(n)&&(s.hasSelectionCapabilities(n)&&s.setSelection(n,a),i(n))},getSelection:function(e){var t;if('selectionStart'in e)t={start:e.selectionStart,end:e.selectionEnd};else if(document.selection&&e.nodeName&&'input'===e.nodeName.toLowerCase()){var n=document.selection.createRange();n.parentElement()===e&&(t={start:-n.moveStart('character',-e.value.length),end:-n.moveEnd('character',-e.value.length)})}else t=a.getOffsets(e);return t||{start:0,end:0}},setSelection:function(e,t){var n=t.start,o=t.end;if(void 0===o&&(o=n),'selectionStart'in e)e.selectionStart=n,e.selectionEnd=Math.min(o,e.value.length);else if(document.selection&&e.nodeName&&'input'===e.nodeName.toLowerCase()){var r=e.createTextRange();r.collapse(!0),r.moveStart('character',n),r.moveEnd('character',o-n),r.select()}else a.setOffsets(e,t)}};e.exports=s},function(e){'use strict';e.exports=function(e){if(e=e||('undefined'==typeof document?void 0:document),'undefined'==typeof e)return null;try{return e.activeElement||e.body}catch(t){return e.body}}},function(e,t,n){'use strict';function o(e,t){for(var n=Math.min(e.length,t.length),o=0;o<n;o++)if(e.charAt(o)!==t.charAt(o))return o;return e.length===t.length?-1:n}function a(e){return e?e.nodeType===F?e.documentElement:e.firstChild:null}function r(e){return e.getAttribute&&e.getAttribute(O)||''}function i(e,t,n,o,a){var r;if(v.logTopLevelRenders){var i=e._currentElement.props.child,d=i.type;r='React mount: '+('string'==typeof d?d:d.displayName||d.name),console.time(r)}var s=k.mountComponent(e,n,null,b(e,t),a,0);r&&console.timeEnd(r),e._renderedComponent._topLevelWrapper=e,H._mountImageIntoNode(s,t,e,o,n)}function d(e,t,n,o){var a=I.ReactReconcileTransaction.getPooled(!n&&E.useCreateElement);a.perform(i,null,e,t,a,n,o),I.ReactReconcileTransaction.release(a)}function s(e,t,n){for(!1,k.unmountComponent(e,n),!1,t.nodeType===F&&(t=t.documentElement);t.lastChild;)t.removeChild(t.lastChild)}function p(e){var t=a(e);if(t){var n=C.getInstanceFromNode(t);return!!(n&&n._hostParent)}}function l(e){return!!(e&&(e.nodeType===U||e.nodeType===F||e.nodeType===V))}function u(e){var t=a(e),n=t&&C.getInstanceFromNode(t);return n&&!n._hostParent?n:null}function c(e){var t=u(e);return t?t._hostContainerInfo._topLevelWrapper:null}var m=n(2),h=n(18),g=n(16),f=n(13),y=n(33),_=n(8),C=n(4),b=n(173),E=n(174),v=n(71),x=n(27),N=n(9),T=n(175),k=n(17),P=n(62),I=n(11),M=n(14),S=n(81),w=n(0),R=n(31),D=n(60),A=n(1),O=g.ID_ATTRIBUTE_NAME,L=g.ROOT_ATTRIBUTE_NAME,U=1,F=9,V=11,j={},B=1,W=function(){this.rootID=B++};W.prototype.isReactComponent={},!1,W.prototype.render=function(){return this.props.child},W.isReactTopLevelWrapper=!0;var H={TopLevelWrapper:W,_instancesByReactRootID:j,scrollMonitor:function(e,t){t()},_updateRootComponent:function(e,t,n,o,a){return H.scrollMonitor(o,function(){P.enqueueElementInternal(e,t,n),a&&P.enqueueCallbackInternal(e,a)}),e},_renderNewRootComponent:function(e,t,n,o){void 0,l(t)?void 0:m('37'),y.ensureScrollValueMonitoring();var a=S(e,!1);I.batchedUpdates(d,a,t,n,o);var r=a._instance.rootID;return j[r]=a,a},renderSubtreeIntoContainer:function(e,t,n,o){return null!=e&&x.has(e)?void 0:m('38'),H._renderSubtreeIntoContainer(e,t,n,o)},_renderSubtreeIntoContainer:function(e,t,n,o){P.validateCallback(o,'ReactDOM.render'),f.isValidElement(t)?void 0:m('39','string'==typeof t?' Instead of passing a string like \'div\', pass React.createElement(\'div\') or <div />.':'function'==typeof t?' Instead of passing a class like Foo, pass React.createElement(Foo) or <Foo />.':null!=t&&void 0!==t.props?' This may be caused by unintentionally loading two independent copies of React.':''),void 0;var i=f.createElement(W,{child:t}),d;if(e){var s=x.get(e);d=s._processChildContext(s._context)}else d=M;var l=c(n);if(l){var u=l._currentElement,h=u.props.child;if(D(h,t)){var g=l._renderedComponent.getPublicInstance(),y=o&&function(){o.call(g)};return H._updateRootComponent(l,i,d,n,y),g}H.unmountComponentAtNode(n)}var _=a(n),C=_&&!!r(_),b=p(n),E=H._renderNewRootComponent(i,n,C&&!l&&!b,d)._renderedComponent.getPublicInstance();return o&&o.call(E),E},render:function(e,t,n){return H._renderSubtreeIntoContainer(null,e,t,n)},unmountComponentAtNode:function(e){void 0,l(e)?void 0:m('40'),!1;var t=c(e);if(!t){var n=p(e),o=1===e.nodeType&&e.hasAttribute(L);return!1,!1}return delete j[t._instance.rootID],I.batchedUpdates(s,t,e,!1),!0},_mountImageIntoNode:function(e,t,n,r,i){if(l(t)?void 0:m('41'),r){var d=a(t);if(T.canReuseMarkup(e,d))return void C.precacheNode(n,d);var s=d.getAttribute(T.CHECKSUM_ATTR_NAME);d.removeAttribute(T.CHECKSUM_ATTR_NAME);var p=d.outerHTML;d.setAttribute(T.CHECKSUM_ATTR_NAME,s);var u=e,c=o(u,p),g=' (client) '+u.substring(c-20,c+20)+'\n (server) '+p.substring(c-20,c+20);t.nodeType===F?m('42',g):void 0,!1}if(t.nodeType===F?m('43'):void 0,i.useCreateElement){for(;t.lastChild;)t.removeChild(t.lastChild);h.insertTreeBefore(t,e,null)}else R(t,e),C.precacheNode(n,t.firstChild)}};e.exports=H},function(e,t,n){'use strict';var o=n(82);e.exports=function(e){for(var t;(t=e._renderedNodeType)===o.COMPOSITE;)e=e._renderedComponent;if(t===o.HOST)return e._renderedComponent;return t===o.EMPTY?null:void 0}},,,,,,,,,,,,function(e,t,n){'use strict';e.exports=n(104)},function(e,t,n){'use strict';var o=n(4),a=n(105),r=n(90),i=n(17),d=n(11),s=n(177),p=n(178),l=n(91),u=n(179),c=n(1);a.inject();var m={findDOMNode:p,render:r.render,unmountComponentAtNode:r.unmountComponentAtNode,version:s,unstable_batchedUpdates:d.batchedUpdates,unstable_renderSubtreeIntoContainer:u};'undefined'!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&'function'==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.inject&&__REACT_DEVTOOLS_GLOBAL_HOOK__.inject({ComponentTree:{getClosestInstanceFromNode:o.getClosestInstanceFromNode,getNodeFromInstance:function(e){return e._renderedComponent&&(e=l(e)),e?o.getNodeFromInstance(e):null}},Mount:r,Reconciler:i});e.exports=m},function(e,t,n){'use strict';var o=n(106),a=n(107),r=n(111),i=n(114),d=n(115),s=n(116),p=n(117),l=n(123),u=n(4),c=n(148),m=n(149),h=n(150),g=n(151),f=n(152),y=n(154),_=n(155),C=n(161),b=n(162),E=n(163),v=!1;e.exports={inject:function(){v||(v=!0,y.EventEmitter.injectReactEventListener(f),y.EventPluginHub.injectEventPluginOrder(i),y.EventPluginUtils.injectComponentTree(u),y.EventPluginUtils.injectTreeTraversal(m),y.EventPluginHub.injectEventPluginsByName({SimpleEventPlugin:E,EnterLeaveEventPlugin:d,ChangeEventPlugin:r,SelectEventPlugin:b,BeforeInputEventPlugin:a}),y.HostComponent.injectGenericComponentClass(l),y.HostComponent.injectTextComponentClass(h),y.DOMProperty.injectDOMPropertyConfig(o),y.DOMProperty.injectDOMPropertyConfig(s),y.DOMProperty.injectDOMPropertyConfig(C),y.EmptyComponent.injectEmptyComponentFactory(function(e){return new c(e)}),y.Updates.injectReconcileTransaction(_),y.Updates.injectBatchingStrategy(g),y.Component.injectEnvironment(p))}}},function(e){'use strict';e.exports={Properties:{"aria-current":0,"aria-details":0,"aria-disabled":0,"aria-hidden":0,"aria-invalid":0,"aria-keyshortcuts":0,"aria-label":0,"aria-roledescription":0,"aria-autocomplete":0,"aria-checked":0,"aria-expanded":0,"aria-haspopup":0,"aria-level":0,"aria-modal":0,"aria-multiline":0,"aria-multiselectable":0,"aria-orientation":0,"aria-placeholder":0,"aria-pressed":0,"aria-readonly":0,"aria-required":0,"aria-selected":0,"aria-sort":0,"aria-valuemax":0,"aria-valuemin":0,"aria-valuenow":0,"aria-valuetext":0,"aria-atomic":0,"aria-busy":0,"aria-live":0,"aria-relevant":0,"aria-dropeffect":0,"aria-grabbed":0,"aria-activedescendant":0,"aria-colcount":0,"aria-colindex":0,"aria-colspan":0,"aria-controls":0,"aria-describedby":0,"aria-errormessage":0,"aria-flowto":0,"aria-labelledby":0,"aria-owns":0,"aria-posinset":0,"aria-rowcount":0,"aria-rowindex":0,"aria-rowspan":0,"aria-setsize":0},DOMAttributeNames:{},DOMPropertyNames:{}}},function(e,t,n){'use strict';function o(e){return(e.ctrlKey||e.altKey||e.metaKey)&&!(e.ctrlKey&&e.altKey)}function a(e){return'topCompositionStart'===e?T.compositionStart:'topCompositionEnd'===e?T.compositionEnd:'topCompositionUpdate'===e?T.compositionUpdate:void 0}function r(e,t){return'topKeyDown'===e&&t.keyCode===_}function i(e,t){return'topKeyUp'===e?-1!==y.indexOf(t.keyCode):'topKeyDown'===e?t.keyCode!==_:'topKeyPress'==e||'topMouseDown'==e||'topBlur'==e}function d(e){var t=e.detail;return'object'==typeof t&&'data'in t?t.data:null}function s(e,t,n,o){var s,p;if(C?s=a(e):P?i(e,n)&&(s=T.compositionEnd):r(e,n)&&(s=T.compositionStart),!s)return null;v&&(P||s!==T.compositionStart?s===T.compositionEnd&&P&&(p=P.getData()):P=h.getPooled(o));var l=g.getPooled(s,t,n,o);if(p)l.data=p;else{var u=d(n);null!==u&&(l.data=u)}return c.accumulateTwoPhaseDispatches(l),l}function p(e,t){switch(e){case'topCompositionEnd':return d(t);case'topKeyPress':var n=t.which;return n===x?(k=!0,N):null;case'topTextInput':var o=t.data;return o===N&&k?null:o;default:return null;}}function l(e,t){if(P){if('topCompositionEnd'===e||!C&&i(e,t)){var n=P.getData();return h.release(P),P=null,n}return null}return'topPaste'===e?null:'topKeyPress'===e?t.which&&!o(t)?String.fromCharCode(t.which):null:'topCompositionEnd'===e?v?null:t.data:null}function u(e,t,n,o){var a;if(a=E?p(e,n):l(e,n),!a)return null;var r=f.getPooled(T.beforeInput,t,n,o);return r.data=a,c.accumulateTwoPhaseDispatches(r),r}var c=n(24),m=n(6),h=n(108),g=n(109),f=n(110),y=[9,13,27,32],_=229,C=m.canUseDOM&&'CompositionEvent'in window,b=null;m.canUseDOM&&'documentMode'in document&&(b=document.documentMode);var E=m.canUseDOM&&'TextEvent'in window&&!b&&!function(){var e=window.opera;return'object'==typeof e&&'function'==typeof e.version&&12>=parseInt(e.version(),10)}(),v=m.canUseDOM&&(!C||b&&8<b&&11>=b),x=32,N=' ',T={beforeInput:{phasedRegistrationNames:{bubbled:'onBeforeInput',captured:'onBeforeInputCapture'},dependencies:['topCompositionEnd','topKeyPress','topTextInput','topPaste']},compositionEnd:{phasedRegistrationNames:{bubbled:'onCompositionEnd',captured:'onCompositionEndCapture'},dependencies:['topBlur','topCompositionEnd','topKeyDown','topKeyPress','topKeyUp','topMouseDown']},compositionStart:{phasedRegistrationNames:{bubbled:'onCompositionStart',captured:'onCompositionStartCapture'},dependencies:['topBlur','topCompositionStart','topKeyDown','topKeyPress','topKeyUp','topMouseDown']},compositionUpdate:{phasedRegistrationNames:{bubbled:'onCompositionUpdate',captured:'onCompositionUpdateCapture'},dependencies:['topBlur','topCompositionUpdate','topKeyDown','topKeyPress','topKeyUp','topMouseDown']}},k=!1,P=null;e.exports={eventTypes:T,extractEvents:function(e,t,n,o){return[s(e,t,n,o),u(e,t,n,o)]}}},function(e,t,n){'use strict';function o(e){this._root=e,this._startText=this.getText(),this._fallbackText=null}var a=n(3),r=n(15),i=n(69);a(o.prototype,{destructor:function(){this._root=null,this._startText=null,this._fallbackText=null},getText:function(){return'value'in this._root?this._root.value:this._root[i()]},getData:function(){if(this._fallbackText)return this._fallbackText;var e=this._startText,t=e.length,n=this.getText(),o=n.length,a,r;for(a=0;a<t&&e[a]===n[a];a++);var i=t-a;for(r=1;r<=i&&e[t-r]===n[o-r];r++);var d=1<r?1-r:void 0;return this._fallbackText=n.slice(a,d),this._fallbackText}}),r.addPoolingTo(o),e.exports=o},function(e,t,n){'use strict';function o(e,t,n,o){return a.call(this,e,t,n,o)}var a=n(12);a.augmentClass(o,{data:null}),e.exports=o},function(e,t,n){'use strict';function o(e,t,n,o){return a.call(this,e,t,n,o)}var a=n(12);a.augmentClass(o,{data:null}),e.exports=o},function(e,t,n){'use strict';function o(e,t,n){var o=k.getPooled(w.change,e,t,n);return o.type='change',v.accumulateTwoPhaseDispatches(o),o}function a(e){var t=e.nodeName&&e.nodeName.toLowerCase();return'select'===t||'input'===t&&'file'===e.type}function r(e){var t=o(D,e,I(e));T.batchedUpdates(i,t)}function i(e){E.enqueueEvents(e),E.processEventQueue(!1)}function d(e,t){R=e,D=t,R.attachEvent('onchange',r)}function s(){R&&(R.detachEvent('onchange',r),R=null,D=null)}function p(e,t){var n=P.updateValueIfChanged(e),o=!0===t.simulated&&L._allowSimulatedPassThrough;if(n||o)return e}function l(e,t){if('topChange'===e)return t}function u(e,t,n){'topFocus'===e?(s(),d(t,n)):'topBlur'===e&&s()}function c(e,t){R=e,D=t,R.attachEvent('onpropertychange',h)}function m(){R&&(R.detachEvent('onpropertychange',h),R=null,D=null)}function h(e){'value'!==e.propertyName||p(D,e)&&r(e)}function g(e,t,n){'topFocus'===e?(m(),c(t,n)):'topBlur'===e&&m()}function f(e,t,n){if('topSelectionChange'===e||'topKeyUp'===e||'topKeyDown'===e)return p(D,n)}function y(e){var t=e.nodeName;return t&&'input'===t.toLowerCase()&&('checkbox'===e.type||'radio'===e.type)}function _(e,t,n){if('topClick'===e)return p(t,n)}function C(e,t,n){if('topInput'===e||'topChange'===e)return p(t,n)}function b(e,t){if(null!=e){var n=e._wrapperState||t._wrapperState;if(n&&n.controlled&&'number'===t.type){var o=''+t.value;t.getAttribute('value')!==o&&t.setAttribute('value',o)}}}var E=n(25),v=n(24),x=n(6),N=n(4),T=n(11),k=n(12),P=n(72),I=n(51),M=n(52),S=n(73),w={change:{phasedRegistrationNames:{bubbled:'onChange',captured:'onChangeCapture'},dependencies:['topBlur','topChange','topClick','topFocus','topInput','topKeyDown','topKeyUp','topSelectionChange']}},R=null,D=null,A=!1;x.canUseDOM&&(A=M('change')&&(!document.documentMode||8<document.documentMode));var O=!1;x.canUseDOM&&(O=M('input')&&(!('documentMode'in document)||9<document.documentMode));var L={eventTypes:w,_allowSimulatedPassThrough:!0,_isInputEventSupported:O,extractEvents:function(e,t,n,r){var i=t?N.getNodeFromInstance(t):window,d,s;if(a(i)?A?d=l:s=u:S(i)?O?d=C:(d=f,s=g):y(i)&&(d=_),d){var p=d(e,t,n);if(p){var c=o(p,n,r);return c}}s&&s(e,i,t),'topBlur'===e&&b(t,i)}};e.exports=L},function(e,t,n){'use strict';function o(e,t,n){'function'==typeof e?e(t.getPublicInstance()):r.addComponentAsRefTo(t,e,n)}function a(e,t,n){'function'==typeof e?e(null):r.removeComponentAsRefFrom(t,e,n)}var r=n(113),i={};i.attachRefs=function(e,t){if(null!==t&&'object'==typeof t){var n=t.ref;null!=n&&o(n,e,t._owner)}},i.shouldUpdateRefs=function(e,t){var n=null,o=null;null!==e&&'object'==typeof e&&(n=e.ref,o=e._owner);var a=null,r=null;return null!==t&&'object'==typeof t&&(a=t.ref,r=t._owner),n!==a||'string'==typeof a&&r!==o},i.detachRefs=function(e,t){if(null!==t&&'object'==typeof t){var n=t.ref;null!=n&&a(n,e,t._owner)}},e.exports=i},function(e,t,n){'use strict';function o(e){return!!(e&&'function'==typeof e.attachRef&&'function'==typeof e.detachRef)}var a=n(2),r=n(0);e.exports={addComponentAsRefTo:function(e,t,n){o(n)?void 0:a('119'),n.attachRef(t,e)},removeComponentAsRefFrom:function(e,t,n){o(n)?void 0:a('120');var r=n.getPublicInstance();r&&r.refs[t]===e.getPublicInstance()&&n.detachRef(t)}}},function(e){'use strict';e.exports=['ResponderEventPlugin','SimpleEventPlugin','TapEventPlugin','EnterLeaveEventPlugin','ChangeEventPlugin','SelectEventPlugin','BeforeInputEventPlugin']},function(e,t,n){'use strict';var o=n(24),a=n(4),r=n(30),i={mouseEnter:{registrationName:'onMouseEnter',dependencies:['topMouseOut','topMouseOver']},mouseLeave:{registrationName:'onMouseLeave',dependencies:['topMouseOut','topMouseOver']}};e.exports={eventTypes:i,extractEvents:function(e,t,n,d){if('topMouseOver'===e&&(n.relatedTarget||n.fromElement))return null;if('topMouseOut'!==e&&'topMouseOver'!==e)return null;var s;if(d.window===d)s=d;else{var p=d.ownerDocument;s=p?p.defaultView||p.parentWindow:window}var l,u;if('topMouseOut'===e){l=t;var c=n.relatedTarget||n.toElement;u=c?a.getClosestInstanceFromNode(c):null}else l=null,u=t;if(l===u)return null;var m=null==l?s:a.getNodeFromInstance(l),h=null==u?s:a.getNodeFromInstance(u),g=r.getPooled(i.mouseLeave,l,n,d);g.type='mouseleave',g.target=m,g.relatedTarget=h;var f=r.getPooled(i.mouseEnter,u,n,d);return f.type='mouseenter',f.target=h,f.relatedTarget=m,o.accumulateEnterLeaveDispatches(g,f,l,u),[g,f]}}},function(e,t,n){'use strict';var o=n(16),a=o.injection.MUST_USE_PROPERTY,r=o.injection.HAS_BOOLEAN_VALUE,i=o.injection.HAS_NUMERIC_VALUE,d=o.injection.HAS_POSITIVE_NUMERIC_VALUE,s=o.injection.HAS_OVERLOADED_BOOLEAN_VALUE,p={isCustomAttribute:RegExp.prototype.test.bind(new RegExp('^(data|aria)-['+o.ATTRIBUTE_NAME_CHAR+']*$')),Properties:{accept:0,acceptCharset:0,accessKey:0,action:0,allowFullScreen:r,allowTransparency:0,alt:0,as:0,async:r,autoComplete:0,autoPlay:r,capture:r,cellPadding:0,cellSpacing:0,charSet:0,challenge:0,checked:a|r,cite:0,classID:0,className:0,cols:d,colSpan:0,content:0,contentEditable:0,contextMenu:0,controls:r,coords:0,crossOrigin:0,data:0,dateTime:0,default:r,defer:r,dir:0,disabled:r,download:s,draggable:0,encType:0,form:0,formAction:0,formEncType:0,formMethod:0,formNoValidate:r,formTarget:0,frameBorder:0,headers:0,height:0,hidden:r,high:0,href:0,hrefLang:0,htmlFor:0,httpEquiv:0,icon:0,id:0,inputMode:0,integrity:0,is:0,keyParams:0,keyType:0,kind:0,label:0,lang:0,list:0,loop:r,low:0,manifest:0,marginHeight:0,marginWidth:0,max:0,maxLength:0,media:0,mediaGroup:0,method:0,min:0,minLength:0,multiple:a|r,muted:a|r,name:0,nonce:0,noValidate:r,open:r,optimum:0,pattern:0,placeholder:0,playsInline:r,poster:0,preload:0,profile:0,radioGroup:0,readOnly:r,referrerPolicy:0,rel:0,required:r,reversed:r,role:0,rows:d,rowSpan:i,sandbox:0,scope:0,scoped:r,scrolling:0,seamless:r,selected:a|r,shape:0,size:d,sizes:0,span:d,spellCheck:0,src:0,srcDoc:0,srcLang:0,srcSet:0,start:i,step:0,style:0,summary:0,tabIndex:0,target:0,title:0,type:0,useMap:0,value:0,width:0,wmode:0,wrap:0,about:0,datatype:0,inlist:0,prefix:0,property:0,resource:0,typeof:0,vocab:0,autoCapitalize:0,autoCorrect:0,autoSave:0,color:0,itemProp:0,itemScope:r,itemType:0,itemID:0,itemRef:0,results:0,security:0,unselectable:0},DOMAttributeNames:{acceptCharset:'accept-charset',className:'class',htmlFor:'for',httpEquiv:'http-equiv'},DOMPropertyNames:{},DOMMutationMethods:{value:function(e,t){return null==t?e.removeAttribute('value'):void('number'!==e.type||!1===e.hasAttribute('value')?e.setAttribute('value',''+t):e.validity&&!e.validity.badInput&&e.ownerDocument.activeElement!==e&&e.setAttribute('value',''+t))}}};e.exports=p},function(e,t,n){'use strict';var o=n(54),a=n(122),r={processChildrenUpdates:a.dangerouslyProcessChildrenUpdates,replaceNodeWithMarkup:o.dangerouslyReplaceNodeWithMarkup};e.exports=r},function(e,t,n){'use strict';var o=n(2),a=n(18),r=n(6),i=n(119),d=n(5),s=n(0);e.exports={dangerouslyReplaceNodeWithMarkup:function(e,t){if(r.canUseDOM?void 0:o('56'),t?void 0:o('57'),'HTML'===e.nodeName?o('58'):void 0,'string'==typeof t){var n=i(t,d)[0];e.parentNode.replaceChild(n,e)}else a.replaceChildWithTree(e,t)}}},function(e,t,n){'use strict';function o(e){var t=e.match(p);return t&&t[1].toLowerCase()}var a=n(6),r=n(120),i=n(121),d=n(0),s=a.canUseDOM?document.createElement('div'):null,p=/^\s*<(\w+)/;e.exports=function(e,t){var n=s;!!s?void 0:d(!1);var a=o(e),p=a&&i(a);if(p){n.innerHTML=p[1]+e+p[2];for(var l=p[0];l--;)n=n.lastChild}else n.innerHTML=e;var u=n.getElementsByTagName('script');u.length&&(t?void 0:d(!1),r(u).forEach(t));for(var c=Array.from(n.childNodes);n.lastChild;)n.removeChild(n.lastChild);return c}},function(e,t,n){'use strict';function o(e){var t=e.length;if(Array.isArray(e)||'object'!=typeof e&&'function'!=typeof e?r(!1):void 0,'number'==typeof t?void 0:r(!1),0===t||t-1 in e?void 0:r(!1),'function'==typeof e.callee?r(!1):void 0,e.hasOwnProperty)try{return Array.prototype.slice.call(e)}catch(t){}for(var n=Array(t),o=0;o<t;o++)n[o]=e[o];return n}function a(e){return!!e&&('object'==typeof e||'function'==typeof e)&&'length'in e&&!('setInterval'in e)&&'number'!=typeof e.nodeType&&(Array.isArray(e)||'callee'in e||'item'in e)}var r=n(0);e.exports=function(e){return a(e)?Array.isArray(e)?e.slice():o(e):[e]}},function(e,t,n){'use strict';var o=n(6),a=n(0),r=o.canUseDOM?document.createElement('div'):null,i={},d=[1,'<select multiple="true">','</select>'],s=[1,'<table>','</table>'],p=[3,'<table><tbody><tr>','</tr></tbody></table>'],l=[1,'<svg xmlns="http://www.w3.org/2000/svg">','</svg>'],u={"*":[1,'?<div>','</div>'],area:[1,'<map>','</map>'],col:[2,'<table><tbody></tbody><colgroup>','</colgroup></table>'],legend:[1,'<fieldset>','</fieldset>'],param:[1,'<object>','</object>'],tr:[2,'<table><tbody>','</tbody></table>'],optgroup:d,option:d,caption:s,colgroup:s,tbody:s,tfoot:s,thead:s,td:p,th:p};['circle','clipPath','defs','ellipse','g','image','line','linearGradient','mask','path','pattern','polygon','polyline','radialGradient','rect','stop','text','tspan'].forEach(function(e){u[e]=l,i[e]=!0}),e.exports=function(e){return r?void 0:a(!1),u.hasOwnProperty(e)||(e='*'),i.hasOwnProperty(e)||(r.innerHTML='*'===e?'<link />':'<'+e+'></'+e+'>',i[e]=!r.firstChild),i[e]?u[e]:null}},function(e,t,n){'use strict';var o=n(54),a=n(4);e.exports={dangerouslyProcessChildrenUpdates:function(e,t){var n=a.getNodeFromInstance(e);o.processUpdates(n,t)}}},function(e,t,n){'use strict';function o(e){if(e){var t=e._currentElement._owner||null;if(t){var n=t.getName();if(n)return' This DOM node was rendered by `'+n+'`.'}}return''}function a(e){if('object'==typeof e){if(Array.isArray(e))return'['+e.map(a).join(', ')+']';var t=[];for(var n in e)if(Object.prototype.hasOwnProperty.call(e,n)){var o=/^[a-z$_][\w$_]*$/i.test(n)?n:JSON.stringify(n);t.push(o+': '+a(e[n]))}return'{'+t.join(', ')+'}'}return'string'==typeof e?JSON.stringify(e):'function'==typeof e?'[function object]':e+''}function r(e,t){t&&(ae[e._tag]&&(null==t.children&&null==t.dangerouslySetInnerHTML?void 0:y('137',e._tag,e._currentElement._owner?' Check the render method of '+e._currentElement._owner.getName()+'.':'')),null!=t.dangerouslySetInnerHTML&&(null==t.children?void 0:y('60'),'object'==typeof t.dangerouslySetInnerHTML&&$ in t.dangerouslySetInnerHTML?void 0:y('61')),!1,null==t.style||'object'==typeof t.style?void 0:y('62',o(e)))}function i(e,t,n,o){if(!(o instanceof L)){var a=e._hostContainerInfo,r=a._node&&a._node.nodeType===J,i=r?a._node:a._ownerDocument;z(t,i),o.getReactMountReady().enqueue(d,{inst:e,registrationName:t,listener:n})}}function d(){var e=this;T.putListener(e.inst,e.registrationName,e.listener)}function s(){var e=this;S.postMountWrapper(e)}function p(){var e=this;D.postMountWrapper(e)}function l(){var e=this;w.postMountWrapper(e)}function u(){W.track(this)}function c(){var e=this;e._rootNodeID?void 0:y('63');var t=Y(e);switch(t?void 0:y('64'),e._tag){case'iframe':case'object':e._wrapperState.listeners=[P.trapBubbledEvent('topLoad','load',t)];break;case'video':case'audio':for(var n in e._wrapperState.listeners=[],te)te.hasOwnProperty(n)&&e._wrapperState.listeners.push(P.trapBubbledEvent(n,te[n],t));break;case'source':e._wrapperState.listeners=[P.trapBubbledEvent('topError','error',t)];break;case'img':e._wrapperState.listeners=[P.trapBubbledEvent('topError','error',t),P.trapBubbledEvent('topLoad','load',t)];break;case'form':e._wrapperState.listeners=[P.trapBubbledEvent('topReset','reset',t),P.trapBubbledEvent('topSubmit','submit',t)];break;case'input':case'select':case'textarea':e._wrapperState.listeners=[P.trapBubbledEvent('topInvalid','invalid',t)];}}function m(){R.postUpdateWrapper(this)}function h(e){de.call(ie,e)||(re.test(e)?void 0:y('65',e),ie[e]=!0)}function g(e,t){return 0<=e.indexOf('-')||null!=t.is}function f(e){var t=e.type;h(t),this._currentElement=e,this._tag=t.toLowerCase(),this._namespaceURI=null,this._renderedChildren=null,this._previousStyle=null,this._previousStyleCopy=null,this._hostNode=null,this._hostParent=null,this._rootNodeID=0,this._domID=0,this._hostContainerInfo=null,this._wrapperState=null,this._topLevelWrapper=null,this._flags=0,!1}var y=n(2),_=n(3),C=n(124),b=n(125),E=n(18),v=n(55),x=n(16),N=n(78),T=n(25),k=n(48),P=n(33),I=n(66),M=n(4),S=n(135),w=n(137),R=n(79),D=n(138),A=n(9),O=n(139),L=n(146),U=n(5),F=n(32),V=n(0),j=n(52),B=n(59),W=n(72),H=n(63),q=n(1),K=T.deleteListener,Y=M.getNodeFromInstance,z=P.listenTo,X=k.registrationNameModules,G={string:!0,number:!0},Q='style',$='__html',Z={children:null,dangerouslySetInnerHTML:null,suppressContentEditableWarning:null},J=11,ee={};var te={topAbort:'abort',topCanPlay:'canplay',topCanPlayThrough:'canplaythrough',topDurationChange:'durationchange',topEmptied:'emptied',topEncrypted:'encrypted',topEnded:'ended',topError:'error',topLoadedData:'loadeddata',topLoadedMetadata:'loadedmetadata',topLoadStart:'loadstart',topPause:'pause',topPlay:'play',topPlaying:'playing',topProgress:'progress',topRateChange:'ratechange',topSeeked:'seeked',topSeeking:'seeking',topStalled:'stalled',topSuspend:'suspend',topTimeUpdate:'timeupdate',topVolumeChange:'volumechange',topWaiting:'waiting'},ne={area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0},oe={listing:!0,pre:!0,textarea:!0},ae=_({menuitem:!0},ne),re=/^[a-zA-Z][a-zA-Z:_\.\-\d]*$/,ie={},de={}.hasOwnProperty,se=1;f.displayName='ReactDOMComponent',f.Mixin={mountComponent:function(e,t,n,o){this._rootNodeID=se++,this._domID=n._idCounter++,this._hostParent=t,this._hostContainerInfo=n;var a=this._currentElement.props;switch(this._tag){case'audio':case'form':case'iframe':case'img':case'link':case'object':case'source':case'video':this._wrapperState={listeners:null},e.getReactMountReady().enqueue(c,this);break;case'input':S.mountWrapper(this,a,t),a=S.getHostProps(this,a),e.getReactMountReady().enqueue(u,this),e.getReactMountReady().enqueue(c,this);break;case'option':w.mountWrapper(this,a,t),a=w.getHostProps(this,a);break;case'select':R.mountWrapper(this,a,t),a=R.getHostProps(this,a),e.getReactMountReady().enqueue(c,this);break;case'textarea':D.mountWrapper(this,a,t),a=D.getHostProps(this,a),e.getReactMountReady().enqueue(u,this),e.getReactMountReady().enqueue(c,this);}r(this,a);var i,d;null==t?n._tag&&(i=n._namespaceURI,d=n._tag):(i=t._namespaceURI,d=t._tag),(null==i||i===v.svg&&'foreignobject'===d)&&(i=v.html),i===v.html&&('svg'===this._tag?i=v.svg:'math'===this._tag&&(i=v.mathml)),this._namespaceURI=i;var m;if(e.useCreateElement){var h=n._ownerDocument,g;if(!(i===v.html))g=h.createElementNS(i,this._currentElement.type);else if('script'===this._tag){var f=h.createElement('div'),y=this._currentElement.type;f.innerHTML='<'+y+'></'+y+'>',g=f.removeChild(f.firstChild)}else g=a.is?h.createElement(this._currentElement.type,a.is):h.createElement(this._currentElement.type);M.precacheNode(this,g),this._flags|=I.hasCachedChildNodes,this._hostParent||N.setAttributeForRoot(g),this._updateDOMProperties(null,a,e);var _=E(g);this._createInitialChildren(e,a,o,_),m=_}else{var b=this._createOpenTagMarkupAndPutListeners(e,a),x=this._createContentMarkup(e,a,o);m=!x&&ne[this._tag]?b+'/>':b+'>'+x+'</'+this._currentElement.type+'>'}switch(this._tag){case'input':e.getReactMountReady().enqueue(s,this),a.autoFocus&&e.getReactMountReady().enqueue(C.focusDOMComponent,this);break;case'textarea':e.getReactMountReady().enqueue(p,this),a.autoFocus&&e.getReactMountReady().enqueue(C.focusDOMComponent,this);break;case'select':a.autoFocus&&e.getReactMountReady().enqueue(C.focusDOMComponent,this);break;case'button':a.autoFocus&&e.getReactMountReady().enqueue(C.focusDOMComponent,this);break;case'option':e.getReactMountReady().enqueue(l,this);}return m},_createOpenTagMarkupAndPutListeners:function(e,t){var n='<'+this._currentElement.type;for(var o in t)if(t.hasOwnProperty(o)){var a=t[o];if(null!=a)if(X.hasOwnProperty(o))a&&i(this,o,a,e);else{o==Q&&(a&&(!1,a=this._previousStyleCopy=_({},t.style)),a=b.createMarkupForStyles(a,this));var r=null;null!=this._tag&&g(this._tag,t)?!Z.hasOwnProperty(o)&&(r=N.createMarkupForCustomAttribute(o,a)):r=N.createMarkupForProperty(o,a),r&&(n+=' '+r)}}return e.renderToStaticMarkup?n:(this._hostParent||(n+=' '+N.createMarkupForRoot()),n+=' '+N.createMarkupForID(this._domID),n)},_createContentMarkup:function(e,t,n){var o='',a=t.dangerouslySetInnerHTML;if(null!=a)null!=a.__html&&(o=a.__html);else{var r=G[typeof t.children]?t.children:null,i=null==r?t.children:null;if(null!=r)o=F(r),!1;else if(null!=i){var d=this.mountChildren(i,e,n);o=d.join('')}}return oe[this._tag]&&'\n'===o.charAt(0)?'\n'+o:o},_createInitialChildren:function(e,t,n,o){var a=t.dangerouslySetInnerHTML;if(null!=a)null!=a.__html&&E.queueHTML(o,a.__html);else{var r=G[typeof t.children]?t.children:null,d=null==r?t.children:null;if(null!=r)''!==r&&(!1,E.queueText(o,r));else if(null!=d)for(var s=this.mountChildren(d,e,n),p=0;p<s.length;p++)E.queueChild(o,s[p])}},receiveComponent:function(e,t,n){var o=this._currentElement;this._currentElement=e,this.updateComponent(t,o,e,n)},updateComponent:function(e,t,n,o){var a=t.props,i=this._currentElement.props;switch(this._tag){case'input':a=S.getHostProps(this,a),i=S.getHostProps(this,i);break;case'option':a=w.getHostProps(this,a),i=w.getHostProps(this,i);break;case'select':a=R.getHostProps(this,a),i=R.getHostProps(this,i);break;case'textarea':a=D.getHostProps(this,a),i=D.getHostProps(this,i);}switch(r(this,i),this._updateDOMProperties(a,i,e),this._updateDOMChildren(a,i,e,o),this._tag){case'input':S.updateWrapper(this);break;case'textarea':D.updateWrapper(this);break;case'select':e.getReactMountReady().enqueue(m,this);}},_updateDOMProperties:function(e,t,n){var o,a,r;for(o in e)if(!t.hasOwnProperty(o)&&e.hasOwnProperty(o)&&null!=e[o])if(o===Q){var d=this._previousStyleCopy;for(a in d)d.hasOwnProperty(a)&&(r=r||{},r[a]='');this._previousStyleCopy=null}else X.hasOwnProperty(o)?e[o]&&K(this,o):g(this._tag,e)?Z.hasOwnProperty(o)||N.deleteValueForAttribute(Y(this),o):(x.properties[o]||x.isCustomAttribute(o))&&N.deleteValueForProperty(Y(this),o);for(o in t){var s=t[o],p=o===Q?this._previousStyleCopy:null==e?void 0:e[o];if(t.hasOwnProperty(o)&&s!==p&&(null!=s||null!=p))if(o===Q){if(s?(!1,s=this._previousStyleCopy=_({},s)):this._previousStyleCopy=null,p){for(a in p)!p.hasOwnProperty(a)||s&&s.hasOwnProperty(a)||(r=r||{},r[a]='');for(a in s)s.hasOwnProperty(a)&&p[a]!==s[a]&&(r=r||{},r[a]=s[a])}else r=s;}else if(X.hasOwnProperty(o))s?i(this,o,s,n):p&&K(this,o);else if(g(this._tag,t))Z.hasOwnProperty(o)||N.setValueForAttribute(Y(this),o,s);else if(x.properties[o]||x.isCustomAttribute(o)){var l=Y(this);null==s?N.deleteValueForProperty(l,o):N.setValueForProperty(l,o,s)}}r&&b.setValueForStyles(Y(this),r,this)},_updateDOMChildren:function(e,t,n,o){var a=G[typeof e.children]?e.children:null,r=G[typeof t.children]?t.children:null,i=e.dangerouslySetInnerHTML&&e.dangerouslySetInnerHTML.__html,d=t.dangerouslySetInnerHTML&&t.dangerouslySetInnerHTML.__html,s=null==a?e.children:null,p=null==r?t.children:null;null!=s&&null==p?this.updateChildren(null,n,o):(null!=a||null!=i)&&!(null!=r||null!=d)&&(this.updateTextContent(''),!1),null==r?null==d?null!=p&&(!1,this.updateChildren(p,n,o)):(i!==d&&this.updateMarkup(''+d),!1):a!==r&&(this.updateTextContent(''+r),!1)},getHostNode:function(){return Y(this)},unmountComponent:function(e){switch(this._tag){case'audio':case'form':case'iframe':case'img':case'link':case'object':case'source':case'video':var t=this._wrapperState.listeners;if(t)for(var n=0;n<t.length;n++)t[n].remove();break;case'input':case'textarea':W.stopTracking(this);break;case'html':case'head':case'body':y('66',this._tag);}this.unmountChildren(e),M.uncacheNode(this),T.deleteAllListeners(this),this._rootNodeID=0,this._domID=0,this._wrapperState=null,!1},getPublicInstance:function(){return Y(this)}},_(f.prototype,f.Mixin,O.Mixin),e.exports=f},function(e,t,n){'use strict';var o=n(4),a=n(76);e.exports={focusDOMComponent:function(){a(o.getNodeFromInstance(this))}}},function(e,t,n){'use strict';var o=n(77),a=n(6),r=n(9),i=n(126),d=n(128),s=n(129),p=n(131),l=n(1),u=p(function(e){return s(e)}),c=!1,m='cssFloat';if(a.canUseDOM){var h=document.createElement('div').style;try{h.font=''}catch(t){c=!0}document.documentElement.style.cssFloat===void 0&&(m='styleFloat')}e.exports={createMarkupForStyles:function(e,t){var n='';for(var o in e)if(e.hasOwnProperty(o)){var a=0===o.indexOf('--'),r=e[o];!1,null!=r&&(n+=u(o)+':',n+=d(o,r,t,a)+';')}return n||null},setValueForStyles:function(e,t,n){var a=e.style;for(var r in t)if(t.hasOwnProperty(r)){var i=0===r.indexOf('--');var s=d(r,t[r],n,i);if(('float'==r||'cssFloat'==r)&&(r=m),i)a.setProperty(r,s);else if(s)a[r]=s;else{var p=c&&o.shorthandPropertyExpansions[r];if(p)for(var l in p)a[l]='';else a[r]=''}}}}},function(e,t,n){'use strict';var o=n(127),a=/^-ms-/;e.exports=function(e){return o(e.replace(a,'ms-'))}},function(e){'use strict';var t=/-(.)/g;e.exports=function(e){return e.replace(t,function(e,t){return t.toUpperCase()})}},function(e,t,n){'use strict';var o=n(77),a=n(1),r=o.isUnitlessNumber;e.exports=function(e,t,n,o){var a=null==t||'boolean'==typeof t||''===t;if(a)return'';var i=isNaN(t);if(o||i||0===t||r.hasOwnProperty(e)&&r[e])return''+t;if('string'==typeof t){t=t.trim()}return t+'px'}},function(e,t,n){'use strict';var o=n(130),a=/^ms-/;e.exports=function(e){return o(e).replace(a,'-ms-')}},function(e){'use strict';var t=/([A-Z])/g;e.exports=function(e){return e.replace(t,'-$1').toLowerCase()}},function(e){'use strict';e.exports=function(e){var t={};return function(n){return t.hasOwnProperty(n)||(t[n]=e.call(this,n)),t[n]}}},function(e,t,n){'use strict';var o=n(32);e.exports=function(e){return'"'+o(e)+'"'}},function(e,t,n){'use strict';function o(e){a.enqueueEvents(e),a.processEventQueue(!1)}var a=n(25);e.exports={handleTopLevel:function(e,t,n,r){var i=a.extractEvents(e,t,n,r);o(i)}}},function(e,t,n){'use strict';function o(e,t){var n={};return n[e.toLowerCase()]=t.toLowerCase(),n['Webkit'+e]='webkit'+t,n['Moz'+e]='moz'+t,n['ms'+e]='MS'+t,n['O'+e]='o'+t.toLowerCase(),n}var a=n(6),r={animationend:o('Animation','AnimationEnd'),animationiteration:o('Animation','AnimationIteration'),animationstart:o('Animation','AnimationStart'),transitionend:o('Transition','TransitionEnd')},i={},d={};a.canUseDOM&&(d=document.createElement('div').style,!('AnimationEvent'in window)&&(delete r.animationend.animation,delete r.animationiteration.animation,delete r.animationstart.animation),!('TransitionEvent'in window)&&delete r.transitionend.transition),e.exports=function(e){if(i[e])return i[e];if(!r[e])return e;var t=r[e];for(var n in t)if(t.hasOwnProperty(n)&&n in d)return i[e]=t[n];return''}},function(e,t,n){'use strict';function o(){this._rootNodeID&&h.updateWrapper(this)}function a(e){var t='checkbox'===e.type||'radio'===e.type;return t?null!=e.checked:null!=e.value}function r(e){var t=this._currentElement.props,n=p.executeOnChange(t,e);u.asap(o,this);var a=t.name;if('radio'===t.type&&null!=a){for(var r=l.getNodeFromInstance(this),d=r;d.parentNode;)d=d.parentNode;for(var s=d.querySelectorAll('input[name='+JSON.stringify(''+a)+'][type="radio"]'),c=0,m;c<s.length;c++)if(m=s[c],m!==r&&m.form===r.form){var h=l.getInstanceFromNode(m);h?void 0:i('90'),u.asap(o,h)}}return n}var i=n(2),d=n(3),s=n(78),p=n(57),l=n(4),u=n(11),c=n(0),m=n(1),h={getHostProps:function(e,t){var n=p.getValue(t),o=p.getChecked(t),a=d({type:void 0,step:void 0,min:void 0,max:void 0},t,{defaultChecked:void 0,defaultValue:void 0,value:null==n?e._wrapperState.initialValue:n,checked:null==o?e._wrapperState.initialChecked:o,onChange:e._wrapperState.onChange});return a},mountWrapper:function(e,t){var n=t.defaultValue;e._wrapperState={initialChecked:null==t.checked?t.defaultChecked:t.checked,initialValue:null==t.value?n:t.value,listeners:null,onChange:r.bind(e),controlled:a(t)}},updateWrapper:function(e){var t=e._currentElement.props;var n=t.checked;null!=n&&s.setValueForProperty(l.getNodeFromInstance(e),'checked',n||!1);var o=l.getNodeFromInstance(e),a=p.getValue(t);if(!(null!=a))null==t.value&&null!=t.defaultValue&&o.defaultValue!==''+t.defaultValue&&(o.defaultValue=''+t.defaultValue),null==t.checked&&null!=t.defaultChecked&&(o.defaultChecked=!!t.defaultChecked);else if(0===a&&''===o.value)o.value='0';else if('number'===t.type){var r=parseFloat(o.value,10)||0;(a!=r||a==r&&o.value!=a)&&(o.value=''+a)}else o.value!==''+a&&(o.value=''+a)},postMountWrapper:function(e){var t=e._currentElement.props,n=l.getNodeFromInstance(e);switch(t.type){case'submit':case'reset':break;case'color':case'date':case'datetime':case'datetime-local':case'month':case'time':case'week':n.value='',n.value=n.defaultValue;break;default:n.value=n.value;}var o=n.name;''!==o&&(n.name=''),n.defaultChecked=!n.defaultChecked,n.defaultChecked=!n.defaultChecked,''!==o&&(n.name=o)}};e.exports=h},function(e){'use strict';e.exports='SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED'},function(e,t,n){'use strict';function o(e){var t='';return r.Children.forEach(e,function(e){null==e||('string'==typeof e||'number'==typeof e?t+=e:!p&&(p=!0,void 0))}),t}var a=n(3),r=n(13),i=n(4),d=n(79),s=n(1),p=!1;e.exports={mountWrapper:function(e,t,n){var a=null;if(null!=n){var r=n;'optgroup'===r._tag&&(r=r._hostParent),null!=r&&'select'===r._tag&&(a=d.getSelectValueContext(r))}var s=null;if(null!=a){var p;if(p=null==t.value?o(t.children):t.value+'',s=!1,Array.isArray(a)){for(var l=0;l<a.length;l++)if(''+a[l]===p){s=!0;break}}else s=''+a===p}e._wrapperState={selected:s}},postMountWrapper:function(e){var t=e._currentElement.props;if(null!=t.value){var n=i.getNodeFromInstance(e);n.setAttribute('value',t.value)}},getHostProps:function(e,t){var n=a({selected:void 0,children:void 0},t);null!=e._wrapperState.selected&&(n.selected=e._wrapperState.selected);var r=o(t.children);return r&&(n.children=r),n}}},function(e,t,n){'use strict';function o(){this._rootNodeID&&c.updateWrapper(this)}function a(e){var t=this._currentElement.props,n=d.executeOnChange(t,e);return p.asap(o,this),n}var r=n(2),i=n(3),d=n(57),s=n(4),p=n(11),l=n(0),u=n(1),c={getHostProps:function(e,t){null==t.dangerouslySetInnerHTML?void 0:r('91');var n=i({},t,{value:void 0,defaultValue:void 0,children:''+e._wrapperState.initialValue,onChange:e._wrapperState.onChange});return n},mountWrapper:function(e,t){var n=d.getValue(t),o=n;if(null==n){var i=t.defaultValue,s=t.children;null!=s&&(!1,null==i?void 0:r('92'),Array.isArray(s)&&(1>=s.length?void 0:r('93'),s=s[0]),i=''+s),null==i&&(i=''),o=i}e._wrapperState={initialValue:''+o,listeners:null,onChange:a.bind(e)}},updateWrapper:function(e){var t=e._currentElement.props,n=s.getNodeFromInstance(e),o=d.getValue(t);if(null!=o){var a=''+o;a!==n.value&&(n.value=a),null==t.defaultValue&&(n.defaultValue=a)}null!=t.defaultValue&&(n.defaultValue=t.defaultValue)},postMountWrapper:function(e){var t=s.getNodeFromInstance(e),n=t.textContent;n===e._wrapperState.initialValue&&(t.value=n)}};e.exports=c},function(e,t,n){'use strict';function o(e,t,n){return{type:'INSERT_MARKUP',content:e,fromIndex:null,fromNode:null,toIndex:n,afterNode:t}}function a(e,t,n){return{type:'MOVE_EXISTING',content:null,fromIndex:e._mountIndex,fromNode:g.getHostNode(e),toIndex:n,afterNode:t}}function r(e,t){return{type:'REMOVE_NODE',content:null,fromIndex:e._mountIndex,fromNode:t,toIndex:null,afterNode:null}}function i(e){return{type:'SET_MARKUP',content:e,fromIndex:null,fromNode:null,toIndex:null,afterNode:null}}function d(e){return{type:'TEXT_CONTENT',content:e,fromIndex:null,fromNode:null,toIndex:null,afterNode:null}}function s(e,t){return t&&(e=e||[],e.push(t)),e}function p(e,t){u.processChildrenUpdates(e,t)}var l=n(2),u=n(58),c=n(27),m=n(9),h=n(8),g=n(17),f=n(140),y=n(5),_=n(145),C=n(0);e.exports={Mixin:{_reconcilerInstantiateChildren:function(e,t,n){return f.instantiateChildren(e,t,n)},_reconcilerUpdateChildren:function(e,t,n,o,a,r){var i=0,d;return d=_(t,i),f.updateChildren(e,d,n,o,a,this,this._hostContainerInfo,r,i),d},mountChildren:function(e,t,n){var o=this._reconcilerInstantiateChildren(e,t,n);this._renderedChildren=o;var a=[],r=0;for(var i in o)if(o.hasOwnProperty(i)){var d=o[i];var s=g.mountComponent(d,t,this,this._hostContainerInfo,n,0);d._mountIndex=r++,a.push(s)}return!1,a},updateTextContent:function(e){var t=this._renderedChildren;for(var n in f.unmountChildren(t,!1),t)t.hasOwnProperty(n)&&l('118');var o=[d(e)];p(this,o)},updateMarkup:function(e){var t=this._renderedChildren;for(var n in f.unmountChildren(t,!1),t)t.hasOwnProperty(n)&&l('118');var o=[i(e)];p(this,o)},updateChildren:function(e,t,n){this._updateChildren(e,t,n)},_updateChildren:function(e,t,n){var o=Math.max,a=this._renderedChildren,r={},i=[],d=this._reconcilerUpdateChildren(a,e,i,r,t,n);if(d||a){var l=null,u=0,c=0,m=0,h=null,f;for(f in d)if(d.hasOwnProperty(f)){var y=a&&a[f],_=d[f];y===_?(l=s(l,this.moveChild(y,h,u,c)),c=o(y._mountIndex,c),y._mountIndex=u):(y&&(c=o(y._mountIndex,c)),l=s(l,this._mountChildAtIndex(_,i[m],h,u,t,n)),m++),u++,h=g.getHostNode(_)}for(f in r)r.hasOwnProperty(f)&&(l=s(l,this._unmountChild(a[f],r[f])));l&&p(this,l),this._renderedChildren=d,!1}},unmountChildren:function(e){var t=this._renderedChildren;f.unmountChildren(t,e),this._renderedChildren=null},moveChild:function(e,t,n,o){if(e._mountIndex<o)return a(e,t,n)},createChild:function(e,t,n){return o(n,t,e._mountIndex)},removeChild:function(e,t){return r(e,t)},_mountChildAtIndex:function(e,t,n,o){return e._mountIndex=o,this.createChild(e,n,t)},_unmountChild:function(e,t){var n=this.removeChild(e,t);return e._mountIndex=null,n}}}},function(e,t,n){'use strict';(function(t){function o(e,t,n){var o=e[n]===void 0;!1,null!=t&&o&&(e[n]=r(t,!0))}var a=n(17),r=n(81),i=n(61),d=n(60),s=n(85),p=n(1);e.exports={instantiateChildren:function(e,t,n,a){if(null==e)return null;var r={};return s(e,o,r),r},updateChildren:function(e,t,n,o,i,s,p,l,u){if(t||e){var c,m;for(c in t)if(t.hasOwnProperty(c)){m=e&&e[c];var h=m&&m._currentElement,g=t[c];if(null!=m&&d(h,g))a.receiveComponent(m,g,i,l),t[c]=m;else{m&&(o[c]=a.getHostNode(m),a.unmountComponent(m,!1));var f=r(g,!0);t[c]=f;var y=a.mountComponent(f,i,s,p,l,u);n.push(y)}}for(c in e)e.hasOwnProperty(c)&&!(t&&t.hasOwnProperty(c))&&(m=e[c],o[c]=a.getHostNode(m),a.unmountComponent(m,!1))}},unmountChildren:function(e,t){for(var n in e)if(e.hasOwnProperty(n)){var o=e[n];a.unmountComponent(o,t)}}}}).call(t,n(80))},function(e,t,n){'use strict';function o(){}function a(){}function r(e){return!!(e.prototype&&e.prototype.isReactComponent)}function i(e){return!!(e.prototype&&e.prototype.isPureReactComponent)}function d(e,t,n){if(0===t)return e();g.debugTool.onBeginLifeCycleTimer(t,n);try{return e()}finally{g.debugTool.onEndLifeCycleTimer(t,n)}}var s=n(2),p=n(3),l=n(13),u=n(58),c=n(8),m=n(50),h=n(27),g=n(9),f=n(82),y=n(17);var _=n(14),C=n(0),b=n(59),E=n(60),v=n(1),x={ImpureClass:0,PureClass:1,StatelessFunctional:2};o.prototype.render=function(){var e=h.get(this)._currentElement.type,t=e(this.props,this.context,this.updater);return a(e,t),t};var N=1;e.exports={construct:function(e){this._currentElement=e,this._rootNodeID=0,this._compositeType=null,this._instance=null,this._hostParent=null,this._hostContainerInfo=null,this._updateBatchNumber=null,this._pendingElement=null,this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1,this._renderedNodeType=null,this._renderedComponent=null,this._context=null,this._mountOrder=0,this._topLevelWrapper=null,this._pendingCallbacks=null,this._calledComponentWillUnmount=!1,!1},mountComponent:function(e,t,n,d){var p=this;this._context=d,this._mountOrder=N++,this._hostParent=t,this._hostContainerInfo=n;var u=this._currentElement.props,c=this._processContext(d),m=this._currentElement.type,g=e.getUpdateQueue(),f=r(m),y=this._constructComponent(f,u,c,g),C;f||null!=y&&null!=y.render?i(m)?this._compositeType=x.PureClass:this._compositeType=x.ImpureClass:(C=y,a(m,C),null===y||!1===y||l.isValidElement(y)?void 0:s('105',m.displayName||m.name||'Component'),y=new o(m),this._compositeType=x.StatelessFunctional);y.props=u,y.context=c,y.refs=_,y.updater=g,this._instance=y,h.set(y,this),!1;var b=y.state;void 0===b&&(y.state=b=null),'object'!=typeof b||Array.isArray(b)?s('106',this.getName()||'ReactCompositeComponent'):void 0,this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1;var E;return E=y.unstable_handleError?this.performInitialMountWithErrorHandling(C,t,n,e,d):this.performInitialMount(C,t,n,e,d),y.componentDidMount&&e.getReactMountReady().enqueue(y.componentDidMount,y),E},_constructComponent:function(e,t,n,o){return this._constructComponentWithoutOwner(e,t,n,o)},_constructComponentWithoutOwner:function(e,t,n,o){var a=this._currentElement.type;return e?new a(t,n,o):a(t,n,o)},performInitialMountWithErrorHandling:function(t,n,o,a,r){var i=a.checkpoint(),d;try{d=this.performInitialMount(t,n,o,a,r)}catch(s){a.rollback(i),this._instance.unstable_handleError(s),this._pendingStateQueue&&(this._instance.state=this._processPendingState(this._instance.props,this._instance.context)),i=a.checkpoint(),this._renderedComponent.unmountComponent(!0),a.rollback(i),d=this.performInitialMount(t,n,o,a,r)}return d},performInitialMount:function(e,t,n,o,a){var r=this._instance,i=0;!1,r.componentWillMount&&(r.componentWillMount(),this._pendingStateQueue&&(r.state=this._processPendingState(r.props,r.context))),e===void 0&&(e=this._renderValidatedComponent());var d=f.getType(e);this._renderedNodeType=d;var s=this._instantiateReactComponent(e,d!==f.EMPTY);this._renderedComponent=s;var p=y.mountComponent(s,o,t,n,this._processChildContext(a),i);return p},getHostNode:function(){return y.getHostNode(this._renderedComponent)},unmountComponent:function(e){if(this._renderedComponent){var t=this._instance;if(t.componentWillUnmount&&!t._calledComponentWillUnmount)if(t._calledComponentWillUnmount=!0,e){var n=this.getName()+'.componentWillUnmount()';m.invokeGuardedCallback(n,t.componentWillUnmount.bind(t))}else t.componentWillUnmount();this._renderedComponent&&(y.unmountComponent(this._renderedComponent,e),this._renderedNodeType=null,this._renderedComponent=null,this._instance=null),this._pendingStateQueue=null,this._pendingReplaceState=!1,this._pendingForceUpdate=!1,this._pendingCallbacks=null,this._pendingElement=null,this._context=null,this._rootNodeID=0,this._topLevelWrapper=null,h.remove(t)}},_maskContext:function(e){var t=this._currentElement.type,n=t.contextTypes;if(!n)return _;var o={};for(var a in n)o[a]=e[a];return o},_processContext:function(e){var t=this._maskContext(e);return t},_processChildContext:function(e){var t=this._currentElement.type,n=this._instance,o;if(n.getChildContext&&(o=n.getChildContext()),o){for(var a in'object'==typeof t.childContextTypes?void 0:s('107',this.getName()||'ReactCompositeComponent'),!1,o)a in t.childContextTypes?void 0:s('108',this.getName()||'ReactCompositeComponent',a);return p({},e,o)}return e},_checkContextTypes:function(){},receiveComponent:function(e,t,n){var o=this._currentElement,a=this._context;this._pendingElement=null,this.updateComponent(t,o,e,a,n)},performUpdateIfNecessary:function(e){null==this._pendingElement?null!==this._pendingStateQueue||this._pendingForceUpdate?this.updateComponent(e,this._currentElement,this._currentElement,this._context,this._context):this._updateBatchNumber=null:y.receiveComponent(this,this._pendingElement,e,this._context)},updateComponent:function(e,t,n,o,a){var r=this._instance;null!=r?void 0:s('136',this.getName()||'ReactCompositeComponent');var i=!1,d;this._context===a?d=r.context:(d=this._processContext(a),i=!0);var p=t.props,l=n.props;t!==n&&(i=!0),i&&r.componentWillReceiveProps&&r.componentWillReceiveProps(l,d);var u=this._processPendingState(l,d),c=!0;this._pendingForceUpdate||(r.shouldComponentUpdate?c=r.shouldComponentUpdate(l,u,d):this._compositeType===x.PureClass&&(c=!b(p,l)||!b(r.state,u))),!1,this._updateBatchNumber=null,c?(this._pendingForceUpdate=!1,this._performComponentUpdate(n,l,u,d,e,a)):(this._currentElement=n,this._context=a,r.props=l,r.state=u,r.context=d)},_processPendingState:function(e,t){var n=this._instance,o=this._pendingStateQueue,a=this._pendingReplaceState;if(this._pendingReplaceState=!1,this._pendingStateQueue=null,!o)return n.state;if(a&&1===o.length)return o[0];for(var r=p({},a?o[0]:n.state),d=a?1:0,i;d<o.length;d++)i=o[d],p(r,'function'==typeof i?i.call(n,r,e,t):i);return r},_performComponentUpdate:function(e,t,n,o,a,r){var i=this,d=this._instance,s=!!d.componentDidUpdate,p,l,u;s&&(p=d.props,l=d.state,u=d.context),d.componentWillUpdate&&d.componentWillUpdate(t,n,o),this._currentElement=e,this._context=r,d.props=t,d.state=n,d.context=o,this._updateRenderedComponent(a,r),s&&a.getReactMountReady().enqueue(d.componentDidUpdate.bind(d,p,l,u),d)},_updateRenderedComponent:function(e,t){var n=this._renderedComponent,o=n._currentElement,a=this._renderValidatedComponent();if(!1,E(o,a))y.receiveComponent(n,a,e,this._processChildContext(t));else{var r=y.getHostNode(n);y.unmountComponent(n,!1);var i=f.getType(a);this._renderedNodeType=i;var d=this._instantiateReactComponent(a,i!==f.EMPTY);this._renderedComponent=d;var s=y.mountComponent(d,e,this._hostParent,this._hostContainerInfo,this._processChildContext(t),0);this._replaceNodeWithMarkup(r,s,n)}},_replaceNodeWithMarkup:function(e,t,n){u.replaceNodeWithMarkup(e,t,n)},_renderValidatedComponentWithoutOwnerOrContext:function(){var e=this._instance,t;return t=e.render(),!1,t},_renderValidatedComponent:function(){var e;if(this._compositeType!==x.StatelessFunctional){c.current=this;try{e=this._renderValidatedComponentWithoutOwnerOrContext()}finally{c.current=null}}else e=this._renderValidatedComponentWithoutOwnerOrContext();return null===e||!1===e||l.isValidElement(e)?void 0:s('109',this.getName()||'ReactCompositeComponent'),e},attachRef:function(e,t){var n=this.getPublicInstance();null!=n?void 0:s('110');var o=t.getPublicInstance();var a=n.refs===_?n.refs={}:n.refs;a[e]=o},detachRef:function(e){var t=this.getPublicInstance().refs;delete t[e]},getName:function(){var e=this._currentElement.type,t=this._instance&&this._instance.constructor;return e.displayName||t&&t.displayName||e.name||t&&t.name||null},getPublicInstance:function(){var e=this._instance;return this._compositeType===x.StatelessFunctional?null:e},_instantiateReactComponent:null}},function(e){'use strict';var t=1;e.exports=function(){return t++}},function(e){'use strict';var t='function'==typeof Symbol&&Symbol['for']&&Symbol['for']('react.element')||60103;e.exports=t},function(e){'use strict';var t='function'==typeof Symbol&&Symbol.iterator;e.exports=function(e){var n=e&&(t&&e[t]||e['@@iterator']);if('function'==typeof n)return n}},function(e,t,n){'use strict';(function(t){function o(e,t,n){if(e&&'object'==typeof e){var o=e,a=o[n]===void 0;!1,a&&null!=t&&(o[n]=t)}}var a=n(61),r=n(85),i=n(1);'undefined'!=typeof t&&{NODE_ENV:'production'}&&!1,e.exports=function(e,t){if(null==e)return e;var n={};return r(e,o,n),n}}).call(t,n(80))},function(e,t,n){'use strict';function o(e){this.reinitializeTransaction(),this.renderToStaticMarkup=e,this.useCreateElement=!1,this.updateQueue=new s(this)}var a=n(3),r=n(15),i=n(29),d=n(9),s=n(147),p=[];var l={enqueue:function(){}};a(o.prototype,i,{getTransactionWrappers:function(){return p},getReactMountReady:function(){return l},getUpdateQueue:function(){return this.updateQueue},destructor:function(){},checkpoint:function(){},rollback:function(){}}),r.addPoolingTo(o),e.exports=o},function(e,t,n){'use strict';function o(e,t){if(!(e instanceof t))throw new TypeError('Cannot call a class as a function')}function a(){}var r=n(62),i=n(1),d=function(){function e(t){o(this,e),this.transaction=t}return e.prototype.isMounted=function(){return!1},e.prototype.enqueueCallback=function(e,t,n){this.transaction.isInTransaction()&&r.enqueueCallback(e,t,n)},e.prototype.enqueueForceUpdate=function(e){this.transaction.isInTransaction()?r.enqueueForceUpdate(e):a(e,'forceUpdate')},e.prototype.enqueueReplaceState=function(e,t){this.transaction.isInTransaction()?r.enqueueReplaceState(e,t):a(e,'replaceState')},e.prototype.enqueueSetState=function(e,t){this.transaction.isInTransaction()?r.enqueueSetState(e,t):a(e,'setState')},e}();e.exports=d},function(e,t,n){'use strict';var o=n(3),a=n(18),r=n(4),i=function(){this._currentElement=null,this._hostNode=null,this._hostParent=null,this._hostContainerInfo=null,this._domID=0};o(i.prototype,{mountComponent:function(e,t,n){var o=n._idCounter++;this._domID=o,this._hostParent=t,this._hostContainerInfo=n;var i=' react-empty: '+this._domID+' ';if(e.useCreateElement){var d=n._ownerDocument,s=d.createComment(i);return r.precacheNode(this,s),a(s)}return e.renderToStaticMarkup?'':'<!--'+i+'-->'},receiveComponent:function(){},getHostNode:function(){return r.getNodeFromInstance(this)},unmountComponent:function(){r.uncacheNode(this)}}),e.exports=i},function(e,t,n){'use strict';function o(e,t){'_hostNode'in e?void 0:a('33'),'_hostNode'in t?void 0:a('33');for(var n=0,o=e;o;o=o._hostParent)n++;for(var r=0,i=t;i;i=i._hostParent)r++;for(;0<n-r;)e=e._hostParent,n--;for(;0<r-n;)t=t._hostParent,r--;for(var d=n;d--;){if(e===t)return e;e=e._hostParent,t=t._hostParent}return null}var a=n(2),r=n(0);e.exports={isAncestor:function(e,t){for(('_hostNode'in e)?void 0:a('35'),('_hostNode'in t)?void 0:a('35');t;){if(t===e)return!0;t=t._hostParent}return!1},getLowestCommonAncestor:o,getParentInstance:function(e){return'_hostNode'in e?void 0:a('36'),e._hostParent},traverseTwoPhase:function(e,t,n){for(var o=[];e;)o.push(e),e=e._hostParent;var a;for(a=o.length;0<a--;)t(o[a],'captured',n);for(a=0;a<o.length;a++)t(o[a],'bubbled',n)},traverseEnterLeave:function(e,t,n,a,r){for(var d=e&&t?o(e,t):null,s=[];e&&e!==d;)s.push(e),e=e._hostParent;for(var p=[];t&&t!==d;)p.push(t),t=t._hostParent;var l;for(l=0;l<s.length;l++)n(s[l],'bubbled',a);for(l=p.length;0<l--;)n(p[l],'captured',r)}}},function(e,t,n){'use strict';var o=n(2),a=n(3),r=n(54),i=n(18),d=n(4),s=n(32),p=n(0),l=n(63),u=function(e){this._currentElement=e,this._stringText=''+e,this._hostNode=null,this._hostParent=null,this._domID=0,this._mountIndex=0,this._closingComment=null,this._commentNodes=null};a(u.prototype,{mountComponent:function(e,t,n){var o=n._idCounter++,a=' react-text: '+o+' ',r=' /react-text ';if(this._domID=o,this._hostParent=t,e.useCreateElement){var p=n._ownerDocument,l=p.createComment(a),u=p.createComment(r),c=i(p.createDocumentFragment());return i.queueChild(c,i(l)),this._stringText&&i.queueChild(c,i(p.createTextNode(this._stringText))),i.queueChild(c,i(u)),d.precacheNode(this,l),this._closingComment=u,c}var m=s(this._stringText);return e.renderToStaticMarkup?m:'<!--'+a+'-->'+m+'<!--'+r+'-->'},receiveComponent:function(e){if(e!==this._currentElement){this._currentElement=e;var t=''+e;if(t!==this._stringText){this._stringText=t;var n=this.getHostNode();r.replaceDelimitedText(n[0],n[1],t)}}},getHostNode:function(){var e=this._commentNodes;if(e)return e;if(!this._closingComment)for(var t=d.getNodeFromInstance(this),n=t.nextSibling;;){if(null==n?o('67',this._domID):void 0,8===n.nodeType&&' /react-text '===n.nodeValue){this._closingComment=n;break}n=n.nextSibling}return e=[this._hostNode,this._closingComment],this._commentNodes=e,e},unmountComponent:function(){this._closingComment=null,this._commentNodes=null,d.uncacheNode(this)}}),e.exports=u},function(e,t,n){'use strict';function o(){this.reinitializeTransaction()}var a=n(3),r=n(11),i=n(29),d=n(5),s={initialize:d,close:r.flushBatchedUpdates.bind(r)},p=[s,{initialize:d,close:function(){u.isBatchingUpdates=!1}}];a(o.prototype,i,{getTransactionWrappers:function(){return p}});var l=new o,u={isBatchingUpdates:!1,batchedUpdates:function(t,n,o,a,r,i){var e=u.isBatchingUpdates;return u.isBatchingUpdates=!0,e?t(n,o,a,r,i):l.perform(t,null,n,o,a,r,i)}};e.exports=u},function(e,t,n){'use strict';function o(e){for(;e._hostParent;)e=e._hostParent;var t=u.getNodeFromInstance(e),n=t.parentNode;return u.getClosestInstanceFromNode(n)}function a(e,t){this.topLevelType=e,this.nativeEvent=t,this.ancestors=[]}function r(e){var t=m(e.nativeEvent),n=u.getClosestInstanceFromNode(t),a=n;do e.ancestors.push(a),a=a&&o(a);while(a);for(var r=0;r<e.ancestors.length;r++)n=e.ancestors[r],g._handleTopLevel(e.topLevelType,n,e.nativeEvent,m(e.nativeEvent))}function i(e){var t=h(window);e(t)}var d=n(3),s=n(87),p=n(6),l=n(15),u=n(4),c=n(11),m=n(51),h=n(153);d(a.prototype,{destructor:function(){this.topLevelType=null,this.nativeEvent=null,this.ancestors.length=0}}),l.addPoolingTo(a,l.twoArgumentPooler);var g={_enabled:!0,_handleTopLevel:null,WINDOW_HANDLE:p.canUseDOM?window:null,setHandleTopLevel:function(e){g._handleTopLevel=e},setEnabled:function(e){g._enabled=!!e},isEnabled:function(){return g._enabled},trapBubbledEvent:function(e,t,n){return n?s.listen(n,t,g.dispatchEvent.bind(null,e)):null},trapCapturedEvent:function(e,t,n){return n?s.capture(n,t,g.dispatchEvent.bind(null,e)):null},monitorScrollValue:function(e){var t=i.bind(null,e);s.listen(window,'scroll',t)},dispatchEvent:function(e,t){if(g._enabled){var n=a.getPooled(e,t);try{c.batchedUpdates(r,n)}finally{a.release(n)}}}};e.exports=g},function(e){'use strict';e.exports=function(e){return e.Window&&e instanceof e.Window?{x:e.pageXOffset||e.document.documentElement.scrollLeft,y:e.pageYOffset||e.document.documentElement.scrollTop}:{x:e.scrollLeft,y:e.scrollTop}}},function(e,t,n){'use strict';var o=n(16),a=n(25),r=n(49),i=n(58),d=n(83),s=n(33),p=n(84),l=n(11),u={Component:i.injection,DOMProperty:o.injection,EmptyComponent:d.injection,EventPluginHub:a.injection,EventPluginUtils:r.injection,EventEmitter:s.injection,HostComponent:p.injection,Updates:l.injection};e.exports=u},function(e,t,n){'use strict';function o(e){this.reinitializeTransaction(),this.renderToStaticMarkup=!1,this.reactMountReady=r.getPooled(null),this.useCreateElement=e}var a=n(3),r=n(70),i=n(15),d=n(33),s=n(88),p=n(9),l=n(29),u=n(62),c={initialize:s.getSelectionInformation,close:s.restoreSelection},m=[c,{initialize:function(){var e=d.isEnabled();return d.setEnabled(!1),e},close:function(e){d.setEnabled(e)}},{initialize:function(){this.reactMountReady.reset()},close:function(){this.reactMountReady.notifyAll()}}];a(o.prototype,l,{getTransactionWrappers:function(){return m},getReactMountReady:function(){return this.reactMountReady},getUpdateQueue:function(){return u},checkpoint:function(){return this.reactMountReady.checkpoint()},rollback:function(e){this.reactMountReady.rollback(e)},destructor:function(){r.release(this.reactMountReady),this.reactMountReady=null}}),i.addPoolingTo(o),e.exports=o},function(e,t,n){'use strict';function o(e,t,n,o){return e===n&&t===o}var a=Math.min,r=n(6),i=n(157),d=n(69),s=r.canUseDOM&&'selection'in document&&!('getSelection'in window),p={getOffsets:s?function(e){var t=document.selection,n=t.createRange(),o=n.text.length,a=n.duplicate();a.moveToElementText(e),a.setEndPoint('EndToStart',n);var r=a.text.length;return{start:r,end:r+o}}:function(e){var t=window.getSelection&&window.getSelection();if(!t||0===t.rangeCount)return null;var n=t.anchorNode,a=t.anchorOffset,r=t.focusNode,i=t.focusOffset,d=t.getRangeAt(0);try{d.startContainer.nodeType,d.endContainer.nodeType}catch(t){return null}var s=o(t.anchorNode,t.anchorOffset,t.focusNode,t.focusOffset),p=s?0:d.toString().length,l=d.cloneRange();l.selectNodeContents(e),l.setEnd(d.startContainer,d.startOffset);var u=o(l.startContainer,l.startOffset,l.endContainer,l.endOffset),c=u?0:l.toString().length,m=c+p,h=document.createRange();h.setStart(n,a),h.setEnd(r,i);var g=h.collapsed;return{start:g?m:c,end:g?c:m}},setOffsets:s?function(e,t){var n=document.selection.createRange().duplicate(),o,a;void 0===t.end?(o=t.start,a=o):t.start>t.end?(o=t.end,a=t.start):(o=t.start,a=t.end),n.moveToElementText(e),n.moveStart('character',o),n.setEndPoint('EndToStart',n),n.moveEnd('character',a-o),n.select()}:function(e,t){if(window.getSelection){var n=window.getSelection(),o=e[d()].length,r=a(t.start,o),s=void 0===t.end?r:a(t.end,o);if(!n.extend&&r>s){var p=s;s=r,r=p}var l=i(e,r),u=i(e,s);if(l&&u){var c=document.createRange();c.setStart(l.node,l.offset),n.removeAllRanges(),r>s?(n.addRange(c),n.extend(u.node,u.offset)):(c.setEnd(u.node,u.offset),n.addRange(c))}}}};e.exports=p},function(e){'use strict';function t(e){for(;e&&e.firstChild;)e=e.firstChild;return e}function n(e){for(;e;){if(e.nextSibling)return e.nextSibling;e=e.parentNode}}e.exports=function(e,o){for(var a=t(e),r=0,i=0;a;){if(3===a.nodeType){if(i=r+a.textContent.length,r<=o&&i>=o)return{node:a,offset:o-r};r=i}a=t(n(a))}}},function(e,t,n){'use strict';function o(e,t){return e&&t&&(e===t||!a(e)&&(a(t)?o(e,t.parentNode):'contains'in e?e.contains(t):!!e.compareDocumentPosition&&!!(16&e.compareDocumentPosition(t))))}var a=n(159);e.exports=o},function(e,t,n){'use strict';var o=n(160);e.exports=function(e){return o(e)&&3==e.nodeType}},function(e){'use strict';e.exports=function(e){var t=e?e.ownerDocument||e:document,n=t.defaultView||window;return!!(e&&('function'==typeof n.Node?e instanceof n.Node:'object'==typeof e&&'number'==typeof e.nodeType&&'string'==typeof e.nodeName))}},function(e){'use strict';var t={xlink:'http://www.w3.org/1999/xlink',xml:'http://www.w3.org/XML/1998/namespace'},n={accentHeight:'accent-height',accumulate:0,additive:0,alignmentBaseline:'alignment-baseline',allowReorder:'allowReorder',alphabetic:0,amplitude:0,arabicForm:'arabic-form',ascent:0,attributeName:'attributeName',attributeType:'attributeType',autoReverse:'autoReverse',azimuth:0,baseFrequency:'baseFrequency',baseProfile:'baseProfile',baselineShift:'baseline-shift',bbox:0,begin:0,bias:0,by:0,calcMode:'calcMode',capHeight:'cap-height',clip:0,clipPath:'clip-path',clipRule:'clip-rule',clipPathUnits:'clipPathUnits',colorInterpolation:'color-interpolation',colorInterpolationFilters:'color-interpolation-filters',colorProfile:'color-profile',colorRendering:'color-rendering',contentScriptType:'contentScriptType',contentStyleType:'contentStyleType',cursor:0,cx:0,cy:0,d:0,decelerate:0,descent:0,diffuseConstant:'diffuseConstant',direction:0,display:0,divisor:0,dominantBaseline:'dominant-baseline',dur:0,dx:0,dy:0,edgeMode:'edgeMode',elevation:0,enableBackground:'enable-background',end:0,exponent:0,externalResourcesRequired:'externalResourcesRequired',fill:0,fillOpacity:'fill-opacity',fillRule:'fill-rule',filter:0,filterRes:'filterRes',filterUnits:'filterUnits',floodColor:'flood-color',floodOpacity:'flood-opacity',focusable:0,fontFamily:'font-family',fontSize:'font-size',fontSizeAdjust:'font-size-adjust',fontStretch:'font-stretch',fontStyle:'font-style',fontVariant:'font-variant',fontWeight:'font-weight',format:0,from:0,fx:0,fy:0,g1:0,g2:0,glyphName:'glyph-name',glyphOrientationHorizontal:'glyph-orientation-horizontal',glyphOrientationVertical:'glyph-orientation-vertical',glyphRef:'glyphRef',gradientTransform:'gradientTransform',gradientUnits:'gradientUnits',hanging:0,horizAdvX:'horiz-adv-x',horizOriginX:'horiz-origin-x',ideographic:0,imageRendering:'image-rendering',in:0,in2:0,intercept:0,k:0,k1:0,k2:0,k3:0,k4:0,kernelMatrix:'kernelMatrix',kernelUnitLength:'kernelUnitLength',kerning:0,keyPoints:'keyPoints',keySplines:'keySplines',keyTimes:'keyTimes',lengthAdjust:'lengthAdjust',letterSpacing:'letter-spacing',lightingColor:'lighting-color',limitingConeAngle:'limitingConeAngle',local:0,markerEnd:'marker-end',markerMid:'marker-mid',markerStart:'marker-start',markerHeight:'markerHeight',markerUnits:'markerUnits',markerWidth:'markerWidth',mask:0,maskContentUnits:'maskContentUnits',maskUnits:'maskUnits',mathematical:0,mode:0,numOctaves:'numOctaves',offset:0,opacity:0,operator:0,order:0,orient:0,orientation:0,origin:0,overflow:0,overlinePosition:'overline-position',overlineThickness:'overline-thickness',paintOrder:'paint-order',panose1:'panose-1',pathLength:'pathLength',patternContentUnits:'patternContentUnits',patternTransform:'patternTransform',patternUnits:'patternUnits',pointerEvents:'pointer-events',points:0,pointsAtX:'pointsAtX',pointsAtY:'pointsAtY',pointsAtZ:'pointsAtZ',preserveAlpha:'preserveAlpha',preserveAspectRatio:'preserveAspectRatio',primitiveUnits:'primitiveUnits',r:0,radius:0,refX:'refX',refY:'refY',renderingIntent:'rendering-intent',repeatCount:'repeatCount',repeatDur:'repeatDur',requiredExtensions:'requiredExtensions',requiredFeatures:'requiredFeatures',restart:0,result:0,rotate:0,rx:0,ry:0,scale:0,seed:0,shapeRendering:'shape-rendering',slope:0,spacing:0,specularConstant:'specularConstant',specularExponent:'specularExponent',speed:0,spreadMethod:'spreadMethod',startOffset:'startOffset',stdDeviation:'stdDeviation',stemh:0,stemv:0,stitchTiles:'stitchTiles',stopColor:'stop-color',stopOpacity:'stop-opacity',strikethroughPosition:'strikethrough-position',strikethroughThickness:'strikethrough-thickness',string:0,stroke:0,strokeDasharray:'stroke-dasharray',strokeDashoffset:'stroke-dashoffset',strokeLinecap:'stroke-linecap',strokeLinejoin:'stroke-linejoin',strokeMiterlimit:'stroke-miterlimit',strokeOpacity:'stroke-opacity',strokeWidth:'stroke-width',surfaceScale:'surfaceScale',systemLanguage:'systemLanguage',tableValues:'tableValues',targetX:'targetX',targetY:'targetY',textAnchor:'text-anchor',textDecoration:'text-decoration',textRendering:'text-rendering',textLength:'textLength',to:0,transform:0,u1:0,u2:0,underlinePosition:'underline-position',underlineThickness:'underline-thickness',unicode:0,unicodeBidi:'unicode-bidi',unicodeRange:'unicode-range',unitsPerEm:'units-per-em',vAlphabetic:'v-alphabetic',vHanging:'v-hanging',vIdeographic:'v-ideographic',vMathematical:'v-mathematical',values:0,vectorEffect:'vector-effect',version:0,vertAdvY:'vert-adv-y',vertOriginX:'vert-origin-x',vertOriginY:'vert-origin-y',viewBox:'viewBox',viewTarget:'viewTarget',visibility:0,widths:0,wordSpacing:'word-spacing',writingMode:'writing-mode',x:0,xHeight:'x-height',x1:0,x2:0,xChannelSelector:'xChannelSelector',xlinkActuate:'xlink:actuate',xlinkArcrole:'xlink:arcrole',xlinkHref:'xlink:href',xlinkRole:'xlink:role',xlinkShow:'xlink:show',xlinkTitle:'xlink:title',xlinkType:'xlink:type',xmlBase:'xml:base',xmlns:0,xmlnsXlink:'xmlns:xlink',xmlLang:'xml:lang',xmlSpace:'xml:space',y:0,y1:0,y2:0,yChannelSelector:'yChannelSelector',z:0,zoomAndPan:'zoomAndPan'},o={Properties:{},DOMAttributeNamespaces:{xlinkActuate:t.xlink,xlinkArcrole:t.xlink,xlinkHref:t.xlink,xlinkRole:t.xlink,xlinkShow:t.xlink,xlinkTitle:t.xlink,xlinkType:t.xlink,xmlBase:t.xml,xmlLang:t.xml,xmlSpace:t.xml},DOMAttributeNames:{}};Object.keys(n).forEach(function(e){o.Properties[e]=0,n[e]&&(o.DOMAttributeNames[e]=n[e])}),e.exports=o},function(e,t,n){'use strict';function o(e){if('selectionStart'in e&&s.hasSelectionCapabilities(e))return{start:e.selectionStart,end:e.selectionEnd};if(window.getSelection){var t=window.getSelection();return{anchorNode:t.anchorNode,anchorOffset:t.anchorOffset,focusNode:t.focusNode,focusOffset:t.focusOffset}}if(document.selection){var n=document.selection.createRange();return{parentElement:n.parentElement(),text:n.text,top:n.boundingTop,left:n.boundingLeft}}}function a(e,t){if(_||null==g||g!==l())return null;var n=o(g);if(!y||!c(y,n)){y=n;var a=p.getPooled(h.select,f,e,t);return a.type='select',a.target=g,r.accumulateTwoPhaseDispatches(a),a}return null}var r=n(24),i=n(6),d=n(4),s=n(88),p=n(12),l=n(89),u=n(73),c=n(59),m=i.canUseDOM&&'documentMode'in document&&11>=document.documentMode,h={select:{phasedRegistrationNames:{bubbled:'onSelect',captured:'onSelectCapture'},dependencies:['topBlur','topContextMenu','topFocus','topKeyDown','topKeyUp','topMouseDown','topMouseUp','topSelectionChange']}},g=null,f=null,y=null,_=!1,C=!1;e.exports={eventTypes:h,extractEvents:function(e,t,n,o){if(!C)return null;var r=t?d.getNodeFromInstance(t):window;switch(e){case'topFocus':(u(r)||'true'===r.contentEditable)&&(g=r,f=t,y=null);break;case'topBlur':g=null,f=null,y=null;break;case'topMouseDown':_=!0;break;case'topContextMenu':case'topMouseUp':return _=!1,a(n,o);case'topSelectionChange':if(m)break;case'topKeyDown':case'topKeyUp':return a(n,o);}return null},didPutListener:function(e,t){'onSelect'===t&&(C=!0)}}},function(e,t,n){'use strict';function o(e){return'.'+e._rootNodeID}function a(e){return'button'===e||'input'===e||'select'===e||'textarea'===e}var r=n(2),i=n(87),d=n(24),s=n(4),p=n(164),l=n(165),u=n(12),c=n(166),m=n(167),h=n(30),g=n(169),f=n(170),y=n(171),_=n(26),C=n(172),b=n(5),E=n(64),v=n(0),x={},N={};['abort','animationEnd','animationIteration','animationStart','blur','canPlay','canPlayThrough','click','contextMenu','copy','cut','doubleClick','drag','dragEnd','dragEnter','dragExit','dragLeave','dragOver','dragStart','drop','durationChange','emptied','encrypted','ended','error','focus','input','invalid','keyDown','keyPress','keyUp','load','loadedData','loadedMetadata','loadStart','mouseDown','mouseMove','mouseOut','mouseOver','mouseUp','paste','pause','play','playing','progress','rateChange','reset','scroll','seeked','seeking','stalled','submit','suspend','timeUpdate','touchCancel','touchEnd','touchMove','touchStart','transitionEnd','volumeChange','waiting','wheel'].forEach(function(e){var t=e[0].toUpperCase()+e.slice(1),n='on'+t,o='top'+t,a={phasedRegistrationNames:{bubbled:n,captured:n+'Capture'},dependencies:[o]};x[e]=a,N[o]=a});var T={};e.exports={eventTypes:x,extractEvents:function(e,t,n,o){var a=N[e];if(!a)return null;var i;switch(e){case'topAbort':case'topCanPlay':case'topCanPlayThrough':case'topDurationChange':case'topEmptied':case'topEncrypted':case'topEnded':case'topError':case'topInput':case'topInvalid':case'topLoad':case'topLoadedData':case'topLoadedMetadata':case'topLoadStart':case'topPause':case'topPlay':case'topPlaying':case'topProgress':case'topRateChange':case'topReset':case'topSeeked':case'topSeeking':case'topStalled':case'topSubmit':case'topSuspend':case'topTimeUpdate':case'topVolumeChange':case'topWaiting':i=u;break;case'topKeyPress':if(0===E(n))return null;case'topKeyDown':case'topKeyUp':i=m;break;case'topBlur':case'topFocus':i=c;break;case'topClick':if(2===n.button)return null;case'topDoubleClick':case'topMouseDown':case'topMouseMove':case'topMouseUp':case'topMouseOut':case'topMouseOver':case'topContextMenu':i=h;break;case'topDrag':case'topDragEnd':case'topDragEnter':case'topDragExit':case'topDragLeave':case'topDragOver':case'topDragStart':case'topDrop':i=g;break;case'topTouchCancel':case'topTouchEnd':case'topTouchMove':case'topTouchStart':i=f;break;case'topAnimationEnd':case'topAnimationIteration':case'topAnimationStart':i=p;break;case'topTransitionEnd':i=y;break;case'topScroll':i=_;break;case'topWheel':i=C;break;case'topCopy':case'topCut':case'topPaste':i=l;}i?void 0:r('86',e);var s=i.getPooled(a,t,n,o);return d.accumulateTwoPhaseDispatches(s),s},didPutListener:function(e,t){if('onClick'===t&&!a(e._tag)){var n=o(e),r=s.getNodeFromInstance(e);T[n]||(T[n]=i.listen(r,'click',b))}},willDeleteListener:function(e,t){if('onClick'===t&&!a(e._tag)){var n=o(e);T[n].remove(),delete T[n]}}}},function(e,t,n){'use strict';function o(e,t,n,o){return a.call(this,e,t,n,o)}var a=n(12);a.augmentClass(o,{animationName:null,elapsedTime:null,pseudoElement:null}),e.exports=o},function(e,t,n){'use strict';function o(e,t,n,o){return a.call(this,e,t,n,o)}var a=n(12);a.augmentClass(o,{clipboardData:function(e){return'clipboardData'in e?e.clipboardData:window.clipboardData}}),e.exports=o},function(e,t,n){'use strict';function o(e,t,n,o){return a.call(this,e,t,n,o)}var a=n(26);a.augmentClass(o,{relatedTarget:null}),e.exports=o},function(e,t,n){'use strict';function o(e,t,n,o){return a.call(this,e,t,n,o)}var a=n(26),r=n(64),i=n(168),d=n(53);a.augmentClass(o,{key:i,location:null,ctrlKey:null,shiftKey:null,altKey:null,metaKey:null,repeat:null,locale:null,getModifierState:d,charCode:function(e){return'keypress'===e.type?r(e):0},keyCode:function(e){return'keydown'===e.type||'keyup'===e.type?e.keyCode:0},which:function(e){return'keypress'===e.type?r(e):'keydown'===e.type||'keyup'===e.type?e.keyCode:0}}),e.exports=o},function(e,t,n){'use strict';var o=n(64),a={Esc:'Escape',Spacebar:' ',Left:'ArrowLeft',Up:'ArrowUp',Right:'ArrowRight',Down:'ArrowDown',Del:'Delete',Win:'OS',Menu:'ContextMenu',Apps:'ContextMenu',Scroll:'ScrollLock',MozPrintableKey:'Unidentified'},r={8:'Backspace',9:'Tab',12:'Clear',13:'Enter',16:'Shift',17:'Control',18:'Alt',19:'Pause',20:'CapsLock',27:'Escape',32:' ',33:'PageUp',34:'PageDown',35:'End',36:'Home',37:'ArrowLeft',38:'ArrowUp',39:'ArrowRight',40:'ArrowDown',45:'Insert',46:'Delete',112:'F1',113:'F2',114:'F3',115:'F4',116:'F5',117:'F6',118:'F7',119:'F8',120:'F9',121:'F10',122:'F11',123:'F12',144:'NumLock',145:'ScrollLock',224:'Meta'};e.exports=function(e){if(e.key){var t=a[e.key]||e.key;if('Unidentified'!==t)return t}if('keypress'===e.type){var n=o(e);return 13===n?'Enter':String.fromCharCode(n)}return'keydown'===e.type||'keyup'===e.type?r[e.keyCode]||'Unidentified':''}},function(e,t,n){'use strict';function o(e,t,n,o){return a.call(this,e,t,n,o)}var a=n(30);a.augmentClass(o,{dataTransfer:null}),e.exports=o},function(e,t,n){'use strict';function o(e,t,n,o){return a.call(this,e,t,n,o)}var a=n(26),r=n(53);a.augmentClass(o,{touches:null,targetTouches:null,changedTouches:null,altKey:null,metaKey:null,ctrlKey:null,shiftKey:null,getModifierState:r}),e.exports=o},function(e,t,n){'use strict';function o(e,t,n,o){return a.call(this,e,t,n,o)}var a=n(12);a.augmentClass(o,{propertyName:null,elapsedTime:null,pseudoElement:null}),e.exports=o},function(e,t,n){'use strict';function o(e,t,n,o){return a.call(this,e,t,n,o)}var a=n(30);a.augmentClass(o,{deltaX:function(e){return'deltaX'in e?e.deltaX:'wheelDeltaX'in e?-e.wheelDeltaX:0},deltaY:function(e){return'deltaY'in e?e.deltaY:'wheelDeltaY'in e?-e.wheelDeltaY:'wheelDelta'in e?-e.wheelDelta:0},deltaZ:null,deltaMode:null}),e.exports=o},function(e,t,n){'use strict';var o=n(63);e.exports=function(e,t){var n={_topLevelWrapper:e,_idCounter:1,_ownerDocument:t?t.nodeType===9?t:t.ownerDocument:null,_node:t,_tag:t?t.nodeName.toLowerCase():null,_namespaceURI:t?t.namespaceURI:null};return!1,n}},function(e){'use strict';e.exports={useCreateElement:!0,useFiber:!1}},function(e,t,n){'use strict';var o=n(176),a=/\/?>/,r=/^<\!\-\-/,i={CHECKSUM_ATTR_NAME:'data-react-checksum',addChecksumToMarkup:function(e){var t=o(e);return r.test(e)?e:e.replace(a,' '+i.CHECKSUM_ATTR_NAME+'="'+t+'"$&')},canReuseMarkup:function(e,t){var n=t.getAttribute(i.CHECKSUM_ATTR_NAME);n=n&&parseInt(n,10);var a=o(e);return a===n}};e.exports=i},function(e){'use strict';var t=65521;e.exports=function(e){for(var o=1,a=0,r=0,i=e.length,d=-4&i;r<d;){for(var s=Math.min(r+4096,d);r<s;r+=4)a+=(o+=e.charCodeAt(r))+(o+=e.charCodeAt(r+1))+(o+=e.charCodeAt(r+2))+(o+=e.charCodeAt(r+3));o%=t,a%=t}for(;r<i;r++)a+=o+=e.charCodeAt(r);return o%=t,a%=t,o|a<<16}},function(e){'use strict';e.exports='15.6.1'},function(e,t,n){'use strict';var o=n(2),a=n(8),r=n(4),i=n(27),d=n(91),s=n(0),p=n(1);e.exports=function(e){if(null==e)return null;if(1===e.nodeType)return e;var t=i.get(e);return t?(t=d(t),t?r.getNodeFromInstance(t):null):void('function'==typeof e.render?o('44'):o('45',Object.keys(e)))}},function(e,t,n){'use strict';var o=n(90);e.exports=o.renderSubtreeIntoContainer}]);
PK
!<�vg���0chrome/toolkit/res/normandy/vendor/classnames.js/* eslint-disable */this.classnames=function(a){function b(d){if(c[d])return c[d].exports;var e=c[d]={i:d,l:!1,exports:{}};return a[d].call(e.exports,e,e.exports,b),e.l=!0,e.exports}var c={};return b.m=a,b.c=c,b.d=function(a,c,d){b.o(a,c)||Object.defineProperty(a,c,{configurable:!1,enumerable:!0,get:d})},b.n=function(a){var c=a&&a.__esModule?function(){return a['default']}:function(){return a};return b.d(c,'a',c),c},b.o=function(a,b){return Object.prototype.hasOwnProperty.call(a,b)},b.p='',b(b.s=92)}({92:function(a,b){var c,d;(function(){'use strict';function e(){for(var a,b=[],c=0;c<arguments.length;c++)if(a=arguments[c],a){var d=typeof a;if('string'==d||'number'==d)b.push(a);else if(Array.isArray(a))b.push(e.apply(null,a));else if('object'==d)for(var g in a)f.call(a,g)&&a[g]&&b.push(g)}return b.join(' ')}var f={}.hasOwnProperty;'undefined'!=typeof a&&a.exports?a.exports=e:(c=[],d=function(){return e}.apply(b,c),!(d!==void 0&&(a.exports=d)))})()}});
PK
!<A|��..$chrome/toolkit/res/password-hide.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M3.067 1.183a.626.626 0 0 0-.885.885l1.306 1.306A8.885 8.885 0 0 0 0 7.595l0 .809C1.325 11.756 4.507 14 8 14c1.687 0 3.294-.535 4.66-1.455l2.273 2.273a.626.626 0 0 0 .884-.886L3.067 1.183zm3.759 5.528 2.463 2.463c-.32.352-.777.576-1.289.576-.965 0-1.75-.785-1.75-1.75 0-.512.225-.969.576-1.289zM8 12.75c-3.013 0-5.669-1.856-6.83-4.75a7.573 7.573 0 0 1 3.201-3.745l1.577 1.577A2.958 2.958 0 0 0 5 8c0 1.654 1.346 3 3 3 .858 0 1.624-.367 2.168-.948l1.613 1.613A7.118 7.118 0 0 1 8 12.75z"/>
  <path d="M8 2c-.687 0-1.356.11-2.007.275l1.049 1.049A7.06 7.06 0 0 1 8 3.25c3.013 0 5.669 1.856 6.83 4.75a7.925 7.925 0 0 1-1.141 1.971l.863.863A9.017 9.017 0 0 0 16 8.404l0-.809C14.675 4.244 11.493 2 8 2z"/>
</svg>
PK
!<Z�0chrome/toolkit/res/password.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M16 7.595C14.675 4.244 11.493 2 8 2S1.325 4.244 0 7.595l0 .809C1.325 11.756 4.507 14 8 14s6.675-2.244 8-5.595l0-.81zM8 12.75c-3.013 0-5.669-1.856-6.83-4.75C2.331 5.106 4.987 3.25 8 3.25S13.669 5.106 14.83 8c-1.161 2.894-3.817 4.75-6.83 4.75z"/>
  <path d="M8 11c-1.654 0-3-1.346-3-3s1.346-3 3-3 3 1.346 3 3-1.346 3-3 3zm0-4.75c-.965 0-1.75.785-1.75 1.75S7.035 9.75 8 9.75 9.75 8.965 9.75 8 8.965 6.25 8 6.25z"/>
</svg>
PK
!<�+��__1chrome/toolkit/skin/classic/global/aboutCache.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

h2 {
  margin-top: 2em;
}

table {
  table-layout: fixed;
  width: 100%;
  margin-bottom: 1em;
}

#disk,
#memory {
  background-color: rgba(0, 0, 0, .1);
}

th {
  width: 14em;
  white-space: nowrap;
  text-align: end;
}

td {
  font-family: monospace;
  word-wrap: break-word;
}

#explanation-dataSize {
  margin-top: 3em;
  word-wrap: break-word;
}

#col-key {
  width: 60%;
}

#col-dataSize,
#col-fetchCount,
#col-lastModified,
#col-expires {
  width: 13%;
}

#col-pinned {
  width: 5em;
}

#entries > tbody > tr:nth-child(odd) {
  background-color: rgba(0, 0, 0, .03);
}

#entries > tbody > tr:nth-child(even) {
  background-color: rgba(0, 0, 0, .06);
}

#entries > tbody > tr > td {
  padding: .5em 0;
  text-align: center;
}

#entries > thead > tr > th {
  text-align: center;
  white-space: normal;
}

#entries > thead > tr > th:first-child,
#entries > tbody > tr > td:first-child {
  text-align: start;
}
PK
!<�B���6chrome/toolkit/skin/classic/global/aboutCacheEntry.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

body {
  display: table;
}

table {
  table-layout: fixed;
}

th {
  width: 12em;
  word-wrap: break-word;
  vertical-align: top;
  text-align: end;
}

td {
  display: block;
  font-family: monospace;
  white-space: pre-wrap;
}

#td-key {
  word-wrap: break-word;
}
PK
!<���:chrome/toolkit/skin/classic/global/aboutHttpsOnlyError.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

.title > h2 {
  padding: 0;
  margin: 0;
  font-size: 17px;
  font-weight: 500;
}

.title {
  background-image: url("chrome://global/content/httpsonlyerror/secure-broken.svg");
}

em {
  font-style: normal;
  font-weight: 600;
}

#insecure-explanation-unavailable {
  margin-bottom: 0.5em;
}
#learn-more-container {
  margin-block: 0 2em;
}
#explanation-continue {
  margin-block: 2em;
}

.button-container {
  display: flex;
  flex-flow: row wrap;
  justify-content: end;
}

@media only screen and (max-width: 480px) {
  .button-container button {
    width: 100%;
    margin: 0.66em 0 0;
  }
}

ul > li {
  line-height: 1.5em;
}

.container {
  position: relative;
}

.suggestion-box {
  position: absolute;
  margin-top: 3em;
  background-image: url("chrome://global/skin/icons/lightbulb.svg");
  background-size: 64px;
  background-repeat: no-repeat;
  -moz-context-properties: fill;
  fill: currentColor;
  margin-inline-start: -5.5em;
  padding-inline-start: 5.5em;
  padding-bottom: 3em;
  animation: fade-in 300ms ease-out;
}

.suggestion-box > h2 {
  padding: 0;
  margin: 0;
  font-size: 17px;
  font-weight: 500;
}

@media (max-width: 970px) {
  .suggestion-box {
    background-image: none;
    padding-inline-start: 0;
    margin-inline-start: 0;
  }
}

@keyframes fade-in {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}
PK
!<ғ3q??3chrome/toolkit/skin/classic/global/aboutLicense.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* License Illustration */

.license-header {
  background-image: url("chrome://global/skin/illustrations/about-license.svg");
  background-repeat: no-repeat;
  background-position: right center;
  min-height: 300px;
  display: flex;
  align-items: center;
  padding-inline-end: 320px;
}

td:nth-child(1),
td:nth-child(2) {
  text-align: left;
  vertical-align: top;
}
PK
!<1�X���3chrome/toolkit/skin/classic/global/aboutLogging.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/in-content/common.css");

/** Content area **/
.main-content {
  width: min(90%, 1024px);
  margin: auto;
}

.page-subsection {
  margin-bottom: 2em;
}

.form-entry {
  /* Center the labels with their checkboxes */
  display: flex;
  align-items: center;
  margin: 0.3em 0;
}

:disabled + label {
  opacity: 0.5;
}

#current-log-modules,
#no-log-modules {
  font-family: monospace;
  margin-bottom: 1em;
  word-break: break-word;
}

#current-log-file,
#no-log-file {
  font-family: monospace;
}

#profiler-configuration,
#log-file-configuration {
  /* 16px is the size of the radio button, 6px is its margin
   * Then it's properly aligned with the text above. */
  padding-inline-start: calc(16px + 6px);
}

label {
  line-height: 1.8em;
}

input[type=text] {
  box-sizing: border-box;
  width: 100%;
  font-family: monospace;

  /* This cancels the default margin applied to all inputs in common-shared.css. */
  margin-inline: 0 !important;
}

.button-row > button:first-of-type {
  /* This cancels the default margin applied to all buttons in common-shared.css. */
  margin-inline-start: 0;
}

.info-box {
  padding: 1em;
  border-radius: 4px;
}

.info-box-label {
  font-weight: 600;
}

#error {
  background-color: rgba(240, 40, 40, 0.5);
  border: 1px solid rgba(240, 40, 40, 0.6);
}

#some-elements-unavailable {
  background-color: var(--in-content-box-info-background);
  border-color: var(--in-content-box-border-color);
}
PK
!<��:A2chrome/toolkit/skin/classic/global/aboutMemory.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/content/aboutMemory.css");
PK
!<0/���4chrome/toolkit/skin/classic/global/aboutNetError.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/error-pages.css");

body {
  --warning-color: #ffa436;
}

@media (prefers-color-scheme: dark)  {
  body {
    --warning-color: #ffbd4f;
  }
}

@media (prefers-contrast) {
  body {
    --warning-color: var(--in-content-page-color);
  }
}

body.certerror {
  width: 100%;
}

/* Normally, setting e.g. `display: flex` overrides the hidden attribute.
 * This allows for element hiding to be controlled entirely in HTML & JS. */
[hidden] {
    display: none !important;
}

body.captiveportal .title {
  background-image: url("chrome://global/skin/in-content/wifi.svg");
}

body.certerror .title {
  background-image: url("chrome://global/skin/icons/warning.svg");
  fill: var(--warning-color);
}

body.blocked .title {
  background-image: url("chrome://global/skin/icons/blocked.svg");
}

body.clockSkewError .title {
  background-image: none;
  background-repeat: no-repeat;
}

/* Pressing the retry button will cause the cursor to flicker from a pointer to
 * not-allowed. Override the disabled cursor behaviour since we will never show
 * the button disabled as the initial state. */
button:disabled {
  cursor: pointer;
}

#errorWhatToDoTitle {
  font-weight: bold;
  margin-top: 2em;
}

#openInNewWindowContainer {
  margin-bottom: 0;
}

#advancedPanelButtonContainer {
  background-color: rgba(128, 128, 147, 0.1);
  display: flex;
  justify-content: end;
  padding: 5px;
  margin-top: 2em;
}

#certErrorAndCaptivePortalButtonContainer {
  display: flex;
}

#netErrorButtonContainer > button {
  margin-top: 1.2em;
}

#openPortalLoginPageButton {
  margin-inline-start: 0;
}

#blockingErrorReporting {
  padding-bottom: 10px;
  /* Pull a bit closer to the top than the other .advanced-panel containers
   * as this is just a checkbox. */
  margin-top: 1.2em;
}

.advanced-panel-container {
  width: 100%;
  position: absolute;
  left: 0;
}

.trr-message-container {
  background-color: var(--in-content-box-background);
  border: 1px solid var(--in-content-box-border-color);
  border-radius: 4px;
  padding: 10px;
}

#badCertAdvancedPanel {
  background-color: var(--in-content-box-background);
  border: 1px solid var(--in-content-box-border-color);
}

.advanced-panel {
  margin: 48px auto;
  min-width: var(--in-content-container-min-width);
  max-width: var(--in-content-container-max-width);
}

#errorCode {
  white-space: nowrap;
}

#viewCertificate {
  margin: 0 3em;
}

#badCertTechnicalInfo {
  margin: 3em 3em 1em;
  overflow: auto;
  white-space: pre-wrap;
}

#certificateErrorDebugInformation {
  background-color: var(--in-content-box-info-background) !important;
  border-top: 1px solid var(--in-content-border-color);
  width: 100%;
  padding: 1em 3%;
  box-sizing: border-box;
}

#certificateErrorText {
  font-family: monospace;
  white-space: pre-wrap;
  padding: 1em 0;
  display: flex;
  flex-wrap: wrap;
}

#cert_domain_link:not([href]) {
  color: var(--in-content-page-color);
  text-decoration: none;
}

.clockSkewError .try-again {
  margin-top: 0.3em;
}

#errorLongDesc strong {
  font-weight: 600;
}

#errorShortDesc {
  font-size: 1.15em;
  line-height: 1.3;
  font-weight: 400;
  margin-top: 10px;
}

@media only screen and (max-width: 959px) {
  #certificateErrorText {
    /* The encoded certificate chain looks better when we're not
     * wrapping words on big screens, but at some point we need
     * to wrap to avoid overflowing */
    word-wrap: anywhere;
  }
}

@media only screen and (max-width: 480px) {
  #badCertTechnicalInfo {
    margin: 10px 10px 5px;
  }

  #viewCertificate {
    margin: 0 10px;
  }

  #errorCode {
    /* Break the error code to avoid long codes overflowing the screen */
    white-space: normal;
    word-wrap: anywhere;
  }
}

@media (max-width: 970px) {
  body.certerror .title {
    /* !important is necessary here until Bug 1723718 is resolved */
    background-image: url("chrome://global/skin/icons/warning.svg") !important;
    background-position: top left;
    padding-top: 60px;
    margin-top: -60px;
  }
  /* Reduce the negative margin on small viewport sizes to avoid exceeding the
   * 38px body vertical padding error-pages.css sets, leaving 8px space: */
  @media (max-height: 480px) {
    body.certerror .title {
      margin-top: -30px;
    }
  }

  body.certerror .title:dir(rtl) {
    background-position-x: right;
  }
}
PK
!<&c����6chrome/toolkit/skin/classic/global/aboutNetworking.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/in-content/common.css");

html {
  height: 100%;
}

body {
  display: flex;
  align-items: stretch;
  height: 100%;
}

#sectionTitle {
  float: inline-start;
}

#refreshDiv {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  margin-bottom: var(--space-small);
}

#refreshButton {
  margin-top: 0;
}

/** Categories **/

.category {
  cursor: pointer;
  /* Center category names */
  display: flex;
  align-items: center;
}

.category .category-name {
  pointer-events: none;
}

@media (max-width: 830px){
  #categories > .category {
    padding-inline-start: 5px;
    margin-inline-start: 0;
  }
}

/** Content area **/

.main-content {
  flex: 1;
}

.tab {
  padding: 0.5em 0;
}

.tab table {
  width: 100%;
}

th, td, table {
  border-collapse: collapse;
  border: none;
  text-align: start;
}

th {
  padding-bottom: 0.5em;
  font-size: larger;
}

td {
  padding-bottom: 0.25em;
  border-bottom: 1px solid var(--in-content-border-color);
}

#clearHTTPCache {
  margin-bottom: var(--space-xlarge);
}
PK
!<L-xB��2chrome/toolkit/skin/classic/global/aboutReader.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* Avoid adding ID selector rules in this style sheet, since they could
 * inadvertently match elements in the article content. */

:root {
  --grey-90-a10: rgba(12, 12, 13, 0.1);
  --grey-90-a20: rgba(12, 12, 13, 0.2);
  --grey-30: #d7d7db;
  --light-theme-background: #fff;
  --light-theme-foreground: rgb(21, 20, 26);
  --dark-theme-background: rgb(28, 27, 34);
  --dark-theme-foreground: rgb(251, 251, 254);
  --body-padding: 64px;
  --font-size: 12;
  --content-width: 22em;
  --line-height: 1.6em;
  --text-alignment: start;
  --word-spacing: 0;
  --letter-spacing: 0;
  --font-weight: normal;
  --font-family: Helvetica, Arial, sans-serif;
  --block-img-margin-left: unset;
  --block-img-margin-right: unset;
}

body {
  --main-background: var(--light-theme-background);
  --main-foreground: var(--light-theme-foreground);
  --primary-color: rgb(0, 97, 224);
  --toolbar-border: var(--grey-90-a20);
  --toolbar-transparent-border: transparent;
  --toolbar-box-shadow: var(--grey-90-a10);
  --toolbar-button-background: transparent;
  --toolbar-button-background-hover: rgba(207, 207, 216, 0.66);
  --toolbar-button-foreground-hover: var(--icon-fill);
  --toolbar-button-background-active: rgb(207, 207, 216);
  --toolbar-button-foreground-active: var(--primary-color);
  --toolbar-button-border: transparent;
  --toolbar-button-border-hover: var(--toolbar-button-border);
  --toolbar-button-border-active: var(--toolbar-button-border);
  --tooltip-background: var(--toolbar-button-background-active);
  --tooltip-foreground: var(--icon-fill);
  --tooltip-border: transparent;
  --popup-background: #fff;
  --popup-foreground: var(--main-foreground);
  --popup-border: rgba(0, 0, 0, 0.12);
  --popup-line: var(--grey-30);
  --popup-shadow: rgba(49, 49, 49, 0.3);
  --popup-button-background: rgba(207, 207, 216, 0.33);
  --popup-button-foreground: var(--main-foreground);
  --popup-button-selected-foreground: var(--color-gray-05);
  --popup-button-background-hover: var(--toolbar-button-background-hover);
  --popup-button-foreground-hover: var(--main-foreground);
  --popup-button-background-active: var(--toolbar-button-background-active);
  --popup-button-border: rgba(0, 0, 0, 0.2);
  --selected-background: rgba(0, 97, 224, 0.3);
  --outline-focus-color: var(--primary-color);
  --font-value-background: rgb(240, 240, 244);
  --font-value-border: var(--grey-30);
  --icon-fill: rgb(91, 91, 102);
  --icon-disabled-fill: rgba(91, 91, 102, 0.4);
  --text-selected-background: var(--selected-background);
  --text-selected-foreground: inherit;
  --link-foreground: var(--primary-color);
  --link-selected-background: var(--selected-background);
  --link-selected-foreground: #333;
  --visited-link-foreground: #b5007f;
  --custom-theme-background: var(--color-white);
  --custom-theme-foreground: var(--color-gray-100);
  --custom-theme-unvisited-links: var(--color-blue-50);
  --custom-theme-visited-links: #321C64;
  --custom-theme-selection-highlight: #FFFFCC;
  /* light colours */
}

body.light {
  color-scheme: light;
}

body.sepia {
  color-scheme: light;
  --main-background: rgb(244, 236, 216);
  --main-foreground: rgb(91, 70, 54);
  --toolbar-border: var(--main-foreground);
  --icon-fill: var(--main-foreground);
  --icon-disabled-fill: rgba(91, 70, 54, 0.4);
}

body.gray {
  color-scheme: light;
  --main-background: var(--grey-30);
  --main-foreground: var(--light-theme-foreground);
  --toolbar-border: var(--main-foreground);
  --icon-fill: var(--main-foreground);
}

body.dark,
body.contrast {
  color-scheme: dark;
  --toolbar-box-shadow: black;
  --toolbar-button-background-hover: rgb(82, 82, 94);
  --toolbar-button-background-active: rgb(91, 91, 102);
  --popup-background: rgb(66, 65, 77);
  --popup-button-border: rgba(255, 255, 255, 0.2);
  --popup-line: rgba(249, 249, 250, 0.1);
  --popup-button-background: rgb(43, 42, 51);
  --font-value-background: rgba(249, 249, 250, 0.15);
  --font-value-border: #656468;
  --icon-disabled-fill: rgba(251, 251, 254, 0.4);
  --link-selected-foreground: #fff;
  --visited-link-foreground: #e675fd;
  --selected-background: rgba(0, 221, 255, 0.3);
  --popup-button-selected-foreground: var(--color-gray-100);
  /* dark colours */

  .moz-reader-block-img {
    filter: brightness(0.8) contrast(1.2);
  }
}

body.dark {
  --main-background: var(--dark-theme-background);
  --main-foreground: var(--dark-theme-foreground);
  --primary-color: rgb(0, 221, 255);
  --toolbar-border: rgba(249, 249, 250, 0.2);
  --icon-fill: rgb(251, 251, 254);
}

body.contrast {
  --main-background: #000000;
  --main-foreground: #fff;
  --primary-color: #D6B4FD;
  --link-color: #8080FF;
  --link-color-hover: #D6B4FD;
  --toolbar-border: #FFEE32;
  --icon-fill: #FFEE32;
}

body.custom {
  color-scheme: light;
  --main-background: var(--custom-theme-background);
  --main-foreground: var(--custom-theme-foreground);
  --link-foreground: var(--custom-theme-unvisited-links);
  --visited-link-foreground: var(--custom-theme-visited-links);
  --popup-foreground: var(--light-theme-foreground);
  --popup-button-foreground: var(--light-theme-foreground);
  --popup-button-foreground-hover: var(--light-theme-foreground);
  --tooltip-foreground: var(--light-theme-foreground);
  --toolbar-border: var(--main-foreground);
  --icon-fill: var(--main-foreground);
  --icon-disabled-fill: rgba(91, 91, 102, 0.4);
}

body.hcm {
  --main-background: Canvas;
  --main-foreground: CanvasText;
  --primary-color: SelectedItem;
  --toolbar-border: CanvasText;
  /* We need a true transparent but in HCM this would compute to an actual color,
     so select the page's background color instead: */
  --toolbar-transparent-border: Canvas;
  --toolbar-box-shadow: Canvas;
  --toolbar-button-background: ButtonFace;
  --toolbar-button-background-hover: SelectedItem;
  --toolbar-button-foreground-hover: SelectedItemText;
  --toolbar-button-background-active: SelectedItemText;
  --toolbar-button-foreground-active: SelectedItem;
  --toolbar-button-border: ButtonText;
  --toolbar-button-border-hover: SelectedItemText;
  --toolbar-button-border-active: SelectedItem;
  --tooltip-background: Canvas;
  --tooltip-foreground: CanvasText;
  --tooltip-border: CanvasText;
  --popup-background: Canvas;
  --popup-foreground: CanvasText;
  --popup-border: CanvasText;
  --popup-line: CanvasText;
  --popup-button-background: ButtonFace;
  --popup-button-foreground: ButtonText;
  --popup-button-background-hover: ButtonFace;
  --popup-button-foreground-hover: SelectedItem;
  --popup-button-background-active: ButtonFace;
  --popup-button-border: ButtonText;
  --link-color-hover: SelectedItem;
  --selected-background: Canvas;
  --outline-focus-color: CanvasText;
  --font-value-background: Canvas;
  --font-value-border: CanvasText;
  --icon-fill: ButtonText;
  --icon-disabled-fill: GrayText;
  --text-selected-background: SelectedItem;
  --text-selected-foreground: SelectedItemText;
  --popup-button-selected-foreground: SelectedItemText;
  --link-foreground: LinkText;
  --link-selected-background: SelectedItem;
  --link-selected-foreground: SelectedItemText;
  --visited-link-foreground: VisitedText;
}

body.hcm .colors-dropdown {
  /* Hide entire colors menu when HCM is on. */
  display: none;
}

body {
  margin: 0;
  padding: var(--body-padding);
  background-color: var(--main-background);
  color: var(--main-foreground);
}

body.loaded {
  transition: color 0.4s, background-color 0.4s;
}

*::selection {
  background-color: var(--text-selected-background);
  color: var(--text-selected-foreground);
}

a {
  border-radius: 2px;
}

a::selection {
  background-color: var(--link-selected-background);
  color: var(--link-selected-foreground);
}

a:focus-visible {
  outline: 2px solid var(--outline-focus-color);
  outline-offset: 1px;
}

body {
  font-family: var(--font-family);
}

body.sans-serif {
  font-family: Helvetica, Arial, sans-serif;
}

body.serif {
  font-family: Georgia, "Times New Roman", serif;
}

/* Override some controls and content styles based on color scheme */

blockquote {
  border-inline-start: 2px solid var(--main-foreground) !important;
}

.color-scheme-buttons {
  .light-button,
  .auto-button {
    color: var(--light-theme-foreground);
    background-color: var(--light-theme-background);
  }

  @media (prefers-color-scheme: dark) {
    .auto-button {
      color: var(--dark-theme-foreground);
      background-color: var(--dark-theme-background);
    }
  }

  .dark-button {
    color: var(--dark-theme-foreground);
    background-color: var(--dark-theme-background);
  }

  .sepia-button {
    color: #5b4636;
    background-color: #f4ecd8;
  }
}

/* Loading/error message */

.reader-message {
  margin-top: 40px;
  display: none;
  text-align: center;
  width: 100%;
  font-size: 0.9em;
}

/* Detector element to see if we're at the top of the doc or not. */
.top-anchor {
  position: absolute;
  top: 0;
  width: 0;
  height: 5px;
  pointer-events: none;
}

/* Header */

.header {
  text-align: start;
  display: none;
}

.domain {
  font-size: 0.9em;
  line-height: 1.48em;
  padding-bottom: 4px;
  font-family: Helvetica, Arial, sans-serif;
  text-decoration: underline var(--main-foreground) !important;
  color: var(--link-foreground);
}

.header > h1 {
  font-size: 1.6em;
  line-height: 1.25em;
  width: 100%;
  margin: 30px 0;
  padding: 0;
}

.header > .credits {
  font-size: 0.9em;
  line-height: 1.48em;
  margin: 0 0 10px;
  padding: 0;
  font-style: italic;
}

.header > .meta-data {
  font-size: 0.65em;
  margin: 0 0 15px;
}

.reader-estimated-time {
  text-align: match-parent;
}

/* Controls toolbar */

.toolbar-container {
  position: sticky;
  z-index: 2;
  top: 32px;
  height: 0; /* take up no space, so body is at the top. */

  /* As a stick container, we're positioned relative to the body. Move us to
   * the edge of the viewport using margins, and take the width of
   * the body padding into account for calculating our width.
   */
  margin-inline-start: calc(-1 * var(--body-padding));
  width: max(var(--body-padding), calc((100% - var(--content-width)) / 2 + var(--body-padding)));
  font-size: var(--font-size); /* Needed to ensure `em` units match, is reset for .reader-controls */
}

.toolbar {
  padding-block: 16px;
  border: 1px solid var(--toolbar-border);
  border-radius: 6px;
  box-shadow: 0 2px 8px var(--toolbar-box-shadow);

  width: 32px; /* basic width, without padding/border */

  /* padding should be 16px, except if there's not enough space for that, in
   * which case use half the available space for padding (=25% on each side).
   * The 34px here is the width + borders. We use a variable because we need
   * to know this size for the margin calculation.
   */
  --inline-padding: min(16px, calc(25% - 0.25 * 34px));
  padding-inline: var(--inline-padding);

  /* Keep a maximum of 96px distance to the body, but center once the margin
   * gets too small. We need to set the start margin, however...
   * To center, we'd want 50% of the container, but we'd subtract half our
   * own width (16px) and half the border (1px) and the inline padding.
   * When the other margin would be 96px, we want 100% - 96px - the complete
   * width of the actual toolbar (34px + 2 * padding)
   */
  margin-inline-start: max(calc(50% - 17px - var(--inline-padding)), calc(100% - 96px - 34px - 2 * var(--inline-padding)));

  font-family: system-ui;
  list-style: none;
  user-select: none;

  /* Set focus outlines to the primary color without having to specify it
   * per each element. */
  accent-color: var(--primary-color);
}

@media (prefers-reduced-motion: no-preference) {
  .toolbar {
    transition-property: border-color, box-shadow;
    transition-duration: 250ms;
  }

  .toolbar .toolbar-button {
    transition-property: opacity;
    transition-duration: 250ms;
  }

  .toolbar-container.scrolled > .toolbar:not(:hover, :focus-within) {
    border-color: var(--toolbar-transparent-border);
    box-shadow: 0 2px 8px transparent;
  }

  .toolbar-container.scrolled > .toolbar:not(:hover, :focus-within) > .reader-controls:not(.dropdown-open) .toolbar-button {
    opacity: 0.6;
  }

  .toolbar-container.transition-location {
    transition-duration: 250ms;
    transition-property: width;
  }
}

.toolbar-container.overlaps .toolbar-button {
  opacity: 0.1;
}

.dropdown-open .toolbar {
  border-color: var(--toolbar-transparent-border);
  box-shadow: 0 2px 8px transparent;
}

.reader-controls {
  /* We use `em`s above this node to get it to the right size. However,
   * the UI inside the toolbar should use a fixed, smaller size. */
  font-size: 11px;
}

button {
  -moz-context-properties: fill;
  color: var(--main-foreground);
  fill: var(--icon-fill);
}

button:disabled {
  /* !important to override other uses of `fill` where the specificity there is greater. */
  fill: var(--icon-disabled-fill) !important;
}

.toolbar button::-moz-focus-inner {
  border: 0;
}

.toolbar-button {
  position: relative;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 1px solid var(--toolbar-button-border);
  border-radius: 4px;
  margin: 4px 0;
  background-color: var(--toolbar-button-background);
  background-size: 16px 16px;
  background-position: center;
  background-repeat: no-repeat;
}

.toolbar-button:hover {
  background-color: var(--toolbar-button-background-hover);
  border-color: var(--toolbar-button-border-hover);
  fill: var(--toolbar-button-foreground-hover);
}

.open .toolbar-button,
.toolbar-button:hover:active {
  background-color: var(--toolbar-button-background-active);
  color: var(--toolbar-button-foreground-active);
  border-color: var(--toolbar-button-border-active);
  fill: var(--toolbar-button-foreground-active);
}

.toolbar-button:focus-visible {
  outline: 2px solid var(--outline-focus-color);
  outline-offset: 2px;
}

.hover-label {
  position: relative;
  inset-inline-start: 36px;
  line-height: 16px;
  white-space: pre; /* make sure we don't wrap */
  background-color: var(--tooltip-background);
  color: var(--tooltip-foreground);
  padding: 4px 8px;
  border: 1px solid var(--tooltip-border);
  border-radius: 2px;
  visibility: hidden;
  pointer-events: none;
  /* Put above .dropdown .dropdown-popup, which has z-index: 1000. */
  z-index: 1001;
}

/* Show the hover tooltip on non-dropdown buttons. */
.toolbar-button:not(.dropdown-toggle):hover > .hover-label,
.toolbar-button:not(.dropdown-toggle):focus-visible > .hover-label,
/* Show the hover tooltip for dropdown buttons unless its dropdown is open. */
:not(.open) > li > .dropdown-toggle:hover > .hover-label,
:not(.open) > li > .dropdown-toggle:focus-visible > .hover-label {
  visibility: visible;
}

.dropdown {
  text-align: center;
  list-style: none;
  margin: 0;
  padding: 0;
  position: relative;
}

.dropdown li {
  margin: 0;
  padding: 0;
}

/* Popup */

.dropdown .dropdown-popup {
  text-align: start;
  position: absolute;
  inset-inline-start: 32px;
  z-index: 1000;
  background-color: var(--popup-background);
  visibility: hidden;
  border-radius: 4px;
  border: 1px solid var(--popup-border);
  box-shadow: 0 0 10px 0 var(--popup-shadow);
  top: var(--space-xsmall);

  h2 {
    font-size: var(--font-size-large);
    font-weight: var(--font-weight-bold);
    color: var(--popup-foreground);
    margin-block: var(--space-small);
  }

  hr {
    width: 100%;
    display: inline-block;
    margin-block: var(--space-small);
    border: none;
    border-top: 1px solid var(--popup-line);
  }
}

.open > .dropdown-popup {
  visibility: visible;
}

/* Font style popup */

.radio-button {
  /* We visually hide these, but we keep them around so they can be focused
   * and changed by interacting with them via the label, or the keyboard, or
   * assistive technology.
   */
  opacity: 0;
  pointer-events: none;
  position: absolute;
}

.radiorow,
.buttonrow {
  display: flex;
  align-content: center;
  justify-content: center;
}

.content-width-value,
.font-size-value,
.line-height-value {
  box-sizing: border-box;
  width: 36px;
  height: 20px;
  line-height: 18px;
  display: flex;
  justify-content: center;
  align-content: center;
  margin: auto;
  border-radius: 10px;
  border: 1px solid var(--font-value-border);
  color: var(--popup-button-foreground);
  background-color: var(--font-value-background);
}

.buttonrow > button {
  border: 0;
  height: 60px;
  width: 90px;
  background-color: transparent;
  background-repeat: no-repeat;
  background-position: center;
  fill: var(--popup-button-foreground);
}

.buttonrow > button:enabled:hover {
  background-color: var(--popup-button-background-hover);
  fill: var(--popup-button-foreground-hover);
}

.buttonrow > button:enabled:hover:active {
  background-color: var(--popup-button-background-active);
}

.buttonrow > button:enabled:focus-visible {
  outline: 2px solid var(--outline-focus-color);
  outline-offset: -2px;
}

.style-dropdown .radiorow:not(:last-child),
.style-dropdown .buttonrow:not(:last-child) {
  border-bottom: 1px solid var(--popup-line);
}

.radiorow > label {
  position: relative;
  box-sizing: border-box;
  border: 2px solid var(--popup-button-border);
}

.radiorow > label[checked] {
  border: 2px solid var(--link-selected-foreground);
}

.radiorow > label:hover {
  background-color: var(--popup-button-background-hover);
  color: var(--popup-button-foreground-hover);
}

.radiorow > input[type=radio]:focus-visible + label {
  outline: 2px solid var(--primary-color);
  outline-offset: var(--focus-outline-offset);
  z-index: 1;
}

.font-type-buttons > label {
  height: 64px;
  width: 105px;
  /* Slightly more space between these items. */
  margin: 10px;
  /* Center the Sans-serif / Serif labels */
  text-align: center;
  background-size: 63px 39px;
  background-repeat: no-repeat;
  background-position: center 18px;
  background-color: var(--popup-button-background);
  color: var(--popup-button-foreground);
  fill: currentColor;
  -moz-context-properties: fill;
  /* This mostly matches baselines, but because of differences in font
   * baselines between serif and sans-serif, this isn't always enough. */
  line-height: 1;
  padding-top: 2px;
  border-radius: 2px;
}

.font-type-buttons {
  > label:first-of-type {
    margin-inline-start: var(--space-large);
  }

  > label:last-of-type {
    margin-inline-end: var(--space-large);
  }
}

.font-type-buttons > label[checked] {
  background-color: var(--selected-background);
}

.sans-serif-button {
  font-family: Helvetica, Arial, sans-serif;
  background-image: url("chrome://global/skin/reader/RM-Sans-Serif.svg");
}

/* Tweak padding to match the baseline on mac */
:root[platform=macosx] .sans-serif-button {
  padding-top: 3px;
}

.serif-button {
  font-family: Georgia, "Times New Roman", serif;
  background-image: url("chrome://global/skin/reader/RM-Serif.svg");
}

body.hcm .color-scheme-buttons {
  /* Disallow selecting themes when HCM is on. */
  display: none;
}

.color-scheme-buttons > label {
  padding: 12px;
  height: 34px;
  font-size: 12px;
  /* Center the labels horizontally as well as vertically */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* We want 10px between items, but there's no margin collapsing in flexbox. */
  margin: 10px 5px;
  border-radius: 2px;
  border-color: var(--popup-border);
}

.color-scheme-buttons > input:first-child + label {
  margin-inline-start: 10px;
}

.color-scheme-buttons > label:last-child {
  margin-inline-end: 10px;
}

/* Improved text and layout menu popup */

#text-layout-controls {
  display: flex;
  flex-direction: column;
  width: 340px;
  padding: var(--space-small) var(--space-large);
  max-height: calc(100vh - 7em);
  overflow-y: auto;
  font-size: 15px;
  color: var(--popup-foreground);
}

#text-size-controls {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-block: var(--space-small);
}

.text-size-buttons {
  gap: var(--space-medium);
}

.text-size-buttons > button {
  height: var(--size-item-large);
  width: var(--size-item-large);
  border: 1px solid transparent;
  border-radius: var(--border-radius-small);
  background-color: var(--popup-button-background);

  &.text-size-minus-button {
    background-image: url("chrome://global/skin/icons/minus.svg");
  }

  &.text-size-plus-button {
    background-image: url("chrome://global/skin/icons/plus.svg");
  }

  body.hcm & {
    border-color: var(--popup-button-border);

    &:hover {
      border-color: SelectedItem;

      &:active {
        border-color: ButtonText;
      }
    }
  }
}

#font-controls {
  display: flex;
  justify-content: space-between;
  gap: var(--space-medium);
  padding-block: var(--space-small);
}

.dropdown-selector {
  display: flex;
  flex-direction: column;
  flex: 1;
  max-width: 50%;
  gap: var(--space-xsmall);

  > select {
    appearance: none;
    min-height: var(--button-min-height);
    border: 1px solid transparent;
    border-radius: var(--border-radius-small);
    background-color: var(--popup-button-background);
    color: var(--popup-button-foreground);
    font-size: inherit;
    font-weight: var(--font-weight-bold);
    padding-inline: var(--space-large) var(--space-xxlarge);
    text-overflow: ellipsis;
    background-image: url("chrome://global/skin/icons/arrow-down.svg");
    background-position: right var(--space-medium) center;
    background-repeat: no-repeat;
    -moz-context-properties: fill;
    fill: currentColor;

    &:hover {
      background-color: var(--popup-button-background-hover);
      color: var(--popup-button-foreground-hover);

      &:active {
        background-color: var(--popup-button-background-active);
      }
    }

    body.hcm & {
      border-color: var(--popup-button-border);

      &:hover {
        border-color: SelectedItem;
      }

      &:hover:active {
        border-color: ButtonText;
      }
    }

    /* Selector chevron should appear on the left for RTL. */
    &:dir(rtl) {
      background-position-x: left var(--space-medium);
    }
  }
}

.accordion-header {
  list-style: none;
  display: flex;
  justify-content: space-between;
  align-items: center;
  cursor: pointer;

  .chevron-icon {
    background-image: url("chrome://global/skin/icons/arrow-down.svg");
    background-position: center;
    background-repeat: no-repeat;
    -moz-context-properties: fill;
    fill: currentColor;
    height: var(--size-item-large);
    width: var(--size-item-large);
    border: 1px solid transparent;
    border-radius: var(--border-radius-small);

    #about-reader-advanced-layout[open] & {
      background-image: url("chrome://global/skin/icons/arrow-up.svg");
    }

    body.hcm & {
      color: ButtonText;
      border-color: var(--popup-button-border);

      &:hover {
        color: SelectedItem;
        border-color: SelectedItem;
      }

      &:hover:active {
        border-color: ButtonText;
      }
    }
  }
}

.slider-container {
  padding-block: var(--space-small);
}

label[for="text-alignment-buttons"] {
  display: block;
  padding-block-start: var(--space-small);
}

.text-alignment-buttons {
  gap: var(--space-xxsmall);
  padding-block: var(--space-small);

  > label {
    display: flex;
    flex: 1;
    min-height: var(--button-min-height);
    background-repeat: no-repeat;
    background-position: center;
    background-color: var(--popup-button-background);
    -moz-context-properties: fill;
    fill: currentColor;
    border: none;

    /* border radius does not target class because button order
     * can be opposite in RTL */
    &:first-of-type {
      border-start-start-radius: var(--border-radius-small);
      border-end-start-radius: var(--border-radius-small);
    }

    &:last-of-type {
      border-start-end-radius: var(--border-radius-small);
      border-end-end-radius: var(--border-radius-small);
    }

    &.left-align-button {
      background-image: url("chrome://global/skin/reader/align-left-20.svg");
    }

    &.center-align-button {
      background-image: url("chrome://global/skin/reader/align-center-20.svg");
    }

    &.right-align-button {
      background-image: url("chrome://global/skin/reader/align-right-20.svg");
    }

    &[checked] {
      background-color: var(--primary-color);
      border: none;
      color: var(--popup-button-selected-foreground);
    }

    body.hcm & {
      border: 1px solid var(--popup-button-border);

      &:hover:not([checked]) {
        border-color: SelectedItem;
      }

      &:hover:active:not([checked]) {
        border-color: ButtonText;
      }
    }
  }
}

/* Separate colors menu popup */

#color-controls {
  padding-block: var(--space-small);
  width: 400px;
}

#about-reader-colors-menu-header {
  margin-inline: var(--space-large) 0;
}

button-group {
  display: flex;
  font-size: var(--font-size-small);
  padding-block: var(--space-small);
}

button[is="named-deck-button"] {
  background: none;
  color: var(--popup-button-foreground);
  border: 1px var(--popup-button-border);
  border-style: solid none;
  padding: var(--space-small);
  flex-basis: 50%;

  &[selected] {
    color: var(--primary-color);
    border-top: 2px solid var(--primary-color);
  }
}

div[name="customtheme"] {
  padding-inline: var(--space-large);
}

.custom-colors-selection {
  display: flex;
  flex-direction: column;
  gap: var(--space-small);
  padding: var(--space-small) 0;
  list-style-type: none;
  font-size: 15px;
  color: var(--popup-button-foreground);
}

.colors-menu-color-scheme-buttons {
  flex-wrap: wrap;
  padding-block: var(--space-xsmall) var(--space-small);
}

.colors-menu-color-scheme-buttons > label {
  height: 48px;
  width: calc(50% - 21px);
  font-size: 15px;
  color: var(--popup-button-foreground);
  border: 1px solid var(--popup-button-border);
  border-radius: var(--border-radius-small);
  /* Center content vertically and justify left horizontally */
  display: inline-flex;
  align-items: center;
  justify-content: start;
  margin: var(--space-xsmall);
}

.colors-menu-color-scheme-buttons > label:before {
  content: "";
  display: inline-block;
  width: 24px;
  height: 24px;
  border-radius: var(--border-radius-circle);
  outline: 1px solid var(--popup-button-border);
  margin-inline: var(--space-medium);
}

.colors-menu-color-scheme-buttons {
  .auto-button:before {
    background: linear-gradient(to right, var(--light-theme-background) 50%, var(--dark-theme-background) 50%);;
  }

  .light-button:before {
    background-color: var(--light-theme-background);
  }

  .dark-button:before {
    background-color: var(--dark-theme-background);
  }

  .sepia-button:before {
    background-color: #f4ecd8;
  }

  .contrast-button:before {
    background-color: #000000;
  }

  .gray-button:before {
    background-color: var(--grey-30);
  }
}

.reset-button {
  display: block;
  background: none;
  border: none;
  color: var(--link-color);
  text-decoration: underline;
  font-size: 15px;
  padding-block: var(--space-small);
  cursor: pointer;

  &.text-layout-reset-button {
    float: inline-end;
  }

  &:hover {
    color: var(--link-color-hover);

    &:active {
      color: var(--link-color-active);
    }
  }
}

/* Toolbar icons */

.close-button {
  background-image: url("chrome://global/skin/icons/close.svg");
}

.style-button {
  background-image: url("chrome://global/skin/reader/RM-Type-Controls-24x24.svg");
}

.improved-style-button {
  background-image: url("chrome://global/skin/reader/RM-Type-Controls-24x24.svg");
}

.colors-button {
  background-image: url("chrome://mozapps/skin/extensions/category-themes.svg");
}

.minus-button {
  background-size: 18px 18px;
  background-image: url("chrome://global/skin/reader/RM-Minus-24x24.svg");
}

.plus-button {
  background-size: 18px 18px;
  background-image: url("chrome://global/skin/reader/RM-Plus-24x24.svg");
}

.content-width-minus-button {
  background-size: 42px 16px;
  background-image: url("chrome://global/skin/reader/RM-Content-Width-Minus-42x16.svg");
}

.content-width-plus-button {
  background-size: 44px 16px;
  background-image: url("chrome://global/skin/reader/RM-Content-Width-Plus-44x16.svg");
}

.line-height-minus-button {
  background-size: 34px 14px;
  background-image: url("chrome://global/skin/reader/RM-Line-Height-Minus-38x14.svg");
}

.line-height-plus-button {
  background-size: 34px 24px;
  background-image: url("chrome://global/skin/reader/RM-Line-Height-Plus-38x24.svg");
}

/* Mirror the line height buttons if the article is RTL. */
.reader-controls[articledir="rtl"] .line-height-minus-button,
.reader-controls[articledir="rtl"] .line-height-plus-button {
  transform: scaleX(-1);
}

@media print {
  .toolbar {
    display: none !important;
  }
}

/* Article content */

/* Note that any class names from the original article that we want to match on
 * must be added to CLASSES_TO_PRESERVE in ReaderMode.sys.mjs, so that
 * Readability.js doesn't strip them out */

.container {
  margin: 0 auto;
  font-size: var(--font-size);
  max-width: var(--content-width);
  line-height: var(--line-height);
  text-align: var(--text-alignment);
  word-spacing: var(--word-spacing);
  letter-spacing: var(--letter-spacing);
  font-weight: var(--font-weight);

  .header {
    text-align: var(--text-alignment);
  }
}

pre {
  font-family: inherit;
}

.moz-reader-content {
  display: none;
  font-size: 1em;
}

@media print {
  .moz-reader-content p,
  .moz-reader-content code,
  .moz-reader-content pre,
  .moz-reader-content blockquote,
  .moz-reader-content ul,
  .moz-reader-content ol,
  .moz-reader-content li,
  .moz-reader-content figure,
  .moz-reader-content .wp-caption {
    margin: 0 0 10px !important;
    padding: 0 !important;
  }
}

.moz-reader-content h1,
.moz-reader-content h2,
.moz-reader-content h3 {
  font-weight: bold;
}

.moz-reader-content h1 {
  font-size: 1.6em;
  line-height: 1.25em;
}

.moz-reader-content h2 {
  font-size: 1.2em;
  line-height: 1.51em;
}

.moz-reader-content h3 {
  font-size: 1em;
  line-height: 1.66em;
}

.moz-reader-content a:link {
  text-decoration: underline;
  font-weight: normal;
}

.moz-reader-content a:link,
.moz-reader-content a:link:hover,
.moz-reader-content a:link:active {
  color: var(--link-foreground);
}

.moz-reader-content a:visited {
  color: var(--visited-link-foreground);
}

.moz-reader-content * {
  max-width: 100%;
  height: auto;
}

.moz-reader-content p,
.moz-reader-content p,
.moz-reader-content code,
.moz-reader-content pre,
.moz-reader-content blockquote,
.moz-reader-content ul,
.moz-reader-content ol,
.moz-reader-content li,
.moz-reader-content figure,
.moz-reader-content .wp-caption {
  margin: -10px -10px calc(8px + var(--line-height) * 0.4);
  padding: 10px;
  border-radius: 5px;
}

.moz-reader-content li {
  margin-bottom: 0;
}

.moz-reader-content li > ul,
.moz-reader-content li > ol {
  margin-bottom: -10px;
}

.moz-reader-content p > img:only-child,
.moz-reader-content p > a:only-child > img:only-child,
.moz-reader-content .wp-caption img,
.moz-reader-content figure img {
  display: block;
}

.moz-reader-content img[moz-reader-center] {
  margin-inline: auto;
}

/* Align non-centered images with the text. Margins are unset unless the user makes a text alignment selection. */
/* Does not use logical properties because the margins are tied to text alignment and not text direction. */
.moz-reader-content img:not([moz-reader-center]) {
  margin-left: var(--block-img-margin-left);
  margin-right: var(--block-img-margin-right);
}

.moz-reader-content .caption,
.moz-reader-content .wp-caption-text
.moz-reader-content figcaption {
  font-size: 0.9em;
  line-height: 1.48em;
  font-style: italic;
}

.moz-reader-content pre {
  white-space: pre-wrap;
}

.moz-reader-content blockquote {
  padding: 0;
  padding-inline-start: 16px;
}

.moz-reader-content ul,
.moz-reader-content ol {
  padding: 0;
}

.moz-reader-content ul {
  padding-inline-start: 30px;
  list-style: disc;
}

.moz-reader-content ol {
  padding-inline-start: 30px;
}

table,
th,
td {
  border: 1px solid currentColor;
  border-collapse: collapse;
  padding: 6px;
  vertical-align: top;
}

table {
  margin: 5px;
}

/* Visually hide (but don't display: none) screen reader elements */
.moz-reader-content .visually-hidden,
.moz-reader-content .visuallyhidden,
.moz-reader-content .sr-only {
  display: inline-block;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  border-width: 0;
}

/* Hide elements with common "hidden" class names */
.moz-reader-content .hidden,
.moz-reader-content .invisible {
  display: none;
}

/* Enforce wordpress and similar emoji/smileys aren't sized to be full-width,
 * see bug 1399616 for context. */
.moz-reader-content img.wp-smiley,
.moz-reader-content img.emoji {
  display: inline-block;
  border-width: 0;
  /* height: auto is implied from `.moz-reader-content *` rule. */
  width: 1em;
  margin: 0 .07em;
  padding: 0;
}

.reader-show-element {
  display: initial;
}

/* Provide extra spacing for images that may be aided with accompanying element such as <figcaption> */
.moz-reader-block-img:not(:last-child) {
  margin-block-end: 12px;
}

.moz-reader-wide-table {
  overflow-x: auto;
  display: block;
}

pre code {
  background-color: var(--main-background);
  border: 1px solid var(--main-foreground);
  display: block;
  overflow: auto;
}
PK
!<4�b,,2chrome/toolkit/skin/classic/global/aboutRights.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* Rights Illustration */

.rights-header {
  background-image: url("chrome://global/skin/illustrations/about-rights.svg");
  background-repeat: no-repeat;
  background-position: right center;
  min-height: 300px;
  display: flex;
  align-items: center;
  padding-inline-end: 320px;
}

.rights-header:dir(rtl) {
  background-position: left center;
}
PK
!<3&3chrome/toolkit/skin/classic/global/aboutSupport.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/in-content/info-pages.css");

#contents {
  clear: both;
}

.major-section {
  margin-block: 2em 1em;
  font-size: large;
  text-align: start;
  font-weight: bold;
}

button {
  margin-inline: 0 8px;
}

th.title-column {
  white-space: nowrap;
  width: 0;
  font-size: medium;
}

td.integer {
  text-align: end;
  font-family: monospace;
}

/* Codec support table w/ zebra striping */
:root {
  --codec-bg-header: #bfbfc9;
  --codec-bg-supported-even: #b3ffe3;
  --codec-bg-supported-odd: #54ffbd;
  --codec-bg-unsupported-even: #ffdfe7;
  --codec-bg-unsupported-odd: #ffbdc5;
  --codec-text-name-even: var(--in-content-box-background);
  --codec-text-name-odd: var(--in-content-box-background-odd);
  --codec-text-other: var(--in-content-text-color);
  --codec-text-supported: #15141a;
  --codec-text-unsupported: #15141a;
}

@media (prefers-color-scheme: dark) {
  :root {
    --codec-bg-header: #42414d;
  }
}

#codec-table {
  text-align: center;
  white-space: nowrap;
  width: auto;
}

#codec-table tr {
  color: var(--codec-text-other);
}

#codec-table th {
  color: var(--codec-text-other);
  background-color: var(--codec-bg-header);
}

#codec-table td:first-child {
  text-align: start;
  unicode-bidi: normal;
  font-weight: 600;
}

#codec-table tr:nth-child(even) {
  background-color: var(--codec-text-name-even);
}

#codec-table tr:nth-child(odd) {
  background-color: var(--codec-text-name-odd);
}

#codec-table tr > td.unsupported {
  color: var(--codec-text-unsupported);
}

#codec-table tr > td.supported {
  color: var(--codec-text-supported);
}

#codec-table tr > td.lack-of-extension {
  font-weight: var(--font-weight-bold);
}

#codec-table tr:nth-child(even) > td.unsupported {
  background-color: var(--codec-bg-unsupported-even);
}

#codec-table tr:nth-child(odd) > td.unsupported {
  background-color: var(--codec-bg-unsupported-odd);
}

#codec-table tr:nth-child(even) > td.supported {
  background-color: var(--codec-bg-supported-even);
}

#codec-table tr:nth-child(odd) > td.supported {
  background-color: var(--codec-bg-supported-odd);
}

#codec-table tr:nth-child(even) > td.lack-of-extension {
  background-color: var(--in-content-box-background-even);
}

#codec-table tr:nth-child(odd) > td.lack-of-extension {
  background-color: var(--in-content-box-background-odd);
}

#update-dir-row > td:dir(rtl),
#profile-row > td:dir(rtl) {
  /* Overrides info-pages.css to display the buttons in the right order compared to the text */
  unicode-bidi: normal;
}

.prefs-table {
  table-layout: fixed;
}

.pref-name {
  width: 70%;
  white-space: nowrap;
  overflow: hidden;
}

.pref-value {
  width: 30%;
  white-space: nowrap;
  overflow: hidden;
}

#crashes-table th:first-child {
  width: 50%;
}

#features-table th:first-child,
#remote-processes-table th:first-child {
  width: 30%;
}

#features-table th:nth-child(2) {
  width: 20%;
}

#reset-box,
#safe-mode-box {
  display: none;
}

#reset-box > h3 {
  margin-top: 0;
}

.action-box button {
  display: block;
}

#enumerate-database-result,
#verify-place-result {
  max-height: 200px;
  overflow: auto;
  text-align: left;
  direction: ltr;
}

.hidden {
  display: none;
}

#sandbox-tbody tr > td.feature-unavailable {
  background-color: var(--background-color-critical);
}

#sandbox-tbody tr > td.feature-unavailable > .user-namespaces-unavailabe-support-link {
  margin: 0 5px
}
PK
!<Ma���,chrome/toolkit/skin/classic/global/alert.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/global.css");

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

#alertBox {
  border: 1px solid threedshadow;
  background-color: -moz-Dialog;
  color: -moz-DialogText;
}

@media (-moz-platform: windows) {
  #alertNotification {
    appearance: none;
    background: transparent;
  }

  #alertBox {
    margin: 10px;
    border-radius: 1px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.59);
  }

  @media (not (prefers-contrast)) and (prefers-color-scheme: light) {
    #alertBox {
      border-color: rgba(107,107,107,.3);
      background-color: rgba(255,255,255,.9);
      color: rgba(0,0,0,.9);
    }
  }
}

@media (-moz-platform: macos) {
  #alertNotification {
    appearance: none;
    background: transparent;
  }

  #alertBox {
    border-radius: 1px;
  }
}

#alertBox[animate] {
  animation-timing-function: cubic-bezier(.12,1.23,.48,1.09);
}

#alertBox[animate][clicked] {
  animation-duration: .6s;
  animation-name: alert-clicked-animation;
}

/* This is used if the close button is clicked
   before the animation has finished. */
#alertBox[animate][closing] {
  animation-duration: .6s;
  animation-name: alert-closing-animation;
}

#alertBox[animate]:not([clicked], [closing]):hover {
  /* !important is necessary because CSS animations have highest
     importance in the cascade with exception to !important rules. */
  opacity: 1 !important;
}

@keyframes alert-animation {
  from {
    opacity: 0;
  }
  5% {
    opacity: 1;
  }
  95% {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

@keyframes alert-clicked-animation {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

@keyframes alert-closing-animation {
  from {
    opacity: 1;
  }
  to {
    opacity: 0;
  }
}

#alertIcon {
  margin-top: 6px;
  margin-inline-start: 8px;
  margin-inline-end: 0;
  margin-bottom: 0;
  width: 16px;
  min-height: 16px;
  max-height: 16px;
}

@media (resolution: 2dppx) {
  #alertIcon {
    image-rendering: -moz-crisp-edges;
  }
}

#alertImage {
  width: 80px;
  height: 80px;
  max-width: 80px;
  max-height: 80px;
  object-fit: scale-down;
  margin: 0 7px 7px;
}

.alertTextBox {
  padding-top: 4px;
  /* The text box width is increased to make up for the lack of image when one
     is not provided. 349px is the text box width when a picture is present,
     255px, plus the width of the image, 80px, and the margins, 7px each. */
  width: 349px;
}

#alertBox[hasImage] > box > #alertTextBox {
  width: 255px;
}

#alertBox:not([hasImage]) > box > #alertTextBox {
  padding-inline-start: 8px;
}

#alertTextLabel {
  padding-inline-end: 8px;
  margin: 0;
}

.alertTitle {
  flex: 1;
  font-weight: bold;
  padding: 6px 8px 0;
  margin: 0;
  width: 255px;
}

#alertFooter {
  align-items: flex-start;
}

#alertBox:not([hasOrigin]) > box > #alertTextBox,
#alertFooter {
  padding-bottom: 5px;
}

#alertSourceLabel {
  flex: 1;
  font-size: 83.334%;
  color: GrayText;
  margin: 0;
}

#alertSettings {
  appearance: none;
  background-color: transparent;
  border-width: 0;
  border-radius: 20px;
  min-width: 0;
  list-style-image: url("chrome://global/skin/icons/settings.svg");
  -moz-context-properties: fill;
  fill: currentColor;
  margin-inline-end: 4px;
  margin-bottom: 0;
}

#alertSettings > .button-box {
  padding: 0;
}

#alertSettings .button-icon {
  margin: 0;
}

#alertSettings:hover,
#alertSettings[open] {
  fill: #ddd;
}

#alertSettings:hover {
  background-color: rgb(128,128,128);
}

#alertSettings[open],
#alertSettings:hover:active {
  background-color: rgb(102,102,102);
}

#alertSettings[focusedViaMouse]:-moz-focusring {
  outline: none;
}

#alertSettings > .button-box > .button-menu-dropmarker,
#alertSettings > .button-box > .box-inherit > .button-text {
  display: none;
}
PK
!<cd6^^0chrome/toolkit/skin/classic/global/appPicker.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#app-picker {
  min-width: 320px;
}

#content-description {
  font-weight: bold;
}

#app-picker-listbox {
  height: 212px;
}

#app-picker-listbox > richlistitem {
  align-items: center;
}

#content-icon,
#app-picker-listbox > richlistitem > image {
  margin: 5px;
  width: 32px;
  height: 32px;
}

#app-picker-listbox > richlistitem > label {
  margin: 0px;
  padding: 5px;
  white-space: nowrap;
}
PK
!<YD/��@chrome/toolkit/skin/classic/global/arrow/panelarrow-vertical.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="10">
  <path d="M 0,10 L 10,0 20,10 z" fill="context-stroke"/>
  <path d="M 1,10 L 10,1 19,10 z" fill="context-fill"/>
</svg>
PK
!<�O�V��3chrome/toolkit/skin/classic/global/commonDialog.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#commonDialog[subdialog] .checkbox-label {
  color: var(--text-color-deemphasized);
}

@media (-moz-platform: windows) {
  #infoContainer {
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  #infoIcon {
    margin: 3px 5px 4px;
  }
}

@media (-moz-platform: macos) {
  #commonDialog:not([subdialog]) {
    line-height: 13px;
  }

  #commonDialog:not([subdialog]) #infoTitle,
  #commonDialog:not([subdialog]) #infoBody {
    font: menu;
    line-height: 16px;
    margin-bottom: 6px;
  }

  #infoContainer {
    max-width: 33em;
  }

  #infoIcon {
    margin: 3px 5px 4px;
    margin-inline-end: 14px;
  }

  #commonDialog:not([subdialog]) #infoTitle {
    font-weight: bold;
  }
}

@media (-moz-platform: linux) {
  #infoContainer {
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

  /*
   * Since we size the window to content, and the icon load is asynchronous, we
   * make sure that we take the whole space we could possibly take, regardless of
   * whether the icon is loaded.
   */
  #iconContainer {
    height: 55px; /* maximum icon height + some margin */
    width: 58px; /* maximum icon width + some margin */
  }

  #infoIcon {
    display: block;
    max-width: 48px;
    max-height: 48px;
    margin: 3px auto 0;
  }
}
PK
!<9���&�&;chrome/toolkit/skin/classic/global/datetimeinputpickers.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:root {
  --border-style: 0.1rem solid;
  --border-radius: 0.3rem;
  --border-color: ButtonBorder;
  --button-color: ButtonText;
  --button-color-hover: SelectedItemText;
  --button-color-active: SelectedItem;
  --button-background: ButtonFace;
  --button-background-hover: SelectedItem;
  --button-background-active: SelectedItemText;
  --button-border-hover: SelectedItem;
  --button-border-active: SelectedItem;
  --today-background: Mark;
  --today-color: MarkText;
  --navigation-arrow-color-hover: SelectedItemText;
  --navigation-arrow-color-active: SelectedItem;
  --navigation-arrow-background-hover: SelectedItem;
  --navigation-arrow-background-active: SelectedItemText;
  --navigation-arrow-border-hover: SelectedItem;
  --navigation-arrow-border-active: SelectedItem;
  --navigation-arrow-fill-opacity: 1;
  --navigation-arrow-fill-opacity-hover: 1;
  --navigation-arrow-fill-opacity-active: 1;
  --calendar-item-background-hover: color-mix(in srgb, FieldText 20%, transparent);

  /* Use -moz-activehyperlinktext to get a system color that
     by default will be closest to Red */
  --weekend-font-color: -moz-activehyperlinktext;

  --disabled-background-color: ButtonShadow;

  /* TODO: these need to be in sync (ish) with DateTimePickerPanel.sys.mjs */
  font-size: 10px;
  --font-size-default: 1.1rem;
  --spinner-width: 3rem;
  --spinner-margin-block: 0.4rem;
  --spinner-item-height: 2.4rem;
  --spinner-item-margin-bottom: 0.1rem;
  --spinner-button-height: 1.8rem;
  --colon-width: 2rem;
  --day-period-spacing-width: 1rem;
  --date-picker-item-height: 2.4rem;
  --date-picker-item-width: 3.3rem;

  /* We need to hide the scroll bar but maintain its scrolling
     capability, so using |overflow: hidden| is not an option. */
  scrollbar-width: none;
}

@media not (prefers-contrast) {
  :root {
    --border-color: color-mix(in srgb, FieldText 65%, transparent);
    --button-color: inherit;
    --button-color-hover: inherit;
    --button-color-active: inherit;
    --button-background: color-mix(in srgb, FieldText 10%, transparent);
    --button-background-hover: color-mix(in srgb, FieldText 20%, transparent);
    --button-background-active: color-mix(in srgb, FieldText 30%, transparent);
    --button-border-hover: var(--border-color);
    --button-border-active: var(--border-color);
    --today-background: color-mix(in srgb, FieldText 30%, transparent);
    --today-color: inherit;
    --navigation-arrow-color-hover: inherit;
    --navigation-arrow-color-active: inherit;
    --navigation-arrow-background-hover: transparent;
    --navigation-arrow-background-active: transparent;
    --navigation-arrow-border-hover: transparent;
    --navigation-arrow-border-active: transparent;
    --navigation-arrow-fill-opacity: 0.5;
    --navigation-arrow-fill-opacity-hover: 0.8;
    --navigation-arrow-fill-opacity-active: 1;
  }
}

body {
  margin: 0;
  font: message-box;
  font-size: var(--font-size-default);
  background-color: Field;
  color: FieldText;
}

button {
  appearance: none;
  padding: 0;
  color: var(--button-color);
  background: none;
  background-color: var(--button-background);
  background-repeat: no-repeat;
  background-position: center;
  border: var(--border-style) var(--border-color);
  border-radius: var(--border-radius);
  -moz-context-properties: fill, fill-opacity;
  fill: currentColor;
}

button:where(:hover) {
  color: var(--button-color-hover);
  background-color: var(--button-background-hover);
  border-color: var(--button-border-hover);
}

button:where(:hover.active) {
  color: var(--button-color-active);
  background-color: var(--button-background-active);
  border-color: var(--button-border-active);
}

button:focus-visible {
  outline: 0.2rem solid SelectedItem;
  outline-offset: 0.2rem;
}

#date-picker {
  /* Add some padding so outlines would not overflow our viewport. */
  padding: 0.4rem;
}

.month-year-nav {
  display: flex;
  justify-content: space-between;
  margin-bottom: 0.8rem;
}

.month-year-nav[monthPickerVisible] {
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.month-year-nav > button,
.spinner-container > button {
  background-color: transparent;
  border-color: transparent;
  fill-opacity: var(--navigation-arrow-fill-opacity);
}

.month-year-nav > button {
  height: 2.4rem;
  width: 2.4rem;
}

.month-year-nav > button:hover,
.spinner-container > button:hover {
  color: var(--navigation-arrow-color-hover);
  background-color: var(--navigation-arrow-background-hover);
  border-color: var(--navigation-arrow-border-hover);
  fill-opacity: var(--navigation-arrow-fill-opacity-hover);
}

.month-year-nav > button:hover.active,
.spinner-container > button:hover.active {
  color: var(--navigation-arrow-color-active);
  background-color: var(--navigation-arrow-background-active);
  border-color: var(--navigation-arrow-border-active);
  fill-opacity: var(--navigation-arrow-fill-opacity-active);
}

.month-year-nav > button.prev:dir(ltr),
.month-year-nav > button.next:dir(rtl) {
  background-image: url("chrome://global/skin/icons/arrow-left.svg");
}

.month-year-nav > button.prev:dir(rtl),
.month-year-nav > button.next:dir(ltr) {
  background-image: url("chrome://global/skin/icons/arrow-right.svg");
}

button.month-year {
  font-size: 1.3rem;
  height: var(--date-picker-item-height);
  padding-block: 0.2rem 0.3rem;
  padding-inline: 1.2rem 2.6rem;
  background-image: url("chrome://global/skin/icons/arrow-down.svg");
  background-position-x: right 0.5rem;
}

@media not (prefers-contrast) {
  button.month-year {
    fill-opacity: .5;
  }
}

button.month-year:dir(rtl) {
  background-position-x: left 0.5rem;
}

button.month-year:hover {
  color: var(--button-color-hover);
  background-color: var(--button-background-hover);
  border-color: var(--button-border-hover);
}

button.month-year.active {
  color: var(--button-color-active);
  background-color: var(--button-background-active);
  border-color: var(--button-border-active);
  background-image: url("chrome://global/skin/icons/arrow-up.svg");
}

.month-year-view > .spinner-container {
  width: 5.5rem;
  margin: 2rem 0.5rem;
}

.order-month-year > #spinner-month,
.order-year-month > #spinner-year {
  order: 1;
}

.order-month-year > #spinner-year,
.order-year-month > #spinner-month {
  order: 2;
}

.calendar-container > table:not([hidden]) {
  display: flex;
  flex-direction: column;
  border-spacing: inherit;
}

.week-header > tr,
.days-view > tr {
  display: flex;
}

.week-header > tr > th {
  opacity: .5;
}

.days-view {
  min-height: 15rem;
}

.week-header > tr > th,
.days-view > tr > td {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  font-weight: inherit;
  height: var(--date-picker-item-height);
  width: var(--date-picker-item-width);
  margin: 0.2rem;
  padding: 0;
  border: var(--border-style) transparent;
  border-radius: var(--border-radius);
}

.days-view > tr > td:hover,
.spinner-container > .spinner > div:hover {
  background-color: var(--calendar-item-background-hover);
  border-color: var(--border-color);
}

.days-view > tr > td:focus-visible {
  outline: 0.2rem solid SelectedItem;
  outline-offset: 0.2rem;
}

.days-view > tr > td.today {
  background-color: var(--today-background);
  color: var(--today-color);
  border-color: transparent;
  font-weight: bold;
}

.days-view > tr > td.today:hover {
  border-color: var(--border-color);
}

.days-view > tr > td.selection,
.spinner-container > .spinner:not(.scrolling) > div.selection {
  background-color: SelectedItem;
  color: SelectedItemText;
  border-color: transparent;
}

.days-view > tr > td.outside {
  opacity: .5;
}

.days-view > tr > td.out-of-range,
.days-view > tr > td.off-step {
  background-color: var(--disabled-background-color);
  border-color: transparent;
}

.weekend {
  color: var(--weekend-font-color);
}

#clear-button {
  height: var(--date-picker-item-height);
  font-size: 1.3rem;
  margin-top: 0.8rem;
  padding-inline: 1.2rem;
}

#time-picker,
.month-year-view:not([hidden]) {
  display: flex;
  justify-content: center;
}

.spinner-container {
  display: flex;
  flex-direction: column;
  width: var(--spinner-width);
}

.spinner-container > button {
  height: var(--spinner-button-height);
}

.spinner-container > button.up {
  background-image: url("chrome://global/skin/icons/arrow-up.svg");
}

.spinner-container > button.down {
  background-image: url("chrome://global/skin/icons/arrow-down.svg");
}

.spinner-container.hide-buttons > button {
  visibility: hidden;
}

.spinner-container > .spinner {
  position: relative;
  margin-block: var(--spinner-margin-block);
  border-radius: var(--border-radius);
  overflow-y: scroll;
  scrollbar-width: none;
  scroll-snap-type: both mandatory;
}

@media not (prefers-reduced-motion) {
  .spinner-container > .spinner {
    scroll-behavior: smooth;
  }
}

.spinner-container > .spinner:focus-visible {
  outline: 0.2rem solid SelectedItem;
  outline-offset: 0.2rem;
}

.spinner-container > .spinner > div {
  display: flex;
  place-content: center;
  align-items: center;
  box-sizing: border-box;
  margin-bottom: var(--spinner-item-margin-bottom);
  height: var(--spinner-item-height);
  border: var(--border-style) transparent;
  border-radius: var(--border-radius);
  user-select: none;
  scroll-snap-align: start;
}

.spinner-container > .spinner > div.disabled {
  visibility: hidden;
}

/* Used only in <input type="time"> */

.colon {
  display: flex;
  justify-content: center;
  align-items: center;
  width: var(--colon-width);
  margin-bottom: 0.3rem;
  cursor: default;
}

.spacer {
  width: var(--day-period-spacing-width);
}
PK
!<��yyAchrome/toolkit/skin/classic/global/design-system/tokens-brand.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* DO NOT EDIT this file directly, instead modify design-tokens.json
 * and run `npm run build` to see your changes. */

@import url("chrome://global/skin/design-system/tokens-shared.css");

@layer tokens-foundation {
  :root,
  :host(.anonymous-content-host) {
    /** Attention Dot **/
    --attention-dot-color: light-dark(#2ac3a2, #54ffbd);

    /** Background Color **/
    --background-color-canvas: light-dark(var(--color-white), var(--color-gray-90));

    /** Border **/
    --border-color-deemphasized: light-dark(var(--color-gray-30), var(--color-gray-70));
    --border-color-interactive: light-dark(var(--color-gray-60), var(--color-gray-50));

    /** Button **/
    --button-background-color: color-mix(in srgb, currentColor 7%, transparent); /* TODO Bug 1821203 - Gray use needs to be consolidated */
    --button-background-color-hover: color-mix(in srgb, currentColor 14%, transparent);
    --button-background-color-active: color-mix(in srgb, currentColor 21%, transparent);
    --button-text-color: light-dark(var(--color-gray-100), var(--color-gray-05));
    --button-text-color-primary: light-dark(var(--color-gray-05), var(--color-gray-100));

    /** Color **/
    --color-accent-primary: light-dark(var(--color-blue-50), var(--color-cyan-50));
    --color-accent-primary-hover: light-dark(var(--color-blue-60), var(--color-cyan-30));
    --color-accent-primary-active: light-dark(var(--color-blue-70), var(--color-cyan-20));

    /** Font Size **/
    --font-size-root: 15px;
    --font-size-small: 0.867rem;
    --font-size-large: 1.133rem;
    --font-size-xlarge: 1.467rem;
    --font-size-xxlarge: 1.6rem;

    /** Link **/
    --link-color: var(--color-accent-primary);
    --link-color-hover: var(--color-accent-primary-hover);
    --link-color-active: var(--color-accent-primary-active);
    --link-color-visited: var(--link-color);

    /** Text **/
    --text-color: light-dark(var(--color-gray-100), var(--color-gray-05));
  }
}
PK
!<�g����-chrome/toolkit/skin/classic/global/dialog.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* ===== dialog.css =====================================================
  == Styles used by the XUL dialog element.
  ======================================================================= */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");

/* ::::: dialog ::::: */

:host {
  padding-block: 8px 10px;
  padding-inline: 8px 10px;
}

/* ::::: dialog buttons ::::: */

button {
  font: menu;
  margin-top: 6px;
}
PK
!<;�RT<chrome/toolkit/skin/classic/global/dirListing/dirListing.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:root {
  background-color: -moz-dialog;
  color: -moz-dialogtext;
  font: message-box;
  padding-inline: 2em;
  color-scheme: light dark;
}

body {
  border: 1px solid ThreeDShadow;
  border-radius: 10px;
  padding: 3em;
  min-width: 30em;
  max-width: 65em;
  margin: 4em auto;
  background-color: Field;
  color: FieldText;
}

h1 {
  font-size: 160%;
  margin: 0 0 .6em;
  border-bottom: 1px solid ThreeDLightShadow;
  font-weight: normal;
}

a {
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

/* ensure multiple spaces are shown correctly. */
td:first-child > a {
  white-space: pre;
}

p {
  font-size: 110%;
}

#UI_goUp {
  margin-top: 0;
  float: inline-start;
}

#UI_showHidden {
  margin-top: 0;
  float: inline-end;
}

table {
  clear: both;
  width: 90%;
  margin: 0 auto;
}

thead {
  font-size: 130%;
}

/* last modified */
th:last-child {
  text-align: center;
}

th:hover > a {
  text-decoration: underline;
}

body > table > tbody > tr:hover {
  outline: 1px solid ThreeDLightShadow;
}

/* let 'Size' and 'Last Modified' take only as much space as they need and 'Name' all the rest */
td:not(:first-child) {
  width: 0;
}

.up {
  padding: 0 .5em;
  margin-inline-start: 20px;
}

.up::before {
  content: "";
  display: inline-block;
  width: 16px;
  height: 16px;
  margin-inline: -20px 4px;
  vertical-align: middle;
  background: url(chrome://global/skin/dirListing/up.png) center/16px no-repeat;
}

.dir::before {
  content: "";
  display: inline-block;
  width: 16px;
  height: 16px;
  background: url(chrome://global/skin/dirListing/folder.png) center/16px no-repeat;
}
PK
!<~B
�TT8chrome/toolkit/skin/classic/global/dirListing/folder.png�PNG


IHDR  D���gAMA���a cHRMz&�����u0�`:�p��Q<�PLTE�������������������������� � �"��������$�$�$�%�)ߣ,������������������������������������������������� �!�!�!�!�"����������������7�ވ���������߆�߄�ހ��|��z��v��P�����$��`������ނ��~��}��x��u��s��q��o��s����������������}��{��y��w��t��r��p��n��l�������߅�߃�ށ����{��y��t��q��j��h�������u��m��k��f��d������|��z��k��i��g��b��`��e��c��a��_��]��n��f��_��[��Y��^��W��U�߂��\��Z��X��V��S��Q��j��T��R��P��N��W��N��L��J��x��m��M��J��H��F��X��O��M��K��D��C��c��P��I��G��E��C��A��>��T��I��?��=��;��O��b��\��U��F��B��;��8�3�%�%�&�&�'�'�(�(�(�)�)�*�*�*�+�,�+�,ߣ,���,��tRNS�`��P�� ������������������PR��bKGD�E��tIME�


��KFIDAT8�c`�QVN^AQIY���99%U55uV
6�
��5�մ��ut98�)PRR���W��0�6422�1�5�33�෴�+P��ѵ��wptrvqus������
+

��������OHL
JNIM+H��������/(,*�J����WUVU�Ԃ��746E7�Ƕ���'tt&uUu����y�7Mh��8	(ߞ4}rJwڔ��Ӧ�̘9�k��I`���M�Y>e��.+��<ѧ8�hziG'��/Y�l9X����U�+���p���k�
���;�^[�p���k֭+�����PZ�	��
(
�|ɢ�7m޲uX�m���@�W�nY�v��5[v�ܵ��������+�,\�|������V�0'�@PeJ����{A^[�޺m��G��tt�}~h��K��[�u��'�=u�dy�
`�.>�t��'N�;���`�U� �iK��|��
�m����p�ҵ�7�
nM��֢���칳(}���S��]���!X��ģǒRO�<}����W�߼�~����>�"�0L�m��X�%tEXtdate:create2022-02-10T17:10:10+00:00<�Z�%tEXtdate:modify2022-02-10T17:10:10+00:00M��IEND�B`�PK
!<ġ��{{4chrome/toolkit/skin/classic/global/dirListing/up.png�PNG


IHDR  szz�sRGB���gAMA���a	pHYs���o�dIDATXG�V�k\U?wb���.�� �HEW���֐�h����&��^���v��~L��v!

vl�J�4I�ɼ���w�;��}��dg�z>�7��i��K��й8��p��|盁�Z�c�DH��P��.Ǫ;5�7V\뚻�N��5):}"��XPU�(��W�_��ĉ4 �GgL^Uŀ�7P0pYt�c�_�n������3��:1d5�?�X�:�#�>;3+��R���?�#�#�WE�<�cɡ�\�8��hп�ÒI�6�\W����Ta�|y��šF:�H^\]�J����T���(�Ue�MJ����2>��� �Db�Q��Z_����ȸ��篥��Q
(�-�ua�&I3R^�C
�
X�!{�6�QBY�!\�{��*�rqWs4�h�����4���Y\���u�<���g�,�.^�0w�.Ͳ^ ���zE�������jy��7����No`X0�=e���{@��I�'D;�`�=�8K�4{�������#�-�౼��>t�:@�C����ӋV5���O��K�!]�4i2�,���<E�=�I����H�_��&n���l)�����
�Zxk��p����d���F0�6���] �L��"~�䯠�[��C��k��R2?(�m�\�N�����R3�qܘJ0N|�.�2�N%r�{aj��c|r��7Jo��}.D��$��ݹް9o��:O�������7��Rԓ4�6����HL���;=�N�nq��,N���?c�M?��aC���r�$���;N�	�ϧFj��@��ENE̦�u���Ԧka��F�
4&����<a�F���3F�q��N}2�Ir�|�zM��?�
�D"gkN�����������I�\�l^̛��%ΛL6�;���Hɜ;|��7�Φs_�-hA��[��y\q�9��M�fr9�o���Z����s���S�r�|�|��3�V�(b]�Q4��p^4��Mr�W�J�v�X���u���3���?G�|?�܇�IEND�B`�PK
!<bc�332chrome/toolkit/skin/classic/global/error-pages.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/in-content/info-pages.css");

body {
  background-size: 64px 32px;
  background-repeat: repeat-x;
  padding: 0;
  /* info-pages.css sets a minimum width of 13em to the content
   * container. If we don't set a min-width here, the content
   * gets clipped in iframes with small width. We don't accomodate
   * any padding to prioritize real estate in the small viewport. */
  min-width: 13em;
}

.button-container {
  display: flex;
  flex-flow: row wrap;
  justify-content: end;
}

.button-spacer {
  flex: 1;
}

@media only screen and (max-width: 959px) {
  body {
    padding: 0 75px;
  }

  .title {
    background-image: none !important;
    padding-inline-start: 0;
    margin-inline-start: 0;
  }

  .title-text {
    padding-top: 0;
  }
}

@media only screen and (max-width: 640px) {
  .title-text {
    padding-bottom: 0;
    border-bottom: none;
  }
}

@media only screen and (max-width: 480px) {
  body {
    padding: 0 38px;
  }

  .container {
    min-width: 0;
  }

  .button-container button {
    width: 100%;
    margin: 0.66em 0 0;
  }

  .title-text {
    font-size: 26px;
  }
}

@media only screen and (max-width: 320px) {
  body {
    padding: 0 12px;
  }
}

@media only screen and (max-height: 480px) {
  body {
    /* Note: if you change the top padding, also update the image positioning
     * media query in aboutNetError.css for the certificate error case. */
    padding-top: 38px;
    /* We get rid of bottom padding for width < 640px, but
     * for height < 480px a bit of space between the content
     * and the viewport edge is nice. */
    padding-bottom: 38px;
  }
}
PK
!<����;chrome/toolkit/skin/classic/global/icons/Authentication.png�PNG


IHDR((���mvIDATx̘�C}�?+X��2��Yێ�
wÍ�۶=ӱ���v�ڜ�����F��?�k=��so���ϣ>yo�G��3>9��مI��l&)[���n,���Mύ{|j����������jL����8nooqss�x"�ӳ�[�Q����u����;���7ؽ []Hg�i�J%�_�B�?��`[��9�8:�#�J �IC�����i�ln�ף�u4���lK�����̈́ɩ1���n��t&-��=���������xY][������.{�P�cO�
�j}�E����D6�E����b0�8;?�F���iE.����-��W�>
`gw�Og�Ғ���]*������"���
�/�C(���!h�?��E�t:�;t��M�f�[���=(`[g�T<�L�'��T��#˼���p$��mH�'divn2926�:>f��c�D":H�<���)O��쟢��^O�����F؃L&p���S���-QV�JQ�uR�/��7�49ON�J�h�`�C������Ȯ-?<�$���W؝I�l6\__acc]|��}Rs�%���j�+�<0����=y�hT�Y*��O���
�#,#��O�ZQ\^^`mm�|Q�B.�GLJ�T�kw�UV��kC�qz��b�Fs>��kum���Y�t.������Ң��1���ed_��v��v�W*�˾RS[����-�BC�zy��D�t
��I? �ًý�[
&!��w��^zp���P���J�v;��ו���HZ�f��ӫ��� J�L&PQ��}��C��-���#=�
���J���r�f��q�ꔨ�*ʮժa4j���
�Հ�ΘU�eƇ��h�x0xM��e�0�<�q��y�z53!u��MP��fcc&N�
���T|�x���HMR�P%~s`�Gwu}��8c�j7���#��nƇ�eVP�|ܢbj�s� *�/Lasg�����w����4��{v���94گ��JR��T�D�V.2����c�����i�
�s�9���ǰ���~�C����h?�k�%���y��}�I_ ��
�J��h�l�AR���D^�g�j���&�X��`mS���^��7�.18ܗ.�����5�R���Ӷ�ӫT��
����O�0Z�0�\�lg,����˹�j�\&���D��k�}^��a���,0�^��j�Qk��\.+��k6�:��K��u�&�V,N�
��+�!&'G9e���\�Pg���X���q��co���ToKu pV���5�ʼnԕTL��z!�
_r���4�͊톡.~�����cc���p�z�kcs�kToC����)]����sK6�I�l.�P>A�ܓJ��RK�	u�������Jz�?�B�m����5�5�6���w	��N��1��i��kK'RB_�%�#����H��|�����<笿��b�P���>4qn���R�����
A����J�p���;�-Zx���p�r�g2��+tv�Y��"�;y^���|��64��vv7s���2b�tzl��������.�#��"�LM�g�e�8������5�,nmod	 ����\��#A����G%�Hp��Zo�j���_~P@��+4�ʚ,��Zwp�Xv�Z��C=.�Gqfv2�i�,��wP�=���㗩���lF���v.�̵K���8����ԇB�>.C���|��G��!�����3�c�/���8�<��<T�]����ئ��<�y�Sܛp��S�]��Բ�3Q��P-�����Yk��Ov��Տ�
d�+����GX
͎�\8�
�@Q:�U�= _4��ٜ&'�m�9��{(�[Y���~�b5�
r�HƄ����d9�3�&f�_�PDG:���kc��߇F�F����k�
ó�uw=%7�Sl�[O�����#�V��y�ZG2�œ������[�إ=�]*���������l=??���P�w��*�+�*�5��n����3f�ã�����i|�M�d����L�4����@3Mc"����y��UU-�$�C.9�X3��XFRUd�*������c�*UL�_T�8T���Q)P�*��������#��@~��HOXIEND�B`�PK
!<�A��7chrome/toolkit/skin/classic/global/icons/arrow-down.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m8.352 10.999 5.466-5.465a.623.623 0 0 0-.001-.884.628.628 0 0 0-.885 0L7.998 9.585l-4.93-4.934a.626.626 0 0 0-.885.885L7.648 11l.704-.001z"/>
</svg>
PK
!<�Z���:chrome/toolkit/skin/classic/global/icons/arrow-left-12.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" width="12" height="12" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m4.001 6.24 3.613 3.614a.498.498 0 0 0 .707 0 .5.5 0 0 0 0-.707L5.174 5.999 8.32 2.853a.5.5 0 0 0-.707-.707L4 5.759l.001.481z"/>
</svg>
PK
!<#�_}��;chrome/toolkit/skin/classic/global/icons/arrow-right-12.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" width="12" height="12" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M8.999 6.24 5.386 9.854a.498.498 0 0 1-.707 0 .5.5 0 0 1 0-.707L7.825 6 4.68 2.854a.5.5 0 0 1 .707-.707L9 5.759l-.001.481z"/>
</svg>
PK
!<�2���8chrome/toolkit/skin/classic/global/icons/arrow-right.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m10.999 8.352-5.465 5.466a.626.626 0 0 1-.884-.886l4.935-4.934-4.934-4.931a.626.626 0 0 1 .885-.885L11 7.647l-.001.705z"/>
</svg>
PK
!<hn���8chrome/toolkit/skin/classic/global/icons/arrow-up-12.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" width="12" height="12" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m6.24 4.001 3.614 3.613a.498.498 0 0 1 0 .707.5.5 0 0 1-.707 0L5.999 5.174 2.854 8.32a.5.5 0 0 1-.707-.707L5.759 4l.481.001z"/>
</svg>
PK
!<{*G���5chrome/toolkit/skin/classic/global/icons/arrow-up.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m8.352 5.001 5.466 5.465a.623.623 0 0 1-.001.884.628.628 0 0 1-.885 0L7.999 6.416l-4.932 4.933a.626.626 0 0 1-.885-.885L7.648 5l.704.001z"/>
</svg>
PK
!<�b��Bchrome/toolkit/skin/classic/global/icons/autoscroll-horizontal.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="10">
  <path d="m 5.707,0.292 a 1,1 0 0 0 -1.414,0 L 0.293,4.288 a 1,1 0 0 0 0,1.412 L 4.293,9.695 a 1,1 0 0 0 1.414,-1.412 l -3.293,-3.289 3.293,-3.289 a 1,1 0 0 0 0,-1.412 z M 21.707,4.288 l -4,-4 a 1,1 0 0 0 -1.414,1.412 l 3.293,3.289 -3.293,3.289 a 1,1 0 1 0 1.414,1.412 l 4,-4 a 1,1 0 0 0 0,-1.412 z" fill="#0c0c0d" fill-opacity=".8"/>
  <circle cx="11" cy="5" r="2" fill="#0c0c0d" fill-opacity=".6"/>
</svg>
PK
!<?����@chrome/toolkit/skin/classic/global/icons/autoscroll-vertical.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="22">
  <path d="m 8.280,16.293 -3.293,3.293 -3.293,-3.293 a 1,1 0 0 0 -1.414,1.414 l 4,4 a 1,1 0 0 0 1.414,0 l 4,-4 a 1,1 0 0 0 -1.414,-1.414 z M 5.694,0.293 a 1,1 0 0 0 -1.414,0 L 0.28,4.293 a 1,1 0 0 0 1.414,1.414 l 3.293,-3.293 3.293,3.293 a 1,1 0 0 0 1.414,-1.414 z" fill="#0c0c0d" fill-opacity=".8"/>
  <circle cx="5" cy="11" r="2" fill="#0c0c0d" fill-opacity=".6"/>
</svg>
PK
!<�\fߜ�7chrome/toolkit/skin/classic/global/icons/autoscroll.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="22">
  <path d="m 13.293,17.293 -2.293,2.293 -2.293,-2.293 a 1,1 0 0 0 -1.414,1.414 l 3,3 a 1,1 0 0 0 1.414,0 l 3,-3 a 1,1 0 0 0 -1.414,-1.414 z m -1.586,-17 a 1,1 0 0 0 -1.414,0 L 7.293,3.293 a 1,1 0 0 0 1.414,1.414 l 2.293,-2.293 2.293,2.293 a 1,1 0 0 0 1.414,-1.414 z M 4.707,7.293 a 1,1 0 0 0 -1.414,0 L 0.293,10.293 a 1,1 0 0 0 0,1.414 l 3,3 a 1,1 0 0 0 1.414,-1.414 l -2.293,-2.293 2.293,-2.293 a 1,1 0 0 0 0,-1.414 z m 17,3 -3,-3 a 1,1 0 0 0 -1.414,1.414 l 2.293,2.293 -2.293,2.293 a 1,1 0 1 0 1.414,1.414 l 3,-3 a 1,1 0 0 0 0,-1.414 z" fill="#0c0c0d" fill-opacity=".8"/>
  <circle cx="11" cy="11" r="2" fill="#0c0c0d" fill-opacity=".6"/>
</svg>
PK
!<LW�t��7chrome/toolkit/skin/classic/global/icons/badge-blue.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><circle fill-opacity=".2" fill="#0DF" cx="8" cy="8" r="4"/><circle stroke-opacity=".5" stroke="#0090ED" fill="#00B3F4" cx="8" cy="8" r="2.5"/></g></svg>PK
!<!�q�''4chrome/toolkit/skin/classic/global/icons/blocked.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill light-dark(black, white)" fill-opacity="context-fill-opacity">
  <path d="M8.6 1.1c-4.1 0-7.5 3.4-7.5 7.5s3.4 7.5 7.5 7.5 7.5-3.4 7.5-7.5-3.3-7.5-7.5-7.5zM12 8c.3 0 .6.3.6.6s-.3.6-.6.6l-6.8 0c-.3 0-.6-.3-.6-.6s.3-.6.6-.6L12 8z"/>
</svg>
PK
!<��R��9chrome/toolkit/skin/classic/global/icons/check-filled.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none">
  <path d="M8 .5a7.5 7.5 0 1 0 0 15 7.5 7.5 0 0 0 0-15ZM6.825 11 4.183 8.357a.625.625 0 0 1 .885-.883l2.071 2.072 3.794-3.795a.626.626 0 0 1 .885.885L7.453 11h-.628Z" fill="context-fill"/>
</svg>
PK
!<�i����:chrome/toolkit/skin/classic/global/icons/check-partial.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity" stroke-width="0.5" stroke="context-stroke none">
  <path d="M13.375 9.25a.625.625 0 0 0 0-1.25L2.625 8a.625.625 0 0 0 0 1.25l10.75 0z"/>
</svg>
PK
!<Ѓ�9,,2chrome/toolkit/skin/classic/global/icons/check.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path stroke="context-stroke none" stroke-width="0.5" d="m6.023 13-4.84-4.839a.626.626 0 0 1 .885-.885l4.307 4.308 7.559-7.561a.628.628 0 0 1 .885 0 .628.628 0 0 1 0 .885l-8.09 8.09-.706.002z"/>
</svg>
PK
!<u���6chrome/toolkit/skin/classic/global/icons/clipboard.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/.-->
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24">
  <path fill="context-fill" fill-rule="evenodd" d="M8.75 10.25h6.5V12h-6.5v-1.75ZM8.75 14.25h3.97V16H8.75v-1.75Z"/>
  <path fill="context-fill" fill-rule="evenodd" d="M17 3h-1.54a4 4 0 0 0-6.92 0H7a2.5 2.5 0 0 0-2.5 2.5v14A2.5 2.5 0 0 0 7 22h10a2.5 2.5 0 0 0 2.5-2.5v-14A2.5 2.5 0 0 0 17 3ZM9.5 8A1.5 1.5 0 0 1 8 6.5V4.75H7a.75.75 0 0 0-.75.75v14c0 .41.34.75.75.75h10c.41 0 .75-.34.75-.75v-14a.75.75 0 0 0-.75-.75h-1V6.5c0 .83-.67 1.5-1.5 1.5h-5ZM11 5.5v-2h2v2h-2Z"/>
</svg>
PK
!<�o���7chrome/toolkit/skin/classic/global/icons/close-fill.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M8 .5a7.5 7.5 0 1 0 0 15 7.5 7.5 0 0 0 0-15zm2.817 9.433a.628.628 0 0 1 0 .885.626.626 0 0 1-.884-.001L8.204 9.088l-.407-.001-1.729 1.729a.624.624 0 0 1-.885.001.628.628 0 0 1 0-.885l1.714-1.714 0-.437-1.714-1.714a.626.626 0 0 1 .885-.885l1.719 1.719.428 0 1.719-1.719a.626.626 0 0 1 .885.885L9.1 7.786l0 .428 1.717 1.719z"/>
</svg>
PK
!<�J:	��9chrome/toolkit/skin/classic/global/icons/columnpicker.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="context-fill">
  <path d="M2.03 2.06l-.03 8h4v-1H3l.03-4H6v1.97h1V5.06h2.97v2h1v-5H2.03zm1 1H6v1H3.03v-1zm3.97 0h2.97v1H7v-1zm5.53 4.97l-5.94.03 2.91 3.35 3.03-3.38z"/>
</svg>
PK
!<��uu=chrome/toolkit/skin/classic/global/icons/content-analysis.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="19" height="13" viewBox="0 0 19 13" fill="context-fill" fill-opacity="context-fill-opacity">
   <!--stroke="#5B5B66" -->
  <rect x="0.5" y="0.5" width="18" height="12" rx="1.5" fill-opacity="0" stroke="context-stroke" stroke-opacity="context-stroke-opacity"/>
  <path d="M2.46875 3.86328H4.64062C6.33203 3.86328 7.32812 4.87109 7.32812 6.66016C7.32812 8.44922 6.33203 9.5 4.64062 9.5H2.46875V3.86328ZM3.64844 4.83594V8.52344H4.5C5.55469 8.52344 6.125 7.88281 6.125 6.66406C6.125 5.48438 5.53906 4.83594 4.5 4.83594H3.64844ZM11.7305 8.52344V9.5H8.08594V3.86328H9.26562V8.52344H11.7305ZM12.4297 3.86328H14.7812C15.9375 3.86328 16.7344 4.63281 16.7344 5.79297C16.7344 6.94922 15.9102 7.72266 14.7227 7.72266H13.6094V9.5H12.4297V3.86328ZM13.6094 4.79688V6.80078H14.4648C15.1406 6.80078 15.5352 6.44141 15.5352 5.79688C15.5352 5.15625 15.1445 4.79688 14.4688 4.79688H13.6094Z" />
</svg>
PK
!<�kџ�;chrome/toolkit/skin/classic/global/icons/defaultFavicon.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M8.5 1a7.5 7.5 0 1 0 0 15 7.5 7.5 0 0 0 0-15zm2.447 1.75a6.255 6.255 0 0 1 3.756 5.125l-2.229 0A9.426 9.426 0 0 0 10.54 2.75l.407 0zm-2.049 0a8.211 8.211 0 0 1 2.321 5.125l-5.438 0A8.211 8.211 0 0 1 8.102 2.75l.796 0zm-2.846 0 .408 0a9.434 9.434 0 0 0-1.934 5.125l-2.229 0A6.254 6.254 0 0 1 6.052 2.75zm0 11.5a6.252 6.252 0 0 1-3.755-5.125l2.229 0A9.426 9.426 0 0 0 6.46 14.25l-.408 0zm2.05 0a8.211 8.211 0 0 1-2.321-5.125l5.437 0a8.211 8.211 0 0 1-2.321 5.125l-.795 0zm2.846 0-.409 0a9.418 9.418 0 0 0 1.934-5.125l2.229 0a6.253 6.253 0 0 1-3.754 5.125z"/>
</svg>
PK
!<U	3�443chrome/toolkit/skin/classic/global/icons/delete.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M7 12.4c0 .3-.3.6-.6.6s-.6-.3-.6-.6l0-4.8c-.1-.3.2-.6.6-.6s.6.3.6.6l0 4.8z"/>
  <path d="M10.2 12.4c0 .3-.3.6-.6.6s-.6-.3-.6-.6l0-4.8c0-.3.3-.6.6-.6s.6.3.6.6l0 4.8z"/>
  <path d="M13.6 3 11 3l0-1c0-1.1-.9-2-2-2L7 0C5.9 0 5 .9 5 2l0 1-2.6 0c-.4 0-.6.2-.6.6s.2.6.6.6l.4 0 0 9.8c0 1.1.9 2 2 2l6.5 0c1.1 0 2-.9 2-2l0-9.8.4 0c.3 0 .6-.3.6-.6S14 3 13.6 3zM6.3 1.8l.6-.6 2.3 0 .6.6 0 1.2-3.5 0c0 0 0-1.2 0-1.2zM12 14.1l-.6.6-6.8 0-.6-.6 0-9.9 8 0 0 9.9z"/>
</svg>
PK
!<���qq6chrome/toolkit/skin/classic/global/icons/developer.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
  <path fill="context-fill light-dark(black, white)" fill-opacity="context-fill-opacity" d="M14.555 3.2l-2.434 2.436a1.243 1.243 0 1 1-1.757-1.757L12.8 1.445A3.956 3.956 0 0 0 11 1a3.976 3.976 0 0 0-3.434 6.02l-6.273 6.273a1 1 0 1 0 1.414 1.414L8.98 8.434A3.96 3.96 0 0 0 11 9a4 4 0 0 0 4-4 3.956 3.956 0 0 0-.445-1.8z"/>
</svg>
PK
!<�����6chrome/toolkit/skin/classic/global/icons/edit-copy.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M6.035 1.25c-1 0-1.812.812-1.812 1.813h1.5c0-.173.14-.313.312-.313h5.95c.172 0 .313.14.313.313v7.65c0 .172-.14.312-.313.312v1.5c1 0 1.813-.812 1.813-1.813v-7.65c0-1-.812-1.812-1.813-1.812h-5.95z"/>
  <path fill-rule="evenodd" clip-rule="evenodd" d="M3.063 4.225c-1.001 0-1.813.812-1.813 1.813v7.65c0 1 .812 1.812 1.813 1.812h5.95c1 0 1.812-.812 1.812-1.812v-7.65c0-1.001-.812-1.813-1.813-1.813h-5.95zM2.75 6.038c0-.173.14-.313.313-.313h5.95c.172 0 .312.14.312.313v7.65c0 .172-.14.312-.313.312h-5.95a.313.313 0 0 1-.312-.312v-7.65z"/>
</svg>
PK
!<�MJ0JJ9chrome/toolkit/skin/classic/global/icons/edit-outline.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="context-fill" fill-opacity="context-fill-opacity" xmlns="http://www.w3.org/2000/svg">
<path d="M0.0184994 13.6645L0.612501 10.4635C0.687501 10.0545 0.884501 9.6805 1.1805 9.3825L9.9815 0.5805C10.7555 -0.1925 12.0145 -0.1945 12.7915 0.5805L14.4195 2.2085C15.1935 2.9835 15.1935 4.2435 14.4195 5.0185L5.6155 13.8215C5.3195 14.1165 4.9455 14.3125 4.5375 14.3875L1.3355 14.9815C1.2655 14.9935 1.1975 15.0005 1.1295 15.0005C0.8325 15.0005 0.544499 14.8835 0.3305 14.6695C0.0674992 14.4055 -0.0495005 14.0305 0.0184994 13.6645ZM12.4715 5.1965L13.6315 4.0365L13.6305 3.1885L11.8105 1.3675L10.9625 1.3685L9.8025 2.5285L12.4715 5.1965ZM4.3105 13.1585C4.4705 13.1285 4.6175 13.0515 4.7335 12.9345L11.5865 6.0815L8.9185 3.4135L2.0655 10.2655C1.9485 10.3835 1.8715 10.5305 1.8405 10.6915L1.3665 13.2485L1.7515 13.6335L4.3105 13.1585Z"/>
</svg>
PK
!<

 �1chrome/toolkit/skin/classic/global/icons/edit.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="context-fill">
  <path d="M14.354,2.353 L13.646,1.646 C12.8634135,0.869077203 11.6005865,0.869077203 10.818,1.646 L10.439,2.025 C10.243809,2.22024993 10.243809,2.53675007 10.439,2.732 L13.268,5.561 C13.4632499,5.75619097 13.7797501,5.75619097 13.975,5.561 L14.354,5.182 C15.1310392,4.39907156 15.1310392,3.13592844 14.354,2.353 L14.354,2.353 Z M9.732,3.439 C9.53675007,3.24380903 9.22024993,3.24380903 9.025,3.439 L3.246,9.218 C3.04609788,9.4202372 2.89195626,9.66304436 2.794,9.93 L1.038,14.32 C0.978937968,14.4730418 0.998703668,14.64532 1.09089211,14.7810086 C1.18308054,14.9166972 1.33596355,14.998534 1.5,15 C1.56446593,14.9999016 1.62830455,14.9873376 1.688,14.963 L6.07,13.211 C6.33884461,13.1135406 6.58319198,12.9586052 6.786,12.757 L12.565,6.979 C12.760191,6.78375007 12.760191,6.46724993 12.565,6.272 L9.732,3.439 Z M5.161,12.5 L2.612,13.52 C2.57485383,13.5348687 2.53242052,13.5261642 2.50412814,13.4978719 C2.47583577,13.4695795 2.46713127,13.4271462 2.482,13.39 L3.5,10.831 C3.51340062,10.80154 3.54023032,10.780387 3.5720041,10.7742308 C3.60377787,10.7680746 3.63656633,10.7776766 3.66,10.8 L5.2,12.335 C5.22422581,12.3595088 5.23412532,12.3947644 5.22619838,12.4283014 C5.21827143,12.4618385 5.1936351,12.488931 5.161,12.5 L5.161,12.5 Z"/>
</svg>
PK
!<2'?��8chrome/toolkit/skin/classic/global/icons/experiments.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m7 13.5-1.5 0-.5.5 0 1.5.5.5L7 16l.5-.5 0-1.5z"/>
  <path d="M14.018 4.983a21.535 21.535 0 0 0-3.017-2.514c1.317-.628 2.571-.982 3.618-.982.886 0 1.626.242 2.14.755.255.256.438.572.562.93.292-.106.603-.173.931-.173.203 0 .399.025.589.067-.179-.731-.511-1.373-1.022-1.884-1.745-1.746-4.963-1.495-8.32.352C6.141-.314 2.924-.563 1.18 1.181c-1.743 1.744-1.489 4.958.356 8.314-1.85 3.36-2.101 6.58-.356 8.326C1.98 18.62 3.089 19 4.379 19c1.526 0 3.307-.55 5.127-1.552C11.322 18.451 13.098 19 14.621 19c1.29 0 2.399-.381 3.199-1.18 2.533-2.534.862-8.173-3.802-12.837zM2.241 2.242c.514-.513 1.254-.755 2.14-.755 1.047 0 2.301.355 3.618.982a21.342 21.342 0 0 0-3.017 2.514 21.371 21.371 0 0 0-2.519 3.025c-1.164-2.436-1.344-4.645-.222-5.766zm0 14.517c-1.121-1.122-.94-3.331.223-5.767a20.923 20.923 0 0 0 1.623 2.076c.313-.401.73-.712 1.217-.89a19.058 19.058 0 0 1-2.019-2.677 19.486 19.486 0 0 1 2.758-3.458A19.497 19.497 0 0 1 9.5 3.284a19.497 19.497 0 0 1 3.457 2.759 19.448 19.448 0 0 1 2.752 3.449 19.265 19.265 0 0 1-2.752 3.465c-3.935 3.934-8.84 5.675-10.716 3.802zm14.518 0c-1.124 1.122-3.336.942-5.774-.226a21.33 21.33 0 0 0 3.033-2.515 21.42 21.42 0 0 0 2.521-3.019c1.16 2.433 1.34 4.639.22 5.76z"/>
  <path d="m19 4.5-1.5 0-.5.5 0 1.5.5.5L19 7l.5-.5 0-1.5z"/>
  <path d="M9.5 12.5c-1.654 0-3-1.346-3-3s1.346-3 3-3 3 1.346 3 3-1.346 3-3 3zm0-4.5C8.673 8 8 8.673 8 9.5S8.673 11 9.5 11s1.5-.673 1.5-1.5S10.327 8 9.5 8z"/>
</svg>
PK
!<��v��3chrome/toolkit/skin/classic/global/icons/folder.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m13.995 14-10.99 0A2.007 2.007 0 0 1 1 11.995l0-8.99C1 1.899 1.899 1 3.005 1l2.958 0a2.01 2.01 0 0 1 1.47.641L8.693 3l5.302 0C15.101 3 16 3.899 16 5.005l0 6.99A2.007 2.007 0 0 1 13.995 14zM2.85 2.25l-.6.6 0 9.3.6.6 11.3 0 .6-.6 0-7.3-.6-.6-5.73 0-.458-.2-1.445-1.559a.758.758 0 0 0-.554-.241l-3.113 0z"/>
</svg>
PK
!<#z����2chrome/toolkit/skin/classic/global/icons/heart.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="context-fill">
  <path d="M8 6s0-4 3.5-4S15 5 15 6c0 4.5-7 9-7 9z"/>
  <path d="M8 6s0-4-3.5-4S1 5 1 6c0 4.5 7 9 7 9l1-6z"/>
</svg>
PK
!<�g�**1chrome/toolkit/skin/classic/global/icons/help.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M7.625 1.75c3.446 0 6.25 2.804 6.25 6.25s-2.804 6.25-6.25 6.25-6.25-2.804-6.25-6.25 2.804-6.25 6.25-6.25m0-1.25a7.5 7.5 0 1 0 0 15 7.5 7.5 0 0 0 0-15z"/>
  <path d="M7.625 9.709A.625.625 0 0 1 7 9.084l0-.767c0-.527.37-.987.879-1.092.577-.12.996-.635.996-1.225a1.252 1.252 0 0 0-2.432-.409.626.626 0 0 1-1.181-.408A2.5 2.5 0 1 1 8.25 8.421l0 .663c0 .345-.28.625-.625.625z"/>
  <path d="m8 12-.75 0-.25-.25L7 11l.25-.25.75 0 .25.25 0 .75z"/>
</svg>
PK
!<�(�s��7chrome/toolkit/skin/classic/global/icons/highlights.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M10 14a1.62 1.62 0 0 1-1.615-1.305l-.357-1.667a1.377 1.377 0 0 0-1.056-1.055l-1.667-.357C4.524 9.447 4 8.798 4 8s.524-1.447 1.305-1.615l1.667-.357a1.38 1.38 0 0 0 1.056-1.056l.358-1.667C8.553 2.524 9.202 2 10 2s1.447.524 1.615 1.305l.357 1.667c.114.528.528.942 1.056 1.055l1.667.357C15.476 6.553 16 7.202 16 8s-.524 1.447-1.305 1.615l-1.667.357a1.378 1.378 0 0 0-1.056 1.057l-.358 1.666A1.618 1.618 0 0 1 10 14zm0-10.75c-.098 0-.331.03-.392.316L9.25 5.234A2.63 2.63 0 0 1 7.234 7.25l-1.667.357c-.286.062-.317.295-.317.393s.031.331.317.393l1.667.357a2.63 2.63 0 0 1 2.016 2.016l.358 1.668c.061.286.294.316.392.316s.331-.03.392-.316l.358-1.667a2.63 2.63 0 0 1 2.016-2.017l1.667-.357c.286-.062.317-.295.317-.393s-.031-.331-.317-.393l-1.667-.357a2.63 2.63 0 0 1-2.016-2.016l-.358-1.668c-.061-.286-.294-.316-.392-.316z"/>
  <path d="m2.444 12.151.182-.849c.086-.402.66-.402.747 0l.182.849a.381.381 0 0 0 .293.293l.849.182c.402.086.402.66 0 .747l-.849.182a.381.381 0 0 0-.293.293l-.182.849c-.086.402-.66.402-.747 0l-.182-.849a.381.381 0 0 0-.293-.293l-.849-.182c-.402-.086-.402-.66 0-.747l.849-.182a.38.38 0 0 0 .293-.293z"/>
  <path d="m2.444 2.151.182-.849c.086-.402.66-.402.747 0l.182.849a.381.381 0 0 0 .293.293l.849.182c.402.086.402.66 0 .747l-.848.183a.38.38 0 0 0-.293.293l-.183.849c-.086.402-.66.402-.747 0l-.182-.849a.38.38 0 0 0-.293-.293l-.849-.183c-.402-.086-.402-.66 0-.747l.849-.182a.38.38 0 0 0 .293-.293z"/>
</svg>
PK
!<�34�XXGchrome/toolkit/skin/classic/global/icons/indicator-private-browsing.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20">
  <circle fill="#8000D7" cx="10" cy="10" r="9.5"/>
  <path fill="#FFFFFF" d="M17.436 7.357c-1.429-1.876-4.235-2.116-5.982-.511l-.977.898-.957 0-.977-.898c-1.748-1.605-4.554-1.365-5.983.511-.185.731.101 3.431.263 3.856.325 1.715 1.727 3.005 3.408 3.005.839 0 1.599-.335 2.2-.871l.366-.313a1.813 1.813 0 0 1 2.354-.019l.55.456 0-.002c.579.466 1.289.751 2.065.751 1.68 0 3.082-1.29 3.408-3.005.161-.427.457-3.115.262-3.858z"/>
  <path fill="#8000D7" d="M6.625 8.553c-.697 0-1.317.291-1.717.743a.65.65 0 0 0 0 .848c.4.452 1.02.743 1.717.743s1.317-.291 1.717-.743a.65.65 0 0 0 0-.848c-.4-.452-1.02-.743-1.717-.743z"/>
  <path fill="#8000D7" d="M13.375 8.553c-.697 0-1.317.291-1.717.743a.65.65 0 0 0 0 .848c.4.452 1.02.743 1.717.743.697 0 1.317-.291 1.717-.743a.65.65 0 0 0 0-.848c-.4-.452-1.02-.743-1.717-.743z"/>
</svg>
PK
!<��;s��6chrome/toolkit/skin/classic/global/icons/lightbulb.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M5.625 16a.625.625 0 0 1 0-1.25l4.75 0a.625.625 0 0 1 0 1.25l-4.75 0z"/>
  <path d="m9.534 13-3.068 0a1.621 1.621 0 0 1-1.601-1.348l-.34-1.956C2.966 8.493 2.226 6.515 2.592 4.488 2.983 2.322 4.685.566 6.828.119c1.66-.343 3.352.062 4.642 1.111a5.482 5.482 0 0 1-.041 8.549l-.294 1.873A1.622 1.622 0 0 1 9.534 13zm.369-1.561.341-1.958c.046-.264.188-.504.402-.676 1.019-.818 1.603-2.022 1.603-3.306s-.572-2.487-1.569-3.298a4.247 4.247 0 0 0-2.692-.95c-.3 0-.604.03-.907.094-1.648.342-2.958 1.695-3.259 3.366a4.214 4.214 0 0 0 1.53 4.093c.215.172.358.413.404.678l.34 1.956.37.312 3.067 0 .37-.311z"/>
</svg>
PK
!<+Z&��1chrome/toolkit/skin/classic/global/icons/link.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="context-fill" fill-opacity="context-fill-opacity">
  <rect x="7" y="3.286" width="2" height="9.429" rx="1" ry="1" transform="rotate(-45 8 8)"/>
  <path d="M2.354 4.522L4.485 2.39a.5.5 0 0 1 .711 0l3.19 3.19.014-.015a2 2 0 0 0 0-2.821L6.272.616a2 2 0 0 0-2.821 0L.616 3.451a2 2 0 0 0 0 2.821L2.744 8.4a1.993 1.993 0 0 0 2.8.02l-3.19-3.186a.5.5 0 0 1 0-.712z"/>
  <path d="M15.416 9.759L13.287 7.63a2 2 0 0 0-2.821 0l-.015.015 3.189 3.189a.5.5 0 0 1 0 .711l-2.132 2.132a.5.5 0 0 1-.711 0L7.61 10.49a1.993 1.993 0 0 0 .02 2.8l2.128 2.128a2 2 0 0 0 2.821 0l2.835-2.835a2 2 0 0 0 .002-2.824z"/>
</svg>
PK
!<n����
�
4chrome/toolkit/skin/classic/global/icons/loading.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill">
  <style>
    @media not (prefers-reduced-motion) {
      @keyframes loadingRotate {
        from { rotate: 0; } to { rotate: 360deg }
      }
      #circle-arrows {
        animation: loadingRotate 1.8s linear infinite;
        transform-origin: 50% 50%;
      }
      #hourglass {
        display: none;
      }
    }

    @media (prefers-reduced-motion) {
      #circle-arrows {
        display: none;
      }
    }
  </style>
  <path id="circle-arrows" d="M9 5.528c0 .42.508.63.804.333l2.528-2.528a.47.47 0 0 0 0-.666L9.805.14A.471.471 0 0 0 9 .472v1.866A5.756 5.756 0 0 0 2.25 8c0 .942.232 1.83.635 2.615l1.143-1.143A4.208 4.208 0 0 1 3.75 8 4.254 4.254 0 0 1 8 3.75c.345 0 .68.042 1 .122v1.656zM7 10.472v1.656c.32.08.655.122 1 .122A4.254 4.254 0 0 0 12.25 8c0-.52-.107-1.013-.279-1.474l1.143-1.143c.404.786.636 1.674.636 2.617A5.756 5.756 0 0 1 7 13.662v1.866a.47.47 0 0 1-.804.333l-2.528-2.528a.47.47 0 0 1 0-.666l2.528-2.528a.47.47 0 0 1 .804.333z"/>
  <g id="hourglass">
    <path d="M13,1 C13.5522847,1 14,1.44771525 14,2 C14,2.55228475 13.5522847,3 13,3 L12.9854217,2.99990801 C12.9950817,3.16495885 13,3.33173274 13,3.5 C13,5.24679885 10.9877318,6.01090495 10.9877318,8.0017538 C10.9877318,9.99260264 13,10.7536922 13,12.5 C13,12.6686079 12.9950617,12.8357163 12.985363,13.0010943 L13,13 C13.5522847,13 14,13.4477153 14,14 C14,14.5522847 13.5522847,15 13,15 L3,15 C2.44771525,15 2,14.5522847 2,14 C2,13.4477153 2.44771525,13 3,13 L3.01463704,13.0010943 C3.00493827,12.8357163 3,12.6686079 3,12.5 C3,10.7536922 4.9877318,9.99260264 5,8.0017538 C5.0122682,6.01090495 3,5.24679885 3,3.5 C3,3.33173274 3.00491834,3.16495885 3.01457832,2.99990801 L3,3 C2.44771525,3 2,2.55228475 2,2 C2,1.44771525 2.44771525,1 3,1 L13,1 Z M10.987,3 L5.012,3 L5.00308914,3.24815712 C5.00103707,3.33163368 5,3.4155948 5,3.5 C5,5.36125069 6.99153646,6.01774089 6.99153646,8.0017538 C6.99153646,9.98576671 5,10.6393737 5,12.5 L5.00307746,12.7513676 L5.01222201,12.9998392 L5.60191711,12.9988344 L6.0425138,12.2959826 C7.02362731,10.7653275 7.67612271,10 8,10 C8.37014547,10 9.16950644,10.9996115 10.3980829,12.9988344 L10.987778,12.9998392 C10.9958674,12.8352104 11,12.66849 11,12.5 C11,10.6393737 8.98689779,10.0147381 8.98689779,8.0017538 C8.98689779,5.98876953 11,5.36125069 11,3.5 L10.9969109,3.24815712 L10.987,3 Z"/>
    <path d="M6,4 L10,4 C8.95166016,6 8.28499349,7 8,7 C7.71500651,7 7.04833984,6 6,4 Z"/>
  </g>
</svg>
PK
!<}�6��0chrome/toolkit/skin/classic/global/icons/mdn.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M48 0H0V48H48V0Z" fill="#15141A"/>
<path d="M19.2174 10.7458L11.0167 37.2165H7.64514L15.8467 10.7419L19.2174 10.7458Z" fill="white"/>
<path d="M22.2067 10.746V37.2166H19.2241V10.746H22.2067Z" fill="white"/>
<path d="M33.8051 10.7458L25.6044 37.2165H22.2299L30.4306 10.7419L33.8051 10.7458Z" fill="white"/>
<path d="M36.7878 10.746V37.2166H33.8052V10.746H36.7878Z" fill="white"/>
</svg>
PK
!<{�Z��2chrome/toolkit/skin/classic/global/icons/minus.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M13.375 9.25a.625.625 0 0 0 0-1.25L2.625 8a.625.625 0 0 0 0 1.25l10.75 0z"/>
</svg>
PK
!<��7���8chrome/toolkit/skin/classic/global/icons/open-in-new.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M10.707 1a.5.5 0 0 0-.354.854l1.454 1.454-3.625 3.625a.628.628 0 0 0 0 .885.63.63 0 0 0 .885-.001l3.625-3.625.281.281 1.173 1.173A.5.5 0 0 0 15 5.293L15 1.3l-.3-.3-3.993 0z"/>
  <path d="M7.375 2.25a.625.625 0 0 0 0-1.25L3 1a2 2 0 0 0-2 2l0 10a2 2 0 0 0 2 2l10 0a2 2 0 0 0 2-2l0-4.375a.625.625 0 0 0-1.25 0l0 4.525-.6.6-10.3 0-.6-.6 0-10.3.6-.6 4.525 0z"/>
</svg>
PK
!<ĕ?�<<;chrome/toolkit/skin/classic/global/icons/page-landscape.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M15 6.578 15 12a2 2 0 0 1-2 2L3 14a2 2 0 0 1-2-2l0-8a2 2 0 0 1 2-2l7.422 0a2 2 0 0 1 1.414.586l2.578 2.578A2 2 0 0 1 15 6.578zM10 3.25l-7.15 0-.6.6 0 8.3.6.6 10.3 0 .6-.6 0-5.15-3.15 0-.6-.6 0-3.15z"/>
</svg>
PK
!<��6ě2�29chrome/toolkit/skin/classic/global/icons/pendingpaint.png�PNG


IHDR<<:��racTLL�-fcTL<<d��:m1�IDATx�؃�<I@��l۶m�Ʋζm۶m�F�ܟr�E_�������e�o�����)���|�ӑ���w��x#�Ė��!���<+1��`�cbR����2_��.�<O,*C�|n��g�^!�v�c.�U!�x��K�`o����s�6��Y�*?�^1��B�K��	��1_��k�ږ.�D�+!x>b�Kr�DŽ�k"���)�x��c��'���\pQ�NZ��	󐅲�����'A<+ľ�nPy���X�|w\�P���G#ܖ>��:�
᷀�`
�;�b0�A7���*�#��g����'�`�-x��Q�H�����R!��}K�0t���7����a��
Xm��w��:��"�mu��ű��9��4�]��P�L�;���5���l\�}�0���
_9�~�����9l����=�q�g�})��j�w``�'�18O�<w��XPΣ�B\��pN��K�	�a?R���L^[p[p�p!�rp�!������߸U��_�����nT�'kua��G��Gқ�ᐾ)�h]�*��}�-�-x�q�/�'=�U؅Q��#8;�D<��q:d���q��D�#�-��C)�Y,©8�����L��c�q���.�!-ۇa���p����]Ў����1�$�D��F�ɗ�4G
?'�	8���<g[��5��C�u��U���@[������Eo(��0ԶD��)�������a��P�
�
�q��Y`����,�`������p+�[��.���!s��E�ú��Lŀ���r��q:�(T�hh���/�,��6�-P!ۄ��A�$�7X"z��Z�!��!ЂeP���eЂ!����k9�v�!�*��ߋ��`�A~T��4h�H��S�
�#cp��
��m0*�``��@��a��/X�]�ږz`z@�1���9��4��Z�kZ�1tnKoK��>�ԁ~e�	fcTL<<d�I��fdATx�ڃo|K@�m۶�Z�L�m�x�m۶�g�Oy'Iqss�Y�-�O�A��ꫯv)���M#r�\q܈G}"=9�ހ�{�S���x(�(ӱ3�
��mU>
�z��:xn�,�[�j�U.����B^��!8�2xn3ؗ��^5��28W�o��4�����.X���i��lkZ:C���B�|�b��.�yxn1��dpn7ľ�)��gM+��x��L���c3!JP�k
��X���P�b�F(A>��8��
ޏ&��1J��|s��
�eP�x�x\1x4�b	 �����b������̗���Ip�Ȱ���{u�T�f�	O��
�@\����<M.�ڀ�^�UmJ�������-��)�a��h�tFC92O�Kَ��q�e�(r��[���݉�ù؆K-�9>`�.NFa*�(C�`q��#ӹҚ�C��.�G�,�K�%8W�xǃ�\<��#+��e������8�X8ۑ�|�v�����Wm���ڞ�9I�bp�'�\�gVvJ��_&�e/N����
/�+����UƒV�<��<G�~�rd)����Ln�� �O-�����}lHgp16@�8���0.��,B����2%<r��Kz�|�
��U��0h}��,�1x�ehf#;`4H��c����t�l�\
�-�c�!f0j���؟����܆ G��B�.�Y8
�?���>G��v^��$zW`<�`6t��G6V�����"`6��(����}Fc&#��~�5��g����F>��5�;Ӷ��"|�Ay�C��CyL�6X��a ��B�h�sP4��[��B�ph��a��6��t0��`���l�C��8
���:��.�'C,�JC�a|zzX�#"����f��C�>==,��
Py�=��
8�Hӻв�QGR�a�vYT�$�*P��6ƴk��Zni
�aZ��P�a�a½%zay��}��xfcTL<<d���TfdATx���#I��~��m۶m�u��g۶m۶m�ܳU��T�;�ɤ{��_y�QW\q�O�،��ڌb(_|o��k��
�WJ�1
��,Pzaz�
^�K�,�cpJs���~��`h�'�!�Q>�g	��Q�`p)n���K�^��3|a�|�%���ף�_00��'�
	Ƈ���-�]s�o�=/�<�
�'�|���y��c��+�YB�<�&�0EP�8[�氖�N/W�w�1�E	�
�]�g��4}�,&���-�//������R�S2�é�uheP�m��㠲t�!�jB�UxI0�<�Z��\��3�b�@���p[Z�m†�)v��d����:8	�W�#�����1f����Z��r����TGuh�I?܉�mw��A;F��r�M�c���<\����=�C�<��gP��\M�(C�/�L�Mr��	[�G��CpSᑾ������֍xwc
ŸZu4�hp!&�
�A&�*�R����7pJ��?�c0�l{��4�rgU�_�����w�>҇�Fփ��y�h����X�Z,EK(O��i|�װ!����X��b����<&&\�i��<��7�S.�E��>�
��"Χ��B\�G���H�������0h��]�)��c�VD�a��鮷D�.h	�ŧ��#m���-�1Q�i���{m�y�G�G�����G|���l�--��ʠ��#��~���Pd9T|�<<�wx=�BY�6���+�Q�6!#e�
�b	�q~�#:bz��a��2��4NN>�A+���;13����Cz��AB=R��Co��^�]����!�_
���&V00)�K���Z�C��D��\�u�5�L �+���D���Ђ�P9�"%��<�`���7B��>�;At��,X//�,̿�n���jZj��h	�T�`4j1�P:�WZ~���M�`q���P��P� ��fcTL<<d�&v�fdATxb����gߚ#a��k�m۶m�J�m۶m۶g��S�Yt����[�&_R5�}���RV�
\������:x%n�XR�W�_�O�0�!nT�����㢐�o�![]��E1d<��2x���)W�F%x���H19��ex@1�ep�)��1��ָK�����H�4��x@1�װ���9�྆��|��mq��=��K����m�2+�&�iM����,�;cz�bP�3���s��~�{; ���ue<A�Ft��Q��SVKWVK�*�� q��cpk�Eh�Q������1�W޴^��+���� 0؋ɨV��bOG��=pk(�Ve�Ј�x��1�A`a;��Z9��P&��.ãZ��&!���!��x��>ۿtOl��ކj��x�2�>L���pFa�Eto�3,B��&T�2��}�;�,�Ms��+�������Բ/�c�{�i݀gp�ߵ�\
.�d�@`a&��I��kx��k8Ur���C<9�'p��<Y��'�%~�(������>��~Z�q�\
��a�0O�K���(���wy�⻈�0���
��N�-nFG�*���`��8S�1ͯM��+�-,���M;��;KW@�܈�Fo ���
��b6�w�c���Y���V�mM��G�8���D�ĕ����Y��E�<A�8�!* 1�C��o��t0�{ !5xG�{5��?q �)�x+{y���n��I�8q$�c%�'����e85�Q���� !Gc��BB���Vߋ��ثĮ�DSD��(�a������p@���3!�����\�c���)w��J��v��b$$�`�Plt<B�݄����|���.JpOH�`�*%x��`e��I=�%{��a�5��5$�``$6cd>�Z�3-G���s�;9X_-9�/��ԁ��efcTL<<d������fdATx�ك�eI��j�m۶m���m�m۶mۻ�O�/��$�Rs�ުt�7�/}���W\�S&ಓ&����M]��/���<�A;ԁq�Ma4A_4	<�Y�<G�2�wpSD�Z��G!u��K	^����M	�3�
"E���⨢���|,T�W�J�5�_�݂�@KD�Q	/�QE�P��D��eni�]r��uJ�2_��BÐ�r=����i��QŬ�vZM�m�cQ�J�<����{6(�{PƢ�0
�\�ɵ-`,�(�kc��*��`,��0n>�k0� RLFm	y��H��Q\(c��2ҚcnV���Ed�CP�$�!:i��n��g��TU�7�f���ຈ�@ȟz1ڞTSe�9I~o0W�C�OX���<ԁ�%��ݾ�R+,r�^�b�Up�c�a�?�u����!������b&�ʲ񨂑�,�"t�Z�Ir����ʐމin-�a�bF�Ik;n����
.�r���|�JDVb�a)D)��C�B)
���K�P���ɺX0����weep�!=��5�7i���\p.��aM��CG܀gq�"FW~����^nQ^,��D��x�� ~�~�>X�֢(��x5Đ��ca��{M�w�%GaҼ���'Ձ��&fa&jB��	���i=��ŏȜ�#�ň�W��I���2�7�؂���V���ތ|>C����*KpG��s�d �
��t�>��B���%���(�/b7�� ^�(���{?��y\	~
�`,�0+10�����(։�d�g��LJ���=���>��|\P	~B���b�k��(�w�
�g��&v0p*&�.��J�h�@pc��?�"��ˑQ색,\��|�Qb�A��+�y%�B��S��E0q�S��ނ���&�ă�="v�e�V�/Lj�@7�A��tk��|���U�`yZ�\��|�R���v���fcTL	<<d����
fdAT
x�؃�$K���ڶm۶�z��N�l۶m��Oy_��INMmOO��&�p���:��ĉ!�b(V�6�0��
+���`�U�)�.ڣ.��0�%,/�!Y���:�,��Yڄ�
+a1�b�ǥJ�А�ma�c���n!�s�V���\�*��.h�C�tlUtHj[�
�h�ApSG�$���X�D�� x��������>¤�]��o��M�m=��"�T��{���Y��^�b�<�Xt�
����q���pZڨ��q�-8r�����*���̇�zY�9��c�(����:��P*�尧-G���p��v�r`(�l��~��z�V���Q�v��DT�n��ñ[�y���!���.L Mq-�x��J��b��([����[�F�Y��xD�
��#t&� ����`�%�E������%}Zgs�l��X��	,Z�� 6�_�Ѳ2�
��@o=ȡ?	�X�n��r��Y^��"X��}�J0818U)��^҃��%��R.Z�J��
r��O���0���a܃�e3x��� �:nÃ�1t�3���@{�(+\����`?~�C���^l�b|�
?�nR����
��y�8�E+�=mH<�3�g\'��-d51�PSNY���=�;��W�	�7�����>B����^<�tR.�Kp��q��~�������U|/<!��(܎<�m����b��/1��R|/�k�B�kx{䊊g񢄅0�q��=,L�n,G�C|��*^���k�P�GM�S�(,@�,��#N�B��5���kf�E�Ƥ]��r��?P�y�f%�9�K2�|��,��Jt*���؛`"��bt��B���~�f17��J�(��+6�D
��EŔ����wQcp�W�W���ρ�-�	���ab6���ImK���a�:a:������J�`}�����؃��i)��I'�S��ELfcTL<<d��f*
fdATx�؃�$K�������m�6�ٶm۶mۈ�����%�J~�5=3Շ��QMo}wj����p�
!�1[���4L(��' ��9���"�l=��ۣ�=�G��ߪA��by��b�xk���=��V!��xM�����$�!S��֋���0����	5!��X+&��+����H�� �U x"�wR����NewB$,H�>���Ė��L�ۅ����X��qҪE$L��I�;�����P�1�U���֘U�Z[��q�`ƣ�o�8���I���ii����:D8� 3n�H��v0���;��DK�JI���
]]�5�va�XZ�	�^����S���s��; �`/�<j��Lj��N#q����~2�
�a�8��l�]�/�yFoF>@l'{�Fh(�>��p�<+�<�#�tLB���GsLE�P����U%OZ��,�%]KzzT�hY�5؉%	lZ�p&vaP��Z&�)�)����/���/�F&!,�Ÿ��%X����]�@����������M0������-G�^t�oZ��ٴ�)M�M��Ja=��/x�`��p;.��jO����
��m�=?w[.@}%�{�N'�<w;�D�r��8?��䂅[��R���������Z�.Wa�o�u���{	lZ��nO��-���t�`�^m`bj�阊��OY�q�gt_W�FB�
b	�;�r�w0(Fl
��S{݇��7��F�'���9b�vd�<��<#�j<e�	i1�Nw�)�3�	�8lŗ�}j���L��י�?��)��F�*�ϰm��j����}��5�L�e9�s�~-a��EX���%�RXe�}v�֘�xJ8.采z|.b�D���`,׉�'�'��D�3x�.K*x.������:8�WD�G�Z��xDߎl��%�R8&f�m-���>^ľ�B��K�<U/��,L�#��Z�Ô,g���miV�SM�`�3Qs$}k)OZ�g�S��`���5�Ԁ�]`afcTL
<<d��ePfdATxb����gL��p�3>۶m۾[s���m۶m�6
���(�l�2U�LF��T����~n�I�Υ � �d
�P��^O��+��i�)x�a��xO`�gM����\�����5���
7>)�����u�H��b��B��E.���,�b�"x��.��(n�}����"��	6e!x<����=��3O���}x�;�A�!a\9��c��4NZ��	��ꤵ�1(�*�̷��Y����U���b:1�6�$�����7Y���R�p�9PP��gU��H^�b(��'s*E����nx����8�����[�ӿNkP�`�)x~ũ�@���M�P���aݰ��5�!a�K,�rd���	Vۮ���2�gqE�eh1&����%��"��A���j,F(���x<�$8��7�O6OZ�0��8�aD���T�k���|X�����Ѳ��	�	���5~��M�(�OB؁�p����!�7׈qf���C�3۬,>Y��>��f�I�08�)}Y'��[�.Z�t�E�[�	�	n/�#x�ãrd��%8Ks���3͛� �7�Õ��1)���p>�g�C��J�"�$8���_��blG$��=x	�Yx}�Ҳ3��6�||f�Y�Sp��Cɂ�[���8��`'��B�����OYX�,�Ǚ�'	�	�G;O�&Oaf����ů	S�.K��<�{fζ��uͭi��;4"h�Ej
�cR�~	;�
�/k��h��M�/܊;t�e0gj�mW�u8�-���Y!�>ml�Tkc.��G_����؊�:K��������_�w�~x��W��;��?.P�s���0�����ׄ��6x:���W�&�.�*�`����up�/bD���&!�2�]���j�t�5�q�`����(bY��J!x����B�V���e˅�n��4���eeZl�_��L��L���&b-&v��OZ���5]8X~Zr�(9,���'fcTL<<d��:��fdATx��S{,K@�c۶m۶�>�m۶m۶�Sκ�H�鯦��S]���F!�.��r�☉��f"����4S�rp<MYc
��
L���	�����Q߄�j����3���t�
���^���{z�>[�
���!�g���OpN����	���!�w�!8�
��wl���B�O�7�i�EC��"�����r����^x]5ڻ��c]e���`x���Zi-�	؂4�A�,d�����f�����1���6�b|_��Xep��W��R�<ʠ/�k�^(x<�<�ц��>�o@��Zx�͈	����L����|��(�$�P+�L����[�0�1����'�{��0�|~lPԮ&�A��}n���k�ϰ�R|o�U��r��0�If؎��e���� 6�=���ѿ��p��K���Z�V`�,<z��ۣ�<��\i��s�%�>�Kz=��ji�����t��ZTaYZ61-�-�i�����q��sq���+��FE��,c����g�`�2�Y��".\�삛�%=����zs�����.��a��3�à��8U]���/jE��S4{�+��8/�E�
�#q��*�)&8�*<-�F�;����>�h�Q�u��.i�l��Ë��`Ъ�)��
a�8��\:�1o��P�0��YeM�і��M�+
�>��KxnŃ�݊Ab{�t\����)\�KqR��Ѧ�a��+1İ��
j.
|8.����q�6Cp����a>��<��>�uI��U��-.��>fC���.����K��2h�ۅ૴���M�����B� ���az�R�}xP���W��e
.��z�����Bin(͑B��e�xP0���XPU�tC�qP�����Q����N���Cn�s��p<
�A�-�ł�.���7"bp
���\O�gC
�����	���}P���
QMK����B��$�lD���WZ8���ۚr��[r�N��O)1ppfcTL<<d�;��fdATxb���'�k�ϵ$q���w�m۶mŞ�ڶm۶m�s��wRLMͷ�3�n��!>�J����e���{��qkw��'���<%�	e�I��X�I*�����
1/���v�z��cw���j3�\!�s#ΠD�e3���a�,8��6��T�C">5B�A��;����W��HjXz^�0Dp8�-I��S��&R�w	��8�n�0J̴�,�k���؎<�F_� `0��
�e�[i�131�4�b|��:6Bi4
��X-�	�S�4F��E�`L�ׂ1I3A��'�ՀW�cR�ǟ��p}u���Ɨ�\�p�·��oΆ��N�r��KPGc�.x:�6�������8a&6c[�}^�SP)G�>�����K�eI�&Xlz�ނ�
���qb�0=�Q�8
�D�<� ���	3����&x�^�#��i����S���һ0 ��e
{�
>�	\�v��1.�Բ��
�
��l��w�����JHKP�@� �Z~�w85��%pX�LY��	W����ҧu���žw���{�{���V<�k16��C-�Žx���ǃ8�;-��t<���;l�Q���`�[<�p�g�)ma���c��6�x�Ї��g#�(,nqT@EL��܈_��S8Qx
�D��^�o;���``\G-c5��a�fn{9� @p9��	q�֧��ự���q��)�.\�˰��z6�1��4�Q�l��Jd�4��-B��P.�p��V(�Z!�4#?��
���y�/�9�<_�!���P�-�^�q�@yT
��_��g�^���Cq��`R�s4��P���m��񶃳�Z��}">g
�
��^�{ۡBKp�`���mB�u�E�E���f�t!x>T�`�D!x��`y�-�
,��ݘ԰4K1*�`` �a`ڵ�gZX���ۺsp�p�w�d�a"�Ԃ��uVfcTL<<d����@�fdATxb���'f�@{��k9`|�׶m۶��.��m۶m۶m����i����ig.�_r����S|V�������s���֥�X�aP�(��a���s�A��)߀�n�<DX�B��a���#��gQJ0�)��϶��H0��'�l��&:'���_�d/X��%|�lp5-�.���e��ľ�r���������B�?�.8��Xi�����ZiM��E(@i��kB�ck��>bF{(�,�a4�����(
O~G�-}��-�%��[�L�6x0^\��Prx,"�^�z#"�c�4o�R�����xQ�y��VP!��r �e��<?b�xa-0
��k�ŋ�&�ZcM��P1��W�]�w:��H�'�
݉�P����������q�a�he�xh�a���<\�><m��Bp?��=�\9�N8/h����l4Kr�57�tK���tJki��r܍gq��Ak�a#�?�t�1�18������r$�i���0
YU���a���i�c���'k�N��^�`�A�}���Wzr���������8��T�v�y؋
io�t�r�Y}���^ܤ1�>���&��v�x&�¨8�n^���&C�w]>��cz%F�:M1�B�2�f�C1̓�i�I��^�n8W8�b>��+�@���Zzkb�CO��8\rx��U�C�#�2�]M��1Jc.�p~�-�"SaQ�g�~�a&ץq�Z�c�슚�,!�8��E8%d.T�4(�B1����b���c"���
_0��3.b��
9E�B�����e�~\,�]Fp'��e���kb�@��h�{���$!�\�N ������<�C���/�o3x�{
�	�Um3x�<
*V�����Z�0���J:X8���jZ����Z0�Ю.�Z�+-K�k�y�9X�-Y�?S���ޏbgfcTL<<d�g�:fdATx��Ӛ';@�q�۶m�L�m۶m۶m�G�u1����t���.~������+��§R�����A)�/���Ƈ1�5���
��7��>X��P>(C�CC���"��1�2�q���qx�`F���,��ۄ�P�ap9��O�</	�e|2~��\����g�:���J�}����ixIpv�7���P���B��"x�%�ѐ�pO� D߂��x^���=��`A�Ik~\�פ5�0�P��	!����kB�g���(�F�����;�Pče�Z~Nx-�ay-}#e�_���
��A�4�UOOb_C�e5����m����8
�G#���O��8���`����[l���C�"�>�8*�*�ݣ�0�_�H�/��C��F(OF����F�Sz"nt��z���O��_am}��R��=�S<ov��"��Q�c������S��V\pIWK�L�s����p?.�|h�ě��b
���MBKpKp16�j���jlD1T ��-��`<
��?oN`u����	�V_Vru��-	��4��KzM#\�c�ߡ�r�����8G�=�'�X��X��yw�Q�$�LTx�-�&蘥h�e�!X
�D�=w�����4�Ř���p��m����)r`�%�`-��/�%��ph��v4�����gc�a锣��)�ftG�a��͎�5��u�^���n��p�G�M¯�z����(�ڢ�-��%v�B`;N�٘ x1t�
�?�0ZP]�=|
8#��(���*b�A(��X뺇�zcF숗��!x�ón4�PڌR�WTwA��f��8U0��������x���B�f�-P1K�
v�А�kp��}���Ђ���{YbB%�B:�.�.!�x�Ap%�A�A���8U0*u00Z��g�8!�pg\�u�C|w��@%
�u�6��'X�gCe,,B]Km0m�r��Ս迖��K�H0�	˯%_v�V#���tEXtSoftwareJapng r119'�aIEND�B`�PK
!<5��~$$8chrome/toolkit/skin/classic/global/icons/performance.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
  <path fill="context-fill light-dark(black, white)" d="M8 1a8 8 0 0 0-8 8 7.89 7.89 0 0 0 .78 3.43 1 1 0 0 0 .9.57.94.94 0 0 0 .43-.1 1 1 0 0 0 .47-1.33A6.07 6.07 0 0 1 2 9a6 6 0 0 1 12 0 5.93 5.93 0 0 1-.59 2.57 1 1 0 0 0 1.81.86A7.89 7.89 0 0 0 16 9a8 8 0 0 0-8-8z"/>
  <path fill="context-fill light-dark(black, white)" d="M11.77 7.08a.5.5 0 0 0-.69.15L8.62 11.1A2.12 2.12 0 0 0 8 11a2 2 0 0 0 0 4 2.05 2.05 0 0 0 1.12-.34 2.31 2.31 0 0 0 .54-.54 2 2 0 0 0 0-2.24 2.31 2.31 0 0 0-.2-.24l2.46-3.87a.5.5 0 0 0-.15-.69z"/>
</svg>
PK
!<���OO3chrome/toolkit/skin/classic/global/icons/plugin.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m13 3 0-1a1 1 0 0 0-1-1l-2 0a1 1 0 0 0-1 1l0 1-2 0 0-1a1 1 0 0 0-1-1L4 1a1 1 0 0 0-1 1l0 1a2 2 0 0 0-2 2l0 7a2 2 0 0 0 2 2l10 0a2 2 0 0 0 2-2l0-7a2 2 0 0 0-2-2zm.75 9.15-.6.6-10.3 0-.6-.6 0-7.3.6-.6 10.3 0 .6.6 0 7.3z"/>
</svg>
PK
!<A1p��4chrome/toolkit/skin/classic/global/icons/plus-20.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" fill="context-fill" fill-opacity="context-fill-opacity"><path d="M9.125 10.875V19h1.75v-8.125H19v-1.75h-8.125V1h-1.75v8.125H1v1.75h8.125z"/></svg>
PK
!<���ؾ�;chrome/toolkit/skin/classic/global/icons/pocket-favicon.ico   �( @ �WB� WB�`WB�WB�WB�WB�WB�WB�WB�`WB� WB�`WB��WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB��WB�`WB�PWB��WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB��WB�PWB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB��WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB��WB�WB�WB��WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB��WB�WB��WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB��WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB� WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB� WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB��WB�`WB�`WB��WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB��WB�WB�WB��WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�pWB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB��WB�WB�WB��WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�pWB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB��WB�WB�WB��WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB��WB�WB�WB�WB�WB�WB�WB�WB�WB�WB��WB�WB�WB��WB�WB�WB�WB�WB�WB�WB�WB�WB�WB��WB�WB�WB�WB�WB�WB�WB�WB�WB�WB��WB�WB�`WB�`WB�WB��WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB��WB�WB�`WB�WB�WB�`WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�`WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�WB�����������������������x<>���������������PK
!<c;�3chrome/toolkit/skin/classic/global/icons/pocket.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M13 2 3 2a2 2 0 0 0-2 2l0 3.524c0 .942.204 1.858.56 2.724.382.865.865 1.604 1.502 2.24a7.14 7.14 0 0 0 2.24 1.476c.865.382 1.756.56 2.724.56a7.15 7.15 0 0 0 2.724-.56 6.433 6.433 0 0 0 2.215-1.502 7.14 7.14 0 0 0 1.476-2.24A6.623 6.623 0 0 0 15 7.498L15 4a2 2 0 0 0-2-2zm-1.183 4.523L8.34 10l-.68 0-3.478-3.477a.626.626 0 0 1 .885-.885L8 8.572l2.933-2.934a.626.626 0 0 1 .885 0 .63.63 0 0 1-.001.885z"/>
</svg>
PK
!<&i���2chrome/toolkit/skin/classic/global/icons/print.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m13 4-1 0 0-2a2 2 0 0 0-2-2L6 0a2 2 0 0 0-2 2l0 2-1 0a2 2 0 0 0-2 2l0 5a2 2 0 0 0 2 2l1 0 0 1a2 2 0 0 0 2 2l4 0a2 2 0 0 0 2-2l0-1 1 0a2 2 0 0 0 2-2l0-5a2 2 0 0 0-2-2zm-2.25 10.15-.6.6-4.3 0-.6-.6 0-4.15 5.5 0 0 4.15zm0-10.15-5.5 0 0-2.15.6-.6 4.3 0 .6.6 0 2.15zM13 7.6l-.4.4-1.2 0-.4-.4 0-1.2.4-.4 1.2 0 .4.4 0 1.2z"/>
</svg>
PK
!<9���	�	8chrome/toolkit/skin/classic/global/icons/rating-star.svg<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
  <!--
       This image defines three versions of the star:
       #full = star filled with full color
       #half = half-filled star (full color at left, transparent color at right)
       #empty = star filled with transparent color
  -->

  <!-- Default image: full star -->
  <view id="full" viewBox="0 0 64 64" />
  <view id="half" viewBox="0 64 64 64" />
  <view id="empty" viewBox="0 128 64 64" />

  <defs>
    <g id="star-shape" fill="context-fill" transform="translate(-140.000000, -607.000000)" fill-opacity="context-fill-opacity">
      <path d="M154.994575,670.99995 C153.704598,671.000763 152.477615,670.442079 151.630967,669.468394 C150.784319,668.49471 150.401158,667.201652 150.580582,665.923653 L153.046749,648.259919 L141.193762,635.514481 C140.080773,634.318044 139.711733,632.608076 140.232152,631.058811 C140.752571,629.509546 142.078939,628.369589 143.688275,628.088421 L160.214424,625.130961 L168.013827,609.468577 C168.767364,607.955994 170.3113,607 172.000594,607 C173.689888,607 175.233824,607.955994 175.98736,609.468577 L183.790813,625.130961 L200.329111,628.08437 C201.934946,628.371492 203.25546,629.513805 203.771316,631.062053 C204.287172,632.610301 203.915846,634.316807 202.803377,635.51043 L190.954439,648.26397 L193.420606,665.923653 C193.652457,667.578241 192.93975,669.223573 191.574418,670.185702 C190.209085,671.147831 188.420524,671.265104 186.941351,670.489485 L172.002619,662.698806 L157.047688,670.50569 C156.413201,670.833752 155.708782,671.003331 154.994575,670.99995 Z"></path>
    </g>
    <clipPath id="left-half">
      <rect x="0" y="0" width="50%" height="100%" />
    </clipPath>
    <clipPath id="right-half">
      <rect x="50%" y="0" width="50%" height="100%" />
    </clipPath>
  </defs>

  <!-- full -->
  <use href="#star-shape" x="0" y="0" />

  <!-- half -->
  <g transform="translate(0, 64)">
    <use href="#star-shape" clip-path="url(#left-half)" />
    <use href="#star-shape" clip-path="url(#right-half)" opacity="0.25" />
  </g>

  <!-- empty -->
  <g transform="translate(0, 128)">
    <use href="#star-shape" opacity="0.25" />
  </g>
</svg>
PK
!<�T'��4chrome/toolkit/skin/classic/global/icons/resizer.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15">
  <path d="M14.342 2.129l-.482-.483a.5.5 0 0 1 0 .708L2.354 13.86a.5.5 0 0 1-.708 0l.483.482a.5.5 0 0 0 .707 0L14.342 2.836a.5.5 0 0 0 0-.707z" fill="#f9f9fa" opacity=".5"/>
  <path d="M14.356 5.649l-.5-.5a.5.5 0 0 1 0 .707l-8 8a.5.5 0 0 1-.707 0l.5.5a.5.5 0 0 0 .707 0l8-8a.5.5 0 0 0 0-.707z" fill="#f9f9fa" opacity=".5"/>
  <path d="M14.363 9.158l-.511-.511a.5.5 0 0 1 0 .707l-4.5 4.5a.5.5 0 0 1-.707 0l.511.511a.5.5 0 0 0 .707 0l4.5-4.5a.5.5 0 0 0 0-.707z" fill="#f9f9fa" opacity=".5"/>
  <path d="M14.315 12.621l-.468-.468a.5.5 0 0 1 0 .707l-.993.993a.5.5 0 0 1-.707 0l.468.468a.5.5 0 0 0 .707 0l.993-.993a.5.5 0 0 0 0-.707z" fill="#f9f9fa" opacity=".5"/>
  <path d="M13.86 1.646a.5.5 0 0 0-.708 0L1.646 13.152a.5.5 0 1 0 .708.708L13.86 2.354a.5.5 0 0 0 0-.708z" fill="#0c0c0d" opacity=".6"/>
  <path d="M13.854 5.146a.5.5 0 0 0-.707 0l-8 8a.5.5 0 0 0 .707.707l8-8a.5.5 0 0 0 0-.707z" fill="#0c0c0d" opacity=".6"/>
  <path d="M13.852 8.647a.5.5 0 0 0-.707 0l-4.5 4.5a.5.5 0 0 0 .707.707l4.5-4.5a.5.5 0 0 0 0-.707z" fill="#0c0c0d" opacity=".6"/>
  <path d="M13.847 12.153a.5.5 0 0 0-.707 0l-.993.993a.5.5 0 1 0 .707.707l.993-.993a.5.5 0 0 0 0-.707z" fill="#0c0c0d" opacity=".6"/>
</svg>
PK
!<���B55;chrome/toolkit/skin/classic/global/icons/search-textbox.svg<?xml version="1.0" encoding="UTF-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
  <path fill="#939393" fill-rule="evenodd" d="M11.354,10.646l-0.707.707L7.295,8A4.483,4.483,0,1,1,9,4.5,4.458,4.458,0,0,1,8,7.295ZM4.5,1A3.5,3.5,0,1,0,8,4.5,3.5,3.5,0,0,0,4.5,1Z" transform="scale(-1, 1) translate(-12, 0)"/>
</svg>PK
!<����=chrome/toolkit/skin/classic/global/icons/security-warning.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m5.172 14.75-3.322 0-.6-.6 0-5.3.6-.6 6.947 0 .266-.464c.227-.396.571-.648.95-.784C10.009 7.001 10.005 7 10 7l0-3c0-2.206-1.794-4-4-4S2 1.794 2 4l0 3a2 2 0 0 0-2 2l0 5a2 2 0 0 0 2 2l3.359 0a1.778 1.778 0 0 1-.187-1.25zM3.25 4c0-1.517 1.233-2.75 2.75-2.75S8.75 2.483 8.75 4l0 3-5.5 0 0-3z"/>
  <path d="M14.971 14.266 11.66 8.493c-.454-.791-1.615-.791-2.069 0L6.28 14.266C5.834 15.041 6.406 16 7.314 16l6.623 0c.907 0 1.479-.959 1.034-1.734zm-3.721-1.892a.625.625 0 0 1-1.25 0l0-1.747a.625.625 0 0 1 1.25 0l0 1.747zM10.625 15a.625.625 0 1 1 0-1.25.625.625 0 0 1 0 1.25z"/>
</svg>
PK
!<cjH�gg5chrome/toolkit/skin/classic/global/icons/security.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m12 7 0-3c0-2.206-1.794-4-4-4S4 1.794 4 4l0 3a2 2 0 0 0-2 2l0 5a2 2 0 0 0 2 2l8 0a2 2 0 0 0 2-2l0-5a2 2 0 0 0-2-2zM5.25 4c0-1.517 1.233-2.75 2.75-2.75S10.75 2.483 10.75 4l0 3-5.5 0 0-3zm7.5 10.15-.6.6-8.3 0-.6-.6 0-5.3.6-.6 8.3 0 .6.6 0 5.3z"/>
</svg>
PK
!<:�7!YY7chrome/toolkit/skin/classic/global/icons/sort-arrow.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
  <path fill="context-fill" d="M12,6l-4.016,4L4,6H12z"/>
</svg>

PK
!<@r[���;chrome/toolkit/skin/classic/global/icons/thumbs-down-20.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)" fill="context-fill" fill-opacity="context-fill-opacity"><path fill-rule="evenodd" clip-rule="evenodd" d="M5 2.125h9.656c1.388 0 2.614.885 3.054 2.201l2.145 6.436a2.751 2.751 0 0 1-.382 2.504 2.75 2.75 0 0 1-2.254 1.153H13.23v2.243a2.843 2.843 0 0 1-2.842 2.838 1.774 1.774 0 0 1-1.687-1.216l-.994-2.982a1.809 1.809 0 0 0-.197-.408l-1.845-2.813H5v.044a1 1 0 0 1-1 1H1a1 1 0 0 1-1-1v-10.5a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v.5zm0 1.75h9.656c.633 0 1.193.403 1.394 1.005l2.145 6.435c.105.318.055.656-.141.928a1.02 1.02 0 0 1-.835.427H11.48v3.992c0 .6-.488 1.088-1.089 1.088l-1.025-3.005a3.55 3.55 0 0 0-.393-.81L6.61 10.331H5V3.875z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h20v20H0z"/></clipPath></defs></svg>PK
!<�E���9chrome/toolkit/skin/classic/global/icons/thumbs-up-20.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg width="20" height="20" xmlns="http://www.w3.org/2000/svg" fill="context-fill" fill-opacity="context-fill-opacity"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M5 18h9.656a3.217 3.217 0 0 0 3.054-2.201l2.145-6.436a2.751 2.751 0 0 0-.382-2.504 2.75 2.75 0 0 0-2.254-1.153H13.23V3.463A2.843 2.843 0 0 0 10.388.625c-.768 0-1.446.489-1.687 1.216l-.994 2.982a1.809 1.809 0 0 1-.197.408L5.665 8.044H5V8a1 1 0 0 0-1-1H1a1 1 0 0 0-1 1v10.5a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1V18zm0-1.75h9.656c.633 0 1.193-.403 1.394-1.005l2.145-6.435a1.022 1.022 0 0 0-.141-.928 1.02 1.02 0 0 0-.835-.427H11.48V3.463c0-.6-.488-1.088-1.089-1.088L9.366 5.38a3.55 3.55 0 0 1-.393.81L6.61 9.794H5v6.456z"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h20v20H0z"/></clipPath></defs></svg>PK
!<b�8D��5chrome/toolkit/skin/classic/global/icons/trending.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M12.7197 5.71968L11.354 4.354C11.039 4.039 11.262 3.5 11.707 3.5H15.5C15.776 3.5 16 3.724 16 4V7.793C16 8.238 15.461 8.462 15.146 8.147L13.7798 6.78084L9.03033 11.5303C8.73743 11.8232 8.26256 11.8232 7.96967 11.5303L5.5 9.06065L1.08233 13.4783L0.0216675 12.4177L4.96967 7.46966C5.26256 7.17677 5.73743 7.17677 6.03033 7.46966L8.5 9.93933L12.7197 5.71968Z"/>
</svg>
PK
!<[�ms1chrome/toolkit/skin/classic/global/icons/undo.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
  <path fill="context-fill" d="M16,11 C16,11.5522847 15.5522847,12 15,12 C14.4477153,12 14,11.5522847 14,11 C14,8.23857625 11.7614237,6 9,6 C6.94968096,6 5.18760031,7.23409522 4.41604369,9 L7.0043326,9 C7.55661734,9 8.0043326,9.44771525 8.0043326,10 C8.0043326,10.5522847 7.55661734,11 7.0043326,11 L2,11 C1.44771525,11 1,10.5522847 1,10 L1,5 C1,4.44771525 1.44771525,4 2,4 C2.55228475,4 3,4.44771525 3,5 L3,7.39241339 C4.22489715,5.35958217 6.45367486,4 9,4 C12.8659932,4 16,7.13400675 16,11 Z"></path>
</svg>
PK
!<�蒁--8chrome/toolkit/skin/classic/global/icons/update-icon.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill">
  <path d="M7.625.5a7.5 7.5 0 1 0 0 15 7.5 7.5 0 0 0 0-15zm3.317 7.38a.623.623 0 0 1-.884 0L8.25 6.072l0 5.304A.625.625 0 0 1 7 11.375l0-5.303L5.192 7.88a.626.626 0 0 1-.885-.885L7.304 4l.643 0 2.996 2.995a.627.627 0 0 1-.001.885z"/>
</svg>
PK
!<� �ww<chrome/toolkit/skin/classic/global/icons/warning-fill-12.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" width="12" height="12" fill-opacity="context-fill-opacity">
  <path fill="context-stroke" d="M6 1.7c-.7 0-1.3.4-1.7 1L1.8 7.2c-.3.6-.3 1.3 0 1.9.3.6 1 1 1.7 1l5.2 0c.7 0 1.3-.4 1.7-1 .3-.6.3-1.3 0-1.9L7.7 2.7c-.4-.6-1-1-1.7-1z"/>
  <path fill="context-fill" d="M9.4 7.7 6.8 3.2c-.4-.6-1.3-.6-1.6 0L2.6 7.7c-.3.6.1 1.3.8 1.3l5.2 0c.7 0 1.1-.7.8-1.3z"/>
</svg>
PK
!<�@���4chrome/toolkit/skin/classic/global/icons/warning.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill light-dark(black, white)" fill-opacity="context-fill-opacity">
  <path d="m14.875 12.037-5.497-10C8.997 1.346 8.311 1 7.625 1s-1.372.346-1.752 1.037l-5.497 10C-.358 13.37.607 15 2.128 15l10.995 0c1.52 0 2.485-1.63 1.752-2.963zM8.25 11.75 8 12l-.75 0-.25-.25L7 11l.25-.25.75 0 .25.25 0 .75zm0-2.688a.625.625 0 0 1-1.25 0l0-3.437a.625.625 0 0 1 1.25 0l0 3.437z"/>
</svg>
PK
!<�R�׹�Bchrome/toolkit/skin/classic/global/illustrations/about-license.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" viewBox="0 0 300 300">
 <defs>
   <linearGradient id="a" x1="-300.021" y1="-272.736" x2="547.138" y2="574.423" gradientUnits="userSpaceOnUse">
     <stop offset="0" stop-color="#ccfbff"/>
     <stop offset="1" stop-color="#c9e4ff"/>
   </linearGradient>
   <linearGradient id="b" x1="-18.672" y1="23.78" x2="279.805" y2="322.256" gradientUnits="userSpaceOnUse">
     <stop offset="0" stop-color="#00c8d7"/>
     <stop offset="1" stop-color="#0a84ff"/>
   </linearGradient>
 </defs>
  <path d="M267.464 87.073h-27.583c-2.347-4.377-8.617-14.64-16.509-16.145-10.217-1.948-12.254 8.237-12.254 8.237s-6.79-17.654-23.97-15.315c-19.262 2.623-10.189 22.9-10.189 22.9h-29l16.568.323h-16.569a1 1 0 0 0 0 2h119.506a1 1 0 0 0 0-2z" fill="#fff"/>
  <path d="M100.384 63.259H84.836c-1.212-2.3-4.758-8.29-9.271-9.15-5.682-1.083-6.814 4.58-6.814 4.58s-3.776-9.817-13.33-8.517c-10.712 1.458-5.666 12.735-5.666 12.735H33.629l18.049.352H33.924a1 1 0 1 0 0 2h66.46a1 1 0 0 0 0-2z" fill="#fff"/>
  <path d="M122.246 134H26.93a1 1 0 1 1 0-2h95.316a1 1 0 1 1 0 2z" fill="#eaeaee"/>
  <path d="M106.678 127.455H60.912a.5.5 0 1 1 0-1h45.766a.5.5 0 0 1 0 1z" fill="#eaeaee"/>
  <path d="M245.16 186.748h-90.336a1 1 0 0 1 0-2h90.336a1 1 0 0 1 0 2z" fill="#eaeaee"/>
  <path fill="none" stroke="#eaeaee" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="12 8 3 4 1 9" d="M132.518 192.779h135.95"/>
  <path d="M148.458 82.782h29s-9.074-20.277 10.189-22.9c17.18-2.339 23.97 15.315 23.97 15.315s2.037-10.185 12.254-8.237c10.074 1.921 17.511 16.634 17.511 16.634h25.25" fill="none" stroke="#eaeaee" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="12 8 3 4 1 9"/>
  <path d="M34.125 60.429H50.25s-5.05-11.276 5.667-12.735c9.554-1.3 13.33 8.517 13.33 8.517s1.133-5.664 6.814-4.58c5.6 1.068 9.738 8.589 9.738 8.589h14.042" fill="none" stroke="#eaeaee" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="12 8 3 4 1 9"/>
  <ellipse cx="149.78" cy="243.175" rx="77.947" ry="6.445" fill="#eaeaee"/>
  <path d="M221.876 77.056H78.01a10.312 10.312 0 0 0-10.3 10.3v134.026a9.824 9.824 0 0 0 9.95 9.669h144.566a9.824 9.824 0 0 0 9.95-9.669V87.356a10.313 10.313 0 0 0-10.3-10.3z" fill="#fff"/>
  <path d="M221.876 81.89H78.01a5.306 5.306 0 0 0-5.3 5.3v134.027a4.821 4.821 0 0 0 4.95 4.669h144.566a4.821 4.821 0 0 0 4.95-4.669V87.19a5.307 5.307 0 0 0-5.3-5.3z" fill="#f9f9fa"/>
  <g fill="url(#a)">
   <path d="M76.368 105.543V219c0 2.28.84 3.13 3.12 3.13H220.4c2.28 0 3.12-.85 3.12-3.13V105.543zM185 200.6a3.693 3.693 0 0 1-3.693 3.693h-60.762a3.693 3.693 0 0 1-3.693-3.693v-78.762a3.693 3.693 0 0 1 3.693-3.693h44.971L185 137.421z"/>
   <path d="M187.187 193.93h-16.773a2.625 2.625 0 0 1-1.924-.89l-5.326-5.759a2.924 2.924 0 0 1 0-3.894l5.326-5.759a2.625 2.625 0 0 1 1.924-.89h16.773c1.6 0 2.9 1.582 2.9 3.534V190.4c-.001 1.948-1.299 3.53-2.9 3.53z"/>
   <path d="M187.187 166.021h-16.773a2.625 2.625 0 0 1-1.924-.89l-5.326-5.759a2.924 2.924 0 0 1 0-3.894l5.326-5.759a2.625 2.625 0 0 1 1.924-.89h16.773c1.6 0 2.9 1.582 2.9 3.534v10.126c-.001 1.949-1.299 3.532-2.9 3.532z"/>
  </g>
  <g fill="url(#b)">
   <path d="M221.876 81.891a5.307 5.307 0 0 1 5.3 5.3v134.026a4.821 4.821 0 0 1-4.95 4.669H77.66a4.821 4.821 0 0 1-4.95-4.669V87.19a5.306 5.306 0 0 1 5.3-5.3h143.866m0-2H78.01a7.308 7.308 0 0 0-7.3 7.3v134.027a6.822 6.822 0 0 0 6.95 6.669h144.566a6.822 6.822 0 0 0 6.95-6.669V87.19a7.309 7.309 0 0 0-7.3-7.3z"/>
   <circle cx="85.771" cy="93.32" r="3.241"/>
   <circle cx="96.226" cy="93.32" r="3.241"/>
   <path d="M186.093 96.986h-72.3a3.509 3.509 0 0 1-3.509-3.509v-.313a3.509 3.509 0 0 1 3.509-3.509h72.3a3.509 3.509 0 0 1 3.509 3.509v.313a3.509 3.509 0 0 1-3.509 3.509z"/>
   <circle cx="203.66" cy="93.32" r="3.241"/>
   <circle cx="214.116" cy="93.32" r="3.241"/>
   <path d="M157.13 131.141h-31.754a1.351 1.351 0 1 1 0-2.7h31.755a1.351 1.351 0 1 1 0 2.7z"/>
   <path d="M153.609 139.09a1.351 1.351 0 0 0-1.351-1.351h-26.882a1.351 1.351 0 0 0 0 2.7h26.882a1.351 1.351 0 0 0 1.351-1.349z"/>
   <path d="M164.676 148.39a1.351 1.351 0 0 0-1.351-1.351h-37.949a1.351 1.351 0 0 0 0 2.7h37.949a1.351 1.351 0 0 0 1.351-1.349z"/>
   <path d="M159.665 157.69a1.351 1.351 0 0 0-1.351-1.351h-32.938a1.351 1.351 0 0 0 0 2.7h32.938a1.351 1.351 0 0 0 1.351-1.349z"/>
   <path d="M153.609 166.99a1.351 1.351 0 0 0-1.351-1.351h-26.882a1.351 1.351 0 1 0 0 2.7h26.882a1.351 1.351 0 0 0 1.351-1.349z"/>
   <path d="M164.676 176.29a1.351 1.351 0 0 0-1.351-1.351h-37.949a1.351 1.351 0 1 0 0 2.7h37.949a1.351 1.351 0 0 0 1.351-1.349z"/>
   <path d="M159.672 185.59a1.351 1.351 0 0 0-1.351-1.351h-32.945a1.351 1.351 0 1 0 0 2.7h32.944a1.351 1.351 0 0 0 1.352-1.349z"/>
   <path d="M164.676 194.89a1.351 1.351 0 0 0-1.351-1.351h-37.949a1.351 1.351 0 1 0 0 2.7h37.949a1.351 1.351 0 0 0 1.351-1.349z"/>
   <path d="M165.517 118.145l19.276 19.276h-15.179a4.1 4.1 0 0 1-4.1-4.1v-15.176m0-1a1 1 0 0 0-1 1v15.179a5.1 5.1 0 0 0 5.1 5.1h15.179a1 1 0 0 0 .707-1.707l-19.276-19.276a1 1 0 0 0-.707-.293z"/>
   <path d="M187.187 178.737c.367 0 .9.6.9 1.534V190.4c0 .936-.532 1.534-.9 1.534h-16.773a.689.689 0 0 1-.455-.248l-5.326-5.759a.932.932 0 0 1 0-1.178l5.326-5.759a.688.688 0 0 1 .455-.248h16.773m0-2h-16.773a2.625 2.625 0 0 0-1.924.89l-5.326 5.759a2.924 2.924 0 0 0 0 3.894l5.326 5.759a2.626 2.626 0 0 0 1.924.89h16.773c1.6 0 2.9-1.582 2.9-3.534v-10.129c0-1.952-1.3-3.534-2.9-3.534z"/>
   <path d="M187.187 150.827c.367 0 .9.6.9 1.534v10.126c0 .936-.532 1.534-.9 1.534h-16.773a.689.689 0 0 1-.455-.248l-5.326-5.759a.932.932 0 0 1 0-1.178l5.326-5.76a.687.687 0 0 1 .455-.248h16.773m0-2h-16.773a2.624 2.624 0 0 0-1.924.89l-5.326 5.759a2.924 2.924 0 0 0 0 3.894l5.326 5.759a2.626 2.626 0 0 0 1.924.89h16.773c1.6 0 2.9-1.582 2.9-3.534v-10.125c0-1.952-1.3-3.534-2.9-3.534z"/>
   <path d="M186.408 136l-19.485-19.276a2 2 0 0 0-1.407-.578h-44.971a5.7 5.7 0 0 0-5.693 5.693V200.6a5.7 5.7 0 0 0 5.693 5.693h60.763A5.693 5.693 0 0 0 187 200.6v-8.67h-2v8.67a3.693 3.693 0 0 1-3.693 3.693h-60.762a3.693 3.693 0 0 1-3.693-3.693v-78.762a3.693 3.693 0 0 1 3.693-3.693h44.971L185 137.421v13.406h2v-13.406a2 2 0 0 0-.592-1.421zM185 178.737h2v-14.716h-2z"/>
   <path d="M180.308 205.81h-58.763a5.7 5.7 0 0 1-5.693-5.693v4.667a5.7 5.7 0 0 0 5.693 5.693h58.763a5.693 5.693 0 0 0 5.692-5.694v-4.667a5.693 5.693 0 0 1-5.693 5.693z"/>
  </g>
</svg>
PK
!<f�Fm/O/OAchrome/toolkit/skin/classic/global/illustrations/about-rights.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" viewBox="0 0 300 300">
  <defs>
    <linearGradient id="a" x1="-300.021" y1="-272.736" x2="547.138" y2="574.423" gradientUnits="userSpaceOnUse">
      <stop offset="0" stop-color="#ccfbff"/>
      <stop offset="1" stop-color="#c9e4ff"/>
    </linearGradient>
    <linearGradient id="b" x1="-18.672" y1="23.78" x2="279.805" y2="322.256" gradientUnits="userSpaceOnUse">
      <stop offset="0" stop-color="#00c8d7"/>
      <stop offset="1" stop-color="#0a84ff"/>
    </linearGradient>
  </defs>
  <path d="M265.342 100.316h-23.336c-2.043-3.774-7.272-12.2-13.831-13.453-8.612-1.642-10.329 6.943-10.329 6.943s-5.724-14.88-20.206-12.906c-16.237 2.211-8.589 19.3-8.589 19.3h-24.443l5.931.115H164.6a1 1 0 0 0 0 2h100.742a1 1 0 0 0 0-2z" fill="#fff"/>
  <path d="M111.942 71.45h-13.19c-1.1-2.054-4.033-6.851-7.731-7.556-4.789-.913-5.744 3.861-5.744 3.861s-3.183-8.276-11.236-7.179c-9.03 1.229-4.776 10.735-4.776 10.735H55.671l7.159.139h-6.91a1 1 0 0 0 0 2h56.022a1 1 0 0 0 0-2z" fill="#fff"/>
  <path d="M235.972 169.581h-69.687a1 1 0 0 1 0-2h69.687a1 1 0 0 1 0 2z" fill="#eaeaee"/>
  <path d="M226.512 163.154h-30.853a.5.5 0 0 1 0-1h30.854a.5.5 0 0 1 0 1z" fill="#eaeaee"/>
  <path d="M251.077 175.007h-1a.5.5 0 0 1 0-1h1a.5.5 0 0 1 0 1zm-5 0h-3a.5.5 0 0 1 0-1h3a.5.5 0 0 1 0 1zm-11 0h-12a.5.5 0 0 1 0-1h12a.5.5 0 0 1 0 1zm-21 0h-1a.5.5 0 0 1 0-1h1a.5.5 0 0 1 0 1zm-5 0h-3a.5.5 0 0 1 0-1h3a.5.5 0 0 1 0 1zm-11 0h-12a.5.5 0 0 1 0-1h12a.5.5 0 0 1 0 1zm-21 0h-1a.5.5 0 0 1 0-1h1a.5.5 0 0 1 0 1zm-5 0h-3a.5.5 0 0 1 0-1h3a.5.5 0 0 1 0 1zm-11 0h-12a.5.5 0 0 1 0-1h12a.5.5 0 0 1 0 1z" fill="#eaeaee"/>
  <path d="M240.909 210.47H92.625a1 1 0 0 1 0-2h148.284a1 1 0 1 1 0 2z" fill="#eaeaee"/>
  <ellipse cx="81.399" cy="227.481" rx="15.603" ry="4.632" fill="#eaeaee"/>
  <path d="M68.86 210.47H41.206a1 1 0 0 1 0-2H68.86a1 1 0 0 1 0 2z" fill="#eaeaee"/>
  <path d="M95.206 125.267H45.287a1 1 0 0 1 0-2h49.919a1 1 0 0 1 0 2z" fill="#eaeaee"/>
  <path d="M86.084 119.993h-3a.5.5 0 0 1 0-1h3a.5.5 0 0 1 0 1zm-11 0h-12a.5.5 0 0 1 0-1h12a.5.5 0 0 1 0 1z" fill="#eaeaee"/>
  <path d="M165.026 96.856h24.444s-7.649-17.093 8.589-19.3c14.482-1.972 20.205 12.91 20.205 12.91s1.717-8.585 10.329-6.943c8.492 1.619 14.761 14.022 14.761 14.022h21.285" fill="none" stroke="#eaeaee" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="12 8 3 4 1 9"/>
  <path d="M56.089 69.222h13.593s-4.253-9.505 4.776-10.735c8.053-1.1 11.236 7.179 11.236 7.179s.955-4.774 5.744-3.861c4.722.9 8.209 7.24 8.209 7.24h11.837" fill="none" stroke="#eaeaee" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="12 8 3 4 1 9"/>
  <ellipse cx="149.135" cy="242.903" rx="45.72" ry="9.272" fill="#eaeaee"/>
  <path d="M209.9 164.086c3.013-9.515 1.374-19.8-4.615-28.958a45.642 45.642 0 0 0-13.924-13.5 6.243 6.243 0 0 0-4.588-4.872 6.223 6.223 0 0 0-4.553-3.595 6.212 6.212 0 0 0-5.383-3.988 6.522 6.522 0 0 0-.436-.016 6.252 6.252 0 0 0-3.955 1.389 6.211 6.211 0 0 0-.867-.061 6.242 6.242 0 0 0-5.513 3.311q-.946-.037-1.882-.037a45.681 45.681 0 0 0-12.117 1.6c-.062-8.393 1.088-19.17 5.862-29.058a12.031 12.031 0 0 0-10.82-17.255 12.046 12.046 0 0 0-10.836 6.8c-9.762 20.224-9.2 41.788-6.872 56.692a31.125 31.125 0 0 0-3.714 14.724 15.666 15.666 0 0 0-.636 1.638l-.814.521c-1.485.29-2.917.638-4.281 1.039a14.748 14.748 0 0 1-2.456-5.59l-.239-4.606a5.728 5.728 0 0 0-3.769-5.1q-.092-.035-.188-.067a5.749 5.749 0 0 0-1.627-6.85 12.379 12.379 0 0 0-2.9-1.732 15.685 15.685 0 0 0-6.68-1.57 12.767 12.767 0 0 0-4.5.8c-5.209 1.954-8.633 6.666-10.178 14.005-1.818 8.637 2.748 27.443 18.278 40.566 5.772 7.177 17.305 12.187 30.22 13.106 1.437.1 2.885.154 4.307.154a53.486 53.486 0 0 0 13.294-1.619 43.673 43.673 0 0 1-.329 11.819 11.877 11.877 0 0 0 .371 5.606 15.988 15.988 0 0 1-3.391 5.385 6.39 6.39 0 0 0-.307 9.009 6.459 6.459 0 0 0 .909.8 6.359 6.359 0 0 0 6.21 4.88 6.416 6.416 0 0 0 2.374-.452 6.376 6.376 0 0 0 7.382-1.615c3.211-3.7 4.993-8.546 5.964-12.321a12.025 12.025 0 0 0 4.18-7.179 68.453 68.453 0 0 0 .4-19.879 43.989 43.989 0 0 0 11.457-2.63c.49.046.981.068 1.469.068q.652 0 1.3-.054a6.244 6.244 0 0 0 8.974-1.6 6.251 6.251 0 0 0 5.171-8.579l-.383-.937a15.7 15.7 0 0 0 .415-1.681 31.279 31.279 0 0 0 4.216-8.511z" fill="#fff"/>
  <g fill="url(#a)">
    <path d="M201.452 136.985a45.33 45.33 0 0 0-52.633-16.478c-8.95 3.593-15.307 10.133-17.9 18.415-5.317 16.983 7.121 36.049 27.725 42.5a46.43 46.43 0 0 0 13.856 2.148c15.476 0 29.041-8.1 33.157-21.25 2.591-8.282 1.098-17.28-4.205-25.335z"/>
    <path d="M161.541 205.621a2.514 2.514 0 0 0-.771-.121 2.524 2.524 0 0 0-2.41 1.758 47.907 47.907 0 0 1-2.917 7.05 9.367 9.367 0 0 1-2.409 3.035 2.53 2.53 0 0 0 2.66 4.3 13.056 13.056 0 0 0 4.165-4.868 52.016 52.016 0 0 0 3.318-7.97 2.535 2.535 0 0 0-1.636-3.184z"/>
    <path d="M164.2 209.058a2.528 2.528 0 0 0-3.182 1.638 48.521 48.521 0 0 1-2.917 7.05 9.375 9.375 0 0 1-2.412 3.035 2.53 2.53 0 0 0 2.662 4.3 13.063 13.063 0 0 0 4.165-4.866 52.056 52.056 0 0 0 3.319-7.971 2.535 2.535 0 0 0-1.635-3.186z"/>
    <path d="M169.179 209.558a2.513 2.513 0 0 0-1.611-1.075 2.542 2.542 0 0 0-.489-.048 2.537 2.537 0 0 0-2.484 2.037 47.923 47.923 0 0 1-2.094 7.336 9.379 9.379 0 0 1-2.048 3.291 2.529 2.529 0 0 0 3.133 3.971 13.016 13.016 0 0 0 3.584-5.312 51.568 51.568 0 0 0 2.389-8.3 2.513 2.513 0 0 0-.38-1.9z"/>
    <path d="M190.9 181.291a10.39 10.39 0 0 1-6.11-1.989l-6.7-4.847a10.947 10.947 0 0 1-2.518-15.06 10.448 10.448 0 0 1 14.756-2.57l6.7 4.847a10.947 10.947 0 0 1 2.518 15.06 10.507 10.507 0 0 1-8.646 4.559z"/>
    <path d="M185.675 122.091a1 1 0 0 0-1.058-.317l-3.187.962.318-3.422a1 1 0 0 0-1.683-.819l-1.856 1.754-1.17-5.146a1 1 0 0 0-1.909-.137l-1.625 4.225-1.34-2.95a1 1 0 0 0-1.9.29c-.216 1.727-.876 7.053-.985 8.508l-.848 1.462a1 1 0 0 0 .58 1.46l11.626 3.462a1 1 0 0 0 1.174-.5l3.984-7.732a1 1 0 0 0-.121-1.1z"/>
    <path d="M135.176 158.7l-2.414-6.577a1.5 1.5 0 0 0-2.216-.745l-4.127 2.642a1.5 1.5 0 0 0 .3 2.673l2.768 1-1.276 1.617a1.5 1.5 0 0 0 1.178 2.429 1.455 1.455 0 0 0 .341-.039l4.381-1.025a1.5 1.5 0 0 0 1.068-1.978z"/>
    <path d="M200.937 176.924l-1.854-4.537a1.491 1.491 0 0 0-.96-.868 1.511 1.511 0 0 0-1.282.2l-5.761 3.989a1.5 1.5 0 0 0-.267 2.231l2.995 3.361a1.5 1.5 0 0 0 1.12.5 1.5 1.5 0 0 0 1.5-1.583l-.113-2.056 2.84.771a1.5 1.5 0 0 0 1.785-2.014z"/>
    <path d="M159.611 159.487c-5.6-3.72-13.21-6.09-21.425-6.675a54.688 54.688 0 0 0-3.871-.138c-15.618 0-27.884 6.871-28.532 15.982-.351 4.933 2.636 9.762 8.411 13.6 5.6 3.72 13.21 6.09 21.425 6.675 1.291.092 2.594.138 3.873.138 15.617 0 27.883-6.871 28.532-15.982.349-4.936-2.638-9.765-8.413-13.6z"/>
    <path d="M124.666 177.47a4.742 4.742 0 0 0-2.313-2.828c-18.9-10.14-22.139-28.077-21.316-31.987.505-2.4 1.184-2.936 1.32-3.021a.589.589 0 0 0 .175-.055 1.012 1.012 0 0 0 .424-.317c.641-.579 2-2.067 5.575-5.963a1.009 1.009 0 0 0-.136-1.488 12.481 12.481 0 0 0-6.611-2.174 8.2 8.2 0 0 0-2.887.51c-3.59 1.346-6.013 4.893-7.2 10.542-1.374 6.526 2.394 29.625 26.144 42.367a4.774 4.774 0 0 0 6.827-5.585z"/>
    <path d="M155.655 145.652a7.9 7.9 0 0 0-6.32-3.121 7.941 7.941 0 0 0-4.518 1.4 7.444 7.444 0 0 0-3.174 4.94 7.292 7.292 0 0 0 1.363 5.588c.189.255 18.886 25.834 14.472 50.251a7.288 7.288 0 0 0 1.232 5.528 7.762 7.762 0 0 0 5.12 3.188 8.12 8.12 0 0 0 1.335.111 7.706 7.706 0 0 0 7.671-6.235c5.564-30.792-16.25-60.402-17.181-61.65z"/>
    <path d="M122.878 156.521q-.132 0-.265.007c-.21.014-.4.021-.589.021-7.346 0-9.405-10.133-9.584-11.093l-.261-4.963a1.009 1.009 0 0 0-1.74-.642l-6.672 7.018a1.009 1.009 0 0 0-.264.863c1.427 8.49 7.125 18.4 18.49 18.4.4 0 .809-.013 1.22-.039a4.8 4.8 0 0 0 3.327-1.629 4.744 4.744 0 0 0 1.183-3.461 4.837 4.837 0 0 0-4.845-4.482z"/>
    <path d="M154.67 78.331a7.593 7.593 0 0 0-4.039-4.394 7.972 7.972 0 0 0-10.422 3.537c-15.809 31.64-3.755 67.22-3.234 68.719a7.8 7.8 0 0 0 7.418 5.159 8.1 8.1 0 0 0 2.466-.387 7.688 7.688 0 0 0 4.584-3.892 7.291 7.291 0 0 0 .372-5.691c-.106-.308-10.519-31.3 2.489-57.333a7.288 7.288 0 0 0 .366-5.718z"/>
  </g>
  <g fill="#f9f9fa">
    <path d="M144.51 178c-8.129-4.344-13.41-12.143-13.455-19.869a1 1 0 0 0-1.107-.988c-10.364 1.124-17.6 6.019-17.6 11.905 0 6.8 9.618 12.135 21.9 12.135a37.451 37.451 0 0 0 10.069-1.34 1 1 0 0 0 .2-1.844z"/>
    <path d="M178.859 150.443l.9-1.739a1 1 0 0 0-1.178-1.415l-4.394 1.327.438-4.705a1 1 0 0 0-1.683-.82l-2.677 2.529-1.578-6.942a1 1 0 0 0-.9-.776 1.011 1.011 0 0 0-1 .639l-2.274 5.915-1.949-4.292a1 1 0 0 0-1.9.29 964.54 964.54 0 0 0-.505 4.108c-11.4-4.874-19.225-9.7-21.771-1.659-.125.395-.235.8-.337 1.215a9.531 9.531 0 0 1 1.743-.172l7.761-.024h.03a9.492 9.492 0 0 1 5.762 17.04c-4.371-1.722-7.326-2.554-8.124-.037a6.264 6.264 0 0 0-.276 2l-2.441.008a24.511 24.511 0 0 1-1.91-2.542.5.5 0 0 0-.851.525 28.4 28.4 0 0 0 5.095 5.823q.628.63 1.318 1.241c.624.607 1.3 1.221 2.028 1.827 2.317 2.521 6.1 4.97 11.886 6.8 6.258 1.982 10.941 2.077 14.3 1.2a27.867 27.867 0 0 0 7.433-1.248 13.317 13.317 0 0 0 2.518-.96c.122-.06.209-.1.264-.124a.5.5 0 0 0-.316-.948 3.916 3.916 0 0 0-.386.173 12.409 12.409 0 0 1-2.354.9l-2.384-1.689a6.64 6.64 0 0 0 1.011-1.926c.769-2.428-1.889-3.463-6.173-4.527a10 10 0 0 1 15.415-10.781l6.333 4.487a10.027 10.027 0 0 1 1.221 1.035c.079-.224.168-.448.239-.671 2.515-7.943-6.418-8.584-18.334-11.085z"/>
  </g>
  <g fill="url(#b)">
    <path d="M147.105 74.29a6.777 6.777 0 0 1 6.093 9.723c-6.372 13.2-7 27.638-6.034 38.767a35.29 35.29 0 0 1 2.353-1.046 39.658 39.658 0 0 1 14.664-2.727 44.406 44.406 0 0 1 6.055.419c.142-1.165.272-2.213.347-2.817a1 1 0 0 1 1.9-.29l1.34 2.95 1.625-4.225a1 1 0 0 1 .94-.641h.065a1 1 0 0 1 .9.776l1.17 5.146 1.856-1.754a1 1 0 0 1 1.683.819l-.295 3.169c.113.047.223.1.335.144l2.828-.854a.991.991 0 0 1 .288-.043 1 1 0 0 1 .89 1.458l-.553 1.073A40.9 40.9 0 0 1 200.89 138c5.11 7.816 6.531 16.517 4 24.5a26.118 26.118 0 0 1-4.221 8.054 10.53 10.53 0 0 1-1.05 3.772l1.17 2.864a1 1 0 0 1-.926 1.379.966.966 0 0 1-.261-.036l-2.713-.736c-.244.2-.495.394-.755.571l.108 1.959a1 1 0 0 1-1.745.72l-1.178-1.322a10.589 10.589 0 0 1-5.228.258 39.5 39.5 0 0 1-15.265 2.981q-.832 0-1.668-.033a65.709 65.709 0 0 1 .545 24.057 6.753 6.753 0 0 1-3.656 4.88c-.621 3.04-2.1 8.412-5.255 12.047a1.128 1.128 0 1 1-1.7-1.479c2.39-2.752 3.742-6.752 4.482-9.855a6.238 6.238 0 0 1-.544.024 6.756 6.756 0 0 1-.834-.053c-.958 3.015-2.981 8.111-6.453 11.36a1.116 1.116 0 0 1-.766.291 1.135 1.135 0 0 1-.83-.346 1.128 1.128 0 0 1 .055-1.592c3.065-2.867 4.928-7.565 5.827-10.372a6.769 6.769 0 0 1-1.105-.681 25.01 25.01 0 0 1-5.63 9.032 1.128 1.128 0 0 1-1.542-1.647c2.683-2.509 4.445-6.409 5.471-9.322a6.73 6.73 0 0 1-.864-4.6 52.712 52.712 0 0 0-.623-19.714 43.539 43.539 0 0 1-17.515 3.365c-1.3 0-2.611-.047-3.934-.14-11.783-.838-22.043-5.258-26.8-11.541-14.73-12.177-18.3-29.328-16.935-35.812 1.151-5.468 3.467-8.889 6.883-10.17a7.522 7.522 0 0 1 2.655-.471 11.751 11.751 0 0 1 6.272 2.129.5.5 0 0 1 .067.737c-4.039 4.409-5.322 5.786-5.815 6.224a.486.486 0 0 1-.047.058 10.287 10.287 0 0 0-1.689 3.318c-.794 2.193.414 11.566 7.831 21.016a14.5 14.5 0 0 1 2.117-2.5c-3.636-3.525-5.712-9.427-6.395-13.521a.5.5 0 0 1 .129-.424l6.588-7.006a.5.5 0 0 1 .364-.159.485.485 0 0 1 .172.032.5.5 0 0 1 .327.443l.26 5c.054.286 1.512 8.332 6.76 10.759a40.21 40.21 0 0 1 7.643-1.982.993.993 0 0 1 .382-.47l2.582-1.653a10.523 10.523 0 0 1 1.317-3.71 25.958 25.958 0 0 1 3.924-14.945c-2.288-13.3-3.642-35.193 6.117-55.411a6.8 6.8 0 0 1 6.107-3.833m0-2a8.8 8.8 0 0 0-7.909 4.964c-9.6 19.9-8.838 41.235-6.411 55.882a27.923 27.923 0 0 0-3.845 14.857 12.418 12.418 0 0 0-1.1 2.974l-1.862 1.192a2.936 2.936 0 0 0-.409.315 42.5 42.5 0 0 0-6.374 1.636c-3.467-2.2-4.749-7.91-4.933-8.818l-.252-4.861a2.491 2.491 0 0 0-1.638-2.218 2.44 2.44 0 0 0-.859-.153 2.509 2.509 0 0 0-1.821.789l-6.588 7.005a2.475 2.475 0 0 0-.273.348 8.137 8.137 0 0 1-.072-2.866 9.7 9.7 0 0 1 1.295-2.661l.011-.012c.6-.561 1.842-1.893 5.839-6.256a2.5 2.5 0 0 0-.347-3.691 13.627 13.627 0 0 0-7.468-2.525 9.532 9.532 0 0 0-3.357.6c-4.1 1.537-6.837 5.451-8.138 11.631-1.445 6.865 2.2 24.905 17.441 37.62 5.151 6.627 15.889 11.271 28.107 12.14a55.8 55.8 0 0 0 4.076.145 47.332 47.332 0 0 0 16-2.6 49.245 49.245 0 0 1 .167 16.607 8.721 8.721 0 0 0 .655 5.085 21.128 21.128 0 0 1-4.657 7.723 3.112 3.112 0 0 0 1.519 5.338 3.166 3.166 0 0 0-.058.511 3.137 3.137 0 0 0 5.264 2.385q.167-.157.331-.318a3.13 3.13 0 0 0 4.854.185c3.18-3.667 4.781-8.834 5.522-12.08a8.779 8.779 0 0 0 3.859-5.826 66.713 66.713 0 0 0-.114-22.372 41.259 41.259 0 0 0 14.755-2.906 12.588 12.588 0 0 0 4.315-.094l.373.419a3 3 0 0 0 5.236-2.142l.839.228a2.93 2.93 0 0 0 .785.106 3 3 0 0 0 2.778-4.134l-.849-2.077a12.43 12.43 0 0 0 .805-3.043 28.07 28.07 0 0 0 4.2-8.213c2.712-8.567 1.208-17.871-4.237-26.2a42.593 42.593 0 0 0-14.413-13.424 3 3 0 0 0-3.8-3.544l-.337.1.043-.463a3 3 0 0 0-4.324-2.965l-.426-1.875a2.984 2.984 0 0 0-2.709-2.327 3.298 3.298 0 0 0-.208-.007 2.986 2.986 0 0 0-2.809 1.921l-.052.135a3 3 0 0 0-4.934 1.9l-.106.851a45.995 45.995 0 0 0-4.312-.2 41.52 41.52 0 0 0-15.224 2.8c-.551-9.739.237-22.9 6.043-34.923a8.781 8.781 0 0 0-7.894-12.592z"/>
    <path d="M201.082 171.875h-.028a.5.5 0 0 1-.472-.526 13.519 13.519 0 0 0-1.958-7 .5.5 0 1 1 .841-.541 14.3 14.3 0 0 1 2.116 7.6.5.5 0 0 1-.499.467z"/>
    <path d="M130.643 148.528a.5.5 0 0 1-.406-.791c1.734-2.425 5.456-3.655 5.613-3.706a.5.5 0 0 1 .309.951c-.036.012-3.568 1.182-5.109 3.337a.5.5 0 0 1-.407.209z"/>
    <path d="M184.377 126.738a.5.5 0 0 1-.372-.834c.572-.639 2.077-2.676 1.5-3.632-.484-.8-2.621.081-3.58.636a.5.5 0 0 1-.742-.528c.257-1.324.354-3.466-.258-3.857a.264.264 0 0 0-.235-.03c-.555.153-1.336 1.295-1.714 2.058a.5.5 0 0 1-.942-.151c-.392-2.705-1.323-5.791-2.031-5.892-.021 0-.076-.012-.177.065-.738.565-1.315 3.218-1.5 4.757a.5.5 0 0 1-.431.435.515.515 0 0 1-.529-.309c-.7-1.729-1.931-3.588-2.5-3.544-.009 0-.046.028-.093.107-.611 1.019-.2 4.857.018 5.394a.512.512 0 0 1-.252.655.486.486 0 0 1-.647-.219c-.278-.557-.808-4.824-.012-6.282a1.129 1.129 0 0 1 .879-.649c1.124-.127 2.166 1.38 2.848 2.674.28-1.367.793-3.18 1.62-3.812a1.223 1.223 0 0 1 .927-.262c1.425.2 2.259 3.345 2.654 5.4a3.488 3.488 0 0 1 1.625-1.392 1.247 1.247 0 0 1 1.042.152c1.032.661.993 2.647.849 3.909 1.2-.539 3.233-1.185 4.047.167 1.06 1.757-1.336 4.508-1.613 4.815a.5.5 0 0 1-.381.169z"/>
    <path d="M130.109 161.852a3.206 3.206 0 0 1-1.385-.28 1.435 1.435 0 0 1-.863-1.084 3.969 3.969 0 0 1 1.2-2.733c-1.424-.448-3.732-1.375-3.751-2.663-.022-1.481 3.985-3.6 5.71-4.441a.5.5 0 1 1 .438.9c-2.727 1.332-5.133 2.98-5.148 3.528.008.539 2.012 1.47 3.8 1.94a.5.5 0 0 1 .221.842c-.674.656-1.578 1.856-1.477 2.468.016.1.066.228.308.343 1.108.53 3.093-.221 3.765-.554a.5.5 0 0 1 .447.895 8.44 8.44 0 0 1-3.265.839z"/>
    <path d="M195.266 182.317c-1.464 0-3.507-2.879-4.366-4.2a.5.5 0 1 1 .838-.545c1.5 2.3 3.13 3.9 3.528 3.745.514-.2.617-1.951.387-3.4a.5.5 0 0 1 .667-.547c1.947.723 3.724.963 3.957.693.2-.487-.784-3.2-2.154-5.923a.5.5 0 0 1 .894-.449c.845 1.678 2.744 5.7 2.142 6.842-.57 1.077-2.9.534-4.423.04.1 1.208.075 3.222-1.111 3.679a1 1 0 0 1-.359.065z"/>
    <path d="M153.084 218.985a.5.5 0 0 1-.342-.865c4.939-4.621 6.88-13.587 6.9-13.677a.5.5 0 1 1 .979.207c-.081.38-2.027 9.366-7.194 14.2a.5.5 0 0 1-.343.135z"/>
    <path d="M155.771 221.9a.5.5 0 0 1-.342-.865c4.935-4.616 6.88-13.588 6.9-13.678a.5.5 0 0 1 .979.207c-.081.38-2.027 9.367-7.194 14.2a.5.5 0 0 1-.343.136z"/>
    <path d="M160.647 222.024a.5.5 0 0 1-.377-.828c4.433-5.107 5.434-14.227 5.443-14.317a.487.487 0 0 1 .549-.445.5.5 0 0 1 .445.549c-.04.387-1.044 9.526-5.683 14.87a.5.5 0 0 1-.377.171z"/>
    <path d="M110.254 161.469a.5.5 0 0 1-.369-.837 23.915 23.915 0 0 1 9.942-5.786.5.5 0 1 1 .3.953 23.391 23.391 0 0 0-9.507 5.508.5.5 0 0 1-.366.162z"/>
    <path d="M107.264 169.781a.5.5 0 0 1-.472-.335c-.049-.139-1.155-3.438 1.8-7.218a.5.5 0 0 1 .787.617c-2.624 3.353-1.657 6.241-1.647 6.271a.5.5 0 0 1-.307.637.489.489 0 0 1-.161.028z"/>
    <path d="M155.876 186.7a.5.5 0 0 1-.2-.96 22.264 22.264 0 0 0 3.824-2.14.5.5 0 1 1 .588.809 22.934 22.934 0 0 1-4.018 2.251.5.5 0 0 1-.194.04z"/>
    <path d="M143.722 96.658a.494.494 0 0 1-.223-.053.5.5 0 0 1-.224-.671c2.965-5.928 1.7-14.842 1.683-14.931a.5.5 0 0 1 .989-.146c.056.378 1.329 9.313-1.778 15.524a.5.5 0 0 1-.447.277z"/>
    <path d="M147.293 99.246a.494.494 0 0 1-.223-.053.5.5 0 0 1-.224-.671c2.965-5.928 1.7-14.841 1.684-14.93a.5.5 0 0 1 .989-.146c.056.378 1.329 9.312-1.778 15.523a.5.5 0 0 1-.448.277z"/>
    <path d="M151.985 97.6a.487.487 0 0 1-.176-.032.5.5 0 0 1-.292-.644c2.334-6.2.148-14.938.126-15.025a.5.5 0 1 1 .969-.248c.095.369 2.287 9.124-.159 15.625a.5.5 0 0 1-.468.324z"/>
    <path d="M188.641 167.773a1 1 0 0 1-.962-.729 3.532 3.532 0 0 0-2.26-2.218 3.574 3.574 0 0 0-3.12.508 1 1 0 1 1-1.271-1.543 5.5 5.5 0 0 1 8.578 2.717 1 1 0 0 1-.965 1.265z"/>
    <path d="M149.323 155.323a1 1 0 0 1-.962-.729 3.532 3.532 0 0 0-2.26-2.218 3.575 3.575 0 0 0-3.12.508 1 1 0 1 1-1.271-1.543 5.5 5.5 0 0 1 8.578 2.717 1 1 0 0 1-.965 1.265z"/>
    <path d="M169.2 174.119c-12.839 0-21.089-9.653-21.187-9.771a1 1 0 0 1 1.534-1.284c.108.13 11.038 12.862 27.056 7.905a1 1 0 1 1 .592 1.91 26.9 26.9 0 0 1-7.995 1.24z"/>
    <path d="M148.308 88.455a5.777 5.777 0 0 1-1.881-.312c-4.221-1.443-5.695-7.407-5.756-7.66a1 1 0 0 1 1.945-.468c.013.052 1.282 5.153 4.462 6.237 1.662.566 3.658-.077 5.939-1.908a1 1 0 0 1 1.252 1.561 9.8 9.8 0 0 1-5.961 2.55z"/>
    <path d="M143.018 98.25a.987.987 0 0 1-.416-.092 1 1 0 0 1-.493-1.325 29.886 29.886 0 0 0 2.285-9.877 1.016 1.016 0 0 1 1.044-.956 1 1 0 0 1 .955 1.043 31.384 31.384 0 0 1-2.465 10.624 1 1 0 0 1-.91.583z"/>
    <path d="M146.893 100.375a1 1 0 0 1-.893-1.457c.031-.062 1.778-3.614 2.015-11.323a.987.987 0 0 1 1.03-.97 1 1 0 0 1 .969 1.03c-.254 8.258-2.155 12.022-2.236 12.179a1 1 0 0 1-.885.541z"/>
    <path d="M151.643 99.5a1 1 0 0 1-.889-1.455 22.246 22.246 0 0 0 1.522-11.677 1 1 0 0 1 1.986-.236 23.772 23.772 0 0 1-1.729 12.824 1 1 0 0 1-.89.544z"/>
    <path d="M175.977 184c-16.543 0-27.147-5.563-33.13-10.23-6.5-5.074-9.045-10.086-9.15-10.3a.5.5 0 0 1 .9-.446c.1.2 10.341 19.974 41.386 19.974a.5.5 0 0 1 0 1z"/>
    <path d="M136.393 131.5a.5.5 0 0 1-.424-.764c.178-.285 4.463-7.025 14.763-10.543a.5.5 0 0 1 .323.947c-9.966 3.4-14.2 10.057-14.237 10.123a.5.5 0 0 1-.425.237z"/>
    <path d="M146.4 151.423a1.358 1.358 0 0 1 .884 1.7l-.918 2.9a1.358 1.358 0 0 1-1.7.884 1.358 1.358 0 0 1-.884-1.7l.918-2.9a1.351 1.351 0 0 1 1.7-.884z"/>
    <path d="M185.721 163.873a1.358 1.358 0 0 1 .884 1.7l-.918 2.9a1.358 1.358 0 0 1-1.7.884 1.358 1.358 0 0 1-.884-1.7l.918-2.9a1.355 1.355 0 0 1 1.7-.884z"/>
    <path d="M171.819 164.988c-2.257-2.156-9.975-5.389-15.3-3.991a2.079 2.079 0 0 0-1.469 2.1c-.055 2.165 2 5.019 6.72 6.512a18.025 18.025 0 0 0 5.313.982 6.476 6.476 0 0 0 3.311-.779 4.566 4.566 0 0 0 2.166-3.1 1.965 1.965 0 0 0-.741-1.724z"/>
    <path d="M121.965 96.833a1 1 0 0 1-1-1v-12.55a1 1 0 0 1 .421-.815L167.6 49.634a1 1 0 0 1 1.158 1.631L122.965 83.8v12.033a1 1 0 0 1-1 1z"/>
    <path d="M116.318 124.646a1 1 0 0 1-.562-.174l-9.2-6.273a1 1 0 0 1-.437-.826v-15.058a1 1 0 1 1 2 0v14.529l8.765 5.976a1 1 0 0 1-.564 1.826z"/>
    <path d="M80.489 227.782a2.5 2.5 0 0 1-2.5-2.5v-74.444a2.5 2.5 0 0 1 5 0v74.444a2.5 2.5 0 0 1-2.5 2.5z"/>
    <path d="M80.492 161.79a2.5 2.5 0 0 0 3.477 3.594l10.508-10.168a42.319 42.319 0 0 1-1.528-5.479zm29.467-25.034a2.5 2.5 0 0 0-3.535-.059l-5.171 5c-.121.289-.245.6-.374.954-.332.917-.3 3.1.371 6.007l8.65-8.37a2.5 2.5 0 0 0 .059-3.532z"/>
    <path d="M141.381 80.325c-18.7 13.4-25.152 18.462-25.416 18.99-.478.955-.552 2.013-.412 13.112.062 4.906.154 12.212-.093 13.978-1.161 1.589-7.3 8.162-12.858 13.906a2.5 2.5 0 1 0 3.593 3.477c4.786-4.944 12.946-13.5 13.824-15.258.605-1.211.69-3.779.534-16.166-.051-4.016-.112-8.893-.007-10.7 1.871-1.531 15.38-11.247 23.162-16.83.027-.094.058-.2.089-.3-.3-.4-.621-.848-.964-1.366a17.248 17.248 0 0 1-1.452-2.843zm24.034-13.956a2.5 2.5 0 0 0-3.483-.6c-4.667 3.287-8.85 6.24-12.611 8.9a6.883 6.883 0 0 1 .722.3 6.719 6.719 0 0 1 3.129 3.111c4.122-2.923 8.12-5.747 11.639-8.226a2.5 2.5 0 0 0 .605-3.485z"/>
  </g>
</svg>
PK
!<��m�VEVEHchrome/toolkit/skin/classic/global/illustrations/error-malformed-url.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="300" height="300" viewBox="0 0 300 300">
  <defs>
    <linearGradient id="a" x1="-300.021" y1="-272.736" x2="547.138" y2="574.423" gradientUnits="userSpaceOnUse">
      <stop offset="0" stop-color="#ccfbff"/>
      <stop offset="1" stop-color="#c9e4ff"/>
    </linearGradient>
    <linearGradient id="b" x1="-18.672" y1="23.78" x2="279.805" y2="322.256" gradientUnits="userSpaceOnUse">
      <stop offset="0" stop-color="#00c8d7"/>
      <stop offset="1" stop-color="#0a84ff"/>
    </linearGradient>
  </defs>
  <path d="M73.275 165.84l63.117-26.74a1 1 0 0 0-.78-1.842L72.494 164a1 1 0 0 0 .78 1.842z" fill="#eaeaee"/>
  <path d="M110.05 144.74l15.834-6.708a.5.5 0 1 0-.39-.921l-15.834 6.708a.5.5 0 0 0 .39.921z" fill="#eaeaee"/>
  <path d="M59.35 176.038a.5.5 0 0 1 .265-.655l2.762-1.17a.5.5 0 1 1 .39.921l-2.762 1.17a.5.5 0 0 1-.655-.266zm10.128-4.291a.5.5 0 0 1 .265-.655l11.049-4.681a.5.5 0 1 1 .39.921l-11.049 4.681a.5.5 0 0 1-.654-.266zm19.336-8.192a.5.5 0 0 1 .265-.655l.921-.39a.5.5 0 1 1 .39.921l-.921.39a.5.5 0 0 1-.654-.266zm4.6-1.951a.5.5 0 0 1 .265-.655l2.762-1.17a.5.5 0 1 1 .39.921l-2.762 1.17a.5.5 0 0 1-.65-.27zm10.128-4.291a.5.5 0 0 1 .265-.655l11.049-4.681a.5.5 0 0 1 .39.921l-11.046 4.68a.5.5 0 0 1-.653-.265z" fill="#eaeaee"/>
  <path d="M103.245 194.26L208.809 161a1 1 0 0 0-.6-1.908l-105.565 33.26a1 1 0 1 0 .6 1.908z" fill="#eaeaee"/>
  <path d="M116 185l26.482-8.343a.5.5 0 1 0-.3-.954l-26.482 8.342a.5.5 0 0 0 .3.954z" fill="#eaeaee"/>
  <path d="M84.918 204.09a.5.5 0 0 1 .327-.627l11.445-3.606a.5.5 0 0 1 .3.954l-11.445 3.606a.5.5 0 0 1-.627-.327zm19.076-6.01a.5.5 0 0 1 .327-.627l2.861-.9a.5.5 0 1 1 .3.954l-2.861.9a.5.5 0 0 1-.627-.326zm6.677-2.1a.5.5 0 0 1 .327-.627l.954-.3a.5.5 0 1 1 .3.954l-.954.3a.5.5 0 0 1-.627-.33zm9.538-3a.5.5 0 0 1 .327-.627l11.445-3.606a.5.5 0 1 1 .3.954l-11.445 3.599a.5.5 0 0 1-.628-.327zm19.076-6.01a.5.5 0 0 1 .327-.627l2.861-.9a.5.5 0 1 1 .3.954l-2.861.9a.5.5 0 0 1-.628-.334zm6.677-2.1a.5.5 0 0 1 .327-.627l.954-.3a.5.5 0 1 1 .3.954l-.954.3a.5.5 0 0 1-.628-.337zm9.538-3a.5.5 0 0 1 .327-.627l11.445-3.606a.5.5 0 1 1 .3.954l-11.445 3.606a.5.5 0 0 1-.627-.342zm19.076-6.01a.5.5 0 0 1 .327-.627l2.861-.9a.5.5 0 0 1 .3.954l-2.861.9a.5.5 0 0 1-.629-.342zm6.677-2.1a.5.5 0 0 1 .327-.627l.954-.3a.5.5 0 1 1 .3.954l-.954.3a.5.5 0 0 1-.629-.345zm9.538-3a.5.5 0 0 1 .327-.627l11.445-3.606a.5.5 0 0 1 .3.954l-11.445 3.606a.5.5 0 0 1-.629-.35zm19.076-6.01a.5.5 0 0 1 .327-.627l2.861-.9a.5.5 0 0 1 .3.954l-2.861.9a.5.5 0 0 1-.629-.349zm6.677-2.1a.5.5 0 0 1 .327-.627l.954-.3a.5.5 0 0 1 .3.954l-.954.3a.5.5 0 0 1-.63-.352zm9.538-3a.5.5 0 0 1 .327-.627l11.445-3.606a.5.5 0 1 1 .3.954l-11.445 3.606a.5.5 0 0 1-.63-.357z" fill="#eaeaee"/>
  <path d="M50.883 109.437L73.366 97.57s-15.334-12.008-1.472-21.924C84.256 66.8 96.745 77.71 96.745 77.71s-2.589-8.73 6.13-11.4c8.6-2.633 20.994 6.885 20.994 6.885l19.577-10.334" fill="#fff"/>
  <path d="M141.872 58.947L136.085 62a.588.588 0 0 1-.549-1.04l5.788-3.055a.588.588 0 1 1 .549 1.04zm-15.152 8l-1.04.549a.588.588 0 0 1-.549-1.04l1.04-.549a.588.588 0 0 1 .549 1.04zM72.4 94.811l-1.674.884a.588.588 0 0 1-.549-1.04l.9-.478a24.222 24.222 0 0 1-.591-.543.588.588 0 1 1 .81-.853c.7.664 1.177 1.039 1.188 1.048a.588.588 0 0 1-.088.983zm-10 5.277l-12.485 6.59a.588.588 0 1 1-.549-1.04l12.485-6.59a.588.588 0 1 1 .549 1.04zm59.158-31.3a.588.588 0 0 1-.6-.026 45.206 45.206 0 0 0-3-1.776.588.588 0 1 1 .559-1.036 46.056 46.056 0 0 1 3.086 1.824.588.588 0 0 1-.045 1.014zM68.064 90.2a.588.588 0 0 1-.751-.176q-.348-.483-.681-1a.588.588 0 0 1 .993-.631c.208.328.423.643.642.946a.588.588 0 0 1-.133.821.666.666 0 0 1-.07.04zm25.892-16.6a.587.587 0 0 1-.6-.031c-.293-.2-.617-.4-.972-.62a.588.588 0 0 1 .611-1.006c.37.225.71.444 1.016.649a.588.588 0 0 1-.052 1.008zm15.815-10.047a.586.586 0 0 1-.405.053 14.722 14.722 0 0 0-7.557-.008 8.578 8.578 0 0 0-4.693 3.166.588.588 0 0 1-.966-.673 9.747 9.747 0 0 1 5.314-3.618 15.855 15.855 0 0 1 8.163-.013.588.588 0 0 1 .144 1.093zm-21.029 7.324a.587.587 0 0 1-.486.029 21.942 21.942 0 0 0-3.291-.988.588.588 0 0 1 .246-1.151 23.255 23.255 0 0 1 3.468 1.04.588.588 0 0 1 .063 1.07zm-23.055 8.291a.589.589 0 0 1-.827-.722 14.421 14.421 0 0 1 5.452-6.558 19.345 19.345 0 0 1 5.307-2.711.588.588 0 1 1 .361 1.123A18.263 18.263 0 0 0 71 72.844a13.284 13.284 0 0 0-5.031 6.006.59.59 0 0 1-.282.318z" fill="#eaeaee"/>
  <path d="M144.627 62.6l-92.656 48.906a1.176 1.176 0 0 1-1.1-2.081l92.656-48.907a1.176 1.176 0 1 1 1.1 2.081z" fill="#fff"/>
  <path d="M185.779 90.824l13.771 3.409s-1.925-10.7 7.531-9.678c8.434.909 9.583 10.091 9.583 10.091s2.165-4.6 6.788-2.471c4.558 2.1 6.186 10.666 6.186 10.666l11.991 2.968" fill="#fff"/>
  <path d="M200.337 92.789l-13.752-3.4a.59.59 0 0 1 .284-1.146l13.752 3.4a.59.59 0 0 1-.284 1.146zm42.412 10.311l-.529-.131a.59.59 0 1 1 .284-1.146l.529.131a.59.59 0 0 1-.284 1.146zm-5.113-1.266l-3.438-.851a.59.59 0 1 1 .284-1.146l3.438.851a.59.59 0 0 1-.284 1.146zm-20.166-8.627a.614.614 0 0 1-.445-.506l-.035-.222a.589.589 0 0 1 .4-.662 5.483 5.483 0 0 1 6-2.637 7.111 7.111 0 0 1 1.259.447 10.622 10.622 0 0 1 4.381 4.221.591.591 0 0 1-1 .626 9.484 9.484 0 0 0-3.872-3.774 5.935 5.935 0 0 0-1.05-.374c-3.3-.818-4.891 2.422-4.957 2.562a.592.592 0 0 1-.681.319zm-14.675-9.061a.591.591 0 0 1-.2-1.053 6.8 6.8 0 0 1 3.628-1.19.591.591 0 0 1 .07 1.179 5.632 5.632 0 0 0-3.009.97.589.589 0 0 1-.489.094zm9 .181a.6.6 0 0 1-.146-.057 9.055 9.055 0 0 0-1-.478.59.59 0 0 1-.324-.769.6.6 0 0 1 .769-.325 10.36 10.36 0 0 1 1.128.54.59.59 0 0 1-.428 1.089z" fill="#eaeaee"/>
  <path d="M242.217 107.236l-56.755-14.049a1.181 1.181 0 1 1 .567-2.292l56.755 14.049a1.181 1.181 0 0 1-.567 2.292z" fill="#fff"/>
  <ellipse cx="155.535" cy="245.952" rx="7.463" ry="43.55" transform="rotate(-86.464 155.535 245.951)" fill="#eaeaee"/>
  <path d="M112.695 226.207l-64.217-18.684a1 1 0 1 0-.559 1.92l64.217 18.684a1 1 0 1 0 .559-1.92z" fill="#eaeaee"/>
  <path d="M243.246 226.609L178.5 209.394a1 1 0 0 0-.514 1.933l64.751 17.215a1 1 0 1 0 .514-1.933z" fill="#eaeaee"/>
  <path d="M146.467 240.275a.789.789 0 0 0 .166-.037.82.82 0 0 1-.205.045z" fill="#fff"/>
  <g fill="#fff">
    <path d="M179.037 101.776l-.121-.547a12.937 12.937 0 0 0-.4-1.386 12.75 12.75 0 0 0-22.367-3.36l-4.583-.2a5 5 0 0 0-4.02-2.981 5 5 0 0 0-1.409-1.736l-.987 5.022-.259 1.327 1.261-.689 2.337-1.286.866 3.905 8.492.371.509-.8a9.751 9.751 0 0 1 16.444 10.478l-1.117 1.753a166 166 0 0 1 5.9 23.121c.32-.012 11.494 70.842 11.421 75.734a61.9 61.9 0 0 1-.1 10.1c-.433 3.64-1.661 13.195-5.4 14.023a2.76 2.76 0 0 1-1.855-.234c-.957-.484-1.864-1.833-3.817-11.386a123 123 0 0 1-15.17 4.294 149.57 149.57 0 0 1-17.448 2.843c2.283 9.563 1.761 10.211 1.121 11.019a2.76 2.76 0 0 1-1.582 1c-3.739.829-8.634-6.281-10.574-9.446a46.87 46.87 0 0 1-2.769-5.378c-2.744-3.417-5.519-2.72-2.775.881a47.78 47.78 0 0 0 2.805 5.4 38.26 38.26 0 0 0 6.116 7.863c2.261 2.07 4.879 3.528 7.652 2.913a5.81 5.81 0 0 0 3.292-2.061c.926-1.229 1.334-2.436 1.126-4.9a43.46 43.46 0 0 0-.916-5.379c4.453-.567 9.452-1.439 14.4-2.536 3.437-.762 7.923-1.869 12.292-3.277A61.92 61.92 0 0 0 179 232.33c.863 2.369 1.735 4.03 3.085 4.714a5.017 5.017 0 0 0 3.538.655c2.812-.623 4.751-1.742 6.522-6.337a46.05 46.05 0 0 0 1.841-10.27 75.3 75.3 0 0 0-.13-11.375c.029-4.493-.48-7.184-.945-13.441-1.184-16.126-9.59-59.447-11.018-64.876a168.439 168.439 0 0 0-5.141-20.053l.342-.537a12.8 12.8 0 0 0 1.475-3.236c.1-.361.189-.718.262-1.082a12.68 12.68 0 0 0 .206-4.716z"/>
    <path d="M134.237 228.656l-6.947-14.593c-1.686-6.992-20.874-79.082-21.813-86.213l-3.447-2.2a9.76 9.76 0 0 1 10.489-16.449l2.087 1.33c4.834-2.638 12.315-6.642 15.256-7.744 3.252-1.223 9.874-6.285 12.141-8.119L146 91.43c-.11-.078-.209-.158-.317-.227a5 5 0 0 0-5.729.4c-3.239 2.623-8.884 6.732-11.307 7.627-2.713 1.021-8.725 4.167-14.083 7.075l-.6-.38a12.76 12.76 0 1 0-13.679 21.468l2.254 1.436c.592 4.611 14.794 90.939 28.1 99.384z"/>
    <path d="M186.722 176.206s10.389-4.558 15.3-2.655c3.46 1.339 7.075 4.468 9.434 12.129s2.346 13.664-1.921 16.234-9.314 1.9-10.178-.979c-.621-2.067-2.547-9.158-2.547-9.158s-1.255-.941-6.281 1.683z"/>
    <path d="M116.553 182.33s-16.275-1.34-19.879 3.447c-2.232 2.964-4.4 7.12-3.154 15.039s3.888 13.307 8.85 13.744 9.2-2.381 8.716-5.346c-.349-2.129-1.73-9.347-1.73-9.347s.135 1.219 11.44 2.024z"/>
  </g>
  <path d="M205.432 180.5c-3.039-4.414-6.895-5.465-18.936.241-3.035-15.577-8.491-43.6-8.527-43.761a260.987 260.987 0 0 0-.7-3.767l-.039-.176-.958-4.325-.113-.508-.361-1.63-.307-1.386-.314-1.416q-.216-.976-.5-1.937l-.206-.927q-.369-1.342-.756-2.67a5 5 0 0 0-.285-.961q-.305-1.008-.618-2c-.065-.293-.179-.575-.275-.871-.3-.957-.6-1.833-.924-2.735l1.623-2.8a7.778 7.778 0 0 0-13.12-8.359l-.448.7a12.79 12.79 0 0 1 7.84 4.408.5.5 0 0 1-.755.659 11.81 11.81 0 0 0-7.682-4.115l-.084.131-11.187-.495-.052-.234-.059.013-.576-2.6-4.955 2.737.067-.343-.192.1 1.014-5.131c-1.3 1.047-8.6 6.812-12.5 8.282-3.064 1.15-11.506 5.715-16.035 8.2-4.027 2.582-4.287 5.938-4.3 6.074a.5.5 0 0 1-.391.455l-.137.03a.5.5 0 0 1-.474-.52c-.035-.156.269-3.778 4.383-6.615l-2.111-1.345a7.778 7.778 0 0 0-8.359 13.12l3.817 2.432.463.287c.312 3.3 1.05 7.88 2.09 13.129a244.916 244.916 0 0 0 2.3 10.359l.182.82s.136 11.739 6.835 36.611c-13.88-.9-19.023.179-20.366 4.046-1.625 4.678.566 13.9 1.116 15.346a4 4 0 0 0 4.6 2.486 4.046 4.046 0 0 0 .557-.167c2.064-.782 1.925-2.583 1.145-4.648-1.047-2.768-.811-7.074-.755-9.09 2.342-1.09 8.613-.388 16.016.208a437.29 437.29 0 0 0 5.028 15.915c3.14 9.547 6.214 11.174 9.089 14.624a46.19 46.19 0 0 0 2.75 5.383c2.74 4.412 5.31 7.181 7.025 8.174l.357.177.091.041.277.092.13.033.2.037.156-.035.137-.03a.821.821 0 0 0 .205-.045l.059-.013a.781.781 0 0 0 .214-.191c.465-.584-.541-5.4-1.791-10.532a17.821 17.821 0 0 1-5.563-1.625.5.5 0 1 1 .446-.9 17.649 17.649 0 0 0 4.866 1.482c2.018-.14 4.231-.364 6.637-.693l.185-.041 2.114-.3.989-.158 1.423-.234 1.45-.25.976-.216 1.836-.356.586-.13 2.119-.47.312-.069a115.067 115.067 0 0 0 17.231-4.884c2.187-1.13 5.312-3.021 5.347-3.049a.5.5 0 0 1 .518.858c-.136.081-2.713 1.625-4.879 2.8 1.044 5.218 2.365 11.073 3.045 11.414a.77.77 0 0 0 .476.069l.049-.011.215-.048.088-.019.205-.138.087-.07.2-.2.09-.1.216-.273.078-.109.228-.358.046-.072a15.559 15.559 0 0 0 1.319-3.293l-.019-.088.221-.807.033-.13.215-.877-.022-.1c.072-.323.143-.646.211-.989.213-1.071 1.234-3.087.359-4.558a61.5 61.5 0 0 0 .075-9.829l-.015-.068-.033-.658c.569-3.666-.534-19.041-.534-19.041l-.4-2.048c5.392-2.54 8.767-3.317 11.088-3.221.826 1.839 2.666 5.179 2.763 8.137.074 2.206.67 4.468 2.876 4.4a4 4 0 0 0 3.869-4.122c-.047-1.53-.432-9.45-3.24-13.53z" fill="url(#a)"/>
  <g fill="#f9f9fa">
    <path d="M120.69 115.886c-2.232 1.282-7.4 4.415-7.3 9.164s2.239 19.146 3.2 20.184 16.5-.365 17.218-1.631-1.208-31.981-1.974-32.714-8.428 3.437-11.144 4.997z"/>
    <path d="M173.771 187.946c-.254-1.838 4.982-9.811 7.446-13.441-2.179-10.881-6.769-31.937-7.541-34.944-1.738-10.523-4.47-21.185-8.722-28.908-2.181-3.961-12.142-6.956-23.757-5.16l-4.669 36.8s-.428 3.26-2.172 3.549c-1.431.237-13.05 3.558-17.183 4.745q.248.932.505 1.867c.248 1.63 4.281 18.045 7.083 28.711 4.165 2.817 11.809 8.193 12.283 10.016.429 1.652-2.666 8.234-4.839 12.552 2.917 7.157 5.781 12.57 7.815 14.158 12.135 9.6 40.827 2.219 43.492-11.964a69.862 69.862 0 0 0 .03-7.868c-3.884-3.376-9.553-8.541-9.771-10.113z"/>
  </g>
  <g fill="url(#b)">
    <path d="M124.228 131.126a1.4 1.4 0 0 0 1.064-1.67l-.679-3.066a1.4 1.4 0 1 0-2.734.606l.679 3.066a1.4 1.4 0 0 0 1.67 1.064z"/>
    <path d="M165.962 143.534a1.4 1.4 0 0 0 1.064-1.67l-.679-3.066a1.4 1.4 0 1 0-2.734.606l.679 3.066a1.4 1.4 0 0 0 1.67 1.064z"/>
    <path d="M125.543 137.062a5.7 5.7 0 0 0 3.981-3.341 1 1 0 1 0-1.876-.7 3.72 3.72 0 0 1-2.548 2.091 3.67 3.67 0 0 1-3.193-.819 1 1 0 0 0-1.4 1.428 5.7 5.7 0 0 0 5.036 1.341z"/>
    <path d="M167.278 149.47a5.7 5.7 0 0 0 3.991-3.343 1 1 0 0 0-1.866-.7 3.64 3.64 0 0 1-5.741 1.272 1 1 0 0 0-1.395 1.426 5.7 5.7 0 0 0 5.011 1.345z"/>
    <path d="M156.452 156.752a6.476 6.476 0 0 1-.522.387 6.69 6.69 0 0 1 .8 1.377 4.69 4.69 0 0 1-.525 4.333 1 1 0 0 0 1.592 1.21 6.72 6.72 0 0 0 .808-6.321 7.62 7.62 0 0 0-.365-.762 4.47 4.47 0 0 0 1.118.081 5 5 0 0 0 3.889-2.108 1 1 0 0 0-1.693-1.061 3.06 3.06 0 0 1-2.335 1.181 4.41 4.41 0 0 1-1.548-.31 6.4 6.4 0 0 1-.921 1.814 1.579 1.579 0 0 1-.298.179z"/>
    <path d="M155.906 157.107a6.476 6.476 0 0 0 .522-.387 1.58 1.58 0 0 0 .249-.184 6.4 6.4 0 0 0 .921-1.814 14.34 14.34 0 0 0 .973-5.9 2.88 2.88 0 0 0-1.181-2.149 1.75 1.75 0 0 0-1.792-.017c-2.144 1-6.864 6.325-7.348 10.764a1.52 1.52 0 0 0 .906 1.594c1.376.615 5.074-.928 6.75-1.907z"/>
    <path d="M205.286 200.2a5.653 5.653 0 0 0 4.378-4.134c.93-3.7-1.4-15.212-7.182-18.87-5.91-3.741-18 3.543-19.94 4.564-.488.258-.789.725-.531 1.213a1 1 0 0 0 1.35.418c14.7-7.753 16.768-5.262 18.106-4.473 4.967 2.926 7.036 13.563 6.257 16.658a3.561 3.561 0 0 1-3.3 2.686.794.794 0 0 1-.858-.791c-1.144-9.775-3.335-12.139-3.582-12.377a1 1 0 0 0-.744-.28c-.358.018-2.956-.343-13.509 4.457a.97.97 0 0 0-.37 1.365 1.049 1.049 0 0 0 1.365.369c7.878-3.365 10.1-3.936 12.094-4.148.54.877 1.925 3.726 2.759 10.845a2.777 2.777 0 0 0 2.714 2.555 4.132 4.132 0 0 0 .993-.057z"/>
    <path d="M122.9 161.253l-16.287 3.61a1 1 0 0 1-.433-1.953l16.287-3.61a1 1 0 0 1 .433 1.953z"/>
    <path d="M124.115 166.721l-16.287 3.61a1 1 0 1 1-.433-1.953l16.287-3.61a1 1 0 1 1 .433 1.953z"/>
    <path d="M125.219 171.7l-16.287 3.61a.5.5 0 1 1-.216-.976l16.284-3.61a.5.5 0 0 1 .216.976z"/>
    <path d="M177.123 139.793a1 1 0 0 1-1.06-1.514l8.964-14.069a1 1 0 0 1 1.687 1.075l-8.964 14.069a1 1 0 0 1-.627.439z"/>
    <path d="M105.051 211.452a5.653 5.653 0 0 1-5.63-2.134c-2.28-3.056-4.647-14.652-.62-20.181 3.171-4.352 22.929-3.128 25.11-2.93a1 1 0 0 1 .9 1.087 1.024 1.024 0 0 1-1.086.9c-22.257-.643-23.329 2.139-23.329 2.139-3.462 4.61-1.284 15.225.625 17.784a3.561 3.561 0 0 0 4.076 1.213.794.794 0 0 0 .488-1.06c-2.7-9.464-1.586-12.489-1.449-12.8a1 1 0 0 1 .58-.544c.337-.121 7.4-.753 19.966.848a1 1 0 1 1-.253 1.983c-10.344-1.318-16.621-1.676-18.541-1.106-.162 1.018-.346 4.18 1.62 11.072a2.777 2.777 0 0 1-1.524 3.4 4.132 4.132 0 0 1-.933.329z"/>
    <path d="M154.954 133.172s2.432 4.544 3.463 4.542 1.742-2.279 4.785-3.524c3.272-1.339 5.033.538 6.124-.251s1.2-5.459.718-6.368-3.7-2.42-8.672-.654-6.905 5.347-6.418 6.255z"/>
    <path d="M131.166 148.6a6.476 6.476 0 0 0 .643.1 1.58 1.58 0 0 0 .307.046 6.4 6.4 0 0 0 1.934-.631 14.34 14.34 0 0 0 4.857-3.481 2.88 2.88 0 0 0 .685-2.355 1.75 1.75 0 0 0-1.255-1.279c-2.225-.807-9.326-.381-12.807 2.416a1.52 1.52 0 0 0-.486 1.768c.538 1.397 4.244 2.916 6.122 3.416z"/>
    <path d="M131.973 147.394s-.186 6.672 2.354 8.45"/>
    <path d="M134.349 156.844a1 1 0 0 1-.595-.181c-2.9-2.028-2.8-8.559-2.781-9.3a.982.982 0 0 1 1.028-.972 1 1 0 0 1 .972 1.027c-.064 2.374.4 6.536 1.929 7.6a1 1 0 0 1-.553 1.819z"/>
    <path d="M191.146 210.564c-.657-8.673-1.389-15.793-2.2-22.113a70.69 70.69 0 0 0-1.943.918c.927 6.929 1.676 13.949 2.144 21.2l.015.068a61.494 61.494 0 0 1-.075 9.829c-.254 3.16-.151 3.467-.359 4.558-.067.343-.139.666-.211.989l.022.1-.215.877-.033.13-.221.807.019.088a15.562 15.562 0 0 1-1.319 3.293l-.046.072-.228.358-.078.109-.216.273-.09.1-.2.2-.087.07-.205.138-.088.019-.215.048-.049.011a.77.77 0 0 1-.476-.069c-.67-.343-2-6.187-3.045-11.414 2.134-1.139 4.734-2.719 4.879-2.8a.5.5 0 1 0-.518-.858s-3.162 1.909-5.347 3.049l-.156.035a113.632 113.632 0 0 1-16.147 4.695l-1.25.277-2.119.47-.586.13-1.837.356-.976.216-1.45.25-1.423.233-.989.158-2.114.3-.185.041c-2.4.326-4.649.559-6.637.693a17.65 17.65 0 0 1-4.866-1.482.5.5 0 1 0-.446.9 17.821 17.821 0 0 0 5.563 1.625c1.25 5.131 2.256 9.948 1.791 10.532a.769.769 0 0 1-.224.193l-.1.022a.784.784 0 0 1-.166.037l-.137.03-.156.035-.2-.037-.13-.033-.276-.092-.091-.041-.357-.177c-1.723-.98-4.285-3.762-7.025-8.174a46.184 46.184 0 0 1-2.75-5.383c-2.877-3.46-6.6-10.108-9.711-19.642q-1.77-5.725-3.244-10.808c-.206-.022-.41-.043-.606-.059-.655-.052-1.3-.1-1.936-.157 4.495 15.768 9.369 28.443 13.727 31.643a46.869 46.869 0 0 0 2.769 5.378c1.93 3.116 6.835 10.274 10.574 9.446a2.76 2.76 0 0 0 1.582-1c.64-.808 1.16-1.466-1.121-11.019a149.565 149.565 0 0 0 17.448-2.843 123 123 0 0 0 15.17-4.294c1.953 9.554 2.86 10.9 3.817 11.386a2.76 2.76 0 0 0 1.855.234c3.739-.829 5.571-8.783 5.4-14.023a61.9 61.9 0 0 0 .1-10.101zm-79.172-59.54l-.182-.82-2.3-10.359c-1.052-5.257-1.793-9.845-2.11-13.124l-.441-.281-3.817-2.432a7.778 7.778 0 1 1 8.359-13.12l2.111 1.345c-4.114 2.837-4.368 6.448-4.383 6.615a.5.5 0 0 0 .474.52l.137-.03a.5.5 0 0 0 .391-.455c-.032-.146.249-3.5 4.3-6.074 4.568-2.5 12.973-7.044 16.035-8.2 3.885-1.465 11.18-7.23 12.5-8.282l.2-.167-1.02 5.194-.067.343 4.955-2.737.059-.013.63 2.841 11.181.492.084-.131a11.81 11.81 0 0 1 7.682 4.115.5.5 0 1 0 .755-.659 12.79 12.79 0 0 0-7.84-4.408l.448-.7a7.778 7.778 0 1 1 13.12 8.359l-1.634 2.564c.311.894.616 1.81.924 2.735.065.293.188.573.275.871q.3.957.618 2a5 5 0 0 0 .285.961q.386 1.328.756 2.67l.206.927q.216.976.5 1.938l.314 1.416.307 1.386.361 1.63.112.508.959 4.325.039.176c2.917 16.877 6.122 32.387 8.549 48.11a99.288 99.288 0 0 1 1.969-.924c-2.147-13.977-4.762-25.188-7.888-44.349a179.865 179.865 0 0 0-6.07-24.087l1.117-1.753a9.751 9.751 0 0 0-16.447-10.479l-.509.8-8.492-.371-.859-3.91-2.337 1.286-1.261.689.259-1.327.987-5.022.076-.4-.3.24-3.993 3.241c-2.342 1.82-8.958 6.861-12.167 8.094-2.941 1.1-10.422 5.106-15.256 7.744l-2.087-1.33a9.76 9.76 0 1 0-11.04 16.1q.269.185.55.351l3.447 2.2A571.373 571.373 0 0 0 117.6 187.6q1.175.069 2.441.158c-6.999-25.479-8.067-36.735-8.067-36.735z"/>
  </g>
</svg>
PK
!<ӗe)�)�?chrome/toolkit/skin/classic/global/in-content/common-shared.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/design-system/tokens-brand.css");
@import url("chrome://global/skin/design-system/text-and-typography.css");

@namespace html "http://www.w3.org/1999/xhtml";
@namespace xul "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

/* TODO(bug 1845150): Remove notification-message from the host selectors in
 * this file */

:host(:is(.anonymous-content-host, notification-message)),
:root {
  --in-content-page-color: rgb(21, 20, 26);
  --in-content-page-background: var(--background-color-canvas);
  --in-content-text-color: var(--in-content-page-color);
  --in-content-box-background: var(--background-color-box);
  --in-content-box-background-odd: rgba(12, 12, 13, 0.05); /* grey 90 a05 */
  --in-content-box-border-color: color-mix(in srgb, currentColor 41%, transparent);
  --in-content-box-info-background: #f0f0f4;
  --in-content-item-hover: color-mix(in srgb, var(--in-content-primary-button-background) 20%, transparent);
  --in-content-item-hover-text: var(--in-content-page-color);
  --in-content-item-selected: var(--in-content-primary-button-background);
  --in-content-item-selected-text: var(--in-content-primary-button-text-color);
  --in-content-icon-color: rgb(91,91,102);
  --in-content-accent-color: var(--in-content-primary-button-background);
  --in-content-accent-color-active: var(--in-content-primary-button-background-hover);
  --in-content-border-hover: var(--grey-90-a50);
  --in-content-border-invalid: var(--red-50);
  --in-content-border-color: #d7d7db;
  --in-content-warning-icon-color: #ffa436;
  --in-content-success-icon-color: #2ac3a2;
  --in-content-button-text-color: var(--in-content-text-color);
  --in-content-button-text-color-hover: var(--in-content-text-color);
  --in-content-button-text-color-active: var(--in-content-button-text-color-hover);
  --in-content-button-background: color-mix(in srgb, currentColor 7%, transparent);
  --in-content-button-background-hover: color-mix(in srgb, currentColor 14%, transparent);
  --in-content-button-background-active: color-mix(in srgb, currentColor 21%, transparent);
  --in-content-button-border-color: transparent;
  --in-content-button-border-color-hover: transparent;
  --in-content-button-border-color-active: var(--in-content-button-border-color-hover);
  --in-content-primary-button-text-color: rgb(251,251,254);
  --in-content-primary-button-text-color-hover: var(--in-content-primary-button-text-color);
  --in-content-primary-button-text-color-active: var(--in-content-primary-button-text-color);
  --in-content-primary-button-background: #0061e0;
  --in-content-primary-button-background-hover: #0250bb;
  --in-content-primary-button-background-active: #053e94;
  --in-content-primary-button-border-color: transparent;
  --in-content-primary-button-border-hover: transparent;
  --in-content-primary-button-border-active: transparent;
  --in-content-danger-button-background: #e22850;
  --in-content-danger-button-background-hover: #c50042;
  --in-content-danger-button-background-active: #810220;
  --in-content-focus-outline-color: var(--focus-outline-color);
  --in-content-focus-outline-width: var(--focus-outline-width);
  --in-content-focus-outline-offset: var(--focus-outline-offset);
  --in-content-focus-outline-inset: var(--focus-outline-inset);
  --in-content-focus-outline: var(--focus-outline);

  --in-content-table-background: #f8f8fa;
  --in-content-table-border-color: var(--in-content-box-border-color);
  --in-content-table-header-background: var(--in-content-primary-button-background);
  --in-content-table-header-color: var(--in-content-primary-button-text-color);
  --in-content-sidebar-width: 280px;

  --dialog-warning-text-color: var(--red-60);

  --blue-40: #45a1ff;
  --blue-50: #0a84ff;
  --blue-60: #0060df;
  --grey-30: #d7d7db;
  --grey-60: #4a4a4f;
  --grey-90-a10: rgba(12, 12, 13, 0.1);
  --grey-90-a20: rgba(12, 12, 13, 0.2);
  --grey-90-a30: rgba(12, 12, 13, 0.3);
  --grey-90-a50: rgba(12, 12, 13, 0.5);
  --grey-90-a60: rgba(12, 12, 13, 0.6);
  --green-60: #12bc00;
  --green-70: #058b00;
  --orange-50: #ff9400;
  --red-40: #ff4f5e;
  --red-50: #ff0039;
  --red-60: #d70022;
  --yellow-50: #ffe900;
  --yellow-60: #d7b600;
  --yellow-60-a30: rgba(215, 182, 0, 0.3);
  --yellow-90: #3e2800;

  --shadow-10: 0 1px 4px var(--grey-90-a10);
  --shadow-30: 0 4px 16px var(--grey-90-a10);

  --card-padding: 16px;
  --card-shadow: var(--shadow-10);
  --card-outline-color: var(--grey-30);
  --card-shadow-hover: var(--card-shadow), 0 0 0 5px var(--card-outline-color);

  -moz-theme: non-native;
  accent-color: var(--in-content-accent-color);
  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :host(:is(.anonymous-content-host, notification-message)),
  :root {
    /* Keep these in sync with:
     *
     *  * nsXPLookAndFeel::GenericDarkColor
     *  * The default value of browser.display.foreground_color.dark and
     *    browser.display.background_color.dark
     *
     * TODO (emilio): Once color-scheme support is complete, perhaps we can
     * just replace most of these for system colors and remove all this
     * duplication (assuming we honor the preferred color scheme for
     * in-content privileged pages and plain-text documents). */
    --in-content-page-color: rgb(251,251,254);

    --in-content-box-background-odd: rgba(249,249,250,0.05);
    --in-content-box-info-background: rgba(249,249,250,0.15);

    --in-content-border-color: rgba(249,249,250,0.2);
    --in-content-border-hover: rgba(249,249,250,0.3);
    --in-content-border-invalid: rgb(255,132,139);

    --in-content-warning-icon-color: #ffbd4f;
    --in-content-success-icon-color: #54FFBD;

    --in-content-icon-color: rgb(251,251,254);

    --in-content-primary-button-text-color: rgb(43,42,51);
    --in-content-primary-button-background: rgb(0,221,255);
    --in-content-primary-button-background-hover: rgb(128,235,255);
    --in-content-primary-button-background-active: rgb(170,242,255);

    --in-content-danger-button-background: #ff848b;
    --in-content-danger-button-background-hover: #ffbdc5;
    --in-content-danger-button-background-active: #ffdfe7;

    --in-content-table-background: rgb(35, 34, 43);

    --card-outline-color: var(--grey-60);

    --dialog-warning-text-color: var(--red-40);

    scrollbar-color: rgba(249,249,250,.4) rgba(20,20,25,.3);
  }

  /* For dialogs, use a different background colour. We don't do
   * this in forced colors mode, as we should be using system colours then.
   */
  @media not (forced-colors) {
    :root[dialogroot] {
      --in-content-page-background: #42414d;
    }
  }
}

@media (forced-colors) {
  :host(:is(.anonymous-content-host, notification-message)),
  :root {
    --in-content-page-color: CanvasText;

    --in-content-box-background-odd: var(--in-content-box-background);
    --in-content-box-border-color: -moz-DialogText;
    --in-content-box-info-background: var(--in-content-box-background);

    --in-content-item-hover: SelectedItem;
    --in-content-item-hover-text: SelectedItemText;
    --in-content-item-selected: SelectedItem;
    --in-content-item-selected-text: SelectedItemText;
    --in-content-icon-color: -moz-DialogText;

    --in-content-accent-color: SelectedItem;
    --in-content-accent-color-active: SelectedItem;

    --in-content-border-hover: ThreeDShadow;
    /* This is not great, but there is no suitable keyword for this.
     * In theory, we shouldn't be conveying invalid state just with a colour
     * change... */
    --in-content-border-invalid: ThreeDShadow;
    --in-content-border-color: var(--border-color);

    --in-content-button-text-color: ButtonText;
    --in-content-button-text-color-hover: SelectedItemText;
    --in-content-button-text-color-active: SelectedItem;
    --in-content-button-background: ButtonFace;
    --in-content-button-background-hover: SelectedItem;
    --in-content-button-background-active: SelectedItemText;
    --in-content-button-border-color: ButtonText;
    --in-content-button-border-color-hover: SelectedItemText;
    --in-content-button-border-color-active: SelectedItem;

    --in-content-primary-button-text-color: ButtonFace;
    --in-content-primary-button-text-color-hover: SelectedItemText;
    --in-content-primary-button-text-color-active: SelectedItem;
    --in-content-primary-button-background: ButtonText;
    --in-content-primary-button-background-hover: SelectedItem;
    --in-content-primary-button-background-active: SelectedItemText;
    --in-content-primary-button-border-color: ButtonFace;
    --in-content-primary-button-border-hover: SelectedItemText;
    --in-content-primary-button-border-active: SelectedItem;

    --in-content-danger-button-background: var(--in-content-primary-button-background);
    --in-content-danger-button-background-hover: var(--in-content-primary-button-background-hover);
    --in-content-danger-button-background-active: var(--in-content-primary-button-background-active);

    --in-content-focus-outline-color: -moz-DialogText;

    --in-content-table-border-color: ThreeDDarkShadow;
    --in-content-table-background: -moz-Dialog;
    --in-content-table-header-background: -moz-Dialog;
    --in-content-table-header-color: -moz-DialogText;

    --dialog-warning-text-color: -moz-FieldText;
  }
}

:root {
  appearance: none;
  background-color: var(--in-content-page-background);
  color: var(--in-content-page-color);
}

:root:not(.system-font-size) {
  font-size: var(--font-size-root);
}

html|body {
  margin: 0;
}

html|hr {
  border-style: solid none none none;
  border-color: var(--in-content-border-color);
}

.main-content {
  padding: 40px 28px;
  overflow: auto;
}

/* tabpanels and tabs */

xul|tabpanels {
  appearance: none;
  border: none;
  padding: 0;
  background-color: transparent;
  color: inherit;
  color-scheme: unset;
}

xul|tabs {
  margin-bottom: 10px;
  border-bottom: 1px solid var(--in-content-border-color);
  background-color: transparent;
  color: inherit;
}

xul|tab {
  appearance: none;
  margin: 0;
  padding: 2px 20px 0;
  min-height: 44px;
  color: inherit;
  background-color: transparent;
  border-bottom: 2px solid transparent;
  transition: background-color 50ms ease 0s;
  color-scheme: unset;
}

xul|tab:where(:hover) {
  border-bottom-color: var(--in-content-border-color);
  background-color: var(--in-content-button-background-hover);
  color: var(--in-content-button-text-color-hover);
}

xul|tab:where(:hover:active) {
  background-color: var(--in-content-button-background-active);
  color: var(--in-content-button-text-color-active);
}

xul|tab[selected] {
  color: var(--in-content-accent-color);
  border-bottom-color: currentColor;
}

xul|tab[selected]:hover {
  background-color: var(--in-content-button-background-hover);
}

xul|tab[selected]:hover:active {
  background-color: var(--in-content-button-background-active);
}

@media (forced-colors) {
  xul|tab:hover,
  xul|tab:hover:active {
    border-bottom-color: currentColor;
  }

  xul|tab[selected]:hover {
    color: var(--in-content-button-text-color-hover);
  }

  xul|tab[selected]:hover:active {
    color: var(--in-content-button-text-color-active);
  }
}

/* html buttons */

html|button {
  font: inherit;
}

/* xul buttons and menulists */

button,
html|select,
html|input[type="color"],
xul|menulist {
  appearance: none;
  min-height: var(--button-min-height);
  color: var(--in-content-button-text-color);
  border: 1px solid var(--in-content-button-border-color);
  border-radius: 4px;
  background-color: var(--in-content-button-background);
  font-weight: 400;
  padding: .45em 1em;
  text-decoration: none;
  margin: 4px 8px;
  /* Ensure font-size isn't overridden by widget styling (e.g. in forms.css) */
  font-size: 1em;
}

button {
  font-weight: var(--button-font-weight);
}

/* Small buttons get sized to 6/12px padding (when adding the 1px border) */
button.small-button {
  padding: .25em 1em;
  font-size: var(--font-size-small);
  min-height: 28px;
}

/* Remove margin added by button.css */
xul|button > .button-box > .button-text {
  margin: 0;
}

button {
  /* Use the same margin of other elements for the alignment */
  margin-inline: 4px;
}

::-moz-focus-inner {
  border: none;
}

button:focus-visible,
html|select:focus-visible,
html|input:where([type="color"]):focus-visible,
xul|menulist:focus-visible,
xul|tab:focus-visible > .tab-middle > .tab-text {
  box-shadow: none;
  /* Don't set `var(--in-content-focus-outline)` here to allow more complicated UIs
     to use a different color when needed */
  outline: var(--in-content-focus-outline-width) solid var(--in-content-focus-outline-color);
  outline-offset: var(--in-content-focus-outline-offset);
}

html|select:not([size], [multiple]) {
  /* The following padding matches how a menulist is internally spaced.
   * 15px is the menulist's standard padding-inline, 3px is for
   * the internal label margin, 12px is the dropmarker's width
   * and 4px is the dropmarker's margin-inline-end.
   *
   * [------|---|---label---|dropmarker|----|------]
   *   15px  3px                12px    4px   15px
   * start-padding          background- end-padding
   *                        image-width
   *
   * Users of this element can change the variable --logical-padding. Other
   * variables should adjust automatically.
   */
  --logical-padding: 15px;
  --start-padding: calc(var(--logical-padding) + 3px);
  --end-padding: calc(var(--logical-padding) + 4px);
  --background-image-width: 12px;
  background-image: url("chrome://global/skin/icons/arrow-down-12.svg");
  background-position: right var(--end-padding) center;
  background-repeat: no-repeat;
  background-size: auto var(--background-image-width);
  -moz-context-properties: fill;
  fill: currentColor;
  font: inherit;
  font-weight: 600;

  /* See above for some explanation about these values. */
  padding-inline-start: var(--start-padding);
  padding-inline-end: calc(var(--background-image-width) + var(--end-padding));
  text-overflow: ellipsis;
}

html|select:not([size], [multiple]):dir(rtl) {
  background-position-x: left var(--end-padding);
}

html|select:not([size], [multiple]) > html|option {
  background-color: var(--in-content-box-background);
  color: var(--in-content-text-color);
}

html|button:enabled:hover,
html|select:not([size], [multiple]):enabled:hover,
html|input[type="color"]:hover,
xul|button:not([disabled="true"]):hover,
xul|menulist:not([disabled="true"]):hover {
  background-color: var(--in-content-button-background-hover);
  color: var(--in-content-button-text-color-hover);
  border-color: var(--in-content-button-border-color-hover);
}

html|button:enabled:hover:active,
html|select:not([size], [multiple]):enabled:hover:active,
html|input[type="color"]:enabled:hover:active,
xul|button:not([disabled="true"]):hover:active,
xul|button[open],
xul|button[open]:hover,
xul|menulist[open="true"]:not([disabled="true"]) {
  background-color: var(--in-content-button-background-active);
  color: var(--in-content-button-text-color-active);
  border-color: var(--in-content-button-border-color-active);
}

html|button:disabled,
html|select:disabled,
html|input[type="color"]:disabled,
xul|button[disabled="true"],
xul|menulist[disabled="true"] {
  opacity: 0.4;
}

html|button[autofocus],
html|button[type="submit"],
xul|button[default],
button.primary {
  background-color: var(--in-content-primary-button-background);
  color: var(--in-content-primary-button-text-color);
  border-color: var(--in-content-primary-button-border-color);
}

html|button[autofocus]:enabled:hover,
html|button[type="submit"]:enabled:hover,
html|button.primary:enabled:hover,
xul|button[default]:not([disabled="true"]):hover,
xul|button.primary:not([disabled="true"]):hover {
  background-color: var(--in-content-primary-button-background-hover);
  color: var(--in-content-primary-button-text-color-hover);
  border-color: var(--in-content-primary-button-border-hover);
}

html|button[autofocus]:enabled:hover:active,
html|button[type="submit"]:enabled:hover:active,
html|button.primary:enabled:hover:active,
xul|button[default]:not([disabled="true"]):hover:active,
xul|button.primary:not([disabled="true"]):hover:active {
  background-color: var(--in-content-primary-button-background-active);
  color: var(--in-content-primary-button-text-color-active);
  border-color: var(--in-content-primary-button-border-active);
}

@media not (forced-colors) {
  html|button.semi-transparent:not(.ghost-button, .primary):enabled {
    background-color: color-mix(in srgb, currentColor 10%, transparent);
  }

  html|button.semi-transparent:not(.primary):enabled:hover {
    background-color: color-mix(in srgb, currentColor 20%, transparent);
  }

  html|button.semi-transparent:not(.primary):enabled:hover:active {
    background-color: color-mix(in srgb, currentColor 30%, transparent);
  }
}

.danger-button {
  --in-content-primary-button-background: var(--in-content-danger-button-background);
  --in-content-primary-button-background-hover: var(--in-content-danger-button-background-hover);
  --in-content-primary-button-background-active: var(--in-content-danger-button-background-active);
  --in-content-focus-outline-color: var(--in-content-danger-button-background);
}

@media not (forced-colors) {
  html|button.ghost-button {
    background-color: transparent;
  }
}

html|button.ghost-button:not(.semi-transparent):enabled:hover {
  background-color: var(--in-content-button-background-hover);
  color: var(--in-content-button-text-color-hover);
}

html|button.ghost-button:not(.semi-transparent):enabled:hover:active {
  background-color: var(--in-content-button-background-active);
  color: var(--in-content-button-text-color-active);
}

html|button.ghost-button.icon-button {
  height: 16px;
  width: 16px;
  min-width: auto;
  background-repeat: no-repeat;
  background-size: 16px;
  background-position: center;
  fill: currentColor;
  -moz-context-properties: fill;
}

html|input[type="color"] {
  padding: 6px;
  width: 50px;
}

xul|menulist[image]::part(icon) {
  margin-inline-end: 5px;
}

xul|menulist > xul|menupopup {
  appearance: none;

  /* Reset native styles on Windows and macOS */
  border: none;
  background-color: transparent;

  --panel-border-color: var(--in-content-box-border-color);
  --panel-border-radius: 2px;
  --panel-background: var(--in-content-box-background);
  --panel-color: var(--in-content-text-color);
  --panel-padding: 0;
}

xul|menulist > xul|menupopup xul|menu,
xul|menulist > xul|menupopup xul|menuitem {
  appearance: none;
  font-size: 1em;
  padding-block: 0.2em;
  padding-inline: 10px 30px;
}

xul|menulist > xul|menupopup > xul|menu:not([disabled="true"])[_moz-menuactive="true"],
xul|menulist > xul|menupopup > xul|menuitem:not([disabled="true"])[_moz-menuactive="true"] {
  color: var(--in-content-item-hover-text);
  background-color: var(--in-content-item-hover);
}

xul|menulist > xul|menupopup > xul|menu:not([disabled="true"])[selected="true"],
xul|menulist > xul|menupopup > xul|menuitem:not([disabled="true"])[selected="true"] {
  color: var(--in-content-item-selected-text);
  background-color: var(--in-content-item-selected);
}

xul|menulist > xul|menupopup > xul|menu[disabled="true"],
xul|menulist > xul|menupopup > xul|menuitem[disabled="true"] {
  color: #999;
  /* override the [_moz-menuactive="true"] background color from
     global/menu.css */
  background-color: transparent;
}

xul|menulist > xul|menupopup xul|menuseparator {
  appearance: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--in-content-box-border-color);
  border-bottom: none;
}

/* textboxes */

html|input:where([type="email"], [type="tel"], [type="text"], [type="password"], [type="url"], [type="number"]),
html|textarea,
xul|search-textbox {
  appearance: none;
  border: 1px solid var(--in-content-box-border-color);
  border-radius: 4px;
  color: inherit;
  background-color: var(--in-content-box-background);
}

xul|search-textbox {
  min-height: var(--input-text-min-height);
  padding-inline: 8px;
}

html|input:where([type="email"], [type="tel"], [type="text"], [type="password"], [type="url"], [type="number"]),
html|textarea {
  box-sizing: border-box;
  font-family: inherit;
  font-size: inherit;
  padding: .45em;
  margin: 2px 4px;
  min-height: var(--input-text-min-height);
}

html|textarea {
  min-height: auto
}

html|input:where([type="email"], [type="tel"], [type="text"], [type="password"], [type="url"], [type="number"]):focus,
html|textarea:focus,
xul|search-textbox[focused],
xul|tree:focus-visible,
xul|richlistbox:focus-visible {
  border-color: transparent;
  outline: var(--in-content-focus-outline);
  outline-offset: -1px; /* Prevents antialising around the corners */
}

html|input:where([type="email"], [type="tel"], [type="text"], [type="password"], [type="url"], [type="number"]):user-invalid,
html|textarea:user-invalid {
  border-color: transparent;
  outline: 2px solid var(--in-content-border-invalid);
  outline-offset: -1px; /* Prevents antialising around the corners */
}

html|input:where([type="email"], [type="tel"], [type="text"], [type="password"], [type="url"], [type="number"]):disabled,
html|textarea:disabled,
xul|search-textbox[disabled="true"] {
  opacity: 0.4;
}

/* Links */

html|a,
.text-link,
::part(support-link) {
  cursor: pointer;
  color: var(--link-color);
}

html|a:visited,
::part(support-link):visited {
  color: var(--link-color-visited);
}

html|a:hover,
.text-link:hover,
button.text-link:is(:not([disabled="true"]), :enabled):hover,
::part(support-link):hover {
  color: var(--link-color-hover);
}

html|a:hover:active,
.text-link:hover:active,
button.text-link:is(:not([disabled="true"]), :enabled):hover:active,
::part(support-link):hover:active {
  color: var(--link-color-active);
  text-decoration: none;
}

html|a:focus-visible,
.text-link:focus-visible,
::part(support-link):focus-visible {
  outline: var(--focus-outline);
  outline-offset: var(--link-focus-outline-offset);
  border-radius: 4px;
}

button.text-link {
  background-color: transparent !important; /* override hover related background changes */
  padding: 0;
  border: 0;
  font-weight: normal;
  min-height: 0;
  min-width: 0;
}

/* Checkboxes and radio buttons */

/* Add invisible vertical click-target */
xul|*.radio-check,
xul|*.checkbox-check,
html|input:where([type="checkbox"], [type="radio"]) {
  /* TODO Bug 1876537: Make this em-based, probably? */
  height: 16px;
  width: 16px;
  padding: 0;
  margin-block: var(--space-xxsmall);
  margin-inline: 0 var(--checkbox-margin-inline);
  flex-shrink: 0; /* avoid shrinking inside flex container */
}

xul|richlistitem > xul|*.checkbox-check {
  margin: 3px 6px;
}

html|*.radio-container-with-text,
html|*.toggle-container-with-text {
  display: flex;
  align-items: center;
}

xul|radio {
  margin-inline-start: 0;
  appearance: none;
}

xul|*.radio-label-box {
  margin-inline: 0 8px;
  padding-inline-start: 0;
}

/* Disabled checkboxes, radios and labels */

xul|checkbox[disabled="true"],
xul|radio[disabled="true"],
xul|label[disabled="true"] {
  color: inherit;
}

xul|checkbox[disabled="true"] > .checkbox-label-box,
xul|radio[disabled="true"] > .radio-label-box,
xul|label[disabled="true"] {
  opacity: 0.5;
}

/* Category List */

#categories {
  appearance: none;
  background-color: initial; /* override the background-color set on all richlistboxes in common.inc.css */
  margin: 70px 0 0;
  border-width: 0;
  width: var(--in-content-sidebar-width);
  outline: none;
}

@media print {
  #categories {
    display: none;
  }
}

html|*#categories {
  box-sizing: border-box;
  padding: 1px;
}

#categories > .category {
  border: 1px solid var(--in-content-primary-button-border-color);
  border-radius: 4px;
  min-height: 48px;
  appearance: none;
  color: inherit;
  margin-inline-start: 34px;
  padding-inline: 10px;
  transition: background-color 150ms;
}

html|*#categories > html|*.category {
  border: 1px solid var(--in-content-primary-button-border-color);
  background-color: initial;
  background-size: 24px;
  background-repeat: no-repeat;
  background-position-x: 10px;
  background-position-y: 12px;
  margin-inline-end: 0;
  min-width: auto;
  padding-inline-start: 34px;
  text-align: start;
  -moz-context-properties: fill, fill-opacity;
  fill: currentColor;
}

html|*#categories > html|*.category:dir(rtl) {
  background-position-x: right 10px;
}

#categories > .category:hover {
  background-color: var(--in-content-button-background-hover);
  color: var(--in-content-button-text-color-hover);
  border-color: var(--in-content-button-border-color-hover);
}

#categories > .category:hover:active {
  background-color: var(--in-content-button-background-active);
  color: var(--in-content-button-text-color-active);
  border-color: var(--in-content-button-border-color-active);
}

@media not (forced-colors) {
  #categories > .category[selected],
  #categories > .category.selected {
    color: var(--in-content-accent-color);
  }

  #categories > .category[selected]:not(:hover) {
    /* override richlistitem selected style while letting hover style above apply */
    background-color: transparent;
  }

  #categories > .category[selected]:hover:active,
  #categories > .category.selected:hover:active {
    color: var(--in-content-accent-color-active);
  }
}

@media (forced-colors) {
  #categories > .category {
    /* The transition causes issues with the text getting a background while
     * transitioning and it looks weird. */
    transition: none;
    /* We need a true transparent but in HCM this would compute to an actual color,
     * so select the page's background color instead: */
    border-color: var(--in-content-page-background);
  }

  #categories > .category[selected],
  #categories > .category.selected {
    background-color: var(--in-content-button-background-hover);
    color: var(--in-content-button-text-color-hover);
    border-color: var(--in-content-button-border-color-hover);
  }
}

#categories[keyboard-navigation="true"]:focus-visible > .category[current],
#categories > .category:focus-visible {
  outline: var(--in-content-focus-outline);
  outline-offset: var(--in-content-focus-outline-inset);
}

html|*#categories[last-input-type="mouse"] > html|button.category:focus-visible {
  outline: none;
}

.category-name {
  font-size: 1.07em;
  line-height: 1.4em;
  padding-inline-start: 9px;
  margin: 0;
  user-select: none;
}

.category-icon {
  width: 24px;
  height: 24px;
  -moz-context-properties: fill, fill-opacity;
  fill: currentColor;
}

.category[selected] > .category-icon,
.category.selected > .category-icon {
  fill-opacity: 1;
}

@media (max-width: 830px) {
  :root {
    --in-content-sidebar-width: 118px;
  }

  html|*.category:not(.category-no-icon) > html|*.category-name,
  .category-icon + .category-name {
    display: none;
  }

  #categories > .category {
    padding-inline-start: 12px; /* make category icons align center */
    margin-inline-end: 33px;
  }

  html|*#categories > html|*.category {
    width: 48px;
    min-width: auto;
    box-sizing: border-box;
  }

  html|*#categories > html|*.category,
  /* We need to override the full-width RTL rule, so explicitly specify RTL. */
  html|*#categories > html|*.category:dir(rtl) {
    background-position: center;
  }

  .main-content {
    padding-inline: 0;
  }

  .pane-container {
    margin-inline-end: 10px;
  }
}

/* header */

.header {
  margin-inline-end: 4px; /* add the 4px end-margin of other elements */
  margin-bottom: 15px;
  padding-bottom: 15px;
  align-items: baseline;
}

.header-name {
  margin: 0;
}

/* List boxes */

html|select[size][multiple],
xul|listheader,
xul|richlistbox {
  appearance: none;
  margin-inline: 0;
  background-color: var(--in-content-box-background);
  border: 1px solid var(--in-content-box-border-color);
  border-radius: 4px;
  color: var(--in-content-text-color);
}

xul|listheader {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  overflow: clip; /* Clip border-radius */
}

xul|listheader + xul|richlistbox {
  margin-top: 0;
  border-top: none;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

html|select[size][multiple] > html|option,
xul|treechildren::-moz-tree-row {
  padding: 0.3em;
  margin: 0;
  border: none;
  border-radius: 0;
  background-image: none;
}

xul|treechildren::-moz-tree-row(multicol, odd) {
  background-color: var(--in-content-box-background-odd);
}

html|select[size][multiple] > html|option:hover,
xul|treechildren::-moz-tree-row(hover) {
  background-color: var(--in-content-item-hover);
  color: var(--in-content-item-hover-text);
}

xul|richlistbox > xul|richlistitem[selected],
xul|treechildren::-moz-tree-row(selected) {
  background-color: var(--in-content-item-selected);
  color: var(--in-content-item-selected-text);
}

@media not (forced-colors) {
  xul|richlistbox:not(#categories) > xul|richlistitem[selected] {
    /* Ensure buttons/menulists inside richlistitems (containers, applications) look OK */
    --in-content-button-background: color-mix(in srgb, currentColor 15%, transparent);
    --in-content-button-background-hover: color-mix(in srgb, currentColor 30%, transparent);
    --in-content-button-background-active: color-mix(in srgb, currentColor 45%, transparent);
    --in-content-button-text-color: var(--in-content-item-selected-text);
    --in-content-button-text-color-hover: var(--in-content-item-selected-text);
    --in-content-button-text-color-active: var(--in-content-button-text-color-hover);
    --in-content-focus-outline-color: var(--in-content-item-selected-text);
  }
}

xul|richlistitem[selected] xul|menulist:focus-visible {
  outline-offset: var(--in-content-focus-outline-inset);
}

/* Use a 2px border so that selected row highlight is still visible behind
    an existing forced colors border that uses the background color */
@media (forced-colors) {
  xul|treechildren::-moz-tree-row(selected) {
     border: 2px solid currentColor;
     border-radius: 4px;
  }
}

xul|panel[type="autocomplete-richlistbox"] {
  background-color: var(--in-content-box-background);
  border: 1px solid var(--in-content-box-border-color);
  color: var(--in-content-text-color);
}

/* Trees */

xul|tree {
  appearance: none;
  font-size: 1em;
  border: 1px solid var(--in-content-box-border-color);
  border-radius: 4px;
  background-color: var(--in-content-box-background);
  color: inherit;
  margin: 0;
}

xul|treecols {
  appearance: none;
  border: none;
  border-top-left-radius: 4px;
  border-top-right-radius: 4px;
  border-bottom: 1px solid var(--in-content-border-color);
  overflow: clip; /* Clip border-radius */
  padding: 0;
}

xul|treecol:not([hideheader="true"]),
.tree-columnpicker-button {
  appearance: none;
  border: none;
  border-radius: unset;
  background-color: var(--in-content-button-background);
  color: var(--in-content-button-text-color, inherit);
  padding: 5px 10px;
}

xul|treecol:not([hideheader="true"], [sortable="false"]):hover,
.tree-columnpicker-button:hover {
  background-color: var(--in-content-button-background-hover);
  color: var(--in-content-button-text-color-hover);
}

xul|treecol:not([hideheader="true"], [sortable="false"]):hover:active,
.tree-columnpicker-button:hover:active {
  background-color: var(--in-content-button-background-active);
  color: var(--in-content-button-text-color-active);
}

xul|treecol:not([hideheader="true"], :first-child),
.tree-columnpicker-button {
  border-inline-start-width: 1px;
  border-inline-start-style: solid;
  border-image: linear-gradient(transparent 0%, transparent 20%, var(--in-content-box-border-color) 20%, var(--in-content-box-border-color) 80%, transparent 80%, transparent 100%) 1 1;
}

@media (forced-colors) {
  xul|treecol:not([hideheader="true"], :first-child),
  xul|treecolpicker {
    --in-content-box-border-color: var(--in-content-button-border-color);
  }
}

xul|treecol[sortDirection]:not([hideheader="true"]) > xul|*.treecol-sortdirection {
  list-style-image: url("chrome://global/skin/icons/sort-arrow.svg");
  -moz-context-properties: fill;
  fill: currentColor;
  width: 18px;
  height: 18px;
}

xul|treecol[sortDirection="ascending"]:not([hideheader="true"]) > xul|*.treecol-sortdirection {
  transform: scaleY(-1);
}

/* This is the only way to increase the height of a tree row unfortunately */
xul|treechildren::-moz-tree-row {
  min-height: 2em;
}

xul|treechildren::-moz-tree-cell-text(hover),
xul|treechildren::-moz-tree-twisty(hover),
xul|treechildren::-moz-tree-image(hover) {
  color: var(--in-content-item-hover-text);
}

xul|treechildren::-moz-tree-cell-text(selected),
xul|treechildren::-moz-tree-twisty(selected),
xul|treechildren::-moz-tree-image(selected) {
  color: var(--in-content-item-selected-text);
}

/* Message bars */
.message-bar {
  background-color: var(--in-content-box-info-background);
  border-radius: 4px;
  min-height: 32px;
  align-items: center;
  padding: 4px;
}

.message-bar-description {
  margin: 2px 0;
  line-height: 1.25;
}

.message-bar-description.rtl-locale {
  direction: rtl;
  text-align: match-parent;
}

/* The message-bar-button styles have extra specificity to override
 * the defaults for buttons. */
.message-bar-content > .message-bar-button {
  background-color: var(--grey-90-a10);
  border: none;
  border-radius: 2px;
  height: 24px;
  margin-inline-start: 8px;
  padding: 0 8px;
}

.message-bar-content > .message-bar-button:hover {
  background-color: var(--grey-90-a20);
}

.message-bar-content > .message-bar-button:hover:active {
  background-color: var(--grey-90-a30);
}

.message-bar-icon {
  content: url("chrome://global/skin/icons/info.svg");
  width: 24px;
  height: 24px;
  padding: 4px;
  margin-inline-end: 4px;
  -moz-context-properties: fill;
  fill: currentColor;
}

/* Warning styles */
.message-bar-warning {
  background-color: var(--yellow-50);
  color: var(--yellow-90);
}

.message-bar-warning > .message-bar-icon {
  content: url("chrome://global/skin/icons/warning.svg");
}

input[type="text"][warning]:enabled:not(:focus) {
  border-color: var(--yellow-60);
  box-shadow: 0 0 0 1px var(--yellow-60), 0 0 0 4px var(--yellow-60-a30);
}

/* Cards */

.card {
  background: var(--in-content-box-background);
  /* Needed for high-contrast where the border will appear. */
  border: 1px solid transparent;
  border-radius: 4px;
  box-shadow: var(--card-shadow);
  margin: 0 0 8px;
  /* Remove the border from the overall padding. */
  padding: calc(var(--card-padding) - 1px);
  transition: box-shadow 150ms;
}

.card:not(.card-no-hover):hover {
  box-shadow: var(--card-shadow-hover);
}

.card-heading-image {
  border-top-left-radius: 4px;
  border-top-right-radius: 4px;
  margin: -16px -16px 16px;
}

.card-heading-image:dir(rtl) {
  transform: scaleX(-1);
}

/* Sidebar footer links */

.sidebar-footer-list {
  list-style-type: none;
  margin-block: 0 36px;
  margin-inline: 34px 0;
  padding: 0;
}

.sidebar-footer-link {
  height: 36px;
  cursor: default;
  border: 1px solid var(--in-content-button-border-color);
  border-radius: 4px;
  display: flex;
  align-items: center;
}

@media (forced-colors) {
  .sidebar-footer-link {
    /* We need a true transparent but in HCM this would compute to an actual color,
     * so select the page's background color instead: */
    border-color: var(--in-content-page-background);
  }
}

.sidebar-footer-link,
.sidebar-footer-link:visited {
  /* Override link style for :hover and :hover:active states */
  text-decoration: none !important;
  color: inherit;
}

xul|*.sidebar-footer-link {
  display: flex;
  align-items: center;
}

.sidebar-footer-link:hover {
  background-color: var(--in-content-button-background-hover);
  color: var(--in-content-button-text-color-hover);
  border-color: var(--in-content-button-border-color-hover);
}

.sidebar-footer-link:hover:active:not([disabled]) {
  background-color: var(--in-content-button-background-active);
  color: var(--in-content-button-text-color-active);
  border-color: var(--in-content-button-border-color-active);
}

.sidebar-footer-link:focus-visible {
  outline: var(--in-content-focus-outline);
  outline-offset: var(--in-content-focus-outline-inset);
}

.sidebar-footer-icon {
  -moz-context-properties: fill, fill-opacity;
  fill: currentColor;
  width: 16px;
  height: 16px;
  margin: 10px;
  margin-inline-start: 13px;
}

.sidebar-footer-label {
  font-size: .9em;
  margin: 0 4px;
  user-select: none;
}

@media (max-width: 830px) {
  .sidebar-footer-list {
    margin-inline-start: 40px;
    align-items: flex-start;
  }

  .sidebar-footer-link {
    width: 36px;
    height: 36px;
    padding-inline-start: 0;
    margin-inline-start: 1px;
  }

  .sidebar-footer-icon {
    margin-inline-start: 10px;
  }

  .sidebar-footer-label {
    display: none;
  }
}

/* Icon helper classes */

xul|*.help-icon {
  list-style-image: url("chrome://global/skin/icons/help.svg");
}

xul|*.addons-icon {
  list-style-image: url("chrome://mozapps/skin/extensions/extension.svg");
}

/* Back button */

.back-button {
  -moz-context-properties: fill;
  fill: currentColor;
  background-image: url("chrome://global/skin/icons/arrow-left.svg");
  background-repeat: no-repeat;
  background-position: center;
  min-width: auto;
  width: 32px;
  margin-block: 0;
  margin-inline-start: 0;
}

.back-button:-moz-locale-dir(rtl),
.back-button:dir(rtl) {
  transform: scaleX(-1);
}

/* Adjust vertical margins for buttons in dialogs. We do this here because
 * this sheet gets inserted into the Shadow DOM for the button box in the dialog,
 * so can actually affect the button styling that way.  */
:host(dialog[subdialog]) .dialog-button-box > button {
  margin: 0 4px;
  min-width: auto;
}
PK
!<��~�$$8chrome/toolkit/skin/classic/global/in-content/common.css/* - This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this file,
   - You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/in-content/common-shared.css");

@namespace html "http://www.w3.org/1999/xhtml";
@namespace xul "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";

@media (prefers-color-scheme: dark) {
  /* Don't apply scrollbar-color since it removes the native scrollbar style on Linux */
  :root {
    scrollbar-color: initial;
  }
}

xul|tab[visuallyselected] {
  /* Override styles for tab[selected] from
     toolkit/themes/linux/global/tabbox.css */
  margin-bottom: 0;
}

/* Overriding appearance also avoids incorrect selection background color with
   light text. */
xul|menulist::part(label-box),
xul|*.radio-label-box,
xul|*.checkbox-label-box,
xul|*.button-menu-dropmarker {
  appearance: none;
}

xul|menulist {
  font-size: inherit;
}

html|button {
  /* XUL button min-width */
  min-width: 6.3em;
}
PK
!<ǁU���<chrome/toolkit/skin/classic/global/in-content/info-pages.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/in-content/common.css");

:root {
  --in-content-container-min-width: 13em;
  --in-content-container-max-width: 52em;
}

/* Body and container */
body {
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  min-height: 100vh;
  padding: 40px 48px;
  align-items: center;
  justify-content: center;
}

body.wide-container {
  display: block;
}

.container {
  min-width: var(--in-content-container-min-width);
  max-width: var(--in-content-container-max-width);
}

/* Typography */
.title {
  background-position: left 0;
  background-repeat: no-repeat;
  background-size: 1.6em;
  /* Ensure sufficient space for the background image: */
  min-height: 1.6em;
  margin-inline-start: -2.3em;
  padding-inline-start: 2.3em;
  font-size: 2.2rem;
  -moz-context-properties: fill;
  fill: currentColor;
}

.title:-moz-locale-dir(rtl),
.title:dir(rtl) {
  background-position: right 0;
}

.title-text {
  font-size: 2.2rem;
  padding-bottom: 0.4em;
}

@media (max-width: 970px) {
  .title {
    padding-inline-start: 0;
    margin-inline-start: 0;
    padding-top: 2.3em;
  }

  .title-text {
    padding-top: 0;
  }
}

.page-subtitle {
  margin-bottom: 2em;
}

ul, ol {
  margin: 1em 0;
  padding: 0;
  margin-inline-start: 2em;
}

ul > li, ol > li {
  margin-bottom: .5em;
}

ul {
  list-style: disc;
}

dt {
  font-weight: bold;
}

ul.columns {
  column-count: 2;
  column-gap: 5em;
}

@media (max-width: 35em) {
  ul.columns {
    column-count: 1;
  }
}

/* Buttons */
.button-container {
  margin-top: 1.2em;
}

button {
  padding: 0 1.5em;
}

.button-container > button:first-child {
  margin-inline-start: 0;
}

.button-container > button:last-child {
  margin-inline-end: 0;
}

/* Trees */

tree {
  width: 100%;
}

/* Tables */

table {
  background-color: var(--in-content-table-background);
  color: var(--in-content-text-color);
  font: message-box;
  text-align: start;
  width: 100%;
  border: 1px solid var(--in-content-table-border-color);
  border-radius: 4px;
  border-spacing: 0;
  overflow: hidden;
}

table button {
  padding-inline: 3px;
}

th, td {
  padding: 4px;
  text-align: match-parent;
}

thead th {
  text-align: center;
}

th {
  background-color: var(--in-content-table-header-background);
  color: var(--in-content-table-header-color);
  border: 1px solid var(--in-content-table-border-color);
}

th.column {
  white-space: nowrap;
  width: 0;
}

td {
  border: 1px solid var(--in-content-border-color);
  unicode-bidi: plaintext; /* Make sure file paths will be LTR */
}

.action-box {
  background-color: var(--in-content-table-background);
  border: 1px solid var(--in-content-box-border-color);
  border-radius: 4px;
  padding: 16px;
  flex: 1 1 25%;
}

.header-flex {
  display: flex;
  gap: 1.5rem;
  margin-bottom: 1rem;
  flex-wrap: wrap;
}

.content-flex {
  flex: 1 1 65%;
}
PK
!<~���nn6chrome/toolkit/skin/classic/global/in-content/wifi.svg<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg version="1.1"
     xmlns="http://www.w3.org/2000/svg"
     width="64"
     height="64"
     viewBox="0 0 64 64">

  <style>
    .gray {
      fill: #797c80;
    }
  </style>

  <defs>
    <clipPath id="clip-path">
      <polygon points="32 52.35 78.88 6.06 -14.88 6.06 32 52.35"/>
    </clipPath>
  </defs>

  <circle class="gray" cx="32" cy="52" r="6"/>

  <g clip-path="url('#clip-path')">
    <path class="gray" d="M71.63,52A39.63,39.63,0,1,1,32,12.38,39.63,39.63,0,0,1,71.63,52ZM32,7.63A44.38,44.38,0,1,0,76.38,52,44.38,44.38,0,0,0,32,7.63Z"/>
    <path class="gray" d="M47.75,52A15.75,15.75,0,1,1,32,36.25,15.75,15.75,0,0,1,47.75,52ZM32,31.65A20.35,20.35,0,1,0,52.35,52,20.35,20.35,0,0,0,32,31.65Z"/>
    <path class="gray" d="M59.58,52A27.58,27.58,0,1,1,32,24.42,27.58,27.58,0,0,1,59.58,52ZM32,19.38A32.63,32.63,0,1,0,64.63,52,32.63,32.63,0,0,0,32,19.38Z"/>
  </g>
</svg>
PK
!<}�˧8chrome/toolkit/skin/classic/global/media/audio-muted.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m11 4.149 0 4.181 1.775 1.775c.3-.641.475-1.35.475-2.105a4.981 4.981 0 0 0-1.818-3.851l-.432 0z"/>
  <path d="M2.067 1.183a.626.626 0 0 0-.885.885L4.115 5 2 5a2 2 0 0 0-2 2l0 2a2 2 0 0 0 2 2l2.117 0 3.128 3.65C7.848 15.353 9 14.927 9 14l0-4.116 3.317 3.317c-.273.232-.56.45-.873.636a.624.624 0 0 0-.218.856.621.621 0 0 0 .856.219 7.58 7.58 0 0 0 1.122-.823l.729.729a.626.626 0 0 0 .884-.886L2.067 1.183z"/>
  <path d="M9 2c0-.926-1.152-1.352-1.755-.649L5.757 3.087 9 6.33 9 2z"/>
  <path d="M11.341 2.169a6.767 6.767 0 0 1 3.409 5.864 6.732 6.732 0 0 1-.83 3.217l.912.912A7.992 7.992 0 0 0 16 8.033a8.018 8.018 0 0 0-4.04-6.95.625.625 0 0 0-.619 1.086z"/>
</svg>
PK
!<���jAA2chrome/toolkit/skin/classic/global/media/audio.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M7.245 1.35 4.117 5 2 5a2 2 0 0 0-2 2l0 2a2 2 0 0 0 2 2l2.117 0 3.128 3.65C7.848 15.353 9 14.927 9 14L9 2c0-.927-1.152-1.353-1.755-.65z"/>
  <path d="M11.764 15a.623.623 0 0 1-.32-1.162 6.783 6.783 0 0 0 3.306-5.805 6.767 6.767 0 0 0-3.409-5.864.624.624 0 1 1 .619-1.085A8.015 8.015 0 0 1 16 8.033a8.038 8.038 0 0 1-3.918 6.879c-.1.06-.21.088-.318.088z"/>
  <path d="M11.434 11.85A4.982 4.982 0 0 0 13.25 8a4.982 4.982 0 0 0-1.819-3.852l-.431 0 0 7.702.434 0z"/>
</svg>
PK
!<Ut�2RR?chrome/toolkit/skin/classic/global/media/audioNoAudioButton.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg"
     width="18px" height="18px" viewBox="0 0 18 18">
  <path fill="context-fill"
        d="M14.901,3.571l-4.412,3.422V1.919L6.286,5.46H4.869c-1.298,0-2.36,1.062-2.36,2.36v2.36
           c0,1.062,0.708,1.888,1.652,2.242l-2.242,1.77l1.18,1.416L16.081,4.987L14.901,3.571z M10.489,16.081V11.36l-2.669,2.36
           L10.489,16.081z"/>
</svg>

PK
!<�W�Achrome/toolkit/skin/classic/global/media/castingButton-active.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg"
     width="18px" height="18px" viewBox="0 0 18 18">
  <path fill="context-fill"
        d="M0 13.91v2.454h2.455A2.451 2.451 0 0 0 0 13.909zm0-3.274v1.637a4.092 4.092 0 0 1 4.09 4.09h1.637A5.723 5.723 0 0 0 0 10.637zM14.727 4.91H3.273v1.334a10.664 10.664 0 0 1 6.848 6.848h4.606zM0 7.364V9a7.364 7.364 0 0 1 7.364 7.364H9a9 9 0 0 0-9-9zm16.364-5.728H1.636C.736 1.636 0 2.373 0 3.273v2.454h1.636V3.273h14.728v11.454h-5.728v1.637h5.728c.9 0 1.636-.737 1.636-1.637V3.273c0-.9-.736-1.637-1.636-1.637z"
        fill-rule="evenodd"/>
</svg>
PK
!<�G\���@chrome/toolkit/skin/classic/global/media/castingButton-ready.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg"
     width="18px" height="18px" viewBox="0 0 18 18">
  <path fill="context-fill"
        d="M16.364 1.636H1.636C.736 1.636 0 2.373 0 3.273v2.454h1.636V3.273h14.728v11.454h-5.728v1.637h5.728c.9 0 1.636-.737 1.636-1.637V3.273c0-.9-.736-1.637-1.636-1.637zM0 13.91v2.455h2.455A2.451 2.451 0 0 0 0 13.909zm0-3.273v1.637a4.092 4.092 0 0 1 4.09 4.09h1.637A5.723 5.723 0 0 0 0 10.637zm0-3.272V9a7.364 7.364 0 0 1 7.364 7.364H9a9 9 0 0 0-9-9z"
        fill-rule="evenodd" />
</svg>
PK
!<=�O..Kchrome/toolkit/skin/classic/global/media/closed-caption-settings-button.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
  - License, v. 2.0. If a copy of the MPL was not distributed with this
  - file, You can obtain one at http://mozilla.org/MPL/2.0/.-->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
  <path fill-rule="evenodd" clip-rule="evenodd" d="m13.15 2.25.6.6v7.3l-.6.6h-1.9c-.28 0-.5.22-.5.5v2.15l-3.04-2.43c-.18-.14-.4-.22-.62-.22H2.85l-.6-.6v-7.3l.6-.6h10.3ZM13 1H3c-1.1 0-2 .9-2 2v7c0 1.1.9 2 2 2h4l3.75 3H12v-3h1c1.1 0 2-.9 2-2V3c0-1.1-.9-2-2-2ZM6.27 7.73H4.86a.62.62 0 0 0-.62.62v.01c0 .342.278.62.62.62h1.41a.62.62 0 0 0 .62-.62v-.01a.62.62 0 0 0-.62-.62Zm2.24 0h2.63a.62.62 0 0 1 .62.62v.01a.62.62 0 0 1-.62.62H8.51a.62.62 0 0 1-.62-.62v-.01a.62.62 0 0 1 .62-.62Zm2.63-2.75H9.73a.62.62 0 0 0-.62.62v.01c0 .342.278.62.62.62h1.41a.62.62 0 0 0 .62-.62V5.6a.62.62 0 0 0-.62-.62Zm-6.28 0h2.63a.62.62 0 0 1 .62.62v.01a.62.62 0 0 1-.62.62H4.86a.62.62 0 0 1-.62-.62V5.6a.62.62 0 0 1 .62-.62Z" fill="context-fill" fill-opacity="context-fill-opacity"/>
</svg>
PK
!<�'[���Gchrome/toolkit/skin/classic/global/media/closedCaptionButton-cc-off.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg"
     width="18px" height="18px" viewBox="0 0 18 18">
  <path fill="context-fill" fill-rule="evenodd"
        d="M16.531,16.107H5.267l1.982-2H15c0.6,0,1-0.4,1-1V5.274
           l1.946-1.964C17.963,3.399,18,3.483,18,3.576v11.031C18,15.407,17.331,16.107,16.531,16.107z M14.016,8.506h-1.218l1.005-1.014
           C13.913,7.789,13.984,8.128,14.016,8.506z M11.786,12.361c-0.828,0-1.476-0.326-1.913-0.902l1.09-1.101
           c0.136,0.323,0.374,0.541,0.796,0.541c0.514,0,0.695-0.44,0.756-1.014h1.535C13.908,11.43,13.071,12.361,11.786,12.361z
           M1.496,16.106C0.697,16.104,0,15.406,0,14.607V3.576c0-0.8,0.7-1.5,1.5-1.5h12.846L16.299,0l1.316,1.283L2.615,17.13L1.496,16.106
           z M3,4.107c-0.6,0-1,0.4-1,1v8c0,0.6,0.4,1,1,1h0.029l2.031-2.16c-0.757-0.503-1.191-1.457-1.191-2.744
           c0-1.936,1.069-3.14,2.428-3.14c1.357,0,2.136,0.76,2.361,2.059l3.777-4.016H3z M8.298,8.506H7.355
           c-0.047-0.623-0.49-1.23-0.99-1.23c-0.561,0-1.337,0.84-1.337,1.995c0,0.674,0.381,1.427,0.95,1.702L8.298,8.506z"/>
</svg>

PK
!<�[Y���Fchrome/toolkit/skin/classic/global/media/closedCaptionButton-cc-on.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg"
     width="18px" height="18px" viewBox="0 0 18 18">
  <path fill="context-fill"
        d="M16.531,1.984H1.5c-0.8,0-1.5,0.7-1.5,1.5v11.031c0,0.8,0.7,1.5,1.5,1.5h15.031
           c0.8,0,1.469-0.7,1.469-1.5V3.484C18,2.684,17.331,1.984,16.531,1.984z
           M16,13.016c0,0.6-0.4,1-1,1H3c-0.6,0-1-0.4-1-1v-8c0-0.6,0.4-1,1-1h12c0.6,0,1,0.4,1,1V13.016z
           M6.426,10.807c-0.811,0-0.96-0.789-0.96-1.628c0-1.155,0.338-1.745,0.899-1.745c0.5,0,0.818,0.357,0.866,0.98
           h1.484C8.585,6.877,7.785,5.972,6.297,5.972c-1.359,0-2.428,1.205-2.428,3.14c0,1.944,0.974,3.157,2.583,3.157
           c1.285,0,2.153-0.93,2.295-2.476H7.244C7.183,10.367,6.94,10.807,6.426,10.807z
           M11.759,10.807c-0.811,0-0.96-0.789-0.96-1.628c0-1.155,0.338-1.745,0.899-1.745c0.5,0,0.756,0.357,0.803,0.98h1.515
           c-0.129-1.537-0.898-2.443-2.385-2.443c-1.359,0-2.396,1.205-2.396,3.14c0,1.944,0.943,3.157,2.552,3.157
           c1.285,0,2.122-0.93,2.264-2.476h-1.535C12.454,10.367,12.273,10.807,11.759,10.807z"/>
</svg>

PK
!<rg��2chrome/toolkit/skin/classic/global/media/error.png�PNG


IHDR�x �1�IDATx��%�Q�q�ݡ��N#a�����~�d�&
��7����;�j�oݞ9�ic��_{OH":v��C�X4�wI��`V�)d*�����I5
u�ݚeYO�0Mڇ*�r��^�W�m�`��I�R�����z���8/�<0��	�$�a�e5]�}���1y�f���w�Nf~0�M:0�h�K:�a�����/��f"�LE�U�e�f4�$�`fA�]�#�f4!I��Y���0���/I�`����
fO�|@`S.����f���L2�P�V�	J��T&�	��`0�`V��0�`�0�0�`s�`�0�`�&��?�v��6`��UA��@�
g�6��^��i�F�<[&@`A8�va��;$��1k�I�T�yn���>?�t�M`�T*�q��v3�%��"o��P�,�
`n׫��jE�$VA`L��)�`l��n�`e�F##X�,��
�f�Zi��zF��Z�j�j���Gb�$���yf���Q��n�m�k�k_z�˥	0���;{{�-�վ�Ek�Z��d̏���[�ץ�Ʀ���-��\����0���`�0�`c0��C�d2Q�~_5��b�/�?�_>�N��<�B`�!���qd�P�G-��<;�$q�������|��p8TI�<��a���ݢ˕3�}�s���#z�2���Km��X��v�wϐ��0�`�*}�!u9�>O�u���w��ql;�x�Wt<��ʿø��F_y����#h���2��:s�0�`0�`�0�`�0�`��H0�`��o{��Ȳ`|��{׶}k۶m۶m۶m۶�UW��tf�ݞ��ߜ|�tt&nܸo#��`��?�'L0&�pA�,��6Cc�	�}0� ��ۑ`xxHЌ&���w�����>8˾h��L0�p�X�*Ud���jP��m�d�D*T��x?<��-�����5��G���`~�x<������`�!.�`��&�5n`&�1�#���DFg<�xŸ��N�Cpb�=,DQD��g�q�����+!>r-�?�`�a;�QF{o����pA��riS�8�~�
&*@<��U��ӧesS�c'�n�A03 �O��
fD��Ç��v��u��I�q�2��3b�D&�`���	���r���ɓ�x�b5�I�繐�M0��!�p���	�����ݺuK�mۦ�{�3�1�8\���w��QۣG�T4�̻n����c-���L��Q�.[I:~�x���a�|��x1�\@%�����_�r"�3gΨI�z�뼸/�t�5��FP1lߗ��!�'�U$���WW���o�v�=z��qout�l܇�%@2��}o5oGE����?h޼��	<a�u��ѣm�ԩSG�;��¾��Xȍ!8y�<��F�/vl����/�ԨQC��_ծ][��z!��B0v|���U�yK���@:Ă����{���(gΜ�h>E�@���[���OȌ��8����LA�A
|K�~0a��&y�敖-[������]�tiI�0��5��E�OH������qX�8�˸�t�p�0#�
P
Y�_ ,l�	⩤I�J�bŤq����X;�!C�H�v�bŊ�9sf���W�B@,��1|��� *�����[)S�T[ŋWT�ZUX���k�Z��J�*�9���I�6��]`�
�#$D�X&�����2���vH�+�L0~�H�ڇ��
&�O�{Q?�LL0@l�#H��F3D��L0�����y����`���a��$[�r�+�`^�!r�+6��K���1`�&�fDj+��h��/h�
ȅ�9H�c���i�`�?���đ|��IEND�B`�PK
!<���kkBchrome/toolkit/skin/classic/global/media/fullscreenEnterButton.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg"
     width="18px" height="18px" viewBox="0 0 18 18">
  <path fill="context-fill"
        d="M6.728,10.188l-3.235,3.094l0.017-2.267l-1.513-0.016l0,5l4.987-0.008l0.011-1.537l-2.281-0.022
           l3.097-3.158L6.728,10.188z M14.453,11.004l-0.022,2.281l-3.158-3.097l-1.086,1.083l3.094,3.235l-2.267-0.017l-0.016,1.514l5,0
           l-0.008-4.988L14.453,11.004z M11.015,2.01l-0.011,1.537l2.281,0.022l-3.097,3.158l1.083,1.086l3.235-3.094L14.49,6.986
           l1.513,0.016v-5L11.015,2.01z M6.986,3.511l0.016-1.514l-5,0L2.01,6.985l1.537,0.011l0.022-2.281l3.158,3.097l1.086-1.083
           L4.718,3.494L6.986,3.511z"/>
</svg>

PK
!<�PWWAchrome/toolkit/skin/classic/global/media/fullscreenExitButton.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg"
     width="18px" height="18px" viewBox="0 0 18 18">
  <path fill="context-fill"
        d="M2.047,11.135l-0.011,1.537l2.281,0.022L1.22,15.851l1.083,1.086l3.235-3.094l-0.017,2.268l1.513,0.016
           l0-5L2.047,11.135z M13.781,12.587l2.267,0.017l0.016-1.514l-5,0l0.008,4.988l1.537,0.011l0.022-2.281l3.158,3.097l1.086-1.083
           L13.781,12.587z M16.058,5.578l-2.281-0.021l3.097-3.158l-1.083-1.086l-3.235,3.094l0.017-2.267L11.06,2.123v5l4.988-0.008
           L16.058,5.578z M5.516,2.098L5.494,4.379L2.336,1.283L1.25,2.365L4.344,5.6L2.077,5.583L2.06,7.097l5,0L7.053,2.109L5.516,2.098z"/>
</svg>

PK
!<�c����?chrome/toolkit/skin/classic/global/media/imagedoc-darknoise.png�PNG


IHDR����tEXtSoftwareAdobe ImageReadyq�e<PLTE"""!!!###   HAȫqIDATx��[	��8��yE���Y������8����f�#��=,����g���z�O�3���WqS}U�y��"ps���z�~��[f�X���21S
��5>���}��O����E��V����Z��}����
�{jy��g�H��j�}Dǯ{r̎�f��p�֙}����ǚ��Ӣvk��~gM�}1��ư�h�I�����J���>f�ZF��/��8�+�ɲՆ[��c���:��E:���dTG�Ik���~t܅��ĸ��6�°�)�Du�F�]t8�R!vh�Ц
�5Pp;QW깚��ʅ�}����M�i
Dž��J��:�-�:��kW��ӌu�.]*���F�ŚjX���J��A�7�Ѣk�fC�T��LT�:����l��,ĦսI�5!���L�U��^k��ͭ�ܢ� pY�w�SRAd���<}�Bhup�_i�Qջ3	+F[�ց���~R��d�+��5�£l�n�BB �.���2�����ҀF��O���>u*�pV	�6�p>�)9��Z�}R	xڍ��q2h�l���	�ಆ�B��	;�ce��x�$�\gJ�1��X
1���V��JR�qj�	Y:��q�͚"����`\�#M�3�B[���[KS�6O�'��W���&-v�� �IGMu�ٔ�;jb�`�cr�s}��θ��M'c�gʞ�j�9"ty�:�:T�t��L�)�CѩF]� ��Eʒ�O�R��J�c�A	���M"u%J���j!M���iD���j�S
-iғ$����q\�(9����rs���cp
8F�n����3���)t�A�ܲ<����AB�h+t�������T��hk�\��KL�j#��G%�Cj8f�,��a�_+�q�œ4#�����v�!��8Q9�ɐo;~I��o�{�g2V:MjC#��Q>�)�r��9|��/��H���0������@�h!w@��!�Е���ܥt�O|')�q�<�	�t4�tO4yP(�f�w4�N��(0��D	z1��Ҭ�1@3�+\I1��A�j�q$��	��4ZoM���A�:c�<��ʽ�+��e+`,UkIg^|ls�1$�Ҥ:��0����"?��L����O&	U�d�S���\�?�2�3�aG85�-�I��Vh�F�'<�`k>�uR=�N<i8�I8��:������e�4C>��sG>]��ȯ���9�)\�.���C�I0ŜRĒ���A�\d�� �ɚ5�<v2|��'���F0��LMc��U�F	��~b�&M�	��y���@��_��ź�R+�M��ۍV��u�6���g�߶Ӄ���@� c�Hg�H&I�W�4w��^C���5}�R���X�|�'€?�B� #��F���C.��[�z���]6�˲�s�zA�W�d�
�>��&´9ڑ�Id>H�釼$�1��j6’v�07�~�w?YF2㧗�*�cE���l*u@��|���[|�=�iL��(��0�ĵ��=n�.��]@(�pC��Ar���D�t�
�㪜��������9��+���np��GL���,w�^���T
�K�dRW��Oprb-�9��z:�{�O���L�ԋ�wW+-;�?�
��	�6 �Gv6�u��)���KSW�x�v���f0F�X�-P� 9���=\*�J~�rVp��~O7�9S��F̈<%rjVc���ۃLM�t���+��t�w��%?�.��i�t������l!�3I�ÍO�1�0E�Y߉7��2�Um�d�$TYa4&FӨ��~�'9ߛ��6vwEZ( $V��9��/ �1q�?$�ڠ�T�Vȴ�)A�0D��F�DŽ�p�I�	V�T�%\R�9d�<+�h�9e�gw��`�z�­��[J
"!it��.V�Ô�����$퐯����0�~~#�L�F�<�# }�(��[�eb�#]/��<~^�o0MKcΊ�N�S2��&�S4��
��s�w����Ν?aK������OB�	������+��(S���ޙ^~��pZ/	4��ۣBݴj\E8�G8�:�z�����om�K8�l|���;kW`�֋�W��+�o�d~Nv���\�>���&E�{J~r���M�ZJ���V��R>0\���DtC����H�T�m��4rV�6����]�e��'�����s�J�%����`�dc�Io�<I�{CB�!O$�n���ec�Dž�����{U��*3\ɱ���sa�)jx(�E
9:y0�h�C�&����#B�M��L���N
|@bA�u���̔a?M�yL���v~��N݌S-xWI��Й�����ʑ~�XiN��]En�prQ��-���uC������=��42����pz�y�����9>4�^��VJή��L^���|�!�9�DT�`ߌ�0�=�]���J�g��o}h��c��]�n��}fQ�9㷂I,L���'��W�o	��;�?�v0~��Jρ�<�t�o�͋r��K��mx_����Û�{�[�G�N����yߊ��scbT钖��b�#���ށID{��tl����+�|�hp������sOX2K�����5�Z���a[#�2����w���9-�L�Ҟ~C�<FYy=����r�&V�T��hg��I_�W6��;�iC��9���Sd6*�t����������g��d����ÕZ���R{Ӆ��~ %�b�16�j�I���պ�|m�#�g��{�^ݾ��3��}fs�"��;�S���;y�񪎗��_�d���}�)�ʝ[���#�3�B'M(����D�+C��!g����|2_�@�:m`J�,v�����; 't�nCh�wΖ#:�]���m���?�SH�9IEND�B`�PK
!<�;?���@chrome/toolkit/skin/classic/global/media/imagedoc-lightnoise.png�PNG


IHDR����9PLTE���555YYY�����͑�����III��㞞����uuu������   ggg������&�^�tRNSl�AMtRNSl�AM�IDATx���r%9
D%@�(A��S1;�����p�*�y2!|MQ2[���,<�"V�%�enO>�
�o~|���S��{zu;<�zt2���8	��F[��y�(>Q^�7�XY���=Z�h�hT���&��Z\'��� OD^�J��-��|Ϙ�8`n<��8��I:�Z�2̝���;��Q���@�j
�a�G���*d�a������F����O	�P-(9?Е:Kţ������PPA�2Nܪ6�Z�p]5�l��l��0 :����Q3Z�v�K%/J�P&	�q�H��*�J��˸�[�V2;.�W
2)&T���t�R3��F����›�tc11�����-b��AD 3.r!�h�V��%����G��򳏃增��[��iC")m�.�錃�}Ę�*rl�l����c4�<��������)1�u�tEZ�F�o%�p�
�n���^���p)�T�c��8ڥ�ka�#2!Z)���0�g��U��w-�-dAv�h�U{zRW;�A	-B��q��B���ǖ�TA�q��;�;����s;6Q�h�9����\	l���Q8�tv%��&�ʜ�>x��-�M}���5�}�%�-��%�ܠc$�էm���v�ɥ�a(�1pO�rG9t?�ͧ�è'�u��P���`�����=�nͨ�9�be��b�]B4ػ��lO�,�E�R�*@�fn��F���<su�wy���o�_��:�p���Z]�=��:�)3F�
)OW�37p�~B�bkZ[�3���DZ���i�*]h��
��ᅲh��R��,�/��=p�2_L���:�R�����%Xi?���>���{��W`�U�ʉK�#EX��KO�̻<Ƙ}�Y`�"�Y[���6�p����gfJ��Щ�M"�m�Lvy *���Uk�A�b���R�����+W�5����ڥ-t�����Nm�}�i
�4h1�А�k�9��=�jn�C%��6�@m��=�B�cy0��r/3o���&�i;���8`��a0��\���
Ԋ��7Q��	w��/?�l��'w����P��:�6uݒ�=�Wp�Vyb=�7�W��J�\(IlW�c[�H����r1�I?A""���b�<�h�o�L7���6���� ��uE+k]��ùf�޶ʏ�G:�ԍm�Qu[���B�ޠ~:�F���4����@~\�uH�sz�`�*�]�Hx��ǩ>�/�7��g������}��k��˂�צ�s*+ԇ0Af�yeىI�g�k-��S+���5<�6�����د��_�$PQ��͎�˧���l%5����^H=}ϼʋRYIkN��:��g�DՒsc�a�3�=�����mO�
�u���P[�@1�)"a�ԁ�|���x�ɿb�w����;�z�g������d�L4�1�i�zPy���Tflp�����.̼Cڤw���#��`y\C�u��v���g�b�`;���0t`�5q�u�d�#�Q���ݔ�0���|���
����p���JD��p�f!f�k�xH¯��v�����n�7nu���^����[��U��$�{�JP�b<9�,�+�ă�@k��=D\:ˎBH�n>�d�����X=�3<{
�Fz}5\{
�Z7�W����|��&����k&~+�_�(��\$V�'��v����M�_��W�%$�K�’6ʾk2�PA.w�RP�пÚ1Q9�F���v4�>jj详fI����Ӛ�I����9W��T�3�=�G��z�
$6��4q^�x��?���.]s�/l�9ч���?z�.e#'�1\w�X!�;�q?�0*/���9��@�i�p�e�ޣ+�q9�<�?A��E�Ps�1�CU�����Uŕ��t�J�y;]2��y��I�A�K�V���.��9<ɾև��Ox�z[�i��}L�S�5շ��=�:6,P%#[b_Chb��~j�7����2�z�)�4ot����Ɉ��O��U���"�>A�w����]����?؎�W�Г�f
�b9X}[�NH����=��=�=�����[����u#^ݐ�n���T�,�fwW$�-,̍<|A�<��=�_��D�k������پ���j��;����;^�����vLm�T~"c;\ǶH���.���Gנ��w����^�;gF�4����񧛖�ru'r���ܐs?�Y�D��Z�]���b��'O/f�PG%$��QZ���4]�!�,�fC�Md<ڦ}�+(���~�y��d_
����Kg���ϯ�sȦ��s�^b^�Ϟ߹r�W�sIS6���^4��u���V]ao��C��O��T�|�B�;�Y��+��;�	&���|G���S�@�x+W=ZCZ�G�~�����9��Q2Fi|N4��8��^�-��}��?|����Q�gS��V�1Gl]�`@(>T���a������"������$$�$�eIn����<�F�)hzk��*|*(���o�ࠈ,`�B�!@�O)��x�NՅc���pΒ ����+q��	�^ B Be|	ҩ�~qByO8�o����}���s�DΉ�&+�����	I_c`��>Zz�����4o��;EA[-�
b�")�D>�t��O����
C��al��^��R��t��n R0���b퓽��Kzg~��7��+)+��8K���o(�
[���܍�#S��
�,Y=�?��N�-��4�{�kOԮA�fO�G�Ԑa=,e?�Ž��.�a6���1?�֗�r���Q
��ە
5G5�Nu�S�(�~�w�[UK�-�4�i�6��[�ϏԎC��V�>�O�R�>%�s�[	�N1=\�q�b6��.Cl\����fN�Cx�!$1��t��)ܘRIdUczʈ���?Cr�ʵ5s�va��$��
��4����}���!"�O��I�J2�e\�߲KY}!K��6cCD�w6�t6�H��L]`���g;��:�Y�y!qky[�d��UM������K����#�r��ɷ��u���"='D��wz
c �Th����SXs��;efN�q��{�WotrK�lj���c�#x����+����.XM�m,������pz#�[�x#�T��7�Ů�����~��1rz�w���]ҕx���G_Gg;�s��fa�1���P-�`�?9�L
��#VP9눡�h�h�5��2D;�BS8ctv��|�0�	1���w�'�%�}V��I���fb�ٯ/0;�;����l�,��s_�pJM8��|�Q�v�|��@�:`I�Ve�	��`9�Kɭf�U�٠D̝p	���(F&nD�Օs����R	1W<�w65��̌�����H�9����%:<�]G,��>��;տxؖB������ �3��AF�C�@@�4���k�V㑒X�TO�-E��7�B>9uNu��
��b�h�����40�Ʀ�H����k�q��s�qo7�5���93ڡ_���)A>��y�SŇ�U�\̣�]����%��<ZCA�M���7�f�y v(S(N�l浞��Չ�qr�{��J�i�x�&o�"�3E|�'oBW���:���~�f.�H�lk�3�sm%��HZC��.ŵK�e<�N;rr����
���o�Tݻr�
�}=���WHZZ�P56��66�3���,>-O��o@�Wh�nO���!��/�CsG��І_Թ������	7_Kz�IEND�B`�PK
!<S��|//7chrome/toolkit/skin/classic/global/media/pause-fill.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity" xmlns="http://www.w3.org/2000/svg">
  <path d="M4.5 14h1A1.5 1.5 0 0 0 7 12.5v-9A1.5 1.5 0 0 0 5.5 2h-1A1.5 1.5 0 0 0 3 3.5v9A1.5 1.5 0 0 0 4.5 14zM10.5 14h1a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 11.5 2h-1A1.5 1.5 0 0 0 9 3.5v9a1.5 1.5 0 0 0 1.5 1.5z" />
</svg>
PK
!<�*jGGFchrome/toolkit/skin/classic/global/media/picture-in-picture-closed.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M8.125 15a.625.625 0 0 0 0-1.25l-6.275 0-.6-.6 0-9.3.6-.6 10.3 0 .6.6 0 4.275a.625.625 0 0 0 1.25 0L14 4a2 2 0 0 0-2-2L2 2a2 2 0 0 0-2 2l0 9a2 2 0 0 0 2 2l6.125 0z"/>
  <path d="m11.286 12.17-.75.75a.314.314 0 0 1-.536-.223l0-2.509.189-.188 2.509 0c.28 0 .42.338.222.536l-.75.75 2.652 2.652a.618.618 0 0 1 .11.718.496.496 0 0 1-.276.276.618.618 0 0 1-.718-.11l-2.652-2.652z"/>
  <path d="M8 5 4 5a1 1 0 0 0-1 1l0 3a1 1 0 0 0 1 1l4 0a1 1 0 0 0 1-1l0-3a1 1 0 0 0-1-1z"/>
</svg>
PK
!<��
#Wchrome/toolkit/skin/classic/global/media/picture-in-picture-enter-fullscreen-button.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/.-->
<svg width="28" height="28" viewBox="0 0 28 28" fill="context-fill" xmlns="http://www.w3.org/2000/svg">
  <path d="M20.7 7h-3.993a.5.5 0 0 0-.354.854l1.454 1.454-7.5 7.5-1.454-1.454a.5.5 0 0 0-.853.353V19.7l.3.3h3.993a.5.5 0 0 0 .354-.854l-1.454-1.454 7.5-7.5 1.454 1.454a.5.5 0 0 0 .853-.353V7.3l-.3-.3Z"/>
</svg>
PK
!<w�K���Vchrome/toolkit/skin/classic/global/media/picture-in-picture-exit-fullscreen-button.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/.-->
<svg width="28" height="28" viewBox="0 0 28 28" fill="context-fill" xmlns="http://www.w3.org/2000/svg">
  <path fill-rule="evenodd" clip-rule="evenodd" d="M21.374 6a.626.626 0 0 1 .444 1.067l-3.625 3.625 1.454 1.454a.5.5 0 0 1-.354.854H15.3l-.3-.3V8.707a.5.5 0 0 1 .853-.353l1.173 1.173.28.281 3.626-3.625A.626.626 0 0 1 21.374 6ZM9.43 14.084A.5.5 0 0 1 9.707 14H13.7l.3.3v3.993a.5.5 0 0 1-.854.353l-1.173-1.173-.281-.28-3.625 3.624a.63.63 0 0 1-.885.001.628.628 0 0 1 0-.885l3.625-3.625-1.454-1.454a.5.5 0 0 1 .076-.77Z"/>
</svg>
PK
!<�F�GGDchrome/toolkit/skin/classic/global/media/picture-in-picture-open.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m15 10-4 0a1 1 0 0 0-1 1l0 3a1 1 0 0 0 1 1l4 0a1 1 0 0 0 1-1l0-3a1 1 0 0 0-1-1z"/>
  <path d="M8.125 15a.625.625 0 0 0 0-1.25l-6.275 0-.6-.6 0-9.3.6-.6 10.3 0 .6.6 0 4.275a.625.625 0 0 0 1.25 0L14 4a2 2 0 0 0-2-2L2 2a2 2 0 0 0-2 2l0 9a2 2 0 0 0 2 2l6.125 0z"/>
  <path d="m6.714 7.83.75-.75A.314.314 0 0 1 8 7.303l0 2.509-.189.188-2.509 0a.314.314 0 0 1-.222-.536l.75-.75-2.652-2.652a.618.618 0 0 1-.11-.718.496.496 0 0 1 .276-.276.618.618 0 0 1 .718.11L6.714 7.83z"/>
</svg>
PK
!<��5''Schrome/toolkit/skin/classic/global/media/picture-in-picture-seekBackward-button.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/.-->
<svg width="28" height="28" viewBox="0 0 28 28" fill="context-fill" xmlns="http://www.w3.org/2000/svg">
  <path d="M9.196 19.097A7.455 7.455 0 0 1 7 13.793a.625.625 0 1 1 1.25 0c0 1.669.65 3.239 1.831 4.419a6.207 6.207 0 0 0 4.419 1.83 6.257 6.257 0 0 0 6.25-6.25 6.257 6.257 0 0 0-6.25-6.25 6.211 6.211 0 0 0-2.978.773l1.125 1.125a.5.5 0 0 1-.354.853h-3.52L8.5 10.02V6.5a.5.5 0 0 1 .854-.353l1.251 1.25A7.451 7.451 0 0 1 14.5 6.294c4.136 0 7.5 3.364 7.5 7.5 0 4.136-3.364 7.5-7.5 7.5a7.456 7.456 0 0 1-5.304-2.196v-.001Z"/>
  <path d="M14.518 17.08c1.296 0 2.177-.844 2.174-2.02.003-1.114-.776-1.915-1.824-1.915-.51 0-.952.213-1.156.5h-.034l.15-1.457h2.543V11.18h-3.557l-.276 3.069 1.094.196c.18-.241.526-.392.87-.392.585.003 1.005.426 1.005 1.037 0 .605-.412 1.023-.989 1.023-.488 0-.883-.307-.91-.76h-1.195c.023 1.007.9 1.725 2.105 1.725Z"/>
</svg>
PK
!<�U�#��Rchrome/toolkit/skin/classic/global/media/picture-in-picture-seekForward-button.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/.-->
<svg width="28" height="28" viewBox="0 0 28 28" fill="context-fill" xmlns="http://www.w3.org/2000/svg">
  <g clip-path="url(#a)">
    <path d="M19.804 19.097A7.455 7.455 0 0 0 22 13.793a.624.624 0 1 0-1.25 0c0 1.669-.65 3.239-1.831 4.419a6.206 6.206 0 0 1-4.419 1.83 6.257 6.257 0 0 1-6.25-6.25 6.257 6.257 0 0 1 6.25-6.25c1.051 0 2.066.276 2.978.773L16.353 9.44a.5.5 0 0 0 .354.853h3.52l.273-.273V6.5a.5.5 0 0 0-.854-.353l-1.251 1.25A7.451 7.451 0 0 0 14.5 6.294C10.364 6.293 7 9.657 7 13.793c0 4.136 3.364 7.5 7.5 7.5 2.003 0 3.887-.78 5.304-2.196Z"/>
    <path d="M14.518 17.08c1.296 0 2.177-.844 2.174-2.02.003-1.114-.776-1.915-1.824-1.915-.51 0-.952.213-1.156.5h-.034l.15-1.457h2.543V11.18h-3.557l-.276 3.069 1.094.196c.18-.241.526-.392.87-.392.585.003 1.005.426 1.005 1.037 0 .605-.412 1.023-.989 1.023-.488 0-.883-.307-.91-.76h-1.195c.023 1.007.9 1.725 2.105 1.725Z"/>
  </g>
  <defs>
    <clipPath id="a">
      <path transform="translate(6 6)" d="M0 0h16v16H0z"/>
    </clipPath>
  </defs>
</svg>
PK
!<ħ�9L.L.6chrome/toolkit/skin/classic/global/media/pipToggle.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* This CSS file is for the Picture-in-Picture toggle.
 *
 * The "experiment" class is used to enable styling for the VARIANT version
 * of the toggle for upcoming PiP Nimbus experiments.
 * @see Bug 1811314.
 *
 * To see each section of style changes, search "PIP STYLING" in this file.
 */

/* SHARED PIP STYLING */

.controlsOverlay[hidetoggle="true"].hovering > .pip-wrapper:not(.hovering) {
  /* If this isn't !important, it will fail to override the other opacity
   * rules, which are currently defined below this point in this file. */
  opacity: 0 !important;
}

.pip-wrapper {
  position: absolute;
  cursor: pointer;
  -moz-appearance: none;
  background: none;
  color: inherit;
  border: none;
  text-align: unset;
  top: calc(70% - 40px);
  opacity: 0;
  padding-inline: 0;
  transition: opacity 200ms;
  --pip-icon-size: 24px;
  --pip-icon-width-with-margins: calc(2 * var(--pip-toggle-margin) + var(--pip-icon-size));
  --pip-highlight-style: solid rgba(0, 254, 255, 1);
  --pip-highlight-width: 2px;
  --pip-toggle-distanceFromVideoEdge: 18px;
  --pip-toggle-focus-outline-offset: 1px;
  --pip-toggle-margin: 8px;
  --pip-border-radius-toggle: 4px;
  --pip-box-shadow-default: 0 0 4px rgba(255, 255, 255, 0.9);
  --pip-box-shadow-hover: 0 0 10px rgba(255, 255, 255, 0.7);
  --pip-expanded-height: 40px;
  --pip-expanded-min-width: 200px;
  --pip-expanded-max-width: max-content;
}

/* Adjust sizing of the regular toggle wrapper to correctly show the
 * focus outline when navigating via keyboard. */
.pip-wrapper[has-used],
.pip-wrapper[small-video] {
  height: var(--pip-icon-width-with-margins);
  width: var(--pip-icon-width-with-margins);
  border-radius: var(--pip-border-radius-toggle);
  margin-right: calc(var(--pip-icon-width-with-margins) * -1);
}

.pip-wrapper[policy="hidden"] {
  display: none;
}

.pip-wrapper[policy="top"] {
  top: 0%;
  translate: var(--pip-toggle-translate-x);
}

.pip-wrapper[policy="one-quarter"] {
  top: 25%;
}

.pip-wrapper[policy="middle"] {
  top: 50%;
}

.pip-wrapper[policy="three-quarters"] {
  top: 75%;
}

.pip-wrapper[policy="bottom"] {
  top: 100%;
  translate: var(--pip-toggle-translate-x) -100%;
}

.pip-wrapper[medium-video] > .pip-expanded > .pip-icon-label > .pip-label {
  font-size: 13px;
}

.pip-wrapper[medium-video] > .pip-expanded {
  font-size: 11px;
}

.pip-wrapper[position="right"] {
  /* move from the right by total width of pip toggle so that it is at least visible in the video element */
  right: calc(var(--pip-icon-width-with-margins) + var(--pip-toggle-distanceFromVideoEdge));
}

/* Re-position the first-time toggle such that it will always be the same distance away from the right edge
 * of the video, even if the label and/or message string(s) are long. */
.pip-wrapper[position="right"] > .pip-expanded {
  translate: calc(-100% + var(--pip-icon-width-with-margins));
  transform-origin: right;
}

.pip-wrapper[position="left"] {
  left: var(--pip-toggle-distanceFromVideoEdge);
}

.pip-expanded,
.pip-small,
.pip-icon,
.pip-explainer {
  position: absolute;
  left: 0;
  top: 0;
}

.pip-wrapper > .pip-expanded {
  display: flex;
  opacity: 0;
  align-items: center;
  scale: 0.33 1;
  font-size: 14px;
}

.pip-wrapper:not([small-video], [has-used]) > .pip-small {
  opacity: 0;
  transition: opacity 200ms;
}

.pip-wrapper:not([small-video], [has-used]) > .pip-expanded {
  opacity: 1;
  scale: 1;
  pointer-events: none;
}

.pip-wrapper:not([small-video], [has-used]).hovering > .pip-expanded {
  pointer-events: auto;
}

.pip-icon {
  top: 8px;
  left: 8px;
  pointer-events: none;
  background-image: url("chrome://global/skin/media/picture-in-picture-open.svg");
  background-position: center, center;
  background-repeat: no-repeat;
  background-size: var(--pip-icon-size) var(--pip-icon-size);
  -moz-context-properties: fill;
  fill: currentColor;
  height: var(--pip-icon-size);
  width: var(--pip-icon-size);
}

.videocontrols[localedir="rtl"] .pip-icon {
  transform: scaleX(-1);
}

.pip-wrapper[position="left"] > .pip-expanded > .pip-icon-label > .pip-label {
  margin-left: var(--pip-icon-width-with-margins);
  margin-right: var(--pip-toggle-margin);
}

.pip-small {
  width: 40px;
  height: 40px;
}

.pip-wrapper[position="left"] > .pip-expanded > .pip-icon-label > .pip-icon {
  display: none;
}

.pip-wrapper:is([small-video], [has-used]) > .pip-expanded,
.pip-wrapper[position="right"]:not([small-video], [has-used]) > .pip-icon {
  display: none;
}

.pip-wrapper[position="right"] > .pip-expanded > .pip-icon-label > .pip-icon {
  position: relative;
  top: 0;
  left: 0;
  display: inline-block;
}

.pip-wrapper[position="right"] > .pip-expanded > .pip-icon-label {
  display: flex;
  flex-direction: row;
  align-content: center;
}

.pip-wrapper[position="right"] > .pip-expanded > .pip-icon-label > .pip-icon,
.pip-wrapper[position="right"] > .pip-expanded > .pip-icon-label > .pip-label {
  margin-block: auto;
}

.pip-wrapper[position="right"] > .pip-expanded > .pip-icon-label > .pip-icon {
  margin-inline: var(--pip-toggle-margin);
}

.pip-wrapper[position="right"] > .pip-expanded > .pip-icon-label > .pip-label {
  margin-right: var(--pip-toggle-margin);
}

@media (prefers-reduced-motion) {
  .pip-wrapper,
  .pip-expanded,
  .pip-small,
  .pip-explainer {
    /* Transition changes in other rules may override this one if reduced motion is preferred.
     * Make sure this one always takes priority. */
    transition: none !important;
  }
}


/* NO EXPERIMENT - PIP STYLING */

.controlsOverlay:not(.experiment).hovering > .pip-wrapper:not(:focus-visible) {
  opacity: 0.8;
}

.controlsOverlay:not(.experiment).hovering > .pip-wrapper.hovering {
  opacity: 1;
}

/* If the PiP toggle is keyboard focused, always show it at 100% opacity */
.pip-wrapper:not([policy="hidden"], .experiment):focus-visible {
  opacity: 1;
}

/* If showing the expanded PiP toggle, don't outline the
 * parent wrapper element - the expanded toggle handles its
 * own outline. This also affects the regular toggle for small-videos. */
.pip-wrapper:not([policy="hidden"], [has-used], .experiment):focus-visible {
  outline: none;
}

/* Override outline set by ua.css for the regular toggle. */
.pip-wrapper[has-used]:not([policy="hidden"], .experiment):focus-visible {
  outline: var(--control-focus-outline);
}

.pip-wrapper:not(.experiment) > .pip-small {
  background-color: rgba(12, 12, 13, 0.65);
  box-shadow: 0 4px 4px rgba(12, 12, 13, 0.25);
  border-radius: var(--pip-border-radius-toggle);
}

.pip-wrapper:not(.experiment) > .pip-expanded,
.pip-wrapper:not(.experiment) > .pip-small {
  border: 1px solid rgba(255, 255, 255, 0.1);
  box-sizing: border-box;
}

/* If first-time toggle is visible and then switched to the regular toggle for smaller videos,
 * maintain the border shown on the first-time toggle. */
.pip-wrapper:not([has-used], .experiment) > .pip-small {
  border: var(--pip-highlight-width) var(--pip-highlight-style);
}

.pip-wrapper:not(.experiment) > .pip-expanded {
  border: var(--pip-highlight-width) var(--pip-highlight-style);
  transition: opacity 250ms, scale 200ms;
  height: var(--pip-expanded-height);
  background-color: rgba(12, 12, 13, 0.9);
  box-shadow: 0 4px 4px rgba(12, 12, 13, 0.25);
  width: var(--pip-expanded-max-width);
  min-width: var(--pip-expanded-min-width);
  border-radius: 8px;
}

.pip-wrapper:not(.experiment).hovering > .pip-expanded {
  box-shadow: none;
  border: var(--pip-highlight-width) var(--pip-highlight-style);
  /* Remove bottom border but keep text centred with padding. */
  border-bottom: none;
  padding-bottom: var(--pip-highlight-width);
}

.pip-wrapper:not([small-video], [has-used], .experiment).hovering > .pip-expanded {
  border-bottom-right-radius: 0;
  border-bottom-left-radius: 0;
}

/* Toggle message only appears for CONTROL variant. */
.pip-wrapper:not(.experiment) > .pip-expanded > .pip-explainer {
  padding: 6px 16px 8px 8px;
  translate: 0;
  transition: opacity 250ms, translate 190ms;
  transition-timing-function: cubic-bezier(.07, .95, 0, 1);
  background: rgba(12, 12, 13, 0.65);
  border-bottom-right-radius: 8px;
  border-bottom-left-radius: 8px;
  border: var(--pip-highlight-width) var(--pip-highlight-style);
  border-top: 0;
  box-shadow: 0 4px 4px rgba(12, 12, 13, 0.25);
  opacity: 0;
  margin-inline: calc(-1 * var(--pip-highlight-width));
  width: calc(100% - 24px);
  word-break: break-word;
  pointer-events: none;
  user-select: none;
}

.videocontrols[localedir="rtl"] .pip-wrapper:not(.experiment) > .pip-explainer {
  text-align: right;
  direction: rtl;
}

.pip-wrapper:not(.experiment).hovering > .pip-expanded > .pip-explainer {
  pointer-events: auto;
  opacity: 1;
  translate: 0 calc(40px - var(--pip-highlight-width));
}


/* EXPERIMENT ONLY - PIP STYLING */

/* Since we change the outline for the first-time PiP toggle VARIANT,
 * override the focus outline in videocontrols.css as well so that
 * there is design consistency. */
.controlsContainer.experiment {
  --control-focus-outline: 2px solid #0060DF;
}

.pip-wrapper.experiment > .pip-expanded > .pip-icon-label > .pip-label {
  font-size: min(16px, 1.4em);
}

/* Only the background will be set at 70% opacity. The icons and labels will remain at 100%. */
.controlsOverlay.experiment.hovering > .pip-wrapper {
  opacity: 1;
}

/* If the PiP toggle is keyboard focused, always show it and override outline set by ua.css.
 * Opacity only affects the toggle icon and label, not the background, which is handled separately. */
.pip-wrapper.experiment:not([policy="hidden"]):focus-visible {
  opacity: 1;
  /* Wrapper size won't always match pip-small or pip-expanded, so don't apply outline on wrapper. */
  outline: none;
}

/* For the regular PiP toggle, take into consideration small videos and has-used=true. */
.pip-wrapper.experiment:is([has-used], [small-video]):not([policy="hidden"]):focus-visible > .pip-small,
.pip-wrapper.experiment:not([policy="hidden"], [has-used]):focus-visible > .pip-expanded {
  outline: var(--control-focus-outline);
  outline-offset: var(--pip-toggle-focus-outline-offset);
}

.pip-wrapper.experiment > .pip-expanded > .pip-explainer {
  display: none;
}

.pip-wrapper.experiment > .pip-small {
  border-radius: var(--pip-border-radius-toggle);
  transition: background-color 200ms;
}

.pip-wrapper.experiment > .pip-expanded {
  transition: opacity 250ms, scale 200ms, translate 190ms, background-color 200ms;
  height: var(--pip-expanded-height);
  width: var(--pip-expanded-max-width);
  min-width: var(--pip-expanded-min-width);
  border-radius: var(--pip-border-radius-toggle);
}

.pip-wrapper.experiment > .pip-small,
.pip-wrapper.experiment > .pip-expanded {
  background-color: rgba(0, 0, 0, 0.7);
  box-sizing: border-box;
}

.pip-wrapper.experiment.hovering > .pip-small,
.pip-wrapper.experiment.hovering > .pip-expanded {
  background-color: rgba(0, 0, 0, 1);
}

.pip-wrapper.experiment:not([policy="hidden"], :focus-visible) > .pip-small,
.pip-wrapper.experiment:not([policy="hidden"], :focus-visible) > .pip-expanded {
  box-shadow: var(--pip-box-shadow-default);
}

.pip-wrapper.experiment:not([policy="hidden"], :focus-visible).hovering > .pip-small,
.pip-wrapper.experiment:not([policy="hidden"], :focus-visible).hovering > .pip-expanded {
  box-shadow: var(--pip-box-shadow-hover);
}

/* Remove white box shadow if there is keyboard focus on the toggle and
 * replace it with blue box shadow instead. */
.pip-wrapper.experiment:not([policy="hidden"]):focus-visible > .pip-small,
.pip-wrapper.experiment:not([policy="hidden"]):focus-visible > .pip-expanded {
  box-shadow: 0 0 10px rgba(0, 96, 223, 0.9);
}
PK
!<	����6chrome/toolkit/skin/classic/global/media/play-fill.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m2.992 13.498 0-10.996a1.5 1.5 0 0 1 2.245-1.303l9.621 5.498a1.5 1.5 0 0 1 0 2.605L5.237 14.8a1.5 1.5 0 0 1-2.245-1.302z"/>
</svg>
PK
!<��QQ4chrome/toolkit/skin/classic/global/media/stalled.png�PNG


IHDR$$���acTL��SfcTL$$x��"b��IDATX�͗KHTQ�g�J���� ڢ=ha{l�V��I(R���BQ"�QK������b@�����V�5��DGGp�0�L��AQg�>9n����8>��\��߹�^���`0xmmm��b[�SG(J���Я��C�`�}�;�x�pp,�@p�K$ ��>��h<d��.���&��|ggg��\Z�g8����O����[ZZ����@VL0��,�0�T{{{
�x@�P�5K� }qqѺ���~��9��3�T)�+8��[6�����l�=�g�c���y���)	�^BE�(S���������J�b�_Gy�����q�ϗ����pˁ(�TѶ@�L8 �S����>�\�+++�(�r_XX8��+PE4�cf�@(c5Tj0R�͢��O�J�Q1S������p���eڄ�]�u�b�b�!�蔛�b��n���~��	���Dg�+��]�Gt��c�����ffb)S�?�L.C��GD�9��dÍ��c��I�_ �����^����4�܀s��q-n�о�Z���j� �gff:�0�3�k�K7�Z�������2�E1�ڥW0&6��2}E�t��փ0�D�|��ښ����ʄ#�P
Ǝ�w��p%K�P'�O�c<�n����Y����c�c�7ױ����{����Ǎe�N�z%��������a��B��	�����������tF���]U�V��%����ؖ��̘�3��{����[�dWWWYww��m�I�03�ɭab����3���?��H#�jdǤ@3����P����1�>1y�h¹N.����H�q51�"3™:��է4]���p�A	$��%'���q:��Q;ˁIKD��b_I���4G�L/�ы���؇����D�,�P=�vt�D�O�	����_�
&
�fcTL#x�a{�fdATX�͕KhSQ��DQ�d#��.D\�VRpi�
ҥ-t��@q���F(]��`+��pa��!��$JC^n�&�͋��s���vѓ<�s��;_��L�I�v�}��j}�h�F9�~�@�f��ƳC	�Θ�z�^ �#��ǘ���z�i��z��B��5�vC���t�D�\�����}�Ch��Y���ǘ�^�Ѹ��V�堼P�m��)@��J�"�"cb��j�����R��!i�a�X�[�j�E�>5^,'��~i�+f+�Z�Ê�\�hkc�^�T*?��s�¾�Y*�N#9��]h~�Ԗ��DU����`�q�zR��g��h{�­0�zP�)���3��h� #�\.wz���W�CV��vh�`�[6��.
��='�BG��2��|hK����ORP��E$�'D݂?�@�r��jl+u�ˏ~N���X�����&O�b������3�P@��&�&������S���}1�	V����D"x�0L��\.�9J?�`0h�Y�
�tw�v�/�|��^��	E�>5Ρ�A�n�o��tN�١gB�S�ġ��:��U����lZ���Dߌ��̡@o�6	D�ׇ���yh�J�<^��؉D�����Cj���Ǯ�
߬����k�*{���̘���;=W^�M���_���-�d	�fcTL#
x�� R:�fdATX�͔KhQ�G�JE��.�� �Nwj��n-fW�r'D�ZmA]�DJ�"䍛57
yA�F�� q�hC^���;0L']L��Ͻ9g��9��IR�f�y��hlP<K��`08��t�܆v��c�0���[��ՄdƘ�����c���z�}�v����0�k�����R�t�Z�ޖey���pꕰBn3�\W��'j��*����)�7��ϊiT�G�!�1�t�S(S�T�+��;������P�1%_.�O��0@?�`JT>���P�ų:�9T�hKL�

9�J�3*���s�͆Kn��	ߢ-�-
� `VN[�9�g�bqB�B�G�9*�z�_��,u*�N��d2+ؗ��l��l:�����\���8[i}���h]�
��ap>�?
��4�uV���Gf�Ȇ7��m�/H(O$��MC�*�c���u$y�݊��5h
���=���=��d�!h<?�J	�F��_ܳU��i��À���}���n|C�p8�|�y��<c*����}-X�se��r��z�w=�c����1�T���S
����`Z��k�جr��=e���/�ڀ?[�E0cV�rP���V�[�G:`�,��O���m�[*�K����P��(�3F?(S.�gnifcTL x����a�fdATX�͗]H�Q�gڇ��E��yUuW^�vٕ�F]� veR7!E~0��4o����B�����(Y��WB6l:��Sq:���y���;k~4<�w�w����s�:]�mnn����+�Ϻ���@,���'�.���ܖ�����^�����!���H3�V�W�1�m��2�����~?m�[YY�WY333�V�p�q�k������].�%�����ձ��԰॥�+����6l Uy��]�|�sʺ���C
�?����[�۝�UH���hp��D����	�WD�yw�c�ST��"8�q�@�����TE���Q��(>sL�� ���p��LA����1?�?-9�G:%12 ;�A���#�_��YN�עE^h�2�5�"z���sw"=5\Q:�}���Xհ���A�1v�w��&��:)`
T����4T�)�7z)�O�@�n;4�E���fs�k2��l�###郃��Г���2|�Y���PT���zt��fB@ۇ��^�o�3T@x��b��M�9�GhVɂ�X��[ZZ��?0�6@
@̈́Ip3]��h�Ղ��Q��%�n�>.nllܳQ��n����N��lk�*Z��[��C<߁��c
����������)��fGGGb�b�ñǬ�j�ǭ�
Q��S�H/V��&�c�K<��%G}��L9r��:I
������u���g��e��S��m�ED�ia��Zq�ס�,G~N.RP�"T�655�E�*���$�XXg*0w!n0��%��e��W#�<,�'!Y���g�b�Ɋ+�`�������\~Bl�T�����@���0q,WYǪ��9�WGj�3a���� �L���GG��cE�	bq1ǴZ3b�ߘ�X ʻfcTL x�f:��fdATX�͖KHTQ���R2���h���pQk[m� 
Z�3jj"e��EX��c|��7�FPe�X6��$�����OΉ;w���^��g�9�����Ϲ��y-..^�����������,������f�������_���F(��DZ}��0�+��؞^���zacc���6�\�����P��AXh���[8G�91>>~krr��iN�3jG���Z��w����������`]hG)"/��j8�8&�x���P�P����
͈	��Њ'�dD������s-�Ͷ�c�!8f�
uKa1��h5AJ��+++�r.@IP�v�m������H@Z����1^p��H�5��/����r��XJ���E@>B=�^ր:�i̱�r}�����L fa��E��:�躄�k"�
���<�w��.,�zD
�,B������r@&��Ɉ�`8���0��t@��5`�.�냈6J�"�z(��0����y�!���~544dA�uuu��"��l�졡����~s���*�r���c
�|�C5�Af,�>�F
����L8��cww�1�J
�M�7���?���=(��j���0%���~P�h��V;�CFy!��YYY�|�477뛚���U�X,�Pl�"���BWwc
�ikk��@%������p������m�p	��f�@��\�5T���r(�WG�m&��@�IΈP�6X),�Ugg�%��%ED����Զ� ��ia�)F�j�p�:�$-�HA.�Pέ����A�	e�O����V���---�^p��t/y&%�ѧ��s@d]���+��ި�L�c�G7�qbt�X�������R=d��Ք���#q�b�")�*��Ɵ�B
8���s+**�Y\ކ�5#�H1�����fcTL	 x��|��fdAT
X�͖�K�Q�'VH?�
� ҫ(�*����@�` F�y�ͤ�Lo��0�&s:��� x���!��A��c�B��ƞG�gg�Ik���9����y�;m��H$R����G�o����n~4m��bA�s��9w�p�j
��Y�h�c�;T8@=�|�{�����omm�A��D+T�Ph!���Y�~ר��`����b���½����p8\����yk{{;�6!���4�]�$���Ժ����v����ۋ���c���~T	�`�Wy��>K�y��s�s*B>��wJ��R"�P�[,��7�]Tk���+|`�b�cj~~~�0��u��HNL�qxs�\_?88����RJ�����P(��Q�8�w����p�����A���o��v^���,�́*�x����a��1������$��$����)H��̍C|g��1���A%��6# W�C��+�w#��w�D*̶(����b 7�lG�K����p#T��-9,���ĥ��)���d���tT�w�w@��chh��^���
pC}ҿC�����̷�����
��G�����{�ާh�<Ϲ������0N��<Wa�i�����?���B���.ȖB�E��.9�d��{�~t�_X��6h�6i���Ł@�8���� >7'�K���=�+6�b���L���ԓS�f�>�+�Z�Z�i��̓V��I�Yt$l�fXY�(�Z��{�`9
�Qmz~�
.�r�	300P�B=5p�HX��>NV����,��H������`��t�3��K�jD�H��:� +�0qU�tJ����B�&B�3^�]�)��BF�d��
�q�k���ttX�9�h�tڠ-ڴ��o��j�!fcTL#x�T�X
�fdATX�͖KhQ�'�TP��B�+-n�F��}Z]���q�b���.Z�V\7)�Z�n���J��L��hȃdJ���)w�2�MB�8��νÜ/��PE9��f�R�����t]H��s���|�{}�A�L&�B?�8p��0H`��ȡ�ƽ��U���r��Z(
����T"L����k��3��Ph0�ء�X,���:��*�U*�<�+�J��.%�
0�p�{�UU�dڦp�!���4i@�6���1~1L�`�¸/zeW̹6f$�y����?�mB�4�"��z,�ˍH�z)��,�(ιf�G���p8��2�r��Y8�O�X�ݑ�w:��|�&Ź��H$F��u3@X��v0�3�R�i�v�����3�`�yD2�!��/H&�ClV�K�D´3���(ɦHh�/lj4�/��
{D���Y��v�!��L�%c��yʺzF�#h�K��n1q�C�]��>~�mG�k4�����=���Zm��\.�
��;��x�BO0�w��|I7]�:������_`\���ל���)��j�c.�0�����Z���W
�����{ë0~Is�"S����d���Ѷ�G�2���kU�fcTL
#x��P*w�fdATX�͖=hSQ��
U
RP(f
����Z[pԵP�$CGq��
f��I,����i��A
!� ���@�I|�r\Bm�\x��sz��~�;!�r�+�L��`V9��l6_H$z��`�c�;U�L&�0�J���0vq�TeP��t:�M#r�87�d�|^W.���
��X,vI�u��sHl���ʈ1#���px�����C��<����J�y�)AR�J�rCS��b�v{pqN���5����6Al���A+��G%�b1��7F�� ��ǿ�^���9YA$�C���I�{��V�[�jhE�6���k�x|@%�1��|$�B�
$����mnY?���
�_���]��?����v��v���Q2�b��Y`�!���\.wu���ɚ��I?��|�-6���t:�Q{DĺIaly�S	�Ѹ�d��Cl�h4z��xj ``�pkX����:���z����L�l��Y�#�֕�~oy<�I4��M=ԛ(gy�4��v���Z�`�s�+��n��Z�Ovvv�|j�X����-��[��;�-�l�Z}0j��vKU՗�x��3��#��l�G�&���U�'d'F�����"x-��*x�`;�"yK+"�\�V�͌J��pL!�+TaU+#X/��ڦ�j�:����Y��C��fcTL#x��="v�fdATX�͕KhQ�G��)R��eEܸ�T��؝�MW]A�Z�n���J�*�75>6
yAP�`^��y
h2yl��ޑK,&�i����a�7�7U�-�h4:��(���Vk�_�^���7s��lGab��	z9�WBN�g�Q�H$2��.	Đ�g-���i�6�8�N�/�j���Y�^߃�,p4�0bd|�x>��z���/"n�@��|�B�q�P(Jc��?>4ˆ�i�`0x���|�U�5
�cij-u$�ͮ��'C؏B�����(�B�W�"Wyft���Q�9�$=#P�q4�Ba��=�L�|��G�/)����n�{cYD���%v�#k֙��`��8�<�~o"�8J1��3��OQ���C�`F�M:��o1���z���Wa�!���/H&�}4+=�Ѱ#�F����I�dEth�B>
�z�� �J
�!=�Ѱ#���k�K�x�#[D4�"��Awl�w���0M�֗(��x����R�|�����6��r8����lg6��?�ryN�
���s��R�B�A�v�q܀�C��]�_QP�<�@�7��!r����QU���-C��m��!��%�i�I!����b@�,.��vL�����MW�7�jՃ9�kfcTL  x�L7f��fdATX��W]H�Q��K�YT�P�W�QAyU�ˮ�L��K�F�ȟ4����ƫ��n-V�3��K�Ħ��:�s=O�/߾Mm�x8���y��}�7u����б���{�uk5L&�恁�l[�V�q��xX��E���B@1`��p�8b��������,��uHG~�+{�@ ����!mW��xF9�t:�vvv�loo����q���eoXr�ۭ{=�{FGG?`~�y��@��[���s �q~[[[mkk�S�n�W'BFNrD�s��OP�D688��H
�r���k�S��U��Vj0Wj:A�A�QE�A�0�b>��Y
H��F�*�)��2d@�D@�	�#HsN���n�$���&�=�೼��1��Zք��t�s�N�6�G�r��pD���֨�&X�Z5͂c���-�{0_^i7!
�D��'�:<����ݦ��d�9mdz
�"��r���N��G�	����X�e��3�H���撊uk=X�333Y^��qrr�5P=55��?d�f�9����t	�ܘ]��'&&�V��������
�L��xN��;�F�o
�F1/CV�?~�-KD�������Z&A����&���Ѹ�w���J6���M��ặᆸ��\P���R1��o����@���߀���D��o�gaa�|��-��~�#^P���e��i�ZcB���ۀ�^�J������ق�ъ����/B썠m�Vb;#����������a���q�ż`u3�]�x$�
�k9�J����0��


�y��r�&�[��yMp�V���_Z�I�R�r�d���ኸ'�\�3�sy��t���_���~;�C�7Cبem�r���l!�Bd!p4l�#������ �V�7��h(S�p�
�N�0�]��7�
f]�\�fcTL  x�����fdATX��WKHTQ-K��$L�ZeڦE,���AC4��V*Ƙ�R�\�N��X��r���1n�lPGM|S:�N�W����U��ׁ�{<��}����a�h}}}'{{{	�5;5�^op�M��y�9׸��@�B#����!8���]���D.a�^�CCCq###�����L���e�����+�T��t���*����t{{� ���-zC򱱱4��i>
|��sT:�\��Tp-W:g�X"�� }���L�BR֍�� |��U�O���:)��������\���o@�s�>���"T���"j9���𴌏��HgQ�I ,Fįι&�755E��DO�ʖ��T�9-W
���p脲m6[,�l?���2�P�T\Us ]M�H�qbb"�_���>$����&X�>/9�Ps.l��LK��݄�/�h+1ݨr
�՗X� -b�i;#��[ˏ3d���&X���<88x�9G~��W�Vk(k����b�NV���쵹���@3P155uj����؆�-��+b���!�!�=x��jr�ɔ��(� �3 �
|����K�0��![�����l�x�"B333SǨ��A�
�J�h�>0����]O��᮷ី��kkk�@2DE�����PD��7����B��ʊ~iii��v����{:�.�?n� ��>�c�1�lnn>��~�Z^^�ċޥ����	/D�Wϳ�@Z,*�-X�V�����0�e����|Q7���(lg�2�?`+��BI�U@'~���ż`e3�]~��6��#6��+pL...�Kgkkky���\��[��yUp���uQ~�G.�� �F�R�r�d����
�'>��L�\A^�yz���j�@���2�	��Ӊy��^��Q�Z�t���,!�Ld��a�	��:�E�)	S�7���(Q�p�9�N$3�]�OB�J|V��+fcTL  x�Lk�UfdATX��W�KSa�,c��P��ʲ�.���?�\ xU0�f�J�/�.�6
�9����e:/L��S%��Ŝ�{y|��ڜ�:��}���=���3�)�kdd�����]�sS�����"�6�������s ��K�=@��G�js.D� r)�\�˚ ����^����933s9�S�7#߭b�^��{�G����x<g��|�󽽽��$�%�{0�A����]sssG����?ȚM����^�m``�:|���*r��C ��Zd �D�O%
=��U�ϙy=H;�G
�(��	�"�J.�b�NMM�*.T���3B���~	��c����0pCl�a8
��CU���h||���\�I�w�C�����s��P�����N�V7O�Xp�ƨ, ꒜$�+���&�]���DЍN�b+6��vۙs��ȁ���ʠc�A~�N0�	��y:���|)s���p ۾�t�&X��*6���#�H�E"�n����1��\(�V�i#b�B�x�0�9�"��69����6����Y�}��E�D���X����A6J[�8����%L��	� �n	p��G@ڪ6��(x

^�?��E#��Z^^>�N�k�����b^�d��N��"�T*���r'/#|a?H�!b
�	 1?�4�s�8Q�YLE�	,���_H}�D�!�t�&������D�6��w�vo___�P�ȵ�^JAT�0�~�R���Z�r��������k�����U�HRR���V���Z���N0�	��[t�mb$��n��f���)���0fcTL#x��x���fdATX�͕KhA�c�DQ(��^<衈7�1<ړ�<X�kN^��V<O�V�ǩ�aH-�4
yCb�<�i�$!ɮ�'��n��p�cfg&������f3�~�E�����fESe��>�oZZ��L��^���^F	�8&��g�%
X�f��8�K&��x<~��D�h�0G+�p17�==��T�F,{
�G�x~��5�O	[����)Ì	����
ϟ�ϢX�3��c��%��9���bٮ����h�M"�8��E��p;f}Jq�9C��1@��[�W���v���A�g)���ţ�=2�_�B�_1�(�}b���*�鷏����2c�ZLK�G�%I:n�7ú�ԏP6�u�r�E�+蝅B�%��(������7�w���M��
m�}U��*�l_Ԃ����%��\�?�C�e��U�Kd��X�R�(�'-��j���ju}���6M�>?R��W.�%(�q�J2m6�ۀ����R��<���yaф��6�����?0�K���矽�n��t:�PCh�s��F�1H6�t���բ�Z�k�+�>�fg�П���[���v�+�A�Z_{�	h�<�9�c�
�������m��|�ԕ�e���kf�A���d�2��+��6��y:�fcTL#x���	��fdATX�͔KhQ�S�D�Pɢ��pх�;�!�vi�W��ҍ��*)��P�*J�V!���b��&H*���"oN^$�?܁!L��i���99w�3�܉����s�`N��`�Z�At����z�/	c渦��[�m@��u"�
�iC���xl�TF�V���Fg��)��G|'�L�R,O��Uт��[U�Bx��X,�K�O�Eŋ|m1s���
������e8N�:��9���{+`��5�jM�R)%x�[�R��E�Z��}�G��a̜j�S�~�x;G����D�fT$Q�@l/�Ӛ�Q:�>�A���RV�#i�Uq��ž�Yi8^����0Lc(��-;������y3�
�M����+��r�c�_�3��5�K��M>C�>�d�ٯx�k�&1�vPJ{��T*W�/��Z1��Ml�M�w���Ypc0�;a�պ�l6� Z��~6��e]o���B��� ��o��-kCb�$_�V�J�<�}}^��1�S�-�Ȱ�K�t�0>L�4F̶D9P��n�{������a̜�#?@F)f�+y��Ng�r���,���*G�&f�<��n��Au[4E�0ǵ�-�/��2���G��A�3�5ͻ7{ ��\�,F�VH
������Y���k��[�km��fcTL#x�ϝZ�fdATX�͔KhSA��V��"E��B���EAĥ;5vi���M�ʝP!>[�.D���䉛6�h�4�A�B�$q�64�M��\�\�;	8�s�g�ߜ����t��n�O@7�&t�&�i�����CJ̧�&w�\g���l��Qb>�5)8 �V�9C������Eī���k�Fa�Μ�+8
����Q�/#�E"��*�-�gn�k�Co�%E������v�����V�q@��5�9s\����%��X{��F�����霅�
�2��kz�93}
��z?o�v�G(�V]�G�;������F�����
������>�
���V�b4S��e�y8hq�/��V���b1}<_H$/o��ɾT�J���d����;qД<�N��#`�ЊJ��g8|h�p�E�+�b�C&�9��j�@:Ч8��(
'���b�pߠ�\n�T*=-��~��Z�R1H�*���h���;�O��7�����Z�54������yZ�h��z�>Iq��
�w��"��MV��j��F��<��|�u�~�����4��lN�{UW(Ι�Z�.���Fn���juTU�+�-���v�~���|�J��:���P�W��:���&�lqu��D��ԓ�����פ�^�w��6i��&s\�?���/
`�jfcTL x� ���fdATX��V[H�a�M;H�it�2/�,ꮼ*�+!��*A���nB�<�2	,o����b���Q� ��
���T��<����Ԛ��o����y��SI
�:;;���\������{��t�׍FcP����"q��f�kkk��|��l>��钤p�n���p�����!%�(�j4p.W����T���^-���=�d��AV�蟣&�%p3�p��`8����È���Iʸ&.�y��U�D�N�vF�"'���>���r*��(䜲!�Th9�t	`�AԨ�44��R�t�j�&�;B����t'D�r����!�0%�#^�`knn�WJ�J���
:400���9o`�$gZ`�%i�
d��SDЍZ�R��Yb!&��s<e����x#�g@�E'���y��f�1盱=\:X,�uK��H:22�3<<\>88�(����ߐܵ�� .:����+�!BdF�upT~x
�$����	0
�� 0.����_̵�(��*9j�
Ԋ��H	��� } >L��D��~
���(T;299��c�ii�4��u7OLLt��5 ��-����c�cuu�����W����~�_��T����� m����;�;����Y *
h4�y���A@r|�[����I�(���t�\�
k�Zn3Z�:�t�&T�...���?(HEu�����k ����aCXٻ���5;;�177���s��4���
�@�V
���5�>�D�#5�J!��3�^��y��̌��s\S�4�4� ���f��l��LE�\M���?@�M�&�!dv�gyy�j��Lk��9::�[|�`���8�5��BD<~�J��^2���B���G���u�_I[��� ��9�m�����J�ﴝ�s�����\��>V'fcTL x��$��fdAT X��VKHTQ�f���I��-|���.j-b���!DA�c|���\����-l@e�L���/RTJ��ٹp�sG��с�3sι�����?�(^FKKKXkkkp����byyy��o6�3�B O�e�g;::��L�x�'744$����v�	���y@1/-�}�$ (����O��ե���>{E����� /JT��5���n?�������r�bD�O���a=
k�Z�(B׉���$���	��
���CճUUU1xQ�+��5uBB(��s����[��P���!��#t�Pݑ�Wt�K��=��s������� e����"�<
��.8��l;�,���K�ep����%Qx��QWһ��tb���0����W��f��_�Lk��x;v|�ǜ�*�8��s��~�A����w�f�ec~�9k`` z��DI��$�]������@-𶻻;v��QѢ7�*===@Vr#�T�b
��0r6+�17���\F-���j�F��0�>���8�z�5�/��188x��p����ރf���D�xxll����h���p�o~�Ñ���G����].�O��"u�Z1�	��_�4_��!�v�xX\Ľ���>11q����Q'�!C�0MMM�`{��|X����NQ�z���@'�ք��vv6��gyQ&�䗈|��qyz�
��0������O���3�f������Q̹���"�i@zx@�� j��"�###�����@f��o��l��_@RI2��5�3���tN7w����(B8At�0y;�3 +�333v\�zVG����3�]�$D�l[@t��`M�0�v�q�H�V׸'_Y8�|�B���{�ܗ�� s�ppo߻$�>�v3�9�ɽ����\`��뇸���ۍߚ
5�Z�onfcTL! x�#��g�fdAT"X��V�K�Q���$�a)QvTH7������u'�5S�n��0\?��M��+�ޤ��dH8uP�cKC��ƞ'�Ng�g.������=�4����w|pp���@M0��D���7��#
�5�g����ml5B��y�>���]��
�c����hs���C_�0�169l3r=�'ȢG<�a�00�턐x�'��^%rXVB�n�Y�/1�|�Jj
h3�y�>�S)r��=Y	�۰v��777�p���Z��}m��ƪUR�?�]��z��m��}�x� W�S4=�n��v�\_6����P����&���tn�4Y�dqE/�v}��r��V_��&r�S������&�c��4>>^
4��������GFFN��|C\���$�
����r
!O���M�>��o�������9�c��@����̋��R���{�Z%Pг��	�n	� mU?L�P�+(x��"����P�H$*��4V:�.�f�U�����zx~~�D<���Ά0����y
\��������L&I�;DxWVVNZ: �b�u� `���=揘]�
�f�e���o@���q�U`x��"X�X,v4��K����T���r����n2[]+��`O�o�RAp����Yv�\��l���Z�E,*fOLOO��g���.��ޤR�(�5m�>$�Xv+�o[�@9D��������9�~�W�'	�i�ـ��
��'�	�+-�d��9"�1����:�3 �Y�b�2ϱ��pr�l��z�-H*Q�ؘh�C��B�~`J@��'�u1D���Yv��� ���h���$�j�ܓ��Iڸ��G��d1�S������8��Y�a��TfcTL##
x��꭛|fdAT$X�ՖKhSA�o-�RA�TZ������r��E����7-�u�Fp��ࢠ��h[h�ɣ%�&!�y@�!��<1�q���h�Dc�?s23���9g.W�~=Z��a=�]t8�v�����j�W�Ri�Z�nW*�ċ��p�G&l6�,̷�	�
Ƙ�`��n�r�|槆2���:��1�A��6��s�m,K@�F���>����
��ػ�.�Z�v0���*SW5p�]��z���@`(�F"���5N&����x�Pn�A��;�W|>�+�����ݖ�����'�9��s���
�B���?���-�x�4�*�`�P4��|�"�O��0�0�
�Cod�r�\�I�ӗ��E֒��n3��b��C�y��қJ��Q�����4"#?�0[��3a2%J�ffG�Аy6�HL�b��h4zD1�Դ������0��
�_B{
�{6���0���V�'�?R"�ژټ�g��D1���bq��&�E �!j�M�[f�A��
����=�o
��=�DFt@M5}��`0Ȓͱ��'_W��L?�0\�@|�3������2`���k��:9`:
s�����{��2�9�c!��������~����fcTL%#
x�c ���fdAT&X�͖IhSQ��
u)(��](���\� �F�p�ƅ��C���U�ҺK^���A
!f&HZ7�	d0!��>���I4�<��'�p�w�O���W�כ��Z�7l6�C�ݮ�z��t.�+�V���n��зN����7�(�]�ź�����;�k���뷛�f���v�.c���X,����+�% 6U�7����SS�:�R�Ԁ~i��D76e�3�{�r���h4�Q�"��0u��=30P{��4S���=[/�aW,[�D"Z��w�oc��uh����ዐ.
�mH��'z����J��.�J?!}�V�<���x�4@��֝`0���#��M
S.�	��O�:B.0�u��?���@ ����,�H��f�Y�P����X,�e!�F~mԈ�A�`ءm�׫��|:��B��zD��p���Xv�#c���{3��
� N�1s̒�����Qc���6�0�C:�ĩTj��d2��>%b�{�y�w����v�ѝ����Cl��y��{Љ�(�>���=�>�6�#손Z��C=DSG������E�0"g�_�D���N�p4cud�E�p����0ǽ}��r�;bL�c�N��0�]8�h����ώ�u��w����fcTL'#x��#|��fdAT(X�͖KhQ��+)R�U�e�;���]�J��UA��Buc-�X��U^�X���1�A��yA�"�1�k�I����4��6~�͙I�7�w��`5��>��y��v�@Vh��pR��*�J�t]}���h��և��{���_w�\Ǻ
R�V��G�R���r��g�ZmA�x<GQ�:s󆐻���thW�@���`u�$�����eэyY��n��L��M0`~�LO����kG{����^�w(�"���|~_.�;	�t�E�V�߿��Z�|=����bqFӴ7�B!��C|��d�[~9�b/��
��3|^�,���b�_ѝψ_GΖN���8
��=�%�C��`� �ht?;"@>�B�;�&���D��M`V82�g�4��T*u�2�z��aG�0��]kՙs�F�l��6���c�X�!�KJ��$�z�����V�9��K�s������1��DB��BkBܫ�f�O�1���G䏴�5
�!�0-�dC��)3�A�Yt�=�+Y"7+��iVz��!��O;đ	�#k2�|��a��5�5�h4�d���G�VG:Y���o�a��m}���z'�b����e[a����
��Y瞹�����"��_��kfcTL)x��A�fdAT*X�͖]H�Q�_��S�C�ʼ*��+�J��JȤ�J���n"
��i�}�x5��X�``0u6Rd�������:�s��q�xw��3M�9��w����E�^g2�������T�պC���P�$��kWVV�}>_���-]^^�������F��t?RN�����{.6��g��2��,
8	@�O3�ݹ��t9̌X���ƒ(�kjj:��=nq�ό�׀�l�|A6��q?��:XF:�ñ����c�ht_(�1�:~����q�Bl�P�j�.2+�����b��S�,��Pp�R&g~R��GV�PQ]]�e���r�>&�+~78���2�5V�7�)d_����0[4T13��e[W3p�6@��
��v7������Gbu�M�_\\Lc*[a8)�\Y�Y݈1;;��v��fff,����sXE<\�E���7xzz:�/�Ϲ��	�N�? ��-�H̓����ۻ�א��h�����Accc�z��BI��_	��tak�7�Mm��r�p̪�ڦ>d��f�ŵ��'���MNN�R����;�����l7:::CO�][[[�_,Q� 3�vȊ������v$@�|��s
6R=�"�t���]P7�z922��hgg��\�7@")����,��B���w�`u�`�b����DJ�X�XX�%Å7�����2�%`�	յ}}}��P�P%c��w����ʑ�-�
�j%8{��t�����4CF!3c��6�-5��f>`~��';b��
:@�(�)P�T���+Pױ��\�1�F�=�2����=�&�K��A�OP�W2�1^�ˡ���l.֘V�8�P��\Μ��
}4�!,6j�F��P8_����f�21����o3�H ��^fcTL+x�o���fdAT,X�͗KHTQƯSV��I����Ѷ���Z��"hc�Q�*Ø�JɂV.���̨���(-*p�c�X6�JRT�qt|�}u.��3�c��8�sΝ���=��(��ƓV��BSSS��lNs8�a�z
4�X�����D�������������3�F���9z�)�,�s�������b^�X��nmm-Ю���u\��---��|�A`F,�ڨ��@.�Dp��d���cn�%�9�9�'���W�
7䀰:TD%�O�k��등�|�X(�c���M��_��^n�)P4��
��q'�gt�����9D��ohQg
Pm�¾8�*����Q�ذ�����Y�sn���D�y̱��/X�qQ	�����e�O]����ѠX�抂�3b��֭4�^�����qaa!a�Y\�q8Vo��p 
JT�CrR�\�곺mzz:unn��B�gggo`#:E.e"�I;
��|�1�.��k�XQ-q��Á;hs$ M�gfffX6���ȇ
�'����;���;	��b���+����Q��CFn---16�-�P�}�:��h��}vdd���v�q��o�/mkk+���+�y�𞱱�����GGG
���B�LT��*п�*�*l�r�p�r��~Dߍ���o\.W������})����"<�Q[OMME3bvh�	�Wn�:���u6U��تC>�)m�2����PpX'��ښA˵p��5�"T��������x�1u_*q"�=!�LE�g?3���_�բJ���	2
�8�9�����9��"
68�5��ё.6@��X���S��A��Y���c��:V5��63Z!���D��B�������:R~|�3�B�8'?���ÇY\�1�q8
�"ڬ��4��ш313��y7sn��e*�6`u�Z�]DZ�$�=�,�X1�fcTL-x���4fdAT.X�͗�kQ�'�J�A��R�jvպԊ��������n����n�\���Đ'Y����K�X��$m�E^���n�w�L'��Ƅ��ǽ9�N~�<�Ό p�X,�=�U��5�p8.�������>����D�	����ݞj����+��}H#�kn���2Or:�s���fNI�:���ɦ��ų�zK�R������oՀ�c˽z��a�����ئhc�ϒhL6�c�U�h��D�쉚p��<��?V�k�V�5@OY����7 )�7P��\�*��3#��|�|>_/���9��� y%8Հ��a�8�zM�L�a�
�Q���@�
p����p7��S�9���k�@��69�!6w���jǸ���1t]�}z��!E�R��FLt4����r|�P�k�|^M5@E(�VI>�t�ڎ��d.�r�9�6h�P(h��.��y��6����!k�/C����#Հia�����b���� =T���l��V�=H�`2�*�k�ީ�w��:w�u�i��N�O$	M*��k�������;�X,�qccÍ~*�L��Y��G*�:��F�%a�
Z@$z�2�DN���$�W���F�Ʈ���~��s�c�W����6����4��$���h��r�}
p?ෛzud�W����̶A��yB9���9���>ЇB�w��b�^\GU
��
�<v��Q�X}.�G�p�N6��ޖ(�cT\ޣP+z|�'9�r����z42�!^����6��s��q��p�M<o�!���d��IEND�B`�PK
!<��¥[[<chrome/toolkit/skin/classic/global/media/textrecognition.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.w3.org/1999/xhtml");

.textrecognition {
  position: absolute;
  overflow: clip;

  /* Prevent unwanted style inheritance. */
  font: normal normal normal 100%/normal sans-serif;
  text-align: left;
  white-space: normal;
}

canvas {
  position: absolute;
  user-select: none;
  inset: 0;
}

span {
  position: absolute;
  transform-origin: 0 0;
  color: transparent;
}
PK
!<Q��w�w5chrome/toolkit/skin/classic/global/media/throbber.png�PNG


IHDR$$���acTL��SfcTL$$��,�sIDATX�͗_hSW�o�n+'+�
��Aα��A�/�O���	�(l�p{|hQD�v� �`-�Ç@�/��wrv{��{����4��!�,3�wKB��=�\��֦K�~��_~�s��WQ���H�Z��V�3B�#��k���1!�3�ɶ��tZ��ӯ��k�x�w������ ⷈx�X,�!:�>!D���!�4mWU�c8������6S,��"b�u�?]��#b�j�&_P�I�O����3����(�B.B>�Hf������_�����J�W�t���J�W���z���&�\j(S�L�ZiD\���ݾo�Z=*�x �X���Z������6�Cu@W`T���]\h�J�}��B��r���\.�_��SӴ������F���s�I]ɲ�8[(��D*
uB!W�4M�u}��A2��D��9�A�e	S@ij���Cr"�PCpMU�3ssso6��6".!�cD�u]��ڥܨ�B�g��Mӂp�rB�������]*����w7��:�B���.��(�|Q��7-!D[6��眏s�'9�\.���/w�W�=|��j��D!���qq��9��r�G��D3�̧���4��7��0�(�(J@��8ά�8�ݷ\.7�95�P8~�rV#ϷUU�4M��Dv)�l�g�q�ja�=��,//�j!P'���F9L�f�m��m�I�(�l��kSl�k)�LnO��=�LfK����ѡ(�b�v'cl�R:�L&�D"qǶ�/_��J�ަ��0�~e�i:cl�1ֿQ�����Ì�{�1�3=�H�YXXx�Q��\�c')�ӫ�d2����Z�`0�fY�˲v�����4�������R�sj���F��1h��
�4o�1�F���-��/..�O)�K)���7�ؠ?���x�eY�MӜ0MsLڄeY���2i��(��J�QJ��LM3��QJGl���}b��)�0n��y��d�)߯�bm�����c'c�m�~��;`��˲��d��?�<��i��d�n�لi��Z>`@dy�j`�dہW$�h3�eY�,��v�0��B���#�$�b�݆a�7�|,��l�����r�y�fcTL$$�k_"��fdATX�͗OhG�E�ڂ�V�b	E�H�A���=x�
z�G�`ۋ
z�B]����$�ew�Bf�#�V�4�̓ �p&z؄��cw�7����{��4_��2���~�����դ�$����8�o'I���x�-c��8�{��I��:MS��N�8�5Ƭ\r�8�V�U���h��άR�VM��(M��u��[5Mӑ�pvv�])uH)u|vvv���Ԫ��SJ���:](M��1�������t]w�}�cpp�`��{���J)!�RJ���9??�!��Z�g%�P�֟����!��y�����I��'�d�O�Ԕ�r�`ϥ�?���v�u��i>�*�s��,\�u�N�g
v!t��Lee50�J���RaEs�$Ivi��i�ef��$ٕ�;��N���3��~G=@�_�DJ�8���Ec̊J���R�l1Ƭ(��z��l�u���R���J)���̼�h�L�B'Bݵ@��]�}��E�cޞ���)�|���R�RJ���u����ru.���566�F]A���7(���R����R��8��1���!t��<N!�:}�_�h,+���Qm5�4�r�V�z�q�4���d��m��s��s�+���9��s���=�q>���;8��g/a�)={��������{B�B���Eb7,��>�֦8��,�*Y/^��&��MqG1����uτ:�8�
��7�6�r	p]���yݷn�j�8�}B��E���p�������@���X(�!�-?�eW�e7�-k�M�Z-���U���X[.������i6�ko�eY���cGc����Ҿ���}���:99�c�4�c�aRJoPJ=J��F��>~�a���K)��h���Ҿ����גc��1�� c�Çۋ��m�


�Zg�v[Mv�?–��=YyꟘ�X��o&�����'�ޜ�����z��5J��(`����	!݄�^Bȥ�z	!ݾ��7��AY�>Ϡ<J��sc�c/`�B�b�,Z�v �k��X�T�\���ð�ѣGo�K�c�+�@Y�1+��\������Ho-P�v��\LA�_%�\*���j;�ȶ� �B.ge�B����鿤
�`S�f���x����	��fcTL$$����BufdATX�͗QhG�L�j�jC	Z["l	�"آOڦyl|Q�!��)(��Lb.�}(Vi�H8؝on�bhM{����,j*���B�C�eg@���>M:+�5!w�k�>�o����|3�iM����Je�R�����x[V&�i�`B�\�B�� &2�L��ApN�+.�`ʸBAp�U���Lh� �-��y�q��{�wqee�)�᜔2&�x��T�B�@J�3�L�a�i�4O��̼�(�g�󇌱c��9��16�������[��H��;�3Ms/�Bh��_��;3��=�?e�=
�s^�<�F��A�!���qe!ij zT��ag`�Flc|��Lq�ϫ�<�z�˖�僑,�B�	!����*��u}�H
�UH��y��]��1c,W.��D���m�j��Z�vK)ۢsЉ���.���W��Z��c��[�QJ&��1Ɨ0ƣ�@�q�4?�4������c��9�9�`
���	���B���a\�}�� ��˝���2�r������V�WR��S��%�P�1�gM���h,�\.,��GVWW�F�dr7t꺾������+�����҉b��S�P�6��w�_��u�������DH)c�Ri�R�'�t�T*��ƘRڳI솅�R�)cpM��4M�iG)��QJg)��"���G)eˮ���a��1��1�M���k�b��ʽ��������n�:�
�����J�����m_��7�eY�\�eY{����C�4-���'�ٶ}�r�r=�����u~~�u۶/B�:�3i��mۿB�	!�7
��ö���L9��s��u�u߬�%4�u��m{z�IB�˲E���1]�;t]�-iK���m�!��H��eY�o*�:�1��l0�J�[r�w]��qn:���B�f�١0��#��
�Q�v�u]?��5���>v�Z�ӎ�LB.�r���r�W��Я��h�l�>���������z�$E}����G0�ΡZ�k0��n�t:}��Ɖt:}lہ��.�C��2��q_<ߵ�@�B]�o�u5�\��U�_	fcTL$$�k�8�fdATX�͖]hW�w�m�Vl,ZE�G}(mZ�>i���"�$T�}�V}�b_$��I�j�P?^�bg�;%�v�LTR�j殂PҐܳ60wvٚ��e�n���kҐ?�0w���9wc�y*���|�OA�c��^ZD��~��A� ����wQӢ
��Z�Z�q���Q�k��
���������W��#�����ۘ��)�N(��<~����3"���3U
��&�+��$�avNOO�,�������V.������R�����������d�=�Z_���HD.�T�ٳg���"�RJ����+���dw���J���� ����֏J�RK��0
�]ѻ5�r��!D��
�R�."f�Ro��
�6���O��O|�,
m�l
�P�D��ӧj)�N �j D���È8��D�T,��ŭ��NDQ8P����L�C#J��|>��f�HSSS�D4��j�������x �]D������”ED��p�W"z��$k�RID� �Rj@)�U�r@ˈ�"����&��0&����!�zD܆�o4����,�JѦF_l؅ų��n)�Y��~=��f��_�3c=���p�4�O�ADq)�Q)堔r��_��������,4cl3�@��\�����1)�{��YRJ.����R~OD�
�0�&�1�X7�-�i����T*�N,��vE�@Up)�
��6, ���4͎J�(�8�_.=�%W�%w�c�%��Z�q.���,��0<<�r��z{{W�:�0V7�r:�^��d�r��8?
!:l����k�s��9?�K@;�eY���y��᫶m-��&��m�Ƕ�˶m_u]��F�`$9�QWu�y�4�����R3���P�m�W�?U�mۗ������w�t��`�u[��A�$B�}����#��$��1(�(n��0�T��1�s�$�	�8�N۶�V	!zǹ�nsyo��1��Q����R~n��h��
dY��@�t�-!��q.W]B)�͒v�X�i�Ѵ��v�0�G��i��\g����_��\��u��m_���T:�~޲�`K�Q���}�e���`��?�9�.�J���)��744�Z�є����{3��r�4[`�eY��v�����:�����J�Z Y�A�:�J�Z(�H,����ΨL]�zo"�X��@e1�63ƎD�y������9LcfcTL$$���P�%fdATX�͗_hG��m#�Ċ �B��/��Z_%���(�i�'
)�?�c�'J*"��݇sg��g�Tn��%�	w;gCn���ܹd��Y�'wg�4_�����s���o�4m�b�휚��255u�1�s��^[R��X;c�!�|�s>�{�k�R6,:P�Phe�Q�=��clL�i�Ph]t ��%�y6��@ec�|�l6�����|>���ccco�Ϥ�1ι��T4�9����p~.�[!�h-�J� ���̼_̣G�>��&)��T�A)����׆sc��sZ�2�z#�J����Y977'��R�X,��s�R���p.����|rrr�<��q����g&''��N
!.+��H���kr��V�J&��R{bbb]8�s��9��
��ϧ��7
!�
!�e@�RJ��@�|��*Q�,nPJ�e����RʆB��\(�ˏ���Ϸ	!�B̔���I!DG-��MJ���ɓw�&�v��G/����b�X�=���7���R:��tC�ܢ��&T?(7�TQ������5%���k}�ڔ�k�R�R�u�(תx<�,���K�ҟB��A<{�lC������f7����\�WIJ�ZJ�UJ���w�va�%��y��E&�9�y��L&slttt������`���a|�b;H)c�L�����t�u����\��Ν;.4Bh�@����4-�ݽ{��u=�p]�r
�+�N/_(]�0�GB=��a��.�47j��+W.�
d2�߆���/ P�dFgHE7�ۥ��J��6��-�c_A�c|�L�\�L&ק�������o�u�����e۶W9�s���.B�B�)B�g�s�J)c�O0�'Bqh��,keM	t]�r�r�q�����	!���>۶?�>�ƸK��N8cơ۷o�Q5����v�q�)�h�BN麾�V����F��̋@�X���jB�W���r ۶�:��H&�/5J)eL�&]כ�K
�B�W4��
t���]����@�|��i�	�6c��`��Ul�뺾Z���@�e�T���q�&BH�*Qԡ_�9��zIB��0�N�m{�M��Řa��L�k����U�4M�R��6BH��8}��>�q�ٶ��m۫"�اl�T�-�Z�1>�`j����w8�ӔJ�v�R�/����_�x���lw�K�F����al�=�e���L=���*:
|�j2Ms$�'H��i�Xt�x<��"��T���xo<_��@�B�Bߩ����S��4�RfcTL	$$�k�a��fdAT
X�͖AhW�g���V��A�6�B�x� ����<5Z�K@��(ڵ�It׋�B�k�����sS�/��=�,!��f�
�a�d3�^^/od3��I�|�c��~���Z�*#Ãa�4yp�zZ�.)�9�TEJ9+��5�sZ�uRJ
I)�J��R�gR�gf]UJ
�;����5��\�5���pm��.��<�;%�8V�T>��i�SJ��J��&@�J��Z�T�nD<���(:EQG0���}�x�WB<B<�}����xO�Y)e5	$���a���---�F�D\Fo޼	�ם8C|�Cq�!�B��y�{��O�RsJ��&礔O�0�;��7�-"ʆ�����}HQL��<�w!{����x����A��)eYJYVJ�Y\\|��z}"����@1������2�O��^+�ʶ�Z��W��l�^��8��!"�f@�x�%���a���ݮT*��1�������-��-E� �<�933&!y�w�]�8�(�޸��8�*����8����-�����j5-�`B�{���<��Vn7�֩(�~@Ċ)��������ݝjY�Zmg�/_�������Aāz����{;va�Ck�*�JGK�Ҙ�9磏?���z�mۻ�$��B�W�:�9��T*�����K��-�uq]�˵����@rp	.��q˲RV�\��u�����kq��������浂�m��1v�R���8	!���B���9��s�o�1y�s��ӧO{�h;\$�L4���;��6\�6ܡ��
�M���۶{�7��w�V+��w@�m�=��8�V��0�)���c�ȇ~\cGcc���cÎ�lmK�\.o!�����q�ؤ9x�;���3�F�
!d�\.oi)�8�@�-c(��X'�������ɤ�4�8�@;o4h�X!`fEVJ�uʶ��moO�z)������Сf@��t㡜���g��f��l����t|�4E:.}�q�C-�����1�Λ��(p�2l��`fI�R�%�L�z��KB��yL~&�OMMmk	dY���g�r���fc���a[3's���Q�3�<cl_[0�N��
B�'�if�&]7��i�[,7B����8��v����J]��5`�(
 ��Af�+
�(��l���IS�Kf}<��lZw�8(�}���L��V�_C��~��
3fcTL$$��p�d�fdATX�͖_hG����b�`S���Њ^ԗ�O%O�Z��)R"�C�K�"�p&z'��
V��@9;�ۂm�/{����D#��K R��w����9��-�5�ݙh�,7������M$J������}��Rj�b�^9��+���A�AP49*���Z�|�@��	�`,�{A�5y/�1���q )尔�A�nw�����K��G�ޟ��8,�8.�8X*��DcZ뤔��<@�����������٣�xO>{�lK[0����9緅?���9O�J��h��2��kRJ�I)S�<D܈��0+aVq����ᖝ���(��(9�!�F;WJm��QJ=0�s�<;J�푓��C�O�(�0���[r�s~ظ
@y�y�\.o��*��I)�R��Lf�R���J��	'q:drO6B�H���2���ziW�e��V�����f˲^:�Zm">F�'�k���<@����͇��T�����:D
��o
ð��ͯ���ɷ8�i!��Bf�6e<�*L�Z�4"��'�8mz*?==�vK"�R�Sq���Y*�>��VCk���4"���M �uD�خV�\.o������N�#*�ʻ�ZmG���}�m^5t\k��<��u�/]������u�<�|��:[�׋�z�׹��>���h<Y(>+
�]׽�y�p�P��y޷��|��0/^��Dk�T7D�^���N&\����]׽��X^+
g��SBk��^��6���`��<�n��b��<�%�Xk��B4sss�/?�eW��rkjC���}c���BaY�:��f��]<��v@�eY��~ٶ��^���c���m�(�����~Ji
c��m�mI�X,�&���e���!���v�`?d���y��W,W7�m{�4m^�'cC���vʗ�f;��C�z�ҴmۻZ�Q�q�%BȠ��+>_k��,�Ӳ��ƒ@�4El�3k��P�|@��xS���lc��b�}c����ȶh�����@�mw7��r�c��b��eBHo�\�em4M�&��:�LBzM?Ƶ.2���r��M��D�1��@e C)M3��'�-�d��_��3����c;[��;e��C��8Ϊ�8�����3��"���۶�[v���pi�Kpb�l�|~d�'�<g�7�J�V�!J�)�E�|(�J-�G�ڃR��R��ɭ���������fcTL
$$�k���fdATX�͖OhTG�w�״�J���Aԃ9�D/=�b*��!=j	�J�CqM4��FhN"‚��������K��lE��Y	%;�{YCf$lޒ�e�<���k��?v�}�3��o��D���c����֪��aY��\.�#�x*��0�4��X����
�N!�s!�!D��!��B����8��|�Y&-�H��g���u����l6{�1��y^�˗/�U��Q!�S�tUN!�h����333M�����R�Rgggg�5��f�b���}���cc�c����s~є��9�be���.��)%�R
���RNJ)O�팁y��Pc��r�\.wP�p�0��Č�\.w�⤔�7)eAJ9]I�ԌR꯺��f�'�3a����$c�������Z!D��6�|�s>.��-�h����=J)O)�O�@	��ٚ@��.�Z�ދ/�:��z�|/�|��zSx�X,�R���z�
Ѕ�@��{��P���|>�YMo޼�\J9��z]
$��J�oj�LOO��dz&�y^���)cm������6n�VJ�2�+���=�ZZ�c��1�����������U�7��Q�T��r”�SJ�����jT+255�3����d2u�������b��g�Z�-Vu\k}��ї��\��~7>>��NR*�ڂ ����r�)�Jg�74�8η��[��SJo��{�q�����/�faa�� ����:�r�f���:y���~�uq]���z%]������%������D�\�A,�r1�����㑱��)�7�0����ܠ�^}�����[����� �`�*�R����p%�l��6����h�)�,k;�&���n�H$��ղ��
l�v3!��1�1BH/!�Ļ>�����^�q�	!]�m7�%��d�"����eB� ��x�@p�F�Ѽ���d2[k
ض}c<d>�/	!����FʗH$�����z�!۶��s���[�cykx��:jY�˲vT�Z1�1��@��٣���W2V���2�J ��#��l�\*�:P�7���R�j ۶�k%��BH\����D"Q��n��C����m��.ӏa�+���d2ٲ:I(!�
T��!BHw�f��U�����̈́�n7
�G9\L�)S����t:�%<�Wp��|xm:�ނ:
�m���L#g��
@W�̺oX+FGG� �Afm������6@'�xД�w�b���Pb��c��7��z�Ƕ��j�tfcTL$$��,��fdATX�͗OhTG���?��T
%��E�xPI�E��[�֣9�$�%�ŋ�ecb6��T�"J�(!�f~�}^��ۋ˾7��Xp�f!��y3�.�LN��ly]c��D�~0�����}���[�Z�q�ۂ ��U�y�J㽴ٶ�.��A�/�J�Ri"��A��m{�k���9�d|�<;�&��9�#05�9^�
}��u�c��r]���jsZ�D�T�^*�&�L��k���o���~��9)��0�o���1��0��x�7��X(���p�/A��2�.��U*�V!Ĉ��bJ1%�|��籕�<��yi��n՜1v7���޼X,��#�gS;�f��bqOM�0�J)!�o5�R���XJ��{̨r��o��{#�˵DTj�����s~�s~ό�=��ry�"/��=
d|JJy:�B�\�}�u�ی���\ngt�ֺizzz�����uSt�\.�B����
��\�y�7�Џ��+�￳lc�b�]!�)�z )��0O,drrr��cwM�n�;����)�&=O���M�!�ȳg�ގ�P(la����{�1v�1v%�ϟ�带�NH)�!!
B�|����̴6���r-�Bag.���&�csss��ryo�\޾�X��^���:A��@tض��U�T*���R?(�~]\\�,..������\�A3�Zm����ϔR*����BE)U5�a�u��d2PJ!�����!D�^�O	��F��C!".�RU��O-D�ʨ�u��m�-�o�>TJ�Q�W�����Rf���6�k�ؿ1k�ٶ�Z���n�N�7@�mۛ���8͈�i�(������6WD<���)�I�C�N�q�c�}!����#�)�#��H��&�0!�+����8�~J�P�����x�����MF݁�x��!�q��y�v��s���?�1Z�m�-�m�ԧZ)��;�x
��(tx) #u_�(���v#b7"~c�{lllwm���Z���9�,����VD�]��B:-�J�Ͷ��"�\2
��� �t�z��D����ѭ�Y�e!�>���i#]ѓ/P2ś���L��"�X0Q�L��	!���輹џk��YOtm6�]O9����L#f���%�.��U�p9�d2 =Af��d2_;P2�l������A3>�L&�\���~m|ş+������m&fcTL$$�j-���fdATX�͗]hW�gc��6��h
R5��ji
��'��>	��}�6��V�K��Mb�~ �����B�9wWR���ΝIC++�6�7](�5$���vcf��/wʰ&�]5sϞ�����a,�r��v�y�'��m_j��6B�*!D;�<�9��=%�h'��z�@B�=B�B*��T���,�:�C�Ї����t:��q�݌���í�lvu���1���r ]���s���R�}R�c�B�-��o�	�u�7]�=k�e�c'��tC#�8��S��
!��0njjj�Rꊔ�Rj�P(�*�n���ʚN��1��:�����ocG�7���h��Q�C�󁉉��P����^�ԯJ��B�R�VJݬJ)�qv۶}��"c��q���6�����;��״���K)_QJe����@�)
m�cǹTd�vc��Xs4�8�͜�fD���)��)�F�R?/4*�<Vhxx�U�L9P?c�kdddm�$ڊ�b�RjHJ�-�RޑRdlll�m۟��m�϶�~۶/���^�0�I)?�jd�R�tO]���ս\:�n�d2�8�s�q��c]�m�~��"Ƥ�I)�t�2R�����Zs��62ƚk)�b��zI��ƥ�zb���3M�-8G� !䉽�����{��]������k���o�~�1�	�����eY[�ffff��y�y���������߅�1#�L�jYV�i�]��eY��Ҏ�<J�����}�{����ݍ���ӥR�m�R��V�3��+BH�r��͵x����y�e@w}ߗ333ɕ��Jf���6����?3�yB`�իW_X�����������R�zJ�!�GqJ�IJ���u��9"���� b)����su6�]m�f;��.褔v�F�1�N�����b��Tj�eY�����Rz���J�FD�\q~~���I`�V㑍Ҳ�8l(S Fi"�4���@�`1� z�Q蝅�t�NB��+488��R�A)�R{������� ��Q�DE����FJ���3�i2#f�A٨���4�.�`����Y��oD|�"�a��u
���#�T�>����(����6!�[S|���U�D����k��L&�\t]��e}�h4�"� ����?����VL5��L�@=px�Xɒ�d+$��}�H&��O(���~˲�u�N���x��
J˲�X�uD���+�m����fcTL$$���w(fdATX�͖OhTG�ߦ���
b� �������V�C�5X{�E-H�Iٸ&Qc���Bb¼������,��Ń
vf7�X6o�(�;��d}�dz��uu7&�������|�;�~cYs�Z�Xc��s�v��th��r���e�� cl�s~9����Z7�r ��Vι�9�g��1�8�s�1���q��9�(���B��y_�RڜN�����/���6�?~A4���qλ�U}U9�9��ZǢ�����}��V,�����P(��L:��H餔�Bz)�g<���%QM6�=�sj9�l�PTW,�!�	!n!n�Lݻw�1[)�̈́�NB�iB��(=��R�;z����V��cQ�I��0<<�)966B�Bd*�"ՐRW�\�B9[	C9I)=�y^�u�eQm.�[�Kd��K�l�c,����E����+���We�7}��Y��!����S�ҞT*�RY��n���-�|����
�5B�߄�j�(���


m����2�#�֝�ă	!l����}[�IBoBB�RJOU��z��y�0Q!v{�!��n��ntt�&�<o	�t��y	B�	BH;!�ʣ�hh�cB�o�����}��X,.��\���R�TK&�i�M^�|~Q�PXs����:��U\k�m�C��
!�R�x�ѣG륔���I)寥R�����]p�<u�u�a��A�
�@A0AAJ)��Z���*�q�m��(���@�1_0Z�RJ�R�I)G���N�J��,��F���4�?#����T*��R� �S	d�F?~<���v�Y�ۦ6�ձ��b֭!�V\�xqέCk�Tk�Ak�j�N&��1ƻ�>�c��`�?{���Ǜ�0�Q)��R�/���0��<y�ؽ����l��'�tb�f�:[�0����J�P)5��*k�u�W���߭;A2�\�8Nw�w	c����}Z�eF�������i].��_a`�Q���8qXQ�h!Ԍj����ӧ�Re�dT�lD�Oj��"��F��1�0�&���'&&V)�*���@ƶ�u�������T����m{�eY1˲,�P���ݶm3
v�H=�T��ZWM)��ONN~PȲ,c��@�@�i#�����ڞ�d�EuZ�SSSW�����̌6p���r[��_���o�m�2�̛���LS6���[���955��p���&L���{�}�z��f詼%���u7�r�x<�;�I�����x��k���:���9_W�Jk�'�	0jfcTL$$�jqtOfdATX�͗Ah\E�w����`KC
�ͩ�x� ��^z��&B��V!Eo��&�M�d{�Q��
J\��ovŨФG߼��If��$����/�K/�y�]6�[�����y������7�H�S�e�B�Bܰ,��~�gi�;�Wl�R�Bd���uǁ	!�۶��b^q�ļ;�@�m�	!j`�X�m{��_����J>�.RJ�XYYy>z��NZ�5iJU��,kRk������R���ǹ�nwK0���9�@Ҍ�9�p>�?�B��<dƆ�y�R��8��R��R�R��q�������0U�(���D��X���)ۼ�3�9���L)�@)�}���8��)S�f
�`��Y��9˲&�w�w������I��R�Pdb�q�KM��b# J�4c,]��S�TG�P�*
]�T���n�R�Rj�в�8W�8������p��2��ѦI�����R�o�R?6�����4����J�0�2e����މIJ9��Z6PK&H)o������c���0�Kq��8���n��Z'����)�����x�T:�j�!��Z)�^���|�u�ލ�����̴��Z�$��M�
�0Di�ߴ����<ϛ�T*�U*����ۃ�
9	f�Κ?ĴY?g��[���y޺�|���u��>�Z'�\��16C)����]�B�k��H�R��@<���_<�ukk�?�9�֠ML�IB��v�����xX������:t%K�Em�ն���r+jg��}��� 8����\�0���XF��;;;�j�z*V�v?�0�����#�v�OqIk�b��<�i�;�0,�.v� l���#��:I9N9^_RD�3e�!"N�q(�!?��r�G8矛�f��� N#��(��M��^�!�f��QJ���s�;r�B�'u0"��a￱��8E���4E���)D\B�S&���u� x?L�S{]�
�h��l�Fk�j��"� "N�ax=�3�ȴ��@����n�r�~Hמ�s:��8P*����،)�M�<P�=P1��0�>4����_ō��?9�fcTL$$���֝nfdATX�͗AhW�g�؊ņ�Z!hj�-҃m���
6�
^ZTj��E��I�D<4���!�ef��-��$;o���lI��mr(Ͳo�m�.߄]����-L�Yw��h��]�{��o��{�iT4=hY�U˲�F�у������e��m��m�c��Z�u;^:P,;fY�=��(c�Ge�j��Kb�]a���`�6���NMM��?&��2��H$��0Ʈٶ=V�Ʈi��.N$�!�!�-..�^XXx�%J����BȈi�Ôҋ�pxgu
c�J� 5v���s>,�x(��BLs����O������2�[�a��~y,�m��JѨ�q۶o�b�nM�4D!�9�	!H�8�p�4)����B� !䦮�]յ�H�eY���;��;�e�G"�C�T*��s>�9���Ms�O7"��Z�0�A�4�������`�ö�=�m�	�k����ҁT*e!h-�|Zq��}`��p-�t�R�711�������7���}!Dx�=Bo�$�o3�"!�J��tHE��B�Iѫ�9�"E��r·���M9	��;)�g	!7M�RJ�(��"b���k���D"�E2���J����?�H�j՗��z!do+i��L&�C����4Ս8"��!�|E����6$�˽'�J)'��c�l��ߐ���T��~�#�~�m7L&���q�H.�{*��CJ9�8�S)e"�P(��4�!�0��\��ot]�]0�x|��r"���9���g�;�3+�<�QJ�^�M(��t]o}7�����~)�xRc�l6����r)ӶZQk����L-��v��|>�V�X<�nw������{f�OX�{�\I���Q,/���YX���@(���h蠝����.�U0��u�<|��I�GXD�,�J�K��aD�_��b�xD�i�V�z3j���y�ID|����{�w�:���U�d�]j��5�\.� �3���������jƟ.	|uu�@C Mk���T5Y]� �n�uݿUdx�Ph|��T��4"*��\=5��^(z�:\j:2��R�<zУ������Rv�UuV*�QD|�y��:{�<����x�7��'6��_�j�s�Eq�fcTL$$�j���fdATX�͗_HG��iӀ
6!))	)�̓�Pl������� i��ShN�1&�P_
"!��g��T����H0!����fz��rӗ98���i������|����?Mۥٶ}ڶ��m��l�x�ƶ�k�G�����m����x;ٶ}!4�1~��G���!۶/�;B�����`���F���,�p�0�󳳳�E�c�[*U�@��4-V輴���8̈́��d2y�q���@?����yB�aYV]�B�:�x(
�ڮ�B�	!�!�뺦�&!d$�L~Q�2
�g@W�?0����e5`�{0ƃ��1�=�e5h��I)c��t��\�}R������Ri�t�wu]?V�;==}c|!ԋ��ߜ��>Sx���|�2J�=q]�L&�WJ.odF�i�q������5SSS'���ND����B#!��v@���Z�B���y?
!��;R2��T*u�u�aB��(��8��8�%�$�Æat�4uA{�j_�S0�u[T1?ug\�!�VVV�-+�eYu�V�]�4��;¯����_o5)elqq�R걪�NBH}��4]׏NV���,�HM&������wk�lGť�1�0>��|��{B)��R���y#��AJiK���VE۫�>U?�L��y3�罠�ΧR�?=�{����)��i�����i���]<��v�뇪�H$SJ����y�l���y�����5a�6�D��I����d>����f#>G)>x@.e�A+jM;x�����jnRʺt:}.�N7T�q�7W�ycl�1��s�p·777O���Ǐl6�9�8�o8�8�co��섔��y@�d2�3��8�oc+���A*=�J)�r���\r˚�N�ϩ4��)�n��Pه� .	!�L�|���Tͼ���$P�� ����0�"'�ȩ��MJ�c�����*��ƒ@�V�EQ1����bWm��~����8��1��8�cl�1V�
Uj����2��s!DB�a>/^"VWW�lll\��朷��L%��"�
P>Ù�X�|�oS������}�m߁�����
!�0E�B��?(��-a�,����?�}�Yw�_Ҹ�7��-fcTL$$��4(yfdATX�͘_hG�4���jC	Z[��lڢOڦy4�(M���)X���ڇ�
RC"�`ws�sմ�Kfg�Ղw7B.;&�����s��	�g�4?��������fO�6�i4M��7[�#�H��^�4�`F�uo"�ر�@�t�($
��AT��t:}tˁ�F�0����O8==�v&��N��a.�kŕUa���:��c|�q���S���B�B�;H@�4�1��L&��S��@I諏���{	!�l۞$�LB�l��q��#+�`�o?n�M�b��i����˦i��1dƐi�cpY�}��iR����\�r۶���PP��R6���Cj�=���T�Bh!�ʁT*u�~߲���5۶o��nB�0Ƨ����	���RƲ�lG6��RƂ�,�:@�c��x�q��Q�@I�Bh!tqbb⍆ET8��&!�
�Vȶ�I��(Ev�qƕM��a�ڧQa�A�B�sK)u�rc|�ѣG�E*��d�BgԢM �."�>[%���!�\Q�]#����fki�����fl�(,�ڝ��X�ձ�Z�,6T\J3�#���W�K^E
��b��m�T��R:^(z�
9�jю�
1��Oˏ��nJ)���Y.��RJg�u\J����i�6�v1���V�H)�(�?QJ����	����#B����A��-…��w)��)� Нr�|�T*]�~@��2m�-jE��~��[4݊Z�:VVV�+���J����[�\9��{��<cl�u����:?�����9��1V�?�?��+1�&w7,��Z�Px�1v�s��1�W09�O\��iX�%��;k�ZW�V�R>�gU*�Ì�<�a�1Vt]�B�"�=�;!��B,���<�Dh?clN���\C���A��w!�
!�B��*�x�~�R/��U�y)d�?��>p]���@��C����`���]��[[[�d�MrΟ0ƊJ���~	&��F��Rʘ�bu�U!��!�|}yy�K�u/p��EV���}?�,
U}�O�|�@g�e<���[����|�B<S6�
!���?��dK����ju�Z��x�w|����0����|fcTL$$�j�FR�fdATX�͗]hW�g�h+���h����i�}�>Y�E�I�J�`� �dži�dcV+��~��bg��]���
;w&
U����]��!�w������/we�n��ML��칿����c��m��m�>�c�l�g���E�˶��@�~����;P*��I˲z���s2�Jm�w 8�j0�8�|����t�c�aYVW.�[RtD����Ds�ҡ��͔�ݮ�n�f�o�Cy��
$ i�v!�P:�^ڣ�끒�����f;(��ǹ�8�e�q.{�w��[vF�Կ�i˲��3�0Ms5��m�ϲ�˲zl�����i�6�@��� ��
��|-\׽H)=ӒS�M�޼GO�Q��@9L�V�၁�
�q��V��{�Rz!
D)=�8�e�u�5����qX�G�X&���d2����QJ�PJ�9�� �u/QJw���{��!	Bȉk׮�Ҵ�����2Ji��88tihh�����Z�uN�6uBڵ-����y�'z1_�N]��^q���R�t:���W/�8!�!dk}KZ"���N)�q]�G�u��r�/��lG�_��4�X�N�f�n���V̶�ӌ�#b̲���up��Β�B��qdd��?0ƺ�Ž(\v�E{J�I�~�����H����A�o0�n2�c�1f�R��l�NDom����a�4�".a�}�! ��4c���E�KR~g�f�\����1v�1F����B�w�-��mQk���7�
�������l'B�&����ɓ��Gĵa���S��"���_#b۟7���9�)�B�8�����x5ß��N��9�Lq�s�B�B�B�&��:::��i����}3� "�MMM�|[�J��B���hp��r�w6-�a߿���A4W*���Je#">wf	!6�6ݮҮmš�3҄�B�u��r��UJ9��z��z,�,��[k��7�����~�X���F�?g2k[�Z�v)�APRJ�)�����j�ڥk�&&&���W!ĭ��ѵM��0�>}�)"��x<D|V@)uN)5����.&�R�jy���+�W9�w�����=!ĭR���%��*�ʦ���ca&q/">���Rf� �@Ap)e#�ޣG�^���-��[v�)��+�J
*)�����T.���E`ƔR��ry߼!�b)eBJ9׭��R&��*�-Rʔ�2A�������ÿ��fcTL$$��^��fdAT X�͗]hW�g�h��U1�P�`_��~���!؇BmQD҂OURv��U	j�'�	3s���:w�PW�A*3C|�aw�Ő���d���ӗ�e�l�]m�p��ߜs�M��L��`���6�6�++�.�ݦi��8W���ht��,k+$�����:aY��7{��Se=�w�/���n2����؜�d�T��:�}/\��xpppK:��*�N�pguK0��	!�@�i� ��K&�˫�:�~-P:*~�m��m�G�q�PJ�:�s�RK��5�S{�'
�أiZD�4M�68f��	�0�
��6M��u�M�4
#��|k�o��SU�K)�5�)U�zwޭ.�������N9@�Rv������oY�*�q�㜮:E)=w�ʕ
�`W= ��(���WYX�8�jD�T�R�6�q�m�:K)���mR��D�B�\�xqI� J��/��U%��8���-
�ܾ}{�a��*S�WY��Y��l�Le�W��������u����$��儐=�i���#��OkKҌ1B)�\e�m�q��+Z��麾ֶR��R&�Y�J��,�Z5�X�S�3���0>V�� tTΒס{��mp]���=���}���v��nմ=�@L����׉;w�|�y��癞�
�����o���"F4˲֛����S�‮��
&��,�</��y�����y^�ݻw?�!���
�W]�[�/���P��(��56�y^��w%��[Sk�{�O����q3"�l�r
/�f�>|�^�~����0���Ϥ�I)�a��|� b�1�
c�`����� ���5M{'ë��Rʢ�rJJ��e��V�c{c�� ����u��P�GGG�b��9==�R���R����~���2Ƙ�`25v3��
�H)c�r�Д���/^�Z@ą�B��P(�#�3+��nTe�V(���o��aU�Z���������b;���B�a��^�[�z�z�[At6���z_J�DJY�R��+����&!��9�	!�!;>>�Ie/��墌�����T6�]�H�4�X,vH)� "��e����R�t�
�9�B0�H���z+~�|~
c�<c�&c얲��Ǐ�h
�:Sa.�JQ)eg.�[Z�CĈ")��4*�HV��###oA�3�������̴"�%!D�P�s~i�/�H�B��s�T�T`�s�Y�Phܬs-D\�9��?Re�?�w�>�oT��m��m����]̃^���fcTL!$$�i�.3�fdAT"X�͗OhW�g�S����A�6�B���B �x��S5�@OZbio����5�ċ�B׶����J��
^��=XY��y3І�0y�X���^^�i�i����?x����~�f-k��ws�?3ֽZ��|>��r���"\4�|~�*�`�16
�O��oN��&e��D������8N�3����
��&UY��p:=�\.o`���~�q��������{�,L��|��ٜ0ϳ@0��sg3"q���x����e�ؠeY9˲,۶w��|�16�5��ض�Ӹ�1��r6��x��H�4���Q�ؖd���t"�B�o������I�۶�F̻����8}M��% �<t��k�s�Ri[�Tڦ��e|uq��铘Im+�gB��DC��B�����B��	�q,�s>�8ξ�N���F�ؐ�hcvt�U�Dp�r5j�q�Z�nlɉ9��h�f���)iEZ�"B�a��DL�ؖe����4�J�Bat��Z���Z�c���4�zI�u���=�r]�|�R�T*������h/��8a����~�J���
��]7�O��9�X,��9�
s]��m{}�`����uϹ����y�)���j�����'W�$
�y۶�u
��]��}�y��Ф��Wkhͥ�ZkEmYk���j��h�&�}J�U��Ǐ��aO�^���KKK����D�Eq��8��u/W�?�}�G!�OB�������ު���h&���D�=%"��v�|�B�B�B����0�_ڍF�8-����8�kZ��W���5��%��9c�
!�4uBD�腲@��""z�+϶�z��^�wg{V�=&M3+������8�?�"���h�N�...�*�nH)�RʪR����bo�ѣG;��~i�r�Z�!�����='�kIaGQ�GJyO)�P)���C)�(��X֋�B|-����	�� X����F���g�B�G�D4����+�EѸ�rNJ�[��(O����o���۾ `��n	&)��sDt\k����Z�R7�R�Y �ԬR�f�E�j���G|�?���#ӎ&M��@&;�`3)��I)�RR0�o͋���Z��R�QJդ��R�Y3>����'Kۊ��7����z����ҙ�����fcTL#$$��,���fdAT$X�͗]hW�g�Zi�b��`�(��Nj����IP�"EJD}(}�(
k�D(�A��Â�f�ܝBӆ��ΔB%+Ids�&��0w.��,g�b�ӇޕuL�Y�H�p�½s��Ϲ����Hٶ�ݶ�otl_l��V2�\Gl۾W�I&���9P&����e��e����Lf�;�Sp�S��Ԓo�N�7:���e��f��#@�t��@W�\��l6�ڲ����8N{:���cl7c��@�m�}��.�q�����Q�~8Q[�8�z�X�m�}�|��K��ݱ��0��_�,��0��a�i���Ƕ�>˲z-��՛����Y�KX��	ף.2�.�rJ�i�����6���`��a�}������ڼi��G?�����7�����Ih�_OD��������V"JDr�ٶ������v�qh������+���T*��a�T*��1����g�v��8{&���k,���u���Sԍ\���ؕ����J�oF�nڤ>�hI∈��C��n]��Xg���-ݔm͔i!�R�u�V)b�m\X��ߋ�V���uU��?��j��R/^�8�\�l���v]����z���"JT�՛�|��e����r��m��\׽���n�r���HDtx""�V�#D�v�`��%�˝w]���7���y?����177��B@D�/}�T@��}��ތ��<o���VЊ+يkjM�r�}TN,���+�-�JeW�T�p�;OOO�����m�bqS�#�fD������G�0<�6�Q�P(|911q�X,Z���|�Ph�}D̄a���i�>"�T*���	!�!�����s�755�^��x,�"�Sjdff&������w5D�W!��8@q&
��O�I�R�U��4͖r���\.o5M��~N���]y
�s>$�8��@ӈ8Y*����*�H)SR�?u��R�^�bq�<�9�����hC��ϟoCD�����"�\kl��� �J�� A�@��J����C!�s������G�>nd�1;;{9"΄aX�=�A��-CJ�TJ�A�w}(�ƥ��ںB���9��=3���?55���v�4"^���=ND�>c�(!��'�|�R>�RޫE
��B��B�����Gc;ӌ��R��y�F��K�a#��,�q�?5�A����Q���;�� �:F���D��8�2I)���;���QJ�_l���=����fcTL%$$�i揠fdAT&X�͗OhW�g�Z��
QDт ڃZ��r1
=xH�ZB�����4�Dkm�z� ,�.+3�2��Dp��Vdf�J�(��������[�z��؝h�<����ߛ�5��BhB�;{>��+�N7@B����nK��M+488x�,����pY���8���!L$���e�a6��b�v�Z��E>�_:�[�g���|~�eY_@�m�-�lv˒`0�0���B��۶7E�N��q�>8�ٶ�	c܁�
�a�/`�$vF��wͲ�v�0R�a�i��.�P�eYݖeu�v���C�KY����.b�/$rJ�i��w�mss��^����O:��r���u�47@���;Nڶ��Z���9���Jy����J�T�V3B(=��ֶ&q蠶�����L&��a�L&�c|c����m�`�"�Ba�eY���!��ё�0����[�cG�PX���>�zh�z�G�-I"�T
c|c|^��c�=�����y)mz�2��h��D��]J��Z�v�^�;Rʿ��wj���>���u��������|c�R*%��uaaAI)��u�����~��0CCC�;���y�M�uo8����M�q�1#e����R�K)礔�a��uU��G�R��4�5���y^��?��y�u��~�w���V�=���2
�cNJ����-Ѓ���{�q��Q 7���~0j��C��VЪk٪j�Xe�>�U�`|�����j��_)��NL�Bac�X�=>>�m�?����.��%��9��9�B��}�������!�wB�BH'!$�w�R����黜�����MOOw,(��A� r��g	!� �ribbⓆ��9�s>
!�+���ׯ?K
S.�7jgrA�
�-��
!�	!�"@/8�ϫ��h�R��R��R�K)��?֧O���m�Ɓ!!�=	���B��jug��;��M)��3�n3��<.��Ƿ�y(T,�5����-��[1��p��T*�c�9��?c#���vJ��>�^���`�B|�o�o-�1��v�qΩ�����0�Rz�1��1���J��0���r�b��F���b��U"��S��禦�NLNN���UJ�c���E�Fc�Gijg����Bi/��;�QJ�����8P�\~L)�_�6R�Ri�-��0ƞT*��2MsM�T�d�=�me�=*�J��i�Yq�P��C��[:}l����H҇wfcTL'$$��p\I�fdAT(X�͗OhG�Em�`�P�Z���-R��b����4-�/zPB�3�I@{iJ��H$v�{��}��x����B_v��ϝ���|����YYWC61
������|��oY�zCQJ�RJ�6��M�-X�l��RJ����h6�mYr�|>�	!�p.��`>�߻�@�	#��]��E�pttt��8�����P(�Lu�T%���;��P(�$���v�q����n�"�D��0���D�rgm�y����:�q�"b�t ��qg�����!�ee,˲l��=��BH/!��l�c�F�.C���d�|�H�4���f�u�ڱ��m�x�1vrlll[�ܶ�u�c�}%�����_d"����z�uf||�u||�Uk�I�j��f�71��4�eB��D�C�3�\n��N�r��jD<��}I���qv����U��.���Ɯ�@Z�Hp�r�k���U�����a�6kNy0��4�Zg� "�1)�AĎ��M-S�m�I�l��r��-~)Ri!QXti�[fffN�����0�-��G��~��7ݾ���@h�3�fs(�O�f�Y7㿞?��b�PJ���4h��E3>jYV�j4��a�w�*C�F����o-�m�+�d���ח��?��ƭ0�I�H8�l6?\D�V�v�Fُ�_-K�啲eWԖ�̮}Rˢ1ΦZ���V��x����;	��5�ryK�Xl������mA|'�,I)�RʻJ�΅~\K���%�O������r��>��'O���RK)}��#��C��c)�R��q�s�Gc�c�\׽�9��;7111�
��R�O)��q3Pw+�ʻia<�{�s~�1v�uݟ6�ۗ贔r�5@���Z��#�^k�255�yjjj���?�b��Ť�Z�16�yޱ9��R'fz$�,�j�M�Z!Ğ����7�7�xO��1v�u�W�L���A<PJ��� �v�R�.�@!�}!�-c��X�T���eJ�R7�F(����7'���\)��Dj���p�^��!�8�~A�K����h]�\^�9?��0�|�s>b`>I��R�D��>}������:S�V�T��;I�j�z�Z�^����V1�y�w�1v(ud�#!Đ�^��
-��)�!~5���2s��ȶ����}��I��o��ʶ�KI���˾�_�_���_f���p fcTL)$$�imfdAT*X�͗]hW�gcm�V��A�G}�Zj�`}���'�V�k�/�b_$�l6_~ TE��%O!vf�ݑT���3i���Vso�P,Q�{vi���5���Y�IwWc�?������{�a<�l�^m�>��w�gV:����m7@34���t�fց���&h�,+�Ш�۲��Y���ÔD��'&&�MNN~EQ�&��	��TI�f8P�����eY`��8�;::�T��㏉�HO�'��%@��m
�6��9���1Vo�v�kc�e��S�MD����$��a�i�+����V˲2�ee�
�i��ӥ,��
ǒ.2ƎV������`4P@D������5�����ot����\���� 3����l.EQ�t@Dѻ	GS���K���������m�KOb���*q��?::Z�loo_�;�kJٶ��8���ѫQ�8
БJab��z#�P8fYV}���&!��Q}OD�D�7�ND_Q���R��-��#�m���1��q�E��eѪG�m$�׫�sB���`�i����H��z�"����	���xw����7���tۥ��^˲�b��0̄a�G�0UA����ؖ���m{��Mm�5Ӣ�w��2�0� Ñ �Ap/�0�a�p�Ν�g
�4�y�����?i��7e�ٷ�����v�^"�A������Z
�N�(�c��=���ѹU�9��
�����g��c�Ԝh����ݻ+q"���s���-���_��0cxxx�R�
{�O)ե���Y�#���u���y�y�w����\.W�;mhhh!"^B�ۈx�+���R��j�|��u���s]�,�,��_Ut��RەR7���P]�b��k�.S�u�s��3��������oJ���x+	����ו�Q��VQ��/q�k9秵3Oy�w�s��,P�P؃��L��
D�QJ��l�R��R^�qb```C<���N�2%��y�ֲ@CCC+�R���푑��xc�BtH)���LG��cppp�v/�9��9��p��x.�{�,�aF�X�C�k�B���7�����8GJyHJyUJ	��*�<��r�Ŝ�î�כ����}��"�R�
����A��|>�J<FD)!�)!ĕ$���Ti������ۻ�s���;S�����\I�����3�`@u�<�����f�4�yR�z!���tE�H)�MӜ7�@��z!D��%�ϗ�-���γ�<`fcTL+$$�����&fdAT,X�͗OhW�g��FZ�
b� ���*�j�����kH�-�hAZ�!e�Ϩ��ɃĂ1af~o���4�<�{���ڰ��FQ�Z�u�f��
�v7&���淿�����j�<e�f�i�?�h�o�7V<��=�iv@t��=�x�nс,��=�at@;���˲�-:���LYt��?pzz��0�
����ebbby�jU��玌��m�f�m��������盥����"���`�P���kˀ���P4��l�^A9`��I���r���fg�����)�T)��W1�i���z#���y�0��0:ԡ���7�r1�0��������59��Qu�T$�RʧSSS�J��	!�!m*
�/��u�h��JNڶ��*����X,V��R�����-�s]��u݆�{%��4��71���U��|���L% )�|����Q���'�#�tF�L�<i�'U�LNN���
ն�r&,���/�”_�A.Au�)�0ܿYME^�x�6�~)��B���A����F�!�KB�1�4��J�k�����q"nF������`���s��E�X����}��m�{�={V}��Pj�5�m��0�Og�@Ę�m�� 2A�A � H����BØ�٤vS����V�{4M�i�\� �
��O���b�}@��nB
�u�-B�!��gW���e�Ӟ?>�xfL�s��j��^�(�!?.=�%ײ%7Ԛ�Į}TKb1�N>�(��lH���Ϸ���H���P�eY5�Z�U6�]�y^��B\BB����=D�QJ�������r��u��YYS����z��.!�y�wKq���B��B��sr�+J�9Ji/���g(��)�Gt]�j��v	!�!F�CAO�<��}��/W��RJO���g����V-��fVB�B\�d2���.�N7���FD|����P��=�㜉)׾�š՞�C������Rn*����L&�����e�Xg*���{cY�*53���_�vmGU�L&�V1$����711/
���hc��\���c�FGG��{15��G`z)���6T�4M{���NuW�!ąl6����L&�0�L�ؕH��d�H)�q������YJ�9�u�QJ��Ėʧ��S��d�ك���{��c��n��`�A�yw���x����ᭉD�D"��fg�"�	բW�TO,������978�e0��s�1�sс�.�J��_U��_M�R���Q�9��kc��q�7η޿��~�UfcTL-$$�i_̆gfdAT.X�͘MhG�W��mu�q �}JZ��B!mJ.9��؅@NN1���fM��_�%�Bq+�����!�J!q��YԠ�`��B#l�4kR$�s.��l�.6�-�;�7?ޛ};��Ѥ!=��ϵ�4o�J&�m0@��i���d2�v�@�l�R�eM�
��ǩl6��@03L���p�ð7�/�:"*��c@Wt��@�p��wii�U˲���m�YXXx{O0�j����m��".V*�5@C��(C��m�G(�#��Y=���^����pf�GĿQ��D�A)�0�0M��	!��eMZ�5�7M�K�KX�unƳH)��P�t�0#q��V�'#�L&�K)��~�m4���F�i�q��)��m�����N@"����_)���r�\�#�^$�$�$k��Xi���V��z�ā��aXPJ�D+�N�SJ�(�Sq BȬm�}u�(��@��0_�2��1�/���AM�M˲F���_k(H�R9����F�g�L�0����)��ҏ)�c��$�SJ/ڶ}d���j�z�� h�L�)�N�@�i�{���,�\J�����r�|�\.�,��-�J�W��6����eY�L�R*!��ZJ��� �H)W����J�[
C�ѽ)�?33z<`F�����R��c)��A�\.���j�Mh@�i�B)��e�]~*��v�Ri^J��F=��
��u��.�r�Rz�����Mm�쵏�P4�ݴ��v�����7����O�aF�X<���:.��B�{����������[B�GB�Em��9�C�#�[M?������>�n�Px�Q���<�B<�hQq���Z�d2ٖ��;�|g��ڒ#��y�Kq �yNAWVV�G���ӎ�\g��f������|K���wy�gy��k��hc3ƺ��16���6��u�p�_��08�x�gqΗt�x�w�X,��Έ�?2��Ԛ~6�5}Q�e���\�Px=�SJ%Ǚ`���@����8�-��t#r]wL�+4��X��'��Y]���9���)��c�\�^�.�Ǘ�R��*��8�cWcW�9�l����l��IEND�B`�PK
!<��A^�4�4:chrome/toolkit/skin/classic/global/media/videocontrols.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.w3.org/1999/xhtml");

.videocontrols {
  writing-mode: horizontal-tb;
  width: 100%;
  height: 100%;
  display: inline-block;
  overflow: hidden;

  direction: ltr;
  /* Prevent selection from interacting weirdly with the page,
   * see bug 1766093. Our text selection story with shadow dom should be
   * better, see bug 1590379 */
  user-select: none;
  /* Prevent unwanted style inheritance. See bug 554717. */
  text-align: left;
  list-style-image: none !important;
  font: normal normal normal 100%/normal sans-serif !important;
  text-decoration: none !important;
  white-space: normal !important;
}

.videocontrols[flipped] {
  transform: scaleX(-1);
}

.controlsContainer {
  --clickToPlay-size: 48px;
  --button-size: 30px;
  --timer-size: 40px;
  --timer-long-size: 60px;
  --track-size: 5px;
  --thumb-size: 13px;
  --label-font-size: 13px;
  --pip-toggle-padding: 5px;
  --control-focus-outline: 2px solid #00DDFF;
  --control-focus-outline-offset: -2px;
  --pip-toggle-icon-width-height: 16px;
  --pip-toggle-translate-x: calc(100% - var(--pip-toggle-icon-width-height) - 2 * var(--pip-toggle-padding));

  color: #fff;
}
.controlsContainer.touch {
  --clickToPlay-size: 64px;
  --button-size: 40px;
  --timer-size: 52px;
  --timer-long-size: 78px;
  --track-size: 7px;
  --thumb-size: 16px;
  --label-font-size: 16px;
}

/* Some CSS custom properties defined here are referenced by videocontrols.js */
.controlBar {
  /* Do not delete: these variables are accessed by JavaScript directly.
     see videocontrols.js and search for |-width|. */
  --clickToPlay-width: var(--clickToPlay-size);
  --playButton-width: var(--button-size);
  --scrubberStack-width: 64px;
  --muteButton-width: var(--button-size);
  --volumeStack-width: 48px;
  --castingButton-width: var(--button-size);
  --closedCaptionButton-width: var(--button-size);
  --fullscreenButton-width: var(--button-size);
  --positionDurationBox-width: var(--timer-size);
  --durationSpan-width: var(--timer-size);
  --positionDurationBox-width-long: var(--timer-long-size);
  --durationSpan-width-long: var(--timer-long-size);
}

.touch .controlBar {
  /* Do not delete: these variables are accessed by JavaScript directly.
     see videocontrols.js and search for |-width|. */
  --scrubberStack-width: 84px;
  --volumeStack-width: 64px;
}

.controlsContainer [hidden],
.controlBar[hidden] .progressBar,
.controlBar[hidden] .bufferBar,
.videocontrols[inDOMFullscreen] > .controlsContainer > .controlsOverlay > #pictureInPictureToggle {
  display: none;
}

/* We hide the controlBar visually so it doesn't obscure the video. However,
 * we still want to expose it to a11y so users who don't use a mouse can access
 * it.
 */
.controlBar[hidden] {
  display: flex;
  opacity: 0;
  pointer-events: none;
}

.controlBar[size="hidden"] {
  display: none;
}

.controlsSpacer[hideCursor] {
  cursor: none;
}

.controlsContainer,
.progressContainer {
  position: relative;
  height: 100%;
}

.stackItem {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 100%;
}

.statusOverlay {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background-color: rgb(80,80,80, .85);
}

.controlsOverlay {
  display: flex;
  flex-direction: column;
  justify-content: center;
  position: relative;
}

.controlsSpacerStack {
  display: flex;
  flex-direction: column;
  flex-grow: 1;
  justify-content: center;
  align-items: center;
}

.controlBar {
  display: flex;
  box-sizing: border-box;
  justify-content: center;
  align-items: center;
  overflow: hidden;
  height: 40px;
  padding: 0 9px;
  background-color: rgba(26,26,26,.8);
}

.touch .controlBar {
  height: 52px;
}

.controlBar > .button {
  /* Prevent #textTrackListContainer from blocking clicks on controls */
  z-index: 1;
  height: 100%;
  min-width: var(--button-size);
  min-height: var(--button-size);
  padding: 6px;
  border: 0;
  margin: 0;
  background-color: transparent;
  background-repeat: no-repeat;
  background-position: center;
  background-origin: content-box;
  background-clip: content-box;
  -moz-context-properties: fill;
  fill: currentColor;
  color: inherit;
  /* We don't suppress ::-moz-focus-inner, so that does for a focus indicator */
  outline: none;
}

/* Keyboard focus styling for interactive control elements (includes control
   bar, click to play and track list) */
.controlBar > .button:focus-visible,
.volumeControl:focus-visible,
.scrubber:focus-visible,
.clickToPlay:focus-visible,
.textTrackList > .textTrackItem:focus-visible {
  outline: var(--control-focus-outline);
  outline-offset: var(--control-focus-outline-offset);
}

.touch .controlBar > .button {
  background-size: 24px 24px;
}

.controlBar > .button:enabled:hover {
  fill: #48a0f7;
}

.controlBar > .button:enabled:hover:active {
  fill: #2d89e6;
}

.playButton {
  background-image: url(chrome://global/skin/media/pause-fill.svg);
}
.playButton[paused] {
  background-image: url(chrome://global/skin/media/play-fill.svg);
}

.muteButton {
  background-image: url(chrome://global/skin/media/audio.svg);
}
.muteButton[muted] {
  background-image: url(chrome://global/skin/media/audio-muted.svg);
}
.muteButton[noAudio] {
  background-image: url(chrome://global/skin/media/audioNoAudioButton.svg);
}
.muteButton[noAudio] + .volumeStack {
  display: none;
}

.castingButton {
  background-image: url(chrome://global/skin/media/castingButton-ready.svg);
}

.castingButton[enabled] {
  background-image: url(chrome://global/skin/media/castingButton-active.svg);
}

.closedCaptionButton {
  background-image: url(chrome://global/skin/media/closedCaptionButton-cc-off.svg);
}
.closedCaptionButton[enabled] {
  background-image: url(chrome://global/skin/media/closedCaptionButton-cc-on.svg);
}

.fullscreenButton {
  background-image: url(chrome://global/skin/media/fullscreenEnterButton.svg);
}
.fullscreenButton[fullscreened] {
  background-image: url(chrome://global/skin/media/fullscreenExitButton.svg);
}

.controlBarSpacer {
  flex-grow: 1;
}

.volumeControl::-moz-range-thumb,
.scrubber::-moz-range-thumb {
  height: var(--thumb-size);
  width: var(--thumb-size);
  border: none;
  border-radius: 50%;
  /* this is a foreground element even though it is implemented as a background */
  background-color: currentColor;
  filter: drop-shadow(0px 0px 2px rgba(0,0,0,0.65));
}

.volumeControl,
.scrubber {
  outline: none;
}

.progressBackgroundBar {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.progressStack {
  position: relative;
  width: 100%;
  height: var(--track-size);
}

.scrubberStack {
  /* minus margin to get basis of required width */
  min-width: calc(var(--scrubberStack-width) - 18px);
  flex-basis: calc(var(--scrubberStack-width) - 18px);
  flex-grow: 2;
  flex-shrink: 0;
  margin: 0 9px;
}

.volumeStack {
  max-width: 60px;
  min-width: var(--volumeStack-width);
  flex-grow: 1;
  flex-shrink: 0;
  margin-right: 6px;
  margin-left: 4px;
}

.bufferBar,
.progressBar,
.scrubber,
.volumeControl {
  bottom: 0;
  color: inherit;
  left: 0;
  position: absolute;
  width: 100%;
  height: 100%;
  padding: 0;
  border: 0;
  border-radius: calc(var(--track-size) / 2);
  margin: 0;
  background: none;
  outline: none;
}

.bufferBar {
  background-color: rgba(0,0,0,0.7);
}

.bufferBar::-moz-progress-bar,
.progressBar::-moz-progress-bar {
  height: 100%;
  padding: 0;
  margin: 0;
  border: 0;
  border-radius: calc(var(--track-size) / 2);
  background: none;
}

.bufferBar::-moz-progress-bar {
  background-color: rgba(255,255,255,0.3);
  border-radius: calc(var(--track-size) / 2);
}

.progressBar::-moz-progress-bar {
  background-color: #00b6f0;
}

.scrubber:hover::-moz-range-thumb,
.volumeControl:hover::-moz-range-thumb {
  background-color: #48a0f7;
}

.scrubber:active::-moz-range-thumb,
.volumeControl:active::-moz-range-thumb {
  background-color: #2d89e6;
}

.scrubber::-moz-range-track,
.scrubber::-moz-range-progress {
  background-color: transparent;
}

.volumeControl::-moz-range-progress,
.volumeControl::-moz-range-track {
  height: var(--track-size);
  border-radius: calc(var(--track-size) / 2);
}

.volumeControl::-moz-range-progress {
  /* this is a foreground element even though it is implemented as a background */
  background-color: currentColor;
}

.volumeControl::-moz-range-track {
  background-color: rgba(0,0,0,0.7);
}

@media (prefers-contrast) {
  /* Show a border in high contrast mode since background-colors
     are not shown. */
  .scrubber::-moz-range-track,
  .volumeControl::-moz-range-track {
    border: 1px solid;
  }

  .scrubber::-moz-range-progress,
  .volumeControl::-moz-range-progress {
    border: 2px solid;
  }
}

.textTrackListContainer {
  position: absolute;
  right: 5px;
  bottom: 45px;
  top: 5px;
  max-width: 80%;
  display: flex;
  flex-direction: column;
  justify-content: end;
}

.textTrackList {
  flex: 0 1 auto;
  border: 1px solid #000;
  border-radius: 2.5px;
  padding: 5px 0;
  vertical-align: middle;
  background-color: #000;
  opacity: 0.7;
  overflow-y: auto;
}

.touch .textTrackList {
  bottom: 58px;
}

.textTrackList > .textTrackItem {
  display: block;
  width: 100%;
  height: var(--button-size);
  font-size: var(--label-font-size);
  padding: 2px 10px;
  border: none;
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-align: left;
  text-overflow: ellipsis;
  background-color: transparent;
  color: inherit;
}

.textTrackList > .textTrackItem:hover {
  background-color: #444;
}

.textTrackList > .textTrackItem[aria-checked="true"] {
  color: #48a0f7;
}

.positionLabel,
.durationLabel {
  display: none;
}

.positionDurationBox {
  text-align: center;
  padding-inline-start: 1px;
  padding-inline-end: 9px;
  white-space: nowrap;
  font: message-box;
  font-size: var(--label-font-size);
  font-size-adjust: 0.55;
  font-variant-numeric: tabular-nums;
}

@media (-moz-platform: macos) {
  .positionDurationBox {
    font-size-adjust: unset;
    font-family: "Helvetica Neue", "Helvetica", sans-serif;
  }
}

.duration {
  display: inline-block;
  white-space: pre;
  color: #929292;
}

.statusIcon {
  width: 36px;
  height: 36px;
  margin-bottom: 20px;
}

/* Not showing the throbber on mobile because of conflict with m.youtube.com (see bug 1289412) */
.controlsContainer:not(.mobile) .statusIcon[type="throbber"] {
  background: url(chrome://global/skin/media/throbber.png) no-repeat center;
}

.controlsContainer:not(.mobile) .statusIcon[type="throbber"][stalled] {
  background: url(chrome://global/skin/media/stalled.png) no-repeat center;
}

.statusIcon[type="error"],
.statusIcon[type="pictureInPicture"] {
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
}

.statusIcon[type="error"] {
  min-width: 70px;
  min-height: 60px;
  background-image: url(chrome://global/skin/media/error.png);
}

.statusIcon[type="pictureInPicture"] {
  min-width: 84px;
  min-height: 84px;
  background-image: url(chrome://global/skin/media/picture-in-picture-open.svg);
  -moz-context-properties: fill;
  fill: currentColor;
}

.videocontrols[localedir="rtl"] .statusIcon[type="pictureInPicture"] {
  transform: scaleX(-1);
}

.pictureInPictureToggleLabel {
  margin-inline-start: var(--pip-toggle-padding);
}

/* Overlay Play button */
.clickToPlay {
  appearance: none;
  border: none;
  min-width: var(--clickToPlay-size);
  min-height: var(--clickToPlay-size);
  border-radius: 50%;
  background-image: url(chrome://global/skin/media/play-fill.svg);
  background-repeat: no-repeat;
  background-position: 54% 50%;
  background-size: 40% 40%;
  background-color: #1a1a1a;
  -moz-context-properties: fill;
  fill: currentColor;
  color: inherit;
  opacity: 0.8;
  position: relative;
  top: 20px;
}

.controlsSpacerStack:hover > .clickToPlay,
.clickToPlay:hover {
  opacity: 0.55;
}

.controlsSpacerStack:hover > .clickToPlay[fadeout] {
  opacity: 0;
}

.controlBar[fullscreen-unavailable] .fullscreenButton {
  display: none;
}

.statusOverlay[fadeout],
.statusOverlay[error] + .controlsOverlay > .controlsSpacerStack {
  opacity: 0;
}

.pictureInPictureOverlay {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  opacity: 1;
  background-color: rgb(12, 12, 13);
}

/* Status description formatting */
.statusLabel {
  display: none;
  padding: 0 10px;
  text-align: center;
  font: message-box;
  font-size: 14px;
}

.videocontrols[localedir="rtl"] .statusLabel {
  direction: rtl;
}

[status="errorAborted"]         > #errorAborted,
[status="errorNetwork"]         > #errorNetwork,
[status="errorDecode"]          > #errorDecode,
[status="errorSrcNotSupported"] > #errorSrcNotSupported,
[status="errorNoSource"]        > #errorNoSource,
[status="errorGeneric"]         > #errorGeneric,
[status="pictureInPicture"]     > #pictureInPicture {
  display: inline;
}

@media (-moz-platform: windows) and (prefers-contrast) {
  .controlsSpacer,
  .clickToPlay {
    background-color: transparent;
  }
}

.a11y-only {
  position: absolute;
  left: -10000px;
  width: 100px;
  height: 100px;
}

:host::cue {
  font-size: var(--cue-font-size);
  writing-mode: var(--cue-writing-mode, inherit);
}
PK
!<Я>�TT4chrome/toolkit/skin/classic/global/narrate/arrow.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12">
  <path fill="context-fill" d="M6 9L1 4l1-1 4 4 4-4 1 1z"/>
</svg>
PK
!<�#Q\\3chrome/toolkit/skin/classic/global/narrate/back.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
  <path fill="context-fill" d="M 5 0 C 4.446 0 4 0.446 4 1 L 4 23 C 4 23.554 4.446 24 5 24 L 7 24 C 7.554 24 8 23.554 8 23 L 8 12.404297 C 8.04108 12.509297 8.109944 12.610125 8.203125 12.703125 L 19.296875 23.775391 C 19.495259 23.972391 19.661613 24.039562 19.796875 23.976562 C 19.932137 23.915564 20 23.748516 20 23.478516 L 20 0.52148438 C 20 0.25248437 19.93214 0.084484365 19.796875 0.021484375 C 19.661613 -0.040515625 19.495259 0.02856248 19.296875 0.2265625 L 8.203125 11.298828 C 8.1099445 11.381828 8.04108 11.481703 8 11.595703 L 8 1 C 8 0.446 7.554 0 7 0 L 5 0 z"/>
</svg>
PK
!<=��ll3chrome/toolkit/skin/classic/global/narrate/fast.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg id="Icons" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 20.4">
    <path fill="context-fill" d="M14.42 16.68a.77.77 0 0 0 .54.7l2.51.68a1.58 1.58 0 0 1 1.06 1.22l.05.39-3.89-.53a4.34 4.34 0 0 1-1.74-.72L7.2 14.03a5.79 5.79 0 0 1-5.34-4.88h-.82a1 1 0 0 1-1-1l2.9-3.24a6.16 6.16 0 0 1 4.7-2.39 5.88 5.88 0 0 1 .77.05 5 5 0 0 1 .87.15c3.75 1 6.5 5.84 6.5 5.84a2.27 2.27 0 0 0 1.14.85h.17a1.27 1.27 0 0 0 1.22-.4l.78-1-2.47-1.2c-3.38-1.46-2.46-5.71-2.46-5.71 0-.26.23-.32.42-.14l5.32 5-4.31-4.81a1.39 1.39 0 0 1 .81-1.22l4.17 6.65.33.31 2.19 1.54a2.44 2.44 0 0 1 .92 1.75v2.77l-.16.13a1.66 1.66 0 0 1-1.63.19l-.75-.36a2.57 2.57 0 0 0-2.55.32l-2.18 1.82a4.28 4.28 0 0 1-.89.55 10.18 10.18 0 0 0-4.62-8.46c-.27-.16-.66.31-.47.48a10.52 10.52 0 0 1 3.68 8.5v.48zm8.38-5.42a.49.49 0 1 0-.49-.49.49.49 0 0 0 .49.49zm-18 9.14v-.52a1.39 1.39 0 0 1 .93-1.25s2.7-.66 3.43-1.84l2.06 1.63a25.62 25.62 0 0 1-6.43 2z"/>
</svg>
PK
!<�^"�886chrome/toolkit/skin/classic/global/narrate/forward.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
  <path fill="context-fill" d="m 19,0 c 0.554,0 1,0.446 1,1 l 0,22 c 0,0.554 -0.446,1 -1,1 l -2,0 c -0.554,0 -1,-0.446 -1,-1 l 0,-10.595703 c -0.04108,0.105 -0.109944,0.205828 -0.203125,0.298828 L 4.703125,23.775391 c -0.198384,0.197 -0.364738,0.264171 -0.5,0.201171 C 4.067863,23.915564 4,23.748516 4,23.478516 L 4,0.52148438 c 0,-0.26900001 0.06786,-0.43700001 0.203125,-0.5 0.135262,-0.062 0.301616,0.0070781 0.5,0.20507812 l 11.09375,11.0722655 c 0.09318,0.083 0.162045,0.182875 0.203125,0.296875 L 16,1 c 0,-0.554 0.446,-1 1,-1 l 2,0 z"/>
</svg>
PK
!<R��GG?chrome/toolkit/skin/classic/global/narrate/headphone-active.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" fill="context-fill">
  <style>
    @keyframes wavy {
      from {
        translate: 0;
      }
      to {
        translate: -17.415px;
      }
    }
  </style>
  <clipPath id="clip">
    <rect width="10" height="16" x="3" y="0"/>
  </clipPath>
  <path d="M14.486 8.65V7.56a6.487 6.487 0 00-12.972 0v1.093A2.076 2.076 0 000 10.643v2.206c0 1.147.93 2.077 2.076 2.077h1.41a.567.567 0 00.566-.567V9.133a.566.566 0 00-.566-.566h-.84V7.56a5.354 5.354 0 1110.708 0v1.007h-.84a.567.567 0 00-.568.566v5.226c0 .313.254.567.568.567h1.408c1.148 0 2.078-.93 2.078-2.077v-2.207a2.078 2.078 0 00-1.514-1.991z"/>
  <g clip-path="url(#clip)">
    <path style="animation: wavy 2s infinite linear" d="M13.193 8.818a1.35 1.35 0 00-.838.293 1.747 1.747 0 00-.47.641c-.216.464-.314.979-.426 1.46-.113.485-.24.931-.4 1.194-.161.263-.257.352-.582.352-.252 0-.315-.05-.434-.194-.12-.142-.244-.41-.38-.726-.139-.317-.29-.687-.59-1.014a1.83 1.83 0 00-1.346-.57c-.576 0-1.036.377-1.282.775-.246.4-.378.84-.517 1.246-.14.406-.285.774-.442.989-.157.215-.25.289-.523.289-.111 0-.138-.017-.192-.065a.885.885 0 01-.197-.324c-.136-.34-.226-.906-.32-1.5A9.493 9.493 0 004 10.447v3.815c.016.016.026.035.043.05.238.21.574.34.92.34.614 0 1.119-.338 1.414-.742.293-.403.449-.86.592-1.28.144-.417.276-.802.412-1.023.136-.22.174-.253.346-.253.304 0 .404.07.535.212.13.143.26.4.392.707.134.31.272.67.545.997.273.327.737.587 1.278.587.683 0 1.23-.403 1.52-.878.29-.476.417-1.014.534-1.516.116-.502.222-.973.35-1.248a.737.737 0 01.172-.252c.043-.035.054-.045.138-.045.307 0 .412.103.596.457.183.354.326.927.467 1.531.142.605.281 1.241.576 1.793.294.552.86 1.045 1.602 1.045.842 0 1.46-.54 1.74-1.13.268-.565.325-1.168.33-1.663a.55.55 0 000-.002.55.55 0 00.006-.076c0-.403.073-.96.258-1.342.185-.38.374-.582.873-.582.333 0 .416.093.576.424.16.331.27.88.361 1.463.092.582.171 1.194.385 1.734.108.27.251.533.488.742.238.21.574.34.92.34.614 0 1.121-.338 1.416-.742.294-.403.448-.86.592-1.28.143-.417.276-.802.412-1.023.136-.22.174-.254.346-.254.304 0 .404.071.535.213.132.143.26.4.393.707.134.31.271.67.544.996.273.327.737.588 1.278.588.666 0 1.2-.385 1.496-.845V9.596c-.03.052-.063.103-.088.156-.216.464-.312.979-.424 1.46-.113.485-.241.931-.402 1.194-.16.263-.257.352-.582.352-.252 0-.315-.05-.434-.194-.12-.142-.243-.41-.38-.726-.138-.317-.291-.687-.59-1.014a1.83 1.83 0 00-1.346-.57c-.576 0-1.036.377-1.281.775-.247.4-.38.84-.518 1.246-.14.406-.282.774-.44.989-.156.215-.253.289-.525.289-.112 0-.137-.017-.191-.065a.885.885 0 01-.198-.324c-.136-.34-.226-.906-.32-1.5-.095-.594-.19-1.218-.457-1.77-.267-.55-.83-1.044-1.566-1.044-.905 0-1.558.576-1.862 1.203-.29.597-.357 1.235-.363 1.738a.55.55 0 000 .004.55.55 0 00-.006.078c0 .41-.066.926-.228 1.268-.162.34-.307.504-.748.504-.327 0-.443-.11-.631-.463-.19-.353-.336-.924-.477-1.526-.14-.602-.276-1.237-.56-1.787-.285-.55-.839-1.05-1.57-1.05z"/>
  </g>
</svg>
PK
!<����8chrome/toolkit/skin/classic/global/narrate/headphone.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" fill="context-fill">
  <path d="M14.486 8.65V7.56a6.487 6.487 0 00-12.972 0v1.093A2.076 2.076 0 000 10.643v2.206c0 1.147.93 2.077 2.076 2.077h1.41a.567.567 0 00.566-.567V9.133a.566.566 0 00-.566-.566h-.84V7.56a5.354 5.354 0 1110.708 0v1.007h-.84a.567.567 0 00-.568.566v5.226c0 .313.254.567.568.567h1.408c1.148 0 2.078-.93 2.078-2.077v-2.207a2.078 2.078 0 00-1.514-1.991z"/>
</svg>
PK
!<�����?chrome/toolkit/skin/classic/global/narrate/skip-backward-20.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20" fill="context-fill">
  <path d="M17 4.502v10.996a1.5 1.5 0 0 1-2.244 1.303l-9.621-5.498a1.514 1.514 0 0 1-.385-.312V17H3V3h1.75v6.01c.106-.12.234-.226.385-.312L14.756 3.2A1.5 1.5 0 0 1 17 4.502z"/>
</svg>
PK
!<�.��>chrome/toolkit/skin/classic/global/narrate/skip-forward-20.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20" fill="context-fill">
  <path d="M3 4.502v10.996A1.5 1.5 0 0 0 5.244 16.8l9.621-5.497c.15-.086.28-.192.385-.312V17H17V3h-1.75v6.01a1.513 1.513 0 0 0-.385-.312L5.244 3.2A1.5 1.5 0 0 0 3 4.502z"/>
</svg>
PK
!<��(�!!3chrome/toolkit/skin/classic/global/narrate/slow.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
    <g fill="context-fill">
        <path d="M1.684,13.486c-0.209,0-0.404-0.132-0.474-0.341c-0.528-1.58-0.23-5.767,4.097-7.921 c1.315-0.656,2.589-0.988,3.787-0.988c3.237,0,5.096,2.341,5.99,3.465c0.158,0.199,0.181,0.533,0,0.713 c-0.793,0.794-1.852,1.542-3.231,2.286c-2.46,1.327-5.045,1.775-7.121,2.134c-1.123,0.194-2.093,0.361-2.89,0.627 C1.789,13.479,1.735,13.486,1.684,13.486L1.684,13.486z"/>
        <path d="M23.185,5.465c-0.86-1.121-2.074-1.819-3.168-1.819c-0.641,0-1.556,0.23-2.273,1.328 c-0.374,0.571-0.577,1.161-0.773,1.73c-0.512,1.482-1.041,3.016-4.662,4.969c-2.316,1.249-4.707,1.664-6.815,2.03 c-2.524,0.438-4.704,0.814-5.455,2.622c-0.069,0.165-0.045,0.354,0.062,0.495c0.107,0.143,0.281,0.217,0.46,0.193 c0.667-0.081,1.533,0.041,2.434,0.217c-0.122,0.146-0.261,0.286-0.391,0.418c-0.38,0.385-0.774,0.783-0.657,1.292 c0.108,0.474,0.604,0.699,0.966,0.828c0.399,0.142,0.843,0.217,1.283,0.217c1.241,0,2.216-0.579,2.649-1.539 c1.704,0.287,3.487,0.313,5.043,0.313l1.639-0.006c0.066,0.056,0.178,0.166,0.264,0.25c0.504,0.506,1.348,1.351,2.721,1.351 c0.129,0,0.264-0.008,0.416-0.026c0.687-0.102,1.351-0.267,1.574-0.787c0.227-0.528-0.123-1.023-0.526-1.597 c-0.481-0.685-1.08-1.532-0.998-2.652c0.196-0.397,0.368-0.824,0.546-1.267c0.479-1.19,0.975-2.421,2.12-3.513 c0.431,0.343,1.022,0.549,1.63,0.549l0,0c0.439,0,0.876-0.102,1.295-0.3c0.624-0.293,1.104-0.967,1.316-1.847 C24.175,7.707,23.914,6.418,23.185,5.465L23.185,5.465z M20.397,7.757c-0.276,0-0.5-0.224-0.5-0.5s0.224-0.5,0.5-0.5 c0.275,0,0.5,0.224,0.5,0.5S20.674,7.757,20.397,7.757z"/>
    </g>
</svg>
PK
!<}����4chrome/toolkit/skin/classic/global/narrate/start.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
    <path d="M21.64 12.44L2.827 22.895c-.217.123-.403.137-.56.042-.155-.094-.233-.264-.233-.51V1.572c0-.244.08-.414.233-.51.157-.093.343-.08.56.044L21.642 11.56c.217.124.326.27.326.44 0 .17-.11.316-.327.44z" fill="context-fill"/>
</svg>
PK
!<�kb�ii3chrome/toolkit/skin/classic/global/narrate/stop.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
    <rect ry="1" rx="1" y="2" x="2" height="20" width="20" fill="context-fill"/>
</svg>
PK
!<���7chrome/toolkit/skin/classic/global/narrate-improved.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* Avoid adding ID selector rules in this style sheet, since they could
 * inadvertently match elements in the article content. */

body {
  --narrate-word-highlight-color: #ffe087;
  --narrating-paragraph-background-color: #ffc;
}

body.sepia {
  --narrate-word-highlight-color: #bdb5a5;
  --narrating-paragraph-background-color: #e0d7c5;
}

body.dark,
body.contrast {
  --narrate-word-highlight-color: #6f6f6f;
  --narrating-paragraph-background-color: #242424;
}

body.custom {
  --narrating-paragraph-background-color: var(--custom-theme-selection-highlight);
}

body.hcm {
  --narrate-word-highlight-color: SelectedItem;
  --narrating-paragraph-background-color: Canvas;
}

.narrating {
  position: relative;
  z-index: 1;
  background-color: var(--narrating-paragraph-background-color);
}

.narrate-word-highlight {
  position: absolute;
  display: none;
  transform: translate(-50%, calc(-50% - 2px));
  z-index: -1;
  border-bottom: 7px solid var(--narrate-word-highlight-color);
  transition: left 0.1s ease, width 0.1s ease;
}

body.hcm .narrate-word-highlight {
  /* Shift the word highlight a bit downwards to not cover the bottom part of characters.
   * The z-index above is meant to have the highlight appear below the text,
   * but that's not best practice in HCM so we do this instead. */
  transform: translate(-50%, calc(-50% + 2px));
  border-bottom-width: 3px;
}

.narrating > .narrate-word-highlight {
  display: inline-block;
}

.narrate-word-highlight.newline {
  transition: none;
}

.narrate-toggle {
  background-image: url("chrome://global/skin/narrate/headphone.svg");

  .speaking & {
    /* This shows with an animation. */
    background-image: url("chrome://global/skin/narrate/headphone-active.svg");
    fill: var(--toolbar-button-foreground-active);
  }

  body.hcm .speaking & {
    background-color: var(--toolbar-button-background-active);
    border-color: var(--toolbar-button-border-active);
  }

  body.hcm .speaking:not(.open) &:hover {
    background-color: var(--toolbar-button-background-hover);
    border-color: var(--toolbar-button-border-hover);
    fill: var(--toolbar-button-foreground-hover);
  }
}

.narrate-dropdown > .dropdown-popup {
  padding: var(--space-small) var(--space-large);

  button:enabled:hover {
    background-color: var(--popup-button-background-hover);
    color: var(--popup-button-foreground-hover);
    fill: var(--popup-button-foreground-hover);

    body.hcm & {
      border-color: SelectedItem;
    }
  }

  button:enabled:hover:active {
    background-color: var(--popup-button-background-active);

    body.hcm & {
      border-color: ButtonText;
    }
  }

  button:enabled:focus-visible {
    outline: 2px solid var(--outline-focus-color);
    outline-offset: -2px;
  }
}

.narrate-row {
  display: flex;
  align-items: center;
  padding-block: var(--space-small);

  &:not(.narrate-voices) {
    direction: ltr;
  }
}

/* Control buttons */

.narrate-control {
  gap: var(--space-xxsmall);

  button {
    display: flex;
    flex: 1;
    min-height: 36px;
    background-repeat: no-repeat;
    background-position: center;
    background-size: 20px 20px;
    background-color: var(--popup-button-background);
    color: var(--popup-button-foreground);
    fill: currentColor;
    -moz-context-properties: fill;
    border: none;

    &:first-of-type {
      border-start-start-radius: var(--border-radius-small);
      border-end-start-radius: var(--border-radius-small);
    }

    &:last-of-type {
      border-start-end-radius: var(--border-radius-small);
      border-end-end-radius: var(--border-radius-small);
    }

    &.narrate-skip-previous {
      background-image: url("chrome://global/skin/narrate/skip-backward-20.svg");
    }

    &.narrate-skip-next {
      background-image: url("chrome://global/skin/narrate/skip-forward-20.svg");
    }

    &.narrate-start-stop {
      background-image: url("chrome://global/skin/media/play-fill.svg");

      .narrate-dropdown.speaking & {
        background-image: url("chrome://global/skin/media/pause-fill.svg");
      }
    }

    body.hcm & {
      border: 1px solid var(--popup-button-border);

      &:disabled {
        border-color: var(--icon-disabled-fill);
      }
    }
  }
}

/* Rate control */

.narrate-rate-icon {
  content: '';
  width: 48px;
  height: 40px;
  background-position: center;
  background-repeat: no-repeat;
  background-size: 24px auto;
  -moz-context-properties: fill;
  fill: var(--popup-foreground);

  &.slow {
    background-image: url("chrome://global/skin/narrate/slow.svg");
  }

  &.fast {
    background-image: url("chrome://global/skin/narrate/fast.svg");
  }
}

.narrate-rate-input {
  width: 200px;
}

/* Voice selection */

.voiceselect {
  width: 100%;

  > button.select-toggle,
  > .options > button.option {
    appearance: none;
    width: 100%;
    min-height: 36px;
    border: 1px solid transparent;
    color: var(--popup-button-foreground);
    font-size: 15px;

    body.hcm & {
      border-color: var(--popup-button-border);
    }
  }

  > button.select-toggle {
    display: flex;
    align-items: center;
    padding-inline: var(--space-medium);
    border-radius: var(--border-radius-small);
    background-color: var(--popup-button-background);
    font-weight: var(--font-weight-bold);
    -moz-context-properties: fill;
    fill: currentColor;

    .current-voice {
      display: flex;
      width: 100%;
      padding-inline: var(--space-small);
    }
  }

  > button.select-toggle::before,
  > button.select-toggle::after {
    content: "";
    width: 24px;
    height: var(--icon-size-default);
    background-repeat: no-repeat;
    background-size: var(--icon-size-default) auto;
  }

  > button.select-toggle::before {
    background-image: url("chrome://global/skin/media/audio.svg");
  }

  > button.select-toggle::after {
    background-image: url("chrome://global/skin/icons/arrow-down.svg");
  }

  &.open > button.select-toggle::after {
    background-image: url("chrome://global/skin/icons/arrow-up.svg");
  }

  > .options {
    display: none;
    overflow-y: auto;

    > button.option {
      background-color: transparent;
      text-align: start;
      padding-inline-start: var(--space-medium);
    }

    > button.option::-moz-focus-inner {
      outline: none;
      border: 0;
    }

    &:not(.hovering) > button.option:hover:not(:focus) {
      background-color: transparent;
    }
  }

  &.open > .options {
    display: block;
  }
}
PK
!<d	a��.chrome/toolkit/skin/classic/global/narrate.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* Avoid adding ID selector rules in this style sheet, since they could
 * inadvertently match elements in the article content. */

body {
  --current-voice: #7f7f7f;
  --narrate-word-highlight-color: #ffe087;
  --narrating-paragraph-background-color: #ffc;
}

body.sepia {
  --narrate-word-highlight-color: #bdb5a5;
  --narrating-paragraph-background-color: #e0d7c5;
}

body.dark,
body.contrast {
  --current-voice: #a09eac;
  --narrate-word-highlight-color: #6f6f6f;
  --narrating-paragraph-background-color: #242424;
}

body.custom {
  --narrating-paragraph-background-color: var(--custom-theme-selection-highlight);
}

body.hcm {
  --current-voice: inherit;
  --narrate-word-highlight-color: SelectedItem;
  --narrating-paragraph-background-color: Canvas;
}

.narrating {
  position: relative;
  z-index: 1;
  background-color: var(--narrating-paragraph-background-color);
}

.narrate-word-highlight {
  position: absolute;
  display: none;
  transform: translate(-50%, calc(-50% - 2px));
  z-index: -1;
  border-bottom: 7px solid var(--narrate-word-highlight-color);
  transition: left 0.1s ease, width 0.1s ease;
}

body.hcm .narrate-word-highlight {
  /* Shift the word highlight a bit downwards to not cover the bottom part of characters.
   * The z-index above is meant to have the highlight appear below the text,
   * but that's not best practice in HCM so we do this instead. */
  transform: translate(-50%, calc(-50% + 2px));
  border-bottom-width: 3px;
}

.narrating > .narrate-word-highlight {
  display: inline-block;
}

.narrate-word-highlight.newline {
  transition: none;
}

.narrate-toggle {
  background-image: url("chrome://global/skin/narrate/headphone.svg");
}

.speaking .narrate-toggle {
  /* This shows with an animation. */
  background-image: url("chrome://global/skin/narrate/headphone-active.svg");
  fill: var(--toolbar-button-foreground-active);
}

body.hcm .speaking .narrate-toggle {
  background-color: var(--toolbar-button-background-active);
  border-color: var(--toolbar-button-border-active);
}

body.hcm .speaking:not(.open) .narrate-toggle:hover {
  background-color: var(--toolbar-button-background-hover);
  border-color: var(--toolbar-button-border-hover);
  fill: var(--toolbar-button-foreground-hover);
}

.narrate-dropdown > .dropdown-popup button {
  background-color: transparent;
  fill: var(--popup-button-foreground);
}

.narrate-dropdown > .dropdown-popup button:enabled:hover {
  background-color: var(--popup-button-background-hover);
  color: var(--popup-button-foreground-hover);
  fill: var(--popup-button-foreground-hover);
}

.narrate-dropdown > .dropdown-popup button:enabled:hover:active {
  background-color: var(--popup-button-background-active);
}

.narrate-dropdown > .dropdown-popup button:enabled:focus-visible {
  outline: 2px solid var(--outline-focus-color);
  outline-offset: -2px;
}

.narrate-row {
  display: flex;
  align-items: center;
  min-height: 40px;
  box-sizing: border-box;
}

.narrate-row:not(.narrate-voices) {
  direction: ltr;
}

.narrate-row:not(:first-child) {
  border-top: 1px solid var(--popup-line);
}

/* Control buttons */

.narrate-control > button {
  background-size: 24px 24px;
  background-repeat: no-repeat;
  background-position: center center;
  height: 64px;
  width: 82px;
  border: none;
  box-sizing: border-box;
}

.narrate-control > button:not(:first-child) {
  border-inline-start: 1px solid var(--popup-line);
}

.narrate-skip-previous {
  border-top-left-radius: 3px;
  background-image: url("chrome://global/skin/narrate/back.svg");
}

.narrate-skip-next {
  border-top-right-radius: 3px;
  background-image: url("chrome://global/skin/narrate/forward.svg");
}

.narrate-start-stop {
  background-image: url("chrome://global/skin/narrate/start.svg");
}

.narrate-dropdown.speaking .narrate-start-stop {
  background-image: url("chrome://global/skin/narrate/stop.svg");
}

/* Rate control */

.narrate-rate::before,
.narrate-rate::after {
  content: '';
  width: 48px;
  height: 40px;
  background-position: center;
  background-repeat: no-repeat;
  background-size: 24px auto;
  -moz-context-properties: fill;
  fill: var(--popup-button-foreground);
}

.narrate-rate::before {
  background-image: url("chrome://global/skin/narrate/slow.svg");
}

.narrate-rate::after {
  background-image: url("chrome://global/skin/narrate/fast.svg");
}

.narrate-rate-input {
  margin: 0 1px;
  flex-grow: 1;
  background-color: var(--popup-background);
  border-radius: 2px;
  width: 148px;
}

.narrate-rate-input:focus-visible {
  outline: 2px solid var(--outline-focus-color);
  outline-offset: 2px;
}

.narrate-rate-input::-moz-range-track {
  background-color: var(--popup-button-foreground);
  height: 2px;
}

.narrate-rate-input::-moz-range-progress {
  background-color: var(--primary-color);
  height: 2px;
}

.narrate-rate-input::-moz-range-thumb {
  background-color: var(--popup-button-foreground);
  height: 16px;
  width: 16px;
  border-radius: 8px;
  border-width: 0;
}

.narrate-rate-input:active::-moz-range-thumb {
  background-color: var(--primary-color);
}

/* Voice selection */

.voiceselect {
  width: 246px;
}

.voiceselect > button.select-toggle,
.voiceselect > .options > button.option {
  appearance: none;
  border: none;
  width: 100%;
  min-height: 40px;
}

.voiceselect > button.select-toggle::after {
  content: '';
  background-image: url("chrome://global/skin/narrate/arrow.svg");
  background-position: center;
  background-repeat: no-repeat;
  background-size: 12px 12px;
  display: inline-block;
  width: 1.5em;
  height: 1em;
  vertical-align: middle;
}

.current-voice {
  color: var(--current-voice);
}

.voiceselect .label {
  color: var(--popup-button-foreground);
}

.voiceselect > .options {
  display: none;
  overflow-y: auto;
}

.voiceselect.open > .options {
  display: block;
  border-top: 1px solid var(--popup-line);
}

.voiceselect > .options > button.option {
  box-sizing: border-box;
  color: var(--popup-button-foreground);
}

.voiceselect > .options > button.option:not(:first-child) {
  border-top: 1px solid var(--popup-line);
}

.voiceselect > .options > button.option::-moz-focus-inner {
  outline: none;
  border: 0;
}

.voiceselect > .options:not(.hovering) > button.option:hover:not(:focus) {
  background-color: transparent;
}

.voiceselect:not(.open) > button,
.voiceselect .option:last-child {
  border-radius: 0 0 3px 3px;
}
PK
!<�J4��:chrome/toolkit/skin/classic/global/offlineSupportPages.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/in-content/info-pages.css");

#offlineSupportContainer {
  width: 100%;
  max-width: 700px;
  font-size: 1.14em;
  line-height: 1.6;
}

#offlineSupportContainer h1 {
  margin-top: 1.4em;
}

#toc .tocnumber {
  display: none;
}
PK
!<�3�9�9>chrome/toolkit/skin/classic/global/pictureinpicture/player.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:root {
  --player-bg-color: #000;
  --player-control-icon-fill: #fff;
  --player-control-item-half-width: clamp(calc(16px / 2), calc(10vmax / 2), calc(32px / 2));
  --player-control-item-height: clamp(16px, 10vmax, 32px);
  --close-btn-fill-color: #000;
  --controls-bottom-distance: 15px;
  --controls-bottom-upper-height: 30px;
  --scrubber-vertical-margin: 7px;
  --resize-margin: 5px;

  background-color: var(--player-bg-color);
  overflow: hidden;
}

button::-moz-focus-inner {
  border: 0;
}

body {
  margin: 0;
}

body:fullscreen {
  -moz-window-dragging: no-drag;
}

.player-holder {
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow: hidden;
}

.seethrough-mode {
  background: transparent;

  .player-holder {
    will-change: opacity, filter;
    transition: opacity 160ms linear, filter 160ms linear;

    body:hover:not(:fullscreen) & {
      opacity: 0.35;
      filter: blur(8px);
    }
  }
}

browser {
  flex: 1;
}

#controls {
  height: calc(100% - 2 * var(--resize-margin));
  left: 0;
  position: absolute;
  top: 0;
  width: calc(100% - 2 * var(--resize-margin));
  margin: var(--resize-margin);
  -moz-window-dragging: drag;
}

#controls button {
  appearance: none;
  border: 0;
  z-index: 1;
}

#controls button:focus-visible,
#controls input:focus-visible,
.switch > input:focus-visible + .slider {
  outline: 3px solid #0060DF;
  box-shadow: 1px 2px 5px #000;
}

/* Styling for background gradient.
 * Opacity changes are handled separately under .control-item.
 */
#controls-bottom-gradient {
  background: linear-gradient(0deg, #000000 -13.24%, rgba(0, 0, 0, 0) 90.44%);
  position: absolute;
  height: calc(var(--controls-bottom-distance) + 2 * var(--resize-margin) + var(--player-control-item-height) + var(--controls-bottom-upper-height) + var(--scrubber-vertical-margin));
  bottom: 0;
  width: 100vw;
  margin: 0 calc(-1 * var(--resize-margin)) calc(-1 * var(--resize-margin)) calc(-1 * var(--resize-margin));
  pointer-events: none;
  -moz-window-dragging: drag;
}

#controls-bottom {
  position: absolute;
  bottom: var(--controls-bottom-distance);
  width: 100%;
}

.controls-bottom-lower {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  margin: 0 24px;
}

.start-controls {
  display: grid;
  justify-self: start;
}

.center-controls {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-areas: "seekbackward playpause seekforward";
  justify-self: center;
  gap: 8px;
}

.end-controls {
  display: grid;
  grid-template-columns: 1fr 2fr 1fr 1fr;
  grid-template-areas: "audio audio-scrubber closedcaption fullscreen";
  justify-self: end;
  gap: 8px;
}

.control-item {
  -moz-window-dragging: no-drag;
  transition: opacity 160ms linear, fill-opacity 160ms linear;
  opacity: 0;
  cursor: pointer;
}

.control-button {
  background-color: transparent;
  border-radius: 4px;
  /**
   * Make the button dimensions a square proportional to one
   * dimension of the window - in this case, the width dimension,
   * since we suspect most videos are wider than they are tall.
   */
  height: var(--player-control-item-height);
  width: 10vmax;
  max-width: 32px;
  min-width: 16px;
  -moz-context-properties: fill, fill-opacity;
  fill: var(--player-control-icon-fill);
  background-position: center;
  background-size: 60%;
  background-repeat: no-repeat;
}

.control-button:hover:enabled {
  background-color: rgba(255, 255, 255, .25);
}

#controls:is([keying], [showing]) .control-button:disabled,
/* Only change opacity on hover events for non-fullscreen mode.
 * Fullscreen mode uses the `showing` attribute instead. */
body:not(:fullscreen) #controls:hover .control-button:disabled {
  /* Set `fill-opacity` to the desired opacity in addition to full `opacity`
   * to allow having the button's tooltip in full opacity even if the button is disabled. */
  fill-opacity: 0.4;
  opacity: 1 !important;
}

.control-item:focus-visible::after,
.control-item:hover::after {
  content: attr(tooltip);
  display: inline-block;
  width: max-content;
  position: relative;
  padding: .4em .5em;
  background: #000000;
  color: #ffffff;
  border-radius: 4px;
  pointer-events: none;
}

/* Since #controls is set to LTR, button tooltips would normally appear
 * as LTR also for RTL locales. To fix this, set the .control-item's ::after
 * to RTL based on the root directionality.
 * Because of that, don't set logical properties for the next set of rules. */
:root:dir(rtl) .control-item::after {
  direction: rtl;
}

/* Set the tooltip position for different playback controls */

.tooltip-under-controls:focus-visible::after,
.tooltip-under-controls:hover::after {
  bottom: -3em;
}

#close:focus-visible::after,
#close:hover::after,
#unpip[mac="true"]:focus-visible::after,
#unpip[mac="true"]:hover::after {
  float: right;
  transform: translateX(1em);
}

#unpip:focus-visible::after,
#unpip:hover::after,
#close[mac="true"]:focus-visible::after,
#close[mac="true"]:hover::after {
  float: left;
  transform: translateX(-1em);
}

.tooltip-over-controls:focus-visible::after,
.tooltip-over-controls:hover::after {
  bottom: 3em;
}

.inline-end-tooltip:focus-visible::after,
.inline-end-tooltip:hover::after {
  float: right;
  right: -1em;
}

.inline-start-tooltip:focus-visible::after,
.inline-start-tooltip:hover::after {
  float: left;
  left: -1em;
}

.center-tooltip:focus-visible::after,
.center-tooltip:hover::after {
  right: 0.8em;
  translate: calc(-50% + var(--player-control-item-half-width));
}

/* Since the unpip button icon is reversed for RTL locales,
 * re-position the tooltip so that the tooltip remains in the original placement */
:root:dir(rtl) #unpip:focus-visible::after,
:root:dir(rtl) #unpip:hover::after {
  float: right;
}

:root:dir(rtl) #unpip[mac="true"]:focus-visible::after,
:root:dir(rtl) #unpip[mac="true"]:hover::after {
  float: left;
}

/* Since the unpip icon is reversed for RTL locales,
 * flip its tooltip back */
:root:dir(rtl) #unpip:focus-visible::after,
:root:dir(rtl) #unpip:hover::after {
  scale: -1 1;
}

/* Set opacity for buttons and scrubber when controls are visible on the pip window and are not hovered over.
 * For fullscreen mode, only apply opacity if there is a showing attribute. */
body:not(:fullscreen) #controls:hover .control-item:not(:hover),
body:fullscreen #controls[showing]:hover .control-item:not(:hover),
#controls[donthide] .control-item {
  opacity: 0.8;
}

#controls[keying] .control-item,
#controls[showing] .control-item,
.control-item:hover {
  opacity: 1;
}

/* Background gradient is the only control item with a fixed opacity value. */
#controls[keying] #controls-bottom-gradient,
#controls[showing] #controls-bottom-gradient,
#controls-bottom-gradient:hover {
  opacity: 0.8;
}

/* For readability, timestamp should maintain full opacity when visible */
body:not(:fullscreen) #controls:hover #timestamp,
body:fullscreen #controls[showing]:hover {
  opacity: 1;
}

#close,
#unpip {
  background-color: rgba(255, 255, 255, .8);
  position: absolute;
  fill: var(--close-btn-fill-color);
}

#close:is(:hover, :focus-visible),
#unpip:is(:hover, :focus-visible) {
  background-color: rgba(255, 255, 255, .9);
}

#close {
  background-image: url("chrome://global/skin/icons/close.svg");
  right: 10px;
  top: 10px;
}

#close[mac="true"] {
  right: auto;
  left: 10px;
}

#unpip {
  background-image: url("chrome://global/skin/media/picture-in-picture-closed.svg");
  left: 10px;
  top: 10px;
}

#unpip[mac="true"] {
  right: 10px;
  left: auto;
}

#playpause {
  grid-area: playpause;
}

#audio {
  grid-area: audio;
}

#audio-scrubber {
  grid-area: audio-scrubber;
  align-self: center;
  width: 64px;
  background-color: transparent;
  padding: 6px 2px;
  margin: 0;

  &::-moz-range-thumb {
    border-radius: 8px;
    background-color: #FFFFFF;
    width: 8px;
    height: 8px;
    padding: 0;
  }

  &::-moz-range-track {
    background-color: #969696;
  }

  &::-moz-range-progress {
    background-color: #FFFFFF;
  }

  &,
  &::-moz-range-track,
  &::-moz-range-progress {
    height: 2px;
    border-radius: 10px;
  }
}

#fullscreen {
  grid-area: fullscreen;
}

#controls.playing #playpause {
  background-image: url("chrome://global/skin/media/pause-fill.svg");
}

#controls:not(.playing) #playpause {
  background-image: url("chrome://global/skin/media/play-fill.svg");
}

#controls.muted #audio {
  background-image: url("chrome://global/skin/media/audio-muted.svg");
}

#controls:not(.muted) #audio {
  background-image: url("chrome://global/skin/media/audio.svg");
}

body:fullscreen #fullscreen {
  background-image: url("chrome://global/skin/media/picture-in-picture-exit-fullscreen-button.svg");
  background-size: auto;
}

body:not(:fullscreen) #fullscreen {
  background-image: url("chrome://global/skin/media/picture-in-picture-enter-fullscreen-button.svg");
  background-size: auto;
}

#seekBackward {
  background-image: url("chrome://global/skin/media/picture-in-picture-seekBackward-button.svg");
  background-size: auto;
  grid-area: seekbackward;
}

#seekForward {
  background-image: url("chrome://global/skin/media/picture-in-picture-seekForward-button.svg");
  background-size: auto;
  grid-area: seekforward;
}

:root:dir(rtl) #unpip {
  transform: scaleX(-1);
}

#closed-caption {
  background-image: url("chrome://global/skin/media/closed-caption-settings-button.svg");
  color: white;
  grid-area: closedcaption;
}

.box {
  -moz-window-dragging: no-drag;
  background-color: #2B2A33;
  text-align: start;
  font-size: 1em;
  width: 186px;
  padding: 0 8px;
  margin: 0;
  border-radius: 8px;
}

:root:dir(rtl) .box {
  direction: rtl;
}

.a11y-only {
  visibility: hidden;
  position: absolute;
}

.hide {
  display: none;
}

.bold {
  font-weight: 590;
}

.box > input[type="radio"] {
  background-color: red;
  fill: currentColor;
}

.box label:not(.switch) {
  color: white;
  font-family: sans-serif;
}

#subtitles-toggle-label {
  width: fit-content;
  padding: 8px;
}

.panel {
  position: absolute;
  bottom: 40px;
  user-select: none;
  right: 24px;
}

.panel-fieldset {
  border: none;
  margin-top: 8px;
  padding-inline-start: 0;
}

.panel-legend {
  font-family: sans-serif;
  color: white;
  margin-top: 8px;
  padding-inline-start: 0;
}

.arrow {
  border: 10px solid transparent;
  border-top-color: #2B2A33;
  width: 0;
  height: 0;
  inset-inline-start: 136px;
  position: relative;
}

.grey-line {
  width: 100%;
  height: 1px;
  background: #8F8F9D;
}

.font-size-selection {
  margin-inline-start: 8px;
  padding-inline-start: 8px;
}

.font-size-selection-radio {
  display: flex;
  width: fit-content;
  cursor: pointer;
  padding-block: 8px;
}

.font-size-selection-radio label {
  cursor: pointer;
}

.font-size-selection-radio > input[type="radio"] {
  appearance: none;
  width: 16px;
  height: 16px;
  border: 1px solid #8f8f9d;
  border-radius: 50%;
  margin: 0;
  margin-inline-end: 6px;
}

.font-size-selection-radio > input[type="radio"]:checked {
  border: 4px solid #00ddff;
}

.subtitle-grid {
  display: grid;
  grid-template-rows: auto;
  grid-template-columns: auto 46px;
  padding: 8px;
}

.switch {
  position: relative;
  display: inline-block;
  width: 32px;
  height: 16px;
  grid-column: 2;
  margin: 8px;
  cursor: pointer;
}

.switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

.slider {
  position: absolute;
  inset: 0;
  transition: transform 250ms;
  border-radius: 13px;
  background-color: #55545f;
}

.slider::before {
  position: absolute;
  content: '';
  height: 12px;
  width: 12px;
  inset-inline-start: 2px;
  bottom: 2px;
  background-color: #2B2A33;
  transition: transform 250ms;
  border-radius: 50%;
}

input:checked + .slider {
  background-color: #00ddff;
}

input:checked + .slider::before {
  transform: translateX(16px);
}

:root:dir(rtl) input:checked + .slider::before {
  transform: translateX(-16px);
}

.font-size-overlay {
  opacity: 0.4;
  pointer-events: none;
}

.controls-bottom-upper {
  width: calc(100% - 38px);
  height: var(--controls-bottom-upper-height);
  margin: 0 19px;
  display: grid;
}

.scrubber-no-drag {
  -moz-window-dragging: no-drag;
  height: 16px;
  margin: var(--scrubber-vertical-margin) 0;
  display: grid;
  justify-items: center;
  align-items: center;
  width: 100%;
}

#scrubber {
  width: 100%;
  background-color: transparent;
  padding: 6px 2px;

  &::-moz-range-thumb {
    border-radius: 14px;
    background-color: #BFBFC9;
    width: 8px;
    height: 8px;
    border: 3px solid #FFFFFF;
    padding: 0;
  }

  &::-moz-range-track {
    background-color: #969696;
  }

  &::-moz-range-progress {
    background-color: #FFFFFF;
  }

  &,
  &::-moz-range-track,
  &::-moz-range-progress {
    height: 4px;
    border-radius: 10px;
  }
}

#timestamp {
  align-self: center;
  color: #FFFFFF;
  cursor: default;
  font-family: system-ui;
  font-size: 0.9em;
  font-variant-numeric: tabular-nums;
  user-select: none;
  width: 16ch;
  grid-area: timestamp;
}

#timestamp::after {
  background: unset;
  content: unset;
}

@media (width <= 630px) {
  #audio-scrubber {
    display: none;
  }

  .end-controls {
    grid-template-columns: repeat(3, 1fr);
    grid-template-areas: "audio closedcaption fullscreen";
  }
}

@media (width <= 475px) {
  #closed-caption {
    display: none;
  }

  .end-controls {
    grid-template-columns: repeat(2, 1fr);
    grid-template-areas: "audio fullscreen";
  }
}

@media (height <= 325px) and (width > 630px) {
  #closed-caption {
    display: none;
  }

  .end-controls {
    grid-template-columns: 1fr 2fr 1fr;
    grid-template-areas: "audio audio-scrubber fullscreen";
  }
}

@media (height <= 325px) and (width <= 630px) {
  #closed-caption,
  #audio-scrubber {
    display: none;
  }

  .end-controls {
    grid-template-columns: repeat(2, 1fr);
    grid-template-areas: "audio fullscreen";
  }
}

@media (width <= 440px) {
  #timestamp {
    display: none;
  }
}

@media (width <= 350px) {
  #fullscreen {
    display: none;
  }

  .end-controls {
    grid-template-columns: repeat(1, 1fr);
    grid-template-areas: "audio";
  }
}

@media (height <= 200px) {
  .scrubber-no-drag {
    display: none;
  }
}

@media (width <= 300px) {
  .scrubber-no-drag,
  #seekForward,
  #seekBackward,
  .start-controls {
    display: none;
  }

  .controls-bottom-lower {
    grid-template-columns: repeat(2, 1fr);
  }

  .center-controls {
    grid-template-columns: repeat(1, 1fr);
    grid-template-areas: "playpause";
  }

  .end-controls {
    justify-self: center;
  }
}
PK
!<�(I�E
E
Bchrome/toolkit/skin/classic/global/pictureinpicture/texttracks.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:root {
    --min-font-size: 14px;
    --max-font-size: 40px;
    --font-scale: 0.06;
    --texttracks-bottom: calc(var(--font-scale) * 100vh);

    /*
     * Move text tracks if they visually overlap with pip player controls
     * New text tracks position is determined by adding together:
     *  1) bottom distance of #controls
     *  2) height of #controls-bottom,
     *  3) border size of player controls buttons
     *  4) hardcoded px value (to ensure consistent distance, regardless of pip window size)
     * NOTE: if changing player.css values, change values here too.
     */
    --resize-margin: 5px;
    --player-controls-height: calc(100vh - 2 * var(--resize-margin));
    --player-controls-bottom-distance: calc(100vh - var(--player-controls-height));
    --player-controls-button-height: 10vmax;
    --player-controls-button-max-height: 32px;
    --player-controls-button-min-height: 16px;
    --player-controls-button-outline-width: 2px;
    --player-controls-scrubber-height: 0px;
    --player-bottom-controls-height: calc(var(--player-controls-scrubber-height) + clamp(var(--player-controls-button-min-height), var(--player-controls-button-height), var(--player-controls-button-max-height)));
    --distance-from-player-controls: 20px;
    --texttracks-bottom-overlapped: calc(var(--player-controls-button-outline-width) + var(--player-controls-bottom-distance) + var(--player-bottom-controls-height) + var(--distance-from-player-controls));
}

#texttracks {
    background-color: black;
    opacity: 80%;
    position: absolute;
    text-align: center;
    box-sizing: border-box;
    color: white;
    margin: 0;
    display: block;
    left: 50%;
    transform: translateX(-50%);
    padding: 8px;
    max-width: calc(0.88 * 100vw);
    bottom: var(--texttracks-bottom);
    font-size: clamp(var(--min-font-size), calc(var(--font-scale) * 100vh), var(--max-font-size));
    line-height: clamp(14.4px, calc(var(--font-scale) * 1.2 * 100vh), 48px);
    white-space: pre-line;
    width: max-content;
    font-family: sans-serif;
    transition: bottom 0.3s;
    transition-delay: 0.1s;
}

#texttracks[overlap-video-controls] {
    bottom: var(--texttracks-bottom-overlapped)
}

#texttracks:empty {
    visibility: hidden;
}

@media (prefers-reduced-motion) {
    #texttracks {
        transition: none;
    }
}

@media screen and (max-width: 319px) {
    #texttracks {
        display: none;
    }
}
PK
!<x
���Jchrome/toolkit/skin/classic/global/reader/RM-Content-Width-Minus-42x16.svg<?xml version="1.0" encoding="utf-8"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<svg xmlns="http://www.w3.org/2000/svg"
     width="42"
     height="16"
     viewBox="0 0 42 16"
     fill="context-fill">

  <path d="M14.5,7 L8.75,1.25 L10,-1.91791433e-15 L18,8 L17.375,8.625 L10,16 L8.75,14.75 L14.5,9 L1.13686838e-13,9 L1.13686838e-13,7 L14.5,7 Z"/>
  <path d="M38.5,7 L32.75,1.25 L34,6.58831647e-15 L42,8 L41.375,8.625 L34,16 L32.75,14.75 L38.5,9 L24,9 L24,7 L38.5,7 Z" transform="translate(33.000000, 8.000000) scale(-1, 1) translate(-33.000000, -8.000000)"/>

</svg>
PK
!<���8��Ichrome/toolkit/skin/classic/global/reader/RM-Content-Width-Plus-44x16.svg<?xml version="1.0" encoding="utf-8"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<svg xmlns="http://www.w3.org/2000/svg"
     width="44"
     height="16"
     viewBox="0 0 44 16"
     fill="context-fill">

  <path d="M14.5,7 L8.75,1.25 L10,-1.91791433e-15 L18,8 L17.375,8.625 L10,16 L8.75,14.75 L14.5,9 L1.13686838e-13,9 L1.13686838e-13,7 L14.5,7 Z" transform="translate(9.000000, 8.000000) scale(-1, 1) translate(-9.000000, -8.000000)"/>
  <path d="M40.5,7 L34.75,1.25 L36,-5.17110888e-16 L44,8 L43.375,8.625 L36,16 L34.75,14.75 L40.5,9 L26,9 L26,7 L40.5,7 Z"/>

</svg>
PK
!<(dPPHchrome/toolkit/skin/classic/global/reader/RM-Line-Height-Minus-38x14.svg<?xml version="1.0" encoding="utf-8"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->


<svg xmlns="http://www.w3.org/2000/svg"
     width="38"
     height="14"
     viewBox="0 0 38 14"
     fill="context-fill">

  <rect x="0" y="0" width="28" height="2"/>
  <rect x="0" y="6" width="38" height="2"/>
  <rect x="0" y="12" width="18" height="2"/>

</svg>
PK
!<ҙ8�Gchrome/toolkit/skin/classic/global/reader/RM-Line-Height-Plus-38x24.svg<?xml version="1.0" encoding="utf-8"?>

<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<svg xmlns="http://www.w3.org/2000/svg"
     width="38"
     height="24"
     viewBox="0 0 38 24"
     fill="context-fill">

  <rect x="0" y="0" width="28" height="2"/>
  <rect x="0" y="11" width="38" height="2"/>
  <rect x="0" y="22" width="18" height="2"/>

</svg>
PK
!<{��SS<chrome/toolkit/skin/classic/global/reader/RM-Minus-24x24.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
  <path fill="context-fill" d="M0,13.5v-3h24v3H0z"/>
</svg>
PK
!<L}��uu;chrome/toolkit/skin/classic/global/reader/RM-Plus-24x24.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
  <path fill="context-fill" d="M24,13.5H13.5V24h-3V13.5H0v-3h10.5V0h3v10.5H24V13.5z"/>
</svg>
PK
!<�G�>��;chrome/toolkit/skin/classic/global/reader/RM-Sans-Serif.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg
   xmlns="http://www.w3.org/2000/svg"
   width="21"
   height="13"
   fill="context-fill">
  <path d="M 7.276027,7.16854 5.432216,1.80247 3.47265,7.16854 Z M 4.572322,0 H 6.43267 l 4.406957,12.146004 H 9.037157 L 7.805193,8.507991 H 3.001361 L 1.686716,12.146004 H 0 Z"/>
  <path
     d="m 13.295286,9.789564 q 0,0.64492 0.471288,1.01699 0.471288,0.372069 1.116209,0.372069 0.78548,0 1.521351,-0.363801 1.240232,-0.603579 1.240232,-1.976103 V 7.639828 Q 17.371515,7.813461 16.941568,7.929216 16.511621,8.044971 16.09821,8.09458 L 15.196975,8.210335 Q 14.38669,8.317822 13.981548,8.549332 13.295286,8.937938 13.295286,9.789564 Z m 3.604941,-3.00963 q 0.512629,-0.06614 0.686262,-0.429947 0.09922,-0.198437 0.09922,-0.570507 0,-0.760675 -0.545702,-1.099672 -0.537434,-0.347265 -1.546156,-0.347265 -1.165818,0 -1.653642,0.628384 -0.272851,0.347265 -0.355533,1.033527 h -1.38906 q 0.04134,-1.637106 1.058331,-2.273759 1.025259,-0.64492 2.372977,-0.64492 1.562693,0 2.538342,0.595311 0.967381,0.595311 0.967381,1.85208 v 5.101487 q 0,0.23151 0.09095,0.37207 0.09922,0.140559 0.405142,0.140559 0.09922,0 0.223242,-0.0083 0.124023,-0.01654 0.264583,-0.04134 v 1.099672 q -0.347265,0.09922 -0.529166,0.124024 -0.1819,0.0248 -0.496093,0.0248 -0.768943,0 -1.116208,-0.545702 -0.181901,-0.289387 -0.256315,-0.818553 -0.454752,0.595311 -1.306378,1.033527 -0.851625,0.438215 -1.876884,0.438215 -1.231964,0 -2.017444,-0.744139 -0.777212,-0.752407 -0.777212,-1.876884 0,-1.231964 0.768944,-1.909958 0.768944,-0.677993 2.017444,-0.835089 z M 15.668263,3.075775 Z"
  />
</svg>
PK
!<
Ճ���6chrome/toolkit/skin/classic/global/reader/RM-Serif.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="21" height="13" fill="context-fill">
  <path
     d="M 12.088127,11.897958 H 7.2016131 v -0.587043 q 0.6449206,-0.04134 1.0665998,-0.165364 0.4299469,-0.124023 0.4299469,-0.305924 0,-0.07441 -0.01654,-0.181901 -0.01654,-0.107486 -0.04961,-0.190168 L 7.656365,7.838266 H 3.547063 Q 3.315553,8.417041 3.166726,8.846988 3.026166,9.276935 2.910411,9.640736 2.802924,9.996269 2.761583,10.219511 q -0.04134,0.223242 -0.04134,0.363802 0,0.330728 0.520897,0.512629 0.520898,0.1819 1.174087,0.214973 v 0.587043 H 0 v -0.587043 q 0.214974,-0.01654 0.53743401,-0.09095 0.32246003,-0.08268 0.52916499,-0.214974 0.330729,-0.223241 0.51263,-0.46302 0.1819,-0.248046 0.355533,-0.677993 Q 2.819461,7.656365 3.88606,4.90305 4.95266,2.149735 5.787749,0 h 0.661457 l 3.910865,10.120293 q 0.124023,0.32246 0.281119,0.520897 0.157096,0.198437 0.438215,0.388606 0.190169,0.115755 0.496093,0.198437 0.305924,0.07441 0.512629,0.08268 z M 7.3587101,7.102395 5.5810441,2.554878 3.836451,7.102395 Z"
     />
  <path
     d="m 20.03388,11.749131 q -0.388606,0.140559 -0.686262,0.223241 -0.289387,0.09095 -0.661457,0.09095 -0.64492,0 -1.033526,-0.297656 -0.380338,-0.305924 -0.487825,-0.884699 H 17.1152 q -0.537434,0.595312 -1.15755,0.909504 -0.611848,0.314192 -1.48001,0.314192 -0.917772,0 -1.513083,-0.562239 -0.587043,-0.562238 -0.587043,-1.471742 0,-0.471288 0.132291,-0.843357 0.132291,-0.37207 0.396874,-0.669726 0.206706,-0.248046 0.545702,-0.438215 0.338997,-0.198437 0.636653,-0.314192 0.372069,-0.14056 1.504814,-0.520897 1.141014,-0.380338 1.537888,-0.595312 V 5.87043 q 0,-0.107487 -0.04961,-0.41341 -0.04134,-0.305924 -0.190169,-0.578775 -0.165364,-0.305924 -0.471288,-0.529166 -0.297656,-0.23151 -0.851626,-0.23151 -0.380338,0 -0.711066,0.132291 -0.322461,0.124024 -0.454752,0.264583 0,0.165365 0.07441,0.487825 0.08268,0.32246 0.08268,0.595311 0,0.289388 -0.264583,0.529166 -0.256315,0.239778 -0.719334,0.239778 -0.413411,0 -0.611848,-0.289387 -0.190169,-0.297656 -0.190169,-0.661458 0,-0.380337 0.264583,-0.727602 0.272851,-0.347265 0.702798,-0.620116 0.372069,-0.23151 0.901235,-0.388606 0.529166,-0.165364 1.033527,-0.165364 0.69453,0 1.207159,0.09922 0.520897,0.09095 0.942576,0.405143 0.421679,0.305923 0.636652,0.835089 0.223242,0.520897 0.223242,1.347719 0,1.182354 -0.02481,2.100126 -0.0248,0.909503 -0.0248,1.992639 0,0.32246 0.107487,0.512629 0.115755,0.190169 0.347265,0.322461 0.124023,0.07441 0.388607,0.08268 0.27285,0.0083 0.553969,0.0083 z M 17.148274,7.383514 q -0.702799,0.206705 -1.231964,0.405143 -0.529166,0.198437 -0.983918,0.496092 -0.41341,0.28112 -0.653188,0.669726 -0.239779,0.380337 -0.239779,0.909503 0,0.686262 0.355534,1.008722 0.363801,0.32246 0.917771,0.32246 0.587043,0 1.033527,-0.281119 0.446483,-0.289387 0.752407,-0.677993 z"
     />
</svg>
PK
!<69a��Dchrome/toolkit/skin/classic/global/reader/RM-Type-Controls-24x24.svg<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
  <path fill="context-fill" d="M10.87,18.989h2.144L8.3,3.991H5.724l-4.739,15H3.044l1.115-4.171h5.6ZM4.652,12.91L6.968,5.69l2.294,7.22H4.652ZM22.1,16.515v-5.06c0-2.31-.984-3.713-3.65-3.713a10.236,10.236,0,0,0-3.7.756L15.116,9.9A9.9,9.9,0,0,1,18.1,9.317c1.533,0,1.958.627,1.958,2.223v0.975h-1.35c-3.086,0-4.871,1.125-4.871,3.5a3.217,3.217,0,0,0,3.527,3.338,3.205,3.205,0,0,0,2.945-1.659,2.573,2.573,0,0,0,2.436,1.659l0.441-1.344A1.408,1.408,0,0,1,22.1,16.515ZM17.8,17.9a1.744,1.744,0,0,1-1.911-1.995c0-1.512,1.029-2.111,3.065-2.111h1.1V16.18C19.426,17.334,18.938,17.9,17.8,17.9Z"/>
</svg>
PK
!<$֣��=chrome/toolkit/skin/classic/global/reader/align-center-20.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20" fill="context-fill">
  <path d="M18 3.125H2v1.75h16v-1.75zM18 11.125H2v1.75h16v-1.75zM6 7.125h8v1.75H6v-1.75zM14 15.125H6v1.75h8v-1.75z"/>
</svg>
PK
!<s���;chrome/toolkit/skin/classic/global/reader/align-left-20.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20" fill="context-fill">
  <path d="M18 3.125H1v1.75h17v-1.75zM15 11.125H1v1.75h14v-1.75zM1 7.125h10v1.75H1v-1.75zM11 15.125H1v1.75h10v-1.75z"/>
</svg>
PK
!<�~*4��<chrome/toolkit/skin/classic/global/reader/align-right-20.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20" fill="context-fill">
  <path d="M2 3.125h17v1.75H2v-1.75zM5 11.125h14v1.75H5v-1.75zM19 7.125H9v1.75h10v-1.75zM9 15.125h10v1.75H9v-1.75z"/>
</svg>
PK
!<S���OOBchrome/toolkit/skin/classic/global/reader/character-spacing-20.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20" fill="context-fill">
  <path d="M1.75 19V1H0v18h1.75zM20 1v18h-1.75V1H20z"/>
  <path fill-rule="evenodd" clip-rule="evenodd" d="M6 15.707 7.342 12.5h5.317L14 15.707h1.897l-4.516-10.8a1 1 0 0 0-.923-.614h-.915c-.403 0-.767.242-.923.614l-4.517 10.8H6zm4-9.564 1.927 4.607H8.074L10 6.143z"/>
</svg>
PK
!<���]]>chrome/toolkit/skin/classic/global/reader/content-width-20.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20" fill="context-fill">
  <path d="M1.75 2v16H0V2h1.75zM20 2v16h-1.75V2H20zM14.064 12.996a.75.75 0 0 1-1.28-.53v-1.591H7.21v1.591a.75.75 0 0 1-1.28.53L3.464 10.53a.75.75 0 0 1 0-1.061L5.93 7.004a.75.75 0 0 1 1.28.53v1.591h5.574V7.533a.75.75 0 0 1 1.28-.53l2.466 2.466a.75.75 0 0 1 0 1.061l-2.466 2.466z"/>
</svg>
PK
!<�Ǫ__=chrome/toolkit/skin/classic/global/reader/line-spacing-20.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20" fill="context-fill">
  <path d="M2 0h16v1.75H2V0zM2 18.25h16V20H2v-1.75zM12.996 5.936a.75.75 0 0 1-.53 1.28h-1.591v5.574h1.591a.75.75 0 0 1 .53 1.28l-2.466 2.466a.75.75 0 0 1-1.061 0L7.004 14.07a.75.75 0 0 1 .53-1.28h1.591V7.215H7.534a.75.75 0 0 1-.53-1.28L9.469 3.47a.75.75 0 0 1 1.061 0l2.466 2.466z"/>
</svg>
PK
!<���\=chrome/toolkit/skin/classic/global/reader/word-spacing-20.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20" fill="context-fill">
  <path fill-rule="evenodd" clip-rule="evenodd" d="M4.422 5.75h.996a.75.75 0 0 1 .69.457L9.839 15H7.938l-1.061-2.5H2.963L1.901 15H0l.073-.174 3.659-8.619a.749.749 0 0 1 .69-.457zm.498 2.139L3.705 10.75h2.43L4.92 7.889z"/>
  <path d="m17.969 7.609 1.865 1.865a.568.568 0 0 1 0 .802l-1.865 1.865A.568.568 0 0 1 17 11.74v-.99h-5v.99a.567.567 0 0 1-.969.401l-1.865-1.865a.566.566 0 0 1 0-.802l1.865-1.865A.568.568 0 0 1 12 8.01V9h5v-.99c0-.506.611-.759.969-.401z"/>
</svg>
PK
!<c[��	�	5chrome/toolkit/skin/classic/global/search-textbox.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:host {
  appearance: auto;
  -moz-default-appearance: textfield;
  cursor: text;
  margin: 2px 4px; /* matches <input> global.css margin */
  padding: 2px 2px 3px;
  padding-inline-start: 4px;
  background-color: Field;
  color: FieldText;
  min-width: 0;
}

@media (-moz-platform: macos) {
  :host {
    -moz-default-appearance: searchfield;
    /* TODO: These are a bit dubious, see bug 1815264 */
    font-size: 12px;
    margin: 4px; /* matches <input> global.css margin */
    padding: 1px;
  }
}

input {
  border: none;
  padding: 0 1px;
  background-color: transparent;
  outline: none;
  color: inherit;
  font: inherit;
  text-shadow: inherit;
  box-sizing: border-box;
  flex: 1;
  text-align: inherit;
  min-width: 0;
}

:host([readonly="true"]) {
  background-color: -moz-Dialog;
  color: -moz-DialogText;
}

:host([disabled="true"]) {
  cursor: default;
  background-color: -moz-Dialog;
  color: GrayText;
}

.textbox-search-icons {
  align-items: center;
  justify-items: center;
}

.textbox-search-icon,
.textbox-search-sign,
.textbox-search-clear {
  /* Search icon is 12px, but we expand to the clear button size so they
   * overlap. That doesn't make the icon noticeably less crisp */
  width: 14px;
}

.textbox-search-sign,
.textbox-search-icon {
  list-style-image: url(chrome://global/skin/icons/search-textbox.svg);
}

.textbox-search-sign:-moz-locale-dir(rtl),
.textbox-search-icon:-moz-locale-dir(rtl) {
  transform: scaleX(-1);
}

.textbox-search-sign {
  margin-inline-end: 5px;
}

.textbox-search-clear {
  list-style-image: url(resource://content-accessible/searchfield-cancel.svg);
}

.textbox-search-icon:not([disabled]) {
  cursor: pointer;
}

.textbox-search-clear:not([disabled]) {
  cursor: default;
}

/* searchbutton disables the icon to the left.
 * Otherwise we don't show the search icon, only the clear icon, see
 * bug 1385902 */
:host([searchbutton]) .textbox-search-sign,
:host(:not([searchbutton])) .textbox-search-icons:not([selectedIndex="1"]) {
  display: none;
}

/* On macOS -moz-default-appearance: searchbox provides the search icon too, so
 * disable those there unconditionally */
@media (-moz-platform: macos) {
  .textbox-search-sign,
  .textbox-search-icons:not([selectedIndex="1"]) {
    display: none;
  }

  .textbox-search-clear {
    margin-bottom: 1px;
  }
}
PK
!<��{9��4chrome/toolkit/skin/classic/global/tree/sort-asc.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 7" width="9" height="7" fill="context-fill">
  <path d="M4.5 1.24a.9.9 0 0 0-.7.2l-2.6 2.7c-.8.87.43 2.1 1.3 1.3l2-2 2 2c.84.53 1.8-.5 1.2-1.3l-2.6-2.7a.9.9 0 0 0-.6-.2z"/>
</svg>
PK
!<���4chrome/toolkit/skin/classic/global/tree/sort-dsc.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 7" width="9" height="7" fill="context-fill">
  <path d="M4.5 5.67a.9.9 0 0 1-.7-.2l-2.6-2.7c-.8-.87.43-2.1 1.3-1.3l2 2 2-2c.84-.53 1.8.5 1.2 1.3l-2.6 2.7a.9.9 0 0 1-.6.2z"/>
</svg>
PK
!<�V�ss-chrome/toolkit/skin/classic/global/wizard.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
@namespace html url("http://www.w3.org/1999/xhtml");

.wizard-header {
  border-bottom: 2px groove ThreeDFace;
  background-color: Window;
  color: WindowText;
}

.wizard-header-box-1 {
  padding: 5px 0;
}

.wizard-header-label {
  margin-inline-start: 23px;
  font-weight: bold;
}

.wizard-header-description {
  margin-inline-start: 44px;
}

:host([data-branded="true"]) .wizard-header-icon {
  list-style-image: url("chrome://branding/content/icon128.png");
  width: 48px;
  height: 48px;
  margin-inline-end: 5px;
}

html|*.wizard-page-box {
  margin: 10px 44px;
}

.wizard-buttons-separator {
  margin-bottom: 0 !important;
}

.wizard-buttons-box-2 {
  margin: 10px;
}

.wizard-button[dlgtype="finish"],
.wizard-button[dlgtype="next"] {
  margin-inline-start: 0 !important;
}

.wizard-button[dlgtype="back"] {
  margin-inline-end: 0 !important;
}
PK
!<$~�4yy5chrome/toolkit/skin/classic/mozapps/aboutProfiles.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/in-content/info-pages.css");

#profiles {
  clear: both;
}

button {
  margin-inline: 0 8px;
}

.opendir {
  margin-inline-start: 3px;
}
PK
!<������;chrome/toolkit/skin/classic/mozapps/aboutServiceWorkers.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

.warningBackground {
  display: none;
  background-color: var(--in-content-page-background);
  width: 100%;
  height: 100%;
  z-index: 10;
  top: 0;
  inset-inline-start: 0;
  position: fixed;
}

.warningMessage {
  min-width: 330px;
  max-width: 50em;
  margin: 4em auto;
  border: 1px solid var(--in-content-box-border-color);
  border-radius: 10px;
  padding: 3em;
  background-color: var(--in-content-box-background);
  text-align: center;
}

.active {
  display: block;
}

.inactive {
  display: none;
}
PK
!<	4�Dchrome/toolkit/skin/classic/mozapps/downloads/unknownContentType.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@media (-moz-platform: macos) {
  #unknownContentType {
    font: menu;
  }

  description {
    font-weight: bold;
  }
}

#type {
  -moz-user-focus: normal;
  user-select: text;
  flex: 1;
  margin: 0;
  &:focus-visible {
    outline: var(--focus-outline);
  }
}

#from {
  margin-top: 1px;
}

#location {
  font-weight: bold;
}

#contentTypeImage {
  height: 16px;
  width: 16px;
  margin: 0;
  margin-inline-end: 5px;
}

.small-indent {
  margin-inline: 15px;
}

.small-indent label {
  margin-inline-start: 0;
}

#source,
#location {
  margin: 0;
  contain: inline-size;
}
PK
!<�����Echrome/toolkit/skin/classic/mozapps/extensions/category-available.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M10 .5C4.8.5.5 4.8.5 10s4.3 9.5 9.5 9.5 9.5-4.3 9.5-9.5S15.2.5 10 .5zM10 18c-4.4 0-8-3.6-8-8s3.6-8 8-8 8 3.6 8 8-3.6 8-8 8zm5.3-9.3c.3.3.3.8 0 1.1s-.8.3-1.1 0l-3.5-3.5v8.9c0 .4-.3.8-.8.8s-.8-.3-.8-.8V6.3L5.8 9.8c-.2.1-.4.2-.5.2s-.4-.1-.6-.2c-.3-.3-.3-.8 0-1.1L9.5 4h1.1l4.7 4.7z"/>
</svg>
PK
!<������Dchrome/toolkit/skin/classic/mozapps/extensions/category-discover.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M12.5 18a2 2 0 0 1-1.993-1.611l-.45-2.094a1.764 1.764 0 0 0-1.352-1.352l-2.094-.45A2 2 0 0 1 5 10.5a2 2 0 0 1 1.611-1.993l2.094-.45a1.764 1.764 0 0 0 1.352-1.352l.45-2.094A2 2 0 0 1 12.5 3a2 2 0 0 1 1.993 1.611l.45 2.094a1.764 1.764 0 0 0 1.352 1.352l2.094.45A2 2 0 0 1 20 10.5a2 2 0 0 1-1.611 1.993l-2.094.45a1.764 1.764 0 0 0-1.352 1.352l-.45 2.094A2 2 0 0 1 12.5 18zm-.977-10.98a3.266 3.266 0 0 1-2.504 2.504l-2.094.45c-.395.085-.425.423-.425.526s.03.441.426.526l2.094.45a3.265 3.265 0 0 1 2.504 2.504l.45 2.094c.085.396.423.426.526.426s.441-.03.526-.426l.45-2.094a3.265 3.265 0 0 1 2.504-2.504l2.094-.45c.396-.085.426-.423.426-.526s-.03-.441-.426-.526l-2.094-.45a3.265 3.265 0 0 1-2.504-2.504l-.45-2.094c-.085-.396-.423-.426-.526-.426s-.441.03-.526.426l-.451 2.094z"/>
  <path d="m2.805 16.439.228-1.062c.108-.503.825-.503.933 0l.228 1.062a.478.478 0 0 0 .367.367l1.062.228c.503.108.503.825 0 .933l-1.062.228a.478.478 0 0 0-.367.367l-.228 1.062c-.108.503-.825.503-.933 0l-.228-1.062a.478.478 0 0 0-.367-.367l-1.062-.228c-.503-.108-.503-.825 0-.933l1.062-.228a.48.48 0 0 0 .367-.367z"/>
  <path d="m2.805 2.439.228-1.062c.108-.503.825-.503.933 0l.228 1.062a.478.478 0 0 0 .367.367l1.062.228c.503.108.503.825 0 .933l-1.062.228a.477.477 0 0 0-.366.366l-.228 1.062c-.108.503-.825.503-.933 0l-.229-1.062a.477.477 0 0 0-.366-.366l-1.062-.228c-.503-.108-.503-.825 0-.933l1.062-.228a.48.48 0 0 0 .366-.367z"/>
</svg>
PK
!<�+�Z��Fchrome/toolkit/skin/classic/mozapps/extensions/category-extensions.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m15.5 5-2 0 0-1.873c0-1.594-1.183-2.961-2.693-3.112A3.003 3.003 0 0 0 7.5 2.999L7.5 5l-3 0A2.5 2.5 0 0 0 2 7.5l0 2.25c0 .69.56 1.25 1.25 1.25l1.623 0c.833 0 1.544.59 1.62 1.343a1.49 1.49 0 0 1-.379 1.163c-.285.314-.69.494-1.113.494L3.25 14C2.56 14 2 14.56 2 15.25l0 2.25A2.5 2.5 0 0 0 4.5 20l11 0a2.5 2.5 0 0 0 2.5-2.5l0-10A2.5 2.5 0 0 0 15.5 5zm1 12.7-.8.8-11.4 0-.8-.8 0-2.2 1.501 0c.846 0 1.657-.36 2.225-.988a3.01 3.01 0 0 0 .759-2.318C7.834 10.683 6.467 9.5 4.873 9.5L3.5 9.5l0-2.2.8-.8 3.7 0a1 1 0 0 0 1-1l0-2.501a1.502 1.502 0 0 1 1.657-1.492c.753.075 1.343.787 1.343 1.62L12 5.5a1 1 0 0 0 1 1l2.7 0 .8.8 0 10.4z"/>
</svg>
PK
!<Bd�*��Cchrome/toolkit/skin/classic/mozapps/extensions/category-plugins.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m17 4.05 0-1.55A1.5 1.5 0 0 0 15.5 1l-2 0A1.5 1.5 0 0 0 12 2.5L12 4 8 4l0-1.5A1.5 1.5 0 0 0 6.5 1l-2 0A1.5 1.5 0 0 0 3 2.5l0 1.55A2.502 2.502 0 0 0 1 6.5l0 9A2.5 2.5 0 0 0 3.5 18l13 0a2.5 2.5 0 0 0 2.5-2.5l0-9a2.502 2.502 0 0 0-2-2.45zm.5 11.65-.8.8-13.4 0-.8-.8 0-9.4.8-.8 13.4 0 .8.8 0 9.4z"/>
</svg>
PK
!<�n���Bchrome/toolkit/skin/classic/mozapps/extensions/category-recent.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20" fill="context-fill" fill-opacity="context-fill-opacity"
     transform="rotate(180)">
  <path d="M10 .5C4.8.5.5 4.8.5 10s4.3 9.5 9.5 9.5 9.5-4.3 9.5-9.5S15.2.5 10 .5zM10 18c-4.4 0-8-3.6-8-8s3.6-8 8-8 8 3.6 8 8-3.6 8-8 8zm5.3-9.3c.3.3.3.8 0 1.1s-.8.3-1.1 0l-3.5-3.5v8.9c0 .4-.3.8-.8.8s-.8-.3-.8-.8V6.3L5.8 9.8c-.2.1-.4.2-.5.2s-.4-.1-.6-.2c-.3-.3-.3-.8 0-1.1L9.5 4h1.1l4.7 4.7z"/>
</svg>
PK
!<��LJchrome/toolkit/skin/classic/mozapps/extensions/category-sitepermission.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M10 .5C4.8.5.5 4.8.5 10s4.3 9.5 9.5 9.5 9.5-4.3 9.5-9.5S15.2.5 10 .5zm8 8.7h-3c-.2-2.5-1.1-4.8-2.7-6.7h.7c2.7 1.2 4.7 3.7 5 6.7zm-7.8 8.2h-.4c-1.9-1.7-3-4.1-3.2-6.6h6.9c-.2 2.5-1.4 4.9-3.3 6.6zM6.5 9.2c.2-2.6 1.3-4.9 3.2-6.7h.4c1.9 1.7 3 4.1 3.2 6.7H6.5zM7 2.6h.7C6.1 4.5 5.2 6.8 5 9.2H2c.3-3 2.3-5.5 5-6.6zm-5 8.2h3c.2 2.5 1.1 4.8 2.7 6.6H7c-2.7-1.1-4.7-3.6-5-6.6zm11 6.6h-.7c1.6-1.9 2.5-4.2 2.7-6.6h3c-.3 3-2.3 5.5-5 6.6z"/>
</svg>
PK
!<'����Dchrome/toolkit/skin/classic/mozapps/extensions/dictionaryGeneric.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="context-fill" fill-opacity="context-fill-opacity" width="32" height="32">
  <path d="M12 8a4 4 0 1 0 4 4 4 4 0 0 0-4-4zm2.2 4.46l-1.92 1.92a.38.38 0 0 1-.54 0L9.8 12.46a.38.38 0 0 1 .54-.54l1.27 1.27V9.88a.38.38 0 0 1 .77 0v3.3l1.27-1.27a.38.38 0 0 1 .54.54z"/>
  <path d="M6.93.64a1 1 0 0 0-1.86 0l-5 14a1 1 0 0 0 .61 1.28A1 1 0 0 0 1 16a1 1 0 0 0 .94-.66l1.58-4.43h3.6a5 5 0 0 1 2.34-3.19zM4.24 8.91L6 4l1.76 4.91z"/>
</svg>
PK
!<���llCchrome/toolkit/skin/classic/mozapps/extensions/extensionGeneric.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="M2.6 8 4 8c1.103 0 2 .897 2 2s-.897 2-2 2l-1.4 0-.6.6 0 1.775C2 15.271 2.729 16 3.625 16l9.75 0c.896 0 1.625-.729 1.625-1.625l0-8.75C15 4.729 14.271 4 13.375 4L11 4l0-2c0-1.103-.897-2-2-2S7 .897 7 2l0 2-3.375 0C2.729 4 2 4.729 2 5.625L2 7.4l.6.6z"/>
</svg>
PK
!<�/P �-�-7chrome/toolkit/skin/classic/mozapps/extensions/line.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
  <defs>
    <radialGradient id="a" cx="14.305" cy="3.031" r="18.199" gradientUnits="userSpaceOnUse">
      <stop offset="0" stop-color="#fff36e"/>
      <stop offset=".5" stop-color="#fc4055"/>
      <stop offset="1" stop-color="#e31587"/>
    </radialGradient>
    <radialGradient id="b" cx="1.315" cy="3.784" r="10.76" gradientUnits="userSpaceOnUse">
      <stop offset=".001" stop-color="#c60084"/>
      <stop offset="1" stop-color="#fc4055" stop-opacity="0"/>
    </radialGradient>
    <radialGradient id="c" cx="15.858" cy="1.995" r="21.371" gradientUnits="userSpaceOnUse">
      <stop offset="0" stop-color="#ffde67" stop-opacity=".6"/>
      <stop offset=".093" stop-color="#ffd966" stop-opacity=".581"/>
      <stop offset=".203" stop-color="#ffca65" stop-opacity=".525"/>
      <stop offset=".321" stop-color="#feb262" stop-opacity=".432"/>
      <stop offset=".446" stop-color="#fe8f5e" stop-opacity=".302"/>
      <stop offset=".573" stop-color="#fd6459" stop-opacity=".137"/>
      <stop offset=".664" stop-color="#fc4055" stop-opacity="0"/>
    </radialGradient>
    <radialGradient id="d" cx="8.451" cy="8.902" r="27.546" gradientUnits="userSpaceOnUse">
      <stop offset=".153" stop-color="#810220"/>
      <stop offset=".167" stop-color="#920b27" stop-opacity=".861"/>
      <stop offset=".216" stop-color="#cb2740" stop-opacity=".398"/>
      <stop offset=".253" stop-color="#ef394f" stop-opacity=".11"/>
      <stop offset=".272" stop-color="#fc4055" stop-opacity="0"/>
    </radialGradient>
    <radialGradient id="e" cx="6.368" cy="8.555" r="27.542" gradientUnits="userSpaceOnUse">
      <stop offset=".113" stop-color="#810220"/>
      <stop offset=".133" stop-color="#920b27" stop-opacity=".861"/>
      <stop offset=".204" stop-color="#cb2740" stop-opacity=".398"/>
      <stop offset=".257" stop-color="#ef394f" stop-opacity=".11"/>
      <stop offset=".284" stop-color="#fc4055" stop-opacity="0"/>
    </radialGradient>
    <radialGradient id="f" cx="13.937" cy="2.416" r="17.079" gradientUnits="userSpaceOnUse">
      <stop offset="0" stop-color="#ff9640"/>
      <stop offset=".8" stop-color="#fc4055"/>
    </radialGradient>
    <radialGradient id="g" cx="13.937" cy="2.416" r="17.079" gradientUnits="userSpaceOnUse">
      <stop offset=".084" stop-color="#ffde67"/>
      <stop offset=".147" stop-color="#ffdc66" stop-opacity=".968"/>
      <stop offset=".246" stop-color="#ffd562" stop-opacity=".879"/>
      <stop offset=".369" stop-color="#ffcb5d" stop-opacity=".734"/>
      <stop offset=".511" stop-color="#ffbc55" stop-opacity=".533"/>
      <stop offset=".667" stop-color="#ffaa4b" stop-opacity=".28"/>
      <stop offset=".822" stop-color="#ff9640" stop-opacity="0"/>
    </radialGradient>
    <radialGradient id="h" cx="10.011" cy="7.729" r="8.36" gradientTransform="matrix(.247 .969 -1.011 .258 15.352 -3.965)" gradientUnits="userSpaceOnUse">
      <stop offset=".363" stop-color="#fc4055"/>
      <stop offset=".443" stop-color="#fd604d" stop-opacity=".633"/>
      <stop offset=".545" stop-color="#fe8644" stop-opacity=".181"/>
      <stop offset=".59" stop-color="#ff9640" stop-opacity="0"/>
    </radialGradient>
    <radialGradient id="i" cx="8.575" cy="8.439" r="8.353" gradientUnits="userSpaceOnUse">
      <stop offset=".216" stop-color="#fc4055" stop-opacity=".8"/>
      <stop offset=".267" stop-color="#fd5251" stop-opacity=".633"/>
      <stop offset=".41" stop-color="#fe8345" stop-opacity=".181"/>
      <stop offset=".474" stop-color="#ff9640" stop-opacity="0"/>
    </radialGradient>
    <radialGradient id="j" cx="17.326" cy=".487" r="28.887" gradientUnits="userSpaceOnUse">
      <stop offset=".054" stop-color="#fff36e"/>
      <stop offset=".457" stop-color="#ff9640"/>
      <stop offset=".639" stop-color="#ff9640"/>
    </radialGradient>
    <linearGradient id="k" x1="8.117" y1="-.134" x2="12.46" y2="12.441" gradientUnits="userSpaceOnUse">
      <stop offset="0" stop-color="#b833e1"/>
      <stop offset=".371" stop-color="#9059ff"/>
      <stop offset=".614" stop-color="#5b6df8"/>
      <stop offset="1" stop-color="#0090ed"/>
    </linearGradient>
    <linearGradient id="l" x1="5.542" y1=".065" x2="13.614" y2="8.137" gradientUnits="userSpaceOnUse">
      <stop offset=".805" stop-color="#722291" stop-opacity="0"/>
      <stop offset="1" stop-color="#592acb" stop-opacity=".5"/>
    </linearGradient>
    <linearGradient id="m" x1="11.836" y1="1.378" x2="3.632" y2="15.587" gradientUnits="userSpaceOnUse">
      <stop offset="0" stop-color="#fff36e" stop-opacity=".8"/>
      <stop offset=".094" stop-color="#fff36e" stop-opacity=".699"/>
      <stop offset=".752" stop-color="#fff36e" stop-opacity="0"/>
    </linearGradient>
  </defs>
  <g style="isolation:isolate">
    <path d="M14.389 3.14A7.894 7.894 0 0 0 8.318 0a7 7 0 0 0-3.867.97A7.472 7.472 0 0 1 8.059.1a7.1 7.1 0 0 1 7.071 6.087 7 7 0 0 1-6.9 8.2 7.151 7.151 0 0 1-3.949-1.127C1.268 11.3 1.258 7.441 1.26 6.825a8.626 8.626 0 0 1 1.189-4.266A6.656 6.656 0 0 0 .576 4.984a7.734 7.734 0 0 0-.519 4.035c.012.1.023.207.036.31a8.013 8.013 0 1 0 14.3-6.189z" fill="url(#a)"/>
    <path d="M14.389 3.14A7.894 7.894 0 0 0 8.318 0a7 7 0 0 0-3.867.97A7.472 7.472 0 0 1 8.059.1a7.1 7.1 0 0 1 7.071 6.087 7 7 0 0 1-6.9 8.2 7.151 7.151 0 0 1-3.949-1.127C1.268 11.3 1.258 7.441 1.26 6.825a8.626 8.626 0 0 1 1.189-4.266A6.656 6.656 0 0 0 .576 4.984a7.734 7.734 0 0 0-.519 4.035c.012.1.023.207.036.31a8.013 8.013 0 1 0 14.3-6.189z" fill="url(#b)" opacity=".67"/>
    <path d="M14.389 3.14A7.894 7.894 0 0 0 8.318 0a7 7 0 0 0-3.867.97A7.472 7.472 0 0 1 8.059.1a7.1 7.1 0 0 1 7.071 6.087 7 7 0 0 1-6.9 8.2 7.151 7.151 0 0 1-3.949-1.127C1.268 11.3 1.258 7.441 1.26 6.825a8.626 8.626 0 0 1 1.189-4.266A6.656 6.656 0 0 0 .576 4.984a7.734 7.734 0 0 0-.519 4.035c.012.1.023.207.036.31a8.013 8.013 0 1 0 14.3-6.189z" fill="url(#c)"/>
    <path d="M14.389 3.14A7.894 7.894 0 0 0 8.318 0a7 7 0 0 0-3.867.97A7.472 7.472 0 0 1 8.059.1a7.1 7.1 0 0 1 7.071 6.087 7 7 0 0 1-6.9 8.2 7.151 7.151 0 0 1-3.949-1.127C1.268 11.3 1.258 7.441 1.26 6.825a8.626 8.626 0 0 1 1.189-4.266A6.656 6.656 0 0 0 .576 4.984a7.734 7.734 0 0 0-.519 4.035c.012.1.023.207.036.31a8.013 8.013 0 1 0 14.3-6.189z" fill="url(#d)"/>
    <path d="M14.389 3.14A7.894 7.894 0 0 0 8.318 0a7 7 0 0 0-3.867.97A7.472 7.472 0 0 1 8.059.1a7.1 7.1 0 0 1 7.071 6.087 7 7 0 0 1-6.9 8.2 7.151 7.151 0 0 1-3.949-1.127C1.268 11.3 1.258 7.441 1.26 6.825a8.626 8.626 0 0 1 1.189-4.266A6.656 6.656 0 0 0 .576 4.984a7.734 7.734 0 0 0-.519 4.035c.012.1.023.207.036.31a8.013 8.013 0 1 0 14.3-6.189z" fill="url(#e)"/>
    <path d="M15.325 5.965C14.875 1.9 11.253.078 8.059.1a7.765 7.765 0 0 0-3.608.872 3.913 3.913 0 0 0-.712.54c.026-.021.1-.085.23-.172l.013-.009.011-.007A5.337 5.337 0 0 1 5.531.609 8.713 8.713 0 0 1 8.168.3a6.65 6.65 0 0 1 6.25 6.4 4.818 4.818 0 0 1-4.58 4.869 4.731 4.731 0 0 1-2.967-.72A5.425 5.425 0 0 1 5.06 8.242a4.552 4.552 0 0 1 .285-3.149A4.726 4.726 0 0 1 8.464 2.7a4.3 4.3 0 0 0-1.782-.585A5.4 5.4 0 0 0 1.7 5.177a6.035 6.035 0 0 0-.2 4.638 6.683 6.683 0 0 0 2.4 3.234A7.177 7.177 0 0 0 7.326 14.4s.153.018.309.029a8.085 8.085 0 0 0 5.439-1.6c2.811-2.377 2.315-6.285 2.251-6.864z" fill="url(#f)"/>
    <path d="M15.325 5.965C14.875 1.9 11.253.078 8.059.1a7.765 7.765 0 0 0-3.608.872 3.913 3.913 0 0 0-.712.54c.026-.021.1-.085.23-.172l.013-.009.011-.007A5.337 5.337 0 0 1 5.531.609 8.713 8.713 0 0 1 8.168.3a6.65 6.65 0 0 1 6.25 6.4 4.818 4.818 0 0 1-4.58 4.869 4.731 4.731 0 0 1-2.967-.72A5.425 5.425 0 0 1 5.06 8.242a4.552 4.552 0 0 1 .285-3.149A4.726 4.726 0 0 1 8.464 2.7a4.3 4.3 0 0 0-1.782-.585A5.4 5.4 0 0 0 1.7 5.177a6.035 6.035 0 0 0-.2 4.638 6.683 6.683 0 0 0 2.4 3.234A7.177 7.177 0 0 0 7.326 14.4s.153.018.309.029a8.085 8.085 0 0 0 5.439-1.6c2.811-2.377 2.315-6.285 2.251-6.864z" fill="url(#g)"/>
    <path d="M15.325 5.965C14.875 1.9 11.253.078 8.059.1a7.765 7.765 0 0 0-3.608.872 3.913 3.913 0 0 0-.712.54c.026-.021.1-.085.23-.172l.013-.009.011-.007A5.337 5.337 0 0 1 5.531.609 8.713 8.713 0 0 1 8.168.3a6.65 6.65 0 0 1 6.25 6.4 4.818 4.818 0 0 1-4.58 4.869 4.731 4.731 0 0 1-2.967-.72A5.425 5.425 0 0 1 5.06 8.242a4.552 4.552 0 0 1 .285-3.149A4.726 4.726 0 0 1 8.464 2.7a4.3 4.3 0 0 0-1.782-.585A5.4 5.4 0 0 0 1.7 5.177a6.035 6.035 0 0 0-.2 4.638 6.683 6.683 0 0 0 2.4 3.234A7.177 7.177 0 0 0 7.326 14.4s.153.018.309.029a8.085 8.085 0 0 0 5.439-1.6c2.811-2.377 2.315-6.285 2.251-6.864z" style="mix-blend-mode:multiply" opacity=".53" fill="url(#h)"/>
    <path d="M15.325 5.965C14.875 1.9 11.253.078 8.059.1a7.765 7.765 0 0 0-3.608.872 3.913 3.913 0 0 0-.712.54c.026-.021.1-.085.23-.172l.013-.009.011-.007A5.337 5.337 0 0 1 5.531.609 8.713 8.713 0 0 1 8.168.3a6.65 6.65 0 0 1 6.25 6.4 4.818 4.818 0 0 1-4.58 4.869 4.731 4.731 0 0 1-2.967-.72A5.425 5.425 0 0 1 5.06 8.242a4.552 4.552 0 0 1 .285-3.149A4.726 4.726 0 0 1 8.464 2.7a4.3 4.3 0 0 0-1.782-.585A5.4 5.4 0 0 0 1.7 5.177a6.035 6.035 0 0 0-.2 4.638 6.683 6.683 0 0 0 2.4 3.234A7.177 7.177 0 0 0 7.326 14.4s.153.018.309.029a8.085 8.085 0 0 0 5.439-1.6c2.811-2.377 2.315-6.285 2.251-6.864z" style="mix-blend-mode:multiply" opacity=".53" fill="url(#i)"/>
    <path d="M9.24 11.568a5.148 5.148 0 0 0 3.183-.815 5.67 5.67 0 0 0 2.39-4.234C14.957 3.381 13.094 0 8.168.3a8.713 8.713 0 0 0-2.637.309 5.745 5.745 0 0 0-1.538.715l-.011.007-.013.009c-.076.054-.151.11-.224.168a6.7 6.7 0 0 1 4.2-.787c2.827.371 5.413 2.571 5.413 5.475a4.076 4.076 0 0 1-3.747 3.817A2.849 2.849 0 0 1 6.9 8.156a2.75 2.75 0 0 1 .919-2.729 2.875 2.875 0 0 0-1.81.919A3.07 3.07 0 0 0 5.735 9.6c.84 1.746 3.031 1.929 3.505 1.968z" fill="url(#j)"/>
    <path d="M14.4 3.745a4.5 4.5 0 0 0-.976-1.629 6.056 6.056 0 0 0-1.819-1.3A8.086 8.086 0 0 0 9.82.184 7.96 7.96 0 0 0 6.507.165a5.727 5.727 0 0 0-2.768 1.346 6.415 6.415 0 0 1 1.606-.64 6.712 6.712 0 0 1 6.234 1.619 5.417 5.417 0 0 1 .866 1.061 4.693 4.693 0 0 1 .123 4.773 3.8 3.8 0 0 1-2.914 1.691A4.726 4.726 0 0 0 14 7.839a4.88 4.88 0 0 0 .4-4.094z" fill="url(#k)"/>
    <path d="M14.4 3.745a4.5 4.5 0 0 0-.976-1.629 6.056 6.056 0 0 0-1.819-1.3A8.086 8.086 0 0 0 9.82.184 7.96 7.96 0 0 0 6.507.165a5.727 5.727 0 0 0-2.768 1.346 6.415 6.415 0 0 1 1.606-.64 6.712 6.712 0 0 1 6.234 1.619 5.417 5.417 0 0 1 .866 1.061 4.693 4.693 0 0 1 .123 4.773 3.8 3.8 0 0 1-2.914 1.691A4.726 4.726 0 0 0 14 7.839a4.88 4.88 0 0 0 .4-4.094z" fill="url(#l)"/>
    <path d="M8.318 0h-.073c.134 0 .269.013.4.022C8.538.021 8.429 0 8.318 0zM3.747 1.5a3.951 3.951 0 0 1 .453-.359 3.547 3.547 0 0 0-.453.364zm1.7-.653c-.032.008-.066.01-.1.019-.07.022-.147.046-.223.068.101-.029.209-.056.319-.082zm-1.7.658zm0 0zM14.389 3.14a8.12 8.12 0 0 0-.675-.77 6.368 6.368 0 0 0-.747-.677c-.072-.063-.156-.116-.233-.176a5.136 5.136 0 0 1 .693.6 4.5 4.5 0 0 1 .973 1.628 4.88 4.88 0 0 1-.4 4.094 4.723 4.723 0 0 1-4.342 2.175 2.609 2.609 0 0 0 .578-.07 2.81 2.81 0 0 1-.625.069A2.849 2.849 0 0 1 6.9 8.156a2.749 2.749 0 0 1 .919-2.729 2.875 2.875 0 0 0-1.81.919A3.07 3.07 0 0 0 5.735 9.6c.03.062.073.109.107.167a4.744 4.744 0 0 1-.782-1.525 4.552 4.552 0 0 1 .285-3.149A4.726 4.726 0 0 1 8.464 2.7a4.3 4.3 0 0 0-1.782-.585A5.4 5.4 0 0 0 1.7 5.177a5.133 5.133 0 0 0-.414 1.17 8.715 8.715 0 0 1 1.163-3.788A6.656 6.656 0 0 0 .576 4.984a7.734 7.734 0 0 0-.519 4.035c.012.1.023.207.036.31a8.013 8.013 0 1 0 14.3-6.189zM4.541.923c-.026.016-.065.033-.09.049.011-.007.025-.011.036-.018s.037-.02.054-.031zm-.09.049c-.017.01-.028.019-.044.029.025-.016.053-.03.079-.046a.378.378 0 0 0-.035.017z" fill="url(#m)"/>
  </g>
</svg>
PK
!<�\��hh>chrome/toolkit/skin/classic/mozapps/extensions/recommended.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg width="12" height="12" xmlns="http://www.w3.org/2000/svg">
  <g fill="context-fill">
    <path d="M11.45 0H.55C.25 0 0 .22 0 .5V3c0 1.1.98 2 2.18 2h.05a3.67 3.67 0 0 0 3.22 2.96V9.5h1.1V7.96A3.67 3.67 0 0 0 9.77 5h.05C11.02 5 12 4.1 12 3V.5c0-.28-.24-.5-.55-.5zM1.1 3V1h1.1v3c-.61 0-1.1-.45-1.1-1zm9.82 0c0 .55-.49 1-1.1 1V1h1.1v2zM7.1 10H4.9c-2.72 0-2.72 2-2.72 2h7.64s0-2-2.73-2z"/>
  </g>
</svg>
PK
!<�����?chrome/toolkit/skin/classic/mozapps/extensions/themeGeneric.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" width="20" height="20" fill="context-fill" fill-opacity="context-fill-opacity">
  <path d="m16.875 7.286 0-6.036c0-.69-.56-1.25-1.25-1.25L4.375 0c-.69 0-1.25.56-1.25 1.25l0 6.036A1.496 1.496 0 0 0 2.5 8.5l0 3A2.5 2.5 0 0 0 5 14l3 0 0 4a2 2 0 1 0 4 0l0-4 3 0a2.5 2.5 0 0 0 2.5-2.5l0-3c0-.501-.248-.941-.625-1.214zm-1.5-4.486 0 4.2-10.75 0 0-4.2.8-.8 1.111 0 .825.916a1.081 1.081 0 0 0 1.496.107l.822-.685.541 0 1.157 1.157c.41.41 1.071.424 1.498.03L14.528 2l.847 0 0 .8z"/>
</svg>
PK
!<�Us��9chrome/toolkit/skin/classic/mozapps/handling/handling.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

richlistitem[type] {
  min-height: 36px; /* Don't forget to update the richlistbox height! */
  padding-inline-start: 2px;
 }

richlistitem {
  align-items: center;
}

richlistbox {
  /* 3 items high, plus 4px for top and bottom margins, less 2px for border */
  min-height: 110px;
}

.name {
  font-weight: bold;
}

.description {
  color: GrayText;
}

@media (prefers-contrast) {
  richlistitem[selected] .description {
    color: inherit;
  }
}
PK
!<�R�ii8chrome/toolkit/skin/classic/mozapps/profileDowngrade.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

dialog::part(button-spacer) {
  display: none;
}

#info {
  list-style-image: url("chrome://global/skin/icons/info.svg");
  width: 32px;
  height: 32px;
}
PK
!<�wb�xx8chrome/toolkit/skin/classic/mozapps/profileSelection.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@import url("chrome://global/skin/global.css");

#managebuttons {
  padding-top: 1em;
}

#profilesContainer {
  flex: 1;
  min-width: 0;
}

#profiles {
  height: 12em;
}
PK
!<�T���6chrome/toolkit/skin/classic/mozapps/update/updates.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* Elevation Dialog */
/* Specify the size for the UI so it has a fixed size. 3rd
   party themes should typically specify the same values. */
#elevationBox,
.update-content {
  height: 360px;
  width: 700px;
}

/* Remove margin and padding so the inner UI will extend to the edge of the
   window. 3rd party themes should typically specify the same values. */
#updates {
  margin: 0;
  padding: 0;
}

.update-header {
  border-bottom: 2px groove ThreeDFace;
  background-color: Window;
  color: WindowText;
}

.update-header-box-1 {
  padding: 5px 0;
}

.update-content {
  padding: 10px;
}

.update-header-label {
  font-weight: bold;
}

#update-button-box {
  margin: 0;
  padding: 6px 10px 10px;
}

.update-buttons-separator {
  margin-block: 0 !important;
}

#updateFinishedName {
  font-weight: bold;
  font-size: larger;
}

/* Update History Window */
update {
  border-bottom: 1px dotted #C0C0C0;
}

.update-name {
  font-weight: bold;
}

.update-label-column {
  align-items: flex-end;
}

.update-type {
  font-weight: bold;
  color: #990000;
}

.update-status-value,
.update-installedOn-value {
  margin-inline-start: 1ch;
}

#historyItems {
  height: 200px;
  flex: 1 auto;
  margin: 1px 5px 5px;
}

#historyItems .update {
  padding: 5px;
  display: flex;
  flex-direction: column;
}

.update-name {
  flex: 1;
}
PK
!<��z�.."chrome/pdfjs/content/PdfJs.sys.mjs/* Copyright 2012 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const PREF_DISABLED = "pdfjs.disabled";
const PREF_MIGRATION_VERSION = "pdfjs.migrationVersion";
const PREF_PREVIOUS_ACTION = "pdfjs.previousHandler.preferredAction";
const PREF_PREVIOUS_ASK = "pdfjs.previousHandler.alwaysAskBeforeHandling";
const PREF_ISDEFAULT_CACHE_STATE = "pdfjs.enabledCache.state";
const TOPIC_PDFJS_HANDLER_CHANGED = "pdfjs:handlerChanged";
const PDF_CONTENT_TYPE = "application/pdf";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";

var Svc = {};
XPCOMUtils.defineLazyServiceGetter(
  Svc,
  "mime",
  "@mozilla.org/mime;1",
  "nsIMIMEService"
);
XPCOMUtils.defineLazyServiceGetter(
  Svc,
  "handlerService",
  "@mozilla.org/uriloader/handler-service;1",
  "nsIHandlerService"
);

// We're supposed to get this type of thing from the OS, and generally we do.
// But doing so is expensive, so on startup paths we can use this to make the
// handler service get and store the Right Thing (it just goes into a JSON
// file) and avoid the performance issues.
const gPdfFakeHandlerInfo = {
  QueryInterface: ChromeUtils.generateQI(["nsIMIMEInfo"]),
  getFileExtensions() {
    return ["pdf"];
  },
  possibleApplicationHandlers: Cc["@mozilla.org/array;1"].createInstance(
    Ci.nsIMutableArray
  ),
  extensionExists(ext) {
    return ext == "pdf";
  },
  alwaysAskBeforeHandling: false,
  preferredAction: Ci.nsIHandlerInfo.handleInternally,
  type: PDF_CONTENT_TYPE,
};

export var PdfJs = {
  QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
  _initialized: false,
  _cachedIsDefault: true,

  init: function init(isNewProfile) {
    if (
      Services.appinfo.processType !== Services.appinfo.PROCESS_TYPE_DEFAULT
    ) {
      throw new Error(
        "PdfJs.init should only get called in the parent process."
      );
    }
    this.initPrefs();
    this.checkIsDefault(isNewProfile);
  },

  initPrefs: function initPrefs() {
    if (this._initialized) {
      return;
    }
    this._initialized = true;

    if (this._prefDisabled) {
      this._unbecomeHandler();
    } else {
      this._migrate();
    }

    // Listen for when a different pdf handler is chosen.
    Services.obs.addObserver(this, TOPIC_PDFJS_HANDLER_CHANGED);
  },

  uninit: function uninit() {
    if (this._initialized) {
      Services.obs.removeObserver(this, TOPIC_PDFJS_HANDLER_CHANGED);
      this._initialized = false;
    }
  },

  _migrate: function migrate() {
    const VERSION = 2;
    var currentVersion = Services.prefs.getIntPref(PREF_MIGRATION_VERSION, 0);
    if (currentVersion >= VERSION) {
      return;
    }
    // Make pdf.js the default pdf viewer on the first migration.
    if (currentVersion < 1) {
      this._becomeHandler();
    }
    if (currentVersion < 2) {
      // cleaning up of unused database preference (see #3994)
      Services.prefs.clearUserPref("pdfjs.database");
    }
    Services.prefs.setIntPref(PREF_MIGRATION_VERSION, VERSION);
  },

  _becomeHandler: function _becomeHandler() {
    // Normally, this happens in the first run at some point, where the
    // handler service doesn't have any info on user preferences yet.
    // Don't bother storing old defaults in this case, as they're
    // meaningless anyway.
    if (!Svc.handlerService.exists(gPdfFakeHandlerInfo)) {
      // Store the requisite info into the DB, and nothing else:
      Svc.handlerService.store(gPdfFakeHandlerInfo);
    } else {
      let handlerInfo = Svc.mime.getFromTypeAndExtension(
        PDF_CONTENT_TYPE,
        "pdf"
      );
      let prefs = Services.prefs;
      if (
        handlerInfo.preferredAction !== Ci.nsIHandlerInfo.handleInternally &&
        handlerInfo.alwaysAskBeforeHandling !== false
      ) {
        // Store the previous settings of preferredAction and
        // alwaysAskBeforeHandling in case we need to revert them in a hotfix that
        // would turn pdf.js off.
        prefs.setIntPref(PREF_PREVIOUS_ACTION, handlerInfo.preferredAction);
        prefs.setBoolPref(
          PREF_PREVIOUS_ASK,
          handlerInfo.alwaysAskBeforeHandling
        );
      }

      // Change and save mime handler settings.
      handlerInfo.alwaysAskBeforeHandling = false;
      handlerInfo.preferredAction = Ci.nsIHandlerInfo.handleInternally;
      Svc.handlerService.store(handlerInfo);
    }
  },

  _unbecomeHandler: function _unbecomeHandler() {
    let handlerInfo = Svc.mime.getFromTypeAndExtension(PDF_CONTENT_TYPE, "pdf");
    if (handlerInfo.preferredAction === Ci.nsIHandlerInfo.handleInternally) {
      // If PDFJS is disabled, but we're still marked to handleInternally,
      // either put it back to what it was, or remove it.
      if (Services.prefs.prefHasUserValue(PREF_PREVIOUS_ACTION)) {
        handlerInfo.preferredAction =
          Services.prefs.getIntPref(PREF_PREVIOUS_ACTION);
        handlerInfo.alwaysAskBeforeHandling =
          Services.prefs.getBoolPref(PREF_PREVIOUS_ASK);
        Svc.handlerService.store(handlerInfo);
      } else {
        Svc.handlerService.remove(handlerInfo);
        // Clear migration pref so the handler comes back if reenabled
        Services.prefs.clearUserPref(PREF_MIGRATION_VERSION);
      }
    }
  },

  /**
   * @param isNewProfile used to decide whether we need to check the
   *                     handler service to see if the user configured
   *                     pdfs differently. If we're on a new profile,
   *                     there's no need to check.
   */
  _isDefault(isNewProfile) {
    let { processType, PROCESS_TYPE_DEFAULT } = Services.appinfo;
    if (processType !== PROCESS_TYPE_DEFAULT) {
      throw new Error(
        "isDefault should only get called in the parent process."
      );
    }

    if (this._prefDisabled) {
      return false;
    }

    // Don't bother with the handler service on a new profile:
    if (isNewProfile) {
      return true;
    }
    // Check if the 'application/pdf' preview handler is configured properly.
    let handlerInfo = Svc.mime.getFromTypeAndExtension(PDF_CONTENT_TYPE, "pdf");
    return (
      !handlerInfo.alwaysAskBeforeHandling &&
      handlerInfo.preferredAction == Ci.nsIHandlerInfo.handleInternally
    );
  },

  checkIsDefault(isNewProfile) {
    this._cachedIsDefault = this._isDefault(isNewProfile);
    Services.prefs.setBoolPref(
      PREF_ISDEFAULT_CACHE_STATE,
      this._cachedIsDefault
    );
  },

  cachedIsDefault() {
    return this._cachedIsDefault;
  },

  // nsIObserver
  observe() {
    this.checkIsDefault();
  },
};

XPCOMUtils.defineLazyPreferenceGetter(
  PdfJs,
  "_prefDisabled",
  PREF_DISABLED,
  false,
  () => PdfJs.checkIsDefault()
);
PK
!<į��)chrome/pdfjs/content/PdfJsNetwork.sys.mjs/* Copyright 2012 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

function getTypedArray(xhr) {
  const data = xhr.response;
  if (typeof data !== "string") {
    return new Uint8Array(data);
  }
  return Uint8Array.from(data, ch => ch.charCodeAt(0) & 0xff);
}

const OK_RESPONSE = 200;
const PARTIAL_CONTENT_RESPONSE = 206;

export class NetworkManager {
  constructor(url, args = {}) {
    this.url = url;
    this.isHttp = /^https?:/i.test(url);
    this.httpHeaders = (this.isHttp && args.httpHeaders) || {};
    this.withCredentials = args.withCredentials || false;
    this.getXhr =
      args.getXhr ||
      function NetworkManager_getXhr() {
        return new XMLHttpRequest({ mozAnon: false });
      };

    this.currXhrId = 0;
    this.pendingRequests = Object.create(null);
  }

  requestRange(begin, end, listeners) {
    var args = {
      begin,
      end,
    };
    for (var prop in listeners) {
      args[prop] = listeners[prop];
    }
    return this.request(args);
  }

  request(args) {
    var xhr = this.getXhr();
    var xhrId = this.currXhrId++;
    var pendingRequest = (this.pendingRequests[xhrId] = {
      xhr,
    });

    xhr.open("GET", this.url);
    xhr.withCredentials = this.withCredentials;
    for (var property in this.httpHeaders) {
      var value = this.httpHeaders[property];
      if (typeof value === "undefined") {
        continue;
      }
      xhr.setRequestHeader(property, value);
    }
    if (this.isHttp && "begin" in args && "end" in args) {
      var rangeStr = args.begin + "-" + (args.end - 1);
      xhr.setRequestHeader("Range", "bytes=" + rangeStr);
      pendingRequest.expectedStatus = 206;
      xhr.channel.QueryInterface(Ci.nsIHttpChannel).redirectionLimit = 0;
    } else {
      pendingRequest.expectedStatus = 200;
    }

    xhr.responseType = "arraybuffer";

    if (args.onError) {
      xhr.onerror = function () {
        args.onError(xhr.status);
      };
    }
    xhr.onreadystatechange = this.onStateChange.bind(this, xhrId);
    xhr.onprogress = this.onProgress.bind(this, xhrId);

    pendingRequest.onHeadersReceived = args.onHeadersReceived;
    pendingRequest.onDone = args.onDone;
    pendingRequest.onError = args.onError;
    pendingRequest.onProgress = args.onProgress;

    xhr.send(null);

    return xhrId;
  }

  onProgress(xhrId, evt) {
    var pendingRequest = this.pendingRequests[xhrId];
    if (!pendingRequest) {
      // Maybe abortRequest was called...
      return;
    }

    var onProgress = pendingRequest.onProgress;
    if (onProgress) {
      onProgress(evt);
    }
  }

  onStateChange(xhrId) {
    var pendingRequest = this.pendingRequests[xhrId];
    if (!pendingRequest) {
      // Maybe abortRequest was called...
      return;
    }

    var xhr = pendingRequest.xhr;
    if (xhr.readyState >= 2 && pendingRequest.onHeadersReceived) {
      pendingRequest.onHeadersReceived();
      delete pendingRequest.onHeadersReceived;
    }

    if (xhr.readyState !== 4) {
      return;
    }

    if (!(xhrId in this.pendingRequests)) {
      // The XHR request might have been aborted in onHeadersReceived()
      // callback, in which case we should abort request
      return;
    }

    delete this.pendingRequests[xhrId];

    // success status == 0 can be on ftp, file and other protocols
    if (xhr.status === 0 && this.isHttp) {
      if (pendingRequest.onError) {
        pendingRequest.onError(xhr.status);
      }
      return;
    }
    var xhrStatus = xhr.status || OK_RESPONSE;

    // From http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.2:
    // "A server MAY ignore the Range header". This means it's possible to
    // get a 200 rather than a 206 response from a range request.
    var ok_response_on_range_request =
      xhrStatus === OK_RESPONSE &&
      pendingRequest.expectedStatus === PARTIAL_CONTENT_RESPONSE;

    if (
      !ok_response_on_range_request &&
      xhrStatus !== pendingRequest.expectedStatus
    ) {
      if (pendingRequest.onError) {
        pendingRequest.onError(xhr.status);
      }
      return;
    }

    const chunk = getTypedArray(xhr);
    if (xhrStatus === PARTIAL_CONTENT_RESPONSE) {
      var rangeHeader = xhr.getResponseHeader("Content-Range");
      var matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader);
      var begin = parseInt(matches[1], 10);
      pendingRequest.onDone({
        begin,
        chunk,
      });
    } else if (chunk) {
      pendingRequest.onDone({
        begin: 0,
        chunk,
      });
    } else if (pendingRequest.onError) {
      pendingRequest.onError(xhr.status);
    }
  }

  abortAllRequests() {
    for (var xhrId in this.pendingRequests) {
      this.abortRequest(xhrId | 0);
    }
  }

  abortRequest(xhrId) {
    var xhr = this.pendingRequests[xhrId].xhr;
    delete this.pendingRequests[xhrId];
    xhr.abort();
  }
}
PK
!<��WW+chrome/pdfjs/content/PdfJsTelemetry.sys.mjs/* Copyright 2013 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

export class PdfJsTelemetryContent {
  static onViewerIsUsed() {
    Glean.pdfjs.used.add(1);
  }
}

export class PdfJsTelemetry {
  static report(aData) {
    const { type } = aData;
    switch (type) {
      case "pageInfo":
        this.onTimeToView(aData.timestamp);
        break;
      case "editing":
        this.onEditing(aData);
        break;
      case "buttons":
      case "gv-buttons":
        {
          const id = aData.data.id.replace(
            /([A-Z])/g,
            c => `_${c.toLowerCase()}`
          );
          if (type === "buttons") {
            this.onButtons(id);
          } else {
            this.onGeckoview(id);
          }
        }
        break;
    }
  }

  static onTimeToView(ms) {
    Glean.pdfjs.timeToView.accumulateSingleSample(ms);
  }

  static onEditing({ type, data }) {
    if (type !== "editing" || !data) {
      return;
    }

    if (!data.type && data.action?.startsWith("pdfjs.image")) {
      this.onImage(data);
      return;
    }

    switch (data.type) {
      case "freetext":
      case "ink":
        Glean.pdfjs.editing[data.type].add(1);
        return;
      case "print":
      case "save": {
        Glean.pdfjs.editing[data.type].add(1);
        const { stats } = data;
        if (!stats) {
          return;
        }
        if (data.type === "highlight") {
          const numbers = ["one", "two", "three", "four", "five"];
          Glean.pdfjsEditingHighlight[data.type].add(1);
          Glean.pdfjsEditingHighlight.numberOfColors[
            numbers[stats.highlight.numberOfColors - 1]
          ].add(1);
          return;
        }
        if (stats.stamp) {
          this.onImage({
            action: "pdfjs.image.added",
            data: stats.stamp,
          });
        }
        return;
      }
      case "stamp":
        if (data.action.startsWith("pdfjs.image.")) {
          this.onImage(data);
          return;
        }

        if (data.action === "added") {
          Glean.pdfjs.editing.stamp.add(1);
          return;
        }
        Glean.pdfjs.stamp[data.action].add(1);
        for (const key of [
          "alt_text_keyboard",
          "alt_text_decorative",
          "alt_text_description",
          "alt_text_edit",
        ]) {
          if (data[key]) {
            Glean.pdfjs.stamp[key].add(1);
          }
        }
        return;
      case "highlight":
      case "free_highlight":
        switch (data.action) {
          case "added":
            Glean.pdfjsEditingHighlight.kind[data.type].add(1);
            Glean.pdfjsEditingHighlight.method[data.methodOfCreation].add(1);
            Glean.pdfjsEditingHighlight.color[data.color].add(1);
            if (data.type === "free_highlight") {
              Glean.pdfjsEditingHighlight.thickness.accumulateSingleSample(
                data.thickness
              );
            }
            break;
          case "color_changed":
            Glean.pdfjsEditingHighlight.color[data.color].add(1);
            Glean.pdfjsEditingHighlight.colorChanged.add(1);
            break;
          case "thickness_changed":
            Glean.pdfjsEditingHighlight.thickness.accumulateSingleSample(
              data.thickness
            );
            Glean.pdfjsEditingHighlight.thicknessChanged.add(1);
            break;
          case "deleted":
            Glean.pdfjsEditingHighlight.deleted.add(1);
            break;
          case "edited":
            Glean.pdfjsEditingHighlight.edited.add(1);
            break;
          case "toggle_visibility":
            Glean.pdfjsEditingHighlight.toggleVisibility.add(1);
            break;
        }
        break;
    }
  }

  static onImage({ action, data = {} }) {
    action = action.split(".").pop();

    switch (action) {
      // New alt text telemetry.
      case "info":
        Glean.pdfjsImageAltText.info.record(data);
        break;
      case "ai_generation_check":
        Glean.pdfjsImageAltText.aiGenerationCheck.record(data);
        break;
      case "settings_displayed":
        Glean.pdfjsImageAltText.settingsDisplayed.record(data);
        break;
      case "settings_ai_generation_check":
        Glean.pdfjsImageAltText.settingsAiGenerationCheck.record(data);
        break;
      case "settings_edit_alt_text_check":
        Glean.pdfjsImageAltText.settingsEditAltTextCheck.record(data);
        break;
      case "save":
        Glean.pdfjsImageAltText.save.record(data);
        break;
      case "dismiss":
        Glean.pdfjsImageAltText.dismiss.record(data);
        break;
      case "model_download_start":
        Glean.pdfjsImageAltText.modelDownloadStart.record(data);
        break;
      case "model_download_complete":
        Glean.pdfjsImageAltText.modelDownloadComplete.record(data);
        break;
      case "model_download_error":
        Glean.pdfjsImageAltText.modelDownloadError.record(data);
        break;
      case "model_deleted":
        Glean.pdfjsImageAltText.modelDeleted.record(data);
        break;
      case "model_result":
        Glean.pdfjsImageAltText.modelResult.record(data);
        break;
      case "user_edit":
        Glean.pdfjsImageAltText.userEdit.record(data);
        break;
      case "image_status_label_displayed":
        Glean.pdfjsImageAltText.imageStatusLabelDisplayed.record(data);
        break;
      case "image_status_label_clicked":
        Glean.pdfjsImageAltText.imageStatusLabelClicked.record(data);
        break;
      // Image telemetry.
      case "icon_click":
        Glean.pdfjsImage.iconClick.record(data);
        break;
      case "add_image_click":
        Glean.pdfjsImage.addImageClick.record(data);
        break;
      case "image_selected":
        Glean.pdfjsImage.imageSelected.record(data);
        break;
      case "image_added":
        Glean.pdfjsImage.imageAdded.record(data);
        break;
      case "added": {
        const { hasAltText, hasNoAltText } = data;
        Glean.pdfjsImage.added.with_alt_text.add(hasAltText);
        Glean.pdfjsImage.added.with_no_alt_text.add(hasNoAltText);
        break;
      }
      case "alt_text_edit":
        Glean.pdfjsImage.altTextEdit.ask_to_edit.set(data.ask_to_edit);
        Glean.pdfjsImage.altTextEdit.ai_generation.set(data.ai_generation);
        break;
    }
  }

  static onButtons(id) {
    Glean.pdfjs.buttons[id].add(1);
  }

  static onGeckoview(id) {
    Glean.pdfjs.geckoview[id].add(1);
  }
}
PK
!<�܏�g	g	'chrome/pdfjs/content/PdfSandbox.sys.mjs/* Copyright 2020 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { SandboxSupportBase } from "resource://pdf.js/build/pdf.sandbox.external.sys.mjs";

class SandboxSupport extends SandboxSupportBase {
  constructor(win, sandbox) {
    super(win);
    this.sandbox = sandbox;
  }
  exportValueToSandbox(val) {
    return Cu.cloneInto(val, this.sandbox);
  }

  importValueFromSandbox(val) {
    return Cu.cloneInto(val, this.win);
  }

  createErrorForSandbox(errorMessage) {
    return new this.sandbox.Error(errorMessage);
  }
}

export class PdfSandbox {
  constructor(window, data) {
    this.window = window;
    const sandbox = Cu.Sandbox(null, {
      sandboxName: "PDF.js scripting sandbox",
      sameZoneAs: window,
      wantXrays: true,
      wantGlobalProperties: [],
      wantComponents: false,
      wantExportHelpers: false,
    });
    this.sandbox = sandbox;
    this.support = new SandboxSupport(window, sandbox);

    const sandboxInit = Cu.createObjectIn(sandbox);
    sandboxInit.data = Cu.cloneInto(data, sandbox);

    sandbox.callExternalFunction = Cu.exportFunction(
      this.support.createSandboxExternals(),
      sandbox
    );

    try {
      // Run the code in the bundle
      Services.scriptloader.loadSubScript(
        "resource://pdf.js/build/pdf.scripting.mjs",
        sandbox
      );

      this.support.commFun =
        sandbox.pdfjsScripting.wrappedJSObject.initSandbox(sandboxInit);
    } catch (error) {
      // error belongs to the sandbox so create a new one
      const msg = error.message;
      this.destroy();
      throw new Error(msg);
    }
  }

  dispatchEvent(event) {
    this.support.callSandboxFunction("dispatchEvent", event);
  }

  destroy() {
    if (this.sandbox) {
      this.support.destroy();
      this.support = null;
      Cu.nukeSandbox(this.sandbox);
      this.sandbox = null;
    }
  }
}
PK
!<��A�)�)�/chrome/pdfjs/content/PdfStreamConverter.sys.mjs/* Copyright 2012 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const PDFJS_EVENT_ID = "pdf.js.message";
const PDF_VIEWER_ORIGIN = "resource://pdf.js";
const PDF_VIEWER_WEB_PAGE = "resource://pdf.js/web/viewer.html";
const MAX_NUMBER_OF_PREFS = 50;
const PDF_CONTENT_TYPE = "application/pdf";

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";

// Non-pdfjs preferences to get when the viewer is created and to observe.
const toolbarDensityPref = "browser.uidensity";
const caretBrowsingModePref = "accessibility.browsewithcaret";

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
  NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
  NetworkManager: "resource://pdf.js/PdfJsNetwork.sys.mjs",
  PdfJs: "resource://pdf.js/PdfJs.sys.mjs",
  PdfJsTelemetryContent: "resource://pdf.js/PdfJsTelemetry.sys.mjs",
  PdfSandbox: "resource://pdf.js/PdfSandbox.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
});

var Svc = {};
XPCOMUtils.defineLazyServiceGetter(
  Svc,
  "mime",
  "@mozilla.org/mime;1",
  "nsIMIMEService"
);
XPCOMUtils.defineLazyServiceGetter(
  Svc,
  "handlers",
  "@mozilla.org/uriloader/handler-service;1",
  "nsIHandlerService"
);

ChromeUtils.defineLazyGetter(lazy, "gOurBinary", () => {
  let file = Services.dirsvc.get("XREExeF", Ci.nsIFile);
  // Make sure to get the .app on macOS
  if (AppConstants.platform == "macosx") {
    while (file) {
      if (/\.app\/?$/i.test(file.leafName)) {
        break;
      }
      file = file.parent;
    }
  }
  return file;
});

function log(aMsg) {
  if (!Services.prefs.getBoolPref("pdfjs.pdfBugEnabled", false)) {
    return;
  }
  var msg = "PdfStreamConverter.js: " + (aMsg.join ? aMsg.join("") : aMsg);
  Services.console.logStringMessage(msg);
  dump(msg + "\n");
}

function getDOMWindow(aChannel, aPrincipal) {
  var requestor = aChannel.notificationCallbacks
    ? aChannel.notificationCallbacks
    : aChannel.loadGroup.notificationCallbacks;
  var win = requestor.getInterface(Ci.nsIDOMWindow);
  // Ensure the window wasn't navigated to something that is not PDF.js.
  if (!win.document.nodePrincipal.equals(aPrincipal)) {
    return null;
  }
  return win;
}

function getActor(window) {
  try {
    const actorName =
      AppConstants.platform === "android" ? "GeckoViewPdfjs" : "Pdfjs";
    return window.windowGlobalChild.getActor(actorName);
  } catch (ex) {
    return null;
  }
}

function isValidMatchesCount(data) {
  if (typeof data !== "object" || data === null) {
    return false;
  }
  const { current, total } = data;
  if (
    typeof total !== "number" ||
    total < 0 ||
    typeof current !== "number" ||
    current < 0 ||
    current > total
  ) {
    return false;
  }
  return true;
}

// PDF data storage
function PdfDataListener(length) {
  this.length = length; // less than 0, if length is unknown
  this.buffers = [];
  this.loaded = 0;
}

PdfDataListener.prototype = {
  append: function PdfDataListener_append(chunk) {
    // In most of the cases we will pass data as we receive it, but at the
    // beginning of the loading we may accumulate some data.
    this.buffers.push(chunk);
    this.loaded += chunk.length;
    if (this.length >= 0 && this.length < this.loaded) {
      this.length = -1; // reset the length, server is giving incorrect one
    }
    this.onprogress(this.loaded, this.length >= 0 ? this.length : void 0);
  },
  readData: function PdfDataListener_readData() {
    if (this.buffers.length === 0) {
      return null;
    }
    if (this.buffers.length === 1) {
      return this.buffers.pop();
    }
    // There are multiple buffers that need to be combined into a single
    // buffer.
    let combinedLength = 0;
    for (let buffer of this.buffers) {
      combinedLength += buffer.length;
    }
    let combinedArray = new Uint8Array(combinedLength);
    let writeOffset = 0;
    while (this.buffers.length) {
      let buffer = this.buffers.shift();
      combinedArray.set(buffer, writeOffset);
      writeOffset += buffer.length;
    }
    return combinedArray;
  },
  get isDone() {
    return !!this.isDataReady;
  },
  finish: function PdfDataListener_finish() {
    this.isDataReady = true;
    if (this.oncompleteCallback) {
      this.oncompleteCallback(this.readData());
    }
  },
  error: function PdfDataListener_error(errorCode) {
    this.errorCode = errorCode;
    if (this.oncompleteCallback) {
      this.oncompleteCallback(null, errorCode);
    }
  },
  onprogress() {},
  get oncomplete() {
    return this.oncompleteCallback;
  },
  set oncomplete(value) {
    this.oncompleteCallback = value;
    if (this.isDataReady) {
      value(this.readData());
    }
    if (this.errorCode) {
      value(null, this.errorCode);
    }
  },
};

class PrefObserver {
  #domWindow;

  #prefs = new Map([
    [
      caretBrowsingModePref,
      {
        name: "supportsCaretBrowsingMode",
        type: "bool",
        dispatchToContent: true,
      },
    ],
  ]);

  constructor(domWindow, isMobile) {
    this.#domWindow = domWindow;
    this.#init(isMobile);
  }

  #init(isMobile) {
    if (!isMobile) {
      this.#prefs.set(toolbarDensityPref, {
        name: "toolbarDensity",
        type: "int",
        dispatchToContent: true,
      });
      this.#prefs.set("pdfjs.enableGuessAltText", {
        name: "enableGuessAltText",
        type: "bool",
        dispatchToContent: true,
        dispatchToParent: true,
      });
      this.#prefs.set("pdfjs.enableAltTextModelDownload", {
        name: "enableAltTextModelDownload",
        type: "bool",
        dispatchToContent: true,
        dispatchToParent: true,
      });

      // Once the experiment for new alt-text stuff is removed, we can remove this.
      this.#prefs.set("pdfjs.enableAltText", {
        name: "enableAltText",
        type: "bool",
        dispatchToParent: true,
      });
      this.#prefs.set("pdfjs.enableNewAltTextWhenAddingImage", {
        name: "enableNewAltTextWhenAddingImage",
        type: "bool",
        dispatchToParent: true,
      });
      this.#prefs.set("browser.ml.enable", {
        name: "browser.ml.enable",
        type: "bool",
        dispatchToParent: true,
      });
    }
    for (const pref of this.#prefs.keys()) {
      Services.prefs.addObserver(pref, this, /* aHoldWeak = */ true);
    }
  }

  observe(_aSubject, aTopic, aPrefName) {
    if (aTopic != "nsPref:changed") {
      return;
    }

    const actor = getActor(this.#domWindow);
    if (!actor) {
      return;
    }
    const { name, type, dispatchToContent, dispatchToParent } =
      this.#prefs.get(aPrefName) || {};
    if (!name) {
      return;
    }
    let value;
    switch (type) {
      case "bool": {
        value = Services.prefs.getBoolPref(aPrefName);
        break;
      }
      case "int": {
        value = Services.prefs.getIntPref(aPrefName);
        break;
      }
    }
    const data = { name, value };
    if (dispatchToContent) {
      actor.dispatchEvent("updatedPreference", data);
    }
    if (dispatchToParent) {
      actor.sendAsyncMessage("PDFJS:Parent:updatedPreference", data);
    }
  }

  QueryInterface = ChromeUtils.generateQI([Ci.nsISupportsWeakReference]);
}

/**
 * All the privileged actions.
 */
class ChromeActions {
  #allowedGlobalEvents = new Set([
    "documentloaded",
    "pagesloaded",
    "layersloaded",
    "outlineloaded",
  ]);

  constructor(domWindow, contentDispositionFilename) {
    this.domWindow = domWindow;
    this.contentDispositionFilename = contentDispositionFilename;
    this.sandbox = null;
    this.unloadListener = null;
    this.observer = new PrefObserver(domWindow, this.isMobile());
  }

  createSandbox(data, sendResponse) {
    function sendResp(res) {
      if (sendResponse) {
        sendResponse(res);
      }
      return res;
    }

    if (!Services.prefs.getBoolPref("pdfjs.enableScripting", false)) {
      return sendResp(false);
    }

    if (this.sandbox !== null) {
      return sendResp(true);
    }

    try {
      this.sandbox = new lazy.PdfSandbox(this.domWindow, data);
    } catch (err) {
      // If there's an error here, it means that something is really wrong
      // on pdf.js side during sandbox initialization phase.
      console.error(err);
      return sendResp(false);
    }

    this.unloadListener = () => {
      this.destroySandbox();
    };
    this.domWindow.addEventListener("unload", this.unloadListener);

    return sendResp(true);
  }

  dispatchEventInSandbox(event) {
    if (this.sandbox) {
      this.sandbox.dispatchEvent(event);
    }
  }

  dispatchAsyncEventInSandbox(event, sendResponse) {
    this.dispatchEventInSandbox(event);
    sendResponse();
  }

  destroySandbox() {
    if (this.sandbox) {
      this.domWindow.removeEventListener("unload", this.unloadListener);
      this.sandbox.destroy();
      this.sandbox = null;
    }
  }

  isInPrivateBrowsing() {
    return lazy.PrivateBrowsingUtils.isContentWindowPrivate(this.domWindow);
  }

  getWindowOriginAttributes() {
    try {
      return this.domWindow.document.nodePrincipal.originAttributes;
    } catch (err) {
      return {};
    }
  }

  async mlDelete(data, sendResponse) {
    const actor = getActor(this.domWindow);
    if (!actor) {
      sendResponse(null);
      return;
    }
    const response = await actor.sendQuery("PDFJS:Parent:mlDelete", data);
    sendResponse(response);
  }

  async mlGuess(data, sendResponse) {
    const actor = getActor(this.domWindow);
    if (!actor) {
      sendResponse(null);
      return;
    }
    const response = await actor.sendQuery("PDFJS:Parent:mlGuess", data);
    sendResponse(response);
  }

  async loadAIEngine(data, sendResponse) {
    const actor = getActor(this.domWindow);
    if (!actor) {
      sendResponse(null);
      return;
    }
    sendResponse(await actor.sendQuery("PDFJS:Parent:loadAIEngine", data));
  }

  download(data) {
    const { originalUrl } = data;
    const blobUrl = data.blobUrl || originalUrl;
    let { filename } = data;
    if (
      typeof filename !== "string" ||
      (!/\.pdf$/i.test(filename) && !data.isAttachment)
    ) {
      filename = "document.pdf";
    }
    const actor = getActor(this.domWindow);
    actor.sendAsyncMessage("PDFJS:Parent:saveURL", {
      blobUrl,
      originalUrl,
      filename,
    });
  }

  getLocaleProperties() {
    const { requestedLocale, defaultLocale, isAppLocaleRTL } = Services.locale;
    return {
      lang: requestedLocale || defaultLocale,
      isRTL: isAppLocaleRTL,
    };
  }

  supportsIntegratedFind() {
    // Integrated find is only supported when we're not in a frame
    return this.domWindow.windowGlobalChild.browsingContext.parent === null;
  }

  async getBrowserPrefs() {
    const isMobile = this.isMobile();
    const nimbusDataStr = isMobile
      ? await this.getNimbusExperimentData()
      : null;

    return {
      allowedGlobalEvents: this.#allowedGlobalEvents,
      canvasMaxAreaInBytes: Services.prefs.getIntPref("gfx.max-alloc-size"),
      isInAutomation: Cu.isInAutomation,
      localeProperties: this.getLocaleProperties(),
      nimbusDataStr,
      supportsDocumentFonts:
        !!Services.prefs.getIntPref("browser.display.use_document_fonts") &&
        Services.prefs.getBoolPref("gfx.downloadable_fonts.enabled"),
      supportsIntegratedFind: this.supportsIntegratedFind(),
      supportsMouseWheelZoomCtrlKey:
        Services.prefs.getIntPref("mousewheel.with_control.action") === 3,
      supportsMouseWheelZoomMetaKey:
        Services.prefs.getIntPref("mousewheel.with_meta.action") === 3,
      supportsPinchToZoom: Services.prefs.getBoolPref("apz.allow_zooming"),
      supportsCaretBrowsingMode: Services.prefs.getBoolPref(
        caretBrowsingModePref
      ),
      toolbarDensity: Services.prefs.getIntPref(toolbarDensityPref, 0),
    };
  }

  isMobile() {
    return AppConstants.platform === "android";
  }

  async getNimbusExperimentData() {
    if (!this.isMobile()) {
      return null;
    }
    const { promise, resolve } = Promise.withResolvers();

    const actor = getActor(this.domWindow);
    actor.sendAsyncMessage("PDFJS:Parent:getNimbus");
    Services.obs.addObserver(
      {
        observe(aSubject, aTopic) {
          if (aTopic === "pdfjs-getNimbus") {
            Services.obs.removeObserver(this, aTopic);
            resolve(aSubject && JSON.stringify(aSubject.wrappedJSObject));
          }
        },
      },
      "pdfjs-getNimbus"
    );
    return promise;
  }

  async dispatchGlobalEvent({ eventName, detail }) {
    if (!this.#allowedGlobalEvents.has(eventName)) {
      return;
    }
    const windowUtils = this.domWindow.windowUtils;
    if (!windowUtils) {
      return;
    }
    const event = new CustomEvent(eventName, {
      bubbles: true,
      cancelable: false,
      detail,
    });
    windowUtils.dispatchEventToChromeOnly(this.domWindow, event);
  }

  reportTelemetry(data) {
    const actor = getActor(this.domWindow);
    actor?.sendAsyncMessage("PDFJS:Parent:reportTelemetry", data);
  }

  updateFindControlState(data) {
    if (!this.supportsIntegratedFind()) {
      return;
    }
    // Verify what we're sending to the findbar.
    var result = data.result;
    var findPrevious = data.findPrevious;
    var findPreviousType = typeof findPrevious;
    if (
      typeof result !== "number" ||
      result < 0 ||
      result > 3 ||
      (findPreviousType !== "undefined" && findPreviousType !== "boolean")
    ) {
      return;
    }
    // Allow the `matchesCount` property to be optional, and ensure that
    // it's valid before including it in the data sent to the findbar.
    let matchesCount = null;
    if (isValidMatchesCount(data.matchesCount)) {
      matchesCount = data.matchesCount;
    }
    // Same for the `rawQuery` property.
    let rawQuery = null;
    if (typeof data.rawQuery === "string") {
      rawQuery = data.rawQuery;
    }

    let actor = getActor(this.domWindow);
    actor?.sendAsyncMessage("PDFJS:Parent:updateControlState", {
      result,
      findPrevious,
      matchesCount,
      rawQuery,
    });
  }

  updateFindMatchesCount(data) {
    if (!this.supportsIntegratedFind()) {
      return;
    }
    // Verify what we're sending to the findbar.
    if (!isValidMatchesCount(data)) {
      return;
    }

    let actor = getActor(this.domWindow);
    actor?.sendAsyncMessage("PDFJS:Parent:updateMatchesCount", data);
  }

  async getPreferences(prefs, sendResponse) {
    const browserPrefs = await this.getBrowserPrefs();

    var defaultBranch = Services.prefs.getDefaultBranch("pdfjs.");
    var currentPrefs = {},
      numberOfPrefs = 0;
    for (var key in prefs) {
      if (++numberOfPrefs > MAX_NUMBER_OF_PREFS) {
        log(
          "getPreferences - Exceeded the maximum number of preferences " +
            "that is allowed to be fetched at once."
        );
        break;
      } else if (!defaultBranch.getPrefType(key)) {
        continue;
      }
      const prefName = `pdfjs.${key}`,
        prefValue = prefs[key];
      switch (typeof prefValue) {
        case "boolean":
          currentPrefs[key] = Services.prefs.getBoolPref(prefName, prefValue);
          break;
        case "number":
          currentPrefs[key] = Services.prefs.getIntPref(prefName, prefValue);
          break;
        case "string":
          // The URL contains some dynamic values (%VERSION%, ...), so we need to
          // format it.
          currentPrefs[key] =
            key === "altTextLearnMoreUrl"
              ? Services.urlFormatter.formatURLPref(prefName)
              : Services.prefs.getStringPref(prefName, prefValue);
          break;
      }
    }

    sendResponse({
      browserPrefs,
      prefs: currentPrefs,
    });
  }

  async setPreferences(data, sendResponse) {
    const actor = getActor(this.domWindow);
    await actor?.sendQuery("PDFJS:Parent:setPreferences", data);

    sendResponse(null);
  }

  /**
   * Set the different editor states in order to be able to update the context
   * menu.
   * @param {Object} details
   */
  updateEditorStates({ details }) {
    const doc = this.domWindow.document;
    if (!doc.editorStates) {
      doc.editorStates = {
        isEditing: false,
        isEmpty: true,
        hasSomethingToUndo: false,
        hasSomethingToRedo: false,
        hasSelectedEditor: false,
        hasSelectedText: false,
      };
    }
    const { editorStates } = doc;
    for (const [key, value] of Object.entries(details)) {
      if (typeof value === "boolean" && key in editorStates) {
        editorStates[key] = value;
      }
    }
  }
}

/**
 * This is for range requests.
 */
class RangedChromeActions extends ChromeActions {
  constructor(
    domWindow,
    contentDispositionFilename,
    originalRequest,
    rangeEnabled,
    streamingEnabled,
    dataListener
  ) {
    super(domWindow, contentDispositionFilename);
    this.dataListener = dataListener;
    this.originalRequest = originalRequest;
    this.rangeEnabled = rangeEnabled;
    this.streamingEnabled = streamingEnabled;

    this.pdfUrl = originalRequest.URI.spec;
    this.contentLength = originalRequest.contentLength;

    // Pass all the headers from the original request through
    var httpHeaderVisitor = {
      headers: {},
      visitHeader(aHeader, aValue) {
        if (aHeader === "Range") {
          // When loading the PDF from cache, firefox seems to set the Range
          // request header to fetch only the unfetched portions of the file
          // (e.g. 'Range: bytes=1024-'). However, we want to set this header
          // manually to fetch the PDF in chunks.
          return;
        }
        this.headers[aHeader] = aValue;
      },
    };
    if (originalRequest.visitRequestHeaders) {
      originalRequest.visitRequestHeaders(httpHeaderVisitor);
    }

    var self = this;
    var xhr_onreadystatechange = function xhr_onreadystatechange() {
      if (this.readyState === 1) {
        // LOADING
        var netChannel = this.channel;
        // override this XMLHttpRequest's OriginAttributes with our cached parent window's
        // OriginAttributes, as we are currently running under the SystemPrincipal
        this.setOriginAttributes(self.getWindowOriginAttributes());
        if (
          "nsIPrivateBrowsingChannel" in Ci &&
          netChannel instanceof Ci.nsIPrivateBrowsingChannel
        ) {
          var docIsPrivate = self.isInPrivateBrowsing();
          netChannel.setPrivate(docIsPrivate);
        }
      }
    };
    var getXhr = function getXhr() {
      var xhr = new XMLHttpRequest({ mozAnon: false });
      xhr.addEventListener("readystatechange", xhr_onreadystatechange);
      return xhr;
    };

    this.networkManager = new lazy.NetworkManager(this.pdfUrl, {
      httpHeaders: httpHeaderVisitor.headers,
      getXhr,
    });

    // If we are in range request mode, this means we manually issued xhr
    // requests, which we need to abort when we leave the page
    domWindow.addEventListener("unload", function unload(e) {
      domWindow.removeEventListener(e.type, unload);
      self.abortLoading();
    });
  }

  initPassiveLoading() {
    let data, done;
    if (!this.streamingEnabled) {
      this.originalRequest.cancel(Cr.NS_BINDING_ABORTED);
      this.originalRequest = null;
      data = this.dataListener.readData();
      done = this.dataListener.isDone;
      this.dataListener = null;
    } else {
      data = this.dataListener.readData();
      done = this.dataListener.isDone;

      this.dataListener.onprogress = (loaded, total) => {
        const chunk = this.dataListener.readData();

        this.domWindow.postMessage(
          {
            pdfjsLoadAction: "progressiveRead",
            loaded,
            total,
            chunk,
          },
          PDF_VIEWER_ORIGIN,
          chunk ? [chunk.buffer] : undefined
        );
      };
      this.dataListener.oncomplete = () => {
        if (!done && this.dataListener.isDone) {
          this.domWindow.postMessage(
            {
              pdfjsLoadAction: "progressiveDone",
            },
            PDF_VIEWER_ORIGIN
          );
        }
        this.dataListener = null;
      };
    }

    this.domWindow.postMessage(
      {
        pdfjsLoadAction: "supportsRangedLoading",
        rangeEnabled: this.rangeEnabled,
        streamingEnabled: this.streamingEnabled,
        pdfUrl: this.pdfUrl,
        length: this.contentLength,
        data,
        done,
        filename: this.contentDispositionFilename,
      },
      PDF_VIEWER_ORIGIN,
      data ? [data.buffer] : undefined
    );

    return true;
  }

  requestDataRange(args) {
    if (!this.rangeEnabled) {
      return;
    }

    var begin = args.begin;
    var end = args.end;
    var domWindow = this.domWindow;
    // TODO(mack): Support error handler. We're not currently not handling
    // errors from chrome code for non-range requests, so this doesn't
    // seem high-pri
    this.networkManager.requestRange(begin, end, {
      onDone: function RangedChromeActions_onDone({ begin, chunk }) {
        domWindow.postMessage(
          {
            pdfjsLoadAction: "range",
            begin,
            chunk,
          },
          PDF_VIEWER_ORIGIN,
          chunk ? [chunk.buffer] : undefined
        );
      },
      onProgress: function RangedChromeActions_onProgress(evt) {
        domWindow.postMessage(
          {
            pdfjsLoadAction: "rangeProgress",
            loaded: evt.loaded,
          },
          PDF_VIEWER_ORIGIN
        );
      },
    });
  }

  abortLoading() {
    this.networkManager.abortAllRequests();
    if (this.originalRequest) {
      this.originalRequest.cancel(Cr.NS_BINDING_ABORTED);
      this.originalRequest = null;
    }
    this.dataListener = null;
  }
}

/**
 * This is for a single network stream.
 */
class StandardChromeActions extends ChromeActions {
  constructor(
    domWindow,
    contentDispositionFilename,
    originalRequest,
    dataListener
  ) {
    super(domWindow, contentDispositionFilename);
    this.originalRequest = originalRequest;
    this.dataListener = dataListener;
  }

  initPassiveLoading() {
    if (!this.dataListener) {
      return false;
    }

    this.dataListener.onprogress = (loaded, total) => {
      this.domWindow.postMessage(
        {
          pdfjsLoadAction: "progress",
          loaded,
          total,
        },
        PDF_VIEWER_ORIGIN
      );
    };

    this.dataListener.oncomplete = (data, errorCode) => {
      this.domWindow.postMessage(
        {
          pdfjsLoadAction: "complete",
          data,
          errorCode,
          filename: this.contentDispositionFilename,
        },
        PDF_VIEWER_ORIGIN,
        data ? [data.buffer] : undefined
      );

      this.dataListener = null;
      this.originalRequest = null;
    };

    return true;
  }

  abortLoading() {
    if (this.originalRequest) {
      this.originalRequest.cancel(Cr.NS_BINDING_ABORTED);
      this.originalRequest = null;
    }
    this.dataListener = null;
  }
}

/**
 * Event listener to trigger chrome privileged code.
 */
class RequestListener {
  constructor(actions) {
    this.actions = actions;
  }

  // Receive an event and (optionally) asynchronously responds.
  receive({ target, detail }) {
    const doc = target.ownerDocument;
    const { action, data, responseExpected } = detail;

    const actionFn = this.actions[action];
    if (!actionFn) {
      log("Unknown action: " + action);
      return;
    }
    let response = null;

    if (!responseExpected) {
      doc.documentElement.removeChild(target);
    } else {
      response = function (aResponse) {
        try {
          const listener = doc.createEvent("CustomEvent");
          const detail = Cu.cloneInto({ response: aResponse }, doc.defaultView);
          listener.initCustomEvent("pdf.js.response", true, false, detail);
          return target.dispatchEvent(listener);
        } catch (e) {
          // doc is no longer accessible because the requestor is already
          // gone. unloaded content cannot receive the response anyway.
          return false;
        }
      };
    }
    actionFn.call(this.actions, data, response);
  }
}

export function PdfStreamConverter() {}

PdfStreamConverter.prototype = {
  QueryInterface: ChromeUtils.generateQI([
    "nsIStreamConverter",
    "nsIStreamListener",
    "nsIRequestObserver",
  ]),

  /*
   * This component works as such:
   * 1. asyncConvertData stores the listener
   * 2. onStartRequest creates a new channel, streams the viewer
   * 3. If range requests are supported:
   *      3.1. Leave the request open until the viewer is ready to switch to
   *           range requests.
   *
   *    If range rquests are not supported:
   *      3.1. Read the stream as it's loaded in onDataAvailable to send
   *           to the viewer
   *
   * The convert function just returns the stream, it's just the synchronous
   * version of asyncConvertData.
   */

  // nsIStreamConverter::convert
  convert() {
    throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
  },

  // nsIStreamConverter::asyncConvertData
  asyncConvertData(aFromType, aToType, aListener, aCtxt) {
    if (aCtxt && aCtxt instanceof Ci.nsIChannel) {
      aCtxt.QueryInterface(Ci.nsIChannel);
    }
    // We need to check if we're supposed to convert here, because not all
    // asyncConvertData consumers will call getConvertedType first:
    this.getConvertedType(aFromType, aCtxt);

    // Store the listener passed to us
    this.listener = aListener;
  },

  _usableHandler(handlerInfo) {
    let { preferredApplicationHandler } = handlerInfo;
    if (
      !preferredApplicationHandler ||
      !(preferredApplicationHandler instanceof Ci.nsILocalHandlerApp)
    ) {
      return false;
    }
    preferredApplicationHandler.QueryInterface(Ci.nsILocalHandlerApp);
    // We have an app, grab the executable
    let { executable } = preferredApplicationHandler;
    if (!executable) {
      return false;
    }
    return !executable.equals(lazy.gOurBinary);
  },

  /*
   * Check if the user wants to use PDF.js. Returns true if PDF.js should
   * handle PDFs, and false if not. Will always return true on non-parent
   * processes.
   *
   * If the user has selected to open PDFs with a helper app, and we are that
   * helper app, or if the user has selected the OS default, and we are that
   * OS default, reset the preference back to pdf.js .
   *
   */
  _validateAndMaybeUpdatePDFPrefs() {
    let { processType, PROCESS_TYPE_DEFAULT } = Services.appinfo;
    // If we're not in the parent, or are the default, then just say yes.
    if (processType != PROCESS_TYPE_DEFAULT || lazy.PdfJs.cachedIsDefault()) {
      return { shouldOpen: true };
    }

    // OK, PDF.js might not be the default. Find out if we've misled the user
    // into making Firefox an external handler or if we're the OS default and
    // Firefox is set to use the OS default:
    let mime = Svc.mime.getFromTypeAndExtension(PDF_CONTENT_TYPE, "pdf");
    // The above might throw errors. We're deliberately letting those bubble
    // back up, where they'll tell the stream converter not to use us.

    if (!mime) {
      // This shouldn't happen, but we can't fix what isn't there. Assume
      // we're OK to handle with PDF.js
      return { shouldOpen: true };
    }

    const { saveToDisk, useHelperApp, useSystemDefault } = Ci.nsIHandlerInfo;
    let { preferredAction, alwaysAskBeforeHandling } = mime;
    // return this info so getConvertedType can use it.
    let rv = { alwaysAskBeforeHandling, shouldOpen: false };
    // If the user has indicated they want to be asked or want to save to
    // disk, we shouldn't render inline immediately:
    if (alwaysAskBeforeHandling || preferredAction == saveToDisk) {
      return rv;
    }
    // If we have usable helper app info, don't use PDF.js
    if (preferredAction == useHelperApp && this._usableHandler(mime)) {
      return rv;
    }
    // If we want the OS default and that's not Firefox, don't use PDF.js
    if (preferredAction == useSystemDefault && !mime.isCurrentAppOSDefault()) {
      return rv;
    }
    rv.shouldOpen = true;
    // Log that we're doing this to help debug issues if people end up being
    // surprised by this behaviour.
    console.error("Found unusable PDF preferences. Fixing back to PDF.js");

    mime.preferredAction = Ci.nsIHandlerInfo.handleInternally;
    mime.alwaysAskBeforeHandling = false;
    Svc.handlers.store(mime);
    return true;
  },

  getConvertedType(aFromType, aChannel) {
    if (aChannel instanceof Ci.nsIMultiPartChannel) {
      throw new Components.Exception(
        "PDF.js doesn't support multipart responses.",
        Cr.NS_ERROR_NOT_IMPLEMENTED
      );
    }

    const HTML = "text/html";
    let channelURI = aChannel?.URI;
    // We can be invoked for application/octet-stream; check if we want the
    // channel first:
    if (aFromType != "application/pdf") {
      // Check if the filename has a PDF extension.
      let isPDF = false;
      try {
        isPDF = aChannel.contentDispositionFilename.endsWith(".pdf");
      } catch (ex) {}
      if (!isPDF) {
        isPDF =
          channelURI?.QueryInterface(Ci.nsIURL).fileExtension.toLowerCase() ==
          "pdf";
      }

      let browsingContext = aChannel?.loadInfo.targetBrowsingContext;
      let toplevelOctetStream =
        aFromType == "application/octet-stream" &&
        browsingContext &&
        !browsingContext.parent;
      if (
        !isPDF ||
        !toplevelOctetStream ||
        !Services.prefs.getBoolPref("pdfjs.handleOctetStream", false)
      ) {
        throw new Components.Exception(
          "Ignore PDF.js for this download.",
          Cr.NS_ERROR_FAILURE
        );
      }
      // fall through, this appears to be a pdf.
    }

    let { alwaysAskBeforeHandling, shouldOpen } =
      this._validateAndMaybeUpdatePDFPrefs();

    if (shouldOpen) {
      return HTML;
    }
    // Hm, so normally, no pdfjs. However... if this is a file: channel there
    // are some edge-cases.
    if (channelURI?.schemeIs("file")) {
      // If we're loaded with system principal, we were likely handed the PDF
      // by the OS or directly from the URL bar. Assume we should load it:
      let triggeringPrincipal = aChannel.loadInfo?.triggeringPrincipal;
      if (triggeringPrincipal?.isSystemPrincipal) {
        return HTML;
      }

      // If we're loading from a file: link, load it in PDF.js unless the user
      // has told us they always want to open/save PDFs.
      // This is because handing off the choice to open in Firefox itself
      // through the dialog doesn't work properly and making it work is
      // non-trivial (see https://bugzilla.mozilla.org/show_bug.cgi?id=1680147#c3 )
      // - and anyway, opening the file is what we do for *all*
      // other file types we handle internally (and users can then use other UI
      // to save or open it with other apps from there).
      if (triggeringPrincipal?.schemeIs("file") && alwaysAskBeforeHandling) {
        return HTML;
      }
    }

    throw new Components.Exception("Can't use PDF.js", Cr.NS_ERROR_FAILURE);
  },

  // nsIStreamListener::onDataAvailable
  onDataAvailable(aRequest, aInputStream, aOffset, aCount) {
    if (!this.dataListener) {
      return;
    }

    var binaryStream = this.binaryStream;
    binaryStream.setInputStream(aInputStream);
    let chunk = new ArrayBuffer(aCount);
    binaryStream.readArrayBuffer(aCount, chunk);
    this.dataListener.append(new Uint8Array(chunk));
  },

  // nsIRequestObserver::onStartRequest
  onStartRequest(aRequest) {
    // Setup the request so we can use it below.
    var isHttpRequest = false;
    try {
      aRequest.QueryInterface(Ci.nsIHttpChannel);
      isHttpRequest = true;
    } catch (e) {}

    var rangeRequest = false;
    var streamRequest = false;
    if (isHttpRequest) {
      var contentEncoding = "identity";
      try {
        contentEncoding = aRequest.getResponseHeader("Content-Encoding");
      } catch (e) {}

      var acceptRanges;
      try {
        acceptRanges = aRequest.getResponseHeader("Accept-Ranges");
      } catch (e) {}

      var hash = aRequest.URI.ref;
      const isPDFBugEnabled = Services.prefs.getBoolPref(
        "pdfjs.pdfBugEnabled",
        false
      );
      rangeRequest =
        contentEncoding === "identity" &&
        acceptRanges === "bytes" &&
        aRequest.contentLength >= 0 &&
        !Services.prefs.getBoolPref("pdfjs.disableRange", false) &&
        (!isPDFBugEnabled || !hash.toLowerCase().includes("disablerange=true"));
      streamRequest =
        contentEncoding === "identity" &&
        aRequest.contentLength >= 0 &&
        !Services.prefs.getBoolPref("pdfjs.disableStream", false) &&
        (!isPDFBugEnabled ||
          !hash.toLowerCase().includes("disablestream=true"));
    }

    aRequest.QueryInterface(Ci.nsIChannel);

    aRequest.QueryInterface(Ci.nsIWritablePropertyBag);

    var contentDispositionFilename;
    try {
      contentDispositionFilename = aRequest.contentDispositionFilename;
    } catch (e) {}

    if (
      contentDispositionFilename &&
      !/\.pdf$/i.test(contentDispositionFilename)
    ) {
      contentDispositionFilename += ".pdf";
    }

    // Change the content type so we don't get stuck in a loop.
    aRequest.setProperty("contentType", aRequest.contentType);
    aRequest.contentType = "text/html";
    if (isHttpRequest) {
      // We trust PDF viewer, using no CSP
      aRequest.setResponseHeader("Content-Security-Policy", "", false);
      aRequest.setResponseHeader(
        "Content-Security-Policy-Report-Only",
        "",
        false
      );
      // The viewer does not need to handle HTTP Refresh header.
      aRequest.setResponseHeader("Refresh", "", false);
    }

    lazy.PdfJsTelemetryContent.onViewerIsUsed();

    // The document will be loaded via the stream converter as html,
    // but since we may have come here via a download or attachment
    // that was opened directly, force the content disposition to be
    // inline so that the html document will be loaded normally instead
    // of going to the helper service.
    aRequest.contentDisposition = Ci.nsIChannel.DISPOSITION_FORCE_INLINE;

    // Creating storage for PDF data
    var contentLength = aRequest.contentLength;
    this.dataListener = new PdfDataListener(contentLength);
    this.binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
      Ci.nsIBinaryInputStream
    );

    // Create a new channel that is viewer loaded as a resource.
    var channel = lazy.NetUtil.newChannel({
      uri: PDF_VIEWER_WEB_PAGE,
      loadUsingSystemPrincipal: true,
    });

    var listener = this.listener;
    var dataListener = this.dataListener;
    // Proxy all the request observer calls, when it gets to onStopRequest
    // we can get the dom window.  We also intentionally pass on the original
    // request(aRequest) below so we don't overwrite the original channel and
    // trigger an assertion.
    var proxy = {
      onStartRequest() {
        listener.onStartRequest(aRequest);
      },
      onDataAvailable(request, inputStream, offset, count) {
        listener.onDataAvailable(aRequest, inputStream, offset, count);
      },
      onStopRequest(request, statusCode) {
        var domWindow = getDOMWindow(channel, resourcePrincipal);
        if (!Components.isSuccessCode(statusCode) || !domWindow) {
          // The request may have been aborted and the document may have been
          // replaced with something that is not PDF.js, abort attaching.
          listener.onStopRequest(aRequest, statusCode);
          return;
        }
        var actions;
        if (rangeRequest || streamRequest) {
          actions = new RangedChromeActions(
            domWindow,
            contentDispositionFilename,
            aRequest,
            rangeRequest,
            streamRequest,
            dataListener
          );
        } else {
          actions = new StandardChromeActions(
            domWindow,
            contentDispositionFilename,
            aRequest,
            dataListener
          );
        }

        var requestListener = new RequestListener(actions);
        domWindow.document.addEventListener(
          PDFJS_EVENT_ID,
          function (event) {
            requestListener.receive(event);
          },
          false,
          true
        );

        let actor = getActor(domWindow);
        actor?.init(actions.supportsIntegratedFind());
        actor?.sendAsyncMessage("PDFJS:Parent:recordExposure");
        listener.onStopRequest(aRequest, statusCode);
      },
    };

    // Keep the URL the same so the browser sees it as the same.
    channel.originalURI = aRequest.URI;
    channel.loadGroup = aRequest.loadGroup;
    channel.loadInfo.originAttributes = aRequest.loadInfo.originAttributes;

    // We can use the resource principal when data is fetched by the chrome,
    // e.g. useful for NoScript. Make make sure we reuse the origin attributes
    // from the request channel to keep isolation consistent.
    var uri = lazy.NetUtil.newURI(PDF_VIEWER_WEB_PAGE);
    var resourcePrincipal =
      Services.scriptSecurityManager.createContentPrincipal(
        uri,
        aRequest.loadInfo.originAttributes
      );
    // Remember the principal we would have had before we mess with it.
    let originalPrincipal =
      Services.scriptSecurityManager.getChannelResultPrincipal(aRequest);
    aRequest.owner = resourcePrincipal;
    aRequest.setProperty("noPDFJSPrincipal", originalPrincipal);

    channel.asyncOpen(proxy);
  },

  // nsIRequestObserver::onStopRequest
  onStopRequest(aRequest, aStatusCode) {
    if (!this.dataListener) {
      // Do nothing
      return;
    }

    if (Components.isSuccessCode(aStatusCode)) {
      this.dataListener.finish();
    } else {
      this.dataListener.error(aStatusCode);
    }
    delete this.dataListener;
    delete this.binaryStream;
  },
};
PK
!<��:PP'chrome/pdfjs/content/PdfjsChild.sys.mjs/* Copyright 2012 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

export class PdfjsChild extends JSWindowActorChild {
  init(aSupportsFind) {
    if (aSupportsFind) {
      this.sendAsyncMessage("PDFJS:Parent:addEventListener");
    }
  }

  dispatchEvent(aType, aDetail) {
    aDetail &&= Cu.cloneInto(aDetail, this.contentWindow);
    const contentWindow = this.contentWindow;
    const forward = contentWindow.document.createEvent("CustomEvent");
    forward.initCustomEvent(aType, true, true, aDetail);
    contentWindow.dispatchEvent(forward);
  }

  receiveMessage(msg) {
    switch (msg.name) {
      case "PDFJS:Child:handleEvent":
        this.dispatchEvent(msg.data.type, msg.data.detail);
        break;
      case "PDFJS:Editing":
        this.dispatchEvent("editingaction", msg.data);
        break;
      case "PDFJS:ZoomIn":
      case "PDFJS:ZoomOut":
      case "PDFJS:ZoomReset":
      case "PDFJS:Save": {
        const type = msg.name.split("PDFJS:")[1].toLowerCase();
        this.dispatchEvent(type, null);
        break;
      }
    }
    return null;
  }
}
PK
!<���&E@E@(chrome/pdfjs/content/PdfjsParent.sys.mjs/* Copyright 2012 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { PdfJsTelemetry } from "resource://pdf.js/PdfJsTelemetry.sys.mjs";

const lazy = {};

ChromeUtils.defineESModuleGetters(lazy, {
  clearTimeout: "resource://gre/modules/Timer.sys.mjs",
  createEngine: "chrome://global/content/ml/EngineProcess.sys.mjs",
  EngineProcess: "chrome://global/content/ml/EngineProcess.sys.mjs",
  IndexedDBCache: "chrome://global/content/ml/ModelHub.sys.mjs",
  MultiProgressAggregator: "chrome://global/content/ml/Utils.sys.mjs",
  Progress: "chrome://global/content/ml/Utils.sys.mjs",
  NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
  SetClipboardSearchString: "resource://gre/modules/Finder.sys.mjs",
  setTimeout: "resource://gre/modules/Timer.sys.mjs",
});

const IMAGE_TO_TEXT_TASK = "moz-image-to-text";
const ML_ENGINE_ID = "pdfjs";
const ML_ENGINE_MAX_TIMEOUT = 60000;

var Svc = {};
XPCOMUtils.defineLazyServiceGetter(
  Svc,
  "mime",
  "@mozilla.org/mime;1",
  "nsIMIMEService"
);

XPCOMUtils.defineLazyPreferenceGetter(
  lazy,
  "matchesCountLimit",
  "accessibility.typeaheadfind.matchesCountLimit"
);

let gFindTypes = [
  "find",
  "findagain",
  "findhighlightallchange",
  "findcasesensitivitychange",
  "findbarclose",
  "finddiacriticmatchingchange",
];

export class PdfjsParent extends JSWindowActorParent {
  #mutablePreferences = new Set([
    "enableGuessAltText",
    "enableAltTextModelDownload",
    "enableNewAltTextWhenAddingImage",
  ]);

  constructor() {
    super();
    this._boundToFindbar = null;
    this._findFailedString = null;

    this._updatedPreference();
  }

  didDestroy() {
    this._removeEventListener();
  }

  receiveMessage(aMsg) {
    switch (aMsg.name) {
      case "PDFJS:Parent:updateControlState":
        return this._updateControlState(aMsg);
      case "PDFJS:Parent:updateMatchesCount":
        return this._updateMatchesCount(aMsg);
      case "PDFJS:Parent:addEventListener":
        return this._addEventListener();
      case "PDFJS:Parent:saveURL":
        return this._saveURL(aMsg);
      case "PDFJS:Parent:recordExposure":
        return this._recordExposure();
      case "PDFJS:Parent:reportTelemetry":
        return this._reportTelemetry(aMsg);
      case "PDFJS:Parent:mlGuess":
        return this._mlGuess(aMsg);
      case "PDFJS:Parent:setPreferences":
        return this._setPreferences(aMsg);
      case "PDFJS:Parent:loadAIEngine":
        return this._loadAIEngine(aMsg);
      case "PDFJS:Parent:mlDelete":
        return this._mlDelete(aMsg);
      case "PDFJS:Parent:updatedPreference":
        return this._updatedPreference(aMsg);
    }
    return undefined;
  }

  /*
   * Internal
   */

  get browser() {
    return this.browsingContext.top.embedderElement;
  }

  _updatedPreference() {
    PdfJsTelemetry.report({
      type: "editing",
      data: {
        type: "stamp",
        action: "pdfjs.image.alt_text_edit",
        data: {
          ask_to_edit:
            Services.prefs.getBoolPref("pdfjs.enableAltText", false) &&
            Services.prefs.getBoolPref(
              "pdfjs.enableNewAltTextWhenAddingImage",
              false
            ),
          ai_generation:
            Services.prefs.getBoolPref("pdfjs.enableAltText", false) &&
            Services.prefs.getBoolPref("pdfjs.enableGuessAltText", false) &&
            Services.prefs.getBoolPref(
              "pdfjs.enableAltTextModelDownload",
              false
            ) &&
            Services.prefs.getBoolPref("browser.ml.enable", false),
        },
      },
    });
  }

  _setPreferences({ data }) {
    if (!data || typeof data !== "object") {
      return;
    }
    const branch = Services.prefs.getBranch("pdfjs.");
    for (const [key, value] of Object.entries(data)) {
      if (!this.#mutablePreferences.has(key)) {
        continue;
      }
      switch (branch.getPrefType(key)) {
        case Services.prefs.PREF_STRING:
          if (typeof value === "string") {
            branch.setStringPref(key, value);
          }
          break;
        case Services.prefs.PREF_INT:
          if (Number.isInteger(value)) {
            branch.setIntPref(key, value);
          }
          break;
        case Services.prefs.PREF_BOOL:
          if (typeof value === "boolean") {
            branch.setBoolPref(key, value);
          }
          break;
      }
    }
  }

  _recordExposure() {
    lazy.NimbusFeatures.pdfjs.recordExposureEvent({ once: true });
  }

  _reportTelemetry({ data }) {
    PdfJsTelemetry.report(data);
  }

  async _mlGuess({ data: { service, request } }) {
    if (service !== IMAGE_TO_TEXT_TASK) {
      return null;
    }
    try {
      const now = Cu.now();

      let response;
      if (Cu.isInAutomation) {
        response = { output: "In Automation" };
      } else {
        const engine = await this.#createAIEngine(service, null);
        response = await engine.run(request);
      }

      const time = Cu.now() - now;
      const length = response?.output.length ?? 0;
      PdfJsTelemetry.report({
        type: "editing",
        data: {
          type: "stamp",
          action: "pdfjs.image.alt_text.model_result",
          data: { time, length },
        },
      });
      return response;
    } catch (e) {
      console.error("Failed to run AI engine", e);
      return { error: true };
    }
  }

  async _loadAIEngine({ data: { service, listenToProgress } }) {
    if (service !== IMAGE_TO_TEXT_TASK) {
      throw new Error("Invalid service");
    }

    if (Cu.isInAutomation) {
      PdfJsTelemetry.report({
        type: "editing",
        data: {
          type: "stamp",
          action: "pdfjs.image.alt_text.model_download_start",
        },
      });
      PdfJsTelemetry.report({
        type: "editing",
        data: {
          type: "stamp",
          action: "pdfjs.image.alt_text.model_download_complete",
        },
      });
      return true;
    }

    let hasDownloadStarted = false;
    const self = this;
    const timeoutCallback = () => {
      lazy.clearTimeout(timeoutId);
      timeoutId = null;
      if (hasDownloadStarted) {
        PdfJsTelemetry.report({
          type: "editing",
          data: {
            type: "stamp",
            action: "pdfjs.image.alt_text.model_download_error",
          },
        });
      }
      if (!listenToProgress) {
        return;
      }
      self.sendAsyncMessage("PDFJS:Child:handleEvent", {
        type: "loadAIEngineProgress",
        detail: {
          service,
          ok: false,
          finished: true,
        },
      });
    };
    let timeoutId = lazy.setTimeout(timeoutCallback, ML_ENGINE_MAX_TIMEOUT);
    const aggregator = new lazy.MultiProgressAggregator({
      progressCallback({ ok, total, totalLoaded, statusText, type }) {
        if (timeoutId !== null) {
          lazy.clearTimeout(timeoutId);
          timeoutId = lazy.setTimeout(timeoutCallback, ML_ENGINE_MAX_TIMEOUT);
        } else {
          // The timeout has already fired, so we don't need to do anything.
          this.progressCallback = null;
          return;
        }
        if (
          !hasDownloadStarted &&
          type === lazy.Progress.ProgressType.DOWNLOAD
        ) {
          hasDownloadStarted = true;
          PdfJsTelemetry.report({
            type: "editing",
            data: {
              type: "stamp",
              action: "pdfjs.image.alt_text.model_download_start",
            },
          });
        }
        const finished = statusText === lazy.Progress.ProgressStatusText.DONE;
        if (listenToProgress) {
          self.sendAsyncMessage("PDFJS:Child:handleEvent", {
            type: "loadAIEngineProgress",
            detail: {
              service,
              ok,
              total,
              totalLoaded,
              finished,
            },
          });
        }
        if (finished) {
          if (
            hasDownloadStarted &&
            type === lazy.Progress.ProgressType.DOWNLOAD
          ) {
            PdfJsTelemetry.report({
              type: "editing",
              data: {
                type: "stamp",
                action: `pdfjs.image.alt_text.model_download_${
                  ok ? "complete" : "error"
                }`,
              },
            });
          }

          lazy.clearTimeout(timeoutId);
          // Once we're done, we can remove the progress callback.
          this.progressCallback = null;
        }
      },
      watchedTypes: [
        lazy.Progress.ProgressType.DOWNLOAD,
        lazy.Progress.ProgressType.LOAD_FROM_CACHE,
      ],
    });
    return !!(await this.#createAIEngine(service, aggregator));
  }

  async _mlDelete({ data: service }) {
    if (service !== IMAGE_TO_TEXT_TASK) {
      return null;
    }
    PdfJsTelemetry.report({
      type: "editing",
      data: {
        type: "stamp",
        action: "pdfjs.image.alt_text.model_deleted",
      },
    });
    if (Cu.isInAutomation) {
      return null;
    }
    try {
      // TODO: Temporary workaround to delete the model from the cache.
      //       See bug 1908941.
      await lazy.EngineProcess.destroyMLEngine();
      const cache = await lazy.IndexedDBCache.init();
      await cache.deleteModels({
        taskName: service,
      });
    } catch (e) {
      console.error("Failed to delete AI model", e);
    }

    return null;
  }

  async #createAIEngine(taskName, aggregator) {
    try {
      return await lazy.createEngine(
        { engineId: ML_ENGINE_ID, taskName },
        aggregator?.aggregateCallback.bind(aggregator) || null
      );
    } catch (e) {
      console.error("Failed to create AI engine", e);
      return null;
    }
  }

  _saveURL(aMsg) {
    const { blobUrl, originalUrl, filename } = aMsg.data;
    this.browser.ownerGlobal.saveURL(
      blobUrl /* aURL */,
      originalUrl /* aOriginalURL */,
      filename /* aFileName */,
      null /* aFilePickerTitleKey */,
      true /* aShouldBypassCache */,
      false /* aSkipPrompt */,
      null /* aReferrerInfo */,
      null /* aCookieJarSettings*/,
      null /* aSourceDocument */,
      lazy.PrivateBrowsingUtils.isBrowserPrivate(
        this.browser
      ) /* aIsContentWindowPrivate */,
      Services.scriptSecurityManager.getSystemPrincipal() /* aPrincipal */,
      () => {
        if (blobUrl.startsWith("blob:")) {
          URL.revokeObjectURL(blobUrl);
        }
        Services.obs.notifyObservers(null, "pdfjs:saveComplete");
      }
    );
  }

  _updateControlState(aMsg) {
    let data = aMsg.data;
    let browser = this.browser;
    let tabbrowser = browser.getTabBrowser();
    let tab = tabbrowser.getTabForBrowser(browser);
    tabbrowser.getFindBar(tab).then(fb => {
      if (!fb) {
        // The tab or window closed.
        return;
      }
      fb.updateControlState(data.result, data.findPrevious);

      if (
        data.result === Ci.nsITypeAheadFind.FIND_FOUND ||
        data.result === Ci.nsITypeAheadFind.FIND_WRAPPED ||
        (data.result === Ci.nsITypeAheadFind.FIND_PENDING &&
          !this._findFailedString)
      ) {
        this._findFailedString = null;
        lazy.SetClipboardSearchString(data.rawQuery);
      } else if (!this._findFailedString) {
        this._findFailedString = data.rawQuery;
        lazy.SetClipboardSearchString(data.rawQuery);
      }

      const matchesCount = this._requestMatchesCount(data.matchesCount);
      fb.onMatchesCountResult(matchesCount);
    });
  }

  _updateMatchesCount(aMsg) {
    let data = aMsg.data;
    let browser = this.browser;
    let tabbrowser = browser.getTabBrowser();
    let tab = tabbrowser.getTabForBrowser(browser);
    tabbrowser.getFindBar(tab).then(fb => {
      if (!fb) {
        // The tab or window closed.
        return;
      }
      const matchesCount = this._requestMatchesCount(data);
      fb.onMatchesCountResult(matchesCount);
    });
  }

  _requestMatchesCount(data) {
    if (!data) {
      return { current: 0, total: 0 };
    }
    let result = {
      current: data.current,
      total: data.total,
      limit:
        typeof lazy.matchesCountLimit === "number" ? lazy.matchesCountLimit : 0,
    };
    if (result.total > result.limit) {
      result.total = -1;
    }
    return result;
  }

  handleEvent(aEvent) {
    const type = aEvent.type;
    // Handle the tab find initialized event specially:
    if (type == "TabFindInitialized") {
      let browser = aEvent.target.linkedBrowser;
      this._hookupEventListeners(browser);
      aEvent.target.removeEventListener(type, this);
      return;
    }

    if (type == "SwapDocShells") {
      this._removeEventListener();
      let newBrowser = aEvent.detail;
      newBrowser.addEventListener(
        "EndSwapDocShells",
        () => {
          this._hookupEventListeners(newBrowser);
        },
        { once: true }
      );
      return;
    }

    // Ignore events findbar events which arrive while the Pdfjs document is in
    // the BFCache.
    if (this.windowContext.isInBFCache) {
      return;
    }

    // To avoid forwarding the message as a CPOW, create a structured cloneable
    // version of the event for both performance, and ease of usage, reasons.
    let detail = null;
    if (type !== "findbarclose") {
      detail = {
        query: aEvent.detail.query,
        caseSensitive: aEvent.detail.caseSensitive,
        entireWord: aEvent.detail.entireWord,
        highlightAll: aEvent.detail.highlightAll,
        findPrevious: aEvent.detail.findPrevious,
        matchDiacritics: aEvent.detail.matchDiacritics,
      };
    }

    let browser = aEvent.currentTarget.browser;
    if (!this._boundToFindbar) {
      throw new Error(
        "FindEventManager was not bound for the current browser."
      );
    }
    browser.sendMessageToActor(
      "PDFJS:Child:handleEvent",
      { type, detail },
      "Pdfjs"
    );
    aEvent.preventDefault();
  }

  _addEventListener() {
    let browser = this.browser;
    if (this._boundToFindbar) {
      throw new Error(
        "FindEventManager was bound 2nd time without unbinding it first."
      );
    }

    this._hookupEventListeners(browser);
  }

  /**
   * Either hook up all the find event listeners if a findbar exists,
   * or listen for a find bar being created and hook up event listeners
   * when it does get created.
   */
  _hookupEventListeners(aBrowser) {
    let tabbrowser = aBrowser.getTabBrowser();
    let tab = tabbrowser.getTabForBrowser(aBrowser);
    let findbar = tabbrowser.getCachedFindBar(tab);
    if (findbar) {
      // And we need to start listening to find events.
      for (var i = 0; i < gFindTypes.length; i++) {
        var type = gFindTypes[i];
        findbar.addEventListener(type, this, true);
      }
      this._boundToFindbar = findbar;
    } else {
      tab.addEventListener("TabFindInitialized", this);
    }
    aBrowser.addEventListener("SwapDocShells", this);
    return !!findbar;
  }

  _removeEventListener() {
    let browser = this.browser;

    // make sure the listener has been removed.
    let findbar = this._boundToFindbar;
    if (findbar) {
      // No reason to listen to find events any longer.
      for (var i = 0; i < gFindTypes.length; i++) {
        var type = gFindTypes[i];
        findbar.removeEventListener(type, this, true);
      }
    } else if (browser) {
      // If we registered a `TabFindInitialized` listener which never fired,
      // make sure we remove it.
      let tabbrowser = browser.getTabBrowser();
      let tab = tabbrowser.getTabForBrowser(browser);
      tab?.removeEventListener("TabFindInitialized", this);
    }

    this._boundToFindbar = null;

    // Clean up any SwapDocShells event listeners.
    browser?.removeEventListener("SwapDocShells", this);
  }
}
PK
!<4xX�X�"chrome/pdfjs/content/build/pdf.mjs/**
 * @licstart The following is the entire license notice for the
 * JavaScript code in this page
 *
 * Copyright 2024 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @licend The above is the entire license notice for the
 * JavaScript code in this page
 */

/******/ // The require scope
/******/ var __webpack_require__ = {};
/******/ 
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ 	// define getter functions for harmony exports
/******/ 	__webpack_require__.d = (exports, definition) => {
/******/ 		for(var key in definition) {
/******/ 			if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ 				Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ 			}
/******/ 		}
/******/ 	};
/******/ })();
/******/ 
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ 	__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/ 
/************************************************************************/
var __webpack_exports__ = globalThis.pdfjsLib = {};

// EXPORTS
__webpack_require__.d(__webpack_exports__, {
  AbortException: () => (/* reexport */ AbortException),
  AnnotationEditorLayer: () => (/* reexport */ AnnotationEditorLayer),
  AnnotationEditorParamsType: () => (/* reexport */ AnnotationEditorParamsType),
  AnnotationEditorType: () => (/* reexport */ AnnotationEditorType),
  AnnotationEditorUIManager: () => (/* reexport */ AnnotationEditorUIManager),
  AnnotationLayer: () => (/* reexport */ AnnotationLayer),
  AnnotationMode: () => (/* reexport */ AnnotationMode),
  CMapCompressionType: () => (/* reexport */ CMapCompressionType),
  ColorPicker: () => (/* reexport */ ColorPicker),
  DOMSVGFactory: () => (/* reexport */ DOMSVGFactory),
  DrawLayer: () => (/* reexport */ DrawLayer),
  FeatureTest: () => (/* reexport */ util_FeatureTest),
  GlobalWorkerOptions: () => (/* reexport */ GlobalWorkerOptions),
  ImageKind: () => (/* reexport */ util_ImageKind),
  InvalidPDFException: () => (/* reexport */ InvalidPDFException),
  MissingPDFException: () => (/* reexport */ MissingPDFException),
  OPS: () => (/* reexport */ OPS),
  PDFDataRangeTransport: () => (/* reexport */ PDFDataRangeTransport),
  PDFDateString: () => (/* reexport */ PDFDateString),
  PDFWorker: () => (/* reexport */ PDFWorker),
  PasswordResponses: () => (/* reexport */ PasswordResponses),
  PermissionFlag: () => (/* reexport */ PermissionFlag),
  PixelsPerInch: () => (/* reexport */ PixelsPerInch),
  RenderingCancelledException: () => (/* reexport */ RenderingCancelledException),
  TextLayer: () => (/* reexport */ TextLayer),
  UnexpectedResponseException: () => (/* reexport */ UnexpectedResponseException),
  Util: () => (/* reexport */ Util),
  VerbosityLevel: () => (/* reexport */ VerbosityLevel),
  XfaLayer: () => (/* reexport */ XfaLayer),
  build: () => (/* reexport */ build),
  createValidAbsoluteUrl: () => (/* reexport */ createValidAbsoluteUrl),
  fetchData: () => (/* reexport */ fetchData),
  getDocument: () => (/* reexport */ getDocument),
  getFilenameFromUrl: () => (/* reexport */ getFilenameFromUrl),
  getPdfFilenameFromUrl: () => (/* reexport */ getPdfFilenameFromUrl),
  getXfaPageViewport: () => (/* reexport */ getXfaPageViewport),
  isDataScheme: () => (/* reexport */ isDataScheme),
  isPdfFile: () => (/* reexport */ isPdfFile),
  noContextMenu: () => (/* reexport */ noContextMenu),
  normalizeUnicode: () => (/* reexport */ normalizeUnicode),
  setLayerDimensions: () => (/* reexport */ setLayerDimensions),
  shadow: () => (/* reexport */ shadow),
  version: () => (/* reexport */ version)
});

;// CONCATENATED MODULE: ./src/shared/util.js
const isNodeJS = false;
const IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
const FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
const MAX_IMAGE_SIZE_TO_CACHE = 10e6;
const LINE_FACTOR = 1.35;
const LINE_DESCENT_FACTOR = 0.35;
const BASELINE_FACTOR = LINE_DESCENT_FACTOR / LINE_FACTOR;
const RenderingIntentFlag = {
  ANY: 0x01,
  DISPLAY: 0x02,
  PRINT: 0x04,
  SAVE: 0x08,
  ANNOTATIONS_FORMS: 0x10,
  ANNOTATIONS_STORAGE: 0x20,
  ANNOTATIONS_DISABLE: 0x40,
  IS_EDITING: 0x80,
  OPLIST: 0x100
};
const AnnotationMode = {
  DISABLE: 0,
  ENABLE: 1,
  ENABLE_FORMS: 2,
  ENABLE_STORAGE: 3
};
const AnnotationEditorPrefix = "pdfjs_internal_editor_";
const AnnotationEditorType = {
  DISABLE: -1,
  NONE: 0,
  FREETEXT: 3,
  HIGHLIGHT: 9,
  STAMP: 13,
  INK: 15
};
const AnnotationEditorParamsType = {
  RESIZE: 1,
  CREATE: 2,
  FREETEXT_SIZE: 11,
  FREETEXT_COLOR: 12,
  FREETEXT_OPACITY: 13,
  INK_COLOR: 21,
  INK_THICKNESS: 22,
  INK_OPACITY: 23,
  HIGHLIGHT_COLOR: 31,
  HIGHLIGHT_DEFAULT_COLOR: 32,
  HIGHLIGHT_THICKNESS: 33,
  HIGHLIGHT_FREE: 34,
  HIGHLIGHT_SHOW_ALL: 35
};
const PermissionFlag = {
  PRINT: 0x04,
  MODIFY_CONTENTS: 0x08,
  COPY: 0x10,
  MODIFY_ANNOTATIONS: 0x20,
  FILL_INTERACTIVE_FORMS: 0x100,
  COPY_FOR_ACCESSIBILITY: 0x200,
  ASSEMBLE: 0x400,
  PRINT_HIGH_QUALITY: 0x800
};
const TextRenderingMode = {
  FILL: 0,
  STROKE: 1,
  FILL_STROKE: 2,
  INVISIBLE: 3,
  FILL_ADD_TO_PATH: 4,
  STROKE_ADD_TO_PATH: 5,
  FILL_STROKE_ADD_TO_PATH: 6,
  ADD_TO_PATH: 7,
  FILL_STROKE_MASK: 3,
  ADD_TO_PATH_FLAG: 4
};
const util_ImageKind = {
  GRAYSCALE_1BPP: 1,
  RGB_24BPP: 2,
  RGBA_32BPP: 3
};
const AnnotationType = {
  TEXT: 1,
  LINK: 2,
  FREETEXT: 3,
  LINE: 4,
  SQUARE: 5,
  CIRCLE: 6,
  POLYGON: 7,
  POLYLINE: 8,
  HIGHLIGHT: 9,
  UNDERLINE: 10,
  SQUIGGLY: 11,
  STRIKEOUT: 12,
  STAMP: 13,
  CARET: 14,
  INK: 15,
  POPUP: 16,
  FILEATTACHMENT: 17,
  SOUND: 18,
  MOVIE: 19,
  WIDGET: 20,
  SCREEN: 21,
  PRINTERMARK: 22,
  TRAPNET: 23,
  WATERMARK: 24,
  THREED: 25,
  REDACT: 26
};
const AnnotationReplyType = {
  GROUP: "Group",
  REPLY: "R"
};
const AnnotationFlag = {
  INVISIBLE: 0x01,
  HIDDEN: 0x02,
  PRINT: 0x04,
  NOZOOM: 0x08,
  NOROTATE: 0x10,
  NOVIEW: 0x20,
  READONLY: 0x40,
  LOCKED: 0x80,
  TOGGLENOVIEW: 0x100,
  LOCKEDCONTENTS: 0x200
};
const AnnotationFieldFlag = {
  READONLY: 0x0000001,
  REQUIRED: 0x0000002,
  NOEXPORT: 0x0000004,
  MULTILINE: 0x0001000,
  PASSWORD: 0x0002000,
  NOTOGGLETOOFF: 0x0004000,
  RADIO: 0x0008000,
  PUSHBUTTON: 0x0010000,
  COMBO: 0x0020000,
  EDIT: 0x0040000,
  SORT: 0x0080000,
  FILESELECT: 0x0100000,
  MULTISELECT: 0x0200000,
  DONOTSPELLCHECK: 0x0400000,
  DONOTSCROLL: 0x0800000,
  COMB: 0x1000000,
  RICHTEXT: 0x2000000,
  RADIOSINUNISON: 0x2000000,
  COMMITONSELCHANGE: 0x4000000
};
const AnnotationBorderStyleType = {
  SOLID: 1,
  DASHED: 2,
  BEVELED: 3,
  INSET: 4,
  UNDERLINE: 5
};
const AnnotationActionEventType = {
  E: "Mouse Enter",
  X: "Mouse Exit",
  D: "Mouse Down",
  U: "Mouse Up",
  Fo: "Focus",
  Bl: "Blur",
  PO: "PageOpen",
  PC: "PageClose",
  PV: "PageVisible",
  PI: "PageInvisible",
  K: "Keystroke",
  F: "Format",
  V: "Validate",
  C: "Calculate"
};
const DocumentActionEventType = {
  WC: "WillClose",
  WS: "WillSave",
  DS: "DidSave",
  WP: "WillPrint",
  DP: "DidPrint"
};
const PageActionEventType = {
  O: "PageOpen",
  C: "PageClose"
};
const VerbosityLevel = {
  ERRORS: 0,
  WARNINGS: 1,
  INFOS: 5
};
const CMapCompressionType = {
  NONE: 0,
  BINARY: 1
};
const OPS = {
  dependency: 1,
  setLineWidth: 2,
  setLineCap: 3,
  setLineJoin: 4,
  setMiterLimit: 5,
  setDash: 6,
  setRenderingIntent: 7,
  setFlatness: 8,
  setGState: 9,
  save: 10,
  restore: 11,
  transform: 12,
  moveTo: 13,
  lineTo: 14,
  curveTo: 15,
  curveTo2: 16,
  curveTo3: 17,
  closePath: 18,
  rectangle: 19,
  stroke: 20,
  closeStroke: 21,
  fill: 22,
  eoFill: 23,
  fillStroke: 24,
  eoFillStroke: 25,
  closeFillStroke: 26,
  closeEOFillStroke: 27,
  endPath: 28,
  clip: 29,
  eoClip: 30,
  beginText: 31,
  endText: 32,
  setCharSpacing: 33,
  setWordSpacing: 34,
  setHScale: 35,
  setLeading: 36,
  setFont: 37,
  setTextRenderingMode: 38,
  setTextRise: 39,
  moveText: 40,
  setLeadingMoveText: 41,
  setTextMatrix: 42,
  nextLine: 43,
  showText: 44,
  showSpacedText: 45,
  nextLineShowText: 46,
  nextLineSetSpacingShowText: 47,
  setCharWidth: 48,
  setCharWidthAndBounds: 49,
  setStrokeColorSpace: 50,
  setFillColorSpace: 51,
  setStrokeColor: 52,
  setStrokeColorN: 53,
  setFillColor: 54,
  setFillColorN: 55,
  setStrokeGray: 56,
  setFillGray: 57,
  setStrokeRGBColor: 58,
  setFillRGBColor: 59,
  setStrokeCMYKColor: 60,
  setFillCMYKColor: 61,
  shadingFill: 62,
  beginInlineImage: 63,
  beginImageData: 64,
  endInlineImage: 65,
  paintXObject: 66,
  markPoint: 67,
  markPointProps: 68,
  beginMarkedContent: 69,
  beginMarkedContentProps: 70,
  endMarkedContent: 71,
  beginCompat: 72,
  endCompat: 73,
  paintFormXObjectBegin: 74,
  paintFormXObjectEnd: 75,
  beginGroup: 76,
  endGroup: 77,
  beginAnnotation: 80,
  endAnnotation: 81,
  paintImageMaskXObject: 83,
  paintImageMaskXObjectGroup: 84,
  paintImageXObject: 85,
  paintInlineImageXObject: 86,
  paintInlineImageXObjectGroup: 87,
  paintImageXObjectRepeat: 88,
  paintImageMaskXObjectRepeat: 89,
  paintSolidColorImageMask: 90,
  constructPath: 91,
  setStrokeTransparent: 92,
  setFillTransparent: 93
};
const PasswordResponses = {
  NEED_PASSWORD: 1,
  INCORRECT_PASSWORD: 2
};
let verbosity = VerbosityLevel.WARNINGS;
function setVerbosityLevel(level) {
  if (Number.isInteger(level)) {
    verbosity = level;
  }
}
function getVerbosityLevel() {
  return verbosity;
}
function info(msg) {
  if (verbosity >= VerbosityLevel.INFOS) {
    console.log(`Info: ${msg}`);
  }
}
function warn(msg) {
  if (verbosity >= VerbosityLevel.WARNINGS) {
    console.log(`Warning: ${msg}`);
  }
}
function unreachable(msg) {
  throw new Error(msg);
}
function assert(cond, msg) {
  if (!cond) {
    unreachable(msg);
  }
}
function _isValidProtocol(url) {
  switch (url?.protocol) {
    case "http:":
    case "https:":
    case "ftp:":
    case "mailto:":
    case "tel:":
      return true;
    default:
      return false;
  }
}
function createValidAbsoluteUrl(url, baseUrl = null, options = null) {
  if (!url) {
    return null;
  }
  try {
    if (options && typeof url === "string") {
      if (options.addDefaultProtocol && url.startsWith("www.")) {
        const dots = url.match(/\./g);
        if (dots?.length >= 2) {
          url = `http://${url}`;
        }
      }
      if (options.tryConvertEncoding) {
        try {
          url = stringToUTF8String(url);
        } catch {}
      }
    }
    const absoluteUrl = baseUrl ? new URL(url, baseUrl) : new URL(url);
    if (_isValidProtocol(absoluteUrl)) {
      return absoluteUrl;
    }
  } catch {}
  return null;
}
function shadow(obj, prop, value, nonSerializable = false) {
  Object.defineProperty(obj, prop, {
    value,
    enumerable: !nonSerializable,
    configurable: true,
    writable: false
  });
  return value;
}
const BaseException = function BaseExceptionClosure() {
  function BaseException(message, name) {
    this.message = message;
    this.name = name;
  }
  BaseException.prototype = new Error();
  BaseException.constructor = BaseException;
  return BaseException;
}();
class PasswordException extends BaseException {
  constructor(msg, code) {
    super(msg, "PasswordException");
    this.code = code;
  }
}
class UnknownErrorException extends BaseException {
  constructor(msg, details) {
    super(msg, "UnknownErrorException");
    this.details = details;
  }
}
class InvalidPDFException extends BaseException {
  constructor(msg) {
    super(msg, "InvalidPDFException");
  }
}
class MissingPDFException extends BaseException {
  constructor(msg) {
    super(msg, "MissingPDFException");
  }
}
class UnexpectedResponseException extends BaseException {
  constructor(msg, status) {
    super(msg, "UnexpectedResponseException");
    this.status = status;
  }
}
class FormatError extends BaseException {
  constructor(msg) {
    super(msg, "FormatError");
  }
}
class AbortException extends BaseException {
  constructor(msg) {
    super(msg, "AbortException");
  }
}
function bytesToString(bytes) {
  if (typeof bytes !== "object" || bytes?.length === undefined) {
    unreachable("Invalid argument for bytesToString");
  }
  const length = bytes.length;
  const MAX_ARGUMENT_COUNT = 8192;
  if (length < MAX_ARGUMENT_COUNT) {
    return String.fromCharCode.apply(null, bytes);
  }
  const strBuf = [];
  for (let i = 0; i < length; i += MAX_ARGUMENT_COUNT) {
    const chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length);
    const chunk = bytes.subarray(i, chunkEnd);
    strBuf.push(String.fromCharCode.apply(null, chunk));
  }
  return strBuf.join("");
}
function stringToBytes(str) {
  if (typeof str !== "string") {
    unreachable("Invalid argument for stringToBytes");
  }
  const length = str.length;
  const bytes = new Uint8Array(length);
  for (let i = 0; i < length; ++i) {
    bytes[i] = str.charCodeAt(i) & 0xff;
  }
  return bytes;
}
function string32(value) {
  return String.fromCharCode(value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff);
}
function objectSize(obj) {
  return Object.keys(obj).length;
}
function objectFromMap(map) {
  const obj = Object.create(null);
  for (const [key, value] of map) {
    obj[key] = value;
  }
  return obj;
}
function isLittleEndian() {
  const buffer8 = new Uint8Array(4);
  buffer8[0] = 1;
  const view32 = new Uint32Array(buffer8.buffer, 0, 1);
  return view32[0] === 1;
}
function isEvalSupported() {
  try {
    new Function("");
    return true;
  } catch {
    return false;
  }
}
class util_FeatureTest {
  static get isLittleEndian() {
    return shadow(this, "isLittleEndian", isLittleEndian());
  }
  static get isEvalSupported() {
    return shadow(this, "isEvalSupported", isEvalSupported());
  }
  static get isOffscreenCanvasSupported() {
    return shadow(this, "isOffscreenCanvasSupported", typeof OffscreenCanvas !== "undefined");
  }
  static get platform() {
    return shadow(this, "platform", {
      isMac: navigator.platform.includes("Mac")
    });
  }
  static get isCSSRoundSupported() {
    return shadow(this, "isCSSRoundSupported", globalThis.CSS?.supports?.("width: round(1.5px, 1px)"));
  }
}
const hexNumbers = Array.from(Array(256).keys(), n => n.toString(16).padStart(2, "0"));
class Util {
  static makeHexColor(r, g, b) {
    return `#${hexNumbers[r]}${hexNumbers[g]}${hexNumbers[b]}`;
  }
  static scaleMinMax(transform, minMax) {
    let temp;
    if (transform[0]) {
      if (transform[0] < 0) {
        temp = minMax[0];
        minMax[0] = minMax[2];
        minMax[2] = temp;
      }
      minMax[0] *= transform[0];
      minMax[2] *= transform[0];
      if (transform[3] < 0) {
        temp = minMax[1];
        minMax[1] = minMax[3];
        minMax[3] = temp;
      }
      minMax[1] *= transform[3];
      minMax[3] *= transform[3];
    } else {
      temp = minMax[0];
      minMax[0] = minMax[1];
      minMax[1] = temp;
      temp = minMax[2];
      minMax[2] = minMax[3];
      minMax[3] = temp;
      if (transform[1] < 0) {
        temp = minMax[1];
        minMax[1] = minMax[3];
        minMax[3] = temp;
      }
      minMax[1] *= transform[1];
      minMax[3] *= transform[1];
      if (transform[2] < 0) {
        temp = minMax[0];
        minMax[0] = minMax[2];
        minMax[2] = temp;
      }
      minMax[0] *= transform[2];
      minMax[2] *= transform[2];
    }
    minMax[0] += transform[4];
    minMax[1] += transform[5];
    minMax[2] += transform[4];
    minMax[3] += transform[5];
  }
  static transform(m1, m2) {
    return [m1[0] * m2[0] + m1[2] * m2[1], m1[1] * m2[0] + m1[3] * m2[1], m1[0] * m2[2] + m1[2] * m2[3], m1[1] * m2[2] + m1[3] * m2[3], m1[0] * m2[4] + m1[2] * m2[5] + m1[4], m1[1] * m2[4] + m1[3] * m2[5] + m1[5]];
  }
  static applyTransform(p, m) {
    const xt = p[0] * m[0] + p[1] * m[2] + m[4];
    const yt = p[0] * m[1] + p[1] * m[3] + m[5];
    return [xt, yt];
  }
  static applyInverseTransform(p, m) {
    const d = m[0] * m[3] - m[1] * m[2];
    const xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
    const yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d;
    return [xt, yt];
  }
  static getAxialAlignedBoundingBox(r, m) {
    const p1 = this.applyTransform(r, m);
    const p2 = this.applyTransform(r.slice(2, 4), m);
    const p3 = this.applyTransform([r[0], r[3]], m);
    const p4 = this.applyTransform([r[2], r[1]], m);
    return [Math.min(p1[0], p2[0], p3[0], p4[0]), Math.min(p1[1], p2[1], p3[1], p4[1]), Math.max(p1[0], p2[0], p3[0], p4[0]), Math.max(p1[1], p2[1], p3[1], p4[1])];
  }
  static inverseTransform(m) {
    const d = m[0] * m[3] - m[1] * m[2];
    return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d, (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d];
  }
  static singularValueDecompose2dScale(m) {
    const transpose = [m[0], m[2], m[1], m[3]];
    const a = m[0] * transpose[0] + m[1] * transpose[2];
    const b = m[0] * transpose[1] + m[1] * transpose[3];
    const c = m[2] * transpose[0] + m[3] * transpose[2];
    const d = m[2] * transpose[1] + m[3] * transpose[3];
    const first = (a + d) / 2;
    const second = Math.sqrt((a + d) ** 2 - 4 * (a * d - c * b)) / 2;
    const sx = first + second || 1;
    const sy = first - second || 1;
    return [Math.sqrt(sx), Math.sqrt(sy)];
  }
  static normalizeRect(rect) {
    const r = rect.slice(0);
    if (rect[0] > rect[2]) {
      r[0] = rect[2];
      r[2] = rect[0];
    }
    if (rect[1] > rect[3]) {
      r[1] = rect[3];
      r[3] = rect[1];
    }
    return r;
  }
  static intersect(rect1, rect2) {
    const xLow = Math.max(Math.min(rect1[0], rect1[2]), Math.min(rect2[0], rect2[2]));
    const xHigh = Math.min(Math.max(rect1[0], rect1[2]), Math.max(rect2[0], rect2[2]));
    if (xLow > xHigh) {
      return null;
    }
    const yLow = Math.max(Math.min(rect1[1], rect1[3]), Math.min(rect2[1], rect2[3]));
    const yHigh = Math.min(Math.max(rect1[1], rect1[3]), Math.max(rect2[1], rect2[3]));
    if (yLow > yHigh) {
      return null;
    }
    return [xLow, yLow, xHigh, yHigh];
  }
  static #getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, t, minMax) {
    if (t <= 0 || t >= 1) {
      return;
    }
    const mt = 1 - t;
    const tt = t * t;
    const ttt = tt * t;
    const x = mt * (mt * (mt * x0 + 3 * t * x1) + 3 * tt * x2) + ttt * x3;
    const y = mt * (mt * (mt * y0 + 3 * t * y1) + 3 * tt * y2) + ttt * y3;
    minMax[0] = Math.min(minMax[0], x);
    minMax[1] = Math.min(minMax[1], y);
    minMax[2] = Math.max(minMax[2], x);
    minMax[3] = Math.max(minMax[3], y);
  }
  static #getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, a, b, c, minMax) {
    if (Math.abs(a) < 1e-12) {
      if (Math.abs(b) >= 1e-12) {
        this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, -c / b, minMax);
      }
      return;
    }
    const delta = b ** 2 - 4 * c * a;
    if (delta < 0) {
      return;
    }
    const sqrtDelta = Math.sqrt(delta);
    const a2 = 2 * a;
    this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, (-b + sqrtDelta) / a2, minMax);
    this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, (-b - sqrtDelta) / a2, minMax);
  }
  static bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3, minMax) {
    if (minMax) {
      minMax[0] = Math.min(minMax[0], x0, x3);
      minMax[1] = Math.min(minMax[1], y0, y3);
      minMax[2] = Math.max(minMax[2], x0, x3);
      minMax[3] = Math.max(minMax[3], y0, y3);
    } else {
      minMax = [Math.min(x0, x3), Math.min(y0, y3), Math.max(x0, x3), Math.max(y0, y3)];
    }
    this.#getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, 3 * (-x0 + 3 * (x1 - x2) + x3), 6 * (x0 - 2 * x1 + x2), 3 * (x1 - x0), minMax);
    this.#getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, 3 * (-y0 + 3 * (y1 - y2) + y3), 6 * (y0 - 2 * y1 + y2), 3 * (y1 - y0), minMax);
    return minMax;
  }
}
const PDFStringTranslateTable = (/* unused pure expression or super */ null && ([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2d8, 0x2c7, 0x2c6, 0x2d9, 0x2dd, 0x2db, 0x2da, 0x2dc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, 0x2013, 0x192, 0x2044, 0x2039, 0x203a, 0x2212, 0x2030, 0x201e, 0x201c, 0x201d, 0x2018, 0x2019, 0x201a, 0x2122, 0xfb01, 0xfb02, 0x141, 0x152, 0x160, 0x178, 0x17d, 0x131, 0x142, 0x153, 0x161, 0x17e, 0, 0x20ac]));
function stringToPDFString(str) {
  if (str[0] >= "\xEF") {
    let encoding;
    if (str[0] === "\xFE" && str[1] === "\xFF") {
      encoding = "utf-16be";
      if (str.length % 2 === 1) {
        str = str.slice(0, -1);
      }
    } else if (str[0] === "\xFF" && str[1] === "\xFE") {
      encoding = "utf-16le";
      if (str.length % 2 === 1) {
        str = str.slice(0, -1);
      }
    } else if (str[0] === "\xEF" && str[1] === "\xBB" && str[2] === "\xBF") {
      encoding = "utf-8";
    }
    if (encoding) {
      try {
        const decoder = new TextDecoder(encoding, {
          fatal: true
        });
        const buffer = stringToBytes(str);
        const decoded = decoder.decode(buffer);
        if (!decoded.includes("\x1b")) {
          return decoded;
        }
        return decoded.replaceAll(/\x1b[^\x1b]*(?:\x1b|$)/g, "");
      } catch (ex) {
        warn(`stringToPDFString: "${ex}".`);
      }
    }
  }
  const strBuf = [];
  for (let i = 0, ii = str.length; i < ii; i++) {
    const charCode = str.charCodeAt(i);
    if (charCode === 0x1b) {
      while (++i < ii && str.charCodeAt(i) !== 0x1b) {}
      continue;
    }
    const code = PDFStringTranslateTable[charCode];
    strBuf.push(code ? String.fromCharCode(code) : str.charAt(i));
  }
  return strBuf.join("");
}
function stringToUTF8String(str) {
  return decodeURIComponent(escape(str));
}
function utf8StringToString(str) {
  return unescape(encodeURIComponent(str));
}
function isArrayEqual(arr1, arr2) {
  if (arr1.length !== arr2.length) {
    return false;
  }
  for (let i = 0, ii = arr1.length; i < ii; i++) {
    if (arr1[i] !== arr2[i]) {
      return false;
    }
  }
  return true;
}
function getModificationDate(date = new Date()) {
  const buffer = [date.getUTCFullYear().toString(), (date.getUTCMonth() + 1).toString().padStart(2, "0"), date.getUTCDate().toString().padStart(2, "0"), date.getUTCHours().toString().padStart(2, "0"), date.getUTCMinutes().toString().padStart(2, "0"), date.getUTCSeconds().toString().padStart(2, "0")];
  return buffer.join("");
}
let NormalizeRegex = null;
let NormalizationMap = null;
function normalizeUnicode(str) {
  if (!NormalizeRegex) {
    NormalizeRegex = /([\u00a0\u00b5\u037e\u0eb3\u2000-\u200a\u202f\u2126\ufb00-\ufb04\ufb06\ufb20-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufba1\ufba4-\ufba9\ufbae-\ufbb1\ufbd3-\ufbdc\ufbde-\ufbe7\ufbea-\ufbf8\ufbfc-\ufbfd\ufc00-\ufc5d\ufc64-\ufcf1\ufcf5-\ufd3d\ufd88\ufdf4\ufdfa-\ufdfb\ufe71\ufe77\ufe79\ufe7b\ufe7d]+)|(\ufb05+)/gu;
    NormalizationMap = new Map([["ſt", "ſt"]]);
  }
  return str.replaceAll(NormalizeRegex, (_, p1, p2) => p1 ? p1.normalize("NFKC") : NormalizationMap.get(p2));
}
function getUuid() {
  return crypto.randomUUID();
}
const AnnotationPrefix = "pdfjs_internal_id_";
const FontRenderOps = {
  BEZIER_CURVE_TO: 0,
  MOVE_TO: 1,
  LINE_TO: 2,
  QUADRATIC_CURVE_TO: 3,
  RESTORE: 4,
  SAVE: 5,
  SCALE: 6,
  TRANSFORM: 7,
  TRANSLATE: 8
};

;// CONCATENATED MODULE: ./src/display/base_factory.js

class BaseFilterFactory {
  addFilter(maps) {
    return "none";
  }
  addHCMFilter(fgColor, bgColor) {
    return "none";
  }
  addAlphaFilter(map) {
    return "none";
  }
  addLuminosityFilter(map) {
    return "none";
  }
  addHighlightHCMFilter(filterName, fgColor, bgColor, newFgColor, newBgColor) {
    return "none";
  }
  destroy(keepHCM = false) {}
}
class BaseCanvasFactory {
  #enableHWA = false;
  constructor({
    enableHWA = false
  } = {}) {
    this.#enableHWA = enableHWA;
  }
  create(width, height) {
    if (width <= 0 || height <= 0) {
      throw new Error("Invalid canvas size");
    }
    const canvas = this._createCanvas(width, height);
    return {
      canvas,
      context: canvas.getContext("2d", {
        willReadFrequently: !this.#enableHWA
      })
    };
  }
  reset(canvasAndContext, width, height) {
    if (!canvasAndContext.canvas) {
      throw new Error("Canvas is not specified");
    }
    if (width <= 0 || height <= 0) {
      throw new Error("Invalid canvas size");
    }
    canvasAndContext.canvas.width = width;
    canvasAndContext.canvas.height = height;
  }
  destroy(canvasAndContext) {
    if (!canvasAndContext.canvas) {
      throw new Error("Canvas is not specified");
    }
    canvasAndContext.canvas.width = 0;
    canvasAndContext.canvas.height = 0;
    canvasAndContext.canvas = null;
    canvasAndContext.context = null;
  }
  _createCanvas(width, height) {
    unreachable("Abstract method `_createCanvas` called.");
  }
}
class BaseCMapReaderFactory {
  constructor({
    baseUrl = null,
    isCompressed = true
  }) {
    this.baseUrl = baseUrl;
    this.isCompressed = isCompressed;
  }
  async fetch({
    name
  }) {
    if (!this.baseUrl) {
      throw new Error("Ensure that the `cMapUrl` and `cMapPacked` API parameters are provided.");
    }
    if (!name) {
      throw new Error("CMap name must be specified.");
    }
    const url = this.baseUrl + name + (this.isCompressed ? ".bcmap" : "");
    const compressionType = this.isCompressed ? CMapCompressionType.BINARY : CMapCompressionType.NONE;
    return this._fetchData(url, compressionType).catch(reason => {
      throw new Error(`Unable to load ${this.isCompressed ? "binary " : ""}CMap at: ${url}`);
    });
  }
  _fetchData(url, compressionType) {
    unreachable("Abstract method `_fetchData` called.");
  }
}
class BaseStandardFontDataFactory {
  constructor({
    baseUrl = null
  }) {
    this.baseUrl = baseUrl;
  }
  async fetch({
    filename
  }) {
    if (!this.baseUrl) {
      throw new Error("Ensure that the `standardFontDataUrl` API parameter is provided.");
    }
    if (!filename) {
      throw new Error("Font filename must be specified.");
    }
    const url = `${this.baseUrl}${filename}`;
    return this._fetchData(url).catch(reason => {
      throw new Error(`Unable to load font data at: ${url}`);
    });
  }
  _fetchData(url) {
    unreachable("Abstract method `_fetchData` called.");
  }
}
class BaseSVGFactory {
  create(width, height, skipDimensions = false) {
    if (width <= 0 || height <= 0) {
      throw new Error("Invalid SVG dimensions");
    }
    const svg = this._createSVG("svg:svg");
    svg.setAttribute("version", "1.1");
    if (!skipDimensions) {
      svg.setAttribute("width", `${width}px`);
      svg.setAttribute("height", `${height}px`);
    }
    svg.setAttribute("preserveAspectRatio", "none");
    svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
    return svg;
  }
  createElement(type) {
    if (typeof type !== "string") {
      throw new Error("Invalid SVG element type");
    }
    return this._createSVG(type);
  }
  _createSVG(type) {
    unreachable("Abstract method `_createSVG` called.");
  }
}

;// CONCATENATED MODULE: ./src/display/display_utils.js


const SVG_NS = "http://www.w3.org/2000/svg";
class PixelsPerInch {
  static CSS = 96.0;
  static PDF = 72.0;
  static PDF_TO_CSS_UNITS = this.CSS / this.PDF;
}
class DOMFilterFactory extends BaseFilterFactory {
  #baseUrl;
  #_cache;
  #_defs;
  #docId;
  #document;
  #_hcmCache;
  #id = 0;
  constructor({
    docId,
    ownerDocument = globalThis.document
  } = {}) {
    super();
    this.#docId = docId;
    this.#document = ownerDocument;
  }
  get #cache() {
    return this.#_cache ||= new Map();
  }
  get #hcmCache() {
    return this.#_hcmCache ||= new Map();
  }
  get #defs() {
    if (!this.#_defs) {
      const div = this.#document.createElement("div");
      const {
        style
      } = div;
      style.visibility = "hidden";
      style.contain = "strict";
      style.width = style.height = 0;
      style.position = "absolute";
      style.top = style.left = 0;
      style.zIndex = -1;
      const svg = this.#document.createElementNS(SVG_NS, "svg");
      svg.setAttribute("width", 0);
      svg.setAttribute("height", 0);
      this.#_defs = this.#document.createElementNS(SVG_NS, "defs");
      div.append(svg);
      svg.append(this.#_defs);
      this.#document.body.append(div);
    }
    return this.#_defs;
  }
  #createTables(maps) {
    if (maps.length === 1) {
      const mapR = maps[0];
      const buffer = new Array(256);
      for (let i = 0; i < 256; i++) {
        buffer[i] = mapR[i] / 255;
      }
      const table = buffer.join(",");
      return [table, table, table];
    }
    const [mapR, mapG, mapB] = maps;
    const bufferR = new Array(256);
    const bufferG = new Array(256);
    const bufferB = new Array(256);
    for (let i = 0; i < 256; i++) {
      bufferR[i] = mapR[i] / 255;
      bufferG[i] = mapG[i] / 255;
      bufferB[i] = mapB[i] / 255;
    }
    return [bufferR.join(","), bufferG.join(","), bufferB.join(",")];
  }
  #createUrl(id) {
    if (this.#baseUrl === undefined) {
      this.#baseUrl = "";
      const url = this.#document.URL;
      if (url !== this.#document.baseURI) {
        if (isDataScheme(url)) {
          warn('#createUrl: ignore "data:"-URL for performance reasons.');
        } else {
          this.#baseUrl = url.split("#", 1)[0];
        }
      }
    }
    return `url(${this.#baseUrl}#${id})`;
  }
  addFilter(maps) {
    if (!maps) {
      return "none";
    }
    let value = this.#cache.get(maps);
    if (value) {
      return value;
    }
    const [tableR, tableG, tableB] = this.#createTables(maps);
    const key = maps.length === 1 ? tableR : `${tableR}${tableG}${tableB}`;
    value = this.#cache.get(key);
    if (value) {
      this.#cache.set(maps, value);
      return value;
    }
    const id = `g_${this.#docId}_transfer_map_${this.#id++}`;
    const url = this.#createUrl(id);
    this.#cache.set(maps, url);
    this.#cache.set(key, url);
    const filter = this.#createFilter(id);
    this.#addTransferMapConversion(tableR, tableG, tableB, filter);
    return url;
  }
  addHCMFilter(fgColor, bgColor) {
    const key = `${fgColor}-${bgColor}`;
    const filterName = "base";
    let info = this.#hcmCache.get(filterName);
    if (info?.key === key) {
      return info.url;
    }
    if (info) {
      info.filter?.remove();
      info.key = key;
      info.url = "none";
      info.filter = null;
    } else {
      info = {
        key,
        url: "none",
        filter: null
      };
      this.#hcmCache.set(filterName, info);
    }
    if (!fgColor || !bgColor) {
      return info.url;
    }
    const fgRGB = this.#getRGB(fgColor);
    fgColor = Util.makeHexColor(...fgRGB);
    const bgRGB = this.#getRGB(bgColor);
    bgColor = Util.makeHexColor(...bgRGB);
    this.#defs.style.color = "";
    if (fgColor === "#000000" && bgColor === "#ffffff" || fgColor === bgColor) {
      return info.url;
    }
    const map = new Array(256);
    for (let i = 0; i <= 255; i++) {
      const x = i / 255;
      map[i] = x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4;
    }
    const table = map.join(",");
    const id = `g_${this.#docId}_hcm_filter`;
    const filter = info.filter = this.#createFilter(id);
    this.#addTransferMapConversion(table, table, table, filter);
    this.#addGrayConversion(filter);
    const getSteps = (c, n) => {
      const start = fgRGB[c] / 255;
      const end = bgRGB[c] / 255;
      const arr = new Array(n + 1);
      for (let i = 0; i <= n; i++) {
        arr[i] = start + i / n * (end - start);
      }
      return arr.join(",");
    };
    this.#addTransferMapConversion(getSteps(0, 5), getSteps(1, 5), getSteps(2, 5), filter);
    info.url = this.#createUrl(id);
    return info.url;
  }
  addAlphaFilter(map) {
    let value = this.#cache.get(map);
    if (value) {
      return value;
    }
    const [tableA] = this.#createTables([map]);
    const key = `alpha_${tableA}`;
    value = this.#cache.get(key);
    if (value) {
      this.#cache.set(map, value);
      return value;
    }
    const id = `g_${this.#docId}_alpha_map_${this.#id++}`;
    const url = this.#createUrl(id);
    this.#cache.set(map, url);
    this.#cache.set(key, url);
    const filter = this.#createFilter(id);
    this.#addTransferMapAlphaConversion(tableA, filter);
    return url;
  }
  addLuminosityFilter(map) {
    let value = this.#cache.get(map || "luminosity");
    if (value) {
      return value;
    }
    let tableA, key;
    if (map) {
      [tableA] = this.#createTables([map]);
      key = `luminosity_${tableA}`;
    } else {
      key = "luminosity";
    }
    value = this.#cache.get(key);
    if (value) {
      this.#cache.set(map, value);
      return value;
    }
    const id = `g_${this.#docId}_luminosity_map_${this.#id++}`;
    const url = this.#createUrl(id);
    this.#cache.set(map, url);
    this.#cache.set(key, url);
    const filter = this.#createFilter(id);
    this.#addLuminosityConversion(filter);
    if (map) {
      this.#addTransferMapAlphaConversion(tableA, filter);
    }
    return url;
  }
  addHighlightHCMFilter(filterName, fgColor, bgColor, newFgColor, newBgColor) {
    const key = `${fgColor}-${bgColor}-${newFgColor}-${newBgColor}`;
    let info = this.#hcmCache.get(filterName);
    if (info?.key === key) {
      return info.url;
    }
    if (info) {
      info.filter?.remove();
      info.key = key;
      info.url = "none";
      info.filter = null;
    } else {
      info = {
        key,
        url: "none",
        filter: null
      };
      this.#hcmCache.set(filterName, info);
    }
    if (!fgColor || !bgColor) {
      return info.url;
    }
    const [fgRGB, bgRGB] = [fgColor, bgColor].map(this.#getRGB.bind(this));
    let fgGray = Math.round(0.2126 * fgRGB[0] + 0.7152 * fgRGB[1] + 0.0722 * fgRGB[2]);
    let bgGray = Math.round(0.2126 * bgRGB[0] + 0.7152 * bgRGB[1] + 0.0722 * bgRGB[2]);
    let [newFgRGB, newBgRGB] = [newFgColor, newBgColor].map(this.#getRGB.bind(this));
    if (bgGray < fgGray) {
      [fgGray, bgGray, newFgRGB, newBgRGB] = [bgGray, fgGray, newBgRGB, newFgRGB];
    }
    this.#defs.style.color = "";
    const getSteps = (fg, bg, n) => {
      const arr = new Array(256);
      const step = (bgGray - fgGray) / n;
      const newStart = fg / 255;
      const newStep = (bg - fg) / (255 * n);
      let prev = 0;
      for (let i = 0; i <= n; i++) {
        const k = Math.round(fgGray + i * step);
        const value = newStart + i * newStep;
        for (let j = prev; j <= k; j++) {
          arr[j] = value;
        }
        prev = k + 1;
      }
      for (let i = prev; i < 256; i++) {
        arr[i] = arr[prev - 1];
      }
      return arr.join(",");
    };
    const id = `g_${this.#docId}_hcm_${filterName}_filter`;
    const filter = info.filter = this.#createFilter(id);
    this.#addGrayConversion(filter);
    this.#addTransferMapConversion(getSteps(newFgRGB[0], newBgRGB[0], 5), getSteps(newFgRGB[1], newBgRGB[1], 5), getSteps(newFgRGB[2], newBgRGB[2], 5), filter);
    info.url = this.#createUrl(id);
    return info.url;
  }
  destroy(keepHCM = false) {
    if (keepHCM && this.#hcmCache.size !== 0) {
      return;
    }
    if (this.#_defs) {
      this.#_defs.parentNode.parentNode.remove();
      this.#_defs = null;
    }
    if (this.#_cache) {
      this.#_cache.clear();
      this.#_cache = null;
    }
    this.#id = 0;
  }
  #addLuminosityConversion(filter) {
    const feColorMatrix = this.#document.createElementNS(SVG_NS, "feColorMatrix");
    feColorMatrix.setAttribute("type", "matrix");
    feColorMatrix.setAttribute("values", "0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0.59 0.11 0 0");
    filter.append(feColorMatrix);
  }
  #addGrayConversion(filter) {
    const feColorMatrix = this.#document.createElementNS(SVG_NS, "feColorMatrix");
    feColorMatrix.setAttribute("type", "matrix");
    feColorMatrix.setAttribute("values", "0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0.2126 0.7152 0.0722 0 0 0 0 0 1 0");
    filter.append(feColorMatrix);
  }
  #createFilter(id) {
    const filter = this.#document.createElementNS(SVG_NS, "filter");
    filter.setAttribute("color-interpolation-filters", "sRGB");
    filter.setAttribute("id", id);
    this.#defs.append(filter);
    return filter;
  }
  #appendFeFunc(feComponentTransfer, func, table) {
    const feFunc = this.#document.createElementNS(SVG_NS, func);
    feFunc.setAttribute("type", "discrete");
    feFunc.setAttribute("tableValues", table);
    feComponentTransfer.append(feFunc);
  }
  #addTransferMapConversion(rTable, gTable, bTable, filter) {
    const feComponentTransfer = this.#document.createElementNS(SVG_NS, "feComponentTransfer");
    filter.append(feComponentTransfer);
    this.#appendFeFunc(feComponentTransfer, "feFuncR", rTable);
    this.#appendFeFunc(feComponentTransfer, "feFuncG", gTable);
    this.#appendFeFunc(feComponentTransfer, "feFuncB", bTable);
  }
  #addTransferMapAlphaConversion(aTable, filter) {
    const feComponentTransfer = this.#document.createElementNS(SVG_NS, "feComponentTransfer");
    filter.append(feComponentTransfer);
    this.#appendFeFunc(feComponentTransfer, "feFuncA", aTable);
  }
  #getRGB(color) {
    this.#defs.style.color = color;
    return getRGB(getComputedStyle(this.#defs).getPropertyValue("color"));
  }
}
class DOMCanvasFactory extends BaseCanvasFactory {
  constructor({
    ownerDocument = globalThis.document,
    enableHWA = false
  } = {}) {
    super({
      enableHWA
    });
    this._document = ownerDocument;
  }
  _createCanvas(width, height) {
    const canvas = this._document.createElement("canvas");
    canvas.width = width;
    canvas.height = height;
    return canvas;
  }
}
async function fetchData(url, type = "text") {
  const response = await fetch(url);
  if (!response.ok) {
    throw new Error(response.statusText);
  }
  switch (type) {
    case "arraybuffer":
      return response.arrayBuffer();
    case "blob":
      return response.blob();
    case "json":
      return response.json();
  }
  return response.text();
}
class DOMCMapReaderFactory extends BaseCMapReaderFactory {
  _fetchData(url, compressionType) {
    return fetchData(url, this.isCompressed ? "arraybuffer" : "text").then(data => ({
      cMapData: data instanceof ArrayBuffer ? new Uint8Array(data) : stringToBytes(data),
      compressionType
    }));
  }
}
class DOMStandardFontDataFactory extends BaseStandardFontDataFactory {
  _fetchData(url) {
    return fetchData(url, "arraybuffer").then(data => new Uint8Array(data));
  }
}
class DOMSVGFactory extends BaseSVGFactory {
  _createSVG(type) {
    return document.createElementNS(SVG_NS, type);
  }
}
class PageViewport {
  constructor({
    viewBox,
    scale,
    rotation,
    offsetX = 0,
    offsetY = 0,
    dontFlip = false
  }) {
    this.viewBox = viewBox;
    this.scale = scale;
    this.rotation = rotation;
    this.offsetX = offsetX;
    this.offsetY = offsetY;
    const centerX = (viewBox[2] + viewBox[0]) / 2;
    const centerY = (viewBox[3] + viewBox[1]) / 2;
    let rotateA, rotateB, rotateC, rotateD;
    rotation %= 360;
    if (rotation < 0) {
      rotation += 360;
    }
    switch (rotation) {
      case 180:
        rotateA = -1;
        rotateB = 0;
        rotateC = 0;
        rotateD = 1;
        break;
      case 90:
        rotateA = 0;
        rotateB = 1;
        rotateC = 1;
        rotateD = 0;
        break;
      case 270:
        rotateA = 0;
        rotateB = -1;
        rotateC = -1;
        rotateD = 0;
        break;
      case 0:
        rotateA = 1;
        rotateB = 0;
        rotateC = 0;
        rotateD = -1;
        break;
      default:
        throw new Error("PageViewport: Invalid rotation, must be a multiple of 90 degrees.");
    }
    if (dontFlip) {
      rotateC = -rotateC;
      rotateD = -rotateD;
    }
    let offsetCanvasX, offsetCanvasY;
    let width, height;
    if (rotateA === 0) {
      offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
      offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
      width = (viewBox[3] - viewBox[1]) * scale;
      height = (viewBox[2] - viewBox[0]) * scale;
    } else {
      offsetCanvasX = Math.abs(centerX - viewBox[0]) * scale + offsetX;
      offsetCanvasY = Math.abs(centerY - viewBox[1]) * scale + offsetY;
      width = (viewBox[2] - viewBox[0]) * scale;
      height = (viewBox[3] - viewBox[1]) * scale;
    }
    this.transform = [rotateA * scale, rotateB * scale, rotateC * scale, rotateD * scale, offsetCanvasX - rotateA * scale * centerX - rotateC * scale * centerY, offsetCanvasY - rotateB * scale * centerX - rotateD * scale * centerY];
    this.width = width;
    this.height = height;
  }
  get rawDims() {
    const {
      viewBox
    } = this;
    return shadow(this, "rawDims", {
      pageWidth: viewBox[2] - viewBox[0],
      pageHeight: viewBox[3] - viewBox[1],
      pageX: viewBox[0],
      pageY: viewBox[1]
    });
  }
  clone({
    scale = this.scale,
    rotation = this.rotation,
    offsetX = this.offsetX,
    offsetY = this.offsetY,
    dontFlip = false
  } = {}) {
    return new PageViewport({
      viewBox: this.viewBox.slice(),
      scale,
      rotation,
      offsetX,
      offsetY,
      dontFlip
    });
  }
  convertToViewportPoint(x, y) {
    return Util.applyTransform([x, y], this.transform);
  }
  convertToViewportRectangle(rect) {
    const topLeft = Util.applyTransform([rect[0], rect[1]], this.transform);
    const bottomRight = Util.applyTransform([rect[2], rect[3]], this.transform);
    return [topLeft[0], topLeft[1], bottomRight[0], bottomRight[1]];
  }
  convertToPdfPoint(x, y) {
    return Util.applyInverseTransform([x, y], this.transform);
  }
}
class RenderingCancelledException extends BaseException {
  constructor(msg, extraDelay = 0) {
    super(msg, "RenderingCancelledException");
    this.extraDelay = extraDelay;
  }
}
function isDataScheme(url) {
  const ii = url.length;
  let i = 0;
  while (i < ii && url[i].trim() === "") {
    i++;
  }
  return url.substring(i, i + 5).toLowerCase() === "data:";
}
function isPdfFile(filename) {
  return typeof filename === "string" && /\.pdf$/i.test(filename);
}
function getFilenameFromUrl(url) {
  [url] = url.split(/[#?]/, 1);
  return url.substring(url.lastIndexOf("/") + 1);
}
function getPdfFilenameFromUrl(url, defaultFilename = "document.pdf") {
  if (typeof url !== "string") {
    return defaultFilename;
  }
  if (isDataScheme(url)) {
    warn('getPdfFilenameFromUrl: ignore "data:"-URL for performance reasons.');
    return defaultFilename;
  }
  const reURI = /^(?:(?:[^:]+:)?\/\/[^/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
  const reFilename = /[^/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
  const splitURI = reURI.exec(url);
  let suggestedFilename = reFilename.exec(splitURI[1]) || reFilename.exec(splitURI[2]) || reFilename.exec(splitURI[3]);
  if (suggestedFilename) {
    suggestedFilename = suggestedFilename[0];
    if (suggestedFilename.includes("%")) {
      try {
        suggestedFilename = reFilename.exec(decodeURIComponent(suggestedFilename))[0];
      } catch {}
    }
  }
  return suggestedFilename || defaultFilename;
}
class StatTimer {
  started = Object.create(null);
  times = [];
  time(name) {
    if (name in this.started) {
      warn(`Timer is already running for ${name}`);
    }
    this.started[name] = Date.now();
  }
  timeEnd(name) {
    if (!(name in this.started)) {
      warn(`Timer has not been started for ${name}`);
    }
    this.times.push({
      name,
      start: this.started[name],
      end: Date.now()
    });
    delete this.started[name];
  }
  toString() {
    const outBuf = [];
    let longest = 0;
    for (const {
      name
    } of this.times) {
      longest = Math.max(name.length, longest);
    }
    for (const {
      name,
      start,
      end
    } of this.times) {
      outBuf.push(`${name.padEnd(longest)} ${end - start}ms\n`);
    }
    return outBuf.join("");
  }
}
function isValidFetchUrl(url, baseUrl) {
  throw new Error("Not implemented: isValidFetchUrl");
}
function noContextMenu(e) {
  e.preventDefault();
}
function deprecated(details) {
  console.log("Deprecated API usage: " + details);
}
let pdfDateStringRegex;
class PDFDateString {
  static toDateObject(input) {
    if (!input || typeof input !== "string") {
      return null;
    }
    pdfDateStringRegex ||= new RegExp("^D:" + "(\\d{4})" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "(\\d{2})?" + "([Z|+|-])?" + "(\\d{2})?" + "'?" + "(\\d{2})?" + "'?");
    const matches = pdfDateStringRegex.exec(input);
    if (!matches) {
      return null;
    }
    const year = parseInt(matches[1], 10);
    let month = parseInt(matches[2], 10);
    month = month >= 1 && month <= 12 ? month - 1 : 0;
    let day = parseInt(matches[3], 10);
    day = day >= 1 && day <= 31 ? day : 1;
    let hour = parseInt(matches[4], 10);
    hour = hour >= 0 && hour <= 23 ? hour : 0;
    let minute = parseInt(matches[5], 10);
    minute = minute >= 0 && minute <= 59 ? minute : 0;
    let second = parseInt(matches[6], 10);
    second = second >= 0 && second <= 59 ? second : 0;
    const universalTimeRelation = matches[7] || "Z";
    let offsetHour = parseInt(matches[8], 10);
    offsetHour = offsetHour >= 0 && offsetHour <= 23 ? offsetHour : 0;
    let offsetMinute = parseInt(matches[9], 10) || 0;
    offsetMinute = offsetMinute >= 0 && offsetMinute <= 59 ? offsetMinute : 0;
    if (universalTimeRelation === "-") {
      hour += offsetHour;
      minute += offsetMinute;
    } else if (universalTimeRelation === "+") {
      hour -= offsetHour;
      minute -= offsetMinute;
    }
    return new Date(Date.UTC(year, month, day, hour, minute, second));
  }
}
function getXfaPageViewport(xfaPage, {
  scale = 1,
  rotation = 0
}) {
  const {
    width,
    height
  } = xfaPage.attributes.style;
  const viewBox = [0, 0, parseInt(width), parseInt(height)];
  return new PageViewport({
    viewBox,
    scale,
    rotation
  });
}
function getRGB(color) {
  if (color.startsWith("#")) {
    const colorRGB = parseInt(color.slice(1), 16);
    return [(colorRGB & 0xff0000) >> 16, (colorRGB & 0x00ff00) >> 8, colorRGB & 0x0000ff];
  }
  if (color.startsWith("rgb(")) {
    return color.slice(4, -1).split(",").map(x => parseInt(x));
  }
  if (color.startsWith("rgba(")) {
    return color.slice(5, -1).split(",").map(x => parseInt(x)).slice(0, 3);
  }
  warn(`Not a valid color format: "${color}"`);
  return [0, 0, 0];
}
function getColorValues(colors) {
  const span = document.createElement("span");
  span.style.visibility = "hidden";
  document.body.append(span);
  for (const name of colors.keys()) {
    span.style.color = name;
    const computedColor = window.getComputedStyle(span).color;
    colors.set(name, getRGB(computedColor));
  }
  span.remove();
}
function getCurrentTransform(ctx) {
  const {
    a,
    b,
    c,
    d,
    e,
    f
  } = ctx.getTransform();
  return [a, b, c, d, e, f];
}
function getCurrentTransformInverse(ctx) {
  const {
    a,
    b,
    c,
    d,
    e,
    f
  } = ctx.getTransform().invertSelf();
  return [a, b, c, d, e, f];
}
function setLayerDimensions(div, viewport, mustFlip = false, mustRotate = true) {
  if (viewport instanceof PageViewport) {
    const {
      pageWidth,
      pageHeight
    } = viewport.rawDims;
    const {
      style
    } = div;
    const useRound = util_FeatureTest.isCSSRoundSupported;
    const w = `var(--scale-factor) * ${pageWidth}px`,
      h = `var(--scale-factor) * ${pageHeight}px`;
    const widthStr = useRound ? `round(${w}, 1px)` : `calc(${w})`,
      heightStr = useRound ? `round(${h}, 1px)` : `calc(${h})`;
    if (!mustFlip || viewport.rotation % 180 === 0) {
      style.width = widthStr;
      style.height = heightStr;
    } else {
      style.width = heightStr;
      style.height = widthStr;
    }
  }
  if (mustRotate) {
    div.setAttribute("data-main-rotation", viewport.rotation);
  }
}

;// CONCATENATED MODULE: ./src/display/editor/toolbar.js

class EditorToolbar {
  #toolbar = null;
  #colorPicker = null;
  #editor;
  #buttons = null;
  #altText = null;
  constructor(editor) {
    this.#editor = editor;
  }
  render() {
    const editToolbar = this.#toolbar = document.createElement("div");
    editToolbar.className = "editToolbar";
    editToolbar.setAttribute("role", "toolbar");
    const signal = this.#editor._uiManager._signal;
    editToolbar.addEventListener("contextmenu", noContextMenu, {
      signal
    });
    editToolbar.addEventListener("pointerdown", EditorToolbar.#pointerDown, {
      signal
    });
    const buttons = this.#buttons = document.createElement("div");
    buttons.className = "buttons";
    editToolbar.append(buttons);
    const position = this.#editor.toolbarPosition;
    if (position) {
      const {
        style
      } = editToolbar;
      const x = this.#editor._uiManager.direction === "ltr" ? 1 - position[0] : position[0];
      style.insetInlineEnd = `${100 * x}%`;
      style.top = `calc(${100 * position[1]}% + var(--editor-toolbar-vert-offset))`;
    }
    this.#addDeleteButton();
    return editToolbar;
  }
  static #pointerDown(e) {
    e.stopPropagation();
  }
  #focusIn(e) {
    this.#editor._focusEventsAllowed = false;
    e.preventDefault();
    e.stopPropagation();
  }
  #focusOut(e) {
    this.#editor._focusEventsAllowed = true;
    e.preventDefault();
    e.stopPropagation();
  }
  #addListenersToElement(element) {
    const signal = this.#editor._uiManager._signal;
    element.addEventListener("focusin", this.#focusIn.bind(this), {
      capture: true,
      signal
    });
    element.addEventListener("focusout", this.#focusOut.bind(this), {
      capture: true,
      signal
    });
    element.addEventListener("contextmenu", noContextMenu, {
      signal
    });
  }
  hide() {
    this.#toolbar.classList.add("hidden");
    this.#colorPicker?.hideDropdown();
  }
  show() {
    this.#toolbar.classList.remove("hidden");
    this.#altText?.shown();
  }
  #addDeleteButton() {
    const button = document.createElement("button");
    button.className = "delete";
    button.tabIndex = 0;
    button.setAttribute("data-l10n-id", `pdfjs-editor-remove-${this.#editor.editorType}-button`);
    this.#addListenersToElement(button);
    button.addEventListener("click", e => {
      this.#editor._uiManager.delete();
    }, {
      signal: this.#editor._uiManager._signal
    });
    this.#buttons.append(button);
  }
  get #divider() {
    const divider = document.createElement("div");
    divider.className = "divider";
    return divider;
  }
  async addAltText(altText) {
    const button = await altText.render();
    this.#addListenersToElement(button);
    this.#buttons.prepend(button, this.#divider);
    this.#altText = altText;
  }
  addColorPicker(colorPicker) {
    this.#colorPicker = colorPicker;
    const button = colorPicker.renderButton();
    this.#addListenersToElement(button);
    this.#buttons.prepend(button, this.#divider);
  }
  remove() {
    this.#toolbar.remove();
    this.#colorPicker?.destroy();
    this.#colorPicker = null;
  }
}
class HighlightToolbar {
  #buttons = null;
  #toolbar = null;
  #uiManager;
  constructor(uiManager) {
    this.#uiManager = uiManager;
  }
  #render() {
    const editToolbar = this.#toolbar = document.createElement("div");
    editToolbar.className = "editToolbar";
    editToolbar.setAttribute("role", "toolbar");
    editToolbar.addEventListener("contextmenu", noContextMenu, {
      signal: this.#uiManager._signal
    });
    const buttons = this.#buttons = document.createElement("div");
    buttons.className = "buttons";
    editToolbar.append(buttons);
    this.#addHighlightButton();
    return editToolbar;
  }
  #getLastPoint(boxes, isLTR) {
    let lastY = 0;
    let lastX = 0;
    for (const box of boxes) {
      const y = box.y + box.height;
      if (y < lastY) {
        continue;
      }
      const x = box.x + (isLTR ? box.width : 0);
      if (y > lastY) {
        lastX = x;
        lastY = y;
        continue;
      }
      if (isLTR) {
        if (x > lastX) {
          lastX = x;
        }
      } else if (x < lastX) {
        lastX = x;
      }
    }
    return [isLTR ? 1 - lastX : lastX, lastY];
  }
  show(parent, boxes, isLTR) {
    const [x, y] = this.#getLastPoint(boxes, isLTR);
    const {
      style
    } = this.#toolbar ||= this.#render();
    parent.append(this.#toolbar);
    style.insetInlineEnd = `${100 * x}%`;
    style.top = `calc(${100 * y}% + var(--editor-toolbar-vert-offset))`;
  }
  hide() {
    this.#toolbar.remove();
  }
  #addHighlightButton() {
    const button = document.createElement("button");
    button.className = "highlightButton";
    button.tabIndex = 0;
    button.setAttribute("data-l10n-id", `pdfjs-highlight-floating-button1`);
    const span = document.createElement("span");
    button.append(span);
    span.className = "visuallyHidden";
    span.setAttribute("data-l10n-id", "pdfjs-highlight-floating-button-label");
    const signal = this.#uiManager._signal;
    button.addEventListener("contextmenu", noContextMenu, {
      signal
    });
    button.addEventListener("click", () => {
      this.#uiManager.highlightSelection("floating_button");
    }, {
      signal
    });
    this.#buttons.append(button);
  }
}

;// CONCATENATED MODULE: ./src/display/editor/tools.js



function bindEvents(obj, element, names) {
  for (const name of names) {
    element.addEventListener(name, obj[name].bind(obj));
  }
}
function opacityToHex(opacity) {
  return Math.round(Math.min(255, Math.max(1, 255 * opacity))).toString(16).padStart(2, "0");
}
class IdManager {
  #id = 0;
  get id() {
    return `${AnnotationEditorPrefix}${this.#id++}`;
  }
}
class ImageManager {
  #baseId = getUuid();
  #id = 0;
  #cache = null;
  static get _isSVGFittingCanvas() {
    const svg = `data:image/svg+xml;charset=UTF-8,<svg viewBox="0 0 1 1" width="1" height="1" xmlns="http://www.w3.org/2000/svg"><rect width="1" height="1" style="fill:red;"/></svg>`;
    const canvas = new OffscreenCanvas(1, 3);
    const ctx = canvas.getContext("2d", {
      willReadFrequently: true
    });
    const image = new Image();
    image.src = svg;
    const promise = image.decode().then(() => {
      ctx.drawImage(image, 0, 0, 1, 1, 0, 0, 1, 3);
      return new Uint32Array(ctx.getImageData(0, 0, 1, 1).data.buffer)[0] === 0;
    });
    return shadow(this, "_isSVGFittingCanvas", promise);
  }
  async #get(key, rawData) {
    this.#cache ||= new Map();
    let data = this.#cache.get(key);
    if (data === null) {
      return null;
    }
    if (data?.bitmap) {
      data.refCounter += 1;
      return data;
    }
    try {
      data ||= {
        bitmap: null,
        id: `image_${this.#baseId}_${this.#id++}`,
        refCounter: 0,
        isSvg: false
      };
      let image;
      if (typeof rawData === "string") {
        data.url = rawData;
        image = await fetchData(rawData, "blob");
      } else {
        image = data.file = rawData;
      }
      if (image.type === "image/svg+xml") {
        const mustRemoveAspectRatioPromise = ImageManager._isSVGFittingCanvas;
        const fileReader = new FileReader();
        const imageElement = new Image();
        const imagePromise = new Promise((resolve, reject) => {
          imageElement.onload = () => {
            data.bitmap = imageElement;
            data.isSvg = true;
            resolve();
          };
          fileReader.onload = async () => {
            const url = data.svgUrl = fileReader.result;
            imageElement.src = (await mustRemoveAspectRatioPromise) ? `${url}#svgView(preserveAspectRatio(none))` : url;
          };
          imageElement.onerror = fileReader.onerror = reject;
        });
        fileReader.readAsDataURL(image);
        await imagePromise;
      } else {
        data.bitmap = await createImageBitmap(image);
      }
      data.refCounter = 1;
    } catch (e) {
      console.error(e);
      data = null;
    }
    this.#cache.set(key, data);
    if (data) {
      this.#cache.set(data.id, data);
    }
    return data;
  }
  async getFromFile(file) {
    const {
      lastModified,
      name,
      size,
      type
    } = file;
    return this.#get(`${lastModified}_${name}_${size}_${type}`, file);
  }
  async getFromUrl(url) {
    return this.#get(url, url);
  }
  async getFromId(id) {
    this.#cache ||= new Map();
    const data = this.#cache.get(id);
    if (!data) {
      return null;
    }
    if (data.bitmap) {
      data.refCounter += 1;
      return data;
    }
    if (data.file) {
      return this.getFromFile(data.file);
    }
    return this.getFromUrl(data.url);
  }
  getSvgUrl(id) {
    const data = this.#cache.get(id);
    if (!data?.isSvg) {
      return null;
    }
    return data.svgUrl;
  }
  deleteId(id) {
    this.#cache ||= new Map();
    const data = this.#cache.get(id);
    if (!data) {
      return;
    }
    data.refCounter -= 1;
    if (data.refCounter !== 0) {
      return;
    }
    data.bitmap = null;
  }
  isValidId(id) {
    return id.startsWith(`image_${this.#baseId}_`);
  }
}
class CommandManager {
  #commands = [];
  #locked = false;
  #maxSize;
  #position = -1;
  constructor(maxSize = 128) {
    this.#maxSize = maxSize;
  }
  add({
    cmd,
    undo,
    post,
    mustExec,
    type = NaN,
    overwriteIfSameType = false,
    keepUndo = false
  }) {
    if (mustExec) {
      cmd();
    }
    if (this.#locked) {
      return;
    }
    const save = {
      cmd,
      undo,
      post,
      type
    };
    if (this.#position === -1) {
      if (this.#commands.length > 0) {
        this.#commands.length = 0;
      }
      this.#position = 0;
      this.#commands.push(save);
      return;
    }
    if (overwriteIfSameType && this.#commands[this.#position].type === type) {
      if (keepUndo) {
        save.undo = this.#commands[this.#position].undo;
      }
      this.#commands[this.#position] = save;
      return;
    }
    const next = this.#position + 1;
    if (next === this.#maxSize) {
      this.#commands.splice(0, 1);
    } else {
      this.#position = next;
      if (next < this.#commands.length) {
        this.#commands.splice(next);
      }
    }
    this.#commands.push(save);
  }
  undo() {
    if (this.#position === -1) {
      return;
    }
    this.#locked = true;
    const {
      undo,
      post
    } = this.#commands[this.#position];
    undo();
    post?.();
    this.#locked = false;
    this.#position -= 1;
  }
  redo() {
    if (this.#position < this.#commands.length - 1) {
      this.#position += 1;
      this.#locked = true;
      const {
        cmd,
        post
      } = this.#commands[this.#position];
      cmd();
      post?.();
      this.#locked = false;
    }
  }
  hasSomethingToUndo() {
    return this.#position !== -1;
  }
  hasSomethingToRedo() {
    return this.#position < this.#commands.length - 1;
  }
  destroy() {
    this.#commands = null;
  }
}
class KeyboardManager {
  constructor(callbacks) {
    this.buffer = [];
    this.callbacks = new Map();
    this.allKeys = new Set();
    const {
      isMac
    } = util_FeatureTest.platform;
    for (const [keys, callback, options = {}] of callbacks) {
      for (const key of keys) {
        const isMacKey = key.startsWith("mac+");
        if (isMac && isMacKey) {
          this.callbacks.set(key.slice(4), {
            callback,
            options
          });
          this.allKeys.add(key.split("+").at(-1));
        } else if (!isMac && !isMacKey) {
          this.callbacks.set(key, {
            callback,
            options
          });
          this.allKeys.add(key.split("+").at(-1));
        }
      }
    }
  }
  #serialize(event) {
    if (event.altKey) {
      this.buffer.push("alt");
    }
    if (event.ctrlKey) {
      this.buffer.push("ctrl");
    }
    if (event.metaKey) {
      this.buffer.push("meta");
    }
    if (event.shiftKey) {
      this.buffer.push("shift");
    }
    this.buffer.push(event.key);
    const str = this.buffer.join("+");
    this.buffer.length = 0;
    return str;
  }
  exec(self, event) {
    if (!this.allKeys.has(event.key)) {
      return;
    }
    const info = this.callbacks.get(this.#serialize(event));
    if (!info) {
      return;
    }
    const {
      callback,
      options: {
        bubbles = false,
        args = [],
        checker = null
      }
    } = info;
    if (checker && !checker(self, event)) {
      return;
    }
    callback.bind(self, ...args, event)();
    if (!bubbles) {
      event.stopPropagation();
      event.preventDefault();
    }
  }
}
class ColorManager {
  static _colorsMapping = new Map([["CanvasText", [0, 0, 0]], ["Canvas", [255, 255, 255]]]);
  get _colors() {
    const colors = new Map([["CanvasText", null], ["Canvas", null]]);
    getColorValues(colors);
    return shadow(this, "_colors", colors);
  }
  convert(color) {
    const rgb = getRGB(color);
    if (!window.matchMedia("(forced-colors: active)").matches) {
      return rgb;
    }
    for (const [name, RGB] of this._colors) {
      if (RGB.every((x, i) => x === rgb[i])) {
        return ColorManager._colorsMapping.get(name);
      }
    }
    return rgb;
  }
  getHexCode(name) {
    const rgb = this._colors.get(name);
    if (!rgb) {
      return name;
    }
    return Util.makeHexColor(...rgb);
  }
}
class AnnotationEditorUIManager {
  #abortController = new AbortController();
  #activeEditor = null;
  #allEditors = new Map();
  #allLayers = new Map();
  #altTextManager = null;
  #annotationStorage = null;
  #changedExistingAnnotations = null;
  #commandManager = new CommandManager();
  #copyPasteAC = null;
  #currentPageIndex = 0;
  #deletedAnnotationsElementIds = new Set();
  #draggingEditors = null;
  #editorTypes = null;
  #editorsToRescale = new Set();
  #enableHighlightFloatingButton = false;
  #enableUpdatedAddImage = false;
  #enableNewAltTextWhenAddingImage = false;
  #filterFactory = null;
  #focusMainContainerTimeoutId = null;
  #focusManagerAC = null;
  #highlightColors = null;
  #highlightWhenShiftUp = false;
  #highlightToolbar = null;
  #idManager = new IdManager();
  #isEnabled = false;
  #isWaiting = false;
  #keyboardManagerAC = null;
  #lastActiveElement = null;
  #mainHighlightColorPicker = null;
  #mlManager = null;
  #mode = AnnotationEditorType.NONE;
  #selectedEditors = new Set();
  #selectedTextNode = null;
  #pageColors = null;
  #showAllStates = null;
  #previousStates = {
    isEditing: false,
    isEmpty: true,
    hasSomethingToUndo: false,
    hasSomethingToRedo: false,
    hasSelectedEditor: false,
    hasSelectedText: false
  };
  #translation = [0, 0];
  #translationTimeoutId = null;
  #container = null;
  #viewer = null;
  static TRANSLATE_SMALL = 1;
  static TRANSLATE_BIG = 10;
  static get _keyboardManager() {
    const proto = AnnotationEditorUIManager.prototype;
    const arrowChecker = self => self.#container.contains(document.activeElement) && document.activeElement.tagName !== "BUTTON" && self.hasSomethingToControl();
    const textInputChecker = (_self, {
      target: el
    }) => {
      if (el instanceof HTMLInputElement) {
        const {
          type
        } = el;
        return type !== "text" && type !== "number";
      }
      return true;
    };
    const small = this.TRANSLATE_SMALL;
    const big = this.TRANSLATE_BIG;
    return shadow(this, "_keyboardManager", new KeyboardManager([[["ctrl+a", "mac+meta+a"], proto.selectAll, {
      checker: textInputChecker
    }], [["ctrl+z", "mac+meta+z"], proto.undo, {
      checker: textInputChecker
    }], [["ctrl+y", "ctrl+shift+z", "mac+meta+shift+z", "ctrl+shift+Z", "mac+meta+shift+Z"], proto.redo, {
      checker: textInputChecker
    }], [["Backspace", "alt+Backspace", "ctrl+Backspace", "shift+Backspace", "mac+Backspace", "mac+alt+Backspace", "mac+ctrl+Backspace", "Delete", "ctrl+Delete", "shift+Delete", "mac+Delete"], proto.delete, {
      checker: textInputChecker
    }], [["Enter", "mac+Enter"], proto.addNewEditorFromKeyboard, {
      checker: (self, {
        target: el
      }) => !(el instanceof HTMLButtonElement) && self.#container.contains(el) && !self.isEnterHandled
    }], [[" ", "mac+ "], proto.addNewEditorFromKeyboard, {
      checker: (self, {
        target: el
      }) => !(el instanceof HTMLButtonElement) && self.#container.contains(document.activeElement)
    }], [["Escape", "mac+Escape"], proto.unselectAll], [["ArrowLeft", "mac+ArrowLeft"], proto.translateSelectedEditors, {
      args: [-small, 0],
      checker: arrowChecker
    }], [["ctrl+ArrowLeft", "mac+shift+ArrowLeft"], proto.translateSelectedEditors, {
      args: [-big, 0],
      checker: arrowChecker
    }], [["ArrowRight", "mac+ArrowRight"], proto.translateSelectedEditors, {
      args: [small, 0],
      checker: arrowChecker
    }], [["ctrl+ArrowRight", "mac+shift+ArrowRight"], proto.translateSelectedEditors, {
      args: [big, 0],
      checker: arrowChecker
    }], [["ArrowUp", "mac+ArrowUp"], proto.translateSelectedEditors, {
      args: [0, -small],
      checker: arrowChecker
    }], [["ctrl+ArrowUp", "mac+shift+ArrowUp"], proto.translateSelectedEditors, {
      args: [0, -big],
      checker: arrowChecker
    }], [["ArrowDown", "mac+ArrowDown"], proto.translateSelectedEditors, {
      args: [0, small],
      checker: arrowChecker
    }], [["ctrl+ArrowDown", "mac+shift+ArrowDown"], proto.translateSelectedEditors, {
      args: [0, big],
      checker: arrowChecker
    }]]));
  }
  constructor(container, viewer, altTextManager, eventBus, pdfDocument, pageColors, highlightColors, enableHighlightFloatingButton, enableUpdatedAddImage, enableNewAltTextWhenAddingImage, mlManager) {
    const signal = this._signal = this.#abortController.signal;
    this.#container = container;
    this.#viewer = viewer;
    this.#altTextManager = altTextManager;
    this._eventBus = eventBus;
    eventBus._on("editingaction", this.onEditingAction.bind(this), {
      signal
    });
    eventBus._on("pagechanging", this.onPageChanging.bind(this), {
      signal
    });
    eventBus._on("scalechanging", this.onScaleChanging.bind(this), {
      signal
    });
    eventBus._on("rotationchanging", this.onRotationChanging.bind(this), {
      signal
    });
    eventBus._on("setpreference", this.onSetPreference.bind(this), {
      signal
    });
    eventBus._on("switchannotationeditorparams", evt => this.updateParams(evt.type, evt.value), {
      signal
    });
    this.#addSelectionListener();
    this.#addDragAndDropListeners();
    this.#addKeyboardManager();
    this.#annotationStorage = pdfDocument.annotationStorage;
    this.#filterFactory = pdfDocument.filterFactory;
    this.#pageColors = pageColors;
    this.#highlightColors = highlightColors || null;
    this.#enableHighlightFloatingButton = enableHighlightFloatingButton;
    this.#enableUpdatedAddImage = enableUpdatedAddImage;
    this.#enableNewAltTextWhenAddingImage = enableNewAltTextWhenAddingImage;
    this.#mlManager = mlManager || null;
    this.viewParameters = {
      realScale: PixelsPerInch.PDF_TO_CSS_UNITS,
      rotation: 0
    };
    this.isShiftKeyDown = false;
  }
  destroy() {
    this.#abortController?.abort();
    this.#abortController = null;
    this._signal = null;
    for (const layer of this.#allLayers.values()) {
      layer.destroy();
    }
    this.#allLayers.clear();
    this.#allEditors.clear();
    this.#editorsToRescale.clear();
    this.#activeEditor = null;
    this.#selectedEditors.clear();
    this.#commandManager.destroy();
    this.#altTextManager?.destroy();
    this.#highlightToolbar?.hide();
    this.#highlightToolbar = null;
    if (this.#focusMainContainerTimeoutId) {
      clearTimeout(this.#focusMainContainerTimeoutId);
      this.#focusMainContainerTimeoutId = null;
    }
    if (this.#translationTimeoutId) {
      clearTimeout(this.#translationTimeoutId);
      this.#translationTimeoutId = null;
    }
  }
  combinedSignal(ac) {
    return AbortSignal.any([this._signal, ac.signal]);
  }
  get mlManager() {
    return this.#mlManager;
  }
  get useNewAltTextFlow() {
    return this.#enableUpdatedAddImage;
  }
  get useNewAltTextWhenAddingImage() {
    return this.#enableNewAltTextWhenAddingImage;
  }
  get hcmFilter() {
    return shadow(this, "hcmFilter", this.#pageColors ? this.#filterFactory.addHCMFilter(this.#pageColors.foreground, this.#pageColors.background) : "none");
  }
  get direction() {
    return shadow(this, "direction", getComputedStyle(this.#container).direction);
  }
  get highlightColors() {
    return shadow(this, "highlightColors", this.#highlightColors ? new Map(this.#highlightColors.split(",").map(pair => pair.split("=").map(x => x.trim()))) : null);
  }
  get highlightColorNames() {
    return shadow(this, "highlightColorNames", this.highlightColors ? new Map(Array.from(this.highlightColors, e => e.reverse())) : null);
  }
  setMainHighlightColorPicker(colorPicker) {
    this.#mainHighlightColorPicker = colorPicker;
  }
  editAltText(editor, firstTime = false) {
    this.#altTextManager?.editAltText(this, editor, firstTime);
  }
  switchToMode(mode, callback) {
    this._eventBus.on("annotationeditormodechanged", callback, {
      once: true,
      signal: this._signal
    });
    this._eventBus.dispatch("showannotationeditorui", {
      source: this,
      mode
    });
  }
  setPreference(name, value) {
    this._eventBus.dispatch("setpreference", {
      source: this,
      name,
      value
    });
  }
  onSetPreference({
    name,
    value
  }) {
    switch (name) {
      case "enableNewAltTextWhenAddingImage":
        this.#enableNewAltTextWhenAddingImage = value;
        break;
    }
  }
  onPageChanging({
    pageNumber
  }) {
    this.#currentPageIndex = pageNumber - 1;
  }
  focusMainContainer() {
    this.#container.focus();
  }
  findParent(x, y) {
    for (const layer of this.#allLayers.values()) {
      const {
        x: layerX,
        y: layerY,
        width,
        height
      } = layer.div.getBoundingClientRect();
      if (x >= layerX && x <= layerX + width && y >= layerY && y <= layerY + height) {
        return layer;
      }
    }
    return null;
  }
  disableUserSelect(value = false) {
    this.#viewer.classList.toggle("noUserSelect", value);
  }
  addShouldRescale(editor) {
    this.#editorsToRescale.add(editor);
  }
  removeShouldRescale(editor) {
    this.#editorsToRescale.delete(editor);
  }
  onScaleChanging({
    scale
  }) {
    this.commitOrRemove();
    this.viewParameters.realScale = scale * PixelsPerInch.PDF_TO_CSS_UNITS;
    for (const editor of this.#editorsToRescale) {
      editor.onScaleChanging();
    }
  }
  onRotationChanging({
    pagesRotation
  }) {
    this.commitOrRemove();
    this.viewParameters.rotation = pagesRotation;
  }
  #getAnchorElementForSelection({
    anchorNode
  }) {
    return anchorNode.nodeType === Node.TEXT_NODE ? anchorNode.parentElement : anchorNode;
  }
  #getLayerForTextLayer(textLayer) {
    const {
      currentLayer
    } = this;
    if (currentLayer.hasTextLayer(textLayer)) {
      return currentLayer;
    }
    for (const layer of this.#allLayers.values()) {
      if (layer.hasTextLayer(textLayer)) {
        return layer;
      }
    }
    return null;
  }
  highlightSelection(methodOfCreation = "") {
    const selection = document.getSelection();
    if (!selection || selection.isCollapsed) {
      return;
    }
    const {
      anchorNode,
      anchorOffset,
      focusNode,
      focusOffset
    } = selection;
    const text = selection.toString();
    const anchorElement = this.#getAnchorElementForSelection(selection);
    const textLayer = anchorElement.closest(".textLayer");
    const boxes = this.getSelectionBoxes(textLayer);
    if (!boxes) {
      return;
    }
    selection.empty();
    const layer = this.#getLayerForTextLayer(textLayer);
    const isNoneMode = this.#mode === AnnotationEditorType.NONE;
    const callback = () => {
      layer?.createAndAddNewEditor({
        x: 0,
        y: 0
      }, false, {
        methodOfCreation,
        boxes,
        anchorNode,
        anchorOffset,
        focusNode,
        focusOffset,
        text
      });
      if (isNoneMode) {
        this.showAllEditors("highlight", true, true);
      }
    };
    if (isNoneMode) {
      this.switchToMode(AnnotationEditorType.HIGHLIGHT, callback);
      return;
    }
    callback();
  }
  #displayHighlightToolbar() {
    const selection = document.getSelection();
    if (!selection || selection.isCollapsed) {
      return;
    }
    const anchorElement = this.#getAnchorElementForSelection(selection);
    const textLayer = anchorElement.closest(".textLayer");
    const boxes = this.getSelectionBoxes(textLayer);
    if (!boxes) {
      return;
    }
    this.#highlightToolbar ||= new HighlightToolbar(this);
    this.#highlightToolbar.show(textLayer, boxes, this.direction === "ltr");
  }
  addToAnnotationStorage(editor) {
    if (!editor.isEmpty() && this.#annotationStorage && !this.#annotationStorage.has(editor.id)) {
      this.#annotationStorage.setValue(editor.id, editor);
    }
  }
  #selectionChange() {
    const selection = document.getSelection();
    if (!selection || selection.isCollapsed) {
      if (this.#selectedTextNode) {
        this.#highlightToolbar?.hide();
        this.#selectedTextNode = null;
        this.#dispatchUpdateStates({
          hasSelectedText: false
        });
      }
      return;
    }
    const {
      anchorNode
    } = selection;
    if (anchorNode === this.#selectedTextNode) {
      return;
    }
    const anchorElement = this.#getAnchorElementForSelection(selection);
    const textLayer = anchorElement.closest(".textLayer");
    if (!textLayer) {
      if (this.#selectedTextNode) {
        this.#highlightToolbar?.hide();
        this.#selectedTextNode = null;
        this.#dispatchUpdateStates({
          hasSelectedText: false
        });
      }
      return;
    }
    this.#highlightToolbar?.hide();
    this.#selectedTextNode = anchorNode;
    this.#dispatchUpdateStates({
      hasSelectedText: true
    });
    if (this.#mode !== AnnotationEditorType.HIGHLIGHT && this.#mode !== AnnotationEditorType.NONE) {
      return;
    }
    if (this.#mode === AnnotationEditorType.HIGHLIGHT) {
      this.showAllEditors("highlight", true, true);
    }
    this.#highlightWhenShiftUp = this.isShiftKeyDown;
    if (!this.isShiftKeyDown) {
      const activeLayer = this.#mode === AnnotationEditorType.HIGHLIGHT ? this.#getLayerForTextLayer(textLayer) : null;
      activeLayer?.toggleDrawing();
      const ac = new AbortController();
      const signal = this.combinedSignal(ac);
      const pointerup = e => {
        if (e.type === "pointerup" && e.button !== 0) {
          return;
        }
        ac.abort();
        activeLayer?.toggleDrawing(true);
        if (e.type === "pointerup") {
          this.#onSelectEnd("main_toolbar");
        }
      };
      window.addEventListener("pointerup", pointerup, {
        signal
      });
      window.addEventListener("blur", pointerup, {
        signal
      });
    }
  }
  #onSelectEnd(methodOfCreation = "") {
    if (this.#mode === AnnotationEditorType.HIGHLIGHT) {
      this.highlightSelection(methodOfCreation);
    } else if (this.#enableHighlightFloatingButton) {
      this.#displayHighlightToolbar();
    }
  }
  #addSelectionListener() {
    document.addEventListener("selectionchange", this.#selectionChange.bind(this), {
      signal: this._signal
    });
  }
  #addFocusManager() {
    if (this.#focusManagerAC) {
      return;
    }
    this.#focusManagerAC = new AbortController();
    const signal = this.combinedSignal(this.#focusManagerAC);
    window.addEventListener("focus", this.focus.bind(this), {
      signal
    });
    window.addEventListener("blur", this.blur.bind(this), {
      signal
    });
  }
  #removeFocusManager() {
    this.#focusManagerAC?.abort();
    this.#focusManagerAC = null;
  }
  blur() {
    this.isShiftKeyDown = false;
    if (this.#highlightWhenShiftUp) {
      this.#highlightWhenShiftUp = false;
      this.#onSelectEnd("main_toolbar");
    }
    if (!this.hasSelection) {
      return;
    }
    const {
      activeElement
    } = document;
    for (const editor of this.#selectedEditors) {
      if (editor.div.contains(activeElement)) {
        this.#lastActiveElement = [editor, activeElement];
        editor._focusEventsAllowed = false;
        break;
      }
    }
  }
  focus() {
    if (!this.#lastActiveElement) {
      return;
    }
    const [lastEditor, lastActiveElement] = this.#lastActiveElement;
    this.#lastActiveElement = null;
    lastActiveElement.addEventListener("focusin", () => {
      lastEditor._focusEventsAllowed = true;
    }, {
      once: true,
      signal: this._signal
    });
    lastActiveElement.focus();
  }
  #addKeyboardManager() {
    if (this.#keyboardManagerAC) {
      return;
    }
    this.#keyboardManagerAC = new AbortController();
    const signal = this.combinedSignal(this.#keyboardManagerAC);
    window.addEventListener("keydown", this.keydown.bind(this), {
      signal
    });
    window.addEventListener("keyup", this.keyup.bind(this), {
      signal
    });
  }
  #removeKeyboardManager() {
    this.#keyboardManagerAC?.abort();
    this.#keyboardManagerAC = null;
  }
  #addCopyPasteListeners() {
    if (this.#copyPasteAC) {
      return;
    }
    this.#copyPasteAC = new AbortController();
    const signal = this.combinedSignal(this.#copyPasteAC);
    document.addEventListener("copy", this.copy.bind(this), {
      signal
    });
    document.addEventListener("cut", this.cut.bind(this), {
      signal
    });
    document.addEventListener("paste", this.paste.bind(this), {
      signal
    });
  }
  #removeCopyPasteListeners() {
    this.#copyPasteAC?.abort();
    this.#copyPasteAC = null;
  }
  #addDragAndDropListeners() {
    const signal = this._signal;
    document.addEventListener("dragover", this.dragOver.bind(this), {
      signal
    });
    document.addEventListener("drop", this.drop.bind(this), {
      signal
    });
  }
  addEditListeners() {
    this.#addKeyboardManager();
    this.#addCopyPasteListeners();
  }
  removeEditListeners() {
    this.#removeKeyboardManager();
    this.#removeCopyPasteListeners();
  }
  dragOver(event) {
    for (const {
      type
    } of event.dataTransfer.items) {
      for (const editorType of this.#editorTypes) {
        if (editorType.isHandlingMimeForPasting(type)) {
          event.dataTransfer.dropEffect = "copy";
          event.preventDefault();
          return;
        }
      }
    }
  }
  drop(event) {
    for (const item of event.dataTransfer.items) {
      for (const editorType of this.#editorTypes) {
        if (editorType.isHandlingMimeForPasting(item.type)) {
          editorType.paste(item, this.currentLayer);
          event.preventDefault();
          return;
        }
      }
    }
  }
  copy(event) {
    event.preventDefault();
    this.#activeEditor?.commitOrRemove();
    if (!this.hasSelection) {
      return;
    }
    const editors = [];
    for (const editor of this.#selectedEditors) {
      const serialized = editor.serialize(true);
      if (serialized) {
        editors.push(serialized);
      }
    }
    if (editors.length === 0) {
      return;
    }
    event.clipboardData.setData("application/pdfjs", JSON.stringify(editors));
  }
  cut(event) {
    this.copy(event);
    this.delete();
  }
  paste(event) {
    event.preventDefault();
    const {
      clipboardData
    } = event;
    for (const item of clipboardData.items) {
      for (const editorType of this.#editorTypes) {
        if (editorType.isHandlingMimeForPasting(item.type)) {
          editorType.paste(item, this.currentLayer);
          return;
        }
      }
    }
    let data = clipboardData.getData("application/pdfjs");
    if (!data) {
      return;
    }
    try {
      data = JSON.parse(data);
    } catch (ex) {
      warn(`paste: "${ex.message}".`);
      return;
    }
    if (!Array.isArray(data)) {
      return;
    }
    this.unselectAll();
    const layer = this.currentLayer;
    try {
      const newEditors = [];
      for (const editor of data) {
        const deserializedEditor = layer.deserialize(editor);
        if (!deserializedEditor) {
          return;
        }
        newEditors.push(deserializedEditor);
      }
      const cmd = () => {
        for (const editor of newEditors) {
          this.#addEditorToLayer(editor);
        }
        this.#selectEditors(newEditors);
      };
      const undo = () => {
        for (const editor of newEditors) {
          editor.remove();
        }
      };
      this.addCommands({
        cmd,
        undo,
        mustExec: true
      });
    } catch (ex) {
      warn(`paste: "${ex.message}".`);
    }
  }
  keydown(event) {
    if (!this.isShiftKeyDown && event.key === "Shift") {
      this.isShiftKeyDown = true;
    }
    if (this.#mode !== AnnotationEditorType.NONE && !this.isEditorHandlingKeyboard) {
      AnnotationEditorUIManager._keyboardManager.exec(this, event);
    }
  }
  keyup(event) {
    if (this.isShiftKeyDown && event.key === "Shift") {
      this.isShiftKeyDown = false;
      if (this.#highlightWhenShiftUp) {
        this.#highlightWhenShiftUp = false;
        this.#onSelectEnd("main_toolbar");
      }
    }
  }
  onEditingAction({
    name
  }) {
    switch (name) {
      case "undo":
      case "redo":
      case "delete":
      case "selectAll":
        this[name]();
        break;
      case "highlightSelection":
        this.highlightSelection("context_menu");
        break;
    }
  }
  #dispatchUpdateStates(details) {
    const hasChanged = Object.entries(details).some(([key, value]) => this.#previousStates[key] !== value);
    if (hasChanged) {
      this._eventBus.dispatch("annotationeditorstateschanged", {
        source: this,
        details: Object.assign(this.#previousStates, details)
      });
      if (this.#mode === AnnotationEditorType.HIGHLIGHT && details.hasSelectedEditor === false) {
        this.#dispatchUpdateUI([[AnnotationEditorParamsType.HIGHLIGHT_FREE, true]]);
      }
    }
  }
  #dispatchUpdateUI(details) {
    this._eventBus.dispatch("annotationeditorparamschanged", {
      source: this,
      details
    });
  }
  setEditingState(isEditing) {
    if (isEditing) {
      this.#addFocusManager();
      this.#addCopyPasteListeners();
      this.#dispatchUpdateStates({
        isEditing: this.#mode !== AnnotationEditorType.NONE,
        isEmpty: this.#isEmpty(),
        hasSomethingToUndo: this.#commandManager.hasSomethingToUndo(),
        hasSomethingToRedo: this.#commandManager.hasSomethingToRedo(),
        hasSelectedEditor: false
      });
    } else {
      this.#removeFocusManager();
      this.#removeCopyPasteListeners();
      this.#dispatchUpdateStates({
        isEditing: false
      });
      this.disableUserSelect(false);
    }
  }
  registerEditorTypes(types) {
    if (this.#editorTypes) {
      return;
    }
    this.#editorTypes = types;
    for (const editorType of this.#editorTypes) {
      this.#dispatchUpdateUI(editorType.defaultPropertiesToUpdate);
    }
  }
  getId() {
    return this.#idManager.id;
  }
  get currentLayer() {
    return this.#allLayers.get(this.#currentPageIndex);
  }
  getLayer(pageIndex) {
    return this.#allLayers.get(pageIndex);
  }
  get currentPageIndex() {
    return this.#currentPageIndex;
  }
  addLayer(layer) {
    this.#allLayers.set(layer.pageIndex, layer);
    if (this.#isEnabled) {
      layer.enable();
    } else {
      layer.disable();
    }
  }
  removeLayer(layer) {
    this.#allLayers.delete(layer.pageIndex);
  }
  updateMode(mode, editId = null, isFromKeyboard = false) {
    if (this.#mode === mode) {
      return;
    }
    this.#mode = mode;
    if (mode === AnnotationEditorType.NONE) {
      this.setEditingState(false);
      this.#disableAll();
      return;
    }
    this.setEditingState(true);
    this.#enableAll();
    this.unselectAll();
    for (const layer of this.#allLayers.values()) {
      layer.updateMode(mode);
    }
    if (!editId && isFromKeyboard) {
      this.addNewEditorFromKeyboard();
      return;
    }
    if (!editId) {
      return;
    }
    for (const editor of this.#allEditors.values()) {
      if (editor.annotationElementId === editId) {
        this.setSelected(editor);
        editor.enterInEditMode();
        break;
      }
    }
  }
  addNewEditorFromKeyboard() {
    if (this.currentLayer.canCreateNewEmptyEditor()) {
      this.currentLayer.addNewEditor();
    }
  }
  updateToolbar(mode) {
    if (mode === this.#mode) {
      return;
    }
    this._eventBus.dispatch("switchannotationeditormode", {
      source: this,
      mode
    });
  }
  updateParams(type, value) {
    if (!this.#editorTypes) {
      return;
    }
    switch (type) {
      case AnnotationEditorParamsType.CREATE:
        this.currentLayer.addNewEditor();
        return;
      case AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR:
        this.#mainHighlightColorPicker?.updateColor(value);
        break;
      case AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL:
        this._eventBus.dispatch("reporttelemetry", {
          source: this,
          details: {
            type: "editing",
            data: {
              type: "highlight",
              action: "toggle_visibility"
            }
          }
        });
        (this.#showAllStates ||= new Map()).set(type, value);
        this.showAllEditors("highlight", value);
        break;
    }
    for (const editor of this.#selectedEditors) {
      editor.updateParams(type, value);
    }
    for (const editorType of this.#editorTypes) {
      editorType.updateDefaultParams(type, value);
    }
  }
  showAllEditors(type, visible, updateButton = false) {
    for (const editor of this.#allEditors.values()) {
      if (editor.editorType === type) {
        editor.show(visible);
      }
    }
    const state = this.#showAllStates?.get(AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL) ?? true;
    if (state !== visible) {
      this.#dispatchUpdateUI([[AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL, visible]]);
    }
  }
  enableWaiting(mustWait = false) {
    if (this.#isWaiting === mustWait) {
      return;
    }
    this.#isWaiting = mustWait;
    for (const layer of this.#allLayers.values()) {
      if (mustWait) {
        layer.disableClick();
      } else {
        layer.enableClick();
      }
      layer.div.classList.toggle("waiting", mustWait);
    }
  }
  #enableAll() {
    if (!this.#isEnabled) {
      this.#isEnabled = true;
      for (const layer of this.#allLayers.values()) {
        layer.enable();
      }
      for (const editor of this.#allEditors.values()) {
        editor.enable();
      }
    }
  }
  #disableAll() {
    this.unselectAll();
    if (this.#isEnabled) {
      this.#isEnabled = false;
      for (const layer of this.#allLayers.values()) {
        layer.disable();
      }
      for (const editor of this.#allEditors.values()) {
        editor.disable();
      }
    }
  }
  getEditors(pageIndex) {
    const editors = [];
    for (const editor of this.#allEditors.values()) {
      if (editor.pageIndex === pageIndex) {
        editors.push(editor);
      }
    }
    return editors;
  }
  getEditor(id) {
    return this.#allEditors.get(id);
  }
  addEditor(editor) {
    this.#allEditors.set(editor.id, editor);
  }
  removeEditor(editor) {
    if (editor.div.contains(document.activeElement)) {
      if (this.#focusMainContainerTimeoutId) {
        clearTimeout(this.#focusMainContainerTimeoutId);
      }
      this.#focusMainContainerTimeoutId = setTimeout(() => {
        this.focusMainContainer();
        this.#focusMainContainerTimeoutId = null;
      }, 0);
    }
    this.#allEditors.delete(editor.id);
    this.unselect(editor);
    if (!editor.annotationElementId || !this.#deletedAnnotationsElementIds.has(editor.annotationElementId)) {
      this.#annotationStorage?.remove(editor.id);
    }
  }
  addDeletedAnnotationElement(editor) {
    this.#deletedAnnotationsElementIds.add(editor.annotationElementId);
    this.addChangedExistingAnnotation(editor);
    editor.deleted = true;
  }
  isDeletedAnnotationElement(annotationElementId) {
    return this.#deletedAnnotationsElementIds.has(annotationElementId);
  }
  removeDeletedAnnotationElement(editor) {
    this.#deletedAnnotationsElementIds.delete(editor.annotationElementId);
    this.removeChangedExistingAnnotation(editor);
    editor.deleted = false;
  }
  #addEditorToLayer(editor) {
    const layer = this.#allLayers.get(editor.pageIndex);
    if (layer) {
      layer.addOrRebuild(editor);
    } else {
      this.addEditor(editor);
      this.addToAnnotationStorage(editor);
    }
  }
  setActiveEditor(editor) {
    if (this.#activeEditor === editor) {
      return;
    }
    this.#activeEditor = editor;
    if (editor) {
      this.#dispatchUpdateUI(editor.propertiesToUpdate);
    }
  }
  get #lastSelectedEditor() {
    let ed = null;
    for (ed of this.#selectedEditors) {}
    return ed;
  }
  updateUI(editor) {
    if (this.#lastSelectedEditor === editor) {
      this.#dispatchUpdateUI(editor.propertiesToUpdate);
    }
  }
  toggleSelected(editor) {
    if (this.#selectedEditors.has(editor)) {
      this.#selectedEditors.delete(editor);
      editor.unselect();
      this.#dispatchUpdateStates({
        hasSelectedEditor: this.hasSelection
      });
      return;
    }
    this.#selectedEditors.add(editor);
    editor.select();
    this.#dispatchUpdateUI(editor.propertiesToUpdate);
    this.#dispatchUpdateStates({
      hasSelectedEditor: true
    });
  }
  setSelected(editor) {
    for (const ed of this.#selectedEditors) {
      if (ed !== editor) {
        ed.unselect();
      }
    }
    this.#selectedEditors.clear();
    this.#selectedEditors.add(editor);
    editor.select();
    this.#dispatchUpdateUI(editor.propertiesToUpdate);
    this.#dispatchUpdateStates({
      hasSelectedEditor: true
    });
  }
  isSelected(editor) {
    return this.#selectedEditors.has(editor);
  }
  get firstSelectedEditor() {
    return this.#selectedEditors.values().next().value;
  }
  unselect(editor) {
    editor.unselect();
    this.#selectedEditors.delete(editor);
    this.#dispatchUpdateStates({
      hasSelectedEditor: this.hasSelection
    });
  }
  get hasSelection() {
    return this.#selectedEditors.size !== 0;
  }
  get isEnterHandled() {
    return this.#selectedEditors.size === 1 && this.firstSelectedEditor.isEnterHandled;
  }
  undo() {
    this.#commandManager.undo();
    this.#dispatchUpdateStates({
      hasSomethingToUndo: this.#commandManager.hasSomethingToUndo(),
      hasSomethingToRedo: true,
      isEmpty: this.#isEmpty()
    });
  }
  redo() {
    this.#commandManager.redo();
    this.#dispatchUpdateStates({
      hasSomethingToUndo: true,
      hasSomethingToRedo: this.#commandManager.hasSomethingToRedo(),
      isEmpty: this.#isEmpty()
    });
  }
  addCommands(params) {
    this.#commandManager.add(params);
    this.#dispatchUpdateStates({
      hasSomethingToUndo: true,
      hasSomethingToRedo: false,
      isEmpty: this.#isEmpty()
    });
  }
  #isEmpty() {
    if (this.#allEditors.size === 0) {
      return true;
    }
    if (this.#allEditors.size === 1) {
      for (const editor of this.#allEditors.values()) {
        return editor.isEmpty();
      }
    }
    return false;
  }
  delete() {
    this.commitOrRemove();
    if (!this.hasSelection) {
      return;
    }
    const editors = [...this.#selectedEditors];
    const cmd = () => {
      for (const editor of editors) {
        editor.remove();
      }
    };
    const undo = () => {
      for (const editor of editors) {
        this.#addEditorToLayer(editor);
      }
    };
    this.addCommands({
      cmd,
      undo,
      mustExec: true
    });
  }
  commitOrRemove() {
    this.#activeEditor?.commitOrRemove();
  }
  hasSomethingToControl() {
    return this.#activeEditor || this.hasSelection;
  }
  #selectEditors(editors) {
    for (const editor of this.#selectedEditors) {
      editor.unselect();
    }
    this.#selectedEditors.clear();
    for (const editor of editors) {
      if (editor.isEmpty()) {
        continue;
      }
      this.#selectedEditors.add(editor);
      editor.select();
    }
    this.#dispatchUpdateStates({
      hasSelectedEditor: this.hasSelection
    });
  }
  selectAll() {
    for (const editor of this.#selectedEditors) {
      editor.commit();
    }
    this.#selectEditors(this.#allEditors.values());
  }
  unselectAll() {
    if (this.#activeEditor) {
      this.#activeEditor.commitOrRemove();
      if (this.#mode !== AnnotationEditorType.NONE) {
        return;
      }
    }
    if (!this.hasSelection) {
      return;
    }
    for (const editor of this.#selectedEditors) {
      editor.unselect();
    }
    this.#selectedEditors.clear();
    this.#dispatchUpdateStates({
      hasSelectedEditor: false
    });
  }
  translateSelectedEditors(x, y, noCommit = false) {
    if (!noCommit) {
      this.commitOrRemove();
    }
    if (!this.hasSelection) {
      return;
    }
    this.#translation[0] += x;
    this.#translation[1] += y;
    const [totalX, totalY] = this.#translation;
    const editors = [...this.#selectedEditors];
    const TIME_TO_WAIT = 1000;
    if (this.#translationTimeoutId) {
      clearTimeout(this.#translationTimeoutId);
    }
    this.#translationTimeoutId = setTimeout(() => {
      this.#translationTimeoutId = null;
      this.#translation[0] = this.#translation[1] = 0;
      this.addCommands({
        cmd: () => {
          for (const editor of editors) {
            if (this.#allEditors.has(editor.id)) {
              editor.translateInPage(totalX, totalY);
            }
          }
        },
        undo: () => {
          for (const editor of editors) {
            if (this.#allEditors.has(editor.id)) {
              editor.translateInPage(-totalX, -totalY);
            }
          }
        },
        mustExec: false
      });
    }, TIME_TO_WAIT);
    for (const editor of editors) {
      editor.translateInPage(x, y);
    }
  }
  setUpDragSession() {
    if (!this.hasSelection) {
      return;
    }
    this.disableUserSelect(true);
    this.#draggingEditors = new Map();
    for (const editor of this.#selectedEditors) {
      this.#draggingEditors.set(editor, {
        savedX: editor.x,
        savedY: editor.y,
        savedPageIndex: editor.pageIndex,
        newX: 0,
        newY: 0,
        newPageIndex: -1
      });
    }
  }
  endDragSession() {
    if (!this.#draggingEditors) {
      return false;
    }
    this.disableUserSelect(false);
    const map = this.#draggingEditors;
    this.#draggingEditors = null;
    let mustBeAddedInUndoStack = false;
    for (const [{
      x,
      y,
      pageIndex
    }, value] of map) {
      value.newX = x;
      value.newY = y;
      value.newPageIndex = pageIndex;
      mustBeAddedInUndoStack ||= x !== value.savedX || y !== value.savedY || pageIndex !== value.savedPageIndex;
    }
    if (!mustBeAddedInUndoStack) {
      return false;
    }
    const move = (editor, x, y, pageIndex) => {
      if (this.#allEditors.has(editor.id)) {
        const parent = this.#allLayers.get(pageIndex);
        if (parent) {
          editor._setParentAndPosition(parent, x, y);
        } else {
          editor.pageIndex = pageIndex;
          editor.x = x;
          editor.y = y;
        }
      }
    };
    this.addCommands({
      cmd: () => {
        for (const [editor, {
          newX,
          newY,
          newPageIndex
        }] of map) {
          move(editor, newX, newY, newPageIndex);
        }
      },
      undo: () => {
        for (const [editor, {
          savedX,
          savedY,
          savedPageIndex
        }] of map) {
          move(editor, savedX, savedY, savedPageIndex);
        }
      },
      mustExec: true
    });
    return true;
  }
  dragSelectedEditors(tx, ty) {
    if (!this.#draggingEditors) {
      return;
    }
    for (const editor of this.#draggingEditors.keys()) {
      editor.drag(tx, ty);
    }
  }
  rebuild(editor) {
    if (editor.parent === null) {
      const parent = this.getLayer(editor.pageIndex);
      if (parent) {
        parent.changeParent(editor);
        parent.addOrRebuild(editor);
      } else {
        this.addEditor(editor);
        this.addToAnnotationStorage(editor);
        editor.rebuild();
      }
    } else {
      editor.parent.addOrRebuild(editor);
    }
  }
  get isEditorHandlingKeyboard() {
    return this.getActive()?.shouldGetKeyboardEvents() || this.#selectedEditors.size === 1 && this.firstSelectedEditor.shouldGetKeyboardEvents();
  }
  isActive(editor) {
    return this.#activeEditor === editor;
  }
  getActive() {
    return this.#activeEditor;
  }
  getMode() {
    return this.#mode;
  }
  get imageManager() {
    return shadow(this, "imageManager", new ImageManager());
  }
  getSelectionBoxes(textLayer) {
    if (!textLayer) {
      return null;
    }
    const selection = document.getSelection();
    for (let i = 0, ii = selection.rangeCount; i < ii; i++) {
      if (!textLayer.contains(selection.getRangeAt(i).commonAncestorContainer)) {
        return null;
      }
    }
    const {
      x: layerX,
      y: layerY,
      width: parentWidth,
      height: parentHeight
    } = textLayer.getBoundingClientRect();
    let rotator;
    switch (textLayer.getAttribute("data-main-rotation")) {
      case "90":
        rotator = (x, y, w, h) => ({
          x: (y - layerY) / parentHeight,
          y: 1 - (x + w - layerX) / parentWidth,
          width: h / parentHeight,
          height: w / parentWidth
        });
        break;
      case "180":
        rotator = (x, y, w, h) => ({
          x: 1 - (x + w - layerX) / parentWidth,
          y: 1 - (y + h - layerY) / parentHeight,
          width: w / parentWidth,
          height: h / parentHeight
        });
        break;
      case "270":
        rotator = (x, y, w, h) => ({
          x: 1 - (y + h - layerY) / parentHeight,
          y: (x - layerX) / parentWidth,
          width: h / parentHeight,
          height: w / parentWidth
        });
        break;
      default:
        rotator = (x, y, w, h) => ({
          x: (x - layerX) / parentWidth,
          y: (y - layerY) / parentHeight,
          width: w / parentWidth,
          height: h / parentHeight
        });
        break;
    }
    const boxes = [];
    for (let i = 0, ii = selection.rangeCount; i < ii; i++) {
      const range = selection.getRangeAt(i);
      if (range.collapsed) {
        continue;
      }
      for (const {
        x,
        y,
        width,
        height
      } of range.getClientRects()) {
        if (width === 0 || height === 0) {
          continue;
        }
        boxes.push(rotator(x, y, width, height));
      }
    }
    return boxes.length === 0 ? null : boxes;
  }
  addChangedExistingAnnotation({
    annotationElementId,
    id
  }) {
    (this.#changedExistingAnnotations ||= new Map()).set(annotationElementId, id);
  }
  removeChangedExistingAnnotation({
    annotationElementId
  }) {
    this.#changedExistingAnnotations?.delete(annotationElementId);
  }
  renderAnnotationElement(annotation) {
    const editorId = this.#changedExistingAnnotations?.get(annotation.data.id);
    if (!editorId) {
      return;
    }
    const editor = this.#annotationStorage.getRawValue(editorId);
    if (!editor) {
      return;
    }
    if (this.#mode === AnnotationEditorType.NONE && !editor.hasBeenModified) {
      return;
    }
    editor.renderAnnotationElement(annotation);
  }
}

;// CONCATENATED MODULE: ./src/display/editor/alt_text.js

class AltText {
  #altText = null;
  #altTextDecorative = false;
  #altTextButton = null;
  #altTextTooltip = null;
  #altTextTooltipTimeout = null;
  #altTextWasFromKeyBoard = false;
  #badge = null;
  #editor = null;
  #guessedText = null;
  #textWithDisclaimer = null;
  #useNewAltTextFlow = false;
  static _l10nPromise = null;
  constructor(editor) {
    this.#editor = editor;
    this.#useNewAltTextFlow = editor._uiManager.useNewAltTextFlow;
  }
  static initialize(l10nPromise) {
    AltText._l10nPromise ||= l10nPromise;
  }
  async render() {
    const altText = this.#altTextButton = document.createElement("button");
    altText.className = "altText";
    let msg;
    if (this.#useNewAltTextFlow) {
      altText.classList.add("new");
      msg = await AltText._l10nPromise.get("pdfjs-editor-new-alt-text-missing-button-label");
    } else {
      msg = await AltText._l10nPromise.get("pdfjs-editor-alt-text-button-label");
    }
    altText.textContent = msg;
    altText.setAttribute("aria-label", msg);
    altText.tabIndex = "0";
    const signal = this.#editor._uiManager._signal;
    altText.addEventListener("contextmenu", noContextMenu, {
      signal
    });
    altText.addEventListener("pointerdown", event => event.stopPropagation(), {
      signal
    });
    const onClick = event => {
      event.preventDefault();
      this.#editor._uiManager.editAltText(this.#editor);
      if (this.#useNewAltTextFlow) {
        this.#editor._reportTelemetry({
          action: "pdfjs.image.alt_text.image_status_label_clicked",
          data: {
            label: this.#label
          }
        });
      }
    };
    altText.addEventListener("click", onClick, {
      capture: true,
      signal
    });
    altText.addEventListener("keydown", event => {
      if (event.target === altText && event.key === "Enter") {
        this.#altTextWasFromKeyBoard = true;
        onClick(event);
      }
    }, {
      signal
    });
    await this.#setState();
    return altText;
  }
  get #label() {
    return this.#altText && "added" || this.#altText === null && this.guessedText && "review" || "missing";
  }
  finish() {
    if (!this.#altTextButton) {
      return;
    }
    this.#altTextButton.focus({
      focusVisible: this.#altTextWasFromKeyBoard
    });
    this.#altTextWasFromKeyBoard = false;
  }
  isEmpty() {
    if (this.#useNewAltTextFlow) {
      return this.#altText === null;
    }
    return !this.#altText && !this.#altTextDecorative;
  }
  hasData() {
    if (this.#useNewAltTextFlow) {
      return this.#altText !== null || !!this.#guessedText;
    }
    return this.isEmpty();
  }
  get guessedText() {
    return this.#guessedText;
  }
  async setGuessedText(guessedText) {
    if (this.#altText !== null) {
      return;
    }
    this.#guessedText = guessedText;
    this.#textWithDisclaimer = await AltText._l10nPromise.get("pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer")({
      generatedAltText: guessedText
    });
    this.#setState();
  }
  toggleAltTextBadge(visibility = false) {
    if (!this.#useNewAltTextFlow || this.#altText) {
      this.#badge?.remove();
      this.#badge = null;
      return;
    }
    if (!this.#badge) {
      const badge = this.#badge = document.createElement("div");
      badge.className = "noAltTextBadge";
      this.#editor.div.append(badge);
    }
    this.#badge.classList.toggle("hidden", !visibility);
  }
  serialize(isForCopying) {
    let altText = this.#altText;
    if (!isForCopying && this.#guessedText === altText) {
      altText = this.#textWithDisclaimer;
    }
    return {
      altText,
      decorative: this.#altTextDecorative,
      guessedText: this.#guessedText,
      textWithDisclaimer: this.#textWithDisclaimer
    };
  }
  get data() {
    return {
      altText: this.#altText,
      decorative: this.#altTextDecorative
    };
  }
  set data({
    altText,
    decorative,
    guessedText,
    textWithDisclaimer,
    cancel = false
  }) {
    if (guessedText) {
      this.#guessedText = guessedText;
      this.#textWithDisclaimer = textWithDisclaimer;
    }
    if (this.#altText === altText && this.#altTextDecorative === decorative) {
      return;
    }
    if (!cancel) {
      this.#altText = altText;
      this.#altTextDecorative = decorative;
    }
    this.#setState();
  }
  toggle(enabled = false) {
    if (!this.#altTextButton) {
      return;
    }
    if (!enabled && this.#altTextTooltipTimeout) {
      clearTimeout(this.#altTextTooltipTimeout);
      this.#altTextTooltipTimeout = null;
    }
    this.#altTextButton.disabled = !enabled;
  }
  shown() {
    this.#editor._reportTelemetry({
      action: "pdfjs.image.alt_text.image_status_label_displayed",
      data: {
        label: this.#label
      }
    });
  }
  destroy() {
    this.#altTextButton?.remove();
    this.#altTextButton = null;
    this.#altTextTooltip = null;
    this.#badge?.remove();
    this.#badge = null;
  }
  async #setState() {
    const button = this.#altTextButton;
    if (!button) {
      return;
    }
    if (this.#useNewAltTextFlow) {
      const label = this.#label;
      const type = label === "review" ? "to-review" : label;
      button.classList.toggle("done", !!this.#altText);
      AltText._l10nPromise.get(`pdfjs-editor-new-alt-text-${type}-button-label`).then(msg => {
        button.setAttribute("aria-label", msg);
        for (const child of button.childNodes) {
          if (child.nodeType === Node.TEXT_NODE) {
            child.textContent = msg;
            break;
          }
        }
      });
      if (!this.#altText) {
        this.#altTextTooltip?.remove();
        return;
      }
    } else {
      if (!this.#altText && !this.#altTextDecorative) {
        button.classList.remove("done");
        this.#altTextTooltip?.remove();
        return;
      }
      button.classList.add("done");
      AltText._l10nPromise.get("pdfjs-editor-alt-text-edit-button-label").then(msg => {
        button.setAttribute("aria-label", msg);
      });
    }
    let tooltip = this.#altTextTooltip;
    if (!tooltip) {
      this.#altTextTooltip = tooltip = document.createElement("span");
      tooltip.className = "tooltip";
      tooltip.setAttribute("role", "tooltip");
      const id = tooltip.id = `alt-text-tooltip-${this.#editor.id}`;
      button.setAttribute("aria-describedby", id);
      const DELAY_TO_SHOW_TOOLTIP = 100;
      const signal = this.#editor._uiManager._signal;
      signal.addEventListener("abort", () => {
        clearTimeout(this.#altTextTooltipTimeout);
        this.#altTextTooltipTimeout = null;
      }, {
        once: true
      });
      button.addEventListener("mouseenter", () => {
        this.#altTextTooltipTimeout = setTimeout(() => {
          this.#altTextTooltipTimeout = null;
          this.#altTextTooltip.classList.add("show");
          this.#editor._reportTelemetry({
            action: "alt_text_tooltip"
          });
        }, DELAY_TO_SHOW_TOOLTIP);
      }, {
        signal
      });
      button.addEventListener("mouseleave", () => {
        if (this.#altTextTooltipTimeout) {
          clearTimeout(this.#altTextTooltipTimeout);
          this.#altTextTooltipTimeout = null;
        }
        this.#altTextTooltip?.classList.remove("show");
      }, {
        signal
      });
    }
    tooltip.innerText = this.#altTextDecorative ? await AltText._l10nPromise.get("pdfjs-editor-alt-text-decorative-tooltip") : this.#altText;
    if (!tooltip.parentNode) {
      button.append(tooltip);
    }
    const element = this.#editor.getImageForAltText();
    element?.setAttribute("aria-describedby", tooltip.id);
  }
}

;// CONCATENATED MODULE: ./src/display/editor/editor.js





class AnnotationEditor {
  #accessibilityData = null;
  #allResizerDivs = null;
  #altText = null;
  #disabled = false;
  #keepAspectRatio = false;
  #resizersDiv = null;
  #savedDimensions = null;
  #focusAC = null;
  #focusedResizerName = "";
  #hasBeenClicked = false;
  #initialPosition = null;
  #isEditing = false;
  #isInEditMode = false;
  #isResizerEnabledForKeyboard = false;
  #moveInDOMTimeout = null;
  #prevDragX = 0;
  #prevDragY = 0;
  #telemetryTimeouts = null;
  _editToolbar = null;
  _initialOptions = Object.create(null);
  _isVisible = true;
  _uiManager = null;
  _focusEventsAllowed = true;
  static _l10nPromise = null;
  static _l10nResizer = null;
  #isDraggable = false;
  #zIndex = AnnotationEditor._zIndex++;
  static _borderLineWidth = -1;
  static _colorManager = new ColorManager();
  static _zIndex = 1;
  static _telemetryTimeout = 1000;
  static get _resizerKeyboardManager() {
    const resize = AnnotationEditor.prototype._resizeWithKeyboard;
    const small = AnnotationEditorUIManager.TRANSLATE_SMALL;
    const big = AnnotationEditorUIManager.TRANSLATE_BIG;
    return shadow(this, "_resizerKeyboardManager", new KeyboardManager([[["ArrowLeft", "mac+ArrowLeft"], resize, {
      args: [-small, 0]
    }], [["ctrl+ArrowLeft", "mac+shift+ArrowLeft"], resize, {
      args: [-big, 0]
    }], [["ArrowRight", "mac+ArrowRight"], resize, {
      args: [small, 0]
    }], [["ctrl+ArrowRight", "mac+shift+ArrowRight"], resize, {
      args: [big, 0]
    }], [["ArrowUp", "mac+ArrowUp"], resize, {
      args: [0, -small]
    }], [["ctrl+ArrowUp", "mac+shift+ArrowUp"], resize, {
      args: [0, -big]
    }], [["ArrowDown", "mac+ArrowDown"], resize, {
      args: [0, small]
    }], [["ctrl+ArrowDown", "mac+shift+ArrowDown"], resize, {
      args: [0, big]
    }], [["Escape", "mac+Escape"], AnnotationEditor.prototype._stopResizingWithKeyboard]]));
  }
  constructor(parameters) {
    this.parent = parameters.parent;
    this.id = parameters.id;
    this.width = this.height = null;
    this.pageIndex = parameters.parent.pageIndex;
    this.name = parameters.name;
    this.div = null;
    this._uiManager = parameters.uiManager;
    this.annotationElementId = null;
    this._willKeepAspectRatio = false;
    this._initialOptions.isCentered = parameters.isCentered;
    this._structTreeParentId = null;
    const {
      rotation,
      rawDims: {
        pageWidth,
        pageHeight,
        pageX,
        pageY
      }
    } = this.parent.viewport;
    this.rotation = rotation;
    this.pageRotation = (360 + rotation - this._uiManager.viewParameters.rotation) % 360;
    this.pageDimensions = [pageWidth, pageHeight];
    this.pageTranslation = [pageX, pageY];
    const [width, height] = this.parentDimensions;
    this.x = parameters.x / width;
    this.y = parameters.y / height;
    this.isAttachedToDOM = false;
    this.deleted = false;
  }
  get editorType() {
    return Object.getPrototypeOf(this).constructor._type;
  }
  static get _defaultLineColor() {
    return shadow(this, "_defaultLineColor", this._colorManager.getHexCode("CanvasText"));
  }
  static deleteAnnotationElement(editor) {
    const fakeEditor = new FakeEditor({
      id: editor.parent.getNextId(),
      parent: editor.parent,
      uiManager: editor._uiManager
    });
    fakeEditor.annotationElementId = editor.annotationElementId;
    fakeEditor.deleted = true;
    fakeEditor._uiManager.addToAnnotationStorage(fakeEditor);
  }
  static initialize(l10n, _uiManager, options) {
    AnnotationEditor._l10nResizer ||= Object.freeze({
      topLeft: "pdfjs-editor-resizer-top-left",
      topMiddle: "pdfjs-editor-resizer-top-middle",
      topRight: "pdfjs-editor-resizer-top-right",
      middleRight: "pdfjs-editor-resizer-middle-right",
      bottomRight: "pdfjs-editor-resizer-bottom-right",
      bottomMiddle: "pdfjs-editor-resizer-bottom-middle",
      bottomLeft: "pdfjs-editor-resizer-bottom-left",
      middleLeft: "pdfjs-editor-resizer-middle-left"
    });
    AnnotationEditor._l10nPromise ||= new Map([...["pdfjs-editor-alt-text-button-label", "pdfjs-editor-alt-text-edit-button-label", "pdfjs-editor-alt-text-decorative-tooltip", "pdfjs-editor-new-alt-text-added-button-label", "pdfjs-editor-new-alt-text-missing-button-label", "pdfjs-editor-new-alt-text-to-review-button-label"].map(str => [str, l10n.get(str)]), ...["pdfjs-editor-new-alt-text-generated-alt-text-with-disclaimer"].map(str => [str, l10n.get.bind(l10n, str)])]);
    if (options?.strings) {
      for (const str of options.strings) {
        AnnotationEditor._l10nPromise.set(str, l10n.get(str));
      }
    }
    if (AnnotationEditor._borderLineWidth !== -1) {
      return;
    }
    const style = getComputedStyle(document.documentElement);
    AnnotationEditor._borderLineWidth = parseFloat(style.getPropertyValue("--outline-width")) || 0;
  }
  static updateDefaultParams(_type, _value) {}
  static get defaultPropertiesToUpdate() {
    return [];
  }
  static isHandlingMimeForPasting(mime) {
    return false;
  }
  static paste(item, parent) {
    unreachable("Not implemented");
  }
  get propertiesToUpdate() {
    return [];
  }
  get _isDraggable() {
    return this.#isDraggable;
  }
  set _isDraggable(value) {
    this.#isDraggable = value;
    this.div?.classList.toggle("draggable", value);
  }
  get isEnterHandled() {
    return true;
  }
  center() {
    const [pageWidth, pageHeight] = this.pageDimensions;
    switch (this.parentRotation) {
      case 90:
        this.x -= this.height * pageHeight / (pageWidth * 2);
        this.y += this.width * pageWidth / (pageHeight * 2);
        break;
      case 180:
        this.x += this.width / 2;
        this.y += this.height / 2;
        break;
      case 270:
        this.x += this.height * pageHeight / (pageWidth * 2);
        this.y -= this.width * pageWidth / (pageHeight * 2);
        break;
      default:
        this.x -= this.width / 2;
        this.y -= this.height / 2;
        break;
    }
    this.fixAndSetPosition();
  }
  addCommands(params) {
    this._uiManager.addCommands(params);
  }
  get currentLayer() {
    return this._uiManager.currentLayer;
  }
  setInBackground() {
    this.div.style.zIndex = 0;
  }
  setInForeground() {
    this.div.style.zIndex = this.#zIndex;
  }
  setParent(parent) {
    if (parent !== null) {
      this.pageIndex = parent.pageIndex;
      this.pageDimensions = parent.pageDimensions;
    } else {
      this.#stopResizing();
    }
    this.parent = parent;
  }
  focusin(event) {
    if (!this._focusEventsAllowed) {
      return;
    }
    if (!this.#hasBeenClicked) {
      this.parent.setSelected(this);
    } else {
      this.#hasBeenClicked = false;
    }
  }
  focusout(event) {
    if (!this._focusEventsAllowed) {
      return;
    }
    if (!this.isAttachedToDOM) {
      return;
    }
    const target = event.relatedTarget;
    if (target?.closest(`#${this.id}`)) {
      return;
    }
    event.preventDefault();
    if (!this.parent?.isMultipleSelection) {
      this.commitOrRemove();
    }
  }
  commitOrRemove() {
    if (this.isEmpty()) {
      this.remove();
    } else {
      this.commit();
    }
  }
  commit() {
    this.addToAnnotationStorage();
  }
  addToAnnotationStorage() {
    this._uiManager.addToAnnotationStorage(this);
  }
  setAt(x, y, tx, ty) {
    const [width, height] = this.parentDimensions;
    [tx, ty] = this.screenToPageTranslation(tx, ty);
    this.x = (x + tx) / width;
    this.y = (y + ty) / height;
    this.fixAndSetPosition();
  }
  #translate([width, height], x, y) {
    [x, y] = this.screenToPageTranslation(x, y);
    this.x += x / width;
    this.y += y / height;
    this.fixAndSetPosition();
  }
  translate(x, y) {
    this.#translate(this.parentDimensions, x, y);
  }
  translateInPage(x, y) {
    this.#initialPosition ||= [this.x, this.y];
    this.#translate(this.pageDimensions, x, y);
    this.div.scrollIntoView({
      block: "nearest"
    });
  }
  drag(tx, ty) {
    this.#initialPosition ||= [this.x, this.y];
    const [parentWidth, parentHeight] = this.parentDimensions;
    this.x += tx / parentWidth;
    this.y += ty / parentHeight;
    if (this.parent && (this.x < 0 || this.x > 1 || this.y < 0 || this.y > 1)) {
      const {
        x,
        y
      } = this.div.getBoundingClientRect();
      if (this.parent.findNewParent(this, x, y)) {
        this.x -= Math.floor(this.x);
        this.y -= Math.floor(this.y);
      }
    }
    let {
      x,
      y
    } = this;
    const [bx, by] = this.getBaseTranslation();
    x += bx;
    y += by;
    this.div.style.left = `${(100 * x).toFixed(2)}%`;
    this.div.style.top = `${(100 * y).toFixed(2)}%`;
    this.div.scrollIntoView({
      block: "nearest"
    });
  }
  get _hasBeenMoved() {
    return !!this.#initialPosition && (this.#initialPosition[0] !== this.x || this.#initialPosition[1] !== this.y);
  }
  getBaseTranslation() {
    const [parentWidth, parentHeight] = this.parentDimensions;
    const {
      _borderLineWidth
    } = AnnotationEditor;
    const x = _borderLineWidth / parentWidth;
    const y = _borderLineWidth / parentHeight;
    switch (this.rotation) {
      case 90:
        return [-x, y];
      case 180:
        return [x, y];
      case 270:
        return [x, -y];
      default:
        return [-x, -y];
    }
  }
  get _mustFixPosition() {
    return true;
  }
  fixAndSetPosition(rotation = this.rotation) {
    const [pageWidth, pageHeight] = this.pageDimensions;
    let {
      x,
      y,
      width,
      height
    } = this;
    width *= pageWidth;
    height *= pageHeight;
    x *= pageWidth;
    y *= pageHeight;
    if (this._mustFixPosition) {
      switch (rotation) {
        case 0:
          x = Math.max(0, Math.min(pageWidth - width, x));
          y = Math.max(0, Math.min(pageHeight - height, y));
          break;
        case 90:
          x = Math.max(0, Math.min(pageWidth - height, x));
          y = Math.min(pageHeight, Math.max(width, y));
          break;
        case 180:
          x = Math.min(pageWidth, Math.max(width, x));
          y = Math.min(pageHeight, Math.max(height, y));
          break;
        case 270:
          x = Math.min(pageWidth, Math.max(height, x));
          y = Math.max(0, Math.min(pageHeight - width, y));
          break;
      }
    }
    this.x = x /= pageWidth;
    this.y = y /= pageHeight;
    const [bx, by] = this.getBaseTranslation();
    x += bx;
    y += by;
    const {
      style
    } = this.div;
    style.left = `${(100 * x).toFixed(2)}%`;
    style.top = `${(100 * y).toFixed(2)}%`;
    this.moveInDOM();
  }
  static #rotatePoint(x, y, angle) {
    switch (angle) {
      case 90:
        return [y, -x];
      case 180:
        return [-x, -y];
      case 270:
        return [-y, x];
      default:
        return [x, y];
    }
  }
  screenToPageTranslation(x, y) {
    return AnnotationEditor.#rotatePoint(x, y, this.parentRotation);
  }
  pageTranslationToScreen(x, y) {
    return AnnotationEditor.#rotatePoint(x, y, 360 - this.parentRotation);
  }
  #getRotationMatrix(rotation) {
    switch (rotation) {
      case 90:
        {
          const [pageWidth, pageHeight] = this.pageDimensions;
          return [0, -pageWidth / pageHeight, pageHeight / pageWidth, 0];
        }
      case 180:
        return [-1, 0, 0, -1];
      case 270:
        {
          const [pageWidth, pageHeight] = this.pageDimensions;
          return [0, pageWidth / pageHeight, -pageHeight / pageWidth, 0];
        }
      default:
        return [1, 0, 0, 1];
    }
  }
  get parentScale() {
    return this._uiManager.viewParameters.realScale;
  }
  get parentRotation() {
    return (this._uiManager.viewParameters.rotation + this.pageRotation) % 360;
  }
  get parentDimensions() {
    const {
      parentScale,
      pageDimensions: [pageWidth, pageHeight]
    } = this;
    const scaledWidth = pageWidth * parentScale;
    const scaledHeight = pageHeight * parentScale;
    return util_FeatureTest.isCSSRoundSupported ? [Math.round(scaledWidth), Math.round(scaledHeight)] : [scaledWidth, scaledHeight];
  }
  setDims(width, height) {
    const [parentWidth, parentHeight] = this.parentDimensions;
    this.div.style.width = `${(100 * width / parentWidth).toFixed(2)}%`;
    if (!this.#keepAspectRatio) {
      this.div.style.height = `${(100 * height / parentHeight).toFixed(2)}%`;
    }
  }
  fixDims() {
    const {
      style
    } = this.div;
    const {
      height,
      width
    } = style;
    const widthPercent = width.endsWith("%");
    const heightPercent = !this.#keepAspectRatio && height.endsWith("%");
    if (widthPercent && heightPercent) {
      return;
    }
    const [parentWidth, parentHeight] = this.parentDimensions;
    if (!widthPercent) {
      style.width = `${(100 * parseFloat(width) / parentWidth).toFixed(2)}%`;
    }
    if (!this.#keepAspectRatio && !heightPercent) {
      style.height = `${(100 * parseFloat(height) / parentHeight).toFixed(2)}%`;
    }
  }
  getInitialTranslation() {
    return [0, 0];
  }
  #createResizers() {
    if (this.#resizersDiv) {
      return;
    }
    this.#resizersDiv = document.createElement("div");
    this.#resizersDiv.classList.add("resizers");
    const classes = this._willKeepAspectRatio ? ["topLeft", "topRight", "bottomRight", "bottomLeft"] : ["topLeft", "topMiddle", "topRight", "middleRight", "bottomRight", "bottomMiddle", "bottomLeft", "middleLeft"];
    const signal = this._uiManager._signal;
    for (const name of classes) {
      const div = document.createElement("div");
      this.#resizersDiv.append(div);
      div.classList.add("resizer", name);
      div.setAttribute("data-resizer-name", name);
      div.addEventListener("pointerdown", this.#resizerPointerdown.bind(this, name), {
        signal
      });
      div.addEventListener("contextmenu", noContextMenu, {
        signal
      });
      div.tabIndex = -1;
    }
    this.div.prepend(this.#resizersDiv);
  }
  #resizerPointerdown(name, event) {
    event.preventDefault();
    const {
      isMac
    } = util_FeatureTest.platform;
    if (event.button !== 0 || event.ctrlKey && isMac) {
      return;
    }
    this.#altText?.toggle(false);
    const savedDraggable = this._isDraggable;
    this._isDraggable = false;
    const ac = new AbortController();
    const signal = this._uiManager.combinedSignal(ac);
    this.parent.togglePointerEvents(false);
    window.addEventListener("pointermove", this.#resizerPointermove.bind(this, name), {
      passive: true,
      capture: true,
      signal
    });
    window.addEventListener("contextmenu", noContextMenu, {
      signal
    });
    const savedX = this.x;
    const savedY = this.y;
    const savedWidth = this.width;
    const savedHeight = this.height;
    const savedParentCursor = this.parent.div.style.cursor;
    const savedCursor = this.div.style.cursor;
    this.div.style.cursor = this.parent.div.style.cursor = window.getComputedStyle(event.target).cursor;
    const pointerUpCallback = () => {
      ac.abort();
      this.parent.togglePointerEvents(true);
      this.#altText?.toggle(true);
      this._isDraggable = savedDraggable;
      this.parent.div.style.cursor = savedParentCursor;
      this.div.style.cursor = savedCursor;
      this.#addResizeToUndoStack(savedX, savedY, savedWidth, savedHeight);
    };
    window.addEventListener("pointerup", pointerUpCallback, {
      signal
    });
    window.addEventListener("blur", pointerUpCallback, {
      signal
    });
  }
  #addResizeToUndoStack(savedX, savedY, savedWidth, savedHeight) {
    const newX = this.x;
    const newY = this.y;
    const newWidth = this.width;
    const newHeight = this.height;
    if (newX === savedX && newY === savedY && newWidth === savedWidth && newHeight === savedHeight) {
      return;
    }
    this.addCommands({
      cmd: () => {
        this.width = newWidth;
        this.height = newHeight;
        this.x = newX;
        this.y = newY;
        const [parentWidth, parentHeight] = this.parentDimensions;
        this.setDims(parentWidth * newWidth, parentHeight * newHeight);
        this.fixAndSetPosition();
      },
      undo: () => {
        this.width = savedWidth;
        this.height = savedHeight;
        this.x = savedX;
        this.y = savedY;
        const [parentWidth, parentHeight] = this.parentDimensions;
        this.setDims(parentWidth * savedWidth, parentHeight * savedHeight);
        this.fixAndSetPosition();
      },
      mustExec: true
    });
  }
  #resizerPointermove(name, event) {
    const [parentWidth, parentHeight] = this.parentDimensions;
    const savedX = this.x;
    const savedY = this.y;
    const savedWidth = this.width;
    const savedHeight = this.height;
    const minWidth = AnnotationEditor.MIN_SIZE / parentWidth;
    const minHeight = AnnotationEditor.MIN_SIZE / parentHeight;
    const round = x => Math.round(x * 10000) / 10000;
    const rotationMatrix = this.#getRotationMatrix(this.rotation);
    const transf = (x, y) => [rotationMatrix[0] * x + rotationMatrix[2] * y, rotationMatrix[1] * x + rotationMatrix[3] * y];
    const invRotationMatrix = this.#getRotationMatrix(360 - this.rotation);
    const invTransf = (x, y) => [invRotationMatrix[0] * x + invRotationMatrix[2] * y, invRotationMatrix[1] * x + invRotationMatrix[3] * y];
    let getPoint;
    let getOpposite;
    let isDiagonal = false;
    let isHorizontal = false;
    switch (name) {
      case "topLeft":
        isDiagonal = true;
        getPoint = (w, h) => [0, 0];
        getOpposite = (w, h) => [w, h];
        break;
      case "topMiddle":
        getPoint = (w, h) => [w / 2, 0];
        getOpposite = (w, h) => [w / 2, h];
        break;
      case "topRight":
        isDiagonal = true;
        getPoint = (w, h) => [w, 0];
        getOpposite = (w, h) => [0, h];
        break;
      case "middleRight":
        isHorizontal = true;
        getPoint = (w, h) => [w, h / 2];
        getOpposite = (w, h) => [0, h / 2];
        break;
      case "bottomRight":
        isDiagonal = true;
        getPoint = (w, h) => [w, h];
        getOpposite = (w, h) => [0, 0];
        break;
      case "bottomMiddle":
        getPoint = (w, h) => [w / 2, h];
        getOpposite = (w, h) => [w / 2, 0];
        break;
      case "bottomLeft":
        isDiagonal = true;
        getPoint = (w, h) => [0, h];
        getOpposite = (w, h) => [w, 0];
        break;
      case "middleLeft":
        isHorizontal = true;
        getPoint = (w, h) => [0, h / 2];
        getOpposite = (w, h) => [w, h / 2];
        break;
    }
    const point = getPoint(savedWidth, savedHeight);
    const oppositePoint = getOpposite(savedWidth, savedHeight);
    let transfOppositePoint = transf(...oppositePoint);
    const oppositeX = round(savedX + transfOppositePoint[0]);
    const oppositeY = round(savedY + transfOppositePoint[1]);
    let ratioX = 1;
    let ratioY = 1;
    let [deltaX, deltaY] = this.screenToPageTranslation(event.movementX, event.movementY);
    [deltaX, deltaY] = invTransf(deltaX / parentWidth, deltaY / parentHeight);
    if (isDiagonal) {
      const oldDiag = Math.hypot(savedWidth, savedHeight);
      ratioX = ratioY = Math.max(Math.min(Math.hypot(oppositePoint[0] - point[0] - deltaX, oppositePoint[1] - point[1] - deltaY) / oldDiag, 1 / savedWidth, 1 / savedHeight), minWidth / savedWidth, minHeight / savedHeight);
    } else if (isHorizontal) {
      ratioX = Math.max(minWidth, Math.min(1, Math.abs(oppositePoint[0] - point[0] - deltaX))) / savedWidth;
    } else {
      ratioY = Math.max(minHeight, Math.min(1, Math.abs(oppositePoint[1] - point[1] - deltaY))) / savedHeight;
    }
    const newWidth = round(savedWidth * ratioX);
    const newHeight = round(savedHeight * ratioY);
    transfOppositePoint = transf(...getOpposite(newWidth, newHeight));
    const newX = oppositeX - transfOppositePoint[0];
    const newY = oppositeY - transfOppositePoint[1];
    this.width = newWidth;
    this.height = newHeight;
    this.x = newX;
    this.y = newY;
    this.setDims(parentWidth * newWidth, parentHeight * newHeight);
    this.fixAndSetPosition();
  }
  altTextFinish() {
    this.#altText?.finish();
  }
  async addEditToolbar() {
    if (this._editToolbar || this.#isInEditMode) {
      return this._editToolbar;
    }
    this._editToolbar = new EditorToolbar(this);
    this.div.append(this._editToolbar.render());
    if (this.#altText) {
      await this._editToolbar.addAltText(this.#altText);
    }
    return this._editToolbar;
  }
  removeEditToolbar() {
    if (!this._editToolbar) {
      return;
    }
    this._editToolbar.remove();
    this._editToolbar = null;
    this.#altText?.destroy();
  }
  getClientDimensions() {
    return this.div.getBoundingClientRect();
  }
  async addAltTextButton() {
    if (this.#altText) {
      return;
    }
    AltText.initialize(AnnotationEditor._l10nPromise);
    this.#altText = new AltText(this);
    if (this.#accessibilityData) {
      this.#altText.data = this.#accessibilityData;
      this.#accessibilityData = null;
    }
    await this.addEditToolbar();
  }
  get altTextData() {
    return this.#altText?.data;
  }
  set altTextData(data) {
    if (!this.#altText) {
      return;
    }
    this.#altText.data = data;
  }
  get guessedAltText() {
    return this.#altText?.guessedText;
  }
  async setGuessedAltText(text) {
    await this.#altText?.setGuessedText(text);
  }
  serializeAltText(isForCopying) {
    return this.#altText?.serialize(isForCopying);
  }
  hasAltText() {
    return !!this.#altText && !this.#altText.isEmpty();
  }
  hasAltTextData() {
    return this.#altText?.hasData() ?? false;
  }
  render() {
    this.div = document.createElement("div");
    this.div.setAttribute("data-editor-rotation", (360 - this.rotation) % 360);
    this.div.className = this.name;
    this.div.setAttribute("id", this.id);
    this.div.tabIndex = this.#disabled ? -1 : 0;
    if (!this._isVisible) {
      this.div.classList.add("hidden");
    }
    this.setInForeground();
    this.#addFocusListeners();
    const [parentWidth, parentHeight] = this.parentDimensions;
    if (this.parentRotation % 180 !== 0) {
      this.div.style.maxWidth = `${(100 * parentHeight / parentWidth).toFixed(2)}%`;
      this.div.style.maxHeight = `${(100 * parentWidth / parentHeight).toFixed(2)}%`;
    }
    const [tx, ty] = this.getInitialTranslation();
    this.translate(tx, ty);
    bindEvents(this, this.div, ["pointerdown"]);
    return this.div;
  }
  pointerdown(event) {
    const {
      isMac
    } = util_FeatureTest.platform;
    if (event.button !== 0 || event.ctrlKey && isMac) {
      event.preventDefault();
      return;
    }
    this.#hasBeenClicked = true;
    if (this._isDraggable) {
      this.#setUpDragSession(event);
      return;
    }
    this.#selectOnPointerEvent(event);
  }
  #selectOnPointerEvent(event) {
    const {
      isMac
    } = util_FeatureTest.platform;
    if (event.ctrlKey && !isMac || event.shiftKey || event.metaKey && isMac) {
      this.parent.toggleSelected(this);
    } else {
      this.parent.setSelected(this);
    }
  }
  #setUpDragSession(event) {
    const isSelected = this._uiManager.isSelected(this);
    this._uiManager.setUpDragSession();
    const ac = new AbortController();
    const signal = this._uiManager.combinedSignal(ac);
    if (isSelected) {
      this.div.classList.add("moving");
      this.#prevDragX = event.clientX;
      this.#prevDragY = event.clientY;
      const pointerMoveCallback = e => {
        const {
          clientX: x,
          clientY: y
        } = e;
        const [tx, ty] = this.screenToPageTranslation(x - this.#prevDragX, y - this.#prevDragY);
        this.#prevDragX = x;
        this.#prevDragY = y;
        this._uiManager.dragSelectedEditors(tx, ty);
      };
      window.addEventListener("pointermove", pointerMoveCallback, {
        passive: true,
        capture: true,
        signal
      });
    }
    const pointerUpCallback = () => {
      ac.abort();
      if (isSelected) {
        this.div.classList.remove("moving");
      }
      this.#hasBeenClicked = false;
      if (!this._uiManager.endDragSession()) {
        this.#selectOnPointerEvent(event);
      }
    };
    window.addEventListener("pointerup", pointerUpCallback, {
      signal
    });
    window.addEventListener("blur", pointerUpCallback, {
      signal
    });
  }
  moveInDOM() {
    if (this.#moveInDOMTimeout) {
      clearTimeout(this.#moveInDOMTimeout);
    }
    this.#moveInDOMTimeout = setTimeout(() => {
      this.#moveInDOMTimeout = null;
      this.parent?.moveEditorInDOM(this);
    }, 0);
  }
  _setParentAndPosition(parent, x, y) {
    parent.changeParent(this);
    this.x = x;
    this.y = y;
    this.fixAndSetPosition();
  }
  getRect(tx, ty, rotation = this.rotation) {
    const scale = this.parentScale;
    const [pageWidth, pageHeight] = this.pageDimensions;
    const [pageX, pageY] = this.pageTranslation;
    const shiftX = tx / scale;
    const shiftY = ty / scale;
    const x = this.x * pageWidth;
    const y = this.y * pageHeight;
    const width = this.width * pageWidth;
    const height = this.height * pageHeight;
    switch (rotation) {
      case 0:
        return [x + shiftX + pageX, pageHeight - y - shiftY - height + pageY, x + shiftX + width + pageX, pageHeight - y - shiftY + pageY];
      case 90:
        return [x + shiftY + pageX, pageHeight - y + shiftX + pageY, x + shiftY + height + pageX, pageHeight - y + shiftX + width + pageY];
      case 180:
        return [x - shiftX - width + pageX, pageHeight - y + shiftY + pageY, x - shiftX + pageX, pageHeight - y + shiftY + height + pageY];
      case 270:
        return [x - shiftY - height + pageX, pageHeight - y - shiftX - width + pageY, x - shiftY + pageX, pageHeight - y - shiftX + pageY];
      default:
        throw new Error("Invalid rotation");
    }
  }
  getRectInCurrentCoords(rect, pageHeight) {
    const [x1, y1, x2, y2] = rect;
    const width = x2 - x1;
    const height = y2 - y1;
    switch (this.rotation) {
      case 0:
        return [x1, pageHeight - y2, width, height];
      case 90:
        return [x1, pageHeight - y1, height, width];
      case 180:
        return [x2, pageHeight - y1, width, height];
      case 270:
        return [x2, pageHeight - y2, height, width];
      default:
        throw new Error("Invalid rotation");
    }
  }
  onceAdded() {}
  isEmpty() {
    return false;
  }
  enableEditMode() {
    this.#isInEditMode = true;
  }
  disableEditMode() {
    this.#isInEditMode = false;
  }
  isInEditMode() {
    return this.#isInEditMode;
  }
  shouldGetKeyboardEvents() {
    return this.#isResizerEnabledForKeyboard;
  }
  needsToBeRebuilt() {
    return this.div && !this.isAttachedToDOM;
  }
  #addFocusListeners() {
    if (this.#focusAC || !this.div) {
      return;
    }
    this.#focusAC = new AbortController();
    const signal = this._uiManager.combinedSignal(this.#focusAC);
    this.div.addEventListener("focusin", this.focusin.bind(this), {
      signal
    });
    this.div.addEventListener("focusout", this.focusout.bind(this), {
      signal
    });
  }
  rebuild() {
    this.#addFocusListeners();
  }
  rotate(_angle) {}
  serialize(isForCopying = false, context = null) {
    unreachable("An editor must be serializable");
  }
  static deserialize(data, parent, uiManager) {
    const editor = new this.prototype.constructor({
      parent,
      id: parent.getNextId(),
      uiManager
    });
    editor.rotation = data.rotation;
    editor.#accessibilityData = data.accessibilityData;
    const [pageWidth, pageHeight] = editor.pageDimensions;
    const [x, y, width, height] = editor.getRectInCurrentCoords(data.rect, pageHeight);
    editor.x = x / pageWidth;
    editor.y = y / pageHeight;
    editor.width = width / pageWidth;
    editor.height = height / pageHeight;
    return editor;
  }
  get hasBeenModified() {
    return !!this.annotationElementId && (this.deleted || this.serialize() !== null);
  }
  remove() {
    this.#focusAC?.abort();
    this.#focusAC = null;
    if (!this.isEmpty()) {
      this.commit();
    }
    if (this.parent) {
      this.parent.remove(this);
    } else {
      this._uiManager.removeEditor(this);
    }
    if (this.#moveInDOMTimeout) {
      clearTimeout(this.#moveInDOMTimeout);
      this.#moveInDOMTimeout = null;
    }
    this.#stopResizing();
    this.removeEditToolbar();
    if (this.#telemetryTimeouts) {
      for (const timeout of this.#telemetryTimeouts.values()) {
        clearTimeout(timeout);
      }
      this.#telemetryTimeouts = null;
    }
    this.parent = null;
  }
  get isResizable() {
    return false;
  }
  makeResizable() {
    if (this.isResizable) {
      this.#createResizers();
      this.#resizersDiv.classList.remove("hidden");
      bindEvents(this, this.div, ["keydown"]);
    }
  }
  get toolbarPosition() {
    return null;
  }
  keydown(event) {
    if (!this.isResizable || event.target !== this.div || event.key !== "Enter") {
      return;
    }
    this._uiManager.setSelected(this);
    this.#savedDimensions = {
      savedX: this.x,
      savedY: this.y,
      savedWidth: this.width,
      savedHeight: this.height
    };
    const children = this.#resizersDiv.children;
    if (!this.#allResizerDivs) {
      this.#allResizerDivs = Array.from(children);
      const boundResizerKeydown = this.#resizerKeydown.bind(this);
      const boundResizerBlur = this.#resizerBlur.bind(this);
      const signal = this._uiManager._signal;
      for (const div of this.#allResizerDivs) {
        const name = div.getAttribute("data-resizer-name");
        div.setAttribute("role", "spinbutton");
        div.addEventListener("keydown", boundResizerKeydown, {
          signal
        });
        div.addEventListener("blur", boundResizerBlur, {
          signal
        });
        div.addEventListener("focus", this.#resizerFocus.bind(this, name), {
          signal
        });
        div.setAttribute("data-l10n-id", AnnotationEditor._l10nResizer[name]);
      }
    }
    const first = this.#allResizerDivs[0];
    let firstPosition = 0;
    for (const div of children) {
      if (div === first) {
        break;
      }
      firstPosition++;
    }
    const nextFirstPosition = (360 - this.rotation + this.parentRotation) % 360 / 90 * (this.#allResizerDivs.length / 4);
    if (nextFirstPosition !== firstPosition) {
      if (nextFirstPosition < firstPosition) {
        for (let i = 0; i < firstPosition - nextFirstPosition; i++) {
          this.#resizersDiv.append(this.#resizersDiv.firstChild);
        }
      } else if (nextFirstPosition > firstPosition) {
        for (let i = 0; i < nextFirstPosition - firstPosition; i++) {
          this.#resizersDiv.firstChild.before(this.#resizersDiv.lastChild);
        }
      }
      let i = 0;
      for (const child of children) {
        const div = this.#allResizerDivs[i++];
        const name = div.getAttribute("data-resizer-name");
        child.setAttribute("data-l10n-id", AnnotationEditor._l10nResizer[name]);
      }
    }
    this.#setResizerTabIndex(0);
    this.#isResizerEnabledForKeyboard = true;
    this.#resizersDiv.firstChild.focus({
      focusVisible: true
    });
    event.preventDefault();
    event.stopImmediatePropagation();
  }
  #resizerKeydown(event) {
    AnnotationEditor._resizerKeyboardManager.exec(this, event);
  }
  #resizerBlur(event) {
    if (this.#isResizerEnabledForKeyboard && event.relatedTarget?.parentNode !== this.#resizersDiv) {
      this.#stopResizing();
    }
  }
  #resizerFocus(name) {
    this.#focusedResizerName = this.#isResizerEnabledForKeyboard ? name : "";
  }
  #setResizerTabIndex(value) {
    if (!this.#allResizerDivs) {
      return;
    }
    for (const div of this.#allResizerDivs) {
      div.tabIndex = value;
    }
  }
  _resizeWithKeyboard(x, y) {
    if (!this.#isResizerEnabledForKeyboard) {
      return;
    }
    this.#resizerPointermove(this.#focusedResizerName, {
      movementX: x,
      movementY: y
    });
  }
  #stopResizing() {
    this.#isResizerEnabledForKeyboard = false;
    this.#setResizerTabIndex(-1);
    if (this.#savedDimensions) {
      const {
        savedX,
        savedY,
        savedWidth,
        savedHeight
      } = this.#savedDimensions;
      this.#addResizeToUndoStack(savedX, savedY, savedWidth, savedHeight);
      this.#savedDimensions = null;
    }
  }
  _stopResizingWithKeyboard() {
    this.#stopResizing();
    this.div.focus();
  }
  select() {
    this.makeResizable();
    this.div?.classList.add("selectedEditor");
    if (!this._editToolbar) {
      this.addEditToolbar().then(() => {
        if (this.div?.classList.contains("selectedEditor")) {
          this._editToolbar?.show();
        }
      });
      return;
    }
    this._editToolbar?.show();
    this.#altText?.toggleAltTextBadge(false);
  }
  unselect() {
    this.#resizersDiv?.classList.add("hidden");
    this.div?.classList.remove("selectedEditor");
    if (this.div?.contains(document.activeElement)) {
      this._uiManager.currentLayer.div.focus({
        preventScroll: true
      });
    }
    this._editToolbar?.hide();
    this.#altText?.toggleAltTextBadge(true);
  }
  updateParams(type, value) {}
  disableEditing() {}
  enableEditing() {}
  enterInEditMode() {}
  getImageForAltText() {
    return null;
  }
  get contentDiv() {
    return this.div;
  }
  get isEditing() {
    return this.#isEditing;
  }
  set isEditing(value) {
    this.#isEditing = value;
    if (!this.parent) {
      return;
    }
    if (value) {
      this.parent.setSelected(this);
      this.parent.setActiveEditor(this);
    } else {
      this.parent.setActiveEditor(null);
    }
  }
  setAspectRatio(width, height) {
    this.#keepAspectRatio = true;
    const aspectRatio = width / height;
    const {
      style
    } = this.div;
    style.aspectRatio = aspectRatio;
    style.height = "auto";
  }
  static get MIN_SIZE() {
    return 16;
  }
  static canCreateNewEmptyEditor() {
    return true;
  }
  get telemetryInitialData() {
    return {
      action: "added"
    };
  }
  get telemetryFinalData() {
    return null;
  }
  _reportTelemetry(data, mustWait = false) {
    if (mustWait) {
      this.#telemetryTimeouts ||= new Map();
      const {
        action
      } = data;
      let timeout = this.#telemetryTimeouts.get(action);
      if (timeout) {
        clearTimeout(timeout);
      }
      timeout = setTimeout(() => {
        this._reportTelemetry(data);
        this.#telemetryTimeouts.delete(action);
        if (this.#telemetryTimeouts.size === 0) {
          this.#telemetryTimeouts = null;
        }
      }, AnnotationEditor._telemetryTimeout);
      this.#telemetryTimeouts.set(action, timeout);
      return;
    }
    data.type ||= this.editorType;
    this._uiManager._eventBus.dispatch("reporttelemetry", {
      source: this,
      details: {
        type: "editing",
        data
      }
    });
  }
  show(visible = this._isVisible) {
    this.div.classList.toggle("hidden", !visible);
    this._isVisible = visible;
  }
  enable() {
    if (this.div) {
      this.div.tabIndex = 0;
    }
    this.#disabled = false;
  }
  disable() {
    if (this.div) {
      this.div.tabIndex = -1;
    }
    this.#disabled = true;
  }
  renderAnnotationElement(annotation) {
    let content = annotation.container.querySelector(".annotationContent");
    if (!content) {
      content = document.createElement("div");
      content.classList.add("annotationContent", this.editorType);
      annotation.container.prepend(content);
    } else if (content.nodeName === "CANVAS") {
      const canvas = content;
      content = document.createElement("div");
      content.classList.add("annotationContent", this.editorType);
      canvas.before(content);
    }
    return content;
  }
  resetAnnotationElement(annotation) {
    const {
      firstChild
    } = annotation.container;
    if (firstChild.nodeName === "DIV" && firstChild.classList.contains("annotationContent")) {
      firstChild.remove();
    }
  }
}
class FakeEditor extends AnnotationEditor {
  constructor(params) {
    super(params);
    this.annotationElementId = params.annotationElementId;
    this.deleted = true;
  }
  serialize() {
    return {
      id: this.annotationElementId,
      deleted: true,
      pageIndex: this.pageIndex
    };
  }
}

;// CONCATENATED MODULE: ./src/shared/murmurhash3.js
const SEED = 0xc3d2e1f0;
const MASK_HIGH = 0xffff0000;
const MASK_LOW = 0xffff;
class MurmurHash3_64 {
  constructor(seed) {
    this.h1 = seed ? seed & 0xffffffff : SEED;
    this.h2 = seed ? seed & 0xffffffff : SEED;
  }
  update(input) {
    let data, length;
    if (typeof input === "string") {
      data = new Uint8Array(input.length * 2);
      length = 0;
      for (let i = 0, ii = input.length; i < ii; i++) {
        const code = input.charCodeAt(i);
        if (code <= 0xff) {
          data[length++] = code;
        } else {
          data[length++] = code >>> 8;
          data[length++] = code & 0xff;
        }
      }
    } else if (ArrayBuffer.isView(input)) {
      data = input.slice();
      length = data.byteLength;
    } else {
      throw new Error("Invalid data format, must be a string or TypedArray.");
    }
    const blockCounts = length >> 2;
    const tailLength = length - blockCounts * 4;
    const dataUint32 = new Uint32Array(data.buffer, 0, blockCounts);
    let k1 = 0,
      k2 = 0;
    let h1 = this.h1,
      h2 = this.h2;
    const C1 = 0xcc9e2d51,
      C2 = 0x1b873593;
    const C1_LOW = C1 & MASK_LOW,
      C2_LOW = C2 & MASK_LOW;
    for (let i = 0; i < blockCounts; i++) {
      if (i & 1) {
        k1 = dataUint32[i];
        k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW;
        k1 = k1 << 15 | k1 >>> 17;
        k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW;
        h1 ^= k1;
        h1 = h1 << 13 | h1 >>> 19;
        h1 = h1 * 5 + 0xe6546b64;
      } else {
        k2 = dataUint32[i];
        k2 = k2 * C1 & MASK_HIGH | k2 * C1_LOW & MASK_LOW;
        k2 = k2 << 15 | k2 >>> 17;
        k2 = k2 * C2 & MASK_HIGH | k2 * C2_LOW & MASK_LOW;
        h2 ^= k2;
        h2 = h2 << 13 | h2 >>> 19;
        h2 = h2 * 5 + 0xe6546b64;
      }
    }
    k1 = 0;
    switch (tailLength) {
      case 3:
        k1 ^= data[blockCounts * 4 + 2] << 16;
      case 2:
        k1 ^= data[blockCounts * 4 + 1] << 8;
      case 1:
        k1 ^= data[blockCounts * 4];
        k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW;
        k1 = k1 << 15 | k1 >>> 17;
        k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW;
        if (blockCounts & 1) {
          h1 ^= k1;
        } else {
          h2 ^= k1;
        }
    }
    this.h1 = h1;
    this.h2 = h2;
  }
  hexdigest() {
    let h1 = this.h1,
      h2 = this.h2;
    h1 ^= h2 >>> 1;
    h1 = h1 * 0xed558ccd & MASK_HIGH | h1 * 0x8ccd & MASK_LOW;
    h2 = h2 * 0xff51afd7 & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xafd7ed55 & MASK_HIGH) >>> 16;
    h1 ^= h2 >>> 1;
    h1 = h1 * 0x1a85ec53 & MASK_HIGH | h1 * 0xec53 & MASK_LOW;
    h2 = h2 * 0xc4ceb9fe & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xb9fe1a85 & MASK_HIGH) >>> 16;
    h1 ^= h2 >>> 1;
    return (h1 >>> 0).toString(16).padStart(8, "0") + (h2 >>> 0).toString(16).padStart(8, "0");
  }
}

;// CONCATENATED MODULE: ./src/display/annotation_storage.js



const SerializableEmpty = Object.freeze({
  map: null,
  hash: "",
  transfer: undefined
});
class AnnotationStorage {
  #modified = false;
  #modifiedIds = null;
  #storage = new Map();
  constructor() {
    this.onSetModified = null;
    this.onResetModified = null;
    this.onAnnotationEditor = null;
  }
  getValue(key, defaultValue) {
    const value = this.#storage.get(key);
    if (value === undefined) {
      return defaultValue;
    }
    return Object.assign(defaultValue, value);
  }
  getRawValue(key) {
    return this.#storage.get(key);
  }
  remove(key) {
    this.#storage.delete(key);
    if (this.#storage.size === 0) {
      this.resetModified();
    }
    if (typeof this.onAnnotationEditor === "function") {
      for (const value of this.#storage.values()) {
        if (value instanceof AnnotationEditor) {
          return;
        }
      }
      this.onAnnotationEditor(null);
    }
  }
  setValue(key, value) {
    const obj = this.#storage.get(key);
    let modified = false;
    if (obj !== undefined) {
      for (const [entry, val] of Object.entries(value)) {
        if (obj[entry] !== val) {
          modified = true;
          obj[entry] = val;
        }
      }
    } else {
      modified = true;
      this.#storage.set(key, value);
    }
    if (modified) {
      this.#setModified();
    }
    if (value instanceof AnnotationEditor && typeof this.onAnnotationEditor === "function") {
      this.onAnnotationEditor(value.constructor._type);
    }
  }
  has(key) {
    return this.#storage.has(key);
  }
  getAll() {
    return this.#storage.size > 0 ? objectFromMap(this.#storage) : null;
  }
  setAll(obj) {
    for (const [key, val] of Object.entries(obj)) {
      this.setValue(key, val);
    }
  }
  get size() {
    return this.#storage.size;
  }
  #setModified() {
    if (!this.#modified) {
      this.#modified = true;
      if (typeof this.onSetModified === "function") {
        this.onSetModified();
      }
    }
  }
  resetModified() {
    if (this.#modified) {
      this.#modified = false;
      if (typeof this.onResetModified === "function") {
        this.onResetModified();
      }
    }
  }
  get print() {
    return new PrintAnnotationStorage(this);
  }
  get serializable() {
    if (this.#storage.size === 0) {
      return SerializableEmpty;
    }
    const map = new Map(),
      hash = new MurmurHash3_64(),
      transfer = [];
    const context = Object.create(null);
    let hasBitmap = false;
    for (const [key, val] of this.#storage) {
      const serialized = val instanceof AnnotationEditor ? val.serialize(false, context) : val;
      if (serialized) {
        map.set(key, serialized);
        hash.update(`${key}:${JSON.stringify(serialized)}`);
        hasBitmap ||= !!serialized.bitmap;
      }
    }
    if (hasBitmap) {
      for (const value of map.values()) {
        if (value.bitmap) {
          transfer.push(value.bitmap);
        }
      }
    }
    return map.size > 0 ? {
      map,
      hash: hash.hexdigest(),
      transfer
    } : SerializableEmpty;
  }
  get editorStats() {
    let stats = null;
    const typeToEditor = new Map();
    for (const value of this.#storage.values()) {
      if (!(value instanceof AnnotationEditor)) {
        continue;
      }
      const editorStats = value.telemetryFinalData;
      if (!editorStats) {
        continue;
      }
      const {
        type
      } = editorStats;
      if (!typeToEditor.has(type)) {
        typeToEditor.set(type, Object.getPrototypeOf(value).constructor);
      }
      stats ||= Object.create(null);
      const map = stats[type] ||= new Map();
      for (const [key, val] of Object.entries(editorStats)) {
        if (key === "type") {
          continue;
        }
        let counters = map.get(key);
        if (!counters) {
          counters = new Map();
          map.set(key, counters);
        }
        const count = counters.get(val) ?? 0;
        counters.set(val, count + 1);
      }
    }
    for (const [type, editor] of typeToEditor) {
      stats[type] = editor.computeTelemetryFinalData(stats[type]);
    }
    return stats;
  }
  resetModifiedIds() {
    this.#modifiedIds = null;
  }
  get modifiedIds() {
    if (this.#modifiedIds) {
      return this.#modifiedIds;
    }
    const ids = [];
    for (const value of this.#storage.values()) {
      if (!(value instanceof AnnotationEditor) || !value.annotationElementId || !value.serialize()) {
        continue;
      }
      ids.push(value.annotationElementId);
    }
    return this.#modifiedIds = {
      ids: new Set(ids),
      hash: ids.join(",")
    };
  }
}
class PrintAnnotationStorage extends AnnotationStorage {
  #serializable;
  constructor(parent) {
    super();
    const {
      map,
      hash,
      transfer
    } = parent.serializable;
    const clone = structuredClone(map, transfer ? {
      transfer
    } : null);
    this.#serializable = {
      map: clone,
      hash,
      transfer
    };
  }
  get print() {
    unreachable("Should not call PrintAnnotationStorage.print");
  }
  get serializable() {
    return this.#serializable;
  }
  get modifiedIds() {
    return shadow(this, "modifiedIds", {
      ids: new Set(),
      hash: ""
    });
  }
}

;// CONCATENATED MODULE: ./src/display/font_loader.js

class FontLoader {
  #systemFonts = new Set();
  constructor({
    ownerDocument = globalThis.document,
    styleElement = null
  }) {
    this._document = ownerDocument;
    this.nativeFontFaces = new Set();
    this.styleElement = null;
  }
  addNativeFontFace(nativeFontFace) {
    this.nativeFontFaces.add(nativeFontFace);
    this._document.fonts.add(nativeFontFace);
  }
  removeNativeFontFace(nativeFontFace) {
    this.nativeFontFaces.delete(nativeFontFace);
    this._document.fonts.delete(nativeFontFace);
  }
  insertRule(rule) {
    if (!this.styleElement) {
      this.styleElement = this._document.createElement("style");
      this._document.documentElement.getElementsByTagName("head")[0].append(this.styleElement);
    }
    const styleSheet = this.styleElement.sheet;
    styleSheet.insertRule(rule, styleSheet.cssRules.length);
  }
  clear() {
    for (const nativeFontFace of this.nativeFontFaces) {
      this._document.fonts.delete(nativeFontFace);
    }
    this.nativeFontFaces.clear();
    this.#systemFonts.clear();
    if (this.styleElement) {
      this.styleElement.remove();
      this.styleElement = null;
    }
  }
  async loadSystemFont({
    systemFontInfo: info,
    _inspectFont
  }) {
    if (!info || this.#systemFonts.has(info.loadedName)) {
      return;
    }
    assert(!this.disableFontFace, "loadSystemFont shouldn't be called when `disableFontFace` is set.");
    if (this.isFontLoadingAPISupported) {
      const {
        loadedName,
        src,
        style
      } = info;
      const fontFace = new FontFace(loadedName, src, style);
      this.addNativeFontFace(fontFace);
      try {
        await fontFace.load();
        this.#systemFonts.add(loadedName);
        _inspectFont?.(info);
      } catch {
        warn(`Cannot load system font: ${info.baseFontName}, installing it could help to improve PDF rendering.`);
        this.removeNativeFontFace(fontFace);
      }
      return;
    }
    unreachable("Not implemented: loadSystemFont without the Font Loading API.");
  }
  async bind(font) {
    if (font.attached || font.missingFile && !font.systemFontInfo) {
      return;
    }
    font.attached = true;
    if (font.systemFontInfo) {
      await this.loadSystemFont(font);
      return;
    }
    if (this.isFontLoadingAPISupported) {
      const nativeFontFace = font.createNativeFontFace();
      if (nativeFontFace) {
        this.addNativeFontFace(nativeFontFace);
        try {
          await nativeFontFace.loaded;
        } catch (ex) {
          warn(`Failed to load font '${nativeFontFace.family}': '${ex}'.`);
          font.disableFontFace = true;
          throw ex;
        }
      }
      return;
    }
    const rule = font.createFontFaceRule();
    if (rule) {
      this.insertRule(rule);
      if (this.isSyncFontLoadingSupported) {
        return;
      }
      throw new Error("Not implemented: async font loading");
    }
  }
  get isFontLoadingAPISupported() {
    const hasFonts = !!this._document?.fonts;
    return shadow(this, "isFontLoadingAPISupported", hasFonts);
  }
  get isSyncFontLoadingSupported() {
    return shadow(this, "isSyncFontLoadingSupported", true);
  }
  _queueLoadingCallback(callback) {
    throw new Error("Not implemented: _queueLoadingCallback");
  }
  get _loadTestFont() {
    throw new Error("Not implemented: _loadTestFont");
  }
  _prepareFontLoadEvent(font, request) {
    throw new Error("Not implemented: _prepareFontLoadEvent");
  }
}
class FontFaceObject {
  constructor(translatedData, {
    disableFontFace = false,
    inspectFont = null
  }) {
    this.compiledGlyphs = Object.create(null);
    for (const i in translatedData) {
      this[i] = translatedData[i];
    }
    this.disableFontFace = disableFontFace === true;
    this._inspectFont = inspectFont;
  }
  createNativeFontFace() {
    if (!this.data || this.disableFontFace) {
      return null;
    }
    let nativeFontFace;
    if (!this.cssFontInfo) {
      nativeFontFace = new FontFace(this.loadedName, this.data, {});
    } else {
      const css = {
        weight: this.cssFontInfo.fontWeight
      };
      if (this.cssFontInfo.italicAngle) {
        css.style = `oblique ${this.cssFontInfo.italicAngle}deg`;
      }
      nativeFontFace = new FontFace(this.cssFontInfo.fontFamily, this.data, css);
    }
    this._inspectFont?.(this);
    return nativeFontFace;
  }
  createFontFaceRule() {
    if (!this.data || this.disableFontFace) {
      return null;
    }
    const data = bytesToString(this.data);
    const url = `url(data:${this.mimetype};base64,${btoa(data)});`;
    let rule;
    if (!this.cssFontInfo) {
      rule = `@font-face {font-family:"${this.loadedName}";src:${url}}`;
    } else {
      let css = `font-weight: ${this.cssFontInfo.fontWeight};`;
      if (this.cssFontInfo.italicAngle) {
        css += `font-style: oblique ${this.cssFontInfo.italicAngle}deg;`;
      }
      rule = `@font-face {font-family:"${this.cssFontInfo.fontFamily}";${css}src:${url}}`;
    }
    this._inspectFont?.(this, url);
    return rule;
  }
  getPathGenerator(objs, character) {
    if (this.compiledGlyphs[character] !== undefined) {
      return this.compiledGlyphs[character];
    }
    let cmds;
    try {
      cmds = objs.get(this.loadedName + "_path_" + character);
    } catch (ex) {
      warn(`getPathGenerator - ignoring character: "${ex}".`);
    }
    if (!Array.isArray(cmds) || cmds.length === 0) {
      return this.compiledGlyphs[character] = function (c, size) {};
    }
    const commands = [];
    for (let i = 0, ii = cmds.length; i < ii;) {
      switch (cmds[i++]) {
        case FontRenderOps.BEZIER_CURVE_TO:
          {
            const [a, b, c, d, e, f] = cmds.slice(i, i + 6);
            commands.push(ctx => ctx.bezierCurveTo(a, b, c, d, e, f));
            i += 6;
          }
          break;
        case FontRenderOps.MOVE_TO:
          {
            const [a, b] = cmds.slice(i, i + 2);
            commands.push(ctx => ctx.moveTo(a, b));
            i += 2;
          }
          break;
        case FontRenderOps.LINE_TO:
          {
            const [a, b] = cmds.slice(i, i + 2);
            commands.push(ctx => ctx.lineTo(a, b));
            i += 2;
          }
          break;
        case FontRenderOps.QUADRATIC_CURVE_TO:
          {
            const [a, b, c, d] = cmds.slice(i, i + 4);
            commands.push(ctx => ctx.quadraticCurveTo(a, b, c, d));
            i += 4;
          }
          break;
        case FontRenderOps.RESTORE:
          commands.push(ctx => ctx.restore());
          break;
        case FontRenderOps.SAVE:
          commands.push(ctx => ctx.save());
          break;
        case FontRenderOps.SCALE:
          assert(commands.length === 2, "Scale command is only valid at the third position.");
          break;
        case FontRenderOps.TRANSFORM:
          {
            const [a, b, c, d, e, f] = cmds.slice(i, i + 6);
            commands.push(ctx => ctx.transform(a, b, c, d, e, f));
            i += 6;
          }
          break;
        case FontRenderOps.TRANSLATE:
          {
            const [a, b] = cmds.slice(i, i + 2);
            commands.push(ctx => ctx.translate(a, b));
            i += 2;
          }
          break;
      }
    }
    return this.compiledGlyphs[character] = function glyphDrawer(ctx, size) {
      commands[0](ctx);
      commands[1](ctx);
      ctx.scale(size, -size);
      for (let i = 2, ii = commands.length; i < ii; i++) {
        commands[i](ctx);
      }
    };
  }
}

;// CONCATENATED MODULE: ./src/display/pattern_helper.js


const PathType = {
  FILL: "Fill",
  STROKE: "Stroke",
  SHADING: "Shading"
};
function applyBoundingBox(ctx, bbox) {
  if (!bbox) {
    return;
  }
  const width = bbox[2] - bbox[0];
  const height = bbox[3] - bbox[1];
  const region = new Path2D();
  region.rect(bbox[0], bbox[1], width, height);
  ctx.clip(region);
}
class BaseShadingPattern {
  getPattern() {
    unreachable("Abstract method `getPattern` called.");
  }
}
class RadialAxialShadingPattern extends BaseShadingPattern {
  constructor(IR) {
    super();
    this._type = IR[1];
    this._bbox = IR[2];
    this._colorStops = IR[3];
    this._p0 = IR[4];
    this._p1 = IR[5];
    this._r0 = IR[6];
    this._r1 = IR[7];
    this.matrix = null;
  }
  _createGradient(ctx) {
    let grad;
    if (this._type === "axial") {
      grad = ctx.createLinearGradient(this._p0[0], this._p0[1], this._p1[0], this._p1[1]);
    } else if (this._type === "radial") {
      grad = ctx.createRadialGradient(this._p0[0], this._p0[1], this._r0, this._p1[0], this._p1[1], this._r1);
    }
    for (const colorStop of this._colorStops) {
      grad.addColorStop(colorStop[0], colorStop[1]);
    }
    return grad;
  }
  getPattern(ctx, owner, inverse, pathType) {
    let pattern;
    if (pathType === PathType.STROKE || pathType === PathType.FILL) {
      const ownerBBox = owner.current.getClippedPathBoundingBox(pathType, getCurrentTransform(ctx)) || [0, 0, 0, 0];
      const width = Math.ceil(ownerBBox[2] - ownerBBox[0]) || 1;
      const height = Math.ceil(ownerBBox[3] - ownerBBox[1]) || 1;
      const tmpCanvas = owner.cachedCanvases.getCanvas("pattern", width, height, true);
      const tmpCtx = tmpCanvas.context;
      tmpCtx.clearRect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
      tmpCtx.beginPath();
      tmpCtx.rect(0, 0, tmpCtx.canvas.width, tmpCtx.canvas.height);
      tmpCtx.translate(-ownerBBox[0], -ownerBBox[1]);
      inverse = Util.transform(inverse, [1, 0, 0, 1, ownerBBox[0], ownerBBox[1]]);
      tmpCtx.transform(...owner.baseTransform);
      if (this.matrix) {
        tmpCtx.transform(...this.matrix);
      }
      applyBoundingBox(tmpCtx, this._bbox);
      tmpCtx.fillStyle = this._createGradient(tmpCtx);
      tmpCtx.fill();
      pattern = ctx.createPattern(tmpCanvas.canvas, "no-repeat");
      const domMatrix = new DOMMatrix(inverse);
      pattern.setTransform(domMatrix);
    } else {
      applyBoundingBox(ctx, this._bbox);
      pattern = this._createGradient(ctx);
    }
    return pattern;
  }
}
function drawTriangle(data, context, p1, p2, p3, c1, c2, c3) {
  const coords = context.coords,
    colors = context.colors;
  const bytes = data.data,
    rowSize = data.width * 4;
  let tmp;
  if (coords[p1 + 1] > coords[p2 + 1]) {
    tmp = p1;
    p1 = p2;
    p2 = tmp;
    tmp = c1;
    c1 = c2;
    c2 = tmp;
  }
  if (coords[p2 + 1] > coords[p3 + 1]) {
    tmp = p2;
    p2 = p3;
    p3 = tmp;
    tmp = c2;
    c2 = c3;
    c3 = tmp;
  }
  if (coords[p1 + 1] > coords[p2 + 1]) {
    tmp = p1;
    p1 = p2;
    p2 = tmp;
    tmp = c1;
    c1 = c2;
    c2 = tmp;
  }
  const x1 = (coords[p1] + context.offsetX) * context.scaleX;
  const y1 = (coords[p1 + 1] + context.offsetY) * context.scaleY;
  const x2 = (coords[p2] + context.offsetX) * context.scaleX;
  const y2 = (coords[p2 + 1] + context.offsetY) * context.scaleY;
  const x3 = (coords[p3] + context.offsetX) * context.scaleX;
  const y3 = (coords[p3 + 1] + context.offsetY) * context.scaleY;
  if (y1 >= y3) {
    return;
  }
  const c1r = colors[c1],
    c1g = colors[c1 + 1],
    c1b = colors[c1 + 2];
  const c2r = colors[c2],
    c2g = colors[c2 + 1],
    c2b = colors[c2 + 2];
  const c3r = colors[c3],
    c3g = colors[c3 + 1],
    c3b = colors[c3 + 2];
  const minY = Math.round(y1),
    maxY = Math.round(y3);
  let xa, car, cag, cab;
  let xb, cbr, cbg, cbb;
  for (let y = minY; y <= maxY; y++) {
    if (y < y2) {
      const k = y < y1 ? 0 : (y1 - y) / (y1 - y2);
      xa = x1 - (x1 - x2) * k;
      car = c1r - (c1r - c2r) * k;
      cag = c1g - (c1g - c2g) * k;
      cab = c1b - (c1b - c2b) * k;
    } else {
      let k;
      if (y > y3) {
        k = 1;
      } else if (y2 === y3) {
        k = 0;
      } else {
        k = (y2 - y) / (y2 - y3);
      }
      xa = x2 - (x2 - x3) * k;
      car = c2r - (c2r - c3r) * k;
      cag = c2g - (c2g - c3g) * k;
      cab = c2b - (c2b - c3b) * k;
    }
    let k;
    if (y < y1) {
      k = 0;
    } else if (y > y3) {
      k = 1;
    } else {
      k = (y1 - y) / (y1 - y3);
    }
    xb = x1 - (x1 - x3) * k;
    cbr = c1r - (c1r - c3r) * k;
    cbg = c1g - (c1g - c3g) * k;
    cbb = c1b - (c1b - c3b) * k;
    const x1_ = Math.round(Math.min(xa, xb));
    const x2_ = Math.round(Math.max(xa, xb));
    let j = rowSize * y + x1_ * 4;
    for (let x = x1_; x <= x2_; x++) {
      k = (xa - x) / (xa - xb);
      if (k < 0) {
        k = 0;
      } else if (k > 1) {
        k = 1;
      }
      bytes[j++] = car - (car - cbr) * k | 0;
      bytes[j++] = cag - (cag - cbg) * k | 0;
      bytes[j++] = cab - (cab - cbb) * k | 0;
      bytes[j++] = 255;
    }
  }
}
function drawFigure(data, figure, context) {
  const ps = figure.coords;
  const cs = figure.colors;
  let i, ii;
  switch (figure.type) {
    case "lattice":
      const verticesPerRow = figure.verticesPerRow;
      const rows = Math.floor(ps.length / verticesPerRow) - 1;
      const cols = verticesPerRow - 1;
      for (i = 0; i < rows; i++) {
        let q = i * verticesPerRow;
        for (let j = 0; j < cols; j++, q++) {
          drawTriangle(data, context, ps[q], ps[q + 1], ps[q + verticesPerRow], cs[q], cs[q + 1], cs[q + verticesPerRow]);
          drawTriangle(data, context, ps[q + verticesPerRow + 1], ps[q + 1], ps[q + verticesPerRow], cs[q + verticesPerRow + 1], cs[q + 1], cs[q + verticesPerRow]);
        }
      }
      break;
    case "triangles":
      for (i = 0, ii = ps.length; i < ii; i += 3) {
        drawTriangle(data, context, ps[i], ps[i + 1], ps[i + 2], cs[i], cs[i + 1], cs[i + 2]);
      }
      break;
    default:
      throw new Error("illegal figure");
  }
}
class MeshShadingPattern extends BaseShadingPattern {
  constructor(IR) {
    super();
    this._coords = IR[2];
    this._colors = IR[3];
    this._figures = IR[4];
    this._bounds = IR[5];
    this._bbox = IR[7];
    this._background = IR[8];
    this.matrix = null;
  }
  _createMeshCanvas(combinedScale, backgroundColor, cachedCanvases) {
    const EXPECTED_SCALE = 1.1;
    const MAX_PATTERN_SIZE = 3000;
    const BORDER_SIZE = 2;
    const offsetX = Math.floor(this._bounds[0]);
    const offsetY = Math.floor(this._bounds[1]);
    const boundsWidth = Math.ceil(this._bounds[2]) - offsetX;
    const boundsHeight = Math.ceil(this._bounds[3]) - offsetY;
    const width = Math.min(Math.ceil(Math.abs(boundsWidth * combinedScale[0] * EXPECTED_SCALE)), MAX_PATTERN_SIZE);
    const height = Math.min(Math.ceil(Math.abs(boundsHeight * combinedScale[1] * EXPECTED_SCALE)), MAX_PATTERN_SIZE);
    const scaleX = boundsWidth / width;
    const scaleY = boundsHeight / height;
    const context = {
      coords: this._coords,
      colors: this._colors,
      offsetX: -offsetX,
      offsetY: -offsetY,
      scaleX: 1 / scaleX,
      scaleY: 1 / scaleY
    };
    const paddedWidth = width + BORDER_SIZE * 2;
    const paddedHeight = height + BORDER_SIZE * 2;
    const tmpCanvas = cachedCanvases.getCanvas("mesh", paddedWidth, paddedHeight, false);
    const tmpCtx = tmpCanvas.context;
    const data = tmpCtx.createImageData(width, height);
    if (backgroundColor) {
      const bytes = data.data;
      for (let i = 0, ii = bytes.length; i < ii; i += 4) {
        bytes[i] = backgroundColor[0];
        bytes[i + 1] = backgroundColor[1];
        bytes[i + 2] = backgroundColor[2];
        bytes[i + 3] = 255;
      }
    }
    for (const figure of this._figures) {
      drawFigure(data, figure, context);
    }
    tmpCtx.putImageData(data, BORDER_SIZE, BORDER_SIZE);
    const canvas = tmpCanvas.canvas;
    return {
      canvas,
      offsetX: offsetX - BORDER_SIZE * scaleX,
      offsetY: offsetY - BORDER_SIZE * scaleY,
      scaleX,
      scaleY
    };
  }
  getPattern(ctx, owner, inverse, pathType) {
    applyBoundingBox(ctx, this._bbox);
    let scale;
    if (pathType === PathType.SHADING) {
      scale = Util.singularValueDecompose2dScale(getCurrentTransform(ctx));
    } else {
      scale = Util.singularValueDecompose2dScale(owner.baseTransform);
      if (this.matrix) {
        const matrixScale = Util.singularValueDecompose2dScale(this.matrix);
        scale = [scale[0] * matrixScale[0], scale[1] * matrixScale[1]];
      }
    }
    const temporaryPatternCanvas = this._createMeshCanvas(scale, pathType === PathType.SHADING ? null : this._background, owner.cachedCanvases);
    if (pathType !== PathType.SHADING) {
      ctx.setTransform(...owner.baseTransform);
      if (this.matrix) {
        ctx.transform(...this.matrix);
      }
    }
    ctx.translate(temporaryPatternCanvas.offsetX, temporaryPatternCanvas.offsetY);
    ctx.scale(temporaryPatternCanvas.scaleX, temporaryPatternCanvas.scaleY);
    return ctx.createPattern(temporaryPatternCanvas.canvas, "no-repeat");
  }
}
class DummyShadingPattern extends BaseShadingPattern {
  getPattern() {
    return "hotpink";
  }
}
function getShadingPattern(IR) {
  switch (IR[0]) {
    case "RadialAxial":
      return new RadialAxialShadingPattern(IR);
    case "Mesh":
      return new MeshShadingPattern(IR);
    case "Dummy":
      return new DummyShadingPattern();
  }
  throw new Error(`Unknown IR type: ${IR[0]}`);
}
const PaintType = {
  COLORED: 1,
  UNCOLORED: 2
};
class TilingPattern {
  static MAX_PATTERN_SIZE = 3000;
  constructor(IR, color, ctx, canvasGraphicsFactory, baseTransform) {
    this.operatorList = IR[2];
    this.matrix = IR[3];
    this.bbox = IR[4];
    this.xstep = IR[5];
    this.ystep = IR[6];
    this.paintType = IR[7];
    this.tilingType = IR[8];
    this.color = color;
    this.ctx = ctx;
    this.canvasGraphicsFactory = canvasGraphicsFactory;
    this.baseTransform = baseTransform;
  }
  createPatternCanvas(owner) {
    const operatorList = this.operatorList;
    const bbox = this.bbox;
    const xstep = this.xstep;
    const ystep = this.ystep;
    const paintType = this.paintType;
    const tilingType = this.tilingType;
    const color = this.color;
    const canvasGraphicsFactory = this.canvasGraphicsFactory;
    info("TilingType: " + tilingType);
    const x0 = bbox[0],
      y0 = bbox[1],
      x1 = bbox[2],
      y1 = bbox[3];
    const matrixScale = Util.singularValueDecompose2dScale(this.matrix);
    const curMatrixScale = Util.singularValueDecompose2dScale(this.baseTransform);
    const combinedScale = [matrixScale[0] * curMatrixScale[0], matrixScale[1] * curMatrixScale[1]];
    const dimx = this.getSizeAndScale(xstep, this.ctx.canvas.width, combinedScale[0]);
    const dimy = this.getSizeAndScale(ystep, this.ctx.canvas.height, combinedScale[1]);
    const tmpCanvas = owner.cachedCanvases.getCanvas("pattern", dimx.size, dimy.size, true);
    const tmpCtx = tmpCanvas.context;
    const graphics = canvasGraphicsFactory.createCanvasGraphics(tmpCtx);
    graphics.groupLevel = owner.groupLevel;
    this.setFillAndStrokeStyleToContext(graphics, paintType, color);
    let adjustedX0 = x0;
    let adjustedY0 = y0;
    let adjustedX1 = x1;
    let adjustedY1 = y1;
    if (x0 < 0) {
      adjustedX0 = 0;
      adjustedX1 += Math.abs(x0);
    }
    if (y0 < 0) {
      adjustedY0 = 0;
      adjustedY1 += Math.abs(y0);
    }
    tmpCtx.translate(-(dimx.scale * adjustedX0), -(dimy.scale * adjustedY0));
    graphics.transform(dimx.scale, 0, 0, dimy.scale, 0, 0);
    tmpCtx.save();
    this.clipBbox(graphics, adjustedX0, adjustedY0, adjustedX1, adjustedY1);
    graphics.baseTransform = getCurrentTransform(graphics.ctx);
    graphics.executeOperatorList(operatorList);
    graphics.endDrawing();
    return {
      canvas: tmpCanvas.canvas,
      scaleX: dimx.scale,
      scaleY: dimy.scale,
      offsetX: adjustedX0,
      offsetY: adjustedY0
    };
  }
  getSizeAndScale(step, realOutputSize, scale) {
    step = Math.abs(step);
    const maxSize = Math.max(TilingPattern.MAX_PATTERN_SIZE, realOutputSize);
    let size = Math.ceil(step * scale);
    if (size >= maxSize) {
      size = maxSize;
    } else {
      scale = size / step;
    }
    return {
      scale,
      size
    };
  }
  clipBbox(graphics, x0, y0, x1, y1) {
    const bboxWidth = x1 - x0;
    const bboxHeight = y1 - y0;
    graphics.ctx.rect(x0, y0, bboxWidth, bboxHeight);
    graphics.current.updateRectMinMax(getCurrentTransform(graphics.ctx), [x0, y0, x1, y1]);
    graphics.clip();
    graphics.endPath();
  }
  setFillAndStrokeStyleToContext(graphics, paintType, color) {
    const context = graphics.ctx,
      current = graphics.current;
    switch (paintType) {
      case PaintType.COLORED:
        const ctx = this.ctx;
        context.fillStyle = ctx.fillStyle;
        context.strokeStyle = ctx.strokeStyle;
        current.fillColor = ctx.fillStyle;
        current.strokeColor = ctx.strokeStyle;
        break;
      case PaintType.UNCOLORED:
        const cssColor = Util.makeHexColor(color[0], color[1], color[2]);
        context.fillStyle = cssColor;
        context.strokeStyle = cssColor;
        current.fillColor = cssColor;
        current.strokeColor = cssColor;
        break;
      default:
        throw new FormatError(`Unsupported paint type: ${paintType}`);
    }
  }
  getPattern(ctx, owner, inverse, pathType) {
    let matrix = inverse;
    if (pathType !== PathType.SHADING) {
      matrix = Util.transform(matrix, owner.baseTransform);
      if (this.matrix) {
        matrix = Util.transform(matrix, this.matrix);
      }
    }
    const temporaryPatternCanvas = this.createPatternCanvas(owner);
    let domMatrix = new DOMMatrix(matrix);
    domMatrix = domMatrix.translate(temporaryPatternCanvas.offsetX, temporaryPatternCanvas.offsetY);
    domMatrix = domMatrix.scale(1 / temporaryPatternCanvas.scaleX, 1 / temporaryPatternCanvas.scaleY);
    const pattern = ctx.createPattern(temporaryPatternCanvas.canvas, "repeat");
    pattern.setTransform(domMatrix);
    return pattern;
  }
}

;// CONCATENATED MODULE: ./src/shared/image_utils.js

function convertToRGBA(params) {
  switch (params.kind) {
    case ImageKind.GRAYSCALE_1BPP:
      return convertBlackAndWhiteToRGBA(params);
    case ImageKind.RGB_24BPP:
      return convertRGBToRGBA(params);
  }
  return null;
}
function convertBlackAndWhiteToRGBA({
  src,
  srcPos = 0,
  dest,
  width,
  height,
  nonBlackColor = 0xffffffff,
  inverseDecode = false
}) {
  const black = util_FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff;
  const [zeroMapping, oneMapping] = inverseDecode ? [nonBlackColor, black] : [black, nonBlackColor];
  const widthInSource = width >> 3;
  const widthRemainder = width & 7;
  const srcLength = src.length;
  dest = new Uint32Array(dest.buffer);
  let destPos = 0;
  for (let i = 0; i < height; i++) {
    for (const max = srcPos + widthInSource; srcPos < max; srcPos++) {
      const elem = srcPos < srcLength ? src[srcPos] : 255;
      dest[destPos++] = elem & 0b10000000 ? oneMapping : zeroMapping;
      dest[destPos++] = elem & 0b1000000 ? oneMapping : zeroMapping;
      dest[destPos++] = elem & 0b100000 ? oneMapping : zeroMapping;
      dest[destPos++] = elem & 0b10000 ? oneMapping : zeroMapping;
      dest[destPos++] = elem & 0b1000 ? oneMapping : zeroMapping;
      dest[destPos++] = elem & 0b100 ? oneMapping : zeroMapping;
      dest[destPos++] = elem & 0b10 ? oneMapping : zeroMapping;
      dest[destPos++] = elem & 0b1 ? oneMapping : zeroMapping;
    }
    if (widthRemainder === 0) {
      continue;
    }
    const elem = srcPos < srcLength ? src[srcPos++] : 255;
    for (let j = 0; j < widthRemainder; j++) {
      dest[destPos++] = elem & 1 << 7 - j ? oneMapping : zeroMapping;
    }
  }
  return {
    srcPos,
    destPos
  };
}
function convertRGBToRGBA({
  src,
  srcPos = 0,
  dest,
  destPos = 0,
  width,
  height
}) {
  let i = 0;
  const len32 = src.length >> 2;
  const src32 = new Uint32Array(src.buffer, srcPos, len32);
  if (FeatureTest.isLittleEndian) {
    for (; i < len32 - 2; i += 3, destPos += 4) {
      const s1 = src32[i];
      const s2 = src32[i + 1];
      const s3 = src32[i + 2];
      dest[destPos] = s1 | 0xff000000;
      dest[destPos + 1] = s1 >>> 24 | s2 << 8 | 0xff000000;
      dest[destPos + 2] = s2 >>> 16 | s3 << 16 | 0xff000000;
      dest[destPos + 3] = s3 >>> 8 | 0xff000000;
    }
    for (let j = i * 4, jj = src.length; j < jj; j += 3) {
      dest[destPos++] = src[j] | src[j + 1] << 8 | src[j + 2] << 16 | 0xff000000;
    }
  } else {
    for (; i < len32 - 2; i += 3, destPos += 4) {
      const s1 = src32[i];
      const s2 = src32[i + 1];
      const s3 = src32[i + 2];
      dest[destPos] = s1 | 0xff;
      dest[destPos + 1] = s1 << 24 | s2 >>> 8 | 0xff;
      dest[destPos + 2] = s2 << 16 | s3 >>> 16 | 0xff;
      dest[destPos + 3] = s3 << 8 | 0xff;
    }
    for (let j = i * 4, jj = src.length; j < jj; j += 3) {
      dest[destPos++] = src[j] << 24 | src[j + 1] << 16 | src[j + 2] << 8 | 0xff;
    }
  }
  return {
    srcPos,
    destPos
  };
}
function grayToRGBA(src, dest) {
  if (FeatureTest.isLittleEndian) {
    for (let i = 0, ii = src.length; i < ii; i++) {
      dest[i] = src[i] * 0x10101 | 0xff000000;
    }
  } else {
    for (let i = 0, ii = src.length; i < ii; i++) {
      dest[i] = src[i] * 0x1010100 | 0x000000ff;
    }
  }
}

;// CONCATENATED MODULE: ./src/display/canvas.js




const MIN_FONT_SIZE = 16;
const MAX_FONT_SIZE = 100;
const EXECUTION_TIME = 15;
const EXECUTION_STEPS = 10;
const MAX_SIZE_TO_COMPILE = 1000;
const FULL_CHUNK_HEIGHT = 16;
function mirrorContextOperations(ctx, destCtx) {
  if (ctx._removeMirroring) {
    throw new Error("Context is already forwarding operations.");
  }
  ctx.__originalSave = ctx.save;
  ctx.__originalRestore = ctx.restore;
  ctx.__originalRotate = ctx.rotate;
  ctx.__originalScale = ctx.scale;
  ctx.__originalTranslate = ctx.translate;
  ctx.__originalTransform = ctx.transform;
  ctx.__originalSetTransform = ctx.setTransform;
  ctx.__originalResetTransform = ctx.resetTransform;
  ctx.__originalClip = ctx.clip;
  ctx.__originalMoveTo = ctx.moveTo;
  ctx.__originalLineTo = ctx.lineTo;
  ctx.__originalBezierCurveTo = ctx.bezierCurveTo;
  ctx.__originalRect = ctx.rect;
  ctx.__originalClosePath = ctx.closePath;
  ctx.__originalBeginPath = ctx.beginPath;
  ctx._removeMirroring = () => {
    ctx.save = ctx.__originalSave;
    ctx.restore = ctx.__originalRestore;
    ctx.rotate = ctx.__originalRotate;
    ctx.scale = ctx.__originalScale;
    ctx.translate = ctx.__originalTranslate;
    ctx.transform = ctx.__originalTransform;
    ctx.setTransform = ctx.__originalSetTransform;
    ctx.resetTransform = ctx.__originalResetTransform;
    ctx.clip = ctx.__originalClip;
    ctx.moveTo = ctx.__originalMoveTo;
    ctx.lineTo = ctx.__originalLineTo;
    ctx.bezierCurveTo = ctx.__originalBezierCurveTo;
    ctx.rect = ctx.__originalRect;
    ctx.closePath = ctx.__originalClosePath;
    ctx.beginPath = ctx.__originalBeginPath;
    delete ctx._removeMirroring;
  };
  ctx.save = function ctxSave() {
    destCtx.save();
    this.__originalSave();
  };
  ctx.restore = function ctxRestore() {
    destCtx.restore();
    this.__originalRestore();
  };
  ctx.translate = function ctxTranslate(x, y) {
    destCtx.translate(x, y);
    this.__originalTranslate(x, y);
  };
  ctx.scale = function ctxScale(x, y) {
    destCtx.scale(x, y);
    this.__originalScale(x, y);
  };
  ctx.transform = function ctxTransform(a, b, c, d, e, f) {
    destCtx.transform(a, b, c, d, e, f);
    this.__originalTransform(a, b, c, d, e, f);
  };
  ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) {
    destCtx.setTransform(a, b, c, d, e, f);
    this.__originalSetTransform(a, b, c, d, e, f);
  };
  ctx.resetTransform = function ctxResetTransform() {
    destCtx.resetTransform();
    this.__originalResetTransform();
  };
  ctx.rotate = function ctxRotate(angle) {
    destCtx.rotate(angle);
    this.__originalRotate(angle);
  };
  ctx.clip = function ctxRotate(rule) {
    destCtx.clip(rule);
    this.__originalClip(rule);
  };
  ctx.moveTo = function (x, y) {
    destCtx.moveTo(x, y);
    this.__originalMoveTo(x, y);
  };
  ctx.lineTo = function (x, y) {
    destCtx.lineTo(x, y);
    this.__originalLineTo(x, y);
  };
  ctx.bezierCurveTo = function (cp1x, cp1y, cp2x, cp2y, x, y) {
    destCtx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
    this.__originalBezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
  };
  ctx.rect = function (x, y, width, height) {
    destCtx.rect(x, y, width, height);
    this.__originalRect(x, y, width, height);
  };
  ctx.closePath = function () {
    destCtx.closePath();
    this.__originalClosePath();
  };
  ctx.beginPath = function () {
    destCtx.beginPath();
    this.__originalBeginPath();
  };
}
class CachedCanvases {
  constructor(canvasFactory) {
    this.canvasFactory = canvasFactory;
    this.cache = Object.create(null);
  }
  getCanvas(id, width, height) {
    let canvasEntry;
    if (this.cache[id] !== undefined) {
      canvasEntry = this.cache[id];
      this.canvasFactory.reset(canvasEntry, width, height);
    } else {
      canvasEntry = this.canvasFactory.create(width, height);
      this.cache[id] = canvasEntry;
    }
    return canvasEntry;
  }
  delete(id) {
    delete this.cache[id];
  }
  clear() {
    for (const id in this.cache) {
      const canvasEntry = this.cache[id];
      this.canvasFactory.destroy(canvasEntry);
      delete this.cache[id];
    }
  }
}
function drawImageAtIntegerCoords(ctx, srcImg, srcX, srcY, srcW, srcH, destX, destY, destW, destH) {
  const [a, b, c, d, tx, ty] = getCurrentTransform(ctx);
  if (b === 0 && c === 0) {
    const tlX = destX * a + tx;
    const rTlX = Math.round(tlX);
    const tlY = destY * d + ty;
    const rTlY = Math.round(tlY);
    const brX = (destX + destW) * a + tx;
    const rWidth = Math.abs(Math.round(brX) - rTlX) || 1;
    const brY = (destY + destH) * d + ty;
    const rHeight = Math.abs(Math.round(brY) - rTlY) || 1;
    ctx.setTransform(Math.sign(a), 0, 0, Math.sign(d), rTlX, rTlY);
    ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rWidth, rHeight);
    ctx.setTransform(a, b, c, d, tx, ty);
    return [rWidth, rHeight];
  }
  if (a === 0 && d === 0) {
    const tlX = destY * c + tx;
    const rTlX = Math.round(tlX);
    const tlY = destX * b + ty;
    const rTlY = Math.round(tlY);
    const brX = (destY + destH) * c + tx;
    const rWidth = Math.abs(Math.round(brX) - rTlX) || 1;
    const brY = (destX + destW) * b + ty;
    const rHeight = Math.abs(Math.round(brY) - rTlY) || 1;
    ctx.setTransform(0, Math.sign(b), Math.sign(c), 0, rTlX, rTlY);
    ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, 0, 0, rHeight, rWidth);
    ctx.setTransform(a, b, c, d, tx, ty);
    return [rHeight, rWidth];
  }
  ctx.drawImage(srcImg, srcX, srcY, srcW, srcH, destX, destY, destW, destH);
  const scaleX = Math.hypot(a, b);
  const scaleY = Math.hypot(c, d);
  return [scaleX * destW, scaleY * destH];
}
function compileType3Glyph(imgData) {
  const {
    width,
    height
  } = imgData;
  if (width > MAX_SIZE_TO_COMPILE || height > MAX_SIZE_TO_COMPILE) {
    return null;
  }
  const POINT_TO_PROCESS_LIMIT = 1000;
  const POINT_TYPES = new Uint8Array([0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0]);
  const width1 = width + 1;
  let points = new Uint8Array(width1 * (height + 1));
  let i, j, j0;
  const lineSize = width + 7 & ~7;
  let data = new Uint8Array(lineSize * height),
    pos = 0;
  for (const elem of imgData.data) {
    let mask = 128;
    while (mask > 0) {
      data[pos++] = elem & mask ? 0 : 255;
      mask >>= 1;
    }
  }
  let count = 0;
  pos = 0;
  if (data[pos] !== 0) {
    points[0] = 1;
    ++count;
  }
  for (j = 1; j < width; j++) {
    if (data[pos] !== data[pos + 1]) {
      points[j] = data[pos] ? 2 : 1;
      ++count;
    }
    pos++;
  }
  if (data[pos] !== 0) {
    points[j] = 2;
    ++count;
  }
  for (i = 1; i < height; i++) {
    pos = i * lineSize;
    j0 = i * width1;
    if (data[pos - lineSize] !== data[pos]) {
      points[j0] = data[pos] ? 1 : 8;
      ++count;
    }
    let sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0);
    for (j = 1; j < width; j++) {
      sum = (sum >> 2) + (data[pos + 1] ? 4 : 0) + (data[pos - lineSize + 1] ? 8 : 0);
      if (POINT_TYPES[sum]) {
        points[j0 + j] = POINT_TYPES[sum];
        ++count;
      }
      pos++;
    }
    if (data[pos - lineSize] !== data[pos]) {
      points[j0 + j] = data[pos] ? 2 : 4;
      ++count;
    }
    if (count > POINT_TO_PROCESS_LIMIT) {
      return null;
    }
  }
  pos = lineSize * (height - 1);
  j0 = i * width1;
  if (data[pos] !== 0) {
    points[j0] = 8;
    ++count;
  }
  for (j = 1; j < width; j++) {
    if (data[pos] !== data[pos + 1]) {
      points[j0 + j] = data[pos] ? 4 : 8;
      ++count;
    }
    pos++;
  }
  if (data[pos] !== 0) {
    points[j0 + j] = 4;
    ++count;
  }
  if (count > POINT_TO_PROCESS_LIMIT) {
    return null;
  }
  const steps = new Int32Array([0, width1, -1, 0, -width1, 0, 0, 0, 1]);
  const path = new Path2D();
  for (i = 0; count && i <= height; i++) {
    let p = i * width1;
    const end = p + width;
    while (p < end && !points[p]) {
      p++;
    }
    if (p === end) {
      continue;
    }
    path.moveTo(p % width1, i);
    const p0 = p;
    let type = points[p];
    do {
      const step = steps[type];
      do {
        p += step;
      } while (!points[p]);
      const pp = points[p];
      if (pp !== 5 && pp !== 10) {
        type = pp;
        points[p] = 0;
      } else {
        type = pp & 0x33 * type >> 4;
        points[p] &= type >> 2 | type << 2;
      }
      path.lineTo(p % width1, p / width1 | 0);
      if (!points[p]) {
        --count;
      }
    } while (p0 !== p);
    --i;
  }
  data = null;
  points = null;
  const drawOutline = function (c) {
    c.save();
    c.scale(1 / width, -1 / height);
    c.translate(0, -height);
    c.fill(path);
    c.beginPath();
    c.restore();
  };
  return drawOutline;
}
class CanvasExtraState {
  constructor(width, height) {
    this.alphaIsShape = false;
    this.fontSize = 0;
    this.fontSizeScale = 1;
    this.textMatrix = IDENTITY_MATRIX;
    this.textMatrixScale = 1;
    this.fontMatrix = FONT_IDENTITY_MATRIX;
    this.leading = 0;
    this.x = 0;
    this.y = 0;
    this.lineX = 0;
    this.lineY = 0;
    this.charSpacing = 0;
    this.wordSpacing = 0;
    this.textHScale = 1;
    this.textRenderingMode = TextRenderingMode.FILL;
    this.textRise = 0;
    this.fillColor = "#000000";
    this.strokeColor = "#000000";
    this.patternFill = false;
    this.fillAlpha = 1;
    this.strokeAlpha = 1;
    this.lineWidth = 1;
    this.activeSMask = null;
    this.transferMaps = "none";
    this.startNewPathAndClipBox([0, 0, width, height]);
  }
  clone() {
    const clone = Object.create(this);
    clone.clipBox = this.clipBox.slice();
    return clone;
  }
  setCurrentPoint(x, y) {
    this.x = x;
    this.y = y;
  }
  updatePathMinMax(transform, x, y) {
    [x, y] = Util.applyTransform([x, y], transform);
    this.minX = Math.min(this.minX, x);
    this.minY = Math.min(this.minY, y);
    this.maxX = Math.max(this.maxX, x);
    this.maxY = Math.max(this.maxY, y);
  }
  updateRectMinMax(transform, rect) {
    const p1 = Util.applyTransform(rect, transform);
    const p2 = Util.applyTransform(rect.slice(2), transform);
    const p3 = Util.applyTransform([rect[0], rect[3]], transform);
    const p4 = Util.applyTransform([rect[2], rect[1]], transform);
    this.minX = Math.min(this.minX, p1[0], p2[0], p3[0], p4[0]);
    this.minY = Math.min(this.minY, p1[1], p2[1], p3[1], p4[1]);
    this.maxX = Math.max(this.maxX, p1[0], p2[0], p3[0], p4[0]);
    this.maxY = Math.max(this.maxY, p1[1], p2[1], p3[1], p4[1]);
  }
  updateScalingPathMinMax(transform, minMax) {
    Util.scaleMinMax(transform, minMax);
    this.minX = Math.min(this.minX, minMax[0]);
    this.minY = Math.min(this.minY, minMax[1]);
    this.maxX = Math.max(this.maxX, minMax[2]);
    this.maxY = Math.max(this.maxY, minMax[3]);
  }
  updateCurvePathMinMax(transform, x0, y0, x1, y1, x2, y2, x3, y3, minMax) {
    const box = Util.bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3, minMax);
    if (minMax) {
      return;
    }
    this.updateRectMinMax(transform, box);
  }
  getPathBoundingBox(pathType = PathType.FILL, transform = null) {
    const box = [this.minX, this.minY, this.maxX, this.maxY];
    if (pathType === PathType.STROKE) {
      if (!transform) {
        unreachable("Stroke bounding box must include transform.");
      }
      const scale = Util.singularValueDecompose2dScale(transform);
      const xStrokePad = scale[0] * this.lineWidth / 2;
      const yStrokePad = scale[1] * this.lineWidth / 2;
      box[0] -= xStrokePad;
      box[1] -= yStrokePad;
      box[2] += xStrokePad;
      box[3] += yStrokePad;
    }
    return box;
  }
  updateClipFromPath() {
    const intersect = Util.intersect(this.clipBox, this.getPathBoundingBox());
    this.startNewPathAndClipBox(intersect || [0, 0, 0, 0]);
  }
  isEmptyClip() {
    return this.minX === Infinity;
  }
  startNewPathAndClipBox(box) {
    this.clipBox = box;
    this.minX = Infinity;
    this.minY = Infinity;
    this.maxX = 0;
    this.maxY = 0;
  }
  getClippedPathBoundingBox(pathType = PathType.FILL, transform = null) {
    return Util.intersect(this.clipBox, this.getPathBoundingBox(pathType, transform));
  }
}
function putBinaryImageData(ctx, imgData) {
  if (typeof ImageData !== "undefined" && imgData instanceof ImageData) {
    ctx.putImageData(imgData, 0, 0);
    return;
  }
  const height = imgData.height,
    width = imgData.width;
  const partialChunkHeight = height % FULL_CHUNK_HEIGHT;
  const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
  const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
  const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
  let srcPos = 0,
    destPos;
  const src = imgData.data;
  const dest = chunkImgData.data;
  let i, j, thisChunkHeight, elemsInThisChunk;
  if (imgData.kind === util_ImageKind.GRAYSCALE_1BPP) {
    const srcLength = src.byteLength;
    const dest32 = new Uint32Array(dest.buffer, 0, dest.byteLength >> 2);
    const dest32DataLength = dest32.length;
    const fullSrcDiff = width + 7 >> 3;
    const white = 0xffffffff;
    const black = util_FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff;
    for (i = 0; i < totalChunks; i++) {
      thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
      destPos = 0;
      for (j = 0; j < thisChunkHeight; j++) {
        const srcDiff = srcLength - srcPos;
        let k = 0;
        const kEnd = srcDiff > fullSrcDiff ? width : srcDiff * 8 - 7;
        const kEndUnrolled = kEnd & ~7;
        let mask = 0;
        let srcByte = 0;
        for (; k < kEndUnrolled; k += 8) {
          srcByte = src[srcPos++];
          dest32[destPos++] = srcByte & 128 ? white : black;
          dest32[destPos++] = srcByte & 64 ? white : black;
          dest32[destPos++] = srcByte & 32 ? white : black;
          dest32[destPos++] = srcByte & 16 ? white : black;
          dest32[destPos++] = srcByte & 8 ? white : black;
          dest32[destPos++] = srcByte & 4 ? white : black;
          dest32[destPos++] = srcByte & 2 ? white : black;
          dest32[destPos++] = srcByte & 1 ? white : black;
        }
        for (; k < kEnd; k++) {
          if (mask === 0) {
            srcByte = src[srcPos++];
            mask = 128;
          }
          dest32[destPos++] = srcByte & mask ? white : black;
          mask >>= 1;
        }
      }
      while (destPos < dest32DataLength) {
        dest32[destPos++] = 0;
      }
      ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
    }
  } else if (imgData.kind === util_ImageKind.RGBA_32BPP) {
    j = 0;
    elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4;
    for (i = 0; i < fullChunks; i++) {
      dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
      srcPos += elemsInThisChunk;
      ctx.putImageData(chunkImgData, 0, j);
      j += FULL_CHUNK_HEIGHT;
    }
    if (i < totalChunks) {
      elemsInThisChunk = width * partialChunkHeight * 4;
      dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
      ctx.putImageData(chunkImgData, 0, j);
    }
  } else if (imgData.kind === util_ImageKind.RGB_24BPP) {
    thisChunkHeight = FULL_CHUNK_HEIGHT;
    elemsInThisChunk = width * thisChunkHeight;
    for (i = 0; i < totalChunks; i++) {
      if (i >= fullChunks) {
        thisChunkHeight = partialChunkHeight;
        elemsInThisChunk = width * thisChunkHeight;
      }
      destPos = 0;
      for (j = elemsInThisChunk; j--;) {
        dest[destPos++] = src[srcPos++];
        dest[destPos++] = src[srcPos++];
        dest[destPos++] = src[srcPos++];
        dest[destPos++] = 255;
      }
      ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
    }
  } else {
    throw new Error(`bad image kind: ${imgData.kind}`);
  }
}
function putBinaryImageMask(ctx, imgData) {
  if (imgData.bitmap) {
    ctx.drawImage(imgData.bitmap, 0, 0);
    return;
  }
  const height = imgData.height,
    width = imgData.width;
  const partialChunkHeight = height % FULL_CHUNK_HEIGHT;
  const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT;
  const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1;
  const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT);
  let srcPos = 0;
  const src = imgData.data;
  const dest = chunkImgData.data;
  for (let i = 0; i < totalChunks; i++) {
    const thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight;
    ({
      srcPos
    } = convertBlackAndWhiteToRGBA({
      src,
      srcPos,
      dest,
      width,
      height: thisChunkHeight,
      nonBlackColor: 0
    }));
    ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT);
  }
}
function copyCtxState(sourceCtx, destCtx) {
  const properties = ["strokeStyle", "fillStyle", "fillRule", "globalAlpha", "lineWidth", "lineCap", "lineJoin", "miterLimit", "globalCompositeOperation", "font", "filter"];
  for (const property of properties) {
    if (sourceCtx[property] !== undefined) {
      destCtx[property] = sourceCtx[property];
    }
  }
  if (sourceCtx.setLineDash !== undefined) {
    destCtx.setLineDash(sourceCtx.getLineDash());
    destCtx.lineDashOffset = sourceCtx.lineDashOffset;
  }
}
function resetCtxToDefault(ctx) {
  ctx.strokeStyle = ctx.fillStyle = "#000000";
  ctx.fillRule = "nonzero";
  ctx.globalAlpha = 1;
  ctx.lineWidth = 1;
  ctx.lineCap = "butt";
  ctx.lineJoin = "miter";
  ctx.miterLimit = 10;
  ctx.globalCompositeOperation = "source-over";
  ctx.font = "10px sans-serif";
  if (ctx.setLineDash !== undefined) {
    ctx.setLineDash([]);
    ctx.lineDashOffset = 0;
  }
  const {
    filter
  } = ctx;
  if (filter !== "none" && filter !== "") {
    ctx.filter = "none";
  }
}
function getImageSmoothingEnabled(transform, interpolate) {
  if (interpolate) {
    return true;
  }
  const scale = Util.singularValueDecompose2dScale(transform);
  scale[0] = Math.fround(scale[0]);
  scale[1] = Math.fround(scale[1]);
  const actualScale = Math.fround((globalThis.devicePixelRatio || 1) * PixelsPerInch.PDF_TO_CSS_UNITS);
  return scale[0] <= actualScale && scale[1] <= actualScale;
}
const LINE_CAP_STYLES = ["butt", "round", "square"];
const LINE_JOIN_STYLES = ["miter", "round", "bevel"];
const NORMAL_CLIP = {};
const EO_CLIP = {};
class CanvasGraphics {
  constructor(canvasCtx, commonObjs, objs, canvasFactory, filterFactory, {
    optionalContentConfig,
    markedContentStack = null
  }, annotationCanvasMap, pageColors) {
    this.ctx = canvasCtx;
    this.current = new CanvasExtraState(this.ctx.canvas.width, this.ctx.canvas.height);
    this.stateStack = [];
    this.pendingClip = null;
    this.pendingEOFill = false;
    this.res = null;
    this.xobjs = null;
    this.commonObjs = commonObjs;
    this.objs = objs;
    this.canvasFactory = canvasFactory;
    this.filterFactory = filterFactory;
    this.groupStack = [];
    this.processingType3 = null;
    this.baseTransform = null;
    this.baseTransformStack = [];
    this.groupLevel = 0;
    this.smaskStack = [];
    this.smaskCounter = 0;
    this.tempSMask = null;
    this.suspendedCtx = null;
    this.contentVisible = true;
    this.markedContentStack = markedContentStack || [];
    this.optionalContentConfig = optionalContentConfig;
    this.cachedCanvases = new CachedCanvases(this.canvasFactory);
    this.cachedPatterns = new Map();
    this.annotationCanvasMap = annotationCanvasMap;
    this.viewportScale = 1;
    this.outputScaleX = 1;
    this.outputScaleY = 1;
    this.pageColors = pageColors;
    this._cachedScaleForStroking = [-1, 0];
    this._cachedGetSinglePixelWidth = null;
    this._cachedBitmapsMap = new Map();
  }
  getObject(data, fallback = null) {
    if (typeof data === "string") {
      return data.startsWith("g_") ? this.commonObjs.get(data) : this.objs.get(data);
    }
    return fallback;
  }
  beginDrawing({
    transform,
    viewport,
    transparency = false,
    background = null
  }) {
    const width = this.ctx.canvas.width;
    const height = this.ctx.canvas.height;
    const savedFillStyle = this.ctx.fillStyle;
    this.ctx.fillStyle = background || "#ffffff";
    this.ctx.fillRect(0, 0, width, height);
    this.ctx.fillStyle = savedFillStyle;
    if (transparency) {
      const transparentCanvas = this.cachedCanvases.getCanvas("transparent", width, height);
      this.compositeCtx = this.ctx;
      this.transparentCanvas = transparentCanvas.canvas;
      this.ctx = transparentCanvas.context;
      this.ctx.save();
      this.ctx.transform(...getCurrentTransform(this.compositeCtx));
    }
    this.ctx.save();
    resetCtxToDefault(this.ctx);
    if (transform) {
      this.ctx.transform(...transform);
      this.outputScaleX = transform[0];
      this.outputScaleY = transform[0];
    }
    this.ctx.transform(...viewport.transform);
    this.viewportScale = viewport.scale;
    this.baseTransform = getCurrentTransform(this.ctx);
  }
  executeOperatorList(operatorList, executionStartIdx, continueCallback, stepper) {
    const argsArray = operatorList.argsArray;
    const fnArray = operatorList.fnArray;
    let i = executionStartIdx || 0;
    const argsArrayLen = argsArray.length;
    if (argsArrayLen === i) {
      return i;
    }
    const chunkOperations = argsArrayLen - i > EXECUTION_STEPS && typeof continueCallback === "function";
    const endTime = chunkOperations ? Date.now() + EXECUTION_TIME : 0;
    let steps = 0;
    const commonObjs = this.commonObjs;
    const objs = this.objs;
    let fnId;
    while (true) {
      if (stepper !== undefined && i === stepper.nextBreakPoint) {
        stepper.breakIt(i, continueCallback);
        return i;
      }
      fnId = fnArray[i];
      if (fnId !== OPS.dependency) {
        this[fnId].apply(this, argsArray[i]);
      } else {
        for (const depObjId of argsArray[i]) {
          const objsPool = depObjId.startsWith("g_") ? commonObjs : objs;
          if (!objsPool.has(depObjId)) {
            objsPool.get(depObjId, continueCallback);
            return i;
          }
        }
      }
      i++;
      if (i === argsArrayLen) {
        return i;
      }
      if (chunkOperations && ++steps > EXECUTION_STEPS) {
        if (Date.now() > endTime) {
          continueCallback();
          return i;
        }
        steps = 0;
      }
    }
  }
  #restoreInitialState() {
    while (this.stateStack.length || this.inSMaskMode) {
      this.restore();
    }
    this.current.activeSMask = null;
    this.ctx.restore();
    if (this.transparentCanvas) {
      this.ctx = this.compositeCtx;
      this.ctx.save();
      this.ctx.setTransform(1, 0, 0, 1, 0, 0);
      this.ctx.drawImage(this.transparentCanvas, 0, 0);
      this.ctx.restore();
      this.transparentCanvas = null;
    }
  }
  endDrawing() {
    this.#restoreInitialState();
    this.cachedCanvases.clear();
    this.cachedPatterns.clear();
    for (const cache of this._cachedBitmapsMap.values()) {
      for (const canvas of cache.values()) {
        if (typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement) {
          canvas.width = canvas.height = 0;
        }
      }
      cache.clear();
    }
    this._cachedBitmapsMap.clear();
    this.#drawFilter();
  }
  #drawFilter() {
    if (this.pageColors) {
      const hcmFilterId = this.filterFactory.addHCMFilter(this.pageColors.foreground, this.pageColors.background);
      if (hcmFilterId !== "none") {
        const savedFilter = this.ctx.filter;
        this.ctx.filter = hcmFilterId;
        this.ctx.drawImage(this.ctx.canvas, 0, 0);
        this.ctx.filter = savedFilter;
      }
    }
  }
  _scaleImage(img, inverseTransform) {
    const width = img.width;
    const height = img.height;
    let widthScale = Math.max(Math.hypot(inverseTransform[0], inverseTransform[1]), 1);
    let heightScale = Math.max(Math.hypot(inverseTransform[2], inverseTransform[3]), 1);
    let paintWidth = width,
      paintHeight = height;
    let tmpCanvasId = "prescale1";
    let tmpCanvas, tmpCtx;
    while (widthScale > 2 && paintWidth > 1 || heightScale > 2 && paintHeight > 1) {
      let newWidth = paintWidth,
        newHeight = paintHeight;
      if (widthScale > 2 && paintWidth > 1) {
        newWidth = paintWidth >= 16384 ? Math.floor(paintWidth / 2) - 1 || 1 : Math.ceil(paintWidth / 2);
        widthScale /= paintWidth / newWidth;
      }
      if (heightScale > 2 && paintHeight > 1) {
        newHeight = paintHeight >= 16384 ? Math.floor(paintHeight / 2) - 1 || 1 : Math.ceil(paintHeight) / 2;
        heightScale /= paintHeight / newHeight;
      }
      tmpCanvas = this.cachedCanvases.getCanvas(tmpCanvasId, newWidth, newHeight);
      tmpCtx = tmpCanvas.context;
      tmpCtx.clearRect(0, 0, newWidth, newHeight);
      tmpCtx.drawImage(img, 0, 0, paintWidth, paintHeight, 0, 0, newWidth, newHeight);
      img = tmpCanvas.canvas;
      paintWidth = newWidth;
      paintHeight = newHeight;
      tmpCanvasId = tmpCanvasId === "prescale1" ? "prescale2" : "prescale1";
    }
    return {
      img,
      paintWidth,
      paintHeight
    };
  }
  _createMaskCanvas(img) {
    const ctx = this.ctx;
    const {
      width,
      height
    } = img;
    const fillColor = this.current.fillColor;
    const isPatternFill = this.current.patternFill;
    const currentTransform = getCurrentTransform(ctx);
    let cache, cacheKey, scaled, maskCanvas;
    if ((img.bitmap || img.data) && img.count > 1) {
      const mainKey = img.bitmap || img.data.buffer;
      cacheKey = JSON.stringify(isPatternFill ? currentTransform : [currentTransform.slice(0, 4), fillColor]);
      cache = this._cachedBitmapsMap.get(mainKey);
      if (!cache) {
        cache = new Map();
        this._cachedBitmapsMap.set(mainKey, cache);
      }
      const cachedImage = cache.get(cacheKey);
      if (cachedImage && !isPatternFill) {
        const offsetX = Math.round(Math.min(currentTransform[0], currentTransform[2]) + currentTransform[4]);
        const offsetY = Math.round(Math.min(currentTransform[1], currentTransform[3]) + currentTransform[5]);
        return {
          canvas: cachedImage,
          offsetX,
          offsetY
        };
      }
      scaled = cachedImage;
    }
    if (!scaled) {
      maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height);
      putBinaryImageMask(maskCanvas.context, img);
    }
    let maskToCanvas = Util.transform(currentTransform, [1 / width, 0, 0, -1 / height, 0, 0]);
    maskToCanvas = Util.transform(maskToCanvas, [1, 0, 0, 1, 0, -height]);
    const [minX, minY, maxX, maxY] = Util.getAxialAlignedBoundingBox([0, 0, width, height], maskToCanvas);
    const drawnWidth = Math.round(maxX - minX) || 1;
    const drawnHeight = Math.round(maxY - minY) || 1;
    const fillCanvas = this.cachedCanvases.getCanvas("fillCanvas", drawnWidth, drawnHeight);
    const fillCtx = fillCanvas.context;
    const offsetX = minX;
    const offsetY = minY;
    fillCtx.translate(-offsetX, -offsetY);
    fillCtx.transform(...maskToCanvas);
    if (!scaled) {
      scaled = this._scaleImage(maskCanvas.canvas, getCurrentTransformInverse(fillCtx));
      scaled = scaled.img;
      if (cache && isPatternFill) {
        cache.set(cacheKey, scaled);
      }
    }
    fillCtx.imageSmoothingEnabled = getImageSmoothingEnabled(getCurrentTransform(fillCtx), img.interpolate);
    drawImageAtIntegerCoords(fillCtx, scaled, 0, 0, scaled.width, scaled.height, 0, 0, width, height);
    fillCtx.globalCompositeOperation = "source-in";
    const inverse = Util.transform(getCurrentTransformInverse(fillCtx), [1, 0, 0, 1, -offsetX, -offsetY]);
    fillCtx.fillStyle = isPatternFill ? fillColor.getPattern(ctx, this, inverse, PathType.FILL) : fillColor;
    fillCtx.fillRect(0, 0, width, height);
    if (cache && !isPatternFill) {
      this.cachedCanvases.delete("fillCanvas");
      cache.set(cacheKey, fillCanvas.canvas);
    }
    return {
      canvas: fillCanvas.canvas,
      offsetX: Math.round(offsetX),
      offsetY: Math.round(offsetY)
    };
  }
  setLineWidth(width) {
    if (width !== this.current.lineWidth) {
      this._cachedScaleForStroking[0] = -1;
    }
    this.current.lineWidth = width;
    this.ctx.lineWidth = width;
  }
  setLineCap(style) {
    this.ctx.lineCap = LINE_CAP_STYLES[style];
  }
  setLineJoin(style) {
    this.ctx.lineJoin = LINE_JOIN_STYLES[style];
  }
  setMiterLimit(limit) {
    this.ctx.miterLimit = limit;
  }
  setDash(dashArray, dashPhase) {
    const ctx = this.ctx;
    if (ctx.setLineDash !== undefined) {
      ctx.setLineDash(dashArray);
      ctx.lineDashOffset = dashPhase;
    }
  }
  setRenderingIntent(intent) {}
  setFlatness(flatness) {}
  setGState(states) {
    for (const [key, value] of states) {
      switch (key) {
        case "LW":
          this.setLineWidth(value);
          break;
        case "LC":
          this.setLineCap(value);
          break;
        case "LJ":
          this.setLineJoin(value);
          break;
        case "ML":
          this.setMiterLimit(value);
          break;
        case "D":
          this.setDash(value[0], value[1]);
          break;
        case "RI":
          this.setRenderingIntent(value);
          break;
        case "FL":
          this.setFlatness(value);
          break;
        case "Font":
          this.setFont(value[0], value[1]);
          break;
        case "CA":
          this.current.strokeAlpha = value;
          break;
        case "ca":
          this.current.fillAlpha = value;
          this.ctx.globalAlpha = value;
          break;
        case "BM":
          this.ctx.globalCompositeOperation = value;
          break;
        case "SMask":
          this.current.activeSMask = value ? this.tempSMask : null;
          this.tempSMask = null;
          this.checkSMaskState();
          break;
        case "TR":
          this.ctx.filter = this.current.transferMaps = this.filterFactory.addFilter(value);
          break;
      }
    }
  }
  get inSMaskMode() {
    return !!this.suspendedCtx;
  }
  checkSMaskState() {
    const inSMaskMode = this.inSMaskMode;
    if (this.current.activeSMask && !inSMaskMode) {
      this.beginSMaskMode();
    } else if (!this.current.activeSMask && inSMaskMode) {
      this.endSMaskMode();
    }
  }
  beginSMaskMode() {
    if (this.inSMaskMode) {
      throw new Error("beginSMaskMode called while already in smask mode");
    }
    const drawnWidth = this.ctx.canvas.width;
    const drawnHeight = this.ctx.canvas.height;
    const cacheId = "smaskGroupAt" + this.groupLevel;
    const scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight);
    this.suspendedCtx = this.ctx;
    this.ctx = scratchCanvas.context;
    const ctx = this.ctx;
    ctx.setTransform(...getCurrentTransform(this.suspendedCtx));
    copyCtxState(this.suspendedCtx, ctx);
    mirrorContextOperations(ctx, this.suspendedCtx);
    this.setGState([["BM", "source-over"], ["ca", 1], ["CA", 1]]);
  }
  endSMaskMode() {
    if (!this.inSMaskMode) {
      throw new Error("endSMaskMode called while not in smask mode");
    }
    this.ctx._removeMirroring();
    copyCtxState(this.ctx, this.suspendedCtx);
    this.ctx = this.suspendedCtx;
    this.suspendedCtx = null;
  }
  compose(dirtyBox) {
    if (!this.current.activeSMask) {
      return;
    }
    if (!dirtyBox) {
      dirtyBox = [0, 0, this.ctx.canvas.width, this.ctx.canvas.height];
    } else {
      dirtyBox[0] = Math.floor(dirtyBox[0]);
      dirtyBox[1] = Math.floor(dirtyBox[1]);
      dirtyBox[2] = Math.ceil(dirtyBox[2]);
      dirtyBox[3] = Math.ceil(dirtyBox[3]);
    }
    const smask = this.current.activeSMask;
    const suspendedCtx = this.suspendedCtx;
    this.composeSMask(suspendedCtx, smask, this.ctx, dirtyBox);
    this.ctx.save();
    this.ctx.setTransform(1, 0, 0, 1, 0, 0);
    this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
    this.ctx.restore();
  }
  composeSMask(ctx, smask, layerCtx, layerBox) {
    const layerOffsetX = layerBox[0];
    const layerOffsetY = layerBox[1];
    const layerWidth = layerBox[2] - layerOffsetX;
    const layerHeight = layerBox[3] - layerOffsetY;
    if (layerWidth === 0 || layerHeight === 0) {
      return;
    }
    this.genericComposeSMask(smask.context, layerCtx, layerWidth, layerHeight, smask.subtype, smask.backdrop, smask.transferMap, layerOffsetX, layerOffsetY, smask.offsetX, smask.offsetY);
    ctx.save();
    ctx.globalAlpha = 1;
    ctx.globalCompositeOperation = "source-over";
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.drawImage(layerCtx.canvas, 0, 0);
    ctx.restore();
  }
  genericComposeSMask(maskCtx, layerCtx, width, height, subtype, backdrop, transferMap, layerOffsetX, layerOffsetY, maskOffsetX, maskOffsetY) {
    let maskCanvas = maskCtx.canvas;
    let maskX = layerOffsetX - maskOffsetX;
    let maskY = layerOffsetY - maskOffsetY;
    if (backdrop) {
      if (maskX < 0 || maskY < 0 || maskX + width > maskCanvas.width || maskY + height > maskCanvas.height) {
        const canvas = this.cachedCanvases.getCanvas("maskExtension", width, height);
        const ctx = canvas.context;
        ctx.drawImage(maskCanvas, -maskX, -maskY);
        if (backdrop.some(c => c !== 0)) {
          ctx.globalCompositeOperation = "destination-atop";
          ctx.fillStyle = Util.makeHexColor(...backdrop);
          ctx.fillRect(0, 0, width, height);
          ctx.globalCompositeOperation = "source-over";
        }
        maskCanvas = canvas.canvas;
        maskX = maskY = 0;
      } else if (backdrop.some(c => c !== 0)) {
        maskCtx.save();
        maskCtx.globalAlpha = 1;
        maskCtx.setTransform(1, 0, 0, 1, 0, 0);
        const clip = new Path2D();
        clip.rect(maskX, maskY, width, height);
        maskCtx.clip(clip);
        maskCtx.globalCompositeOperation = "destination-atop";
        maskCtx.fillStyle = Util.makeHexColor(...backdrop);
        maskCtx.fillRect(maskX, maskY, width, height);
        maskCtx.restore();
      }
    }
    layerCtx.save();
    layerCtx.globalAlpha = 1;
    layerCtx.setTransform(1, 0, 0, 1, 0, 0);
    if (subtype === "Alpha" && transferMap) {
      layerCtx.filter = this.filterFactory.addAlphaFilter(transferMap);
    } else if (subtype === "Luminosity") {
      layerCtx.filter = this.filterFactory.addLuminosityFilter(transferMap);
    }
    const clip = new Path2D();
    clip.rect(layerOffsetX, layerOffsetY, width, height);
    layerCtx.clip(clip);
    layerCtx.globalCompositeOperation = "destination-in";
    layerCtx.drawImage(maskCanvas, maskX, maskY, width, height, layerOffsetX, layerOffsetY, width, height);
    layerCtx.restore();
  }
  save() {
    if (this.inSMaskMode) {
      copyCtxState(this.ctx, this.suspendedCtx);
      this.suspendedCtx.save();
    } else {
      this.ctx.save();
    }
    const old = this.current;
    this.stateStack.push(old);
    this.current = old.clone();
  }
  restore() {
    if (this.stateStack.length === 0 && this.inSMaskMode) {
      this.endSMaskMode();
    }
    if (this.stateStack.length !== 0) {
      this.current = this.stateStack.pop();
      if (this.inSMaskMode) {
        this.suspendedCtx.restore();
        copyCtxState(this.suspendedCtx, this.ctx);
      } else {
        this.ctx.restore();
      }
      this.checkSMaskState();
      this.pendingClip = null;
      this._cachedScaleForStroking[0] = -1;
      this._cachedGetSinglePixelWidth = null;
    }
  }
  transform(a, b, c, d, e, f) {
    this.ctx.transform(a, b, c, d, e, f);
    this._cachedScaleForStroking[0] = -1;
    this._cachedGetSinglePixelWidth = null;
  }
  constructPath(ops, args, minMax) {
    const ctx = this.ctx;
    const current = this.current;
    let x = current.x,
      y = current.y;
    let startX, startY;
    const currentTransform = getCurrentTransform(ctx);
    const isScalingMatrix = currentTransform[0] === 0 && currentTransform[3] === 0 || currentTransform[1] === 0 && currentTransform[2] === 0;
    const minMaxForBezier = isScalingMatrix ? minMax.slice(0) : null;
    for (let i = 0, j = 0, ii = ops.length; i < ii; i++) {
      switch (ops[i] | 0) {
        case OPS.rectangle:
          x = args[j++];
          y = args[j++];
          const width = args[j++];
          const height = args[j++];
          const xw = x + width;
          const yh = y + height;
          ctx.moveTo(x, y);
          if (width === 0 || height === 0) {
            ctx.lineTo(xw, yh);
          } else {
            ctx.lineTo(xw, y);
            ctx.lineTo(xw, yh);
            ctx.lineTo(x, yh);
          }
          if (!isScalingMatrix) {
            current.updateRectMinMax(currentTransform, [x, y, xw, yh]);
          }
          ctx.closePath();
          break;
        case OPS.moveTo:
          x = args[j++];
          y = args[j++];
          ctx.moveTo(x, y);
          if (!isScalingMatrix) {
            current.updatePathMinMax(currentTransform, x, y);
          }
          break;
        case OPS.lineTo:
          x = args[j++];
          y = args[j++];
          ctx.lineTo(x, y);
          if (!isScalingMatrix) {
            current.updatePathMinMax(currentTransform, x, y);
          }
          break;
        case OPS.curveTo:
          startX = x;
          startY = y;
          x = args[j + 4];
          y = args[j + 5];
          ctx.bezierCurveTo(args[j], args[j + 1], args[j + 2], args[j + 3], x, y);
          current.updateCurvePathMinMax(currentTransform, startX, startY, args[j], args[j + 1], args[j + 2], args[j + 3], x, y, minMaxForBezier);
          j += 6;
          break;
        case OPS.curveTo2:
          startX = x;
          startY = y;
          ctx.bezierCurveTo(x, y, args[j], args[j + 1], args[j + 2], args[j + 3]);
          current.updateCurvePathMinMax(currentTransform, startX, startY, x, y, args[j], args[j + 1], args[j + 2], args[j + 3], minMaxForBezier);
          x = args[j + 2];
          y = args[j + 3];
          j += 4;
          break;
        case OPS.curveTo3:
          startX = x;
          startY = y;
          x = args[j + 2];
          y = args[j + 3];
          ctx.bezierCurveTo(args[j], args[j + 1], x, y, x, y);
          current.updateCurvePathMinMax(currentTransform, startX, startY, args[j], args[j + 1], x, y, x, y, minMaxForBezier);
          j += 4;
          break;
        case OPS.closePath:
          ctx.closePath();
          break;
      }
    }
    if (isScalingMatrix) {
      current.updateScalingPathMinMax(currentTransform, minMaxForBezier);
    }
    current.setCurrentPoint(x, y);
  }
  closePath() {
    this.ctx.closePath();
  }
  stroke(consumePath = true) {
    const ctx = this.ctx;
    const strokeColor = this.current.strokeColor;
    ctx.globalAlpha = this.current.strokeAlpha;
    if (this.contentVisible) {
      if (typeof strokeColor === "object" && strokeColor?.getPattern) {
        ctx.save();
        ctx.strokeStyle = strokeColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.STROKE);
        this.rescaleAndStroke(false);
        ctx.restore();
      } else {
        this.rescaleAndStroke(true);
      }
    }
    if (consumePath) {
      this.consumePath(this.current.getClippedPathBoundingBox());
    }
    ctx.globalAlpha = this.current.fillAlpha;
  }
  closeStroke() {
    this.closePath();
    this.stroke();
  }
  fill(consumePath = true) {
    const ctx = this.ctx;
    const fillColor = this.current.fillColor;
    const isPatternFill = this.current.patternFill;
    let needRestore = false;
    if (isPatternFill) {
      ctx.save();
      ctx.fillStyle = fillColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.FILL);
      needRestore = true;
    }
    const intersect = this.current.getClippedPathBoundingBox();
    if (this.contentVisible && intersect !== null) {
      if (this.pendingEOFill) {
        ctx.fill("evenodd");
        this.pendingEOFill = false;
      } else {
        ctx.fill();
      }
    }
    if (needRestore) {
      ctx.restore();
    }
    if (consumePath) {
      this.consumePath(intersect);
    }
  }
  eoFill() {
    this.pendingEOFill = true;
    this.fill();
  }
  fillStroke() {
    this.fill(false);
    this.stroke(false);
    this.consumePath();
  }
  eoFillStroke() {
    this.pendingEOFill = true;
    this.fillStroke();
  }
  closeFillStroke() {
    this.closePath();
    this.fillStroke();
  }
  closeEOFillStroke() {
    this.pendingEOFill = true;
    this.closePath();
    this.fillStroke();
  }
  endPath() {
    this.consumePath();
  }
  clip() {
    this.pendingClip = NORMAL_CLIP;
  }
  eoClip() {
    this.pendingClip = EO_CLIP;
  }
  beginText() {
    this.current.textMatrix = IDENTITY_MATRIX;
    this.current.textMatrixScale = 1;
    this.current.x = this.current.lineX = 0;
    this.current.y = this.current.lineY = 0;
  }
  endText() {
    const paths = this.pendingTextPaths;
    const ctx = this.ctx;
    if (paths === undefined) {
      ctx.beginPath();
      return;
    }
    ctx.save();
    ctx.beginPath();
    for (const path of paths) {
      ctx.setTransform(...path.transform);
      ctx.translate(path.x, path.y);
      path.addToPath(ctx, path.fontSize);
    }
    ctx.restore();
    ctx.clip();
    ctx.beginPath();
    delete this.pendingTextPaths;
  }
  setCharSpacing(spacing) {
    this.current.charSpacing = spacing;
  }
  setWordSpacing(spacing) {
    this.current.wordSpacing = spacing;
  }
  setHScale(scale) {
    this.current.textHScale = scale / 100;
  }
  setLeading(leading) {
    this.current.leading = -leading;
  }
  setFont(fontRefName, size) {
    const fontObj = this.commonObjs.get(fontRefName);
    const current = this.current;
    if (!fontObj) {
      throw new Error(`Can't find font for ${fontRefName}`);
    }
    current.fontMatrix = fontObj.fontMatrix || FONT_IDENTITY_MATRIX;
    if (current.fontMatrix[0] === 0 || current.fontMatrix[3] === 0) {
      warn("Invalid font matrix for font " + fontRefName);
    }
    if (size < 0) {
      size = -size;
      current.fontDirection = -1;
    } else {
      current.fontDirection = 1;
    }
    this.current.font = fontObj;
    this.current.fontSize = size;
    if (fontObj.isType3Font) {
      return;
    }
    const name = fontObj.loadedName || "sans-serif";
    const typeface = fontObj.systemFontInfo?.css || `"${name}", ${fontObj.fallbackName}`;
    let bold = "normal";
    if (fontObj.black) {
      bold = "900";
    } else if (fontObj.bold) {
      bold = "bold";
    }
    const italic = fontObj.italic ? "italic" : "normal";
    let browserFontSize = size;
    if (size < MIN_FONT_SIZE) {
      browserFontSize = MIN_FONT_SIZE;
    } else if (size > MAX_FONT_SIZE) {
      browserFontSize = MAX_FONT_SIZE;
    }
    this.current.fontSizeScale = size / browserFontSize;
    this.ctx.font = `${italic} ${bold} ${browserFontSize}px ${typeface}`;
  }
  setTextRenderingMode(mode) {
    this.current.textRenderingMode = mode;
  }
  setTextRise(rise) {
    this.current.textRise = rise;
  }
  moveText(x, y) {
    this.current.x = this.current.lineX += x;
    this.current.y = this.current.lineY += y;
  }
  setLeadingMoveText(x, y) {
    this.setLeading(-y);
    this.moveText(x, y);
  }
  setTextMatrix(a, b, c, d, e, f) {
    this.current.textMatrix = [a, b, c, d, e, f];
    this.current.textMatrixScale = Math.hypot(a, b);
    this.current.x = this.current.lineX = 0;
    this.current.y = this.current.lineY = 0;
  }
  nextLine() {
    this.moveText(0, this.current.leading);
  }
  paintChar(character, x, y, patternTransform) {
    const ctx = this.ctx;
    const current = this.current;
    const font = current.font;
    const textRenderingMode = current.textRenderingMode;
    const fontSize = current.fontSize / current.fontSizeScale;
    const fillStrokeMode = textRenderingMode & TextRenderingMode.FILL_STROKE_MASK;
    const isAddToPathSet = !!(textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG);
    const patternFill = current.patternFill && !font.missingFile;
    let addToPath;
    if (font.disableFontFace || isAddToPathSet || patternFill) {
      addToPath = font.getPathGenerator(this.commonObjs, character);
    }
    if (font.disableFontFace || patternFill) {
      ctx.save();
      ctx.translate(x, y);
      ctx.beginPath();
      addToPath(ctx, fontSize);
      if (patternTransform) {
        ctx.setTransform(...patternTransform);
      }
      if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
        ctx.fill();
      }
      if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
        ctx.stroke();
      }
      ctx.restore();
    } else {
      if (fillStrokeMode === TextRenderingMode.FILL || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
        ctx.fillText(character, x, y);
      }
      if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
        ctx.strokeText(character, x, y);
      }
    }
    if (isAddToPathSet) {
      const paths = this.pendingTextPaths ||= [];
      paths.push({
        transform: getCurrentTransform(ctx),
        x,
        y,
        fontSize,
        addToPath
      });
    }
  }
  get isFontSubpixelAAEnabled() {
    const {
      context: ctx
    } = this.cachedCanvases.getCanvas("isFontSubpixelAAEnabled", 10, 10);
    ctx.scale(1.5, 1);
    ctx.fillText("I", 0, 10);
    const data = ctx.getImageData(0, 0, 10, 10).data;
    let enabled = false;
    for (let i = 3; i < data.length; i += 4) {
      if (data[i] > 0 && data[i] < 255) {
        enabled = true;
        break;
      }
    }
    return shadow(this, "isFontSubpixelAAEnabled", enabled);
  }
  showText(glyphs) {
    const current = this.current;
    const font = current.font;
    if (font.isType3Font) {
      return this.showType3Text(glyphs);
    }
    const fontSize = current.fontSize;
    if (fontSize === 0) {
      return undefined;
    }
    const ctx = this.ctx;
    const fontSizeScale = current.fontSizeScale;
    const charSpacing = current.charSpacing;
    const wordSpacing = current.wordSpacing;
    const fontDirection = current.fontDirection;
    const textHScale = current.textHScale * fontDirection;
    const glyphsLength = glyphs.length;
    const vertical = font.vertical;
    const spacingDir = vertical ? 1 : -1;
    const defaultVMetrics = font.defaultVMetrics;
    const widthAdvanceScale = fontSize * current.fontMatrix[0];
    const simpleFillText = current.textRenderingMode === TextRenderingMode.FILL && !font.disableFontFace && !current.patternFill;
    ctx.save();
    ctx.transform(...current.textMatrix);
    ctx.translate(current.x, current.y + current.textRise);
    if (fontDirection > 0) {
      ctx.scale(textHScale, -1);
    } else {
      ctx.scale(textHScale, 1);
    }
    let patternTransform;
    if (current.patternFill) {
      ctx.save();
      const pattern = current.fillColor.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.FILL);
      patternTransform = getCurrentTransform(ctx);
      ctx.restore();
      ctx.fillStyle = pattern;
    }
    let lineWidth = current.lineWidth;
    const scale = current.textMatrixScale;
    if (scale === 0 || lineWidth === 0) {
      const fillStrokeMode = current.textRenderingMode & TextRenderingMode.FILL_STROKE_MASK;
      if (fillStrokeMode === TextRenderingMode.STROKE || fillStrokeMode === TextRenderingMode.FILL_STROKE) {
        lineWidth = this.getSinglePixelWidth();
      }
    } else {
      lineWidth /= scale;
    }
    if (fontSizeScale !== 1.0) {
      ctx.scale(fontSizeScale, fontSizeScale);
      lineWidth /= fontSizeScale;
    }
    ctx.lineWidth = lineWidth;
    if (font.isInvalidPDFjsFont) {
      const chars = [];
      let width = 0;
      for (const glyph of glyphs) {
        chars.push(glyph.unicode);
        width += glyph.width;
      }
      ctx.fillText(chars.join(""), 0, 0);
      current.x += width * widthAdvanceScale * textHScale;
      ctx.restore();
      this.compose();
      return undefined;
    }
    let x = 0,
      i;
    for (i = 0; i < glyphsLength; ++i) {
      const glyph = glyphs[i];
      if (typeof glyph === "number") {
        x += spacingDir * glyph * fontSize / 1000;
        continue;
      }
      let restoreNeeded = false;
      const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
      const character = glyph.fontChar;
      const accent = glyph.accent;
      let scaledX, scaledY;
      let width = glyph.width;
      if (vertical) {
        const vmetric = glyph.vmetric || defaultVMetrics;
        const vx = -(glyph.vmetric ? vmetric[1] : width * 0.5) * widthAdvanceScale;
        const vy = vmetric[2] * widthAdvanceScale;
        width = vmetric ? -vmetric[0] : width;
        scaledX = vx / fontSizeScale;
        scaledY = (x + vy) / fontSizeScale;
      } else {
        scaledX = x / fontSizeScale;
        scaledY = 0;
      }
      if (font.remeasure && width > 0) {
        const measuredWidth = ctx.measureText(character).width * 1000 / fontSize * fontSizeScale;
        if (width < measuredWidth && this.isFontSubpixelAAEnabled) {
          const characterScaleX = width / measuredWidth;
          restoreNeeded = true;
          ctx.save();
          ctx.scale(characterScaleX, 1);
          scaledX /= characterScaleX;
        } else if (width !== measuredWidth) {
          scaledX += (width - measuredWidth) / 2000 * fontSize / fontSizeScale;
        }
      }
      if (this.contentVisible && (glyph.isInFont || font.missingFile)) {
        if (simpleFillText && !accent) {
          ctx.fillText(character, scaledX, scaledY);
        } else {
          this.paintChar(character, scaledX, scaledY, patternTransform);
          if (accent) {
            const scaledAccentX = scaledX + fontSize * accent.offset.x / fontSizeScale;
            const scaledAccentY = scaledY - fontSize * accent.offset.y / fontSizeScale;
            this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY, patternTransform);
          }
        }
      }
      const charWidth = vertical ? width * widthAdvanceScale - spacing * fontDirection : width * widthAdvanceScale + spacing * fontDirection;
      x += charWidth;
      if (restoreNeeded) {
        ctx.restore();
      }
    }
    if (vertical) {
      current.y -= x;
    } else {
      current.x += x * textHScale;
    }
    ctx.restore();
    this.compose();
    return undefined;
  }
  showType3Text(glyphs) {
    const ctx = this.ctx;
    const current = this.current;
    const font = current.font;
    const fontSize = current.fontSize;
    const fontDirection = current.fontDirection;
    const spacingDir = font.vertical ? 1 : -1;
    const charSpacing = current.charSpacing;
    const wordSpacing = current.wordSpacing;
    const textHScale = current.textHScale * fontDirection;
    const fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX;
    const glyphsLength = glyphs.length;
    const isTextInvisible = current.textRenderingMode === TextRenderingMode.INVISIBLE;
    let i, glyph, width, spacingLength;
    if (isTextInvisible || fontSize === 0) {
      return;
    }
    this._cachedScaleForStroking[0] = -1;
    this._cachedGetSinglePixelWidth = null;
    ctx.save();
    ctx.transform(...current.textMatrix);
    ctx.translate(current.x, current.y);
    ctx.scale(textHScale, fontDirection);
    for (i = 0; i < glyphsLength; ++i) {
      glyph = glyphs[i];
      if (typeof glyph === "number") {
        spacingLength = spacingDir * glyph * fontSize / 1000;
        this.ctx.translate(spacingLength, 0);
        current.x += spacingLength * textHScale;
        continue;
      }
      const spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
      const operatorList = font.charProcOperatorList[glyph.operatorListId];
      if (!operatorList) {
        warn(`Type3 character "${glyph.operatorListId}" is not available.`);
        continue;
      }
      if (this.contentVisible) {
        this.processingType3 = glyph;
        this.save();
        ctx.scale(fontSize, fontSize);
        ctx.transform(...fontMatrix);
        this.executeOperatorList(operatorList);
        this.restore();
      }
      const transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
      width = transformed[0] * fontSize + spacing;
      ctx.translate(width, 0);
      current.x += width * textHScale;
    }
    ctx.restore();
    this.processingType3 = null;
  }
  setCharWidth(xWidth, yWidth) {}
  setCharWidthAndBounds(xWidth, yWidth, llx, lly, urx, ury) {
    this.ctx.rect(llx, lly, urx - llx, ury - lly);
    this.ctx.clip();
    this.endPath();
  }
  getColorN_Pattern(IR) {
    let pattern;
    if (IR[0] === "TilingPattern") {
      const color = IR[1];
      const baseTransform = this.baseTransform || getCurrentTransform(this.ctx);
      const canvasGraphicsFactory = {
        createCanvasGraphics: ctx => new CanvasGraphics(ctx, this.commonObjs, this.objs, this.canvasFactory, this.filterFactory, {
          optionalContentConfig: this.optionalContentConfig,
          markedContentStack: this.markedContentStack
        })
      };
      pattern = new TilingPattern(IR, color, this.ctx, canvasGraphicsFactory, baseTransform);
    } else {
      pattern = this._getPattern(IR[1], IR[2]);
    }
    return pattern;
  }
  setStrokeColorN() {
    this.current.strokeColor = this.getColorN_Pattern(arguments);
  }
  setFillColorN() {
    this.current.fillColor = this.getColorN_Pattern(arguments);
    this.current.patternFill = true;
  }
  setStrokeRGBColor(r, g, b) {
    this.ctx.strokeStyle = this.current.strokeColor = Util.makeHexColor(r, g, b);
  }
  setStrokeTransparent() {
    this.ctx.strokeStyle = this.current.strokeColor = "transparent";
  }
  setFillRGBColor(r, g, b) {
    this.ctx.fillStyle = this.current.fillColor = Util.makeHexColor(r, g, b);
    this.current.patternFill = false;
  }
  setFillTransparent() {
    this.ctx.fillStyle = this.current.fillColor = "transparent";
    this.current.patternFill = false;
  }
  _getPattern(objId, matrix = null) {
    let pattern;
    if (this.cachedPatterns.has(objId)) {
      pattern = this.cachedPatterns.get(objId);
    } else {
      pattern = getShadingPattern(this.getObject(objId));
      this.cachedPatterns.set(objId, pattern);
    }
    if (matrix) {
      pattern.matrix = matrix;
    }
    return pattern;
  }
  shadingFill(objId) {
    if (!this.contentVisible) {
      return;
    }
    const ctx = this.ctx;
    this.save();
    const pattern = this._getPattern(objId);
    ctx.fillStyle = pattern.getPattern(ctx, this, getCurrentTransformInverse(ctx), PathType.SHADING);
    const inv = getCurrentTransformInverse(ctx);
    if (inv) {
      const {
        width,
        height
      } = ctx.canvas;
      const [x0, y0, x1, y1] = Util.getAxialAlignedBoundingBox([0, 0, width, height], inv);
      this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
    } else {
      this.ctx.fillRect(-1e10, -1e10, 2e10, 2e10);
    }
    this.compose(this.current.getClippedPathBoundingBox());
    this.restore();
  }
  beginInlineImage() {
    unreachable("Should not call beginInlineImage");
  }
  beginImageData() {
    unreachable("Should not call beginImageData");
  }
  paintFormXObjectBegin(matrix, bbox) {
    if (!this.contentVisible) {
      return;
    }
    this.save();
    this.baseTransformStack.push(this.baseTransform);
    if (matrix) {
      this.transform(...matrix);
    }
    this.baseTransform = getCurrentTransform(this.ctx);
    if (bbox) {
      const width = bbox[2] - bbox[0];
      const height = bbox[3] - bbox[1];
      this.ctx.rect(bbox[0], bbox[1], width, height);
      this.current.updateRectMinMax(getCurrentTransform(this.ctx), bbox);
      this.clip();
      this.endPath();
    }
  }
  paintFormXObjectEnd() {
    if (!this.contentVisible) {
      return;
    }
    this.restore();
    this.baseTransform = this.baseTransformStack.pop();
  }
  beginGroup(group) {
    if (!this.contentVisible) {
      return;
    }
    this.save();
    if (this.inSMaskMode) {
      this.endSMaskMode();
      this.current.activeSMask = null;
    }
    const currentCtx = this.ctx;
    if (!group.isolated) {
      info("TODO: Support non-isolated groups.");
    }
    if (group.knockout) {
      warn("Knockout groups not supported.");
    }
    const currentTransform = getCurrentTransform(currentCtx);
    if (group.matrix) {
      currentCtx.transform(...group.matrix);
    }
    if (!group.bbox) {
      throw new Error("Bounding box is required.");
    }
    let bounds = Util.getAxialAlignedBoundingBox(group.bbox, getCurrentTransform(currentCtx));
    const canvasBounds = [0, 0, currentCtx.canvas.width, currentCtx.canvas.height];
    bounds = Util.intersect(bounds, canvasBounds) || [0, 0, 0, 0];
    const offsetX = Math.floor(bounds[0]);
    const offsetY = Math.floor(bounds[1]);
    const drawnWidth = Math.max(Math.ceil(bounds[2]) - offsetX, 1);
    const drawnHeight = Math.max(Math.ceil(bounds[3]) - offsetY, 1);
    this.current.startNewPathAndClipBox([0, 0, drawnWidth, drawnHeight]);
    let cacheId = "groupAt" + this.groupLevel;
    if (group.smask) {
      cacheId += "_smask_" + this.smaskCounter++ % 2;
    }
    const scratchCanvas = this.cachedCanvases.getCanvas(cacheId, drawnWidth, drawnHeight);
    const groupCtx = scratchCanvas.context;
    groupCtx.translate(-offsetX, -offsetY);
    groupCtx.transform(...currentTransform);
    if (group.smask) {
      this.smaskStack.push({
        canvas: scratchCanvas.canvas,
        context: groupCtx,
        offsetX,
        offsetY,
        subtype: group.smask.subtype,
        backdrop: group.smask.backdrop,
        transferMap: group.smask.transferMap || null,
        startTransformInverse: null
      });
    } else {
      currentCtx.setTransform(1, 0, 0, 1, 0, 0);
      currentCtx.translate(offsetX, offsetY);
      currentCtx.save();
    }
    copyCtxState(currentCtx, groupCtx);
    this.ctx = groupCtx;
    this.setGState([["BM", "source-over"], ["ca", 1], ["CA", 1]]);
    this.groupStack.push(currentCtx);
    this.groupLevel++;
  }
  endGroup(group) {
    if (!this.contentVisible) {
      return;
    }
    this.groupLevel--;
    const groupCtx = this.ctx;
    const ctx = this.groupStack.pop();
    this.ctx = ctx;
    this.ctx.imageSmoothingEnabled = false;
    if (group.smask) {
      this.tempSMask = this.smaskStack.pop();
      this.restore();
    } else {
      this.ctx.restore();
      const currentMtx = getCurrentTransform(this.ctx);
      this.restore();
      this.ctx.save();
      this.ctx.setTransform(...currentMtx);
      const dirtyBox = Util.getAxialAlignedBoundingBox([0, 0, groupCtx.canvas.width, groupCtx.canvas.height], currentMtx);
      this.ctx.drawImage(groupCtx.canvas, 0, 0);
      this.ctx.restore();
      this.compose(dirtyBox);
    }
  }
  beginAnnotation(id, rect, transform, matrix, hasOwnCanvas) {
    this.#restoreInitialState();
    resetCtxToDefault(this.ctx);
    this.ctx.save();
    this.save();
    if (this.baseTransform) {
      this.ctx.setTransform(...this.baseTransform);
    }
    if (rect) {
      const width = rect[2] - rect[0];
      const height = rect[3] - rect[1];
      if (hasOwnCanvas && this.annotationCanvasMap) {
        transform = transform.slice();
        transform[4] -= rect[0];
        transform[5] -= rect[1];
        rect = rect.slice();
        rect[0] = rect[1] = 0;
        rect[2] = width;
        rect[3] = height;
        const [scaleX, scaleY] = Util.singularValueDecompose2dScale(getCurrentTransform(this.ctx));
        const {
          viewportScale
        } = this;
        const canvasWidth = Math.ceil(width * this.outputScaleX * viewportScale);
        const canvasHeight = Math.ceil(height * this.outputScaleY * viewportScale);
        this.annotationCanvas = this.canvasFactory.create(canvasWidth, canvasHeight);
        const {
          canvas,
          context
        } = this.annotationCanvas;
        this.annotationCanvasMap.set(id, canvas);
        this.annotationCanvas.savedCtx = this.ctx;
        this.ctx = context;
        this.ctx.save();
        this.ctx.setTransform(scaleX, 0, 0, -scaleY, 0, height * scaleY);
        resetCtxToDefault(this.ctx);
      } else {
        resetCtxToDefault(this.ctx);
        this.ctx.rect(rect[0], rect[1], width, height);
        this.ctx.clip();
        this.endPath();
      }
    }
    this.current = new CanvasExtraState(this.ctx.canvas.width, this.ctx.canvas.height);
    this.transform(...transform);
    this.transform(...matrix);
  }
  endAnnotation() {
    if (this.annotationCanvas) {
      this.ctx.restore();
      this.#drawFilter();
      this.ctx = this.annotationCanvas.savedCtx;
      delete this.annotationCanvas.savedCtx;
      delete this.annotationCanvas;
    }
  }
  paintImageMaskXObject(img) {
    if (!this.contentVisible) {
      return;
    }
    const count = img.count;
    img = this.getObject(img.data, img);
    img.count = count;
    const ctx = this.ctx;
    const glyph = this.processingType3;
    if (glyph) {
      if (glyph.compiled === undefined) {
        glyph.compiled = compileType3Glyph(img);
      }
      if (glyph.compiled) {
        glyph.compiled(ctx);
        return;
      }
    }
    const mask = this._createMaskCanvas(img);
    const maskCanvas = mask.canvas;
    ctx.save();
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.drawImage(maskCanvas, mask.offsetX, mask.offsetY);
    ctx.restore();
    this.compose();
  }
  paintImageMaskXObjectRepeat(img, scaleX, skewX = 0, skewY = 0, scaleY, positions) {
    if (!this.contentVisible) {
      return;
    }
    img = this.getObject(img.data, img);
    const ctx = this.ctx;
    ctx.save();
    const currentTransform = getCurrentTransform(ctx);
    ctx.transform(scaleX, skewX, skewY, scaleY, 0, 0);
    const mask = this._createMaskCanvas(img);
    ctx.setTransform(1, 0, 0, 1, mask.offsetX - currentTransform[4], mask.offsetY - currentTransform[5]);
    for (let i = 0, ii = positions.length; i < ii; i += 2) {
      const trans = Util.transform(currentTransform, [scaleX, skewX, skewY, scaleY, positions[i], positions[i + 1]]);
      const [x, y] = Util.applyTransform([0, 0], trans);
      ctx.drawImage(mask.canvas, x, y);
    }
    ctx.restore();
    this.compose();
  }
  paintImageMaskXObjectGroup(images) {
    if (!this.contentVisible) {
      return;
    }
    const ctx = this.ctx;
    const fillColor = this.current.fillColor;
    const isPatternFill = this.current.patternFill;
    for (const image of images) {
      const {
        data,
        width,
        height,
        transform
      } = image;
      const maskCanvas = this.cachedCanvases.getCanvas("maskCanvas", width, height);
      const maskCtx = maskCanvas.context;
      maskCtx.save();
      const img = this.getObject(data, image);
      putBinaryImageMask(maskCtx, img);
      maskCtx.globalCompositeOperation = "source-in";
      maskCtx.fillStyle = isPatternFill ? fillColor.getPattern(maskCtx, this, getCurrentTransformInverse(ctx), PathType.FILL) : fillColor;
      maskCtx.fillRect(0, 0, width, height);
      maskCtx.restore();
      ctx.save();
      ctx.transform(...transform);
      ctx.scale(1, -1);
      drawImageAtIntegerCoords(ctx, maskCanvas.canvas, 0, 0, width, height, 0, -1, 1, 1);
      ctx.restore();
    }
    this.compose();
  }
  paintImageXObject(objId) {
    if (!this.contentVisible) {
      return;
    }
    const imgData = this.getObject(objId);
    if (!imgData) {
      warn("Dependent image isn't ready yet");
      return;
    }
    this.paintInlineImageXObject(imgData);
  }
  paintImageXObjectRepeat(objId, scaleX, scaleY, positions) {
    if (!this.contentVisible) {
      return;
    }
    const imgData = this.getObject(objId);
    if (!imgData) {
      warn("Dependent image isn't ready yet");
      return;
    }
    const width = imgData.width;
    const height = imgData.height;
    const map = [];
    for (let i = 0, ii = positions.length; i < ii; i += 2) {
      map.push({
        transform: [scaleX, 0, 0, scaleY, positions[i], positions[i + 1]],
        x: 0,
        y: 0,
        w: width,
        h: height
      });
    }
    this.paintInlineImageXObjectGroup(imgData, map);
  }
  applyTransferMapsToCanvas(ctx) {
    if (this.current.transferMaps !== "none") {
      ctx.filter = this.current.transferMaps;
      ctx.drawImage(ctx.canvas, 0, 0);
      ctx.filter = "none";
    }
    return ctx.canvas;
  }
  applyTransferMapsToBitmap(imgData) {
    if (this.current.transferMaps === "none") {
      return imgData.bitmap;
    }
    const {
      bitmap,
      width,
      height
    } = imgData;
    const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height);
    const tmpCtx = tmpCanvas.context;
    tmpCtx.filter = this.current.transferMaps;
    tmpCtx.drawImage(bitmap, 0, 0);
    tmpCtx.filter = "none";
    return tmpCanvas.canvas;
  }
  paintInlineImageXObject(imgData) {
    if (!this.contentVisible) {
      return;
    }
    const width = imgData.width;
    const height = imgData.height;
    const ctx = this.ctx;
    this.save();
    const {
      filter
    } = ctx;
    if (filter !== "none" && filter !== "") {
      ctx.filter = "none";
    }
    ctx.scale(1 / width, -1 / height);
    let imgToPaint;
    if (imgData.bitmap) {
      imgToPaint = this.applyTransferMapsToBitmap(imgData);
    } else if (typeof HTMLElement === "function" && imgData instanceof HTMLElement || !imgData.data) {
      imgToPaint = imgData;
    } else {
      const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", width, height);
      const tmpCtx = tmpCanvas.context;
      putBinaryImageData(tmpCtx, imgData);
      imgToPaint = this.applyTransferMapsToCanvas(tmpCtx);
    }
    const scaled = this._scaleImage(imgToPaint, getCurrentTransformInverse(ctx));
    ctx.imageSmoothingEnabled = getImageSmoothingEnabled(getCurrentTransform(ctx), imgData.interpolate);
    drawImageAtIntegerCoords(ctx, scaled.img, 0, 0, scaled.paintWidth, scaled.paintHeight, 0, -height, width, height);
    this.compose();
    this.restore();
  }
  paintInlineImageXObjectGroup(imgData, map) {
    if (!this.contentVisible) {
      return;
    }
    const ctx = this.ctx;
    let imgToPaint;
    if (imgData.bitmap) {
      imgToPaint = imgData.bitmap;
    } else {
      const w = imgData.width;
      const h = imgData.height;
      const tmpCanvas = this.cachedCanvases.getCanvas("inlineImage", w, h);
      const tmpCtx = tmpCanvas.context;
      putBinaryImageData(tmpCtx, imgData);
      imgToPaint = this.applyTransferMapsToCanvas(tmpCtx);
    }
    for (const entry of map) {
      ctx.save();
      ctx.transform(...entry.transform);
      ctx.scale(1, -1);
      drawImageAtIntegerCoords(ctx, imgToPaint, entry.x, entry.y, entry.w, entry.h, 0, -1, 1, 1);
      ctx.restore();
    }
    this.compose();
  }
  paintSolidColorImageMask() {
    if (!this.contentVisible) {
      return;
    }
    this.ctx.fillRect(0, 0, 1, 1);
    this.compose();
  }
  markPoint(tag) {}
  markPointProps(tag, properties) {}
  beginMarkedContent(tag) {
    this.markedContentStack.push({
      visible: true
    });
  }
  beginMarkedContentProps(tag, properties) {
    if (tag === "OC") {
      this.markedContentStack.push({
        visible: this.optionalContentConfig.isVisible(properties)
      });
    } else {
      this.markedContentStack.push({
        visible: true
      });
    }
    this.contentVisible = this.isContentVisible();
  }
  endMarkedContent() {
    this.markedContentStack.pop();
    this.contentVisible = this.isContentVisible();
  }
  beginCompat() {}
  endCompat() {}
  consumePath(clipBox) {
    const isEmpty = this.current.isEmptyClip();
    if (this.pendingClip) {
      this.current.updateClipFromPath();
    }
    if (!this.pendingClip) {
      this.compose(clipBox);
    }
    const ctx = this.ctx;
    if (this.pendingClip) {
      if (!isEmpty) {
        if (this.pendingClip === EO_CLIP) {
          ctx.clip("evenodd");
        } else {
          ctx.clip();
        }
      }
      this.pendingClip = null;
    }
    this.current.startNewPathAndClipBox(this.current.clipBox);
    ctx.beginPath();
  }
  getSinglePixelWidth() {
    if (!this._cachedGetSinglePixelWidth) {
      const m = getCurrentTransform(this.ctx);
      if (m[1] === 0 && m[2] === 0) {
        this._cachedGetSinglePixelWidth = 1 / Math.min(Math.abs(m[0]), Math.abs(m[3]));
      } else {
        const absDet = Math.abs(m[0] * m[3] - m[2] * m[1]);
        const normX = Math.hypot(m[0], m[2]);
        const normY = Math.hypot(m[1], m[3]);
        this._cachedGetSinglePixelWidth = Math.max(normX, normY) / absDet;
      }
    }
    return this._cachedGetSinglePixelWidth;
  }
  getScaleForStroking() {
    if (this._cachedScaleForStroking[0] === -1) {
      const {
        lineWidth
      } = this.current;
      const {
        a,
        b,
        c,
        d
      } = this.ctx.getTransform();
      let scaleX, scaleY;
      if (b === 0 && c === 0) {
        const normX = Math.abs(a);
        const normY = Math.abs(d);
        if (normX === normY) {
          if (lineWidth === 0) {
            scaleX = scaleY = 1 / normX;
          } else {
            const scaledLineWidth = normX * lineWidth;
            scaleX = scaleY = scaledLineWidth < 1 ? 1 / scaledLineWidth : 1;
          }
        } else if (lineWidth === 0) {
          scaleX = 1 / normX;
          scaleY = 1 / normY;
        } else {
          const scaledXLineWidth = normX * lineWidth;
          const scaledYLineWidth = normY * lineWidth;
          scaleX = scaledXLineWidth < 1 ? 1 / scaledXLineWidth : 1;
          scaleY = scaledYLineWidth < 1 ? 1 / scaledYLineWidth : 1;
        }
      } else {
        const absDet = Math.abs(a * d - b * c);
        const normX = Math.hypot(a, b);
        const normY = Math.hypot(c, d);
        if (lineWidth === 0) {
          scaleX = normY / absDet;
          scaleY = normX / absDet;
        } else {
          const baseArea = lineWidth * absDet;
          scaleX = normY > baseArea ? normY / baseArea : 1;
          scaleY = normX > baseArea ? normX / baseArea : 1;
        }
      }
      this._cachedScaleForStroking[0] = scaleX;
      this._cachedScaleForStroking[1] = scaleY;
    }
    return this._cachedScaleForStroking;
  }
  rescaleAndStroke(saveRestore) {
    const {
      ctx
    } = this;
    const {
      lineWidth
    } = this.current;
    const [scaleX, scaleY] = this.getScaleForStroking();
    ctx.lineWidth = lineWidth || 1;
    if (scaleX === 1 && scaleY === 1) {
      ctx.stroke();
      return;
    }
    const dashes = ctx.getLineDash();
    if (saveRestore) {
      ctx.save();
    }
    ctx.scale(scaleX, scaleY);
    if (dashes.length > 0) {
      const scale = Math.max(scaleX, scaleY);
      ctx.setLineDash(dashes.map(x => x / scale));
      ctx.lineDashOffset /= scale;
    }
    ctx.stroke();
    if (saveRestore) {
      ctx.restore();
    }
  }
  isContentVisible() {
    for (let i = this.markedContentStack.length - 1; i >= 0; i--) {
      if (!this.markedContentStack[i].visible) {
        return false;
      }
    }
    return true;
  }
}
for (const op in OPS) {
  if (CanvasGraphics.prototype[op] !== undefined) {
    CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op];
  }
}

;// CONCATENATED MODULE: ./src/display/worker_options.js
class GlobalWorkerOptions {
  static #port = null;
  static #src = "";
  static get workerPort() {
    return this.#port;
  }
  static set workerPort(val) {
    if (!(typeof Worker !== "undefined" && val instanceof Worker) && val !== null) {
      throw new Error("Invalid `workerPort` type.");
    }
    this.#port = val;
  }
  static get workerSrc() {
    return this.#src;
  }
  static set workerSrc(val) {
    if (typeof val !== "string") {
      throw new Error("Invalid `workerSrc` type.");
    }
    this.#src = val;
  }
}

;// CONCATENATED MODULE: ./src/shared/message_handler.js

const CallbackKind = {
  UNKNOWN: 0,
  DATA: 1,
  ERROR: 2
};
const StreamKind = {
  UNKNOWN: 0,
  CANCEL: 1,
  CANCEL_COMPLETE: 2,
  CLOSE: 3,
  ENQUEUE: 4,
  ERROR: 5,
  PULL: 6,
  PULL_COMPLETE: 7,
  START_COMPLETE: 8
};
function wrapReason(reason) {
  if (!(reason instanceof Error || typeof reason === "object" && reason !== null)) {
    unreachable('wrapReason: Expected "reason" to be a (possibly cloned) Error.');
  }
  switch (reason.name) {
    case "AbortException":
      return new AbortException(reason.message);
    case "MissingPDFException":
      return new MissingPDFException(reason.message);
    case "PasswordException":
      return new PasswordException(reason.message, reason.code);
    case "UnexpectedResponseException":
      return new UnexpectedResponseException(reason.message, reason.status);
    case "UnknownErrorException":
      return new UnknownErrorException(reason.message, reason.details);
    default:
      return new UnknownErrorException(reason.message, reason.toString());
  }
}
class MessageHandler {
  constructor(sourceName, targetName, comObj) {
    this.sourceName = sourceName;
    this.targetName = targetName;
    this.comObj = comObj;
    this.callbackId = 1;
    this.streamId = 1;
    this.streamSinks = Object.create(null);
    this.streamControllers = Object.create(null);
    this.callbackCapabilities = Object.create(null);
    this.actionHandler = Object.create(null);
    this._onComObjOnMessage = event => {
      const data = event.data;
      if (data.targetName !== this.sourceName) {
        return;
      }
      if (data.stream) {
        this.#processStreamMessage(data);
        return;
      }
      if (data.callback) {
        const callbackId = data.callbackId;
        const capability = this.callbackCapabilities[callbackId];
        if (!capability) {
          throw new Error(`Cannot resolve callback ${callbackId}`);
        }
        delete this.callbackCapabilities[callbackId];
        if (data.callback === CallbackKind.DATA) {
          capability.resolve(data.data);
        } else if (data.callback === CallbackKind.ERROR) {
          capability.reject(wrapReason(data.reason));
        } else {
          throw new Error("Unexpected callback case");
        }
        return;
      }
      const action = this.actionHandler[data.action];
      if (!action) {
        throw new Error(`Unknown action from worker: ${data.action}`);
      }
      if (data.callbackId) {
        const cbSourceName = this.sourceName;
        const cbTargetName = data.sourceName;
        new Promise(function (resolve) {
          resolve(action(data.data));
        }).then(function (result) {
          comObj.postMessage({
            sourceName: cbSourceName,
            targetName: cbTargetName,
            callback: CallbackKind.DATA,
            callbackId: data.callbackId,
            data: result
          });
        }, function (reason) {
          comObj.postMessage({
            sourceName: cbSourceName,
            targetName: cbTargetName,
            callback: CallbackKind.ERROR,
            callbackId: data.callbackId,
            reason: wrapReason(reason)
          });
        });
        return;
      }
      if (data.streamId) {
        this.#createStreamSink(data);
        return;
      }
      action(data.data);
    };
    comObj.addEventListener("message", this._onComObjOnMessage);
  }
  on(actionName, handler) {
    const ah = this.actionHandler;
    if (ah[actionName]) {
      throw new Error(`There is already an actionName called "${actionName}"`);
    }
    ah[actionName] = handler;
  }
  send(actionName, data, transfers) {
    this.comObj.postMessage({
      sourceName: this.sourceName,
      targetName: this.targetName,
      action: actionName,
      data
    }, transfers);
  }
  sendWithPromise(actionName, data, transfers) {
    const callbackId = this.callbackId++;
    const capability = Promise.withResolvers();
    this.callbackCapabilities[callbackId] = capability;
    try {
      this.comObj.postMessage({
        sourceName: this.sourceName,
        targetName: this.targetName,
        action: actionName,
        callbackId,
        data
      }, transfers);
    } catch (ex) {
      capability.reject(ex);
    }
    return capability.promise;
  }
  sendWithStream(actionName, data, queueingStrategy, transfers) {
    const streamId = this.streamId++,
      sourceName = this.sourceName,
      targetName = this.targetName,
      comObj = this.comObj;
    return new ReadableStream({
      start: controller => {
        const startCapability = Promise.withResolvers();
        this.streamControllers[streamId] = {
          controller,
          startCall: startCapability,
          pullCall: null,
          cancelCall: null,
          isClosed: false
        };
        comObj.postMessage({
          sourceName,
          targetName,
          action: actionName,
          streamId,
          data,
          desiredSize: controller.desiredSize
        }, transfers);
        return startCapability.promise;
      },
      pull: controller => {
        const pullCapability = Promise.withResolvers();
        this.streamControllers[streamId].pullCall = pullCapability;
        comObj.postMessage({
          sourceName,
          targetName,
          stream: StreamKind.PULL,
          streamId,
          desiredSize: controller.desiredSize
        });
        return pullCapability.promise;
      },
      cancel: reason => {
        assert(reason instanceof Error, "cancel must have a valid reason");
        const cancelCapability = Promise.withResolvers();
        this.streamControllers[streamId].cancelCall = cancelCapability;
        this.streamControllers[streamId].isClosed = true;
        comObj.postMessage({
          sourceName,
          targetName,
          stream: StreamKind.CANCEL,
          streamId,
          reason: wrapReason(reason)
        });
        return cancelCapability.promise;
      }
    }, queueingStrategy);
  }
  #createStreamSink(data) {
    const streamId = data.streamId,
      sourceName = this.sourceName,
      targetName = data.sourceName,
      comObj = this.comObj;
    const self = this,
      action = this.actionHandler[data.action];
    const streamSink = {
      enqueue(chunk, size = 1, transfers) {
        if (this.isCancelled) {
          return;
        }
        const lastDesiredSize = this.desiredSize;
        this.desiredSize -= size;
        if (lastDesiredSize > 0 && this.desiredSize <= 0) {
          this.sinkCapability = Promise.withResolvers();
          this.ready = this.sinkCapability.promise;
        }
        comObj.postMessage({
          sourceName,
          targetName,
          stream: StreamKind.ENQUEUE,
          streamId,
          chunk
        }, transfers);
      },
      close() {
        if (this.isCancelled) {
          return;
        }
        this.isCancelled = true;
        comObj.postMessage({
          sourceName,
          targetName,
          stream: StreamKind.CLOSE,
          streamId
        });
        delete self.streamSinks[streamId];
      },
      error(reason) {
        assert(reason instanceof Error, "error must have a valid reason");
        if (this.isCancelled) {
          return;
        }
        this.isCancelled = true;
        comObj.postMessage({
          sourceName,
          targetName,
          stream: StreamKind.ERROR,
          streamId,
          reason: wrapReason(reason)
        });
      },
      sinkCapability: Promise.withResolvers(),
      onPull: null,
      onCancel: null,
      isCancelled: false,
      desiredSize: data.desiredSize,
      ready: null
    };
    streamSink.sinkCapability.resolve();
    streamSink.ready = streamSink.sinkCapability.promise;
    this.streamSinks[streamId] = streamSink;
    new Promise(function (resolve) {
      resolve(action(data.data, streamSink));
    }).then(function () {
      comObj.postMessage({
        sourceName,
        targetName,
        stream: StreamKind.START_COMPLETE,
        streamId,
        success: true
      });
    }, function (reason) {
      comObj.postMessage({
        sourceName,
        targetName,
        stream: StreamKind.START_COMPLETE,
        streamId,
        reason: wrapReason(reason)
      });
    });
  }
  #processStreamMessage(data) {
    const streamId = data.streamId,
      sourceName = this.sourceName,
      targetName = data.sourceName,
      comObj = this.comObj;
    const streamController = this.streamControllers[streamId],
      streamSink = this.streamSinks[streamId];
    switch (data.stream) {
      case StreamKind.START_COMPLETE:
        if (data.success) {
          streamController.startCall.resolve();
        } else {
          streamController.startCall.reject(wrapReason(data.reason));
        }
        break;
      case StreamKind.PULL_COMPLETE:
        if (data.success) {
          streamController.pullCall.resolve();
        } else {
          streamController.pullCall.reject(wrapReason(data.reason));
        }
        break;
      case StreamKind.PULL:
        if (!streamSink) {
          comObj.postMessage({
            sourceName,
            targetName,
            stream: StreamKind.PULL_COMPLETE,
            streamId,
            success: true
          });
          break;
        }
        if (streamSink.desiredSize <= 0 && data.desiredSize > 0) {
          streamSink.sinkCapability.resolve();
        }
        streamSink.desiredSize = data.desiredSize;
        new Promise(function (resolve) {
          resolve(streamSink.onPull?.());
        }).then(function () {
          comObj.postMessage({
            sourceName,
            targetName,
            stream: StreamKind.PULL_COMPLETE,
            streamId,
            success: true
          });
        }, function (reason) {
          comObj.postMessage({
            sourceName,
            targetName,
            stream: StreamKind.PULL_COMPLETE,
            streamId,
            reason: wrapReason(reason)
          });
        });
        break;
      case StreamKind.ENQUEUE:
        assert(streamController, "enqueue should have stream controller");
        if (streamController.isClosed) {
          break;
        }
        streamController.controller.enqueue(data.chunk);
        break;
      case StreamKind.CLOSE:
        assert(streamController, "close should have stream controller");
        if (streamController.isClosed) {
          break;
        }
        streamController.isClosed = true;
        streamController.controller.close();
        this.#deleteStreamController(streamController, streamId);
        break;
      case StreamKind.ERROR:
        assert(streamController, "error should have stream controller");
        streamController.controller.error(wrapReason(data.reason));
        this.#deleteStreamController(streamController, streamId);
        break;
      case StreamKind.CANCEL_COMPLETE:
        if (data.success) {
          streamController.cancelCall.resolve();
        } else {
          streamController.cancelCall.reject(wrapReason(data.reason));
        }
        this.#deleteStreamController(streamController, streamId);
        break;
      case StreamKind.CANCEL:
        if (!streamSink) {
          break;
        }
        new Promise(function (resolve) {
          resolve(streamSink.onCancel?.(wrapReason(data.reason)));
        }).then(function () {
          comObj.postMessage({
            sourceName,
            targetName,
            stream: StreamKind.CANCEL_COMPLETE,
            streamId,
            success: true
          });
        }, function (reason) {
          comObj.postMessage({
            sourceName,
            targetName,
            stream: StreamKind.CANCEL_COMPLETE,
            streamId,
            reason: wrapReason(reason)
          });
        });
        streamSink.sinkCapability.reject(wrapReason(data.reason));
        streamSink.isCancelled = true;
        delete this.streamSinks[streamId];
        break;
      default:
        throw new Error("Unexpected stream case");
    }
  }
  async #deleteStreamController(streamController, streamId) {
    await Promise.allSettled([streamController.startCall?.promise, streamController.pullCall?.promise, streamController.cancelCall?.promise]);
    delete this.streamControllers[streamId];
  }
  destroy() {
    this.comObj.removeEventListener("message", this._onComObjOnMessage);
  }
}

;// CONCATENATED MODULE: ./src/display/metadata.js

class Metadata {
  #metadataMap;
  #data;
  constructor({
    parsedData,
    rawData
  }) {
    this.#metadataMap = parsedData;
    this.#data = rawData;
  }
  getRaw() {
    return this.#data;
  }
  get(name) {
    return this.#metadataMap.get(name) ?? null;
  }
  getAll() {
    return objectFromMap(this.#metadataMap);
  }
  has(name) {
    return this.#metadataMap.has(name);
  }
}

;// CONCATENATED MODULE: ./src/display/optional_content_config.js


const INTERNAL = Symbol("INTERNAL");
class OptionalContentGroup {
  #isDisplay = false;
  #isPrint = false;
  #userSet = false;
  #visible = true;
  constructor(renderingIntent, {
    name,
    intent,
    usage
  }) {
    this.#isDisplay = !!(renderingIntent & RenderingIntentFlag.DISPLAY);
    this.#isPrint = !!(renderingIntent & RenderingIntentFlag.PRINT);
    this.name = name;
    this.intent = intent;
    this.usage = usage;
  }
  get visible() {
    if (this.#userSet) {
      return this.#visible;
    }
    if (!this.#visible) {
      return false;
    }
    const {
      print,
      view
    } = this.usage;
    if (this.#isDisplay) {
      return view?.viewState !== "OFF";
    } else if (this.#isPrint) {
      return print?.printState !== "OFF";
    }
    return true;
  }
  _setVisible(internal, visible, userSet = false) {
    if (internal !== INTERNAL) {
      unreachable("Internal method `_setVisible` called.");
    }
    this.#userSet = userSet;
    this.#visible = visible;
  }
}
class OptionalContentConfig {
  #cachedGetHash = null;
  #groups = new Map();
  #initialHash = null;
  #order = null;
  constructor(data, renderingIntent = RenderingIntentFlag.DISPLAY) {
    this.renderingIntent = renderingIntent;
    this.name = null;
    this.creator = null;
    if (data === null) {
      return;
    }
    this.name = data.name;
    this.creator = data.creator;
    this.#order = data.order;
    for (const group of data.groups) {
      this.#groups.set(group.id, new OptionalContentGroup(renderingIntent, group));
    }
    if (data.baseState === "OFF") {
      for (const group of this.#groups.values()) {
        group._setVisible(INTERNAL, false);
      }
    }
    for (const on of data.on) {
      this.#groups.get(on)._setVisible(INTERNAL, true);
    }
    for (const off of data.off) {
      this.#groups.get(off)._setVisible(INTERNAL, false);
    }
    this.#initialHash = this.getHash();
  }
  #evaluateVisibilityExpression(array) {
    const length = array.length;
    if (length < 2) {
      return true;
    }
    const operator = array[0];
    for (let i = 1; i < length; i++) {
      const element = array[i];
      let state;
      if (Array.isArray(element)) {
        state = this.#evaluateVisibilityExpression(element);
      } else if (this.#groups.has(element)) {
        state = this.#groups.get(element).visible;
      } else {
        warn(`Optional content group not found: ${element}`);
        return true;
      }
      switch (operator) {
        case "And":
          if (!state) {
            return false;
          }
          break;
        case "Or":
          if (state) {
            return true;
          }
          break;
        case "Not":
          return !state;
        default:
          return true;
      }
    }
    return operator === "And";
  }
  isVisible(group) {
    if (this.#groups.size === 0) {
      return true;
    }
    if (!group) {
      info("Optional content group not defined.");
      return true;
    }
    if (group.type === "OCG") {
      if (!this.#groups.has(group.id)) {
        warn(`Optional content group not found: ${group.id}`);
        return true;
      }
      return this.#groups.get(group.id).visible;
    } else if (group.type === "OCMD") {
      if (group.expression) {
        return this.#evaluateVisibilityExpression(group.expression);
      }
      if (!group.policy || group.policy === "AnyOn") {
        for (const id of group.ids) {
          if (!this.#groups.has(id)) {
            warn(`Optional content group not found: ${id}`);
            return true;
          }
          if (this.#groups.get(id).visible) {
            return true;
          }
        }
        return false;
      } else if (group.policy === "AllOn") {
        for (const id of group.ids) {
          if (!this.#groups.has(id)) {
            warn(`Optional content group not found: ${id}`);
            return true;
          }
          if (!this.#groups.get(id).visible) {
            return false;
          }
        }
        return true;
      } else if (group.policy === "AnyOff") {
        for (const id of group.ids) {
          if (!this.#groups.has(id)) {
            warn(`Optional content group not found: ${id}`);
            return true;
          }
          if (!this.#groups.get(id).visible) {
            return true;
          }
        }
        return false;
      } else if (group.policy === "AllOff") {
        for (const id of group.ids) {
          if (!this.#groups.has(id)) {
            warn(`Optional content group not found: ${id}`);
            return true;
          }
          if (this.#groups.get(id).visible) {
            return false;
          }
        }
        return true;
      }
      warn(`Unknown optional content policy ${group.policy}.`);
      return true;
    }
    warn(`Unknown group type ${group.type}.`);
    return true;
  }
  setVisibility(id, visible = true) {
    const group = this.#groups.get(id);
    if (!group) {
      warn(`Optional content group not found: ${id}`);
      return;
    }
    group._setVisible(INTERNAL, !!visible, true);
    this.#cachedGetHash = null;
  }
  setOCGState({
    state,
    preserveRB
  }) {
    let operator;
    for (const elem of state) {
      switch (elem) {
        case "ON":
        case "OFF":
        case "Toggle":
          operator = elem;
          continue;
      }
      const group = this.#groups.get(elem);
      if (!group) {
        continue;
      }
      switch (operator) {
        case "ON":
          group._setVisible(INTERNAL, true);
          break;
        case "OFF":
          group._setVisible(INTERNAL, false);
          break;
        case "Toggle":
          group._setVisible(INTERNAL, !group.visible);
          break;
      }
    }
    this.#cachedGetHash = null;
  }
  get hasInitialVisibility() {
    return this.#initialHash === null || this.getHash() === this.#initialHash;
  }
  getOrder() {
    if (!this.#groups.size) {
      return null;
    }
    if (this.#order) {
      return this.#order.slice();
    }
    return [...this.#groups.keys()];
  }
  getGroups() {
    return this.#groups.size > 0 ? objectFromMap(this.#groups) : null;
  }
  getGroup(id) {
    return this.#groups.get(id) || null;
  }
  getHash() {
    if (this.#cachedGetHash !== null) {
      return this.#cachedGetHash;
    }
    const hash = new MurmurHash3_64();
    for (const [id, group] of this.#groups) {
      hash.update(`${id}:${group.visible}`);
    }
    return this.#cachedGetHash = hash.hexdigest();
  }
}

;// CONCATENATED MODULE: ./src/display/transport_stream.js


class PDFDataTransportStream {
  constructor(pdfDataRangeTransport, {
    disableRange = false,
    disableStream = false
  }) {
    assert(pdfDataRangeTransport, 'PDFDataTransportStream - missing required "pdfDataRangeTransport" argument.');
    const {
      length,
      initialData,
      progressiveDone,
      contentDispositionFilename
    } = pdfDataRangeTransport;
    this._queuedChunks = [];
    this._progressiveDone = progressiveDone;
    this._contentDispositionFilename = contentDispositionFilename;
    if (initialData?.length > 0) {
      const buffer = initialData instanceof Uint8Array && initialData.byteLength === initialData.buffer.byteLength ? initialData.buffer : new Uint8Array(initialData).buffer;
      this._queuedChunks.push(buffer);
    }
    this._pdfDataRangeTransport = pdfDataRangeTransport;
    this._isStreamingSupported = !disableStream;
    this._isRangeSupported = !disableRange;
    this._contentLength = length;
    this._fullRequestReader = null;
    this._rangeReaders = [];
    pdfDataRangeTransport.addRangeListener((begin, chunk) => {
      this._onReceiveData({
        begin,
        chunk
      });
    });
    pdfDataRangeTransport.addProgressListener((loaded, total) => {
      this._onProgress({
        loaded,
        total
      });
    });
    pdfDataRangeTransport.addProgressiveReadListener(chunk => {
      this._onReceiveData({
        chunk
      });
    });
    pdfDataRangeTransport.addProgressiveDoneListener(() => {
      this._onProgressiveDone();
    });
    pdfDataRangeTransport.transportReady();
  }
  _onReceiveData({
    begin,
    chunk
  }) {
    const buffer = chunk instanceof Uint8Array && chunk.byteLength === chunk.buffer.byteLength ? chunk.buffer : new Uint8Array(chunk).buffer;
    if (begin === undefined) {
      if (this._fullRequestReader) {
        this._fullRequestReader._enqueue(buffer);
      } else {
        this._queuedChunks.push(buffer);
      }
    } else {
      const found = this._rangeReaders.some(function (rangeReader) {
        if (rangeReader._begin !== begin) {
          return false;
        }
        rangeReader._enqueue(buffer);
        return true;
      });
      assert(found, "_onReceiveData - no `PDFDataTransportStreamRangeReader` instance found.");
    }
  }
  get _progressiveDataLength() {
    return this._fullRequestReader?._loaded ?? 0;
  }
  _onProgress(evt) {
    if (evt.total === undefined) {
      this._rangeReaders[0]?.onProgress?.({
        loaded: evt.loaded
      });
    } else {
      this._fullRequestReader?.onProgress?.({
        loaded: evt.loaded,
        total: evt.total
      });
    }
  }
  _onProgressiveDone() {
    this._fullRequestReader?.progressiveDone();
    this._progressiveDone = true;
  }
  _removeRangeReader(reader) {
    const i = this._rangeReaders.indexOf(reader);
    if (i >= 0) {
      this._rangeReaders.splice(i, 1);
    }
  }
  getFullReader() {
    assert(!this._fullRequestReader, "PDFDataTransportStream.getFullReader can only be called once.");
    const queuedChunks = this._queuedChunks;
    this._queuedChunks = null;
    return new PDFDataTransportStreamReader(this, queuedChunks, this._progressiveDone, this._contentDispositionFilename);
  }
  getRangeReader(begin, end) {
    if (end <= this._progressiveDataLength) {
      return null;
    }
    const reader = new PDFDataTransportStreamRangeReader(this, begin, end);
    this._pdfDataRangeTransport.requestDataRange(begin, end);
    this._rangeReaders.push(reader);
    return reader;
  }
  cancelAllRequests(reason) {
    this._fullRequestReader?.cancel(reason);
    for (const reader of this._rangeReaders.slice(0)) {
      reader.cancel(reason);
    }
    this._pdfDataRangeTransport.abort();
  }
}
class PDFDataTransportStreamReader {
  constructor(stream, queuedChunks, progressiveDone = false, contentDispositionFilename = null) {
    this._stream = stream;
    this._done = progressiveDone || false;
    this._filename = isPdfFile(contentDispositionFilename) ? contentDispositionFilename : null;
    this._queuedChunks = queuedChunks || [];
    this._loaded = 0;
    for (const chunk of this._queuedChunks) {
      this._loaded += chunk.byteLength;
    }
    this._requests = [];
    this._headersReady = Promise.resolve();
    stream._fullRequestReader = this;
    this.onProgress = null;
  }
  _enqueue(chunk) {
    if (this._done) {
      return;
    }
    if (this._requests.length > 0) {
      const requestCapability = this._requests.shift();
      requestCapability.resolve({
        value: chunk,
        done: false
      });
    } else {
      this._queuedChunks.push(chunk);
    }
    this._loaded += chunk.byteLength;
  }
  get headersReady() {
    return this._headersReady;
  }
  get filename() {
    return this._filename;
  }
  get isRangeSupported() {
    return this._stream._isRangeSupported;
  }
  get isStreamingSupported() {
    return this._stream._isStreamingSupported;
  }
  get contentLength() {
    return this._stream._contentLength;
  }
  async read() {
    if (this._queuedChunks.length > 0) {
      const chunk = this._queuedChunks.shift();
      return {
        value: chunk,
        done: false
      };
    }
    if (this._done) {
      return {
        value: undefined,
        done: true
      };
    }
    const requestCapability = Promise.withResolvers();
    this._requests.push(requestCapability);
    return requestCapability.promise;
  }
  cancel(reason) {
    this._done = true;
    for (const requestCapability of this._requests) {
      requestCapability.resolve({
        value: undefined,
        done: true
      });
    }
    this._requests.length = 0;
  }
  progressiveDone() {
    if (this._done) {
      return;
    }
    this._done = true;
  }
}
class PDFDataTransportStreamRangeReader {
  constructor(stream, begin, end) {
    this._stream = stream;
    this._begin = begin;
    this._end = end;
    this._queuedChunk = null;
    this._requests = [];
    this._done = false;
    this.onProgress = null;
  }
  _enqueue(chunk) {
    if (this._done) {
      return;
    }
    if (this._requests.length === 0) {
      this._queuedChunk = chunk;
    } else {
      const requestsCapability = this._requests.shift();
      requestsCapability.resolve({
        value: chunk,
        done: false
      });
      for (const requestCapability of this._requests) {
        requestCapability.resolve({
          value: undefined,
          done: true
        });
      }
      this._requests.length = 0;
    }
    this._done = true;
    this._stream._removeRangeReader(this);
  }
  get isStreamingSupported() {
    return false;
  }
  async read() {
    if (this._queuedChunk) {
      const chunk = this._queuedChunk;
      this._queuedChunk = null;
      return {
        value: chunk,
        done: false
      };
    }
    if (this._done) {
      return {
        value: undefined,
        done: true
      };
    }
    const requestCapability = Promise.withResolvers();
    this._requests.push(requestCapability);
    return requestCapability.promise;
  }
  cancel(reason) {
    this._done = true;
    for (const requestCapability of this._requests) {
      requestCapability.resolve({
        value: undefined,
        done: true
      });
    }
    this._requests.length = 0;
    this._stream._removeRangeReader(this);
  }
}

;// CONCATENATED MODULE: ./src/display/text_layer.js


const MAX_TEXT_DIVS_TO_RENDER = 100000;
const DEFAULT_FONT_SIZE = 30;
const DEFAULT_FONT_ASCENT = 0.8;
class TextLayer {
  #capability = Promise.withResolvers();
  #container = null;
  #disableProcessItems = false;
  #fontInspectorEnabled = !!globalThis.FontInspector?.enabled;
  #lang = null;
  #layoutTextParams = null;
  #pageHeight = 0;
  #pageWidth = 0;
  #reader = null;
  #rootContainer = null;
  #rotation = 0;
  #scale = 0;
  #styleCache = Object.create(null);
  #textContentItemsStr = [];
  #textContentSource = null;
  #textDivs = [];
  #textDivProperties = new WeakMap();
  #transform = null;
  static #ascentCache = new Map();
  static #canvasContexts = new Map();
  static #minFontSize = null;
  static #pendingTextLayers = new Set();
  constructor({
    textContentSource,
    container,
    viewport
  }) {
    if (textContentSource instanceof ReadableStream) {
      this.#textContentSource = textContentSource;
    } else {
      throw new Error('No "textContentSource" parameter specified.');
    }
    this.#container = this.#rootContainer = container;
    this.#scale = viewport.scale * (globalThis.devicePixelRatio || 1);
    this.#rotation = viewport.rotation;
    this.#layoutTextParams = {
      prevFontSize: null,
      prevFontFamily: null,
      div: null,
      properties: null,
      ctx: null
    };
    const {
      pageWidth,
      pageHeight,
      pageX,
      pageY
    } = viewport.rawDims;
    this.#transform = [1, 0, 0, -1, -pageX, pageY + pageHeight];
    this.#pageWidth = pageWidth;
    this.#pageHeight = pageHeight;
    TextLayer.#ensureMinFontSizeComputed();
    setLayerDimensions(container, viewport);
    this.#capability.promise.catch(() => {}).then(() => {
      TextLayer.#pendingTextLayers.delete(this);
      this.#layoutTextParams = null;
      this.#styleCache = null;
    });
  }
  render() {
    const pump = () => {
      this.#reader.read().then(({
        value,
        done
      }) => {
        if (done) {
          this.#capability.resolve();
          return;
        }
        this.#lang ??= value.lang;
        Object.assign(this.#styleCache, value.styles);
        this.#processItems(value.items);
        pump();
      }, this.#capability.reject);
    };
    this.#reader = this.#textContentSource.getReader();
    TextLayer.#pendingTextLayers.add(this);
    pump();
    return this.#capability.promise;
  }
  update({
    viewport,
    onBefore = null
  }) {
    const scale = viewport.scale * (globalThis.devicePixelRatio || 1);
    const rotation = viewport.rotation;
    if (rotation !== this.#rotation) {
      onBefore?.();
      this.#rotation = rotation;
      setLayerDimensions(this.#rootContainer, {
        rotation
      });
    }
    if (scale !== this.#scale) {
      onBefore?.();
      this.#scale = scale;
      const params = {
        prevFontSize: null,
        prevFontFamily: null,
        div: null,
        properties: null,
        ctx: TextLayer.#getCtx(this.#lang)
      };
      for (const div of this.#textDivs) {
        params.properties = this.#textDivProperties.get(div);
        params.div = div;
        this.#layout(params);
      }
    }
  }
  cancel() {
    const abortEx = new AbortException("TextLayer task cancelled.");
    this.#reader?.cancel(abortEx).catch(() => {});
    this.#reader = null;
    this.#capability.reject(abortEx);
  }
  get textDivs() {
    return this.#textDivs;
  }
  get textContentItemsStr() {
    return this.#textContentItemsStr;
  }
  #processItems(items) {
    if (this.#disableProcessItems) {
      return;
    }
    this.#layoutTextParams.ctx ??= TextLayer.#getCtx(this.#lang);
    const textDivs = this.#textDivs,
      textContentItemsStr = this.#textContentItemsStr;
    for (const item of items) {
      if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER) {
        warn("Ignoring additional textDivs for performance reasons.");
        this.#disableProcessItems = true;
        return;
      }
      if (item.str === undefined) {
        if (item.type === "beginMarkedContentProps" || item.type === "beginMarkedContent") {
          const parent = this.#container;
          this.#container = document.createElement("span");
          this.#container.classList.add("markedContent");
          if (item.id !== null) {
            this.#container.setAttribute("id", `${item.id}`);
          }
          parent.append(this.#container);
        } else if (item.type === "endMarkedContent") {
          this.#container = this.#container.parentNode;
        }
        continue;
      }
      textContentItemsStr.push(item.str);
      this.#appendText(item);
    }
  }
  #appendText(geom) {
    const textDiv = document.createElement("span");
    const textDivProperties = {
      angle: 0,
      canvasWidth: 0,
      hasText: geom.str !== "",
      hasEOL: geom.hasEOL,
      fontSize: 0
    };
    this.#textDivs.push(textDiv);
    const tx = Util.transform(this.#transform, geom.transform);
    let angle = Math.atan2(tx[1], tx[0]);
    const style = this.#styleCache[geom.fontName];
    if (style.vertical) {
      angle += Math.PI / 2;
    }
    const fontFamily = this.#fontInspectorEnabled && style.fontSubstitution || style.fontFamily;
    const fontHeight = Math.hypot(tx[2], tx[3]);
    const fontAscent = fontHeight * TextLayer.#getAscent(fontFamily, this.#lang);
    let left, top;
    if (angle === 0) {
      left = tx[4];
      top = tx[5] - fontAscent;
    } else {
      left = tx[4] + fontAscent * Math.sin(angle);
      top = tx[5] - fontAscent * Math.cos(angle);
    }
    const scaleFactorStr = "calc(var(--scale-factor)*";
    const divStyle = textDiv.style;
    if (this.#container === this.#rootContainer) {
      divStyle.left = `${(100 * left / this.#pageWidth).toFixed(2)}%`;
      divStyle.top = `${(100 * top / this.#pageHeight).toFixed(2)}%`;
    } else {
      divStyle.left = `${scaleFactorStr}${left.toFixed(2)}px)`;
      divStyle.top = `${scaleFactorStr}${top.toFixed(2)}px)`;
    }
    divStyle.fontSize = `${scaleFactorStr}${(TextLayer.#minFontSize * fontHeight).toFixed(2)}px)`;
    divStyle.fontFamily = fontFamily;
    textDivProperties.fontSize = fontHeight;
    textDiv.setAttribute("role", "presentation");
    textDiv.textContent = geom.str;
    textDiv.dir = geom.dir;
    if (this.#fontInspectorEnabled) {
      textDiv.dataset.fontName = style.fontSubstitutionLoadedName || geom.fontName;
    }
    if (angle !== 0) {
      textDivProperties.angle = angle * (180 / Math.PI);
    }
    let shouldScaleText = false;
    if (geom.str.length > 1) {
      shouldScaleText = true;
    } else if (geom.str !== " " && geom.transform[0] !== geom.transform[3]) {
      const absScaleX = Math.abs(geom.transform[0]),
        absScaleY = Math.abs(geom.transform[3]);
      if (absScaleX !== absScaleY && Math.max(absScaleX, absScaleY) / Math.min(absScaleX, absScaleY) > 1.5) {
        shouldScaleText = true;
      }
    }
    if (shouldScaleText) {
      textDivProperties.canvasWidth = style.vertical ? geom.height : geom.width;
    }
    this.#textDivProperties.set(textDiv, textDivProperties);
    this.#layoutTextParams.div = textDiv;
    this.#layoutTextParams.properties = textDivProperties;
    this.#layout(this.#layoutTextParams);
    if (textDivProperties.hasText) {
      this.#container.append(textDiv);
    }
    if (textDivProperties.hasEOL) {
      const br = document.createElement("br");
      br.setAttribute("role", "presentation");
      this.#container.append(br);
    }
  }
  #layout(params) {
    const {
      div,
      properties,
      ctx,
      prevFontSize,
      prevFontFamily
    } = params;
    const {
      style
    } = div;
    let transform = "";
    if (TextLayer.#minFontSize > 1) {
      transform = `scale(${1 / TextLayer.#minFontSize})`;
    }
    if (properties.canvasWidth !== 0 && properties.hasText) {
      const {
        fontFamily
      } = style;
      const {
        canvasWidth,
        fontSize
      } = properties;
      if (prevFontSize !== fontSize || prevFontFamily !== fontFamily) {
        ctx.font = `${fontSize * this.#scale}px ${fontFamily}`;
        params.prevFontSize = fontSize;
        params.prevFontFamily = fontFamily;
      }
      const {
        width
      } = ctx.measureText(div.textContent);
      if (width > 0) {
        transform = `scaleX(${canvasWidth * this.#scale / width}) ${transform}`;
      }
    }
    if (properties.angle !== 0) {
      transform = `rotate(${properties.angle}deg) ${transform}`;
    }
    if (transform.length > 0) {
      style.transform = transform;
    }
  }
  static cleanup() {
    if (this.#pendingTextLayers.size > 0) {
      return;
    }
    this.#ascentCache.clear();
    for (const {
      canvas
    } of this.#canvasContexts.values()) {
      canvas.remove();
    }
    this.#canvasContexts.clear();
  }
  static #getCtx(lang = null) {
    let canvasContext = this.#canvasContexts.get(lang ||= "");
    if (!canvasContext) {
      const canvas = document.createElement("canvas");
      canvas.className = "hiddenCanvasElement";
      canvas.lang = lang;
      document.body.append(canvas);
      canvasContext = canvas.getContext("2d", {
        alpha: false,
        willReadFrequently: true
      });
      this.#canvasContexts.set(lang, canvasContext);
    }
    return canvasContext;
  }
  static #ensureMinFontSizeComputed() {
    if (this.#minFontSize !== null) {
      return;
    }
    const div = document.createElement("div");
    div.style.opacity = 0;
    div.style.lineHeight = 1;
    div.style.fontSize = "1px";
    div.style.position = "absolute";
    div.textContent = "X";
    document.body.append(div);
    this.#minFontSize = div.getBoundingClientRect().height;
    div.remove();
  }
  static #getAscent(fontFamily, lang) {
    const cachedAscent = this.#ascentCache.get(fontFamily);
    if (cachedAscent) {
      return cachedAscent;
    }
    const ctx = this.#getCtx(lang);
    const savedFont = ctx.font;
    ctx.canvas.width = ctx.canvas.height = DEFAULT_FONT_SIZE;
    ctx.font = `${DEFAULT_FONT_SIZE}px ${fontFamily}`;
    const metrics = ctx.measureText("");
    let ascent = metrics.fontBoundingBoxAscent;
    let descent = Math.abs(metrics.fontBoundingBoxDescent);
    if (ascent) {
      const ratio = ascent / (ascent + descent);
      this.#ascentCache.set(fontFamily, ratio);
      ctx.canvas.width = ctx.canvas.height = 0;
      ctx.font = savedFont;
      return ratio;
    }
    ctx.strokeStyle = "red";
    ctx.clearRect(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
    ctx.strokeText("g", 0, 0);
    let pixels = ctx.getImageData(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE).data;
    descent = 0;
    for (let i = pixels.length - 1 - 3; i >= 0; i -= 4) {
      if (pixels[i] > 0) {
        descent = Math.ceil(i / 4 / DEFAULT_FONT_SIZE);
        break;
      }
    }
    ctx.clearRect(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE);
    ctx.strokeText("A", 0, DEFAULT_FONT_SIZE);
    pixels = ctx.getImageData(0, 0, DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE).data;
    ascent = 0;
    for (let i = 0, ii = pixels.length; i < ii; i += 4) {
      if (pixels[i] > 0) {
        ascent = DEFAULT_FONT_SIZE - Math.floor(i / 4 / DEFAULT_FONT_SIZE);
        break;
      }
    }
    ctx.canvas.width = ctx.canvas.height = 0;
    ctx.font = savedFont;
    const ratio = ascent ? ascent / (ascent + descent) : DEFAULT_FONT_ASCENT;
    this.#ascentCache.set(fontFamily, ratio);
    return ratio;
  }
}

;// CONCATENATED MODULE: ./src/display/xfa_text.js
class XfaText {
  static textContent(xfa) {
    const items = [];
    const output = {
      items,
      styles: Object.create(null)
    };
    function walk(node) {
      if (!node) {
        return;
      }
      let str = null;
      const name = node.name;
      if (name === "#text") {
        str = node.value;
      } else if (!XfaText.shouldBuildText(name)) {
        return;
      } else if (node?.attributes?.textContent) {
        str = node.attributes.textContent;
      } else if (node.value) {
        str = node.value;
      }
      if (str !== null) {
        items.push({
          str
        });
      }
      if (!node.children) {
        return;
      }
      for (const child of node.children) {
        walk(child);
      }
    }
    walk(xfa);
    return output;
  }
  static shouldBuildText(name) {
    return !(name === "textarea" || name === "input" || name === "option" || name === "select");
  }
}

;// CONCATENATED MODULE: ./src/display/api.js
















const DEFAULT_RANGE_CHUNK_SIZE = 65536;
const RENDERING_CANCELLED_TIMEOUT = 100;
const DELAYED_CLEANUP_TIMEOUT = 5000;
const DefaultCanvasFactory = DOMCanvasFactory;
const DefaultCMapReaderFactory = DOMCMapReaderFactory;
const DefaultFilterFactory = DOMFilterFactory;
const DefaultStandardFontDataFactory = DOMStandardFontDataFactory;
function getDocument(src = {}) {
  const task = new PDFDocumentLoadingTask();
  const {
    docId
  } = task;
  const url = src.url ? getUrlProp(src.url) : null;
  const data = src.data ? getDataProp(src.data) : null;
  const httpHeaders = src.httpHeaders || null;
  const withCredentials = src.withCredentials === true;
  const password = src.password ?? null;
  const rangeTransport = src.range instanceof PDFDataRangeTransport ? src.range : null;
  const rangeChunkSize = Number.isInteger(src.rangeChunkSize) && src.rangeChunkSize > 0 ? src.rangeChunkSize : DEFAULT_RANGE_CHUNK_SIZE;
  let worker = src.worker instanceof PDFWorker ? src.worker : null;
  const verbosity = src.verbosity;
  const docBaseUrl = typeof src.docBaseUrl === "string" && !isDataScheme(src.docBaseUrl) ? src.docBaseUrl : null;
  const cMapUrl = typeof src.cMapUrl === "string" ? src.cMapUrl : null;
  const cMapPacked = src.cMapPacked !== false;
  const CMapReaderFactory = src.CMapReaderFactory || DefaultCMapReaderFactory;
  const standardFontDataUrl = typeof src.standardFontDataUrl === "string" ? src.standardFontDataUrl : null;
  const StandardFontDataFactory = src.StandardFontDataFactory || DefaultStandardFontDataFactory;
  const ignoreErrors = src.stopAtErrors !== true;
  const maxImageSize = Number.isInteger(src.maxImageSize) && src.maxImageSize > -1 ? src.maxImageSize : -1;
  const isEvalSupported = src.isEvalSupported !== false;
  const isOffscreenCanvasSupported = typeof src.isOffscreenCanvasSupported === "boolean" ? src.isOffscreenCanvasSupported : !isNodeJS;
  const canvasMaxAreaInBytes = Number.isInteger(src.canvasMaxAreaInBytes) ? src.canvasMaxAreaInBytes : -1;
  const disableFontFace = typeof src.disableFontFace === "boolean" ? src.disableFontFace : isNodeJS;
  const fontExtraProperties = src.fontExtraProperties === true;
  const enableXfa = src.enableXfa === true;
  const ownerDocument = src.ownerDocument || globalThis.document;
  const disableRange = src.disableRange === true;
  const disableStream = src.disableStream === true;
  const disableAutoFetch = src.disableAutoFetch === true;
  const pdfBug = src.pdfBug === true;
  const enableHWA = src.enableHWA === true;
  const length = rangeTransport ? rangeTransport.length : src.length ?? NaN;
  const useSystemFonts = typeof src.useSystemFonts === "boolean" ? src.useSystemFonts : !isNodeJS && !disableFontFace;
  const useWorkerFetch = typeof src.useWorkerFetch === "boolean" ? src.useWorkerFetch : true;
  const canvasFactory = src.canvasFactory || new DefaultCanvasFactory({
    ownerDocument,
    enableHWA
  });
  const filterFactory = src.filterFactory || new DefaultFilterFactory({
    docId,
    ownerDocument
  });
  const styleElement = null;
  setVerbosityLevel(verbosity);
  const transportFactory = {
    canvasFactory,
    filterFactory
  };
  if (!useWorkerFetch) {
    transportFactory.cMapReaderFactory = new CMapReaderFactory({
      baseUrl: cMapUrl,
      isCompressed: cMapPacked
    });
    transportFactory.standardFontDataFactory = new StandardFontDataFactory({
      baseUrl: standardFontDataUrl
    });
  }
  if (!worker) {
    const workerParams = {
      verbosity,
      port: GlobalWorkerOptions.workerPort
    };
    worker = workerParams.port ? PDFWorker.fromPort(workerParams) : new PDFWorker(workerParams);
    task._worker = worker;
  }
  const docParams = {
    docId,
    apiVersion: "4.6.60",
    data,
    password,
    disableAutoFetch,
    rangeChunkSize,
    length,
    docBaseUrl,
    enableXfa,
    evaluatorOptions: {
      maxImageSize,
      disableFontFace,
      ignoreErrors,
      isEvalSupported,
      isOffscreenCanvasSupported,
      canvasMaxAreaInBytes,
      fontExtraProperties,
      useSystemFonts,
      cMapUrl: useWorkerFetch ? cMapUrl : null,
      standardFontDataUrl: useWorkerFetch ? standardFontDataUrl : null
    }
  };
  const transportParams = {
    disableFontFace,
    fontExtraProperties,
    ownerDocument,
    pdfBug,
    styleElement,
    loadingParams: {
      disableAutoFetch,
      enableXfa
    }
  };
  worker.promise.then(function () {
    if (task.destroyed) {
      throw new Error("Loading aborted");
    }
    if (worker.destroyed) {
      throw new Error("Worker was destroyed");
    }
    const workerIdPromise = worker.messageHandler.sendWithPromise("GetDocRequest", docParams, data ? [data.buffer] : null);
    let networkStream;
    if (rangeTransport) {
      networkStream = new PDFDataTransportStream(rangeTransport, {
        disableRange,
        disableStream
      });
    } else if (!data) {
      throw new Error("Not implemented: createPDFNetworkStream");
    }
    return workerIdPromise.then(workerId => {
      if (task.destroyed) {
        throw new Error("Loading aborted");
      }
      if (worker.destroyed) {
        throw new Error("Worker was destroyed");
      }
      const messageHandler = new MessageHandler(docId, workerId, worker.port);
      const transport = new WorkerTransport(messageHandler, task, networkStream, transportParams, transportFactory);
      task._transport = transport;
      messageHandler.send("Ready", null);
    });
  }).catch(task._capability.reject);
  return task;
}
function getUrlProp(val) {
  return null;
}
function getDataProp(val) {
  if (val instanceof Uint8Array && val.byteLength === val.buffer.byteLength) {
    return val;
  }
  if (typeof val === "string") {
    return stringToBytes(val);
  }
  if (val instanceof ArrayBuffer || ArrayBuffer.isView(val) || typeof val === "object" && !isNaN(val?.length)) {
    return new Uint8Array(val);
  }
  throw new Error("Invalid PDF binary data: either TypedArray, " + "string, or array-like object is expected in the data property.");
}
function isRefProxy(ref) {
  return typeof ref === "object" && Number.isInteger(ref?.num) && ref.num >= 0 && Number.isInteger(ref?.gen) && ref.gen >= 0;
}
class PDFDocumentLoadingTask {
  static #docId = 0;
  constructor() {
    this._capability = Promise.withResolvers();
    this._transport = null;
    this._worker = null;
    this.docId = `d${PDFDocumentLoadingTask.#docId++}`;
    this.destroyed = false;
    this.onPassword = null;
    this.onProgress = null;
  }
  get promise() {
    return this._capability.promise;
  }
  async destroy() {
    this.destroyed = true;
    try {
      if (this._worker?.port) {
        this._worker._pendingDestroy = true;
      }
      await this._transport?.destroy();
    } catch (ex) {
      if (this._worker?.port) {
        delete this._worker._pendingDestroy;
      }
      throw ex;
    }
    this._transport = null;
    if (this._worker) {
      this._worker.destroy();
      this._worker = null;
    }
  }
}
class PDFDataRangeTransport {
  constructor(length, initialData, progressiveDone = false, contentDispositionFilename = null) {
    this.length = length;
    this.initialData = initialData;
    this.progressiveDone = progressiveDone;
    this.contentDispositionFilename = contentDispositionFilename;
    this._rangeListeners = [];
    this._progressListeners = [];
    this._progressiveReadListeners = [];
    this._progressiveDoneListeners = [];
    this._readyCapability = Promise.withResolvers();
  }
  addRangeListener(listener) {
    this._rangeListeners.push(listener);
  }
  addProgressListener(listener) {
    this._progressListeners.push(listener);
  }
  addProgressiveReadListener(listener) {
    this._progressiveReadListeners.push(listener);
  }
  addProgressiveDoneListener(listener) {
    this._progressiveDoneListeners.push(listener);
  }
  onDataRange(begin, chunk) {
    for (const listener of this._rangeListeners) {
      listener(begin, chunk);
    }
  }
  onDataProgress(loaded, total) {
    this._readyCapability.promise.then(() => {
      for (const listener of this._progressListeners) {
        listener(loaded, total);
      }
    });
  }
  onDataProgressiveRead(chunk) {
    this._readyCapability.promise.then(() => {
      for (const listener of this._progressiveReadListeners) {
        listener(chunk);
      }
    });
  }
  onDataProgressiveDone() {
    this._readyCapability.promise.then(() => {
      for (const listener of this._progressiveDoneListeners) {
        listener();
      }
    });
  }
  transportReady() {
    this._readyCapability.resolve();
  }
  requestDataRange(begin, end) {
    unreachable("Abstract method PDFDataRangeTransport.requestDataRange");
  }
  abort() {}
}
class PDFDocumentProxy {
  constructor(pdfInfo, transport) {
    this._pdfInfo = pdfInfo;
    this._transport = transport;
  }
  get annotationStorage() {
    return this._transport.annotationStorage;
  }
  get filterFactory() {
    return this._transport.filterFactory;
  }
  get numPages() {
    return this._pdfInfo.numPages;
  }
  get fingerprints() {
    return this._pdfInfo.fingerprints;
  }
  get isPureXfa() {
    return shadow(this, "isPureXfa", !!this._transport._htmlForXfa);
  }
  get allXfaHtml() {
    return this._transport._htmlForXfa;
  }
  getPage(pageNumber) {
    return this._transport.getPage(pageNumber);
  }
  getPageIndex(ref) {
    return this._transport.getPageIndex(ref);
  }
  getDestinations() {
    return this._transport.getDestinations();
  }
  getDestination(id) {
    return this._transport.getDestination(id);
  }
  getPageLabels() {
    return this._transport.getPageLabels();
  }
  getPageLayout() {
    return this._transport.getPageLayout();
  }
  getPageMode() {
    return this._transport.getPageMode();
  }
  getViewerPreferences() {
    return this._transport.getViewerPreferences();
  }
  getOpenAction() {
    return this._transport.getOpenAction();
  }
  getAttachments() {
    return this._transport.getAttachments();
  }
  getJSActions() {
    return this._transport.getDocJSActions();
  }
  getOutline() {
    return this._transport.getOutline();
  }
  getOptionalContentConfig({
    intent = "display"
  } = {}) {
    const {
      renderingIntent
    } = this._transport.getRenderingIntent(intent);
    return this._transport.getOptionalContentConfig(renderingIntent);
  }
  getPermissions() {
    return this._transport.getPermissions();
  }
  getMetadata() {
    return this._transport.getMetadata();
  }
  getMarkInfo() {
    return this._transport.getMarkInfo();
  }
  getData() {
    return this._transport.getData();
  }
  saveDocument() {
    return this._transport.saveDocument();
  }
  getDownloadInfo() {
    return this._transport.downloadInfoCapability.promise;
  }
  cleanup(keepLoadedFonts = false) {
    return this._transport.startCleanup(keepLoadedFonts || this.isPureXfa);
  }
  destroy() {
    return this.loadingTask.destroy();
  }
  cachedPageNumber(ref) {
    return this._transport.cachedPageNumber(ref);
  }
  get loadingParams() {
    return this._transport.loadingParams;
  }
  get loadingTask() {
    return this._transport.loadingTask;
  }
  getFieldObjects() {
    return this._transport.getFieldObjects();
  }
  hasJSActions() {
    return this._transport.hasJSActions();
  }
  getCalculationOrderIds() {
    return this._transport.getCalculationOrderIds();
  }
}
class PDFPageProxy {
  #delayedCleanupTimeout = null;
  #pendingCleanup = false;
  constructor(pageIndex, pageInfo, transport, pdfBug = false) {
    this._pageIndex = pageIndex;
    this._pageInfo = pageInfo;
    this._transport = transport;
    this._stats = pdfBug ? new StatTimer() : null;
    this._pdfBug = pdfBug;
    this.commonObjs = transport.commonObjs;
    this.objs = new PDFObjects();
    this._maybeCleanupAfterRender = false;
    this._intentStates = new Map();
    this.destroyed = false;
  }
  get pageNumber() {
    return this._pageIndex + 1;
  }
  get rotate() {
    return this._pageInfo.rotate;
  }
  get ref() {
    return this._pageInfo.ref;
  }
  get userUnit() {
    return this._pageInfo.userUnit;
  }
  get view() {
    return this._pageInfo.view;
  }
  getViewport({
    scale,
    rotation = this.rotate,
    offsetX = 0,
    offsetY = 0,
    dontFlip = false
  } = {}) {
    return new PageViewport({
      viewBox: this.view,
      scale,
      rotation,
      offsetX,
      offsetY,
      dontFlip
    });
  }
  getAnnotations({
    intent = "display"
  } = {}) {
    const {
      renderingIntent
    } = this._transport.getRenderingIntent(intent);
    return this._transport.getAnnotations(this._pageIndex, renderingIntent);
  }
  getJSActions() {
    return this._transport.getPageJSActions(this._pageIndex);
  }
  get filterFactory() {
    return this._transport.filterFactory;
  }
  get isPureXfa() {
    return shadow(this, "isPureXfa", !!this._transport._htmlForXfa);
  }
  async getXfa() {
    return this._transport._htmlForXfa?.children[this._pageIndex] || null;
  }
  render({
    canvasContext,
    viewport,
    intent = "display",
    annotationMode = AnnotationMode.ENABLE,
    transform = null,
    background = null,
    optionalContentConfigPromise = null,
    annotationCanvasMap = null,
    pageColors = null,
    printAnnotationStorage = null,
    isEditing = false
  }) {
    this._stats?.time("Overall");
    const intentArgs = this._transport.getRenderingIntent(intent, annotationMode, printAnnotationStorage, isEditing);
    const {
      renderingIntent,
      cacheKey
    } = intentArgs;
    this.#pendingCleanup = false;
    this.#abortDelayedCleanup();
    optionalContentConfigPromise ||= this._transport.getOptionalContentConfig(renderingIntent);
    let intentState = this._intentStates.get(cacheKey);
    if (!intentState) {
      intentState = Object.create(null);
      this._intentStates.set(cacheKey, intentState);
    }
    if (intentState.streamReaderCancelTimeout) {
      clearTimeout(intentState.streamReaderCancelTimeout);
      intentState.streamReaderCancelTimeout = null;
    }
    const intentPrint = !!(renderingIntent & RenderingIntentFlag.PRINT);
    if (!intentState.displayReadyCapability) {
      intentState.displayReadyCapability = Promise.withResolvers();
      intentState.operatorList = {
        fnArray: [],
        argsArray: [],
        lastChunk: false,
        separateAnnots: null
      };
      this._stats?.time("Page Request");
      this._pumpOperatorList(intentArgs);
    }
    const complete = error => {
      intentState.renderTasks.delete(internalRenderTask);
      if (this._maybeCleanupAfterRender || intentPrint) {
        this.#pendingCleanup = true;
      }
      this.#tryCleanup(!intentPrint);
      if (error) {
        internalRenderTask.capability.reject(error);
        this._abortOperatorList({
          intentState,
          reason: error instanceof Error ? error : new Error(error)
        });
      } else {
        internalRenderTask.capability.resolve();
      }
      if (this._stats) {
        this._stats.timeEnd("Rendering");
        this._stats.timeEnd("Overall");
        if (globalThis.Stats?.enabled) {
          globalThis.Stats.add(this.pageNumber, this._stats);
        }
      }
    };
    const internalRenderTask = new InternalRenderTask({
      callback: complete,
      params: {
        canvasContext,
        viewport,
        transform,
        background
      },
      objs: this.objs,
      commonObjs: this.commonObjs,
      annotationCanvasMap,
      operatorList: intentState.operatorList,
      pageIndex: this._pageIndex,
      canvasFactory: this._transport.canvasFactory,
      filterFactory: this._transport.filterFactory,
      useRequestAnimationFrame: !intentPrint,
      pdfBug: this._pdfBug,
      pageColors
    });
    (intentState.renderTasks ||= new Set()).add(internalRenderTask);
    const renderTask = internalRenderTask.task;
    Promise.all([intentState.displayReadyCapability.promise, optionalContentConfigPromise]).then(([transparency, optionalContentConfig]) => {
      if (this.destroyed) {
        complete();
        return;
      }
      this._stats?.time("Rendering");
      if (!(optionalContentConfig.renderingIntent & renderingIntent)) {
        throw new Error("Must use the same `intent`-argument when calling the `PDFPageProxy.render` " + "and `PDFDocumentProxy.getOptionalContentConfig` methods.");
      }
      internalRenderTask.initializeGraphics({
        transparency,
        optionalContentConfig
      });
      internalRenderTask.operatorListChanged();
    }).catch(complete);
    return renderTask;
  }
  getOperatorList({
    intent = "display",
    annotationMode = AnnotationMode.ENABLE,
    printAnnotationStorage = null,
    isEditing = false
  } = {}) {
    throw new Error("Not implemented: getOperatorList");
  }
  streamTextContent({
    includeMarkedContent = false,
    disableNormalization = false
  } = {}) {
    const TEXT_CONTENT_CHUNK_SIZE = 100;
    return this._transport.messageHandler.sendWithStream("GetTextContent", {
      pageIndex: this._pageIndex,
      includeMarkedContent: includeMarkedContent === true,
      disableNormalization: disableNormalization === true
    }, {
      highWaterMark: TEXT_CONTENT_CHUNK_SIZE,
      size(textContent) {
        return textContent.items.length;
      }
    });
  }
  getTextContent(params = {}) {
    if (this._transport._htmlForXfa) {
      return this.getXfa().then(xfa => XfaText.textContent(xfa));
    }
    const readableStream = this.streamTextContent(params);
    return new Promise(function (resolve, reject) {
      function pump() {
        reader.read().then(function ({
          value,
          done
        }) {
          if (done) {
            resolve(textContent);
            return;
          }
          textContent.lang ??= value.lang;
          Object.assign(textContent.styles, value.styles);
          textContent.items.push(...value.items);
          pump();
        }, reject);
      }
      const reader = readableStream.getReader();
      const textContent = {
        items: [],
        styles: Object.create(null),
        lang: null
      };
      pump();
    });
  }
  getStructTree() {
    return this._transport.getStructTree(this._pageIndex);
  }
  _destroy() {
    this.destroyed = true;
    const waitOn = [];
    for (const intentState of this._intentStates.values()) {
      this._abortOperatorList({
        intentState,
        reason: new Error("Page was destroyed."),
        force: true
      });
      if (intentState.opListReadCapability) {
        continue;
      }
      for (const internalRenderTask of intentState.renderTasks) {
        waitOn.push(internalRenderTask.completed);
        internalRenderTask.cancel();
      }
    }
    this.objs.clear();
    this.#pendingCleanup = false;
    this.#abortDelayedCleanup();
    return Promise.all(waitOn);
  }
  cleanup(resetStats = false) {
    this.#pendingCleanup = true;
    const success = this.#tryCleanup(false);
    if (resetStats && success) {
      this._stats &&= new StatTimer();
    }
    return success;
  }
  #tryCleanup(delayed = false) {
    this.#abortDelayedCleanup();
    if (!this.#pendingCleanup || this.destroyed) {
      return false;
    }
    if (delayed) {
      this.#delayedCleanupTimeout = setTimeout(() => {
        this.#delayedCleanupTimeout = null;
        this.#tryCleanup(false);
      }, DELAYED_CLEANUP_TIMEOUT);
      return false;
    }
    for (const {
      renderTasks,
      operatorList
    } of this._intentStates.values()) {
      if (renderTasks.size > 0 || !operatorList.lastChunk) {
        return false;
      }
    }
    this._intentStates.clear();
    this.objs.clear();
    this.#pendingCleanup = false;
    return true;
  }
  #abortDelayedCleanup() {
    if (this.#delayedCleanupTimeout) {
      clearTimeout(this.#delayedCleanupTimeout);
      this.#delayedCleanupTimeout = null;
    }
  }
  _startRenderPage(transparency, cacheKey) {
    const intentState = this._intentStates.get(cacheKey);
    if (!intentState) {
      return;
    }
    this._stats?.timeEnd("Page Request");
    intentState.displayReadyCapability?.resolve(transparency);
  }
  _renderPageChunk(operatorListChunk, intentState) {
    for (let i = 0, ii = operatorListChunk.length; i < ii; i++) {
      intentState.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
      intentState.operatorList.argsArray.push(operatorListChunk.argsArray[i]);
    }
    intentState.operatorList.lastChunk = operatorListChunk.lastChunk;
    intentState.operatorList.separateAnnots = operatorListChunk.separateAnnots;
    for (const internalRenderTask of intentState.renderTasks) {
      internalRenderTask.operatorListChanged();
    }
    if (operatorListChunk.lastChunk) {
      this.#tryCleanup(true);
    }
  }
  _pumpOperatorList({
    renderingIntent,
    cacheKey,
    annotationStorageSerializable,
    modifiedIds
  }) {
    const {
      map,
      transfer
    } = annotationStorageSerializable;
    const readableStream = this._transport.messageHandler.sendWithStream("GetOperatorList", {
      pageIndex: this._pageIndex,
      intent: renderingIntent,
      cacheKey,
      annotationStorage: map,
      modifiedIds
    }, transfer);
    const reader = readableStream.getReader();
    const intentState = this._intentStates.get(cacheKey);
    intentState.streamReader = reader;
    const pump = () => {
      reader.read().then(({
        value,
        done
      }) => {
        if (done) {
          intentState.streamReader = null;
          return;
        }
        if (this._transport.destroyed) {
          return;
        }
        this._renderPageChunk(value, intentState);
        pump();
      }, reason => {
        intentState.streamReader = null;
        if (this._transport.destroyed) {
          return;
        }
        if (intentState.operatorList) {
          intentState.operatorList.lastChunk = true;
          for (const internalRenderTask of intentState.renderTasks) {
            internalRenderTask.operatorListChanged();
          }
          this.#tryCleanup(true);
        }
        if (intentState.displayReadyCapability) {
          intentState.displayReadyCapability.reject(reason);
        } else if (intentState.opListReadCapability) {
          intentState.opListReadCapability.reject(reason);
        } else {
          throw reason;
        }
      });
    };
    pump();
  }
  _abortOperatorList({
    intentState,
    reason,
    force = false
  }) {
    if (!intentState.streamReader) {
      return;
    }
    if (intentState.streamReaderCancelTimeout) {
      clearTimeout(intentState.streamReaderCancelTimeout);
      intentState.streamReaderCancelTimeout = null;
    }
    if (!force) {
      if (intentState.renderTasks.size > 0) {
        return;
      }
      if (reason instanceof RenderingCancelledException) {
        let delay = RENDERING_CANCELLED_TIMEOUT;
        if (reason.extraDelay > 0 && reason.extraDelay < 1000) {
          delay += reason.extraDelay;
        }
        intentState.streamReaderCancelTimeout = setTimeout(() => {
          intentState.streamReaderCancelTimeout = null;
          this._abortOperatorList({
            intentState,
            reason,
            force: true
          });
        }, delay);
        return;
      }
    }
    intentState.streamReader.cancel(new AbortException(reason.message)).catch(() => {});
    intentState.streamReader = null;
    if (this._transport.destroyed) {
      return;
    }
    for (const [curCacheKey, curIntentState] of this._intentStates) {
      if (curIntentState === intentState) {
        this._intentStates.delete(curCacheKey);
        break;
      }
    }
    this.cleanup();
  }
  get stats() {
    return this._stats;
  }
}
class LoopbackPort {
  #listeners = new Set();
  #deferred = Promise.resolve();
  postMessage(obj, transfer) {
    const event = {
      data: structuredClone(obj, transfer ? {
        transfer
      } : null)
    };
    this.#deferred.then(() => {
      for (const listener of this.#listeners) {
        listener.call(this, event);
      }
    });
  }
  addEventListener(name, listener) {
    this.#listeners.add(listener);
  }
  removeEventListener(name, listener) {
    this.#listeners.delete(listener);
  }
  terminate() {
    this.#listeners.clear();
  }
}
class PDFWorker {
  static #fakeWorkerId = 0;
  static #isWorkerDisabled = false;
  static #workerPorts;
  constructor({
    name = null,
    port = null,
    verbosity = getVerbosityLevel()
  } = {}) {
    this.name = name;
    this.destroyed = false;
    this.verbosity = verbosity;
    this._readyCapability = Promise.withResolvers();
    this._port = null;
    this._webWorker = null;
    this._messageHandler = null;
    this._initialize();
  }
  get promise() {
    return this._readyCapability.promise;
  }
  #resolve() {
    this._readyCapability.resolve();
    this._messageHandler.send("configure", {
      verbosity: this.verbosity
    });
  }
  get port() {
    return this._port;
  }
  get messageHandler() {
    return this._messageHandler;
  }
  _initializeFromPort(port) {
    throw new Error("Not implemented: _initializeFromPort");
  }
  _initialize() {
    if (PDFWorker.#isWorkerDisabled || PDFWorker.#mainThreadWorkerMessageHandler) {
      this._setupFakeWorker();
      return;
    }
    let {
      workerSrc
    } = PDFWorker;
    try {
      const worker = new Worker(workerSrc, {
        type: "module"
      });
      const messageHandler = new MessageHandler("main", "worker", worker);
      const terminateEarly = () => {
        ac.abort();
        messageHandler.destroy();
        worker.terminate();
        if (this.destroyed) {
          this._readyCapability.reject(new Error("Worker was destroyed"));
        } else {
          this._setupFakeWorker();
        }
      };
      const ac = new AbortController();
      worker.addEventListener("error", () => {
        if (!this._webWorker) {
          terminateEarly();
        }
      }, {
        signal: ac.signal
      });
      messageHandler.on("test", data => {
        ac.abort();
        if (this.destroyed || !data) {
          terminateEarly();
          return;
        }
        this._messageHandler = messageHandler;
        this._port = worker;
        this._webWorker = worker;
        this.#resolve();
      });
      messageHandler.on("ready", data => {
        ac.abort();
        if (this.destroyed) {
          terminateEarly();
          return;
        }
        try {
          sendTest();
        } catch {
          this._setupFakeWorker();
        }
      });
      const sendTest = () => {
        const testObj = new Uint8Array();
        messageHandler.send("test", testObj, [testObj.buffer]);
      };
      sendTest();
      return;
    } catch {
      info("The worker has been disabled.");
    }
    this._setupFakeWorker();
  }
  _setupFakeWorker() {
    if (!PDFWorker.#isWorkerDisabled) {
      warn("Setting up fake worker.");
      PDFWorker.#isWorkerDisabled = true;
    }
    PDFWorker._setupFakeWorkerGlobal.then(WorkerMessageHandler => {
      if (this.destroyed) {
        this._readyCapability.reject(new Error("Worker was destroyed"));
        return;
      }
      const port = new LoopbackPort();
      this._port = port;
      const id = `fake${PDFWorker.#fakeWorkerId++}`;
      const workerHandler = new MessageHandler(id + "_worker", id, port);
      WorkerMessageHandler.setup(workerHandler, port);
      this._messageHandler = new MessageHandler(id, id + "_worker", port);
      this.#resolve();
    }).catch(reason => {
      this._readyCapability.reject(new Error(`Setting up fake worker failed: "${reason.message}".`));
    });
  }
  destroy() {
    this.destroyed = true;
    if (this._webWorker) {
      this._webWorker.terminate();
      this._webWorker = null;
    }
    PDFWorker.#workerPorts?.delete(this._port);
    this._port = null;
    if (this._messageHandler) {
      this._messageHandler.destroy();
      this._messageHandler = null;
    }
  }
  static fromPort(params) {
    throw new Error("Not implemented: fromPort");
  }
  static get workerSrc() {
    if (GlobalWorkerOptions.workerSrc) {
      return GlobalWorkerOptions.workerSrc;
    }
    throw new Error('No "GlobalWorkerOptions.workerSrc" specified.');
  }
  static get #mainThreadWorkerMessageHandler() {
    try {
      return globalThis.pdfjsWorker?.WorkerMessageHandler || null;
    } catch {
      return null;
    }
  }
  static get _setupFakeWorkerGlobal() {
    const loader = async () => {
      if (this.#mainThreadWorkerMessageHandler) {
        return this.#mainThreadWorkerMessageHandler;
      }
      const worker = await import( /*webpackIgnore: true*/this.workerSrc);
      return worker.WorkerMessageHandler;
    };
    return shadow(this, "_setupFakeWorkerGlobal", loader());
  }
}
class WorkerTransport {
  #methodPromises = new Map();
  #pageCache = new Map();
  #pagePromises = new Map();
  #pageRefCache = new Map();
  #passwordCapability = null;
  constructor(messageHandler, loadingTask, networkStream, params, factory) {
    this.messageHandler = messageHandler;
    this.loadingTask = loadingTask;
    this.commonObjs = new PDFObjects();
    this.fontLoader = new FontLoader({
      ownerDocument: params.ownerDocument,
      styleElement: params.styleElement
    });
    this.loadingParams = params.loadingParams;
    this._params = params;
    this.canvasFactory = factory.canvasFactory;
    this.filterFactory = factory.filterFactory;
    this.cMapReaderFactory = factory.cMapReaderFactory;
    this.standardFontDataFactory = factory.standardFontDataFactory;
    this.destroyed = false;
    this.destroyCapability = null;
    this._networkStream = networkStream;
    this._fullReader = null;
    this._lastProgress = null;
    this.downloadInfoCapability = Promise.withResolvers();
    this.setupMessageHandler();
  }
  #cacheSimpleMethod(name, data = null) {
    const cachedPromise = this.#methodPromises.get(name);
    if (cachedPromise) {
      return cachedPromise;
    }
    const promise = this.messageHandler.sendWithPromise(name, data);
    this.#methodPromises.set(name, promise);
    return promise;
  }
  get annotationStorage() {
    return shadow(this, "annotationStorage", new AnnotationStorage());
  }
  getRenderingIntent(intent, annotationMode = AnnotationMode.ENABLE, printAnnotationStorage = null, isEditing = false, isOpList = false) {
    let renderingIntent = RenderingIntentFlag.DISPLAY;
    let annotationStorageSerializable = SerializableEmpty;
    switch (intent) {
      case "any":
        renderingIntent = RenderingIntentFlag.ANY;
        break;
      case "display":
        break;
      case "print":
        renderingIntent = RenderingIntentFlag.PRINT;
        break;
      default:
        warn(`getRenderingIntent - invalid intent: ${intent}`);
    }
    const annotationStorage = renderingIntent & RenderingIntentFlag.PRINT && printAnnotationStorage instanceof PrintAnnotationStorage ? printAnnotationStorage : this.annotationStorage;
    switch (annotationMode) {
      case AnnotationMode.DISABLE:
        renderingIntent += RenderingIntentFlag.ANNOTATIONS_DISABLE;
        break;
      case AnnotationMode.ENABLE:
        break;
      case AnnotationMode.ENABLE_FORMS:
        renderingIntent += RenderingIntentFlag.ANNOTATIONS_FORMS;
        break;
      case AnnotationMode.ENABLE_STORAGE:
        renderingIntent += RenderingIntentFlag.ANNOTATIONS_STORAGE;
        annotationStorageSerializable = annotationStorage.serializable;
        break;
      default:
        warn(`getRenderingIntent - invalid annotationMode: ${annotationMode}`);
    }
    if (isEditing) {
      renderingIntent += RenderingIntentFlag.IS_EDITING;
    }
    if (isOpList) {
      renderingIntent += RenderingIntentFlag.OPLIST;
    }
    const {
      ids: modifiedIds,
      hash: modifiedIdsHash
    } = annotationStorage.modifiedIds;
    const cacheKeyBuf = [renderingIntent, annotationStorageSerializable.hash, modifiedIdsHash];
    return {
      renderingIntent,
      cacheKey: cacheKeyBuf.join("_"),
      annotationStorageSerializable,
      modifiedIds
    };
  }
  destroy() {
    if (this.destroyCapability) {
      return this.destroyCapability.promise;
    }
    this.destroyed = true;
    this.destroyCapability = Promise.withResolvers();
    this.#passwordCapability?.reject(new Error("Worker was destroyed during onPassword callback"));
    const waitOn = [];
    for (const page of this.#pageCache.values()) {
      waitOn.push(page._destroy());
    }
    this.#pageCache.clear();
    this.#pagePromises.clear();
    this.#pageRefCache.clear();
    if (this.hasOwnProperty("annotationStorage")) {
      this.annotationStorage.resetModified();
    }
    const terminated = this.messageHandler.sendWithPromise("Terminate", null);
    waitOn.push(terminated);
    Promise.all(waitOn).then(() => {
      this.commonObjs.clear();
      this.fontLoader.clear();
      this.#methodPromises.clear();
      this.filterFactory.destroy();
      TextLayer.cleanup();
      this._networkStream?.cancelAllRequests(new AbortException("Worker was terminated."));
      if (this.messageHandler) {
        this.messageHandler.destroy();
        this.messageHandler = null;
      }
      this.destroyCapability.resolve();
    }, this.destroyCapability.reject);
    return this.destroyCapability.promise;
  }
  setupMessageHandler() {
    const {
      messageHandler,
      loadingTask
    } = this;
    messageHandler.on("GetReader", (data, sink) => {
      assert(this._networkStream, "GetReader - no `IPDFStream` instance available.");
      this._fullReader = this._networkStream.getFullReader();
      this._fullReader.onProgress = evt => {
        this._lastProgress = {
          loaded: evt.loaded,
          total: evt.total
        };
      };
      sink.onPull = () => {
        this._fullReader.read().then(function ({
          value,
          done
        }) {
          if (done) {
            sink.close();
            return;
          }
          assert(value instanceof ArrayBuffer, "GetReader - expected an ArrayBuffer.");
          sink.enqueue(new Uint8Array(value), 1, [value]);
        }).catch(reason => {
          sink.error(reason);
        });
      };
      sink.onCancel = reason => {
        this._fullReader.cancel(reason);
        sink.ready.catch(readyReason => {
          if (this.destroyed) {
            return;
          }
          throw readyReason;
        });
      };
    });
    messageHandler.on("ReaderHeadersReady", data => {
      const headersCapability = Promise.withResolvers();
      const fullReader = this._fullReader;
      fullReader.headersReady.then(() => {
        if (!fullReader.isStreamingSupported || !fullReader.isRangeSupported) {
          if (this._lastProgress) {
            loadingTask.onProgress?.(this._lastProgress);
          }
          fullReader.onProgress = evt => {
            loadingTask.onProgress?.({
              loaded: evt.loaded,
              total: evt.total
            });
          };
        }
        headersCapability.resolve({
          isStreamingSupported: fullReader.isStreamingSupported,
          isRangeSupported: fullReader.isRangeSupported,
          contentLength: fullReader.contentLength
        });
      }, headersCapability.reject);
      return headersCapability.promise;
    });
    messageHandler.on("GetRangeReader", (data, sink) => {
      assert(this._networkStream, "GetRangeReader - no `IPDFStream` instance available.");
      const rangeReader = this._networkStream.getRangeReader(data.begin, data.end);
      if (!rangeReader) {
        sink.close();
        return;
      }
      sink.onPull = () => {
        rangeReader.read().then(function ({
          value,
          done
        }) {
          if (done) {
            sink.close();
            return;
          }
          assert(value instanceof ArrayBuffer, "GetRangeReader - expected an ArrayBuffer.");
          sink.enqueue(new Uint8Array(value), 1, [value]);
        }).catch(reason => {
          sink.error(reason);
        });
      };
      sink.onCancel = reason => {
        rangeReader.cancel(reason);
        sink.ready.catch(readyReason => {
          if (this.destroyed) {
            return;
          }
          throw readyReason;
        });
      };
    });
    messageHandler.on("GetDoc", ({
      pdfInfo
    }) => {
      this._numPages = pdfInfo.numPages;
      this._htmlForXfa = pdfInfo.htmlForXfa;
      delete pdfInfo.htmlForXfa;
      loadingTask._capability.resolve(new PDFDocumentProxy(pdfInfo, this));
    });
    messageHandler.on("DocException", function (ex) {
      let reason;
      switch (ex.name) {
        case "PasswordException":
          reason = new PasswordException(ex.message, ex.code);
          break;
        case "InvalidPDFException":
          reason = new InvalidPDFException(ex.message);
          break;
        case "MissingPDFException":
          reason = new MissingPDFException(ex.message);
          break;
        case "UnexpectedResponseException":
          reason = new UnexpectedResponseException(ex.message, ex.status);
          break;
        case "UnknownErrorException":
          reason = new UnknownErrorException(ex.message, ex.details);
          break;
        default:
          unreachable("DocException - expected a valid Error.");
      }
      loadingTask._capability.reject(reason);
    });
    messageHandler.on("PasswordRequest", exception => {
      this.#passwordCapability = Promise.withResolvers();
      if (loadingTask.onPassword) {
        const updatePassword = password => {
          if (password instanceof Error) {
            this.#passwordCapability.reject(password);
          } else {
            this.#passwordCapability.resolve({
              password
            });
          }
        };
        try {
          loadingTask.onPassword(updatePassword, exception.code);
        } catch (ex) {
          this.#passwordCapability.reject(ex);
        }
      } else {
        this.#passwordCapability.reject(new PasswordException(exception.message, exception.code));
      }
      return this.#passwordCapability.promise;
    });
    messageHandler.on("DataLoaded", data => {
      loadingTask.onProgress?.({
        loaded: data.length,
        total: data.length
      });
      this.downloadInfoCapability.resolve(data);
    });
    messageHandler.on("StartRenderPage", data => {
      if (this.destroyed) {
        return;
      }
      const page = this.#pageCache.get(data.pageIndex);
      page._startRenderPage(data.transparency, data.cacheKey);
    });
    messageHandler.on("commonobj", ([id, type, exportedData]) => {
      if (this.destroyed) {
        return null;
      }
      if (this.commonObjs.has(id)) {
        return null;
      }
      switch (type) {
        case "Font":
          const {
            disableFontFace,
            fontExtraProperties,
            pdfBug
          } = this._params;
          if ("error" in exportedData) {
            const exportedError = exportedData.error;
            warn(`Error during font loading: ${exportedError}`);
            this.commonObjs.resolve(id, exportedError);
            break;
          }
          const inspectFont = pdfBug && globalThis.FontInspector?.enabled ? (font, url) => globalThis.FontInspector.fontAdded(font, url) : null;
          const font = new FontFaceObject(exportedData, {
            disableFontFace,
            inspectFont
          });
          this.fontLoader.bind(font).catch(() => messageHandler.sendWithPromise("FontFallback", {
            id
          })).finally(() => {
            if (!fontExtraProperties && font.data) {
              font.data = null;
            }
            this.commonObjs.resolve(id, font);
          });
          break;
        case "CopyLocalImage":
          const {
            imageRef
          } = exportedData;
          assert(imageRef, "The imageRef must be defined.");
          for (const pageProxy of this.#pageCache.values()) {
            for (const [, data] of pageProxy.objs) {
              if (data?.ref !== imageRef) {
                continue;
              }
              if (!data.dataLen) {
                return null;
              }
              this.commonObjs.resolve(id, structuredClone(data));
              return data.dataLen;
            }
          }
          break;
        case "FontPath":
        case "Image":
        case "Pattern":
          this.commonObjs.resolve(id, exportedData);
          break;
        default:
          throw new Error(`Got unknown common object type ${type}`);
      }
      return null;
    });
    messageHandler.on("obj", ([id, pageIndex, type, imageData]) => {
      if (this.destroyed) {
        return;
      }
      const pageProxy = this.#pageCache.get(pageIndex);
      if (pageProxy.objs.has(id)) {
        return;
      }
      if (pageProxy._intentStates.size === 0) {
        imageData?.bitmap?.close();
        return;
      }
      switch (type) {
        case "Image":
          pageProxy.objs.resolve(id, imageData);
          if (imageData?.dataLen > MAX_IMAGE_SIZE_TO_CACHE) {
            pageProxy._maybeCleanupAfterRender = true;
          }
          break;
        case "Pattern":
          pageProxy.objs.resolve(id, imageData);
          break;
        default:
          throw new Error(`Got unknown object type ${type}`);
      }
    });
    messageHandler.on("DocProgress", data => {
      if (this.destroyed) {
        return;
      }
      loadingTask.onProgress?.({
        loaded: data.loaded,
        total: data.total
      });
    });
    messageHandler.on("FetchBuiltInCMap", data => {
      if (this.destroyed) {
        return Promise.reject(new Error("Worker was destroyed."));
      }
      if (!this.cMapReaderFactory) {
        return Promise.reject(new Error("CMapReaderFactory not initialized, see the `useWorkerFetch` parameter."));
      }
      return this.cMapReaderFactory.fetch(data);
    });
    messageHandler.on("FetchStandardFontData", data => {
      if (this.destroyed) {
        return Promise.reject(new Error("Worker was destroyed."));
      }
      if (!this.standardFontDataFactory) {
        return Promise.reject(new Error("StandardFontDataFactory not initialized, see the `useWorkerFetch` parameter."));
      }
      return this.standardFontDataFactory.fetch(data);
    });
  }
  getData() {
    return this.messageHandler.sendWithPromise("GetData", null);
  }
  saveDocument() {
    if (this.annotationStorage.size <= 0) {
      warn("saveDocument called while `annotationStorage` is empty, " + "please use the getData-method instead.");
    }
    const {
      map,
      transfer
    } = this.annotationStorage.serializable;
    return this.messageHandler.sendWithPromise("SaveDocument", {
      isPureXfa: !!this._htmlForXfa,
      numPages: this._numPages,
      annotationStorage: map,
      filename: this._fullReader?.filename ?? null
    }, transfer).finally(() => {
      this.annotationStorage.resetModified();
    });
  }
  getPage(pageNumber) {
    if (!Number.isInteger(pageNumber) || pageNumber <= 0 || pageNumber > this._numPages) {
      return Promise.reject(new Error("Invalid page request."));
    }
    const pageIndex = pageNumber - 1,
      cachedPromise = this.#pagePromises.get(pageIndex);
    if (cachedPromise) {
      return cachedPromise;
    }
    const promise = this.messageHandler.sendWithPromise("GetPage", {
      pageIndex
    }).then(pageInfo => {
      if (this.destroyed) {
        throw new Error("Transport destroyed");
      }
      if (pageInfo.refStr) {
        this.#pageRefCache.set(pageInfo.refStr, pageNumber);
      }
      const page = new PDFPageProxy(pageIndex, pageInfo, this, this._params.pdfBug);
      this.#pageCache.set(pageIndex, page);
      return page;
    });
    this.#pagePromises.set(pageIndex, promise);
    return promise;
  }
  getPageIndex(ref) {
    if (!isRefProxy(ref)) {
      return Promise.reject(new Error("Invalid pageIndex request."));
    }
    return this.messageHandler.sendWithPromise("GetPageIndex", {
      num: ref.num,
      gen: ref.gen
    });
  }
  getAnnotations(pageIndex, intent) {
    return this.messageHandler.sendWithPromise("GetAnnotations", {
      pageIndex,
      intent
    });
  }
  getFieldObjects() {
    return this.#cacheSimpleMethod("GetFieldObjects");
  }
  hasJSActions() {
    return this.#cacheSimpleMethod("HasJSActions");
  }
  getCalculationOrderIds() {
    return this.messageHandler.sendWithPromise("GetCalculationOrderIds", null);
  }
  getDestinations() {
    return this.messageHandler.sendWithPromise("GetDestinations", null);
  }
  getDestination(id) {
    if (typeof id !== "string") {
      return Promise.reject(new Error("Invalid destination request."));
    }
    return this.messageHandler.sendWithPromise("GetDestination", {
      id
    });
  }
  getPageLabels() {
    return this.messageHandler.sendWithPromise("GetPageLabels", null);
  }
  getPageLayout() {
    return this.messageHandler.sendWithPromise("GetPageLayout", null);
  }
  getPageMode() {
    return this.messageHandler.sendWithPromise("GetPageMode", null);
  }
  getViewerPreferences() {
    return this.messageHandler.sendWithPromise("GetViewerPreferences", null);
  }
  getOpenAction() {
    return this.messageHandler.sendWithPromise("GetOpenAction", null);
  }
  getAttachments() {
    return this.messageHandler.sendWithPromise("GetAttachments", null);
  }
  getDocJSActions() {
    return this.#cacheSimpleMethod("GetDocJSActions");
  }
  getPageJSActions(pageIndex) {
    return this.messageHandler.sendWithPromise("GetPageJSActions", {
      pageIndex
    });
  }
  getStructTree(pageIndex) {
    return this.messageHandler.sendWithPromise("GetStructTree", {
      pageIndex
    });
  }
  getOutline() {
    return this.messageHandler.sendWithPromise("GetOutline", null);
  }
  getOptionalContentConfig(renderingIntent) {
    return this.#cacheSimpleMethod("GetOptionalContentConfig").then(data => new OptionalContentConfig(data, renderingIntent));
  }
  getPermissions() {
    return this.messageHandler.sendWithPromise("GetPermissions", null);
  }
  getMetadata() {
    const name = "GetMetadata",
      cachedPromise = this.#methodPromises.get(name);
    if (cachedPromise) {
      return cachedPromise;
    }
    const promise = this.messageHandler.sendWithPromise(name, null).then(results => ({
      info: results[0],
      metadata: results[1] ? new Metadata(results[1]) : null,
      contentDispositionFilename: this._fullReader?.filename ?? null,
      contentLength: this._fullReader?.contentLength ?? null
    }));
    this.#methodPromises.set(name, promise);
    return promise;
  }
  getMarkInfo() {
    return this.messageHandler.sendWithPromise("GetMarkInfo", null);
  }
  async startCleanup(keepLoadedFonts = false) {
    if (this.destroyed) {
      return;
    }
    await this.messageHandler.sendWithPromise("Cleanup", null);
    for (const page of this.#pageCache.values()) {
      const cleanupSuccessful = page.cleanup();
      if (!cleanupSuccessful) {
        throw new Error(`startCleanup: Page ${page.pageNumber} is currently rendering.`);
      }
    }
    this.commonObjs.clear();
    if (!keepLoadedFonts) {
      this.fontLoader.clear();
    }
    this.#methodPromises.clear();
    this.filterFactory.destroy(true);
    TextLayer.cleanup();
  }
  cachedPageNumber(ref) {
    if (!isRefProxy(ref)) {
      return null;
    }
    const refStr = ref.gen === 0 ? `${ref.num}R` : `${ref.num}R${ref.gen}`;
    return this.#pageRefCache.get(refStr) ?? null;
  }
}
const INITIAL_DATA = Symbol("INITIAL_DATA");
class PDFObjects {
  #objs = Object.create(null);
  #ensureObj(objId) {
    return this.#objs[objId] ||= {
      ...Promise.withResolvers(),
      data: INITIAL_DATA
    };
  }
  get(objId, callback = null) {
    if (callback) {
      const obj = this.#ensureObj(objId);
      obj.promise.then(() => callback(obj.data));
      return null;
    }
    const obj = this.#objs[objId];
    if (!obj || obj.data === INITIAL_DATA) {
      throw new Error(`Requesting object that isn't resolved yet ${objId}.`);
    }
    return obj.data;
  }
  has(objId) {
    const obj = this.#objs[objId];
    return !!obj && obj.data !== INITIAL_DATA;
  }
  resolve(objId, data = null) {
    const obj = this.#ensureObj(objId);
    obj.data = data;
    obj.resolve();
  }
  clear() {
    for (const objId in this.#objs) {
      const {
        data
      } = this.#objs[objId];
      data?.bitmap?.close();
    }
    this.#objs = Object.create(null);
  }
  *[Symbol.iterator]() {
    for (const objId in this.#objs) {
      const {
        data
      } = this.#objs[objId];
      if (data === INITIAL_DATA) {
        continue;
      }
      yield [objId, data];
    }
  }
}
class RenderTask {
  #internalRenderTask = null;
  constructor(internalRenderTask) {
    this.#internalRenderTask = internalRenderTask;
    this.onContinue = null;
  }
  get promise() {
    return this.#internalRenderTask.capability.promise;
  }
  cancel(extraDelay = 0) {
    this.#internalRenderTask.cancel(null, extraDelay);
  }
  get separateAnnots() {
    const {
      separateAnnots
    } = this.#internalRenderTask.operatorList;
    if (!separateAnnots) {
      return false;
    }
    const {
      annotationCanvasMap
    } = this.#internalRenderTask;
    return separateAnnots.form || separateAnnots.canvas && annotationCanvasMap?.size > 0;
  }
}
class InternalRenderTask {
  #rAF = null;
  static #canvasInUse = new WeakSet();
  constructor({
    callback,
    params,
    objs,
    commonObjs,
    annotationCanvasMap,
    operatorList,
    pageIndex,
    canvasFactory,
    filterFactory,
    useRequestAnimationFrame = false,
    pdfBug = false,
    pageColors = null
  }) {
    this.callback = callback;
    this.params = params;
    this.objs = objs;
    this.commonObjs = commonObjs;
    this.annotationCanvasMap = annotationCanvasMap;
    this.operatorListIdx = null;
    this.operatorList = operatorList;
    this._pageIndex = pageIndex;
    this.canvasFactory = canvasFactory;
    this.filterFactory = filterFactory;
    this._pdfBug = pdfBug;
    this.pageColors = pageColors;
    this.running = false;
    this.graphicsReadyCallback = null;
    this.graphicsReady = false;
    this._useRequestAnimationFrame = useRequestAnimationFrame === true && typeof window !== "undefined";
    this.cancelled = false;
    this.capability = Promise.withResolvers();
    this.task = new RenderTask(this);
    this._cancelBound = this.cancel.bind(this);
    this._continueBound = this._continue.bind(this);
    this._scheduleNextBound = this._scheduleNext.bind(this);
    this._nextBound = this._next.bind(this);
    this._canvas = params.canvasContext.canvas;
  }
  get completed() {
    return this.capability.promise.catch(function () {});
  }
  initializeGraphics({
    transparency = false,
    optionalContentConfig
  }) {
    if (this.cancelled) {
      return;
    }
    if (this._canvas) {
      if (InternalRenderTask.#canvasInUse.has(this._canvas)) {
        throw new Error("Cannot use the same canvas during multiple render() operations. " + "Use different canvas or ensure previous operations were " + "cancelled or completed.");
      }
      InternalRenderTask.#canvasInUse.add(this._canvas);
    }
    if (this._pdfBug && globalThis.StepperManager?.enabled) {
      this.stepper = globalThis.StepperManager.create(this._pageIndex);
      this.stepper.init(this.operatorList);
      this.stepper.nextBreakPoint = this.stepper.getNextBreakPoint();
    }
    const {
      canvasContext,
      viewport,
      transform,
      background
    } = this.params;
    this.gfx = new CanvasGraphics(canvasContext, this.commonObjs, this.objs, this.canvasFactory, this.filterFactory, {
      optionalContentConfig
    }, this.annotationCanvasMap, this.pageColors);
    this.gfx.beginDrawing({
      transform,
      viewport,
      transparency,
      background
    });
    this.operatorListIdx = 0;
    this.graphicsReady = true;
    this.graphicsReadyCallback?.();
  }
  cancel(error = null, extraDelay = 0) {
    this.running = false;
    this.cancelled = true;
    this.gfx?.endDrawing();
    if (this.#rAF) {
      window.cancelAnimationFrame(this.#rAF);
      this.#rAF = null;
    }
    InternalRenderTask.#canvasInUse.delete(this._canvas);
    this.callback(error || new RenderingCancelledException(`Rendering cancelled, page ${this._pageIndex + 1}`, extraDelay));
  }
  operatorListChanged() {
    if (!this.graphicsReady) {
      this.graphicsReadyCallback ||= this._continueBound;
      return;
    }
    this.stepper?.updateOperatorList(this.operatorList);
    if (this.running) {
      return;
    }
    this._continue();
  }
  _continue() {
    this.running = true;
    if (this.cancelled) {
      return;
    }
    if (this.task.onContinue) {
      this.task.onContinue(this._scheduleNextBound);
    } else {
      this._scheduleNext();
    }
  }
  _scheduleNext() {
    if (this._useRequestAnimationFrame) {
      this.#rAF = window.requestAnimationFrame(() => {
        this.#rAF = null;
        this._nextBound().catch(this._cancelBound);
      });
    } else {
      Promise.resolve().then(this._nextBound).catch(this._cancelBound);
    }
  }
  async _next() {
    if (this.cancelled) {
      return;
    }
    this.operatorListIdx = this.gfx.executeOperatorList(this.operatorList, this.operatorListIdx, this._continueBound, this.stepper);
    if (this.operatorListIdx === this.operatorList.argsArray.length) {
      this.running = false;
      if (this.operatorList.lastChunk) {
        this.gfx.endDrawing();
        InternalRenderTask.#canvasInUse.delete(this._canvas);
        this.callback();
      }
    }
  }
}
const version = "4.6.60";
const build = "10a846417";

;// CONCATENATED MODULE: ./src/shared/scripting_utils.js
function makeColorComp(n) {
  return Math.floor(Math.max(0, Math.min(1, n)) * 255).toString(16).padStart(2, "0");
}
function scaleAndClamp(x) {
  return Math.max(0, Math.min(255, 255 * x));
}
class ColorConverters {
  static CMYK_G([c, y, m, k]) {
    return ["G", 1 - Math.min(1, 0.3 * c + 0.59 * m + 0.11 * y + k)];
  }
  static G_CMYK([g]) {
    return ["CMYK", 0, 0, 0, 1 - g];
  }
  static G_RGB([g]) {
    return ["RGB", g, g, g];
  }
  static G_rgb([g]) {
    g = scaleAndClamp(g);
    return [g, g, g];
  }
  static G_HTML([g]) {
    const G = makeColorComp(g);
    return `#${G}${G}${G}`;
  }
  static RGB_G([r, g, b]) {
    return ["G", 0.3 * r + 0.59 * g + 0.11 * b];
  }
  static RGB_rgb(color) {
    return color.map(scaleAndClamp);
  }
  static RGB_HTML(color) {
    return `#${color.map(makeColorComp).join("")}`;
  }
  static T_HTML() {
    return "#00000000";
  }
  static T_rgb() {
    return [null];
  }
  static CMYK_RGB([c, y, m, k]) {
    return ["RGB", 1 - Math.min(1, c + k), 1 - Math.min(1, m + k), 1 - Math.min(1, y + k)];
  }
  static CMYK_rgb([c, y, m, k]) {
    return [scaleAndClamp(1 - Math.min(1, c + k)), scaleAndClamp(1 - Math.min(1, m + k)), scaleAndClamp(1 - Math.min(1, y + k))];
  }
  static CMYK_HTML(components) {
    const rgb = this.CMYK_RGB(components).slice(1);
    return this.RGB_HTML(rgb);
  }
  static RGB_CMYK([r, g, b]) {
    const c = 1 - r;
    const m = 1 - g;
    const y = 1 - b;
    const k = Math.min(c, m, y);
    return ["CMYK", c, m, y, k];
  }
}

;// CONCATENATED MODULE: ./src/display/xfa_layer.js

class XfaLayer {
  static setupStorage(html, id, element, storage, intent) {
    const storedData = storage.getValue(id, {
      value: null
    });
    switch (element.name) {
      case "textarea":
        if (storedData.value !== null) {
          html.textContent = storedData.value;
        }
        if (intent === "print") {
          break;
        }
        html.addEventListener("input", event => {
          storage.setValue(id, {
            value: event.target.value
          });
        });
        break;
      case "input":
        if (element.attributes.type === "radio" || element.attributes.type === "checkbox") {
          if (storedData.value === element.attributes.xfaOn) {
            html.setAttribute("checked", true);
          } else if (storedData.value === element.attributes.xfaOff) {
            html.removeAttribute("checked");
          }
          if (intent === "print") {
            break;
          }
          html.addEventListener("change", event => {
            storage.setValue(id, {
              value: event.target.checked ? event.target.getAttribute("xfaOn") : event.target.getAttribute("xfaOff")
            });
          });
        } else {
          if (storedData.value !== null) {
            html.setAttribute("value", storedData.value);
          }
          if (intent === "print") {
            break;
          }
          html.addEventListener("input", event => {
            storage.setValue(id, {
              value: event.target.value
            });
          });
        }
        break;
      case "select":
        if (storedData.value !== null) {
          html.setAttribute("value", storedData.value);
          for (const option of element.children) {
            if (option.attributes.value === storedData.value) {
              option.attributes.selected = true;
            } else if (option.attributes.hasOwnProperty("selected")) {
              delete option.attributes.selected;
            }
          }
        }
        html.addEventListener("input", event => {
          const options = event.target.options;
          const value = options.selectedIndex === -1 ? "" : options[options.selectedIndex].value;
          storage.setValue(id, {
            value
          });
        });
        break;
    }
  }
  static setAttributes({
    html,
    element,
    storage = null,
    intent,
    linkService
  }) {
    const {
      attributes
    } = element;
    const isHTMLAnchorElement = html instanceof HTMLAnchorElement;
    if (attributes.type === "radio") {
      attributes.name = `${attributes.name}-${intent}`;
    }
    for (const [key, value] of Object.entries(attributes)) {
      if (value === null || value === undefined) {
        continue;
      }
      switch (key) {
        case "class":
          if (value.length) {
            html.setAttribute(key, value.join(" "));
          }
          break;
        case "dataId":
          break;
        case "id":
          html.setAttribute("data-element-id", value);
          break;
        case "style":
          Object.assign(html.style, value);
          break;
        case "textContent":
          html.textContent = value;
          break;
        default:
          if (!isHTMLAnchorElement || key !== "href" && key !== "newWindow") {
            html.setAttribute(key, value);
          }
      }
    }
    if (isHTMLAnchorElement) {
      linkService.addLinkAttributes(html, attributes.href, attributes.newWindow);
    }
    if (storage && attributes.dataId) {
      this.setupStorage(html, attributes.dataId, element, storage);
    }
  }
  static render(parameters) {
    const storage = parameters.annotationStorage;
    const linkService = parameters.linkService;
    const root = parameters.xfaHtml;
    const intent = parameters.intent || "display";
    const rootHtml = document.createElement(root.name);
    if (root.attributes) {
      this.setAttributes({
        html: rootHtml,
        element: root,
        intent,
        linkService
      });
    }
    const isNotForRichText = intent !== "richText";
    const rootDiv = parameters.div;
    rootDiv.append(rootHtml);
    if (parameters.viewport) {
      const transform = `matrix(${parameters.viewport.transform.join(",")})`;
      rootDiv.style.transform = transform;
    }
    if (isNotForRichText) {
      rootDiv.setAttribute("class", "xfaLayer xfaFont");
    }
    const textDivs = [];
    if (root.children.length === 0) {
      if (root.value) {
        const node = document.createTextNode(root.value);
        rootHtml.append(node);
        if (isNotForRichText && XfaText.shouldBuildText(root.name)) {
          textDivs.push(node);
        }
      }
      return {
        textDivs
      };
    }
    const stack = [[root, -1, rootHtml]];
    while (stack.length > 0) {
      const [parent, i, html] = stack.at(-1);
      if (i + 1 === parent.children.length) {
        stack.pop();
        continue;
      }
      const child = parent.children[++stack.at(-1)[1]];
      if (child === null) {
        continue;
      }
      const {
        name
      } = child;
      if (name === "#text") {
        const node = document.createTextNode(child.value);
        textDivs.push(node);
        html.append(node);
        continue;
      }
      const childHtml = child?.attributes?.xmlns ? document.createElementNS(child.attributes.xmlns, name) : document.createElement(name);
      html.append(childHtml);
      if (child.attributes) {
        this.setAttributes({
          html: childHtml,
          element: child,
          storage,
          intent,
          linkService
        });
      }
      if (child.children?.length > 0) {
        stack.push([child, -1, childHtml]);
      } else if (child.value) {
        const node = document.createTextNode(child.value);
        if (isNotForRichText && XfaText.shouldBuildText(name)) {
          textDivs.push(node);
        }
        childHtml.append(node);
      }
    }
    for (const el of rootDiv.querySelectorAll(".xfaNonInteractive input, .xfaNonInteractive textarea")) {
      el.setAttribute("readOnly", true);
    }
    return {
      textDivs
    };
  }
  static update(parameters) {
    const transform = `matrix(${parameters.viewport.transform.join(",")})`;
    parameters.div.style.transform = transform;
    parameters.div.hidden = false;
  }
}

;// CONCATENATED MODULE: ./src/display/annotation_layer.js





const DEFAULT_TAB_INDEX = 1000;
const annotation_layer_DEFAULT_FONT_SIZE = 9;
const GetElementsByNameSet = new WeakSet();
function getRectDims(rect) {
  return {
    width: rect[2] - rect[0],
    height: rect[3] - rect[1]
  };
}
class AnnotationElementFactory {
  static create(parameters) {
    const subtype = parameters.data.annotationType;
    switch (subtype) {
      case AnnotationType.LINK:
        return new LinkAnnotationElement(parameters);
      case AnnotationType.TEXT:
        return new TextAnnotationElement(parameters);
      case AnnotationType.WIDGET:
        const fieldType = parameters.data.fieldType;
        switch (fieldType) {
          case "Tx":
            return new TextWidgetAnnotationElement(parameters);
          case "Btn":
            if (parameters.data.radioButton) {
              return new RadioButtonWidgetAnnotationElement(parameters);
            } else if (parameters.data.checkBox) {
              return new CheckboxWidgetAnnotationElement(parameters);
            }
            return new PushButtonWidgetAnnotationElement(parameters);
          case "Ch":
            return new ChoiceWidgetAnnotationElement(parameters);
          case "Sig":
            return new SignatureWidgetAnnotationElement(parameters);
        }
        return new WidgetAnnotationElement(parameters);
      case AnnotationType.POPUP:
        return new PopupAnnotationElement(parameters);
      case AnnotationType.FREETEXT:
        return new FreeTextAnnotationElement(parameters);
      case AnnotationType.LINE:
        return new LineAnnotationElement(parameters);
      case AnnotationType.SQUARE:
        return new SquareAnnotationElement(parameters);
      case AnnotationType.CIRCLE:
        return new CircleAnnotationElement(parameters);
      case AnnotationType.POLYLINE:
        return new PolylineAnnotationElement(parameters);
      case AnnotationType.CARET:
        return new CaretAnnotationElement(parameters);
      case AnnotationType.INK:
        return new InkAnnotationElement(parameters);
      case AnnotationType.POLYGON:
        return new PolygonAnnotationElement(parameters);
      case AnnotationType.HIGHLIGHT:
        return new HighlightAnnotationElement(parameters);
      case AnnotationType.UNDERLINE:
        return new UnderlineAnnotationElement(parameters);
      case AnnotationType.SQUIGGLY:
        return new SquigglyAnnotationElement(parameters);
      case AnnotationType.STRIKEOUT:
        return new StrikeOutAnnotationElement(parameters);
      case AnnotationType.STAMP:
        return new StampAnnotationElement(parameters);
      case AnnotationType.FILEATTACHMENT:
        return new FileAttachmentAnnotationElement(parameters);
      default:
        return new AnnotationElement(parameters);
    }
  }
}
class AnnotationElement {
  #updates = null;
  #hasBorder = false;
  #popupElement = null;
  constructor(parameters, {
    isRenderable = false,
    ignoreBorder = false,
    createQuadrilaterals = false
  } = {}) {
    this.isRenderable = isRenderable;
    this.data = parameters.data;
    this.layer = parameters.layer;
    this.linkService = parameters.linkService;
    this.downloadManager = parameters.downloadManager;
    this.imageResourcesPath = parameters.imageResourcesPath;
    this.renderForms = parameters.renderForms;
    this.svgFactory = parameters.svgFactory;
    this.annotationStorage = parameters.annotationStorage;
    this.enableScripting = parameters.enableScripting;
    this.hasJSActions = parameters.hasJSActions;
    this._fieldObjects = parameters.fieldObjects;
    this.parent = parameters.parent;
    if (isRenderable) {
      this.container = this._createContainer(ignoreBorder);
    }
    if (createQuadrilaterals) {
      this._createQuadrilaterals();
    }
  }
  static _hasPopupData({
    titleObj,
    contentsObj,
    richText
  }) {
    return !!(titleObj?.str || contentsObj?.str || richText?.str);
  }
  get _isEditable() {
    return this.data.isEditable;
  }
  get hasPopupData() {
    return AnnotationElement._hasPopupData(this.data);
  }
  updateEdited(params) {
    if (!this.container) {
      return;
    }
    this.#updates ||= {
      rect: this.data.rect.slice(0)
    };
    const {
      rect
    } = params;
    if (rect) {
      this.#setRectEdited(rect);
    }
    this.#popupElement?.popup.updateEdited(params);
  }
  resetEdited() {
    if (!this.#updates) {
      return;
    }
    this.#setRectEdited(this.#updates.rect);
    this.#popupElement?.popup.resetEdited();
    this.#updates = null;
  }
  #setRectEdited(rect) {
    const {
      container: {
        style
      },
      data: {
        rect: currentRect,
        rotation
      },
      parent: {
        viewport: {
          rawDims: {
            pageWidth,
            pageHeight,
            pageX,
            pageY
          }
        }
      }
    } = this;
    currentRect?.splice(0, 4, ...rect);
    const {
      width,
      height
    } = getRectDims(rect);
    style.left = `${100 * (rect[0] - pageX) / pageWidth}%`;
    style.top = `${100 * (pageHeight - rect[3] + pageY) / pageHeight}%`;
    if (rotation === 0) {
      style.width = `${100 * width / pageWidth}%`;
      style.height = `${100 * height / pageHeight}%`;
    } else {
      this.setRotation(rotation);
    }
  }
  _createContainer(ignoreBorder) {
    const {
      data,
      parent: {
        page,
        viewport
      }
    } = this;
    const container = document.createElement("section");
    container.setAttribute("data-annotation-id", data.id);
    if (!(this instanceof WidgetAnnotationElement)) {
      container.tabIndex = DEFAULT_TAB_INDEX;
    }
    const {
      style
    } = container;
    style.zIndex = this.parent.zIndex++;
    if (data.popupRef) {
      container.setAttribute("aria-haspopup", "dialog");
    }
    if (data.alternativeText) {
      container.title = data.alternativeText;
    }
    if (data.noRotate) {
      container.classList.add("norotate");
    }
    if (!data.rect || this instanceof PopupAnnotationElement) {
      const {
        rotation
      } = data;
      if (!data.hasOwnCanvas && rotation !== 0) {
        this.setRotation(rotation, container);
      }
      return container;
    }
    const {
      width,
      height
    } = getRectDims(data.rect);
    if (!ignoreBorder && data.borderStyle.width > 0) {
      style.borderWidth = `${data.borderStyle.width}px`;
      const horizontalRadius = data.borderStyle.horizontalCornerRadius;
      const verticalRadius = data.borderStyle.verticalCornerRadius;
      if (horizontalRadius > 0 || verticalRadius > 0) {
        const radius = `calc(${horizontalRadius}px * var(--scale-factor)) / calc(${verticalRadius}px * var(--scale-factor))`;
        style.borderRadius = radius;
      } else if (this instanceof RadioButtonWidgetAnnotationElement) {
        const radius = `calc(${width}px * var(--scale-factor)) / calc(${height}px * var(--scale-factor))`;
        style.borderRadius = radius;
      }
      switch (data.borderStyle.style) {
        case AnnotationBorderStyleType.SOLID:
          style.borderStyle = "solid";
          break;
        case AnnotationBorderStyleType.DASHED:
          style.borderStyle = "dashed";
          break;
        case AnnotationBorderStyleType.BEVELED:
          warn("Unimplemented border style: beveled");
          break;
        case AnnotationBorderStyleType.INSET:
          warn("Unimplemented border style: inset");
          break;
        case AnnotationBorderStyleType.UNDERLINE:
          style.borderBottomStyle = "solid";
          break;
        default:
          break;
      }
      const borderColor = data.borderColor || null;
      if (borderColor) {
        this.#hasBorder = true;
        style.borderColor = Util.makeHexColor(borderColor[0] | 0, borderColor[1] | 0, borderColor[2] | 0);
      } else {
        style.borderWidth = 0;
      }
    }
    const rect = Util.normalizeRect([data.rect[0], page.view[3] - data.rect[1] + page.view[1], data.rect[2], page.view[3] - data.rect[3] + page.view[1]]);
    const {
      pageWidth,
      pageHeight,
      pageX,
      pageY
    } = viewport.rawDims;
    style.left = `${100 * (rect[0] - pageX) / pageWidth}%`;
    style.top = `${100 * (rect[1] - pageY) / pageHeight}%`;
    const {
      rotation
    } = data;
    if (data.hasOwnCanvas || rotation === 0) {
      style.width = `${100 * width / pageWidth}%`;
      style.height = `${100 * height / pageHeight}%`;
    } else {
      this.setRotation(rotation, container);
    }
    return container;
  }
  setRotation(angle, container = this.container) {
    if (!this.data.rect) {
      return;
    }
    const {
      pageWidth,
      pageHeight
    } = this.parent.viewport.rawDims;
    const {
      width,
      height
    } = getRectDims(this.data.rect);
    let elementWidth, elementHeight;
    if (angle % 180 === 0) {
      elementWidth = 100 * width / pageWidth;
      elementHeight = 100 * height / pageHeight;
    } else {
      elementWidth = 100 * height / pageWidth;
      elementHeight = 100 * width / pageHeight;
    }
    container.style.width = `${elementWidth}%`;
    container.style.height = `${elementHeight}%`;
    container.setAttribute("data-main-rotation", (360 - angle) % 360);
  }
  get _commonActions() {
    const setColor = (jsName, styleName, event) => {
      const color = event.detail[jsName];
      const colorType = color[0];
      const colorArray = color.slice(1);
      event.target.style[styleName] = ColorConverters[`${colorType}_HTML`](colorArray);
      this.annotationStorage.setValue(this.data.id, {
        [styleName]: ColorConverters[`${colorType}_rgb`](colorArray)
      });
    };
    return shadow(this, "_commonActions", {
      display: event => {
        const {
          display
        } = event.detail;
        const hidden = display % 2 === 1;
        this.container.style.visibility = hidden ? "hidden" : "visible";
        this.annotationStorage.setValue(this.data.id, {
          noView: hidden,
          noPrint: display === 1 || display === 2
        });
      },
      print: event => {
        this.annotationStorage.setValue(this.data.id, {
          noPrint: !event.detail.print
        });
      },
      hidden: event => {
        const {
          hidden
        } = event.detail;
        this.container.style.visibility = hidden ? "hidden" : "visible";
        this.annotationStorage.setValue(this.data.id, {
          noPrint: hidden,
          noView: hidden
        });
      },
      focus: event => {
        setTimeout(() => event.target.focus({
          preventScroll: false
        }), 0);
      },
      userName: event => {
        event.target.title = event.detail.userName;
      },
      readonly: event => {
        event.target.disabled = event.detail.readonly;
      },
      required: event => {
        this._setRequired(event.target, event.detail.required);
      },
      bgColor: event => {
        setColor("bgColor", "backgroundColor", event);
      },
      fillColor: event => {
        setColor("fillColor", "backgroundColor", event);
      },
      fgColor: event => {
        setColor("fgColor", "color", event);
      },
      textColor: event => {
        setColor("textColor", "color", event);
      },
      borderColor: event => {
        setColor("borderColor", "borderColor", event);
      },
      strokeColor: event => {
        setColor("strokeColor", "borderColor", event);
      },
      rotation: event => {
        const angle = event.detail.rotation;
        this.setRotation(angle);
        this.annotationStorage.setValue(this.data.id, {
          rotation: angle
        });
      }
    });
  }
  _dispatchEventFromSandbox(actions, jsEvent) {
    const commonActions = this._commonActions;
    for (const name of Object.keys(jsEvent.detail)) {
      const action = actions[name] || commonActions[name];
      action?.(jsEvent);
    }
  }
  _setDefaultPropertiesFromJS(element) {
    if (!this.enableScripting) {
      return;
    }
    const storedData = this.annotationStorage.getRawValue(this.data.id);
    if (!storedData) {
      return;
    }
    const commonActions = this._commonActions;
    for (const [actionName, detail] of Object.entries(storedData)) {
      const action = commonActions[actionName];
      if (action) {
        const eventProxy = {
          detail: {
            [actionName]: detail
          },
          target: element
        };
        action(eventProxy);
        delete storedData[actionName];
      }
    }
  }
  _createQuadrilaterals() {
    if (!this.container) {
      return;
    }
    const {
      quadPoints
    } = this.data;
    if (!quadPoints) {
      return;
    }
    const [rectBlX, rectBlY, rectTrX, rectTrY] = this.data.rect.map(x => Math.fround(x));
    if (quadPoints.length === 8) {
      const [trX, trY, blX, blY] = quadPoints.subarray(2, 6);
      if (rectTrX === trX && rectTrY === trY && rectBlX === blX && rectBlY === blY) {
        return;
      }
    }
    const {
      style
    } = this.container;
    let svgBuffer;
    if (this.#hasBorder) {
      const {
        borderColor,
        borderWidth
      } = style;
      style.borderWidth = 0;
      svgBuffer = ["url('data:image/svg+xml;utf8,", `<svg xmlns="http://www.w3.org/2000/svg"`, ` preserveAspectRatio="none" viewBox="0 0 1 1">`, `<g fill="transparent" stroke="${borderColor}" stroke-width="${borderWidth}">`];
      this.container.classList.add("hasBorder");
    }
    const width = rectTrX - rectBlX;
    const height = rectTrY - rectBlY;
    const {
      svgFactory
    } = this;
    const svg = svgFactory.createElement("svg");
    svg.classList.add("quadrilateralsContainer");
    svg.setAttribute("width", 0);
    svg.setAttribute("height", 0);
    const defs = svgFactory.createElement("defs");
    svg.append(defs);
    const clipPath = svgFactory.createElement("clipPath");
    const id = `clippath_${this.data.id}`;
    clipPath.setAttribute("id", id);
    clipPath.setAttribute("clipPathUnits", "objectBoundingBox");
    defs.append(clipPath);
    for (let i = 2, ii = quadPoints.length; i < ii; i += 8) {
      const trX = quadPoints[i];
      const trY = quadPoints[i + 1];
      const blX = quadPoints[i + 2];
      const blY = quadPoints[i + 3];
      const rect = svgFactory.createElement("rect");
      const x = (blX - rectBlX) / width;
      const y = (rectTrY - trY) / height;
      const rectWidth = (trX - blX) / width;
      const rectHeight = (trY - blY) / height;
      rect.setAttribute("x", x);
      rect.setAttribute("y", y);
      rect.setAttribute("width", rectWidth);
      rect.setAttribute("height", rectHeight);
      clipPath.append(rect);
      svgBuffer?.push(`<rect vector-effect="non-scaling-stroke" x="${x}" y="${y}" width="${rectWidth}" height="${rectHeight}"/>`);
    }
    if (this.#hasBorder) {
      svgBuffer.push(`</g></svg>')`);
      style.backgroundImage = svgBuffer.join("");
    }
    this.container.append(svg);
    this.container.style.clipPath = `url(#${id})`;
  }
  _createPopup() {
    const {
      container,
      data
    } = this;
    container.setAttribute("aria-haspopup", "dialog");
    const popup = this.#popupElement = new PopupAnnotationElement({
      data: {
        color: data.color,
        titleObj: data.titleObj,
        modificationDate: data.modificationDate,
        contentsObj: data.contentsObj,
        richText: data.richText,
        parentRect: data.rect,
        borderStyle: 0,
        id: `popup_${data.id}`,
        rotation: data.rotation
      },
      parent: this.parent,
      elements: [this]
    });
    this.parent.div.append(popup.render());
  }
  render() {
    unreachable("Abstract method `AnnotationElement.render` called");
  }
  _getElementsByName(name, skipId = null) {
    const fields = [];
    if (this._fieldObjects) {
      const fieldObj = this._fieldObjects[name];
      if (fieldObj) {
        for (const {
          page,
          id,
          exportValues
        } of fieldObj) {
          if (page === -1) {
            continue;
          }
          if (id === skipId) {
            continue;
          }
          const exportValue = typeof exportValues === "string" ? exportValues : null;
          const domElement = document.querySelector(`[data-element-id="${id}"]`);
          if (domElement && !GetElementsByNameSet.has(domElement)) {
            warn(`_getElementsByName - element not allowed: ${id}`);
            continue;
          }
          fields.push({
            id,
            exportValue,
            domElement
          });
        }
      }
      return fields;
    }
    for (const domElement of document.getElementsByName(name)) {
      const {
        exportValue
      } = domElement;
      const id = domElement.getAttribute("data-element-id");
      if (id === skipId) {
        continue;
      }
      if (!GetElementsByNameSet.has(domElement)) {
        continue;
      }
      fields.push({
        id,
        exportValue,
        domElement
      });
    }
    return fields;
  }
  show() {
    if (this.container) {
      this.container.hidden = false;
    }
    this.popup?.maybeShow();
  }
  hide() {
    if (this.container) {
      this.container.hidden = true;
    }
    this.popup?.forceHide();
  }
  getElementsToTriggerPopup() {
    return this.container;
  }
  addHighlightArea() {
    const triggers = this.getElementsToTriggerPopup();
    if (Array.isArray(triggers)) {
      for (const element of triggers) {
        element.classList.add("highlightArea");
      }
    } else {
      triggers.classList.add("highlightArea");
    }
  }
  _editOnDoubleClick() {
    if (!this._isEditable) {
      return;
    }
    const {
      annotationEditorType: mode,
      data: {
        id: editId
      }
    } = this;
    this.container.addEventListener("dblclick", () => {
      this.linkService.eventBus?.dispatch("switchannotationeditormode", {
        source: this,
        mode,
        editId
      });
    });
  }
}
class LinkAnnotationElement extends AnnotationElement {
  constructor(parameters, options = null) {
    super(parameters, {
      isRenderable: true,
      ignoreBorder: !!options?.ignoreBorder,
      createQuadrilaterals: true
    });
    this.isTooltipOnly = parameters.data.isTooltipOnly;
  }
  render() {
    const {
      data,
      linkService
    } = this;
    const link = document.createElement("a");
    link.setAttribute("data-element-id", data.id);
    let isBound = false;
    if (data.url) {
      linkService.addLinkAttributes(link, data.url, data.newWindow);
      isBound = true;
    } else if (data.action) {
      this._bindNamedAction(link, data.action);
      isBound = true;
    } else if (data.attachment) {
      this.#bindAttachment(link, data.attachment, data.attachmentDest);
      isBound = true;
    } else if (data.setOCGState) {
      this.#bindSetOCGState(link, data.setOCGState);
      isBound = true;
    } else if (data.dest) {
      this._bindLink(link, data.dest);
      isBound = true;
    } else {
      if (data.actions && (data.actions.Action || data.actions["Mouse Up"] || data.actions["Mouse Down"]) && this.enableScripting && this.hasJSActions) {
        this._bindJSAction(link, data);
        isBound = true;
      }
      if (data.resetForm) {
        this._bindResetFormAction(link, data.resetForm);
        isBound = true;
      } else if (this.isTooltipOnly && !isBound) {
        this._bindLink(link, "");
        isBound = true;
      }
    }
    this.container.classList.add("linkAnnotation");
    if (isBound) {
      this.container.append(link);
    }
    return this.container;
  }
  #setInternalLink() {
    this.container.setAttribute("data-internal-link", "");
  }
  _bindLink(link, destination) {
    link.href = this.linkService.getDestinationHash(destination);
    link.onclick = () => {
      if (destination) {
        this.linkService.goToDestination(destination);
      }
      return false;
    };
    if (destination || destination === "") {
      this.#setInternalLink();
    }
  }
  _bindNamedAction(link, action) {
    link.href = this.linkService.getAnchorUrl("");
    link.onclick = () => {
      this.linkService.executeNamedAction(action);
      return false;
    };
    this.#setInternalLink();
  }
  #bindAttachment(link, attachment, dest = null) {
    link.href = this.linkService.getAnchorUrl("");
    if (attachment.description) {
      link.title = attachment.description;
    }
    link.onclick = () => {
      this.downloadManager?.openOrDownloadData(attachment.content, attachment.filename, dest);
      return false;
    };
    this.#setInternalLink();
  }
  #bindSetOCGState(link, action) {
    link.href = this.linkService.getAnchorUrl("");
    link.onclick = () => {
      this.linkService.executeSetOCGState(action);
      return false;
    };
    this.#setInternalLink();
  }
  _bindJSAction(link, data) {
    link.href = this.linkService.getAnchorUrl("");
    const map = new Map([["Action", "onclick"], ["Mouse Up", "onmouseup"], ["Mouse Down", "onmousedown"]]);
    for (const name of Object.keys(data.actions)) {
      const jsName = map.get(name);
      if (!jsName) {
        continue;
      }
      link[jsName] = () => {
        this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
          source: this,
          detail: {
            id: data.id,
            name
          }
        });
        return false;
      };
    }
    if (!link.onclick) {
      link.onclick = () => false;
    }
    this.#setInternalLink();
  }
  _bindResetFormAction(link, resetForm) {
    const otherClickAction = link.onclick;
    if (!otherClickAction) {
      link.href = this.linkService.getAnchorUrl("");
    }
    this.#setInternalLink();
    if (!this._fieldObjects) {
      warn(`_bindResetFormAction - "resetForm" action not supported, ` + "ensure that the `fieldObjects` parameter is provided.");
      if (!otherClickAction) {
        link.onclick = () => false;
      }
      return;
    }
    link.onclick = () => {
      otherClickAction?.();
      const {
        fields: resetFormFields,
        refs: resetFormRefs,
        include
      } = resetForm;
      const allFields = [];
      if (resetFormFields.length !== 0 || resetFormRefs.length !== 0) {
        const fieldIds = new Set(resetFormRefs);
        for (const fieldName of resetFormFields) {
          const fields = this._fieldObjects[fieldName] || [];
          for (const {
            id
          } of fields) {
            fieldIds.add(id);
          }
        }
        for (const fields of Object.values(this._fieldObjects)) {
          for (const field of fields) {
            if (fieldIds.has(field.id) === include) {
              allFields.push(field);
            }
          }
        }
      } else {
        for (const fields of Object.values(this._fieldObjects)) {
          allFields.push(...fields);
        }
      }
      const storage = this.annotationStorage;
      const allIds = [];
      for (const field of allFields) {
        const {
          id
        } = field;
        allIds.push(id);
        switch (field.type) {
          case "text":
            {
              const value = field.defaultValue || "";
              storage.setValue(id, {
                value
              });
              break;
            }
          case "checkbox":
          case "radiobutton":
            {
              const value = field.defaultValue === field.exportValues;
              storage.setValue(id, {
                value
              });
              break;
            }
          case "combobox":
          case "listbox":
            {
              const value = field.defaultValue || "";
              storage.setValue(id, {
                value
              });
              break;
            }
          default:
            continue;
        }
        const domElement = document.querySelector(`[data-element-id="${id}"]`);
        if (!domElement) {
          continue;
        } else if (!GetElementsByNameSet.has(domElement)) {
          warn(`_bindResetFormAction - element not allowed: ${id}`);
          continue;
        }
        domElement.dispatchEvent(new Event("resetform"));
      }
      if (this.enableScripting) {
        this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
          source: this,
          detail: {
            id: "app",
            ids: allIds,
            name: "ResetForm"
          }
        });
      }
      return false;
    };
  }
}
class TextAnnotationElement extends AnnotationElement {
  constructor(parameters) {
    super(parameters, {
      isRenderable: true
    });
  }
  render() {
    this.container.classList.add("textAnnotation");
    const image = document.createElement("img");
    image.src = this.imageResourcesPath + "annotation-" + this.data.name.toLowerCase() + ".svg";
    image.setAttribute("data-l10n-id", "pdfjs-text-annotation-type");
    image.setAttribute("data-l10n-args", JSON.stringify({
      type: this.data.name
    }));
    if (!this.data.popupRef && this.hasPopupData) {
      this._createPopup();
    }
    this.container.append(image);
    return this.container;
  }
}
class WidgetAnnotationElement extends AnnotationElement {
  render() {
    return this.container;
  }
  showElementAndHideCanvas(element) {
    if (this.data.hasOwnCanvas) {
      if (element.previousSibling?.nodeName === "CANVAS") {
        element.previousSibling.hidden = true;
      }
      element.hidden = false;
    }
  }
  _getKeyModifier(event) {
    return util_FeatureTest.platform.isMac ? event.metaKey : event.ctrlKey;
  }
  _setEventListener(element, elementData, baseName, eventName, valueGetter) {
    if (baseName.includes("mouse")) {
      element.addEventListener(baseName, event => {
        this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
          source: this,
          detail: {
            id: this.data.id,
            name: eventName,
            value: valueGetter(event),
            shift: event.shiftKey,
            modifier: this._getKeyModifier(event)
          }
        });
      });
    } else {
      element.addEventListener(baseName, event => {
        if (baseName === "blur") {
          if (!elementData.focused || !event.relatedTarget) {
            return;
          }
          elementData.focused = false;
        } else if (baseName === "focus") {
          if (elementData.focused) {
            return;
          }
          elementData.focused = true;
        }
        if (!valueGetter) {
          return;
        }
        this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
          source: this,
          detail: {
            id: this.data.id,
            name: eventName,
            value: valueGetter(event)
          }
        });
      });
    }
  }
  _setEventListeners(element, elementData, names, getter) {
    for (const [baseName, eventName] of names) {
      if (eventName === "Action" || this.data.actions?.[eventName]) {
        if (eventName === "Focus" || eventName === "Blur") {
          elementData ||= {
            focused: false
          };
        }
        this._setEventListener(element, elementData, baseName, eventName, getter);
        if (eventName === "Focus" && !this.data.actions?.Blur) {
          this._setEventListener(element, elementData, "blur", "Blur", null);
        } else if (eventName === "Blur" && !this.data.actions?.Focus) {
          this._setEventListener(element, elementData, "focus", "Focus", null);
        }
      }
    }
  }
  _setBackgroundColor(element) {
    const color = this.data.backgroundColor || null;
    element.style.backgroundColor = color === null ? "transparent" : Util.makeHexColor(color[0], color[1], color[2]);
  }
  _setTextStyle(element) {
    const TEXT_ALIGNMENT = ["left", "center", "right"];
    const {
      fontColor
    } = this.data.defaultAppearanceData;
    const fontSize = this.data.defaultAppearanceData.fontSize || annotation_layer_DEFAULT_FONT_SIZE;
    const style = element.style;
    let computedFontSize;
    const BORDER_SIZE = 2;
    const roundToOneDecimal = x => Math.round(10 * x) / 10;
    if (this.data.multiLine) {
      const height = Math.abs(this.data.rect[3] - this.data.rect[1] - BORDER_SIZE);
      const numberOfLines = Math.round(height / (LINE_FACTOR * fontSize)) || 1;
      const lineHeight = height / numberOfLines;
      computedFontSize = Math.min(fontSize, roundToOneDecimal(lineHeight / LINE_FACTOR));
    } else {
      const height = Math.abs(this.data.rect[3] - this.data.rect[1] - BORDER_SIZE);
      computedFontSize = Math.min(fontSize, roundToOneDecimal(height / LINE_FACTOR));
    }
    style.fontSize = `calc(${computedFontSize}px * var(--scale-factor))`;
    style.color = Util.makeHexColor(fontColor[0], fontColor[1], fontColor[2]);
    if (this.data.textAlignment !== null) {
      style.textAlign = TEXT_ALIGNMENT[this.data.textAlignment];
    }
  }
  _setRequired(element, isRequired) {
    if (isRequired) {
      element.setAttribute("required", true);
    } else {
      element.removeAttribute("required");
    }
    element.setAttribute("aria-required", isRequired);
  }
}
class TextWidgetAnnotationElement extends WidgetAnnotationElement {
  constructor(parameters) {
    const isRenderable = parameters.renderForms || parameters.data.hasOwnCanvas || !parameters.data.hasAppearance && !!parameters.data.fieldValue;
    super(parameters, {
      isRenderable
    });
  }
  setPropertyOnSiblings(base, key, value, keyInStorage) {
    const storage = this.annotationStorage;
    for (const element of this._getElementsByName(base.name, base.id)) {
      if (element.domElement) {
        element.domElement[key] = value;
      }
      storage.setValue(element.id, {
        [keyInStorage]: value
      });
    }
  }
  render() {
    const storage = this.annotationStorage;
    const id = this.data.id;
    this.container.classList.add("textWidgetAnnotation");
    let element = null;
    if (this.renderForms) {
      const storedData = storage.getValue(id, {
        value: this.data.fieldValue
      });
      let textContent = storedData.value || "";
      const maxLen = storage.getValue(id, {
        charLimit: this.data.maxLen
      }).charLimit;
      if (maxLen && textContent.length > maxLen) {
        textContent = textContent.slice(0, maxLen);
      }
      let fieldFormattedValues = storedData.formattedValue || this.data.textContent?.join("\n") || null;
      if (fieldFormattedValues && this.data.comb) {
        fieldFormattedValues = fieldFormattedValues.replaceAll(/\s+/g, "");
      }
      const elementData = {
        userValue: textContent,
        formattedValue: fieldFormattedValues,
        lastCommittedValue: null,
        commitKey: 1,
        focused: false
      };
      if (this.data.multiLine) {
        element = document.createElement("textarea");
        element.textContent = fieldFormattedValues ?? textContent;
        if (this.data.doNotScroll) {
          element.style.overflowY = "hidden";
        }
      } else {
        element = document.createElement("input");
        element.type = "text";
        element.setAttribute("value", fieldFormattedValues ?? textContent);
        if (this.data.doNotScroll) {
          element.style.overflowX = "hidden";
        }
      }
      if (this.data.hasOwnCanvas) {
        element.hidden = true;
      }
      GetElementsByNameSet.add(element);
      element.setAttribute("data-element-id", id);
      element.disabled = this.data.readOnly;
      element.name = this.data.fieldName;
      element.tabIndex = DEFAULT_TAB_INDEX;
      this._setRequired(element, this.data.required);
      if (maxLen) {
        element.maxLength = maxLen;
      }
      element.addEventListener("input", event => {
        storage.setValue(id, {
          value: event.target.value
        });
        this.setPropertyOnSiblings(element, "value", event.target.value, "value");
        elementData.formattedValue = null;
      });
      element.addEventListener("resetform", event => {
        const defaultValue = this.data.defaultFieldValue ?? "";
        element.value = elementData.userValue = defaultValue;
        elementData.formattedValue = null;
      });
      let blurListener = event => {
        const {
          formattedValue
        } = elementData;
        if (formattedValue !== null && formattedValue !== undefined) {
          event.target.value = formattedValue;
        }
        event.target.scrollLeft = 0;
      };
      if (this.enableScripting && this.hasJSActions) {
        element.addEventListener("focus", event => {
          if (elementData.focused) {
            return;
          }
          const {
            target
          } = event;
          if (elementData.userValue) {
            target.value = elementData.userValue;
          }
          elementData.lastCommittedValue = target.value;
          elementData.commitKey = 1;
          if (!this.data.actions?.Focus) {
            elementData.focused = true;
          }
        });
        element.addEventListener("updatefromsandbox", jsEvent => {
          this.showElementAndHideCanvas(jsEvent.target);
          const actions = {
            value(event) {
              elementData.userValue = event.detail.value ?? "";
              storage.setValue(id, {
                value: elementData.userValue.toString()
              });
              event.target.value = elementData.userValue;
            },
            formattedValue(event) {
              const {
                formattedValue
              } = event.detail;
              elementData.formattedValue = formattedValue;
              if (formattedValue !== null && formattedValue !== undefined && event.target !== document.activeElement) {
                event.target.value = formattedValue;
              }
              storage.setValue(id, {
                formattedValue
              });
            },
            selRange(event) {
              event.target.setSelectionRange(...event.detail.selRange);
            },
            charLimit: event => {
              const {
                charLimit
              } = event.detail;
              const {
                target
              } = event;
              if (charLimit === 0) {
                target.removeAttribute("maxLength");
                return;
              }
              target.setAttribute("maxLength", charLimit);
              let value = elementData.userValue;
              if (!value || value.length <= charLimit) {
                return;
              }
              value = value.slice(0, charLimit);
              target.value = elementData.userValue = value;
              storage.setValue(id, {
                value
              });
              this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
                source: this,
                detail: {
                  id,
                  name: "Keystroke",
                  value,
                  willCommit: true,
                  commitKey: 1,
                  selStart: target.selectionStart,
                  selEnd: target.selectionEnd
                }
              });
            }
          };
          this._dispatchEventFromSandbox(actions, jsEvent);
        });
        element.addEventListener("keydown", event => {
          elementData.commitKey = 1;
          let commitKey = -1;
          if (event.key === "Escape") {
            commitKey = 0;
          } else if (event.key === "Enter" && !this.data.multiLine) {
            commitKey = 2;
          } else if (event.key === "Tab") {
            elementData.commitKey = 3;
          }
          if (commitKey === -1) {
            return;
          }
          const {
            value
          } = event.target;
          if (elementData.lastCommittedValue === value) {
            return;
          }
          elementData.lastCommittedValue = value;
          elementData.userValue = value;
          this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
            source: this,
            detail: {
              id,
              name: "Keystroke",
              value,
              willCommit: true,
              commitKey,
              selStart: event.target.selectionStart,
              selEnd: event.target.selectionEnd
            }
          });
        });
        const _blurListener = blurListener;
        blurListener = null;
        element.addEventListener("blur", event => {
          if (!elementData.focused || !event.relatedTarget) {
            return;
          }
          if (!this.data.actions?.Blur) {
            elementData.focused = false;
          }
          const {
            value
          } = event.target;
          elementData.userValue = value;
          if (elementData.lastCommittedValue !== value) {
            this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
              source: this,
              detail: {
                id,
                name: "Keystroke",
                value,
                willCommit: true,
                commitKey: elementData.commitKey,
                selStart: event.target.selectionStart,
                selEnd: event.target.selectionEnd
              }
            });
          }
          _blurListener(event);
        });
        if (this.data.actions?.Keystroke) {
          element.addEventListener("beforeinput", event => {
            elementData.lastCommittedValue = null;
            const {
              data,
              target
            } = event;
            const {
              value,
              selectionStart,
              selectionEnd
            } = target;
            let selStart = selectionStart,
              selEnd = selectionEnd;
            switch (event.inputType) {
              case "deleteWordBackward":
                {
                  const match = value.substring(0, selectionStart).match(/\w*[^\w]*$/);
                  if (match) {
                    selStart -= match[0].length;
                  }
                  break;
                }
              case "deleteWordForward":
                {
                  const match = value.substring(selectionStart).match(/^[^\w]*\w*/);
                  if (match) {
                    selEnd += match[0].length;
                  }
                  break;
                }
              case "deleteContentBackward":
                if (selectionStart === selectionEnd) {
                  selStart -= 1;
                }
                break;
              case "deleteContentForward":
                if (selectionStart === selectionEnd) {
                  selEnd += 1;
                }
                break;
            }
            event.preventDefault();
            this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
              source: this,
              detail: {
                id,
                name: "Keystroke",
                value,
                change: data || "",
                willCommit: false,
                selStart,
                selEnd
              }
            });
          });
        }
        this._setEventListeners(element, elementData, [["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"]], event => event.target.value);
      }
      if (blurListener) {
        element.addEventListener("blur", blurListener);
      }
      if (this.data.comb) {
        const fieldWidth = this.data.rect[2] - this.data.rect[0];
        const combWidth = fieldWidth / maxLen;
        element.classList.add("comb");
        element.style.letterSpacing = `calc(${combWidth}px * var(--scale-factor) - 1ch)`;
      }
    } else {
      element = document.createElement("div");
      element.textContent = this.data.fieldValue;
      element.style.verticalAlign = "middle";
      element.style.display = "table-cell";
      if (this.data.hasOwnCanvas) {
        element.hidden = true;
      }
    }
    this._setTextStyle(element);
    this._setBackgroundColor(element);
    this._setDefaultPropertiesFromJS(element);
    this.container.append(element);
    return this.container;
  }
}
class SignatureWidgetAnnotationElement extends WidgetAnnotationElement {
  constructor(parameters) {
    super(parameters, {
      isRenderable: !!parameters.data.hasOwnCanvas
    });
  }
}
class CheckboxWidgetAnnotationElement extends WidgetAnnotationElement {
  constructor(parameters) {
    super(parameters, {
      isRenderable: parameters.renderForms
    });
  }
  render() {
    const storage = this.annotationStorage;
    const data = this.data;
    const id = data.id;
    let value = storage.getValue(id, {
      value: data.exportValue === data.fieldValue
    }).value;
    if (typeof value === "string") {
      value = value !== "Off";
      storage.setValue(id, {
        value
      });
    }
    this.container.classList.add("buttonWidgetAnnotation", "checkBox");
    const element = document.createElement("input");
    GetElementsByNameSet.add(element);
    element.setAttribute("data-element-id", id);
    element.disabled = data.readOnly;
    this._setRequired(element, this.data.required);
    element.type = "checkbox";
    element.name = data.fieldName;
    if (value) {
      element.setAttribute("checked", true);
    }
    element.setAttribute("exportValue", data.exportValue);
    element.tabIndex = DEFAULT_TAB_INDEX;
    element.addEventListener("change", event => {
      const {
        name,
        checked
      } = event.target;
      for (const checkbox of this._getElementsByName(name, id)) {
        const curChecked = checked && checkbox.exportValue === data.exportValue;
        if (checkbox.domElement) {
          checkbox.domElement.checked = curChecked;
        }
        storage.setValue(checkbox.id, {
          value: curChecked
        });
      }
      storage.setValue(id, {
        value: checked
      });
    });
    element.addEventListener("resetform", event => {
      const defaultValue = data.defaultFieldValue || "Off";
      event.target.checked = defaultValue === data.exportValue;
    });
    if (this.enableScripting && this.hasJSActions) {
      element.addEventListener("updatefromsandbox", jsEvent => {
        const actions = {
          value(event) {
            event.target.checked = event.detail.value !== "Off";
            storage.setValue(id, {
              value: event.target.checked
            });
          }
        };
        this._dispatchEventFromSandbox(actions, jsEvent);
      });
      this._setEventListeners(element, null, [["change", "Validate"], ["change", "Action"], ["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"]], event => event.target.checked);
    }
    this._setBackgroundColor(element);
    this._setDefaultPropertiesFromJS(element);
    this.container.append(element);
    return this.container;
  }
}
class RadioButtonWidgetAnnotationElement extends WidgetAnnotationElement {
  constructor(parameters) {
    super(parameters, {
      isRenderable: parameters.renderForms
    });
  }
  render() {
    this.container.classList.add("buttonWidgetAnnotation", "radioButton");
    const storage = this.annotationStorage;
    const data = this.data;
    const id = data.id;
    let value = storage.getValue(id, {
      value: data.fieldValue === data.buttonValue
    }).value;
    if (typeof value === "string") {
      value = value !== data.buttonValue;
      storage.setValue(id, {
        value
      });
    }
    if (value) {
      for (const radio of this._getElementsByName(data.fieldName, id)) {
        storage.setValue(radio.id, {
          value: false
        });
      }
    }
    const element = document.createElement("input");
    GetElementsByNameSet.add(element);
    element.setAttribute("data-element-id", id);
    element.disabled = data.readOnly;
    this._setRequired(element, this.data.required);
    element.type = "radio";
    element.name = data.fieldName;
    if (value) {
      element.setAttribute("checked", true);
    }
    element.tabIndex = DEFAULT_TAB_INDEX;
    element.addEventListener("change", event => {
      const {
        name,
        checked
      } = event.target;
      for (const radio of this._getElementsByName(name, id)) {
        storage.setValue(radio.id, {
          value: false
        });
      }
      storage.setValue(id, {
        value: checked
      });
    });
    element.addEventListener("resetform", event => {
      const defaultValue = data.defaultFieldValue;
      event.target.checked = defaultValue !== null && defaultValue !== undefined && defaultValue === data.buttonValue;
    });
    if (this.enableScripting && this.hasJSActions) {
      const pdfButtonValue = data.buttonValue;
      element.addEventListener("updatefromsandbox", jsEvent => {
        const actions = {
          value: event => {
            const checked = pdfButtonValue === event.detail.value;
            for (const radio of this._getElementsByName(event.target.name)) {
              const curChecked = checked && radio.id === id;
              if (radio.domElement) {
                radio.domElement.checked = curChecked;
              }
              storage.setValue(radio.id, {
                value: curChecked
              });
            }
          }
        };
        this._dispatchEventFromSandbox(actions, jsEvent);
      });
      this._setEventListeners(element, null, [["change", "Validate"], ["change", "Action"], ["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"]], event => event.target.checked);
    }
    this._setBackgroundColor(element);
    this._setDefaultPropertiesFromJS(element);
    this.container.append(element);
    return this.container;
  }
}
class PushButtonWidgetAnnotationElement extends LinkAnnotationElement {
  constructor(parameters) {
    super(parameters, {
      ignoreBorder: parameters.data.hasAppearance
    });
  }
  render() {
    const container = super.render();
    container.classList.add("buttonWidgetAnnotation", "pushButton");
    const linkElement = container.lastChild;
    if (this.enableScripting && this.hasJSActions && linkElement) {
      this._setDefaultPropertiesFromJS(linkElement);
      linkElement.addEventListener("updatefromsandbox", jsEvent => {
        this._dispatchEventFromSandbox({}, jsEvent);
      });
    }
    return container;
  }
}
class ChoiceWidgetAnnotationElement extends WidgetAnnotationElement {
  constructor(parameters) {
    super(parameters, {
      isRenderable: parameters.renderForms
    });
  }
  render() {
    this.container.classList.add("choiceWidgetAnnotation");
    const storage = this.annotationStorage;
    const id = this.data.id;
    const storedData = storage.getValue(id, {
      value: this.data.fieldValue
    });
    const selectElement = document.createElement("select");
    GetElementsByNameSet.add(selectElement);
    selectElement.setAttribute("data-element-id", id);
    selectElement.disabled = this.data.readOnly;
    this._setRequired(selectElement, this.data.required);
    selectElement.name = this.data.fieldName;
    selectElement.tabIndex = DEFAULT_TAB_INDEX;
    let addAnEmptyEntry = this.data.combo && this.data.options.length > 0;
    if (!this.data.combo) {
      selectElement.size = this.data.options.length;
      if (this.data.multiSelect) {
        selectElement.multiple = true;
      }
    }
    selectElement.addEventListener("resetform", event => {
      const defaultValue = this.data.defaultFieldValue;
      for (const option of selectElement.options) {
        option.selected = option.value === defaultValue;
      }
    });
    for (const option of this.data.options) {
      const optionElement = document.createElement("option");
      optionElement.textContent = option.displayValue;
      optionElement.value = option.exportValue;
      if (storedData.value.includes(option.exportValue)) {
        optionElement.setAttribute("selected", true);
        addAnEmptyEntry = false;
      }
      selectElement.append(optionElement);
    }
    let removeEmptyEntry = null;
    if (addAnEmptyEntry) {
      const noneOptionElement = document.createElement("option");
      noneOptionElement.value = " ";
      noneOptionElement.setAttribute("hidden", true);
      noneOptionElement.setAttribute("selected", true);
      selectElement.prepend(noneOptionElement);
      removeEmptyEntry = () => {
        noneOptionElement.remove();
        selectElement.removeEventListener("input", removeEmptyEntry);
        removeEmptyEntry = null;
      };
      selectElement.addEventListener("input", removeEmptyEntry);
    }
    const getValue = isExport => {
      const name = isExport ? "value" : "textContent";
      const {
        options,
        multiple
      } = selectElement;
      if (!multiple) {
        return options.selectedIndex === -1 ? null : options[options.selectedIndex][name];
      }
      return Array.prototype.filter.call(options, option => option.selected).map(option => option[name]);
    };
    let selectedValues = getValue(false);
    const getItems = event => {
      const options = event.target.options;
      return Array.prototype.map.call(options, option => ({
        displayValue: option.textContent,
        exportValue: option.value
      }));
    };
    if (this.enableScripting && this.hasJSActions) {
      selectElement.addEventListener("updatefromsandbox", jsEvent => {
        const actions = {
          value(event) {
            removeEmptyEntry?.();
            const value = event.detail.value;
            const values = new Set(Array.isArray(value) ? value : [value]);
            for (const option of selectElement.options) {
              option.selected = values.has(option.value);
            }
            storage.setValue(id, {
              value: getValue(true)
            });
            selectedValues = getValue(false);
          },
          multipleSelection(event) {
            selectElement.multiple = true;
          },
          remove(event) {
            const options = selectElement.options;
            const index = event.detail.remove;
            options[index].selected = false;
            selectElement.remove(index);
            if (options.length > 0) {
              const i = Array.prototype.findIndex.call(options, option => option.selected);
              if (i === -1) {
                options[0].selected = true;
              }
            }
            storage.setValue(id, {
              value: getValue(true),
              items: getItems(event)
            });
            selectedValues = getValue(false);
          },
          clear(event) {
            while (selectElement.length !== 0) {
              selectElement.remove(0);
            }
            storage.setValue(id, {
              value: null,
              items: []
            });
            selectedValues = getValue(false);
          },
          insert(event) {
            const {
              index,
              displayValue,
              exportValue
            } = event.detail.insert;
            const selectChild = selectElement.children[index];
            const optionElement = document.createElement("option");
            optionElement.textContent = displayValue;
            optionElement.value = exportValue;
            if (selectChild) {
              selectChild.before(optionElement);
            } else {
              selectElement.append(optionElement);
            }
            storage.setValue(id, {
              value: getValue(true),
              items: getItems(event)
            });
            selectedValues = getValue(false);
          },
          items(event) {
            const {
              items
            } = event.detail;
            while (selectElement.length !== 0) {
              selectElement.remove(0);
            }
            for (const item of items) {
              const {
                displayValue,
                exportValue
              } = item;
              const optionElement = document.createElement("option");
              optionElement.textContent = displayValue;
              optionElement.value = exportValue;
              selectElement.append(optionElement);
            }
            if (selectElement.options.length > 0) {
              selectElement.options[0].selected = true;
            }
            storage.setValue(id, {
              value: getValue(true),
              items: getItems(event)
            });
            selectedValues = getValue(false);
          },
          indices(event) {
            const indices = new Set(event.detail.indices);
            for (const option of event.target.options) {
              option.selected = indices.has(option.index);
            }
            storage.setValue(id, {
              value: getValue(true)
            });
            selectedValues = getValue(false);
          },
          editable(event) {
            event.target.disabled = !event.detail.editable;
          }
        };
        this._dispatchEventFromSandbox(actions, jsEvent);
      });
      selectElement.addEventListener("input", event => {
        const exportValue = getValue(true);
        const change = getValue(false);
        storage.setValue(id, {
          value: exportValue
        });
        event.preventDefault();
        this.linkService.eventBus?.dispatch("dispatcheventinsandbox", {
          source: this,
          detail: {
            id,
            name: "Keystroke",
            value: selectedValues,
            change,
            changeEx: exportValue,
            willCommit: false,
            commitKey: 1,
            keyDown: false
          }
        });
      });
      this._setEventListeners(selectElement, null, [["focus", "Focus"], ["blur", "Blur"], ["mousedown", "Mouse Down"], ["mouseenter", "Mouse Enter"], ["mouseleave", "Mouse Exit"], ["mouseup", "Mouse Up"], ["input", "Action"], ["input", "Validate"]], event => event.target.value);
    } else {
      selectElement.addEventListener("input", function (event) {
        storage.setValue(id, {
          value: getValue(true)
        });
      });
    }
    if (this.data.combo) {
      this._setTextStyle(selectElement);
    } else {}
    this._setBackgroundColor(selectElement);
    this._setDefaultPropertiesFromJS(selectElement);
    this.container.append(selectElement);
    return this.container;
  }
}
class PopupAnnotationElement extends AnnotationElement {
  constructor(parameters) {
    const {
      data,
      elements
    } = parameters;
    super(parameters, {
      isRenderable: AnnotationElement._hasPopupData(data)
    });
    this.elements = elements;
    this.popup = null;
  }
  render() {
    this.container.classList.add("popupAnnotation");
    const popup = this.popup = new PopupElement({
      container: this.container,
      color: this.data.color,
      titleObj: this.data.titleObj,
      modificationDate: this.data.modificationDate,
      contentsObj: this.data.contentsObj,
      richText: this.data.richText,
      rect: this.data.rect,
      parentRect: this.data.parentRect || null,
      parent: this.parent,
      elements: this.elements,
      open: this.data.open
    });
    const elementIds = [];
    for (const element of this.elements) {
      element.popup = popup;
      elementIds.push(element.data.id);
      element.addHighlightArea();
    }
    this.container.setAttribute("aria-controls", elementIds.map(id => `${AnnotationPrefix}${id}`).join(","));
    return this.container;
  }
}
class PopupElement {
  #boundKeyDown = this.#keyDown.bind(this);
  #boundHide = this.#hide.bind(this);
  #boundShow = this.#show.bind(this);
  #boundToggle = this.#toggle.bind(this);
  #color = null;
  #container = null;
  #contentsObj = null;
  #dateObj = null;
  #elements = null;
  #parent = null;
  #parentRect = null;
  #pinned = false;
  #popup = null;
  #position = null;
  #rect = null;
  #richText = null;
  #titleObj = null;
  #updates = null;
  #wasVisible = false;
  constructor({
    container,
    color,
    elements,
    titleObj,
    modificationDate,
    contentsObj,
    richText,
    parent,
    rect,
    parentRect,
    open
  }) {
    this.#container = container;
    this.#titleObj = titleObj;
    this.#contentsObj = contentsObj;
    this.#richText = richText;
    this.#parent = parent;
    this.#color = color;
    this.#rect = rect;
    this.#parentRect = parentRect;
    this.#elements = elements;
    this.#dateObj = PDFDateString.toDateObject(modificationDate);
    this.trigger = elements.flatMap(e => e.getElementsToTriggerPopup());
    for (const element of this.trigger) {
      element.addEventListener("click", this.#boundToggle);
      element.addEventListener("mouseenter", this.#boundShow);
      element.addEventListener("mouseleave", this.#boundHide);
      element.classList.add("popupTriggerArea");
    }
    for (const element of elements) {
      element.container?.addEventListener("keydown", this.#boundKeyDown);
    }
    this.#container.hidden = true;
    if (open) {
      this.#toggle();
    }
  }
  render() {
    if (this.#popup) {
      return;
    }
    const popup = this.#popup = document.createElement("div");
    popup.className = "popup";
    if (this.#color) {
      const baseColor = popup.style.outlineColor = Util.makeHexColor(...this.#color);
      popup.style.backgroundColor = `color-mix(in srgb, ${baseColor} 30%, white)`;
    }
    const header = document.createElement("span");
    header.className = "header";
    const title = document.createElement("h1");
    header.append(title);
    ({
      dir: title.dir,
      str: title.textContent
    } = this.#titleObj);
    popup.append(header);
    if (this.#dateObj) {
      const modificationDate = document.createElement("span");
      modificationDate.classList.add("popupDate");
      modificationDate.setAttribute("data-l10n-id", "pdfjs-annotation-date-time-string");
      modificationDate.setAttribute("data-l10n-args", JSON.stringify({
        dateObj: this.#dateObj.valueOf()
      }));
      header.append(modificationDate);
    }
    const html = this.#html;
    if (html) {
      XfaLayer.render({
        xfaHtml: html,
        intent: "richText",
        div: popup
      });
      popup.lastChild.classList.add("richText", "popupContent");
    } else {
      const contents = this._formatContents(this.#contentsObj);
      popup.append(contents);
    }
    this.#container.append(popup);
  }
  get #html() {
    const richText = this.#richText;
    const contentsObj = this.#contentsObj;
    if (richText?.str && (!contentsObj?.str || contentsObj.str === richText.str)) {
      return this.#richText.html || null;
    }
    return null;
  }
  get #fontSize() {
    return this.#html?.attributes?.style?.fontSize || 0;
  }
  get #fontColor() {
    return this.#html?.attributes?.style?.color || null;
  }
  #makePopupContent(text) {
    const popupLines = [];
    const popupContent = {
      str: text,
      html: {
        name: "div",
        attributes: {
          dir: "auto"
        },
        children: [{
          name: "p",
          children: popupLines
        }]
      }
    };
    const lineAttributes = {
      style: {
        color: this.#fontColor,
        fontSize: this.#fontSize ? `calc(${this.#fontSize}px * var(--scale-factor))` : ""
      }
    };
    for (const line of text.split("\n")) {
      popupLines.push({
        name: "span",
        value: line,
        attributes: lineAttributes
      });
    }
    return popupContent;
  }
  _formatContents({
    str,
    dir
  }) {
    const p = document.createElement("p");
    p.classList.add("popupContent");
    p.dir = dir;
    const lines = str.split(/(?:\r\n?|\n)/);
    for (let i = 0, ii = lines.length; i < ii; ++i) {
      const line = lines[i];
      p.append(document.createTextNode(line));
      if (i < ii - 1) {
        p.append(document.createElement("br"));
      }
    }
    return p;
  }
  #keyDown(event) {
    if (event.altKey || event.shiftKey || event.ctrlKey || event.metaKey) {
      return;
    }
    if (event.key === "Enter" || event.key === "Escape" && this.#pinned) {
      this.#toggle();
    }
  }
  updateEdited({
    rect,
    popupContent
  }) {
    this.#updates ||= {
      contentsObj: this.#contentsObj,
      richText: this.#richText
    };
    if (rect) {
      this.#position = null;
    }
    if (popupContent) {
      this.#richText = this.#makePopupContent(popupContent);
      this.#contentsObj = null;
    }
    this.#popup?.remove();
    this.#popup = null;
  }
  resetEdited() {
    if (!this.#updates) {
      return;
    }
    ({
      contentsObj: this.#contentsObj,
      richText: this.#richText
    } = this.#updates);
    this.#updates = null;
    this.#popup?.remove();
    this.#popup = null;
    this.#position = null;
  }
  #setPosition() {
    if (this.#position !== null) {
      return;
    }
    const {
      page: {
        view
      },
      viewport: {
        rawDims: {
          pageWidth,
          pageHeight,
          pageX,
          pageY
        }
      }
    } = this.#parent;
    let useParentRect = !!this.#parentRect;
    let rect = useParentRect ? this.#parentRect : this.#rect;
    for (const element of this.#elements) {
      if (!rect || Util.intersect(element.data.rect, rect) !== null) {
        rect = element.data.rect;
        useParentRect = true;
        break;
      }
    }
    const normalizedRect = Util.normalizeRect([rect[0], view[3] - rect[1] + view[1], rect[2], view[3] - rect[3] + view[1]]);
    const HORIZONTAL_SPACE_AFTER_ANNOTATION = 5;
    const parentWidth = useParentRect ? rect[2] - rect[0] + HORIZONTAL_SPACE_AFTER_ANNOTATION : 0;
    const popupLeft = normalizedRect[0] + parentWidth;
    const popupTop = normalizedRect[1];
    this.#position = [100 * (popupLeft - pageX) / pageWidth, 100 * (popupTop - pageY) / pageHeight];
    const {
      style
    } = this.#container;
    style.left = `${this.#position[0]}%`;
    style.top = `${this.#position[1]}%`;
  }
  #toggle() {
    this.#pinned = !this.#pinned;
    if (this.#pinned) {
      this.#show();
      this.#container.addEventListener("click", this.#boundToggle);
      this.#container.addEventListener("keydown", this.#boundKeyDown);
    } else {
      this.#hide();
      this.#container.removeEventListener("click", this.#boundToggle);
      this.#container.removeEventListener("keydown", this.#boundKeyDown);
    }
  }
  #show() {
    if (!this.#popup) {
      this.render();
    }
    if (!this.isVisible) {
      this.#setPosition();
      this.#container.hidden = false;
      this.#container.style.zIndex = parseInt(this.#container.style.zIndex) + 1000;
    } else if (this.#pinned) {
      this.#container.classList.add("focused");
    }
  }
  #hide() {
    this.#container.classList.remove("focused");
    if (this.#pinned || !this.isVisible) {
      return;
    }
    this.#container.hidden = true;
    this.#container.style.zIndex = parseInt(this.#container.style.zIndex) - 1000;
  }
  forceHide() {
    this.#wasVisible = this.isVisible;
    if (!this.#wasVisible) {
      return;
    }
    this.#container.hidden = true;
  }
  maybeShow() {
    if (!this.#wasVisible) {
      return;
    }
    if (!this.#popup) {
      this.#show();
    }
    this.#wasVisible = false;
    this.#container.hidden = false;
  }
  get isVisible() {
    return this.#container.hidden === false;
  }
}
class FreeTextAnnotationElement extends AnnotationElement {
  constructor(parameters) {
    super(parameters, {
      isRenderable: true,
      ignoreBorder: true
    });
    this.textContent = parameters.data.textContent;
    this.textPosition = parameters.data.textPosition;
    this.annotationEditorType = AnnotationEditorType.FREETEXT;
  }
  render() {
    this.container.classList.add("freeTextAnnotation");
    if (this.textContent) {
      const content = document.createElement("div");
      content.classList.add("annotationTextContent");
      content.setAttribute("role", "comment");
      for (const line of this.textContent) {
        const lineSpan = document.createElement("span");
        lineSpan.textContent = line;
        content.append(lineSpan);
      }
      this.container.append(content);
    }
    if (!this.data.popupRef && this.hasPopupData) {
      this._createPopup();
    }
    this._editOnDoubleClick();
    return this.container;
  }
}
class LineAnnotationElement extends AnnotationElement {
  #line = null;
  constructor(parameters) {
    super(parameters, {
      isRenderable: true,
      ignoreBorder: true
    });
  }
  render() {
    this.container.classList.add("lineAnnotation");
    const data = this.data;
    const {
      width,
      height
    } = getRectDims(data.rect);
    const svg = this.svgFactory.create(width, height, true);
    const line = this.#line = this.svgFactory.createElement("svg:line");
    line.setAttribute("x1", data.rect[2] - data.lineCoordinates[0]);
    line.setAttribute("y1", data.rect[3] - data.lineCoordinates[1]);
    line.setAttribute("x2", data.rect[2] - data.lineCoordinates[2]);
    line.setAttribute("y2", data.rect[3] - data.lineCoordinates[3]);
    line.setAttribute("stroke-width", data.borderStyle.width || 1);
    line.setAttribute("stroke", "transparent");
    line.setAttribute("fill", "transparent");
    svg.append(line);
    this.container.append(svg);
    if (!data.popupRef && this.hasPopupData) {
      this._createPopup();
    }
    return this.container;
  }
  getElementsToTriggerPopup() {
    return this.#line;
  }
  addHighlightArea() {
    this.container.classList.add("highlightArea");
  }
}
class SquareAnnotationElement extends AnnotationElement {
  #square = null;
  constructor(parameters) {
    super(parameters, {
      isRenderable: true,
      ignoreBorder: true
    });
  }
  render() {
    this.container.classList.add("squareAnnotation");
    const data = this.data;
    const {
      width,
      height
    } = getRectDims(data.rect);
    const svg = this.svgFactory.create(width, height, true);
    const borderWidth = data.borderStyle.width;
    const square = this.#square = this.svgFactory.createElement("svg:rect");
    square.setAttribute("x", borderWidth / 2);
    square.setAttribute("y", borderWidth / 2);
    square.setAttribute("width", width - borderWidth);
    square.setAttribute("height", height - borderWidth);
    square.setAttribute("stroke-width", borderWidth || 1);
    square.setAttribute("stroke", "transparent");
    square.setAttribute("fill", "transparent");
    svg.append(square);
    this.container.append(svg);
    if (!data.popupRef && this.hasPopupData) {
      this._createPopup();
    }
    return this.container;
  }
  getElementsToTriggerPopup() {
    return this.#square;
  }
  addHighlightArea() {
    this.container.classList.add("highlightArea");
  }
}
class CircleAnnotationElement extends AnnotationElement {
  #circle = null;
  constructor(parameters) {
    super(parameters, {
      isRenderable: true,
      ignoreBorder: true
    });
  }
  render() {
    this.container.classList.add("circleAnnotation");
    const data = this.data;
    const {
      width,
      height
    } = getRectDims(data.rect);
    const svg = this.svgFactory.create(width, height, true);
    const borderWidth = data.borderStyle.width;
    const circle = this.#circle = this.svgFactory.createElement("svg:ellipse");
    circle.setAttribute("cx", width / 2);
    circle.setAttribute("cy", height / 2);
    circle.setAttribute("rx", width / 2 - borderWidth / 2);
    circle.setAttribute("ry", height / 2 - borderWidth / 2);
    circle.setAttribute("stroke-width", borderWidth || 1);
    circle.setAttribute("stroke", "transparent");
    circle.setAttribute("fill", "transparent");
    svg.append(circle);
    this.container.append(svg);
    if (!data.popupRef && this.hasPopupData) {
      this._createPopup();
    }
    return this.container;
  }
  getElementsToTriggerPopup() {
    return this.#circle;
  }
  addHighlightArea() {
    this.container.classList.add("highlightArea");
  }
}
class PolylineAnnotationElement extends AnnotationElement {
  #polyline = null;
  constructor(parameters) {
    super(parameters, {
      isRenderable: true,
      ignoreBorder: true
    });
    this.containerClassName = "polylineAnnotation";
    this.svgElementName = "svg:polyline";
  }
  render() {
    this.container.classList.add(this.containerClassName);
    const {
      data: {
        rect,
        vertices,
        borderStyle,
        popupRef
      }
    } = this;
    if (!vertices) {
      return this.container;
    }
    const {
      width,
      height
    } = getRectDims(rect);
    const svg = this.svgFactory.create(width, height, true);
    let points = [];
    for (let i = 0, ii = vertices.length; i < ii; i += 2) {
      const x = vertices[i] - rect[0];
      const y = rect[3] - vertices[i + 1];
      points.push(`${x},${y}`);
    }
    points = points.join(" ");
    const polyline = this.#polyline = this.svgFactory.createElement(this.svgElementName);
    polyline.setAttribute("points", points);
    polyline.setAttribute("stroke-width", borderStyle.width || 1);
    polyline.setAttribute("stroke", "transparent");
    polyline.setAttribute("fill", "transparent");
    svg.append(polyline);
    this.container.append(svg);
    if (!popupRef && this.hasPopupData) {
      this._createPopup();
    }
    return this.container;
  }
  getElementsToTriggerPopup() {
    return this.#polyline;
  }
  addHighlightArea() {
    this.container.classList.add("highlightArea");
  }
}
class PolygonAnnotationElement extends PolylineAnnotationElement {
  constructor(parameters) {
    super(parameters);
    this.containerClassName = "polygonAnnotation";
    this.svgElementName = "svg:polygon";
  }
}
class CaretAnnotationElement extends AnnotationElement {
  constructor(parameters) {
    super(parameters, {
      isRenderable: true,
      ignoreBorder: true
    });
  }
  render() {
    this.container.classList.add("caretAnnotation");
    if (!this.data.popupRef && this.hasPopupData) {
      this._createPopup();
    }
    return this.container;
  }
}
class InkAnnotationElement extends AnnotationElement {
  #polylines = [];
  constructor(parameters) {
    super(parameters, {
      isRenderable: true,
      ignoreBorder: true
    });
    this.containerClassName = "inkAnnotation";
    this.svgElementName = "svg:polyline";
    this.annotationEditorType = AnnotationEditorType.INK;
  }
  render() {
    this.container.classList.add(this.containerClassName);
    const {
      data: {
        rect,
        inkLists,
        borderStyle,
        popupRef
      }
    } = this;
    const {
      width,
      height
    } = getRectDims(rect);
    const svg = this.svgFactory.create(width, height, true);
    for (const inkList of inkLists) {
      let points = [];
      for (let i = 0, ii = inkList.length; i < ii; i += 2) {
        const x = inkList[i] - rect[0];
        const y = rect[3] - inkList[i + 1];
        points.push(`${x},${y}`);
      }
      points = points.join(" ");
      const polyline = this.svgFactory.createElement(this.svgElementName);
      this.#polylines.push(polyline);
      polyline.setAttribute("points", points);
      polyline.setAttribute("stroke-width", borderStyle.width || 1);
      polyline.setAttribute("stroke", "transparent");
      polyline.setAttribute("fill", "transparent");
      if (!popupRef && this.hasPopupData) {
        this._createPopup();
      }
      svg.append(polyline);
    }
    this.container.append(svg);
    return this.container;
  }
  getElementsToTriggerPopup() {
    return this.#polylines;
  }
  addHighlightArea() {
    this.container.classList.add("highlightArea");
  }
}
class HighlightAnnotationElement extends AnnotationElement {
  constructor(parameters) {
    super(parameters, {
      isRenderable: true,
      ignoreBorder: true,
      createQuadrilaterals: true
    });
  }
  render() {
    if (!this.data.popupRef && this.hasPopupData) {
      this._createPopup();
    }
    this.container.classList.add("highlightAnnotation");
    return this.container;
  }
}
class UnderlineAnnotationElement extends AnnotationElement {
  constructor(parameters) {
    super(parameters, {
      isRenderable: true,
      ignoreBorder: true,
      createQuadrilaterals: true
    });
  }
  render() {
    if (!this.data.popupRef && this.hasPopupData) {
      this._createPopup();
    }
    this.container.classList.add("underlineAnnotation");
    return this.container;
  }
}
class SquigglyAnnotationElement extends AnnotationElement {
  constructor(parameters) {
    super(parameters, {
      isRenderable: true,
      ignoreBorder: true,
      createQuadrilaterals: true
    });
  }
  render() {
    if (!this.data.popupRef && this.hasPopupData) {
      this._createPopup();
    }
    this.container.classList.add("squigglyAnnotation");
    return this.container;
  }
}
class StrikeOutAnnotationElement extends AnnotationElement {
  constructor(parameters) {
    super(parameters, {
      isRenderable: true,
      ignoreBorder: true,
      createQuadrilaterals: true
    });
  }
  render() {
    if (!this.data.popupRef && this.hasPopupData) {
      this._createPopup();
    }
    this.container.classList.add("strikeoutAnnotation");
    return this.container;
  }
}
class StampAnnotationElement extends AnnotationElement {
  constructor(parameters) {
    super(parameters, {
      isRenderable: true,
      ignoreBorder: true
    });
  }
  render() {
    this.container.classList.add("stampAnnotation");
    if (!this.data.popupRef && this.hasPopupData) {
      this._createPopup();
    }
    return this.container;
  }
}
class FileAttachmentAnnotationElement extends AnnotationElement {
  #trigger = null;
  constructor(parameters) {
    super(parameters, {
      isRenderable: true
    });
    const {
      file
    } = this.data;
    this.filename = file.filename;
    this.content = file.content;
    this.linkService.eventBus?.dispatch("fileattachmentannotation", {
      source: this,
      ...file
    });
  }
  render() {
    this.container.classList.add("fileAttachmentAnnotation");
    const {
      container,
      data
    } = this;
    let trigger;
    if (data.hasAppearance || data.fillAlpha === 0) {
      trigger = document.createElement("div");
    } else {
      trigger = document.createElement("img");
      trigger.src = `${this.imageResourcesPath}annotation-${/paperclip/i.test(data.name) ? "paperclip" : "pushpin"}.svg`;
      if (data.fillAlpha && data.fillAlpha < 1) {
        trigger.style = `filter: opacity(${Math.round(data.fillAlpha * 100)}%);`;
      }
    }
    trigger.addEventListener("dblclick", this.#download.bind(this));
    this.#trigger = trigger;
    const {
      isMac
    } = util_FeatureTest.platform;
    container.addEventListener("keydown", evt => {
      if (evt.key === "Enter" && (isMac ? evt.metaKey : evt.ctrlKey)) {
        this.#download();
      }
    });
    if (!data.popupRef && this.hasPopupData) {
      this._createPopup();
    } else {
      trigger.classList.add("popupTriggerArea");
    }
    container.append(trigger);
    return container;
  }
  getElementsToTriggerPopup() {
    return this.#trigger;
  }
  addHighlightArea() {
    this.container.classList.add("highlightArea");
  }
  #download() {
    this.downloadManager?.openOrDownloadData(this.content, this.filename);
  }
}
class AnnotationLayer {
  #accessibilityManager = null;
  #annotationCanvasMap = null;
  #editableAnnotations = new Map();
  constructor({
    div,
    accessibilityManager,
    annotationCanvasMap,
    annotationEditorUIManager,
    page,
    viewport
  }) {
    this.div = div;
    this.#accessibilityManager = accessibilityManager;
    this.#annotationCanvasMap = annotationCanvasMap;
    this.page = page;
    this.viewport = viewport;
    this.zIndex = 0;
    this._annotationEditorUIManager = annotationEditorUIManager;
  }
  hasEditableAnnotations() {
    return this.#editableAnnotations.size > 0;
  }
  #appendElement(element, id) {
    const contentElement = element.firstChild || element;
    contentElement.id = `${AnnotationPrefix}${id}`;
    this.div.append(element);
    this.#accessibilityManager?.moveElementInDOM(this.div, element, contentElement, false);
  }
  async render(params) {
    const {
      annotations
    } = params;
    const layer = this.div;
    setLayerDimensions(layer, this.viewport);
    const popupToElements = new Map();
    const elementParams = {
      data: null,
      layer,
      linkService: params.linkService,
      downloadManager: params.downloadManager,
      imageResourcesPath: params.imageResourcesPath || "",
      renderForms: params.renderForms !== false,
      svgFactory: new DOMSVGFactory(),
      annotationStorage: params.annotationStorage || new AnnotationStorage(),
      enableScripting: params.enableScripting === true,
      hasJSActions: params.hasJSActions,
      fieldObjects: params.fieldObjects,
      parent: this,
      elements: null
    };
    for (const data of annotations) {
      if (data.noHTML) {
        continue;
      }
      const isPopupAnnotation = data.annotationType === AnnotationType.POPUP;
      if (!isPopupAnnotation) {
        const {
          width,
          height
        } = getRectDims(data.rect);
        if (width <= 0 || height <= 0) {
          continue;
        }
      } else {
        const elements = popupToElements.get(data.id);
        if (!elements) {
          continue;
        }
        elementParams.elements = elements;
      }
      elementParams.data = data;
      const element = AnnotationElementFactory.create(elementParams);
      if (!element.isRenderable) {
        continue;
      }
      if (!isPopupAnnotation && data.popupRef) {
        const elements = popupToElements.get(data.popupRef);
        if (!elements) {
          popupToElements.set(data.popupRef, [element]);
        } else {
          elements.push(element);
        }
      }
      const rendered = element.render();
      if (data.hidden) {
        rendered.style.visibility = "hidden";
      }
      this.#appendElement(rendered, data.id);
      if (element._isEditable) {
        this.#editableAnnotations.set(element.data.id, element);
        this._annotationEditorUIManager?.renderAnnotationElement(element);
      }
    }
    this.#setAnnotationCanvasMap();
  }
  update({
    viewport
  }) {
    const layer = this.div;
    this.viewport = viewport;
    setLayerDimensions(layer, {
      rotation: viewport.rotation
    });
    this.#setAnnotationCanvasMap();
    layer.hidden = false;
  }
  #setAnnotationCanvasMap() {
    if (!this.#annotationCanvasMap) {
      return;
    }
    const layer = this.div;
    for (const [id, canvas] of this.#annotationCanvasMap) {
      const element = layer.querySelector(`[data-annotation-id="${id}"]`);
      if (!element) {
        continue;
      }
      canvas.className = "annotationContent";
      const {
        firstChild
      } = element;
      if (!firstChild) {
        element.append(canvas);
      } else if (firstChild.nodeName === "CANVAS") {
        firstChild.replaceWith(canvas);
      } else if (!firstChild.classList.contains("annotationContent")) {
        firstChild.before(canvas);
      } else {
        firstChild.after(canvas);
      }
    }
    this.#annotationCanvasMap.clear();
  }
  getEditableAnnotations() {
    return Array.from(this.#editableAnnotations.values());
  }
  getEditableAnnotation(id) {
    return this.#editableAnnotations.get(id);
  }
}

;// CONCATENATED MODULE: ./src/display/editor/freetext.js




const EOL_PATTERN = /\r\n?|\n/g;
class FreeTextEditor extends AnnotationEditor {
  #color;
  #content = "";
  #editorDivId = `${this.id}-editor`;
  #editModeAC = null;
  #fontSize;
  #initialData = null;
  static _freeTextDefaultContent = "";
  static _internalPadding = 0;
  static _defaultColor = null;
  static _defaultFontSize = 10;
  static get _keyboardManager() {
    const proto = FreeTextEditor.prototype;
    const arrowChecker = self => self.isEmpty();
    const small = AnnotationEditorUIManager.TRANSLATE_SMALL;
    const big = AnnotationEditorUIManager.TRANSLATE_BIG;
    return shadow(this, "_keyboardManager", new KeyboardManager([[["ctrl+s", "mac+meta+s", "ctrl+p", "mac+meta+p"], proto.commitOrRemove, {
      bubbles: true
    }], [["ctrl+Enter", "mac+meta+Enter", "Escape", "mac+Escape"], proto.commitOrRemove], [["ArrowLeft", "mac+ArrowLeft"], proto._translateEmpty, {
      args: [-small, 0],
      checker: arrowChecker
    }], [["ctrl+ArrowLeft", "mac+shift+ArrowLeft"], proto._translateEmpty, {
      args: [-big, 0],
      checker: arrowChecker
    }], [["ArrowRight", "mac+ArrowRight"], proto._translateEmpty, {
      args: [small, 0],
      checker: arrowChecker
    }], [["ctrl+ArrowRight", "mac+shift+ArrowRight"], proto._translateEmpty, {
      args: [big, 0],
      checker: arrowChecker
    }], [["ArrowUp", "mac+ArrowUp"], proto._translateEmpty, {
      args: [0, -small],
      checker: arrowChecker
    }], [["ctrl+ArrowUp", "mac+shift+ArrowUp"], proto._translateEmpty, {
      args: [0, -big],
      checker: arrowChecker
    }], [["ArrowDown", "mac+ArrowDown"], proto._translateEmpty, {
      args: [0, small],
      checker: arrowChecker
    }], [["ctrl+ArrowDown", "mac+shift+ArrowDown"], proto._translateEmpty, {
      args: [0, big],
      checker: arrowChecker
    }]]));
  }
  static _type = "freetext";
  static _editorType = AnnotationEditorType.FREETEXT;
  constructor(params) {
    super({
      ...params,
      name: "freeTextEditor"
    });
    this.#color = params.color || FreeTextEditor._defaultColor || AnnotationEditor._defaultLineColor;
    this.#fontSize = params.fontSize || FreeTextEditor._defaultFontSize;
  }
  static initialize(l10n, uiManager) {
    AnnotationEditor.initialize(l10n, uiManager, {
      strings: ["pdfjs-free-text-default-content"]
    });
    const style = getComputedStyle(document.documentElement);
    this._internalPadding = parseFloat(style.getPropertyValue("--freetext-padding"));
  }
  static updateDefaultParams(type, value) {
    switch (type) {
      case AnnotationEditorParamsType.FREETEXT_SIZE:
        FreeTextEditor._defaultFontSize = value;
        break;
      case AnnotationEditorParamsType.FREETEXT_COLOR:
        FreeTextEditor._defaultColor = value;
        break;
    }
  }
  updateParams(type, value) {
    switch (type) {
      case AnnotationEditorParamsType.FREETEXT_SIZE:
        this.#updateFontSize(value);
        break;
      case AnnotationEditorParamsType.FREETEXT_COLOR:
        this.#updateColor(value);
        break;
    }
  }
  static get defaultPropertiesToUpdate() {
    return [[AnnotationEditorParamsType.FREETEXT_SIZE, FreeTextEditor._defaultFontSize], [AnnotationEditorParamsType.FREETEXT_COLOR, FreeTextEditor._defaultColor || AnnotationEditor._defaultLineColor]];
  }
  get propertiesToUpdate() {
    return [[AnnotationEditorParamsType.FREETEXT_SIZE, this.#fontSize], [AnnotationEditorParamsType.FREETEXT_COLOR, this.#color]];
  }
  #updateFontSize(fontSize) {
    const setFontsize = size => {
      this.editorDiv.style.fontSize = `calc(${size}px * var(--scale-factor))`;
      this.translate(0, -(size - this.#fontSize) * this.parentScale);
      this.#fontSize = size;
      this.#setEditorDimensions();
    };
    const savedFontsize = this.#fontSize;
    this.addCommands({
      cmd: setFontsize.bind(this, fontSize),
      undo: setFontsize.bind(this, savedFontsize),
      post: this._uiManager.updateUI.bind(this._uiManager, this),
      mustExec: true,
      type: AnnotationEditorParamsType.FREETEXT_SIZE,
      overwriteIfSameType: true,
      keepUndo: true
    });
  }
  #updateColor(color) {
    const setColor = col => {
      this.#color = this.editorDiv.style.color = col;
    };
    const savedColor = this.#color;
    this.addCommands({
      cmd: setColor.bind(this, color),
      undo: setColor.bind(this, savedColor),
      post: this._uiManager.updateUI.bind(this._uiManager, this),
      mustExec: true,
      type: AnnotationEditorParamsType.FREETEXT_COLOR,
      overwriteIfSameType: true,
      keepUndo: true
    });
  }
  _translateEmpty(x, y) {
    this._uiManager.translateSelectedEditors(x, y, true);
  }
  getInitialTranslation() {
    const scale = this.parentScale;
    return [-FreeTextEditor._internalPadding * scale, -(FreeTextEditor._internalPadding + this.#fontSize) * scale];
  }
  rebuild() {
    if (!this.parent) {
      return;
    }
    super.rebuild();
    if (this.div === null) {
      return;
    }
    if (!this.isAttachedToDOM) {
      this.parent.add(this);
    }
  }
  enableEditMode() {
    if (this.isInEditMode()) {
      return;
    }
    this.parent.setEditingState(false);
    this.parent.updateToolbar(AnnotationEditorType.FREETEXT);
    super.enableEditMode();
    this.overlayDiv.classList.remove("enabled");
    this.editorDiv.contentEditable = true;
    this._isDraggable = false;
    this.div.removeAttribute("aria-activedescendant");
    this.#editModeAC = new AbortController();
    const signal = this._uiManager.combinedSignal(this.#editModeAC);
    this.editorDiv.addEventListener("keydown", this.editorDivKeydown.bind(this), {
      signal
    });
    this.editorDiv.addEventListener("focus", this.editorDivFocus.bind(this), {
      signal
    });
    this.editorDiv.addEventListener("blur", this.editorDivBlur.bind(this), {
      signal
    });
    this.editorDiv.addEventListener("input", this.editorDivInput.bind(this), {
      signal
    });
    this.editorDiv.addEventListener("paste", this.editorDivPaste.bind(this), {
      signal
    });
  }
  disableEditMode() {
    if (!this.isInEditMode()) {
      return;
    }
    this.parent.setEditingState(true);
    super.disableEditMode();
    this.overlayDiv.classList.add("enabled");
    this.editorDiv.contentEditable = false;
    this.div.setAttribute("aria-activedescendant", this.#editorDivId);
    this._isDraggable = true;
    this.#editModeAC?.abort();
    this.#editModeAC = null;
    this.div.focus({
      preventScroll: true
    });
    this.isEditing = false;
    this.parent.div.classList.add("freetextEditing");
  }
  focusin(event) {
    if (!this._focusEventsAllowed) {
      return;
    }
    super.focusin(event);
    if (event.target !== this.editorDiv) {
      this.editorDiv.focus();
    }
  }
  onceAdded() {
    if (this.width) {
      return;
    }
    this.enableEditMode();
    this.editorDiv.focus();
    if (this._initialOptions?.isCentered) {
      this.center();
    }
    this._initialOptions = null;
  }
  isEmpty() {
    return !this.editorDiv || this.editorDiv.innerText.trim() === "";
  }
  remove() {
    this.isEditing = false;
    if (this.parent) {
      this.parent.setEditingState(true);
      this.parent.div.classList.add("freetextEditing");
    }
    super.remove();
  }
  #extractText() {
    const buffer = [];
    this.editorDiv.normalize();
    for (const child of this.editorDiv.childNodes) {
      buffer.push(FreeTextEditor.#getNodeContent(child));
    }
    return buffer.join("\n");
  }
  #setEditorDimensions() {
    const [parentWidth, parentHeight] = this.parentDimensions;
    let rect;
    if (this.isAttachedToDOM) {
      rect = this.div.getBoundingClientRect();
    } else {
      const {
        currentLayer,
        div
      } = this;
      const savedDisplay = div.style.display;
      const savedVisibility = div.classList.contains("hidden");
      div.classList.remove("hidden");
      div.style.display = "hidden";
      currentLayer.div.append(this.div);
      rect = div.getBoundingClientRect();
      div.remove();
      div.style.display = savedDisplay;
      div.classList.toggle("hidden", savedVisibility);
    }
    if (this.rotation % 180 === this.parentRotation % 180) {
      this.width = rect.width / parentWidth;
      this.height = rect.height / parentHeight;
    } else {
      this.width = rect.height / parentWidth;
      this.height = rect.width / parentHeight;
    }
    this.fixAndSetPosition();
  }
  commit() {
    if (!this.isInEditMode()) {
      return;
    }
    super.commit();
    this.disableEditMode();
    const savedText = this.#content;
    const newText = this.#content = this.#extractText().trimEnd();
    if (savedText === newText) {
      return;
    }
    const setText = text => {
      this.#content = text;
      if (!text) {
        this.remove();
        return;
      }
      this.#setContent();
      this._uiManager.rebuild(this);
      this.#setEditorDimensions();
    };
    this.addCommands({
      cmd: () => {
        setText(newText);
      },
      undo: () => {
        setText(savedText);
      },
      mustExec: false
    });
    this.#setEditorDimensions();
  }
  shouldGetKeyboardEvents() {
    return this.isInEditMode();
  }
  enterInEditMode() {
    this.enableEditMode();
    this.editorDiv.focus();
  }
  dblclick(event) {
    this.enterInEditMode();
  }
  keydown(event) {
    if (event.target === this.div && event.key === "Enter") {
      this.enterInEditMode();
      event.preventDefault();
    }
  }
  editorDivKeydown(event) {
    FreeTextEditor._keyboardManager.exec(this, event);
  }
  editorDivFocus(event) {
    this.isEditing = true;
  }
  editorDivBlur(event) {
    this.isEditing = false;
  }
  editorDivInput(event) {
    this.parent.div.classList.toggle("freetextEditing", this.isEmpty());
  }
  disableEditing() {
    this.editorDiv.setAttribute("role", "comment");
    this.editorDiv.removeAttribute("aria-multiline");
  }
  enableEditing() {
    this.editorDiv.setAttribute("role", "textbox");
    this.editorDiv.setAttribute("aria-multiline", true);
  }
  render() {
    if (this.div) {
      return this.div;
    }
    let baseX, baseY;
    if (this.width) {
      baseX = this.x;
      baseY = this.y;
    }
    super.render();
    this.editorDiv = document.createElement("div");
    this.editorDiv.className = "internal";
    this.editorDiv.setAttribute("id", this.#editorDivId);
    this.editorDiv.setAttribute("data-l10n-id", "pdfjs-free-text");
    this.enableEditing();
    AnnotationEditor._l10nPromise.get("pdfjs-free-text-default-content").then(msg => this.editorDiv?.setAttribute("default-content", msg));
    this.editorDiv.contentEditable = true;
    const {
      style
    } = this.editorDiv;
    style.fontSize = `calc(${this.#fontSize}px * var(--scale-factor))`;
    style.color = this.#color;
    this.div.append(this.editorDiv);
    this.overlayDiv = document.createElement("div");
    this.overlayDiv.classList.add("overlay", "enabled");
    this.div.append(this.overlayDiv);
    bindEvents(this, this.div, ["dblclick", "keydown"]);
    if (this.width) {
      const [parentWidth, parentHeight] = this.parentDimensions;
      if (this.annotationElementId) {
        const {
          position
        } = this.#initialData;
        let [tx, ty] = this.getInitialTranslation();
        [tx, ty] = this.pageTranslationToScreen(tx, ty);
        const [pageWidth, pageHeight] = this.pageDimensions;
        const [pageX, pageY] = this.pageTranslation;
        let posX, posY;
        switch (this.rotation) {
          case 0:
            posX = baseX + (position[0] - pageX) / pageWidth;
            posY = baseY + this.height - (position[1] - pageY) / pageHeight;
            break;
          case 90:
            posX = baseX + (position[0] - pageX) / pageWidth;
            posY = baseY - (position[1] - pageY) / pageHeight;
            [tx, ty] = [ty, -tx];
            break;
          case 180:
            posX = baseX - this.width + (position[0] - pageX) / pageWidth;
            posY = baseY - (position[1] - pageY) / pageHeight;
            [tx, ty] = [-tx, -ty];
            break;
          case 270:
            posX = baseX + (position[0] - pageX - this.height * pageHeight) / pageWidth;
            posY = baseY + (position[1] - pageY - this.width * pageWidth) / pageHeight;
            [tx, ty] = [-ty, tx];
            break;
        }
        this.setAt(posX * parentWidth, posY * parentHeight, tx, ty);
      } else {
        this.setAt(baseX * parentWidth, baseY * parentHeight, this.width * parentWidth, this.height * parentHeight);
      }
      this.#setContent();
      this._isDraggable = true;
      this.editorDiv.contentEditable = false;
    } else {
      this._isDraggable = false;
      this.editorDiv.contentEditable = true;
    }
    return this.div;
  }
  static #getNodeContent(node) {
    return (node.nodeType === Node.TEXT_NODE ? node.nodeValue : node.innerText).replaceAll(EOL_PATTERN, "");
  }
  editorDivPaste(event) {
    const clipboardData = event.clipboardData || window.clipboardData;
    const {
      types
    } = clipboardData;
    if (types.length === 1 && types[0] === "text/plain") {
      return;
    }
    event.preventDefault();
    const paste = FreeTextEditor.#deserializeContent(clipboardData.getData("text") || "").replaceAll(EOL_PATTERN, "\n");
    if (!paste) {
      return;
    }
    const selection = window.getSelection();
    if (!selection.rangeCount) {
      return;
    }
    this.editorDiv.normalize();
    selection.deleteFromDocument();
    const range = selection.getRangeAt(0);
    if (!paste.includes("\n")) {
      range.insertNode(document.createTextNode(paste));
      this.editorDiv.normalize();
      selection.collapseToStart();
      return;
    }
    const {
      startContainer,
      startOffset
    } = range;
    const bufferBefore = [];
    const bufferAfter = [];
    if (startContainer.nodeType === Node.TEXT_NODE) {
      const parent = startContainer.parentElement;
      bufferAfter.push(startContainer.nodeValue.slice(startOffset).replaceAll(EOL_PATTERN, ""));
      if (parent !== this.editorDiv) {
        let buffer = bufferBefore;
        for (const child of this.editorDiv.childNodes) {
          if (child === parent) {
            buffer = bufferAfter;
            continue;
          }
          buffer.push(FreeTextEditor.#getNodeContent(child));
        }
      }
      bufferBefore.push(startContainer.nodeValue.slice(0, startOffset).replaceAll(EOL_PATTERN, ""));
    } else if (startContainer === this.editorDiv) {
      let buffer = bufferBefore;
      let i = 0;
      for (const child of this.editorDiv.childNodes) {
        if (i++ === startOffset) {
          buffer = bufferAfter;
        }
        buffer.push(FreeTextEditor.#getNodeContent(child));
      }
    }
    this.#content = `${bufferBefore.join("\n")}${paste}${bufferAfter.join("\n")}`;
    this.#setContent();
    const newRange = new Range();
    let beforeLength = bufferBefore.reduce((acc, line) => acc + line.length, 0);
    for (const {
      firstChild
    } of this.editorDiv.childNodes) {
      if (firstChild.nodeType === Node.TEXT_NODE) {
        const length = firstChild.nodeValue.length;
        if (beforeLength <= length) {
          newRange.setStart(firstChild, beforeLength);
          newRange.setEnd(firstChild, beforeLength);
          break;
        }
        beforeLength -= length;
      }
    }
    selection.removeAllRanges();
    selection.addRange(newRange);
  }
  #setContent() {
    this.editorDiv.replaceChildren();
    if (!this.#content) {
      return;
    }
    for (const line of this.#content.split("\n")) {
      const div = document.createElement("div");
      div.append(line ? document.createTextNode(line) : document.createElement("br"));
      this.editorDiv.append(div);
    }
  }
  #serializeContent() {
    return this.#content.replaceAll("\xa0", " ");
  }
  static #deserializeContent(content) {
    return content.replaceAll(" ", "\xa0");
  }
  get contentDiv() {
    return this.editorDiv;
  }
  static deserialize(data, parent, uiManager) {
    let initialData = null;
    if (data instanceof FreeTextAnnotationElement) {
      const {
        data: {
          defaultAppearanceData: {
            fontSize,
            fontColor
          },
          rect,
          rotation,
          id
        },
        textContent,
        textPosition,
        parent: {
          page: {
            pageNumber
          }
        }
      } = data;
      if (!textContent || textContent.length === 0) {
        return null;
      }
      initialData = data = {
        annotationType: AnnotationEditorType.FREETEXT,
        color: Array.from(fontColor),
        fontSize,
        value: textContent.join("\n"),
        position: textPosition,
        pageIndex: pageNumber - 1,
        rect: rect.slice(0),
        rotation,
        id,
        deleted: false
      };
    }
    const editor = super.deserialize(data, parent, uiManager);
    editor.#fontSize = data.fontSize;
    editor.#color = Util.makeHexColor(...data.color);
    editor.#content = FreeTextEditor.#deserializeContent(data.value);
    editor.annotationElementId = data.id || null;
    editor.#initialData = initialData;
    return editor;
  }
  serialize(isForCopying = false) {
    if (this.isEmpty()) {
      return null;
    }
    if (this.deleted) {
      return {
        pageIndex: this.pageIndex,
        id: this.annotationElementId,
        deleted: true
      };
    }
    const padding = FreeTextEditor._internalPadding * this.parentScale;
    const rect = this.getRect(padding, padding);
    const color = AnnotationEditor._colorManager.convert(this.isAttachedToDOM ? getComputedStyle(this.editorDiv).color : this.#color);
    const serialized = {
      annotationType: AnnotationEditorType.FREETEXT,
      color,
      fontSize: this.#fontSize,
      value: this.#serializeContent(),
      pageIndex: this.pageIndex,
      rect,
      rotation: this.rotation,
      structTreeParentId: this._structTreeParentId
    };
    if (isForCopying) {
      return serialized;
    }
    if (this.annotationElementId && !this.#hasElementChanged(serialized)) {
      return null;
    }
    serialized.id = this.annotationElementId;
    return serialized;
  }
  #hasElementChanged(serialized) {
    const {
      value,
      fontSize,
      color,
      pageIndex
    } = this.#initialData;
    return this._hasBeenMoved || serialized.value !== value || serialized.fontSize !== fontSize || serialized.color.some((c, i) => c !== color[i]) || serialized.pageIndex !== pageIndex;
  }
  renderAnnotationElement(annotation) {
    const content = super.renderAnnotationElement(annotation);
    if (this.deleted) {
      return content;
    }
    const {
      style
    } = content;
    style.fontSize = `calc(${this.#fontSize}px * var(--scale-factor))`;
    style.color = this.#color;
    content.replaceChildren();
    for (const line of this.#content.split("\n")) {
      const div = document.createElement("div");
      div.append(line ? document.createTextNode(line) : document.createElement("br"));
      content.append(div);
    }
    const padding = FreeTextEditor._internalPadding * this.parentScale;
    annotation.updateEdited({
      rect: this.getRect(padding, padding),
      popupContent: this.#content
    });
    return content;
  }
  resetAnnotationElement(annotation) {
    super.resetAnnotationElement(annotation);
    annotation.resetEdited();
  }
}

;// CONCATENATED MODULE: ./src/display/editor/outliner.js

class Outliner {
  #box;
  #verticalEdges = [];
  #intervals = [];
  constructor(boxes, borderWidth = 0, innerMargin = 0, isLTR = true) {
    let minX = Infinity;
    let maxX = -Infinity;
    let minY = Infinity;
    let maxY = -Infinity;
    const NUMBER_OF_DIGITS = 4;
    const EPSILON = 10 ** -NUMBER_OF_DIGITS;
    for (const {
      x,
      y,
      width,
      height
    } of boxes) {
      const x1 = Math.floor((x - borderWidth) / EPSILON) * EPSILON;
      const x2 = Math.ceil((x + width + borderWidth) / EPSILON) * EPSILON;
      const y1 = Math.floor((y - borderWidth) / EPSILON) * EPSILON;
      const y2 = Math.ceil((y + height + borderWidth) / EPSILON) * EPSILON;
      const left = [x1, y1, y2, true];
      const right = [x2, y1, y2, false];
      this.#verticalEdges.push(left, right);
      minX = Math.min(minX, x1);
      maxX = Math.max(maxX, x2);
      minY = Math.min(minY, y1);
      maxY = Math.max(maxY, y2);
    }
    const bboxWidth = maxX - minX + 2 * innerMargin;
    const bboxHeight = maxY - minY + 2 * innerMargin;
    const shiftedMinX = minX - innerMargin;
    const shiftedMinY = minY - innerMargin;
    const lastEdge = this.#verticalEdges.at(isLTR ? -1 : -2);
    const lastPoint = [lastEdge[0], lastEdge[2]];
    for (const edge of this.#verticalEdges) {
      const [x, y1, y2] = edge;
      edge[0] = (x - shiftedMinX) / bboxWidth;
      edge[1] = (y1 - shiftedMinY) / bboxHeight;
      edge[2] = (y2 - shiftedMinY) / bboxHeight;
    }
    this.#box = {
      x: shiftedMinX,
      y: shiftedMinY,
      width: bboxWidth,
      height: bboxHeight,
      lastPoint
    };
  }
  getOutlines() {
    this.#verticalEdges.sort((a, b) => a[0] - b[0] || a[1] - b[1] || a[2] - b[2]);
    const outlineVerticalEdges = [];
    for (const edge of this.#verticalEdges) {
      if (edge[3]) {
        outlineVerticalEdges.push(...this.#breakEdge(edge));
        this.#insert(edge);
      } else {
        this.#remove(edge);
        outlineVerticalEdges.push(...this.#breakEdge(edge));
      }
    }
    return this.#getOutlines(outlineVerticalEdges);
  }
  #getOutlines(outlineVerticalEdges) {
    const edges = [];
    const allEdges = new Set();
    for (const edge of outlineVerticalEdges) {
      const [x, y1, y2] = edge;
      edges.push([x, y1, edge], [x, y2, edge]);
    }
    edges.sort((a, b) => a[1] - b[1] || a[0] - b[0]);
    for (let i = 0, ii = edges.length; i < ii; i += 2) {
      const edge1 = edges[i][2];
      const edge2 = edges[i + 1][2];
      edge1.push(edge2);
      edge2.push(edge1);
      allEdges.add(edge1);
      allEdges.add(edge2);
    }
    const outlines = [];
    let outline;
    while (allEdges.size > 0) {
      const edge = allEdges.values().next().value;
      let [x, y1, y2, edge1, edge2] = edge;
      allEdges.delete(edge);
      let lastPointX = x;
      let lastPointY = y1;
      outline = [x, y2];
      outlines.push(outline);
      while (true) {
        let e;
        if (allEdges.has(edge1)) {
          e = edge1;
        } else if (allEdges.has(edge2)) {
          e = edge2;
        } else {
          break;
        }
        allEdges.delete(e);
        [x, y1, y2, edge1, edge2] = e;
        if (lastPointX !== x) {
          outline.push(lastPointX, lastPointY, x, lastPointY === y1 ? y1 : y2);
          lastPointX = x;
        }
        lastPointY = lastPointY === y1 ? y2 : y1;
      }
      outline.push(lastPointX, lastPointY);
    }
    return new HighlightOutline(outlines, this.#box);
  }
  #binarySearch(y) {
    const array = this.#intervals;
    let start = 0;
    let end = array.length - 1;
    while (start <= end) {
      const middle = start + end >> 1;
      const y1 = array[middle][0];
      if (y1 === y) {
        return middle;
      }
      if (y1 < y) {
        start = middle + 1;
      } else {
        end = middle - 1;
      }
    }
    return end + 1;
  }
  #insert([, y1, y2]) {
    const index = this.#binarySearch(y1);
    this.#intervals.splice(index, 0, [y1, y2]);
  }
  #remove([, y1, y2]) {
    const index = this.#binarySearch(y1);
    for (let i = index; i < this.#intervals.length; i++) {
      const [start, end] = this.#intervals[i];
      if (start !== y1) {
        break;
      }
      if (start === y1 && end === y2) {
        this.#intervals.splice(i, 1);
        return;
      }
    }
    for (let i = index - 1; i >= 0; i--) {
      const [start, end] = this.#intervals[i];
      if (start !== y1) {
        break;
      }
      if (start === y1 && end === y2) {
        this.#intervals.splice(i, 1);
        return;
      }
    }
  }
  #breakEdge(edge) {
    const [x, y1, y2] = edge;
    const results = [[x, y1, y2]];
    const index = this.#binarySearch(y2);
    for (let i = 0; i < index; i++) {
      const [start, end] = this.#intervals[i];
      for (let j = 0, jj = results.length; j < jj; j++) {
        const [, y3, y4] = results[j];
        if (end <= y3 || y4 <= start) {
          continue;
        }
        if (y3 >= start) {
          if (y4 > end) {
            results[j][1] = end;
          } else {
            if (jj === 1) {
              return [];
            }
            results.splice(j, 1);
            j--;
            jj--;
          }
          continue;
        }
        results[j][2] = start;
        if (y4 > end) {
          results.push([x, end, y4]);
        }
      }
    }
    return results;
  }
}
class Outline {
  toSVGPath() {
    throw new Error("Abstract method `toSVGPath` must be implemented.");
  }
  get box() {
    throw new Error("Abstract getter `box` must be implemented.");
  }
  serialize(_bbox, _rotation) {
    throw new Error("Abstract method `serialize` must be implemented.");
  }
  get free() {
    return this instanceof FreeHighlightOutline;
  }
}
class HighlightOutline extends Outline {
  #box;
  #outlines;
  constructor(outlines, box) {
    super();
    this.#outlines = outlines;
    this.#box = box;
  }
  toSVGPath() {
    const buffer = [];
    for (const polygon of this.#outlines) {
      let [prevX, prevY] = polygon;
      buffer.push(`M${prevX} ${prevY}`);
      for (let i = 2; i < polygon.length; i += 2) {
        const x = polygon[i];
        const y = polygon[i + 1];
        if (x === prevX) {
          buffer.push(`V${y}`);
          prevY = y;
        } else if (y === prevY) {
          buffer.push(`H${x}`);
          prevX = x;
        }
      }
      buffer.push("Z");
    }
    return buffer.join(" ");
  }
  serialize([blX, blY, trX, trY], _rotation) {
    const outlines = [];
    const width = trX - blX;
    const height = trY - blY;
    for (const outline of this.#outlines) {
      const points = new Array(outline.length);
      for (let i = 0; i < outline.length; i += 2) {
        points[i] = blX + outline[i] * width;
        points[i + 1] = trY - outline[i + 1] * height;
      }
      outlines.push(points);
    }
    return outlines;
  }
  get box() {
    return this.#box;
  }
}
class FreeOutliner {
  #box;
  #bottom = [];
  #innerMargin;
  #isLTR;
  #top = [];
  #last = new Float64Array(18);
  #lastX;
  #lastY;
  #min;
  #min_dist;
  #scaleFactor;
  #thickness;
  #points = [];
  static #MIN_DIST = 8;
  static #MIN_DIFF = 2;
  static #MIN = FreeOutliner.#MIN_DIST + FreeOutliner.#MIN_DIFF;
  constructor({
    x,
    y
  }, box, scaleFactor, thickness, isLTR, innerMargin = 0) {
    this.#box = box;
    this.#thickness = thickness * scaleFactor;
    this.#isLTR = isLTR;
    this.#last.set([NaN, NaN, NaN, NaN, x, y], 6);
    this.#innerMargin = innerMargin;
    this.#min_dist = FreeOutliner.#MIN_DIST * scaleFactor;
    this.#min = FreeOutliner.#MIN * scaleFactor;
    this.#scaleFactor = scaleFactor;
    this.#points.push(x, y);
  }
  get free() {
    return true;
  }
  isEmpty() {
    return isNaN(this.#last[8]);
  }
  #getLastCoords() {
    const lastTop = this.#last.subarray(4, 6);
    const lastBottom = this.#last.subarray(16, 18);
    const [x, y, width, height] = this.#box;
    return [(this.#lastX + (lastTop[0] - lastBottom[0]) / 2 - x) / width, (this.#lastY + (lastTop[1] - lastBottom[1]) / 2 - y) / height, (this.#lastX + (lastBottom[0] - lastTop[0]) / 2 - x) / width, (this.#lastY + (lastBottom[1] - lastTop[1]) / 2 - y) / height];
  }
  add({
    x,
    y
  }) {
    this.#lastX = x;
    this.#lastY = y;
    const [layerX, layerY, layerWidth, layerHeight] = this.#box;
    let [x1, y1, x2, y2] = this.#last.subarray(8, 12);
    const diffX = x - x2;
    const diffY = y - y2;
    const d = Math.hypot(diffX, diffY);
    if (d < this.#min) {
      return false;
    }
    const diffD = d - this.#min_dist;
    const K = diffD / d;
    const shiftX = K * diffX;
    const shiftY = K * diffY;
    let x0 = x1;
    let y0 = y1;
    x1 = x2;
    y1 = y2;
    x2 += shiftX;
    y2 += shiftY;
    this.#points?.push(x, y);
    const nX = -shiftY / diffD;
    const nY = shiftX / diffD;
    const thX = nX * this.#thickness;
    const thY = nY * this.#thickness;
    this.#last.set(this.#last.subarray(2, 8), 0);
    this.#last.set([x2 + thX, y2 + thY], 4);
    this.#last.set(this.#last.subarray(14, 18), 12);
    this.#last.set([x2 - thX, y2 - thY], 16);
    if (isNaN(this.#last[6])) {
      if (this.#top.length === 0) {
        this.#last.set([x1 + thX, y1 + thY], 2);
        this.#top.push(NaN, NaN, NaN, NaN, (x1 + thX - layerX) / layerWidth, (y1 + thY - layerY) / layerHeight);
        this.#last.set([x1 - thX, y1 - thY], 14);
        this.#bottom.push(NaN, NaN, NaN, NaN, (x1 - thX - layerX) / layerWidth, (y1 - thY - layerY) / layerHeight);
      }
      this.#last.set([x0, y0, x1, y1, x2, y2], 6);
      return !this.isEmpty();
    }
    this.#last.set([x0, y0, x1, y1, x2, y2], 6);
    const angle = Math.abs(Math.atan2(y0 - y1, x0 - x1) - Math.atan2(shiftY, shiftX));
    if (angle < Math.PI / 2) {
      [x1, y1, x2, y2] = this.#last.subarray(2, 6);
      this.#top.push(NaN, NaN, NaN, NaN, ((x1 + x2) / 2 - layerX) / layerWidth, ((y1 + y2) / 2 - layerY) / layerHeight);
      [x1, y1, x0, y0] = this.#last.subarray(14, 18);
      this.#bottom.push(NaN, NaN, NaN, NaN, ((x0 + x1) / 2 - layerX) / layerWidth, ((y0 + y1) / 2 - layerY) / layerHeight);
      return true;
    }
    [x0, y0, x1, y1, x2, y2] = this.#last.subarray(0, 6);
    this.#top.push(((x0 + 5 * x1) / 6 - layerX) / layerWidth, ((y0 + 5 * y1) / 6 - layerY) / layerHeight, ((5 * x1 + x2) / 6 - layerX) / layerWidth, ((5 * y1 + y2) / 6 - layerY) / layerHeight, ((x1 + x2) / 2 - layerX) / layerWidth, ((y1 + y2) / 2 - layerY) / layerHeight);
    [x2, y2, x1, y1, x0, y0] = this.#last.subarray(12, 18);
    this.#bottom.push(((x0 + 5 * x1) / 6 - layerX) / layerWidth, ((y0 + 5 * y1) / 6 - layerY) / layerHeight, ((5 * x1 + x2) / 6 - layerX) / layerWidth, ((5 * y1 + y2) / 6 - layerY) / layerHeight, ((x1 + x2) / 2 - layerX) / layerWidth, ((y1 + y2) / 2 - layerY) / layerHeight);
    return true;
  }
  toSVGPath() {
    if (this.isEmpty()) {
      return "";
    }
    const top = this.#top;
    const bottom = this.#bottom;
    const lastTop = this.#last.subarray(4, 6);
    const lastBottom = this.#last.subarray(16, 18);
    const [x, y, width, height] = this.#box;
    const [lastTopX, lastTopY, lastBottomX, lastBottomY] = this.#getLastCoords();
    if (isNaN(this.#last[6]) && !this.isEmpty()) {
      return `M${(this.#last[2] - x) / width} ${(this.#last[3] - y) / height} L${(this.#last[4] - x) / width} ${(this.#last[5] - y) / height} L${lastTopX} ${lastTopY} L${lastBottomX} ${lastBottomY} L${(this.#last[16] - x) / width} ${(this.#last[17] - y) / height} L${(this.#last[14] - x) / width} ${(this.#last[15] - y) / height} Z`;
    }
    const buffer = [];
    buffer.push(`M${top[4]} ${top[5]}`);
    for (let i = 6; i < top.length; i += 6) {
      if (isNaN(top[i])) {
        buffer.push(`L${top[i + 4]} ${top[i + 5]}`);
      } else {
        buffer.push(`C${top[i]} ${top[i + 1]} ${top[i + 2]} ${top[i + 3]} ${top[i + 4]} ${top[i + 5]}`);
      }
    }
    buffer.push(`L${(lastTop[0] - x) / width} ${(lastTop[1] - y) / height} L${lastTopX} ${lastTopY} L${lastBottomX} ${lastBottomY} L${(lastBottom[0] - x) / width} ${(lastBottom[1] - y) / height}`);
    for (let i = bottom.length - 6; i >= 6; i -= 6) {
      if (isNaN(bottom[i])) {
        buffer.push(`L${bottom[i + 4]} ${bottom[i + 5]}`);
      } else {
        buffer.push(`C${bottom[i]} ${bottom[i + 1]} ${bottom[i + 2]} ${bottom[i + 3]} ${bottom[i + 4]} ${bottom[i + 5]}`);
      }
    }
    buffer.push(`L${bottom[4]} ${bottom[5]} Z`);
    return buffer.join(" ");
  }
  getOutlines() {
    const top = this.#top;
    const bottom = this.#bottom;
    const last = this.#last;
    const lastTop = last.subarray(4, 6);
    const lastBottom = last.subarray(16, 18);
    const [layerX, layerY, layerWidth, layerHeight] = this.#box;
    const points = new Float64Array((this.#points?.length ?? 0) + 2);
    for (let i = 0, ii = points.length - 2; i < ii; i += 2) {
      points[i] = (this.#points[i] - layerX) / layerWidth;
      points[i + 1] = (this.#points[i + 1] - layerY) / layerHeight;
    }
    points[points.length - 2] = (this.#lastX - layerX) / layerWidth;
    points[points.length - 1] = (this.#lastY - layerY) / layerHeight;
    const [lastTopX, lastTopY, lastBottomX, lastBottomY] = this.#getLastCoords();
    if (isNaN(last[6]) && !this.isEmpty()) {
      const outline = new Float64Array(36);
      outline.set([NaN, NaN, NaN, NaN, (last[2] - layerX) / layerWidth, (last[3] - layerY) / layerHeight, NaN, NaN, NaN, NaN, (last[4] - layerX) / layerWidth, (last[5] - layerY) / layerHeight, NaN, NaN, NaN, NaN, lastTopX, lastTopY, NaN, NaN, NaN, NaN, lastBottomX, lastBottomY, NaN, NaN, NaN, NaN, (last[16] - layerX) / layerWidth, (last[17] - layerY) / layerHeight, NaN, NaN, NaN, NaN, (last[14] - layerX) / layerWidth, (last[15] - layerY) / layerHeight], 0);
      return new FreeHighlightOutline(outline, points, this.#box, this.#scaleFactor, this.#innerMargin, this.#isLTR);
    }
    const outline = new Float64Array(this.#top.length + 24 + this.#bottom.length);
    let N = top.length;
    for (let i = 0; i < N; i += 2) {
      if (isNaN(top[i])) {
        outline[i] = outline[i + 1] = NaN;
        continue;
      }
      outline[i] = top[i];
      outline[i + 1] = top[i + 1];
    }
    outline.set([NaN, NaN, NaN, NaN, (lastTop[0] - layerX) / layerWidth, (lastTop[1] - layerY) / layerHeight, NaN, NaN, NaN, NaN, lastTopX, lastTopY, NaN, NaN, NaN, NaN, lastBottomX, lastBottomY, NaN, NaN, NaN, NaN, (lastBottom[0] - layerX) / layerWidth, (lastBottom[1] - layerY) / layerHeight], N);
    N += 24;
    for (let i = bottom.length - 6; i >= 6; i -= 6) {
      for (let j = 0; j < 6; j += 2) {
        if (isNaN(bottom[i + j])) {
          outline[N] = outline[N + 1] = NaN;
          N += 2;
          continue;
        }
        outline[N] = bottom[i + j];
        outline[N + 1] = bottom[i + j + 1];
        N += 2;
      }
    }
    outline.set([NaN, NaN, NaN, NaN, bottom[4], bottom[5]], N);
    return new FreeHighlightOutline(outline, points, this.#box, this.#scaleFactor, this.#innerMargin, this.#isLTR);
  }
}
class FreeHighlightOutline extends Outline {
  #box;
  #bbox = null;
  #innerMargin;
  #isLTR;
  #points;
  #scaleFactor;
  #outline;
  constructor(outline, points, box, scaleFactor, innerMargin, isLTR) {
    super();
    this.#outline = outline;
    this.#points = points;
    this.#box = box;
    this.#scaleFactor = scaleFactor;
    this.#innerMargin = innerMargin;
    this.#isLTR = isLTR;
    this.#computeMinMax(isLTR);
    const {
      x,
      y,
      width,
      height
    } = this.#bbox;
    for (let i = 0, ii = outline.length; i < ii; i += 2) {
      outline[i] = (outline[i] - x) / width;
      outline[i + 1] = (outline[i + 1] - y) / height;
    }
    for (let i = 0, ii = points.length; i < ii; i += 2) {
      points[i] = (points[i] - x) / width;
      points[i + 1] = (points[i + 1] - y) / height;
    }
  }
  toSVGPath() {
    const buffer = [`M${this.#outline[4]} ${this.#outline[5]}`];
    for (let i = 6, ii = this.#outline.length; i < ii; i += 6) {
      if (isNaN(this.#outline[i])) {
        buffer.push(`L${this.#outline[i + 4]} ${this.#outline[i + 5]}`);
        continue;
      }
      buffer.push(`C${this.#outline[i]} ${this.#outline[i + 1]} ${this.#outline[i + 2]} ${this.#outline[i + 3]} ${this.#outline[i + 4]} ${this.#outline[i + 5]}`);
    }
    buffer.push("Z");
    return buffer.join(" ");
  }
  serialize([blX, blY, trX, trY], rotation) {
    const width = trX - blX;
    const height = trY - blY;
    let outline;
    let points;
    switch (rotation) {
      case 0:
        outline = this.#rescale(this.#outline, blX, trY, width, -height);
        points = this.#rescale(this.#points, blX, trY, width, -height);
        break;
      case 90:
        outline = this.#rescaleAndSwap(this.#outline, blX, blY, width, height);
        points = this.#rescaleAndSwap(this.#points, blX, blY, width, height);
        break;
      case 180:
        outline = this.#rescale(this.#outline, trX, blY, -width, height);
        points = this.#rescale(this.#points, trX, blY, -width, height);
        break;
      case 270:
        outline = this.#rescaleAndSwap(this.#outline, trX, trY, -width, -height);
        points = this.#rescaleAndSwap(this.#points, trX, trY, -width, -height);
        break;
    }
    return {
      outline: Array.from(outline),
      points: [Array.from(points)]
    };
  }
  #rescale(src, tx, ty, sx, sy) {
    const dest = new Float64Array(src.length);
    for (let i = 0, ii = src.length; i < ii; i += 2) {
      dest[i] = tx + src[i] * sx;
      dest[i + 1] = ty + src[i + 1] * sy;
    }
    return dest;
  }
  #rescaleAndSwap(src, tx, ty, sx, sy) {
    const dest = new Float64Array(src.length);
    for (let i = 0, ii = src.length; i < ii; i += 2) {
      dest[i] = tx + src[i + 1] * sx;
      dest[i + 1] = ty + src[i] * sy;
    }
    return dest;
  }
  #computeMinMax(isLTR) {
    const outline = this.#outline;
    let lastX = outline[4];
    let lastY = outline[5];
    let minX = lastX;
    let minY = lastY;
    let maxX = lastX;
    let maxY = lastY;
    let lastPointX = lastX;
    let lastPointY = lastY;
    const ltrCallback = isLTR ? Math.max : Math.min;
    for (let i = 6, ii = outline.length; i < ii; i += 6) {
      if (isNaN(outline[i])) {
        minX = Math.min(minX, outline[i + 4]);
        minY = Math.min(minY, outline[i + 5]);
        maxX = Math.max(maxX, outline[i + 4]);
        maxY = Math.max(maxY, outline[i + 5]);
        if (lastPointY < outline[i + 5]) {
          lastPointX = outline[i + 4];
          lastPointY = outline[i + 5];
        } else if (lastPointY === outline[i + 5]) {
          lastPointX = ltrCallback(lastPointX, outline[i + 4]);
        }
      } else {
        const bbox = Util.bezierBoundingBox(lastX, lastY, ...outline.slice(i, i + 6));
        minX = Math.min(minX, bbox[0]);
        minY = Math.min(minY, bbox[1]);
        maxX = Math.max(maxX, bbox[2]);
        maxY = Math.max(maxY, bbox[3]);
        if (lastPointY < bbox[3]) {
          lastPointX = bbox[2];
          lastPointY = bbox[3];
        } else if (lastPointY === bbox[3]) {
          lastPointX = ltrCallback(lastPointX, bbox[2]);
        }
      }
      lastX = outline[i + 4];
      lastY = outline[i + 5];
    }
    const x = minX - this.#innerMargin,
      y = minY - this.#innerMargin,
      width = maxX - minX + 2 * this.#innerMargin,
      height = maxY - minY + 2 * this.#innerMargin;
    this.#bbox = {
      x,
      y,
      width,
      height,
      lastPoint: [lastPointX, lastPointY]
    };
  }
  get box() {
    return this.#bbox;
  }
  getNewOutline(thickness, innerMargin) {
    const {
      x,
      y,
      width,
      height
    } = this.#bbox;
    const [layerX, layerY, layerWidth, layerHeight] = this.#box;
    const sx = width * layerWidth;
    const sy = height * layerHeight;
    const tx = x * layerWidth + layerX;
    const ty = y * layerHeight + layerY;
    const outliner = new FreeOutliner({
      x: this.#points[0] * sx + tx,
      y: this.#points[1] * sy + ty
    }, this.#box, this.#scaleFactor, thickness, this.#isLTR, innerMargin ?? this.#innerMargin);
    for (let i = 2; i < this.#points.length; i += 2) {
      outliner.add({
        x: this.#points[i] * sx + tx,
        y: this.#points[i + 1] * sy + ty
      });
    }
    return outliner.getOutlines();
  }
}

;// CONCATENATED MODULE: ./src/display/editor/color_picker.js



class ColorPicker {
  #boundKeyDown = this.#keyDown.bind(this);
  #boundPointerDown = this.#pointerDown.bind(this);
  #button = null;
  #buttonSwatch = null;
  #defaultColor;
  #dropdown = null;
  #dropdownWasFromKeyboard = false;
  #isMainColorPicker = false;
  #editor = null;
  #eventBus;
  #uiManager = null;
  #type;
  static get _keyboardManager() {
    return shadow(this, "_keyboardManager", new KeyboardManager([[["Escape", "mac+Escape"], ColorPicker.prototype._hideDropdownFromKeyboard], [[" ", "mac+ "], ColorPicker.prototype._colorSelectFromKeyboard], [["ArrowDown", "ArrowRight", "mac+ArrowDown", "mac+ArrowRight"], ColorPicker.prototype._moveToNext], [["ArrowUp", "ArrowLeft", "mac+ArrowUp", "mac+ArrowLeft"], ColorPicker.prototype._moveToPrevious], [["Home", "mac+Home"], ColorPicker.prototype._moveToBeginning], [["End", "mac+End"], ColorPicker.prototype._moveToEnd]]));
  }
  constructor({
    editor = null,
    uiManager = null
  }) {
    if (editor) {
      this.#isMainColorPicker = false;
      this.#type = AnnotationEditorParamsType.HIGHLIGHT_COLOR;
      this.#editor = editor;
    } else {
      this.#isMainColorPicker = true;
      this.#type = AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR;
    }
    this.#uiManager = editor?._uiManager || uiManager;
    this.#eventBus = this.#uiManager._eventBus;
    this.#defaultColor = editor?.color || this.#uiManager?.highlightColors.values().next().value || "#FFFF98";
  }
  renderButton() {
    const button = this.#button = document.createElement("button");
    button.className = "colorPicker";
    button.tabIndex = "0";
    button.setAttribute("data-l10n-id", "pdfjs-editor-colorpicker-button");
    button.setAttribute("aria-haspopup", true);
    const signal = this.#uiManager._signal;
    button.addEventListener("click", this.#openDropdown.bind(this), {
      signal
    });
    button.addEventListener("keydown", this.#boundKeyDown, {
      signal
    });
    const swatch = this.#buttonSwatch = document.createElement("span");
    swatch.className = "swatch";
    swatch.setAttribute("aria-hidden", true);
    swatch.style.backgroundColor = this.#defaultColor;
    button.append(swatch);
    return button;
  }
  renderMainDropdown() {
    const dropdown = this.#dropdown = this.#getDropdownRoot();
    dropdown.setAttribute("aria-orientation", "horizontal");
    dropdown.setAttribute("aria-labelledby", "highlightColorPickerLabel");
    return dropdown;
  }
  #getDropdownRoot() {
    const div = document.createElement("div");
    const signal = this.#uiManager._signal;
    div.addEventListener("contextmenu", noContextMenu, {
      signal
    });
    div.className = "dropdown";
    div.role = "listbox";
    div.setAttribute("aria-multiselectable", false);
    div.setAttribute("aria-orientation", "vertical");
    div.setAttribute("data-l10n-id", "pdfjs-editor-colorpicker-dropdown");
    for (const [name, color] of this.#uiManager.highlightColors) {
      const button = document.createElement("button");
      button.tabIndex = "0";
      button.role = "option";
      button.setAttribute("data-color", color);
      button.title = name;
      button.setAttribute("data-l10n-id", `pdfjs-editor-colorpicker-${name}`);
      const swatch = document.createElement("span");
      button.append(swatch);
      swatch.className = "swatch";
      swatch.style.backgroundColor = color;
      button.setAttribute("aria-selected", color === this.#defaultColor);
      button.addEventListener("click", this.#colorSelect.bind(this, color), {
        signal
      });
      div.append(button);
    }
    div.addEventListener("keydown", this.#boundKeyDown, {
      signal
    });
    return div;
  }
  #colorSelect(color, event) {
    event.stopPropagation();
    this.#eventBus.dispatch("switchannotationeditorparams", {
      source: this,
      type: this.#type,
      value: color
    });
  }
  _colorSelectFromKeyboard(event) {
    if (event.target === this.#button) {
      this.#openDropdown(event);
      return;
    }
    const color = event.target.getAttribute("data-color");
    if (!color) {
      return;
    }
    this.#colorSelect(color, event);
  }
  _moveToNext(event) {
    if (!this.#isDropdownVisible) {
      this.#openDropdown(event);
      return;
    }
    if (event.target === this.#button) {
      this.#dropdown.firstChild?.focus();
      return;
    }
    event.target.nextSibling?.focus();
  }
  _moveToPrevious(event) {
    if (event.target === this.#dropdown?.firstChild || event.target === this.#button) {
      if (this.#isDropdownVisible) {
        this._hideDropdownFromKeyboard();
      }
      return;
    }
    if (!this.#isDropdownVisible) {
      this.#openDropdown(event);
    }
    event.target.previousSibling?.focus();
  }
  _moveToBeginning(event) {
    if (!this.#isDropdownVisible) {
      this.#openDropdown(event);
      return;
    }
    this.#dropdown.firstChild?.focus();
  }
  _moveToEnd(event) {
    if (!this.#isDropdownVisible) {
      this.#openDropdown(event);
      return;
    }
    this.#dropdown.lastChild?.focus();
  }
  #keyDown(event) {
    ColorPicker._keyboardManager.exec(this, event);
  }
  #openDropdown(event) {
    if (this.#isDropdownVisible) {
      this.hideDropdown();
      return;
    }
    this.#dropdownWasFromKeyboard = event.detail === 0;
    window.addEventListener("pointerdown", this.#boundPointerDown, {
      signal: this.#uiManager._signal
    });
    if (this.#dropdown) {
      this.#dropdown.classList.remove("hidden");
      return;
    }
    const root = this.#dropdown = this.#getDropdownRoot();
    this.#button.append(root);
  }
  #pointerDown(event) {
    if (this.#dropdown?.contains(event.target)) {
      return;
    }
    this.hideDropdown();
  }
  hideDropdown() {
    this.#dropdown?.classList.add("hidden");
    window.removeEventListener("pointerdown", this.#boundPointerDown);
  }
  get #isDropdownVisible() {
    return this.#dropdown && !this.#dropdown.classList.contains("hidden");
  }
  _hideDropdownFromKeyboard() {
    if (this.#isMainColorPicker) {
      return;
    }
    if (!this.#isDropdownVisible) {
      this.#editor?.unselect();
      return;
    }
    this.hideDropdown();
    this.#button.focus({
      preventScroll: true,
      focusVisible: this.#dropdownWasFromKeyboard
    });
  }
  updateColor(color) {
    if (this.#buttonSwatch) {
      this.#buttonSwatch.style.backgroundColor = color;
    }
    if (!this.#dropdown) {
      return;
    }
    const i = this.#uiManager.highlightColors.values();
    for (const child of this.#dropdown.children) {
      child.setAttribute("aria-selected", i.next().value === color);
    }
  }
  destroy() {
    this.#button?.remove();
    this.#button = null;
    this.#buttonSwatch = null;
    this.#dropdown?.remove();
    this.#dropdown = null;
  }
}

;// CONCATENATED MODULE: ./src/display/editor/highlight.js






class HighlightEditor extends AnnotationEditor {
  #anchorNode = null;
  #anchorOffset = 0;
  #boxes;
  #clipPathId = null;
  #colorPicker = null;
  #focusOutlines = null;
  #focusNode = null;
  #focusOffset = 0;
  #highlightDiv = null;
  #highlightOutlines = null;
  #id = null;
  #isFreeHighlight = false;
  #lastPoint = null;
  #opacity;
  #outlineId = null;
  #text = "";
  #thickness;
  #methodOfCreation = "";
  static _defaultColor = null;
  static _defaultOpacity = 1;
  static _defaultThickness = 12;
  static _l10nPromise;
  static _type = "highlight";
  static _editorType = AnnotationEditorType.HIGHLIGHT;
  static _freeHighlightId = -1;
  static _freeHighlight = null;
  static _freeHighlightClipId = "";
  static get _keyboardManager() {
    const proto = HighlightEditor.prototype;
    return shadow(this, "_keyboardManager", new KeyboardManager([[["ArrowLeft", "mac+ArrowLeft"], proto._moveCaret, {
      args: [0]
    }], [["ArrowRight", "mac+ArrowRight"], proto._moveCaret, {
      args: [1]
    }], [["ArrowUp", "mac+ArrowUp"], proto._moveCaret, {
      args: [2]
    }], [["ArrowDown", "mac+ArrowDown"], proto._moveCaret, {
      args: [3]
    }]]));
  }
  constructor(params) {
    super({
      ...params,
      name: "highlightEditor"
    });
    this.color = params.color || HighlightEditor._defaultColor;
    this.#thickness = params.thickness || HighlightEditor._defaultThickness;
    this.#opacity = params.opacity || HighlightEditor._defaultOpacity;
    this.#boxes = params.boxes || null;
    this.#methodOfCreation = params.methodOfCreation || "";
    this.#text = params.text || "";
    this._isDraggable = false;
    if (params.highlightId > -1) {
      this.#isFreeHighlight = true;
      this.#createFreeOutlines(params);
      this.#addToDrawLayer();
    } else {
      this.#anchorNode = params.anchorNode;
      this.#anchorOffset = params.anchorOffset;
      this.#focusNode = params.focusNode;
      this.#focusOffset = params.focusOffset;
      this.#createOutlines();
      this.#addToDrawLayer();
      this.rotate(this.rotation);
    }
  }
  get telemetryInitialData() {
    return {
      action: "added",
      type: this.#isFreeHighlight ? "free_highlight" : "highlight",
      color: this._uiManager.highlightColorNames.get(this.color),
      thickness: this.#thickness,
      methodOfCreation: this.#methodOfCreation
    };
  }
  get telemetryFinalData() {
    return {
      type: "highlight",
      color: this._uiManager.highlightColorNames.get(this.color)
    };
  }
  static computeTelemetryFinalData(data) {
    return {
      numberOfColors: data.get("color").size
    };
  }
  #createOutlines() {
    const outliner = new Outliner(this.#boxes, 0.001);
    this.#highlightOutlines = outliner.getOutlines();
    ({
      x: this.x,
      y: this.y,
      width: this.width,
      height: this.height
    } = this.#highlightOutlines.box);
    const outlinerForOutline = new Outliner(this.#boxes, 0.0025, 0.001, this._uiManager.direction === "ltr");
    this.#focusOutlines = outlinerForOutline.getOutlines();
    const {
      lastPoint
    } = this.#focusOutlines.box;
    this.#lastPoint = [(lastPoint[0] - this.x) / this.width, (lastPoint[1] - this.y) / this.height];
  }
  #createFreeOutlines({
    highlightOutlines,
    highlightId,
    clipPathId
  }) {
    this.#highlightOutlines = highlightOutlines;
    const extraThickness = 1.5;
    this.#focusOutlines = highlightOutlines.getNewOutline(this.#thickness / 2 + extraThickness, 0.0025);
    if (highlightId >= 0) {
      this.#id = highlightId;
      this.#clipPathId = clipPathId;
      this.parent.drawLayer.finalizeLine(highlightId, highlightOutlines);
      this.#outlineId = this.parent.drawLayer.highlightOutline(this.#focusOutlines);
    } else if (this.parent) {
      const angle = this.parent.viewport.rotation;
      this.parent.drawLayer.updateLine(this.#id, highlightOutlines);
      this.parent.drawLayer.updateBox(this.#id, HighlightEditor.#rotateBbox(this.#highlightOutlines.box, (angle - this.rotation + 360) % 360));
      this.parent.drawLayer.updateLine(this.#outlineId, this.#focusOutlines);
      this.parent.drawLayer.updateBox(this.#outlineId, HighlightEditor.#rotateBbox(this.#focusOutlines.box, angle));
    }
    const {
      x,
      y,
      width,
      height
    } = highlightOutlines.box;
    switch (this.rotation) {
      case 0:
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
        break;
      case 90:
        {
          const [pageWidth, pageHeight] = this.parentDimensions;
          this.x = y;
          this.y = 1 - x;
          this.width = width * pageHeight / pageWidth;
          this.height = height * pageWidth / pageHeight;
          break;
        }
      case 180:
        this.x = 1 - x;
        this.y = 1 - y;
        this.width = width;
        this.height = height;
        break;
      case 270:
        {
          const [pageWidth, pageHeight] = this.parentDimensions;
          this.x = 1 - y;
          this.y = x;
          this.width = width * pageHeight / pageWidth;
          this.height = height * pageWidth / pageHeight;
          break;
        }
    }
    const {
      lastPoint
    } = this.#focusOutlines.box;
    this.#lastPoint = [(lastPoint[0] - x) / width, (lastPoint[1] - y) / height];
  }
  static initialize(l10n, uiManager) {
    AnnotationEditor.initialize(l10n, uiManager);
    HighlightEditor._defaultColor ||= uiManager.highlightColors?.values().next().value || "#fff066";
  }
  static updateDefaultParams(type, value) {
    switch (type) {
      case AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR:
        HighlightEditor._defaultColor = value;
        break;
      case AnnotationEditorParamsType.HIGHLIGHT_THICKNESS:
        HighlightEditor._defaultThickness = value;
        break;
    }
  }
  translateInPage(x, y) {}
  get toolbarPosition() {
    return this.#lastPoint;
  }
  updateParams(type, value) {
    switch (type) {
      case AnnotationEditorParamsType.HIGHLIGHT_COLOR:
        this.#updateColor(value);
        break;
      case AnnotationEditorParamsType.HIGHLIGHT_THICKNESS:
        this.#updateThickness(value);
        break;
    }
  }
  static get defaultPropertiesToUpdate() {
    return [[AnnotationEditorParamsType.HIGHLIGHT_DEFAULT_COLOR, HighlightEditor._defaultColor], [AnnotationEditorParamsType.HIGHLIGHT_THICKNESS, HighlightEditor._defaultThickness]];
  }
  get propertiesToUpdate() {
    return [[AnnotationEditorParamsType.HIGHLIGHT_COLOR, this.color || HighlightEditor._defaultColor], [AnnotationEditorParamsType.HIGHLIGHT_THICKNESS, this.#thickness || HighlightEditor._defaultThickness], [AnnotationEditorParamsType.HIGHLIGHT_FREE, this.#isFreeHighlight]];
  }
  #updateColor(color) {
    const setColor = col => {
      this.color = col;
      this.parent?.drawLayer.changeColor(this.#id, col);
      this.#colorPicker?.updateColor(col);
    };
    const savedColor = this.color;
    this.addCommands({
      cmd: setColor.bind(this, color),
      undo: setColor.bind(this, savedColor),
      post: this._uiManager.updateUI.bind(this._uiManager, this),
      mustExec: true,
      type: AnnotationEditorParamsType.HIGHLIGHT_COLOR,
      overwriteIfSameType: true,
      keepUndo: true
    });
    this._reportTelemetry({
      action: "color_changed",
      color: this._uiManager.highlightColorNames.get(color)
    }, true);
  }
  #updateThickness(thickness) {
    const savedThickness = this.#thickness;
    const setThickness = th => {
      this.#thickness = th;
      this.#changeThickness(th);
    };
    this.addCommands({
      cmd: setThickness.bind(this, thickness),
      undo: setThickness.bind(this, savedThickness),
      post: this._uiManager.updateUI.bind(this._uiManager, this),
      mustExec: true,
      type: AnnotationEditorParamsType.INK_THICKNESS,
      overwriteIfSameType: true,
      keepUndo: true
    });
    this._reportTelemetry({
      action: "thickness_changed",
      thickness
    }, true);
  }
  async addEditToolbar() {
    const toolbar = await super.addEditToolbar();
    if (!toolbar) {
      return null;
    }
    if (this._uiManager.highlightColors) {
      this.#colorPicker = new ColorPicker({
        editor: this
      });
      toolbar.addColorPicker(this.#colorPicker);
    }
    return toolbar;
  }
  disableEditing() {
    super.disableEditing();
    this.div.classList.toggle("disabled", true);
  }
  enableEditing() {
    super.enableEditing();
    this.div.classList.toggle("disabled", false);
  }
  fixAndSetPosition() {
    return super.fixAndSetPosition(this.#getRotation());
  }
  getBaseTranslation() {
    return [0, 0];
  }
  getRect(tx, ty) {
    return super.getRect(tx, ty, this.#getRotation());
  }
  onceAdded() {
    this.parent.addUndoableEditor(this);
    this.div.focus();
  }
  remove() {
    this.#cleanDrawLayer();
    this._reportTelemetry({
      action: "deleted"
    });
    super.remove();
  }
  rebuild() {
    if (!this.parent) {
      return;
    }
    super.rebuild();
    if (this.div === null) {
      return;
    }
    this.#addToDrawLayer();
    if (!this.isAttachedToDOM) {
      this.parent.add(this);
    }
  }
  setParent(parent) {
    let mustBeSelected = false;
    if (this.parent && !parent) {
      this.#cleanDrawLayer();
    } else if (parent) {
      this.#addToDrawLayer(parent);
      mustBeSelected = !this.parent && this.div?.classList.contains("selectedEditor");
    }
    super.setParent(parent);
    this.show(this._isVisible);
    if (mustBeSelected) {
      this.select();
    }
  }
  #changeThickness(thickness) {
    if (!this.#isFreeHighlight) {
      return;
    }
    this.#createFreeOutlines({
      highlightOutlines: this.#highlightOutlines.getNewOutline(thickness / 2)
    });
    this.fixAndSetPosition();
    const [parentWidth, parentHeight] = this.parentDimensions;
    this.setDims(this.width * parentWidth, this.height * parentHeight);
  }
  #cleanDrawLayer() {
    if (this.#id === null || !this.parent) {
      return;
    }
    this.parent.drawLayer.remove(this.#id);
    this.#id = null;
    this.parent.drawLayer.remove(this.#outlineId);
    this.#outlineId = null;
  }
  #addToDrawLayer(parent = this.parent) {
    if (this.#id !== null) {
      return;
    }
    ({
      id: this.#id,
      clipPathId: this.#clipPathId
    } = parent.drawLayer.highlight(this.#highlightOutlines, this.color, this.#opacity));
    this.#outlineId = parent.drawLayer.highlightOutline(this.#focusOutlines);
    if (this.#highlightDiv) {
      this.#highlightDiv.style.clipPath = this.#clipPathId;
    }
  }
  static #rotateBbox({
    x,
    y,
    width,
    height
  }, angle) {
    switch (angle) {
      case 90:
        return {
          x: 1 - y - height,
          y: x,
          width: height,
          height: width
        };
      case 180:
        return {
          x: 1 - x - width,
          y: 1 - y - height,
          width,
          height
        };
      case 270:
        return {
          x: y,
          y: 1 - x - width,
          width: height,
          height: width
        };
    }
    return {
      x,
      y,
      width,
      height
    };
  }
  rotate(angle) {
    const {
      drawLayer
    } = this.parent;
    let box;
    if (this.#isFreeHighlight) {
      angle = (angle - this.rotation + 360) % 360;
      box = HighlightEditor.#rotateBbox(this.#highlightOutlines.box, angle);
    } else {
      box = HighlightEditor.#rotateBbox(this, angle);
    }
    drawLayer.rotate(this.#id, angle);
    drawLayer.rotate(this.#outlineId, angle);
    drawLayer.updateBox(this.#id, box);
    drawLayer.updateBox(this.#outlineId, HighlightEditor.#rotateBbox(this.#focusOutlines.box, angle));
  }
  render() {
    if (this.div) {
      return this.div;
    }
    const div = super.render();
    if (this.#text) {
      div.setAttribute("aria-label", this.#text);
      div.setAttribute("role", "mark");
    }
    if (this.#isFreeHighlight) {
      div.classList.add("free");
    } else {
      this.div.addEventListener("keydown", this.#keydown.bind(this), {
        signal: this._uiManager._signal
      });
    }
    const highlightDiv = this.#highlightDiv = document.createElement("div");
    div.append(highlightDiv);
    highlightDiv.setAttribute("aria-hidden", "true");
    highlightDiv.className = "internal";
    highlightDiv.style.clipPath = this.#clipPathId;
    const [parentWidth, parentHeight] = this.parentDimensions;
    this.setDims(this.width * parentWidth, this.height * parentHeight);
    bindEvents(this, this.#highlightDiv, ["pointerover", "pointerleave"]);
    this.enableEditing();
    return div;
  }
  pointerover() {
    this.parent.drawLayer.addClass(this.#outlineId, "hovered");
  }
  pointerleave() {
    this.parent.drawLayer.removeClass(this.#outlineId, "hovered");
  }
  #keydown(event) {
    HighlightEditor._keyboardManager.exec(this, event);
  }
  _moveCaret(direction) {
    this.parent.unselect(this);
    switch (direction) {
      case 0:
      case 2:
        this.#setCaret(true);
        break;
      case 1:
      case 3:
        this.#setCaret(false);
        break;
    }
  }
  #setCaret(start) {
    if (!this.#anchorNode) {
      return;
    }
    const selection = window.getSelection();
    if (start) {
      selection.setPosition(this.#anchorNode, this.#anchorOffset);
    } else {
      selection.setPosition(this.#focusNode, this.#focusOffset);
    }
  }
  select() {
    super.select();
    if (!this.#outlineId) {
      return;
    }
    this.parent?.drawLayer.removeClass(this.#outlineId, "hovered");
    this.parent?.drawLayer.addClass(this.#outlineId, "selected");
  }
  unselect() {
    super.unselect();
    if (!this.#outlineId) {
      return;
    }
    this.parent?.drawLayer.removeClass(this.#outlineId, "selected");
    if (!this.#isFreeHighlight) {
      this.#setCaret(false);
    }
  }
  get _mustFixPosition() {
    return !this.#isFreeHighlight;
  }
  show(visible = this._isVisible) {
    super.show(visible);
    if (this.parent) {
      this.parent.drawLayer.show(this.#id, visible);
      this.parent.drawLayer.show(this.#outlineId, visible);
    }
  }
  #getRotation() {
    return this.#isFreeHighlight ? this.rotation : 0;
  }
  #serializeBoxes() {
    if (this.#isFreeHighlight) {
      return null;
    }
    const [pageWidth, pageHeight] = this.pageDimensions;
    const [pageX, pageY] = this.pageTranslation;
    const boxes = this.#boxes;
    const quadPoints = new Float32Array(boxes.length * 8);
    let i = 0;
    for (const {
      x,
      y,
      width,
      height
    } of boxes) {
      const sx = x * pageWidth + pageX;
      const sy = (1 - y - height) * pageHeight + pageY;
      quadPoints[i] = quadPoints[i + 4] = sx;
      quadPoints[i + 1] = quadPoints[i + 3] = sy;
      quadPoints[i + 2] = quadPoints[i + 6] = sx + width * pageWidth;
      quadPoints[i + 5] = quadPoints[i + 7] = sy + height * pageHeight;
      i += 8;
    }
    return quadPoints;
  }
  #serializeOutlines(rect) {
    return this.#highlightOutlines.serialize(rect, this.#getRotation());
  }
  static startHighlighting(parent, isLTR, {
    target: textLayer,
    x,
    y
  }) {
    const {
      x: layerX,
      y: layerY,
      width: parentWidth,
      height: parentHeight
    } = textLayer.getBoundingClientRect();
    const ac = new AbortController();
    const signal = parent.combinedSignal(ac);
    const pointerDown = e => {
      e.preventDefault();
      e.stopPropagation();
    };
    const pointerUpCallback = e => {
      ac.abort();
      this.#endHighlight(parent, e);
    };
    window.addEventListener("blur", pointerUpCallback, {
      signal
    });
    window.addEventListener("pointerup", pointerUpCallback, {
      signal
    });
    window.addEventListener("pointerdown", pointerDown, {
      capture: true,
      passive: false,
      signal
    });
    window.addEventListener("contextmenu", noContextMenu, {
      signal
    });
    textLayer.addEventListener("pointermove", this.#highlightMove.bind(this, parent), {
      signal
    });
    this._freeHighlight = new FreeOutliner({
      x,
      y
    }, [layerX, layerY, parentWidth, parentHeight], parent.scale, this._defaultThickness / 2, isLTR, 0.001);
    ({
      id: this._freeHighlightId,
      clipPathId: this._freeHighlightClipId
    } = parent.drawLayer.highlight(this._freeHighlight, this._defaultColor, this._defaultOpacity, true));
  }
  static #highlightMove(parent, event) {
    if (this._freeHighlight.add(event)) {
      parent.drawLayer.updatePath(this._freeHighlightId, this._freeHighlight);
    }
  }
  static #endHighlight(parent, event) {
    if (!this._freeHighlight.isEmpty()) {
      parent.createAndAddNewEditor(event, false, {
        highlightId: this._freeHighlightId,
        highlightOutlines: this._freeHighlight.getOutlines(),
        clipPathId: this._freeHighlightClipId,
        methodOfCreation: "main_toolbar"
      });
    } else {
      parent.drawLayer.removeFreeHighlight(this._freeHighlightId);
    }
    this._freeHighlightId = -1;
    this._freeHighlight = null;
    this._freeHighlightClipId = "";
  }
  static deserialize(data, parent, uiManager) {
    const editor = super.deserialize(data, parent, uiManager);
    const {
      rect: [blX, blY, trX, trY],
      color,
      quadPoints
    } = data;
    editor.color = Util.makeHexColor(...color);
    editor.#opacity = data.opacity;
    const [pageWidth, pageHeight] = editor.pageDimensions;
    editor.width = (trX - blX) / pageWidth;
    editor.height = (trY - blY) / pageHeight;
    const boxes = editor.#boxes = [];
    for (let i = 0; i < quadPoints.length; i += 8) {
      boxes.push({
        x: (quadPoints[4] - trX) / pageWidth,
        y: (trY - (1 - quadPoints[i + 5])) / pageHeight,
        width: (quadPoints[i + 2] - quadPoints[i]) / pageWidth,
        height: (quadPoints[i + 5] - quadPoints[i + 1]) / pageHeight
      });
    }
    editor.#createOutlines();
    return editor;
  }
  serialize(isForCopying = false) {
    if (this.isEmpty() || isForCopying) {
      return null;
    }
    const rect = this.getRect(0, 0);
    const color = AnnotationEditor._colorManager.convert(this.color);
    return {
      annotationType: AnnotationEditorType.HIGHLIGHT,
      color,
      opacity: this.#opacity,
      thickness: this.#thickness,
      quadPoints: this.#serializeBoxes(),
      outlines: this.#serializeOutlines(rect),
      pageIndex: this.pageIndex,
      rect,
      rotation: this.#getRotation(),
      structTreeParentId: this._structTreeParentId
    };
  }
  static canCreateNewEmptyEditor() {
    return false;
  }
}

;// CONCATENATED MODULE: ./src/display/editor/ink.js





class InkEditor extends AnnotationEditor {
  #baseHeight = 0;
  #baseWidth = 0;
  #canvasContextMenuTimeoutId = null;
  #currentPath2D = new Path2D();
  #disableEditing = false;
  #drawingAC = null;
  #hasSomethingToDraw = false;
  #isCanvasInitialized = false;
  #observer = null;
  #pointerdownAC = null;
  #realWidth = 0;
  #realHeight = 0;
  #requestFrameCallback = null;
  static _defaultColor = null;
  static _defaultOpacity = 1;
  static _defaultThickness = 1;
  static _type = "ink";
  static _editorType = AnnotationEditorType.INK;
  constructor(params) {
    super({
      ...params,
      name: "inkEditor"
    });
    this.color = params.color || null;
    this.thickness = params.thickness || null;
    this.opacity = params.opacity || null;
    this.paths = [];
    this.bezierPath2D = [];
    this.allRawPaths = [];
    this.currentPath = [];
    this.scaleFactor = 1;
    this.translationX = this.translationY = 0;
    this.x = 0;
    this.y = 0;
    this._willKeepAspectRatio = true;
  }
  static initialize(l10n, uiManager) {
    AnnotationEditor.initialize(l10n, uiManager);
  }
  static updateDefaultParams(type, value) {
    switch (type) {
      case AnnotationEditorParamsType.INK_THICKNESS:
        InkEditor._defaultThickness = value;
        break;
      case AnnotationEditorParamsType.INK_COLOR:
        InkEditor._defaultColor = value;
        break;
      case AnnotationEditorParamsType.INK_OPACITY:
        InkEditor._defaultOpacity = value / 100;
        break;
    }
  }
  updateParams(type, value) {
    switch (type) {
      case AnnotationEditorParamsType.INK_THICKNESS:
        this.#updateThickness(value);
        break;
      case AnnotationEditorParamsType.INK_COLOR:
        this.#updateColor(value);
        break;
      case AnnotationEditorParamsType.INK_OPACITY:
        this.#updateOpacity(value);
        break;
    }
  }
  static get defaultPropertiesToUpdate() {
    return [[AnnotationEditorParamsType.INK_THICKNESS, InkEditor._defaultThickness], [AnnotationEditorParamsType.INK_COLOR, InkEditor._defaultColor || AnnotationEditor._defaultLineColor], [AnnotationEditorParamsType.INK_OPACITY, Math.round(InkEditor._defaultOpacity * 100)]];
  }
  get propertiesToUpdate() {
    return [[AnnotationEditorParamsType.INK_THICKNESS, this.thickness || InkEditor._defaultThickness], [AnnotationEditorParamsType.INK_COLOR, this.color || InkEditor._defaultColor || AnnotationEditor._defaultLineColor], [AnnotationEditorParamsType.INK_OPACITY, Math.round(100 * (this.opacity ?? InkEditor._defaultOpacity))]];
  }
  #updateThickness(thickness) {
    const setThickness = th => {
      this.thickness = th;
      this.#fitToContent();
    };
    const savedThickness = this.thickness;
    this.addCommands({
      cmd: setThickness.bind(this, thickness),
      undo: setThickness.bind(this, savedThickness),
      post: this._uiManager.updateUI.bind(this._uiManager, this),
      mustExec: true,
      type: AnnotationEditorParamsType.INK_THICKNESS,
      overwriteIfSameType: true,
      keepUndo: true
    });
  }
  #updateColor(color) {
    const setColor = col => {
      this.color = col;
      this.#redraw();
    };
    const savedColor = this.color;
    this.addCommands({
      cmd: setColor.bind(this, color),
      undo: setColor.bind(this, savedColor),
      post: this._uiManager.updateUI.bind(this._uiManager, this),
      mustExec: true,
      type: AnnotationEditorParamsType.INK_COLOR,
      overwriteIfSameType: true,
      keepUndo: true
    });
  }
  #updateOpacity(opacity) {
    const setOpacity = op => {
      this.opacity = op;
      this.#redraw();
    };
    opacity /= 100;
    const savedOpacity = this.opacity;
    this.addCommands({
      cmd: setOpacity.bind(this, opacity),
      undo: setOpacity.bind(this, savedOpacity),
      post: this._uiManager.updateUI.bind(this._uiManager, this),
      mustExec: true,
      type: AnnotationEditorParamsType.INK_OPACITY,
      overwriteIfSameType: true,
      keepUndo: true
    });
  }
  rebuild() {
    if (!this.parent) {
      return;
    }
    super.rebuild();
    if (this.div === null) {
      return;
    }
    if (!this.canvas) {
      this.#createCanvas();
      this.#createObserver();
    }
    if (!this.isAttachedToDOM) {
      this.parent.add(this);
      this.#setCanvasDims();
    }
    this.#fitToContent();
  }
  remove() {
    if (this.canvas === null) {
      return;
    }
    if (!this.isEmpty()) {
      this.commit();
    }
    this.canvas.width = this.canvas.height = 0;
    this.canvas.remove();
    this.canvas = null;
    if (this.#canvasContextMenuTimeoutId) {
      clearTimeout(this.#canvasContextMenuTimeoutId);
      this.#canvasContextMenuTimeoutId = null;
    }
    this.#observer?.disconnect();
    this.#observer = null;
    super.remove();
  }
  setParent(parent) {
    if (!this.parent && parent) {
      this._uiManager.removeShouldRescale(this);
    } else if (this.parent && parent === null) {
      this._uiManager.addShouldRescale(this);
    }
    super.setParent(parent);
  }
  onScaleChanging() {
    const [parentWidth, parentHeight] = this.parentDimensions;
    const width = this.width * parentWidth;
    const height = this.height * parentHeight;
    this.setDimensions(width, height);
  }
  enableEditMode() {
    if (this.#disableEditing || this.canvas === null) {
      return;
    }
    super.enableEditMode();
    this._isDraggable = false;
    this.#addPointerdownListener();
  }
  disableEditMode() {
    if (!this.isInEditMode() || this.canvas === null) {
      return;
    }
    super.disableEditMode();
    this._isDraggable = !this.isEmpty();
    this.div.classList.remove("editing");
    this.#removePointerdownListener();
  }
  onceAdded() {
    this._isDraggable = !this.isEmpty();
  }
  isEmpty() {
    return this.paths.length === 0 || this.paths.length === 1 && this.paths[0].length === 0;
  }
  #getInitialBBox() {
    const {
      parentRotation,
      parentDimensions: [width, height]
    } = this;
    switch (parentRotation) {
      case 90:
        return [0, height, height, width];
      case 180:
        return [width, height, width, height];
      case 270:
        return [width, 0, height, width];
      default:
        return [0, 0, width, height];
    }
  }
  #setStroke() {
    const {
      ctx,
      color,
      opacity,
      thickness,
      parentScale,
      scaleFactor
    } = this;
    ctx.lineWidth = thickness * parentScale / scaleFactor;
    ctx.lineCap = "round";
    ctx.lineJoin = "round";
    ctx.miterLimit = 10;
    ctx.strokeStyle = `${color}${opacityToHex(opacity)}`;
  }
  #startDrawing(x, y) {
    this.canvas.addEventListener("contextmenu", noContextMenu, {
      signal: this._uiManager._signal
    });
    this.#removePointerdownListener();
    this.#drawingAC = new AbortController();
    const signal = this._uiManager.combinedSignal(this.#drawingAC);
    this.canvas.addEventListener("pointerleave", this.canvasPointerleave.bind(this), {
      signal
    });
    this.canvas.addEventListener("pointermove", this.canvasPointermove.bind(this), {
      signal
    });
    this.canvas.addEventListener("pointerup", this.canvasPointerup.bind(this), {
      signal
    });
    this.isEditing = true;
    if (!this.#isCanvasInitialized) {
      this.#isCanvasInitialized = true;
      this.#setCanvasDims();
      this.thickness ||= InkEditor._defaultThickness;
      this.color ||= InkEditor._defaultColor || AnnotationEditor._defaultLineColor;
      this.opacity ??= InkEditor._defaultOpacity;
    }
    this.currentPath.push([x, y]);
    this.#hasSomethingToDraw = false;
    this.#setStroke();
    this.#requestFrameCallback = () => {
      this.#drawPoints();
      if (this.#requestFrameCallback) {
        window.requestAnimationFrame(this.#requestFrameCallback);
      }
    };
    window.requestAnimationFrame(this.#requestFrameCallback);
  }
  #draw(x, y) {
    const [lastX, lastY] = this.currentPath.at(-1);
    if (this.currentPath.length > 1 && x === lastX && y === lastY) {
      return;
    }
    const currentPath = this.currentPath;
    let path2D = this.#currentPath2D;
    currentPath.push([x, y]);
    this.#hasSomethingToDraw = true;
    if (currentPath.length <= 2) {
      path2D.moveTo(...currentPath[0]);
      path2D.lineTo(x, y);
      return;
    }
    if (currentPath.length === 3) {
      this.#currentPath2D = path2D = new Path2D();
      path2D.moveTo(...currentPath[0]);
    }
    this.#makeBezierCurve(path2D, ...currentPath.at(-3), ...currentPath.at(-2), x, y);
  }
  #endPath() {
    if (this.currentPath.length === 0) {
      return;
    }
    const lastPoint = this.currentPath.at(-1);
    this.#currentPath2D.lineTo(...lastPoint);
  }
  #stopDrawing(x, y) {
    this.#requestFrameCallback = null;
    x = Math.min(Math.max(x, 0), this.canvas.width);
    y = Math.min(Math.max(y, 0), this.canvas.height);
    this.#draw(x, y);
    this.#endPath();
    let bezier;
    if (this.currentPath.length !== 1) {
      bezier = this.#generateBezierPoints();
    } else {
      const xy = [x, y];
      bezier = [[xy, xy.slice(), xy.slice(), xy]];
    }
    const path2D = this.#currentPath2D;
    const currentPath = this.currentPath;
    this.currentPath = [];
    this.#currentPath2D = new Path2D();
    const cmd = () => {
      this.allRawPaths.push(currentPath);
      this.paths.push(bezier);
      this.bezierPath2D.push(path2D);
      this._uiManager.rebuild(this);
    };
    const undo = () => {
      this.allRawPaths.pop();
      this.paths.pop();
      this.bezierPath2D.pop();
      if (this.paths.length === 0) {
        this.remove();
      } else {
        if (!this.canvas) {
          this.#createCanvas();
          this.#createObserver();
        }
        this.#fitToContent();
      }
    };
    this.addCommands({
      cmd,
      undo,
      mustExec: true
    });
  }
  #drawPoints() {
    if (!this.#hasSomethingToDraw) {
      return;
    }
    this.#hasSomethingToDraw = false;
    const thickness = Math.ceil(this.thickness * this.parentScale);
    const lastPoints = this.currentPath.slice(-3);
    const x = lastPoints.map(xy => xy[0]);
    const y = lastPoints.map(xy => xy[1]);
    const xMin = Math.min(...x) - thickness;
    const xMax = Math.max(...x) + thickness;
    const yMin = Math.min(...y) - thickness;
    const yMax = Math.max(...y) + thickness;
    const {
      ctx
    } = this;
    ctx.save();
    ctx.clearRect(xMin, yMin, xMax - xMin, yMax - yMin);
    ctx.beginPath();
    ctx.rect(xMin, yMin, xMax - xMin, yMax - yMin);
    ctx.clip();
    for (const path of this.bezierPath2D) {
      ctx.stroke(path);
    }
    ctx.stroke(this.#currentPath2D);
    ctx.restore();
  }
  #makeBezierCurve(path2D, x0, y0, x1, y1, x2, y2) {
    const prevX = (x0 + x1) / 2;
    const prevY = (y0 + y1) / 2;
    const x3 = (x1 + x2) / 2;
    const y3 = (y1 + y2) / 2;
    path2D.bezierCurveTo(prevX + 2 * (x1 - prevX) / 3, prevY + 2 * (y1 - prevY) / 3, x3 + 2 * (x1 - x3) / 3, y3 + 2 * (y1 - y3) / 3, x3, y3);
  }
  #generateBezierPoints() {
    const path = this.currentPath;
    if (path.length <= 2) {
      return [[path[0], path[0], path.at(-1), path.at(-1)]];
    }
    const bezierPoints = [];
    let i;
    let [x0, y0] = path[0];
    for (i = 1; i < path.length - 2; i++) {
      const [x1, y1] = path[i];
      const [x2, y2] = path[i + 1];
      const x3 = (x1 + x2) / 2;
      const y3 = (y1 + y2) / 2;
      const control1 = [x0 + 2 * (x1 - x0) / 3, y0 + 2 * (y1 - y0) / 3];
      const control2 = [x3 + 2 * (x1 - x3) / 3, y3 + 2 * (y1 - y3) / 3];
      bezierPoints.push([[x0, y0], control1, control2, [x3, y3]]);
      [x0, y0] = [x3, y3];
    }
    const [x1, y1] = path[i];
    const [x2, y2] = path[i + 1];
    const control1 = [x0 + 2 * (x1 - x0) / 3, y0 + 2 * (y1 - y0) / 3];
    const control2 = [x2 + 2 * (x1 - x2) / 3, y2 + 2 * (y1 - y2) / 3];
    bezierPoints.push([[x0, y0], control1, control2, [x2, y2]]);
    return bezierPoints;
  }
  #redraw() {
    if (this.isEmpty()) {
      this.#updateTransform();
      return;
    }
    this.#setStroke();
    const {
      canvas,
      ctx
    } = this;
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    this.#updateTransform();
    for (const path of this.bezierPath2D) {
      ctx.stroke(path);
    }
  }
  commit() {
    if (this.#disableEditing) {
      return;
    }
    super.commit();
    this.isEditing = false;
    this.disableEditMode();
    this.setInForeground();
    this.#disableEditing = true;
    this.div.classList.add("disabled");
    this.#fitToContent(true);
    this.select();
    this.parent.addInkEditorIfNeeded(true);
    this.moveInDOM();
    this.div.focus({
      preventScroll: true
    });
  }
  focusin(event) {
    if (!this._focusEventsAllowed) {
      return;
    }
    super.focusin(event);
    this.enableEditMode();
  }
  #addPointerdownListener() {
    if (this.#pointerdownAC) {
      return;
    }
    this.#pointerdownAC = new AbortController();
    const signal = this._uiManager.combinedSignal(this.#pointerdownAC);
    this.canvas.addEventListener("pointerdown", this.canvasPointerdown.bind(this), {
      signal
    });
  }
  #removePointerdownListener() {
    this.pointerdownAC?.abort();
    this.pointerdownAC = null;
  }
  canvasPointerdown(event) {
    if (event.button !== 0 || !this.isInEditMode() || this.#disableEditing) {
      return;
    }
    this.setInForeground();
    event.preventDefault();
    if (!this.div.contains(document.activeElement)) {
      this.div.focus({
        preventScroll: true
      });
    }
    this.#startDrawing(event.offsetX, event.offsetY);
  }
  canvasPointermove(event) {
    event.preventDefault();
    this.#draw(event.offsetX, event.offsetY);
  }
  canvasPointerup(event) {
    event.preventDefault();
    this.#endDrawing(event);
  }
  canvasPointerleave(event) {
    this.#endDrawing(event);
  }
  #endDrawing(event) {
    this.#drawingAC?.abort();
    this.#drawingAC = null;
    this.#addPointerdownListener();
    if (this.#canvasContextMenuTimeoutId) {
      clearTimeout(this.#canvasContextMenuTimeoutId);
    }
    this.#canvasContextMenuTimeoutId = setTimeout(() => {
      this.#canvasContextMenuTimeoutId = null;
      this.canvas.removeEventListener("contextmenu", noContextMenu);
    }, 10);
    this.#stopDrawing(event.offsetX, event.offsetY);
    this.addToAnnotationStorage();
    this.setInBackground();
  }
  #createCanvas() {
    this.canvas = document.createElement("canvas");
    this.canvas.width = this.canvas.height = 0;
    this.canvas.className = "inkEditorCanvas";
    this.canvas.setAttribute("data-l10n-id", "pdfjs-ink-canvas");
    this.div.append(this.canvas);
    this.ctx = this.canvas.getContext("2d");
  }
  #createObserver() {
    this.#observer = new ResizeObserver(entries => {
      const rect = entries[0].contentRect;
      if (rect.width && rect.height) {
        this.setDimensions(rect.width, rect.height);
      }
    });
    this.#observer.observe(this.div);
    this._uiManager._signal.addEventListener("abort", () => {
      this.#observer?.disconnect();
      this.#observer = null;
    }, {
      once: true
    });
  }
  get isResizable() {
    return !this.isEmpty() && this.#disableEditing;
  }
  render() {
    if (this.div) {
      return this.div;
    }
    let baseX, baseY;
    if (this.width) {
      baseX = this.x;
      baseY = this.y;
    }
    super.render();
    this.div.setAttribute("data-l10n-id", "pdfjs-ink");
    const [x, y, w, h] = this.#getInitialBBox();
    this.setAt(x, y, 0, 0);
    this.setDims(w, h);
    this.#createCanvas();
    if (this.width) {
      const [parentWidth, parentHeight] = this.parentDimensions;
      this.setAspectRatio(this.width * parentWidth, this.height * parentHeight);
      this.setAt(baseX * parentWidth, baseY * parentHeight, this.width * parentWidth, this.height * parentHeight);
      this.#isCanvasInitialized = true;
      this.#setCanvasDims();
      this.setDims(this.width * parentWidth, this.height * parentHeight);
      this.#redraw();
      this.div.classList.add("disabled");
    } else {
      this.div.classList.add("editing");
      this.enableEditMode();
    }
    this.#createObserver();
    return this.div;
  }
  #setCanvasDims() {
    if (!this.#isCanvasInitialized) {
      return;
    }
    const [parentWidth, parentHeight] = this.parentDimensions;
    this.canvas.width = Math.ceil(this.width * parentWidth);
    this.canvas.height = Math.ceil(this.height * parentHeight);
    this.#updateTransform();
  }
  setDimensions(width, height) {
    const roundedWidth = Math.round(width);
    const roundedHeight = Math.round(height);
    if (this.#realWidth === roundedWidth && this.#realHeight === roundedHeight) {
      return;
    }
    this.#realWidth = roundedWidth;
    this.#realHeight = roundedHeight;
    this.canvas.style.visibility = "hidden";
    const [parentWidth, parentHeight] = this.parentDimensions;
    this.width = width / parentWidth;
    this.height = height / parentHeight;
    this.fixAndSetPosition();
    if (this.#disableEditing) {
      this.#setScaleFactor(width, height);
    }
    this.#setCanvasDims();
    this.#redraw();
    this.canvas.style.visibility = "visible";
    this.fixDims();
  }
  #setScaleFactor(width, height) {
    const padding = this.#getPadding();
    const scaleFactorW = (width - padding) / this.#baseWidth;
    const scaleFactorH = (height - padding) / this.#baseHeight;
    this.scaleFactor = Math.min(scaleFactorW, scaleFactorH);
  }
  #updateTransform() {
    const padding = this.#getPadding() / 2;
    this.ctx.setTransform(this.scaleFactor, 0, 0, this.scaleFactor, this.translationX * this.scaleFactor + padding, this.translationY * this.scaleFactor + padding);
  }
  static #buildPath2D(bezier) {
    const path2D = new Path2D();
    for (let i = 0, ii = bezier.length; i < ii; i++) {
      const [first, control1, control2, second] = bezier[i];
      if (i === 0) {
        path2D.moveTo(...first);
      }
      path2D.bezierCurveTo(control1[0], control1[1], control2[0], control2[1], second[0], second[1]);
    }
    return path2D;
  }
  static #toPDFCoordinates(points, rect, rotation) {
    const [blX, blY, trX, trY] = rect;
    switch (rotation) {
      case 0:
        for (let i = 0, ii = points.length; i < ii; i += 2) {
          points[i] += blX;
          points[i + 1] = trY - points[i + 1];
        }
        break;
      case 90:
        for (let i = 0, ii = points.length; i < ii; i += 2) {
          const x = points[i];
          points[i] = points[i + 1] + blX;
          points[i + 1] = x + blY;
        }
        break;
      case 180:
        for (let i = 0, ii = points.length; i < ii; i += 2) {
          points[i] = trX - points[i];
          points[i + 1] += blY;
        }
        break;
      case 270:
        for (let i = 0, ii = points.length; i < ii; i += 2) {
          const x = points[i];
          points[i] = trX - points[i + 1];
          points[i + 1] = trY - x;
        }
        break;
      default:
        throw new Error("Invalid rotation");
    }
    return points;
  }
  static #fromPDFCoordinates(points, rect, rotation) {
    const [blX, blY, trX, trY] = rect;
    switch (rotation) {
      case 0:
        for (let i = 0, ii = points.length; i < ii; i += 2) {
          points[i] -= blX;
          points[i + 1] = trY - points[i + 1];
        }
        break;
      case 90:
        for (let i = 0, ii = points.length; i < ii; i += 2) {
          const x = points[i];
          points[i] = points[i + 1] - blY;
          points[i + 1] = x - blX;
        }
        break;
      case 180:
        for (let i = 0, ii = points.length; i < ii; i += 2) {
          points[i] = trX - points[i];
          points[i + 1] -= blY;
        }
        break;
      case 270:
        for (let i = 0, ii = points.length; i < ii; i += 2) {
          const x = points[i];
          points[i] = trY - points[i + 1];
          points[i + 1] = trX - x;
        }
        break;
      default:
        throw new Error("Invalid rotation");
    }
    return points;
  }
  #serializePaths(s, tx, ty, rect) {
    const paths = [];
    const padding = this.thickness / 2;
    const shiftX = s * tx + padding;
    const shiftY = s * ty + padding;
    for (const bezier of this.paths) {
      const buffer = [];
      const points = [];
      for (let j = 0, jj = bezier.length; j < jj; j++) {
        const [first, control1, control2, second] = bezier[j];
        if (first[0] === second[0] && first[1] === second[1] && jj === 1) {
          const p0 = s * first[0] + shiftX;
          const p1 = s * first[1] + shiftY;
          buffer.push(p0, p1);
          points.push(p0, p1);
          break;
        }
        const p10 = s * first[0] + shiftX;
        const p11 = s * first[1] + shiftY;
        const p20 = s * control1[0] + shiftX;
        const p21 = s * control1[1] + shiftY;
        const p30 = s * control2[0] + shiftX;
        const p31 = s * control2[1] + shiftY;
        const p40 = s * second[0] + shiftX;
        const p41 = s * second[1] + shiftY;
        if (j === 0) {
          buffer.push(p10, p11);
          points.push(p10, p11);
        }
        buffer.push(p20, p21, p30, p31, p40, p41);
        points.push(p20, p21);
        if (j === jj - 1) {
          points.push(p40, p41);
        }
      }
      paths.push({
        bezier: InkEditor.#toPDFCoordinates(buffer, rect, this.rotation),
        points: InkEditor.#toPDFCoordinates(points, rect, this.rotation)
      });
    }
    return paths;
  }
  #getBbox() {
    let xMin = Infinity;
    let xMax = -Infinity;
    let yMin = Infinity;
    let yMax = -Infinity;
    for (const path of this.paths) {
      for (const [first, control1, control2, second] of path) {
        const bbox = Util.bezierBoundingBox(...first, ...control1, ...control2, ...second);
        xMin = Math.min(xMin, bbox[0]);
        yMin = Math.min(yMin, bbox[1]);
        xMax = Math.max(xMax, bbox[2]);
        yMax = Math.max(yMax, bbox[3]);
      }
    }
    return [xMin, yMin, xMax, yMax];
  }
  #getPadding() {
    return this.#disableEditing ? Math.ceil(this.thickness * this.parentScale) : 0;
  }
  #fitToContent(firstTime = false) {
    if (this.isEmpty()) {
      return;
    }
    if (!this.#disableEditing) {
      this.#redraw();
      return;
    }
    const bbox = this.#getBbox();
    const padding = this.#getPadding();
    this.#baseWidth = Math.max(AnnotationEditor.MIN_SIZE, bbox[2] - bbox[0]);
    this.#baseHeight = Math.max(AnnotationEditor.MIN_SIZE, bbox[3] - bbox[1]);
    const width = Math.ceil(padding + this.#baseWidth * this.scaleFactor);
    const height = Math.ceil(padding + this.#baseHeight * this.scaleFactor);
    const [parentWidth, parentHeight] = this.parentDimensions;
    this.width = width / parentWidth;
    this.height = height / parentHeight;
    this.setAspectRatio(width, height);
    const prevTranslationX = this.translationX;
    const prevTranslationY = this.translationY;
    this.translationX = -bbox[0];
    this.translationY = -bbox[1];
    this.#setCanvasDims();
    this.#redraw();
    this.#realWidth = width;
    this.#realHeight = height;
    this.setDims(width, height);
    const unscaledPadding = firstTime ? padding / this.scaleFactor / 2 : 0;
    this.translate(prevTranslationX - this.translationX - unscaledPadding, prevTranslationY - this.translationY - unscaledPadding);
  }
  static deserialize(data, parent, uiManager) {
    if (data instanceof InkAnnotationElement) {
      return null;
    }
    const editor = super.deserialize(data, parent, uiManager);
    editor.thickness = data.thickness;
    editor.color = Util.makeHexColor(...data.color);
    editor.opacity = data.opacity;
    const [pageWidth, pageHeight] = editor.pageDimensions;
    const width = editor.width * pageWidth;
    const height = editor.height * pageHeight;
    const scaleFactor = editor.parentScale;
    const padding = data.thickness / 2;
    editor.#disableEditing = true;
    editor.#realWidth = Math.round(width);
    editor.#realHeight = Math.round(height);
    const {
      paths,
      rect,
      rotation
    } = data;
    for (let {
      bezier
    } of paths) {
      bezier = InkEditor.#fromPDFCoordinates(bezier, rect, rotation);
      const path = [];
      editor.paths.push(path);
      let p0 = scaleFactor * (bezier[0] - padding);
      let p1 = scaleFactor * (bezier[1] - padding);
      for (let i = 2, ii = bezier.length; i < ii; i += 6) {
        const p10 = scaleFactor * (bezier[i] - padding);
        const p11 = scaleFactor * (bezier[i + 1] - padding);
        const p20 = scaleFactor * (bezier[i + 2] - padding);
        const p21 = scaleFactor * (bezier[i + 3] - padding);
        const p30 = scaleFactor * (bezier[i + 4] - padding);
        const p31 = scaleFactor * (bezier[i + 5] - padding);
        path.push([[p0, p1], [p10, p11], [p20, p21], [p30, p31]]);
        p0 = p30;
        p1 = p31;
      }
      const path2D = this.#buildPath2D(path);
      editor.bezierPath2D.push(path2D);
    }
    const bbox = editor.#getBbox();
    editor.#baseWidth = Math.max(AnnotationEditor.MIN_SIZE, bbox[2] - bbox[0]);
    editor.#baseHeight = Math.max(AnnotationEditor.MIN_SIZE, bbox[3] - bbox[1]);
    editor.#setScaleFactor(width, height);
    return editor;
  }
  serialize() {
    if (this.isEmpty()) {
      return null;
    }
    const rect = this.getRect(0, 0);
    const color = AnnotationEditor._colorManager.convert(this.ctx.strokeStyle);
    return {
      annotationType: AnnotationEditorType.INK,
      color,
      thickness: this.thickness,
      opacity: this.opacity,
      paths: this.#serializePaths(this.scaleFactor / this.parentScale, this.translationX, this.translationY, rect),
      pageIndex: this.pageIndex,
      rect,
      rotation: this.rotation,
      structTreeParentId: this._structTreeParentId
    };
  }
}

;// CONCATENATED MODULE: ./src/display/editor/stamp.js




class StampEditor extends AnnotationEditor {
  #bitmap = null;
  #bitmapId = null;
  #bitmapPromise = null;
  #bitmapUrl = null;
  #bitmapFile = null;
  #bitmapFileName = "";
  #canvas = null;
  #observer = null;
  #resizeTimeoutId = null;
  #isSvg = false;
  #hasBeenAddedInUndoStack = false;
  static _type = "stamp";
  static _editorType = AnnotationEditorType.STAMP;
  constructor(params) {
    super({
      ...params,
      name: "stampEditor"
    });
    this.#bitmapUrl = params.bitmapUrl;
    this.#bitmapFile = params.bitmapFile;
  }
  static initialize(l10n, uiManager) {
    AnnotationEditor.initialize(l10n, uiManager);
  }
  static get supportedTypes() {
    const types = ["apng", "avif", "bmp", "gif", "jpeg", "png", "svg+xml", "webp", "x-icon"];
    return shadow(this, "supportedTypes", types.map(type => `image/${type}`));
  }
  static get supportedTypesStr() {
    return shadow(this, "supportedTypesStr", this.supportedTypes.join(","));
  }
  static isHandlingMimeForPasting(mime) {
    return this.supportedTypes.includes(mime);
  }
  static paste(item, parent) {
    parent.pasteEditor(AnnotationEditorType.STAMP, {
      bitmapFile: item.getAsFile()
    });
  }
  altTextFinish() {
    if (this._uiManager.useNewAltTextFlow) {
      this.div.hidden = false;
    }
    super.altTextFinish();
  }
  get telemetryFinalData() {
    return {
      type: "stamp",
      hasAltText: !!this.altTextData?.altText
    };
  }
  static computeTelemetryFinalData(data) {
    const hasAltTextStats = data.get("hasAltText");
    return {
      hasAltText: hasAltTextStats.get(true) ?? 0,
      hasNoAltText: hasAltTextStats.get(false) ?? 0
    };
  }
  #getBitmapFetched(data, fromId = false) {
    if (!data) {
      this.remove();
      return;
    }
    this.#bitmap = data.bitmap;
    if (!fromId) {
      this.#bitmapId = data.id;
      this.#isSvg = data.isSvg;
    }
    if (data.file) {
      this.#bitmapFileName = data.file.name;
    }
    this.#createCanvas();
  }
  #getBitmapDone() {
    this.#bitmapPromise = null;
    this._uiManager.enableWaiting(false);
    if (!this.#canvas) {
      return;
    }
    if (this._uiManager.useNewAltTextWhenAddingImage && this._uiManager.useNewAltTextFlow && this.#bitmap) {
      this._editToolbar.hide();
      this._uiManager.editAltText(this, true);
      return;
    }
    if (!this._uiManager.useNewAltTextWhenAddingImage && this._uiManager.useNewAltTextFlow && this.#bitmap) {
      this._reportTelemetry({
        action: "pdfjs.image.image_added",
        data: {
          alt_text_modal: false,
          alt_text_type: "empty"
        }
      });
      try {
        this.mlGuessAltText();
      } catch {}
    }
    this.div.focus();
  }
  async mlGuessAltText(imageData = null, updateAltTextData = true) {
    if (this.hasAltTextData()) {
      return null;
    }
    const {
      mlManager
    } = this._uiManager;
    if (!mlManager) {
      throw new Error("No ML.");
    }
    if (!(await mlManager.isEnabledFor("altText"))) {
      throw new Error("ML isn't enabled for alt text.");
    }
    const {
      data,
      width,
      height
    } = imageData || this.copyCanvas(null, true).imageData;
    const response = await mlManager.guess({
      name: "altText",
      request: {
        data,
        width,
        height,
        channels: data.length / (width * height)
      }
    });
    if (!response) {
      throw new Error("No response from the AI service.");
    }
    if (response.error) {
      throw new Error("Error from the AI service.");
    }
    if (response.cancel) {
      return null;
    }
    if (!response.output) {
      throw new Error("No valid response from the AI service.");
    }
    const altText = response.output;
    await this.setGuessedAltText(altText);
    if (updateAltTextData && !this.hasAltTextData()) {
      this.altTextData = {
        alt: altText,
        decorative: false
      };
    }
    return altText;
  }
  #getBitmap() {
    if (this.#bitmapId) {
      this._uiManager.enableWaiting(true);
      this._uiManager.imageManager.getFromId(this.#bitmapId).then(data => this.#getBitmapFetched(data, true)).finally(() => this.#getBitmapDone());
      return;
    }
    if (this.#bitmapUrl) {
      const url = this.#bitmapUrl;
      this.#bitmapUrl = null;
      this._uiManager.enableWaiting(true);
      this.#bitmapPromise = this._uiManager.imageManager.getFromUrl(url).then(data => this.#getBitmapFetched(data)).finally(() => this.#getBitmapDone());
      return;
    }
    if (this.#bitmapFile) {
      const file = this.#bitmapFile;
      this.#bitmapFile = null;
      this._uiManager.enableWaiting(true);
      this.#bitmapPromise = this._uiManager.imageManager.getFromFile(file).then(data => this.#getBitmapFetched(data)).finally(() => this.#getBitmapDone());
      return;
    }
    const input = document.createElement("input");
    input.type = "file";
    input.accept = StampEditor.supportedTypesStr;
    const signal = this._uiManager._signal;
    this.#bitmapPromise = new Promise(resolve => {
      input.addEventListener("change", async () => {
        if (!input.files || input.files.length === 0) {
          this.remove();
        } else {
          this._uiManager.enableWaiting(true);
          const data = await this._uiManager.imageManager.getFromFile(input.files[0]);
          this._reportTelemetry({
            action: "pdfjs.image.image_selected",
            data: {
              alt_text_modal: this._uiManager.useNewAltTextFlow
            }
          });
          this.#getBitmapFetched(data);
        }
        resolve();
      }, {
        signal
      });
      input.addEventListener("cancel", () => {
        this.remove();
        resolve();
      }, {
        signal
      });
    }).finally(() => this.#getBitmapDone());
    input.click();
  }
  remove() {
    if (this.#bitmapId) {
      this.#bitmap = null;
      this._uiManager.imageManager.deleteId(this.#bitmapId);
      this.#canvas?.remove();
      this.#canvas = null;
      this.#observer?.disconnect();
      this.#observer = null;
      if (this.#resizeTimeoutId) {
        clearTimeout(this.#resizeTimeoutId);
        this.#resizeTimeoutId = null;
      }
    }
    super.remove();
  }
  rebuild() {
    if (!this.parent) {
      if (this.#bitmapId) {
        this.#getBitmap();
      }
      return;
    }
    super.rebuild();
    if (this.div === null) {
      return;
    }
    if (this.#bitmapId && this.#canvas === null) {
      this.#getBitmap();
    }
    if (!this.isAttachedToDOM) {
      this.parent.add(this);
    }
  }
  onceAdded() {
    this._isDraggable = true;
    this.div.focus();
  }
  isEmpty() {
    return !(this.#bitmapPromise || this.#bitmap || this.#bitmapUrl || this.#bitmapFile || this.#bitmapId);
  }
  get isResizable() {
    return true;
  }
  render() {
    if (this.div) {
      return this.div;
    }
    let baseX, baseY;
    if (this.width) {
      baseX = this.x;
      baseY = this.y;
    }
    super.render();
    this.div.hidden = true;
    this.addAltTextButton();
    if (this.#bitmap) {
      this.#createCanvas();
    } else {
      this.#getBitmap();
    }
    if (this.width) {
      const [parentWidth, parentHeight] = this.parentDimensions;
      this.setAt(baseX * parentWidth, baseY * parentHeight, this.width * parentWidth, this.height * parentHeight);
    }
    return this.div;
  }
  #createCanvas() {
    const {
      div
    } = this;
    let {
      width,
      height
    } = this.#bitmap;
    const [pageWidth, pageHeight] = this.pageDimensions;
    const MAX_RATIO = 0.75;
    if (this.width) {
      width = this.width * pageWidth;
      height = this.height * pageHeight;
    } else if (width > MAX_RATIO * pageWidth || height > MAX_RATIO * pageHeight) {
      const factor = Math.min(MAX_RATIO * pageWidth / width, MAX_RATIO * pageHeight / height);
      width *= factor;
      height *= factor;
    }
    const [parentWidth, parentHeight] = this.parentDimensions;
    this.setDims(width * parentWidth / pageWidth, height * parentHeight / pageHeight);
    this._uiManager.enableWaiting(false);
    const canvas = this.#canvas = document.createElement("canvas");
    div.append(canvas);
    if (!this._uiManager.useNewAltTextWhenAddingImage || !this._uiManager.useNewAltTextFlow) {
      div.hidden = false;
    }
    this.#drawBitmap(width, height);
    this.#createObserver();
    if (!this.#hasBeenAddedInUndoStack) {
      this.parent.addUndoableEditor(this);
      this.#hasBeenAddedInUndoStack = true;
    }
    this._reportTelemetry({
      action: "inserted_image"
    });
    if (this.#bitmapFileName) {
      canvas.setAttribute("aria-label", this.#bitmapFileName);
    }
  }
  copyCanvas(maxDimension, createImageData = false) {
    if (!maxDimension) {
      maxDimension = 224;
    }
    const {
      width: bitmapWidth,
      height: bitmapHeight
    } = this.#bitmap;
    const canvas = document.createElement("canvas");
    let bitmap = this.#bitmap;
    let width = bitmapWidth,
      height = bitmapHeight;
    if (bitmapWidth > maxDimension || bitmapHeight > maxDimension) {
      const ratio = Math.min(maxDimension / bitmapWidth, maxDimension / bitmapHeight);
      width = Math.floor(bitmapWidth * ratio);
      height = Math.floor(bitmapHeight * ratio);
      if (!this.#isSvg) {
        bitmap = this.#scaleBitmap(width, height);
      }
    }
    canvas.width = width;
    canvas.height = height;
    const ctx = canvas.getContext("2d");
    ctx.filter = this._uiManager.hcmFilter;
    let white = "white",
      black = "#cfcfd8";
    if (this._uiManager.hcmFilter !== "none") {
      black = "black";
    } else if (window.matchMedia?.("(prefers-color-scheme: dark)").matches) {
      white = "#8f8f9d";
      black = "#42414d";
    }
    const boxDim = 15;
    const pattern = new OffscreenCanvas(boxDim * 2, boxDim * 2);
    const patternCtx = pattern.getContext("2d");
    patternCtx.fillStyle = white;
    patternCtx.fillRect(0, 0, boxDim * 2, boxDim * 2);
    patternCtx.fillStyle = black;
    patternCtx.fillRect(0, 0, boxDim, boxDim);
    patternCtx.fillRect(boxDim, boxDim, boxDim, boxDim);
    ctx.fillStyle = ctx.createPattern(pattern, "repeat");
    ctx.fillRect(0, 0, width, height);
    if (createImageData) {
      const offscreen = new OffscreenCanvas(width, height);
      const offscreenCtx = offscreen.getContext("2d", {
        willReadFrequently: true
      });
      offscreenCtx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, width, height);
      const data = offscreenCtx.getImageData(0, 0, width, height).data;
      ctx.drawImage(offscreen, 0, 0);
      return {
        canvas,
        imageData: {
          width,
          height,
          data
        }
      };
    }
    ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, width, height);
    return {
      canvas,
      imageData: null
    };
  }
  #setDimensions(width, height) {
    const [parentWidth, parentHeight] = this.parentDimensions;
    this.width = width / parentWidth;
    this.height = height / parentHeight;
    this.setDims(width, height);
    if (this._initialOptions?.isCentered) {
      this.center();
    } else {
      this.fixAndSetPosition();
    }
    this._initialOptions = null;
    if (this.#resizeTimeoutId !== null) {
      clearTimeout(this.#resizeTimeoutId);
    }
    const TIME_TO_WAIT = 200;
    this.#resizeTimeoutId = setTimeout(() => {
      this.#resizeTimeoutId = null;
      this.#drawBitmap(width, height);
    }, TIME_TO_WAIT);
  }
  #scaleBitmap(width, height) {
    const {
      width: bitmapWidth,
      height: bitmapHeight
    } = this.#bitmap;
    let newWidth = bitmapWidth;
    let newHeight = bitmapHeight;
    let bitmap = this.#bitmap;
    while (newWidth > 2 * width || newHeight > 2 * height) {
      const prevWidth = newWidth;
      const prevHeight = newHeight;
      if (newWidth > 2 * width) {
        newWidth = newWidth >= 16384 ? Math.floor(newWidth / 2) - 1 : Math.ceil(newWidth / 2);
      }
      if (newHeight > 2 * height) {
        newHeight = newHeight >= 16384 ? Math.floor(newHeight / 2) - 1 : Math.ceil(newHeight / 2);
      }
      const offscreen = new OffscreenCanvas(newWidth, newHeight);
      const ctx = offscreen.getContext("2d");
      ctx.drawImage(bitmap, 0, 0, prevWidth, prevHeight, 0, 0, newWidth, newHeight);
      bitmap = offscreen.transferToImageBitmap();
    }
    return bitmap;
  }
  #drawBitmap(width, height) {
    width = Math.ceil(width);
    height = Math.ceil(height);
    const canvas = this.#canvas;
    if (!canvas || canvas.width === width && canvas.height === height) {
      return;
    }
    canvas.width = width;
    canvas.height = height;
    const bitmap = this.#isSvg ? this.#bitmap : this.#scaleBitmap(width, height);
    const ctx = canvas.getContext("2d");
    ctx.filter = this._uiManager.hcmFilter;
    ctx.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height, 0, 0, width, height);
  }
  getImageForAltText() {
    return this.#canvas;
  }
  #serializeBitmap(toUrl) {
    if (toUrl) {
      if (this.#isSvg) {
        const url = this._uiManager.imageManager.getSvgUrl(this.#bitmapId);
        if (url) {
          return url;
        }
      }
      const canvas = document.createElement("canvas");
      ({
        width: canvas.width,
        height: canvas.height
      } = this.#bitmap);
      const ctx = canvas.getContext("2d");
      ctx.drawImage(this.#bitmap, 0, 0);
      return canvas.toDataURL();
    }
    if (this.#isSvg) {
      const [pageWidth, pageHeight] = this.pageDimensions;
      const width = Math.round(this.width * pageWidth * PixelsPerInch.PDF_TO_CSS_UNITS);
      const height = Math.round(this.height * pageHeight * PixelsPerInch.PDF_TO_CSS_UNITS);
      const offscreen = new OffscreenCanvas(width, height);
      const ctx = offscreen.getContext("2d");
      ctx.drawImage(this.#bitmap, 0, 0, this.#bitmap.width, this.#bitmap.height, 0, 0, width, height);
      return offscreen.transferToImageBitmap();
    }
    return structuredClone(this.#bitmap);
  }
  #createObserver() {
    if (!this._uiManager._signal) {
      return;
    }
    this.#observer = new ResizeObserver(entries => {
      const rect = entries[0].contentRect;
      if (rect.width && rect.height) {
        this.#setDimensions(rect.width, rect.height);
      }
    });
    this.#observer.observe(this.div);
    this._uiManager._signal.addEventListener("abort", () => {
      this.#observer?.disconnect();
      this.#observer = null;
    }, {
      once: true
    });
  }
  static deserialize(data, parent, uiManager) {
    if (data instanceof StampAnnotationElement) {
      return null;
    }
    const editor = super.deserialize(data, parent, uiManager);
    const {
      rect,
      bitmapUrl,
      bitmapId,
      isSvg,
      accessibilityData
    } = data;
    if (bitmapId && uiManager.imageManager.isValidId(bitmapId)) {
      editor.#bitmapId = bitmapId;
    } else {
      editor.#bitmapUrl = bitmapUrl;
    }
    editor.#isSvg = isSvg;
    const [parentWidth, parentHeight] = editor.pageDimensions;
    editor.width = (rect[2] - rect[0]) / parentWidth;
    editor.height = (rect[3] - rect[1]) / parentHeight;
    if (accessibilityData) {
      editor.altTextData = accessibilityData;
    }
    return editor;
  }
  serialize(isForCopying = false, context = null) {
    if (this.isEmpty()) {
      return null;
    }
    const serialized = {
      annotationType: AnnotationEditorType.STAMP,
      bitmapId: this.#bitmapId,
      pageIndex: this.pageIndex,
      rect: this.getRect(0, 0),
      rotation: this.rotation,
      isSvg: this.#isSvg,
      structTreeParentId: this._structTreeParentId
    };
    if (isForCopying) {
      serialized.bitmapUrl = this.#serializeBitmap(true);
      serialized.accessibilityData = this.serializeAltText(true);
      return serialized;
    }
    const {
      decorative,
      altText
    } = this.serializeAltText(false);
    if (!decorative && altText) {
      serialized.accessibilityData = {
        type: "Figure",
        alt: altText
      };
    }
    if (context === null) {
      return serialized;
    }
    context.stamps ||= new Map();
    const area = this.#isSvg ? (serialized.rect[2] - serialized.rect[0]) * (serialized.rect[3] - serialized.rect[1]) : null;
    if (!context.stamps.has(this.#bitmapId)) {
      context.stamps.set(this.#bitmapId, {
        area,
        serialized
      });
      serialized.bitmap = this.#serializeBitmap(false);
    } else if (this.#isSvg) {
      const prevData = context.stamps.get(this.#bitmapId);
      if (area > prevData.area) {
        prevData.area = area;
        prevData.serialized.bitmap.close();
        prevData.serialized.bitmap = this.#serializeBitmap(false);
      }
    }
    return serialized;
  }
}

;// CONCATENATED MODULE: ./src/display/editor/annotation_editor_layer.js







class AnnotationEditorLayer {
  #accessibilityManager;
  #allowClick = false;
  #annotationLayer = null;
  #clickAC = null;
  #editorFocusTimeoutId = null;
  #editors = new Map();
  #hadPointerDown = false;
  #isCleaningUp = false;
  #isDisabling = false;
  #textLayer = null;
  #textSelectionAC = null;
  #uiManager;
  static _initialized = false;
  static #editorTypes = new Map([FreeTextEditor, InkEditor, StampEditor, HighlightEditor].map(type => [type._editorType, type]));
  constructor({
    uiManager,
    pageIndex,
    div,
    accessibilityManager,
    annotationLayer,
    drawLayer,
    textLayer,
    viewport,
    l10n
  }) {
    const editorTypes = [...AnnotationEditorLayer.#editorTypes.values()];
    if (!AnnotationEditorLayer._initialized) {
      AnnotationEditorLayer._initialized = true;
      for (const editorType of editorTypes) {
        editorType.initialize(l10n, uiManager);
      }
    }
    uiManager.registerEditorTypes(editorTypes);
    this.#uiManager = uiManager;
    this.pageIndex = pageIndex;
    this.div = div;
    this.#accessibilityManager = accessibilityManager;
    this.#annotationLayer = annotationLayer;
    this.viewport = viewport;
    this.#textLayer = textLayer;
    this.drawLayer = drawLayer;
    this.#uiManager.addLayer(this);
  }
  get isEmpty() {
    return this.#editors.size === 0;
  }
  get isInvisible() {
    return this.isEmpty && this.#uiManager.getMode() === AnnotationEditorType.NONE;
  }
  updateToolbar(mode) {
    this.#uiManager.updateToolbar(mode);
  }
  updateMode(mode = this.#uiManager.getMode()) {
    this.#cleanup();
    switch (mode) {
      case AnnotationEditorType.NONE:
        this.disableTextSelection();
        this.togglePointerEvents(false);
        this.toggleAnnotationLayerPointerEvents(true);
        this.disableClick();
        return;
      case AnnotationEditorType.INK:
        this.addInkEditorIfNeeded(false);
        this.disableTextSelection();
        this.togglePointerEvents(true);
        this.disableClick();
        break;
      case AnnotationEditorType.HIGHLIGHT:
        this.enableTextSelection();
        this.togglePointerEvents(false);
        this.disableClick();
        break;
      default:
        this.disableTextSelection();
        this.togglePointerEvents(true);
        this.enableClick();
    }
    this.toggleAnnotationLayerPointerEvents(false);
    const {
      classList
    } = this.div;
    for (const editorType of AnnotationEditorLayer.#editorTypes.values()) {
      classList.toggle(`${editorType._type}Editing`, mode === editorType._editorType);
    }
    this.div.hidden = false;
  }
  hasTextLayer(textLayer) {
    return textLayer === this.#textLayer?.div;
  }
  addInkEditorIfNeeded(isCommitting) {
    if (this.#uiManager.getMode() !== AnnotationEditorType.INK) {
      return;
    }
    if (!isCommitting) {
      for (const editor of this.#editors.values()) {
        if (editor.isEmpty()) {
          editor.setInBackground();
          return;
        }
      }
    }
    const editor = this.createAndAddNewEditor({
      offsetX: 0,
      offsetY: 0
    }, false);
    editor.setInBackground();
  }
  setEditingState(isEditing) {
    this.#uiManager.setEditingState(isEditing);
  }
  addCommands(params) {
    this.#uiManager.addCommands(params);
  }
  toggleDrawing(enabled = false) {
    this.div.classList.toggle("drawing", !enabled);
  }
  togglePointerEvents(enabled = false) {
    this.div.classList.toggle("disabled", !enabled);
  }
  toggleAnnotationLayerPointerEvents(enabled = false) {
    this.#annotationLayer?.div.classList.toggle("disabled", !enabled);
  }
  enable() {
    this.div.tabIndex = 0;
    this.togglePointerEvents(true);
    const annotationElementIds = new Set();
    for (const editor of this.#editors.values()) {
      editor.enableEditing();
      editor.show(true);
      if (editor.annotationElementId) {
        this.#uiManager.removeChangedExistingAnnotation(editor);
        annotationElementIds.add(editor.annotationElementId);
      }
    }
    if (!this.#annotationLayer) {
      return;
    }
    const editables = this.#annotationLayer.getEditableAnnotations();
    for (const editable of editables) {
      editable.hide();
      if (this.#uiManager.isDeletedAnnotationElement(editable.data.id)) {
        continue;
      }
      if (annotationElementIds.has(editable.data.id)) {
        continue;
      }
      const editor = this.deserialize(editable);
      if (!editor) {
        continue;
      }
      this.addOrRebuild(editor);
      editor.enableEditing();
    }
  }
  disable() {
    this.#isDisabling = true;
    this.div.tabIndex = -1;
    this.togglePointerEvents(false);
    const changedAnnotations = new Map();
    const resetAnnotations = new Map();
    for (const editor of this.#editors.values()) {
      editor.disableEditing();
      if (!editor.annotationElementId) {
        continue;
      }
      if (editor.serialize() !== null) {
        changedAnnotations.set(editor.annotationElementId, editor);
        continue;
      } else {
        resetAnnotations.set(editor.annotationElementId, editor);
      }
      this.getEditableAnnotation(editor.annotationElementId)?.show();
      editor.remove();
    }
    if (this.#annotationLayer) {
      const editables = this.#annotationLayer.getEditableAnnotations();
      for (const editable of editables) {
        const {
          id
        } = editable.data;
        if (this.#uiManager.isDeletedAnnotationElement(id)) {
          continue;
        }
        let editor = resetAnnotations.get(id);
        if (editor) {
          editor.resetAnnotationElement(editable);
          editor.show(false);
          editable.show();
          continue;
        }
        editor = changedAnnotations.get(id);
        if (editor) {
          this.#uiManager.addChangedExistingAnnotation(editor);
          editor.renderAnnotationElement(editable);
          editor.show(false);
        }
        editable.show();
      }
    }
    this.#cleanup();
    if (this.isEmpty) {
      this.div.hidden = true;
    }
    const {
      classList
    } = this.div;
    for (const editorType of AnnotationEditorLayer.#editorTypes.values()) {
      classList.remove(`${editorType._type}Editing`);
    }
    this.disableTextSelection();
    this.toggleAnnotationLayerPointerEvents(true);
    this.#isDisabling = false;
  }
  getEditableAnnotation(id) {
    return this.#annotationLayer?.getEditableAnnotation(id) || null;
  }
  setActiveEditor(editor) {
    const currentActive = this.#uiManager.getActive();
    if (currentActive === editor) {
      return;
    }
    this.#uiManager.setActiveEditor(editor);
  }
  enableTextSelection() {
    this.div.tabIndex = -1;
    if (this.#textLayer?.div && !this.#textSelectionAC) {
      this.#textSelectionAC = new AbortController();
      const signal = this.#uiManager.combinedSignal(this.#textSelectionAC);
      this.#textLayer.div.addEventListener("pointerdown", this.#textLayerPointerDown.bind(this), {
        signal
      });
      this.#textLayer.div.classList.add("highlighting");
    }
  }
  disableTextSelection() {
    this.div.tabIndex = 0;
    if (this.#textLayer?.div && this.#textSelectionAC) {
      this.#textSelectionAC.abort();
      this.#textSelectionAC = null;
      this.#textLayer.div.classList.remove("highlighting");
    }
  }
  #textLayerPointerDown(event) {
    this.#uiManager.unselectAll();
    const {
      target
    } = event;
    if (target === this.#textLayer.div || target.classList.contains("endOfContent") && this.#textLayer.div.contains(target)) {
      const {
        isMac
      } = util_FeatureTest.platform;
      if (event.button !== 0 || event.ctrlKey && isMac) {
        return;
      }
      this.#uiManager.showAllEditors("highlight", true, true);
      this.#textLayer.div.classList.add("free");
      this.toggleDrawing();
      HighlightEditor.startHighlighting(this, this.#uiManager.direction === "ltr", event);
      this.#textLayer.div.addEventListener("pointerup", () => {
        this.#textLayer.div.classList.remove("free");
        this.toggleDrawing(true);
      }, {
        once: true,
        signal: this.#uiManager._signal
      });
      event.preventDefault();
    }
  }
  enableClick() {
    if (this.#clickAC) {
      return;
    }
    this.#clickAC = new AbortController();
    const signal = this.#uiManager.combinedSignal(this.#clickAC);
    this.div.addEventListener("pointerdown", this.pointerdown.bind(this), {
      signal
    });
    this.div.addEventListener("pointerup", this.pointerup.bind(this), {
      signal
    });
  }
  disableClick() {
    this.#clickAC?.abort();
    this.#clickAC = null;
  }
  attach(editor) {
    this.#editors.set(editor.id, editor);
    const {
      annotationElementId
    } = editor;
    if (annotationElementId && this.#uiManager.isDeletedAnnotationElement(annotationElementId)) {
      this.#uiManager.removeDeletedAnnotationElement(editor);
    }
  }
  detach(editor) {
    this.#editors.delete(editor.id);
    this.#accessibilityManager?.removePointerInTextLayer(editor.contentDiv);
    if (!this.#isDisabling && editor.annotationElementId) {
      this.#uiManager.addDeletedAnnotationElement(editor);
    }
  }
  remove(editor) {
    this.detach(editor);
    this.#uiManager.removeEditor(editor);
    editor.div.remove();
    editor.isAttachedToDOM = false;
    if (!this.#isCleaningUp) {
      this.addInkEditorIfNeeded(false);
    }
  }
  changeParent(editor) {
    if (editor.parent === this) {
      return;
    }
    if (editor.parent && editor.annotationElementId) {
      this.#uiManager.addDeletedAnnotationElement(editor.annotationElementId);
      AnnotationEditor.deleteAnnotationElement(editor);
      editor.annotationElementId = null;
    }
    this.attach(editor);
    editor.parent?.detach(editor);
    editor.setParent(this);
    if (editor.div && editor.isAttachedToDOM) {
      editor.div.remove();
      this.div.append(editor.div);
    }
  }
  add(editor) {
    if (editor.parent === this && editor.isAttachedToDOM) {
      return;
    }
    this.changeParent(editor);
    this.#uiManager.addEditor(editor);
    this.attach(editor);
    if (!editor.isAttachedToDOM) {
      const div = editor.render();
      this.div.append(div);
      editor.isAttachedToDOM = true;
    }
    editor.fixAndSetPosition();
    editor.onceAdded();
    this.#uiManager.addToAnnotationStorage(editor);
    editor._reportTelemetry(editor.telemetryInitialData);
  }
  moveEditorInDOM(editor) {
    if (!editor.isAttachedToDOM) {
      return;
    }
    const {
      activeElement
    } = document;
    if (editor.div.contains(activeElement) && !this.#editorFocusTimeoutId) {
      editor._focusEventsAllowed = false;
      this.#editorFocusTimeoutId = setTimeout(() => {
        this.#editorFocusTimeoutId = null;
        if (!editor.div.contains(document.activeElement)) {
          editor.div.addEventListener("focusin", () => {
            editor._focusEventsAllowed = true;
          }, {
            once: true,
            signal: this.#uiManager._signal
          });
          activeElement.focus();
        } else {
          editor._focusEventsAllowed = true;
        }
      }, 0);
    }
    editor._structTreeParentId = this.#accessibilityManager?.moveElementInDOM(this.div, editor.div, editor.contentDiv, true);
  }
  addOrRebuild(editor) {
    if (editor.needsToBeRebuilt()) {
      editor.parent ||= this;
      editor.rebuild();
      editor.show();
    } else {
      this.add(editor);
    }
  }
  addUndoableEditor(editor) {
    const cmd = () => editor._uiManager.rebuild(editor);
    const undo = () => {
      editor.remove();
    };
    this.addCommands({
      cmd,
      undo,
      mustExec: false
    });
  }
  getNextId() {
    return this.#uiManager.getId();
  }
  get #currentEditorType() {
    return AnnotationEditorLayer.#editorTypes.get(this.#uiManager.getMode());
  }
  combinedSignal(ac) {
    return this.#uiManager.combinedSignal(ac);
  }
  #createNewEditor(params) {
    const editorType = this.#currentEditorType;
    return editorType ? new editorType.prototype.constructor(params) : null;
  }
  canCreateNewEmptyEditor() {
    return this.#currentEditorType?.canCreateNewEmptyEditor();
  }
  pasteEditor(mode, params) {
    this.#uiManager.updateToolbar(mode);
    this.#uiManager.updateMode(mode);
    const {
      offsetX,
      offsetY
    } = this.#getCenterPoint();
    const id = this.getNextId();
    const editor = this.#createNewEditor({
      parent: this,
      id,
      x: offsetX,
      y: offsetY,
      uiManager: this.#uiManager,
      isCentered: true,
      ...params
    });
    if (editor) {
      this.add(editor);
    }
  }
  deserialize(data) {
    return AnnotationEditorLayer.#editorTypes.get(data.annotationType ?? data.annotationEditorType)?.deserialize(data, this, this.#uiManager) || null;
  }
  createAndAddNewEditor(event, isCentered, data = {}) {
    const id = this.getNextId();
    const editor = this.#createNewEditor({
      parent: this,
      id,
      x: event.offsetX,
      y: event.offsetY,
      uiManager: this.#uiManager,
      isCentered,
      ...data
    });
    if (editor) {
      this.add(editor);
    }
    return editor;
  }
  #getCenterPoint() {
    const {
      x,
      y,
      width,
      height
    } = this.div.getBoundingClientRect();
    const tlX = Math.max(0, x);
    const tlY = Math.max(0, y);
    const brX = Math.min(window.innerWidth, x + width);
    const brY = Math.min(window.innerHeight, y + height);
    const centerX = (tlX + brX) / 2 - x;
    const centerY = (tlY + brY) / 2 - y;
    const [offsetX, offsetY] = this.viewport.rotation % 180 === 0 ? [centerX, centerY] : [centerY, centerX];
    return {
      offsetX,
      offsetY
    };
  }
  addNewEditor() {
    this.createAndAddNewEditor(this.#getCenterPoint(), true);
  }
  setSelected(editor) {
    this.#uiManager.setSelected(editor);
  }
  toggleSelected(editor) {
    this.#uiManager.toggleSelected(editor);
  }
  isSelected(editor) {
    return this.#uiManager.isSelected(editor);
  }
  unselect(editor) {
    this.#uiManager.unselect(editor);
  }
  pointerup(event) {
    const {
      isMac
    } = util_FeatureTest.platform;
    if (event.button !== 0 || event.ctrlKey && isMac) {
      return;
    }
    if (event.target !== this.div) {
      return;
    }
    if (!this.#hadPointerDown) {
      return;
    }
    this.#hadPointerDown = false;
    if (!this.#allowClick) {
      this.#allowClick = true;
      return;
    }
    if (this.#uiManager.getMode() === AnnotationEditorType.STAMP) {
      this.#uiManager.unselectAll();
      return;
    }
    this.createAndAddNewEditor(event, false);
  }
  pointerdown(event) {
    if (this.#uiManager.getMode() === AnnotationEditorType.HIGHLIGHT) {
      this.enableTextSelection();
    }
    if (this.#hadPointerDown) {
      this.#hadPointerDown = false;
      return;
    }
    const {
      isMac
    } = util_FeatureTest.platform;
    if (event.button !== 0 || event.ctrlKey && isMac) {
      return;
    }
    if (event.target !== this.div) {
      return;
    }
    this.#hadPointerDown = true;
    const editor = this.#uiManager.getActive();
    this.#allowClick = !editor || editor.isEmpty();
  }
  findNewParent(editor, x, y) {
    const layer = this.#uiManager.findParent(x, y);
    if (layer === null || layer === this) {
      return false;
    }
    layer.changeParent(editor);
    return true;
  }
  destroy() {
    if (this.#uiManager.getActive()?.parent === this) {
      this.#uiManager.commitOrRemove();
      this.#uiManager.setActiveEditor(null);
    }
    if (this.#editorFocusTimeoutId) {
      clearTimeout(this.#editorFocusTimeoutId);
      this.#editorFocusTimeoutId = null;
    }
    for (const editor of this.#editors.values()) {
      this.#accessibilityManager?.removePointerInTextLayer(editor.contentDiv);
      editor.setParent(null);
      editor.isAttachedToDOM = false;
      editor.div.remove();
    }
    this.div = null;
    this.#editors.clear();
    this.#uiManager.removeLayer(this);
  }
  #cleanup() {
    this.#isCleaningUp = true;
    for (const editor of this.#editors.values()) {
      if (editor.isEmpty()) {
        editor.remove();
      }
    }
    this.#isCleaningUp = false;
  }
  render({
    viewport
  }) {
    this.viewport = viewport;
    setLayerDimensions(this.div, viewport);
    for (const editor of this.#uiManager.getEditors(this.pageIndex)) {
      this.add(editor);
      editor.rebuild();
    }
    this.updateMode();
  }
  update({
    viewport
  }) {
    this.#uiManager.commitOrRemove();
    this.#cleanup();
    const oldRotation = this.viewport.rotation;
    const rotation = viewport.rotation;
    this.viewport = viewport;
    setLayerDimensions(this.div, {
      rotation
    });
    if (oldRotation !== rotation) {
      for (const editor of this.#editors.values()) {
        editor.rotate(rotation);
      }
    }
    this.addInkEditorIfNeeded(false);
  }
  get pageDimensions() {
    const {
      pageWidth,
      pageHeight
    } = this.viewport.rawDims;
    return [pageWidth, pageHeight];
  }
  get scale() {
    return this.#uiManager.viewParameters.realScale;
  }
}

;// CONCATENATED MODULE: ./src/display/draw_layer.js


class DrawLayer {
  #parent = null;
  #id = 0;
  #mapping = new Map();
  #toUpdate = new Map();
  constructor({
    pageIndex
  }) {
    this.pageIndex = pageIndex;
  }
  setParent(parent) {
    if (!this.#parent) {
      this.#parent = parent;
      return;
    }
    if (this.#parent !== parent) {
      if (this.#mapping.size > 0) {
        for (const root of this.#mapping.values()) {
          root.remove();
          parent.append(root);
        }
      }
      this.#parent = parent;
    }
  }
  static get _svgFactory() {
    return shadow(this, "_svgFactory", new DOMSVGFactory());
  }
  static #setBox(element, {
    x = 0,
    y = 0,
    width = 1,
    height = 1
  } = {}) {
    const {
      style
    } = element;
    style.top = `${100 * y}%`;
    style.left = `${100 * x}%`;
    style.width = `${100 * width}%`;
    style.height = `${100 * height}%`;
  }
  #createSVG(box) {
    const svg = DrawLayer._svgFactory.create(1, 1, true);
    this.#parent.append(svg);
    svg.setAttribute("aria-hidden", true);
    DrawLayer.#setBox(svg, box);
    return svg;
  }
  #createClipPath(defs, pathId) {
    const clipPath = DrawLayer._svgFactory.createElement("clipPath");
    defs.append(clipPath);
    const clipPathId = `clip_${pathId}`;
    clipPath.setAttribute("id", clipPathId);
    clipPath.setAttribute("clipPathUnits", "objectBoundingBox");
    const clipPathUse = DrawLayer._svgFactory.createElement("use");
    clipPath.append(clipPathUse);
    clipPathUse.setAttribute("href", `#${pathId}`);
    clipPathUse.classList.add("clip");
    return clipPathId;
  }
  highlight(outlines, color, opacity, isPathUpdatable = false) {
    const id = this.#id++;
    const root = this.#createSVG(outlines.box);
    root.classList.add("highlight");
    if (outlines.free) {
      root.classList.add("free");
    }
    const defs = DrawLayer._svgFactory.createElement("defs");
    root.append(defs);
    const path = DrawLayer._svgFactory.createElement("path");
    defs.append(path);
    const pathId = `path_p${this.pageIndex}_${id}`;
    path.setAttribute("id", pathId);
    path.setAttribute("d", outlines.toSVGPath());
    if (isPathUpdatable) {
      this.#toUpdate.set(id, path);
    }
    const clipPathId = this.#createClipPath(defs, pathId);
    const use = DrawLayer._svgFactory.createElement("use");
    root.append(use);
    root.setAttribute("fill", color);
    root.setAttribute("fill-opacity", opacity);
    use.setAttribute("href", `#${pathId}`);
    this.#mapping.set(id, root);
    return {
      id,
      clipPathId: `url(#${clipPathId})`
    };
  }
  highlightOutline(outlines) {
    const id = this.#id++;
    const root = this.#createSVG(outlines.box);
    root.classList.add("highlightOutline");
    const defs = DrawLayer._svgFactory.createElement("defs");
    root.append(defs);
    const path = DrawLayer._svgFactory.createElement("path");
    defs.append(path);
    const pathId = `path_p${this.pageIndex}_${id}`;
    path.setAttribute("id", pathId);
    path.setAttribute("d", outlines.toSVGPath());
    path.setAttribute("vector-effect", "non-scaling-stroke");
    let maskId;
    if (outlines.free) {
      root.classList.add("free");
      const mask = DrawLayer._svgFactory.createElement("mask");
      defs.append(mask);
      maskId = `mask_p${this.pageIndex}_${id}`;
      mask.setAttribute("id", maskId);
      mask.setAttribute("maskUnits", "objectBoundingBox");
      const rect = DrawLayer._svgFactory.createElement("rect");
      mask.append(rect);
      rect.setAttribute("width", "1");
      rect.setAttribute("height", "1");
      rect.setAttribute("fill", "white");
      const use = DrawLayer._svgFactory.createElement("use");
      mask.append(use);
      use.setAttribute("href", `#${pathId}`);
      use.setAttribute("stroke", "none");
      use.setAttribute("fill", "black");
      use.setAttribute("fill-rule", "nonzero");
      use.classList.add("mask");
    }
    const use1 = DrawLayer._svgFactory.createElement("use");
    root.append(use1);
    use1.setAttribute("href", `#${pathId}`);
    if (maskId) {
      use1.setAttribute("mask", `url(#${maskId})`);
    }
    const use2 = use1.cloneNode();
    root.append(use2);
    use1.classList.add("mainOutline");
    use2.classList.add("secondaryOutline");
    this.#mapping.set(id, root);
    return id;
  }
  finalizeLine(id, line) {
    const path = this.#toUpdate.get(id);
    this.#toUpdate.delete(id);
    this.updateBox(id, line.box);
    path.setAttribute("d", line.toSVGPath());
  }
  updateLine(id, line) {
    const root = this.#mapping.get(id);
    const defs = root.firstChild;
    const path = defs.firstChild;
    path.setAttribute("d", line.toSVGPath());
  }
  removeFreeHighlight(id) {
    this.remove(id);
    this.#toUpdate.delete(id);
  }
  updatePath(id, line) {
    this.#toUpdate.get(id).setAttribute("d", line.toSVGPath());
  }
  updateBox(id, box) {
    DrawLayer.#setBox(this.#mapping.get(id), box);
  }
  show(id, visible) {
    this.#mapping.get(id).classList.toggle("hidden", !visible);
  }
  rotate(id, angle) {
    this.#mapping.get(id).setAttribute("data-main-rotation", angle);
  }
  changeColor(id, color) {
    this.#mapping.get(id).setAttribute("fill", color);
  }
  changeOpacity(id, opacity) {
    this.#mapping.get(id).setAttribute("fill-opacity", opacity);
  }
  addClass(id, className) {
    this.#mapping.get(id).classList.add(className);
  }
  removeClass(id, className) {
    this.#mapping.get(id).classList.remove(className);
  }
  remove(id) {
    if (this.#parent === null) {
      return;
    }
    this.#mapping.get(id).remove();
    this.#mapping.delete(id);
  }
  destroy() {
    this.#parent = null;
    for (const root of this.#mapping.values()) {
      root.remove();
    }
    this.#mapping.clear();
  }
}

;// CONCATENATED MODULE: ./src/pdf.js












const pdfjsVersion = "4.6.60";
const pdfjsBuild = "10a846417";

var __webpack_exports__AbortException = __webpack_exports__.AbortException;
var __webpack_exports__AnnotationEditorLayer = __webpack_exports__.AnnotationEditorLayer;
var __webpack_exports__AnnotationEditorParamsType = __webpack_exports__.AnnotationEditorParamsType;
var __webpack_exports__AnnotationEditorType = __webpack_exports__.AnnotationEditorType;
var __webpack_exports__AnnotationEditorUIManager = __webpack_exports__.AnnotationEditorUIManager;
var __webpack_exports__AnnotationLayer = __webpack_exports__.AnnotationLayer;
var __webpack_exports__AnnotationMode = __webpack_exports__.AnnotationMode;
var __webpack_exports__CMapCompressionType = __webpack_exports__.CMapCompressionType;
var __webpack_exports__ColorPicker = __webpack_exports__.ColorPicker;
var __webpack_exports__DOMSVGFactory = __webpack_exports__.DOMSVGFactory;
var __webpack_exports__DrawLayer = __webpack_exports__.DrawLayer;
var __webpack_exports__FeatureTest = __webpack_exports__.FeatureTest;
var __webpack_exports__GlobalWorkerOptions = __webpack_exports__.GlobalWorkerOptions;
var __webpack_exports__ImageKind = __webpack_exports__.ImageKind;
var __webpack_exports__InvalidPDFException = __webpack_exports__.InvalidPDFException;
var __webpack_exports__MissingPDFException = __webpack_exports__.MissingPDFException;
var __webpack_exports__OPS = __webpack_exports__.OPS;
var __webpack_exports__PDFDataRangeTransport = __webpack_exports__.PDFDataRangeTransport;
var __webpack_exports__PDFDateString = __webpack_exports__.PDFDateString;
var __webpack_exports__PDFWorker = __webpack_exports__.PDFWorker;
var __webpack_exports__PasswordResponses = __webpack_exports__.PasswordResponses;
var __webpack_exports__PermissionFlag = __webpack_exports__.PermissionFlag;
var __webpack_exports__PixelsPerInch = __webpack_exports__.PixelsPerInch;
var __webpack_exports__RenderingCancelledException = __webpack_exports__.RenderingCancelledException;
var __webpack_exports__TextLayer = __webpack_exports__.TextLayer;
var __webpack_exports__UnexpectedResponseException = __webpack_exports__.UnexpectedResponseException;
var __webpack_exports__Util = __webpack_exports__.Util;
var __webpack_exports__VerbosityLevel = __webpack_exports__.VerbosityLevel;
var __webpack_exports__XfaLayer = __webpack_exports__.XfaLayer;
var __webpack_exports__build = __webpack_exports__.build;
var __webpack_exports__createValidAbsoluteUrl = __webpack_exports__.createValidAbsoluteUrl;
var __webpack_exports__fetchData = __webpack_exports__.fetchData;
var __webpack_exports__getDocument = __webpack_exports__.getDocument;
var __webpack_exports__getFilenameFromUrl = __webpack_exports__.getFilenameFromUrl;
var __webpack_exports__getPdfFilenameFromUrl = __webpack_exports__.getPdfFilenameFromUrl;
var __webpack_exports__getXfaPageViewport = __webpack_exports__.getXfaPageViewport;
var __webpack_exports__isDataScheme = __webpack_exports__.isDataScheme;
var __webpack_exports__isPdfFile = __webpack_exports__.isPdfFile;
var __webpack_exports__noContextMenu = __webpack_exports__.noContextMenu;
var __webpack_exports__normalizeUnicode = __webpack_exports__.normalizeUnicode;
var __webpack_exports__setLayerDimensions = __webpack_exports__.setLayerDimensions;
var __webpack_exports__shadow = __webpack_exports__.shadow;
var __webpack_exports__version = __webpack_exports__.version;
export { __webpack_exports__AbortException as AbortException, __webpack_exports__AnnotationEditorLayer as AnnotationEditorLayer, __webpack_exports__AnnotationEditorParamsType as AnnotationEditorParamsType, __webpack_exports__AnnotationEditorType as AnnotationEditorType, __webpack_exports__AnnotationEditorUIManager as AnnotationEditorUIManager, __webpack_exports__AnnotationLayer as AnnotationLayer, __webpack_exports__AnnotationMode as AnnotationMode, __webpack_exports__CMapCompressionType as CMapCompressionType, __webpack_exports__ColorPicker as ColorPicker, __webpack_exports__DOMSVGFactory as DOMSVGFactory, __webpack_exports__DrawLayer as DrawLayer, __webpack_exports__FeatureTest as FeatureTest, __webpack_exports__GlobalWorkerOptions as GlobalWorkerOptions, __webpack_exports__ImageKind as ImageKind, __webpack_exports__InvalidPDFException as InvalidPDFException, __webpack_exports__MissingPDFException as MissingPDFException, __webpack_exports__OPS as OPS, __webpack_exports__PDFDataRangeTransport as PDFDataRangeTransport, __webpack_exports__PDFDateString as PDFDateString, __webpack_exports__PDFWorker as PDFWorker, __webpack_exports__PasswordResponses as PasswordResponses, __webpack_exports__PermissionFlag as PermissionFlag, __webpack_exports__PixelsPerInch as PixelsPerInch, __webpack_exports__RenderingCancelledException as RenderingCancelledException, __webpack_exports__TextLayer as TextLayer, __webpack_exports__UnexpectedResponseException as UnexpectedResponseException, __webpack_exports__Util as Util, __webpack_exports__VerbosityLevel as VerbosityLevel, __webpack_exports__XfaLayer as XfaLayer, __webpack_exports__build as build, __webpack_exports__createValidAbsoluteUrl as createValidAbsoluteUrl, __webpack_exports__fetchData as fetchData, __webpack_exports__getDocument as getDocument, __webpack_exports__getFilenameFromUrl as getFilenameFromUrl, __webpack_exports__getPdfFilenameFromUrl as getPdfFilenameFromUrl, __webpack_exports__getXfaPageViewport as getXfaPageViewport, __webpack_exports__isDataScheme as isDataScheme, __webpack_exports__isPdfFile as isPdfFile, __webpack_exports__noContextMenu as noContextMenu, __webpack_exports__normalizeUnicode as normalizeUnicode, __webpack_exports__setLayerDimensions as setLayerDimensions, __webpack_exports__shadow as shadow, __webpack_exports__version as version };
PK
!<�ۥ���7chrome/pdfjs/content/build/pdf.sandbox.external.sys.mjs/* Copyright 2024 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

export class SandboxSupportBase {
  constructor(win) {
    this.win = win;
    this.timeoutIds = new Map();
    this.commFun = null;
  }
  destroy() {
    this.commFun = null;
    for (const id of this.timeoutIds.values()) {
      this.win.clearTimeout(id);
    }
    this.timeoutIds = null;
  }
  exportValueToSandbox(val) {
    throw new Error("Not implemented");
  }
  importValueFromSandbox(val) {
    throw new Error("Not implemented");
  }
  createErrorForSandbox(errorMessage) {
    throw new Error("Not implemented");
  }
  callSandboxFunction(name, args) {
    if (!this.commFun) {
      return;
    }
    try {
      args = this.exportValueToSandbox(args);
      this.commFun(name, args);
    } catch (e) {
      this.win.console.error(e);
    }
  }
  createSandboxExternals() {
    const externals = {
      setTimeout: (callbackId, nMilliseconds) => {
        if (typeof callbackId !== "number" || typeof nMilliseconds !== "number") {
          return;
        }
        if (callbackId === 0) {
          this.win.clearTimeout(this.timeoutIds.get(callbackId));
        }
        const id = this.win.setTimeout(() => {
          this.timeoutIds.delete(callbackId);
          this.callSandboxFunction("timeoutCb", {
            callbackId,
            interval: false
          });
        }, nMilliseconds);
        this.timeoutIds.set(callbackId, id);
      },
      clearTimeout: callbackId => {
        this.win.clearTimeout(this.timeoutIds.get(callbackId));
        this.timeoutIds.delete(callbackId);
      },
      setInterval: (callbackId, nMilliseconds) => {
        if (typeof callbackId !== "number" || typeof nMilliseconds !== "number") {
          return;
        }
        const id = this.win.setInterval(() => {
          this.callSandboxFunction("timeoutCb", {
            callbackId,
            interval: true
          });
        }, nMilliseconds);
        this.timeoutIds.set(callbackId, id);
      },
      clearInterval: callbackId => {
        this.win.clearInterval(this.timeoutIds.get(callbackId));
        this.timeoutIds.delete(callbackId);
      },
      alert: cMsg => {
        if (typeof cMsg !== "string") {
          return;
        }
        this.win.alert(cMsg);
      },
      confirm: cMsg => {
        if (typeof cMsg !== "string") {
          return false;
        }
        return this.win.confirm(cMsg);
      },
      prompt: (cQuestion, cDefault) => {
        if (typeof cQuestion !== "string" || typeof cDefault !== "string") {
          return null;
        }
        return this.win.prompt(cQuestion, cDefault);
      },
      parseURL: cUrl => {
        const url = new this.win.URL(cUrl);
        const props = ["hash", "host", "hostname", "href", "origin", "password", "pathname", "port", "protocol", "search", "searchParams", "username"];
        return Object.fromEntries(props.map(name => [name, url[name].toString()]));
      },
      send: data => {
        if (!data) {
          return;
        }
        const event = new this.win.CustomEvent("updatefromsandbox", {
          detail: this.importValueFromSandbox(data)
        });
        this.win.dispatchEvent(event);
      }
    };
    Object.setPrototypeOf(externals, null);
    return (name, args) => {
      try {
        const result = externals[name](...args);
        return this.exportValueToSandbox(result);
      } catch (error) {
        throw this.createErrorForSandbox(error?.toString() ?? "");
      }
    };
  }
}PK
!<lv��L�L�,chrome/pdfjs/content/build/pdf.scripting.mjs/**
 * @licstart The following is the entire license notice for the
 * JavaScript code in this page
 *
 * Copyright 2024 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @licend The above is the entire license notice for the
 * JavaScript code in this page
 */

var __webpack_exports__ = {};

;// CONCATENATED MODULE: ./src/scripting_api/constants.js
const Border = Object.freeze({
  s: "solid",
  d: "dashed",
  b: "beveled",
  i: "inset",
  u: "underline"
});
const Cursor = Object.freeze({
  visible: 0,
  hidden: 1,
  delay: 2
});
const Display = Object.freeze({
  visible: 0,
  hidden: 1,
  noPrint: 2,
  noView: 3
});
const Font = Object.freeze({
  Times: "Times-Roman",
  TimesB: "Times-Bold",
  TimesI: "Times-Italic",
  TimesBI: "Times-BoldItalic",
  Helv: "Helvetica",
  HelvB: "Helvetica-Bold",
  HelvI: "Helvetica-Oblique",
  HelvBI: "Helvetica-BoldOblique",
  Cour: "Courier",
  CourB: "Courier-Bold",
  CourI: "Courier-Oblique",
  CourBI: "Courier-BoldOblique",
  Symbol: "Symbol",
  ZapfD: "ZapfDingbats",
  KaGo: "HeiseiKakuGo-W5-UniJIS-UCS2-H",
  KaMi: "HeiseiMin-W3-UniJIS-UCS2-H"
});
const Highlight = Object.freeze({
  n: "none",
  i: "invert",
  p: "push",
  o: "outline"
});
const Position = Object.freeze({
  textOnly: 0,
  iconOnly: 1,
  iconTextV: 2,
  textIconV: 3,
  iconTextH: 4,
  textIconH: 5,
  overlay: 6
});
const ScaleHow = Object.freeze({
  proportional: 0,
  anamorphic: 1
});
const ScaleWhen = Object.freeze({
  always: 0,
  never: 1,
  tooBig: 2,
  tooSmall: 3
});
const Style = Object.freeze({
  ch: "check",
  cr: "cross",
  di: "diamond",
  ci: "circle",
  st: "star",
  sq: "square"
});
const Trans = Object.freeze({
  blindsH: "BlindsHorizontal",
  blindsV: "BlindsVertical",
  boxI: "BoxIn",
  boxO: "BoxOut",
  dissolve: "Dissolve",
  glitterD: "GlitterDown",
  glitterR: "GlitterRight",
  glitterRD: "GlitterRightDown",
  random: "Random",
  replace: "Replace",
  splitHI: "SplitHorizontalIn",
  splitHO: "SplitHorizontalOut",
  splitVI: "SplitVerticalIn",
  splitVO: "SplitVerticalOut",
  wipeD: "WipeDown",
  wipeL: "WipeLeft",
  wipeR: "WipeRight",
  wipeU: "WipeUp"
});
const ZoomType = Object.freeze({
  none: "NoVary",
  fitP: "FitPage",
  fitW: "FitWidth",
  fitH: "FitHeight",
  fitV: "FitVisibleWidth",
  pref: "Preferred",
  refW: "ReflowWidth"
});
const GlobalConstants = Object.freeze({
  IDS_GREATER_THAN: "Invalid value: must be greater than or equal to % s.",
  IDS_GT_AND_LT: "Invalid value: must be greater than or equal to % s " + "and less than or equal to % s.",
  IDS_LESS_THAN: "Invalid value: must be less than or equal to % s.",
  IDS_INVALID_MONTH: "** Invalid **",
  IDS_INVALID_DATE: "Invalid date / time: please ensure that the date / time exists. Field",
  IDS_INVALID_DATE2: " should match format ",
  IDS_INVALID_VALUE: "The value entered does not match the format of the field",
  IDS_AM: "am",
  IDS_PM: "pm",
  IDS_MONTH_INFO: "January[1] February[2] March[3] April[4] May[5] " + "June[6] July[7] August[8] September[9] October[10] " + "November[11] December[12] Sept[9] Jan[1] Feb[2] Mar[3] " + "Apr[4] Jun[6] Jul[7] Aug[8] Sep[9] Oct[10] Nov[11] Dec[12]",
  IDS_STARTUP_CONSOLE_MSG: "** ^ _ ^ **",
  RE_NUMBER_ENTRY_DOT_SEP: ["[+-]?\\d*\\.?\\d*"],
  RE_NUMBER_COMMIT_DOT_SEP: ["[+-]?\\d+(\\.\\d+)?", "[+-]?\\.\\d+", "[+-]?\\d+\\."],
  RE_NUMBER_ENTRY_COMMA_SEP: ["[+-]?\\d*,?\\d*"],
  RE_NUMBER_COMMIT_COMMA_SEP: ["[+-]?\\d+([.,]\\d+)?", "[+-]?[.,]\\d+", "[+-]?\\d+[.,]"],
  RE_ZIP_ENTRY: ["\\d{0,5}"],
  RE_ZIP_COMMIT: ["\\d{5}"],
  RE_ZIP4_ENTRY: ["\\d{0,5}(\\.|[- ])?\\d{0,4}"],
  RE_ZIP4_COMMIT: ["\\d{5}(\\.|[- ])?\\d{4}"],
  RE_PHONE_ENTRY: ["\\d{0,3}(\\.|[- ])?\\d{0,3}(\\.|[- ])?\\d{0,4}", "\\(\\d{0,3}", "\\(\\d{0,3}\\)(\\.|[- ])?\\d{0,3}(\\.|[- ])?\\d{0,4}", "\\(\\d{0,3}(\\.|[- ])?\\d{0,3}(\\.|[- ])?\\d{0,4}", "\\d{0,3}\\)(\\.|[- ])?\\d{0,3}(\\.|[- ])?\\d{0,4}", "011(\\.|[- \\d])*"],
  RE_PHONE_COMMIT: ["\\d{3}(\\.|[- ])?\\d{4}", "\\d{3}(\\.|[- ])?\\d{3}(\\.|[- ])?\\d{4}", "\\(\\d{3}\\)(\\.|[- ])?\\d{3}(\\.|[- ])?\\d{4}", "011(\\.|[- \\d])*"],
  RE_SSN_ENTRY: ["\\d{0,3}(\\.|[- ])?\\d{0,2}(\\.|[- ])?\\d{0,4}"],
  RE_SSN_COMMIT: ["\\d{3}(\\.|[- ])?\\d{2}(\\.|[- ])?\\d{4}"]
});

;// CONCATENATED MODULE: ./src/scripting_api/common.js
const FieldType = {
  none: 0,
  number: 1,
  percent: 2,
  date: 3,
  time: 4
};
function createActionsMap(actions) {
  const actionsMap = new Map();
  if (actions) {
    for (const [eventType, actionsForEvent] of Object.entries(actions)) {
      actionsMap.set(eventType, actionsForEvent);
    }
  }
  return actionsMap;
}
function getFieldType(actions) {
  let format = actions.get("Format");
  if (!format) {
    return FieldType.none;
  }
  format = format[0];
  format = format.trim();
  if (format.startsWith("AFNumber_")) {
    return FieldType.number;
  }
  if (format.startsWith("AFPercent_")) {
    return FieldType.percent;
  }
  if (format.startsWith("AFDate_")) {
    return FieldType.date;
  }
  if (format.startsWith("AFTime_")) {
    return FieldType.time;
  }
  return FieldType.none;
}

;// CONCATENATED MODULE: ./src/shared/scripting_utils.js
function makeColorComp(n) {
  return Math.floor(Math.max(0, Math.min(1, n)) * 255).toString(16).padStart(2, "0");
}
function scaleAndClamp(x) {
  return Math.max(0, Math.min(255, 255 * x));
}
class ColorConverters {
  static CMYK_G([c, y, m, k]) {
    return ["G", 1 - Math.min(1, 0.3 * c + 0.59 * m + 0.11 * y + k)];
  }
  static G_CMYK([g]) {
    return ["CMYK", 0, 0, 0, 1 - g];
  }
  static G_RGB([g]) {
    return ["RGB", g, g, g];
  }
  static G_rgb([g]) {
    g = scaleAndClamp(g);
    return [g, g, g];
  }
  static G_HTML([g]) {
    const G = makeColorComp(g);
    return `#${G}${G}${G}`;
  }
  static RGB_G([r, g, b]) {
    return ["G", 0.3 * r + 0.59 * g + 0.11 * b];
  }
  static RGB_rgb(color) {
    return color.map(scaleAndClamp);
  }
  static RGB_HTML(color) {
    return `#${color.map(makeColorComp).join("")}`;
  }
  static T_HTML() {
    return "#00000000";
  }
  static T_rgb() {
    return [null];
  }
  static CMYK_RGB([c, y, m, k]) {
    return ["RGB", 1 - Math.min(1, c + k), 1 - Math.min(1, m + k), 1 - Math.min(1, y + k)];
  }
  static CMYK_rgb([c, y, m, k]) {
    return [scaleAndClamp(1 - Math.min(1, c + k)), scaleAndClamp(1 - Math.min(1, m + k)), scaleAndClamp(1 - Math.min(1, y + k))];
  }
  static CMYK_HTML(components) {
    const rgb = this.CMYK_RGB(components).slice(1);
    return this.RGB_HTML(rgb);
  }
  static RGB_CMYK([r, g, b]) {
    const c = 1 - r;
    const m = 1 - g;
    const y = 1 - b;
    const k = Math.min(c, m, y);
    return ["CMYK", c, m, y, k];
  }
}

;// CONCATENATED MODULE: ./src/scripting_api/pdf_object.js
class PDFObject {
  constructor(data) {
    this._expandos = Object.create(null);
    this._send = data.send || null;
    this._id = data.id || null;
  }
}

;// CONCATENATED MODULE: ./src/scripting_api/color.js


class Color extends PDFObject {
  constructor() {
    super({});
    this.transparent = ["T"];
    this.black = ["G", 0];
    this.white = ["G", 1];
    this.red = ["RGB", 1, 0, 0];
    this.green = ["RGB", 0, 1, 0];
    this.blue = ["RGB", 0, 0, 1];
    this.cyan = ["CMYK", 1, 0, 0, 0];
    this.magenta = ["CMYK", 0, 1, 0, 0];
    this.yellow = ["CMYK", 0, 0, 1, 0];
    this.dkGray = ["G", 0.25];
    this.gray = ["G", 0.5];
    this.ltGray = ["G", 0.75];
  }
  static _isValidSpace(cColorSpace) {
    return typeof cColorSpace === "string" && (cColorSpace === "T" || cColorSpace === "G" || cColorSpace === "RGB" || cColorSpace === "CMYK");
  }
  static _isValidColor(colorArray) {
    if (!Array.isArray(colorArray) || colorArray.length === 0) {
      return false;
    }
    const space = colorArray[0];
    if (!Color._isValidSpace(space)) {
      return false;
    }
    switch (space) {
      case "T":
        if (colorArray.length !== 1) {
          return false;
        }
        break;
      case "G":
        if (colorArray.length !== 2) {
          return false;
        }
        break;
      case "RGB":
        if (colorArray.length !== 4) {
          return false;
        }
        break;
      case "CMYK":
        if (colorArray.length !== 5) {
          return false;
        }
        break;
      default:
        return false;
    }
    return colorArray.slice(1).every(c => typeof c === "number" && c >= 0 && c <= 1);
  }
  static _getCorrectColor(colorArray) {
    return Color._isValidColor(colorArray) ? colorArray : ["G", 0];
  }
  convert(colorArray, cColorSpace) {
    if (!Color._isValidSpace(cColorSpace)) {
      return this.black;
    }
    if (cColorSpace === "T") {
      return ["T"];
    }
    colorArray = Color._getCorrectColor(colorArray);
    if (colorArray[0] === cColorSpace) {
      return colorArray;
    }
    if (colorArray[0] === "T") {
      return this.convert(this.black, cColorSpace);
    }
    return ColorConverters[`${colorArray[0]}_${cColorSpace}`](colorArray.slice(1));
  }
  equal(colorArray1, colorArray2) {
    colorArray1 = Color._getCorrectColor(colorArray1);
    colorArray2 = Color._getCorrectColor(colorArray2);
    if (colorArray1[0] === "T" || colorArray2[0] === "T") {
      return colorArray1[0] === "T" && colorArray2[0] === "T";
    }
    if (colorArray1[0] !== colorArray2[0]) {
      colorArray2 = this.convert(colorArray2, colorArray1[0]);
    }
    return colorArray1.slice(1).every((c, i) => c === colorArray2[i + 1]);
  }
}

;// CONCATENATED MODULE: ./src/scripting_api/field.js



class Field extends PDFObject {
  constructor(data) {
    super(data);
    this.alignment = data.alignment || "left";
    this.borderStyle = data.borderStyle || "";
    this.buttonAlignX = data.buttonAlignX || 50;
    this.buttonAlignY = data.buttonAlignY || 50;
    this.buttonFitBounds = data.buttonFitBounds;
    this.buttonPosition = data.buttonPosition;
    this.buttonScaleHow = data.buttonScaleHow;
    this.ButtonScaleWhen = data.buttonScaleWhen;
    this.calcOrderIndex = data.calcOrderIndex;
    this.comb = data.comb;
    this.commitOnSelChange = data.commitOnSelChange;
    this.currentValueIndices = data.currentValueIndices;
    this.defaultStyle = data.defaultStyle;
    this.defaultValue = data.defaultValue;
    this.doNotScroll = data.doNotScroll;
    this.doNotSpellCheck = data.doNotSpellCheck;
    this.delay = data.delay;
    this.display = data.display;
    this.doc = data.doc.wrapped;
    this.editable = data.editable;
    this.exportValues = data.exportValues;
    this.fileSelect = data.fileSelect;
    this.hidden = data.hidden;
    this.highlight = data.highlight;
    this.lineWidth = data.lineWidth;
    this.multiline = data.multiline;
    this.multipleSelection = !!data.multipleSelection;
    this.name = data.name;
    this.password = data.password;
    this.print = data.print;
    this.radiosInUnison = data.radiosInUnison;
    this.readonly = data.readonly;
    this.rect = data.rect;
    this.required = data.required;
    this.richText = data.richText;
    this.richValue = data.richValue;
    this.style = data.style;
    this.submitName = data.submitName;
    this.textFont = data.textFont;
    this.textSize = data.textSize;
    this.type = data.type;
    this.userName = data.userName;
    this._actions = createActionsMap(data.actions);
    this._browseForFileToSubmit = data.browseForFileToSubmit || null;
    this._buttonCaption = null;
    this._buttonIcon = null;
    this._charLimit = data.charLimit;
    this._children = null;
    this._currentValueIndices = data.currentValueIndices || 0;
    this._document = data.doc;
    this._fieldPath = data.fieldPath;
    this._fillColor = data.fillColor || ["T"];
    this._isChoice = Array.isArray(data.items);
    this._items = data.items || [];
    this._hasValue = data.hasOwnProperty("value");
    this._page = data.page || 0;
    this._strokeColor = data.strokeColor || ["G", 0];
    this._textColor = data.textColor || ["G", 0];
    this._value = null;
    this._kidIds = data.kidIds || null;
    this._fieldType = getFieldType(this._actions);
    this._siblings = data.siblings || null;
    this._rotation = data.rotation || 0;
    this._globalEval = data.globalEval;
    this._appObjects = data.appObjects;
    this.value = data.value || "";
  }
  get currentValueIndices() {
    if (!this._isChoice) {
      return 0;
    }
    return this._currentValueIndices;
  }
  set currentValueIndices(indices) {
    if (!this._isChoice) {
      return;
    }
    if (!Array.isArray(indices)) {
      indices = [indices];
    }
    if (!indices.every(i => typeof i === "number" && Number.isInteger(i) && i >= 0 && i < this.numItems)) {
      return;
    }
    indices.sort();
    if (this.multipleSelection) {
      this._currentValueIndices = indices;
      this._value = [];
      indices.forEach(i => {
        this._value.push(this._items[i].displayValue);
      });
    } else if (indices.length > 0) {
      indices = indices.splice(1, indices.length - 1);
      this._currentValueIndices = indices[0];
      this._value = this._items[this._currentValueIndices];
    }
    this._send({
      id: this._id,
      indices
    });
  }
  get fillColor() {
    return this._fillColor;
  }
  set fillColor(color) {
    if (Color._isValidColor(color)) {
      this._fillColor = color;
    }
  }
  get bgColor() {
    return this.fillColor;
  }
  set bgColor(color) {
    this.fillColor = color;
  }
  get charLimit() {
    return this._charLimit;
  }
  set charLimit(limit) {
    if (typeof limit !== "number") {
      throw new Error("Invalid argument value");
    }
    this._charLimit = Math.max(0, Math.floor(limit));
  }
  get numItems() {
    if (!this._isChoice) {
      throw new Error("Not a choice widget");
    }
    return this._items.length;
  }
  set numItems(_) {
    throw new Error("field.numItems is read-only");
  }
  get strokeColor() {
    return this._strokeColor;
  }
  set strokeColor(color) {
    if (Color._isValidColor(color)) {
      this._strokeColor = color;
    }
  }
  get borderColor() {
    return this.strokeColor;
  }
  set borderColor(color) {
    this.strokeColor = color;
  }
  get page() {
    return this._page;
  }
  set page(_) {
    throw new Error("field.page is read-only");
  }
  get rotation() {
    return this._rotation;
  }
  set rotation(angle) {
    angle = Math.floor(angle);
    if (angle % 90 !== 0) {
      throw new Error("Invalid rotation: must be a multiple of 90");
    }
    angle %= 360;
    if (angle < 0) {
      angle += 360;
    }
    this._rotation = angle;
  }
  get textColor() {
    return this._textColor;
  }
  set textColor(color) {
    if (Color._isValidColor(color)) {
      this._textColor = color;
    }
  }
  get fgColor() {
    return this.textColor;
  }
  set fgColor(color) {
    this.textColor = color;
  }
  get value() {
    return this._value;
  }
  set value(value) {
    if (this._isChoice) {
      this._setChoiceValue(value);
      return;
    }
    if (value === "" || typeof value !== "string" || this._fieldType >= FieldType.date) {
      this._originalValue = undefined;
      this._value = value;
      return;
    }
    this._originalValue = value;
    const _value = value.trim().replace(",", ".");
    this._value = !isNaN(_value) ? parseFloat(_value) : value;
  }
  _getValue() {
    return this._originalValue ?? this.value;
  }
  _setChoiceValue(value) {
    if (this.multipleSelection) {
      if (!Array.isArray(value)) {
        value = [value];
      }
      const values = new Set(value);
      if (Array.isArray(this._currentValueIndices)) {
        this._currentValueIndices.length = 0;
        this._value.length = 0;
      } else {
        this._currentValueIndices = [];
        this._value = [];
      }
      this._items.forEach((item, i) => {
        if (values.has(item.exportValue)) {
          this._currentValueIndices.push(i);
          this._value.push(item.exportValue);
        }
      });
    } else {
      if (Array.isArray(value)) {
        value = value[0];
      }
      const index = this._items.findIndex(({
        exportValue
      }) => value === exportValue);
      if (index !== -1) {
        this._currentValueIndices = index;
        this._value = this._items[index].exportValue;
      }
    }
  }
  get valueAsString() {
    return (this._value ?? "").toString();
  }
  set valueAsString(_) {}
  browseForFileToSubmit() {
    if (this._browseForFileToSubmit) {
      this._browseForFileToSubmit();
    }
  }
  buttonGetCaption(nFace = 0) {
    if (this._buttonCaption) {
      return this._buttonCaption[nFace];
    }
    return "";
  }
  buttonGetIcon(nFace = 0) {
    if (this._buttonIcon) {
      return this._buttonIcon[nFace];
    }
    return null;
  }
  buttonImportIcon(cPath = null, nPave = 0) {}
  buttonSetCaption(cCaption, nFace = 0) {
    if (!this._buttonCaption) {
      this._buttonCaption = ["", "", ""];
    }
    this._buttonCaption[nFace] = cCaption;
  }
  buttonSetIcon(oIcon, nFace = 0) {
    if (!this._buttonIcon) {
      this._buttonIcon = [null, null, null];
    }
    this._buttonIcon[nFace] = oIcon;
  }
  checkThisBox(nWidget, bCheckIt = true) {}
  clearItems() {
    if (!this._isChoice) {
      throw new Error("Not a choice widget");
    }
    this._items = [];
    this._send({
      id: this._id,
      clear: null
    });
  }
  deleteItemAt(nIdx = null) {
    if (!this._isChoice) {
      throw new Error("Not a choice widget");
    }
    if (!this.numItems) {
      return;
    }
    if (nIdx === null) {
      nIdx = Array.isArray(this._currentValueIndices) ? this._currentValueIndices[0] : this._currentValueIndices;
      nIdx ||= 0;
    }
    if (nIdx < 0 || nIdx >= this.numItems) {
      nIdx = this.numItems - 1;
    }
    this._items.splice(nIdx, 1);
    if (Array.isArray(this._currentValueIndices)) {
      let index = this._currentValueIndices.findIndex(i => i >= nIdx);
      if (index !== -1) {
        if (this._currentValueIndices[index] === nIdx) {
          this._currentValueIndices.splice(index, 1);
        }
        for (const ii = this._currentValueIndices.length; index < ii; index++) {
          --this._currentValueIndices[index];
        }
      }
    } else if (this._currentValueIndices === nIdx) {
      this._currentValueIndices = this.numItems > 0 ? 0 : -1;
    } else if (this._currentValueIndices > nIdx) {
      --this._currentValueIndices;
    }
    this._send({
      id: this._id,
      remove: nIdx
    });
  }
  getItemAt(nIdx = -1, bExportValue = false) {
    if (!this._isChoice) {
      throw new Error("Not a choice widget");
    }
    if (nIdx < 0 || nIdx >= this.numItems) {
      nIdx = this.numItems - 1;
    }
    const item = this._items[nIdx];
    return bExportValue ? item.exportValue : item.displayValue;
  }
  getArray() {
    if (this._kidIds) {
      const array = [];
      const fillArrayWithKids = kidIds => {
        for (const id of kidIds) {
          const obj = this._appObjects[id];
          if (!obj) {
            continue;
          }
          if (obj.obj._hasValue) {
            array.push(obj.wrapped);
          }
          if (obj.obj._kidIds) {
            fillArrayWithKids(obj.obj._kidIds);
          }
        }
      };
      fillArrayWithKids(this._kidIds);
      return array;
    }
    if (this._children === null) {
      this._children = this._document.obj._getTerminalChildren(this._fieldPath);
    }
    return this._children;
  }
  getLock() {
    return undefined;
  }
  isBoxChecked(nWidget) {
    return false;
  }
  isDefaultChecked(nWidget) {
    return false;
  }
  insertItemAt(cName, cExport = undefined, nIdx = 0) {
    if (!this._isChoice) {
      throw new Error("Not a choice widget");
    }
    if (!cName) {
      return;
    }
    if (nIdx < 0 || nIdx > this.numItems) {
      nIdx = this.numItems;
    }
    if (this._items.some(({
      displayValue
    }) => displayValue === cName)) {
      return;
    }
    if (cExport === undefined) {
      cExport = cName;
    }
    const data = {
      displayValue: cName,
      exportValue: cExport
    };
    this._items.splice(nIdx, 0, data);
    if (Array.isArray(this._currentValueIndices)) {
      let index = this._currentValueIndices.findIndex(i => i >= nIdx);
      if (index !== -1) {
        for (const ii = this._currentValueIndices.length; index < ii; index++) {
          ++this._currentValueIndices[index];
        }
      }
    } else if (this._currentValueIndices >= nIdx) {
      ++this._currentValueIndices;
    }
    this._send({
      id: this._id,
      insert: {
        index: nIdx,
        ...data
      }
    });
  }
  setAction(cTrigger, cScript) {
    if (typeof cTrigger !== "string" || typeof cScript !== "string") {
      return;
    }
    if (!(cTrigger in this._actions)) {
      this._actions[cTrigger] = [];
    }
    this._actions[cTrigger].push(cScript);
  }
  setFocus() {
    this._send({
      id: this._id,
      focus: true
    });
  }
  setItems(oArray) {
    if (!this._isChoice) {
      throw new Error("Not a choice widget");
    }
    this._items.length = 0;
    for (const element of oArray) {
      let displayValue, exportValue;
      if (Array.isArray(element)) {
        displayValue = element[0]?.toString() || "";
        exportValue = element[1]?.toString() || "";
      } else {
        displayValue = exportValue = element?.toString() || "";
      }
      this._items.push({
        displayValue,
        exportValue
      });
    }
    this._currentValueIndices = 0;
    this._send({
      id: this._id,
      items: this._items
    });
  }
  setLock() {}
  signatureGetModifications() {}
  signatureGetSeedValue() {}
  signatureInfo() {}
  signatureSetSeedValue() {}
  signatureSign() {}
  signatureValidate() {}
  _isButton() {
    return false;
  }
  _reset() {
    this.value = this.defaultValue;
  }
  _runActions(event) {
    const eventName = event.name;
    if (!this._actions.has(eventName)) {
      return false;
    }
    const actions = this._actions.get(eventName);
    try {
      for (const action of actions) {
        this._globalEval(action);
      }
    } catch (error) {
      event.rc = false;
      throw error;
    }
    return true;
  }
}
class RadioButtonField extends Field {
  constructor(otherButtons, data) {
    super(data);
    this.exportValues = [this.exportValues];
    this._radioIds = [this._id];
    this._radioActions = [this._actions];
    for (const radioData of otherButtons) {
      this.exportValues.push(radioData.exportValues);
      this._radioIds.push(radioData.id);
      this._radioActions.push(createActionsMap(radioData.actions));
      if (this._value === radioData.exportValues) {
        this._id = radioData.id;
      }
    }
    this._hasBeenInitialized = true;
    this._value = data.value || "";
  }
  get value() {
    return this._value;
  }
  set value(value) {
    if (!this._hasBeenInitialized) {
      return;
    }
    if (value === null || value === undefined) {
      this._value = "";
    }
    const i = this.exportValues.indexOf(value);
    if (0 <= i && i < this._radioIds.length) {
      this._id = this._radioIds[i];
      this._value = value;
    } else if (value === "Off" && this._radioIds.length === 2) {
      const nextI = (1 + this._radioIds.indexOf(this._id)) % 2;
      this._id = this._radioIds[nextI];
      this._value = this.exportValues[nextI];
    }
  }
  checkThisBox(nWidget, bCheckIt = true) {
    if (nWidget < 0 || nWidget >= this._radioIds.length || !bCheckIt) {
      return;
    }
    this._id = this._radioIds[nWidget];
    this._value = this.exportValues[nWidget];
    this._send({
      id: this._id,
      value: this._value
    });
  }
  isBoxChecked(nWidget) {
    return nWidget >= 0 && nWidget < this._radioIds.length && this._id === this._radioIds[nWidget];
  }
  isDefaultChecked(nWidget) {
    return nWidget >= 0 && nWidget < this.exportValues.length && this.defaultValue === this.exportValues[nWidget];
  }
  _getExportValue(state) {
    const i = this._radioIds.indexOf(this._id);
    return this.exportValues[i];
  }
  _runActions(event) {
    const i = this._radioIds.indexOf(this._id);
    this._actions = this._radioActions[i];
    return super._runActions(event);
  }
  _isButton() {
    return true;
  }
}
class CheckboxField extends RadioButtonField {
  get value() {
    return this._value;
  }
  set value(value) {
    if (!value || value === "Off") {
      this._value = "Off";
    } else {
      super.value = value;
    }
  }
  _getExportValue(state) {
    return state ? super._getExportValue(state) : "Off";
  }
  isBoxChecked(nWidget) {
    if (this._value === "Off") {
      return false;
    }
    return super.isBoxChecked(nWidget);
  }
  isDefaultChecked(nWidget) {
    if (this.defaultValue === "Off") {
      return this._value === "Off";
    }
    return super.isDefaultChecked(nWidget);
  }
  checkThisBox(nWidget, bCheckIt = true) {
    if (nWidget < 0 || nWidget >= this._radioIds.length) {
      return;
    }
    this._id = this._radioIds[nWidget];
    this._value = bCheckIt ? this.exportValues[nWidget] : "Off";
    this._send({
      id: this._id,
      value: this._value
    });
  }
}

;// CONCATENATED MODULE: ./src/scripting_api/aform.js

class AForm {
  constructor(document, app, util, color) {
    this._document = document;
    this._app = app;
    this._util = util;
    this._color = color;
    this._dateFormats = ["m/d", "m/d/yy", "mm/dd/yy", "mm/yy", "d-mmm", "d-mmm-yy", "dd-mmm-yy", "yy-mm-dd", "mmm-yy", "mmmm-yy", "mmm d, yyyy", "mmmm d, yyyy", "m/d/yy h:MM tt", "m/d/yy HH:MM"];
    this._timeFormats = ["HH:MM", "h:MM tt", "HH:MM:ss", "h:MM:ss tt"];
    this._dateActionsCache = new Map();
    this._emailRegex = new RegExp("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+" + "@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?" + "(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$");
  }
  _mkTargetName(event) {
    return event.target ? `[ ${event.target.name} ]` : "";
  }
  _tryToGuessDate(cFormat, cDate) {
    let actions = this._dateActionsCache.get(cFormat);
    if (!actions) {
      actions = [];
      this._dateActionsCache.set(cFormat, actions);
      cFormat.replaceAll(/(d+)|(m+)|(y+)|(H+)|(M+)|(s+)/g, function (match, d, m, y, H, M, s) {
        if (d) {
          actions.push((n, date) => {
            if (n >= 1 && n <= 31) {
              date.setDate(n);
              return true;
            }
            return false;
          });
        } else if (m) {
          actions.push((n, date) => {
            if (n >= 1 && n <= 12) {
              date.setMonth(n - 1);
              return true;
            }
            return false;
          });
        } else if (y) {
          actions.push((n, date) => {
            if (n < 50) {
              n += 2000;
            } else if (n < 100) {
              n += 1900;
            }
            date.setYear(n);
            return true;
          });
        } else if (H) {
          actions.push((n, date) => {
            if (n >= 0 && n <= 23) {
              date.setHours(n);
              return true;
            }
            return false;
          });
        } else if (M) {
          actions.push((n, date) => {
            if (n >= 0 && n <= 59) {
              date.setMinutes(n);
              return true;
            }
            return false;
          });
        } else if (s) {
          actions.push((n, date) => {
            if (n >= 0 && n <= 59) {
              date.setSeconds(n);
              return true;
            }
            return false;
          });
        }
        return "";
      });
    }
    const number = /\d+/g;
    let i = 0;
    let array;
    const date = new Date();
    while ((array = number.exec(cDate)) !== null) {
      if (i < actions.length) {
        if (!actions[i++](parseInt(array[0]), date)) {
          return null;
        }
      } else {
        break;
      }
    }
    if (i === 0) {
      return null;
    }
    return date;
  }
  _parseDate(cFormat, cDate, strict = false) {
    let date = null;
    try {
      date = this._util.scand(cFormat, cDate);
    } catch {}
    if (!date) {
      if (strict) {
        return null;
      }
      let format = cFormat;
      if (/mm(?!m)/.test(format)) {
        format = format.replace("mm", "m");
      }
      if (/dd(?!d)/.test(format)) {
        format = format.replace("dd", "d");
      }
      try {
        date = this._util.scand(format, cDate);
      } catch {}
    }
    if (!date) {
      date = Date.parse(cDate);
      date = isNaN(date) ? this._tryToGuessDate(cFormat, cDate) : new Date(date);
    }
    return date;
  }
  AFMergeChange(event = globalThis.event) {
    if (event.willCommit) {
      return event.value.toString();
    }
    return this._app._eventDispatcher.mergeChange(event);
  }
  AFParseDateEx(cString, cOrder) {
    return this._parseDate(cOrder, cString);
  }
  AFExtractNums(str) {
    if (typeof str === "number") {
      return [str];
    }
    if (!str || typeof str !== "string") {
      return null;
    }
    const first = str.charAt(0);
    if (first === "." || first === ",") {
      str = `0${str}`;
    }
    const numbers = str.match(/(\d+)/g);
    if (numbers.length === 0) {
      return null;
    }
    return numbers;
  }
  AFMakeNumber(str) {
    if (typeof str === "number") {
      return str;
    }
    if (typeof str !== "string") {
      return null;
    }
    str = str.trim().replace(",", ".");
    const number = parseFloat(str);
    if (isNaN(number) || !isFinite(number)) {
      return null;
    }
    return number;
  }
  AFMakeArrayFromList(string) {
    if (typeof string === "string") {
      return string.split(/, ?/g);
    }
    return string;
  }
  AFNumber_Format(nDec, sepStyle, negStyle, currStyle, strCurrency, bCurrencyPrepend) {
    const event = globalThis.event;
    let value = this.AFMakeNumber(event.value);
    if (value === null) {
      event.value = "";
      return;
    }
    const sign = Math.sign(value);
    const buf = [];
    let hasParen = false;
    if (sign === -1 && bCurrencyPrepend && negStyle === 0) {
      buf.push("-");
    }
    if ((negStyle === 2 || negStyle === 3) && sign === -1) {
      buf.push("(");
      hasParen = true;
    }
    if (bCurrencyPrepend) {
      buf.push(strCurrency);
    }
    sepStyle = Math.min(Math.max(0, Math.floor(sepStyle)), 4);
    buf.push("%,", sepStyle, ".", nDec.toString(), "f");
    if (!bCurrencyPrepend) {
      buf.push(strCurrency);
    }
    if (hasParen) {
      buf.push(")");
    }
    if (negStyle === 1 || negStyle === 3) {
      event.target.textColor = sign === 1 ? this._color.black : this._color.red;
    }
    if ((negStyle !== 0 || bCurrencyPrepend) && sign === -1) {
      value = -value;
    }
    const formatStr = buf.join("");
    event.value = this._util.printf(formatStr, value);
  }
  AFNumber_Keystroke(nDec, sepStyle, negStyle, currStyle, strCurrency, bCurrencyPrepend) {
    const event = globalThis.event;
    let value = this.AFMergeChange(event);
    if (!value) {
      return;
    }
    value = value.trim();
    let pattern;
    if (sepStyle > 1) {
      pattern = event.willCommit ? /^[+-]?(\d+(,\d*)?|,\d+)$/ : /^[+-]?\d*,?\d*$/;
    } else {
      pattern = event.willCommit ? /^[+-]?(\d+(\.\d*)?|\.\d+)$/ : /^[+-]?\d*\.?\d*$/;
    }
    if (!pattern.test(value)) {
      if (event.willCommit) {
        const err = `${GlobalConstants.IDS_INVALID_VALUE} ${this._mkTargetName(event)}`;
        this._app.alert(err);
      }
      event.rc = false;
    }
    if (event.willCommit && sepStyle > 1) {
      event.value = parseFloat(value.replace(",", "."));
    }
  }
  AFPercent_Format(nDec, sepStyle, percentPrepend = false) {
    if (typeof nDec !== "number") {
      return;
    }
    if (typeof sepStyle !== "number") {
      return;
    }
    if (nDec < 0) {
      throw new Error("Invalid nDec value in AFPercent_Format");
    }
    const event = globalThis.event;
    if (nDec > 512) {
      event.value = "%";
      return;
    }
    nDec = Math.floor(nDec);
    sepStyle = Math.min(Math.max(0, Math.floor(sepStyle)), 4);
    let value = this.AFMakeNumber(event.value);
    if (value === null) {
      event.value = "%";
      return;
    }
    const formatStr = `%,${sepStyle}.${nDec}f`;
    value = this._util.printf(formatStr, value * 100);
    event.value = percentPrepend ? `%${value}` : `${value}%`;
  }
  AFPercent_Keystroke(nDec, sepStyle) {
    this.AFNumber_Keystroke(nDec, sepStyle, 0, 0, "", true);
  }
  AFDate_FormatEx(cFormat) {
    const event = globalThis.event;
    const value = event.value;
    if (!value) {
      return;
    }
    const date = this._parseDate(cFormat, value);
    if (date !== null) {
      event.value = this._util.printd(cFormat, date);
    }
  }
  AFDate_Format(pdf) {
    if (pdf >= 0 && pdf < this._dateFormats.length) {
      this.AFDate_FormatEx(this._dateFormats[pdf]);
    }
  }
  AFDate_KeystrokeEx(cFormat) {
    const event = globalThis.event;
    if (!event.willCommit) {
      return;
    }
    const value = this.AFMergeChange(event);
    if (!value) {
      return;
    }
    if (this._parseDate(cFormat, value, true) === null) {
      const invalid = GlobalConstants.IDS_INVALID_DATE;
      const invalid2 = GlobalConstants.IDS_INVALID_DATE2;
      const err = `${invalid} ${this._mkTargetName(event)}${invalid2}${cFormat}`;
      this._app.alert(err);
      event.rc = false;
    }
  }
  AFDate_Keystroke(pdf) {
    if (pdf >= 0 && pdf < this._dateFormats.length) {
      this.AFDate_KeystrokeEx(this._dateFormats[pdf]);
    }
  }
  AFRange_Validate(bGreaterThan, nGreaterThan, bLessThan, nLessThan) {
    const event = globalThis.event;
    if (!event.value) {
      return;
    }
    const value = this.AFMakeNumber(event.value);
    if (value === null) {
      return;
    }
    bGreaterThan = !!bGreaterThan;
    bLessThan = !!bLessThan;
    if (bGreaterThan) {
      nGreaterThan = this.AFMakeNumber(nGreaterThan);
      if (nGreaterThan === null) {
        return;
      }
    }
    if (bLessThan) {
      nLessThan = this.AFMakeNumber(nLessThan);
      if (nLessThan === null) {
        return;
      }
    }
    let err = "";
    if (bGreaterThan && bLessThan) {
      if (value < nGreaterThan || value > nLessThan) {
        err = this._util.printf(GlobalConstants.IDS_GT_AND_LT, nGreaterThan, nLessThan);
      }
    } else if (bGreaterThan) {
      if (value < nGreaterThan) {
        err = this._util.printf(GlobalConstants.IDS_GREATER_THAN, nGreaterThan);
      }
    } else if (value > nLessThan) {
      err = this._util.printf(GlobalConstants.IDS_LESS_THAN, nLessThan);
    }
    if (err) {
      this._app.alert(err);
      event.rc = false;
    }
  }
  AFSimple(cFunction, nValue1, nValue2) {
    const value1 = this.AFMakeNumber(nValue1);
    if (value1 === null) {
      throw new Error("Invalid nValue1 in AFSimple");
    }
    const value2 = this.AFMakeNumber(nValue2);
    if (value2 === null) {
      throw new Error("Invalid nValue2 in AFSimple");
    }
    switch (cFunction) {
      case "AVG":
        return (value1 + value2) / 2;
      case "SUM":
        return value1 + value2;
      case "PRD":
        return value1 * value2;
      case "MIN":
        return Math.min(value1, value2);
      case "MAX":
        return Math.max(value1, value2);
    }
    throw new Error("Invalid cFunction in AFSimple");
  }
  AFSimple_Calculate(cFunction, cFields) {
    const actions = {
      AVG: args => args.reduce((acc, value) => acc + value, 0) / args.length,
      SUM: args => args.reduce((acc, value) => acc + value, 0),
      PRD: args => args.reduce((acc, value) => acc * value, 1),
      MIN: args => args.reduce((acc, value) => Math.min(acc, value), Number.MAX_VALUE),
      MAX: args => args.reduce((acc, value) => Math.max(acc, value), Number.MIN_VALUE)
    };
    if (!(cFunction in actions)) {
      throw new TypeError("Invalid function in AFSimple_Calculate");
    }
    const event = globalThis.event;
    const values = [];
    cFields = this.AFMakeArrayFromList(cFields);
    for (const cField of cFields) {
      const field = this._document.getField(cField);
      if (!field) {
        continue;
      }
      for (const child of field.getArray()) {
        const number = this.AFMakeNumber(child.value);
        values.push(number ?? 0);
      }
    }
    if (values.length === 0) {
      event.value = 0;
      return;
    }
    const res = actions[cFunction](values);
    event.value = Math.round(1e6 * res) / 1e6;
  }
  AFSpecial_Format(psf) {
    const event = globalThis.event;
    if (!event.value) {
      return;
    }
    psf = this.AFMakeNumber(psf);
    let formatStr;
    switch (psf) {
      case 0:
        formatStr = "99999";
        break;
      case 1:
        formatStr = "99999-9999";
        break;
      case 2:
        formatStr = this._util.printx("9999999999", event.value).length >= 10 ? "(999) 999-9999" : "999-9999";
        break;
      case 3:
        formatStr = "999-99-9999";
        break;
      default:
        throw new Error("Invalid psf in AFSpecial_Format");
    }
    event.value = this._util.printx(formatStr, event.value);
  }
  AFSpecial_KeystrokeEx(cMask) {
    if (!cMask) {
      return;
    }
    const event = globalThis.event;
    const value = this.AFMergeChange(event);
    if (!value) {
      return;
    }
    const checkers = new Map([["9", char => char >= "0" && char <= "9"], ["A", char => "a" <= char && char <= "z" || "A" <= char && char <= "Z"], ["O", char => "a" <= char && char <= "z" || "A" <= char && char <= "Z" || "0" <= char && char <= "9"], ["X", char => true]]);
    function _checkValidity(_value, _cMask) {
      for (let i = 0, ii = _value.length; i < ii; i++) {
        const mask = _cMask.charAt(i);
        const char = _value.charAt(i);
        const checker = checkers.get(mask);
        if (checker) {
          if (!checker(char)) {
            return false;
          }
        } else if (mask !== char) {
          return false;
        }
      }
      return true;
    }
    const err = `${GlobalConstants.IDS_INVALID_VALUE} = "${cMask}"`;
    if (value.length > cMask.length) {
      this._app.alert(err);
      event.rc = false;
      return;
    }
    if (event.willCommit) {
      if (value.length < cMask.length) {
        this._app.alert(err);
        event.rc = false;
        return;
      }
      if (!_checkValidity(value, cMask)) {
        this._app.alert(err);
        event.rc = false;
        return;
      }
      event.value += cMask.substring(value.length);
      return;
    }
    if (value.length < cMask.length) {
      cMask = cMask.substring(0, value.length);
    }
    if (!_checkValidity(value, cMask)) {
      this._app.alert(err);
      event.rc = false;
    }
  }
  AFSpecial_Keystroke(psf) {
    const event = globalThis.event;
    psf = this.AFMakeNumber(psf);
    let formatStr;
    switch (psf) {
      case 0:
        formatStr = "99999";
        break;
      case 1:
        formatStr = "99999-9999";
        break;
      case 2:
        const value = this.AFMergeChange(event);
        formatStr = value.length > 8 || value.startsWith("(") ? "(999) 999-9999" : "999-9999";
        break;
      case 3:
        formatStr = "999-99-9999";
        break;
      default:
        throw new Error("Invalid psf in AFSpecial_Keystroke");
    }
    this.AFSpecial_KeystrokeEx(formatStr);
  }
  AFTime_FormatEx(cFormat) {
    this.AFDate_FormatEx(cFormat);
  }
  AFTime_Format(pdf) {
    if (pdf >= 0 && pdf < this._timeFormats.length) {
      this.AFDate_FormatEx(this._timeFormats[pdf]);
    }
  }
  AFTime_KeystrokeEx(cFormat) {
    this.AFDate_KeystrokeEx(cFormat);
  }
  AFTime_Keystroke(pdf) {
    if (pdf >= 0 && pdf < this._timeFormats.length) {
      this.AFDate_KeystrokeEx(this._timeFormats[pdf]);
    }
  }
  eMailValidate(str) {
    return this._emailRegex.test(str);
  }
  AFExactMatch(rePatterns, str) {
    if (rePatterns instanceof RegExp) {
      return str.match(rePatterns)?.[0] === str || 0;
    }
    return rePatterns.findIndex(re => str.match(re)?.[0] === str) + 1;
  }
}

;// CONCATENATED MODULE: ./src/scripting_api/app_utils.js
const VIEWER_TYPE = "PDF.js";
const VIEWER_VARIATION = "Full";
const VIEWER_VERSION = 21.00720099;
const FORMS_VERSION = 21.00720099;
const USERACTIVATION_CALLBACKID = 0;
const USERACTIVATION_MAXTIME_VALIDITY = 5000;
function serializeError(error) {
  const value = `${error.toString()}\n${error.stack}`;
  return {
    command: "error",
    value
  };
}

;// CONCATENATED MODULE: ./src/scripting_api/event.js

class Event {
  constructor(data) {
    this.change = data.change || "";
    this.changeEx = data.changeEx || null;
    this.commitKey = data.commitKey || 0;
    this.fieldFull = data.fieldFull || false;
    this.keyDown = data.keyDown || false;
    this.modifier = data.modifier || false;
    this.name = data.name;
    this.rc = true;
    this.richChange = data.richChange || [];
    this.richChangeEx = data.richChangeEx || [];
    this.richValue = data.richValue || [];
    this.selEnd = data.selEnd ?? -1;
    this.selStart = data.selStart ?? -1;
    this.shift = data.shift || false;
    this.source = data.source || null;
    this.target = data.target || null;
    this.targetName = "";
    this.type = "Field";
    this.value = data.value || "";
    this.willCommit = data.willCommit || false;
  }
}
class EventDispatcher {
  constructor(document, calculationOrder, objects, externalCall) {
    this._document = document;
    this._calculationOrder = calculationOrder;
    this._objects = objects;
    this._externalCall = externalCall;
    this._document.obj._eventDispatcher = this;
    this._isCalculating = false;
  }
  mergeChange(event) {
    let value = event.value;
    if (Array.isArray(value)) {
      return value;
    }
    if (typeof value !== "string") {
      value = value.toString();
    }
    const prefix = event.selStart >= 0 ? value.substring(0, event.selStart) : "";
    const postfix = event.selEnd >= 0 && event.selEnd <= value.length ? value.substring(event.selEnd) : "";
    return `${prefix}${event.change}${postfix}`;
  }
  userActivation() {
    this._document.obj._userActivation = true;
    this._externalCall("setTimeout", [USERACTIVATION_CALLBACKID, USERACTIVATION_MAXTIME_VALIDITY]);
  }
  dispatch(baseEvent) {
    const id = baseEvent.id;
    if (!(id in this._objects)) {
      let event;
      if (id === "doc" || id === "page") {
        event = globalThis.event = new Event(baseEvent);
        event.source = event.target = this._document.wrapped;
        event.name = baseEvent.name;
      }
      if (id === "doc") {
        const eventName = event.name;
        if (eventName === "Open") {
          this.userActivation();
          this._document.obj._initActions();
          this.formatAll();
        }
        if (!["DidPrint", "DidSave", "WillPrint", "WillSave"].includes(eventName)) {
          this.userActivation();
        }
        this._document.obj._dispatchDocEvent(event.name);
      } else if (id === "page") {
        this.userActivation();
        this._document.obj._dispatchPageEvent(event.name, baseEvent.actions, baseEvent.pageNumber);
      } else if (id === "app" && baseEvent.name === "ResetForm") {
        this.userActivation();
        for (const fieldId of baseEvent.ids) {
          const obj = this._objects[fieldId];
          obj?.obj._reset();
        }
      }
      return;
    }
    const name = baseEvent.name;
    const source = this._objects[id];
    const event = globalThis.event = new Event(baseEvent);
    let savedChange;
    this.userActivation();
    if (source.obj._isButton()) {
      source.obj._id = id;
      event.value = source.obj._getExportValue(event.value);
      if (name === "Action") {
        source.obj._value = event.value;
      }
    }
    switch (name) {
      case "Keystroke":
        savedChange = {
          value: event.value,
          changeEx: event.changeEx,
          change: event.change,
          selStart: event.selStart,
          selEnd: event.selEnd
        };
        break;
      case "Blur":
      case "Focus":
        Object.defineProperty(event, "value", {
          configurable: false,
          writable: false,
          enumerable: true,
          value: event.value
        });
        break;
      case "Validate":
        this.runValidation(source, event);
        return;
      case "Action":
        this.runActions(source, source, event, name);
        this.runCalculate(source, event);
        return;
    }
    this.runActions(source, source, event, name);
    if (name !== "Keystroke") {
      return;
    }
    if (event.rc) {
      if (event.willCommit) {
        this.runValidation(source, event);
      } else {
        if (source.obj._isChoice) {
          source.obj.value = savedChange.changeEx;
          source.obj._send({
            id: source.obj._id,
            siblings: source.obj._siblings,
            value: source.obj.value
          });
          return;
        }
        const value = source.obj.value = this.mergeChange(event);
        let selStart, selEnd;
        if (event.selStart !== savedChange.selStart || event.selEnd !== savedChange.selEnd) {
          selStart = event.selStart;
          selEnd = event.selEnd;
        } else {
          selEnd = selStart = savedChange.selStart + event.change.length;
        }
        source.obj._send({
          id: source.obj._id,
          siblings: source.obj._siblings,
          value,
          selRange: [selStart, selEnd]
        });
      }
    } else if (!event.willCommit) {
      source.obj._send({
        id: source.obj._id,
        siblings: source.obj._siblings,
        value: savedChange.value,
        selRange: [savedChange.selStart, savedChange.selEnd]
      });
    } else {
      source.obj._send({
        id: source.obj._id,
        siblings: source.obj._siblings,
        value: "",
        formattedValue: null,
        selRange: [0, 0]
      });
    }
  }
  formatAll() {
    const event = globalThis.event = new Event({});
    for (const source of Object.values(this._objects)) {
      event.value = source.obj._getValue();
      this.runActions(source, source, event, "Format");
    }
  }
  runValidation(source, event) {
    const didValidateRun = this.runActions(source, source, event, "Validate");
    if (event.rc) {
      source.obj.value = event.value;
      this.runCalculate(source, event);
      const savedValue = event.value = source.obj._getValue();
      let formattedValue = null;
      if (this.runActions(source, source, event, "Format")) {
        formattedValue = event.value?.toString?.();
      }
      source.obj._send({
        id: source.obj._id,
        siblings: source.obj._siblings,
        value: savedValue,
        formattedValue
      });
      event.value = savedValue;
    } else if (didValidateRun) {
      source.obj._send({
        id: source.obj._id,
        siblings: source.obj._siblings,
        value: "",
        formattedValue: null,
        selRange: [0, 0],
        focus: true
      });
    }
  }
  runActions(source, target, event, eventName) {
    event.source = source.wrapped;
    event.target = target.wrapped;
    event.name = eventName;
    event.targetName = target.obj.name;
    event.rc = true;
    return target.obj._runActions(event);
  }
  calculateNow() {
    if (!this._calculationOrder || this._isCalculating || !this._document.obj.calculate) {
      return;
    }
    this._isCalculating = true;
    const first = this._calculationOrder[0];
    const source = this._objects[first];
    globalThis.event = new Event({});
    try {
      this.runCalculate(source, globalThis.event);
    } catch (error) {
      this._isCalculating = false;
      throw error;
    }
    this._isCalculating = false;
  }
  runCalculate(source, event) {
    if (!this._calculationOrder || !this._document.obj.calculate) {
      return;
    }
    for (const targetId of this._calculationOrder) {
      if (!(targetId in this._objects)) {
        continue;
      }
      if (!this._document.obj.calculate) {
        break;
      }
      event.value = null;
      const target = this._objects[targetId];
      let savedValue = target.obj._getValue();
      try {
        this.runActions(source, target, event, "Calculate");
      } catch (error) {
        const fieldId = target.obj._id;
        const serializedError = serializeError(error);
        serializedError.value = `Error when calculating value for field "${fieldId}"\n${serializedError.value}`;
        this._externalCall("send", [serializedError]);
        continue;
      }
      if (!event.rc) {
        continue;
      }
      if (event.value !== null) {
        target.obj.value = event.value;
      } else {
        event.value = target.obj._getValue();
      }
      this.runActions(target, target, event, "Validate");
      if (!event.rc) {
        if (target.obj._getValue() !== savedValue) {
          target.wrapped.value = savedValue;
        }
        continue;
      }
      if (event.value === null) {
        event.value = target.obj._getValue();
      }
      savedValue = target.obj._getValue();
      let formattedValue = null;
      if (this.runActions(target, target, event, "Format")) {
        formattedValue = event.value?.toString?.();
      }
      target.obj._send({
        id: target.obj._id,
        siblings: target.obj._siblings,
        value: savedValue,
        formattedValue
      });
    }
  }
}

;// CONCATENATED MODULE: ./src/scripting_api/fullscreen.js


class FullScreen extends PDFObject {
  constructor(data) {
    super(data);
    this._backgroundColor = [];
    this._clickAdvances = true;
    this._cursor = Cursor.hidden;
    this._defaultTransition = "";
    this._escapeExits = true;
    this._isFullScreen = true;
    this._loop = false;
    this._timeDelay = 3600;
    this._usePageTiming = false;
    this._useTimer = false;
  }
  get backgroundColor() {
    return this._backgroundColor;
  }
  set backgroundColor(_) {}
  get clickAdvances() {
    return this._clickAdvances;
  }
  set clickAdvances(_) {}
  get cursor() {
    return this._cursor;
  }
  set cursor(_) {}
  get defaultTransition() {
    return this._defaultTransition;
  }
  set defaultTransition(_) {}
  get escapeExits() {
    return this._escapeExits;
  }
  set escapeExits(_) {}
  get isFullScreen() {
    return this._isFullScreen;
  }
  set isFullScreen(_) {}
  get loop() {
    return this._loop;
  }
  set loop(_) {}
  get timeDelay() {
    return this._timeDelay;
  }
  set timeDelay(_) {}
  get transitions() {
    return ["Replace", "WipeRight", "WipeLeft", "WipeDown", "WipeUp", "SplitHorizontalIn", "SplitHorizontalOut", "SplitVerticalIn", "SplitVerticalOut", "BlindsHorizontal", "BlindsVertical", "BoxIn", "BoxOut", "GlitterRight", "GlitterDown", "GlitterRightDown", "Dissolve", "Random"];
  }
  set transitions(_) {
    throw new Error("fullscreen.transitions is read-only");
  }
  get usePageTiming() {
    return this._usePageTiming;
  }
  set usePageTiming(_) {}
  get useTimer() {
    return this._useTimer;
  }
  set useTimer(_) {}
}

;// CONCATENATED MODULE: ./src/scripting_api/thermometer.js

class Thermometer extends PDFObject {
  constructor(data) {
    super(data);
    this._cancelled = false;
    this._duration = 100;
    this._text = "";
    this._value = 0;
  }
  get cancelled() {
    return this._cancelled;
  }
  set cancelled(_) {
    throw new Error("thermometer.cancelled is read-only");
  }
  get duration() {
    return this._duration;
  }
  set duration(val) {
    this._duration = val;
  }
  get text() {
    return this._text;
  }
  set text(val) {
    this._text = val;
  }
  get value() {
    return this._value;
  }
  set value(val) {
    this._value = val;
  }
  begin() {}
  end() {}
}

;// CONCATENATED MODULE: ./src/scripting_api/app.js






class App extends PDFObject {
  constructor(data) {
    super(data);
    this._constants = null;
    this._focusRect = true;
    this._fs = null;
    this._language = App._getLanguage(data.language);
    this._openInPlace = false;
    this._platform = App._getPlatform(data.platform);
    this._runtimeHighlight = false;
    this._runtimeHighlightColor = ["T"];
    this._thermometer = null;
    this._toolbar = false;
    this._document = data._document;
    this._proxyHandler = data.proxyHandler;
    this._objects = Object.create(null);
    this._eventDispatcher = new EventDispatcher(this._document, data.calculationOrder, this._objects, data.externalCall);
    this._timeoutIds = new WeakMap();
    if (typeof FinalizationRegistry !== "undefined") {
      this._timeoutIdsRegistry = new FinalizationRegistry(this._cleanTimeout.bind(this));
    } else {
      this._timeoutIdsRegistry = null;
    }
    this._timeoutCallbackIds = new Map();
    this._timeoutCallbackId = USERACTIVATION_CALLBACKID + 1;
    this._globalEval = data.globalEval;
    this._externalCall = data.externalCall;
  }
  _dispatchEvent(pdfEvent) {
    this._eventDispatcher.dispatch(pdfEvent);
  }
  _registerTimeoutCallback(cExpr) {
    const id = this._timeoutCallbackId++;
    this._timeoutCallbackIds.set(id, cExpr);
    return id;
  }
  _unregisterTimeoutCallback(id) {
    this._timeoutCallbackIds.delete(id);
  }
  _evalCallback({
    callbackId,
    interval
  }) {
    if (callbackId === USERACTIVATION_CALLBACKID) {
      this._document.obj._userActivation = false;
      return;
    }
    const expr = this._timeoutCallbackIds.get(callbackId);
    if (!interval) {
      this._unregisterTimeoutCallback(callbackId);
    }
    if (expr) {
      this._globalEval(expr);
    }
  }
  _registerTimeout(callbackId, interval) {
    const timeout = Object.create(null);
    const id = {
      callbackId,
      interval
    };
    this._timeoutIds.set(timeout, id);
    this._timeoutIdsRegistry?.register(timeout, id);
    return timeout;
  }
  _unregisterTimeout(timeout) {
    this._timeoutIdsRegistry?.unregister(timeout);
    const data = this._timeoutIds.get(timeout);
    if (!data) {
      return;
    }
    this._timeoutIds.delete(timeout);
    this._cleanTimeout(data);
  }
  _cleanTimeout({
    callbackId,
    interval
  }) {
    this._unregisterTimeoutCallback(callbackId);
    if (interval) {
      this._externalCall("clearInterval", [callbackId]);
    } else {
      this._externalCall("clearTimeout", [callbackId]);
    }
  }
  static _getPlatform(platform) {
    if (typeof platform === "string") {
      platform = platform.toLowerCase();
      if (platform.includes("win")) {
        return "WIN";
      } else if (platform.includes("mac")) {
        return "MAC";
      }
    }
    return "UNIX";
  }
  static _getLanguage(language) {
    const [main, sub] = language.toLowerCase().split(/[-_]/);
    switch (main) {
      case "zh":
        if (sub === "cn" || sub === "sg") {
          return "CHS";
        }
        return "CHT";
      case "da":
        return "DAN";
      case "de":
        return "DEU";
      case "es":
        return "ESP";
      case "fr":
        return "FRA";
      case "it":
        return "ITA";
      case "ko":
        return "KOR";
      case "ja":
        return "JPN";
      case "nl":
        return "NLD";
      case "no":
        return "NOR";
      case "pt":
        if (sub === "br") {
          return "PTB";
        }
        return "ENU";
      case "fi":
        return "SUO";
      case "SV":
        return "SVE";
      default:
        return "ENU";
    }
  }
  get activeDocs() {
    return [this._document.wrapped];
  }
  set activeDocs(_) {
    throw new Error("app.activeDocs is read-only");
  }
  get calculate() {
    return this._document.obj.calculate;
  }
  set calculate(calculate) {
    this._document.obj.calculate = calculate;
  }
  get constants() {
    if (!this._constants) {
      this._constants = Object.freeze({
        align: Object.freeze({
          left: 0,
          center: 1,
          right: 2,
          top: 3,
          bottom: 4
        })
      });
    }
    return this._constants;
  }
  set constants(_) {
    throw new Error("app.constants is read-only");
  }
  get focusRect() {
    return this._focusRect;
  }
  set focusRect(val) {
    this._focusRect = val;
  }
  get formsVersion() {
    return FORMS_VERSION;
  }
  set formsVersion(_) {
    throw new Error("app.formsVersion is read-only");
  }
  get fromPDFConverters() {
    return [];
  }
  set fromPDFConverters(_) {
    throw new Error("app.fromPDFConverters is read-only");
  }
  get fs() {
    if (this._fs === null) {
      this._fs = new Proxy(new FullScreen({
        send: this._send
      }), this._proxyHandler);
    }
    return this._fs;
  }
  set fs(_) {
    throw new Error("app.fs is read-only");
  }
  get language() {
    return this._language;
  }
  set language(_) {
    throw new Error("app.language is read-only");
  }
  get media() {
    return undefined;
  }
  set media(_) {
    throw new Error("app.media is read-only");
  }
  get monitors() {
    return [];
  }
  set monitors(_) {
    throw new Error("app.monitors is read-only");
  }
  get numPlugins() {
    return 0;
  }
  set numPlugins(_) {
    throw new Error("app.numPlugins is read-only");
  }
  get openInPlace() {
    return this._openInPlace;
  }
  set openInPlace(val) {
    this._openInPlace = val;
  }
  get platform() {
    return this._platform;
  }
  set platform(_) {
    throw new Error("app.platform is read-only");
  }
  get plugins() {
    return [];
  }
  set plugins(_) {
    throw new Error("app.plugins is read-only");
  }
  get printColorProfiles() {
    return [];
  }
  set printColorProfiles(_) {
    throw new Error("app.printColorProfiles is read-only");
  }
  get printerNames() {
    return [];
  }
  set printerNames(_) {
    throw new Error("app.printerNames is read-only");
  }
  get runtimeHighlight() {
    return this._runtimeHighlight;
  }
  set runtimeHighlight(val) {
    this._runtimeHighlight = val;
  }
  get runtimeHighlightColor() {
    return this._runtimeHighlightColor;
  }
  set runtimeHighlightColor(val) {
    if (Color._isValidColor(val)) {
      this._runtimeHighlightColor = val;
    }
  }
  get thermometer() {
    if (this._thermometer === null) {
      this._thermometer = new Proxy(new Thermometer({
        send: this._send
      }), this._proxyHandler);
    }
    return this._thermometer;
  }
  set thermometer(_) {
    throw new Error("app.thermometer is read-only");
  }
  get toolbar() {
    return this._toolbar;
  }
  set toolbar(val) {
    this._toolbar = val;
  }
  get toolbarHorizontal() {
    return this.toolbar;
  }
  set toolbarHorizontal(value) {
    this.toolbar = value;
  }
  get toolbarVertical() {
    return this.toolbar;
  }
  set toolbarVertical(value) {
    this.toolbar = value;
  }
  get viewerType() {
    return VIEWER_TYPE;
  }
  set viewerType(_) {
    throw new Error("app.viewerType is read-only");
  }
  get viewerVariation() {
    return VIEWER_VARIATION;
  }
  set viewerVariation(_) {
    throw new Error("app.viewerVariation is read-only");
  }
  get viewerVersion() {
    return VIEWER_VERSION;
  }
  set viewerVersion(_) {
    throw new Error("app.viewerVersion is read-only");
  }
  addMenuItem() {}
  addSubMenu() {}
  addToolButton() {}
  alert(cMsg, nIcon = 0, nType = 0, cTitle = "PDF.js", oDoc = null, oCheckbox = null) {
    if (!this._document.obj._userActivation) {
      return 0;
    }
    this._document.obj._userActivation = false;
    if (cMsg && typeof cMsg === "object") {
      nType = cMsg.nType;
      cMsg = cMsg.cMsg;
    }
    cMsg = (cMsg || "").toString();
    nType = typeof nType !== "number" || isNaN(nType) || nType < 0 || nType > 3 ? 0 : nType;
    if (nType >= 2) {
      return this._externalCall("confirm", [cMsg]) ? 4 : 3;
    }
    this._externalCall("alert", [cMsg]);
    return 1;
  }
  beep() {}
  beginPriv() {}
  browseForDoc() {}
  clearInterval(oInterval) {
    this._unregisterTimeout(oInterval);
  }
  clearTimeOut(oTime) {
    this._unregisterTimeout(oTime);
  }
  endPriv() {}
  execDialog() {}
  execMenuItem(item) {
    if (!this._document.obj._userActivation) {
      return;
    }
    this._document.obj._userActivation = false;
    switch (item) {
      case "SaveAs":
        if (this._document.obj._disableSaving) {
          return;
        }
        this._send({
          command: item
        });
        break;
      case "FirstPage":
      case "LastPage":
      case "NextPage":
      case "PrevPage":
      case "ZoomViewIn":
      case "ZoomViewOut":
        this._send({
          command: item
        });
        break;
      case "FitPage":
        this._send({
          command: "zoom",
          value: "page-fit"
        });
        break;
      case "Print":
        if (this._document.obj._disablePrinting) {
          return;
        }
        this._send({
          command: "print"
        });
        break;
    }
  }
  getNthPlugInName() {}
  getPath() {}
  goBack() {}
  goForward() {}
  hideMenuItem() {}
  hideToolbarButton() {}
  launchURL() {}
  listMenuItems() {}
  listToolbarButtons() {}
  loadPolicyFile() {}
  mailGetAddrs() {}
  mailMsg() {}
  newDoc() {}
  newCollection() {}
  newFDF() {}
  openDoc() {}
  openFDF() {}
  popUpMenu() {}
  popUpMenuEx() {}
  removeToolButton() {}
  response(cQuestion, cTitle = "", cDefault = "", bPassword = "", cLabel = "") {
    if (cQuestion && typeof cQuestion === "object") {
      cDefault = cQuestion.cDefault;
      cQuestion = cQuestion.cQuestion;
    }
    cQuestion = (cQuestion || "").toString();
    cDefault = (cDefault || "").toString();
    return this._externalCall("prompt", [cQuestion, cDefault || ""]);
  }
  setInterval(cExpr, nMilliseconds = 0) {
    if (cExpr && typeof cExpr === "object") {
      nMilliseconds = cExpr.nMilliseconds || 0;
      cExpr = cExpr.cExpr;
    }
    if (typeof cExpr !== "string") {
      throw new TypeError("First argument of app.setInterval must be a string");
    }
    if (typeof nMilliseconds !== "number") {
      throw new TypeError("Second argument of app.setInterval must be a number");
    }
    const callbackId = this._registerTimeoutCallback(cExpr);
    this._externalCall("setInterval", [callbackId, nMilliseconds]);
    return this._registerTimeout(callbackId, true);
  }
  setTimeOut(cExpr, nMilliseconds = 0) {
    if (cExpr && typeof cExpr === "object") {
      nMilliseconds = cExpr.nMilliseconds || 0;
      cExpr = cExpr.cExpr;
    }
    if (typeof cExpr !== "string") {
      throw new TypeError("First argument of app.setTimeOut must be a string");
    }
    if (typeof nMilliseconds !== "number") {
      throw new TypeError("Second argument of app.setTimeOut must be a number");
    }
    const callbackId = this._registerTimeoutCallback(cExpr);
    this._externalCall("setTimeout", [callbackId, nMilliseconds]);
    return this._registerTimeout(callbackId, false);
  }
  trustedFunction() {}
  trustPropagatorFunction() {}
}

;// CONCATENATED MODULE: ./src/scripting_api/console.js

class Console extends PDFObject {
  clear() {
    this._send({
      id: "clear"
    });
  }
  hide() {}
  println(msg) {
    if (typeof msg === "string") {
      this._send({
        command: "println",
        value: "PDF.js Console:: " + msg
      });
    }
  }
  show() {}
}

;// CONCATENATED MODULE: ./src/scripting_api/print_params.js
class PrintParams {
  constructor(data) {
    this.binaryOk = true;
    this.bitmapDPI = 150;
    this.booklet = {
      binding: 0,
      duplexMode: 0,
      subsetFrom: 0,
      subsetTo: -1
    };
    this.colorOverride = 0;
    this.colorProfile = "";
    this.constants = Object.freeze({
      bookletBindings: Object.freeze({
        Left: 0,
        Right: 1,
        LeftTall: 2,
        RightTall: 3
      }),
      bookletDuplexMode: Object.freeze({
        BothSides: 0,
        FrontSideOnly: 1,
        BasicSideOnly: 2
      }),
      colorOverrides: Object.freeze({
        auto: 0,
        gray: 1,
        mono: 2
      }),
      fontPolicies: Object.freeze({
        everyPage: 0,
        jobStart: 1,
        pageRange: 2
      }),
      handling: Object.freeze({
        none: 0,
        fit: 1,
        shrink: 2,
        tileAll: 3,
        tileLarge: 4,
        nUp: 5,
        booklet: 6
      }),
      interactionLevel: Object.freeze({
        automatic: 0,
        full: 1,
        silent: 2
      }),
      nUpPageOrders: Object.freeze({
        Horizontal: 0,
        HorizontalReversed: 1,
        Vertical: 2
      }),
      printContents: Object.freeze({
        doc: 0,
        docAndComments: 1,
        formFieldsOnly: 2
      }),
      flagValues: Object.freeze({
        applyOverPrint: 1,
        applySoftProofSettings: 1 << 1,
        applyWorkingColorSpaces: 1 << 2,
        emitHalftones: 1 << 3,
        emitPostScriptXObjects: 1 << 4,
        emitFormsAsPSForms: 1 << 5,
        maxJP2KRes: 1 << 6,
        setPageSize: 1 << 7,
        suppressBG: 1 << 8,
        suppressCenter: 1 << 9,
        suppressCJKFontSubst: 1 << 10,
        suppressCropClip: 1 << 1,
        suppressRotate: 1 << 12,
        suppressTransfer: 1 << 13,
        suppressUCR: 1 << 14,
        useTrapAnnots: 1 << 15,
        usePrintersMarks: 1 << 16
      }),
      rasterFlagValues: Object.freeze({
        textToOutline: 1,
        strokesToOutline: 1 << 1,
        allowComplexClip: 1 << 2,
        preserveOverprint: 1 << 3
      }),
      subsets: Object.freeze({
        all: 0,
        even: 1,
        odd: 2
      }),
      tileMarks: Object.freeze({
        none: 0,
        west: 1,
        east: 2
      }),
      usages: Object.freeze({
        auto: 0,
        use: 1,
        noUse: 2
      })
    });
    this.downloadFarEastFonts = false;
    this.fileName = "";
    this.firstPage = 0;
    this.flags = 0;
    this.fontPolicy = 0;
    this.gradientDPI = 150;
    this.interactive = 1;
    this.lastPage = data.lastPage;
    this.npUpAutoRotate = false;
    this.npUpNumPagesH = 2;
    this.npUpNumPagesV = 2;
    this.npUpPageBorder = false;
    this.npUpPageOrder = 0;
    this.pageHandling = 0;
    this.pageSubset = 0;
    this.printAsImage = false;
    this.printContent = 0;
    this.printerName = "";
    this.psLevel = 0;
    this.rasterFlags = 0;
    this.reversePages = false;
    this.tileLabel = false;
    this.tileMark = 0;
    this.tileOverlap = 0;
    this.tileScale = 1.0;
    this.transparencyLevel = 75;
    this.usePrinterCRD = 0;
    this.useT1Conversion = 0;
  }
}

;// CONCATENATED MODULE: ./src/scripting_api/doc.js





const DOC_EXTERNAL = false;
class InfoProxyHandler {
  static get(obj, prop) {
    return obj[prop.toLowerCase()];
  }
  static set(obj, prop, value) {
    throw new Error(`doc.info.${prop} is read-only`);
  }
}
class Doc extends PDFObject {
  constructor(data) {
    super(data);
    this._expandos = globalThis;
    this._baseURL = data.baseURL || "";
    this._calculate = true;
    this._delay = false;
    this._dirty = false;
    this._disclosed = false;
    this._media = undefined;
    this._metadata = data.metadata || "";
    this._noautocomplete = undefined;
    this._nocache = undefined;
    this._spellDictionaryOrder = [];
    this._spellLanguageOrder = [];
    this._printParams = null;
    this._fields = new Map();
    this._fieldNames = [];
    this._event = null;
    this._author = data.Author || "";
    this._creator = data.Creator || "";
    this._creationDate = this._getDate(data.CreationDate) || null;
    this._docID = data.docID || ["", ""];
    this._documentFileName = data.filename || "";
    this._filesize = data.filesize || 0;
    this._keywords = data.Keywords || "";
    this._layout = data.layout || "";
    this._modDate = this._getDate(data.ModDate) || null;
    this._numFields = 0;
    this._numPages = data.numPages || 1;
    this._pageNum = data.pageNum || 0;
    this._producer = data.Producer || "";
    this._securityHandler = data.EncryptFilterName || null;
    this._subject = data.Subject || "";
    this._title = data.Title || "";
    this._URL = data.URL || "";
    this._info = new Proxy({
      title: this._title,
      author: this._author,
      authors: data.authors || [this._author],
      subject: this._subject,
      keywords: this._keywords,
      creator: this._creator,
      producer: this._producer,
      creationdate: this._creationDate,
      moddate: this._modDate,
      trapped: data.Trapped || "Unknown"
    }, InfoProxyHandler);
    this._zoomType = ZoomType.none;
    this._zoom = data.zoom || 100;
    this._actions = createActionsMap(data.actions);
    this._globalEval = data.globalEval;
    this._pageActions = null;
    this._userActivation = false;
    this._disablePrinting = false;
    this._disableSaving = false;
    this._otherPageActions = null;
  }
  _initActions() {
    const dontRun = new Set(["WillClose", "WillSave", "DidSave", "WillPrint", "DidPrint", "OpenAction"]);
    this._disableSaving = true;
    for (const actionName of this._actions.keys()) {
      if (!dontRun.has(actionName)) {
        this._runActions(actionName);
      }
    }
    this._runActions("OpenAction");
    this._disableSaving = false;
  }
  _dispatchDocEvent(name) {
    switch (name) {
      case "Open":
        this._disableSaving = true;
        this._runActions("OpenAction");
        this._disableSaving = false;
        break;
      case "WillPrint":
        this._disablePrinting = true;
        try {
          this._runActions(name);
        } catch (error) {
          this._send(serializeError(error));
        }
        this._send({
          command: "WillPrintFinished"
        });
        this._disablePrinting = false;
        break;
      case "WillSave":
        this._disableSaving = true;
        this._runActions(name);
        this._disableSaving = false;
        break;
      default:
        this._runActions(name);
    }
  }
  _dispatchPageEvent(name, actions, pageNumber) {
    if (name === "PageOpen") {
      this._pageActions ||= new Map();
      if (!this._pageActions.has(pageNumber)) {
        this._pageActions.set(pageNumber, createActionsMap(actions));
      }
      this._pageNum = pageNumber - 1;
    }
    for (const acts of [this._pageActions, this._otherPageActions]) {
      actions = acts?.get(pageNumber)?.get(name);
      if (actions) {
        for (const action of actions) {
          this._globalEval(action);
        }
      }
    }
  }
  _runActions(name) {
    const actions = this._actions.get(name);
    if (actions) {
      for (const action of actions) {
        this._globalEval(action);
      }
    }
  }
  _addField(name, field) {
    this._fields.set(name, field);
    this._fieldNames.push(name);
    this._numFields++;
    const po = field.obj._actions.get("PageOpen");
    const pc = field.obj._actions.get("PageClose");
    if (po || pc) {
      this._otherPageActions ||= new Map();
      let actions = this._otherPageActions.get(field.obj._page + 1);
      if (!actions) {
        actions = new Map();
        this._otherPageActions.set(field.obj._page + 1, actions);
      }
      if (po) {
        let poActions = actions.get("PageOpen");
        if (!poActions) {
          poActions = [];
          actions.set("PageOpen", poActions);
        }
        poActions.push(...po);
      }
      if (pc) {
        let pcActions = actions.get("PageClose");
        if (!pcActions) {
          pcActions = [];
          actions.set("PageClose", pcActions);
        }
        pcActions.push(...pc);
      }
    }
  }
  _getDate(date) {
    if (!date || date.length < 15 || !date.startsWith("D:")) {
      return date;
    }
    date = date.substring(2);
    const year = date.substring(0, 4);
    const month = date.substring(4, 6);
    const day = date.substring(6, 8);
    const hour = date.substring(8, 10);
    const minute = date.substring(10, 12);
    const o = date.charAt(12);
    let second, offsetPos;
    if (o === "Z" || o === "+" || o === "-") {
      second = "00";
      offsetPos = 12;
    } else {
      second = date.substring(12, 14);
      offsetPos = 14;
    }
    const offset = date.substring(offsetPos).replaceAll("'", "");
    return new Date(`${year}-${month}-${day}T${hour}:${minute}:${second}${offset}`);
  }
  get author() {
    return this._author;
  }
  set author(_) {
    throw new Error("doc.author is read-only");
  }
  get baseURL() {
    return this._baseURL;
  }
  set baseURL(baseURL) {
    this._baseURL = baseURL;
  }
  get bookmarkRoot() {
    return undefined;
  }
  set bookmarkRoot(_) {
    throw new Error("doc.bookmarkRoot is read-only");
  }
  get calculate() {
    return this._calculate;
  }
  set calculate(calculate) {
    this._calculate = calculate;
  }
  get creator() {
    return this._creator;
  }
  set creator(_) {
    throw new Error("doc.creator is read-only");
  }
  get dataObjects() {
    return [];
  }
  set dataObjects(_) {
    throw new Error("doc.dataObjects is read-only");
  }
  get delay() {
    return this._delay;
  }
  set delay(delay) {
    this._delay = delay;
  }
  get dirty() {
    return this._dirty;
  }
  set dirty(dirty) {
    this._dirty = dirty;
  }
  get disclosed() {
    return this._disclosed;
  }
  set disclosed(disclosed) {
    this._disclosed = disclosed;
  }
  get docID() {
    return this._docID;
  }
  set docID(_) {
    throw new Error("doc.docID is read-only");
  }
  get documentFileName() {
    return this._documentFileName;
  }
  set documentFileName(_) {
    throw new Error("doc.documentFileName is read-only");
  }
  get dynamicXFAForm() {
    return false;
  }
  set dynamicXFAForm(_) {
    throw new Error("doc.dynamicXFAForm is read-only");
  }
  get external() {
    return DOC_EXTERNAL;
  }
  set external(_) {
    throw new Error("doc.external is read-only");
  }
  get filesize() {
    return this._filesize;
  }
  set filesize(_) {
    throw new Error("doc.filesize is read-only");
  }
  get hidden() {
    return false;
  }
  set hidden(_) {
    throw new Error("doc.hidden is read-only");
  }
  get hostContainer() {
    return undefined;
  }
  set hostContainer(_) {
    throw new Error("doc.hostContainer is read-only");
  }
  get icons() {
    return undefined;
  }
  set icons(_) {
    throw new Error("doc.icons is read-only");
  }
  get info() {
    return this._info;
  }
  set info(_) {
    throw new Error("doc.info is read-only");
  }
  get innerAppWindowRect() {
    return [0, 0, 0, 0];
  }
  set innerAppWindowRect(_) {
    throw new Error("doc.innerAppWindowRect is read-only");
  }
  get innerDocWindowRect() {
    return [0, 0, 0, 0];
  }
  set innerDocWindowRect(_) {
    throw new Error("doc.innerDocWindowRect is read-only");
  }
  get isModal() {
    return false;
  }
  set isModal(_) {
    throw new Error("doc.isModal is read-only");
  }
  get keywords() {
    return this._keywords;
  }
  set keywords(_) {
    throw new Error("doc.keywords is read-only");
  }
  get layout() {
    return this._layout;
  }
  set layout(value) {
    if (!this._userActivation) {
      return;
    }
    this._userActivation = false;
    if (typeof value !== "string") {
      return;
    }
    if (value !== "SinglePage" && value !== "OneColumn" && value !== "TwoColumnLeft" && value !== "TwoPageLeft" && value !== "TwoColumnRight" && value !== "TwoPageRight") {
      value = "SinglePage";
    }
    this._send({
      command: "layout",
      value
    });
    this._layout = value;
  }
  get media() {
    return this._media;
  }
  set media(media) {
    this._media = media;
  }
  get metadata() {
    return this._metadata;
  }
  set metadata(metadata) {
    this._metadata = metadata;
  }
  get modDate() {
    return this._modDate;
  }
  set modDate(_) {
    throw new Error("doc.modDate is read-only");
  }
  get mouseX() {
    return 0;
  }
  set mouseX(_) {
    throw new Error("doc.mouseX is read-only");
  }
  get mouseY() {
    return 0;
  }
  set mouseY(_) {
    throw new Error("doc.mouseY is read-only");
  }
  get noautocomplete() {
    return this._noautocomplete;
  }
  set noautocomplete(noautocomplete) {
    this._noautocomplete = noautocomplete;
  }
  get nocache() {
    return this._nocache;
  }
  set nocache(nocache) {
    this._nocache = nocache;
  }
  get numFields() {
    return this._numFields;
  }
  set numFields(_) {
    throw new Error("doc.numFields is read-only");
  }
  get numPages() {
    return this._numPages;
  }
  set numPages(_) {
    throw new Error("doc.numPages is read-only");
  }
  get numTemplates() {
    return 0;
  }
  set numTemplates(_) {
    throw new Error("doc.numTemplates is read-only");
  }
  get outerAppWindowRect() {
    return [0, 0, 0, 0];
  }
  set outerAppWindowRect(_) {
    throw new Error("doc.outerAppWindowRect is read-only");
  }
  get outerDocWindowRect() {
    return [0, 0, 0, 0];
  }
  set outerDocWindowRect(_) {
    throw new Error("doc.outerDocWindowRect is read-only");
  }
  get pageNum() {
    return this._pageNum;
  }
  set pageNum(value) {
    if (!this._userActivation) {
      return;
    }
    this._userActivation = false;
    if (typeof value !== "number" || value < 0 || value >= this._numPages) {
      return;
    }
    this._send({
      command: "page-num",
      value
    });
    this._pageNum = value;
  }
  get pageWindowRect() {
    return [0, 0, 0, 0];
  }
  set pageWindowRect(_) {
    throw new Error("doc.pageWindowRect is read-only");
  }
  get path() {
    return "";
  }
  set path(_) {
    throw new Error("doc.path is read-only");
  }
  get permStatusReady() {
    return true;
  }
  set permStatusReady(_) {
    throw new Error("doc.permStatusReady is read-only");
  }
  get producer() {
    return this._producer;
  }
  set producer(_) {
    throw new Error("doc.producer is read-only");
  }
  get requiresFullSave() {
    return false;
  }
  set requiresFullSave(_) {
    throw new Error("doc.requiresFullSave is read-only");
  }
  get securityHandler() {
    return this._securityHandler;
  }
  set securityHandler(_) {
    throw new Error("doc.securityHandler is read-only");
  }
  get selectedAnnots() {
    return [];
  }
  set selectedAnnots(_) {
    throw new Error("doc.selectedAnnots is read-only");
  }
  get sounds() {
    return [];
  }
  set sounds(_) {
    throw new Error("doc.sounds is read-only");
  }
  get spellDictionaryOrder() {
    return this._spellDictionaryOrder;
  }
  set spellDictionaryOrder(spellDictionaryOrder) {
    this._spellDictionaryOrder = spellDictionaryOrder;
  }
  get spellLanguageOrder() {
    return this._spellLanguageOrder;
  }
  set spellLanguageOrder(spellLanguageOrder) {
    this._spellLanguageOrder = spellLanguageOrder;
  }
  get subject() {
    return this._subject;
  }
  set subject(_) {
    throw new Error("doc.subject is read-only");
  }
  get templates() {
    return [];
  }
  set templates(_) {
    throw new Error("doc.templates is read-only");
  }
  get title() {
    return this._title;
  }
  set title(_) {
    throw new Error("doc.title is read-only");
  }
  get URL() {
    return this._URL;
  }
  set URL(_) {
    throw new Error("doc.URL is read-only");
  }
  get viewState() {
    return undefined;
  }
  set viewState(_) {
    throw new Error("doc.viewState is read-only");
  }
  get xfa() {
    return this._xfa;
  }
  set xfa(_) {
    throw new Error("doc.xfa is read-only");
  }
  get XFAForeground() {
    return false;
  }
  set XFAForeground(_) {
    throw new Error("doc.XFAForeground is read-only");
  }
  get zoomType() {
    return this._zoomType;
  }
  set zoomType(type) {
    if (!this._userActivation) {
      return;
    }
    this._userActivation = false;
    if (typeof type !== "string") {
      return;
    }
    switch (type) {
      case ZoomType.none:
        this._send({
          command: "zoom",
          value: 1
        });
        break;
      case ZoomType.fitP:
        this._send({
          command: "zoom",
          value: "page-fit"
        });
        break;
      case ZoomType.fitW:
        this._send({
          command: "zoom",
          value: "page-width"
        });
        break;
      case ZoomType.fitH:
        this._send({
          command: "zoom",
          value: "page-height"
        });
        break;
      case ZoomType.fitV:
        this._send({
          command: "zoom",
          value: "auto"
        });
        break;
      case ZoomType.pref:
      case ZoomType.refW:
        break;
      default:
        return;
    }
    this._zoomType = type;
  }
  get zoom() {
    return this._zoom;
  }
  set zoom(value) {
    if (!this._userActivation) {
      return;
    }
    this._userActivation = false;
    if (typeof value !== "number" || value < 8.33 || value > 6400) {
      return;
    }
    this._send({
      command: "zoom",
      value: value / 100
    });
  }
  addAnnot() {}
  addField() {}
  addIcon() {}
  addLink() {}
  addRecipientListCryptFilter() {}
  addRequirement() {}
  addScript() {}
  addThumbnails() {}
  addWatermarkFromFile() {}
  addWatermarkFromText() {}
  addWeblinks() {}
  bringToFront() {}
  calculateNow() {
    this._eventDispatcher.calculateNow();
  }
  closeDoc() {}
  colorConvertPage() {}
  createDataObject() {}
  createTemplate() {}
  deletePages() {}
  deleteSound() {}
  embedDocAsDataObject() {}
  embedOutputIntent() {}
  encryptForRecipients() {}
  encryptUsingPolicy() {}
  exportAsFDF() {}
  exportAsFDFStr() {}
  exportAsText() {}
  exportAsXFDF() {}
  exportAsXFDFStr() {}
  exportDataObject() {}
  exportXFAData() {}
  extractPages() {}
  flattenPages() {}
  getAnnot() {}
  getAnnots() {}
  getAnnot3D() {}
  getAnnots3D() {}
  getColorConvertAction() {}
  getDataObject() {}
  getDataObjectContents() {}
  _getField(cName) {
    if (cName && typeof cName === "object") {
      cName = cName.cName;
    }
    if (typeof cName !== "string") {
      throw new TypeError("Invalid field name: must be a string");
    }
    const searchedField = this._fields.get(cName);
    if (searchedField) {
      return searchedField;
    }
    const parts = cName.split("#");
    let childIndex = NaN;
    if (parts.length === 2) {
      childIndex = Math.floor(parseFloat(parts[1]));
      cName = parts[0];
    }
    for (const [name, field] of this._fields.entries()) {
      if (name.endsWith(cName)) {
        if (!isNaN(childIndex)) {
          const children = this._getChildren(name);
          if (childIndex < 0 || childIndex >= children.length) {
            childIndex = 0;
          }
          if (childIndex < children.length) {
            this._fields.set(cName, children[childIndex]);
            return children[childIndex];
          }
        }
        this._fields.set(cName, field);
        return field;
      }
    }
    return null;
  }
  getField(cName) {
    const field = this._getField(cName);
    if (!field) {
      return null;
    }
    return field.wrapped;
  }
  _getChildren(fieldName) {
    const len = fieldName.length;
    const children = [];
    const pattern = /^\.[^.]+$/;
    for (const [name, field] of this._fields.entries()) {
      if (name.startsWith(fieldName)) {
        const finalPart = name.slice(len);
        if (pattern.test(finalPart)) {
          children.push(field);
        }
      }
    }
    return children;
  }
  _getTerminalChildren(fieldName) {
    const children = [];
    const len = fieldName.length;
    for (const [name, field] of this._fields.entries()) {
      if (name.startsWith(fieldName)) {
        const finalPart = name.slice(len);
        if (field.obj._hasValue && (finalPart === "" || finalPart.startsWith("."))) {
          children.push(field.wrapped);
        }
      }
    }
    return children;
  }
  getIcon() {}
  getLegalWarnings() {}
  getLinks() {}
  getNthFieldName(nIndex) {
    if (nIndex && typeof nIndex === "object") {
      nIndex = nIndex.nIndex;
    }
    if (typeof nIndex !== "number") {
      throw new TypeError("Invalid field index: must be a number");
    }
    if (0 <= nIndex && nIndex < this.numFields) {
      return this._fieldNames[Math.trunc(nIndex)];
    }
    return null;
  }
  getNthTemplate() {
    return null;
  }
  getOCGs() {}
  getOCGOrder() {}
  getPageBox() {}
  getPageLabel() {}
  getPageNthWord() {}
  getPageNthWordQuads() {}
  getPageNumWords() {}
  getPageRotation() {}
  getPageTransition() {}
  getPrintParams() {
    return this._printParams ||= new PrintParams({
      lastPage: this._numPages - 1
    });
  }
  getSound() {}
  getTemplate() {}
  getURL() {}
  gotoNamedDest() {}
  importAnFDF() {}
  importAnXFDF() {}
  importDataObject() {}
  importIcon() {}
  importSound() {}
  importTextData() {}
  importXFAData() {}
  insertPages() {}
  mailDoc() {}
  mailForm() {}
  movePage() {}
  newPage() {}
  openDataObject() {}
  print(bUI = true, nStart = 0, nEnd = -1, bSilent = false, bShrinkToFit = false, bPrintAsImage = false, bReverse = false, bAnnotations = true, printParams = null) {
    if (this._disablePrinting || !this._userActivation) {
      return;
    }
    this._userActivation = false;
    if (bUI && typeof bUI === "object") {
      nStart = bUI.nStart;
      nEnd = bUI.nEnd;
      bSilent = bUI.bSilent;
      bShrinkToFit = bUI.bShrinkToFit;
      bPrintAsImage = bUI.bPrintAsImage;
      bReverse = bUI.bReverse;
      bAnnotations = bUI.bAnnotations;
      printParams = bUI.printParams;
      bUI = bUI.bUI;
    }
    if (printParams) {
      nStart = printParams.firstPage;
      nEnd = printParams.lastPage;
    }
    nStart = typeof nStart === "number" ? Math.max(0, Math.trunc(nStart)) : 0;
    nEnd = typeof nEnd === "number" ? Math.max(0, Math.trunc(nEnd)) : -1;
    this._send({
      command: "print",
      start: nStart,
      end: nEnd
    });
  }
  removeDataObject() {}
  removeField() {}
  removeIcon() {}
  removeLinks() {}
  removeRequirement() {}
  removeScript() {}
  removeTemplate() {}
  removeThumbnails() {}
  removeWeblinks() {}
  replacePages() {}
  resetForm(aFields = null) {
    if (aFields && typeof aFields === "object" && !Array.isArray(aFields)) {
      aFields = aFields.aFields;
    }
    if (aFields && !Array.isArray(aFields)) {
      aFields = [aFields];
    }
    let mustCalculate = false;
    let fieldsToReset;
    if (aFields) {
      fieldsToReset = [];
      for (const fieldName of aFields) {
        if (!fieldName) {
          continue;
        }
        if (typeof fieldName !== "string") {
          fieldsToReset = null;
          break;
        }
        const field = this._getField(fieldName);
        if (!field) {
          continue;
        }
        fieldsToReset.push(field);
        mustCalculate = true;
      }
    }
    if (!fieldsToReset) {
      fieldsToReset = this._fields.values();
      mustCalculate = this._fields.size !== 0;
    }
    for (const field of fieldsToReset) {
      field.obj.value = field.obj.defaultValue;
      this._send({
        id: field.obj._id,
        siblings: field.obj._siblings,
        value: field.obj.defaultValue,
        formattedValue: null,
        selRange: [0, 0]
      });
    }
    if (mustCalculate) {
      this.calculateNow();
    }
  }
  saveAs() {}
  scroll() {}
  selectPageNthWord() {}
  setAction() {}
  setDataObjectContents() {}
  setOCGOrder() {}
  setPageAction() {}
  setPageBoxes() {}
  setPageLabels() {}
  setPageRotations() {}
  setPageTabOrder() {}
  setPageTransitions() {}
  spawnPageFromTemplate() {}
  submitForm() {}
  syncAnnotScan() {}
}

;// CONCATENATED MODULE: ./src/scripting_api/proxy.js
class ProxyHandler {
  constructor() {
    this.nosend = new Set(["delay"]);
  }
  get(obj, prop) {
    if (prop in obj._expandos) {
      const val = obj._expandos[prop];
      if (typeof val === "function") {
        return val.bind(obj);
      }
      return val;
    }
    if (typeof prop === "string" && !prop.startsWith("_") && prop in obj) {
      const val = obj[prop];
      if (typeof val === "function") {
        return val.bind(obj);
      }
      return val;
    }
    return undefined;
  }
  set(obj, prop, value) {
    if (obj._kidIds) {
      obj._kidIds.forEach(id => {
        obj._appObjects[id].wrapped[prop] = value;
      });
    }
    if (typeof prop === "string" && !prop.startsWith("_") && prop in obj) {
      const old = obj[prop];
      obj[prop] = value;
      if (!this.nosend.has(prop) && obj._send && obj._id !== null && typeof old !== "function") {
        const data = {
          id: obj._id
        };
        data[prop] = prop === "value" ? obj._getValue() : obj[prop];
        if (!obj._siblings) {
          obj._send(data);
        } else {
          data.siblings = obj._siblings;
          obj._send(data);
        }
      }
    } else {
      obj._expandos[prop] = value;
    }
    return true;
  }
  has(obj, prop) {
    return prop in obj._expandos || typeof prop === "string" && !prop.startsWith("_") && prop in obj;
  }
  getPrototypeOf(obj) {
    return null;
  }
  setPrototypeOf(obj, proto) {
    return false;
  }
  isExtensible(obj) {
    return true;
  }
  preventExtensions(obj) {
    return false;
  }
  getOwnPropertyDescriptor(obj, prop) {
    if (prop in obj._expandos) {
      return {
        configurable: true,
        enumerable: true,
        value: obj._expandos[prop]
      };
    }
    if (typeof prop === "string" && !prop.startsWith("_") && prop in obj) {
      return {
        configurable: true,
        enumerable: true,
        value: obj[prop]
      };
    }
    return undefined;
  }
  defineProperty(obj, key, descriptor) {
    Object.defineProperty(obj._expandos, key, descriptor);
    return true;
  }
  deleteProperty(obj, prop) {
    if (prop in obj._expandos) {
      delete obj._expandos[prop];
    }
  }
  ownKeys(obj) {
    const fromExpandos = Reflect.ownKeys(obj._expandos);
    const fromObj = Reflect.ownKeys(obj).filter(k => !k.startsWith("_"));
    return fromExpandos.concat(fromObj);
  }
}

;// CONCATENATED MODULE: ./src/scripting_api/util.js

class Util extends PDFObject {
  constructor(data) {
    super(data);
    this._scandCache = new Map();
    this._months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
    this._days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
    this.MILLISECONDS_IN_DAY = 86400000;
    this.MILLISECONDS_IN_WEEK = 604800000;
    this._externalCall = data.externalCall;
  }
  printf(...args) {
    if (args.length === 0) {
      throw new Error("Invalid number of params in printf");
    }
    if (typeof args[0] !== "string") {
      throw new TypeError("First argument of printf must be a string");
    }
    const pattern = /%(,[0-4])?([+ 0#]+)?(\d+)?(\.\d+)?(.)/g;
    const PLUS = 1;
    const SPACE = 2;
    const ZERO = 4;
    const HASH = 8;
    let i = 0;
    return args[0].replaceAll(pattern, function (match, nDecSep, cFlags, nWidth, nPrecision, cConvChar) {
      if (cConvChar !== "d" && cConvChar !== "f" && cConvChar !== "s" && cConvChar !== "x") {
        const buf = ["%"];
        for (const str of [nDecSep, cFlags, nWidth, nPrecision, cConvChar]) {
          if (str) {
            buf.push(str);
          }
        }
        return buf.join("");
      }
      i++;
      if (i === args.length) {
        throw new Error("Not enough arguments in printf");
      }
      const arg = args[i];
      if (cConvChar === "s") {
        return arg.toString();
      }
      let flags = 0;
      if (cFlags) {
        for (const flag of cFlags) {
          switch (flag) {
            case "+":
              flags |= PLUS;
              break;
            case " ":
              flags |= SPACE;
              break;
            case "0":
              flags |= ZERO;
              break;
            case "#":
              flags |= HASH;
              break;
          }
        }
      }
      cFlags = flags;
      if (nWidth) {
        nWidth = parseInt(nWidth);
      }
      let intPart = Math.trunc(arg);
      if (cConvChar === "x") {
        let hex = Math.abs(intPart).toString(16).toUpperCase();
        if (nWidth !== undefined) {
          hex = hex.padStart(nWidth, cFlags & ZERO ? "0" : " ");
        }
        if (cFlags & HASH) {
          hex = `0x${hex}`;
        }
        return hex;
      }
      if (nPrecision) {
        nPrecision = parseInt(nPrecision.substring(1));
      }
      nDecSep = nDecSep ? nDecSep.substring(1) : "0";
      const separators = {
        0: [",", "."],
        1: ["", "."],
        2: [".", ","],
        3: ["", ","],
        4: ["'", "."]
      };
      const [thousandSep, decimalSep] = separators[nDecSep];
      let decPart = "";
      if (cConvChar === "f") {
        decPart = nPrecision !== undefined ? Math.abs(arg - intPart).toFixed(nPrecision) : Math.abs(arg - intPart).toString();
        if (decPart.length > 2) {
          decPart = `${decimalSep}${decPart.substring(2)}`;
        } else {
          if (decPart === "1") {
            intPart += Math.sign(arg);
          }
          decPart = cFlags & HASH ? "." : "";
        }
      }
      let sign = "";
      if (intPart < 0) {
        sign = "-";
        intPart = -intPart;
      } else if (cFlags & PLUS) {
        sign = "+";
      } else if (cFlags & SPACE) {
        sign = " ";
      }
      if (thousandSep && intPart >= 1000) {
        const buf = [];
        while (true) {
          buf.push((intPart % 1000).toString().padStart(3, "0"));
          intPart = Math.trunc(intPart / 1000);
          if (intPart < 1000) {
            buf.push(intPart.toString());
            break;
          }
        }
        intPart = buf.reverse().join(thousandSep);
      } else {
        intPart = intPart.toString();
      }
      let n = `${intPart}${decPart}`;
      if (nWidth !== undefined) {
        n = n.padStart(nWidth - sign.length, cFlags & ZERO ? "0" : " ");
      }
      return `${sign}${n}`;
    });
  }
  iconStreamFromIcon() {}
  printd(cFormat, oDate) {
    switch (cFormat) {
      case 0:
        return this.printd("D:yyyymmddHHMMss", oDate);
      case 1:
        return this.printd("yyyy.mm.dd HH:MM:ss", oDate);
      case 2:
        return this.printd("m/d/yy h:MM:ss tt", oDate);
    }
    const handlers = {
      mmmm: data => this._months[data.month],
      mmm: data => this._months[data.month].substring(0, 3),
      mm: data => (data.month + 1).toString().padStart(2, "0"),
      m: data => (data.month + 1).toString(),
      dddd: data => this._days[data.dayOfWeek],
      ddd: data => this._days[data.dayOfWeek].substring(0, 3),
      dd: data => data.day.toString().padStart(2, "0"),
      d: data => data.day.toString(),
      yyyy: data => data.year.toString(),
      yy: data => (data.year % 100).toString().padStart(2, "0"),
      HH: data => data.hours.toString().padStart(2, "0"),
      H: data => data.hours.toString(),
      hh: data => (1 + (data.hours + 11) % 12).toString().padStart(2, "0"),
      h: data => (1 + (data.hours + 11) % 12).toString(),
      MM: data => data.minutes.toString().padStart(2, "0"),
      M: data => data.minutes.toString(),
      ss: data => data.seconds.toString().padStart(2, "0"),
      s: data => data.seconds.toString(),
      tt: data => data.hours < 12 ? "am" : "pm",
      t: data => data.hours < 12 ? "a" : "p"
    };
    const data = {
      year: oDate.getFullYear(),
      month: oDate.getMonth(),
      day: oDate.getDate(),
      dayOfWeek: oDate.getDay(),
      hours: oDate.getHours(),
      minutes: oDate.getMinutes(),
      seconds: oDate.getSeconds()
    };
    const patterns = /(mmmm|mmm|mm|m|dddd|ddd|dd|d|yyyy|yy|HH|H|hh|h|MM|M|ss|s|tt|t|\\.)/g;
    return cFormat.replaceAll(patterns, function (match, pattern) {
      if (pattern in handlers) {
        return handlers[pattern](data);
      }
      return pattern.charCodeAt(1);
    });
  }
  printx(cFormat, cSource) {
    cSource = (cSource ?? "").toString();
    const handlers = [x => x, x => x.toUpperCase(), x => x.toLowerCase()];
    const buf = [];
    let i = 0;
    const ii = cSource.length;
    let currCase = handlers[0];
    let escaped = false;
    for (const command of cFormat) {
      if (escaped) {
        buf.push(command);
        escaped = false;
        continue;
      }
      if (i >= ii) {
        break;
      }
      switch (command) {
        case "?":
          buf.push(currCase(cSource.charAt(i++)));
          break;
        case "X":
          while (i < ii) {
            const char = cSource.charAt(i++);
            if ("a" <= char && char <= "z" || "A" <= char && char <= "Z" || "0" <= char && char <= "9") {
              buf.push(currCase(char));
              break;
            }
          }
          break;
        case "A":
          while (i < ii) {
            const char = cSource.charAt(i++);
            if ("a" <= char && char <= "z" || "A" <= char && char <= "Z") {
              buf.push(currCase(char));
              break;
            }
          }
          break;
        case "9":
          while (i < ii) {
            const char = cSource.charAt(i++);
            if ("0" <= char && char <= "9") {
              buf.push(char);
              break;
            }
          }
          break;
        case "*":
          while (i < ii) {
            buf.push(currCase(cSource.charAt(i++)));
          }
          break;
        case "\\":
          escaped = true;
          break;
        case ">":
          currCase = handlers[1];
          break;
        case "<":
          currCase = handlers[2];
          break;
        case "=":
          currCase = handlers[0];
          break;
        default:
          buf.push(command);
      }
    }
    return buf.join("");
  }
  scand(cFormat, cDate) {
    if (typeof cDate !== "string") {
      return new Date(cDate);
    }
    if (cDate === "") {
      return new Date();
    }
    switch (cFormat) {
      case 0:
        return this.scand("D:yyyymmddHHMMss", cDate);
      case 1:
        return this.scand("yyyy.mm.dd HH:MM:ss", cDate);
      case 2:
        return this.scand("m/d/yy h:MM:ss tt", cDate);
    }
    if (!this._scandCache.has(cFormat)) {
      const months = this._months;
      const days = this._days;
      const handlers = {
        mmmm: {
          pattern: `(${months.join("|")})`,
          action: (value, data) => {
            data.month = months.indexOf(value);
          }
        },
        mmm: {
          pattern: `(${months.map(month => month.substring(0, 3)).join("|")})`,
          action: (value, data) => {
            data.month = months.findIndex(month => month.substring(0, 3) === value);
          }
        },
        mm: {
          pattern: `(\\d{2})`,
          action: (value, data) => {
            data.month = parseInt(value) - 1;
          }
        },
        m: {
          pattern: `(\\d{1,2})`,
          action: (value, data) => {
            data.month = parseInt(value) - 1;
          }
        },
        dddd: {
          pattern: `(${days.join("|")})`,
          action: (value, data) => {
            data.day = days.indexOf(value);
          }
        },
        ddd: {
          pattern: `(${days.map(day => day.substring(0, 3)).join("|")})`,
          action: (value, data) => {
            data.day = days.findIndex(day => day.substring(0, 3) === value);
          }
        },
        dd: {
          pattern: "(\\d{2})",
          action: (value, data) => {
            data.day = parseInt(value);
          }
        },
        d: {
          pattern: "(\\d{1,2})",
          action: (value, data) => {
            data.day = parseInt(value);
          }
        },
        yyyy: {
          pattern: "(\\d{4})",
          action: (value, data) => {
            data.year = parseInt(value);
          }
        },
        yy: {
          pattern: "(\\d{2})",
          action: (value, data) => {
            data.year = 2000 + parseInt(value);
          }
        },
        HH: {
          pattern: "(\\d{2})",
          action: (value, data) => {
            data.hours = parseInt(value);
          }
        },
        H: {
          pattern: "(\\d{1,2})",
          action: (value, data) => {
            data.hours = parseInt(value);
          }
        },
        hh: {
          pattern: "(\\d{2})",
          action: (value, data) => {
            data.hours = parseInt(value);
          }
        },
        h: {
          pattern: "(\\d{1,2})",
          action: (value, data) => {
            data.hours = parseInt(value);
          }
        },
        MM: {
          pattern: "(\\d{2})",
          action: (value, data) => {
            data.minutes = parseInt(value);
          }
        },
        M: {
          pattern: "(\\d{1,2})",
          action: (value, data) => {
            data.minutes = parseInt(value);
          }
        },
        ss: {
          pattern: "(\\d{2})",
          action: (value, data) => {
            data.seconds = parseInt(value);
          }
        },
        s: {
          pattern: "(\\d{1,2})",
          action: (value, data) => {
            data.seconds = parseInt(value);
          }
        },
        tt: {
          pattern: "([aApP][mM])",
          action: (value, data) => {
            const char = value.charAt(0);
            data.am = char === "a" || char === "A";
          }
        },
        t: {
          pattern: "([aApP])",
          action: (value, data) => {
            data.am = value === "a" || value === "A";
          }
        }
      };
      const escapedFormat = cFormat.replaceAll(/[.*+\-?^${}()|[\]\\]/g, "\\$&");
      const patterns = /(mmmm|mmm|mm|m|dddd|ddd|dd|d|yyyy|yy|HH|H|hh|h|MM|M|ss|s|tt|t)/g;
      const actions = [];
      const re = escapedFormat.replaceAll(patterns, function (match, patternElement) {
        const {
          pattern,
          action
        } = handlers[patternElement];
        actions.push(action);
        return pattern;
      });
      this._scandCache.set(cFormat, [re, actions]);
    }
    const [re, actions] = this._scandCache.get(cFormat);
    const matches = new RegExp(`^${re}$`, "g").exec(cDate);
    if (!matches || matches.length !== actions.length + 1) {
      return null;
    }
    const data = {
      year: 2000,
      month: 0,
      day: 1,
      hours: 0,
      minutes: 0,
      seconds: 0,
      am: null
    };
    actions.forEach((action, i) => action(matches[i + 1], data));
    if (data.am !== null) {
      data.hours = data.hours % 12 + (data.am ? 0 : 12);
    }
    return new Date(data.year, data.month, data.day, data.hours, data.minutes, data.seconds);
  }
  spansToXML() {}
  stringFromStream() {}
  xmlToSpans() {}
}

;// CONCATENATED MODULE: ./src/scripting_api/initialization.js










function initSandbox(params) {
  delete globalThis.pdfjsScripting;
  const externalCall = globalThis.callExternalFunction;
  delete globalThis.callExternalFunction;
  const globalEval = code => globalThis.eval(code);
  const send = data => externalCall("send", [data]);
  const proxyHandler = new ProxyHandler();
  const {
    data
  } = params;
  const doc = new Doc({
    send,
    globalEval,
    ...data.docInfo
  });
  const _document = {
    obj: doc,
    wrapped: new Proxy(doc, proxyHandler)
  };
  const app = new App({
    send,
    globalEval,
    externalCall,
    _document,
    calculationOrder: data.calculationOrder,
    proxyHandler,
    ...data.appInfo
  });
  const util = new Util({
    externalCall
  });
  const appObjects = app._objects;
  if (data.objects) {
    const annotations = [];
    for (const [name, objs] of Object.entries(data.objects)) {
      annotations.length = 0;
      let container = null;
      for (const obj of objs) {
        if (obj.type !== "") {
          annotations.push(obj);
        } else {
          container = obj;
        }
      }
      let obj = container;
      if (annotations.length > 0) {
        obj = annotations[0];
        obj.send = send;
      }
      obj.globalEval = globalEval;
      obj.doc = _document;
      obj.fieldPath = name;
      obj.appObjects = appObjects;
      let field;
      switch (obj.type) {
        case "radiobutton":
          {
            const otherButtons = annotations.slice(1);
            field = new RadioButtonField(otherButtons, obj);
            break;
          }
        case "checkbox":
          {
            const otherButtons = annotations.slice(1);
            field = new CheckboxField(otherButtons, obj);
            break;
          }
        case "text":
          if (annotations.length <= 1) {
            field = new Field(obj);
            break;
          }
          obj.siblings = annotations.map(x => x.id).slice(1);
          field = new Field(obj);
          break;
        default:
          field = new Field(obj);
      }
      const wrapped = new Proxy(field, proxyHandler);
      const _object = {
        obj: field,
        wrapped
      };
      doc._addField(name, _object);
      for (const object of objs) {
        appObjects[object.id] = _object;
      }
      if (container) {
        appObjects[container.id] = _object;
      }
    }
  }
  const color = new Color();
  globalThis.event = null;
  globalThis.global = Object.create(null);
  globalThis.app = new Proxy(app, proxyHandler);
  globalThis.color = new Proxy(color, proxyHandler);
  globalThis.console = new Proxy(new Console({
    send
  }), proxyHandler);
  globalThis.util = new Proxy(util, proxyHandler);
  globalThis.border = Border;
  globalThis.cursor = Cursor;
  globalThis.display = Display;
  globalThis.font = Font;
  globalThis.highlight = Highlight;
  globalThis.position = Position;
  globalThis.scaleHow = ScaleHow;
  globalThis.scaleWhen = ScaleWhen;
  globalThis.style = Style;
  globalThis.trans = Trans;
  globalThis.zoomtype = ZoomType;
  globalThis.ADBE = {
    Reader_Value_Asked: true,
    Viewer_Value_Asked: true
  };
  const aform = new AForm(doc, app, util, color);
  for (const name of Object.getOwnPropertyNames(AForm.prototype)) {
    if (name !== "constructor" && !name.startsWith("_")) {
      globalThis[name] = aform[name].bind(aform);
    }
  }
  for (const [name, value] of Object.entries(GlobalConstants)) {
    Object.defineProperty(globalThis, name, {
      value,
      writable: false
    });
  }
  Object.defineProperties(globalThis, {
    ColorConvert: {
      value: color.convert.bind(color),
      writable: true
    },
    ColorEqual: {
      value: color.equal.bind(color),
      writable: true
    }
  });
  const properties = Object.create(null);
  for (const name of Object.getOwnPropertyNames(Doc.prototype)) {
    if (name === "constructor" || name.startsWith("_")) {
      continue;
    }
    const descriptor = Object.getOwnPropertyDescriptor(Doc.prototype, name);
    if (descriptor.get) {
      properties[name] = {
        get: descriptor.get.bind(doc),
        set: descriptor.set.bind(doc)
      };
    } else {
      properties[name] = {
        value: Doc.prototype[name].bind(doc)
      };
    }
  }
  Object.defineProperties(globalThis, properties);
  const functions = {
    dispatchEvent: app._dispatchEvent.bind(app),
    timeoutCb: app._evalCallback.bind(app)
  };
  return (name, args) => {
    try {
      functions[name](args);
    } catch (error) {
      send(serializeError(error));
    }
  };
}

;// CONCATENATED MODULE: ./src/pdf.scripting.js

const pdfjsVersion = "4.6.60";
const pdfjsBuild = "10a846417";
globalThis.pdfjsScripting = {
  initSandbox: initSandbox
};
PK
!<���HUd!Ud!)chrome/pdfjs/content/build/pdf.worker.mjs/**
 * @licstart The following is the entire license notice for the
 * JavaScript code in this page
 *
 * Copyright 2024 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @licend The above is the entire license notice for the
 * JavaScript code in this page
 */

/******/ // The require scope
/******/ var __webpack_require__ = {};
/******/ 
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ 	// define getter functions for harmony exports
/******/ 	__webpack_require__.d = (exports, definition) => {
/******/ 		for(var key in definition) {
/******/ 			if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ 				Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ 			}
/******/ 		}
/******/ 	};
/******/ })();
/******/ 
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ 	__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/ 
/************************************************************************/
var __webpack_exports__ = globalThis.pdfjsWorker = {};

// EXPORTS
__webpack_require__.d(__webpack_exports__, {
  WorkerMessageHandler: () => (/* reexport */ WorkerMessageHandler)
});

;// CONCATENATED MODULE: ./src/shared/util.js
const isNodeJS = false;
const IDENTITY_MATRIX = [1, 0, 0, 1, 0, 0];
const FONT_IDENTITY_MATRIX = [0.001, 0, 0, 0.001, 0, 0];
const MAX_IMAGE_SIZE_TO_CACHE = 10e6;
const LINE_FACTOR = 1.35;
const LINE_DESCENT_FACTOR = 0.35;
const BASELINE_FACTOR = LINE_DESCENT_FACTOR / LINE_FACTOR;
const RenderingIntentFlag = {
  ANY: 0x01,
  DISPLAY: 0x02,
  PRINT: 0x04,
  SAVE: 0x08,
  ANNOTATIONS_FORMS: 0x10,
  ANNOTATIONS_STORAGE: 0x20,
  ANNOTATIONS_DISABLE: 0x40,
  IS_EDITING: 0x80,
  OPLIST: 0x100
};
const AnnotationMode = {
  DISABLE: 0,
  ENABLE: 1,
  ENABLE_FORMS: 2,
  ENABLE_STORAGE: 3
};
const AnnotationEditorPrefix = "pdfjs_internal_editor_";
const AnnotationEditorType = {
  DISABLE: -1,
  NONE: 0,
  FREETEXT: 3,
  HIGHLIGHT: 9,
  STAMP: 13,
  INK: 15
};
const AnnotationEditorParamsType = {
  RESIZE: 1,
  CREATE: 2,
  FREETEXT_SIZE: 11,
  FREETEXT_COLOR: 12,
  FREETEXT_OPACITY: 13,
  INK_COLOR: 21,
  INK_THICKNESS: 22,
  INK_OPACITY: 23,
  HIGHLIGHT_COLOR: 31,
  HIGHLIGHT_DEFAULT_COLOR: 32,
  HIGHLIGHT_THICKNESS: 33,
  HIGHLIGHT_FREE: 34,
  HIGHLIGHT_SHOW_ALL: 35
};
const PermissionFlag = {
  PRINT: 0x04,
  MODIFY_CONTENTS: 0x08,
  COPY: 0x10,
  MODIFY_ANNOTATIONS: 0x20,
  FILL_INTERACTIVE_FORMS: 0x100,
  COPY_FOR_ACCESSIBILITY: 0x200,
  ASSEMBLE: 0x400,
  PRINT_HIGH_QUALITY: 0x800
};
const TextRenderingMode = {
  FILL: 0,
  STROKE: 1,
  FILL_STROKE: 2,
  INVISIBLE: 3,
  FILL_ADD_TO_PATH: 4,
  STROKE_ADD_TO_PATH: 5,
  FILL_STROKE_ADD_TO_PATH: 6,
  ADD_TO_PATH: 7,
  FILL_STROKE_MASK: 3,
  ADD_TO_PATH_FLAG: 4
};
const ImageKind = {
  GRAYSCALE_1BPP: 1,
  RGB_24BPP: 2,
  RGBA_32BPP: 3
};
const AnnotationType = {
  TEXT: 1,
  LINK: 2,
  FREETEXT: 3,
  LINE: 4,
  SQUARE: 5,
  CIRCLE: 6,
  POLYGON: 7,
  POLYLINE: 8,
  HIGHLIGHT: 9,
  UNDERLINE: 10,
  SQUIGGLY: 11,
  STRIKEOUT: 12,
  STAMP: 13,
  CARET: 14,
  INK: 15,
  POPUP: 16,
  FILEATTACHMENT: 17,
  SOUND: 18,
  MOVIE: 19,
  WIDGET: 20,
  SCREEN: 21,
  PRINTERMARK: 22,
  TRAPNET: 23,
  WATERMARK: 24,
  THREED: 25,
  REDACT: 26
};
const AnnotationReplyType = {
  GROUP: "Group",
  REPLY: "R"
};
const AnnotationFlag = {
  INVISIBLE: 0x01,
  HIDDEN: 0x02,
  PRINT: 0x04,
  NOZOOM: 0x08,
  NOROTATE: 0x10,
  NOVIEW: 0x20,
  READONLY: 0x40,
  LOCKED: 0x80,
  TOGGLENOVIEW: 0x100,
  LOCKEDCONTENTS: 0x200
};
const AnnotationFieldFlag = {
  READONLY: 0x0000001,
  REQUIRED: 0x0000002,
  NOEXPORT: 0x0000004,
  MULTILINE: 0x0001000,
  PASSWORD: 0x0002000,
  NOTOGGLETOOFF: 0x0004000,
  RADIO: 0x0008000,
  PUSHBUTTON: 0x0010000,
  COMBO: 0x0020000,
  EDIT: 0x0040000,
  SORT: 0x0080000,
  FILESELECT: 0x0100000,
  MULTISELECT: 0x0200000,
  DONOTSPELLCHECK: 0x0400000,
  DONOTSCROLL: 0x0800000,
  COMB: 0x1000000,
  RICHTEXT: 0x2000000,
  RADIOSINUNISON: 0x2000000,
  COMMITONSELCHANGE: 0x4000000
};
const AnnotationBorderStyleType = {
  SOLID: 1,
  DASHED: 2,
  BEVELED: 3,
  INSET: 4,
  UNDERLINE: 5
};
const AnnotationActionEventType = {
  E: "Mouse Enter",
  X: "Mouse Exit",
  D: "Mouse Down",
  U: "Mouse Up",
  Fo: "Focus",
  Bl: "Blur",
  PO: "PageOpen",
  PC: "PageClose",
  PV: "PageVisible",
  PI: "PageInvisible",
  K: "Keystroke",
  F: "Format",
  V: "Validate",
  C: "Calculate"
};
const DocumentActionEventType = {
  WC: "WillClose",
  WS: "WillSave",
  DS: "DidSave",
  WP: "WillPrint",
  DP: "DidPrint"
};
const PageActionEventType = {
  O: "PageOpen",
  C: "PageClose"
};
const VerbosityLevel = {
  ERRORS: 0,
  WARNINGS: 1,
  INFOS: 5
};
const CMapCompressionType = {
  NONE: 0,
  BINARY: 1
};
const OPS = {
  dependency: 1,
  setLineWidth: 2,
  setLineCap: 3,
  setLineJoin: 4,
  setMiterLimit: 5,
  setDash: 6,
  setRenderingIntent: 7,
  setFlatness: 8,
  setGState: 9,
  save: 10,
  restore: 11,
  transform: 12,
  moveTo: 13,
  lineTo: 14,
  curveTo: 15,
  curveTo2: 16,
  curveTo3: 17,
  closePath: 18,
  rectangle: 19,
  stroke: 20,
  closeStroke: 21,
  fill: 22,
  eoFill: 23,
  fillStroke: 24,
  eoFillStroke: 25,
  closeFillStroke: 26,
  closeEOFillStroke: 27,
  endPath: 28,
  clip: 29,
  eoClip: 30,
  beginText: 31,
  endText: 32,
  setCharSpacing: 33,
  setWordSpacing: 34,
  setHScale: 35,
  setLeading: 36,
  setFont: 37,
  setTextRenderingMode: 38,
  setTextRise: 39,
  moveText: 40,
  setLeadingMoveText: 41,
  setTextMatrix: 42,
  nextLine: 43,
  showText: 44,
  showSpacedText: 45,
  nextLineShowText: 46,
  nextLineSetSpacingShowText: 47,
  setCharWidth: 48,
  setCharWidthAndBounds: 49,
  setStrokeColorSpace: 50,
  setFillColorSpace: 51,
  setStrokeColor: 52,
  setStrokeColorN: 53,
  setFillColor: 54,
  setFillColorN: 55,
  setStrokeGray: 56,
  setFillGray: 57,
  setStrokeRGBColor: 58,
  setFillRGBColor: 59,
  setStrokeCMYKColor: 60,
  setFillCMYKColor: 61,
  shadingFill: 62,
  beginInlineImage: 63,
  beginImageData: 64,
  endInlineImage: 65,
  paintXObject: 66,
  markPoint: 67,
  markPointProps: 68,
  beginMarkedContent: 69,
  beginMarkedContentProps: 70,
  endMarkedContent: 71,
  beginCompat: 72,
  endCompat: 73,
  paintFormXObjectBegin: 74,
  paintFormXObjectEnd: 75,
  beginGroup: 76,
  endGroup: 77,
  beginAnnotation: 80,
  endAnnotation: 81,
  paintImageMaskXObject: 83,
  paintImageMaskXObjectGroup: 84,
  paintImageXObject: 85,
  paintInlineImageXObject: 86,
  paintInlineImageXObjectGroup: 87,
  paintImageXObjectRepeat: 88,
  paintImageMaskXObjectRepeat: 89,
  paintSolidColorImageMask: 90,
  constructPath: 91,
  setStrokeTransparent: 92,
  setFillTransparent: 93
};
const PasswordResponses = {
  NEED_PASSWORD: 1,
  INCORRECT_PASSWORD: 2
};
let verbosity = VerbosityLevel.WARNINGS;
function setVerbosityLevel(level) {
  if (Number.isInteger(level)) {
    verbosity = level;
  }
}
function getVerbosityLevel() {
  return verbosity;
}
function info(msg) {
  if (verbosity >= VerbosityLevel.INFOS) {
    console.log(`Info: ${msg}`);
  }
}
function warn(msg) {
  if (verbosity >= VerbosityLevel.WARNINGS) {
    console.log(`Warning: ${msg}`);
  }
}
function unreachable(msg) {
  throw new Error(msg);
}
function assert(cond, msg) {
  if (!cond) {
    unreachable(msg);
  }
}
function _isValidProtocol(url) {
  switch (url?.protocol) {
    case "http:":
    case "https:":
    case "ftp:":
    case "mailto:":
    case "tel:":
      return true;
    default:
      return false;
  }
}
function createValidAbsoluteUrl(url, baseUrl = null, options = null) {
  if (!url) {
    return null;
  }
  try {
    if (options && typeof url === "string") {
      if (options.addDefaultProtocol && url.startsWith("www.")) {
        const dots = url.match(/\./g);
        if (dots?.length >= 2) {
          url = `http://${url}`;
        }
      }
      if (options.tryConvertEncoding) {
        try {
          url = stringToUTF8String(url);
        } catch {}
      }
    }
    const absoluteUrl = baseUrl ? new URL(url, baseUrl) : new URL(url);
    if (_isValidProtocol(absoluteUrl)) {
      return absoluteUrl;
    }
  } catch {}
  return null;
}
function shadow(obj, prop, value, nonSerializable = false) {
  Object.defineProperty(obj, prop, {
    value,
    enumerable: !nonSerializable,
    configurable: true,
    writable: false
  });
  return value;
}
const BaseException = function BaseExceptionClosure() {
  function BaseException(message, name) {
    this.message = message;
    this.name = name;
  }
  BaseException.prototype = new Error();
  BaseException.constructor = BaseException;
  return BaseException;
}();
class PasswordException extends BaseException {
  constructor(msg, code) {
    super(msg, "PasswordException");
    this.code = code;
  }
}
class UnknownErrorException extends BaseException {
  constructor(msg, details) {
    super(msg, "UnknownErrorException");
    this.details = details;
  }
}
class InvalidPDFException extends BaseException {
  constructor(msg) {
    super(msg, "InvalidPDFException");
  }
}
class MissingPDFException extends BaseException {
  constructor(msg) {
    super(msg, "MissingPDFException");
  }
}
class UnexpectedResponseException extends BaseException {
  constructor(msg, status) {
    super(msg, "UnexpectedResponseException");
    this.status = status;
  }
}
class FormatError extends BaseException {
  constructor(msg) {
    super(msg, "FormatError");
  }
}
class AbortException extends BaseException {
  constructor(msg) {
    super(msg, "AbortException");
  }
}
function bytesToString(bytes) {
  if (typeof bytes !== "object" || bytes?.length === undefined) {
    unreachable("Invalid argument for bytesToString");
  }
  const length = bytes.length;
  const MAX_ARGUMENT_COUNT = 8192;
  if (length < MAX_ARGUMENT_COUNT) {
    return String.fromCharCode.apply(null, bytes);
  }
  const strBuf = [];
  for (let i = 0; i < length; i += MAX_ARGUMENT_COUNT) {
    const chunkEnd = Math.min(i + MAX_ARGUMENT_COUNT, length);
    const chunk = bytes.subarray(i, chunkEnd);
    strBuf.push(String.fromCharCode.apply(null, chunk));
  }
  return strBuf.join("");
}
function stringToBytes(str) {
  if (typeof str !== "string") {
    unreachable("Invalid argument for stringToBytes");
  }
  const length = str.length;
  const bytes = new Uint8Array(length);
  for (let i = 0; i < length; ++i) {
    bytes[i] = str.charCodeAt(i) & 0xff;
  }
  return bytes;
}
function string32(value) {
  return String.fromCharCode(value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff);
}
function objectSize(obj) {
  return Object.keys(obj).length;
}
function objectFromMap(map) {
  const obj = Object.create(null);
  for (const [key, value] of map) {
    obj[key] = value;
  }
  return obj;
}
function isLittleEndian() {
  const buffer8 = new Uint8Array(4);
  buffer8[0] = 1;
  const view32 = new Uint32Array(buffer8.buffer, 0, 1);
  return view32[0] === 1;
}
function isEvalSupported() {
  try {
    new Function("");
    return true;
  } catch {
    return false;
  }
}
class FeatureTest {
  static get isLittleEndian() {
    return shadow(this, "isLittleEndian", isLittleEndian());
  }
  static get isEvalSupported() {
    return shadow(this, "isEvalSupported", isEvalSupported());
  }
  static get isOffscreenCanvasSupported() {
    return shadow(this, "isOffscreenCanvasSupported", typeof OffscreenCanvas !== "undefined");
  }
  static get platform() {
    return shadow(this, "platform", {
      isMac: navigator.platform.includes("Mac")
    });
  }
  static get isCSSRoundSupported() {
    return shadow(this, "isCSSRoundSupported", globalThis.CSS?.supports?.("width: round(1.5px, 1px)"));
  }
}
const hexNumbers = Array.from(Array(256).keys(), n => n.toString(16).padStart(2, "0"));
class Util {
  static makeHexColor(r, g, b) {
    return `#${hexNumbers[r]}${hexNumbers[g]}${hexNumbers[b]}`;
  }
  static scaleMinMax(transform, minMax) {
    let temp;
    if (transform[0]) {
      if (transform[0] < 0) {
        temp = minMax[0];
        minMax[0] = minMax[2];
        minMax[2] = temp;
      }
      minMax[0] *= transform[0];
      minMax[2] *= transform[0];
      if (transform[3] < 0) {
        temp = minMax[1];
        minMax[1] = minMax[3];
        minMax[3] = temp;
      }
      minMax[1] *= transform[3];
      minMax[3] *= transform[3];
    } else {
      temp = minMax[0];
      minMax[0] = minMax[1];
      minMax[1] = temp;
      temp = minMax[2];
      minMax[2] = minMax[3];
      minMax[3] = temp;
      if (transform[1] < 0) {
        temp = minMax[1];
        minMax[1] = minMax[3];
        minMax[3] = temp;
      }
      minMax[1] *= transform[1];
      minMax[3] *= transform[1];
      if (transform[2] < 0) {
        temp = minMax[0];
        minMax[0] = minMax[2];
        minMax[2] = temp;
      }
      minMax[0] *= transform[2];
      minMax[2] *= transform[2];
    }
    minMax[0] += transform[4];
    minMax[1] += transform[5];
    minMax[2] += transform[4];
    minMax[3] += transform[5];
  }
  static transform(m1, m2) {
    return [m1[0] * m2[0] + m1[2] * m2[1], m1[1] * m2[0] + m1[3] * m2[1], m1[0] * m2[2] + m1[2] * m2[3], m1[1] * m2[2] + m1[3] * m2[3], m1[0] * m2[4] + m1[2] * m2[5] + m1[4], m1[1] * m2[4] + m1[3] * m2[5] + m1[5]];
  }
  static applyTransform(p, m) {
    const xt = p[0] * m[0] + p[1] * m[2] + m[4];
    const yt = p[0] * m[1] + p[1] * m[3] + m[5];
    return [xt, yt];
  }
  static applyInverseTransform(p, m) {
    const d = m[0] * m[3] - m[1] * m[2];
    const xt = (p[0] * m[3] - p[1] * m[2] + m[2] * m[5] - m[4] * m[3]) / d;
    const yt = (-p[0] * m[1] + p[1] * m[0] + m[4] * m[1] - m[5] * m[0]) / d;
    return [xt, yt];
  }
  static getAxialAlignedBoundingBox(r, m) {
    const p1 = this.applyTransform(r, m);
    const p2 = this.applyTransform(r.slice(2, 4), m);
    const p3 = this.applyTransform([r[0], r[3]], m);
    const p4 = this.applyTransform([r[2], r[1]], m);
    return [Math.min(p1[0], p2[0], p3[0], p4[0]), Math.min(p1[1], p2[1], p3[1], p4[1]), Math.max(p1[0], p2[0], p3[0], p4[0]), Math.max(p1[1], p2[1], p3[1], p4[1])];
  }
  static inverseTransform(m) {
    const d = m[0] * m[3] - m[1] * m[2];
    return [m[3] / d, -m[1] / d, -m[2] / d, m[0] / d, (m[2] * m[5] - m[4] * m[3]) / d, (m[4] * m[1] - m[5] * m[0]) / d];
  }
  static singularValueDecompose2dScale(m) {
    const transpose = [m[0], m[2], m[1], m[3]];
    const a = m[0] * transpose[0] + m[1] * transpose[2];
    const b = m[0] * transpose[1] + m[1] * transpose[3];
    const c = m[2] * transpose[0] + m[3] * transpose[2];
    const d = m[2] * transpose[1] + m[3] * transpose[3];
    const first = (a + d) / 2;
    const second = Math.sqrt((a + d) ** 2 - 4 * (a * d - c * b)) / 2;
    const sx = first + second || 1;
    const sy = first - second || 1;
    return [Math.sqrt(sx), Math.sqrt(sy)];
  }
  static normalizeRect(rect) {
    const r = rect.slice(0);
    if (rect[0] > rect[2]) {
      r[0] = rect[2];
      r[2] = rect[0];
    }
    if (rect[1] > rect[3]) {
      r[1] = rect[3];
      r[3] = rect[1];
    }
    return r;
  }
  static intersect(rect1, rect2) {
    const xLow = Math.max(Math.min(rect1[0], rect1[2]), Math.min(rect2[0], rect2[2]));
    const xHigh = Math.min(Math.max(rect1[0], rect1[2]), Math.max(rect2[0], rect2[2]));
    if (xLow > xHigh) {
      return null;
    }
    const yLow = Math.max(Math.min(rect1[1], rect1[3]), Math.min(rect2[1], rect2[3]));
    const yHigh = Math.min(Math.max(rect1[1], rect1[3]), Math.max(rect2[1], rect2[3]));
    if (yLow > yHigh) {
      return null;
    }
    return [xLow, yLow, xHigh, yHigh];
  }
  static #getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, t, minMax) {
    if (t <= 0 || t >= 1) {
      return;
    }
    const mt = 1 - t;
    const tt = t * t;
    const ttt = tt * t;
    const x = mt * (mt * (mt * x0 + 3 * t * x1) + 3 * tt * x2) + ttt * x3;
    const y = mt * (mt * (mt * y0 + 3 * t * y1) + 3 * tt * y2) + ttt * y3;
    minMax[0] = Math.min(minMax[0], x);
    minMax[1] = Math.min(minMax[1], y);
    minMax[2] = Math.max(minMax[2], x);
    minMax[3] = Math.max(minMax[3], y);
  }
  static #getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, a, b, c, minMax) {
    if (Math.abs(a) < 1e-12) {
      if (Math.abs(b) >= 1e-12) {
        this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, -c / b, minMax);
      }
      return;
    }
    const delta = b ** 2 - 4 * c * a;
    if (delta < 0) {
      return;
    }
    const sqrtDelta = Math.sqrt(delta);
    const a2 = 2 * a;
    this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, (-b + sqrtDelta) / a2, minMax);
    this.#getExtremumOnCurve(x0, x1, x2, x3, y0, y1, y2, y3, (-b - sqrtDelta) / a2, minMax);
  }
  static bezierBoundingBox(x0, y0, x1, y1, x2, y2, x3, y3, minMax) {
    if (minMax) {
      minMax[0] = Math.min(minMax[0], x0, x3);
      minMax[1] = Math.min(minMax[1], y0, y3);
      minMax[2] = Math.max(minMax[2], x0, x3);
      minMax[3] = Math.max(minMax[3], y0, y3);
    } else {
      minMax = [Math.min(x0, x3), Math.min(y0, y3), Math.max(x0, x3), Math.max(y0, y3)];
    }
    this.#getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, 3 * (-x0 + 3 * (x1 - x2) + x3), 6 * (x0 - 2 * x1 + x2), 3 * (x1 - x0), minMax);
    this.#getExtremum(x0, x1, x2, x3, y0, y1, y2, y3, 3 * (-y0 + 3 * (y1 - y2) + y3), 6 * (y0 - 2 * y1 + y2), 3 * (y1 - y0), minMax);
    return minMax;
  }
}
const PDFStringTranslateTable = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2d8, 0x2c7, 0x2c6, 0x2d9, 0x2dd, 0x2db, 0x2da, 0x2dc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x2022, 0x2020, 0x2021, 0x2026, 0x2014, 0x2013, 0x192, 0x2044, 0x2039, 0x203a, 0x2212, 0x2030, 0x201e, 0x201c, 0x201d, 0x2018, 0x2019, 0x201a, 0x2122, 0xfb01, 0xfb02, 0x141, 0x152, 0x160, 0x178, 0x17d, 0x131, 0x142, 0x153, 0x161, 0x17e, 0, 0x20ac];
function stringToPDFString(str) {
  if (str[0] >= "\xEF") {
    let encoding;
    if (str[0] === "\xFE" && str[1] === "\xFF") {
      encoding = "utf-16be";
      if (str.length % 2 === 1) {
        str = str.slice(0, -1);
      }
    } else if (str[0] === "\xFF" && str[1] === "\xFE") {
      encoding = "utf-16le";
      if (str.length % 2 === 1) {
        str = str.slice(0, -1);
      }
    } else if (str[0] === "\xEF" && str[1] === "\xBB" && str[2] === "\xBF") {
      encoding = "utf-8";
    }
    if (encoding) {
      try {
        const decoder = new TextDecoder(encoding, {
          fatal: true
        });
        const buffer = stringToBytes(str);
        const decoded = decoder.decode(buffer);
        if (!decoded.includes("\x1b")) {
          return decoded;
        }
        return decoded.replaceAll(/\x1b[^\x1b]*(?:\x1b|$)/g, "");
      } catch (ex) {
        warn(`stringToPDFString: "${ex}".`);
      }
    }
  }
  const strBuf = [];
  for (let i = 0, ii = str.length; i < ii; i++) {
    const charCode = str.charCodeAt(i);
    if (charCode === 0x1b) {
      while (++i < ii && str.charCodeAt(i) !== 0x1b) {}
      continue;
    }
    const code = PDFStringTranslateTable[charCode];
    strBuf.push(code ? String.fromCharCode(code) : str.charAt(i));
  }
  return strBuf.join("");
}
function stringToUTF8String(str) {
  return decodeURIComponent(escape(str));
}
function utf8StringToString(str) {
  return unescape(encodeURIComponent(str));
}
function isArrayEqual(arr1, arr2) {
  if (arr1.length !== arr2.length) {
    return false;
  }
  for (let i = 0, ii = arr1.length; i < ii; i++) {
    if (arr1[i] !== arr2[i]) {
      return false;
    }
  }
  return true;
}
function getModificationDate(date = new Date()) {
  const buffer = [date.getUTCFullYear().toString(), (date.getUTCMonth() + 1).toString().padStart(2, "0"), date.getUTCDate().toString().padStart(2, "0"), date.getUTCHours().toString().padStart(2, "0"), date.getUTCMinutes().toString().padStart(2, "0"), date.getUTCSeconds().toString().padStart(2, "0")];
  return buffer.join("");
}
let NormalizeRegex = null;
let NormalizationMap = null;
function normalizeUnicode(str) {
  if (!NormalizeRegex) {
    NormalizeRegex = /([\u00a0\u00b5\u037e\u0eb3\u2000-\u200a\u202f\u2126\ufb00-\ufb04\ufb06\ufb20-\ufb36\ufb38-\ufb3c\ufb3e\ufb40-\ufb41\ufb43-\ufb44\ufb46-\ufba1\ufba4-\ufba9\ufbae-\ufbb1\ufbd3-\ufbdc\ufbde-\ufbe7\ufbea-\ufbf8\ufbfc-\ufbfd\ufc00-\ufc5d\ufc64-\ufcf1\ufcf5-\ufd3d\ufd88\ufdf4\ufdfa-\ufdfb\ufe71\ufe77\ufe79\ufe7b\ufe7d]+)|(\ufb05+)/gu;
    NormalizationMap = new Map([["ſt", "ſt"]]);
  }
  return str.replaceAll(NormalizeRegex, (_, p1, p2) => p1 ? p1.normalize("NFKC") : NormalizationMap.get(p2));
}
function getUuid() {
  return crypto.randomUUID();
}
const AnnotationPrefix = "pdfjs_internal_id_";
const FontRenderOps = {
  BEZIER_CURVE_TO: 0,
  MOVE_TO: 1,
  LINE_TO: 2,
  QUADRATIC_CURVE_TO: 3,
  RESTORE: 4,
  SAVE: 5,
  SCALE: 6,
  TRANSFORM: 7,
  TRANSLATE: 8
};

;// CONCATENATED MODULE: ./src/core/primitives.js

const CIRCULAR_REF = Symbol("CIRCULAR_REF");
const EOF = Symbol("EOF");
let CmdCache = Object.create(null);
let NameCache = Object.create(null);
let RefCache = Object.create(null);
function clearPrimitiveCaches() {
  CmdCache = Object.create(null);
  NameCache = Object.create(null);
  RefCache = Object.create(null);
}
class Name {
  constructor(name) {
    this.name = name;
  }
  static get(name) {
    return NameCache[name] ||= new Name(name);
  }
}
class Cmd {
  constructor(cmd) {
    this.cmd = cmd;
  }
  static get(cmd) {
    return CmdCache[cmd] ||= new Cmd(cmd);
  }
}
const nonSerializable = function nonSerializableClosure() {
  return nonSerializable;
};
class Dict {
  constructor(xref = null) {
    this._map = Object.create(null);
    this.xref = xref;
    this.objId = null;
    this.suppressEncryption = false;
    this.__nonSerializable__ = nonSerializable;
  }
  assignXref(newXref) {
    this.xref = newXref;
  }
  get size() {
    return Object.keys(this._map).length;
  }
  get(key1, key2, key3) {
    let value = this._map[key1];
    if (value === undefined && key2 !== undefined) {
      value = this._map[key2];
      if (value === undefined && key3 !== undefined) {
        value = this._map[key3];
      }
    }
    if (value instanceof Ref && this.xref) {
      return this.xref.fetch(value, this.suppressEncryption);
    }
    return value;
  }
  async getAsync(key1, key2, key3) {
    let value = this._map[key1];
    if (value === undefined && key2 !== undefined) {
      value = this._map[key2];
      if (value === undefined && key3 !== undefined) {
        value = this._map[key3];
      }
    }
    if (value instanceof Ref && this.xref) {
      return this.xref.fetchAsync(value, this.suppressEncryption);
    }
    return value;
  }
  getArray(key1, key2, key3) {
    let value = this._map[key1];
    if (value === undefined && key2 !== undefined) {
      value = this._map[key2];
      if (value === undefined && key3 !== undefined) {
        value = this._map[key3];
      }
    }
    if (value instanceof Ref && this.xref) {
      value = this.xref.fetch(value, this.suppressEncryption);
    }
    if (Array.isArray(value)) {
      value = value.slice();
      for (let i = 0, ii = value.length; i < ii; i++) {
        if (value[i] instanceof Ref && this.xref) {
          value[i] = this.xref.fetch(value[i], this.suppressEncryption);
        }
      }
    }
    return value;
  }
  getRaw(key) {
    return this._map[key];
  }
  getKeys() {
    return Object.keys(this._map);
  }
  getRawValues() {
    return Object.values(this._map);
  }
  set(key, value) {
    this._map[key] = value;
  }
  has(key) {
    return this._map[key] !== undefined;
  }
  forEach(callback) {
    for (const key in this._map) {
      callback(key, this.get(key));
    }
  }
  static get empty() {
    const emptyDict = new Dict(null);
    emptyDict.set = (key, value) => {
      unreachable("Should not call `set` on the empty dictionary.");
    };
    return shadow(this, "empty", emptyDict);
  }
  static merge({
    xref,
    dictArray,
    mergeSubDicts = false
  }) {
    const mergedDict = new Dict(xref),
      properties = new Map();
    for (const dict of dictArray) {
      if (!(dict instanceof Dict)) {
        continue;
      }
      for (const [key, value] of Object.entries(dict._map)) {
        let property = properties.get(key);
        if (property === undefined) {
          property = [];
          properties.set(key, property);
        } else if (!mergeSubDicts || !(value instanceof Dict)) {
          continue;
        }
        property.push(value);
      }
    }
    for (const [name, values] of properties) {
      if (values.length === 1 || !(values[0] instanceof Dict)) {
        mergedDict._map[name] = values[0];
        continue;
      }
      const subDict = new Dict(xref);
      for (const dict of values) {
        for (const [key, value] of Object.entries(dict._map)) {
          if (subDict._map[key] === undefined) {
            subDict._map[key] = value;
          }
        }
      }
      if (subDict.size > 0) {
        mergedDict._map[name] = subDict;
      }
    }
    properties.clear();
    return mergedDict.size > 0 ? mergedDict : Dict.empty;
  }
  clone() {
    const dict = new Dict(this.xref);
    for (const key of this.getKeys()) {
      dict.set(key, this.getRaw(key));
    }
    return dict;
  }
  delete(key) {
    delete this._map[key];
  }
}
class Ref {
  constructor(num, gen) {
    this.num = num;
    this.gen = gen;
  }
  toString() {
    if (this.gen === 0) {
      return `${this.num}R`;
    }
    return `${this.num}R${this.gen}`;
  }
  static fromString(str) {
    const ref = RefCache[str];
    if (ref) {
      return ref;
    }
    const m = /^(\d+)R(\d*)$/.exec(str);
    if (!m || m[1] === "0") {
      return null;
    }
    return RefCache[str] = new Ref(parseInt(m[1]), !m[2] ? 0 : parseInt(m[2]));
  }
  static get(num, gen) {
    const key = gen === 0 ? `${num}R` : `${num}R${gen}`;
    return RefCache[key] ||= new Ref(num, gen);
  }
}
class RefSet {
  constructor(parent = null) {
    this._set = new Set(parent?._set);
  }
  has(ref) {
    return this._set.has(ref.toString());
  }
  put(ref) {
    this._set.add(ref.toString());
  }
  remove(ref) {
    this._set.delete(ref.toString());
  }
  [Symbol.iterator]() {
    return this._set.values();
  }
  clear() {
    this._set.clear();
  }
}
class RefSetCache {
  constructor() {
    this._map = new Map();
  }
  get size() {
    return this._map.size;
  }
  get(ref) {
    return this._map.get(ref.toString());
  }
  has(ref) {
    return this._map.has(ref.toString());
  }
  put(ref, obj) {
    this._map.set(ref.toString(), obj);
  }
  putAlias(ref, aliasRef) {
    this._map.set(ref.toString(), this.get(aliasRef));
  }
  [Symbol.iterator]() {
    return this._map.values();
  }
  clear() {
    this._map.clear();
  }
  *items() {
    for (const [ref, value] of this._map) {
      yield [Ref.fromString(ref), value];
    }
  }
}
function isName(v, name) {
  return v instanceof Name && (name === undefined || v.name === name);
}
function isCmd(v, cmd) {
  return v instanceof Cmd && (cmd === undefined || v.cmd === cmd);
}
function isDict(v, type) {
  return v instanceof Dict && (type === undefined || isName(v.get("Type"), type));
}
function isRefsEqual(v1, v2) {
  return v1.num === v2.num && v1.gen === v2.gen;
}

;// CONCATENATED MODULE: ./src/core/base_stream.js

class BaseStream {
  get length() {
    unreachable("Abstract getter `length` accessed");
  }
  get isEmpty() {
    unreachable("Abstract getter `isEmpty` accessed");
  }
  get isDataLoaded() {
    return shadow(this, "isDataLoaded", true);
  }
  getByte() {
    unreachable("Abstract method `getByte` called");
  }
  getBytes(length) {
    unreachable("Abstract method `getBytes` called");
  }
  async getImageData(length, decoderOptions) {
    return this.getBytes(length, decoderOptions);
  }
  async asyncGetBytes() {
    unreachable("Abstract method `asyncGetBytes` called");
  }
  get isAsync() {
    return false;
  }
  get canAsyncDecodeImageFromBuffer() {
    return false;
  }
  peekByte() {
    const peekedByte = this.getByte();
    if (peekedByte !== -1) {
      this.pos--;
    }
    return peekedByte;
  }
  peekBytes(length) {
    const bytes = this.getBytes(length);
    this.pos -= bytes.length;
    return bytes;
  }
  getUint16() {
    const b0 = this.getByte();
    const b1 = this.getByte();
    if (b0 === -1 || b1 === -1) {
      return -1;
    }
    return (b0 << 8) + b1;
  }
  getInt32() {
    const b0 = this.getByte();
    const b1 = this.getByte();
    const b2 = this.getByte();
    const b3 = this.getByte();
    return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
  }
  getByteRange(begin, end) {
    unreachable("Abstract method `getByteRange` called");
  }
  getString(length) {
    return bytesToString(this.getBytes(length));
  }
  skip(n) {
    this.pos += n || 1;
  }
  reset() {
    unreachable("Abstract method `reset` called");
  }
  moveStart() {
    unreachable("Abstract method `moveStart` called");
  }
  makeSubStream(start, length, dict = null) {
    unreachable("Abstract method `makeSubStream` called");
  }
  getBaseStreams() {
    return null;
  }
}

;// CONCATENATED MODULE: ./src/core/core_utils.js



const PDF_VERSION_REGEXP = /^[1-9]\.\d$/;
function getLookupTableFactory(initializer) {
  let lookup;
  return function () {
    if (initializer) {
      lookup = Object.create(null);
      initializer(lookup);
      initializer = null;
    }
    return lookup;
  };
}
class MissingDataException extends BaseException {
  constructor(begin, end) {
    super(`Missing data [${begin}, ${end})`, "MissingDataException");
    this.begin = begin;
    this.end = end;
  }
}
class ParserEOFException extends BaseException {
  constructor(msg) {
    super(msg, "ParserEOFException");
  }
}
class XRefEntryException extends BaseException {
  constructor(msg) {
    super(msg, "XRefEntryException");
  }
}
class XRefParseException extends BaseException {
  constructor(msg) {
    super(msg, "XRefParseException");
  }
}
function arrayBuffersToBytes(arr) {
  const length = arr.length;
  if (length === 0) {
    return new Uint8Array(0);
  }
  if (length === 1) {
    return new Uint8Array(arr[0]);
  }
  let dataLength = 0;
  for (let i = 0; i < length; i++) {
    dataLength += arr[i].byteLength;
  }
  const data = new Uint8Array(dataLength);
  let pos = 0;
  for (let i = 0; i < length; i++) {
    const item = new Uint8Array(arr[i]);
    data.set(item, pos);
    pos += item.byteLength;
  }
  return data;
}
function getInheritableProperty({
  dict,
  key,
  getArray = false,
  stopWhenFound = true
}) {
  let values;
  const visited = new RefSet();
  while (dict instanceof Dict && !(dict.objId && visited.has(dict.objId))) {
    if (dict.objId) {
      visited.put(dict.objId);
    }
    const value = getArray ? dict.getArray(key) : dict.get(key);
    if (value !== undefined) {
      if (stopWhenFound) {
        return value;
      }
      (values ||= []).push(value);
    }
    dict = dict.get("Parent");
  }
  return values;
}
const ROMAN_NUMBER_MAP = ["", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM", "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC", "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"];
function toRomanNumerals(number, lowerCase = false) {
  assert(Number.isInteger(number) && number > 0, "The number should be a positive integer.");
  const romanBuf = [];
  let pos;
  while (number >= 1000) {
    number -= 1000;
    romanBuf.push("M");
  }
  pos = number / 100 | 0;
  number %= 100;
  romanBuf.push(ROMAN_NUMBER_MAP[pos]);
  pos = number / 10 | 0;
  number %= 10;
  romanBuf.push(ROMAN_NUMBER_MAP[10 + pos]);
  romanBuf.push(ROMAN_NUMBER_MAP[20 + number]);
  const romanStr = romanBuf.join("");
  return lowerCase ? romanStr.toLowerCase() : romanStr;
}
function log2(x) {
  if (x <= 0) {
    return 0;
  }
  return Math.ceil(Math.log2(x));
}
function readInt8(data, offset) {
  return data[offset] << 24 >> 24;
}
function readUint16(data, offset) {
  return data[offset] << 8 | data[offset + 1];
}
function readUint32(data, offset) {
  return (data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3]) >>> 0;
}
function isWhiteSpace(ch) {
  return ch === 0x20 || ch === 0x09 || ch === 0x0d || ch === 0x0a;
}
function isBooleanArray(arr, len) {
  return Array.isArray(arr) && (len === null || arr.length === len) && arr.every(x => typeof x === "boolean");
}
function isNumberArray(arr, len) {
  if (Array.isArray(arr)) {
    return (len === null || arr.length === len) && arr.every(x => typeof x === "number");
  }
  return ArrayBuffer.isView(arr) && (arr.length === 0 || typeof arr[0] === "number") && (len === null || arr.length === len);
}
function lookupMatrix(arr, fallback) {
  return isNumberArray(arr, 6) ? arr : fallback;
}
function lookupRect(arr, fallback) {
  return isNumberArray(arr, 4) ? arr : fallback;
}
function lookupNormalRect(arr, fallback) {
  return isNumberArray(arr, 4) ? Util.normalizeRect(arr) : fallback;
}
function parseXFAPath(path) {
  const positionPattern = /(.+)\[(\d+)\]$/;
  return path.split(".").map(component => {
    const m = component.match(positionPattern);
    if (m) {
      return {
        name: m[1],
        pos: parseInt(m[2], 10)
      };
    }
    return {
      name: component,
      pos: 0
    };
  });
}
function escapePDFName(str) {
  const buffer = [];
  let start = 0;
  for (let i = 0, ii = str.length; i < ii; i++) {
    const char = str.charCodeAt(i);
    if (char < 0x21 || char > 0x7e || char === 0x23 || char === 0x28 || char === 0x29 || char === 0x3c || char === 0x3e || char === 0x5b || char === 0x5d || char === 0x7b || char === 0x7d || char === 0x2f || char === 0x25) {
      if (start < i) {
        buffer.push(str.substring(start, i));
      }
      buffer.push(`#${char.toString(16)}`);
      start = i + 1;
    }
  }
  if (buffer.length === 0) {
    return str;
  }
  if (start < str.length) {
    buffer.push(str.substring(start, str.length));
  }
  return buffer.join("");
}
function escapeString(str) {
  return str.replaceAll(/([()\\\n\r])/g, match => {
    if (match === "\n") {
      return "\\n";
    } else if (match === "\r") {
      return "\\r";
    }
    return `\\${match}`;
  });
}
function _collectJS(entry, xref, list, parents) {
  if (!entry) {
    return;
  }
  let parent = null;
  if (entry instanceof Ref) {
    if (parents.has(entry)) {
      return;
    }
    parent = entry;
    parents.put(parent);
    entry = xref.fetch(entry);
  }
  if (Array.isArray(entry)) {
    for (const element of entry) {
      _collectJS(element, xref, list, parents);
    }
  } else if (entry instanceof Dict) {
    if (isName(entry.get("S"), "JavaScript")) {
      const js = entry.get("JS");
      let code;
      if (js instanceof BaseStream) {
        code = js.getString();
      } else if (typeof js === "string") {
        code = js;
      }
      code &&= stringToPDFString(code).replaceAll("\x00", "");
      if (code) {
        list.push(code);
      }
    }
    _collectJS(entry.getRaw("Next"), xref, list, parents);
  }
  if (parent) {
    parents.remove(parent);
  }
}
function collectActions(xref, dict, eventType) {
  const actions = Object.create(null);
  const additionalActionsDicts = getInheritableProperty({
    dict,
    key: "AA",
    stopWhenFound: false
  });
  if (additionalActionsDicts) {
    for (let i = additionalActionsDicts.length - 1; i >= 0; i--) {
      const additionalActions = additionalActionsDicts[i];
      if (!(additionalActions instanceof Dict)) {
        continue;
      }
      for (const key of additionalActions.getKeys()) {
        const action = eventType[key];
        if (!action) {
          continue;
        }
        const actionDict = additionalActions.getRaw(key);
        const parents = new RefSet();
        const list = [];
        _collectJS(actionDict, xref, list, parents);
        if (list.length > 0) {
          actions[action] = list;
        }
      }
    }
  }
  if (dict.has("A")) {
    const actionDict = dict.get("A");
    const parents = new RefSet();
    const list = [];
    _collectJS(actionDict, xref, list, parents);
    if (list.length > 0) {
      actions.Action = list;
    }
  }
  return objectSize(actions) > 0 ? actions : null;
}
const XMLEntities = {
  0x3c: "&lt;",
  0x3e: "&gt;",
  0x26: "&amp;",
  0x22: "&quot;",
  0x27: "&apos;"
};
function* codePointIter(str) {
  for (let i = 0, ii = str.length; i < ii; i++) {
    const char = str.codePointAt(i);
    if (char > 0xd7ff && (char < 0xe000 || char > 0xfffd)) {
      i++;
    }
    yield char;
  }
}
function encodeToXmlString(str) {
  const buffer = [];
  let start = 0;
  for (let i = 0, ii = str.length; i < ii; i++) {
    const char = str.codePointAt(i);
    if (0x20 <= char && char <= 0x7e) {
      const entity = XMLEntities[char];
      if (entity) {
        if (start < i) {
          buffer.push(str.substring(start, i));
        }
        buffer.push(entity);
        start = i + 1;
      }
    } else {
      if (start < i) {
        buffer.push(str.substring(start, i));
      }
      buffer.push(`&#x${char.toString(16).toUpperCase()};`);
      if (char > 0xd7ff && (char < 0xe000 || char > 0xfffd)) {
        i++;
      }
      start = i + 1;
    }
  }
  if (buffer.length === 0) {
    return str;
  }
  if (start < str.length) {
    buffer.push(str.substring(start, str.length));
  }
  return buffer.join("");
}
function validateFontName(fontFamily, mustWarn = false) {
  const m = /^("|').*("|')$/.exec(fontFamily);
  if (m && m[1] === m[2]) {
    const re = new RegExp(`[^\\\\]${m[1]}`);
    if (re.test(fontFamily.slice(1, -1))) {
      if (mustWarn) {
        warn(`FontFamily contains unescaped ${m[1]}: ${fontFamily}.`);
      }
      return false;
    }
  } else {
    for (const ident of fontFamily.split(/[ \t]+/)) {
      if (/^(\d|(-(\d|-)))/.test(ident) || !/^[\w-\\]+$/.test(ident)) {
        if (mustWarn) {
          warn(`FontFamily contains invalid <custom-ident>: ${fontFamily}.`);
        }
        return false;
      }
    }
  }
  return true;
}
function validateCSSFont(cssFontInfo) {
  const DEFAULT_CSS_FONT_OBLIQUE = "14";
  const DEFAULT_CSS_FONT_WEIGHT = "400";
  const CSS_FONT_WEIGHT_VALUES = new Set(["100", "200", "300", "400", "500", "600", "700", "800", "900", "1000", "normal", "bold", "bolder", "lighter"]);
  const {
    fontFamily,
    fontWeight,
    italicAngle
  } = cssFontInfo;
  if (!validateFontName(fontFamily, true)) {
    return false;
  }
  const weight = fontWeight ? fontWeight.toString() : "";
  cssFontInfo.fontWeight = CSS_FONT_WEIGHT_VALUES.has(weight) ? weight : DEFAULT_CSS_FONT_WEIGHT;
  const angle = parseFloat(italicAngle);
  cssFontInfo.italicAngle = isNaN(angle) || angle < -90 || angle > 90 ? DEFAULT_CSS_FONT_OBLIQUE : italicAngle.toString();
  return true;
}
function recoverJsURL(str) {
  const URL_OPEN_METHODS = ["app.launchURL", "window.open", "xfa.host.gotoURL"];
  const regex = new RegExp("^\\s*(" + URL_OPEN_METHODS.join("|").replaceAll(".", "\\.") + ")\\((?:'|\")([^'\"]*)(?:'|\")(?:,\\s*(\\w+)\\)|\\))", "i");
  const jsUrl = regex.exec(str);
  if (jsUrl?.[2]) {
    const url = jsUrl[2];
    let newWindow = false;
    if (jsUrl[3] === "true" && jsUrl[1] === "app.launchURL") {
      newWindow = true;
    }
    return {
      url,
      newWindow
    };
  }
  return null;
}
function numberToString(value) {
  if (Number.isInteger(value)) {
    return value.toString();
  }
  const roundedValue = Math.round(value * 100);
  if (roundedValue % 100 === 0) {
    return (roundedValue / 100).toString();
  }
  if (roundedValue % 10 === 0) {
    return value.toFixed(1);
  }
  return value.toFixed(2);
}
function getNewAnnotationsMap(annotationStorage) {
  if (!annotationStorage) {
    return null;
  }
  const newAnnotationsByPage = new Map();
  for (const [key, value] of annotationStorage) {
    if (!key.startsWith(AnnotationEditorPrefix)) {
      continue;
    }
    let annotations = newAnnotationsByPage.get(value.pageIndex);
    if (!annotations) {
      annotations = [];
      newAnnotationsByPage.set(value.pageIndex, annotations);
    }
    annotations.push(value);
  }
  return newAnnotationsByPage.size > 0 ? newAnnotationsByPage : null;
}
function stringToAsciiOrUTF16BE(str) {
  return isAscii(str) ? str : stringToUTF16String(str, true);
}
function isAscii(str) {
  return /^[\x00-\x7F]*$/.test(str);
}
function stringToUTF16HexString(str) {
  const buf = [];
  for (let i = 0, ii = str.length; i < ii; i++) {
    const char = str.charCodeAt(i);
    buf.push((char >> 8 & 0xff).toString(16).padStart(2, "0"), (char & 0xff).toString(16).padStart(2, "0"));
  }
  return buf.join("");
}
function stringToUTF16String(str, bigEndian = false) {
  const buf = [];
  if (bigEndian) {
    buf.push("\xFE\xFF");
  }
  for (let i = 0, ii = str.length; i < ii; i++) {
    const char = str.charCodeAt(i);
    buf.push(String.fromCharCode(char >> 8 & 0xff), String.fromCharCode(char & 0xff));
  }
  return buf.join("");
}
function getRotationMatrix(rotation, width, height) {
  switch (rotation) {
    case 90:
      return [0, 1, -1, 0, width, 0];
    case 180:
      return [-1, 0, 0, -1, width, height];
    case 270:
      return [0, -1, 1, 0, 0, height];
    default:
      throw new Error("Invalid rotation");
  }
}
function getSizeInBytes(x) {
  return Math.ceil(Math.ceil(Math.log2(1 + x)) / 8);
}

;// CONCATENATED MODULE: ./src/core/stream.js


class Stream extends BaseStream {
  constructor(arrayBuffer, start, length, dict) {
    super();
    this.bytes = arrayBuffer instanceof Uint8Array ? arrayBuffer : new Uint8Array(arrayBuffer);
    this.start = start || 0;
    this.pos = this.start;
    this.end = start + length || this.bytes.length;
    this.dict = dict;
  }
  get length() {
    return this.end - this.start;
  }
  get isEmpty() {
    return this.length === 0;
  }
  getByte() {
    if (this.pos >= this.end) {
      return -1;
    }
    return this.bytes[this.pos++];
  }
  getBytes(length) {
    const bytes = this.bytes;
    const pos = this.pos;
    const strEnd = this.end;
    if (!length) {
      return bytes.subarray(pos, strEnd);
    }
    let end = pos + length;
    if (end > strEnd) {
      end = strEnd;
    }
    this.pos = end;
    return bytes.subarray(pos, end);
  }
  getByteRange(begin, end) {
    if (begin < 0) {
      begin = 0;
    }
    if (end > this.end) {
      end = this.end;
    }
    return this.bytes.subarray(begin, end);
  }
  reset() {
    this.pos = this.start;
  }
  moveStart() {
    this.start = this.pos;
  }
  makeSubStream(start, length, dict = null) {
    return new Stream(this.bytes.buffer, start, length, dict);
  }
}
class StringStream extends Stream {
  constructor(str) {
    super(stringToBytes(str));
  }
}
class NullStream extends Stream {
  constructor() {
    super(new Uint8Array(0));
  }
}

;// CONCATENATED MODULE: ./src/core/chunked_stream.js



class ChunkedStream extends Stream {
  constructor(length, chunkSize, manager) {
    super(new Uint8Array(length), 0, length, null);
    this.chunkSize = chunkSize;
    this._loadedChunks = new Set();
    this.numChunks = Math.ceil(length / chunkSize);
    this.manager = manager;
    this.progressiveDataLength = 0;
    this.lastSuccessfulEnsureByteChunk = -1;
  }
  getMissingChunks() {
    const chunks = [];
    for (let chunk = 0, n = this.numChunks; chunk < n; ++chunk) {
      if (!this._loadedChunks.has(chunk)) {
        chunks.push(chunk);
      }
    }
    return chunks;
  }
  get numChunksLoaded() {
    return this._loadedChunks.size;
  }
  get isDataLoaded() {
    return this.numChunksLoaded === this.numChunks;
  }
  onReceiveData(begin, chunk) {
    const chunkSize = this.chunkSize;
    if (begin % chunkSize !== 0) {
      throw new Error(`Bad begin offset: ${begin}`);
    }
    const end = begin + chunk.byteLength;
    if (end % chunkSize !== 0 && end !== this.bytes.length) {
      throw new Error(`Bad end offset: ${end}`);
    }
    this.bytes.set(new Uint8Array(chunk), begin);
    const beginChunk = Math.floor(begin / chunkSize);
    const endChunk = Math.floor((end - 1) / chunkSize) + 1;
    for (let curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
      this._loadedChunks.add(curChunk);
    }
  }
  onReceiveProgressiveData(data) {
    let position = this.progressiveDataLength;
    const beginChunk = Math.floor(position / this.chunkSize);
    this.bytes.set(new Uint8Array(data), position);
    position += data.byteLength;
    this.progressiveDataLength = position;
    const endChunk = position >= this.end ? this.numChunks : Math.floor(position / this.chunkSize);
    for (let curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
      this._loadedChunks.add(curChunk);
    }
  }
  ensureByte(pos) {
    if (pos < this.progressiveDataLength) {
      return;
    }
    const chunk = Math.floor(pos / this.chunkSize);
    if (chunk > this.numChunks) {
      return;
    }
    if (chunk === this.lastSuccessfulEnsureByteChunk) {
      return;
    }
    if (!this._loadedChunks.has(chunk)) {
      throw new MissingDataException(pos, pos + 1);
    }
    this.lastSuccessfulEnsureByteChunk = chunk;
  }
  ensureRange(begin, end) {
    if (begin >= end) {
      return;
    }
    if (end <= this.progressiveDataLength) {
      return;
    }
    const beginChunk = Math.floor(begin / this.chunkSize);
    if (beginChunk > this.numChunks) {
      return;
    }
    const endChunk = Math.min(Math.floor((end - 1) / this.chunkSize) + 1, this.numChunks);
    for (let chunk = beginChunk; chunk < endChunk; ++chunk) {
      if (!this._loadedChunks.has(chunk)) {
        throw new MissingDataException(begin, end);
      }
    }
  }
  nextEmptyChunk(beginChunk) {
    const numChunks = this.numChunks;
    for (let i = 0; i < numChunks; ++i) {
      const chunk = (beginChunk + i) % numChunks;
      if (!this._loadedChunks.has(chunk)) {
        return chunk;
      }
    }
    return null;
  }
  hasChunk(chunk) {
    return this._loadedChunks.has(chunk);
  }
  getByte() {
    const pos = this.pos;
    if (pos >= this.end) {
      return -1;
    }
    if (pos >= this.progressiveDataLength) {
      this.ensureByte(pos);
    }
    return this.bytes[this.pos++];
  }
  getBytes(length) {
    const bytes = this.bytes;
    const pos = this.pos;
    const strEnd = this.end;
    if (!length) {
      if (strEnd > this.progressiveDataLength) {
        this.ensureRange(pos, strEnd);
      }
      return bytes.subarray(pos, strEnd);
    }
    let end = pos + length;
    if (end > strEnd) {
      end = strEnd;
    }
    if (end > this.progressiveDataLength) {
      this.ensureRange(pos, end);
    }
    this.pos = end;
    return bytes.subarray(pos, end);
  }
  getByteRange(begin, end) {
    if (begin < 0) {
      begin = 0;
    }
    if (end > this.end) {
      end = this.end;
    }
    if (end > this.progressiveDataLength) {
      this.ensureRange(begin, end);
    }
    return this.bytes.subarray(begin, end);
  }
  makeSubStream(start, length, dict = null) {
    if (length) {
      if (start + length > this.progressiveDataLength) {
        this.ensureRange(start, start + length);
      }
    } else if (start >= this.progressiveDataLength) {
      this.ensureByte(start);
    }
    function ChunkedStreamSubstream() {}
    ChunkedStreamSubstream.prototype = Object.create(this);
    ChunkedStreamSubstream.prototype.getMissingChunks = function () {
      const chunkSize = this.chunkSize;
      const beginChunk = Math.floor(this.start / chunkSize);
      const endChunk = Math.floor((this.end - 1) / chunkSize) + 1;
      const missingChunks = [];
      for (let chunk = beginChunk; chunk < endChunk; ++chunk) {
        if (!this._loadedChunks.has(chunk)) {
          missingChunks.push(chunk);
        }
      }
      return missingChunks;
    };
    Object.defineProperty(ChunkedStreamSubstream.prototype, "isDataLoaded", {
      get() {
        if (this.numChunksLoaded === this.numChunks) {
          return true;
        }
        return this.getMissingChunks().length === 0;
      },
      configurable: true
    });
    const subStream = new ChunkedStreamSubstream();
    subStream.pos = subStream.start = start;
    subStream.end = start + length || this.end;
    subStream.dict = dict;
    return subStream;
  }
  getBaseStreams() {
    return [this];
  }
}
class ChunkedStreamManager {
  constructor(pdfNetworkStream, args) {
    this.length = args.length;
    this.chunkSize = args.rangeChunkSize;
    this.stream = new ChunkedStream(this.length, this.chunkSize, this);
    this.pdfNetworkStream = pdfNetworkStream;
    this.disableAutoFetch = args.disableAutoFetch;
    this.msgHandler = args.msgHandler;
    this.currRequestId = 0;
    this._chunksNeededByRequest = new Map();
    this._requestsByChunk = new Map();
    this._promisesByRequest = new Map();
    this.progressiveDataLength = 0;
    this.aborted = false;
    this._loadedStreamCapability = Promise.withResolvers();
  }
  sendRequest(begin, end) {
    const rangeReader = this.pdfNetworkStream.getRangeReader(begin, end);
    if (!rangeReader.isStreamingSupported) {
      rangeReader.onProgress = this.onProgress.bind(this);
    }
    let chunks = [],
      loaded = 0;
    return new Promise((resolve, reject) => {
      const readChunk = ({
        value,
        done
      }) => {
        try {
          if (done) {
            const chunkData = arrayBuffersToBytes(chunks);
            chunks = null;
            resolve(chunkData);
            return;
          }
          loaded += value.byteLength;
          if (rangeReader.isStreamingSupported) {
            this.onProgress({
              loaded
            });
          }
          chunks.push(value);
          rangeReader.read().then(readChunk, reject);
        } catch (e) {
          reject(e);
        }
      };
      rangeReader.read().then(readChunk, reject);
    }).then(data => {
      if (this.aborted) {
        return;
      }
      this.onReceiveData({
        chunk: data,
        begin
      });
    });
  }
  requestAllChunks(noFetch = false) {
    if (!noFetch) {
      const missingChunks = this.stream.getMissingChunks();
      this._requestChunks(missingChunks);
    }
    return this._loadedStreamCapability.promise;
  }
  _requestChunks(chunks) {
    const requestId = this.currRequestId++;
    const chunksNeeded = new Set();
    this._chunksNeededByRequest.set(requestId, chunksNeeded);
    for (const chunk of chunks) {
      if (!this.stream.hasChunk(chunk)) {
        chunksNeeded.add(chunk);
      }
    }
    if (chunksNeeded.size === 0) {
      return Promise.resolve();
    }
    const capability = Promise.withResolvers();
    this._promisesByRequest.set(requestId, capability);
    const chunksToRequest = [];
    for (const chunk of chunksNeeded) {
      let requestIds = this._requestsByChunk.get(chunk);
      if (!requestIds) {
        requestIds = [];
        this._requestsByChunk.set(chunk, requestIds);
        chunksToRequest.push(chunk);
      }
      requestIds.push(requestId);
    }
    if (chunksToRequest.length > 0) {
      const groupedChunksToRequest = this.groupChunks(chunksToRequest);
      for (const groupedChunk of groupedChunksToRequest) {
        const begin = groupedChunk.beginChunk * this.chunkSize;
        const end = Math.min(groupedChunk.endChunk * this.chunkSize, this.length);
        this.sendRequest(begin, end).catch(capability.reject);
      }
    }
    return capability.promise.catch(reason => {
      if (this.aborted) {
        return;
      }
      throw reason;
    });
  }
  getStream() {
    return this.stream;
  }
  requestRange(begin, end) {
    end = Math.min(end, this.length);
    const beginChunk = this.getBeginChunk(begin);
    const endChunk = this.getEndChunk(end);
    const chunks = [];
    for (let chunk = beginChunk; chunk < endChunk; ++chunk) {
      chunks.push(chunk);
    }
    return this._requestChunks(chunks);
  }
  requestRanges(ranges = []) {
    const chunksToRequest = [];
    for (const range of ranges) {
      const beginChunk = this.getBeginChunk(range.begin);
      const endChunk = this.getEndChunk(range.end);
      for (let chunk = beginChunk; chunk < endChunk; ++chunk) {
        if (!chunksToRequest.includes(chunk)) {
          chunksToRequest.push(chunk);
        }
      }
    }
    chunksToRequest.sort(function (a, b) {
      return a - b;
    });
    return this._requestChunks(chunksToRequest);
  }
  groupChunks(chunks) {
    const groupedChunks = [];
    let beginChunk = -1;
    let prevChunk = -1;
    for (let i = 0, ii = chunks.length; i < ii; ++i) {
      const chunk = chunks[i];
      if (beginChunk < 0) {
        beginChunk = chunk;
      }
      if (prevChunk >= 0 && prevChunk + 1 !== chunk) {
        groupedChunks.push({
          beginChunk,
          endChunk: prevChunk + 1
        });
        beginChunk = chunk;
      }
      if (i + 1 === chunks.length) {
        groupedChunks.push({
          beginChunk,
          endChunk: chunk + 1
        });
      }
      prevChunk = chunk;
    }
    return groupedChunks;
  }
  onProgress(args) {
    this.msgHandler.send("DocProgress", {
      loaded: this.stream.numChunksLoaded * this.chunkSize + args.loaded,
      total: this.length
    });
  }
  onReceiveData(args) {
    const chunk = args.chunk;
    const isProgressive = args.begin === undefined;
    const begin = isProgressive ? this.progressiveDataLength : args.begin;
    const end = begin + chunk.byteLength;
    const beginChunk = Math.floor(begin / this.chunkSize);
    const endChunk = end < this.length ? Math.floor(end / this.chunkSize) : Math.ceil(end / this.chunkSize);
    if (isProgressive) {
      this.stream.onReceiveProgressiveData(chunk);
      this.progressiveDataLength = end;
    } else {
      this.stream.onReceiveData(begin, chunk);
    }
    if (this.stream.isDataLoaded) {
      this._loadedStreamCapability.resolve(this.stream);
    }
    const loadedRequests = [];
    for (let curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
      const requestIds = this._requestsByChunk.get(curChunk);
      if (!requestIds) {
        continue;
      }
      this._requestsByChunk.delete(curChunk);
      for (const requestId of requestIds) {
        const chunksNeeded = this._chunksNeededByRequest.get(requestId);
        if (chunksNeeded.has(curChunk)) {
          chunksNeeded.delete(curChunk);
        }
        if (chunksNeeded.size > 0) {
          continue;
        }
        loadedRequests.push(requestId);
      }
    }
    if (!this.disableAutoFetch && this._requestsByChunk.size === 0) {
      let nextEmptyChunk;
      if (this.stream.numChunksLoaded === 1) {
        const lastChunk = this.stream.numChunks - 1;
        if (!this.stream.hasChunk(lastChunk)) {
          nextEmptyChunk = lastChunk;
        }
      } else {
        nextEmptyChunk = this.stream.nextEmptyChunk(endChunk);
      }
      if (Number.isInteger(nextEmptyChunk)) {
        this._requestChunks([nextEmptyChunk]);
      }
    }
    for (const requestId of loadedRequests) {
      const capability = this._promisesByRequest.get(requestId);
      this._promisesByRequest.delete(requestId);
      capability.resolve();
    }
    this.msgHandler.send("DocProgress", {
      loaded: this.stream.numChunksLoaded * this.chunkSize,
      total: this.length
    });
  }
  onError(err) {
    this._loadedStreamCapability.reject(err);
  }
  getBeginChunk(begin) {
    return Math.floor(begin / this.chunkSize);
  }
  getEndChunk(end) {
    return Math.floor((end - 1) / this.chunkSize) + 1;
  }
  abort(reason) {
    this.aborted = true;
    this.pdfNetworkStream?.cancelAllRequests(reason);
    for (const capability of this._promisesByRequest.values()) {
      capability.reject(reason);
    }
  }
}

;// CONCATENATED MODULE: ./src/core/colorspace.js




function resizeRgbImage(src, dest, w1, h1, w2, h2, alpha01) {
  const COMPONENTS = 3;
  alpha01 = alpha01 !== 1 ? 0 : alpha01;
  const xRatio = w1 / w2;
  const yRatio = h1 / h2;
  let newIndex = 0,
    oldIndex;
  const xScaled = new Uint16Array(w2);
  const w1Scanline = w1 * COMPONENTS;
  for (let i = 0; i < w2; i++) {
    xScaled[i] = Math.floor(i * xRatio) * COMPONENTS;
  }
  for (let i = 0; i < h2; i++) {
    const py = Math.floor(i * yRatio) * w1Scanline;
    for (let j = 0; j < w2; j++) {
      oldIndex = py + xScaled[j];
      dest[newIndex++] = src[oldIndex++];
      dest[newIndex++] = src[oldIndex++];
      dest[newIndex++] = src[oldIndex++];
      newIndex += alpha01;
    }
  }
}
class ColorSpace {
  constructor(name, numComps) {
    this.name = name;
    this.numComps = numComps;
  }
  getRgb(src, srcOffset) {
    const rgb = new Uint8ClampedArray(3);
    this.getRgbItem(src, srcOffset, rgb, 0);
    return rgb;
  }
  getRgbItem(src, srcOffset, dest, destOffset) {
    unreachable("Should not call ColorSpace.getRgbItem");
  }
  getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
    unreachable("Should not call ColorSpace.getRgbBuffer");
  }
  getOutputLength(inputLength, alpha01) {
    unreachable("Should not call ColorSpace.getOutputLength");
  }
  isPassthrough(bits) {
    return false;
  }
  isDefaultDecode(decodeMap, bpc) {
    return ColorSpace.isDefaultDecode(decodeMap, this.numComps);
  }
  fillRgb(dest, originalWidth, originalHeight, width, height, actualHeight, bpc, comps, alpha01) {
    const count = originalWidth * originalHeight;
    let rgbBuf = null;
    const numComponentColors = 1 << bpc;
    const needsResizing = originalHeight !== height || originalWidth !== width;
    if (this.isPassthrough(bpc)) {
      rgbBuf = comps;
    } else if (this.numComps === 1 && count > numComponentColors && this.name !== "DeviceGray" && this.name !== "DeviceRGB") {
      const allColors = bpc <= 8 ? new Uint8Array(numComponentColors) : new Uint16Array(numComponentColors);
      for (let i = 0; i < numComponentColors; i++) {
        allColors[i] = i;
      }
      const colorMap = new Uint8ClampedArray(numComponentColors * 3);
      this.getRgbBuffer(allColors, 0, numComponentColors, colorMap, 0, bpc, 0);
      if (!needsResizing) {
        let destPos = 0;
        for (let i = 0; i < count; ++i) {
          const key = comps[i] * 3;
          dest[destPos++] = colorMap[key];
          dest[destPos++] = colorMap[key + 1];
          dest[destPos++] = colorMap[key + 2];
          destPos += alpha01;
        }
      } else {
        rgbBuf = new Uint8Array(count * 3);
        let rgbPos = 0;
        for (let i = 0; i < count; ++i) {
          const key = comps[i] * 3;
          rgbBuf[rgbPos++] = colorMap[key];
          rgbBuf[rgbPos++] = colorMap[key + 1];
          rgbBuf[rgbPos++] = colorMap[key + 2];
        }
      }
    } else if (!needsResizing) {
      this.getRgbBuffer(comps, 0, width * actualHeight, dest, 0, bpc, alpha01);
    } else {
      rgbBuf = new Uint8ClampedArray(count * 3);
      this.getRgbBuffer(comps, 0, count, rgbBuf, 0, bpc, 0);
    }
    if (rgbBuf) {
      if (needsResizing) {
        resizeRgbImage(rgbBuf, dest, originalWidth, originalHeight, width, height, alpha01);
      } else {
        let destPos = 0,
          rgbPos = 0;
        for (let i = 0, ii = width * actualHeight; i < ii; i++) {
          dest[destPos++] = rgbBuf[rgbPos++];
          dest[destPos++] = rgbBuf[rgbPos++];
          dest[destPos++] = rgbBuf[rgbPos++];
          destPos += alpha01;
        }
      }
    }
  }
  get usesZeroToOneRange() {
    return shadow(this, "usesZeroToOneRange", true);
  }
  static _cache(cacheKey, xref, localColorSpaceCache, parsedColorSpace) {
    if (!localColorSpaceCache) {
      throw new Error('ColorSpace._cache - expected "localColorSpaceCache" argument.');
    }
    if (!parsedColorSpace) {
      throw new Error('ColorSpace._cache - expected "parsedColorSpace" argument.');
    }
    let csName, csRef;
    if (cacheKey instanceof Ref) {
      csRef = cacheKey;
      cacheKey = xref.fetch(cacheKey);
    }
    if (cacheKey instanceof Name) {
      csName = cacheKey.name;
    }
    if (csName || csRef) {
      localColorSpaceCache.set(csName, csRef, parsedColorSpace);
    }
  }
  static getCached(cacheKey, xref, localColorSpaceCache) {
    if (!localColorSpaceCache) {
      throw new Error('ColorSpace.getCached - expected "localColorSpaceCache" argument.');
    }
    if (cacheKey instanceof Ref) {
      const localColorSpace = localColorSpaceCache.getByRef(cacheKey);
      if (localColorSpace) {
        return localColorSpace;
      }
      try {
        cacheKey = xref.fetch(cacheKey);
      } catch (ex) {
        if (ex instanceof MissingDataException) {
          throw ex;
        }
      }
    }
    if (cacheKey instanceof Name) {
      const localColorSpace = localColorSpaceCache.getByName(cacheKey.name);
      if (localColorSpace) {
        return localColorSpace;
      }
    }
    return null;
  }
  static async parseAsync({
    cs,
    xref,
    resources = null,
    pdfFunctionFactory,
    localColorSpaceCache
  }) {
    const parsedColorSpace = this._parse(cs, xref, resources, pdfFunctionFactory);
    this._cache(cs, xref, localColorSpaceCache, parsedColorSpace);
    return parsedColorSpace;
  }
  static parse({
    cs,
    xref,
    resources = null,
    pdfFunctionFactory,
    localColorSpaceCache
  }) {
    const cachedColorSpace = this.getCached(cs, xref, localColorSpaceCache);
    if (cachedColorSpace) {
      return cachedColorSpace;
    }
    const parsedColorSpace = this._parse(cs, xref, resources, pdfFunctionFactory);
    this._cache(cs, xref, localColorSpaceCache, parsedColorSpace);
    return parsedColorSpace;
  }
  static _parse(cs, xref, resources = null, pdfFunctionFactory) {
    cs = xref.fetchIfRef(cs);
    if (cs instanceof Name) {
      switch (cs.name) {
        case "G":
        case "DeviceGray":
          return this.singletons.gray;
        case "RGB":
        case "DeviceRGB":
          return this.singletons.rgb;
        case "DeviceRGBA":
          return this.singletons.rgba;
        case "CMYK":
        case "DeviceCMYK":
          return this.singletons.cmyk;
        case "Pattern":
          return new PatternCS(null);
        default:
          if (resources instanceof Dict) {
            const colorSpaces = resources.get("ColorSpace");
            if (colorSpaces instanceof Dict) {
              const resourcesCS = colorSpaces.get(cs.name);
              if (resourcesCS) {
                if (resourcesCS instanceof Name) {
                  return this._parse(resourcesCS, xref, resources, pdfFunctionFactory);
                }
                cs = resourcesCS;
                break;
              }
            }
          }
          warn(`Unrecognized ColorSpace: ${cs.name}`);
          return this.singletons.gray;
      }
    }
    if (Array.isArray(cs)) {
      const mode = xref.fetchIfRef(cs[0]).name;
      let params, numComps, baseCS, whitePoint, blackPoint, gamma;
      switch (mode) {
        case "G":
        case "DeviceGray":
          return this.singletons.gray;
        case "RGB":
        case "DeviceRGB":
          return this.singletons.rgb;
        case "CMYK":
        case "DeviceCMYK":
          return this.singletons.cmyk;
        case "CalGray":
          params = xref.fetchIfRef(cs[1]);
          whitePoint = params.getArray("WhitePoint");
          blackPoint = params.getArray("BlackPoint");
          gamma = params.get("Gamma");
          return new CalGrayCS(whitePoint, blackPoint, gamma);
        case "CalRGB":
          params = xref.fetchIfRef(cs[1]);
          whitePoint = params.getArray("WhitePoint");
          blackPoint = params.getArray("BlackPoint");
          gamma = params.getArray("Gamma");
          const matrix = params.getArray("Matrix");
          return new CalRGBCS(whitePoint, blackPoint, gamma, matrix);
        case "ICCBased":
          const stream = xref.fetchIfRef(cs[1]);
          const dict = stream.dict;
          numComps = dict.get("N");
          const alt = dict.get("Alternate");
          if (alt) {
            const altCS = this._parse(alt, xref, resources, pdfFunctionFactory);
            if (altCS.numComps === numComps) {
              return altCS;
            }
            warn("ICCBased color space: Ignoring incorrect /Alternate entry.");
          }
          if (numComps === 1) {
            return this.singletons.gray;
          } else if (numComps === 3) {
            return this.singletons.rgb;
          } else if (numComps === 4) {
            return this.singletons.cmyk;
          }
          break;
        case "Pattern":
          baseCS = cs[1] || null;
          if (baseCS) {
            baseCS = this._parse(baseCS, xref, resources, pdfFunctionFactory);
          }
          return new PatternCS(baseCS);
        case "I":
        case "Indexed":
          baseCS = this._parse(cs[1], xref, resources, pdfFunctionFactory);
          const hiVal = xref.fetchIfRef(cs[2]) + 1;
          const lookup = xref.fetchIfRef(cs[3]);
          return new IndexedCS(baseCS, hiVal, lookup);
        case "Separation":
        case "DeviceN":
          const name = xref.fetchIfRef(cs[1]);
          numComps = Array.isArray(name) ? name.length : 1;
          baseCS = this._parse(cs[2], xref, resources, pdfFunctionFactory);
          const tintFn = pdfFunctionFactory.create(cs[3]);
          return new AlternateCS(numComps, baseCS, tintFn);
        case "Lab":
          params = xref.fetchIfRef(cs[1]);
          whitePoint = params.getArray("WhitePoint");
          blackPoint = params.getArray("BlackPoint");
          const range = params.getArray("Range");
          return new LabCS(whitePoint, blackPoint, range);
        default:
          warn(`Unimplemented ColorSpace object: ${mode}`);
          return this.singletons.gray;
      }
    }
    warn(`Unrecognized ColorSpace object: ${cs}`);
    return this.singletons.gray;
  }
  static isDefaultDecode(decode, numComps) {
    if (!Array.isArray(decode)) {
      return true;
    }
    if (numComps * 2 !== decode.length) {
      warn("The decode map is not the correct length");
      return true;
    }
    for (let i = 0, ii = decode.length; i < ii; i += 2) {
      if (decode[i] !== 0 || decode[i + 1] !== 1) {
        return false;
      }
    }
    return true;
  }
  static get singletons() {
    return shadow(this, "singletons", {
      get gray() {
        return shadow(this, "gray", new DeviceGrayCS());
      },
      get rgb() {
        return shadow(this, "rgb", new DeviceRgbCS());
      },
      get rgba() {
        return shadow(this, "rgba", new DeviceRgbaCS());
      },
      get cmyk() {
        return shadow(this, "cmyk", new DeviceCmykCS());
      }
    });
  }
}
class AlternateCS extends ColorSpace {
  constructor(numComps, base, tintFn) {
    super("Alternate", numComps);
    this.base = base;
    this.tintFn = tintFn;
    this.tmpBuf = new Float32Array(base.numComps);
  }
  getRgbItem(src, srcOffset, dest, destOffset) {
    const tmpBuf = this.tmpBuf;
    this.tintFn(src, srcOffset, tmpBuf, 0);
    this.base.getRgbItem(tmpBuf, 0, dest, destOffset);
  }
  getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
    const tintFn = this.tintFn;
    const base = this.base;
    const scale = 1 / ((1 << bits) - 1);
    const baseNumComps = base.numComps;
    const usesZeroToOneRange = base.usesZeroToOneRange;
    const isPassthrough = (base.isPassthrough(8) || !usesZeroToOneRange) && alpha01 === 0;
    let pos = isPassthrough ? destOffset : 0;
    const baseBuf = isPassthrough ? dest : new Uint8ClampedArray(baseNumComps * count);
    const numComps = this.numComps;
    const scaled = new Float32Array(numComps);
    const tinted = new Float32Array(baseNumComps);
    let i, j;
    for (i = 0; i < count; i++) {
      for (j = 0; j < numComps; j++) {
        scaled[j] = src[srcOffset++] * scale;
      }
      tintFn(scaled, 0, tinted, 0);
      if (usesZeroToOneRange) {
        for (j = 0; j < baseNumComps; j++) {
          baseBuf[pos++] = tinted[j] * 255;
        }
      } else {
        base.getRgbItem(tinted, 0, baseBuf, pos);
        pos += baseNumComps;
      }
    }
    if (!isPassthrough) {
      base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8, alpha01);
    }
  }
  getOutputLength(inputLength, alpha01) {
    return this.base.getOutputLength(inputLength * this.base.numComps / this.numComps, alpha01);
  }
}
class PatternCS extends ColorSpace {
  constructor(baseCS) {
    super("Pattern", null);
    this.base = baseCS;
  }
  isDefaultDecode(decodeMap, bpc) {
    unreachable("Should not call PatternCS.isDefaultDecode");
  }
}
class IndexedCS extends ColorSpace {
  constructor(base, highVal, lookup) {
    super("Indexed", 1);
    this.base = base;
    this.highVal = highVal;
    const length = base.numComps * highVal;
    this.lookup = new Uint8Array(length);
    if (lookup instanceof BaseStream) {
      const bytes = lookup.getBytes(length);
      this.lookup.set(bytes);
    } else if (typeof lookup === "string") {
      for (let i = 0; i < length; ++i) {
        this.lookup[i] = lookup.charCodeAt(i) & 0xff;
      }
    } else {
      throw new FormatError(`IndexedCS - unrecognized lookup table: ${lookup}`);
    }
  }
  getRgbItem(src, srcOffset, dest, destOffset) {
    const numComps = this.base.numComps;
    const start = src[srcOffset] * numComps;
    this.base.getRgbBuffer(this.lookup, start, 1, dest, destOffset, 8, 0);
  }
  getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
    const base = this.base;
    const numComps = base.numComps;
    const outputDelta = base.getOutputLength(numComps, alpha01);
    const lookup = this.lookup;
    for (let i = 0; i < count; ++i) {
      const lookupPos = src[srcOffset++] * numComps;
      base.getRgbBuffer(lookup, lookupPos, 1, dest, destOffset, 8, alpha01);
      destOffset += outputDelta;
    }
  }
  getOutputLength(inputLength, alpha01) {
    return this.base.getOutputLength(inputLength * this.base.numComps, alpha01);
  }
  isDefaultDecode(decodeMap, bpc) {
    if (!Array.isArray(decodeMap)) {
      return true;
    }
    if (decodeMap.length !== 2) {
      warn("Decode map length is not correct");
      return true;
    }
    if (!Number.isInteger(bpc) || bpc < 1) {
      warn("Bits per component is not correct");
      return true;
    }
    return decodeMap[0] === 0 && decodeMap[1] === (1 << bpc) - 1;
  }
}
class DeviceGrayCS extends ColorSpace {
  constructor() {
    super("DeviceGray", 1);
  }
  getRgbItem(src, srcOffset, dest, destOffset) {
    const c = src[srcOffset] * 255;
    dest[destOffset] = dest[destOffset + 1] = dest[destOffset + 2] = c;
  }
  getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
    const scale = 255 / ((1 << bits) - 1);
    let j = srcOffset,
      q = destOffset;
    for (let i = 0; i < count; ++i) {
      const c = scale * src[j++];
      dest[q++] = c;
      dest[q++] = c;
      dest[q++] = c;
      q += alpha01;
    }
  }
  getOutputLength(inputLength, alpha01) {
    return inputLength * (3 + alpha01);
  }
}
class DeviceRgbCS extends ColorSpace {
  constructor() {
    super("DeviceRGB", 3);
  }
  getRgbItem(src, srcOffset, dest, destOffset) {
    dest[destOffset] = src[srcOffset] * 255;
    dest[destOffset + 1] = src[srcOffset + 1] * 255;
    dest[destOffset + 2] = src[srcOffset + 2] * 255;
  }
  getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
    if (bits === 8 && alpha01 === 0) {
      dest.set(src.subarray(srcOffset, srcOffset + count * 3), destOffset);
      return;
    }
    const scale = 255 / ((1 << bits) - 1);
    let j = srcOffset,
      q = destOffset;
    for (let i = 0; i < count; ++i) {
      dest[q++] = scale * src[j++];
      dest[q++] = scale * src[j++];
      dest[q++] = scale * src[j++];
      q += alpha01;
    }
  }
  getOutputLength(inputLength, alpha01) {
    return inputLength * (3 + alpha01) / 3 | 0;
  }
  isPassthrough(bits) {
    return bits === 8;
  }
}
class DeviceRgbaCS extends ColorSpace {
  constructor() {
    super("DeviceRGBA", 4);
  }
  getOutputLength(inputLength, _alpha01) {
    return inputLength * 4;
  }
  isPassthrough(bits) {
    return bits === 8;
  }
}
class DeviceCmykCS extends ColorSpace {
  constructor() {
    super("DeviceCMYK", 4);
  }
  #toRgb(src, srcOffset, srcScale, dest, destOffset) {
    const c = src[srcOffset] * srcScale;
    const m = src[srcOffset + 1] * srcScale;
    const y = src[srcOffset + 2] * srcScale;
    const k = src[srcOffset + 3] * srcScale;
    dest[destOffset] = 255 + c * (-4.387332384609988 * c + 54.48615194189176 * m + 18.82290502165302 * y + 212.25662451639585 * k + -285.2331026137004) + m * (1.7149763477362134 * m - 5.6096736904047315 * y + -17.873870861415444 * k - 5.497006427196366) + y * (-2.5217340131683033 * y - 21.248923337353073 * k + 17.5119270841813) + k * (-21.86122147463605 * k - 189.48180835922747);
    dest[destOffset + 1] = 255 + c * (8.841041422036149 * c + 60.118027045597366 * m + 6.871425592049007 * y + 31.159100130055922 * k + -79.2970844816548) + m * (-15.310361306967817 * m + 17.575251261109482 * y + 131.35250912493976 * k - 190.9453302588951) + y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) + k * (-20.737325471181034 * k - 187.80453709719578);
    dest[destOffset + 2] = 255 + c * (0.8842522430003296 * c + 8.078677503112928 * m + 30.89978309703729 * y - 0.23883238689178934 * k + -14.183576799673286) + m * (10.49593273432072 * m + 63.02378494754052 * y + 50.606957656360734 * k - 112.23884253719248) + y * (0.03296041114873217 * y + 115.60384449646641 * k + -193.58209356861505) + k * (-22.33816807309886 * k - 180.12613974708367);
  }
  getRgbItem(src, srcOffset, dest, destOffset) {
    this.#toRgb(src, srcOffset, 1, dest, destOffset);
  }
  getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
    const scale = 1 / ((1 << bits) - 1);
    for (let i = 0; i < count; i++) {
      this.#toRgb(src, srcOffset, scale, dest, destOffset);
      srcOffset += 4;
      destOffset += 3 + alpha01;
    }
  }
  getOutputLength(inputLength, alpha01) {
    return inputLength / 4 * (3 + alpha01) | 0;
  }
}
class CalGrayCS extends ColorSpace {
  constructor(whitePoint, blackPoint, gamma) {
    super("CalGray", 1);
    if (!whitePoint) {
      throw new FormatError("WhitePoint missing - required for color space CalGray");
    }
    [this.XW, this.YW, this.ZW] = whitePoint;
    [this.XB, this.YB, this.ZB] = blackPoint || [0, 0, 0];
    this.G = gamma || 1;
    if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) {
      throw new FormatError(`Invalid WhitePoint components for ${this.name}, no fallback available`);
    }
    if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
      info(`Invalid BlackPoint for ${this.name}, falling back to default.`);
      this.XB = this.YB = this.ZB = 0;
    }
    if (this.XB !== 0 || this.YB !== 0 || this.ZB !== 0) {
      warn(`${this.name}, BlackPoint: XB: ${this.XB}, YB: ${this.YB}, ` + `ZB: ${this.ZB}, only default values are supported.`);
    }
    if (this.G < 1) {
      info(`Invalid Gamma: ${this.G} for ${this.name}, falling back to default.`);
      this.G = 1;
    }
  }
  #toRgb(src, srcOffset, dest, destOffset, scale) {
    const A = src[srcOffset] * scale;
    const AG = A ** this.G;
    const L = this.YW * AG;
    const val = Math.max(295.8 * L ** 0.3333333333333333 - 40.8, 0);
    dest[destOffset] = val;
    dest[destOffset + 1] = val;
    dest[destOffset + 2] = val;
  }
  getRgbItem(src, srcOffset, dest, destOffset) {
    this.#toRgb(src, srcOffset, dest, destOffset, 1);
  }
  getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
    const scale = 1 / ((1 << bits) - 1);
    for (let i = 0; i < count; ++i) {
      this.#toRgb(src, srcOffset, dest, destOffset, scale);
      srcOffset += 1;
      destOffset += 3 + alpha01;
    }
  }
  getOutputLength(inputLength, alpha01) {
    return inputLength * (3 + alpha01);
  }
}
class CalRGBCS extends ColorSpace {
  static #BRADFORD_SCALE_MATRIX = new Float32Array([0.8951, 0.2664, -0.1614, -0.7502, 1.7135, 0.0367, 0.0389, -0.0685, 1.0296]);
  static #BRADFORD_SCALE_INVERSE_MATRIX = new Float32Array([0.9869929, -0.1470543, 0.1599627, 0.4323053, 0.5183603, 0.0492912, -0.0085287, 0.0400428, 0.9684867]);
  static #SRGB_D65_XYZ_TO_RGB_MATRIX = new Float32Array([3.2404542, -1.5371385, -0.4985314, -0.9692660, 1.8760108, 0.0415560, 0.0556434, -0.2040259, 1.0572252]);
  static #FLAT_WHITEPOINT_MATRIX = new Float32Array([1, 1, 1]);
  static #tempNormalizeMatrix = new Float32Array(3);
  static #tempConvertMatrix1 = new Float32Array(3);
  static #tempConvertMatrix2 = new Float32Array(3);
  static #DECODE_L_CONSTANT = ((8 + 16) / 116) ** 3 / 8.0;
  constructor(whitePoint, blackPoint, gamma, matrix) {
    super("CalRGB", 3);
    if (!whitePoint) {
      throw new FormatError("WhitePoint missing - required for color space CalRGB");
    }
    const [XW, YW, ZW] = this.whitePoint = whitePoint;
    const [XB, YB, ZB] = this.blackPoint = blackPoint || new Float32Array(3);
    [this.GR, this.GG, this.GB] = gamma || new Float32Array([1, 1, 1]);
    [this.MXA, this.MYA, this.MZA, this.MXB, this.MYB, this.MZB, this.MXC, this.MYC, this.MZC] = matrix || new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1]);
    if (XW < 0 || ZW < 0 || YW !== 1) {
      throw new FormatError(`Invalid WhitePoint components for ${this.name}, no fallback available`);
    }
    if (XB < 0 || YB < 0 || ZB < 0) {
      info(`Invalid BlackPoint for ${this.name} [${XB}, ${YB}, ${ZB}], ` + "falling back to default.");
      this.blackPoint = new Float32Array(3);
    }
    if (this.GR < 0 || this.GG < 0 || this.GB < 0) {
      info(`Invalid Gamma [${this.GR}, ${this.GG}, ${this.GB}] for ` + `${this.name}, falling back to default.`);
      this.GR = this.GG = this.GB = 1;
    }
  }
  #matrixProduct(a, b, result) {
    result[0] = a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
    result[1] = a[3] * b[0] + a[4] * b[1] + a[5] * b[2];
    result[2] = a[6] * b[0] + a[7] * b[1] + a[8] * b[2];
  }
  #toFlat(sourceWhitePoint, LMS, result) {
    result[0] = LMS[0] * 1 / sourceWhitePoint[0];
    result[1] = LMS[1] * 1 / sourceWhitePoint[1];
    result[2] = LMS[2] * 1 / sourceWhitePoint[2];
  }
  #toD65(sourceWhitePoint, LMS, result) {
    const D65X = 0.95047;
    const D65Y = 1;
    const D65Z = 1.08883;
    result[0] = LMS[0] * D65X / sourceWhitePoint[0];
    result[1] = LMS[1] * D65Y / sourceWhitePoint[1];
    result[2] = LMS[2] * D65Z / sourceWhitePoint[2];
  }
  #sRGBTransferFunction(color) {
    if (color <= 0.0031308) {
      return this.#adjustToRange(0, 1, 12.92 * color);
    }
    if (color >= 0.99554525) {
      return 1;
    }
    return this.#adjustToRange(0, 1, (1 + 0.055) * color ** (1 / 2.4) - 0.055);
  }
  #adjustToRange(min, max, value) {
    return Math.max(min, Math.min(max, value));
  }
  #decodeL(L) {
    if (L < 0) {
      return -this.#decodeL(-L);
    }
    if (L > 8.0) {
      return ((L + 16) / 116) ** 3;
    }
    return L * CalRGBCS.#DECODE_L_CONSTANT;
  }
  #compensateBlackPoint(sourceBlackPoint, XYZ_Flat, result) {
    if (sourceBlackPoint[0] === 0 && sourceBlackPoint[1] === 0 && sourceBlackPoint[2] === 0) {
      result[0] = XYZ_Flat[0];
      result[1] = XYZ_Flat[1];
      result[2] = XYZ_Flat[2];
      return;
    }
    const zeroDecodeL = this.#decodeL(0);
    const X_DST = zeroDecodeL;
    const X_SRC = this.#decodeL(sourceBlackPoint[0]);
    const Y_DST = zeroDecodeL;
    const Y_SRC = this.#decodeL(sourceBlackPoint[1]);
    const Z_DST = zeroDecodeL;
    const Z_SRC = this.#decodeL(sourceBlackPoint[2]);
    const X_Scale = (1 - X_DST) / (1 - X_SRC);
    const X_Offset = 1 - X_Scale;
    const Y_Scale = (1 - Y_DST) / (1 - Y_SRC);
    const Y_Offset = 1 - Y_Scale;
    const Z_Scale = (1 - Z_DST) / (1 - Z_SRC);
    const Z_Offset = 1 - Z_Scale;
    result[0] = XYZ_Flat[0] * X_Scale + X_Offset;
    result[1] = XYZ_Flat[1] * Y_Scale + Y_Offset;
    result[2] = XYZ_Flat[2] * Z_Scale + Z_Offset;
  }
  #normalizeWhitePointToFlat(sourceWhitePoint, XYZ_In, result) {
    if (sourceWhitePoint[0] === 1 && sourceWhitePoint[2] === 1) {
      result[0] = XYZ_In[0];
      result[1] = XYZ_In[1];
      result[2] = XYZ_In[2];
      return;
    }
    const LMS = result;
    this.#matrixProduct(CalRGBCS.#BRADFORD_SCALE_MATRIX, XYZ_In, LMS);
    const LMS_Flat = CalRGBCS.#tempNormalizeMatrix;
    this.#toFlat(sourceWhitePoint, LMS, LMS_Flat);
    this.#matrixProduct(CalRGBCS.#BRADFORD_SCALE_INVERSE_MATRIX, LMS_Flat, result);
  }
  #normalizeWhitePointToD65(sourceWhitePoint, XYZ_In, result) {
    const LMS = result;
    this.#matrixProduct(CalRGBCS.#BRADFORD_SCALE_MATRIX, XYZ_In, LMS);
    const LMS_D65 = CalRGBCS.#tempNormalizeMatrix;
    this.#toD65(sourceWhitePoint, LMS, LMS_D65);
    this.#matrixProduct(CalRGBCS.#BRADFORD_SCALE_INVERSE_MATRIX, LMS_D65, result);
  }
  #toRgb(src, srcOffset, dest, destOffset, scale) {
    const A = this.#adjustToRange(0, 1, src[srcOffset] * scale);
    const B = this.#adjustToRange(0, 1, src[srcOffset + 1] * scale);
    const C = this.#adjustToRange(0, 1, src[srcOffset + 2] * scale);
    const AGR = A === 1 ? 1 : A ** this.GR;
    const BGG = B === 1 ? 1 : B ** this.GG;
    const CGB = C === 1 ? 1 : C ** this.GB;
    const X = this.MXA * AGR + this.MXB * BGG + this.MXC * CGB;
    const Y = this.MYA * AGR + this.MYB * BGG + this.MYC * CGB;
    const Z = this.MZA * AGR + this.MZB * BGG + this.MZC * CGB;
    const XYZ = CalRGBCS.#tempConvertMatrix1;
    XYZ[0] = X;
    XYZ[1] = Y;
    XYZ[2] = Z;
    const XYZ_Flat = CalRGBCS.#tempConvertMatrix2;
    this.#normalizeWhitePointToFlat(this.whitePoint, XYZ, XYZ_Flat);
    const XYZ_Black = CalRGBCS.#tempConvertMatrix1;
    this.#compensateBlackPoint(this.blackPoint, XYZ_Flat, XYZ_Black);
    const XYZ_D65 = CalRGBCS.#tempConvertMatrix2;
    this.#normalizeWhitePointToD65(CalRGBCS.#FLAT_WHITEPOINT_MATRIX, XYZ_Black, XYZ_D65);
    const SRGB = CalRGBCS.#tempConvertMatrix1;
    this.#matrixProduct(CalRGBCS.#SRGB_D65_XYZ_TO_RGB_MATRIX, XYZ_D65, SRGB);
    dest[destOffset] = this.#sRGBTransferFunction(SRGB[0]) * 255;
    dest[destOffset + 1] = this.#sRGBTransferFunction(SRGB[1]) * 255;
    dest[destOffset + 2] = this.#sRGBTransferFunction(SRGB[2]) * 255;
  }
  getRgbItem(src, srcOffset, dest, destOffset) {
    this.#toRgb(src, srcOffset, dest, destOffset, 1);
  }
  getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
    const scale = 1 / ((1 << bits) - 1);
    for (let i = 0; i < count; ++i) {
      this.#toRgb(src, srcOffset, dest, destOffset, scale);
      srcOffset += 3;
      destOffset += 3 + alpha01;
    }
  }
  getOutputLength(inputLength, alpha01) {
    return inputLength * (3 + alpha01) / 3 | 0;
  }
}
class LabCS extends ColorSpace {
  constructor(whitePoint, blackPoint, range) {
    super("Lab", 3);
    if (!whitePoint) {
      throw new FormatError("WhitePoint missing - required for color space Lab");
    }
    [this.XW, this.YW, this.ZW] = whitePoint;
    [this.amin, this.amax, this.bmin, this.bmax] = range || [-100, 100, -100, 100];
    [this.XB, this.YB, this.ZB] = blackPoint || [0, 0, 0];
    if (this.XW < 0 || this.ZW < 0 || this.YW !== 1) {
      throw new FormatError("Invalid WhitePoint components, no fallback available");
    }
    if (this.XB < 0 || this.YB < 0 || this.ZB < 0) {
      info("Invalid BlackPoint, falling back to default");
      this.XB = this.YB = this.ZB = 0;
    }
    if (this.amin > this.amax || this.bmin > this.bmax) {
      info("Invalid Range, falling back to defaults");
      this.amin = -100;
      this.amax = 100;
      this.bmin = -100;
      this.bmax = 100;
    }
  }
  #fn_g(x) {
    return x >= 6 / 29 ? x ** 3 : 108 / 841 * (x - 4 / 29);
  }
  #decode(value, high1, low2, high2) {
    return low2 + value * (high2 - low2) / high1;
  }
  #toRgb(src, srcOffset, maxVal, dest, destOffset) {
    let Ls = src[srcOffset];
    let as = src[srcOffset + 1];
    let bs = src[srcOffset + 2];
    if (maxVal !== false) {
      Ls = this.#decode(Ls, maxVal, 0, 100);
      as = this.#decode(as, maxVal, this.amin, this.amax);
      bs = this.#decode(bs, maxVal, this.bmin, this.bmax);
    }
    if (as > this.amax) {
      as = this.amax;
    } else if (as < this.amin) {
      as = this.amin;
    }
    if (bs > this.bmax) {
      bs = this.bmax;
    } else if (bs < this.bmin) {
      bs = this.bmin;
    }
    const M = (Ls + 16) / 116;
    const L = M + as / 500;
    const N = M - bs / 200;
    const X = this.XW * this.#fn_g(L);
    const Y = this.YW * this.#fn_g(M);
    const Z = this.ZW * this.#fn_g(N);
    let r, g, b;
    if (this.ZW < 1) {
      r = X * 3.1339 + Y * -1.617 + Z * -0.4906;
      g = X * -0.9785 + Y * 1.916 + Z * 0.0333;
      b = X * 0.072 + Y * -0.229 + Z * 1.4057;
    } else {
      r = X * 3.2406 + Y * -1.5372 + Z * -0.4986;
      g = X * -0.9689 + Y * 1.8758 + Z * 0.0415;
      b = X * 0.0557 + Y * -0.204 + Z * 1.057;
    }
    dest[destOffset] = Math.sqrt(r) * 255;
    dest[destOffset + 1] = Math.sqrt(g) * 255;
    dest[destOffset + 2] = Math.sqrt(b) * 255;
  }
  getRgbItem(src, srcOffset, dest, destOffset) {
    this.#toRgb(src, srcOffset, false, dest, destOffset);
  }
  getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
    const maxVal = (1 << bits) - 1;
    for (let i = 0; i < count; i++) {
      this.#toRgb(src, srcOffset, maxVal, dest, destOffset);
      srcOffset += 3;
      destOffset += 3 + alpha01;
    }
  }
  getOutputLength(inputLength, alpha01) {
    return inputLength * (3 + alpha01) / 3 | 0;
  }
  isDefaultDecode(decodeMap, bpc) {
    return true;
  }
  get usesZeroToOneRange() {
    return shadow(this, "usesZeroToOneRange", false);
  }
}

;// CONCATENATED MODULE: ./src/core/binary_cmap.js

function hexToInt(a, size) {
  let n = 0;
  for (let i = 0; i <= size; i++) {
    n = n << 8 | a[i];
  }
  return n >>> 0;
}
function hexToStr(a, size) {
  if (size === 1) {
    return String.fromCharCode(a[0], a[1]);
  }
  if (size === 3) {
    return String.fromCharCode(a[0], a[1], a[2], a[3]);
  }
  return String.fromCharCode(...a.subarray(0, size + 1));
}
function addHex(a, b, size) {
  let c = 0;
  for (let i = size; i >= 0; i--) {
    c += a[i] + b[i];
    a[i] = c & 255;
    c >>= 8;
  }
}
function incHex(a, size) {
  let c = 1;
  for (let i = size; i >= 0 && c > 0; i--) {
    c += a[i];
    a[i] = c & 255;
    c >>= 8;
  }
}
const MAX_NUM_SIZE = 16;
const MAX_ENCODED_NUM_SIZE = 19;
class BinaryCMapStream {
  constructor(data) {
    this.buffer = data;
    this.pos = 0;
    this.end = data.length;
    this.tmpBuf = new Uint8Array(MAX_ENCODED_NUM_SIZE);
  }
  readByte() {
    if (this.pos >= this.end) {
      return -1;
    }
    return this.buffer[this.pos++];
  }
  readNumber() {
    let n = 0;
    let last;
    do {
      const b = this.readByte();
      if (b < 0) {
        throw new FormatError("unexpected EOF in bcmap");
      }
      last = !(b & 0x80);
      n = n << 7 | b & 0x7f;
    } while (!last);
    return n;
  }
  readSigned() {
    const n = this.readNumber();
    return n & 1 ? ~(n >>> 1) : n >>> 1;
  }
  readHex(num, size) {
    num.set(this.buffer.subarray(this.pos, this.pos + size + 1));
    this.pos += size + 1;
  }
  readHexNumber(num, size) {
    let last;
    const stack = this.tmpBuf;
    let sp = 0;
    do {
      const b = this.readByte();
      if (b < 0) {
        throw new FormatError("unexpected EOF in bcmap");
      }
      last = !(b & 0x80);
      stack[sp++] = b & 0x7f;
    } while (!last);
    let i = size,
      buffer = 0,
      bufferSize = 0;
    while (i >= 0) {
      while (bufferSize < 8 && stack.length > 0) {
        buffer |= stack[--sp] << bufferSize;
        bufferSize += 7;
      }
      num[i] = buffer & 255;
      i--;
      buffer >>= 8;
      bufferSize -= 8;
    }
  }
  readHexSigned(num, size) {
    this.readHexNumber(num, size);
    const sign = num[size] & 1 ? 255 : 0;
    let c = 0;
    for (let i = 0; i <= size; i++) {
      c = (c & 1) << 8 | num[i];
      num[i] = c >> 1 ^ sign;
    }
  }
  readString() {
    const len = this.readNumber(),
      buf = new Array(len);
    for (let i = 0; i < len; i++) {
      buf[i] = this.readNumber();
    }
    return String.fromCharCode(...buf);
  }
}
class BinaryCMapReader {
  async process(data, cMap, extend) {
    const stream = new BinaryCMapStream(data);
    const header = stream.readByte();
    cMap.vertical = !!(header & 1);
    let useCMap = null;
    const start = new Uint8Array(MAX_NUM_SIZE);
    const end = new Uint8Array(MAX_NUM_SIZE);
    const char = new Uint8Array(MAX_NUM_SIZE);
    const charCode = new Uint8Array(MAX_NUM_SIZE);
    const tmp = new Uint8Array(MAX_NUM_SIZE);
    let code;
    let b;
    while ((b = stream.readByte()) >= 0) {
      const type = b >> 5;
      if (type === 7) {
        switch (b & 0x1f) {
          case 0:
            stream.readString();
            break;
          case 1:
            useCMap = stream.readString();
            break;
        }
        continue;
      }
      const sequence = !!(b & 0x10);
      const dataSize = b & 15;
      if (dataSize + 1 > MAX_NUM_SIZE) {
        throw new Error("BinaryCMapReader.process: Invalid dataSize.");
      }
      const ucs2DataSize = 1;
      const subitemsCount = stream.readNumber();
      switch (type) {
        case 0:
          stream.readHex(start, dataSize);
          stream.readHexNumber(end, dataSize);
          addHex(end, start, dataSize);
          cMap.addCodespaceRange(dataSize + 1, hexToInt(start, dataSize), hexToInt(end, dataSize));
          for (let i = 1; i < subitemsCount; i++) {
            incHex(end, dataSize);
            stream.readHexNumber(start, dataSize);
            addHex(start, end, dataSize);
            stream.readHexNumber(end, dataSize);
            addHex(end, start, dataSize);
            cMap.addCodespaceRange(dataSize + 1, hexToInt(start, dataSize), hexToInt(end, dataSize));
          }
          break;
        case 1:
          stream.readHex(start, dataSize);
          stream.readHexNumber(end, dataSize);
          addHex(end, start, dataSize);
          stream.readNumber();
          for (let i = 1; i < subitemsCount; i++) {
            incHex(end, dataSize);
            stream.readHexNumber(start, dataSize);
            addHex(start, end, dataSize);
            stream.readHexNumber(end, dataSize);
            addHex(end, start, dataSize);
            stream.readNumber();
          }
          break;
        case 2:
          stream.readHex(char, dataSize);
          code = stream.readNumber();
          cMap.mapOne(hexToInt(char, dataSize), code);
          for (let i = 1; i < subitemsCount; i++) {
            incHex(char, dataSize);
            if (!sequence) {
              stream.readHexNumber(tmp, dataSize);
              addHex(char, tmp, dataSize);
            }
            code = stream.readSigned() + (code + 1);
            cMap.mapOne(hexToInt(char, dataSize), code);
          }
          break;
        case 3:
          stream.readHex(start, dataSize);
          stream.readHexNumber(end, dataSize);
          addHex(end, start, dataSize);
          code = stream.readNumber();
          cMap.mapCidRange(hexToInt(start, dataSize), hexToInt(end, dataSize), code);
          for (let i = 1; i < subitemsCount; i++) {
            incHex(end, dataSize);
            if (!sequence) {
              stream.readHexNumber(start, dataSize);
              addHex(start, end, dataSize);
            } else {
              start.set(end);
            }
            stream.readHexNumber(end, dataSize);
            addHex(end, start, dataSize);
            code = stream.readNumber();
            cMap.mapCidRange(hexToInt(start, dataSize), hexToInt(end, dataSize), code);
          }
          break;
        case 4:
          stream.readHex(char, ucs2DataSize);
          stream.readHex(charCode, dataSize);
          cMap.mapOne(hexToInt(char, ucs2DataSize), hexToStr(charCode, dataSize));
          for (let i = 1; i < subitemsCount; i++) {
            incHex(char, ucs2DataSize);
            if (!sequence) {
              stream.readHexNumber(tmp, ucs2DataSize);
              addHex(char, tmp, ucs2DataSize);
            }
            incHex(charCode, dataSize);
            stream.readHexSigned(tmp, dataSize);
            addHex(charCode, tmp, dataSize);
            cMap.mapOne(hexToInt(char, ucs2DataSize), hexToStr(charCode, dataSize));
          }
          break;
        case 5:
          stream.readHex(start, ucs2DataSize);
          stream.readHexNumber(end, ucs2DataSize);
          addHex(end, start, ucs2DataSize);
          stream.readHex(charCode, dataSize);
          cMap.mapBfRange(hexToInt(start, ucs2DataSize), hexToInt(end, ucs2DataSize), hexToStr(charCode, dataSize));
          for (let i = 1; i < subitemsCount; i++) {
            incHex(end, ucs2DataSize);
            if (!sequence) {
              stream.readHexNumber(start, ucs2DataSize);
              addHex(start, end, ucs2DataSize);
            } else {
              start.set(end);
            }
            stream.readHexNumber(end, ucs2DataSize);
            addHex(end, start, ucs2DataSize);
            stream.readHex(charCode, dataSize);
            cMap.mapBfRange(hexToInt(start, ucs2DataSize), hexToInt(end, ucs2DataSize), hexToStr(charCode, dataSize));
          }
          break;
        default:
          throw new Error(`BinaryCMapReader.process - unknown type: ${type}`);
      }
    }
    if (useCMap) {
      return extend(useCMap);
    }
    return cMap;
  }
}

;// CONCATENATED MODULE: ./src/core/decode_stream.js


const emptyBuffer = new Uint8Array(0);
class DecodeStream extends BaseStream {
  constructor(maybeMinBufferLength) {
    super();
    this._rawMinBufferLength = maybeMinBufferLength || 0;
    this.pos = 0;
    this.bufferLength = 0;
    this.eof = false;
    this.buffer = emptyBuffer;
    this.minBufferLength = 512;
    if (maybeMinBufferLength) {
      while (this.minBufferLength < maybeMinBufferLength) {
        this.minBufferLength *= 2;
      }
    }
  }
  get isEmpty() {
    while (!this.eof && this.bufferLength === 0) {
      this.readBlock();
    }
    return this.bufferLength === 0;
  }
  ensureBuffer(requested) {
    const buffer = this.buffer;
    if (requested <= buffer.byteLength) {
      return buffer;
    }
    let size = this.minBufferLength;
    while (size < requested) {
      size *= 2;
    }
    const buffer2 = new Uint8Array(size);
    buffer2.set(buffer);
    return this.buffer = buffer2;
  }
  getByte() {
    const pos = this.pos;
    while (this.bufferLength <= pos) {
      if (this.eof) {
        return -1;
      }
      this.readBlock();
    }
    return this.buffer[this.pos++];
  }
  getBytes(length, decoderOptions = null) {
    const pos = this.pos;
    let end;
    if (length) {
      this.ensureBuffer(pos + length);
      end = pos + length;
      while (!this.eof && this.bufferLength < end) {
        this.readBlock(decoderOptions);
      }
      const bufEnd = this.bufferLength;
      if (end > bufEnd) {
        end = bufEnd;
      }
    } else {
      while (!this.eof) {
        this.readBlock(decoderOptions);
      }
      end = this.bufferLength;
    }
    this.pos = end;
    return this.buffer.subarray(pos, end);
  }
  async getImageData(length, decoderOptions = null) {
    if (!this.canAsyncDecodeImageFromBuffer) {
      return this.getBytes(length, decoderOptions);
    }
    const data = await this.stream.asyncGetBytes();
    return this.decodeImage(data, decoderOptions);
  }
  reset() {
    this.pos = 0;
  }
  makeSubStream(start, length, dict = null) {
    if (length === undefined) {
      while (!this.eof) {
        this.readBlock();
      }
    } else {
      const end = start + length;
      while (this.bufferLength <= end && !this.eof) {
        this.readBlock();
      }
    }
    return new Stream(this.buffer, start, length, dict);
  }
  getBaseStreams() {
    return this.str ? this.str.getBaseStreams() : null;
  }
}
class StreamsSequenceStream extends DecodeStream {
  constructor(streams, onError = null) {
    let maybeLength = 0;
    for (const stream of streams) {
      maybeLength += stream instanceof DecodeStream ? stream._rawMinBufferLength : stream.length;
    }
    super(maybeLength);
    this.streams = streams;
    this._onError = onError;
  }
  readBlock() {
    const streams = this.streams;
    if (streams.length === 0) {
      this.eof = true;
      return;
    }
    const stream = streams.shift();
    let chunk;
    try {
      chunk = stream.getBytes();
    } catch (reason) {
      if (this._onError) {
        this._onError(reason, stream.dict?.objId);
        return;
      }
      throw reason;
    }
    const bufferLength = this.bufferLength;
    const newLength = bufferLength + chunk.length;
    const buffer = this.ensureBuffer(newLength);
    buffer.set(chunk, bufferLength);
    this.bufferLength = newLength;
  }
  getBaseStreams() {
    const baseStreamsBuf = [];
    for (const stream of this.streams) {
      const baseStreams = stream.getBaseStreams();
      if (baseStreams) {
        baseStreamsBuf.push(...baseStreams);
      }
    }
    return baseStreamsBuf.length > 0 ? baseStreamsBuf : null;
  }
}

;// CONCATENATED MODULE: ./src/core/ascii_85_stream.js


class Ascii85Stream extends DecodeStream {
  constructor(str, maybeLength) {
    if (maybeLength) {
      maybeLength *= 0.8;
    }
    super(maybeLength);
    this.str = str;
    this.dict = str.dict;
    this.input = new Uint8Array(5);
  }
  readBlock() {
    const TILDA_CHAR = 0x7e;
    const Z_LOWER_CHAR = 0x7a;
    const EOF = -1;
    const str = this.str;
    let c = str.getByte();
    while (isWhiteSpace(c)) {
      c = str.getByte();
    }
    if (c === EOF || c === TILDA_CHAR) {
      this.eof = true;
      return;
    }
    const bufferLength = this.bufferLength;
    let buffer, i;
    if (c === Z_LOWER_CHAR) {
      buffer = this.ensureBuffer(bufferLength + 4);
      for (i = 0; i < 4; ++i) {
        buffer[bufferLength + i] = 0;
      }
      this.bufferLength += 4;
    } else {
      const input = this.input;
      input[0] = c;
      for (i = 1; i < 5; ++i) {
        c = str.getByte();
        while (isWhiteSpace(c)) {
          c = str.getByte();
        }
        input[i] = c;
        if (c === EOF || c === TILDA_CHAR) {
          break;
        }
      }
      buffer = this.ensureBuffer(bufferLength + i - 1);
      this.bufferLength += i - 1;
      if (i < 5) {
        for (; i < 5; ++i) {
          input[i] = 0x21 + 84;
        }
        this.eof = true;
      }
      let t = 0;
      for (i = 0; i < 5; ++i) {
        t = t * 85 + (input[i] - 0x21);
      }
      for (i = 3; i >= 0; --i) {
        buffer[bufferLength + i] = t & 0xff;
        t >>= 8;
      }
    }
  }
}

;// CONCATENATED MODULE: ./src/core/ascii_hex_stream.js

class AsciiHexStream extends DecodeStream {
  constructor(str, maybeLength) {
    if (maybeLength) {
      maybeLength *= 0.5;
    }
    super(maybeLength);
    this.str = str;
    this.dict = str.dict;
    this.firstDigit = -1;
  }
  readBlock() {
    const UPSTREAM_BLOCK_SIZE = 8000;
    const bytes = this.str.getBytes(UPSTREAM_BLOCK_SIZE);
    if (!bytes.length) {
      this.eof = true;
      return;
    }
    const maxDecodeLength = bytes.length + 1 >> 1;
    const buffer = this.ensureBuffer(this.bufferLength + maxDecodeLength);
    let bufferLength = this.bufferLength;
    let firstDigit = this.firstDigit;
    for (const ch of bytes) {
      let digit;
      if (ch >= 0x30 && ch <= 0x39) {
        digit = ch & 0x0f;
      } else if (ch >= 0x41 && ch <= 0x46 || ch >= 0x61 && ch <= 0x66) {
        digit = (ch & 0x0f) + 9;
      } else if (ch === 0x3e) {
        this.eof = true;
        break;
      } else {
        continue;
      }
      if (firstDigit < 0) {
        firstDigit = digit;
      } else {
        buffer[bufferLength++] = firstDigit << 4 | digit;
        firstDigit = -1;
      }
    }
    if (firstDigit >= 0 && this.eof) {
      buffer[bufferLength++] = firstDigit << 4;
      firstDigit = -1;
    }
    this.firstDigit = firstDigit;
    this.bufferLength = bufferLength;
  }
}

;// CONCATENATED MODULE: ./src/core/ccitt.js

const ccittEOL = -2;
const ccittEOF = -1;
const twoDimPass = 0;
const twoDimHoriz = 1;
const twoDimVert0 = 2;
const twoDimVertR1 = 3;
const twoDimVertL1 = 4;
const twoDimVertR2 = 5;
const twoDimVertL2 = 6;
const twoDimVertR3 = 7;
const twoDimVertL3 = 8;
const twoDimTable = [[-1, -1], [-1, -1], [7, twoDimVertL3], [7, twoDimVertR3], [6, twoDimVertL2], [6, twoDimVertL2], [6, twoDimVertR2], [6, twoDimVertR2], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [4, twoDimPass], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimHoriz], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertL1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [3, twoDimVertR1], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0], [1, twoDimVert0]];
const whiteTable1 = [[-1, -1], [12, ccittEOL], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [11, 1792], [11, 1792], [12, 1984], [12, 2048], [12, 2112], [12, 2176], [12, 2240], [12, 2304], [11, 1856], [11, 1856], [11, 1920], [11, 1920], [12, 2368], [12, 2432], [12, 2496], [12, 2560]];
const whiteTable2 = [[-1, -1], [-1, -1], [-1, -1], [-1, -1], [8, 29], [8, 29], [8, 30], [8, 30], [8, 45], [8, 45], [8, 46], [8, 46], [7, 22], [7, 22], [7, 22], [7, 22], [7, 23], [7, 23], [7, 23], [7, 23], [8, 47], [8, 47], [8, 48], [8, 48], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [6, 13], [7, 20], [7, 20], [7, 20], [7, 20], [8, 33], [8, 33], [8, 34], [8, 34], [8, 35], [8, 35], [8, 36], [8, 36], [8, 37], [8, 37], [8, 38], [8, 38], [7, 19], [7, 19], [7, 19], [7, 19], [8, 31], [8, 31], [8, 32], [8, 32], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 1], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [6, 12], [8, 53], [8, 53], [8, 54], [8, 54], [7, 26], [7, 26], [7, 26], [7, 26], [8, 39], [8, 39], [8, 40], [8, 40], [8, 41], [8, 41], [8, 42], [8, 42], [8, 43], [8, 43], [8, 44], [8, 44], [7, 21], [7, 21], [7, 21], [7, 21], [7, 28], [7, 28], [7, 28], [7, 28], [8, 61], [8, 61], [8, 62], [8, 62], [8, 63], [8, 63], [8, 0], [8, 0], [8, 320], [8, 320], [8, 384], [8, 384], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 10], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [5, 11], [7, 27], [7, 27], [7, 27], [7, 27], [8, 59], [8, 59], [8, 60], [8, 60], [9, 1472], [9, 1536], [9, 1600], [9, 1728], [7, 18], [7, 18], [7, 18], [7, 18], [7, 24], [7, 24], [7, 24], [7, 24], [8, 49], [8, 49], [8, 50], [8, 50], [8, 51], [8, 51], [8, 52], [8, 52], [7, 25], [7, 25], [7, 25], [7, 25], [8, 55], [8, 55], [8, 56], [8, 56], [8, 57], [8, 57], [8, 58], [8, 58], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 192], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [6, 1664], [8, 448], [8, 448], [8, 512], [8, 512], [9, 704], [9, 768], [8, 640], [8, 640], [8, 576], [8, 576], [9, 832], [9, 896], [9, 960], [9, 1024], [9, 1088], [9, 1152], [9, 1216], [9, 1280], [9, 1344], [9, 1408], [7, 256], [7, 256], [7, 256], [7, 256], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 2], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [4, 3], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 128], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 8], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [5, 9], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 16], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [6, 17], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 4], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [4, 5], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 14], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [6, 15], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [5, 64], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 6], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7], [4, 7]];
const blackTable1 = [[-1, -1], [-1, -1], [12, ccittEOL], [12, ccittEOL], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [11, 1792], [11, 1792], [11, 1792], [11, 1792], [12, 1984], [12, 1984], [12, 2048], [12, 2048], [12, 2112], [12, 2112], [12, 2176], [12, 2176], [12, 2240], [12, 2240], [12, 2304], [12, 2304], [11, 1856], [11, 1856], [11, 1856], [11, 1856], [11, 1920], [11, 1920], [11, 1920], [11, 1920], [12, 2368], [12, 2368], [12, 2432], [12, 2432], [12, 2496], [12, 2496], [12, 2560], [12, 2560], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [10, 18], [12, 52], [12, 52], [13, 640], [13, 704], [13, 768], [13, 832], [12, 55], [12, 55], [12, 56], [12, 56], [13, 1280], [13, 1344], [13, 1408], [13, 1472], [12, 59], [12, 59], [12, 60], [12, 60], [13, 1536], [13, 1600], [11, 24], [11, 24], [11, 24], [11, 24], [11, 25], [11, 25], [11, 25], [11, 25], [13, 1664], [13, 1728], [12, 320], [12, 320], [12, 384], [12, 384], [12, 448], [12, 448], [13, 512], [13, 576], [12, 53], [12, 53], [12, 54], [12, 54], [13, 896], [13, 960], [13, 1024], [13, 1088], [13, 1152], [13, 1216], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64], [10, 64]];
const blackTable2 = [[8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [8, 13], [11, 23], [11, 23], [12, 50], [12, 51], [12, 44], [12, 45], [12, 46], [12, 47], [12, 57], [12, 58], [12, 61], [12, 256], [10, 16], [10, 16], [10, 16], [10, 16], [10, 17], [10, 17], [10, 17], [10, 17], [12, 48], [12, 49], [12, 62], [12, 63], [12, 30], [12, 31], [12, 32], [12, 33], [12, 40], [12, 41], [11, 22], [11, 22], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [8, 14], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 10], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [7, 11], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [9, 15], [12, 128], [12, 192], [12, 26], [12, 27], [12, 28], [12, 29], [11, 19], [11, 19], [11, 20], [11, 20], [12, 34], [12, 35], [12, 36], [12, 37], [12, 38], [12, 39], [11, 21], [11, 21], [12, 42], [12, 43], [10, 0], [10, 0], [10, 0], [10, 0], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12], [7, 12]];
const blackTable3 = [[-1, -1], [-1, -1], [-1, -1], [-1, -1], [6, 9], [6, 8], [5, 7], [5, 7], [4, 6], [4, 6], [4, 6], [4, 6], [4, 5], [4, 5], [4, 5], [4, 5], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 1], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [3, 4], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2], [2, 2]];
class CCITTFaxDecoder {
  constructor(source, options = {}) {
    if (!source || typeof source.next !== "function") {
      throw new Error('CCITTFaxDecoder - invalid "source" parameter.');
    }
    this.source = source;
    this.eof = false;
    this.encoding = options.K || 0;
    this.eoline = options.EndOfLine || false;
    this.byteAlign = options.EncodedByteAlign || false;
    this.columns = options.Columns || 1728;
    this.rows = options.Rows || 0;
    this.eoblock = options.EndOfBlock ?? true;
    this.black = options.BlackIs1 || false;
    this.codingLine = new Uint32Array(this.columns + 1);
    this.refLine = new Uint32Array(this.columns + 2);
    this.codingLine[0] = this.columns;
    this.codingPos = 0;
    this.row = 0;
    this.nextLine2D = this.encoding < 0;
    this.inputBits = 0;
    this.inputBuf = 0;
    this.outputBits = 0;
    this.rowsDone = false;
    let code1;
    while ((code1 = this._lookBits(12)) === 0) {
      this._eatBits(1);
    }
    if (code1 === 1) {
      this._eatBits(12);
    }
    if (this.encoding > 0) {
      this.nextLine2D = !this._lookBits(1);
      this._eatBits(1);
    }
  }
  readNextChar() {
    if (this.eof) {
      return -1;
    }
    const refLine = this.refLine;
    const codingLine = this.codingLine;
    const columns = this.columns;
    let refPos, blackPixels, bits, i;
    if (this.outputBits === 0) {
      if (this.rowsDone) {
        this.eof = true;
      }
      if (this.eof) {
        return -1;
      }
      this.err = false;
      let code1, code2, code3;
      if (this.nextLine2D) {
        for (i = 0; codingLine[i] < columns; ++i) {
          refLine[i] = codingLine[i];
        }
        refLine[i++] = columns;
        refLine[i] = columns;
        codingLine[0] = 0;
        this.codingPos = 0;
        refPos = 0;
        blackPixels = 0;
        while (codingLine[this.codingPos] < columns) {
          code1 = this._getTwoDimCode();
          switch (code1) {
            case twoDimPass:
              this._addPixels(refLine[refPos + 1], blackPixels);
              if (refLine[refPos + 1] < columns) {
                refPos += 2;
              }
              break;
            case twoDimHoriz:
              code1 = code2 = 0;
              if (blackPixels) {
                do {
                  code1 += code3 = this._getBlackCode();
                } while (code3 >= 64);
                do {
                  code2 += code3 = this._getWhiteCode();
                } while (code3 >= 64);
              } else {
                do {
                  code1 += code3 = this._getWhiteCode();
                } while (code3 >= 64);
                do {
                  code2 += code3 = this._getBlackCode();
                } while (code3 >= 64);
              }
              this._addPixels(codingLine[this.codingPos] + code1, blackPixels);
              if (codingLine[this.codingPos] < columns) {
                this._addPixels(codingLine[this.codingPos] + code2, blackPixels ^ 1);
              }
              while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
                refPos += 2;
              }
              break;
            case twoDimVertR3:
              this._addPixels(refLine[refPos] + 3, blackPixels);
              blackPixels ^= 1;
              if (codingLine[this.codingPos] < columns) {
                ++refPos;
                while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
                  refPos += 2;
                }
              }
              break;
            case twoDimVertR2:
              this._addPixels(refLine[refPos] + 2, blackPixels);
              blackPixels ^= 1;
              if (codingLine[this.codingPos] < columns) {
                ++refPos;
                while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
                  refPos += 2;
                }
              }
              break;
            case twoDimVertR1:
              this._addPixels(refLine[refPos] + 1, blackPixels);
              blackPixels ^= 1;
              if (codingLine[this.codingPos] < columns) {
                ++refPos;
                while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
                  refPos += 2;
                }
              }
              break;
            case twoDimVert0:
              this._addPixels(refLine[refPos], blackPixels);
              blackPixels ^= 1;
              if (codingLine[this.codingPos] < columns) {
                ++refPos;
                while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
                  refPos += 2;
                }
              }
              break;
            case twoDimVertL3:
              this._addPixelsNeg(refLine[refPos] - 3, blackPixels);
              blackPixels ^= 1;
              if (codingLine[this.codingPos] < columns) {
                if (refPos > 0) {
                  --refPos;
                } else {
                  ++refPos;
                }
                while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
                  refPos += 2;
                }
              }
              break;
            case twoDimVertL2:
              this._addPixelsNeg(refLine[refPos] - 2, blackPixels);
              blackPixels ^= 1;
              if (codingLine[this.codingPos] < columns) {
                if (refPos > 0) {
                  --refPos;
                } else {
                  ++refPos;
                }
                while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
                  refPos += 2;
                }
              }
              break;
            case twoDimVertL1:
              this._addPixelsNeg(refLine[refPos] - 1, blackPixels);
              blackPixels ^= 1;
              if (codingLine[this.codingPos] < columns) {
                if (refPos > 0) {
                  --refPos;
                } else {
                  ++refPos;
                }
                while (refLine[refPos] <= codingLine[this.codingPos] && refLine[refPos] < columns) {
                  refPos += 2;
                }
              }
              break;
            case ccittEOF:
              this._addPixels(columns, 0);
              this.eof = true;
              break;
            default:
              info("bad 2d code");
              this._addPixels(columns, 0);
              this.err = true;
          }
        }
      } else {
        codingLine[0] = 0;
        this.codingPos = 0;
        blackPixels = 0;
        while (codingLine[this.codingPos] < columns) {
          code1 = 0;
          if (blackPixels) {
            do {
              code1 += code3 = this._getBlackCode();
            } while (code3 >= 64);
          } else {
            do {
              code1 += code3 = this._getWhiteCode();
            } while (code3 >= 64);
          }
          this._addPixels(codingLine[this.codingPos] + code1, blackPixels);
          blackPixels ^= 1;
        }
      }
      let gotEOL = false;
      if (this.byteAlign) {
        this.inputBits &= ~7;
      }
      if (!this.eoblock && this.row === this.rows - 1) {
        this.rowsDone = true;
      } else {
        code1 = this._lookBits(12);
        if (this.eoline) {
          while (code1 !== ccittEOF && code1 !== 1) {
            this._eatBits(1);
            code1 = this._lookBits(12);
          }
        } else {
          while (code1 === 0) {
            this._eatBits(1);
            code1 = this._lookBits(12);
          }
        }
        if (code1 === 1) {
          this._eatBits(12);
          gotEOL = true;
        } else if (code1 === ccittEOF) {
          this.eof = true;
        }
      }
      if (!this.eof && this.encoding > 0 && !this.rowsDone) {
        this.nextLine2D = !this._lookBits(1);
        this._eatBits(1);
      }
      if (this.eoblock && gotEOL && this.byteAlign) {
        code1 = this._lookBits(12);
        if (code1 === 1) {
          this._eatBits(12);
          if (this.encoding > 0) {
            this._lookBits(1);
            this._eatBits(1);
          }
          if (this.encoding >= 0) {
            for (i = 0; i < 4; ++i) {
              code1 = this._lookBits(12);
              if (code1 !== 1) {
                info("bad rtc code: " + code1);
              }
              this._eatBits(12);
              if (this.encoding > 0) {
                this._lookBits(1);
                this._eatBits(1);
              }
            }
          }
          this.eof = true;
        }
      } else if (this.err && this.eoline) {
        while (true) {
          code1 = this._lookBits(13);
          if (code1 === ccittEOF) {
            this.eof = true;
            return -1;
          }
          if (code1 >> 1 === 1) {
            break;
          }
          this._eatBits(1);
        }
        this._eatBits(12);
        if (this.encoding > 0) {
          this._eatBits(1);
          this.nextLine2D = !(code1 & 1);
        }
      }
      this.outputBits = codingLine[0] > 0 ? codingLine[this.codingPos = 0] : codingLine[this.codingPos = 1];
      this.row++;
    }
    let c;
    if (this.outputBits >= 8) {
      c = this.codingPos & 1 ? 0 : 0xff;
      this.outputBits -= 8;
      if (this.outputBits === 0 && codingLine[this.codingPos] < columns) {
        this.codingPos++;
        this.outputBits = codingLine[this.codingPos] - codingLine[this.codingPos - 1];
      }
    } else {
      bits = 8;
      c = 0;
      do {
        if (typeof this.outputBits !== "number") {
          throw new FormatError('Invalid /CCITTFaxDecode data, "outputBits" must be a number.');
        }
        if (this.outputBits > bits) {
          c <<= bits;
          if (!(this.codingPos & 1)) {
            c |= 0xff >> 8 - bits;
          }
          this.outputBits -= bits;
          bits = 0;
        } else {
          c <<= this.outputBits;
          if (!(this.codingPos & 1)) {
            c |= 0xff >> 8 - this.outputBits;
          }
          bits -= this.outputBits;
          this.outputBits = 0;
          if (codingLine[this.codingPos] < columns) {
            this.codingPos++;
            this.outputBits = codingLine[this.codingPos] - codingLine[this.codingPos - 1];
          } else if (bits > 0) {
            c <<= bits;
            bits = 0;
          }
        }
      } while (bits);
    }
    if (this.black) {
      c ^= 0xff;
    }
    return c;
  }
  _addPixels(a1, blackPixels) {
    const codingLine = this.codingLine;
    let codingPos = this.codingPos;
    if (a1 > codingLine[codingPos]) {
      if (a1 > this.columns) {
        info("row is wrong length");
        this.err = true;
        a1 = this.columns;
      }
      if (codingPos & 1 ^ blackPixels) {
        ++codingPos;
      }
      codingLine[codingPos] = a1;
    }
    this.codingPos = codingPos;
  }
  _addPixelsNeg(a1, blackPixels) {
    const codingLine = this.codingLine;
    let codingPos = this.codingPos;
    if (a1 > codingLine[codingPos]) {
      if (a1 > this.columns) {
        info("row is wrong length");
        this.err = true;
        a1 = this.columns;
      }
      if (codingPos & 1 ^ blackPixels) {
        ++codingPos;
      }
      codingLine[codingPos] = a1;
    } else if (a1 < codingLine[codingPos]) {
      if (a1 < 0) {
        info("invalid code");
        this.err = true;
        a1 = 0;
      }
      while (codingPos > 0 && a1 < codingLine[codingPos - 1]) {
        --codingPos;
      }
      codingLine[codingPos] = a1;
    }
    this.codingPos = codingPos;
  }
  _findTableCode(start, end, table, limit) {
    const limitValue = limit || 0;
    for (let i = start; i <= end; ++i) {
      let code = this._lookBits(i);
      if (code === ccittEOF) {
        return [true, 1, false];
      }
      if (i < end) {
        code <<= end - i;
      }
      if (!limitValue || code >= limitValue) {
        const p = table[code - limitValue];
        if (p[0] === i) {
          this._eatBits(i);
          return [true, p[1], true];
        }
      }
    }
    return [false, 0, false];
  }
  _getTwoDimCode() {
    let code = 0;
    let p;
    if (this.eoblock) {
      code = this._lookBits(7);
      p = twoDimTable[code];
      if (p?.[0] > 0) {
        this._eatBits(p[0]);
        return p[1];
      }
    } else {
      const result = this._findTableCode(1, 7, twoDimTable);
      if (result[0] && result[2]) {
        return result[1];
      }
    }
    info("Bad two dim code");
    return ccittEOF;
  }
  _getWhiteCode() {
    let code = 0;
    let p;
    if (this.eoblock) {
      code = this._lookBits(12);
      if (code === ccittEOF) {
        return 1;
      }
      p = code >> 5 === 0 ? whiteTable1[code] : whiteTable2[code >> 3];
      if (p[0] > 0) {
        this._eatBits(p[0]);
        return p[1];
      }
    } else {
      let result = this._findTableCode(1, 9, whiteTable2);
      if (result[0]) {
        return result[1];
      }
      result = this._findTableCode(11, 12, whiteTable1);
      if (result[0]) {
        return result[1];
      }
    }
    info("bad white code");
    this._eatBits(1);
    return 1;
  }
  _getBlackCode() {
    let code, p;
    if (this.eoblock) {
      code = this._lookBits(13);
      if (code === ccittEOF) {
        return 1;
      }
      if (code >> 7 === 0) {
        p = blackTable1[code];
      } else if (code >> 9 === 0 && code >> 7 !== 0) {
        p = blackTable2[(code >> 1) - 64];
      } else {
        p = blackTable3[code >> 7];
      }
      if (p[0] > 0) {
        this._eatBits(p[0]);
        return p[1];
      }
    } else {
      let result = this._findTableCode(2, 6, blackTable3);
      if (result[0]) {
        return result[1];
      }
      result = this._findTableCode(7, 12, blackTable2, 64);
      if (result[0]) {
        return result[1];
      }
      result = this._findTableCode(10, 13, blackTable1);
      if (result[0]) {
        return result[1];
      }
    }
    info("bad black code");
    this._eatBits(1);
    return 1;
  }
  _lookBits(n) {
    let c;
    while (this.inputBits < n) {
      if ((c = this.source.next()) === -1) {
        if (this.inputBits === 0) {
          return ccittEOF;
        }
        return this.inputBuf << n - this.inputBits & 0xffff >> 16 - n;
      }
      this.inputBuf = this.inputBuf << 8 | c;
      this.inputBits += 8;
    }
    return this.inputBuf >> this.inputBits - n & 0xffff >> 16 - n;
  }
  _eatBits(n) {
    if ((this.inputBits -= n) < 0) {
      this.inputBits = 0;
    }
  }
}

;// CONCATENATED MODULE: ./src/core/ccitt_stream.js



class CCITTFaxStream extends DecodeStream {
  constructor(str, maybeLength, params) {
    super(maybeLength);
    this.str = str;
    this.dict = str.dict;
    if (!(params instanceof Dict)) {
      params = Dict.empty;
    }
    const source = {
      next() {
        return str.getByte();
      }
    };
    this.ccittFaxDecoder = new CCITTFaxDecoder(source, {
      K: params.get("K"),
      EndOfLine: params.get("EndOfLine"),
      EncodedByteAlign: params.get("EncodedByteAlign"),
      Columns: params.get("Columns"),
      Rows: params.get("Rows"),
      EndOfBlock: params.get("EndOfBlock"),
      BlackIs1: params.get("BlackIs1")
    });
  }
  readBlock() {
    while (!this.eof) {
      const c = this.ccittFaxDecoder.readNextChar();
      if (c === -1) {
        this.eof = true;
        return;
      }
      this.ensureBuffer(this.bufferLength + 1);
      this.buffer[this.bufferLength++] = c;
    }
  }
}

;// CONCATENATED MODULE: ./src/core/flate_stream.js



const codeLenCodeMap = new Int32Array([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
const lengthDecode = new Int32Array([0x00003, 0x00004, 0x00005, 0x00006, 0x00007, 0x00008, 0x00009, 0x0000a, 0x1000b, 0x1000d, 0x1000f, 0x10011, 0x20013, 0x20017, 0x2001b, 0x2001f, 0x30023, 0x3002b, 0x30033, 0x3003b, 0x40043, 0x40053, 0x40063, 0x40073, 0x50083, 0x500a3, 0x500c3, 0x500e3, 0x00102, 0x00102, 0x00102]);
const distDecode = new Int32Array([0x00001, 0x00002, 0x00003, 0x00004, 0x10005, 0x10007, 0x20009, 0x2000d, 0x30011, 0x30019, 0x40021, 0x40031, 0x50041, 0x50061, 0x60081, 0x600c1, 0x70101, 0x70181, 0x80201, 0x80301, 0x90401, 0x90601, 0xa0801, 0xa0c01, 0xb1001, 0xb1801, 0xc2001, 0xc3001, 0xd4001, 0xd6001]);
const fixedLitCodeTab = [new Int32Array([0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c0, 0x70108, 0x80060, 0x80020, 0x900a0, 0x80000, 0x80080, 0x80040, 0x900e0, 0x70104, 0x80058, 0x80018, 0x90090, 0x70114, 0x80078, 0x80038, 0x900d0, 0x7010c, 0x80068, 0x80028, 0x900b0, 0x80008, 0x80088, 0x80048, 0x900f0, 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c8, 0x7010a, 0x80064, 0x80024, 0x900a8, 0x80004, 0x80084, 0x80044, 0x900e8, 0x70106, 0x8005c, 0x8001c, 0x90098, 0x70116, 0x8007c, 0x8003c, 0x900d8, 0x7010e, 0x8006c, 0x8002c, 0x900b8, 0x8000c, 0x8008c, 0x8004c, 0x900f8, 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c4, 0x70109, 0x80062, 0x80022, 0x900a4, 0x80002, 0x80082, 0x80042, 0x900e4, 0x70105, 0x8005a, 0x8001a, 0x90094, 0x70115, 0x8007a, 0x8003a, 0x900d4, 0x7010d, 0x8006a, 0x8002a, 0x900b4, 0x8000a, 0x8008a, 0x8004a, 0x900f4, 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cc, 0x7010b, 0x80066, 0x80026, 0x900ac, 0x80006, 0x80086, 0x80046, 0x900ec, 0x70107, 0x8005e, 0x8001e, 0x9009c, 0x70117, 0x8007e, 0x8003e, 0x900dc, 0x7010f, 0x8006e, 0x8002e, 0x900bc, 0x8000e, 0x8008e, 0x8004e, 0x900fc, 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c2, 0x70108, 0x80061, 0x80021, 0x900a2, 0x80001, 0x80081, 0x80041, 0x900e2, 0x70104, 0x80059, 0x80019, 0x90092, 0x70114, 0x80079, 0x80039, 0x900d2, 0x7010c, 0x80069, 0x80029, 0x900b2, 0x80009, 0x80089, 0x80049, 0x900f2, 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900ca, 0x7010a, 0x80065, 0x80025, 0x900aa, 0x80005, 0x80085, 0x80045, 0x900ea, 0x70106, 0x8005d, 0x8001d, 0x9009a, 0x70116, 0x8007d, 0x8003d, 0x900da, 0x7010e, 0x8006d, 0x8002d, 0x900ba, 0x8000d, 0x8008d, 0x8004d, 0x900fa, 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c6, 0x70109, 0x80063, 0x80023, 0x900a6, 0x80003, 0x80083, 0x80043, 0x900e6, 0x70105, 0x8005b, 0x8001b, 0x90096, 0x70115, 0x8007b, 0x8003b, 0x900d6, 0x7010d, 0x8006b, 0x8002b, 0x900b6, 0x8000b, 0x8008b, 0x8004b, 0x900f6, 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900ce, 0x7010b, 0x80067, 0x80027, 0x900ae, 0x80007, 0x80087, 0x80047, 0x900ee, 0x70107, 0x8005f, 0x8001f, 0x9009e, 0x70117, 0x8007f, 0x8003f, 0x900de, 0x7010f, 0x8006f, 0x8002f, 0x900be, 0x8000f, 0x8008f, 0x8004f, 0x900fe, 0x70100, 0x80050, 0x80010, 0x80118, 0x70110, 0x80070, 0x80030, 0x900c1, 0x70108, 0x80060, 0x80020, 0x900a1, 0x80000, 0x80080, 0x80040, 0x900e1, 0x70104, 0x80058, 0x80018, 0x90091, 0x70114, 0x80078, 0x80038, 0x900d1, 0x7010c, 0x80068, 0x80028, 0x900b1, 0x80008, 0x80088, 0x80048, 0x900f1, 0x70102, 0x80054, 0x80014, 0x8011c, 0x70112, 0x80074, 0x80034, 0x900c9, 0x7010a, 0x80064, 0x80024, 0x900a9, 0x80004, 0x80084, 0x80044, 0x900e9, 0x70106, 0x8005c, 0x8001c, 0x90099, 0x70116, 0x8007c, 0x8003c, 0x900d9, 0x7010e, 0x8006c, 0x8002c, 0x900b9, 0x8000c, 0x8008c, 0x8004c, 0x900f9, 0x70101, 0x80052, 0x80012, 0x8011a, 0x70111, 0x80072, 0x80032, 0x900c5, 0x70109, 0x80062, 0x80022, 0x900a5, 0x80002, 0x80082, 0x80042, 0x900e5, 0x70105, 0x8005a, 0x8001a, 0x90095, 0x70115, 0x8007a, 0x8003a, 0x900d5, 0x7010d, 0x8006a, 0x8002a, 0x900b5, 0x8000a, 0x8008a, 0x8004a, 0x900f5, 0x70103, 0x80056, 0x80016, 0x8011e, 0x70113, 0x80076, 0x80036, 0x900cd, 0x7010b, 0x80066, 0x80026, 0x900ad, 0x80006, 0x80086, 0x80046, 0x900ed, 0x70107, 0x8005e, 0x8001e, 0x9009d, 0x70117, 0x8007e, 0x8003e, 0x900dd, 0x7010f, 0x8006e, 0x8002e, 0x900bd, 0x8000e, 0x8008e, 0x8004e, 0x900fd, 0x70100, 0x80051, 0x80011, 0x80119, 0x70110, 0x80071, 0x80031, 0x900c3, 0x70108, 0x80061, 0x80021, 0x900a3, 0x80001, 0x80081, 0x80041, 0x900e3, 0x70104, 0x80059, 0x80019, 0x90093, 0x70114, 0x80079, 0x80039, 0x900d3, 0x7010c, 0x80069, 0x80029, 0x900b3, 0x80009, 0x80089, 0x80049, 0x900f3, 0x70102, 0x80055, 0x80015, 0x8011d, 0x70112, 0x80075, 0x80035, 0x900cb, 0x7010a, 0x80065, 0x80025, 0x900ab, 0x80005, 0x80085, 0x80045, 0x900eb, 0x70106, 0x8005d, 0x8001d, 0x9009b, 0x70116, 0x8007d, 0x8003d, 0x900db, 0x7010e, 0x8006d, 0x8002d, 0x900bb, 0x8000d, 0x8008d, 0x8004d, 0x900fb, 0x70101, 0x80053, 0x80013, 0x8011b, 0x70111, 0x80073, 0x80033, 0x900c7, 0x70109, 0x80063, 0x80023, 0x900a7, 0x80003, 0x80083, 0x80043, 0x900e7, 0x70105, 0x8005b, 0x8001b, 0x90097, 0x70115, 0x8007b, 0x8003b, 0x900d7, 0x7010d, 0x8006b, 0x8002b, 0x900b7, 0x8000b, 0x8008b, 0x8004b, 0x900f7, 0x70103, 0x80057, 0x80017, 0x8011f, 0x70113, 0x80077, 0x80037, 0x900cf, 0x7010b, 0x80067, 0x80027, 0x900af, 0x80007, 0x80087, 0x80047, 0x900ef, 0x70107, 0x8005f, 0x8001f, 0x9009f, 0x70117, 0x8007f, 0x8003f, 0x900df, 0x7010f, 0x8006f, 0x8002f, 0x900bf, 0x8000f, 0x8008f, 0x8004f, 0x900ff]), 9];
const fixedDistCodeTab = [new Int32Array([0x50000, 0x50010, 0x50008, 0x50018, 0x50004, 0x50014, 0x5000c, 0x5001c, 0x50002, 0x50012, 0x5000a, 0x5001a, 0x50006, 0x50016, 0x5000e, 0x00000, 0x50001, 0x50011, 0x50009, 0x50019, 0x50005, 0x50015, 0x5000d, 0x5001d, 0x50003, 0x50013, 0x5000b, 0x5001b, 0x50007, 0x50017, 0x5000f, 0x00000]), 5];
class FlateStream extends DecodeStream {
  constructor(str, maybeLength) {
    super(maybeLength);
    this.str = str;
    this.dict = str.dict;
    const cmf = str.getByte();
    const flg = str.getByte();
    if (cmf === -1 || flg === -1) {
      throw new FormatError(`Invalid header in flate stream: ${cmf}, ${flg}`);
    }
    if ((cmf & 0x0f) !== 0x08) {
      throw new FormatError(`Unknown compression method in flate stream: ${cmf}, ${flg}`);
    }
    if (((cmf << 8) + flg) % 31 !== 0) {
      throw new FormatError(`Bad FCHECK in flate stream: ${cmf}, ${flg}`);
    }
    if (flg & 0x20) {
      throw new FormatError(`FDICT bit set in flate stream: ${cmf}, ${flg}`);
    }
    this.codeSize = 0;
    this.codeBuf = 0;
  }
  async getImageData(length, _decoderOptions) {
    const data = await this.asyncGetBytes();
    return data?.subarray(0, length) || this.getBytes(length);
  }
  async asyncGetBytes() {
    this.str.reset();
    const bytes = this.str.getBytes();
    try {
      const {
        readable,
        writable
      } = new DecompressionStream("deflate");
      const writer = writable.getWriter();
      writer.write(bytes);
      writer.close();
      const chunks = [];
      let totalLength = 0;
      for await (const chunk of readable) {
        chunks.push(chunk);
        totalLength += chunk.byteLength;
      }
      const data = new Uint8Array(totalLength);
      let offset = 0;
      for (const chunk of chunks) {
        data.set(chunk, offset);
        offset += chunk.byteLength;
      }
      return data;
    } catch {
      this.str = new Stream(bytes, 2, bytes.length, this.str.dict);
      this.reset();
      return null;
    }
  }
  get isAsync() {
    return true;
  }
  getBits(bits) {
    const str = this.str;
    let codeSize = this.codeSize;
    let codeBuf = this.codeBuf;
    let b;
    while (codeSize < bits) {
      if ((b = str.getByte()) === -1) {
        throw new FormatError("Bad encoding in flate stream");
      }
      codeBuf |= b << codeSize;
      codeSize += 8;
    }
    b = codeBuf & (1 << bits) - 1;
    this.codeBuf = codeBuf >> bits;
    this.codeSize = codeSize -= bits;
    return b;
  }
  getCode(table) {
    const str = this.str;
    const codes = table[0];
    const maxLen = table[1];
    let codeSize = this.codeSize;
    let codeBuf = this.codeBuf;
    let b;
    while (codeSize < maxLen) {
      if ((b = str.getByte()) === -1) {
        break;
      }
      codeBuf |= b << codeSize;
      codeSize += 8;
    }
    const code = codes[codeBuf & (1 << maxLen) - 1];
    const codeLen = code >> 16;
    const codeVal = code & 0xffff;
    if (codeLen < 1 || codeSize < codeLen) {
      throw new FormatError("Bad encoding in flate stream");
    }
    this.codeBuf = codeBuf >> codeLen;
    this.codeSize = codeSize - codeLen;
    return codeVal;
  }
  generateHuffmanTable(lengths) {
    const n = lengths.length;
    let maxLen = 0;
    let i;
    for (i = 0; i < n; ++i) {
      if (lengths[i] > maxLen) {
        maxLen = lengths[i];
      }
    }
    const size = 1 << maxLen;
    const codes = new Int32Array(size);
    for (let len = 1, code = 0, skip = 2; len <= maxLen; ++len, code <<= 1, skip <<= 1) {
      for (let val = 0; val < n; ++val) {
        if (lengths[val] === len) {
          let code2 = 0;
          let t = code;
          for (i = 0; i < len; ++i) {
            code2 = code2 << 1 | t & 1;
            t >>= 1;
          }
          for (i = code2; i < size; i += skip) {
            codes[i] = len << 16 | val;
          }
          ++code;
        }
      }
    }
    return [codes, maxLen];
  }
  #endsStreamOnError(err) {
    info(err);
    this.eof = true;
  }
  readBlock() {
    let buffer, hdr, len;
    const str = this.str;
    try {
      hdr = this.getBits(3);
    } catch (ex) {
      this.#endsStreamOnError(ex.message);
      return;
    }
    if (hdr & 1) {
      this.eof = true;
    }
    hdr >>= 1;
    if (hdr === 0) {
      let b;
      if ((b = str.getByte()) === -1) {
        this.#endsStreamOnError("Bad block header in flate stream");
        return;
      }
      let blockLen = b;
      if ((b = str.getByte()) === -1) {
        this.#endsStreamOnError("Bad block header in flate stream");
        return;
      }
      blockLen |= b << 8;
      if ((b = str.getByte()) === -1) {
        this.#endsStreamOnError("Bad block header in flate stream");
        return;
      }
      let check = b;
      if ((b = str.getByte()) === -1) {
        this.#endsStreamOnError("Bad block header in flate stream");
        return;
      }
      check |= b << 8;
      if (check !== (~blockLen & 0xffff) && (blockLen !== 0 || check !== 0)) {
        throw new FormatError("Bad uncompressed block length in flate stream");
      }
      this.codeBuf = 0;
      this.codeSize = 0;
      const bufferLength = this.bufferLength,
        end = bufferLength + blockLen;
      buffer = this.ensureBuffer(end);
      this.bufferLength = end;
      if (blockLen === 0) {
        if (str.peekByte() === -1) {
          this.eof = true;
        }
      } else {
        const block = str.getBytes(blockLen);
        buffer.set(block, bufferLength);
        if (block.length < blockLen) {
          this.eof = true;
        }
      }
      return;
    }
    let litCodeTable;
    let distCodeTable;
    if (hdr === 1) {
      litCodeTable = fixedLitCodeTab;
      distCodeTable = fixedDistCodeTab;
    } else if (hdr === 2) {
      const numLitCodes = this.getBits(5) + 257;
      const numDistCodes = this.getBits(5) + 1;
      const numCodeLenCodes = this.getBits(4) + 4;
      const codeLenCodeLengths = new Uint8Array(codeLenCodeMap.length);
      let i;
      for (i = 0; i < numCodeLenCodes; ++i) {
        codeLenCodeLengths[codeLenCodeMap[i]] = this.getBits(3);
      }
      const codeLenCodeTab = this.generateHuffmanTable(codeLenCodeLengths);
      len = 0;
      i = 0;
      const codes = numLitCodes + numDistCodes;
      const codeLengths = new Uint8Array(codes);
      let bitsLength, bitsOffset, what;
      while (i < codes) {
        const code = this.getCode(codeLenCodeTab);
        if (code === 16) {
          bitsLength = 2;
          bitsOffset = 3;
          what = len;
        } else if (code === 17) {
          bitsLength = 3;
          bitsOffset = 3;
          what = len = 0;
        } else if (code === 18) {
          bitsLength = 7;
          bitsOffset = 11;
          what = len = 0;
        } else {
          codeLengths[i++] = len = code;
          continue;
        }
        let repeatLength = this.getBits(bitsLength) + bitsOffset;
        while (repeatLength-- > 0) {
          codeLengths[i++] = what;
        }
      }
      litCodeTable = this.generateHuffmanTable(codeLengths.subarray(0, numLitCodes));
      distCodeTable = this.generateHuffmanTable(codeLengths.subarray(numLitCodes, codes));
    } else {
      throw new FormatError("Unknown block type in flate stream");
    }
    buffer = this.buffer;
    let limit = buffer ? buffer.length : 0;
    let pos = this.bufferLength;
    while (true) {
      let code1 = this.getCode(litCodeTable);
      if (code1 < 256) {
        if (pos + 1 >= limit) {
          buffer = this.ensureBuffer(pos + 1);
          limit = buffer.length;
        }
        buffer[pos++] = code1;
        continue;
      }
      if (code1 === 256) {
        this.bufferLength = pos;
        return;
      }
      code1 -= 257;
      code1 = lengthDecode[code1];
      let code2 = code1 >> 16;
      if (code2 > 0) {
        code2 = this.getBits(code2);
      }
      len = (code1 & 0xffff) + code2;
      code1 = this.getCode(distCodeTable);
      code1 = distDecode[code1];
      code2 = code1 >> 16;
      if (code2 > 0) {
        code2 = this.getBits(code2);
      }
      const dist = (code1 & 0xffff) + code2;
      if (pos + len >= limit) {
        buffer = this.ensureBuffer(pos + len);
        limit = buffer.length;
      }
      for (let k = 0; k < len; ++k, ++pos) {
        buffer[pos] = buffer[pos - dist];
      }
    }
  }
}

;// CONCATENATED MODULE: ./src/core/arithmetic_decoder.js
const QeTable = [{
  qe: 0x5601,
  nmps: 1,
  nlps: 1,
  switchFlag: 1
}, {
  qe: 0x3401,
  nmps: 2,
  nlps: 6,
  switchFlag: 0
}, {
  qe: 0x1801,
  nmps: 3,
  nlps: 9,
  switchFlag: 0
}, {
  qe: 0x0ac1,
  nmps: 4,
  nlps: 12,
  switchFlag: 0
}, {
  qe: 0x0521,
  nmps: 5,
  nlps: 29,
  switchFlag: 0
}, {
  qe: 0x0221,
  nmps: 38,
  nlps: 33,
  switchFlag: 0
}, {
  qe: 0x5601,
  nmps: 7,
  nlps: 6,
  switchFlag: 1
}, {
  qe: 0x5401,
  nmps: 8,
  nlps: 14,
  switchFlag: 0
}, {
  qe: 0x4801,
  nmps: 9,
  nlps: 14,
  switchFlag: 0
}, {
  qe: 0x3801,
  nmps: 10,
  nlps: 14,
  switchFlag: 0
}, {
  qe: 0x3001,
  nmps: 11,
  nlps: 17,
  switchFlag: 0
}, {
  qe: 0x2401,
  nmps: 12,
  nlps: 18,
  switchFlag: 0
}, {
  qe: 0x1c01,
  nmps: 13,
  nlps: 20,
  switchFlag: 0
}, {
  qe: 0x1601,
  nmps: 29,
  nlps: 21,
  switchFlag: 0
}, {
  qe: 0x5601,
  nmps: 15,
  nlps: 14,
  switchFlag: 1
}, {
  qe: 0x5401,
  nmps: 16,
  nlps: 14,
  switchFlag: 0
}, {
  qe: 0x5101,
  nmps: 17,
  nlps: 15,
  switchFlag: 0
}, {
  qe: 0x4801,
  nmps: 18,
  nlps: 16,
  switchFlag: 0
}, {
  qe: 0x3801,
  nmps: 19,
  nlps: 17,
  switchFlag: 0
}, {
  qe: 0x3401,
  nmps: 20,
  nlps: 18,
  switchFlag: 0
}, {
  qe: 0x3001,
  nmps: 21,
  nlps: 19,
  switchFlag: 0
}, {
  qe: 0x2801,
  nmps: 22,
  nlps: 19,
  switchFlag: 0
}, {
  qe: 0x2401,
  nmps: 23,
  nlps: 20,
  switchFlag: 0
}, {
  qe: 0x2201,
  nmps: 24,
  nlps: 21,
  switchFlag: 0
}, {
  qe: 0x1c01,
  nmps: 25,
  nlps: 22,
  switchFlag: 0
}, {
  qe: 0x1801,
  nmps: 26,
  nlps: 23,
  switchFlag: 0
}, {
  qe: 0x1601,
  nmps: 27,
  nlps: 24,
  switchFlag: 0
}, {
  qe: 0x1401,
  nmps: 28,
  nlps: 25,
  switchFlag: 0
}, {
  qe: 0x1201,
  nmps: 29,
  nlps: 26,
  switchFlag: 0
}, {
  qe: 0x1101,
  nmps: 30,
  nlps: 27,
  switchFlag: 0
}, {
  qe: 0x0ac1,
  nmps: 31,
  nlps: 28,
  switchFlag: 0
}, {
  qe: 0x09c1,
  nmps: 32,
  nlps: 29,
  switchFlag: 0
}, {
  qe: 0x08a1,
  nmps: 33,
  nlps: 30,
  switchFlag: 0
}, {
  qe: 0x0521,
  nmps: 34,
  nlps: 31,
  switchFlag: 0
}, {
  qe: 0x0441,
  nmps: 35,
  nlps: 32,
  switchFlag: 0
}, {
  qe: 0x02a1,
  nmps: 36,
  nlps: 33,
  switchFlag: 0
}, {
  qe: 0x0221,
  nmps: 37,
  nlps: 34,
  switchFlag: 0
}, {
  qe: 0x0141,
  nmps: 38,
  nlps: 35,
  switchFlag: 0
}, {
  qe: 0x0111,
  nmps: 39,
  nlps: 36,
  switchFlag: 0
}, {
  qe: 0x0085,
  nmps: 40,
  nlps: 37,
  switchFlag: 0
}, {
  qe: 0x0049,
  nmps: 41,
  nlps: 38,
  switchFlag: 0
}, {
  qe: 0x0025,
  nmps: 42,
  nlps: 39,
  switchFlag: 0
}, {
  qe: 0x0015,
  nmps: 43,
  nlps: 40,
  switchFlag: 0
}, {
  qe: 0x0009,
  nmps: 44,
  nlps: 41,
  switchFlag: 0
}, {
  qe: 0x0005,
  nmps: 45,
  nlps: 42,
  switchFlag: 0
}, {
  qe: 0x0001,
  nmps: 45,
  nlps: 43,
  switchFlag: 0
}, {
  qe: 0x5601,
  nmps: 46,
  nlps: 46,
  switchFlag: 0
}];
class ArithmeticDecoder {
  constructor(data, start, end) {
    this.data = data;
    this.bp = start;
    this.dataEnd = end;
    this.chigh = data[start];
    this.clow = 0;
    this.byteIn();
    this.chigh = this.chigh << 7 & 0xffff | this.clow >> 9 & 0x7f;
    this.clow = this.clow << 7 & 0xffff;
    this.ct -= 7;
    this.a = 0x8000;
  }
  byteIn() {
    const data = this.data;
    let bp = this.bp;
    if (data[bp] === 0xff) {
      if (data[bp + 1] > 0x8f) {
        this.clow += 0xff00;
        this.ct = 8;
      } else {
        bp++;
        this.clow += data[bp] << 9;
        this.ct = 7;
        this.bp = bp;
      }
    } else {
      bp++;
      this.clow += bp < this.dataEnd ? data[bp] << 8 : 0xff00;
      this.ct = 8;
      this.bp = bp;
    }
    if (this.clow > 0xffff) {
      this.chigh += this.clow >> 16;
      this.clow &= 0xffff;
    }
  }
  readBit(contexts, pos) {
    let cx_index = contexts[pos] >> 1,
      cx_mps = contexts[pos] & 1;
    const qeTableIcx = QeTable[cx_index];
    const qeIcx = qeTableIcx.qe;
    let d;
    let a = this.a - qeIcx;
    if (this.chigh < qeIcx) {
      if (a < qeIcx) {
        a = qeIcx;
        d = cx_mps;
        cx_index = qeTableIcx.nmps;
      } else {
        a = qeIcx;
        d = 1 ^ cx_mps;
        if (qeTableIcx.switchFlag === 1) {
          cx_mps = d;
        }
        cx_index = qeTableIcx.nlps;
      }
    } else {
      this.chigh -= qeIcx;
      if ((a & 0x8000) !== 0) {
        this.a = a;
        return cx_mps;
      }
      if (a < qeIcx) {
        d = 1 ^ cx_mps;
        if (qeTableIcx.switchFlag === 1) {
          cx_mps = d;
        }
        cx_index = qeTableIcx.nlps;
      } else {
        d = cx_mps;
        cx_index = qeTableIcx.nmps;
      }
    }
    do {
      if (this.ct === 0) {
        this.byteIn();
      }
      a <<= 1;
      this.chigh = this.chigh << 1 & 0xffff | this.clow >> 15 & 1;
      this.clow = this.clow << 1 & 0xffff;
      this.ct--;
    } while ((a & 0x8000) === 0);
    this.a = a;
    contexts[pos] = cx_index << 1 | cx_mps;
    return d;
  }
}

;// CONCATENATED MODULE: ./src/core/jbig2.js




class Jbig2Error extends BaseException {
  constructor(msg) {
    super(msg, "Jbig2Error");
  }
}
class ContextCache {
  getContexts(id) {
    if (id in this) {
      return this[id];
    }
    return this[id] = new Int8Array(1 << 16);
  }
}
class DecodingContext {
  constructor(data, start, end) {
    this.data = data;
    this.start = start;
    this.end = end;
  }
  get decoder() {
    const decoder = new ArithmeticDecoder(this.data, this.start, this.end);
    return shadow(this, "decoder", decoder);
  }
  get contextCache() {
    const cache = new ContextCache();
    return shadow(this, "contextCache", cache);
  }
}
const MAX_INT_32 = 2 ** 31 - 1;
const MIN_INT_32 = -(2 ** 31);
function decodeInteger(contextCache, procedure, decoder) {
  const contexts = contextCache.getContexts(procedure);
  let prev = 1;
  function readBits(length) {
    let v = 0;
    for (let i = 0; i < length; i++) {
      const bit = decoder.readBit(contexts, prev);
      prev = prev < 256 ? prev << 1 | bit : (prev << 1 | bit) & 511 | 256;
      v = v << 1 | bit;
    }
    return v >>> 0;
  }
  const sign = readBits(1);
  const value = readBits(1) ? readBits(1) ? readBits(1) ? readBits(1) ? readBits(1) ? readBits(32) + 4436 : readBits(12) + 340 : readBits(8) + 84 : readBits(6) + 20 : readBits(4) + 4 : readBits(2);
  let signedValue;
  if (sign === 0) {
    signedValue = value;
  } else if (value > 0) {
    signedValue = -value;
  }
  if (signedValue >= MIN_INT_32 && signedValue <= MAX_INT_32) {
    return signedValue;
  }
  return null;
}
function decodeIAID(contextCache, decoder, codeLength) {
  const contexts = contextCache.getContexts("IAID");
  let prev = 1;
  for (let i = 0; i < codeLength; i++) {
    const bit = decoder.readBit(contexts, prev);
    prev = prev << 1 | bit;
  }
  if (codeLength < 31) {
    return prev & (1 << codeLength) - 1;
  }
  return prev & 0x7fffffff;
}
const SegmentTypes = ["SymbolDictionary", null, null, null, "IntermediateTextRegion", null, "ImmediateTextRegion", "ImmediateLosslessTextRegion", null, null, null, null, null, null, null, null, "PatternDictionary", null, null, null, "IntermediateHalftoneRegion", null, "ImmediateHalftoneRegion", "ImmediateLosslessHalftoneRegion", null, null, null, null, null, null, null, null, null, null, null, null, "IntermediateGenericRegion", null, "ImmediateGenericRegion", "ImmediateLosslessGenericRegion", "IntermediateGenericRefinementRegion", null, "ImmediateGenericRefinementRegion", "ImmediateLosslessGenericRefinementRegion", null, null, null, null, "PageInformation", "EndOfPage", "EndOfStripe", "EndOfFile", "Profiles", "Tables", null, null, null, null, null, null, null, null, "Extension"];
const CodingTemplates = [[{
  x: -1,
  y: -2
}, {
  x: 0,
  y: -2
}, {
  x: 1,
  y: -2
}, {
  x: -2,
  y: -1
}, {
  x: -1,
  y: -1
}, {
  x: 0,
  y: -1
}, {
  x: 1,
  y: -1
}, {
  x: 2,
  y: -1
}, {
  x: -4,
  y: 0
}, {
  x: -3,
  y: 0
}, {
  x: -2,
  y: 0
}, {
  x: -1,
  y: 0
}], [{
  x: -1,
  y: -2
}, {
  x: 0,
  y: -2
}, {
  x: 1,
  y: -2
}, {
  x: 2,
  y: -2
}, {
  x: -2,
  y: -1
}, {
  x: -1,
  y: -1
}, {
  x: 0,
  y: -1
}, {
  x: 1,
  y: -1
}, {
  x: 2,
  y: -1
}, {
  x: -3,
  y: 0
}, {
  x: -2,
  y: 0
}, {
  x: -1,
  y: 0
}], [{
  x: -1,
  y: -2
}, {
  x: 0,
  y: -2
}, {
  x: 1,
  y: -2
}, {
  x: -2,
  y: -1
}, {
  x: -1,
  y: -1
}, {
  x: 0,
  y: -1
}, {
  x: 1,
  y: -1
}, {
  x: -2,
  y: 0
}, {
  x: -1,
  y: 0
}], [{
  x: -3,
  y: -1
}, {
  x: -2,
  y: -1
}, {
  x: -1,
  y: -1
}, {
  x: 0,
  y: -1
}, {
  x: 1,
  y: -1
}, {
  x: -4,
  y: 0
}, {
  x: -3,
  y: 0
}, {
  x: -2,
  y: 0
}, {
  x: -1,
  y: 0
}]];
const RefinementTemplates = [{
  coding: [{
    x: 0,
    y: -1
  }, {
    x: 1,
    y: -1
  }, {
    x: -1,
    y: 0
  }],
  reference: [{
    x: 0,
    y: -1
  }, {
    x: 1,
    y: -1
  }, {
    x: -1,
    y: 0
  }, {
    x: 0,
    y: 0
  }, {
    x: 1,
    y: 0
  }, {
    x: -1,
    y: 1
  }, {
    x: 0,
    y: 1
  }, {
    x: 1,
    y: 1
  }]
}, {
  coding: [{
    x: -1,
    y: -1
  }, {
    x: 0,
    y: -1
  }, {
    x: 1,
    y: -1
  }, {
    x: -1,
    y: 0
  }],
  reference: [{
    x: 0,
    y: -1
  }, {
    x: -1,
    y: 0
  }, {
    x: 0,
    y: 0
  }, {
    x: 1,
    y: 0
  }, {
    x: 0,
    y: 1
  }, {
    x: 1,
    y: 1
  }]
}];
const ReusedContexts = [0x9b25, 0x0795, 0x00e5, 0x0195];
const RefinementReusedContexts = [0x0020, 0x0008];
function decodeBitmapTemplate0(width, height, decodingContext) {
  const decoder = decodingContext.decoder;
  const contexts = decodingContext.contextCache.getContexts("GB");
  const bitmap = [];
  let contextLabel, i, j, pixel, row, row1, row2;
  const OLD_PIXEL_MASK = 0x7bf7;
  for (i = 0; i < height; i++) {
    row = bitmap[i] = new Uint8Array(width);
    row1 = i < 1 ? row : bitmap[i - 1];
    row2 = i < 2 ? row : bitmap[i - 2];
    contextLabel = row2[0] << 13 | row2[1] << 12 | row2[2] << 11 | row1[0] << 7 | row1[1] << 6 | row1[2] << 5 | row1[3] << 4;
    for (j = 0; j < width; j++) {
      row[j] = pixel = decoder.readBit(contexts, contextLabel);
      contextLabel = (contextLabel & OLD_PIXEL_MASK) << 1 | (j + 3 < width ? row2[j + 3] << 11 : 0) | (j + 4 < width ? row1[j + 4] << 4 : 0) | pixel;
    }
  }
  return bitmap;
}
function decodeBitmap(mmr, width, height, templateIndex, prediction, skip, at, decodingContext) {
  if (mmr) {
    const input = new Reader(decodingContext.data, decodingContext.start, decodingContext.end);
    return decodeMMRBitmap(input, width, height, false);
  }
  if (templateIndex === 0 && !skip && !prediction && at.length === 4 && at[0].x === 3 && at[0].y === -1 && at[1].x === -3 && at[1].y === -1 && at[2].x === 2 && at[2].y === -2 && at[3].x === -2 && at[3].y === -2) {
    return decodeBitmapTemplate0(width, height, decodingContext);
  }
  const useskip = !!skip;
  const template = CodingTemplates[templateIndex].concat(at);
  template.sort(function (a, b) {
    return a.y - b.y || a.x - b.x;
  });
  const templateLength = template.length;
  const templateX = new Int8Array(templateLength);
  const templateY = new Int8Array(templateLength);
  const changingTemplateEntries = [];
  let reuseMask = 0,
    minX = 0,
    maxX = 0,
    minY = 0;
  let c, k;
  for (k = 0; k < templateLength; k++) {
    templateX[k] = template[k].x;
    templateY[k] = template[k].y;
    minX = Math.min(minX, template[k].x);
    maxX = Math.max(maxX, template[k].x);
    minY = Math.min(minY, template[k].y);
    if (k < templateLength - 1 && template[k].y === template[k + 1].y && template[k].x === template[k + 1].x - 1) {
      reuseMask |= 1 << templateLength - 1 - k;
    } else {
      changingTemplateEntries.push(k);
    }
  }
  const changingEntriesLength = changingTemplateEntries.length;
  const changingTemplateX = new Int8Array(changingEntriesLength);
  const changingTemplateY = new Int8Array(changingEntriesLength);
  const changingTemplateBit = new Uint16Array(changingEntriesLength);
  for (c = 0; c < changingEntriesLength; c++) {
    k = changingTemplateEntries[c];
    changingTemplateX[c] = template[k].x;
    changingTemplateY[c] = template[k].y;
    changingTemplateBit[c] = 1 << templateLength - 1 - k;
  }
  const sbb_left = -minX;
  const sbb_top = -minY;
  const sbb_right = width - maxX;
  const pseudoPixelContext = ReusedContexts[templateIndex];
  let row = new Uint8Array(width);
  const bitmap = [];
  const decoder = decodingContext.decoder;
  const contexts = decodingContext.contextCache.getContexts("GB");
  let ltp = 0,
    j,
    i0,
    j0,
    contextLabel = 0,
    bit,
    shift;
  for (let i = 0; i < height; i++) {
    if (prediction) {
      const sltp = decoder.readBit(contexts, pseudoPixelContext);
      ltp ^= sltp;
      if (ltp) {
        bitmap.push(row);
        continue;
      }
    }
    row = new Uint8Array(row);
    bitmap.push(row);
    for (j = 0; j < width; j++) {
      if (useskip && skip[i][j]) {
        row[j] = 0;
        continue;
      }
      if (j >= sbb_left && j < sbb_right && i >= sbb_top) {
        contextLabel = contextLabel << 1 & reuseMask;
        for (k = 0; k < changingEntriesLength; k++) {
          i0 = i + changingTemplateY[k];
          j0 = j + changingTemplateX[k];
          bit = bitmap[i0][j0];
          if (bit) {
            bit = changingTemplateBit[k];
            contextLabel |= bit;
          }
        }
      } else {
        contextLabel = 0;
        shift = templateLength - 1;
        for (k = 0; k < templateLength; k++, shift--) {
          j0 = j + templateX[k];
          if (j0 >= 0 && j0 < width) {
            i0 = i + templateY[k];
            if (i0 >= 0) {
              bit = bitmap[i0][j0];
              if (bit) {
                contextLabel |= bit << shift;
              }
            }
          }
        }
      }
      const pixel = decoder.readBit(contexts, contextLabel);
      row[j] = pixel;
    }
  }
  return bitmap;
}
function decodeRefinement(width, height, templateIndex, referenceBitmap, offsetX, offsetY, prediction, at, decodingContext) {
  let codingTemplate = RefinementTemplates[templateIndex].coding;
  if (templateIndex === 0) {
    codingTemplate = codingTemplate.concat([at[0]]);
  }
  const codingTemplateLength = codingTemplate.length;
  const codingTemplateX = new Int32Array(codingTemplateLength);
  const codingTemplateY = new Int32Array(codingTemplateLength);
  let k;
  for (k = 0; k < codingTemplateLength; k++) {
    codingTemplateX[k] = codingTemplate[k].x;
    codingTemplateY[k] = codingTemplate[k].y;
  }
  let referenceTemplate = RefinementTemplates[templateIndex].reference;
  if (templateIndex === 0) {
    referenceTemplate = referenceTemplate.concat([at[1]]);
  }
  const referenceTemplateLength = referenceTemplate.length;
  const referenceTemplateX = new Int32Array(referenceTemplateLength);
  const referenceTemplateY = new Int32Array(referenceTemplateLength);
  for (k = 0; k < referenceTemplateLength; k++) {
    referenceTemplateX[k] = referenceTemplate[k].x;
    referenceTemplateY[k] = referenceTemplate[k].y;
  }
  const referenceWidth = referenceBitmap[0].length;
  const referenceHeight = referenceBitmap.length;
  const pseudoPixelContext = RefinementReusedContexts[templateIndex];
  const bitmap = [];
  const decoder = decodingContext.decoder;
  const contexts = decodingContext.contextCache.getContexts("GR");
  let ltp = 0;
  for (let i = 0; i < height; i++) {
    if (prediction) {
      const sltp = decoder.readBit(contexts, pseudoPixelContext);
      ltp ^= sltp;
      if (ltp) {
        throw new Jbig2Error("prediction is not supported");
      }
    }
    const row = new Uint8Array(width);
    bitmap.push(row);
    for (let j = 0; j < width; j++) {
      let i0, j0;
      let contextLabel = 0;
      for (k = 0; k < codingTemplateLength; k++) {
        i0 = i + codingTemplateY[k];
        j0 = j + codingTemplateX[k];
        if (i0 < 0 || j0 < 0 || j0 >= width) {
          contextLabel <<= 1;
        } else {
          contextLabel = contextLabel << 1 | bitmap[i0][j0];
        }
      }
      for (k = 0; k < referenceTemplateLength; k++) {
        i0 = i + referenceTemplateY[k] - offsetY;
        j0 = j + referenceTemplateX[k] - offsetX;
        if (i0 < 0 || i0 >= referenceHeight || j0 < 0 || j0 >= referenceWidth) {
          contextLabel <<= 1;
        } else {
          contextLabel = contextLabel << 1 | referenceBitmap[i0][j0];
        }
      }
      const pixel = decoder.readBit(contexts, contextLabel);
      row[j] = pixel;
    }
  }
  return bitmap;
}
function decodeSymbolDictionary(huffman, refinement, symbols, numberOfNewSymbols, numberOfExportedSymbols, huffmanTables, templateIndex, at, refinementTemplateIndex, refinementAt, decodingContext, huffmanInput) {
  if (huffman && refinement) {
    throw new Jbig2Error("symbol refinement with Huffman is not supported");
  }
  const newSymbols = [];
  let currentHeight = 0;
  let symbolCodeLength = log2(symbols.length + numberOfNewSymbols);
  const decoder = decodingContext.decoder;
  const contextCache = decodingContext.contextCache;
  let tableB1, symbolWidths;
  if (huffman) {
    tableB1 = getStandardTable(1);
    symbolWidths = [];
    symbolCodeLength = Math.max(symbolCodeLength, 1);
  }
  while (newSymbols.length < numberOfNewSymbols) {
    const deltaHeight = huffman ? huffmanTables.tableDeltaHeight.decode(huffmanInput) : decodeInteger(contextCache, "IADH", decoder);
    currentHeight += deltaHeight;
    let currentWidth = 0,
      totalWidth = 0;
    const firstSymbol = huffman ? symbolWidths.length : 0;
    while (true) {
      const deltaWidth = huffman ? huffmanTables.tableDeltaWidth.decode(huffmanInput) : decodeInteger(contextCache, "IADW", decoder);
      if (deltaWidth === null) {
        break;
      }
      currentWidth += deltaWidth;
      totalWidth += currentWidth;
      let bitmap;
      if (refinement) {
        const numberOfInstances = decodeInteger(contextCache, "IAAI", decoder);
        if (numberOfInstances > 1) {
          bitmap = decodeTextRegion(huffman, refinement, currentWidth, currentHeight, 0, numberOfInstances, 1, symbols.concat(newSymbols), symbolCodeLength, 0, 0, 1, 0, huffmanTables, refinementTemplateIndex, refinementAt, decodingContext, 0, huffmanInput);
        } else {
          const symbolId = decodeIAID(contextCache, decoder, symbolCodeLength);
          const rdx = decodeInteger(contextCache, "IARDX", decoder);
          const rdy = decodeInteger(contextCache, "IARDY", decoder);
          const symbol = symbolId < symbols.length ? symbols[symbolId] : newSymbols[symbolId - symbols.length];
          bitmap = decodeRefinement(currentWidth, currentHeight, refinementTemplateIndex, symbol, rdx, rdy, false, refinementAt, decodingContext);
        }
        newSymbols.push(bitmap);
      } else if (huffman) {
        symbolWidths.push(currentWidth);
      } else {
        bitmap = decodeBitmap(false, currentWidth, currentHeight, templateIndex, false, null, at, decodingContext);
        newSymbols.push(bitmap);
      }
    }
    if (huffman && !refinement) {
      const bitmapSize = huffmanTables.tableBitmapSize.decode(huffmanInput);
      huffmanInput.byteAlign();
      let collectiveBitmap;
      if (bitmapSize === 0) {
        collectiveBitmap = readUncompressedBitmap(huffmanInput, totalWidth, currentHeight);
      } else {
        const originalEnd = huffmanInput.end;
        const bitmapEnd = huffmanInput.position + bitmapSize;
        huffmanInput.end = bitmapEnd;
        collectiveBitmap = decodeMMRBitmap(huffmanInput, totalWidth, currentHeight, false);
        huffmanInput.end = originalEnd;
        huffmanInput.position = bitmapEnd;
      }
      const numberOfSymbolsDecoded = symbolWidths.length;
      if (firstSymbol === numberOfSymbolsDecoded - 1) {
        newSymbols.push(collectiveBitmap);
      } else {
        let i,
          y,
          xMin = 0,
          xMax,
          bitmapWidth,
          symbolBitmap;
        for (i = firstSymbol; i < numberOfSymbolsDecoded; i++) {
          bitmapWidth = symbolWidths[i];
          xMax = xMin + bitmapWidth;
          symbolBitmap = [];
          for (y = 0; y < currentHeight; y++) {
            symbolBitmap.push(collectiveBitmap[y].subarray(xMin, xMax));
          }
          newSymbols.push(symbolBitmap);
          xMin = xMax;
        }
      }
    }
  }
  const exportedSymbols = [],
    flags = [];
  let currentFlag = false,
    i,
    ii;
  const totalSymbolsLength = symbols.length + numberOfNewSymbols;
  while (flags.length < totalSymbolsLength) {
    let runLength = huffman ? tableB1.decode(huffmanInput) : decodeInteger(contextCache, "IAEX", decoder);
    while (runLength--) {
      flags.push(currentFlag);
    }
    currentFlag = !currentFlag;
  }
  for (i = 0, ii = symbols.length; i < ii; i++) {
    if (flags[i]) {
      exportedSymbols.push(symbols[i]);
    }
  }
  for (let j = 0; j < numberOfNewSymbols; i++, j++) {
    if (flags[i]) {
      exportedSymbols.push(newSymbols[j]);
    }
  }
  return exportedSymbols;
}
function decodeTextRegion(huffman, refinement, width, height, defaultPixelValue, numberOfSymbolInstances, stripSize, inputSymbols, symbolCodeLength, transposed, dsOffset, referenceCorner, combinationOperator, huffmanTables, refinementTemplateIndex, refinementAt, decodingContext, logStripSize, huffmanInput) {
  if (huffman && refinement) {
    throw new Jbig2Error("refinement with Huffman is not supported");
  }
  const bitmap = [];
  let i, row;
  for (i = 0; i < height; i++) {
    row = new Uint8Array(width);
    if (defaultPixelValue) {
      for (let j = 0; j < width; j++) {
        row[j] = defaultPixelValue;
      }
    }
    bitmap.push(row);
  }
  const decoder = decodingContext.decoder;
  const contextCache = decodingContext.contextCache;
  let stripT = huffman ? -huffmanTables.tableDeltaT.decode(huffmanInput) : -decodeInteger(contextCache, "IADT", decoder);
  let firstS = 0;
  i = 0;
  while (i < numberOfSymbolInstances) {
    const deltaT = huffman ? huffmanTables.tableDeltaT.decode(huffmanInput) : decodeInteger(contextCache, "IADT", decoder);
    stripT += deltaT;
    const deltaFirstS = huffman ? huffmanTables.tableFirstS.decode(huffmanInput) : decodeInteger(contextCache, "IAFS", decoder);
    firstS += deltaFirstS;
    let currentS = firstS;
    do {
      let currentT = 0;
      if (stripSize > 1) {
        currentT = huffman ? huffmanInput.readBits(logStripSize) : decodeInteger(contextCache, "IAIT", decoder);
      }
      const t = stripSize * stripT + currentT;
      const symbolId = huffman ? huffmanTables.symbolIDTable.decode(huffmanInput) : decodeIAID(contextCache, decoder, symbolCodeLength);
      const applyRefinement = refinement && (huffman ? huffmanInput.readBit() : decodeInteger(contextCache, "IARI", decoder));
      let symbolBitmap = inputSymbols[symbolId];
      let symbolWidth = symbolBitmap[0].length;
      let symbolHeight = symbolBitmap.length;
      if (applyRefinement) {
        const rdw = decodeInteger(contextCache, "IARDW", decoder);
        const rdh = decodeInteger(contextCache, "IARDH", decoder);
        const rdx = decodeInteger(contextCache, "IARDX", decoder);
        const rdy = decodeInteger(contextCache, "IARDY", decoder);
        symbolWidth += rdw;
        symbolHeight += rdh;
        symbolBitmap = decodeRefinement(symbolWidth, symbolHeight, refinementTemplateIndex, symbolBitmap, (rdw >> 1) + rdx, (rdh >> 1) + rdy, false, refinementAt, decodingContext);
      }
      let increment = 0;
      if (!transposed) {
        if (referenceCorner > 1) {
          currentS += symbolWidth - 1;
        } else {
          increment = symbolWidth - 1;
        }
      } else if (!(referenceCorner & 1)) {
        currentS += symbolHeight - 1;
      } else {
        increment = symbolHeight - 1;
      }
      const offsetT = t - (referenceCorner & 1 ? 0 : symbolHeight - 1);
      const offsetS = currentS - (referenceCorner & 2 ? symbolWidth - 1 : 0);
      let s2, t2, symbolRow;
      if (transposed) {
        for (s2 = 0; s2 < symbolHeight; s2++) {
          row = bitmap[offsetS + s2];
          if (!row) {
            continue;
          }
          symbolRow = symbolBitmap[s2];
          const maxWidth = Math.min(width - offsetT, symbolWidth);
          switch (combinationOperator) {
            case 0:
              for (t2 = 0; t2 < maxWidth; t2++) {
                row[offsetT + t2] |= symbolRow[t2];
              }
              break;
            case 2:
              for (t2 = 0; t2 < maxWidth; t2++) {
                row[offsetT + t2] ^= symbolRow[t2];
              }
              break;
            default:
              throw new Jbig2Error(`operator ${combinationOperator} is not supported`);
          }
        }
      } else {
        for (t2 = 0; t2 < symbolHeight; t2++) {
          row = bitmap[offsetT + t2];
          if (!row) {
            continue;
          }
          symbolRow = symbolBitmap[t2];
          switch (combinationOperator) {
            case 0:
              for (s2 = 0; s2 < symbolWidth; s2++) {
                row[offsetS + s2] |= symbolRow[s2];
              }
              break;
            case 2:
              for (s2 = 0; s2 < symbolWidth; s2++) {
                row[offsetS + s2] ^= symbolRow[s2];
              }
              break;
            default:
              throw new Jbig2Error(`operator ${combinationOperator} is not supported`);
          }
        }
      }
      i++;
      const deltaS = huffman ? huffmanTables.tableDeltaS.decode(huffmanInput) : decodeInteger(contextCache, "IADS", decoder);
      if (deltaS === null) {
        break;
      }
      currentS += increment + deltaS + dsOffset;
    } while (true);
  }
  return bitmap;
}
function decodePatternDictionary(mmr, patternWidth, patternHeight, maxPatternIndex, template, decodingContext) {
  const at = [];
  if (!mmr) {
    at.push({
      x: -patternWidth,
      y: 0
    });
    if (template === 0) {
      at.push({
        x: -3,
        y: -1
      }, {
        x: 2,
        y: -2
      }, {
        x: -2,
        y: -2
      });
    }
  }
  const collectiveWidth = (maxPatternIndex + 1) * patternWidth;
  const collectiveBitmap = decodeBitmap(mmr, collectiveWidth, patternHeight, template, false, null, at, decodingContext);
  const patterns = [];
  for (let i = 0; i <= maxPatternIndex; i++) {
    const patternBitmap = [];
    const xMin = patternWidth * i;
    const xMax = xMin + patternWidth;
    for (let y = 0; y < patternHeight; y++) {
      patternBitmap.push(collectiveBitmap[y].subarray(xMin, xMax));
    }
    patterns.push(patternBitmap);
  }
  return patterns;
}
function decodeHalftoneRegion(mmr, patterns, template, regionWidth, regionHeight, defaultPixelValue, enableSkip, combinationOperator, gridWidth, gridHeight, gridOffsetX, gridOffsetY, gridVectorX, gridVectorY, decodingContext) {
  const skip = null;
  if (enableSkip) {
    throw new Jbig2Error("skip is not supported");
  }
  if (combinationOperator !== 0) {
    throw new Jbig2Error(`operator "${combinationOperator}" is not supported in halftone region`);
  }
  const regionBitmap = [];
  let i, j, row;
  for (i = 0; i < regionHeight; i++) {
    row = new Uint8Array(regionWidth);
    if (defaultPixelValue) {
      for (j = 0; j < regionWidth; j++) {
        row[j] = defaultPixelValue;
      }
    }
    regionBitmap.push(row);
  }
  const numberOfPatterns = patterns.length;
  const pattern0 = patterns[0];
  const patternWidth = pattern0[0].length,
    patternHeight = pattern0.length;
  const bitsPerValue = log2(numberOfPatterns);
  const at = [];
  if (!mmr) {
    at.push({
      x: template <= 1 ? 3 : 2,
      y: -1
    });
    if (template === 0) {
      at.push({
        x: -3,
        y: -1
      }, {
        x: 2,
        y: -2
      }, {
        x: -2,
        y: -2
      });
    }
  }
  const grayScaleBitPlanes = [];
  let mmrInput, bitmap;
  if (mmr) {
    mmrInput = new Reader(decodingContext.data, decodingContext.start, decodingContext.end);
  }
  for (i = bitsPerValue - 1; i >= 0; i--) {
    if (mmr) {
      bitmap = decodeMMRBitmap(mmrInput, gridWidth, gridHeight, true);
    } else {
      bitmap = decodeBitmap(false, gridWidth, gridHeight, template, false, skip, at, decodingContext);
    }
    grayScaleBitPlanes[i] = bitmap;
  }
  let mg, ng, bit, patternIndex, patternBitmap, x, y, patternRow, regionRow;
  for (mg = 0; mg < gridHeight; mg++) {
    for (ng = 0; ng < gridWidth; ng++) {
      bit = 0;
      patternIndex = 0;
      for (j = bitsPerValue - 1; j >= 0; j--) {
        bit ^= grayScaleBitPlanes[j][mg][ng];
        patternIndex |= bit << j;
      }
      patternBitmap = patterns[patternIndex];
      x = gridOffsetX + mg * gridVectorY + ng * gridVectorX >> 8;
      y = gridOffsetY + mg * gridVectorX - ng * gridVectorY >> 8;
      if (x >= 0 && x + patternWidth <= regionWidth && y >= 0 && y + patternHeight <= regionHeight) {
        for (i = 0; i < patternHeight; i++) {
          regionRow = regionBitmap[y + i];
          patternRow = patternBitmap[i];
          for (j = 0; j < patternWidth; j++) {
            regionRow[x + j] |= patternRow[j];
          }
        }
      } else {
        let regionX, regionY;
        for (i = 0; i < patternHeight; i++) {
          regionY = y + i;
          if (regionY < 0 || regionY >= regionHeight) {
            continue;
          }
          regionRow = regionBitmap[regionY];
          patternRow = patternBitmap[i];
          for (j = 0; j < patternWidth; j++) {
            regionX = x + j;
            if (regionX >= 0 && regionX < regionWidth) {
              regionRow[regionX] |= patternRow[j];
            }
          }
        }
      }
    }
  }
  return regionBitmap;
}
function readSegmentHeader(data, start) {
  const segmentHeader = {};
  segmentHeader.number = readUint32(data, start);
  const flags = data[start + 4];
  const segmentType = flags & 0x3f;
  if (!SegmentTypes[segmentType]) {
    throw new Jbig2Error("invalid segment type: " + segmentType);
  }
  segmentHeader.type = segmentType;
  segmentHeader.typeName = SegmentTypes[segmentType];
  segmentHeader.deferredNonRetain = !!(flags & 0x80);
  const pageAssociationFieldSize = !!(flags & 0x40);
  const referredFlags = data[start + 5];
  let referredToCount = referredFlags >> 5 & 7;
  const retainBits = [referredFlags & 31];
  let position = start + 6;
  if (referredFlags === 7) {
    referredToCount = readUint32(data, position - 1) & 0x1fffffff;
    position += 3;
    let bytes = referredToCount + 7 >> 3;
    retainBits[0] = data[position++];
    while (--bytes > 0) {
      retainBits.push(data[position++]);
    }
  } else if (referredFlags === 5 || referredFlags === 6) {
    throw new Jbig2Error("invalid referred-to flags");
  }
  segmentHeader.retainBits = retainBits;
  let referredToSegmentNumberSize = 4;
  if (segmentHeader.number <= 256) {
    referredToSegmentNumberSize = 1;
  } else if (segmentHeader.number <= 65536) {
    referredToSegmentNumberSize = 2;
  }
  const referredTo = [];
  let i, ii;
  for (i = 0; i < referredToCount; i++) {
    let number;
    if (referredToSegmentNumberSize === 1) {
      number = data[position];
    } else if (referredToSegmentNumberSize === 2) {
      number = readUint16(data, position);
    } else {
      number = readUint32(data, position);
    }
    referredTo.push(number);
    position += referredToSegmentNumberSize;
  }
  segmentHeader.referredTo = referredTo;
  if (!pageAssociationFieldSize) {
    segmentHeader.pageAssociation = data[position++];
  } else {
    segmentHeader.pageAssociation = readUint32(data, position);
    position += 4;
  }
  segmentHeader.length = readUint32(data, position);
  position += 4;
  if (segmentHeader.length === 0xffffffff) {
    if (segmentType === 38) {
      const genericRegionInfo = readRegionSegmentInformation(data, position);
      const genericRegionSegmentFlags = data[position + RegionSegmentInformationFieldLength];
      const genericRegionMmr = !!(genericRegionSegmentFlags & 1);
      const searchPatternLength = 6;
      const searchPattern = new Uint8Array(searchPatternLength);
      if (!genericRegionMmr) {
        searchPattern[0] = 0xff;
        searchPattern[1] = 0xac;
      }
      searchPattern[2] = genericRegionInfo.height >>> 24 & 0xff;
      searchPattern[3] = genericRegionInfo.height >> 16 & 0xff;
      searchPattern[4] = genericRegionInfo.height >> 8 & 0xff;
      searchPattern[5] = genericRegionInfo.height & 0xff;
      for (i = position, ii = data.length; i < ii; i++) {
        let j = 0;
        while (j < searchPatternLength && searchPattern[j] === data[i + j]) {
          j++;
        }
        if (j === searchPatternLength) {
          segmentHeader.length = i + searchPatternLength;
          break;
        }
      }
      if (segmentHeader.length === 0xffffffff) {
        throw new Jbig2Error("segment end was not found");
      }
    } else {
      throw new Jbig2Error("invalid unknown segment length");
    }
  }
  segmentHeader.headerEnd = position;
  return segmentHeader;
}
function readSegments(header, data, start, end) {
  const segments = [];
  let position = start;
  while (position < end) {
    const segmentHeader = readSegmentHeader(data, position);
    position = segmentHeader.headerEnd;
    const segment = {
      header: segmentHeader,
      data
    };
    if (!header.randomAccess) {
      segment.start = position;
      position += segmentHeader.length;
      segment.end = position;
    }
    segments.push(segment);
    if (segmentHeader.type === 51) {
      break;
    }
  }
  if (header.randomAccess) {
    for (let i = 0, ii = segments.length; i < ii; i++) {
      segments[i].start = position;
      position += segments[i].header.length;
      segments[i].end = position;
    }
  }
  return segments;
}
function readRegionSegmentInformation(data, start) {
  return {
    width: readUint32(data, start),
    height: readUint32(data, start + 4),
    x: readUint32(data, start + 8),
    y: readUint32(data, start + 12),
    combinationOperator: data[start + 16] & 7
  };
}
const RegionSegmentInformationFieldLength = 17;
function processSegment(segment, visitor) {
  const header = segment.header;
  const data = segment.data,
    end = segment.end;
  let position = segment.start;
  let args, at, i, atLength;
  switch (header.type) {
    case 0:
      const dictionary = {};
      const dictionaryFlags = readUint16(data, position);
      dictionary.huffman = !!(dictionaryFlags & 1);
      dictionary.refinement = !!(dictionaryFlags & 2);
      dictionary.huffmanDHSelector = dictionaryFlags >> 2 & 3;
      dictionary.huffmanDWSelector = dictionaryFlags >> 4 & 3;
      dictionary.bitmapSizeSelector = dictionaryFlags >> 6 & 1;
      dictionary.aggregationInstancesSelector = dictionaryFlags >> 7 & 1;
      dictionary.bitmapCodingContextUsed = !!(dictionaryFlags & 256);
      dictionary.bitmapCodingContextRetained = !!(dictionaryFlags & 512);
      dictionary.template = dictionaryFlags >> 10 & 3;
      dictionary.refinementTemplate = dictionaryFlags >> 12 & 1;
      position += 2;
      if (!dictionary.huffman) {
        atLength = dictionary.template === 0 ? 4 : 1;
        at = [];
        for (i = 0; i < atLength; i++) {
          at.push({
            x: readInt8(data, position),
            y: readInt8(data, position + 1)
          });
          position += 2;
        }
        dictionary.at = at;
      }
      if (dictionary.refinement && !dictionary.refinementTemplate) {
        at = [];
        for (i = 0; i < 2; i++) {
          at.push({
            x: readInt8(data, position),
            y: readInt8(data, position + 1)
          });
          position += 2;
        }
        dictionary.refinementAt = at;
      }
      dictionary.numberOfExportedSymbols = readUint32(data, position);
      position += 4;
      dictionary.numberOfNewSymbols = readUint32(data, position);
      position += 4;
      args = [dictionary, header.number, header.referredTo, data, position, end];
      break;
    case 6:
    case 7:
      const textRegion = {};
      textRegion.info = readRegionSegmentInformation(data, position);
      position += RegionSegmentInformationFieldLength;
      const textRegionSegmentFlags = readUint16(data, position);
      position += 2;
      textRegion.huffman = !!(textRegionSegmentFlags & 1);
      textRegion.refinement = !!(textRegionSegmentFlags & 2);
      textRegion.logStripSize = textRegionSegmentFlags >> 2 & 3;
      textRegion.stripSize = 1 << textRegion.logStripSize;
      textRegion.referenceCorner = textRegionSegmentFlags >> 4 & 3;
      textRegion.transposed = !!(textRegionSegmentFlags & 64);
      textRegion.combinationOperator = textRegionSegmentFlags >> 7 & 3;
      textRegion.defaultPixelValue = textRegionSegmentFlags >> 9 & 1;
      textRegion.dsOffset = textRegionSegmentFlags << 17 >> 27;
      textRegion.refinementTemplate = textRegionSegmentFlags >> 15 & 1;
      if (textRegion.huffman) {
        const textRegionHuffmanFlags = readUint16(data, position);
        position += 2;
        textRegion.huffmanFS = textRegionHuffmanFlags & 3;
        textRegion.huffmanDS = textRegionHuffmanFlags >> 2 & 3;
        textRegion.huffmanDT = textRegionHuffmanFlags >> 4 & 3;
        textRegion.huffmanRefinementDW = textRegionHuffmanFlags >> 6 & 3;
        textRegion.huffmanRefinementDH = textRegionHuffmanFlags >> 8 & 3;
        textRegion.huffmanRefinementDX = textRegionHuffmanFlags >> 10 & 3;
        textRegion.huffmanRefinementDY = textRegionHuffmanFlags >> 12 & 3;
        textRegion.huffmanRefinementSizeSelector = !!(textRegionHuffmanFlags & 0x4000);
      }
      if (textRegion.refinement && !textRegion.refinementTemplate) {
        at = [];
        for (i = 0; i < 2; i++) {
          at.push({
            x: readInt8(data, position),
            y: readInt8(data, position + 1)
          });
          position += 2;
        }
        textRegion.refinementAt = at;
      }
      textRegion.numberOfSymbolInstances = readUint32(data, position);
      position += 4;
      args = [textRegion, header.referredTo, data, position, end];
      break;
    case 16:
      const patternDictionary = {};
      const patternDictionaryFlags = data[position++];
      patternDictionary.mmr = !!(patternDictionaryFlags & 1);
      patternDictionary.template = patternDictionaryFlags >> 1 & 3;
      patternDictionary.patternWidth = data[position++];
      patternDictionary.patternHeight = data[position++];
      patternDictionary.maxPatternIndex = readUint32(data, position);
      position += 4;
      args = [patternDictionary, header.number, data, position, end];
      break;
    case 22:
    case 23:
      const halftoneRegion = {};
      halftoneRegion.info = readRegionSegmentInformation(data, position);
      position += RegionSegmentInformationFieldLength;
      const halftoneRegionFlags = data[position++];
      halftoneRegion.mmr = !!(halftoneRegionFlags & 1);
      halftoneRegion.template = halftoneRegionFlags >> 1 & 3;
      halftoneRegion.enableSkip = !!(halftoneRegionFlags & 8);
      halftoneRegion.combinationOperator = halftoneRegionFlags >> 4 & 7;
      halftoneRegion.defaultPixelValue = halftoneRegionFlags >> 7 & 1;
      halftoneRegion.gridWidth = readUint32(data, position);
      position += 4;
      halftoneRegion.gridHeight = readUint32(data, position);
      position += 4;
      halftoneRegion.gridOffsetX = readUint32(data, position) & 0xffffffff;
      position += 4;
      halftoneRegion.gridOffsetY = readUint32(data, position) & 0xffffffff;
      position += 4;
      halftoneRegion.gridVectorX = readUint16(data, position);
      position += 2;
      halftoneRegion.gridVectorY = readUint16(data, position);
      position += 2;
      args = [halftoneRegion, header.referredTo, data, position, end];
      break;
    case 38:
    case 39:
      const genericRegion = {};
      genericRegion.info = readRegionSegmentInformation(data, position);
      position += RegionSegmentInformationFieldLength;
      const genericRegionSegmentFlags = data[position++];
      genericRegion.mmr = !!(genericRegionSegmentFlags & 1);
      genericRegion.template = genericRegionSegmentFlags >> 1 & 3;
      genericRegion.prediction = !!(genericRegionSegmentFlags & 8);
      if (!genericRegion.mmr) {
        atLength = genericRegion.template === 0 ? 4 : 1;
        at = [];
        for (i = 0; i < atLength; i++) {
          at.push({
            x: readInt8(data, position),
            y: readInt8(data, position + 1)
          });
          position += 2;
        }
        genericRegion.at = at;
      }
      args = [genericRegion, data, position, end];
      break;
    case 48:
      const pageInfo = {
        width: readUint32(data, position),
        height: readUint32(data, position + 4),
        resolutionX: readUint32(data, position + 8),
        resolutionY: readUint32(data, position + 12)
      };
      if (pageInfo.height === 0xffffffff) {
        delete pageInfo.height;
      }
      const pageSegmentFlags = data[position + 16];
      readUint16(data, position + 17);
      pageInfo.lossless = !!(pageSegmentFlags & 1);
      pageInfo.refinement = !!(pageSegmentFlags & 2);
      pageInfo.defaultPixelValue = pageSegmentFlags >> 2 & 1;
      pageInfo.combinationOperator = pageSegmentFlags >> 3 & 3;
      pageInfo.requiresBuffer = !!(pageSegmentFlags & 32);
      pageInfo.combinationOperatorOverride = !!(pageSegmentFlags & 64);
      args = [pageInfo];
      break;
    case 49:
      break;
    case 50:
      break;
    case 51:
      break;
    case 53:
      args = [header.number, data, position, end];
      break;
    case 62:
      break;
    default:
      throw new Jbig2Error(`segment type ${header.typeName}(${header.type}) is not implemented`);
  }
  const callbackName = "on" + header.typeName;
  if (callbackName in visitor) {
    visitor[callbackName].apply(visitor, args);
  }
}
function processSegments(segments, visitor) {
  for (let i = 0, ii = segments.length; i < ii; i++) {
    processSegment(segments[i], visitor);
  }
}
function parseJbig2Chunks(chunks) {
  const visitor = new SimpleSegmentVisitor();
  for (let i = 0, ii = chunks.length; i < ii; i++) {
    const chunk = chunks[i];
    const segments = readSegments({}, chunk.data, chunk.start, chunk.end);
    processSegments(segments, visitor);
  }
  return visitor.buffer;
}
function parseJbig2(data) {
  throw new Error("Not implemented: parseJbig2");
}
class SimpleSegmentVisitor {
  onPageInformation(info) {
    this.currentPageInfo = info;
    const rowSize = info.width + 7 >> 3;
    const buffer = new Uint8ClampedArray(rowSize * info.height);
    if (info.defaultPixelValue) {
      buffer.fill(0xff);
    }
    this.buffer = buffer;
  }
  drawBitmap(regionInfo, bitmap) {
    const pageInfo = this.currentPageInfo;
    const width = regionInfo.width,
      height = regionInfo.height;
    const rowSize = pageInfo.width + 7 >> 3;
    const combinationOperator = pageInfo.combinationOperatorOverride ? regionInfo.combinationOperator : pageInfo.combinationOperator;
    const buffer = this.buffer;
    const mask0 = 128 >> (regionInfo.x & 7);
    let offset0 = regionInfo.y * rowSize + (regionInfo.x >> 3);
    let i, j, mask, offset;
    switch (combinationOperator) {
      case 0:
        for (i = 0; i < height; i++) {
          mask = mask0;
          offset = offset0;
          for (j = 0; j < width; j++) {
            if (bitmap[i][j]) {
              buffer[offset] |= mask;
            }
            mask >>= 1;
            if (!mask) {
              mask = 128;
              offset++;
            }
          }
          offset0 += rowSize;
        }
        break;
      case 2:
        for (i = 0; i < height; i++) {
          mask = mask0;
          offset = offset0;
          for (j = 0; j < width; j++) {
            if (bitmap[i][j]) {
              buffer[offset] ^= mask;
            }
            mask >>= 1;
            if (!mask) {
              mask = 128;
              offset++;
            }
          }
          offset0 += rowSize;
        }
        break;
      default:
        throw new Jbig2Error(`operator ${combinationOperator} is not supported`);
    }
  }
  onImmediateGenericRegion(region, data, start, end) {
    const regionInfo = region.info;
    const decodingContext = new DecodingContext(data, start, end);
    const bitmap = decodeBitmap(region.mmr, regionInfo.width, regionInfo.height, region.template, region.prediction, null, region.at, decodingContext);
    this.drawBitmap(regionInfo, bitmap);
  }
  onImmediateLosslessGenericRegion() {
    this.onImmediateGenericRegion(...arguments);
  }
  onSymbolDictionary(dictionary, currentSegment, referredSegments, data, start, end) {
    let huffmanTables, huffmanInput;
    if (dictionary.huffman) {
      huffmanTables = getSymbolDictionaryHuffmanTables(dictionary, referredSegments, this.customTables);
      huffmanInput = new Reader(data, start, end);
    }
    let symbols = this.symbols;
    if (!symbols) {
      this.symbols = symbols = {};
    }
    const inputSymbols = [];
    for (const referredSegment of referredSegments) {
      const referredSymbols = symbols[referredSegment];
      if (referredSymbols) {
        inputSymbols.push(...referredSymbols);
      }
    }
    const decodingContext = new DecodingContext(data, start, end);
    symbols[currentSegment] = decodeSymbolDictionary(dictionary.huffman, dictionary.refinement, inputSymbols, dictionary.numberOfNewSymbols, dictionary.numberOfExportedSymbols, huffmanTables, dictionary.template, dictionary.at, dictionary.refinementTemplate, dictionary.refinementAt, decodingContext, huffmanInput);
  }
  onImmediateTextRegion(region, referredSegments, data, start, end) {
    const regionInfo = region.info;
    let huffmanTables, huffmanInput;
    const symbols = this.symbols;
    const inputSymbols = [];
    for (const referredSegment of referredSegments) {
      const referredSymbols = symbols[referredSegment];
      if (referredSymbols) {
        inputSymbols.push(...referredSymbols);
      }
    }
    const symbolCodeLength = log2(inputSymbols.length);
    if (region.huffman) {
      huffmanInput = new Reader(data, start, end);
      huffmanTables = getTextRegionHuffmanTables(region, referredSegments, this.customTables, inputSymbols.length, huffmanInput);
    }
    const decodingContext = new DecodingContext(data, start, end);
    const bitmap = decodeTextRegion(region.huffman, region.refinement, regionInfo.width, regionInfo.height, region.defaultPixelValue, region.numberOfSymbolInstances, region.stripSize, inputSymbols, symbolCodeLength, region.transposed, region.dsOffset, region.referenceCorner, region.combinationOperator, huffmanTables, region.refinementTemplate, region.refinementAt, decodingContext, region.logStripSize, huffmanInput);
    this.drawBitmap(regionInfo, bitmap);
  }
  onImmediateLosslessTextRegion() {
    this.onImmediateTextRegion(...arguments);
  }
  onPatternDictionary(dictionary, currentSegment, data, start, end) {
    let patterns = this.patterns;
    if (!patterns) {
      this.patterns = patterns = {};
    }
    const decodingContext = new DecodingContext(data, start, end);
    patterns[currentSegment] = decodePatternDictionary(dictionary.mmr, dictionary.patternWidth, dictionary.patternHeight, dictionary.maxPatternIndex, dictionary.template, decodingContext);
  }
  onImmediateHalftoneRegion(region, referredSegments, data, start, end) {
    const patterns = this.patterns[referredSegments[0]];
    const regionInfo = region.info;
    const decodingContext = new DecodingContext(data, start, end);
    const bitmap = decodeHalftoneRegion(region.mmr, patterns, region.template, regionInfo.width, regionInfo.height, region.defaultPixelValue, region.enableSkip, region.combinationOperator, region.gridWidth, region.gridHeight, region.gridOffsetX, region.gridOffsetY, region.gridVectorX, region.gridVectorY, decodingContext);
    this.drawBitmap(regionInfo, bitmap);
  }
  onImmediateLosslessHalftoneRegion() {
    this.onImmediateHalftoneRegion(...arguments);
  }
  onTables(currentSegment, data, start, end) {
    let customTables = this.customTables;
    if (!customTables) {
      this.customTables = customTables = {};
    }
    customTables[currentSegment] = decodeTablesSegment(data, start, end);
  }
}
class HuffmanLine {
  constructor(lineData) {
    if (lineData.length === 2) {
      this.isOOB = true;
      this.rangeLow = 0;
      this.prefixLength = lineData[0];
      this.rangeLength = 0;
      this.prefixCode = lineData[1];
      this.isLowerRange = false;
    } else {
      this.isOOB = false;
      this.rangeLow = lineData[0];
      this.prefixLength = lineData[1];
      this.rangeLength = lineData[2];
      this.prefixCode = lineData[3];
      this.isLowerRange = lineData[4] === "lower";
    }
  }
}
class HuffmanTreeNode {
  constructor(line) {
    this.children = [];
    if (line) {
      this.isLeaf = true;
      this.rangeLength = line.rangeLength;
      this.rangeLow = line.rangeLow;
      this.isLowerRange = line.isLowerRange;
      this.isOOB = line.isOOB;
    } else {
      this.isLeaf = false;
    }
  }
  buildTree(line, shift) {
    const bit = line.prefixCode >> shift & 1;
    if (shift <= 0) {
      this.children[bit] = new HuffmanTreeNode(line);
    } else {
      let node = this.children[bit];
      if (!node) {
        this.children[bit] = node = new HuffmanTreeNode(null);
      }
      node.buildTree(line, shift - 1);
    }
  }
  decodeNode(reader) {
    if (this.isLeaf) {
      if (this.isOOB) {
        return null;
      }
      const htOffset = reader.readBits(this.rangeLength);
      return this.rangeLow + (this.isLowerRange ? -htOffset : htOffset);
    }
    const node = this.children[reader.readBit()];
    if (!node) {
      throw new Jbig2Error("invalid Huffman data");
    }
    return node.decodeNode(reader);
  }
}
class HuffmanTable {
  constructor(lines, prefixCodesDone) {
    if (!prefixCodesDone) {
      this.assignPrefixCodes(lines);
    }
    this.rootNode = new HuffmanTreeNode(null);
    for (let i = 0, ii = lines.length; i < ii; i++) {
      const line = lines[i];
      if (line.prefixLength > 0) {
        this.rootNode.buildTree(line, line.prefixLength - 1);
      }
    }
  }
  decode(reader) {
    return this.rootNode.decodeNode(reader);
  }
  assignPrefixCodes(lines) {
    const linesLength = lines.length;
    let prefixLengthMax = 0;
    for (let i = 0; i < linesLength; i++) {
      prefixLengthMax = Math.max(prefixLengthMax, lines[i].prefixLength);
    }
    const histogram = new Uint32Array(prefixLengthMax + 1);
    for (let i = 0; i < linesLength; i++) {
      histogram[lines[i].prefixLength]++;
    }
    let currentLength = 1,
      firstCode = 0,
      currentCode,
      currentTemp,
      line;
    histogram[0] = 0;
    while (currentLength <= prefixLengthMax) {
      firstCode = firstCode + histogram[currentLength - 1] << 1;
      currentCode = firstCode;
      currentTemp = 0;
      while (currentTemp < linesLength) {
        line = lines[currentTemp];
        if (line.prefixLength === currentLength) {
          line.prefixCode = currentCode;
          currentCode++;
        }
        currentTemp++;
      }
      currentLength++;
    }
  }
}
function decodeTablesSegment(data, start, end) {
  const flags = data[start];
  const lowestValue = readUint32(data, start + 1) & 0xffffffff;
  const highestValue = readUint32(data, start + 5) & 0xffffffff;
  const reader = new Reader(data, start + 9, end);
  const prefixSizeBits = (flags >> 1 & 7) + 1;
  const rangeSizeBits = (flags >> 4 & 7) + 1;
  const lines = [];
  let prefixLength,
    rangeLength,
    currentRangeLow = lowestValue;
  do {
    prefixLength = reader.readBits(prefixSizeBits);
    rangeLength = reader.readBits(rangeSizeBits);
    lines.push(new HuffmanLine([currentRangeLow, prefixLength, rangeLength, 0]));
    currentRangeLow += 1 << rangeLength;
  } while (currentRangeLow < highestValue);
  prefixLength = reader.readBits(prefixSizeBits);
  lines.push(new HuffmanLine([lowestValue - 1, prefixLength, 32, 0, "lower"]));
  prefixLength = reader.readBits(prefixSizeBits);
  lines.push(new HuffmanLine([highestValue, prefixLength, 32, 0]));
  if (flags & 1) {
    prefixLength = reader.readBits(prefixSizeBits);
    lines.push(new HuffmanLine([prefixLength, 0]));
  }
  return new HuffmanTable(lines, false);
}
const standardTablesCache = {};
function getStandardTable(number) {
  let table = standardTablesCache[number];
  if (table) {
    return table;
  }
  let lines;
  switch (number) {
    case 1:
      lines = [[0, 1, 4, 0x0], [16, 2, 8, 0x2], [272, 3, 16, 0x6], [65808, 3, 32, 0x7]];
      break;
    case 2:
      lines = [[0, 1, 0, 0x0], [1, 2, 0, 0x2], [2, 3, 0, 0x6], [3, 4, 3, 0xe], [11, 5, 6, 0x1e], [75, 6, 32, 0x3e], [6, 0x3f]];
      break;
    case 3:
      lines = [[-256, 8, 8, 0xfe], [0, 1, 0, 0x0], [1, 2, 0, 0x2], [2, 3, 0, 0x6], [3, 4, 3, 0xe], [11, 5, 6, 0x1e], [-257, 8, 32, 0xff, "lower"], [75, 7, 32, 0x7e], [6, 0x3e]];
      break;
    case 4:
      lines = [[1, 1, 0, 0x0], [2, 2, 0, 0x2], [3, 3, 0, 0x6], [4, 4, 3, 0xe], [12, 5, 6, 0x1e], [76, 5, 32, 0x1f]];
      break;
    case 5:
      lines = [[-255, 7, 8, 0x7e], [1, 1, 0, 0x0], [2, 2, 0, 0x2], [3, 3, 0, 0x6], [4, 4, 3, 0xe], [12, 5, 6, 0x1e], [-256, 7, 32, 0x7f, "lower"], [76, 6, 32, 0x3e]];
      break;
    case 6:
      lines = [[-2048, 5, 10, 0x1c], [-1024, 4, 9, 0x8], [-512, 4, 8, 0x9], [-256, 4, 7, 0xa], [-128, 5, 6, 0x1d], [-64, 5, 5, 0x1e], [-32, 4, 5, 0xb], [0, 2, 7, 0x0], [128, 3, 7, 0x2], [256, 3, 8, 0x3], [512, 4, 9, 0xc], [1024, 4, 10, 0xd], [-2049, 6, 32, 0x3e, "lower"], [2048, 6, 32, 0x3f]];
      break;
    case 7:
      lines = [[-1024, 4, 9, 0x8], [-512, 3, 8, 0x0], [-256, 4, 7, 0x9], [-128, 5, 6, 0x1a], [-64, 5, 5, 0x1b], [-32, 4, 5, 0xa], [0, 4, 5, 0xb], [32, 5, 5, 0x1c], [64, 5, 6, 0x1d], [128, 4, 7, 0xc], [256, 3, 8, 0x1], [512, 3, 9, 0x2], [1024, 3, 10, 0x3], [-1025, 5, 32, 0x1e, "lower"], [2048, 5, 32, 0x1f]];
      break;
    case 8:
      lines = [[-15, 8, 3, 0xfc], [-7, 9, 1, 0x1fc], [-5, 8, 1, 0xfd], [-3, 9, 0, 0x1fd], [-2, 7, 0, 0x7c], [-1, 4, 0, 0xa], [0, 2, 1, 0x0], [2, 5, 0, 0x1a], [3, 6, 0, 0x3a], [4, 3, 4, 0x4], [20, 6, 1, 0x3b], [22, 4, 4, 0xb], [38, 4, 5, 0xc], [70, 5, 6, 0x1b], [134, 5, 7, 0x1c], [262, 6, 7, 0x3c], [390, 7, 8, 0x7d], [646, 6, 10, 0x3d], [-16, 9, 32, 0x1fe, "lower"], [1670, 9, 32, 0x1ff], [2, 0x1]];
      break;
    case 9:
      lines = [[-31, 8, 4, 0xfc], [-15, 9, 2, 0x1fc], [-11, 8, 2, 0xfd], [-7, 9, 1, 0x1fd], [-5, 7, 1, 0x7c], [-3, 4, 1, 0xa], [-1, 3, 1, 0x2], [1, 3, 1, 0x3], [3, 5, 1, 0x1a], [5, 6, 1, 0x3a], [7, 3, 5, 0x4], [39, 6, 2, 0x3b], [43, 4, 5, 0xb], [75, 4, 6, 0xc], [139, 5, 7, 0x1b], [267, 5, 8, 0x1c], [523, 6, 8, 0x3c], [779, 7, 9, 0x7d], [1291, 6, 11, 0x3d], [-32, 9, 32, 0x1fe, "lower"], [3339, 9, 32, 0x1ff], [2, 0x0]];
      break;
    case 10:
      lines = [[-21, 7, 4, 0x7a], [-5, 8, 0, 0xfc], [-4, 7, 0, 0x7b], [-3, 5, 0, 0x18], [-2, 2, 2, 0x0], [2, 5, 0, 0x19], [3, 6, 0, 0x36], [4, 7, 0, 0x7c], [5, 8, 0, 0xfd], [6, 2, 6, 0x1], [70, 5, 5, 0x1a], [102, 6, 5, 0x37], [134, 6, 6, 0x38], [198, 6, 7, 0x39], [326, 6, 8, 0x3a], [582, 6, 9, 0x3b], [1094, 6, 10, 0x3c], [2118, 7, 11, 0x7d], [-22, 8, 32, 0xfe, "lower"], [4166, 8, 32, 0xff], [2, 0x2]];
      break;
    case 11:
      lines = [[1, 1, 0, 0x0], [2, 2, 1, 0x2], [4, 4, 0, 0xc], [5, 4, 1, 0xd], [7, 5, 1, 0x1c], [9, 5, 2, 0x1d], [13, 6, 2, 0x3c], [17, 7, 2, 0x7a], [21, 7, 3, 0x7b], [29, 7, 4, 0x7c], [45, 7, 5, 0x7d], [77, 7, 6, 0x7e], [141, 7, 32, 0x7f]];
      break;
    case 12:
      lines = [[1, 1, 0, 0x0], [2, 2, 0, 0x2], [3, 3, 1, 0x6], [5, 5, 0, 0x1c], [6, 5, 1, 0x1d], [8, 6, 1, 0x3c], [10, 7, 0, 0x7a], [11, 7, 1, 0x7b], [13, 7, 2, 0x7c], [17, 7, 3, 0x7d], [25, 7, 4, 0x7e], [41, 8, 5, 0xfe], [73, 8, 32, 0xff]];
      break;
    case 13:
      lines = [[1, 1, 0, 0x0], [2, 3, 0, 0x4], [3, 4, 0, 0xc], [4, 5, 0, 0x1c], [5, 4, 1, 0xd], [7, 3, 3, 0x5], [15, 6, 1, 0x3a], [17, 6, 2, 0x3b], [21, 6, 3, 0x3c], [29, 6, 4, 0x3d], [45, 6, 5, 0x3e], [77, 7, 6, 0x7e], [141, 7, 32, 0x7f]];
      break;
    case 14:
      lines = [[-2, 3, 0, 0x4], [-1, 3, 0, 0x5], [0, 1, 0, 0x0], [1, 3, 0, 0x6], [2, 3, 0, 0x7]];
      break;
    case 15:
      lines = [[-24, 7, 4, 0x7c], [-8, 6, 2, 0x3c], [-4, 5, 1, 0x1c], [-2, 4, 0, 0xc], [-1, 3, 0, 0x4], [0, 1, 0, 0x0], [1, 3, 0, 0x5], [2, 4, 0, 0xd], [3, 5, 1, 0x1d], [5, 6, 2, 0x3d], [9, 7, 4, 0x7d], [-25, 7, 32, 0x7e, "lower"], [25, 7, 32, 0x7f]];
      break;
    default:
      throw new Jbig2Error(`standard table B.${number} does not exist`);
  }
  for (let i = 0, ii = lines.length; i < ii; i++) {
    lines[i] = new HuffmanLine(lines[i]);
  }
  table = new HuffmanTable(lines, true);
  standardTablesCache[number] = table;
  return table;
}
class Reader {
  constructor(data, start, end) {
    this.data = data;
    this.start = start;
    this.end = end;
    this.position = start;
    this.shift = -1;
    this.currentByte = 0;
  }
  readBit() {
    if (this.shift < 0) {
      if (this.position >= this.end) {
        throw new Jbig2Error("end of data while reading bit");
      }
      this.currentByte = this.data[this.position++];
      this.shift = 7;
    }
    const bit = this.currentByte >> this.shift & 1;
    this.shift--;
    return bit;
  }
  readBits(numBits) {
    let result = 0,
      i;
    for (i = numBits - 1; i >= 0; i--) {
      result |= this.readBit() << i;
    }
    return result;
  }
  byteAlign() {
    this.shift = -1;
  }
  next() {
    if (this.position >= this.end) {
      return -1;
    }
    return this.data[this.position++];
  }
}
function getCustomHuffmanTable(index, referredTo, customTables) {
  let currentIndex = 0;
  for (let i = 0, ii = referredTo.length; i < ii; i++) {
    const table = customTables[referredTo[i]];
    if (table) {
      if (index === currentIndex) {
        return table;
      }
      currentIndex++;
    }
  }
  throw new Jbig2Error("can't find custom Huffman table");
}
function getTextRegionHuffmanTables(textRegion, referredTo, customTables, numberOfSymbols, reader) {
  const codes = [];
  for (let i = 0; i <= 34; i++) {
    const codeLength = reader.readBits(4);
    codes.push(new HuffmanLine([i, codeLength, 0, 0]));
  }
  const runCodesTable = new HuffmanTable(codes, false);
  codes.length = 0;
  for (let i = 0; i < numberOfSymbols;) {
    const codeLength = runCodesTable.decode(reader);
    if (codeLength >= 32) {
      let repeatedLength, numberOfRepeats, j;
      switch (codeLength) {
        case 32:
          if (i === 0) {
            throw new Jbig2Error("no previous value in symbol ID table");
          }
          numberOfRepeats = reader.readBits(2) + 3;
          repeatedLength = codes[i - 1].prefixLength;
          break;
        case 33:
          numberOfRepeats = reader.readBits(3) + 3;
          repeatedLength = 0;
          break;
        case 34:
          numberOfRepeats = reader.readBits(7) + 11;
          repeatedLength = 0;
          break;
        default:
          throw new Jbig2Error("invalid code length in symbol ID table");
      }
      for (j = 0; j < numberOfRepeats; j++) {
        codes.push(new HuffmanLine([i, repeatedLength, 0, 0]));
        i++;
      }
    } else {
      codes.push(new HuffmanLine([i, codeLength, 0, 0]));
      i++;
    }
  }
  reader.byteAlign();
  const symbolIDTable = new HuffmanTable(codes, false);
  let customIndex = 0,
    tableFirstS,
    tableDeltaS,
    tableDeltaT;
  switch (textRegion.huffmanFS) {
    case 0:
    case 1:
      tableFirstS = getStandardTable(textRegion.huffmanFS + 6);
      break;
    case 3:
      tableFirstS = getCustomHuffmanTable(customIndex, referredTo, customTables);
      customIndex++;
      break;
    default:
      throw new Jbig2Error("invalid Huffman FS selector");
  }
  switch (textRegion.huffmanDS) {
    case 0:
    case 1:
    case 2:
      tableDeltaS = getStandardTable(textRegion.huffmanDS + 8);
      break;
    case 3:
      tableDeltaS = getCustomHuffmanTable(customIndex, referredTo, customTables);
      customIndex++;
      break;
    default:
      throw new Jbig2Error("invalid Huffman DS selector");
  }
  switch (textRegion.huffmanDT) {
    case 0:
    case 1:
    case 2:
      tableDeltaT = getStandardTable(textRegion.huffmanDT + 11);
      break;
    case 3:
      tableDeltaT = getCustomHuffmanTable(customIndex, referredTo, customTables);
      customIndex++;
      break;
    default:
      throw new Jbig2Error("invalid Huffman DT selector");
  }
  if (textRegion.refinement) {
    throw new Jbig2Error("refinement with Huffman is not supported");
  }
  return {
    symbolIDTable,
    tableFirstS,
    tableDeltaS,
    tableDeltaT
  };
}
function getSymbolDictionaryHuffmanTables(dictionary, referredTo, customTables) {
  let customIndex = 0,
    tableDeltaHeight,
    tableDeltaWidth;
  switch (dictionary.huffmanDHSelector) {
    case 0:
    case 1:
      tableDeltaHeight = getStandardTable(dictionary.huffmanDHSelector + 4);
      break;
    case 3:
      tableDeltaHeight = getCustomHuffmanTable(customIndex, referredTo, customTables);
      customIndex++;
      break;
    default:
      throw new Jbig2Error("invalid Huffman DH selector");
  }
  switch (dictionary.huffmanDWSelector) {
    case 0:
    case 1:
      tableDeltaWidth = getStandardTable(dictionary.huffmanDWSelector + 2);
      break;
    case 3:
      tableDeltaWidth = getCustomHuffmanTable(customIndex, referredTo, customTables);
      customIndex++;
      break;
    default:
      throw new Jbig2Error("invalid Huffman DW selector");
  }
  let tableBitmapSize, tableAggregateInstances;
  if (dictionary.bitmapSizeSelector) {
    tableBitmapSize = getCustomHuffmanTable(customIndex, referredTo, customTables);
    customIndex++;
  } else {
    tableBitmapSize = getStandardTable(1);
  }
  if (dictionary.aggregationInstancesSelector) {
    tableAggregateInstances = getCustomHuffmanTable(customIndex, referredTo, customTables);
  } else {
    tableAggregateInstances = getStandardTable(1);
  }
  return {
    tableDeltaHeight,
    tableDeltaWidth,
    tableBitmapSize,
    tableAggregateInstances
  };
}
function readUncompressedBitmap(reader, width, height) {
  const bitmap = [];
  for (let y = 0; y < height; y++) {
    const row = new Uint8Array(width);
    bitmap.push(row);
    for (let x = 0; x < width; x++) {
      row[x] = reader.readBit();
    }
    reader.byteAlign();
  }
  return bitmap;
}
function decodeMMRBitmap(input, width, height, endOfBlock) {
  const params = {
    K: -1,
    Columns: width,
    Rows: height,
    BlackIs1: true,
    EndOfBlock: endOfBlock
  };
  const decoder = new CCITTFaxDecoder(input, params);
  const bitmap = [];
  let currentByte,
    eof = false;
  for (let y = 0; y < height; y++) {
    const row = new Uint8Array(width);
    bitmap.push(row);
    let shift = -1;
    for (let x = 0; x < width; x++) {
      if (shift < 0) {
        currentByte = decoder.readNextChar();
        if (currentByte === -1) {
          currentByte = 0;
          eof = true;
        }
        shift = 7;
      }
      row[x] = currentByte >> shift & 1;
      shift--;
    }
  }
  if (endOfBlock && !eof) {
    const lookForEOFLimit = 5;
    for (let i = 0; i < lookForEOFLimit; i++) {
      if (decoder.readNextChar() === -1) {
        break;
      }
    }
  }
  return bitmap;
}
class Jbig2Image {
  parseChunks(chunks) {
    return parseJbig2Chunks(chunks);
  }
  parse(data) {
    throw new Error("Not implemented: Jbig2Image.parse");
  }
}

;// CONCATENATED MODULE: ./src/core/jbig2_stream.js





class Jbig2Stream extends DecodeStream {
  constructor(stream, maybeLength, params) {
    super(maybeLength);
    this.stream = stream;
    this.dict = stream.dict;
    this.maybeLength = maybeLength;
    this.params = params;
  }
  get bytes() {
    return shadow(this, "bytes", this.stream.getBytes(this.maybeLength));
  }
  ensureBuffer(requested) {}
  readBlock() {
    this.decodeImage();
  }
  decodeImage(bytes) {
    if (this.eof) {
      return this.buffer;
    }
    bytes ||= this.bytes;
    const jbig2Image = new Jbig2Image();
    const chunks = [];
    if (this.params instanceof Dict) {
      const globalsStream = this.params.get("JBIG2Globals");
      if (globalsStream instanceof BaseStream) {
        const globals = globalsStream.getBytes();
        chunks.push({
          data: globals,
          start: 0,
          end: globals.length
        });
      }
    }
    chunks.push({
      data: bytes,
      start: 0,
      end: bytes.length
    });
    const data = jbig2Image.parseChunks(chunks);
    const dataLength = data.length;
    for (let i = 0; i < dataLength; i++) {
      data[i] ^= 0xff;
    }
    this.buffer = data;
    this.bufferLength = dataLength;
    this.eof = true;
    return this.buffer;
  }
  get canAsyncDecodeImageFromBuffer() {
    return this.stream.isAsync;
  }
}

;// CONCATENATED MODULE: ./src/shared/image_utils.js

function convertToRGBA(params) {
  switch (params.kind) {
    case ImageKind.GRAYSCALE_1BPP:
      return convertBlackAndWhiteToRGBA(params);
    case ImageKind.RGB_24BPP:
      return convertRGBToRGBA(params);
  }
  return null;
}
function convertBlackAndWhiteToRGBA({
  src,
  srcPos = 0,
  dest,
  width,
  height,
  nonBlackColor = 0xffffffff,
  inverseDecode = false
}) {
  const black = FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff;
  const [zeroMapping, oneMapping] = inverseDecode ? [nonBlackColor, black] : [black, nonBlackColor];
  const widthInSource = width >> 3;
  const widthRemainder = width & 7;
  const srcLength = src.length;
  dest = new Uint32Array(dest.buffer);
  let destPos = 0;
  for (let i = 0; i < height; i++) {
    for (const max = srcPos + widthInSource; srcPos < max; srcPos++) {
      const elem = srcPos < srcLength ? src[srcPos] : 255;
      dest[destPos++] = elem & 0b10000000 ? oneMapping : zeroMapping;
      dest[destPos++] = elem & 0b1000000 ? oneMapping : zeroMapping;
      dest[destPos++] = elem & 0b100000 ? oneMapping : zeroMapping;
      dest[destPos++] = elem & 0b10000 ? oneMapping : zeroMapping;
      dest[destPos++] = elem & 0b1000 ? oneMapping : zeroMapping;
      dest[destPos++] = elem & 0b100 ? oneMapping : zeroMapping;
      dest[destPos++] = elem & 0b10 ? oneMapping : zeroMapping;
      dest[destPos++] = elem & 0b1 ? oneMapping : zeroMapping;
    }
    if (widthRemainder === 0) {
      continue;
    }
    const elem = srcPos < srcLength ? src[srcPos++] : 255;
    for (let j = 0; j < widthRemainder; j++) {
      dest[destPos++] = elem & 1 << 7 - j ? oneMapping : zeroMapping;
    }
  }
  return {
    srcPos,
    destPos
  };
}
function convertRGBToRGBA({
  src,
  srcPos = 0,
  dest,
  destPos = 0,
  width,
  height
}) {
  let i = 0;
  const len32 = src.length >> 2;
  const src32 = new Uint32Array(src.buffer, srcPos, len32);
  if (FeatureTest.isLittleEndian) {
    for (; i < len32 - 2; i += 3, destPos += 4) {
      const s1 = src32[i];
      const s2 = src32[i + 1];
      const s3 = src32[i + 2];
      dest[destPos] = s1 | 0xff000000;
      dest[destPos + 1] = s1 >>> 24 | s2 << 8 | 0xff000000;
      dest[destPos + 2] = s2 >>> 16 | s3 << 16 | 0xff000000;
      dest[destPos + 3] = s3 >>> 8 | 0xff000000;
    }
    for (let j = i * 4, jj = src.length; j < jj; j += 3) {
      dest[destPos++] = src[j] | src[j + 1] << 8 | src[j + 2] << 16 | 0xff000000;
    }
  } else {
    for (; i < len32 - 2; i += 3, destPos += 4) {
      const s1 = src32[i];
      const s2 = src32[i + 1];
      const s3 = src32[i + 2];
      dest[destPos] = s1 | 0xff;
      dest[destPos + 1] = s1 << 24 | s2 >>> 8 | 0xff;
      dest[destPos + 2] = s2 << 16 | s3 >>> 16 | 0xff;
      dest[destPos + 3] = s3 << 8 | 0xff;
    }
    for (let j = i * 4, jj = src.length; j < jj; j += 3) {
      dest[destPos++] = src[j] << 24 | src[j + 1] << 16 | src[j + 2] << 8 | 0xff;
    }
  }
  return {
    srcPos,
    destPos
  };
}
function grayToRGBA(src, dest) {
  if (FeatureTest.isLittleEndian) {
    for (let i = 0, ii = src.length; i < ii; i++) {
      dest[i] = src[i] * 0x10101 | 0xff000000;
    }
  } else {
    for (let i = 0, ii = src.length; i < ii; i++) {
      dest[i] = src[i] * 0x1010100 | 0x000000ff;
    }
  }
}

;// CONCATENATED MODULE: ./src/core/jpg.js



class JpegError extends BaseException {
  constructor(msg) {
    super(msg, "JpegError");
  }
}
class DNLMarkerError extends BaseException {
  constructor(message, scanLines) {
    super(message, "DNLMarkerError");
    this.scanLines = scanLines;
  }
}
class EOIMarkerError extends BaseException {
  constructor(msg) {
    super(msg, "EOIMarkerError");
  }
}
const dctZigZag = new Uint8Array([0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63]);
const dctCos1 = 4017;
const dctSin1 = 799;
const dctCos3 = 3406;
const dctSin3 = 2276;
const dctCos6 = 1567;
const dctSin6 = 3784;
const dctSqrt2 = 5793;
const dctSqrt1d2 = 2896;
function buildHuffmanTable(codeLengths, values) {
  let k = 0,
    i,
    j,
    length = 16;
  while (length > 0 && !codeLengths[length - 1]) {
    length--;
  }
  const code = [{
    children: [],
    index: 0
  }];
  let p = code[0],
    q;
  for (i = 0; i < length; i++) {
    for (j = 0; j < codeLengths[i]; j++) {
      p = code.pop();
      p.children[p.index] = values[k];
      while (p.index > 0) {
        p = code.pop();
      }
      p.index++;
      code.push(p);
      while (code.length <= i) {
        code.push(q = {
          children: [],
          index: 0
        });
        p.children[p.index] = q.children;
        p = q;
      }
      k++;
    }
    if (i + 1 < length) {
      code.push(q = {
        children: [],
        index: 0
      });
      p.children[p.index] = q.children;
      p = q;
    }
  }
  return code[0].children;
}
function getBlockBufferOffset(component, row, col) {
  return 64 * ((component.blocksPerLine + 1) * row + col);
}
function decodeScan(data, offset, frame, components, resetInterval, spectralStart, spectralEnd, successivePrev, successive, parseDNLMarker = false) {
  const mcusPerLine = frame.mcusPerLine;
  const progressive = frame.progressive;
  const startOffset = offset;
  let bitsData = 0,
    bitsCount = 0;
  function readBit() {
    if (bitsCount > 0) {
      bitsCount--;
      return bitsData >> bitsCount & 1;
    }
    bitsData = data[offset++];
    if (bitsData === 0xff) {
      const nextByte = data[offset++];
      if (nextByte) {
        if (nextByte === 0xdc && parseDNLMarker) {
          offset += 2;
          const scanLines = readUint16(data, offset);
          offset += 2;
          if (scanLines > 0 && scanLines !== frame.scanLines) {
            throw new DNLMarkerError("Found DNL marker (0xFFDC) while parsing scan data", scanLines);
          }
        } else if (nextByte === 0xd9) {
          if (parseDNLMarker) {
            const maybeScanLines = blockRow * (frame.precision === 8 ? 8 : 0);
            if (maybeScanLines > 0 && Math.round(frame.scanLines / maybeScanLines) >= 5) {
              throw new DNLMarkerError("Found EOI marker (0xFFD9) while parsing scan data, " + "possibly caused by incorrect `scanLines` parameter", maybeScanLines);
            }
          }
          throw new EOIMarkerError("Found EOI marker (0xFFD9) while parsing scan data");
        }
        throw new JpegError(`unexpected marker ${(bitsData << 8 | nextByte).toString(16)}`);
      }
    }
    bitsCount = 7;
    return bitsData >>> 7;
  }
  function decodeHuffman(tree) {
    let node = tree;
    while (true) {
      node = node[readBit()];
      switch (typeof node) {
        case "number":
          return node;
        case "object":
          continue;
      }
      throw new JpegError("invalid huffman sequence");
    }
  }
  function receive(length) {
    let n = 0;
    while (length > 0) {
      n = n << 1 | readBit();
      length--;
    }
    return n;
  }
  function receiveAndExtend(length) {
    if (length === 1) {
      return readBit() === 1 ? 1 : -1;
    }
    const n = receive(length);
    if (n >= 1 << length - 1) {
      return n;
    }
    return n + (-1 << length) + 1;
  }
  function decodeBaseline(component, blockOffset) {
    const t = decodeHuffman(component.huffmanTableDC);
    const diff = t === 0 ? 0 : receiveAndExtend(t);
    component.blockData[blockOffset] = component.pred += diff;
    let k = 1;
    while (k < 64) {
      const rs = decodeHuffman(component.huffmanTableAC);
      const s = rs & 15,
        r = rs >> 4;
      if (s === 0) {
        if (r < 15) {
          break;
        }
        k += 16;
        continue;
      }
      k += r;
      const z = dctZigZag[k];
      component.blockData[blockOffset + z] = receiveAndExtend(s);
      k++;
    }
  }
  function decodeDCFirst(component, blockOffset) {
    const t = decodeHuffman(component.huffmanTableDC);
    const diff = t === 0 ? 0 : receiveAndExtend(t) << successive;
    component.blockData[blockOffset] = component.pred += diff;
  }
  function decodeDCSuccessive(component, blockOffset) {
    component.blockData[blockOffset] |= readBit() << successive;
  }
  let eobrun = 0;
  function decodeACFirst(component, blockOffset) {
    if (eobrun > 0) {
      eobrun--;
      return;
    }
    let k = spectralStart;
    const e = spectralEnd;
    while (k <= e) {
      const rs = decodeHuffman(component.huffmanTableAC);
      const s = rs & 15,
        r = rs >> 4;
      if (s === 0) {
        if (r < 15) {
          eobrun = receive(r) + (1 << r) - 1;
          break;
        }
        k += 16;
        continue;
      }
      k += r;
      const z = dctZigZag[k];
      component.blockData[blockOffset + z] = receiveAndExtend(s) * (1 << successive);
      k++;
    }
  }
  let successiveACState = 0,
    successiveACNextValue;
  function decodeACSuccessive(component, blockOffset) {
    let k = spectralStart;
    const e = spectralEnd;
    let r = 0;
    let s;
    let rs;
    while (k <= e) {
      const offsetZ = blockOffset + dctZigZag[k];
      const sign = component.blockData[offsetZ] < 0 ? -1 : 1;
      switch (successiveACState) {
        case 0:
          rs = decodeHuffman(component.huffmanTableAC);
          s = rs & 15;
          r = rs >> 4;
          if (s === 0) {
            if (r < 15) {
              eobrun = receive(r) + (1 << r);
              successiveACState = 4;
            } else {
              r = 16;
              successiveACState = 1;
            }
          } else {
            if (s !== 1) {
              throw new JpegError("invalid ACn encoding");
            }
            successiveACNextValue = receiveAndExtend(s);
            successiveACState = r ? 2 : 3;
          }
          continue;
        case 1:
        case 2:
          if (component.blockData[offsetZ]) {
            component.blockData[offsetZ] += sign * (readBit() << successive);
          } else {
            r--;
            if (r === 0) {
              successiveACState = successiveACState === 2 ? 3 : 0;
            }
          }
          break;
        case 3:
          if (component.blockData[offsetZ]) {
            component.blockData[offsetZ] += sign * (readBit() << successive);
          } else {
            component.blockData[offsetZ] = successiveACNextValue << successive;
            successiveACState = 0;
          }
          break;
        case 4:
          if (component.blockData[offsetZ]) {
            component.blockData[offsetZ] += sign * (readBit() << successive);
          }
          break;
      }
      k++;
    }
    if (successiveACState === 4) {
      eobrun--;
      if (eobrun === 0) {
        successiveACState = 0;
      }
    }
  }
  let blockRow = 0;
  function decodeMcu(component, decode, mcu, row, col) {
    const mcuRow = mcu / mcusPerLine | 0;
    const mcuCol = mcu % mcusPerLine;
    blockRow = mcuRow * component.v + row;
    const blockCol = mcuCol * component.h + col;
    const blockOffset = getBlockBufferOffset(component, blockRow, blockCol);
    decode(component, blockOffset);
  }
  function decodeBlock(component, decode, mcu) {
    blockRow = mcu / component.blocksPerLine | 0;
    const blockCol = mcu % component.blocksPerLine;
    const blockOffset = getBlockBufferOffset(component, blockRow, blockCol);
    decode(component, blockOffset);
  }
  const componentsLength = components.length;
  let component, i, j, k, n;
  let decodeFn;
  if (progressive) {
    if (spectralStart === 0) {
      decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive;
    } else {
      decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive;
    }
  } else {
    decodeFn = decodeBaseline;
  }
  let mcu = 0,
    fileMarker;
  const mcuExpected = componentsLength === 1 ? components[0].blocksPerLine * components[0].blocksPerColumn : mcusPerLine * frame.mcusPerColumn;
  let h, v;
  while (mcu <= mcuExpected) {
    const mcuToRead = resetInterval ? Math.min(mcuExpected - mcu, resetInterval) : mcuExpected;
    if (mcuToRead > 0) {
      for (i = 0; i < componentsLength; i++) {
        components[i].pred = 0;
      }
      eobrun = 0;
      if (componentsLength === 1) {
        component = components[0];
        for (n = 0; n < mcuToRead; n++) {
          decodeBlock(component, decodeFn, mcu);
          mcu++;
        }
      } else {
        for (n = 0; n < mcuToRead; n++) {
          for (i = 0; i < componentsLength; i++) {
            component = components[i];
            h = component.h;
            v = component.v;
            for (j = 0; j < v; j++) {
              for (k = 0; k < h; k++) {
                decodeMcu(component, decodeFn, mcu, j, k);
              }
            }
          }
          mcu++;
        }
      }
    }
    bitsCount = 0;
    fileMarker = findNextFileMarker(data, offset);
    if (!fileMarker) {
      break;
    }
    if (fileMarker.invalid) {
      const partialMsg = mcuToRead > 0 ? "unexpected" : "excessive";
      warn(`decodeScan - ${partialMsg} MCU data, current marker is: ${fileMarker.invalid}`);
      offset = fileMarker.offset;
    }
    if (fileMarker.marker >= 0xffd0 && fileMarker.marker <= 0xffd7) {
      offset += 2;
    } else {
      break;
    }
  }
  return offset - startOffset;
}
function quantizeAndInverse(component, blockBufferOffset, p) {
  const qt = component.quantizationTable,
    blockData = component.blockData;
  let v0, v1, v2, v3, v4, v5, v6, v7;
  let p0, p1, p2, p3, p4, p5, p6, p7;
  let t;
  if (!qt) {
    throw new JpegError("missing required Quantization Table.");
  }
  for (let row = 0; row < 64; row += 8) {
    p0 = blockData[blockBufferOffset + row];
    p1 = blockData[blockBufferOffset + row + 1];
    p2 = blockData[blockBufferOffset + row + 2];
    p3 = blockData[blockBufferOffset + row + 3];
    p4 = blockData[blockBufferOffset + row + 4];
    p5 = blockData[blockBufferOffset + row + 5];
    p6 = blockData[blockBufferOffset + row + 6];
    p7 = blockData[blockBufferOffset + row + 7];
    p0 *= qt[row];
    if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) {
      t = dctSqrt2 * p0 + 512 >> 10;
      p[row] = t;
      p[row + 1] = t;
      p[row + 2] = t;
      p[row + 3] = t;
      p[row + 4] = t;
      p[row + 5] = t;
      p[row + 6] = t;
      p[row + 7] = t;
      continue;
    }
    p1 *= qt[row + 1];
    p2 *= qt[row + 2];
    p3 *= qt[row + 3];
    p4 *= qt[row + 4];
    p5 *= qt[row + 5];
    p6 *= qt[row + 6];
    p7 *= qt[row + 7];
    v0 = dctSqrt2 * p0 + 128 >> 8;
    v1 = dctSqrt2 * p4 + 128 >> 8;
    v2 = p2;
    v3 = p6;
    v4 = dctSqrt1d2 * (p1 - p7) + 128 >> 8;
    v7 = dctSqrt1d2 * (p1 + p7) + 128 >> 8;
    v5 = p3 << 4;
    v6 = p5 << 4;
    v0 = v0 + v1 + 1 >> 1;
    v1 = v0 - v1;
    t = v2 * dctSin6 + v3 * dctCos6 + 128 >> 8;
    v2 = v2 * dctCos6 - v3 * dctSin6 + 128 >> 8;
    v3 = t;
    v4 = v4 + v6 + 1 >> 1;
    v6 = v4 - v6;
    v7 = v7 + v5 + 1 >> 1;
    v5 = v7 - v5;
    v0 = v0 + v3 + 1 >> 1;
    v3 = v0 - v3;
    v1 = v1 + v2 + 1 >> 1;
    v2 = v1 - v2;
    t = v4 * dctSin3 + v7 * dctCos3 + 2048 >> 12;
    v4 = v4 * dctCos3 - v7 * dctSin3 + 2048 >> 12;
    v7 = t;
    t = v5 * dctSin1 + v6 * dctCos1 + 2048 >> 12;
    v5 = v5 * dctCos1 - v6 * dctSin1 + 2048 >> 12;
    v6 = t;
    p[row] = v0 + v7;
    p[row + 7] = v0 - v7;
    p[row + 1] = v1 + v6;
    p[row + 6] = v1 - v6;
    p[row + 2] = v2 + v5;
    p[row + 5] = v2 - v5;
    p[row + 3] = v3 + v4;
    p[row + 4] = v3 - v4;
  }
  for (let col = 0; col < 8; ++col) {
    p0 = p[col];
    p1 = p[col + 8];
    p2 = p[col + 16];
    p3 = p[col + 24];
    p4 = p[col + 32];
    p5 = p[col + 40];
    p6 = p[col + 48];
    p7 = p[col + 56];
    if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) {
      t = dctSqrt2 * p0 + 8192 >> 14;
      if (t < -2040) {
        t = 0;
      } else if (t >= 2024) {
        t = 255;
      } else {
        t = t + 2056 >> 4;
      }
      blockData[blockBufferOffset + col] = t;
      blockData[blockBufferOffset + col + 8] = t;
      blockData[blockBufferOffset + col + 16] = t;
      blockData[blockBufferOffset + col + 24] = t;
      blockData[blockBufferOffset + col + 32] = t;
      blockData[blockBufferOffset + col + 40] = t;
      blockData[blockBufferOffset + col + 48] = t;
      blockData[blockBufferOffset + col + 56] = t;
      continue;
    }
    v0 = dctSqrt2 * p0 + 2048 >> 12;
    v1 = dctSqrt2 * p4 + 2048 >> 12;
    v2 = p2;
    v3 = p6;
    v4 = dctSqrt1d2 * (p1 - p7) + 2048 >> 12;
    v7 = dctSqrt1d2 * (p1 + p7) + 2048 >> 12;
    v5 = p3;
    v6 = p5;
    v0 = (v0 + v1 + 1 >> 1) + 4112;
    v1 = v0 - v1;
    t = v2 * dctSin6 + v3 * dctCos6 + 2048 >> 12;
    v2 = v2 * dctCos6 - v3 * dctSin6 + 2048 >> 12;
    v3 = t;
    v4 = v4 + v6 + 1 >> 1;
    v6 = v4 - v6;
    v7 = v7 + v5 + 1 >> 1;
    v5 = v7 - v5;
    v0 = v0 + v3 + 1 >> 1;
    v3 = v0 - v3;
    v1 = v1 + v2 + 1 >> 1;
    v2 = v1 - v2;
    t = v4 * dctSin3 + v7 * dctCos3 + 2048 >> 12;
    v4 = v4 * dctCos3 - v7 * dctSin3 + 2048 >> 12;
    v7 = t;
    t = v5 * dctSin1 + v6 * dctCos1 + 2048 >> 12;
    v5 = v5 * dctCos1 - v6 * dctSin1 + 2048 >> 12;
    v6 = t;
    p0 = v0 + v7;
    p7 = v0 - v7;
    p1 = v1 + v6;
    p6 = v1 - v6;
    p2 = v2 + v5;
    p5 = v2 - v5;
    p3 = v3 + v4;
    p4 = v3 - v4;
    if (p0 < 16) {
      p0 = 0;
    } else if (p0 >= 4080) {
      p0 = 255;
    } else {
      p0 >>= 4;
    }
    if (p1 < 16) {
      p1 = 0;
    } else if (p1 >= 4080) {
      p1 = 255;
    } else {
      p1 >>= 4;
    }
    if (p2 < 16) {
      p2 = 0;
    } else if (p2 >= 4080) {
      p2 = 255;
    } else {
      p2 >>= 4;
    }
    if (p3 < 16) {
      p3 = 0;
    } else if (p3 >= 4080) {
      p3 = 255;
    } else {
      p3 >>= 4;
    }
    if (p4 < 16) {
      p4 = 0;
    } else if (p4 >= 4080) {
      p4 = 255;
    } else {
      p4 >>= 4;
    }
    if (p5 < 16) {
      p5 = 0;
    } else if (p5 >= 4080) {
      p5 = 255;
    } else {
      p5 >>= 4;
    }
    if (p6 < 16) {
      p6 = 0;
    } else if (p6 >= 4080) {
      p6 = 255;
    } else {
      p6 >>= 4;
    }
    if (p7 < 16) {
      p7 = 0;
    } else if (p7 >= 4080) {
      p7 = 255;
    } else {
      p7 >>= 4;
    }
    blockData[blockBufferOffset + col] = p0;
    blockData[blockBufferOffset + col + 8] = p1;
    blockData[blockBufferOffset + col + 16] = p2;
    blockData[blockBufferOffset + col + 24] = p3;
    blockData[blockBufferOffset + col + 32] = p4;
    blockData[blockBufferOffset + col + 40] = p5;
    blockData[blockBufferOffset + col + 48] = p6;
    blockData[blockBufferOffset + col + 56] = p7;
  }
}
function buildComponentData(frame, component) {
  const blocksPerLine = component.blocksPerLine;
  const blocksPerColumn = component.blocksPerColumn;
  const computationBuffer = new Int16Array(64);
  for (let blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
    for (let blockCol = 0; blockCol < blocksPerLine; blockCol++) {
      const offset = getBlockBufferOffset(component, blockRow, blockCol);
      quantizeAndInverse(component, offset, computationBuffer);
    }
  }
  return component.blockData;
}
function findNextFileMarker(data, currentPos, startPos = currentPos) {
  const maxPos = data.length - 1;
  let newPos = startPos < currentPos ? startPos : currentPos;
  if (currentPos >= maxPos) {
    return null;
  }
  const currentMarker = readUint16(data, currentPos);
  if (currentMarker >= 0xffc0 && currentMarker <= 0xfffe) {
    return {
      invalid: null,
      marker: currentMarker,
      offset: currentPos
    };
  }
  let newMarker = readUint16(data, newPos);
  while (!(newMarker >= 0xffc0 && newMarker <= 0xfffe)) {
    if (++newPos >= maxPos) {
      return null;
    }
    newMarker = readUint16(data, newPos);
  }
  return {
    invalid: currentMarker.toString(16),
    marker: newMarker,
    offset: newPos
  };
}
class JpegImage {
  constructor({
    decodeTransform = null,
    colorTransform = -1
  } = {}) {
    this._decodeTransform = decodeTransform;
    this._colorTransform = colorTransform;
  }
  parse(data, {
    dnlScanLines = null
  } = {}) {
    function readDataBlock() {
      const length = readUint16(data, offset);
      offset += 2;
      let endOffset = offset + length - 2;
      const fileMarker = findNextFileMarker(data, endOffset, offset);
      if (fileMarker?.invalid) {
        warn("readDataBlock - incorrect length, current marker is: " + fileMarker.invalid);
        endOffset = fileMarker.offset;
      }
      const array = data.subarray(offset, endOffset);
      offset += array.length;
      return array;
    }
    function prepareComponents(frame) {
      const mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH);
      const mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV);
      for (const component of frame.components) {
        const blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * component.h / frame.maxH);
        const blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines / 8) * component.v / frame.maxV);
        const blocksPerLineForMcu = mcusPerLine * component.h;
        const blocksPerColumnForMcu = mcusPerColumn * component.v;
        const blocksBufferSize = 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1);
        component.blockData = new Int16Array(blocksBufferSize);
        component.blocksPerLine = blocksPerLine;
        component.blocksPerColumn = blocksPerColumn;
      }
      frame.mcusPerLine = mcusPerLine;
      frame.mcusPerColumn = mcusPerColumn;
    }
    let offset = 0;
    let jfif = null;
    let adobe = null;
    let frame, resetInterval;
    let numSOSMarkers = 0;
    const quantizationTables = [];
    const huffmanTablesAC = [],
      huffmanTablesDC = [];
    let fileMarker = readUint16(data, offset);
    offset += 2;
    if (fileMarker !== 0xffd8) {
      throw new JpegError("SOI not found");
    }
    fileMarker = readUint16(data, offset);
    offset += 2;
    markerLoop: while (fileMarker !== 0xffd9) {
      let i, j, l;
      switch (fileMarker) {
        case 0xffe0:
        case 0xffe1:
        case 0xffe2:
        case 0xffe3:
        case 0xffe4:
        case 0xffe5:
        case 0xffe6:
        case 0xffe7:
        case 0xffe8:
        case 0xffe9:
        case 0xffea:
        case 0xffeb:
        case 0xffec:
        case 0xffed:
        case 0xffee:
        case 0xffef:
        case 0xfffe:
          const appData = readDataBlock();
          if (fileMarker === 0xffe0) {
            if (appData[0] === 0x4a && appData[1] === 0x46 && appData[2] === 0x49 && appData[3] === 0x46 && appData[4] === 0) {
              jfif = {
                version: {
                  major: appData[5],
                  minor: appData[6]
                },
                densityUnits: appData[7],
                xDensity: appData[8] << 8 | appData[9],
                yDensity: appData[10] << 8 | appData[11],
                thumbWidth: appData[12],
                thumbHeight: appData[13],
                thumbData: appData.subarray(14, 14 + 3 * appData[12] * appData[13])
              };
            }
          }
          if (fileMarker === 0xffee) {
            if (appData[0] === 0x41 && appData[1] === 0x64 && appData[2] === 0x6f && appData[3] === 0x62 && appData[4] === 0x65) {
              adobe = {
                version: appData[5] << 8 | appData[6],
                flags0: appData[7] << 8 | appData[8],
                flags1: appData[9] << 8 | appData[10],
                transformCode: appData[11]
              };
            }
          }
          break;
        case 0xffdb:
          const quantizationTablesLength = readUint16(data, offset);
          offset += 2;
          const quantizationTablesEnd = quantizationTablesLength + offset - 2;
          let z;
          while (offset < quantizationTablesEnd) {
            const quantizationTableSpec = data[offset++];
            const tableData = new Uint16Array(64);
            if (quantizationTableSpec >> 4 === 0) {
              for (j = 0; j < 64; j++) {
                z = dctZigZag[j];
                tableData[z] = data[offset++];
              }
            } else if (quantizationTableSpec >> 4 === 1) {
              for (j = 0; j < 64; j++) {
                z = dctZigZag[j];
                tableData[z] = readUint16(data, offset);
                offset += 2;
              }
            } else {
              throw new JpegError("DQT - invalid table spec");
            }
            quantizationTables[quantizationTableSpec & 15] = tableData;
          }
          break;
        case 0xffc0:
        case 0xffc1:
        case 0xffc2:
          if (frame) {
            throw new JpegError("Only single frame JPEGs supported");
          }
          offset += 2;
          frame = {};
          frame.extended = fileMarker === 0xffc1;
          frame.progressive = fileMarker === 0xffc2;
          frame.precision = data[offset++];
          const sofScanLines = readUint16(data, offset);
          offset += 2;
          frame.scanLines = dnlScanLines || sofScanLines;
          frame.samplesPerLine = readUint16(data, offset);
          offset += 2;
          frame.components = [];
          frame.componentIds = {};
          const componentsCount = data[offset++];
          let maxH = 0,
            maxV = 0;
          for (i = 0; i < componentsCount; i++) {
            const componentId = data[offset];
            const h = data[offset + 1] >> 4;
            const v = data[offset + 1] & 15;
            if (maxH < h) {
              maxH = h;
            }
            if (maxV < v) {
              maxV = v;
            }
            const qId = data[offset + 2];
            l = frame.components.push({
              h,
              v,
              quantizationId: qId,
              quantizationTable: null
            });
            frame.componentIds[componentId] = l - 1;
            offset += 3;
          }
          frame.maxH = maxH;
          frame.maxV = maxV;
          prepareComponents(frame);
          break;
        case 0xffc4:
          const huffmanLength = readUint16(data, offset);
          offset += 2;
          for (i = 2; i < huffmanLength;) {
            const huffmanTableSpec = data[offset++];
            const codeLengths = new Uint8Array(16);
            let codeLengthSum = 0;
            for (j = 0; j < 16; j++, offset++) {
              codeLengthSum += codeLengths[j] = data[offset];
            }
            const huffmanValues = new Uint8Array(codeLengthSum);
            for (j = 0; j < codeLengthSum; j++, offset++) {
              huffmanValues[j] = data[offset];
            }
            i += 17 + codeLengthSum;
            (huffmanTableSpec >> 4 === 0 ? huffmanTablesDC : huffmanTablesAC)[huffmanTableSpec & 15] = buildHuffmanTable(codeLengths, huffmanValues);
          }
          break;
        case 0xffdd:
          offset += 2;
          resetInterval = readUint16(data, offset);
          offset += 2;
          break;
        case 0xffda:
          const parseDNLMarker = ++numSOSMarkers === 1 && !dnlScanLines;
          offset += 2;
          const selectorsCount = data[offset++],
            components = [];
          for (i = 0; i < selectorsCount; i++) {
            const index = data[offset++];
            const componentIndex = frame.componentIds[index];
            const component = frame.components[componentIndex];
            component.index = index;
            const tableSpec = data[offset++];
            component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4];
            component.huffmanTableAC = huffmanTablesAC[tableSpec & 15];
            components.push(component);
          }
          const spectralStart = data[offset++],
            spectralEnd = data[offset++],
            successiveApproximation = data[offset++];
          try {
            const processed = decodeScan(data, offset, frame, components, resetInterval, spectralStart, spectralEnd, successiveApproximation >> 4, successiveApproximation & 15, parseDNLMarker);
            offset += processed;
          } catch (ex) {
            if (ex instanceof DNLMarkerError) {
              warn(`${ex.message} -- attempting to re-parse the JPEG image.`);
              return this.parse(data, {
                dnlScanLines: ex.scanLines
              });
            } else if (ex instanceof EOIMarkerError) {
              warn(`${ex.message} -- ignoring the rest of the image data.`);
              break markerLoop;
            }
            throw ex;
          }
          break;
        case 0xffdc:
          offset += 4;
          break;
        case 0xffff:
          if (data[offset] !== 0xff) {
            offset--;
          }
          break;
        default:
          const nextFileMarker = findNextFileMarker(data, offset - 2, offset - 3);
          if (nextFileMarker?.invalid) {
            warn("JpegImage.parse - unexpected data, current marker is: " + nextFileMarker.invalid);
            offset = nextFileMarker.offset;
            break;
          }
          if (!nextFileMarker || offset >= data.length - 1) {
            warn("JpegImage.parse - reached the end of the image data " + "without finding an EOI marker (0xFFD9).");
            break markerLoop;
          }
          throw new JpegError("JpegImage.parse - unknown marker: " + fileMarker.toString(16));
      }
      fileMarker = readUint16(data, offset);
      offset += 2;
    }
    if (!frame) {
      throw new JpegError("JpegImage.parse - no frame data found.");
    }
    this.width = frame.samplesPerLine;
    this.height = frame.scanLines;
    this.jfif = jfif;
    this.adobe = adobe;
    this.components = [];
    for (const component of frame.components) {
      const quantizationTable = quantizationTables[component.quantizationId];
      if (quantizationTable) {
        component.quantizationTable = quantizationTable;
      }
      this.components.push({
        index: component.index,
        output: buildComponentData(frame, component),
        scaleX: component.h / frame.maxH,
        scaleY: component.v / frame.maxV,
        blocksPerLine: component.blocksPerLine,
        blocksPerColumn: component.blocksPerColumn
      });
    }
    this.numComponents = this.components.length;
    return undefined;
  }
  _getLinearizedBlockData(width, height, isSourcePDF = false) {
    const scaleX = this.width / width,
      scaleY = this.height / height;
    let component, componentScaleX, componentScaleY, blocksPerScanline;
    let x, y, i, j, k;
    let index;
    let offset = 0;
    let output;
    const numComponents = this.components.length;
    const dataLength = width * height * numComponents;
    const data = new Uint8ClampedArray(dataLength);
    const xScaleBlockOffset = new Uint32Array(width);
    const mask3LSB = 0xfffffff8;
    let lastComponentScaleX;
    for (i = 0; i < numComponents; i++) {
      component = this.components[i];
      componentScaleX = component.scaleX * scaleX;
      componentScaleY = component.scaleY * scaleY;
      offset = i;
      output = component.output;
      blocksPerScanline = component.blocksPerLine + 1 << 3;
      if (componentScaleX !== lastComponentScaleX) {
        for (x = 0; x < width; x++) {
          j = 0 | x * componentScaleX;
          xScaleBlockOffset[x] = (j & mask3LSB) << 3 | j & 7;
        }
        lastComponentScaleX = componentScaleX;
      }
      for (y = 0; y < height; y++) {
        j = 0 | y * componentScaleY;
        index = blocksPerScanline * (j & mask3LSB) | (j & 7) << 3;
        for (x = 0; x < width; x++) {
          data[offset] = output[index + xScaleBlockOffset[x]];
          offset += numComponents;
        }
      }
    }
    let transform = this._decodeTransform;
    if (!isSourcePDF && numComponents === 4 && !transform) {
      transform = new Int32Array([-256, 255, -256, 255, -256, 255, -256, 255]);
    }
    if (transform) {
      for (i = 0; i < dataLength;) {
        for (j = 0, k = 0; j < numComponents; j++, i++, k += 2) {
          data[i] = (data[i] * transform[k] >> 8) + transform[k + 1];
        }
      }
    }
    return data;
  }
  get _isColorConversionNeeded() {
    if (this.adobe) {
      return !!this.adobe.transformCode;
    }
    if (this.numComponents === 3) {
      if (this._colorTransform === 0) {
        return false;
      } else if (this.components[0].index === 0x52 && this.components[1].index === 0x47 && this.components[2].index === 0x42) {
        return false;
      }
      return true;
    }
    if (this._colorTransform === 1) {
      return true;
    }
    return false;
  }
  _convertYccToRgb(data) {
    let Y, Cb, Cr;
    for (let i = 0, length = data.length; i < length; i += 3) {
      Y = data[i];
      Cb = data[i + 1];
      Cr = data[i + 2];
      data[i] = Y - 179.456 + 1.402 * Cr;
      data[i + 1] = Y + 135.459 - 0.344 * Cb - 0.714 * Cr;
      data[i + 2] = Y - 226.816 + 1.772 * Cb;
    }
    return data;
  }
  _convertYccToRgba(data, out) {
    for (let i = 0, j = 0, length = data.length; i < length; i += 3, j += 4) {
      const Y = data[i];
      const Cb = data[i + 1];
      const Cr = data[i + 2];
      out[j] = Y - 179.456 + 1.402 * Cr;
      out[j + 1] = Y + 135.459 - 0.344 * Cb - 0.714 * Cr;
      out[j + 2] = Y - 226.816 + 1.772 * Cb;
      out[j + 3] = 255;
    }
    return out;
  }
  _convertYcckToRgb(data) {
    let Y, Cb, Cr, k;
    let offset = 0;
    for (let i = 0, length = data.length; i < length; i += 4) {
      Y = data[i];
      Cb = data[i + 1];
      Cr = data[i + 2];
      k = data[i + 3];
      data[offset++] = -122.67195406894 + Cb * (-6.60635669420364e-5 * Cb + 0.000437130475926232 * Cr - 5.4080610064599e-5 * Y + 0.00048449797120281 * k - 0.154362151871126) + Cr * (-0.000957964378445773 * Cr + 0.000817076911346625 * Y - 0.00477271405408747 * k + 1.53380253221734) + Y * (0.000961250184130688 * Y - 0.00266257332283933 * k + 0.48357088451265) + k * (-0.000336197177618394 * k + 0.484791561490776);
      data[offset++] = 107.268039397724 + Cb * (2.19927104525741e-5 * Cb - 0.000640992018297945 * Cr + 0.000659397001245577 * Y + 0.000426105652938837 * k - 0.176491792462875) + Cr * (-0.000778269941513683 * Cr + 0.00130872261408275 * Y + 0.000770482631801132 * k - 0.151051492775562) + Y * (0.00126935368114843 * Y - 0.00265090189010898 * k + 0.25802910206845) + k * (-0.000318913117588328 * k - 0.213742400323665);
      data[offset++] = -20.810012546947 + Cb * (-0.000570115196973677 * Cb - 2.63409051004589e-5 * Cr + 0.0020741088115012 * Y - 0.00288260236853442 * k + 0.814272968359295) + Cr * (-1.53496057440975e-5 * Cr - 0.000132689043961446 * Y + 0.000560833691242812 * k - 0.195152027534049) + Y * (0.00174418132927582 * Y - 0.00255243321439347 * k + 0.116935020465145) + k * (-0.000343531996510555 * k + 0.24165260232407);
    }
    return data.subarray(0, offset);
  }
  _convertYcckToRgba(data) {
    for (let i = 0, length = data.length; i < length; i += 4) {
      const Y = data[i];
      const Cb = data[i + 1];
      const Cr = data[i + 2];
      const k = data[i + 3];
      data[i] = -122.67195406894 + Cb * (-6.60635669420364e-5 * Cb + 0.000437130475926232 * Cr - 5.4080610064599e-5 * Y + 0.00048449797120281 * k - 0.154362151871126) + Cr * (-0.000957964378445773 * Cr + 0.000817076911346625 * Y - 0.00477271405408747 * k + 1.53380253221734) + Y * (0.000961250184130688 * Y - 0.00266257332283933 * k + 0.48357088451265) + k * (-0.000336197177618394 * k + 0.484791561490776);
      data[i + 1] = 107.268039397724 + Cb * (2.19927104525741e-5 * Cb - 0.000640992018297945 * Cr + 0.000659397001245577 * Y + 0.000426105652938837 * k - 0.176491792462875) + Cr * (-0.000778269941513683 * Cr + 0.00130872261408275 * Y + 0.000770482631801132 * k - 0.151051492775562) + Y * (0.00126935368114843 * Y - 0.00265090189010898 * k + 0.25802910206845) + k * (-0.000318913117588328 * k - 0.213742400323665);
      data[i + 2] = -20.810012546947 + Cb * (-0.000570115196973677 * Cb - 2.63409051004589e-5 * Cr + 0.0020741088115012 * Y - 0.00288260236853442 * k + 0.814272968359295) + Cr * (-1.53496057440975e-5 * Cr - 0.000132689043961446 * Y + 0.000560833691242812 * k - 0.195152027534049) + Y * (0.00174418132927582 * Y - 0.00255243321439347 * k + 0.116935020465145) + k * (-0.000343531996510555 * k + 0.24165260232407);
      data[i + 3] = 255;
    }
    return data;
  }
  _convertYcckToCmyk(data) {
    let Y, Cb, Cr;
    for (let i = 0, length = data.length; i < length; i += 4) {
      Y = data[i];
      Cb = data[i + 1];
      Cr = data[i + 2];
      data[i] = 434.456 - Y - 1.402 * Cr;
      data[i + 1] = 119.541 - Y + 0.344 * Cb + 0.714 * Cr;
      data[i + 2] = 481.816 - Y - 1.772 * Cb;
    }
    return data;
  }
  _convertCmykToRgb(data) {
    let c, m, y, k;
    let offset = 0;
    for (let i = 0, length = data.length; i < length; i += 4) {
      c = data[i];
      m = data[i + 1];
      y = data[i + 2];
      k = data[i + 3];
      data[offset++] = 255 + c * (-0.00006747147073602441 * c + 0.0008379262121013727 * m + 0.0002894718188643294 * y + 0.003264231057537806 * k - 1.1185611867203937) + m * (0.000026374107616089405 * m - 0.00008626949158638572 * y - 0.0002748769067499491 * k - 0.02155688794978967) + y * (-0.00003878099212869363 * y - 0.0003267808279485286 * k + 0.0686742238595345) - k * (0.0003361971776183937 * k + 0.7430659151342254);
      data[offset++] = 255 + c * (0.00013596372813588848 * c + 0.000924537132573585 * m + 0.00010567359618683593 * y + 0.0004791864687436512 * k - 0.3109689587515875) + m * (-0.00023545346108370344 * m + 0.0002702845253534714 * y + 0.0020200308977307156 * k - 0.7488052167015494) + y * (0.00006834815998235662 * y + 0.00015168452363460973 * k - 0.09751927774728933) - k * (0.0003189131175883281 * k + 0.7364883807733168);
      data[offset++] = 255 + c * (0.000013598650411385307 * c + 0.00012423956175490851 * m + 0.0004751985097583589 * y - 0.0000036729317476630422 * k - 0.05562186980264034) + m * (0.00016141380598724676 * m + 0.0009692239130725186 * y + 0.0007782692450036253 * k - 0.44015232367526463) + y * (5.068882914068769e-7 * y + 0.0017778369011375071 * k - 0.7591454649749609) - k * (0.0003435319965105553 * k + 0.7063770186160144);
    }
    return data.subarray(0, offset);
  }
  _convertCmykToRgba(data) {
    for (let i = 0, length = data.length; i < length; i += 4) {
      const c = data[i];
      const m = data[i + 1];
      const y = data[i + 2];
      const k = data[i + 3];
      data[i] = 255 + c * (-0.00006747147073602441 * c + 0.0008379262121013727 * m + 0.0002894718188643294 * y + 0.003264231057537806 * k - 1.1185611867203937) + m * (0.000026374107616089405 * m - 0.00008626949158638572 * y - 0.0002748769067499491 * k - 0.02155688794978967) + y * (-0.00003878099212869363 * y - 0.0003267808279485286 * k + 0.0686742238595345) - k * (0.0003361971776183937 * k + 0.7430659151342254);
      data[i + 1] = 255 + c * (0.00013596372813588848 * c + 0.000924537132573585 * m + 0.00010567359618683593 * y + 0.0004791864687436512 * k - 0.3109689587515875) + m * (-0.00023545346108370344 * m + 0.0002702845253534714 * y + 0.0020200308977307156 * k - 0.7488052167015494) + y * (0.00006834815998235662 * y + 0.00015168452363460973 * k - 0.09751927774728933) - k * (0.0003189131175883281 * k + 0.7364883807733168);
      data[i + 2] = 255 + c * (0.000013598650411385307 * c + 0.00012423956175490851 * m + 0.0004751985097583589 * y - 0.0000036729317476630422 * k - 0.05562186980264034) + m * (0.00016141380598724676 * m + 0.0009692239130725186 * y + 0.0007782692450036253 * k - 0.44015232367526463) + y * (5.068882914068769e-7 * y + 0.0017778369011375071 * k - 0.7591454649749609) - k * (0.0003435319965105553 * k + 0.7063770186160144);
      data[i + 3] = 255;
    }
    return data;
  }
  getData({
    width,
    height,
    forceRGBA = false,
    forceRGB = false,
    isSourcePDF = false
  }) {
    if (this.numComponents > 4) {
      throw new JpegError("Unsupported color mode");
    }
    const data = this._getLinearizedBlockData(width, height, isSourcePDF);
    if (this.numComponents === 1 && (forceRGBA || forceRGB)) {
      const len = data.length * (forceRGBA ? 4 : 3);
      const rgbaData = new Uint8ClampedArray(len);
      let offset = 0;
      if (forceRGBA) {
        grayToRGBA(data, new Uint32Array(rgbaData.buffer));
      } else {
        for (const grayColor of data) {
          rgbaData[offset++] = grayColor;
          rgbaData[offset++] = grayColor;
          rgbaData[offset++] = grayColor;
        }
      }
      return rgbaData;
    } else if (this.numComponents === 3 && this._isColorConversionNeeded) {
      if (forceRGBA) {
        const rgbaData = new Uint8ClampedArray(data.length / 3 * 4);
        return this._convertYccToRgba(data, rgbaData);
      }
      return this._convertYccToRgb(data);
    } else if (this.numComponents === 4) {
      if (this._isColorConversionNeeded) {
        if (forceRGBA) {
          return this._convertYcckToRgba(data);
        }
        if (forceRGB) {
          return this._convertYcckToRgb(data);
        }
        return this._convertYcckToCmyk(data);
      } else if (forceRGBA) {
        return this._convertCmykToRgba(data);
      } else if (forceRGB) {
        return this._convertCmykToRgb(data);
      }
    }
    return data;
  }
}

;// CONCATENATED MODULE: ./src/core/jpeg_stream.js




class JpegStream extends DecodeStream {
  constructor(stream, maybeLength, params) {
    super(maybeLength);
    this.stream = stream;
    this.dict = stream.dict;
    this.maybeLength = maybeLength;
    this.params = params;
  }
  get bytes() {
    return shadow(this, "bytes", this.stream.getBytes(this.maybeLength));
  }
  ensureBuffer(requested) {}
  readBlock() {
    this.decodeImage();
  }
  decodeImage(bytes) {
    if (this.eof) {
      return this.buffer;
    }
    bytes ||= this.bytes;
    for (let i = 0, ii = bytes.length - 1; i < ii; i++) {
      if (bytes[i] === 0xff && bytes[i + 1] === 0xd8) {
        if (i > 0) {
          bytes = bytes.subarray(i);
        }
        break;
      }
    }
    const jpegOptions = {
      decodeTransform: undefined,
      colorTransform: undefined
    };
    const decodeArr = this.dict.getArray("D", "Decode");
    if ((this.forceRGBA || this.forceRGB) && Array.isArray(decodeArr)) {
      const bitsPerComponent = this.dict.get("BPC", "BitsPerComponent") || 8;
      const decodeArrLength = decodeArr.length;
      const transform = new Int32Array(decodeArrLength);
      let transformNeeded = false;
      const maxValue = (1 << bitsPerComponent) - 1;
      for (let i = 0; i < decodeArrLength; i += 2) {
        transform[i] = (decodeArr[i + 1] - decodeArr[i]) * 256 | 0;
        transform[i + 1] = decodeArr[i] * maxValue | 0;
        if (transform[i] !== 256 || transform[i + 1] !== 0) {
          transformNeeded = true;
        }
      }
      if (transformNeeded) {
        jpegOptions.decodeTransform = transform;
      }
    }
    if (this.params instanceof Dict) {
      const colorTransform = this.params.get("ColorTransform");
      if (Number.isInteger(colorTransform)) {
        jpegOptions.colorTransform = colorTransform;
      }
    }
    const jpegImage = new JpegImage(jpegOptions);
    jpegImage.parse(bytes);
    const data = jpegImage.getData({
      width: this.drawWidth,
      height: this.drawHeight,
      forceRGBA: this.forceRGBA,
      forceRGB: this.forceRGB,
      isSourcePDF: true
    });
    this.buffer = data;
    this.bufferLength = data.length;
    this.eof = true;
    return this.buffer;
  }
  get canAsyncDecodeImageFromBuffer() {
    return this.stream.isAsync;
  }
}

;// CONCATENATED MODULE: ./external/openjpeg/openjpeg.js
var OpenJPEG = (() => {
  var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;
  return function (moduleArg = {}) {
    var moduleRtn;
    var Module = moduleArg;
    var readyPromiseResolve, readyPromiseReject;
    var readyPromise = new Promise((resolve, reject) => {
      readyPromiseResolve = resolve;
      readyPromiseReject = reject;
    });
    var ENVIRONMENT_IS_WEB = true;
    var ENVIRONMENT_IS_WORKER = false;
    Module.decode = function (bytes, {
      numComponents = 4,
      isIndexedColormap = false,
      smaskInData = false
    }) {
      const size = bytes.length;
      const ptr = Module._malloc(size);
      Module.HEAPU8.set(bytes, ptr);
      const ret = Module._jp2_decode(ptr, size, numComponents > 0 ? numComponents : 0, !!isIndexedColormap, !!smaskInData);
      Module._free(ptr);
      if (ret) {
        const {
          errorMessages: errorMessages
        } = Module;
        if (errorMessages) {
          delete Module.errorMessages;
          return errorMessages;
        }
        return "Unknown error";
      }
      const {
        imageData: imageData
      } = Module;
      Module.imageData = null;
      return imageData;
    };
    var moduleOverrides = Object.assign({}, Module);
    var arguments_ = [];
    var thisProgram = "./this.program";
    var quit_ = (status, toThrow) => {
      throw toThrow;
    };
    var scriptDirectory = "";
    var read_, readAsync, readBinary;
    if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
      if (ENVIRONMENT_IS_WORKER) {
        scriptDirectory = self.location.href;
      } else if (typeof document != "undefined" && document.currentScript) {
        scriptDirectory = document.currentScript.src;
      }
      if (_scriptName) {
        scriptDirectory = _scriptName;
      }
      if (scriptDirectory.startsWith("blob:")) {
        scriptDirectory = "";
      } else {
        scriptDirectory = scriptDirectory.substr(0, scriptDirectory.replace(/[?#].*/, "").lastIndexOf("/") + 1);
      }
      read_ = url => {
        var xhr = new XMLHttpRequest();
        xhr.open("GET", url, false);
        xhr.send(null);
        return xhr.responseText;
      };
      if (ENVIRONMENT_IS_WORKER) {
        readBinary = url => {
          var xhr = new XMLHttpRequest();
          xhr.open("GET", url, false);
          xhr.responseType = "arraybuffer";
          xhr.send(null);
          return new Uint8Array(xhr.response);
        };
      }
      readAsync = (url, onload, onerror) => {
        fetch(url, {
          credentials: "same-origin"
        }).then(response => {
          if (response.ok) {
            return response.arrayBuffer();
          }
          return Promise.reject(new Error(response.status + " : " + response.url));
        }).then(onload, onerror);
      };
    } else {}
    var out = Module["print"] || console.log.bind(console);
    var err = Module["printErr"] || console.error.bind(console);
    Object.assign(Module, moduleOverrides);
    moduleOverrides = null;
    if (Module["arguments"]) arguments_ = Module["arguments"];
    if (Module["thisProgram"]) thisProgram = Module["thisProgram"];
    if (Module["quit"]) quit_ = Module["quit"];
    var wasmBinary;
    if (Module["wasmBinary"]) wasmBinary = Module["wasmBinary"];
    function intArrayFromBase64(s) {
      var decoded = atob(s);
      var bytes = new Uint8Array(decoded.length);
      for (var i = 0; i < decoded.length; ++i) {
        bytes[i] = decoded.charCodeAt(i);
      }
      return bytes;
    }
    function tryParseAsDataURI(filename) {
      if (!isDataURI(filename)) {
        return;
      }
      return intArrayFromBase64(filename.slice(dataURIPrefix.length));
    }
    var wasmMemory;
    var ABORT = false;
    var HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32, HEAPF32, HEAPF64;
    function updateMemoryViews() {
      var b = wasmMemory.buffer;
      Module["HEAP8"] = HEAP8 = new Int8Array(b);
      Module["HEAP16"] = HEAP16 = new Int16Array(b);
      Module["HEAPU8"] = HEAPU8 = new Uint8Array(b);
      Module["HEAPU16"] = HEAPU16 = new Uint16Array(b);
      Module["HEAP32"] = HEAP32 = new Int32Array(b);
      Module["HEAPU32"] = HEAPU32 = new Uint32Array(b);
      Module["HEAPF32"] = HEAPF32 = new Float32Array(b);
      Module["HEAPF64"] = HEAPF64 = new Float64Array(b);
    }
    var __ATPRERUN__ = [];
    var __ATINIT__ = [];
    var __ATPOSTRUN__ = [];
    var runtimeInitialized = false;
    function preRun() {
      if (Module["preRun"]) {
        if (typeof Module["preRun"] == "function") Module["preRun"] = [Module["preRun"]];
        while (Module["preRun"].length) {
          addOnPreRun(Module["preRun"].shift());
        }
      }
      callRuntimeCallbacks(__ATPRERUN__);
    }
    function initRuntime() {
      runtimeInitialized = true;
      callRuntimeCallbacks(__ATINIT__);
    }
    function postRun() {
      if (Module["postRun"]) {
        if (typeof Module["postRun"] == "function") Module["postRun"] = [Module["postRun"]];
        while (Module["postRun"].length) {
          addOnPostRun(Module["postRun"].shift());
        }
      }
      callRuntimeCallbacks(__ATPOSTRUN__);
    }
    function addOnPreRun(cb) {
      __ATPRERUN__.unshift(cb);
    }
    function addOnInit(cb) {
      __ATINIT__.unshift(cb);
    }
    function addOnPostRun(cb) {
      __ATPOSTRUN__.unshift(cb);
    }
    var runDependencies = 0;
    var runDependencyWatcher = null;
    var dependenciesFulfilled = null;
    function addRunDependency(id) {
      runDependencies++;
      Module["monitorRunDependencies"]?.(runDependencies);
    }
    function removeRunDependency(id) {
      runDependencies--;
      Module["monitorRunDependencies"]?.(runDependencies);
      if (runDependencies == 0) {
        if (runDependencyWatcher !== null) {
          clearInterval(runDependencyWatcher);
          runDependencyWatcher = null;
        }
        if (dependenciesFulfilled) {
          var callback = dependenciesFulfilled;
          dependenciesFulfilled = null;
          callback();
        }
      }
    }
    var dataURIPrefix = "data:application/octet-stream;base64,";
    var isDataURI = filename => filename.startsWith(dataURIPrefix);
    function findWasmBinary() {
      var f = "data:application/octet-stream;base64,AGFzbQEAAAABzgEaYAN/f38Bf2AEf39/fwF/YAF/AGACf38AYAF/AX9gA39/fwBgAn9/AX9gBH9/f38AYAN/fn8BfmAFf39/f38Bf2ACfn8Bf2ACfn8BfmAFf39/f38AYAN/fn8Bf2AAAX9gB39/f39/f38Bf2AJf39/f39/f39/AX9gC39/f39/f39/f39/AX9gBn9/f39/fwF/YAZ/fH9/f38Bf2AIf39/f39/f38AYAh/f39/f39/fwF/YAAAYAZ/f39/f38AYAd/f39/f39/AGACfH8BfAJbDwFhAWEAAgFhAWIAAQFhAWMABQFhAWQAAgFhAWUADAFhAWYABwFhAWcAAwFhAWgABwFhAWkABQFhAWoACQFhAWsABAFhAWwABgFhAW0ABgFhAW4ABAFhAW8AAwPAAb4BBwIFAAYEAAUGBAUBBAwFFAYCAgICAAYQEQQCChICBQIEBwQCDgICDQYCFQMHAAAEAwEWCQkDAAkGAQQEBQUODwEBAwADBgIQBBcYAgcGAwcHAQECAAQZBAYHBA8MAAQCAgIABgAGAQEBAQEBAQEAAAAAAAYDAgICAwMDAwMAAxMIBA4EAAgDAwkECAoLCAAAAQEBAQEBAQENAQAEBAUJDwESEQEAAAYDAwEFBQUFBQUFBQELAQEBAQEBAQEBCgQFAXABbm4FBwEBggKAgAIGCAF/AUGQ2QULBxsGAXACAAFxAEEBcgCYAQFzABABdAEAAXUAlwEJvQEBAEEBC21RzAHCAXNzNqcBnAGZAYsBigGJAYgBhwGGAYUBhAFSgQGAAX9+fXx7enl4d3Z1ywHKAckByAHHAcYBQMUBxAFAQMMBwQHAAb8BvgG9AbwBuwG6AbkBswGoAaYBpQGkAaMBogGhAaABnwGeAZ0BmwGaAUlKTFJIgwFTOFCCAU9FRk4rJ6sBqgGsAbQBuAG1Aa8BqQGtAa4BtgG3AXCwAbEBsgFRlgGVAYwBjgGNAZIBkwGUAZABjwEKkZoOvgGCAgEDfyMAQZAEayIEJAACQCAARQ0AAkACQAJAAkAgAUEBaw4EAAEEAgQLIABBDGohAQwCCyAAQRBqIQEgAEEEaiEADAELIABBFGohASAAQQhqIQALIAEoAgAiBUUNACACRQ0AIAAoAgAhBiAEQQBBgAQQFSIBIAM2AowEIwBBoAFrIgAkACAAIAE2ApQBIABB/wM2ApgBIABBAEGQARAVIgBBfzYCTCAAQeYANgIkIABBfzYCUCAAIABBnwFqNgIsIAAgAEGUAWo2AlQgAUEAOgAAIAAgAiADQecAQegAEGsgAEGgAWokACABQQA6AP8DIAEgBiAFEQMACyAEQZAEaiQAC9ACAQV/IAAEQCAAQQRrIgMoAgAiBCEBIAMhAiAAQQhrKAIAIgAgAEF+cSIARwRAIAIgAGsiAigCBCIBIAIoAggiBTYCCCAFIAE2AgQgACAEaiEBCyADIARqIgAoAgAiAyAAIANqQQRrKAIARwRAIAAoAgQiBCAAKAIIIgA2AgggACAENgIEIAEgA2ohAQsgAiABNgIAIAIgAUF8cWpBBGsgAUEBcjYCACACAn8gAigCAEEIayIAQf8ATQRAIABBA3ZBAWsMAQsgAGchAyAAQR0gA2t2QQRzIANBAnRrQe4AaiAAQf8fTQ0AGkE/IABBHiADa3ZBAnMgA0EBdGtBxwBqIgAgAEE/TxsLIgFBBHQiAEGgxwFqNgIEIAIgAEGoxwFqIgAoAgA2AgggACACNgIAIAIoAgggAjYCBEGozwFBqM8BKQMAQgEgAa2GhDcDAAsLyQIBBH8gAUEANgIAAkAgAkUNACABIAJqIQMCQCACQRBJBEAgACEBDAELAkAgACACaiABTQ0AIAAgA08NACAAIQEMAQsgA0EQayEGIAAgAkFwcSIFaiEBIAMgBWshAwNAIAYgBGsgACAEav0AAAD9DAAAAAAAAAAAAAAAAAAAAAD9DQ8ODQwLCgkIBwYFBAMCAQD9CwAAIARBEGoiBCAFRw0ACyACIAVGDQELAkAgAkEDcSIGRQRAIAUhBAwBC0EAIQAgBSEEA0AgA0EBayIDIAEtAAA6AAAgBEEBaiEEIAFBAWohASAAQQFqIgAgBkcNAAsLIAUgAmtBfEsNAANAIANBAWsgAS0AADoAACADQQJrIAEtAAE6AAAgA0EDayABLQACOgAAIANBBGsiAyABLQADOgAAIAFBBGohASAEQQRqIgQgAkcNAAsLC4AEAQN/IAJBgARPBEAgACABIAIQAiAADwsgACACaiEDAkAgACABc0EDcUUEQAJAIABBA3FFBEAgACECDAELIAJFBEAgACECDAELIAAhAgNAIAIgAS0AADoAACABQQFqIQEgAkEBaiICQQNxRQ0BIAIgA0kNAAsLAkAgA0F8cSIEQcAASQ0AIAIgBEFAaiIFSw0AA0AgAiABKAIANgIAIAIgASgCBDYCBCACIAEoAgg2AgggAiABKAIMNgIMIAIgASgCEDYCECACIAEoAhQ2AhQgAiABKAIYNgIYIAIgASgCHDYCHCACIAEoAiA2AiAgAiABKAIkNgIkIAIgASgCKDYCKCACIAEoAiw2AiwgAiABKAIwNgIwIAIgASgCNDYCNCACIAEoAjg2AjggAiABKAI8NgI8IAFBQGshASACQUBrIgIgBU0NAAsLIAIgBE8NAQNAIAIgASgCADYCACABQQRqIQEgAkEEaiICIARJDQALDAELIANBBEkEQCAAIQIMAQsgACADQQRrIgRLBEAgACECDAELIAAhAgNAIAIgAS0AADoAACACIAEtAAE6AAEgAiABLQACOgACIAIgAS0AAzoAAyABQQRqIQEgAkEEaiICIARNDQALCyACIANJBEADQCACIAEtAAA6AAAgAUEBaiEBIAJBAWoiAiADRw0ACwsgAAswAQF/AkAgAEUNACABRQ0AQQggACABbCIBECUiAARAIABBACABEBUaCyAAIQILIAILEQAgAEUEQEEADwtBCCAAECUL8gICAn8BfgJAIAJFDQAgACABOgAAIAAgAmoiA0EBayABOgAAIAJBA0kNACAAIAE6AAIgACABOgABIANBA2sgAToAACADQQJrIAE6AAAgAkEHSQ0AIAAgAToAAyADQQRrIAE6AAAgAkEJSQ0AIABBACAAa0EDcSIEaiIDIAFB/wFxQYGChAhsIgE2AgAgAyACIARrQXxxIgRqIgJBBGsgATYCACAEQQlJDQAgAyABNgIIIAMgATYCBCACQQhrIAE2AgAgAkEMayABNgIAIARBGUkNACADIAE2AhggAyABNgIUIAMgATYCECADIAE2AgwgAkEQayABNgIAIAJBFGsgATYCACACQRhrIAE2AgAgAkEcayABNgIAIAQgA0EEcUEYciIEayICQSBJDQAgAa1CgYCAgBB+IQUgAyAEaiEBA0AgASAFNwMYIAEgBTcDECABIAU3AwggASAFNwMAIAFBIGohASACQSBrIgJBH0sNAAsLIAALJwEBfyMAQRBrIgMkACADIAI2AgwgACABIAJBAEEAEGsgA0EQaiQAC+gFAQl/IAFFBEBBAA8LAn8gAEUEQEEIIAEQJQwBCyABRQRAIAAQEEEADAELAkAgAUFHSw0AIAACf0EIIAFBA2pBfHEgAUEITRsiB0EIaiEBAkACfwJAIABBBGsiCiIEKAIAIgUgBGoiAigCACIJIAIgCWoiCEEEaygCAEcEQCAIIAEgBGoiA0EQak8EQCACKAIEIgUgAigCCCICNgIIIAIgBTYCBCADIAggA2siAjYCACADIAJBfHFqQQRrIAJBAXI2AgAgAwJ/IAMoAgBBCGsiAkH/AE0EQCACQQN2QQFrDAELIAJBHSACZyIFa3ZBBHMgBUECdGtB7gBqIAJB/x9NDQAaQT8gAkEeIAVrdkECcyAFQQF0a0HHAGoiAiACQT9PGwsiAkEEdCIFQaDHAWo2AgQgAyAFQajHAWoiBSgCADYCCCAFIAM2AgAgAygCCCADNgIEQajPAUGozwEpAwBCASACrYaENwMAIAQgATYCAAwECyADIAhLDQEgAigCBCIBIAIoAggiAzYCCCADIAE2AgQgBCAFIAlqIgE2AgAMAwsgBSABQRBqTwRAIAQgATYCACAEIAFBfHFqQQRrIAE2AgAgASAEaiIDIAUgAWsiATYCACADIAFBfHFqQQRrIAFBAXI2AgAgAwJ/IAMoAgBBCGsiAUH/AE0EQCABQQN2QQFrDAELIAFBHSABZyIEa3ZBBHMgBEECdGtB7gBqIAFB/x9NDQAaQT8gAUEeIARrdkECcyAEQQF0a0HHAGoiASABQT9PGwsiAUEEdCIEQaDHAWo2AgQgAyAEQajHAWoiBCgCADYCCCAEIAM2AgAgAygCCCADNgIEQajPAUGozwEpAwBCASABrYaENwMAQQEMBAtBASABIAVNDQEaC0EACwwBCyAEIAFBfHFqQQRrIAE2AgBBAQsNARpBCCAHECUiAUUNACABIAAgByAKKAIAQQhrIgYgBiAHSxsQEhogABAQIAEhBgsgBgsLNwECfyMAQRBrIgEkACAABH8gAUEMakEQIAAQbCEAQQAgASgCDCAAGwVBAAshAiABQRBqJAAgAgsXACAALQAAQSBxRQRAIAEgAiAAED0aCwu8BAEFfyACIAAoAjAiBU0EQCABIAAoAiQgAhASGiAAIAAoAiQgAmo2AiQgACAAKAIwIAJrNgIwIAAgACkDOCACrXw3AzggAg8LIAAtAERBBHEEQCABIAAoAiQgBRASGiAAKAIwIQEgAEEANgIwIAAgASAAKAIkajYCJCAAIAApAzggAa18NwM4IAVBfyAFGw8LAkAgBQRAIAEgACgCJCAFEBIhBCAAIAAoAiAiBzYCJCAAKAIwIQEgAEEANgIwIAAgACkDOCABrXw3AzggAiABayECIAEgBGohAQwBCyAAIAAoAiAiBzYCJAsCQAJAA0ACQCAAKAIAIQQgACgCECEGAkAgACgCQCIIIAJLBEAgACAHIAggBCAGEQAAIgY2AjAgBkF/RgRADAYLIAIgBk0NAiABIAAoAiQgBhASGiAAIAAoAiAiBzYCJCAAKAIwIQQMAQsgACABIAIgBCAGEQAAIgQ2AjAgBEF/RgRADAULIAIgBE0NAyAAIAAoAiAiBzYCJCAEIQYLIABBADYCMCAAIAApAzggBK18NwM4IAEgBGohASACIARrIQIgBSAGaiEFDAELCyABIAAoAiQgAhASGiAAIAAoAiQgAmo2AiQgACAAKAIwIAJrNgIwIAAgACkDOCACrXw3AzggAiAFag8LIABBADYCMCAAIAAoAiA2AiQgACAAKQM4IAStfDcDOCAEIAVqDwsgA0EEQZv1AEEAEA8gAEEANgIwIAAgACgCREEEcjYCRCAFQX8gBRsLiwcCDX8BfiAAKAIQIgdBIE8EQCAAKQMIpw8LAkAgACgCGCICQQROBEAgACgCACIBKAIAIQQgACACQQRrIgU2AhggACABQQRqNgIADAELQX9BACAAKAIcGyEEIAJBAEwEQCACIQUMAQsgAkEBcSEMIAAoAgAhAQJAIAJBAUYEQCABIQYMAQsgAkH+////B3EhCgNAIAAgAUEBajYCACABLQAAIQkgACABQQJqIgY2AgAgACACQQFrNgIYIAEtAAEhASAAIAJBAmsiAjYCGCAEQf8BIAN0QX9zcSAJIAN0ckGA/gMgA3RBf3NxIAEgA0EIcnRyIQQgA0EQaiEDIAYhASAFQQJqIgUgCkcNAAsLQQAhBSAMRQ0AIAAgBkEBajYCACAGLQAAIQEgACACQQFrNgIYIARB/wEgA3RBf3NxIAEgA3RyIQQLIAAoAhQhASAAIARBGHYiCkH/AUY2AhQgAEEHQQggARsiAUEHQQggBEH/AXEiBkH/AUYbaiICQQdBCCAEQQh2Qf8BcSIDQf8BRhtqIglBB0EIIARBEHZB/wFxIgRB/wFGGyAHamoiCDYCECAAIAApAwggAyABdCAEIAJ0ciAKIAl0ciAGcq0gB62GhCIONwMIIAhBH00EQAJAIAVBBE4EQCAAKAIAIgEoAgAhAiAAIAVBBGs2AhggACABQQRqNgIADAELQQAhA0F/QQAgACgCHBshAiAFQQBMDQAgBUEBcSENIAAoAgAhAQJAIAVBAUYEQCABIQQMAQsgBUH+////B3EhCUEAIQYDQCAAIAFBAWo2AgAgAS0AACELIAAgAUECaiIENgIAIAAgBUEBazYCGCABLQABIQEgACAFQQJrIgU2AhggAkH/ASADdEF/c3EgCyADdHJBgP4DIAN0QX9zcSABIANBCHJ0ciECIANBEGohAyAEIQEgBkECaiIGIAlHDQALCyANRQ0AIAAgBEEBajYCACAELQAAIQEgACAFQQFrNgIYIAJB/wEgA3RBf3NxIAEgA3RyIQILIAAgAkEYdiIBQf8BRjYCFCAAQQdBCCAKQf8BRhsiBEEHQQggAkH/AXEiBkH/AUYbaiIFQQdBCCACQQh2Qf8BcSIDQf8BRhtqIgdBB0EIIAJBEHZB/wFxIgJB/wFGGyAIamo2AhAgACADIAR0IAIgBXRyIAEgB3RyIAZyrSAIrYYgDoQiDjcDCAsgDqcLawEBfyMAQYACayIFJAACQCACIANMDQAgBEGAwARxDQAgBSABIAIgA2siA0GAAiADQYACSSIBGxAVGiABRQRAA0AgACAFQYACEBkgA0GAAmsiA0H/AUsNAAsLIAAgBSADEBkLIAVBgAJqJAALMQAgAQJ/IAIoAkxBAEgEQCAAIAEgAhA9DAELIAAgASACED0LIgBGBEAPCyAAIAFuGgsXACAAIAEgAiADIAQgBSAGIAdBARAmGguhAQEEfyABQQBMBEBBAA8LIAAoAgwhAiAAKAIQIQMDQCABIQUCQCADDQAgACACQQh0QYD+A3EiAjYCDCAAQQdBCCACQYD+A0YbIgM2AhAgACgCCCIBIAAoAgRPDQAgACABQQFqNgIIIAAgAiABLQAAciICNgIMCyAAIANBAWsiAzYCECACIAN2QQFxIAVBAWsiAXQgBHIhBCAFQQFLDQALIAQLHgAgACgCDARAIABBADYCKANAIAAoAhhBAEoNAAsLC2oBA38gAARAIAAoAhgiAQRAIAAoAhAiAgR/QQAhAQNAIAAoAhggAUE0bGooAiwiAwRAIAMQECAAKAIQIQILIAFBAWoiASACSQ0ACyAAKAIYBSABCxAQCyAAKAIcIgEEQCABEBALIAAQEAsLkhUBD38CQAJAIAAoAgxFBEBBASEPIAAoAgRBAEoNASAAKAIIQQFKDQEMAgtBASENIAAoAghBAEoNACAAKAIEQQJIDQELIAAoAgAiCCANQQV0aiEEAkAgACgCECIHIAAoAhQiCk8NACAEIAdBBnRqIQECQCAKIAdrQQNxIgZFBEAgByECDAELIAchAgNAIAEgAf0ABAD9DFh2nT9Ydp0/WHadP1h2nT/95gH9CwQAIAEgAf0ABBD9DFh2nT9Ydp0/WHadP1h2nT/95gH9CwQQIAFBQGshASACQQFqIQIgA0EBaiIDIAZHDQALCyAHIAprQXxLDQADQCABIAH9AAQA/QxYdp0/WHadP1h2nT9Ydp0//eYB/QsEACABIAH9AAQQ/QxYdp0/WHadP1h2nT9Ydp0//eYB/QsEECABIAH9AARA/QxYdp0/WHadP1h2nT9Ydp0//eYB/QsEQCABIAH9AARQ/QxYdp0/WHadP1h2nT9Ydp0//eYB/QsEUCABIAH9AASAAf0MWHadP1h2nT9Ydp0/WHadP/3mAf0LBIABIAEgAf0ABJAB/QxYdp0/WHadP1h2nT9Ydp0//eYB/QsEkAEgASAB/QAEwAH9DFh2nT9Ydp0/WHadP1h2nT/95gH9CwTAASABIAH9AATQAf0MWHadP1h2nT9Ydp0/WHadP/3mAf0LBNABIAFBgAJqIQEgAkEEaiICIApHDQALCyAIIA9BBXRqIQUCQCAAKAIYIgYgACgCHCILTw0AIAUgBkEGdGohAQJAIAsgBmtBA3EiCEUEQCAGIQIMAQtBACEDIAYhAgNAIAEgAf0ABAD9DAAY0D8AGNA/ABjQPwAY0D/95gH9CwQAIAEgAf0ABBD9DAAY0D8AGNA/ABjQPwAY0D/95gH9CwQQIAFBQGshASACQQFqIQIgA0EBaiIDIAhHDQALCyAGIAtrQXxLDQADQCABIAH9AAQA/QwAGNA/ABjQPwAY0D8AGNA//eYB/QsEACABIAH9AAQQ/QwAGNA/ABjQPwAY0D8AGNA//eYB/QsEECABIAH9AARA/QwAGNA/ABjQPwAY0D8AGNA//eYB/QsEQCABIAH9AARQ/QwAGNA/ABjQPwAY0D8AGNA//eYB/QsEUCABIAH9AASAAf0MABjQPwAY0D8AGNA/ABjQP/3mAf0LBIABIAEgAf0ABJAB/QwAGNA/ABjQPwAY0D8AGNA//eYB/QsEkAEgASAB/QAEwAH9DAAY0D8AGNA/ABjQPwAY0D/95gH9CwTAASABIAH9AATQAf0MABjQPwAY0D8AGNA/ABjQP/3mAf0LBNABIAFBgAJqIQEgAkEEaiICIAtHDQALCyAKIAAoAggiCSAAKAIEIg4gDWsiACAAIAlKGyIIIAggCksbIQwgBEEgaiEBAn8gB0UEQCAMRQRAQQAhAyABDAILIAQgBP0ABAAgBf0ABAAgBP0ABCD95AH9DFUT4z5VE+M+VRPjPlUT4z795gH95QH9CwQAIAQgBP0ABBAgBf0ABBAgBP0ABDD95AH9DFUT4z5VE+M+VRPjPlUT4z795gH95QH9CwQQQQEhAyAEQeAAagwBCyABIAciA0EGdGoLIQIgAyAMSQRAA0AgAkEgayIAIAD9AAQAIAJBQGr9AAQAIAL9AAQA/eQB/QxVE+M+VRPjPlUT4z5VE+M+/eYB/eUB/QsEACACQRBrIgAgAP0ABAAgAkEwa/0ABAAgAv0ABBD95AH9DFUT4z5VE+M+VRPjPlUT4z795gH95QH9CwQAIAJBQGshAiADQQFqIgMgDEcNAAsLIAggCk8iDUUEQCACQSBrIgAgAP0ABAAgAkFAav0ABAD9DFUTYz9VE2M/VRNjP1UTYz/95gH95QH9CwQAIAJBEGsiACAA/QAEACACQTBr/QAEAP0MVRNjP1UTYz9VE2M/VRNjP/3mAf3lAf0LBAALIAsgDiAJIA9rIgAgACAOShsiDiALIA5JGyEJIAVBIGohAiAJAn8gBkUEQCAJRQRAIAIhA0EADAILIAUgBf0ABAAgBP0ABAAgBf0ABCD95AH9DHYGYj92BmI/dgZiP3YGYj/95gH95QH9CwQAIAUgBf0ABBAgBP0ABBAgBf0ABDD95AH9DHYGYj92BmI/dgZiP3YGYj/95gH95QH9CwQQIAVB4ABqIQNBAQwBCyACIAZBBnRqIQMgBgsiAEsEQANAIANBIGsiCCAI/QAEACADQUBq/QAEACAD/QAEAP3kAf0MdgZiP3YGYj92BmI/dgZiP/3mAf3lAf0LBAAgA0EQayIIIAj9AAQAIANBMGv9AAQAIAP9AAQQ/eQB/Qx2BmI/dgZiP3YGYj92BmI//eYB/eUB/QsEACADQUBrIQMgAEEBaiIAIAlHDQALCyALIA5NIghFBEAgA0EgayIAIAD9AAQAIANBQGr9AAQA/Qx2BuI/dgbiP3YG4j92BuI//eYB/eUB/QsEACADQRBrIgAgAP0ABAAgA0Ewa/0ABAD9DHYG4j92BuI/dgbiP3YG4j/95gH95QH9CwQACwJAIAdFBEAgDEUEQEEAIQcMAgsgBCAE/QAEACAF/QAEACAE/QAEIP3kAf0MrgFZPa4BWT2uAVk9rgFZPf3mAf3kAf0LBAAgBCAE/QAEECAF/QAEECAE/QAEMP3kAf0MrgFZPa4BWT2uAVk9rgFZPf3mAf3kAf0LBBAgBEHgAGohAUEBIQcMAQsgASAHQQZ0aiEBCyAHIAxJBEADQCABQSBrIgAgAP0ABAAgAUFAav0ABAAgAf0ABAD95AH9DK4BWT2uAVk9rgFZPa4BWT395gH95AH9CwQAIAFBEGsiACAA/QAEACABQTBr/QAEACAB/QAEEP3kAf0MrgFZPa4BWT2uAVk9rgFZPf3mAf3kAf0LBAAgAUFAayEBIAdBAWoiByAMRw0ACwsgDUUEQCABQSBrIgAgAP0ABAAgAUFAav0ABAD9DK4B2T2uAdk9rgHZPa4B2T395gH95AH9CwQAIAFBEGsiACAA/QAEACABQTBr/QAEAP0MrgHZPa4B2T2uAdk9rgHZPf3mAf3kAf0LBAALAkAgBkUEQCAJRQRAQQAhBgwCCyAFIAX9AAQAIAT9AAQAIAX9AAQg/eQB/QxzBss/cwbLP3MGyz9zBss//eYB/eQB/QsEACAFIAX9AAQQIAT9AAQQIAX9AAQw/eQB/QxzBss/cwbLP3MGyz9zBss//eYB/eQB/QsEECAFQeAAaiECQQEhBgwBCyACIAZBBnRqIQILIAYgCUkEQANAIAJBIGsiACAA/QAEACACQUBq/QAEACAC/QAEAP3kAf0McwbLP3MGyz9zBss/cwbLP/3mAf3kAf0LBAAgAkEQayIAIAD9AAQAIAJBMGv9AAQAIAL9AAQQ/eQB/QxzBss/cwbLP3MGyz9zBss//eYB/eQB/QsEACACQUBrIQIgBkEBaiIGIAlHDQALCyAIDQAgAkEgayIAIAD9AAQAIAJBQGr9AAQA/QxzBktAcwZLQHMGS0BzBktA/eYB/eQB/QsEACACQRBrIgAgAP0ABAAgAkEwa/0ABAD9DHMGS0BzBktAcwZLQHMGS0D95gH95AH9CwQACwtdAQR/IAAEQCAAKAIUIgEgACgCECICbARAA0AgACgCGCADQQJ0aigCACIEBEAgBBAQIAAoAhAhAiAAKAIUIQELIANBAWoiAyABIAJsSQ0ACwsgACgCGBAQIAAQEAsLhQEBAn8CQAJAIAAoAgQiAyAAKAIAIgRHBEAgACgCCCEDDAELIAAgA0EKaiIENgIEIAAoAgggBEECdBAXIgNFDQEgACADNgIIIAAoAgAhBAsgAyAEQQJ0aiABNgIAIAAgBEEBajYCAEEBDwsgACgCCBAQIABCADcCACACQQFB0i5BABAPQQALkwQCBn8CfgJAAkADQCAAIABBAWtxDQEgAUFHSw0BIABBCCAAQQhLIgcbIQBBqM8BKQMAIggCf0EIIAFBA2pBfHEgAUEITRsiAUH/AE0EQCABQQN2QQFrDAELIAFnIQMgAUEdIANrdkEEcyADQQJ0a0HuAGogAUH/H00NABpBPyABQR4gA2t2QQJzIANBAXRrQccAaiIDIANBP08bCyIDrYgiCUIAUgRAA0AgCSAJeiIIiCEJAn4gAyAIp2oiA0EEdCIEQajHAWooAgAiAiAEQaDHAWoiBUcEQCACIAAgARA8IgQNBiACKAIEIgQgAigCCCIGNgIIIAYgBDYCBCACIAU2AgggAiAFKAIENgIEIAUgAjYCBCACKAIEIAI2AgggA0EBaiEDIAlCAYgMAQtBqM8BQajPASkDAEJ+IAOtiYM3AwAgCUIBhQsiCUIAUg0AC0GozwEpAwAhCAtBPyAIeadrIQUCQCAIUARAQQAhAgwBCyAFQQR0IgRBqMcBaigCACECIAhCgICAgARUDQBB4wAhAyACIARBoMcBaiIGRg0AA0AgA0UNASACIAAgARA8IgQNBCADQQFrIQMgAigCCCICIAZHDQALCyABIABBMGpBMCAHG2oQbQ0ACyACRQ0AIAIgBUEEdEGgxwFqIgNGDQADQCACIAAgARA8IgQNAiACKAIIIgIgA0cNAAsLQQAhBAsgBAvaIwIrfwN7AkAgACgCACIJIANJDQAgASADTw0AIAEgCU8NACAAKAIEIgkgBEkNACACIARPDQAgAiAJTw0AIAVBHGshJyAAKAIIIhlBAnQhESAHQQJ0IQ8gBkECdCEfIAVBBGshKCACIAAoAgxuIR4gGSAZIAEgGW4iKWwgAWtqISogBkEIRyEjIAIhHQNAIAAoAgwiCSEKIAIgHUYEQCAJIAIgCXBrIQoLIAogBCAdayIMIAogDEkbIhNBfHEhGyATQQNxIRYgE0F4cSErIBNBB3EhJCATQQFrIRogGSAJQQJ0IApBAnRrQQRqbCEgIAZBAkYgE0EBRnEhLCAJIAprIBlsISUgJyAPIB0gAmsiDGwiCWohJiAJIChqIS0gBSAJaiEuIAUgByAMbEECdGohHCApISEgASEYA0AgKiAZIAEgGEYbIgwgAyAYayIJIAkgDEsbIRAgGSAMayEJICFBAnQiDSAAKAIYIAAoAhAgHmxBAnRqaigCACESAkACQCAIBEACQAJAAkACQAJAIBIEQCASICVBAnRqIAlBAnRqIQogGCABayENIAZBAUYNBCAcIAYgDWxBAnRqIQsgEEEBRg0DICwNAiAjDQEgEEEHTQ0BIBNFDQggJiANIB9saiAQQQV0aiEVIBIgICAQQQJ0aiAMQQJ0a2ohIiAQQXxxIQ1BACESDAULIAZBAUcEQCATRQ0IIBBBfHEhDSAQQQNxIQwgHCAYIAFrIAZsQQJ0aiELQQAhEiAQQQFrQQNJIRQDQAJAIBBFDQBBACEJQQAhCkEAIQ4gFEUEQANAIAsgBiAKbEECdGpBADYCACALIApBAXIgBmxBAnRqQQA2AgAgCyAKQQJyIAZsQQJ0akEANgIAIAsgCkEDciAGbEECdGpBADYCACAKQQRqIQogDkEEaiIOIA1HDQALCyAMRQ0AA0AgCyAGIApsQQJ0akEANgIAIApBAWohCiAJQQFqIgkgDEcNAAsLIAsgD2ohCyATIBJBAWoiEkcNAAsMCAsgE0UNByAQQQJ0IQwgHCAYIAFrQQJ0aiELQQAhCSAaQQdPBEADQCALQQAgDBAVIA9qQQAgDBAVIA9qQQAgDBAVIA9qQQAgDBAVIA9qQQAgDBAVIA9qQQAgDBAVIA9qQQAgDBAVIA9qQQAgDBAVIA9qIQsgCUEIaiIJICtHDQALC0EAIQkgJEUNBwNAIAtBACAMEBUgD2ohCyAJQQFqIgkgJEcNAAsMBwsgE0UNBiAQQXxxIRQgEEEDcSESQQAhDSAQQQFrQQNJIRcMBQtBACEJIBBBfHEiDgRAA0AgCyAJQQN0aiAKIAlBAnRqKAIANgIAIAsgCUEBciIUQQN0aiAKIBRBAnRqKAIANgIAIAsgCUECciIUQQN0aiAKIBRBAnRqKAIANgIAIAsgCUEDciIUQQN0aiAKIBRBAnRqKAIANgIAIAlBBGoiCSAOSQ0ACwsgCSAQTw0FAkAgECAJayIUQRBJDQAgLiANIB9sIg1qIAlBA3RqIBIgIGoiDiAQIAxrQQJ0akkEQCAOIAkgDGtBAnRqIA0gLWogEEEDdGpJDQELIAogCUECdGohDSAJ/RH9DAAAAAABAAAAAgAAAAMAAAD9rgEhNCAJIBRBfHEiDGohCUEAIQ4DQCALIDRBAf2rASI1/RsAQQJ0aiANIA5BAnRq/QACACI2/VoCAAAgCyA1/RsBQQJ0aiA2/VoCAAEgCyA1/RsCQQJ0aiA2/VoCAAIgCyA1/RsDQQJ0aiA2/VoCAAMgNP0MBAAAAAQAAAAEAAAABAAAAP2uASE0IA5BBGoiDiAMRw0ACyAMIBRGDQYLQQAhDCAJIQ4gECAJa0EDcSINBEADQCALIA5BA3RqIAogDkECdGooAgA2AgAgDkEBaiEOIAxBAWoiDCANRw0ACwsgCSAQa0F8Sw0FA0AgCyAOQQN0aiAKIA5BAnRqKAIANgIAIAsgDkEBaiIJQQN0aiAKIAlBAnRqKAIANgIAIAsgDkECaiIJQQN0aiAKIAlBAnRqKAIANgIAIAsgDkEDaiIJQQN0aiAKIAlBAnRqKAIANgIAIA5BBGoiDiAQRw0ACwwFCyATRQ0EQQAhCSAaQQNPBEADQCALIAooAgA2AgAgCyAPaiIMIAogEWoiDSgCADYCACAMIA9qIgwgDSARaiINKAIANgIAIAwgD2oiDCANIBFqIg0oAgA2AgAgDSARaiEKIAwgD2ohCyAJQQRqIgkgG0cNAAsLQQAhCSAWRQ0EA0AgCyAKKAIANgIAIAogEWohCiALIA9qIQsgCUEBaiIJIBZHDQALDAQLIBwgDUECdGohCyAQQQRHBEAgE0UNBCAQQQJ0IQlBACEOIBpBA08EQANAIAsgCiAJEBIhMCAKIBFqIg0gEWoiCyARaiISIBFqIQogMCAPaiANIAkQEiAPaiALIAkQEiAPaiASIAkQEiAPaiELIA5BBGoiDiAbRw0ACwtBACEOIBZFDQQDQCALIAogCRASITEgCiARaiEKIDEgD2ohCyAOQQFqIg4gFkcNAAsMBAsgE0UNA0EAIQkgGkEDTwRAA0AgCyAK/QACAP0LAgAgCyAPaiIMIAogEWoiDf0AAgD9CwIAIAwgD2oiDCANIBFqIg39AAIA/QsCACAMIA9qIgwgDSARaiIN/QACAP0LAgAgDSARaiEKIAwgD2ohCyAJQQRqIgkgG0cNAAsLQQAhCSAWRQ0DA0AgCyAK/QACAP0LAgAgCiARaiEKIAsgD2ohCyAJQQFqIgkgFkcNAAsMAwsDQEEAIQkgDQRAA0AgCyAJQQV0aiAKIAlBAnRqKAIANgIAIAsgCUEBciIMQQV0aiAKIAxBAnRqKAIANgIAIAsgCUECciIMQQV0aiAKIAxBAnRqKAIANgIAIAsgCUEDciIMQQV0aiAKIAxBAnRqKAIANgIAIAlBBGoiCSANSQ0ACwsCQCAJIBBPDQACQCAQIAlrIhRBCE8EQAJAIAsgCUEFdGogIiARIBJsak8NACAKIAlBAnRqIBUgDyASbGpPDQAgCSEMDAILIAn9Ef0MAAAAAAEAAAACAAAAAwAAAP2uASE0IAkgFEF8cSIXaiEMQQAhDgNAIAsgNEED/asBIjX9GwBBAnRqIAogCSAOakECdGr9AAIAIjb9WgIAACALIDX9GwFBAnRqIDb9WgIAASALIDX9GwJBAnRqIDb9WgIAAiALIDX9GwNBAnRqIDb9WgIAAyA0/QwEAAAABAAAAAQAAAAEAAAA/a4BITQgDkEEaiIOIBdHDQALIBQgF0YNAgwBCyAJIQwLQQAhDiAQIAwiCWtBA3EiFARAA0AgCyAJQQV0aiAKIAlBAnRqKAIANgIAIAlBAWohCSAOQQFqIg4gFEcNAAsLIAwgEGtBfEsNAANAIAsgCUEFdGogCiAJQQJ0aigCADYCACALIAlBAWoiDEEFdGogCiAMQQJ0aigCADYCACALIAlBAmoiDEEFdGogCiAMQQJ0aigCADYCACALIAlBA2oiDEEFdGogCiAMQQJ0aigCADYCACAJQQRqIgkgEEcNAAsLIAogEWohCiALIA9qIQsgEyASQQFqIhJHDQALDAILIBJFBEBBASAAKAIIIAAoAgxsQQJ0EBMiEkUEQEEADwsgACgCGCAAKAIQIB5sQQJ0aiANaiASNgIACyASICVBAnRqIAlBAnRqIQsgGCABayEJAkACQAJAAkAgBkEBRwRAIBwgBiAJbEECdGohCiAQQQFGDQEgIw0CIBBBB00NAiATRQ0GICYgCSAfbGogEEEFdGohIiAgIBBBAnRqIAxBAnRrIS8gEEF8cSEUQQAhDANAQQAhCSAUBEADQCALIAlBAnRqIAogCUEFdGooAgA2AgAgCyAJQQFyIg1BAnRqIAogDUEFdGooAgA2AgAgCyAJQQJyIg1BAnRqIAogDUEFdGooAgA2AgAgCyAJQQNyIg1BAnRqIAogDUEFdGooAgA2AgAgCUEEaiIJIBRJDQALCwJAIAkgEE8NAAJAIBAgCWsiF0EITwRAAkAgCyAJQQJ0aiAiIAwgD2xqTw0AIAogCUEFdGogEiAvIAwgEWxqak8NACAJIQ0MAgsgCf0R/QwAAAAAAQAAAAIAAAADAAAA/a4BITQgCSAXQXxxIhVqIQ1BACEOA0AgCyAJIA5qQQJ0aiAKIDRBA/2rASI1/RsDQQJ0aiAKIDX9GwJBAnRqIAogNf0bAUECdGogCiA1/RsAQQJ0av0JAgD9VgIAAf1WAgAC/VYCAAP9CwIAIDT9DAQAAAAEAAAABAAAAAQAAAD9rgEhNCAOQQRqIg4gFUcNAAsgFSAXRg0CDAELIAkhDQtBACEOIBAgDSIJa0EDcSIXBEADQCALIAlBAnRqIAogCUEFdGooAgA2AgAgCUEBaiEJIA5BAWoiDiAXRw0ACwsgDSAQa0F8Sw0AA0AgCyAJQQJ0aiAKIAlBBXRqKAIANgIAIAsgCUEBaiINQQJ0aiAKIA1BBXRqKAIANgIAIAsgCUECaiINQQJ0aiAKIA1BBXRqKAIANgIAIAsgCUEDaiINQQJ0aiAKIA1BBXRqKAIANgIAIAlBBGoiCSAQRw0ACwsgCyARaiELIAogD2ohCiATIAxBAWoiDEcNAAsMBgsgHCAJQQJ0aiEKIBBBBEYNAiATRQ0FIBBBAnQhCUEAIQ4gGkEDTwRAA0AgCyAKIAkQEiEyIAogD2oiDSAPaiILIA9qIhIgD2ohCiAyIBFqIA0gCRASIBFqIAsgCRASIBFqIBIgCRASIBFqIQsgDkEEaiIOIBtHDQALC0EAIQ4gFkUNBQNAIAsgCiAJEBIhMyAKIA9qIQogMyARaiELIA5BAWoiDiAWRw0ACwwFCyATRQ0EQQAhCSAaQQNPBEADQCALIAooAgA2AgAgCyARaiIMIAogD2oiDSgCADYCACAMIBFqIgwgDSAPaiINKAIANgIAIAwgEWoiDCANIA9qIg0oAgA2AgAgDCARaiELIA0gD2ohCiAJQQRqIgkgG0cNAAsLQQAhCSAWRQ0EA0AgCyAKKAIANgIAIAsgEWohCyAKIA9qIQogCUEBaiIJIBZHDQALDAQLIBNFDQMgEEF8cSEUIBBBA3EhEkEAIQ0gEEEBa0EDSSEXDAELIBNFDQJBACEJIBpBA08EQANAIAsgCv0AAgD9CwIAIAsgEWoiDCAKIA9qIg39AAIA/QsCACAMIBFqIgwgDSAPaiIN/QACAP0LAgAgDCARaiIMIA0gD2oiDf0AAgD9CwIAIA0gD2ohCiAMIBFqIQsgCUEEaiIJIBtHDQALC0EAIQkgFkUNAgNAIAsgCv0AAgD9CwIAIAogD2ohCiALIBFqIQsgCUEBaiIJIBZHDQALDAILA0ACQCAQRQ0AQQAhDkEAIQlBACEMIBdFBEADQCALIAlBAnRqIAogBiAJbEECdGooAgA2AgAgCyAJQQFyIhVBAnRqIAogBiAVbEECdGooAgA2AgAgCyAJQQJyIhVBAnRqIAogBiAVbEECdGooAgA2AgAgCyAJQQNyIhVBAnRqIAogBiAVbEECdGooAgA2AgAgCUEEaiEJIAxBBGoiDCAURw0ACwsgEkUNAANAIAsgCUECdGogCiAGIAlsQQJ0aigCADYCACAJQQFqIQkgDkEBaiIOIBJHDQALCyALIBFqIQsgCiAPaiEKIBMgDUEBaiINRw0ACwwBCwNAAkAgEEUNAEEAIQ5BACEJQQAhDCAXRQRAA0AgCyAGIAlsQQJ0aiAKIAlBAnRqKAIANgIAIAsgCUEBciIVIAZsQQJ0aiAKIBVBAnRqKAIANgIAIAsgCUECciIVIAZsQQJ0aiAKIBVBAnRqKAIANgIAIAsgCUEDciIVIAZsQQJ0aiAKIBVBAnRqKAIANgIAIAlBBGohCSAMQQRqIgwgFEcNAAsLIBJFDQADQCALIAYgCWxBAnRqIAogCUECdGooAgA2AgAgCUEBaiEJIA5BAWoiDiASRw0ACwsgCiARaiEKIAsgD2ohCyANQQFqIg0gE0cNAAsLICFBAWohISAQIBhqIhggA0kNAAsgHkEBaiEeIBMgHWoiHSAESQ0ACwtBAQvDMwUmfw9+AXsBfQF8IwBB0ABrIg4kACAOQZD/AzYCKCAAKAJsIAAoAmhsIRcCfwJAAkACQCAAKAIIIgtBCEcEQEEAIAtBgAJHDQQaIA5B2f8DNgIoDAELIAAtAERBAXENACAXQQFxISIgF0F8cSEPIBdBAWutQowsfiIxQiCIp0EARyEjIDGnISQgDkHNAGohJSAOQcwAaiEoIA5ByABqISkgF0EkSSEqQZD/AyELAkACQAJAA0ACQCALQZP/A0YNAAJAA0AgCSkDCCIxUAR+QgAFIDEgCSkDOH0LUARAIABBwAA2AggMAwsgCSAAKAIQQQIgChAaQQJHBEAgCkEBQZYSQQAQD0EADAsLIAAoAhAgDkEkakECEBEgDigCJCILQQFNBEAgCkEBQYcuQQAQD0EADAsLAkAgDigCKEGAgQJGBEAgCSkDCCIxUAR+QgAFIDEgCSkDOH0LUA0BIA4oAiQhCwsgACgCCCIUQRBxBEAgACAAKAIYIAtrQQJrNgIYCyAOIAtBAmsiEjYCJEHgvQEhDCAOKAIoIQ0DQCAMIgsoAgAiGARAIAtBDGohDCANIBhHDQELCyALKAIEIBRxRQRAIApBAUH8KEEAEA9BAAwMCwJAIAAoAhQgEk8EQCAAKAIQIQwMAQsgCSkDCCIxUAR+QgAFIDEgCSkDOH0LIBKtUwRAIApBAUGMLEEAEA9BAAwNCyAAKAIQIA4oAiQQFyIMRQRAIAAoAhAQECAAQgA3AxAgCkEBQdQlQQAQD0EADA0LIAAgDDYCECAAIA4oAiQiEjYCFAsgCSAMIBIgChAaIgwgDigCJEcEQCAKQQFBlhJBABAPQQAMDAsgCygCCCILRQRAIApBAUHa1gBBABAPQQAMDAsgACAAKAIQIAwgCiALEQEARQRAIA4gDigCKDYCICAKQQFBlOgAIA5BIGoQD0EADAwLIAkpAzghMSAOKAIkIREgACgCyAEiFCgCKCISIAAoAswBIgxBKGwiDWoiFigCFCIcQQFqIh0gFigCHCILSwRAIBYCfyALs0MAAMhCkiJBQwAAgE9dIEFDAAAAAGBxBEAgQakMAQtBAAsiCzYCHCAWKAIYIAtBGGwQFyELIBQoAigiEiANaiEWIAtFDQMgFiALNgIYIBYoAhQiHEEBaiEdCyANIBJqIg0oAhggHEEYbGoiCyARQQRqNgIQIAsgMacgEWtBBGsiDKw3AwggCyAYOwEAIA0gHTYCFAJAIBhBkP8DRw0AIA0oAhAiCwRAIAsgDSgCDEEYbGogDK03AwALIAkpAzinIA4oAiRrQQRrrSIxIAApAzBXDQAgACAxNwMwCyAALQBEQQRxBEAgCSAANQIYIAogCSgCKBEIACAANQIYUgRAIApBAUGWEkEAEA9BAAwNCyAOQZP/AzYCKAwECyAJIAAoAhBBAiAKEBpBAkcEQCAKQQFBlhJBABAPQQAMDAsgACgCECAOQShqQQIQESAOKAIoQZP/A0cNAQwDCwsgAEHAADYCCAwBCyAWKAIYEBAgFCgCKCAMQShsaiIAQQA2AhwgAEIANwIUIApBAUGFHUEAEA9BAAwICwJAIAkpAwgiMVAEfkIABSAxIAkpAzh9C1AEQCAAKAIIQcAARg0BCwJAAkAgAC0ARCILQQRxRQRAIAAoAswBQYwsbCEMIAAoApwBIS4CQAJAIAAoAjgEQCAJKQMIIjFQBH5CAAUgMSAJKQM4fQunIRMMAQsgACgCGCITQQJJDQELIAAgE0ECayITNgIYCyAuIAxqIRggE0UNASAJKQMIIjFQBH5CAAUgMSAJKQM4fQsgE61TBEAgACgCuAEEQCAKQQFBuSxBABAPQQAMDQsgCkECQbksQQAQDwsgACgCGCINQX5PBEAgCkEBQf4KQQAQD0EADAwLAkAgGCgC3CsiDARAIBgoAuArIgtBfSANa0sEQCAKQQFBlglBABAPQQAMDgsgDCALIA1qQQJqEBciCwRAIBggCzYC3CsMBAsgGCgC3CsQECAYQQA2AtwrDAELIBggDUECahAUIgs2AtwrIAsNAgsgCkEBQYcvQQAQD0EADAsLIABBCDYCCCAAIAtB+gFxOgBEDAELIAAoAsgBIhYEQCAWKAIoIhIgACgCzAEiFEEobCIRaiIMKAIQIAwoAgxBGGxqIgsgCSkDOCIyQgJ9IjE3AwggCyAyIAA1Ahh8NwMQIAAoAhghDQJAIAwoAhQiHEEBaiIdIAwoAhwiC00EQCAMKAIYIQwMAQsgDAJ/IAuzQwAAyEKSIkFDAACAT10gQUMAAAAAYHEEQCBBqQwBC0EACyILNgIcIAwoAhggC0EYbBAXIQwgFigCKCISIBFqIQsgDEUNBiALIAw2AhggCygCFCIcQQFqIR0LIAwgHEEYbGoiCyANQQJqNgIQIAsgMcQ3AwggC0GT/wM7AQAgESASaiAdNgIUCyAAKAIYIQwCQCATRQRAQQAhEwwBCyAJIBgoAtwrIBgoAuAraiAMIAoQGiETIAAoAhghDAsgAEEIQcAAIAwgE0YbNgIIIBggGCgC4CsgE2o2AuArIAAtAEQiC0EJcUEBRw0AIAAgC0EIcjoARCAAKALMASENIAkoAhxBAkYNACAJKQM4IjFCf1ENAAJAA0BBACEMIAkgDkHGAGoiC0ECIAoQGkECRw0BIAsgDkFAa0ECEBEgDigCQEGQ/wNHDQFBlhIhEiAJIAtBAiAKEBpBAkcNCSALIA5BPGpBAhARIA4oAjxBCkcEQEGHLiESDAoLIA5BCDYCPCAJIA5BxgBqQQggChAaIgsgDigCPEcNCSALQQhHBEBBvR4hEgwKCyAOQcYAaiAOQThqQQIQESApIA5BNGpBBBARICggDkEwakEBEBEgJSAOQSxqQQEQESANIA4oAjhHBEAgDigCNCILQQ5JDQIgDiALQQxrIgs2AjQgCSALrSAKIAkoAigRCAAgDjUCNFENAQwCCwsgDigCMCAOKAIsRiEMCyAJIDEgCiAJKAIsEQ0ARQ0IIAxFDQAgACAALQBEQe4BcUEQcjoARAJAIBdFDQAgACgCnAEhE0EAIQsCQCAqDQAgE0HYK2oiDCAkaiAMSSAjcg0AA0AgEyALQYwsbGoiHCgC2CsiHf0RIBMgC0EBckGMLGxqIhgoAtgrIhb9HAEgEyALQQJyQYwsbGoiESgC2CsiFP0cAiATIAtBA3JBjCxsaiINKALYKyIM/RwD/QwAAAAAAAAAAAAAAAAAAAAA/TgiQP0bAEEBcQRAIBxB2CtqIB1BAWo2AgALIED9GwFBAXEEQCAYQdgraiAWQQFqNgIACyBA/RsCQQFxBEAgEUHYK2ogFEEBajYCAAsgQP0bA0EBcQRAIA1B2CtqIAxBAWo2AgALIAtBBGoiCyAPRw0ACyAXIA8iC0YNAQsgC0EBciEMICIEQCATIAtBjCxsaiINKALYKyILBEAgDUHYK2ogC0EBajYCAAsgDCELCyAMIBdGDQADQCATIAtBjCxsaiINKALYKyIMBEAgDUHYK2ogDEEBajYCAAsgDUHk1wBqIg0oAgAiDARAIA0gDEEBajYCAAsgC0ECaiILIBdHDQALCyAKQQJBlMQAQQAQDwsgAC0AREEBcQ0AIAkgACgCEEECIAoQGkECRwRAAkAgACgCzAFBAWogF0cNACAXRQ0AIAAoApwBIQxBACELA0AgDCALQYwsbGoiCSgC1CtFBEAgCSgC2CtFDQgLIAtBAWoiCyAXRw0ACwsgCkEBQZYSQQAQD0EADAkLIAAoAhAgDkEoakECEBEgDigCKCELIAAtAERBAXENAiALQdn/A0cNAQwCCwsgDigCKCELCyALQdn/A0cNAiAAKAIIQYACRg0CIABBgAI2AgggAEEANgLMAQwCCyALKAIYEBAgFigCKCAUQShsaiIAQQA2AhwgAEIANwIUIApBAUGFHUEAEA9BAAwECyAOIAs2AhAgCkEEQefRACAOQRBqEA8gACALNgLMASAOQdn/AzYCKCAAQYACNgIICyAAKALMASELIAAoApwBIQkCQAJAIAAtAERBAXENAAJAAkAgCyAXTw0AIAkgC0GMLGxqIRMDQCATKALcKw0BIAAgC0EBaiILNgLMASATQYwsaiETIAsgF0cNAAsMAQsgCyAXRw0BCyAIQQA2AgAMAQsCQAJAIApBASAJIAtBjCxsaiIRKAK0KAR/QZw0BSARLQCILEECcUUNAgJAIBEoAqgoIg9FBEBBACEMDAELIBEoAqwoIQlBACEMQQAhCyAPQQRPBEAgD0F8cSEL/QwAAAAAAAAAAAAAAAAAAAAAIUBBACESA0AgCSASQQN0aiIMQRxqIAxBFGogDEEMaiAM/QkCBP1WAgAB/VYCAAL9VgIAAyBA/a4BIUAgEkEEaiISIAtHDQALIEAgQCBA/Q0ICQoLDA0ODwABAgMAAQID/a4BIkAgQCBA/Q0EBQYHAAECAwABAgMAAQID/a4B/RsAIQwgCyAPRg0BCwNAIAkgC0EDdGooAgQgDGohDCALQQFqIgsgD0cNAAsLIBEgDBAUIgk2ArQoIAkNAUGXHgtBABAPIApBAUH1PEEAEA9BAAwFCyARIAw2ArwoIBEoAqwoIQkgESgCqCgiDARAQQAhEkEAIQsDQCAJIAtBA3QiFGoiDSgCACIPBEAgESgCtCggEmogDyANKAIEEBIaIBEoAqwoIBRqIgkoAgQhLyAJKAIAEBAgESgCrCgiCSAUakIANwIAIC8gEmohEiARKAKoKCEMCyALQQFqIgsgDEkNAAsLIBFBADYCqCggCRAQIBFBADYCrCggESARKAK0KDYCsCggESARKAK8KDYCuCgLAn9BACEoIAAoAtABIgsoAhwiJigCTCAAKALMASIJQYwsbGooAtArIRsgCygCGCIUKAIYIScgCygCFCgCACIeICYoAgQgJigCDCILIAkgCSAmKAIYIgluIgwgCWxrbGoiDSAUKAIAIgkgCSANSRsiDzYCACAeQX8gCyANaiIJIAkgDUkbIgsgFCgCCCIJIAkgC0sbIgk2AggCQCAJIA9KIA9BAE5xRQRAIApBAUGBM0EAEA8MAQsgHigCFCEQIB4gJigCCCAMICYoAhAiC2xqIg8gFCgCBCIJIAkgD0kbIgw2AgQgHkF/IAsgD2oiCSAJIA9JGyILIBQoAgwiCSAJIAtLGyIJNgIMIAkgDEogDEEATnFFBEAgCkEBQdsyQQAQDwwBCwJAIBsoAgQEQCAeKAIQDQFBAQwDCyAKQQFB1ShBABAPDAELAkACQANAICdBADYCJCAQICc0AgAiNUIBfSIxIB40AgB8IDV/PgIAIBAgJzQCBCI0QgF9IjIgHjQCBHwgNH8+AgQgECAxIB40Agh8IDV/PgIIIB40AgwhMSAQICg2AhAgECAxIDJ8IDR/PgIMIBAgGygCBCILNgIUIBBBASALICYoAlAiCWsgCSALSxs2AhggECgCNBAQIBBBADYCRCAQ/QwAAAAAAAAAAAAAAAAAAAAA/QsCNCALQZgBbCEMAkAgECgCHCIJRQRAIBAgDBAUIgk2AhwgCUUNBSAQIAw2AiAgCUEAIAwQFRoMAQsgDCAQKAIgTQ0AIAkgDBAXIgtFBEAgCkEBQYAXQQAQDyAQKAIcEBAgEEIANwIcDAULIBAgCzYCHCALIBAoAiAiCWpBACAMIAlrEBUaIBAgDDYCIAsgECgCFCILBEAgG0GwB2ohHSAbQawGaiEYIBtBHGohFyAQKAIcIRpBACErA0AgGkJ/IAtBAWsiCa0iM4ZCf4UiMiAQNAIAfCAzh6ciFjYCACAaIDIgEDQCBHwgM4enIhE2AgQgGiAyIBA0Agh8IDOHIjGnIhQ2AgggGiAyIBA0Agx8IDOHIjSnIg02AgwgMcRCASAYICtBAnQiDGooAgAiH60iMYZ8QgF9IDGHpyAfdCIPQQBIDQQgNMRCfyAMIB1qKAIAIiCtIjGGQn+FfCAxh6cgIHQiDEEASA0EIBogDEF/ICB0IBFxIhNrICB1QQAgDSARRxsiDDYCFCAaIA9BfyAfdCAWcSIiayAfdUEAIBQgFkcbIg82AhACQCAPRQ0AIA+tIAytfkIgiFANAAwECyAMIA9sIiNB58yZM08NAyAjQShsISEgGiArBH8gIEEBayEgIB9BAWshHyATrEIBfEIBiKchEyAirEIBfEIBiKchIkEDBUEBCzYCGCAaQRxqIRVCASALrSI2hiE3Qn8gGygCDCILICAgCyAgSRsiLK0iPIZCf4UhPUJ/IBsoAggiCyAfIAsgH0kbIi2tIj6GQn+FIT9BACEpA0ACfiArRQRAIDIgEDQCBHwgM4chOCAyIBA0AgB8IDOHITlBACELIDIiMSE6IDMMAQsgNyApQQFqIgtBAXatIDOGQn+FfCI6IBA0AgR8IDaHITggNyALQQFxrSAzhkJ/hXwiMSAQNAIAfCA2hyE5IDYLITsgEDQCCCE1IBA0AgwhNCAVIDg+AgQgFSA5PgIAIBUgCzYCECAVIDQgOnwgO4c+AgwgFSAxIDV8IDuHPgIIQQAhDAJAIBsoAhRFDQAgC0UNAEECQQEgC0EDRhshDAtEAAAAAAAA8D8hQgJAICcoAhggDGogFygCACIMayILQYAITgRARAAAAAAAAOB/IUIgC0H/D0kEQCALQf8HayELDAILRAAAAAAAAPB/IUJB/RcgCyALQf0XTxtB/g9rIQsMAQsgC0GBeEoNAEQAAAAAAABgAyFCIAtBuHBLBEAgC0HJB2ohCwwBC0QAAAAAAAAAACFCQfBoIAsgC0HwaE0bQZIPaiELCyAVIBcoAgS3RAAAAAAAAEA/okQAAAAAAADwP6AgQiALQf8Haq1CNIa/oqK2OAIgIBUgDCAbKAKkBmpBAWs2AhwgFSgCFCELAkACQAJAICNFDQAgCw0AIBUgIRAUIgs2AhQgC0UEQCAKQQFBlBVBABAPDAoLIAtBACAhEBUaIBUgITYCGAwBCyAhIBUoAhhLBEAgCyAhEBciDEUEQCAKQQFBlBVBABAPIBUoAhQQECAVQgA3AhQMCgsgFSAMNgIUIAwgFSgCGCILakEAICEgC2sQFRogFSAhNgIYCyAjRQ0BCyAVKAIUIQtBACEkA0AgCyAkICQgGigCECIMbiIWIAxsayINIB90ICJqIg8gFSgCACIMIAwgD0gbIhE2AgAgCyAWICB0IBNqIg8gFSgCBCIMIAwgD0gbIhQ2AgQgCyANQQFqIB90ICJqIg8gFSgCCCIMIAwgD0obIg02AgggCyAWQQFqICB0IBNqIg8gFSgCDCIMIAwgD0obIgw2AgwgCyA/IA2sfCA+h6cgESAtdSIWayAtdCAtdSIPNgIQIAsgPSAMrHwgPIenIBQgLHUiEWsgLHQgLHUiDDYCFCAMIA9sIiWtQgaGQiCIQgBSBEAgCkEBQeUVQQAQDwwJCyAlQQZ0IQ0CQAJ/AkAgCygCGCIMDQAgJUUNACALIA0QFCIMNgIYIAxFDQsgDEEAIA0QFRogC0EcagwBCyANIAsoAhxNDQEgDCANEBciD0UEQCALKAIYEBAgC0IANwIYIApBAUHjEkEAEA8MCwsgCyAPNgIYIA8gCygCHCIMakEAIA0gDGsQFRogC0EcagsgDTYCAAsgCygCFCENIAsoAhAhDyALAn8gCygCICIMRQRAIA8gDSAKEGMMAQsgDCAPIA0gChBhCzYCICALKAIUIQ0gCygCECEPIAsCfyALKAIkIgxFBEAgDyANIAoQYwwBCyAMIA8gDSAKEGELNgIkICUEQEEAIRIDQCASIAsoAhAiDW4hHAJAIAsoAhggEkEGdGoiGSgCACIUBEAgGSgCOCEPIBkoAgQhDCAZKAIwISogGSgCPBAQIBn9DAAAAAAAAAAAAAAAAAAAAAD9CwIoIBlCADcCOCAZ/QwAAAAAAAAAAAAAAAAAAAAA/QsCGCAZ/QwAAAAAAAAAAAAAAAAAAAAA/QsCCCAZIBQ2AgAgGSAqNgIwICoEQCAUQQAgKkEYbBAVGgsgGSAPNgI4IBkgDDYCBAwBCyAZQQpBGBATIgw2AgAgDEUNCyAZQQo2AjALIBkgEiANIBxsayAWaiIUIC10Ig8gCygCACIMIAwgD0gbNgIIIBkgESAcaiINICx0Ig8gCygCBCIMIAwgD0gbNgIMIBkgFEEBaiAtdCIPIAsoAggiDCAMIA9KGzYCECAZIA1BAWogLHQiDyALKAIMIgwgDCAPShs2AhQgEkEBaiISICVHDQALCyALQShqIQsgJEEBaiIkICNHDQALCyAXQQhqIRcgFUEkaiEVIClBAWoiKSAaKAIYSQ0ACyAaQZgBaiEaIAkhCyArQQFqIisgECgCFEkNAAsLICdBNGohJyAQQcwAaiEQIBtBuAhqIRsgKEEBaiIoIB4oAhBJDQALQQEMAwsgCkEBQZQWQQAQDwwBCyAKQQFBsxFBABAPC0EAC0UEQCAKQQFBwhtBABAPQQAMBAsgACgCzAEhCSAOIAAoAmggACgCbGw2AgQgDiAJQQFqNgIAIApBBEG+1wAgDhAPIAEgACgCzAE2AgAgCEEBNgIAIAIEQCACIAAoAtABQQAQVCIBNgIAQQAgAUF/Rg0EGgsgAyAAKALQASgCFCgCACIBKAIANgIAIAQgASgCBDYCACAFIAEoAgg2AgAgBiABKAIMNgIAIAcgASgCEDYCACAAIAAoAghBgAFyNgIIC0EBDAILIApBASASQQAQDwsgCkEBQeQbQQAQD0EACyEwIA5B0ABqJAAgMAveEAINfwJ+AkAgACgCICIFDQACQCAAKAIQIglBBUoEQCAJIQMMAQsCQAJAIAAoAhQiAkEFTgRAIAAoAgAiASgCACEFIAAgAUEEajYCACACQQRrIQcMAQsgAkEATARAQX8hBQwCCyAAKAIAIQECfyACQQFGBEBBfyEGQQAMAQtBfyEGIAJBAWsiA0EBcSENAkAgAkECRgRAQQAhBSACIQQMAQsgA0F+cSELQQAhBSABIQMgAiEEA0AgACADQQFqNgIAIAMtAAAhDCAAIANBAmoiATYCACAAIARBAWs2AhQgAy0AASEDIAAgBEECayIENgIUIAZB/wEgBXRBf3NxIAwgBXRyQYD+AyAFdEF/c3EgAyAFQQhydHIhBiAFQRBqIQUgASEDIAhBAmoiCCALRw0ACwsgDQRAIAAgAUEBaiIDNgIAIAEtAAAhASAAIARBAWs2AhQgBkH/ASAFdEF/c3EgASAFdHIhBiADIQELIAJBA3RBCGsLIQUgACABQQFqNgIAIAZB/wEgBXRBf3NxIAEtAABBD3IgBXRyIQULIAAgBzYCFAsgACgCGCEBIAAgBUEYdiIHQf8BRjYCGCAAIAkgBUEQdkH/AXEiCEH/AUYiCiAFQQh2Qf8BcSILQf8BRiIMIAEgBUH/AXEiBEH/AUYiAmpqaiIBa0EgaiIDNgIQIAAgACkDCCAEQQdBCCACG3QgC3JBB0EIIAwbdCAIckEHQQggCht0IAdyrSABIAlrQSBqrYaENwMIQQAhBSADQQZIDQELIAAoAhwiAUECdEGgnQFqKAIAIQICfiAAKQMIIg5CAFMEQEEMIAFBAWogAUELThshBCADQQFrIQNBfyACdEF/c0EBdCEBQgEMAQsgAUEBa0EAIAFBAUobIQQgDkE/IAJrrYinQX8gAnRBf3NxQQF0QQFyIQEgAyACQQFqIgJrIQMgAq0LIQ8gACADNgIQIAAgBDYCHCAAIA4gD4Y3AwggACABrCAAKQMoQkCDhDcDKEEBIQUgA0EGSA0AIAAoAhwiAUECdEGgnQFqKAIAIQICfiAAKQMIIg5CAFMEQEEMIAFBAWogAUELThshBCADQQFrIQNBfyACdEF/c0EBdCEBQgEMAQsgAUEBa0EAIAFBAUobIQQgDkE/IAJrrYinQX8gAnRBf3NxQQF0QQFyIQEgAyACQQFqIgJrIQMgAq0LIQ8gACADNgIQIAAgBDYCHCAAIA4gD4Y3AwggACAAKQMoQv9AgyABrEIHhoQ3AyhBAiEFIANBBkgNACAAKAIcIgFBAnRBoJ0BaigCACECAn4gACkDCCIOQgBTBEBBDCABQQFqIAFBC04bIQQgA0EBayEDQX8gAnRBf3NBAXQhAUIBDAELIAFBAWtBACABQQFKGyEEIA5BPyACa62Ip0F/IAJ0QX9zcUEBdEEBciEBIAMgAkEBaiICayEDIAKtCyEPIAAgAzYCECAAIAQ2AhwgACAOIA+GNwMIIAAgACkDKEL//0CDIAGsQg6GhDcDKEEDIQUgA0EGSA0AIAAoAhwiAUECdEGgnQFqKAIAIQICfiAAKQMIIg5CAFMEQEEMIAFBAWogAUELThshBCADQQFrIQNBfyACdEF/c0EBdCEBQgEMAQsgAUEBa0EAIAFBAUobIQQgDkE/IAJrrYinQX8gAnRBf3NxQQF0QQFyIQEgAyACQQFqIgJrIQMgAq0LIQ8gACADNgIQIAAgBDYCHCAAIA4gD4Y3AwggACAAKQMoQv///0CDIAGsQhWGhDcDKEEEIQUgA0EGSA0AIAAoAhwiAUECdEGgnQFqKAIAIQICfiAAKQMIIg5CAFMEQEEMIAFBAWogAUELThshBCADQQFrIQNBfyACdEF/c0EBdCEBQgEMAQsgAUEBa0EAIAFBAUobIQQgDkE/IAJrrYinQX8gAnRBf3NxQQF0QQFyIQEgAyACQQFqIgJrIQMgAq0LIQ8gACADNgIQIAAgBDYCHCAAIA4gD4Y3AwggACAAKQMoQv////9AgyABrEIchoQ3AyhBBSEFIANBBkgNACAAKAIcIgFBAnRBoJ0BaigCACECAn4gACkDCCIOQgBTBEBBDCABQQFqIAFBC04bIQQgA0EBayEDQX8gAnRBf3NBAXQhAUIBDAELIAFBAWtBACABQQFKGyEEIA5BPyACa62Ip0F/IAJ0QX9zcUEBdEEBciEBIAMgAkEBaiICayEDIAKtCyEPIAAgAzYCECAAIAQ2AhwgACAOIA+GNwMIIAAgACkDKEL//////0CDIAGtQiOGhDcDKEEGIQUgA0EGSA0AIAAoAhwiAUECdEGgnQFqKAIAIQICfiAAKQMIIg5CAFMEQEEMIAFBAWogAUELThshBCADQQFrIQNBfyACdEF/c0EBdCEBQgEMAQsgAUEBa0EAIAFBAUobIQQgDkE/IAJrrYinQX8gAnRBf3NxQQF0QQFyIQEgAyACQQFqIgJrIQMgAq0LIQ8gACADNgIQIAAgBDYCHCAAIA4gD4Y3AwggACAAKQMoQv///////0CDIAGtQiqGhDcDKEEHIQUgA0EGSA0AIAAoAhwiAUECdEGgnQFqKAIAIQICfiAAKQMIIg5CAFMEQEEMIAFBAWogAUELThshBCADQQFrIQNBfyACdEF/c0EBdCEBQgEMAQsgAUEBa0EAIAFBAUobIQQgDkE/IAJrrYinQX8gAnRBf3NxQQF0QQFyIQEgAyACQQFqIgJrIQMgAq0LIQ8gACADNgIQIAAgBDYCHCAAIA4gD4Y3AwggACAAKQMoQv////////9AgyABrUIxhoQ3AyhBCCEFCyAAIAVBAWs2AiAgACAAKQMoIg5CB4g3AyggDqdB/wBxCyIBAX8gAARAIAAoAgwiAQRAIAEQECAAQQA2AgwLIAAQEAsLigECAX4FfwJAIABCgICAgBBUBEAgACECDAELA0AgAUEBayIBIABCCoAiAkL2AX4gAHynQTByOgAAIABC/////58BViEGIAIhACAGDQALCyACQgBSBEAgAqchAwNAIAFBAWsiASADQQpuIgRB9gFsIANqQTByOgAAIANBCUshByAEIQMgBw0ACwsgAQv54gEEen8Gewh+AX0jAEEQayJOJAACQCAALQAIQYABcUUNACAAKALMASABRw0AIAAoApwBIAFBjCxsaiJPKALcKyIVRQRAIE8QLgwBCyAAKALIARogACgC0AEhGSAAKAJMIgdFBEAgACgCSCEHCyAHKAIAIQYgBygCBCELIAcoAgghCSAHKAIMIQ0gACgCPCEHIAAoAkAhCCBPKALgKyEKIwBBEGsiQCQAIBkgATYCJCAZKAIcKAJMIQwgGUEBNgJAIBkgDTYCPCAZIAk2AjggGSALNgI0IBkgBjYCMCAZIAwgAUGMLGxqNgIgIBkoAkQQEEEAIQsgGUEANgJEAkAgBwRAQQQgGSgCGCgCEBATIgtFBEAMAgtBACENQQAhCSAHQQRPBEAgB0F8cSEMQQAhAQNAIAsgCCAJQQJ0aiIGKAIAQQJ0akEBNgIAIAsgBigCBEECdGpBATYCACALIAYoAghBAnRqQQE2AgAgCyAGKAIMQQJ0akEBNgIAIAlBBGohCSABQQRqIgEgDEcNAAsLIAdBA3EiAQRAA0AgCyAIIAlBAnRqKAIAQQJ0akEBNgIAIAlBAWohCSANQQFqIg0gAUcNAAsLIBkgCzYCRAsCQAJAIBkoAhgiBigCECINRQ0AQQAhCQJAA0ACQCALBEAgCyAJQQJ0aigCAEUNAQsgBigCGCAJQTRsaiIBNQIEIoYBQgF9IooBIBk1Ajx8IIYBgCGLASABNQIAIocBQgF9IogBIBk1Ajh8IIcBgCGMASCKASAZNQI0fCCGAYAhhgEgGSgCFCgCACgCFCAJQcwAbGoiASgCFCABKAIYayIHQR9LDQACQCCIASAZNQIwfCCHAYCnIgggASgCAGsiDEEAIAggDE8bIAd2DQAghgGnIgggASgCBGsiDEEAIAggDE8bIAd2DQAgASgCCCIIIIwBp2siDEEAIAggDE8bIAd2DQAgASgCDCIBIIsBp2siCEEAIAEgCE8bIAd2RQ0BCyAZQQA2AkAMAgsgCUEBaiIJIA1HDQALIBkoAkBFDQAgDUUNAUEAIQ0DQCAZKAIUKAIAKAIUIA1BzABsaiIBKAIcIAEoAhhBmAFsaiIHQZQBaygCACEGIAdBjAFrKAIAIQsgB0GYAWsoAgAhCSAHQZABaygCACEIAkAgGSgCRCIHBEAgByANQQJ0aigCAEUNAQsgCyAGayEHIAggCWshCQJAIAYgC0YNACAHrSAJrX5CIIhQDQAgBUEBQZQWQQAQDwwGCyAHIAlsIgdBgICAgARPBEAgBUEBQZQWQQAQDwwGCyABIAdBAnQiBzYCLAJ/AkACQAJAIAEoAiQiBgRAIAcgASgCME0NBSABKAIoDQELIAEgBxAYIgc2AiQgB0EBIAEoAiwiBxtFDQEgASAHNgIwIAFBKGoMAwsgBhAQIAEgASgCLBAYIgc2AiQgBw0BIAFBADYCMCABQgA3AigLIAVBAUGUFkEAEA8MBwsgASABKAIsNgIwIAFBKGoLQQE2AgALIA1BAWoiDSAZKAIYIgYoAhBJDQALDAELIA1FDQAgBigCGCEPIBkoAhQoAgAoAhQhFkEAIQEDQAJAIAsEQCALIAFBAnRqKAIARQ0BCyAWIAFBzABsaiIHIAcoAgAiCSAPIAFBNGxqIgg1AgAihgFCAX0iigEgGTUCMHwghgGApyIMIAkgDEsbIgk2AjggByAHKAIEIgwgCDUCBCKHAUIBfSKLASAZNQI0fCCHAYCnIgggCCAMSRsiCDYCPCAHIAcoAggiDCCKASAZNQI4fCCGAYCnIhcgDCAXSRsiDDYCQCAHIAcoAgwiFyCLASAZNQI8fCCHAYCnIg4gDiAXSxsiFzYCRCAJIAxLDQMgCCAXSw0DIAcoAhQiDkUNACAOrSGLASAXrSGIASAMrSGMASAIrSGNASAJrSGJASAHKAIcIQlCACGHAQNAIAkghwGnIghBmAFsaiIHQn8gDiAIQX9zaq0ihgGGQn+FIooBIIgBfCCGAYg+ApQBIAcgigEgjAF8IIYBiD4CkAEgByCKASCNAXwghgGIPgKMASAHIIkBIIoBfCCGAYg+AogBIIcBQgF8IocBIIsBUg0ACwsgAUEBaiIBIA1HDQALCyBAQQA2AgggGSgCHCEBQQFBCBATIhsEQCAbIAE2AgQgGyAGNgIACyAbRQ0BIBkoAiQhESAZKAIUKAIAISAjAEHwAGsiEyQAIBFBjCxsIgEgGygCBCIIKAJMaiIcKAKkAyEoAn8gGygCACIeIRcgBSEzQQAhDSMAQSBrIg8kACABIAgoAkxqIh0oAqQDIRgCQCAXKAIQIhZBkARsEBQiDEUNAAJAIBZBAnQQFCILRQRAIAwhCwwBCwJ/IAgoAkwgEUGMLGxqIgkoAqQDIhpBAWoiAUHwARATIgcEQAJAIAEEQCAXKAIQIQ4gByEBA0AgASAzNgLsASABIA5BEBATIgY2AsgBIAZFDQIgASAXKAIQIh82AsQBQQAhBkEAIQ4gHwRAA0AgASgCyAEgBkEEdGoiDiAJKALQKyAGQbgIbGoiHygCBEEQEBMiITYCDCAhRQ0EIA4gHygCBDYCCCAGQQFqIgYgFygCECIOSQ0ACwsgAUHwAWohASASIBpGIXMgEkEBaiESIHNFDQALCyAHDAILIAcoAgQiAQRAIAEQECAHQQA2AgQLIAchAUEAIQkDQCABKALIASIGBEBBACEOIAEoAsQBIhIEfwNAIAYoAgwiHwRAIB8QECAGQQA2AgwgASgCxAEhEgsgBkEQaiEGIA5BAWoiDiASSQ0ACyABKALIAQUgBgsQECABQQA2AsgBCyABQfABaiEBIAkgGkYhdCAJQQFqIQkgdEUNAAsgBxAQC0EACyIHBEACQCAWRQ0AQQAhCSAMIQYgFkEETwRAIAYgFkF8cSIJQZAEbGohBiAMIQEDQCALIBBBAnRqIAH9Ef0MAAAAABACAAAgBAAAMAYAAP2uAf0LAgAgAUHAEGohASAQQQRqIhAgCUcNAAsgCSAWRg0BCwNAIAsgCUECdGogBjYCACAGQZAEaiEGIAlBAWoiCSAWRw0ACwsgCyEOQQAhEiAIKAJMIBFBjCxsaigC0CshASAXKAIYIQkgDyAIKAIEIAgoAgwgESARIAgoAhgiBm4iCyAGbGtsaiIGIBcoAgAiECAGIBBLGzYCFCAPQX8gBiAIKAIMaiIQIAYgEEsbIgYgFygCCCIQIAYgEEkbNgIQIA8gCCgCCCAIKAIQIAtsaiIGIBcoAgQiCyAGIAtLGzYCDCAPQX8gBiAIKAIQaiILIAYgC0sbIgYgFygCDCILIAYgC0kbNgIIIA9BADYCGCAPQQA2AhwgD0H/////BzYCBCAPQf////8HNgIAIBcoAhAEQANAIA4EfyAOIBJBAnRqKAIABUEACyELIAk1AgQihgFCAX0iigEgDzUCCHwghgGAIYsBIAk1AgAihwFCAX0iiAEgDzUCEHwghwGAIYwBIIoBIA81Agx8IIYBgCGGASCIASAPNQIUfCCHAYAhhwEgASgCBCIIIA8oAhxLBEAgDyAINgIcIAEoAgQhCAsgCARAIIsBQv////8PgyGKASCMAUL/////D4MhiwEghgFC/////w+DIYgBIIcBQv////8PgyGMASABQbAHaiEfIAFBrAZqISFBACEaA0AgHyAaQQJ0IhBqKAIAIQYgECAhaigCACERQQAhECALBEAgCyAGNgIEIAsgETYCACALQQhqIRALAkAgESAIQQFrIghqIgtBH0sNACAJKAIAIiJBfyALdksNACAPIA8oAgQiJyAiIAt0IgsgCyAnSxs2AgQLAkAgBiAIaiILQR9LDQAgCSgCBCIiQX8gC3ZLDQAgDyAPKAIAIicgIiALdCILIAsgJ0sbNgIAC0EAIQsgigFCfyAIrSKGAYZCf4UihwF8IIYBiCKNAUL/////D4NCASAGrSKJAYZ8QgF9IIkBiKcghwEgiAF8IIYBiKciIiAGdmtBfyAGdnFBACAiII0Bp0cbIQYghwEgiwF8IIYBiCKNAUL/////D4NCASARrSKJAYZ8QgF9IIkBiKcghwEgjAF8IIYBiKciIiARdmtBfyARdnFBACAiII0Bp0cbIREgEARAIBAgBjYCBCAQIBE2AgAgEEEIaiELCyAGIBFsIgYgDygCGEsEQCAPIAY2AhgLIBpBAWoiGiABKAIESQ0ACwsgCUE0aiEJIAFBuAhqIQEgEkEBaiISIBcoAhBJDQALCyAYQQFqISEgDygCHCERIA8oAhghEiAHQQA2AgQCQCAdKAIIQQFqIgGtIBEgEiAWbCIibCIarX5CIIhQBEAgByABIBpsIgE2AgggByABQQIQEyIBNgIEIAENAQsgDBAQIA4QECAHKAIEIgEEQCABEBAgB0EANgIECyAhRQRAIAchCwwDC0EAIQsgByEBA0AgASgCyAEiCQRAQQAhBiABKALEASIQBH8DQCAJKAIMIggEQCAIEBAgCUEANgIMIAEoAsQBIRALIAlBEGohCSAGQQFqIgYgEEkNAAsgASgCyAEFIAkLEBAgAUEANgLIAQsgAUHwAWohASALIBhGIXUgC0EBaiELIHVFDQALIAchCwwCCyAXKAIYIRcgByAPKAIUIic2AswBIAcgDygCDCIwNgLQASAHIA8oAhAiLTYC1AEgByAPKAIIIis2AtgBIAcgGjYCDCAHICI2AhAgByASNgIUQQEhHyAHQQE2AhggFgRAIAcoAsgBIQFBACEIIBchCwNAIA4gCEECdGooAgAhCSABIAsoAgA2AgAgASALKAIENgIEAkAgASgCCCINRQ0AIAEoAgwhBiANQQFHBEAgDUF+cSEvQQAhEANAIAYgCSgCADYCACAGIAkoAgQ2AgQgBiAJKAIINgIIIAYgCSgCDDYCDCAGIAkoAhA2AhAgBiAJKAIUNgIUIAYgCSgCGDYCGCAGIAkoAhw2AhwgBkEgaiEGIAlBIGohCSAQQQJqIhAgL0cNAAsLIA1BAXFFDQAgBiAJKAIANgIAIAYgCSgCBDYCBCAGIAkoAgg2AgggBiAJKAIMNgIMCyALQTRqIQsgAUEQaiEBIAhBAWoiCCAWRw0ACwsgIUEBSwRAIAchDQNAIA0gKzYCyAMgDSAtNgLEAyANIDA2AsADIA0gJzYCvAMgDUEBNgKIAiANIBI2AoQCIA0gIjYCgAIgDSAaNgL8ASAWBEAgDSgCuAMhAUEAIQggFyELA0AgDiAIQQJ0aigCACEJIAEgCygCADYCACABIAsoAgQ2AgQCQCABKAIIIiFFDQAgASgCDCEGICFBAUcEQCAhQX5xIS9BACEQA0AgBiAJKAIANgIAIAYgCSgCBDYCBCAGIAkoAgg2AgggBiAJKAIMNgIMIAYgCSgCEDYCECAGIAkoAhQ2AhQgBiAJKAIYNgIYIAYgCSgCHDYCHCAGQSBqIQYgCUEgaiEJIBBBAmoiECAvRw0ACwsgIUEBcUUNACAGIAkoAgA2AgAgBiAJKAIENgIEIAYgCSgCCDYCCCAGIAkoAgw2AgwLIAtBNGohCyABQRBqIQEgCEEBaiIIIBZHDQALCyANIA0pAgQ3AvQBIBggH0chdiANQfABaiENIB9BAWohHyB2DQALCyAMEBAgDhAQIB0oAqQDIQsCQCAdLQCILEEEcQRAIAtBf0YNASAdQagDaiEGIB0oAgghAUEAIRAgByEJA0AgBigCJCENIAlBATYCLCAJIA02AlQgCSAGKAIANgIwIAYoAgQhDSAJQgA3AkQgCSANNgI0IAkgBigCDDYCPCAJIAYoAhA2AkAgBigCCCENIAkgEjYCTCAJIA0gASABIA1LGzYCOCAGQZQBaiEGIAlB8AFqIQkgCyAQRiF3IBBBAWohECB3RQ0ACwwBCyALQX9GDQAgHSgCCCEGIB0oAgQhDSAHIQkgCwRAIAtBAWpBfnEhCEEAIQEDQCAJQgA3AkQgCUEANgI0IAlCATcCLCAJIA02AlQgCSARNgI8IAkgDTYCxAIgCSASNgJMIAkgBjYCOCAJQgA3ArQCIAlBADYCpAIgCUIBNwKcAiAJIBE2AqwCIAkgBjYCqAIgCSASNgK8AiAJIAkoAsQBNgJAIAkgCSgCtAM2ArACIAlB4ANqIQkgAUECaiIBIAhHDQALCyALQQFxDQAgCUIANwJEIAlBADYCNCAJQgE3AiwgCSANNgJUIAkgETYCPCAJIBI2AkwgCSAGNgI4IAkgCSgCxAE2AkALIAchDQwCCyAMEBALIAsQEAsgD0EgaiQAQQAgDSIHRQ0AGiAoQQFqIQ4gFSEdIAchCwJAAkADQCALKAJUQX9GDQIgHigCEEECdBAUIgFFDQIgAUEBIB4oAhBBAnQQFSEJIAsQVwRAA0AgICgCFCEIAkACQCALKAIoIBwoAgxPDQAgCygCICIBIAggCygCHEHMAGxqIgYoAhhPDQAgBigCHCABQZgBbGoiDSgCGEUNACANQRxqIQhBACEBAkADQCAZIAsoAhwgCygCICAIIAFBJGxqIgYoAhAgBigCFCALKAIkQShsaiIGKAIAIAYoAgQgBigCCCAGKAIMEDlFBEAgAUEBaiIBIA0oAhhJDQEMAgsLIAkgCygCHEECdGpBADYCACATQQA2AmggGygCBCAgKAIUIBwgCyATQewAaiAdIBNB6ABqIAogMxBWRQ0GIAsoAiAhCCALKAIcIRYgEygCaCEaIBMoAmwEQCATQQA2AmggICgCFCAWQcwAbGooAhwgCEGYAWxqIh8oAhgiAQR/IAogGmshGCAKIB1qISEgH0EcaiEMQQAhEUEAIQ8gGiAdaiIiIRIDQAJAIAwoAgggDCgCAEYNACAMKAIMIAwoAgRGDQAgDCgCFCALKAIkQShsaiIGKAIUIAYoAhBsIihFDQAgBigCGCEBQQAhFgNAIA8EQCABQQA2AjQLIAEoAiQiFwRAIAEoAgAhCAJAIAEgASgCKCIGBH8gCCAGQRhsaiIIQRRrKAIAIAhBDGsoAgBHBEAgCEEYayEIDAILIAZBAWoFQQELNgIoCwJAA0ACQAJAAkAgCCgCFCINIBJBf3NLDQAgDw0AIA0gEmogIU0NAQsgCygCHCEGIAsoAiAhFyALKAIkIQ8gGygCBCgCaARAIBMgBjYCWCATIBc2AlQgEyARNgJQIBMgDzYCTCATIBY2AkggEyAYNgJEIBMgDTYCQCAzQQFB8u0AIBNBQGsQDwwRCyATIAY2AjggEyAXNgI0IBMgETYCMCATIA82AiwgEyAWNgIoIBMgGDYCJCATIA02AiAgM0ECQfLtACATQSBqEA8gAUEANgI0IAggCCgCECIGIAgoAgRqNgIEIAEgASgCJCINIAZrIhc2AiRBASEPIAYgDUYNASABIAEoAihBAWoiCDYCKAwDCyABKAIEIRAgASgCNCIPIAEoAjhHBH8gFwUgECAPQQF0QQFyIgZBA3QQFyIQRQRAIDNBAUGACEEAEA8MEQsgASAGNgI4IAEgEDYCBCABKAI0IQ8gCCgCFCENIAEoAiQLIQYgECAPQQN0aiIXIA02AgQgFyASNgIAIAEgD0EBajYCNCAIIAgoAgAgDWo2AgAgCCAIKAIQIhAgCCgCBGoiDzYCBCABIAYgEGsiFzYCJCAIIA82AgggDSASaiESQQAhDyAGIBBGDQAgASABKAIoQQFqNgIoIAhBGGohCAsgFw0ACyABKAIoIQgLIAEgCDYCLAsgAUFAayEBIBZBAWoiFiAoRw0ACyAfKAIYIQELIAxBJGohDCARQQFqIhEgAUkNAAsgCygCHCEWIAsoAiAhCCAYIBIgImsgDxsFQQALIBpqIRoLIB4oAhggFkE0bGoiASAIIAEoAiQiASABIAhJGzYCJAwCCyAgKAIUIQgLIBNBADYCaCAbKAIEIAggHCALIBNB7ABqIB0gE0HoAGogCiAzEFZFDQQgCygCHCEWIBMoAmghGiATKAJsRQ0AAkAgICgCFCAWQcwAbGooAhwgCygCICIiQZgBbGoiASgCGCIoRQRAQQAhFwwBCyAKIBprIRAgAUEcaiEMIAsoAiQhIUEAIRdBACEYA0ACQCAMKAIIIAwoAgBGDQAgDCgCDCAMKAIERg0AIAwoAhQgIUEobGoiASgCFCABKAIQbCInRQ0AIAEoAhghEUEAIR8DQCARKAIkIgEEQCARKAIAIQgCQCARIBEoAigiEgR/IAggEkEYbGoiCEEUaygCACAIQQxrKAIARwRAIAhBGGshCAwCCyASQQFqBUEBCyISNgIoCwJAAkAgCCgCFCIPIBdqIg0gD0kNACANIBBLDQADQCANIRcgCCAIKAIQIg0gCCgCBGo2AgQgASANayEGIAEgDUYNAiARIBJBAWoiEjYCKCAIKAIsIg8gF2oiDSAPTwRAIAhBGGohCCAGIQEgDSAQTQ0BCwsgESAGNgIkCyAbKAIEKAJoIQEgEyAWNgIYIBMgIjYCFCATIBg2AhAgEyAhNgIMIBMgHzYCCCATIBA2AgQgEyAPNgIAIDNBAUECIAEbQZ3tACATEA8gAQ0KIAsoAhwhFgwFCyARIAY2AiQLIBFBQGshESAfQQFqIh8gJ0cNAAsLIAxBJGohDCAYQQFqIhggKEcNAAsLIBcgGmohGgsCQCAJIBZBAnRqKAIARQ0AIB4oAhggFkE0bGoiASgCJA0AIAEgICgCFCAWQcwAbGooAhhBAWs2AiQLIAogGmshCiAaIB1qIR0gCxBXDQALCyAJEBAgC0HwAWohCyAjQQFqIiMgHCgCpANNDQALIAcgDhA6IEAgHSAVazYCCEEBDAILIAcgDhA6IAkQEEEADAELIAcgDhA6QQALIXggE0HwAGokACAbECwgeEUNASAZKAIgKALQKyEJIBkoAhQoAgAiFigCFCEdIEBBATYCDEEAIQ1BACEVIBkoAiAiASgCDCABKAIIRgRAIAkoAhBBBHZBAXEhFQsCQCAWKAIQIjFFDQADQAJAIBkoAkQiAQRAIAEgDUECdGooAgBFDQELIEBBDGohE0EAITECQCAdKAIYIgFFDQAgGSgCLCEQA0AgHSgCHCAxQZgBbGoiDCgCGCILBEAgDEEcaiESIAwoAhQhASAMKAIQIRdBACEOA0AgASAXbARAIBIgDkEkbGohD0EAIQgDQCAZIB0oAhAgMSAPKAIQIA8oAhQgCEEobGoiBygCACAHKAIEIAcoAgggBygCDBA5IQYgBygCFCILIAcoAhAiCmwhAQJAIAYEQCABRQ0BQQAhCgNAAkAgGSAdKAIQIDEgDygCECAHKAIYIApBBnRqIgYoAgggBigCDCAGKAIQIAYoAhQQOUUEQCAGKAI8IgFFDQEgARAQIAZBADYCPAwBCyAZKAJARQRAIAYoAjwNASAGKAIQIAYoAghGDQEgBigCFCAGKAIMRg0BC0EBQSwQEyIBRQRAIEBBADYCDAwKCyAZKAJAIQsgAUEANgIkIAEgEzYCHCABIAk2AhQgASAdNgIQIAEgDzYCDCABIAY2AgggASAxNgIEIAEgCzYCACABIBU2AiggASAzNgIgIAEgECgCBEEBSjYCGCAQQQ4gARAtIEAoAgxFDQkLIApBAWoiCiAHKAIUIAcoAhBsSQ0ACwwBCyABRQ0AQQAhFwNAIAcoAhggF0EGdGoiASgCPCIGBEAgBhAQIAFBADYCPCAHKAIQIQogBygCFCELCyAXQQFqIhcgCiALbEkNAAsLIAhBAWoiCCAMKAIUIgEgDCgCECIXbEkNAAsgDCgCGCELCyAOQQFqIg4gC0kNAAsgHSgCGCEBCyAxQQFqIjEgAUkNAAsLIEAoAgxFDQIgFigCECExCyAJQbgIaiEJIB1BzABqIR0gDUEBaiINIDFJDQALC0EAITEgGSgCLBAgIEAoAgxFDQECQCAZKAJADQAgGSgCGCIdKAIQRQ0AQQAhCQNAIBkoAhQoAgAoAhQgCUHMAGxqIgEoAhwgHSgCGCAJQTRsaigCJEGYAWxqIgcoAogBIQYgBygCkAEhCyAHKAKMASEKIAcoApQBIQcgASgCNBAQIAFBADYCNAJAIBkoAkQiDQRAIA0gCUECdGooAgBFDQELIAYgC0YNACAHIApGDQAgByAKayIHrSALIAZrIgatfkIgiEIAUgRAIDNBAUGUFkEAEA8MBQsgBiAHbCIHQYCAgIAETwRAIDNBAUGUFkEAEA8MBQsgASAHQQJ0EBgiATYCNCABDQAgM0EBQZQWQQAQDwwECyAJQQFqIgkgGSgCGCIdKAIQSQ0ACwsgGSgCICEdIBkoAhQoAgAiFygCEARAIBcoAhQhCSAdKALQKyEdIBkoAhgoAhghDUEAIQsDQAJAIBkoAkQiAQRAIAEgC0ECdGooAgBFDQELIA0oAiRBAWohASAdKAIUQQFGBEAgASEeQQAhBkEAIQz9DAAAAAAAAAAAAAAAAAAAAAAhgAEjAEEgayIlJAACQAJAIBkoAkAEQEEBIQcgAUEBRg0CIAkoAhwiDCAJKAIYQZgBbGoiAUGQAWsoAgAiECABQZgBaygCACITRg0CIAwoAgQhESAMKAIMIRggDCgCACEaIAwoAgghGyAZKAIsIg4oAgQhFiAeQQFrIgohFSAMIQcCQCAKQQRPBEAgCkEDcSEVIAcgCkF8cSIIQZgBbGohB0EAIQEDQCCAASAMIAFBmAFsaiIGQegEaiAGQdADaiAGQbgCaiAG/QkCoAH9VgIAAf1WAgAC/VYCAAMgBkHgBGogBkHIA2ogBkGwAmogBv0JApgB/VYCAAH9VgIAAv1WAgAD/bEB/bkBIAZB7ARqIAZB1ANqIAZBvAJqIAb9CQKkAf1WAgAB/VYCAAL9VgIAAyAGQeQEaiAGQcwDaiAGQbQCaiAG/QkCnAH9VgIAAf1WAgAC/VYCAAP9sQH9uQEhgAEgAUEEaiIBIAhHDQALIIABIIABIIAB/Q0ICQoLDA0ODwABAgMAAQID/bkBIoABIIABIIAB/Q0EBQYHAAECAwABAgMAAQID/bkB/RsAIQYgCCAKRg0BCwNAIAYgBygCoAEgBygCmAFrIgEgASAGSRsiASAHKAKkASAHKAKcAWsiBiABIAZLGyEGIAdBmAFqIQcgFUEBayIVDQALC0EAIQcgBkH///8/Sw0CICUgBkEFdCISEDEiDzYCECAPRQ0CICUgDzYCACAKBEAgECATayEQIBggEWshCCAbIBprIQEDQCAJKAIkIRMgJSAIIhU2AgggJSABIgc2AhggDCgCnAEhBiAMKAKkASEIIAwoAqABIQEgJSAMKAKYASIRQQJvNgIcICUgASARayIBIAdrNgIUAkAgFkECSCIaRSAIIAZrIghBAUtxRQRAQQAhBiAIRQ0BA0AgJUEQaiATIAYgEGxBAnRqEF0gBkEBaiIGIAhHDQALDAELIAggFiAIIBZJGyIRQQFrIRsgCCARbiEYQQAhBwNAQSQQFCIGRQ0FICX9AAIQIYABIAYgEzYCGCAGIBA2AhQgBiABNgIQIAYggAH9CwIAIAYgByAYbDYCHCAHIBtGIR8gBiAIIAdBAWoiByAYbCAfGzYCICAGIBIQMSIfNgIAIB9FBEBBACEHIA4QICAGEBAgDxAQDAcLIA5BCiAGEC0gByARRw0ACyAOECALICUgCCAVazYCBCAlIAwoApwBQQJvNgIMAkAgGkUgAUEBS3FFBEBBCCEHQQAhBiABQQhPBEADQCAlIBMgBkECdGogEEEIEDAgByIGQQhqIgcgAU0NAAsLIAEgBk0NASAlIBMgBkECdGogECABIAZrEDAMAQsgASAWIAEgFkkbIhVBAWshGCABIBVuIRFBACEHA0BBJBAUIgZFDQUgJf0AAgAhgAEgBiATNgIYIAYgEDYCFCAGIAg2AhAgBiCAAf0LAgAgBiAHIBFsNgIcIAcgGEYhGiAGIAEgB0EBaiIHIBFsIBobNgIgIAYgEhAxIho2AgAgGkUEQEEAIQcgDhAgIAYQECAPEBAMBwsgDkELIAYQLSAHIBVHDQALIA4QIAsgDEGYAWohDCAKQQFrIgoNAAsLQQEhByAPEBAMAgtBASEHIAkoAhwiCCAeQZgBbGoiNUGYAWsiXygCACA1QZABaygCAEYNASA1QZQBayJgKAIAIDVBjAFrKAIARg0BIAgoAgQhDiAIKAIMIQ8gCCgCACEWIAgoAgghECAJKAJEISEgCSgCQCEiIAkoAjwhKCAJKAI4ITAgCSAeEFwiOUUEQEEAIQcMAgsCQAJAIB5BAUcEQAJAAkAgHkEBayIKQQRJBEAgCiEBIAghBwwBCyAKQQNxIQEgCCAKQXxxIhVBmAFsaiEHA0AggAEgCCAMQZgBbGoiBkHoBGogBkHQA2ogBkG4AmogBv0JAqAB/VYCAAH9VgIAAv1WAgADIAZB4ARqIAZByANqIAZBsAJqIAb9CQKYAf1WAgAB/VYCAAL9VgIAA/2xAf25ASAGQewEaiAGQdQDaiAGQbwCaiAG/QkCpAH9VgIAAf1WAgAC/VYCAAMgBkHkBGogBkHMA2ogBkG0AmogBv0JApwB/VYCAAH9VgIAAv1WAgAD/bEB/bkBIYABIAxBBGoiDCAVRw0ACyCAASCAASCAAf0NCAkKCwwNDg8AAQIDAAECA/25ASKAASCAASCAAf0NBAUGBwABAgMAAQIDAAECA/25Af0bACEGIAogFUYNAQsDQCAGIAcoAqABIAcoApgBayIKIAYgCksbIgYgBygCpAEgBygCnAFrIgogBiAKSxshBiAHQZgBaiEHIAFBAWsiAQ0ACwsgBkGAgICAAU8NAiAGQQR0EDEiFEUNAgJAIB5FDQAgDyAOayESIBAgFmshGiAUQQRrITsgFEEEaiEkIBRBDGohKSAUQRxqIUMgFEEYaiEfIBRBFGohICAUQQxrIUQgFEEIaiEqIBRBEGohNiAUQRBrITcgFEEIayFBICGtIYYBICKtIYcBICitIYoBIDCtIYsBQQEhRgNAIAgoApwBIgFBAm8hRyAIKAKYASIHQQJvITwgCCgCpAEgAWsiJyASayEsIAgoAqABIAdrIi0gGmshLiAwIgwhByAoIgYhCiAiIgEhOiAhIg8hEQJAIAkoAhQiFSBGRg0AIBUgRmshFUEAIQpBACEHIAwEQEJ/IBWtIogBhkJ/hSCLAXwgiAGIpyEHCyAoBEBCfyAVrSKIAYZCf4UgigF8IIgBiKchCgtBACEPQQAhASAiBEBCfyAVrSKIAYZCf4UghwF8IIgBiKchAQsgIQRAQn8gFa0iiAGGQn+FIIYBfCCIAYinIQ8LQQAhOkEAIQxBASAVQQFrdCIOIDBJBEAgMCAOa61CfyAVrSKIAYZCf4V8IIgBiKchDAsgDiAiSQRAICIgDmutQn8gFa0iiAGGQn+FfCCIAYinIToLQQAhEUEAIQYgDiAoSQRAICggDmutQn8gFa0iiAGGQn+FfCCIAYinIQYLIA4gIU8NACAhIA5rrUJ/IBWtIogBhkJ/hXwgiAGIpyERC0F/IDogCCgCtAEiFWsiDkEAIA4gOk0bIg5BAmoiFiAOIBZLGyIOIC4gDiAuSRsiNEF/IAEgCCgC2AEiE2siDkEAIAEgDk8bIgFBAmoiDiABIA5LGyIBIBogASAaSRsiJiA8G0EBdCIBICYgNCA8G0EBdEEBciIOIAEgDksbIkggLUkhGCAMIBVrIgFBACABIAxNGyIBQQJrIgxBACABIAxPGyIQIAcgE2siAUEAIAEgB00bIgFBAmsiDEEAIAEgDE8bIhYgPBtBAXQiDCAWIBAgPBtBAXRBAXIiK0khLyAKIAgoArgBIhtrIhVBACAKIBVPGyIKQQJrIhVBACAKIBVPGyIVISMgBiAIKALcASIKayIOQQAgBiAOTxsiBkECayIOQQAgBiAOTxsiDiE9QX8gDyAbayIGQQAgBiAPTRsiBkECaiIPIAYgD0sbIgYgEiAGIBJJGyIbIT5BfyARIAprIgZBACAGIBFNGyIGQQJqIgogBiAKSxsiBiAsIAYgLEkbIhwhPyBHBEAgFSE9IBwhPiAbIT8gDiEjCyBIIC0gGBshSSAMICsgLxshGCASIBxqIVAgDiASaiFRICcEQCAUIBZBA3QiBmoiRUEEaiA7IC5BA3QiCmoiUiAWIC5IIgwbIVMgBiAkaiIGICYgLiAmIC5IGyIPIAcgEyAHIBNJG0ECIAEgAUECTxtqIgFqIhMgB2tBAmsiEUEDdCIraiAGSSApIAcgAWtBA3RqIgEgK2ogAUlyIBFB/////wFLciFUIDQgGkEBayAaIDRKGyEvQQAhESAaQQFKIC5BAEpyIVUgJCA8QQJ0IgFrIBBBA3RqIVYgASBFaiFXIBYgB0F/cyATaiJKQXxxIjJqITggFkEBaiITIDJqIUIgGiA0aiFYIBAgGmohWSAW/RH9DAAAAAABAAAAAgAAAAMAAAD9rgEhgwEgFCAYQQJ0aiFaIEEgGkEDdCIBaiFLIAEgO2ohTCAKIEFqIU0gGkUgLkEBRnEhWyAUIElBAnQiAWohXCABIDtqIV0gE/0R/QwAAAAAAQAAAAIAAAADAAAA/a4BIYQBIDsgFiAuIAwbQQN0aiFeA0ACQAJAIBEgG0kgESAVT3ENACARIFBJIBEgUU9xDQAgEUEBaiErDAELIC0gSEsEQCBdQQA2AgAgXEEANgIACyA5IBYgESAmIBFBAWoiKyBXQQJBABAeIDkgWSARIFggKyBWQQJBABAeAkACQAJAIDxFBEAgVUUNAyAWICZODQICQAJAIBZBAEoEQCBeKAIAIQcMAQsgJCgCACIHIQEgFkEASA0BCyAHIQEgUygCACEHCyBFIEUoAgAgASAHakECakECdWs2AgAgEyIHIA9ODQFBACEHIIQBIYABIIMBIYIBIBMhASAWIQogSkEUSSBUckUEQANAIBQggAFBAf2rASKBAf0bAEECdGoiASAUIIEB/RsDQQJ0aiIGIBQggQH9GwJBAnRqIgogFCCBAf0bAUECdGoiDCAB/QkCAP1WAgAB/VYCAAL9VgIAAyAUIIIBQQH9qwH9DAEAAAABAAAAAQAAAAEAAAD9UCKFAf0bA0ECdGogFCCFAf0bAkECdGogFCCFAf0bAUECdGogFCCFAf0bAEECdGr9CQIA/VYCAAH9VgIAAv1WAgADIBQggQH9DAEAAAABAAAAAQAAAAEAAAD9UCKBAf0bA0ECdGogFCCBAf0bAkECdGogFCCBAf0bAUECdGogFCCBAf0bAEECdGr9CQIA/VYCAAH9VgIAAv1WAgAD/a4B/QwCAAAAAgAAAAIAAAACAAAA/a4BQQL9rAH9sQEigQH9WgIAACAMIIEB/VoCAAEgCiCBAf1aAgACIAYggQH9WgIAAyCCAf0MBAAAAAQAAAAEAAAABAAAAP2uASGCASCAAf0MBAAAAAQAAAAEAAAABAAAAP2uASGAASAHQQRqIgcgMkcNAAsgQiEBIDghCiAPIQcgMiBKRg0CCwNAIBQgAUEDdGoiByAHKAIAIBQgCkEDdGooAgQgBygCBGpBAmpBAnVrNgIAIAEiCkEBaiIBIA9HDQALIA8hBwwBCwJAIFtFBEAgFiIHICZODQEDQCAUIAdBA3RqIgEoAgQhBiABIAYCfwJAIAdBAE4EQCABIE0gByAuSBsoAgAhOiAHQQFqIQEMAQsgFCgCACE6QQAhASAUIAdBAWoiBw0BGgsgASAuTgRAIAEhByBNDAELIBQgASIHQQN0agsoAgAgOmpBAmpBAnVrNgIEIAcgJkgNAAsMAQsgFCAUKAIAQQJtNgIADAMLIBAiByA0Tg0CA0AgFCAHQQN0aiIBKAIAIQoCfyAHQQBIBEAgJCgCACEGICQMAQsgFCAHQQN0akEEaiBMIAcgGkgbKAIAIQYgJCAHRQ0AGiBMIAFBBGsgByAaShsLIQwgASAMKAIAIAZqQQF1IApqNgIAIAdBAWoiByA0Rw0ACwwCCyAHICZODQADQCAUIAdBA3RqIgEgASgCAAJ/AkAgB0EASgRAIDsgByAuIAcgLkgbQQN0aigCACEKDAELICQoAgAhCiAkIAdBAEgNARoLIFIgByAuTg0AGiAUIAdBA3RqQQRqCygCACAKakECakECdWs2AgAgB0EBaiIHICZHDQALCyAQIDRODQAgLyAQIgEiB0oEQANAIBQgB0EDdGoiASABKAIEIBQgB0EBaiIHQQN0aigCACABKAIAakEBdWo2AgQgByAvRw0ACyAvIQELIAEgNE4NAANAAn8CQCABIgdBAE4EQCAUIAFBA3RqIEsgASAaSBsoAgAhDCABQQFqIQoMAQsgFCgCACEMQQAhCiAUIAdBAWoiAQ0BGgsgCiAaTgRAIAohASBLDAELIBQgCiIBQQN0agshBiAUIAdBA3RqIgcgBygCBCAGKAIAIAxqQQF1ajYCBCABIDRIDQALCyA5IBggESBJICsgWkEBQQBBABAmRQ0GCyArIhEgJ0cNAAsLIAhBmAFqIQggPkEBdCIBID9BAXRBAXIiByABIAdLGyIBICcgASAnSRshSCBDIBVBBXQiAWogOyAsQQV0IgdqIBUgLEgiBhshSiABIB9qIAcgQWogBhshSyABICBqIAcgRGogBhshTCABIDZqIAcgN2ogBhshTSAcIBJBAWsgEiAcShshDCAsQQBKIg8gEkEBSnIhUiABIBRqIisgR0EEdGohUyApIBJBA3QiGkEIayI+QQAgEkEATBtBAnQiCmohVCAKICpqIVUgCiAkaiFWIAogFGohVyApQQAgLEEDdCIKQQhrIj8gDxtBAnQiD2ohWCAPICpqIVkgDyAkaiFaIA8gFGohWyAUQQQgR0ECdGtBAnRqIA5BBXRqIVwgGyAsIBsgLEgbIQ8gFUEBaiEQIBQgI0EBdCIWID1BAXRBAXIiEyATIBZLGyJdQQR0aiFeIAEgKWohPSABICpqISMgASAkaiEvIBpBAWshOCAaQQJrIUIgGkEDayEuIBQgEkEFdGohYSAaQQRrITQgCkEFayFiIApBBmshYyAKQQdrIWQgEkUgLEEBRnEhZSApIAdBEGsiAWohJiABICpqITogASAkaiE8IAEgFGohRSApID5BAnQiAWohaCABICpqIWkgASAkaiFqIAEgFGohayA7IBUgLCAGG0EFdCIBaiFsIAEgQWohEyABIERqIREgASA3aiFtICkgP0ECdCIBaiFuIAEgKmohbyABICRqIXAgASAUaiFxA0ACQAJAAn8CQCAYIhYgSUkEQCA5IBYgFUEEIEkgFmsiASABQQRPGyAWaiIYIBsgU0EBQQgQHiA5IBYgUSAYIFAgXEEBQQgQHiBHRQRAIFJFDQUgFSAbTg0EAn8gFUEASgRAIG0oAgAhByATIQYgESEKIGwMAQsgNigCACEHIBVBAEgNAyAfIQYgICEKIEMLIXkgKyArKAIAIAcgTSgCAGpBAmpBAnVrNgIAIC8gLygCACAKKAIAIEwoAgBqQQJqQQJ1azYCACAjICMoAgAgBigCACBLKAIAakECakECdWs2AgAgSigCACEHIHkoAgAMAwsgZQRAIBQgFCgCAEECbTYCACAkICQoAgBBAm02AgAgKiAqKAIAQQJtNgIAICkgKSgCAEECbTYCAAwFCyAbIBUiB0oEQANAIAdBA3QhAQJ/AkAgB0EASARAIAdBf0YNASAUIAFBAnRqIgEgASgCECAUKAIAQQF0QQJqQQJ1azYCECABIAEoAhQgJCgCAEEBdEECakECdWs2AhQgASABKAIYICooAgBBAXRBAmpBAnVrNgIYICkoAgBBAXRBAmohBiABQRxqDAILICwgB0EBaiIGTARAIBQgAUECdGoiCiAKKAIQIBQgASA/IAcgLEgiBhtBAnRqKAIAIHEoAgBqQQJqQQJ1azYCECAKIAooAhQgFCABQQFyIGQgBhtBAnRqKAIAIHAoAgBqQQJqQQJ1azYCFCAKIAooAhggFCABQQJyIGMgBhtBAnRqKAIAIG8oAgBqQQJqQQJ1azYCGCAUIAFBA3IgYiAGG0ECdGooAgAgbigCAGpBAmohBiAKQRxqDAILIBQgAUECdGoiASABKAIQIAEoAgAgFCAGQQV0aiIGKAIAakECakECdWs2AhAgASABKAIUIAEoAgQgBigCBGpBAmpBAnVrNgIUIAEgASgCGCABKAIIIAYoAghqQQJqQQJ1azYCGCABKAIMIAYoAgxqQQJqIQYgAUEcagwBCyA3IDcoAgAgFCgCACBbKAIAakECakECdWs2AgAgRCBEKAIAICQoAgAgWigCAGpBAmpBAnVrNgIAIEEgQSgCACAqKAIAIFkoAgBqQQJqQQJ1azYCACApKAIAIFgoAgBqQQJqIQYgOwsiASABKAIAIAZBAnVrNgIAIAdBAWoiByAbRw0ACwsgHCAOIgdMDQQDQCAHQQN0IQECfyAHQQBIBEAgFCABQQJ0aiIBIAEoAgAgNigCAEEBdEEBdWo2AgAgASABKAIEIBQoAhRBAXRBAXVqNgIEIAEgASgCCCAUKAIYQQF0QQF1ajYCCCAUKAIcQQF0IQogAUEMagwBCyAHBEAgFCABQQJ0aiIGIAYoAgAgYSAGIAcgEkoiMhtBEGsoAgAgFCABQQRyIDQgByASSCIKG0ECdGooAgBqQQF1ajYCACAGIAYoAgQgRCAaIAEgMhtBAnQiMmooAgAgFCABQQVyIC4gChtBAnRqKAIAakEBdWo2AgQgBiAGKAIIIDIgQWooAgAgFCABQQZyIEIgChtBAnRqKAIAakEBdWo2AgggMiA7aigCACAUIAFBB3IgOCAKG0ECdGooAgBqIQogBkEMagwBCyAUIBQoAgAgNigCACAUQQQgNCAHIBJIIgEbQQJ0aigCAGpBAXVqNgIAICQgJCgCACAUKAIUIBRBBSAuIAEbQQJ0aigCAGpBAXVqNgIAICogKigCACAUKAIYIBRBBiBCIAEbQQJ0aigCAGpBAXVqNgIAIBQoAhwgFEEHIDggARtBAnRqKAIAaiEKICkLIgEgASgCACAKQQF1ajYCACAHQQFqIgcgHEcNAAsMBAsgLSEaICchEiBGQQFqIkYgHkcNBQwGCyArICsoAgAgB0EBdEECakECdWs2AgAgLyAvKAIAICAoAgBBAXRBAmpBAnVrNgIAICMgIygCACAfKAIAQQF0QQJqQQJ1azYCACBDKAIAIgcLIQEgPSA9KAIAIAEgB2pBAmpBAnVrNgIAIBUhBiAQIgEiByAPSARAA0AgFCABQQV0aiIHIAf9AAIAIDYgBkEFdGr9AAIAIAf9AAIQ/a4B/QwCAAAAAgAAAAIAAAACAAAA/a4BQQL9rAH9sQH9CwIAIAEiBkEBaiIBIA9HDQALIA8hBwsgByAbTg0AA0AgB0EDdCEBIAcgLEghBgJAIAdBAEwEQCA2KAIAIQogB0EATgRAIBQgAUECdCIBaiIyIDIoAgAgCiABIDZqIEUgBhsoAgBqQQJqQQJ1azYCACABICRqIgogCigCACAgKAIAIAEgIGogPCAGGygCAGpBAmpBAnVrNgIAIAEgKmoiCiAKKAIAIB8oAgAgASAfaiA6IAYbKAIAakECakECdWs2AgAgQygCACABIENqICYgBhsoAgBqQQJqIQYgASApaiEBDAILIBQgAUECdCIBaiIGIAYoAgAgCkEBdEECakECdWs2AgAgASAkaiIGIAYoAgAgFCgCFEEBdEECakECdWs2AgAgASAqaiIGIAYoAgAgFCgCGEEBdEECakECdWs2AgAgASApaiEBIBQoAhxBAXRBAmohBgwBCyAUIAcgLCAGG0EDdEEEa0ECdCIKaigCACEyIAZFBEAgFCABQQJ0IgFqIgYgBigCACAyIEUoAgBqQQJqQQJ1azYCACABICRqIgYgBigCACAKICRqKAIAIDwoAgBqQQJqQQJ1azYCACABICpqIgYgBigCACAKICpqKAIAIDooAgBqQQJqQQJ1azYCACABIClqIQEgCiApaigCACAmKAIAakECaiEGDAELIBQgAUECdCIBaiIGIAYoAgAgMiAGKAIQakECakECdWs2AgAgASAkaiIGIAYoAgAgCiAkaigCACAGKAIQakECakECdWs2AgAgASAqaiIGIAYoAgAgCiAqaigCACAGKAIQakECakECdWs2AgAgCiApaigCACABIClqIgEoAhBqQQJqIQYLIAEgASgCACAGQQJ1azYCACAHQQFqIgcgG0cNAAsLIA4gHE4NACAMIA4iASIHSgRAA0AgFCABQQV0aiIHIAf9AAIgIAf9AAIA/a4BQQH9rAEgB/0AAhD9rgH9CwIQIAFBAWoiASAMRw0ACyAMIQcLIAcgHE4NAANAIEMgB0EDdCIBQQJ0aiIyAn8gB0EASARAIBQoAgAhBiAHQX9HBEAgNiABQQJ0IgFqIgogCigCACAGajYCACABICBqIgYgBigCACAkKAIAajYCACABIB9qIgEgASgCACAqKAIAajYCACApKAIADAILIDYgAUECdCIBaiIKIAooAgAgVygCACAGakEBdWo2AgAgASAgaiIGIAYoAgAgVigCACAkKAIAakEBdWo2AgAgASAfaiIBIAEoAgAgVSgCACAqKAIAakEBdWo2AgAgVCgCACApKAIAakEBdQwBCyABID4gByASSBshBiASIAdBAWoiZkwEQCA2IAFBAnQiCmoiASABKAIAIGsoAgAgFCAGQQJ0aiIBKAIAakEBdWo2AgAgCiAgaiIGIAYoAgAgaigCACABKAIEakEBdWo2AgAgCiAfaiIGIAYoAgAgaSgCACABKAIIakEBdWo2AgAgaCgCACABKAIMakEBdQwBCyA2IAFBAnQiCmoiASABKAIAIBQgZkEFdGoiASgCACAUIAZBAnRqIgYoAgBqQQF1ajYCACAKICBqImYgZigCACABKAIEIAYoAgRqQQF1ajYCACAKIB9qIgogCigCACABKAIIIAYoAghqQQF1ajYCACABKAIMIAYoAgxqQQF1CyAyKAIAajYCACAHQQFqIgcgHEcNAAsLIDkgFiBdIBggSCBeQQFBBEEAECYNAAsLDAILIBQQEEEBIQcLIDkgNUEQaygCACIBIF8oAgAiBmsgNUEMaygCACBgKAIAIgprIDVBCGsoAgAiCCAGayA1QQRrKAIAIAprIAkoAjRBASAIIAFrEB4gORAjDAMLIDkQIyAUEBBBACEHDAILIDkQI0EAIQcMAQtBACEHIA4QICAPEBALICVBIGokACAHDQEMBQsgASEIQQAhDv0MAAAAAAAAAAAAAAAAAAAAACGAASMAQUBqIhwkAAJAAn8CQCAZKAJABEAgCSgCHCIVIAkoAhhBmAFsaiIBQZgBaygCACEaIAFBkAFrKAIAIRsgFSgCBCEMIBUoAgwheiAVKAIAIRAgFSgCCCETQQEhByAZKAIsIh8oAgQhKyAIQQFGDQNBACEGIAhBAWsiFiEIIBUhAQJAIBZBBE8EQCAWQQNxIQggASAWQXxxIgpBmAFsaiEBQQAhBwNAIIABIBUgB0GYAWxqIgZB6ARqIAZB0ANqIAZBuAJqIAb9CQKgAf1WAgAB/VYCAAL9VgIAAyAGQeAEaiAGQcgDaiAGQbACaiAG/QkCmAH9VgIAAf1WAgAC/VYCAAP9sQH9uQEgBkHsBGogBkHUA2ogBkG8AmogBv0JAqQB/VYCAAH9VgIAAv1WAgADIAZB5ARqIAZBzANqIAZBtAJqIAb9CQKcAf1WAgAB/VYCAAL9VgIAA/2xAf25ASGAASAHQQRqIgcgCkcNAAsggAEggAEggAH9DQgJCgsMDQ4PAAECAwABAgP9uQEigAEggAEggAH9DQQFBgcAAQIDAAECAwABAgP9uQH9GwAhBiAKIBZGDQELA0AgBiABKAKgASABKAKYAWsiByAGIAdLGyIHIAEoAqQBIAEoApwBayIGIAYgB0kbIQYgAUGYAWohASAIQQFrIggNAAsLQQAhByAGQf///z9LDQMgHCAGQQV0IkYQGCIBNgIgIAFFDQMgHCABNgIAIBZFBEBBASEHIAEQEAwECyB6IAxrIQ8gEyAQayEOQQIgK0EBdiIBIAFBAk0bIUcgCSgCJCIKIBtBHGwiTSAaQRxsIl9raiEvIAogG0EYbCJgIBpBGGwiUmtqIT0gCiAbQRRsIlMgGkEUbCJUa2ohPiAKIBtBBHQiVSAaQQR0IlZraiE/IAogG0EMbCJXIBpBDGwiWGtqITggGyAaayIQQQdsIUkgEEEGbCFFIBBBBWwhMiAQQQNsIUggEEEBdCFQIAogEEEDdCJRaiFCIAogEEECdCJBaiEUIBBBBXQhWSAQ/REhhAEDQCAcIA82AgggHCAOIgE2AiggFSgCnAEhJCAVKAKkASEpIBUoAqABIR4gFSgCmAEhICAcQQA2AjggHCABNgI0IBxBADYCMCAcICBBAm8iGDYCLCAcIB4gIGsiDiABayITNgI8IBwgEzYCJAJAICtBAkgiWkUgKSAkayIPQQ9LcUUEQEEAIQcgCiEGIA9BCEkNASA/IAYgUyAeQQJ0IgFqIFQgIEECdCIIamtqIjpJID4gBiABIFVqIAggVmpraiJDSXEgPSBDSSA/IAYgASBgaiAIIFJqa2oiPElxciAvIENJID8gBiABIE1qIAggX2praiJESXFyIVsgPSBESSAvIDxJcSFcID4gREkgLyA6SXEhXSA8ID5LIDogPUtxIV4gQiAGIAEgV2ogCCBYamtqIkpJIDggBiABIFFqIAhraiJLSXEhYSAUIEpJIDggBiAbIB5qIBogIGprQQJ0aiJMSXEhYiAUIEtJIEIgTElxIWMgBiABIAhraiEqIA5BfHEhCCAcKAIgIhMgDkEFdGoiEUEQayElIBFBFGshLCARQRhrIS4gEUEcayE2IBFBBGshOSARQQhrITsgEUEMayE0QQAhGCATQQxqIiMgHiAgQX9zaiIMQQV0IgFqICNJIAxB////P0siDCATQQRqIiEgAWogIUkgASATaiATSXJyIBNBCGoiIiABaiAiSXJyIA5ByAJJciFkIBNBFGoiKCABaiAoSSATQRBqIicgAWogJ0lyIAxyIBNBGGoiMCABaiAwSXIgE0EcaiItIAFqIC1JciAOQdQASXIhZQNAIAchDCAcQSBqIgEgBiAQQQgQOyABECICQCAORQ0AIBggWWwhB0EAIQECQAJAIGQNACBhIAYgNkkgEyAHICpqIjdJcSAGIAcgSmoiEkkgKiA4S3EgFCAqSSAGIAcgTGoiJklxIAYgByBLaiI1SSAqIEJLcXJyciAGIC5JICEgN0lxciAGICxJICIgN0lxciAGICVJICMgN0lxciBjciBiciATICZJIAcgFGoiNyA2SXFyICEgJkkgLiA3S3FyICIgJkkgLCA3S3FyICMgJkkgJSA3S3Fycg0AIBMgNUkgByBCaiImIDZJcQ0AICEgNUkgJiAuSXENACAiIDVJICYgLElxDQAgIyA1SSAlICZLcQ0AIAcgOGoiJiA2SSASIBNLcQ0AICYgLkkgEiAhS3ENACAmICxJIBIgIktxDQAgEiAjSyAlICZLcQ0AA0AgBiABQQJ0aiATIAFBBXRqIhL9CQIAIBIqAiD9IAEgEkFAayoCAP0gAiASKgJg/SAD/QsCACAGIAEgEGpBAnRqIBL9CQIEIBIqAiT9IAEgEioCRP0gAiASKgJk/SAD/QsCACAGIAEgUGpBAnRqIBL9CQIIIBIqAij9IAEgEioCSP0gAiASKgJo/SAD/QsCACAGIAEgSGpBAnRqIBL9CQIMIBIqAiz9IAEgEioCTP0gAiASKgJs/SAD/QsCACABQQRqIgEgCEcNAAsgCCIBIA5GDQELA0AgBiABQQJ0aiATIAFBBXRqIhIqAgA4AgAgBiABIBBqQQJ0aiASKgIEOAIAIAYgASBQakECdGogEioCCDgCACAGIAEgSGpBAnRqIBIqAgw4AgAgAUEBaiIBIA5HDQALC0EAIQECQCBlDQAgXCAHID5qIhIgNEkgJyAHIDpqIiZJcSBbIAcgP2oiNSA0SSAnIAcgQ2oiN0lxciAoIDdJIDUgO0lxciAwIDdJIDUgOUlxciAtIDdJIBEgNUtxciBeciBdcnIgEiA7SSAmIChLcXIgEiA5SSAmIDBLcXIgJiAtSyARIBJLcXJyDQAgByA9aiISIDRJICcgByA8aiImSXENACASIDtJICYgKEtxDQAgEiA5SSAmIDBLcQ0AICYgLUsgESASS3ENACAHIC9qIhIgNEkgJyAHIERqIgdJcQ0AIBIgO0kgByAoS3ENACASIDlJIAcgMEtxDQAgByAtSyARIBJLcQ0AA0AgBiABIEFqQQJ0aiATIAFBBXRqIgf9CQIQIAcqAjD9IAEgByoCUP0gAiAHKgJw/SAD/QsCACAGIAEgMmpBAnRqIAf9CQIUIAcqAjT9IAEgByoCVP0gAiAHKgJ0/SAD/QsCACAGIAEgRWpBAnRqIAf9CQIYIAcqAjj9IAEgByoCWP0gAiAHKgJ4/SAD/QsCACAGIAEgSWpBAnRqIAf9CQIcIAcqAjz9IAEgByoCXP0gAiAHKgJ8/SAD/QsCACABQQRqIgEgCEcNAAsgCCIBIA5GDQELA0AgBiABIEFqQQJ0aiATIAFBBXRqIgcqAhA4AgAgBiABIDJqQQJ0aiAHKgIUOAIAIAYgASBFakECdGogByoCGDgCACAGIAEgSWpBAnRqIAcqAhw4AgAgAUEBaiIBIA5HDQALCyAYQQFqIRggDEEIaiEHIAYgUUECdGohBiAMQQ9qIA9JDQALDAELIA8gD0EDdiIHICsgByArSRsiEm5BeHEhESAPQXhxIQdBACEIIAohBgNAQTAQFCIMRQ0EIAwgRhAYIiM2AgAgI0UEQCAfECAgDBAQQQAMBgsgDCAGNgIoIAwgEDYCJCAMIA42AiAgDCATNgIcIAxBADYCGCAMIAE2AhQgDEEANgIQIAwgGDYCDCAMIAE2AgggDCATNgIEIAwgByAIIBFsayARIAhBAWoiCCASRhsiIzYCLCAfQQwgDBAtIAYgECAjbEECdGohBiAIIBJHDQALIB8QIAsCQCAHIA9PDQAgHEEgaiIBIAYgECAPIAdrIhgQOyABECIgDkUNACAcKAIgIiMgHkEFdEEBIBggGEEBTRsiEkECdGogIEEFdGtqQSBrIR4gEkEDcSEgIBJBfHEhDCBBIBJBAWtsISFBACEIA0AgIyAIQQV0aiETQQAhBwJAAkAgGEEESQ0AIB4gBiAIQQJ0IhFqIgEgBiARICFqaiIRIAEgEUkbSwRAICMgASARIAEgEUsbQQRqSQ0BCyAI/REhgQH9DAAAAAABAAAAAgAAAAMAAAAhgAFBACEBA0AgBiCAASCEAf21ASCBAf2uASKCAf0bAEECdGogEyABQQJ0av0AAgAigwH9HwA4AgAgBiCCAf0bAUECdGoggwH9HwE4AgAgBiCCAf0bAkECdGoggwH9HwI4AgAgBiCCAf0bA0ECdGoggwH9HwM4AgAggAH9DAQAAAAEAAAABAAAAAQAAAD9rgEhgAEgAUEEaiIBIAxHDQALIAwiByASRg0BC0EAIREgByEBICAEQANAIAYgASAQbCAIakECdGogEyABQQJ0aioCADgCACABQQFqIQEgEUEBaiIRICBHDQALCyAHIBJrQXxLDQADQCAGIAEgEGwgCGpBAnRqIBMgAUECdGoqAgA4AgAgBiABQQFqIgcgEGwgCGpBAnRqIBMgB0ECdGoqAgA4AgAgBiABQQJqIgcgEGwgCGpBAnRqIBMgB0ECdGoqAgA4AgAgBiABQQNqIgcgEGwgCGpBAnRqIBMgB0ECdGoqAgA4AgAgGCABQQRqIgFHDQALCyAIQQFqIgggDkcNAAsLIBwgDyAcKAIIIgxrIhM2AgQgFSgCnAEhASAcQQA2AhAgHCAMNgIUIBxBADYCGCAcIBM2AhwgHCABQQJvIhg2AgwCQCBaRSAOQQ9LcUUEQCAKIQEgDkEISQ0BIA9BfnEhISAPQQFxISIgE0F+cSEoIBNBAXEhJyAMQX5xITAgDEEBcSEtICkgJEF/c2ohIyAcKAIAIhIgGEEFdCIHaiEgIBIgB2tBIGohHiAMIBBsQQJ0ISogDiEIA0BBACEGQQAhBwJAAkACQCAMDgICAQALA0AgICAGQQZ0aiIRIAEgBiAQbEECdGoiJf0AAgD9CwIAIBEgJf0AAhD9CwIQICAgBkEBciIRQQZ0aiIlIAEgECARbEECdGoiEf0AAhD9CwIQICUgEf0AAgD9CwIAIAZBAmohBiAHQQJqIgcgMEcNAAsLIC1FDQAgICAGQQZ0aiIHIAEgBiAQbEECdGoiBv0AAgD9CwIAIAcgBv0AAhD9CwIQCwJAIAwgD0YNACABICpqIQdBACEGQQAhESAMICNHBEADQCAeIAZBBnRqIiUgByAGIBBsQQJ0aiIs/QACAP0LAgAgJSAs/QACEP0LAhAgHiAGQQFyIiVBBnRqIiwgByAQICVsQQJ0aiIl/QACEP0LAhAgLCAl/QACAP0LAgAgBkECaiEGIBFBAmoiESAoRw0ACwsgJ0UNACAeIAZBBnRqIhEgByAGIBBsQQJ0aiIH/QACAP0LAgAgESAH/QACEP0LAhALIBwQIgJAIA9FDQBBACEGQQAhByAjBEADQCABIAYgEGxBAnRqIhEgEiAGQQV0aiIl/QACAP0LAgAgESAl/QACEP0LAhAgASAGQQFyIhEgEGxBAnRqIiUgEiARQQV0aiIR/QACEP0LAhAgJSAR/QACAP0LAgAgBkECaiEGIAdBAmoiByAhRw0ACwsgIkUNACABIAYgEGxBAnRqIgcgEiAGQQV0aiIG/QACAP0LAgAgByAG/QACEP0LAhALIAFBIGohASAIQQhrIghBB0sNAAsMAQtBASAOQQN2IgEgRyABIEdJGyIIIAhBAU0bIREgDiAIbkF4cSESIA5BeHEhIEEAIQcgCiEBA0BBMBAUIgZFDQQgBiBGEBgiHjYCACAeRQRAIB8QICAGEBBBAAwGCyAGIAE2AiggBiAQNgIkIAYgDzYCICAGIBM2AhwgBkEANgIYIAYgDDYCFCAGQQA2AhAgBiAYNgIMIAYgDDYCCCAGIBM2AgQgBiAgIAcgEmxrIBIgB0EBaiIHIAhGGyIeNgIsIB9BDSAGEC0gASAeQQJ0aiEBIAcgEUcNAAsgHxAgCwJAIA5BB3EiEkUNACAYQQV0ISAgHCgCACEIAkAgDEUNACAIICBqIREgEkECdCEYQQAhBiAMQQFHBEAgDEF+cSEeQQAhBwNAIBEgBkEGdGogASAGIBBsQQJ0aiAYEBIaIBEgBkEBciIjQQZ0aiABIBAgI2xBAnRqIBgQEhogBkECaiEGIAdBAmoiByAeRw0ACwsgDEEBcUUNACARIAZBBnRqIAEgBiAQbEECdGogGBASGgsCQCAMIA9GDQAgCCAga0EgaiEHIAEgDCAQbEECdGohESASQQJ0IRhBACEGIAwgKSAkQX9zakcEQCATQX5xISBBACEMA0AgByAGQQZ0aiARIAYgEGxBAnRqIBgQEhogByAGQQFyIh5BBnRqIBEgECAebEECdGogGBASGiAGQQJqIQYgDEECaiIMICBHDQALCyATQQFxRQ0AIAcgBkEGdGogESAGIBBsQQJ0aiAYEBIaCyAcECIgD0UNACASQQJ0IQdBACEGICRBAWogKUcEQCAPQX5xIQxBACERA0AgASAGIBBsQQJ0aiAIIAZBBXRqIAcQEhogASAGQQFyIhMgEGxBAnRqIAggE0EFdGogBxASGiAGQQJqIQYgEUECaiIRIAxHDQALCyAPQQFxRQ0AIAEgBiAQbEECdGogCCAGQQV0aiAHEBIaCyAVQZgBaiEVIBZBAWsiFg0AC0EBDAILQQEhByAJKAIcIgwgCEGYAWxqIiNBmAFrIi8oAgAgI0GQAWsoAgBGDQIgI0GUAWsiPSgCACAjQYwBaygCAEYNAiAMKAIEIQ8gDCgCDCEWIAwoAgAhECAMKAIIIRMgCSgCRCESIAkoAkAhESAJKAI8IRogCSgCOCEfIAkgCBBcIh5FBEBBACEHDAMLIAhBAUYEQCAeICNBEGsoAgAiASAvKAIAIgZrICNBDGsoAgAgPSgCACIKayAjQQhrKAIAIgggBmsgI0EEaygCACAKayAJKAI0QQEgCCABaxAeIB4QIwwDC0EAIQYCQAJAIAhBAWsiCkEESQRAIAohByAMIQEMAQsgCkEDcSEHIAwgCkF8cSIVQZgBbGohAQNAIIABIAwgDkGYAWxqIgZB6ARqIAZB0ANqIAZBuAJqIAb9CQKgAf1WAgAB/VYCAAL9VgIAAyAGQeAEaiAGQcgDaiAGQbACaiAG/QkCmAH9VgIAAf1WAgAC/VYCAAP9sQH9uQEgBkHsBGogBkHUA2ogBkG8AmogBv0JAqQB/VYCAAH9VgIAAv1WAgADIAZB5ARqIAZBzANqIAZBtAJqIAb9CQKcAf1WAgAB/VYCAAL9VgIAA/2xAf25ASGAASAOQQRqIg4gFUcNAAsggAEggAEggAH9DQgJCgsMDQ4PAAECAwABAgP9uQEigAEggAEggAH9DQQFBgcAAQIDAAECAwABAgP9uQH9GwAhBiAKIBVGDQELA0AgBiABKAKgASABKAKYAWsiCiAGIApLGyIGIAEoAqQBIAEoApwBayIKIAYgCksbIQYgAUGYAWohASAHQQFrIgcNAAsLAkAgBkGAgIDAAE8NACAcIAZBBXQQGCIhNgIgICFFDQAgHCAhNgIAAkAgCARAIBYgD2shCiATIBBrIQYgIUEgaiE+IAitIYcBIBKtIYoBIBGtIYsBIBqtIYgBIB+tIYwBIAkoAhQiQq0hjQFCASGGAQNAIBwgCjYCCCAcIAY2AiggDCgCpAEhByAMKAKgASEIIAwoApwBIQEgHCAMKAKYASIVQQJvIiI2AiwgHCABQQJvIj82AgwgHCAIIBVrIiAgBmsiKDYCJCAcIAcgAWsiEyAKayI4NgIEIB8iFiEIIBoiASEOIBEiByEYIBIiFSEPAkAghgEgjQFRDQAgQiCGAadrIRBBACEOQQAhCCAWBEBCfyAQrSKJAYZCf4UgjAF8IIkBiKchCAsgGgRAQn8gEK0iiQGGQn+FIIgBfCCJAYinIQ4LQQAhFUEAIQcgEQRAQn8gEK0iiQGGQn+FIIsBfCCJAYinIQcLIBIEQEJ/IBCtIokBhkJ/hSCKAXwgiQGIpyEVC0EAIRhBACEWQQEgEEEBa3QiGyAfSQRAIB8gG2utQn8gEK0iiQGGQn+FfCCJAYinIRYLIBEgG0sEQCARIBtrrUJ/IBCtIokBhkJ/hXwgiQGIpyEYC0EAIQ9BACEBIBogG0sEQCAaIBtrrUJ/IBCtIokBhkJ/hXwgiQGIpyEBCyASIBtNDQAgEiAba61CfyAQrSKJAYZCf4V8IIkBiKchDwtBfyAYIAwoArQBIhBrIhtBACAYIBtPGyIYQQRqIhsgGCAbSxsiGCAoIBggKEkbIi1BfyAHIAwoAtgBIhhrIhtBACAHIBtPGyIHQQRqIhsgByAbSxsiByAGIAYgB0sbIisgIhtBAXQiByArIC0gIhtBAXRBAXIiGyAHIBtLGyIoICBJIRQgFiAQayIHQQAgByAWTRsiB0EEayIWQQAgByAWTxsiJyAIIBhrIgdBACAHIAhNGyIHQQRrIghBACAHIAhPGyIwICIbQQF0IhggMCAnICIbQQF0QQFyIiRJISkgDiAMKAK4ASIWayIHQQAgByAOTRsiB0EEayIIQQAgByAITxsiCCEQIAEgDCgC3AEiDmsiB0EAIAEgB08bIgFBBGsiB0EAIAEgB08bIgEhB0F/IBUgFmsiFkEAIBUgFk8bIhVBBGoiFiAVIBZLGyIVIAogCiAVSxsiFiEVQX8gDyAOayIOQQAgDiAPTRsiDkEEaiIPIA4gD0sbIg4gOCAOIDhJGyIbIQ8gPwRAIAEhECAWIQ8gGyEVIAghBwsgKCAgIBQbISggGCAkICkbIRggHCAtNgI8IBwgJzYCOCAcICs2AjQgHCAwNgIwAkAgE0EISQRAQQchBkEAIQ4MAQsgPiAiQQV0Ig5rICdBBnRqITggDiAhaiAwQQZ0aiEUIAYgLWohLSAGICdqIScgCiAbaiEkIAEgCmohKSAhIBhBBXRqISpBACEOA0ACQAJAIA4gFkkgDkEHciIGIAhPcQ0AIA4gJEkgBiApT3ENACAOQQhqIQ4MAQtBCCATIA5rIgYgBkEITxshJUEAIQYDQCAeIDAgBiAOaiIiICsgIkEBaiIsIBQgBkECdCIuakEQQQAQHiAeICcgIiAtICwgLiA4akEQQQAQHiAGQQFqIgYgJUcNAAsgHEEgahAiIB4gGCAOICggDkEIaiIOICpBCEEBQQAQJkUNBQsgDkEHciIGIBNJDQALCwJAIA4gE08NACAOIBZJIAYgCE9xRQRAIA4gCiAbak8NASAGIAEgCmpJDQELIBxBIGohBkEAISIgEyAOayIwBEADQCAeIAYoAhAiLSAOICJqIicgBigCFCAnQQFqIisgIkECdCI4IAYoAgAgBigCDEEFdGogLUEGdGpqQRBBABAeIB4gBigCGCItIAYoAggiFGogJyAGKAIcIBRqICsgBigCACAGKAIMQQV0ayAtQQZ0aiA4akEgakEQQQAQHiAiQQFqIiIgMEcNAAsLIAYQIiAeIBggDiAoIBMgISAYQQV0akEIQQFBABAmRQ0DCyAcIBs2AhwgHCABNgIYIBwgFjYCFCAcIAg2AhAgGCAoSQRAIBVBAXQiBiAPQQF0QQFyIhUgBiAVSxsiBiATIAYgE0kbIQYgPiA/QQV0IhVrIAFBBnRqIQ4gFSAhaiAIQQZ0aiEVIAogG2ohDyABIApqIQogISAQQQF0IgEgB0EBdEEBciIHIAEgB0kbIgdBBXRqIRADQCAeIBggCEEIICggGGsiASABQQhPGyAYaiIBIBYgFUEBQRAQHiAeIBggCiABIA8gDkEBQRAQHiAcECIgHiAYIAcgASAGIBBBAUEIQQAQJkUNBCAYQQhqIhggKEkNAAsLIAxBmAFqIQwgICEGIBMhCiCGAUIBfCKGASCHAVINAAsLQQEhByAeICNBEGsoAgAiASAvKAIAIgZrICNBDGsoAgAgPSgCACIKayAjQQhrKAIAIgggBmsgI0EEaygCACAKayAJKAI0QQEgCCABaxAeIB4QIyAhEBAMBAsgHhAjICEQEEEAIQcMAwsgHhAjQQAhBwwCCyAfECBBAAshByAcKAIgEBALIBxBQGskACAHDQAMBAsgHUG4CGohHSANQTRqIQ0gCUHMAGohCSALQQFqIgsgFygCEEkNAAsgGSgCICEdIBkoAhQoAgAhFwsCQCAdKAIQIglFDQAgGSgCRA0AIBcoAhQiDSgCHCEBAkACQAJAIBkoAkAiBgRAIBcoAhAiC0EDSQ0CAkAgDSgCGCIHIA0oAmRGBEAgByANKAKwAUYNAQsgM0EBQdTKAEEAEA8MBwsCQCAZKAIYKAIYIgooAiQiCCAKKAJYRw0AIAggCigCjAFHDQAgASAHQZgBbCIKaiIBQYwBaygCACABQZQBaygCAGsgAUGQAWsoAgAgAUGYAWsoAgBrbCIBIA0oAmggCmoiB0GMAWsoAgAgB0GUAWsoAgBrIAdBkAFrKAIAIAdBmAFrKAIAa2xHDQAgDSgCtAEgCmoiB0GMAWsoAgAgB0GUAWsoAgBrIAdBkAFrKAIAIAdBmAFrKAIAa2wgAUYNAgsgM0EBQdTKAEEAEA8MBgsgFygCECILQQNJDQECQCAZKAIYKAIYIgcoAiQiCiAHKAJYRw0AIAogBygCjAEiCEcNACABIApBmAFsIgdqIgEoApQBIAEoAowBayABKAKQASABKAKIAWtsIgEgByANKAJoaiIHKAKUASAHKAKMAWsgBygCkAEgBygCiAFrbEcNACANKAK0ASAIQZgBbGoiBygClAEgBygCjAFrIAcoApABIAcoAogBa2wgAUYNAQsgM0EBQdTKAEEAEA8MBQsgCUECRgRAIB0oAugrRQ0DIAtBAnQQFCILRQ0FIBcoAhAiCEUNAiAZKAJABEBBACEXAkAgCEEMSQRAQQAhBgwBCyANQSRqIQoCQCALIA0gCEHMAGxqQSRrTw0AIAogCyAIQQJ0ak8NAEEAIQYMAQsgDUGIAmohDCANQbwBaiEVIA1B8ABqIQ4gDSAIQXxxIgZBzABsaiENQQAhCQNAIAsgCUECdGogDCAJQcwAbCIHaiAHIBVqIAcgDmogByAKav0JAgD9VgIAAf1WAgAC/VYCAAP9CwIAIAlBBGoiCSAGRw0ACyAGIAhGDQQLAkAgCEEDcSIHRQRAIAYhCQwBCyAGIQkDQCALIAlBAnRqIA0oAiQ2AgAgCUEBaiEJIA1BzABqIQ0gF0EBaiIXIAdHDQALCyAGIAhrQXxLDQMgC0EMaiEGIAtBCGohCiALQQRqIQwDQCALIAlBAnQiB2ogDSgCJDYCACAHIAxqIA0oAnA2AgAgByAKaiANKAK8ATYCACAGIAdqIA0oAogCNgIAIA1BsAJqIQ0gCUEEaiIJIAhHDQALDAMLQQAhFwJAIAhBDEkEQEEAIQYMAQsgDUE0aiEKAkAgCyANIAhBzABsakEUa08NACAKIAsgCEECdGpPDQBBACEGDAELIA1BmAJqIQwgDUHMAWohFSANQYABaiEOIA0gCEF8cSIGQcwAbGohDUEAIQkDQCALIAlBAnRqIAwgCUHMAGwiB2ogByAVaiAHIA5qIAcgCmr9CQIA/VYCAAH9VgIAAv1WAgAD/QsCACAJQQRqIgkgBkcNAAsgBiAIRg0DCwJAIAhBA3EiB0UEQCAGIQkMAQsgBiEJA0AgCyAJQQJ0aiANKAI0NgIAIAlBAWohCSANQcwAaiENIBdBAWoiFyAHRw0ACwsgBiAIa0F8Sw0CIAtBDGohBiALQQhqIQogC0EEaiEMA0AgCyAJQQJ0IgdqIA0oAjQ2AgAgByAMaiANKAKAATYCACAHIApqIA0oAswBNgIAIAYgB2ogDSgCmAI2AgAgDUGwAmohDSAJQQRqIgkgCEcNAAsMAgsgHSgC0CsoAhRBAUYEQCAGBEAgDSgCJCANKAJwIA0oArwBIAEQXwwECyANKAI0IA0oAoABIA0oAswBIAEQXwwDCyAGBEAgDSgCJCANKAJwIA0oArwBIAEQXgwDCyANKAI0IA0oAoABIA0oAswBIAEQXgwCCyBAIAs2AgAgM0EBQZHLACBAEA8MAQsgGSgCGCgCGCgCIBoCfyAdKALoKyEHQQAhDkEAIAhBA3QQFCINRQ0AGgJAIAFFDQAgCEUNACANIAhBAnRqIRMgCEF8cSEPIAhBA3EhDCAIQQFrIRADQEEAIRdBACEJIBBBA08EQANAIA0gF0ECdCIGaiAGIAtqKAIAKgIAOAIAIA0gBkEEciIKaiAKIAtqKAIAKgIAOAIAIA0gBkEIciIKaiAKIAtqKAIAKgIAOAIAIA0gBkEMciIGaiAGIAtqKAIAKgIAOAIAIBdBBGohFyAJQQRqIgkgD0cNAAsLQQAhCiAMBEADQCANIBdBAnQiBmogBiALaigCACoCADgCACAXQQFqIRcgCkEBaiIKIAxHDQALC0EAIQYgByEXA0AgEyAGQQJ0IhJqIglBADYCAEMAAAAAIY4BQQAhCkEAIRYgEEECSwRAA0AgCSAXKgIAIA0gCkECdGoiFSoCAJQgjgGSIo4BOAIAIAkgFyoCBCAVKgIElCCOAZIijgE4AgAgCSAXKgIIIBUqAgiUII4BkiKOATgCACAJIBcqAgwgFSoCDJQgjgGSIo4BOAIAIApBBGohCiAXQRBqIRcgFkEEaiIWIA9HDQALC0EAIRUgDARAA0AgCSAXKgIAIA0gCkECdGoqAgCUII4BkiKOATgCACAKQQFqIQogF0EEaiEXIBVBAWoiFSAMRw0ACwsgCyASaiIKIAooAgAiCkEEajYCACAKII4BOAIAIAZBAWoiBiAIRw0ACyAOQQFqIg4gAUcNAAsLIA0QEEEBCyF7IAsQECB7RQ0CCyAZKAIUKAIAIhYoAhBFBEBBASExDAILIBkoAiAoAtArIhdBuAhqIRMgF0G0CGohEiAZKAJEIRAgFigCFCEHIBkoAhgoAhghCkEAIQgDQAJAIBAEQCAQIAhBAnRqKAIARQ0BCyAHKAIcIgEgCigCJEGYAWxqIQsCfyAZKAJARQRAIAsoApQBIAsoAowBayEGIAsoApABIAsoAogBayEBQQAhDEE0DAELIAEgBygCGEGYAWxqIgZBkAFrKAIAIAsoAgggCygCAGsiASAGQZgBaygCAGprIQwgCygCDCALKAIEayEGQSQLIQkgCigCGCELAn8gCigCIARAQQEgC0EBa3QiC0EBayEdQQAgC2sMAQtBfyALdEF/cyEdQQALIQ8gAUUNACAGRQ0AIAcgCWooAgAhCSAXKAIUQQFGBEAgEyAIQbgIbCILaiERIAsgEmohGCABQQFxIRogAUECdCEzIAFBfHEiDkECdCEbIB39ESGCASAP/REhgAFBACEVIAFBBEkhHwNAAkACQAJAIB8NACAJIBFJIBggCSAzaklxDQAgCSAbaiENIBf9CQK0CCGDAUEAIQsDQCAJIAtBAnRqIiAggAEggwEgIP0AAgD9rgEihAEgggH9tgEghAEggAH9Of1S/QsCACALQQRqIgsgDkcNAAsgDiILIAFGDQIMAQsgCSENQQAhCwsgC0EBciEJIBoEQCANIA8gFygCtAggDSgCAGoiCyAdIAsgHUgbIAsgD0gbNgIAIA1BBGohDSAJIQsLIAEgCUYNAANAIA0gDyAXKAK0CCANKAIAaiIJIB0gCSAdSBsgCSAPSBs2AgAgDSAPIBcoArQIIA0oAgRqIgkgHSAJIB1IGyAJIA9IGzYCBCANQQhqIQ0gC0ECaiILIAFHDQALCyANIAxBAnRqIQkgFUEBaiIVIAZHDQALDAELIB2sIYYBIA+sIYcBQQAhFQNAQQAhCwNAIAkCfyAdIAkqAgAijgFDAAAAT14NABogDyCOAUMAAADPXQ0AGiCHASAXNAK0CAJ/II4BkCKOAYtDAAAAT10EQCCOAagMAQtBgICAgHgLrHwiigEghgEghgEgigFVGyCHASCKAVUbpws2AgAgCUEEaiEJIAtBAWoiCyABRw0ACyAJIAxBAnRqIQkgFUEBaiIVIAZHDQALCyAHQcwAaiEHIBdBuAhqIRcgCkE0aiEKQQEhMSAIQQFqIgggFigCEEkNAAsMAQsgBUEBQZoZQQAQDwsgQEEQaiQAIDFFBEAgTxAuIAAgACgCCEGAgAJyNgIIIAVBAUHw1ABBABAPDAELAkAgAkUNAAJ/IAIhB0EAIQYCQCAAKALQASIVQQEQVCIBQX9GDQAgASADSw0AQQEgFSgCGCIBKAIQRQ0BGiABKAIYIQggFSgCFCgCACgCFCEXA0AgCCgCGCIBQQdxIQIgAUEDdiEDIBcoAhwiBiAIKAIkQZgBbGohAQJ/IBUoAkAEQCAGIBcoAhhBmAFsaiIGQZABaygCACABKAIIIAEoAgBrIgsgBkGYAWsoAgBqayEMIAEoAgwgASgCBGshCUEkDAELIAEoApQBIAEoAowBayEJIAEoApABIAEoAogBayELQQAhDEE0CyAXaigCACEBAkACQAJAAkACQEEEIAMgAkEAR2oiAiACQQNGG0EBaw4EAQIEAAQLIAlFDQMgCyAMaiEGIAtBAnQhAiAJQQRPBEAgCUF8cSEKQQAhCwNAIAcgASACEBIhByABIAZBAnQiA2oiDSADaiIMIANqIg4gA2ohASACIAdqIA0gAhASIAJqIAwgAhASIAJqIA4gAhASIAJqIQcgC0EEaiILIApHDQALC0EAIQsgCUEDcSIDRQ0DA0AgByABIAIQEiEHIAEgBkECdGohASACIAdqIQcgC0EBaiILIANHDQALDAMLIAlFIAtFciECIAgoAiBFDQEgAg0CIAtBAnQhDiALQXxxIgNBAnQhD0EAIQ0DQAJAAkACQCALQQRJDQAgASAHIAtqSSABIA5qIAdLcQ0AIAMgB2ohfCABIA9qIQZBACEKA0AgByAKaiABIApBAnRq/QACAP0MAAAAAAAAAAAAAAAAAAAAAP0NAAQIDAAAAAAAAAAAAAAAAP1aAAAAIApBBGoiCiADRw0ACyB8IQcgAyICIAtGDQIMAQsgASEGQQAhAgtBACEKIAsgAiIBa0EHcSIWBEADQCAHIAYoAgA6AAAgAUEBaiEBIAdBAWohByAGQQRqIQYgCkEBaiIKIBZHDQALCyACIAtrQXhLDQADQCAHIAYoAgA6AAAgByAGKAIEOgABIAcgBigCCDoAAiAHIAYoAgw6AAMgByAGKAIQOgAEIAcgBigCFDoABSAHIAYoAhg6AAYgByAGKAIcOgAHIAdBCGohByAGQSBqIQYgAUEIaiIBIAtHDQALCyAGIAxBAnRqIQEgDUEBaiINIAlHDQALDAILIAlFIAtFciECIAgoAiAEQCACDQIgC0ECdCEOIAtBAXQhDyALQXxxIgNBAnQhFiADQQF0IRBBACENA0ACQAJAAkAgC0EESQ0AIAEgByAPakkgASAOaiAHS3ENACABIBZqIQYgByAQaiF9QQAhCgNAIAcgCkEBdGogASAKQQJ0av0AAgD9DAAAAAAAAAAAAAAAAAAAAAD9DQABBAUICQwNAAEAAQABAAH9WwEAACAKQQRqIgogA0cNAAsgfSEHIAMiAiALRg0CDAELIAEhBkEAIQILQQAhCiALIAIiAWtBB3EiEwRAA0AgByAGKAIAOwEAIAFBAWohASAHQQJqIQcgBkEEaiEGIApBAWoiCiATRw0ACwsgAiALa0F4Sw0AA0AgByAGKAIAOwEAIAcgBigCBDsBAiAHIAYoAgg7AQQgByAGKAIMOwEGIAcgBigCEDsBCCAHIAYoAhQ7AQogByAGKAIYOwEMIAcgBigCHDsBDiAHQRBqIQcgBkEgaiEGIAFBCGoiASALRw0ACwsgBiAMQQJ0aiEBIA1BAWoiDSAJRw0ACwwCCyACDQEgC0ECdCEOIAtBAXQhDyALQXxxIgNBAnQhFiADQQF0IRBBACENA0ACQAJAAkAgC0EESQ0AIAEgByAPakkgASAOaiAHS3ENACABIBZqIQYgByAQaiF+QQAhCgNAIAcgCkEBdGogASAKQQJ0av0AAgD9DAAAAAAAAAAAAAAAAAAAAAD9DQABBAUICQwNAAEAAQABAAH9WwEAACAKQQRqIgogA0cNAAsgfiEHIAMiAiALRg0CDAELIAEhBkEAIQILQQAhCiALIAIiAWtBB3EiEwRAA0AgByAGKAIAOwEAIAFBAWohASAHQQJqIQcgBkEEaiEGIApBAWoiCiATRw0ACwsgAiALa0F4Sw0AA0AgByAGKAIAOwEAIAcgBigCBDsBAiAHIAYoAgg7AQQgByAGKAIMOwEGIAcgBigCEDsBCCAHIAYoAhQ7AQogByAGKAIYOwEMIAcgBigCHDsBDiAHQRBqIQcgBkEgaiEGIAFBCGoiASALRw0ACwsgBiAMQQJ0aiEBIA1BAWoiDSAJRw0ACwwBCyACDQAgC0ECdCEOIAtBfHEiA0ECdCEPQQAhDQNAAkACQAJAIAtBBEkNACABIAcgC2pJIAEgDmogB0txDQAgAyAHaiF/IAEgD2ohBkEAIQoDQCAHIApqIAEgCkECdGr9AAIA/QwAAAAAAAAAAAAAAAAAAAAA/Q0ABAgMAAAAAAAAAAAAAAAA/VoAAAAgCkEEaiIKIANHDQALIH8hByADIgIgC0YNAgwBCyABIQZBACECC0EAIQogCyACIgFrQQdxIhYEQANAIAcgBigCADoAACABQQFqIQEgB0EBaiEHIAZBBGohBiAKQQFqIgogFkcNAAsLIAIgC2tBeEsNAANAIAcgBigCADoAACAHIAYoAgQ6AAEgByAGKAIIOgACIAcgBigCDDoAAyAHIAYoAhA6AAQgByAGKAIUOgAFIAcgBigCGDoABiAHIAYoAhw6AAcgB0EIaiEHIAZBIGohBiABQQhqIgEgC0cNAAsLIAYgDEECdGohASANQQFqIg0gCUcNAAsLIBdBzABqIRcgCEE0aiEIQQEhBiByQQFqInIgFSgCGCgCEEkNAAsLIAYLRQ0BIE8oAtwrIgFFDQAgARAQIE9CADcC3CsLIAAgAC0AREH+AXE6AEQgACAAKAIIQf9+cTYCCEEBIWcgBCkDCCKGAVAEfkIABSCGASAEKQM4fQtQIAAoAggiAUHAAEZxDQAgAUGAAkYNACAEIE5BCmpBAiAFEBpBAkcEQCAFQQFBAiAAKAK4ARtBlhJBABAPIAAoArgBRSFnDAELIE5BCmogTkEMakECEBEgTigCDCIBQZD/A0YNACABQdn/A0YEQCAAQYACNgIIIABBADYCzAEMAQsgBCkDCCKGAVAEfkIABSCGASAEKQM4fQtQBEAgAEHAADYCCCAFQQJBrD9BABAPDAELQQAhZyAFQQFB7D5BABAPCyBOQRBqJAAgZwsLACAABEAgABAQCwu0AQEBfyAAKAIMRQRAIAIgACgCJCABEQMADwsCQEEIEBQiA0UNACADIAI2AgQgAyABNgIAQQgQFCIBRQRAIAMQEA8LIAEgAzYCACAAIAAoAgRB5ABsIgI2AigDQCAAKAIYIAJKDQALIAEgACgCFDYCBCAAIAE2AhQgACAAKAIYQQFqNgIYIAAoAhwiAUUNACABKAIAQQA2AgggACABKAIENgIcIAAgACgCIEEBazYCICABEBALC/oCAQR/AkAgAEUNACAAKAKsKCIBBEAgACgCqCgiAgRAQQAhAQNAIAAoAqwoIAFBA3RqKAIAIgMEQCADEBAgACgCqCghAgsgAUEBaiIBIAJJDQALIAAoAqwoIQELIABBADYCqCggARAQIABBADYCrCgLIAAoArQoIgEEQCABEBAgAEEANgK0KAsgACgC0CsiAQRAIAEQECAAQQA2AtArCyAAKALsKyIBBEAgARAQIABBADYC7CsLIAAoAugrIgEEQCABEBAgAEEANgLoKwsgACgC/CsiAQRAIAEQECAAQQA2AoQsIABCADcC/CsLIAAoAvArIgEEQCAAKAL0KyIDBH9BACECA0AgASgCDCIEBEAgBBAQIAFBADYCDCAAKAL0KyEDCyABQRRqIQEgAkEBaiICIANJDQALIAAoAvArBSABCxAQIABBADYC8CsLIAAoAuQrIgEEQCABEBAgAEEANgLkKwsgACgC3CsiAUUNACABEBAgAEIANwLcKwsLyAcCEX8BfiAAKAIQIghBIE8EQCAAKQMIpw8LAkAgACgCFCIDQQROBEAgACgCACICQQNrKAIAIQEgACADQQRrIgM2AhQgACACQQRrNgIADAELIANBAEwEQAwBCyADQQFxIQ0gACgCACECAkAgA0EBRgRAQRghBAwBCyADQf7///8HcSEJQRghBANAIAAgAkEBayIGNgIAIAItAAAhDCAAIAJBAmsiAjYCACAAIANBAWs2AhQgBi0AACEGIAAgA0ECayIDNgIUIAwgBHQgAXIgBiAEQQhrdHIhASAEQRBrIQQgBUECaiIFIAlHDQALCyANBEAgACACQQFrNgIAIAItAAAhDiAAIANBAWs2AhQgDiAEdCABciEBC0EAIQMLIAAoAhghAiAAIAFB/wFxIglBjwFLNgIYIABBB0EIIAFBgICA+AdxQYCAgPgHRhtBCCACGyICQQhBB0EIIAFBgID8A3FBgID8A0YbIAFB/////3hNG2oiBEEIQQdBCCABQYD+AXFBgP4BRhsgAUEQdkH/AXEiBUGPAU0baiIGQQhBB0EIIAFB/wBxQf8ARhsgAUEIdkH/AXEiB0GPAU0bIAhqaiIKNgIQIAAgACkDCCAFIAJ0IAFBGHZyIAcgBHRyIAkgBnRyrSAIrYaEIhI3AwggCkEfTQRAAkAgA0EETgRAIAAoAgAiAkEDaygCACEBIAAgA0EEazYCFCAAIAJBBGs2AgAMAQsgA0EATARAQQAhAQwBCyADQQFxIRAgACgCACECAkAgA0EBRgRAQRghBEEAIQEMAQsgA0H+////B3EhBkEYIQRBACEBQQAhBQNAIAAgAkEBayIHNgIAIAItAAAhDyAAIAJBAmsiAjYCACAAIANBAWs2AhQgBy0AACEHIAAgA0ECayIDNgIUIA8gBHQgAXIgByAEQQhrdHIhASAEQRBrIQQgBUECaiIFIAZHDQALCyAQRQ0AIAAgAkEBazYCACACLQAAIREgACADQQFrNgIUIBEgBHQgAXIhAQsgACABQf8BcSICQY8BSzYCGCAAQQhBB0EIIAFBgICA+AdxQYCAgPgHRhsgCUGPAU0bIgNBCEEHQQggAUGAgPwDcUGAgPwDRhsgAUH/////eE0baiIEQQhBB0EIIAFBgP4BcUGA/gFGGyABQRB2Qf8BcSIFQY8BTRtqIghBCEEHQQggAUH/AHFB/wBGGyABQQh2Qf8BcSIJQY8BTRsgCmpqNgIQIAAgBSADdCABQRh2ciAJIAR0ciACIAh0cq0gCq2GIBKEIhI3AwgLIBKnC8kUAh1/BnsgACgCCCIKIAAoAgRqIQgCQCAAKAIMRQRAIAhBAkgNASADQQBMDQEgACgCACIFIAhBBGsiBkEBdiIMQQJ0IgkgASAKQQJ0aiIHIANBAnQiBGpqQQRqSSAFIAxBA3RqQQhqIgAgB0EEaktxIAUgASAEaiAJakEEakkgAUEEaiAASXFyIRIgCEEESSIUIAJBAUdyIRUgAkEBRiAGQQVLcSEWIAhB/P///wdxIRMgCEEBcSEXIApBAWohDyAIQQNxIREgASAFayEYIAUgCEECdGohGSAFIAhBAWsiAEECdGohGiAMQQFqIhtBfHEiEEEBdCELIAIgCmxBAnQhHCAAQQF2IAJsQQJ0IR0DQCABKAIAIAEgHGooAgAiCUEBakEBdWshBwJAIBQEQCAJIQRBACEGDAELQQAhBgJAAn9BACAWRQ0AGkEAIBINABogCf0RISIgB/0RISH9DAAAAAACAAAABAAAAAYAAAAhJUEAIQADQCABIABBAnRq/QACBCEkIAEgACAPakECdGr9AAIAISMgBSAAQQN0aiIEICH9WgIAAyAEQQhqICQgIyAiICP9DQwNDg8QERITFBUWFxgZGhsiJP2uAf0MAgAAAAIAAAACAAAAAgAAAP2uAUEC/awB/bEBIiL9WgIAACAEQRBqICL9WgIAASAEQRhqICL9WgIAAiAFICX9DAEAAAABAAAAAQAAAAEAAAD9UCIm/RsAQQJ0aiAiICEgIv0NDA0ODxAREhMUFRYXGBkaG/2uAUEB/awBICT9rgEiIf1aAgAAIAUgJv0bAUECdGogIf1aAgABIAUgJv0bAkECdGogIf1aAgACIAUgJv0bA0ECdGogIf1aAgADICX9DAgAAAAIAAAACAAAAAgAAAD9rgEhJSAiISEgIyEiIABBBGoiACAQRw0ACyAi/RsDIQQgIf0bAyEHIBAgG0YNASALIQYgBCEJIBALIQADQCABIABBAWoiCiACbEECdGooAgAhHiABIAAgD2ogAmxBAnRqKAIAIQQgBSAGQQJ0aiIOIAc2AgAgDiAHIB4gBCAJakECakECdWsiB2pBAXUgCWo2AgQgBkECaiEGIAAgDEchHyAEIQkgCiEAIB8NAAsMAQsgCyEGCyAFIAZBAnRqIAc2AgBBfCEAIBcEfyAaIAEgHWooAgAgBEEBakEBdWsiADYCACAAIAdqQQF1IQdBeAVBfAsgGWogBCAHajYCAEEAIQZBACEAQQAhBAJAIBUgGCANQQJ0akEQSXJFBEADQCABIABBAnQiBGogBCAFav0AAgD9CwIAIABBBGoiACATRw0ACyATIgQgCEYNAQsgBCEAIBEEQANAIAEgACACbEECdGogBSAAQQJ0aigCADYCACAAQQFqIQAgBkEBaiIGIBFHDQALCyAEIAhrQXxLDQADQCABIAAgAmxBAnRqIAUgAEECdGooAgA2AgAgASAAQQFqIgQgAmxBAnRqIAUgBEECdGooAgA2AgAgASAAQQJqIgQgAmxBAnRqIAUgBEECdGooAgA2AgAgASAAQQNqIgQgAmxBAnRqIAUgBEECdGooAgA2AgAgAEEEaiIAIAhHDQALCyABQQRqIQEgDUEBaiINIANHDQALDAELAkACQAJAIAhBAWsOAgABAgsgA0EATA0CQQAhAgJAIANBBEkEQCABIQAMAQsgASADQfz///8HcSICQQJ0aiEAA0AgASAGQQJ0aiIEIAT9AAIAIiH9GwBBAm39ESAh/RsBQQJt/RwBICH9GwJBAm39HAIgIf0bA0ECbf0cA/0LAgAgBkEEaiIGIAJHDQALIAIgA0YNAwsDQCAAIAAoAgBBAm02AgAgAEEEaiEAIAJBAWoiAiADRw0ACwwCCyADQQBMDQEgACgCACEJIAIgCmxBAnQhBwNAIAkgASgCACABIAdqIgQoAgBBAWpBAXVrIgA2AgQgCSAAIAQoAgBqIgA2AgAgASAANgIAIAEgAkECdGogCSgCBDYCACABQQRqIQEgBkEBaiIGIANHDQALDAELIAhBA0gNACADQQBMDQAgACgCACIFIAggCEEBcSIURSIGa0EEayIJQQF2IgtBAnQiByABIANBAnQiAGpqSSAFIAtBA3RqQQxqIgQgAUEEaktxIAVBBGogACABIApBAnRqIgBqIAdqQQhqSSAAQQhqIARJcXIhFSACQQFHIAhBBElyIRYgAkEBRiAJQQVLcSEXIAhB/P///wdxIRAgCEEDcSERIAEgBWshGCAFIAhBAnRqQQRrIRkgBSAIQQJrIgBBAnRqIRogC0EBaiISQXxxIgxBAXIhEyAMQQF0QQFyIQsgAiAKbEECdCEbIAAgBmtBAkkhHCAIQQF2QQFrIAJsQQJ0IR0DQCAFIAEoAgAgASAbaiIPIAJBAnRqKAIAIgkgDygCACIAakECakECdWsiByAAajYCAEEBIQQCQCAcBEAgCSEGDAELAkACf0EBIBdFDQAaQQEgFQ0AGiAJ/REhISAH/REhIkEAIQADQCAFIABBA3RqIgcgASAAQQJ0IgRq/QACBCAhIAQgD2r9AAIIIiH9DQwNDg8QERITFBUWFxgZGhsiJCAh/a4B/QwCAAAAAgAAAAIAAAACAAAA/a4BQQL9rAH9sQEiIyAjICIgI/0NDA0ODxAREhMUFRYXGBkaG/2uAUEB/awBICT9rgEiJP0NBAUGBxgZGhsICQoLHB0eH/0LAhQgByAiICT9DQwNDg8QERITAAECAxQVFhcgI/0NAAECAwQFBgcQERITDA0OD/0LAgQgIyEiIABBBGoiACAMRw0ACyAh/RsDIQYgIv0bAyEHIAwgEkYNASALIQQgBiEJIBMLIQADQCABIAAgAmxBAnRqKAIAIR4gDyAAQQFqIgogAmxBAnRqKAIAIQYgBSAEQQJ0aiIOIAc2AgAgDiAHIB4gBiAJakECakECdWsiB2pBAXUgCWo2AgQgBEECaiEEIAAgEkchICAKIQAgBiEJICANAAsMAQsgCyEECyAYIA1BAnRqIQkgBSAEQQJ0aiAHNgIAAkAgFEUEQCAaIAEgHWooAgAgBkEBakEBdWsiACAHakEBdSAGajYCAAwBCyAGIAdqIQALIBkgADYCAEEAIQZBACEAQQAhBAJAIBYgCUEQSXJFBEADQCABIABBAnQiBGogBCAFav0AAgD9CwIAIABBBGoiACAQRw0ACyAQIgQgCEYNAQsgBCEAIBEEQANAIAEgACACbEECdGogBSAAQQJ0aigCADYCACAAQQFqIQAgBkEBaiIGIBFHDQALCyAEIAhrQXxLDQADQCABIAAgAmxBAnRqIAUgAEECdGooAgA2AgAgASAAQQFqIgQgAmxBAnRqIAUgBEECdGooAgA2AgAgASAAQQJqIgQgAmxBAnRqIAUgBEECdGooAgA2AgAgASAAQQNqIgQgAmxBAnRqIAUgBEECdGooAgA2AgAgAEEEaiIAIAhHDQALCyABQQRqIQEgDUEBaiINIANHDQALCws3AQJ/IwBBEGsiASQAIAAEfyABQQxqQSAgABBsIQBBACABKAIMIAAbBUEACyECIAFBEGokACACCxsBAX8gAARAIAAoAggiAQRAIAEQEAsgABAQCwsxAQJ/QQFBDBATIgAEQCAAQQo2AgQgAEEKQQQQEyIBNgIIIAEEQCAADwsgABAQC0EACy8BAX8gAARAIAAoAgQiAQRAIAAoAgAgARECAAsgACgCIBAQIABBADYCICAAEBALCyoAIAAEQCAAKAIwIABBFEEQIAAoAkwbaigCABECACAAQQA2AjAgABAQCwtTAQJ/IABBADYCMCAAIAAoAiA2AiQgASAAKAIAIAAoAhwRCgAhBCAAKAJEIQIgBEUEQCAAIAJBBHI2AkRBAA8LIAAgATcDOCAAIAJBe3E2AkRBAQuGAwIFfwp+IwBBIGsiAyQAAkAgACgCECIFRQRAQQEhAgwBCwJAIAA0AgAiB0IAUw0AIAA0AgQiCEIAUw0AIAA0AggiCUIAUw0AIAA0AgwiCkIAUw0AIAAoAhghACAHQgF9IQwgCEIBfSENIAlCAX0hCSAKQgF9IQoDQCAAIAwgACgCACICrSIHfCAHgCILPgIQIAAgDSAAKAIEIgatIgd8IAeAIg4+AhRCASAANQIoIgeGIg9CAX0iCCAJIAKsIhB8IBB/xHwgB4enIAggC8R8IAeHp2siAkEASARAIAMgAjYCBCADIAQ2AgAgAUEBQdPkACADEA9BACECDAMLIAAgAjYCCCAIIAogBqwiC3wgC3/EfCAHh6cgDsQgD3xCAX0gB4enayICQQBIBEAgAyACNgIUIAMgBDYCECABQQFBmOUAIANBEGoQD0EAIQIMAwsgACACNgIMIABBNGohAEEBIQIgBEEBaiIEIAVHDQALDAELIAFBAUGnM0EAEA8LIANBIGokACACC9cGAQZ/IAAEQAJAIAAoAgAEQCAAKAIMIgEEQCABEC4gACgCDBAQIABBADYCDAsgACgCECIBBEAgARAQIABCADcDEAsgACgCQBAQIABCADcCPAwBCyAAKAIsIgEEQCABEBAgAEEANgIsCyAAKAIgIgEEQCABEBAgAEIANwMgCyAAKAI0IgFFDQAgARAQIABCADcCNAsgACgC0AEQVSAAKAKcASIBBEAgACgCaCAAKAJsbCIDBH8DQCABEC4gAUGMLGohASACQQFqIgIgA0cNAAsgACgCnAEFIAELEBAgAEEANgKcAQsgACgCdCIBBEAgACgCcCICBEBBACEBA0AgACgCdCABQQN0aigCACIDBEAgAxAQIAAoAnAhAgsgAUEBaiIBIAJJDQALIAAoAnQhAQsgAEEANgJwIAEQECAAQQA2AnQLIAAoAogBEBAgAEEANgJ4IABBADYCiAEgACgCZBAQIABBADYCZCAALQC8AUECcUUEQCAAKAKoARAQCyAAQdAAakEAQfAAEBUaIAAoAsABEDIgAEEANgLAASAAKALEARAyIABBADYCwAEgACgCyAEiAQRAIAEoAhwiAgRAIAIQECABQQA2AhwLIAEoAigiAgRAIAEoAiQEQANAIAIgBUEobCIDaigCJCIEBEAgBBAQIAEoAigiAiADakEANgIkCyACIANqKAIQIgQEQCAEEBAgASgCKCICIANqQQA2AhALIAIgA2ooAhgiBARAIAQQECABKAIoIgIgA2pBADYCGAsgBUEBaiIFIAEoAiRJDQALCyACEBAgAUEANgIoCyABEBALIABBADYCyAEgACgCSBAhIABBADYCSCAAKAJMECEgAEEANgJMIAAoAtQBIgMEQAJAIAMoAghFDQAgAygCDARAIANBADYCKANAIAMoAhhBAEoNAAsLIANBATYCECADKAIAEBAgAygCHCICRQ0AA0AgAigCBCEBIAIQECADIAE2AhwgASICDQALCyADKAIkIgIEQCACKAIEIgVBAEoEQEEAIQEDQCACKAIAIAFBDGxqIgQoAggiBgRAIAQoAgQgBhECACACKAIEIQULIAFBAWoiASAFSA0ACwsgAigCABAQIAIQEAsgAxAQCyAAQQA2AtQBIAAQEAsL5gMCCH8EfiAAKAIUKAIAKAIUIAFBzABsaiIJKAIMIgggACgCGCgCGCABQTRsaiIKNQIEIhBCAX0iEiAANQI8fCAQgKciCyAIIAtJGyEMIAkoAggiCCAKNQIAIhFCAX0iEyAANQI4fCARgKciCiAIIApJGyEKIAkoAgQiCCASIAA1AjR8IBCApyILIAggC0sbIQsgCSgCACIIIBMgADUCMHwgEYCnIg0gCCANSxshDUEAIQggACgCICgC0CsgAUG4CGxqKAIUIQ4CQCAJKAIUQQAgAmtBfyACG2oiAkUEQCAKIQAgDSEIIAshAQwBCyADQQFxIAJBAWsiD3QiCSANSQRAIA0gCWutQn8gAq0iEIZCf4V8IBCIpyEIC0EAIQBBACEBIANBAXYgD3QiAyALSQRAIAsgA2utQn8gAq0iEIZCf4V8IBCIpyEBCyAJIApJBEAgCiAJa61CfyACrSIQhkJ/hXwgEIinIQALIAMgDE8EQEEAIQwMAQsgDCADa61CfyACrSIQhkJ/hXwgEIinIQwLQX8gAEECQQMgDkEBRhsiAmoiAyAAIANLGyAES0F/IAIgDGoiACAAIAxJGyAFS3EgCCACayIAQQAgACAITRsgBklxIAEgAmsiAEEAIAAgAU0bIAdJcQuiAQEGfyAABEAgACgCBCICBEAgAhAQIABBADYCBAsgAQRAIAAhAgNAIAIoAsgBIgMEQEEAIQUgAigCxAEiBAR/A0AgAygCDCIGBEAgBhAQIANBADYCDCACKALEASEECyADQRBqIQMgBUEBaiIFIARJDQALIAIoAsgBBSADCxAQIAJBADYCyAELIAJB8AFqIQIgB0EBaiIHIAFHDQALCyAAEBALC9UZAhN/A3sgACgCACIKIAAoAgwiDUEFdCIFaiEGIAogBWshFiAAKAIQIQUgACgCHCELIAAoAhQhCSAAKAIIIQ4CQAJAAkACQCADQQhJDQAgAUEPcQ0AIAZBD3FFDQELIAUgCU8NAgJAAkAgA0EBaw4CAAEDCwJAIAkgBWsiCEEYSQ0AIAEgBUECdGohByANQQV0IgQgCiAFQQZ0amogASAJQQJ0akkEQCAHIAogCUEGdGogBGpBPGtJDQELIAX9Ef0MAAAAAAEAAAACAAAAAwAAAP2uASEYIAUgCEF8cSIPaiEFQQAhBANAIAYgGEEE/asBIhf9GwBBAnRqIAcgBEECdGr9AAIAIhn9HwA4AgAgBiAX/RsBQQJ0aiAZ/R8BOAIAIAYgF/0bAkECdGogGf0fAjgCACAGIBf9GwNBAnRqIBn9HwM4AgAgGP0MBAAAAAQAAAAEAAAABAAAAP2uASEYIARBBGoiBCAPRw0ACyAIIA9GDQQLIAUhBCAJIAVrQQNxIgcEQEEAIQgDQCAGIARBBnRqIAEgBEECdGoqAgA4AgAgBEEBaiEEIAhBAWoiCCAHRw0ACwsgBSAJa0F8Sw0DA0AgBiAEQQZ0aiABIARBAnRqKgIAOAIAIAYgBEEBaiIFQQZ0aiABIAVBAnRqKgIAOAIAIAYgBEECaiIFQQZ0aiABIAVBAnRqKgIAOAIAIAYgBEEDaiIFQQZ0aiABIAVBAnRqKgIAOAIAIARBBGoiBCAJRw0ACwwDCyABIAJBAnRqIQgCQCAJIAVrIg9BPEkEQCAFIQQMAQsgCiAFQQZ0IA1BBXRqaiIEIAkgBUF/c2oiB0EGdCIQaiAESQRAIAUhBAwBCyAEQQRqIgQgEGogBEkEQCAFIQQMAQsgB0H///8fSwRAIAUhBAwBCyANQQV0IgQgCiAFQQZ0amoiByABIAIgCWpBAnRqSSAKIAlBBnRqIARqQThrIgQgASACIAVqQQJ0aktxBEAgBSEEDAELIAcgASAJQQJ0akkgASAFQQJ0aiAESXEEQCAFIQQMAQsgBf0R/QwAAAAAAQAAAAIAAAADAAAA/a4BIRggBSAPQXxxIhBqIQRBACEHA0AgBiAYQQT9qwEiF/0bAEECdGoiESABIAUgB2pBAnQiDGr9AAIAIhn9HwA4AgAgBiAX/RsBQQJ0aiITIBn9HwE4AgAgBiAX/RsCQQJ0aiIUIBn9HwI4AgAgBiAX/RsDQQJ0aiIVIBn9HwM4AgAgESAIIAxq/QACACIX/R8AOAIEIBMgF/0fATgCBCAUIBf9HwI4AgQgFSAX/R8DOAIEIBj9DAQAAAAEAAAABAAAAAQAAAD9rgEhGCAHQQRqIgcgEEcNAAsgDyAQRg0DCyAEQQFqIQUgCSAEa0EBcQRAIAYgBEEGdGoiByABIARBAnQiBGoqAgA4AgAgByAEIAhqKgIAOAIEIAUhBAsgBSAJRg0CA0AgBiAEQQZ0aiIFIAEgBEECdCIHaioCADgCACAFIAcgCGoqAgA4AgQgBiAEQQFqIgVBBnRqIgcgASAFQQJ0IgVqKgIAOAIAIAcgBSAIaioCADgCBCAEQQJqIgQgCUcNAAsMAgsgBSAJTw0BIAEgAkECdGohCANAIAYgBUEGdGoiBCABIAVBAnRqKgIAOAIAIAQgASACIAVqIgdBAnRqKgIAOAIEIAQgASACIAdqIgdBAnRqKgIAOAIIIAQgASACIAdqIgdBAnRqKgIAOAIMIAQgASACIAdqIgdBAnRqKgIAOAIQIAQgASACIAdqIgdBAnRqKgIAOAIUIAQgASACIAdqQQJ0IgdqKgIAOAIYIAQgByAIaioCADgCHCAFQQFqIgUgCUcNAAsMAQsgASACQQJ0aiEIIANBA0YhByADQQRGIQ8gA0EFRiEQIANBB0YhEQNAIAYgBUEGdGoiBCABIAVBAnRqKgIAOAIAIAQgASACIAVqIgxBAnRqKgIAOAIEIAQgASACIAxqIgxBAnRqKgIAOAIIAkAgBw0AIAQgASACIAxqIgxBAnRqKgIAOAIMIA8NACAEIAEgAiAMaiIMQQJ0aioCADgCECAQDQAgBCABIAIgDGoiDEECdGoqAgA4AhQgA0EGRg0AIAQgASACIAxqQQJ0IgxqKgIAOAIYIBENACAEIAggDGoqAgA4AhwLIAVBAWoiBSAJRw0ACwsgFkEgaiEGIAEgDkECdGohBCAAKAIYIQUCQAJAAkAgA0EISQ0AIARBD3ENACAGQQ9xRQ0BCyAFIAtPDQECQAJAAkAgA0EBaw4CAAECCwJAIAsgBWsiAEEcSQ0AIAogBUEGdEEgciANQQV0IgJraiABIAsgDmpBAnRqSQRAIAEgBSAOakECdGogC0EGdCACayAKakEca0kNAQsgBCAFQQJ0aiEDIAX9Ef0MAAAAAAEAAAACAAAAAwAAAP2uASEYIAUgAEF8cSIBaiEFQQAhAgNAIAYgGEEE/asBIhf9GwBBAnRqIAMgAkECdGr9AAIAIhn9HwA4AgAgBiAX/RsBQQJ0aiAZ/R8BOAIAIAYgF/0bAkECdGogGf0fAjgCACAGIBf9GwNBAnRqIBn9HwM4AgAgGP0MBAAAAAQAAAAEAAAABAAAAP2uASEYIAJBBGoiAiABRw0ACyAAIAFGDQQLIAUhAiALIAVrQQNxIgAEQEEAIQEDQCAGIAJBBnRqIAQgAkECdGoqAgA4AgAgAkEBaiECIAFBAWoiASAARw0ACwsgBSALa0F8Sw0DA0AgBiACQQZ0aiAEIAJBAnRqKgIAOAIAIAYgAkEBaiIAQQZ0aiAEIABBAnRqKgIAOAIAIAYgAkECaiIAQQZ0aiAEIABBAnRqKgIAOAIAIAYgAkEDaiIAQQZ0aiAEIABBAnRqKgIAOAIAIAJBBGoiAiALRw0ACwwDCyAEIAJBAnRqIQMCQCALIAVrIgBBxABJBEAgBSECDAELIAogBUEGdCIJQSByIA1BBXQiCGtqIgcgCyAFQX9zaiIPQQZ0IhBqIAdJBEAgBSECDAELIAogCUEkciAIa2oiCSAQaiAJSQRAIAUhAgwBCyAPQf///x9LBEAgBSECDAELIAogBUEGdEEgciANQQV0IglraiINIAEgCyAOaiIIIAJqQQJ0akkgC0EGdCAJayAKakEYayIJIAEgDkECdGogBUECdGoiCiACQQJ0aktxBEAgBSECDAELIA0gASAIQQJ0akkgCSAKS3EEQCAFIQIMAQsgBf0R/QwAAAAAAQAAAAIAAAADAAAA/a4BIRggBSAAQXxxIglqIQJBACEBA0AgBiAYQQT9qwEiF/0bAEECdGoiCiAEIAEgBWpBAnQiDWr9AAIAIhn9HwA4AgAgBiAX/RsBQQJ0aiIOIBn9HwE4AgAgBiAX/RsCQQJ0aiIIIBn9HwI4AgAgBiAX/RsDQQJ0aiIHIBn9HwM4AgAgCiADIA1q/QACACIX/R8AOAIEIA4gF/0fATgCBCAIIBf9HwI4AgQgByAX/R8DOAIEIBj9DAQAAAAEAAAABAAAAAQAAAD9rgEhGCABQQRqIgEgCUcNAAsgACAJRg0DCyACQQFqIQAgCyACa0EBcQRAIAYgAkEGdGoiASAEIAJBAnQiAmoqAgA4AgAgASACIANqKgIAOAIEIAAhAgsgACALRg0CA0AgBiACQQZ0aiIAIAQgAkECdCIBaioCADgCACAAIAEgA2oqAgA4AgQgBiACQQFqIgBBBnRqIgEgBCAAQQJ0IgBqKgIAOAIAIAEgACADaioCADgCBCACQQJqIgIgC0cNAAsMAgsgBCACQQJ0aiEBIANBA0YhCSADQQRGIQogA0EFRiENIANBB0YhDgNAIAYgBUEGdGoiACAEIAVBAnRqKgIAOAIAIAAgBCACIAVqIghBAnRqKgIAOAIEIAAgBCACIAhqIghBAnRqKgIAOAIIAkAgCQ0AIAAgBCACIAhqIghBAnRqKgIAOAIMIAoNACAAIAQgAiAIaiIIQQJ0aioCADgCECANDQAgACAEIAIgCGoiCEECdGoqAgA4AhQgA0EGRg0AIAAgBCACIAhqQQJ0IghqKgIAOAIYIA4NACAAIAEgCGoqAgA4AhwLIAVBAWoiBSALRw0ACwwBCyAFIAtPDQAgBCACQQJ0aiEBA0AgBiAFQQZ0aiIAIAQgBUECdGoqAgA4AgAgACAEIAIgBWoiA0ECdGoqAgA4AgQgACAEIAIgA2oiA0ECdGoqAgA4AgggACAEIAIgA2oiA0ECdGoqAgA4AgwgACAEIAIgA2oiA0ECdGoqAgA4AhAgACAEIAIgA2oiA0ECdGoqAgA4AhQgACAEIAIgA2pBAnQiA2oqAgA4AhggACABIANqKgIAOAIcIAVBAWoiBSALRw0ACwsLmwMBBH8gASAAQQRqIgRqQQFrQQAgAWtxIgUgAmogACAAKAIAIgFqQQRrTQR/IAAoAgQiAyAAKAIIIgY2AgggBiADNgIEIAQgBUcEQCAAIABBBGsoAgBBfnFrIgMgBSAEayIEIAMoAgBqIgU2AgAgAyAFQXxxakEEayAFNgIAIAAgBGoiACABIARrIgE2AgALAn8gASACQRhqTwRAIAAgAmpBCGoiAyABIAJrQQhrIgE2AgAgAyABQXxxakEEayABQQFyNgIAIAMCfyADKAIAQQhrIgFB/wBNBEAgAUEDdkEBawwBCyABZyEEIAFBHSAEa3ZBBHMgBEECdGtB7gBqIAFB/x9NDQAaQT8gAUEeIARrdkECcyAEQQF0a0HHAGoiASABQT9PGwsiAUEEdCIEQaDHAWo2AgQgAyAEQajHAWoiBCgCADYCCCAEIAM2AgAgAygCCCADNgIEQajPAUGozwEpAwBCASABrYaENwMAIAAgAkEIaiIBNgIAIAAgAUF8cWoMAQsgACABagtBBGsgATYCACAAQQRqBUEACwvCAQEDfwJAIAEgAigCECIDBH8gAwUgAhA+DQEgAigCEAsgAigCFCIEa0sEQCACIAAgASACKAIkEQAADwsCQAJAIAIoAlBBAEgNACABRQ0AIAEhAwNAIAAgA2oiBUEBay0AAEEKRwRAIANBAWsiAw0BDAILCyACIAAgAyACKAIkEQAAIgQgA0kNAiABIANrIQEgAigCFCEEDAELIAAhBUEAIQMLIAQgBSABEBIaIAIgAigCFCABajYCFCABIANqIQQLIAQLWQEBfyAAIAAoAkgiAUEBayABcjYCSCAAKAIAIgFBCHEEQCAAIAFBIHI2AgBBfw8LIABCADcCBCAAIAAoAiwiATYCHCAAIAE2AhQgACABIAAoAjBqNgIQQQALzAIBBH8gASAA/QACAP0LAgAgASgCGCICBEAgASgCECIDBH9BACECA0AgASgCGCACQTRsaigCLCIEBEAgBBAQIAEoAhAhAwsgAkEBaiICIANJDQALIAEoAhgFIAILEBAgAUEANgIYCyABIAAoAhAiAjYCECABIAJBNGwQFCICNgIYIAIEQCABKAIQBEBBACEDA0AgAiADQTRsIgVqIgIgACgCGCAFaiIE/QACAP0LAgAgAiAEKAIwNgIwIAIgBP0AAiD9CwIgIAIgBP0AAhD9CwIQIAEoAhgiAiAFakEANgIsIANBAWoiAyABKAIQSQ0ACwsgASAAKAIUNgIUIAEgACgCICICNgIgIAIEQCABIAIQFCICNgIcIAJFBEAgAUIANwIcDwsgAiAAKAIcIAAoAiAQEhoPCyABQQA2AhwPCyABQQA2AhAgAUEANgIYCwQAQQELxgEBA38DQCAAQQR0IgFBpMcBaiABQaDHAWoiAjYCACABQajHAWogAjYCACAAQQFqIgBBwABHDQALQTAQbRojAEEQayIAJAACQCAAQQxqIABBCGoQDA0AQbDPAUEIIAAoAgxBAnRBBGoQJSIBNgIAIAFFDQBBCCAAKAIIECUiAQRAQbDPASgCACICIAAoAgxBAnRqQQA2AgAgAiABEAtFDQELQbDPAUEANgIACyAAQRBqJABBzM8BQSo2AgBBlNABQdjQATYCAAuQBgIFfwN7IwBBEGsiBiQAAn8gACgCCEEQRgRAIAAoApwBIAAoAswBQYwsbGoMAQsgACgCDAshAAJAIAMoAgAiBUUEQEEAIQIgBEEBQcATQQAQDwwBCyAAKALQKyEJIAMgBUEBazYCACACIAZBDGpBARARIAkgAUG4CGxqIgcgBigCDCIAQQV2NgKkBiAHIABBH3EiATYCGCACQQFqIQAgAwJ/An8CQAJ/AkACQCABDgIAAwELIAMoAgAMAQsgAygCAEEBdgsiBUHiAE8EfyAGQuGAgICQDDcCBCAGIAU2AgAgBEECQcX4ACAGEA8gBygCGAUgAQsEQCAFIgENAUEADAILIAUEQCAHQRxqIQFBACECA0AgACAGQQxqQQEQESACQeAATQRAIAYoAgwhBCABIAJBA3RqIghBADYCBCAIIARBA3Y2AgALIABBAWohACACQQFqIgIgBUcNAAsLQQAhAiADKAIAIgAgBUkNAyAAIAVrDAILIAdBHGohBEEAIQIDQCAAIAZBDGpBAhARIAJB4ABNBEAgBCACQQN0aiIFIAYoAgwiCEH/D3E2AgQgBSAIQQt2NgIACyAAQQJqIQAgAkEBaiICIAFHDQALIAFBAXQLIQBBACECIAMoAgAiASAASQ0BIAEgAGsLNgIAQQEhAiAHKAIYQQFHDQAgB0EcaiEEIAf9CQIcIQwgBygCICED/QwBAAAAAgAAAAMAAAAEAAAAIQtBACEBA0AgBCABQQN0aiIAQRhqIAwgC/0M//////////////////////2uASIK/RsAQQNu/REgCv0bAUEDbv0cASAK/RsCQQNu/RwCIAr9GwNBA279HAP9sQH9DAAAAAAAAAAAAAAAAAAAAAD9uAEiCv1aAgACIABBEGogCv1aAgABIABBCGogCv1aAgAAIAQgAUEEaiIBQQN0aiIFIAr9WgIAAyAAIAM2AhwgACADNgIUIAAgAzYCDCAFIAM2AgQgC/0MBAAAAAQAAAAEAAAABAAAAP2uASELIAFB4ABHDQALCyAGQRBqJAAgAgufBgEGfyMAQSBrIgYkAAJ/IAAoAghBEEYEQCAAKAKcASAAKALMAUGMLGxqDAELIAAoAgwLIQUCQCADKAIAQQRNBEBBACEAIARBAUGdE0EAEA8MAQsgAiAFKALQKyABQbgIbGoiBSIJQQRqQQEQESAFIAUoAgRBAWoiBzYCBCAHQSJPBEAgBkEhNgIEIAYgBzYCACAEQQFB+TkgBhAPQQAhAAwBCyAHIAAoAqABIghNBEAgBiAHNgIYIAYgCDYCFCAGIAE2AhAgBEEBQbT7ACAGQRBqEA8gACAAKAIIQYCAAnI2AghBACEADAELIAJBAWogBUEIakEBEBEgBSAFKAIIQQJqNgIIIAJBAmogBUEMakEBEBEgBSAFKAIMQQJqIgA2AgwCQAJAIAUoAggiAUEKSw0AIABBCksNACAAIAFqQQ1JDQELQQAhACAEQQFBwylBABAPDAELIAJBA2ogBUEQakEBEBEgBS0AEEGAAXEEQEEAIQAgBEEBQYsyQQAQDwwBCyACQQRqIAVBFGpBARARIAUoAhRBAk8EQEEAIQAgBEEBQcoxQQAQDwwBCyADIAMoAgBBBWsiBzYCAEEBIQAgBSgCBCEBIAUtAABBAXFFBEAgAUUNASAFQbAHaiEBIAVBrAZqIQJBACEFA0AgAiAFQQJ0IgBqQQ82AgAgACABakEPNgIAQQEhACAFQQFqIgUgCSgCBEkNAAsMAQsgASAHTQRAAkAgAUUEQEEAIQEMAQsgAkEFaiAGQRxqQQEQESAFIAYoAhwiAEEEdjYCsAcgBSAAQQ9xNgKsBiAFKAIEIgFBAk8EQCAFQbAHaiEHIAVBrAZqIQggAkEGaiEAQQEhBQNAIAAgBkEcakEBEBECQCAGKAIcIgFBEE8EQCABQQ9xIgINAQtBACEAIARBAUHwLUEAEA8MBQsgCCAFQQJ0IgpqIAI2AgAgByAKaiABQQR2NgIAIABBAWohACAFQQFqIgUgCSgCBCIBSQ0ACwsgAygCACEHCyADIAcgAWs2AgBBASEADAELQQAhACAEQQFBnRNBABAPCyAGQSBqJAAgAAtSACABIAAtAAA6AAcgASAALQABOgAGIAEgAC0AAjoABSABIAAtAAM6AAQgASAALQAEOgADIAEgAC0ABToAAiABIAAtAAY6AAEgASAALQAHOgAAC5IBAQR/IAAgATYCoAECQCAAKAJIIgNFDQAgAygCGCIGRQ0AIAAoAgwiBEUNACAEKALQK0UNACADKAIQIgRFBEBBAQ8LQQAhAwNAIAEgACgCDCgC0CsgA0G4CGxqKAIETwRAIAJBAUGixQBBABAPQQAPCyAGIANBNGxqIAE2AihBASEFIANBAWoiAyAERw0ACwsgBQusBwIJfwh+IwBBEGsiCiQAAkAgAkUEQCADQQFB+tUAQQAQDwwBCyACKAIQIgsgACgCSCIGKAIQSQRAIANBAUG1zgBBABAPDAELIAQgACgCaCIFIAAoAmxsIgdPBEAgCiAENgIAIAogB0EBazYCBCADQQFB9/oAIAoQD0EAIQUMAQsgAiAAKAJUIAQgBSAEIAVuIgdsayIIIAAoAlxsaiIFNgIAIAIgBSAGKAIAIgYgBSAGSxsiBjYCACACIAAoAlQgACgCXCAIQQFqbGoiBTYCCCACIAUgACgCSCgCCCIIIAUgCEkbIgg2AgggAiAAKAJYIAAoAmAgB2xqIgU2AgQgAiAFIAAoAkgoAgQiCSAFIAlLGyIJNgIEIAIgACgCWCAAKAJgIAdBAWpsaiIFNgIMIAIgBSAAKAJIKAIMIgcgBSAHSRsiBTYCDCAAKAJIIgwoAhAiBwRAIAWsQgF9IREgCKxCAX0hEiAJrUIBfSETIAatQgF9IRQgDCgCGCEIIAIoAhghBUEAIQYDQCAFIAggBkE0bGooAigiCTYCKCAFIBQgBSgCACIMrSIOfCAOgCIVPgIQIAUgEyAFKAIEIg2tIg58IA6AIhA+AhQgBUJ/IAmtIg6GIg8gEMR9IA6HpyAPIBEgDawiEHwgEH/EfSAOh6drNgIMIAUgDyAVxH0gDoenIA8gEiAMrCIPfCAPf8R9IA6Hp2s2AgggBUE0aiEFIAZBAWoiBiAHRw0ACwsgByALSQRAIAIoAhghBQNAIAUgB0E0bCIGaigCLBAQIAIoAhgiBSAGakEANgIsIAdBAWoiByACKAIQSQ0ACyACIAAoAkgoAhA2AhALIAAoAkwiBQRAIAUQIQsgAEEBQSQQEyIHNgJMQQAhBSAHRQ0AIAIgBxA/IAAgBDYCLCAAKALAAUEXIAMQJEUNACAAKALAASIEKAIAIQYgBCgCCCEHAkAgBgRAQQEhBSAGQQFxIQsgBkEBRgR/QQAFIAZBfnEhCEEAIQYDQAJ/QQAgBUUNABpBACAAIAEgAyAHKAIAEQAARQ0AGiAAIAEgAyAHKAIEEQAAQQBHCyEFIAdBCGohByAGQQJqIgYgCEcNAAsgBUEBcwshBgJAAkAgCwRAIAYNASAAIAEgAyAHKAIAEQAAQQBHIQULIARBADYCACAFQQFxRQ0BDAMLIARBADYCAAsgACgCSBAhQQAhBSAAQQA2AkgMAgsgBEEANgIACyAAIAIQRyEFCyAKQRBqJAAgBQvyAwEFfwJAAkAgACgCPCICRQRAIAEoAhANAUEBDwsgAkE0bBAUIgVFDQEgASgCEARAIAEoAhghAgNAIAIgA0E0bCIEaigCLBAQIAEoAhgiAiAEakEANgIsIANBAWoiAyABKAIQIgRJDQALCyABIAAoAjwEfyAAKAJMKAIYIQNBACECA0AgBSACQTRsaiIEIAMgACgCQCACQQJ0aigCAEE0bCIGaiID/QACAP0LAgAgBCADKAIwNgIwIAQgA/0AAiD9CwIgIAQgA/0AAhD9CwIQIAQgACgCTCgCGCIDIAZqIgYoAiQ2AiQgBCAGKAIsNgIsIAZBADYCLCACQQFqIgIgACgCPCIGSQ0ACyABKAIQBSAECwR/IAAoAkwoAhghAkEAIQMDQCACIANBNGwiBGooAiwQECAAKAJMKAIYIgIgBGpBADYCLCADQQFqIgMgASgCEEkNAAsgACgCPAUgBgs2AhAgASgCGBAQIAEgBTYCGEEBDwsgASgCGCEEIAAoAkwoAhghA0EAIQIDQCAEIAJBNGwiBWoiBCADIAVqKAIkNgIkIAQoAiwQECABKAIYIgQgBWogACgCTCgCGCIDIAVqIgUoAiw2AiwgBUEANgIsIAJBAWoiAiABKAIQSQ0AC0EBDwsgACgCSBAhIABBADYCSEEAC84EAQh/AkAgAkUNAAJAIAAoAqABIgVFDQAgACgCSCIERQ0AIAQoAhBFDQAgBCgCGCgCKCAFRw0AIAIoAhAiCEUNACACKAIYIgYoAigNACAGKAIsDQBBACEEIAhBCE8EQCAIQXhxIQkDQCAGIARBNGxqIAU2AiggBiAEQQFyQTRsaiAFNgIoIAYgBEECckE0bGogBTYCKCAGIARBA3JBNGxqIAU2AiggBiAEQQRyQTRsaiAFNgIoIAYgBEEFckE0bGogBTYCKCAGIARBBnJBNGxqIAU2AiggBiAEQQdyQTRsaiAFNgIoIARBCGohBCAKQQhqIgogCUcNAAsLIAhBB3EiCARAA0AgBiAEQTRsaiAFNgIoIARBAWohBCALQQFqIgsgCEcNAAsLIAIgAxA3DQBBAA8LIAAoAkwiBUUEQCAAQQFBJBATIgU2AkwgBUUNAQsgAiAFED8gACgCwAFBFiADECRFDQAgACgCwAEiBigCACEEIAYoAgghBQJAIAQEQEEBIQcgBEEBcSEIIARBAUYEf0EABSAEQX5xIQlBACEEA0ACf0EAIAdFDQAaQQAgACABIAMgBSgCABEAAEUNABogACABIAMgBSgCBBEAAEEARwshByAFQQhqIQUgBEECaiIEIAlHDQALIAdBAXMLIQQCQAJAIAgEQCAEDQEgACABIAMgBSgCABEAAEEARyEHCyAGQQA2AgAgB0EBcUUNAQwDCyAGQQA2AgALIAAoAkgQISAAQQA2AkhBAA8LIAZBADYCAAsgACACEEchBwsgBwv4BAEGfwJAQQFBMBATIgIEfyACIAAoAsgBIgH9AAMA/QsDACACIAEpAxA3AxAgAiABKAIYIgE2AhggAiABQRhsEBQiATYCHCABRQRAIAIQEEEADwsCQCAAKALIASgCHCIDBEAgASADIAIoAhhBGGwQEhoMAQsgARAQIAJBADYCHAsgAiAAKALIASgCJCIBNgIkIAIgAUEoEBMiATYCKCABRQRAIAIoAhwQECACEBBBAA8LAkAgACgCyAEoAigEQCACKAIkRQ0BA0AgASAFQShsIgNqIAAoAsgBKAIoIANqKAIUIgE2AhQgAUEYbBAUIQEgAigCKCIEIANqIgYgATYCGCABRQRAIAUEf0EAIQEDQCACKAIoIAFBKGxqKAIYEBAgAUEBaiIBIAVHDQALIAIoAigFIAQLEBAMBQsCQCAAKALIASgCKCADaigCGCIEBEAgASAEIAYoAhRBGGwQEhogAigCKCEBDAELIAEQECACKAIoIgEgA2pBADYCGAsgASADaiAAKALIASgCKCADaigCBCIBNgIEIAFBGGwQFCEBIAIoAigiBCADaiIGIAE2AhAgAUUEQCAFBH9BACEBA0AgAUEobCIAIAIoAihqKAIYEBAgAigCKCAAaigCEBAQIAFBAWoiASAFRw0ACyACKAIoBSAECxAQDAULAkAgACgCyAEoAiggA2ooAhAiBARAIAEgBCAGKAIEQRhsEBIaIAIoAighAQwBCyABEBAgAigCKCIBIANqQQA2AhALIAEgA2pCADcCICAFQQFqIgUgAigCJEkNAAsMAQsgARAQIAJBADYCKAsgAgVBAAsPCyACKAIcEBAgAhAQQQALoAYCDn8BeyMAQRBrIggkACAAKAJIKAIQIQ0gCEEBQTgQEyIBNgIMAkAgAUUNACABIAAoAkgoAhAiCTYCGCABIAD9AAJU/QsCACABIAAoAmg2AhAgACgCbCECIAFBADYCNCABIAI2AhQgASAAKAIMIgwoAgA2AiAgASAMKAIENgIkIAEgDCgCCDYCKCABIAwoAhA2AiwgASAJQbgIEBMiADYCMCAABEAgDQRAA0AgDkG4CGwiACABKAIwaiIFIAwoAtArIABqIgT9AAIAIg/9CwIEIAUgBCgCEDYCFCAFIAQoAhQ2AhggD/0bASIAQSBNBEAgBUG0B2ogBEGwB2ogABASGiAFQbAGaiAEQawGaiAEKAIEEBIaCyAFIAQoAhgiADYCHCAFIAQoAqQGNgKoBkEBIQYCQCAAQQFHBEAgBCgCBEEDbCIAQQNrQd8ASw0BIABBAmshBgsgBUGkA2ohCSAFQSBqIQogBEEcaiELQQAhAAJAIAZBCEkNACAEIAZBA3RqQRxqIApLBEAgCyAFIAZBAnRqQaQDakkNAQsgBkF8cSEAQQAhAgNAIAogAkECdCIDaiALIAJBA3RqIgdBHGogB0EUaiAHQQxqIAf9CQIE/VYCAAH9VgIAAv1WAgAD/QsCACADIAlqIAdBGGogB0EQaiAHQQhqIAf9CQIA/VYCAAH9VgIAAv1WAgAD/QsCACACQQRqIgIgAEcNAAsgACAGRg0BCyAAQQFyIQMgBkEBcQRAIAogAEECdCICaiALIABBA3RqIgAoAgQ2AgAgAiAJaiAAKAIANgIAIAMhAAsgAyAGRg0AA0AgCiAAQQJ0IgJqIAsgAEEDdGoiAygCBDYCACACIAlqIAMoAgA2AgAgCiAAQQFqIgNBAnQiAmogCyADQQN0aiIDKAIENgIAIAIgCWogAygCADYCACAAQQJqIgAgBkcNAAsLIAUgBCgCqAY2AqwGIA5BAWoiDiANRw0ACwsgASEDDAELIAhBDGoEQCAIKAIMIgEoAjAiAAR/IAAQECAIKAIMBSABCxAQIAhBADYCDAsLIAhBEGokACADC/kEAQh/IwBBgAJrIgMkACAABEBB/AxBESACEB0gAyAAKAIANgLwASACQZoRIANB8AFqEBYgAyAAKAIENgLgASACQacRIANB4AFqEBYgAyAAKAIINgLQASACQYI3IANB0AFqEBYgAyAAKAIQNgLAASACQf0QIANBwAFqEBYgAUEASgRAA0AgACgC0CshBCADIAc2ArABIAJBog0gA0GwAWoQFiADIAQgB0G4CGxqIgQoAgA2AqABIAJBmREgA0GgAWoQFiADIAQoAgQ2ApABIAJB9DcgA0GQAWoQFiADIAQoAgg2AoABIAJBoDYgA0GAAWoQFiADIAQoAgw2AnAgAkGwNiADQfAAahAWIAMgBCgCEDYCYCACQYgRIANB4ABqEBYgAyAEKAIUNgJQIAJBtjggA0HQAGoQFkHVC0EXIAIQHSAEKAIEBEAgBEGwB2ohBiAEQawGaiEIQQAhBQNAIAggBUECdCIJaigCACEKIAMgBiAJaigCADYCRCADIAo2AkAgAkGLDCADQUBrEBYgBUEBaiIFIAQoAgRJDQALCyACEG4gAyAEKAIYNgIwIAJBwDYgA0EwahAWIAMgBCgCpAY2AiAgAkHxNiADQSBqEBZBASEGQe0LQRQgAhAdAkAgBCgCGEEBRwRAIAQoAgQiBUEATA0BIAVBA2xBAmshBgsgBEEcaiEIQQAhBQNAIAMgCCAFQQN0aikCAEIgiTcDECACQYsMIANBEGoQFiAFQQFqIgUgBkcNAAsLIAIQbiADIAQoAqgGNgIAIAJB4DYgAxAWQZkMQQUgAhAdIAdBAWoiByABRw0ACwtBmgxBBCACEB0LIANBgAJqJAAL5goDCX8BewF+IwBBsAFrIgUkAAJAIAFBgANxBEBBni1BCyACEB0MAQsCQCABQQFxRQ0AIAAoAkgiBkUNACMAQdAAayIDJABB7gxBDSACEB0gA0EAOgBPIANBCToATiADIAYpAgA3AkQgAyADQc4AaiIENgJAIAJBhjkgA0FAaxAWIAMgBikCCDcCNCADIAQ2AjAgAkH1OCADQTBqEBYgAyAGKAIQNgIkIAMgBDYCICACQZM3IANBIGoQFgJAIAYoAhhFDQAgBigCEEUNAANAIAMgA0HOAGoiCjYCECADIAc2AhQgAkGODSADQRBqEBYgBigCGCAHQTRsaiEIIwBBMGsiBCQAIARBCTsALiAEQQk6AC0gBCAIKQIANwIkIAQgBEEtaiIJNgIgIAJBzzYgBEEgahAWIAQgCCgCGDYCFCAEIAk2AhAgAkHFOCAEQRBqEBYgBCAIKAIgNgIEIAQgCTYCACACQao4IAQQFiAEQTBqJAAgAyAKNgIAIAJBlAwgAxAWIAdBAWoiByAGKAIQSQ0ACwtBnAxBAiACEB0gA0HQAGokAAsCQCABQQJxRQ0AIAAoAkhFDQBB+Q1BJCACEB0gBSAAKQJUNwOgASACQecRIAVBoAFqEBYgBSAAKQJcNwOQASACQcURIAVBkAFqEBYgBSAAKQNoNwOAASACQdcRIAVBgAFqEBYgACgCDCAAKAJIKAIQIAIQS0GcDEECIAIQHQsCQCABQQhxRQ0AIAAoAkhFDQAgACgCaCAAKAJsbCIERQ0AIAAoApwBIQMDQCADIAAoAkgoAhAgAhBLIANBjCxqIQMgC0EBaiILIARHDQALCyABQRBxRQ0AIAAoAsgBIQFB0w1BJSACEB0gBSAB/QADAP0LBHAgAkHJKyAFQfAAahAWQcENQREgAhAdAkAgASgCHEUNACABKAIYRQ0AQQAhAwNAIAEoAhwgA0EYbGoiAC8BACEEIAApAwghDSAFIAAoAhA2AmAgBSANNwNYIAUgBDYCUCACQYs4IAVB0ABqEBYgA0EBaiIDIAEoAhhJDQALC0GaDEEEIAIQHQJAIAEoAigiBEUNACABKAIkIgdFDQBBACEDQQAhAAJAIAdBBE8EQCAHQXxxIQADQCAEIANBA3JBKGxqQQRqIAQgA0ECckEobGpBBGogBCADQQFyQShsakEEaiAEIANBKGxq/QkCBP1WAgAB/VYCAAL9VgIAAyAM/a4BIQwgA0EEaiIDIABHDQALIAwgDCAM/Q0ICQoLDA0ODwABAgMAAQID/a4BIgwgDCAM/Q0EBQYHAAECAwABAgMAAQID/a4B/RsAIQMgACAHRg0BCwNAIAQgAEEobGooAgQgA2ohAyAAQQFqIgAgB0cNAAsLIANFDQBBsA1BECACEB0gASgCJARAIAEoAighAEEAIQcDQCAFIAAgB0EobCIEaigCBCIGNgJEIAUgBzYCQCACQdE4IAVBQGsQFiABKAIoIQACQCAGRQ0AQQAhAyAAIARqKAIQRQ0AA0AgASgCKCAEaigCECADQRhsaiIA/QADACEMIAUgACkDEDcDOCAFIAz9CwMoIAUgAzYCICACQaXRACAFQSBqEBYgA0EBaiIDIAZHDQALIAEoAighAAsCQCAAIARqIgYoAhhFDQBBACEDIAYoAhRFDQADQCAAIARqKAIYIANBGGxqIgAvAQAhBiAAKQMIIQ0gBSAAKAIQNgIQIAUgDTcDCCAFIAY2AgAgAkGLOCAFEBYgA0EBaiIDIAEoAigiACAEaigCFEkNAAsLIAdBAWoiByABKAIkSQ0ACwtBmgxBBCACEB0LQZwMQQIgAhAdCyAFQbABaiQAC48CAQN/AkBBAUHoARATIgEEfyABQQE2AgAgAUEBNgK4ASABIAEtALwBQQZyOgC8ASABQQFBjCwQEyIANgIMIABFDQEgAUEBQegHEBMiADYCECAARQ0BIAFCADcDMCABQX82AiwgAUHoBzYCFAJAQQFBMBATIgAEQCAAQQA2AhggAEHkADYCICAAQeQAQRgQEyICNgIcIAINASAAEBALIAFBADYCyAEMAgsgAEEANgIoIAEgADYCyAEgARAzIgA2AsQBIABFDQEgARAzIgA2AsABIABFDQECQBCRAUUNAAsgAUEAEGYiADYC1AEgAEUEQCABQQAQZiIANgLUASAARQ0CCyABBUEACw8LIAEQOEEAC40JAgl/AX4jAEHQAWsiByQAIAAoAkghCQJAAkACQCAAKAJoQQFHDQAgACgCbEEBRw0AIAAoApwBKALcKw0BCyAAKAIIQQhGDQAgBkEBQeHOAEEAEA8MAQsCQCABKAIQIgxFDQAgACgCoAEhCiABKAIYIQsgDEEITwRAIAxBeHEhDwNAIAsgCEE0bGogCjYCKCALIAhBAXJBNGxqIAo2AiggCyAIQQJyQTRsaiAKNgIoIAsgCEEDckE0bGogCjYCKCALIAhBBHJBNGxqIAo2AiggCyAIQQVyQTRsaiAKNgIoIAsgCEEGckE0bGogCjYCKCALIAhBB3JBNGxqIAo2AiggCEEIaiEIIA5BCGoiDiAPRw0ACwsgDEEHcSIMRQ0AA0AgCyAIQTRsaiAKNgIoIAhBAWohCCANQQFqIg0gDEcNAAsLIAIgA3IgBHIgBXJFBEAgBkEEQa8wQQAQDyAAQgA3AhwgACAAKQJoNwIkIAEgCf0AAgD9CwIAIAEgBhA3IQgMAQsgAkEASARAIAcgAjYCACAGQQFBx90AIAcQD0EAIQgMAQsgAiAJKAIIIghLBEAgByAINgIUIAcgAjYCECAGQQFBm+EAIAdBEGoQD0EAIQgMAQsCQCACIAkoAgAiCEkEQCAHIAg2AsQBIAcgAjYCwAEgBkECQfvjACAHQcABahAPIABBADYCHCAJKAIAIQIMAQsgACACIAAoAlRrIAAoAlxuNgIcCyABIAI2AgAgA0EASARAIAcgAzYCICAGQQFBh90AIAdBIGoQD0EAIQgMAQsgAyAJKAIMIgJLBEAgByACNgI0IAcgAzYCMCAGQQFB7t8AIAdBMGoQD0EAIQgMAQsCQCADIAkoAgQiAkkEQCAHIAI2ArQBIAcgAzYCsAEgBkECQcziACAHQbABahAPIABBADYCICAJKAIEIQMMAQsgACADIAAoAlhrIAAoAmBuNgIgCyABIAM2AgRBACEIIARBAEwEQCAHIAQ2AkAgBkEBQcXcACAHQUBrEA8MAQsgBCAJKAIAIgJJBEAgByACNgJUIAcgBDYCUCAGQQFBouMAIAdB0ABqEA8MAQsCQCAEIAkoAggiAksEQCAHIAI2AqQBIAcgBDYCoAEgBkECQcPgACAHQaABahAPIAAgACgCaDYCJCAJKAIIIQQMAQsgACAANQJcIhAgBCAAKAJUa618QgF9IBCAPgIkCyABIAQ2AgggBUEATARAIAcgBTYCYCAGQQFBgtwAIAdB4ABqEA8MAQsgBSAJKAIEIgJJBEAgByACNgJ0IAcgBTYCcCAGQQFB8uEAIAdB8ABqEA8MAQsCQCAFIAkoAgwiAksEQCAHIAI2ApQBIAcgBTYCkAEgBkECQZXfACAHQZABahAPIAAgACgCbDYCKCAJKAIMIQUMAQsgACAANQJgIhAgBSAAKAJYa618QgF9IBCAPgIoCyABIAU2AgwgACAALQBEQQJyOgBEIAEgBhA3IghFBEBBACEIDAELIAcgAf0AAgD9CwSAASAGQQRBtDkgB0GAAWoQDwsgB0HQAWokACAIC5UCAQd/IwBBIGsiBSQAAn8gACgCSCIERQRAIANBAUHF5gBBABAPQQAMAQtBAEEEIAQoAhAQEyIERQ0AGiABBEAgACgCSCEIA0ACQAJAIAIgBkECdGooAgAiByAIKAIQTwRAIAUgBzYCECADQQFB+REgBUEQahAPDAELIAQgB0ECdGoiCSgCAEUNASAFIAc2AgAgA0EBQY0aIAUQDwsgBBAQQQAMAwsgCUEBNgIAIAZBAWoiBiABRw0ACwsgBBAQIAAoAkAQEAJAIAEEQCAAIAFBAnQiBBAUIgM2AkAgA0UEQCAAQQA2AjxBAAwDCyADIAIgBBASGgwBCyAAQQA2AkALIAAgATYCPEEBCyEKIAVBIGokACAKC7wFAQd/IAFBAUEkEBMiBDYCSAJAAkAgBEUNAAJAIAEoAsQBQRIgAxAkBEAgASgCxAFBEyADECQNAQsMAgsgASgCxAEiBygCACEGIAcoAgghBAJAIAYEQEEBIQUgBkEBRwRAIAZBfnEhCQNAAn9BACAFRQ0AGkEAIAEgACADIAQoAgARAABFDQAaIAEgACADIAQoAgQRAABBAEcLIQUgBEEIaiEEIAhBAmoiCCAJRw0ACwsCQAJAIAZBAXEEQCAFRQ0BIAEgACADIAQoAgARAABBAEchBQsgB0EANgIAIAVFDQEMAwsgB0EANgIACwwDCyAHQQA2AgALAkAgASgCwAFBFCADECQEQCABKALAAUEVIAMQJA0BCwwCCyABKALAASIHKAIAIQYgBygCCCEEAkAgBgRAQQEhBSAGQQFxIQkgBkEBRgR/QQAFIAZBfnEhBkEAIQgDQAJ/QQAgBUUNABpBACABIAAgAyAEKAIAEQAARQ0AGiABIAAgAyAEKAIEEQAAQQBHCyEFIARBCGohBCAIQQJqIgggBkcNAAsgBUULIQYCQAJAIAkEQCAGDQEgASAAIAMgBCgCABEAAEEARyEFCyAHQQA2AgAgBUUNAQwDCyAHQQA2AgALDAMLIAdBADYCAAsgAkEBQSQQEyIANgIAIABFDQAgASgCSCAAED8gASgCyAEgASgCbCABKAJobCIANgIkIABBKBATIQMgASgCyAEiACADNgIoAkAgA0UNACAAKAIkRQRAQQEPC0EAIQQDQCADIARBKGwiBWoiAEEANgIUIABB5AA2AhxB5ABBGBATIQAgBSABKALIASIHKAIoIgNqIAA2AhggAEUNAUEBIQogBEEBaiIEIAcoAiRJDQALDAELIAIoAgAQIUEAIQogAkEANgIACyAKDwsgASgCSBAhIAFBADYCSEEACwIACwQAQQELNAACQCAARQ0AIAFFDQAgACABKAIENgKkASAAIAEoAgA2AqABIAAgASgCuEBBAnE2AuABCwu0BQEIfyAAKAIYIgQoAhAiCUUEQEEADwsgBCgCGCEFIAAoAhQoAgAoAhQhBAJAAkAgAUUEQEEAIQEDQCAFKAIYIQIgBCgCHCAEKAIYQZgBbGoiAEGMAWsoAgAiByAAQZQBaygCACIIayEDIABBkAFrKAIAIABBmAFrKAIAayEAAkAgByAIRg0AIACtIAOtfkIgiFANAAwECyAAIANsIQMCQEEEIAJBA3YgAkEHcUEAR2oiACAAQQNGGyICRQ0AIAKtIAOtfkIgiFANAAwEC0F/IQAgAiADbCICIAFBf3NLDQIgBEHMAGohBCAFQTRqIQUgASACaiIBIQAgBkEBaiIGIAlHDQALDAELQQAhASAAKAJARQRAA0AgBSgCGCECIAQoAhwgBCgCGEGYAWxqIgBBBGsoAgAiByAAQQxrKAIAIghrIQMgAEEIaygCACAAQRBrKAIAayEAAkAgByAIRg0AIACtIAOtfkIgiFANAAwECyAAIANsIQMCQEEEIAJBA3YgAkEHcUEAR2oiACAAQQNGGyICRQ0AIAKtIAOtfkIgiFANAAwEC0F/IQAgAiADbCICIAFBf3NLDQIgBEHMAGohBCAFQTRqIQUgASACaiIBIQAgBkEBaiIGIAlHDQALDAELA0AgBSgCGCECIAQoAhwgBCgCGEGYAWxqIgBBjAFrKAIAIgcgAEGUAWsoAgAiCGshAyAAQZABaygCACAAQZgBaygCAGshAAJAIAcgCEYNACAArSADrX5CIIhQDQAMAwsgACADbCEDAkBBBCACQQN2IAJBB3FBAEdqIgAgAEEDRhsiAkUNACACrSADrX5CIIhQDQAMAwtBfyEAIAIgA2wiAiABQX9zSw0BIARBzABqIQQgBUE0aiEFIAEgAmoiASEAIAZBAWoiBiAJRw0ACwsgAA8LQX8L2gQBC38gAARAIAAoAhQiAQRAIAEoAgAiBQRAIAUoAhQhAyAFKAIQBH9BEEERIAAtAChBAXEbIQgDQCADKAIcIgIEQCADKAIgIgFBmAFuIQpBACEJIAFBmAFPBH8DQCACKAIwIgEEQCACKAI0IgZBKG4hB0EAIQQgBkEoTwR/A0AgASgCIBApIAFBADYCICABKAIkECkgAUEANgIkIAEgCBECACABQShqIQEgBEEBaiIEIAdHDQALIAIoAjAFIAELEBAgAkEANgIwCyACKAJUIgEEQCACKAJYIgZBKG4hB0EAIQQgBkEoTwR/A0AgASgCIBApIAFBADYCICABKAIkECkgAUEANgIkIAEgCBECACABQShqIQEgBEEBaiIEIAdHDQALIAIoAlQFIAELEBAgAkEANgJUCyACKAJ4IgEEQCACKAJ8IgZBKG4hB0EAIQQgBkEoTwR/A0AgASgCIBApIAFBADYCICABKAIkECkgAUEANgIkIAEgCBECACABQShqIQEgBEEBaiIEIAdHDQALIAIoAngFIAELEBAgAkEANgJ4CyACQZgBaiECIAlBAWoiCSAKRw0ACyADKAIcBSACCxAQIANBADYCHAsCQCADKAIoRQ0AIAMoAiQiAUUNACABEBAgA/0MAAAAAAAAAAAAAAAAAAAAAP0LAiQLIAMoAjQQECADQcwAaiEDIAtBAWoiCyAFKAIQSQ0ACyAFKAIUBSADCxAQIAVBADYCFCAAKAIUKAIAEBAgACgCFCIBQQA2AgALIAEQECAAQQA2AhQLIAAoAkQQECAAEBALC8sTARV/IwBBIGsiDyQAIA8gBTYCGCABIAMoAhxBzABsaigCHCADKAIgQZgBbGohEQJAAkAgAygCKA0AIBEoAhhFDQAgEUEcaiEJA0ACQCAJKAIIIAkoAgBHBH8gCSgCDCAJKAIERgVBAQsNACADKAIkIgEgCSgCGEEobk8EQCAIQQFBghVBABAPDAQLIAkoAhQgAUEobGoiASgCIBBiIAEoAiQQYiABKAIUIAEoAhBsIg1FDQAgASgCGCEBIA1BCE8EQCANQXhxIQtBACEKA0AgAUIANwLoAyABQgA3AqgDIAFCADcC6AIgAUIANwKoAiABQgA3AugBIAFCADcCqAEgAUIANwJoIAFCADcCKCABQYAEaiEBIApBCGoiCiALRw0ACwtBACEKIA1BB3EiDUUNAANAIAFCADcCKCABQUBrIQEgCkEBaiIKIA1HDQALCyAJQSRqIQkgDEEBaiIMIBEoAhhJDQALCyAFIQ0CQCACLQAAQQJxRQ0AIAdBBU0EQCAIQQJBsR9BABAPDAELAkAgBS0AAEH/AUYEQCAFLQABQZEBRg0BCyAIQQJB2x9BABAPDAELIA8gBUEGaiINNgIYC0EUEBQiC0UNAAJ/IAAtAGxBAXEEQCAAQShqIQcgACgCKCENIABBLGoMAQsgAi0AiCxBAnEEQCACQbAoaiEHIAIoArAoIQ0gAkG8KGoMAQsgDyAFIAdqIA1rNgIcIA9BGGohByAPQRxqCyISKAIAIQAgC0IANwIMIAsgDTYCCCALIA02AgAgCyAAIA1qNgIEIAtBARAfRQRAIAsQZBogCygCCCALKAIAayEaIAsQLCAaIA1qIQECQCACLQAAQQRxRQ0AIAcoAgAgEigCACABa2pBAU0EQCAIQQJBmCFBABAPDAELAkAgAS0AAEH/AUYEQCABLQABQZIBRg0BCyAIQQJBwiFBABAPDAELIAFBAmohAQsgEiASKAIAIAcoAgAgAWtqNgIAIAcgATYCACAEQQA2AgAgBiAPKAIYIAVrNgIAQQEhFwwBCyARKAIYBEAgEUEcaiEQA0AgAygCJCEAIBAoAhQhAQJAIBAoAgggECgCAEcEfyAQKAIMIBAoAgRGBUEBCw0AIAEgAEEobGoiFCgCFCAUKAIQbCIYRQ0AIBQoAhghCUEAIRUDQAJAAn8gCSgCKEUEQCALIBQoAiAgFSADKAIoQQFqEGAMAQsgC0EBEB8LRQRAIAlBADYCJAwBCyAJKAIoRQRAQQAhAQNAIAEiAEEBaiEBIAsgFCgCJCAVIAAQYEUNAAsgECgCHCEBIAlBAzYCICAJIAE2AhggCSABIABrQQFqNgIcCyAJAn9BASALQQEQH0UNABpBAiALQQEQH0UNABogC0ECEB8iAEEDRwRAIABBA2oMAQsgC0EFEB8iAEEfRwRAIABBBmoMAQsgC0EHEB9BJWoLNgIkQQAhAQNAIAEiAEEBaiEBIAtBARAfDQALIAkgCSgCICAAajYCIAJAAkACfyAJKAIoIgBFBEAgAigC0CsgAygCHEG4CGxqKAIQIQAgCSgCMEUEQCAJKAIAQfABEBciAUUNBCAJIAE2AgAgASAJKAIwQRhsakEAQfABEBUaIAlBCjYCMAsgCSgCACIB/QwAAAAAAAAAAAAAAAAAAAAA/QsCACABQgA3AhBBAUEKQe0AIABBAXEbIABBBHEbIQpBAAwBCyAJKAIAIgEgAEEBayIMQRhsaiIKKAIEIAooAgxHDQEgAigC0CsgAygCHEG4CGxqKAIQIQogCSgCMCIMIABBAWpJBH8gASAMQQpqIgxBGGwQFyIBRQ0DIAkgATYCACABIAkoAjBBGGxqQQBB8AEQFRogCSAMNgIwIAkoAgAFIAELIABBGGxqIgH9DAAAAAAAAAAAAAAAAAAAAAD9CwIAIAFCADcCEAJ/QQEgCkEEcQ0AGkHtACAKQQFxRQ0AGkECQQJBASABQQxrKAIAIgpBCkYbIApBAUYbCyEKIAALIQwgASAKNgIMCyAJKAIkIQAgAigC0CsgAygCHEG4CGxqLQAQQcAAcQRAA0AgDEEYbCIOIAkoAgBqIABBASAMGyITNgIQIAkoAiAhFkEAIQogACEBIBNBAk8EQANAIApBAWohCiABQQNLIRsgAUEBdiEBIBsNAAsLIAogFmoiAUEhTwRAIA8gATYCECAIQQFBvPQAIA9BEGoQDwwDCyALIAEQHyEKIAkoAgAiASAOaiIOIAo2AhQgACAOKAIQayIAQQBMDQMgAigC0CsgAygCHEG4CGxqKAIQIQogCSgCMCIOIAxBAmpJBEAgASAOQQpqIg5BGGwQFyIBRQ0DIAkgATYCACABIAkoAjBBGGxqQQBB8AEQFRogCSAONgIwIAkoAgAhAQsgASAMQQFqIgxBGGxqIgH9DAAAAAAAAAAAAAAAAAAAAAD9CwIAIAFCADcCECABAn9BASAKQQRxDQAaQe0AIApBAXFFDQAaQQJBAkEBIAFBDGsoAgAiAUEKRhsgAUEBRhsLNgIMDAALAAsDQCAMQRhsIg4gCSgCAGoiASABKAIMIAEoAgRrIgEgACAAIAFKGyIBNgIQIAkoAiAhE0EAIQogAUECTwRAA0AgCkEBaiEKIAFBA0shHCABQQF2IQEgHA0ACwsgCiATaiIBQSFPBEAgDyABNgIAIAhBAUG89AAgDxAPDAILIAsgARAfIQogCSgCACIBIA5qIg4gCjYCFCAAIA4oAhBrIgBBAEwNAiACKALQKyADKAIcQbgIbGooAhAhCiAJKAIwIg4gDEECakkEQCABIA5BCmoiDkEYbBAXIgFFDQIgCSABNgIAIAEgCSgCMEEYbGpBAEHwARAVGiAJIA42AjAgCSgCACEBCyABIAxBAWoiDEEYbGoiAf0MAAAAAAAAAAAAAAAAAAAAAP0LAgAgAUIANwIQIAECf0EBIApBBHENABpB7QAgCkEBcUUNABpBAkECQQEgAUEMaygCACIBQQpGGyABQQFGGws2AgwMAAsACyALECwMBQsgCUFAayEJIBVBAWoiFSAYRw0ACwsgEEEkaiEQIBlBAWoiGSARKAIYSQ0ACwsgCxBkRQRAIAsQLAwBCyALKAIIIAsoAgBrIR0gCxAsIB0gDWohAQJAIAItAABBBHFFDQAgBygCACASKAIAIAFrakEBTQRAIAhBAkGYIUEAEA8MAQsCQCABLQAAQf8BRgRAIAEtAAFBkgFGDQELIAhBAkHCIUEAEA8MAQsgAUECaiEBCyASIBIoAgAgBygCACABa2o2AgAgByABNgIAQQEhFyAEQQE2AgAgBiAPKAIYIAVrNgIACyAPQSBqJAAgFwuWJAIUfw5+AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCAAKAJUDgUAAQIDBAoLAkAgACgCNCIGIAAoAsQBIgFJBEAgACgCQCIHIAFBAWpJDQELIAAoAuwBQQFB9D9BABAPDAwLIAAoAixFBEAgACgCJCECQQAhAQwFCyAAQQA2AiwgACgCRCEDQQEhAQwECwJAIAAoAjQiBiAAKALEASIBSQRAIAAoAkAiByABQQFqSQ0BCyAAKALsAUEBQaHAAEEAEA8MCwsgACgCLEUEQCAAKAIkIQRBACEBDAgLIABBADYCLCAAKAIwIQNBASEBDAcLAkAgACgCNCIEIAAoAsQBIgpJBEAgACgCQCIOIApBAWpJDQELIAAoAuwBQQFBqMEAQQAQDwwKCyAAKAIsRQRAIAAoAighCwwGCyAAQgA3AuQBIABBADYCLCAAKALIASEMA0AgDCAHQQR0aiIFKAIIIg8EQCAFKAIMIRJBACEBA0ACQCAPIAFBf3NqIhAgEiABQQR0aiIRKAIAaiIJQR9LDQAgBSgCACITQX8gCXZLDQAgACACIBMgCXQiCSACIAlJGyAJIAIbIgI2AuQBCwJAIBEoAgQgEGoiCUEfSw0AIAUoAgQiEEF/IAl2Sw0AIAAgAyAQIAl0IgkgAyAJSRsgCSADGyIDNgLoAQsgAUEBaiIBIA9HDQALCyAHQQFqIgcgCkcNAAsgAkUNByADRQ0HIAAtAABFBEAgACAAKALQATYCbCAAIAAoAswBNgJkIAAgACgC2AE2AnAgACAAKALUATYCaAsgACgCMCEFQQEhAQwFCwJAIAAoAjQiBSAAKALEASIJSQRAIAAoAkAiEiAJQQFqSQ0BCyAAKALsAUEBQfvAAEEAEA8MCQsgACgCLEUEQCAAKALIASINIAAoAhwiBEEEdGohCyAAKAIoIQgMBAsgAEIANwLkASAAQQA2AiwgACgCyAEhDQNAIA0gBkEEdGoiCigCCCIOBEAgCigCDCEQQQAhAQNAAkAgDiABQX9zaiIRIBAgAUEEdGoiEygCAGoiDEEfSw0AIAooAgAiFEF/IAx2Sw0AIAAgAiAUIAx0IgwgAiAMSRsgDCACGyICNgLkAQsCQCATKAIEIBFqIgxBH0sNACAKKAIEIhFBfyAMdksNACAAIAMgESAMdCIMIAMgDEkbIAwgAxsiAzYC6AELIAFBAWoiASAORw0ACwsgBkEBaiIGIAlHDQALIAJFDQYgA0UNBgJAIAAtAAAEQCAAKAJsIQYMAQsgACAAKALQASIGNgJsIAAgACgCzAE2AmQgACAAKALYATYCcCAAIAAoAtQBNgJoC0EBIQEMAwsCQCAAKAI0IgYgACgCxAEiAUkEQCAAKAJAIg8gAUEBakkNAQsgACgC7AFBAUHOwABBABAPDAYLIAAoAixFBEAgACgCyAEgACgCHCIGQQR0aiEFIAAoAighB0EAIQEMAgsgACAGNgIcIABBADYCLEEBIQEMAQsDQAJ/AkAgAUUEQCACQQFqIQIMAQsgACADNgIoIAAoAjggA00NCSAAKAIwIQRBAAwBC0EBCyEBA0ACQAJAAkACQCABRQRAIAAgBDYCICAEIAAoAjxPDQEgACAGNgIcIAYhAUEAIQUMBAsgACACNgIkIAAoAkwgAk0EQCAAKAIcIQFBASEFDAQLIAAoAhAgACgCIGwgACgCDCAAKAIobGogACgCFCAAKAIcbGogACgCGCACbGoiASAAKAIITwRADAwLIAAoAgQgAUEBdGoiAS8BAA0BDA0LIAAoAihBAWohAwwBC0EAIQEMAwtBASEBDAILA0ACQAJAAkAgBUUEQCABIAdPDQEgACgCICIFIAAoAsgBIAFBBHRqIg0oAghPDQMgAC0AAEUEQCAAIA0oAgwgBUEEdGoiASgCDCABKAIIbDYCTAsgACgCSCECQQEhAQwFCyAAIAFBAWoiATYCHAwBCyAAKAIgQQFqIQRBACEBDAMLQQAhBQwBC0EBIQUMAAsACwALAAsDQAJ/AkAgAUUEQCAAIAdBAWoiBzYCKAwBCyAGIA9PDQggAEIANwLkASAAKALIASAGQQR0aiIFKAIIIgtFDQggBSgCDCEKQQAhAkEAIQRBACEBA0ACQCALIAFBf3NqIgkgCiABQQR0aiIOKAIAaiIIQR9LDQAgBSgCACIMQX8gCHZLDQAgACAEIAwgCHQiCCAEIAhJGyAIIAQbIgQ2AuQBCwJAIA4oAgQgCWoiCEEfSw0AIAUoAgQiCUF/IAh2Sw0AIAAgAiAJIAh0IgggAiAISRsgCCACGyICNgLoAQsgAUEBaiIBIAtHDQALIARFDQYgAkUNBgJAIAAtAAAEQCAAKAJsIQIMAQsgACAAKALQASICNgJsIAAgACgCzAE2AmQgACAAKALYATYCcCAAIAAoAtQBNgJoC0EADAELQQELIQEDQAJAAkACQAJAIAFFBEAgACACNgLgASACIAAoAnBPDQEgACgCZCENQQAhAQwECyAAKAI4IAdNBEAgACgCICEDQQEhAQwECyAAKAIQIAAoAiBsIAAoAgwgB2xqIAAoAhQgBmxqIAAoAhggACgCJGxqIgEgACgCCE8EQAwLCyAAKAIEIAFBAXRqIgEvAQANAQwMCyAAIAZBAWoiBjYCHAwBC0EAIQEMAwtBASEBDAILA0ACQAJAAkAgAAJ/IAFFBEAgACANNgLcASANIAAoAmhPDQIgACgCMAwBCyADQQFqCyIDNgIgIAAoAjwiASAFKAIIIgQgASAESRsgA0sEQCAFKAIAIgEgAa0iHiAEIANBf3NqIgitIhaGIhcgFoinRw0DIAUoAgQiBEJ/IBaIp3EgBEcNAyAErSIVIBaGIhhCAX0iGSAANQLYAXwgGIAhHyAZIAAoAtABIgmtfCAYgCEaIBdCAX0iGyAANQLUAXwgF4AhICAbIAAoAswBIg6tfCAXgCEcIAFCfyAFKAIMIANBBHRqIgsoAgAiCiAIaq0iHYincSABRw0DIAQgFSALKAIEIgEgCGqtIhWGIiEgFYinRw0DIAAoAuABIgStIiIgIYJCAFIEQCAEIAlHDQRCfyAVhkJ/hSAaQv////8PgyAWhoNQDQQLIAAoAtwBIgStIhUgHiAdhoJCAFIEQCAEIA5HDQRCfyAdhkJ/hSAcQv////8PgyAWhoNQDQQLIAsoAggiBEUNAyALKAIMRQ0DIBynIgsgIKdGDQMgGqciCCAfp0YNAyAAIAAoAkQiBzYCKCAAIBUgG3wgF4CnIAp2IAsgCnZrIBkgInwgGICnIAF2IAggAXZrIARsajYCJEEBIQEMBQsgACgC3AEiASAAKALkASIEaiABIARwayENDAELIAAoAuABIgEgACgC6AEiBGogASAEcGshAkEAIQEMAwtBACEBDAELQQEhAQwACwALAAsACwNAAn8CQCABRQRAIAAgCEEBaiIINgIoDAELIAAgBjYC4AEgACgCcCAGTQ0HIAAoAmQhD0EADAELQQELIQEDQAJAAkACQAJAIAFFBEAgACAPNgLcASAPIAAoAmhPDQEgACAFNgIcIAUhBEEAIQEMBAsgACgCOCAITQRAIAAoAiAhB0EBIQEMBAsgACgCECAAKAIgbCAAKAIMIAhsaiAAKAIUIARsaiAAKAIYIAAoAiRsaiIBIAAoAghPBEAMCgsgACgCBCABQQF0aiIBLwEADQEMCwsgACgC4AEiASAAKALoASIGaiABIAZwayEGDAELQQAhAQwDC0EBIQEMAgsDQAJAAkACQAJAIAFFBEAgBCASTw0CIAAgACgCMCIHNgIgIA0gBEEEdGohCwwBCyAAIAdBAWoiBzYCIAsgACgCPCIBIAsoAggiAiABIAJJGyAHSwRAIAsoAgAiASABrSIeIAIgB0F/c2oiCq0iFoYiFyAWiKdHDQMgCygCBCICQn8gFoincSACRw0DIAKtIhUgFoYiGEIBfSIZIAA1AtgBfCAYgCEfIBkgACgC0AEiDq18IBiAIRogF0IBfSIbIAA1AtQBfCAXgCEgIBsgACgCzAEiDK18IBeAIRwgAUJ/IAsoAgwgB0EEdGoiAygCACIJIApqrSIdiKdxIAFHDQMgAiAVIAMoAgQiASAKaq0iFYYiISAViKdHDQMgACgC4AEiAq0iIiAhgkIAUgRAIAIgDkcNBEJ/IBWGQn+FIBpC/////w+DIBaGg1ANBAsgACgC3AEiAq0iFSAeIB2GgkIAUgRAIAIgDEcNBEJ/IB2GQn+FIBxC/////w+DIBaGg1ANBAsgAygCCCICRQ0DIAMoAgxFDQMgHKciAyAgp0YNAyAapyIKIB+nRg0DIAAgACgCRCIINgIoIAAgFSAbfCAXgKcgCXYgAyAJdmsgGSAifCAYgKcgAXYgCiABdmsgAmxqNgIkQQEhAQwFCyAAIARBAWoiBDYCHAwBCyAAKALcASIBIAAoAuQBIgJqIAEgAnBrIQ9BACEBDAMLQQAhAQwBC0EBIQEMAAsACwALAAsDQAJ/AkAgAUUEQCAAIAtBAWoiCzYCKAwBCyAAIAU2AiAgACgCPCAFTQ0GIAAoAmwhCEEADAELQQELIQEDQAJAAkACQAJAIAFFBEAgACAINgLgASAIIAAoAnBPDQEgACgCZCENQQAhAQwECyAAKAI4IAtNBEAgACgCHCEGQQEhAQwECyAAKAIQIAAoAiBsIAAoAgwgC2xqIAAoAhQgACgCHGxqIAAoAhggACgCJGxqIgEgACgCCE8EQAwJCyAAKAIEIAFBAXRqIgEvAQANAQwKCyAAKAIgQQFqIQUMAQtBACEBDAMLQQEhAQwCCwNAAkACQAJAAkAgAUUEQCAAIA02AtwBIA0gACgCaE8NAiAAIAQ2AhwgBCEGDAELIAAgBkEBaiIGNgIcCyAGIA5JBEAgACgCICIHIAAoAsgBIAZBBHRqIgEoAggiA08NAyABKAIAIgIgAq0iHiADIAdBf3NqIgqtIhaGIhcgFoinRw0DIAEoAgQiA0J/IBaIp3EgA0cNAyADrSIVIBaGIhhCAX0iGSAANQLYAXwgGIAhHyAZIAAoAtABIg+tfCAYgCEaIBdCAX0iGyAANQLUAXwgF4AhICAbIAAoAswBIgmtfCAXgCEcIAJCfyABKAIMIAdBBHRqIgEoAgAiByAKaq0iHYincSACRw0DIAMgFSABKAIEIgIgCmqtIhWGIiEgFYinRw0DIAAoAuABIgOtIiIgIYJCAFIEQCADIA9HDQRCfyAVhkJ/hSAaQv////8PgyAWhoNQDQQLIAAoAtwBIgOtIhUgHiAdhoJCAFIEQCADIAlHDQRCfyAdhkJ/hSAcQv////8PgyAWhoNQDQQLIAEoAggiA0UNAyABKAIMRQ0DIBynIgEgIKdGDQMgGqciCiAfp0YNAyAAIAAoAkQiCzYCKCAAIBUgG3wgF4CnIAd2IAEgB3ZrIBkgInwgGICnIAJ2IAogAnZrIANsajYCJEEBIQEMBQsgACgC3AEiASAAKALkASICaiABIAJwayENDAELIAAoAuABIgEgACgC6AEiAmogASACcGshCEEAIQEMAwtBACEBDAELQQEhAQwACwALAAsACwNAAn8CQCABRQRAIARBAWohBAwBCyAAIAM2AiAgACgCPCADTQ0FIAAoAkQhAkEADAELQQELIQEDQAJAAkACQAJAIAFFBEAgACACNgIoIAIgACgCOE8NASAAIAY2AhwgBiEBQQAhBQwECyAAIAQ2AiQgACgCTCAETQRAIAAoAhwhAUEBIQUMBAsgACgCECAAKAIgbCAAKAIMIAAoAihsaiAAKAIUIAAoAhxsaiAAKAIYIARsaiIBIAAoAghPBEAMCAsgACgCBCABQQF0aiIBLwEADQEMCQsgACgCIEEBaiEDDAELQQAhAQwDC0EBIQEMAgsDQAJAAkACQCAFRQRAIAEgB08NASAAKAIgIgUgACgCyAEgAUEEdGoiDSgCCE8NAyAALQAARQRAIAAgDSgCDCAFQQR0aiIBKAIMIAEoAghsNgJMCyAAKAJIIQRBASEBDAULIAAgAUEBaiIBNgIcDAELIAAoAihBAWohAkEAIQEMAwtBACEFDAELQQEhBQwACwALAAsAC0EADwsgACgC7AFBAUGaCkEAEA8LQQAPCyABQQE7AQBBAQuRCwEKfwJAIAEoAgAgBEEDbCIMdiIGQZCAgAFxDQAgACAAQRxqIg4gACgCbCAGQe8DcWotAABBAnRqIgo2AmggACAAKAIEIAooAgAiCSgCACIIayIGNgIEAkAgCCAAKAIAIgdBEHZLBEAgCSgCBCELIAAgCDYCBCAKIAlBCEEMIAYgCEkiBhtqKAIANgIAIAsgC0UgBhshCSAAKAIIIQYDQAJAIAYNACAAKAIQIgZBAWohCyAGLQABIQogBi0AAEH/AUYEQCAKQZABTwRAIAAgACgCDEEBajYCDCAHQYD+A2ohB0EIIQYMAgsgACALNgIQIAcgCkEJdGohB0EHIQYMAQsgACALNgIQQQghBiAHIApBCHRqIQcLIAAgBkEBayIGNgIIIAAgB0EBdCIHNgIAIAAgCEEBdCIINgIEIAhBgIACSQ0ACyAIIQYMAQsgACAHIAhBEHRrIgc2AgAgBkGAgAJxRQRAIAkoAgQhCyAKIAlBDEEIIAYgCEkiCBtqKAIANgIAIAtFIAsgCBshCSAAKAIIIQgDQAJAIAgNACAAKAIQIghBAWohCyAILQABIQogCC0AAEH/AUYEQCAKQZABTwRAIAAgACgCDEEBajYCDCAHQYD+A2ohB0EIIQgMAgsgACALNgIQIAcgCkEJdGohB0EHIQgMAQsgACALNgIQQQghCCAHIApBCHRqIQcLIAAgCEEBayIINgIIIAAgB0EBdCIHNgIAIAAgBkEBdCIGNgIEIAZBgIACSQ0ACwwBCyAJKAIEIQkLIAlFDQAgACAOIAEoAgQgDEERanZBBHEgAUEEayINKAIAIAxBE2p2QQFxIAEoAgAiCCAMQRBqdkHAAHEgCCAMdkGqAXFyIAggDEEMakEOIAQbdkEQcXJyciIPQdC5AWotAABBAnRqIgs2AmggACAGIAsoAgAiCigCACIIayIGNgIEAkAgCCAHQRB2SwRAIAooAgQhCSAAIAg2AgQgCyAKQQhBDCAGIAhJIgYbaigCADYCACAJIAlFIAYbIQogACgCCCEGA0ACQCAGDQAgACgCECIGQQFqIQsgBi0AASEJIAYtAABB/wFGBEAgCUGQAU8EQCAAIAAoAgxBAWo2AgwgB0GA/gNqIQdBCCEGDAILIAAgCzYCECAHIAlBCXRqIQdBByEGDAELIAAgCzYCEEEIIQYgByAJQQh0aiEHCyAAIAZBAWsiBjYCCCAAIAdBAXQiBzYCACAAIAhBAXQiCDYCBCAIQYCAAkkNAAsMAQsgACAHIAhBEHRrIgk2AgAgBkGAgAJxRQRAIAooAgQhByALIApBDEEIIAYgCEkiCBtqKAIANgIAIAdFIAcgCBshCiAAKAIIIQcDQAJAIAcNACAAKAIQIgdBAWohCyAHLQABIQggBy0AAEH/AUYEQCAIQZABTwRAIAAgACgCDEEBajYCDCAJQYD+A2ohCUEIIQcMAgsgACALNgIQIAkgCEEJdGohCUEHIQcMAQsgACALNgIQQQghByAJIAhBCHRqIQkLIAAgB0EBayIHNgIIIAAgCUEBdCIJNgIAIAAgBkEBdCIGNgIEIAZBgIACSQ0ACwwBCyAKKAIEIQoLIAJBACADayADIAogD0HQuwFqLQAAcyIDGzYCACANIA0oAgBBICAMdHI2AgAgASABKAIAIANBE3RBEHIgDHRyNgIAIAEgASgCBEEIIAx0cjYCBCAEIAVyRQRAIAFBfiAAKAJ8a0ECdGoiAiACKAIEQYCAAnI2AgQgAiACKAIAIANBH3RyQYCABHI2AgAgAkEEayICIAIoAgBBgIAIcjYCAAsgBEEDRw0AIAEgACgCfEECdGoiAEEEaiAAKAIEQQRyNgIAIAAgACgCDEEBcjYCDCAAIAAoAgggA0ESdHJBAnI2AggLC6sLAQl/AkAgASgCACAEQQNsIg12IgdBkICAAXENACAHQe8DcSIHRQ0AIAAgAEEcaiIOIAAoAmwgB2otAABBAnRqIgs2AmggACAAKAIEIAsoAgAiCigCACIJayIHNgIEAkAgCSAAKAIAIghBEHZLBEAgCigCBCEMIAAgCTYCBCALIApBCEEMIAcgCUkiBxtqKAIANgIAIAwgDEUgBxshCiAAKAIIIQcDQAJAIAcNACAAKAIQIgdBAWohDCAHLQABIQsgBy0AAEH/AUYEQCALQZABTwRAIAAgACgCDEEBajYCDCAIQYD+A2ohCEEIIQcMAgsgACAMNgIQIAggC0EJdGohCEEHIQcMAQsgACAMNgIQQQghByAIIAtBCHRqIQgLIAAgB0EBayIHNgIIIAAgCEEBdCIINgIAIAAgCUEBdCIJNgIEIAlBgIACSQ0ACyAJIQcMAQsgACAIIAlBEHRrIgg2AgAgB0GAgAJxRQRAIAooAgQhDCALIApBDEEIIAcgCUkiCRtqKAIANgIAIAxFIAwgCRshCiAAKAIIIQkDQAJAIAkNACAAKAIQIglBAWohDCAJLQABIQsgCS0AAEH/AUYEQCALQZABTwRAIAAgACgCDEEBajYCDCAIQYD+A2ohCEEIIQkMAgsgACAMNgIQIAggC0EJdGohCEEHIQkMAQsgACAMNgIQQQghCSAIIAtBCHRqIQgLIAAgCUEBayIJNgIIIAAgCEEBdCIINgIAIAAgB0EBdCIHNgIEIAdBgIACSQ0ACwwBCyAKKAIEIQoLAkAgCkUNACAAIA4gASgCBCANQRFqdkEEcSABQQRrIg8oAgAgDUETanZBAXEgASgCACIJIA1BEGp2QcAAcSAJIA12QaoBcXIgCSANQQxqQQ4gBBt2QRBxcnJyIgpB0LkBai0AAEECdGoiDDYCaCAAIAcgDCgCACILKAIAIglrIgc2AgQgCkHQuwFqLQAAIQ4CQCAJIAhBEHZLBEAgCygCBCEKIAAgCTYCBCAMIAtBCEEMIAcgCUkiBxtqKAIANgIAIAogCkUgBxshCyAAKAIIIQcDQAJAIAcNACAAKAIQIgdBAWohDCAHLQABIQogBy0AAEH/AUYEQCAKQZABTwRAIAAgACgCDEEBajYCDCAIQYD+A2ohCEEIIQcMAgsgACAMNgIQIAggCkEJdGohCEEHIQcMAQsgACAMNgIQQQghByAIIApBCHRqIQgLIAAgB0EBayIHNgIIIAAgCEEBdCIINgIAIAAgCUEBdCIJNgIEIAlBgIACSQ0ACwwBCyAAIAggCUEQdGsiCjYCACAHQYCAAnFFBEAgCygCBCEIIAwgC0EMQQggByAJSSIJG2ooAgA2AgAgCEUgCCAJGyELIAAoAgghCANAAkAgCA0AIAAoAhAiCEEBaiEMIAgtAAEhCSAILQAAQf8BRgRAIAlBkAFPBEAgACAAKAIMQQFqNgIMIApBgP4DaiEKQQghCAwCCyAAIAw2AhAgCiAJQQl0aiEKQQchCAwBCyAAIAw2AhBBCCEIIAogCUEIdGohCgsgACAIQQFrIgg2AgggACAKQQF0Igo2AgAgACAHQQF0Igc2AgQgB0GAgAJJDQALDAELIAsoAgQhCwsgAkEAIANrIAMgCyAOcyICGzYCACAPIA8oAgBBICANdHI2AgAgASABKAIAIAJBE3RBEHIgDXRyNgIAIAEgASgCBEEIIA10cjYCBCAEIAZyRQRAIAEgBUECdGsiACAAKAIEQYCAAnI2AgQgACAAKAIAIAJBH3RyQYCABHI2AgAgAEEEayIAIAAoAgBBgIAIcjYCAAsgBEEDRw0AIAEgBUECdGoiACAAKAIEQQFyNgIEIAAgACgCACACQRJ0ckECcjYCACAAQQRrIgAgACgCAEEEcjYCAAsgASABKAIAQYCAgAEgDXRyNgIACwutAQAgAEHwnQE2AmQgAEHwnQE2AmAgAEHwnQE2AlwgAEHwnQE2AlggAEHwnQE2AlQgAEHwnQE2AlAgAEHwnQE2AkwgAEHwnQE2AkggAEHwnQE2AkQgAEHwnQE2AkAgAEHwnQE2AjwgAEHwnQE2AjggAEHwnQE2AjQgAEHwnQE2AjAgAEHwnQE2AiwgAEHwnQE2AiggAEHwnQE2AiQgAEHwnQE2AiAgAEHwnQE2AhwLkgYCCX8EfiAAIAE2AgAgAP0MAAAAAAAAAAAAAAAAAAAAAP0LAwggACADNgIcIAAgAkEBayIFNgIYIAFBA3EhCgJ/IAJBAEwEQCABIQQgAwwBCyAAIAFBAWoiBDYCACABLQAACyEBQQghByAAQQg2AhAgACABrSINNwMIIAAgDUL/AYMiDkL/AVEiCTYCFAJAIApBA0YNACAAIAJBAmsiCDYCGAJ/IAJBAkgEQCAEIQEgAwwBCyAAIARBAWoiATYCACAELQAACyEEIABBD0EQIA5C/wFRGyIHNgIQIAAgBK0iDkL/AYMiD0L/AVEiCTYCFCAAIA5CCIYgDYQiDTcDCCAKQQJGBEAgASEEIAUhAiAIIQUMAQsgACACQQNrIgs2AhggAAJ/IAJBA0gEQCABIQYgAwwBCyAAIAFBAWoiBjYCACABLQAAC60iDkL/AYMiEEL/AVEiCTYCFCAAQQdBCCAPQv8BURsgB2oiATYCECAAIA4gB62GIA2EIg03AwggCkEBRgRAIAYhBCABIQcgCCECIAshBQwBCyAAIAJBBGsiBTYCGCAAAn8gAkEESARAIAYhBCADDAELIAAgBkEBaiIENgIAIAYtAAALrSIOQv8Bg0L/AVEiCTYCFCAAQQdBCCAQQv8BURsgAWoiBzYCECAAIA4gAa2GIA2EIg03AwggCyECCwJAIAJBBU4EQCAEKAIAIQMgACACQQVrNgIYIAAgBEEEajYCAAwBC0EAIQFBf0EAIAMbIQMgAkECSA0AA0AgACAEQQFqIgI2AgAgBC0AACEEIAAgBUEBayIGNgIYIANB/wEgAXRBf3NxIAQgAXRyIQMgAUEIaiEBIAVBAUshDCACIQQgBiEFIAwNAAsLIAAgA0EYdiIBQf8BRjYCFCAAQQdBCCAJGyICQQdBCCADQf8BcSIEQf8BRhtqIgVBB0EIIANBCHZB/wFxIgZB/wFGG2oiCEEHQQggA0EQdkH/AXEiA0H/AUYbIAdqajYCECAAIAYgAnQgAyAFdHIgASAIdHIgBHKtIAethiANhDcDCAu2BQISfwJ+An8gACgCHCABQZgBbGoiAkGQAWsoAgAgAkGYAWsoAgBrIgMhBSACQYwBaygCACACQZQBaygCAGsiAiEGQcAAIAMgA0HAAE8bIQNBwAAgAiACQcAATxshBAJAIAVFDQAgBkUNACADRQ0AIARFDQBBfyAEbkECdiADSQ0AQQFBHBATIgIgBDYCDCACIAM2AgggAiAGNgIEIAIgBTYCACACIAStIhQgBq18QgF9IBSAIhSnIgQ2AhQgAiADrSIVIAWtfEIBfSAVgCIVpyIDNgIQAkAgFEL/////D4MgFUL/////D4N+QiCIpw0AIAJBBCADIARsEBMiAzYCGCADRQ0AIAIMAgsgAhAQC0EACyIJRQRAQQAPCwJAIAEEQANAIA5BmAFsIg8gACgCHGoiBSgCGCICBEAgBUEcaiEQIAUoAhQhAyAFKAIQIQRBACEKA0AgAyAEbARAIBAgCkEkbGohBkEAIQsDQCAGKAIUIAtBKGxqIggoAhQiAiAIKAIQIgdsBEBBACEEA0AgCCgCGCAEQQZ0aiIDKAI8IhEEQCADKAIMIQcgAygCFCESIAMoAhAhDCADKAIIIhMgBigCAGshAyAGKAIQIg1BAXEEQCAAKAIcIA9qIgJBkAFrKAIAIANqIAJBmAFrKAIAayEDCyAHIAYoAgRrIQIgDUECcQRAIAIgACgCHCAPaiINQYwBaygCAGogDUGUAWsoAgBrIQILIAkgAyACIAMgDCATayIMaiASIAdrIAJqIBFBASAMQQAQJkUNCSAIKAIQIQcgCCgCFCECCyAEQQFqIgQgAiAHbEkNAAsgBSgCECEEIAUoAhQhAwsgC0EBaiILIAMgBGxJDQALIAUoAhghAgsgCkEBaiIKIAJJDQALCyAOQQFqIg4gAUcNAAsLIAkPCyAJECNBAAvQDAIQfwZ7IAAoAggiCyAAKAIEaiEHAkAgACgCDEUEQCAHQQJIDQEgASgCACABIAtBAnRqIg0oAgAiBEEBakEBdWshAyAAKAIAIQYCQCAHQQRJBEAgBCECDAELIAdBBGsiAEEBdiIJQQFqIQwCQCAAQRZJBEBBASEADAELIAYgASALQQJ0aiIFIAlBAnQiAmpBCGpJIAYgCUEDdGpBCGoiACAFQQRqS3EEQEEBIQAMAQsgBiABIAJqQQhqSSABQQRqIABJcQRAQQEhAAwBCyAMQfz///8HcSIFQQFyIQAgBUEBdCEIIAT9ESESIAP9ESET/QwAAAAAAgAAAAQAAAAGAAAAIRZBACECA0AgASACQQJ0QQRyIgNq/QACACEVIAMgDWr9AAIAIRQgBiACQQN0aiIDIBP9WgIAAyADQQhqIBUgFCASIBT9DQwNDg8QERITFBUWFxgZGhsiFf2uAf0MAgAAAAIAAAACAAAAAgAAAP2uAUEC/awB/bEBIhL9WgIAACADQRBqIBL9WgIAASADQRhqIBL9WgIAAiAGIBb9DAEAAAABAAAAAQAAAAEAAAD9UCIX/RsAQQJ0aiASIBMgEv0NDA0ODxAREhMUFRYXGBkaG/2uAUEB/awBIBX9rgEiE/1aAgAAIAYgF/0bAUECdGogE/1aAgABIAYgF/0bAkECdGogE/1aAgACIAYgF/0bA0ECdGogE/1aAgADIBb9DAgAAAAIAAAACAAAAAgAAAD9rgEhFiASIRMgFCESIAJBBGoiAiAFRw0ACyAS/RsDIQIgE/0bAyEDIAUgDEYNASACIQQLA0AgASAAQQJ0IgJqKAIAIQkgAiANaigCACECIAYgCEECdGoiBSADNgIAIAUgAyAJIAIgBGpBAmpBAnVrIgNqQQF1IARqNgIEIAhBAmohCCAAIAxHIRAgAiEEIABBAWohACAQDQALCyAGIAhBAnRqIAM2AgBBfCEAIAdBAXEEfyAGIAdBAWsiAEECdGogASAAQQF0aigCACACQQFqQQF1ayIANgIAIAAgA2pBAXUhA0F4BUF8CyAGIAdBAnQiAGpqIAIgA2o2AgAgASAGIAAQEhoPCwJAAkACQCAHQQFrDgIAAQILIAEgASgCAEECbTYCAA8LIAAoAgAiBCABKAIAIAEgC0ECdGoiAygCAEEBakEBdWsiADYCBCAEIAAgAygCAGo2AgAgASAEKQIANwIADwsgB0EDSA0AIAAoAgAiCiABKAIAIAEgC0ECdGoiDigCBCIEIA4oAgAiAGpBAmpBAnVrIgMgAGo2AgBBASEIAkAgB0ECayIGIAdBAXEiDEUiAGtBAkkEQCAEIQIMAQsgByAAa0EEayIAQQF2IgJBAWohDwJAAkAgAEEWSQ0AIApBBGoiBSABIAJBAnQiAGpBCGpJIAogAkEDdGpBDGoiAiABQQRqS3ENACAFIAAgASALQQJ0aiIAakEMakkgAEEIaiACSXENACAPQXxxIgVBAXIhACAFQQF0QQFyIQggBP0RIRMgA/0RIRJBACECA0AgCiACQQN0aiIEIAEgAkECdCIDav0AAgQgEyADIA5q/QACCCIT/Q0MDQ4PEBESExQVFhcYGRobIhUgE/2uAf0MAgAAAAIAAAACAAAAAgAAAP2uAUEC/awB/bEBIhQgFCASIBT9DQwNDg8QERITFBUWFxgZGhv9rgFBAf2sASAV/a4BIhX9DQQFBgcYGRobCAkKCxwdHh/9CwIUIAQgEiAV/Q0MDQ4PEBESEwABAgMUFRYXIBT9DQABAgMEBQYHEBESEwwNDg/9CwIEIBQhEiACQQRqIgIgBUcNAAsgE/0bAyECIBL9GwMhAyAFIA9GDQIgAiEEDAELQQEhAAsDQCABIABBAnRqKAIAIQ0gDiAAQQFqIgVBAnRqKAIAIQIgCiAIQQJ0aiIJIAM2AgAgCSADIA0gAiAEakECakECdWsiA2pBAXUgBGo2AgQgCEECaiEIIAAgD0chESACIQQgBSEAIBENAAsLIAogCEECdGogAzYCAAJAIAxFBEAgCiAGQQJ0aiABIAdBAXRqQQRrKAIAIAJBAWpBAXVrIgAgA2pBAXUgAmo2AgAMAQsgAiADaiEACyAKIAdBAnQiA2pBBGsgADYCACABIAogAxASGgsLoAcDA30DewJ/IANBCE8EQCADQQN2IQsDQCAB/QAEACEHIAAgAP0ABAAiCCAC/QAEACIJ/Qy8dLM/vHSzP7x0sz+8dLM//eYB/eQB/QsEACABIAggB/0MzzGwPs8xsD7PMbA+zzGwPv3mAf3lASAJ/Qzh0TY/4dE2P+HRNj/h0TY//eYB/eUB/QsEACACIAggB/0M5dDiP+XQ4j/l0OI/5dDiP/3mAf3kAf0LBAAgAf0ABBAhByAAIAD9AAQQIgggAv0ABBAiCf0MvHSzP7x0sz+8dLM/vHSzP/3mAf3kAf0LBBAgASAIIAf9DM8xsD7PMbA+zzGwPs8xsD795gH95QEgCf0M4dE2P+HRNj/h0TY/4dE2P/3mAf3lAf0LBBAgAiAIIAf9DOXQ4j/l0OI/5dDiP+XQ4j/95gH95AH9CwQQIAJBIGohAiABQSBqIQEgAEEgaiEAIApBAWoiCiALRw0ACwsCQCADQQdxIgNFDQAgASoCACEEIAAgAioCACIGQ7x0sz+UIAAqAgAiBZI4AgAgASAFIARDzzGwvpSSIAZD4dE2v5SSOAIAIAIgBSAEQ+XQ4j+UkjgCACADQQFGDQAgASoCBCEEIAAgAioCBCIGQ7x0sz+UIAAqAgQiBZI4AgQgASAFIARDzzGwvpSSIAZD4dE2v5SSOAIEIAIgBSAEQ+XQ4j+UkjgCBCADQQJGDQAgASoCCCEEIAAgAioCCCIGQ7x0sz+UIAAqAggiBZI4AgggASAFIARDzzGwvpSSIAZD4dE2v5SSOAIIIAIgBSAEQ+XQ4j+UkjgCCCADQQNGDQAgASoCDCEEIAAgAioCDCIGQ7x0sz+UIAAqAgwiBZI4AgwgASAFIARDzzGwvpSSIAZD4dE2v5SSOAIMIAIgBSAEQ+XQ4j+UkjgCDCADQQRGDQAgASoCECEEIAAgAioCECIGQ7x0sz+UIAAqAhAiBZI4AhAgASAFIARDzzGwvpSSIAZD4dE2v5SSOAIQIAIgBSAEQ+XQ4j+UkjgCECADQQVGDQAgASoCFCEEIAAgAioCFCIGQ7x0sz+UIAAqAhQiBZI4AhQgASAFIARDzzGwvpSSIAZD4dE2v5SSOAIUIAIgBSAEQ+XQ4j+UkjgCFCADQQZGDQAgASoCGCEEIAAgAioCGCIGQ7x0sz+UIAAqAhgiBZI4AhggASAFIARDzzGwvpSSIAZD4dE2v5SSOAIYIAIgBSAEQ+XQ4j+UkjgCGAsL4AECBn8DewJAIANFDQAgA0EETwRAIANBfHEhBgNAIAAgBEECdCIFaiIHIAf9AAIAIAIgBWoiB/0AAgAiCyABIAVqIgX9AAIAIgz9rgFBAv2sAf2xASIKIAv9rgH9CwIAIAUgCv0LAgAgByAKIAz9rgH9CwIAIARBBGoiBCAGRw0ACyADIAZGDQELA0AgACAGQQJ0IgRqIgUgBSgCACACIARqIgUoAgAiByABIARqIggoAgAiCWpBAnVrIgQgB2o2AgAgCCAENgIAIAUgBCAJajYCACAGQQFqIgYgA0cNAAsLC90BAQR/IwBBgAFrIgYkACAGIQUCQCABKAIMIAJBBHRqIgIoAgAiBEUEQCACIQEMAQsDQCAFIAI2AgAgBUEEaiEFIAQiASICKAIAIgQNAAsLQQAhBANAIAEoAggiAiAESARAIAEgBDYCCCAEIQILAkAgAiADTg0AA0AgAiABKAIETg0BAkAgAEEBEB8EQCABIAI2AgQMAQsgAkEBaiECCyACIANIDQALCyABIAI2AgggBSAGRwRAIAVBBGsiBSgCACEBIAIhBAwBCwsgASgCBCEHIAZBgAFqJAAgByADSAv9BgELfyMAQYACayIKJAACQCAARQRAQQAhAAwBCwJAIAEgACgCAEYEQCAAKAIEIAJGDQELIAAgAjYCBCAAIAE2AgAgCiACNgIAIAogATYCgAEgAiEEIAEhBQNAIAogByIMQQFqIgdBAnQiCGogBEEBakECbSIJNgIAIApBgAFqIAhqIAVBAWpBAm0iCDYCACAGIAQgBWwiC2ohBiAJIQQgCCEFIAtBAUsNAAsgACAGNgIIAkACQAJAAkAgBkUEQCAAKAIMIgRFDQIgAEEMaiEFDAELIAZBBHQiBCAAKAIQTQ0DIAAoAgwgBBAXIgENAiADQQFBmjFBABAPIABBDGoiBSgCACIERQ0BCyAEEBAgBUEANgIACyAAEBBBACEADAMLIAAgATYCDCABIAAoAhAiAmpBACAEIAJrEBUaIAAgBDYCECAAKAIEIQIgACgCACEBCyAAKAIMIQUgDARAQQAhAyAFIAEgAmxBBHRqIgQhBgNAAkAgCiADQQJ0IgFqKAIAIghBAEwNACAIQQFrIQtBACEJAkACQCAKQYABaiABaigCACICQQBMBEAgCEEBcSENQQAhByAIQQFHDQEgBiEBDAILA0AgBiEBIAIhBgNAAkAgBSAENgIAIAZBAUYEQCAFQRBqIQUgBEEQaiEEDAELIAUgBDYCECAEQRBqIQQgBUEgaiEFIAZBAkohDiAGQQJrIQYgDg0BCwsgBCABIAJBBHRqIAkgCSALRnJBAXEiBxshBiAEIAEgBxshBCAJQQFqIgkgCEcNAAsMAgsgCEH+////B3EhCANAIAcgC0YhASAHQQJqIQcgBCAGIAEbIgQhBiAEIQEgCUECaiIJIAhHDQALCyANRQRAIAQhBgwBCyAEIAEgAkEEdGogByAHIAtGckEBcSICGyEGIAQgASACGyEECyADQQFqIgMgDEcNAAsLIAVBADYCAAsgACgCCCIBRQ0AIAAoAgwhBCABQQRPBEAgAUF8cSECQQAhBQNAIARBADYCPCAEQucHNwI0IARBADYCLCAEQucHNwIkIARBADYCHCAEQucHNwIUIARBADYCDCAEQucHNwIEIARBQGshBCAFQQRqIgUgAkcNAAsLIAFBA3EiAUUNAEEAIQUDQCAEQQA2AgwgBELnBzcCBCAEQRBqIQQgBUEBaiIFIAFHDQALCyAKQYACaiQAIAALsQEBA38CQCAARQ0AIAAoAggiAUUNACAAKAIMIQAgAUEETwRAIAFBfHEhAwNAIABBADYCPCAAQucHNwI0IABBADYCLCAAQucHNwIkIABBADYCHCAAQucHNwIUIABBADYCDCAAQucHNwIEIABBQGshACACQQRqIgIgA0cNAAsLIAFBA3EiAUUNAEEAIQIDQCAAQQA2AgwgAELnBzcCBCAAQRBqIQAgAkEBaiICIAFHDQALCwv7BQEQfyMAQYACayIIJAACf0EBQRQQEyIGRQRAIAJBAUH0MEEAEA9BAAwBCyAGIAE2AgQgBiAANgIAIAggATYCACAIIAA2AoABA0AgCCAFIg1BAWoiBUECdCIHaiABQQFqQQJtIgM2AgAgCEGAAWogB2ogAEEBakECbSIHNgIAIAQgACABbCIJaiEEIAMhASAHIQAgCUEBSw0ACyAGIAQ2AgggBEUEQCAGEBBBAAwBCyAGIARBEBATIgM2AgwgA0UEQCACQQFB2hpBABAPIAYQEEEADAELIAYgBigCCCILQQR0NgIQIAMhACANBEAgAyAGKAIEIAYoAgBsQQR0aiIEIQEDQAJAIAggDkECdCICaigCACIJQQBMDQAgCUEBayEMQQAhBwJAIAhBgAFqIAJqKAIAIgJBAEwEQEEAIQUgCUEBRwRAIAlB/v///wdxIQoDQCAFIAxGIQ8gBUECaiEFIAEgBCAPGyIEIQEgB0ECaiIHIApHDQALCyAJQQFxDQEgBCEBDAILA0AgBCEFIAIhBANAAkAgACABNgIAIARBAUYEQCAAQRBqIQAgAUEQaiEBDAELIAAgATYCECABQRBqIQEgAEEgaiEAIARBAkohECAEQQJrIQQgEA0BCwsgASAFIAJBBHRqIAcgByAMRnJBAXEiChshBCABIAUgChshASAHQQFqIgcgCUcNAAsMAQsgASAEIAJBBHRqIAUgBSAMRnJBAXEiBRshESABIAQgBRshASARIQQLIA5BAWoiDiANRw0ACwsgAEEANgIAAkAgC0UNACALQQRPBEAgC0F8cSEAQQAhAQNAIANBADYCPCADQucHNwI0IANBADYCLCADQucHNwIkIANBADYCHCADQucHNwIUIANBADYCDCADQucHNwIEIANBQGshAyABQQRqIgEgAEcNAAsLIAtBA3EiAEUNAEEAIQEDQCADQQA2AgwgA0LnBzcCBCADQRBqIQMgAUEBaiIBIABHDQALCyAGCyESIAhBgAJqJAAgEgtTAQF/An8gAC0ADEH/AUYEQCAAQoD+g4DwADcCDEEAIAAoAggiASAAKAIETw0BGiAAIAFBAWo2AgggACABLQAAQYD+A3I2AgwLIABBADYCEEEBCwt+AgF/AX4gAL0iA0I0iKdB/w9xIgJB/w9HBHwgAkUEQCABIABEAAAAAAAAAABhBH9BAAUgAEQAAAAAAADwQ6IgARBlIQAgASgCAEFAags2AgAgAA8LIAEgAkH+B2s2AgAgA0L/////////h4B/g0KAgICAgICA8D+EvwUgAAsLSQEBfwJAQQFBLBATIgEEQCABQQA2AhACQCAAQQBMBEAgAUEBQQgQEyIANgIkIABFDQEMAwsgAUEANgIMCyABEBALQQAhAQsgAQuRAgAgAEUEQEEADwsCfwJAIAFB/wBNDQACQEGU0AEoAgAoAgBFBEAgAUGAf3FBgL8DRg0CDAELIAFB/w9NBEAgACABQT9xQYABcjoAASAAIAFBBnZBwAFyOgAAQQIMAwsgAUGAQHFBgMADRyABQYCwA09xRQRAIAAgAUE/cUGAAXI6AAIgACABQQx2QeABcjoAACAAIAFBBnZBP3FBgAFyOgABQQMMAwsgAUGAgARrQf//P00EQCAAIAFBP3FBgAFyOgADIAAgAUESdkHwAXI6AAAgACABQQZ2QT9xQYABcjoAAiAAIAFBDHZBP3FBgAFyOgABQQQMAwsLQZTHAUEZNgIAQX8MAQsgACABOgAAQQELC7wCAAJAAkACQAJAAkACQAJAAkACQAJAAkAgAUEJaw4SAAgJCggJAQIDBAoJCgoICQUGBwsgAiACKAIAIgFBBGo2AgAgACABKAIANgIADwsgAiACKAIAIgFBBGo2AgAgACABMgEANwMADwsgAiACKAIAIgFBBGo2AgAgACABMwEANwMADwsgAiACKAIAIgFBBGo2AgAgACABMAAANwMADwsgAiACKAIAIgFBBGo2AgAgACABMQAANwMADwsgAiACKAIAQQdqQXhxIgFBCGo2AgAgACABKwMAOQMADwsgACACIAMRAwALDwsgAiACKAIAIgFBBGo2AgAgACABNAIANwMADwsgAiACKAIAIgFBBGo2AgAgACABNQIANwMADwsgAiACKAIAQQdqQXhxIgFBCGo2AgAgACABKQMANwMAC3MBBn8gACgCACIDLAAAQTBrIgFBCUsEQEEADwsDQEF/IQQgAkHMmbPmAE0EQEF/IAEgAkEKbCIFaiABIAVB/////wdzSxshBAsgACADQQFqIgU2AgAgAywAASEGIAQhAiAFIQMgBkEwayIBQQpJDQALIAILtBQCFX8BfiMAQUBqIggkACAIIAE2AjwgCEEnaiEWIAhBKGohEQJAAkACQAJAA0BBACEHA0AgASENIAcgDkH/////B3NKDQIgByAOaiEOAkACQAJAAkAgASIHLQAAIgsEQANAAkACQCALQf8BcSIBRQRAIAchAQwBCyABQSVHDQEgByELA0AgCy0AAUElRwRAIAshAQwCCyAHQQFqIQcgCy0AAiEZIAtBAmoiASELIBlBJUYNAAsLIAcgDWsiByAOQf////8HcyIXSg0JIAAEQCAAIA0gBxAZCyAHDQcgCCABNgI8IAFBAWohB0F/IRACQCABLAABQTBrIglBCUsNACABLQACQSRHDQAgAUEDaiEHQQEhEiAJIRALIAggBzYCPEEAIQwCQCAHLAAAIgtBIGsiAUEfSwRAIAchCQwBCyAHIQlBASABdCIBQYnRBHFFDQADQCAIIAdBAWoiCTYCPCABIAxyIQwgBywAASILQSBrIgFBIE8NASAJIQdBASABdCIBQYnRBHENAAsLAkAgC0EqRgRAAn8CQCAJLAABQTBrIgFBCUsNACAJLQACQSRHDQACfyAARQRAIAQgAUECdGpBCjYCAEEADAELIAMgAUEDdGooAgALIQ8gCUEDaiEBQQEMAQsgEg0GIAlBAWohASAARQRAIAggATYCPEEAIRJBACEPDAMLIAIgAigCACIHQQRqNgIAIAcoAgAhD0EACyESIAggATYCPCAPQQBODQFBACAPayEPIAxBgMAAciEMDAELIAhBPGoQaSIPQQBIDQogCCgCPCEBC0EAIQdBfyEKAn9BACABLQAAQS5HDQAaIAEtAAFBKkYEQAJ/AkAgASwAAkEwayIJQQlLDQAgAS0AA0EkRw0AIAFBBGohAQJ/IABFBEAgBCAJQQJ0akEKNgIAQQAMAQsgAyAJQQN0aigCAAsMAQsgEg0GIAFBAmohAUEAIABFDQAaIAIgAigCACIJQQRqNgIAIAkoAgALIQogCCABNgI8IApBAE4MAQsgCCABQQFqNgI8IAhBPGoQaSEKIAgoAjwhAUEBCyETA0AgByEUQRwhCSABIhgsAAAiB0H7AGtBRkkNCyABQQFqIQEgByAUQTpsakG/wAFqLQAAIgdBAWtBCEkNAAsgCCABNgI8AkAgB0EbRwRAIAdFDQwgEEEATgRAIABFBEAgBCAQQQJ0aiAHNgIADAwLIAggAyAQQQN0aikDADcDMAwCCyAARQ0IIAhBMGogByACIAYQaAwBCyAQQQBODQtBACEHIABFDQgLIAAtAABBIHENCyAMQf//e3EiCyAMIAxBgMAAcRshDEEAIRBBsAghFSARIQkCQAJAAn8CQAJAAkACQAJAAkACfwJAAkACQAJAAkACQAJAIBgsAAAiB0FTcSAHIAdBD3FBA0YbIAcgFBsiB0HYAGsOIQQWFhYWFhYWFhAWCQYQEBAWBhYWFhYCBQMWFgoWARYWBAALAkAgB0HBAGsOBxAWCxYQEBAACyAHQdMARg0LDBULIAgpAzAhHEGwCAwFC0EAIQcCQAJAAkACQAJAAkACQCAUQf8BcQ4IAAECAwQcBQYcCyAIKAIwIA42AgAMGwsgCCgCMCAONgIADBoLIAgoAjAgDqw3AwAMGQsgCCgCMCAOOwEADBgLIAgoAjAgDjoAAAwXCyAIKAIwIA42AgAMFgsgCCgCMCAOrDcDAAwVC0EIIAogCkEITRshCiAMQQhyIQxB+AAhBwsgESEBIAgpAzAiHEIAUgRAIAdBIHEhDQNAIAFBAWsiASAcp0EPcUHQxAFqLQAAIA1yOgAAIBxCD1YhGiAcQgSIIRwgGg0ACwsgASENIAgpAzBQDQMgDEEIcUUNAyAHQQR2QbAIaiEVQQIhEAwDCyARIQEgCCkDMCIcQgBSBEADQCABQQFrIgEgHKdBB3FBMHI6AAAgHEIHViEbIBxCA4ghHCAbDQALCyABIQ0gDEEIcUUNAiAKIBEgAWsiAUEBaiABIApIGyEKDAILIAgpAzAiHEIAUwRAIAhCACAcfSIcNwMwQQEhEEGwCAwBCyAMQYAQcQRAQQEhEEGxCAwBC0GyCEGwCCAMQQFxIhAbCyEVIBwgERAqIQ0LIBMgCkEASHENESAMQf//e3EgDCATGyEMAkAgCCkDMCIcQgBSDQAgCg0AIBEhDUEAIQoMDgsgCiAcUCARIA1raiIBIAEgCkgbIQoMDQsgCCkDMCEcDAsLAn9B/////wcgCiAKQf////8HTxsiDCIHQQBHIQkCQAJAAkAgCCgCMCIBQYQMIAEbIg0iAUEDcUUNACAHRQ0AA0AgAS0AAEUNAiAHQQFrIgdBAEchCSABQQFqIgFBA3FFDQEgBw0ACwsgCUUNAQJAIAEtAABFDQAgB0EESQ0AA0BBgIKECCABKAIAIglrIAlyQYCBgoR4cUGAgYKEeEcNAiABQQRqIQEgB0EEayIHQQNLDQALCyAHRQ0BCwNAIAEgAS0AAEUNAhogAUEBaiEBIAdBAWsiBw0ACwtBAAsiASANayAMIAEbIgEgDWohCSAKQQBOBEAgCyEMIAEhCgwMCyALIQwgASEKIAktAAANDwwLCyAIKQMwIhxCAFINAUIAIRwMCQsgCgRAIAgoAjAMAgtBACEHIABBICAPQQAgDBAcDAILIAhBADYCDCAIIBw+AgggCCAIQQhqIgc2AjBBfyEKIAcLIQtBACEHA0ACQCALKAIAIg1FDQAgCEEEaiANEGciDUEASA0PIA0gCiAHa0sNACALQQRqIQsgByANaiIHIApJDQELC0E9IQkgB0EASA0MIABBICAPIAcgDBAcIAdFBEBBACEHDAELQQAhCSAIKAIwIQsDQCALKAIAIg1FDQEgCEEEaiIKIA0QZyINIAlqIgkgB0sNASAAIAogDRAZIAtBBGohCyAHIAlLDQALCyAAQSAgDyAHIAxBgMAAcxAcIA8gByAHIA9IGyEHDAgLIBMgCkEASHENCUE9IQkgACAIKwMwIA8gCiAMIAcgBRETACIHQQBODQcMCgsgBy0AASELIAdBAWohBwwACwALIAANCSASRQ0DQQEhBwNAIAQgB0ECdGooAgAiAARAIAMgB0EDdGogACACIAYQaEEBIQ4gB0EBaiIHQQpHDQEMCwsLQQEhDiAHQQpPDQkDQCAEIAdBAnRqKAIADQEgB0EBaiIHQQpHDQALDAkLQRwhCQwGCyAIIBw8ACdBASEKIBYhDSALIQwLIAogCSANayILIAogC0obIgogEEH/////B3NKDQNBPSEJIA8gCiAQaiIBIAEgD0gbIgcgF0oNBCAAQSAgByABIAwQHCAAIBUgEBAZIABBMCAHIAEgDEGAgARzEBwgAEEwIAogC0EAEBwgACANIAsQGSAAQSAgByABIAxBgMAAcxAcIAgoAjwhAQwBCwsLQQAhDgwDC0E9IQkLQZTHASAJNgIAC0F/IQ4LIAhBQGskACAOC6gCAQR/IwBB0AFrIgUkACAFIAI2AswBIAVBoAFqIgJBAEEoEBUaIAUgBSgCzAE2AsgBAkBBACABIAVByAFqIAVB0ABqIAIgAyAEEGpBAEgNACAAKAJMQQBIIQggACAAKAIAIgdBX3E2AgACfwJAAkAgACgCMEUEQCAAQdAANgIwIABBADYCHCAAQgA3AxAgACgCLCEGIAAgBTYCLAwBCyAAKAIQDQELQX8gABA+DQEaCyAAIAEgBUHIAWogBUHQAGogBUGgAWogAyAEEGoLIQEgBgR/IABBAEEAIAAoAiQRAAAaIABBADYCMCAAIAY2AiwgAEEANgIcIAAoAhQaIABCADcDEEEABSABCxogACAAKAIAIAdBIHFyNgIAIAgNAAsgBUHQAWokAAsnAQF/QRwhAyABQQNxBH9BHAUgACABIAIQJSIANgIAQQBBMCAAGwsL/QMBBX8Cf0HgxAEoAgAiAiAAQQdqQXhxIgFBB2pBeHEiA2ohAAJAIANBACAAIAJNG0UEQCAAPwBBEHRNDQEgABAKDQELQZTHAUEwNgIAQX8MAQtB4MQBIAA2AgAgAgsiAkF/RwRAIAEgAmoiAEEEa0EQNgIAIABBEGsiA0EQNgIAAkACf0GgzwEoAgAiAQR/IAEoAggFQQALIAJGBEAgAiACQQRrKAIAQX5xayIEQQRrKAIAIQUgASAANgIIIAQgBUF+cWsiACAAKAIAakEEay0AAEEBcQRAIAAoAgQiASAAKAIIIgQ2AgggBCABNgIEIAAgAyAAayIBNgIADAMLIAJBEGsMAQsgAkEQNgIAIAIgADYCCCACIAE2AgQgAkEQNgIMQaDPASACNgIAIAJBEGoLIgAgAyAAayIBNgIACyAAIAFBfHFqQQRrIAFBAXI2AgAgAAJ/IAAoAgBBCGsiAUH/AE0EQCABQQN2QQFrDAELIAFBHSABZyIDa3ZBBHMgA0ECdGtB7gBqIAFB/x9NDQAaQT8gAUEeIANrdkECcyADQQF0a0HHAGoiASABQT9PGwsiAUEEdCIDQaDHAWo2AgQgACADQajHAWoiAygCADYCCCADIAA2AgAgACgCCCAANgIEQajPAUGozwEpAwBCASABrYaENwMACyACQX9HC70BAQJ/AkAgACgCTCIBQQBOBEAgAUUNAUHMzwEoAgAgAUH/////A3FHDQELAkAgACgCUEEKRg0AIAAoAhQiASAAKAIQRg0AIAAgAUEBajYCFCABQQo6AAAPCyAAEG8PCyAAQcwAaiIBIAEoAgAiAkH/////AyACGzYCAAJAAkAgACgCUEEKRg0AIAAoAhQiAiAAKAIQRg0AIAAgAkEBajYCFCACQQo6AAAMAQsgABBvCyABKAIAGiABQQA2AgALfAECfyMAQRBrIgEkACABQQo6AA8CQAJAIAAoAhAiAgR/IAIFIAAQPg0CIAAoAhALIAAoAhQiAkYNACAAKAJQQQpGDQAgACACQQFqNgIUIAJBCjoAAAwBCyAAIAFBD2pBASAAKAIkEQAAQQFHDQAgAS0ADxoLIAFBEGokAAuwAgECfyAABEAgACgCABA4IABBADYCACAAKAJIIgEEQCABEBAgAEEANgJICyAAKAJEIgEEQCABEBAgAEEANgJECyAAKAJsIgEEQCABEBAgAEEANgJsCyAAKAJ0IgEEQCABKAIAIgIEQCACEBAgACgCdCIBQQA2AgALIAEQECAAQQA2AnQLIAAoAngiAQRAIAEoAgwiAgRAIAIQECAAKAJ4IgFBADYCDAsgASgCBCICBEAgAhAQIAAoAngiAUEANgIECyABKAIIIgIEQCACEBAgACgCeCIBQQA2AggLIAEoAgAiAgRAIAIQECAAKAJ4IgFBADYCAAsgARAQIABBADYCeAsgACgCBCIBBEAgARAyIABBADYCBAsgACgCCCIBBEAgARAyIABBADYCCAsgABAQCwuLGwIefwV7IwBB8AFrIgkkAEEBIQ4CQCAAKAIAKAI8DQAgACgCgAENAAJAAkAgACgCdCIIRQRAIAAoAnghBAwBCyABKAIQIQMgCC8BBCEGAkAgACgCeCIERQ0AIAQoAgxFDQAgBC0AEiEDCwJAIAYEQCAIKAIAIQgDQCAIIAVBBmxqIgovAQAiByADTwRAIAkgAzYCtAEgCSAHNgKwASACQQFBoOYAIAlBsAFqEA9BACEODAYLAkAgCi8BBCIKRQ0AIApB//8DRg0AIApBAWsiCiADSQ0AIAkgAzYCpAEgCSAKNgKgASACQQFBoOYAIAlBoAFqEA9BACEODAYLIAVBAWoiBSAGRw0ACwwBCyADDQIMAQsDQCADQQFrIQNBACEFA0AgCCAFQQZsai8BACADRwRAIAVBAWoiBSAGRw0BDAQLCyADDQALCwJAIARFDQAgBCgCDCIKRQ0AAkACQCAELQASIggEQEEAIQVBASEHA0AgASgCECIDIAogBUECdGovAQAiBE0EQCAJIAM2ApQBIAkgBDYCkAEgAkEBQaDmACAJQZABahAPQQAhBwsgBUEBaiIFIAhHDQALIAhBBBATIgNFDQFBACEFA0ACQCAKIAVBAnRqIgQtAAIiBkECTwRAIAkgBjYCRCAJIAU2AkAgAkEBQcvZACAJQUBrEA9BACEHDAELIAggBC0AAyIETQRAIAkgBDYCgAEgAkEBQZPZACAJQYABahAPQQAhBwwBCyADIARBAnRqIQsCQCAGQQFHIgwNACALKAIARQ0AIAkgBDYCUCACQQFBvNUAIAlB0ABqEA9BACEHDAELAkAgBg0AIARFDQAgCSAENgJkIAkgBTYCYCACQQFBitgAIAlB4ABqEA9BACEHDAELAkAgDA0AIAQgBUYNACAJIAQ2AnggCSAFNgJ0IAkgBTYCcCACQQFBrtgAIAlB8ABqEA9BACEHDAELIAtBATYCAAsgBUEBaiIFIAhHDQALQQAhBQNAAkACQCADIAVBAnQiBGooAgBFBEAgBCAKai0AAg0BCyAFQQFqIgUgCEcNAiAHRQ0BIAEoAhBBAUcNBUEAIQUDQCADIAVBAnRqKAIABEAgCCAFQQFqIgVHDQEMBwsLQQAhByACQQJB7sUAQQAQDyAIQRBPBEAgCEHwAXEhB0EAIQQDQCAKIARBAnRqIgZBAToAAiAGIAQ6AAMgBkEBOgA+IAZBAToAOiAGQQE6ADYgBkEBOgAyIAZBAToALiAGQQE6ACogBkEBOgAmIAZBAToAIiAGQQE6AB4gBkEBOgAaIAZBAToAFiAGQQE6ABIgBkEBOgAOIAZBAToACiAGQQE6AAYgBiAEQQFyOgAHIAYgBEEPcjoAPyAGIARBDnI6ADsgBiAEQQ1yOgA3IAYgBEEMcjoAMyAGIARBC3I6AC8gBiAEQQpyOgArIAYgBEEJcjoAJyAGIARBCHI6ACMgBiAEQQdyOgAfIAYgBEEGcjoAGyAGIARBBXI6ABcgBiAEQQRyOgATIAYgBEEDcjoADyAGIARBAnI6AAsgBEEQaiIEIAdHDQALIAcgCEYNBgsDQCAKIAdBAnRqIgQgBzoAAyAEQQE6AAIgB0EBaiIHIAhHDQALDAULIAkgBTYCMCACQQFByNIAIAlBMGoQD0EAIQcgBUEBaiIFIAhHDQELCyADEBBBACEODAULIAhBBBATIgMNAQtBACEOIAJBAUGK2wBBABAPDAMLIAMQEAsCQCAAKAJ4IgNFDQAgAygCDCIPRQRAIAMoAgQQECAAKAJ4KAIIEBAgACgCeCgCABAQIAAoAngiAygCDCIEBH8gBBAQIAAoAngFIAMLEBAgAEEANgJ4DAELIAEoAhghDQJAAkAgAy0AEiIKBEAgAygCACEUIAMoAgQhBiADKAIIIQhBACEFAkADQCANIA8gBUECdGovAQBBNGxqKAIsBEAgCiAFQQFqIgVHDQEMAgsLIAkgBTYCICACQQFBwucAIAlBIGoQD0EAIQ4MBgsgCkE0bBAUIgtFDQFBACEFA0AgDyAFQQJ0aiIDLwEAIQcgCyADLQACBH8gAy0AAwUgBQtBNGxqIgQgDSAHQTRsaiID/QACAP0LAgAgBCADKAIwNgIwIAQgA/0AAiD9CwIgIAQgA/0AAhD9CwIQIAsgBUE0bGoiBCADKAIIIAMoAgxsQQJ0EBgiAzYCLCADRQRAIAUEQCAFQf//A3EhAANAIABBNGwgC2pBCGsoAgAQECAAQQFrIgANAAsLIAsQEEEAIQ4gAkEBQY7nAEEAEA8MBwsgBCAFIAhqLQAANgIYIAQgBSAGai0AADYCICAFQQFqIgUgCkcNAAsgACgCeC8BECIQQQFrIRIDQCALIBNBNGxqIgMoAgwgAygCCGwhBiANIA8gE0ECdGoiBC8BAEE0bGooAiwhCAJAIAQtAAJFBEAgBkUNASADKAIsIQVBACEHQQAhBAJAIAZBBEkNACAFIAhrQRBJDQAgBkF8cSEEQQAhAwNAIAUgA0ECdCIMaiAIIAxq/QACAP0LAgAgA0EEaiIDIARHDQALIAQgBkYNAgsgBCEDIAZBA3EiDARAA0AgBSADQQJ0IhFqIAggEWooAgA2AgAgA0EBaiEDIAdBAWoiByAMRw0ACwsgBCAGa0F8Sw0BA0AgBSADQQJ0IgRqIAQgCGooAgA2AgAgBSAEQQRqIgdqIAcgCGooAgA2AgAgBSAEQQhqIgdqIAcgCGooAgA2AgAgBSAEQQxqIgRqIAQgCGooAgA2AgAgA0EEaiIDIAZHDQALDAELIAZFDQAgFCAELQADIgNBAnRqIQQgCyADQTRsaigCLCEFQQAhAyAGQQFHBEAgBkF+cSEVQQAhDANAIAUgA0ECdCIHaiAEIAcgCGooAgAiESASIBAgEUobQQAgEUEAThsgCmxBAnRqKAIANgIAIAUgB0EEciIHaiAEIAcgCGooAgAiByASIAcgEEgbQQAgB0EAThsgCmxBAnRqKAIANgIAIANBAmohAyAMQQJqIgwgFUcNAAsLIAZBAXFFDQAgBSADQQJ0IgNqIAQgAyAIaigCACIDIBIgAyAQSBtBACADQQBOGyAKbEECdGooAgA2AgALIBNBAWoiEyAKRw0ACwwCCyAKQTRsEBQiCw0BC0EAIQ4gAkEBQY7nAEEAEA8MAwsgASgCECIDBEBBACEFA0AgDSAFQTRsaigCLCIEBEAgBBAQCyAFQQFqIgUgA0cNAAsLIA0QECABIAo2AhAgASALNgIYCyAAKAJ0IgVFDQEgBSgCACEHIAUvAQQiCwRAIAdBKmohEiAHQSRqIRMgB0EeaiERIAdBGGohFCAHQRJqIRUgB0EMaiEWIAdBBmohFyALQQJrIRhBACEFQQEhBANAAkAgASgCECIDIAcgBUEGbGoiDS8BACIGTQRAIAkgAzYCFCAJIAY2AhAgAkECQcw3IAlBEGoQDwwBCyANLwEEIghBAWpB//8DcUEBTQRAIAEoAhggBkE0bGogDS8BAjsBMAwBCyAIQQFrIgpB//8DcSIPIANPBEAgCSADNgIEIAkgDzYCACACQQJBozcgCRAPDAELAkAgBiAPRg0AIA0vAQINACAJIAEoAhgiCCAGQTRsaiIDKAIwNgLoASAJIAP9AAIg/QsD2AEgCSAD/QACEP0LA8gBIAkgA/0AAgD9CwO4ASADIAggD0E0bCIMaiIIKQIINwIIIAMgCCkCEDcCECADIAgpAhg3AhggAyAIKQIgNwIgIAMgCCkCKDcCKCADIAgoAjA2AjAgAyAIKQIANwIAIAEoAhggDGoiAyAJ/QADuAH9CwIAIAMgCf0AA9gB/QsCICADIAn9AAPIAf0LAhAgAyAJKALoATYCMCAFQQFqIAtPDQAgBCEIIBggBWtB//8DcSIDQQdPBEAgBCADQQFqIhlB+P8HcSIQaiEIIAr9ECEkIAb9ECEjQQAhDANAICMgJCASIAQgDGpBBmwiA2oiGiADIBNqIhsgAyARaiIcIAMgFGoiHSADIBVqIh4gAyAWaiIfIAMgF2oiICADIAdqIgP9CAEA/VUBAAH9VQEAAv1VAQAD/VUBAAT9VQEABf1VAQAG/VUBAAciISAj/S4gISAk/S0iJf1O/VIhIiAhICP9LSAl/VAiIf0ZAEEBcQRAIAMgIv1ZAQAACyAh/RkBQQFxBEAgICAi/VkBAAELICH9GQJBAXEEQCAfICL9WQEAAgsgIf0ZA0EBcQRAIB4gIv1ZAQADCyAh/RkEQQFxBEAgHSAi/VkBAAQLICH9GQVBAXEEQCAcICL9WQEABQsgIf0ZBkEBcQRAIBsgIv1ZAQAGCyAh/RkHQQFxBEAgGiAi/VkBAAcLIAxBCGoiDCAQRw0ACyAQIBlGDQELA0AgCiEDAkAgBiAHIAhBBmxqIgwvAQAiEEcEQCAGIQMgDyAQRw0BCyAMIAM7AQALIAsgCEEBaiIIQf//A3FHDQALCyABKAIYIAZBNGxqIA0vAQI7ATALIARBAWohBCAFQQFqIgUgC0cNAAsgACgCdCIFKAIAIQcLIAcEfyAHEBAgACgCdAUgBQsQECAAQQA2AnQMAQtBACEOIAJBAUGhxgBBABAPCyAJQfABaiQAIA4L6QEBBn8jAEEgayIEJAACfwJAIAAoAjwiAwRAQQEhBQNAIAAoAkwoAhggACgCQCACQQJ0aigCACIGQTRsaigCLEUEQCAEIAY2AhAgAUECQdo5IARBEGoQD0EAIQUgACgCPCEDCyACQQFqIgIgA0kNAAsMAQtBASEFQQEgACgCTCIDKAIQRQ0BGgNAIAMoAhggAkE0bGooAixFBEAgBCACNgIAIAFBAkHaOSAEEA9BACEFIAAoAkwhAwsgAkEBaiICIAMoAhBJDQALC0EBIAUNABogAUEBQb8VQQAQD0EACyEHIARBIGokACAHCwQAQX8LhgcCFn8CfiAAKAIYIhAoAhBFBEBBAQ8LIBAoAhghDSAAKAIUKAIAKAIUIQsDQCABIA0oAiQiAjYCJCALKAIcIgYgAkGYAWxqIQMCQAJAAn8gACgCQCIRBEAgBiALKAIYQZgBbGoiAkGQAWsoAgAgAkGYAWsoAgBrIQwgA0EMaiEGIANBBGohBCADKAIIIQIgAygCACEFQSQMAQsgA0GUAWohBiADQYwBaiEEIAMoApABIgIgAygCiAEiBWshDEE0CyALaigCACISRQ0AIAQoAgAhByAGKAIAIQkgAiAFayEGIAEoAggiA0J/IAE1AigiGIZCf4UiGSABNQIQfCAYiKciCGohBAJ/IAUgCEsEQCAFIAhrIQ5BACEIQQAgAiAETQ0BGiAGIAQgBWsiBmsMAQsgCCAFayEIIAIgBE0EQCAGIAhrIQZBACEOQQAMAQtBACEOIAMhBiACIARrCyEVIAkgB2shAiABKAIMIgQgGSABNQIUfCAYiKciCmohBQJ/IAcgCksEQCAHIAprIQ9BACEKQQAgBSAJTw0BGiACIAUgB2siAmsMAQsgCiAHayEKIAUgCU8EQCACIAprIQJBACEPQQAMAQtBACEPIAQhAiAJIAVrCyEHQQAhBSAIQQBIDQEgCkEASA0BIBVBAEgNASAHQQBIDQEgBkEASA0BIAJBAEgNASADIA9sIA5qIQcgCiAMbCAIaiEJAkACQAJAIAEoAiwiCA0AIAkNACAHDQAgAyAMRw0AIAMgBkcNACACIARHDQEgASALQSRBNCARG2oiAigCADYCLCACQQA2AgAMAwsgCA0BCyAERQ0CIAStIAOtfkIgiKcNAiADIARsIgNB/////wNLDQIgASADQQJ0EBgiAzYCLCADRQ0CIAYgASgCCCIERiABKAIMIgUgAkZxDQAgA0EAIAQgBWxBAnQQFRoLIAJFDQAgAkEBcSEXIAZBAnQhBiABKAIsIAdBAnRqIQQgEiAJQQJ0aiEFIAJBAUcEQCACQf7///8HcSEHQQAhAgNAIAQgBSAGEBIhFiAFIAxBAnQiCWoiCCAJaiEFIBYgASgCCEECdGogCCAGEBIgASgCCEECdGohBCACQQJqIgIgB0cNAAsLIBdFDQAgBCAFIAYQEhoLIAtBzABqIQsgDUE0aiENIAFBNGohAUEBIQUgFEEBaiIUIBAoAhBJDQELCyAFC9USAgl/DH4jAEGgAWsiBSQAAkAgAkEjTQRAQQAhAiADQQFBti5BABAPDAELIAJBJGsiAiACQQNuIglBA2xHBEBBACECIANBAUG2LkEAEA8MAQsgACgCSCEGIAEgBUGcAWoiAkECEBEgACAFKAKcATsBUCABQQJqIAZBCGpBBBARIAFBBmogBkEMakEEEBEgAUEKaiAGQQQQESABQQ5qIAZBBGpBBBARIAFBEmogAEHcAGpBBBARIAFBFmogAEHgAGpBBBARIAFBGmogAEHUAGpBBBARIAFBHmogAEHYAGpBBBARIAFBImogAkECEBECQAJAAkAgBSgCnAEiAkGAgAFNBEAgBiACNgIQIAIgCUcEQCAFIAk2AoQBIAUgAjYCgAEgA0EBQZHwACAFQYABahAPQQAhAgwFCyAGKAIEIgIgBigCDCIISSAGKAIIIgsgBigCACIES3FFBEAgBSAIrSACrX03A3ggBSALrSAErX03A3AgA0EBQdvsACAFQfAAahAPQQAhAgwFCyAAKAJcIgdBACAAKAJgIgobRQRAIAUgCjYCBCAFIAc2AgAgA0EBQYPxACAFEA9BACECDAULAkACQCAAKAJUIgwgBEsNAEF/IAcgDGoiByAHIAxJGyAETQ0AIAAoAlgiByACSw0AQX8gByAKaiIKIAcgCksbIAJLDQELQQAhAiADQQFB1hRBABAPDAULAkAgACgC4AENACAAKALYASIHRQ0AIAAoAtwBIgpFDQAgCyAEayIEIAdGIAggAmsiAiAKRnENACAFIAI2AmwgBSAENgJoIAUgCjYCZCAFIAc2AmAgA0EBQcPoACAFQeAAahAPQQAhAgwFCyAGIAlBNBATIgQ2AhggBEUNAQJAIAYoAhBFDQAgAUEkaiAFQZgBaiICQQEQESAEIAUoApgBIglBB3YiCjYCICAEIAlB/wBxQQFqIgw2AhggACgC4AEhCyABQSVqIAJBARARIAQgBSgCmAE2AgAgAUEmaiACQQEQESAEIAUoApgBIgg2AgRBACECIAQoAgAiB0GAAmtBgX5JBEBBACEJDAULQQAhCSAIQYACa0GBfkkNBCAEKAIYIghBH0sNAyAEQQA2AiQgBCAAKAKgATYCKEEBIQkgBigCEEEBTQ0AQQAgCiALGyEKQQAgDCALGyELIAFBJ2ohAQNAIAEgBUGYAWpBARARIAQgBSgCmAEiB0EHdiIINgJUIAQgB0H/AHFBAWoiBzYCTAJAIAAoAuABDQAgAC0AvAFBBHENACAHIAtGIAggCkZxDQAgBSAINgJUIAUgBzYCUCAFIAk2AkwgBSAKNgJIIAUgCzYCRCAFIAk2AkAgA0ECQcfuACAFQUBrEA8LIAFBAWogBUGYAWoiCEEBEBEgBCAFKAKYATYCNCABQQJqIAhBARARIAQgBSgCmAEiCDYCOCAEKAI0IgdBgAJrQYF+SQ0FIAhBgAJrQYB+TQ0FIAQoAkwiCEEgTw0EIAFBA2ohASAEQQA2AlggBCAAKAKgATYCXCAEQTRqIQQgCUEBaiIJIAYoAhBJDQALC0EAIQIgACgCXCIIRQ0EIAAoAmAiC0UNBCAAIAitIg1CAX0iDyAGKAIIIAAoAlQiB2utfCANgKciATYCaCAAIAutIg5CAX0iECAGKAIMIAAoAlgiCmutfCAOgKciBDYCbAJAAkAgAUUNACAERQ0AQf//AyAEbiABTw0BCyAFIAQ2AhQgBSABNgIQIANBAUG16QAgBUEQahAPDAULIAEgBGwhCQJAIAAtAERBAnEEQCAAIAAoAhwgB2sgCG42AhwgACAAKAIgIAprIAtuNgIgIAAgDyAAKAIkIAdrrXwgDYA+AiQgACAQIAAoAiggCmutfCAOgD4CKAwBCyAAIAQ2AiggACABNgIkIABCADcCHAsgACAJQYwsEBMiATYCnAEgAUUEQCADQQFBzR1BABAPDAULIAYoAhBBuAgQEyEBIAAoAgwgATYC0CsgACgCDCgC0CtFBEAgA0EBQc0dQQAQDwwFC0EKQRQQEyEBIAAoAgwgATYC8CsgACgCDCIBKALwK0UEQCADQQFBzR1BABAPDAULIAFBCjYC+CtBCkEUEBMhASAAKAIMIAE2AvwrIAAoAgwiASgC/CtFBEAgA0EBQc0dQQAQDwwFCyABQQo2AoQsAkAgBigCECIERQ0AIAYoAhghCEEAIQEgBEEBRwRAIARBfnEhCwNAIAggAUE0bGoiBygCIEUEQCAAKAIMKALQKyABQbgIbGpBASAHKAIYQQFrdDYCtAgLIAggAUEBciIHQTRsaiIKKAIgRQRAIAAoAgwoAtArIAdBuAhsakEBIAooAhhBAWt0NgK0CAsgAUECaiEBIAJBAmoiAiALRw0ACwsgBEEBcUUNACAIIAFBNGxqIgIoAiANACAAKAIMKALQKyABQbgIbGpBASACKAIYQQFrdDYCtAgLIAkEQCAAKAKcASEBQQAhAgNAIAEgBigCEEG4CBATIgQ2AtArIARFBEBBACECIANBAUHNHUEAEA8MBwsgAUGMLGohASACQQFqIgIgCUkNAAsLIABBBDYCCCAGKAIQIgMEQEF/IAAoAlgiASAAKAJgIgIgACgCbEEBa2xqIgQgAmoiAiACIARJGyICIAYoAgwiBCACIARJG60hEEF/IAAoAlQiAiAAKAJcIgQgACgCaEEBa2xqIgAgBGoiBCAAIARLGyIAIAYoAggiBCAAIARJG60hESABIAYoAgQiACAAIAFJG60hEiACIAYoAgAiACAAIAJJG60hEyAGKAIYIQBBACEBA0AgACAANQIEIg1CAX0iFCASfCANgCIVPgIUIAAgADUCACIOQgF9IhYgE3wgDoAiFz4CECAAQn8gADUCKCIPhkJ/hSIYIBAgFHwgDYAgFX1C/////w+DfCAPiD4CDCAAIBEgFnwgDoAgF31C/////w+DIBh8IA+IPgIIIABBNGohACABQQFqIgEgA0cNAAsLQQEhAgwECyAFIAI2ApABIANBAUH2OyAFQZABahAPQQAhAgwDC0EAIQIgBkEANgIQIANBAUHNHUEAEA8MAgsgBSAINgI0IAUgCTYCMCADQQFBt/MAIAVBMGoQDwwBCyAFIAg2AiggBSAHNgIkIAUgCTYCICADQQFBkesAIAVBIGoQDwsgBUGgAWokACACC54DAQd/IwBBEGsiBiQAAn8gAiACQQFBAiAAKAJIKAIQIghBgQJJGyIHQQF0QQVqIgRuIgUgBGxGIAIgBE9xRQRAIANBAUGKI0EAEA9BAAwBCwJ/IAAoAghBEEYEQCAAKAKcASAAKALMAUGMLGxqDAELIAAoAgwLIQRBACEAIAQtAIgsIgJBBHEEQCAEKAKkA0EBaiEACyAAIAVqIgVBIE8EQCAGIAU2AgAgA0EBQYs7IAYQD0EADAELIAQgAkEEcjoAiCwgACAFSQRAIAQgAEGUAWxqQagDaiECA0AgASACQQEQESABQQFqIgEgAkEEaiAHEBEgASAHaiIBIAJBCGpBAhARIAIgAigCCCIDIAQoAggiCSADIAlJGzYCCCABQQJqIAJBDGpBARARIAFBA2oiASACQRBqIAcQESABIAdqIgEgBkEMakEBEBEgAiAGKAIMNgIkIAIgAigCECIDIAggAyAISRs2AhAgAkGUAWohAiABQQFqIQEgAEEBaiIAIAVHDQALCyAEIAVBAWs2AqQDQQELIQogBkEQaiQAIAoL7AEBBH8jAEEQayIEJAACfwJAIAEgBEEIagJ/IAAoAkgoAhBBgAJNBEAgAgRAQX8hBUEBDAILIANBAUG+I0EAEA9BAAwDCyACQQFNDQFBfiEFQQILIgYQESAEIAIgBWo2AgwgBCgCCCICIAAoAkgoAhAiBU8EQCAEIAU2AgQgBCACNgIAIANBAUHGOiAEEA9BAAwCCyAAIAIgASAGaiAEQQxqIAMQQkUEQCADQQFBviNBABAPQQAMAgtBASAEKAIMRQ0BGiADQQFBviNBABAPQQAMAQsgA0EBQb4jQQAQD0EACyEHIARBEGokACAHC9kBAQR/IwBBEGsiBCQAIAQgAjYCDAJAAkAgAEEAIAEgBEEMaiADEEJFDQAgBCgCDA0AAn8gACgCCEEQRgRAIAAoApwBIAAoAswBQYwsbGoMAQsgACgCDAshB0EBIQUgACgCSCgCEEECSQ0BIAcoAtArIgJBHGohBkEBIQEgAiEDA0AgAyACKAIYNgLQCCADIAIoAqQGNgLcDiADQdQIaiAGQYgGEBIaIANBuAhqIQMgAUEBaiIBIAAoAkgoAhBJDQALDAELIANBAUHWIkEAEA8LIARBEGokACAFC9YBAQN/IwBBEGsiBCQAAkAgAkEBQQIgACgCSCgCECIGQYECSRsiBUECakcEQEEAIQAgA0EBQYogQQAQDwwBCwJ/IAAoAghBEEYEQCAAKAKcASAAKALMAUGMLGxqDAELIAAoAgwLIQIgASAEQQxqIAUQEUEBIQAgASAFaiIFIARBCGpBARARIAYgBCgCDCIBTQRAIAQgBjYCBCAEIAE2AgAgA0EBQdjvACAEEA9BACEADAELIAVBAWogAigC0CsgAUG4CGxqQagGakEBEBELIARBEGokACAAC4QCAQV/IwBBEGsiBCQAAn8gACgCCEEQRgRAIAAoApwBIAAoAswBQYwsbGoMAQsgACgCDAshBgJAIAJBAUECIAAoAkgiBygCEEGBAkkbIgVNBEBBACECIANBAUGkI0EAEA8MAQsgBCAFQX9zIAJqNgIMIAEgBEEIaiAFEBEgBCgCCCIIIAcoAhBPBEBBACECIANBAUGA6QBBABAPDAELQQEhAiABIAVqIgEgBigC0CsgCEG4CGxqQQEQESAAIAQoAgggAUEBaiAEQQxqIAMQQ0UEQEEAIQIgA0EBQaQjQQAQDwwBCyAEKAIMRQ0AQQAhAiADQQFBpCNBABAPCyAEQRBqJAAgAgusBgEHfyMAQRBrIgYkACAGIAI2AgwgACgCSCEJAn8gACgCCEEQRgRAIAAoApwBIAAoAswBQYwsbGoMAQsgACgCDAsiBCAELQCILEEBcjoAiCwCQCACQQRNBEAgA0EBQbwiQQAQDwwBCyABIARBARARIAQoAgBBCE8EQCADQQFBmiJBABAPDAELIAFBAWogBkEIakEBEBEgBCAGKAIIIgI2AgQgAkEFTgRAIANBAUHxIUEAEA8gBEF/NgIECyABQQJqIARBCGpBAhARIAQoAggiB0GAgARrQYCAfE0EQCAGIAc2AgAgA0EBQak9IAYQDwwBCyAEIAAoAqQBIgIgByACGzYCDCABQQRqIARBEGpBARARIAQoAhBBAk8EQCADQQFBhypBABAPDAELIAFBBWohAiAGIAYoAgxBBWs2AgwCQCAJKAIQIgdFDQAgBCgCAEEBcSEIIAQoAtArIQRBACEJIAdBCE8EQCAHQXhxIQEDQCAEIAVBuAhsaiAINgIAIAQgBUEBckG4CGxqIAg2AgAgBCAFQQJyQbgIbGogCDYCACAEIAVBA3JBuAhsaiAINgIAIAQgBUEEckG4CGxqIAg2AgAgBCAFQQVyQbgIbGogCDYCACAEIAVBBnJBuAhsaiAINgIAIAQgBUEHckG4CGxqIAg2AgAgBUEIaiEFIApBCGoiCiABRw0ACwsgB0EHcSIBRQ0AA0AgBCAFQbgIbGogCDYCACAFQQFqIQUgCUEBaiIJIAFHDQALC0EAIQUgAEEAIAIgBkEMaiADEENFBEAgA0EBQbwiQQAQDwwBCyAGKAIMBEAgA0EBQbwiQQAQDwwBCwJ/IAAoAghBEEYEQCAAKAKcASAAKALMAUGMLGxqDAELIAAoAgwLIQEgACgCSCgCEEECTwRAIAEoAtArIgEoAgRBAnQhByABQbAHaiEKIAFBrAZqIQNBASEJIAEhAgNAIAIgAf0AAgT9CwK8CCACIAEoAhQ2AswIIAJB5A5qIAMgBxASGiACQegPaiAKIAcQEhogAkG4CGohAiAJQQFqIgkgACgCSCgCEEkNAAsLQQEhBQsgBkEQaiQAIAUL7AkBBn8jAEHwAGsiBCQAIARBADYCaAJAIAJBCEcEQCADQQFBvR5BABAPIANBAUG9HkEAEA8MAQsgASAAQcwBakECEBEgAUECaiAEQewAakEEEBEgAUEGaiAEQeQAakEBEBEgAUEHaiAEQegAakEBEBEgACgCzAEiAiAAKAJoIgggACgCbGxPBEAgBCACNgJgIANBAUGdOyAEQeAAahAPDAELIAAoApwBIAJBjCxsaiEFIAIgCG4hByAEKAJkIQECQCAAKAIsIgZBAE4gAiAGR3ENACAFKALUK0EBaiIGIAFGDQAgBCAGNgJYIAQgATYCVCAEIAI2AlAgA0EBQbU7IARB0ABqEA9BACEFDAELIAUgATYC1CsCQAJAIAQoAmwiAUEBa0EMTQR/IAFBDEcNASAEQQw2AjAgA0ECQeXXACAEQTBqEA8gBCgCbAUgAQtFBEAgA0EEQbLPAEEAEA8gAEEBNgI4CwJAAkACQAJAIAUoAtgrIgEEQCAEKAJkIgYgAUkNASAEIAE2AiQgBCAGNgIgIANBAUGFJyAEQSBqEA8gAEEBNgI4QQAhBQwHCyAEKAJoIgYNAQwDCyAEKAJoIgZFDQELIAQgBiAALQBEQQR2QQFxaiIBNgJoIAQoAmQiBiAFKALYKyIJQQFrSwRAIAQgCTYCBCAEIAY2AgAgA0EBQaImIAQQDyAAQQE2AjhBACEFDAULIAEgBk0EQCAEIAE2AhQgBCAGNgIQIANBAUHpJyAEQRBqEA8gAEEBNgI4QQAhBQwFCyAFIAE2AtgrCyABIAQoAmRBAWpHDQAgACAALQBEQQFyOgBECyAEKAJsIQEgAEEQNgIIIABBACABQQxrIAAoAjgbNgIYAkAgACgCLCIBQX9GBEBBBCEFIAIgByAIbGsiASAAKAIcSQ0BIAEgACgCJE8NASAHIAAoAiBJDQEgByAAKAIoT0ECdCEFDAELIAAoAswBIAFHQQJ0IQULIAAgAC0AREH7AXEgBXI6AERBASEFIAAoAsgBIgFFDQIgASgCKCIGIAAoAswBIgJBKGxqIgcgAjYCACAHIAQoAmQiCDYCDCAEKAJoIgEEQCAHIAE2AgQgByAEKAJoIgE2AgggBygCECICRQRAIAFBGBATIQEgACgCyAEoAiggACgCzAFBKGxqIAE2AhAgAQ0EQQAhBSADQQFByTRBABAPDAQLIAIgAUEYbBAXIQEgACgCyAEoAiggACgCzAFBKGxqIQIgAUUEQCACKAIQEBBBACEFIAAoAsgBKAIoIAAoAswBQShsakEANgIQIANBAUHJNEEAEA8MBAsgAiABNgIQDAMLIAcoAhAiAUUEQCAHQQo2AghBCkEYEBMhASAAKALIASgCKCIGIAAoAswBIgJBKGxqIgcgATYCECABRQ0CIAQoAmQhCAsgCCAGIAJBKGxqIgIoAghJDQIgAiAIQQFqIgI2AgggASACQRhsEBchASAAKALIASgCKCAAKALMAUEobGohAiABRQRAIAIoAhAQEEEAIQUgACgCyAEoAiggACgCzAFBKGxqIgBBADYCCCAAQQA2AhAgA0EBQck0QQAQDwwDCyACIAE2AhAMAgsgBCABNgJAIANBAUHy2QAgBEFAaxAPQQAhBQwBC0EAIQUgB0EANgIIIANBAUHJNEEAEA8LIARB8ABqJAAgBQurBwEIfyMAQdAAayIEJAAgBEEBNgJMAkACQCAAKALIASIFKAIoIgMNACAFIAAoAmwgACgCaGwiAzYCJCADQSgQEyEDIAAoAsgBIgUgAzYCKCADRQRAQQAhBQwCCyAFKAIkRQ0AA0BBACEFIAMgBkEobCIHaiIDQQA2AhQgA0HkADYCHEHkAEEYEBMhCSAHIAAoAsgBIggoAigiA2ogCTYCGCAJRQ0CIAZBAWoiBiAIKAIkSQ0ACwsgACgCLCEJAkAgAygCEEUNAAJAIAMgCUEobGoiAygCBEUEQCABIAApAzBCAnwgAhA2DQFBACEFIAJBAUGnKUEAEA8MAwsgASADKAIQKQMAQgJ8IAIQNg0AQQAhBSACQQFBpylBABAPDAILIAAoAghBgAJHDQAgAEEINgIICwJAIAAoAmwgACgCaGwiB0UNACAAKAKcASEFQQAhAyAHQQhPBEAgB0F4cSEIQQAhBgNAIAUgA0GMLGxqQX82AtQrIAUgA0EBckGMLGxqQX82AtQrIAUgA0ECckGMLGxqQX82AtQrIAUgA0EDckGMLGxqQX82AtQrIAUgA0EEckGMLGxqQX82AtQrIAUgA0EFckGMLGxqQX82AtQrIAUgA0EGckGMLGxqQX82AtQrIAUgA0EHckGMLGxqQX82AtQrIANBCGohAyAGQQhqIgYgCEcNAAsLIAdBB3EiBkUNAANAIAUgA0GMLGxqQX82AtQrIANBAWohAyAKQQFqIgogBkcNAAsLQQAhBSAAIARByABqQQAgBEHEAGogBEFAayAEQTxqIARBOGogBEE0aiAEQcwAaiABIAIQJ0UNACAJQQFqIQcDQAJAIAQoAkxFDQAgACAEKAJIIgNBAEEAIAEgAhArRQ0CIAAoAmghCCAAKAJsIQogBCADQQFqIgY2AiAgBCAIIApsNgIkIAJBBEGg1wAgBEEgahAPIAAoAtABIAAoAkwoAhgQdEUNAiAAKAKcASADQYwsbGoiBSgC3CsiCARAIAgQECAFQgA3AtwrCyAEIAY2AhAgAkEEQeb8ACAEQRBqEA8gAyAJRgRAIAEgACgCyAEpAwhCAnwgAhA2DQFBACEFIAJBAUGnKUEAEA8MAwsgBCAHNgIEIAQgBjYCACACQQJB3eUAIAQQD0EAIQUgACAEQcgAakEAIARBxABqIARBQGsgBEE8aiAEQThqIARBNGogBEHMAGogASACECcNAQwCCwsgACACEHIhBQsgBEHQAGokACAFC8gGAgd/AX4jAEHQAGsiAyQAIANBATYCTAJAAkAgACgCaCIEQQFHDQAgACgCbEEBRw0AIAAoAlQNACAAKAJYDQAgACgCTCIFKAIADQAgBSgCBA0AIAUoAgggACgCXEcNACAFKAIMIAAoAmBHDQBBACEEIAAgA0HIAGpBACADQcQAaiADQUBrIANBPGogA0E4aiADQTRqIANBzABqIAEgAhAnRQ0BAkAgACADKAJIQQBBACABIAIQKwRAIAAoAkwiASgCEA0BQQEhBAwDCyACQQFBkcIAQQAQDwwCCyABKAIYIQFBACECA0AgASACQTRsIgRqKAIsEBAgACgCTCIFKAIYIgEgBGoiBiAAKALQASIHKAIUKAIAKAIUIAJBzABsaiIIKAIkNgIsIAYgBygCGCgCGCAEaigCJDYCJCAIQQA2AiRBASEEIAJBAWoiAiAFKAIQSQ0ACwwBCwNAAkACfwJAIARBAUcNACAAKAJsQQFHDQAgACgCnAEoAtwrRQ0AIANBADYCSCAAQQA2AswBIAAgACgCCEGAAXI2AghBAAwBC0EAIQQgACADQcgAakEAIANBxABqIANBQGsgA0E8aiADQThqIANBNGogA0HMAGogASACECdFDQMgAygCTEUNASADKAJICyIHQQFqIQQgACAHQQBBACABIAIQKyEJIAAoAmggACgCbGwhBSAJRQRAIAMgBTYCBCADIAQ2AgAgAkEBQZc5IAMQD0EAIQQMAwsgAyAFNgIkIAMgBDYCICACQQRBoNcAIANBIGoQDyAAKALQASAAKAJMKAIYEHRFBEBBACEEDAMLAkACQCAAKAJoQQFHDQAgACgCbEEBRw0AIAAoAkwiBSgCACAAKAJIIgYoAgBHDQEgBSgCBCAGKAIERw0BIAUoAgggBigCCEcNASAFKAIMIAYoAgxHDQELIAAoApwBIAdBjCxsaiIFKALcKyIGRQ0AIAYQECAFQgA3AtwrCyADIAQ2AhAgAkEEQeb8ACADQRBqEA8gASkDCCIKUAR+QgAFIAogASkDOH0LUARAIAAoAghBwABGDQELIAhBAWoiCCAAKAJoIgQgACgCbGxHDQELCyAAIAIQciEECyADQdAAaiQAIAQLtQYBDH8gACgCSCEJAkAgACgCaCAAKAJsbCIMBEAgCSgCECIBQbgIbCENIAEgAWxBAnQhCiAAKAIMIQQgACgCnAEhAwNAIAMoAtArIQsgAyAEQYwsEBIiAUEANgLoKyABQX82AtQrIAFBADYCsCggAUEANgKELCABQQA2AvArIAFCADcC+CsgASALNgLQKyABIAEtAIgsQfwBcToAiCwgBCgC6CsEQCABIAoQFCIDNgLoKyADRQRAQQAPCyADIAQoAugrIAoQEhoLIAEgBCgC+CtBFGwiBRAUIgM2AvArQQAhCCADRQ0CIAMgBCgC8CsgBRASGiAEKAL0KyIGBEAgBCgC8CshAyABKALwKyEFQQAhBwNAIAMoAgwEQCAFIAMoAhAQFCIGNgIMIAZFBEBBAA8LIAYgAygCDCADKAIQEBIaIAQoAvQrIQYLIAEgASgC+CtBAWo2AvgrIAVBFGohBSADQRRqIQMgB0EBaiIHIAZJDQALCyABIAQoAoQsQRRsIgUQFCIDNgL8KyADRQ0CIAMgBCgC/CsgBRASGiABIAQoAoQsIgg2AoQsIAgEQCAEKAL8KyEDIAEoAvwrIQVBACEHA0AgAygCCCIGBEAgBSABKALwKyAGIAQoAvAra2o2AggLIAMoAgwiBgRAIAUgASgC8CsgBiAEKALwK2tqNgIMCyAFQRRqIQUgA0EUaiEDIAdBAWoiByAIRw0ACwsgCyAEKALQKyANEBIaIAFBjCxqIQMgDkEBaiIOIAxHDQALC0EBIQggAAJ/QQBBAUHIABATIgFFDQAaIAEgAS0AKEH+AXFBAXI6ACggAUEBQQQQEyIENgIUIAEgBA0AGiABEBBBAAsiATYC0AEgAUUEQEEADwsgACgC1AEhBUEAIQQgASAAQdAAajYCHCABIAk2AhhBAUHQBhATIQMgASgCFCADNgIAAkAgA0UNACAJKAIQQcwAEBMhAyABKAIUKAIAIgcgAzYCFCADRQ0AIAcgCSgCEDYCECAAKAKkASEEIAEgBTYCLCABIAQ2AgBBASEECyAEDQAgACgC0AEQVUEAIQggAEEANgLQASACQQFBwhtBABAPCyAIC9USAwx/AX0BfiMAQTBrIggkACAAQQE2AggCfwJAAkAgASAIQShqIgVBAiACEBpBAkcNACAFIAhBLGpBAhARIAgoAixBz/4DRw0AIABBAjYCCCAAKALIASABKQM4QgJ9IhA3AwAgCCAQNwMQIAJBBEHu3gAgCEEQahAPIAAoAsgBIgMpAwAhECADKAIYIgdBAWoiBSADKAIgIgRNBEAgAygCHCEEDAILIAMCfyAEs0MAAMhCkiIPQwAAgE9dIA9DAAAAAGBxBEAgD6kMAQtBAAsiBTYCICADKAIcIAVBGGwQFyIEBEAgAyAENgIcIAMoAhgiB0EBaiEFDAILIAMoAhwQECADQQA2AiAgA0IANwMYIAJBAUGpHUEAEA8LIAJBAUG19QBBABAPQQAMAQsgBCAHQRhsaiIEQQI2AhAgBCAQxDcDCCAEQc/+AzsBACADIAU2AhggASAAKAIQQQIgAhAaQQJHBEAgAkEBQZYSQQAQD0EADAELIAAoAhAgCEEoakECEBECQAJAIAgoAigiBEGQ/wNHBEADQEHgvQEhByAEQf/9A00EQCAIIAQ2AgAgAkEBQcoQIAgQD0EADAULA0AgByIFKAIAIgMEQCAFQQxqIQcgAyAERw0BCwsCQAJAIAMNAEECIQYgAkECQfUcQQAQD0GWEiEHAkACQCABIAAoAhBBAiACEBpBAkcNAANAIAAoAhAgCEEsakECEBFB4L0BIQMgCCgCLCIEQYD+A08EQANAIAMiBSgCACIMBEAgA0EMaiEDIAQgDEcNAQsLIAUoAgQgACgCCHFFBEBB/CghBwwDCyAMBEAgDEGQ/wNGBEAgCEGQ/wM2AigMBwsgASkDOCEQIAAoAsgBIgMoAhgiBUEBaiIEIAMoAiAiB00EQCADKAIcIQcMBQsgAwJ/IAezQwAAyEKSIg9DAACAT10gD0MAAAAAYHEEQCAPqQwBC0EACyIFNgIgIAMoAhwgBUEYbBAXIgcEQCADIAc2AhwgAygCGCIFQQFqIQQMBQsgAygCHBAQIANBADYCICADQgA3AxhBqR0hBwwDCyAGQQJqIQYLIAEgACgCEEECIAIQGkECRg0ACwsgAkEBIAdBABAPIAJBAUH9yABBABAPQQAMBwsgByAFQRhsaiIFIAY2AhAgBSAQpyAGa6w3AwggBUEAOwEAIAMgBDYCGCAIIAw2AihB4L0BIQQDQCAEIgUoAgAiA0UNASAEQQxqIQQgAyAMRw0ACwsgBSgCBCAAKAIIcUUEQCACQQFB/ChBABAPQQAMBgsgASAAKAIQQQIgAhAaQQJHBEAgAkEBQZYSQQAQD0EADAYLIAAoAhAgCEEkakECEBEgCCgCJCIEQQFNBEAgAkEBQaEuQQAQD0EADAYLIAggBEECayIHNgIkIAAoAhAhBCAAKAIUIAdJBEAgBCAHEBciBEUEQCAAKAIQEBAgAEIANwMQIAJBAUHUJUEAEA9BAAwHCyAAIAQ2AhAgACAIKAIkIgc2AhQLIAEgBCAHIAIQGiIEIAgoAiRHBEAgAkEBQZYSQQAQD0EADAYLIAAgACgCECAEIAIgBSgCCBEBAEUEQCACQQFBqBJBABAPQQAMBgsgASkDOCEQIAgoAiQhDAJAIAAoAsgBIgUoAhgiBkEBaiIHIAUoAiAiBE0EQCAFKAIcIQQMAQsgBQJ/IASzQwAAyEKSIg9DAACAT10gD0MAAAAAYHEEQCAPqQwBC0EACyIENgIgIAUoAhwgBEEYbBAXIgRFDQUgBSAENgIcIAUoAhgiBkEBaiEHCyAEIAZBGGxqIgQgDEEEajYCECAEIBCnIAxrQQRrrDcDCCAEIAM7AQAgBSAHNgIYIAEgACgCEEECIAIQGkECRwRAIAJBAUGWEkEAEA9BAAwGC0EBIAogA0Hc/gNGGyEKQQEgCyADQdL+A0YbIQtBASANIANB0f4DRhshDSAAKAIQIAhBKGpBAhARIAgoAigiBEGQ/wNHDQELCyANDQELIAJBAUGYJEEAEA9BAAwCCyALRQRAIAJBAUHGJEEAEA9BAAwCCyAKRQRAIAJBAUH0JEEAEA9BAAwCC0EAIQNBACENIwBBEGsiBCQAQQEhBwJAIAAtALwBQQFxRQ0AAkAgACgCcCILRQ0AAkADQCAAKAJ0IA1BA3RqIgUoAgAiCgRAIAMgBSgCBCIGayIFQQAgAyAFTxshBSADIAZJBEAgBiADayELIAMgCmohCgNAIAtBBEkEQEGOKyEDDAULIAogBEEMakEEEBEgBCgCDCIDQX9zIAlJBEBB9CohAwwFCyADIAtBBGsiBmsgBSADIAZLIgwbIQUgAyAJaiEJIAYgA2shCyAKQQAgAyAMG2pBBGohCiADIAZJDQALIAAoAnAhCwsgBSEDCyANQQFqIg0gC0kNAAsgA0UNAUEAIQcgAkEBQekWQQAQDwwCC0EAIQcgAkEBIANBABAPDAELIAAgCRAUIgM2AogBIANFBEBBACEHIAJBAUG+IEEAEA8MAQsgACAJNgJ8IAAoAnQhBgJAIAAoAnAiCgRAQQAhCUEAIQNBACEFA0AgBiAFQQN0Ig1qIgwoAgAiCwRAIAAoAogBIANqIQoCfyAMKAIEIgYgCU0EQCAKIAsgBhASGiADIAZqIQMgCSAGawwBCyAKIAsgCRASGiADIAlqIQMgBiAJayIGBEAgCSALaiEJA0AgBkEESQ0GIAkgBEEIakEEEBEgCUEEaiEJIAAoAogBIANqIQogBkEEayIGIAQoAggiC0kEQCAKIAkgBhASGiADIAZqIQMgBCgCCCAGawwDCyAKIAkgCxASGiAEKAIIIgogA2ohAyAJIApqIQkgBiAKayIGDQALC0EACyEJIAAoAnQgDWooAgAQECAAKAJ0IgYgDWpCADcCACAAKAJwIQoLIAVBAWoiBSAKSQ0ACyAAKAJ8IQkgACgCiAEhAwsgACAJNgKQASAAIAM2AnggAEEANgJwIAYQECAAQQA2AnQMAQtBACEHIAJBAUGOK0EAEA8LIARBEGokACAHRQRAIAJBAUGPPUEAEA9BAAwCCyACQQRB99YAQQAQDyAAKALIASABKQM4Qv7///8PfEL/////D4M3AwggAEEINgIIQQEMAQsgBSgCHBAQIAVBADYCICAFQgA3AxggAkEBQakdQQAQD0EACyEOIAhBMGokACAOCxwAIAAoAghFIAAoAsABQQBHIAAoAsQBQQBHcXELBABBAAsPACAABEAgACABNgK4AQsLjwEBBH8gACgCGCIBBEAgACgCHCIDQTRuIQQgA0E0TwR/QQAhAwNAIAEoAgAiAgRAIAJBAWsQECABQQA2AgALIAEoAgQiAgRAIAIQECABQQA2AgQLIAEoAggiAgRAIAIQECABQQA2AggLIAFBNGohASADQQFqIgMgBEcNAAsgACgCGAUgAQsQECAAQQA2AhgLC4YBAQR/IAAoAhgiAQRAIAAoAhwiAkHAAE8EfyACQQZ2IQRBACECA0AgASgCACIDBEAgAxAQIAFBADYCAAsgASgCBCIDBEAgAxAQIAFBADYCBAsgASgCPBAQIAFBADYCPCABQUBrIQEgAkEBaiICIARHDQALIAAoAhgFIAELEBAgAEEANgIYCws/AQF/IAAEQCAAKAJ0IgEEQCABEBAgAEEANgJ0CyAAKAJ4IgEEQCABEBAgAEEANgJ4CyAAKAKUARAQIAAQEAsLwaYFBFx/AnsGfgF9IwBB4ABrIiMkACAAKAIIIRoCQAJAAkACQCAAKAIARQRAIBogGigCECAaKAIIayAaKAIUIBooAgxrbEECdCIGEBgiAzYCPCADRQRAIAAoAiQaIAAoAiBBAUHRPEEAEA8gACgCJBogAEEcaiEQDAMLIANBACAGEBUaDAELIBooAjwiA0UNACADEBAgGkEANgI8CyAAKAIQIjIoAhwgMigCGEGYAWxqIgNBmAFrKAIAITUgA0GQAWsoAgAhNiAAKAIUIS8gACgCDCEwIAAoAgQhNyAAKAIcKAIARQ0CIABBHGohEAJAAn9BACABKAIEIgNBAEwNABogASgCACEGAkADQCAGIAdBDGxqIgQoAgBFDQEgB0EBaiIHIANHDQALQQAMAQsgBCgCBAsiBA0AQQFBnAEQEyIERQRAIAAoAiBBAUGQMEEAEA8MAgsgBEEANgKMASABKAIEIgNB/////wdHBH8CfyABKAIAIQYgA0EASgRAA0AgBiAJQQxsaiIHKAIARQRAIAcoAggiAwR/IAcoAgQgAxECACABKAIABSAGCyAJQQxsaiIBQQ82AgggASAENgIEQQEMAwsgCUEBaiIJIANHDQALC0EAIAYgA0EMbEEMahAXIgNFDQAaIAEgAzYCACADIAEoAgQiBkEMbGoiA0EPNgIIIAMgBDYCBCADQQA2AgAgASAGQQFqNgIEQQELBUEACw0AIAAoAiBBAUGMP0EAEA8gBCgCdCIBBEAgARAQIARBADYCdAsgBCgCeCIBBEAgARAQIARBADYCeAsgBCgClAEQECAEEBAMAQsgBCAAKAIYNgKQASAAKAIoISsgACgCJCEhIAAoAiAhHSAvKAKoBiERIDAoAhAhAQJAAkAgLygCECIWQcAAcQRAIBYhCiMAQbACayIPJAACQCARBEAgIQRAQQAhByAdQQFBgRhBABAPDAILQQAhByAdQQFBgRhBABAPDAELIAQoAnQhBwJAAkAgGigCFCAaKAIMayIDIBooAhAgGigCCGsiBmwiASAEKAKEAUsEQCAHEBAgBCABQQJ0IhEQGCIHNgJ0IAdFBEBBACEHDAQLIAQgATYChAEMAQsgB0UNASABQQJ0IRELIAdBACAREBUaCyAEKAJ4IQcCQCAEKAKIAUHPFEsNACAHEBAgBEHA0gAQGCIHNgJ4IAcNAEEAIQcMAQsgBEHQFDYCiAEgB0EAQcDSABAVGiAEIAM2AoABIAQgBjYCfCAaKAIYIgJFBEBBASEHDAELIBooAhwhDUEBIQcCQAJAAkACQAJAIBooAjQiAwRAIBooAgQhCUEAIQdBACEBAkAgA0EETwRAIANBfHEhAQNAIAkgCEEDdGoiBkEcaiAGQRRqIAZBDGogBv0JAgT9VgIAAf1WAgAC/VYCAAMgXv2uASFeIAhBBGoiCCABRw0ACyBeIF4gXv0NCAkKCwwNDg8AAQIDAAECA/2uASJeIF4gXv0NBAUGBwABAgMAAQIDAAECA/2uAf0bACEHIAEgA0YNAQsDQCAJIAFBA3RqKAIEIAdqIQcgAUEBaiIBIANHDQALCyADQQFGBEAgBCgCkAFFDQULIAcgBCgCmAFNDQEgBCgClAEgBxAXIhENAkEAIQcMBgsgBCgCkAFFDQULIAQoApQBIhENAUEAIQcMBAsgBCAHNgKYASAEIBE2ApQBCyAaKAI0RQRAQQAhBwwCCyAaKAIEIQhBACEHQQAhAQNAIAcgEWogCCABQQN0IgNqIgYoAgAgBigCBBASGiAaKAIEIgggA2ooAgQgB2ohByABQQFqIgEgGigCNEkNAAsMAQsgGigCBCgCACERC0EAIQFBACEIAn9BACAaKAIoIgNFDQAaIBooAgAiBigCCCEIQQAgA0EBRg0AGiAGKAIgCyEDIAIgDWshRQJAIAMgCGoiCEUEQEEAIQkMAQtBASEBIBooAgAiAygCACEFQQAhCSAIQQFGBEBBACEBDAELIAMoAhghCQsgRUEBaiEWIAQoAnQhDiAEKAJ4IRQgGigCDCESIBooAhQhGCAaKAIIISQgGigCECErAkACQAJAAkACQAJAAkACQAJAIAFFDQAgCQ0AICFFDQEgHUECQaHQAEEAEA9BASEIDAILIAhBBEkNASAhBEAgDyAINgJwIB1BAUH8xgAgD0HwAGoQDwwICyAPIAg2AmAgHUEBQfzGACAPQeAAahAPQQAhBwwICyAdQQJBodAAQQAQDyAaKAIYIgFBHksNAUEBIQwgASAWTw0DDAULIBooAhgiAUEeTQ0BICFFDQAgDyABNgIgIB1BAUGb2wAgD0EgahAPDAULIA8gATYCACAdQQFBm9sAIA8QD0EAIQcMBQsgASAWSQ0BIAhBAkkEQCAIIQwMAQsgASAWRwRAIAghDAwBC0EBIQxBkMcBLQAADQAgIUUEQEGQxwFBAToAACAPIAg2AkAgHUECQabMACAPQUBrEA8MAQtBkMcBLQAARQRAQZDHAUEBOgAAIA8gCDYCUCAdQQJBpswAIA9B0ABqEA8LCwJAAkAgBUECSQ0AIAUgB0sNACAFIAlqIAdNDQELICEEQEEAIQcgHUEBQcLGAEEAEA8MBQtBACEHIB1BAUHCxgBBABAPDAQLAkACQCAFIBFqIhNBAWstAABBBHQgE0ECay0AAEEPcXIiBkECSQ0AIAUgBkgNACAGQfAfSQ0BCyAhBEBBACEHIB1BAUHW8gBBABAPDAULQQAhByAdQQFB1vIAQQAQDwwECyAaKAIcISYgD0EANgKQAiAPQQA2ApgCIA9CADcDiAIgD0IANwOoAiAPQgA3ApwCIA8gBkEBayIHNgKUAiAPIAUgEWogBmsiATYCgAJC/wEhYCAGQQJPBEAgATEAACFgC0EIIQMgD0EINgKQAiAPIAZBAmsiCDYClAIgDyBgQg+EIGAgB0EBRhsiYDcDiAIgDyABIAZBAUpqIgc2AoACIA8gYEL/AVEiDTYCmAICfwJAIAFBA3EiAkEDRg0AQv8BIWEgDQRAQQAgBy0AAEGPAUsNAhoLIAZBA04EQCAHMQAAIWELIA8gBkEDayINNgKUAiAPQQ9BECBgQv8BUSILGyIDNgKQAiAPIAcgBkECSmoiATYCgAIgDyBhQg+EIGEgCEEBRhsiYUL/AVE2ApgCIA8gYEIHQgggCxuGIGGEImA3A4gCIAJBAkYNACBhQv8BUQRAQQAgAS0AAEGPAUsNAhoLQv8BIWIgBkEETgRAIAExAAAhYgsgDyAGQQRrIgc2ApQCIA8gASAGQQNKaiIBNgKAAiAPIGJCD4QgYiANQQFGGyJiQv8BUTYCmAIgDyADQQdBCCBhQv8BUSIIG2oiAzYCkAIgDyBgQgdCCCAIG4YgYoQiYDcDiAIgAkEBRg0AQv8BIWEgYkL/AVEEQEEAIAEtAABBjwFLDQIaCyAGQQVOBEAgATEAACFhCyAPIAZBBWs2ApQCIA8gASAGQQRKajYCgAIgDyBhQg+EIGEgB0EBRhsiYUL/AVE2ApgCIA8gA0EHQQggYkL/AVEiARtqIgM2ApACIA8gYEIHQgggARuGIGGEImA3A4gCCyAPIGBBwAAgA2uthjcDiAJBAQtFBEAgIQRAQQAhByAdQQFBg9UAQQAQDwwFC0EAIQcgHUEBQYPVAEEAEA8MBAsgKyAkayEVIA8gBkECayILNgL0ASAPIAUgEWoiAkEDayIDNgLgASAPIAJBAmstAAAiGUGPAUsiDTYC+AEgDyAZQQR2rSJgNwPoASAPQQNBBCBgQgeDQgdRGyIBNgLwASADQQNxQQFqIgcgCyAHIAtIGyEIAkACQCAGQQJMBEAgDyALIAhrIgI2AvQBDAELIA8gAkEEayIHNgLgASAPIAMtAAAiF0GPAUsiDTYC+AEgDyAXrSJhIAGthiBghCJgNwPoASAPQQhBB0EIIGFC/wCDQv8AURsgGUGPAU0bIAFqIgE2AvABAkAgCEEBRgRAIAchAwwBCyAPIAJBBWsiAzYC4AEgDyAHLQAAIhlBjwFLIg02AvgBIA8gGa0iYSABrYYgYIQiYDcD6AEgD0EIQQdBCCBhQv8Ag0L/AFEbIBdBjwFNGyABaiIBNgLwASAIQQJGDQAgDyACQQZrIgc2AuABIA8gAy0AACIXQY8BSyINNgL4ASAPIBetImEgAa2GIGCEImA3A+gBIA9BCEEHQQggYUL/AINC/wBRGyAZQY8BTRsgAWoiATYC8AEgCEEDRgRAIAchAwwBCyAPIAJBB2siAzYC4AEgDyAHMQAAImFCjwFWIg02AvgBIA8gYSABrYYgYIQiYDcD6AEgD0EIQQdBCCBhQv8Ag0L/AFEbIBdBjwFNGyABaiIBNgLwAQsgDyALIAhrIgI2AvQBIAFBIEsNAQsCQCACQQROBEAgA0EDaygCACEHIA8gAkEEazYC9AEgDyADQQRrNgLgAQwBCyACQQBMBEBBACEHDAELIAJBAXEhRwJAIAJBAUYEQEEYIQhBACEHDAELIAJB/v///wdxIRdBGCEIQQAhB0EAIQsDQCAPIANBAWsiHzYC4AEgAy0AACFGIA8gA0ECayIDNgLgASAPIAJBAWs2AvQBIB8tAAAhHyAPIAJBAmsiAjYC9AEgRiAIdCAHciAfIAhBCGt0ciEHIAhBEGshCCALQQJqIgsgF0cNAAsLIEdFDQAgDyADQQFrNgLgASADLQAAIUggDyACQQFrNgL0ASBIIAh0IAdyIQcLIA8gB0H/AXEiA0GPAUs2AvgBIA9BB0EIIAdBgICA+AdxQYCAgPgHRhtBCCANGyICQQhBB0EIIAdBgID8A3FBgID8A0YbIAdB/////3hNG2oiCEEIQQdBCCAHQYD+AXFBgP4BRhsgB0EQdkH/AXEiDUGPAU0baiILQQhBB0EIIAdB/wBxQf8ARhsgB0EIdkH/AXEiGUGPAU0bIAFqajYC8AEgDyANIAJ0IAdBGHZyIBkgCHRyIAMgC3RyrSABrYYgYIQ3A+gBCyAPQcABaiARIAUgBmtB/wEQWwJ/QQAgDEECSQ0AGiAPQaABaiATIAlBABBbQQAgDEECRg0AGkIAIWBCACFiIA9BATYCmAEgD0EANgKQASAPQgA3A4gBIA8gCUEBayIGNgKUASAPIAUgEWogCWoiA0EBayIBNgKAASABQQNxIQUCQCAJQQBMBEAgASEDDAELIA8gA0ECayIDNgKAASABMQAAIWALIA8gYDcDiAEgDyBgQo8BViIRNgKYASAPQQdBCCBgQv8Ag0L/AFEbIg02ApABAkAgBUUNACAPIAlBAmsiAjYClAECQCAJQQJIBEAgAyEHDAELIA8gA0EBayIHNgKAASADMQAAIWILIA8gYkKPAVYiETYCmAEgDyBiIA2thiBghCJhNwOIASAPQQhBB0EIIGJC/wCDQv8AURsgYEKPAVgbIA1qIg02ApABIAVBAUYEQCAHIQMgYSFgIAYhCSACIQYMAQsgDyAJQQNrIgg2ApQBAkAgCUEDSARAIAchAQwBCyAPIAdBAWsiATYCgAEgBzEAACFjCyAPIGNCjwFWIhE2ApgBIA8gYyANrYYgYYQiYDcDiAEgD0EIQQdBCCBjQv8Ag0L/AFEbIGJCjwFYGyANaiINNgKQASAFQQJGBEAgASEDIAIhCSAIIQYMAQsgDyAJQQRrIgY2ApQBQgAhYgJAIAlBBEgEQCABIQMMAQsgDyABQQFrIgM2AoABIAExAAAhYgsgDyBiQo8BViIRNgKYASAPIGIgDa2GIGCEImA3A4gBIA9BCEEHQQggYkL/AINC/wBRGyBjQo8BWBsgDWoiDTYCkAEgCCEJCyANQSBNBEACQCAJQQVOBEAgA0EDaygCACEHIA8gCUEFazYClAEgDyADQQRrNgKAAQwBC0EAIQcgCUECSA0AQRghCQNAIA8gA0EBayIBNgKAASADLQAAIUkgDyAGQQFrIgI2ApQBIEkgCXQgB3IhByAGQQFLIUogASEDIAlBCGshCSACIQYgSg0ACwsgDyAHQf8BcSIBQY8BSzYCmAEgD0EHQQggB0GAgID4B3FBgICA+AdGG0EIIBEbIgNBCEEHQQggB0GAgPwDcUGAgPwDRhsgB0H/////eE0baiIGQQhBB0EIIAdBgP4BcUGA/gFGGyAHQRB2Qf8BcSIJQY8BTRtqIgJBCEEHQQggB0H/AHFB/wBGGyAHQQh2Qf8BcSIIQY8BTRsgDWpqNgKQASAPIAkgA3QgB0EYdnIgCCAGdHIgASACdHKtIA2thiBghDcDiAELQQELITEgGCASayEfIBZBAWohLCAUQQA6AMAQIBRBwBBqIQsgD0GAAmoQKCECIBVBAEoEQCAmQQFrIRMgFCEDIAshCEEAIREgDiEGQQAhDQNAIA0hBSARQQh0IA9B4AFqEC9B/wBxQQF0ckGg/QBqLwEAIQECQCARDQAgAUEAIAJBAmsiB0F/RhshASACQQFKBEAgByECDAELIA9BgAJqECghAgsgDykD6AEhZCAPKALwASFLIAMgAygCACABQQR2IhhBA3EgAUECdkEwcXIgInRyIhY2AgAgAUEFdkEHcSABQRBxIh5BBHZyIREgSyABQQdxIgdrIQ0gZCAHrYgiYKchCUEAIQcgFSAFQQJySgRAIBFBCHQgCUH/AHFBAXRyQaD9AGovAQAhBwJAIBENACAHQQAgAkECayIJQX9GGyEHIAJBAUoEQCAJIQIMAQsgD0GAAmoQKCECCyAHQQR2QQFxIAdBBXZBB3FyIREgDSAHQQdxIglrIQ0gYCAJrYgiYKchCQsgAyAHQQJ0QYAGcSAHQTBxciAiQQRqdCAWcjYCAAJAIAdBAnZBAnEgAUEDdkEBcXIiF0EDRw0AQQRBAyACQQJrIhZBf0YbIRcgAkEBSgRAIBYhAgwBCyAPQYACahAoIQILAn8gF0UEQCAPQoGAgIAQNwJ4QQAMAQsgF0ECTQRAIA9BASAJQQdxQdSdAWotAAAiFkEFdkF/IBZBAnZBB3EiGXRBf3MgCSAWQQNxIgl2cWpBAWoiFiAXQQFGIhcbNgJ8IA8gFkEBIBcbNgJ4IAkgGWoMAQsgCSAJQQdxQdSdAWotAAAiFkEDcSIZdiEJIBdBA0YEQCAWQQV2QQFqIRcgGUEDRgRAIA8gCUEBcUECcjYCfCAPIBdBfyAWQQJ2QQdxIhZ0QX9zIAlBAXZxajYCeCAWQQRqDAILIA8gFyAJIAlBB3FB1J0Bai0AACIJQQNxIhJ2IiBBfyAWQQJ2QQdxIhZ0QX9zcWo2AnggD0F/IAlBAnZBB3EiF3RBf3MgICAWdnEgCUEFdmpBAWo2AnwgFiAZaiASaiAXagwBCyAPIAkgCUEHcUHUnQFqLQAAIglBA3EiEnYiIEF/IBZBAnZBB3EiF3RBf3NxIBZBBXZqQQNqNgJ4IA9BfyAJQQJ2QQdxIhZ0QX9zICAgF3ZxIAlBBXZqQQNqNgJ8IBIgGWogF2ogFmoLIQkCQCAsIA8oAngiGU8EQCAPKAJ8IhIgLE0NAQsgIQRAQQAhByAdQQFBmfYAQQAQDwwHC0EAIQcgHUEBQZn2AEEAEA8MBgsgDyANIAlrNgLwASAPIGAgCa2INwPoASAHQfABcSAYQQ9xckH/AUH/ASAFQQRqIg0gFWtBAXR2IA0gFUwbIgkgCUHVAHEgH0EBShsiCUF/c3EEQCAhBEBBACEHIB1BAUGv2gBBABAPDAcLQQAhByAdQQFBr9oAQQAQDwwGCwJAAkAgHgRAIA9BwAFqEBshFyAPIA8oAtABIBkgAUETdEEfdWoiFms2AtABIA8gDykDyAEgFq2INwPIASAXQX8gFnRBf3NxIAFBCHZBAXEgFnRyQQFyQQJqIBN0IBdBH3RyIRYMAQtBACEWIAlBAXFFDQELIAYgFjYCAAsCQCABQSBxBEAgD0HAAWoQGyEXIA8gDygC0AEgGSABQRJ0QR91aiIWazYC0AEgDyAPKQPIASAWrYg3A8gBIAYgFUECdGogF0F/IBZ0QX9zcSABQQl2QQFxIBZ0ckEBciIWQQJqIBN0IBdBH3RyNgIAIAhBICAWZ2siFiAILQAAQf8AcSIXIBYgF0sbQYABcjoAAAwBCyAJQQJxRQ0AIAYgFUECdGpBADYCAAsgBkEEaiEXAkACQCABQcAAcQRAIA9BwAFqEBshGCAPIA8oAtABIBkgAUERdEEfdWoiFms2AtABIA8gDykDyAEgFq2INwPIASAYQX8gFnRBf3NxIAFBCnZBAXEgFnRyQQFyQQJqIBN0IBhBH3RyIRYMAQtBACEWIAlBBHFFDQELIBcgFjYCAAsgCEEAOgABAkAgAUGAAXEEQCAPQcABahAbIRggDyAPKALQASAZIAFBEHRBH3VqIhZrNgLQASAPIA8pA8gBIBatiDcDyAEgFyAVQQJ0aiAYQX8gFnRBf3NxIAFBC3ZBAXEgFnRyQQFyIgFBAmogE3QgGEEfdHI2AgAgCEGgfyABZ2s6AAEMAQsgCUEIcUUNACAXIBVBAnRqQQA2AgALIAZBCGohAQJAAkAgB0EQcQRAIA9BwAFqEBshGSAPIA8oAtABIBIgB0ETdEEfdWoiFms2AtABIA8gDykDyAEgFq2INwPIASAZQX8gFnRBf3NxIAdBCHZBAXEgFnRyQQFyQQJqIBN0IBlBH3RyIRcMAQtBACEXIAlBEHFFDQELIAEgFzYCAAsCQCAHQSBxBEAgD0HAAWoQGyEZIA8gDygC0AEgEiAHQRJ0QR91aiIWazYC0AEgDyAPKQPIASAWrYg3A8gBIAEgFUECdGogGUF/IBZ0QX9zcSAHQQl2QQFxIBZ0ckEBciIBQQJqIBN0IBlBH3RyNgIAIAhBICABZ2siASAILQABQf8AcSIWIAEgFksbQYABcjoAAQwBCyAJQSBxRQ0AIAEgFUECdGpBADYCAAsgBkEMaiEBAkACQCAHQcAAcQRAIA9BwAFqEBshGSAPIA8oAtABIBIgB0ERdEEfdWoiFms2AtABIA8gDykDyAEgFq2INwPIASAZQX8gFnRBf3NxIAdBCnZBAXEgFnRyQQFyQQJqIBN0IBlBH3RyIRcMAQtBACEXIAlBwABxRQ0BCyABIBc2AgALIAhBAmoiCEEAOgAAAkAgB0GAAXEEQCAPQcABahAbIRYgDyAPKALQASASIAdBEHRBH3VqIglrNgLQASAPIA8pA8gBIAmtiDcDyAEgASAVQQJ0aiAWQX8gCXRBf3NxIAdBC3ZBAXEgCXRyQQFyIgFBAmogE3QgFkEfdHI2AgAgCEGgfyABZ2s6AAAMAQsgCUGAAUkNACABIBVBAnRqQQA2AgALICJBEHMhIiADIAVBBHFqIQMgBkEQaiEGIA0gFUgNAAsLIApBCHEhOCAUQbAMaiEoIBRBoAhqISkgFEGQBGohJSAfQQNOBEAgFUEDbCE5IBVBAXQhOiAmQQFrISBBAyAmQQJrIgF0IS1BASABdCEuIBVBB2pBAXZB/P///wdxQQRqIT0gKyAkQX9zaiIBQQN2IgNBAnQiPkEEaiE7IANBAWoiP0H8////A3EiHEECdCE8IBxBA3QhEiABQRhJIUBBAiEZA0AgGSETIAstAAAhFiALQQA6AAAgIkFvcUECcyEiAkAgFUEATARAIBNBAmohGQwBCyAlIBQgE0EEcRshESATQQJqIRkgDiATIBVsQQJ0aiEIQQAhCiALIQZBACENA0AgDSEFIAYtAAFBBXZBBHEgCiAWQQd2cnIiA0EIdCAPQeABahAvQf8AcUEBdHJBoI0Bai8BACEBAkAgAw0AIAFBACACQQJrIgNBf0YbIQEgAkEBSgRAIAMhAgwBCyAPQYACahAoIQILIA8pA+gBIWUgDygC8AEhTCARIBEoAgAgAUEEdkEDcSABQQJ2QTBxciAidHIiCTYCACABQcAAcSIqQQV2IAFBgAFxIidBBnZyIQogTCABQQdxIgNrIRcgZSADrYgiYKchDUEAIRgCQCAVIAVBAnJMBEBBACEHDAELIAogBi0AAkEFdkEEcSAGLQABQQd2cnIiA0EIdCANQf8AcUEBdHJBoI0Bai8BACEHAkAgAw0AIAdBACACQQJrIgNBf0YbIQcgAkEBSgRAIAMhAgwBCyAPQYACahAoIQILIAdBBXYgB0EGdnJBAnEhCiAXIAdBB3EiA2shFyBgIAOtiCJgpyENCyARIAdBAnRBgAZxIAdBMHFyICJBBGp0IAlyNgIAQQEhCUEBIQMCQCAHQQJ2QQJxIAFBA3ZBAXFyIh5FDQAgDSANQQdxQdSdAWotAAAiA0EDcSINdiEJIB5BA0cEQEEBIAlBfyADQQJ2QQdxIhh0QX9zcSADQQV2akEBaiIDIB5BAUYiHhshCSADQQEgHhshAyANIBhqIRgMAQsgCUEHcUHUnQFqLQAAIh5BA3EiMyANIANBAnZBB3EiG2pqIB5BAnZBB3EiDWohGCAJIDN2IglBfyAbdEF/c3EgA0EFdmpBAWohA0F/IA10QX9zIAkgG3ZxIB5BBXZqQQFqIQkLIA8gFyAYazYC8AEgDyBgIBitiDcD6AEgAUHwAXEiDSANQQFrcQRAIAMgFkH/AHEiFiAGLQABQf8AcSIXIBYgF0sbIhZBAmsiF0EAIBYgF08baiEDCyAHQfABcSIXIBdBAWtxBEAgCSAGLQABQf8AcSIWIAYtAAJB/wBxIhggFiAYSxsiFkECa0EAIBZBAksbaiEJCyADICxNIAkgLE1xRQRAICEEQEEAIQcgHUEBQf32AEEAEA8MCQtBACEHIB1BAUH99gBBABAPDAgLIAYtAAIhFiAGQQA7AAEgFyANQQR2ckH/AUH/ASAFQQRqIg0gFWtBAXR2IA0gFUwbIhdB1QBxIBcgGSAfShsiGEF/c3EEQCAhBEBBACEHIB1BAUGv2gBBABAPDAkLQQAhByAdQQFBr9oAQQAQDwwICwJAAkAgAUEQcQRAIA9BwAFqEBshHiAPIA8oAtABIAMgAUETdEEfdWoiF2s2AtABIA8gDykDyAEgF62INwPIASAeQX8gF3RBf3NxIAFBCHZBAXEgF3RyQQFyQQJqICB0IB5BH3RyIRcMAQtBACEXIBhBAXFFDQELIAggFzYCAAsCQCABQSBxBEAgD0HAAWoQGyEeIA8gDygC0AEgAyABQRJ0QR91aiIXazYC0AEgDyAPKQPIASAXrYg3A8gBIAggFUECdGogHkF/IBd0QX9zcSABQQl2QQFxIBd0ckEBciIXQQJqICB0IB5BH3RyNgIAIAZBICAXZ2siFyAGLQAAQf8AcSIeIBcgHksbQYABcjoAAAwBCyAYQQJxRQ0AIAggFUECdGpBADYCAAsgCEEEaiEeAkACQCAqBEAgD0HAAWoQGyEbIA8gDygC0AEgAyABQRF0QR91aiIXazYC0AEgDyAPKQPIASAXrYg3A8gBIBtBfyAXdEF/c3EgAUEKdkEBcSAXdHJBAXJBAmogIHQgG0EfdHIhFwwBC0EAIRcgGEEEcUUNAQsgHiAXNgIACwJAICcEQCAPQcABahAbIRcgDyAPKALQASADIAFBEHRBH3VqIgNrNgLQASAPIA8pA8gBIAOtiDcDyAEgHiAVQQJ0aiAXQX8gA3RBf3NxIAFBC3ZBAXEgA3RyQQFyIgFBAmogIHQgF0EfdHI2AgAgBkGgfyABZ2s6AAEMAQsgGEEIcUUNACAeIBVBAnRqQQA2AgALIAhBCGohAQJAAkAgB0EQcQRAIA9BwAFqEBshFyAPIA8oAtABIAkgB0ETdEEfdWoiA2s2AtABIA8gDykDyAEgA62INwPIASAXQX8gA3RBf3NxIAdBCHZBAXEgA3RyQQFyQQJqICB0IBdBH3RyIQMMAQtBACEDIBhBEHFFDQELIAEgAzYCAAsCQCAHQSBxBEAgD0HAAWoQGyEXIA8gDygC0AEgCSAHQRJ0QR91aiIDazYC0AEgDyAPKQPIASADrYg3A8gBIAEgFUECdGogF0F/IAN0QX9zcSAHQQl2QQFxIAN0ckEBciIBQQJqICB0IBdBH3RyNgIAIAZBICABZ2siASAGLQABQf8AcSIDIAEgA0sbQYABcjoAAQwBCyAYQSBxRQ0AIAEgFUECdGpBADYCAAsgCEEMaiEBAkACQCAHQcAAcQRAIA9BwAFqEBshFyAPIA8oAtABIAkgB0ERdEEfdWoiA2s2AtABIA8gDykDyAEgA62INwPIASAXQX8gA3RBf3NxIAdBCnZBAXEgA3RyQQFyQQJqICB0IBdBH3RyIQMMAQtBACEDIBhBwABxRQ0BCyABIAM2AgALIAZBAmohBgJAIAdBgAFxBEAgD0HAAWoQGyEXIA8gDygC0AEgCSAHQRB0QR91aiIDazYC0AEgDyAPKQPIASADrYg3A8gBIAEgFUECdGogF0F/IAN0QX9zcSAHQQt2QQFxIAN0ckEBciIBQQJqICB0IBdBH3RyNgIAIAZBoH8gAWdrOgAADAELIBhBgAFJDQAgASAVQQJ0akEANgIACyAiQRBzISIgESAFQQRxaiERIAhBEGohCCANIBVIDQALCwJAIAxBAkkNACATQQJxRQ0AIBlBBHEhAwJAAn8CQAJAIDEEQCAUICUgAxshFkEAIRggFUEATA0BIA4gE0ECayAVbEECdGohEQNAIA9BgAFqEC8hB0EAIQEgFigCACIIBEAgESAYQQJ0aiEBQQAhCUEPIQYDQAJAIAYgCHFFDQAgBkGRosSIAXEiDSAIcQRAIAEgASgCACAHQX9zQQFxICB0cyAucjYCACAHQQF2IQcLIA1BAXQgCHEEQCABIBVBAnRqIgUgBSgCACAHQX9zQQFxICB0cyAucjYCACAHQQF2IQcLIA1BAnQgCHEEQCABIDpBAnRqIgUgBSgCACAHQX9zQQFxICB0cyAucjYCACAHQQF2IQcLIA1BA3QgCHFFDQAgASA5QQJ0aiINIA0oAgAgB0F/c0EBcSAgdHMgLnI2AgAgB0EBdiEHCyABQQRqIQEgBkEEdCEGIAlBAWoiCUEIRw0ACyAIaSEBCyAWQQRqIRYgDyAPKAKQASABazYCkAEgDyAPKQOIASABrYg3A4gBIBhBCGoiGCAVSA0ACwsgKSAoIAMbIQUgFCAlIAMbIRYgA0UhGCAVQQBMDQNBACEDIEANASAFIBYgO2pJIBYgBSA7aiIHSXENAUEAIAUiASAWIgYgPmpBCGpJIAZBBGogB0lxDQIaIAYgPGohBiABIDxqIQH9DAAAAAAAAAAAAAAAAAAAAAAhXkEAIQcDQCAFIAdBAnQiA2oiCSADIBZqIgP9AAIAIl9BBP2tASBfQQT9qwEgXiBf/Q0MDQ4PEBESExQVFhcYGRobQRz9rQH9UP1QIF/9UCJe/QsCACAJIF4gA/0AAgRBHP2rAf1QIl5BAf2tAf0Md3d3d3d3d3d3d3d3d3d3d/1OIF5BAf2rAf0M7u7u7u7u7u7u7u7u7u7u7v1O/VAgXv1QIF/9T/0LAgAgXyFeIAdBBGoiByAcRw0ACyAcID9GDQMgEiEDIF79GwMMAgsgA0UhGCApICggAxshBQwCCyAFIQEgFiEGQQALIQcDQCAHQRx2IQkgASAGKAIAIgdBBHYgCSAHQQR0cnIgB3IiCTYCACABIAkgBigCBEEcdHIiCUEBdkH37t27B3EgCUEBdEHu3bv3fnFyIAlyIAdBf3NxNgIAIAFBBGohASAGQQRqIQYgA0EIaiIDIBVIDQALCyATQQZJDQBBACEJQQAhESAWIQEgKSAoIBgbIhshByAUICUgGBsiFyEGAkAgFUEATCINDQADQCABQQRqIQMgBygCACEIIAEoAgAhASAHIDgEfyAIBSABQQR0IBFBHHZyIAFBBHZyIAMoAgBBHHRyIAFyQQN0QYiRosR4cSAIcgsgBigCAEF/c3E2AgAgBkEEaiEGIAdBBGohByABIREgAyEBIAlBCGoiCSAVSA0ACyANDQAgDiATQQZrIBVsQQJ0aiFBQQAhHiAXIREDQEEAIQMgGygCACIBBEAgFSAeayFCQQAhB0EAIQoDQCAHIU0gD0GgAWoQGyEHAkAgCiAKQQRqIgYgQiAGIB5qIBVIGyIzTiJDBEBBACEGDAELIBEoAgBBf3MhKiBBIAogHnJBAnRqIRhBACEGQQ8gCiIJQQJ0IkR0Ig0hCANAAkAgASAIcUUNACAIQZGixIgBcSInIAFxBEAgB0EBcQRAIAMgJ3IhA0EyIAlBAnR0ICpxIAFyIQELIAdBAXYhByAGQQFqIQYLIAEgJ0EBdCI0cQRAIAdBAXEEQCADIDRyIQMgAUH0ACAJQQJ0dCAqcXIhAQsgB0EBdiEHIAZBAWohBgsgASAnQQJ0IjRxBEAgB0EBcQRAIAMgNHIhAyABQegBIAlBAnR0ICpxciEBCyAHQQF2IQcgBkEBaiEGCyABICdBA3QiJ3FFDQAgB0EBcQRAIAMgJ3IhAyABQcABIAlBAnR0ICpxciEBCyAGQQFqIQYgB0EBdiEHCyAIQQR0IQggCUEBaiIJIDNIDQALIAMgRHZB//8DcUUNACBDDQADQAJAIAMgDXFFDQAgDUGRosSIAXEiCSADcQRAIBggGCgCACAHQR90ciAtcjYCACAHQQF2IQcgBkEBaiEGCyAJQQF0IANxBEAgGCAVQQJ0aiIIIAgoAgAgB0EfdHIgLXI2AgAgB0EBdiEHIAZBAWohBgsgCUECdCADcQRAIBggOkECdGoiCCAIKAIAIAdBH3RyIC1yNgIAIAdBAXYhByAGQQFqIQYLIAlBA3QgA3FFDQAgGCA5QQJ0aiIJIAkoAgAgB0EfdHIgLXI2AgAgBkEBaiEGIAdBAXYhBwsgDUEEdCENIBhBBGohGCAKQQFqIgogM0gNAAsLIA8gDygCsAEgBms2ArABIA8gDykDqAEgBq2INwOoAUEBIQdBBCEKIE1BAXFFDQALIBsgGygCBCADQRt2QQ5xIANBHXZyIANBHHZyIBEoAgRBf3NxcjYCBAsgESgCACADciIDQQN2QZGixIgBcSIBQQR2IAFBBHRyIAFyIQYgHgRAIAVBBGsiByAHKAIAIBZBBGsoAgBBf3MgAUEcdHFyNgIACyAFIAUoAgAgBiAWKAIAQX9zcXI2AgAgBSAFKAIEIBYoAgRBf3MgA0EfdnFyNgIEIBtBBGohGyARQQRqIREgBUEEaiEFIBZBBGohFiAeQQhqIh4gFUgNAAsLIBdBACA9EBUaCyAZIB9IDQALCwJAIAxBAkkNAAJAIB9BA3FBAWsiFkECSSAxcQRAIBVBAEwNAUEBICZBAmt0IQIgDiAfQfz//wdxIBVsQQJ0aiERICUgFCAfQQRxGyEFICZBAWshCEEAIQogFUEMbCEMIBVBA3QhCwNAIA9BgAFqEC8hB0EAIQEgBSgCACIDBEAgESAKQQJ0aiEBQQ8hBkEAIQkDQAJAIAMgBnFFDQAgBkGRosSIAXEiDSADcQRAIAEgASgCACAHQX9zQQFxIAh0cyACcjYCACAHQQF2IQcLIA1BAXQgA3EEQCABIBVBAnRqIh0gHSgCACAHQX9zQQFxIAh0cyACcjYCACAHQQF2IQcLIA1BAnQgA3EEQCABIAtqIh0gHSgCACAHQX9zQQFxIAh0cyACcjYCACAHQQF2IQcLIA1BA3QgA3FFDQAgASAMaiINIA0oAgAgB0F/c0EBcSAIdHMgAnI2AgAgB0EBdiEHCyABQQRqIQEgBkEEdCEGIAlBAWoiCUEIRw0ACyADaSEBCyAFQQRqIQUgDyAPKAKQASABazYCkAEgDyAPKQOIASABrYg3A4gBIApBCGoiCiAVSA0ACwsgFkEBSw0AIBVBAEwNACAlIBQgH0EEcSIBGyEJICggKSABGyECQQAhAwJ/AkAgKyAkQX9zaiIBQThJDQAgAiAJIAFBAXZB/P///wdxIgZBBGoiB2pJIAkgAiAHaiIHSXENACACIAYgCWpBCGpJIAlBBGogB0lxDQAgAUEDdkEBaiINQfz///8DcSIIQQN0IQMgCSAIQQJ0IgFqIQYgASACaiEB/QwAAAAAAAAAAAAAAAAAAAAAIV5BACEHA0AgAiAHQQJ0IhZqIhEgCSAWaiIW/QACACJfQQT9rQEgX0EE/asBIF4gX/0NDA0ODxAREhMUFRYXGBkaG0Ec/a0B/VD9UCBf/VAiXv0LAgAgESBeIBb9AAIEQRz9qwH9UCJeQQH9rQH9DHd3d3d3d3d3d3d3d3d3d3f9TiBeQQH9qwH9DO7u7u7u7u7u7u7u7u7u7u79Tv1QIF79UCBf/U/9CwIAIF8hXiAHQQRqIgcgCEcNAAsgCCANRg0CIF79GwMMAQsgAiEBIAkhBkEACyEHA0AgB0EcdiEJIAEgBigCACIHQQR2IAkgB0EEdHJyIAdyIgk2AgAgASAJIAYoAgRBHHRyIglBAXZB9+7duwdxIAlBAXRB7t27935xciAJciAHQX9zcTYCACABQQRqIQEgBkEEaiEGIANBCGoiAyAVSA0ACwsgHyAfQQFqQQNxa0EDa0EAIB9BBkobIhEgH04NAEEDICZBAmt0IRkgKyAkQX9zaiIBQQN2IgNBAnQiK0EEaiEdIANBAWoiA0H8////A3EiEkECdCEhIBJBA3QhFiAVQQxsISwgFUEDdCEtIAFBGEkhJiADIBJGIRsDQAJAAkACQAJAAn8CQCAfIBFrIgFBAWsiA0EDTwRAQX8hFyABQQVIDQUgFUEATA0GICUgFCARQQRxIgEbIQIgKCApIAEbIQkgOARAQQAhBiAmDQQgAiAJIB1qSSACIB1qIAlLcQ0EIAIgIWohASAJICFqIQcDQCAJIAZBAnQiA2oiCCAI/QACACACIANq/QACAP1P/QsCACAGQQRqIgYgEkcNAAsgFiEGIBsNBgwFCyAUICUgARshDUEAIQMgJg0BIAkgDSAdakkgDSAJIB1qIgFJcQ0BIAkgDSArakEIakkgDUEEaiABSXENASAJIAIgHWpJIAEgAktxDQEgAiAhaiEIIAkgIWohASANICFqIQf9DAAAAAAAAAAAAAAAAAAAAAAhXkEAIQYDQCAJIAZBAnQiA2oiBSADIA1qIgz9AAIAIl9BBP2tASBfQQT9qwEgXiBf/Q0MDQ4PEBESExQVFhcYGRobQRz9rQH9UP1QIAz9AAIEQRz9qwH9UCBf/VBBA/2rAf0MiIiIiIiIiIiIiIiIiIiIiP1OIAX9AAIA/VAgAiADav0AAgD9T/0LAgAgXyFeIAZBBGoiBiASRw0ACyAbDQUgFiEDIF79GwMMAgsgA0ECdEHcnQFqKAIAIRcMBAsgDSEHIAkhASACIQhBAAshBgNAIAZBHHYhCSABIAEoAgAgBygCACIGQQR2IAkgBkEEdHJyIAcoAgRBHHRyIAZyQQN0QYiRosR4cXIgCCgCAEF/c3E2AgAgCEEEaiEIIAFBBGohASAHQQRqIQcgA0EIaiIDIBVIDQALDAILIAkhByACIQELA0AgByAHKAIAIAEoAgBBf3NxNgIAIAFBBGohASAHQQRqIQcgBkEIaiIGIBVIDQALCyAVQQBMDQAgJSAUIBFBBHEiARshCiAoICkgARshAiAUICUgARshEyApICggARshHiAOIBEgFWxBAnRqIS5BACEFA0BBACEDIAIoAgAgF3EiAQRAIBUgBWshKkEAIQdBACENA0AgByFOIA9BoAFqEBshBwJAIA0gDUEEaiIGICogBSAGaiAVSBsiJE4iJwRAQQAhBgwBCyAXIAooAgBBf3NxIRggLiAFIA1yQQJ0aiELQQAhBkEPIA0iCUECdCIcdCIgIQgDQAJAIAEgCHFFDQAgCEGRosSIAXEiIiABcQRAIAdBAXEEQCADICJyIQNBMiAJQQJ0dCAYcSABciEBCyAHQQF2IQcgBkEBaiEGCyABICJBAXQiMXEEQCAHQQFxBEAgAyAxciEDIAFB9AAgCUECdHQgGHFyIQELIAdBAXYhByAGQQFqIQYLIAEgIkECdCIxcQRAIAdBAXEEQCADIDFyIQMgAUHoASAJQQJ0dCAYcXIhAQsgB0EBdiEHIAZBAWohBgsgASAiQQN0IiJxRQ0AIAdBAXEEQCADICJyIQMgAUHAASAJQQJ0dCAYcXIhAQsgBkEBaiEGIAdBAXYhBwsgCEEEdCEIIAlBAWoiCSAkSA0ACyADIBx2Qf//A3FFDQAgJw0AA0ACQCADICBxRQ0AICBBkaLEiAFxIgkgA3EEQCALIAsoAgAgB0EfdHIgGXI2AgAgB0EBdiEHIAZBAWohBgsgCUEBdCADcQRAIAsgFUECdGoiCCAIKAIAIAdBH3RyIBlyNgIAIAdBAXYhByAGQQFqIQYLIAlBAnQgA3EEQCALIC1qIgggCCgCACAHQR90ciAZcjYCACAHQQF2IQcgBkEBaiEGCyAJQQN0IANxRQ0AIAsgLGoiCSAJKAIAIAdBH3RyIBlyNgIAIAZBAWohBiAHQQF2IQcLICBBBHQhICALQQRqIQsgDUEBaiINICRIDQALCyAPIA8oArABIAZrNgKwASAPIA8pA6gBIAatiDcDqAFBASEHQQQhDSBOQQFxRQ0ACyACIAIoAgQgA0EbdkEOcSADQR12ciADQRx2ciAKKAIEQX9zcXI2AgQLIAooAgAgA3IiA0EDdkGRosSIAXEiAUEEdiABQQR0ciABciEGIAUEQCAeQQRrIgcgBygCACATQQRrKAIAQX9zIAFBHHRxcjYCAAsgHiAeKAIAIAYgEygCAEF/c3FyNgIAIB4gHigCBCATKAIEQX9zIANBH3ZxcjYCBCACQQRqIQIgCkEEaiEKIB5BBGohHiATQQRqIRMgBUEIaiIFIBVIDQALCyARQQRqIhEgH0gNAAsLQQEhByAfQQBMDQMgFUEATA0DIBVB/P///wdxIgZBAnQhAiAVQQRJIQhBACEJA0AgDiAJIBVsQQJ0aiEDAkACQCAIBEAgAyEHQQAhAQwBCyACIANqIQdBACEBA0AgAyABQQJ0aiINIA39AAIAIl79DP///3////9/////f////3/9TiJf/aEBIF8gXv0MAAAAAAAAAAAAAAAAAAAAAP05/VL9CwIAIAFBBGoiASAGRw0ACyAGIgEgFUYNAQsDQCAHQQAgBygCACIDQf////8HcSINayANIANBAEgbNgIAIAdBBGohByABQQFqIgEgFUcNAAsLQQEhByAJQQFqIgkgH0cNAAsMAwsgIUUNACAPIBooAhg2AjQgDyAWNgIwIB1BAUHcxwAgD0EwahAPDAELIA8gATYCFCAPIBY2AhAgHUEBQdzHACAPQRBqEA9BACEHDAELQQAhBwsgD0GwAmokACAHDQEMAwsgBCABQQl0QdCpAWo2AmwCfyAEKAJ0IQECQAJAIBooAhAgGigCCGsiBSAaKAIUIBooAgxrIglsIgMgBCgChAFLBEAgARAQIAQgA0ECdBAYIgE2AnRBACABRQ0DGiAEIAM2AoQBDAELIAFFDQELIAFBACADQQJ0EBUaCyAEKAJ4IQECQCAFQQJqIgYgCUEDakECdiIMQQJqbCIDIAQoAogBTQRAIANBAnQhCAwBCyABEBAgBCADQQJ0IggQGCIBNgJ4IAENAEEADAELIAQgAzYCiAEgAUEAIAgQFRoCQCAGRQ0AIAQoAngiByEBAkAgBkEETwRAIAcgBkF8cSINQQJ0aiEBQQAhCANAIAcgCEECdGr9DAAAIEkAACBJAAAgSQAAIEn9CwIAIAhBBGoiCCANRw0ACyAGIA1GDQELA0AgAUGAgIDJBDYCACABQQRqIQEgDUEBaiINIAZHDQALCyAHIAxBAWogBmxBAnRqIQNBACENAkACQCAGQQRJBEAgAyEBDAELIAMgBkF8cSINQQJ0aiEBQQAhCANAIAMgCEECdGr9DAAAIEkAACBJAAAgSQAAIEn9CwIAIAhBBGoiCCANRw0ACyAGIA1GDQELA0AgAUGAgIDJBDYCACABQQRqIQEgDUEBaiINIAZHDQALCyAJQQNxIgFFDQAgBkUNAEGAgIDIBEGAgIDABEGAgICABCABQQJGGyABQQFGGyELIAcgBiAMbEECdGohA0EAIQ0CQCAGQQRJBEAgAyEBDAELIAMgBkF8cSINQQJ0aiEBIAv9ESFfQQAhCANAIAMgCEECdGogX/0LAgAgCEEEaiIIIA1HDQALIAYgDUYNAQsDQCABIAs2AgAgAUEEaiEBIA1BAWoiDSAGRw0ACwsgBCAJNgKAASAEIAU2AnxBAQtFDQIgGigCHCARaiIZQR9OBEAgIUUNAiAjIBk2AhAgHUECQdXBACAjQRBqEA8MAwsgBBBaQQAhASAEQbCpATYCZCAEQdCeATYCYCAEQfCeATYCHAJAAkACQAJAIBooAjQiB0EBSw0AIAQoApABRQ0CIAcNAAwBCyAaKAIEIQMgB0EETwRAIAdBfHEhAkEAIQYDQCADIAZBA3RqIgFBHGogAUEUaiABQQxqIAH9CQIE/VYCAAH9VgIAAv1WAgADIF79rgEhXiAGQQRqIgYgAkcNAAsgXiBeIF79DQgJCgsMDQ4PAAECAwABAgP9rgEiXiBeIF79DQQFBgcAAQIDAAECAwABAgP9rgH9GwAhASACIAdGDQELA0AgAyACQQN0aigCBCABaiEBIAJBAWoiAiAHRw0ACwsgAUECaiIDIAQoApgBSwRAIAQoApQBIAMQFyIGRQ0FIAQgBjYClAEgASAGakEAOwAAIAQgAzYCmAEgGigCNCEHCyAEKAKUASEeIAdFDQEgGigCBCEGQQAhAkEAIQEDQCACIB5qIAYgAUEDdCIDaiIGKAIAIAYoAgQQEhogGigCBCIGIANqKAIEIAJqIQIgAUEBaiIBIBooAjRJDQALDAELIAdBAUcNASAaKAIEKAIAIR4LIBooAjwiAQRAIAQoAnQhLCAEIAE2AnQLIBooAiwEQCAWQQhxISUgBEEcaiEPIBZBAXEhLSAWQQJxRSEuQQIhHwNAIB4gKGohASAaKAIAIClBGGxqIiAoAgAhAwJAIC0gH0ECSSAZIBooAhxBBGtMcXEiIgRAIAQgATYCFCAEIAEgA2oiAzYCGCAEIAMvAAA7AXAgA0H/AToAACAEKAIYQf8BOgABIARBADYCCCAEQQA2AgAgBCABNgIQDAELIAQgATYCFCAEIAEgA2oiBjYCGCAEIAYvAAA7AXAgBkH/AToAACAEKAIYQf8BOgABIAQgBEEcajYCaCAEIAE2AhAgBEEANgIMIAQgAwR/IAEtAABBEHQFQYCA/AcLIgM2AgBBASEGIAFBAWohCSABLQABIQcCfyABLQAAQf8BRgRAIAdBkAFPBEAgBEEBNgIMIANBgP4DcgwCCyAEIAk2AhBBACEGIAdBCXQgA2oMAQsgBCAJNgIQIAdBCHQgA3ILIQEgBCAGNgIIIARBgIACNgIEIAQgAUEHdDYCAAsgICgCACEqAkAgGUEATA0AICAoAghFDQAgIiAuciEnQQAhJgNAAkACQAJAAkACQCAfQQFrDgIBAgALICIEQEEBIBl0IgFBAXYgAXIhESAEKAJ8IgVBAnQiDSAEKAJ4akEMaiEBIAQoAnQhBkEAIQggBCgCgAEiA0EETwRAIAVFDQUgBUEDbCECIAVBAXQhDEEAIBFrIQkDQCAMQQJ0IQtBACEDA0ACQCABIgcoAgAiAUUNAAJAIAFBkICAAXENACABQe8DcUUNACAEKAIAIQECQCAEKAIIIhANACABQf8BRiEKIAQoAhAiEC0AACEBAkAgCkUEQCAEIAE2AgAgBCAQQQFqNgIQDAELIAFBjwFNBEAgBCABNgIAIAQgEEEBajYCEEEHIRAMAgtB/wEhASAEQf8BNgIAC0EIIRALIAQgEEEBayIQNgIIAkAgASAQdkEBcUUNAAJAIBANACABQf8BRiEKIAQoAhAiEC0AACEBAkAgCkUEQCAEIAE2AgAgBCAQQQFqNgIQDAELIAFBjwFNBEAgBCABNgIAIAQgEEEBajYCEEEHIRAMAgtB/wEhASAEQf8BNgIAC0EIIRALIAQgEEEBayIQNgIIIAYgCSARIAEgEHZBAXEiEBs2AgAgBCgCfCEBIAdBBGsiCiAKKAIAQSByNgIAIAcgBygCBEEIcjYCBCAHIAcoAgAgEEETdHJBEHI2AgAgJQ0AIAdBfiABa0ECdGoiASABKAIEQYCAAnI2AgQgASABKAIAIBBBH3RyQYCABHI2AgAgAUEEayIBIAEoAgBBgIAIcjYCAAsgByAHKAIAQYCAgAFyIgE2AgALAkAgAUGAgYAIcQ0AIAFB+B5xRQ0AIAQoAgAhAQJAIAQoAggiEA0AIAFB/wFGIQogBCgCECIQLQAAIQECQCAKRQRAIAQgATYCACAEIBBBAWo2AhAMAQsgAUGPAU0EQCAEIAE2AgAgBCAQQQFqNgIQQQchEAwCC0H/ASEBIARB/wE2AgALQQghEAsgBCAQQQFrIhA2AgggBwJ/IAEgEHZBAXFFBEAgBygCAAwBCwJAIBANACABQf8BRiEKIAQoAhAiEC0AACEBAkAgCkUEQCAEIAE2AgAgBCAQQQFqNgIQDAELIAFBjwFNBEAgBCABNgIAIAQgEEEBajYCEEEHIRAMAgtB/wEhASAEQf8BNgIAC0EIIRALIAQgEEEBayIQNgIIIAYgDWogCSARIAEgEHZBAXEiARs2AgAgB0EEayIQIBAoAgBBgAJyNgIAIAcgBygCBEHAAHI2AgQgBygCACABQRZ0ckGAAXILQYCAgAhyIgE2AgALAkAgAUGAiIDAAHENACABQcD3AXFFDQAgBCgCACEBAkAgBCgCCCIQDQAgAUH/AUYhCiAEKAIQIhAtAAAhAQJAIApFBEAgBCABNgIAIAQgEEEBajYCEAwBCyABQY8BTQRAIAQgATYCACAEIBBBAWo2AhBBByEQDAILQf8BIQEgBEH/ATYCAAtBCCEQCyAEIBBBAWsiEDYCCCAHAn8gASAQdkEBcUUEQCAHKAIADAELAkAgEA0AIAFB/wFGIQogBCgCECIQLQAAIQECQCAKRQRAIAQgATYCACAEIBBBAWo2AhAMAQsgAUGPAU0EQCAEIAE2AgAgBCAQQQFqNgIQQQchEAwCC0H/ASEBIARB/wE2AgALQQghEAsgBCAQQQFrIhA2AgggBiALaiAJIBEgASAQdkEBcSIBGzYCACAHQQRrIhAgECgCAEGAEHI2AgAgByAHKAIEQYAEcjYCBCAHKAIAIAFBGXRyQYAIcgtBgICAwAByIgE2AgALIAFBgMCAgARxDQAgAUGAvA9xRQ0AIAQoAgAhAQJAIAQoAggiEA0AIAFB/wFGIQogBCgCECIQLQAAIQECQCAKRQRAIAQgATYCACAEIBBBAWo2AhAMAQsgAUGPAU0EQCAEIAE2AgAgBCAQQQFqNgIQQQchEAwCC0H/ASEBIARB/wE2AgALQQghEAsgBCAQQQFrIhA2AgggASAQdkEBcQRAIAYgAkECdGohTwJAIBANACABQf8BRiEUIAQoAhAiEC0AACEBAkAgFEUEQCAEIAE2AgAgBCAQQQFqNgIQDAELIAFBjwFNBEAgBCABNgIAIAQgEEEBajYCEEEHIRAMAgtB/wEhASAEQf8BNgIAC0EIIRALIAQgEEEBayIQNgIIIE8gCSARIAEgEHZBAXEiEBs2AgAgBCgCfCEBIAdBBGsiCiAKKAIAQYCAAXI2AgAgByAHKAIEQYAgcjYCBCAHIAcoAgAgEEEcdHJBgMAAcjYCACAHIAFBAnRqIgEgASgCBEEEcjYCBCABIAEoAgxBAXI2AgwgASABKAIIIBBBEnRyQQJyNgIICyAHIAcoAgBBgICAgARyNgIACyAGQQRqIQYgB0EEaiEBIANBAWoiAyAFRw0ACyAHQQxqIQEgBiACQQJ0aiEGIAhBBGoiCCAEKAKAASIDQXxxSQ0ACwsgAyAITQ0DIAVFDQNBACETQQAgEWshCyADIRADQAJAIAggEEYEQCAIIRAMAQsgAUEEayEMIAEoAgAhDUEAIQIDQAJAIA0gAkEDbCIHdiIJQZCAgAFxDQAgCUHvA3FFDQAgBCgCACEDAkAgBCgCCCIJDQAgA0H/AUchECAEKAIQIgktAAAhAwJAIBBFBEAgA0GQAU8EQEH/ASEDIARB/wE2AgAMAgsgBCADNgIAIAQgCUEBajYCEEEHIQkMAgsgBCADNgIAIAQgCUEBajYCEAtBCCEJCyAEIAlBAWsiCTYCCAJAIAMgCXZBAXFFDQAgBiACIAVsQQJ0aiFQAkAgCQ0AIANB/wFHIQ0gBCgCECIJLQAAIQMCQCANRQRAIANBkAFPBEBB/wEhAyAEQf8BNgIADAILIAQgAzYCACAEIAlBAWo2AhBBByEJDAILIAQgAzYCACAEIAlBAWo2AhALQQghCQsgBCAJQQFrIgk2AgggUCALIBEgAyAJdkEBcSIJGzYCACAEKAJ8IRAgDCAMKAIAQSAgB3RyNgIAIAEgASgCACAJQRN0QRByIAd0cjYCACABIAEoAgRBCCAHdHI2AgQgAiAlckUEQCABQX4gEGtBAnRqIgMgAygCBEGAgAJyNgIEIAMgAygCACAJQR90ckGAgARyNgIAIANBBGsiAyADKAIAQYCACHI2AgALIAJBA0cNACABIBBBAnRqIgMgAygCBEEEcjYCBCADIAMoAgxBAXI2AgwgAyADKAIIIAlBEnRyQQJyNgIICyABIAEoAgBBgICAASAHdHIiDTYCACAEKAKAASEDCyADIRAgAkEBaiICIAMgCGtJDQALCyAGQQRqIQYgAUEEaiEBIBNBAWoiEyAFRw0ACwwDC0EAIQdBACENQQAhFwJAAkACQAJAIAQoAnwiEEHAAEcNACAEKAKAAUHAAEcNAEEAQQEgGXQiAUEBdiABciIRayEFIARBHGohECAEKAJ4QYwCaiEGIAQoAgghCCAEKAIEIQMgBCgCACECIAQoAmghDCAEKAJ0IQEgFkEIcQ0BA0BBACEXA0AgASEJIAYiBygCACIGBEACQCAGQZCAgAFxDQAgBkHvA3EiAUUNACADIBAgBCgCbCABai0AAEECdGoiDCgCACILKAIAIgFrIQMCfyABIAJBEHZLBEAgCygCBCEKIAwgC0EIQQwgASADSyIUG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCyAILQABIQMgCC0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgMAgsgBCALNgIQIANBCXQgAmohAkEHIQgMAQsgBCALNgIQQQghCCADQQh0IAJqIQILIAhBAWshCCACQQF0IQIgAUEBdCIBQYCAAkkNAAsgASEDIAogCkUgFBsMAQsgAiABQRB0ayECIANBgIACcUUEQCALKAIEIQogDCALQQxBCCABIANLIhQbaigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiELIAgtAAEhASAILQAAQf8BRgRAIAFBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAwCCyAEIAs2AhAgAUEJdCACaiECQQchCAwBCyAEIAs2AhBBCCEIIAFBCHQgAmohAgsgCEEBayEIIAJBAXQhAiADQQF0IgNBgIACSQ0ACyAKRSAKIBQbDAELIAsoAgQLBH8gAyAQIAcoAgRBEXZBBHEgB0EEayIKKAIAQRN2QQFxIAZBDnZBEHEgBkEQdkHAAHEgBkGqAXFycnJyIhRB0LkBai0AAEECdGoiDCgCACILKAIAIgFrIQMgFEHQuwFqLQAAIRMgCSAFIBECfyABIAJBEHZLBEAgCygCBCEUIAwgC0EIQQwgASADSyIOG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCyAILQABIQMgCC0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgMAgsgBCALNgIQIANBCXQgAmohAkEHIQgMAQsgBCALNgIQQQghCCADQQh0IAJqIQILIAhBAWshCCACQQF0IQIgAUEBdCIBQYCAAkkNAAsgASEDIBQgFEUgDhsMAQsgAiABQRB0ayECIANBgIACcUUEQCALKAIEIRQgDCALQQxBCCABIANLIg4baigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiELIAgtAAEhASAILQAAQf8BRgRAIAFBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAwCCyAEIAs2AhAgAUEJdCACaiECQQchCAwBCyAEIAs2AhBBCCEIIAFBCHQgAmohAgsgCEEBayEIIAJBAXQhAiADQQF0IgNBgIACSQ0ACyAURSAUIA4bDAELIAsoAgQLIBNzIgEbNgIAIAogCigCAEEgcjYCACAHIAcoAgRBCHI2AgQgB0GMAmsiCyALKAIAQYCACHI2AgAgB0GEAmsiCyALKAIAQYCAAnI2AgAgB0GIAmsiCyALKAIAIAFBH3RyQYCABHI2AgAgBiABQRN0ckEQcgUgBgtBgICAAXIhBgsCQCAGQYCBgAhxDQAgBkH4HnFFDQAgAyAQIAQoAmwgBkEDdiIUQe8DcWotAABBAnRqIgwoAgAiCygCACIBayEDAn8gASACQRB2SwRAIAsoAgQhCiAMIAtBCEEMIAEgA0siExtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQsgCC0AASEDIAgtAABB/wFGBEAgA0GQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEIDAILIAQgCzYCECADQQl0IAJqIQJBByEIDAELIAQgCzYCEEEIIQggA0EIdCACaiECCyAIQQFrIQggAkEBdCECIAFBAXQiAUGAgAJJDQALIAEhAyAKIApFIBMbDAELIAIgAUEQdGshAiADQYCAAnFFBEAgCygCBCEKIAwgC0EMQQggASADSyITG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCyAILQABIQEgCC0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgMAgsgBCALNgIQIAFBCXQgAmohAkEHIQgMAQsgBCALNgIQQQghCCABQQh0IAJqIQILIAhBAWshCCACQQF0IQIgA0EBdCIDQYCAAkkNAAsgCkUgCiATGwwBCyALKAIECwR/IAMgECAHKAIEQRR2QQRxIAdBBGsiCigCAEEWdkEBcSAGQQ92QRBxIAZBE3ZBwABxIBRBqgFxcnJyciIUQdC5AWotAABBAnRqIgwoAgAiCygCACIBayEDIBRB0LsBai0AACETIAkgBSARAn8gASACQRB2SwRAIAsoAgQhFCAMIAtBCEEMIAEgA0siDhtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQsgCC0AASEDIAgtAABB/wFGBEAgA0GQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEIDAILIAQgCzYCECADQQl0IAJqIQJBByEIDAELIAQgCzYCEEEIIQggA0EIdCACaiECCyAIQQFrIQggAkEBdCECIAFBAXQiAUGAgAJJDQALIAEhAyAUIBRFIA4bDAELIAIgAUEQdGshAiADQYCAAnFFBEAgCygCBCEUIAwgC0EMQQggASADSyIOG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCyAILQABIQEgCC0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgMAgsgBCALNgIQIAFBCXQgAmohAkEHIQgMAQsgBCALNgIQQQghCCABQQh0IAJqIQILIAhBAWshCCACQQF0IQIgA0EBdCIDQYCAAkkNAAsgFEUgFCAOGwwBCyALKAIECyATcyIBGzYCgAIgCiAKKAIAQYACcjYCACAHIAcoAgRBwAByNgIEIAYgAUEWdHJBgAFyBSAGC0GAgIAIciEGCwJAIAZBgIiAwABxDQAgBkHA9wFxRQ0AIAMgECAEKAJsIAZBBnYiFEHvA3FqLQAAQQJ0aiIMKAIAIgsoAgAiAWshAwJ/IAEgAkEQdksEQCALKAIEIQogDCALQQhBDCABIANLIhMbaigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiELIAgtAAEhAyAILQAAQf8BRgRAIANBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAwCCyAEIAs2AhAgA0EJdCACaiECQQchCAwBCyAEIAs2AhBBCCEIIANBCHQgAmohAgsgCEEBayEIIAJBAXQhAiABQQF0IgFBgIACSQ0ACyABIQMgCiAKRSATGwwBCyACIAFBEHRrIQIgA0GAgAJxRQRAIAsoAgQhCiAMIAtBDEEIIAEgA0siExtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQsgCC0AASEBIAgtAABB/wFGBEAgAUGQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEIDAILIAQgCzYCECABQQl0IAJqIQJBByEIDAELIAQgCzYCEEEIIQggAUEIdCACaiECCyAIQQFrIQggAkEBdCECIANBAXQiA0GAgAJJDQALIApFIAogExsMAQsgCygCBAsEfyADIBAgBygCBEEXdkEEcSAHQQRrIgooAgBBGXZBAXEgBkESdkEQcSAGQRZ2QcAAcSAUQaoBcXJycnIiFEHQuQFqLQAAQQJ0aiIMKAIAIgsoAgAiAWshAyAUQdC7AWotAAAhEyAJIAUgEQJ/IAEgAkEQdksEQCALKAIEIRQgDCALQQhBDCABIANLIg4baigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiELIAgtAAEhAyAILQAAQf8BRgRAIANBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAwCCyAEIAs2AhAgA0EJdCACaiECQQchCAwBCyAEIAs2AhBBCCEIIANBCHQgAmohAgsgCEEBayEIIAJBAXQhAiABQQF0IgFBgIACSQ0ACyABIQMgFCAURSAOGwwBCyACIAFBEHRrIQIgA0GAgAJxRQRAIAsoAgQhFCAMIAtBDEEIIAEgA0siDhtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQsgCC0AASEBIAgtAABB/wFGBEAgAUGQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEIDAILIAQgCzYCECABQQl0IAJqIQJBByEIDAELIAQgCzYCEEEIIQggAUEIdCACaiECCyAIQQFrIQggAkEBdCECIANBAXQiA0GAgAJJDQALIBRFIBQgDhsMAQsgCygCBAsgE3MiARs2AoAEIAogCigCAEGAEHI2AgAgByAHKAIEQYAEcjYCBCAGIAFBGXRyQYAIcgUgBgtBgICAwAByIQYLAkAgBkGAwICABHENACAGQYC8D3FFDQAgAyAQIAQoAmwgBkEJdiIUQe8DcWotAABBAnRqIgwoAgAiCygCACIBayEDAn8gASACQRB2SwRAIAsoAgQhCiAMIAtBCEEMIAEgA0siExtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQsgCC0AASEDIAgtAABB/wFGBEAgA0GQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEIDAILIAQgCzYCECADQQl0IAJqIQJBByEIDAELIAQgCzYCEEEIIQggA0EIdCACaiECCyAIQQFrIQggAkEBdCECIAFBAXQiAUGAgAJJDQALIAEhAyAKIApFIBMbDAELIAIgAUEQdGshAiADQYCAAnFFBEAgCygCBCEKIAwgC0EMQQggASADSyITG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCyAILQABIQEgCC0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgMAgsgBCALNgIQIAFBCXQgAmohAkEHIQgMAQsgBCALNgIQQQghCCABQQh0IAJqIQILIAhBAWshCCACQQF0IQIgA0EBdCIDQYCAAkkNAAsgCkUgCiATGwwBCyALKAIECwR/IAMgECAHKAIEQRp2QQRxIAdBBGsiCigCAEEcdkEBcSAGQRV2QRBxIAZBGXZBwABxIBRBqgFxcnJyciIUQdC5AWotAABBAnRqIgwoAgAiCygCACIBayEDIBRB0LsBai0AACETIAkgBSARAn8gASACQRB2SwRAIAsoAgQhFCAMIAtBCEEMIAEgA0siDhtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQsgCC0AASEDIAgtAABB/wFGBEAgA0GQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEIDAILIAQgCzYCECADQQl0IAJqIQJBByEIDAELIAQgCzYCEEEIIQggA0EIdCACaiECCyAIQQFrIQggAkEBdCECIAFBAXQiAUGAgAJJDQALIAEhAyAUIBRFIA4bDAELIAIgAUEQdGshAiADQYCAAnFFBEAgCygCBCEUIAwgC0EMQQggASADSyIOG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCyAILQABIQEgCC0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgMAgsgBCALNgIQIAFBCXQgAmohAkEHIQgMAQsgBCALNgIQQQghCCABQQh0IAJqIQILIAhBAWshCCACQQF0IQIgA0EBdCIDQYCAAkkNAAsgFEUgFCAOGwwBCyALKAIECyATcyIBGzYCgAYgCiAKKAIAQYCAAXI2AgAgByAHKAIEQYAgcjYCBCAHIAcoAoQCQQRyNgKEAiAHIAcoAowCQQFyNgKMAiAHIAcoAogCIAFBEnRyQQJyNgKIAiAGIAFBHHRyQYDAAHIFIAYLQYCAgIAEciEGCyAHIAY2AgALIAdBBGohBiAJQQRqIQEgF0EBaiIXQcAARw0ACyAHQQxqIQYgCUGEBmohASANQTxJIVEgDUEEaiENIFENAAsMAgtBASAZdCIBQQF2IAFyIQ0gBCgCeCIJIBBBAnRqQQxqIQYgBCgCgAEhASAEKAIIIQggBCgCBCEDIAQoAgAhAiAEKAJoIQwgBCgCdCERAkAgFkEIcQRAAkAgAUEESQ0AIBAEQEEAIA1rIRQgBEEcaiEFIBBBDGwhEyAQQQN0IRUDQEEAIQsDQCAGIgkoAgAiBgRAAkAgBkGQgIABcQ0AIAZB7wNxIgFFDQAgAyAFIAQoAmwgAWotAABBAnRqIgwoAgAiCigCACIBayEDAn8gASACQRB2TQRAIAIgAUEQdGshAiADQYCAAnEEQCAKKAIEDAILIAooAgQhDiAMIApBDEEIIAEgA0siEhtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQogCC0AASEBIAgtAABB/wFHBEAgBCAKNgIQQQghCCABQQh0IAJqIQIMAQsgAUGPAU0EQCAEIAo2AhAgAUEJdCACaiECQQchCAwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEICyAIQQFrIQggAkEBdCECIANBAXQiA0GAgAJJDQALIA5FIA4gEhsMAQsgCigCBCEOIAwgCkEIQQwgASADSyISG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCiAILQABIQMgCC0AAEH/AUcEQCAEIAo2AhBBCCEIIANBCHQgAmohAgwBCyADQY8BTQRAIAQgCjYCECADQQl0IAJqIQJBByEIDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgLIAhBAWshCCACQQF0IQIgAUEBdCIBQYCAAkkNAAsgASEDIA4gDkUgEhsLBH8gAyAFIAkoAgRBEXZBBHEgCUEEayIOKAIAQRN2QQFxIAZBDnZBEHEgBkEQdkHAAHEgBkGqAXFycnJyIhJB0LkBai0AAEECdGoiDCgCACIKKAIAIgFrIQMgEkHQuwFqLQAAIRggESAUIA0CfyABIAJBEHZNBEAgAiABQRB0ayECIANBgIACcQRAIAooAgQMAgsgCigCBCESIAwgCkEMQQggASADSyIbG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCiAILQABIQEgCC0AAEH/AUcEQCAEIAo2AhBBCCEIIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgCjYCECABQQl0IAJqIQJBByEIDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgLIAhBAWshCCACQQF0IQIgA0EBdCIDQYCAAkkNAAsgEkUgEiAbGwwBCyAKKAIEIRIgDCAKQQhBDCABIANLIhsbaigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiEKIAgtAAEhAyAILQAAQf8BRwRAIAQgCjYCEEEIIQggA0EIdCACaiECDAELIANBjwFNBEAgBCAKNgIQIANBCXQgAmohAkEHIQgMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAsgCEEBayEIIAJBAXQhAiABQQF0IgFBgIACSQ0ACyABIQMgEiASRSAbGwsgGHMiARs2AgAgDiAOKAIAQSByNgIAIAkgCSgCBEEIcjYCBCAGIAFBE3RyQRByBSAGC0GAgIABciEGCwJAIAZBgIGACHENACAGQfgecUUNACADIAUgBCgCbCAGQQN2IhJB7wNxai0AAEECdGoiDCgCACIKKAIAIgFrIQMCfyABIAJBEHZNBEAgAiABQRB0ayECIANBgIACcQRAIAooAgQMAgsgCigCBCEOIAwgCkEMQQggASADSyIYG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCiAILQABIQEgCC0AAEH/AUcEQCAEIAo2AhBBCCEIIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgCjYCECABQQl0IAJqIQJBByEIDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgLIAhBAWshCCACQQF0IQIgA0EBdCIDQYCAAkkNAAsgDkUgDiAYGwwBCyAKKAIEIQ4gDCAKQQhBDCABIANLIhgbaigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiEKIAgtAAEhAyAILQAAQf8BRwRAIAQgCjYCEEEIIQggA0EIdCACaiECDAELIANBjwFNBEAgBCAKNgIQIANBCXQgAmohAkEHIQgMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAsgCEEBayEIIAJBAXQhAiABQQF0IgFBgIACSQ0ACyABIQMgDiAORSAYGwsEfyADIAUgCSgCBEEUdkEEcSAJQQRrIg4oAgBBFnZBAXEgBkEPdkEQcSAGQRN2QcAAcSASQaoBcXJycnIiEkHQuQFqLQAAQQJ0aiIMKAIAIgooAgAiAWshAyASQdC7AWotAAAhGCARIBBBAnRqIBQgDQJ/IAEgAkEQdk0EQCACIAFBEHRrIQIgA0GAgAJxBEAgCigCBAwCCyAKKAIEIRIgDCAKQQxBCCABIANLIhsbaigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiEKIAgtAAEhASAILQAAQf8BRwRAIAQgCjYCEEEIIQggAUEIdCACaiECDAELIAFBjwFNBEAgBCAKNgIQIAFBCXQgAmohAkEHIQgMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAsgCEEBayEIIAJBAXQhAiADQQF0IgNBgIACSQ0ACyASRSASIBsbDAELIAooAgQhEiAMIApBCEEMIAEgA0siGxtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQogCC0AASEDIAgtAABB/wFHBEAgBCAKNgIQQQghCCADQQh0IAJqIQIMAQsgA0GPAU0EQCAEIAo2AhAgA0EJdCACaiECQQchCAwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEICyAIQQFrIQggAkEBdCECIAFBAXQiAUGAgAJJDQALIAEhAyASIBJFIBsbCyAYcyIBGzYCACAOIA4oAgBBgAJyNgIAIAkgCSgCBEHAAHI2AgQgBiABQRZ0ckGAAXIFIAYLQYCAgAhyIQYLAkAgBkGAiIDAAHENACAGQcD3AXFFDQAgAyAFIAQoAmwgBkEGdiISQe8DcWotAABBAnRqIgwoAgAiCigCACIBayEDAn8gASACQRB2TQRAIAIgAUEQdGshAiADQYCAAnEEQCAKKAIEDAILIAooAgQhDiAMIApBDEEIIAEgA0siGBtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQogCC0AASEBIAgtAABB/wFHBEAgBCAKNgIQQQghCCABQQh0IAJqIQIMAQsgAUGPAU0EQCAEIAo2AhAgAUEJdCACaiECQQchCAwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEICyAIQQFrIQggAkEBdCECIANBAXQiA0GAgAJJDQALIA5FIA4gGBsMAQsgCigCBCEOIAwgCkEIQQwgASADSyIYG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCiAILQABIQMgCC0AAEH/AUcEQCAEIAo2AhBBCCEIIANBCHQgAmohAgwBCyADQY8BTQRAIAQgCjYCECADQQl0IAJqIQJBByEIDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgLIAhBAWshCCACQQF0IQIgAUEBdCIBQYCAAkkNAAsgASEDIA4gDkUgGBsLBH8gAyAFIAkoAgRBF3ZBBHEgCUEEayIOKAIAQRl2QQFxIAZBEnZBEHEgBkEWdkHAAHEgEkGqAXFycnJyIhJB0LkBai0AAEECdGoiDCgCACIKKAIAIgFrIQMgEkHQuwFqLQAAIRggESAVaiAUIA0CfyABIAJBEHZNBEAgAiABQRB0ayECIANBgIACcQRAIAooAgQMAgsgCigCBCESIAwgCkEMQQggASADSyIbG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCiAILQABIQEgCC0AAEH/AUcEQCAEIAo2AhBBCCEIIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgCjYCECABQQl0IAJqIQJBByEIDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgLIAhBAWshCCACQQF0IQIgA0EBdCIDQYCAAkkNAAsgEkUgEiAbGwwBCyAKKAIEIRIgDCAKQQhBDCABIANLIhsbaigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiEKIAgtAAEhAyAILQAAQf8BRwRAIAQgCjYCEEEIIQggA0EIdCACaiECDAELIANBjwFNBEAgBCAKNgIQIANBCXQgAmohAkEHIQgMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAsgCEEBayEIIAJBAXQhAiABQQF0IgFBgIACSQ0ACyABIQMgEiASRSAbGwsgGHMiARs2AgAgDiAOKAIAQYAQcjYCACAJIAkoAgRBgARyNgIEIAYgAUEZdHJBgAhyBSAGC0GAgIDAAHIhBgsCQCAGQYDAgIAEcQ0AIAZBgLwPcUUNACADIAUgBCgCbCAGQQl2IhJB7wNxai0AAEECdGoiDCgCACIKKAIAIgFrIQMCfyABIAJBEHZNBEAgAiABQRB0ayECIANBgIACcQRAIAooAgQMAgsgCigCBCEOIAwgCkEMQQggASADSyIYG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCiAILQABIQEgCC0AAEH/AUcEQCAEIAo2AhBBCCEIIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgCjYCECABQQl0IAJqIQJBByEIDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgLIAhBAWshCCACQQF0IQIgA0EBdCIDQYCAAkkNAAsgDkUgDiAYGwwBCyAKKAIEIQ4gDCAKQQhBDCABIANLIhgbaigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiEKIAgtAAEhAyAILQAAQf8BRwRAIAQgCjYCEEEIIQggA0EIdCACaiECDAELIANBjwFNBEAgBCAKNgIQIANBCXQgAmohAkEHIQgMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAsgCEEBayEIIAJBAXQhAiABQQF0IgFBgIACSQ0ACyABIQMgDiAORSAYGwsEfyADIAUgCSgCBEEadkEEcSAJQQRrIg4oAgBBHHZBAXEgBkEVdkEQcSAGQRl2QcAAcSASQaoBcXJycnIiEkHQuQFqLQAAQQJ0aiIMKAIAIgooAgAiAWshAyASQdC7AWotAAAhGCARIBNqIBQgDQJ/IAEgAkEQdk0EQCACIAFBEHRrIQIgA0GAgAJxBEAgCigCBAwCCyAKKAIEIRIgDCAKQQxBCCABIANLIhsbaigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiEKIAgtAAEhASAILQAAQf8BRwRAIAQgCjYCEEEIIQggAUEIdCACaiECDAELIAFBjwFNBEAgBCAKNgIQIAFBCXQgAmohAkEHIQgMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAsgCEEBayEIIAJBAXQhAiADQQF0IgNBgIACSQ0ACyASRSASIBsbDAELIAooAgQhEiAMIApBCEEMIAEgA0siGxtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQogCC0AASEDIAgtAABB/wFHBEAgBCAKNgIQQQghCCADQQh0IAJqIQIMAQsgA0GPAU0EQCAEIAo2AhAgA0EJdCACaiECQQchCAwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEICyAIQQFrIQggAkEBdCECIAFBAXQiAUGAgAJJDQALIAEhAyASIBJFIBsbCyAYcyIKGzYCACAOIA4oAgBBgIABcjYCACAJIAkoAgRBgCByNgIEIAQoAnxBAnQgCWoiASABKAIEQQRyNgIEIAEgASgCDEEBcjYCDCABIAEoAgggCkESdHJBAnI2AgggBiAKQRx0ckGAwAByBSAGC0GAgICABHIhBgsgCSAGNgIACyAJQQRqIQYgEUEEaiERIAtBAWoiCyAQRw0ACyAJQQxqIQYgESATaiERIAdBBGoiByAEKAKAASIBQXxxSQ0ACwwBC0EEIAFBfHEiBiAGQQRNG0EBayIGQXxxQQRqIQcgCSAGQQF0QXhxakEUaiEGCyAEIAg2AgggBCADNgIEIAQgAjYCACAEIAw2AmggEEUNASABIAdNDQEDQCABIAdGIVJBACEIIAchASBSRQRAA0AgBCAGIBEgCCAQbEECdGogDSAIIAQoAnxBAmpBARBZIAhBAWoiCCAEKAKAASIBIAdrSQ0ACwsgBkEEaiEGIBFBBGohESAXQQFqIhcgEEcNAAsMAQsCQCABQQRJDQAgEARAQQAgDWshFCAEQRxqIQUgEEEMbCETIBBBA3QhFQNAQQAhCwNAIAYiCSgCACIGBEACQCAGQZCAgAFxDQAgBkHvA3EiAUUNACADIAUgBCgCbCABai0AAEECdGoiDCgCACIKKAIAIgFrIQMCfyABIAJBEHZNBEAgAiABQRB0ayECIANBgIACcQRAIAooAgQMAgsgCigCBCEOIAwgCkEMQQggASADSyISG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCiAILQABIQEgCC0AAEH/AUcEQCAEIAo2AhBBCCEIIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgCjYCECABQQl0IAJqIQJBByEIDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgLIAhBAWshCCACQQF0IQIgA0EBdCIDQYCAAkkNAAsgDkUgDiASGwwBCyAKKAIEIQ4gDCAKQQhBDCABIANLIhIbaigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiEKIAgtAAEhAyAILQAAQf8BRwRAIAQgCjYCEEEIIQggA0EIdCACaiECDAELIANBjwFNBEAgBCAKNgIQIANBCXQgAmohAkEHIQgMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAsgCEEBayEIIAJBAXQhAiABQQF0IgFBgIACSQ0ACyABIQMgDiAORSASGwsEfyADIAUgCSgCBEERdkEEcSAJQQRrIg4oAgBBE3ZBAXEgBkEOdkEQcSAGQRB2QcAAcSAGQaoBcXJycnIiEkHQuQFqLQAAQQJ0aiIMKAIAIgooAgAiAWshAyASQdC7AWotAAAhGCARIBQgDQJ/IAEgAkEQdk0EQCACIAFBEHRrIQIgA0GAgAJxBEAgCigCBAwCCyAKKAIEIRIgDCAKQQxBCCABIANLIhsbaigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiEKIAgtAAEhASAILQAAQf8BRwRAIAQgCjYCEEEIIQggAUEIdCACaiECDAELIAFBjwFNBEAgBCAKNgIQIAFBCXQgAmohAkEHIQgMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAsgCEEBayEIIAJBAXQhAiADQQF0IgNBgIACSQ0ACyASRSASIBsbDAELIAooAgQhEiAMIApBCEEMIAEgA0siGxtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQogCC0AASEDIAgtAABB/wFHBEAgBCAKNgIQQQghCCADQQh0IAJqIQIMAQsgA0GPAU0EQCAEIAo2AhAgA0EJdCACaiECQQchCAwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEICyAIQQFrIQggAkEBdCECIAFBAXQiAUGAgAJJDQALIAEhAyASIBJFIBsbCyAYcyIKGzYCACAOIA4oAgBBIHI2AgAgCSAJKAIEQQhyNgIEIAlBfiAEKAJ8a0ECdGoiASABKAIEQYCAAnI2AgQgASABKAIAIApBH3RyQYCABHI2AgAgAUEEayIBIAEoAgBBgIAIcjYCACAGIApBE3RyQRByBSAGC0GAgIABciEGCwJAIAZBgIGACHENACAGQfgecUUNACADIAUgBCgCbCAGQQN2IhJB7wNxai0AAEECdGoiDCgCACIKKAIAIgFrIQMCfyABIAJBEHZNBEAgAiABQRB0ayECIANBgIACcQRAIAooAgQMAgsgCigCBCEOIAwgCkEMQQggASADSyIYG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCiAILQABIQEgCC0AAEH/AUcEQCAEIAo2AhBBCCEIIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgCjYCECABQQl0IAJqIQJBByEIDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgLIAhBAWshCCACQQF0IQIgA0EBdCIDQYCAAkkNAAsgDkUgDiAYGwwBCyAKKAIEIQ4gDCAKQQhBDCABIANLIhgbaigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiEKIAgtAAEhAyAILQAAQf8BRwRAIAQgCjYCEEEIIQggA0EIdCACaiECDAELIANBjwFNBEAgBCAKNgIQIANBCXQgAmohAkEHIQgMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAsgCEEBayEIIAJBAXQhAiABQQF0IgFBgIACSQ0ACyABIQMgDiAORSAYGwsEfyADIAUgCSgCBEEUdkEEcSAJQQRrIg4oAgBBFnZBAXEgBkEPdkEQcSAGQRN2QcAAcSASQaoBcXJycnIiEkHQuQFqLQAAQQJ0aiIMKAIAIgooAgAiAWshAyASQdC7AWotAAAhGCARIBBBAnRqIBQgDQJ/IAEgAkEQdk0EQCACIAFBEHRrIQIgA0GAgAJxBEAgCigCBAwCCyAKKAIEIRIgDCAKQQxBCCABIANLIhsbaigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiEKIAgtAAEhASAILQAAQf8BRwRAIAQgCjYCEEEIIQggAUEIdCACaiECDAELIAFBjwFNBEAgBCAKNgIQIAFBCXQgAmohAkEHIQgMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAsgCEEBayEIIAJBAXQhAiADQQF0IgNBgIACSQ0ACyASRSASIBsbDAELIAooAgQhEiAMIApBCEEMIAEgA0siGxtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQogCC0AASEDIAgtAABB/wFHBEAgBCAKNgIQQQghCCADQQh0IAJqIQIMAQsgA0GPAU0EQCAEIAo2AhAgA0EJdCACaiECQQchCAwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEICyAIQQFrIQggAkEBdCECIAFBAXQiAUGAgAJJDQALIAEhAyASIBJFIBsbCyAYcyIBGzYCACAOIA4oAgBBgAJyNgIAIAkgCSgCBEHAAHI2AgQgBiABQRZ0ckGAAXIFIAYLQYCAgAhyIQYLAkAgBkGAiIDAAHENACAGQcD3AXFFDQAgAyAFIAQoAmwgBkEGdiISQe8DcWotAABBAnRqIgwoAgAiCigCACIBayEDAn8gASACQRB2TQRAIAIgAUEQdGshAiADQYCAAnEEQCAKKAIEDAILIAooAgQhDiAMIApBDEEIIAEgA0siGBtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQogCC0AASEBIAgtAABB/wFHBEAgBCAKNgIQQQghCCABQQh0IAJqIQIMAQsgAUGPAU0EQCAEIAo2AhAgAUEJdCACaiECQQchCAwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEICyAIQQFrIQggAkEBdCECIANBAXQiA0GAgAJJDQALIA5FIA4gGBsMAQsgCigCBCEOIAwgCkEIQQwgASADSyIYG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCiAILQABIQMgCC0AAEH/AUcEQCAEIAo2AhBBCCEIIANBCHQgAmohAgwBCyADQY8BTQRAIAQgCjYCECADQQl0IAJqIQJBByEIDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgLIAhBAWshCCACQQF0IQIgAUEBdCIBQYCAAkkNAAsgASEDIA4gDkUgGBsLBH8gAyAFIAkoAgRBF3ZBBHEgCUEEayIOKAIAQRl2QQFxIAZBEnZBEHEgBkEWdkHAAHEgEkGqAXFycnJyIhJB0LkBai0AAEECdGoiDCgCACIKKAIAIgFrIQMgEkHQuwFqLQAAIRggESAVaiAUIA0CfyABIAJBEHZNBEAgAiABQRB0ayECIANBgIACcQRAIAooAgQMAgsgCigCBCESIAwgCkEMQQggASADSyIbG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCiAILQABIQEgCC0AAEH/AUcEQCAEIAo2AhBBCCEIIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgCjYCECABQQl0IAJqIQJBByEIDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgLIAhBAWshCCACQQF0IQIgA0EBdCIDQYCAAkkNAAsgEkUgEiAbGwwBCyAKKAIEIRIgDCAKQQhBDCABIANLIhsbaigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiEKIAgtAAEhAyAILQAAQf8BRwRAIAQgCjYCEEEIIQggA0EIdCACaiECDAELIANBjwFNBEAgBCAKNgIQIANBCXQgAmohAkEHIQgMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAsgCEEBayEIIAJBAXQhAiABQQF0IgFBgIACSQ0ACyABIQMgEiASRSAbGwsgGHMiARs2AgAgDiAOKAIAQYAQcjYCACAJIAkoAgRBgARyNgIEIAYgAUEZdHJBgAhyBSAGC0GAgIDAAHIhBgsCQCAGQYDAgIAEcQ0AIAZBgLwPcUUNACADIAUgBCgCbCAGQQl2IhJB7wNxai0AAEECdGoiDCgCACIKKAIAIgFrIQMCfyABIAJBEHZNBEAgAiABQRB0ayECIANBgIACcQRAIAooAgQMAgsgCigCBCEOIAwgCkEMQQggASADSyIYG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCiAILQABIQEgCC0AAEH/AUcEQCAEIAo2AhBBCCEIIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgCjYCECABQQl0IAJqIQJBByEIDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgLIAhBAWshCCACQQF0IQIgA0EBdCIDQYCAAkkNAAsgDkUgDiAYGwwBCyAKKAIEIQ4gDCAKQQhBDCABIANLIhgbaigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiEKIAgtAAEhAyAILQAAQf8BRwRAIAQgCjYCEEEIIQggA0EIdCACaiECDAELIANBjwFNBEAgBCAKNgIQIANBCXQgAmohAkEHIQgMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAsgCEEBayEIIAJBAXQhAiABQQF0IgFBgIACSQ0ACyABIQMgDiAORSAYGwsEfyADIAUgCSgCBEEadkEEcSAJQQRrIg4oAgBBHHZBAXEgBkEVdkEQcSAGQRl2QcAAcSASQaoBcXJycnIiEkHQuQFqLQAAQQJ0aiIMKAIAIgooAgAiAWshAyASQdC7AWotAAAhGCARIBNqIBQgDQJ/IAEgAkEQdk0EQCACIAFBEHRrIQIgA0GAgAJxBEAgCigCBAwCCyAKKAIEIRIgDCAKQQxBCCABIANLIhsbaigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiEKIAgtAAEhASAILQAAQf8BRwRAIAQgCjYCEEEIIQggAUEIdCACaiECDAELIAFBjwFNBEAgBCAKNgIQIAFBCXQgAmohAkEHIQgMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAsgCEEBayEIIAJBAXQhAiADQQF0IgNBgIACSQ0ACyASRSASIBsbDAELIAooAgQhEiAMIApBCEEMIAEgA0siGxtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQogCC0AASEDIAgtAABB/wFHBEAgBCAKNgIQQQghCCADQQh0IAJqIQIMAQsgA0GPAU0EQCAEIAo2AhAgA0EJdCACaiECQQchCAwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEICyAIQQFrIQggAkEBdCECIAFBAXQiAUGAgAJJDQALIAEhAyASIBJFIBsbCyAYcyIKGzYCACAOIA4oAgBBgIABcjYCACAJIAkoAgRBgCByNgIEIAQoAnxBAnQgCWoiASABKAIEQQRyNgIEIAEgASgCDEEBcjYCDCABIAEoAgggCkESdHJBAnI2AgggBiAKQRx0ckGAwAByBSAGC0GAgICABHIhBgsgCSAGNgIACyAJQQRqIQYgEUEEaiERIAtBAWoiCyAQRw0ACyAJQQxqIQYgESATaiERIAdBBGoiByAEKAKAASIBQXxxSQ0ACwwBC0EEIAFBfHEiBiAGQQRNG0EBayIGQXxxQQRqIQcgCSAGQQF0QXhxakEUaiEGCyAEIAg2AgggBCADNgIEIAQgAjYCACAEIAw2AmggEEUNACABIAdNDQADQCABIAdGIVNBACEIIAchASBTRQRAA0AgBCAGIBEgCCAQbEECdGogDSAIIAQoAnxBAmpBABBZIAhBAWoiCCAEKAKAASIBIAdrSQ0ACwsgBkEEaiEGIBFBBGohESAXQQFqIhcgEEcNAAsLDAILA0BBACEXA0AgASEJIAYiBygCACIGBEACQCAGQZCAgAFxDQAgBkHvA3EiAUUNACADIBAgBCgCbCABai0AAEECdGoiDCgCACILKAIAIgFrIQMCfyABIAJBEHZLBEAgCygCBCEKIAwgC0EIQQwgASADSyIUG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCyAILQABIQMgCC0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgMAgsgBCALNgIQIANBCXQgAmohAkEHIQgMAQsgBCALNgIQQQghCCADQQh0IAJqIQILIAhBAWshCCACQQF0IQIgAUEBdCIBQYCAAkkNAAsgASEDIAogCkUgFBsMAQsgAiABQRB0ayECIANBgIACcUUEQCALKAIEIQogDCALQQxBCCABIANLIhQbaigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiELIAgtAAEhASAILQAAQf8BRgRAIAFBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAwCCyAEIAs2AhAgAUEJdCACaiECQQchCAwBCyAEIAs2AhBBCCEIIAFBCHQgAmohAgsgCEEBayEIIAJBAXQhAiADQQF0IgNBgIACSQ0ACyAKRSAKIBQbDAELIAsoAgQLBH8gAyAQIAcoAgRBEXZBBHEgB0EEayIKKAIAQRN2QQFxIAZBDnZBEHEgBkEQdkHAAHEgBkGqAXFycnJyIhRB0LkBai0AAEECdGoiDCgCACILKAIAIgFrIQMgFEHQuwFqLQAAIRMgCSAFIBECfyABIAJBEHZLBEAgCygCBCEUIAwgC0EIQQwgASADSyIOG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCyAILQABIQMgCC0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgMAgsgBCALNgIQIANBCXQgAmohAkEHIQgMAQsgBCALNgIQQQghCCADQQh0IAJqIQILIAhBAWshCCACQQF0IQIgAUEBdCIBQYCAAkkNAAsgASEDIBQgFEUgDhsMAQsgAiABQRB0ayECIANBgIACcUUEQCALKAIEIRQgDCALQQxBCCABIANLIg4baigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiELIAgtAAEhASAILQAAQf8BRgRAIAFBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAwCCyAEIAs2AhAgAUEJdCACaiECQQchCAwBCyAEIAs2AhBBCCEIIAFBCHQgAmohAgsgCEEBayEIIAJBAXQhAiADQQF0IgNBgIACSQ0ACyAURSAUIA4bDAELIAsoAgQLIBNzIgEbNgIAIAogCigCAEEgcjYCACAHIAcoAgRBCHI2AgQgBiABQRN0ckEQcgUgBgtBgICAAXIhBgsCQCAGQYCBgAhxDQAgBkH4HnFFDQAgAyAQIAQoAmwgBkEDdiIUQe8DcWotAABBAnRqIgwoAgAiCygCACIBayEDAn8gASACQRB2SwRAIAsoAgQhCiAMIAtBCEEMIAEgA0siExtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQsgCC0AASEDIAgtAABB/wFGBEAgA0GQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEIDAILIAQgCzYCECADQQl0IAJqIQJBByEIDAELIAQgCzYCEEEIIQggA0EIdCACaiECCyAIQQFrIQggAkEBdCECIAFBAXQiAUGAgAJJDQALIAEhAyAKIApFIBMbDAELIAIgAUEQdGshAiADQYCAAnFFBEAgCygCBCEKIAwgC0EMQQggASADSyITG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCyAILQABIQEgCC0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgMAgsgBCALNgIQIAFBCXQgAmohAkEHIQgMAQsgBCALNgIQQQghCCABQQh0IAJqIQILIAhBAWshCCACQQF0IQIgA0EBdCIDQYCAAkkNAAsgCkUgCiATGwwBCyALKAIECwR/IAMgECAHKAIEQRR2QQRxIAdBBGsiCigCAEEWdkEBcSAGQQ92QRBxIAZBE3ZBwABxIBRBqgFxcnJyciIUQdC5AWotAABBAnRqIgwoAgAiCygCACIBayEDIBRB0LsBai0AACETIAkgBSARAn8gASACQRB2SwRAIAsoAgQhFCAMIAtBCEEMIAEgA0siDhtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQsgCC0AASEDIAgtAABB/wFGBEAgA0GQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEIDAILIAQgCzYCECADQQl0IAJqIQJBByEIDAELIAQgCzYCEEEIIQggA0EIdCACaiECCyAIQQFrIQggAkEBdCECIAFBAXQiAUGAgAJJDQALIAEhAyAUIBRFIA4bDAELIAIgAUEQdGshAiADQYCAAnFFBEAgCygCBCEUIAwgC0EMQQggASADSyIOG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCyAILQABIQEgCC0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgMAgsgBCALNgIQIAFBCXQgAmohAkEHIQgMAQsgBCALNgIQQQghCCABQQh0IAJqIQILIAhBAWshCCACQQF0IQIgA0EBdCIDQYCAAkkNAAsgFEUgFCAOGwwBCyALKAIECyATcyIBGzYCgAIgCiAKKAIAQYACcjYCACAHIAcoAgRBwAByNgIEIAYgAUEWdHJBgAFyBSAGC0GAgIAIciEGCwJAIAZBgIiAwABxDQAgBkHA9wFxRQ0AIAMgECAEKAJsIAZBBnYiFEHvA3FqLQAAQQJ0aiIMKAIAIgsoAgAiAWshAwJ/IAEgAkEQdksEQCALKAIEIQogDCALQQhBDCABIANLIhMbaigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiELIAgtAAEhAyAILQAAQf8BRgRAIANBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAwCCyAEIAs2AhAgA0EJdCACaiECQQchCAwBCyAEIAs2AhBBCCEIIANBCHQgAmohAgsgCEEBayEIIAJBAXQhAiABQQF0IgFBgIACSQ0ACyABIQMgCiAKRSATGwwBCyACIAFBEHRrIQIgA0GAgAJxRQRAIAsoAgQhCiAMIAtBDEEIIAEgA0siExtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQsgCC0AASEBIAgtAABB/wFGBEAgAUGQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEIDAILIAQgCzYCECABQQl0IAJqIQJBByEIDAELIAQgCzYCEEEIIQggAUEIdCACaiECCyAIQQFrIQggAkEBdCECIANBAXQiA0GAgAJJDQALIApFIAogExsMAQsgCygCBAsEfyADIBAgBygCBEEXdkEEcSAHQQRrIgooAgBBGXZBAXEgBkESdkEQcSAGQRZ2QcAAcSAUQaoBcXJycnIiFEHQuQFqLQAAQQJ0aiIMKAIAIgsoAgAiAWshAyAUQdC7AWotAAAhEyAJIAUgEQJ/IAEgAkEQdksEQCALKAIEIRQgDCALQQhBDCABIANLIg4baigCADYCAANAAkAgCA0AIAQoAhAiCEEBaiELIAgtAAEhAyAILQAAQf8BRgRAIANBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghCAwCCyAEIAs2AhAgA0EJdCACaiECQQchCAwBCyAEIAs2AhBBCCEIIANBCHQgAmohAgsgCEEBayEIIAJBAXQhAiABQQF0IgFBgIACSQ0ACyABIQMgFCAURSAOGwwBCyACIAFBEHRrIQIgA0GAgAJxRQRAIAsoAgQhFCAMIAtBDEEIIAEgA0siDhtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQsgCC0AASEBIAgtAABB/wFGBEAgAUGQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEIDAILIAQgCzYCECABQQl0IAJqIQJBByEIDAELIAQgCzYCEEEIIQggAUEIdCACaiECCyAIQQFrIQggAkEBdCECIANBAXQiA0GAgAJJDQALIBRFIBQgDhsMAQsgCygCBAsgE3MiARs2AoAEIAogCigCAEGAEHI2AgAgByAHKAIEQYAEcjYCBCAGIAFBGXRyQYAIcgUgBgtBgICAwAByIQYLAkAgBkGAwICABHENACAGQYC8D3FFDQAgAyAQIAQoAmwgBkEJdiIUQe8DcWotAABBAnRqIgwoAgAiCygCACIBayEDAn8gASACQRB2SwRAIAsoAgQhCiAMIAtBCEEMIAEgA0siExtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQsgCC0AASEDIAgtAABB/wFGBEAgA0GQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEIDAILIAQgCzYCECADQQl0IAJqIQJBByEIDAELIAQgCzYCEEEIIQggA0EIdCACaiECCyAIQQFrIQggAkEBdCECIAFBAXQiAUGAgAJJDQALIAEhAyAKIApFIBMbDAELIAIgAUEQdGshAiADQYCAAnFFBEAgCygCBCEKIAwgC0EMQQggASADSyITG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCyAILQABIQEgCC0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgMAgsgBCALNgIQIAFBCXQgAmohAkEHIQgMAQsgBCALNgIQQQghCCABQQh0IAJqIQILIAhBAWshCCACQQF0IQIgA0EBdCIDQYCAAkkNAAsgCkUgCiATGwwBCyALKAIECwR/IAMgECAHKAIEQRp2QQRxIAdBBGsiCigCAEEcdkEBcSAGQRV2QRBxIAZBGXZBwABxIBRBqgFxcnJyciIUQdC5AWotAABBAnRqIgwoAgAiCygCACIBayEDIBRB0LsBai0AACETIAkgBSARAn8gASACQRB2SwRAIAsoAgQhFCAMIAtBCEEMIAEgA0siDhtqKAIANgIAA0ACQCAIDQAgBCgCECIIQQFqIQsgCC0AASEDIAgtAABB/wFGBEAgA0GQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEIDAILIAQgCzYCECADQQl0IAJqIQJBByEIDAELIAQgCzYCEEEIIQggA0EIdCACaiECCyAIQQFrIQggAkEBdCECIAFBAXQiAUGAgAJJDQALIAEhAyAUIBRFIA4bDAELIAIgAUEQdGshAiADQYCAAnFFBEAgCygCBCEUIAwgC0EMQQggASADSyIOG2ooAgA2AgADQAJAIAgNACAEKAIQIghBAWohCyAILQABIQEgCC0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQgMAgsgBCALNgIQIAFBCXQgAmohAkEHIQgMAQsgBCALNgIQQQghCCABQQh0IAJqIQILIAhBAWshCCACQQF0IQIgA0EBdCIDQYCAAkkNAAsgFEUgFCAOGwwBCyALKAIECyATcyIBGzYCgAYgCiAKKAIAQYCAAXI2AgAgByAHKAIEQYAgcjYCBCAHIAcoAoQCQQRyNgKEAiAHIAcoAowCQQFyNgKMAiAHIAcoAogCIAFBEnRyQQJyNgKIAiAGIAFBHHRyQYDAAHIFIAYLQYCAgIAEciEGCyAHIAY2AgALIAdBBGohBiAJQQRqIQEgF0EBaiIXQcAARw0ACyAHQQxqIQYgCUGEBmohASANQTxJIVQgDUEEaiENIFQNAAsLIAQgCDYCCCAEIAM2AgQgBCACNgIAIAQgDDYCaAsMAgsgIgRAQQEgGXRBAXYhCSAEKAJ8IhFBAnQiDCAEKAJ4akEMaiEBIAQoAnQhBkEAIQ0gBCgCgAEiA0EETwRAIBFFDQQgEUEDbCEFIBFBAXQhC0EAIAlrIQIDQCALQQJ0IQpBACEDA0ACQCABIgcoAgAiAUUNACABQZCAgAFxQRBGBEAgBCgCACEBAkAgBCgCCCIQDQAgAUH/AUYhECAEKAIQIggtAAAhAQJAIBBFBEAgBCABNgIAIAQgCEEBajYCEAwBCyABQY8BTQRAIAQgATYCACAEIAhBAWo2AhBBByEQDAILQf8BIQEgBEH/ATYCAAtBCCEQCyAEIBBBAWsiCDYCCCAGIAIgCSABIAh2QQFxIAYoAgAiAUEfdkYbIAFqNgIAIAcgBygCAEGAgMAAciIBNgIACyABQYCBgAhxQYABRgRAIAQoAgAhAQJAIAQoAggiEA0AIAFB/wFGIRAgBCgCECIILQAAIQECQCAQRQRAIAQgATYCACAEIAhBAWo2AhAMAQsgAUGPAU0EQCAEIAE2AgAgBCAIQQFqNgIQQQchEAwCC0H/ASEBIARB/wE2AgALQQghEAsgBCAQQQFrIgg2AgggBiAMaiIQIAIgCSABIAh2QQFxIBAoAgAiAUEfdkYbIAFqNgIAIAcgBygCAEGAgIAEciIBNgIACyABQYCIgMAAcUGACEYEQCAEKAIAIQECQCAEKAIIIhANACABQf8BRiEQIAQoAhAiCC0AACEBAkAgEEUEQCAEIAE2AgAgBCAIQQFqNgIQDAELIAFBjwFNBEAgBCABNgIAIAQgCEEBajYCEEEHIRAMAgtB/wEhASAEQf8BNgIAC0EIIRALIAQgEEEBayIINgIIIAYgCmoiECACIAkgASAIdkEBcSAQKAIAIgFBH3ZGGyABajYCACAHIAcoAgBBgICAIHIiATYCAAsgAUGAwICABHFBgMAARw0AIAYgBUECdGohECAEKAIAIQECQCAEKAIIIggNACABQf8BRiEUIAQoAhAiCC0AACEBAkAgFEUEQCAEIAE2AgAgBCAIQQFqNgIQDAELIAFBjwFNBEAgBCABNgIAIAQgCEEBajYCEEEHIQgMAgtB/wEhASAEQf8BNgIAC0EIIQgLIAQgCEEBayIINgIIIBAgAiAJIAEgCHZBAXEgECgCACIBQR92RhsgAWo2AgAgByAHKAIAQYCAgIACcjYCAAsgBkEEaiEGIAdBBGohASADQQFqIgMgEUcNAAsgB0EMaiEBIAYgBUECdGohBiANQQRqIg0gBCgCgAEiA0F8cUkNAAsLIAMgDU0NAiARRQ0CQQAhE0EAIAlrIQUgAyEHA0ACQCAHIA1GBEAgDSEHDAELIAEoAgAhEEEAIQIDQEGQgIABIAJBA2wiB3QgEHFBECAHdEYEQCAGIAIgEWxBAnRqIRAgBCgCACEDAkAgBCgCCCIIDQAgA0H/AUchDCAEKAIQIggtAAAhAwJAIAxFBEAgA0GQAU8EQEH/ASEDIARB/wE2AgAMAgsgBCADNgIAIAQgCEEBajYCEEEHIQgMAgsgBCADNgIAIAQgCEEBajYCEAtBCCEICyAEIAhBAWsiCDYCCCAQIAUgCSADIAh2QQFxIBAoAgAiA0EfdkYbIANqNgIAIAEgASgCAEGAgMAAIAd0ciIQNgIAIAQoAoABIQMLIAMhByACQQFqIgIgAyANa0kNAAsLIAZBBGohBiABQQRqIQEgE0EBaiITIBFHDQALDAILIAQoAnghCCAEKAJ0IQcgBCgCgAEhAwJAIAQoAnwiDEHAAEcNACADQcAARw0AIAhBjAJqIQNBACETQQBBASAZdEEBdiIFayEMIAQoAgghAiAEKAIEIQYgBCgCACEBIAQoAmghDQNAQQAhCANAIAchCSADIhAoAgAiBwRAIAMhVSAHQZCAgAFxQRBGBEAgBiAPQRBBD0EOIAdB7wNxGyAHQYCAwABxG0ECdGoiDSgCACIRKAIAIgNrIQYCfyADIAFBEHZLBEAgESgCBCELIA0gEUEIQQwgAyAGSyIKG2ooAgA2AgADQAJAIAINACAEKAIQIgJBAWohESACLQABIQYgAi0AAEH/AUYEQCAGQZABTwRAIAQgBCgCDEEBajYCDCABQYD+A2ohAUEIIQIMAgsgBCARNgIQIAZBCXQgAWohAUEHIQIMAQsgBCARNgIQQQghAiAGQQh0IAFqIQELIAJBAWshAiABQQF0IQEgA0EBdCIDQYCAAkkNAAsgAyEGIAsgC0UgChsMAQsgASADQRB0ayEBIAZBgIACcUUEQCARKAIEIQsgDSARQQxBCCADIAZLIgobaigCADYCAANAAkAgAg0AIAQoAhAiAkEBaiERIAItAAEhAyACLQAAQf8BRgRAIANBkAFPBEAgBCAEKAIMQQFqNgIMIAFBgP4DaiEBQQghAgwCCyAEIBE2AhAgA0EJdCABaiEBQQchAgwBCyAEIBE2AhBBCCECIANBCHQgAWohAQsgAkEBayECIAFBAXQhASAGQQF0IgZBgIACSQ0ACyALRSALIAobDAELIBEoAgQLIQMgCSAMIAUgAyAJKAIAIhFBH3ZGGyARajYCACAHQYCAwAByIQcLIAdBgIGACHFBgAFGBEAgBiAPQRBBD0EOIAdB+B5xGyAHQYCAgARxG0ECdGoiDSgCACIRKAIAIgNrIQYCfyADIAFBEHZLBEAgESgCBCELIA0gEUEIQQwgAyAGSyIKG2ooAgA2AgADQAJAIAINACAEKAIQIgJBAWohESACLQABIQYgAi0AAEH/AUYEQCAGQZABTwRAIAQgBCgCDEEBajYCDCABQYD+A2ohAUEIIQIMAgsgBCARNgIQIAZBCXQgAWohAUEHIQIMAQsgBCARNgIQQQghAiAGQQh0IAFqIQELIAJBAWshAiABQQF0IQEgA0EBdCIDQYCAAkkNAAsgAyEGIAsgC0UgChsMAQsgASADQRB0ayEBIAZBgIACcUUEQCARKAIEIQsgDSARQQxBCCADIAZLIgobaigCADYCAANAAkAgAg0AIAQoAhAiAkEBaiERIAItAAEhAyACLQAAQf8BRgRAIANBkAFPBEAgBCAEKAIMQQFqNgIMIAFBgP4DaiEBQQghAgwCCyAEIBE2AhAgA0EJdCABaiEBQQchAgwBCyAEIBE2AhBBCCECIANBCHQgAWohAQsgAkEBayECIAFBAXQhASAGQQF0IgZBgIACSQ0ACyALRSALIAobDAELIBEoAgQLIQMgCSAMIAUgAyAJKAKAAiIRQR92RhsgEWo2AoACIAdBgICABHIhBwsgB0GAiIDAAHFBgAhGBEAgBiAPQRBBD0EOIAdBwPcBcRsgB0GAgIAgcRtBAnRqIg0oAgAiESgCACIDayEGAn8gAyABQRB2SwRAIBEoAgQhCyANIBFBCEEMIAMgBksiChtqKAIANgIAA0ACQCACDQAgBCgCECICQQFqIREgAi0AASEGIAItAABB/wFGBEAgBkGQAU8EQCAEIAQoAgxBAWo2AgwgAUGA/gNqIQFBCCECDAILIAQgETYCECAGQQl0IAFqIQFBByECDAELIAQgETYCEEEIIQIgBkEIdCABaiEBCyACQQFrIQIgAUEBdCEBIANBAXQiA0GAgAJJDQALIAMhBiALIAtFIAobDAELIAEgA0EQdGshASAGQYCAAnFFBEAgESgCBCELIA0gEUEMQQggAyAGSyIKG2ooAgA2AgADQAJAIAINACAEKAIQIgJBAWohESACLQABIQMgAi0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCABQYD+A2ohAUEIIQIMAgsgBCARNgIQIANBCXQgAWohAUEHIQIMAQsgBCARNgIQQQghAiADQQh0IAFqIQELIAJBAWshAiABQQF0IQEgBkEBdCIGQYCAAkkNAAsgC0UgCyAKGwwBCyARKAIECyEDIAkgDCAFIAMgCSgCgAQiEUEfdkYbIBFqNgKABCAHQYCAgCByIQcLIFUgB0GAwICABHFBgMAARgR/IAYgD0EQQQ9BDiAHQYC8D3EbIAdBgICAgAJxG0ECdGoiDSgCACIRKAIAIgNrIQYCfyADIAFBEHZLBEAgESgCBCELIA0gEUEIQQwgAyAGSyIKG2ooAgA2AgADQAJAIAINACAEKAIQIgJBAWohESACLQABIQYgAi0AAEH/AUYEQCAGQZABTwRAIAQgBCgCDEEBajYCDCABQYD+A2ohAUEIIQIMAgsgBCARNgIQIAZBCXQgAWohAUEHIQIMAQsgBCARNgIQQQghAiAGQQh0IAFqIQELIAJBAWshAiABQQF0IQEgA0EBdCIDQYCAAkkNAAsgAyEGIAsgC0UgChsMAQsgASADQRB0ayEBIAZBgIACcUUEQCARKAIEIQsgDSARQQxBCCADIAZLIgobaigCADYCAANAAkAgAg0AIAQoAhAiAkEBaiERIAItAAEhAyACLQAAQf8BRgRAIANBkAFPBEAgBCAEKAIMQQFqNgIMIAFBgP4DaiEBQQghAgwCCyAEIBE2AhAgA0EJdCABaiEBQQchAgwBCyAEIBE2AhBBCCECIANBCHQgAWohAQsgAkEBayECIAFBAXQhASAGQQF0IgZBgIACSQ0ACyALRSALIAobDAELIBEoAgQLIQMgCSAMIAUgAyAJKAKABiIRQR92RhsgEWo2AoAGIAdBgICAgAJyBSAHCzYCAAsgEEEEaiEDIAlBBGohByAIQQFqIghBwABHDQALIBBBDGohAyAJQYQGaiEHIBNBPEkhViATQQRqIRMgVg0ACyAEIAI2AgggBCAGNgIEIAQgATYCACAEIA02AmgMAgtBASAZdEEBdiELIAggDEECdCIOakEMaiEJIAQoAgghAiAEKAIEIQYgBCgCACEBIAQoAmghDUEAIRECQCADQQRJDQAgDARAIAxBA2whFCAMQQF0IRdBACALayEKA0AgF0ECdCESQQAhCANAIAkiBSgCACIQBEAgEEGQgIABcUEQRgRAIAYgD0EQQQ9BDiAQQe8DcRsgEEGAgMAAcRtBAnRqIg0oAgAiCSgCACIDayEGAn8gAyABQRB2TQRAIAEgA0EQdGshASAGQYCAAnEEQCAJKAIEDAILIAkoAgQhEyANIAlBDEEIIAMgBksiFRtqKAIANgIAA0ACQCACDQAgBCgCECIJQQFqIQIgCS0AASEDIAktAABB/wFHBEAgBCACNgIQQQghAiADQQh0IAFqIQEMAQsgA0GPAU0EQCAEIAI2AhAgA0EJdCABaiEBQQchAgwBCyAEIAQoAgxBAWo2AgwgAUGA/gNqIQFBCCECCyACQQFrIQIgAUEBdCEBIAZBAXQiBkGAgAJJDQALIBNFIBMgFRsMAQsgCSgCBCETIA0gCUEIQQwgAyAGSyIVG2ooAgA2AgADQAJAIAINACAEKAIQIglBAWohAiAJLQABIQYgCS0AAEH/AUcEQCAEIAI2AhBBCCECIAZBCHQgAWohAQwBCyAGQY8BTQRAIAQgAjYCECAGQQl0IAFqIQFBByECDAELIAQgBCgCDEEBajYCDCABQYD+A2ohAUEIIQILIAJBAWshAiABQQF0IQEgA0EBdCIDQYCAAkkNAAsgAyEGIBMgE0UgFRsLIQMgByAKIAsgAyAHKAIAIglBH3ZGGyAJajYCACAQQYCAwAByIRALIBBBgIGACHFBgAFGBEAgBiAPQRBBD0EOIBBB+B5xGyAQQYCAgARxG0ECdGoiDSgCACIJKAIAIgNrIQYCfyADIAFBEHZNBEAgASADQRB0ayEBIAZBgIACcQRAIAkoAgQMAgsgCSgCBCETIA0gCUEMQQggAyAGSyIVG2ooAgA2AgADQAJAIAINACAEKAIQIglBAWohAiAJLQABIQMgCS0AAEH/AUcEQCAEIAI2AhBBCCECIANBCHQgAWohAQwBCyADQY8BTQRAIAQgAjYCECADQQl0IAFqIQFBByECDAELIAQgBCgCDEEBajYCDCABQYD+A2ohAUEIIQILIAJBAWshAiABQQF0IQEgBkEBdCIGQYCAAkkNAAsgE0UgEyAVGwwBCyAJKAIEIRMgDSAJQQhBDCADIAZLIhUbaigCADYCAANAAkAgAg0AIAQoAhAiCUEBaiECIAktAAEhBiAJLQAAQf8BRwRAIAQgAjYCEEEIIQIgBkEIdCABaiEBDAELIAZBjwFNBEAgBCACNgIQIAZBCXQgAWohAUEHIQIMAQsgBCAEKAIMQQFqNgIMIAFBgP4DaiEBQQghAgsgAkEBayECIAFBAXQhASADQQF0IgNBgIACSQ0ACyADIQYgEyATRSAVGwshAyAHIA5qIgkgCiALIAMgCSgCACIJQR92RhsgCWo2AgAgEEGAgIAEciEQCyAQQYCIgMAAcUGACEYEQCAGIA9BEEEPQQ4gEEHA9wFxGyAQQYCAgCBxG0ECdGoiDSgCACIJKAIAIgNrIQYCfyADIAFBEHZNBEAgASADQRB0ayEBIAZBgIACcQRAIAkoAgQMAgsgCSgCBCETIA0gCUEMQQggAyAGSyIVG2ooAgA2AgADQAJAIAINACAEKAIQIglBAWohAiAJLQABIQMgCS0AAEH/AUcEQCAEIAI2AhBBCCECIANBCHQgAWohAQwBCyADQY8BTQRAIAQgAjYCECADQQl0IAFqIQFBByECDAELIAQgBCgCDEEBajYCDCABQYD+A2ohAUEIIQILIAJBAWshAiABQQF0IQEgBkEBdCIGQYCAAkkNAAsgE0UgEyAVGwwBCyAJKAIEIRMgDSAJQQhBDCADIAZLIhUbaigCADYCAANAAkAgAg0AIAQoAhAiCUEBaiECIAktAAEhBiAJLQAAQf8BRwRAIAQgAjYCEEEIIQIgBkEIdCABaiEBDAELIAZBjwFNBEAgBCACNgIQIAZBCXQgAWohAUEHIQIMAQsgBCAEKAIMQQFqNgIMIAFBgP4DaiEBQQghAgsgAkEBayECIAFBAXQhASADQQF0IgNBgIACSQ0ACyADIQYgEyATRSAVGwshAyAHIBJqIgkgCiALIAMgCSgCACIJQR92RhsgCWo2AgAgEEGAgIAgciEQCyAFIBBBgMCAgARxQYDAAEYEfyAGIA9BEEEPQQ4gEEGAvA9xGyAQQYCAgIACcRtBAnRqIg0oAgAiCSgCACIDayEGAn8gAyABQRB2TQRAIAEgA0EQdGshASAGQYCAAnEEQCAJKAIEDAILIAkoAgQhEyANIAlBDEEIIAMgBksiFRtqKAIANgIAA0ACQCACDQAgBCgCECIJQQFqIQIgCS0AASEDIAktAABB/wFHBEAgBCACNgIQQQghAiADQQh0IAFqIQEMAQsgA0GPAU0EQCAEIAI2AhAgA0EJdCABaiEBQQchAgwBCyAEIAQoAgxBAWo2AgwgAUGA/gNqIQFBCCECCyACQQFrIQIgAUEBdCEBIAZBAXQiBkGAgAJJDQALIBNFIBMgFRsMAQsgCSgCBCETIA0gCUEIQQwgAyAGSyIVG2ooAgA2AgADQAJAIAINACAEKAIQIglBAWohAiAJLQABIQYgCS0AAEH/AUcEQCAEIAI2AhBBCCECIAZBCHQgAWohAQwBCyAGQY8BTQRAIAQgAjYCECAGQQl0IAFqIQFBByECDAELIAQgBCgCDEEBajYCDCABQYD+A2ohAUEIIQILIAJBAWshAiABQQF0IQEgA0EBdCIDQYCAAkkNAAsgAyEGIBMgE0UgFRsLIQMgByAUQQJ0aiIJIAogCyADIAkoAgAiCUEfdkYbIAlqNgIAIBBBgICAgAJyBSAQCzYCAAsgBUEEaiEJIAdBBGohByAIQQFqIgggDEcNAAsgBUEMaiEJIAcgFEECdGohByARQQRqIhEgBCgCgAEiA0F8cUkNAAsMAQtBBCADQXxxIgkgCUEETRtBAWsiCUF8cUEEaiERIAggCUEBdEF4cWpBFGohCQsgBCACNgIIIAQgBjYCBCAEIAE2AgAgBCANNgJoIAxFDQEgAyARTQ0BQQAhE0EAIAtrIRQgAyEBA0ACQCABIBFGBEAgESEBDAELIAkoAgAhAkEAIRADQEGQgIABIBBBA2wiCHQgAnFBECAIdEYEQCAHIAwgEGxBAnRqIQUgBCAPQRBBD0EOIAIgCHYiAUHvA3EbIAFBgIDAAHEbQQJ0aiINNgJoIAQgBCgCBCANKAIAIgIoAgAiAWsiAzYCBAJ/IAEgBCgCACIGQRB2SwRAIAIoAgQhCiAEIAE2AgQgDSACQQhBDCABIANLIg4baigCADYCACAEKAIIIQIDQAJAIAINACAEKAIQIgJBAWohDSACLQABIQMgAi0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCAGQYD+A2ohBkEIIQIMAgsgBCANNgIQIANBCXQgBmohBkEHIQIMAQsgBCANNgIQQQghAiADQQh0IAZqIQYLIAQgAkEBayICNgIIIAQgBkEBdCIGNgIAIAQgAUEBdCIBNgIEIAFBgIACSQ0ACyAKIApFIA4bDAELIAQgBiABQRB0ayIGNgIAIANBgIACcUUEQCACKAIEIQogDSACQQxBCCABIANLIg4baigCADYCACAEKAIIIQIDQAJAIAINACAEKAIQIgJBAWohDSACLQABIQEgAi0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCAGQYD+A2ohBkEIIQIMAgsgBCANNgIQIAFBCXQgBmohBkEHIQIMAQsgBCANNgIQQQghAiABQQh0IAZqIQYLIAQgAkEBayICNgIIIAQgBkEBdCIGNgIAIAQgA0EBdCIDNgIEIANBgIACSQ0ACyAKRSAKIA4bDAELIAIoAgQLIQEgBSAUIAsgASAFKAIAIgNBH3ZGGyADajYCACAJIAkoAgBBgIDAACAIdHIiAjYCACAEKAKAASEDCyAQQQFqIhAgAyIBIBFrSQ0ACwsgCUEEaiEJIAdBBGohByATQQFqIhMgDEcNAAsMAQtBACERQQAhFwJAAkACQAJAIAQoAnwiFEHAAEcNACAEKAKAAUHAAEcNAEEAQQEgGXQiAUEBdiABciIUayETIARB5ABqIQggBEHgAGohECAEQRxqIQsgBCgCeEGMAmohBiAEKAIIIQUgBCgCBCEBIAQoAgAhAiAEKAJoIQkgBCgCdCEDIBZBCHENAQNAQQAhDANAIAMhEQJAAkACfwJAAkAgBiINKAIAIgZFBEAgASAQKAIAIgMoAgAiBmshAQJ/IAYgAkEQdksEQCADKAIEIQcgECADQQhBDCABIAZJIgobaigCADYCAANAAkAgBQ0AIAQoAhAiA0EBaiEJIAMtAAEhASADLQAAQf8BRgRAIAFBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAk2AhAgAUEJdCACaiECQQchBQwBCyAEIAk2AhBBCCEFIAFBCHQgAmohAgsgBUEBayEFIAJBAXQhAiAGQQF0IgZBgIACSQ0ACyAGIQEgByAHRSAKGwwBCyACIAZBEHRrIQIgAUGAgAJxRQRAIAMoAgQhByAQIANBDEEIIAEgBkkiChtqKAIANgIAA0ACQCAFDQAgBCgCECIGQQFqIQkgBi0AASEDIAYtAABB/wFGBEAgA0GQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgCTYCECADQQl0IAJqIQJBByEFDAELIAQgCTYCEEEIIQUgA0EIdCACaiECCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIAdFIAcgChsMAQsgAygCBAtFBEAgECEJDAYLIAEgCCgCACIDKAIAIgZrIQECfyAGIAJBEHZLBEAgAygCBCEHIAggA0EIQQwgASAGSSIKG2ooAgAiAzYCAANAAkAgBQ0AIAQoAhAiCUEBaiEFIAktAAEhASAJLQAAQf8BRgRAIAFBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAU2AhAgAUEJdCACaiECQQchBQwBCyAEIAU2AhBBCCEFIAFBCHQgAmohAgsgBUEBayEFIAJBAXQhAiAGQQF0IgZBgIACSQ0ACyAGIQEgByAHRSAKGwwBCyACIAZBEHRrIQIgAUGAgAJxRQRAIAMoAgQhByAIIANBDEEIIAEgBkkiChtqKAIAIgM2AgADQAJAIAUNACAEKAIQIglBAWohBSAJLQABIQYgCS0AAEH/AUYEQCAGQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAFNgIQIAZBCXQgAmohAkEHIQUMAQsgBCAFNgIQQQghBSAGQQh0IAJqIQILIAVBAWshBSACQQF0IQIgAUEBdCIBQYCAAkkNAAsgB0UgByAKGwwBCyADKAIECyEKIAEgAygCACIGayEBAn8gBiACQRB2SwRAIAMoAgQhByAIIANBCEEMIAEgBkkiDhtqKAIANgIAA0ACQCAFDQAgBCgCECIDQQFqIQkgAy0AASEBIAMtAABB/wFGBEAgAUGQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgCTYCECABQQl0IAJqIQJBByEFDAELIAQgCTYCEEEIIQUgAUEIdCACaiECCyAFQQFrIQUgAkEBdCECIAZBAXQiBkGAgAJJDQALIAYhASAHIAdFIA4bDAELIAIgBkEQdGshAiABQYCAAnFFBEAgAygCBCEHIAggA0EMQQggASAGSSIOG2ooAgA2AgADQAJAIAUNACAEKAIQIgZBAWohCSAGLQABIQMgBi0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAJNgIQIANBCXQgAmohAkEHIQUMAQsgBCAJNgIQQQghBSADQQh0IAJqIQILIAVBAWshBSACQQF0IQIgAUEBdCIBQYCAAkkNAAsgB0UgByAOGwwBCyADKAIECyEDQQAhBiAIIQkCQAJAAkACfwJAAkAgAyAKQQF0cg4EAAEDBQoLIAEgCyANKAIEQRF2QQRxIA1BBGsiBygCAEETdkEBcXIiDkHQuQFqLQAAQQJ0aiIJKAIAIgMoAgAiBmshAQJ/IAYgAkEQdksEQCADKAIEIQogCSADQQhBDCABIAZJIhIbaigCADYCAANAAkAgBQ0AIAQoAhAiA0EBaiEJIAMtAAEhASADLQAAQf8BRgRAIAFBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAk2AhAgAUEJdCACaiECQQchBQwBCyAEIAk2AhBBCCEFIAFBCHQgAmohAgsgBUEBayEFIAJBAXQhAiAGQQF0IgZBgIACSQ0ACyAGIQEgCiAKRSASGwwBCyACIAZBEHRrIQIgAUGAgAJxRQRAIAMoAgQhCiAJIANBDEEIIAEgBkkiEhtqKAIANgIAA0ACQCAFDQAgBCgCECIGQQFqIQkgBi0AASEDIAYtAABB/wFGBEAgA0GQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgCTYCECADQQl0IAJqIQJBByEFDAELIAQgCTYCEEEIIQUgA0EIdCACaiECCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIApFIAogEhsMAQsgAygCBAshAyARIBMgFCADIA5B0LsBai0AAHMiAxs2AgAgByAHKAIAQSByNgIAIA0gDSgCBEEIcjYCBCANQYwCayIGIAYoAgBBgIAIcjYCACANQYQCayIGIAYoAgBBgIACcjYCACANQYgCayIGIAYoAgAgA0EfdHJBgIAEcjYCACADQRN0IVcgASALIAQoAmwtAAJBAnRqIgcoAgAiAygCACIGayEBAn8gBiACQRB2SwRAIAMoAgQhCSAHIANBCEEMIAEgBkkiDhtqKAIANgIAA0ACQCAFDQAgBCgCECIDQQFqIQcgAy0AASEBIAMtAABB/wFGBEAgAUGQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBzYCECABQQl0IAJqIQJBByEFDAELIAQgBzYCEEEIIQUgAUEIdCACaiECCyAFQQFrIQUgAkEBdCECIAZBAXQiBkGAgAJJDQALIAYhASAJIAlFIA4bDAELIAIgBkEQdGshAiABQYCAAnFFBEAgAygCBCEJIAcgA0EMQQggASAGSSIOG2ooAgA2AgADQAJAIAUNACAEKAIQIgZBAWohByAGLQABIQMgBi0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAHNgIQIANBCXQgAmohAkEHIQUMAQsgBCAHNgIQQQghBSADQQh0IAJqIQILIAVBAWshBSACQQF0IQIgAUEBdCIBQYCAAkkNAAsgCUUgCSAOGwwBCyADKAIECyEDIFdBEHIiBiADRQ0BGgsgASALIA0oAgRBFHZBBHEgDUEEayIJKAIAQRZ2QQFxIAZBD3ZBEHEgBkETdkHAAHEgBkEDdkGqAXFycnJyIhJB0LkBai0AAEECdGoiCigCACIHKAIAIgNrIQECfyADIAJBEHZLBEAgBygCBCEOIAogB0EIQQwgASADSSIKG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAFNgIQIAFBCXQgAmohAkEHIQUMAQsgBCAFNgIQQQghBSABQQh0IAJqIQILIAVBAWshBSACQQF0IQIgA0EBdCIDQYCAAkkNAAsgAyEBIA4gDkUgChsMAQsgAiADQRB0ayECIAFBgIACcUUEQCAHKAIEIQ4gCiAHQQxBCCABIANJIgobaigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEFIActAAEhAyAHLQAAQf8BRgRAIANBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAU2AhAgA0EJdCACaiECQQchBQwBCyAEIAU2AhBBCCEFIANBCHQgAmohAgsgBUEBayEFIAJBAXQhAiABQQF0IgFBgIACSQ0ACyAORSAOIAobDAELIAcoAgQLIQMgESATIBQgAyASQdC7AWotAABzIgMbNgKAAiAJIAkoAgBBgAJyNgIAIA0gDSgCBEHAAHI2AgQgBiADQRZ0ckGAAXILIQYgASALIAQoAmwgBkEGdkHvA3FqLQAAQQJ0aiIJKAIAIgcoAgAiA2shAQJ/IAMgAkEQdksEQCAHKAIEIQogCSAHQQhBDCABIANJIg4baigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEJIActAAEhASAHLQAAQf8BRgRAIAFBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAk2AhAgAUEJdCACaiECQQchBQwBCyAEIAk2AhBBCCEFIAFBCHQgAmohAgsgBUEBayEFIAJBAXQhAiADQQF0IgNBgIACSQ0ACyADIQEgCiAKRSAOGwwBCyACIANBEHRrIQIgAUGAgAJxRQRAIAcoAgQhCiAJIAdBDEEIIAEgA0kiDhtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQkgBy0AASEDIActAABB/wFGBEAgA0GQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgCTYCECADQQl0IAJqIQJBByEFDAELIAQgCTYCEEEIIQUgA0EIdCACaiECCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIApFIAogDhsMAQsgBygCBAtFDQELIAEgCyANKAIEQRd2QQRxIA1BBGsiCSgCAEEZdkEBcSAGQRJ2QRBxIAZBFnZBwABxIAZBBnZBqgFxcnJyciISQdC5AWotAABBAnRqIgooAgAiBygCACIDayEBAn8gAyACQRB2SwRAIAcoAgQhDiAKIAdBCEEMIAEgA0kiChtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEBIActAABB/wFGBEAgAUGQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBTYCEEEIIQUgAUEIdCACaiECCyAFQQFrIQUgAkEBdCECIANBAXQiA0GAgAJJDQALIAMhASAOIA5FIAobDAELIAIgA0EQdGshAiABQYCAAnFFBEAgBygCBCEOIAogB0EMQQggASADSSIKG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQMgBy0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAFNgIQIANBCXQgAmohAkEHIQUMAQsgBCAFNgIQQQghBSADQQh0IAJqIQILIAVBAWshBSACQQF0IQIgAUEBdCIBQYCAAkkNAAsgDkUgDiAKGwwBCyAHKAIECyEDIBEgEyAUIAMgEkHQuwFqLQAAcyIDGzYCgAQgCSAJKAIAQYAQcjYCACANIA0oAgRBgARyNgIEIAYgA0EZdHJBgAhyIQYLIAEgCyAEKAJsIAZBCXZB7wNxai0AAEECdGoiCSgCACIHKAIAIgNrIQECfyADIAJBEHZLBEAgBygCBCEKIAkgB0EIQQwgASADSSIOG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAFNgIQIAFBCXQgAmohAkEHIQUMAQsgBCAFNgIQQQghBSABQQh0IAJqIQILIAVBAWshBSACQQF0IQIgA0EBdCIDQYCAAkkNAAsgAyEBIAogCkUgDhsMAQsgAiADQRB0ayECIAFBgIACcUUEQCAHKAIEIQogCSAHQQxBCCABIANJIg4baigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEFIActAAEhAyAHLQAAQf8BRgRAIANBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAU2AhAgA0EJdCACaiECQQchBQwBCyAEIAU2AhBBCCEFIANBCHQgAmohAgsgBUEBayEFIAJBAXQhAiABQQF0IgFBgIACSQ0ACyAKRSAKIA4bDAELIAcoAgQLRQ0FCyABIAsgDSgCBEEadkEEcSANQQRrIg4oAgBBHHZBAXEgBkEVdkEQcSAGQRl2QcAAcSAGQQl2QaoBcXJycnIiCkHQuQFqLQAAQQJ0aiIJKAIAIgcoAgAiA2shASADIAJBEHZLBEAgBygCBCESIAkgB0EIQQwgASADSSIVG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAFNgIQIAFBCXQgAmohAkEHIQUMAQsgBCAFNgIQQQghBSABQQh0IAJqIQILIAVBAWshBSACQQF0IQIgA0EBdCIDQYCAAkkNAAsgAyEBIBIgEkUgFRsMBAsgAiADQRB0ayECIAFBgIACcQ0BIAcoAgQhEiAJIAdBDEEIIAEgA0kiFRtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEDIActAABB/wFGBEAgA0GQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBTYCECADQQl0IAJqIQJBByEFDAELIAQgBTYCEEEIIQUgA0EIdCACaiECCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIBJFIBIgFRsMAwsCQCAGQZCAgAFxDQAgASALIAQoAmwgBkHvA3FqLQAAQQJ0aiIJKAIAIgcoAgAiA2shAQJ/IAMgAkEQdksEQCAHKAIEIQogCSAHQQhBDCABIANJIg4baigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEFIActAAEhASAHLQAAQf8BRgRAIAFBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAU2AhAgAUEJdCACaiECQQchBQwBCyAEIAU2AhBBCCEFIAFBCHQgAmohAgsgBUEBayEFIAJBAXQhAiADQQF0IgNBgIACSQ0ACyADIQEgCiAKRSAOGwwBCyACIANBEHRrIQIgAUGAgAJxRQRAIAcoAgQhCiAJIAdBDEEIIAEgA0kiDhtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEDIActAABB/wFGBEAgA0GQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBTYCECADQQl0IAJqIQJBByEFDAELIAQgBTYCEEEIIQUgA0EIdCACaiECCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIApFIAogDhsMAQsgBygCBAtFDQAgASALIA0oAgRBEXZBBHEgDUEEayIKKAIAQRN2QQFxIAZBDnZBEHEgBkEQdkHAAHEgBkGqAXFycnJyIhJB0LkBai0AAEECdGoiCSgCACIHKAIAIgNrIQECfyADIAJBEHZLBEAgBygCBCEOIAkgB0EIQQwgASADSSIVG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAFNgIQIAFBCXQgAmohAkEHIQUMAQsgBCAFNgIQQQghBSABQQh0IAJqIQILIAVBAWshBSACQQF0IQIgA0EBdCIDQYCAAkkNAAsgAyEBIA4gDkUgFRsMAQsgAiADQRB0ayECIAFBgIACcUUEQCAHKAIEIQ4gCSAHQQxBCCABIANJIhUbaigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEFIActAAEhAyAHLQAAQf8BRgRAIANBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAU2AhAgA0EJdCACaiECQQchBQwBCyAEIAU2AhBBCCEFIANBCHQgAmohAgsgBUEBayEFIAJBAXQhAiABQQF0IgFBgIACSQ0ACyAORSAOIBUbDAELIAcoAgQLIQMgESATIBQgAyASQdC7AWotAABzIgMbNgIAIAogCigCAEEgcjYCACANIA0oAgRBCHI2AgQgDUGMAmsiByAHKAIAQYCACHI2AgAgDUGEAmsiByAHKAIAQYCAAnI2AgAgDUGIAmsiByAHKAIAIANBH3RyQYCABHI2AgAgBiADQRN0ckEQciEGCwJAIAZBgIGACHENACABIAsgBCgCbCAGQQN2Ig5B7wNxai0AAEECdGoiCSgCACIHKAIAIgNrIQECfyADIAJBEHZLBEAgBygCBCEKIAkgB0EIQQwgASADSSISG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAFNgIQIAFBCXQgAmohAkEHIQUMAQsgBCAFNgIQQQghBSABQQh0IAJqIQILIAVBAWshBSACQQF0IQIgA0EBdCIDQYCAAkkNAAsgAyEBIAogCkUgEhsMAQsgAiADQRB0ayECIAFBgIACcUUEQCAHKAIEIQogCSAHQQxBCCABIANJIhIbaigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEFIActAAEhAyAHLQAAQf8BRgRAIANBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAU2AhAgA0EJdCACaiECQQchBQwBCyAEIAU2AhBBCCEFIANBCHQgAmohAgsgBUEBayEFIAJBAXQhAiABQQF0IgFBgIACSQ0ACyAKRSAKIBIbDAELIAcoAgQLRQ0AIAEgCyANKAIEQRR2QQRxIA1BBGsiCigCAEEWdkEBcSAGQQ92QRBxIAZBE3ZBwABxIA5BqgFxcnJyciISQdC5AWotAABBAnRqIgkoAgAiBygCACIDayEBAn8gAyACQRB2SwRAIAcoAgQhDiAJIAdBCEEMIAEgA0kiFRtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEBIActAABB/wFGBEAgAUGQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBTYCEEEIIQUgAUEIdCACaiECCyAFQQFrIQUgAkEBdCECIANBAXQiA0GAgAJJDQALIAMhASAOIA5FIBUbDAELIAIgA0EQdGshAiABQYCAAnFFBEAgBygCBCEOIAkgB0EMQQggASADSSIVG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQMgBy0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAFNgIQIANBCXQgAmohAkEHIQUMAQsgBCAFNgIQQQghBSADQQh0IAJqIQILIAVBAWshBSACQQF0IQIgAUEBdCIBQYCAAkkNAAsgDkUgDiAVGwwBCyAHKAIECyEDIBEgEyAUIAMgEkHQuwFqLQAAcyIDGzYCgAIgCiAKKAIAQYACcjYCACANIA0oAgRBwAByNgIEIAYgA0EWdHJBgAFyIQYLAkAgBkGAiIDAAHENACABIAsgBCgCbCAGQQZ2Ig5B7wNxai0AAEECdGoiCSgCACIHKAIAIgNrIQECfyADIAJBEHZLBEAgBygCBCEKIAkgB0EIQQwgASADSSISG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAFNgIQIAFBCXQgAmohAkEHIQUMAQsgBCAFNgIQQQghBSABQQh0IAJqIQILIAVBAWshBSACQQF0IQIgA0EBdCIDQYCAAkkNAAsgAyEBIAogCkUgEhsMAQsgAiADQRB0ayECIAFBgIACcUUEQCAHKAIEIQogCSAHQQxBCCABIANJIhIbaigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEFIActAAEhAyAHLQAAQf8BRgRAIANBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAU2AhAgA0EJdCACaiECQQchBQwBCyAEIAU2AhBBCCEFIANBCHQgAmohAgsgBUEBayEFIAJBAXQhAiABQQF0IgFBgIACSQ0ACyAKRSAKIBIbDAELIAcoAgQLRQ0AIAEgCyANKAIEQRd2QQRxIA1BBGsiCigCAEEZdkEBcSAGQRJ2QRBxIAZBFnZBwABxIA5BqgFxcnJyciISQdC5AWotAABBAnRqIgkoAgAiBygCACIDayEBAn8gAyACQRB2SwRAIAcoAgQhDiAJIAdBCEEMIAEgA0kiFRtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEBIActAABB/wFGBEAgAUGQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBTYCEEEIIQUgAUEIdCACaiECCyAFQQFrIQUgAkEBdCECIANBAXQiA0GAgAJJDQALIAMhASAOIA5FIBUbDAELIAIgA0EQdGshAiABQYCAAnFFBEAgBygCBCEOIAkgB0EMQQggASADSSIVG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQMgBy0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAFNgIQIANBCXQgAmohAkEHIQUMAQsgBCAFNgIQQQghBSADQQh0IAJqIQILIAVBAWshBSACQQF0IQIgAUEBdCIBQYCAAkkNAAsgDkUgDiAVGwwBCyAHKAIECyEDIBEgEyAUIAMgEkHQuwFqLQAAcyIDGzYCgAQgCiAKKAIAQYAQcjYCACANIA0oAgRBgARyNgIEIAYgA0EZdHJBgAhyIQYLIAZBgMCAgARxDQMgASALIAQoAmwgBkEJdiISQe8DcWotAABBAnRqIgkoAgAiASgCACIDayEHAn8gAyACQRB2SwRAIAEoAgQhCiAJIAFBCEEMIAMgB0siDhtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEBIActAABB/wFGBEAgAUGQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBTYCEEEIIQUgAUEIdCACaiECCyAFQQFrIQUgAkEBdCECIANBAXQiA0GAgAJJDQALIAMhByAKIApFIA4bDAELIAIgA0EQdGshAiAHQYCAAnFFBEAgASgCBCEKIAkgAUEMQQggAyAHSyIOG2ooAgA2AgADQAJAIAUNACAEKAIQIgNBAWohBSADLQABIQEgAy0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAFNgIQIAFBCXQgAmohAkEHIQUMAQsgBCAFNgIQQQghBSABQQh0IAJqIQILIAVBAWshBSACQQF0IQIgB0EBdCIHQYCAAkkNAAsgCkUgCiAOGwwBCyABKAIEC0UEQCAHIQEMBAsgByALIA0oAgRBGnZBBHEgDUEEayIOKAIAQRx2QQFxIAZBFXZBEHEgBkEZdkHAAHEgEkGqAXFycnJyIgpB0LkBai0AAEECdGoiCSgCACIHKAIAIgFrIQMgASACQRB2SwRAIAcoAgQhEiAJIAdBCEEMIAEgA0siFRtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEDIActAABB/wFGBEAgA0GQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBTYCECADQQl0IAJqIQJBByEFDAELIAQgBTYCEEEIIQUgA0EIdCACaiECCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIBIgEkUgFRsMAwsgAiABQRB0ayECIANBgIACcUUNASADIQELIAcoAgQMAQsgBygCBCESIAkgB0EMQQggASADSyIVG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAFNgIQIAFBCXQgAmohAkEHIQUMAQsgBCAFNgIQQQghBSABQQh0IAJqIQILIAVBAWshBSACQQF0IQIgA0EBdCIDQYCAAkkNAAsgAyEBIBJFIBIgFRsLIQMgESATIBQgAyAKQdC7AWotAABzIgMbNgKABiAOIA4oAgBBgIABcjYCACANIA0oAgRBgCByNgIEIA0gDSgChAJBBHI2AoQCIA0gDSgCjAJBAXI2AowCIA0gDSgCiAIgA0ESdHJBAnI2AogCIAYgA0EcdHJBgMAAciEGCyANIAZB////tntxNgIACyANQQRqIQYgEUEEaiEDIAxBAWoiDEHAAEcNAAsgDUEMaiEGIBFBhAZqIQMgF0E8SSFYIBdBBGohFyBYDQALDAILQQEgGXQiAUEBdiABciEOIAQoAngiByAUQQJ0akEMaiEDIAQoAoABIQYgBCgCCCEFIAQoAgQhASAEKAIAIQIgBCgCaCEJIAQoAnQhCwJAAkAgFkEIcQRAIAZBBEkNAiAURQ0BIARB5ABqIRAgBEHgAGohDSAUQQNsIRsgFEEBdCEkQQAgDmshFSAEQRxqIRIDQEEAIRgDQAJAAkACfwJAIAMiCCgCACIDBEACQCADQZCAgAFxDQAgASASIAQoAmwgA0HvA3FqLQAAQQJ0aiIJKAIAIgcoAgAiBmshAQJ/IAYgAkEQdk0EQCACIAZBEHRrIQIgAUGAgAJxBEAgBygCBAwCCyAHKAIEIQwgCSAHQQxBCCABIAZJIgobaigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEFIActAAEhBiAHLQAAQf8BRwRAIAQgBTYCEEEIIQUgBkEIdCACaiECDAELIAZBjwFNBEAgBCAFNgIQIAZBCXQgAmohAkEHIQUMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQsgBUEBayEFIAJBAXQhAiABQQF0IgFBgIACSQ0ACyAMRSAMIAobDAELIAcoAgQhDCAJIAdBCEEMIAEgBkkiChtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEBIActAABB/wFHBEAgBCAFNgIQQQghBSABQQh0IAJqIQIMAQsgAUGPAU0EQCAEIAU2AhAgAUEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAZBAXQiBkGAgAJJDQALIAYhASAMIAxFIAobC0UNACABIBIgCCgCBEERdkEEcSAIQQRrIgwoAgBBE3ZBAXEgA0EOdkEQcSADQRB2QcAAcSADQaoBcXJycnIiE0HQuQFqLQAAQQJ0aiIJKAIAIgcoAgAiBmshAQJ/IAYgAkEQdk0EQCACIAZBEHRrIQIgAUGAgAJxBEAgBygCBAwCCyAHKAIEIQogCSAHQQxBCCABIAZJIhwbaigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEFIActAAEhBiAHLQAAQf8BRwRAIAQgBTYCEEEIIQUgBkEIdCACaiECDAELIAZBjwFNBEAgBCAFNgIQIAZBCXQgAmohAkEHIQUMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQsgBUEBayEFIAJBAXQhAiABQQF0IgFBgIACSQ0ACyAKRSAKIBwbDAELIAcoAgQhCiAJIAdBCEEMIAEgBkkiHBtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEBIActAABB/wFHBEAgBCAFNgIQQQghBSABQQh0IAJqIQIMAQsgAUGPAU0EQCAEIAU2AhAgAUEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAZBAXQiBkGAgAJJDQALIAYhASAKIApFIBwbCyEGIAsgFSAOIAYgE0HQuwFqLQAAcyIGGzYCACAMIAwoAgBBIHI2AgAgCCAIKAIEQQhyNgIEIAMgBkETdHJBEHIhAwsCQCADQYCBgAhxDQAgASASIAQoAmwgA0EDdiIKQe8DcWotAABBAnRqIgkoAgAiBygCACIGayEBAn8gBiACQRB2TQRAIAIgBkEQdGshAiABQYCAAnEEQCAHKAIEDAILIAcoAgQhDCAJIAdBDEEIIAEgBkkiExtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEGIActAABB/wFHBEAgBCAFNgIQQQghBSAGQQh0IAJqIQIMAQsgBkGPAU0EQCAEIAU2AhAgBkEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIAxFIAwgExsMAQsgBygCBCEMIAkgB0EIQQwgASAGSSITG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUcEQCAEIAU2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAwgDEUgExsLRQ0AIAEgEiAIKAIEQRR2QQRxIAhBBGsiDCgCAEEWdkEBcSADQQ92QRBxIANBE3ZBwABxIApBqgFxcnJyciITQdC5AWotAABBAnRqIgkoAgAiBygCACIGayEBAn8gBiACQRB2TQRAIAIgBkEQdGshAiABQYCAAnEEQCAHKAIEDAILIAcoAgQhCiAJIAdBDEEIIAEgBkkiHBtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEGIActAABB/wFHBEAgBCAFNgIQQQghBSAGQQh0IAJqIQIMAQsgBkGPAU0EQCAEIAU2AhAgBkEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIApFIAogHBsMAQsgBygCBCEKIAkgB0EIQQwgASAGSSIcG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUcEQCAEIAU2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAogCkUgHBsLIQYgCyAUQQJ0aiAVIA4gBiATQdC7AWotAABzIgYbNgIAIAwgDCgCAEGAAnI2AgAgCCAIKAIEQcAAcjYCBCADIAZBFnRyQYABciEDCwJAIANBgIiAwABxDQAgASASIAQoAmwgA0EGdiIKQe8DcWotAABBAnRqIgkoAgAiBygCACIGayEBAn8gBiACQRB2TQRAIAIgBkEQdGshAiABQYCAAnEEQCAHKAIEDAILIAcoAgQhDCAJIAdBDEEIIAEgBkkiExtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEGIActAABB/wFHBEAgBCAFNgIQQQghBSAGQQh0IAJqIQIMAQsgBkGPAU0EQCAEIAU2AhAgBkEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIAxFIAwgExsMAQsgBygCBCEMIAkgB0EIQQwgASAGSSITG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUcEQCAEIAU2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAwgDEUgExsLRQ0AIAEgEiAIKAIEQRd2QQRxIAhBBGsiDCgCAEEZdkEBcSADQRJ2QRBxIANBFnZBwABxIApBqgFxcnJyciITQdC5AWotAABBAnRqIgkoAgAiBygCACIGayEBAn8gBiACQRB2TQRAIAIgBkEQdGshAiABQYCAAnEEQCAHKAIEDAILIAcoAgQhCiAJIAdBDEEIIAEgBkkiHBtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEGIActAABB/wFHBEAgBCAFNgIQQQghBSAGQQh0IAJqIQIMAQsgBkGPAU0EQCAEIAU2AhAgBkEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIApFIAogHBsMAQsgBygCBCEKIAkgB0EIQQwgASAGSSIcG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUcEQCAEIAU2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAogCkUgHBsLIQYgCyAkQQJ0aiAVIA4gBiATQdC7AWotAABzIgYbNgIAIAwgDCgCAEGAEHI2AgAgCCAIKAIEQYAEcjYCBCADIAZBGXRyQYAIciEDCyADQYDAgIAEcQ0DIAEgEiAEKAJsIANBCXYiCkHvA3FqLQAAQQJ0aiIJKAIAIgEoAgAiBmshBwJ/IAYgAkEQdk0EQCACIAZBEHRrIQIgB0GAgAJxBEAgASgCBAwCCyABKAIEIQwgCSABQQxBCCAGIAdLIhMbaigCADYCAANAAkAgBQ0AIAQoAhAiBkEBaiEFIAYtAAEhASAGLQAAQf8BRwRAIAQgBTYCEEEIIQUgAUEIdCACaiECDAELIAFBjwFNBEAgBCAFNgIQIAFBCXQgAmohAkEHIQUMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQsgBUEBayEFIAJBAXQhAiAHQQF0IgdBgIACSQ0ACyAMRSAMIBMbDAELIAEoAgQhDCAJIAFBCEEMIAYgB0siExtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEBIActAABB/wFHBEAgBCAFNgIQQQghBSABQQh0IAJqIQIMAQsgAUGPAU0EQCAEIAU2AhAgAUEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAZBAXQiBkGAgAJJDQALIAYhByAMIAxFIBMbC0UEQCAHIQEMBAsgByASIAgoAgRBGnZBBHEgCEEEayIMKAIAQRx2QQFxIANBFXZBEHEgA0EZdkHAAHEgCkGqAXFycnJyIhNB0LkBai0AAEECdGoiCSgCACIKKAIAIgFrIQYgASACQRB2TQRAIAIgAUEQdGshAiAGQYCAAnEEQCAGIQEMAwsgCigCBCEHIAkgCkEMQQggASAGSyIcG2ooAgA2AgADQAJAIAUNACAEKAIQIgVBAWohCiAFLQABIQEgBS0AAEH/AUcEQCAEIAo2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgCjYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAdFIAcgHBsMAwsgCigCBCEHIAkgCkEIQQwgASAGSyIcG2ooAgA2AgADQAJAIAUNACAEKAIQIgVBAWohCiAFLQABIQYgBS0AAEH/AUcEQCAEIAo2AhBBCCEFIAZBCHQgAmohAgwBCyAGQY8BTQRAIAQgCjYCECAGQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgAUEBdCIBQYCAAkkNAAsgByAHRSAcGwwCCyABIA0oAgAiBigCACIDayEBAn8gAyACQRB2TQRAIAIgA0EQdGshAiABQYCAAnEEQCAGKAIEDAILIAYoAgQhByANIAZBDEEIIAEgA0kiDBtqKAIANgIAA0ACQCAFDQAgBCgCECIGQQFqIQkgBi0AASEDIAYtAABB/wFHBEAgBCAJNgIQQQghBSADQQh0IAJqIQIMAQsgA0GPAU0EQCAEIAk2AhAgA0EJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIAdFIAcgDBsMAQsgBigCBCEHIA0gBkEIQQwgASADSSIMG2ooAgA2AgADQAJAIAUNACAEKAIQIgZBAWohCSAGLQABIQEgBi0AAEH/AUcEQCAEIAk2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgCTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgA0EBdCIDQYCAAkkNAAsgAyEBIAcgB0UgDBsLRQRAIA0hCQwECyABIBAoAgAiBigCACIDayEBAn8gAyACQRB2TQRAIAIgA0EQdGshAiABQYCAAnEEQCAGKAIEDAILIAYoAgQhByAQIAZBDEEIIAEgA0kiDBtqKAIAIgY2AgADQAJAIAUNACAEKAIQIglBAWohBSAJLQABIQMgCS0AAEH/AUcEQCAEIAU2AhBBCCEFIANBCHQgAmohAgwBCyADQY8BTQRAIAQgBTYCECADQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgAUEBdCIBQYCAAkkNAAsgB0UgByAMGwwBCyAGKAIEIQcgECAGQQhBDCABIANJIgwbaigCACIGNgIAA0ACQCAFDQAgBCgCECIJQQFqIQUgCS0AASEBIAktAABB/wFHBEAgBCAFNgIQQQghBSABQQh0IAJqIQIMAQsgAUGPAU0EQCAEIAU2AhAgAUEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIANBAXQiA0GAgAJJDQALIAMhASAHIAdFIAwbCyEMIAEgBigCACIDayEBAn8gAyACQRB2TQRAIAIgA0EQdGshAiABQYCAAnEEQCAGKAIEDAILIAYoAgQhByAQIAZBDEEIIAEgA0kiChtqKAIANgIAA0ACQCAFDQAgBCgCECIGQQFqIQkgBi0AASEDIAYtAABB/wFHBEAgBCAJNgIQQQghBSADQQh0IAJqIQIMAQsgA0GPAU0EQCAEIAk2AhAgA0EJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIAdFIAcgChsMAQsgBigCBCEHIBAgBkEIQQwgASADSSIKG2ooAgA2AgADQAJAIAUNACAEKAIQIgZBAWohCSAGLQABIQEgBi0AAEH/AUcEQCAEIAk2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgCTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgA0EBdCIDQYCAAkkNAAsgAyEBIAcgB0UgChsLIQZBACEDIBAhCQJAAkACQAJ/AkACQCAGIAxBAXRyDgQAAQMFCAsgASASIAgoAgRBEXZBBHEgCEEEayIHKAIAQRN2QQFxciIKQdC5AWotAABBAnRqIgkoAgAiBigCACIDayEBAn8gAyACQRB2TQRAIAIgA0EQdGshAiABQYCAAnEEQCAGKAIEDAILIAYoAgQhDCAJIAZBDEEIIAEgA0kiExtqKAIANgIAA0ACQCAFDQAgBCgCECIGQQFqIQkgBi0AASEDIAYtAABB/wFHBEAgBCAJNgIQQQghBSADQQh0IAJqIQIMAQsgA0GPAU0EQCAEIAk2AhAgA0EJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIAxFIAwgExsMAQsgBigCBCEMIAkgBkEIQQwgASADSSITG2ooAgA2AgADQAJAIAUNACAEKAIQIgZBAWohCSAGLQABIQEgBi0AAEH/AUcEQCAEIAk2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgCTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgA0EBdCIDQYCAAkkNAAsgAyEBIAwgDEUgExsLIQMgCyAVIA4gAyAKQdC7AWotAABzIgMbNgIAIAcgBygCAEEgcjYCACAIIAgoAgRBCHI2AgQgA0ETdCFZIAEgEiAEKAJsLQACQQJ0aiIHKAIAIgYoAgAiA2shAQJ/IAMgAkEQdk0EQCACIANBEHRrIQIgAUGAgAJxBEAgBigCBAwCCyAGKAIEIQkgByAGQQxBCCABIANJIgobaigCADYCAANAAkAgBQ0AIAQoAhAiBkEBaiEHIAYtAAEhAyAGLQAAQf8BRwRAIAQgBzYCEEEIIQUgA0EIdCACaiECDAELIANBjwFNBEAgBCAHNgIQIANBCXQgAmohAkEHIQUMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQsgBUEBayEFIAJBAXQhAiABQQF0IgFBgIACSQ0ACyAJRSAJIAobDAELIAYoAgQhCSAHIAZBCEEMIAEgA0kiChtqKAIANgIAA0ACQCAFDQAgBCgCECIGQQFqIQcgBi0AASEBIAYtAABB/wFHBEAgBCAHNgIQQQghBSABQQh0IAJqIQIMAQsgAUGPAU0EQCAEIAc2AhAgAUEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIANBAXQiA0GAgAJJDQALIAMhASAJIAlFIAobCyEGIFlBEHIiAyAGRQ0BGgsgASASIAgoAgRBFHZBBHEgCEEEayIJKAIAQRZ2QQFxIANBD3ZBEHEgA0ETdkHAAHEgA0EDdkGqAXFycnJyIhNB0LkBai0AAEECdGoiDCgCACIHKAIAIgZrIQECfyAGIAJBEHZNBEAgAiAGQRB0ayECIAFBgIACcQRAIAcoAgQMAgsgBygCBCEKIAwgB0EMQQggASAGSSIMG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQYgBy0AAEH/AUcEQCAEIAU2AhBBCCEFIAZBCHQgAmohAgwBCyAGQY8BTQRAIAQgBTYCECAGQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgAUEBdCIBQYCAAkkNAAsgCkUgCiAMGwwBCyAHKAIEIQogDCAHQQhBDCABIAZJIgwbaigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEFIActAAEhASAHLQAAQf8BRwRAIAQgBTYCEEEIIQUgAUEIdCACaiECDAELIAFBjwFNBEAgBCAFNgIQIAFBCXQgAmohAkEHIQUMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQsgBUEBayEFIAJBAXQhAiAGQQF0IgZBgIACSQ0ACyAGIQEgCiAKRSAMGwshBiALIBRBAnRqIBUgDiAGIBNB0LsBai0AAHMiBhs2AgAgCSAJKAIAQYACcjYCACAIIAgoAgRBwAByNgIEIAMgBkEWdHJBgAFyCyEDIAEgEiAEKAJsIANBBnZB7wNxai0AAEECdGoiCSgCACIHKAIAIgZrIQECfyAGIAJBEHZNBEAgAiAGQRB0ayECIAFBgIACcQRAIAcoAgQMAgsgBygCBCEMIAkgB0EMQQggASAGSSIKG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohCSAHLQABIQYgBy0AAEH/AUcEQCAEIAk2AhBBCCEFIAZBCHQgAmohAgwBCyAGQY8BTQRAIAQgCTYCECAGQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgAUEBdCIBQYCAAkkNAAsgDEUgDCAKGwwBCyAHKAIEIQwgCSAHQQhBDCABIAZJIgobaigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEJIActAAEhASAHLQAAQf8BRwRAIAQgCTYCEEEIIQUgAUEIdCACaiECDAELIAFBjwFNBEAgBCAJNgIQIAFBCXQgAmohAkEHIQUMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQsgBUEBayEFIAJBAXQhAiAGQQF0IgZBgIACSQ0ACyAGIQEgDCAMRSAKGwtFDQELIAEgEiAIKAIEQRd2QQRxIAhBBGsiCSgCAEEZdkEBcSADQRJ2QRBxIANBFnZBwABxIANBBnZBqgFxcnJyciITQdC5AWotAABBAnRqIgwoAgAiBygCACIGayEBAn8gBiACQRB2TQRAIAIgBkEQdGshAiABQYCAAnEEQCAHKAIEDAILIAcoAgQhCiAMIAdBDEEIIAEgBkkiDBtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEGIActAABB/wFHBEAgBCAFNgIQQQghBSAGQQh0IAJqIQIMAQsgBkGPAU0EQCAEIAU2AhAgBkEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIApFIAogDBsMAQsgBygCBCEKIAwgB0EIQQwgASAGSSIMG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUcEQCAEIAU2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAogCkUgDBsLIQYgCyAkQQJ0aiAVIA4gBiATQdC7AWotAABzIgYbNgIAIAkgCSgCAEGAEHI2AgAgCCAIKAIEQYAEcjYCBCADIAZBGXRyQYAIciEDCyABIBIgBCgCbCADQQl2Qe8DcWotAABBAnRqIgkoAgAiBygCACIGayEBAn8gBiACQRB2TQRAIAIgBkEQdGshAiABQYCAAnEEQCAHKAIEDAILIAcoAgQhDCAJIAdBDEEIIAEgBkkiChtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEGIActAABB/wFHBEAgBCAFNgIQQQghBSAGQQh0IAJqIQIMAQsgBkGPAU0EQCAEIAU2AhAgBkEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIAxFIAwgChsMAQsgBygCBCEMIAkgB0EIQQwgASAGSSIKG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUcEQCAEIAU2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAwgDEUgChsLRQ0DCyABIBIgCCgCBEEadkEEcSAIQQRrIgwoAgBBHHZBAXEgA0EVdkEQcSADQRl2QcAAcSADQQl2QaoBcXJycnIiE0HQuQFqLQAAQQJ0aiIJKAIAIgooAgAiBmshASAGIAJBEHZNBEAgAiAGQRB0ayECIAFBgIACcQ0BIAooAgQhByAJIApBDEEIIAEgBkkiHBtqKAIANgIAA0ACQCAFDQAgBCgCECIFQQFqIQogBS0AASEGIAUtAABB/wFHBEAgBCAKNgIQQQghBSAGQQh0IAJqIQIMAQsgBkGPAU0EQCAEIAo2AhAgBkEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIAdFIAcgHBsMAgsgCigCBCEHIAkgCkEIQQwgASAGSSIcG2ooAgA2AgADQAJAIAUNACAEKAIQIgVBAWohCiAFLQABIQEgBS0AAEH/AUcEQCAEIAo2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgCjYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAcgB0UgHBsMAQsgCigCBAshBiALIBtBAnRqIBUgDiAGIBNB0LsBai0AAHMiBxs2AgAgDCAMKAIAQYCAAXI2AgAgCCAIKAIEQYAgcjYCBCAEKAJ8QQJ0IAhqIgYgBigCBEEEcjYCBCAGIAYoAgxBAXI2AgwgBiAGKAIIIAdBEnRyQQJyNgIIIAMgB0EcdHJBgMAAciEDCyAIIANB////tntxNgIACyAIQQRqIQMgC0EEaiELIBhBAWoiGCAURw0ACyAIQQxqIQMgCyAbQQJ0aiELIBFBBGoiESAEKAKAASIGQXxxSQ0ACwwCCwJAIAZBBEkNACAUBEAgBEHkAGohECAEQeAAaiENIBRBA2whGyAUQQF0ISRBACAOayEVIARBHGohEgNAQQAhGANAAkACQAJ/AkAgAyIIKAIAIgMEQAJAIANBkICAAXENACABIBIgBCgCbCADQe8DcWotAABBAnRqIgkoAgAiBygCACIGayEBAn8gBiACQRB2TQRAIAIgBkEQdGshAiABQYCAAnEEQCAHKAIEDAILIAcoAgQhDCAJIAdBDEEIIAEgBkkiChtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEGIActAABB/wFHBEAgBCAFNgIQQQghBSAGQQh0IAJqIQIMAQsgBkGPAU0EQCAEIAU2AhAgBkEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIAxFIAwgChsMAQsgBygCBCEMIAkgB0EIQQwgASAGSSIKG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUcEQCAEIAU2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAwgDEUgChsLRQ0AIAEgEiAIKAIEQRF2QQRxIAhBBGsiDCgCAEETdkEBcSADQQ52QRBxIANBEHZBwABxIANBqgFxcnJyciITQdC5AWotAABBAnRqIgkoAgAiBygCACIGayEBAn8gBiACQRB2TQRAIAIgBkEQdGshAiABQYCAAnEEQCAHKAIEDAILIAcoAgQhCiAJIAdBDEEIIAEgBkkiHBtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEGIActAABB/wFHBEAgBCAFNgIQQQghBSAGQQh0IAJqIQIMAQsgBkGPAU0EQCAEIAU2AhAgBkEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIApFIAogHBsMAQsgBygCBCEKIAkgB0EIQQwgASAGSSIcG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUcEQCAEIAU2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAogCkUgHBsLIQYgCyAVIA4gBiATQdC7AWotAABzIgcbNgIAIAwgDCgCAEEgcjYCACAIIAgoAgRBCHI2AgQgCEF+IAQoAnxrQQJ0aiIGIAYoAgRBgIACcjYCBCAGIAYoAgAgB0EfdHJBgIAEcjYCACAGQQRrIgYgBigCAEGAgAhyNgIAIAMgB0ETdHJBEHIhAwsCQCADQYCBgAhxDQAgASASIAQoAmwgA0EDdiIKQe8DcWotAABBAnRqIgkoAgAiBygCACIGayEBAn8gBiACQRB2TQRAIAIgBkEQdGshAiABQYCAAnEEQCAHKAIEDAILIAcoAgQhDCAJIAdBDEEIIAEgBkkiExtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEGIActAABB/wFHBEAgBCAFNgIQQQghBSAGQQh0IAJqIQIMAQsgBkGPAU0EQCAEIAU2AhAgBkEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIAxFIAwgExsMAQsgBygCBCEMIAkgB0EIQQwgASAGSSITG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUcEQCAEIAU2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAwgDEUgExsLRQ0AIAEgEiAIKAIEQRR2QQRxIAhBBGsiDCgCAEEWdkEBcSADQQ92QRBxIANBE3ZBwABxIApBqgFxcnJyciITQdC5AWotAABBAnRqIgkoAgAiBygCACIGayEBAn8gBiACQRB2TQRAIAIgBkEQdGshAiABQYCAAnEEQCAHKAIEDAILIAcoAgQhCiAJIAdBDEEIIAEgBkkiHBtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEGIActAABB/wFHBEAgBCAFNgIQQQghBSAGQQh0IAJqIQIMAQsgBkGPAU0EQCAEIAU2AhAgBkEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIApFIAogHBsMAQsgBygCBCEKIAkgB0EIQQwgASAGSSIcG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUcEQCAEIAU2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAogCkUgHBsLIQYgCyAUQQJ0aiAVIA4gBiATQdC7AWotAABzIgYbNgIAIAwgDCgCAEGAAnI2AgAgCCAIKAIEQcAAcjYCBCADIAZBFnRyQYABciEDCwJAIANBgIiAwABxDQAgASASIAQoAmwgA0EGdiIKQe8DcWotAABBAnRqIgkoAgAiBygCACIGayEBAn8gBiACQRB2TQRAIAIgBkEQdGshAiABQYCAAnEEQCAHKAIEDAILIAcoAgQhDCAJIAdBDEEIIAEgBkkiExtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEGIActAABB/wFHBEAgBCAFNgIQQQghBSAGQQh0IAJqIQIMAQsgBkGPAU0EQCAEIAU2AhAgBkEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIAxFIAwgExsMAQsgBygCBCEMIAkgB0EIQQwgASAGSSITG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUcEQCAEIAU2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAwgDEUgExsLRQ0AIAEgEiAIKAIEQRd2QQRxIAhBBGsiDCgCAEEZdkEBcSADQRJ2QRBxIANBFnZBwABxIApBqgFxcnJyciITQdC5AWotAABBAnRqIgkoAgAiBygCACIGayEBAn8gBiACQRB2TQRAIAIgBkEQdGshAiABQYCAAnEEQCAHKAIEDAILIAcoAgQhCiAJIAdBDEEIIAEgBkkiHBtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEGIActAABB/wFHBEAgBCAFNgIQQQghBSAGQQh0IAJqIQIMAQsgBkGPAU0EQCAEIAU2AhAgBkEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIApFIAogHBsMAQsgBygCBCEKIAkgB0EIQQwgASAGSSIcG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUcEQCAEIAU2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAogCkUgHBsLIQYgCyAkQQJ0aiAVIA4gBiATQdC7AWotAABzIgYbNgIAIAwgDCgCAEGAEHI2AgAgCCAIKAIEQYAEcjYCBCADIAZBGXRyQYAIciEDCyADQYDAgIAEcQ0DIAEgEiAEKAJsIANBCXYiCkHvA3FqLQAAQQJ0aiIJKAIAIgEoAgAiBmshBwJ/IAYgAkEQdk0EQCACIAZBEHRrIQIgB0GAgAJxBEAgASgCBAwCCyABKAIEIQwgCSABQQxBCCAGIAdLIhMbaigCADYCAANAAkAgBQ0AIAQoAhAiBkEBaiEFIAYtAAEhASAGLQAAQf8BRwRAIAQgBTYCEEEIIQUgAUEIdCACaiECDAELIAFBjwFNBEAgBCAFNgIQIAFBCXQgAmohAkEHIQUMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQsgBUEBayEFIAJBAXQhAiAHQQF0IgdBgIACSQ0ACyAMRSAMIBMbDAELIAEoAgQhDCAJIAFBCEEMIAYgB0siExtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEBIActAABB/wFHBEAgBCAFNgIQQQghBSABQQh0IAJqIQIMAQsgAUGPAU0EQCAEIAU2AhAgAUEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAZBAXQiBkGAgAJJDQALIAYhByAMIAxFIBMbC0UEQCAHIQEMBAsgByASIAgoAgRBGnZBBHEgCEEEayIMKAIAQRx2QQFxIANBFXZBEHEgA0EZdkHAAHEgCkGqAXFycnJyIhNB0LkBai0AAEECdGoiCSgCACIKKAIAIgFrIQYgASACQRB2TQRAIAIgAUEQdGshAiAGQYCAAnEEQCAGIQEMAwsgCigCBCEHIAkgCkEMQQggASAGSyIcG2ooAgA2AgADQAJAIAUNACAEKAIQIgVBAWohCiAFLQABIQEgBS0AAEH/AUcEQCAEIAo2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgCjYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAdFIAcgHBsMAwsgCigCBCEHIAkgCkEIQQwgASAGSyIcG2ooAgA2AgADQAJAIAUNACAEKAIQIgVBAWohCiAFLQABIQYgBS0AAEH/AUcEQCAEIAo2AhBBCCEFIAZBCHQgAmohAgwBCyAGQY8BTQRAIAQgCjYCECAGQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgAUEBdCIBQYCAAkkNAAsgByAHRSAcGwwCCyABIA0oAgAiBigCACIDayEBAn8gAyACQRB2TQRAIAIgA0EQdGshAiABQYCAAnEEQCAGKAIEDAILIAYoAgQhByANIAZBDEEIIAEgA0kiDBtqKAIANgIAA0ACQCAFDQAgBCgCECIGQQFqIQkgBi0AASEDIAYtAABB/wFHBEAgBCAJNgIQQQghBSADQQh0IAJqIQIMAQsgA0GPAU0EQCAEIAk2AhAgA0EJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIAdFIAcgDBsMAQsgBigCBCEHIA0gBkEIQQwgASADSSIMG2ooAgA2AgADQAJAIAUNACAEKAIQIgZBAWohCSAGLQABIQEgBi0AAEH/AUcEQCAEIAk2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgCTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgA0EBdCIDQYCAAkkNAAsgAyEBIAcgB0UgDBsLRQRAIA0hCQwECyABIBAoAgAiBigCACIDayEBAn8gAyACQRB2TQRAIAIgA0EQdGshAiABQYCAAnEEQCAGKAIEDAILIAYoAgQhByAQIAZBDEEIIAEgA0kiDBtqKAIAIgY2AgADQAJAIAUNACAEKAIQIglBAWohBSAJLQABIQMgCS0AAEH/AUcEQCAEIAU2AhBBCCEFIANBCHQgAmohAgwBCyADQY8BTQRAIAQgBTYCECADQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgAUEBdCIBQYCAAkkNAAsgB0UgByAMGwwBCyAGKAIEIQcgECAGQQhBDCABIANJIgwbaigCACIGNgIAA0ACQCAFDQAgBCgCECIJQQFqIQUgCS0AASEBIAktAABB/wFHBEAgBCAFNgIQQQghBSABQQh0IAJqIQIMAQsgAUGPAU0EQCAEIAU2AhAgAUEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIANBAXQiA0GAgAJJDQALIAMhASAHIAdFIAwbCyEMIAEgBigCACIDayEBAn8gAyACQRB2TQRAIAIgA0EQdGshAiABQYCAAnEEQCAGKAIEDAILIAYoAgQhByAQIAZBDEEIIAEgA0kiChtqKAIANgIAA0ACQCAFDQAgBCgCECIGQQFqIQkgBi0AASEDIAYtAABB/wFHBEAgBCAJNgIQQQghBSADQQh0IAJqIQIMAQsgA0GPAU0EQCAEIAk2AhAgA0EJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIAdFIAcgChsMAQsgBigCBCEHIBAgBkEIQQwgASADSSIKG2ooAgA2AgADQAJAIAUNACAEKAIQIgZBAWohCSAGLQABIQEgBi0AAEH/AUcEQCAEIAk2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgCTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgA0EBdCIDQYCAAkkNAAsgAyEBIAcgB0UgChsLIQZBACEDIBAhCQJAAkACQAJ/AkACQCAGIAxBAXRyDgQAAQMFCAsgASASIAgoAgRBEXZBBHEgCEEEayIHKAIAQRN2QQFxciIKQdC5AWotAABBAnRqIgkoAgAiBigCACIDayEBAn8gAyACQRB2TQRAIAIgA0EQdGshAiABQYCAAnEEQCAGKAIEDAILIAYoAgQhDCAJIAZBDEEIIAEgA0kiExtqKAIANgIAA0ACQCAFDQAgBCgCECIGQQFqIQkgBi0AASEDIAYtAABB/wFHBEAgBCAJNgIQQQghBSADQQh0IAJqIQIMAQsgA0GPAU0EQCAEIAk2AhAgA0EJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIAxFIAwgExsMAQsgBigCBCEMIAkgBkEIQQwgASADSSITG2ooAgA2AgADQAJAIAUNACAEKAIQIgZBAWohCSAGLQABIQEgBi0AAEH/AUcEQCAEIAk2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgCTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgA0EBdCIDQYCAAkkNAAsgAyEBIAwgDEUgExsLIQMgCyAVIA4gAyAKQdC7AWotAABzIgYbNgIAIAcgBygCAEEgcjYCACAIIAgoAgRBCHI2AgQgCEF+IAQoAnxrQQJ0aiIDIAMoAgRBgIACcjYCBCADIAMoAgAgBkEfdHJBgIAEcjYCACADQQRrIgMgAygCAEGAgAhyNgIAIAZBE3QhWiABIBIgBCgCbC0AAkECdGoiBygCACIGKAIAIgNrIQECfyADIAJBEHZNBEAgAiADQRB0ayECIAFBgIACcQRAIAYoAgQMAgsgBigCBCEJIAcgBkEMQQggASADSSIKG2ooAgA2AgADQAJAIAUNACAEKAIQIgZBAWohByAGLQABIQMgBi0AAEH/AUcEQCAEIAc2AhBBCCEFIANBCHQgAmohAgwBCyADQY8BTQRAIAQgBzYCECADQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgAUEBdCIBQYCAAkkNAAsgCUUgCSAKGwwBCyAGKAIEIQkgByAGQQhBDCABIANJIgobaigCADYCAANAAkAgBQ0AIAQoAhAiBkEBaiEHIAYtAAEhASAGLQAAQf8BRwRAIAQgBzYCEEEIIQUgAUEIdCACaiECDAELIAFBjwFNBEAgBCAHNgIQIAFBCXQgAmohAkEHIQUMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQsgBUEBayEFIAJBAXQhAiADQQF0IgNBgIACSQ0ACyADIQEgCSAJRSAKGwshBiBaQRByIgMgBkUNARoLIAEgEiAIKAIEQRR2QQRxIAhBBGsiCSgCAEEWdkEBcSADQQ92QRBxIANBE3ZBwABxIANBA3ZBqgFxcnJyciITQdC5AWotAABBAnRqIgwoAgAiBygCACIGayEBAn8gBiACQRB2TQRAIAIgBkEQdGshAiABQYCAAnEEQCAHKAIEDAILIAcoAgQhCiAMIAdBDEEIIAEgBkkiDBtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEGIActAABB/wFHBEAgBCAFNgIQQQghBSAGQQh0IAJqIQIMAQsgBkGPAU0EQCAEIAU2AhAgBkEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIApFIAogDBsMAQsgBygCBCEKIAwgB0EIQQwgASAGSSIMG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUcEQCAEIAU2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAogCkUgDBsLIQYgCyAUQQJ0aiAVIA4gBiATQdC7AWotAABzIgYbNgIAIAkgCSgCAEGAAnI2AgAgCCAIKAIEQcAAcjYCBCADIAZBFnRyQYABcgshAyABIBIgBCgCbCADQQZ2Qe8DcWotAABBAnRqIgkoAgAiBygCACIGayEBAn8gBiACQRB2TQRAIAIgBkEQdGshAiABQYCAAnEEQCAHKAIEDAILIAcoAgQhDCAJIAdBDEEIIAEgBkkiChtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQkgBy0AASEGIActAABB/wFHBEAgBCAJNgIQQQghBSAGQQh0IAJqIQIMAQsgBkGPAU0EQCAEIAk2AhAgBkEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIAxFIAwgChsMAQsgBygCBCEMIAkgB0EIQQwgASAGSSIKG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohCSAHLQABIQEgBy0AAEH/AUcEQCAEIAk2AhBBCCEFIAFBCHQgAmohAgwBCyABQY8BTQRAIAQgCTYCECABQQl0IAJqIQJBByEFDAELIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQULIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAwgDEUgChsLRQ0BCyABIBIgCCgCBEEXdkEEcSAIQQRrIgkoAgBBGXZBAXEgA0ESdkEQcSADQRZ2QcAAcSADQQZ2QaoBcXJycnIiE0HQuQFqLQAAQQJ0aiIMKAIAIgcoAgAiBmshAQJ/IAYgAkEQdk0EQCACIAZBEHRrIQIgAUGAgAJxBEAgBygCBAwCCyAHKAIEIQogDCAHQQxBCCABIAZJIgwbaigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEFIActAAEhBiAHLQAAQf8BRwRAIAQgBTYCEEEIIQUgBkEIdCACaiECDAELIAZBjwFNBEAgBCAFNgIQIAZBCXQgAmohAkEHIQUMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQsgBUEBayEFIAJBAXQhAiABQQF0IgFBgIACSQ0ACyAKRSAKIAwbDAELIAcoAgQhCiAMIAdBCEEMIAEgBkkiDBtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEBIActAABB/wFHBEAgBCAFNgIQQQghBSABQQh0IAJqIQIMAQsgAUGPAU0EQCAEIAU2AhAgAUEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAZBAXQiBkGAgAJJDQALIAYhASAKIApFIAwbCyEGIAsgJEECdGogFSAOIAYgE0HQuwFqLQAAcyIGGzYCACAJIAkoAgBBgBByNgIAIAggCCgCBEGABHI2AgQgAyAGQRl0ckGACHIhAwsgASASIAQoAmwgA0EJdkHvA3FqLQAAQQJ0aiIJKAIAIgcoAgAiBmshAQJ/IAYgAkEQdk0EQCACIAZBEHRrIQIgAUGAgAJxBEAgBygCBAwCCyAHKAIEIQwgCSAHQQxBCCABIAZJIgobaigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEFIActAAEhBiAHLQAAQf8BRwRAIAQgBTYCEEEIIQUgBkEIdCACaiECDAELIAZBjwFNBEAgBCAFNgIQIAZBCXQgAmohAkEHIQUMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQsgBUEBayEFIAJBAXQhAiABQQF0IgFBgIACSQ0ACyAMRSAMIAobDAELIAcoAgQhDCAJIAdBCEEMIAEgBkkiChtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEBIActAABB/wFHBEAgBCAFNgIQQQghBSABQQh0IAJqIQIMAQsgAUGPAU0EQCAEIAU2AhAgAUEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAZBAXQiBkGAgAJJDQALIAYhASAMIAxFIAobC0UNAwsgASASIAgoAgRBGnZBBHEgCEEEayIMKAIAQRx2QQFxIANBFXZBEHEgA0EZdkHAAHEgA0EJdkGqAXFycnJyIhNB0LkBai0AAEECdGoiCSgCACIKKAIAIgZrIQEgBiACQRB2TQRAIAIgBkEQdGshAiABQYCAAnENASAKKAIEIQcgCSAKQQxBCCABIAZJIhwbaigCADYCAANAAkAgBQ0AIAQoAhAiBUEBaiEKIAUtAAEhBiAFLQAAQf8BRwRAIAQgCjYCEEEIIQUgBkEIdCACaiECDAELIAZBjwFNBEAgBCAKNgIQIAZBCXQgAmohAkEHIQUMAQsgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQsgBUEBayEFIAJBAXQhAiABQQF0IgFBgIACSQ0ACyAHRSAHIBwbDAILIAooAgQhByAJIApBCEEMIAEgBkkiHBtqKAIANgIAA0ACQCAFDQAgBCgCECIFQQFqIQogBS0AASEBIAUtAABB/wFHBEAgBCAKNgIQQQghBSABQQh0IAJqIQIMAQsgAUGPAU0EQCAEIAo2AhAgAUEJdCACaiECQQchBQwBCyAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFCyAFQQFrIQUgAkEBdCECIAZBAXQiBkGAgAJJDQALIAYhASAHIAdFIBwbDAELIAooAgQLIQYgCyAbQQJ0aiAVIA4gBiATQdC7AWotAABzIgcbNgIAIAwgDCgCAEGAgAFyNgIAIAggCCgCBEGAIHI2AgQgBCgCfEECdCAIaiIGIAYoAgRBBHI2AgQgBiAGKAIMQQFyNgIMIAYgBigCCCAHQRJ0ckECcjYCCCADIAdBHHRyQYDAAHIhAwsgCCADQf///7Z7cTYCAAsgCEEEaiEDIAtBBGohCyAYQQFqIhggFEcNAAsgCEEMaiEDIAsgG0ECdGohCyARQQRqIhEgBCgCgAEiBkF8cUkNAAsMAQtBBCAGQXxxIgMgA0EETRtBAWsiA0F8cUEEaiERIAcgA0EBdEF4cWpBFGohAwsgBCAFNgIIIAQgATYCBCAEIAI2AgAgBCAJNgJoIBRFDQQgBiARTQ0EA0BBACEFIBEgBCgCgAFHBEADQCAEIAMgCyAFIBRsQQJ0aiAOIAVBABBYIAVBAWoiBSAEKAKAASARa0kNAAsLIAMgAygCAEH///+2e3E2AgAgC0EEaiELIANBBGohAyAXQQFqIhcgFEcNAAsMBAtBBCAGQXxxIgMgA0EETRtBAWsiA0F8cUEEaiERIAcgA0EBdEF4cWpBFGohAwsgBCAFNgIIIAQgATYCBCAEIAI2AgAgBCAJNgJoIBRFDQIgBiARTQ0CA0BBACEFIBEgBCgCgAFHBEADQCAEIAMgCyAFIBRsQQJ0aiAOIAVBARBYIAVBAWoiBSAEKAKAASARa0kNAAsLIAMgAygCAEH///+2e3E2AgAgC0EEaiELIANBBGohAyAXQQFqIhcgFEcNAAsMAgsDQEEAIQwDQCADIRECQAJAAn8CQAJAIAYiDSgCACIGRQRAIAEgECgCACIDKAIAIgZrIQECfyAGIAJBEHZLBEAgAygCBCEHIBAgA0EIQQwgASAGSSIKG2ooAgA2AgADQAJAIAUNACAEKAIQIgNBAWohCSADLQABIQEgAy0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAJNgIQIAFBCXQgAmohAkEHIQUMAQsgBCAJNgIQQQghBSABQQh0IAJqIQILIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAcgB0UgChsMAQsgAiAGQRB0ayECIAFBgIACcUUEQCADKAIEIQcgECADQQxBCCABIAZJIgobaigCADYCAANAAkAgBQ0AIAQoAhAiBkEBaiEJIAYtAAEhAyAGLQAAQf8BRgRAIANBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAk2AhAgA0EJdCACaiECQQchBQwBCyAEIAk2AhBBCCEFIANBCHQgAmohAgsgBUEBayEFIAJBAXQhAiABQQF0IgFBgIACSQ0ACyAHRSAHIAobDAELIAMoAgQLRQRAIBAhCQwGCyABIAgoAgAiAygCACIGayEBAn8gBiACQRB2SwRAIAMoAgQhByAIIANBCEEMIAEgBkkiChtqKAIAIgM2AgADQAJAIAUNACAEKAIQIglBAWohBSAJLQABIQEgCS0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAFNgIQIAFBCXQgAmohAkEHIQUMAQsgBCAFNgIQQQghBSABQQh0IAJqIQILIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAcgB0UgChsMAQsgAiAGQRB0ayECIAFBgIACcUUEQCADKAIEIQcgCCADQQxBCCABIAZJIgobaigCACIDNgIAA0ACQCAFDQAgBCgCECIJQQFqIQUgCS0AASEGIAktAABB/wFGBEAgBkGQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBTYCECAGQQl0IAJqIQJBByEFDAELIAQgBTYCEEEIIQUgBkEIdCACaiECCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIAdFIAcgChsMAQsgAygCBAshCiABIAMoAgAiBmshAQJ/IAYgAkEQdksEQCADKAIEIQcgCCADQQhBDCABIAZJIg4baigCADYCAANAAkAgBQ0AIAQoAhAiA0EBaiEJIAMtAAEhASADLQAAQf8BRgRAIAFBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAk2AhAgAUEJdCACaiECQQchBQwBCyAEIAk2AhBBCCEFIAFBCHQgAmohAgsgBUEBayEFIAJBAXQhAiAGQQF0IgZBgIACSQ0ACyAGIQEgByAHRSAOGwwBCyACIAZBEHRrIQIgAUGAgAJxRQRAIAMoAgQhByAIIANBDEEIIAEgBkkiDhtqKAIANgIAA0ACQCAFDQAgBCgCECIGQQFqIQkgBi0AASEDIAYtAABB/wFGBEAgA0GQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgCTYCECADQQl0IAJqIQJBByEFDAELIAQgCTYCEEEIIQUgA0EIdCACaiECCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIAdFIAcgDhsMAQsgAygCBAshA0EAIQYgCCEJAkACQAJAAn8CQAJAIAMgCkEBdHIOBAABAwUKCyABIAsgDSgCBEERdkEEcSANQQRrIgcoAgBBE3ZBAXFyIg5B0LkBai0AAEECdGoiCSgCACIDKAIAIgZrIQECfyAGIAJBEHZLBEAgAygCBCEKIAkgA0EIQQwgASAGSSISG2ooAgA2AgADQAJAIAUNACAEKAIQIgNBAWohCSADLQABIQEgAy0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAJNgIQIAFBCXQgAmohAkEHIQUMAQsgBCAJNgIQQQghBSABQQh0IAJqIQILIAVBAWshBSACQQF0IQIgBkEBdCIGQYCAAkkNAAsgBiEBIAogCkUgEhsMAQsgAiAGQRB0ayECIAFBgIACcUUEQCADKAIEIQogCSADQQxBCCABIAZJIhIbaigCADYCAANAAkAgBQ0AIAQoAhAiBkEBaiEJIAYtAAEhAyAGLQAAQf8BRgRAIANBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAk2AhAgA0EJdCACaiECQQchBQwBCyAEIAk2AhBBCCEFIANBCHQgAmohAgsgBUEBayEFIAJBAXQhAiABQQF0IgFBgIACSQ0ACyAKRSAKIBIbDAELIAMoAgQLIQMgESATIBQgAyAOQdC7AWotAABzIgMbNgIAIAcgBygCAEEgcjYCACANIA0oAgRBCHI2AgQgA0ETdCFbIAEgCyAEKAJsLQACQQJ0aiIHKAIAIgMoAgAiBmshAQJ/IAYgAkEQdksEQCADKAIEIQkgByADQQhBDCABIAZJIg4baigCADYCAANAAkAgBQ0AIAQoAhAiA0EBaiEHIAMtAAEhASADLQAAQf8BRgRAIAFBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAc2AhAgAUEJdCACaiECQQchBQwBCyAEIAc2AhBBCCEFIAFBCHQgAmohAgsgBUEBayEFIAJBAXQhAiAGQQF0IgZBgIACSQ0ACyAGIQEgCSAJRSAOGwwBCyACIAZBEHRrIQIgAUGAgAJxRQRAIAMoAgQhCSAHIANBDEEIIAEgBkkiDhtqKAIANgIAA0ACQCAFDQAgBCgCECIGQQFqIQcgBi0AASEDIAYtAABB/wFGBEAgA0GQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBzYCECADQQl0IAJqIQJBByEFDAELIAQgBzYCEEEIIQUgA0EIdCACaiECCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIAlFIAkgDhsMAQsgAygCBAshAyBbQRByIgYgA0UNARoLIAEgCyANKAIEQRR2QQRxIA1BBGsiCSgCAEEWdkEBcSAGQQ92QRBxIAZBE3ZBwABxIAZBA3ZBqgFxcnJyciISQdC5AWotAABBAnRqIgooAgAiBygCACIDayEBAn8gAyACQRB2SwRAIAcoAgQhDiAKIAdBCEEMIAEgA0kiChtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEBIActAABB/wFGBEAgAUGQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBTYCEEEIIQUgAUEIdCACaiECCyAFQQFrIQUgAkEBdCECIANBAXQiA0GAgAJJDQALIAMhASAOIA5FIAobDAELIAIgA0EQdGshAiABQYCAAnFFBEAgBygCBCEOIAogB0EMQQggASADSSIKG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQMgBy0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAFNgIQIANBCXQgAmohAkEHIQUMAQsgBCAFNgIQQQghBSADQQh0IAJqIQILIAVBAWshBSACQQF0IQIgAUEBdCIBQYCAAkkNAAsgDkUgDiAKGwwBCyAHKAIECyEDIBEgEyAUIAMgEkHQuwFqLQAAcyIDGzYCgAIgCSAJKAIAQYACcjYCACANIA0oAgRBwAByNgIEIAYgA0EWdHJBgAFyCyEGIAEgCyAEKAJsIAZBBnZB7wNxai0AAEECdGoiCSgCACIHKAIAIgNrIQECfyADIAJBEHZLBEAgBygCBCEKIAkgB0EIQQwgASADSSIOG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohCSAHLQABIQEgBy0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAJNgIQIAFBCXQgAmohAkEHIQUMAQsgBCAJNgIQQQghBSABQQh0IAJqIQILIAVBAWshBSACQQF0IQIgA0EBdCIDQYCAAkkNAAsgAyEBIAogCkUgDhsMAQsgAiADQRB0ayECIAFBgIACcUUEQCAHKAIEIQogCSAHQQxBCCABIANJIg4baigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEJIActAAEhAyAHLQAAQf8BRgRAIANBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAk2AhAgA0EJdCACaiECQQchBQwBCyAEIAk2AhBBCCEFIANBCHQgAmohAgsgBUEBayEFIAJBAXQhAiABQQF0IgFBgIACSQ0ACyAKRSAKIA4bDAELIAcoAgQLRQ0BCyABIAsgDSgCBEEXdkEEcSANQQRrIgkoAgBBGXZBAXEgBkESdkEQcSAGQRZ2QcAAcSAGQQZ2QaoBcXJycnIiEkHQuQFqLQAAQQJ0aiIKKAIAIgcoAgAiA2shAQJ/IAMgAkEQdksEQCAHKAIEIQ4gCiAHQQhBDCABIANJIgobaigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEFIActAAEhASAHLQAAQf8BRgRAIAFBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAU2AhAgAUEJdCACaiECQQchBQwBCyAEIAU2AhBBCCEFIAFBCHQgAmohAgsgBUEBayEFIAJBAXQhAiADQQF0IgNBgIACSQ0ACyADIQEgDiAORSAKGwwBCyACIANBEHRrIQIgAUGAgAJxRQRAIAcoAgQhDiAKIAdBDEEIIAEgA0kiChtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEDIActAABB/wFGBEAgA0GQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBTYCECADQQl0IAJqIQJBByEFDAELIAQgBTYCEEEIIQUgA0EIdCACaiECCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIA5FIA4gChsMAQsgBygCBAshAyARIBMgFCADIBJB0LsBai0AAHMiAxs2AoAEIAkgCSgCAEGAEHI2AgAgDSANKAIEQYAEcjYCBCAGIANBGXRyQYAIciEGCyABIAsgBCgCbCAGQQl2Qe8DcWotAABBAnRqIgkoAgAiBygCACIDayEBAn8gAyACQRB2SwRAIAcoAgQhCiAJIAdBCEEMIAEgA0kiDhtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEBIActAABB/wFGBEAgAUGQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBTYCEEEIIQUgAUEIdCACaiECCyAFQQFrIQUgAkEBdCECIANBAXQiA0GAgAJJDQALIAMhASAKIApFIA4bDAELIAIgA0EQdGshAiABQYCAAnFFBEAgBygCBCEKIAkgB0EMQQggASADSSIOG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQMgBy0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAFNgIQIANBCXQgAmohAkEHIQUMAQsgBCAFNgIQQQghBSADQQh0IAJqIQILIAVBAWshBSACQQF0IQIgAUEBdCIBQYCAAkkNAAsgCkUgCiAOGwwBCyAHKAIEC0UNBQsgASALIA0oAgRBGnZBBHEgDUEEayIOKAIAQRx2QQFxIAZBFXZBEHEgBkEZdkHAAHEgBkEJdkGqAXFycnJyIgpB0LkBai0AAEECdGoiCSgCACIHKAIAIgNrIQEgAyACQRB2SwRAIAcoAgQhEiAJIAdBCEEMIAEgA0kiFRtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEBIActAABB/wFGBEAgAUGQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBTYCEEEIIQUgAUEIdCACaiECCyAFQQFrIQUgAkEBdCECIANBAXQiA0GAgAJJDQALIAMhASASIBJFIBUbDAQLIAIgA0EQdGshAiABQYCAAnENASAHKAIEIRIgCSAHQQxBCCABIANJIhUbaigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEFIActAAEhAyAHLQAAQf8BRgRAIANBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAU2AhAgA0EJdCACaiECQQchBQwBCyAEIAU2AhBBCCEFIANBCHQgAmohAgsgBUEBayEFIAJBAXQhAiABQQF0IgFBgIACSQ0ACyASRSASIBUbDAMLAkAgBkGQgIABcQ0AIAEgCyAEKAJsIAZB7wNxai0AAEECdGoiCSgCACIHKAIAIgNrIQECfyADIAJBEHZLBEAgBygCBCEKIAkgB0EIQQwgASADSSIOG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQEgBy0AAEH/AUYEQCABQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAFNgIQIAFBCXQgAmohAkEHIQUMAQsgBCAFNgIQQQghBSABQQh0IAJqIQILIAVBAWshBSACQQF0IQIgA0EBdCIDQYCAAkkNAAsgAyEBIAogCkUgDhsMAQsgAiADQRB0ayECIAFBgIACcUUEQCAHKAIEIQogCSAHQQxBCCABIANJIg4baigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEFIActAAEhAyAHLQAAQf8BRgRAIANBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAU2AhAgA0EJdCACaiECQQchBQwBCyAEIAU2AhBBCCEFIANBCHQgAmohAgsgBUEBayEFIAJBAXQhAiABQQF0IgFBgIACSQ0ACyAKRSAKIA4bDAELIAcoAgQLRQ0AIAEgCyANKAIEQRF2QQRxIA1BBGsiCigCAEETdkEBcSAGQQ52QRBxIAZBEHZBwABxIAZBqgFxcnJyciISQdC5AWotAABBAnRqIgkoAgAiBygCACIDayEBAn8gAyACQRB2SwRAIAcoAgQhDiAJIAdBCEEMIAEgA0kiFRtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEBIActAABB/wFGBEAgAUGQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBTYCEEEIIQUgAUEIdCACaiECCyAFQQFrIQUgAkEBdCECIANBAXQiA0GAgAJJDQALIAMhASAOIA5FIBUbDAELIAIgA0EQdGshAiABQYCAAnFFBEAgBygCBCEOIAkgB0EMQQggASADSSIVG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQMgBy0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAFNgIQIANBCXQgAmohAkEHIQUMAQsgBCAFNgIQQQghBSADQQh0IAJqIQILIAVBAWshBSACQQF0IQIgAUEBdCIBQYCAAkkNAAsgDkUgDiAVGwwBCyAHKAIECyEDIBEgEyAUIAMgEkHQuwFqLQAAcyIDGzYCACAKIAooAgBBIHI2AgAgDSANKAIEQQhyNgIEIAYgA0ETdHJBEHIhBgsCQCAGQYCBgAhxDQAgASALIAQoAmwgBkEDdiIOQe8DcWotAABBAnRqIgkoAgAiBygCACIDayEBAn8gAyACQRB2SwRAIAcoAgQhCiAJIAdBCEEMIAEgA0kiEhtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEBIActAABB/wFGBEAgAUGQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBTYCEEEIIQUgAUEIdCACaiECCyAFQQFrIQUgAkEBdCECIANBAXQiA0GAgAJJDQALIAMhASAKIApFIBIbDAELIAIgA0EQdGshAiABQYCAAnFFBEAgBygCBCEKIAkgB0EMQQggASADSSISG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQMgBy0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAFNgIQIANBCXQgAmohAkEHIQUMAQsgBCAFNgIQQQghBSADQQh0IAJqIQILIAVBAWshBSACQQF0IQIgAUEBdCIBQYCAAkkNAAsgCkUgCiASGwwBCyAHKAIEC0UNACABIAsgDSgCBEEUdkEEcSANQQRrIgooAgBBFnZBAXEgBkEPdkEQcSAGQRN2QcAAcSAOQaoBcXJycnIiEkHQuQFqLQAAQQJ0aiIJKAIAIgcoAgAiA2shAQJ/IAMgAkEQdksEQCAHKAIEIQ4gCSAHQQhBDCABIANJIhUbaigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEFIActAAEhASAHLQAAQf8BRgRAIAFBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAU2AhAgAUEJdCACaiECQQchBQwBCyAEIAU2AhBBCCEFIAFBCHQgAmohAgsgBUEBayEFIAJBAXQhAiADQQF0IgNBgIACSQ0ACyADIQEgDiAORSAVGwwBCyACIANBEHRrIQIgAUGAgAJxRQRAIAcoAgQhDiAJIAdBDEEIIAEgA0kiFRtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEDIActAABB/wFGBEAgA0GQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBTYCECADQQl0IAJqIQJBByEFDAELIAQgBTYCEEEIIQUgA0EIdCACaiECCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIA5FIA4gFRsMAQsgBygCBAshAyARIBMgFCADIBJB0LsBai0AAHMiAxs2AoACIAogCigCAEGAAnI2AgAgDSANKAIEQcAAcjYCBCAGIANBFnRyQYABciEGCwJAIAZBgIiAwABxDQAgASALIAQoAmwgBkEGdiIOQe8DcWotAABBAnRqIgkoAgAiBygCACIDayEBAn8gAyACQRB2SwRAIAcoAgQhCiAJIAdBCEEMIAEgA0kiEhtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEBIActAABB/wFGBEAgAUGQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBTYCEEEIIQUgAUEIdCACaiECCyAFQQFrIQUgAkEBdCECIANBAXQiA0GAgAJJDQALIAMhASAKIApFIBIbDAELIAIgA0EQdGshAiABQYCAAnFFBEAgBygCBCEKIAkgB0EMQQggASADSSISG2ooAgA2AgADQAJAIAUNACAEKAIQIgdBAWohBSAHLQABIQMgBy0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCACQYD+A2ohAkEIIQUMAgsgBCAFNgIQIANBCXQgAmohAkEHIQUMAQsgBCAFNgIQQQghBSADQQh0IAJqIQILIAVBAWshBSACQQF0IQIgAUEBdCIBQYCAAkkNAAsgCkUgCiASGwwBCyAHKAIEC0UNACABIAsgDSgCBEEXdkEEcSANQQRrIgooAgBBGXZBAXEgBkESdkEQcSAGQRZ2QcAAcSAOQaoBcXJycnIiEkHQuQFqLQAAQQJ0aiIJKAIAIgcoAgAiA2shAQJ/IAMgAkEQdksEQCAHKAIEIQ4gCSAHQQhBDCABIANJIhUbaigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEFIActAAEhASAHLQAAQf8BRgRAIAFBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAU2AhAgAUEJdCACaiECQQchBQwBCyAEIAU2AhBBCCEFIAFBCHQgAmohAgsgBUEBayEFIAJBAXQhAiADQQF0IgNBgIACSQ0ACyADIQEgDiAORSAVGwwBCyACIANBEHRrIQIgAUGAgAJxRQRAIAcoAgQhDiAJIAdBDEEIIAEgA0kiFRtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEDIActAABB/wFGBEAgA0GQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBTYCECADQQl0IAJqIQJBByEFDAELIAQgBTYCEEEIIQUgA0EIdCACaiECCyAFQQFrIQUgAkEBdCECIAFBAXQiAUGAgAJJDQALIA5FIA4gFRsMAQsgBygCBAshAyARIBMgFCADIBJB0LsBai0AAHMiAxs2AoAEIAogCigCAEGAEHI2AgAgDSANKAIEQYAEcjYCBCAGIANBGXRyQYAIciEGCyAGQYDAgIAEcQ0DIAEgCyAEKAJsIAZBCXYiEkHvA3FqLQAAQQJ0aiIJKAIAIgEoAgAiA2shBwJ/IAMgAkEQdksEQCABKAIEIQogCSABQQhBDCADIAdLIg4baigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEFIActAAEhASAHLQAAQf8BRgRAIAFBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAU2AhAgAUEJdCACaiECQQchBQwBCyAEIAU2AhBBCCEFIAFBCHQgAmohAgsgBUEBayEFIAJBAXQhAiADQQF0IgNBgIACSQ0ACyADIQcgCiAKRSAOGwwBCyACIANBEHRrIQIgB0GAgAJxRQRAIAEoAgQhCiAJIAFBDEEIIAMgB0siDhtqKAIANgIAA0ACQCAFDQAgBCgCECIDQQFqIQUgAy0AASEBIAMtAABB/wFGBEAgAUGQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBTYCEEEIIQUgAUEIdCACaiECCyAFQQFrIQUgAkEBdCECIAdBAXQiB0GAgAJJDQALIApFIAogDhsMAQsgASgCBAtFBEAgByEBDAQLIAcgCyANKAIEQRp2QQRxIA1BBGsiDigCAEEcdkEBcSAGQRV2QRBxIAZBGXZBwABxIBJBqgFxcnJyciIKQdC5AWotAABBAnRqIgkoAgAiBygCACIBayEDIAEgAkEQdksEQCAHKAIEIRIgCSAHQQhBDCABIANLIhUbaigCADYCAANAAkAgBQ0AIAQoAhAiB0EBaiEFIActAAEhAyAHLQAAQf8BRgRAIANBkAFPBEAgBCAEKAIMQQFqNgIMIAJBgP4DaiECQQghBQwCCyAEIAU2AhAgA0EJdCACaiECQQchBQwBCyAEIAU2AhBBCCEFIANBCHQgAmohAgsgBUEBayEFIAJBAXQhAiABQQF0IgFBgIACSQ0ACyASIBJFIBUbDAMLIAIgAUEQdGshAiADQYCAAnFFDQEgAyEBCyAHKAIEDAELIAcoAgQhEiAJIAdBDEEIIAEgA0siFRtqKAIANgIAA0ACQCAFDQAgBCgCECIHQQFqIQUgBy0AASEBIActAABB/wFGBEAgAUGQAU8EQCAEIAQoAgxBAWo2AgwgAkGA/gNqIQJBCCEFDAILIAQgBTYCECABQQl0IAJqIQJBByEFDAELIAQgBTYCEEEIIQUgAUEIdCACaiECCyAFQQFrIQUgAkEBdCECIANBAXQiA0GAgAJJDQALIAMhASASRSASIBUbCyEDIBEgEyAUIAMgCkHQuwFqLQAAcyIDGzYCgAYgDiAOKAIAQYCAAXI2AgAgDSANKAIEQYAgcjYCBCANIA0oAoQCQQRyNgKEAiANIA0oAowCQQFyNgKMAiANIA0oAogCIANBEnRyQQJyNgKIAiAGIANBHHRyQYDAAHIhBgsgDSAGQf///7Z7cTYCAAsgDUEEaiEGIBFBBGohAyAMQQFqIgxBwABHDQALIA1BDGohBiARQYQGaiEDIBdBPEkhXCAXQQRqIRcgXA0ACwsgBCAFNgIIIAQgATYCBCAEIAI2AgAgBCAJNgJoCwJAIBZBIHFFDQAgBCAEQeQAajYCaCAEIAQoAgQgBCgCZCIGKAIAIgFrIgI2AgQCQCABIAQoAgAiBUEQdksEQCAEIAE2AgQgBCAGQQhBDCABIAJLG2ooAgAiBjYCZCAEKAIIIQIDQAJAIAINACAEKAIQIgdBAWohCSAHLQABIQMgBy0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCAFQYD+A2ohBUEIIQIMAgsgBCAJNgIQIANBCXQgBWohBUEHIQIMAQsgBCAJNgIQQQghAiADQQh0IAVqIQULIAQgAkEBayICNgIIIAQgBUEBdCIFNgIAIAQgAUEBdCIBNgIEIAFBgIACSQ0ACyABIQIMAQsgBCAFIAFBEHRrIgU2AgAgAkGAgAJxDQAgBCAGQQxBCCABIAJLG2ooAgAiBjYCZCAEKAIIIQEDQAJAIAENACAEKAIQIgFBAWohByABLQABIQMgAS0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCAFQYD+A2ohBUEIIQEMAgsgBCAHNgIQIANBCXQgBWohBUEHIQEMAQsgBCAHNgIQQQghASADQQh0IAVqIQULIAQgAUEBayIBNgIIIAQgBUEBdCIFNgIAIAQgAkEBdCICNgIEIAJBgIACSQ0ACwsgBCACIAYoAgAiAWsiAjYCBAJAIAEgBUEQdksEQCAEIAE2AgQgBCAGQQhBDCABIAJLG2ooAgAiBjYCZCAEKAIIIQIDQAJAIAINACAEKAIQIgdBAWohCSAHLQABIQMgBy0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCAFQYD+A2ohBUEIIQIMAgsgBCAJNgIQIANBCXQgBWohBUEHIQIMAQsgBCAJNgIQQQghAiADQQh0IAVqIQULIAQgAkEBayICNgIIIAQgBUEBdCIFNgIAIAQgAUEBdCIBNgIEIAFBgIACSQ0ACyABIQIMAQsgBCAFIAFBEHRrIgU2AgAgAkGAgAJxDQAgBCAGQQxBCCABIAJLG2ooAgAiBjYCZCAEKAIIIQEDQAJAIAENACAEKAIQIgFBAWohByABLQABIQMgAS0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCAFQYD+A2ohBUEIIQEMAgsgBCAHNgIQIANBCXQgBWohBUEHIQEMAQsgBCAHNgIQQQghASADQQh0IAVqIQULIAQgAUEBayIBNgIIIAQgBUEBdCIFNgIAIAQgAkEBdCICNgIEIAJBgIACSQ0ACwsgBCACIAYoAgAiAWsiAjYCBAJAIAEgBUEQdksEQCAEIAE2AgQgBCAGQQhBDCABIAJLG2ooAgAiBjYCZCAEKAIIIQIDQAJAIAINACAEKAIQIgdBAWohCSAHLQABIQMgBy0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCAFQYD+A2ohBUEIIQIMAgsgBCAJNgIQIANBCXQgBWohBUEHIQIMAQsgBCAJNgIQQQghAiADQQh0IAVqIQULIAQgAkEBayICNgIIIAQgBUEBdCIFNgIAIAQgAUEBdCIBNgIEIAFBgIACSQ0ACyABIQIMAQsgBCAFIAFBEHRrIgU2AgAgAkGAgAJxDQAgBCAGQQxBCCABIAJLG2ooAgAiBjYCZCAEKAIIIQEDQAJAIAENACAEKAIQIgFBAWohByABLQABIQMgAS0AAEH/AUYEQCADQZABTwRAIAQgBCgCDEEBajYCDCAFQYD+A2ohBUEIIQEMAgsgBCAHNgIQIANBCXQgBWohBUEHIQEMAQsgBCAHNgIQQQghASADQQh0IAVqIQULIAQgAUEBayIBNgIIIAQgBUEBdCIFNgIAIAQgAkEBdCICNgIEIAJBgIACSQ0ACwsgBCACIAYoAgAiAWsiAjYCBCABIAVBEHZLBEAgBCABNgIEIAQgBkEIQQwgASACSxtqKAIANgJkIAQoAgghAgNAAkAgAg0AIAQoAhAiBkEBaiEHIAYtAAEhAyAGLQAAQf8BRgRAIANBkAFPBEAgBCAEKAIMQQFqNgIMIAVBgP4DaiEFQQghAgwCCyAEIAc2AhAgA0EJdCAFaiEFQQchAgwBCyAEIAc2AhBBCCECIANBCHQgBWohBQsgBCACQQFrIgI2AgggBCAFQQF0IgU2AgAgBCABQQF0IgE2AgQgAUGAgAJJDQALDAELIAQgBSABQRB0ayIHNgIAIAJBgIACcQ0AIAQgBkEMQQggASACSxtqKAIANgJkIAQoAgghBQNAAkAgBQ0AIAQoAhAiA0EBaiEGIAMtAAEhASADLQAAQf8BRgRAIAFBkAFPBEAgBCAEKAIMQQFqNgIMIAdBgP4DaiEHQQghBQwCCyAEIAY2AhAgAUEJdCAHaiEHQQchBQwBCyAEIAY2AhBBCCEFIAFBCHQgB2ohBwsgBCAFQQFrIgU2AgggBCAHQQF0Igc2AgAgBCACQQF0IgI2AgQgAkGAgAJJDQALCwsgJw0AIAQQWiAEQbCpATYCZCAEQdCeATYCYCAEQfCeATYCHAtBACAfQQFqIgEgAUEDRiIBGyEfIBkgAWshGSAmQQFqIiYgICgCCE8NASAZQQBKDQALCyAoICpqISggBCgCGCAELwFwOwAAIClBAWoiKSAaKAIsSQ0ACwsCQCArRQ0AAkAgBCgCGCIBIAQoAhAiA0ECaksEQCAhRQ0BICMgASAEKAIUIgZrNgI4ICMgAyAGazYCNCAjIAEgA2tBAms2AjAgHUECQZDyACAjQTBqEA8MAgsgBCgCDCIBQQNJDQEgIQRAICMgATYCUCAdQQJB6TUgI0HQAGoQDwwCCyAjIAE2AkAgHUECQek1ICNBQGsQDwwBCyAjIAEgBCgCFCIGazYCKCAjIAMgBms2AiQgIyABIANrQQJrNgIgIB1BAkGQ8gAgI0EgahAPCyAaKAI8RQ0AIAQgLDYCdAsgMCgCBCEBIBooAgwhXSAaKAIIIDAoAgBrIQggMCgCECIGQQFxBEAgMigCHCA3QZgBbGoiB0GQAWsoAgAgCGogB0GYAWsoAgBrIQgLIF0gAWshAyAGQQJxBEAgMigCHCA3QZgBbGoiAUGMAWsoAgAgA2ogAUGUAWsoAgBrIQMLIBooAjwiBiECIAZFBEAgBCgCdCECCyAEKAKAASEWIAQoAnwhDQJAIC8oAqgGIgdFDQAgFkUgDUVyIQEgB0EeTARAIAENAUEAIRADQCANIBBsIQRBACEBA0AgAiABIARqQQJ0aiIRKAIAIgkgCUEfdSIFcyAFayIFIAd2BEAgEUEAIAUgLygCqAZ2IhFrIBEgCUEASBs2AgALIAFBAWoiASANRw0ACyAQQQFqIhAgFkcNAAsMAQsgAQ0AIAJBACANIBZsQQJ0EBUaCyAGBEAgDSAWbCEGIC8oAhRBAUYEQCAGRQ0FQQAhASAGQQRPBEAgBkF8cSEBQQAhBANAIAIgBEECdGoiAyAD/QACACJe/RsAQQJt/REgXv0bAUECbf0cASBe/RsCQQJt/RwCIF79GwNBAm39HAP9CwIAIARBBGoiBCABRw0ACyABIAZGDQYLA0AgAiABQQJ0aiIDIAMoAgBBAm02AgAgAUEBaiIBIAZHDQALDAULIAZFDQQgMCoCIEMAAAA/lCFmQQAhBAJAIAZBBEkEQCACIQEMAQsgAiAGQXxxIgRBAnRqIQEgZv0TIV5BACEDA0AgAiADQQJ0aiIHIF4gB/0AAgD9+gH95gH9CwIAIANBBGoiAyAERw0ACyAEIAZGDQULA0AgASBmIAEoAgCylDgCACABQQRqIQEgBEEBaiIEIAZHDQALDAQLIDYgNWshESAvKAIUQQFHDQIgFkUNAyAyKAIkIgYgAyARbCIDQQJ0aiAIQQJ0aiEJIA1BfHEiDEEBayIBQQRxIQsgNiANIDVqa0ECdCEaIAFBAnZBAWpB/v///wdxIR0gAyAIakECdCAGaiACayEKQQAhCCABQQNHIRQDQEEAIQECQCAMRQ0AIAggDWwhAyAJIAggEWxBAnRqIQZBACEHIBQEQANAIAYgAUECdGogAiABIANqQQJ0av0AAgAiXv0bAEECbf0RIF79GwFBAm39HAEgXv0bAkECbf0cAiBe/RsDQQJt/RwD/QsCACAGIAFBBHIiBEECdGogAiADIARqQQJ0av0AAgAiXv0bAEECbf0RIF79GwFBAm39HAEgXv0bAkECbf0cAiBe/RsDQQJt/RwD/QsCACABQQhqIQEgB0ECaiIHIB1HDQALCyALDQAgBiABQQJ0aiACIAEgA2pBAnRq/QACACJe/RsAQQJt/REgXv0bAUECbf0cASBe/RsCQQJt/RwCIF79GwNBAm39HAP9CwIAIAFBBGohAQsCQCABIA1PDQAgCCANbCEDIAkgCCARbEECdGohBwJAIA0gAWsiEEEESQRAIAEhBAwBCyAKIAggGmxqQRBJBEAgASEEDAELIAEgEEF8cSIFaiEEQQAhBgNAIAcgASAGaiIhQQJ0aiACIAMgIWpBAnRq/QACACJe/RsAQQJt/REgXv0bAUECbf0cASBe/RsCQQJt/RwCIF79GwNBAm39HAP9CwIAIAZBBGoiBiAFRw0ACyAFIBBGDQELIARBAWohASANIARrQQFxBEAgByAEQQJ0aiACIAMgBGpBAnRqKAIAQQJtNgIAIAEhBAsgASANRg0AA0AgByAEQQJ0aiACIAMgBGpBAnRqKAIAQQJtNgIAIAcgBEEBaiIBQQJ0aiACIAEgA2pBAnRqKAIAQQJtNgIAIARBAmoiBCANRw0ACwsgCEEBaiIIIBZHDQALDAMLICMgGTYCACAdQQJB1cEAICMQDwsgECgCAEEANgIADAELIBZFDQAgDUUNACAyKAIkIAMgEWxBAnRqIAhBAnRqIQcgDUF8cSIDQQJ0IQYgMCoCIEMAAAA/lCJm/RMhXkEAIRAgDUEESSEIA0ACQAJAIAgEQCACIQkgByEBQQAhBAwBCyAGIAdqIQEgAiAGaiEJQQAhBANAIAcgBEECdCIFaiBeIAIgBWr9AAIA/foB/eYB/QsCACAEQQRqIgQgA0cNAAsgCSECIAMiBCANRg0BCyAJIQIDQCABIGYgAigCALKUOAIAIAFBBGohASACQQRqIQIgBEEBaiIEIA1HDQALCyAHIBFBAnRqIQcgEEEBaiIQIBZHDQALCyAAEBAgI0HgAGokAAvWBAEJfyAAKAIsQQhPBEAgACgCKCEFQQghCgNAIAAoAgxBBXQhCCAAKAIAIQQgACgCJCEDAkAgACgCFCIGIAAoAhAiAU0NACAEIAhqIQcgAUEBaiECIAYgAWtBAXEEQCAHIAFBBnRqIgkgBSABIANsQQJ0aiIB/QACAP0LAgAgCSAB/QACEP0LAhAgAiEBCyACIAZGDQADQCAHIAFBBnRqIgIgBSABIANsQQJ0aiIJ/QACAP0LAgAgAiAJ/QACEP0LAhAgByABQQFqIgJBBnRqIgkgBSACIANsQQJ0aiIC/QACEP0LAhAgCSAC/QACAP0LAgAgAUECaiIBIAZHDQALCwJAIAAoAhwiBiAAKAIYIgFNDQAgBCAIa0EgaiEHIAUgACgCCCADbEECdGohCCABQQFqIQIgBiABa0EBcQRAIAcgAUEGdGoiBCAIIAEgA2xBAnRqIgH9AAIA/QsCACAEIAH9AAIQ/QsCECACIQELIAIgBkYNAANAIAcgAUEGdGoiAiAIIAEgA2xBAnRqIgT9AAIA/QsCACACIAT9AAIQ/QsCECAHIAFBAWoiAkEGdGoiBCAIIAIgA2xBAnRqIgL9AAIQ/QsCECAEIAL9AAIA/QsCACABQQJqIgEgBkcNAAsLIAAQIkEAIQEgACgCIARAA0AgBSAAKAIkIAFsQQJ0aiICIAAoAgAgAUEFdGoiA/0AAgD9CwIAIAIgA/0AAhD9CwIQIAFBAWoiASAAKAIgSQ0ACwsgBUEgaiEFIApBCGoiCiAAKAIsTQ0ACwsgACgCABAQIAAQEAv3DQElfyAAKAIsQQhPBEAgACgCJCIKQQV0IR4gCkEHbCEWIApBBmwhFyAKQQVsIRggCkEDbCEZIApBAXQhGiAAKAIoIgEgCkEcbGohHyABIApBGGxqISAgASAKQRRsaiEhIAEgCkEEdGohIiABIApBDGxqISMgASAKQQN0IiRqISUgASAKQQJ0IhtqISZBCCEcA0AgACABIAAoAiRBCBA7IAAQIgJAIAAoAiAiC0UNACAdIB5sIQggACgCACEGQQAhBAJAAkAgC0HoAkkNACAGQQxqIg4gC0EBayICQQV0IgNqIA5JDQAgBkEIaiIPIANqIA9JDQAgAyAGaiAGSQ0AIAZBBGoiECADaiAQSQ0AIAJB////P0sNACABIAggJmoiAyALQQJ0IgVqIgxJIAMgASAFaiIHSXENACABIAggJWoiAiAFaiINSSACIAdJcQ0AIAEgBSAIICNqIglqIgVJIAcgCUtxDQAgBiAHSSABIAYgC0EFdGoiEUEcayISSXENACABIBFBGGsiE0kgByAQS3ENACABIBFBFGsiFEkgByAPS3ENACAHIA5LIAEgEUEQayIHSXENACADIA1JIAIgDElxDQAgAyAFSSAJIAxJcQ0AIAMgEkkgBiAMSXENACADIBNJIAwgEEtxDQAgAyAUSSAMIA9LcQ0AIAMgB0kgDCAOS3ENACACIAVJIAkgDUlxDQAgAiASSSAGIA1JcQ0AIAIgE0kgDSAQS3ENACACIBRJIA0gD0txDQAgAiAHSSANIA5LcQ0AIAkgEkkgBSAGS3ENACAJIBNJIAUgEEtxDQAgCSAUSSAFIA9LcQ0AIAcgCUsgBSAOS3ENACALQfz///8AcSEEQQAhAwNAIAEgA0ECdGogBiADQQV0aiIC/QkCACACKgIg/SABIAJBQGsqAgD9IAIgAioCYP0gA/0LAgAgASADIApqQQJ0aiAC/QkCBCACKgIk/SABIAIqAkT9IAIgAioCZP0gA/0LAgAgASADIBpqQQJ0aiAC/QkCCCACKgIo/SABIAIqAkj9IAIgAioCaP0gA/0LAgAgASADIBlqQQJ0aiAC/QkCDCACKgIs/SABIAIqAkz9IAIgAioCbP0gA/0LAgAgA0EEaiIDIARHDQALIAQgC0YNAQsDQCABIARBAnRqIAYgBEEFdGoiAyoCADgCACABIAQgCmpBAnRqIAMqAgQ4AgAgASAEIBpqQQJ0aiADKgIIOAIAIAEgBCAZakECdGogAyoCDDgCACAEQQFqIgQgC0cNAAsLIAAoAgAhBkEAIQQCQCALQdwASQ0AIAZBHGoiDyALQQFrIgJBBXQiA2ogD0kNACAGQRhqIhAgA2ogEEkNACAGQRBqIhEgA2ogEUkNACAGQRRqIhIgA2ogEkkNACACQf///z9LDQAgCCAiaiIDIAggIWoiAiALQQJ0IgVqIgxJIAIgAyAFaiIHSXENACADIAggIGoiCSAFaiINSSAHIAlLcQ0AIAMgCCAfaiIIIAVqIgVJIAcgCEtxDQAgAyAGIAtBBXRqIg5BDGsiE0kgByARS3ENACADIA5BCGsiFEkgByASS3ENACADIA5BBGsiFUkgByAQS3ENACADIA5JIAcgD0txDQAgAiANSSAJIAxJcQ0AIAIgBUkgCCAMSXENACACIBNJIAwgEUtxDQAgAiAUSSAMIBJLcQ0AIAIgFUkgDCAQS3ENACACIA5JIAwgD0txDQAgCCANSSAFIAlLcQ0AIAkgE0kgDSARS3ENACAJIBRJIA0gEktxDQAgCSAVSSANIBBLcQ0AIAkgDkkgDSAPS3ENACAIIBNJIAUgEUtxDQAgCCAUSSAFIBJLcQ0AIAggFUkgBSAQS3ENACAIIA5JIAUgD0txDQAgC0H8////AHEhBEEAIQMDQCABIAMgG2pBAnRqIAYgA0EFdGoiAv0JAhAgAioCMP0gASACKgJQ/SACIAIqAnD9IAP9CwIAIAEgAyAYakECdGogAv0JAhQgAioCNP0gASACKgJU/SACIAIqAnT9IAP9CwIAIAEgAyAXakECdGogAv0JAhggAioCOP0gASACKgJY/SACIAIqAnj9IAP9CwIAIAEgAyAWakECdGogAv0JAhwgAioCPP0gASACKgJc/SACIAIqAnz9IAP9CwIAIANBBGoiAyAERw0ACyAEIAtGDQELA0AgASAEIBtqQQJ0aiAGIARBBXRqIgMqAhA4AgAgASAEIBhqQQJ0aiADKgIUOAIAIAEgBCAXakECdGogAyoCGDgCACABIAQgFmpBAnRqIAMqAhw4AgAgBEEBaiIEIAtHDQALCyAdQQFqIR0gASAkQQJ0aiEBIBxBCGoiHCAAKAIsTQ0ACwsgACgCABAQIAAQEAtzAQJ/IAAoAhwiAUEIaiIDIAAoAiAiAk0EQANAIAAgACgCGCABQQJ0aiAAKAIUQQgQMCADIgFBCGoiAyAAKAIgIgJNDQALCyABIAJJBEAgACAAKAIYIAFBAnRqIAAoAhQgAiABaxAwCyAAKAIAEBAgABAQC0QAIAAoAhwiASAAKAIgSQRAA0AgACAAKAIYIAAoAhQgAWxBAnRqEF0gAUEBaiIBIAAoAiBJDQALCyAAKAIAEBAgABAQC6gBAQV/IAAoAlQiAygCACEFIAMoAgQiBCAAKAIUIAAoAhwiB2siBiAEIAZJGyIGBEAgBSAHIAYQEhogAyADKAIAIAZqIgU2AgAgAyADKAIEIAZrIgQ2AgQLIAQgAiACIARLGyIEBEAgBSABIAQQEhogAyADKAIAIARqIgU2AgAgAyADKAIEIARrNgIECyAFQQA6AAAgACAAKAIsIgE2AhwgACABNgIUIAILngUCBn4EfyABIAEoAgBBB2pBeHEiAUEQajYCACAAIQsgASkDACEDIAEpAwghByMAQSBrIggkACAHQv///////z+DIQQCfiAHQjCIQv//AYMiBaciCkGB+ABrQf0PTQRAIARCBIYgA0I8iIQhAiAKQYD4AGutIQUCQCADQv//////////D4MiA0KBgICAgICAgAhaBEAgAkIBfCECDAELIANCgICAgICAgIAIUg0AIAJCAYMgAnwhAgtCACACIAJC/////////wdWIgAbIQIgAK0gBXwMAQsCQCADIASEUA0AIAVC//8BUg0AIARCBIYgA0I8iIRCgICAgICAgASEIQJC/w8MAQtC/w8gCkH+hwFLDQAaQgBBgPgAQYH4ACAFUCIBGyIAIAprIglB8ABKDQAaIAMhAiAEIARCgICAgICAwACEIAEbIgYhBAJAQYABIAlrIgFBwABxBEAgAyABQUBqrYYhBEIAIQIMAQsgAUUNACAEIAGtIgWGIAJBwAAgAWutiIQhBCACIAWGIQILIAggAjcDECAIIAQ3AxgCQCAJQcAAcQRAIAYgCUFAaq2IIQNCACEGDAELIAlFDQAgBkHAACAJa62GIAMgCa0iAoiEIQMgBiACiCEGCyAIIAM3AwAgCCAGNwMIIAgpAwhCBIYgCCkDACICQjyIhCEDAkAgACAKRyAIKQMQIAgpAxiEQgBSca0gAkL//////////w+DhCICQoGAgICAgICACFoEQCADQgF8IQMMAQsgAkKAgICAgICAgAhSDQAgA0IBgyADfCEDCyADQoCAgICAgIAIhSADIANC/////////wdWIgAbIQIgAK0LIQMgCEEgaiQAIAsgB0KAgICAgICAgIB/gyADQjSGhCAChL85AwALhhgDE38BfAN+IwBBsARrIgwkACAMQQA2AiwCQCABvSIaQgBTBEBBASERQboIIRMgAZoiAb0hGgwBCyAEQYAQcQRAQQEhEUG9CCETDAELQcAIQbsIIARBAXEiERshEyARRSEVCwJAIBpCgICAgICAgPj/AINCgICAgICAgPj/AFEEQCAAQSAgAiARQQNqIgMgBEH//3txEBwgACATIBEQGSAAQZIJQfYKIAVBIHEiBRtB+wlB+gogBRsgASABYhtBAxAZIABBICACIAMgBEGAwABzEBwgAyACIAIgA0gbIQoMAQsgDEEQaiESAkACfwJAIAEgDEEsahBlIgEgAaAiAUQAAAAAAAAAAGIEQCAMIAwoAiwiBkEBazYCLCAFQSByIg5B4QBHDQEMAwsgBUEgciIOQeEARg0CIAwoAiwhCUEGIAMgA0EASBsMAQsgDCAGQR1rIgk2AiwgAUQAAAAAAACwQaIhAUEGIAMgA0EASBsLIQsgDEEwakGgAkEAIAlBAE4baiINIQcDQCAHAn8gAUQAAAAAAADwQWMgAUQAAAAAAAAAAGZxBEAgAasMAQtBAAsiAzYCACAHQQRqIQcgASADuKFEAAAAAGXNzUGiIgFEAAAAAAAAAABiDQALAkAgCUEATARAIAkhAyAHIQYgDSEIDAELIA0hCCAJIQMDQEEdIAMgA0EdTxshAwJAIAdBBGsiBiAISQ0AIAOtIRxCACEaA0AgBiAaQv////8PgyAGNQIAIByGfCIbQoCU69wDgCIaQoDslKMMfiAbfD4CACAGQQRrIgYgCE8NAAsgG0KAlOvcA1QNACAIQQRrIgggGj4CAAsDQCAIIAciBkkEQCAGQQRrIgcoAgBFDQELCyAMIAwoAiwgA2siAzYCLCAGIQcgA0EASg0ACwsgA0EASARAIAtBGWpBCW5BAWohDyAOQeYARiEQA0BBCUEAIANrIgMgA0EJTxshCgJAIAYgCE0EQCAIKAIARUECdCEHDAELQYCU69wDIAp2IRRBfyAKdEF/cyEWQQAhAyAIIQcDQCAHIAMgBygCACIXIAp2ajYCACAWIBdxIBRsIQMgB0EEaiIHIAZJDQALIAgoAgBFQQJ0IQcgA0UNACAGIAM2AgAgBkEEaiEGCyAMIAwoAiwgCmoiAzYCLCANIAcgCGoiCCAQGyIHIA9BAnRqIAYgBiAHa0ECdSAPShshBiADQQBIDQALC0EAIQMCQCAGIAhNDQAgDSAIa0ECdUEJbCEDQQohByAIKAIAIgpBCkkNAANAIANBAWohAyAKIAdBCmwiB08NAAsLIAsgA0EAIA5B5gBHG2sgDkHnAEYgC0EAR3FrIgcgBiANa0ECdUEJbEEJa0gEQCAMQTBqQYRgQaRiIAlBAEgbaiAHQYDIAGoiCkEJbSIPQQJ0aiEJQQohByAPQXdsIApqIgpBB0wEQANAIAdBCmwhByAKQQFqIgpBCEcNAAsLAkAgCSgCACIQIBAgB24iDyAHbCIKRiAJQQRqIhQgBkZxDQAgECAKayEQAkAgD0EBcUUEQEQAAAAAAABAQyEBIAdBgJTr3ANHDQEgCCAJTw0BIAlBBGstAABBAXFFDQELRAEAAAAAAEBDIQELRAAAAAAAAOA/RAAAAAAAAPA/RAAAAAAAAPg/IAYgFEYbRAAAAAAAAPg/IBAgB0EBdiIURhsgECAUSRshGQJAIBUNACATLQAAQS1HDQAgGZohGSABmiEBCyAJIAo2AgAgASAZoCABYQ0AIAkgByAKaiIDNgIAIANBgJTr3ANPBEADQCAJQQA2AgAgCCAJQQRrIglLBEAgCEEEayIIQQA2AgALIAkgCSgCAEEBaiIDNgIAIANB/5Pr3ANLDQALCyANIAhrQQJ1QQlsIQNBCiEHIAgoAgAiCkEKSQ0AA0AgA0EBaiEDIAogB0EKbCIHTw0ACwsgCUEEaiIHIAYgBiAHSxshBgsDQCAGIgcgCE0iCkUEQCAGQQRrIgYoAgBFDQELCwJAIA5B5wBHBEAgBEEIcSEJDAELIANBf3NBfyALQQEgCxsiBiADSiADQXtKcSIJGyAGaiELQX9BfiAJGyAFaiEFIARBCHEiCQ0AQXchBgJAIAoNACAHQQRrKAIAIg5FDQBBCiEKQQAhBiAOQQpwDQADQCAGIglBAWohBiAOIApBCmwiCnBFDQALIAlBf3MhBgsgByANa0ECdUEJbCEKIAVBX3FBxgBGBEBBACEJIAsgBiAKakEJayIGQQAgBkEAShsiBiAGIAtKGyELDAELQQAhCSALIAMgCmogBmpBCWsiBkEAIAZBAEobIgYgBiALShshCwtBfyEKIAtB/f///wdB/v///wcgCSALciIQG0oNASALIBBBAEdqQQFqIQ4CQCAFQV9xIhVBxgBGBEAgAyAOQf////8Hc0oNAyADQQAgA0EAShshBgwBCyASIAMgA0EfdSIGcyAGa60gEhAqIgZrQQFMBEADQCAGQQFrIgZBMDoAACASIAZrQQJIDQALCyAGQQJrIg8gBToAACAGQQFrQS1BKyADQQBIGzoAACASIA9rIgYgDkH/////B3NKDQILIAYgDmoiAyARQf////8Hc0oNASAAQSAgAiADIBFqIgMgBBAcIAAgEyAREBkgAEEwIAIgAyAEQYCABHMQHAJAAkACQCAVQcYARgRAIAxBEGpBCXIhBSANIAggCCANSxsiCSEIA0AgCDUCACAFECohBgJAIAggCUcEQCAGIAxBEGpNDQEDQCAGQQFrIgZBMDoAACAGIAxBEGpLDQALDAELIAUgBkcNACAGQQFrIgZBMDoAAAsgACAGIAUgBmsQGSAIQQRqIgggDU0NAAsgEARAIABBggxBARAZCyAHIAhNDQEgC0EATA0BA0AgCDUCACAFECoiBiAMQRBqSwRAA0AgBkEBayIGQTA6AAAgBiAMQRBqSw0ACwsgACAGQQkgCyALQQlOGxAZIAtBCWshBiAIQQRqIgggB08NAyALQQlKIRggBiELIBgNAAsMAgsCQCALQQBIDQAgByAIQQRqIAcgCEsbIQ0gDEEQakEJciEFIAghBwNAIAUgBzUCACAFECoiBkYEQCAGQQFrIgZBMDoAAAsCQCAHIAhHBEAgBiAMQRBqTQ0BA0AgBkEBayIGQTA6AAAgBiAMQRBqSw0ACwwBCyAAIAZBARAZIAZBAWohBiAJIAtyRQ0AIABBggxBARAZCyAAIAYgBSAGayIGIAsgBiALSBsQGSALIAZrIQsgB0EEaiIHIA1PDQEgC0EATg0ACwsgAEEwIAtBEmpBEkEAEBwgACAPIBIgD2sQGQwCCyALIQYLIABBMCAGQQlqQQlBABAcCyAAQSAgAiADIARBgMAAcxAcIAMgAiACIANIGyEKDAELIBMgBUEadEEfdUEJcWohCAJAIANBC0sNAEEMIANrIQZEAAAAAAAAMEAhGQNAIBlEAAAAAAAAMECiIRkgBkEBayIGDQALIAgtAABBLUYEQCAZIAGaIBmhoJohAQwBCyABIBmgIBmhIQELIBIgDCgCLCIHIAdBH3UiBnMgBmutIBIQKiIGRgRAIAZBAWsiBkEwOgAACyARQQJyIQsgBUEgcSENIAZBAmsiCSAFQQ9qOgAAIAZBAWtBLUErIAdBAEgbOgAAIARBCHEhBiAMQRBqIQcDQCAHIgUCfyABmUQAAAAAAADgQWMEQCABqgwBC0GAgICAeAsiB0HQxAFqLQAAIA1yOgAAIAEgB7ehRAAAAAAAADBAoiEBAkAgBUEBaiIHIAxBEGprQQFHDQACQCAGDQAgA0EASg0AIAFEAAAAAAAAAABhDQELIAVBLjoAASAFQQJqIQcLIAFEAAAAAAAAAABiDQALQX8hCkH9////ByALIBIgCWsiBmoiDWsgA0gNACAAQSAgAiANIANBAmogByAMQRBqIgdrIgUgBUECayADSBsgBSADGyIKaiIDIAQQHCAAIAggCxAZIABBMCACIAMgBEGAgARzEBwgACAHIAUQGSAAQTAgCiAFa0EAQQAQHCAAIAkgBhAZIABBICACIAMgBEGAwABzEBwgAyACIAIgA0gbIQoLIAxBsARqJAAgCgsEAEIACwQAQQALnwMBCX9B5gohAAJAA0AgAC0AACIBRQ0BIAFBPUYNASAAQQFqIgBBA3ENAAsCQAJAQYCChAggACgCACICayACckGAgYKEeHFBgIGChHhHDQADQEGAgoQIIAJBvfr06QNzIgFrIAFyQYCBgoR4cUGAgYKEeEcNASAAKAIEIQIgAEEEaiIBIQAgAkGAgoQIIAJrckGAgYKEeHFBgIGChHhGDQALDAELIAAhAQsDQCABIgAtAAAiAkUNASAAQQFqIQEgAkE9Rw0ACwsgACIBQeYKRgRAQQAPCwJAIAFB5gprIgBB5gpqLQAADQBBsM8BKAIAIgRFDQAgBCgCACIFRQ0AA0ACQAJ/IAUhAkHmCiEGQQAgACIBRQ0AGkHmCi0AACIDBH8CQANAIAMgAi0AACIHRw0BIAdFDQEgAUEBayIBRQ0BIAJBAWohAiAGLQABIQMgBkEBaiEGIAMNAAtBACEDCyADBUEACyACLQAAawtFBEAgACAFaiIBLQAAQT1GDQELIAQoAgQhBSAEQQRqIQQgBQ0BDAILCyABQQFqIQgLIAgLCQAgACgCPBANC84CAQh/IwBBIGsiAyQAIAMgACgCHCIENgIQIAAoAhQhBSADIAI2AhwgAyABNgIYIAMgBSAEayIBNgIUIAEgAmohBUECIQYgA0EQaiEBAn8DQAJAAkACQCAAKAI8IAEgBiADQQxqEAEiBAR/QZTHASAENgIAQX8FQQALRQRAIAUgAygCDCIHRg0BIAdBAE4NAgwDCyAFQX9HDQILIAAgACgCLCIBNgIcIAAgATYCFCAAIAEgACgCMGo2AhAgAgwDCyABIAcgASgCBCIISyIJQQN0aiIEIAcgCEEAIAkbayIIIAQoAgBqNgIAIAFBDEEEIAkbaiIBIAEoAgAgCGs2AgAgBSAHayEFIAYgCWshBiAEIQEMAQsLIABBADYCHCAAQgA3AxAgACAAKAIAQSByNgIAQQAgBkECRg0AGiACIAEoAgRrCyEKIANBIGokACAKC1YBAn8gACgCPCEEIwBBEGsiACQAIAQgAacgAUIgiKcgAkH/AXEgAEEIahAJIgIEf0GUxwEgAjYCAEF/BUEACyECIAApAwghASAAQRBqJABCfyABIAIbCwYAIAAQAAsGACAAEAML8n4FAnw2fwh7A34GfSMAQeDAAGsiGCQAIBhBADYCIEECIQwCQAJAIAAoAgAiB0GNlJzUAEYNACAHQf+f/Y8FRwRAAkAgB0GAgIDgAEcNACAAKAIEQeqggYECRw0AIAAoAghBjZSc1ABGDQILQc0IEABBASEMDAILQQAhDAsCf0EAQQFB4AAQEyIHRQ0AGiAHQQE2AkwCQAJAAkACQCAMDgMAAwEDCyAHQcMANgJYIAdBxAA2AlQgB0HFADYCUCAHQcYANgIQIAdBxwA2AgQgB0HIADYCHCAHQckANgIYIAdBygA2AhQgB0HLADYCACAHQcwANgJcIAdBzQA2AiwgB0HOADYCKCAHQc8ANgIkIAdB0AA2AiAgB0HRADYCDCAHQdIANgIIIAcQTSIINgIwIAgNAQwCCyAHQdMANgJYIAdB1AA2AlQgB0HVADYCUCAHQdYANgIQIAdB1wA2AgQgB0HYADYCXCAHQdkANgIsIAdB2gA2AiggB0HbADYCJCAHQdwANgIgIAdB3QA2AhwgB0HeADYCGCAHQd8ANgIUIAdB4AA2AgwgB0HhADYCCCAHQeIANgIAIAcCf0EBQYgBEBMiCARAIAgQTSIONgIAAkAgDkUNACAI/QwAAAAAAAAAAAAAAAAAAAAA/QsCbCAIQQA6AHwgCBAzIg42AgQgDkUNACAIEDMiDjYCCCAORQ0AIAgMAgsgCBBwC0EACyIINgIwIAhFDQELIAdBATYCSCAHQQE2AkAgB0EANgI8IAdCADcCNCAHQQE2AkQgBwwBCyAHEBBBAAsiCARAIAhBADYCPCAIQeMANgJICyAIBEAgCEEANgI4IAhB5AA2AkQLIAgEQCAIQQA2AjQgCEHlADYCQAsgGEEkaiIHBEAgB0EAQbjAABAVIgdBADYCuEAgB0J/NwKIQAsgAwRAIBggGCgC3EBBAXI2AtxACyAYIAE2AhwgGCAANgIYIBggADYCFEEBIQxBACEBAkAgGEEUaiIHRQ0AQQFByAAQEyIABH8CfyAAQYCAwAA2AkAgAEGAgMAAEBQiDjYCICAORQRAIAAQEEEADAELIAAgDjYCJCAAQQI2AhwgAEEDNgIYIABBBDYCFCAAQQU2AhAgAEEGNgIsIABBCDYCKCAAIAAoAkRBAnI2AkQgAAsFQQALIgBFDQAgAARAIABBADYCBCAAIAc2AgALIAc1AgghRSAABEAgACBFNwMICwJAIABFDQAgAC0AREECcUUNACAAQT82AhALIAAEQCAAQcEANgIYCyAABEAgAEHCADYCHAsgACEBCyABIQACfyAYQSRqIQECQCAIRQ0AIAFFDQAgCCgCTEUEQCAIQTRqQQFBtMkAQQAQD0EADAILIAgoAjAgASAIKAIYEQMAQQEhCwsgCwtFBEBB3AgQACAAEDQgCBA1DAELAn8gGEEgaiEBQQAhBwJAIABFDQAgCEUNACAIKAJMRQRAIAhBNGpBAUGFygBBABAPQQAMAgsgACAIKAIwIAEgCEE0aiAIKAIAEQEAIQcLIAcLRQRAQfgIEAAgABA0IAgQNSAYKAIgECEMAQsgGCgCICEBQQAhBwJAIAhFDQAgAEUNACAIKAJMRQ0AIAgoAjAgACABIAhBNGogCCgCBBEBACEHCwJAIAcEQEEAIQcCQCAIRQ0AIABFDQAgCCgCTEUNACAIKAIwIAAgCEE0aiAIKAIQEQAAIQcLIAcNAQtB/wkQACAIEDUgABA0IBgoAiAQIQwBCyAAEDQgCBA1IBgoAiAiDSgCHCIABEAgABAQIBgoAiAiDUIANwIcCyANKAIQISECQAJAIAJFBEACQCAERQ0AICFBBEcNAEEBIRlBBCEhDAMLAkACQCANKAIUIgFBA0YNACAhQQNHDQAgDSgCGCIAKAIAIAAoAgRHDQEgACgCNEEBRg0BIA1BAzYCFAwDCyAhQQJLDQAgDUECNgIUDAMLAkACQCABQQNrDgMDAQAECyMAQRBrIg4kAAJAAkACQCANKAIQQQRJDQAgDSgCGCIAKAIAIgEgACgCNEcNACABIAAoAmhHDQAgASAAKAKcAUcNACAAKAIEIgEgACgCOEcNACABIAAoAmxHDQAgASAAKAKgAUYNAQsgDkGHCDYCBCAOQbgKNgIAQejEAUHtPSAOEBYMAQsCQCAAKAIMIAAoAghsIghFBEAgACgCyAEhAQwBC0MAAIA/QX8gACgCtAF0QX9zs5UhSEMAAIA/QX8gACgCgAF0QX9zs5UhSkMAAIA/QX8gACgCTHRBf3OzlSFLQwAAgD9BfyAAKAIYdEF/c7OVIUkgACgCyAEhASAAKAKUASECIAAoAmAhCiAAKAIsIQdBACEAAkAgCEEISQ0AIAcgCiAIQQJ0IgtqIg9JIAogByALaiIXSXENACACIBdJIAcgAiALaiIJSXENACABIBdJIAcgASALaiILSXENACACIA9JIAkgCktxDQAgASAPSSAKIAtJcQ0AIAEgCUkgAiALSXENACAIQXxxIQAgSP0TIT0gSv0TIT4gS/0TIUMgSf0TIUBBACELA0AgAiALQQJ0Ig9qIhf9AAIAIUEgCiAPaiIJ/QACACFCIAcgD2oiEP0MAACAPwAAgD8AAIA/AACAPyBAIBD9AAIA/foB/eYB/eUB/QwAAH9DAAB/QwAAf0MAAH9D/eYB/QwAAIA/AACAPwAAgD8AAIA/ID0gASAPav0AAgD9+gH95gH95QEiP/3mAf34Af0LAgAgCf0MAACAPwAAgD8AAIA/AACAPyBDIEL9+gH95gH95QH9DAAAf0MAAH9DAAB/QwAAf0P95gEgP/3mAf34Af0LAgAgF/0MAACAPwAAgD8AAIA/AACAPyA+IEH9+gH95gH95QH9DAAAf0MAAH9DAAB/QwAAf0P95gEgP/3mAf34Af0LAgAgC0EEaiILIABHDQALIAAgCEYNAQsDQAJ/QwAAgD8gSSAHIABBAnQiC2oiDygCALKUk0MAAH9DlEMAAIA/IEggASALaigCALKUkyJMlCJNi0MAAABPXQRAIE2oDAELQYCAgIB4CyEXIAIgC2oiCSgCACEQIAogC2oiCygCACEMIA8gFzYCACALAn9DAACAPyBLIAyylJNDAAB/Q5QgTJQiTYtDAAAAT10EQCBNqAwBC0GAgICAeAs2AgAgCQJ/QwAAgD8gSiAQspSTQwAAf0OUIEyUIkyLQwAAAE9dBEAgTKgMAQtBgICAgHgLNgIAIABBAWoiACAIRw0ACwsgARAQIA0oAhgiAEEINgKAASAAQQg2AkwgAEEINgIYIABBADYCyAEgDUEBNgIUIA0gDSgCEEEBayIANgIQIABBBEkNAEEDIQADQCANKAIYIABBNGxqIgEgASgCZDYCMCABIAH9AAJU/QsCICABIAH9AAJE/QsCECABIAH9AAI0/QsCACAAQQFqIgAgDSgCEEkNAAsLIA5BEGokAAwDCyMAQRBrIgskAAJAAkACQCANKAIQQQNJDQAgDSgCGCIAKAIAIgEgACgCNEcNACABIAAoAmhHDQAgACgCBCIBIAAoAjhHDQAgASAAKAJsRg0BCyALQcUINgIEIAtBuAo2AgBB6MQBQZc+IAsQFgwBCwJAIAAoAgwgACgCCGwiAkUNAEF/IAAoAhgiCnRBf3MhAUEAQQEgCkEBa3QiCiAAKAKIARshD0EAIAogACgCVBshFyAAKAKUASEKIAAoAmAhByAAKAIsIQ5BACEAAkAgAkEESQ0AIA4gByACQQJ0IghqIglJIAcgCCAOaiIQSXENACAKIBBJIA4gCCAKaiIISXENACAHIAhJIAkgCktxDQAgAkF8cSEAIAH9ESE/IA/9ESFAIBf9ESFBQQAhCANAIA4gCEECdCIJaiIQID8gCSAKaiIM/QACACBA/bEB/foBIj39DGl0sz9pdLM/aXSzP2l0sz/95gEgByAJaiIJ/QACACBB/bEB/foBIj79DLNZGrizWRq4s1kauLNZGrj95gEgEP0AAgD9+gEiQ/3kAf3kAf0MAAAAPwAAAD8AAAA/AAAAP/3kAf34ASJC/QwAAAAAAAAAAAAAAAAAAAAA/bgBID8gQv05/VL9CwIAIAkgPyA9/QwZ0Da/GdA2vxnQNr8Z0Da//eYBIEP9DNUJgD/VCYA/1QmAP9UJgD/95gEgPv0MJzGwvicxsL4nMbC+JzGwvv3mAf3kAf3kAf0MAAAAPwAAAD8AAAA/AAAAP/3kAf34ASJC/QwAAAAAAAAAAAAAAAAAAAAA/bgBID8gQv05/VL9CwIAIAwgPyA9/Qy9Nwa3vTcGt703Bre9Nwa3/eYBIEP9DGb0fz9m9H8/ZvR/P2b0fz/95gEgPv0MNdLiPzXS4j810uI/NdLiP/3mAf3kAf3kAf0MAAAAPwAAAD8AAAA/AAAAP/3kAf34ASI9/QwAAAAAAAAAAAAAAAAAAAAA/bgBID8gPf05/VL9CwIAIAhBBGoiCCAARw0ACyAAIAJGDQELA0ACfyAKIABBAnQiCGoiCSgCACAPa7IiSENpdLM/lCAHIAhqIhAoAgAgF2uyIkpDs1kauJQgCCAOaiIMKAIAsiJLkpJDAAAAP5IiSYtDAAAAT10EQCBJqAwBC0GAgICAeAshCCAMIAEgCEEAIAhBAEobIAEgCEgbNgIAIBAgAQJ/IEhDGdA2v5QgS0PVCYA/lCBKQycxsL6UkpJDAAAAP5IiSYtDAAAAT10EQCBJqAwBC0GAgICAeAsiCEEAIAhBAEobIAEgCEgbNgIAIAkgAQJ/IEhDvTcGt5QgS0Nm9H8/lCBKQzXS4j+UkpJDAAAAP5IiSItDAAAAT10EQCBIqAwBC0GAgICAeAsiCEEAIAhBAEobIAEgCEgbNgIAIABBAWoiACACRw0ACwsgDUEBNgIUCyALQRBqJAAMAgsgISACIAIgIUsbISFBASEZDAELAkACQAJ/AkACQCANKAIYIgEoAgBBAUcNAAJAAkAgASgCNEEBaw4CAQACCyABKAJoQQJHDQECQCABKAIEQQFHDQAgASgCOEECRw0AIAEoAmxBAkcNAEEAIQsgDSIXKAIYIgAoAhghASAAKAKUASERIAAoAmAhCiAAKAIsIRAgACgCCCINIAAoAgwiAmxBAnQiABAYIQcgABAYIQggABAYIQ4CQAJAAkACQAJAAkAgB0UNACAIRQ0AIA5FDQBBfyABdEF/cyEJQQEgAUEBa3QhDCACIBcoAgRBAXEiAGshHiAXKAIAQQFxIRsgAEUNAyANRQ0DAn9BACAMa7K7IgVEarx0kxgE1j+iIAVEDAIrhxbZ5j+ioCIGmUQAAAAAAADgQWMEQCAGqgwBC0GAgICAeAshFAJ/IAVEJzEIrBxa/D+iIgaZRAAAAAAAAOBBYwRAIAaqDAELQYCAgIB4CyEaIA1BCEkhOAJ/IAVEO99PjZdu9j+iIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4CyEdIDgNASAIIAdrQRBJDQEgDiAHa0EQSQ0BIAcgEGtBEEkNASAOIAhrQRBJDQEgCCAQa0EQSQ0BIA4gEGtBEEkNASAOIA1BfHEiC0ECdCICaiEBIAIgB2ohACAa/REhPiAU/REhQyAJ/REhPyAd/REhQANAIAcgD0ECdCITav0MAAAAAAAAAAAAAAAAAAAAACAQIBNq/QACACI9IED9rgEiQSA//bYBIEH9DAAAAAAAAAAAAAAAAAAAAAD9Of1S/QsCACAIIBNq/QwAAAAAAAAAAAAAAAAAAAAAID0gQ/2xASJBID/9tgEgQf0MAAAAAAAAAAAAAAAAAAAAAP05/VL9CwIAIA4gE2r9DAAAAAAAAAAAAAAAAAAAAAAgPSA+/a4BIj0gP/22ASA9/QwAAAAAAAAAAAAAAAAAAAAA/Tn9Uv0LAgAgD0EEaiIPIAtHDQALIAIgEGohECACIAhqIQIgCyANRg0EDAILIAcQECAIEBAgDhAQDAQLIAchACAIIQIgDiEBCwNAIAAgECgCACIPIB1qIhMgCSAJIBNKG0EAIBNBAE4bNgIAIAIgDyAUayITIAkgCSATShtBACATQQBOGzYCACABIA8gGmoiDyAJIAkgD0obQQAgD0EAThs2AgAgAUEEaiEBIAJBBGohAiAAQQRqIQAgEEEEaiEQIAtBAWoiCyANRw0ACwwBCyAOIQEgCCECIAchAAsgDSAbayEaAkAgHkF+cSIdBH8Cf0EAIAxrsrsiBURqvHSTGATWP6IgBUQMAiuHFtnmP6KgIgaZRAAAAAAAAOBBYwRAIAaqDAELQYCAgIB4CyEiIBpBfnEiHEEBayE5An8gBUQnMQisHFr8P6IiBplEAAAAAAAA4EFjBEAgBqoMAQtBgICAgHgLISMgOUF+cSE6An8gBUQ730+Nl272P6IiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgLISQgHUEBayElIDpBAmohJiANQQJ0IQ0DQCABIA1qIQ8gAiANaiETIAAgDWohCyANIBBqIRQgGwRAIAAgECgCACIVICRqIhIgCSAJIBJKG0EAIBJBAE4bNgIAIAIgFSAiayISIAkgCSASShtBACASQQBOGzYCACABIBUgI2oiFSAJIAkgFUobQQAgFUEAThs2AgAgCigCACEWIAsCfyARKAIAIAxrsrsiBUQ730+Nl272P6IiBplEAAAAAAAA4EFjBEAgBqoMAQtBgICAgHgLIBQoAgAiFWoiEiAJIAkgEkobQQAgEkEAThs2AgAgEyAVAn8gFiAMa7K7IgZEarx0kxgE1j+iIAVEDAIrhxbZ5j+ioCIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAtrIhIgCSAJIBJKG0EAIBJBAE4bNgIAIA8CfyAGRCcxCKwcWvw/oiIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAsgFWoiFSAJIAkgFUobQQAgFUEAThs2AgAgD0EEaiEPIBNBBGohEyALQQRqIQsgFEEEaiEUIAJBBGohAiAQQQRqIRAgAUEEaiEBIABBBGohAAtBACEVIBwEfwNAIAooAgAhHyAAAn8gESgCACAMa7K7IgVEO99PjZdu9j+iIgaZRAAAAAAAAOBBYwRAIAaqDAELQYCAgIB4CyAQKAIAIhJqIhYgCSAJIBZKG0EAIBZBAE4bNgIAIAIgEgJ/IB8gDGuyuyIGRGq8dJMYBNY/oiAFRAwCK4cW2eY/oqAiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgLayIWIAkgCSAWShtBACAWQQBOGzYCACABAn8gBkQnMQisHFr8P6IiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgLIBJqIhIgCSAJIBJKG0EAIBJBAE4bNgIAIAooAgAhHyAAAn8gESgCACAMa7K7IgVEO99PjZdu9j+iIgaZRAAAAAAAAOBBYwRAIAaqDAELQYCAgIB4CyAQKAIEIhJqIhYgCSAJIBZKG0EAIBZBAE4bNgIEIAIgEgJ/IB8gDGuyuyIGRGq8dJMYBNY/oiAFRAwCK4cW2eY/oqAiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgLayIWIAkgCSAWShtBACAWQQBOGzYCBCABAn8gBkQnMQisHFr8P6IiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgLIBJqIhIgCSAJIBJKG0EAIBJBAE4bNgIEIAooAgAhHyALAn8gESgCACAMa7K7IgVEO99PjZdu9j+iIgaZRAAAAAAAAOBBYwRAIAaqDAELQYCAgIB4CyAUKAIAIhJqIhYgCSAJIBZKG0EAIBZBAE4bNgIAIBMgEgJ/IB8gDGuyuyIGRGq8dJMYBNY/oiAFRAwCK4cW2eY/oqAiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgLayIWIAkgCSAWShtBACAWQQBOGzYCACAPAn8gBkQnMQisHFr8P6IiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgLIBJqIhIgCSAJIBJKG0EAIBJBAE4bNgIAIAooAgAhHyALAn8gESgCACAMa7K7IgVEO99PjZdu9j+iIgaZRAAAAAAAAOBBYwRAIAaqDAELQYCAgIB4CyAUKAIEIhJqIhYgCSAJIBZKG0EAIBZBAE4bNgIEIBMgEgJ/IB8gDGuyuyIGRGq8dJMYBNY/oiAFRAwCK4cW2eY/oqAiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgLayIWIAkgCSAWShtBACAWQQBOGzYCBCAPAn8gBkQnMQisHFr8P6IiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgLIBJqIhIgCSAJIBJKG0EAIBJBAE4bNgIEIBFBBGohESAKQQRqIQogD0EIaiEPIBNBCGohEyALQQhqIQsgFEEIaiEUIAFBCGohASACQQhqIQIgAEEIaiEAIBBBCGohECAVQQJqIhUgHEkNAAsgJgVBAAsgGkkEfyAKKAIAIRYgAAJ/IBEoAgAgDGuyuyIFRDvfT42XbvY/oiIGmUQAAAAAAADgQWMEQCAGqgwBC0GAgICAeAsgECgCACIVaiISIAkgCSASShtBACASQQBOGzYCACACIBUCfyAWIAxrsrsiBkRqvHSTGATWP6IgBUQMAiuHFtnmP6KgIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4C2siEiAJIAkgEkobQQAgEkEAThs2AgAgAQJ/IAZEJzEIrBxa/D+iIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4CyAVaiIVIAkgCSAVShtBACAVQQBOGzYCACAKKAIAIRUgCwJ/IBEoAgAgDGuyuyIFRDvfT42XbvY/oiIGmUQAAAAAAADgQWMEQCAGqgwBC0GAgICAeAsgFCgCACILaiIUIAkgCSAUShtBACAUQQBOGzYCACATIAsCfyAVIAxrsrsiBkRqvHSTGATWP6IgBUQMAiuHFtnmP6KgIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4C2siEyAJIAkgE0obQQAgE0EAThs2AgAgDwJ/IAZEJzEIrBxa/D+iIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4CyALaiILIAkgCSALShtBACALQQBOGzYCACARQQRqIREgCkEEaiEKIAJBBGohAiAQQQRqIRAgAEEEaiEAIAFBBGoFIAELIA1qIQEgAiANaiECIAAgDWohACANIBBqIRAgIEECaiIgIB1JDQALICVBfnFBAmoFQQALIB5PDQAgGwRAIAACf0EAIAxrsrsiBUQ730+Nl272P6IiBplEAAAAAAAA4EFjBEAgBqoMAQtBgICAgHgLIBAoAgAiC2oiDSAJIAkgDUobQQAgDUEAThs2AgAgAiALAn8gBURqvHSTGATWP6IgBUQMAiuHFtnmP6KgIgaZRAAAAAAAAOBBYwRAIAaqDAELQYCAgIB4C2siDSAJIAkgDUobQQAgDUEAThs2AgAgAQJ/IAVEJzEIrBxa/D+iIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4CyALaiILIAkgCSALShtBACALQQBOGzYCACACQQRqIQIgEEEEaiEQIAFBBGohASAAQQRqIQALIBpBfnEiIAR/ICBBAWsiC0F+cSE7AkACf0EAICBBD0kNABpBACAAIAIgC0EBdiIUQQN0QQhqIhNqIgtJIAIgACATaiINSXENABpBACABIA1JIAAgASATaiIPSXENABpBACAAIBAgE2oiE0kgDSAQS3ENABpBACAKIA1JIAAgCiAUQQJ0QQRqIh5qIhtJcQ0AGkEAIA0gEUsgACARIB5qIg1JcQ0AGkEAIAIgD0kgASALSXENABpBACACIBNJIAsgEEtxDQAaQQAgCiALSSACIBtJcQ0AGkEAIAIgDUkgCyARS3ENABpBACABIBNJIA8gEEtxDQAaQQAgCiAPSSABIBtJcQ0AGkEAIAEgDUkgDyARS3ENABogCiAUQQFqIhZB/P///wdxIhtBAnQiImohCyABIBtBA3QiHmohDSAAIB5qIQ8gCf0RIT8gDP0RIUNBACETA0AgECATQQN0IhRBGHIiHWoiIyAQIBRBEHIiHGoiJCAQIBRBCHIiFWoiJSAQIBRqIib9CQIA/VYCAAH9VgIAAv1WAgADIT0CfyARIBNBAnQiH2r9AAIAIEP9sQH9+gEiPv1fIkD9DDvfT42XbvY/O99PjZdu9j/98gEiQf0hASIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAshJyAKIB9q/QACACFCIAAgFGoiH/0MAAAAAAAAAAAAAAAAAAAAACA9An8gQf0hACIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAv9ESAn/RwBAn8gPiA+/Q0ICQoLDA0ODwABAgMAAQID/V8iQf0MO99PjZdu9j8730+Nl272P/3yASI+/SEAIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4C/0cAgJ/ID79IQEiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgL/RwDIkT9rgEiPiA//bYBID79DAAAAAAAAAAAAAAAAAAAAAD9Of1SIj79WgIAACAAIBVqIicgPv1aAgABIAAgHGoiKSA+/VoCAAIgACAdaiIqID79WgIAAwJ/IEIgQ/2xAf36ASI+/V8iQv0Marx0kxgE1j9qvHSTGATWP/3yASBA/QwMAiuHFtnmPwwCK4cW2eY//fIB/fABIkD9IQEiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgLISggAiAUaiIr/QwAAAAAAAAAAAAAAAAAAAAAID0CfyBA/SEAIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4C/0RICj9HAECfyA+/QwAAAAAAAAAAAAAAAAAAAAA/Q0ICQoLDA0ODwABAgMAAQID/V8iQP0Marx0kxgE1j9qvHSTGATWP/3yASBB/QwMAiuHFtnmPwwCK4cW2eY//fIB/fABIj79IQAiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgL/RwCAn8gPv0hASIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAv9HAMiQf2xASI+ID/9tgEgPv0MAAAAAAAAAAAAAAAAAAAAAP05/VIiPv1aAgAAIAIgFWoiKCA+/VoCAAEgAiAcaiIsID79WgIAAiACIB1qIi0gPv1aAgADAn8gQv0MJzEIrBxa/D8nMQisHFr8P/3yASI+/SEBIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4CyEuIAEgFGoiFP0MAAAAAAAAAAAAAAAAAAAAACA9An8gPv0hACIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAv9ESAu/RwBAn8gQP0MJzEIrBxa/D8nMQisHFr8P/3yASI9/SEAIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4C/0cAgJ/ID39IQEiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgL/RwDIkD9rgEiPSA//bYBID39DAAAAAAAAAAAAAAAAAAAAAD9Of1SIj39WgIAACABIBVqIhUgPf1aAgABIAEgHGoiHCA9/VoCAAIgASAdaiIdID39WgIAAyAf/QwAAAAAAAAAAAAAAAAAAAAAICNBBGogJEEEaiAlQQRqICb9CQIE/VYCAAH9VgIAAv1WAgADIj4gRP2uASI9ID/9tgEgPf0MAAAAAAAAAAAAAAAAAAAAAP05/VIiPf1aAgQAICcgPf1aAgQBICkgPf1aAgQCICogPf1aAgQDICv9DAAAAAAAAAAAAAAAAAAAAAAgPiBB/bEBIj0gP/22ASA9/QwAAAAAAAAAAAAAAAAAAAAA/Tn9UiI9/VoCBAAgKCA9/VoCBAEgLCA9/VoCBAIgLSA9/VoCBAMgFP0MAAAAAAAAAAAAAAAAAAAAACA+IED9rgEiPSA//bYBID39DAAAAAAAAAAAAAAAAAAAAAD9Of1SIj39WgIEACAVID39WgIEASAcID39WgIEAiAdID39WgIEAyATQQRqIhMgG0cNAAsgESAiaiERIBAgHmohECACIB5qIQIgFiAbRgRAIA8hACANIQEgCyEKDAILIA8hACANIQEgCyEKIBtBAXQLIQsDQCAKKAIAIRMgAAJ/IBEoAgAgDGuyuyIFRDvfT42XbvY/oiIGmUQAAAAAAADgQWMEQCAGqgwBC0GAgICAeAsgECgCACINaiIPIAkgCSAPShtBACAPQQBOGzYCACACIA0CfyATIAxrsrsiBkRqvHSTGATWP6IgBUQMAiuHFtnmP6KgIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4C2siDyAJIAkgD0obQQAgD0EAThs2AgAgAQJ/IAZEJzEIrBxa/D+iIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4CyANaiINIAkgCSANShtBACANQQBOGzYCACAKKAIAIRMgAAJ/IBEoAgAgDGuyuyIFRDvfT42XbvY/oiIGmUQAAAAAAADgQWMEQCAGqgwBC0GAgICAeAsgECgCBCINaiIPIAkgCSAPShtBACAPQQBOGzYCBCACIA0CfyATIAxrsrsiBkRqvHSTGATWP6IgBUQMAiuHFtnmP6KgIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4C2siDyAJIAkgD0obQQAgD0EAThs2AgQgAQJ/IAZEJzEIrBxa/D+iIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4CyANaiINIAkgCSANShtBACANQQBOGzYCBCARQQRqIREgCkEEaiEKIAFBCGohASACQQhqIQIgAEEIaiEAIBBBCGohECALQQJqIgsgIEkNAAsLIDtBAmoFQQALIBpPDQAgCigCACELIAACfyARKAIAIAxrsrsiBUQ730+Nl272P6IiBplEAAAAAAAA4EFjBEAgBqoMAQtBgICAgHgLIBAoAgAiAGoiCiAJIAkgCkobQQAgCkEAThs2AgAgAiAAAn8gCyAMa7K7IgZEarx0kxgE1j+iIAVEDAIrhxbZ5j+ioCIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAtrIgIgCSACIAlIG0EAIAJBAE4bNgIAIAECfyAGRCcxCKwcWvw/oiIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAsgAGoiACAJIAAgCUgbQQAgAEEAThs2AgALIBcoAhgoAiwQECAXKAIYIgAgBzYCLCAAKAJgEBAgFygCGCIAIAg2AmAgACgClAEQECAXKAIYIgAgDjYClAEgACAA/QACACI//QsCaCAAID/9CwI0IBdBATYCFAsMBwsgASgCBEEBRw0BIAEoAjhBAUcNASABKAJsQQFHDQEgASgCGCEAIAEoApQBIQIgASgCYCEHIAEoAiwhDCABKAIIIgogASgCDCIWbEECdCIBEBghDyABEBghFyABEBghCSAPRQ0FIBdFDQUgCUUNBSAWBEAgCiANKAIAQQFxIh9rISICf0EAQQEgAEEBa3QiFGuyuyIFRGq8dJMYBNY/oiAFRAwCK4cW2eY/oqAiBplEAAAAAAAA4EFjBEAgBqoMAQtBgICAgHgLISdBfyAAdCE8ICJBfnEiHUEBayIKQQF2IgBBAWohIwJ/IAVEJzEIrBxa/D+iIgaZRAAAAAAAAOBBYwRAIAaqDAELQYCAgIB4CyEpIApBfnEhCiAAQQJ0IQggAEEDdCEAICNBfHEhGyA8QX9zIRECfyAFRDvfT42XbvY/oiIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAshKiAKQQJqISQgCEEEaiElIABBCGohICAbQQJ0ISYgG0EDdCEeIBtBAXQhECAR/REhPyAU/REhQyAdQQdJISggDyEKIBchACAJIQ4DQCAfBEAgCiAMKAIAIgEgKmoiCCARIAggEUgbQQAgCEEAThs2AgAgACABICdrIgggESAIIBFIG0EAIAhBAE4bNgIAIA4gASApaiIBIBEgASARSBtBACABQQBOGzYCACAOQQRqIQ4gCkEEaiEKIAxBBGohDCAAQQRqIQALAn8CfyAdRQRAIAchASAOIQsgCiEIQQAMAQtBACEZAkACQCAoDQAgCiAAICBqIgFJIAAgCiAgaiIISXENACAKIA4gIGoiC0kgCCAOS3ENACAKIAwgIGoiGkkgCCAMS3ENACAHIAhJIAogByAlaiIcSXENACACIAhJIAogAiAlaiIISXENACAAIAtJIAEgDktxDQAgACAaSSABIAxLcQ0AIAAgHEkgASAHS3ENACAAIAhJIAEgAktxDQAgDiAaSSALIAxLcQ0AIA4gHEkgByALSXENACACIAtJIAggDktxDQAgByAmaiEBIA4gHmohCyAKIB5qIQgDQCAMIBlBA3QiGkEYciIcaiIrIAwgGkEQciIVaiIsIAwgGkEIciISaiItIAwgGmoiLv0JAgD9VgIAAf1WAgAC/VYCAAMhPQJ/IAIgGUECdCIvav0AAgAgQ/2xAf36ASI+/V8iQP0MO99PjZdu9j8730+Nl272P/3yASJB/SEBIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4CyEwIAcgL2r9AAIAIUIgCiAaaiIv/QwAAAAAAAAAAAAAAAAAAAAAID0CfyBB/SEAIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4C/0RIDD9HAECfyA+ID79DQgJCgsMDQ4PAAECAwABAgP9XyJB/Qw730+Nl272PzvfT42XbvY//fIBIj79IQAiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgL/RwCAn8gPv0hASIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAv9HAMiRP2uASI+ID/9tgEgPv0MAAAAAAAAAAAAAAAAAAAAAP05/VIiPv1aAgAAIAogEmoiMCA+/VoCAAEgCiAVaiIyID79WgIAAiAKIBxqIjMgPv1aAgADAn8gQiBD/bEB/foBIj79XyJC/QxqvHSTGATWP2q8dJMYBNY//fIBIED9DAwCK4cW2eY/DAIrhxbZ5j/98gH98AEiQP0hASIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAshMSAAIBpqIjT9DAAAAAAAAAAAAAAAAAAAAAAgPQJ/IED9IQAiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgL/REgMf0cAQJ/ID79DAAAAAAAAAAAAAAAAAAAAAD9DQgJCgsMDQ4PAAECAwABAgP9XyJA/QxqvHSTGATWP2q8dJMYBNY//fIBIEH9DAwCK4cW2eY/DAIrhxbZ5j/98gH98AEiPv0hACIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAv9HAICfyA+/SEBIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4C/0cAyJB/bEBIj4gP/22ASA+/QwAAAAAAAAAAAAAAAAAAAAA/Tn9UiI+/VoCAAAgACASaiIxID79WgIAASAAIBVqIjUgPv1aAgACIAAgHGoiNiA+/VoCAAMCfyBC/QwnMQisHFr8PycxCKwcWvw//fIBIj79IQEiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgLITcgDiAaaiIa/QwAAAAAAAAAAAAAAAAAAAAAID0CfyA+/SEAIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4C/0RIDf9HAECfyBA/QwnMQisHFr8PycxCKwcWvw//fIBIj39IQAiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgL/RwCAn8gPf0hASIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAv9HAMiQP2uASI9ID/9tgEgPf0MAAAAAAAAAAAAAAAAAAAAAP05/VIiPf1aAgAAIA4gEmoiEiA9/VoCAAEgDiAVaiIVID39WgIAAiAOIBxqIhwgPf1aAgADIC/9DAAAAAAAAAAAAAAAAAAAAAAgK0EEaiAsQQRqIC1BBGogLv0JAgT9VgIAAf1WAgAC/VYCAAMiPiBE/a4BIj0gP/22ASA9/QwAAAAAAAAAAAAAAAAAAAAA/Tn9UiI9/VoCBAAgMCA9/VoCBAEgMiA9/VoCBAIgMyA9/VoCBAMgNP0MAAAAAAAAAAAAAAAAAAAAACA+IEH9sQEiPSA//bYBID39DAAAAAAAAAAAAAAAAAAAAAD9Of1SIj39WgIEACAxID39WgIEASA1ID39WgIEAiA2ID39WgIEAyAa/QwAAAAAAAAAAAAAAAAAAAAAID4gQP2uASI9ID/9tgEgPf0MAAAAAAAAAAAAAAAAAAAAAP05/VIiPf1aAgQAIBIgPf1aAgQBIBUgPf1aAgQCIBwgPf1aAgQDIBlBBGoiGSAbRw0ACyACICZqIQIgDCAeaiEMIAAgHmohACAQIRkgJCAbICNGDQIaDAELIAohCCAOIQsgByEBCwNAIAEoAgAhDiAIAn8gAigCACAUa7K7IgVEO99PjZdu9j+iIgaZRAAAAAAAAOBBYwRAIAaqDAELQYCAgIB4CyAMKAIAIgpqIgcgESAHIBFIG0EAIAdBAE4bNgIAIAAgCgJ/IA4gFGuyuyIGRGq8dJMYBNY/oiAFRAwCK4cW2eY/oqAiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgLayIHIBEgByARSBtBACAHQQBOGzYCACALAn8gBkQnMQisHFr8P6IiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgLIApqIgogESAKIBFIG0EAIApBAE4bNgIAIAEoAgAhDiAIAn8gAigCACAUa7K7IgVEO99PjZdu9j+iIgaZRAAAAAAAAOBBYwRAIAaqDAELQYCAgIB4CyAMKAIEIgpqIgcgESAHIBFIG0EAIAdBAE4bNgIEIAAgCgJ/IA4gFGuyuyIGRGq8dJMYBNY/oiAFRAwCK4cW2eY/oqAiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgLayIHIBEgByARSBtBACAHQQBOGzYCBCALAn8gBkQnMQisHFr8P6IiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgLIApqIgogESAKIBFIG0EAIApBAE4bNgIEIAJBBGohAiABQQRqIQEgC0EIaiELIABBCGohACAIQQhqIQggDEEIaiEMIBlBAmoiGSAdSQ0ACyAkCyAiTwRAIAEhByAIIQogCwwBCyABKAIAIQ4gCAJ/IAIoAgAgFGuyuyIFRDvfT42XbvY/oiIGmUQAAAAAAADgQWMEQCAGqgwBC0GAgICAeAsgDCgCACIKaiIHIBEgByARSBtBACAHQQBOGzYCACAAIAoCfyAOIBRrsrsiBkRqvHSTGATWP6IgBUQMAiuHFtnmP6KgIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4C2siByARIAcgEUgbQQAgB0EAThs2AgAgCwJ/IAZEJzEIrBxa/D+iIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4CyAKaiIKIBEgCiARSBtBACAKQQBOGzYCACACQQRqIQIgAUEEaiEHIABBBGohACAIQQRqIQogDEEEaiEMIAtBBGoLIQ4gE0EBaiITIBZHDQALCyANKAIYKAIsEBAgDSgCGCIAIA82AiwgACgCYBAQIA0oAhgiACAXNgJgIAAoApQBEBAgDSgCGCIAIAk2ApQBIAAgAP0AAgAiP/0LAmggACA//QsCNCANQQE2AhRBACEZDAYLIAEoAmhBAUcNACABKAIEQQFHDQAgASgCOEEBRw0AIAEoAmxBAUcNACABKAIYIQIgASgClAEhCCABKAJgIQwgASgCLCEAIAEoAgwgASgCCGwiF0ECdCIBEBghByABEBghDyABEBghDgJAIAdFDQAgD0UNACAORQ0AIBdFDQRBfyACdEF/cyEZQQEgAkEBa3QhESAXQQhJDQIgDyAHa0EQSQ0CIA4gB2tBEEkNAiAHIABrQRBJDQIgByAMa0EQSQ0CIAcgCGtBEEkNAiAOIA9rQRBJDQIgDyAAa0EQSQ0CIA8gDGtBEEkNAiAPIAhrQRBJDQIgDiAAa0EQSQ0CIA4gDGtBEEkNAiAOIAhrQRBJDQIgCCAXQXxxIgpBAnQiCWohCyAJIA5qIQEgByAJaiECIBn9ESE/IBH9ESE9A0ACfyAIIBNBAnQiEGr9AAIAID39sQH9+gEiPv1fIkD9DDvfT42XbvY/O99PjZdu9j/98gEiQf0hASIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAshFCAMIBBq/QACACFCIAcgEGr9DAAAAAAAAAAAAAAAAAAAAAAgACAQav0AAgAiQwJ/IEH9IQAiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgL/REgFP0cAQJ/ID4gPv0NCAkKCwwNDg8AAQIDAAECA/1fIj79DDvfT42XbvY/O99PjZdu9j/98gEiQf0hACIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAv9HAICfyBB/SEBIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4C/0cA/2uASJBID/9tgEgQf0MAAAAAAAAAAAAAAAAAAAAAP05/VL9CwIAAn8gQiA9/bEB/foBIkH9XyJC/QxqvHSTGATWP2q8dJMYBNY//fIBIED9DAwCK4cW2eY/DAIrhxbZ5j/98gH98AEiQP0hASIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAshFCAPIBBq/QwAAAAAAAAAAAAAAAAAAAAAIEMCfyBA/SEAIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4C/0RIBT9HAECfyBB/QwAAAAAAAAAAAAAAAAAAAAA/Q0ICQoLDA0ODwABAgMAAQID/V8iQP0Marx0kxgE1j9qvHSTGATWP/3yASA+/QwMAiuHFtnmPwwCK4cW2eY//fIB/fABIj79IQAiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgL/RwCAn8gPv0hASIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAv9HAP9sQEiPiA//bYBID79DAAAAAAAAAAAAAAAAAAAAAD9Of1S/QsCAAJ/IEL9DCcxCKwcWvw/JzEIrBxa/D/98gEiPv0hASIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAshFCAOIBBq/QwAAAAAAAAAAAAAAAAAAAAAIEMCfyA+/SEAIgWZRAAAAAAAAOBBYwRAIAWqDAELQYCAgIB4C/0RIBT9HAECfyBA/QwnMQisHFr8PycxCKwcWvw//fIBIj79IQAiBZlEAAAAAAAA4EFjBEAgBaoMAQtBgICAgHgL/RwCAn8gPv0hASIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAv9HAP9rgEiPiA//bYBID79DAAAAAAAAAAAAAAAAAAAAAD9Of1S/QsCACATQQRqIhMgCkcNAAsgCiAXRg0EIAkgDGohDCAAIAlqIQAgCSAPagwDCyAHEBAgDxAQIA4QEAwFCyAYQbkDNgIEIBhBuAo2AgBB6MQBQcI+IBgQFgwECyAHIQIgDiEBIAghCyAPCyEIA0AgDCgCACETIAICfyALKAIAIBFrsrsiBUQ730+Nl272P6IiBplEAAAAAAAA4EFjBEAgBqoMAQtBgICAgHgLIAAoAgAiCWoiECAZIBAgGUgbQQAgEEEAThs2AgAgCCAJAn8gEyARa7K7IgZEarx0kxgE1j+iIAVEDAIrhxbZ5j+ioCIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAtrIhAgGSAQIBlIG0EAIBBBAE4bNgIAIAECfyAGRCcxCKwcWvw/oiIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAsgCWoiCSAZIAkgGUgbQQAgCUEAThs2AgAgAUEEaiEBIAhBBGohCCACQQRqIQIgC0EEaiELIAxBBGohDCAAQQRqIQAgCkEBaiIKIBdHDQALCyANKAIYKAIsEBAgDSgCGCIAIAc2AiwgACgCYBAQIA0oAhgiACAPNgJgIAAoApQBEBAgDSgCGCAONgKUASANQQE2AhRBACEZDAELIA8QECAXEBAgCRAQCyAYKAIgIQACQCADDQAgIUUNACAAKAIYIQ5BACETA0AgDiATQTRsaiIDKAIYIgJBCEcEQAJAIAJBB00EQCADKAIMIAMoAghsIQEgAygCLCEKIAMoAiAEQCABRQ0CQQEgAkEBa3StIUVBACEHIAFBBE8EQCABQXxxIQcgRf0SIT9BACEMA0AgCiAMQQJ0aiICIAL9AAIAIj39xwFBB/3LASI+/R0AID/9HQAiRn/9EiA+/R0BID/9HQEiR3/9HgEgPSA//Q0ICQoLDA0ODwABAgMAAQID/ccBQQf9ywEiPf0dACBGf/0SID39HQEgR3/9HgH9DQABAgMICQoLEBESExgZGhv9CwIAIAxBBGoiDCAHRw0ACyABIAdGDQMLA0AgCiAHQQJ0aiICIAI0AgBCB4YgRX8+AgAgB0EBaiIHIAFHDQALDAILIAFFDQFBfyACdEF/c60hRUEAIQcgAUEETwRAIAFBfHEhByBF/RIhP0EAIQwDQCAKIAxBAnRqIgIgAv0AAgAiPf3JAf0M/wAAAAAAAAD/AAAAAAAAAP3VASI+/R0AID/9HQAiRoD9EiA+/R0BID/9HQEiR4D9HgEgPSA//Q0ICQoLDA0ODwABAgMAAQID/ckB/Qz/AAAAAAAAAP8AAAAAAAAA/dUBIj39HQAgRoD9EiA9/R0BIEeA/R4B/Q0AAQIDCAkKCxAREhMYGRob/QsCACAMQQRqIgwgB0cNAAsgASAHRg0CCwNAIAogB0ECdGoiAiACNQIAQv8BfiBFgD4CACAHQQFqIgcgAUcNAAsMAQsgAkEIayEKIAMoAgwgAygCCGwhASADKAIsIQggAygCIARAIAFFDQFBACEHIAFBBE8EQCABQXxxIQdBACECA0AgCCACQQJ0aiILIAv9AAIAIAr9rAH9CwIAIAJBBGoiAiAHRw0ACyABIAdGDQILA0AgCCAHQQJ0aiICIAIoAgAgCnU2AgAgB0EBaiIHIAFHDQALDAELIAFFDQBBACEHIAFBBE8EQCABQXxxIQdBACECA0AgCCACQQJ0aiILIAv9AAIAIAr9rQH9CwIAIAJBBGoiAiAHRw0ACyABIAdGDQELA0AgCCAHQQJ0aiICIAIoAgAgCnY2AgAgB0EBaiIHIAFHDQALCyADQQg2AhgLIBNBAWoiEyAhRw0ACwsgACgCDCAAKAIIbCEBAkAgGUUEQCAAKAIUQQJGBEAgACgCEEEBRgRAIAAoAhgoAiwgARAODAMLIARFDQIgACgCGCIAKAIsIAAoAmAgARAIDAILIAAoAhgiACgCLCAAKAJgIAAoApQBIAEQBwwBCwJAAkACQCAhQQFrDgQAAwECAwsgACgCGCgCLCABEAYMAgsgACgCGCIAKAIsIAAoAmAgACgClAEgARAFDAELIAAoAhgiACgCLCAAKAJgIAAoApQBIAAoAsgBIAEQBAsgGCgCIBAhQQAhDAsgGEHgwABqJAAgDAsIAEEIIAAQJQurAgICfgJ/Qn8hAyAALQBEQQhxRQRAIAAgACgCICIGNgIkAkACQAJAIAAgACgCMCIFBH8DQCAGIAUgACgCACAAKAIUEQAAIgVBf0YNAiAAIAAoAiQgBWoiBjYCJCAAIAAoAjAgBWsiBTYCMCAFDQALIAAoAiAFIAYLNgIkIAFCAFUNAUIAIQMMAgsgACAAKAJEQQhyNgJEIAJBBEGB9QBBABAPIABBADYCMCAAIAAoAkRBCHI2AkRCfw8LQgAhAwNAIAEgACgCACAAKAIYEQsAIgRCf1EEQCACQQRB8vQAQQAQDyAAIAAoAkRBCHI2AkQgACAAKQM4IAN8NwM4Qn8gAyADUBsPCyADIAR8IQMgASAEfSIBQgBVDQALCyAAIAApAzggA3w3AzgLIAMLIwEBfyABIAEoAgAgASgCCCIBIACnIgIgASACSRtqNgIEQQELPAICfwF+IAEoAgAgASgCCGoiAyABKAIEIgJGBEBCfw8LIAEgAiAAp2o2AgQgAyACa6wiBCAAIAAgBFUbC5gDAgJ+An8gACgCMCIFIAGnIgZPBEAgACAFIAZrNgIwIAAgACgCJCAGajYCJCAAIAApAzggAXw3AzggAQ8LIAAtAERBBHEEQCAAQQA2AjAgACAAKAIkIAVqNgIkIAAgBa0iASAAKQM4fDcDOCABQn8gBRsPCwJAIAVFBEAMAQsgAEEANgIwIAAgACgCIDYCJCABIAWtIgN9IQELIAFCAFUEQANAIAApAwggACkDOCABIAN8fFQEQCACQQRBm/UAQQAQDyAAQQA2AjAgACAAKAIgNgIkIAAgACkDOCADfCIDNwM4IAApAwgiASADfSEEIAEgACgCACAAKAIcEQoAIQUgACgCRCECIAAgBQR/IAAgATcDOCACQXtxBSACC0EEcjYCREJ/IAQgASADURsPCyABIAAoAgAgACgCGBELACIEQn9RBEAgAkEEQZv1AEEAEA8gACAAKAJEQQRyNgJEIAAgACkDOCADfDcDOEJ/IAMgA1AbDwsgAyAEfCEDIAEgBH0iAUIAVQ0ACwsgACAAKQM4IAN8NwM4IAMLmwEBBX9BASACKAIIIgcgB0EBTRshBCACKAIEIgMgAigCAGshBgNAIAQiBUEBdCEEIAUgBmsgAUkNAAsgBSAHRwRAIAUQFCIDRQRAQX8PCyACKAIAIgQEQCADIAQgBhASGiACKAIAEBALIAIgBTYCCCACIAM2AgAgAiADIAZqIgM2AgQLIAMgACABEBIaIAIgAigCBCABajYCBCABC0YBAn8gAigCACACKAIIaiIEIAIoAgQiA0YEQEF/DwsgACADIAQgA2siACABIAAgAUkbIgAQEhogAiACKAIEIABqNgIEIAALqgIBBH8jAEEQayIEJAACQCAAKAJ0DQAgAkEBTQRAIANBAUH7wgBBABAPDAELIAEgBEEMakECEBEgBCgCDCIGQf//A3EiB0UEQCADQQFBnMMAQQAQDwwBCyACIAdBBmxBAmpJBEAgA0EBQfvCAEEAEA8MAQsgBkEGbBAUIgNFDQAgAEEIEBQiAjYCdCACRQRAIAMQEAwBCyACIAM2AgAgAiAELwEMIgI7AQQgAkUEQEEBIQUMAQtBACECA0AgAUECaiAEQQxqIgVBAhARIAMgAkEGbGoiBiAEKAIMOwEAIAFBBGogBUECEBEgBiAEKAIMOwECIAFBBmoiASAFQQIQESAGIAQoAgw7AQRBASEFIAJBAWoiAiAAKAJ0LwEESQ0ACwsgBEEQaiQAIAUL8AEBBX8jAEEQayIEJAACfyAAKAJ4IgVFBEAgA0EBQc3CAEEAEA9BAAwBCyAFKAIMBEAgA0EBQdvVAEEAEA9BAAwBCyACIAUtABIiBUECdCIGSQRAIANBAUGswgBBABAPQQAMAQtBACAGEBQiAkUNABogBQRAQQAhAwNAIAEgBEEMaiIHQQIQESACIANBAnRqIgYgBCgCDDsBACABQQJqIAdBARARIAYgBCgCDDoAAiABQQNqIAdBARARIAYgBCgCDDoAAyABQQRqIQEgA0EBaiIDIAVHDQALCyAAKAJ4IAI2AgxBAQshCCAEQRBqJAAgCAvwAwEJfyMAQRBrIgUkAAJAIAJBA0kNACAAKAJ4DQAgASAFQQxqQQIQESAFLwEMIglBgQhrQf93TQRAIAUgCTYCACADQQFBtBogBRAPDAELIAFBAmogBUEMakEBEBEgBS8BDCIIRQRAIANBAUHUF0EAEA8MAQsgCEEDaiACSw0AIAggCWxBAnQQFCIHRQ0AIAgQFCIKRQRAIAcQEAwBCyAIEBQiC0UEQCAHEBAgChAQDAELQRQQFCIGRQRAIAcQECAKEBAgCxAQDAELIAFBA2ohAyAGIAo2AgggBiALNgIEIAYgCTsBECAGIAc2AgAgBSgCDCEMIAZBADYCDCAGIAw6ABIgACAGNgJ4A0AgAyAFQQxqQQEQESAEIApqIAUtAAxB/wBxQQFqOgAAIAQgC2ogBSgCDEGAAXFBB3Y6AAAgA0EBaiEDIARBAWoiBCAIRw0ACyAJRQRAQQEhBAwBC0EAIQYDQEEAIQRBACEAA0AgAkEEIAQgCmotAABBB2pBA3YiBCAEQQRPGyIEIAMgAWtqSARAQQAhBAwDCyADIAVBDGogBBARIAcgBSgCDDYCACAHQQRqIQcgAyAEaiEDIABBAWoiAEH//wNxIgQgCEkNAAtBASEEIAZBAWoiBkH//wNxIAlJDQALCyAFQRBqJAAgBAuYAQECfyMAQRBrIgUkACAAKAIYIgRB/wFHBEAgBSAENgIAIANBAkHkEyAFEA8LAkACQCACIAAoAhRGBEAgAg0BQQEhBAwCC0EAIQQgA0EBQbvsAEEAEA8MAQtBACECA0BBASEEIAEgACgCSCACQQxsakEIakEBEBEgAUEBaiEBIAJBAWoiAiAAKAIUSQ0ACwsgBUEQaiQAIAQLjgYBBn8jAEHQAGsiBCQAAkAgAkECTQRAIANBAUGb7ABBABAPDAELIAAtAHwEQCADQQRB7tIAQQAQD0EBIQYMAQtBASEGIAEgAEEoakEBEBEgAUEBaiAAQTRqQQEQESABQQJqIABBLGpBARARIAFBA2ohBQJAAkACQAJAAkAgACgCKCIHQQFrDgIAAQILIAJBBk0EQCAEIAI2AhAgA0EBQcDxACAEQRBqEA9BACEGDAULAkAgAkEHRg0AIAAoAjBBDkYNACAEIAI2AjAgA0ECQcDxACAEQTBqEA8LIAUgAEEwakEEEBEgACgCMEEORw0DQSQQFCIFRQRAQQAhBiADQQFBszxBABAPDAULIAVBDjYCACAEQQA2AkAgBEEANgI4IARBADYCSCAEQQA2AjwgBEEANgJEIARBADYCTEGw6pACIQYgBEGw6pACNgI0IAVBgIyVogQ2AgQCfyACQQdHBEAgAkEjRgRAIAFBB2ogBEHMAGpBBBARIAFBC2ogBEHIAGpBBBARIAFBD2ogBEHEAGpBBBARIAFBE2ogBEFAa0EEEBEgAUEXaiAEQTxqQQQQESABQRtqIARBOGpBBBARIAFBH2ogBEE0akEEEBEgBUEANgIEIAQoAjQhBiAEKAI4IQIgBCgCQCEDIAQoAjwhByAEKAJEIQggBCgCTCEJIAQoAkgMAgsgBCACNgIgIANBAkHk8QAgBEEgahAPC0EAIQJBACEDQQAhB0EACyEBIAUgBzYCGCAFIAg2AhAgBSAJNgIIIAUgBjYCICAFIAI2AhwgBSADNgIUIAUgATYCDCAAQQA2AnAgACAFNgJsDAMLIAAgAkEDayIBNgJwIABBASABEBMiAzYCbCADRQ0BIAJBA0wNAkEAIQIDQCAFIARBzABqQQEQESAAKAJsIAJqIAQoAkw6AAAgBUEBaiEFIAJBAWoiAiABRw0ACwwCCyAHQQNJDQIgBCAHNgIAIANBBEHb9wAgBBAPDAILQQAhBiAAQQA2AnAMAQtBASEGIABBAToAfAsgBEHQAGokACAGC7QDAQN/IwBBIGsiBCQAAkAgACgCSARAIANBAkGNNUEAEA9BASECDAELIAJBDkcEQEEAIQIgA0EBQfrrAEEAEA8MAQsgASAAQRBqQQQQESABQQRqIABBDGpBBBARIAFBCGogAEEUakECEBEgACgCDCEFAkAgBAJ/IAAoAhAiBkUEQCAAKAIUDAELIAAoAhQiAiAFRQ0AGiACDQFBAAs2AgggBCAGNgIEIAQgBTYCACADQQFB3uoAIAQQD0EAIQIMAQsgAkGBgAFrQf//fk0EQEEAIQIgA0EBQYjqAEEAEA8MAQsgACACQQwQEyICNgJIIAJFBEBBACECIANBAUGt6gBBABAPDAELQQEhAiABQQpqIABBGGpBARARIAFBC2ogAEEcakEBEBEgACgCHCIFQQdHBEAgBCAFNgIQIANBBEGd+gAgBEEQahAPCyABQQxqIABBIGpBARARIAFBDWogAEEkakEBEBEgACgCACIBIAEtALwBQfsBcSAAKAIYQf8BRkECdHI6ALwBIAAoAgAiASAAKAIMNgLYASABIAAoAhA2AtwBIABBAToAhQELIARBIGokACACC7oEAQZ/IwBBEGsiBiQAAn8gAC0AZEECcUUEQCADQQFBkdQAQQAQD0EADAELIABBADYCaAJAAkACQCACBEADQCACQQdNBEAgA0EBQbkZQQAQDwwFCyABIAZBDGoiBUEEEBEgBigCDCEEIAFBBGogBUEEEBFBCCEHIAYoAgwhBQJAAkACQAJAIAQOAgEAAwsgAkEQSQRAQeEZIQQMBwsgAUEIaiAGQQhqQQQQESAGKAIIBEBByj8hBAwHCyABQQxqIAZBDGpBBBARIAYoAgwiBA0BQbIYIQQMBgsgA0EBQbIYQQAQDwwGC0EQIQcLIAQgB0kEQCADQQFBhcUAQQAQDwwFCyACIARJBEAgA0EBQb3EAEEAEA9BAAwGCwJAAkAgACABIAdqIAQgB2sgAwJ/AkACQAJAIAVB8di9mwZMBEAgBUHjxsGTBkYNASAFQebKkZsGRg0DIAVB8MK1mwZHDQVB4MABDAQLIAVB8tiNgwdGDQFBwMABIAVB8sihywZGDQMaIAVB8ti9mwZHDQRByMABDAMLQdDAAQwCC0HYwAEMAQtB6MABCygCBBEBAA0BQQAMBwsgACAAKAJoQf////8HcjYCaAtBASAIIAVB8sihywZGGyEIIAEgBGohASACIARrIgINAAsgCA0BCyADQQFB2cMAQQAQD0EADAMLIABBAToAhAEgACAAKAJkQQRyNgJkQQEMAgsgA0EBIARBABAPCyADQQFBng5BABAPQQALIQkgBkEQaiQAIAkL4gEBAX8gACgCZEEBRwRAIANBAUG+1ABBABAPQQAPCwJAIAJBB00EQAwBCyABIABBOGpBBBARIAFBBGogAEE8akEEEBEgAkEDcQRADAELIAAgAkEIayICQQJ2IgQ2AkACQCACRQ0AIAAgBEEEEBMiAjYCRCACRQRAIANBAUGpEEEAEA9BAA8LIAAoAkBFDQAgAUEIaiEDQQAhAgNAIAMgACgCRCACQQJ0akEEEBEgA0EEaiEDIAJBAWoiAiAAKAJASQ0ACwsgACAAKAJkQQJyNgJkQQEPCyADQQFBqi1BABAPQQALxAEBAn8gACAAKAIgIgQ2AiQCQCAAKAIwIgMEQANAIAQgAyAAKAIAIAAoAhQRAAAiA0F/Rg0CIAAgACgCJCADaiIENgIkIAAgACgCMCADayIDNgIwIAMNAAsgACgCICEECyAAQQA2AjAgACAENgIkIAEgACgCACAAKAIcEQoARQRAIAAgACgCREEIcjYCREEADwsgACABNwM4QQEPCyAAIAAoAkRBCHI2AkQgAkEEQYH1AEEAEA8gACAAKAJEQQhyNgJEQQALggEBAn8jAEEQayIEJAACfyAAKAJkBEAgA0EBQdvTAEEAEA9BAAwBCyACQQRHBEAgA0EBQc4tQQAQD0EADAELIAEgBEEMakEEEBEgBCgCDEGKjqroAEcEQCADQQFB9iVBABAPQQAMAQsgACAAKAJkQQFyNgJkQQELIQUgBEEQaiQAIAULDQAgACgCACABIAIQRQsJACAAKAIAEEoLCQAgACgCABBJCw0AIAAoAgAgASACEEwLQQEBfyACBH8gA0ECQdvLAEEAEA8gACgCACABIAIgAyAEEEZFBEAgA0EBQakvQQAQD0EADwsgACACIAMQcQVBAAsLFQAgACgCACABIAIgAyAEIAUgBhBOCw8AIAAoAgAgASACIAMQTwsTACAAKAIAIAEgAiADIAQgBRArCx0AIAAoAgAgASACIAMgBCAFIAYgByAIIAkgChAnC+oEAQd/AkAgASgCCEE1IAMQJEUNACABKAIEIgcoAgAhBSAHKAIIIQQCQCAFBEBBASEGIAVBAUcEQCAFQX5xIQoDQAJ/QQAgBkUNABpBACABIAAgAyAEKAIAEQAARQ0AGiABIAAgAyAEKAIEEQAAQQBHCyEGIARBCGohBCAJQQJqIgkgCkcNAAsLAkAgBUEBcQRAIAZFDQEgASAAIAMgBCgCABEAAEEARyEGCyAHQQA2AgAgBkUNAwwCCyAHQQA2AgBBAA8LIAdBADYCAAsgASgCCCIHKAIAIQUgBygCCCEEAkACQAJ/AkAgBQRAQQEhBiAFQQFxIQggBUEBRw0BQQAMAgsgB0EANgIADAILIAVBfnEhBUEAIQkDQAJ/QQAgBkUNABpBACABIAAgAyAEKAIAEQAARQ0AGiABIAAgAyAEKAIEEQAAQQBHCyEGIARBCGohBCAJQQJqIgkgBUcNAAsgBkULIQUgCARAIAUNAiABIAAgAyAEKAIAEQAAQQBHIQYLIAdBADYCAEEAIQggBkUNAgsgAS0AhAFFBEAgA0EBQb3WAEEAEA9BAA8LIAEtAIUBRQRAIANBAUGg1gBBABAPQQAPCyAAIAEoAgAgAiADEFAhCCACRQ0BIAIoAgAiAEUNAUEBIQQCQAJAAkACQAJAAkAgASgCMEEMaw4NAwQEBAUAAQQEBAQEAgQLQQIhBAwEC0EDIQQMAwtBBCEEDAILQQUhBAwBC0F/IQQLIAAgBDYCFCABKAJsIgNFDQEgACADNgIcIAIoAgAgASgCcDYCICABQQA2AmwgCA8LIAdBADYCAEEAIQgLIAgL5AkCCn8BfiMAQfAAayIDJABBgAghCAJ/AkBBAUGACBATIgYEQCADQdwAaiELIANB7ABqIQkDQAJAAkACQCABIANB6ABqIgRBCCACEBpBCEcNACAEIANB2ABqQQQQESAJIAtBBBARQQghBQJAAkACQAJAAkAgAygCWA4CAAEECyABKQMIIg1QBH5CAAUgDSABKQM4fQsiDUL4////D1MNASACQQFByj9BABAPDAQLIAEgA0HoAGoiBEEIIAIQGkEIRw0DIAQgA0HkAGpBBBARIAMoAmRFDQEgAkEBQco/QQAQDwwDCyADIA2nQQhqNgJYDAELIAkgA0HYAGpBBBARQRAhBQsgAygCXCIEQePkwNMGRgRAIAAoAmQiAUEEcQRAIAAgAUEIcjYCZAwCCyACQQFBrStBABAPIAYQEEEADAcLIAMoAlgiB0UEQCACQQFBshhBABAPIAYQEEEADAcLIAUgB0sEQCADIAQ2AgQgAyAHNgIAIAJBAUH65wAgAxAPDAYLAkACfwJ/AkACfwJAAkACQAJAAkAgBEHx2L2bBkwEQCAEQePGwZMGRg0CIARB5sqRmwZGDQQgBEHwwrWbBkcNAUHgwAEMBgsgBEGfwMDSBkwEQCAEQfLYvZsGRg0FQcDAASAEQfLIocsGRg0GGiAEQfDy0bMGRw0BQajAAQwICyAEQfLYjYMHRg0CIARBoMDA0gZGDQZBsMABIARB6OTA0wZGDQcaCyAAKAJkIgRBAXENCCACQQFB/A5BABAPIAYQEEEADA8LQdDAAQwDC0HYwAEMAgtB6MABDAELQcjAAQshCiADIARB/wFxNgJMIAMgBEEYdjYCQCADIARBCHZB/wFxNgJIIAMgBEEQdkH/AXE2AkQgAkECQckOIANBQGsQDyAHIAVrIgUgAC0AZEEEcQ0CGiADIAMoAlwiBEEYdjYCMCADIARB/wFxNgI8IAMgBEEQdkH/AXE2AjQgAyAEQQh2Qf8BcTYCOCACQQJB2jMgA0EwahAPIAAgACgCZEH/////B3I2AmQgASAFrSINIAIgASgCKBEIACANUQ0HIAJBAUGSHEEAEA8gBhAQQQAMCgtBoMABCyEKIAcgBWsLIQUgASkDCCINUAR+QgAFIA0gASkDOH0LIAWtUwRAIAMoAlghBCADKAJcIQAgAyABKQMIIg1QBH5CAAUgDSABKQM4fQs+AiggAyAFNgIkIAMgAEH/AXE2AiAgAyAAQRh2NgIUIAMgBDYCECADIABBCHZB/wFxNgIcIAMgAEEQdkH/AXE2AhggAkEBQc31ACADQRBqEA8MBwsgBSAITQRAIAYhBAwECyAFIQggBiAFEBciBA0DIAYQECACQQFB/w9BABAPQQAMBwsgBEECcUUEQCACQQFBwg9BABAPIAYQEEEADAcLIAAgBEH/////B3I2AmQgASAHIAVrrSINIAIgASgCKBEIACANUQ0DIAAtAGRBCHFFDQEgAkECQZIcQQAQDwsgBhAQQQEMBQsgAkEBQZIcQQAQDyAGEBBBAAwECyABIAQgBSACEBogBUcEQCACQQFBxBxBABAPIAQQEEEADAQLIAAgBCIGIAUgAiAKKAIEEQEADQALIAQQEEEADAILIAJBAUGiJUEAEA9BAAwBCyAGEBBBAAshDCADQfAAaiQAIAwL5gEBBn8gACgCCEE1IAIQJARAAkAgACgCCCIGKAIAIQMgBigCCCEFAkACQAJ/AkAgAwRAQQEhBCADQQFxIQcgA0EBRw0BQQAMAgsgBkEANgIADAILIANBfnEhAwNAAn9BACAERQ0AGkEAIAAgASACIAUoAgARAABFDQAaIAAgASACIAUoAgQRAABBAEcLIQQgBUEIaiEFIAhBAmoiCCADRw0ACyAERQshAyAHBEAgAw0CIAAgASACIAUoAgARAABBAEchBAsgBkEANgIAIARFDQILIAAoAgAaQQEPCyAGQQA2AgALC0EACwoAIAAoAgAaQQALFAAgACgCACIABEAgACABNgK4AQsLIQAgACgCACABEFMgAEEAOgB8IAAgASgCuEBBAXE2AoABCzIAIAJFBEBBAA8LIAAoAgAgASACIAMQSEUEQCADQQFBqS9BABAPQQAPCyAAIAIgAxBxC2kCAn8BfCMAQRBrIgMkACACBEADQCAAIANBCGoQRCABAn8gAysDCCIFmUQAAAAAAADgQWMEQCAFqgwBC0GAgICAeAs2AgAgAUEEaiEBIABBCGohACAEQQFqIgQgAkcNAAsLIANBEGokAAuEAQICfwF9IwBBEGsiAyQAIAIEQANAIAMgAC0AADoADyADIAAtAAE6AA4gAyAALQACOgANIAMgAC0AAzoADCABAn8gAyoCDCIFi0MAAABPXQRAIAWoDAELQYCAgIB4CzYCACABQQRqIQEgAEEEaiEAIARBAWoiBCACRw0ACwsgA0EQaiQAC0sBAn8jAEEQayIDJAAgAgRAA0AgACADQQxqQQQQESABIAMoAgw2AgAgAUEEaiEBIABBBGohACAEQQFqIgQgAkcNAAsLIANBEGokAAtLAQJ/IwBBEGsiAyQAIAIEQANAIAAgA0EMakECEBEgASADKAIMNgIAIAFBBGohASAAQQJqIQAgBEEBaiIEIAJHDQALCyADQRBqJAALSgECfyMAQRBrIgMkACACBEADQCAAIANBCGoQRCABIAMrAwi2OAIAIAFBBGohASAAQQhqIQAgBEEBaiIEIAJHDQALCyADQRBqJAALaAECfyMAQRBrIgMkACACBEADQCADIAAtAAA6AA8gAyAALQABOgAOIAMgAC0AAjoADSADIAAtAAM6AAwgASADKgIMOAIAIAFBBGohASAAQQRqIQAgBEEBaiIEIAJHDQALCyADQRBqJAALTAECfyMAQRBrIgMkACACBEADQCAAIANBDGpBBBARIAEgAygCDLM4AgAgAUEEaiEBIABBBGohACAEQQFqIgQgAkcNAAsLIANBEGokAAtMAQJ/IwBBEGsiAyQAIAIEQANAIAAgA0EMakECEBEgASADKAIMszgCACABQQRqIQEgAEECaiEAIARBAWoiBCACRw0ACwsgA0EQaiQAC6oIAg1/AXsjAEEQayIIJAACfyAAKAIIQRBGBEAgACgCnAEgACgCzAFBjCxsagwBCyAAKAIMCyEJAkAgAkUEQCADQQFB8B9BABAPDAELIAAoAkghBkEBIQQgASAIQQhqQQEQESAIKAIIIgVBAk8EQCADQQJBxsgAQQAQDwwBCyACIAVBAWpHBEBBACEEIANBAkHwH0EAEA8MAQsCQCAGKAIQIgNFDQAgCSgC0CshBCADQQhPBEAgA0F4cSEGQQAhAgNAIARBADYCvEMgBEEANgKEOyAEQQA2AswyIARBADYClCogBEEANgLcISAEQQA2AqQZIARBADYC7BAgBEEANgK0CCAEQcDDAGohBCACQQhqIgIgBkcNAAsLIANBB3EiA0UNAEEAIQIDQCAEQQA2ArQIIARBuAhqIQQgAkEBaiICIANHDQALCyAJKALoKyICBH8gAhAQIAlBADYC6CsgCCgCCAUgBQtFBEBBASEEDAELA0AgAUEBaiIBIAhBDGpBARARAkAgCSgCgCxFDQAgCSgC/CsiAygCACAIKAIMRw0AIAMoAgQiBSAAKAJIIgYoAhBHDQAgAygCCCICBEBBACEEIAIoAhAgBSAFbCIFIAIoAgBBAnRB0L0BaigCAGxHDQMgCSAFQQJ0EBQiBzYC6CsgB0UNAyACKAIMIAcgBSACKAIAQQJ0QYDAAWooAgARBQALIAMoAgwiAkUNAEEAIQQgAigCECAGKAIQIgMgAigCAEECdEHQvQFqKAIAbEcNAiADQQJ0EBQiBUUNAiACKAIMIAUgAyACKAIAQQJ0QZDAAWooAgARBQACQCAGKAIQIgdFDQAgCSgC0CshBEEAIQsCQAJAIAdBBEkNACAEQbQIaiIMIAUgB0ECdGpJBEAgBSAEIAdBuAhsakkNAQsgBEHcIWohDSAEQaQZaiEOIARB7BBqIQ8gBSAHQXxxIgZBAnRqIQIgBCAGQbgIbGohBEEAIQMDQCAMIANBuAhsIgpqIAUgA0ECdGr9AAIAIhH9WgIAACAKIA9qIBH9WgIAASAKIA5qIBH9WgIAAiAKIA1qIBH9WgIAAyADQQRqIgMgBkcNAAsgBiAHRg0CDAELIAUhAkEAIQYLIAcgBiIDa0EHcSIKBEADQCAEIAIoAgA2ArQIIANBAWohAyAEQbgIaiEEIAJBBGohAiALQQFqIgsgCkcNAAsLIAYgB2tBeEsNAANAIAQgAigCADYCtAggBCACKAIENgLsECAEIAIoAgg2AqQZIAQgAigCDDYC3CEgBCACKAIQNgKUKiAEIAIoAhQ2AswyIAQgAigCGDYChDsgBCACKAIcNgK8QyAEQcDDAGohBCACQSBqIQIgA0EIaiIDIAdHDQALCyAFEBALQQEhBCAQQQFqIhAgCCgCCEkNAAsLIAhBEGokACAECwQAQn8LvwkBC38jAEEQayIFJAACfyAAKAIIQRBGBEAgACgCnAEgACgCzAFBjCxsagwBCyAAKAIMCyEHAn8gAkEBTQRAIANBAUHYI0EAEA9BAAwBCyABIAVBDGpBAhARIAUoAgwEQCADQQJB8CxBABAPQQEMAQsgAkEGTQRAIANBAUHYI0EAEA9BAAwBCyABQQJqIAVBCGpBARARIAcoAvwrIgkhAAJAAkACQCAHKAKALCIGRQ0AIAUoAgghCANAIAAoAgAgCEYNASAAQRRqIQAgBEEBaiIEIAZHDQALDAELIAQgBkcNAQsgBygChCwgBkYEfyAHIAZBCmoiADYChCwgCSAAQRRsEBciAEUEQCAHKAL8KxAQIAdBADYChCwgB0IANwL8KyADQQFB8iNBABAPQQAMAwsgByAANgL8KyAAIAcoAoAsIgRBFGxqQQAgBygChCwgBGtBFGwQFRogBygC/CshCSAHKAKALAUgBgtBFGwgCWohAEEBIQsLIAAgBSgCCDYCACABQQNqIAVBDGpBAhARIAUoAgwEQCADQQJB8CxBABAPQQEMAQsgAUEFaiAFQQRqQQIQESAFKAIEIgRBAk8EQCADQQJBqBdBABAPQQEMAQsgAkEHayEGIAQEQCABQQdqIQJBACEJA0AgBkECTQRAIANBAUHYI0EAEA9BAAwDCyACIAVBDGpBARARIAUoAgxBAUcEQCADQQJBsipBABAPQQEMAwsgAkEBaiAFQQIQESAAIAUoAgAiBEH//wFxIgE2AgQgBkEDayIIIARBD3ZBAWoiBiABbEECaiIKSQRAIANBAUHYI0EAEA9BAAwDCyACQQNqIQJBACEEIAEEQANAIAIgBUEMaiAGEBEgBCAFKAIMRwRAIANBAkHaL0EAEA9BAQwFCyACIAZqIQIgBEEBaiIEIAAoAgRJDQALCyACIAVBAhARIAUgBSgCACIEQf//AXEiATYCACAAKAIEIAFHBEAgA0ECQdgYQQAQD0EBDAMLIAggCmsiCiAEQQ92QQFqIgYgAWxBA2oiDEkEQCADQQFB2CNBABAPQQAMAwsgAkECaiECQQAhBCABBEADQCACIAVBDGogBhARIAQgBSgCDEcEQCADQQJB2i9BABAPQQEMBQsgAiAGaiECIARBAWoiBCAAKAIESQ0ACwsgAiAFQQxqQQMQESAFKAIMIQYgAEIANwIIIAAgBkGAgARxRSAALQAQQf4BcXI6ABAgBSAGQf8BcSIINgIIAkAgCEUNACAHKAL0KyINBEAgBygC8CshBEEAIQEDQCAIIAQoAghGBEAgACAENgIIDAMLIARBFGohBCABQQFqIgEgDUcNAAsLIANBAUHYI0EAEA9BAAwDCyAFIAZBCHZB/wFxIgY2AggCQCAGRQ0AIAcoAvQrIggEQCAHKALwKyEEQQAhAQNAIAYgBCgCCEYEQCAAIAQ2AgwMAwsgBEEUaiEEIAFBAWoiASAIRw0ACwsgA0EBQdgjQQAQD0EADAMLIAogDGshBiACQQNqIQIgCUEBaiIJIAUoAgRJDQALCyAGBEAgA0EBQdgjQQAQD0EADAELQQEgC0UNABogByAHKAKALEEBajYCgCxBAQshDiAFQRBqJAAgDgv1AQEFfyMAQRBrIgQkAAJAIAIgACgCSCgCECIGQQJqRwRAIANBAUHwIkEAEA8MAQsgASAEQQxqQQIQESAGIAQoAgxHBEAgA0EBQfAiQQAQDwwBCyAGRQRAQQEhBQwBCyABQQJqIQIgACgCSCgCGCEAQQAhAQNAIAIgBEEIakEBEBEgACAEKAIIIgVB/wBxIgdBAWoiCDYCGCAAIAVBB3ZBAXE2AiAgB0EfTwRAIAQgCDYCBCAEIAE2AgAgA0EBQbfzACAEEA9BACEFDAILIABBNGohAEEBIQUgAkEBaiECIAFBAWoiASAGRw0ACwsgBEEQaiQAIAULmAUBCn8jAEEQayIHJAACfyAAKAIIQRBGBEAgACgCnAEgACgCzAFBjCxsagwBCyAAKAIMCyEFAn8gAkEBTQRAIANBAUHxHkEAEA9BAAwBCyABIAdBDGpBAhARAkAgBygCDARAIANBAkGGG0EAEA8MAQsgAkEGTQRAIANBAUHxHkEAEA9BAAwCCyABQQJqIAdBDGpBAhARIAUoAvArIQQgBy0ADCEKAkACQAJAIAUoAvQrIgZFBEAgBCEADAELIAQhAANAIAAoAgggCkYNASAAQRRqIQAgCEEBaiIIIAZHDQALDAELIAYgCEcNAQsgBSgC+CsgBkYEQCAFIAZBCmoiADYC+CsgBCAAQRRsEBchACAFKALwKyEEIABFBEAgBBAQIAVBADYC+CsgBUIANwLwKyADQQFBix9BABAPQQAMBAsCQCAAIARGDQAgBSgCgCwiC0UNACAFKAL8KyEMQQAhCANAIAwgCEEUbGoiBigCCCIJBEAgBiAAIAkgBGtqNgIICyAGKAIMIgkEQCAGIAAgCSAEa2o2AgwLIAhBAWoiCCALRw0ACwsgBSAANgLwKyAAIAUoAvQrIgRBFGxqQQAgBSgC+CsgBGtBFGwQFRogBSgC9CshBiAFKALwKyEECyAFIAZBAWo2AvQrIAQgBkEUbGohAAsgACgCDCIEBEAgBBAQIABCADcCDAsgACAKNgIIIAAgBygCDCIEQQp2QQNxNgIAIAAgBEEIdkEDcTYCBCABQQRqIAdBDGpBAhARIAcoAgwEQCADQQJBvRZBABAPDAELIAAgAkEGayICEBQiBDYCDCAERQRAIANBAUHxHkEAEA9BAAwCCyAEIAFBBmogAhASGiAAIAI2AhALQQELIQ0gB0EQaiQAIA0LJwBBASEBIAIgACgCSCgCEEECdEcEfyADQQFB1yFBABAPQQAFQQELC6sDAQV/IwBBEGsiBiQAAn8gAkEBTQRAIANBAUH9HUEAEA9BAAwBCyAALQC8AUEBcQRAIANBAUGJ3gBBABAPQQAMAQsgACgCnAEgACgCzAFBjCxsaiIAIAAtAIgsQQJyOgCILCABIAZBDGpBARARAkAgACgCrCgiBEUEQCAAIAYoAgxBAWoiBUEIEBMiBDYCrCggBEUEQCADQQFBlx5BABAPQQAMAwsgACAFNgKoKAwBCyAGKAIMIgUgACgCqChJDQAgBCAFQQFqIgRBA3QQFyIFRQRAIANBAUGXHkEAEA9BAAwCCyAAIAU2AqwoIAUgACgCqCgiB0EDdGpBACAEIAdrQQN0EBUaIAAgBDYCqCggACgCrCghBAsgBCAGKAIMIgVBA3RqKAIABEAgBiAFNgIAIANBAUG9NSAGEA9BAAwBCyACQQFrIgIQFCEEIAAoAqwoIgAgBigCDCIFQQN0aiAENgIAIARFBEAgA0EBQZceQQAQD0EADAELIAAgBUEDdGogAjYCBCAAIAYoAgxBA3RqKAIAIAFBAWogAhASGkEBCyEIIAZBEGokACAIC/UCAQV/IwBBEGsiBiQAAn8gAkEBTQRAIANBAUGkIEEAEA9BAAwBCyAAIAAtALwBQQFyOgC8ASABIAZBDGpBARARAkAgACgCdCIERQRAIAAgBigCDEEBaiIFQQgQEyIENgJ0IARFBEAgA0EBQb4gQQAQD0EADAMLIAAgBTYCcAwBCyAGKAIMIgUgACgCcEkNACAEIAVBAWoiBEEDdBAXIgVFBEAgA0EBQb4gQQAQD0EADAILIAAgBTYCdCAFIAAoAnAiB0EDdGpBACAEIAdrQQN0EBUaIAAgBDYCcCAAKAJ0IQQLIAQgBigCDCIFQQN0aigCAARAIAYgBTYCACADQQFB0zUgBhAPQQAMAQsgAkEBayICEBQhBCAAKAJ0IgAgBigCDCIFQQN0aiAENgIAIARFBEAgA0EBQb4gQQAQD0EADAELIAAgBUEDdGogAjYCBCAAIAYoAgxBA3RqKAIAIAFBAWogAhASGkEBCyEIIAZBEGokACAIC6ABAQR/IwBBEGsiBCQAAn8gAkUEQCADQQFB1x5BABAPQQAMAQsgASAEQQxqQQEQEUEBIAJBAWsiBUUNABpBACEAQQAhAgNAIAFBAWoiASAEQQhqQQEQESAEKAIIIgZBGHRBH3UgBkH/AHEgAnJBB3RxIQIgAEEBaiIAIAVHDQALQQEgAkUNABogA0EBQdceQQAQD0EACyEHIARBEGokACAHCxsAQQEhACACBH9BAQUgA0EBQf4gQQAQD0EACwuAAQEBfyMAQRBrIgAkAEEBIQQCQCACQQFNBEBBACEEIANBAUHkIEEAEA8MAQsgASAAQQxqQQEQESABQQFqIABBCGpBARARIAJBAmsgACgCCCIBQQV2QQJxIAFBBHZBA3FqQQJqcEUNAEEAIQQgA0EBQeQgQQAQDwsgAEEQaiQAIAQLBABBAAsLorwBIQBBgAgLkXVjYW5ub3QgYWxsb2NhdGUgb3BqX3RjZF9zZWdfZGF0YV9jaHVua190KiBhcnJheQAtKyAgIDBYMHgALTBYKzBYIDBYLTB4KzB4IDB4AFVua25vd24gZm9ybWF0AEZhaWxlZCB0byBzZXR1cCB0aGUgZGVjb2RlcgBGYWlsZWQgdG8gcmVhZCB0aGUgaGVhZGVyAG5hbgAqbF90aWxlX2xlbiA+IFVJTlRfTUFYIC0gT1BKX0NPTU1PTl9DQkxLX0RBVEFfRVhUUkEgLSBwX2oyay0+bV9zcGVjaWZpY19wYXJhbS5tX2RlY29kZXIubV9zb3RfbGVuZ3RoAGluZgBGYWlsZWQgdG8gZGVjb2RlIHRoZSBpbWFnZQBJbnZhbGlkIGFjY2VzcyB0byBwaS0+aW5jbHVkZQAvdG1wL29wZW5qcGVnL3NyYy9iaW4vY29tbW9uL2NvbG9yLmMAQUxMX0NQVVMAT1BKX05VTV9USFJFQURTAE5BTgBJTkYAcF9qMmstPm1fc3BlY2lmaWNfcGFyYW0ubV9kZWNvZGVyLm1fc290X2xlbmd0aCA+IFVJTlRfTUFYIC0gT1BKX0NPTU1PTl9DQkxLX0RBVEFfRVhUUkEACQkJIHByZWNjaW50c2l6ZSAodyxoKT0ACQkJIHN0ZXBzaXplcyAobSxlKT0ALgAobnVsbCkAKCVkLCVkKSAAJXN9CgAJCSB9CgBbREVWXSBEdW1wIGFuIGltYWdlX2NvbXBfaGVhZGVyIHN0cnVjdCB7CgBbREVWXSBEdW1wIGFuIGltYWdlX2hlYWRlciBzdHJ1Y3QgewoASW1hZ2UgaW5mbyB7CgAJIGRlZmF1bHQgdGlsZSB7CgAlcwkgY29tcG9uZW50ICVkIHsKAAkJIGNvbXAgJWQgewoACSBUaWxlIGluZGV4OiB7CgAJIE1hcmtlciBsaXN0OiB7CgBDb2Rlc3RyZWFtIGluZGV4IGZyb20gbWFpbiBoZWFkZXI6IHsKAENvZGVzdHJlYW0gaW5mbyBmcm9tIG1haW4gaGVhZGVyOiB7CgBTdHJlYW0gZXJyb3Igd2hpbGUgcmVhZGluZyBKUDIgSGVhZGVyIGJveAoARm91bmQgYSBtaXNwbGFjZWQgJyVjJWMlYyVjJyBib3ggb3V0c2lkZSBqcDJoIGJveAoATWFsZm9ybWVkIEpQMiBmaWxlIGZvcm1hdDogZmlyc3QgYm94IG11c3QgYmUgSlBFRyAyMDAwIHNpZ25hdHVyZSBib3gKAE1hbGZvcm1lZCBKUDIgZmlsZSBmb3JtYXQ6IHNlY29uZCBib3ggbXVzdCBiZSBmaWxlIHR5cGUgYm94CgBOb3QgZW5vdWdoIG1lbW9yeSB0byBoYW5kbGUganBlZzIwMDAgYm94CgBOb3QgZW5vdWdoIG1lbW9yeSB3aXRoIEZUWVAgQm94CgBBIG1hcmtlciBJRCB3YXMgZXhwZWN0ZWQgKDB4ZmYtLSkgaW5zdGVhZCBvZiAlLjh4CgAJCSBtY3Q9JXgKAAkJCSBjYmxrc3R5PSUjeAoACQkJIGNzdHk9JSN4CgAJCSBwcmc9JSN4CgBJbnRlZ2VyIG92ZXJmbG93CgAJIHRkeD0ldSwgdGR5PSV1CgAJIHR3PSV1LCB0aD0ldQoACSB0eDA9JXUsIHR5MD0ldQoASW52YWxpZCBjb21wb25lbnQgaW5kZXg6ICV1CgBTdHJlYW0gdG9vIHNob3J0CgBNYXJrZXIgaGFuZGxlciBmdW5jdGlvbiBmYWlsZWQgdG8gcmVhZCB0aGUgbWFya2VyIHNlZ21lbnQKAE5vdCBlbm91Z2ggbWVtb3J5IGZvciBjdXJyZW50IHByZWNpbmN0IGNvZGVibG9jayBlbGVtZW50CgBFcnJvciByZWFkaW5nIFNQQ29kIFNQQ29jIGVsZW1lbnQKAEVycm9yIHJlYWRpbmcgU1FjZCBvciBTUWNjIGVsZW1lbnQKAEEgQlBDQyBoZWFkZXIgYm94IGlzIGF2YWlsYWJsZSBhbHRob3VnaCBCUEMgZ2l2ZW4gYnkgdGhlIElIRFIgYm94ICglZCkgaW5kaWNhdGUgY29tcG9uZW50cyBiaXQgZGVwdGggaXMgY29uc3RhbnQKAEVycm9yIHdpdGggU0laIG1hcmtlcjogaWxsZWdhbCB0aWxlIG9mZnNldAoASW52YWxpZCBwcmVjaW5jdAoATm90IGVub3VnaCBtZW1vcnkgdG8gaGFuZGxlIGJhbmQgcHJlY2ludHMKAEZhaWxlZCB0byBkZWNvZGUgYWxsIHVzZWQgY29tcG9uZW50cwoAU2l6ZSBvZiBjb2RlIGJsb2NrIGRhdGEgZXhjZWVkcyBzeXN0ZW0gbGltaXRzCgBTaXplIG9mIHRpbGUgZGF0YSBleGNlZWRzIHN5c3RlbSBsaW1pdHMKAENhbm5vdCB0YWtlIGluIGNoYXJnZSBtdWx0aXBsZSBNQ1QgbWFya2VycwoAQ29ycnVwdGVkIFBQTSBtYXJrZXJzCgBOb3QgZW5vdWdoIG1lbW9yeSBmb3IgdGlsZSByZXNvbHV0aW9ucwoAQ2Fubm90IHRha2UgaW4gY2hhcmdlIG11bHRpcGxlIGNvbGxlY3Rpb25zCgBJbnZhbGlkIFBDTFIgYm94LiBSZXBvcnRzIDAgcGFsZXR0ZSBjb2x1bW5zCgBXZSBkbyBub3Qgc3VwcG9ydCBST0kgaW4gZGVjb2RpbmcgSFQgY29kZWJsb2NrcwoAQ2Fubm90IGhhbmRsZSBib3ggb2YgdW5kZWZpbmVkIHNpemVzCgBDYW5ub3QgdGFrZSBpbiBjaGFyZ2UgY29sbGVjdGlvbnMgd2l0aG91dCBzYW1lIG51bWJlciBvZiBpbmRpeGVzCgBJbnZhbGlkIHRpbGVjLT53aW5feHh4IHZhbHVlcwoAQ2Fubm90IGhhbmRsZSBib3ggb2YgbGVzcyB0aGFuIDggYnl0ZXMKAENhbm5vdCBoYW5kbGUgWEwgYm94IG9mIGxlc3MgdGhhbiAxNiBieXRlcwoAQ29tcG9uZW50IGluZGV4ICV1IHVzZWQgc2V2ZXJhbCB0aW1lcwoASW52YWxpZCBQQ0xSIGJveC4gUmVwb3J0cyAlZCBlbnRyaWVzCgBOb3QgZW5vdWdoIG1lbW9yeSB0byBjcmVhdGUgVGFnLXRyZWUgbm9kZXMKAENhbm5vdCB0YWtlIGluIGNoYXJnZSBtY3QgZGF0YSB3aXRoaW4gbXVsdGlwbGUgTUNUIHJlY29yZHMKAENhbm5vdCBkZWNvZGUgdGlsZSwgbWVtb3J5IGVycm9yCgBvcGpfajJrX2FwcGx5X25iX3RpbGVfcGFydHNfY29ycmVjdGlvbiBlcnJvcgoAUHJvYmxlbSB3aXRoIHNraXBwaW5nIEpQRUcyMDAwIGJveCwgc3RyZWFtIGVycm9yCgBQcm9ibGVtIHdpdGggcmVhZGluZyBKUEVHMjAwMCBib3gsIHN0cmVhbSBlcnJvcgoAVW5rbm93biBtYXJrZXIKAE5vdCBlbm91Z2ggbWVtb3J5IHRvIGFkZCB0bCBtYXJrZXIKAE5vdCBlbm91Z2ggbWVtb3J5IHRvIGFkZCBtaCBtYXJrZXIKAE5vdCBlbm91Z2ggbWVtb3J5IHRvIHRha2UgaW4gY2hhcmdlIFNJWiBtYXJrZXIKAEVycm9yIHJlYWRpbmcgUFBUIG1hcmtlcgoATm90IGVub3VnaCBtZW1vcnkgdG8gcmVhZCBQUFQgbWFya2VyCgBFcnJvciByZWFkaW5nIFNPVCBtYXJrZXIKAEVycm9yIHJlYWRpbmcgUExUIG1hcmtlcgoARXJyb3IgcmVhZGluZyBNQ1QgbWFya2VyCgBOb3QgZW5vdWdoIG1lbW9yeSB0byByZWFkIE1DVCBtYXJrZXIKAE5vdCBlbm91Z2ggc3BhY2UgZm9yIGV4cGVjdGVkIFNPUCBtYXJrZXIKAEV4cGVjdGVkIFNPUCBtYXJrZXIKAEVycm9yIHJlYWRpbmcgTUNPIG1hcmtlcgoARXJyb3IgcmVhZGluZyBSR04gbWFya2VyCgBFcnJvciByZWFkaW5nIFBQTSBtYXJrZXIKAE5vdCBlbm91Z2ggbWVtb3J5IHRvIHJlYWQgUFBNIG1hcmtlcgoARXJyb3IgcmVhZGluZyBUTE0gbWFya2VyCgBFcnJvciByZWFkaW5nIFBMTSBtYXJrZXIKAE5vdCBlbm91Z2ggc3BhY2UgZm9yIGV4cGVjdGVkIEVQSCBtYXJrZXIKAEV4cGVjdGVkIEVQSCBtYXJrZXIKAEVycm9yIHJlYWRpbmcgQ1JHIG1hcmtlcgoAVW5rbm93biBwcm9ncmVzc2lvbiBvcmRlciBpbiBDT0QgbWFya2VyCgBVbmtub3duIFNjb2QgdmFsdWUgaW4gQ09EIG1hcmtlcgoARXJyb3IgcmVhZGluZyBDT0QgbWFya2VyCgBFcnJvciByZWFkaW5nIFFDRCBtYXJrZXIKAENycm9yIHJlYWRpbmcgQ0JEIG1hcmtlcgoARXJyb3IgcmVhZGluZyBQT0MgbWFya2VyCgBFcnJvciByZWFkaW5nIENPQyBtYXJrZXIKAEVycm9yIHJlYWRpbmcgUUNDIG1hcmtlcgoARXJyb3IgcmVhZGluZyBNQ0MgbWFya2VyCgBOb3QgZW5vdWdoIG1lbW9yeSB0byByZWFkIE1DQyBtYXJrZXIKAHJlcXVpcmVkIFNJWiBtYXJrZXIgbm90IGZvdW5kIGluIG1haW4gaGVhZGVyCgByZXF1aXJlZCBDT0QgbWFya2VyIG5vdCBmb3VuZCBpbiBtYWluIGhlYWRlcgoAcmVxdWlyZWQgUUNEIG1hcmtlciBub3QgZm91bmQgaW4gbWFpbiBoZWFkZXIKAE5vdCBlbm91Z2ggbWVtb3J5IHRvIGhhbmRsZSBqcGVnMjAwMCBmaWxlIGhlYWRlcgoATm90IGVub3VnaCBtZW1vcnkgdG8gcmVhZCBoZWFkZXIKAEVycm9yIHdpdGggSlAgU2lnbmF0dXJlIDogYmFkIG1hZ2ljIG51bWJlcgoASW4gU09UIG1hcmtlciwgVFBTb3QgKCVkKSBpcyBub3QgdmFsaWQgcmVnYXJkcyB0byB0aGUgY3VycmVudCBudW1iZXIgb2YgdGlsZS1wYXJ0ICglZCksIGdpdmluZyB1cAoASW4gU09UIG1hcmtlciwgVFBTb3QgKCVkKSBpcyBub3QgdmFsaWQgcmVnYXJkcyB0byB0aGUgcHJldmlvdXMgbnVtYmVyIG9mIHRpbGUtcGFydCAoJWQpLCBnaXZpbmcgdXAKAEluIFNPVCBtYXJrZXIsIFRQU290ICglZCkgaXMgbm90IHZhbGlkIHJlZ2FyZHMgdG8gdGhlIGN1cnJlbnQgbnVtYmVyIG9mIHRpbGUtcGFydCAoaGVhZGVyKSAoJWQpLCBnaXZpbmcgdXAKAHRpbGVzIHJlcXVpcmUgYXQgbGVhc3Qgb25lIHJlc29sdXRpb24KAE1hcmtlciBpcyBub3QgY29tcGxpYW50IHdpdGggaXRzIHBvc2l0aW9uCgBQcm9ibGVtIHdpdGggc2VlayBmdW5jdGlvbgoARXJyb3IgcmVhZGluZyBTUENvZCBTUENvYyBlbGVtZW50LCBJbnZhbGlkIGNibGt3L2NibGtoIGNvbWJpbmF0aW9uCgBJbnZhbGlkIG11bHRpcGxlIGNvbXBvbmVudCB0cmFuc2Zvcm1hdGlvbgoAQ2Fubm90IHRha2UgaW4gY2hhcmdlIGNvbGxlY3Rpb25zIG90aGVyIHRoYW4gYXJyYXkgZGVjb3JyZWxhdGlvbgoAVG9vIGxhcmdlIHZhbHVlIGZvciBOcHBtCgBOb3QgZW5vdWdoIGJ5dGVzIHRvIHJlYWQgTnBwbQoAYmFkIHBsYWNlZCBqcGVnIGNvZGVzdHJlYW0KAAkgTWFpbiBoZWFkZXIgc3RhcnQgcG9zaXRpb249JWxsaQoJIE1haW4gaGVhZGVyIGVuZCBwb3NpdGlvbj0lbGxpCgBNYXJrZXIgc2l6ZSBpbmNvbnNpc3RlbnQgd2l0aCBzdHJlYW0gbGVuZ3RoCgBUaWxlIHBhcnQgbGVuZ3RoIHNpemUgaW5jb25zaXN0ZW50IHdpdGggc3RyZWFtIGxlbmd0aAoAQ2Fubm90IHRha2UgaW4gY2hhcmdlIG11bHRpcGxlIGRhdGEgc3Bhbm5pbmcKAFdyb25nIGZsYWcKAEVycm9yIHdpdGggRlRZUCBzaWduYXR1cmUgQm94IHNpemUKAEVycm9yIHdpdGggSlAgc2lnbmF0dXJlIEJveCBzaXplCgBJbnZhbGlkIHByZWNpbmN0IHNpemUKAEluY29uc2lzdGVudCBtYXJrZXIgc2l6ZQoASW52YWxpZCBtYXJrZXIgc2l6ZQoARXJyb3Igd2l0aCBTSVogbWFya2VyIHNpemUKAE5vdCBlbm91Z2ggbWVtb3J5IHRvIGFkZCBhIG5ldyB2YWxpZGF0aW9uIHByb2NlZHVyZQoATm90IGVub3VnaCBtZW1vcnkgdG8gZGVjb2RlIHRpbGUKAEZhaWxlZCB0byBkZWNvZGUgdGhlIGNvZGVzdHJlYW0gaW4gdGhlIEpQMiBmaWxlCgBDYW5ub3QgdGFrZSBpbiBjaGFyZ2UgY29sbGVjdGlvbnMgd2l0aCBpbmRpeCBzaHVmZmxlCgBDYW5ub3QgYWxsb2NhdGUgVGllciAxIGhhbmRsZQoATm8gZGVjb2RlZCBhcmVhIHBhcmFtZXRlcnMsIHNldCB0aGUgZGVjb2RlZCBhcmVhIHRvIHRoZSB3aG9sZSBpbWFnZQoATm90IGVub3VnaCBtZW1vcnkgdG8gY3JlYXRlIFRhZy10cmVlCgBOb3QgZW5vdWdoIG1lbW9yeSB0byByZWluaXRpYWxpemUgdGhlIHRhZyB0cmVlCgBFcnJvciByZWFkaW5nIFNQQ29kIFNQQ29jIGVsZW1lbnQsIEludmFsaWQgdHJhbnNmb3JtYXRpb24gZm91bmQKAEVycm9yIHJlYWRpbmcgU1BDb2QgU1BDb2MgZWxlbWVudC4gVW5zdXBwb3J0ZWQgTWl4ZWQgSFQgY29kZS1ibG9jayBzdHlsZSBmb3VuZAoAVGlsZSBZIGNvb3JkaW5hdGVzIGFyZSBub3Qgc3VwcG9ydGVkCgBUaWxlIFggY29vcmRpbmF0ZXMgYXJlIG5vdCBzdXBwb3J0ZWQKAEltYWdlIGNvb3JkaW5hdGVzIGFib3ZlIElOVF9NQVggYXJlIG5vdCBzdXBwb3J0ZWQKAEpQRUcyMDAwIEhlYWRlciBib3ggbm90IHJlYWQgeWV0LCAnJWMlYyVjJWMnIGJveCB3aWxsIGJlIGlnbm9yZWQKAG9wal9qMmtfbWVyZ2VfcHB0KCkgaGFzIGFscmVhZHkgYmVlbiBjYWxsZWQKAE5vdCBlbm91Z2ggbWVtb3J5IHRvIHJlYWQgU09UIG1hcmtlci4gVGlsZSBpbmRleCBhbGxvY2F0aW9uIGZhaWxlZAoASWdub3JpbmcgaWhkciBib3guIEZpcnN0IGloZHIgYm94IGFscmVhZHkgcmVhZAoAWnBwdCAldSBhbHJlYWR5IHJlYWQKAFpwcG0gJXUgYWxyZWFkeSByZWFkCgBQVEVSTSBjaGVjayBmYWlsdXJlOiAlZCBzeW50aGV0aXplZCAweEZGIG1hcmtlcnMgcmVhZAoACQkJIGNibGt3PTJeJWQKAAkJCSBjYmxraD0yXiVkCgAJCQkgcW50c3R5PSVkCgAlcyBkeD0lZCwgZHk9JWQKAAkJCSByb2lzaGlmdD0lZAoACQkJIG51bWdiaXRzPSVkCgAJCSBudW1sYXllcnM9JWQKACVzIG51bWNvbXBzPSVkCgBvcGpfanAyX2FwcGx5X2NkZWY6IGFjbj0lZCwgbnVtY29tcHM9JWQKAG9wal9qcDJfYXBwbHlfY2RlZjogY249JWQsIG51bWNvbXBzPSVkCgAJCQkgbnVtcmVzb2x1dGlvbnM9JWQKAAkJIHR5cGU9JSN4LCBwb3M9JWxsaSwgbGVuPSVkCgAlcyBzZ25kPSVkCgAJCQkgcW1mYmlkPSVkCgAlcyBwcmVjPSVkCgAJCSBuYiBvZiB0aWxlLXBhcnQgaW4gdGlsZSBbJWRdPSVkCgAlcyB4MT0lZCwgeTE9JWQKACVzIHgwPSVkLCB5MD0lZAoARmFpbGVkIHRvIGRlY29kZSB0aWxlICVkLyVkCgBTZXR0aW5nIGRlY29kaW5nIGFyZWEgdG8gJWQsJWQsJWQsJWQKAEZhaWxlZCB0byBkZWNvZGUgY29tcG9uZW50ICVkCgBJbnZhbGlkIHZhbHVlIGZvciBudW1yZXNvbHV0aW9ucyA6ICVkLCBtYXggdmFsdWUgaXMgc2V0IGluIG9wZW5qcGVnLmggYXQgJWQKAEludmFsaWQgY29tcG9uZW50IG51bWJlcjogJWQsIHJlZ2FyZGluZyB0aGUgbnVtYmVyIG9mIGNvbXBvbmVudHMgJWQKAFRvbyBtYW55IFBPQ3MgJWQKAEludmFsaWQgdGlsZSBudW1iZXIgJWQKAEludmFsaWQgdGlsZSBwYXJ0IGluZGV4IGZvciB0aWxlIG51bWJlciAlZC4gR290ICVkLCBleHBlY3RlZCAlZAoARXJyb3Igd2l0aCBTSVogbWFya2VyOiBudW1iZXIgb2YgY29tcG9uZW50IGlzIGlsbGVnYWwgLT4gJWQKAE5vdCBlbm91Z2ggbWVtb3J5IGZvciBjaWVsYWIKAENhbm5vdCBhbGxvY2F0ZSBjYmxrLT5kZWNvZGVkX2RhdGEKAEZhaWxlZCB0byBtZXJnZSBQUFQgZGF0YQoARmFpbGVkIHRvIG1lcmdlIFBQTSBkYXRhCgBJbnZhbGlkIG51bWJlciBvZiBsYXllcnMgaW4gQ09EIG1hcmtlciA6ICVkIG5vdCBpbiByYW5nZSBbMS02NTUzNV0KACVzOiVkOmNvbG9yX2NteWtfdG9fcmdiCglDQU4gTk9UIENPTlZFUlQKACVzOiVkOmNvbG9yX2VzeWNjX3RvX3JnYgoJQ0FOIE5PVCBDT05WRVJUCgAlczolZDpjb2xvcl9zeWNjX3RvX3JnYgoJQ0FOIE5PVCBDT05WRVJUCgBTdHJlYW0gdG9vIHNob3J0LCBleHBlY3RlZCBTT1QKAFVuYWJsZSB0byBzZXQgdDEgaGFuZGxlIGFzIFRMUwoAU3RyZWFtIGRvZXMgbm90IGVuZCB3aXRoIEVPQwoAQ2Fubm90IGhhbmRsZSBib3ggc2l6ZXMgaGlnaGVyIHRoYW4gMl4zMgoAb3BqX3BpX25leHRfbHJjcCgpOiBpbnZhbGlkIGNvbXBubzAvY29tcG5vMQoAb3BqX3BpX25leHRfcmxjcCgpOiBpbnZhbGlkIGNvbXBubzAvY29tcG5vMQoAb3BqX3BpX25leHRfY3BybCgpOiBpbnZhbGlkIGNvbXBubzAvY29tcG5vMQoAb3BqX3BpX25leHRfcGNybCgpOiBpbnZhbGlkIGNvbXBubzAvY29tcG5vMQoAb3BqX3BpX25leHRfcnBjbCgpOiBpbnZhbGlkIGNvbXBubzAvY29tcG5vMQoAb3BqX3QxX2RlY29kZV9jYmxrKCk6IHVuc3VwcG9ydGVkIGJwbm9fcGx1c19vbmUgPSAlZCA+PSAzMQoARmFpbGVkIHRvIGRlY29kZSB0aWxlIDEvMQoASW5zdWZmaWNpZW50IGRhdGEgZm9yIENNQVAgYm94LgoATmVlZCB0byByZWFkIGEgUENMUiBib3ggYmVmb3JlIHRoZSBDTUFQIGJveC4KAEluc3VmZmljaWVudCBkYXRhIGZvciBDREVGIGJveC4KAE51bWJlciBvZiBjaGFubmVsIGRlc2NyaXB0aW9uIGlzIGVxdWFsIHRvIHplcm8gaW4gQ0RFRiBib3guCgBTdHJlYW0gZXJyb3Igd2hpbGUgcmVhZGluZyBKUDIgSGVhZGVyIGJveDogbm8gJ2loZHInIGJveC4KAE5vbiBjb25mb3JtYW50IGNvZGVzdHJlYW0gVFBzb3Q9PVROc290LgoAU3RyZWFtIGVycm9yIHdoaWxlIHJlYWRpbmcgSlAyIEhlYWRlciBib3g6IGJveCBsZW5ndGggaXMgaW5jb25zaXN0ZW50LgoAQm94IGxlbmd0aCBpcyBpbmNvbnNpc3RlbnQuCgBSZXNvbHV0aW9uIGZhY3RvciBpcyBncmVhdGVyIHRoYW4gdGhlIG1heGltdW0gcmVzb2x1dGlvbiBpbiB0aGUgY29tcG9uZW50LgoAQ29tcG9uZW50IG1hcHBpbmcgc2VlbXMgd3JvbmcuIFRyeWluZyB0byBjb3JyZWN0LgoASW5jb21wbGV0ZSBjaGFubmVsIGRlZmluaXRpb25zLgoATWFsZm9ybWVkIEhUIGNvZGVibG9jay4gSW52YWxpZCBjb2RlYmxvY2sgbGVuZ3RoIHZhbHVlcy4KAFdlIGRvIG5vdCBzdXBwb3J0IG1vcmUgdGhhbiAzIGNvZGluZyBwYXNzZXMgaW4gYW4gSFQgY29kZWJsb2NrOyBUaGlzIGNvZGVibG9ja3MgaGFzICVkIHBhc3Nlcy4KAE1hbGZvcm1lZCBIVCBjb2RlYmxvY2suIERlY29kaW5nIHRoaXMgY29kZWJsb2NrIGlzIHN0b3BwZWQuIFRoZXJlIGFyZSAlZCB6ZXJvIGJpdHBsYW5lcyBpbiAlZCBiaXRwbGFuZXMuCgBDYW5ub3QgdGFrZSBpbiBjaGFyZ2UgbXVsdGlwbGUgdHJhbnNmb3JtYXRpb24gc3RhZ2VzLgoAVW5rbm93biBtYXJrZXIgaGFzIGJlZW4gZGV0ZWN0ZWQgYW5kIGdlbmVyYXRlZCBlcnJvci4KAENvZGVjIHByb3ZpZGVkIHRvIHRoZSBvcGpfc2V0dXBfZGVjb2RlciBmdW5jdGlvbiBpcyBub3QgYSBkZWNvbXByZXNzb3IgaGFuZGxlci4KAENvZGVjIHByb3ZpZGVkIHRvIHRoZSBvcGpfcmVhZF9oZWFkZXIgZnVuY3Rpb24gaXMgbm90IGEgZGVjb21wcmVzc29yIGhhbmRsZXIuCgBUaWxlcyBkb24ndCBhbGwgaGF2ZSB0aGUgc2FtZSBkaW1lbnNpb24uIFNraXAgdGhlIE1DVCBzdGVwLgoATnVtYmVyIG9mIGNvbXBvbmVudHMgKCVkKSBpcyBpbmNvbnNpc3RlbnQgd2l0aCBhIE1DVC4gU2tpcCB0aGUgTUNUIHN0ZXAuCgBKUDIgYm94IHdoaWNoIGFyZSBhZnRlciB0aGUgY29kZXN0cmVhbSB3aWxsIG5vdCBiZSByZWFkIGJ5IHRoaXMgZnVuY3Rpb24uCgBNYWxmb3JtZWQgSFQgY29kZWJsb2NrLiBXaGVuIHRoZSBudW1iZXIgb2YgemVybyBwbGFuZXMgYml0cGxhbmVzIGlzIGVxdWFsIHRvIHRoZSBudW1iZXIgb2YgYml0cGxhbmVzLCBvbmx5IHRoZSBjbGVhbnVwIHBhc3MgbWFrZXMgc2Vuc2UsIGJ1dCB3ZSBoYXZlICVkIHBhc3NlcyBpbiB0aGlzIGNvZGVibG9jay4gVGhlcmVmb3JlLCBvbmx5IHRoZSBjbGVhbnVwIHBhc3Mgd2lsbCBiZSBkZWNvZGVkLiBUaGlzIG1lc3NhZ2Ugd2lsbCBub3QgYmUgZGlzcGxheWVkIGFnYWluLgoASW1hZ2UgaGFzIGxlc3MgY29tcG9uZW50cyB0aGFuIGNvZGVzdHJlYW0uCgBOZWVkIHRvIGRlY29kZSB0aGUgbWFpbiBoZWFkZXIgYmVmb3JlIGJlZ2luIHRvIGRlY29kZSB0aGUgcmVtYWluaW5nIGNvZGVzdHJlYW0uCgBQc290IHZhbHVlIG9mIHRoZSBjdXJyZW50IHRpbGUtcGFydCBpcyBlcXVhbCB0byB6ZXJvLCB3ZSBhc3N1bWluZyBpdCBpcyB0aGUgbGFzdCB0aWxlLXBhcnQgb2YgdGhlIGNvZGVzdHJlYW0uCgBBIG1hbGZvcm1lZCBjb2RlYmxvY2sgdGhhdCBoYXMgbW9yZSB0aGFuIG9uZSBjb2RpbmcgcGFzcywgYnV0IHplcm8gbGVuZ3RoIGZvciAybmQgYW5kIHBvdGVudGlhbGx5IHRoZSAzcmQgcGFzcyBpbiBhbiBIVCBjb2RlYmxvY2suCgAJCQkgdGlsZS1wYXJ0WyVkXTogc3Rhcl9wb3M9JWxsaSwgZW5kX2hlYWRlcj0lbGxpLCBlbmRfcG9zPSVsbGkuCgBUaWxlICV1IGhhcyBUUHNvdCA9PSAwIGFuZCBUTnNvdCA9PSAwLCBidXQgbm8gb3RoZXIgdGlsZS1wYXJ0cyB3ZXJlIGZvdW5kLiBFT0MgaXMgYWxzbyBtaXNzaW5nLgoAQ29tcG9uZW50ICVkIGRvZXNuJ3QgaGF2ZSBhIG1hcHBpbmcuCgBBIGNvbmZvcm1pbmcgSlAyIHJlYWRlciBzaGFsbCBpZ25vcmUgYWxsIENvbG91ciBTcGVjaWZpY2F0aW9uIGJveGVzIGFmdGVyIHRoZSBmaXJzdCwgc28gd2UgaWdub3JlIHRoaXMgb25lLgoAVGhlIHNpZ25hdHVyZSBib3ggbXVzdCBiZSB0aGUgZmlyc3QgYm94IGluIHRoZSBmaWxlLgoAVGhlICBib3ggbXVzdCBiZSB0aGUgZmlyc3QgYm94IGluIHRoZSBmaWxlLgoAVGhlIGZ0eXAgYm94IG11c3QgYmUgdGhlIHNlY29uZCBib3ggaW4gdGhlIGZpbGUuCgBGYWlsZWQgdG8gZGVjb2RlLgoATWFsZm9ybWVkIEhUIGNvZGVibG9jay4gSW5jb3JyZWN0IE1FTCBzZWdtZW50IHNlcXVlbmNlLgoAQ29tcG9uZW50ICVkIGlzIG1hcHBlZCB0d2ljZS4KAE9ubHkgb25lIENNQVAgYm94IGlzIGFsbG93ZWQuCgBXZSBuZWVkIGFuIGltYWdlIHByZXZpb3VzbHkgY3JlYXRlZC4KAElIRFIgYm94X21pc3NpbmcuIFJlcXVpcmVkLgoASlAySCBib3ggbWlzc2luZy4gUmVxdWlyZWQuCgBOb3Qgc3VyZSBob3cgdGhhdCBoYXBwZW5lZC4KAE1haW4gaGVhZGVyIGhhcyBiZWVuIGNvcnJlY3RseSBkZWNvZGVkLgoAVGlsZSAlZC8lZCBoYXMgYmVlbiBkZWNvZGVkLgoASGVhZGVyIG9mIHRpbGUgJWQgLyAlZCBoYXMgYmVlbiByZWFkLgoARW1wdHkgU09UIG1hcmtlciBkZXRlY3RlZDogUHNvdD0lZC4KAERpcmVjdCB1c2UgYXQgIyVkIGhvd2V2ZXIgcGNvbD0lZC4KAEltcGxlbWVudGF0aW9uIGxpbWl0YXRpb246IGZvciBwYWxldHRlIG1hcHBpbmcsIHBjb2xbJWRdIHNob3VsZCBiZSBlcXVhbCB0byAlZCwgYnV0IGlzIGVxdWFsIHRvICVkLgoASW52YWxpZCBjb21wb25lbnQvcGFsZXR0ZSBpbmRleCBmb3IgZGlyZWN0IG1hcHBpbmcgJWQuCgBJbnZhbGlkIHZhbHVlIGZvciBjbWFwWyVkXS5tdHlwID0gJWQuCgBQc290IHZhbHVlIGlzIG5vdCBjb3JyZWN0IHJlZ2FyZHMgdG8gdGhlIEpQRUcyMDAwIG5vcm06ICVkLgoATWFsZm9ybWVkIEhUIGNvZGVibG9jay4gVkxDIGNvZGUgcHJvZHVjZXMgc2lnbmlmaWNhbnQgc2FtcGxlcyBvdXRzaWRlIHRoZSBjb2RlYmxvY2sgYXJlYS4KAFVuZXhwZWN0ZWQgT09NLgoAMzIgYml0cyBhcmUgbm90IGVub3VnaCB0byBkZWNvZGUgdGhpcyBjb2RlYmxvY2ssIHNpbmNlIHRoZSBudW1iZXIgb2YgYml0cGxhbmUsICVkLCBpcyBsYXJnZXIgdGhhbiAzMC4KAEJvdHRvbSBwb3NpdGlvbiBvZiB0aGUgZGVjb2RlZCBhcmVhIChyZWdpb25feTE9JWQpIHNob3VsZCBiZSA+IDAuCgBSaWdodCBwb3NpdGlvbiBvZiB0aGUgZGVjb2RlZCBhcmVhIChyZWdpb25feDE9JWQpIHNob3VsZCBiZSA+IDAuCgBVcCBwb3NpdGlvbiBvZiB0aGUgZGVjb2RlZCBhcmVhIChyZWdpb25feTA9JWQpIHNob3VsZCBiZSA+PSAwLgoATGVmdCBwb3NpdGlvbiBvZiB0aGUgZGVjb2RlZCBhcmVhIChyZWdpb25feDA9JWQpIHNob3VsZCBiZSA+PSAwLgoARXJyb3IgcmVhZGluZyBQUFQgbWFya2VyOiBwYWNrZXQgaGVhZGVyIGhhdmUgYmVlbiBwcmV2aW91c2x5IGZvdW5kIGluIHRoZSBtYWluIGhlYWRlciAoUFBNIG1hcmtlcikuCgBTdGFydCB0byByZWFkIGoyayBtYWluIGhlYWRlciAoJWxsZCkuCgBCb3R0b20gcG9zaXRpb24gb2YgdGhlIGRlY29kZWQgYXJlYSAocmVnaW9uX3kxPSVkKSBpcyBvdXRzaWRlIHRoZSBpbWFnZSBhcmVhIChZc2l6PSVkKS4KAFVwIHBvc2l0aW9uIG9mIHRoZSBkZWNvZGVkIGFyZWEgKHJlZ2lvbl95MD0lZCkgaXMgb3V0c2lkZSB0aGUgaW1hZ2UgYXJlYSAoWXNpej0lZCkuCgBSaWdodCBwb3NpdGlvbiBvZiB0aGUgZGVjb2RlZCBhcmVhIChyZWdpb25feDE9JWQpIGlzIG91dHNpZGUgdGhlIGltYWdlIGFyZWEgKFhzaXo9JWQpLgoATGVmdCBwb3NpdGlvbiBvZiB0aGUgZGVjb2RlZCBhcmVhIChyZWdpb25feDA9JWQpIGlzIG91dHNpZGUgdGhlIGltYWdlIGFyZWEgKFhzaXo9JWQpLgoAQm90dG9tIHBvc2l0aW9uIG9mIHRoZSBkZWNvZGVkIGFyZWEgKHJlZ2lvbl95MT0lZCkgaXMgb3V0c2lkZSB0aGUgaW1hZ2UgYXJlYSAoWU9zaXo9JWQpLgoAVXAgcG9zaXRpb24gb2YgdGhlIGRlY29kZWQgYXJlYSAocmVnaW9uX3kwPSVkKSBpcyBvdXRzaWRlIHRoZSBpbWFnZSBhcmVhIChZT3Npej0lZCkuCgBSaWdodCBwb3NpdGlvbiBvZiB0aGUgZGVjb2RlZCBhcmVhIChyZWdpb25feDE9JWQpIGlzIG91dHNpZGUgdGhlIGltYWdlIGFyZWEgKFhPc2l6PSVkKS4KAExlZnQgcG9zaXRpb24gb2YgdGhlIGRlY29kZWQgYXJlYSAocmVnaW9uX3gwPSVkKSBpcyBvdXRzaWRlIHRoZSBpbWFnZSBhcmVhIChYT3Npej0lZCkuCgBTaXplIHggb2YgdGhlIGRlY29kZWQgY29tcG9uZW50IGltYWdlIGlzIGluY29ycmVjdCAoY29tcFslZF0udz0lZCkuCgBTaXplIHkgb2YgdGhlIGRlY29kZWQgY29tcG9uZW50IGltYWdlIGlzIGluY29ycmVjdCAoY29tcFslZF0uaD0lZCkuCgBUaWxlIHJlYWQsIGRlY29kZWQgYW5kIHVwZGF0ZWQgaXMgbm90IHRoZSBkZXNpcmVkIG9uZSAoJWQgdnMgJWQpLgoASW52YWxpZCBjb21wb25lbnQgaW5kZXggJWQgKD49ICVkKS4KAG9wal9yZWFkX2hlYWRlcigpIHNob3VsZCBiZSBjYWxsZWQgYmVmb3JlIG9wal9zZXRfZGVjb2RlZF9jb21wb25lbnRzKCkuCgBNZW1vcnkgYWxsb2NhdGlvbiBmYWlsdXJlIGluIG9wal9qcDJfYXBwbHlfcGNscigpLgoAaW1hZ2UtPmNvbXBzWyVkXS5kYXRhID09IE5VTEwgaW4gb3BqX2pwMl9hcHBseV9wY2xyKCkuCgBpbnZhbGlkIGJveCBzaXplICVkICgleCkKAEZhaWwgdG8gcmVhZCB0aGUgY3VycmVudCBtYXJrZXIgc2VnbWVudCAoJSN4KQoARXJyb3Igd2l0aCBTSVogbWFya2VyOiBJSERSIHcoJXUpIGgoJXUpIHZzLiBTSVogdygldSkgaCgldSkKAEVycm9yIHJlYWRpbmcgQ09DIG1hcmtlciAoYmFkIG51bWJlciBvZiBjb21wb25lbnRzKQoASW52YWxpZCBudW1iZXIgb2YgdGlsZXMgOiAldSB4ICV1IChtYXhpbXVtIGZpeGVkIGJ5IGpwZWcyMDAwIG5vcm0gaXMgNjU1MzUgdGlsZXMpCgBJbnZhbGlkIG51bWJlciBvZiBjb21wb25lbnRzIChpaGRyKQoATm90IGVub3VnaCBtZW1vcnkgdG8gaGFuZGxlIGltYWdlIGhlYWRlciAoaWhkcikKAFdyb25nIHZhbHVlcyBmb3I6IHcoJWQpIGgoJWQpIG51bWNvbXBzKCVkKSAoaWhkcikKAEludmFsaWQgdmFsdWVzIGZvciBjb21wID0gJWQgOiBkeD0ldSBkeT0ldSAoc2hvdWxkIGJlIGJldHdlZW4gMSBhbmQgMjU1IGFjY29yZGluZyB0byB0aGUgSlBFRzIwMDAgbm9ybSkKAEJhZCBpbWFnZSBoZWFkZXIgYm94IChiYWQgc2l6ZSkKAEJhZCBDT0xSIGhlYWRlciBib3ggKGJhZCBzaXplKQoAQmFkIEJQQ0MgaGVhZGVyIGJveCAoYmFkIHNpemUpCgBFcnJvciB3aXRoIFNJWiBtYXJrZXI6IG5lZ2F0aXZlIG9yIHplcm8gaW1hZ2Ugc2l6ZSAoJWxsZCB4ICVsbGQpCgBza2lwOiBzZWdtZW50IHRvbyBsb25nICglZCkgd2l0aCBtYXggKCVkKSBmb3IgY29kZWJsb2NrICVkIChwPSVkLCBiPSVkLCByPSVkLCBjPSVkKQoAcmVhZDogc2VnbWVudCB0b28gbG9uZyAoJWQpIHdpdGggbWF4ICglZCkgZm9yIGNvZGVibG9jayAlZCAocD0lZCwgYj0lZCwgcj0lZCwgYz0lZCkKAERlc3BpdGUgSlAyIEJQQyE9MjU1LCBwcmVjaXNpb24gYW5kL29yIHNnbmQgdmFsdWVzIGZvciBjb21wWyVkXSBpcyBkaWZmZXJlbnQgdGhhbiBjb21wWzBdOgogICAgICAgIFswXSBwcmVjKCVkKSBzZ25kKCVkKSBbJWRdIHByZWMoJWQpIHNnbmQoJWQpCgBiYWQgY29tcG9uZW50IG51bWJlciBpbiBSR04gKCVkIHdoZW4gdGhlcmUgYXJlIG9ubHkgJWQpCgBFcnJvciB3aXRoIFNJWiBtYXJrZXI6IG51bWJlciBvZiBjb21wb25lbnQgaXMgbm90IGNvbXBhdGlibGUgd2l0aCB0aGUgcmVtYWluaW5nIG51bWJlciBvZiBwYXJhbWV0ZXJzICggJWQgdnMgJWQpCgBFcnJvciB3aXRoIFNJWiBtYXJrZXI6IGludmFsaWQgdGlsZSBzaXplICh0ZHg6ICVkLCB0ZHk6ICVkKQoAQmFkIENPTFIgaGVhZGVyIGJveCAoYmFkIHNpemU6ICVkKQoAQmFkIENPTFIgaGVhZGVyIGJveCAoQ0lFTGFiLCBiYWQgc2l6ZTogJWQpCgBQVEVSTSBjaGVjayBmYWlsdXJlOiAlZCByZW1haW5pbmcgYnl0ZXMgaW4gY29kZSBibG9jayAoJWQgdXNlZCAvICVkKQoATWFsZm9ybWVkIEhUIGNvZGVibG9jay4gT25lIG9mIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uIGlzIG5vdCBtZXQ6IDIgPD0gU2N1cCA8PSBtaW4oTGN1cCwgNDA3OSkKAEludmFsaWQgdmFsdWVzIGZvciBjb21wID0gJWQgOiBwcmVjPSV1IChzaG91bGQgYmUgYmV0d2VlbiAxIGFuZCAzOCBhY2NvcmRpbmcgdG8gdGhlIEpQRUcyMDAwIG5vcm0uIE9wZW5KcGVnIG9ubHkgc3VwcG9ydHMgdXAgdG8gMzEpCgBJbnZhbGlkIGJpdCBudW1iZXIgJWQgaW4gb3BqX3QyX3JlYWRfcGFja2V0X2hlYWRlcigpCgBTdHJlYW0gZXJyb3IhCgBFcnJvciBvbiB3cml0aW5nIHN0cmVhbSEKAFN0cmVhbSByZWFjaGVkIGl0cyBlbmQgIQoARXhwZWN0ZWQgYSBTT0MgbWFya2VyIAoASW52YWxpZCBib3ggc2l6ZSAlZCBmb3IgYm94ICclYyVjJWMlYycuIE5lZWQgJWQgYnl0ZXMsICVkIGJ5dGVzIHJlbWFpbmluZyAKAE1hbGZvcm1lZCBIVCBjb2RlYmxvY2suIERlY29kaW5nIHRoaXMgY29kZWJsb2NrIGlzIHN0b3BwZWQuIFVfcSBpcyBsYXJnZXIgdGhhbiB6ZXJvIGJpdHBsYW5lcyArIDEgCgBNYWxmb3JtZWQgSFQgY29kZWJsb2NrLiBEZWNvZGluZyB0aGlzIGNvZGVibG9jayBpcyBzdG9wcGVkLiBVX3EgaXNsYXJnZXIgdGhhbiBiaXRwbGFuZXMgKyAxIAoAQ09MUiBCT1ggbWV0aCB2YWx1ZSBpcyBub3QgYSByZWd1bGFyIHZhbHVlICglZCksIHNvIHdlIHdpbGwgaWdub3JlIHRoZSBlbnRpcmUgQ29sb3VyIFNwZWNpZmljYXRpb24gYm94LiAKAFdoaWxlIHJlYWRpbmcgQ0NQX1FOVFNUWSBlbGVtZW50IGluc2lkZSBRQ0Qgb3IgUUNDIG1hcmtlciBzZWdtZW50LCBudW1iZXIgb2Ygc3ViYmFuZHMgKCVkKSBpcyBncmVhdGVyIHRvIE9QSl9KMktfTUFYQkFORFMgKCVkKS4gU28gd2UgbGltaXQgdGhlIG51bWJlciBvZiBlbGVtZW50cyBzdG9yZWQgdG8gT1BKX0oyS19NQVhCQU5EUyAoJWQpIGFuZCBza2lwIHRoZSByZXN0LiAKAEpQMiBJSERSIGJveDogY29tcHJlc3Npb24gdHlwZSBpbmRpY2F0ZSB0aGF0IHRoZSBmaWxlIGlzIG5vdCBhIGNvbmZvcm1pbmcgSlAyIGZpbGUgKCVkKSAKAFRpbGUgaW5kZXggcHJvdmlkZWQgYnkgdGhlIHVzZXIgaXMgaW5jb3JyZWN0ICVkIChtYXggPSAlZCkgCgBFcnJvciBkZWNvZGluZyBjb21wb25lbnQgJWQuClRoZSBudW1iZXIgb2YgcmVzb2x1dGlvbnMgdG8gcmVtb3ZlICglZCkgaXMgZ3JlYXRlciBvciBlcXVhbCB0aGFuIHRoZSBudW1iZXIgb2YgcmVzb2x1dGlvbnMgb2YgdGhpcyBjb21wb25lbnQgKCVkKQpNb2RpZnkgdGhlIGNwX3JlZHVjZSBwYXJhbWV0ZXIuCgoASW1hZ2UgZGF0YSBoYXMgYmVlbiB1cGRhdGVkIHdpdGggdGlsZSAlZC4KCgBBoP0AC4AgIwClAEMAZgCDAO6oFADf2CMAvhBDAP/1gwB+IFUAX1EjADUAQwBORIMAzsQUAM/MIwD+4kMA/5mDAJYAxQA/MSMApQBDAF5EgwDOyBQA3xEjAP70QwD//IMAngBVAHcAIwA1AEMA//GDAK6IFAC3ACMA/vhDAO/kgwCOiMUAHxEjAKUAQwBmAIMA7qgUAN9UIwC+EEMA7yKDAH4gVQB/IiMANQBDAE5EgwDOxBQAvxEjAP7iQwD3AIMAlgDFAD8iIwClAEMAXkSDAM7IFADXACMA/vRDAP+6gwCeAFUAbwAjADUAQwD/5oMArogUAK+iIwD++EMA5wCDAI6IxQAvIgIAxQCEAH4gAgDOxCQA9wACAP6iRABWAAIAngAUANcAAgC+EIQAZgACAK6IJADfEQIA7qhEADYAAgCOiBQAHxECAMUAhABuAAIAzogkAP+IAgD+uEQATkQCAJYAFAC3AAIA/uSEAF5EAgCmACQA5wACAN5URAAuIgIAPgAUAHcAAgDFAIQAfiACAM7EJAD/8QIA/qJEAFYAAgCeABQAvxECAL4QhABmAAIArogkAO8iAgDuqEQANgACAI6IFAB/IgIAxQCEAG4AAgDOiCQA7+QCAP64RABORAIAlgAUAK+iAgD+5IQAXkQCAKYAJADf2AIA3lREAC4iAgA+ABQAX1ECAFUAhABmAAIA3ogkAP8yAgD+EUQATkQCAK4AFAC3AAIAfjGEAF5RAgDGACQA1wACAO4gRAAeEQIAngAUAHcAAgBVAIQAXlQCAM5EJADnAAIA/vFEADYAAgCmABQAX1UCAP50hAA+EQIAviAkAH90AgDexEQA//gCAJYAFAAvIgIAVQCEAGYAAgDeiCQA9wACAP4RRABORAIArgAUAI+IAgB+MYQAXlECAMYAJADPyAIA7iBEAB4RAgCeABQAbwACAFUAhABeVAIAzkQkAN/RAgD+8UQANgACAKYAFAB/IgIA/nSEAD4RAgC+ICQAvyICAN7ERADvIgIAlgAUAD8yAwDe1P30//wUAD4RVQCPiAMAvjKFAOcAJQBeUf6qf3IDAM5E/fjvRBQAfmRFAK+iAwCmAF1V35n98TYA/vVvYgMA3tH99P/mFAB+cVUAv7EDAK6IhQDf1SUATkT+8n9mAwDGAP347+IUAF5URQCfEQMAlgBdVc/I/fEeEe7IZwADAN7U/fT/8xQAPhFVAL8RAwC+MoUA39glAF5R/qovIgMAzkT9+PcAFAB+ZEUAn5gDAKYAXVXXAP3xNgD+9W9EAwDe0f30/7kUAH5xVQC3AAMAroiFAN/cJQBORP7ydwADAMYA/fjv5BQAXlRFAH9zAwCWAF1Vv7j98R4R7sg/MgIApQCEAH5AAgDeECQA3xECAP5yRABWAAIArqgUAL+yAgCWAIQAZgACAMYAJADnAAIA7shEAC4iAgCOiBQAdwACAKUAhABuAAIAzogkAPcAAgD+kUQANgACAK6iFACvqgIA/riEAF4AAgC+ACQAz8QCAO5ERAD/9AIAPiIUAB8RAgClAIQAfkACAN4QJAD/mQIA/nJEAFYAAgCuqBQAtwACAJYAhABmAAIAxgAkANcAAgDuyEQALiICAI6IFABPRAIApQCEAG4AAgDOiCQA7+ICAP6RRAA2AAIArqIUAH9EAgD+uIQAXgACAL4AJACfAAIA7kREAP92AgA+IhQAPzEDAMYAhQD/2f3yfmT+8b+ZAwCuoiUA72b99FYA7uJ/cwMAvphFAPcA/fhmAP52n4gDAI6IFQDf1aUALiLemE9EAwC+soUA//z98m4ilgC3AAMArqolAN/R/fQ2AN7Ub2QDAK6oRQDv6v34XkTu6H9xAwA+MhUAz8SlAP/6zog/MQMAxgCFAP93/fJ+ZP7xv7MDAK6iJQDnAP30VgDu4ncAAwC+mEUA7+T9+GYA/nZ/ZgMAjogVANcApQAuIt6YPzMDAL6yhQD/df3ybiKWAJ+RAwCuqiUA35n99DYA3tRfUQMArqhFAO/s/fheRO7of3IDAD4yFQC/saUA//POiB8RAwDeVP3yHhEUAH5k/vjPzAMAvpFFAO8iJQAuIv7zj4gDAMYAhQD3ABQAXhH+/K+oAwCmADUA38j98T4x/mZvZAMAzsj98v/1FABmAP70v7oDAK4iRQDnACUAPjL+6n9zAwC+soUA31UUAFYAfnGfEQMAlgA1AM/E/fE+M+7oT0QDAN5U/fIeERQAfmT++L+ZAwC+kUUA7+IlAC4i/vN/ZgMAxgCFAO/kFABeEf78n5gDAKYANQDXAP3xPjH+Zm8iAwDOyP3y/7kUAGYA/vS3AAMAriJFAN/RJQA+Mv7qdwADAL6yhQDv7BQAVgB+cX9yAwCWADUAv7j98T4z7uhfVPzx3tH9+tcA/PgWAP3/f3T89H5x/fO/s/zy7+ru6E9E/PGuIgUAv7j8+PcA/vx3APz0XhH99X91/PLf2O7iPzP88b6y/frPiPz4//v9/39z/PRuAP3ztwD88u9m/vk/MfzxngAFAL+6/Pj//f72ZwD89CYA/fWPiPzy39ze1C8i/PHe0f36z8T8+BYA/f9/cvz0fnH987+Z/PLv7O7oRwD88a4iBQCnAPz4//f+/FcA/PReEf31lwD88t/V7uI3APzxvrL9+scA/Pj//v3/f2b89G4A/fOvqPzy5wD++T8y/PGeAAUAv7H8+O/k/vZfVPz0JgD99YcA/PLfmd7UHxETAGUAQwDeAIMAjYgjAE5EEwClAEMAroiDADUAIwDXABMAxQBDAJ4AgwBVACMALiITAJUAQwB+AIMA/hAjAHcAEwBlAEMAzoiDAI2IIwAeERMApQBDAF4AgwA1ACMA5wATAMUAQwC+AIMAVQAjAP8REwCVAEMAPgCDAO5AIwCvohMAZQBDAN4AgwCNiCMATkQTAKUAQwCuiIMANQAjAO9EEwDFAEMAngCDAFUAIwAuIhMAlQBDAH4AgwD+ECMAtwATAGUAQwDOiIMAjYgjAB4REwClAEMAXgCDADUAIwDPxBMAxQBDAL4AgwBVACMA9wATAJUAQwA+AIMA7kAjAG8AAQCEAAEAVgABABQAAQDXAAEAJAABAJYAAQBFAAEAdwABAIQAAQDGAAEAFAABAI+IAQAkAAEA9wABADUAAQAvIgEAhAABAP5AAQAUAAEAtwABACQAAQC/AAEARQABAGcAAQCEAAEApgABABQAAQBPRAEAJAABAOcAAQA1AAEAPxEBAIQAAQBWAAEAFAABAM8AAQAkAAEAlgABAEUAAQBvAAEAhAABAMYAAQAUAAEAnwABACQAAQDvAAEANQABAD8yAQCEAAEA/kABABQAAQCvAAEAJAABAP9EAQBFAAEAXwABAIQAAQCmAAEAFAABAH8AAQAkAAEA3wABADUAAQAfEQEAJAABAFYAAQCFAAEAvwABABQAAQD3AAEAxgABAHcAAQAkAAEA//gBAEUAAQB/AAEAFAABAN8AAQCmAAEAPzEBACQAAQAuIgEAhQABALcAAQAUAAEA70QBAK6iAQBnAAEAJAABAP9RAQBFAAEAlwABABQAAQDPAAEANgABAD8iAQAkAAEAVgABAIUAAQC/sgEAFAABAO9AAQDGAAEAbwABACQAAQD/cgEARQABAJ8AAQAUAAEA1wABAKYAAQBPRAEAJAABAC4iAQCFAAEAr6gBABQAAQDnAAEArqIBAF8AAQAkAAEA/0QBAEUAAQCPiAEAFAABAK+qAQA2AAEAHxECAP74JABWAAIAtgCFAP9mAgDOABQAHhECAJYANQCvqAIA9gAkAD4xAgCmAEUAv7MCAL6yFAD/9QIAZgB+UV9UAgD+8iQALiICAK4ihQDvRAIAxgAUAP/0AgB2ADUAf0QCAN5AJAA+MgIAngBFANcAAgC+iBQA//oCAF4R/vFPRAIA/vgkAFYAAgC2AIUA78gCAM4AFAAeEQIAlgA1AI+IAgD2ACQAPjECAKYARQDfRAIAvrIUAP+oAgBmAH5RbwACAP7yJAAuIgIAriKFAOcAAgDGABQA7+ICAHYANQB/cgIA3kAkAD4yAgCeAEUAv7ECAL6IFAD/cwIAXhH+8T8zAQCEAAEA7iABAMUAAQDPxAEARAABAP8yAQAVAAEAj4gBAIQAAQBmAAEAJQABAK8AAQBEAAEA7yIBAKYAAQBfAAEAhAABAE5EAQDFAAEAz8wBAEQAAQD3AAEAFQABAG8AAQCEAAEAVgABACUAAQCfAAEARAABAN8AAQD+MAEALyIBAIQAAQDuIAEAxQABAM/IAQBEAAEA/xEBABUAAQB3AAEAhAABAGYAAQAlAAEAfwABAEQAAQDnAAEApgABADcAAQCEAAEATkQBAMUAAQC3AAEARAABAL8AAQAVAAEAPwABAIQAAQBWAAEAJQABAJcAAQBEAAEA1wABAP4wAQAfEQIA7qhEAI6IAgDWAMUA//MCAP78JQA+AAIAtgBVAN/YAgD++EQAZgACAH4ghQD/mQIA5gD1ADYAAgCmABUAnwACAP7yRAB2AAIAzkTFAP92AgD+8SUATkQCAK4AVQDPyAIA/vREAF5EAgC+EIUA7+QCAN5U9QAeEQIAlgAVAC8iAgDuqEQAjogCANYAxQD/+gIA/vwlAD4AAgC2AFUAvxECAP74RABmAAIAfiCFAO8iAgDmAPUANgACAKYAFQB/IgIA/vJEAHYAAgDORMUA/9UCAP7xJQBORAIArgBVAG8AAgD+9EQAXkQCAL4QhQDfEQIA3lT1AB4RAgCWABUAX1EDAPYAFAAeEUQAjoilAN/UAwCuolUA/3YkAD4itgCvqgMA5gAUAP/1RABmAIUAz8wDAJ4AxQDvRCQANgD++H8xAwDu6BQA//FEAHYApQDPxAMAfiJVAN/RJABORP70X1EDANYAFADv4kQAXkSFAL8iAwCWAMUA38gkAC4i/vJvIgMA9gAUAB4RRACOiKUAv7EDAK6iVQD/MyQAPiK2AK+oAwDmABQA/7lEAGYAhQC/qAMAngDFAO/kJAA2AP74b2QDAO7oFAD//EQAdgClAM/IAwB+IlUA7+okAE5E/vR/dAMA1gAUAP/6RABeRIUAv7IDAJYAxQDfRCQALiL+8j8x8wD++v3xNgAEAL4ydQDfEfMA3lT98u/k1QB+cf78f3PzAP7z/fgeEQQAlgBVAL+x8wDOALUA39j99GYA/rlfVPMA/nb98SYABACmAHUAnwDzAK4A/fL/99UARgD+9X908wDmAP34FgAEAIYAVQCPiPMAxgC1AO/i/fReEe6oPxHzAP76/fE2AAQAvjJ1AN/R8wDeVP3y//vVAH5x/vx/RPMA/vP9+B4RBACWAFUAf3LzAM4AtQDvIv30ZgD+uU9E8wD+dv3xJgAEAKYAdQC/EfMArgD98v//1QBGAP71PzLzAOYA/fgWAAQAhgBVAG8A8wDGALUAv7j99F4R7qgvIgBBrJ0BC6QeAQAAAAEAAAABAAAAAgAAAAIAAAACAAAAAwAAAAMAAAAEAAAABQAAALchQiFnIUIhERERETMzMzN3d3d3AAAAAAAAAAABVgAAAAAAABBPAAAgTwAAAVYAAAEAAAAgTwAAEE8AAAE0AAAAAAAAME8AALBPAAABNAAAAQAAAEBPAADATwAAARgAAAAAAABQTwAAEFAAAAEYAAABAAAAYE8AACBQAADBCgAAAAAAAHBPAABwUAAAwQoAAAEAAACATwAAgFAAACEFAAAAAAAAkE8AAJBSAAAhBQAAAQAAAKBPAACgUgAAIQIAAAAAAACwUwAAEFMAACECAAABAAAAwFMAACBTAAABVgAAAAAAANBPAADATwAAAVYAAAEAAADgTwAAsE8AAAFUAAAAAAAA8E8AALBQAAABVAAAAQAAAABQAADAUAAAAUgAAAAAAAAQUAAAsFAAAAFIAAABAAAAIFAAAMBQAAABOAAAAAAAADBQAACwUAAAATgAAAEAAABAUAAAwFAAAAEwAAAAAAAAUFAAABBRAAABMAAAAQAAAGBQAAAgUQAAASQAAAAAAABwUAAAMFEAAAEkAAABAAAAgFAAAEBRAAABHAAAAAAAAJBQAABwUQAAARwAAAEAAACgUAAAgFEAAAEWAAAAAAAAkFIAAJBRAAABFgAAAQAAAKBSAACgUQAAAVYAAAAAAADQUAAAwFAAAAFWAAABAAAA4FAAALBQAAABVAAAAAAAAPBQAACwUAAAAVQAAAEAAAAAUQAAwFAAAAFRAAAAAAAAEFEAANBQAAABUQAAAQAAACBRAADgUAAAAUgAAAAAAAAwUQAA8FAAAAFIAAABAAAAQFEAAABRAAABOAAAAAAAAFBRAAAQUQAAATgAAAEAAABgUQAAIFEAAAE0AAAAAAAAcFEAADBRAAABNAAAAQAAAIBRAABAUQAAATAAAAAAAACQUQAAUFEAAAEwAAABAAAAoFEAAGBRAAABKAAAAAAAALBRAABQUQAAASgAAAEAAADAUQAAYFEAAAEkAAAAAAAA0FEAAHBRAAABJAAAAQAAAOBRAACAUQAAASIAAAAAAADwUQAAkFEAAAEiAAABAAAAAFIAAKBRAAABHAAAAAAAABBSAACwUQAAARwAAAEAAAAgUgAAwFEAAAEYAAAAAAAAMFIAANBRAAABGAAAAQAAAEBSAADgUQAAARYAAAAAAABQUgAA8FEAAAEWAAABAAAAYFIAAABSAAABFAAAAAAAAHBSAAAQUgAAARQAAAEAAACAUgAAIFIAAAESAAAAAAAAkFIAADBSAAABEgAAAQAAAKBSAABAUgAAAREAAAAAAACwUgAAUFIAAAERAAABAAAAwFIAAGBSAADBCgAAAAAAANBSAABwUgAAwQoAAAEAAADgUgAAgFIAAMEJAAAAAAAA8FIAAJBSAADBCQAAAQAAAABTAACgUgAAoQgAAAAAAAAQUwAAsFIAAKEIAAABAAAAIFMAAMBSAAAhBQAAAAAAADBTAADQUgAAIQUAAAEAAABAUwAA4FIAAEEEAAAAAAAAUFMAAPBSAABBBAAAAQAAAGBTAAAAUwAAoQIAAAAAAABwUwAAEFMAAKECAAABAAAAgFMAACBTAAAhAgAAAAAAAJBTAAAwUwAAIQIAAAEAAACgUwAAQFMAAEEBAAAAAAAAsFMAAFBTAABBAQAAAQAAAMBTAABgUwAAEQEAAAAAAADQUwAAcFMAABEBAAABAAAA4FMAAIBTAACFAAAAAAAAAPBTAACQUwAAhQAAAAEAAAAAVAAAoFMAAEkAAAAAAAAAEFQAALBTAABJAAAAAQAAACBUAADAUwAAJQAAAAAAAAAwVAAA0FMAACUAAAABAAAAQFQAAOBTAAAVAAAAAAAAAFBUAADwUwAAFQAAAAEAAABgVAAAAFQAAAkAAAAAAAAAcFQAABBUAAAJAAAAAQAAAIBUAAAgVAAABQAAAAAAAACQVAAAMFQAAAUAAAABAAAAoFQAAEBUAAABAAAAAAAAAJBUAABQVAAAAQAAAAEAAACgVAAAYFQAAAFWAAAAAAAAsFQAALBUAAABVgAAAQAAAMBUAADAVAAAAAEDAwECAwMFBgcHBgYHBwABAwMBAgMDBQYHBwYGBwcFBgcHBgYHBwgICAgICAgIBQYHBwYGBwcICAgICAgICAECAwMCAgMDBgYHBwYGBwcBAgMDAgIDAwYGBwcGBgcHBgYHBwYGBwcICAgICAgICAYGBwcGBgcHCAgICAgICAgDAwQEAwMEBAcHBwcHBwcHAwMEBAMDBAQHBwcHBwcHBwcHBwcHBwcHCAgICAgICAgHBwcHBwcHBwgICAgICAgIAwMEBAMDBAQHBwcHBwcHBwMDBAQDAwQEBwcHBwcHBwcHBwcHBwcHBwgICAgICAgIBwcHBwcHBwcICAgICAgICAECAwMCAgMDBgYHBwYGBwcBAgMDAgIDAwYGBwcGBgcHBgYHBwYGBwcICAgICAgICAYGBwcGBgcHCAgICAgICAgCAgMDAgIDAwYGBwcGBgcHAgIDAwICAwMGBgcHBgYHBwYGBwcGBgcHCAgICAgICAgGBgcHBgYHBwgICAgICAgIAwMEBAMDBAQHBwcHBwcHBwMDBAQDAwQEBwcHBwcHBwcHBwcHBwcHBwgICAgICAgIBwcHBwcHBwcICAgICAgICAMDBAQDAwQEBwcHBwcHBwcDAwQEAwMEBAcHBwcHBwcHBwcHBwcHBwcICAgICAgICAcHBwcHBwcHCAgICAgICAgAAQUGAQIGBgMDBwcDAwcHAAEFBgECBgYDAwcHAwMHBwMDBwcDAwcHBAQHBwQEBwcDAwcHAwMHBwQEBwcEBAcHAQIGBgICBgYDAwcHAwMHBwECBgYCAgYGAwMHBwMDBwcDAwcHAwMHBwQEBwcEBAcHAwMHBwMDBwcEBAcHBAQHBwUGCAgGBggIBwcICAcHCAgFBggIBgYICAcHCAgHBwgIBwcICAcHCAgHBwgIBwcICAcHCAgHBwgIBwcICAcHCAgGBggIBgYICAcHCAgHBwgIBgYICAYGCAgHBwgIBwcICAcHCAgHBwgIBwcICAcHCAgHBwgIBwcICAcHCAgHBwgIAQIGBgICBgYDAwcHAwMHBwECBgYCAgYGAwMHBwMDBwcDAwcHAwMHBwQEBwcEBAcHAwMHBwMDBwcEBAcHBAQHBwICBgYCAgYGAwMHBwMDBwcCAgYGAgIGBgMDBwcDAwcHAwMHBwMDBwcEBAcHBAQHBwMDBwcDAwcHBAQHBwQEBwcGBggIBgYICAcHCAgHBwgIBgYICAYGCAgHBwgIBwcICAcHCAgHBwgIBwcICAcHCAgHBwgIBwcICAcHCAgHBwgIBgYICAYGCAgHBwgIBwcICAYGCAgGBggIBwcICAcHCAgHBwgIBwcICAcHCAgHBwgIBwcICAcHCAgHBwgIBwcICAABAwMBAgMDBQYHBwYGBwcAAQMDAQIDAwUGBwcGBgcHBQYHBwYGBwcICAgICAgICAUGBwcGBgcHCAgICAgICAgBAgMDAgIDAwYGBwcGBgcHAQIDAwICAwMGBgcHBgYHBwYGBwcGBgcHCAgICAgICAgGBgcHBgYHBwgICAgICAgIAwMEBAMDBAQHBwcHBwcHBwMDBAQDAwQEBwcHBwcHBwcHBwcHBwcHBwgICAgICAgIBwcHBwcHBwcICAgICAgICAMDBAQDAwQEBwcHBwcHBwcDAwQEAwMEBAcHBwcHBwcHBwcHBwcHBwcICAgICAgICAcHBwcHBwcHCAgICAgICAgBAgMDAgIDAwYGBwcGBgcHAQIDAwICAwMGBgcHBgYHBwYGBwcGBgcHCAgICAgICAgGBgcHBgYHBwgICAgICAgIAgIDAwICAwMGBgcHBgYHBwICAwMCAgMDBgYHBwYGBwcGBgcHBgYHBwgICAgICAgIBgYHBwYGBwcICAgICAgICAMDBAQDAwQEBwcHBwcHBwcDAwQEAwMEBAcHBwcHBwcHBwcHBwcHBwcICAgICAgICAcHBwcHBwcHCAgICAgICAgDAwQEAwMEBAcHBwcHBwcHAwMEBAMDBAQHBwcHBwcHBwcHBwcHBwcHCAgICAgICAgHBwcHBwcHBwgICAgICAgIAAMBBAMGBAcBBAIFBAcFBwADAQQDBgQHAQQCBQQHBQcBBAIFBAcFBwIFAgUFBwUHAQQCBQQHBQcCBQIFBQcFBwMGBAcGCAcIBAcFBwcIBwgDBgQHBggHCAQHBQcHCAcIBAcFBwcIBwgFBwUHBwgHCAQHBQcHCAcIBQcFBwcIBwgBBAIFBAcFBwIFAgUFBwUHAQQCBQQHBQcCBQIFBQcFBwIFAgUFBwUHAgUCBQUHBQcCBQIFBQcFBwIFAgUFBwUHBAcFBwcIBwgFBwUHBwgHCAQHBQcHCAcIBQcFBwcIBwgFBwUHBwgHCAUHBQcHCAcIBQcFBwcIBwgFBwUHBwgHCAMGBAcGCAcIBAcFBwcIBwgDBgQHBggHCAQHBQcHCAcIBAcFBwcIBwgFBwUHBwgHCAQHBQcHCAcIBQcFBwcIBwgGCAcICAgICAcIBwgICAgIBggHCAgICAgHCAcICAgICAcIBwgICAgIBwgHCAgICAgHCAcICAgICAcIBwgICAgIBAcFBwcIBwgFBwUHBwgHCAQHBQcHCAcIBQcFBwcIBwgFBwUHBwgHCAUHBQcHCAcIBQcFBwcIBwgFBwUHBwgHCAcIBwgICAgIBwgHCAgICAgHCAcICAgICAcIBwgICAgIBwgHCAgICAgHCAcICAgICAcIBwgICAgIBwgHCAgICAgJCQoKCQkKCgwMDQsMDA0LCQkKCgkJCgoMDAsNDAwLDQwMDQ0MDAsLDAkNCgkMCgsMDAsLDAwNDQwJCwoJDAoNCQkKCgkJCgoMDA0LDAwNCwkJCgoJCQoKDAwLDQwMCw0MDA0NDAwLCwwJDQoJDAoLDAwLCwwMDQ0MCQsKCQwKDQoKCgoKCgoKDQsNCw0LDQsKCgkJCgoJCQ0LDAwNCwwMDQ0NDQsLCwsNCg0KCgsKCw0NDAwLCwwMDQoMCQoLCQwKCgkJCgoJCQsNDAwLDQwMCgoKCgoKCgoLDQsNCw0LDQsLDAwNDQwMCwoMCQoNCQwLCwsLDQ0NDQsKCwoKDQoNAEHZuwELNwEAAQABAAEAAAEBAAABAQABAAEAAQABAAAAAAEBAQEAAAAAAAEAAQAAAAABAQEBAAAAAQABAQEAQZm8AQs3AQABAAEAAQAAAQEAAAEBAAEAAQABAAEAAAAAAQEBAQAAAAAAAQABAAAAAAEBAQEAAAABAAEBAQBB2bwBCwcBAAEAAQABAEHpvAELlQIBAAEAAQABAAAAAAEBAQEAAAAAAAEAAQAAAAABAQEBAAAAAAABAAEBAQAAAQEAAAABAAEAAQABAQEBAQEBAQEAAQABAAEAAQAAAAABAQEBAAEAAAEBAAEAAAAAAQEBAQABAAEBAQEBAgAAAAQAAAAEAAAACAAAAJD/AAAMAAAAGAAAAFL/AAAUAAAAGQAAAFP/AAAUAAAAGgAAAF7/AAAUAAAAGwAAAFz/AAAUAAAAHAAAAF3/AAAUAAAAHQAAAF//AAAUAAAAHgAAAFH/AAACAAAAHwAAAFX/AAAEAAAAIAAAAFf/AAAEAAAAIQAAAFj/AAAQAAAAIgAAAGD/AAAEAAAAIwAAAGH/AAAQAAAAJAAAAJH/AEGIvwELZWP/AAAEAAAAJQAAAGT/AAAUAAAAJgAAAHT/AAAUAAAAJwAAAHj/AAAEAAAAKAAAAFD/AAAEAAAAKQAAAFn/AAAEAAAAKgAAAHX/AAAUAAAAKwAAAHf/AAAUAAAALAAAAAAAAAAUAEGAwAELNS0AAAAuAAAALwAAADAAAAAxAAAAMgAAADMAAAA0AAAAICBQajYAAABweXRmNwAAAGgycGo4AEHAwAELMnJkaGk5AAAAcmxvYzoAAABjY3BiOwAAAHJsY3A8AAAAcGFtYz0AAABmZWRjPgAAAPhiAEGAwQELQRkACwAZGRkAAAAABQAAAAAAAAkAAAAACwAAAAAAAAAAGQAKChkZGQMKBwABAAkLGAAACQYLAAALAAYZAAAAGRkZAEHRwQELIQ4AAAAAAAAAABkACw0ZGRkADQAAAgAJDgAAAAkADgAADgBBi8IBCwEMAEGXwgELFRMAAAAAEwAAAAAJDAAAAAAADAAADABBxcIBCwEQAEHRwgELFQ8AAAAEDwAAAAAJEAAAAAAAEAAAEABB/8IBCwESAEGLwwELHhEAAAAAEQAAAAAJEgAAAAAAEgAAEgAAGgAAABoaGgBBwsMBCw4aAAAAGhoaAAAAAAAACQBB88MBCwEUAEH/wwELFRcAAAAAFwAAAAAJFAAAAAAAFAAAFABBrcQBCwEWAEG5xAELJxUAAAAAFQAAAAAJFgAAAAAAFgAAFgAAMDEyMzQ1Njc4OUFCQ0RFRgBB4MQBCwmQbAEAAAAAAAUAQfTEAQsBaQBBjMUBCwpqAAAAawAAAHhoAEGkxQELAQIAQbTFAQsI//////////8AQfjFAQsBBQBBhMYBCwFsAEGcxgELDmoAAABtAAAAiGgAAAAEAEG0xgELAQEAQcTGAQsF/////wo=";
      return f;
    }
    var wasmBinaryFile;
    function getBinarySync(file) {
      if (file == wasmBinaryFile && wasmBinary) {
        return new Uint8Array(wasmBinary);
      }
      var binary = tryParseAsDataURI(file);
      if (binary) {
        return binary;
      }
      if (readBinary) {
        return readBinary(file);
      }
      throw 'sync fetching of the wasm failed: you can preload it to Module["wasmBinary"] manually, or emcc.py will do that for you when generating HTML (but not JS)';
    }
    function instantiateSync(file, info) {
      var module;
      var binary = getBinarySync(file);
      module = new WebAssembly.Module(binary);
      var instance = new WebAssembly.Instance(module, info);
      return [instance, module];
    }
    function getWasmImports() {
      return {
        a: wasmImports
      };
    }
    function createWasm() {
      var info = getWasmImports();
      function receiveInstance(instance, module) {
        wasmExports = instance.exports;
        wasmMemory = wasmExports["p"];
        updateMemoryViews();
        addOnInit(wasmExports["q"]);
        removeRunDependency("wasm-instantiate");
        return wasmExports;
      }
      addRunDependency("wasm-instantiate");
      if (Module["instantiateWasm"]) {
        try {
          return Module["instantiateWasm"](info, receiveInstance);
        } catch (e) {
          err(`Module.instantiateWasm callback failed with error: ${e}`);
          readyPromiseReject(e);
        }
      }
      if (!wasmBinaryFile) wasmBinaryFile = findWasmBinary();
      var result = instantiateSync(wasmBinaryFile, info);
      return receiveInstance(result[0]);
    }
    var callRuntimeCallbacks = callbacks => {
      while (callbacks.length > 0) {
        callbacks.shift()(Module);
      }
    };
    var noExitRuntime = Module["noExitRuntime"] || true;
    var __emscripten_memcpy_js = (dest, src, num) => HEAPU8.copyWithin(dest, src, src + num);
    function _copy_pixels_1(compG_ptr, nb_pixels) {
      compG_ptr >>= 2;
      const imageData = Module.imageData = new Uint8ClampedArray(nb_pixels);
      const compG = Module.HEAP32.subarray(compG_ptr, compG_ptr + nb_pixels);
      imageData.set(compG);
    }
    function _copy_pixels_3(compR_ptr, compG_ptr, compB_ptr, nb_pixels) {
      compR_ptr >>= 2;
      compG_ptr >>= 2;
      compB_ptr >>= 2;
      const imageData = Module.imageData = new Uint8ClampedArray(nb_pixels * 3);
      const compR = Module.HEAP32.subarray(compR_ptr, compR_ptr + nb_pixels);
      const compG = Module.HEAP32.subarray(compG_ptr, compG_ptr + nb_pixels);
      const compB = Module.HEAP32.subarray(compB_ptr, compB_ptr + nb_pixels);
      for (let i = 0; i < nb_pixels; i++) {
        imageData[3 * i] = compR[i];
        imageData[3 * i + 1] = compG[i];
        imageData[3 * i + 2] = compB[i];
      }
    }
    function _copy_pixels_4(compR_ptr, compG_ptr, compB_ptr, compA_ptr, nb_pixels) {
      compR_ptr >>= 2;
      compG_ptr >>= 2;
      compB_ptr >>= 2;
      compA_ptr >>= 2;
      const imageData = Module.imageData = new Uint8ClampedArray(nb_pixels * 4);
      const compR = Module.HEAP32.subarray(compR_ptr, compR_ptr + nb_pixels);
      const compG = Module.HEAP32.subarray(compG_ptr, compG_ptr + nb_pixels);
      const compB = Module.HEAP32.subarray(compB_ptr, compB_ptr + nb_pixels);
      const compA = Module.HEAP32.subarray(compA_ptr, compA_ptr + nb_pixels);
      for (let i = 0; i < nb_pixels; i++) {
        imageData[4 * i] = compR[i];
        imageData[4 * i + 1] = compG[i];
        imageData[4 * i + 2] = compB[i];
        imageData[4 * i + 3] = compA[i];
      }
    }
    var getHeapMax = () => 2147483648;
    var growMemory = size => {
      var b = wasmMemory.buffer;
      var pages = (size - b.byteLength + 65535) / 65536;
      try {
        wasmMemory.grow(pages);
        updateMemoryViews();
        return 1;
      } catch (e) {}
    };
    var _emscripten_resize_heap = requestedSize => {
      var oldSize = HEAPU8.length;
      requestedSize >>>= 0;
      var maxHeapSize = getHeapMax();
      if (requestedSize > maxHeapSize) {
        return false;
      }
      var alignUp = (x, multiple) => x + (multiple - x % multiple) % multiple;
      for (var cutDown = 1; cutDown <= 4; cutDown *= 2) {
        var overGrownHeapSize = oldSize * (1 + .2 / cutDown);
        overGrownHeapSize = Math.min(overGrownHeapSize, requestedSize + 100663296);
        var newSize = Math.min(maxHeapSize, alignUp(Math.max(requestedSize, overGrownHeapSize), 65536));
        var replacement = growMemory(newSize);
        if (replacement) {
          return true;
        }
      }
      return false;
    };
    var ENV = {};
    var getExecutableName = () => thisProgram || "./this.program";
    var getEnvStrings = () => {
      if (!getEnvStrings.strings) {
        var lang = (typeof navigator == "object" && navigator.languages && navigator.languages[0] || "C").replace("-", "_") + ".UTF-8";
        var env = {
          USER: "web_user",
          LOGNAME: "web_user",
          PATH: "/",
          PWD: "/",
          HOME: "/home/web_user",
          LANG: lang,
          _: getExecutableName()
        };
        for (var x in ENV) {
          if (ENV[x] === undefined) delete env[x];else env[x] = ENV[x];
        }
        var strings = [];
        for (var x in env) {
          strings.push(`${x}=${env[x]}`);
        }
        getEnvStrings.strings = strings;
      }
      return getEnvStrings.strings;
    };
    var stringToAscii = (str, buffer) => {
      for (var i = 0; i < str.length; ++i) {
        HEAP8[buffer++] = str.charCodeAt(i);
      }
      HEAP8[buffer] = 0;
    };
    var _environ_get = (__environ, environ_buf) => {
      var bufSize = 0;
      getEnvStrings().forEach((string, i) => {
        var ptr = environ_buf + bufSize;
        HEAPU32[__environ + i * 4 >> 2] = ptr;
        stringToAscii(string, ptr);
        bufSize += string.length + 1;
      });
      return 0;
    };
    var _environ_sizes_get = (penviron_count, penviron_buf_size) => {
      var strings = getEnvStrings();
      HEAPU32[penviron_count >> 2] = strings.length;
      var bufSize = 0;
      strings.forEach(string => bufSize += string.length + 1);
      HEAPU32[penviron_buf_size >> 2] = bufSize;
      return 0;
    };
    var _fd_close = fd => 52;
    var convertI32PairToI53Checked = (lo, hi) => hi + 2097152 >>> 0 < 4194305 - !!lo ? (lo >>> 0) + hi * 4294967296 : NaN;
    function _fd_seek(fd, offset_low, offset_high, whence, newOffset) {
      var offset = convertI32PairToI53Checked(offset_low, offset_high);
      return 70;
    }
    var printCharBuffers = [null, [], []];
    var UTF8Decoder = typeof TextDecoder != "undefined" ? new TextDecoder("utf8") : undefined;
    var UTF8ArrayToString = (heapOrArray, idx, maxBytesToRead) => {
      var endIdx = idx + maxBytesToRead;
      var endPtr = idx;
      while (heapOrArray[endPtr] && !(endPtr >= endIdx)) ++endPtr;
      if (endPtr - idx > 16 && heapOrArray.buffer && UTF8Decoder) {
        return UTF8Decoder.decode(heapOrArray.subarray(idx, endPtr));
      }
      var str = "";
      while (idx < endPtr) {
        var u0 = heapOrArray[idx++];
        if (!(u0 & 128)) {
          str += String.fromCharCode(u0);
          continue;
        }
        var u1 = heapOrArray[idx++] & 63;
        if ((u0 & 224) == 192) {
          str += String.fromCharCode((u0 & 31) << 6 | u1);
          continue;
        }
        var u2 = heapOrArray[idx++] & 63;
        if ((u0 & 240) == 224) {
          u0 = (u0 & 15) << 12 | u1 << 6 | u2;
        } else {
          u0 = (u0 & 7) << 18 | u1 << 12 | u2 << 6 | heapOrArray[idx++] & 63;
        }
        if (u0 < 65536) {
          str += String.fromCharCode(u0);
        } else {
          var ch = u0 - 65536;
          str += String.fromCharCode(55296 | ch >> 10, 56320 | ch & 1023);
        }
      }
      return str;
    };
    var printChar = (stream, curr) => {
      var buffer = printCharBuffers[stream];
      if (curr === 0 || curr === 10) {
        (stream === 1 ? out : err)(UTF8ArrayToString(buffer, 0));
        buffer.length = 0;
      } else {
        buffer.push(curr);
      }
    };
    var UTF8ToString = (ptr, maxBytesToRead) => ptr ? UTF8ArrayToString(HEAPU8, ptr, maxBytesToRead) : "";
    var _fd_write = (fd, iov, iovcnt, pnum) => {
      var num = 0;
      for (var i = 0; i < iovcnt; i++) {
        var ptr = HEAPU32[iov >> 2];
        var len = HEAPU32[iov + 4 >> 2];
        iov += 8;
        for (var j = 0; j < len; j++) {
          printChar(fd, HEAPU8[ptr + j]);
        }
        num += len;
      }
      HEAPU32[pnum >> 2] = num;
      return 0;
    };
    function _gray_to_rgba(compG_ptr, nb_pixels) {
      compG_ptr >>= 2;
      const imageData = Module.imageData = new Uint8ClampedArray(nb_pixels * 4);
      const compG = Module.HEAP32.subarray(compG_ptr, compG_ptr + nb_pixels);
      for (let i = 0; i < nb_pixels; i++) {
        imageData[4 * i] = imageData[4 * i + 1] = imageData[4 * i + 2] = compG[i];
        imageData[4 * i + 3] = 255;
      }
    }
    function _graya_to_rgba(compG_ptr, compA_ptr, nb_pixels) {
      compG_ptr >>= 2;
      compA_ptr >>= 2;
      const imageData = Module.imageData = new Uint8ClampedArray(nb_pixels * 4);
      const compG = Module.HEAP32.subarray(compG_ptr, compG_ptr + nb_pixels);
      const compA = Module.HEAP32.subarray(compA_ptr, compA_ptr + nb_pixels);
      for (let i = 0; i < nb_pixels; i++) {
        imageData[4 * i] = imageData[4 * i + 1] = imageData[4 * i + 2] = compG[i];
        imageData[4 * i + 3] = compA[i];
      }
    }
    function _jsPrintWarning(message_ptr) {
      const message = UTF8ToString(message_ptr);
      (Module.warn || console.warn)(`OpenJPEG: ${message}`);
    }
    function _rgb_to_rgba(compR_ptr, compG_ptr, compB_ptr, nb_pixels) {
      compR_ptr >>= 2;
      compG_ptr >>= 2;
      compB_ptr >>= 2;
      const imageData = Module.imageData = new Uint8ClampedArray(nb_pixels * 4);
      const compR = Module.HEAP32.subarray(compR_ptr, compR_ptr + nb_pixels);
      const compG = Module.HEAP32.subarray(compG_ptr, compG_ptr + nb_pixels);
      const compB = Module.HEAP32.subarray(compB_ptr, compB_ptr + nb_pixels);
      for (let i = 0; i < nb_pixels; i++) {
        imageData[4 * i] = compR[i];
        imageData[4 * i + 1] = compG[i];
        imageData[4 * i + 2] = compB[i];
        imageData[4 * i + 3] = 255;
      }
    }
    function _storeErrorMessage(message_ptr) {
      const message = UTF8ToString(message_ptr);
      if (!Module.errorMessages) {
        Module.errorMessages = message;
      } else {
        Module.errorMessages += "\n" + message;
      }
    }
    var wasmImports = {
      c: __emscripten_memcpy_js,
      g: _copy_pixels_1,
      f: _copy_pixels_3,
      e: _copy_pixels_4,
      k: _emscripten_resize_heap,
      l: _environ_get,
      m: _environ_sizes_get,
      n: _fd_close,
      j: _fd_seek,
      b: _fd_write,
      o: _gray_to_rgba,
      i: _graya_to_rgba,
      d: _jsPrintWarning,
      h: _rgb_to_rgba,
      a: _storeErrorMessage
    };
    var wasmExports = createWasm();
    var ___wasm_call_ctors = wasmExports["q"];
    var _malloc = Module["_malloc"] = wasmExports["r"];
    var _free = Module["_free"] = wasmExports["s"];
    var _jp2_decode = Module["_jp2_decode"] = wasmExports["u"];
    var calledRun;
    dependenciesFulfilled = function runCaller() {
      if (!calledRun) run();
      if (!calledRun) dependenciesFulfilled = runCaller;
    };
    function run() {
      if (runDependencies > 0) {
        return;
      }
      preRun();
      if (runDependencies > 0) {
        return;
      }
      function doRun() {
        if (calledRun) return;
        calledRun = true;
        Module["calledRun"] = true;
        if (ABORT) return;
        initRuntime();
        readyPromiseResolve(Module);
        if (Module["onRuntimeInitialized"]) Module["onRuntimeInitialized"]();
        postRun();
      }
      if (Module["setStatus"]) {
        Module["setStatus"]("Running...");
        setTimeout(function () {
          setTimeout(function () {
            Module["setStatus"]("");
          }, 1);
          doRun();
        }, 1);
      } else {
        doRun();
      }
    }
    if (Module["preInit"]) {
      if (typeof Module["preInit"] == "function") Module["preInit"] = [Module["preInit"]];
      while (Module["preInit"].length > 0) {
        Module["preInit"].pop()();
      }
    }
    run();
    moduleRtn = Module;
    return moduleRtn;
  };
})();
/* harmony default export */ const openjpeg = (OpenJPEG);
;// CONCATENATED MODULE: ./src/core/jpx.js



class JpxError extends BaseException {
  constructor(msg) {
    super(msg, "JpxError");
  }
}
class JpxImage {
  static #module = null;
  static decode(data, decoderOptions) {
    decoderOptions ||= {};
    this.#module ||= openjpeg({
      warn: warn
    });
    const imageData = this.#module.decode(data, decoderOptions);
    if (typeof imageData === "string") {
      throw new JpxError(imageData);
    }
    return imageData;
  }
  static cleanup() {
    this.#module = null;
  }
  static parseImageProperties(stream) {
    let newByte = stream.getByte();
    while (newByte >= 0) {
      const oldByte = newByte;
      newByte = stream.getByte();
      const code = oldByte << 8 | newByte;
      if (code === 0xff51) {
        stream.skip(4);
        const Xsiz = stream.getInt32() >>> 0;
        const Ysiz = stream.getInt32() >>> 0;
        const XOsiz = stream.getInt32() >>> 0;
        const YOsiz = stream.getInt32() >>> 0;
        stream.skip(16);
        const Csiz = stream.getUint16();
        return {
          width: Xsiz - XOsiz,
          height: Ysiz - YOsiz,
          bitsPerComponent: 8,
          componentsCount: Csiz
        };
      }
    }
    throw new JpxError("No size marker found in JPX stream");
  }
}

;// CONCATENATED MODULE: ./src/core/jpx_stream.js



class JpxStream extends DecodeStream {
  constructor(stream, maybeLength, params) {
    super(maybeLength);
    this.stream = stream;
    this.dict = stream.dict;
    this.maybeLength = maybeLength;
    this.params = params;
  }
  get bytes() {
    return shadow(this, "bytes", this.stream.getBytes(this.maybeLength));
  }
  ensureBuffer(requested) {}
  readBlock(decoderOptions) {
    this.decodeImage(null, decoderOptions);
  }
  decodeImage(bytes, decoderOptions) {
    if (this.eof) {
      return this.buffer;
    }
    bytes ||= this.bytes;
    this.buffer = JpxImage.decode(bytes, decoderOptions);
    this.bufferLength = this.buffer.length;
    this.eof = true;
    return this.buffer;
  }
  get canAsyncDecodeImageFromBuffer() {
    return this.stream.isAsync;
  }
}

;// CONCATENATED MODULE: ./src/core/lzw_stream.js

class LZWStream extends DecodeStream {
  constructor(str, maybeLength, earlyChange) {
    super(maybeLength);
    this.str = str;
    this.dict = str.dict;
    this.cachedData = 0;
    this.bitsCached = 0;
    const maxLzwDictionarySize = 4096;
    const lzwState = {
      earlyChange,
      codeLength: 9,
      nextCode: 258,
      dictionaryValues: new Uint8Array(maxLzwDictionarySize),
      dictionaryLengths: new Uint16Array(maxLzwDictionarySize),
      dictionaryPrevCodes: new Uint16Array(maxLzwDictionarySize),
      currentSequence: new Uint8Array(maxLzwDictionarySize),
      currentSequenceLength: 0
    };
    for (let i = 0; i < 256; ++i) {
      lzwState.dictionaryValues[i] = i;
      lzwState.dictionaryLengths[i] = 1;
    }
    this.lzwState = lzwState;
  }
  readBits(n) {
    let bitsCached = this.bitsCached;
    let cachedData = this.cachedData;
    while (bitsCached < n) {
      const c = this.str.getByte();
      if (c === -1) {
        this.eof = true;
        return null;
      }
      cachedData = cachedData << 8 | c;
      bitsCached += 8;
    }
    this.bitsCached = bitsCached -= n;
    this.cachedData = cachedData;
    this.lastCode = null;
    return cachedData >>> bitsCached & (1 << n) - 1;
  }
  readBlock() {
    const blockSize = 512,
      decodedSizeDelta = blockSize;
    let estimatedDecodedSize = blockSize * 2;
    let i, j, q;
    const lzwState = this.lzwState;
    if (!lzwState) {
      return;
    }
    const earlyChange = lzwState.earlyChange;
    let nextCode = lzwState.nextCode;
    const dictionaryValues = lzwState.dictionaryValues;
    const dictionaryLengths = lzwState.dictionaryLengths;
    const dictionaryPrevCodes = lzwState.dictionaryPrevCodes;
    let codeLength = lzwState.codeLength;
    let prevCode = lzwState.prevCode;
    const currentSequence = lzwState.currentSequence;
    let currentSequenceLength = lzwState.currentSequenceLength;
    let decodedLength = 0;
    let currentBufferLength = this.bufferLength;
    let buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize);
    for (i = 0; i < blockSize; i++) {
      const code = this.readBits(codeLength);
      const hasPrev = currentSequenceLength > 0;
      if (code < 256) {
        currentSequence[0] = code;
        currentSequenceLength = 1;
      } else if (code >= 258) {
        if (code < nextCode) {
          currentSequenceLength = dictionaryLengths[code];
          for (j = currentSequenceLength - 1, q = code; j >= 0; j--) {
            currentSequence[j] = dictionaryValues[q];
            q = dictionaryPrevCodes[q];
          }
        } else {
          currentSequence[currentSequenceLength++] = currentSequence[0];
        }
      } else if (code === 256) {
        codeLength = 9;
        nextCode = 258;
        currentSequenceLength = 0;
        continue;
      } else {
        this.eof = true;
        delete this.lzwState;
        break;
      }
      if (hasPrev) {
        dictionaryPrevCodes[nextCode] = prevCode;
        dictionaryLengths[nextCode] = dictionaryLengths[prevCode] + 1;
        dictionaryValues[nextCode] = currentSequence[0];
        nextCode++;
        codeLength = nextCode + earlyChange & nextCode + earlyChange - 1 ? codeLength : Math.min(Math.log(nextCode + earlyChange) / 0.6931471805599453 + 1, 12) | 0;
      }
      prevCode = code;
      decodedLength += currentSequenceLength;
      if (estimatedDecodedSize < decodedLength) {
        do {
          estimatedDecodedSize += decodedSizeDelta;
        } while (estimatedDecodedSize < decodedLength);
        buffer = this.ensureBuffer(this.bufferLength + estimatedDecodedSize);
      }
      for (j = 0; j < currentSequenceLength; j++) {
        buffer[currentBufferLength++] = currentSequence[j];
      }
    }
    lzwState.nextCode = nextCode;
    lzwState.codeLength = codeLength;
    lzwState.prevCode = prevCode;
    lzwState.currentSequenceLength = currentSequenceLength;
    this.bufferLength = currentBufferLength;
  }
}

;// CONCATENATED MODULE: ./src/core/predictor_stream.js



class PredictorStream extends DecodeStream {
  constructor(str, maybeLength, params) {
    super(maybeLength);
    if (!(params instanceof Dict)) {
      return str;
    }
    const predictor = this.predictor = params.get("Predictor") || 1;
    if (predictor <= 1) {
      return str;
    }
    if (predictor !== 2 && (predictor < 10 || predictor > 15)) {
      throw new FormatError(`Unsupported predictor: ${predictor}`);
    }
    this.readBlock = predictor === 2 ? this.readBlockTiff : this.readBlockPng;
    this.str = str;
    this.dict = str.dict;
    const colors = this.colors = params.get("Colors") || 1;
    const bits = this.bits = params.get("BPC", "BitsPerComponent") || 8;
    const columns = this.columns = params.get("Columns") || 1;
    this.pixBytes = colors * bits + 7 >> 3;
    this.rowBytes = columns * colors * bits + 7 >> 3;
    return this;
  }
  readBlockTiff() {
    const rowBytes = this.rowBytes;
    const bufferLength = this.bufferLength;
    const buffer = this.ensureBuffer(bufferLength + rowBytes);
    const bits = this.bits;
    const colors = this.colors;
    const rawBytes = this.str.getBytes(rowBytes);
    this.eof = !rawBytes.length;
    if (this.eof) {
      return;
    }
    let inbuf = 0,
      outbuf = 0;
    let inbits = 0,
      outbits = 0;
    let pos = bufferLength;
    let i;
    if (bits === 1 && colors === 1) {
      for (i = 0; i < rowBytes; ++i) {
        let c = rawBytes[i] ^ inbuf;
        c ^= c >> 1;
        c ^= c >> 2;
        c ^= c >> 4;
        inbuf = (c & 1) << 7;
        buffer[pos++] = c;
      }
    } else if (bits === 8) {
      for (i = 0; i < colors; ++i) {
        buffer[pos++] = rawBytes[i];
      }
      for (; i < rowBytes; ++i) {
        buffer[pos] = buffer[pos - colors] + rawBytes[i];
        pos++;
      }
    } else if (bits === 16) {
      const bytesPerPixel = colors * 2;
      for (i = 0; i < bytesPerPixel; ++i) {
        buffer[pos++] = rawBytes[i];
      }
      for (; i < rowBytes; i += 2) {
        const sum = ((rawBytes[i] & 0xff) << 8) + (rawBytes[i + 1] & 0xff) + ((buffer[pos - bytesPerPixel] & 0xff) << 8) + (buffer[pos - bytesPerPixel + 1] & 0xff);
        buffer[pos++] = sum >> 8 & 0xff;
        buffer[pos++] = sum & 0xff;
      }
    } else {
      const compArray = new Uint8Array(colors + 1);
      const bitMask = (1 << bits) - 1;
      let j = 0,
        k = bufferLength;
      const columns = this.columns;
      for (i = 0; i < columns; ++i) {
        for (let kk = 0; kk < colors; ++kk) {
          if (inbits < bits) {
            inbuf = inbuf << 8 | rawBytes[j++] & 0xff;
            inbits += 8;
          }
          compArray[kk] = compArray[kk] + (inbuf >> inbits - bits) & bitMask;
          inbits -= bits;
          outbuf = outbuf << bits | compArray[kk];
          outbits += bits;
          if (outbits >= 8) {
            buffer[k++] = outbuf >> outbits - 8 & 0xff;
            outbits -= 8;
          }
        }
      }
      if (outbits > 0) {
        buffer[k++] = (outbuf << 8 - outbits) + (inbuf & (1 << 8 - outbits) - 1);
      }
    }
    this.bufferLength += rowBytes;
  }
  readBlockPng() {
    const rowBytes = this.rowBytes;
    const pixBytes = this.pixBytes;
    const predictor = this.str.getByte();
    const rawBytes = this.str.getBytes(rowBytes);
    this.eof = !rawBytes.length;
    if (this.eof) {
      return;
    }
    const bufferLength = this.bufferLength;
    const buffer = this.ensureBuffer(bufferLength + rowBytes);
    let prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength);
    if (prevRow.length === 0) {
      prevRow = new Uint8Array(rowBytes);
    }
    let i,
      j = bufferLength,
      up,
      c;
    switch (predictor) {
      case 0:
        for (i = 0; i < rowBytes; ++i) {
          buffer[j++] = rawBytes[i];
        }
        break;
      case 1:
        for (i = 0; i < pixBytes; ++i) {
          buffer[j++] = rawBytes[i];
        }
        for (; i < rowBytes; ++i) {
          buffer[j] = buffer[j - pixBytes] + rawBytes[i] & 0xff;
          j++;
        }
        break;
      case 2:
        for (i = 0; i < rowBytes; ++i) {
          buffer[j++] = prevRow[i] + rawBytes[i] & 0xff;
        }
        break;
      case 3:
        for (i = 0; i < pixBytes; ++i) {
          buffer[j++] = (prevRow[i] >> 1) + rawBytes[i];
        }
        for (; i < rowBytes; ++i) {
          buffer[j] = (prevRow[i] + buffer[j - pixBytes] >> 1) + rawBytes[i] & 0xff;
          j++;
        }
        break;
      case 4:
        for (i = 0; i < pixBytes; ++i) {
          up = prevRow[i];
          c = rawBytes[i];
          buffer[j++] = up + c;
        }
        for (; i < rowBytes; ++i) {
          up = prevRow[i];
          const upLeft = prevRow[i - pixBytes];
          const left = buffer[j - pixBytes];
          const p = left + up - upLeft;
          let pa = p - left;
          if (pa < 0) {
            pa = -pa;
          }
          let pb = p - up;
          if (pb < 0) {
            pb = -pb;
          }
          let pc = p - upLeft;
          if (pc < 0) {
            pc = -pc;
          }
          c = rawBytes[i];
          if (pa <= pb && pa <= pc) {
            buffer[j++] = left + c;
          } else if (pb <= pc) {
            buffer[j++] = up + c;
          } else {
            buffer[j++] = upLeft + c;
          }
        }
        break;
      default:
        throw new FormatError(`Unsupported predictor: ${predictor}`);
    }
    this.bufferLength += rowBytes;
  }
}

;// CONCATENATED MODULE: ./src/core/run_length_stream.js

class RunLengthStream extends DecodeStream {
  constructor(str, maybeLength) {
    super(maybeLength);
    this.str = str;
    this.dict = str.dict;
  }
  readBlock() {
    const repeatHeader = this.str.getBytes(2);
    if (!repeatHeader || repeatHeader.length < 2 || repeatHeader[0] === 128) {
      this.eof = true;
      return;
    }
    let buffer;
    let bufferLength = this.bufferLength;
    let n = repeatHeader[0];
    if (n < 128) {
      buffer = this.ensureBuffer(bufferLength + n + 1);
      buffer[bufferLength++] = repeatHeader[1];
      if (n > 0) {
        const source = this.str.getBytes(n);
        buffer.set(source, bufferLength);
        bufferLength += n;
      }
    } else {
      n = 257 - n;
      const b = repeatHeader[1];
      buffer = this.ensureBuffer(bufferLength + n + 1);
      for (let i = 0; i < n; i++) {
        buffer[bufferLength++] = b;
      }
    }
    this.bufferLength = bufferLength;
  }
}

;// CONCATENATED MODULE: ./src/core/parser.js














const MAX_LENGTH_TO_CACHE = 1000;
function getInlineImageCacheKey(bytes) {
  const strBuf = [],
    ii = bytes.length;
  let i = 0;
  while (i < ii - 1) {
    strBuf.push(bytes[i++] << 8 | bytes[i++]);
  }
  if (i < ii) {
    strBuf.push(bytes[i]);
  }
  return ii + "_" + String.fromCharCode.apply(null, strBuf);
}
class Parser {
  constructor({
    lexer,
    xref,
    allowStreams = false,
    recoveryMode = false
  }) {
    this.lexer = lexer;
    this.xref = xref;
    this.allowStreams = allowStreams;
    this.recoveryMode = recoveryMode;
    this.imageCache = Object.create(null);
    this._imageId = 0;
    this.refill();
  }
  refill() {
    this.buf1 = this.lexer.getObj();
    this.buf2 = this.lexer.getObj();
  }
  shift() {
    if (this.buf2 instanceof Cmd && this.buf2.cmd === "ID") {
      this.buf1 = this.buf2;
      this.buf2 = null;
    } else {
      this.buf1 = this.buf2;
      this.buf2 = this.lexer.getObj();
    }
  }
  tryShift() {
    try {
      this.shift();
      return true;
    } catch (e) {
      if (e instanceof MissingDataException) {
        throw e;
      }
      return false;
    }
  }
  getObj(cipherTransform = null) {
    const buf1 = this.buf1;
    this.shift();
    if (buf1 instanceof Cmd) {
      switch (buf1.cmd) {
        case "BI":
          return this.makeInlineImage(cipherTransform);
        case "[":
          const array = [];
          while (!isCmd(this.buf1, "]") && this.buf1 !== EOF) {
            array.push(this.getObj(cipherTransform));
          }
          if (this.buf1 === EOF) {
            if (this.recoveryMode) {
              return array;
            }
            throw new ParserEOFException("End of file inside array.");
          }
          this.shift();
          return array;
        case "<<":
          const dict = new Dict(this.xref);
          while (!isCmd(this.buf1, ">>") && this.buf1 !== EOF) {
            if (!(this.buf1 instanceof Name)) {
              info("Malformed dictionary: key must be a name object");
              this.shift();
              continue;
            }
            const key = this.buf1.name;
            this.shift();
            if (this.buf1 === EOF) {
              break;
            }
            dict.set(key, this.getObj(cipherTransform));
          }
          if (this.buf1 === EOF) {
            if (this.recoveryMode) {
              return dict;
            }
            throw new ParserEOFException("End of file inside dictionary.");
          }
          if (isCmd(this.buf2, "stream")) {
            return this.allowStreams ? this.makeStream(dict, cipherTransform) : dict;
          }
          this.shift();
          return dict;
        default:
          return buf1;
      }
    }
    if (Number.isInteger(buf1)) {
      if (Number.isInteger(this.buf1) && isCmd(this.buf2, "R")) {
        const ref = Ref.get(buf1, this.buf1);
        this.shift();
        this.shift();
        return ref;
      }
      return buf1;
    }
    if (typeof buf1 === "string") {
      if (cipherTransform) {
        return cipherTransform.decryptString(buf1);
      }
      return buf1;
    }
    return buf1;
  }
  findDefaultInlineStreamEnd(stream) {
    const E = 0x45,
      I = 0x49,
      SPACE = 0x20,
      LF = 0xa,
      CR = 0xd,
      NUL = 0x0;
    const {
        knownCommands
      } = this.lexer,
      startPos = stream.pos,
      n = 15;
    let state = 0,
      ch,
      maybeEIPos;
    while ((ch = stream.getByte()) !== -1) {
      if (state === 0) {
        state = ch === E ? 1 : 0;
      } else if (state === 1) {
        state = ch === I ? 2 : 0;
      } else {
        if (ch === SPACE || ch === LF || ch === CR) {
          maybeEIPos = stream.pos;
          const followingBytes = stream.peekBytes(n);
          const ii = followingBytes.length;
          if (ii === 0) {
            break;
          }
          for (let i = 0; i < ii; i++) {
            ch = followingBytes[i];
            if (ch === NUL && followingBytes[i + 1] !== NUL) {
              continue;
            }
            if (ch !== LF && ch !== CR && (ch < SPACE || ch > 0x7f)) {
              state = 0;
              break;
            }
          }
          if (state !== 2) {
            continue;
          }
          if (!knownCommands) {
            warn("findDefaultInlineStreamEnd - `lexer.knownCommands` is undefined.");
            continue;
          }
          const tmpLexer = new Lexer(new Stream(followingBytes.slice()), knownCommands);
          tmpLexer._hexStringWarn = () => {};
          let numArgs = 0;
          while (true) {
            const nextObj = tmpLexer.getObj();
            if (nextObj === EOF) {
              state = 0;
              break;
            }
            if (nextObj instanceof Cmd) {
              const knownCommand = knownCommands[nextObj.cmd];
              if (!knownCommand) {
                state = 0;
                break;
              } else if (knownCommand.variableArgs ? numArgs <= knownCommand.numArgs : numArgs === knownCommand.numArgs) {
                break;
              }
              numArgs = 0;
              continue;
            }
            numArgs++;
          }
          if (state === 2) {
            break;
          }
        } else {
          state = 0;
        }
      }
    }
    if (ch === -1) {
      warn("findDefaultInlineStreamEnd: " + "Reached the end of the stream without finding a valid EI marker");
      if (maybeEIPos) {
        warn('... trying to recover by using the last "EI" occurrence.');
        stream.skip(-(stream.pos - maybeEIPos));
      }
    }
    let endOffset = 4;
    stream.skip(-endOffset);
    ch = stream.peekByte();
    stream.skip(endOffset);
    if (!isWhiteSpace(ch)) {
      endOffset--;
    }
    return stream.pos - endOffset - startPos;
  }
  findDCTDecodeInlineStreamEnd(stream) {
    const startPos = stream.pos;
    let foundEOI = false,
      b,
      markerLength;
    while ((b = stream.getByte()) !== -1) {
      if (b !== 0xff) {
        continue;
      }
      switch (stream.getByte()) {
        case 0x00:
          break;
        case 0xff:
          stream.skip(-1);
          break;
        case 0xd9:
          foundEOI = true;
          break;
        case 0xc0:
        case 0xc1:
        case 0xc2:
        case 0xc3:
        case 0xc5:
        case 0xc6:
        case 0xc7:
        case 0xc9:
        case 0xca:
        case 0xcb:
        case 0xcd:
        case 0xce:
        case 0xcf:
        case 0xc4:
        case 0xcc:
        case 0xda:
        case 0xdb:
        case 0xdc:
        case 0xdd:
        case 0xde:
        case 0xdf:
        case 0xe0:
        case 0xe1:
        case 0xe2:
        case 0xe3:
        case 0xe4:
        case 0xe5:
        case 0xe6:
        case 0xe7:
        case 0xe8:
        case 0xe9:
        case 0xea:
        case 0xeb:
        case 0xec:
        case 0xed:
        case 0xee:
        case 0xef:
        case 0xfe:
          markerLength = stream.getUint16();
          if (markerLength > 2) {
            stream.skip(markerLength - 2);
          } else {
            stream.skip(-2);
          }
          break;
      }
      if (foundEOI) {
        break;
      }
    }
    const length = stream.pos - startPos;
    if (b === -1) {
      warn("Inline DCTDecode image stream: " + "EOI marker not found, searching for /EI/ instead.");
      stream.skip(-length);
      return this.findDefaultInlineStreamEnd(stream);
    }
    this.inlineStreamSkipEI(stream);
    return length;
  }
  findASCII85DecodeInlineStreamEnd(stream) {
    const TILDE = 0x7e,
      GT = 0x3e;
    const startPos = stream.pos;
    let ch;
    while ((ch = stream.getByte()) !== -1) {
      if (ch === TILDE) {
        const tildePos = stream.pos;
        ch = stream.peekByte();
        while (isWhiteSpace(ch)) {
          stream.skip();
          ch = stream.peekByte();
        }
        if (ch === GT) {
          stream.skip();
          break;
        }
        if (stream.pos > tildePos) {
          const maybeEI = stream.peekBytes(2);
          if (maybeEI[0] === 0x45 && maybeEI[1] === 0x49) {
            break;
          }
        }
      }
    }
    const length = stream.pos - startPos;
    if (ch === -1) {
      warn("Inline ASCII85Decode image stream: " + "EOD marker not found, searching for /EI/ instead.");
      stream.skip(-length);
      return this.findDefaultInlineStreamEnd(stream);
    }
    this.inlineStreamSkipEI(stream);
    return length;
  }
  findASCIIHexDecodeInlineStreamEnd(stream) {
    const GT = 0x3e;
    const startPos = stream.pos;
    let ch;
    while ((ch = stream.getByte()) !== -1) {
      if (ch === GT) {
        break;
      }
    }
    const length = stream.pos - startPos;
    if (ch === -1) {
      warn("Inline ASCIIHexDecode image stream: " + "EOD marker not found, searching for /EI/ instead.");
      stream.skip(-length);
      return this.findDefaultInlineStreamEnd(stream);
    }
    this.inlineStreamSkipEI(stream);
    return length;
  }
  inlineStreamSkipEI(stream) {
    const E = 0x45,
      I = 0x49;
    let state = 0,
      ch;
    while ((ch = stream.getByte()) !== -1) {
      if (state === 0) {
        state = ch === E ? 1 : 0;
      } else if (state === 1) {
        state = ch === I ? 2 : 0;
      } else if (state === 2) {
        break;
      }
    }
  }
  makeInlineImage(cipherTransform) {
    const lexer = this.lexer;
    const stream = lexer.stream;
    const dictMap = Object.create(null);
    let dictLength;
    while (!isCmd(this.buf1, "ID") && this.buf1 !== EOF) {
      if (!(this.buf1 instanceof Name)) {
        throw new FormatError("Dictionary key must be a name object");
      }
      const key = this.buf1.name;
      this.shift();
      if (this.buf1 === EOF) {
        break;
      }
      dictMap[key] = this.getObj(cipherTransform);
    }
    if (lexer.beginInlineImagePos !== -1) {
      dictLength = stream.pos - lexer.beginInlineImagePos;
    }
    const filter = this.xref.fetchIfRef(dictMap.F || dictMap.Filter);
    let filterName;
    if (filter instanceof Name) {
      filterName = filter.name;
    } else if (Array.isArray(filter)) {
      const filterZero = this.xref.fetchIfRef(filter[0]);
      if (filterZero instanceof Name) {
        filterName = filterZero.name;
      }
    }
    const startPos = stream.pos;
    let length;
    switch (filterName) {
      case "DCT":
      case "DCTDecode":
        length = this.findDCTDecodeInlineStreamEnd(stream);
        break;
      case "A85":
      case "ASCII85Decode":
        length = this.findASCII85DecodeInlineStreamEnd(stream);
        break;
      case "AHx":
      case "ASCIIHexDecode":
        length = this.findASCIIHexDecodeInlineStreamEnd(stream);
        break;
      default:
        length = this.findDefaultInlineStreamEnd(stream);
    }
    let cacheKey;
    if (length < MAX_LENGTH_TO_CACHE && dictLength > 0) {
      const initialStreamPos = stream.pos;
      stream.pos = lexer.beginInlineImagePos;
      cacheKey = getInlineImageCacheKey(stream.getBytes(dictLength + length));
      stream.pos = initialStreamPos;
      const cacheEntry = this.imageCache[cacheKey];
      if (cacheEntry !== undefined) {
        this.buf2 = Cmd.get("EI");
        this.shift();
        cacheEntry.reset();
        return cacheEntry;
      }
    }
    const dict = new Dict(this.xref);
    for (const key in dictMap) {
      dict.set(key, dictMap[key]);
    }
    let imageStream = stream.makeSubStream(startPos, length, dict);
    if (cipherTransform) {
      imageStream = cipherTransform.createStream(imageStream, length);
    }
    imageStream = this.filter(imageStream, dict, length);
    imageStream.dict = dict;
    if (cacheKey !== undefined) {
      imageStream.cacheKey = `inline_img_${++this._imageId}`;
      this.imageCache[cacheKey] = imageStream;
    }
    this.buf2 = Cmd.get("EI");
    this.shift();
    return imageStream;
  }
  #findStreamLength(startPos) {
    const {
      stream
    } = this.lexer;
    stream.pos = startPos;
    const SCAN_BLOCK_LENGTH = 2048;
    const signatureLength = "endstream".length;
    const END_SIGNATURE = new Uint8Array([0x65, 0x6e, 0x64]);
    const endLength = END_SIGNATURE.length;
    const PARTIAL_SIGNATURE = [new Uint8Array([0x73, 0x74, 0x72, 0x65, 0x61, 0x6d]), new Uint8Array([0x73, 0x74, 0x65, 0x61, 0x6d]), new Uint8Array([0x73, 0x74, 0x72, 0x65, 0x61])];
    const normalLength = signatureLength - endLength;
    while (stream.pos < stream.end) {
      const scanBytes = stream.peekBytes(SCAN_BLOCK_LENGTH);
      const scanLength = scanBytes.length - signatureLength;
      if (scanLength <= 0) {
        break;
      }
      let pos = 0;
      while (pos < scanLength) {
        let j = 0;
        while (j < endLength && scanBytes[pos + j] === END_SIGNATURE[j]) {
          j++;
        }
        if (j >= endLength) {
          let found = false;
          for (const part of PARTIAL_SIGNATURE) {
            const partLen = part.length;
            let k = 0;
            while (k < partLen && scanBytes[pos + j + k] === part[k]) {
              k++;
            }
            if (k >= normalLength) {
              found = true;
              break;
            }
            if (k >= partLen) {
              const lastByte = scanBytes[pos + j + k];
              if (isWhiteSpace(lastByte)) {
                info(`Found "${bytesToString([...END_SIGNATURE, ...part])}" when ` + "searching for endstream command.");
                found = true;
              }
              break;
            }
          }
          if (found) {
            stream.pos += pos;
            return stream.pos - startPos;
          }
        }
        pos++;
      }
      stream.pos += scanLength;
    }
    return -1;
  }
  makeStream(dict, cipherTransform) {
    const lexer = this.lexer;
    let stream = lexer.stream;
    lexer.skipToNextLine();
    const startPos = stream.pos - 1;
    let length = dict.get("Length");
    if (!Number.isInteger(length)) {
      info(`Bad length "${length && length.toString()}" in stream.`);
      length = 0;
    }
    stream.pos = startPos + length;
    lexer.nextChar();
    if (this.tryShift() && isCmd(this.buf2, "endstream")) {
      this.shift();
    } else {
      length = this.#findStreamLength(startPos);
      if (length < 0) {
        throw new FormatError("Missing endstream command.");
      }
      lexer.nextChar();
      this.shift();
      this.shift();
    }
    this.shift();
    stream = stream.makeSubStream(startPos, length, dict);
    if (cipherTransform) {
      stream = cipherTransform.createStream(stream, length);
    }
    stream = this.filter(stream, dict, length);
    stream.dict = dict;
    return stream;
  }
  filter(stream, dict, length) {
    let filter = dict.get("F", "Filter");
    let params = dict.get("DP", "DecodeParms");
    if (filter instanceof Name) {
      if (Array.isArray(params)) {
        warn("/DecodeParms should not be an Array, when /Filter is a Name.");
      }
      return this.makeFilter(stream, filter.name, length, params);
    }
    let maybeLength = length;
    if (Array.isArray(filter)) {
      const filterArray = filter;
      const paramsArray = params;
      for (let i = 0, ii = filterArray.length; i < ii; ++i) {
        filter = this.xref.fetchIfRef(filterArray[i]);
        if (!(filter instanceof Name)) {
          throw new FormatError(`Bad filter name "${filter}"`);
        }
        params = null;
        if (Array.isArray(paramsArray) && i in paramsArray) {
          params = this.xref.fetchIfRef(paramsArray[i]);
        }
        stream = this.makeFilter(stream, filter.name, maybeLength, params);
        maybeLength = null;
      }
    }
    return stream;
  }
  makeFilter(stream, name, maybeLength, params) {
    if (maybeLength === 0) {
      warn(`Empty "${name}" stream.`);
      return new NullStream();
    }
    try {
      switch (name) {
        case "Fl":
        case "FlateDecode":
          if (params) {
            return new PredictorStream(new FlateStream(stream, maybeLength), maybeLength, params);
          }
          return new FlateStream(stream, maybeLength);
        case "LZW":
        case "LZWDecode":
          let earlyChange = 1;
          if (params) {
            if (params.has("EarlyChange")) {
              earlyChange = params.get("EarlyChange");
            }
            return new PredictorStream(new LZWStream(stream, maybeLength, earlyChange), maybeLength, params);
          }
          return new LZWStream(stream, maybeLength, earlyChange);
        case "DCT":
        case "DCTDecode":
          return new JpegStream(stream, maybeLength, params);
        case "JPX":
        case "JPXDecode":
          return new JpxStream(stream, maybeLength, params);
        case "A85":
        case "ASCII85Decode":
          return new Ascii85Stream(stream, maybeLength);
        case "AHx":
        case "ASCIIHexDecode":
          return new AsciiHexStream(stream, maybeLength);
        case "CCF":
        case "CCITTFaxDecode":
          return new CCITTFaxStream(stream, maybeLength, params);
        case "RL":
        case "RunLengthDecode":
          return new RunLengthStream(stream, maybeLength);
        case "JBIG2Decode":
          return new Jbig2Stream(stream, maybeLength, params);
      }
      warn(`Filter "${name}" is not supported.`);
      return stream;
    } catch (ex) {
      if (ex instanceof MissingDataException) {
        throw ex;
      }
      warn(`Invalid stream: "${ex}"`);
      return new NullStream();
    }
  }
}
const specialChars = [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
function toHexDigit(ch) {
  if (ch >= 0x30 && ch <= 0x39) {
    return ch & 0x0f;
  }
  if (ch >= 0x41 && ch <= 0x46 || ch >= 0x61 && ch <= 0x66) {
    return (ch & 0x0f) + 9;
  }
  return -1;
}
class Lexer {
  constructor(stream, knownCommands = null) {
    this.stream = stream;
    this.nextChar();
    this.strBuf = [];
    this.knownCommands = knownCommands;
    this._hexStringNumWarn = 0;
    this.beginInlineImagePos = -1;
  }
  nextChar() {
    return this.currentChar = this.stream.getByte();
  }
  peekChar() {
    return this.stream.peekByte();
  }
  getNumber() {
    let ch = this.currentChar;
    let eNotation = false;
    let divideBy = 0;
    let sign = 1;
    if (ch === 0x2d) {
      sign = -1;
      ch = this.nextChar();
      if (ch === 0x2d) {
        ch = this.nextChar();
      }
    } else if (ch === 0x2b) {
      ch = this.nextChar();
    }
    if (ch === 0x0a || ch === 0x0d) {
      do {
        ch = this.nextChar();
      } while (ch === 0x0a || ch === 0x0d);
    }
    if (ch === 0x2e) {
      divideBy = 10;
      ch = this.nextChar();
    }
    if (ch < 0x30 || ch > 0x39) {
      const msg = `Invalid number: ${String.fromCharCode(ch)} (charCode ${ch})`;
      if (isWhiteSpace(ch) || ch === -1) {
        info(`Lexer.getNumber - "${msg}".`);
        return 0;
      }
      throw new FormatError(msg);
    }
    let baseValue = ch - 0x30;
    let powerValue = 0;
    let powerValueSign = 1;
    while ((ch = this.nextChar()) >= 0) {
      if (ch >= 0x30 && ch <= 0x39) {
        const currentDigit = ch - 0x30;
        if (eNotation) {
          powerValue = powerValue * 10 + currentDigit;
        } else {
          if (divideBy !== 0) {
            divideBy *= 10;
          }
          baseValue = baseValue * 10 + currentDigit;
        }
      } else if (ch === 0x2e) {
        if (divideBy === 0) {
          divideBy = 1;
        } else {
          break;
        }
      } else if (ch === 0x2d) {
        warn("Badly formatted number: minus sign in the middle");
      } else if (ch === 0x45 || ch === 0x65) {
        ch = this.peekChar();
        if (ch === 0x2b || ch === 0x2d) {
          powerValueSign = ch === 0x2d ? -1 : 1;
          this.nextChar();
        } else if (ch < 0x30 || ch > 0x39) {
          break;
        }
        eNotation = true;
      } else {
        break;
      }
    }
    if (divideBy !== 0) {
      baseValue /= divideBy;
    }
    if (eNotation) {
      baseValue *= 10 ** (powerValueSign * powerValue);
    }
    return sign * baseValue;
  }
  getString() {
    let numParen = 1;
    let done = false;
    const strBuf = this.strBuf;
    strBuf.length = 0;
    let ch = this.nextChar();
    while (true) {
      let charBuffered = false;
      switch (ch | 0) {
        case -1:
          warn("Unterminated string");
          done = true;
          break;
        case 0x28:
          ++numParen;
          strBuf.push("(");
          break;
        case 0x29:
          if (--numParen === 0) {
            this.nextChar();
            done = true;
          } else {
            strBuf.push(")");
          }
          break;
        case 0x5c:
          ch = this.nextChar();
          switch (ch) {
            case -1:
              warn("Unterminated string");
              done = true;
              break;
            case 0x6e:
              strBuf.push("\n");
              break;
            case 0x72:
              strBuf.push("\r");
              break;
            case 0x74:
              strBuf.push("\t");
              break;
            case 0x62:
              strBuf.push("\b");
              break;
            case 0x66:
              strBuf.push("\f");
              break;
            case 0x5c:
            case 0x28:
            case 0x29:
              strBuf.push(String.fromCharCode(ch));
              break;
            case 0x30:
            case 0x31:
            case 0x32:
            case 0x33:
            case 0x34:
            case 0x35:
            case 0x36:
            case 0x37:
              let x = ch & 0x0f;
              ch = this.nextChar();
              charBuffered = true;
              if (ch >= 0x30 && ch <= 0x37) {
                x = (x << 3) + (ch & 0x0f);
                ch = this.nextChar();
                if (ch >= 0x30 && ch <= 0x37) {
                  charBuffered = false;
                  x = (x << 3) + (ch & 0x0f);
                }
              }
              strBuf.push(String.fromCharCode(x));
              break;
            case 0x0d:
              if (this.peekChar() === 0x0a) {
                this.nextChar();
              }
              break;
            case 0x0a:
              break;
            default:
              strBuf.push(String.fromCharCode(ch));
              break;
          }
          break;
        default:
          strBuf.push(String.fromCharCode(ch));
          break;
      }
      if (done) {
        break;
      }
      if (!charBuffered) {
        ch = this.nextChar();
      }
    }
    return strBuf.join("");
  }
  getName() {
    let ch, previousCh;
    const strBuf = this.strBuf;
    strBuf.length = 0;
    while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) {
      if (ch === 0x23) {
        ch = this.nextChar();
        if (specialChars[ch]) {
          warn("Lexer_getName: " + "NUMBER SIGN (#) should be followed by a hexadecimal number.");
          strBuf.push("#");
          break;
        }
        const x = toHexDigit(ch);
        if (x !== -1) {
          previousCh = ch;
          ch = this.nextChar();
          const x2 = toHexDigit(ch);
          if (x2 === -1) {
            warn(`Lexer_getName: Illegal digit (${String.fromCharCode(ch)}) ` + "in hexadecimal number.");
            strBuf.push("#", String.fromCharCode(previousCh));
            if (specialChars[ch]) {
              break;
            }
            strBuf.push(String.fromCharCode(ch));
            continue;
          }
          strBuf.push(String.fromCharCode(x << 4 | x2));
        } else {
          strBuf.push("#", String.fromCharCode(ch));
        }
      } else {
        strBuf.push(String.fromCharCode(ch));
      }
    }
    if (strBuf.length > 127) {
      warn(`Name token is longer than allowed by the spec: ${strBuf.length}`);
    }
    return Name.get(strBuf.join(""));
  }
  _hexStringWarn(ch) {
    const MAX_HEX_STRING_NUM_WARN = 5;
    if (this._hexStringNumWarn++ === MAX_HEX_STRING_NUM_WARN) {
      warn("getHexString - ignoring additional invalid characters.");
      return;
    }
    if (this._hexStringNumWarn > MAX_HEX_STRING_NUM_WARN) {
      return;
    }
    warn(`getHexString - ignoring invalid character: ${ch}`);
  }
  getHexString() {
    const strBuf = this.strBuf;
    strBuf.length = 0;
    let ch = this.currentChar;
    let firstDigit = -1,
      digit = -1;
    this._hexStringNumWarn = 0;
    while (true) {
      if (ch < 0) {
        warn("Unterminated hex string");
        break;
      } else if (ch === 0x3e) {
        this.nextChar();
        break;
      } else if (specialChars[ch] === 1) {
        ch = this.nextChar();
        continue;
      } else {
        digit = toHexDigit(ch);
        if (digit === -1) {
          this._hexStringWarn(ch);
        } else if (firstDigit === -1) {
          firstDigit = digit;
        } else {
          strBuf.push(String.fromCharCode(firstDigit << 4 | digit));
          firstDigit = -1;
        }
        ch = this.nextChar();
      }
    }
    if (firstDigit !== -1) {
      strBuf.push(String.fromCharCode(firstDigit << 4));
    }
    return strBuf.join("");
  }
  getObj() {
    let comment = false;
    let ch = this.currentChar;
    while (true) {
      if (ch < 0) {
        return EOF;
      }
      if (comment) {
        if (ch === 0x0a || ch === 0x0d) {
          comment = false;
        }
      } else if (ch === 0x25) {
        comment = true;
      } else if (specialChars[ch] !== 1) {
        break;
      }
      ch = this.nextChar();
    }
    switch (ch | 0) {
      case 0x30:
      case 0x31:
      case 0x32:
      case 0x33:
      case 0x34:
      case 0x35:
      case 0x36:
      case 0x37:
      case 0x38:
      case 0x39:
      case 0x2b:
      case 0x2d:
      case 0x2e:
        return this.getNumber();
      case 0x28:
        return this.getString();
      case 0x2f:
        return this.getName();
      case 0x5b:
        this.nextChar();
        return Cmd.get("[");
      case 0x5d:
        this.nextChar();
        return Cmd.get("]");
      case 0x3c:
        ch = this.nextChar();
        if (ch === 0x3c) {
          this.nextChar();
          return Cmd.get("<<");
        }
        return this.getHexString();
      case 0x3e:
        ch = this.nextChar();
        if (ch === 0x3e) {
          this.nextChar();
          return Cmd.get(">>");
        }
        return Cmd.get(">");
      case 0x7b:
        this.nextChar();
        return Cmd.get("{");
      case 0x7d:
        this.nextChar();
        return Cmd.get("}");
      case 0x29:
        this.nextChar();
        throw new FormatError(`Illegal character: ${ch}`);
    }
    let str = String.fromCharCode(ch);
    if (ch < 0x20 || ch > 0x7f) {
      const nextCh = this.peekChar();
      if (nextCh >= 0x20 && nextCh <= 0x7f) {
        this.nextChar();
        return Cmd.get(str);
      }
    }
    const knownCommands = this.knownCommands;
    let knownCommandFound = knownCommands?.[str] !== undefined;
    while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) {
      const possibleCommand = str + String.fromCharCode(ch);
      if (knownCommandFound && knownCommands[possibleCommand] === undefined) {
        break;
      }
      if (str.length === 128) {
        throw new FormatError(`Command token too long: ${str.length}`);
      }
      str = possibleCommand;
      knownCommandFound = knownCommands?.[str] !== undefined;
    }
    if (str === "true") {
      return true;
    }
    if (str === "false") {
      return false;
    }
    if (str === "null") {
      return null;
    }
    if (str === "BI") {
      this.beginInlineImagePos = this.stream.pos;
    }
    return Cmd.get(str);
  }
  skipToNextLine() {
    let ch = this.currentChar;
    while (ch >= 0) {
      if (ch === 0x0d) {
        ch = this.nextChar();
        if (ch === 0x0a) {
          this.nextChar();
        }
        break;
      } else if (ch === 0x0a) {
        this.nextChar();
        break;
      }
      ch = this.nextChar();
    }
  }
}
class Linearization {
  static create(stream) {
    function getInt(linDict, name, allowZeroValue = false) {
      const obj = linDict.get(name);
      if (Number.isInteger(obj) && (allowZeroValue ? obj >= 0 : obj > 0)) {
        return obj;
      }
      throw new Error(`The "${name}" parameter in the linearization ` + "dictionary is invalid.");
    }
    function getHints(linDict) {
      const hints = linDict.get("H");
      let hintsLength;
      if (Array.isArray(hints) && ((hintsLength = hints.length) === 2 || hintsLength === 4)) {
        for (let index = 0; index < hintsLength; index++) {
          const hint = hints[index];
          if (!(Number.isInteger(hint) && hint > 0)) {
            throw new Error(`Hint (${index}) in the linearization dictionary is invalid.`);
          }
        }
        return hints;
      }
      throw new Error("Hint array in the linearization dictionary is invalid.");
    }
    const parser = new Parser({
      lexer: new Lexer(stream),
      xref: null
    });
    const obj1 = parser.getObj();
    const obj2 = parser.getObj();
    const obj3 = parser.getObj();
    const linDict = parser.getObj();
    let obj, length;
    if (!(Number.isInteger(obj1) && Number.isInteger(obj2) && isCmd(obj3, "obj") && linDict instanceof Dict && typeof (obj = linDict.get("Linearized")) === "number" && obj > 0)) {
      return null;
    } else if ((length = getInt(linDict, "L")) !== stream.length) {
      throw new Error('The "L" parameter in the linearization dictionary ' + "does not equal the stream length.");
    }
    return {
      length,
      hints: getHints(linDict),
      objectNumberFirst: getInt(linDict, "O"),
      endFirst: getInt(linDict, "E"),
      numPages: getInt(linDict, "N"),
      mainXRefEntriesOffset: getInt(linDict, "T"),
      pageFirst: linDict.has("P") ? getInt(linDict, "P", true) : 0
    };
  }
}

;// CONCATENATED MODULE: ./src/core/cmap.js







const BUILT_IN_CMAPS = ["Adobe-GB1-UCS2", "Adobe-CNS1-UCS2", "Adobe-Japan1-UCS2", "Adobe-Korea1-UCS2", "78-EUC-H", "78-EUC-V", "78-H", "78-RKSJ-H", "78-RKSJ-V", "78-V", "78ms-RKSJ-H", "78ms-RKSJ-V", "83pv-RKSJ-H", "90ms-RKSJ-H", "90ms-RKSJ-V", "90msp-RKSJ-H", "90msp-RKSJ-V", "90pv-RKSJ-H", "90pv-RKSJ-V", "Add-H", "Add-RKSJ-H", "Add-RKSJ-V", "Add-V", "Adobe-CNS1-0", "Adobe-CNS1-1", "Adobe-CNS1-2", "Adobe-CNS1-3", "Adobe-CNS1-4", "Adobe-CNS1-5", "Adobe-CNS1-6", "Adobe-GB1-0", "Adobe-GB1-1", "Adobe-GB1-2", "Adobe-GB1-3", "Adobe-GB1-4", "Adobe-GB1-5", "Adobe-Japan1-0", "Adobe-Japan1-1", "Adobe-Japan1-2", "Adobe-Japan1-3", "Adobe-Japan1-4", "Adobe-Japan1-5", "Adobe-Japan1-6", "Adobe-Korea1-0", "Adobe-Korea1-1", "Adobe-Korea1-2", "B5-H", "B5-V", "B5pc-H", "B5pc-V", "CNS-EUC-H", "CNS-EUC-V", "CNS1-H", "CNS1-V", "CNS2-H", "CNS2-V", "ETHK-B5-H", "ETHK-B5-V", "ETen-B5-H", "ETen-B5-V", "ETenms-B5-H", "ETenms-B5-V", "EUC-H", "EUC-V", "Ext-H", "Ext-RKSJ-H", "Ext-RKSJ-V", "Ext-V", "GB-EUC-H", "GB-EUC-V", "GB-H", "GB-V", "GBK-EUC-H", "GBK-EUC-V", "GBK2K-H", "GBK2K-V", "GBKp-EUC-H", "GBKp-EUC-V", "GBT-EUC-H", "GBT-EUC-V", "GBT-H", "GBT-V", "GBTpc-EUC-H", "GBTpc-EUC-V", "GBpc-EUC-H", "GBpc-EUC-V", "H", "HKdla-B5-H", "HKdla-B5-V", "HKdlb-B5-H", "HKdlb-B5-V", "HKgccs-B5-H", "HKgccs-B5-V", "HKm314-B5-H", "HKm314-B5-V", "HKm471-B5-H", "HKm471-B5-V", "HKscs-B5-H", "HKscs-B5-V", "Hankaku", "Hiragana", "KSC-EUC-H", "KSC-EUC-V", "KSC-H", "KSC-Johab-H", "KSC-Johab-V", "KSC-V", "KSCms-UHC-H", "KSCms-UHC-HW-H", "KSCms-UHC-HW-V", "KSCms-UHC-V", "KSCpc-EUC-H", "KSCpc-EUC-V", "Katakana", "NWP-H", "NWP-V", "RKSJ-H", "RKSJ-V", "Roman", "UniCNS-UCS2-H", "UniCNS-UCS2-V", "UniCNS-UTF16-H", "UniCNS-UTF16-V", "UniCNS-UTF32-H", "UniCNS-UTF32-V", "UniCNS-UTF8-H", "UniCNS-UTF8-V", "UniGB-UCS2-H", "UniGB-UCS2-V", "UniGB-UTF16-H", "UniGB-UTF16-V", "UniGB-UTF32-H", "UniGB-UTF32-V", "UniGB-UTF8-H", "UniGB-UTF8-V", "UniJIS-UCS2-H", "UniJIS-UCS2-HW-H", "UniJIS-UCS2-HW-V", "UniJIS-UCS2-V", "UniJIS-UTF16-H", "UniJIS-UTF16-V", "UniJIS-UTF32-H", "UniJIS-UTF32-V", "UniJIS-UTF8-H", "UniJIS-UTF8-V", "UniJIS2004-UTF16-H", "UniJIS2004-UTF16-V", "UniJIS2004-UTF32-H", "UniJIS2004-UTF32-V", "UniJIS2004-UTF8-H", "UniJIS2004-UTF8-V", "UniJISPro-UCS2-HW-V", "UniJISPro-UCS2-V", "UniJISPro-UTF8-V", "UniJISX0213-UTF32-H", "UniJISX0213-UTF32-V", "UniJISX02132004-UTF32-H", "UniJISX02132004-UTF32-V", "UniKS-UCS2-H", "UniKS-UCS2-V", "UniKS-UTF16-H", "UniKS-UTF16-V", "UniKS-UTF32-H", "UniKS-UTF32-V", "UniKS-UTF8-H", "UniKS-UTF8-V", "V", "WP-Symbol"];
const MAX_MAP_RANGE = 2 ** 24 - 1;
class CMap {
  constructor(builtInCMap = false) {
    this.codespaceRanges = [[], [], [], []];
    this.numCodespaceRanges = 0;
    this._map = [];
    this.name = "";
    this.vertical = false;
    this.useCMap = null;
    this.builtInCMap = builtInCMap;
  }
  addCodespaceRange(n, low, high) {
    this.codespaceRanges[n - 1].push(low, high);
    this.numCodespaceRanges++;
  }
  mapCidRange(low, high, dstLow) {
    if (high - low > MAX_MAP_RANGE) {
      throw new Error("mapCidRange - ignoring data above MAX_MAP_RANGE.");
    }
    while (low <= high) {
      this._map[low++] = dstLow++;
    }
  }
  mapBfRange(low, high, dstLow) {
    if (high - low > MAX_MAP_RANGE) {
      throw new Error("mapBfRange - ignoring data above MAX_MAP_RANGE.");
    }
    const lastByte = dstLow.length - 1;
    while (low <= high) {
      this._map[low++] = dstLow;
      const nextCharCode = dstLow.charCodeAt(lastByte) + 1;
      if (nextCharCode > 0xff) {
        dstLow = dstLow.substring(0, lastByte - 1) + String.fromCharCode(dstLow.charCodeAt(lastByte - 1) + 1) + "\x00";
        continue;
      }
      dstLow = dstLow.substring(0, lastByte) + String.fromCharCode(nextCharCode);
    }
  }
  mapBfRangeToArray(low, high, array) {
    if (high - low > MAX_MAP_RANGE) {
      throw new Error("mapBfRangeToArray - ignoring data above MAX_MAP_RANGE.");
    }
    const ii = array.length;
    let i = 0;
    while (low <= high && i < ii) {
      this._map[low] = array[i++];
      ++low;
    }
  }
  mapOne(src, dst) {
    this._map[src] = dst;
  }
  lookup(code) {
    return this._map[code];
  }
  contains(code) {
    return this._map[code] !== undefined;
  }
  forEach(callback) {
    const map = this._map;
    const length = map.length;
    if (length <= 0x10000) {
      for (let i = 0; i < length; i++) {
        if (map[i] !== undefined) {
          callback(i, map[i]);
        }
      }
    } else {
      for (const i in map) {
        callback(i, map[i]);
      }
    }
  }
  charCodeOf(value) {
    const map = this._map;
    if (map.length <= 0x10000) {
      return map.indexOf(value);
    }
    for (const charCode in map) {
      if (map[charCode] === value) {
        return charCode | 0;
      }
    }
    return -1;
  }
  getMap() {
    return this._map;
  }
  readCharCode(str, offset, out) {
    let c = 0;
    const codespaceRanges = this.codespaceRanges;
    for (let n = 0, nn = codespaceRanges.length; n < nn; n++) {
      c = (c << 8 | str.charCodeAt(offset + n)) >>> 0;
      const codespaceRange = codespaceRanges[n];
      for (let k = 0, kk = codespaceRange.length; k < kk;) {
        const low = codespaceRange[k++];
        const high = codespaceRange[k++];
        if (c >= low && c <= high) {
          out.charcode = c;
          out.length = n + 1;
          return;
        }
      }
    }
    out.charcode = 0;
    out.length = 1;
  }
  getCharCodeLength(charCode) {
    const codespaceRanges = this.codespaceRanges;
    for (let n = 0, nn = codespaceRanges.length; n < nn; n++) {
      const codespaceRange = codespaceRanges[n];
      for (let k = 0, kk = codespaceRange.length; k < kk;) {
        const low = codespaceRange[k++];
        const high = codespaceRange[k++];
        if (charCode >= low && charCode <= high) {
          return n + 1;
        }
      }
    }
    return 1;
  }
  get length() {
    return this._map.length;
  }
  get isIdentityCMap() {
    if (!(this.name === "Identity-H" || this.name === "Identity-V")) {
      return false;
    }
    if (this._map.length !== 0x10000) {
      return false;
    }
    for (let i = 0; i < 0x10000; i++) {
      if (this._map[i] !== i) {
        return false;
      }
    }
    return true;
  }
}
class IdentityCMap extends CMap {
  constructor(vertical, n) {
    super();
    this.vertical = vertical;
    this.addCodespaceRange(n, 0, 0xffff);
  }
  mapCidRange(low, high, dstLow) {
    unreachable("should not call mapCidRange");
  }
  mapBfRange(low, high, dstLow) {
    unreachable("should not call mapBfRange");
  }
  mapBfRangeToArray(low, high, array) {
    unreachable("should not call mapBfRangeToArray");
  }
  mapOne(src, dst) {
    unreachable("should not call mapCidOne");
  }
  lookup(code) {
    return Number.isInteger(code) && code <= 0xffff ? code : undefined;
  }
  contains(code) {
    return Number.isInteger(code) && code <= 0xffff;
  }
  forEach(callback) {
    for (let i = 0; i <= 0xffff; i++) {
      callback(i, i);
    }
  }
  charCodeOf(value) {
    return Number.isInteger(value) && value <= 0xffff ? value : -1;
  }
  getMap() {
    const map = new Array(0x10000);
    for (let i = 0; i <= 0xffff; i++) {
      map[i] = i;
    }
    return map;
  }
  get length() {
    return 0x10000;
  }
  get isIdentityCMap() {
    unreachable("should not access .isIdentityCMap");
  }
}
function strToInt(str) {
  let a = 0;
  for (let i = 0; i < str.length; i++) {
    a = a << 8 | str.charCodeAt(i);
  }
  return a >>> 0;
}
function expectString(obj) {
  if (typeof obj !== "string") {
    throw new FormatError("Malformed CMap: expected string.");
  }
}
function expectInt(obj) {
  if (!Number.isInteger(obj)) {
    throw new FormatError("Malformed CMap: expected int.");
  }
}
function parseBfChar(cMap, lexer) {
  while (true) {
    let obj = lexer.getObj();
    if (obj === EOF) {
      break;
    }
    if (isCmd(obj, "endbfchar")) {
      return;
    }
    expectString(obj);
    const src = strToInt(obj);
    obj = lexer.getObj();
    expectString(obj);
    const dst = obj;
    cMap.mapOne(src, dst);
  }
}
function parseBfRange(cMap, lexer) {
  while (true) {
    let obj = lexer.getObj();
    if (obj === EOF) {
      break;
    }
    if (isCmd(obj, "endbfrange")) {
      return;
    }
    expectString(obj);
    const low = strToInt(obj);
    obj = lexer.getObj();
    expectString(obj);
    const high = strToInt(obj);
    obj = lexer.getObj();
    if (Number.isInteger(obj) || typeof obj === "string") {
      const dstLow = Number.isInteger(obj) ? String.fromCharCode(obj) : obj;
      cMap.mapBfRange(low, high, dstLow);
    } else if (isCmd(obj, "[")) {
      obj = lexer.getObj();
      const array = [];
      while (!isCmd(obj, "]") && obj !== EOF) {
        array.push(obj);
        obj = lexer.getObj();
      }
      cMap.mapBfRangeToArray(low, high, array);
    } else {
      break;
    }
  }
  throw new FormatError("Invalid bf range.");
}
function parseCidChar(cMap, lexer) {
  while (true) {
    let obj = lexer.getObj();
    if (obj === EOF) {
      break;
    }
    if (isCmd(obj, "endcidchar")) {
      return;
    }
    expectString(obj);
    const src = strToInt(obj);
    obj = lexer.getObj();
    expectInt(obj);
    const dst = obj;
    cMap.mapOne(src, dst);
  }
}
function parseCidRange(cMap, lexer) {
  while (true) {
    let obj = lexer.getObj();
    if (obj === EOF) {
      break;
    }
    if (isCmd(obj, "endcidrange")) {
      return;
    }
    expectString(obj);
    const low = strToInt(obj);
    obj = lexer.getObj();
    expectString(obj);
    const high = strToInt(obj);
    obj = lexer.getObj();
    expectInt(obj);
    const dstLow = obj;
    cMap.mapCidRange(low, high, dstLow);
  }
}
function parseCodespaceRange(cMap, lexer) {
  while (true) {
    let obj = lexer.getObj();
    if (obj === EOF) {
      break;
    }
    if (isCmd(obj, "endcodespacerange")) {
      return;
    }
    if (typeof obj !== "string") {
      break;
    }
    const low = strToInt(obj);
    obj = lexer.getObj();
    if (typeof obj !== "string") {
      break;
    }
    const high = strToInt(obj);
    cMap.addCodespaceRange(obj.length, low, high);
  }
  throw new FormatError("Invalid codespace range.");
}
function parseWMode(cMap, lexer) {
  const obj = lexer.getObj();
  if (Number.isInteger(obj)) {
    cMap.vertical = !!obj;
  }
}
function parseCMapName(cMap, lexer) {
  const obj = lexer.getObj();
  if (obj instanceof Name) {
    cMap.name = obj.name;
  }
}
async function parseCMap(cMap, lexer, fetchBuiltInCMap, useCMap) {
  let previous, embeddedUseCMap;
  objLoop: while (true) {
    try {
      const obj = lexer.getObj();
      if (obj === EOF) {
        break;
      } else if (obj instanceof Name) {
        if (obj.name === "WMode") {
          parseWMode(cMap, lexer);
        } else if (obj.name === "CMapName") {
          parseCMapName(cMap, lexer);
        }
        previous = obj;
      } else if (obj instanceof Cmd) {
        switch (obj.cmd) {
          case "endcmap":
            break objLoop;
          case "usecmap":
            if (previous instanceof Name) {
              embeddedUseCMap = previous.name;
            }
            break;
          case "begincodespacerange":
            parseCodespaceRange(cMap, lexer);
            break;
          case "beginbfchar":
            parseBfChar(cMap, lexer);
            break;
          case "begincidchar":
            parseCidChar(cMap, lexer);
            break;
          case "beginbfrange":
            parseBfRange(cMap, lexer);
            break;
          case "begincidrange":
            parseCidRange(cMap, lexer);
            break;
        }
      }
    } catch (ex) {
      if (ex instanceof MissingDataException) {
        throw ex;
      }
      warn("Invalid cMap data: " + ex);
      continue;
    }
  }
  if (!useCMap && embeddedUseCMap) {
    useCMap = embeddedUseCMap;
  }
  if (useCMap) {
    return extendCMap(cMap, fetchBuiltInCMap, useCMap);
  }
  return cMap;
}
async function extendCMap(cMap, fetchBuiltInCMap, useCMap) {
  cMap.useCMap = await createBuiltInCMap(useCMap, fetchBuiltInCMap);
  if (cMap.numCodespaceRanges === 0) {
    const useCodespaceRanges = cMap.useCMap.codespaceRanges;
    for (let i = 0; i < useCodespaceRanges.length; i++) {
      cMap.codespaceRanges[i] = useCodespaceRanges[i].slice();
    }
    cMap.numCodespaceRanges = cMap.useCMap.numCodespaceRanges;
  }
  cMap.useCMap.forEach(function (key, value) {
    if (!cMap.contains(key)) {
      cMap.mapOne(key, cMap.useCMap.lookup(key));
    }
  });
  return cMap;
}
async function createBuiltInCMap(name, fetchBuiltInCMap) {
  if (name === "Identity-H") {
    return new IdentityCMap(false, 2);
  } else if (name === "Identity-V") {
    return new IdentityCMap(true, 2);
  }
  if (!BUILT_IN_CMAPS.includes(name)) {
    throw new Error("Unknown CMap name: " + name);
  }
  if (!fetchBuiltInCMap) {
    throw new Error("Built-in CMap parameters are not provided.");
  }
  const {
    cMapData,
    compressionType
  } = await fetchBuiltInCMap(name);
  const cMap = new CMap(true);
  if (compressionType === CMapCompressionType.BINARY) {
    return new BinaryCMapReader().process(cMapData, cMap, useCMap => extendCMap(cMap, fetchBuiltInCMap, useCMap));
  }
  if (compressionType === CMapCompressionType.NONE) {
    const lexer = new Lexer(new Stream(cMapData));
    return parseCMap(cMap, lexer, fetchBuiltInCMap, null);
  }
  throw new Error(`Invalid CMap "compressionType" value: ${compressionType}`);
}
class CMapFactory {
  static async create({
    encoding,
    fetchBuiltInCMap,
    useCMap
  }) {
    if (encoding instanceof Name) {
      return createBuiltInCMap(encoding.name, fetchBuiltInCMap);
    } else if (encoding instanceof BaseStream) {
      const parsedCMap = await parseCMap(new CMap(), new Lexer(encoding), fetchBuiltInCMap, useCMap);
      if (parsedCMap.isIdentityCMap) {
        return createBuiltInCMap(parsedCMap.name, fetchBuiltInCMap);
      }
      return parsedCMap;
    }
    throw new Error("Encoding required.");
  }
}

;// CONCATENATED MODULE: ./src/core/charsets.js
const ISOAdobeCharset = [".notdef", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent", "sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl", "endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet", "quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", "perthousand", "questiondown", "grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE", "ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls", "onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", "Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn", "threequarters", "twosuperior", "registered", "minus", "eth", "multiply", "threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", "Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", "Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", "Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute", "acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute", "ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis", "igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde", "scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis", "zcaron"];
const ExpertCharset = [".notdef", "space", "exclamsmall", "Hungarumlautsmall", "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", "parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon", "commasuperior", "threequartersemdash", "periodsuperior", "questionsmall", "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", "tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", "parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", "exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall", "figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall", "onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", "centinferior", "dollarinferior", "periodinferior", "commainferior", "Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall", "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall", "Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", "Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", "Ydieresissmall"];
const ExpertSubsetCharset = [".notdef", "space", "dollaroldstyle", "dollarsuperior", "parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon", "commasuperior", "threequartersemdash", "periodsuperior", "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", "tsuperior", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", "parenrightinferior", "hyphensuperior", "colonmonetary", "onefitted", "rupiah", "centoldstyle", "figuredash", "hypheninferior", "onequarter", "onehalf", "threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", "zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", "centinferior", "dollarinferior", "periodinferior", "commainferior"];

;// CONCATENATED MODULE: ./src/core/encodings.js
const ExpertEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "exclamsmall", "Hungarumlautsmall", "", "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", "parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon", "commasuperior", "threequartersemdash", "periodsuperior", "questionsmall", "", "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "", "", "", "isuperior", "", "", "lsuperior", "msuperior", "nsuperior", "osuperior", "", "", "rsuperior", "ssuperior", "tsuperior", "", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", "", "parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "exclamdownsmall", "centoldstyle", "Lslashsmall", "", "", "Scaronsmall", "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall", "", "Dotaccentsmall", "", "", "Macronsmall", "", "", "figuredash", "hypheninferior", "", "", "Ogoneksmall", "Ringsmall", "Cedillasmall", "", "", "", "onequarter", "onehalf", "threequarters", "questiondownsmall", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", "", "", "zerosuperior", "onesuperior", "twosuperior", "threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", "centinferior", "dollarinferior", "periodinferior", "commainferior", "Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall", "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall", "Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", "Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", "Ydieresissmall"];
const MacExpertEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "exclamsmall", "Hungarumlautsmall", "centoldstyle", "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", "parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", "comma", "hyphen", "period", "fraction", "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "colon", "semicolon", "", "threequartersemdash", "", "questionsmall", "", "", "", "", "Ethsmall", "", "", "onequarter", "onehalf", "threequarters", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", "", "", "", "", "", "", "ff", "fi", "fl", "ffi", "ffl", "parenleftinferior", "", "parenrightinferior", "Circumflexsmall", "hypheninferior", "Gravesmall", "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", "", "", "asuperior", "centsuperior", "", "", "", "", "Aacutesmall", "Agravesmall", "Acircumflexsmall", "Adieresissmall", "Atildesmall", "Aringsmall", "Ccedillasmall", "Eacutesmall", "Egravesmall", "Ecircumflexsmall", "Edieresissmall", "Iacutesmall", "Igravesmall", "Icircumflexsmall", "Idieresissmall", "Ntildesmall", "Oacutesmall", "Ogravesmall", "Ocircumflexsmall", "Odieresissmall", "Otildesmall", "Uacutesmall", "Ugravesmall", "Ucircumflexsmall", "Udieresissmall", "", "eightsuperior", "fourinferior", "threeinferior", "sixinferior", "eightinferior", "seveninferior", "Scaronsmall", "", "centinferior", "twoinferior", "", "Dieresissmall", "", "Caronsmall", "osuperior", "fiveinferior", "", "commainferior", "periodinferior", "Yacutesmall", "", "dollarinferior", "", "", "Thornsmall", "", "nineinferior", "zeroinferior", "Zcaronsmall", "AEsmall", "Oslashsmall", "questiondownsmall", "oneinferior", "Lslashsmall", "", "", "", "", "", "", "Cedillasmall", "", "", "", "", "", "OEsmall", "figuredash", "hyphensuperior", "", "", "", "", "exclamdownsmall", "", "Ydieresissmall", "", "onesuperior", "twosuperior", "threesuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", "ninesuperior", "zerosuperior", "", "esuperior", "rsuperior", "tsuperior", "", "", "isuperior", "ssuperior", "dsuperior", "", "", "", "", "", "lsuperior", "Ogoneksmall", "Brevesmall", "Macronsmall", "bsuperior", "nsuperior", "msuperior", "commasuperior", "periodsuperior", "Dotaccentsmall", "Ringsmall", "", "", "", ""];
const MacRomanEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quotesingle", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "grave", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "", "Adieresis", "Aring", "Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis", "aacute", "agrave", "acircumflex", "adieresis", "atilde", "aring", "ccedilla", "eacute", "egrave", "ecircumflex", "edieresis", "iacute", "igrave", "icircumflex", "idieresis", "ntilde", "oacute", "ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave", "ucircumflex", "udieresis", "dagger", "degree", "cent", "sterling", "section", "bullet", "paragraph", "germandbls", "registered", "copyright", "trademark", "acute", "dieresis", "notequal", "AE", "Oslash", "infinity", "plusminus", "lessequal", "greaterequal", "yen", "mu", "partialdiff", "summation", "product", "pi", "integral", "ordfeminine", "ordmasculine", "Omega", "ae", "oslash", "questiondown", "exclamdown", "logicalnot", "radical", "florin", "approxequal", "Delta", "guillemotleft", "guillemotright", "ellipsis", "space", "Agrave", "Atilde", "Otilde", "OE", "oe", "endash", "emdash", "quotedblleft", "quotedblright", "quoteleft", "quoteright", "divide", "lozenge", "ydieresis", "Ydieresis", "fraction", "currency", "guilsinglleft", "guilsinglright", "fi", "fl", "daggerdbl", "periodcentered", "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex", "Ecircumflex", "Aacute", "Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex", "apple", "Ograve", "Uacute", "Ucircumflex", "Ugrave", "dotlessi", "circumflex", "tilde", "macron", "breve", "dotaccent", "ring", "cedilla", "hungarumlaut", "ogonek", "caron"];
const StandardEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "exclamdown", "cent", "sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl", "", "endash", "dagger", "daggerdbl", "periodcentered", "", "paragraph", "bullet", "quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", "perthousand", "", "questiondown", "", "grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent", "dieresis", "", "ring", "cedilla", "", "hungarumlaut", "ogonek", "caron", "emdash", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "AE", "", "ordfeminine", "", "", "", "", "Lslash", "Oslash", "OE", "ordmasculine", "", "", "", "", "", "ae", "", "", "", "dotlessi", "", "", "lslash", "oslash", "oe", "germandbls", "", "", "", ""];
const WinAnsiEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quotesingle", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "grave", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "bullet", "Euro", "bullet", "quotesinglbase", "florin", "quotedblbase", "ellipsis", "dagger", "daggerdbl", "circumflex", "perthousand", "Scaron", "guilsinglleft", "OE", "bullet", "Zcaron", "bullet", "bullet", "quoteleft", "quoteright", "quotedblleft", "quotedblright", "bullet", "endash", "emdash", "tilde", "trademark", "scaron", "guilsinglright", "oe", "bullet", "zcaron", "Ydieresis", "space", "exclamdown", "cent", "sterling", "currency", "yen", "brokenbar", "section", "dieresis", "copyright", "ordfeminine", "guillemotleft", "logicalnot", "hyphen", "registered", "macron", "degree", "plusminus", "twosuperior", "threesuperior", "acute", "mu", "paragraph", "periodcentered", "cedilla", "onesuperior", "ordmasculine", "guillemotright", "onequarter", "onehalf", "threequarters", "questiondown", "Agrave", "Aacute", "Acircumflex", "Atilde", "Adieresis", "Aring", "AE", "Ccedilla", "Egrave", "Eacute", "Ecircumflex", "Edieresis", "Igrave", "Iacute", "Icircumflex", "Idieresis", "Eth", "Ntilde", "Ograve", "Oacute", "Ocircumflex", "Otilde", "Odieresis", "multiply", "Oslash", "Ugrave", "Uacute", "Ucircumflex", "Udieresis", "Yacute", "Thorn", "germandbls", "agrave", "aacute", "acircumflex", "atilde", "adieresis", "aring", "ae", "ccedilla", "egrave", "eacute", "ecircumflex", "edieresis", "igrave", "iacute", "icircumflex", "idieresis", "eth", "ntilde", "ograve", "oacute", "ocircumflex", "otilde", "odieresis", "divide", "oslash", "ugrave", "uacute", "ucircumflex", "udieresis", "yacute", "thorn", "ydieresis"];
const SymbolSetEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "exclam", "universal", "numbersign", "existential", "percent", "ampersand", "suchthat", "parenleft", "parenright", "asteriskmath", "plus", "comma", "minus", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "congruent", "Alpha", "Beta", "Chi", "Delta", "Epsilon", "Phi", "Gamma", "Eta", "Iota", "theta1", "Kappa", "Lambda", "Mu", "Nu", "Omicron", "Pi", "Theta", "Rho", "Sigma", "Tau", "Upsilon", "sigma1", "Omega", "Xi", "Psi", "Zeta", "bracketleft", "therefore", "bracketright", "perpendicular", "underscore", "radicalex", "alpha", "beta", "chi", "delta", "epsilon", "phi", "gamma", "eta", "iota", "phi1", "kappa", "lambda", "mu", "nu", "omicron", "pi", "theta", "rho", "sigma", "tau", "upsilon", "omega1", "omega", "xi", "psi", "zeta", "braceleft", "bar", "braceright", "similar", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "Euro", "Upsilon1", "minute", "lessequal", "fraction", "infinity", "florin", "club", "diamond", "heart", "spade", "arrowboth", "arrowleft", "arrowup", "arrowright", "arrowdown", "degree", "plusminus", "second", "greaterequal", "multiply", "proportional", "partialdiff", "bullet", "divide", "notequal", "equivalence", "approxequal", "ellipsis", "arrowvertex", "arrowhorizex", "carriagereturn", "aleph", "Ifraktur", "Rfraktur", "weierstrass", "circlemultiply", "circleplus", "emptyset", "intersection", "union", "propersuperset", "reflexsuperset", "notsubset", "propersubset", "reflexsubset", "element", "notelement", "angle", "gradient", "registerserif", "copyrightserif", "trademarkserif", "product", "radical", "dotmath", "logicalnot", "logicaland", "logicalor", "arrowdblboth", "arrowdblleft", "arrowdblup", "arrowdblright", "arrowdbldown", "lozenge", "angleleft", "registersans", "copyrightsans", "trademarksans", "summation", "parenlefttp", "parenleftex", "parenleftbt", "bracketlefttp", "bracketleftex", "bracketleftbt", "bracelefttp", "braceleftmid", "braceleftbt", "braceex", "", "angleright", "integral", "integraltp", "integralex", "integralbt", "parenrighttp", "parenrightex", "parenrightbt", "bracketrighttp", "bracketrightex", "bracketrightbt", "bracerighttp", "bracerightmid", "bracerightbt", ""];
const ZapfDingbatsEncoding = ["", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "space", "a1", "a2", "a202", "a3", "a4", "a5", "a119", "a118", "a117", "a11", "a12", "a13", "a14", "a15", "a16", "a105", "a17", "a18", "a19", "a20", "a21", "a22", "a23", "a24", "a25", "a26", "a27", "a28", "a6", "a7", "a8", "a9", "a10", "a29", "a30", "a31", "a32", "a33", "a34", "a35", "a36", "a37", "a38", "a39", "a40", "a41", "a42", "a43", "a44", "a45", "a46", "a47", "a48", "a49", "a50", "a51", "a52", "a53", "a54", "a55", "a56", "a57", "a58", "a59", "a60", "a61", "a62", "a63", "a64", "a65", "a66", "a67", "a68", "a69", "a70", "a71", "a72", "a73", "a74", "a203", "a75", "a204", "a76", "a77", "a78", "a79", "a81", "a82", "a83", "a84", "a97", "a98", "a99", "a100", "", "a89", "a90", "a93", "a94", "a91", "a92", "a205", "a85", "a206", "a86", "a87", "a88", "a95", "a96", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "a101", "a102", "a103", "a104", "a106", "a107", "a108", "a112", "a111", "a110", "a109", "a120", "a121", "a122", "a123", "a124", "a125", "a126", "a127", "a128", "a129", "a130", "a131", "a132", "a133", "a134", "a135", "a136", "a137", "a138", "a139", "a140", "a141", "a142", "a143", "a144", "a145", "a146", "a147", "a148", "a149", "a150", "a151", "a152", "a153", "a154", "a155", "a156", "a157", "a158", "a159", "a160", "a161", "a163", "a164", "a196", "a165", "a192", "a166", "a167", "a168", "a169", "a170", "a171", "a172", "a173", "a162", "a174", "a175", "a176", "a177", "a178", "a179", "a193", "a180", "a199", "a181", "a200", "a182", "", "a201", "a183", "a184", "a197", "a185", "a194", "a198", "a186", "a195", "a187", "a188", "a189", "a190", "a191", ""];
function getEncoding(encodingName) {
  switch (encodingName) {
    case "WinAnsiEncoding":
      return WinAnsiEncoding;
    case "StandardEncoding":
      return StandardEncoding;
    case "MacRomanEncoding":
      return MacRomanEncoding;
    case "SymbolSetEncoding":
      return SymbolSetEncoding;
    case "ZapfDingbatsEncoding":
      return ZapfDingbatsEncoding;
    case "ExpertEncoding":
      return ExpertEncoding;
    case "MacExpertEncoding":
      return MacExpertEncoding;
    default:
      return null;
  }
}

;// CONCATENATED MODULE: ./src/core/cff_parser.js



const MAX_SUBR_NESTING = 10;
const CFFStandardStrings = [".notdef", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quoteright", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "quoteleft", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "exclamdown", "cent", "sterling", "fraction", "yen", "florin", "section", "currency", "quotesingle", "quotedblleft", "guillemotleft", "guilsinglleft", "guilsinglright", "fi", "fl", "endash", "dagger", "daggerdbl", "periodcentered", "paragraph", "bullet", "quotesinglbase", "quotedblbase", "quotedblright", "guillemotright", "ellipsis", "perthousand", "questiondown", "grave", "acute", "circumflex", "tilde", "macron", "breve", "dotaccent", "dieresis", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "emdash", "AE", "ordfeminine", "Lslash", "Oslash", "OE", "ordmasculine", "ae", "dotlessi", "lslash", "oslash", "oe", "germandbls", "onesuperior", "logicalnot", "mu", "trademark", "Eth", "onehalf", "plusminus", "Thorn", "onequarter", "divide", "brokenbar", "degree", "thorn", "threequarters", "twosuperior", "registered", "minus", "eth", "multiply", "threesuperior", "copyright", "Aacute", "Acircumflex", "Adieresis", "Agrave", "Aring", "Atilde", "Ccedilla", "Eacute", "Ecircumflex", "Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave", "Ntilde", "Oacute", "Ocircumflex", "Odieresis", "Ograve", "Otilde", "Scaron", "Uacute", "Ucircumflex", "Udieresis", "Ugrave", "Yacute", "Ydieresis", "Zcaron", "aacute", "acircumflex", "adieresis", "agrave", "aring", "atilde", "ccedilla", "eacute", "ecircumflex", "edieresis", "egrave", "iacute", "icircumflex", "idieresis", "igrave", "ntilde", "oacute", "ocircumflex", "odieresis", "ograve", "otilde", "scaron", "uacute", "ucircumflex", "udieresis", "ugrave", "yacute", "ydieresis", "zcaron", "exclamsmall", "Hungarumlautsmall", "dollaroldstyle", "dollarsuperior", "ampersandsmall", "Acutesmall", "parenleftsuperior", "parenrightsuperior", "twodotenleader", "onedotenleader", "zerooldstyle", "oneoldstyle", "twooldstyle", "threeoldstyle", "fouroldstyle", "fiveoldstyle", "sixoldstyle", "sevenoldstyle", "eightoldstyle", "nineoldstyle", "commasuperior", "threequartersemdash", "periodsuperior", "questionsmall", "asuperior", "bsuperior", "centsuperior", "dsuperior", "esuperior", "isuperior", "lsuperior", "msuperior", "nsuperior", "osuperior", "rsuperior", "ssuperior", "tsuperior", "ff", "ffi", "ffl", "parenleftinferior", "parenrightinferior", "Circumflexsmall", "hyphensuperior", "Gravesmall", "Asmall", "Bsmall", "Csmall", "Dsmall", "Esmall", "Fsmall", "Gsmall", "Hsmall", "Ismall", "Jsmall", "Ksmall", "Lsmall", "Msmall", "Nsmall", "Osmall", "Psmall", "Qsmall", "Rsmall", "Ssmall", "Tsmall", "Usmall", "Vsmall", "Wsmall", "Xsmall", "Ysmall", "Zsmall", "colonmonetary", "onefitted", "rupiah", "Tildesmall", "exclamdownsmall", "centoldstyle", "Lslashsmall", "Scaronsmall", "Zcaronsmall", "Dieresissmall", "Brevesmall", "Caronsmall", "Dotaccentsmall", "Macronsmall", "figuredash", "hypheninferior", "Ogoneksmall", "Ringsmall", "Cedillasmall", "questiondownsmall", "oneeighth", "threeeighths", "fiveeighths", "seveneighths", "onethird", "twothirds", "zerosuperior", "foursuperior", "fivesuperior", "sixsuperior", "sevensuperior", "eightsuperior", "ninesuperior", "zeroinferior", "oneinferior", "twoinferior", "threeinferior", "fourinferior", "fiveinferior", "sixinferior", "seveninferior", "eightinferior", "nineinferior", "centinferior", "dollarinferior", "periodinferior", "commainferior", "Agravesmall", "Aacutesmall", "Acircumflexsmall", "Atildesmall", "Adieresissmall", "Aringsmall", "AEsmall", "Ccedillasmall", "Egravesmall", "Eacutesmall", "Ecircumflexsmall", "Edieresissmall", "Igravesmall", "Iacutesmall", "Icircumflexsmall", "Idieresissmall", "Ethsmall", "Ntildesmall", "Ogravesmall", "Oacutesmall", "Ocircumflexsmall", "Otildesmall", "Odieresissmall", "OEsmall", "Oslashsmall", "Ugravesmall", "Uacutesmall", "Ucircumflexsmall", "Udieresissmall", "Yacutesmall", "Thornsmall", "Ydieresissmall", "001.000", "001.001", "001.002", "001.003", "Black", "Bold", "Book", "Light", "Medium", "Regular", "Roman", "Semibold"];
const NUM_STANDARD_CFF_STRINGS = 391;
const CharstringValidationData = [null, {
  id: "hstem",
  min: 2,
  stackClearing: true,
  stem: true
}, null, {
  id: "vstem",
  min: 2,
  stackClearing: true,
  stem: true
}, {
  id: "vmoveto",
  min: 1,
  stackClearing: true
}, {
  id: "rlineto",
  min: 2,
  resetStack: true
}, {
  id: "hlineto",
  min: 1,
  resetStack: true
}, {
  id: "vlineto",
  min: 1,
  resetStack: true
}, {
  id: "rrcurveto",
  min: 6,
  resetStack: true
}, null, {
  id: "callsubr",
  min: 1,
  undefStack: true
}, {
  id: "return",
  min: 0,
  undefStack: true
}, null, null, {
  id: "endchar",
  min: 0,
  stackClearing: true
}, null, null, null, {
  id: "hstemhm",
  min: 2,
  stackClearing: true,
  stem: true
}, {
  id: "hintmask",
  min: 0,
  stackClearing: true
}, {
  id: "cntrmask",
  min: 0,
  stackClearing: true
}, {
  id: "rmoveto",
  min: 2,
  stackClearing: true
}, {
  id: "hmoveto",
  min: 1,
  stackClearing: true
}, {
  id: "vstemhm",
  min: 2,
  stackClearing: true,
  stem: true
}, {
  id: "rcurveline",
  min: 8,
  resetStack: true
}, {
  id: "rlinecurve",
  min: 8,
  resetStack: true
}, {
  id: "vvcurveto",
  min: 4,
  resetStack: true
}, {
  id: "hhcurveto",
  min: 4,
  resetStack: true
}, null, {
  id: "callgsubr",
  min: 1,
  undefStack: true
}, {
  id: "vhcurveto",
  min: 4,
  resetStack: true
}, {
  id: "hvcurveto",
  min: 4,
  resetStack: true
}];
const CharstringValidationData12 = [null, null, null, {
  id: "and",
  min: 2,
  stackDelta: -1
}, {
  id: "or",
  min: 2,
  stackDelta: -1
}, {
  id: "not",
  min: 1,
  stackDelta: 0
}, null, null, null, {
  id: "abs",
  min: 1,
  stackDelta: 0
}, {
  id: "add",
  min: 2,
  stackDelta: -1,
  stackFn(stack, index) {
    stack[index - 2] = stack[index - 2] + stack[index - 1];
  }
}, {
  id: "sub",
  min: 2,
  stackDelta: -1,
  stackFn(stack, index) {
    stack[index - 2] = stack[index - 2] - stack[index - 1];
  }
}, {
  id: "div",
  min: 2,
  stackDelta: -1,
  stackFn(stack, index) {
    stack[index - 2] = stack[index - 2] / stack[index - 1];
  }
}, null, {
  id: "neg",
  min: 1,
  stackDelta: 0,
  stackFn(stack, index) {
    stack[index - 1] = -stack[index - 1];
  }
}, {
  id: "eq",
  min: 2,
  stackDelta: -1
}, null, null, {
  id: "drop",
  min: 1,
  stackDelta: -1
}, null, {
  id: "put",
  min: 2,
  stackDelta: -2
}, {
  id: "get",
  min: 1,
  stackDelta: 0
}, {
  id: "ifelse",
  min: 4,
  stackDelta: -3
}, {
  id: "random",
  min: 0,
  stackDelta: 1
}, {
  id: "mul",
  min: 2,
  stackDelta: -1,
  stackFn(stack, index) {
    stack[index - 2] = stack[index - 2] * stack[index - 1];
  }
}, null, {
  id: "sqrt",
  min: 1,
  stackDelta: 0
}, {
  id: "dup",
  min: 1,
  stackDelta: 1
}, {
  id: "exch",
  min: 2,
  stackDelta: 0
}, {
  id: "index",
  min: 2,
  stackDelta: 0
}, {
  id: "roll",
  min: 3,
  stackDelta: -2
}, null, null, null, {
  id: "hflex",
  min: 7,
  resetStack: true
}, {
  id: "flex",
  min: 13,
  resetStack: true
}, {
  id: "hflex1",
  min: 9,
  resetStack: true
}, {
  id: "flex1",
  min: 11,
  resetStack: true
}];
class CFFParser {
  constructor(file, properties, seacAnalysisEnabled) {
    this.bytes = file.getBytes();
    this.properties = properties;
    this.seacAnalysisEnabled = !!seacAnalysisEnabled;
  }
  parse() {
    const properties = this.properties;
    const cff = new CFF();
    this.cff = cff;
    const header = this.parseHeader();
    const nameIndex = this.parseIndex(header.endPos);
    const topDictIndex = this.parseIndex(nameIndex.endPos);
    const stringIndex = this.parseIndex(topDictIndex.endPos);
    const globalSubrIndex = this.parseIndex(stringIndex.endPos);
    const topDictParsed = this.parseDict(topDictIndex.obj.get(0));
    const topDict = this.createDict(CFFTopDict, topDictParsed, cff.strings);
    cff.header = header.obj;
    cff.names = this.parseNameIndex(nameIndex.obj);
    cff.strings = this.parseStringIndex(stringIndex.obj);
    cff.topDict = topDict;
    cff.globalSubrIndex = globalSubrIndex.obj;
    this.parsePrivateDict(cff.topDict);
    cff.isCIDFont = topDict.hasName("ROS");
    const charStringOffset = topDict.getByName("CharStrings");
    const charStringIndex = this.parseIndex(charStringOffset).obj;
    const fontMatrix = topDict.getByName("FontMatrix");
    if (fontMatrix) {
      properties.fontMatrix = fontMatrix;
    }
    const fontBBox = topDict.getByName("FontBBox");
    if (fontBBox) {
      properties.ascent = Math.max(fontBBox[3], fontBBox[1]);
      properties.descent = Math.min(fontBBox[1], fontBBox[3]);
      properties.ascentScaled = true;
    }
    let charset, encoding;
    if (cff.isCIDFont) {
      const fdArrayIndex = this.parseIndex(topDict.getByName("FDArray")).obj;
      for (let i = 0, ii = fdArrayIndex.count; i < ii; ++i) {
        const dictRaw = fdArrayIndex.get(i);
        const fontDict = this.createDict(CFFTopDict, this.parseDict(dictRaw), cff.strings);
        this.parsePrivateDict(fontDict);
        cff.fdArray.push(fontDict);
      }
      encoding = null;
      charset = this.parseCharsets(topDict.getByName("charset"), charStringIndex.count, cff.strings, true);
      cff.fdSelect = this.parseFDSelect(topDict.getByName("FDSelect"), charStringIndex.count);
    } else {
      charset = this.parseCharsets(topDict.getByName("charset"), charStringIndex.count, cff.strings, false);
      encoding = this.parseEncoding(topDict.getByName("Encoding"), properties, cff.strings, charset.charset);
    }
    cff.charset = charset;
    cff.encoding = encoding;
    const charStringsAndSeacs = this.parseCharStrings({
      charStrings: charStringIndex,
      localSubrIndex: topDict.privateDict.subrsIndex,
      globalSubrIndex: globalSubrIndex.obj,
      fdSelect: cff.fdSelect,
      fdArray: cff.fdArray,
      privateDict: topDict.privateDict
    });
    cff.charStrings = charStringsAndSeacs.charStrings;
    cff.seacs = charStringsAndSeacs.seacs;
    cff.widths = charStringsAndSeacs.widths;
    return cff;
  }
  parseHeader() {
    let bytes = this.bytes;
    const bytesLength = bytes.length;
    let offset = 0;
    while (offset < bytesLength && bytes[offset] !== 1) {
      ++offset;
    }
    if (offset >= bytesLength) {
      throw new FormatError("Invalid CFF header");
    }
    if (offset !== 0) {
      info("cff data is shifted");
      bytes = bytes.subarray(offset);
      this.bytes = bytes;
    }
    const major = bytes[0];
    const minor = bytes[1];
    const hdrSize = bytes[2];
    const offSize = bytes[3];
    const header = new CFFHeader(major, minor, hdrSize, offSize);
    return {
      obj: header,
      endPos: hdrSize
    };
  }
  parseDict(dict) {
    let pos = 0;
    function parseOperand() {
      let value = dict[pos++];
      if (value === 30) {
        return parseFloatOperand();
      } else if (value === 28) {
        value = dict[pos++];
        value = (value << 24 | dict[pos++] << 16) >> 16;
        return value;
      } else if (value === 29) {
        value = dict[pos++];
        value = value << 8 | dict[pos++];
        value = value << 8 | dict[pos++];
        value = value << 8 | dict[pos++];
        return value;
      } else if (value >= 32 && value <= 246) {
        return value - 139;
      } else if (value >= 247 && value <= 250) {
        return (value - 247) * 256 + dict[pos++] + 108;
      } else if (value >= 251 && value <= 254) {
        return -((value - 251) * 256) - dict[pos++] - 108;
      }
      warn('CFFParser_parseDict: "' + value + '" is a reserved command.');
      return NaN;
    }
    function parseFloatOperand() {
      let str = "";
      const eof = 15;
      const lookup = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ".", "E", "E-", null, "-"];
      const length = dict.length;
      while (pos < length) {
        const b = dict[pos++];
        const b1 = b >> 4;
        const b2 = b & 15;
        if (b1 === eof) {
          break;
        }
        str += lookup[b1];
        if (b2 === eof) {
          break;
        }
        str += lookup[b2];
      }
      return parseFloat(str);
    }
    let operands = [];
    const entries = [];
    pos = 0;
    const end = dict.length;
    while (pos < end) {
      let b = dict[pos];
      if (b <= 21) {
        if (b === 12) {
          b = b << 8 | dict[++pos];
        }
        entries.push([b, operands]);
        operands = [];
        ++pos;
      } else {
        operands.push(parseOperand());
      }
    }
    return entries;
  }
  parseIndex(pos) {
    const cffIndex = new CFFIndex();
    const bytes = this.bytes;
    const count = bytes[pos++] << 8 | bytes[pos++];
    const offsets = [];
    let end = pos;
    let i, ii;
    if (count !== 0) {
      const offsetSize = bytes[pos++];
      const startPos = pos + (count + 1) * offsetSize - 1;
      for (i = 0, ii = count + 1; i < ii; ++i) {
        let offset = 0;
        for (let j = 0; j < offsetSize; ++j) {
          offset <<= 8;
          offset += bytes[pos++];
        }
        offsets.push(startPos + offset);
      }
      end = offsets[count];
    }
    for (i = 0, ii = offsets.length - 1; i < ii; ++i) {
      const offsetStart = offsets[i];
      const offsetEnd = offsets[i + 1];
      cffIndex.add(bytes.subarray(offsetStart, offsetEnd));
    }
    return {
      obj: cffIndex,
      endPos: end
    };
  }
  parseNameIndex(index) {
    const names = [];
    for (let i = 0, ii = index.count; i < ii; ++i) {
      const name = index.get(i);
      names.push(bytesToString(name));
    }
    return names;
  }
  parseStringIndex(index) {
    const strings = new CFFStrings();
    for (let i = 0, ii = index.count; i < ii; ++i) {
      const data = index.get(i);
      strings.add(bytesToString(data));
    }
    return strings;
  }
  createDict(Type, dict, strings) {
    const cffDict = new Type(strings);
    for (const [key, value] of dict) {
      cffDict.setByKey(key, value);
    }
    return cffDict;
  }
  parseCharString(state, data, localSubrIndex, globalSubrIndex) {
    if (!data || state.callDepth > MAX_SUBR_NESTING) {
      return false;
    }
    let stackSize = state.stackSize;
    const stack = state.stack;
    let length = data.length;
    for (let j = 0; j < length;) {
      const value = data[j++];
      let validationCommand = null;
      if (value === 12) {
        const q = data[j++];
        if (q === 0) {
          data[j - 2] = 139;
          data[j - 1] = 22;
          stackSize = 0;
        } else {
          validationCommand = CharstringValidationData12[q];
        }
      } else if (value === 28) {
        stack[stackSize] = (data[j] << 24 | data[j + 1] << 16) >> 16;
        j += 2;
        stackSize++;
      } else if (value === 14) {
        if (stackSize >= 4) {
          stackSize -= 4;
          if (this.seacAnalysisEnabled) {
            state.seac = stack.slice(stackSize, stackSize + 4);
            return false;
          }
        }
        validationCommand = CharstringValidationData[value];
      } else if (value >= 32 && value <= 246) {
        stack[stackSize] = value - 139;
        stackSize++;
      } else if (value >= 247 && value <= 254) {
        stack[stackSize] = value < 251 ? (value - 247 << 8) + data[j] + 108 : -(value - 251 << 8) - data[j] - 108;
        j++;
        stackSize++;
      } else if (value === 255) {
        stack[stackSize] = (data[j] << 24 | data[j + 1] << 16 | data[j + 2] << 8 | data[j + 3]) / 65536;
        j += 4;
        stackSize++;
      } else if (value === 19 || value === 20) {
        state.hints += stackSize >> 1;
        if (state.hints === 0) {
          data.copyWithin(j - 1, j, -1);
          j -= 1;
          length -= 1;
          continue;
        }
        j += state.hints + 7 >> 3;
        stackSize %= 2;
        validationCommand = CharstringValidationData[value];
      } else if (value === 10 || value === 29) {
        const subrsIndex = value === 10 ? localSubrIndex : globalSubrIndex;
        if (!subrsIndex) {
          validationCommand = CharstringValidationData[value];
          warn("Missing subrsIndex for " + validationCommand.id);
          return false;
        }
        let bias = 32768;
        if (subrsIndex.count < 1240) {
          bias = 107;
        } else if (subrsIndex.count < 33900) {
          bias = 1131;
        }
        const subrNumber = stack[--stackSize] + bias;
        if (subrNumber < 0 || subrNumber >= subrsIndex.count || isNaN(subrNumber)) {
          validationCommand = CharstringValidationData[value];
          warn("Out of bounds subrIndex for " + validationCommand.id);
          return false;
        }
        state.stackSize = stackSize;
        state.callDepth++;
        const valid = this.parseCharString(state, subrsIndex.get(subrNumber), localSubrIndex, globalSubrIndex);
        if (!valid) {
          return false;
        }
        state.callDepth--;
        stackSize = state.stackSize;
        continue;
      } else if (value === 11) {
        state.stackSize = stackSize;
        return true;
      } else if (value === 0 && j === data.length) {
        data[j - 1] = 14;
        validationCommand = CharstringValidationData[14];
      } else if (value === 9) {
        data.copyWithin(j - 1, j, -1);
        j -= 1;
        length -= 1;
        continue;
      } else {
        validationCommand = CharstringValidationData[value];
      }
      if (validationCommand) {
        if (validationCommand.stem) {
          state.hints += stackSize >> 1;
          if (value === 3 || value === 23) {
            state.hasVStems = true;
          } else if (state.hasVStems && (value === 1 || value === 18)) {
            warn("CFF stem hints are in wrong order");
            data[j - 1] = value === 1 ? 3 : 23;
          }
        }
        if ("min" in validationCommand) {
          if (!state.undefStack && stackSize < validationCommand.min) {
            warn("Not enough parameters for " + validationCommand.id + "; actual: " + stackSize + ", expected: " + validationCommand.min);
            if (stackSize === 0) {
              data[j - 1] = 14;
              return true;
            }
            return false;
          }
        }
        if (state.firstStackClearing && validationCommand.stackClearing) {
          state.firstStackClearing = false;
          stackSize -= validationCommand.min;
          if (stackSize >= 2 && validationCommand.stem) {
            stackSize %= 2;
          } else if (stackSize > 1) {
            warn("Found too many parameters for stack-clearing command");
          }
          if (stackSize > 0) {
            state.width = stack[stackSize - 1];
          }
        }
        if ("stackDelta" in validationCommand) {
          if ("stackFn" in validationCommand) {
            validationCommand.stackFn(stack, stackSize);
          }
          stackSize += validationCommand.stackDelta;
        } else if (validationCommand.stackClearing) {
          stackSize = 0;
        } else if (validationCommand.resetStack) {
          stackSize = 0;
          state.undefStack = false;
        } else if (validationCommand.undefStack) {
          stackSize = 0;
          state.undefStack = true;
          state.firstStackClearing = false;
        }
      }
    }
    if (length < data.length) {
      data.fill(14, length);
    }
    state.stackSize = stackSize;
    return true;
  }
  parseCharStrings({
    charStrings,
    localSubrIndex,
    globalSubrIndex,
    fdSelect,
    fdArray,
    privateDict
  }) {
    const seacs = [];
    const widths = [];
    const count = charStrings.count;
    for (let i = 0; i < count; i++) {
      const charstring = charStrings.get(i);
      const state = {
        callDepth: 0,
        stackSize: 0,
        stack: [],
        undefStack: true,
        hints: 0,
        firstStackClearing: true,
        seac: null,
        width: null,
        hasVStems: false
      };
      let valid = true;
      let localSubrToUse = null;
      let privateDictToUse = privateDict;
      if (fdSelect && fdArray.length) {
        const fdIndex = fdSelect.getFDIndex(i);
        if (fdIndex === -1) {
          warn("Glyph index is not in fd select.");
          valid = false;
        }
        if (fdIndex >= fdArray.length) {
          warn("Invalid fd index for glyph index.");
          valid = false;
        }
        if (valid) {
          privateDictToUse = fdArray[fdIndex].privateDict;
          localSubrToUse = privateDictToUse.subrsIndex;
        }
      } else if (localSubrIndex) {
        localSubrToUse = localSubrIndex;
      }
      if (valid) {
        valid = this.parseCharString(state, charstring, localSubrToUse, globalSubrIndex);
      }
      if (state.width !== null) {
        const nominalWidth = privateDictToUse.getByName("nominalWidthX");
        widths[i] = nominalWidth + state.width;
      } else {
        const defaultWidth = privateDictToUse.getByName("defaultWidthX");
        widths[i] = defaultWidth;
      }
      if (state.seac !== null) {
        seacs[i] = state.seac;
      }
      if (!valid) {
        charStrings.set(i, new Uint8Array([14]));
      }
    }
    return {
      charStrings,
      seacs,
      widths
    };
  }
  emptyPrivateDictionary(parentDict) {
    const privateDict = this.createDict(CFFPrivateDict, [], parentDict.strings);
    parentDict.setByKey(18, [0, 0]);
    parentDict.privateDict = privateDict;
  }
  parsePrivateDict(parentDict) {
    if (!parentDict.hasName("Private")) {
      this.emptyPrivateDictionary(parentDict);
      return;
    }
    const privateOffset = parentDict.getByName("Private");
    if (!Array.isArray(privateOffset) || privateOffset.length !== 2) {
      parentDict.removeByName("Private");
      return;
    }
    const size = privateOffset[0];
    const offset = privateOffset[1];
    if (size === 0 || offset >= this.bytes.length) {
      this.emptyPrivateDictionary(parentDict);
      return;
    }
    const privateDictEnd = offset + size;
    const dictData = this.bytes.subarray(offset, privateDictEnd);
    const dict = this.parseDict(dictData);
    const privateDict = this.createDict(CFFPrivateDict, dict, parentDict.strings);
    parentDict.privateDict = privateDict;
    if (privateDict.getByName("ExpansionFactor") === 0) {
      privateDict.setByName("ExpansionFactor", 0.06);
    }
    if (!privateDict.getByName("Subrs")) {
      return;
    }
    const subrsOffset = privateDict.getByName("Subrs");
    const relativeOffset = offset + subrsOffset;
    if (subrsOffset === 0 || relativeOffset >= this.bytes.length) {
      this.emptyPrivateDictionary(parentDict);
      return;
    }
    const subrsIndex = this.parseIndex(relativeOffset);
    privateDict.subrsIndex = subrsIndex.obj;
  }
  parseCharsets(pos, length, strings, cid) {
    if (pos === 0) {
      return new CFFCharset(true, CFFCharsetPredefinedTypes.ISO_ADOBE, ISOAdobeCharset);
    } else if (pos === 1) {
      return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT, ExpertCharset);
    } else if (pos === 2) {
      return new CFFCharset(true, CFFCharsetPredefinedTypes.EXPERT_SUBSET, ExpertSubsetCharset);
    }
    const bytes = this.bytes;
    const start = pos;
    const format = bytes[pos++];
    const charset = [cid ? 0 : ".notdef"];
    let id, count, i;
    length -= 1;
    switch (format) {
      case 0:
        for (i = 0; i < length; i++) {
          id = bytes[pos++] << 8 | bytes[pos++];
          charset.push(cid ? id : strings.get(id));
        }
        break;
      case 1:
        while (charset.length <= length) {
          id = bytes[pos++] << 8 | bytes[pos++];
          count = bytes[pos++];
          for (i = 0; i <= count; i++) {
            charset.push(cid ? id++ : strings.get(id++));
          }
        }
        break;
      case 2:
        while (charset.length <= length) {
          id = bytes[pos++] << 8 | bytes[pos++];
          count = bytes[pos++] << 8 | bytes[pos++];
          for (i = 0; i <= count; i++) {
            charset.push(cid ? id++ : strings.get(id++));
          }
        }
        break;
      default:
        throw new FormatError("Unknown charset format");
    }
    const end = pos;
    const raw = bytes.subarray(start, end);
    return new CFFCharset(false, format, charset, raw);
  }
  parseEncoding(pos, properties, strings, charset) {
    const encoding = Object.create(null);
    const bytes = this.bytes;
    let predefined = false;
    let format, i, ii;
    let raw = null;
    function readSupplement() {
      const supplementsCount = bytes[pos++];
      for (i = 0; i < supplementsCount; i++) {
        const code = bytes[pos++];
        const sid = (bytes[pos++] << 8) + (bytes[pos++] & 0xff);
        encoding[code] = charset.indexOf(strings.get(sid));
      }
    }
    if (pos === 0 || pos === 1) {
      predefined = true;
      format = pos;
      const baseEncoding = pos ? ExpertEncoding : StandardEncoding;
      for (i = 0, ii = charset.length; i < ii; i++) {
        const index = baseEncoding.indexOf(charset[i]);
        if (index !== -1) {
          encoding[index] = i;
        }
      }
    } else {
      const dataStart = pos;
      format = bytes[pos++];
      switch (format & 0x7f) {
        case 0:
          const glyphsCount = bytes[pos++];
          for (i = 1; i <= glyphsCount; i++) {
            encoding[bytes[pos++]] = i;
          }
          break;
        case 1:
          const rangesCount = bytes[pos++];
          let gid = 1;
          for (i = 0; i < rangesCount; i++) {
            const start = bytes[pos++];
            const left = bytes[pos++];
            for (let j = start; j <= start + left; j++) {
              encoding[j] = gid++;
            }
          }
          break;
        default:
          throw new FormatError(`Unknown encoding format: ${format} in CFF`);
      }
      const dataEnd = pos;
      if (format & 0x80) {
        bytes[dataStart] &= 0x7f;
        readSupplement();
      }
      raw = bytes.subarray(dataStart, dataEnd);
    }
    format &= 0x7f;
    return new CFFEncoding(predefined, format, encoding, raw);
  }
  parseFDSelect(pos, length) {
    const bytes = this.bytes;
    const format = bytes[pos++];
    const fdSelect = [];
    let i;
    switch (format) {
      case 0:
        for (i = 0; i < length; ++i) {
          const id = bytes[pos++];
          fdSelect.push(id);
        }
        break;
      case 3:
        const rangesCount = bytes[pos++] << 8 | bytes[pos++];
        for (i = 0; i < rangesCount; ++i) {
          let first = bytes[pos++] << 8 | bytes[pos++];
          if (i === 0 && first !== 0) {
            warn("parseFDSelect: The first range must have a first GID of 0" + " -- trying to recover.");
            first = 0;
          }
          const fdIndex = bytes[pos++];
          const next = bytes[pos] << 8 | bytes[pos + 1];
          for (let j = first; j < next; ++j) {
            fdSelect.push(fdIndex);
          }
        }
        pos += 2;
        break;
      default:
        throw new FormatError(`parseFDSelect: Unknown format "${format}".`);
    }
    if (fdSelect.length !== length) {
      throw new FormatError("parseFDSelect: Invalid font data.");
    }
    return new CFFFDSelect(format, fdSelect);
  }
}
class CFF {
  constructor() {
    this.header = null;
    this.names = [];
    this.topDict = null;
    this.strings = new CFFStrings();
    this.globalSubrIndex = null;
    this.encoding = null;
    this.charset = null;
    this.charStrings = null;
    this.fdArray = [];
    this.fdSelect = null;
    this.isCIDFont = false;
  }
  duplicateFirstGlyph() {
    if (this.charStrings.count >= 65535) {
      warn("Not enough space in charstrings to duplicate first glyph.");
      return;
    }
    const glyphZero = this.charStrings.get(0);
    this.charStrings.add(glyphZero);
    if (this.isCIDFont) {
      this.fdSelect.fdSelect.push(this.fdSelect.fdSelect[0]);
    }
  }
  hasGlyphId(id) {
    if (id < 0 || id >= this.charStrings.count) {
      return false;
    }
    const glyph = this.charStrings.get(id);
    return glyph.length > 0;
  }
}
class CFFHeader {
  constructor(major, minor, hdrSize, offSize) {
    this.major = major;
    this.minor = minor;
    this.hdrSize = hdrSize;
    this.offSize = offSize;
  }
}
class CFFStrings {
  constructor() {
    this.strings = [];
  }
  get(index) {
    if (index >= 0 && index <= NUM_STANDARD_CFF_STRINGS - 1) {
      return CFFStandardStrings[index];
    }
    if (index - NUM_STANDARD_CFF_STRINGS <= this.strings.length) {
      return this.strings[index - NUM_STANDARD_CFF_STRINGS];
    }
    return CFFStandardStrings[0];
  }
  getSID(str) {
    let index = CFFStandardStrings.indexOf(str);
    if (index !== -1) {
      return index;
    }
    index = this.strings.indexOf(str);
    if (index !== -1) {
      return index + NUM_STANDARD_CFF_STRINGS;
    }
    return -1;
  }
  add(value) {
    this.strings.push(value);
  }
  get count() {
    return this.strings.length;
  }
}
class CFFIndex {
  constructor() {
    this.objects = [];
    this.length = 0;
  }
  add(data) {
    this.length += data.length;
    this.objects.push(data);
  }
  set(index, data) {
    this.length += data.length - this.objects[index].length;
    this.objects[index] = data;
  }
  get(index) {
    return this.objects[index];
  }
  get count() {
    return this.objects.length;
  }
}
class CFFDict {
  constructor(tables, strings) {
    this.keyToNameMap = tables.keyToNameMap;
    this.nameToKeyMap = tables.nameToKeyMap;
    this.defaults = tables.defaults;
    this.types = tables.types;
    this.opcodes = tables.opcodes;
    this.order = tables.order;
    this.strings = strings;
    this.values = Object.create(null);
  }
  setByKey(key, value) {
    if (!(key in this.keyToNameMap)) {
      return false;
    }
    if (value.length === 0) {
      return true;
    }
    for (const val of value) {
      if (isNaN(val)) {
        warn(`Invalid CFFDict value: "${value}" for key "${key}".`);
        return true;
      }
    }
    const type = this.types[key];
    if (type === "num" || type === "sid" || type === "offset") {
      value = value[0];
    }
    this.values[key] = value;
    return true;
  }
  setByName(name, value) {
    if (!(name in this.nameToKeyMap)) {
      throw new FormatError(`Invalid dictionary name "${name}"`);
    }
    this.values[this.nameToKeyMap[name]] = value;
  }
  hasName(name) {
    return this.nameToKeyMap[name] in this.values;
  }
  getByName(name) {
    if (!(name in this.nameToKeyMap)) {
      throw new FormatError(`Invalid dictionary name ${name}"`);
    }
    const key = this.nameToKeyMap[name];
    if (!(key in this.values)) {
      return this.defaults[key];
    }
    return this.values[key];
  }
  removeByName(name) {
    delete this.values[this.nameToKeyMap[name]];
  }
  static createTables(layout) {
    const tables = {
      keyToNameMap: {},
      nameToKeyMap: {},
      defaults: {},
      types: {},
      opcodes: {},
      order: []
    };
    for (const entry of layout) {
      const key = Array.isArray(entry[0]) ? (entry[0][0] << 8) + entry[0][1] : entry[0];
      tables.keyToNameMap[key] = entry[1];
      tables.nameToKeyMap[entry[1]] = key;
      tables.types[key] = entry[2];
      tables.defaults[key] = entry[3];
      tables.opcodes[key] = Array.isArray(entry[0]) ? entry[0] : [entry[0]];
      tables.order.push(key);
    }
    return tables;
  }
}
const CFFTopDictLayout = [[[12, 30], "ROS", ["sid", "sid", "num"], null], [[12, 20], "SyntheticBase", "num", null], [0, "version", "sid", null], [1, "Notice", "sid", null], [[12, 0], "Copyright", "sid", null], [2, "FullName", "sid", null], [3, "FamilyName", "sid", null], [4, "Weight", "sid", null], [[12, 1], "isFixedPitch", "num", 0], [[12, 2], "ItalicAngle", "num", 0], [[12, 3], "UnderlinePosition", "num", -100], [[12, 4], "UnderlineThickness", "num", 50], [[12, 5], "PaintType", "num", 0], [[12, 6], "CharstringType", "num", 2], [[12, 7], "FontMatrix", ["num", "num", "num", "num", "num", "num"], [0.001, 0, 0, 0.001, 0, 0]], [13, "UniqueID", "num", null], [5, "FontBBox", ["num", "num", "num", "num"], [0, 0, 0, 0]], [[12, 8], "StrokeWidth", "num", 0], [14, "XUID", "array", null], [15, "charset", "offset", 0], [16, "Encoding", "offset", 0], [17, "CharStrings", "offset", 0], [18, "Private", ["offset", "offset"], null], [[12, 21], "PostScript", "sid", null], [[12, 22], "BaseFontName", "sid", null], [[12, 23], "BaseFontBlend", "delta", null], [[12, 31], "CIDFontVersion", "num", 0], [[12, 32], "CIDFontRevision", "num", 0], [[12, 33], "CIDFontType", "num", 0], [[12, 34], "CIDCount", "num", 8720], [[12, 35], "UIDBase", "num", null], [[12, 37], "FDSelect", "offset", null], [[12, 36], "FDArray", "offset", null], [[12, 38], "FontName", "sid", null]];
class CFFTopDict extends CFFDict {
  static get tables() {
    return shadow(this, "tables", this.createTables(CFFTopDictLayout));
  }
  constructor(strings) {
    super(CFFTopDict.tables, strings);
    this.privateDict = null;
  }
}
const CFFPrivateDictLayout = [[6, "BlueValues", "delta", null], [7, "OtherBlues", "delta", null], [8, "FamilyBlues", "delta", null], [9, "FamilyOtherBlues", "delta", null], [[12, 9], "BlueScale", "num", 0.039625], [[12, 10], "BlueShift", "num", 7], [[12, 11], "BlueFuzz", "num", 1], [10, "StdHW", "num", null], [11, "StdVW", "num", null], [[12, 12], "StemSnapH", "delta", null], [[12, 13], "StemSnapV", "delta", null], [[12, 14], "ForceBold", "num", 0], [[12, 17], "LanguageGroup", "num", 0], [[12, 18], "ExpansionFactor", "num", 0.06], [[12, 19], "initialRandomSeed", "num", 0], [20, "defaultWidthX", "num", 0], [21, "nominalWidthX", "num", 0], [19, "Subrs", "offset", null]];
class CFFPrivateDict extends CFFDict {
  static get tables() {
    return shadow(this, "tables", this.createTables(CFFPrivateDictLayout));
  }
  constructor(strings) {
    super(CFFPrivateDict.tables, strings);
    this.subrsIndex = null;
  }
}
const CFFCharsetPredefinedTypes = {
  ISO_ADOBE: 0,
  EXPERT: 1,
  EXPERT_SUBSET: 2
};
class CFFCharset {
  constructor(predefined, format, charset, raw) {
    this.predefined = predefined;
    this.format = format;
    this.charset = charset;
    this.raw = raw;
  }
}
class CFFEncoding {
  constructor(predefined, format, encoding, raw) {
    this.predefined = predefined;
    this.format = format;
    this.encoding = encoding;
    this.raw = raw;
  }
}
class CFFFDSelect {
  constructor(format, fdSelect) {
    this.format = format;
    this.fdSelect = fdSelect;
  }
  getFDIndex(glyphIndex) {
    if (glyphIndex < 0 || glyphIndex >= this.fdSelect.length) {
      return -1;
    }
    return this.fdSelect[glyphIndex];
  }
}
class CFFOffsetTracker {
  constructor() {
    this.offsets = Object.create(null);
  }
  isTracking(key) {
    return key in this.offsets;
  }
  track(key, location) {
    if (key in this.offsets) {
      throw new FormatError(`Already tracking location of ${key}`);
    }
    this.offsets[key] = location;
  }
  offset(value) {
    for (const key in this.offsets) {
      this.offsets[key] += value;
    }
  }
  setEntryLocation(key, values, output) {
    if (!(key in this.offsets)) {
      throw new FormatError(`Not tracking location of ${key}`);
    }
    const data = output.data;
    const dataOffset = this.offsets[key];
    const size = 5;
    for (let i = 0, ii = values.length; i < ii; ++i) {
      const offset0 = i * size + dataOffset;
      const offset1 = offset0 + 1;
      const offset2 = offset0 + 2;
      const offset3 = offset0 + 3;
      const offset4 = offset0 + 4;
      if (data[offset0] !== 0x1d || data[offset1] !== 0 || data[offset2] !== 0 || data[offset3] !== 0 || data[offset4] !== 0) {
        throw new FormatError("writing to an offset that is not empty");
      }
      const value = values[i];
      data[offset0] = 0x1d;
      data[offset1] = value >> 24 & 0xff;
      data[offset2] = value >> 16 & 0xff;
      data[offset3] = value >> 8 & 0xff;
      data[offset4] = value & 0xff;
    }
  }
}
class CFFCompiler {
  constructor(cff) {
    this.cff = cff;
  }
  compile() {
    const cff = this.cff;
    const output = {
      data: [],
      length: 0,
      add(data) {
        try {
          this.data.push(...data);
        } catch {
          this.data = this.data.concat(data);
        }
        this.length = this.data.length;
      }
    };
    const header = this.compileHeader(cff.header);
    output.add(header);
    const nameIndex = this.compileNameIndex(cff.names);
    output.add(nameIndex);
    if (cff.isCIDFont) {
      if (cff.topDict.hasName("FontMatrix")) {
        const base = cff.topDict.getByName("FontMatrix");
        cff.topDict.removeByName("FontMatrix");
        for (const subDict of cff.fdArray) {
          let matrix = base.slice(0);
          if (subDict.hasName("FontMatrix")) {
            matrix = Util.transform(matrix, subDict.getByName("FontMatrix"));
          }
          subDict.setByName("FontMatrix", matrix);
        }
      }
    }
    const xuid = cff.topDict.getByName("XUID");
    if (xuid?.length > 16) {
      cff.topDict.removeByName("XUID");
    }
    cff.topDict.setByName("charset", 0);
    let compiled = this.compileTopDicts([cff.topDict], output.length, cff.isCIDFont);
    output.add(compiled.output);
    const topDictTracker = compiled.trackers[0];
    const stringIndex = this.compileStringIndex(cff.strings.strings);
    output.add(stringIndex);
    const globalSubrIndex = this.compileIndex(cff.globalSubrIndex);
    output.add(globalSubrIndex);
    if (cff.encoding && cff.topDict.hasName("Encoding")) {
      if (cff.encoding.predefined) {
        topDictTracker.setEntryLocation("Encoding", [cff.encoding.format], output);
      } else {
        const encoding = this.compileEncoding(cff.encoding);
        topDictTracker.setEntryLocation("Encoding", [output.length], output);
        output.add(encoding);
      }
    }
    const charset = this.compileCharset(cff.charset, cff.charStrings.count, cff.strings, cff.isCIDFont);
    topDictTracker.setEntryLocation("charset", [output.length], output);
    output.add(charset);
    const charStrings = this.compileCharStrings(cff.charStrings);
    topDictTracker.setEntryLocation("CharStrings", [output.length], output);
    output.add(charStrings);
    if (cff.isCIDFont) {
      topDictTracker.setEntryLocation("FDSelect", [output.length], output);
      const fdSelect = this.compileFDSelect(cff.fdSelect);
      output.add(fdSelect);
      compiled = this.compileTopDicts(cff.fdArray, output.length, true);
      topDictTracker.setEntryLocation("FDArray", [output.length], output);
      output.add(compiled.output);
      const fontDictTrackers = compiled.trackers;
      this.compilePrivateDicts(cff.fdArray, fontDictTrackers, output);
    }
    this.compilePrivateDicts([cff.topDict], [topDictTracker], output);
    output.add([0]);
    return output.data;
  }
  encodeNumber(value) {
    if (Number.isInteger(value)) {
      return this.encodeInteger(value);
    }
    return this.encodeFloat(value);
  }
  static get EncodeFloatRegExp() {
    return shadow(this, "EncodeFloatRegExp", /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/);
  }
  encodeFloat(num) {
    let value = num.toString();
    const m = CFFCompiler.EncodeFloatRegExp.exec(value);
    if (m) {
      const epsilon = parseFloat("1e" + ((m[2] ? +m[2] : 0) + m[1].length));
      value = (Math.round(num * epsilon) / epsilon).toString();
    }
    let nibbles = "";
    let i, ii;
    for (i = 0, ii = value.length; i < ii; ++i) {
      const a = value[i];
      if (a === "e") {
        nibbles += value[++i] === "-" ? "c" : "b";
      } else if (a === ".") {
        nibbles += "a";
      } else if (a === "-") {
        nibbles += "e";
      } else {
        nibbles += a;
      }
    }
    nibbles += nibbles.length & 1 ? "f" : "ff";
    const out = [30];
    for (i = 0, ii = nibbles.length; i < ii; i += 2) {
      out.push(parseInt(nibbles.substring(i, i + 2), 16));
    }
    return out;
  }
  encodeInteger(value) {
    let code;
    if (value >= -107 && value <= 107) {
      code = [value + 139];
    } else if (value >= 108 && value <= 1131) {
      value -= 108;
      code = [(value >> 8) + 247, value & 0xff];
    } else if (value >= -1131 && value <= -108) {
      value = -value - 108;
      code = [(value >> 8) + 251, value & 0xff];
    } else if (value >= -32768 && value <= 32767) {
      code = [0x1c, value >> 8 & 0xff, value & 0xff];
    } else {
      code = [0x1d, value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff];
    }
    return code;
  }
  compileHeader(header) {
    return [header.major, header.minor, 4, header.offSize];
  }
  compileNameIndex(names) {
    const nameIndex = new CFFIndex();
    for (const name of names) {
      const length = Math.min(name.length, 127);
      let sanitizedName = new Array(length);
      for (let j = 0; j < length; j++) {
        let char = name[j];
        if (char < "!" || char > "~" || char === "[" || char === "]" || char === "(" || char === ")" || char === "{" || char === "}" || char === "<" || char === ">" || char === "/" || char === "%") {
          char = "_";
        }
        sanitizedName[j] = char;
      }
      sanitizedName = sanitizedName.join("");
      if (sanitizedName === "") {
        sanitizedName = "Bad_Font_Name";
      }
      nameIndex.add(stringToBytes(sanitizedName));
    }
    return this.compileIndex(nameIndex);
  }
  compileTopDicts(dicts, length, removeCidKeys) {
    const fontDictTrackers = [];
    let fdArrayIndex = new CFFIndex();
    for (const fontDict of dicts) {
      if (removeCidKeys) {
        fontDict.removeByName("CIDFontVersion");
        fontDict.removeByName("CIDFontRevision");
        fontDict.removeByName("CIDFontType");
        fontDict.removeByName("CIDCount");
        fontDict.removeByName("UIDBase");
      }
      const fontDictTracker = new CFFOffsetTracker();
      const fontDictData = this.compileDict(fontDict, fontDictTracker);
      fontDictTrackers.push(fontDictTracker);
      fdArrayIndex.add(fontDictData);
      fontDictTracker.offset(length);
    }
    fdArrayIndex = this.compileIndex(fdArrayIndex, fontDictTrackers);
    return {
      trackers: fontDictTrackers,
      output: fdArrayIndex
    };
  }
  compilePrivateDicts(dicts, trackers, output) {
    for (let i = 0, ii = dicts.length; i < ii; ++i) {
      const fontDict = dicts[i];
      const privateDict = fontDict.privateDict;
      if (!privateDict || !fontDict.hasName("Private")) {
        throw new FormatError("There must be a private dictionary.");
      }
      const privateDictTracker = new CFFOffsetTracker();
      const privateDictData = this.compileDict(privateDict, privateDictTracker);
      let outputLength = output.length;
      privateDictTracker.offset(outputLength);
      if (!privateDictData.length) {
        outputLength = 0;
      }
      trackers[i].setEntryLocation("Private", [privateDictData.length, outputLength], output);
      output.add(privateDictData);
      if (privateDict.subrsIndex && privateDict.hasName("Subrs")) {
        const subrs = this.compileIndex(privateDict.subrsIndex);
        privateDictTracker.setEntryLocation("Subrs", [privateDictData.length], output);
        output.add(subrs);
      }
    }
  }
  compileDict(dict, offsetTracker) {
    const out = [];
    for (const key of dict.order) {
      if (!(key in dict.values)) {
        continue;
      }
      let values = dict.values[key];
      let types = dict.types[key];
      if (!Array.isArray(types)) {
        types = [types];
      }
      if (!Array.isArray(values)) {
        values = [values];
      }
      if (values.length === 0) {
        continue;
      }
      for (let j = 0, jj = types.length; j < jj; ++j) {
        const type = types[j];
        const value = values[j];
        switch (type) {
          case "num":
          case "sid":
            out.push(...this.encodeNumber(value));
            break;
          case "offset":
            const name = dict.keyToNameMap[key];
            if (!offsetTracker.isTracking(name)) {
              offsetTracker.track(name, out.length);
            }
            out.push(0x1d, 0, 0, 0, 0);
            break;
          case "array":
          case "delta":
            out.push(...this.encodeNumber(value));
            for (let k = 1, kk = values.length; k < kk; ++k) {
              out.push(...this.encodeNumber(values[k]));
            }
            break;
          default:
            throw new FormatError(`Unknown data type of ${type}`);
        }
      }
      out.push(...dict.opcodes[key]);
    }
    return out;
  }
  compileStringIndex(strings) {
    const stringIndex = new CFFIndex();
    for (const string of strings) {
      stringIndex.add(stringToBytes(string));
    }
    return this.compileIndex(stringIndex);
  }
  compileCharStrings(charStrings) {
    const charStringsIndex = new CFFIndex();
    for (let i = 0; i < charStrings.count; i++) {
      const glyph = charStrings.get(i);
      if (glyph.length === 0) {
        charStringsIndex.add(new Uint8Array([0x8b, 0x0e]));
        continue;
      }
      charStringsIndex.add(glyph);
    }
    return this.compileIndex(charStringsIndex);
  }
  compileCharset(charset, numGlyphs, strings, isCIDFont) {
    let out;
    const numGlyphsLessNotDef = numGlyphs - 1;
    if (isCIDFont) {
      out = new Uint8Array([2, 0, 0, numGlyphsLessNotDef >> 8 & 0xff, numGlyphsLessNotDef & 0xff]);
    } else {
      const length = 1 + numGlyphsLessNotDef * 2;
      out = new Uint8Array(length);
      out[0] = 0;
      let charsetIndex = 0;
      const numCharsets = charset.charset.length;
      let warned = false;
      for (let i = 1; i < out.length; i += 2) {
        let sid = 0;
        if (charsetIndex < numCharsets) {
          const name = charset.charset[charsetIndex++];
          sid = strings.getSID(name);
          if (sid === -1) {
            sid = 0;
            if (!warned) {
              warned = true;
              warn(`Couldn't find ${name} in CFF strings`);
            }
          }
        }
        out[i] = sid >> 8 & 0xff;
        out[i + 1] = sid & 0xff;
      }
    }
    return this.compileTypedArray(out);
  }
  compileEncoding(encoding) {
    return this.compileTypedArray(encoding.raw);
  }
  compileFDSelect(fdSelect) {
    const format = fdSelect.format;
    let out, i;
    switch (format) {
      case 0:
        out = new Uint8Array(1 + fdSelect.fdSelect.length);
        out[0] = format;
        for (i = 0; i < fdSelect.fdSelect.length; i++) {
          out[i + 1] = fdSelect.fdSelect[i];
        }
        break;
      case 3:
        const start = 0;
        let lastFD = fdSelect.fdSelect[0];
        const ranges = [format, 0, 0, start >> 8 & 0xff, start & 0xff, lastFD];
        for (i = 1; i < fdSelect.fdSelect.length; i++) {
          const currentFD = fdSelect.fdSelect[i];
          if (currentFD !== lastFD) {
            ranges.push(i >> 8 & 0xff, i & 0xff, currentFD);
            lastFD = currentFD;
          }
        }
        const numRanges = (ranges.length - 3) / 3;
        ranges[1] = numRanges >> 8 & 0xff;
        ranges[2] = numRanges & 0xff;
        ranges.push(i >> 8 & 0xff, i & 0xff);
        out = new Uint8Array(ranges);
        break;
    }
    return this.compileTypedArray(out);
  }
  compileTypedArray(data) {
    return Array.from(data);
  }
  compileIndex(index, trackers = []) {
    const objects = index.objects;
    const count = objects.length;
    if (count === 0) {
      return [0, 0];
    }
    const data = [count >> 8 & 0xff, count & 0xff];
    let lastOffset = 1,
      i;
    for (i = 0; i < count; ++i) {
      lastOffset += objects[i].length;
    }
    let offsetSize;
    if (lastOffset < 0x100) {
      offsetSize = 1;
    } else if (lastOffset < 0x10000) {
      offsetSize = 2;
    } else if (lastOffset < 0x1000000) {
      offsetSize = 3;
    } else {
      offsetSize = 4;
    }
    data.push(offsetSize);
    let relativeOffset = 1;
    for (i = 0; i < count + 1; i++) {
      if (offsetSize === 1) {
        data.push(relativeOffset & 0xff);
      } else if (offsetSize === 2) {
        data.push(relativeOffset >> 8 & 0xff, relativeOffset & 0xff);
      } else if (offsetSize === 3) {
        data.push(relativeOffset >> 16 & 0xff, relativeOffset >> 8 & 0xff, relativeOffset & 0xff);
      } else {
        data.push(relativeOffset >>> 24 & 0xff, relativeOffset >> 16 & 0xff, relativeOffset >> 8 & 0xff, relativeOffset & 0xff);
      }
      if (objects[i]) {
        relativeOffset += objects[i].length;
      }
    }
    for (i = 0; i < count; i++) {
      if (trackers[i]) {
        trackers[i].offset(data.length);
      }
      data.push(...objects[i]);
    }
    return data;
  }
}

;// CONCATENATED MODULE: ./src/core/glyphlist.js

const getGlyphsUnicode = getLookupTableFactory(function (t) {
  t.A = 0x0041;
  t.AE = 0x00c6;
  t.AEacute = 0x01fc;
  t.AEmacron = 0x01e2;
  t.AEsmall = 0xf7e6;
  t.Aacute = 0x00c1;
  t.Aacutesmall = 0xf7e1;
  t.Abreve = 0x0102;
  t.Abreveacute = 0x1eae;
  t.Abrevecyrillic = 0x04d0;
  t.Abrevedotbelow = 0x1eb6;
  t.Abrevegrave = 0x1eb0;
  t.Abrevehookabove = 0x1eb2;
  t.Abrevetilde = 0x1eb4;
  t.Acaron = 0x01cd;
  t.Acircle = 0x24b6;
  t.Acircumflex = 0x00c2;
  t.Acircumflexacute = 0x1ea4;
  t.Acircumflexdotbelow = 0x1eac;
  t.Acircumflexgrave = 0x1ea6;
  t.Acircumflexhookabove = 0x1ea8;
  t.Acircumflexsmall = 0xf7e2;
  t.Acircumflextilde = 0x1eaa;
  t.Acute = 0xf6c9;
  t.Acutesmall = 0xf7b4;
  t.Acyrillic = 0x0410;
  t.Adblgrave = 0x0200;
  t.Adieresis = 0x00c4;
  t.Adieresiscyrillic = 0x04d2;
  t.Adieresismacron = 0x01de;
  t.Adieresissmall = 0xf7e4;
  t.Adotbelow = 0x1ea0;
  t.Adotmacron = 0x01e0;
  t.Agrave = 0x00c0;
  t.Agravesmall = 0xf7e0;
  t.Ahookabove = 0x1ea2;
  t.Aiecyrillic = 0x04d4;
  t.Ainvertedbreve = 0x0202;
  t.Alpha = 0x0391;
  t.Alphatonos = 0x0386;
  t.Amacron = 0x0100;
  t.Amonospace = 0xff21;
  t.Aogonek = 0x0104;
  t.Aring = 0x00c5;
  t.Aringacute = 0x01fa;
  t.Aringbelow = 0x1e00;
  t.Aringsmall = 0xf7e5;
  t.Asmall = 0xf761;
  t.Atilde = 0x00c3;
  t.Atildesmall = 0xf7e3;
  t.Aybarmenian = 0x0531;
  t.B = 0x0042;
  t.Bcircle = 0x24b7;
  t.Bdotaccent = 0x1e02;
  t.Bdotbelow = 0x1e04;
  t.Becyrillic = 0x0411;
  t.Benarmenian = 0x0532;
  t.Beta = 0x0392;
  t.Bhook = 0x0181;
  t.Blinebelow = 0x1e06;
  t.Bmonospace = 0xff22;
  t.Brevesmall = 0xf6f4;
  t.Bsmall = 0xf762;
  t.Btopbar = 0x0182;
  t.C = 0x0043;
  t.Caarmenian = 0x053e;
  t.Cacute = 0x0106;
  t.Caron = 0xf6ca;
  t.Caronsmall = 0xf6f5;
  t.Ccaron = 0x010c;
  t.Ccedilla = 0x00c7;
  t.Ccedillaacute = 0x1e08;
  t.Ccedillasmall = 0xf7e7;
  t.Ccircle = 0x24b8;
  t.Ccircumflex = 0x0108;
  t.Cdot = 0x010a;
  t.Cdotaccent = 0x010a;
  t.Cedillasmall = 0xf7b8;
  t.Chaarmenian = 0x0549;
  t.Cheabkhasiancyrillic = 0x04bc;
  t.Checyrillic = 0x0427;
  t.Chedescenderabkhasiancyrillic = 0x04be;
  t.Chedescendercyrillic = 0x04b6;
  t.Chedieresiscyrillic = 0x04f4;
  t.Cheharmenian = 0x0543;
  t.Chekhakassiancyrillic = 0x04cb;
  t.Cheverticalstrokecyrillic = 0x04b8;
  t.Chi = 0x03a7;
  t.Chook = 0x0187;
  t.Circumflexsmall = 0xf6f6;
  t.Cmonospace = 0xff23;
  t.Coarmenian = 0x0551;
  t.Csmall = 0xf763;
  t.D = 0x0044;
  t.DZ = 0x01f1;
  t.DZcaron = 0x01c4;
  t.Daarmenian = 0x0534;
  t.Dafrican = 0x0189;
  t.Dcaron = 0x010e;
  t.Dcedilla = 0x1e10;
  t.Dcircle = 0x24b9;
  t.Dcircumflexbelow = 0x1e12;
  t.Dcroat = 0x0110;
  t.Ddotaccent = 0x1e0a;
  t.Ddotbelow = 0x1e0c;
  t.Decyrillic = 0x0414;
  t.Deicoptic = 0x03ee;
  t.Delta = 0x2206;
  t.Deltagreek = 0x0394;
  t.Dhook = 0x018a;
  t.Dieresis = 0xf6cb;
  t.DieresisAcute = 0xf6cc;
  t.DieresisGrave = 0xf6cd;
  t.Dieresissmall = 0xf7a8;
  t.Digammagreek = 0x03dc;
  t.Djecyrillic = 0x0402;
  t.Dlinebelow = 0x1e0e;
  t.Dmonospace = 0xff24;
  t.Dotaccentsmall = 0xf6f7;
  t.Dslash = 0x0110;
  t.Dsmall = 0xf764;
  t.Dtopbar = 0x018b;
  t.Dz = 0x01f2;
  t.Dzcaron = 0x01c5;
  t.Dzeabkhasiancyrillic = 0x04e0;
  t.Dzecyrillic = 0x0405;
  t.Dzhecyrillic = 0x040f;
  t.E = 0x0045;
  t.Eacute = 0x00c9;
  t.Eacutesmall = 0xf7e9;
  t.Ebreve = 0x0114;
  t.Ecaron = 0x011a;
  t.Ecedillabreve = 0x1e1c;
  t.Echarmenian = 0x0535;
  t.Ecircle = 0x24ba;
  t.Ecircumflex = 0x00ca;
  t.Ecircumflexacute = 0x1ebe;
  t.Ecircumflexbelow = 0x1e18;
  t.Ecircumflexdotbelow = 0x1ec6;
  t.Ecircumflexgrave = 0x1ec0;
  t.Ecircumflexhookabove = 0x1ec2;
  t.Ecircumflexsmall = 0xf7ea;
  t.Ecircumflextilde = 0x1ec4;
  t.Ecyrillic = 0x0404;
  t.Edblgrave = 0x0204;
  t.Edieresis = 0x00cb;
  t.Edieresissmall = 0xf7eb;
  t.Edot = 0x0116;
  t.Edotaccent = 0x0116;
  t.Edotbelow = 0x1eb8;
  t.Efcyrillic = 0x0424;
  t.Egrave = 0x00c8;
  t.Egravesmall = 0xf7e8;
  t.Eharmenian = 0x0537;
  t.Ehookabove = 0x1eba;
  t.Eightroman = 0x2167;
  t.Einvertedbreve = 0x0206;
  t.Eiotifiedcyrillic = 0x0464;
  t.Elcyrillic = 0x041b;
  t.Elevenroman = 0x216a;
  t.Emacron = 0x0112;
  t.Emacronacute = 0x1e16;
  t.Emacrongrave = 0x1e14;
  t.Emcyrillic = 0x041c;
  t.Emonospace = 0xff25;
  t.Encyrillic = 0x041d;
  t.Endescendercyrillic = 0x04a2;
  t.Eng = 0x014a;
  t.Enghecyrillic = 0x04a4;
  t.Enhookcyrillic = 0x04c7;
  t.Eogonek = 0x0118;
  t.Eopen = 0x0190;
  t.Epsilon = 0x0395;
  t.Epsilontonos = 0x0388;
  t.Ercyrillic = 0x0420;
  t.Ereversed = 0x018e;
  t.Ereversedcyrillic = 0x042d;
  t.Escyrillic = 0x0421;
  t.Esdescendercyrillic = 0x04aa;
  t.Esh = 0x01a9;
  t.Esmall = 0xf765;
  t.Eta = 0x0397;
  t.Etarmenian = 0x0538;
  t.Etatonos = 0x0389;
  t.Eth = 0x00d0;
  t.Ethsmall = 0xf7f0;
  t.Etilde = 0x1ebc;
  t.Etildebelow = 0x1e1a;
  t.Euro = 0x20ac;
  t.Ezh = 0x01b7;
  t.Ezhcaron = 0x01ee;
  t.Ezhreversed = 0x01b8;
  t.F = 0x0046;
  t.Fcircle = 0x24bb;
  t.Fdotaccent = 0x1e1e;
  t.Feharmenian = 0x0556;
  t.Feicoptic = 0x03e4;
  t.Fhook = 0x0191;
  t.Fitacyrillic = 0x0472;
  t.Fiveroman = 0x2164;
  t.Fmonospace = 0xff26;
  t.Fourroman = 0x2163;
  t.Fsmall = 0xf766;
  t.G = 0x0047;
  t.GBsquare = 0x3387;
  t.Gacute = 0x01f4;
  t.Gamma = 0x0393;
  t.Gammaafrican = 0x0194;
  t.Gangiacoptic = 0x03ea;
  t.Gbreve = 0x011e;
  t.Gcaron = 0x01e6;
  t.Gcedilla = 0x0122;
  t.Gcircle = 0x24bc;
  t.Gcircumflex = 0x011c;
  t.Gcommaaccent = 0x0122;
  t.Gdot = 0x0120;
  t.Gdotaccent = 0x0120;
  t.Gecyrillic = 0x0413;
  t.Ghadarmenian = 0x0542;
  t.Ghemiddlehookcyrillic = 0x0494;
  t.Ghestrokecyrillic = 0x0492;
  t.Gheupturncyrillic = 0x0490;
  t.Ghook = 0x0193;
  t.Gimarmenian = 0x0533;
  t.Gjecyrillic = 0x0403;
  t.Gmacron = 0x1e20;
  t.Gmonospace = 0xff27;
  t.Grave = 0xf6ce;
  t.Gravesmall = 0xf760;
  t.Gsmall = 0xf767;
  t.Gsmallhook = 0x029b;
  t.Gstroke = 0x01e4;
  t.H = 0x0048;
  t.H18533 = 0x25cf;
  t.H18543 = 0x25aa;
  t.H18551 = 0x25ab;
  t.H22073 = 0x25a1;
  t.HPsquare = 0x33cb;
  t.Haabkhasiancyrillic = 0x04a8;
  t.Hadescendercyrillic = 0x04b2;
  t.Hardsigncyrillic = 0x042a;
  t.Hbar = 0x0126;
  t.Hbrevebelow = 0x1e2a;
  t.Hcedilla = 0x1e28;
  t.Hcircle = 0x24bd;
  t.Hcircumflex = 0x0124;
  t.Hdieresis = 0x1e26;
  t.Hdotaccent = 0x1e22;
  t.Hdotbelow = 0x1e24;
  t.Hmonospace = 0xff28;
  t.Hoarmenian = 0x0540;
  t.Horicoptic = 0x03e8;
  t.Hsmall = 0xf768;
  t.Hungarumlaut = 0xf6cf;
  t.Hungarumlautsmall = 0xf6f8;
  t.Hzsquare = 0x3390;
  t.I = 0x0049;
  t.IAcyrillic = 0x042f;
  t.IJ = 0x0132;
  t.IUcyrillic = 0x042e;
  t.Iacute = 0x00cd;
  t.Iacutesmall = 0xf7ed;
  t.Ibreve = 0x012c;
  t.Icaron = 0x01cf;
  t.Icircle = 0x24be;
  t.Icircumflex = 0x00ce;
  t.Icircumflexsmall = 0xf7ee;
  t.Icyrillic = 0x0406;
  t.Idblgrave = 0x0208;
  t.Idieresis = 0x00cf;
  t.Idieresisacute = 0x1e2e;
  t.Idieresiscyrillic = 0x04e4;
  t.Idieresissmall = 0xf7ef;
  t.Idot = 0x0130;
  t.Idotaccent = 0x0130;
  t.Idotbelow = 0x1eca;
  t.Iebrevecyrillic = 0x04d6;
  t.Iecyrillic = 0x0415;
  t.Ifraktur = 0x2111;
  t.Igrave = 0x00cc;
  t.Igravesmall = 0xf7ec;
  t.Ihookabove = 0x1ec8;
  t.Iicyrillic = 0x0418;
  t.Iinvertedbreve = 0x020a;
  t.Iishortcyrillic = 0x0419;
  t.Imacron = 0x012a;
  t.Imacroncyrillic = 0x04e2;
  t.Imonospace = 0xff29;
  t.Iniarmenian = 0x053b;
  t.Iocyrillic = 0x0401;
  t.Iogonek = 0x012e;
  t.Iota = 0x0399;
  t.Iotaafrican = 0x0196;
  t.Iotadieresis = 0x03aa;
  t.Iotatonos = 0x038a;
  t.Ismall = 0xf769;
  t.Istroke = 0x0197;
  t.Itilde = 0x0128;
  t.Itildebelow = 0x1e2c;
  t.Izhitsacyrillic = 0x0474;
  t.Izhitsadblgravecyrillic = 0x0476;
  t.J = 0x004a;
  t.Jaarmenian = 0x0541;
  t.Jcircle = 0x24bf;
  t.Jcircumflex = 0x0134;
  t.Jecyrillic = 0x0408;
  t.Jheharmenian = 0x054b;
  t.Jmonospace = 0xff2a;
  t.Jsmall = 0xf76a;
  t.K = 0x004b;
  t.KBsquare = 0x3385;
  t.KKsquare = 0x33cd;
  t.Kabashkircyrillic = 0x04a0;
  t.Kacute = 0x1e30;
  t.Kacyrillic = 0x041a;
  t.Kadescendercyrillic = 0x049a;
  t.Kahookcyrillic = 0x04c3;
  t.Kappa = 0x039a;
  t.Kastrokecyrillic = 0x049e;
  t.Kaverticalstrokecyrillic = 0x049c;
  t.Kcaron = 0x01e8;
  t.Kcedilla = 0x0136;
  t.Kcircle = 0x24c0;
  t.Kcommaaccent = 0x0136;
  t.Kdotbelow = 0x1e32;
  t.Keharmenian = 0x0554;
  t.Kenarmenian = 0x053f;
  t.Khacyrillic = 0x0425;
  t.Kheicoptic = 0x03e6;
  t.Khook = 0x0198;
  t.Kjecyrillic = 0x040c;
  t.Klinebelow = 0x1e34;
  t.Kmonospace = 0xff2b;
  t.Koppacyrillic = 0x0480;
  t.Koppagreek = 0x03de;
  t.Ksicyrillic = 0x046e;
  t.Ksmall = 0xf76b;
  t.L = 0x004c;
  t.LJ = 0x01c7;
  t.LL = 0xf6bf;
  t.Lacute = 0x0139;
  t.Lambda = 0x039b;
  t.Lcaron = 0x013d;
  t.Lcedilla = 0x013b;
  t.Lcircle = 0x24c1;
  t.Lcircumflexbelow = 0x1e3c;
  t.Lcommaaccent = 0x013b;
  t.Ldot = 0x013f;
  t.Ldotaccent = 0x013f;
  t.Ldotbelow = 0x1e36;
  t.Ldotbelowmacron = 0x1e38;
  t.Liwnarmenian = 0x053c;
  t.Lj = 0x01c8;
  t.Ljecyrillic = 0x0409;
  t.Llinebelow = 0x1e3a;
  t.Lmonospace = 0xff2c;
  t.Lslash = 0x0141;
  t.Lslashsmall = 0xf6f9;
  t.Lsmall = 0xf76c;
  t.M = 0x004d;
  t.MBsquare = 0x3386;
  t.Macron = 0xf6d0;
  t.Macronsmall = 0xf7af;
  t.Macute = 0x1e3e;
  t.Mcircle = 0x24c2;
  t.Mdotaccent = 0x1e40;
  t.Mdotbelow = 0x1e42;
  t.Menarmenian = 0x0544;
  t.Mmonospace = 0xff2d;
  t.Msmall = 0xf76d;
  t.Mturned = 0x019c;
  t.Mu = 0x039c;
  t.N = 0x004e;
  t.NJ = 0x01ca;
  t.Nacute = 0x0143;
  t.Ncaron = 0x0147;
  t.Ncedilla = 0x0145;
  t.Ncircle = 0x24c3;
  t.Ncircumflexbelow = 0x1e4a;
  t.Ncommaaccent = 0x0145;
  t.Ndotaccent = 0x1e44;
  t.Ndotbelow = 0x1e46;
  t.Nhookleft = 0x019d;
  t.Nineroman = 0x2168;
  t.Nj = 0x01cb;
  t.Njecyrillic = 0x040a;
  t.Nlinebelow = 0x1e48;
  t.Nmonospace = 0xff2e;
  t.Nowarmenian = 0x0546;
  t.Nsmall = 0xf76e;
  t.Ntilde = 0x00d1;
  t.Ntildesmall = 0xf7f1;
  t.Nu = 0x039d;
  t.O = 0x004f;
  t.OE = 0x0152;
  t.OEsmall = 0xf6fa;
  t.Oacute = 0x00d3;
  t.Oacutesmall = 0xf7f3;
  t.Obarredcyrillic = 0x04e8;
  t.Obarreddieresiscyrillic = 0x04ea;
  t.Obreve = 0x014e;
  t.Ocaron = 0x01d1;
  t.Ocenteredtilde = 0x019f;
  t.Ocircle = 0x24c4;
  t.Ocircumflex = 0x00d4;
  t.Ocircumflexacute = 0x1ed0;
  t.Ocircumflexdotbelow = 0x1ed8;
  t.Ocircumflexgrave = 0x1ed2;
  t.Ocircumflexhookabove = 0x1ed4;
  t.Ocircumflexsmall = 0xf7f4;
  t.Ocircumflextilde = 0x1ed6;
  t.Ocyrillic = 0x041e;
  t.Odblacute = 0x0150;
  t.Odblgrave = 0x020c;
  t.Odieresis = 0x00d6;
  t.Odieresiscyrillic = 0x04e6;
  t.Odieresissmall = 0xf7f6;
  t.Odotbelow = 0x1ecc;
  t.Ogoneksmall = 0xf6fb;
  t.Ograve = 0x00d2;
  t.Ogravesmall = 0xf7f2;
  t.Oharmenian = 0x0555;
  t.Ohm = 0x2126;
  t.Ohookabove = 0x1ece;
  t.Ohorn = 0x01a0;
  t.Ohornacute = 0x1eda;
  t.Ohorndotbelow = 0x1ee2;
  t.Ohorngrave = 0x1edc;
  t.Ohornhookabove = 0x1ede;
  t.Ohorntilde = 0x1ee0;
  t.Ohungarumlaut = 0x0150;
  t.Oi = 0x01a2;
  t.Oinvertedbreve = 0x020e;
  t.Omacron = 0x014c;
  t.Omacronacute = 0x1e52;
  t.Omacrongrave = 0x1e50;
  t.Omega = 0x2126;
  t.Omegacyrillic = 0x0460;
  t.Omegagreek = 0x03a9;
  t.Omegaroundcyrillic = 0x047a;
  t.Omegatitlocyrillic = 0x047c;
  t.Omegatonos = 0x038f;
  t.Omicron = 0x039f;
  t.Omicrontonos = 0x038c;
  t.Omonospace = 0xff2f;
  t.Oneroman = 0x2160;
  t.Oogonek = 0x01ea;
  t.Oogonekmacron = 0x01ec;
  t.Oopen = 0x0186;
  t.Oslash = 0x00d8;
  t.Oslashacute = 0x01fe;
  t.Oslashsmall = 0xf7f8;
  t.Osmall = 0xf76f;
  t.Ostrokeacute = 0x01fe;
  t.Otcyrillic = 0x047e;
  t.Otilde = 0x00d5;
  t.Otildeacute = 0x1e4c;
  t.Otildedieresis = 0x1e4e;
  t.Otildesmall = 0xf7f5;
  t.P = 0x0050;
  t.Pacute = 0x1e54;
  t.Pcircle = 0x24c5;
  t.Pdotaccent = 0x1e56;
  t.Pecyrillic = 0x041f;
  t.Peharmenian = 0x054a;
  t.Pemiddlehookcyrillic = 0x04a6;
  t.Phi = 0x03a6;
  t.Phook = 0x01a4;
  t.Pi = 0x03a0;
  t.Piwrarmenian = 0x0553;
  t.Pmonospace = 0xff30;
  t.Psi = 0x03a8;
  t.Psicyrillic = 0x0470;
  t.Psmall = 0xf770;
  t.Q = 0x0051;
  t.Qcircle = 0x24c6;
  t.Qmonospace = 0xff31;
  t.Qsmall = 0xf771;
  t.R = 0x0052;
  t.Raarmenian = 0x054c;
  t.Racute = 0x0154;
  t.Rcaron = 0x0158;
  t.Rcedilla = 0x0156;
  t.Rcircle = 0x24c7;
  t.Rcommaaccent = 0x0156;
  t.Rdblgrave = 0x0210;
  t.Rdotaccent = 0x1e58;
  t.Rdotbelow = 0x1e5a;
  t.Rdotbelowmacron = 0x1e5c;
  t.Reharmenian = 0x0550;
  t.Rfraktur = 0x211c;
  t.Rho = 0x03a1;
  t.Ringsmall = 0xf6fc;
  t.Rinvertedbreve = 0x0212;
  t.Rlinebelow = 0x1e5e;
  t.Rmonospace = 0xff32;
  t.Rsmall = 0xf772;
  t.Rsmallinverted = 0x0281;
  t.Rsmallinvertedsuperior = 0x02b6;
  t.S = 0x0053;
  t.SF010000 = 0x250c;
  t.SF020000 = 0x2514;
  t.SF030000 = 0x2510;
  t.SF040000 = 0x2518;
  t.SF050000 = 0x253c;
  t.SF060000 = 0x252c;
  t.SF070000 = 0x2534;
  t.SF080000 = 0x251c;
  t.SF090000 = 0x2524;
  t.SF100000 = 0x2500;
  t.SF110000 = 0x2502;
  t.SF190000 = 0x2561;
  t.SF200000 = 0x2562;
  t.SF210000 = 0x2556;
  t.SF220000 = 0x2555;
  t.SF230000 = 0x2563;
  t.SF240000 = 0x2551;
  t.SF250000 = 0x2557;
  t.SF260000 = 0x255d;
  t.SF270000 = 0x255c;
  t.SF280000 = 0x255b;
  t.SF360000 = 0x255e;
  t.SF370000 = 0x255f;
  t.SF380000 = 0x255a;
  t.SF390000 = 0x2554;
  t.SF400000 = 0x2569;
  t.SF410000 = 0x2566;
  t.SF420000 = 0x2560;
  t.SF430000 = 0x2550;
  t.SF440000 = 0x256c;
  t.SF450000 = 0x2567;
  t.SF460000 = 0x2568;
  t.SF470000 = 0x2564;
  t.SF480000 = 0x2565;
  t.SF490000 = 0x2559;
  t.SF500000 = 0x2558;
  t.SF510000 = 0x2552;
  t.SF520000 = 0x2553;
  t.SF530000 = 0x256b;
  t.SF540000 = 0x256a;
  t.Sacute = 0x015a;
  t.Sacutedotaccent = 0x1e64;
  t.Sampigreek = 0x03e0;
  t.Scaron = 0x0160;
  t.Scarondotaccent = 0x1e66;
  t.Scaronsmall = 0xf6fd;
  t.Scedilla = 0x015e;
  t.Schwa = 0x018f;
  t.Schwacyrillic = 0x04d8;
  t.Schwadieresiscyrillic = 0x04da;
  t.Scircle = 0x24c8;
  t.Scircumflex = 0x015c;
  t.Scommaaccent = 0x0218;
  t.Sdotaccent = 0x1e60;
  t.Sdotbelow = 0x1e62;
  t.Sdotbelowdotaccent = 0x1e68;
  t.Seharmenian = 0x054d;
  t.Sevenroman = 0x2166;
  t.Shaarmenian = 0x0547;
  t.Shacyrillic = 0x0428;
  t.Shchacyrillic = 0x0429;
  t.Sheicoptic = 0x03e2;
  t.Shhacyrillic = 0x04ba;
  t.Shimacoptic = 0x03ec;
  t.Sigma = 0x03a3;
  t.Sixroman = 0x2165;
  t.Smonospace = 0xff33;
  t.Softsigncyrillic = 0x042c;
  t.Ssmall = 0xf773;
  t.Stigmagreek = 0x03da;
  t.T = 0x0054;
  t.Tau = 0x03a4;
  t.Tbar = 0x0166;
  t.Tcaron = 0x0164;
  t.Tcedilla = 0x0162;
  t.Tcircle = 0x24c9;
  t.Tcircumflexbelow = 0x1e70;
  t.Tcommaaccent = 0x0162;
  t.Tdotaccent = 0x1e6a;
  t.Tdotbelow = 0x1e6c;
  t.Tecyrillic = 0x0422;
  t.Tedescendercyrillic = 0x04ac;
  t.Tenroman = 0x2169;
  t.Tetsecyrillic = 0x04b4;
  t.Theta = 0x0398;
  t.Thook = 0x01ac;
  t.Thorn = 0x00de;
  t.Thornsmall = 0xf7fe;
  t.Threeroman = 0x2162;
  t.Tildesmall = 0xf6fe;
  t.Tiwnarmenian = 0x054f;
  t.Tlinebelow = 0x1e6e;
  t.Tmonospace = 0xff34;
  t.Toarmenian = 0x0539;
  t.Tonefive = 0x01bc;
  t.Tonesix = 0x0184;
  t.Tonetwo = 0x01a7;
  t.Tretroflexhook = 0x01ae;
  t.Tsecyrillic = 0x0426;
  t.Tshecyrillic = 0x040b;
  t.Tsmall = 0xf774;
  t.Twelveroman = 0x216b;
  t.Tworoman = 0x2161;
  t.U = 0x0055;
  t.Uacute = 0x00da;
  t.Uacutesmall = 0xf7fa;
  t.Ubreve = 0x016c;
  t.Ucaron = 0x01d3;
  t.Ucircle = 0x24ca;
  t.Ucircumflex = 0x00db;
  t.Ucircumflexbelow = 0x1e76;
  t.Ucircumflexsmall = 0xf7fb;
  t.Ucyrillic = 0x0423;
  t.Udblacute = 0x0170;
  t.Udblgrave = 0x0214;
  t.Udieresis = 0x00dc;
  t.Udieresisacute = 0x01d7;
  t.Udieresisbelow = 0x1e72;
  t.Udieresiscaron = 0x01d9;
  t.Udieresiscyrillic = 0x04f0;
  t.Udieresisgrave = 0x01db;
  t.Udieresismacron = 0x01d5;
  t.Udieresissmall = 0xf7fc;
  t.Udotbelow = 0x1ee4;
  t.Ugrave = 0x00d9;
  t.Ugravesmall = 0xf7f9;
  t.Uhookabove = 0x1ee6;
  t.Uhorn = 0x01af;
  t.Uhornacute = 0x1ee8;
  t.Uhorndotbelow = 0x1ef0;
  t.Uhorngrave = 0x1eea;
  t.Uhornhookabove = 0x1eec;
  t.Uhorntilde = 0x1eee;
  t.Uhungarumlaut = 0x0170;
  t.Uhungarumlautcyrillic = 0x04f2;
  t.Uinvertedbreve = 0x0216;
  t.Ukcyrillic = 0x0478;
  t.Umacron = 0x016a;
  t.Umacroncyrillic = 0x04ee;
  t.Umacrondieresis = 0x1e7a;
  t.Umonospace = 0xff35;
  t.Uogonek = 0x0172;
  t.Upsilon = 0x03a5;
  t.Upsilon1 = 0x03d2;
  t.Upsilonacutehooksymbolgreek = 0x03d3;
  t.Upsilonafrican = 0x01b1;
  t.Upsilondieresis = 0x03ab;
  t.Upsilondieresishooksymbolgreek = 0x03d4;
  t.Upsilonhooksymbol = 0x03d2;
  t.Upsilontonos = 0x038e;
  t.Uring = 0x016e;
  t.Ushortcyrillic = 0x040e;
  t.Usmall = 0xf775;
  t.Ustraightcyrillic = 0x04ae;
  t.Ustraightstrokecyrillic = 0x04b0;
  t.Utilde = 0x0168;
  t.Utildeacute = 0x1e78;
  t.Utildebelow = 0x1e74;
  t.V = 0x0056;
  t.Vcircle = 0x24cb;
  t.Vdotbelow = 0x1e7e;
  t.Vecyrillic = 0x0412;
  t.Vewarmenian = 0x054e;
  t.Vhook = 0x01b2;
  t.Vmonospace = 0xff36;
  t.Voarmenian = 0x0548;
  t.Vsmall = 0xf776;
  t.Vtilde = 0x1e7c;
  t.W = 0x0057;
  t.Wacute = 0x1e82;
  t.Wcircle = 0x24cc;
  t.Wcircumflex = 0x0174;
  t.Wdieresis = 0x1e84;
  t.Wdotaccent = 0x1e86;
  t.Wdotbelow = 0x1e88;
  t.Wgrave = 0x1e80;
  t.Wmonospace = 0xff37;
  t.Wsmall = 0xf777;
  t.X = 0x0058;
  t.Xcircle = 0x24cd;
  t.Xdieresis = 0x1e8c;
  t.Xdotaccent = 0x1e8a;
  t.Xeharmenian = 0x053d;
  t.Xi = 0x039e;
  t.Xmonospace = 0xff38;
  t.Xsmall = 0xf778;
  t.Y = 0x0059;
  t.Yacute = 0x00dd;
  t.Yacutesmall = 0xf7fd;
  t.Yatcyrillic = 0x0462;
  t.Ycircle = 0x24ce;
  t.Ycircumflex = 0x0176;
  t.Ydieresis = 0x0178;
  t.Ydieresissmall = 0xf7ff;
  t.Ydotaccent = 0x1e8e;
  t.Ydotbelow = 0x1ef4;
  t.Yericyrillic = 0x042b;
  t.Yerudieresiscyrillic = 0x04f8;
  t.Ygrave = 0x1ef2;
  t.Yhook = 0x01b3;
  t.Yhookabove = 0x1ef6;
  t.Yiarmenian = 0x0545;
  t.Yicyrillic = 0x0407;
  t.Yiwnarmenian = 0x0552;
  t.Ymonospace = 0xff39;
  t.Ysmall = 0xf779;
  t.Ytilde = 0x1ef8;
  t.Yusbigcyrillic = 0x046a;
  t.Yusbigiotifiedcyrillic = 0x046c;
  t.Yuslittlecyrillic = 0x0466;
  t.Yuslittleiotifiedcyrillic = 0x0468;
  t.Z = 0x005a;
  t.Zaarmenian = 0x0536;
  t.Zacute = 0x0179;
  t.Zcaron = 0x017d;
  t.Zcaronsmall = 0xf6ff;
  t.Zcircle = 0x24cf;
  t.Zcircumflex = 0x1e90;
  t.Zdot = 0x017b;
  t.Zdotaccent = 0x017b;
  t.Zdotbelow = 0x1e92;
  t.Zecyrillic = 0x0417;
  t.Zedescendercyrillic = 0x0498;
  t.Zedieresiscyrillic = 0x04de;
  t.Zeta = 0x0396;
  t.Zhearmenian = 0x053a;
  t.Zhebrevecyrillic = 0x04c1;
  t.Zhecyrillic = 0x0416;
  t.Zhedescendercyrillic = 0x0496;
  t.Zhedieresiscyrillic = 0x04dc;
  t.Zlinebelow = 0x1e94;
  t.Zmonospace = 0xff3a;
  t.Zsmall = 0xf77a;
  t.Zstroke = 0x01b5;
  t.a = 0x0061;
  t.aabengali = 0x0986;
  t.aacute = 0x00e1;
  t.aadeva = 0x0906;
  t.aagujarati = 0x0a86;
  t.aagurmukhi = 0x0a06;
  t.aamatragurmukhi = 0x0a3e;
  t.aarusquare = 0x3303;
  t.aavowelsignbengali = 0x09be;
  t.aavowelsigndeva = 0x093e;
  t.aavowelsigngujarati = 0x0abe;
  t.abbreviationmarkarmenian = 0x055f;
  t.abbreviationsigndeva = 0x0970;
  t.abengali = 0x0985;
  t.abopomofo = 0x311a;
  t.abreve = 0x0103;
  t.abreveacute = 0x1eaf;
  t.abrevecyrillic = 0x04d1;
  t.abrevedotbelow = 0x1eb7;
  t.abrevegrave = 0x1eb1;
  t.abrevehookabove = 0x1eb3;
  t.abrevetilde = 0x1eb5;
  t.acaron = 0x01ce;
  t.acircle = 0x24d0;
  t.acircumflex = 0x00e2;
  t.acircumflexacute = 0x1ea5;
  t.acircumflexdotbelow = 0x1ead;
  t.acircumflexgrave = 0x1ea7;
  t.acircumflexhookabove = 0x1ea9;
  t.acircumflextilde = 0x1eab;
  t.acute = 0x00b4;
  t.acutebelowcmb = 0x0317;
  t.acutecmb = 0x0301;
  t.acutecomb = 0x0301;
  t.acutedeva = 0x0954;
  t.acutelowmod = 0x02cf;
  t.acutetonecmb = 0x0341;
  t.acyrillic = 0x0430;
  t.adblgrave = 0x0201;
  t.addakgurmukhi = 0x0a71;
  t.adeva = 0x0905;
  t.adieresis = 0x00e4;
  t.adieresiscyrillic = 0x04d3;
  t.adieresismacron = 0x01df;
  t.adotbelow = 0x1ea1;
  t.adotmacron = 0x01e1;
  t.ae = 0x00e6;
  t.aeacute = 0x01fd;
  t.aekorean = 0x3150;
  t.aemacron = 0x01e3;
  t.afii00208 = 0x2015;
  t.afii08941 = 0x20a4;
  t.afii10017 = 0x0410;
  t.afii10018 = 0x0411;
  t.afii10019 = 0x0412;
  t.afii10020 = 0x0413;
  t.afii10021 = 0x0414;
  t.afii10022 = 0x0415;
  t.afii10023 = 0x0401;
  t.afii10024 = 0x0416;
  t.afii10025 = 0x0417;
  t.afii10026 = 0x0418;
  t.afii10027 = 0x0419;
  t.afii10028 = 0x041a;
  t.afii10029 = 0x041b;
  t.afii10030 = 0x041c;
  t.afii10031 = 0x041d;
  t.afii10032 = 0x041e;
  t.afii10033 = 0x041f;
  t.afii10034 = 0x0420;
  t.afii10035 = 0x0421;
  t.afii10036 = 0x0422;
  t.afii10037 = 0x0423;
  t.afii10038 = 0x0424;
  t.afii10039 = 0x0425;
  t.afii10040 = 0x0426;
  t.afii10041 = 0x0427;
  t.afii10042 = 0x0428;
  t.afii10043 = 0x0429;
  t.afii10044 = 0x042a;
  t.afii10045 = 0x042b;
  t.afii10046 = 0x042c;
  t.afii10047 = 0x042d;
  t.afii10048 = 0x042e;
  t.afii10049 = 0x042f;
  t.afii10050 = 0x0490;
  t.afii10051 = 0x0402;
  t.afii10052 = 0x0403;
  t.afii10053 = 0x0404;
  t.afii10054 = 0x0405;
  t.afii10055 = 0x0406;
  t.afii10056 = 0x0407;
  t.afii10057 = 0x0408;
  t.afii10058 = 0x0409;
  t.afii10059 = 0x040a;
  t.afii10060 = 0x040b;
  t.afii10061 = 0x040c;
  t.afii10062 = 0x040e;
  t.afii10063 = 0xf6c4;
  t.afii10064 = 0xf6c5;
  t.afii10065 = 0x0430;
  t.afii10066 = 0x0431;
  t.afii10067 = 0x0432;
  t.afii10068 = 0x0433;
  t.afii10069 = 0x0434;
  t.afii10070 = 0x0435;
  t.afii10071 = 0x0451;
  t.afii10072 = 0x0436;
  t.afii10073 = 0x0437;
  t.afii10074 = 0x0438;
  t.afii10075 = 0x0439;
  t.afii10076 = 0x043a;
  t.afii10077 = 0x043b;
  t.afii10078 = 0x043c;
  t.afii10079 = 0x043d;
  t.afii10080 = 0x043e;
  t.afii10081 = 0x043f;
  t.afii10082 = 0x0440;
  t.afii10083 = 0x0441;
  t.afii10084 = 0x0442;
  t.afii10085 = 0x0443;
  t.afii10086 = 0x0444;
  t.afii10087 = 0x0445;
  t.afii10088 = 0x0446;
  t.afii10089 = 0x0447;
  t.afii10090 = 0x0448;
  t.afii10091 = 0x0449;
  t.afii10092 = 0x044a;
  t.afii10093 = 0x044b;
  t.afii10094 = 0x044c;
  t.afii10095 = 0x044d;
  t.afii10096 = 0x044e;
  t.afii10097 = 0x044f;
  t.afii10098 = 0x0491;
  t.afii10099 = 0x0452;
  t.afii10100 = 0x0453;
  t.afii10101 = 0x0454;
  t.afii10102 = 0x0455;
  t.afii10103 = 0x0456;
  t.afii10104 = 0x0457;
  t.afii10105 = 0x0458;
  t.afii10106 = 0x0459;
  t.afii10107 = 0x045a;
  t.afii10108 = 0x045b;
  t.afii10109 = 0x045c;
  t.afii10110 = 0x045e;
  t.afii10145 = 0x040f;
  t.afii10146 = 0x0462;
  t.afii10147 = 0x0472;
  t.afii10148 = 0x0474;
  t.afii10192 = 0xf6c6;
  t.afii10193 = 0x045f;
  t.afii10194 = 0x0463;
  t.afii10195 = 0x0473;
  t.afii10196 = 0x0475;
  t.afii10831 = 0xf6c7;
  t.afii10832 = 0xf6c8;
  t.afii10846 = 0x04d9;
  t.afii299 = 0x200e;
  t.afii300 = 0x200f;
  t.afii301 = 0x200d;
  t.afii57381 = 0x066a;
  t.afii57388 = 0x060c;
  t.afii57392 = 0x0660;
  t.afii57393 = 0x0661;
  t.afii57394 = 0x0662;
  t.afii57395 = 0x0663;
  t.afii57396 = 0x0664;
  t.afii57397 = 0x0665;
  t.afii57398 = 0x0666;
  t.afii57399 = 0x0667;
  t.afii57400 = 0x0668;
  t.afii57401 = 0x0669;
  t.afii57403 = 0x061b;
  t.afii57407 = 0x061f;
  t.afii57409 = 0x0621;
  t.afii57410 = 0x0622;
  t.afii57411 = 0x0623;
  t.afii57412 = 0x0624;
  t.afii57413 = 0x0625;
  t.afii57414 = 0x0626;
  t.afii57415 = 0x0627;
  t.afii57416 = 0x0628;
  t.afii57417 = 0x0629;
  t.afii57418 = 0x062a;
  t.afii57419 = 0x062b;
  t.afii57420 = 0x062c;
  t.afii57421 = 0x062d;
  t.afii57422 = 0x062e;
  t.afii57423 = 0x062f;
  t.afii57424 = 0x0630;
  t.afii57425 = 0x0631;
  t.afii57426 = 0x0632;
  t.afii57427 = 0x0633;
  t.afii57428 = 0x0634;
  t.afii57429 = 0x0635;
  t.afii57430 = 0x0636;
  t.afii57431 = 0x0637;
  t.afii57432 = 0x0638;
  t.afii57433 = 0x0639;
  t.afii57434 = 0x063a;
  t.afii57440 = 0x0640;
  t.afii57441 = 0x0641;
  t.afii57442 = 0x0642;
  t.afii57443 = 0x0643;
  t.afii57444 = 0x0644;
  t.afii57445 = 0x0645;
  t.afii57446 = 0x0646;
  t.afii57448 = 0x0648;
  t.afii57449 = 0x0649;
  t.afii57450 = 0x064a;
  t.afii57451 = 0x064b;
  t.afii57452 = 0x064c;
  t.afii57453 = 0x064d;
  t.afii57454 = 0x064e;
  t.afii57455 = 0x064f;
  t.afii57456 = 0x0650;
  t.afii57457 = 0x0651;
  t.afii57458 = 0x0652;
  t.afii57470 = 0x0647;
  t.afii57505 = 0x06a4;
  t.afii57506 = 0x067e;
  t.afii57507 = 0x0686;
  t.afii57508 = 0x0698;
  t.afii57509 = 0x06af;
  t.afii57511 = 0x0679;
  t.afii57512 = 0x0688;
  t.afii57513 = 0x0691;
  t.afii57514 = 0x06ba;
  t.afii57519 = 0x06d2;
  t.afii57534 = 0x06d5;
  t.afii57636 = 0x20aa;
  t.afii57645 = 0x05be;
  t.afii57658 = 0x05c3;
  t.afii57664 = 0x05d0;
  t.afii57665 = 0x05d1;
  t.afii57666 = 0x05d2;
  t.afii57667 = 0x05d3;
  t.afii57668 = 0x05d4;
  t.afii57669 = 0x05d5;
  t.afii57670 = 0x05d6;
  t.afii57671 = 0x05d7;
  t.afii57672 = 0x05d8;
  t.afii57673 = 0x05d9;
  t.afii57674 = 0x05da;
  t.afii57675 = 0x05db;
  t.afii57676 = 0x05dc;
  t.afii57677 = 0x05dd;
  t.afii57678 = 0x05de;
  t.afii57679 = 0x05df;
  t.afii57680 = 0x05e0;
  t.afii57681 = 0x05e1;
  t.afii57682 = 0x05e2;
  t.afii57683 = 0x05e3;
  t.afii57684 = 0x05e4;
  t.afii57685 = 0x05e5;
  t.afii57686 = 0x05e6;
  t.afii57687 = 0x05e7;
  t.afii57688 = 0x05e8;
  t.afii57689 = 0x05e9;
  t.afii57690 = 0x05ea;
  t.afii57694 = 0xfb2a;
  t.afii57695 = 0xfb2b;
  t.afii57700 = 0xfb4b;
  t.afii57705 = 0xfb1f;
  t.afii57716 = 0x05f0;
  t.afii57717 = 0x05f1;
  t.afii57718 = 0x05f2;
  t.afii57723 = 0xfb35;
  t.afii57793 = 0x05b4;
  t.afii57794 = 0x05b5;
  t.afii57795 = 0x05b6;
  t.afii57796 = 0x05bb;
  t.afii57797 = 0x05b8;
  t.afii57798 = 0x05b7;
  t.afii57799 = 0x05b0;
  t.afii57800 = 0x05b2;
  t.afii57801 = 0x05b1;
  t.afii57802 = 0x05b3;
  t.afii57803 = 0x05c2;
  t.afii57804 = 0x05c1;
  t.afii57806 = 0x05b9;
  t.afii57807 = 0x05bc;
  t.afii57839 = 0x05bd;
  t.afii57841 = 0x05bf;
  t.afii57842 = 0x05c0;
  t.afii57929 = 0x02bc;
  t.afii61248 = 0x2105;
  t.afii61289 = 0x2113;
  t.afii61352 = 0x2116;
  t.afii61573 = 0x202c;
  t.afii61574 = 0x202d;
  t.afii61575 = 0x202e;
  t.afii61664 = 0x200c;
  t.afii63167 = 0x066d;
  t.afii64937 = 0x02bd;
  t.agrave = 0x00e0;
  t.agujarati = 0x0a85;
  t.agurmukhi = 0x0a05;
  t.ahiragana = 0x3042;
  t.ahookabove = 0x1ea3;
  t.aibengali = 0x0990;
  t.aibopomofo = 0x311e;
  t.aideva = 0x0910;
  t.aiecyrillic = 0x04d5;
  t.aigujarati = 0x0a90;
  t.aigurmukhi = 0x0a10;
  t.aimatragurmukhi = 0x0a48;
  t.ainarabic = 0x0639;
  t.ainfinalarabic = 0xfeca;
  t.aininitialarabic = 0xfecb;
  t.ainmedialarabic = 0xfecc;
  t.ainvertedbreve = 0x0203;
  t.aivowelsignbengali = 0x09c8;
  t.aivowelsigndeva = 0x0948;
  t.aivowelsigngujarati = 0x0ac8;
  t.akatakana = 0x30a2;
  t.akatakanahalfwidth = 0xff71;
  t.akorean = 0x314f;
  t.alef = 0x05d0;
  t.alefarabic = 0x0627;
  t.alefdageshhebrew = 0xfb30;
  t.aleffinalarabic = 0xfe8e;
  t.alefhamzaabovearabic = 0x0623;
  t.alefhamzaabovefinalarabic = 0xfe84;
  t.alefhamzabelowarabic = 0x0625;
  t.alefhamzabelowfinalarabic = 0xfe88;
  t.alefhebrew = 0x05d0;
  t.aleflamedhebrew = 0xfb4f;
  t.alefmaddaabovearabic = 0x0622;
  t.alefmaddaabovefinalarabic = 0xfe82;
  t.alefmaksuraarabic = 0x0649;
  t.alefmaksurafinalarabic = 0xfef0;
  t.alefmaksurainitialarabic = 0xfef3;
  t.alefmaksuramedialarabic = 0xfef4;
  t.alefpatahhebrew = 0xfb2e;
  t.alefqamatshebrew = 0xfb2f;
  t.aleph = 0x2135;
  t.allequal = 0x224c;
  t.alpha = 0x03b1;
  t.alphatonos = 0x03ac;
  t.amacron = 0x0101;
  t.amonospace = 0xff41;
  t.ampersand = 0x0026;
  t.ampersandmonospace = 0xff06;
  t.ampersandsmall = 0xf726;
  t.amsquare = 0x33c2;
  t.anbopomofo = 0x3122;
  t.angbopomofo = 0x3124;
  t.angbracketleft = 0x3008;
  t.angbracketright = 0x3009;
  t.angkhankhuthai = 0x0e5a;
  t.angle = 0x2220;
  t.anglebracketleft = 0x3008;
  t.anglebracketleftvertical = 0xfe3f;
  t.anglebracketright = 0x3009;
  t.anglebracketrightvertical = 0xfe40;
  t.angleleft = 0x2329;
  t.angleright = 0x232a;
  t.angstrom = 0x212b;
  t.anoteleia = 0x0387;
  t.anudattadeva = 0x0952;
  t.anusvarabengali = 0x0982;
  t.anusvaradeva = 0x0902;
  t.anusvaragujarati = 0x0a82;
  t.aogonek = 0x0105;
  t.apaatosquare = 0x3300;
  t.aparen = 0x249c;
  t.apostrophearmenian = 0x055a;
  t.apostrophemod = 0x02bc;
  t.apple = 0xf8ff;
  t.approaches = 0x2250;
  t.approxequal = 0x2248;
  t.approxequalorimage = 0x2252;
  t.approximatelyequal = 0x2245;
  t.araeaekorean = 0x318e;
  t.araeakorean = 0x318d;
  t.arc = 0x2312;
  t.arighthalfring = 0x1e9a;
  t.aring = 0x00e5;
  t.aringacute = 0x01fb;
  t.aringbelow = 0x1e01;
  t.arrowboth = 0x2194;
  t.arrowdashdown = 0x21e3;
  t.arrowdashleft = 0x21e0;
  t.arrowdashright = 0x21e2;
  t.arrowdashup = 0x21e1;
  t.arrowdblboth = 0x21d4;
  t.arrowdbldown = 0x21d3;
  t.arrowdblleft = 0x21d0;
  t.arrowdblright = 0x21d2;
  t.arrowdblup = 0x21d1;
  t.arrowdown = 0x2193;
  t.arrowdownleft = 0x2199;
  t.arrowdownright = 0x2198;
  t.arrowdownwhite = 0x21e9;
  t.arrowheaddownmod = 0x02c5;
  t.arrowheadleftmod = 0x02c2;
  t.arrowheadrightmod = 0x02c3;
  t.arrowheadupmod = 0x02c4;
  t.arrowhorizex = 0xf8e7;
  t.arrowleft = 0x2190;
  t.arrowleftdbl = 0x21d0;
  t.arrowleftdblstroke = 0x21cd;
  t.arrowleftoverright = 0x21c6;
  t.arrowleftwhite = 0x21e6;
  t.arrowright = 0x2192;
  t.arrowrightdblstroke = 0x21cf;
  t.arrowrightheavy = 0x279e;
  t.arrowrightoverleft = 0x21c4;
  t.arrowrightwhite = 0x21e8;
  t.arrowtableft = 0x21e4;
  t.arrowtabright = 0x21e5;
  t.arrowup = 0x2191;
  t.arrowupdn = 0x2195;
  t.arrowupdnbse = 0x21a8;
  t.arrowupdownbase = 0x21a8;
  t.arrowupleft = 0x2196;
  t.arrowupleftofdown = 0x21c5;
  t.arrowupright = 0x2197;
  t.arrowupwhite = 0x21e7;
  t.arrowvertex = 0xf8e6;
  t.asciicircum = 0x005e;
  t.asciicircummonospace = 0xff3e;
  t.asciitilde = 0x007e;
  t.asciitildemonospace = 0xff5e;
  t.ascript = 0x0251;
  t.ascriptturned = 0x0252;
  t.asmallhiragana = 0x3041;
  t.asmallkatakana = 0x30a1;
  t.asmallkatakanahalfwidth = 0xff67;
  t.asterisk = 0x002a;
  t.asteriskaltonearabic = 0x066d;
  t.asteriskarabic = 0x066d;
  t.asteriskmath = 0x2217;
  t.asteriskmonospace = 0xff0a;
  t.asterisksmall = 0xfe61;
  t.asterism = 0x2042;
  t.asuperior = 0xf6e9;
  t.asymptoticallyequal = 0x2243;
  t.at = 0x0040;
  t.atilde = 0x00e3;
  t.atmonospace = 0xff20;
  t.atsmall = 0xfe6b;
  t.aturned = 0x0250;
  t.aubengali = 0x0994;
  t.aubopomofo = 0x3120;
  t.audeva = 0x0914;
  t.augujarati = 0x0a94;
  t.augurmukhi = 0x0a14;
  t.aulengthmarkbengali = 0x09d7;
  t.aumatragurmukhi = 0x0a4c;
  t.auvowelsignbengali = 0x09cc;
  t.auvowelsigndeva = 0x094c;
  t.auvowelsigngujarati = 0x0acc;
  t.avagrahadeva = 0x093d;
  t.aybarmenian = 0x0561;
  t.ayin = 0x05e2;
  t.ayinaltonehebrew = 0xfb20;
  t.ayinhebrew = 0x05e2;
  t.b = 0x0062;
  t.babengali = 0x09ac;
  t.backslash = 0x005c;
  t.backslashmonospace = 0xff3c;
  t.badeva = 0x092c;
  t.bagujarati = 0x0aac;
  t.bagurmukhi = 0x0a2c;
  t.bahiragana = 0x3070;
  t.bahtthai = 0x0e3f;
  t.bakatakana = 0x30d0;
  t.bar = 0x007c;
  t.barmonospace = 0xff5c;
  t.bbopomofo = 0x3105;
  t.bcircle = 0x24d1;
  t.bdotaccent = 0x1e03;
  t.bdotbelow = 0x1e05;
  t.beamedsixteenthnotes = 0x266c;
  t.because = 0x2235;
  t.becyrillic = 0x0431;
  t.beharabic = 0x0628;
  t.behfinalarabic = 0xfe90;
  t.behinitialarabic = 0xfe91;
  t.behiragana = 0x3079;
  t.behmedialarabic = 0xfe92;
  t.behmeeminitialarabic = 0xfc9f;
  t.behmeemisolatedarabic = 0xfc08;
  t.behnoonfinalarabic = 0xfc6d;
  t.bekatakana = 0x30d9;
  t.benarmenian = 0x0562;
  t.bet = 0x05d1;
  t.beta = 0x03b2;
  t.betasymbolgreek = 0x03d0;
  t.betdagesh = 0xfb31;
  t.betdageshhebrew = 0xfb31;
  t.bethebrew = 0x05d1;
  t.betrafehebrew = 0xfb4c;
  t.bhabengali = 0x09ad;
  t.bhadeva = 0x092d;
  t.bhagujarati = 0x0aad;
  t.bhagurmukhi = 0x0a2d;
  t.bhook = 0x0253;
  t.bihiragana = 0x3073;
  t.bikatakana = 0x30d3;
  t.bilabialclick = 0x0298;
  t.bindigurmukhi = 0x0a02;
  t.birusquare = 0x3331;
  t.blackcircle = 0x25cf;
  t.blackdiamond = 0x25c6;
  t.blackdownpointingtriangle = 0x25bc;
  t.blackleftpointingpointer = 0x25c4;
  t.blackleftpointingtriangle = 0x25c0;
  t.blacklenticularbracketleft = 0x3010;
  t.blacklenticularbracketleftvertical = 0xfe3b;
  t.blacklenticularbracketright = 0x3011;
  t.blacklenticularbracketrightvertical = 0xfe3c;
  t.blacklowerlefttriangle = 0x25e3;
  t.blacklowerrighttriangle = 0x25e2;
  t.blackrectangle = 0x25ac;
  t.blackrightpointingpointer = 0x25ba;
  t.blackrightpointingtriangle = 0x25b6;
  t.blacksmallsquare = 0x25aa;
  t.blacksmilingface = 0x263b;
  t.blacksquare = 0x25a0;
  t.blackstar = 0x2605;
  t.blackupperlefttriangle = 0x25e4;
  t.blackupperrighttriangle = 0x25e5;
  t.blackuppointingsmalltriangle = 0x25b4;
  t.blackuppointingtriangle = 0x25b2;
  t.blank = 0x2423;
  t.blinebelow = 0x1e07;
  t.block = 0x2588;
  t.bmonospace = 0xff42;
  t.bobaimaithai = 0x0e1a;
  t.bohiragana = 0x307c;
  t.bokatakana = 0x30dc;
  t.bparen = 0x249d;
  t.bqsquare = 0x33c3;
  t.braceex = 0xf8f4;
  t.braceleft = 0x007b;
  t.braceleftbt = 0xf8f3;
  t.braceleftmid = 0xf8f2;
  t.braceleftmonospace = 0xff5b;
  t.braceleftsmall = 0xfe5b;
  t.bracelefttp = 0xf8f1;
  t.braceleftvertical = 0xfe37;
  t.braceright = 0x007d;
  t.bracerightbt = 0xf8fe;
  t.bracerightmid = 0xf8fd;
  t.bracerightmonospace = 0xff5d;
  t.bracerightsmall = 0xfe5c;
  t.bracerighttp = 0xf8fc;
  t.bracerightvertical = 0xfe38;
  t.bracketleft = 0x005b;
  t.bracketleftbt = 0xf8f0;
  t.bracketleftex = 0xf8ef;
  t.bracketleftmonospace = 0xff3b;
  t.bracketlefttp = 0xf8ee;
  t.bracketright = 0x005d;
  t.bracketrightbt = 0xf8fb;
  t.bracketrightex = 0xf8fa;
  t.bracketrightmonospace = 0xff3d;
  t.bracketrighttp = 0xf8f9;
  t.breve = 0x02d8;
  t.brevebelowcmb = 0x032e;
  t.brevecmb = 0x0306;
  t.breveinvertedbelowcmb = 0x032f;
  t.breveinvertedcmb = 0x0311;
  t.breveinverteddoublecmb = 0x0361;
  t.bridgebelowcmb = 0x032a;
  t.bridgeinvertedbelowcmb = 0x033a;
  t.brokenbar = 0x00a6;
  t.bstroke = 0x0180;
  t.bsuperior = 0xf6ea;
  t.btopbar = 0x0183;
  t.buhiragana = 0x3076;
  t.bukatakana = 0x30d6;
  t.bullet = 0x2022;
  t.bulletinverse = 0x25d8;
  t.bulletoperator = 0x2219;
  t.bullseye = 0x25ce;
  t.c = 0x0063;
  t.caarmenian = 0x056e;
  t.cabengali = 0x099a;
  t.cacute = 0x0107;
  t.cadeva = 0x091a;
  t.cagujarati = 0x0a9a;
  t.cagurmukhi = 0x0a1a;
  t.calsquare = 0x3388;
  t.candrabindubengali = 0x0981;
  t.candrabinducmb = 0x0310;
  t.candrabindudeva = 0x0901;
  t.candrabindugujarati = 0x0a81;
  t.capslock = 0x21ea;
  t.careof = 0x2105;
  t.caron = 0x02c7;
  t.caronbelowcmb = 0x032c;
  t.caroncmb = 0x030c;
  t.carriagereturn = 0x21b5;
  t.cbopomofo = 0x3118;
  t.ccaron = 0x010d;
  t.ccedilla = 0x00e7;
  t.ccedillaacute = 0x1e09;
  t.ccircle = 0x24d2;
  t.ccircumflex = 0x0109;
  t.ccurl = 0x0255;
  t.cdot = 0x010b;
  t.cdotaccent = 0x010b;
  t.cdsquare = 0x33c5;
  t.cedilla = 0x00b8;
  t.cedillacmb = 0x0327;
  t.cent = 0x00a2;
  t.centigrade = 0x2103;
  t.centinferior = 0xf6df;
  t.centmonospace = 0xffe0;
  t.centoldstyle = 0xf7a2;
  t.centsuperior = 0xf6e0;
  t.chaarmenian = 0x0579;
  t.chabengali = 0x099b;
  t.chadeva = 0x091b;
  t.chagujarati = 0x0a9b;
  t.chagurmukhi = 0x0a1b;
  t.chbopomofo = 0x3114;
  t.cheabkhasiancyrillic = 0x04bd;
  t.checkmark = 0x2713;
  t.checyrillic = 0x0447;
  t.chedescenderabkhasiancyrillic = 0x04bf;
  t.chedescendercyrillic = 0x04b7;
  t.chedieresiscyrillic = 0x04f5;
  t.cheharmenian = 0x0573;
  t.chekhakassiancyrillic = 0x04cc;
  t.cheverticalstrokecyrillic = 0x04b9;
  t.chi = 0x03c7;
  t.chieuchacirclekorean = 0x3277;
  t.chieuchaparenkorean = 0x3217;
  t.chieuchcirclekorean = 0x3269;
  t.chieuchkorean = 0x314a;
  t.chieuchparenkorean = 0x3209;
  t.chochangthai = 0x0e0a;
  t.chochanthai = 0x0e08;
  t.chochingthai = 0x0e09;
  t.chochoethai = 0x0e0c;
  t.chook = 0x0188;
  t.cieucacirclekorean = 0x3276;
  t.cieucaparenkorean = 0x3216;
  t.cieuccirclekorean = 0x3268;
  t.cieuckorean = 0x3148;
  t.cieucparenkorean = 0x3208;
  t.cieucuparenkorean = 0x321c;
  t.circle = 0x25cb;
  t.circlecopyrt = 0x00a9;
  t.circlemultiply = 0x2297;
  t.circleot = 0x2299;
  t.circleplus = 0x2295;
  t.circlepostalmark = 0x3036;
  t.circlewithlefthalfblack = 0x25d0;
  t.circlewithrighthalfblack = 0x25d1;
  t.circumflex = 0x02c6;
  t.circumflexbelowcmb = 0x032d;
  t.circumflexcmb = 0x0302;
  t.clear = 0x2327;
  t.clickalveolar = 0x01c2;
  t.clickdental = 0x01c0;
  t.clicklateral = 0x01c1;
  t.clickretroflex = 0x01c3;
  t.club = 0x2663;
  t.clubsuitblack = 0x2663;
  t.clubsuitwhite = 0x2667;
  t.cmcubedsquare = 0x33a4;
  t.cmonospace = 0xff43;
  t.cmsquaredsquare = 0x33a0;
  t.coarmenian = 0x0581;
  t.colon = 0x003a;
  t.colonmonetary = 0x20a1;
  t.colonmonospace = 0xff1a;
  t.colonsign = 0x20a1;
  t.colonsmall = 0xfe55;
  t.colontriangularhalfmod = 0x02d1;
  t.colontriangularmod = 0x02d0;
  t.comma = 0x002c;
  t.commaabovecmb = 0x0313;
  t.commaaboverightcmb = 0x0315;
  t.commaaccent = 0xf6c3;
  t.commaarabic = 0x060c;
  t.commaarmenian = 0x055d;
  t.commainferior = 0xf6e1;
  t.commamonospace = 0xff0c;
  t.commareversedabovecmb = 0x0314;
  t.commareversedmod = 0x02bd;
  t.commasmall = 0xfe50;
  t.commasuperior = 0xf6e2;
  t.commaturnedabovecmb = 0x0312;
  t.commaturnedmod = 0x02bb;
  t.compass = 0x263c;
  t.congruent = 0x2245;
  t.contourintegral = 0x222e;
  t.control = 0x2303;
  t.controlACK = 0x0006;
  t.controlBEL = 0x0007;
  t.controlBS = 0x0008;
  t.controlCAN = 0x0018;
  t.controlCR = 0x000d;
  t.controlDC1 = 0x0011;
  t.controlDC2 = 0x0012;
  t.controlDC3 = 0x0013;
  t.controlDC4 = 0x0014;
  t.controlDEL = 0x007f;
  t.controlDLE = 0x0010;
  t.controlEM = 0x0019;
  t.controlENQ = 0x0005;
  t.controlEOT = 0x0004;
  t.controlESC = 0x001b;
  t.controlETB = 0x0017;
  t.controlETX = 0x0003;
  t.controlFF = 0x000c;
  t.controlFS = 0x001c;
  t.controlGS = 0x001d;
  t.controlHT = 0x0009;
  t.controlLF = 0x000a;
  t.controlNAK = 0x0015;
  t.controlNULL = 0x0000;
  t.controlRS = 0x001e;
  t.controlSI = 0x000f;
  t.controlSO = 0x000e;
  t.controlSOT = 0x0002;
  t.controlSTX = 0x0001;
  t.controlSUB = 0x001a;
  t.controlSYN = 0x0016;
  t.controlUS = 0x001f;
  t.controlVT = 0x000b;
  t.copyright = 0x00a9;
  t.copyrightsans = 0xf8e9;
  t.copyrightserif = 0xf6d9;
  t.cornerbracketleft = 0x300c;
  t.cornerbracketlefthalfwidth = 0xff62;
  t.cornerbracketleftvertical = 0xfe41;
  t.cornerbracketright = 0x300d;
  t.cornerbracketrighthalfwidth = 0xff63;
  t.cornerbracketrightvertical = 0xfe42;
  t.corporationsquare = 0x337f;
  t.cosquare = 0x33c7;
  t.coverkgsquare = 0x33c6;
  t.cparen = 0x249e;
  t.cruzeiro = 0x20a2;
  t.cstretched = 0x0297;
  t.curlyand = 0x22cf;
  t.curlyor = 0x22ce;
  t.currency = 0x00a4;
  t.cyrBreve = 0xf6d1;
  t.cyrFlex = 0xf6d2;
  t.cyrbreve = 0xf6d4;
  t.cyrflex = 0xf6d5;
  t.d = 0x0064;
  t.daarmenian = 0x0564;
  t.dabengali = 0x09a6;
  t.dadarabic = 0x0636;
  t.dadeva = 0x0926;
  t.dadfinalarabic = 0xfebe;
  t.dadinitialarabic = 0xfebf;
  t.dadmedialarabic = 0xfec0;
  t.dagesh = 0x05bc;
  t.dageshhebrew = 0x05bc;
  t.dagger = 0x2020;
  t.daggerdbl = 0x2021;
  t.dagujarati = 0x0aa6;
  t.dagurmukhi = 0x0a26;
  t.dahiragana = 0x3060;
  t.dakatakana = 0x30c0;
  t.dalarabic = 0x062f;
  t.dalet = 0x05d3;
  t.daletdagesh = 0xfb33;
  t.daletdageshhebrew = 0xfb33;
  t.dalethebrew = 0x05d3;
  t.dalfinalarabic = 0xfeaa;
  t.dammaarabic = 0x064f;
  t.dammalowarabic = 0x064f;
  t.dammatanaltonearabic = 0x064c;
  t.dammatanarabic = 0x064c;
  t.danda = 0x0964;
  t.dargahebrew = 0x05a7;
  t.dargalefthebrew = 0x05a7;
  t.dasiapneumatacyrilliccmb = 0x0485;
  t.dblGrave = 0xf6d3;
  t.dblanglebracketleft = 0x300a;
  t.dblanglebracketleftvertical = 0xfe3d;
  t.dblanglebracketright = 0x300b;
  t.dblanglebracketrightvertical = 0xfe3e;
  t.dblarchinvertedbelowcmb = 0x032b;
  t.dblarrowleft = 0x21d4;
  t.dblarrowright = 0x21d2;
  t.dbldanda = 0x0965;
  t.dblgrave = 0xf6d6;
  t.dblgravecmb = 0x030f;
  t.dblintegral = 0x222c;
  t.dbllowline = 0x2017;
  t.dbllowlinecmb = 0x0333;
  t.dbloverlinecmb = 0x033f;
  t.dblprimemod = 0x02ba;
  t.dblverticalbar = 0x2016;
  t.dblverticallineabovecmb = 0x030e;
  t.dbopomofo = 0x3109;
  t.dbsquare = 0x33c8;
  t.dcaron = 0x010f;
  t.dcedilla = 0x1e11;
  t.dcircle = 0x24d3;
  t.dcircumflexbelow = 0x1e13;
  t.dcroat = 0x0111;
  t.ddabengali = 0x09a1;
  t.ddadeva = 0x0921;
  t.ddagujarati = 0x0aa1;
  t.ddagurmukhi = 0x0a21;
  t.ddalarabic = 0x0688;
  t.ddalfinalarabic = 0xfb89;
  t.dddhadeva = 0x095c;
  t.ddhabengali = 0x09a2;
  t.ddhadeva = 0x0922;
  t.ddhagujarati = 0x0aa2;
  t.ddhagurmukhi = 0x0a22;
  t.ddotaccent = 0x1e0b;
  t.ddotbelow = 0x1e0d;
  t.decimalseparatorarabic = 0x066b;
  t.decimalseparatorpersian = 0x066b;
  t.decyrillic = 0x0434;
  t.degree = 0x00b0;
  t.dehihebrew = 0x05ad;
  t.dehiragana = 0x3067;
  t.deicoptic = 0x03ef;
  t.dekatakana = 0x30c7;
  t.deleteleft = 0x232b;
  t.deleteright = 0x2326;
  t.delta = 0x03b4;
  t.deltaturned = 0x018d;
  t.denominatorminusonenumeratorbengali = 0x09f8;
  t.dezh = 0x02a4;
  t.dhabengali = 0x09a7;
  t.dhadeva = 0x0927;
  t.dhagujarati = 0x0aa7;
  t.dhagurmukhi = 0x0a27;
  t.dhook = 0x0257;
  t.dialytikatonos = 0x0385;
  t.dialytikatonoscmb = 0x0344;
  t.diamond = 0x2666;
  t.diamondsuitwhite = 0x2662;
  t.dieresis = 0x00a8;
  t.dieresisacute = 0xf6d7;
  t.dieresisbelowcmb = 0x0324;
  t.dieresiscmb = 0x0308;
  t.dieresisgrave = 0xf6d8;
  t.dieresistonos = 0x0385;
  t.dihiragana = 0x3062;
  t.dikatakana = 0x30c2;
  t.dittomark = 0x3003;
  t.divide = 0x00f7;
  t.divides = 0x2223;
  t.divisionslash = 0x2215;
  t.djecyrillic = 0x0452;
  t.dkshade = 0x2593;
  t.dlinebelow = 0x1e0f;
  t.dlsquare = 0x3397;
  t.dmacron = 0x0111;
  t.dmonospace = 0xff44;
  t.dnblock = 0x2584;
  t.dochadathai = 0x0e0e;
  t.dodekthai = 0x0e14;
  t.dohiragana = 0x3069;
  t.dokatakana = 0x30c9;
  t.dollar = 0x0024;
  t.dollarinferior = 0xf6e3;
  t.dollarmonospace = 0xff04;
  t.dollaroldstyle = 0xf724;
  t.dollarsmall = 0xfe69;
  t.dollarsuperior = 0xf6e4;
  t.dong = 0x20ab;
  t.dorusquare = 0x3326;
  t.dotaccent = 0x02d9;
  t.dotaccentcmb = 0x0307;
  t.dotbelowcmb = 0x0323;
  t.dotbelowcomb = 0x0323;
  t.dotkatakana = 0x30fb;
  t.dotlessi = 0x0131;
  t.dotlessj = 0xf6be;
  t.dotlessjstrokehook = 0x0284;
  t.dotmath = 0x22c5;
  t.dottedcircle = 0x25cc;
  t.doubleyodpatah = 0xfb1f;
  t.doubleyodpatahhebrew = 0xfb1f;
  t.downtackbelowcmb = 0x031e;
  t.downtackmod = 0x02d5;
  t.dparen = 0x249f;
  t.dsuperior = 0xf6eb;
  t.dtail = 0x0256;
  t.dtopbar = 0x018c;
  t.duhiragana = 0x3065;
  t.dukatakana = 0x30c5;
  t.dz = 0x01f3;
  t.dzaltone = 0x02a3;
  t.dzcaron = 0x01c6;
  t.dzcurl = 0x02a5;
  t.dzeabkhasiancyrillic = 0x04e1;
  t.dzecyrillic = 0x0455;
  t.dzhecyrillic = 0x045f;
  t.e = 0x0065;
  t.eacute = 0x00e9;
  t.earth = 0x2641;
  t.ebengali = 0x098f;
  t.ebopomofo = 0x311c;
  t.ebreve = 0x0115;
  t.ecandradeva = 0x090d;
  t.ecandragujarati = 0x0a8d;
  t.ecandravowelsigndeva = 0x0945;
  t.ecandravowelsigngujarati = 0x0ac5;
  t.ecaron = 0x011b;
  t.ecedillabreve = 0x1e1d;
  t.echarmenian = 0x0565;
  t.echyiwnarmenian = 0x0587;
  t.ecircle = 0x24d4;
  t.ecircumflex = 0x00ea;
  t.ecircumflexacute = 0x1ebf;
  t.ecircumflexbelow = 0x1e19;
  t.ecircumflexdotbelow = 0x1ec7;
  t.ecircumflexgrave = 0x1ec1;
  t.ecircumflexhookabove = 0x1ec3;
  t.ecircumflextilde = 0x1ec5;
  t.ecyrillic = 0x0454;
  t.edblgrave = 0x0205;
  t.edeva = 0x090f;
  t.edieresis = 0x00eb;
  t.edot = 0x0117;
  t.edotaccent = 0x0117;
  t.edotbelow = 0x1eb9;
  t.eegurmukhi = 0x0a0f;
  t.eematragurmukhi = 0x0a47;
  t.efcyrillic = 0x0444;
  t.egrave = 0x00e8;
  t.egujarati = 0x0a8f;
  t.eharmenian = 0x0567;
  t.ehbopomofo = 0x311d;
  t.ehiragana = 0x3048;
  t.ehookabove = 0x1ebb;
  t.eibopomofo = 0x311f;
  t.eight = 0x0038;
  t.eightarabic = 0x0668;
  t.eightbengali = 0x09ee;
  t.eightcircle = 0x2467;
  t.eightcircleinversesansserif = 0x2791;
  t.eightdeva = 0x096e;
  t.eighteencircle = 0x2471;
  t.eighteenparen = 0x2485;
  t.eighteenperiod = 0x2499;
  t.eightgujarati = 0x0aee;
  t.eightgurmukhi = 0x0a6e;
  t.eighthackarabic = 0x0668;
  t.eighthangzhou = 0x3028;
  t.eighthnotebeamed = 0x266b;
  t.eightideographicparen = 0x3227;
  t.eightinferior = 0x2088;
  t.eightmonospace = 0xff18;
  t.eightoldstyle = 0xf738;
  t.eightparen = 0x247b;
  t.eightperiod = 0x248f;
  t.eightpersian = 0x06f8;
  t.eightroman = 0x2177;
  t.eightsuperior = 0x2078;
  t.eightthai = 0x0e58;
  t.einvertedbreve = 0x0207;
  t.eiotifiedcyrillic = 0x0465;
  t.ekatakana = 0x30a8;
  t.ekatakanahalfwidth = 0xff74;
  t.ekonkargurmukhi = 0x0a74;
  t.ekorean = 0x3154;
  t.elcyrillic = 0x043b;
  t.element = 0x2208;
  t.elevencircle = 0x246a;
  t.elevenparen = 0x247e;
  t.elevenperiod = 0x2492;
  t.elevenroman = 0x217a;
  t.ellipsis = 0x2026;
  t.ellipsisvertical = 0x22ee;
  t.emacron = 0x0113;
  t.emacronacute = 0x1e17;
  t.emacrongrave = 0x1e15;
  t.emcyrillic = 0x043c;
  t.emdash = 0x2014;
  t.emdashvertical = 0xfe31;
  t.emonospace = 0xff45;
  t.emphasismarkarmenian = 0x055b;
  t.emptyset = 0x2205;
  t.enbopomofo = 0x3123;
  t.encyrillic = 0x043d;
  t.endash = 0x2013;
  t.endashvertical = 0xfe32;
  t.endescendercyrillic = 0x04a3;
  t.eng = 0x014b;
  t.engbopomofo = 0x3125;
  t.enghecyrillic = 0x04a5;
  t.enhookcyrillic = 0x04c8;
  t.enspace = 0x2002;
  t.eogonek = 0x0119;
  t.eokorean = 0x3153;
  t.eopen = 0x025b;
  t.eopenclosed = 0x029a;
  t.eopenreversed = 0x025c;
  t.eopenreversedclosed = 0x025e;
  t.eopenreversedhook = 0x025d;
  t.eparen = 0x24a0;
  t.epsilon = 0x03b5;
  t.epsilontonos = 0x03ad;
  t.equal = 0x003d;
  t.equalmonospace = 0xff1d;
  t.equalsmall = 0xfe66;
  t.equalsuperior = 0x207c;
  t.equivalence = 0x2261;
  t.erbopomofo = 0x3126;
  t.ercyrillic = 0x0440;
  t.ereversed = 0x0258;
  t.ereversedcyrillic = 0x044d;
  t.escyrillic = 0x0441;
  t.esdescendercyrillic = 0x04ab;
  t.esh = 0x0283;
  t.eshcurl = 0x0286;
  t.eshortdeva = 0x090e;
  t.eshortvowelsigndeva = 0x0946;
  t.eshreversedloop = 0x01aa;
  t.eshsquatreversed = 0x0285;
  t.esmallhiragana = 0x3047;
  t.esmallkatakana = 0x30a7;
  t.esmallkatakanahalfwidth = 0xff6a;
  t.estimated = 0x212e;
  t.esuperior = 0xf6ec;
  t.eta = 0x03b7;
  t.etarmenian = 0x0568;
  t.etatonos = 0x03ae;
  t.eth = 0x00f0;
  t.etilde = 0x1ebd;
  t.etildebelow = 0x1e1b;
  t.etnahtafoukhhebrew = 0x0591;
  t.etnahtafoukhlefthebrew = 0x0591;
  t.etnahtahebrew = 0x0591;
  t.etnahtalefthebrew = 0x0591;
  t.eturned = 0x01dd;
  t.eukorean = 0x3161;
  t.euro = 0x20ac;
  t.evowelsignbengali = 0x09c7;
  t.evowelsigndeva = 0x0947;
  t.evowelsigngujarati = 0x0ac7;
  t.exclam = 0x0021;
  t.exclamarmenian = 0x055c;
  t.exclamdbl = 0x203c;
  t.exclamdown = 0x00a1;
  t.exclamdownsmall = 0xf7a1;
  t.exclammonospace = 0xff01;
  t.exclamsmall = 0xf721;
  t.existential = 0x2203;
  t.ezh = 0x0292;
  t.ezhcaron = 0x01ef;
  t.ezhcurl = 0x0293;
  t.ezhreversed = 0x01b9;
  t.ezhtail = 0x01ba;
  t.f = 0x0066;
  t.fadeva = 0x095e;
  t.fagurmukhi = 0x0a5e;
  t.fahrenheit = 0x2109;
  t.fathaarabic = 0x064e;
  t.fathalowarabic = 0x064e;
  t.fathatanarabic = 0x064b;
  t.fbopomofo = 0x3108;
  t.fcircle = 0x24d5;
  t.fdotaccent = 0x1e1f;
  t.feharabic = 0x0641;
  t.feharmenian = 0x0586;
  t.fehfinalarabic = 0xfed2;
  t.fehinitialarabic = 0xfed3;
  t.fehmedialarabic = 0xfed4;
  t.feicoptic = 0x03e5;
  t.female = 0x2640;
  t.ff = 0xfb00;
  t.f_f = 0xfb00;
  t.ffi = 0xfb03;
  t.f_f_i = 0xfb03;
  t.ffl = 0xfb04;
  t.f_f_l = 0xfb04;
  t.fi = 0xfb01;
  t.f_i = 0xfb01;
  t.fifteencircle = 0x246e;
  t.fifteenparen = 0x2482;
  t.fifteenperiod = 0x2496;
  t.figuredash = 0x2012;
  t.filledbox = 0x25a0;
  t.filledrect = 0x25ac;
  t.finalkaf = 0x05da;
  t.finalkafdagesh = 0xfb3a;
  t.finalkafdageshhebrew = 0xfb3a;
  t.finalkafhebrew = 0x05da;
  t.finalmem = 0x05dd;
  t.finalmemhebrew = 0x05dd;
  t.finalnun = 0x05df;
  t.finalnunhebrew = 0x05df;
  t.finalpe = 0x05e3;
  t.finalpehebrew = 0x05e3;
  t.finaltsadi = 0x05e5;
  t.finaltsadihebrew = 0x05e5;
  t.firsttonechinese = 0x02c9;
  t.fisheye = 0x25c9;
  t.fitacyrillic = 0x0473;
  t.five = 0x0035;
  t.fivearabic = 0x0665;
  t.fivebengali = 0x09eb;
  t.fivecircle = 0x2464;
  t.fivecircleinversesansserif = 0x278e;
  t.fivedeva = 0x096b;
  t.fiveeighths = 0x215d;
  t.fivegujarati = 0x0aeb;
  t.fivegurmukhi = 0x0a6b;
  t.fivehackarabic = 0x0665;
  t.fivehangzhou = 0x3025;
  t.fiveideographicparen = 0x3224;
  t.fiveinferior = 0x2085;
  t.fivemonospace = 0xff15;
  t.fiveoldstyle = 0xf735;
  t.fiveparen = 0x2478;
  t.fiveperiod = 0x248c;
  t.fivepersian = 0x06f5;
  t.fiveroman = 0x2174;
  t.fivesuperior = 0x2075;
  t.fivethai = 0x0e55;
  t.fl = 0xfb02;
  t.f_l = 0xfb02;
  t.florin = 0x0192;
  t.fmonospace = 0xff46;
  t.fmsquare = 0x3399;
  t.fofanthai = 0x0e1f;
  t.fofathai = 0x0e1d;
  t.fongmanthai = 0x0e4f;
  t.forall = 0x2200;
  t.four = 0x0034;
  t.fourarabic = 0x0664;
  t.fourbengali = 0x09ea;
  t.fourcircle = 0x2463;
  t.fourcircleinversesansserif = 0x278d;
  t.fourdeva = 0x096a;
  t.fourgujarati = 0x0aea;
  t.fourgurmukhi = 0x0a6a;
  t.fourhackarabic = 0x0664;
  t.fourhangzhou = 0x3024;
  t.fourideographicparen = 0x3223;
  t.fourinferior = 0x2084;
  t.fourmonospace = 0xff14;
  t.fournumeratorbengali = 0x09f7;
  t.fouroldstyle = 0xf734;
  t.fourparen = 0x2477;
  t.fourperiod = 0x248b;
  t.fourpersian = 0x06f4;
  t.fourroman = 0x2173;
  t.foursuperior = 0x2074;
  t.fourteencircle = 0x246d;
  t.fourteenparen = 0x2481;
  t.fourteenperiod = 0x2495;
  t.fourthai = 0x0e54;
  t.fourthtonechinese = 0x02cb;
  t.fparen = 0x24a1;
  t.fraction = 0x2044;
  t.franc = 0x20a3;
  t.g = 0x0067;
  t.gabengali = 0x0997;
  t.gacute = 0x01f5;
  t.gadeva = 0x0917;
  t.gafarabic = 0x06af;
  t.gaffinalarabic = 0xfb93;
  t.gafinitialarabic = 0xfb94;
  t.gafmedialarabic = 0xfb95;
  t.gagujarati = 0x0a97;
  t.gagurmukhi = 0x0a17;
  t.gahiragana = 0x304c;
  t.gakatakana = 0x30ac;
  t.gamma = 0x03b3;
  t.gammalatinsmall = 0x0263;
  t.gammasuperior = 0x02e0;
  t.gangiacoptic = 0x03eb;
  t.gbopomofo = 0x310d;
  t.gbreve = 0x011f;
  t.gcaron = 0x01e7;
  t.gcedilla = 0x0123;
  t.gcircle = 0x24d6;
  t.gcircumflex = 0x011d;
  t.gcommaaccent = 0x0123;
  t.gdot = 0x0121;
  t.gdotaccent = 0x0121;
  t.gecyrillic = 0x0433;
  t.gehiragana = 0x3052;
  t.gekatakana = 0x30b2;
  t.geometricallyequal = 0x2251;
  t.gereshaccenthebrew = 0x059c;
  t.gereshhebrew = 0x05f3;
  t.gereshmuqdamhebrew = 0x059d;
  t.germandbls = 0x00df;
  t.gershayimaccenthebrew = 0x059e;
  t.gershayimhebrew = 0x05f4;
  t.getamark = 0x3013;
  t.ghabengali = 0x0998;
  t.ghadarmenian = 0x0572;
  t.ghadeva = 0x0918;
  t.ghagujarati = 0x0a98;
  t.ghagurmukhi = 0x0a18;
  t.ghainarabic = 0x063a;
  t.ghainfinalarabic = 0xfece;
  t.ghaininitialarabic = 0xfecf;
  t.ghainmedialarabic = 0xfed0;
  t.ghemiddlehookcyrillic = 0x0495;
  t.ghestrokecyrillic = 0x0493;
  t.gheupturncyrillic = 0x0491;
  t.ghhadeva = 0x095a;
  t.ghhagurmukhi = 0x0a5a;
  t.ghook = 0x0260;
  t.ghzsquare = 0x3393;
  t.gihiragana = 0x304e;
  t.gikatakana = 0x30ae;
  t.gimarmenian = 0x0563;
  t.gimel = 0x05d2;
  t.gimeldagesh = 0xfb32;
  t.gimeldageshhebrew = 0xfb32;
  t.gimelhebrew = 0x05d2;
  t.gjecyrillic = 0x0453;
  t.glottalinvertedstroke = 0x01be;
  t.glottalstop = 0x0294;
  t.glottalstopinverted = 0x0296;
  t.glottalstopmod = 0x02c0;
  t.glottalstopreversed = 0x0295;
  t.glottalstopreversedmod = 0x02c1;
  t.glottalstopreversedsuperior = 0x02e4;
  t.glottalstopstroke = 0x02a1;
  t.glottalstopstrokereversed = 0x02a2;
  t.gmacron = 0x1e21;
  t.gmonospace = 0xff47;
  t.gohiragana = 0x3054;
  t.gokatakana = 0x30b4;
  t.gparen = 0x24a2;
  t.gpasquare = 0x33ac;
  t.gradient = 0x2207;
  t.grave = 0x0060;
  t.gravebelowcmb = 0x0316;
  t.gravecmb = 0x0300;
  t.gravecomb = 0x0300;
  t.gravedeva = 0x0953;
  t.gravelowmod = 0x02ce;
  t.gravemonospace = 0xff40;
  t.gravetonecmb = 0x0340;
  t.greater = 0x003e;
  t.greaterequal = 0x2265;
  t.greaterequalorless = 0x22db;
  t.greatermonospace = 0xff1e;
  t.greaterorequivalent = 0x2273;
  t.greaterorless = 0x2277;
  t.greateroverequal = 0x2267;
  t.greatersmall = 0xfe65;
  t.gscript = 0x0261;
  t.gstroke = 0x01e5;
  t.guhiragana = 0x3050;
  t.guillemotleft = 0x00ab;
  t.guillemotright = 0x00bb;
  t.guilsinglleft = 0x2039;
  t.guilsinglright = 0x203a;
  t.gukatakana = 0x30b0;
  t.guramusquare = 0x3318;
  t.gysquare = 0x33c9;
  t.h = 0x0068;
  t.haabkhasiancyrillic = 0x04a9;
  t.haaltonearabic = 0x06c1;
  t.habengali = 0x09b9;
  t.hadescendercyrillic = 0x04b3;
  t.hadeva = 0x0939;
  t.hagujarati = 0x0ab9;
  t.hagurmukhi = 0x0a39;
  t.haharabic = 0x062d;
  t.hahfinalarabic = 0xfea2;
  t.hahinitialarabic = 0xfea3;
  t.hahiragana = 0x306f;
  t.hahmedialarabic = 0xfea4;
  t.haitusquare = 0x332a;
  t.hakatakana = 0x30cf;
  t.hakatakanahalfwidth = 0xff8a;
  t.halantgurmukhi = 0x0a4d;
  t.hamzaarabic = 0x0621;
  t.hamzalowarabic = 0x0621;
  t.hangulfiller = 0x3164;
  t.hardsigncyrillic = 0x044a;
  t.harpoonleftbarbup = 0x21bc;
  t.harpoonrightbarbup = 0x21c0;
  t.hasquare = 0x33ca;
  t.hatafpatah = 0x05b2;
  t.hatafpatah16 = 0x05b2;
  t.hatafpatah23 = 0x05b2;
  t.hatafpatah2f = 0x05b2;
  t.hatafpatahhebrew = 0x05b2;
  t.hatafpatahnarrowhebrew = 0x05b2;
  t.hatafpatahquarterhebrew = 0x05b2;
  t.hatafpatahwidehebrew = 0x05b2;
  t.hatafqamats = 0x05b3;
  t.hatafqamats1b = 0x05b3;
  t.hatafqamats28 = 0x05b3;
  t.hatafqamats34 = 0x05b3;
  t.hatafqamatshebrew = 0x05b3;
  t.hatafqamatsnarrowhebrew = 0x05b3;
  t.hatafqamatsquarterhebrew = 0x05b3;
  t.hatafqamatswidehebrew = 0x05b3;
  t.hatafsegol = 0x05b1;
  t.hatafsegol17 = 0x05b1;
  t.hatafsegol24 = 0x05b1;
  t.hatafsegol30 = 0x05b1;
  t.hatafsegolhebrew = 0x05b1;
  t.hatafsegolnarrowhebrew = 0x05b1;
  t.hatafsegolquarterhebrew = 0x05b1;
  t.hatafsegolwidehebrew = 0x05b1;
  t.hbar = 0x0127;
  t.hbopomofo = 0x310f;
  t.hbrevebelow = 0x1e2b;
  t.hcedilla = 0x1e29;
  t.hcircle = 0x24d7;
  t.hcircumflex = 0x0125;
  t.hdieresis = 0x1e27;
  t.hdotaccent = 0x1e23;
  t.hdotbelow = 0x1e25;
  t.he = 0x05d4;
  t.heart = 0x2665;
  t.heartsuitblack = 0x2665;
  t.heartsuitwhite = 0x2661;
  t.hedagesh = 0xfb34;
  t.hedageshhebrew = 0xfb34;
  t.hehaltonearabic = 0x06c1;
  t.heharabic = 0x0647;
  t.hehebrew = 0x05d4;
  t.hehfinalaltonearabic = 0xfba7;
  t.hehfinalalttwoarabic = 0xfeea;
  t.hehfinalarabic = 0xfeea;
  t.hehhamzaabovefinalarabic = 0xfba5;
  t.hehhamzaaboveisolatedarabic = 0xfba4;
  t.hehinitialaltonearabic = 0xfba8;
  t.hehinitialarabic = 0xfeeb;
  t.hehiragana = 0x3078;
  t.hehmedialaltonearabic = 0xfba9;
  t.hehmedialarabic = 0xfeec;
  t.heiseierasquare = 0x337b;
  t.hekatakana = 0x30d8;
  t.hekatakanahalfwidth = 0xff8d;
  t.hekutaarusquare = 0x3336;
  t.henghook = 0x0267;
  t.herutusquare = 0x3339;
  t.het = 0x05d7;
  t.hethebrew = 0x05d7;
  t.hhook = 0x0266;
  t.hhooksuperior = 0x02b1;
  t.hieuhacirclekorean = 0x327b;
  t.hieuhaparenkorean = 0x321b;
  t.hieuhcirclekorean = 0x326d;
  t.hieuhkorean = 0x314e;
  t.hieuhparenkorean = 0x320d;
  t.hihiragana = 0x3072;
  t.hikatakana = 0x30d2;
  t.hikatakanahalfwidth = 0xff8b;
  t.hiriq = 0x05b4;
  t.hiriq14 = 0x05b4;
  t.hiriq21 = 0x05b4;
  t.hiriq2d = 0x05b4;
  t.hiriqhebrew = 0x05b4;
  t.hiriqnarrowhebrew = 0x05b4;
  t.hiriqquarterhebrew = 0x05b4;
  t.hiriqwidehebrew = 0x05b4;
  t.hlinebelow = 0x1e96;
  t.hmonospace = 0xff48;
  t.hoarmenian = 0x0570;
  t.hohipthai = 0x0e2b;
  t.hohiragana = 0x307b;
  t.hokatakana = 0x30db;
  t.hokatakanahalfwidth = 0xff8e;
  t.holam = 0x05b9;
  t.holam19 = 0x05b9;
  t.holam26 = 0x05b9;
  t.holam32 = 0x05b9;
  t.holamhebrew = 0x05b9;
  t.holamnarrowhebrew = 0x05b9;
  t.holamquarterhebrew = 0x05b9;
  t.holamwidehebrew = 0x05b9;
  t.honokhukthai = 0x0e2e;
  t.hookabovecomb = 0x0309;
  t.hookcmb = 0x0309;
  t.hookpalatalizedbelowcmb = 0x0321;
  t.hookretroflexbelowcmb = 0x0322;
  t.hoonsquare = 0x3342;
  t.horicoptic = 0x03e9;
  t.horizontalbar = 0x2015;
  t.horncmb = 0x031b;
  t.hotsprings = 0x2668;
  t.house = 0x2302;
  t.hparen = 0x24a3;
  t.hsuperior = 0x02b0;
  t.hturned = 0x0265;
  t.huhiragana = 0x3075;
  t.huiitosquare = 0x3333;
  t.hukatakana = 0x30d5;
  t.hukatakanahalfwidth = 0xff8c;
  t.hungarumlaut = 0x02dd;
  t.hungarumlautcmb = 0x030b;
  t.hv = 0x0195;
  t.hyphen = 0x002d;
  t.hypheninferior = 0xf6e5;
  t.hyphenmonospace = 0xff0d;
  t.hyphensmall = 0xfe63;
  t.hyphensuperior = 0xf6e6;
  t.hyphentwo = 0x2010;
  t.i = 0x0069;
  t.iacute = 0x00ed;
  t.iacyrillic = 0x044f;
  t.ibengali = 0x0987;
  t.ibopomofo = 0x3127;
  t.ibreve = 0x012d;
  t.icaron = 0x01d0;
  t.icircle = 0x24d8;
  t.icircumflex = 0x00ee;
  t.icyrillic = 0x0456;
  t.idblgrave = 0x0209;
  t.ideographearthcircle = 0x328f;
  t.ideographfirecircle = 0x328b;
  t.ideographicallianceparen = 0x323f;
  t.ideographiccallparen = 0x323a;
  t.ideographiccentrecircle = 0x32a5;
  t.ideographicclose = 0x3006;
  t.ideographiccomma = 0x3001;
  t.ideographiccommaleft = 0xff64;
  t.ideographiccongratulationparen = 0x3237;
  t.ideographiccorrectcircle = 0x32a3;
  t.ideographicearthparen = 0x322f;
  t.ideographicenterpriseparen = 0x323d;
  t.ideographicexcellentcircle = 0x329d;
  t.ideographicfestivalparen = 0x3240;
  t.ideographicfinancialcircle = 0x3296;
  t.ideographicfinancialparen = 0x3236;
  t.ideographicfireparen = 0x322b;
  t.ideographichaveparen = 0x3232;
  t.ideographichighcircle = 0x32a4;
  t.ideographiciterationmark = 0x3005;
  t.ideographiclaborcircle = 0x3298;
  t.ideographiclaborparen = 0x3238;
  t.ideographicleftcircle = 0x32a7;
  t.ideographiclowcircle = 0x32a6;
  t.ideographicmedicinecircle = 0x32a9;
  t.ideographicmetalparen = 0x322e;
  t.ideographicmoonparen = 0x322a;
  t.ideographicnameparen = 0x3234;
  t.ideographicperiod = 0x3002;
  t.ideographicprintcircle = 0x329e;
  t.ideographicreachparen = 0x3243;
  t.ideographicrepresentparen = 0x3239;
  t.ideographicresourceparen = 0x323e;
  t.ideographicrightcircle = 0x32a8;
  t.ideographicsecretcircle = 0x3299;
  t.ideographicselfparen = 0x3242;
  t.ideographicsocietyparen = 0x3233;
  t.ideographicspace = 0x3000;
  t.ideographicspecialparen = 0x3235;
  t.ideographicstockparen = 0x3231;
  t.ideographicstudyparen = 0x323b;
  t.ideographicsunparen = 0x3230;
  t.ideographicsuperviseparen = 0x323c;
  t.ideographicwaterparen = 0x322c;
  t.ideographicwoodparen = 0x322d;
  t.ideographiczero = 0x3007;
  t.ideographmetalcircle = 0x328e;
  t.ideographmooncircle = 0x328a;
  t.ideographnamecircle = 0x3294;
  t.ideographsuncircle = 0x3290;
  t.ideographwatercircle = 0x328c;
  t.ideographwoodcircle = 0x328d;
  t.ideva = 0x0907;
  t.idieresis = 0x00ef;
  t.idieresisacute = 0x1e2f;
  t.idieresiscyrillic = 0x04e5;
  t.idotbelow = 0x1ecb;
  t.iebrevecyrillic = 0x04d7;
  t.iecyrillic = 0x0435;
  t.ieungacirclekorean = 0x3275;
  t.ieungaparenkorean = 0x3215;
  t.ieungcirclekorean = 0x3267;
  t.ieungkorean = 0x3147;
  t.ieungparenkorean = 0x3207;
  t.igrave = 0x00ec;
  t.igujarati = 0x0a87;
  t.igurmukhi = 0x0a07;
  t.ihiragana = 0x3044;
  t.ihookabove = 0x1ec9;
  t.iibengali = 0x0988;
  t.iicyrillic = 0x0438;
  t.iideva = 0x0908;
  t.iigujarati = 0x0a88;
  t.iigurmukhi = 0x0a08;
  t.iimatragurmukhi = 0x0a40;
  t.iinvertedbreve = 0x020b;
  t.iishortcyrillic = 0x0439;
  t.iivowelsignbengali = 0x09c0;
  t.iivowelsigndeva = 0x0940;
  t.iivowelsigngujarati = 0x0ac0;
  t.ij = 0x0133;
  t.ikatakana = 0x30a4;
  t.ikatakanahalfwidth = 0xff72;
  t.ikorean = 0x3163;
  t.ilde = 0x02dc;
  t.iluyhebrew = 0x05ac;
  t.imacron = 0x012b;
  t.imacroncyrillic = 0x04e3;
  t.imageorapproximatelyequal = 0x2253;
  t.imatragurmukhi = 0x0a3f;
  t.imonospace = 0xff49;
  t.increment = 0x2206;
  t.infinity = 0x221e;
  t.iniarmenian = 0x056b;
  t.integral = 0x222b;
  t.integralbottom = 0x2321;
  t.integralbt = 0x2321;
  t.integralex = 0xf8f5;
  t.integraltop = 0x2320;
  t.integraltp = 0x2320;
  t.intersection = 0x2229;
  t.intisquare = 0x3305;
  t.invbullet = 0x25d8;
  t.invcircle = 0x25d9;
  t.invsmileface = 0x263b;
  t.iocyrillic = 0x0451;
  t.iogonek = 0x012f;
  t.iota = 0x03b9;
  t.iotadieresis = 0x03ca;
  t.iotadieresistonos = 0x0390;
  t.iotalatin = 0x0269;
  t.iotatonos = 0x03af;
  t.iparen = 0x24a4;
  t.irigurmukhi = 0x0a72;
  t.ismallhiragana = 0x3043;
  t.ismallkatakana = 0x30a3;
  t.ismallkatakanahalfwidth = 0xff68;
  t.issharbengali = 0x09fa;
  t.istroke = 0x0268;
  t.isuperior = 0xf6ed;
  t.iterationhiragana = 0x309d;
  t.iterationkatakana = 0x30fd;
  t.itilde = 0x0129;
  t.itildebelow = 0x1e2d;
  t.iubopomofo = 0x3129;
  t.iucyrillic = 0x044e;
  t.ivowelsignbengali = 0x09bf;
  t.ivowelsigndeva = 0x093f;
  t.ivowelsigngujarati = 0x0abf;
  t.izhitsacyrillic = 0x0475;
  t.izhitsadblgravecyrillic = 0x0477;
  t.j = 0x006a;
  t.jaarmenian = 0x0571;
  t.jabengali = 0x099c;
  t.jadeva = 0x091c;
  t.jagujarati = 0x0a9c;
  t.jagurmukhi = 0x0a1c;
  t.jbopomofo = 0x3110;
  t.jcaron = 0x01f0;
  t.jcircle = 0x24d9;
  t.jcircumflex = 0x0135;
  t.jcrossedtail = 0x029d;
  t.jdotlessstroke = 0x025f;
  t.jecyrillic = 0x0458;
  t.jeemarabic = 0x062c;
  t.jeemfinalarabic = 0xfe9e;
  t.jeeminitialarabic = 0xfe9f;
  t.jeemmedialarabic = 0xfea0;
  t.jeharabic = 0x0698;
  t.jehfinalarabic = 0xfb8b;
  t.jhabengali = 0x099d;
  t.jhadeva = 0x091d;
  t.jhagujarati = 0x0a9d;
  t.jhagurmukhi = 0x0a1d;
  t.jheharmenian = 0x057b;
  t.jis = 0x3004;
  t.jmonospace = 0xff4a;
  t.jparen = 0x24a5;
  t.jsuperior = 0x02b2;
  t.k = 0x006b;
  t.kabashkircyrillic = 0x04a1;
  t.kabengali = 0x0995;
  t.kacute = 0x1e31;
  t.kacyrillic = 0x043a;
  t.kadescendercyrillic = 0x049b;
  t.kadeva = 0x0915;
  t.kaf = 0x05db;
  t.kafarabic = 0x0643;
  t.kafdagesh = 0xfb3b;
  t.kafdageshhebrew = 0xfb3b;
  t.kaffinalarabic = 0xfeda;
  t.kafhebrew = 0x05db;
  t.kafinitialarabic = 0xfedb;
  t.kafmedialarabic = 0xfedc;
  t.kafrafehebrew = 0xfb4d;
  t.kagujarati = 0x0a95;
  t.kagurmukhi = 0x0a15;
  t.kahiragana = 0x304b;
  t.kahookcyrillic = 0x04c4;
  t.kakatakana = 0x30ab;
  t.kakatakanahalfwidth = 0xff76;
  t.kappa = 0x03ba;
  t.kappasymbolgreek = 0x03f0;
  t.kapyeounmieumkorean = 0x3171;
  t.kapyeounphieuphkorean = 0x3184;
  t.kapyeounpieupkorean = 0x3178;
  t.kapyeounssangpieupkorean = 0x3179;
  t.karoriisquare = 0x330d;
  t.kashidaautoarabic = 0x0640;
  t.kashidaautonosidebearingarabic = 0x0640;
  t.kasmallkatakana = 0x30f5;
  t.kasquare = 0x3384;
  t.kasraarabic = 0x0650;
  t.kasratanarabic = 0x064d;
  t.kastrokecyrillic = 0x049f;
  t.katahiraprolongmarkhalfwidth = 0xff70;
  t.kaverticalstrokecyrillic = 0x049d;
  t.kbopomofo = 0x310e;
  t.kcalsquare = 0x3389;
  t.kcaron = 0x01e9;
  t.kcedilla = 0x0137;
  t.kcircle = 0x24da;
  t.kcommaaccent = 0x0137;
  t.kdotbelow = 0x1e33;
  t.keharmenian = 0x0584;
  t.kehiragana = 0x3051;
  t.kekatakana = 0x30b1;
  t.kekatakanahalfwidth = 0xff79;
  t.kenarmenian = 0x056f;
  t.kesmallkatakana = 0x30f6;
  t.kgreenlandic = 0x0138;
  t.khabengali = 0x0996;
  t.khacyrillic = 0x0445;
  t.khadeva = 0x0916;
  t.khagujarati = 0x0a96;
  t.khagurmukhi = 0x0a16;
  t.khaharabic = 0x062e;
  t.khahfinalarabic = 0xfea6;
  t.khahinitialarabic = 0xfea7;
  t.khahmedialarabic = 0xfea8;
  t.kheicoptic = 0x03e7;
  t.khhadeva = 0x0959;
  t.khhagurmukhi = 0x0a59;
  t.khieukhacirclekorean = 0x3278;
  t.khieukhaparenkorean = 0x3218;
  t.khieukhcirclekorean = 0x326a;
  t.khieukhkorean = 0x314b;
  t.khieukhparenkorean = 0x320a;
  t.khokhaithai = 0x0e02;
  t.khokhonthai = 0x0e05;
  t.khokhuatthai = 0x0e03;
  t.khokhwaithai = 0x0e04;
  t.khomutthai = 0x0e5b;
  t.khook = 0x0199;
  t.khorakhangthai = 0x0e06;
  t.khzsquare = 0x3391;
  t.kihiragana = 0x304d;
  t.kikatakana = 0x30ad;
  t.kikatakanahalfwidth = 0xff77;
  t.kiroguramusquare = 0x3315;
  t.kiromeetorusquare = 0x3316;
  t.kirosquare = 0x3314;
  t.kiyeokacirclekorean = 0x326e;
  t.kiyeokaparenkorean = 0x320e;
  t.kiyeokcirclekorean = 0x3260;
  t.kiyeokkorean = 0x3131;
  t.kiyeokparenkorean = 0x3200;
  t.kiyeoksioskorean = 0x3133;
  t.kjecyrillic = 0x045c;
  t.klinebelow = 0x1e35;
  t.klsquare = 0x3398;
  t.kmcubedsquare = 0x33a6;
  t.kmonospace = 0xff4b;
  t.kmsquaredsquare = 0x33a2;
  t.kohiragana = 0x3053;
  t.kohmsquare = 0x33c0;
  t.kokaithai = 0x0e01;
  t.kokatakana = 0x30b3;
  t.kokatakanahalfwidth = 0xff7a;
  t.kooposquare = 0x331e;
  t.koppacyrillic = 0x0481;
  t.koreanstandardsymbol = 0x327f;
  t.koroniscmb = 0x0343;
  t.kparen = 0x24a6;
  t.kpasquare = 0x33aa;
  t.ksicyrillic = 0x046f;
  t.ktsquare = 0x33cf;
  t.kturned = 0x029e;
  t.kuhiragana = 0x304f;
  t.kukatakana = 0x30af;
  t.kukatakanahalfwidth = 0xff78;
  t.kvsquare = 0x33b8;
  t.kwsquare = 0x33be;
  t.l = 0x006c;
  t.labengali = 0x09b2;
  t.lacute = 0x013a;
  t.ladeva = 0x0932;
  t.lagujarati = 0x0ab2;
  t.lagurmukhi = 0x0a32;
  t.lakkhangyaothai = 0x0e45;
  t.lamaleffinalarabic = 0xfefc;
  t.lamalefhamzaabovefinalarabic = 0xfef8;
  t.lamalefhamzaaboveisolatedarabic = 0xfef7;
  t.lamalefhamzabelowfinalarabic = 0xfefa;
  t.lamalefhamzabelowisolatedarabic = 0xfef9;
  t.lamalefisolatedarabic = 0xfefb;
  t.lamalefmaddaabovefinalarabic = 0xfef6;
  t.lamalefmaddaaboveisolatedarabic = 0xfef5;
  t.lamarabic = 0x0644;
  t.lambda = 0x03bb;
  t.lambdastroke = 0x019b;
  t.lamed = 0x05dc;
  t.lameddagesh = 0xfb3c;
  t.lameddageshhebrew = 0xfb3c;
  t.lamedhebrew = 0x05dc;
  t.lamfinalarabic = 0xfede;
  t.lamhahinitialarabic = 0xfcca;
  t.laminitialarabic = 0xfedf;
  t.lamjeeminitialarabic = 0xfcc9;
  t.lamkhahinitialarabic = 0xfccb;
  t.lamlamhehisolatedarabic = 0xfdf2;
  t.lammedialarabic = 0xfee0;
  t.lammeemhahinitialarabic = 0xfd88;
  t.lammeeminitialarabic = 0xfccc;
  t.largecircle = 0x25ef;
  t.lbar = 0x019a;
  t.lbelt = 0x026c;
  t.lbopomofo = 0x310c;
  t.lcaron = 0x013e;
  t.lcedilla = 0x013c;
  t.lcircle = 0x24db;
  t.lcircumflexbelow = 0x1e3d;
  t.lcommaaccent = 0x013c;
  t.ldot = 0x0140;
  t.ldotaccent = 0x0140;
  t.ldotbelow = 0x1e37;
  t.ldotbelowmacron = 0x1e39;
  t.leftangleabovecmb = 0x031a;
  t.lefttackbelowcmb = 0x0318;
  t.less = 0x003c;
  t.lessequal = 0x2264;
  t.lessequalorgreater = 0x22da;
  t.lessmonospace = 0xff1c;
  t.lessorequivalent = 0x2272;
  t.lessorgreater = 0x2276;
  t.lessoverequal = 0x2266;
  t.lesssmall = 0xfe64;
  t.lezh = 0x026e;
  t.lfblock = 0x258c;
  t.lhookretroflex = 0x026d;
  t.lira = 0x20a4;
  t.liwnarmenian = 0x056c;
  t.lj = 0x01c9;
  t.ljecyrillic = 0x0459;
  t.ll = 0xf6c0;
  t.lladeva = 0x0933;
  t.llagujarati = 0x0ab3;
  t.llinebelow = 0x1e3b;
  t.llladeva = 0x0934;
  t.llvocalicbengali = 0x09e1;
  t.llvocalicdeva = 0x0961;
  t.llvocalicvowelsignbengali = 0x09e3;
  t.llvocalicvowelsigndeva = 0x0963;
  t.lmiddletilde = 0x026b;
  t.lmonospace = 0xff4c;
  t.lmsquare = 0x33d0;
  t.lochulathai = 0x0e2c;
  t.logicaland = 0x2227;
  t.logicalnot = 0x00ac;
  t.logicalnotreversed = 0x2310;
  t.logicalor = 0x2228;
  t.lolingthai = 0x0e25;
  t.longs = 0x017f;
  t.lowlinecenterline = 0xfe4e;
  t.lowlinecmb = 0x0332;
  t.lowlinedashed = 0xfe4d;
  t.lozenge = 0x25ca;
  t.lparen = 0x24a7;
  t.lslash = 0x0142;
  t.lsquare = 0x2113;
  t.lsuperior = 0xf6ee;
  t.ltshade = 0x2591;
  t.luthai = 0x0e26;
  t.lvocalicbengali = 0x098c;
  t.lvocalicdeva = 0x090c;
  t.lvocalicvowelsignbengali = 0x09e2;
  t.lvocalicvowelsigndeva = 0x0962;
  t.lxsquare = 0x33d3;
  t.m = 0x006d;
  t.mabengali = 0x09ae;
  t.macron = 0x00af;
  t.macronbelowcmb = 0x0331;
  t.macroncmb = 0x0304;
  t.macronlowmod = 0x02cd;
  t.macronmonospace = 0xffe3;
  t.macute = 0x1e3f;
  t.madeva = 0x092e;
  t.magujarati = 0x0aae;
  t.magurmukhi = 0x0a2e;
  t.mahapakhhebrew = 0x05a4;
  t.mahapakhlefthebrew = 0x05a4;
  t.mahiragana = 0x307e;
  t.maichattawalowleftthai = 0xf895;
  t.maichattawalowrightthai = 0xf894;
  t.maichattawathai = 0x0e4b;
  t.maichattawaupperleftthai = 0xf893;
  t.maieklowleftthai = 0xf88c;
  t.maieklowrightthai = 0xf88b;
  t.maiekthai = 0x0e48;
  t.maiekupperleftthai = 0xf88a;
  t.maihanakatleftthai = 0xf884;
  t.maihanakatthai = 0x0e31;
  t.maitaikhuleftthai = 0xf889;
  t.maitaikhuthai = 0x0e47;
  t.maitholowleftthai = 0xf88f;
  t.maitholowrightthai = 0xf88e;
  t.maithothai = 0x0e49;
  t.maithoupperleftthai = 0xf88d;
  t.maitrilowleftthai = 0xf892;
  t.maitrilowrightthai = 0xf891;
  t.maitrithai = 0x0e4a;
  t.maitriupperleftthai = 0xf890;
  t.maiyamokthai = 0x0e46;
  t.makatakana = 0x30de;
  t.makatakanahalfwidth = 0xff8f;
  t.male = 0x2642;
  t.mansyonsquare = 0x3347;
  t.maqafhebrew = 0x05be;
  t.mars = 0x2642;
  t.masoracirclehebrew = 0x05af;
  t.masquare = 0x3383;
  t.mbopomofo = 0x3107;
  t.mbsquare = 0x33d4;
  t.mcircle = 0x24dc;
  t.mcubedsquare = 0x33a5;
  t.mdotaccent = 0x1e41;
  t.mdotbelow = 0x1e43;
  t.meemarabic = 0x0645;
  t.meemfinalarabic = 0xfee2;
  t.meeminitialarabic = 0xfee3;
  t.meemmedialarabic = 0xfee4;
  t.meemmeeminitialarabic = 0xfcd1;
  t.meemmeemisolatedarabic = 0xfc48;
  t.meetorusquare = 0x334d;
  t.mehiragana = 0x3081;
  t.meizierasquare = 0x337e;
  t.mekatakana = 0x30e1;
  t.mekatakanahalfwidth = 0xff92;
  t.mem = 0x05de;
  t.memdagesh = 0xfb3e;
  t.memdageshhebrew = 0xfb3e;
  t.memhebrew = 0x05de;
  t.menarmenian = 0x0574;
  t.merkhahebrew = 0x05a5;
  t.merkhakefulahebrew = 0x05a6;
  t.merkhakefulalefthebrew = 0x05a6;
  t.merkhalefthebrew = 0x05a5;
  t.mhook = 0x0271;
  t.mhzsquare = 0x3392;
  t.middledotkatakanahalfwidth = 0xff65;
  t.middot = 0x00b7;
  t.mieumacirclekorean = 0x3272;
  t.mieumaparenkorean = 0x3212;
  t.mieumcirclekorean = 0x3264;
  t.mieumkorean = 0x3141;
  t.mieumpansioskorean = 0x3170;
  t.mieumparenkorean = 0x3204;
  t.mieumpieupkorean = 0x316e;
  t.mieumsioskorean = 0x316f;
  t.mihiragana = 0x307f;
  t.mikatakana = 0x30df;
  t.mikatakanahalfwidth = 0xff90;
  t.minus = 0x2212;
  t.minusbelowcmb = 0x0320;
  t.minuscircle = 0x2296;
  t.minusmod = 0x02d7;
  t.minusplus = 0x2213;
  t.minute = 0x2032;
  t.miribaarusquare = 0x334a;
  t.mirisquare = 0x3349;
  t.mlonglegturned = 0x0270;
  t.mlsquare = 0x3396;
  t.mmcubedsquare = 0x33a3;
  t.mmonospace = 0xff4d;
  t.mmsquaredsquare = 0x339f;
  t.mohiragana = 0x3082;
  t.mohmsquare = 0x33c1;
  t.mokatakana = 0x30e2;
  t.mokatakanahalfwidth = 0xff93;
  t.molsquare = 0x33d6;
  t.momathai = 0x0e21;
  t.moverssquare = 0x33a7;
  t.moverssquaredsquare = 0x33a8;
  t.mparen = 0x24a8;
  t.mpasquare = 0x33ab;
  t.mssquare = 0x33b3;
  t.msuperior = 0xf6ef;
  t.mturned = 0x026f;
  t.mu = 0x00b5;
  t.mu1 = 0x00b5;
  t.muasquare = 0x3382;
  t.muchgreater = 0x226b;
  t.muchless = 0x226a;
  t.mufsquare = 0x338c;
  t.mugreek = 0x03bc;
  t.mugsquare = 0x338d;
  t.muhiragana = 0x3080;
  t.mukatakana = 0x30e0;
  t.mukatakanahalfwidth = 0xff91;
  t.mulsquare = 0x3395;
  t.multiply = 0x00d7;
  t.mumsquare = 0x339b;
  t.munahhebrew = 0x05a3;
  t.munahlefthebrew = 0x05a3;
  t.musicalnote = 0x266a;
  t.musicalnotedbl = 0x266b;
  t.musicflatsign = 0x266d;
  t.musicsharpsign = 0x266f;
  t.mussquare = 0x33b2;
  t.muvsquare = 0x33b6;
  t.muwsquare = 0x33bc;
  t.mvmegasquare = 0x33b9;
  t.mvsquare = 0x33b7;
  t.mwmegasquare = 0x33bf;
  t.mwsquare = 0x33bd;
  t.n = 0x006e;
  t.nabengali = 0x09a8;
  t.nabla = 0x2207;
  t.nacute = 0x0144;
  t.nadeva = 0x0928;
  t.nagujarati = 0x0aa8;
  t.nagurmukhi = 0x0a28;
  t.nahiragana = 0x306a;
  t.nakatakana = 0x30ca;
  t.nakatakanahalfwidth = 0xff85;
  t.napostrophe = 0x0149;
  t.nasquare = 0x3381;
  t.nbopomofo = 0x310b;
  t.nbspace = 0x00a0;
  t.ncaron = 0x0148;
  t.ncedilla = 0x0146;
  t.ncircle = 0x24dd;
  t.ncircumflexbelow = 0x1e4b;
  t.ncommaaccent = 0x0146;
  t.ndotaccent = 0x1e45;
  t.ndotbelow = 0x1e47;
  t.nehiragana = 0x306d;
  t.nekatakana = 0x30cd;
  t.nekatakanahalfwidth = 0xff88;
  t.newsheqelsign = 0x20aa;
  t.nfsquare = 0x338b;
  t.ngabengali = 0x0999;
  t.ngadeva = 0x0919;
  t.ngagujarati = 0x0a99;
  t.ngagurmukhi = 0x0a19;
  t.ngonguthai = 0x0e07;
  t.nhiragana = 0x3093;
  t.nhookleft = 0x0272;
  t.nhookretroflex = 0x0273;
  t.nieunacirclekorean = 0x326f;
  t.nieunaparenkorean = 0x320f;
  t.nieuncieuckorean = 0x3135;
  t.nieuncirclekorean = 0x3261;
  t.nieunhieuhkorean = 0x3136;
  t.nieunkorean = 0x3134;
  t.nieunpansioskorean = 0x3168;
  t.nieunparenkorean = 0x3201;
  t.nieunsioskorean = 0x3167;
  t.nieuntikeutkorean = 0x3166;
  t.nihiragana = 0x306b;
  t.nikatakana = 0x30cb;
  t.nikatakanahalfwidth = 0xff86;
  t.nikhahitleftthai = 0xf899;
  t.nikhahitthai = 0x0e4d;
  t.nine = 0x0039;
  t.ninearabic = 0x0669;
  t.ninebengali = 0x09ef;
  t.ninecircle = 0x2468;
  t.ninecircleinversesansserif = 0x2792;
  t.ninedeva = 0x096f;
  t.ninegujarati = 0x0aef;
  t.ninegurmukhi = 0x0a6f;
  t.ninehackarabic = 0x0669;
  t.ninehangzhou = 0x3029;
  t.nineideographicparen = 0x3228;
  t.nineinferior = 0x2089;
  t.ninemonospace = 0xff19;
  t.nineoldstyle = 0xf739;
  t.nineparen = 0x247c;
  t.nineperiod = 0x2490;
  t.ninepersian = 0x06f9;
  t.nineroman = 0x2178;
  t.ninesuperior = 0x2079;
  t.nineteencircle = 0x2472;
  t.nineteenparen = 0x2486;
  t.nineteenperiod = 0x249a;
  t.ninethai = 0x0e59;
  t.nj = 0x01cc;
  t.njecyrillic = 0x045a;
  t.nkatakana = 0x30f3;
  t.nkatakanahalfwidth = 0xff9d;
  t.nlegrightlong = 0x019e;
  t.nlinebelow = 0x1e49;
  t.nmonospace = 0xff4e;
  t.nmsquare = 0x339a;
  t.nnabengali = 0x09a3;
  t.nnadeva = 0x0923;
  t.nnagujarati = 0x0aa3;
  t.nnagurmukhi = 0x0a23;
  t.nnnadeva = 0x0929;
  t.nohiragana = 0x306e;
  t.nokatakana = 0x30ce;
  t.nokatakanahalfwidth = 0xff89;
  t.nonbreakingspace = 0x00a0;
  t.nonenthai = 0x0e13;
  t.nonuthai = 0x0e19;
  t.noonarabic = 0x0646;
  t.noonfinalarabic = 0xfee6;
  t.noonghunnaarabic = 0x06ba;
  t.noonghunnafinalarabic = 0xfb9f;
  t.nooninitialarabic = 0xfee7;
  t.noonjeeminitialarabic = 0xfcd2;
  t.noonjeemisolatedarabic = 0xfc4b;
  t.noonmedialarabic = 0xfee8;
  t.noonmeeminitialarabic = 0xfcd5;
  t.noonmeemisolatedarabic = 0xfc4e;
  t.noonnoonfinalarabic = 0xfc8d;
  t.notcontains = 0x220c;
  t.notelement = 0x2209;
  t.notelementof = 0x2209;
  t.notequal = 0x2260;
  t.notgreater = 0x226f;
  t.notgreaternorequal = 0x2271;
  t.notgreaternorless = 0x2279;
  t.notidentical = 0x2262;
  t.notless = 0x226e;
  t.notlessnorequal = 0x2270;
  t.notparallel = 0x2226;
  t.notprecedes = 0x2280;
  t.notsubset = 0x2284;
  t.notsucceeds = 0x2281;
  t.notsuperset = 0x2285;
  t.nowarmenian = 0x0576;
  t.nparen = 0x24a9;
  t.nssquare = 0x33b1;
  t.nsuperior = 0x207f;
  t.ntilde = 0x00f1;
  t.nu = 0x03bd;
  t.nuhiragana = 0x306c;
  t.nukatakana = 0x30cc;
  t.nukatakanahalfwidth = 0xff87;
  t.nuktabengali = 0x09bc;
  t.nuktadeva = 0x093c;
  t.nuktagujarati = 0x0abc;
  t.nuktagurmukhi = 0x0a3c;
  t.numbersign = 0x0023;
  t.numbersignmonospace = 0xff03;
  t.numbersignsmall = 0xfe5f;
  t.numeralsigngreek = 0x0374;
  t.numeralsignlowergreek = 0x0375;
  t.numero = 0x2116;
  t.nun = 0x05e0;
  t.nundagesh = 0xfb40;
  t.nundageshhebrew = 0xfb40;
  t.nunhebrew = 0x05e0;
  t.nvsquare = 0x33b5;
  t.nwsquare = 0x33bb;
  t.nyabengali = 0x099e;
  t.nyadeva = 0x091e;
  t.nyagujarati = 0x0a9e;
  t.nyagurmukhi = 0x0a1e;
  t.o = 0x006f;
  t.oacute = 0x00f3;
  t.oangthai = 0x0e2d;
  t.obarred = 0x0275;
  t.obarredcyrillic = 0x04e9;
  t.obarreddieresiscyrillic = 0x04eb;
  t.obengali = 0x0993;
  t.obopomofo = 0x311b;
  t.obreve = 0x014f;
  t.ocandradeva = 0x0911;
  t.ocandragujarati = 0x0a91;
  t.ocandravowelsigndeva = 0x0949;
  t.ocandravowelsigngujarati = 0x0ac9;
  t.ocaron = 0x01d2;
  t.ocircle = 0x24de;
  t.ocircumflex = 0x00f4;
  t.ocircumflexacute = 0x1ed1;
  t.ocircumflexdotbelow = 0x1ed9;
  t.ocircumflexgrave = 0x1ed3;
  t.ocircumflexhookabove = 0x1ed5;
  t.ocircumflextilde = 0x1ed7;
  t.ocyrillic = 0x043e;
  t.odblacute = 0x0151;
  t.odblgrave = 0x020d;
  t.odeva = 0x0913;
  t.odieresis = 0x00f6;
  t.odieresiscyrillic = 0x04e7;
  t.odotbelow = 0x1ecd;
  t.oe = 0x0153;
  t.oekorean = 0x315a;
  t.ogonek = 0x02db;
  t.ogonekcmb = 0x0328;
  t.ograve = 0x00f2;
  t.ogujarati = 0x0a93;
  t.oharmenian = 0x0585;
  t.ohiragana = 0x304a;
  t.ohookabove = 0x1ecf;
  t.ohorn = 0x01a1;
  t.ohornacute = 0x1edb;
  t.ohorndotbelow = 0x1ee3;
  t.ohorngrave = 0x1edd;
  t.ohornhookabove = 0x1edf;
  t.ohorntilde = 0x1ee1;
  t.ohungarumlaut = 0x0151;
  t.oi = 0x01a3;
  t.oinvertedbreve = 0x020f;
  t.okatakana = 0x30aa;
  t.okatakanahalfwidth = 0xff75;
  t.okorean = 0x3157;
  t.olehebrew = 0x05ab;
  t.omacron = 0x014d;
  t.omacronacute = 0x1e53;
  t.omacrongrave = 0x1e51;
  t.omdeva = 0x0950;
  t.omega = 0x03c9;
  t.omega1 = 0x03d6;
  t.omegacyrillic = 0x0461;
  t.omegalatinclosed = 0x0277;
  t.omegaroundcyrillic = 0x047b;
  t.omegatitlocyrillic = 0x047d;
  t.omegatonos = 0x03ce;
  t.omgujarati = 0x0ad0;
  t.omicron = 0x03bf;
  t.omicrontonos = 0x03cc;
  t.omonospace = 0xff4f;
  t.one = 0x0031;
  t.onearabic = 0x0661;
  t.onebengali = 0x09e7;
  t.onecircle = 0x2460;
  t.onecircleinversesansserif = 0x278a;
  t.onedeva = 0x0967;
  t.onedotenleader = 0x2024;
  t.oneeighth = 0x215b;
  t.onefitted = 0xf6dc;
  t.onegujarati = 0x0ae7;
  t.onegurmukhi = 0x0a67;
  t.onehackarabic = 0x0661;
  t.onehalf = 0x00bd;
  t.onehangzhou = 0x3021;
  t.oneideographicparen = 0x3220;
  t.oneinferior = 0x2081;
  t.onemonospace = 0xff11;
  t.onenumeratorbengali = 0x09f4;
  t.oneoldstyle = 0xf731;
  t.oneparen = 0x2474;
  t.oneperiod = 0x2488;
  t.onepersian = 0x06f1;
  t.onequarter = 0x00bc;
  t.oneroman = 0x2170;
  t.onesuperior = 0x00b9;
  t.onethai = 0x0e51;
  t.onethird = 0x2153;
  t.oogonek = 0x01eb;
  t.oogonekmacron = 0x01ed;
  t.oogurmukhi = 0x0a13;
  t.oomatragurmukhi = 0x0a4b;
  t.oopen = 0x0254;
  t.oparen = 0x24aa;
  t.openbullet = 0x25e6;
  t.option = 0x2325;
  t.ordfeminine = 0x00aa;
  t.ordmasculine = 0x00ba;
  t.orthogonal = 0x221f;
  t.oshortdeva = 0x0912;
  t.oshortvowelsigndeva = 0x094a;
  t.oslash = 0x00f8;
  t.oslashacute = 0x01ff;
  t.osmallhiragana = 0x3049;
  t.osmallkatakana = 0x30a9;
  t.osmallkatakanahalfwidth = 0xff6b;
  t.ostrokeacute = 0x01ff;
  t.osuperior = 0xf6f0;
  t.otcyrillic = 0x047f;
  t.otilde = 0x00f5;
  t.otildeacute = 0x1e4d;
  t.otildedieresis = 0x1e4f;
  t.oubopomofo = 0x3121;
  t.overline = 0x203e;
  t.overlinecenterline = 0xfe4a;
  t.overlinecmb = 0x0305;
  t.overlinedashed = 0xfe49;
  t.overlinedblwavy = 0xfe4c;
  t.overlinewavy = 0xfe4b;
  t.overscore = 0x00af;
  t.ovowelsignbengali = 0x09cb;
  t.ovowelsigndeva = 0x094b;
  t.ovowelsigngujarati = 0x0acb;
  t.p = 0x0070;
  t.paampssquare = 0x3380;
  t.paasentosquare = 0x332b;
  t.pabengali = 0x09aa;
  t.pacute = 0x1e55;
  t.padeva = 0x092a;
  t.pagedown = 0x21df;
  t.pageup = 0x21de;
  t.pagujarati = 0x0aaa;
  t.pagurmukhi = 0x0a2a;
  t.pahiragana = 0x3071;
  t.paiyannoithai = 0x0e2f;
  t.pakatakana = 0x30d1;
  t.palatalizationcyrilliccmb = 0x0484;
  t.palochkacyrillic = 0x04c0;
  t.pansioskorean = 0x317f;
  t.paragraph = 0x00b6;
  t.parallel = 0x2225;
  t.parenleft = 0x0028;
  t.parenleftaltonearabic = 0xfd3e;
  t.parenleftbt = 0xf8ed;
  t.parenleftex = 0xf8ec;
  t.parenleftinferior = 0x208d;
  t.parenleftmonospace = 0xff08;
  t.parenleftsmall = 0xfe59;
  t.parenleftsuperior = 0x207d;
  t.parenlefttp = 0xf8eb;
  t.parenleftvertical = 0xfe35;
  t.parenright = 0x0029;
  t.parenrightaltonearabic = 0xfd3f;
  t.parenrightbt = 0xf8f8;
  t.parenrightex = 0xf8f7;
  t.parenrightinferior = 0x208e;
  t.parenrightmonospace = 0xff09;
  t.parenrightsmall = 0xfe5a;
  t.parenrightsuperior = 0x207e;
  t.parenrighttp = 0xf8f6;
  t.parenrightvertical = 0xfe36;
  t.partialdiff = 0x2202;
  t.paseqhebrew = 0x05c0;
  t.pashtahebrew = 0x0599;
  t.pasquare = 0x33a9;
  t.patah = 0x05b7;
  t.patah11 = 0x05b7;
  t.patah1d = 0x05b7;
  t.patah2a = 0x05b7;
  t.patahhebrew = 0x05b7;
  t.patahnarrowhebrew = 0x05b7;
  t.patahquarterhebrew = 0x05b7;
  t.patahwidehebrew = 0x05b7;
  t.pazerhebrew = 0x05a1;
  t.pbopomofo = 0x3106;
  t.pcircle = 0x24df;
  t.pdotaccent = 0x1e57;
  t.pe = 0x05e4;
  t.pecyrillic = 0x043f;
  t.pedagesh = 0xfb44;
  t.pedageshhebrew = 0xfb44;
  t.peezisquare = 0x333b;
  t.pefinaldageshhebrew = 0xfb43;
  t.peharabic = 0x067e;
  t.peharmenian = 0x057a;
  t.pehebrew = 0x05e4;
  t.pehfinalarabic = 0xfb57;
  t.pehinitialarabic = 0xfb58;
  t.pehiragana = 0x307a;
  t.pehmedialarabic = 0xfb59;
  t.pekatakana = 0x30da;
  t.pemiddlehookcyrillic = 0x04a7;
  t.perafehebrew = 0xfb4e;
  t.percent = 0x0025;
  t.percentarabic = 0x066a;
  t.percentmonospace = 0xff05;
  t.percentsmall = 0xfe6a;
  t.period = 0x002e;
  t.periodarmenian = 0x0589;
  t.periodcentered = 0x00b7;
  t.periodhalfwidth = 0xff61;
  t.periodinferior = 0xf6e7;
  t.periodmonospace = 0xff0e;
  t.periodsmall = 0xfe52;
  t.periodsuperior = 0xf6e8;
  t.perispomenigreekcmb = 0x0342;
  t.perpendicular = 0x22a5;
  t.perthousand = 0x2030;
  t.peseta = 0x20a7;
  t.pfsquare = 0x338a;
  t.phabengali = 0x09ab;
  t.phadeva = 0x092b;
  t.phagujarati = 0x0aab;
  t.phagurmukhi = 0x0a2b;
  t.phi = 0x03c6;
  t.phi1 = 0x03d5;
  t.phieuphacirclekorean = 0x327a;
  t.phieuphaparenkorean = 0x321a;
  t.phieuphcirclekorean = 0x326c;
  t.phieuphkorean = 0x314d;
  t.phieuphparenkorean = 0x320c;
  t.philatin = 0x0278;
  t.phinthuthai = 0x0e3a;
  t.phisymbolgreek = 0x03d5;
  t.phook = 0x01a5;
  t.phophanthai = 0x0e1e;
  t.phophungthai = 0x0e1c;
  t.phosamphaothai = 0x0e20;
  t.pi = 0x03c0;
  t.pieupacirclekorean = 0x3273;
  t.pieupaparenkorean = 0x3213;
  t.pieupcieuckorean = 0x3176;
  t.pieupcirclekorean = 0x3265;
  t.pieupkiyeokkorean = 0x3172;
  t.pieupkorean = 0x3142;
  t.pieupparenkorean = 0x3205;
  t.pieupsioskiyeokkorean = 0x3174;
  t.pieupsioskorean = 0x3144;
  t.pieupsiostikeutkorean = 0x3175;
  t.pieupthieuthkorean = 0x3177;
  t.pieuptikeutkorean = 0x3173;
  t.pihiragana = 0x3074;
  t.pikatakana = 0x30d4;
  t.pisymbolgreek = 0x03d6;
  t.piwrarmenian = 0x0583;
  t.planckover2pi = 0x210f;
  t.planckover2pi1 = 0x210f;
  t.plus = 0x002b;
  t.plusbelowcmb = 0x031f;
  t.pluscircle = 0x2295;
  t.plusminus = 0x00b1;
  t.plusmod = 0x02d6;
  t.plusmonospace = 0xff0b;
  t.plussmall = 0xfe62;
  t.plussuperior = 0x207a;
  t.pmonospace = 0xff50;
  t.pmsquare = 0x33d8;
  t.pohiragana = 0x307d;
  t.pointingindexdownwhite = 0x261f;
  t.pointingindexleftwhite = 0x261c;
  t.pointingindexrightwhite = 0x261e;
  t.pointingindexupwhite = 0x261d;
  t.pokatakana = 0x30dd;
  t.poplathai = 0x0e1b;
  t.postalmark = 0x3012;
  t.postalmarkface = 0x3020;
  t.pparen = 0x24ab;
  t.precedes = 0x227a;
  t.prescription = 0x211e;
  t.primemod = 0x02b9;
  t.primereversed = 0x2035;
  t.product = 0x220f;
  t.projective = 0x2305;
  t.prolongedkana = 0x30fc;
  t.propellor = 0x2318;
  t.propersubset = 0x2282;
  t.propersuperset = 0x2283;
  t.proportion = 0x2237;
  t.proportional = 0x221d;
  t.psi = 0x03c8;
  t.psicyrillic = 0x0471;
  t.psilipneumatacyrilliccmb = 0x0486;
  t.pssquare = 0x33b0;
  t.puhiragana = 0x3077;
  t.pukatakana = 0x30d7;
  t.pvsquare = 0x33b4;
  t.pwsquare = 0x33ba;
  t.q = 0x0071;
  t.qadeva = 0x0958;
  t.qadmahebrew = 0x05a8;
  t.qafarabic = 0x0642;
  t.qaffinalarabic = 0xfed6;
  t.qafinitialarabic = 0xfed7;
  t.qafmedialarabic = 0xfed8;
  t.qamats = 0x05b8;
  t.qamats10 = 0x05b8;
  t.qamats1a = 0x05b8;
  t.qamats1c = 0x05b8;
  t.qamats27 = 0x05b8;
  t.qamats29 = 0x05b8;
  t.qamats33 = 0x05b8;
  t.qamatsde = 0x05b8;
  t.qamatshebrew = 0x05b8;
  t.qamatsnarrowhebrew = 0x05b8;
  t.qamatsqatanhebrew = 0x05b8;
  t.qamatsqatannarrowhebrew = 0x05b8;
  t.qamatsqatanquarterhebrew = 0x05b8;
  t.qamatsqatanwidehebrew = 0x05b8;
  t.qamatsquarterhebrew = 0x05b8;
  t.qamatswidehebrew = 0x05b8;
  t.qarneyparahebrew = 0x059f;
  t.qbopomofo = 0x3111;
  t.qcircle = 0x24e0;
  t.qhook = 0x02a0;
  t.qmonospace = 0xff51;
  t.qof = 0x05e7;
  t.qofdagesh = 0xfb47;
  t.qofdageshhebrew = 0xfb47;
  t.qofhebrew = 0x05e7;
  t.qparen = 0x24ac;
  t.quarternote = 0x2669;
  t.qubuts = 0x05bb;
  t.qubuts18 = 0x05bb;
  t.qubuts25 = 0x05bb;
  t.qubuts31 = 0x05bb;
  t.qubutshebrew = 0x05bb;
  t.qubutsnarrowhebrew = 0x05bb;
  t.qubutsquarterhebrew = 0x05bb;
  t.qubutswidehebrew = 0x05bb;
  t.question = 0x003f;
  t.questionarabic = 0x061f;
  t.questionarmenian = 0x055e;
  t.questiondown = 0x00bf;
  t.questiondownsmall = 0xf7bf;
  t.questiongreek = 0x037e;
  t.questionmonospace = 0xff1f;
  t.questionsmall = 0xf73f;
  t.quotedbl = 0x0022;
  t.quotedblbase = 0x201e;
  t.quotedblleft = 0x201c;
  t.quotedblmonospace = 0xff02;
  t.quotedblprime = 0x301e;
  t.quotedblprimereversed = 0x301d;
  t.quotedblright = 0x201d;
  t.quoteleft = 0x2018;
  t.quoteleftreversed = 0x201b;
  t.quotereversed = 0x201b;
  t.quoteright = 0x2019;
  t.quoterightn = 0x0149;
  t.quotesinglbase = 0x201a;
  t.quotesingle = 0x0027;
  t.quotesinglemonospace = 0xff07;
  t.r = 0x0072;
  t.raarmenian = 0x057c;
  t.rabengali = 0x09b0;
  t.racute = 0x0155;
  t.radeva = 0x0930;
  t.radical = 0x221a;
  t.radicalex = 0xf8e5;
  t.radoverssquare = 0x33ae;
  t.radoverssquaredsquare = 0x33af;
  t.radsquare = 0x33ad;
  t.rafe = 0x05bf;
  t.rafehebrew = 0x05bf;
  t.ragujarati = 0x0ab0;
  t.ragurmukhi = 0x0a30;
  t.rahiragana = 0x3089;
  t.rakatakana = 0x30e9;
  t.rakatakanahalfwidth = 0xff97;
  t.ralowerdiagonalbengali = 0x09f1;
  t.ramiddlediagonalbengali = 0x09f0;
  t.ramshorn = 0x0264;
  t.ratio = 0x2236;
  t.rbopomofo = 0x3116;
  t.rcaron = 0x0159;
  t.rcedilla = 0x0157;
  t.rcircle = 0x24e1;
  t.rcommaaccent = 0x0157;
  t.rdblgrave = 0x0211;
  t.rdotaccent = 0x1e59;
  t.rdotbelow = 0x1e5b;
  t.rdotbelowmacron = 0x1e5d;
  t.referencemark = 0x203b;
  t.reflexsubset = 0x2286;
  t.reflexsuperset = 0x2287;
  t.registered = 0x00ae;
  t.registersans = 0xf8e8;
  t.registerserif = 0xf6da;
  t.reharabic = 0x0631;
  t.reharmenian = 0x0580;
  t.rehfinalarabic = 0xfeae;
  t.rehiragana = 0x308c;
  t.rekatakana = 0x30ec;
  t.rekatakanahalfwidth = 0xff9a;
  t.resh = 0x05e8;
  t.reshdageshhebrew = 0xfb48;
  t.reshhebrew = 0x05e8;
  t.reversedtilde = 0x223d;
  t.reviahebrew = 0x0597;
  t.reviamugrashhebrew = 0x0597;
  t.revlogicalnot = 0x2310;
  t.rfishhook = 0x027e;
  t.rfishhookreversed = 0x027f;
  t.rhabengali = 0x09dd;
  t.rhadeva = 0x095d;
  t.rho = 0x03c1;
  t.rhook = 0x027d;
  t.rhookturned = 0x027b;
  t.rhookturnedsuperior = 0x02b5;
  t.rhosymbolgreek = 0x03f1;
  t.rhotichookmod = 0x02de;
  t.rieulacirclekorean = 0x3271;
  t.rieulaparenkorean = 0x3211;
  t.rieulcirclekorean = 0x3263;
  t.rieulhieuhkorean = 0x3140;
  t.rieulkiyeokkorean = 0x313a;
  t.rieulkiyeoksioskorean = 0x3169;
  t.rieulkorean = 0x3139;
  t.rieulmieumkorean = 0x313b;
  t.rieulpansioskorean = 0x316c;
  t.rieulparenkorean = 0x3203;
  t.rieulphieuphkorean = 0x313f;
  t.rieulpieupkorean = 0x313c;
  t.rieulpieupsioskorean = 0x316b;
  t.rieulsioskorean = 0x313d;
  t.rieulthieuthkorean = 0x313e;
  t.rieultikeutkorean = 0x316a;
  t.rieulyeorinhieuhkorean = 0x316d;
  t.rightangle = 0x221f;
  t.righttackbelowcmb = 0x0319;
  t.righttriangle = 0x22bf;
  t.rihiragana = 0x308a;
  t.rikatakana = 0x30ea;
  t.rikatakanahalfwidth = 0xff98;
  t.ring = 0x02da;
  t.ringbelowcmb = 0x0325;
  t.ringcmb = 0x030a;
  t.ringhalfleft = 0x02bf;
  t.ringhalfleftarmenian = 0x0559;
  t.ringhalfleftbelowcmb = 0x031c;
  t.ringhalfleftcentered = 0x02d3;
  t.ringhalfright = 0x02be;
  t.ringhalfrightbelowcmb = 0x0339;
  t.ringhalfrightcentered = 0x02d2;
  t.rinvertedbreve = 0x0213;
  t.rittorusquare = 0x3351;
  t.rlinebelow = 0x1e5f;
  t.rlongleg = 0x027c;
  t.rlonglegturned = 0x027a;
  t.rmonospace = 0xff52;
  t.rohiragana = 0x308d;
  t.rokatakana = 0x30ed;
  t.rokatakanahalfwidth = 0xff9b;
  t.roruathai = 0x0e23;
  t.rparen = 0x24ad;
  t.rrabengali = 0x09dc;
  t.rradeva = 0x0931;
  t.rragurmukhi = 0x0a5c;
  t.rreharabic = 0x0691;
  t.rrehfinalarabic = 0xfb8d;
  t.rrvocalicbengali = 0x09e0;
  t.rrvocalicdeva = 0x0960;
  t.rrvocalicgujarati = 0x0ae0;
  t.rrvocalicvowelsignbengali = 0x09c4;
  t.rrvocalicvowelsigndeva = 0x0944;
  t.rrvocalicvowelsigngujarati = 0x0ac4;
  t.rsuperior = 0xf6f1;
  t.rtblock = 0x2590;
  t.rturned = 0x0279;
  t.rturnedsuperior = 0x02b4;
  t.ruhiragana = 0x308b;
  t.rukatakana = 0x30eb;
  t.rukatakanahalfwidth = 0xff99;
  t.rupeemarkbengali = 0x09f2;
  t.rupeesignbengali = 0x09f3;
  t.rupiah = 0xf6dd;
  t.ruthai = 0x0e24;
  t.rvocalicbengali = 0x098b;
  t.rvocalicdeva = 0x090b;
  t.rvocalicgujarati = 0x0a8b;
  t.rvocalicvowelsignbengali = 0x09c3;
  t.rvocalicvowelsigndeva = 0x0943;
  t.rvocalicvowelsigngujarati = 0x0ac3;
  t.s = 0x0073;
  t.sabengali = 0x09b8;
  t.sacute = 0x015b;
  t.sacutedotaccent = 0x1e65;
  t.sadarabic = 0x0635;
  t.sadeva = 0x0938;
  t.sadfinalarabic = 0xfeba;
  t.sadinitialarabic = 0xfebb;
  t.sadmedialarabic = 0xfebc;
  t.sagujarati = 0x0ab8;
  t.sagurmukhi = 0x0a38;
  t.sahiragana = 0x3055;
  t.sakatakana = 0x30b5;
  t.sakatakanahalfwidth = 0xff7b;
  t.sallallahoualayhewasallamarabic = 0xfdfa;
  t.samekh = 0x05e1;
  t.samekhdagesh = 0xfb41;
  t.samekhdageshhebrew = 0xfb41;
  t.samekhhebrew = 0x05e1;
  t.saraaathai = 0x0e32;
  t.saraaethai = 0x0e41;
  t.saraaimaimalaithai = 0x0e44;
  t.saraaimaimuanthai = 0x0e43;
  t.saraamthai = 0x0e33;
  t.saraathai = 0x0e30;
  t.saraethai = 0x0e40;
  t.saraiileftthai = 0xf886;
  t.saraiithai = 0x0e35;
  t.saraileftthai = 0xf885;
  t.saraithai = 0x0e34;
  t.saraothai = 0x0e42;
  t.saraueeleftthai = 0xf888;
  t.saraueethai = 0x0e37;
  t.saraueleftthai = 0xf887;
  t.sarauethai = 0x0e36;
  t.sarauthai = 0x0e38;
  t.sarauuthai = 0x0e39;
  t.sbopomofo = 0x3119;
  t.scaron = 0x0161;
  t.scarondotaccent = 0x1e67;
  t.scedilla = 0x015f;
  t.schwa = 0x0259;
  t.schwacyrillic = 0x04d9;
  t.schwadieresiscyrillic = 0x04db;
  t.schwahook = 0x025a;
  t.scircle = 0x24e2;
  t.scircumflex = 0x015d;
  t.scommaaccent = 0x0219;
  t.sdotaccent = 0x1e61;
  t.sdotbelow = 0x1e63;
  t.sdotbelowdotaccent = 0x1e69;
  t.seagullbelowcmb = 0x033c;
  t.second = 0x2033;
  t.secondtonechinese = 0x02ca;
  t.section = 0x00a7;
  t.seenarabic = 0x0633;
  t.seenfinalarabic = 0xfeb2;
  t.seeninitialarabic = 0xfeb3;
  t.seenmedialarabic = 0xfeb4;
  t.segol = 0x05b6;
  t.segol13 = 0x05b6;
  t.segol1f = 0x05b6;
  t.segol2c = 0x05b6;
  t.segolhebrew = 0x05b6;
  t.segolnarrowhebrew = 0x05b6;
  t.segolquarterhebrew = 0x05b6;
  t.segoltahebrew = 0x0592;
  t.segolwidehebrew = 0x05b6;
  t.seharmenian = 0x057d;
  t.sehiragana = 0x305b;
  t.sekatakana = 0x30bb;
  t.sekatakanahalfwidth = 0xff7e;
  t.semicolon = 0x003b;
  t.semicolonarabic = 0x061b;
  t.semicolonmonospace = 0xff1b;
  t.semicolonsmall = 0xfe54;
  t.semivoicedmarkkana = 0x309c;
  t.semivoicedmarkkanahalfwidth = 0xff9f;
  t.sentisquare = 0x3322;
  t.sentosquare = 0x3323;
  t.seven = 0x0037;
  t.sevenarabic = 0x0667;
  t.sevenbengali = 0x09ed;
  t.sevencircle = 0x2466;
  t.sevencircleinversesansserif = 0x2790;
  t.sevendeva = 0x096d;
  t.seveneighths = 0x215e;
  t.sevengujarati = 0x0aed;
  t.sevengurmukhi = 0x0a6d;
  t.sevenhackarabic = 0x0667;
  t.sevenhangzhou = 0x3027;
  t.sevenideographicparen = 0x3226;
  t.seveninferior = 0x2087;
  t.sevenmonospace = 0xff17;
  t.sevenoldstyle = 0xf737;
  t.sevenparen = 0x247a;
  t.sevenperiod = 0x248e;
  t.sevenpersian = 0x06f7;
  t.sevenroman = 0x2176;
  t.sevensuperior = 0x2077;
  t.seventeencircle = 0x2470;
  t.seventeenparen = 0x2484;
  t.seventeenperiod = 0x2498;
  t.seventhai = 0x0e57;
  t.sfthyphen = 0x00ad;
  t.shaarmenian = 0x0577;
  t.shabengali = 0x09b6;
  t.shacyrillic = 0x0448;
  t.shaddaarabic = 0x0651;
  t.shaddadammaarabic = 0xfc61;
  t.shaddadammatanarabic = 0xfc5e;
  t.shaddafathaarabic = 0xfc60;
  t.shaddakasraarabic = 0xfc62;
  t.shaddakasratanarabic = 0xfc5f;
  t.shade = 0x2592;
  t.shadedark = 0x2593;
  t.shadelight = 0x2591;
  t.shademedium = 0x2592;
  t.shadeva = 0x0936;
  t.shagujarati = 0x0ab6;
  t.shagurmukhi = 0x0a36;
  t.shalshelethebrew = 0x0593;
  t.shbopomofo = 0x3115;
  t.shchacyrillic = 0x0449;
  t.sheenarabic = 0x0634;
  t.sheenfinalarabic = 0xfeb6;
  t.sheeninitialarabic = 0xfeb7;
  t.sheenmedialarabic = 0xfeb8;
  t.sheicoptic = 0x03e3;
  t.sheqel = 0x20aa;
  t.sheqelhebrew = 0x20aa;
  t.sheva = 0x05b0;
  t.sheva115 = 0x05b0;
  t.sheva15 = 0x05b0;
  t.sheva22 = 0x05b0;
  t.sheva2e = 0x05b0;
  t.shevahebrew = 0x05b0;
  t.shevanarrowhebrew = 0x05b0;
  t.shevaquarterhebrew = 0x05b0;
  t.shevawidehebrew = 0x05b0;
  t.shhacyrillic = 0x04bb;
  t.shimacoptic = 0x03ed;
  t.shin = 0x05e9;
  t.shindagesh = 0xfb49;
  t.shindageshhebrew = 0xfb49;
  t.shindageshshindot = 0xfb2c;
  t.shindageshshindothebrew = 0xfb2c;
  t.shindageshsindot = 0xfb2d;
  t.shindageshsindothebrew = 0xfb2d;
  t.shindothebrew = 0x05c1;
  t.shinhebrew = 0x05e9;
  t.shinshindot = 0xfb2a;
  t.shinshindothebrew = 0xfb2a;
  t.shinsindot = 0xfb2b;
  t.shinsindothebrew = 0xfb2b;
  t.shook = 0x0282;
  t.sigma = 0x03c3;
  t.sigma1 = 0x03c2;
  t.sigmafinal = 0x03c2;
  t.sigmalunatesymbolgreek = 0x03f2;
  t.sihiragana = 0x3057;
  t.sikatakana = 0x30b7;
  t.sikatakanahalfwidth = 0xff7c;
  t.siluqhebrew = 0x05bd;
  t.siluqlefthebrew = 0x05bd;
  t.similar = 0x223c;
  t.sindothebrew = 0x05c2;
  t.siosacirclekorean = 0x3274;
  t.siosaparenkorean = 0x3214;
  t.sioscieuckorean = 0x317e;
  t.sioscirclekorean = 0x3266;
  t.sioskiyeokkorean = 0x317a;
  t.sioskorean = 0x3145;
  t.siosnieunkorean = 0x317b;
  t.siosparenkorean = 0x3206;
  t.siospieupkorean = 0x317d;
  t.siostikeutkorean = 0x317c;
  t.six = 0x0036;
  t.sixarabic = 0x0666;
  t.sixbengali = 0x09ec;
  t.sixcircle = 0x2465;
  t.sixcircleinversesansserif = 0x278f;
  t.sixdeva = 0x096c;
  t.sixgujarati = 0x0aec;
  t.sixgurmukhi = 0x0a6c;
  t.sixhackarabic = 0x0666;
  t.sixhangzhou = 0x3026;
  t.sixideographicparen = 0x3225;
  t.sixinferior = 0x2086;
  t.sixmonospace = 0xff16;
  t.sixoldstyle = 0xf736;
  t.sixparen = 0x2479;
  t.sixperiod = 0x248d;
  t.sixpersian = 0x06f6;
  t.sixroman = 0x2175;
  t.sixsuperior = 0x2076;
  t.sixteencircle = 0x246f;
  t.sixteencurrencydenominatorbengali = 0x09f9;
  t.sixteenparen = 0x2483;
  t.sixteenperiod = 0x2497;
  t.sixthai = 0x0e56;
  t.slash = 0x002f;
  t.slashmonospace = 0xff0f;
  t.slong = 0x017f;
  t.slongdotaccent = 0x1e9b;
  t.smileface = 0x263a;
  t.smonospace = 0xff53;
  t.sofpasuqhebrew = 0x05c3;
  t.softhyphen = 0x00ad;
  t.softsigncyrillic = 0x044c;
  t.sohiragana = 0x305d;
  t.sokatakana = 0x30bd;
  t.sokatakanahalfwidth = 0xff7f;
  t.soliduslongoverlaycmb = 0x0338;
  t.solidusshortoverlaycmb = 0x0337;
  t.sorusithai = 0x0e29;
  t.sosalathai = 0x0e28;
  t.sosothai = 0x0e0b;
  t.sosuathai = 0x0e2a;
  t.space = 0x0020;
  t.spacehackarabic = 0x0020;
  t.spade = 0x2660;
  t.spadesuitblack = 0x2660;
  t.spadesuitwhite = 0x2664;
  t.sparen = 0x24ae;
  t.squarebelowcmb = 0x033b;
  t.squarecc = 0x33c4;
  t.squarecm = 0x339d;
  t.squarediagonalcrosshatchfill = 0x25a9;
  t.squarehorizontalfill = 0x25a4;
  t.squarekg = 0x338f;
  t.squarekm = 0x339e;
  t.squarekmcapital = 0x33ce;
  t.squareln = 0x33d1;
  t.squarelog = 0x33d2;
  t.squaremg = 0x338e;
  t.squaremil = 0x33d5;
  t.squaremm = 0x339c;
  t.squaremsquared = 0x33a1;
  t.squareorthogonalcrosshatchfill = 0x25a6;
  t.squareupperlefttolowerrightfill = 0x25a7;
  t.squareupperrighttolowerleftfill = 0x25a8;
  t.squareverticalfill = 0x25a5;
  t.squarewhitewithsmallblack = 0x25a3;
  t.srsquare = 0x33db;
  t.ssabengali = 0x09b7;
  t.ssadeva = 0x0937;
  t.ssagujarati = 0x0ab7;
  t.ssangcieuckorean = 0x3149;
  t.ssanghieuhkorean = 0x3185;
  t.ssangieungkorean = 0x3180;
  t.ssangkiyeokkorean = 0x3132;
  t.ssangnieunkorean = 0x3165;
  t.ssangpieupkorean = 0x3143;
  t.ssangsioskorean = 0x3146;
  t.ssangtikeutkorean = 0x3138;
  t.ssuperior = 0xf6f2;
  t.sterling = 0x00a3;
  t.sterlingmonospace = 0xffe1;
  t.strokelongoverlaycmb = 0x0336;
  t.strokeshortoverlaycmb = 0x0335;
  t.subset = 0x2282;
  t.subsetnotequal = 0x228a;
  t.subsetorequal = 0x2286;
  t.succeeds = 0x227b;
  t.suchthat = 0x220b;
  t.suhiragana = 0x3059;
  t.sukatakana = 0x30b9;
  t.sukatakanahalfwidth = 0xff7d;
  t.sukunarabic = 0x0652;
  t.summation = 0x2211;
  t.sun = 0x263c;
  t.superset = 0x2283;
  t.supersetnotequal = 0x228b;
  t.supersetorequal = 0x2287;
  t.svsquare = 0x33dc;
  t.syouwaerasquare = 0x337c;
  t.t = 0x0074;
  t.tabengali = 0x09a4;
  t.tackdown = 0x22a4;
  t.tackleft = 0x22a3;
  t.tadeva = 0x0924;
  t.tagujarati = 0x0aa4;
  t.tagurmukhi = 0x0a24;
  t.taharabic = 0x0637;
  t.tahfinalarabic = 0xfec2;
  t.tahinitialarabic = 0xfec3;
  t.tahiragana = 0x305f;
  t.tahmedialarabic = 0xfec4;
  t.taisyouerasquare = 0x337d;
  t.takatakana = 0x30bf;
  t.takatakanahalfwidth = 0xff80;
  t.tatweelarabic = 0x0640;
  t.tau = 0x03c4;
  t.tav = 0x05ea;
  t.tavdages = 0xfb4a;
  t.tavdagesh = 0xfb4a;
  t.tavdageshhebrew = 0xfb4a;
  t.tavhebrew = 0x05ea;
  t.tbar = 0x0167;
  t.tbopomofo = 0x310a;
  t.tcaron = 0x0165;
  t.tccurl = 0x02a8;
  t.tcedilla = 0x0163;
  t.tcheharabic = 0x0686;
  t.tchehfinalarabic = 0xfb7b;
  t.tchehinitialarabic = 0xfb7c;
  t.tchehmedialarabic = 0xfb7d;
  t.tcircle = 0x24e3;
  t.tcircumflexbelow = 0x1e71;
  t.tcommaaccent = 0x0163;
  t.tdieresis = 0x1e97;
  t.tdotaccent = 0x1e6b;
  t.tdotbelow = 0x1e6d;
  t.tecyrillic = 0x0442;
  t.tedescendercyrillic = 0x04ad;
  t.teharabic = 0x062a;
  t.tehfinalarabic = 0xfe96;
  t.tehhahinitialarabic = 0xfca2;
  t.tehhahisolatedarabic = 0xfc0c;
  t.tehinitialarabic = 0xfe97;
  t.tehiragana = 0x3066;
  t.tehjeeminitialarabic = 0xfca1;
  t.tehjeemisolatedarabic = 0xfc0b;
  t.tehmarbutaarabic = 0x0629;
  t.tehmarbutafinalarabic = 0xfe94;
  t.tehmedialarabic = 0xfe98;
  t.tehmeeminitialarabic = 0xfca4;
  t.tehmeemisolatedarabic = 0xfc0e;
  t.tehnoonfinalarabic = 0xfc73;
  t.tekatakana = 0x30c6;
  t.tekatakanahalfwidth = 0xff83;
  t.telephone = 0x2121;
  t.telephoneblack = 0x260e;
  t.telishagedolahebrew = 0x05a0;
  t.telishaqetanahebrew = 0x05a9;
  t.tencircle = 0x2469;
  t.tenideographicparen = 0x3229;
  t.tenparen = 0x247d;
  t.tenperiod = 0x2491;
  t.tenroman = 0x2179;
  t.tesh = 0x02a7;
  t.tet = 0x05d8;
  t.tetdagesh = 0xfb38;
  t.tetdageshhebrew = 0xfb38;
  t.tethebrew = 0x05d8;
  t.tetsecyrillic = 0x04b5;
  t.tevirhebrew = 0x059b;
  t.tevirlefthebrew = 0x059b;
  t.thabengali = 0x09a5;
  t.thadeva = 0x0925;
  t.thagujarati = 0x0aa5;
  t.thagurmukhi = 0x0a25;
  t.thalarabic = 0x0630;
  t.thalfinalarabic = 0xfeac;
  t.thanthakhatlowleftthai = 0xf898;
  t.thanthakhatlowrightthai = 0xf897;
  t.thanthakhatthai = 0x0e4c;
  t.thanthakhatupperleftthai = 0xf896;
  t.theharabic = 0x062b;
  t.thehfinalarabic = 0xfe9a;
  t.thehinitialarabic = 0xfe9b;
  t.thehmedialarabic = 0xfe9c;
  t.thereexists = 0x2203;
  t.therefore = 0x2234;
  t.theta = 0x03b8;
  t.theta1 = 0x03d1;
  t.thetasymbolgreek = 0x03d1;
  t.thieuthacirclekorean = 0x3279;
  t.thieuthaparenkorean = 0x3219;
  t.thieuthcirclekorean = 0x326b;
  t.thieuthkorean = 0x314c;
  t.thieuthparenkorean = 0x320b;
  t.thirteencircle = 0x246c;
  t.thirteenparen = 0x2480;
  t.thirteenperiod = 0x2494;
  t.thonangmonthothai = 0x0e11;
  t.thook = 0x01ad;
  t.thophuthaothai = 0x0e12;
  t.thorn = 0x00fe;
  t.thothahanthai = 0x0e17;
  t.thothanthai = 0x0e10;
  t.thothongthai = 0x0e18;
  t.thothungthai = 0x0e16;
  t.thousandcyrillic = 0x0482;
  t.thousandsseparatorarabic = 0x066c;
  t.thousandsseparatorpersian = 0x066c;
  t.three = 0x0033;
  t.threearabic = 0x0663;
  t.threebengali = 0x09e9;
  t.threecircle = 0x2462;
  t.threecircleinversesansserif = 0x278c;
  t.threedeva = 0x0969;
  t.threeeighths = 0x215c;
  t.threegujarati = 0x0ae9;
  t.threegurmukhi = 0x0a69;
  t.threehackarabic = 0x0663;
  t.threehangzhou = 0x3023;
  t.threeideographicparen = 0x3222;
  t.threeinferior = 0x2083;
  t.threemonospace = 0xff13;
  t.threenumeratorbengali = 0x09f6;
  t.threeoldstyle = 0xf733;
  t.threeparen = 0x2476;
  t.threeperiod = 0x248a;
  t.threepersian = 0x06f3;
  t.threequarters = 0x00be;
  t.threequartersemdash = 0xf6de;
  t.threeroman = 0x2172;
  t.threesuperior = 0x00b3;
  t.threethai = 0x0e53;
  t.thzsquare = 0x3394;
  t.tihiragana = 0x3061;
  t.tikatakana = 0x30c1;
  t.tikatakanahalfwidth = 0xff81;
  t.tikeutacirclekorean = 0x3270;
  t.tikeutaparenkorean = 0x3210;
  t.tikeutcirclekorean = 0x3262;
  t.tikeutkorean = 0x3137;
  t.tikeutparenkorean = 0x3202;
  t.tilde = 0x02dc;
  t.tildebelowcmb = 0x0330;
  t.tildecmb = 0x0303;
  t.tildecomb = 0x0303;
  t.tildedoublecmb = 0x0360;
  t.tildeoperator = 0x223c;
  t.tildeoverlaycmb = 0x0334;
  t.tildeverticalcmb = 0x033e;
  t.timescircle = 0x2297;
  t.tipehahebrew = 0x0596;
  t.tipehalefthebrew = 0x0596;
  t.tippigurmukhi = 0x0a70;
  t.titlocyrilliccmb = 0x0483;
  t.tiwnarmenian = 0x057f;
  t.tlinebelow = 0x1e6f;
  t.tmonospace = 0xff54;
  t.toarmenian = 0x0569;
  t.tohiragana = 0x3068;
  t.tokatakana = 0x30c8;
  t.tokatakanahalfwidth = 0xff84;
  t.tonebarextrahighmod = 0x02e5;
  t.tonebarextralowmod = 0x02e9;
  t.tonebarhighmod = 0x02e6;
  t.tonebarlowmod = 0x02e8;
  t.tonebarmidmod = 0x02e7;
  t.tonefive = 0x01bd;
  t.tonesix = 0x0185;
  t.tonetwo = 0x01a8;
  t.tonos = 0x0384;
  t.tonsquare = 0x3327;
  t.topatakthai = 0x0e0f;
  t.tortoiseshellbracketleft = 0x3014;
  t.tortoiseshellbracketleftsmall = 0xfe5d;
  t.tortoiseshellbracketleftvertical = 0xfe39;
  t.tortoiseshellbracketright = 0x3015;
  t.tortoiseshellbracketrightsmall = 0xfe5e;
  t.tortoiseshellbracketrightvertical = 0xfe3a;
  t.totaothai = 0x0e15;
  t.tpalatalhook = 0x01ab;
  t.tparen = 0x24af;
  t.trademark = 0x2122;
  t.trademarksans = 0xf8ea;
  t.trademarkserif = 0xf6db;
  t.tretroflexhook = 0x0288;
  t.triagdn = 0x25bc;
  t.triaglf = 0x25c4;
  t.triagrt = 0x25ba;
  t.triagup = 0x25b2;
  t.ts = 0x02a6;
  t.tsadi = 0x05e6;
  t.tsadidagesh = 0xfb46;
  t.tsadidageshhebrew = 0xfb46;
  t.tsadihebrew = 0x05e6;
  t.tsecyrillic = 0x0446;
  t.tsere = 0x05b5;
  t.tsere12 = 0x05b5;
  t.tsere1e = 0x05b5;
  t.tsere2b = 0x05b5;
  t.tserehebrew = 0x05b5;
  t.tserenarrowhebrew = 0x05b5;
  t.tserequarterhebrew = 0x05b5;
  t.tserewidehebrew = 0x05b5;
  t.tshecyrillic = 0x045b;
  t.tsuperior = 0xf6f3;
  t.ttabengali = 0x099f;
  t.ttadeva = 0x091f;
  t.ttagujarati = 0x0a9f;
  t.ttagurmukhi = 0x0a1f;
  t.tteharabic = 0x0679;
  t.ttehfinalarabic = 0xfb67;
  t.ttehinitialarabic = 0xfb68;
  t.ttehmedialarabic = 0xfb69;
  t.tthabengali = 0x09a0;
  t.tthadeva = 0x0920;
  t.tthagujarati = 0x0aa0;
  t.tthagurmukhi = 0x0a20;
  t.tturned = 0x0287;
  t.tuhiragana = 0x3064;
  t.tukatakana = 0x30c4;
  t.tukatakanahalfwidth = 0xff82;
  t.tusmallhiragana = 0x3063;
  t.tusmallkatakana = 0x30c3;
  t.tusmallkatakanahalfwidth = 0xff6f;
  t.twelvecircle = 0x246b;
  t.twelveparen = 0x247f;
  t.twelveperiod = 0x2493;
  t.twelveroman = 0x217b;
  t.twentycircle = 0x2473;
  t.twentyhangzhou = 0x5344;
  t.twentyparen = 0x2487;
  t.twentyperiod = 0x249b;
  t.two = 0x0032;
  t.twoarabic = 0x0662;
  t.twobengali = 0x09e8;
  t.twocircle = 0x2461;
  t.twocircleinversesansserif = 0x278b;
  t.twodeva = 0x0968;
  t.twodotenleader = 0x2025;
  t.twodotleader = 0x2025;
  t.twodotleadervertical = 0xfe30;
  t.twogujarati = 0x0ae8;
  t.twogurmukhi = 0x0a68;
  t.twohackarabic = 0x0662;
  t.twohangzhou = 0x3022;
  t.twoideographicparen = 0x3221;
  t.twoinferior = 0x2082;
  t.twomonospace = 0xff12;
  t.twonumeratorbengali = 0x09f5;
  t.twooldstyle = 0xf732;
  t.twoparen = 0x2475;
  t.twoperiod = 0x2489;
  t.twopersian = 0x06f2;
  t.tworoman = 0x2171;
  t.twostroke = 0x01bb;
  t.twosuperior = 0x00b2;
  t.twothai = 0x0e52;
  t.twothirds = 0x2154;
  t.u = 0x0075;
  t.uacute = 0x00fa;
  t.ubar = 0x0289;
  t.ubengali = 0x0989;
  t.ubopomofo = 0x3128;
  t.ubreve = 0x016d;
  t.ucaron = 0x01d4;
  t.ucircle = 0x24e4;
  t.ucircumflex = 0x00fb;
  t.ucircumflexbelow = 0x1e77;
  t.ucyrillic = 0x0443;
  t.udattadeva = 0x0951;
  t.udblacute = 0x0171;
  t.udblgrave = 0x0215;
  t.udeva = 0x0909;
  t.udieresis = 0x00fc;
  t.udieresisacute = 0x01d8;
  t.udieresisbelow = 0x1e73;
  t.udieresiscaron = 0x01da;
  t.udieresiscyrillic = 0x04f1;
  t.udieresisgrave = 0x01dc;
  t.udieresismacron = 0x01d6;
  t.udotbelow = 0x1ee5;
  t.ugrave = 0x00f9;
  t.ugujarati = 0x0a89;
  t.ugurmukhi = 0x0a09;
  t.uhiragana = 0x3046;
  t.uhookabove = 0x1ee7;
  t.uhorn = 0x01b0;
  t.uhornacute = 0x1ee9;
  t.uhorndotbelow = 0x1ef1;
  t.uhorngrave = 0x1eeb;
  t.uhornhookabove = 0x1eed;
  t.uhorntilde = 0x1eef;
  t.uhungarumlaut = 0x0171;
  t.uhungarumlautcyrillic = 0x04f3;
  t.uinvertedbreve = 0x0217;
  t.ukatakana = 0x30a6;
  t.ukatakanahalfwidth = 0xff73;
  t.ukcyrillic = 0x0479;
  t.ukorean = 0x315c;
  t.umacron = 0x016b;
  t.umacroncyrillic = 0x04ef;
  t.umacrondieresis = 0x1e7b;
  t.umatragurmukhi = 0x0a41;
  t.umonospace = 0xff55;
  t.underscore = 0x005f;
  t.underscoredbl = 0x2017;
  t.underscoremonospace = 0xff3f;
  t.underscorevertical = 0xfe33;
  t.underscorewavy = 0xfe4f;
  t.union = 0x222a;
  t.universal = 0x2200;
  t.uogonek = 0x0173;
  t.uparen = 0x24b0;
  t.upblock = 0x2580;
  t.upperdothebrew = 0x05c4;
  t.upsilon = 0x03c5;
  t.upsilondieresis = 0x03cb;
  t.upsilondieresistonos = 0x03b0;
  t.upsilonlatin = 0x028a;
  t.upsilontonos = 0x03cd;
  t.uptackbelowcmb = 0x031d;
  t.uptackmod = 0x02d4;
  t.uragurmukhi = 0x0a73;
  t.uring = 0x016f;
  t.ushortcyrillic = 0x045e;
  t.usmallhiragana = 0x3045;
  t.usmallkatakana = 0x30a5;
  t.usmallkatakanahalfwidth = 0xff69;
  t.ustraightcyrillic = 0x04af;
  t.ustraightstrokecyrillic = 0x04b1;
  t.utilde = 0x0169;
  t.utildeacute = 0x1e79;
  t.utildebelow = 0x1e75;
  t.uubengali = 0x098a;
  t.uudeva = 0x090a;
  t.uugujarati = 0x0a8a;
  t.uugurmukhi = 0x0a0a;
  t.uumatragurmukhi = 0x0a42;
  t.uuvowelsignbengali = 0x09c2;
  t.uuvowelsigndeva = 0x0942;
  t.uuvowelsigngujarati = 0x0ac2;
  t.uvowelsignbengali = 0x09c1;
  t.uvowelsigndeva = 0x0941;
  t.uvowelsigngujarati = 0x0ac1;
  t.v = 0x0076;
  t.vadeva = 0x0935;
  t.vagujarati = 0x0ab5;
  t.vagurmukhi = 0x0a35;
  t.vakatakana = 0x30f7;
  t.vav = 0x05d5;
  t.vavdagesh = 0xfb35;
  t.vavdagesh65 = 0xfb35;
  t.vavdageshhebrew = 0xfb35;
  t.vavhebrew = 0x05d5;
  t.vavholam = 0xfb4b;
  t.vavholamhebrew = 0xfb4b;
  t.vavvavhebrew = 0x05f0;
  t.vavyodhebrew = 0x05f1;
  t.vcircle = 0x24e5;
  t.vdotbelow = 0x1e7f;
  t.vecyrillic = 0x0432;
  t.veharabic = 0x06a4;
  t.vehfinalarabic = 0xfb6b;
  t.vehinitialarabic = 0xfb6c;
  t.vehmedialarabic = 0xfb6d;
  t.vekatakana = 0x30f9;
  t.venus = 0x2640;
  t.verticalbar = 0x007c;
  t.verticallineabovecmb = 0x030d;
  t.verticallinebelowcmb = 0x0329;
  t.verticallinelowmod = 0x02cc;
  t.verticallinemod = 0x02c8;
  t.vewarmenian = 0x057e;
  t.vhook = 0x028b;
  t.vikatakana = 0x30f8;
  t.viramabengali = 0x09cd;
  t.viramadeva = 0x094d;
  t.viramagujarati = 0x0acd;
  t.visargabengali = 0x0983;
  t.visargadeva = 0x0903;
  t.visargagujarati = 0x0a83;
  t.vmonospace = 0xff56;
  t.voarmenian = 0x0578;
  t.voicediterationhiragana = 0x309e;
  t.voicediterationkatakana = 0x30fe;
  t.voicedmarkkana = 0x309b;
  t.voicedmarkkanahalfwidth = 0xff9e;
  t.vokatakana = 0x30fa;
  t.vparen = 0x24b1;
  t.vtilde = 0x1e7d;
  t.vturned = 0x028c;
  t.vuhiragana = 0x3094;
  t.vukatakana = 0x30f4;
  t.w = 0x0077;
  t.wacute = 0x1e83;
  t.waekorean = 0x3159;
  t.wahiragana = 0x308f;
  t.wakatakana = 0x30ef;
  t.wakatakanahalfwidth = 0xff9c;
  t.wakorean = 0x3158;
  t.wasmallhiragana = 0x308e;
  t.wasmallkatakana = 0x30ee;
  t.wattosquare = 0x3357;
  t.wavedash = 0x301c;
  t.wavyunderscorevertical = 0xfe34;
  t.wawarabic = 0x0648;
  t.wawfinalarabic = 0xfeee;
  t.wawhamzaabovearabic = 0x0624;
  t.wawhamzaabovefinalarabic = 0xfe86;
  t.wbsquare = 0x33dd;
  t.wcircle = 0x24e6;
  t.wcircumflex = 0x0175;
  t.wdieresis = 0x1e85;
  t.wdotaccent = 0x1e87;
  t.wdotbelow = 0x1e89;
  t.wehiragana = 0x3091;
  t.weierstrass = 0x2118;
  t.wekatakana = 0x30f1;
  t.wekorean = 0x315e;
  t.weokorean = 0x315d;
  t.wgrave = 0x1e81;
  t.whitebullet = 0x25e6;
  t.whitecircle = 0x25cb;
  t.whitecircleinverse = 0x25d9;
  t.whitecornerbracketleft = 0x300e;
  t.whitecornerbracketleftvertical = 0xfe43;
  t.whitecornerbracketright = 0x300f;
  t.whitecornerbracketrightvertical = 0xfe44;
  t.whitediamond = 0x25c7;
  t.whitediamondcontainingblacksmalldiamond = 0x25c8;
  t.whitedownpointingsmalltriangle = 0x25bf;
  t.whitedownpointingtriangle = 0x25bd;
  t.whiteleftpointingsmalltriangle = 0x25c3;
  t.whiteleftpointingtriangle = 0x25c1;
  t.whitelenticularbracketleft = 0x3016;
  t.whitelenticularbracketright = 0x3017;
  t.whiterightpointingsmalltriangle = 0x25b9;
  t.whiterightpointingtriangle = 0x25b7;
  t.whitesmallsquare = 0x25ab;
  t.whitesmilingface = 0x263a;
  t.whitesquare = 0x25a1;
  t.whitestar = 0x2606;
  t.whitetelephone = 0x260f;
  t.whitetortoiseshellbracketleft = 0x3018;
  t.whitetortoiseshellbracketright = 0x3019;
  t.whiteuppointingsmalltriangle = 0x25b5;
  t.whiteuppointingtriangle = 0x25b3;
  t.wihiragana = 0x3090;
  t.wikatakana = 0x30f0;
  t.wikorean = 0x315f;
  t.wmonospace = 0xff57;
  t.wohiragana = 0x3092;
  t.wokatakana = 0x30f2;
  t.wokatakanahalfwidth = 0xff66;
  t.won = 0x20a9;
  t.wonmonospace = 0xffe6;
  t.wowaenthai = 0x0e27;
  t.wparen = 0x24b2;
  t.wring = 0x1e98;
  t.wsuperior = 0x02b7;
  t.wturned = 0x028d;
  t.wynn = 0x01bf;
  t.x = 0x0078;
  t.xabovecmb = 0x033d;
  t.xbopomofo = 0x3112;
  t.xcircle = 0x24e7;
  t.xdieresis = 0x1e8d;
  t.xdotaccent = 0x1e8b;
  t.xeharmenian = 0x056d;
  t.xi = 0x03be;
  t.xmonospace = 0xff58;
  t.xparen = 0x24b3;
  t.xsuperior = 0x02e3;
  t.y = 0x0079;
  t.yaadosquare = 0x334e;
  t.yabengali = 0x09af;
  t.yacute = 0x00fd;
  t.yadeva = 0x092f;
  t.yaekorean = 0x3152;
  t.yagujarati = 0x0aaf;
  t.yagurmukhi = 0x0a2f;
  t.yahiragana = 0x3084;
  t.yakatakana = 0x30e4;
  t.yakatakanahalfwidth = 0xff94;
  t.yakorean = 0x3151;
  t.yamakkanthai = 0x0e4e;
  t.yasmallhiragana = 0x3083;
  t.yasmallkatakana = 0x30e3;
  t.yasmallkatakanahalfwidth = 0xff6c;
  t.yatcyrillic = 0x0463;
  t.ycircle = 0x24e8;
  t.ycircumflex = 0x0177;
  t.ydieresis = 0x00ff;
  t.ydotaccent = 0x1e8f;
  t.ydotbelow = 0x1ef5;
  t.yeharabic = 0x064a;
  t.yehbarreearabic = 0x06d2;
  t.yehbarreefinalarabic = 0xfbaf;
  t.yehfinalarabic = 0xfef2;
  t.yehhamzaabovearabic = 0x0626;
  t.yehhamzaabovefinalarabic = 0xfe8a;
  t.yehhamzaaboveinitialarabic = 0xfe8b;
  t.yehhamzaabovemedialarabic = 0xfe8c;
  t.yehinitialarabic = 0xfef3;
  t.yehmedialarabic = 0xfef4;
  t.yehmeeminitialarabic = 0xfcdd;
  t.yehmeemisolatedarabic = 0xfc58;
  t.yehnoonfinalarabic = 0xfc94;
  t.yehthreedotsbelowarabic = 0x06d1;
  t.yekorean = 0x3156;
  t.yen = 0x00a5;
  t.yenmonospace = 0xffe5;
  t.yeokorean = 0x3155;
  t.yeorinhieuhkorean = 0x3186;
  t.yerahbenyomohebrew = 0x05aa;
  t.yerahbenyomolefthebrew = 0x05aa;
  t.yericyrillic = 0x044b;
  t.yerudieresiscyrillic = 0x04f9;
  t.yesieungkorean = 0x3181;
  t.yesieungpansioskorean = 0x3183;
  t.yesieungsioskorean = 0x3182;
  t.yetivhebrew = 0x059a;
  t.ygrave = 0x1ef3;
  t.yhook = 0x01b4;
  t.yhookabove = 0x1ef7;
  t.yiarmenian = 0x0575;
  t.yicyrillic = 0x0457;
  t.yikorean = 0x3162;
  t.yinyang = 0x262f;
  t.yiwnarmenian = 0x0582;
  t.ymonospace = 0xff59;
  t.yod = 0x05d9;
  t.yoddagesh = 0xfb39;
  t.yoddageshhebrew = 0xfb39;
  t.yodhebrew = 0x05d9;
  t.yodyodhebrew = 0x05f2;
  t.yodyodpatahhebrew = 0xfb1f;
  t.yohiragana = 0x3088;
  t.yoikorean = 0x3189;
  t.yokatakana = 0x30e8;
  t.yokatakanahalfwidth = 0xff96;
  t.yokorean = 0x315b;
  t.yosmallhiragana = 0x3087;
  t.yosmallkatakana = 0x30e7;
  t.yosmallkatakanahalfwidth = 0xff6e;
  t.yotgreek = 0x03f3;
  t.yoyaekorean = 0x3188;
  t.yoyakorean = 0x3187;
  t.yoyakthai = 0x0e22;
  t.yoyingthai = 0x0e0d;
  t.yparen = 0x24b4;
  t.ypogegrammeni = 0x037a;
  t.ypogegrammenigreekcmb = 0x0345;
  t.yr = 0x01a6;
  t.yring = 0x1e99;
  t.ysuperior = 0x02b8;
  t.ytilde = 0x1ef9;
  t.yturned = 0x028e;
  t.yuhiragana = 0x3086;
  t.yuikorean = 0x318c;
  t.yukatakana = 0x30e6;
  t.yukatakanahalfwidth = 0xff95;
  t.yukorean = 0x3160;
  t.yusbigcyrillic = 0x046b;
  t.yusbigiotifiedcyrillic = 0x046d;
  t.yuslittlecyrillic = 0x0467;
  t.yuslittleiotifiedcyrillic = 0x0469;
  t.yusmallhiragana = 0x3085;
  t.yusmallkatakana = 0x30e5;
  t.yusmallkatakanahalfwidth = 0xff6d;
  t.yuyekorean = 0x318b;
  t.yuyeokorean = 0x318a;
  t.yyabengali = 0x09df;
  t.yyadeva = 0x095f;
  t.z = 0x007a;
  t.zaarmenian = 0x0566;
  t.zacute = 0x017a;
  t.zadeva = 0x095b;
  t.zagurmukhi = 0x0a5b;
  t.zaharabic = 0x0638;
  t.zahfinalarabic = 0xfec6;
  t.zahinitialarabic = 0xfec7;
  t.zahiragana = 0x3056;
  t.zahmedialarabic = 0xfec8;
  t.zainarabic = 0x0632;
  t.zainfinalarabic = 0xfeb0;
  t.zakatakana = 0x30b6;
  t.zaqefgadolhebrew = 0x0595;
  t.zaqefqatanhebrew = 0x0594;
  t.zarqahebrew = 0x0598;
  t.zayin = 0x05d6;
  t.zayindagesh = 0xfb36;
  t.zayindageshhebrew = 0xfb36;
  t.zayinhebrew = 0x05d6;
  t.zbopomofo = 0x3117;
  t.zcaron = 0x017e;
  t.zcircle = 0x24e9;
  t.zcircumflex = 0x1e91;
  t.zcurl = 0x0291;
  t.zdot = 0x017c;
  t.zdotaccent = 0x017c;
  t.zdotbelow = 0x1e93;
  t.zecyrillic = 0x0437;
  t.zedescendercyrillic = 0x0499;
  t.zedieresiscyrillic = 0x04df;
  t.zehiragana = 0x305c;
  t.zekatakana = 0x30bc;
  t.zero = 0x0030;
  t.zeroarabic = 0x0660;
  t.zerobengali = 0x09e6;
  t.zerodeva = 0x0966;
  t.zerogujarati = 0x0ae6;
  t.zerogurmukhi = 0x0a66;
  t.zerohackarabic = 0x0660;
  t.zeroinferior = 0x2080;
  t.zeromonospace = 0xff10;
  t.zerooldstyle = 0xf730;
  t.zeropersian = 0x06f0;
  t.zerosuperior = 0x2070;
  t.zerothai = 0x0e50;
  t.zerowidthjoiner = 0xfeff;
  t.zerowidthnonjoiner = 0x200c;
  t.zerowidthspace = 0x200b;
  t.zeta = 0x03b6;
  t.zhbopomofo = 0x3113;
  t.zhearmenian = 0x056a;
  t.zhebrevecyrillic = 0x04c2;
  t.zhecyrillic = 0x0436;
  t.zhedescendercyrillic = 0x0497;
  t.zhedieresiscyrillic = 0x04dd;
  t.zihiragana = 0x3058;
  t.zikatakana = 0x30b8;
  t.zinorhebrew = 0x05ae;
  t.zlinebelow = 0x1e95;
  t.zmonospace = 0xff5a;
  t.zohiragana = 0x305e;
  t.zokatakana = 0x30be;
  t.zparen = 0x24b5;
  t.zretroflexhook = 0x0290;
  t.zstroke = 0x01b6;
  t.zuhiragana = 0x305a;
  t.zukatakana = 0x30ba;
  t[".notdef"] = 0x0000;
  t.angbracketleftbig = 0x2329;
  t.angbracketleftBig = 0x2329;
  t.angbracketleftbigg = 0x2329;
  t.angbracketleftBigg = 0x2329;
  t.angbracketrightBig = 0x232a;
  t.angbracketrightbig = 0x232a;
  t.angbracketrightBigg = 0x232a;
  t.angbracketrightbigg = 0x232a;
  t.arrowhookleft = 0x21aa;
  t.arrowhookright = 0x21a9;
  t.arrowlefttophalf = 0x21bc;
  t.arrowleftbothalf = 0x21bd;
  t.arrownortheast = 0x2197;
  t.arrownorthwest = 0x2196;
  t.arrowrighttophalf = 0x21c0;
  t.arrowrightbothalf = 0x21c1;
  t.arrowsoutheast = 0x2198;
  t.arrowsouthwest = 0x2199;
  t.backslashbig = 0x2216;
  t.backslashBig = 0x2216;
  t.backslashBigg = 0x2216;
  t.backslashbigg = 0x2216;
  t.bardbl = 0x2016;
  t.bracehtipdownleft = 0xfe37;
  t.bracehtipdownright = 0xfe37;
  t.bracehtipupleft = 0xfe38;
  t.bracehtipupright = 0xfe38;
  t.braceleftBig = 0x007b;
  t.braceleftbig = 0x007b;
  t.braceleftbigg = 0x007b;
  t.braceleftBigg = 0x007b;
  t.bracerightBig = 0x007d;
  t.bracerightbig = 0x007d;
  t.bracerightbigg = 0x007d;
  t.bracerightBigg = 0x007d;
  t.bracketleftbig = 0x005b;
  t.bracketleftBig = 0x005b;
  t.bracketleftbigg = 0x005b;
  t.bracketleftBigg = 0x005b;
  t.bracketrightBig = 0x005d;
  t.bracketrightbig = 0x005d;
  t.bracketrightbigg = 0x005d;
  t.bracketrightBigg = 0x005d;
  t.ceilingleftbig = 0x2308;
  t.ceilingleftBig = 0x2308;
  t.ceilingleftBigg = 0x2308;
  t.ceilingleftbigg = 0x2308;
  t.ceilingrightbig = 0x2309;
  t.ceilingrightBig = 0x2309;
  t.ceilingrightbigg = 0x2309;
  t.ceilingrightBigg = 0x2309;
  t.circledotdisplay = 0x2299;
  t.circledottext = 0x2299;
  t.circlemultiplydisplay = 0x2297;
  t.circlemultiplytext = 0x2297;
  t.circleplusdisplay = 0x2295;
  t.circleplustext = 0x2295;
  t.contintegraldisplay = 0x222e;
  t.contintegraltext = 0x222e;
  t.coproductdisplay = 0x2210;
  t.coproducttext = 0x2210;
  t.floorleftBig = 0x230a;
  t.floorleftbig = 0x230a;
  t.floorleftbigg = 0x230a;
  t.floorleftBigg = 0x230a;
  t.floorrightbig = 0x230b;
  t.floorrightBig = 0x230b;
  t.floorrightBigg = 0x230b;
  t.floorrightbigg = 0x230b;
  t.hatwide = 0x0302;
  t.hatwider = 0x0302;
  t.hatwidest = 0x0302;
  t.intercal = 0x1d40;
  t.integraldisplay = 0x222b;
  t.integraltext = 0x222b;
  t.intersectiondisplay = 0x22c2;
  t.intersectiontext = 0x22c2;
  t.logicalanddisplay = 0x2227;
  t.logicalandtext = 0x2227;
  t.logicalordisplay = 0x2228;
  t.logicalortext = 0x2228;
  t.parenleftBig = 0x0028;
  t.parenleftbig = 0x0028;
  t.parenleftBigg = 0x0028;
  t.parenleftbigg = 0x0028;
  t.parenrightBig = 0x0029;
  t.parenrightbig = 0x0029;
  t.parenrightBigg = 0x0029;
  t.parenrightbigg = 0x0029;
  t.prime = 0x2032;
  t.productdisplay = 0x220f;
  t.producttext = 0x220f;
  t.radicalbig = 0x221a;
  t.radicalBig = 0x221a;
  t.radicalBigg = 0x221a;
  t.radicalbigg = 0x221a;
  t.radicalbt = 0x221a;
  t.radicaltp = 0x221a;
  t.radicalvertex = 0x221a;
  t.slashbig = 0x002f;
  t.slashBig = 0x002f;
  t.slashBigg = 0x002f;
  t.slashbigg = 0x002f;
  t.summationdisplay = 0x2211;
  t.summationtext = 0x2211;
  t.tildewide = 0x02dc;
  t.tildewider = 0x02dc;
  t.tildewidest = 0x02dc;
  t.uniondisplay = 0x22c3;
  t.unionmultidisplay = 0x228e;
  t.unionmultitext = 0x228e;
  t.unionsqdisplay = 0x2294;
  t.unionsqtext = 0x2294;
  t.uniontext = 0x22c3;
  t.vextenddouble = 0x2225;
  t.vextendsingle = 0x2223;
});
const getDingbatsGlyphsUnicode = getLookupTableFactory(function (t) {
  t.space = 0x0020;
  t.a1 = 0x2701;
  t.a2 = 0x2702;
  t.a202 = 0x2703;
  t.a3 = 0x2704;
  t.a4 = 0x260e;
  t.a5 = 0x2706;
  t.a119 = 0x2707;
  t.a118 = 0x2708;
  t.a117 = 0x2709;
  t.a11 = 0x261b;
  t.a12 = 0x261e;
  t.a13 = 0x270c;
  t.a14 = 0x270d;
  t.a15 = 0x270e;
  t.a16 = 0x270f;
  t.a105 = 0x2710;
  t.a17 = 0x2711;
  t.a18 = 0x2712;
  t.a19 = 0x2713;
  t.a20 = 0x2714;
  t.a21 = 0x2715;
  t.a22 = 0x2716;
  t.a23 = 0x2717;
  t.a24 = 0x2718;
  t.a25 = 0x2719;
  t.a26 = 0x271a;
  t.a27 = 0x271b;
  t.a28 = 0x271c;
  t.a6 = 0x271d;
  t.a7 = 0x271e;
  t.a8 = 0x271f;
  t.a9 = 0x2720;
  t.a10 = 0x2721;
  t.a29 = 0x2722;
  t.a30 = 0x2723;
  t.a31 = 0x2724;
  t.a32 = 0x2725;
  t.a33 = 0x2726;
  t.a34 = 0x2727;
  t.a35 = 0x2605;
  t.a36 = 0x2729;
  t.a37 = 0x272a;
  t.a38 = 0x272b;
  t.a39 = 0x272c;
  t.a40 = 0x272d;
  t.a41 = 0x272e;
  t.a42 = 0x272f;
  t.a43 = 0x2730;
  t.a44 = 0x2731;
  t.a45 = 0x2732;
  t.a46 = 0x2733;
  t.a47 = 0x2734;
  t.a48 = 0x2735;
  t.a49 = 0x2736;
  t.a50 = 0x2737;
  t.a51 = 0x2738;
  t.a52 = 0x2739;
  t.a53 = 0x273a;
  t.a54 = 0x273b;
  t.a55 = 0x273c;
  t.a56 = 0x273d;
  t.a57 = 0x273e;
  t.a58 = 0x273f;
  t.a59 = 0x2740;
  t.a60 = 0x2741;
  t.a61 = 0x2742;
  t.a62 = 0x2743;
  t.a63 = 0x2744;
  t.a64 = 0x2745;
  t.a65 = 0x2746;
  t.a66 = 0x2747;
  t.a67 = 0x2748;
  t.a68 = 0x2749;
  t.a69 = 0x274a;
  t.a70 = 0x274b;
  t.a71 = 0x25cf;
  t.a72 = 0x274d;
  t.a73 = 0x25a0;
  t.a74 = 0x274f;
  t.a203 = 0x2750;
  t.a75 = 0x2751;
  t.a204 = 0x2752;
  t.a76 = 0x25b2;
  t.a77 = 0x25bc;
  t.a78 = 0x25c6;
  t.a79 = 0x2756;
  t.a81 = 0x25d7;
  t.a82 = 0x2758;
  t.a83 = 0x2759;
  t.a84 = 0x275a;
  t.a97 = 0x275b;
  t.a98 = 0x275c;
  t.a99 = 0x275d;
  t.a100 = 0x275e;
  t.a101 = 0x2761;
  t.a102 = 0x2762;
  t.a103 = 0x2763;
  t.a104 = 0x2764;
  t.a106 = 0x2765;
  t.a107 = 0x2766;
  t.a108 = 0x2767;
  t.a112 = 0x2663;
  t.a111 = 0x2666;
  t.a110 = 0x2665;
  t.a109 = 0x2660;
  t.a120 = 0x2460;
  t.a121 = 0x2461;
  t.a122 = 0x2462;
  t.a123 = 0x2463;
  t.a124 = 0x2464;
  t.a125 = 0x2465;
  t.a126 = 0x2466;
  t.a127 = 0x2467;
  t.a128 = 0x2468;
  t.a129 = 0x2469;
  t.a130 = 0x2776;
  t.a131 = 0x2777;
  t.a132 = 0x2778;
  t.a133 = 0x2779;
  t.a134 = 0x277a;
  t.a135 = 0x277b;
  t.a136 = 0x277c;
  t.a137 = 0x277d;
  t.a138 = 0x277e;
  t.a139 = 0x277f;
  t.a140 = 0x2780;
  t.a141 = 0x2781;
  t.a142 = 0x2782;
  t.a143 = 0x2783;
  t.a144 = 0x2784;
  t.a145 = 0x2785;
  t.a146 = 0x2786;
  t.a147 = 0x2787;
  t.a148 = 0x2788;
  t.a149 = 0x2789;
  t.a150 = 0x278a;
  t.a151 = 0x278b;
  t.a152 = 0x278c;
  t.a153 = 0x278d;
  t.a154 = 0x278e;
  t.a155 = 0x278f;
  t.a156 = 0x2790;
  t.a157 = 0x2791;
  t.a158 = 0x2792;
  t.a159 = 0x2793;
  t.a160 = 0x2794;
  t.a161 = 0x2192;
  t.a163 = 0x2194;
  t.a164 = 0x2195;
  t.a196 = 0x2798;
  t.a165 = 0x2799;
  t.a192 = 0x279a;
  t.a166 = 0x279b;
  t.a167 = 0x279c;
  t.a168 = 0x279d;
  t.a169 = 0x279e;
  t.a170 = 0x279f;
  t.a171 = 0x27a0;
  t.a172 = 0x27a1;
  t.a173 = 0x27a2;
  t.a162 = 0x27a3;
  t.a174 = 0x27a4;
  t.a175 = 0x27a5;
  t.a176 = 0x27a6;
  t.a177 = 0x27a7;
  t.a178 = 0x27a8;
  t.a179 = 0x27a9;
  t.a193 = 0x27aa;
  t.a180 = 0x27ab;
  t.a199 = 0x27ac;
  t.a181 = 0x27ad;
  t.a200 = 0x27ae;
  t.a182 = 0x27af;
  t.a201 = 0x27b1;
  t.a183 = 0x27b2;
  t.a184 = 0x27b3;
  t.a197 = 0x27b4;
  t.a185 = 0x27b5;
  t.a194 = 0x27b6;
  t.a198 = 0x27b7;
  t.a186 = 0x27b8;
  t.a195 = 0x27b9;
  t.a187 = 0x27ba;
  t.a188 = 0x27bb;
  t.a189 = 0x27bc;
  t.a190 = 0x27bd;
  t.a191 = 0x27be;
  t.a89 = 0x2768;
  t.a90 = 0x2769;
  t.a93 = 0x276a;
  t.a94 = 0x276b;
  t.a91 = 0x276c;
  t.a92 = 0x276d;
  t.a205 = 0x276e;
  t.a85 = 0x276f;
  t.a206 = 0x2770;
  t.a86 = 0x2771;
  t.a87 = 0x2772;
  t.a88 = 0x2773;
  t.a95 = 0x2774;
  t.a96 = 0x2775;
  t[".notdef"] = 0x0000;
});

;// CONCATENATED MODULE: ./src/core/unicode.js

const getSpecialPUASymbols = getLookupTableFactory(function (t) {
  t[63721] = 0x00a9;
  t[63193] = 0x00a9;
  t[63720] = 0x00ae;
  t[63194] = 0x00ae;
  t[63722] = 0x2122;
  t[63195] = 0x2122;
  t[63729] = 0x23a7;
  t[63730] = 0x23a8;
  t[63731] = 0x23a9;
  t[63740] = 0x23ab;
  t[63741] = 0x23ac;
  t[63742] = 0x23ad;
  t[63726] = 0x23a1;
  t[63727] = 0x23a2;
  t[63728] = 0x23a3;
  t[63737] = 0x23a4;
  t[63738] = 0x23a5;
  t[63739] = 0x23a6;
  t[63723] = 0x239b;
  t[63724] = 0x239c;
  t[63725] = 0x239d;
  t[63734] = 0x239e;
  t[63735] = 0x239f;
  t[63736] = 0x23a0;
});
function mapSpecialUnicodeValues(code) {
  if (code >= 0xfff0 && code <= 0xffff) {
    return 0;
  } else if (code >= 0xf600 && code <= 0xf8ff) {
    return getSpecialPUASymbols()[code] || code;
  } else if (code === 0x00ad) {
    return 0x002d;
  }
  return code;
}
function getUnicodeForGlyph(name, glyphsUnicodeMap) {
  let unicode = glyphsUnicodeMap[name];
  if (unicode !== undefined) {
    return unicode;
  }
  if (!name) {
    return -1;
  }
  if (name[0] === "u") {
    const nameLen = name.length;
    let hexStr;
    if (nameLen === 7 && name[1] === "n" && name[2] === "i") {
      hexStr = name.substring(3);
    } else if (nameLen >= 5 && nameLen <= 7) {
      hexStr = name.substring(1);
    } else {
      return -1;
    }
    if (hexStr === hexStr.toUpperCase()) {
      unicode = parseInt(hexStr, 16);
      if (unicode >= 0) {
        return unicode;
      }
    }
  }
  return -1;
}
const UnicodeRanges = [[0x0000, 0x007f], [0x0080, 0x00ff], [0x0100, 0x017f], [0x0180, 0x024f], [0x0250, 0x02af, 0x1d00, 0x1d7f, 0x1d80, 0x1dbf], [0x02b0, 0x02ff, 0xa700, 0xa71f], [0x0300, 0x036f, 0x1dc0, 0x1dff], [0x0370, 0x03ff], [0x2c80, 0x2cff], [0x0400, 0x04ff, 0x0500, 0x052f, 0x2de0, 0x2dff, 0xa640, 0xa69f], [0x0530, 0x058f], [0x0590, 0x05ff], [0xa500, 0xa63f], [0x0600, 0x06ff, 0x0750, 0x077f], [0x07c0, 0x07ff], [0x0900, 0x097f], [0x0980, 0x09ff], [0x0a00, 0x0a7f], [0x0a80, 0x0aff], [0x0b00, 0x0b7f], [0x0b80, 0x0bff], [0x0c00, 0x0c7f], [0x0c80, 0x0cff], [0x0d00, 0x0d7f], [0x0e00, 0x0e7f], [0x0e80, 0x0eff], [0x10a0, 0x10ff, 0x2d00, 0x2d2f], [0x1b00, 0x1b7f], [0x1100, 0x11ff], [0x1e00, 0x1eff, 0x2c60, 0x2c7f, 0xa720, 0xa7ff], [0x1f00, 0x1fff], [0x2000, 0x206f, 0x2e00, 0x2e7f], [0x2070, 0x209f], [0x20a0, 0x20cf], [0x20d0, 0x20ff], [0x2100, 0x214f], [0x2150, 0x218f], [0x2190, 0x21ff, 0x27f0, 0x27ff, 0x2900, 0x297f, 0x2b00, 0x2bff], [0x2200, 0x22ff, 0x2a00, 0x2aff, 0x27c0, 0x27ef, 0x2980, 0x29ff], [0x2300, 0x23ff], [0x2400, 0x243f], [0x2440, 0x245f], [0x2460, 0x24ff], [0x2500, 0x257f], [0x2580, 0x259f], [0x25a0, 0x25ff], [0x2600, 0x26ff], [0x2700, 0x27bf], [0x3000, 0x303f], [0x3040, 0x309f], [0x30a0, 0x30ff, 0x31f0, 0x31ff], [0x3100, 0x312f, 0x31a0, 0x31bf], [0x3130, 0x318f], [0xa840, 0xa87f], [0x3200, 0x32ff], [0x3300, 0x33ff], [0xac00, 0xd7af], [0xd800, 0xdfff], [0x10900, 0x1091f], [0x4e00, 0x9fff, 0x2e80, 0x2eff, 0x2f00, 0x2fdf, 0x2ff0, 0x2fff, 0x3400, 0x4dbf, 0x20000, 0x2a6df, 0x3190, 0x319f], [0xe000, 0xf8ff], [0x31c0, 0x31ef, 0xf900, 0xfaff, 0x2f800, 0x2fa1f], [0xfb00, 0xfb4f], [0xfb50, 0xfdff], [0xfe20, 0xfe2f], [0xfe10, 0xfe1f], [0xfe50, 0xfe6f], [0xfe70, 0xfeff], [0xff00, 0xffef], [0xfff0, 0xffff], [0x0f00, 0x0fff], [0x0700, 0x074f], [0x0780, 0x07bf], [0x0d80, 0x0dff], [0x1000, 0x109f], [0x1200, 0x137f, 0x1380, 0x139f, 0x2d80, 0x2ddf], [0x13a0, 0x13ff], [0x1400, 0x167f], [0x1680, 0x169f], [0x16a0, 0x16ff], [0x1780, 0x17ff], [0x1800, 0x18af], [0x2800, 0x28ff], [0xa000, 0xa48f], [0x1700, 0x171f, 0x1720, 0x173f, 0x1740, 0x175f, 0x1760, 0x177f], [0x10300, 0x1032f], [0x10330, 0x1034f], [0x10400, 0x1044f], [0x1d000, 0x1d0ff, 0x1d100, 0x1d1ff, 0x1d200, 0x1d24f], [0x1d400, 0x1d7ff], [0xff000, 0xffffd], [0xfe00, 0xfe0f, 0xe0100, 0xe01ef], [0xe0000, 0xe007f], [0x1900, 0x194f], [0x1950, 0x197f], [0x1980, 0x19df], [0x1a00, 0x1a1f], [0x2c00, 0x2c5f], [0x2d30, 0x2d7f], [0x4dc0, 0x4dff], [0xa800, 0xa82f], [0x10000, 0x1007f, 0x10080, 0x100ff, 0x10100, 0x1013f], [0x10140, 0x1018f], [0x10380, 0x1039f], [0x103a0, 0x103df], [0x10450, 0x1047f], [0x10480, 0x104af], [0x10800, 0x1083f], [0x10a00, 0x10a5f], [0x1d300, 0x1d35f], [0x12000, 0x123ff, 0x12400, 0x1247f], [0x1d360, 0x1d37f], [0x1b80, 0x1bbf], [0x1c00, 0x1c4f], [0x1c50, 0x1c7f], [0xa880, 0xa8df], [0xa900, 0xa92f], [0xa930, 0xa95f], [0xaa00, 0xaa5f], [0x10190, 0x101cf], [0x101d0, 0x101ff], [0x102a0, 0x102df, 0x10280, 0x1029f, 0x10920, 0x1093f], [0x1f030, 0x1f09f, 0x1f000, 0x1f02f]];
function getUnicodeRangeFor(value, lastPosition = -1) {
  if (lastPosition !== -1) {
    const range = UnicodeRanges[lastPosition];
    for (let i = 0, ii = range.length; i < ii; i += 2) {
      if (value >= range[i] && value <= range[i + 1]) {
        return lastPosition;
      }
    }
  }
  for (let i = 0, ii = UnicodeRanges.length; i < ii; i++) {
    const range = UnicodeRanges[i];
    for (let j = 0, jj = range.length; j < jj; j += 2) {
      if (value >= range[j] && value <= range[j + 1]) {
        return i;
      }
    }
  }
  return -1;
}
const SpecialCharRegExp = new RegExp("^(\\s)|(\\p{Mn})|(\\p{Cf})$", "u");
const CategoryCache = new Map();
function getCharUnicodeCategory(char) {
  const cachedCategory = CategoryCache.get(char);
  if (cachedCategory) {
    return cachedCategory;
  }
  const groups = char.match(SpecialCharRegExp);
  const category = {
    isWhitespace: !!groups?.[1],
    isZeroWidthDiacritic: !!groups?.[2],
    isInvisibleFormatMark: !!groups?.[3]
  };
  CategoryCache.set(char, category);
  return category;
}
function clearUnicodeCaches() {
  CategoryCache.clear();
}

;// CONCATENATED MODULE: ./src/core/fonts_utils.js





const SEAC_ANALYSIS_ENABLED = true;
const FontFlags = {
  FixedPitch: 1,
  Serif: 2,
  Symbolic: 4,
  Script: 8,
  Nonsymbolic: 32,
  Italic: 64,
  AllCap: 65536,
  SmallCap: 131072,
  ForceBold: 262144
};
const MacStandardGlyphOrdering = [".notdef", ".null", "nonmarkingreturn", "space", "exclam", "quotedbl", "numbersign", "dollar", "percent", "ampersand", "quotesingle", "parenleft", "parenright", "asterisk", "plus", "comma", "hyphen", "period", "slash", "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "colon", "semicolon", "less", "equal", "greater", "question", "at", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "bracketleft", "backslash", "bracketright", "asciicircum", "underscore", "grave", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "braceleft", "bar", "braceright", "asciitilde", "Adieresis", "Aring", "Ccedilla", "Eacute", "Ntilde", "Odieresis", "Udieresis", "aacute", "agrave", "acircumflex", "adieresis", "atilde", "aring", "ccedilla", "eacute", "egrave", "ecircumflex", "edieresis", "iacute", "igrave", "icircumflex", "idieresis", "ntilde", "oacute", "ograve", "ocircumflex", "odieresis", "otilde", "uacute", "ugrave", "ucircumflex", "udieresis", "dagger", "degree", "cent", "sterling", "section", "bullet", "paragraph", "germandbls", "registered", "copyright", "trademark", "acute", "dieresis", "notequal", "AE", "Oslash", "infinity", "plusminus", "lessequal", "greaterequal", "yen", "mu", "partialdiff", "summation", "product", "pi", "integral", "ordfeminine", "ordmasculine", "Omega", "ae", "oslash", "questiondown", "exclamdown", "logicalnot", "radical", "florin", "approxequal", "Delta", "guillemotleft", "guillemotright", "ellipsis", "nonbreakingspace", "Agrave", "Atilde", "Otilde", "OE", "oe", "endash", "emdash", "quotedblleft", "quotedblright", "quoteleft", "quoteright", "divide", "lozenge", "ydieresis", "Ydieresis", "fraction", "currency", "guilsinglleft", "guilsinglright", "fi", "fl", "daggerdbl", "periodcentered", "quotesinglbase", "quotedblbase", "perthousand", "Acircumflex", "Ecircumflex", "Aacute", "Edieresis", "Egrave", "Iacute", "Icircumflex", "Idieresis", "Igrave", "Oacute", "Ocircumflex", "apple", "Ograve", "Uacute", "Ucircumflex", "Ugrave", "dotlessi", "circumflex", "tilde", "macron", "breve", "dotaccent", "ring", "cedilla", "hungarumlaut", "ogonek", "caron", "Lslash", "lslash", "Scaron", "scaron", "Zcaron", "zcaron", "brokenbar", "Eth", "eth", "Yacute", "yacute", "Thorn", "thorn", "minus", "multiply", "onesuperior", "twosuperior", "threesuperior", "onehalf", "onequarter", "threequarters", "franc", "Gbreve", "gbreve", "Idotaccent", "Scedilla", "scedilla", "Cacute", "cacute", "Ccaron", "ccaron", "dcroat"];
function recoverGlyphName(name, glyphsUnicodeMap) {
  if (glyphsUnicodeMap[name] !== undefined) {
    return name;
  }
  const unicode = getUnicodeForGlyph(name, glyphsUnicodeMap);
  if (unicode !== -1) {
    for (const key in glyphsUnicodeMap) {
      if (glyphsUnicodeMap[key] === unicode) {
        return key;
      }
    }
  }
  info("Unable to recover a standard glyph name for: " + name);
  return name;
}
function type1FontGlyphMapping(properties, builtInEncoding, glyphNames) {
  const charCodeToGlyphId = Object.create(null);
  let glyphId, charCode, baseEncoding;
  const isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
  if (properties.isInternalFont) {
    baseEncoding = builtInEncoding;
    for (charCode = 0; charCode < baseEncoding.length; charCode++) {
      glyphId = glyphNames.indexOf(baseEncoding[charCode]);
      charCodeToGlyphId[charCode] = glyphId >= 0 ? glyphId : 0;
    }
  } else if (properties.baseEncodingName) {
    baseEncoding = getEncoding(properties.baseEncodingName);
    for (charCode = 0; charCode < baseEncoding.length; charCode++) {
      glyphId = glyphNames.indexOf(baseEncoding[charCode]);
      charCodeToGlyphId[charCode] = glyphId >= 0 ? glyphId : 0;
    }
  } else if (isSymbolicFont) {
    for (charCode in builtInEncoding) {
      charCodeToGlyphId[charCode] = builtInEncoding[charCode];
    }
  } else {
    baseEncoding = StandardEncoding;
    for (charCode = 0; charCode < baseEncoding.length; charCode++) {
      glyphId = glyphNames.indexOf(baseEncoding[charCode]);
      charCodeToGlyphId[charCode] = glyphId >= 0 ? glyphId : 0;
    }
  }
  const differences = properties.differences;
  let glyphsUnicodeMap;
  if (differences) {
    for (charCode in differences) {
      const glyphName = differences[charCode];
      glyphId = glyphNames.indexOf(glyphName);
      if (glyphId === -1) {
        if (!glyphsUnicodeMap) {
          glyphsUnicodeMap = getGlyphsUnicode();
        }
        const standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap);
        if (standardGlyphName !== glyphName) {
          glyphId = glyphNames.indexOf(standardGlyphName);
        }
      }
      charCodeToGlyphId[charCode] = glyphId >= 0 ? glyphId : 0;
    }
  }
  return charCodeToGlyphId;
}
function normalizeFontName(name) {
  return name.replaceAll(/[,_]/g, "-").replaceAll(/\s/g, "");
}
const getVerticalPresentationForm = getLookupTableFactory(t => {
  t[0x2013] = 0xfe32;
  t[0x2014] = 0xfe31;
  t[0x2025] = 0xfe30;
  t[0x2026] = 0xfe19;
  t[0x3001] = 0xfe11;
  t[0x3002] = 0xfe12;
  t[0x3008] = 0xfe3f;
  t[0x3009] = 0xfe40;
  t[0x300a] = 0xfe3d;
  t[0x300b] = 0xfe3e;
  t[0x300c] = 0xfe41;
  t[0x300d] = 0xfe42;
  t[0x300e] = 0xfe43;
  t[0x300f] = 0xfe44;
  t[0x3010] = 0xfe3b;
  t[0x3011] = 0xfe3c;
  t[0x3014] = 0xfe39;
  t[0x3015] = 0xfe3a;
  t[0x3016] = 0xfe17;
  t[0x3017] = 0xfe18;
  t[0xfe4f] = 0xfe34;
  t[0xff01] = 0xfe15;
  t[0xff08] = 0xfe35;
  t[0xff09] = 0xfe36;
  t[0xff0c] = 0xfe10;
  t[0xff1a] = 0xfe13;
  t[0xff1b] = 0xfe14;
  t[0xff1f] = 0xfe16;
  t[0xff3b] = 0xfe47;
  t[0xff3d] = 0xfe48;
  t[0xff3f] = 0xfe33;
  t[0xff5b] = 0xfe37;
  t[0xff5d] = 0xfe38;
});

;// CONCATENATED MODULE: ./src/core/standard_fonts.js


const getStdFontMap = getLookupTableFactory(function (t) {
  t["Times-Roman"] = "Times-Roman";
  t.Helvetica = "Helvetica";
  t.Courier = "Courier";
  t.Symbol = "Symbol";
  t["Times-Bold"] = "Times-Bold";
  t["Helvetica-Bold"] = "Helvetica-Bold";
  t["Courier-Bold"] = "Courier-Bold";
  t.ZapfDingbats = "ZapfDingbats";
  t["Times-Italic"] = "Times-Italic";
  t["Helvetica-Oblique"] = "Helvetica-Oblique";
  t["Courier-Oblique"] = "Courier-Oblique";
  t["Times-BoldItalic"] = "Times-BoldItalic";
  t["Helvetica-BoldOblique"] = "Helvetica-BoldOblique";
  t["Courier-BoldOblique"] = "Courier-BoldOblique";
  t.ArialNarrow = "Helvetica";
  t["ArialNarrow-Bold"] = "Helvetica-Bold";
  t["ArialNarrow-BoldItalic"] = "Helvetica-BoldOblique";
  t["ArialNarrow-Italic"] = "Helvetica-Oblique";
  t.ArialBlack = "Helvetica";
  t["ArialBlack-Bold"] = "Helvetica-Bold";
  t["ArialBlack-BoldItalic"] = "Helvetica-BoldOblique";
  t["ArialBlack-Italic"] = "Helvetica-Oblique";
  t["Arial-Black"] = "Helvetica";
  t["Arial-Black-Bold"] = "Helvetica-Bold";
  t["Arial-Black-BoldItalic"] = "Helvetica-BoldOblique";
  t["Arial-Black-Italic"] = "Helvetica-Oblique";
  t.Arial = "Helvetica";
  t["Arial-Bold"] = "Helvetica-Bold";
  t["Arial-BoldItalic"] = "Helvetica-BoldOblique";
  t["Arial-Italic"] = "Helvetica-Oblique";
  t.ArialMT = "Helvetica";
  t["Arial-BoldItalicMT"] = "Helvetica-BoldOblique";
  t["Arial-BoldMT"] = "Helvetica-Bold";
  t["Arial-ItalicMT"] = "Helvetica-Oblique";
  t["Arial-BoldItalicMT-BoldItalic"] = "Helvetica-BoldOblique";
  t["Arial-BoldMT-Bold"] = "Helvetica-Bold";
  t["Arial-ItalicMT-Italic"] = "Helvetica-Oblique";
  t.ArialUnicodeMS = "Helvetica";
  t["ArialUnicodeMS-Bold"] = "Helvetica-Bold";
  t["ArialUnicodeMS-BoldItalic"] = "Helvetica-BoldOblique";
  t["ArialUnicodeMS-Italic"] = "Helvetica-Oblique";
  t["Courier-BoldItalic"] = "Courier-BoldOblique";
  t["Courier-Italic"] = "Courier-Oblique";
  t.CourierNew = "Courier";
  t["CourierNew-Bold"] = "Courier-Bold";
  t["CourierNew-BoldItalic"] = "Courier-BoldOblique";
  t["CourierNew-Italic"] = "Courier-Oblique";
  t["CourierNewPS-BoldItalicMT"] = "Courier-BoldOblique";
  t["CourierNewPS-BoldMT"] = "Courier-Bold";
  t["CourierNewPS-ItalicMT"] = "Courier-Oblique";
  t.CourierNewPSMT = "Courier";
  t["Helvetica-BoldItalic"] = "Helvetica-BoldOblique";
  t["Helvetica-Italic"] = "Helvetica-Oblique";
  t["Symbol-Bold"] = "Symbol";
  t["Symbol-BoldItalic"] = "Symbol";
  t["Symbol-Italic"] = "Symbol";
  t.TimesNewRoman = "Times-Roman";
  t["TimesNewRoman-Bold"] = "Times-Bold";
  t["TimesNewRoman-BoldItalic"] = "Times-BoldItalic";
  t["TimesNewRoman-Italic"] = "Times-Italic";
  t.TimesNewRomanPS = "Times-Roman";
  t["TimesNewRomanPS-Bold"] = "Times-Bold";
  t["TimesNewRomanPS-BoldItalic"] = "Times-BoldItalic";
  t["TimesNewRomanPS-BoldItalicMT"] = "Times-BoldItalic";
  t["TimesNewRomanPS-BoldMT"] = "Times-Bold";
  t["TimesNewRomanPS-Italic"] = "Times-Italic";
  t["TimesNewRomanPS-ItalicMT"] = "Times-Italic";
  t.TimesNewRomanPSMT = "Times-Roman";
  t["TimesNewRomanPSMT-Bold"] = "Times-Bold";
  t["TimesNewRomanPSMT-BoldItalic"] = "Times-BoldItalic";
  t["TimesNewRomanPSMT-Italic"] = "Times-Italic";
});
const getFontNameToFileMap = getLookupTableFactory(function (t) {
  t.Courier = "FoxitFixed.pfb";
  t["Courier-Bold"] = "FoxitFixedBold.pfb";
  t["Courier-BoldOblique"] = "FoxitFixedBoldItalic.pfb";
  t["Courier-Oblique"] = "FoxitFixedItalic.pfb";
  t.Helvetica = "LiberationSans-Regular.ttf";
  t["Helvetica-Bold"] = "LiberationSans-Bold.ttf";
  t["Helvetica-BoldOblique"] = "LiberationSans-BoldItalic.ttf";
  t["Helvetica-Oblique"] = "LiberationSans-Italic.ttf";
  t["Times-Roman"] = "FoxitSerif.pfb";
  t["Times-Bold"] = "FoxitSerifBold.pfb";
  t["Times-BoldItalic"] = "FoxitSerifBoldItalic.pfb";
  t["Times-Italic"] = "FoxitSerifItalic.pfb";
  t.Symbol = "FoxitSymbol.pfb";
  t.ZapfDingbats = "FoxitDingbats.pfb";
  t["LiberationSans-Regular"] = "LiberationSans-Regular.ttf";
  t["LiberationSans-Bold"] = "LiberationSans-Bold.ttf";
  t["LiberationSans-Italic"] = "LiberationSans-Italic.ttf";
  t["LiberationSans-BoldItalic"] = "LiberationSans-BoldItalic.ttf";
});
const getNonStdFontMap = getLookupTableFactory(function (t) {
  t.Calibri = "Helvetica";
  t["Calibri-Bold"] = "Helvetica-Bold";
  t["Calibri-BoldItalic"] = "Helvetica-BoldOblique";
  t["Calibri-Italic"] = "Helvetica-Oblique";
  t.CenturyGothic = "Helvetica";
  t["CenturyGothic-Bold"] = "Helvetica-Bold";
  t["CenturyGothic-BoldItalic"] = "Helvetica-BoldOblique";
  t["CenturyGothic-Italic"] = "Helvetica-Oblique";
  t.ComicSansMS = "Comic Sans MS";
  t["ComicSansMS-Bold"] = "Comic Sans MS-Bold";
  t["ComicSansMS-BoldItalic"] = "Comic Sans MS-BoldItalic";
  t["ComicSansMS-Italic"] = "Comic Sans MS-Italic";
  t.Impact = "Helvetica";
  t["ItcSymbol-Bold"] = "Helvetica-Bold";
  t["ItcSymbol-BoldItalic"] = "Helvetica-BoldOblique";
  t["ItcSymbol-Book"] = "Helvetica";
  t["ItcSymbol-BookItalic"] = "Helvetica-Oblique";
  t["ItcSymbol-Medium"] = "Helvetica";
  t["ItcSymbol-MediumItalic"] = "Helvetica-Oblique";
  t.LucidaConsole = "Courier";
  t["LucidaConsole-Bold"] = "Courier-Bold";
  t["LucidaConsole-BoldItalic"] = "Courier-BoldOblique";
  t["LucidaConsole-Italic"] = "Courier-Oblique";
  t["LucidaSans-Demi"] = "Helvetica-Bold";
  t["MS-Gothic"] = "MS Gothic";
  t["MS-Gothic-Bold"] = "MS Gothic-Bold";
  t["MS-Gothic-BoldItalic"] = "MS Gothic-BoldItalic";
  t["MS-Gothic-Italic"] = "MS Gothic-Italic";
  t["MS-Mincho"] = "MS Mincho";
  t["MS-Mincho-Bold"] = "MS Mincho-Bold";
  t["MS-Mincho-BoldItalic"] = "MS Mincho-BoldItalic";
  t["MS-Mincho-Italic"] = "MS Mincho-Italic";
  t["MS-PGothic"] = "MS PGothic";
  t["MS-PGothic-Bold"] = "MS PGothic-Bold";
  t["MS-PGothic-BoldItalic"] = "MS PGothic-BoldItalic";
  t["MS-PGothic-Italic"] = "MS PGothic-Italic";
  t["MS-PMincho"] = "MS PMincho";
  t["MS-PMincho-Bold"] = "MS PMincho-Bold";
  t["MS-PMincho-BoldItalic"] = "MS PMincho-BoldItalic";
  t["MS-PMincho-Italic"] = "MS PMincho-Italic";
  t.NuptialScript = "Times-Italic";
  t.SegoeUISymbol = "Helvetica";
});
const getSerifFonts = getLookupTableFactory(function (t) {
  t["Adobe Jenson"] = true;
  t["Adobe Text"] = true;
  t.Albertus = true;
  t.Aldus = true;
  t.Alexandria = true;
  t.Algerian = true;
  t["American Typewriter"] = true;
  t.Antiqua = true;
  t.Apex = true;
  t.Arno = true;
  t.Aster = true;
  t.Aurora = true;
  t.Baskerville = true;
  t.Bell = true;
  t.Bembo = true;
  t["Bembo Schoolbook"] = true;
  t.Benguiat = true;
  t["Berkeley Old Style"] = true;
  t["Bernhard Modern"] = true;
  t["Berthold City"] = true;
  t.Bodoni = true;
  t["Bauer Bodoni"] = true;
  t["Book Antiqua"] = true;
  t.Bookman = true;
  t["Bordeaux Roman"] = true;
  t["Californian FB"] = true;
  t.Calisto = true;
  t.Calvert = true;
  t.Capitals = true;
  t.Cambria = true;
  t.Cartier = true;
  t.Caslon = true;
  t.Catull = true;
  t.Centaur = true;
  t["Century Old Style"] = true;
  t["Century Schoolbook"] = true;
  t.Chaparral = true;
  t["Charis SIL"] = true;
  t.Cheltenham = true;
  t["Cholla Slab"] = true;
  t.Clarendon = true;
  t.Clearface = true;
  t.Cochin = true;
  t.Colonna = true;
  t["Computer Modern"] = true;
  t["Concrete Roman"] = true;
  t.Constantia = true;
  t["Cooper Black"] = true;
  t.Corona = true;
  t.Ecotype = true;
  t.Egyptienne = true;
  t.Elephant = true;
  t.Excelsior = true;
  t.Fairfield = true;
  t["FF Scala"] = true;
  t.Folkard = true;
  t.Footlight = true;
  t.FreeSerif = true;
  t["Friz Quadrata"] = true;
  t.Garamond = true;
  t.Gentium = true;
  t.Georgia = true;
  t.Gloucester = true;
  t["Goudy Old Style"] = true;
  t["Goudy Schoolbook"] = true;
  t["Goudy Pro Font"] = true;
  t.Granjon = true;
  t["Guardian Egyptian"] = true;
  t.Heather = true;
  t.Hercules = true;
  t["High Tower Text"] = true;
  t.Hiroshige = true;
  t["Hoefler Text"] = true;
  t["Humana Serif"] = true;
  t.Imprint = true;
  t["Ionic No. 5"] = true;
  t.Janson = true;
  t.Joanna = true;
  t.Korinna = true;
  t.Lexicon = true;
  t.LiberationSerif = true;
  t["Liberation Serif"] = true;
  t["Linux Libertine"] = true;
  t.Literaturnaya = true;
  t.Lucida = true;
  t["Lucida Bright"] = true;
  t.Melior = true;
  t.Memphis = true;
  t.Miller = true;
  t.Minion = true;
  t.Modern = true;
  t["Mona Lisa"] = true;
  t["Mrs Eaves"] = true;
  t["MS Serif"] = true;
  t["Museo Slab"] = true;
  t["New York"] = true;
  t["Nimbus Roman"] = true;
  t["NPS Rawlinson Roadway"] = true;
  t.NuptialScript = true;
  t.Palatino = true;
  t.Perpetua = true;
  t.Plantin = true;
  t["Plantin Schoolbook"] = true;
  t.Playbill = true;
  t["Poor Richard"] = true;
  t["Rawlinson Roadway"] = true;
  t.Renault = true;
  t.Requiem = true;
  t.Rockwell = true;
  t.Roman = true;
  t["Rotis Serif"] = true;
  t.Sabon = true;
  t.Scala = true;
  t.Seagull = true;
  t.Sistina = true;
  t.Souvenir = true;
  t.STIX = true;
  t["Stone Informal"] = true;
  t["Stone Serif"] = true;
  t.Sylfaen = true;
  t.Times = true;
  t.Trajan = true;
  t["Trinité"] = true;
  t["Trump Mediaeval"] = true;
  t.Utopia = true;
  t["Vale Type"] = true;
  t["Bitstream Vera"] = true;
  t["Vera Serif"] = true;
  t.Versailles = true;
  t.Wanted = true;
  t.Weiss = true;
  t["Wide Latin"] = true;
  t.Windsor = true;
  t.XITS = true;
});
const getSymbolsFonts = getLookupTableFactory(function (t) {
  t.Dingbats = true;
  t.Symbol = true;
  t.ZapfDingbats = true;
  t.Wingdings = true;
  t["Wingdings-Bold"] = true;
  t["Wingdings-Regular"] = true;
});
const getGlyphMapForStandardFonts = getLookupTableFactory(function (t) {
  t[2] = 10;
  t[3] = 32;
  t[4] = 33;
  t[5] = 34;
  t[6] = 35;
  t[7] = 36;
  t[8] = 37;
  t[9] = 38;
  t[10] = 39;
  t[11] = 40;
  t[12] = 41;
  t[13] = 42;
  t[14] = 43;
  t[15] = 44;
  t[16] = 45;
  t[17] = 46;
  t[18] = 47;
  t[19] = 48;
  t[20] = 49;
  t[21] = 50;
  t[22] = 51;
  t[23] = 52;
  t[24] = 53;
  t[25] = 54;
  t[26] = 55;
  t[27] = 56;
  t[28] = 57;
  t[29] = 58;
  t[30] = 894;
  t[31] = 60;
  t[32] = 61;
  t[33] = 62;
  t[34] = 63;
  t[35] = 64;
  t[36] = 65;
  t[37] = 66;
  t[38] = 67;
  t[39] = 68;
  t[40] = 69;
  t[41] = 70;
  t[42] = 71;
  t[43] = 72;
  t[44] = 73;
  t[45] = 74;
  t[46] = 75;
  t[47] = 76;
  t[48] = 77;
  t[49] = 78;
  t[50] = 79;
  t[51] = 80;
  t[52] = 81;
  t[53] = 82;
  t[54] = 83;
  t[55] = 84;
  t[56] = 85;
  t[57] = 86;
  t[58] = 87;
  t[59] = 88;
  t[60] = 89;
  t[61] = 90;
  t[62] = 91;
  t[63] = 92;
  t[64] = 93;
  t[65] = 94;
  t[66] = 95;
  t[67] = 96;
  t[68] = 97;
  t[69] = 98;
  t[70] = 99;
  t[71] = 100;
  t[72] = 101;
  t[73] = 102;
  t[74] = 103;
  t[75] = 104;
  t[76] = 105;
  t[77] = 106;
  t[78] = 107;
  t[79] = 108;
  t[80] = 109;
  t[81] = 110;
  t[82] = 111;
  t[83] = 112;
  t[84] = 113;
  t[85] = 114;
  t[86] = 115;
  t[87] = 116;
  t[88] = 117;
  t[89] = 118;
  t[90] = 119;
  t[91] = 120;
  t[92] = 121;
  t[93] = 122;
  t[94] = 123;
  t[95] = 124;
  t[96] = 125;
  t[97] = 126;
  t[98] = 196;
  t[99] = 197;
  t[100] = 199;
  t[101] = 201;
  t[102] = 209;
  t[103] = 214;
  t[104] = 220;
  t[105] = 225;
  t[106] = 224;
  t[107] = 226;
  t[108] = 228;
  t[109] = 227;
  t[110] = 229;
  t[111] = 231;
  t[112] = 233;
  t[113] = 232;
  t[114] = 234;
  t[115] = 235;
  t[116] = 237;
  t[117] = 236;
  t[118] = 238;
  t[119] = 239;
  t[120] = 241;
  t[121] = 243;
  t[122] = 242;
  t[123] = 244;
  t[124] = 246;
  t[125] = 245;
  t[126] = 250;
  t[127] = 249;
  t[128] = 251;
  t[129] = 252;
  t[130] = 8224;
  t[131] = 176;
  t[132] = 162;
  t[133] = 163;
  t[134] = 167;
  t[135] = 8226;
  t[136] = 182;
  t[137] = 223;
  t[138] = 174;
  t[139] = 169;
  t[140] = 8482;
  t[141] = 180;
  t[142] = 168;
  t[143] = 8800;
  t[144] = 198;
  t[145] = 216;
  t[146] = 8734;
  t[147] = 177;
  t[148] = 8804;
  t[149] = 8805;
  t[150] = 165;
  t[151] = 181;
  t[152] = 8706;
  t[153] = 8721;
  t[154] = 8719;
  t[156] = 8747;
  t[157] = 170;
  t[158] = 186;
  t[159] = 8486;
  t[160] = 230;
  t[161] = 248;
  t[162] = 191;
  t[163] = 161;
  t[164] = 172;
  t[165] = 8730;
  t[166] = 402;
  t[167] = 8776;
  t[168] = 8710;
  t[169] = 171;
  t[170] = 187;
  t[171] = 8230;
  t[179] = 8220;
  t[180] = 8221;
  t[181] = 8216;
  t[182] = 8217;
  t[200] = 193;
  t[203] = 205;
  t[207] = 211;
  t[210] = 218;
  t[223] = 711;
  t[224] = 321;
  t[225] = 322;
  t[226] = 352;
  t[227] = 353;
  t[228] = 381;
  t[229] = 382;
  t[233] = 221;
  t[234] = 253;
  t[252] = 263;
  t[253] = 268;
  t[254] = 269;
  t[258] = 258;
  t[260] = 260;
  t[261] = 261;
  t[265] = 280;
  t[266] = 281;
  t[267] = 282;
  t[268] = 283;
  t[269] = 313;
  t[275] = 323;
  t[276] = 324;
  t[278] = 328;
  t[283] = 344;
  t[284] = 345;
  t[285] = 346;
  t[286] = 347;
  t[292] = 367;
  t[295] = 377;
  t[296] = 378;
  t[298] = 380;
  t[305] = 963;
  t[306] = 964;
  t[307] = 966;
  t[308] = 8215;
  t[309] = 8252;
  t[310] = 8319;
  t[311] = 8359;
  t[312] = 8592;
  t[313] = 8593;
  t[337] = 9552;
  t[493] = 1039;
  t[494] = 1040;
  t[672] = 1488;
  t[673] = 1489;
  t[674] = 1490;
  t[675] = 1491;
  t[676] = 1492;
  t[677] = 1493;
  t[678] = 1494;
  t[679] = 1495;
  t[680] = 1496;
  t[681] = 1497;
  t[682] = 1498;
  t[683] = 1499;
  t[684] = 1500;
  t[685] = 1501;
  t[686] = 1502;
  t[687] = 1503;
  t[688] = 1504;
  t[689] = 1505;
  t[690] = 1506;
  t[691] = 1507;
  t[692] = 1508;
  t[693] = 1509;
  t[694] = 1510;
  t[695] = 1511;
  t[696] = 1512;
  t[697] = 1513;
  t[698] = 1514;
  t[705] = 1524;
  t[706] = 8362;
  t[710] = 64288;
  t[711] = 64298;
  t[759] = 1617;
  t[761] = 1776;
  t[763] = 1778;
  t[775] = 1652;
  t[777] = 1764;
  t[778] = 1780;
  t[779] = 1781;
  t[780] = 1782;
  t[782] = 771;
  t[783] = 64726;
  t[786] = 8363;
  t[788] = 8532;
  t[790] = 768;
  t[791] = 769;
  t[792] = 768;
  t[795] = 803;
  t[797] = 64336;
  t[798] = 64337;
  t[799] = 64342;
  t[800] = 64343;
  t[801] = 64344;
  t[802] = 64345;
  t[803] = 64362;
  t[804] = 64363;
  t[805] = 64364;
  t[2424] = 7821;
  t[2425] = 7822;
  t[2426] = 7823;
  t[2427] = 7824;
  t[2428] = 7825;
  t[2429] = 7826;
  t[2430] = 7827;
  t[2433] = 7682;
  t[2678] = 8045;
  t[2679] = 8046;
  t[2830] = 1552;
  t[2838] = 686;
  t[2840] = 751;
  t[2842] = 753;
  t[2843] = 754;
  t[2844] = 755;
  t[2846] = 757;
  t[2856] = 767;
  t[2857] = 848;
  t[2858] = 849;
  t[2862] = 853;
  t[2863] = 854;
  t[2864] = 855;
  t[2865] = 861;
  t[2866] = 862;
  t[2906] = 7460;
  t[2908] = 7462;
  t[2909] = 7463;
  t[2910] = 7464;
  t[2912] = 7466;
  t[2913] = 7467;
  t[2914] = 7468;
  t[2916] = 7470;
  t[2917] = 7471;
  t[2918] = 7472;
  t[2920] = 7474;
  t[2921] = 7475;
  t[2922] = 7476;
  t[2924] = 7478;
  t[2925] = 7479;
  t[2926] = 7480;
  t[2928] = 7482;
  t[2929] = 7483;
  t[2930] = 7484;
  t[2932] = 7486;
  t[2933] = 7487;
  t[2934] = 7488;
  t[2936] = 7490;
  t[2937] = 7491;
  t[2938] = 7492;
  t[2940] = 7494;
  t[2941] = 7495;
  t[2942] = 7496;
  t[2944] = 7498;
  t[2946] = 7500;
  t[2948] = 7502;
  t[2950] = 7504;
  t[2951] = 7505;
  t[2952] = 7506;
  t[2954] = 7508;
  t[2955] = 7509;
  t[2956] = 7510;
  t[2958] = 7512;
  t[2959] = 7513;
  t[2960] = 7514;
  t[2962] = 7516;
  t[2963] = 7517;
  t[2964] = 7518;
  t[2966] = 7520;
  t[2967] = 7521;
  t[2968] = 7522;
  t[2970] = 7524;
  t[2971] = 7525;
  t[2972] = 7526;
  t[2974] = 7528;
  t[2975] = 7529;
  t[2976] = 7530;
  t[2978] = 1537;
  t[2979] = 1538;
  t[2980] = 1539;
  t[2982] = 1549;
  t[2983] = 1551;
  t[2984] = 1552;
  t[2986] = 1554;
  t[2987] = 1555;
  t[2988] = 1556;
  t[2990] = 1623;
  t[2991] = 1624;
  t[2995] = 1775;
  t[2999] = 1791;
  t[3002] = 64290;
  t[3003] = 64291;
  t[3004] = 64292;
  t[3006] = 64294;
  t[3007] = 64295;
  t[3008] = 64296;
  t[3011] = 1900;
  t[3014] = 8223;
  t[3015] = 8244;
  t[3017] = 7532;
  t[3018] = 7533;
  t[3019] = 7534;
  t[3075] = 7590;
  t[3076] = 7591;
  t[3079] = 7594;
  t[3080] = 7595;
  t[3083] = 7598;
  t[3084] = 7599;
  t[3087] = 7602;
  t[3088] = 7603;
  t[3091] = 7606;
  t[3092] = 7607;
  t[3095] = 7610;
  t[3096] = 7611;
  t[3099] = 7614;
  t[3100] = 7615;
  t[3103] = 7618;
  t[3104] = 7619;
  t[3107] = 8337;
  t[3108] = 8338;
  t[3116] = 1884;
  t[3119] = 1885;
  t[3120] = 1885;
  t[3123] = 1886;
  t[3124] = 1886;
  t[3127] = 1887;
  t[3128] = 1887;
  t[3131] = 1888;
  t[3132] = 1888;
  t[3135] = 1889;
  t[3136] = 1889;
  t[3139] = 1890;
  t[3140] = 1890;
  t[3143] = 1891;
  t[3144] = 1891;
  t[3147] = 1892;
  t[3148] = 1892;
  t[3153] = 580;
  t[3154] = 581;
  t[3157] = 584;
  t[3158] = 585;
  t[3161] = 588;
  t[3162] = 589;
  t[3165] = 891;
  t[3166] = 892;
  t[3169] = 1274;
  t[3170] = 1275;
  t[3173] = 1278;
  t[3174] = 1279;
  t[3181] = 7622;
  t[3182] = 7623;
  t[3282] = 11799;
  t[3316] = 578;
  t[3379] = 42785;
  t[3393] = 1159;
  t[3416] = 8377;
});
const getSupplementalGlyphMapForArialBlack = getLookupTableFactory(function (t) {
  t[227] = 322;
  t[264] = 261;
  t[291] = 346;
});
const getSupplementalGlyphMapForCalibri = getLookupTableFactory(function (t) {
  t[1] = 32;
  t[4] = 65;
  t[5] = 192;
  t[6] = 193;
  t[9] = 196;
  t[17] = 66;
  t[18] = 67;
  t[21] = 268;
  t[24] = 68;
  t[28] = 69;
  t[29] = 200;
  t[30] = 201;
  t[32] = 282;
  t[38] = 70;
  t[39] = 71;
  t[44] = 72;
  t[47] = 73;
  t[48] = 204;
  t[49] = 205;
  t[58] = 74;
  t[60] = 75;
  t[62] = 76;
  t[68] = 77;
  t[69] = 78;
  t[75] = 79;
  t[76] = 210;
  t[80] = 214;
  t[87] = 80;
  t[89] = 81;
  t[90] = 82;
  t[92] = 344;
  t[94] = 83;
  t[97] = 352;
  t[100] = 84;
  t[104] = 85;
  t[109] = 220;
  t[115] = 86;
  t[116] = 87;
  t[121] = 88;
  t[122] = 89;
  t[124] = 221;
  t[127] = 90;
  t[129] = 381;
  t[258] = 97;
  t[259] = 224;
  t[260] = 225;
  t[263] = 228;
  t[268] = 261;
  t[271] = 98;
  t[272] = 99;
  t[273] = 263;
  t[275] = 269;
  t[282] = 100;
  t[286] = 101;
  t[287] = 232;
  t[288] = 233;
  t[290] = 283;
  t[295] = 281;
  t[296] = 102;
  t[336] = 103;
  t[346] = 104;
  t[349] = 105;
  t[350] = 236;
  t[351] = 237;
  t[361] = 106;
  t[364] = 107;
  t[367] = 108;
  t[371] = 322;
  t[373] = 109;
  t[374] = 110;
  t[381] = 111;
  t[382] = 242;
  t[383] = 243;
  t[386] = 246;
  t[393] = 112;
  t[395] = 113;
  t[396] = 114;
  t[398] = 345;
  t[400] = 115;
  t[401] = 347;
  t[403] = 353;
  t[410] = 116;
  t[437] = 117;
  t[442] = 252;
  t[448] = 118;
  t[449] = 119;
  t[454] = 120;
  t[455] = 121;
  t[457] = 253;
  t[460] = 122;
  t[462] = 382;
  t[463] = 380;
  t[853] = 44;
  t[855] = 58;
  t[856] = 46;
  t[876] = 47;
  t[878] = 45;
  t[882] = 45;
  t[894] = 40;
  t[895] = 41;
  t[896] = 91;
  t[897] = 93;
  t[923] = 64;
  t[1004] = 48;
  t[1005] = 49;
  t[1006] = 50;
  t[1007] = 51;
  t[1008] = 52;
  t[1009] = 53;
  t[1010] = 54;
  t[1011] = 55;
  t[1012] = 56;
  t[1013] = 57;
  t[1081] = 37;
  t[1085] = 43;
  t[1086] = 45;
});
function getStandardFontName(name) {
  const fontName = normalizeFontName(name);
  const stdFontMap = getStdFontMap();
  return stdFontMap[fontName];
}
function isKnownFontName(name) {
  const fontName = normalizeFontName(name);
  return !!(getStdFontMap()[fontName] || getNonStdFontMap()[fontName] || getSerifFonts()[fontName] || getSymbolsFonts()[fontName]);
}

;// CONCATENATED MODULE: ./src/core/to_unicode_map.js

class ToUnicodeMap {
  constructor(cmap = []) {
    this._map = cmap;
  }
  get length() {
    return this._map.length;
  }
  forEach(callback) {
    for (const charCode in this._map) {
      callback(charCode, this._map[charCode].charCodeAt(0));
    }
  }
  has(i) {
    return this._map[i] !== undefined;
  }
  get(i) {
    return this._map[i];
  }
  charCodeOf(value) {
    const map = this._map;
    if (map.length <= 0x10000) {
      return map.indexOf(value);
    }
    for (const charCode in map) {
      if (map[charCode] === value) {
        return charCode | 0;
      }
    }
    return -1;
  }
  amend(map) {
    for (const charCode in map) {
      this._map[charCode] = map[charCode];
    }
  }
}
class IdentityToUnicodeMap {
  constructor(firstChar, lastChar) {
    this.firstChar = firstChar;
    this.lastChar = lastChar;
  }
  get length() {
    return this.lastChar + 1 - this.firstChar;
  }
  forEach(callback) {
    for (let i = this.firstChar, ii = this.lastChar; i <= ii; i++) {
      callback(i, i);
    }
  }
  has(i) {
    return this.firstChar <= i && i <= this.lastChar;
  }
  get(i) {
    if (this.firstChar <= i && i <= this.lastChar) {
      return String.fromCharCode(i);
    }
    return undefined;
  }
  charCodeOf(v) {
    return Number.isInteger(v) && v >= this.firstChar && v <= this.lastChar ? v : -1;
  }
  amend(map) {
    unreachable("Should not call amend()");
  }
}

;// CONCATENATED MODULE: ./src/core/cff_font.js



class CFFFont {
  constructor(file, properties) {
    this.properties = properties;
    const parser = new CFFParser(file, properties, SEAC_ANALYSIS_ENABLED);
    this.cff = parser.parse();
    this.cff.duplicateFirstGlyph();
    const compiler = new CFFCompiler(this.cff);
    this.seacs = this.cff.seacs;
    try {
      this.data = compiler.compile();
    } catch {
      warn("Failed to compile font " + properties.loadedName);
      this.data = file;
    }
    this._createBuiltInEncoding();
  }
  get numGlyphs() {
    return this.cff.charStrings.count;
  }
  getCharset() {
    return this.cff.charset.charset;
  }
  getGlyphMapping() {
    const cff = this.cff;
    const properties = this.properties;
    const {
      cidToGidMap,
      cMap
    } = properties;
    const charsets = cff.charset.charset;
    let charCodeToGlyphId;
    let glyphId;
    if (properties.composite) {
      let invCidToGidMap;
      if (cidToGidMap?.length > 0) {
        invCidToGidMap = Object.create(null);
        for (let i = 0, ii = cidToGidMap.length; i < ii; i++) {
          const gid = cidToGidMap[i];
          if (gid !== undefined) {
            invCidToGidMap[gid] = i;
          }
        }
      }
      charCodeToGlyphId = Object.create(null);
      let charCode;
      if (cff.isCIDFont) {
        for (glyphId = 0; glyphId < charsets.length; glyphId++) {
          const cid = charsets[glyphId];
          charCode = cMap.charCodeOf(cid);
          if (invCidToGidMap?.[charCode] !== undefined) {
            charCode = invCidToGidMap[charCode];
          }
          charCodeToGlyphId[charCode] = glyphId;
        }
      } else {
        for (glyphId = 0; glyphId < cff.charStrings.count; glyphId++) {
          charCode = cMap.charCodeOf(glyphId);
          charCodeToGlyphId[charCode] = glyphId;
        }
      }
      return charCodeToGlyphId;
    }
    let encoding = cff.encoding ? cff.encoding.encoding : null;
    if (properties.isInternalFont) {
      encoding = properties.defaultEncoding;
    }
    charCodeToGlyphId = type1FontGlyphMapping(properties, encoding, charsets);
    return charCodeToGlyphId;
  }
  hasGlyphId(id) {
    return this.cff.hasGlyphId(id);
  }
  _createBuiltInEncoding() {
    const {
      charset,
      encoding
    } = this.cff;
    if (!charset || !encoding) {
      return;
    }
    const charsets = charset.charset,
      encodings = encoding.encoding;
    const map = [];
    for (const charCode in encodings) {
      const glyphId = encodings[charCode];
      if (glyphId >= 0) {
        const glyphName = charsets[glyphId];
        if (glyphName) {
          map[charCode] = glyphName;
        }
      }
    }
    if (map.length > 0) {
      this.properties.builtInEncoding = map;
    }
  }
}

;// CONCATENATED MODULE: ./src/core/font_renderer.js






function getUint32(data, offset) {
  return (data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3]) >>> 0;
}
function getUint16(data, offset) {
  return data[offset] << 8 | data[offset + 1];
}
function getInt16(data, offset) {
  return (data[offset] << 24 | data[offset + 1] << 16) >> 16;
}
function getInt8(data, offset) {
  return data[offset] << 24 >> 24;
}
function getFloat214(data, offset) {
  return getInt16(data, offset) / 16384;
}
function getSubroutineBias(subrs) {
  const numSubrs = subrs.length;
  let bias = 32768;
  if (numSubrs < 1240) {
    bias = 107;
  } else if (numSubrs < 33900) {
    bias = 1131;
  }
  return bias;
}
function parseCmap(data, start, end) {
  const offset = getUint16(data, start + 2) === 1 ? getUint32(data, start + 8) : getUint32(data, start + 16);
  const format = getUint16(data, start + offset);
  let ranges, p, i;
  if (format === 4) {
    getUint16(data, start + offset + 2);
    const segCount = getUint16(data, start + offset + 6) >> 1;
    p = start + offset + 14;
    ranges = [];
    for (i = 0; i < segCount; i++, p += 2) {
      ranges[i] = {
        end: getUint16(data, p)
      };
    }
    p += 2;
    for (i = 0; i < segCount; i++, p += 2) {
      ranges[i].start = getUint16(data, p);
    }
    for (i = 0; i < segCount; i++, p += 2) {
      ranges[i].idDelta = getUint16(data, p);
    }
    for (i = 0; i < segCount; i++, p += 2) {
      let idOffset = getUint16(data, p);
      if (idOffset === 0) {
        continue;
      }
      ranges[i].ids = [];
      for (let j = 0, jj = ranges[i].end - ranges[i].start + 1; j < jj; j++) {
        ranges[i].ids[j] = getUint16(data, p + idOffset);
        idOffset += 2;
      }
    }
    return ranges;
  } else if (format === 12) {
    const groups = getUint32(data, start + offset + 12);
    p = start + offset + 16;
    ranges = [];
    for (i = 0; i < groups; i++) {
      start = getUint32(data, p);
      ranges.push({
        start,
        end: getUint32(data, p + 4),
        idDelta: getUint32(data, p + 8) - start
      });
      p += 12;
    }
    return ranges;
  }
  throw new FormatError(`unsupported cmap: ${format}`);
}
function parseCff(data, start, end, seacAnalysisEnabled) {
  const properties = {};
  const parser = new CFFParser(new Stream(data, start, end - start), properties, seacAnalysisEnabled);
  const cff = parser.parse();
  return {
    glyphs: cff.charStrings.objects,
    subrs: cff.topDict.privateDict?.subrsIndex?.objects,
    gsubrs: cff.globalSubrIndex?.objects,
    isCFFCIDFont: cff.isCIDFont,
    fdSelect: cff.fdSelect,
    fdArray: cff.fdArray
  };
}
function parseGlyfTable(glyf, loca, isGlyphLocationsLong) {
  let itemSize, itemDecode;
  if (isGlyphLocationsLong) {
    itemSize = 4;
    itemDecode = getUint32;
  } else {
    itemSize = 2;
    itemDecode = (data, offset) => 2 * getUint16(data, offset);
  }
  const glyphs = [];
  let startOffset = itemDecode(loca, 0);
  for (let j = itemSize; j < loca.length; j += itemSize) {
    const endOffset = itemDecode(loca, j);
    glyphs.push(glyf.subarray(startOffset, endOffset));
    startOffset = endOffset;
  }
  return glyphs;
}
function lookupCmap(ranges, unicode) {
  const code = unicode.codePointAt(0);
  let gid = 0,
    l = 0,
    r = ranges.length - 1;
  while (l < r) {
    const c = l + r + 1 >> 1;
    if (code < ranges[c].start) {
      r = c - 1;
    } else {
      l = c;
    }
  }
  if (ranges[l].start <= code && code <= ranges[l].end) {
    gid = ranges[l].idDelta + (ranges[l].ids ? ranges[l].ids[code - ranges[l].start] : code) & 0xffff;
  }
  return {
    charCode: code,
    glyphId: gid
  };
}
function compileGlyf(code, cmds, font) {
  function moveTo(x, y) {
    cmds.add(FontRenderOps.MOVE_TO, [x, y]);
  }
  function lineTo(x, y) {
    cmds.add(FontRenderOps.LINE_TO, [x, y]);
  }
  function quadraticCurveTo(xa, ya, x, y) {
    cmds.add(FontRenderOps.QUADRATIC_CURVE_TO, [xa, ya, x, y]);
  }
  let i = 0;
  const numberOfContours = getInt16(code, i);
  let flags;
  let x = 0,
    y = 0;
  i += 10;
  if (numberOfContours < 0) {
    do {
      flags = getUint16(code, i);
      const glyphIndex = getUint16(code, i + 2);
      i += 4;
      let arg1, arg2;
      if (flags & 0x01) {
        if (flags & 0x02) {
          arg1 = getInt16(code, i);
          arg2 = getInt16(code, i + 2);
        } else {
          arg1 = getUint16(code, i);
          arg2 = getUint16(code, i + 2);
        }
        i += 4;
      } else if (flags & 0x02) {
        arg1 = getInt8(code, i++);
        arg2 = getInt8(code, i++);
      } else {
        arg1 = code[i++];
        arg2 = code[i++];
      }
      if (flags & 0x02) {
        x = arg1;
        y = arg2;
      } else {
        x = 0;
        y = 0;
      }
      let scaleX = 1,
        scaleY = 1,
        scale01 = 0,
        scale10 = 0;
      if (flags & 0x08) {
        scaleX = scaleY = getFloat214(code, i);
        i += 2;
      } else if (flags & 0x40) {
        scaleX = getFloat214(code, i);
        scaleY = getFloat214(code, i + 2);
        i += 4;
      } else if (flags & 0x80) {
        scaleX = getFloat214(code, i);
        scale01 = getFloat214(code, i + 2);
        scale10 = getFloat214(code, i + 4);
        scaleY = getFloat214(code, i + 6);
        i += 8;
      }
      const subglyph = font.glyphs[glyphIndex];
      if (subglyph) {
        cmds.add(FontRenderOps.SAVE);
        cmds.add(FontRenderOps.TRANSFORM, [scaleX, scale01, scale10, scaleY, x, y]);
        if (!(flags & 0x02)) {}
        compileGlyf(subglyph, cmds, font);
        cmds.add(FontRenderOps.RESTORE);
      }
    } while (flags & 0x20);
  } else {
    const endPtsOfContours = [];
    let j, jj;
    for (j = 0; j < numberOfContours; j++) {
      endPtsOfContours.push(getUint16(code, i));
      i += 2;
    }
    const instructionLength = getUint16(code, i);
    i += 2 + instructionLength;
    const numberOfPoints = endPtsOfContours.at(-1) + 1;
    const points = [];
    while (points.length < numberOfPoints) {
      flags = code[i++];
      let repeat = 1;
      if (flags & 0x08) {
        repeat += code[i++];
      }
      while (repeat-- > 0) {
        points.push({
          flags
        });
      }
    }
    for (j = 0; j < numberOfPoints; j++) {
      switch (points[j].flags & 0x12) {
        case 0x00:
          x += getInt16(code, i);
          i += 2;
          break;
        case 0x02:
          x -= code[i++];
          break;
        case 0x12:
          x += code[i++];
          break;
      }
      points[j].x = x;
    }
    for (j = 0; j < numberOfPoints; j++) {
      switch (points[j].flags & 0x24) {
        case 0x00:
          y += getInt16(code, i);
          i += 2;
          break;
        case 0x04:
          y -= code[i++];
          break;
        case 0x24:
          y += code[i++];
          break;
      }
      points[j].y = y;
    }
    let startPoint = 0;
    for (i = 0; i < numberOfContours; i++) {
      const endPoint = endPtsOfContours[i];
      const contour = points.slice(startPoint, endPoint + 1);
      if (contour[0].flags & 1) {
        contour.push(contour[0]);
      } else if (contour.at(-1).flags & 1) {
        contour.unshift(contour.at(-1));
      } else {
        const p = {
          flags: 1,
          x: (contour[0].x + contour.at(-1).x) / 2,
          y: (contour[0].y + contour.at(-1).y) / 2
        };
        contour.unshift(p);
        contour.push(p);
      }
      moveTo(contour[0].x, contour[0].y);
      for (j = 1, jj = contour.length; j < jj; j++) {
        if (contour[j].flags & 1) {
          lineTo(contour[j].x, contour[j].y);
        } else if (contour[j + 1].flags & 1) {
          quadraticCurveTo(contour[j].x, contour[j].y, contour[j + 1].x, contour[j + 1].y);
          j++;
        } else {
          quadraticCurveTo(contour[j].x, contour[j].y, (contour[j].x + contour[j + 1].x) / 2, (contour[j].y + contour[j + 1].y) / 2);
        }
      }
      startPoint = endPoint + 1;
    }
  }
}
function compileCharString(charStringCode, cmds, font, glyphId) {
  function moveTo(x, y) {
    cmds.add(FontRenderOps.MOVE_TO, [x, y]);
  }
  function lineTo(x, y) {
    cmds.add(FontRenderOps.LINE_TO, [x, y]);
  }
  function bezierCurveTo(x1, y1, x2, y2, x, y) {
    cmds.add(FontRenderOps.BEZIER_CURVE_TO, [x1, y1, x2, y2, x, y]);
  }
  const stack = [];
  let x = 0,
    y = 0;
  let stems = 0;
  function parse(code) {
    let i = 0;
    while (i < code.length) {
      let stackClean = false;
      let v = code[i++];
      let xa, xb, ya, yb, y1, y2, y3, n, subrCode;
      switch (v) {
        case 1:
          stems += stack.length >> 1;
          stackClean = true;
          break;
        case 3:
          stems += stack.length >> 1;
          stackClean = true;
          break;
        case 4:
          y += stack.pop();
          moveTo(x, y);
          stackClean = true;
          break;
        case 5:
          while (stack.length > 0) {
            x += stack.shift();
            y += stack.shift();
            lineTo(x, y);
          }
          break;
        case 6:
          while (stack.length > 0) {
            x += stack.shift();
            lineTo(x, y);
            if (stack.length === 0) {
              break;
            }
            y += stack.shift();
            lineTo(x, y);
          }
          break;
        case 7:
          while (stack.length > 0) {
            y += stack.shift();
            lineTo(x, y);
            if (stack.length === 0) {
              break;
            }
            x += stack.shift();
            lineTo(x, y);
          }
          break;
        case 8:
          while (stack.length > 0) {
            xa = x + stack.shift();
            ya = y + stack.shift();
            xb = xa + stack.shift();
            yb = ya + stack.shift();
            x = xb + stack.shift();
            y = yb + stack.shift();
            bezierCurveTo(xa, ya, xb, yb, x, y);
          }
          break;
        case 10:
          n = stack.pop();
          subrCode = null;
          if (font.isCFFCIDFont) {
            const fdIndex = font.fdSelect.getFDIndex(glyphId);
            if (fdIndex >= 0 && fdIndex < font.fdArray.length) {
              const fontDict = font.fdArray[fdIndex];
              let subrs;
              if (fontDict.privateDict?.subrsIndex) {
                subrs = fontDict.privateDict.subrsIndex.objects;
              }
              if (subrs) {
                n += getSubroutineBias(subrs);
                subrCode = subrs[n];
              }
            } else {
              warn("Invalid fd index for glyph index.");
            }
          } else {
            subrCode = font.subrs[n + font.subrsBias];
          }
          if (subrCode) {
            parse(subrCode);
          }
          break;
        case 11:
          return;
        case 12:
          v = code[i++];
          switch (v) {
            case 34:
              xa = x + stack.shift();
              xb = xa + stack.shift();
              y1 = y + stack.shift();
              x = xb + stack.shift();
              bezierCurveTo(xa, y, xb, y1, x, y1);
              xa = x + stack.shift();
              xb = xa + stack.shift();
              x = xb + stack.shift();
              bezierCurveTo(xa, y1, xb, y, x, y);
              break;
            case 35:
              xa = x + stack.shift();
              ya = y + stack.shift();
              xb = xa + stack.shift();
              yb = ya + stack.shift();
              x = xb + stack.shift();
              y = yb + stack.shift();
              bezierCurveTo(xa, ya, xb, yb, x, y);
              xa = x + stack.shift();
              ya = y + stack.shift();
              xb = xa + stack.shift();
              yb = ya + stack.shift();
              x = xb + stack.shift();
              y = yb + stack.shift();
              bezierCurveTo(xa, ya, xb, yb, x, y);
              stack.pop();
              break;
            case 36:
              xa = x + stack.shift();
              y1 = y + stack.shift();
              xb = xa + stack.shift();
              y2 = y1 + stack.shift();
              x = xb + stack.shift();
              bezierCurveTo(xa, y1, xb, y2, x, y2);
              xa = x + stack.shift();
              xb = xa + stack.shift();
              y3 = y2 + stack.shift();
              x = xb + stack.shift();
              bezierCurveTo(xa, y2, xb, y3, x, y);
              break;
            case 37:
              const x0 = x,
                y0 = y;
              xa = x + stack.shift();
              ya = y + stack.shift();
              xb = xa + stack.shift();
              yb = ya + stack.shift();
              x = xb + stack.shift();
              y = yb + stack.shift();
              bezierCurveTo(xa, ya, xb, yb, x, y);
              xa = x + stack.shift();
              ya = y + stack.shift();
              xb = xa + stack.shift();
              yb = ya + stack.shift();
              x = xb;
              y = yb;
              if (Math.abs(x - x0) > Math.abs(y - y0)) {
                x += stack.shift();
              } else {
                y += stack.shift();
              }
              bezierCurveTo(xa, ya, xb, yb, x, y);
              break;
            default:
              throw new FormatError(`unknown operator: 12 ${v}`);
          }
          break;
        case 14:
          if (stack.length >= 4) {
            const achar = stack.pop();
            const bchar = stack.pop();
            y = stack.pop();
            x = stack.pop();
            cmds.add(FontRenderOps.SAVE);
            cmds.add(FontRenderOps.TRANSLATE, [x, y]);
            let cmap = lookupCmap(font.cmap, String.fromCharCode(font.glyphNameMap[StandardEncoding[achar]]));
            compileCharString(font.glyphs[cmap.glyphId], cmds, font, cmap.glyphId);
            cmds.add(FontRenderOps.RESTORE);
            cmap = lookupCmap(font.cmap, String.fromCharCode(font.glyphNameMap[StandardEncoding[bchar]]));
            compileCharString(font.glyphs[cmap.glyphId], cmds, font, cmap.glyphId);
          }
          return;
        case 18:
          stems += stack.length >> 1;
          stackClean = true;
          break;
        case 19:
          stems += stack.length >> 1;
          i += stems + 7 >> 3;
          stackClean = true;
          break;
        case 20:
          stems += stack.length >> 1;
          i += stems + 7 >> 3;
          stackClean = true;
          break;
        case 21:
          y += stack.pop();
          x += stack.pop();
          moveTo(x, y);
          stackClean = true;
          break;
        case 22:
          x += stack.pop();
          moveTo(x, y);
          stackClean = true;
          break;
        case 23:
          stems += stack.length >> 1;
          stackClean = true;
          break;
        case 24:
          while (stack.length > 2) {
            xa = x + stack.shift();
            ya = y + stack.shift();
            xb = xa + stack.shift();
            yb = ya + stack.shift();
            x = xb + stack.shift();
            y = yb + stack.shift();
            bezierCurveTo(xa, ya, xb, yb, x, y);
          }
          x += stack.shift();
          y += stack.shift();
          lineTo(x, y);
          break;
        case 25:
          while (stack.length > 6) {
            x += stack.shift();
            y += stack.shift();
            lineTo(x, y);
          }
          xa = x + stack.shift();
          ya = y + stack.shift();
          xb = xa + stack.shift();
          yb = ya + stack.shift();
          x = xb + stack.shift();
          y = yb + stack.shift();
          bezierCurveTo(xa, ya, xb, yb, x, y);
          break;
        case 26:
          if (stack.length % 2) {
            x += stack.shift();
          }
          while (stack.length > 0) {
            xa = x;
            ya = y + stack.shift();
            xb = xa + stack.shift();
            yb = ya + stack.shift();
            x = xb;
            y = yb + stack.shift();
            bezierCurveTo(xa, ya, xb, yb, x, y);
          }
          break;
        case 27:
          if (stack.length % 2) {
            y += stack.shift();
          }
          while (stack.length > 0) {
            xa = x + stack.shift();
            ya = y;
            xb = xa + stack.shift();
            yb = ya + stack.shift();
            x = xb + stack.shift();
            y = yb;
            bezierCurveTo(xa, ya, xb, yb, x, y);
          }
          break;
        case 28:
          stack.push((code[i] << 24 | code[i + 1] << 16) >> 16);
          i += 2;
          break;
        case 29:
          n = stack.pop() + font.gsubrsBias;
          subrCode = font.gsubrs[n];
          if (subrCode) {
            parse(subrCode);
          }
          break;
        case 30:
          while (stack.length > 0) {
            xa = x;
            ya = y + stack.shift();
            xb = xa + stack.shift();
            yb = ya + stack.shift();
            x = xb + stack.shift();
            y = yb + (stack.length === 1 ? stack.shift() : 0);
            bezierCurveTo(xa, ya, xb, yb, x, y);
            if (stack.length === 0) {
              break;
            }
            xa = x + stack.shift();
            ya = y;
            xb = xa + stack.shift();
            yb = ya + stack.shift();
            y = yb + stack.shift();
            x = xb + (stack.length === 1 ? stack.shift() : 0);
            bezierCurveTo(xa, ya, xb, yb, x, y);
          }
          break;
        case 31:
          while (stack.length > 0) {
            xa = x + stack.shift();
            ya = y;
            xb = xa + stack.shift();
            yb = ya + stack.shift();
            y = yb + stack.shift();
            x = xb + (stack.length === 1 ? stack.shift() : 0);
            bezierCurveTo(xa, ya, xb, yb, x, y);
            if (stack.length === 0) {
              break;
            }
            xa = x;
            ya = y + stack.shift();
            xb = xa + stack.shift();
            yb = ya + stack.shift();
            x = xb + stack.shift();
            y = yb + (stack.length === 1 ? stack.shift() : 0);
            bezierCurveTo(xa, ya, xb, yb, x, y);
          }
          break;
        default:
          if (v < 32) {
            throw new FormatError(`unknown operator: ${v}`);
          }
          if (v < 247) {
            stack.push(v - 139);
          } else if (v < 251) {
            stack.push((v - 247) * 256 + code[i++] + 108);
          } else if (v < 255) {
            stack.push(-(v - 251) * 256 - code[i++] - 108);
          } else {
            stack.push((code[i] << 24 | code[i + 1] << 16 | code[i + 2] << 8 | code[i + 3]) / 65536);
            i += 4;
          }
          break;
      }
      if (stackClean) {
        stack.length = 0;
      }
    }
  }
  parse(charStringCode);
}
const NOOP = [];
class Commands {
  cmds = [];
  add(cmd, args) {
    if (args) {
      if (!isNumberArray(args, null)) {
        warn(`Commands.add - "${cmd}" has at least one non-number arg: "${args}".`);
        const newArgs = args.map(arg => typeof arg === "number" ? arg : 0);
        this.cmds.push(cmd, ...newArgs);
      } else {
        this.cmds.push(cmd, ...args);
      }
    } else {
      this.cmds.push(cmd);
    }
  }
}
class CompiledFont {
  constructor(fontMatrix) {
    this.fontMatrix = fontMatrix;
    this.compiledGlyphs = Object.create(null);
    this.compiledCharCodeToGlyphId = Object.create(null);
  }
  getPathJs(unicode) {
    const {
      charCode,
      glyphId
    } = lookupCmap(this.cmap, unicode);
    let fn = this.compiledGlyphs[glyphId],
      compileEx;
    if (!fn) {
      try {
        fn = this.compileGlyph(this.glyphs[glyphId], glyphId);
      } catch (ex) {
        fn = NOOP;
        compileEx = ex;
      }
      this.compiledGlyphs[glyphId] = fn;
    }
    this.compiledCharCodeToGlyphId[charCode] ??= glyphId;
    if (compileEx) {
      throw compileEx;
    }
    return fn;
  }
  compileGlyph(code, glyphId) {
    if (!code || code.length === 0 || code[0] === 14) {
      return NOOP;
    }
    let fontMatrix = this.fontMatrix;
    if (this.isCFFCIDFont) {
      const fdIndex = this.fdSelect.getFDIndex(glyphId);
      if (fdIndex >= 0 && fdIndex < this.fdArray.length) {
        const fontDict = this.fdArray[fdIndex];
        fontMatrix = fontDict.getByName("FontMatrix") || FONT_IDENTITY_MATRIX;
      } else {
        warn("Invalid fd index for glyph index.");
      }
    }
    const cmds = new Commands();
    cmds.add(FontRenderOps.SAVE);
    cmds.add(FontRenderOps.TRANSFORM, fontMatrix.slice());
    cmds.add(FontRenderOps.SCALE);
    this.compileGlyphImpl(code, cmds, glyphId);
    cmds.add(FontRenderOps.RESTORE);
    return cmds.cmds;
  }
  compileGlyphImpl() {
    unreachable("Children classes should implement this.");
  }
  hasBuiltPath(unicode) {
    const {
      charCode,
      glyphId
    } = lookupCmap(this.cmap, unicode);
    return this.compiledGlyphs[glyphId] !== undefined && this.compiledCharCodeToGlyphId[charCode] !== undefined;
  }
}
class TrueTypeCompiled extends CompiledFont {
  constructor(glyphs, cmap, fontMatrix) {
    super(fontMatrix || [0.000488, 0, 0, 0.000488, 0, 0]);
    this.glyphs = glyphs;
    this.cmap = cmap;
  }
  compileGlyphImpl(code, cmds) {
    compileGlyf(code, cmds, this);
  }
}
class Type2Compiled extends CompiledFont {
  constructor(cffInfo, cmap, fontMatrix, glyphNameMap) {
    super(fontMatrix || [0.001, 0, 0, 0.001, 0, 0]);
    this.glyphs = cffInfo.glyphs;
    this.gsubrs = cffInfo.gsubrs || [];
    this.subrs = cffInfo.subrs || [];
    this.cmap = cmap;
    this.glyphNameMap = glyphNameMap || getGlyphsUnicode();
    this.gsubrsBias = getSubroutineBias(this.gsubrs);
    this.subrsBias = getSubroutineBias(this.subrs);
    this.isCFFCIDFont = cffInfo.isCFFCIDFont;
    this.fdSelect = cffInfo.fdSelect;
    this.fdArray = cffInfo.fdArray;
  }
  compileGlyphImpl(code, cmds, glyphId) {
    compileCharString(code, cmds, this, glyphId);
  }
}
class FontRendererFactory {
  static create(font, seacAnalysisEnabled) {
    const data = new Uint8Array(font.data);
    let cmap, glyf, loca, cff, indexToLocFormat, unitsPerEm;
    const numTables = getUint16(data, 4);
    for (let i = 0, p = 12; i < numTables; i++, p += 16) {
      const tag = bytesToString(data.subarray(p, p + 4));
      const offset = getUint32(data, p + 8);
      const length = getUint32(data, p + 12);
      switch (tag) {
        case "cmap":
          cmap = parseCmap(data, offset, offset + length);
          break;
        case "glyf":
          glyf = data.subarray(offset, offset + length);
          break;
        case "loca":
          loca = data.subarray(offset, offset + length);
          break;
        case "head":
          unitsPerEm = getUint16(data, offset + 18);
          indexToLocFormat = getUint16(data, offset + 50);
          break;
        case "CFF ":
          cff = parseCff(data, offset, offset + length, seacAnalysisEnabled);
          break;
      }
    }
    if (glyf) {
      const fontMatrix = !unitsPerEm ? font.fontMatrix : [1 / unitsPerEm, 0, 0, 1 / unitsPerEm, 0, 0];
      return new TrueTypeCompiled(parseGlyfTable(glyf, loca, indexToLocFormat), cmap, fontMatrix);
    }
    return new Type2Compiled(cff, cmap, font.fontMatrix, font.glyphNameMap);
  }
}

;// CONCATENATED MODULE: ./src/core/metrics.js

const getMetrics = getLookupTableFactory(function (t) {
  t.Courier = 600;
  t["Courier-Bold"] = 600;
  t["Courier-BoldOblique"] = 600;
  t["Courier-Oblique"] = 600;
  t.Helvetica = getLookupTableFactory(function (t) {
    t.space = 278;
    t.exclam = 278;
    t.quotedbl = 355;
    t.numbersign = 556;
    t.dollar = 556;
    t.percent = 889;
    t.ampersand = 667;
    t.quoteright = 222;
    t.parenleft = 333;
    t.parenright = 333;
    t.asterisk = 389;
    t.plus = 584;
    t.comma = 278;
    t.hyphen = 333;
    t.period = 278;
    t.slash = 278;
    t.zero = 556;
    t.one = 556;
    t.two = 556;
    t.three = 556;
    t.four = 556;
    t.five = 556;
    t.six = 556;
    t.seven = 556;
    t.eight = 556;
    t.nine = 556;
    t.colon = 278;
    t.semicolon = 278;
    t.less = 584;
    t.equal = 584;
    t.greater = 584;
    t.question = 556;
    t.at = 1015;
    t.A = 667;
    t.B = 667;
    t.C = 722;
    t.D = 722;
    t.E = 667;
    t.F = 611;
    t.G = 778;
    t.H = 722;
    t.I = 278;
    t.J = 500;
    t.K = 667;
    t.L = 556;
    t.M = 833;
    t.N = 722;
    t.O = 778;
    t.P = 667;
    t.Q = 778;
    t.R = 722;
    t.S = 667;
    t.T = 611;
    t.U = 722;
    t.V = 667;
    t.W = 944;
    t.X = 667;
    t.Y = 667;
    t.Z = 611;
    t.bracketleft = 278;
    t.backslash = 278;
    t.bracketright = 278;
    t.asciicircum = 469;
    t.underscore = 556;
    t.quoteleft = 222;
    t.a = 556;
    t.b = 556;
    t.c = 500;
    t.d = 556;
    t.e = 556;
    t.f = 278;
    t.g = 556;
    t.h = 556;
    t.i = 222;
    t.j = 222;
    t.k = 500;
    t.l = 222;
    t.m = 833;
    t.n = 556;
    t.o = 556;
    t.p = 556;
    t.q = 556;
    t.r = 333;
    t.s = 500;
    t.t = 278;
    t.u = 556;
    t.v = 500;
    t.w = 722;
    t.x = 500;
    t.y = 500;
    t.z = 500;
    t.braceleft = 334;
    t.bar = 260;
    t.braceright = 334;
    t.asciitilde = 584;
    t.exclamdown = 333;
    t.cent = 556;
    t.sterling = 556;
    t.fraction = 167;
    t.yen = 556;
    t.florin = 556;
    t.section = 556;
    t.currency = 556;
    t.quotesingle = 191;
    t.quotedblleft = 333;
    t.guillemotleft = 556;
    t.guilsinglleft = 333;
    t.guilsinglright = 333;
    t.fi = 500;
    t.fl = 500;
    t.endash = 556;
    t.dagger = 556;
    t.daggerdbl = 556;
    t.periodcentered = 278;
    t.paragraph = 537;
    t.bullet = 350;
    t.quotesinglbase = 222;
    t.quotedblbase = 333;
    t.quotedblright = 333;
    t.guillemotright = 556;
    t.ellipsis = 1000;
    t.perthousand = 1000;
    t.questiondown = 611;
    t.grave = 333;
    t.acute = 333;
    t.circumflex = 333;
    t.tilde = 333;
    t.macron = 333;
    t.breve = 333;
    t.dotaccent = 333;
    t.dieresis = 333;
    t.ring = 333;
    t.cedilla = 333;
    t.hungarumlaut = 333;
    t.ogonek = 333;
    t.caron = 333;
    t.emdash = 1000;
    t.AE = 1000;
    t.ordfeminine = 370;
    t.Lslash = 556;
    t.Oslash = 778;
    t.OE = 1000;
    t.ordmasculine = 365;
    t.ae = 889;
    t.dotlessi = 278;
    t.lslash = 222;
    t.oslash = 611;
    t.oe = 944;
    t.germandbls = 611;
    t.Idieresis = 278;
    t.eacute = 556;
    t.abreve = 556;
    t.uhungarumlaut = 556;
    t.ecaron = 556;
    t.Ydieresis = 667;
    t.divide = 584;
    t.Yacute = 667;
    t.Acircumflex = 667;
    t.aacute = 556;
    t.Ucircumflex = 722;
    t.yacute = 500;
    t.scommaaccent = 500;
    t.ecircumflex = 556;
    t.Uring = 722;
    t.Udieresis = 722;
    t.aogonek = 556;
    t.Uacute = 722;
    t.uogonek = 556;
    t.Edieresis = 667;
    t.Dcroat = 722;
    t.commaaccent = 250;
    t.copyright = 737;
    t.Emacron = 667;
    t.ccaron = 500;
    t.aring = 556;
    t.Ncommaaccent = 722;
    t.lacute = 222;
    t.agrave = 556;
    t.Tcommaaccent = 611;
    t.Cacute = 722;
    t.atilde = 556;
    t.Edotaccent = 667;
    t.scaron = 500;
    t.scedilla = 500;
    t.iacute = 278;
    t.lozenge = 471;
    t.Rcaron = 722;
    t.Gcommaaccent = 778;
    t.ucircumflex = 556;
    t.acircumflex = 556;
    t.Amacron = 667;
    t.rcaron = 333;
    t.ccedilla = 500;
    t.Zdotaccent = 611;
    t.Thorn = 667;
    t.Omacron = 778;
    t.Racute = 722;
    t.Sacute = 667;
    t.dcaron = 643;
    t.Umacron = 722;
    t.uring = 556;
    t.threesuperior = 333;
    t.Ograve = 778;
    t.Agrave = 667;
    t.Abreve = 667;
    t.multiply = 584;
    t.uacute = 556;
    t.Tcaron = 611;
    t.partialdiff = 476;
    t.ydieresis = 500;
    t.Nacute = 722;
    t.icircumflex = 278;
    t.Ecircumflex = 667;
    t.adieresis = 556;
    t.edieresis = 556;
    t.cacute = 500;
    t.nacute = 556;
    t.umacron = 556;
    t.Ncaron = 722;
    t.Iacute = 278;
    t.plusminus = 584;
    t.brokenbar = 260;
    t.registered = 737;
    t.Gbreve = 778;
    t.Idotaccent = 278;
    t.summation = 600;
    t.Egrave = 667;
    t.racute = 333;
    t.omacron = 556;
    t.Zacute = 611;
    t.Zcaron = 611;
    t.greaterequal = 549;
    t.Eth = 722;
    t.Ccedilla = 722;
    t.lcommaaccent = 222;
    t.tcaron = 317;
    t.eogonek = 556;
    t.Uogonek = 722;
    t.Aacute = 667;
    t.Adieresis = 667;
    t.egrave = 556;
    t.zacute = 500;
    t.iogonek = 222;
    t.Oacute = 778;
    t.oacute = 556;
    t.amacron = 556;
    t.sacute = 500;
    t.idieresis = 278;
    t.Ocircumflex = 778;
    t.Ugrave = 722;
    t.Delta = 612;
    t.thorn = 556;
    t.twosuperior = 333;
    t.Odieresis = 778;
    t.mu = 556;
    t.igrave = 278;
    t.ohungarumlaut = 556;
    t.Eogonek = 667;
    t.dcroat = 556;
    t.threequarters = 834;
    t.Scedilla = 667;
    t.lcaron = 299;
    t.Kcommaaccent = 667;
    t.Lacute = 556;
    t.trademark = 1000;
    t.edotaccent = 556;
    t.Igrave = 278;
    t.Imacron = 278;
    t.Lcaron = 556;
    t.onehalf = 834;
    t.lessequal = 549;
    t.ocircumflex = 556;
    t.ntilde = 556;
    t.Uhungarumlaut = 722;
    t.Eacute = 667;
    t.emacron = 556;
    t.gbreve = 556;
    t.onequarter = 834;
    t.Scaron = 667;
    t.Scommaaccent = 667;
    t.Ohungarumlaut = 778;
    t.degree = 400;
    t.ograve = 556;
    t.Ccaron = 722;
    t.ugrave = 556;
    t.radical = 453;
    t.Dcaron = 722;
    t.rcommaaccent = 333;
    t.Ntilde = 722;
    t.otilde = 556;
    t.Rcommaaccent = 722;
    t.Lcommaaccent = 556;
    t.Atilde = 667;
    t.Aogonek = 667;
    t.Aring = 667;
    t.Otilde = 778;
    t.zdotaccent = 500;
    t.Ecaron = 667;
    t.Iogonek = 278;
    t.kcommaaccent = 500;
    t.minus = 584;
    t.Icircumflex = 278;
    t.ncaron = 556;
    t.tcommaaccent = 278;
    t.logicalnot = 584;
    t.odieresis = 556;
    t.udieresis = 556;
    t.notequal = 549;
    t.gcommaaccent = 556;
    t.eth = 556;
    t.zcaron = 500;
    t.ncommaaccent = 556;
    t.onesuperior = 333;
    t.imacron = 278;
    t.Euro = 556;
  });
  t["Helvetica-Bold"] = getLookupTableFactory(function (t) {
    t.space = 278;
    t.exclam = 333;
    t.quotedbl = 474;
    t.numbersign = 556;
    t.dollar = 556;
    t.percent = 889;
    t.ampersand = 722;
    t.quoteright = 278;
    t.parenleft = 333;
    t.parenright = 333;
    t.asterisk = 389;
    t.plus = 584;
    t.comma = 278;
    t.hyphen = 333;
    t.period = 278;
    t.slash = 278;
    t.zero = 556;
    t.one = 556;
    t.two = 556;
    t.three = 556;
    t.four = 556;
    t.five = 556;
    t.six = 556;
    t.seven = 556;
    t.eight = 556;
    t.nine = 556;
    t.colon = 333;
    t.semicolon = 333;
    t.less = 584;
    t.equal = 584;
    t.greater = 584;
    t.question = 611;
    t.at = 975;
    t.A = 722;
    t.B = 722;
    t.C = 722;
    t.D = 722;
    t.E = 667;
    t.F = 611;
    t.G = 778;
    t.H = 722;
    t.I = 278;
    t.J = 556;
    t.K = 722;
    t.L = 611;
    t.M = 833;
    t.N = 722;
    t.O = 778;
    t.P = 667;
    t.Q = 778;
    t.R = 722;
    t.S = 667;
    t.T = 611;
    t.U = 722;
    t.V = 667;
    t.W = 944;
    t.X = 667;
    t.Y = 667;
    t.Z = 611;
    t.bracketleft = 333;
    t.backslash = 278;
    t.bracketright = 333;
    t.asciicircum = 584;
    t.underscore = 556;
    t.quoteleft = 278;
    t.a = 556;
    t.b = 611;
    t.c = 556;
    t.d = 611;
    t.e = 556;
    t.f = 333;
    t.g = 611;
    t.h = 611;
    t.i = 278;
    t.j = 278;
    t.k = 556;
    t.l = 278;
    t.m = 889;
    t.n = 611;
    t.o = 611;
    t.p = 611;
    t.q = 611;
    t.r = 389;
    t.s = 556;
    t.t = 333;
    t.u = 611;
    t.v = 556;
    t.w = 778;
    t.x = 556;
    t.y = 556;
    t.z = 500;
    t.braceleft = 389;
    t.bar = 280;
    t.braceright = 389;
    t.asciitilde = 584;
    t.exclamdown = 333;
    t.cent = 556;
    t.sterling = 556;
    t.fraction = 167;
    t.yen = 556;
    t.florin = 556;
    t.section = 556;
    t.currency = 556;
    t.quotesingle = 238;
    t.quotedblleft = 500;
    t.guillemotleft = 556;
    t.guilsinglleft = 333;
    t.guilsinglright = 333;
    t.fi = 611;
    t.fl = 611;
    t.endash = 556;
    t.dagger = 556;
    t.daggerdbl = 556;
    t.periodcentered = 278;
    t.paragraph = 556;
    t.bullet = 350;
    t.quotesinglbase = 278;
    t.quotedblbase = 500;
    t.quotedblright = 500;
    t.guillemotright = 556;
    t.ellipsis = 1000;
    t.perthousand = 1000;
    t.questiondown = 611;
    t.grave = 333;
    t.acute = 333;
    t.circumflex = 333;
    t.tilde = 333;
    t.macron = 333;
    t.breve = 333;
    t.dotaccent = 333;
    t.dieresis = 333;
    t.ring = 333;
    t.cedilla = 333;
    t.hungarumlaut = 333;
    t.ogonek = 333;
    t.caron = 333;
    t.emdash = 1000;
    t.AE = 1000;
    t.ordfeminine = 370;
    t.Lslash = 611;
    t.Oslash = 778;
    t.OE = 1000;
    t.ordmasculine = 365;
    t.ae = 889;
    t.dotlessi = 278;
    t.lslash = 278;
    t.oslash = 611;
    t.oe = 944;
    t.germandbls = 611;
    t.Idieresis = 278;
    t.eacute = 556;
    t.abreve = 556;
    t.uhungarumlaut = 611;
    t.ecaron = 556;
    t.Ydieresis = 667;
    t.divide = 584;
    t.Yacute = 667;
    t.Acircumflex = 722;
    t.aacute = 556;
    t.Ucircumflex = 722;
    t.yacute = 556;
    t.scommaaccent = 556;
    t.ecircumflex = 556;
    t.Uring = 722;
    t.Udieresis = 722;
    t.aogonek = 556;
    t.Uacute = 722;
    t.uogonek = 611;
    t.Edieresis = 667;
    t.Dcroat = 722;
    t.commaaccent = 250;
    t.copyright = 737;
    t.Emacron = 667;
    t.ccaron = 556;
    t.aring = 556;
    t.Ncommaaccent = 722;
    t.lacute = 278;
    t.agrave = 556;
    t.Tcommaaccent = 611;
    t.Cacute = 722;
    t.atilde = 556;
    t.Edotaccent = 667;
    t.scaron = 556;
    t.scedilla = 556;
    t.iacute = 278;
    t.lozenge = 494;
    t.Rcaron = 722;
    t.Gcommaaccent = 778;
    t.ucircumflex = 611;
    t.acircumflex = 556;
    t.Amacron = 722;
    t.rcaron = 389;
    t.ccedilla = 556;
    t.Zdotaccent = 611;
    t.Thorn = 667;
    t.Omacron = 778;
    t.Racute = 722;
    t.Sacute = 667;
    t.dcaron = 743;
    t.Umacron = 722;
    t.uring = 611;
    t.threesuperior = 333;
    t.Ograve = 778;
    t.Agrave = 722;
    t.Abreve = 722;
    t.multiply = 584;
    t.uacute = 611;
    t.Tcaron = 611;
    t.partialdiff = 494;
    t.ydieresis = 556;
    t.Nacute = 722;
    t.icircumflex = 278;
    t.Ecircumflex = 667;
    t.adieresis = 556;
    t.edieresis = 556;
    t.cacute = 556;
    t.nacute = 611;
    t.umacron = 611;
    t.Ncaron = 722;
    t.Iacute = 278;
    t.plusminus = 584;
    t.brokenbar = 280;
    t.registered = 737;
    t.Gbreve = 778;
    t.Idotaccent = 278;
    t.summation = 600;
    t.Egrave = 667;
    t.racute = 389;
    t.omacron = 611;
    t.Zacute = 611;
    t.Zcaron = 611;
    t.greaterequal = 549;
    t.Eth = 722;
    t.Ccedilla = 722;
    t.lcommaaccent = 278;
    t.tcaron = 389;
    t.eogonek = 556;
    t.Uogonek = 722;
    t.Aacute = 722;
    t.Adieresis = 722;
    t.egrave = 556;
    t.zacute = 500;
    t.iogonek = 278;
    t.Oacute = 778;
    t.oacute = 611;
    t.amacron = 556;
    t.sacute = 556;
    t.idieresis = 278;
    t.Ocircumflex = 778;
    t.Ugrave = 722;
    t.Delta = 612;
    t.thorn = 611;
    t.twosuperior = 333;
    t.Odieresis = 778;
    t.mu = 611;
    t.igrave = 278;
    t.ohungarumlaut = 611;
    t.Eogonek = 667;
    t.dcroat = 611;
    t.threequarters = 834;
    t.Scedilla = 667;
    t.lcaron = 400;
    t.Kcommaaccent = 722;
    t.Lacute = 611;
    t.trademark = 1000;
    t.edotaccent = 556;
    t.Igrave = 278;
    t.Imacron = 278;
    t.Lcaron = 611;
    t.onehalf = 834;
    t.lessequal = 549;
    t.ocircumflex = 611;
    t.ntilde = 611;
    t.Uhungarumlaut = 722;
    t.Eacute = 667;
    t.emacron = 556;
    t.gbreve = 611;
    t.onequarter = 834;
    t.Scaron = 667;
    t.Scommaaccent = 667;
    t.Ohungarumlaut = 778;
    t.degree = 400;
    t.ograve = 611;
    t.Ccaron = 722;
    t.ugrave = 611;
    t.radical = 549;
    t.Dcaron = 722;
    t.rcommaaccent = 389;
    t.Ntilde = 722;
    t.otilde = 611;
    t.Rcommaaccent = 722;
    t.Lcommaaccent = 611;
    t.Atilde = 722;
    t.Aogonek = 722;
    t.Aring = 722;
    t.Otilde = 778;
    t.zdotaccent = 500;
    t.Ecaron = 667;
    t.Iogonek = 278;
    t.kcommaaccent = 556;
    t.minus = 584;
    t.Icircumflex = 278;
    t.ncaron = 611;
    t.tcommaaccent = 333;
    t.logicalnot = 584;
    t.odieresis = 611;
    t.udieresis = 611;
    t.notequal = 549;
    t.gcommaaccent = 611;
    t.eth = 611;
    t.zcaron = 500;
    t.ncommaaccent = 611;
    t.onesuperior = 333;
    t.imacron = 278;
    t.Euro = 556;
  });
  t["Helvetica-BoldOblique"] = getLookupTableFactory(function (t) {
    t.space = 278;
    t.exclam = 333;
    t.quotedbl = 474;
    t.numbersign = 556;
    t.dollar = 556;
    t.percent = 889;
    t.ampersand = 722;
    t.quoteright = 278;
    t.parenleft = 333;
    t.parenright = 333;
    t.asterisk = 389;
    t.plus = 584;
    t.comma = 278;
    t.hyphen = 333;
    t.period = 278;
    t.slash = 278;
    t.zero = 556;
    t.one = 556;
    t.two = 556;
    t.three = 556;
    t.four = 556;
    t.five = 556;
    t.six = 556;
    t.seven = 556;
    t.eight = 556;
    t.nine = 556;
    t.colon = 333;
    t.semicolon = 333;
    t.less = 584;
    t.equal = 584;
    t.greater = 584;
    t.question = 611;
    t.at = 975;
    t.A = 722;
    t.B = 722;
    t.C = 722;
    t.D = 722;
    t.E = 667;
    t.F = 611;
    t.G = 778;
    t.H = 722;
    t.I = 278;
    t.J = 556;
    t.K = 722;
    t.L = 611;
    t.M = 833;
    t.N = 722;
    t.O = 778;
    t.P = 667;
    t.Q = 778;
    t.R = 722;
    t.S = 667;
    t.T = 611;
    t.U = 722;
    t.V = 667;
    t.W = 944;
    t.X = 667;
    t.Y = 667;
    t.Z = 611;
    t.bracketleft = 333;
    t.backslash = 278;
    t.bracketright = 333;
    t.asciicircum = 584;
    t.underscore = 556;
    t.quoteleft = 278;
    t.a = 556;
    t.b = 611;
    t.c = 556;
    t.d = 611;
    t.e = 556;
    t.f = 333;
    t.g = 611;
    t.h = 611;
    t.i = 278;
    t.j = 278;
    t.k = 556;
    t.l = 278;
    t.m = 889;
    t.n = 611;
    t.o = 611;
    t.p = 611;
    t.q = 611;
    t.r = 389;
    t.s = 556;
    t.t = 333;
    t.u = 611;
    t.v = 556;
    t.w = 778;
    t.x = 556;
    t.y = 556;
    t.z = 500;
    t.braceleft = 389;
    t.bar = 280;
    t.braceright = 389;
    t.asciitilde = 584;
    t.exclamdown = 333;
    t.cent = 556;
    t.sterling = 556;
    t.fraction = 167;
    t.yen = 556;
    t.florin = 556;
    t.section = 556;
    t.currency = 556;
    t.quotesingle = 238;
    t.quotedblleft = 500;
    t.guillemotleft = 556;
    t.guilsinglleft = 333;
    t.guilsinglright = 333;
    t.fi = 611;
    t.fl = 611;
    t.endash = 556;
    t.dagger = 556;
    t.daggerdbl = 556;
    t.periodcentered = 278;
    t.paragraph = 556;
    t.bullet = 350;
    t.quotesinglbase = 278;
    t.quotedblbase = 500;
    t.quotedblright = 500;
    t.guillemotright = 556;
    t.ellipsis = 1000;
    t.perthousand = 1000;
    t.questiondown = 611;
    t.grave = 333;
    t.acute = 333;
    t.circumflex = 333;
    t.tilde = 333;
    t.macron = 333;
    t.breve = 333;
    t.dotaccent = 333;
    t.dieresis = 333;
    t.ring = 333;
    t.cedilla = 333;
    t.hungarumlaut = 333;
    t.ogonek = 333;
    t.caron = 333;
    t.emdash = 1000;
    t.AE = 1000;
    t.ordfeminine = 370;
    t.Lslash = 611;
    t.Oslash = 778;
    t.OE = 1000;
    t.ordmasculine = 365;
    t.ae = 889;
    t.dotlessi = 278;
    t.lslash = 278;
    t.oslash = 611;
    t.oe = 944;
    t.germandbls = 611;
    t.Idieresis = 278;
    t.eacute = 556;
    t.abreve = 556;
    t.uhungarumlaut = 611;
    t.ecaron = 556;
    t.Ydieresis = 667;
    t.divide = 584;
    t.Yacute = 667;
    t.Acircumflex = 722;
    t.aacute = 556;
    t.Ucircumflex = 722;
    t.yacute = 556;
    t.scommaaccent = 556;
    t.ecircumflex = 556;
    t.Uring = 722;
    t.Udieresis = 722;
    t.aogonek = 556;
    t.Uacute = 722;
    t.uogonek = 611;
    t.Edieresis = 667;
    t.Dcroat = 722;
    t.commaaccent = 250;
    t.copyright = 737;
    t.Emacron = 667;
    t.ccaron = 556;
    t.aring = 556;
    t.Ncommaaccent = 722;
    t.lacute = 278;
    t.agrave = 556;
    t.Tcommaaccent = 611;
    t.Cacute = 722;
    t.atilde = 556;
    t.Edotaccent = 667;
    t.scaron = 556;
    t.scedilla = 556;
    t.iacute = 278;
    t.lozenge = 494;
    t.Rcaron = 722;
    t.Gcommaaccent = 778;
    t.ucircumflex = 611;
    t.acircumflex = 556;
    t.Amacron = 722;
    t.rcaron = 389;
    t.ccedilla = 556;
    t.Zdotaccent = 611;
    t.Thorn = 667;
    t.Omacron = 778;
    t.Racute = 722;
    t.Sacute = 667;
    t.dcaron = 743;
    t.Umacron = 722;
    t.uring = 611;
    t.threesuperior = 333;
    t.Ograve = 778;
    t.Agrave = 722;
    t.Abreve = 722;
    t.multiply = 584;
    t.uacute = 611;
    t.Tcaron = 611;
    t.partialdiff = 494;
    t.ydieresis = 556;
    t.Nacute = 722;
    t.icircumflex = 278;
    t.Ecircumflex = 667;
    t.adieresis = 556;
    t.edieresis = 556;
    t.cacute = 556;
    t.nacute = 611;
    t.umacron = 611;
    t.Ncaron = 722;
    t.Iacute = 278;
    t.plusminus = 584;
    t.brokenbar = 280;
    t.registered = 737;
    t.Gbreve = 778;
    t.Idotaccent = 278;
    t.summation = 600;
    t.Egrave = 667;
    t.racute = 389;
    t.omacron = 611;
    t.Zacute = 611;
    t.Zcaron = 611;
    t.greaterequal = 549;
    t.Eth = 722;
    t.Ccedilla = 722;
    t.lcommaaccent = 278;
    t.tcaron = 389;
    t.eogonek = 556;
    t.Uogonek = 722;
    t.Aacute = 722;
    t.Adieresis = 722;
    t.egrave = 556;
    t.zacute = 500;
    t.iogonek = 278;
    t.Oacute = 778;
    t.oacute = 611;
    t.amacron = 556;
    t.sacute = 556;
    t.idieresis = 278;
    t.Ocircumflex = 778;
    t.Ugrave = 722;
    t.Delta = 612;
    t.thorn = 611;
    t.twosuperior = 333;
    t.Odieresis = 778;
    t.mu = 611;
    t.igrave = 278;
    t.ohungarumlaut = 611;
    t.Eogonek = 667;
    t.dcroat = 611;
    t.threequarters = 834;
    t.Scedilla = 667;
    t.lcaron = 400;
    t.Kcommaaccent = 722;
    t.Lacute = 611;
    t.trademark = 1000;
    t.edotaccent = 556;
    t.Igrave = 278;
    t.Imacron = 278;
    t.Lcaron = 611;
    t.onehalf = 834;
    t.lessequal = 549;
    t.ocircumflex = 611;
    t.ntilde = 611;
    t.Uhungarumlaut = 722;
    t.Eacute = 667;
    t.emacron = 556;
    t.gbreve = 611;
    t.onequarter = 834;
    t.Scaron = 667;
    t.Scommaaccent = 667;
    t.Ohungarumlaut = 778;
    t.degree = 400;
    t.ograve = 611;
    t.Ccaron = 722;
    t.ugrave = 611;
    t.radical = 549;
    t.Dcaron = 722;
    t.rcommaaccent = 389;
    t.Ntilde = 722;
    t.otilde = 611;
    t.Rcommaaccent = 722;
    t.Lcommaaccent = 611;
    t.Atilde = 722;
    t.Aogonek = 722;
    t.Aring = 722;
    t.Otilde = 778;
    t.zdotaccent = 500;
    t.Ecaron = 667;
    t.Iogonek = 278;
    t.kcommaaccent = 556;
    t.minus = 584;
    t.Icircumflex = 278;
    t.ncaron = 611;
    t.tcommaaccent = 333;
    t.logicalnot = 584;
    t.odieresis = 611;
    t.udieresis = 611;
    t.notequal = 549;
    t.gcommaaccent = 611;
    t.eth = 611;
    t.zcaron = 500;
    t.ncommaaccent = 611;
    t.onesuperior = 333;
    t.imacron = 278;
    t.Euro = 556;
  });
  t["Helvetica-Oblique"] = getLookupTableFactory(function (t) {
    t.space = 278;
    t.exclam = 278;
    t.quotedbl = 355;
    t.numbersign = 556;
    t.dollar = 556;
    t.percent = 889;
    t.ampersand = 667;
    t.quoteright = 222;
    t.parenleft = 333;
    t.parenright = 333;
    t.asterisk = 389;
    t.plus = 584;
    t.comma = 278;
    t.hyphen = 333;
    t.period = 278;
    t.slash = 278;
    t.zero = 556;
    t.one = 556;
    t.two = 556;
    t.three = 556;
    t.four = 556;
    t.five = 556;
    t.six = 556;
    t.seven = 556;
    t.eight = 556;
    t.nine = 556;
    t.colon = 278;
    t.semicolon = 278;
    t.less = 584;
    t.equal = 584;
    t.greater = 584;
    t.question = 556;
    t.at = 1015;
    t.A = 667;
    t.B = 667;
    t.C = 722;
    t.D = 722;
    t.E = 667;
    t.F = 611;
    t.G = 778;
    t.H = 722;
    t.I = 278;
    t.J = 500;
    t.K = 667;
    t.L = 556;
    t.M = 833;
    t.N = 722;
    t.O = 778;
    t.P = 667;
    t.Q = 778;
    t.R = 722;
    t.S = 667;
    t.T = 611;
    t.U = 722;
    t.V = 667;
    t.W = 944;
    t.X = 667;
    t.Y = 667;
    t.Z = 611;
    t.bracketleft = 278;
    t.backslash = 278;
    t.bracketright = 278;
    t.asciicircum = 469;
    t.underscore = 556;
    t.quoteleft = 222;
    t.a = 556;
    t.b = 556;
    t.c = 500;
    t.d = 556;
    t.e = 556;
    t.f = 278;
    t.g = 556;
    t.h = 556;
    t.i = 222;
    t.j = 222;
    t.k = 500;
    t.l = 222;
    t.m = 833;
    t.n = 556;
    t.o = 556;
    t.p = 556;
    t.q = 556;
    t.r = 333;
    t.s = 500;
    t.t = 278;
    t.u = 556;
    t.v = 500;
    t.w = 722;
    t.x = 500;
    t.y = 500;
    t.z = 500;
    t.braceleft = 334;
    t.bar = 260;
    t.braceright = 334;
    t.asciitilde = 584;
    t.exclamdown = 333;
    t.cent = 556;
    t.sterling = 556;
    t.fraction = 167;
    t.yen = 556;
    t.florin = 556;
    t.section = 556;
    t.currency = 556;
    t.quotesingle = 191;
    t.quotedblleft = 333;
    t.guillemotleft = 556;
    t.guilsinglleft = 333;
    t.guilsinglright = 333;
    t.fi = 500;
    t.fl = 500;
    t.endash = 556;
    t.dagger = 556;
    t.daggerdbl = 556;
    t.periodcentered = 278;
    t.paragraph = 537;
    t.bullet = 350;
    t.quotesinglbase = 222;
    t.quotedblbase = 333;
    t.quotedblright = 333;
    t.guillemotright = 556;
    t.ellipsis = 1000;
    t.perthousand = 1000;
    t.questiondown = 611;
    t.grave = 333;
    t.acute = 333;
    t.circumflex = 333;
    t.tilde = 333;
    t.macron = 333;
    t.breve = 333;
    t.dotaccent = 333;
    t.dieresis = 333;
    t.ring = 333;
    t.cedilla = 333;
    t.hungarumlaut = 333;
    t.ogonek = 333;
    t.caron = 333;
    t.emdash = 1000;
    t.AE = 1000;
    t.ordfeminine = 370;
    t.Lslash = 556;
    t.Oslash = 778;
    t.OE = 1000;
    t.ordmasculine = 365;
    t.ae = 889;
    t.dotlessi = 278;
    t.lslash = 222;
    t.oslash = 611;
    t.oe = 944;
    t.germandbls = 611;
    t.Idieresis = 278;
    t.eacute = 556;
    t.abreve = 556;
    t.uhungarumlaut = 556;
    t.ecaron = 556;
    t.Ydieresis = 667;
    t.divide = 584;
    t.Yacute = 667;
    t.Acircumflex = 667;
    t.aacute = 556;
    t.Ucircumflex = 722;
    t.yacute = 500;
    t.scommaaccent = 500;
    t.ecircumflex = 556;
    t.Uring = 722;
    t.Udieresis = 722;
    t.aogonek = 556;
    t.Uacute = 722;
    t.uogonek = 556;
    t.Edieresis = 667;
    t.Dcroat = 722;
    t.commaaccent = 250;
    t.copyright = 737;
    t.Emacron = 667;
    t.ccaron = 500;
    t.aring = 556;
    t.Ncommaaccent = 722;
    t.lacute = 222;
    t.agrave = 556;
    t.Tcommaaccent = 611;
    t.Cacute = 722;
    t.atilde = 556;
    t.Edotaccent = 667;
    t.scaron = 500;
    t.scedilla = 500;
    t.iacute = 278;
    t.lozenge = 471;
    t.Rcaron = 722;
    t.Gcommaaccent = 778;
    t.ucircumflex = 556;
    t.acircumflex = 556;
    t.Amacron = 667;
    t.rcaron = 333;
    t.ccedilla = 500;
    t.Zdotaccent = 611;
    t.Thorn = 667;
    t.Omacron = 778;
    t.Racute = 722;
    t.Sacute = 667;
    t.dcaron = 643;
    t.Umacron = 722;
    t.uring = 556;
    t.threesuperior = 333;
    t.Ograve = 778;
    t.Agrave = 667;
    t.Abreve = 667;
    t.multiply = 584;
    t.uacute = 556;
    t.Tcaron = 611;
    t.partialdiff = 476;
    t.ydieresis = 500;
    t.Nacute = 722;
    t.icircumflex = 278;
    t.Ecircumflex = 667;
    t.adieresis = 556;
    t.edieresis = 556;
    t.cacute = 500;
    t.nacute = 556;
    t.umacron = 556;
    t.Ncaron = 722;
    t.Iacute = 278;
    t.plusminus = 584;
    t.brokenbar = 260;
    t.registered = 737;
    t.Gbreve = 778;
    t.Idotaccent = 278;
    t.summation = 600;
    t.Egrave = 667;
    t.racute = 333;
    t.omacron = 556;
    t.Zacute = 611;
    t.Zcaron = 611;
    t.greaterequal = 549;
    t.Eth = 722;
    t.Ccedilla = 722;
    t.lcommaaccent = 222;
    t.tcaron = 317;
    t.eogonek = 556;
    t.Uogonek = 722;
    t.Aacute = 667;
    t.Adieresis = 667;
    t.egrave = 556;
    t.zacute = 500;
    t.iogonek = 222;
    t.Oacute = 778;
    t.oacute = 556;
    t.amacron = 556;
    t.sacute = 500;
    t.idieresis = 278;
    t.Ocircumflex = 778;
    t.Ugrave = 722;
    t.Delta = 612;
    t.thorn = 556;
    t.twosuperior = 333;
    t.Odieresis = 778;
    t.mu = 556;
    t.igrave = 278;
    t.ohungarumlaut = 556;
    t.Eogonek = 667;
    t.dcroat = 556;
    t.threequarters = 834;
    t.Scedilla = 667;
    t.lcaron = 299;
    t.Kcommaaccent = 667;
    t.Lacute = 556;
    t.trademark = 1000;
    t.edotaccent = 556;
    t.Igrave = 278;
    t.Imacron = 278;
    t.Lcaron = 556;
    t.onehalf = 834;
    t.lessequal = 549;
    t.ocircumflex = 556;
    t.ntilde = 556;
    t.Uhungarumlaut = 722;
    t.Eacute = 667;
    t.emacron = 556;
    t.gbreve = 556;
    t.onequarter = 834;
    t.Scaron = 667;
    t.Scommaaccent = 667;
    t.Ohungarumlaut = 778;
    t.degree = 400;
    t.ograve = 556;
    t.Ccaron = 722;
    t.ugrave = 556;
    t.radical = 453;
    t.Dcaron = 722;
    t.rcommaaccent = 333;
    t.Ntilde = 722;
    t.otilde = 556;
    t.Rcommaaccent = 722;
    t.Lcommaaccent = 556;
    t.Atilde = 667;
    t.Aogonek = 667;
    t.Aring = 667;
    t.Otilde = 778;
    t.zdotaccent = 500;
    t.Ecaron = 667;
    t.Iogonek = 278;
    t.kcommaaccent = 500;
    t.minus = 584;
    t.Icircumflex = 278;
    t.ncaron = 556;
    t.tcommaaccent = 278;
    t.logicalnot = 584;
    t.odieresis = 556;
    t.udieresis = 556;
    t.notequal = 549;
    t.gcommaaccent = 556;
    t.eth = 556;
    t.zcaron = 500;
    t.ncommaaccent = 556;
    t.onesuperior = 333;
    t.imacron = 278;
    t.Euro = 556;
  });
  t.Symbol = getLookupTableFactory(function (t) {
    t.space = 250;
    t.exclam = 333;
    t.universal = 713;
    t.numbersign = 500;
    t.existential = 549;
    t.percent = 833;
    t.ampersand = 778;
    t.suchthat = 439;
    t.parenleft = 333;
    t.parenright = 333;
    t.asteriskmath = 500;
    t.plus = 549;
    t.comma = 250;
    t.minus = 549;
    t.period = 250;
    t.slash = 278;
    t.zero = 500;
    t.one = 500;
    t.two = 500;
    t.three = 500;
    t.four = 500;
    t.five = 500;
    t.six = 500;
    t.seven = 500;
    t.eight = 500;
    t.nine = 500;
    t.colon = 278;
    t.semicolon = 278;
    t.less = 549;
    t.equal = 549;
    t.greater = 549;
    t.question = 444;
    t.congruent = 549;
    t.Alpha = 722;
    t.Beta = 667;
    t.Chi = 722;
    t.Delta = 612;
    t.Epsilon = 611;
    t.Phi = 763;
    t.Gamma = 603;
    t.Eta = 722;
    t.Iota = 333;
    t.theta1 = 631;
    t.Kappa = 722;
    t.Lambda = 686;
    t.Mu = 889;
    t.Nu = 722;
    t.Omicron = 722;
    t.Pi = 768;
    t.Theta = 741;
    t.Rho = 556;
    t.Sigma = 592;
    t.Tau = 611;
    t.Upsilon = 690;
    t.sigma1 = 439;
    t.Omega = 768;
    t.Xi = 645;
    t.Psi = 795;
    t.Zeta = 611;
    t.bracketleft = 333;
    t.therefore = 863;
    t.bracketright = 333;
    t.perpendicular = 658;
    t.underscore = 500;
    t.radicalex = 500;
    t.alpha = 631;
    t.beta = 549;
    t.chi = 549;
    t.delta = 494;
    t.epsilon = 439;
    t.phi = 521;
    t.gamma = 411;
    t.eta = 603;
    t.iota = 329;
    t.phi1 = 603;
    t.kappa = 549;
    t.lambda = 549;
    t.mu = 576;
    t.nu = 521;
    t.omicron = 549;
    t.pi = 549;
    t.theta = 521;
    t.rho = 549;
    t.sigma = 603;
    t.tau = 439;
    t.upsilon = 576;
    t.omega1 = 713;
    t.omega = 686;
    t.xi = 493;
    t.psi = 686;
    t.zeta = 494;
    t.braceleft = 480;
    t.bar = 200;
    t.braceright = 480;
    t.similar = 549;
    t.Euro = 750;
    t.Upsilon1 = 620;
    t.minute = 247;
    t.lessequal = 549;
    t.fraction = 167;
    t.infinity = 713;
    t.florin = 500;
    t.club = 753;
    t.diamond = 753;
    t.heart = 753;
    t.spade = 753;
    t.arrowboth = 1042;
    t.arrowleft = 987;
    t.arrowup = 603;
    t.arrowright = 987;
    t.arrowdown = 603;
    t.degree = 400;
    t.plusminus = 549;
    t.second = 411;
    t.greaterequal = 549;
    t.multiply = 549;
    t.proportional = 713;
    t.partialdiff = 494;
    t.bullet = 460;
    t.divide = 549;
    t.notequal = 549;
    t.equivalence = 549;
    t.approxequal = 549;
    t.ellipsis = 1000;
    t.arrowvertex = 603;
    t.arrowhorizex = 1000;
    t.carriagereturn = 658;
    t.aleph = 823;
    t.Ifraktur = 686;
    t.Rfraktur = 795;
    t.weierstrass = 987;
    t.circlemultiply = 768;
    t.circleplus = 768;
    t.emptyset = 823;
    t.intersection = 768;
    t.union = 768;
    t.propersuperset = 713;
    t.reflexsuperset = 713;
    t.notsubset = 713;
    t.propersubset = 713;
    t.reflexsubset = 713;
    t.element = 713;
    t.notelement = 713;
    t.angle = 768;
    t.gradient = 713;
    t.registerserif = 790;
    t.copyrightserif = 790;
    t.trademarkserif = 890;
    t.product = 823;
    t.radical = 549;
    t.dotmath = 250;
    t.logicalnot = 713;
    t.logicaland = 603;
    t.logicalor = 603;
    t.arrowdblboth = 1042;
    t.arrowdblleft = 987;
    t.arrowdblup = 603;
    t.arrowdblright = 987;
    t.arrowdbldown = 603;
    t.lozenge = 494;
    t.angleleft = 329;
    t.registersans = 790;
    t.copyrightsans = 790;
    t.trademarksans = 786;
    t.summation = 713;
    t.parenlefttp = 384;
    t.parenleftex = 384;
    t.parenleftbt = 384;
    t.bracketlefttp = 384;
    t.bracketleftex = 384;
    t.bracketleftbt = 384;
    t.bracelefttp = 494;
    t.braceleftmid = 494;
    t.braceleftbt = 494;
    t.braceex = 494;
    t.angleright = 329;
    t.integral = 274;
    t.integraltp = 686;
    t.integralex = 686;
    t.integralbt = 686;
    t.parenrighttp = 384;
    t.parenrightex = 384;
    t.parenrightbt = 384;
    t.bracketrighttp = 384;
    t.bracketrightex = 384;
    t.bracketrightbt = 384;
    t.bracerighttp = 494;
    t.bracerightmid = 494;
    t.bracerightbt = 494;
    t.apple = 790;
  });
  t["Times-Roman"] = getLookupTableFactory(function (t) {
    t.space = 250;
    t.exclam = 333;
    t.quotedbl = 408;
    t.numbersign = 500;
    t.dollar = 500;
    t.percent = 833;
    t.ampersand = 778;
    t.quoteright = 333;
    t.parenleft = 333;
    t.parenright = 333;
    t.asterisk = 500;
    t.plus = 564;
    t.comma = 250;
    t.hyphen = 333;
    t.period = 250;
    t.slash = 278;
    t.zero = 500;
    t.one = 500;
    t.two = 500;
    t.three = 500;
    t.four = 500;
    t.five = 500;
    t.six = 500;
    t.seven = 500;
    t.eight = 500;
    t.nine = 500;
    t.colon = 278;
    t.semicolon = 278;
    t.less = 564;
    t.equal = 564;
    t.greater = 564;
    t.question = 444;
    t.at = 921;
    t.A = 722;
    t.B = 667;
    t.C = 667;
    t.D = 722;
    t.E = 611;
    t.F = 556;
    t.G = 722;
    t.H = 722;
    t.I = 333;
    t.J = 389;
    t.K = 722;
    t.L = 611;
    t.M = 889;
    t.N = 722;
    t.O = 722;
    t.P = 556;
    t.Q = 722;
    t.R = 667;
    t.S = 556;
    t.T = 611;
    t.U = 722;
    t.V = 722;
    t.W = 944;
    t.X = 722;
    t.Y = 722;
    t.Z = 611;
    t.bracketleft = 333;
    t.backslash = 278;
    t.bracketright = 333;
    t.asciicircum = 469;
    t.underscore = 500;
    t.quoteleft = 333;
    t.a = 444;
    t.b = 500;
    t.c = 444;
    t.d = 500;
    t.e = 444;
    t.f = 333;
    t.g = 500;
    t.h = 500;
    t.i = 278;
    t.j = 278;
    t.k = 500;
    t.l = 278;
    t.m = 778;
    t.n = 500;
    t.o = 500;
    t.p = 500;
    t.q = 500;
    t.r = 333;
    t.s = 389;
    t.t = 278;
    t.u = 500;
    t.v = 500;
    t.w = 722;
    t.x = 500;
    t.y = 500;
    t.z = 444;
    t.braceleft = 480;
    t.bar = 200;
    t.braceright = 480;
    t.asciitilde = 541;
    t.exclamdown = 333;
    t.cent = 500;
    t.sterling = 500;
    t.fraction = 167;
    t.yen = 500;
    t.florin = 500;
    t.section = 500;
    t.currency = 500;
    t.quotesingle = 180;
    t.quotedblleft = 444;
    t.guillemotleft = 500;
    t.guilsinglleft = 333;
    t.guilsinglright = 333;
    t.fi = 556;
    t.fl = 556;
    t.endash = 500;
    t.dagger = 500;
    t.daggerdbl = 500;
    t.periodcentered = 250;
    t.paragraph = 453;
    t.bullet = 350;
    t.quotesinglbase = 333;
    t.quotedblbase = 444;
    t.quotedblright = 444;
    t.guillemotright = 500;
    t.ellipsis = 1000;
    t.perthousand = 1000;
    t.questiondown = 444;
    t.grave = 333;
    t.acute = 333;
    t.circumflex = 333;
    t.tilde = 333;
    t.macron = 333;
    t.breve = 333;
    t.dotaccent = 333;
    t.dieresis = 333;
    t.ring = 333;
    t.cedilla = 333;
    t.hungarumlaut = 333;
    t.ogonek = 333;
    t.caron = 333;
    t.emdash = 1000;
    t.AE = 889;
    t.ordfeminine = 276;
    t.Lslash = 611;
    t.Oslash = 722;
    t.OE = 889;
    t.ordmasculine = 310;
    t.ae = 667;
    t.dotlessi = 278;
    t.lslash = 278;
    t.oslash = 500;
    t.oe = 722;
    t.germandbls = 500;
    t.Idieresis = 333;
    t.eacute = 444;
    t.abreve = 444;
    t.uhungarumlaut = 500;
    t.ecaron = 444;
    t.Ydieresis = 722;
    t.divide = 564;
    t.Yacute = 722;
    t.Acircumflex = 722;
    t.aacute = 444;
    t.Ucircumflex = 722;
    t.yacute = 500;
    t.scommaaccent = 389;
    t.ecircumflex = 444;
    t.Uring = 722;
    t.Udieresis = 722;
    t.aogonek = 444;
    t.Uacute = 722;
    t.uogonek = 500;
    t.Edieresis = 611;
    t.Dcroat = 722;
    t.commaaccent = 250;
    t.copyright = 760;
    t.Emacron = 611;
    t.ccaron = 444;
    t.aring = 444;
    t.Ncommaaccent = 722;
    t.lacute = 278;
    t.agrave = 444;
    t.Tcommaaccent = 611;
    t.Cacute = 667;
    t.atilde = 444;
    t.Edotaccent = 611;
    t.scaron = 389;
    t.scedilla = 389;
    t.iacute = 278;
    t.lozenge = 471;
    t.Rcaron = 667;
    t.Gcommaaccent = 722;
    t.ucircumflex = 500;
    t.acircumflex = 444;
    t.Amacron = 722;
    t.rcaron = 333;
    t.ccedilla = 444;
    t.Zdotaccent = 611;
    t.Thorn = 556;
    t.Omacron = 722;
    t.Racute = 667;
    t.Sacute = 556;
    t.dcaron = 588;
    t.Umacron = 722;
    t.uring = 500;
    t.threesuperior = 300;
    t.Ograve = 722;
    t.Agrave = 722;
    t.Abreve = 722;
    t.multiply = 564;
    t.uacute = 500;
    t.Tcaron = 611;
    t.partialdiff = 476;
    t.ydieresis = 500;
    t.Nacute = 722;
    t.icircumflex = 278;
    t.Ecircumflex = 611;
    t.adieresis = 444;
    t.edieresis = 444;
    t.cacute = 444;
    t.nacute = 500;
    t.umacron = 500;
    t.Ncaron = 722;
    t.Iacute = 333;
    t.plusminus = 564;
    t.brokenbar = 200;
    t.registered = 760;
    t.Gbreve = 722;
    t.Idotaccent = 333;
    t.summation = 600;
    t.Egrave = 611;
    t.racute = 333;
    t.omacron = 500;
    t.Zacute = 611;
    t.Zcaron = 611;
    t.greaterequal = 549;
    t.Eth = 722;
    t.Ccedilla = 667;
    t.lcommaaccent = 278;
    t.tcaron = 326;
    t.eogonek = 444;
    t.Uogonek = 722;
    t.Aacute = 722;
    t.Adieresis = 722;
    t.egrave = 444;
    t.zacute = 444;
    t.iogonek = 278;
    t.Oacute = 722;
    t.oacute = 500;
    t.amacron = 444;
    t.sacute = 389;
    t.idieresis = 278;
    t.Ocircumflex = 722;
    t.Ugrave = 722;
    t.Delta = 612;
    t.thorn = 500;
    t.twosuperior = 300;
    t.Odieresis = 722;
    t.mu = 500;
    t.igrave = 278;
    t.ohungarumlaut = 500;
    t.Eogonek = 611;
    t.dcroat = 500;
    t.threequarters = 750;
    t.Scedilla = 556;
    t.lcaron = 344;
    t.Kcommaaccent = 722;
    t.Lacute = 611;
    t.trademark = 980;
    t.edotaccent = 444;
    t.Igrave = 333;
    t.Imacron = 333;
    t.Lcaron = 611;
    t.onehalf = 750;
    t.lessequal = 549;
    t.ocircumflex = 500;
    t.ntilde = 500;
    t.Uhungarumlaut = 722;
    t.Eacute = 611;
    t.emacron = 444;
    t.gbreve = 500;
    t.onequarter = 750;
    t.Scaron = 556;
    t.Scommaaccent = 556;
    t.Ohungarumlaut = 722;
    t.degree = 400;
    t.ograve = 500;
    t.Ccaron = 667;
    t.ugrave = 500;
    t.radical = 453;
    t.Dcaron = 722;
    t.rcommaaccent = 333;
    t.Ntilde = 722;
    t.otilde = 500;
    t.Rcommaaccent = 667;
    t.Lcommaaccent = 611;
    t.Atilde = 722;
    t.Aogonek = 722;
    t.Aring = 722;
    t.Otilde = 722;
    t.zdotaccent = 444;
    t.Ecaron = 611;
    t.Iogonek = 333;
    t.kcommaaccent = 500;
    t.minus = 564;
    t.Icircumflex = 333;
    t.ncaron = 500;
    t.tcommaaccent = 278;
    t.logicalnot = 564;
    t.odieresis = 500;
    t.udieresis = 500;
    t.notequal = 549;
    t.gcommaaccent = 500;
    t.eth = 500;
    t.zcaron = 444;
    t.ncommaaccent = 500;
    t.onesuperior = 300;
    t.imacron = 278;
    t.Euro = 500;
  });
  t["Times-Bold"] = getLookupTableFactory(function (t) {
    t.space = 250;
    t.exclam = 333;
    t.quotedbl = 555;
    t.numbersign = 500;
    t.dollar = 500;
    t.percent = 1000;
    t.ampersand = 833;
    t.quoteright = 333;
    t.parenleft = 333;
    t.parenright = 333;
    t.asterisk = 500;
    t.plus = 570;
    t.comma = 250;
    t.hyphen = 333;
    t.period = 250;
    t.slash = 278;
    t.zero = 500;
    t.one = 500;
    t.two = 500;
    t.three = 500;
    t.four = 500;
    t.five = 500;
    t.six = 500;
    t.seven = 500;
    t.eight = 500;
    t.nine = 500;
    t.colon = 333;
    t.semicolon = 333;
    t.less = 570;
    t.equal = 570;
    t.greater = 570;
    t.question = 500;
    t.at = 930;
    t.A = 722;
    t.B = 667;
    t.C = 722;
    t.D = 722;
    t.E = 667;
    t.F = 611;
    t.G = 778;
    t.H = 778;
    t.I = 389;
    t.J = 500;
    t.K = 778;
    t.L = 667;
    t.M = 944;
    t.N = 722;
    t.O = 778;
    t.P = 611;
    t.Q = 778;
    t.R = 722;
    t.S = 556;
    t.T = 667;
    t.U = 722;
    t.V = 722;
    t.W = 1000;
    t.X = 722;
    t.Y = 722;
    t.Z = 667;
    t.bracketleft = 333;
    t.backslash = 278;
    t.bracketright = 333;
    t.asciicircum = 581;
    t.underscore = 500;
    t.quoteleft = 333;
    t.a = 500;
    t.b = 556;
    t.c = 444;
    t.d = 556;
    t.e = 444;
    t.f = 333;
    t.g = 500;
    t.h = 556;
    t.i = 278;
    t.j = 333;
    t.k = 556;
    t.l = 278;
    t.m = 833;
    t.n = 556;
    t.o = 500;
    t.p = 556;
    t.q = 556;
    t.r = 444;
    t.s = 389;
    t.t = 333;
    t.u = 556;
    t.v = 500;
    t.w = 722;
    t.x = 500;
    t.y = 500;
    t.z = 444;
    t.braceleft = 394;
    t.bar = 220;
    t.braceright = 394;
    t.asciitilde = 520;
    t.exclamdown = 333;
    t.cent = 500;
    t.sterling = 500;
    t.fraction = 167;
    t.yen = 500;
    t.florin = 500;
    t.section = 500;
    t.currency = 500;
    t.quotesingle = 278;
    t.quotedblleft = 500;
    t.guillemotleft = 500;
    t.guilsinglleft = 333;
    t.guilsinglright = 333;
    t.fi = 556;
    t.fl = 556;
    t.endash = 500;
    t.dagger = 500;
    t.daggerdbl = 500;
    t.periodcentered = 250;
    t.paragraph = 540;
    t.bullet = 350;
    t.quotesinglbase = 333;
    t.quotedblbase = 500;
    t.quotedblright = 500;
    t.guillemotright = 500;
    t.ellipsis = 1000;
    t.perthousand = 1000;
    t.questiondown = 500;
    t.grave = 333;
    t.acute = 333;
    t.circumflex = 333;
    t.tilde = 333;
    t.macron = 333;
    t.breve = 333;
    t.dotaccent = 333;
    t.dieresis = 333;
    t.ring = 333;
    t.cedilla = 333;
    t.hungarumlaut = 333;
    t.ogonek = 333;
    t.caron = 333;
    t.emdash = 1000;
    t.AE = 1000;
    t.ordfeminine = 300;
    t.Lslash = 667;
    t.Oslash = 778;
    t.OE = 1000;
    t.ordmasculine = 330;
    t.ae = 722;
    t.dotlessi = 278;
    t.lslash = 278;
    t.oslash = 500;
    t.oe = 722;
    t.germandbls = 556;
    t.Idieresis = 389;
    t.eacute = 444;
    t.abreve = 500;
    t.uhungarumlaut = 556;
    t.ecaron = 444;
    t.Ydieresis = 722;
    t.divide = 570;
    t.Yacute = 722;
    t.Acircumflex = 722;
    t.aacute = 500;
    t.Ucircumflex = 722;
    t.yacute = 500;
    t.scommaaccent = 389;
    t.ecircumflex = 444;
    t.Uring = 722;
    t.Udieresis = 722;
    t.aogonek = 500;
    t.Uacute = 722;
    t.uogonek = 556;
    t.Edieresis = 667;
    t.Dcroat = 722;
    t.commaaccent = 250;
    t.copyright = 747;
    t.Emacron = 667;
    t.ccaron = 444;
    t.aring = 500;
    t.Ncommaaccent = 722;
    t.lacute = 278;
    t.agrave = 500;
    t.Tcommaaccent = 667;
    t.Cacute = 722;
    t.atilde = 500;
    t.Edotaccent = 667;
    t.scaron = 389;
    t.scedilla = 389;
    t.iacute = 278;
    t.lozenge = 494;
    t.Rcaron = 722;
    t.Gcommaaccent = 778;
    t.ucircumflex = 556;
    t.acircumflex = 500;
    t.Amacron = 722;
    t.rcaron = 444;
    t.ccedilla = 444;
    t.Zdotaccent = 667;
    t.Thorn = 611;
    t.Omacron = 778;
    t.Racute = 722;
    t.Sacute = 556;
    t.dcaron = 672;
    t.Umacron = 722;
    t.uring = 556;
    t.threesuperior = 300;
    t.Ograve = 778;
    t.Agrave = 722;
    t.Abreve = 722;
    t.multiply = 570;
    t.uacute = 556;
    t.Tcaron = 667;
    t.partialdiff = 494;
    t.ydieresis = 500;
    t.Nacute = 722;
    t.icircumflex = 278;
    t.Ecircumflex = 667;
    t.adieresis = 500;
    t.edieresis = 444;
    t.cacute = 444;
    t.nacute = 556;
    t.umacron = 556;
    t.Ncaron = 722;
    t.Iacute = 389;
    t.plusminus = 570;
    t.brokenbar = 220;
    t.registered = 747;
    t.Gbreve = 778;
    t.Idotaccent = 389;
    t.summation = 600;
    t.Egrave = 667;
    t.racute = 444;
    t.omacron = 500;
    t.Zacute = 667;
    t.Zcaron = 667;
    t.greaterequal = 549;
    t.Eth = 722;
    t.Ccedilla = 722;
    t.lcommaaccent = 278;
    t.tcaron = 416;
    t.eogonek = 444;
    t.Uogonek = 722;
    t.Aacute = 722;
    t.Adieresis = 722;
    t.egrave = 444;
    t.zacute = 444;
    t.iogonek = 278;
    t.Oacute = 778;
    t.oacute = 500;
    t.amacron = 500;
    t.sacute = 389;
    t.idieresis = 278;
    t.Ocircumflex = 778;
    t.Ugrave = 722;
    t.Delta = 612;
    t.thorn = 556;
    t.twosuperior = 300;
    t.Odieresis = 778;
    t.mu = 556;
    t.igrave = 278;
    t.ohungarumlaut = 500;
    t.Eogonek = 667;
    t.dcroat = 556;
    t.threequarters = 750;
    t.Scedilla = 556;
    t.lcaron = 394;
    t.Kcommaaccent = 778;
    t.Lacute = 667;
    t.trademark = 1000;
    t.edotaccent = 444;
    t.Igrave = 389;
    t.Imacron = 389;
    t.Lcaron = 667;
    t.onehalf = 750;
    t.lessequal = 549;
    t.ocircumflex = 500;
    t.ntilde = 556;
    t.Uhungarumlaut = 722;
    t.Eacute = 667;
    t.emacron = 444;
    t.gbreve = 500;
    t.onequarter = 750;
    t.Scaron = 556;
    t.Scommaaccent = 556;
    t.Ohungarumlaut = 778;
    t.degree = 400;
    t.ograve = 500;
    t.Ccaron = 722;
    t.ugrave = 556;
    t.radical = 549;
    t.Dcaron = 722;
    t.rcommaaccent = 444;
    t.Ntilde = 722;
    t.otilde = 500;
    t.Rcommaaccent = 722;
    t.Lcommaaccent = 667;
    t.Atilde = 722;
    t.Aogonek = 722;
    t.Aring = 722;
    t.Otilde = 778;
    t.zdotaccent = 444;
    t.Ecaron = 667;
    t.Iogonek = 389;
    t.kcommaaccent = 556;
    t.minus = 570;
    t.Icircumflex = 389;
    t.ncaron = 556;
    t.tcommaaccent = 333;
    t.logicalnot = 570;
    t.odieresis = 500;
    t.udieresis = 556;
    t.notequal = 549;
    t.gcommaaccent = 500;
    t.eth = 500;
    t.zcaron = 444;
    t.ncommaaccent = 556;
    t.onesuperior = 300;
    t.imacron = 278;
    t.Euro = 500;
  });
  t["Times-BoldItalic"] = getLookupTableFactory(function (t) {
    t.space = 250;
    t.exclam = 389;
    t.quotedbl = 555;
    t.numbersign = 500;
    t.dollar = 500;
    t.percent = 833;
    t.ampersand = 778;
    t.quoteright = 333;
    t.parenleft = 333;
    t.parenright = 333;
    t.asterisk = 500;
    t.plus = 570;
    t.comma = 250;
    t.hyphen = 333;
    t.period = 250;
    t.slash = 278;
    t.zero = 500;
    t.one = 500;
    t.two = 500;
    t.three = 500;
    t.four = 500;
    t.five = 500;
    t.six = 500;
    t.seven = 500;
    t.eight = 500;
    t.nine = 500;
    t.colon = 333;
    t.semicolon = 333;
    t.less = 570;
    t.equal = 570;
    t.greater = 570;
    t.question = 500;
    t.at = 832;
    t.A = 667;
    t.B = 667;
    t.C = 667;
    t.D = 722;
    t.E = 667;
    t.F = 667;
    t.G = 722;
    t.H = 778;
    t.I = 389;
    t.J = 500;
    t.K = 667;
    t.L = 611;
    t.M = 889;
    t.N = 722;
    t.O = 722;
    t.P = 611;
    t.Q = 722;
    t.R = 667;
    t.S = 556;
    t.T = 611;
    t.U = 722;
    t.V = 667;
    t.W = 889;
    t.X = 667;
    t.Y = 611;
    t.Z = 611;
    t.bracketleft = 333;
    t.backslash = 278;
    t.bracketright = 333;
    t.asciicircum = 570;
    t.underscore = 500;
    t.quoteleft = 333;
    t.a = 500;
    t.b = 500;
    t.c = 444;
    t.d = 500;
    t.e = 444;
    t.f = 333;
    t.g = 500;
    t.h = 556;
    t.i = 278;
    t.j = 278;
    t.k = 500;
    t.l = 278;
    t.m = 778;
    t.n = 556;
    t.o = 500;
    t.p = 500;
    t.q = 500;
    t.r = 389;
    t.s = 389;
    t.t = 278;
    t.u = 556;
    t.v = 444;
    t.w = 667;
    t.x = 500;
    t.y = 444;
    t.z = 389;
    t.braceleft = 348;
    t.bar = 220;
    t.braceright = 348;
    t.asciitilde = 570;
    t.exclamdown = 389;
    t.cent = 500;
    t.sterling = 500;
    t.fraction = 167;
    t.yen = 500;
    t.florin = 500;
    t.section = 500;
    t.currency = 500;
    t.quotesingle = 278;
    t.quotedblleft = 500;
    t.guillemotleft = 500;
    t.guilsinglleft = 333;
    t.guilsinglright = 333;
    t.fi = 556;
    t.fl = 556;
    t.endash = 500;
    t.dagger = 500;
    t.daggerdbl = 500;
    t.periodcentered = 250;
    t.paragraph = 500;
    t.bullet = 350;
    t.quotesinglbase = 333;
    t.quotedblbase = 500;
    t.quotedblright = 500;
    t.guillemotright = 500;
    t.ellipsis = 1000;
    t.perthousand = 1000;
    t.questiondown = 500;
    t.grave = 333;
    t.acute = 333;
    t.circumflex = 333;
    t.tilde = 333;
    t.macron = 333;
    t.breve = 333;
    t.dotaccent = 333;
    t.dieresis = 333;
    t.ring = 333;
    t.cedilla = 333;
    t.hungarumlaut = 333;
    t.ogonek = 333;
    t.caron = 333;
    t.emdash = 1000;
    t.AE = 944;
    t.ordfeminine = 266;
    t.Lslash = 611;
    t.Oslash = 722;
    t.OE = 944;
    t.ordmasculine = 300;
    t.ae = 722;
    t.dotlessi = 278;
    t.lslash = 278;
    t.oslash = 500;
    t.oe = 722;
    t.germandbls = 500;
    t.Idieresis = 389;
    t.eacute = 444;
    t.abreve = 500;
    t.uhungarumlaut = 556;
    t.ecaron = 444;
    t.Ydieresis = 611;
    t.divide = 570;
    t.Yacute = 611;
    t.Acircumflex = 667;
    t.aacute = 500;
    t.Ucircumflex = 722;
    t.yacute = 444;
    t.scommaaccent = 389;
    t.ecircumflex = 444;
    t.Uring = 722;
    t.Udieresis = 722;
    t.aogonek = 500;
    t.Uacute = 722;
    t.uogonek = 556;
    t.Edieresis = 667;
    t.Dcroat = 722;
    t.commaaccent = 250;
    t.copyright = 747;
    t.Emacron = 667;
    t.ccaron = 444;
    t.aring = 500;
    t.Ncommaaccent = 722;
    t.lacute = 278;
    t.agrave = 500;
    t.Tcommaaccent = 611;
    t.Cacute = 667;
    t.atilde = 500;
    t.Edotaccent = 667;
    t.scaron = 389;
    t.scedilla = 389;
    t.iacute = 278;
    t.lozenge = 494;
    t.Rcaron = 667;
    t.Gcommaaccent = 722;
    t.ucircumflex = 556;
    t.acircumflex = 500;
    t.Amacron = 667;
    t.rcaron = 389;
    t.ccedilla = 444;
    t.Zdotaccent = 611;
    t.Thorn = 611;
    t.Omacron = 722;
    t.Racute = 667;
    t.Sacute = 556;
    t.dcaron = 608;
    t.Umacron = 722;
    t.uring = 556;
    t.threesuperior = 300;
    t.Ograve = 722;
    t.Agrave = 667;
    t.Abreve = 667;
    t.multiply = 570;
    t.uacute = 556;
    t.Tcaron = 611;
    t.partialdiff = 494;
    t.ydieresis = 444;
    t.Nacute = 722;
    t.icircumflex = 278;
    t.Ecircumflex = 667;
    t.adieresis = 500;
    t.edieresis = 444;
    t.cacute = 444;
    t.nacute = 556;
    t.umacron = 556;
    t.Ncaron = 722;
    t.Iacute = 389;
    t.plusminus = 570;
    t.brokenbar = 220;
    t.registered = 747;
    t.Gbreve = 722;
    t.Idotaccent = 389;
    t.summation = 600;
    t.Egrave = 667;
    t.racute = 389;
    t.omacron = 500;
    t.Zacute = 611;
    t.Zcaron = 611;
    t.greaterequal = 549;
    t.Eth = 722;
    t.Ccedilla = 667;
    t.lcommaaccent = 278;
    t.tcaron = 366;
    t.eogonek = 444;
    t.Uogonek = 722;
    t.Aacute = 667;
    t.Adieresis = 667;
    t.egrave = 444;
    t.zacute = 389;
    t.iogonek = 278;
    t.Oacute = 722;
    t.oacute = 500;
    t.amacron = 500;
    t.sacute = 389;
    t.idieresis = 278;
    t.Ocircumflex = 722;
    t.Ugrave = 722;
    t.Delta = 612;
    t.thorn = 500;
    t.twosuperior = 300;
    t.Odieresis = 722;
    t.mu = 576;
    t.igrave = 278;
    t.ohungarumlaut = 500;
    t.Eogonek = 667;
    t.dcroat = 500;
    t.threequarters = 750;
    t.Scedilla = 556;
    t.lcaron = 382;
    t.Kcommaaccent = 667;
    t.Lacute = 611;
    t.trademark = 1000;
    t.edotaccent = 444;
    t.Igrave = 389;
    t.Imacron = 389;
    t.Lcaron = 611;
    t.onehalf = 750;
    t.lessequal = 549;
    t.ocircumflex = 500;
    t.ntilde = 556;
    t.Uhungarumlaut = 722;
    t.Eacute = 667;
    t.emacron = 444;
    t.gbreve = 500;
    t.onequarter = 750;
    t.Scaron = 556;
    t.Scommaaccent = 556;
    t.Ohungarumlaut = 722;
    t.degree = 400;
    t.ograve = 500;
    t.Ccaron = 667;
    t.ugrave = 556;
    t.radical = 549;
    t.Dcaron = 722;
    t.rcommaaccent = 389;
    t.Ntilde = 722;
    t.otilde = 500;
    t.Rcommaaccent = 667;
    t.Lcommaaccent = 611;
    t.Atilde = 667;
    t.Aogonek = 667;
    t.Aring = 667;
    t.Otilde = 722;
    t.zdotaccent = 389;
    t.Ecaron = 667;
    t.Iogonek = 389;
    t.kcommaaccent = 500;
    t.minus = 606;
    t.Icircumflex = 389;
    t.ncaron = 556;
    t.tcommaaccent = 278;
    t.logicalnot = 606;
    t.odieresis = 500;
    t.udieresis = 556;
    t.notequal = 549;
    t.gcommaaccent = 500;
    t.eth = 500;
    t.zcaron = 389;
    t.ncommaaccent = 556;
    t.onesuperior = 300;
    t.imacron = 278;
    t.Euro = 500;
  });
  t["Times-Italic"] = getLookupTableFactory(function (t) {
    t.space = 250;
    t.exclam = 333;
    t.quotedbl = 420;
    t.numbersign = 500;
    t.dollar = 500;
    t.percent = 833;
    t.ampersand = 778;
    t.quoteright = 333;
    t.parenleft = 333;
    t.parenright = 333;
    t.asterisk = 500;
    t.plus = 675;
    t.comma = 250;
    t.hyphen = 333;
    t.period = 250;
    t.slash = 278;
    t.zero = 500;
    t.one = 500;
    t.two = 500;
    t.three = 500;
    t.four = 500;
    t.five = 500;
    t.six = 500;
    t.seven = 500;
    t.eight = 500;
    t.nine = 500;
    t.colon = 333;
    t.semicolon = 333;
    t.less = 675;
    t.equal = 675;
    t.greater = 675;
    t.question = 500;
    t.at = 920;
    t.A = 611;
    t.B = 611;
    t.C = 667;
    t.D = 722;
    t.E = 611;
    t.F = 611;
    t.G = 722;
    t.H = 722;
    t.I = 333;
    t.J = 444;
    t.K = 667;
    t.L = 556;
    t.M = 833;
    t.N = 667;
    t.O = 722;
    t.P = 611;
    t.Q = 722;
    t.R = 611;
    t.S = 500;
    t.T = 556;
    t.U = 722;
    t.V = 611;
    t.W = 833;
    t.X = 611;
    t.Y = 556;
    t.Z = 556;
    t.bracketleft = 389;
    t.backslash = 278;
    t.bracketright = 389;
    t.asciicircum = 422;
    t.underscore = 500;
    t.quoteleft = 333;
    t.a = 500;
    t.b = 500;
    t.c = 444;
    t.d = 500;
    t.e = 444;
    t.f = 278;
    t.g = 500;
    t.h = 500;
    t.i = 278;
    t.j = 278;
    t.k = 444;
    t.l = 278;
    t.m = 722;
    t.n = 500;
    t.o = 500;
    t.p = 500;
    t.q = 500;
    t.r = 389;
    t.s = 389;
    t.t = 278;
    t.u = 500;
    t.v = 444;
    t.w = 667;
    t.x = 444;
    t.y = 444;
    t.z = 389;
    t.braceleft = 400;
    t.bar = 275;
    t.braceright = 400;
    t.asciitilde = 541;
    t.exclamdown = 389;
    t.cent = 500;
    t.sterling = 500;
    t.fraction = 167;
    t.yen = 500;
    t.florin = 500;
    t.section = 500;
    t.currency = 500;
    t.quotesingle = 214;
    t.quotedblleft = 556;
    t.guillemotleft = 500;
    t.guilsinglleft = 333;
    t.guilsinglright = 333;
    t.fi = 500;
    t.fl = 500;
    t.endash = 500;
    t.dagger = 500;
    t.daggerdbl = 500;
    t.periodcentered = 250;
    t.paragraph = 523;
    t.bullet = 350;
    t.quotesinglbase = 333;
    t.quotedblbase = 556;
    t.quotedblright = 556;
    t.guillemotright = 500;
    t.ellipsis = 889;
    t.perthousand = 1000;
    t.questiondown = 500;
    t.grave = 333;
    t.acute = 333;
    t.circumflex = 333;
    t.tilde = 333;
    t.macron = 333;
    t.breve = 333;
    t.dotaccent = 333;
    t.dieresis = 333;
    t.ring = 333;
    t.cedilla = 333;
    t.hungarumlaut = 333;
    t.ogonek = 333;
    t.caron = 333;
    t.emdash = 889;
    t.AE = 889;
    t.ordfeminine = 276;
    t.Lslash = 556;
    t.Oslash = 722;
    t.OE = 944;
    t.ordmasculine = 310;
    t.ae = 667;
    t.dotlessi = 278;
    t.lslash = 278;
    t.oslash = 500;
    t.oe = 667;
    t.germandbls = 500;
    t.Idieresis = 333;
    t.eacute = 444;
    t.abreve = 500;
    t.uhungarumlaut = 500;
    t.ecaron = 444;
    t.Ydieresis = 556;
    t.divide = 675;
    t.Yacute = 556;
    t.Acircumflex = 611;
    t.aacute = 500;
    t.Ucircumflex = 722;
    t.yacute = 444;
    t.scommaaccent = 389;
    t.ecircumflex = 444;
    t.Uring = 722;
    t.Udieresis = 722;
    t.aogonek = 500;
    t.Uacute = 722;
    t.uogonek = 500;
    t.Edieresis = 611;
    t.Dcroat = 722;
    t.commaaccent = 250;
    t.copyright = 760;
    t.Emacron = 611;
    t.ccaron = 444;
    t.aring = 500;
    t.Ncommaaccent = 667;
    t.lacute = 278;
    t.agrave = 500;
    t.Tcommaaccent = 556;
    t.Cacute = 667;
    t.atilde = 500;
    t.Edotaccent = 611;
    t.scaron = 389;
    t.scedilla = 389;
    t.iacute = 278;
    t.lozenge = 471;
    t.Rcaron = 611;
    t.Gcommaaccent = 722;
    t.ucircumflex = 500;
    t.acircumflex = 500;
    t.Amacron = 611;
    t.rcaron = 389;
    t.ccedilla = 444;
    t.Zdotaccent = 556;
    t.Thorn = 611;
    t.Omacron = 722;
    t.Racute = 611;
    t.Sacute = 500;
    t.dcaron = 544;
    t.Umacron = 722;
    t.uring = 500;
    t.threesuperior = 300;
    t.Ograve = 722;
    t.Agrave = 611;
    t.Abreve = 611;
    t.multiply = 675;
    t.uacute = 500;
    t.Tcaron = 556;
    t.partialdiff = 476;
    t.ydieresis = 444;
    t.Nacute = 667;
    t.icircumflex = 278;
    t.Ecircumflex = 611;
    t.adieresis = 500;
    t.edieresis = 444;
    t.cacute = 444;
    t.nacute = 500;
    t.umacron = 500;
    t.Ncaron = 667;
    t.Iacute = 333;
    t.plusminus = 675;
    t.brokenbar = 275;
    t.registered = 760;
    t.Gbreve = 722;
    t.Idotaccent = 333;
    t.summation = 600;
    t.Egrave = 611;
    t.racute = 389;
    t.omacron = 500;
    t.Zacute = 556;
    t.Zcaron = 556;
    t.greaterequal = 549;
    t.Eth = 722;
    t.Ccedilla = 667;
    t.lcommaaccent = 278;
    t.tcaron = 300;
    t.eogonek = 444;
    t.Uogonek = 722;
    t.Aacute = 611;
    t.Adieresis = 611;
    t.egrave = 444;
    t.zacute = 389;
    t.iogonek = 278;
    t.Oacute = 722;
    t.oacute = 500;
    t.amacron = 500;
    t.sacute = 389;
    t.idieresis = 278;
    t.Ocircumflex = 722;
    t.Ugrave = 722;
    t.Delta = 612;
    t.thorn = 500;
    t.twosuperior = 300;
    t.Odieresis = 722;
    t.mu = 500;
    t.igrave = 278;
    t.ohungarumlaut = 500;
    t.Eogonek = 611;
    t.dcroat = 500;
    t.threequarters = 750;
    t.Scedilla = 500;
    t.lcaron = 300;
    t.Kcommaaccent = 667;
    t.Lacute = 556;
    t.trademark = 980;
    t.edotaccent = 444;
    t.Igrave = 333;
    t.Imacron = 333;
    t.Lcaron = 611;
    t.onehalf = 750;
    t.lessequal = 549;
    t.ocircumflex = 500;
    t.ntilde = 500;
    t.Uhungarumlaut = 722;
    t.Eacute = 611;
    t.emacron = 444;
    t.gbreve = 500;
    t.onequarter = 750;
    t.Scaron = 500;
    t.Scommaaccent = 500;
    t.Ohungarumlaut = 722;
    t.degree = 400;
    t.ograve = 500;
    t.Ccaron = 667;
    t.ugrave = 500;
    t.radical = 453;
    t.Dcaron = 722;
    t.rcommaaccent = 389;
    t.Ntilde = 667;
    t.otilde = 500;
    t.Rcommaaccent = 611;
    t.Lcommaaccent = 556;
    t.Atilde = 611;
    t.Aogonek = 611;
    t.Aring = 611;
    t.Otilde = 722;
    t.zdotaccent = 389;
    t.Ecaron = 611;
    t.Iogonek = 333;
    t.kcommaaccent = 444;
    t.minus = 675;
    t.Icircumflex = 333;
    t.ncaron = 500;
    t.tcommaaccent = 278;
    t.logicalnot = 675;
    t.odieresis = 500;
    t.udieresis = 500;
    t.notequal = 549;
    t.gcommaaccent = 500;
    t.eth = 500;
    t.zcaron = 389;
    t.ncommaaccent = 500;
    t.onesuperior = 300;
    t.imacron = 278;
    t.Euro = 500;
  });
  t.ZapfDingbats = getLookupTableFactory(function (t) {
    t.space = 278;
    t.a1 = 974;
    t.a2 = 961;
    t.a202 = 974;
    t.a3 = 980;
    t.a4 = 719;
    t.a5 = 789;
    t.a119 = 790;
    t.a118 = 791;
    t.a117 = 690;
    t.a11 = 960;
    t.a12 = 939;
    t.a13 = 549;
    t.a14 = 855;
    t.a15 = 911;
    t.a16 = 933;
    t.a105 = 911;
    t.a17 = 945;
    t.a18 = 974;
    t.a19 = 755;
    t.a20 = 846;
    t.a21 = 762;
    t.a22 = 761;
    t.a23 = 571;
    t.a24 = 677;
    t.a25 = 763;
    t.a26 = 760;
    t.a27 = 759;
    t.a28 = 754;
    t.a6 = 494;
    t.a7 = 552;
    t.a8 = 537;
    t.a9 = 577;
    t.a10 = 692;
    t.a29 = 786;
    t.a30 = 788;
    t.a31 = 788;
    t.a32 = 790;
    t.a33 = 793;
    t.a34 = 794;
    t.a35 = 816;
    t.a36 = 823;
    t.a37 = 789;
    t.a38 = 841;
    t.a39 = 823;
    t.a40 = 833;
    t.a41 = 816;
    t.a42 = 831;
    t.a43 = 923;
    t.a44 = 744;
    t.a45 = 723;
    t.a46 = 749;
    t.a47 = 790;
    t.a48 = 792;
    t.a49 = 695;
    t.a50 = 776;
    t.a51 = 768;
    t.a52 = 792;
    t.a53 = 759;
    t.a54 = 707;
    t.a55 = 708;
    t.a56 = 682;
    t.a57 = 701;
    t.a58 = 826;
    t.a59 = 815;
    t.a60 = 789;
    t.a61 = 789;
    t.a62 = 707;
    t.a63 = 687;
    t.a64 = 696;
    t.a65 = 689;
    t.a66 = 786;
    t.a67 = 787;
    t.a68 = 713;
    t.a69 = 791;
    t.a70 = 785;
    t.a71 = 791;
    t.a72 = 873;
    t.a73 = 761;
    t.a74 = 762;
    t.a203 = 762;
    t.a75 = 759;
    t.a204 = 759;
    t.a76 = 892;
    t.a77 = 892;
    t.a78 = 788;
    t.a79 = 784;
    t.a81 = 438;
    t.a82 = 138;
    t.a83 = 277;
    t.a84 = 415;
    t.a97 = 392;
    t.a98 = 392;
    t.a99 = 668;
    t.a100 = 668;
    t.a89 = 390;
    t.a90 = 390;
    t.a93 = 317;
    t.a94 = 317;
    t.a91 = 276;
    t.a92 = 276;
    t.a205 = 509;
    t.a85 = 509;
    t.a206 = 410;
    t.a86 = 410;
    t.a87 = 234;
    t.a88 = 234;
    t.a95 = 334;
    t.a96 = 334;
    t.a101 = 732;
    t.a102 = 544;
    t.a103 = 544;
    t.a104 = 910;
    t.a106 = 667;
    t.a107 = 760;
    t.a108 = 760;
    t.a112 = 776;
    t.a111 = 595;
    t.a110 = 694;
    t.a109 = 626;
    t.a120 = 788;
    t.a121 = 788;
    t.a122 = 788;
    t.a123 = 788;
    t.a124 = 788;
    t.a125 = 788;
    t.a126 = 788;
    t.a127 = 788;
    t.a128 = 788;
    t.a129 = 788;
    t.a130 = 788;
    t.a131 = 788;
    t.a132 = 788;
    t.a133 = 788;
    t.a134 = 788;
    t.a135 = 788;
    t.a136 = 788;
    t.a137 = 788;
    t.a138 = 788;
    t.a139 = 788;
    t.a140 = 788;
    t.a141 = 788;
    t.a142 = 788;
    t.a143 = 788;
    t.a144 = 788;
    t.a145 = 788;
    t.a146 = 788;
    t.a147 = 788;
    t.a148 = 788;
    t.a149 = 788;
    t.a150 = 788;
    t.a151 = 788;
    t.a152 = 788;
    t.a153 = 788;
    t.a154 = 788;
    t.a155 = 788;
    t.a156 = 788;
    t.a157 = 788;
    t.a158 = 788;
    t.a159 = 788;
    t.a160 = 894;
    t.a161 = 838;
    t.a163 = 1016;
    t.a164 = 458;
    t.a196 = 748;
    t.a165 = 924;
    t.a192 = 748;
    t.a166 = 918;
    t.a167 = 927;
    t.a168 = 928;
    t.a169 = 928;
    t.a170 = 834;
    t.a171 = 873;
    t.a172 = 828;
    t.a173 = 924;
    t.a162 = 924;
    t.a174 = 917;
    t.a175 = 930;
    t.a176 = 931;
    t.a177 = 463;
    t.a178 = 883;
    t.a179 = 836;
    t.a193 = 836;
    t.a180 = 867;
    t.a199 = 867;
    t.a181 = 696;
    t.a200 = 696;
    t.a182 = 874;
    t.a201 = 874;
    t.a183 = 760;
    t.a184 = 946;
    t.a197 = 771;
    t.a185 = 865;
    t.a194 = 771;
    t.a198 = 888;
    t.a186 = 967;
    t.a195 = 888;
    t.a187 = 831;
    t.a188 = 873;
    t.a189 = 927;
    t.a190 = 970;
    t.a191 = 918;
  });
});
const getFontBasicMetrics = getLookupTableFactory(function (t) {
  t.Courier = {
    ascent: 629,
    descent: -157,
    capHeight: 562,
    xHeight: -426
  };
  t["Courier-Bold"] = {
    ascent: 629,
    descent: -157,
    capHeight: 562,
    xHeight: 439
  };
  t["Courier-Oblique"] = {
    ascent: 629,
    descent: -157,
    capHeight: 562,
    xHeight: 426
  };
  t["Courier-BoldOblique"] = {
    ascent: 629,
    descent: -157,
    capHeight: 562,
    xHeight: 426
  };
  t.Helvetica = {
    ascent: 718,
    descent: -207,
    capHeight: 718,
    xHeight: 523
  };
  t["Helvetica-Bold"] = {
    ascent: 718,
    descent: -207,
    capHeight: 718,
    xHeight: 532
  };
  t["Helvetica-Oblique"] = {
    ascent: 718,
    descent: -207,
    capHeight: 718,
    xHeight: 523
  };
  t["Helvetica-BoldOblique"] = {
    ascent: 718,
    descent: -207,
    capHeight: 718,
    xHeight: 532
  };
  t["Times-Roman"] = {
    ascent: 683,
    descent: -217,
    capHeight: 662,
    xHeight: 450
  };
  t["Times-Bold"] = {
    ascent: 683,
    descent: -217,
    capHeight: 676,
    xHeight: 461
  };
  t["Times-Italic"] = {
    ascent: 683,
    descent: -217,
    capHeight: 653,
    xHeight: 441
  };
  t["Times-BoldItalic"] = {
    ascent: 683,
    descent: -217,
    capHeight: 669,
    xHeight: 462
  };
  t.Symbol = {
    ascent: Math.NaN,
    descent: Math.NaN,
    capHeight: Math.NaN,
    xHeight: Math.NaN
  };
  t.ZapfDingbats = {
    ascent: Math.NaN,
    descent: Math.NaN,
    capHeight: Math.NaN,
    xHeight: Math.NaN
  };
});

;// CONCATENATED MODULE: ./src/core/glyf.js
const ON_CURVE_POINT = 1 << 0;
const X_SHORT_VECTOR = 1 << 1;
const Y_SHORT_VECTOR = 1 << 2;
const REPEAT_FLAG = 1 << 3;
const X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR = 1 << 4;
const Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR = 1 << 5;
const OVERLAP_SIMPLE = 1 << 6;
const ARG_1_AND_2_ARE_WORDS = 1 << 0;
const ARGS_ARE_XY_VALUES = 1 << 1;
const WE_HAVE_A_SCALE = 1 << 3;
const MORE_COMPONENTS = 1 << 5;
const WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6;
const WE_HAVE_A_TWO_BY_TWO = 1 << 7;
const WE_HAVE_INSTRUCTIONS = 1 << 8;
class GlyfTable {
  constructor({
    glyfTable,
    isGlyphLocationsLong,
    locaTable,
    numGlyphs
  }) {
    this.glyphs = [];
    const loca = new DataView(locaTable.buffer, locaTable.byteOffset, locaTable.byteLength);
    const glyf = new DataView(glyfTable.buffer, glyfTable.byteOffset, glyfTable.byteLength);
    const offsetSize = isGlyphLocationsLong ? 4 : 2;
    let prev = isGlyphLocationsLong ? loca.getUint32(0) : 2 * loca.getUint16(0);
    let pos = 0;
    for (let i = 0; i < numGlyphs; i++) {
      pos += offsetSize;
      const next = isGlyphLocationsLong ? loca.getUint32(pos) : 2 * loca.getUint16(pos);
      if (next === prev) {
        this.glyphs.push(new Glyph({}));
        continue;
      }
      const glyph = Glyph.parse(prev, glyf);
      this.glyphs.push(glyph);
      prev = next;
    }
  }
  getSize() {
    return this.glyphs.reduce((a, g) => {
      const size = g.getSize();
      return a + (size + 3 & ~3);
    }, 0);
  }
  write() {
    const totalSize = this.getSize();
    const glyfTable = new DataView(new ArrayBuffer(totalSize));
    const isLocationLong = totalSize > 0x1fffe;
    const offsetSize = isLocationLong ? 4 : 2;
    const locaTable = new DataView(new ArrayBuffer((this.glyphs.length + 1) * offsetSize));
    if (isLocationLong) {
      locaTable.setUint32(0, 0);
    } else {
      locaTable.setUint16(0, 0);
    }
    let pos = 0;
    let locaIndex = 0;
    for (const glyph of this.glyphs) {
      pos += glyph.write(pos, glyfTable);
      pos = pos + 3 & ~3;
      locaIndex += offsetSize;
      if (isLocationLong) {
        locaTable.setUint32(locaIndex, pos);
      } else {
        locaTable.setUint16(locaIndex, pos >> 1);
      }
    }
    return {
      isLocationLong,
      loca: new Uint8Array(locaTable.buffer),
      glyf: new Uint8Array(glyfTable.buffer)
    };
  }
  scale(factors) {
    for (let i = 0, ii = this.glyphs.length; i < ii; i++) {
      this.glyphs[i].scale(factors[i]);
    }
  }
}
class Glyph {
  constructor({
    header = null,
    simple = null,
    composites = null
  }) {
    this.header = header;
    this.simple = simple;
    this.composites = composites;
  }
  static parse(pos, glyf) {
    const [read, header] = GlyphHeader.parse(pos, glyf);
    pos += read;
    if (header.numberOfContours < 0) {
      const composites = [];
      while (true) {
        const [n, composite] = CompositeGlyph.parse(pos, glyf);
        pos += n;
        composites.push(composite);
        if (!(composite.flags & MORE_COMPONENTS)) {
          break;
        }
      }
      return new Glyph({
        header,
        composites
      });
    }
    const simple = SimpleGlyph.parse(pos, glyf, header.numberOfContours);
    return new Glyph({
      header,
      simple
    });
  }
  getSize() {
    if (!this.header) {
      return 0;
    }
    const size = this.simple ? this.simple.getSize() : this.composites.reduce((a, c) => a + c.getSize(), 0);
    return this.header.getSize() + size;
  }
  write(pos, buf) {
    if (!this.header) {
      return 0;
    }
    const spos = pos;
    pos += this.header.write(pos, buf);
    if (this.simple) {
      pos += this.simple.write(pos, buf);
    } else {
      for (const composite of this.composites) {
        pos += composite.write(pos, buf);
      }
    }
    return pos - spos;
  }
  scale(factor) {
    if (!this.header) {
      return;
    }
    const xMiddle = (this.header.xMin + this.header.xMax) / 2;
    this.header.scale(xMiddle, factor);
    if (this.simple) {
      this.simple.scale(xMiddle, factor);
    } else {
      for (const composite of this.composites) {
        composite.scale(xMiddle, factor);
      }
    }
  }
}
class GlyphHeader {
  constructor({
    numberOfContours,
    xMin,
    yMin,
    xMax,
    yMax
  }) {
    this.numberOfContours = numberOfContours;
    this.xMin = xMin;
    this.yMin = yMin;
    this.xMax = xMax;
    this.yMax = yMax;
  }
  static parse(pos, glyf) {
    return [10, new GlyphHeader({
      numberOfContours: glyf.getInt16(pos),
      xMin: glyf.getInt16(pos + 2),
      yMin: glyf.getInt16(pos + 4),
      xMax: glyf.getInt16(pos + 6),
      yMax: glyf.getInt16(pos + 8)
    })];
  }
  getSize() {
    return 10;
  }
  write(pos, buf) {
    buf.setInt16(pos, this.numberOfContours);
    buf.setInt16(pos + 2, this.xMin);
    buf.setInt16(pos + 4, this.yMin);
    buf.setInt16(pos + 6, this.xMax);
    buf.setInt16(pos + 8, this.yMax);
    return 10;
  }
  scale(x, factor) {
    this.xMin = Math.round(x + (this.xMin - x) * factor);
    this.xMax = Math.round(x + (this.xMax - x) * factor);
  }
}
class Contour {
  constructor({
    flags,
    xCoordinates,
    yCoordinates
  }) {
    this.xCoordinates = xCoordinates;
    this.yCoordinates = yCoordinates;
    this.flags = flags;
  }
}
class SimpleGlyph {
  constructor({
    contours,
    instructions
  }) {
    this.contours = contours;
    this.instructions = instructions;
  }
  static parse(pos, glyf, numberOfContours) {
    const endPtsOfContours = [];
    for (let i = 0; i < numberOfContours; i++) {
      const endPt = glyf.getUint16(pos);
      pos += 2;
      endPtsOfContours.push(endPt);
    }
    const numberOfPt = endPtsOfContours[numberOfContours - 1] + 1;
    const instructionLength = glyf.getUint16(pos);
    pos += 2;
    const instructions = new Uint8Array(glyf).slice(pos, pos + instructionLength);
    pos += instructionLength;
    const flags = [];
    for (let i = 0; i < numberOfPt; pos++, i++) {
      let flag = glyf.getUint8(pos);
      flags.push(flag);
      if (flag & REPEAT_FLAG) {
        const count = glyf.getUint8(++pos);
        flag ^= REPEAT_FLAG;
        for (let m = 0; m < count; m++) {
          flags.push(flag);
        }
        i += count;
      }
    }
    const allXCoordinates = [];
    let xCoordinates = [];
    let yCoordinates = [];
    let pointFlags = [];
    const contours = [];
    let endPtsOfContoursIndex = 0;
    let lastCoordinate = 0;
    for (let i = 0; i < numberOfPt; i++) {
      const flag = flags[i];
      if (flag & X_SHORT_VECTOR) {
        const x = glyf.getUint8(pos++);
        lastCoordinate += flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR ? x : -x;
        xCoordinates.push(lastCoordinate);
      } else if (flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR) {
        xCoordinates.push(lastCoordinate);
      } else {
        lastCoordinate += glyf.getInt16(pos);
        pos += 2;
        xCoordinates.push(lastCoordinate);
      }
      if (endPtsOfContours[endPtsOfContoursIndex] === i) {
        endPtsOfContoursIndex++;
        allXCoordinates.push(xCoordinates);
        xCoordinates = [];
      }
    }
    lastCoordinate = 0;
    endPtsOfContoursIndex = 0;
    for (let i = 0; i < numberOfPt; i++) {
      const flag = flags[i];
      if (flag & Y_SHORT_VECTOR) {
        const y = glyf.getUint8(pos++);
        lastCoordinate += flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR ? y : -y;
        yCoordinates.push(lastCoordinate);
      } else if (flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR) {
        yCoordinates.push(lastCoordinate);
      } else {
        lastCoordinate += glyf.getInt16(pos);
        pos += 2;
        yCoordinates.push(lastCoordinate);
      }
      pointFlags.push(flag & ON_CURVE_POINT | flag & OVERLAP_SIMPLE);
      if (endPtsOfContours[endPtsOfContoursIndex] === i) {
        xCoordinates = allXCoordinates[endPtsOfContoursIndex];
        endPtsOfContoursIndex++;
        contours.push(new Contour({
          flags: pointFlags,
          xCoordinates,
          yCoordinates
        }));
        yCoordinates = [];
        pointFlags = [];
      }
    }
    return new SimpleGlyph({
      contours,
      instructions
    });
  }
  getSize() {
    let size = this.contours.length * 2 + 2 + this.instructions.length;
    let lastX = 0;
    let lastY = 0;
    for (const contour of this.contours) {
      size += contour.flags.length;
      for (let i = 0, ii = contour.xCoordinates.length; i < ii; i++) {
        const x = contour.xCoordinates[i];
        const y = contour.yCoordinates[i];
        let abs = Math.abs(x - lastX);
        if (abs > 255) {
          size += 2;
        } else if (abs > 0) {
          size += 1;
        }
        lastX = x;
        abs = Math.abs(y - lastY);
        if (abs > 255) {
          size += 2;
        } else if (abs > 0) {
          size += 1;
        }
        lastY = y;
      }
    }
    return size;
  }
  write(pos, buf) {
    const spos = pos;
    const xCoordinates = [];
    const yCoordinates = [];
    const flags = [];
    let lastX = 0;
    let lastY = 0;
    for (const contour of this.contours) {
      for (let i = 0, ii = contour.xCoordinates.length; i < ii; i++) {
        let flag = contour.flags[i];
        const x = contour.xCoordinates[i];
        let delta = x - lastX;
        if (delta === 0) {
          flag |= X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR;
          xCoordinates.push(0);
        } else {
          const abs = Math.abs(delta);
          if (abs <= 255) {
            flag |= delta >= 0 ? X_SHORT_VECTOR | X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR : X_SHORT_VECTOR;
            xCoordinates.push(abs);
          } else {
            xCoordinates.push(delta);
          }
        }
        lastX = x;
        const y = contour.yCoordinates[i];
        delta = y - lastY;
        if (delta === 0) {
          flag |= Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR;
          yCoordinates.push(0);
        } else {
          const abs = Math.abs(delta);
          if (abs <= 255) {
            flag |= delta >= 0 ? Y_SHORT_VECTOR | Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR : Y_SHORT_VECTOR;
            yCoordinates.push(abs);
          } else {
            yCoordinates.push(delta);
          }
        }
        lastY = y;
        flags.push(flag);
      }
      buf.setUint16(pos, xCoordinates.length - 1);
      pos += 2;
    }
    buf.setUint16(pos, this.instructions.length);
    pos += 2;
    if (this.instructions.length) {
      new Uint8Array(buf.buffer, 0, buf.buffer.byteLength).set(this.instructions, pos);
      pos += this.instructions.length;
    }
    for (const flag of flags) {
      buf.setUint8(pos++, flag);
    }
    for (let i = 0, ii = xCoordinates.length; i < ii; i++) {
      const x = xCoordinates[i];
      const flag = flags[i];
      if (flag & X_SHORT_VECTOR) {
        buf.setUint8(pos++, x);
      } else if (!(flag & X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR)) {
        buf.setInt16(pos, x);
        pos += 2;
      }
    }
    for (let i = 0, ii = yCoordinates.length; i < ii; i++) {
      const y = yCoordinates[i];
      const flag = flags[i];
      if (flag & Y_SHORT_VECTOR) {
        buf.setUint8(pos++, y);
      } else if (!(flag & Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR)) {
        buf.setInt16(pos, y);
        pos += 2;
      }
    }
    return pos - spos;
  }
  scale(x, factor) {
    for (const contour of this.contours) {
      if (contour.xCoordinates.length === 0) {
        continue;
      }
      for (let i = 0, ii = contour.xCoordinates.length; i < ii; i++) {
        contour.xCoordinates[i] = Math.round(x + (contour.xCoordinates[i] - x) * factor);
      }
    }
  }
}
class CompositeGlyph {
  constructor({
    flags,
    glyphIndex,
    argument1,
    argument2,
    transf,
    instructions
  }) {
    this.flags = flags;
    this.glyphIndex = glyphIndex;
    this.argument1 = argument1;
    this.argument2 = argument2;
    this.transf = transf;
    this.instructions = instructions;
  }
  static parse(pos, glyf) {
    const spos = pos;
    const transf = [];
    let flags = glyf.getUint16(pos);
    const glyphIndex = glyf.getUint16(pos + 2);
    pos += 4;
    let argument1, argument2;
    if (flags & ARG_1_AND_2_ARE_WORDS) {
      if (flags & ARGS_ARE_XY_VALUES) {
        argument1 = glyf.getInt16(pos);
        argument2 = glyf.getInt16(pos + 2);
      } else {
        argument1 = glyf.getUint16(pos);
        argument2 = glyf.getUint16(pos + 2);
      }
      pos += 4;
      flags ^= ARG_1_AND_2_ARE_WORDS;
    } else {
      if (flags & ARGS_ARE_XY_VALUES) {
        argument1 = glyf.getInt8(pos);
        argument2 = glyf.getInt8(pos + 1);
      } else {
        argument1 = glyf.getUint8(pos);
        argument2 = glyf.getUint8(pos + 1);
      }
      pos += 2;
    }
    if (flags & WE_HAVE_A_SCALE) {
      transf.push(glyf.getUint16(pos));
      pos += 2;
    } else if (flags & WE_HAVE_AN_X_AND_Y_SCALE) {
      transf.push(glyf.getUint16(pos), glyf.getUint16(pos + 2));
      pos += 4;
    } else if (flags & WE_HAVE_A_TWO_BY_TWO) {
      transf.push(glyf.getUint16(pos), glyf.getUint16(pos + 2), glyf.getUint16(pos + 4), glyf.getUint16(pos + 6));
      pos += 8;
    }
    let instructions = null;
    if (flags & WE_HAVE_INSTRUCTIONS) {
      const instructionLength = glyf.getUint16(pos);
      pos += 2;
      instructions = new Uint8Array(glyf).slice(pos, pos + instructionLength);
      pos += instructionLength;
    }
    return [pos - spos, new CompositeGlyph({
      flags,
      glyphIndex,
      argument1,
      argument2,
      transf,
      instructions
    })];
  }
  getSize() {
    let size = 2 + 2 + this.transf.length * 2;
    if (this.flags & WE_HAVE_INSTRUCTIONS) {
      size += 2 + this.instructions.length;
    }
    size += 2;
    if (this.flags & 2) {
      if (!(this.argument1 >= -128 && this.argument1 <= 127 && this.argument2 >= -128 && this.argument2 <= 127)) {
        size += 2;
      }
    } else if (!(this.argument1 >= 0 && this.argument1 <= 255 && this.argument2 >= 0 && this.argument2 <= 255)) {
      size += 2;
    }
    return size;
  }
  write(pos, buf) {
    const spos = pos;
    if (this.flags & ARGS_ARE_XY_VALUES) {
      if (!(this.argument1 >= -128 && this.argument1 <= 127 && this.argument2 >= -128 && this.argument2 <= 127)) {
        this.flags |= ARG_1_AND_2_ARE_WORDS;
      }
    } else if (!(this.argument1 >= 0 && this.argument1 <= 255 && this.argument2 >= 0 && this.argument2 <= 255)) {
      this.flags |= ARG_1_AND_2_ARE_WORDS;
    }
    buf.setUint16(pos, this.flags);
    buf.setUint16(pos + 2, this.glyphIndex);
    pos += 4;
    if (this.flags & ARG_1_AND_2_ARE_WORDS) {
      if (this.flags & ARGS_ARE_XY_VALUES) {
        buf.setInt16(pos, this.argument1);
        buf.setInt16(pos + 2, this.argument2);
      } else {
        buf.setUint16(pos, this.argument1);
        buf.setUint16(pos + 2, this.argument2);
      }
      pos += 4;
    } else {
      buf.setUint8(pos, this.argument1);
      buf.setUint8(pos + 1, this.argument2);
      pos += 2;
    }
    if (this.flags & WE_HAVE_INSTRUCTIONS) {
      buf.setUint16(pos, this.instructions.length);
      pos += 2;
      if (this.instructions.length) {
        new Uint8Array(buf.buffer, 0, buf.buffer.byteLength).set(this.instructions, pos);
        pos += this.instructions.length;
      }
    }
    return pos - spos;
  }
  scale(x, factor) {}
}

;// CONCATENATED MODULE: ./src/core/opentype_file_builder.js


function writeInt16(dest, offset, num) {
  dest[offset] = num >> 8 & 0xff;
  dest[offset + 1] = num & 0xff;
}
function writeInt32(dest, offset, num) {
  dest[offset] = num >> 24 & 0xff;
  dest[offset + 1] = num >> 16 & 0xff;
  dest[offset + 2] = num >> 8 & 0xff;
  dest[offset + 3] = num & 0xff;
}
function writeData(dest, offset, data) {
  if (data instanceof Uint8Array) {
    dest.set(data, offset);
  } else if (typeof data === "string") {
    for (let i = 0, ii = data.length; i < ii; i++) {
      dest[offset++] = data.charCodeAt(i) & 0xff;
    }
  } else {
    for (const num of data) {
      dest[offset++] = num & 0xff;
    }
  }
}
const OTF_HEADER_SIZE = 12;
const OTF_TABLE_ENTRY_SIZE = 16;
class OpenTypeFileBuilder {
  constructor(sfnt) {
    this.sfnt = sfnt;
    this.tables = Object.create(null);
  }
  static getSearchParams(entriesCount, entrySize) {
    let maxPower2 = 1,
      log2 = 0;
    while ((maxPower2 ^ entriesCount) > maxPower2) {
      maxPower2 <<= 1;
      log2++;
    }
    const searchRange = maxPower2 * entrySize;
    return {
      range: searchRange,
      entry: log2,
      rangeShift: entrySize * entriesCount - searchRange
    };
  }
  toArray() {
    let sfnt = this.sfnt;
    const tables = this.tables;
    const tablesNames = Object.keys(tables);
    tablesNames.sort();
    const numTables = tablesNames.length;
    let i, j, jj, table, tableName;
    let offset = OTF_HEADER_SIZE + numTables * OTF_TABLE_ENTRY_SIZE;
    const tableOffsets = [offset];
    for (i = 0; i < numTables; i++) {
      table = tables[tablesNames[i]];
      const paddedLength = (table.length + 3 & ~3) >>> 0;
      offset += paddedLength;
      tableOffsets.push(offset);
    }
    const file = new Uint8Array(offset);
    for (i = 0; i < numTables; i++) {
      table = tables[tablesNames[i]];
      writeData(file, tableOffsets[i], table);
    }
    if (sfnt === "true") {
      sfnt = string32(0x00010000);
    }
    file[0] = sfnt.charCodeAt(0) & 0xff;
    file[1] = sfnt.charCodeAt(1) & 0xff;
    file[2] = sfnt.charCodeAt(2) & 0xff;
    file[3] = sfnt.charCodeAt(3) & 0xff;
    writeInt16(file, 4, numTables);
    const searchParams = OpenTypeFileBuilder.getSearchParams(numTables, 16);
    writeInt16(file, 6, searchParams.range);
    writeInt16(file, 8, searchParams.entry);
    writeInt16(file, 10, searchParams.rangeShift);
    offset = OTF_HEADER_SIZE;
    for (i = 0; i < numTables; i++) {
      tableName = tablesNames[i];
      file[offset] = tableName.charCodeAt(0) & 0xff;
      file[offset + 1] = tableName.charCodeAt(1) & 0xff;
      file[offset + 2] = tableName.charCodeAt(2) & 0xff;
      file[offset + 3] = tableName.charCodeAt(3) & 0xff;
      let checksum = 0;
      for (j = tableOffsets[i], jj = tableOffsets[i + 1]; j < jj; j += 4) {
        const quad = readUint32(file, j);
        checksum = checksum + quad >>> 0;
      }
      writeInt32(file, offset + 4, checksum);
      writeInt32(file, offset + 8, tableOffsets[i]);
      writeInt32(file, offset + 12, tables[tableName].length);
      offset += OTF_TABLE_ENTRY_SIZE;
    }
    return file;
  }
  addTable(tag, data) {
    if (tag in this.tables) {
      throw new Error("Table " + tag + " already exists");
    }
    this.tables[tag] = data;
  }
}

;// CONCATENATED MODULE: ./src/core/type1_parser.js




const HINTING_ENABLED = false;
const COMMAND_MAP = {
  hstem: [1],
  vstem: [3],
  vmoveto: [4],
  rlineto: [5],
  hlineto: [6],
  vlineto: [7],
  rrcurveto: [8],
  callsubr: [10],
  flex: [12, 35],
  drop: [12, 18],
  endchar: [14],
  rmoveto: [21],
  hmoveto: [22],
  vhcurveto: [30],
  hvcurveto: [31]
};
class Type1CharString {
  constructor() {
    this.width = 0;
    this.lsb = 0;
    this.flexing = false;
    this.output = [];
    this.stack = [];
  }
  convert(encoded, subrs, seacAnalysisEnabled) {
    const count = encoded.length;
    let error = false;
    let wx, sbx, subrNumber;
    for (let i = 0; i < count; i++) {
      let value = encoded[i];
      if (value < 32) {
        if (value === 12) {
          value = (value << 8) + encoded[++i];
        }
        switch (value) {
          case 1:
            if (!HINTING_ENABLED) {
              this.stack = [];
              break;
            }
            error = this.executeCommand(2, COMMAND_MAP.hstem);
            break;
          case 3:
            if (!HINTING_ENABLED) {
              this.stack = [];
              break;
            }
            error = this.executeCommand(2, COMMAND_MAP.vstem);
            break;
          case 4:
            if (this.flexing) {
              if (this.stack.length < 1) {
                error = true;
                break;
              }
              const dy = this.stack.pop();
              this.stack.push(0, dy);
              break;
            }
            error = this.executeCommand(1, COMMAND_MAP.vmoveto);
            break;
          case 5:
            error = this.executeCommand(2, COMMAND_MAP.rlineto);
            break;
          case 6:
            error = this.executeCommand(1, COMMAND_MAP.hlineto);
            break;
          case 7:
            error = this.executeCommand(1, COMMAND_MAP.vlineto);
            break;
          case 8:
            error = this.executeCommand(6, COMMAND_MAP.rrcurveto);
            break;
          case 9:
            this.stack = [];
            break;
          case 10:
            if (this.stack.length < 1) {
              error = true;
              break;
            }
            subrNumber = this.stack.pop();
            if (!subrs[subrNumber]) {
              error = true;
              break;
            }
            error = this.convert(subrs[subrNumber], subrs, seacAnalysisEnabled);
            break;
          case 11:
            return error;
          case 13:
            if (this.stack.length < 2) {
              error = true;
              break;
            }
            wx = this.stack.pop();
            sbx = this.stack.pop();
            this.lsb = sbx;
            this.width = wx;
            this.stack.push(wx, sbx);
            error = this.executeCommand(2, COMMAND_MAP.hmoveto);
            break;
          case 14:
            this.output.push(COMMAND_MAP.endchar[0]);
            break;
          case 21:
            if (this.flexing) {
              break;
            }
            error = this.executeCommand(2, COMMAND_MAP.rmoveto);
            break;
          case 22:
            if (this.flexing) {
              this.stack.push(0);
              break;
            }
            error = this.executeCommand(1, COMMAND_MAP.hmoveto);
            break;
          case 30:
            error = this.executeCommand(4, COMMAND_MAP.vhcurveto);
            break;
          case 31:
            error = this.executeCommand(4, COMMAND_MAP.hvcurveto);
            break;
          case (12 << 8) + 0:
            this.stack = [];
            break;
          case (12 << 8) + 1:
            if (!HINTING_ENABLED) {
              this.stack = [];
              break;
            }
            error = this.executeCommand(2, COMMAND_MAP.vstem);
            break;
          case (12 << 8) + 2:
            if (!HINTING_ENABLED) {
              this.stack = [];
              break;
            }
            error = this.executeCommand(2, COMMAND_MAP.hstem);
            break;
          case (12 << 8) + 6:
            if (seacAnalysisEnabled) {
              const asb = this.stack.at(-5);
              this.seac = this.stack.splice(-4, 4);
              this.seac[0] += this.lsb - asb;
              error = this.executeCommand(0, COMMAND_MAP.endchar);
            } else {
              error = this.executeCommand(4, COMMAND_MAP.endchar);
            }
            break;
          case (12 << 8) + 7:
            if (this.stack.length < 4) {
              error = true;
              break;
            }
            this.stack.pop();
            wx = this.stack.pop();
            const sby = this.stack.pop();
            sbx = this.stack.pop();
            this.lsb = sbx;
            this.width = wx;
            this.stack.push(wx, sbx, sby);
            error = this.executeCommand(3, COMMAND_MAP.rmoveto);
            break;
          case (12 << 8) + 12:
            if (this.stack.length < 2) {
              error = true;
              break;
            }
            const num2 = this.stack.pop();
            const num1 = this.stack.pop();
            this.stack.push(num1 / num2);
            break;
          case (12 << 8) + 16:
            if (this.stack.length < 2) {
              error = true;
              break;
            }
            subrNumber = this.stack.pop();
            const numArgs = this.stack.pop();
            if (subrNumber === 0 && numArgs === 3) {
              const flexArgs = this.stack.splice(-17, 17);
              this.stack.push(flexArgs[2] + flexArgs[0], flexArgs[3] + flexArgs[1], flexArgs[4], flexArgs[5], flexArgs[6], flexArgs[7], flexArgs[8], flexArgs[9], flexArgs[10], flexArgs[11], flexArgs[12], flexArgs[13], flexArgs[14]);
              error = this.executeCommand(13, COMMAND_MAP.flex, true);
              this.flexing = false;
              this.stack.push(flexArgs[15], flexArgs[16]);
            } else if (subrNumber === 1 && numArgs === 0) {
              this.flexing = true;
            }
            break;
          case (12 << 8) + 17:
            break;
          case (12 << 8) + 33:
            this.stack = [];
            break;
          default:
            warn('Unknown type 1 charstring command of "' + value + '"');
            break;
        }
        if (error) {
          break;
        }
        continue;
      } else if (value <= 246) {
        value -= 139;
      } else if (value <= 250) {
        value = (value - 247) * 256 + encoded[++i] + 108;
      } else if (value <= 254) {
        value = -((value - 251) * 256) - encoded[++i] - 108;
      } else {
        value = (encoded[++i] & 0xff) << 24 | (encoded[++i] & 0xff) << 16 | (encoded[++i] & 0xff) << 8 | (encoded[++i] & 0xff) << 0;
      }
      this.stack.push(value);
    }
    return error;
  }
  executeCommand(howManyArgs, command, keepStack) {
    const stackLength = this.stack.length;
    if (howManyArgs > stackLength) {
      return true;
    }
    const start = stackLength - howManyArgs;
    for (let i = start; i < stackLength; i++) {
      let value = this.stack[i];
      if (Number.isInteger(value)) {
        this.output.push(28, value >> 8 & 0xff, value & 0xff);
      } else {
        value = 65536 * value | 0;
        this.output.push(255, value >> 24 & 0xff, value >> 16 & 0xff, value >> 8 & 0xff, value & 0xff);
      }
    }
    this.output.push(...command);
    if (keepStack) {
      this.stack.splice(start, howManyArgs);
    } else {
      this.stack.length = 0;
    }
    return false;
  }
}
const EEXEC_ENCRYPT_KEY = 55665;
const CHAR_STRS_ENCRYPT_KEY = 4330;
function isHexDigit(code) {
  return code >= 48 && code <= 57 || code >= 65 && code <= 70 || code >= 97 && code <= 102;
}
function decrypt(data, key, discardNumber) {
  if (discardNumber >= data.length) {
    return new Uint8Array(0);
  }
  const c1 = 52845,
    c2 = 22719;
  let r = key | 0,
    i,
    j;
  for (i = 0; i < discardNumber; i++) {
    r = (data[i] + r) * c1 + c2 & (1 << 16) - 1;
  }
  const count = data.length - discardNumber;
  const decrypted = new Uint8Array(count);
  for (i = discardNumber, j = 0; j < count; i++, j++) {
    const value = data[i];
    decrypted[j] = value ^ r >> 8;
    r = (value + r) * c1 + c2 & (1 << 16) - 1;
  }
  return decrypted;
}
function decryptAscii(data, key, discardNumber) {
  const c1 = 52845,
    c2 = 22719;
  let r = key | 0;
  const count = data.length,
    maybeLength = count >>> 1;
  const decrypted = new Uint8Array(maybeLength);
  let i, j;
  for (i = 0, j = 0; i < count; i++) {
    const digit1 = data[i];
    if (!isHexDigit(digit1)) {
      continue;
    }
    i++;
    let digit2;
    while (i < count && !isHexDigit(digit2 = data[i])) {
      i++;
    }
    if (i < count) {
      const value = parseInt(String.fromCharCode(digit1, digit2), 16);
      decrypted[j++] = value ^ r >> 8;
      r = (value + r) * c1 + c2 & (1 << 16) - 1;
    }
  }
  return decrypted.slice(discardNumber, j);
}
function isSpecial(c) {
  return c === 0x2f || c === 0x5b || c === 0x5d || c === 0x7b || c === 0x7d || c === 0x28 || c === 0x29;
}
class Type1Parser {
  constructor(stream, encrypted, seacAnalysisEnabled) {
    if (encrypted) {
      const data = stream.getBytes();
      const isBinary = !((isHexDigit(data[0]) || isWhiteSpace(data[0])) && isHexDigit(data[1]) && isHexDigit(data[2]) && isHexDigit(data[3]) && isHexDigit(data[4]) && isHexDigit(data[5]) && isHexDigit(data[6]) && isHexDigit(data[7]));
      stream = new Stream(isBinary ? decrypt(data, EEXEC_ENCRYPT_KEY, 4) : decryptAscii(data, EEXEC_ENCRYPT_KEY, 4));
    }
    this.seacAnalysisEnabled = !!seacAnalysisEnabled;
    this.stream = stream;
    this.nextChar();
  }
  readNumberArray() {
    this.getToken();
    const array = [];
    while (true) {
      const token = this.getToken();
      if (token === null || token === "]" || token === "}") {
        break;
      }
      array.push(parseFloat(token || 0));
    }
    return array;
  }
  readNumber() {
    const token = this.getToken();
    return parseFloat(token || 0);
  }
  readInt() {
    const token = this.getToken();
    return parseInt(token || 0, 10) | 0;
  }
  readBoolean() {
    const token = this.getToken();
    return token === "true" ? 1 : 0;
  }
  nextChar() {
    return this.currentChar = this.stream.getByte();
  }
  prevChar() {
    this.stream.skip(-2);
    return this.currentChar = this.stream.getByte();
  }
  getToken() {
    let comment = false;
    let ch = this.currentChar;
    while (true) {
      if (ch === -1) {
        return null;
      }
      if (comment) {
        if (ch === 0x0a || ch === 0x0d) {
          comment = false;
        }
      } else if (ch === 0x25) {
        comment = true;
      } else if (!isWhiteSpace(ch)) {
        break;
      }
      ch = this.nextChar();
    }
    if (isSpecial(ch)) {
      this.nextChar();
      return String.fromCharCode(ch);
    }
    let token = "";
    do {
      token += String.fromCharCode(ch);
      ch = this.nextChar();
    } while (ch >= 0 && !isWhiteSpace(ch) && !isSpecial(ch));
    return token;
  }
  readCharStrings(bytes, lenIV) {
    if (lenIV === -1) {
      return bytes;
    }
    return decrypt(bytes, CHAR_STRS_ENCRYPT_KEY, lenIV);
  }
  extractFontProgram(properties) {
    const stream = this.stream;
    const subrs = [],
      charstrings = [];
    const privateData = Object.create(null);
    privateData.lenIV = 4;
    const program = {
      subrs: [],
      charstrings: [],
      properties: {
        privateData
      }
    };
    let token, length, data, lenIV;
    while ((token = this.getToken()) !== null) {
      if (token !== "/") {
        continue;
      }
      token = this.getToken();
      switch (token) {
        case "CharStrings":
          this.getToken();
          this.getToken();
          this.getToken();
          this.getToken();
          while (true) {
            token = this.getToken();
            if (token === null || token === "end") {
              break;
            }
            if (token !== "/") {
              continue;
            }
            const glyph = this.getToken();
            length = this.readInt();
            this.getToken();
            data = length > 0 ? stream.getBytes(length) : new Uint8Array(0);
            lenIV = program.properties.privateData.lenIV;
            const encoded = this.readCharStrings(data, lenIV);
            this.nextChar();
            token = this.getToken();
            if (token === "noaccess") {
              this.getToken();
            } else if (token === "/") {
              this.prevChar();
            }
            charstrings.push({
              glyph,
              encoded
            });
          }
          break;
        case "Subrs":
          this.readInt();
          this.getToken();
          while (this.getToken() === "dup") {
            const index = this.readInt();
            length = this.readInt();
            this.getToken();
            data = length > 0 ? stream.getBytes(length) : new Uint8Array(0);
            lenIV = program.properties.privateData.lenIV;
            const encoded = this.readCharStrings(data, lenIV);
            this.nextChar();
            token = this.getToken();
            if (token === "noaccess") {
              this.getToken();
            }
            subrs[index] = encoded;
          }
          break;
        case "BlueValues":
        case "OtherBlues":
        case "FamilyBlues":
        case "FamilyOtherBlues":
          const blueArray = this.readNumberArray();
          if (blueArray.length > 0 && blueArray.length % 2 === 0 && HINTING_ENABLED) {
            program.properties.privateData[token] = blueArray;
          }
          break;
        case "StemSnapH":
        case "StemSnapV":
          program.properties.privateData[token] = this.readNumberArray();
          break;
        case "StdHW":
        case "StdVW":
          program.properties.privateData[token] = this.readNumberArray()[0];
          break;
        case "BlueShift":
        case "lenIV":
        case "BlueFuzz":
        case "BlueScale":
        case "LanguageGroup":
          program.properties.privateData[token] = this.readNumber();
          break;
        case "ExpansionFactor":
          program.properties.privateData[token] = this.readNumber() || 0.06;
          break;
        case "ForceBold":
          program.properties.privateData[token] = this.readBoolean();
          break;
      }
    }
    for (const {
      encoded,
      glyph
    } of charstrings) {
      const charString = new Type1CharString();
      const error = charString.convert(encoded, subrs, this.seacAnalysisEnabled);
      let output = charString.output;
      if (error) {
        output = [14];
      }
      const charStringObject = {
        glyphName: glyph,
        charstring: output,
        width: charString.width,
        lsb: charString.lsb,
        seac: charString.seac
      };
      if (glyph === ".notdef") {
        program.charstrings.unshift(charStringObject);
      } else {
        program.charstrings.push(charStringObject);
      }
      if (properties.builtInEncoding) {
        const index = properties.builtInEncoding.indexOf(glyph);
        if (index > -1 && properties.widths[index] === undefined && index >= properties.firstChar && index <= properties.lastChar) {
          properties.widths[index] = charString.width;
        }
      }
    }
    return program;
  }
  extractFontHeader(properties) {
    let token;
    while ((token = this.getToken()) !== null) {
      if (token !== "/") {
        continue;
      }
      token = this.getToken();
      switch (token) {
        case "FontMatrix":
          const matrix = this.readNumberArray();
          properties.fontMatrix = matrix;
          break;
        case "Encoding":
          const encodingArg = this.getToken();
          let encoding;
          if (!/^\d+$/.test(encodingArg)) {
            encoding = getEncoding(encodingArg);
          } else {
            encoding = [];
            const size = parseInt(encodingArg, 10) | 0;
            this.getToken();
            for (let j = 0; j < size; j++) {
              token = this.getToken();
              while (token !== "dup" && token !== "def") {
                token = this.getToken();
                if (token === null) {
                  return;
                }
              }
              if (token === "def") {
                break;
              }
              const index = this.readInt();
              this.getToken();
              const glyph = this.getToken();
              encoding[index] = glyph;
              this.getToken();
            }
          }
          properties.builtInEncoding = encoding;
          break;
        case "FontBBox":
          const fontBBox = this.readNumberArray();
          properties.ascent = Math.max(fontBBox[3], fontBBox[1]);
          properties.descent = Math.min(fontBBox[1], fontBBox[3]);
          properties.ascentScaled = true;
          break;
      }
    }
  }
}

;// CONCATENATED MODULE: ./src/core/type1_font.js






function findBlock(streamBytes, signature, startIndex) {
  const streamBytesLength = streamBytes.length;
  const signatureLength = signature.length;
  const scanLength = streamBytesLength - signatureLength;
  let i = startIndex,
    found = false;
  while (i < scanLength) {
    let j = 0;
    while (j < signatureLength && streamBytes[i + j] === signature[j]) {
      j++;
    }
    if (j >= signatureLength) {
      i += j;
      while (i < streamBytesLength && isWhiteSpace(streamBytes[i])) {
        i++;
      }
      found = true;
      break;
    }
    i++;
  }
  return {
    found,
    length: i
  };
}
function getHeaderBlock(stream, suggestedLength) {
  const EEXEC_SIGNATURE = [0x65, 0x65, 0x78, 0x65, 0x63];
  const streamStartPos = stream.pos;
  let headerBytes, headerBytesLength, block;
  try {
    headerBytes = stream.getBytes(suggestedLength);
    headerBytesLength = headerBytes.length;
  } catch {}
  if (headerBytesLength === suggestedLength) {
    block = findBlock(headerBytes, EEXEC_SIGNATURE, suggestedLength - 2 * EEXEC_SIGNATURE.length);
    if (block.found && block.length === suggestedLength) {
      return {
        stream: new Stream(headerBytes),
        length: suggestedLength
      };
    }
  }
  warn('Invalid "Length1" property in Type1 font -- trying to recover.');
  stream.pos = streamStartPos;
  const SCAN_BLOCK_LENGTH = 2048;
  let actualLength;
  while (true) {
    const scanBytes = stream.peekBytes(SCAN_BLOCK_LENGTH);
    block = findBlock(scanBytes, EEXEC_SIGNATURE, 0);
    if (block.length === 0) {
      break;
    }
    stream.pos += block.length;
    if (block.found) {
      actualLength = stream.pos - streamStartPos;
      break;
    }
  }
  stream.pos = streamStartPos;
  if (actualLength) {
    return {
      stream: new Stream(stream.getBytes(actualLength)),
      length: actualLength
    };
  }
  warn('Unable to recover "Length1" property in Type1 font -- using as is.');
  return {
    stream: new Stream(stream.getBytes(suggestedLength)),
    length: suggestedLength
  };
}
function getEexecBlock(stream, suggestedLength) {
  const eexecBytes = stream.getBytes();
  if (eexecBytes.length === 0) {
    throw new FormatError("getEexecBlock - no font program found.");
  }
  return {
    stream: new Stream(eexecBytes),
    length: eexecBytes.length
  };
}
class Type1Font {
  constructor(name, file, properties) {
    const PFB_HEADER_SIZE = 6;
    let headerBlockLength = properties.length1;
    let eexecBlockLength = properties.length2;
    let pfbHeader = file.peekBytes(PFB_HEADER_SIZE);
    const pfbHeaderPresent = pfbHeader[0] === 0x80 && pfbHeader[1] === 0x01;
    if (pfbHeaderPresent) {
      file.skip(PFB_HEADER_SIZE);
      headerBlockLength = pfbHeader[5] << 24 | pfbHeader[4] << 16 | pfbHeader[3] << 8 | pfbHeader[2];
    }
    const headerBlock = getHeaderBlock(file, headerBlockLength);
    const headerBlockParser = new Type1Parser(headerBlock.stream, false, SEAC_ANALYSIS_ENABLED);
    headerBlockParser.extractFontHeader(properties);
    if (pfbHeaderPresent) {
      pfbHeader = file.getBytes(PFB_HEADER_SIZE);
      eexecBlockLength = pfbHeader[5] << 24 | pfbHeader[4] << 16 | pfbHeader[3] << 8 | pfbHeader[2];
    }
    const eexecBlock = getEexecBlock(file, eexecBlockLength);
    const eexecBlockParser = new Type1Parser(eexecBlock.stream, true, SEAC_ANALYSIS_ENABLED);
    const data = eexecBlockParser.extractFontProgram(properties);
    for (const key in data.properties) {
      properties[key] = data.properties[key];
    }
    const charstrings = data.charstrings;
    const type2Charstrings = this.getType2Charstrings(charstrings);
    const subrs = this.getType2Subrs(data.subrs);
    this.charstrings = charstrings;
    this.data = this.wrap(name, type2Charstrings, this.charstrings, subrs, properties);
    this.seacs = this.getSeacs(data.charstrings);
  }
  get numGlyphs() {
    return this.charstrings.length + 1;
  }
  getCharset() {
    const charset = [".notdef"];
    for (const {
      glyphName
    } of this.charstrings) {
      charset.push(glyphName);
    }
    return charset;
  }
  getGlyphMapping(properties) {
    const charstrings = this.charstrings;
    if (properties.composite) {
      const charCodeToGlyphId = Object.create(null);
      for (let glyphId = 0, charstringsLen = charstrings.length; glyphId < charstringsLen; glyphId++) {
        const charCode = properties.cMap.charCodeOf(glyphId);
        charCodeToGlyphId[charCode] = glyphId + 1;
      }
      return charCodeToGlyphId;
    }
    const glyphNames = [".notdef"];
    let builtInEncoding, glyphId;
    for (glyphId = 0; glyphId < charstrings.length; glyphId++) {
      glyphNames.push(charstrings[glyphId].glyphName);
    }
    const encoding = properties.builtInEncoding;
    if (encoding) {
      builtInEncoding = Object.create(null);
      for (const charCode in encoding) {
        glyphId = glyphNames.indexOf(encoding[charCode]);
        if (glyphId >= 0) {
          builtInEncoding[charCode] = glyphId;
        }
      }
    }
    return type1FontGlyphMapping(properties, builtInEncoding, glyphNames);
  }
  hasGlyphId(id) {
    if (id < 0 || id >= this.numGlyphs) {
      return false;
    }
    if (id === 0) {
      return true;
    }
    const glyph = this.charstrings[id - 1];
    return glyph.charstring.length > 0;
  }
  getSeacs(charstrings) {
    const seacMap = [];
    for (let i = 0, ii = charstrings.length; i < ii; i++) {
      const charstring = charstrings[i];
      if (charstring.seac) {
        seacMap[i + 1] = charstring.seac;
      }
    }
    return seacMap;
  }
  getType2Charstrings(type1Charstrings) {
    const type2Charstrings = [];
    for (const type1Charstring of type1Charstrings) {
      type2Charstrings.push(type1Charstring.charstring);
    }
    return type2Charstrings;
  }
  getType2Subrs(type1Subrs) {
    let bias = 0;
    const count = type1Subrs.length;
    if (count < 1133) {
      bias = 107;
    } else if (count < 33769) {
      bias = 1131;
    } else {
      bias = 32768;
    }
    const type2Subrs = [];
    let i;
    for (i = 0; i < bias; i++) {
      type2Subrs.push([0x0b]);
    }
    for (i = 0; i < count; i++) {
      type2Subrs.push(type1Subrs[i]);
    }
    return type2Subrs;
  }
  wrap(name, glyphs, charstrings, subrs, properties) {
    const cff = new CFF();
    cff.header = new CFFHeader(1, 0, 4, 4);
    cff.names = [name];
    const topDict = new CFFTopDict();
    topDict.setByName("version", 391);
    topDict.setByName("Notice", 392);
    topDict.setByName("FullName", 393);
    topDict.setByName("FamilyName", 394);
    topDict.setByName("Weight", 395);
    topDict.setByName("Encoding", null);
    topDict.setByName("FontMatrix", properties.fontMatrix);
    topDict.setByName("FontBBox", properties.bbox);
    topDict.setByName("charset", null);
    topDict.setByName("CharStrings", null);
    topDict.setByName("Private", null);
    cff.topDict = topDict;
    const strings = new CFFStrings();
    strings.add("Version 0.11");
    strings.add("See original notice");
    strings.add(name);
    strings.add(name);
    strings.add("Medium");
    cff.strings = strings;
    cff.globalSubrIndex = new CFFIndex();
    const count = glyphs.length;
    const charsetArray = [".notdef"];
    let i, ii;
    for (i = 0; i < count; i++) {
      const glyphName = charstrings[i].glyphName;
      const index = CFFStandardStrings.indexOf(glyphName);
      if (index === -1) {
        strings.add(glyphName);
      }
      charsetArray.push(glyphName);
    }
    cff.charset = new CFFCharset(false, 0, charsetArray);
    const charStringsIndex = new CFFIndex();
    charStringsIndex.add([0x8b, 0x0e]);
    for (i = 0; i < count; i++) {
      charStringsIndex.add(glyphs[i]);
    }
    cff.charStrings = charStringsIndex;
    const privateDict = new CFFPrivateDict();
    privateDict.setByName("Subrs", null);
    const fields = ["BlueValues", "OtherBlues", "FamilyBlues", "FamilyOtherBlues", "StemSnapH", "StemSnapV", "BlueShift", "BlueFuzz", "BlueScale", "LanguageGroup", "ExpansionFactor", "ForceBold", "StdHW", "StdVW"];
    for (i = 0, ii = fields.length; i < ii; i++) {
      const field = fields[i];
      if (!(field in properties.privateData)) {
        continue;
      }
      const value = properties.privateData[field];
      if (Array.isArray(value)) {
        for (let j = value.length - 1; j > 0; j--) {
          value[j] -= value[j - 1];
        }
      }
      privateDict.setByName(field, value);
    }
    cff.topDict.privateDict = privateDict;
    const subrIndex = new CFFIndex();
    for (i = 0, ii = subrs.length; i < ii; i++) {
      subrIndex.add(subrs[i]);
    }
    privateDict.subrsIndex = subrIndex;
    const compiler = new CFFCompiler(cff);
    return compiler.compile();
  }
}

;// CONCATENATED MODULE: ./src/core/fonts.js

















const PRIVATE_USE_AREAS = [[0xe000, 0xf8ff], [0x100000, 0x10fffd]];
const PDF_GLYPH_SPACE_UNITS = 1000;
const EXPORT_DATA_PROPERTIES = ["ascent", "bbox", "black", "bold", "charProcOperatorList", "composite", "cssFontInfo", "data", "defaultVMetrics", "defaultWidth", "descent", "fallbackName", "fontMatrix", "isInvalidPDFjsFont", "isType3Font", "italic", "loadedName", "mimetype", "missingFile", "name", "remeasure", "subtype", "systemFontInfo", "type", "vertical"];
const EXPORT_DATA_EXTRA_PROPERTIES = ["cMap", "defaultEncoding", "differences", "isMonospace", "isSerifFont", "isSymbolicFont", "seacMap", "toFontChar", "toUnicode", "vmetrics", "widths"];
function adjustWidths(properties) {
  if (!properties.fontMatrix) {
    return;
  }
  if (properties.fontMatrix[0] === FONT_IDENTITY_MATRIX[0]) {
    return;
  }
  const scale = 0.001 / properties.fontMatrix[0];
  const glyphsWidths = properties.widths;
  for (const glyph in glyphsWidths) {
    glyphsWidths[glyph] *= scale;
  }
  properties.defaultWidth *= scale;
}
function adjustTrueTypeToUnicode(properties, isSymbolicFont, nameRecords) {
  if (properties.isInternalFont) {
    return;
  }
  if (properties.hasIncludedToUnicodeMap) {
    return;
  }
  if (properties.hasEncoding) {
    return;
  }
  if (properties.toUnicode instanceof IdentityToUnicodeMap) {
    return;
  }
  if (!isSymbolicFont) {
    return;
  }
  if (nameRecords.length === 0) {
    return;
  }
  if (properties.defaultEncoding === WinAnsiEncoding) {
    return;
  }
  for (const r of nameRecords) {
    if (!isWinNameRecord(r)) {
      return;
    }
  }
  const encoding = WinAnsiEncoding;
  const toUnicode = [],
    glyphsUnicodeMap = getGlyphsUnicode();
  for (const charCode in encoding) {
    const glyphName = encoding[charCode];
    if (glyphName === "") {
      continue;
    }
    const unicode = glyphsUnicodeMap[glyphName];
    if (unicode === undefined) {
      continue;
    }
    toUnicode[charCode] = String.fromCharCode(unicode);
  }
  if (toUnicode.length > 0) {
    properties.toUnicode.amend(toUnicode);
  }
}
function adjustType1ToUnicode(properties, builtInEncoding) {
  if (properties.isInternalFont) {
    return;
  }
  if (properties.hasIncludedToUnicodeMap) {
    return;
  }
  if (builtInEncoding === properties.defaultEncoding) {
    return;
  }
  if (properties.toUnicode instanceof IdentityToUnicodeMap) {
    return;
  }
  const toUnicode = [],
    glyphsUnicodeMap = getGlyphsUnicode();
  for (const charCode in builtInEncoding) {
    if (properties.hasEncoding) {
      if (properties.baseEncodingName || properties.differences[charCode] !== undefined) {
        continue;
      }
    }
    const glyphName = builtInEncoding[charCode];
    const unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
    if (unicode !== -1) {
      toUnicode[charCode] = String.fromCharCode(unicode);
    }
  }
  if (toUnicode.length > 0) {
    properties.toUnicode.amend(toUnicode);
  }
}
function amendFallbackToUnicode(properties) {
  if (!properties.fallbackToUnicode) {
    return;
  }
  if (properties.toUnicode instanceof IdentityToUnicodeMap) {
    return;
  }
  const toUnicode = [];
  for (const charCode in properties.fallbackToUnicode) {
    if (properties.toUnicode.has(charCode)) {
      continue;
    }
    toUnicode[charCode] = properties.fallbackToUnicode[charCode];
  }
  if (toUnicode.length > 0) {
    properties.toUnicode.amend(toUnicode);
  }
}
class fonts_Glyph {
  constructor(originalCharCode, fontChar, unicode, accent, width, vmetric, operatorListId, isSpace, isInFont) {
    this.originalCharCode = originalCharCode;
    this.fontChar = fontChar;
    this.unicode = unicode;
    this.accent = accent;
    this.width = width;
    this.vmetric = vmetric;
    this.operatorListId = operatorListId;
    this.isSpace = isSpace;
    this.isInFont = isInFont;
  }
  get category() {
    return shadow(this, "category", getCharUnicodeCategory(this.unicode), true);
  }
}
function int16(b0, b1) {
  return (b0 << 8) + b1;
}
function writeSignedInt16(bytes, index, value) {
  bytes[index + 1] = value;
  bytes[index] = value >>> 8;
}
function signedInt16(b0, b1) {
  const value = (b0 << 8) + b1;
  return value & 1 << 15 ? value - 0x10000 : value;
}
function writeUint32(bytes, index, value) {
  bytes[index + 3] = value & 0xff;
  bytes[index + 2] = value >>> 8;
  bytes[index + 1] = value >>> 16;
  bytes[index] = value >>> 24;
}
function int32(b0, b1, b2, b3) {
  return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
}
function string16(value) {
  return String.fromCharCode(value >> 8 & 0xff, value & 0xff);
}
function safeString16(value) {
  if (value > 0x7fff) {
    value = 0x7fff;
  } else if (value < -0x8000) {
    value = -0x8000;
  }
  return String.fromCharCode(value >> 8 & 0xff, value & 0xff);
}
function isTrueTypeFile(file) {
  const header = file.peekBytes(4);
  return readUint32(header, 0) === 0x00010000 || bytesToString(header) === "true";
}
function isTrueTypeCollectionFile(file) {
  const header = file.peekBytes(4);
  return bytesToString(header) === "ttcf";
}
function isOpenTypeFile(file) {
  const header = file.peekBytes(4);
  return bytesToString(header) === "OTTO";
}
function isType1File(file) {
  const header = file.peekBytes(2);
  if (header[0] === 0x25 && header[1] === 0x21) {
    return true;
  }
  if (header[0] === 0x80 && header[1] === 0x01) {
    return true;
  }
  return false;
}
function isCFFFile(file) {
  const header = file.peekBytes(4);
  if (header[0] >= 1 && header[3] >= 1 && header[3] <= 4) {
    return true;
  }
  return false;
}
function getFontFileType(file, {
  type,
  subtype,
  composite
}) {
  let fileType, fileSubtype;
  if (isTrueTypeFile(file) || isTrueTypeCollectionFile(file)) {
    fileType = composite ? "CIDFontType2" : "TrueType";
  } else if (isOpenTypeFile(file)) {
    fileType = composite ? "CIDFontType2" : "OpenType";
  } else if (isType1File(file)) {
    if (composite) {
      fileType = "CIDFontType0";
    } else {
      fileType = type === "MMType1" ? "MMType1" : "Type1";
    }
  } else if (isCFFFile(file)) {
    if (composite) {
      fileType = "CIDFontType0";
      fileSubtype = "CIDFontType0C";
    } else {
      fileType = type === "MMType1" ? "MMType1" : "Type1";
      fileSubtype = "Type1C";
    }
  } else {
    warn("getFontFileType: Unable to detect correct font file Type/Subtype.");
    fileType = type;
    fileSubtype = subtype;
  }
  return [fileType, fileSubtype];
}
function applyStandardFontGlyphMap(map, glyphMap) {
  for (const charCode in glyphMap) {
    map[+charCode] = glyphMap[charCode];
  }
}
function buildToFontChar(encoding, glyphsUnicodeMap, differences) {
  const toFontChar = [];
  let unicode;
  for (let i = 0, ii = encoding.length; i < ii; i++) {
    unicode = getUnicodeForGlyph(encoding[i], glyphsUnicodeMap);
    if (unicode !== -1) {
      toFontChar[i] = unicode;
    }
  }
  for (const charCode in differences) {
    unicode = getUnicodeForGlyph(differences[charCode], glyphsUnicodeMap);
    if (unicode !== -1) {
      toFontChar[+charCode] = unicode;
    }
  }
  return toFontChar;
}
function isMacNameRecord(r) {
  return r.platform === 1 && r.encoding === 0 && r.language === 0;
}
function isWinNameRecord(r) {
  return r.platform === 3 && r.encoding === 1 && r.language === 0x409;
}
function convertCidString(charCode, cid, shouldThrow = false) {
  switch (cid.length) {
    case 1:
      return cid.charCodeAt(0);
    case 2:
      return cid.charCodeAt(0) << 8 | cid.charCodeAt(1);
  }
  const msg = `Unsupported CID string (charCode ${charCode}): "${cid}".`;
  if (shouldThrow) {
    throw new FormatError(msg);
  }
  warn(msg);
  return cid;
}
function adjustMapping(charCodeToGlyphId, hasGlyph, newGlyphZeroId, toUnicode) {
  const newMap = Object.create(null);
  const toUnicodeExtraMap = new Map();
  const toFontChar = [];
  const usedGlyphIds = new Set();
  let privateUseAreaIndex = 0;
  const privateUseOffetStart = PRIVATE_USE_AREAS[privateUseAreaIndex][0];
  let nextAvailableFontCharCode = privateUseOffetStart;
  let privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1];
  const isInPrivateArea = code => PRIVATE_USE_AREAS[0][0] <= code && code <= PRIVATE_USE_AREAS[0][1] || PRIVATE_USE_AREAS[1][0] <= code && code <= PRIVATE_USE_AREAS[1][1];
  for (const originalCharCode in charCodeToGlyphId) {
    let glyphId = charCodeToGlyphId[originalCharCode];
    if (!hasGlyph(glyphId)) {
      continue;
    }
    if (nextAvailableFontCharCode > privateUseOffetEnd) {
      privateUseAreaIndex++;
      if (privateUseAreaIndex >= PRIVATE_USE_AREAS.length) {
        warn("Ran out of space in font private use area.");
        break;
      }
      nextAvailableFontCharCode = PRIVATE_USE_AREAS[privateUseAreaIndex][0];
      privateUseOffetEnd = PRIVATE_USE_AREAS[privateUseAreaIndex][1];
    }
    const fontCharCode = nextAvailableFontCharCode++;
    if (glyphId === 0) {
      glyphId = newGlyphZeroId;
    }
    let unicode = toUnicode.get(originalCharCode);
    if (typeof unicode === "string") {
      unicode = unicode.codePointAt(0);
    }
    if (unicode && !isInPrivateArea(unicode) && !usedGlyphIds.has(glyphId)) {
      toUnicodeExtraMap.set(unicode, glyphId);
      usedGlyphIds.add(glyphId);
    }
    newMap[fontCharCode] = glyphId;
    toFontChar[originalCharCode] = fontCharCode;
  }
  return {
    toFontChar,
    charCodeToGlyphId: newMap,
    toUnicodeExtraMap,
    nextAvailableFontCharCode
  };
}
function getRanges(glyphs, toUnicodeExtraMap, numGlyphs) {
  const codes = [];
  for (const charCode in glyphs) {
    if (glyphs[charCode] >= numGlyphs) {
      continue;
    }
    codes.push({
      fontCharCode: charCode | 0,
      glyphId: glyphs[charCode]
    });
  }
  if (toUnicodeExtraMap) {
    for (const [unicode, glyphId] of toUnicodeExtraMap) {
      if (glyphId >= numGlyphs) {
        continue;
      }
      codes.push({
        fontCharCode: unicode,
        glyphId
      });
    }
  }
  if (codes.length === 0) {
    codes.push({
      fontCharCode: 0,
      glyphId: 0
    });
  }
  codes.sort(function fontGetRangesSort(a, b) {
    return a.fontCharCode - b.fontCharCode;
  });
  const ranges = [];
  const length = codes.length;
  for (let n = 0; n < length;) {
    const start = codes[n].fontCharCode;
    const codeIndices = [codes[n].glyphId];
    ++n;
    let end = start;
    while (n < length && end + 1 === codes[n].fontCharCode) {
      codeIndices.push(codes[n].glyphId);
      ++end;
      ++n;
      if (end === 0xffff) {
        break;
      }
    }
    ranges.push([start, end, codeIndices]);
  }
  return ranges;
}
function createCmapTable(glyphs, toUnicodeExtraMap, numGlyphs) {
  const ranges = getRanges(glyphs, toUnicodeExtraMap, numGlyphs);
  const numTables = ranges.at(-1)[1] > 0xffff ? 2 : 1;
  let cmap = "\x00\x00" + string16(numTables) + "\x00\x03" + "\x00\x01" + string32(4 + numTables * 8);
  let i, ii, j, jj;
  for (i = ranges.length - 1; i >= 0; --i) {
    if (ranges[i][0] <= 0xffff) {
      break;
    }
  }
  const bmpLength = i + 1;
  if (ranges[i][0] < 0xffff && ranges[i][1] === 0xffff) {
    ranges[i][1] = 0xfffe;
  }
  const trailingRangesCount = ranges[i][1] < 0xffff ? 1 : 0;
  const segCount = bmpLength + trailingRangesCount;
  const searchParams = OpenTypeFileBuilder.getSearchParams(segCount, 2);
  let startCount = "";
  let endCount = "";
  let idDeltas = "";
  let idRangeOffsets = "";
  let glyphsIds = "";
  let bias = 0;
  let range, start, end, codes;
  for (i = 0, ii = bmpLength; i < ii; i++) {
    range = ranges[i];
    start = range[0];
    end = range[1];
    startCount += string16(start);
    endCount += string16(end);
    codes = range[2];
    let contiguous = true;
    for (j = 1, jj = codes.length; j < jj; ++j) {
      if (codes[j] !== codes[j - 1] + 1) {
        contiguous = false;
        break;
      }
    }
    if (!contiguous) {
      const offset = (segCount - i) * 2 + bias * 2;
      bias += end - start + 1;
      idDeltas += string16(0);
      idRangeOffsets += string16(offset);
      for (j = 0, jj = codes.length; j < jj; ++j) {
        glyphsIds += string16(codes[j]);
      }
    } else {
      const startCode = codes[0];
      idDeltas += string16(startCode - start & 0xffff);
      idRangeOffsets += string16(0);
    }
  }
  if (trailingRangesCount > 0) {
    endCount += "\xFF\xFF";
    startCount += "\xFF\xFF";
    idDeltas += "\x00\x01";
    idRangeOffsets += "\x00\x00";
  }
  const format314 = "\x00\x00" + string16(2 * segCount) + string16(searchParams.range) + string16(searchParams.entry) + string16(searchParams.rangeShift) + endCount + "\x00\x00" + startCount + idDeltas + idRangeOffsets + glyphsIds;
  let format31012 = "";
  let header31012 = "";
  if (numTables > 1) {
    cmap += "\x00\x03" + "\x00\x0A" + string32(4 + numTables * 8 + 4 + format314.length);
    format31012 = "";
    for (i = 0, ii = ranges.length; i < ii; i++) {
      range = ranges[i];
      start = range[0];
      codes = range[2];
      let code = codes[0];
      for (j = 1, jj = codes.length; j < jj; ++j) {
        if (codes[j] !== codes[j - 1] + 1) {
          end = range[0] + j - 1;
          format31012 += string32(start) + string32(end) + string32(code);
          start = end + 1;
          code = codes[j];
        }
      }
      format31012 += string32(start) + string32(range[1]) + string32(code);
    }
    header31012 = "\x00\x0C" + "\x00\x00" + string32(format31012.length + 16) + "\x00\x00\x00\x00" + string32(format31012.length / 12);
  }
  return cmap + "\x00\x04" + string16(format314.length + 4) + format314 + header31012 + format31012;
}
function validateOS2Table(os2, file) {
  file.pos = (file.start || 0) + os2.offset;
  const version = file.getUint16();
  file.skip(60);
  const selection = file.getUint16();
  if (version < 4 && selection & 0x0300) {
    return false;
  }
  const firstChar = file.getUint16();
  const lastChar = file.getUint16();
  if (firstChar > lastChar) {
    return false;
  }
  file.skip(6);
  const usWinAscent = file.getUint16();
  if (usWinAscent === 0) {
    return false;
  }
  os2.data[8] = os2.data[9] = 0;
  return true;
}
function createOS2Table(properties, charstrings, override) {
  override ||= {
    unitsPerEm: 0,
    yMax: 0,
    yMin: 0,
    ascent: 0,
    descent: 0
  };
  let ulUnicodeRange1 = 0;
  let ulUnicodeRange2 = 0;
  let ulUnicodeRange3 = 0;
  let ulUnicodeRange4 = 0;
  let firstCharIndex = null;
  let lastCharIndex = 0;
  let position = -1;
  if (charstrings) {
    for (let code in charstrings) {
      code |= 0;
      if (firstCharIndex > code || !firstCharIndex) {
        firstCharIndex = code;
      }
      if (lastCharIndex < code) {
        lastCharIndex = code;
      }
      position = getUnicodeRangeFor(code, position);
      if (position < 32) {
        ulUnicodeRange1 |= 1 << position;
      } else if (position < 64) {
        ulUnicodeRange2 |= 1 << position - 32;
      } else if (position < 96) {
        ulUnicodeRange3 |= 1 << position - 64;
      } else if (position < 123) {
        ulUnicodeRange4 |= 1 << position - 96;
      } else {
        throw new FormatError("Unicode ranges Bits > 123 are reserved for internal usage");
      }
    }
    if (lastCharIndex > 0xffff) {
      lastCharIndex = 0xffff;
    }
  } else {
    firstCharIndex = 0;
    lastCharIndex = 255;
  }
  const bbox = properties.bbox || [0, 0, 0, 0];
  const unitsPerEm = override.unitsPerEm || (properties.fontMatrix ? 1 / Math.max(...properties.fontMatrix.slice(0, 4).map(Math.abs)) : 1000);
  const scale = properties.ascentScaled ? 1.0 : unitsPerEm / PDF_GLYPH_SPACE_UNITS;
  const typoAscent = override.ascent || Math.round(scale * (properties.ascent || bbox[3]));
  let typoDescent = override.descent || Math.round(scale * (properties.descent || bbox[1]));
  if (typoDescent > 0 && properties.descent > 0 && bbox[1] < 0) {
    typoDescent = -typoDescent;
  }
  const winAscent = override.yMax || typoAscent;
  const winDescent = -override.yMin || -typoDescent;
  return "\x00\x03" + "\x02\x24" + "\x01\xF4" + "\x00\x05" + "\x00\x00" + "\x02\x8A" + "\x02\xBB" + "\x00\x00" + "\x00\x8C" + "\x02\x8A" + "\x02\xBB" + "\x00\x00" + "\x01\xDF" + "\x00\x31" + "\x01\x02" + "\x00\x00" + "\x00\x00\x06" + String.fromCharCode(properties.fixedPitch ? 0x09 : 0x00) + "\x00\x00\x00\x00\x00\x00" + string32(ulUnicodeRange1) + string32(ulUnicodeRange2) + string32(ulUnicodeRange3) + string32(ulUnicodeRange4) + "\x2A\x32\x31\x2A" + string16(properties.italicAngle ? 1 : 0) + string16(firstCharIndex || properties.firstChar) + string16(lastCharIndex || properties.lastChar) + string16(typoAscent) + string16(typoDescent) + "\x00\x64" + string16(winAscent) + string16(winDescent) + "\x00\x00\x00\x00" + "\x00\x00\x00\x00" + string16(properties.xHeight) + string16(properties.capHeight) + string16(0) + string16(firstCharIndex || properties.firstChar) + "\x00\x03";
}
function createPostTable(properties) {
  const angle = Math.floor(properties.italicAngle * 2 ** 16);
  return "\x00\x03\x00\x00" + string32(angle) + "\x00\x00" + "\x00\x00" + string32(properties.fixedPitch ? 1 : 0) + "\x00\x00\x00\x00" + "\x00\x00\x00\x00" + "\x00\x00\x00\x00" + "\x00\x00\x00\x00";
}
function createPostscriptName(name) {
  return name.replaceAll(/[^\x21-\x7E]|[[\](){}<>/%]/g, "").slice(0, 63);
}
function createNameTable(name, proto) {
  if (!proto) {
    proto = [[], []];
  }
  const strings = [proto[0][0] || "Original licence", proto[0][1] || name, proto[0][2] || "Unknown", proto[0][3] || "uniqueID", proto[0][4] || name, proto[0][5] || "Version 0.11", proto[0][6] || createPostscriptName(name), proto[0][7] || "Unknown", proto[0][8] || "Unknown", proto[0][9] || "Unknown"];
  const stringsUnicode = [];
  let i, ii, j, jj, str;
  for (i = 0, ii = strings.length; i < ii; i++) {
    str = proto[1][i] || strings[i];
    const strBufUnicode = [];
    for (j = 0, jj = str.length; j < jj; j++) {
      strBufUnicode.push(string16(str.charCodeAt(j)));
    }
    stringsUnicode.push(strBufUnicode.join(""));
  }
  const names = [strings, stringsUnicode];
  const platforms = ["\x00\x01", "\x00\x03"];
  const encodings = ["\x00\x00", "\x00\x01"];
  const languages = ["\x00\x00", "\x04\x09"];
  const namesRecordCount = strings.length * platforms.length;
  let nameTable = "\x00\x00" + string16(namesRecordCount) + string16(namesRecordCount * 12 + 6);
  let strOffset = 0;
  for (i = 0, ii = platforms.length; i < ii; i++) {
    const strs = names[i];
    for (j = 0, jj = strs.length; j < jj; j++) {
      str = strs[j];
      const nameRecord = platforms[i] + encodings[i] + languages[i] + string16(j) + string16(str.length) + string16(strOffset);
      nameTable += nameRecord;
      strOffset += str.length;
    }
  }
  nameTable += strings.join("") + stringsUnicode.join("");
  return nameTable;
}
class Font {
  constructor(name, file, properties) {
    this.name = name;
    this.psName = null;
    this.mimetype = null;
    this.disableFontFace = false;
    this.loadedName = properties.loadedName;
    this.isType3Font = properties.isType3Font;
    this.missingFile = false;
    this.cssFontInfo = properties.cssFontInfo;
    this._charsCache = Object.create(null);
    this._glyphCache = Object.create(null);
    let isSerifFont = !!(properties.flags & FontFlags.Serif);
    if (!isSerifFont && !properties.isSimulatedFlags) {
      const baseName = name.replaceAll(/[,_]/g, "-").split("-", 1)[0],
        serifFonts = getSerifFonts();
      for (const namePart of baseName.split("+")) {
        if (serifFonts[namePart]) {
          isSerifFont = true;
          break;
        }
      }
    }
    this.isSerifFont = isSerifFont;
    this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
    this.isMonospace = !!(properties.flags & FontFlags.FixedPitch);
    let {
      type,
      subtype
    } = properties;
    this.type = type;
    this.subtype = subtype;
    this.systemFontInfo = properties.systemFontInfo;
    const matches = name.match(/^InvalidPDFjsFont_(.*)_\d+$/);
    this.isInvalidPDFjsFont = !!matches;
    if (this.isInvalidPDFjsFont) {
      this.fallbackName = matches[1];
    } else if (this.isMonospace) {
      this.fallbackName = "monospace";
    } else if (this.isSerifFont) {
      this.fallbackName = "serif";
    } else {
      this.fallbackName = "sans-serif";
    }
    if (this.systemFontInfo?.guessFallback) {
      this.systemFontInfo.guessFallback = false;
      this.systemFontInfo.css += `,${this.fallbackName}`;
    }
    this.differences = properties.differences;
    this.widths = properties.widths;
    this.defaultWidth = properties.defaultWidth;
    this.composite = properties.composite;
    this.cMap = properties.cMap;
    this.capHeight = properties.capHeight / PDF_GLYPH_SPACE_UNITS;
    this.ascent = properties.ascent / PDF_GLYPH_SPACE_UNITS;
    this.descent = properties.descent / PDF_GLYPH_SPACE_UNITS;
    this.lineHeight = this.ascent - this.descent;
    this.fontMatrix = properties.fontMatrix;
    this.bbox = properties.bbox;
    this.defaultEncoding = properties.defaultEncoding;
    this.toUnicode = properties.toUnicode;
    this.toFontChar = [];
    if (properties.type === "Type3") {
      for (let charCode = 0; charCode < 256; charCode++) {
        this.toFontChar[charCode] = this.differences[charCode] || properties.defaultEncoding[charCode];
      }
      return;
    }
    this.cidEncoding = properties.cidEncoding || "";
    this.vertical = !!properties.vertical;
    if (this.vertical) {
      this.vmetrics = properties.vmetrics;
      this.defaultVMetrics = properties.defaultVMetrics;
    }
    if (!file || file.isEmpty) {
      if (file) {
        warn('Font file is empty in "' + name + '" (' + this.loadedName + ")");
      }
      this.fallbackToSystemFont(properties);
      return;
    }
    [type, subtype] = getFontFileType(file, properties);
    if (type !== this.type || subtype !== this.subtype) {
      info("Inconsistent font file Type/SubType, expected: " + `${this.type}/${this.subtype} but found: ${type}/${subtype}.`);
    }
    let data;
    try {
      switch (type) {
        case "MMType1":
          info("MMType1 font (" + name + "), falling back to Type1.");
        case "Type1":
        case "CIDFontType0":
          this.mimetype = "font/opentype";
          const cff = subtype === "Type1C" || subtype === "CIDFontType0C" ? new CFFFont(file, properties) : new Type1Font(name, file, properties);
          adjustWidths(properties);
          data = this.convert(name, cff, properties);
          break;
        case "OpenType":
        case "TrueType":
        case "CIDFontType2":
          this.mimetype = "font/opentype";
          data = this.checkAndRepair(name, file, properties);
          if (this.isOpenType) {
            adjustWidths(properties);
            type = "OpenType";
          }
          break;
        default:
          throw new FormatError(`Font ${type} is not supported`);
      }
    } catch (e) {
      warn(e);
      this.fallbackToSystemFont(properties);
      return;
    }
    amendFallbackToUnicode(properties);
    this.data = data;
    this.type = type;
    this.subtype = subtype;
    this.fontMatrix = properties.fontMatrix;
    this.widths = properties.widths;
    this.defaultWidth = properties.defaultWidth;
    this.toUnicode = properties.toUnicode;
    this.seacMap = properties.seacMap;
  }
  get renderer() {
    const renderer = FontRendererFactory.create(this, SEAC_ANALYSIS_ENABLED);
    return shadow(this, "renderer", renderer);
  }
  exportData(extraProperties = false) {
    const exportDataProperties = extraProperties ? [...EXPORT_DATA_PROPERTIES, ...EXPORT_DATA_EXTRA_PROPERTIES] : EXPORT_DATA_PROPERTIES;
    const data = Object.create(null);
    let property, value;
    for (property of exportDataProperties) {
      value = this[property];
      if (value !== undefined) {
        data[property] = value;
      }
    }
    return data;
  }
  fallbackToSystemFont(properties) {
    this.missingFile = true;
    const {
      name,
      type
    } = this;
    let fontName = normalizeFontName(name);
    const stdFontMap = getStdFontMap(),
      nonStdFontMap = getNonStdFontMap();
    const isStandardFont = !!stdFontMap[fontName];
    const isMappedToStandardFont = !!(nonStdFontMap[fontName] && stdFontMap[nonStdFontMap[fontName]]);
    fontName = stdFontMap[fontName] || nonStdFontMap[fontName] || fontName;
    const fontBasicMetricsMap = getFontBasicMetrics();
    const metrics = fontBasicMetricsMap[fontName];
    if (metrics) {
      if (isNaN(this.ascent)) {
        this.ascent = metrics.ascent / PDF_GLYPH_SPACE_UNITS;
      }
      if (isNaN(this.descent)) {
        this.descent = metrics.descent / PDF_GLYPH_SPACE_UNITS;
      }
      if (isNaN(this.capHeight)) {
        this.capHeight = metrics.capHeight / PDF_GLYPH_SPACE_UNITS;
      }
    }
    this.bold = /bold/gi.test(fontName);
    this.italic = /oblique|italic/gi.test(fontName);
    this.black = /Black/g.test(name);
    const isNarrow = /Narrow/g.test(name);
    this.remeasure = (!isStandardFont || isNarrow) && Object.keys(this.widths).length > 0;
    if ((isStandardFont || isMappedToStandardFont) && type === "CIDFontType2" && this.cidEncoding.startsWith("Identity-")) {
      const cidToGidMap = properties.cidToGidMap;
      const map = [];
      applyStandardFontGlyphMap(map, getGlyphMapForStandardFonts());
      if (/Arial-?Black/i.test(name)) {
        applyStandardFontGlyphMap(map, getSupplementalGlyphMapForArialBlack());
      } else if (/Calibri/i.test(name)) {
        applyStandardFontGlyphMap(map, getSupplementalGlyphMapForCalibri());
      }
      if (cidToGidMap) {
        for (const charCode in map) {
          const cid = map[charCode];
          if (cidToGidMap[cid] !== undefined) {
            map[+charCode] = cidToGidMap[cid];
          }
        }
        if (cidToGidMap.length !== this.toUnicode.length && properties.hasIncludedToUnicodeMap && this.toUnicode instanceof IdentityToUnicodeMap) {
          this.toUnicode.forEach(function (charCode, unicodeCharCode) {
            const cid = map[charCode];
            if (cidToGidMap[cid] === undefined) {
              map[+charCode] = unicodeCharCode;
            }
          });
        }
      }
      if (!(this.toUnicode instanceof IdentityToUnicodeMap)) {
        this.toUnicode.forEach(function (charCode, unicodeCharCode) {
          map[+charCode] = unicodeCharCode;
        });
      }
      this.toFontChar = map;
      this.toUnicode = new ToUnicodeMap(map);
    } else if (/Symbol/i.test(fontName)) {
      this.toFontChar = buildToFontChar(SymbolSetEncoding, getGlyphsUnicode(), this.differences);
    } else if (/Dingbats/i.test(fontName)) {
      this.toFontChar = buildToFontChar(ZapfDingbatsEncoding, getDingbatsGlyphsUnicode(), this.differences);
    } else if (isStandardFont || isMappedToStandardFont) {
      const map = buildToFontChar(this.defaultEncoding, getGlyphsUnicode(), this.differences);
      if (type === "CIDFontType2" && !this.cidEncoding.startsWith("Identity-") && !(this.toUnicode instanceof IdentityToUnicodeMap)) {
        this.toUnicode.forEach(function (charCode, unicodeCharCode) {
          map[+charCode] = unicodeCharCode;
        });
      }
      this.toFontChar = map;
    } else {
      const glyphsUnicodeMap = getGlyphsUnicode();
      const map = [];
      this.toUnicode.forEach((charCode, unicodeCharCode) => {
        if (!this.composite) {
          const glyphName = this.differences[charCode] || this.defaultEncoding[charCode];
          const unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
          if (unicode !== -1) {
            unicodeCharCode = unicode;
          }
        }
        map[+charCode] = unicodeCharCode;
      });
      if (this.composite && this.toUnicode instanceof IdentityToUnicodeMap) {
        if (/Tahoma|Verdana/i.test(name)) {
          applyStandardFontGlyphMap(map, getGlyphMapForStandardFonts());
        }
      }
      this.toFontChar = map;
    }
    amendFallbackToUnicode(properties);
    this.loadedName = fontName.split("-", 1)[0];
  }
  checkAndRepair(name, font, properties) {
    const VALID_TABLES = ["OS/2", "cmap", "head", "hhea", "hmtx", "maxp", "name", "post", "loca", "glyf", "fpgm", "prep", "cvt ", "CFF "];
    function readTables(file, numTables) {
      const tables = Object.create(null);
      tables["OS/2"] = null;
      tables.cmap = null;
      tables.head = null;
      tables.hhea = null;
      tables.hmtx = null;
      tables.maxp = null;
      tables.name = null;
      tables.post = null;
      for (let i = 0; i < numTables; i++) {
        const table = readTableEntry(file);
        if (!VALID_TABLES.includes(table.tag)) {
          continue;
        }
        if (table.length === 0) {
          continue;
        }
        tables[table.tag] = table;
      }
      return tables;
    }
    function readTableEntry(file) {
      const tag = file.getString(4);
      const checksum = file.getInt32() >>> 0;
      const offset = file.getInt32() >>> 0;
      const length = file.getInt32() >>> 0;
      const previousPosition = file.pos;
      file.pos = file.start || 0;
      file.skip(offset);
      const data = file.getBytes(length);
      file.pos = previousPosition;
      if (tag === "head") {
        data[8] = data[9] = data[10] = data[11] = 0;
        data[17] |= 0x20;
      }
      return {
        tag,
        checksum,
        length,
        offset,
        data
      };
    }
    function readOpenTypeHeader(ttf) {
      return {
        version: ttf.getString(4),
        numTables: ttf.getUint16(),
        searchRange: ttf.getUint16(),
        entrySelector: ttf.getUint16(),
        rangeShift: ttf.getUint16()
      };
    }
    function readTrueTypeCollectionHeader(ttc) {
      const ttcTag = ttc.getString(4);
      assert(ttcTag === "ttcf", "Must be a TrueType Collection font.");
      const majorVersion = ttc.getUint16();
      const minorVersion = ttc.getUint16();
      const numFonts = ttc.getInt32() >>> 0;
      const offsetTable = [];
      for (let i = 0; i < numFonts; i++) {
        offsetTable.push(ttc.getInt32() >>> 0);
      }
      const header = {
        ttcTag,
        majorVersion,
        minorVersion,
        numFonts,
        offsetTable
      };
      switch (majorVersion) {
        case 1:
          return header;
        case 2:
          header.dsigTag = ttc.getInt32() >>> 0;
          header.dsigLength = ttc.getInt32() >>> 0;
          header.dsigOffset = ttc.getInt32() >>> 0;
          return header;
      }
      throw new FormatError(`Invalid TrueType Collection majorVersion: ${majorVersion}.`);
    }
    function readTrueTypeCollectionData(ttc, fontName) {
      const {
        numFonts,
        offsetTable
      } = readTrueTypeCollectionHeader(ttc);
      const fontNameParts = fontName.split("+");
      let fallbackData;
      for (let i = 0; i < numFonts; i++) {
        ttc.pos = (ttc.start || 0) + offsetTable[i];
        const potentialHeader = readOpenTypeHeader(ttc);
        const potentialTables = readTables(ttc, potentialHeader.numTables);
        if (!potentialTables.name) {
          throw new FormatError('TrueType Collection font must contain a "name" table.');
        }
        const [nameTable] = readNameTable(potentialTables.name);
        for (let j = 0, jj = nameTable.length; j < jj; j++) {
          for (let k = 0, kk = nameTable[j].length; k < kk; k++) {
            const nameEntry = nameTable[j][k]?.replaceAll(/\s/g, "");
            if (!nameEntry) {
              continue;
            }
            if (nameEntry === fontName) {
              return {
                header: potentialHeader,
                tables: potentialTables
              };
            }
            if (fontNameParts.length < 2) {
              continue;
            }
            for (const part of fontNameParts) {
              if (nameEntry === part) {
                fallbackData = {
                  name: part,
                  header: potentialHeader,
                  tables: potentialTables
                };
              }
            }
          }
        }
      }
      if (fallbackData) {
        warn(`TrueType Collection does not contain "${fontName}" font, ` + `falling back to "${fallbackData.name}" font instead.`);
        return {
          header: fallbackData.header,
          tables: fallbackData.tables
        };
      }
      throw new FormatError(`TrueType Collection does not contain "${fontName}" font.`);
    }
    function readCmapTable(cmap, file, isSymbolicFont, hasEncoding) {
      if (!cmap) {
        warn("No cmap table available.");
        return {
          platformId: -1,
          encodingId: -1,
          mappings: [],
          hasShortCmap: false
        };
      }
      let segment;
      let start = (file.start || 0) + cmap.offset;
      file.pos = start;
      file.skip(2);
      const numTables = file.getUint16();
      let potentialTable;
      let canBreak = false;
      for (let i = 0; i < numTables; i++) {
        const platformId = file.getUint16();
        const encodingId = file.getUint16();
        const offset = file.getInt32() >>> 0;
        let useTable = false;
        if (potentialTable?.platformId === platformId && potentialTable?.encodingId === encodingId) {
          continue;
        }
        if (platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 3)) {
          useTable = true;
        } else if (platformId === 1 && encodingId === 0) {
          useTable = true;
        } else if (platformId === 3 && encodingId === 1 && (hasEncoding || !potentialTable)) {
          useTable = true;
          if (!isSymbolicFont) {
            canBreak = true;
          }
        } else if (isSymbolicFont && platformId === 3 && encodingId === 0) {
          useTable = true;
          let correctlySorted = true;
          if (i < numTables - 1) {
            const nextBytes = file.peekBytes(2),
              nextPlatformId = int16(nextBytes[0], nextBytes[1]);
            if (nextPlatformId < platformId) {
              correctlySorted = false;
            }
          }
          if (correctlySorted) {
            canBreak = true;
          }
        }
        if (useTable) {
          potentialTable = {
            platformId,
            encodingId,
            offset
          };
        }
        if (canBreak) {
          break;
        }
      }
      if (potentialTable) {
        file.pos = start + potentialTable.offset;
      }
      if (!potentialTable || file.peekByte() === -1) {
        warn("Could not find a preferred cmap table.");
        return {
          platformId: -1,
          encodingId: -1,
          mappings: [],
          hasShortCmap: false
        };
      }
      const format = file.getUint16();
      let hasShortCmap = false;
      const mappings = [];
      let j, glyphId;
      if (format === 0) {
        file.skip(2 + 2);
        for (j = 0; j < 256; j++) {
          const index = file.getByte();
          if (!index) {
            continue;
          }
          mappings.push({
            charCode: j,
            glyphId: index
          });
        }
        hasShortCmap = true;
      } else if (format === 2) {
        file.skip(2 + 2);
        const subHeaderKeys = [];
        let maxSubHeaderKey = 0;
        for (let i = 0; i < 256; i++) {
          const subHeaderKey = file.getUint16() >> 3;
          subHeaderKeys.push(subHeaderKey);
          maxSubHeaderKey = Math.max(subHeaderKey, maxSubHeaderKey);
        }
        const subHeaders = [];
        for (let i = 0; i <= maxSubHeaderKey; i++) {
          subHeaders.push({
            firstCode: file.getUint16(),
            entryCount: file.getUint16(),
            idDelta: signedInt16(file.getByte(), file.getByte()),
            idRangePos: file.pos + file.getUint16()
          });
        }
        for (let i = 0; i < 256; i++) {
          if (subHeaderKeys[i] === 0) {
            file.pos = subHeaders[0].idRangePos + 2 * i;
            glyphId = file.getUint16();
            mappings.push({
              charCode: i,
              glyphId
            });
          } else {
            const s = subHeaders[subHeaderKeys[i]];
            for (j = 0; j < s.entryCount; j++) {
              const charCode = (i << 8) + j + s.firstCode;
              file.pos = s.idRangePos + 2 * j;
              glyphId = file.getUint16();
              if (glyphId !== 0) {
                glyphId = (glyphId + s.idDelta) % 65536;
              }
              mappings.push({
                charCode,
                glyphId
              });
            }
          }
        }
      } else if (format === 4) {
        file.skip(2 + 2);
        const segCount = file.getUint16() >> 1;
        file.skip(6);
        const segments = [];
        let segIndex;
        for (segIndex = 0; segIndex < segCount; segIndex++) {
          segments.push({
            end: file.getUint16()
          });
        }
        file.skip(2);
        for (segIndex = 0; segIndex < segCount; segIndex++) {
          segments[segIndex].start = file.getUint16();
        }
        for (segIndex = 0; segIndex < segCount; segIndex++) {
          segments[segIndex].delta = file.getUint16();
        }
        let offsetsCount = 0,
          offsetIndex;
        for (segIndex = 0; segIndex < segCount; segIndex++) {
          segment = segments[segIndex];
          const rangeOffset = file.getUint16();
          if (!rangeOffset) {
            segment.offsetIndex = -1;
            continue;
          }
          offsetIndex = (rangeOffset >> 1) - (segCount - segIndex);
          segment.offsetIndex = offsetIndex;
          offsetsCount = Math.max(offsetsCount, offsetIndex + segment.end - segment.start + 1);
        }
        const offsets = [];
        for (j = 0; j < offsetsCount; j++) {
          offsets.push(file.getUint16());
        }
        for (segIndex = 0; segIndex < segCount; segIndex++) {
          segment = segments[segIndex];
          start = segment.start;
          const end = segment.end;
          const delta = segment.delta;
          offsetIndex = segment.offsetIndex;
          for (j = start; j <= end; j++) {
            if (j === 0xffff) {
              continue;
            }
            glyphId = offsetIndex < 0 ? j : offsets[offsetIndex + j - start];
            glyphId = glyphId + delta & 0xffff;
            mappings.push({
              charCode: j,
              glyphId
            });
          }
        }
      } else if (format === 6) {
        file.skip(2 + 2);
        const firstCode = file.getUint16();
        const entryCount = file.getUint16();
        for (j = 0; j < entryCount; j++) {
          glyphId = file.getUint16();
          const charCode = firstCode + j;
          mappings.push({
            charCode,
            glyphId
          });
        }
      } else if (format === 12) {
        file.skip(2 + 4 + 4);
        const nGroups = file.getInt32() >>> 0;
        for (j = 0; j < nGroups; j++) {
          const startCharCode = file.getInt32() >>> 0;
          const endCharCode = file.getInt32() >>> 0;
          let glyphCode = file.getInt32() >>> 0;
          for (let charCode = startCharCode; charCode <= endCharCode; charCode++) {
            mappings.push({
              charCode,
              glyphId: glyphCode++
            });
          }
        }
      } else {
        warn("cmap table has unsupported format: " + format);
        return {
          platformId: -1,
          encodingId: -1,
          mappings: [],
          hasShortCmap: false
        };
      }
      mappings.sort(function (a, b) {
        return a.charCode - b.charCode;
      });
      for (let i = 1; i < mappings.length; i++) {
        if (mappings[i - 1].charCode === mappings[i].charCode) {
          mappings.splice(i, 1);
          i--;
        }
      }
      return {
        platformId: potentialTable.platformId,
        encodingId: potentialTable.encodingId,
        mappings,
        hasShortCmap
      };
    }
    function sanitizeMetrics(file, header, metrics, headTable, numGlyphs, dupFirstEntry) {
      if (!header) {
        if (metrics) {
          metrics.data = null;
        }
        return;
      }
      file.pos = (file.start || 0) + header.offset;
      file.pos += 4;
      file.pos += 2;
      file.pos += 2;
      file.pos += 2;
      file.pos += 2;
      file.pos += 2;
      file.pos += 2;
      file.pos += 2;
      file.pos += 2;
      file.pos += 2;
      const caretOffset = file.getUint16();
      file.pos += 8;
      file.pos += 2;
      let numOfMetrics = file.getUint16();
      if (caretOffset !== 0) {
        const macStyle = int16(headTable.data[44], headTable.data[45]);
        if (!(macStyle & 2)) {
          header.data[22] = 0;
          header.data[23] = 0;
        }
      }
      if (numOfMetrics > numGlyphs) {
        info(`The numOfMetrics (${numOfMetrics}) should not be ` + `greater than the numGlyphs (${numGlyphs}).`);
        numOfMetrics = numGlyphs;
        header.data[34] = (numOfMetrics & 0xff00) >> 8;
        header.data[35] = numOfMetrics & 0x00ff;
      }
      const numOfSidebearings = numGlyphs - numOfMetrics;
      const numMissing = numOfSidebearings - (metrics.length - numOfMetrics * 4 >> 1);
      if (numMissing > 0) {
        const entries = new Uint8Array(metrics.length + numMissing * 2);
        entries.set(metrics.data);
        if (dupFirstEntry) {
          entries[metrics.length] = metrics.data[2];
          entries[metrics.length + 1] = metrics.data[3];
        }
        metrics.data = entries;
      }
    }
    function sanitizeGlyph(source, sourceStart, sourceEnd, dest, destStart, hintsValid) {
      const glyphProfile = {
        length: 0,
        sizeOfInstructions: 0
      };
      if (sourceStart < 0 || sourceStart >= source.length || sourceEnd > source.length || sourceEnd - sourceStart <= 12) {
        return glyphProfile;
      }
      const glyf = source.subarray(sourceStart, sourceEnd);
      const xMin = signedInt16(glyf[2], glyf[3]);
      const yMin = signedInt16(glyf[4], glyf[5]);
      const xMax = signedInt16(glyf[6], glyf[7]);
      const yMax = signedInt16(glyf[8], glyf[9]);
      if (xMin > xMax) {
        writeSignedInt16(glyf, 2, xMax);
        writeSignedInt16(glyf, 6, xMin);
      }
      if (yMin > yMax) {
        writeSignedInt16(glyf, 4, yMax);
        writeSignedInt16(glyf, 8, yMin);
      }
      const contoursCount = signedInt16(glyf[0], glyf[1]);
      if (contoursCount < 0) {
        if (contoursCount < -1) {
          return glyphProfile;
        }
        dest.set(glyf, destStart);
        glyphProfile.length = glyf.length;
        return glyphProfile;
      }
      let i,
        j = 10,
        flagsCount = 0;
      for (i = 0; i < contoursCount; i++) {
        const endPoint = glyf[j] << 8 | glyf[j + 1];
        flagsCount = endPoint + 1;
        j += 2;
      }
      const instructionsStart = j;
      const instructionsLength = glyf[j] << 8 | glyf[j + 1];
      glyphProfile.sizeOfInstructions = instructionsLength;
      j += 2 + instructionsLength;
      const instructionsEnd = j;
      let coordinatesLength = 0;
      for (i = 0; i < flagsCount; i++) {
        const flag = glyf[j++];
        if (flag & 0xc0) {
          glyf[j - 1] = flag & 0x3f;
        }
        let xLength = 2;
        if (flag & 2) {
          xLength = 1;
        } else if (flag & 16) {
          xLength = 0;
        }
        let yLength = 2;
        if (flag & 4) {
          yLength = 1;
        } else if (flag & 32) {
          yLength = 0;
        }
        const xyLength = xLength + yLength;
        coordinatesLength += xyLength;
        if (flag & 8) {
          const repeat = glyf[j++];
          if (repeat === 0) {
            glyf[j - 1] ^= 8;
          }
          i += repeat;
          coordinatesLength += repeat * xyLength;
        }
      }
      if (coordinatesLength === 0) {
        return glyphProfile;
      }
      let glyphDataLength = j + coordinatesLength;
      if (glyphDataLength > glyf.length) {
        return glyphProfile;
      }
      if (!hintsValid && instructionsLength > 0) {
        dest.set(glyf.subarray(0, instructionsStart), destStart);
        dest.set([0, 0], destStart + instructionsStart);
        dest.set(glyf.subarray(instructionsEnd, glyphDataLength), destStart + instructionsStart + 2);
        glyphDataLength -= instructionsLength;
        if (glyf.length - glyphDataLength > 3) {
          glyphDataLength = glyphDataLength + 3 & ~3;
        }
        glyphProfile.length = glyphDataLength;
        return glyphProfile;
      }
      if (glyf.length - glyphDataLength > 3) {
        glyphDataLength = glyphDataLength + 3 & ~3;
        dest.set(glyf.subarray(0, glyphDataLength), destStart);
        glyphProfile.length = glyphDataLength;
        return glyphProfile;
      }
      dest.set(glyf, destStart);
      glyphProfile.length = glyf.length;
      return glyphProfile;
    }
    function sanitizeHead(head, numGlyphs, locaLength) {
      const data = head.data;
      const version = int32(data[0], data[1], data[2], data[3]);
      if (version >> 16 !== 1) {
        info("Attempting to fix invalid version in head table: " + version);
        data[0] = 0;
        data[1] = 1;
        data[2] = 0;
        data[3] = 0;
      }
      const indexToLocFormat = int16(data[50], data[51]);
      if (indexToLocFormat < 0 || indexToLocFormat > 1) {
        info("Attempting to fix invalid indexToLocFormat in head table: " + indexToLocFormat);
        const numGlyphsPlusOne = numGlyphs + 1;
        if (locaLength === numGlyphsPlusOne << 1) {
          data[50] = 0;
          data[51] = 0;
        } else if (locaLength === numGlyphsPlusOne << 2) {
          data[50] = 0;
          data[51] = 1;
        } else {
          throw new FormatError("Could not fix indexToLocFormat: " + indexToLocFormat);
        }
      }
    }
    function sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong, hintsValid, dupFirstEntry, maxSizeOfInstructions) {
      let itemSize, itemDecode, itemEncode;
      if (isGlyphLocationsLong) {
        itemSize = 4;
        itemDecode = function fontItemDecodeLong(data, offset) {
          return data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3];
        };
        itemEncode = function fontItemEncodeLong(data, offset, value) {
          data[offset] = value >>> 24 & 0xff;
          data[offset + 1] = value >> 16 & 0xff;
          data[offset + 2] = value >> 8 & 0xff;
          data[offset + 3] = value & 0xff;
        };
      } else {
        itemSize = 2;
        itemDecode = function fontItemDecode(data, offset) {
          return data[offset] << 9 | data[offset + 1] << 1;
        };
        itemEncode = function fontItemEncode(data, offset, value) {
          data[offset] = value >> 9 & 0xff;
          data[offset + 1] = value >> 1 & 0xff;
        };
      }
      const numGlyphsOut = dupFirstEntry ? numGlyphs + 1 : numGlyphs;
      const locaDataSize = itemSize * (1 + numGlyphsOut);
      const locaData = new Uint8Array(locaDataSize);
      locaData.set(loca.data.subarray(0, locaDataSize));
      loca.data = locaData;
      const oldGlyfData = glyf.data;
      const oldGlyfDataLength = oldGlyfData.length;
      const newGlyfData = new Uint8Array(oldGlyfDataLength);
      let i, j;
      const locaEntries = [];
      for (i = 0, j = 0; i < numGlyphs + 1; i++, j += itemSize) {
        let offset = itemDecode(locaData, j);
        if (offset > oldGlyfDataLength) {
          offset = oldGlyfDataLength;
        }
        locaEntries.push({
          index: i,
          offset,
          endOffset: 0
        });
      }
      locaEntries.sort((a, b) => a.offset - b.offset);
      for (i = 0; i < numGlyphs; i++) {
        locaEntries[i].endOffset = locaEntries[i + 1].offset;
      }
      locaEntries.sort((a, b) => a.index - b.index);
      for (i = 0; i < numGlyphs; i++) {
        const {
          offset,
          endOffset
        } = locaEntries[i];
        if (offset !== 0 || endOffset !== 0) {
          break;
        }
        const nextOffset = locaEntries[i + 1].offset;
        if (nextOffset === 0) {
          continue;
        }
        locaEntries[i].endOffset = nextOffset;
        break;
      }
      const last = locaEntries.at(-2);
      if (last.offset !== 0 && last.endOffset === 0) {
        last.endOffset = oldGlyfDataLength;
      }
      const missingGlyphs = Object.create(null);
      let writeOffset = 0;
      itemEncode(locaData, 0, writeOffset);
      for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
        const glyphProfile = sanitizeGlyph(oldGlyfData, locaEntries[i].offset, locaEntries[i].endOffset, newGlyfData, writeOffset, hintsValid);
        const newLength = glyphProfile.length;
        if (newLength === 0) {
          missingGlyphs[i] = true;
        }
        if (glyphProfile.sizeOfInstructions > maxSizeOfInstructions) {
          maxSizeOfInstructions = glyphProfile.sizeOfInstructions;
        }
        writeOffset += newLength;
        itemEncode(locaData, j, writeOffset);
      }
      if (writeOffset === 0) {
        const simpleGlyph = new Uint8Array([0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 49, 0]);
        for (i = 0, j = itemSize; i < numGlyphsOut; i++, j += itemSize) {
          itemEncode(locaData, j, simpleGlyph.length);
        }
        glyf.data = simpleGlyph;
      } else if (dupFirstEntry) {
        const firstEntryLength = itemDecode(locaData, itemSize);
        if (newGlyfData.length > firstEntryLength + writeOffset) {
          glyf.data = newGlyfData.subarray(0, firstEntryLength + writeOffset);
        } else {
          glyf.data = new Uint8Array(firstEntryLength + writeOffset);
          glyf.data.set(newGlyfData.subarray(0, writeOffset));
        }
        glyf.data.set(newGlyfData.subarray(0, firstEntryLength), writeOffset);
        itemEncode(loca.data, locaData.length - itemSize, writeOffset + firstEntryLength);
      } else {
        glyf.data = newGlyfData.subarray(0, writeOffset);
      }
      return {
        missingGlyphs,
        maxSizeOfInstructions
      };
    }
    function readPostScriptTable(post, propertiesObj, maxpNumGlyphs) {
      const start = (font.start || 0) + post.offset;
      font.pos = start;
      const length = post.length,
        end = start + length;
      const version = font.getInt32();
      font.skip(28);
      let glyphNames;
      let valid = true;
      let i;
      switch (version) {
        case 0x00010000:
          glyphNames = MacStandardGlyphOrdering;
          break;
        case 0x00020000:
          const numGlyphs = font.getUint16();
          if (numGlyphs !== maxpNumGlyphs) {
            valid = false;
            break;
          }
          const glyphNameIndexes = [];
          for (i = 0; i < numGlyphs; ++i) {
            const index = font.getUint16();
            if (index >= 32768) {
              valid = false;
              break;
            }
            glyphNameIndexes.push(index);
          }
          if (!valid) {
            break;
          }
          const customNames = [],
            strBuf = [];
          while (font.pos < end) {
            const stringLength = font.getByte();
            strBuf.length = stringLength;
            for (i = 0; i < stringLength; ++i) {
              strBuf[i] = String.fromCharCode(font.getByte());
            }
            customNames.push(strBuf.join(""));
          }
          glyphNames = [];
          for (i = 0; i < numGlyphs; ++i) {
            const j = glyphNameIndexes[i];
            if (j < 258) {
              glyphNames.push(MacStandardGlyphOrdering[j]);
              continue;
            }
            glyphNames.push(customNames[j - 258]);
          }
          break;
        case 0x00030000:
          break;
        default:
          warn("Unknown/unsupported post table version " + version);
          valid = false;
          if (propertiesObj.defaultEncoding) {
            glyphNames = propertiesObj.defaultEncoding;
          }
          break;
      }
      propertiesObj.glyphNames = glyphNames;
      return valid;
    }
    function readNameTable(nameTable) {
      const start = (font.start || 0) + nameTable.offset;
      font.pos = start;
      const names = [[], []],
        records = [];
      const length = nameTable.length,
        end = start + length;
      const format = font.getUint16();
      const FORMAT_0_HEADER_LENGTH = 6;
      if (format !== 0 || length < FORMAT_0_HEADER_LENGTH) {
        return [names, records];
      }
      const numRecords = font.getUint16();
      const stringsStart = font.getUint16();
      const NAME_RECORD_LENGTH = 12;
      let i, ii;
      for (i = 0; i < numRecords && font.pos + NAME_RECORD_LENGTH <= end; i++) {
        const r = {
          platform: font.getUint16(),
          encoding: font.getUint16(),
          language: font.getUint16(),
          name: font.getUint16(),
          length: font.getUint16(),
          offset: font.getUint16()
        };
        if (isMacNameRecord(r) || isWinNameRecord(r)) {
          records.push(r);
        }
      }
      for (i = 0, ii = records.length; i < ii; i++) {
        const record = records[i];
        if (record.length <= 0) {
          continue;
        }
        const pos = start + stringsStart + record.offset;
        if (pos + record.length > end) {
          continue;
        }
        font.pos = pos;
        const nameIndex = record.name;
        if (record.encoding) {
          let str = "";
          for (let j = 0, jj = record.length; j < jj; j += 2) {
            str += String.fromCharCode(font.getUint16());
          }
          names[1][nameIndex] = str;
        } else {
          names[0][nameIndex] = font.getString(record.length);
        }
      }
      return [names, records];
    }
    const TTOpsStackDeltas = [0, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, -2, 0, 0, -2, -5, -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, -1, -1, 1, -1, -999, 0, 1, 0, -1, -2, 0, -1, -2, -1, -1, 0, -1, -1, 0, 0, -999, -999, -1, -1, -1, -1, -2, -999, -2, -2, -999, 0, -2, -2, 0, 0, -2, 0, -2, 0, 0, 0, -2, -1, -1, 1, 1, 0, 0, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, 0, -999, -1, -1, -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -2, -999, -999, -999, -999, -999, -1, -1, -2, -2, 0, 0, 0, 0, -1, -1, -999, -2, -2, 0, 0, -1, -2, -2, 0, 0, 0, -1, -1, -1, -2];
    function sanitizeTTProgram(table, ttContext) {
      let data = table.data;
      let i = 0,
        j,
        n,
        b,
        funcId,
        pc,
        lastEndf = 0,
        lastDeff = 0;
      const stack = [];
      const callstack = [];
      const functionsCalled = [];
      let tooComplexToFollowFunctions = ttContext.tooComplexToFollowFunctions;
      let inFDEF = false,
        ifLevel = 0,
        inELSE = 0;
      for (let ii = data.length; i < ii;) {
        const op = data[i++];
        if (op === 0x40) {
          n = data[i++];
          if (inFDEF || inELSE) {
            i += n;
          } else {
            for (j = 0; j < n; j++) {
              stack.push(data[i++]);
            }
          }
        } else if (op === 0x41) {
          n = data[i++];
          if (inFDEF || inELSE) {
            i += n * 2;
          } else {
            for (j = 0; j < n; j++) {
              b = data[i++];
              stack.push(b << 8 | data[i++]);
            }
          }
        } else if ((op & 0xf8) === 0xb0) {
          n = op - 0xb0 + 1;
          if (inFDEF || inELSE) {
            i += n;
          } else {
            for (j = 0; j < n; j++) {
              stack.push(data[i++]);
            }
          }
        } else if ((op & 0xf8) === 0xb8) {
          n = op - 0xb8 + 1;
          if (inFDEF || inELSE) {
            i += n * 2;
          } else {
            for (j = 0; j < n; j++) {
              b = data[i++];
              stack.push(b << 8 | data[i++]);
            }
          }
        } else if (op === 0x2b && !tooComplexToFollowFunctions) {
          if (!inFDEF && !inELSE) {
            funcId = stack.at(-1);
            if (isNaN(funcId)) {
              info("TT: CALL empty stack (or invalid entry).");
            } else {
              ttContext.functionsUsed[funcId] = true;
              if (funcId in ttContext.functionsStackDeltas) {
                const newStackLength = stack.length + ttContext.functionsStackDeltas[funcId];
                if (newStackLength < 0) {
                  warn("TT: CALL invalid functions stack delta.");
                  ttContext.hintsValid = false;
                  return;
                }
                stack.length = newStackLength;
              } else if (funcId in ttContext.functionsDefined && !functionsCalled.includes(funcId)) {
                callstack.push({
                  data,
                  i,
                  stackTop: stack.length - 1
                });
                functionsCalled.push(funcId);
                pc = ttContext.functionsDefined[funcId];
                if (!pc) {
                  warn("TT: CALL non-existent function");
                  ttContext.hintsValid = false;
                  return;
                }
                data = pc.data;
                i = pc.i;
              }
            }
          }
        } else if (op === 0x2c && !tooComplexToFollowFunctions) {
          if (inFDEF || inELSE) {
            warn("TT: nested FDEFs not allowed");
            tooComplexToFollowFunctions = true;
          }
          inFDEF = true;
          lastDeff = i;
          funcId = stack.pop();
          ttContext.functionsDefined[funcId] = {
            data,
            i
          };
        } else if (op === 0x2d) {
          if (inFDEF) {
            inFDEF = false;
            lastEndf = i;
          } else {
            pc = callstack.pop();
            if (!pc) {
              warn("TT: ENDF bad stack");
              ttContext.hintsValid = false;
              return;
            }
            funcId = functionsCalled.pop();
            data = pc.data;
            i = pc.i;
            ttContext.functionsStackDeltas[funcId] = stack.length - pc.stackTop;
          }
        } else if (op === 0x89) {
          if (inFDEF || inELSE) {
            warn("TT: nested IDEFs not allowed");
            tooComplexToFollowFunctions = true;
          }
          inFDEF = true;
          lastDeff = i;
        } else if (op === 0x58) {
          ++ifLevel;
        } else if (op === 0x1b) {
          inELSE = ifLevel;
        } else if (op === 0x59) {
          if (inELSE === ifLevel) {
            inELSE = 0;
          }
          --ifLevel;
        } else if (op === 0x1c) {
          if (!inFDEF && !inELSE) {
            const offset = stack.at(-1);
            if (offset > 0) {
              i += offset - 1;
            }
          }
        }
        if (!inFDEF && !inELSE) {
          let stackDelta = 0;
          if (op <= 0x8e) {
            stackDelta = TTOpsStackDeltas[op];
          } else if (op >= 0xc0 && op <= 0xdf) {
            stackDelta = -1;
          } else if (op >= 0xe0) {
            stackDelta = -2;
          }
          if (op >= 0x71 && op <= 0x75) {
            n = stack.pop();
            if (!isNaN(n)) {
              stackDelta = -n * 2;
            }
          }
          while (stackDelta < 0 && stack.length > 0) {
            stack.pop();
            stackDelta++;
          }
          while (stackDelta > 0) {
            stack.push(NaN);
            stackDelta--;
          }
        }
      }
      ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions;
      const content = [data];
      if (i > data.length) {
        content.push(new Uint8Array(i - data.length));
      }
      if (lastDeff > lastEndf) {
        warn("TT: complementing a missing function tail");
        content.push(new Uint8Array([0x22, 0x2d]));
      }
      foldTTTable(table, content);
    }
    function checkInvalidFunctions(ttContext, maxFunctionDefs) {
      if (ttContext.tooComplexToFollowFunctions) {
        return;
      }
      if (ttContext.functionsDefined.length > maxFunctionDefs) {
        warn("TT: more functions defined than expected");
        ttContext.hintsValid = false;
        return;
      }
      for (let j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) {
        if (j > maxFunctionDefs) {
          warn("TT: invalid function id: " + j);
          ttContext.hintsValid = false;
          return;
        }
        if (ttContext.functionsUsed[j] && !ttContext.functionsDefined[j]) {
          warn("TT: undefined function: " + j);
          ttContext.hintsValid = false;
          return;
        }
      }
    }
    function foldTTTable(table, content) {
      if (content.length > 1) {
        let newLength = 0;
        let j, jj;
        for (j = 0, jj = content.length; j < jj; j++) {
          newLength += content[j].length;
        }
        newLength = newLength + 3 & ~3;
        const result = new Uint8Array(newLength);
        let pos = 0;
        for (j = 0, jj = content.length; j < jj; j++) {
          result.set(content[j], pos);
          pos += content[j].length;
        }
        table.data = result;
        table.length = newLength;
      }
    }
    function sanitizeTTPrograms(fpgm, prep, cvt, maxFunctionDefs) {
      const ttContext = {
        functionsDefined: [],
        functionsUsed: [],
        functionsStackDeltas: [],
        tooComplexToFollowFunctions: false,
        hintsValid: true
      };
      if (fpgm) {
        sanitizeTTProgram(fpgm, ttContext);
      }
      if (prep) {
        sanitizeTTProgram(prep, ttContext);
      }
      if (fpgm) {
        checkInvalidFunctions(ttContext, maxFunctionDefs);
      }
      if (cvt && cvt.length & 1) {
        const cvtData = new Uint8Array(cvt.length + 1);
        cvtData.set(cvt.data);
        cvt.data = cvtData;
      }
      return ttContext.hintsValid;
    }
    font = new Stream(new Uint8Array(font.getBytes()));
    let header, tables;
    if (isTrueTypeCollectionFile(font)) {
      const ttcData = readTrueTypeCollectionData(font, this.name);
      header = ttcData.header;
      tables = ttcData.tables;
    } else {
      header = readOpenTypeHeader(font);
      tables = readTables(font, header.numTables);
    }
    let cff, cffFile;
    const isTrueType = !tables["CFF "];
    if (!isTrueType) {
      const isComposite = properties.composite && (properties.cidToGidMap?.length > 0 || !(properties.cMap instanceof IdentityCMap));
      if (header.version === "OTTO" && !isComposite || !tables.head || !tables.hhea || !tables.maxp || !tables.post) {
        cffFile = new Stream(tables["CFF "].data);
        cff = new CFFFont(cffFile, properties);
        adjustWidths(properties);
        return this.convert(name, cff, properties);
      }
      delete tables.glyf;
      delete tables.loca;
      delete tables.fpgm;
      delete tables.prep;
      delete tables["cvt "];
      this.isOpenType = true;
    } else {
      if (!tables.loca) {
        throw new FormatError('Required "loca" table is not found');
      }
      if (!tables.glyf) {
        warn('Required "glyf" table is not found -- trying to recover.');
        tables.glyf = {
          tag: "glyf",
          data: new Uint8Array(0)
        };
      }
      this.isOpenType = false;
    }
    if (!tables.maxp) {
      throw new FormatError('Required "maxp" table is not found');
    }
    font.pos = (font.start || 0) + tables.maxp.offset;
    let version = font.getInt32();
    const numGlyphs = font.getUint16();
    if (version !== 0x00010000 && version !== 0x00005000) {
      if (tables.maxp.length === 6) {
        version = 0x0005000;
      } else if (tables.maxp.length >= 32) {
        version = 0x00010000;
      } else {
        throw new FormatError(`"maxp" table has a wrong version number`);
      }
      writeUint32(tables.maxp.data, 0, version);
    }
    if (properties.scaleFactors?.length === numGlyphs && isTrueType) {
      const {
        scaleFactors
      } = properties;
      const isGlyphLocationsLong = int16(tables.head.data[50], tables.head.data[51]);
      const glyphs = new GlyfTable({
        glyfTable: tables.glyf.data,
        isGlyphLocationsLong,
        locaTable: tables.loca.data,
        numGlyphs
      });
      glyphs.scale(scaleFactors);
      const {
        glyf,
        loca,
        isLocationLong
      } = glyphs.write();
      tables.glyf.data = glyf;
      tables.loca.data = loca;
      if (isLocationLong !== !!isGlyphLocationsLong) {
        tables.head.data[50] = 0;
        tables.head.data[51] = isLocationLong ? 1 : 0;
      }
      const metrics = tables.hmtx.data;
      for (let i = 0; i < numGlyphs; i++) {
        const j = 4 * i;
        const advanceWidth = Math.round(scaleFactors[i] * int16(metrics[j], metrics[j + 1]));
        metrics[j] = advanceWidth >> 8 & 0xff;
        metrics[j + 1] = advanceWidth & 0xff;
        const lsb = Math.round(scaleFactors[i] * signedInt16(metrics[j + 2], metrics[j + 3]));
        writeSignedInt16(metrics, j + 2, lsb);
      }
    }
    let numGlyphsOut = numGlyphs + 1;
    let dupFirstEntry = true;
    if (numGlyphsOut > 0xffff) {
      dupFirstEntry = false;
      numGlyphsOut = numGlyphs;
      warn("Not enough space in glyfs to duplicate first glyph.");
    }
    let maxFunctionDefs = 0;
    let maxSizeOfInstructions = 0;
    if (version >= 0x00010000 && tables.maxp.length >= 32) {
      font.pos += 8;
      const maxZones = font.getUint16();
      if (maxZones > 2) {
        tables.maxp.data[14] = 0;
        tables.maxp.data[15] = 2;
      }
      font.pos += 4;
      maxFunctionDefs = font.getUint16();
      font.pos += 4;
      maxSizeOfInstructions = font.getUint16();
    }
    tables.maxp.data[4] = numGlyphsOut >> 8;
    tables.maxp.data[5] = numGlyphsOut & 255;
    const hintsValid = sanitizeTTPrograms(tables.fpgm, tables.prep, tables["cvt "], maxFunctionDefs);
    if (!hintsValid) {
      delete tables.fpgm;
      delete tables.prep;
      delete tables["cvt "];
    }
    sanitizeMetrics(font, tables.hhea, tables.hmtx, tables.head, numGlyphsOut, dupFirstEntry);
    if (!tables.head) {
      throw new FormatError('Required "head" table is not found');
    }
    sanitizeHead(tables.head, numGlyphs, isTrueType ? tables.loca.length : 0);
    let missingGlyphs = Object.create(null);
    if (isTrueType) {
      const isGlyphLocationsLong = int16(tables.head.data[50], tables.head.data[51]);
      const glyphsInfo = sanitizeGlyphLocations(tables.loca, tables.glyf, numGlyphs, isGlyphLocationsLong, hintsValid, dupFirstEntry, maxSizeOfInstructions);
      missingGlyphs = glyphsInfo.missingGlyphs;
      if (version >= 0x00010000 && tables.maxp.length >= 32) {
        tables.maxp.data[26] = glyphsInfo.maxSizeOfInstructions >> 8;
        tables.maxp.data[27] = glyphsInfo.maxSizeOfInstructions & 255;
      }
    }
    if (!tables.hhea) {
      throw new FormatError('Required "hhea" table is not found');
    }
    if (tables.hhea.data[10] === 0 && tables.hhea.data[11] === 0) {
      tables.hhea.data[10] = 0xff;
      tables.hhea.data[11] = 0xff;
    }
    const metricsOverride = {
      unitsPerEm: int16(tables.head.data[18], tables.head.data[19]),
      yMax: signedInt16(tables.head.data[42], tables.head.data[43]),
      yMin: signedInt16(tables.head.data[38], tables.head.data[39]),
      ascent: signedInt16(tables.hhea.data[4], tables.hhea.data[5]),
      descent: signedInt16(tables.hhea.data[6], tables.hhea.data[7]),
      lineGap: signedInt16(tables.hhea.data[8], tables.hhea.data[9])
    };
    this.ascent = metricsOverride.ascent / metricsOverride.unitsPerEm;
    this.descent = metricsOverride.descent / metricsOverride.unitsPerEm;
    this.lineGap = metricsOverride.lineGap / metricsOverride.unitsPerEm;
    if (this.cssFontInfo?.lineHeight) {
      this.lineHeight = this.cssFontInfo.metrics.lineHeight;
      this.lineGap = this.cssFontInfo.metrics.lineGap;
    } else {
      this.lineHeight = this.ascent - this.descent + this.lineGap;
    }
    if (tables.post) {
      readPostScriptTable(tables.post, properties, numGlyphs);
    }
    tables.post = {
      tag: "post",
      data: createPostTable(properties)
    };
    const charCodeToGlyphId = Object.create(null);
    function hasGlyph(glyphId) {
      return !missingGlyphs[glyphId];
    }
    if (properties.composite) {
      const cidToGidMap = properties.cidToGidMap || [];
      const isCidToGidMapEmpty = cidToGidMap.length === 0;
      properties.cMap.forEach(function (charCode, cid) {
        if (typeof cid === "string") {
          cid = convertCidString(charCode, cid, true);
        }
        if (cid > 0xffff) {
          throw new FormatError("Max size of CID is 65,535");
        }
        let glyphId = -1;
        if (isCidToGidMapEmpty) {
          glyphId = cid;
        } else if (cidToGidMap[cid] !== undefined) {
          glyphId = cidToGidMap[cid];
        }
        if (glyphId >= 0 && glyphId < numGlyphs && hasGlyph(glyphId)) {
          charCodeToGlyphId[charCode] = glyphId;
        }
      });
    } else {
      const cmapTable = readCmapTable(tables.cmap, font, this.isSymbolicFont, properties.hasEncoding);
      const cmapPlatformId = cmapTable.platformId;
      const cmapEncodingId = cmapTable.encodingId;
      const cmapMappings = cmapTable.mappings;
      let baseEncoding = [],
        forcePostTable = false;
      if (properties.hasEncoding && (properties.baseEncodingName === "MacRomanEncoding" || properties.baseEncodingName === "WinAnsiEncoding")) {
        baseEncoding = getEncoding(properties.baseEncodingName);
      }
      if (properties.hasEncoding && !this.isSymbolicFont && (cmapPlatformId === 3 && cmapEncodingId === 1 || cmapPlatformId === 1 && cmapEncodingId === 0)) {
        const glyphsUnicodeMap = getGlyphsUnicode();
        for (let charCode = 0; charCode < 256; charCode++) {
          let glyphName;
          if (this.differences[charCode] !== undefined) {
            glyphName = this.differences[charCode];
          } else if (baseEncoding.length && baseEncoding[charCode] !== "") {
            glyphName = baseEncoding[charCode];
          } else {
            glyphName = StandardEncoding[charCode];
          }
          if (!glyphName) {
            continue;
          }
          const standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap);
          let unicodeOrCharCode;
          if (cmapPlatformId === 3 && cmapEncodingId === 1) {
            unicodeOrCharCode = glyphsUnicodeMap[standardGlyphName];
          } else if (cmapPlatformId === 1 && cmapEncodingId === 0) {
            unicodeOrCharCode = MacRomanEncoding.indexOf(standardGlyphName);
          }
          if (unicodeOrCharCode === undefined) {
            if (!properties.glyphNames && properties.hasIncludedToUnicodeMap && !(this.toUnicode instanceof IdentityToUnicodeMap)) {
              const unicode = this.toUnicode.get(charCode);
              if (unicode) {
                unicodeOrCharCode = unicode.codePointAt(0);
              }
            }
            if (unicodeOrCharCode === undefined) {
              continue;
            }
          }
          for (const mapping of cmapMappings) {
            if (mapping.charCode !== unicodeOrCharCode) {
              continue;
            }
            charCodeToGlyphId[charCode] = mapping.glyphId;
            break;
          }
        }
      } else if (cmapPlatformId === 0) {
        for (const mapping of cmapMappings) {
          charCodeToGlyphId[mapping.charCode] = mapping.glyphId;
        }
        forcePostTable = true;
      } else if (cmapPlatformId === 3 && cmapEncodingId === 0) {
        for (const mapping of cmapMappings) {
          let charCode = mapping.charCode;
          if (charCode >= 0xf000 && charCode <= 0xf0ff) {
            charCode &= 0xff;
          }
          charCodeToGlyphId[charCode] = mapping.glyphId;
        }
      } else {
        for (const mapping of cmapMappings) {
          charCodeToGlyphId[mapping.charCode] = mapping.glyphId;
        }
      }
      if (properties.glyphNames && (baseEncoding.length || this.differences.length)) {
        for (let i = 0; i < 256; ++i) {
          if (!forcePostTable && charCodeToGlyphId[i] !== undefined) {
            continue;
          }
          const glyphName = this.differences[i] || baseEncoding[i];
          if (!glyphName) {
            continue;
          }
          const glyphId = properties.glyphNames.indexOf(glyphName);
          if (glyphId > 0 && hasGlyph(glyphId)) {
            charCodeToGlyphId[i] = glyphId;
          }
        }
      }
    }
    if (charCodeToGlyphId.length === 0) {
      charCodeToGlyphId[0] = 0;
    }
    let glyphZeroId = numGlyphsOut - 1;
    if (!dupFirstEntry) {
      glyphZeroId = 0;
    }
    if (!properties.cssFontInfo) {
      const newMapping = adjustMapping(charCodeToGlyphId, hasGlyph, glyphZeroId, this.toUnicode);
      this.toFontChar = newMapping.toFontChar;
      tables.cmap = {
        tag: "cmap",
        data: createCmapTable(newMapping.charCodeToGlyphId, newMapping.toUnicodeExtraMap, numGlyphsOut)
      };
      if (!tables["OS/2"] || !validateOS2Table(tables["OS/2"], font)) {
        tables["OS/2"] = {
          tag: "OS/2",
          data: createOS2Table(properties, newMapping.charCodeToGlyphId, metricsOverride)
        };
      }
    }
    if (!isTrueType) {
      try {
        cffFile = new Stream(tables["CFF "].data);
        const parser = new CFFParser(cffFile, properties, SEAC_ANALYSIS_ENABLED);
        cff = parser.parse();
        cff.duplicateFirstGlyph();
        const compiler = new CFFCompiler(cff);
        tables["CFF "].data = compiler.compile();
      } catch {
        warn("Failed to compile font " + properties.loadedName);
      }
    }
    if (!tables.name) {
      tables.name = {
        tag: "name",
        data: createNameTable(this.name)
      };
    } else {
      const [namePrototype, nameRecords] = readNameTable(tables.name);
      tables.name.data = createNameTable(name, namePrototype);
      this.psName = namePrototype[0][6] || null;
      if (!properties.composite) {
        adjustTrueTypeToUnicode(properties, this.isSymbolicFont, nameRecords);
      }
    }
    const builder = new OpenTypeFileBuilder(header.version);
    for (const tableTag in tables) {
      builder.addTable(tableTag, tables[tableTag].data);
    }
    return builder.toArray();
  }
  convert(fontName, font, properties) {
    properties.fixedPitch = false;
    if (properties.builtInEncoding) {
      adjustType1ToUnicode(properties, properties.builtInEncoding);
    }
    let glyphZeroId = 1;
    if (font instanceof CFFFont) {
      glyphZeroId = font.numGlyphs - 1;
    }
    const mapping = font.getGlyphMapping(properties);
    let newMapping = null;
    let newCharCodeToGlyphId = mapping;
    let toUnicodeExtraMap = null;
    if (!properties.cssFontInfo) {
      newMapping = adjustMapping(mapping, font.hasGlyphId.bind(font), glyphZeroId, this.toUnicode);
      this.toFontChar = newMapping.toFontChar;
      newCharCodeToGlyphId = newMapping.charCodeToGlyphId;
      toUnicodeExtraMap = newMapping.toUnicodeExtraMap;
    }
    const numGlyphs = font.numGlyphs;
    function getCharCodes(charCodeToGlyphId, glyphId) {
      let charCodes = null;
      for (const charCode in charCodeToGlyphId) {
        if (glyphId === charCodeToGlyphId[charCode]) {
          (charCodes ||= []).push(charCode | 0);
        }
      }
      return charCodes;
    }
    function createCharCode(charCodeToGlyphId, glyphId) {
      for (const charCode in charCodeToGlyphId) {
        if (glyphId === charCodeToGlyphId[charCode]) {
          return charCode | 0;
        }
      }
      newMapping.charCodeToGlyphId[newMapping.nextAvailableFontCharCode] = glyphId;
      return newMapping.nextAvailableFontCharCode++;
    }
    const seacs = font.seacs;
    if (newMapping && SEAC_ANALYSIS_ENABLED && seacs?.length) {
      const matrix = properties.fontMatrix || FONT_IDENTITY_MATRIX;
      const charset = font.getCharset();
      const seacMap = Object.create(null);
      for (let glyphId in seacs) {
        glyphId |= 0;
        const seac = seacs[glyphId];
        const baseGlyphName = StandardEncoding[seac[2]];
        const accentGlyphName = StandardEncoding[seac[3]];
        const baseGlyphId = charset.indexOf(baseGlyphName);
        const accentGlyphId = charset.indexOf(accentGlyphName);
        if (baseGlyphId < 0 || accentGlyphId < 0) {
          continue;
        }
        const accentOffset = {
          x: seac[0] * matrix[0] + seac[1] * matrix[2] + matrix[4],
          y: seac[0] * matrix[1] + seac[1] * matrix[3] + matrix[5]
        };
        const charCodes = getCharCodes(mapping, glyphId);
        if (!charCodes) {
          continue;
        }
        for (const charCode of charCodes) {
          const charCodeToGlyphId = newMapping.charCodeToGlyphId;
          const baseFontCharCode = createCharCode(charCodeToGlyphId, baseGlyphId);
          const accentFontCharCode = createCharCode(charCodeToGlyphId, accentGlyphId);
          seacMap[charCode] = {
            baseFontCharCode,
            accentFontCharCode,
            accentOffset
          };
        }
      }
      properties.seacMap = seacMap;
    }
    const unitsPerEm = properties.fontMatrix ? 1 / Math.max(...properties.fontMatrix.slice(0, 4).map(Math.abs)) : 1000;
    const builder = new OpenTypeFileBuilder("\x4F\x54\x54\x4F");
    builder.addTable("CFF ", font.data);
    builder.addTable("OS/2", createOS2Table(properties, newCharCodeToGlyphId));
    builder.addTable("cmap", createCmapTable(newCharCodeToGlyphId, toUnicodeExtraMap, numGlyphs));
    builder.addTable("head", "\x00\x01\x00\x00" + "\x00\x00\x10\x00" + "\x00\x00\x00\x00" + "\x5F\x0F\x3C\xF5" + "\x00\x00" + safeString16(unitsPerEm) + "\x00\x00\x00\x00\x9e\x0b\x7e\x27" + "\x00\x00\x00\x00\x9e\x0b\x7e\x27" + "\x00\x00" + safeString16(properties.descent) + "\x0F\xFF" + safeString16(properties.ascent) + string16(properties.italicAngle ? 2 : 0) + "\x00\x11" + "\x00\x00" + "\x00\x00" + "\x00\x00");
    builder.addTable("hhea", "\x00\x01\x00\x00" + safeString16(properties.ascent) + safeString16(properties.descent) + "\x00\x00" + "\xFF\xFF" + "\x00\x00" + "\x00\x00" + "\x00\x00" + safeString16(properties.capHeight) + safeString16(Math.tan(properties.italicAngle) * properties.xHeight) + "\x00\x00" + "\x00\x00" + "\x00\x00" + "\x00\x00" + "\x00\x00" + "\x00\x00" + string16(numGlyphs));
    builder.addTable("hmtx", function fontFieldsHmtx() {
      const charstrings = font.charstrings;
      const cffWidths = font.cff ? font.cff.widths : null;
      let hmtx = "\x00\x00\x00\x00";
      for (let i = 1, ii = numGlyphs; i < ii; i++) {
        let width = 0;
        if (charstrings) {
          const charstring = charstrings[i - 1];
          width = "width" in charstring ? charstring.width : 0;
        } else if (cffWidths) {
          width = Math.ceil(cffWidths[i] || 0);
        }
        hmtx += string16(width) + string16(0);
      }
      return hmtx;
    }());
    builder.addTable("maxp", "\x00\x00\x50\x00" + string16(numGlyphs));
    builder.addTable("name", createNameTable(fontName));
    builder.addTable("post", createPostTable(properties));
    return builder.toArray();
  }
  get _spaceWidth() {
    const possibleSpaceReplacements = ["space", "minus", "one", "i", "I"];
    let width;
    for (const glyphName of possibleSpaceReplacements) {
      if (glyphName in this.widths) {
        width = this.widths[glyphName];
        break;
      }
      const glyphsUnicodeMap = getGlyphsUnicode();
      const glyphUnicode = glyphsUnicodeMap[glyphName];
      let charcode = 0;
      if (this.composite && this.cMap.contains(glyphUnicode)) {
        charcode = this.cMap.lookup(glyphUnicode);
        if (typeof charcode === "string") {
          charcode = convertCidString(glyphUnicode, charcode);
        }
      }
      if (!charcode && this.toUnicode) {
        charcode = this.toUnicode.charCodeOf(glyphUnicode);
      }
      if (charcode <= 0) {
        charcode = glyphUnicode;
      }
      width = this.widths[charcode];
      if (width) {
        break;
      }
    }
    return shadow(this, "_spaceWidth", width || this.defaultWidth);
  }
  _charToGlyph(charcode, isSpace = false) {
    let glyph = this._glyphCache[charcode];
    if (glyph?.isSpace === isSpace) {
      return glyph;
    }
    let fontCharCode, width, operatorListId;
    let widthCode = charcode;
    if (this.cMap?.contains(charcode)) {
      widthCode = this.cMap.lookup(charcode);
      if (typeof widthCode === "string") {
        widthCode = convertCidString(charcode, widthCode);
      }
    }
    width = this.widths[widthCode];
    if (typeof width !== "number") {
      width = this.defaultWidth;
    }
    const vmetric = this.vmetrics?.[widthCode];
    let unicode = this.toUnicode.get(charcode) || charcode;
    if (typeof unicode === "number") {
      unicode = String.fromCharCode(unicode);
    }
    let isInFont = this.toFontChar[charcode] !== undefined;
    fontCharCode = this.toFontChar[charcode] || charcode;
    if (this.missingFile) {
      const glyphName = this.differences[charcode] || this.defaultEncoding[charcode];
      if ((glyphName === ".notdef" || glyphName === "") && this.type === "Type1") {
        fontCharCode = 0x20;
        if (glyphName === "") {
          width ||= this._spaceWidth;
          unicode = String.fromCharCode(fontCharCode);
        }
      }
      fontCharCode = mapSpecialUnicodeValues(fontCharCode);
    }
    if (this.isType3Font) {
      operatorListId = fontCharCode;
    }
    let accent = null;
    if (this.seacMap?.[charcode]) {
      isInFont = true;
      const seac = this.seacMap[charcode];
      fontCharCode = seac.baseFontCharCode;
      accent = {
        fontChar: String.fromCodePoint(seac.accentFontCharCode),
        offset: seac.accentOffset
      };
    }
    let fontChar = "";
    if (typeof fontCharCode === "number") {
      if (fontCharCode <= 0x10ffff) {
        fontChar = String.fromCodePoint(fontCharCode);
      } else {
        warn(`charToGlyph - invalid fontCharCode: ${fontCharCode}`);
      }
    }
    if (this.missingFile && this.vertical && fontChar.length === 1) {
      const vertical = getVerticalPresentationForm()[fontChar.charCodeAt(0)];
      if (vertical) {
        fontChar = unicode = String.fromCharCode(vertical);
      }
    }
    glyph = new fonts_Glyph(charcode, fontChar, unicode, accent, width, vmetric, operatorListId, isSpace, isInFont);
    return this._glyphCache[charcode] = glyph;
  }
  charsToGlyphs(chars) {
    let glyphs = this._charsCache[chars];
    if (glyphs) {
      return glyphs;
    }
    glyphs = [];
    if (this.cMap) {
      const c = Object.create(null),
        ii = chars.length;
      let i = 0;
      while (i < ii) {
        this.cMap.readCharCode(chars, i, c);
        const {
          charcode,
          length
        } = c;
        i += length;
        const glyph = this._charToGlyph(charcode, length === 1 && chars.charCodeAt(i - 1) === 0x20);
        glyphs.push(glyph);
      }
    } else {
      for (let i = 0, ii = chars.length; i < ii; ++i) {
        const charcode = chars.charCodeAt(i);
        const glyph = this._charToGlyph(charcode, charcode === 0x20);
        glyphs.push(glyph);
      }
    }
    return this._charsCache[chars] = glyphs;
  }
  getCharPositions(chars) {
    const positions = [];
    if (this.cMap) {
      const c = Object.create(null);
      let i = 0;
      while (i < chars.length) {
        this.cMap.readCharCode(chars, i, c);
        const length = c.length;
        positions.push([i, i + length]);
        i += length;
      }
    } else {
      for (let i = 0, ii = chars.length; i < ii; ++i) {
        positions.push([i, i + 1]);
      }
    }
    return positions;
  }
  get glyphCacheValues() {
    return Object.values(this._glyphCache);
  }
  encodeString(str) {
    const buffers = [];
    const currentBuf = [];
    const hasCurrentBufErrors = () => buffers.length % 2 === 1;
    const getCharCode = this.toUnicode instanceof IdentityToUnicodeMap ? unicode => this.toUnicode.charCodeOf(unicode) : unicode => this.toUnicode.charCodeOf(String.fromCodePoint(unicode));
    for (let i = 0, ii = str.length; i < ii; i++) {
      const unicode = str.codePointAt(i);
      if (unicode > 0xd7ff && (unicode < 0xe000 || unicode > 0xfffd)) {
        i++;
      }
      if (this.toUnicode) {
        const charCode = getCharCode(unicode);
        if (charCode !== -1) {
          if (hasCurrentBufErrors()) {
            buffers.push(currentBuf.join(""));
            currentBuf.length = 0;
          }
          const charCodeLength = this.cMap ? this.cMap.getCharCodeLength(charCode) : 1;
          for (let j = charCodeLength - 1; j >= 0; j--) {
            currentBuf.push(String.fromCharCode(charCode >> 8 * j & 0xff));
          }
          continue;
        }
      }
      if (!hasCurrentBufErrors()) {
        buffers.push(currentBuf.join(""));
        currentBuf.length = 0;
      }
      currentBuf.push(String.fromCodePoint(unicode));
    }
    buffers.push(currentBuf.join(""));
    return buffers;
  }
}
class ErrorFont {
  constructor(error) {
    this.error = error;
    this.loadedName = "g_font_error";
    this.missingFile = true;
  }
  charsToGlyphs() {
    return [];
  }
  encodeString(chars) {
    return [chars];
  }
  exportData(extraProperties = false) {
    return {
      error: this.error
    };
  }
}

;// CONCATENATED MODULE: ./src/core/pattern.js




const ShadingType = {
  FUNCTION_BASED: 1,
  AXIAL: 2,
  RADIAL: 3,
  FREE_FORM_MESH: 4,
  LATTICE_FORM_MESH: 5,
  COONS_PATCH_MESH: 6,
  TENSOR_PATCH_MESH: 7
};
class Pattern {
  constructor() {
    unreachable("Cannot initialize Pattern.");
  }
  static parseShading(shading, xref, res, pdfFunctionFactory, localColorSpaceCache) {
    const dict = shading instanceof BaseStream ? shading.dict : shading;
    const type = dict.get("ShadingType");
    try {
      switch (type) {
        case ShadingType.AXIAL:
        case ShadingType.RADIAL:
          return new RadialAxialShading(dict, xref, res, pdfFunctionFactory, localColorSpaceCache);
        case ShadingType.FREE_FORM_MESH:
        case ShadingType.LATTICE_FORM_MESH:
        case ShadingType.COONS_PATCH_MESH:
        case ShadingType.TENSOR_PATCH_MESH:
          return new MeshShading(shading, xref, res, pdfFunctionFactory, localColorSpaceCache);
        default:
          throw new FormatError("Unsupported ShadingType: " + type);
      }
    } catch (ex) {
      if (ex instanceof MissingDataException) {
        throw ex;
      }
      warn(ex);
      return new DummyShading();
    }
  }
}
class BaseShading {
  static SMALL_NUMBER = 1e-6;
  getIR() {
    unreachable("Abstract method `getIR` called.");
  }
}
class RadialAxialShading extends BaseShading {
  constructor(dict, xref, resources, pdfFunctionFactory, localColorSpaceCache) {
    super();
    this.shadingType = dict.get("ShadingType");
    let coordsLen = 0;
    if (this.shadingType === ShadingType.AXIAL) {
      coordsLen = 4;
    } else if (this.shadingType === ShadingType.RADIAL) {
      coordsLen = 6;
    }
    this.coordsArr = dict.getArray("Coords");
    if (!isNumberArray(this.coordsArr, coordsLen)) {
      throw new FormatError("RadialAxialShading: Invalid /Coords array.");
    }
    const cs = ColorSpace.parse({
      cs: dict.getRaw("CS") || dict.getRaw("ColorSpace"),
      xref,
      resources,
      pdfFunctionFactory,
      localColorSpaceCache
    });
    this.bbox = lookupNormalRect(dict.getArray("BBox"), null);
    let t0 = 0.0,
      t1 = 1.0;
    const domainArr = dict.getArray("Domain");
    if (isNumberArray(domainArr, 2)) {
      [t0, t1] = domainArr;
    }
    let extendStart = false,
      extendEnd = false;
    const extendArr = dict.getArray("Extend");
    if (isBooleanArray(extendArr, 2)) {
      [extendStart, extendEnd] = extendArr;
    }
    if (this.shadingType === ShadingType.RADIAL && (!extendStart || !extendEnd)) {
      const [x1, y1, r1, x2, y2, r2] = this.coordsArr;
      const distance = Math.hypot(x1 - x2, y1 - y2);
      if (r1 <= r2 + distance && r2 <= r1 + distance) {
        warn("Unsupported radial gradient.");
      }
    }
    this.extendStart = extendStart;
    this.extendEnd = extendEnd;
    const fnObj = dict.getRaw("Function");
    const fn = pdfFunctionFactory.createFromArray(fnObj);
    const NUMBER_OF_SAMPLES = 840;
    const step = (t1 - t0) / NUMBER_OF_SAMPLES;
    const colorStops = this.colorStops = [];
    if (t0 >= t1 || step <= 0) {
      info("Bad shading domain.");
      return;
    }
    const color = new Float32Array(cs.numComps),
      ratio = new Float32Array(1);
    let rgbColor;
    let iBase = 0;
    ratio[0] = t0;
    fn(ratio, 0, color, 0);
    let rgbBase = cs.getRgb(color, 0);
    const cssColorBase = Util.makeHexColor(rgbBase[0], rgbBase[1], rgbBase[2]);
    colorStops.push([0, cssColorBase]);
    let iPrev = 1;
    ratio[0] = t0 + step;
    fn(ratio, 0, color, 0);
    let rgbPrev = cs.getRgb(color, 0);
    let maxSlopeR = rgbPrev[0] - rgbBase[0] + 1;
    let maxSlopeG = rgbPrev[1] - rgbBase[1] + 1;
    let maxSlopeB = rgbPrev[2] - rgbBase[2] + 1;
    let minSlopeR = rgbPrev[0] - rgbBase[0] - 1;
    let minSlopeG = rgbPrev[1] - rgbBase[1] - 1;
    let minSlopeB = rgbPrev[2] - rgbBase[2] - 1;
    for (let i = 2; i < NUMBER_OF_SAMPLES; i++) {
      ratio[0] = t0 + i * step;
      fn(ratio, 0, color, 0);
      rgbColor = cs.getRgb(color, 0);
      const run = i - iBase;
      maxSlopeR = Math.min(maxSlopeR, (rgbColor[0] - rgbBase[0] + 1) / run);
      maxSlopeG = Math.min(maxSlopeG, (rgbColor[1] - rgbBase[1] + 1) / run);
      maxSlopeB = Math.min(maxSlopeB, (rgbColor[2] - rgbBase[2] + 1) / run);
      minSlopeR = Math.max(minSlopeR, (rgbColor[0] - rgbBase[0] - 1) / run);
      minSlopeG = Math.max(minSlopeG, (rgbColor[1] - rgbBase[1] - 1) / run);
      minSlopeB = Math.max(minSlopeB, (rgbColor[2] - rgbBase[2] - 1) / run);
      const slopesExist = minSlopeR <= maxSlopeR && minSlopeG <= maxSlopeG && minSlopeB <= maxSlopeB;
      if (!slopesExist) {
        const cssColor = Util.makeHexColor(rgbPrev[0], rgbPrev[1], rgbPrev[2]);
        colorStops.push([iPrev / NUMBER_OF_SAMPLES, cssColor]);
        maxSlopeR = rgbColor[0] - rgbPrev[0] + 1;
        maxSlopeG = rgbColor[1] - rgbPrev[1] + 1;
        maxSlopeB = rgbColor[2] - rgbPrev[2] + 1;
        minSlopeR = rgbColor[0] - rgbPrev[0] - 1;
        minSlopeG = rgbColor[1] - rgbPrev[1] - 1;
        minSlopeB = rgbColor[2] - rgbPrev[2] - 1;
        iBase = iPrev;
        rgbBase = rgbPrev;
      }
      iPrev = i;
      rgbPrev = rgbColor;
    }
    const cssColor = Util.makeHexColor(rgbPrev[0], rgbPrev[1], rgbPrev[2]);
    colorStops.push([1, cssColor]);
    let background = "transparent";
    if (dict.has("Background")) {
      rgbColor = cs.getRgb(dict.get("Background"), 0);
      background = Util.makeHexColor(rgbColor[0], rgbColor[1], rgbColor[2]);
    }
    if (!extendStart) {
      colorStops.unshift([0, background]);
      colorStops[1][0] += BaseShading.SMALL_NUMBER;
    }
    if (!extendEnd) {
      colorStops.at(-1)[0] -= BaseShading.SMALL_NUMBER;
      colorStops.push([1, background]);
    }
    this.colorStops = colorStops;
  }
  getIR() {
    const {
      coordsArr,
      shadingType
    } = this;
    let type, p0, p1, r0, r1;
    if (shadingType === ShadingType.AXIAL) {
      p0 = [coordsArr[0], coordsArr[1]];
      p1 = [coordsArr[2], coordsArr[3]];
      r0 = null;
      r1 = null;
      type = "axial";
    } else if (shadingType === ShadingType.RADIAL) {
      p0 = [coordsArr[0], coordsArr[1]];
      p1 = [coordsArr[3], coordsArr[4]];
      r0 = coordsArr[2];
      r1 = coordsArr[5];
      type = "radial";
    } else {
      unreachable(`getPattern type unknown: ${shadingType}`);
    }
    return ["RadialAxial", type, this.bbox, this.colorStops, p0, p1, r0, r1];
  }
}
class MeshStreamReader {
  constructor(stream, context) {
    this.stream = stream;
    this.context = context;
    this.buffer = 0;
    this.bufferLength = 0;
    const numComps = context.numComps;
    this.tmpCompsBuf = new Float32Array(numComps);
    const csNumComps = context.colorSpace.numComps;
    this.tmpCsCompsBuf = context.colorFn ? new Float32Array(csNumComps) : this.tmpCompsBuf;
  }
  get hasData() {
    if (this.stream.end) {
      return this.stream.pos < this.stream.end;
    }
    if (this.bufferLength > 0) {
      return true;
    }
    const nextByte = this.stream.getByte();
    if (nextByte < 0) {
      return false;
    }
    this.buffer = nextByte;
    this.bufferLength = 8;
    return true;
  }
  readBits(n) {
    let buffer = this.buffer;
    let bufferLength = this.bufferLength;
    if (n === 32) {
      if (bufferLength === 0) {
        return (this.stream.getByte() << 24 | this.stream.getByte() << 16 | this.stream.getByte() << 8 | this.stream.getByte()) >>> 0;
      }
      buffer = buffer << 24 | this.stream.getByte() << 16 | this.stream.getByte() << 8 | this.stream.getByte();
      const nextByte = this.stream.getByte();
      this.buffer = nextByte & (1 << bufferLength) - 1;
      return (buffer << 8 - bufferLength | (nextByte & 0xff) >> bufferLength) >>> 0;
    }
    if (n === 8 && bufferLength === 0) {
      return this.stream.getByte();
    }
    while (bufferLength < n) {
      buffer = buffer << 8 | this.stream.getByte();
      bufferLength += 8;
    }
    bufferLength -= n;
    this.bufferLength = bufferLength;
    this.buffer = buffer & (1 << bufferLength) - 1;
    return buffer >> bufferLength;
  }
  align() {
    this.buffer = 0;
    this.bufferLength = 0;
  }
  readFlag() {
    return this.readBits(this.context.bitsPerFlag);
  }
  readCoordinate() {
    const bitsPerCoordinate = this.context.bitsPerCoordinate;
    const xi = this.readBits(bitsPerCoordinate);
    const yi = this.readBits(bitsPerCoordinate);
    const decode = this.context.decode;
    const scale = bitsPerCoordinate < 32 ? 1 / ((1 << bitsPerCoordinate) - 1) : 2.3283064365386963e-10;
    return [xi * scale * (decode[1] - decode[0]) + decode[0], yi * scale * (decode[3] - decode[2]) + decode[2]];
  }
  readComponents() {
    const numComps = this.context.numComps;
    const bitsPerComponent = this.context.bitsPerComponent;
    const scale = bitsPerComponent < 32 ? 1 / ((1 << bitsPerComponent) - 1) : 2.3283064365386963e-10;
    const decode = this.context.decode;
    const components = this.tmpCompsBuf;
    for (let i = 0, j = 4; i < numComps; i++, j += 2) {
      const ci = this.readBits(bitsPerComponent);
      components[i] = ci * scale * (decode[j + 1] - decode[j]) + decode[j];
    }
    const color = this.tmpCsCompsBuf;
    if (this.context.colorFn) {
      this.context.colorFn(components, 0, color, 0);
    }
    return this.context.colorSpace.getRgb(color, 0);
  }
}
let bCache = Object.create(null);
function buildB(count) {
  const lut = [];
  for (let i = 0; i <= count; i++) {
    const t = i / count,
      t_ = 1 - t;
    lut.push(new Float32Array([t_ ** 3, 3 * t * t_ ** 2, 3 * t ** 2 * t_, t ** 3]));
  }
  return lut;
}
function getB(count) {
  return bCache[count] ||= buildB(count);
}
function clearPatternCaches() {
  bCache = Object.create(null);
}
class MeshShading extends BaseShading {
  static MIN_SPLIT_PATCH_CHUNKS_AMOUNT = 3;
  static MAX_SPLIT_PATCH_CHUNKS_AMOUNT = 20;
  static TRIANGLE_DENSITY = 20;
  constructor(stream, xref, resources, pdfFunctionFactory, localColorSpaceCache) {
    super();
    if (!(stream instanceof BaseStream)) {
      throw new FormatError("Mesh data is not a stream");
    }
    const dict = stream.dict;
    this.shadingType = dict.get("ShadingType");
    this.bbox = lookupNormalRect(dict.getArray("BBox"), null);
    const cs = ColorSpace.parse({
      cs: dict.getRaw("CS") || dict.getRaw("ColorSpace"),
      xref,
      resources,
      pdfFunctionFactory,
      localColorSpaceCache
    });
    this.background = dict.has("Background") ? cs.getRgb(dict.get("Background"), 0) : null;
    const fnObj = dict.getRaw("Function");
    const fn = fnObj ? pdfFunctionFactory.createFromArray(fnObj) : null;
    this.coords = [];
    this.colors = [];
    this.figures = [];
    const decodeContext = {
      bitsPerCoordinate: dict.get("BitsPerCoordinate"),
      bitsPerComponent: dict.get("BitsPerComponent"),
      bitsPerFlag: dict.get("BitsPerFlag"),
      decode: dict.getArray("Decode"),
      colorFn: fn,
      colorSpace: cs,
      numComps: fn ? 1 : cs.numComps
    };
    const reader = new MeshStreamReader(stream, decodeContext);
    let patchMesh = false;
    switch (this.shadingType) {
      case ShadingType.FREE_FORM_MESH:
        this._decodeType4Shading(reader);
        break;
      case ShadingType.LATTICE_FORM_MESH:
        const verticesPerRow = dict.get("VerticesPerRow") | 0;
        if (verticesPerRow < 2) {
          throw new FormatError("Invalid VerticesPerRow");
        }
        this._decodeType5Shading(reader, verticesPerRow);
        break;
      case ShadingType.COONS_PATCH_MESH:
        this._decodeType6Shading(reader);
        patchMesh = true;
        break;
      case ShadingType.TENSOR_PATCH_MESH:
        this._decodeType7Shading(reader);
        patchMesh = true;
        break;
      default:
        unreachable("Unsupported mesh type.");
        break;
    }
    if (patchMesh) {
      this._updateBounds();
      for (let i = 0, ii = this.figures.length; i < ii; i++) {
        this._buildFigureFromPatch(i);
      }
    }
    this._updateBounds();
    this._packData();
  }
  _decodeType4Shading(reader) {
    const coords = this.coords;
    const colors = this.colors;
    const operators = [];
    const ps = [];
    let verticesLeft = 0;
    while (reader.hasData) {
      const f = reader.readFlag();
      const coord = reader.readCoordinate();
      const color = reader.readComponents();
      if (verticesLeft === 0) {
        if (!(0 <= f && f <= 2)) {
          throw new FormatError("Unknown type4 flag");
        }
        switch (f) {
          case 0:
            verticesLeft = 3;
            break;
          case 1:
            ps.push(ps.at(-2), ps.at(-1));
            verticesLeft = 1;
            break;
          case 2:
            ps.push(ps.at(-3), ps.at(-1));
            verticesLeft = 1;
            break;
        }
        operators.push(f);
      }
      ps.push(coords.length);
      coords.push(coord);
      colors.push(color);
      verticesLeft--;
      reader.align();
    }
    this.figures.push({
      type: "triangles",
      coords: new Int32Array(ps),
      colors: new Int32Array(ps)
    });
  }
  _decodeType5Shading(reader, verticesPerRow) {
    const coords = this.coords;
    const colors = this.colors;
    const ps = [];
    while (reader.hasData) {
      const coord = reader.readCoordinate();
      const color = reader.readComponents();
      ps.push(coords.length);
      coords.push(coord);
      colors.push(color);
    }
    this.figures.push({
      type: "lattice",
      coords: new Int32Array(ps),
      colors: new Int32Array(ps),
      verticesPerRow
    });
  }
  _decodeType6Shading(reader) {
    const coords = this.coords;
    const colors = this.colors;
    const ps = new Int32Array(16);
    const cs = new Int32Array(4);
    while (reader.hasData) {
      const f = reader.readFlag();
      if (!(0 <= f && f <= 3)) {
        throw new FormatError("Unknown type6 flag");
      }
      const pi = coords.length;
      for (let i = 0, ii = f !== 0 ? 8 : 12; i < ii; i++) {
        coords.push(reader.readCoordinate());
      }
      const ci = colors.length;
      for (let i = 0, ii = f !== 0 ? 2 : 4; i < ii; i++) {
        colors.push(reader.readComponents());
      }
      let tmp1, tmp2, tmp3, tmp4;
      switch (f) {
        case 0:
          ps[12] = pi + 3;
          ps[13] = pi + 4;
          ps[14] = pi + 5;
          ps[15] = pi + 6;
          ps[8] = pi + 2;
          ps[11] = pi + 7;
          ps[4] = pi + 1;
          ps[7] = pi + 8;
          ps[0] = pi;
          ps[1] = pi + 11;
          ps[2] = pi + 10;
          ps[3] = pi + 9;
          cs[2] = ci + 1;
          cs[3] = ci + 2;
          cs[0] = ci;
          cs[1] = ci + 3;
          break;
        case 1:
          tmp1 = ps[12];
          tmp2 = ps[13];
          tmp3 = ps[14];
          tmp4 = ps[15];
          ps[12] = tmp4;
          ps[13] = pi + 0;
          ps[14] = pi + 1;
          ps[15] = pi + 2;
          ps[8] = tmp3;
          ps[11] = pi + 3;
          ps[4] = tmp2;
          ps[7] = pi + 4;
          ps[0] = tmp1;
          ps[1] = pi + 7;
          ps[2] = pi + 6;
          ps[3] = pi + 5;
          tmp1 = cs[2];
          tmp2 = cs[3];
          cs[2] = tmp2;
          cs[3] = ci;
          cs[0] = tmp1;
          cs[1] = ci + 1;
          break;
        case 2:
          tmp1 = ps[15];
          tmp2 = ps[11];
          ps[12] = ps[3];
          ps[13] = pi + 0;
          ps[14] = pi + 1;
          ps[15] = pi + 2;
          ps[8] = ps[7];
          ps[11] = pi + 3;
          ps[4] = tmp2;
          ps[7] = pi + 4;
          ps[0] = tmp1;
          ps[1] = pi + 7;
          ps[2] = pi + 6;
          ps[3] = pi + 5;
          tmp1 = cs[3];
          cs[2] = cs[1];
          cs[3] = ci;
          cs[0] = tmp1;
          cs[1] = ci + 1;
          break;
        case 3:
          ps[12] = ps[0];
          ps[13] = pi + 0;
          ps[14] = pi + 1;
          ps[15] = pi + 2;
          ps[8] = ps[1];
          ps[11] = pi + 3;
          ps[4] = ps[2];
          ps[7] = pi + 4;
          ps[0] = ps[3];
          ps[1] = pi + 7;
          ps[2] = pi + 6;
          ps[3] = pi + 5;
          cs[2] = cs[0];
          cs[3] = ci;
          cs[0] = cs[1];
          cs[1] = ci + 1;
          break;
      }
      ps[5] = coords.length;
      coords.push([(-4 * coords[ps[0]][0] - coords[ps[15]][0] + 6 * (coords[ps[4]][0] + coords[ps[1]][0]) - 2 * (coords[ps[12]][0] + coords[ps[3]][0]) + 3 * (coords[ps[13]][0] + coords[ps[7]][0])) / 9, (-4 * coords[ps[0]][1] - coords[ps[15]][1] + 6 * (coords[ps[4]][1] + coords[ps[1]][1]) - 2 * (coords[ps[12]][1] + coords[ps[3]][1]) + 3 * (coords[ps[13]][1] + coords[ps[7]][1])) / 9]);
      ps[6] = coords.length;
      coords.push([(-4 * coords[ps[3]][0] - coords[ps[12]][0] + 6 * (coords[ps[2]][0] + coords[ps[7]][0]) - 2 * (coords[ps[0]][0] + coords[ps[15]][0]) + 3 * (coords[ps[4]][0] + coords[ps[14]][0])) / 9, (-4 * coords[ps[3]][1] - coords[ps[12]][1] + 6 * (coords[ps[2]][1] + coords[ps[7]][1]) - 2 * (coords[ps[0]][1] + coords[ps[15]][1]) + 3 * (coords[ps[4]][1] + coords[ps[14]][1])) / 9]);
      ps[9] = coords.length;
      coords.push([(-4 * coords[ps[12]][0] - coords[ps[3]][0] + 6 * (coords[ps[8]][0] + coords[ps[13]][0]) - 2 * (coords[ps[0]][0] + coords[ps[15]][0]) + 3 * (coords[ps[11]][0] + coords[ps[1]][0])) / 9, (-4 * coords[ps[12]][1] - coords[ps[3]][1] + 6 * (coords[ps[8]][1] + coords[ps[13]][1]) - 2 * (coords[ps[0]][1] + coords[ps[15]][1]) + 3 * (coords[ps[11]][1] + coords[ps[1]][1])) / 9]);
      ps[10] = coords.length;
      coords.push([(-4 * coords[ps[15]][0] - coords[ps[0]][0] + 6 * (coords[ps[11]][0] + coords[ps[14]][0]) - 2 * (coords[ps[12]][0] + coords[ps[3]][0]) + 3 * (coords[ps[2]][0] + coords[ps[8]][0])) / 9, (-4 * coords[ps[15]][1] - coords[ps[0]][1] + 6 * (coords[ps[11]][1] + coords[ps[14]][1]) - 2 * (coords[ps[12]][1] + coords[ps[3]][1]) + 3 * (coords[ps[2]][1] + coords[ps[8]][1])) / 9]);
      this.figures.push({
        type: "patch",
        coords: new Int32Array(ps),
        colors: new Int32Array(cs)
      });
    }
  }
  _decodeType7Shading(reader) {
    const coords = this.coords;
    const colors = this.colors;
    const ps = new Int32Array(16);
    const cs = new Int32Array(4);
    while (reader.hasData) {
      const f = reader.readFlag();
      if (!(0 <= f && f <= 3)) {
        throw new FormatError("Unknown type7 flag");
      }
      const pi = coords.length;
      for (let i = 0, ii = f !== 0 ? 12 : 16; i < ii; i++) {
        coords.push(reader.readCoordinate());
      }
      const ci = colors.length;
      for (let i = 0, ii = f !== 0 ? 2 : 4; i < ii; i++) {
        colors.push(reader.readComponents());
      }
      let tmp1, tmp2, tmp3, tmp4;
      switch (f) {
        case 0:
          ps[12] = pi + 3;
          ps[13] = pi + 4;
          ps[14] = pi + 5;
          ps[15] = pi + 6;
          ps[8] = pi + 2;
          ps[9] = pi + 13;
          ps[10] = pi + 14;
          ps[11] = pi + 7;
          ps[4] = pi + 1;
          ps[5] = pi + 12;
          ps[6] = pi + 15;
          ps[7] = pi + 8;
          ps[0] = pi;
          ps[1] = pi + 11;
          ps[2] = pi + 10;
          ps[3] = pi + 9;
          cs[2] = ci + 1;
          cs[3] = ci + 2;
          cs[0] = ci;
          cs[1] = ci + 3;
          break;
        case 1:
          tmp1 = ps[12];
          tmp2 = ps[13];
          tmp3 = ps[14];
          tmp4 = ps[15];
          ps[12] = tmp4;
          ps[13] = pi + 0;
          ps[14] = pi + 1;
          ps[15] = pi + 2;
          ps[8] = tmp3;
          ps[9] = pi + 9;
          ps[10] = pi + 10;
          ps[11] = pi + 3;
          ps[4] = tmp2;
          ps[5] = pi + 8;
          ps[6] = pi + 11;
          ps[7] = pi + 4;
          ps[0] = tmp1;
          ps[1] = pi + 7;
          ps[2] = pi + 6;
          ps[3] = pi + 5;
          tmp1 = cs[2];
          tmp2 = cs[3];
          cs[2] = tmp2;
          cs[3] = ci;
          cs[0] = tmp1;
          cs[1] = ci + 1;
          break;
        case 2:
          tmp1 = ps[15];
          tmp2 = ps[11];
          ps[12] = ps[3];
          ps[13] = pi + 0;
          ps[14] = pi + 1;
          ps[15] = pi + 2;
          ps[8] = ps[7];
          ps[9] = pi + 9;
          ps[10] = pi + 10;
          ps[11] = pi + 3;
          ps[4] = tmp2;
          ps[5] = pi + 8;
          ps[6] = pi + 11;
          ps[7] = pi + 4;
          ps[0] = tmp1;
          ps[1] = pi + 7;
          ps[2] = pi + 6;
          ps[3] = pi + 5;
          tmp1 = cs[3];
          cs[2] = cs[1];
          cs[3] = ci;
          cs[0] = tmp1;
          cs[1] = ci + 1;
          break;
        case 3:
          ps[12] = ps[0];
          ps[13] = pi + 0;
          ps[14] = pi + 1;
          ps[15] = pi + 2;
          ps[8] = ps[1];
          ps[9] = pi + 9;
          ps[10] = pi + 10;
          ps[11] = pi + 3;
          ps[4] = ps[2];
          ps[5] = pi + 8;
          ps[6] = pi + 11;
          ps[7] = pi + 4;
          ps[0] = ps[3];
          ps[1] = pi + 7;
          ps[2] = pi + 6;
          ps[3] = pi + 5;
          cs[2] = cs[0];
          cs[3] = ci;
          cs[0] = cs[1];
          cs[1] = ci + 1;
          break;
      }
      this.figures.push({
        type: "patch",
        coords: new Int32Array(ps),
        colors: new Int32Array(cs)
      });
    }
  }
  _buildFigureFromPatch(index) {
    const figure = this.figures[index];
    assert(figure.type === "patch", "Unexpected patch mesh figure");
    const coords = this.coords,
      colors = this.colors;
    const pi = figure.coords;
    const ci = figure.colors;
    const figureMinX = Math.min(coords[pi[0]][0], coords[pi[3]][0], coords[pi[12]][0], coords[pi[15]][0]);
    const figureMinY = Math.min(coords[pi[0]][1], coords[pi[3]][1], coords[pi[12]][1], coords[pi[15]][1]);
    const figureMaxX = Math.max(coords[pi[0]][0], coords[pi[3]][0], coords[pi[12]][0], coords[pi[15]][0]);
    const figureMaxY = Math.max(coords[pi[0]][1], coords[pi[3]][1], coords[pi[12]][1], coords[pi[15]][1]);
    let splitXBy = Math.ceil((figureMaxX - figureMinX) * MeshShading.TRIANGLE_DENSITY / (this.bounds[2] - this.bounds[0]));
    splitXBy = Math.max(MeshShading.MIN_SPLIT_PATCH_CHUNKS_AMOUNT, Math.min(MeshShading.MAX_SPLIT_PATCH_CHUNKS_AMOUNT, splitXBy));
    let splitYBy = Math.ceil((figureMaxY - figureMinY) * MeshShading.TRIANGLE_DENSITY / (this.bounds[3] - this.bounds[1]));
    splitYBy = Math.max(MeshShading.MIN_SPLIT_PATCH_CHUNKS_AMOUNT, Math.min(MeshShading.MAX_SPLIT_PATCH_CHUNKS_AMOUNT, splitYBy));
    const verticesPerRow = splitXBy + 1;
    const figureCoords = new Int32Array((splitYBy + 1) * verticesPerRow);
    const figureColors = new Int32Array((splitYBy + 1) * verticesPerRow);
    let k = 0;
    const cl = new Uint8Array(3),
      cr = new Uint8Array(3);
    const c0 = colors[ci[0]],
      c1 = colors[ci[1]],
      c2 = colors[ci[2]],
      c3 = colors[ci[3]];
    const bRow = getB(splitYBy),
      bCol = getB(splitXBy);
    for (let row = 0; row <= splitYBy; row++) {
      cl[0] = (c0[0] * (splitYBy - row) + c2[0] * row) / splitYBy | 0;
      cl[1] = (c0[1] * (splitYBy - row) + c2[1] * row) / splitYBy | 0;
      cl[2] = (c0[2] * (splitYBy - row) + c2[2] * row) / splitYBy | 0;
      cr[0] = (c1[0] * (splitYBy - row) + c3[0] * row) / splitYBy | 0;
      cr[1] = (c1[1] * (splitYBy - row) + c3[1] * row) / splitYBy | 0;
      cr[2] = (c1[2] * (splitYBy - row) + c3[2] * row) / splitYBy | 0;
      for (let col = 0; col <= splitXBy; col++, k++) {
        if ((row === 0 || row === splitYBy) && (col === 0 || col === splitXBy)) {
          continue;
        }
        let x = 0,
          y = 0;
        let q = 0;
        for (let i = 0; i <= 3; i++) {
          for (let j = 0; j <= 3; j++, q++) {
            const m = bRow[row][i] * bCol[col][j];
            x += coords[pi[q]][0] * m;
            y += coords[pi[q]][1] * m;
          }
        }
        figureCoords[k] = coords.length;
        coords.push([x, y]);
        figureColors[k] = colors.length;
        const newColor = new Uint8Array(3);
        newColor[0] = (cl[0] * (splitXBy - col) + cr[0] * col) / splitXBy | 0;
        newColor[1] = (cl[1] * (splitXBy - col) + cr[1] * col) / splitXBy | 0;
        newColor[2] = (cl[2] * (splitXBy - col) + cr[2] * col) / splitXBy | 0;
        colors.push(newColor);
      }
    }
    figureCoords[0] = pi[0];
    figureColors[0] = ci[0];
    figureCoords[splitXBy] = pi[3];
    figureColors[splitXBy] = ci[1];
    figureCoords[verticesPerRow * splitYBy] = pi[12];
    figureColors[verticesPerRow * splitYBy] = ci[2];
    figureCoords[verticesPerRow * splitYBy + splitXBy] = pi[15];
    figureColors[verticesPerRow * splitYBy + splitXBy] = ci[3];
    this.figures[index] = {
      type: "lattice",
      coords: figureCoords,
      colors: figureColors,
      verticesPerRow
    };
  }
  _updateBounds() {
    let minX = this.coords[0][0],
      minY = this.coords[0][1],
      maxX = minX,
      maxY = minY;
    for (let i = 1, ii = this.coords.length; i < ii; i++) {
      const x = this.coords[i][0],
        y = this.coords[i][1];
      minX = minX > x ? x : minX;
      minY = minY > y ? y : minY;
      maxX = maxX < x ? x : maxX;
      maxY = maxY < y ? y : maxY;
    }
    this.bounds = [minX, minY, maxX, maxY];
  }
  _packData() {
    let i, ii, j, jj;
    const coords = this.coords;
    const coordsPacked = new Float32Array(coords.length * 2);
    for (i = 0, j = 0, ii = coords.length; i < ii; i++) {
      const xy = coords[i];
      coordsPacked[j++] = xy[0];
      coordsPacked[j++] = xy[1];
    }
    this.coords = coordsPacked;
    const colors = this.colors;
    const colorsPacked = new Uint8Array(colors.length * 3);
    for (i = 0, j = 0, ii = colors.length; i < ii; i++) {
      const c = colors[i];
      colorsPacked[j++] = c[0];
      colorsPacked[j++] = c[1];
      colorsPacked[j++] = c[2];
    }
    this.colors = colorsPacked;
    const figures = this.figures;
    for (i = 0, ii = figures.length; i < ii; i++) {
      const figure = figures[i],
        ps = figure.coords,
        cs = figure.colors;
      for (j = 0, jj = ps.length; j < jj; j++) {
        ps[j] *= 2;
        cs[j] *= 3;
      }
    }
  }
  getIR() {
    const {
      bounds
    } = this;
    if (bounds[2] - bounds[0] === 0 || bounds[3] - bounds[1] === 0) {
      throw new FormatError(`Invalid MeshShading bounds: [${bounds}].`);
    }
    return ["Mesh", this.shadingType, this.coords, this.colors, this.figures, bounds, this.bbox, this.background];
  }
}
class DummyShading extends BaseShading {
  getIR() {
    return ["Dummy"];
  }
}
function getTilingPatternIR(operatorList, dict, color) {
  const matrix = lookupMatrix(dict.getArray("Matrix"), IDENTITY_MATRIX);
  const bbox = lookupNormalRect(dict.getArray("BBox"), null);
  if (!bbox || bbox[2] - bbox[0] === 0 || bbox[3] - bbox[1] === 0) {
    throw new FormatError(`Invalid getTilingPatternIR /BBox array.`);
  }
  const xstep = dict.get("XStep");
  if (typeof xstep !== "number") {
    throw new FormatError(`Invalid getTilingPatternIR /XStep value.`);
  }
  const ystep = dict.get("YStep");
  if (typeof ystep !== "number") {
    throw new FormatError(`Invalid getTilingPatternIR /YStep value.`);
  }
  const paintType = dict.get("PaintType");
  if (!Number.isInteger(paintType)) {
    throw new FormatError(`Invalid getTilingPatternIR /PaintType value.`);
  }
  const tilingType = dict.get("TilingType");
  if (!Number.isInteger(tilingType)) {
    throw new FormatError(`Invalid getTilingPatternIR /TilingType value.`);
  }
  return ["TilingPattern", color, operatorList, matrix, bbox, xstep, ystep, paintType, tilingType];
}

;// CONCATENATED MODULE: ./src/core/calibri_factors.js
const CalibriBoldFactors = [1.3877, 1, 1, 1, 0.97801, 0.92482, 0.89552, 0.91133, 0.81988, 0.97566, 0.98152, 0.93548, 0.93548, 1.2798, 0.85284, 0.92794, 1, 0.96134, 1.54657, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.82845, 0.82845, 0.85284, 0.85284, 0.85284, 0.75859, 0.92138, 0.83908, 0.7762, 0.73293, 0.87289, 0.73133, 0.7514, 0.81921, 0.87356, 0.95958, 0.59526, 0.75727, 0.69225, 1.04924, 0.9121, 0.86943, 0.79795, 0.88198, 0.77958, 0.70864, 0.81055, 0.90399, 0.88653, 0.96017, 0.82577, 0.77892, 0.78257, 0.97507, 1.54657, 0.97507, 0.85284, 0.89552, 0.90176, 0.88762, 0.8785, 0.75241, 0.8785, 0.90518, 0.95015, 0.77618, 0.8785, 0.88401, 0.91916, 0.86304, 0.88401, 0.91488, 0.8785, 0.8801, 0.8785, 0.8785, 0.91343, 0.7173, 1.04106, 0.8785, 0.85075, 0.95794, 0.82616, 0.85162, 0.79492, 0.88331, 1.69808, 0.88331, 0.85284, 0.97801, 0.89552, 0.91133, 0.89552, 0.91133, 1.7801, 0.89552, 1.24487, 1.13254, 1.12401, 0.96839, 0.85284, 0.68787, 0.70645, 0.85592, 0.90747, 1.01466, 1.0088, 0.90323, 1, 1.07463, 1, 0.91056, 0.75806, 1.19118, 0.96839, 0.78864, 0.82845, 0.84133, 0.75859, 0.83908, 0.83908, 0.83908, 0.83908, 0.83908, 0.83908, 0.77539, 0.73293, 0.73133, 0.73133, 0.73133, 0.73133, 0.95958, 0.95958, 0.95958, 0.95958, 0.88506, 0.9121, 0.86943, 0.86943, 0.86943, 0.86943, 0.86943, 0.85284, 0.87508, 0.90399, 0.90399, 0.90399, 0.90399, 0.77892, 0.79795, 0.90807, 0.88762, 0.88762, 0.88762, 0.88762, 0.88762, 0.88762, 0.8715, 0.75241, 0.90518, 0.90518, 0.90518, 0.90518, 0.88401, 0.88401, 0.88401, 0.88401, 0.8785, 0.8785, 0.8801, 0.8801, 0.8801, 0.8801, 0.8801, 0.90747, 0.89049, 0.8785, 0.8785, 0.8785, 0.8785, 0.85162, 0.8785, 0.85162, 0.83908, 0.88762, 0.83908, 0.88762, 0.83908, 0.88762, 0.73293, 0.75241, 0.73293, 0.75241, 0.73293, 0.75241, 0.73293, 0.75241, 0.87289, 0.83016, 0.88506, 0.93125, 0.73133, 0.90518, 0.73133, 0.90518, 0.73133, 0.90518, 0.73133, 0.90518, 0.73133, 0.90518, 0.81921, 0.77618, 0.81921, 0.77618, 0.81921, 0.77618, 1, 1, 0.87356, 0.8785, 0.91075, 0.89608, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.76229, 0.90167, 0.59526, 0.91916, 1, 1, 0.86304, 0.69225, 0.88401, 1, 1, 0.70424, 0.79468, 0.91926, 0.88175, 0.70823, 0.94903, 0.9121, 0.8785, 1, 1, 0.9121, 0.8785, 0.87802, 0.88656, 0.8785, 0.86943, 0.8801, 0.86943, 0.8801, 0.86943, 0.8801, 0.87402, 0.89291, 0.77958, 0.91343, 1, 1, 0.77958, 0.91343, 0.70864, 0.7173, 0.70864, 0.7173, 0.70864, 0.7173, 0.70864, 0.7173, 1, 1, 0.81055, 0.75841, 0.81055, 1.06452, 0.90399, 0.8785, 0.90399, 0.8785, 0.90399, 0.8785, 0.90399, 0.8785, 0.90399, 0.8785, 0.90399, 0.8785, 0.96017, 0.95794, 0.77892, 0.85162, 0.77892, 0.78257, 0.79492, 0.78257, 0.79492, 0.78257, 0.79492, 0.9297, 0.56892, 0.83908, 0.88762, 0.77539, 0.8715, 0.87508, 0.89049, 1, 1, 0.81055, 1.04106, 1.20528, 1.20528, 1, 1.15543, 0.70674, 0.98387, 0.94721, 1.33431, 1.45894, 0.95161, 1.06303, 0.83908, 0.80352, 0.57184, 0.6965, 0.56289, 0.82001, 0.56029, 0.81235, 1.02988, 0.83908, 0.7762, 0.68156, 0.80367, 0.73133, 0.78257, 0.87356, 0.86943, 0.95958, 0.75727, 0.89019, 1.04924, 0.9121, 0.7648, 0.86943, 0.87356, 0.79795, 0.78275, 0.81055, 0.77892, 0.9762, 0.82577, 0.99819, 0.84896, 0.95958, 0.77892, 0.96108, 1.01407, 0.89049, 1.02988, 0.94211, 0.96108, 0.8936, 0.84021, 0.87842, 0.96399, 0.79109, 0.89049, 1.00813, 1.02988, 0.86077, 0.87445, 0.92099, 0.84723, 0.86513, 0.8801, 0.75638, 0.85714, 0.78216, 0.79586, 0.87965, 0.94211, 0.97747, 0.78287, 0.97926, 0.84971, 1.02988, 0.94211, 0.8801, 0.94211, 0.84971, 0.73133, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90264, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90518, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90548, 1, 1, 1, 1, 1, 1, 0.96017, 0.95794, 0.96017, 0.95794, 0.96017, 0.95794, 0.77892, 0.85162, 1, 1, 0.89552, 0.90527, 1, 0.90363, 0.92794, 0.92794, 0.92794, 0.92794, 0.87012, 0.87012, 0.87012, 0.89552, 0.89552, 1.42259, 0.71143, 1.06152, 1, 1, 1.03372, 1.03372, 0.97171, 1.4956, 2.2807, 0.93835, 0.83406, 0.91133, 0.84107, 0.91133, 1, 1, 1, 0.72021, 1, 1.23108, 0.83489, 0.88525, 0.88525, 0.81499, 0.90527, 1.81055, 0.90527, 1.81055, 1.31006, 1.53711, 0.94434, 1.08696, 1, 0.95018, 0.77192, 0.85284, 0.90747, 1.17534, 0.69825, 0.9716, 1.37077, 0.90747, 0.90747, 0.85356, 0.90747, 0.90747, 1.44947, 0.85284, 0.8941, 0.8941, 0.70572, 0.8, 0.70572, 0.70572, 0.70572, 0.70572, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.99862, 0.99862, 1, 1, 1, 1, 1, 1.08004, 0.91027, 1, 1, 1, 0.99862, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90727, 0.90727, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const CalibriBoldMetrics = {
  lineHeight: 1.2207,
  lineGap: 0.2207
};
const CalibriBoldItalicFactors = [1.3877, 1, 1, 1, 0.97801, 0.92482, 0.89552, 0.91133, 0.81988, 0.97566, 0.98152, 0.93548, 0.93548, 1.2798, 0.85284, 0.92794, 1, 0.96134, 1.56239, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.82845, 0.82845, 0.85284, 0.85284, 0.85284, 0.75859, 0.92138, 0.83908, 0.7762, 0.71805, 0.87289, 0.73133, 0.7514, 0.81921, 0.87356, 0.95958, 0.59526, 0.75727, 0.69225, 1.04924, 0.90872, 0.85938, 0.79795, 0.87068, 0.77958, 0.69766, 0.81055, 0.90399, 0.88653, 0.96068, 0.82577, 0.77892, 0.78257, 0.97507, 1.529, 0.97507, 0.85284, 0.89552, 0.90176, 0.94908, 0.86411, 0.74012, 0.86411, 0.88323, 0.95015, 0.86411, 0.86331, 0.88401, 0.91916, 0.86304, 0.88401, 0.9039, 0.86331, 0.86331, 0.86411, 0.86411, 0.90464, 0.70852, 1.04106, 0.86331, 0.84372, 0.95794, 0.82616, 0.84548, 0.79492, 0.88331, 1.69808, 0.88331, 0.85284, 0.97801, 0.89552, 0.91133, 0.89552, 0.91133, 1.7801, 0.89552, 1.24487, 1.13254, 1.19129, 0.96839, 0.85284, 0.68787, 0.70645, 0.85592, 0.90747, 1.01466, 1.0088, 0.90323, 1, 1.07463, 1, 0.91056, 0.75806, 1.19118, 0.96839, 0.78864, 0.82845, 0.84133, 0.75859, 0.83908, 0.83908, 0.83908, 0.83908, 0.83908, 0.83908, 0.77539, 0.71805, 0.73133, 0.73133, 0.73133, 0.73133, 0.95958, 0.95958, 0.95958, 0.95958, 0.88506, 0.90872, 0.85938, 0.85938, 0.85938, 0.85938, 0.85938, 0.85284, 0.87068, 0.90399, 0.90399, 0.90399, 0.90399, 0.77892, 0.79795, 0.90807, 0.94908, 0.94908, 0.94908, 0.94908, 0.94908, 0.94908, 0.85887, 0.74012, 0.88323, 0.88323, 0.88323, 0.88323, 0.88401, 0.88401, 0.88401, 0.88401, 0.8785, 0.86331, 0.86331, 0.86331, 0.86331, 0.86331, 0.86331, 0.90747, 0.89049, 0.86331, 0.86331, 0.86331, 0.86331, 0.84548, 0.86411, 0.84548, 0.83908, 0.94908, 0.83908, 0.94908, 0.83908, 0.94908, 0.71805, 0.74012, 0.71805, 0.74012, 0.71805, 0.74012, 0.71805, 0.74012, 0.87289, 0.79538, 0.88506, 0.92726, 0.73133, 0.88323, 0.73133, 0.88323, 0.73133, 0.88323, 0.73133, 0.88323, 0.73133, 0.88323, 0.81921, 0.86411, 0.81921, 0.86411, 0.81921, 0.86411, 1, 1, 0.87356, 0.86331, 0.91075, 0.8777, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.95958, 0.88401, 0.76467, 0.90167, 0.59526, 0.91916, 1, 1, 0.86304, 0.69225, 0.88401, 1, 1, 0.70424, 0.77312, 0.91926, 0.88175, 0.70823, 0.94903, 0.90872, 0.86331, 1, 1, 0.90872, 0.86331, 0.86906, 0.88116, 0.86331, 0.85938, 0.86331, 0.85938, 0.86331, 0.85938, 0.86331, 0.87402, 0.86549, 0.77958, 0.90464, 1, 1, 0.77958, 0.90464, 0.69766, 0.70852, 0.69766, 0.70852, 0.69766, 0.70852, 0.69766, 0.70852, 1, 1, 0.81055, 0.75841, 0.81055, 1.06452, 0.90399, 0.86331, 0.90399, 0.86331, 0.90399, 0.86331, 0.90399, 0.86331, 0.90399, 0.86331, 0.90399, 0.86331, 0.96068, 0.95794, 0.77892, 0.84548, 0.77892, 0.78257, 0.79492, 0.78257, 0.79492, 0.78257, 0.79492, 0.9297, 0.56892, 0.83908, 0.94908, 0.77539, 0.85887, 0.87068, 0.89049, 1, 1, 0.81055, 1.04106, 1.20528, 1.20528, 1, 1.15543, 0.70088, 0.98387, 0.94721, 1.33431, 1.45894, 0.95161, 1.48387, 0.83908, 0.80352, 0.57118, 0.6965, 0.56347, 0.79179, 0.55853, 0.80346, 1.02988, 0.83908, 0.7762, 0.67174, 0.86036, 0.73133, 0.78257, 0.87356, 0.86441, 0.95958, 0.75727, 0.89019, 1.04924, 0.90872, 0.74889, 0.85938, 0.87891, 0.79795, 0.7957, 0.81055, 0.77892, 0.97447, 0.82577, 0.97466, 0.87179, 0.95958, 0.77892, 0.94252, 0.95612, 0.8753, 1.02988, 0.92733, 0.94252, 0.87411, 0.84021, 0.8728, 0.95612, 0.74081, 0.8753, 1.02189, 1.02988, 0.84814, 0.87445, 0.91822, 0.84723, 0.85668, 0.86331, 0.81344, 0.87581, 0.76422, 0.82046, 0.96057, 0.92733, 0.99375, 0.78022, 0.95452, 0.86015, 1.02988, 0.92733, 0.86331, 0.92733, 0.86015, 0.73133, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90631, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.88323, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.85174, 1, 1, 1, 1, 1, 1, 0.96068, 0.95794, 0.96068, 0.95794, 0.96068, 0.95794, 0.77892, 0.84548, 1, 1, 0.89552, 0.90527, 1, 0.90363, 0.92794, 0.92794, 0.92794, 0.89807, 0.87012, 0.87012, 0.87012, 0.89552, 0.89552, 1.42259, 0.71094, 1.06152, 1, 1, 1.03372, 1.03372, 0.97171, 1.4956, 2.2807, 0.92972, 0.83406, 0.91133, 0.83326, 0.91133, 1, 1, 1, 0.72021, 1, 1.23108, 0.83489, 0.88525, 0.88525, 0.81499, 0.90616, 1.81055, 0.90527, 1.81055, 1.3107, 1.53711, 0.94434, 1.08696, 1, 0.95018, 0.77192, 0.85284, 0.90747, 1.17534, 0.69825, 0.9716, 1.37077, 0.90747, 0.90747, 0.85356, 0.90747, 0.90747, 1.44947, 0.85284, 0.8941, 0.8941, 0.70572, 0.8, 0.70572, 0.70572, 0.70572, 0.70572, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.99862, 0.99862, 1, 1, 1, 1, 1, 1.08004, 0.91027, 1, 1, 1, 0.99862, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90727, 0.90727, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const CalibriBoldItalicMetrics = {
  lineHeight: 1.2207,
  lineGap: 0.2207
};
const CalibriItalicFactors = [1.3877, 1, 1, 1, 1.17223, 1.1293, 0.89552, 0.91133, 0.80395, 1.02269, 1.15601, 0.91056, 0.91056, 1.2798, 0.85284, 0.89807, 1, 0.90861, 1.39543, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.96309, 0.96309, 0.85284, 0.85284, 0.85284, 0.83319, 0.88071, 0.8675, 0.81552, 0.72346, 0.85193, 0.73206, 0.7522, 0.81105, 0.86275, 0.90685, 0.6377, 0.77892, 0.75593, 1.02638, 0.89249, 0.84118, 0.77452, 0.85374, 0.75186, 0.67789, 0.79776, 0.88844, 0.85066, 0.94309, 0.77818, 0.7306, 0.76659, 1.10369, 1.38313, 1.10369, 1.06139, 0.89552, 0.8739, 0.9245, 0.9245, 0.83203, 0.9245, 0.85865, 1.09842, 0.9245, 0.9245, 1.03297, 1.07692, 0.90918, 1.03297, 0.94959, 0.9245, 0.92274, 0.9245, 0.9245, 1.02933, 0.77832, 1.20562, 0.9245, 0.8916, 0.98986, 0.86621, 0.89453, 0.79004, 0.94152, 1.77256, 0.94152, 0.85284, 0.97801, 0.89552, 0.91133, 0.89552, 0.91133, 1.91729, 0.89552, 1.17889, 1.13254, 1.16359, 0.92098, 0.85284, 0.68787, 0.71353, 0.84737, 0.90747, 1.0088, 1.0044, 0.87683, 1, 1.09091, 1, 0.92229, 0.739, 1.15642, 0.92098, 0.76288, 0.80504, 0.80972, 0.75859, 0.8675, 0.8675, 0.8675, 0.8675, 0.8675, 0.8675, 0.76318, 0.72346, 0.73206, 0.73206, 0.73206, 0.73206, 0.90685, 0.90685, 0.90685, 0.90685, 0.86477, 0.89249, 0.84118, 0.84118, 0.84118, 0.84118, 0.84118, 0.85284, 0.84557, 0.88844, 0.88844, 0.88844, 0.88844, 0.7306, 0.77452, 0.86331, 0.9245, 0.9245, 0.9245, 0.9245, 0.9245, 0.9245, 0.84843, 0.83203, 0.85865, 0.85865, 0.85865, 0.85865, 0.82601, 0.82601, 0.82601, 0.82601, 0.94469, 0.9245, 0.92274, 0.92274, 0.92274, 0.92274, 0.92274, 0.90747, 0.86651, 0.9245, 0.9245, 0.9245, 0.9245, 0.89453, 0.9245, 0.89453, 0.8675, 0.9245, 0.8675, 0.9245, 0.8675, 0.9245, 0.72346, 0.83203, 0.72346, 0.83203, 0.72346, 0.83203, 0.72346, 0.83203, 0.85193, 0.8875, 0.86477, 0.99034, 0.73206, 0.85865, 0.73206, 0.85865, 0.73206, 0.85865, 0.73206, 0.85865, 0.73206, 0.85865, 0.81105, 0.9245, 0.81105, 0.9245, 0.81105, 0.9245, 1, 1, 0.86275, 0.9245, 0.90872, 0.93591, 0.90685, 0.82601, 0.90685, 0.82601, 0.90685, 0.82601, 0.90685, 1.03297, 0.90685, 0.82601, 0.77896, 1.05611, 0.6377, 1.07692, 1, 1, 0.90918, 0.75593, 1.03297, 1, 1, 0.76032, 0.9375, 0.98156, 0.93407, 0.77261, 1.11429, 0.89249, 0.9245, 1, 1, 0.89249, 0.9245, 0.92534, 0.86698, 0.9245, 0.84118, 0.92274, 0.84118, 0.92274, 0.84118, 0.92274, 0.8667, 0.86291, 0.75186, 1.02933, 1, 1, 0.75186, 1.02933, 0.67789, 0.77832, 0.67789, 0.77832, 0.67789, 0.77832, 0.67789, 0.77832, 1, 1, 0.79776, 0.97655, 0.79776, 1.23023, 0.88844, 0.9245, 0.88844, 0.9245, 0.88844, 0.9245, 0.88844, 0.9245, 0.88844, 0.9245, 0.88844, 0.9245, 0.94309, 0.98986, 0.7306, 0.89453, 0.7306, 0.76659, 0.79004, 0.76659, 0.79004, 0.76659, 0.79004, 1.09231, 0.54873, 0.8675, 0.9245, 0.76318, 0.84843, 0.84557, 0.86651, 1, 1, 0.79776, 1.20562, 1.18622, 1.18622, 1, 1.1437, 0.67009, 0.96334, 0.93695, 1.35191, 1.40909, 0.95161, 1.48387, 0.8675, 0.90861, 0.6192, 0.7363, 0.64824, 0.82411, 0.56321, 0.85696, 1.23516, 0.8675, 0.81552, 0.7286, 0.84134, 0.73206, 0.76659, 0.86275, 0.84369, 0.90685, 0.77892, 0.85871, 1.02638, 0.89249, 0.75828, 0.84118, 0.85984, 0.77452, 0.76466, 0.79776, 0.7306, 0.90782, 0.77818, 0.903, 0.87291, 0.90685, 0.7306, 0.99058, 1.03667, 0.94635, 1.23516, 0.9849, 0.99058, 0.92393, 0.8916, 0.942, 1.03667, 0.75026, 0.94635, 1.0297, 1.23516, 0.90918, 0.94048, 0.98217, 0.89746, 0.84153, 0.92274, 0.82507, 0.88832, 0.84438, 0.88178, 1.03525, 0.9849, 1.00225, 0.78086, 0.97248, 0.89404, 1.23516, 0.9849, 0.92274, 0.9849, 0.89404, 0.73206, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.89693, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.85865, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.90933, 1, 1, 1, 1, 1, 1, 0.94309, 0.98986, 0.94309, 0.98986, 0.94309, 0.98986, 0.7306, 0.89453, 1, 1, 0.89552, 0.90527, 1, 0.90186, 1.12308, 1.12308, 1.12308, 1.12308, 1.2566, 1.2566, 1.2566, 0.89552, 0.89552, 1.42259, 0.68994, 1.03809, 1, 1, 1.0176, 1.0176, 1.11523, 1.4956, 2.01462, 0.97858, 0.82616, 0.91133, 0.83437, 0.91133, 1, 1, 1, 0.70508, 1, 1.23108, 0.79801, 0.84426, 0.84426, 0.774, 0.90572, 1.81055, 0.90749, 1.81055, 1.28809, 1.55469, 0.94434, 1.07806, 1, 0.97094, 0.7589, 0.85284, 0.90747, 1.19658, 0.69825, 0.97622, 1.33512, 0.90747, 0.90747, 0.85284, 0.90747, 0.90747, 1.44947, 0.85284, 0.8941, 0.8941, 0.70572, 0.8, 0.70572, 0.70572, 0.70572, 0.70572, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.99862, 0.99862, 1, 1, 1, 1, 1, 1.0336, 0.91027, 1, 1, 1, 0.99862, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.05859, 1.05859, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const CalibriItalicMetrics = {
  lineHeight: 1.2207,
  lineGap: 0.2207
};
const CalibriRegularFactors = [1.3877, 1, 1, 1, 1.17223, 1.1293, 0.89552, 0.91133, 0.80395, 1.02269, 1.15601, 0.91056, 0.91056, 1.2798, 0.85284, 0.89807, 1, 0.90861, 1.39016, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.91133, 0.96309, 0.96309, 0.85284, 0.85284, 0.85284, 0.83319, 0.88071, 0.8675, 0.81552, 0.73834, 0.85193, 0.73206, 0.7522, 0.81105, 0.86275, 0.90685, 0.6377, 0.77892, 0.75593, 1.02638, 0.89385, 0.85122, 0.77452, 0.86503, 0.75186, 0.68887, 0.79776, 0.88844, 0.85066, 0.94258, 0.77818, 0.7306, 0.76659, 1.10369, 1.39016, 1.10369, 1.06139, 0.89552, 0.8739, 0.86128, 0.94469, 0.8457, 0.94469, 0.89464, 1.09842, 0.84636, 0.94469, 1.03297, 1.07692, 0.90918, 1.03297, 0.95897, 0.94469, 0.9482, 0.94469, 0.94469, 1.04692, 0.78223, 1.20562, 0.94469, 0.90332, 0.98986, 0.86621, 0.90527, 0.79004, 0.94152, 1.77256, 0.94152, 0.85284, 0.97801, 0.89552, 0.91133, 0.89552, 0.91133, 1.91729, 0.89552, 1.17889, 1.13254, 1.08707, 0.92098, 0.85284, 0.68787, 0.71353, 0.84737, 0.90747, 1.0088, 1.0044, 0.87683, 1, 1.09091, 1, 0.92229, 0.739, 1.15642, 0.92098, 0.76288, 0.80504, 0.80972, 0.75859, 0.8675, 0.8675, 0.8675, 0.8675, 0.8675, 0.8675, 0.76318, 0.73834, 0.73206, 0.73206, 0.73206, 0.73206, 0.90685, 0.90685, 0.90685, 0.90685, 0.86477, 0.89385, 0.85122, 0.85122, 0.85122, 0.85122, 0.85122, 0.85284, 0.85311, 0.88844, 0.88844, 0.88844, 0.88844, 0.7306, 0.77452, 0.86331, 0.86128, 0.86128, 0.86128, 0.86128, 0.86128, 0.86128, 0.8693, 0.8457, 0.89464, 0.89464, 0.89464, 0.89464, 0.82601, 0.82601, 0.82601, 0.82601, 0.94469, 0.94469, 0.9482, 0.9482, 0.9482, 0.9482, 0.9482, 0.90747, 0.86651, 0.94469, 0.94469, 0.94469, 0.94469, 0.90527, 0.94469, 0.90527, 0.8675, 0.86128, 0.8675, 0.86128, 0.8675, 0.86128, 0.73834, 0.8457, 0.73834, 0.8457, 0.73834, 0.8457, 0.73834, 0.8457, 0.85193, 0.92454, 0.86477, 0.9921, 0.73206, 0.89464, 0.73206, 0.89464, 0.73206, 0.89464, 0.73206, 0.89464, 0.73206, 0.89464, 0.81105, 0.84636, 0.81105, 0.84636, 0.81105, 0.84636, 1, 1, 0.86275, 0.94469, 0.90872, 0.95786, 0.90685, 0.82601, 0.90685, 0.82601, 0.90685, 0.82601, 0.90685, 1.03297, 0.90685, 0.82601, 0.77741, 1.05611, 0.6377, 1.07692, 1, 1, 0.90918, 0.75593, 1.03297, 1, 1, 0.76032, 0.90452, 0.98156, 1.11842, 0.77261, 1.11429, 0.89385, 0.94469, 1, 1, 0.89385, 0.94469, 0.95877, 0.86901, 0.94469, 0.85122, 0.9482, 0.85122, 0.9482, 0.85122, 0.9482, 0.8667, 0.90016, 0.75186, 1.04692, 1, 1, 0.75186, 1.04692, 0.68887, 0.78223, 0.68887, 0.78223, 0.68887, 0.78223, 0.68887, 0.78223, 1, 1, 0.79776, 0.92188, 0.79776, 1.23023, 0.88844, 0.94469, 0.88844, 0.94469, 0.88844, 0.94469, 0.88844, 0.94469, 0.88844, 0.94469, 0.88844, 0.94469, 0.94258, 0.98986, 0.7306, 0.90527, 0.7306, 0.76659, 0.79004, 0.76659, 0.79004, 0.76659, 0.79004, 1.09231, 0.54873, 0.8675, 0.86128, 0.76318, 0.8693, 0.85311, 0.86651, 1, 1, 0.79776, 1.20562, 1.18622, 1.18622, 1, 1.1437, 0.67742, 0.96334, 0.93695, 1.35191, 1.40909, 0.95161, 1.48387, 0.86686, 0.90861, 0.62267, 0.74359, 0.65649, 0.85498, 0.56963, 0.88254, 1.23516, 0.8675, 0.81552, 0.75443, 0.84503, 0.73206, 0.76659, 0.86275, 0.85122, 0.90685, 0.77892, 0.85746, 1.02638, 0.89385, 0.75657, 0.85122, 0.86275, 0.77452, 0.74171, 0.79776, 0.7306, 0.95165, 0.77818, 0.89772, 0.88831, 0.90685, 0.7306, 0.98142, 1.02191, 0.96576, 1.23516, 0.99018, 0.98142, 0.9236, 0.89258, 0.94035, 1.02191, 0.78848, 0.96576, 0.9561, 1.23516, 0.90918, 0.92578, 0.95424, 0.89746, 0.83969, 0.9482, 0.80113, 0.89442, 0.85208, 0.86155, 0.98022, 0.99018, 1.00452, 0.81209, 0.99247, 0.89181, 1.23516, 0.99018, 0.9482, 0.99018, 0.89181, 0.73206, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.88844, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.89464, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.96766, 1, 1, 1, 1, 1, 1, 0.94258, 0.98986, 0.94258, 0.98986, 0.94258, 0.98986, 0.7306, 0.90527, 1, 1, 0.89552, 0.90527, 1, 0.90186, 1.12308, 1.12308, 1.12308, 1.12308, 1.2566, 1.2566, 1.2566, 0.89552, 0.89552, 1.42259, 0.69043, 1.03809, 1, 1, 1.0176, 1.0176, 1.11523, 1.4956, 2.01462, 0.99331, 0.82616, 0.91133, 0.84286, 0.91133, 1, 1, 1, 0.70508, 1, 1.23108, 0.79801, 0.84426, 0.84426, 0.774, 0.90527, 1.81055, 0.90527, 1.81055, 1.28809, 1.55469, 0.94434, 1.07806, 1, 0.97094, 0.7589, 0.85284, 0.90747, 1.19658, 0.69825, 0.97622, 1.33512, 0.90747, 0.90747, 0.85356, 0.90747, 0.90747, 1.44947, 0.85284, 0.8941, 0.8941, 0.70572, 0.8, 0.70572, 0.70572, 0.70572, 0.70572, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.99862, 0.99862, 1, 1, 1, 1, 1, 1.0336, 0.91027, 1, 1, 1, 0.99862, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.05859, 1.05859, 1, 1, 1, 1.07185, 0.99413, 0.96334, 1.08065, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const CalibriRegularMetrics = {
  lineHeight: 1.2207,
  lineGap: 0.2207
};

;// CONCATENATED MODULE: ./src/core/helvetica_factors.js
const HelveticaBoldFactors = [0.76116, 1, 1, 1.0006, 0.99998, 0.99974, 0.99973, 0.99973, 0.99982, 0.99977, 1.00087, 0.99998, 0.99998, 0.99959, 1.00003, 1.0006, 0.99998, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99998, 1, 1.00003, 1.00003, 1.00003, 1.00026, 0.9999, 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00026, 1.00022, 0.99977, 1.0006, 0.99973, 0.99977, 1.00026, 0.99999, 0.99977, 1.00022, 1.00001, 1.00022, 0.99977, 1.00001, 1.00026, 0.99977, 1.00001, 1.00016, 1.00001, 1.00001, 1.00026, 0.99998, 1.0006, 0.99998, 1.00003, 0.99973, 0.99998, 0.99973, 1.00026, 0.99973, 1.00026, 0.99973, 0.99998, 1.00026, 1.00026, 1.0006, 1.0006, 0.99973, 1.0006, 0.99982, 1.00026, 1.00026, 1.00026, 1.00026, 0.99959, 0.99973, 0.99998, 1.00026, 0.99973, 1.00022, 0.99973, 0.99973, 1, 0.99959, 1.00077, 0.99959, 1.00003, 0.99998, 0.99973, 0.99973, 0.99973, 0.99973, 1.00077, 0.99973, 0.99998, 1.00025, 0.99968, 0.99973, 1.00003, 1.00025, 0.60299, 1.00024, 1.06409, 1, 1, 0.99998, 1, 0.99973, 1.0006, 0.99998, 1, 0.99936, 0.99973, 1.00002, 1.00002, 1.00002, 1.00026, 0.99977, 0.99977, 0.99977, 0.99977, 0.99977, 0.99977, 1, 0.99977, 1.00001, 1.00001, 1.00001, 1.00001, 1.0006, 1.0006, 1.0006, 1.0006, 0.99977, 0.99977, 1.00022, 1.00022, 1.00022, 1.00022, 1.00022, 1.00003, 1.00022, 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00001, 1.00026, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99982, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 1.06409, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 0.99973, 1.00026, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 1.03374, 0.99977, 1.00026, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.00042, 0.99973, 0.99973, 1.0006, 0.99977, 0.99973, 0.99973, 1.00026, 1.0006, 1.00026, 1.0006, 1.00026, 1.03828, 1.00026, 0.99999, 1.00026, 1.0006, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.9993, 0.9998, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1, 1.00016, 0.99977, 0.99959, 0.99977, 0.99959, 0.99977, 0.99959, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00026, 0.99998, 1.00026, 0.8121, 1.00026, 0.99998, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 1.00016, 1.00022, 1.00001, 0.99973, 1.00001, 1.00026, 1, 1.00026, 1, 1.00026, 1, 1.0006, 0.99973, 0.99977, 0.99973, 1, 0.99982, 1.00022, 1.00026, 1.00001, 0.99973, 1.00026, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 1.00034, 0.99977, 1, 0.99997, 1.00026, 1.00078, 1.00036, 0.99973, 1.00013, 1.0006, 0.99977, 0.99977, 0.99988, 0.85148, 1.00001, 1.00026, 0.99977, 1.00022, 1.0006, 0.99977, 1.00001, 0.99999, 0.99977, 1.00069, 1.00022, 0.99977, 1.00001, 0.99984, 1.00026, 1.00001, 1.00024, 1.00001, 0.9999, 1, 1.0006, 1.00001, 1.00041, 0.99962, 1.00026, 1.0006, 0.99995, 1.00041, 0.99942, 0.99973, 0.99927, 1.00082, 0.99902, 1.00026, 1.00087, 1.0006, 1.00069, 0.99973, 0.99867, 0.99973, 0.9993, 1.00026, 1.00049, 1.00056, 1, 0.99988, 0.99935, 0.99995, 0.99954, 1.00055, 0.99945, 1.00032, 1.0006, 0.99995, 1.00026, 0.99995, 1.00032, 1.00001, 1.00008, 0.99971, 1.00019, 0.9994, 1.00001, 1.0006, 1.00044, 0.99973, 1.00023, 1.00047, 1, 0.99942, 0.99561, 0.99989, 1.00035, 0.99977, 1.00035, 0.99977, 1.00019, 0.99944, 1.00001, 1.00021, 0.99926, 1.00035, 1.00035, 0.99942, 1.00048, 0.99999, 0.99977, 1.00022, 1.00035, 1.00001, 0.99977, 1.00026, 0.99989, 1.00057, 1.00001, 0.99936, 1.00052, 1.00012, 0.99996, 1.00043, 1, 1.00035, 0.9994, 0.99976, 1.00035, 0.99973, 1.00052, 1.00041, 1.00119, 1.00037, 0.99973, 1.00002, 0.99986, 1.00041, 1.00041, 0.99902, 0.9996, 1.00034, 0.99999, 1.00026, 0.99999, 1.00026, 0.99973, 1.00052, 0.99973, 1, 0.99973, 1.00041, 1.00075, 0.9994, 1.0003, 0.99999, 1, 1.00041, 0.99955, 1, 0.99915, 0.99973, 0.99973, 1.00026, 1.00119, 0.99955, 0.99973, 1.0006, 0.99911, 1.0006, 1.00026, 0.99972, 1.00026, 0.99902, 1.00041, 0.99973, 0.99999, 1, 1, 1.00038, 1.0005, 1.00016, 1.00022, 1.00016, 1.00022, 1.00016, 1.00022, 1.00001, 0.99973, 1, 1, 0.99973, 1, 1, 0.99955, 1.0006, 1.0006, 1.0006, 1.0006, 1, 1, 1, 0.99973, 0.99973, 0.99972, 1, 1, 1.00106, 0.99999, 0.99998, 0.99998, 0.99999, 0.99998, 1.66475, 1, 0.99973, 0.99973, 1.00023, 0.99973, 0.99971, 1.00047, 1.00023, 1, 0.99991, 0.99984, 1.00002, 1.00002, 1.00002, 1.00002, 1, 1, 1, 1, 1, 1, 1, 0.99972, 1, 1.20985, 1.39713, 1.00003, 1.00031, 1.00015, 1, 0.99561, 1.00027, 1.00031, 1.00031, 0.99915, 1.00031, 1.00031, 0.99999, 1.00003, 0.99999, 0.99999, 1.41144, 1.6, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.40579, 1.40579, 1.36625, 0.99999, 1, 0.99861, 0.99861, 1, 1.00026, 1.00026, 1.00026, 1.00026, 0.99972, 0.99999, 0.99999, 0.99999, 0.99999, 1.40483, 1, 0.99977, 1.00054, 1, 1, 0.99953, 0.99962, 1.00042, 0.9995, 1, 1, 1, 1, 1, 1, 1, 1, 0.99998, 0.99998, 0.99998, 0.99998, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const HelveticaBoldMetrics = {
  lineHeight: 1.2,
  lineGap: 0.2
};
const HelveticaBoldItalicFactors = [0.76116, 1, 1, 1.0006, 0.99998, 0.99974, 0.99973, 0.99973, 0.99982, 0.99977, 1.00087, 0.99998, 0.99998, 0.99959, 1.00003, 1.0006, 0.99998, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99998, 1, 1.00003, 1.00003, 1.00003, 1.00026, 0.9999, 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00026, 1.00022, 0.99977, 1.0006, 0.99973, 0.99977, 1.00026, 0.99999, 0.99977, 1.00022, 1.00001, 1.00022, 0.99977, 1.00001, 1.00026, 0.99977, 1.00001, 1.00016, 1.00001, 1.00001, 1.00026, 0.99998, 1.0006, 0.99998, 1.00003, 0.99973, 0.99998, 0.99973, 1.00026, 0.99973, 1.00026, 0.99973, 0.99998, 1.00026, 1.00026, 1.0006, 1.0006, 0.99973, 1.0006, 0.99982, 1.00026, 1.00026, 1.00026, 1.00026, 0.99959, 0.99973, 0.99998, 1.00026, 0.99973, 1.00022, 0.99973, 0.99973, 1, 0.99959, 1.00077, 0.99959, 1.00003, 0.99998, 0.99973, 0.99973, 0.99973, 0.99973, 1.00077, 0.99973, 0.99998, 1.00025, 0.99968, 0.99973, 1.00003, 1.00025, 0.60299, 1.00024, 1.06409, 1, 1, 0.99998, 1, 0.99973, 1.0006, 0.99998, 1, 0.99936, 0.99973, 1.00002, 1.00002, 1.00002, 1.00026, 0.99977, 0.99977, 0.99977, 0.99977, 0.99977, 0.99977, 1, 0.99977, 1.00001, 1.00001, 1.00001, 1.00001, 1.0006, 1.0006, 1.0006, 1.0006, 0.99977, 0.99977, 1.00022, 1.00022, 1.00022, 1.00022, 1.00022, 1.00003, 1.00022, 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00001, 1.00026, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99982, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 1.06409, 1.00026, 1.00026, 1.00026, 1.00026, 1.00026, 0.99973, 1.00026, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 1.0044, 0.99977, 1.00026, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 0.99971, 0.99973, 0.99973, 1.0006, 0.99977, 0.99973, 0.99973, 1.00026, 1.0006, 1.00026, 1.0006, 1.00026, 1.01011, 1.00026, 0.99999, 1.00026, 1.0006, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.9993, 0.9998, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1.00022, 1.00026, 1, 1.00016, 0.99977, 0.99959, 0.99977, 0.99959, 0.99977, 0.99959, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00026, 0.99998, 1.00026, 0.8121, 1.00026, 0.99998, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 0.99977, 1.00026, 1.00016, 1.00022, 1.00001, 0.99973, 1.00001, 1.00026, 1, 1.00026, 1, 1.00026, 1, 1.0006, 0.99973, 0.99977, 0.99973, 1, 0.99982, 1.00022, 1.00026, 1.00001, 0.99973, 1.00026, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99977, 1, 1, 1.00026, 0.99969, 0.99972, 0.99981, 0.9998, 1.0006, 0.99977, 0.99977, 1.00022, 0.91155, 1.00001, 1.00026, 0.99977, 1.00022, 1.0006, 0.99977, 1.00001, 0.99999, 0.99977, 0.99966, 1.00022, 1.00032, 1.00001, 0.99944, 1.00026, 1.00001, 0.99968, 1.00001, 1.00047, 1, 1.0006, 1.00001, 0.99981, 1.00101, 1.00026, 1.0006, 0.99948, 0.99981, 1.00064, 0.99973, 0.99942, 1.00101, 1.00061, 1.00026, 1.00069, 1.0006, 1.00014, 0.99973, 1.01322, 0.99973, 1.00065, 1.00026, 1.00012, 0.99923, 1, 1.00064, 1.00076, 0.99948, 1.00055, 1.00063, 1.00007, 0.99943, 1.0006, 0.99948, 1.00026, 0.99948, 0.99943, 1.00001, 1.00001, 1.00029, 1.00038, 1.00035, 1.00001, 1.0006, 1.0006, 0.99973, 0.99978, 1.00001, 1.00057, 0.99989, 0.99967, 0.99964, 0.99967, 0.99977, 0.99999, 0.99977, 1.00038, 0.99977, 1.00001, 0.99973, 1.00066, 0.99967, 0.99967, 1.00041, 0.99998, 0.99999, 0.99977, 1.00022, 0.99967, 1.00001, 0.99977, 1.00026, 0.99964, 1.00031, 1.00001, 0.99999, 0.99999, 1, 1.00023, 1, 1, 0.99999, 1.00035, 1.00001, 0.99999, 0.99973, 0.99977, 0.99999, 1.00058, 0.99973, 0.99973, 0.99955, 0.9995, 1.00026, 1.00026, 1.00032, 0.99989, 1.00034, 0.99999, 1.00026, 1.00026, 1.00026, 0.99973, 0.45998, 0.99973, 1.00026, 0.99973, 1.00001, 0.99999, 0.99982, 0.99994, 0.99996, 1, 1.00042, 1.00044, 1.00029, 1.00023, 0.99973, 0.99973, 1.00026, 0.99949, 1.00002, 0.99973, 1.0006, 1.0006, 1.0006, 0.99975, 1.00026, 1.00026, 1.00032, 0.98685, 0.99973, 1.00026, 1, 1, 0.99966, 1.00044, 1.00016, 1.00022, 1.00016, 1.00022, 1.00016, 1.00022, 1.00001, 0.99973, 1, 1, 0.99973, 1, 1, 0.99955, 1.0006, 1.0006, 1.0006, 1.0006, 1, 1, 1, 0.99973, 0.99973, 0.99972, 1, 1, 1.00106, 0.99999, 0.99998, 0.99998, 0.99999, 0.99998, 1.66475, 1, 0.99973, 0.99973, 1, 0.99973, 0.99971, 0.99978, 1, 1, 0.99991, 0.99984, 1.00002, 1.00002, 1.00002, 1.00002, 1.00098, 1, 1, 1, 1.00049, 1, 1, 0.99972, 1, 1.20985, 1.39713, 1.00003, 1.00031, 1.00015, 1, 0.99561, 1.00027, 1.00031, 1.00031, 0.99915, 1.00031, 1.00031, 0.99999, 1.00003, 0.99999, 0.99999, 1.41144, 1.6, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.40579, 1.40579, 1.36625, 0.99999, 1, 0.99861, 0.99861, 1, 1.00026, 1.00026, 1.00026, 1.00026, 0.99972, 0.99999, 0.99999, 0.99999, 0.99999, 1.40483, 1, 0.99977, 1.00054, 1, 1, 0.99953, 0.99962, 1.00042, 0.9995, 1, 1, 1, 1, 1, 1, 1, 1, 0.99998, 0.99998, 0.99998, 0.99998, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const HelveticaBoldItalicMetrics = {
  lineHeight: 1.35,
  lineGap: 0.2
};
const HelveticaItalicFactors = [0.76116, 1, 1, 1.0006, 1.0006, 1.00006, 0.99973, 0.99973, 0.99982, 1.00001, 1.00043, 0.99998, 0.99998, 0.99959, 1.00003, 1.0006, 0.99998, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 1.0006, 1, 1.00003, 1.00003, 1.00003, 0.99973, 0.99987, 1.00001, 1.00001, 0.99977, 0.99977, 1.00001, 1.00026, 1.00022, 0.99977, 1.0006, 1, 1.00001, 0.99973, 0.99999, 0.99977, 1.00022, 1.00001, 1.00022, 0.99977, 1.00001, 1.00026, 0.99977, 1.00001, 1.00016, 1.00001, 1.00001, 1.00026, 1.0006, 1.0006, 1.0006, 0.99949, 0.99973, 0.99998, 0.99973, 0.99973, 1, 0.99973, 0.99973, 1.0006, 0.99973, 0.99973, 0.99924, 0.99924, 1, 0.99924, 0.99999, 0.99973, 0.99973, 0.99973, 0.99973, 0.99998, 1, 1.0006, 0.99973, 1, 0.99977, 1, 1, 1, 1.00005, 1.0009, 1.00005, 1.00003, 0.99998, 0.99973, 0.99973, 0.99973, 0.99973, 1.0009, 0.99973, 0.99998, 1.00025, 0.99968, 0.99973, 1.00003, 1.00025, 0.60299, 1.00024, 1.06409, 1, 1, 0.99998, 1, 0.9998, 1.0006, 0.99998, 1, 0.99936, 0.99973, 1.00002, 1.00002, 1.00002, 1.00026, 1.00001, 1.00001, 1.00001, 1.00001, 1.00001, 1.00001, 1, 0.99977, 1.00001, 1.00001, 1.00001, 1.00001, 1.0006, 1.0006, 1.0006, 1.0006, 0.99977, 0.99977, 1.00022, 1.00022, 1.00022, 1.00022, 1.00022, 1.00003, 1.00022, 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00001, 1.00026, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99982, 1, 0.99973, 0.99973, 0.99973, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 1.06409, 1.00026, 0.99973, 0.99973, 0.99973, 0.99973, 1, 0.99973, 1, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 0.99977, 1, 0.99977, 1, 0.99977, 1, 0.99977, 1, 0.99977, 1.0288, 0.99977, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 0.99924, 1.0006, 1.0006, 0.99946, 1.00034, 1, 0.99924, 1.00001, 1, 1, 0.99973, 0.99924, 0.99973, 0.99924, 0.99973, 1.06311, 0.99973, 1.00024, 0.99973, 0.99924, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 1.00041, 0.9998, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1, 1.00016, 0.99977, 0.99998, 0.99977, 0.99998, 0.99977, 0.99998, 1.00001, 1, 1.00001, 1, 1.00001, 1, 1.00001, 1, 1.00026, 1.0006, 1.00026, 0.89547, 1.00026, 1.0006, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 1.00016, 0.99977, 1.00001, 1, 1.00001, 1.00026, 1, 1.00026, 1, 1.00026, 1, 0.99924, 0.99973, 1.00001, 0.99973, 1, 0.99982, 1.00022, 1.00026, 1.00001, 1, 1.00026, 1.0006, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 1.00001, 1, 1.00054, 0.99977, 1.00084, 1.00007, 0.99973, 1.00013, 0.99924, 1.00001, 1.00001, 0.99945, 0.91221, 1.00001, 1.00026, 0.99977, 1.00022, 1.0006, 1.00001, 1.00001, 0.99999, 0.99977, 0.99933, 1.00022, 1.00054, 1.00001, 1.00065, 1.00026, 1.00001, 1.0001, 1.00001, 1.00052, 1, 1.0006, 1.00001, 0.99945, 0.99897, 0.99968, 0.99924, 1.00036, 0.99945, 0.99949, 1, 1.0006, 0.99897, 0.99918, 0.99968, 0.99911, 0.99924, 1, 0.99962, 1.01487, 1, 1.0005, 0.99973, 1.00012, 1.00043, 1, 0.99995, 0.99994, 1.00036, 0.99947, 1.00019, 1.00063, 1.00025, 0.99924, 1.00036, 0.99973, 1.00036, 1.00025, 1.00001, 1.00001, 1.00027, 1.0001, 1.00068, 1.00001, 1.0006, 1.0006, 1, 1.00008, 0.99957, 0.99972, 0.9994, 0.99954, 0.99975, 1.00051, 1.00001, 1.00019, 1.00001, 1.0001, 0.99986, 1.00001, 1.00001, 1.00038, 0.99954, 0.99954, 0.9994, 1.00066, 0.99999, 0.99977, 1.00022, 1.00054, 1.00001, 0.99977, 1.00026, 0.99975, 1.0001, 1.00001, 0.99993, 0.9995, 0.99955, 1.00016, 0.99978, 0.99974, 1.00019, 1.00022, 0.99955, 1.00053, 0.99973, 1.00089, 1.00005, 0.99967, 1.00048, 0.99973, 1.00002, 1.00034, 0.99973, 0.99973, 0.99964, 1.00006, 1.00066, 0.99947, 0.99973, 0.98894, 0.99973, 1, 0.44898, 1, 0.99946, 1, 1.00039, 1.00082, 0.99991, 0.99991, 0.99985, 1.00022, 1.00023, 1.00061, 1.00006, 0.99966, 0.99973, 0.99973, 0.99973, 1.00019, 1.0008, 1, 0.99924, 0.99924, 0.99924, 0.99983, 1.00044, 0.99973, 0.99964, 0.98332, 1, 0.99973, 1, 1, 0.99962, 0.99895, 1.00016, 0.99977, 1.00016, 0.99977, 1.00016, 0.99977, 1.00001, 1, 1, 1, 0.99973, 1, 1, 0.99955, 0.99924, 0.99924, 0.99924, 0.99924, 0.99998, 0.99998, 0.99998, 0.99973, 0.99973, 0.99972, 1, 1, 1.00267, 0.99999, 0.99998, 0.99998, 1, 0.99998, 1.66475, 1, 0.99973, 0.99973, 1.00023, 0.99973, 1.00423, 0.99925, 0.99999, 1, 0.99991, 0.99984, 1.00002, 1.00002, 1.00002, 1.00002, 1.00049, 1, 1.00245, 1, 1, 1, 1, 0.96329, 1, 1.20985, 1.39713, 1.00003, 0.8254, 1.00015, 1, 1.00035, 1.00027, 1.00031, 1.00031, 1.00003, 1.00031, 1.00031, 0.99999, 1.00003, 0.99999, 0.99999, 1.41144, 1.6, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.40579, 1.40579, 1.36625, 0.99999, 1, 0.99861, 0.99861, 1, 1.00026, 1.00026, 1.00026, 1.00026, 0.95317, 0.99999, 0.99999, 0.99999, 0.99999, 1.40483, 1, 0.99977, 1.00054, 1, 1, 0.99953, 0.99962, 1.00042, 0.9995, 1, 1, 1, 1, 1, 1, 1, 1, 0.99998, 0.99998, 0.99998, 0.99998, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const HelveticaItalicMetrics = {
  lineHeight: 1.35,
  lineGap: 0.2
};
const HelveticaRegularFactors = [0.76116, 1, 1, 1.0006, 1.0006, 1.00006, 0.99973, 0.99973, 0.99982, 1.00001, 1.00043, 0.99998, 0.99998, 0.99959, 1.00003, 1.0006, 0.99998, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 1.0006, 1, 1.00003, 1.00003, 1.00003, 0.99973, 0.99987, 1.00001, 1.00001, 0.99977, 0.99977, 1.00001, 1.00026, 1.00022, 0.99977, 1.0006, 1, 1.00001, 0.99973, 0.99999, 0.99977, 1.00022, 1.00001, 1.00022, 0.99977, 1.00001, 1.00026, 0.99977, 1.00001, 1.00016, 1.00001, 1.00001, 1.00026, 1.0006, 1.0006, 1.0006, 0.99949, 0.99973, 0.99998, 0.99973, 0.99973, 1, 0.99973, 0.99973, 1.0006, 0.99973, 0.99973, 0.99924, 0.99924, 1, 0.99924, 0.99999, 0.99973, 0.99973, 0.99973, 0.99973, 0.99998, 1, 1.0006, 0.99973, 1, 0.99977, 1, 1, 1, 1.00005, 1.0009, 1.00005, 1.00003, 0.99998, 0.99973, 0.99973, 0.99973, 0.99973, 1.0009, 0.99973, 0.99998, 1.00025, 0.99968, 0.99973, 1.00003, 1.00025, 0.60299, 1.00024, 1.06409, 1, 1, 0.99998, 1, 0.9998, 1.0006, 0.99998, 1, 0.99936, 0.99973, 1.00002, 1.00002, 1.00002, 1.00026, 1.00001, 1.00001, 1.00001, 1.00001, 1.00001, 1.00001, 1, 0.99977, 1.00001, 1.00001, 1.00001, 1.00001, 1.0006, 1.0006, 1.0006, 1.0006, 0.99977, 0.99977, 1.00022, 1.00022, 1.00022, 1.00022, 1.00022, 1.00003, 1.00022, 0.99977, 0.99977, 0.99977, 0.99977, 1.00001, 1.00001, 1.00026, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99982, 1, 0.99973, 0.99973, 0.99973, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 0.99973, 1.06409, 1.00026, 0.99973, 0.99973, 0.99973, 0.99973, 1, 0.99973, 1, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 0.99977, 1, 0.99977, 1, 0.99977, 1, 0.99977, 1, 0.99977, 1.04596, 0.99977, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00001, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 1.0006, 0.99924, 1.0006, 1.0006, 1.00019, 1.00034, 1, 0.99924, 1.00001, 1, 1, 0.99973, 0.99924, 0.99973, 0.99924, 0.99973, 1.02572, 0.99973, 1.00005, 0.99973, 0.99924, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99999, 0.9998, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1.00022, 0.99973, 1, 1.00016, 0.99977, 0.99998, 0.99977, 0.99998, 0.99977, 0.99998, 1.00001, 1, 1.00001, 1, 1.00001, 1, 1.00001, 1, 1.00026, 1.0006, 1.00026, 0.84533, 1.00026, 1.0006, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 0.99977, 0.99973, 1.00016, 0.99977, 1.00001, 1, 1.00001, 1.00026, 1, 1.00026, 1, 1.00026, 1, 0.99924, 0.99973, 1.00001, 0.99973, 1, 0.99982, 1.00022, 1.00026, 1.00001, 1, 1.00026, 1.0006, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99998, 0.99928, 1, 0.99977, 1.00013, 1.00055, 0.99947, 0.99945, 0.99941, 0.99924, 1.00001, 1.00001, 1.0004, 0.91621, 1.00001, 1.00026, 0.99977, 1.00022, 1.0006, 1.00001, 1.00005, 0.99999, 0.99977, 1.00015, 1.00022, 0.99977, 1.00001, 0.99973, 1.00026, 1.00001, 1.00019, 1.00001, 0.99946, 1, 1.0006, 1.00001, 0.99978, 1.00045, 0.99973, 0.99924, 1.00023, 0.99978, 0.99966, 1, 1.00065, 1.00045, 1.00019, 0.99973, 0.99973, 0.99924, 1, 1, 0.96499, 1, 1.00055, 0.99973, 1.00008, 1.00027, 1, 0.9997, 0.99995, 1.00023, 0.99933, 1.00019, 1.00015, 1.00031, 0.99924, 1.00023, 0.99973, 1.00023, 1.00031, 1.00001, 0.99928, 1.00029, 1.00092, 1.00035, 1.00001, 1.0006, 1.0006, 1, 0.99988, 0.99975, 1, 1.00082, 0.99561, 0.9996, 1.00035, 1.00001, 0.99962, 1.00001, 1.00092, 0.99964, 1.00001, 0.99963, 0.99999, 1.00035, 1.00035, 1.00082, 0.99962, 0.99999, 0.99977, 1.00022, 1.00035, 1.00001, 0.99977, 1.00026, 0.9996, 0.99967, 1.00001, 1.00034, 1.00074, 1.00054, 1.00053, 1.00063, 0.99971, 0.99962, 1.00035, 0.99975, 0.99977, 0.99973, 1.00043, 0.99953, 1.0007, 0.99915, 0.99973, 1.00008, 0.99892, 1.00073, 1.00073, 1.00114, 0.99915, 1.00073, 0.99955, 0.99973, 1.00092, 0.99973, 1, 0.99998, 1, 1.0003, 1, 1.00043, 1.00001, 0.99969, 1.0003, 1, 1.00035, 1.00001, 0.9995, 1, 1.00092, 0.99973, 0.99973, 0.99973, 1.0007, 0.9995, 1, 0.99924, 1.0006, 0.99924, 0.99972, 1.00062, 0.99973, 1.00114, 1.00073, 1, 0.99955, 1, 1, 1.00047, 0.99968, 1.00016, 0.99977, 1.00016, 0.99977, 1.00016, 0.99977, 1.00001, 1, 1, 1, 0.99973, 1, 1, 0.99955, 0.99924, 0.99924, 0.99924, 0.99924, 0.99998, 0.99998, 0.99998, 0.99973, 0.99973, 0.99972, 1, 1, 1.00267, 0.99999, 0.99998, 0.99998, 1, 0.99998, 1.66475, 1, 0.99973, 0.99973, 1.00023, 0.99973, 0.99971, 0.99925, 1.00023, 1, 0.99991, 0.99984, 1.00002, 1.00002, 1.00002, 1.00002, 1, 1, 1, 1, 1, 1, 1, 0.96329, 1, 1.20985, 1.39713, 1.00003, 0.8254, 1.00015, 1, 1.00035, 1.00027, 1.00031, 1.00031, 0.99915, 1.00031, 1.00031, 0.99999, 1.00003, 0.99999, 0.99999, 1.41144, 1.6, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.41144, 1.40579, 1.40579, 1.36625, 0.99999, 1, 0.99861, 0.99861, 1, 1.00026, 1.00026, 1.00026, 1.00026, 0.95317, 0.99999, 0.99999, 0.99999, 0.99999, 1.40483, 1, 0.99977, 1.00054, 1, 1, 0.99953, 0.99962, 1.00042, 0.9995, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const HelveticaRegularMetrics = {
  lineHeight: 1.2,
  lineGap: 0.2
};

;// CONCATENATED MODULE: ./src/core/liberationsans_widths.js
const LiberationSansBoldWidths = [365, 0, 333, 278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667, 611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 333, 278, 333, 584, 556, 333, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556, 278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584, 333, 556, 556, 556, 556, 280, 556, 333, 737, 370, 556, 584, 737, 552, 400, 549, 333, 333, 333, 576, 556, 278, 333, 333, 365, 556, 834, 834, 834, 611, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 556, 556, 556, 556, 556, 278, 278, 278, 278, 611, 611, 611, 611, 611, 611, 611, 549, 611, 611, 611, 611, 611, 556, 611, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 719, 722, 611, 667, 556, 667, 556, 667, 556, 667, 556, 667, 556, 778, 611, 778, 611, 778, 611, 778, 611, 722, 611, 722, 611, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 785, 556, 556, 278, 722, 556, 556, 611, 278, 611, 278, 611, 385, 611, 479, 611, 278, 722, 611, 722, 611, 722, 611, 708, 723, 611, 778, 611, 778, 611, 778, 611, 1000, 944, 722, 389, 722, 389, 722, 389, 667, 556, 667, 556, 667, 556, 667, 556, 611, 333, 611, 479, 611, 333, 722, 611, 722, 611, 722, 611, 722, 611, 722, 611, 722, 611, 944, 778, 667, 556, 667, 611, 500, 611, 500, 611, 500, 278, 556, 722, 556, 1000, 889, 778, 611, 667, 556, 611, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 465, 722, 333, 853, 906, 474, 825, 927, 838, 278, 722, 722, 601, 719, 667, 611, 722, 778, 278, 722, 667, 833, 722, 644, 778, 722, 667, 600, 611, 667, 821, 667, 809, 802, 278, 667, 615, 451, 611, 278, 582, 615, 610, 556, 606, 475, 460, 611, 541, 278, 558, 556, 612, 556, 445, 611, 766, 619, 520, 684, 446, 582, 715, 576, 753, 845, 278, 582, 611, 582, 845, 667, 669, 885, 567, 711, 667, 278, 276, 556, 1094, 1062, 875, 610, 722, 622, 719, 722, 719, 722, 567, 712, 667, 904, 626, 719, 719, 610, 702, 833, 722, 778, 719, 667, 722, 611, 622, 854, 667, 730, 703, 1005, 1019, 870, 979, 719, 711, 1031, 719, 556, 618, 615, 417, 635, 556, 709, 497, 615, 615, 500, 635, 740, 604, 611, 604, 611, 556, 490, 556, 875, 556, 615, 581, 833, 844, 729, 854, 615, 552, 854, 583, 556, 556, 611, 417, 552, 556, 278, 281, 278, 969, 906, 611, 500, 615, 556, 604, 778, 611, 487, 447, 944, 778, 944, 778, 944, 778, 667, 556, 333, 333, 556, 1000, 1000, 552, 278, 278, 278, 278, 500, 500, 500, 556, 556, 350, 1000, 1000, 240, 479, 333, 333, 604, 333, 167, 396, 556, 556, 1094, 556, 885, 489, 1115, 1000, 768, 600, 834, 834, 834, 834, 1000, 500, 1000, 500, 1000, 500, 500, 494, 612, 823, 713, 584, 549, 713, 979, 722, 274, 549, 549, 583, 549, 549, 604, 584, 604, 604, 708, 625, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 729, 604, 604, 354, 354, 1000, 990, 990, 990, 990, 494, 604, 604, 604, 604, 354, 1021, 1052, 917, 750, 750, 531, 656, 594, 510, 500, 750, 750, 611, 611, 333, 333, 333, 333, 333, 333, 333, 333, 222, 222, 333, 333, 333, 333, 333, 333, 333, 333];
const LiberationSansBoldMapping = [-1, -1, -1, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 402, 506, 507, 508, 509, 510, 511, 536, 537, 538, 539, 710, 711, 713, 728, 729, 730, 731, 732, 733, 900, 901, 902, 903, 904, 905, 906, 908, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1138, 1139, 1168, 1169, 7808, 7809, 7810, 7811, 7812, 7813, 7922, 7923, 8208, 8209, 8211, 8212, 8213, 8215, 8216, 8217, 8218, 8219, 8220, 8221, 8222, 8224, 8225, 8226, 8230, 8240, 8242, 8243, 8249, 8250, 8252, 8254, 8260, 8319, 8355, 8356, 8359, 8364, 8453, 8467, 8470, 8482, 8486, 8494, 8539, 8540, 8541, 8542, 8592, 8593, 8594, 8595, 8596, 8597, 8616, 8706, 8710, 8719, 8721, 8722, 8730, 8734, 8735, 8745, 8747, 8776, 8800, 8801, 8804, 8805, 8962, 8976, 8992, 8993, 9472, 9474, 9484, 9488, 9492, 9496, 9500, 9508, 9516, 9524, 9532, 9552, 9553, 9554, 9555, 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9564, 9565, 9566, 9567, 9568, 9569, 9570, 9571, 9572, 9573, 9574, 9575, 9576, 9577, 9578, 9579, 9580, 9600, 9604, 9608, 9612, 9616, 9617, 9618, 9619, 9632, 9633, 9642, 9643, 9644, 9650, 9658, 9660, 9668, 9674, 9675, 9679, 9688, 9689, 9702, 9786, 9787, 9788, 9792, 9794, 9824, 9827, 9829, 9830, 9834, 9835, 9836, 61441, 61442, 61445, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
const LiberationSansBoldItalicWidths = [365, 0, 333, 278, 333, 474, 556, 556, 889, 722, 238, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667, 611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 333, 278, 333, 584, 556, 333, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556, 278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584, 333, 556, 556, 556, 556, 280, 556, 333, 737, 370, 556, 584, 737, 552, 400, 549, 333, 333, 333, 576, 556, 278, 333, 333, 365, 556, 834, 834, 834, 611, 722, 722, 722, 722, 722, 722, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 556, 556, 556, 556, 556, 278, 278, 278, 278, 611, 611, 611, 611, 611, 611, 611, 549, 611, 611, 611, 611, 611, 556, 611, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 740, 722, 611, 667, 556, 667, 556, 667, 556, 667, 556, 667, 556, 778, 611, 778, 611, 778, 611, 778, 611, 722, 611, 722, 611, 278, 278, 278, 278, 278, 278, 278, 278, 278, 278, 782, 556, 556, 278, 722, 556, 556, 611, 278, 611, 278, 611, 396, 611, 479, 611, 278, 722, 611, 722, 611, 722, 611, 708, 723, 611, 778, 611, 778, 611, 778, 611, 1000, 944, 722, 389, 722, 389, 722, 389, 667, 556, 667, 556, 667, 556, 667, 556, 611, 333, 611, 479, 611, 333, 722, 611, 722, 611, 722, 611, 722, 611, 722, 611, 722, 611, 944, 778, 667, 556, 667, 611, 500, 611, 500, 611, 500, 278, 556, 722, 556, 1000, 889, 778, 611, 667, 556, 611, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 722, 333, 854, 906, 473, 844, 930, 847, 278, 722, 722, 610, 671, 667, 611, 722, 778, 278, 722, 667, 833, 722, 657, 778, 718, 667, 590, 611, 667, 822, 667, 829, 781, 278, 667, 620, 479, 611, 278, 591, 620, 621, 556, 610, 479, 492, 611, 558, 278, 566, 556, 603, 556, 450, 611, 712, 605, 532, 664, 409, 591, 704, 578, 773, 834, 278, 591, 611, 591, 834, 667, 667, 886, 614, 719, 667, 278, 278, 556, 1094, 1042, 854, 622, 719, 677, 719, 722, 708, 722, 614, 722, 667, 927, 643, 719, 719, 615, 687, 833, 722, 778, 719, 667, 722, 611, 677, 781, 667, 729, 708, 979, 989, 854, 1000, 708, 719, 1042, 729, 556, 619, 604, 534, 618, 556, 736, 510, 611, 611, 507, 622, 740, 604, 611, 611, 611, 556, 889, 556, 885, 556, 646, 583, 889, 935, 707, 854, 594, 552, 865, 589, 556, 556, 611, 469, 563, 556, 278, 278, 278, 969, 906, 611, 507, 619, 556, 611, 778, 611, 575, 467, 944, 778, 944, 778, 944, 778, 667, 556, 333, 333, 556, 1000, 1000, 552, 278, 278, 278, 278, 500, 500, 500, 556, 556, 350, 1000, 1000, 240, 479, 333, 333, 604, 333, 167, 396, 556, 556, 1104, 556, 885, 516, 1146, 1000, 768, 600, 834, 834, 834, 834, 999, 500, 1000, 500, 1000, 500, 500, 494, 612, 823, 713, 584, 549, 713, 979, 722, 274, 549, 549, 583, 549, 549, 604, 584, 604, 604, 708, 625, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 729, 604, 604, 354, 354, 1000, 990, 990, 990, 990, 494, 604, 604, 604, 604, 354, 1021, 1052, 917, 750, 750, 531, 656, 594, 510, 500, 750, 750, 611, 611, 333, 333, 333, 333, 333, 333, 333, 333, 222, 222, 333, 333, 333, 333, 333, 333, 333, 333];
const LiberationSansBoldItalicMapping = [-1, -1, -1, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 402, 506, 507, 508, 509, 510, 511, 536, 537, 538, 539, 710, 711, 713, 728, 729, 730, 731, 732, 733, 900, 901, 902, 903, 904, 905, 906, 908, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1138, 1139, 1168, 1169, 7808, 7809, 7810, 7811, 7812, 7813, 7922, 7923, 8208, 8209, 8211, 8212, 8213, 8215, 8216, 8217, 8218, 8219, 8220, 8221, 8222, 8224, 8225, 8226, 8230, 8240, 8242, 8243, 8249, 8250, 8252, 8254, 8260, 8319, 8355, 8356, 8359, 8364, 8453, 8467, 8470, 8482, 8486, 8494, 8539, 8540, 8541, 8542, 8592, 8593, 8594, 8595, 8596, 8597, 8616, 8706, 8710, 8719, 8721, 8722, 8730, 8734, 8735, 8745, 8747, 8776, 8800, 8801, 8804, 8805, 8962, 8976, 8992, 8993, 9472, 9474, 9484, 9488, 9492, 9496, 9500, 9508, 9516, 9524, 9532, 9552, 9553, 9554, 9555, 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9564, 9565, 9566, 9567, 9568, 9569, 9570, 9571, 9572, 9573, 9574, 9575, 9576, 9577, 9578, 9579, 9580, 9600, 9604, 9608, 9612, 9616, 9617, 9618, 9619, 9632, 9633, 9642, 9643, 9644, 9650, 9658, 9660, 9668, 9674, 9675, 9679, 9688, 9689, 9702, 9786, 9787, 9788, 9792, 9794, 9824, 9827, 9829, 9830, 9834, 9835, 9836, 61441, 61442, 61445, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
const LiberationSansItalicWidths = [365, 0, 333, 278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556, 333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584, 333, 556, 556, 556, 556, 260, 556, 333, 737, 370, 556, 584, 737, 552, 400, 549, 333, 333, 333, 576, 537, 278, 333, 333, 365, 556, 834, 834, 834, 611, 667, 667, 667, 667, 667, 667, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 500, 556, 556, 556, 556, 278, 278, 278, 278, 556, 556, 556, 556, 556, 556, 556, 549, 611, 556, 556, 556, 556, 500, 556, 500, 667, 556, 667, 556, 667, 556, 722, 500, 722, 500, 722, 500, 722, 500, 722, 625, 722, 556, 667, 556, 667, 556, 667, 556, 667, 556, 667, 556, 778, 556, 778, 556, 778, 556, 778, 556, 722, 556, 722, 556, 278, 278, 278, 278, 278, 278, 278, 222, 278, 278, 733, 444, 500, 222, 667, 500, 500, 556, 222, 556, 222, 556, 281, 556, 400, 556, 222, 722, 556, 722, 556, 722, 556, 615, 723, 556, 778, 556, 778, 556, 778, 556, 1000, 944, 722, 333, 722, 333, 722, 333, 667, 500, 667, 500, 667, 500, 667, 500, 611, 278, 611, 354, 611, 278, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 944, 722, 667, 500, 667, 611, 500, 611, 500, 611, 500, 222, 556, 667, 556, 1000, 889, 778, 611, 667, 500, 611, 278, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 667, 278, 789, 846, 389, 794, 865, 775, 222, 667, 667, 570, 671, 667, 611, 722, 778, 278, 667, 667, 833, 722, 648, 778, 725, 667, 600, 611, 667, 837, 667, 831, 761, 278, 667, 570, 439, 555, 222, 550, 570, 571, 500, 556, 439, 463, 555, 542, 222, 500, 492, 548, 500, 447, 556, 670, 573, 486, 603, 374, 550, 652, 546, 728, 779, 222, 550, 556, 550, 779, 667, 667, 843, 544, 708, 667, 278, 278, 500, 1066, 982, 844, 589, 715, 639, 724, 667, 651, 667, 544, 704, 667, 917, 614, 715, 715, 589, 686, 833, 722, 778, 725, 667, 722, 611, 639, 795, 667, 727, 673, 920, 923, 805, 886, 651, 694, 1022, 682, 556, 562, 522, 493, 553, 556, 688, 465, 556, 556, 472, 564, 686, 550, 556, 556, 556, 500, 833, 500, 835, 500, 572, 518, 830, 851, 621, 736, 526, 492, 752, 534, 556, 556, 556, 378, 496, 500, 222, 222, 222, 910, 828, 556, 472, 565, 500, 556, 778, 556, 492, 339, 944, 722, 944, 722, 944, 722, 667, 500, 333, 333, 556, 1000, 1000, 552, 222, 222, 222, 222, 333, 333, 333, 556, 556, 350, 1000, 1000, 188, 354, 333, 333, 500, 333, 167, 365, 556, 556, 1094, 556, 885, 323, 1083, 1000, 768, 600, 834, 834, 834, 834, 1000, 500, 998, 500, 1000, 500, 500, 494, 612, 823, 713, 584, 549, 713, 979, 719, 274, 549, 549, 584, 549, 549, 604, 584, 604, 604, 708, 625, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 729, 604, 604, 354, 354, 1000, 990, 990, 990, 990, 494, 604, 604, 604, 604, 354, 1021, 1052, 917, 750, 750, 531, 656, 594, 510, 500, 750, 750, 500, 500, 333, 333, 333, 333, 333, 333, 333, 333, 222, 222, 294, 294, 324, 324, 316, 328, 398, 285];
const LiberationSansItalicMapping = [-1, -1, -1, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 402, 506, 507, 508, 509, 510, 511, 536, 537, 538, 539, 710, 711, 713, 728, 729, 730, 731, 732, 733, 900, 901, 902, 903, 904, 905, 906, 908, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1138, 1139, 1168, 1169, 7808, 7809, 7810, 7811, 7812, 7813, 7922, 7923, 8208, 8209, 8211, 8212, 8213, 8215, 8216, 8217, 8218, 8219, 8220, 8221, 8222, 8224, 8225, 8226, 8230, 8240, 8242, 8243, 8249, 8250, 8252, 8254, 8260, 8319, 8355, 8356, 8359, 8364, 8453, 8467, 8470, 8482, 8486, 8494, 8539, 8540, 8541, 8542, 8592, 8593, 8594, 8595, 8596, 8597, 8616, 8706, 8710, 8719, 8721, 8722, 8730, 8734, 8735, 8745, 8747, 8776, 8800, 8801, 8804, 8805, 8962, 8976, 8992, 8993, 9472, 9474, 9484, 9488, 9492, 9496, 9500, 9508, 9516, 9524, 9532, 9552, 9553, 9554, 9555, 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9564, 9565, 9566, 9567, 9568, 9569, 9570, 9571, 9572, 9573, 9574, 9575, 9576, 9577, 9578, 9579, 9580, 9600, 9604, 9608, 9612, 9616, 9617, 9618, 9619, 9632, 9633, 9642, 9643, 9644, 9650, 9658, 9660, 9668, 9674, 9675, 9679, 9688, 9689, 9702, 9786, 9787, 9788, 9792, 9794, 9824, 9827, 9829, 9830, 9834, 9835, 9836, 61441, 61442, 61445, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
const LiberationSansRegularWidths = [365, 0, 333, 278, 278, 355, 556, 556, 889, 667, 191, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556, 556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667, 611, 778, 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667, 667, 611, 278, 278, 278, 469, 556, 333, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500, 222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584, 333, 556, 556, 556, 556, 260, 556, 333, 737, 370, 556, 584, 737, 552, 400, 549, 333, 333, 333, 576, 537, 278, 333, 333, 365, 556, 834, 834, 834, 611, 667, 667, 667, 667, 667, 667, 1000, 722, 667, 667, 667, 667, 278, 278, 278, 278, 722, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667, 667, 611, 556, 556, 556, 556, 556, 556, 889, 500, 556, 556, 556, 556, 278, 278, 278, 278, 556, 556, 556, 556, 556, 556, 556, 549, 611, 556, 556, 556, 556, 500, 556, 500, 667, 556, 667, 556, 667, 556, 722, 500, 722, 500, 722, 500, 722, 500, 722, 615, 722, 556, 667, 556, 667, 556, 667, 556, 667, 556, 667, 556, 778, 556, 778, 556, 778, 556, 778, 556, 722, 556, 722, 556, 278, 278, 278, 278, 278, 278, 278, 222, 278, 278, 735, 444, 500, 222, 667, 500, 500, 556, 222, 556, 222, 556, 292, 556, 334, 556, 222, 722, 556, 722, 556, 722, 556, 604, 723, 556, 778, 556, 778, 556, 778, 556, 1000, 944, 722, 333, 722, 333, 722, 333, 667, 500, 667, 500, 667, 500, 667, 500, 611, 278, 611, 375, 611, 278, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 722, 556, 944, 722, 667, 500, 667, 611, 500, 611, 500, 611, 500, 222, 556, 667, 556, 1000, 889, 778, 611, 667, 500, 611, 278, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 333, 667, 278, 784, 838, 384, 774, 855, 752, 222, 667, 667, 551, 668, 667, 611, 722, 778, 278, 667, 668, 833, 722, 650, 778, 722, 667, 618, 611, 667, 798, 667, 835, 748, 278, 667, 578, 446, 556, 222, 547, 578, 575, 500, 557, 446, 441, 556, 556, 222, 500, 500, 576, 500, 448, 556, 690, 569, 482, 617, 395, 547, 648, 525, 713, 781, 222, 547, 556, 547, 781, 667, 667, 865, 542, 719, 667, 278, 278, 500, 1057, 1010, 854, 583, 722, 635, 719, 667, 656, 667, 542, 677, 667, 923, 604, 719, 719, 583, 656, 833, 722, 778, 719, 667, 722, 611, 635, 760, 667, 740, 667, 917, 938, 792, 885, 656, 719, 1010, 722, 556, 573, 531, 365, 583, 556, 669, 458, 559, 559, 438, 583, 688, 552, 556, 542, 556, 500, 458, 500, 823, 500, 573, 521, 802, 823, 625, 719, 521, 510, 750, 542, 556, 556, 556, 365, 510, 500, 222, 278, 222, 906, 812, 556, 438, 559, 500, 552, 778, 556, 489, 411, 944, 722, 944, 722, 944, 722, 667, 500, 333, 333, 556, 1000, 1000, 552, 222, 222, 222, 222, 333, 333, 333, 556, 556, 350, 1000, 1000, 188, 354, 333, 333, 500, 333, 167, 365, 556, 556, 1094, 556, 885, 323, 1073, 1000, 768, 600, 834, 834, 834, 834, 1000, 500, 1000, 500, 1000, 500, 500, 494, 612, 823, 713, 584, 549, 713, 979, 719, 274, 549, 549, 583, 549, 549, 604, 584, 604, 604, 708, 625, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 708, 729, 604, 604, 354, 354, 1000, 990, 990, 990, 990, 494, 604, 604, 604, 604, 354, 1021, 1052, 917, 750, 750, 531, 656, 594, 510, 500, 750, 750, 500, 500, 333, 333, 333, 333, 333, 333, 333, 333, 222, 222, 294, 294, 324, 324, 316, 328, 398, 285];
const LiberationSansRegularMapping = [-1, -1, -1, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, 380, 381, 382, 383, 402, 506, 507, 508, 509, 510, 511, 536, 537, 538, 539, 710, 711, 713, 728, 729, 730, 731, 732, 733, 900, 901, 902, 903, 904, 905, 906, 908, 910, 911, 912, 913, 914, 915, 916, 917, 918, 919, 920, 921, 922, 923, 924, 925, 926, 927, 928, 929, 931, 932, 933, 934, 935, 936, 937, 938, 939, 940, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080, 1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1101, 1102, 1103, 1104, 1105, 1106, 1107, 1108, 1109, 1110, 1111, 1112, 1113, 1114, 1115, 1116, 1117, 1118, 1119, 1138, 1139, 1168, 1169, 7808, 7809, 7810, 7811, 7812, 7813, 7922, 7923, 8208, 8209, 8211, 8212, 8213, 8215, 8216, 8217, 8218, 8219, 8220, 8221, 8222, 8224, 8225, 8226, 8230, 8240, 8242, 8243, 8249, 8250, 8252, 8254, 8260, 8319, 8355, 8356, 8359, 8364, 8453, 8467, 8470, 8482, 8486, 8494, 8539, 8540, 8541, 8542, 8592, 8593, 8594, 8595, 8596, 8597, 8616, 8706, 8710, 8719, 8721, 8722, 8730, 8734, 8735, 8745, 8747, 8776, 8800, 8801, 8804, 8805, 8962, 8976, 8992, 8993, 9472, 9474, 9484, 9488, 9492, 9496, 9500, 9508, 9516, 9524, 9532, 9552, 9553, 9554, 9555, 9556, 9557, 9558, 9559, 9560, 9561, 9562, 9563, 9564, 9565, 9566, 9567, 9568, 9569, 9570, 9571, 9572, 9573, 9574, 9575, 9576, 9577, 9578, 9579, 9580, 9600, 9604, 9608, 9612, 9616, 9617, 9618, 9619, 9632, 9633, 9642, 9643, 9644, 9650, 9658, 9660, 9668, 9674, 9675, 9679, 9688, 9689, 9702, 9786, 9787, 9788, 9792, 9794, 9824, 9827, 9829, 9830, 9834, 9835, 9836, 61441, 61442, 61445, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];

;// CONCATENATED MODULE: ./src/core/myriadpro_factors.js
const MyriadProBoldFactors = [1.36898, 1, 1, 0.72706, 0.80479, 0.83734, 0.98894, 0.99793, 0.9897, 0.93884, 0.86209, 0.94292, 0.94292, 1.16661, 1.02058, 0.93582, 0.96694, 0.93582, 1.19137, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.99793, 0.78076, 0.78076, 1.02058, 1.02058, 1.02058, 0.72851, 0.78966, 0.90838, 0.83637, 0.82391, 0.96376, 0.80061, 0.86275, 0.8768, 0.95407, 1.0258, 0.73901, 0.85022, 0.83655, 1.0156, 0.95546, 0.92179, 0.87107, 0.92179, 0.82114, 0.8096, 0.89713, 0.94438, 0.95353, 0.94083, 0.91905, 0.90406, 0.9446, 0.94292, 1.18777, 0.94292, 1.02058, 0.89903, 0.90088, 0.94938, 0.97898, 0.81093, 0.97571, 0.94938, 1.024, 0.9577, 0.95933, 0.98621, 1.0474, 0.97455, 0.98981, 0.9672, 0.95933, 0.9446, 0.97898, 0.97407, 0.97646, 0.78036, 1.10208, 0.95442, 0.95298, 0.97579, 0.9332, 0.94039, 0.938, 0.80687, 1.01149, 0.80687, 1.02058, 0.80479, 0.99793, 0.99793, 0.99793, 0.99793, 1.01149, 1.00872, 0.90088, 0.91882, 1.0213, 0.8361, 1.02058, 0.62295, 0.54324, 0.89022, 1.08595, 1, 1, 0.90088, 1, 0.97455, 0.93582, 0.90088, 1, 1.05686, 0.8361, 0.99642, 0.99642, 0.99642, 0.72851, 0.90838, 0.90838, 0.90838, 0.90838, 0.90838, 0.90838, 0.868, 0.82391, 0.80061, 0.80061, 0.80061, 0.80061, 1.0258, 1.0258, 1.0258, 1.0258, 0.97484, 0.95546, 0.92179, 0.92179, 0.92179, 0.92179, 0.92179, 1.02058, 0.92179, 0.94438, 0.94438, 0.94438, 0.94438, 0.90406, 0.86958, 0.98225, 0.94938, 0.94938, 0.94938, 0.94938, 0.94938, 0.94938, 0.9031, 0.81093, 0.94938, 0.94938, 0.94938, 0.94938, 0.98621, 0.98621, 0.98621, 0.98621, 0.93969, 0.95933, 0.9446, 0.9446, 0.9446, 0.9446, 0.9446, 1.08595, 0.9446, 0.95442, 0.95442, 0.95442, 0.95442, 0.94039, 0.97898, 0.94039, 0.90838, 0.94938, 0.90838, 0.94938, 0.90838, 0.94938, 0.82391, 0.81093, 0.82391, 0.81093, 0.82391, 0.81093, 0.82391, 0.81093, 0.96376, 0.84313, 0.97484, 0.97571, 0.80061, 0.94938, 0.80061, 0.94938, 0.80061, 0.94938, 0.80061, 0.94938, 0.80061, 0.94938, 0.8768, 0.9577, 0.8768, 0.9577, 0.8768, 0.9577, 1, 1, 0.95407, 0.95933, 0.97069, 0.95933, 1.0258, 0.98621, 1.0258, 0.98621, 1.0258, 0.98621, 1.0258, 0.98621, 1.0258, 0.98621, 0.887, 1.01591, 0.73901, 1.0474, 1, 1, 0.97455, 0.83655, 0.98981, 1, 1, 0.83655, 0.73977, 0.83655, 0.73903, 0.84638, 1.033, 0.95546, 0.95933, 1, 1, 0.95546, 0.95933, 0.8271, 0.95417, 0.95933, 0.92179, 0.9446, 0.92179, 0.9446, 0.92179, 0.9446, 0.936, 0.91964, 0.82114, 0.97646, 1, 1, 0.82114, 0.97646, 0.8096, 0.78036, 0.8096, 0.78036, 1, 1, 0.8096, 0.78036, 1, 1, 0.89713, 0.77452, 0.89713, 1.10208, 0.94438, 0.95442, 0.94438, 0.95442, 0.94438, 0.95442, 0.94438, 0.95442, 0.94438, 0.95442, 0.94438, 0.95442, 0.94083, 0.97579, 0.90406, 0.94039, 0.90406, 0.9446, 0.938, 0.9446, 0.938, 0.9446, 0.938, 1, 0.99793, 0.90838, 0.94938, 0.868, 0.9031, 0.92179, 0.9446, 1, 1, 0.89713, 1.10208, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90989, 0.9358, 0.91945, 0.83181, 0.75261, 0.87992, 0.82976, 0.96034, 0.83689, 0.97268, 1.0078, 0.90838, 0.83637, 0.8019, 0.90157, 0.80061, 0.9446, 0.95407, 0.92436, 1.0258, 0.85022, 0.97153, 1.0156, 0.95546, 0.89192, 0.92179, 0.92361, 0.87107, 0.96318, 0.89713, 0.93704, 0.95638, 0.91905, 0.91709, 0.92796, 1.0258, 0.93704, 0.94836, 1.0373, 0.95933, 1.0078, 0.95871, 0.94836, 0.96174, 0.92601, 0.9498, 0.98607, 0.95776, 0.95933, 1.05453, 1.0078, 0.98275, 0.9314, 0.95617, 0.91701, 1.05993, 0.9446, 0.78367, 0.9553, 1, 0.86832, 1.0128, 0.95871, 0.99394, 0.87548, 0.96361, 0.86774, 1.0078, 0.95871, 0.9446, 0.95871, 0.86774, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.94083, 0.97579, 0.94083, 0.97579, 0.94083, 0.97579, 0.90406, 0.94039, 0.96694, 1, 0.89903, 1, 1, 1, 0.93582, 0.93582, 0.93582, 1, 0.908, 0.908, 0.918, 0.94219, 0.94219, 0.96544, 1, 1.285, 1, 1, 0.81079, 0.81079, 1, 1, 0.74854, 1, 1, 1, 1, 0.99793, 1, 1, 1, 0.65, 1, 1.36145, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.17173, 1, 0.80535, 0.76169, 1.02058, 1.0732, 1.05486, 1, 1, 1.30692, 1.08595, 1.08595, 1, 1.08595, 1.08595, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.16161, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const MyriadProBoldMetrics = {
  lineHeight: 1.2,
  lineGap: 0.2
};
const MyriadProBoldItalicFactors = [1.36898, 1, 1, 0.66227, 0.80779, 0.81625, 0.97276, 0.97276, 0.97733, 0.92222, 0.83266, 0.94292, 0.94292, 1.16148, 1.02058, 0.93582, 0.96694, 0.93582, 1.17337, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.97276, 0.78076, 0.78076, 1.02058, 1.02058, 1.02058, 0.71541, 0.76813, 0.85576, 0.80591, 0.80729, 0.94299, 0.77512, 0.83655, 0.86523, 0.92222, 0.98621, 0.71743, 0.81698, 0.79726, 0.98558, 0.92222, 0.90637, 0.83809, 0.90637, 0.80729, 0.76463, 0.86275, 0.90699, 0.91605, 0.9154, 0.85308, 0.85458, 0.90531, 0.94292, 1.21296, 0.94292, 1.02058, 0.89903, 1.18616, 0.99613, 0.91677, 0.78216, 0.91677, 0.90083, 0.98796, 0.9135, 0.92168, 0.95381, 0.98981, 0.95298, 0.95381, 0.93459, 0.92168, 0.91513, 0.92004, 0.91677, 0.95077, 0.748, 1.04502, 0.91677, 0.92061, 0.94236, 0.89544, 0.89364, 0.9, 0.80687, 0.8578, 0.80687, 1.02058, 0.80779, 0.97276, 0.97276, 0.97276, 0.97276, 0.8578, 0.99973, 1.18616, 0.91339, 1.08074, 0.82891, 1.02058, 0.55509, 0.71526, 0.89022, 1.08595, 1, 1, 1.18616, 1, 0.96736, 0.93582, 1.18616, 1, 1.04864, 0.82711, 0.99043, 0.99043, 0.99043, 0.71541, 0.85576, 0.85576, 0.85576, 0.85576, 0.85576, 0.85576, 0.845, 0.80729, 0.77512, 0.77512, 0.77512, 0.77512, 0.98621, 0.98621, 0.98621, 0.98621, 0.95961, 0.92222, 0.90637, 0.90637, 0.90637, 0.90637, 0.90637, 1.02058, 0.90251, 0.90699, 0.90699, 0.90699, 0.90699, 0.85458, 0.83659, 0.94951, 0.99613, 0.99613, 0.99613, 0.99613, 0.99613, 0.99613, 0.85811, 0.78216, 0.90083, 0.90083, 0.90083, 0.90083, 0.95381, 0.95381, 0.95381, 0.95381, 0.9135, 0.92168, 0.91513, 0.91513, 0.91513, 0.91513, 0.91513, 1.08595, 0.91677, 0.91677, 0.91677, 0.91677, 0.91677, 0.89364, 0.92332, 0.89364, 0.85576, 0.99613, 0.85576, 0.99613, 0.85576, 0.99613, 0.80729, 0.78216, 0.80729, 0.78216, 0.80729, 0.78216, 0.80729, 0.78216, 0.94299, 0.76783, 0.95961, 0.91677, 0.77512, 0.90083, 0.77512, 0.90083, 0.77512, 0.90083, 0.77512, 0.90083, 0.77512, 0.90083, 0.86523, 0.9135, 0.86523, 0.9135, 0.86523, 0.9135, 1, 1, 0.92222, 0.92168, 0.92222, 0.92168, 0.98621, 0.95381, 0.98621, 0.95381, 0.98621, 0.95381, 0.98621, 0.95381, 0.98621, 0.95381, 0.86036, 0.97096, 0.71743, 0.98981, 1, 1, 0.95298, 0.79726, 0.95381, 1, 1, 0.79726, 0.6894, 0.79726, 0.74321, 0.81691, 1.0006, 0.92222, 0.92168, 1, 1, 0.92222, 0.92168, 0.79464, 0.92098, 0.92168, 0.90637, 0.91513, 0.90637, 0.91513, 0.90637, 0.91513, 0.909, 0.87514, 0.80729, 0.95077, 1, 1, 0.80729, 0.95077, 0.76463, 0.748, 0.76463, 0.748, 1, 1, 0.76463, 0.748, 1, 1, 0.86275, 0.72651, 0.86275, 1.04502, 0.90699, 0.91677, 0.90699, 0.91677, 0.90699, 0.91677, 0.90699, 0.91677, 0.90699, 0.91677, 0.90699, 0.91677, 0.9154, 0.94236, 0.85458, 0.89364, 0.85458, 0.90531, 0.9, 0.90531, 0.9, 0.90531, 0.9, 1, 0.97276, 0.85576, 0.99613, 0.845, 0.85811, 0.90251, 0.91677, 1, 1, 0.86275, 1.04502, 1.18616, 1.18616, 1.18616, 1.18616, 1.18616, 1.18616, 1.18616, 1.18616, 1.18616, 1.00899, 1.30628, 0.85576, 0.80178, 0.66862, 0.7927, 0.69323, 0.88127, 0.72459, 0.89711, 0.95381, 0.85576, 0.80591, 0.7805, 0.94729, 0.77512, 0.90531, 0.92222, 0.90637, 0.98621, 0.81698, 0.92655, 0.98558, 0.92222, 0.85359, 0.90637, 0.90976, 0.83809, 0.94523, 0.86275, 0.83509, 0.93157, 0.85308, 0.83392, 0.92346, 0.98621, 0.83509, 0.92886, 0.91324, 0.92168, 0.95381, 0.90646, 0.92886, 0.90557, 0.86847, 0.90276, 0.91324, 0.86842, 0.92168, 0.99531, 0.95381, 0.9224, 0.85408, 0.92699, 0.86847, 1.0051, 0.91513, 0.80487, 0.93481, 1, 0.88159, 1.05214, 0.90646, 0.97355, 0.81539, 0.89398, 0.85923, 0.95381, 0.90646, 0.91513, 0.90646, 0.85923, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.9154, 0.94236, 0.9154, 0.94236, 0.9154, 0.94236, 0.85458, 0.89364, 0.96694, 1, 0.89903, 1, 1, 1, 0.91782, 0.91782, 0.91782, 1, 0.896, 0.896, 0.896, 0.9332, 0.9332, 0.95973, 1, 1.26, 1, 1, 0.80479, 0.80178, 1, 1, 0.85633, 1, 1, 1, 1, 0.97276, 1, 1, 1, 0.698, 1, 1.36145, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.14542, 1, 0.79199, 0.78694, 1.02058, 1.03493, 1.05486, 1, 1, 1.23026, 1.08595, 1.08595, 1, 1.08595, 1.08595, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.20006, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const MyriadProBoldItalicMetrics = {
  lineHeight: 1.2,
  lineGap: 0.2
};
const MyriadProItalicFactors = [1.36898, 1, 1, 0.65507, 0.84943, 0.85639, 0.88465, 0.88465, 0.86936, 0.88307, 0.86948, 0.85283, 0.85283, 1.06383, 1.02058, 0.75945, 0.9219, 0.75945, 1.17337, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.88465, 0.75945, 0.75945, 1.02058, 1.02058, 1.02058, 0.69046, 0.70926, 0.85158, 0.77812, 0.76852, 0.89591, 0.70466, 0.76125, 0.80094, 0.86822, 0.83864, 0.728, 0.77212, 0.79475, 0.93637, 0.87514, 0.8588, 0.76013, 0.8588, 0.72421, 0.69866, 0.77598, 0.85991, 0.80811, 0.87832, 0.78112, 0.77512, 0.8562, 1.0222, 1.18417, 1.0222, 1.27014, 0.89903, 1.15012, 0.93859, 0.94399, 0.846, 0.94399, 0.81453, 1.0186, 0.94219, 0.96017, 1.03075, 1.02175, 0.912, 1.03075, 0.96998, 0.96017, 0.93859, 0.94399, 0.94399, 0.95493, 0.746, 1.12658, 0.94578, 0.91, 0.979, 0.882, 0.882, 0.83, 0.85034, 0.83537, 0.85034, 1.02058, 0.70869, 0.88465, 0.88465, 0.88465, 0.88465, 0.83537, 0.90083, 1.15012, 0.9161, 0.94565, 0.73541, 1.02058, 0.53609, 0.69353, 0.79519, 1.08595, 1, 1, 1.15012, 1, 0.91974, 0.75945, 1.15012, 1, 0.9446, 0.73361, 0.9005, 0.9005, 0.9005, 0.62864, 0.85158, 0.85158, 0.85158, 0.85158, 0.85158, 0.85158, 0.773, 0.76852, 0.70466, 0.70466, 0.70466, 0.70466, 0.83864, 0.83864, 0.83864, 0.83864, 0.90561, 0.87514, 0.8588, 0.8588, 0.8588, 0.8588, 0.8588, 1.02058, 0.85751, 0.85991, 0.85991, 0.85991, 0.85991, 0.77512, 0.76013, 0.88075, 0.93859, 0.93859, 0.93859, 0.93859, 0.93859, 0.93859, 0.8075, 0.846, 0.81453, 0.81453, 0.81453, 0.81453, 0.82424, 0.82424, 0.82424, 0.82424, 0.9278, 0.96017, 0.93859, 0.93859, 0.93859, 0.93859, 0.93859, 1.08595, 0.8562, 0.94578, 0.94578, 0.94578, 0.94578, 0.882, 0.94578, 0.882, 0.85158, 0.93859, 0.85158, 0.93859, 0.85158, 0.93859, 0.76852, 0.846, 0.76852, 0.846, 0.76852, 0.846, 0.76852, 0.846, 0.89591, 0.8544, 0.90561, 0.94399, 0.70466, 0.81453, 0.70466, 0.81453, 0.70466, 0.81453, 0.70466, 0.81453, 0.70466, 0.81453, 0.80094, 0.94219, 0.80094, 0.94219, 0.80094, 0.94219, 1, 1, 0.86822, 0.96017, 0.86822, 0.96017, 0.83864, 0.82424, 0.83864, 0.82424, 0.83864, 0.82424, 0.83864, 1.03075, 0.83864, 0.82424, 0.81402, 1.02738, 0.728, 1.02175, 1, 1, 0.912, 0.79475, 1.03075, 1, 1, 0.79475, 0.83911, 0.79475, 0.66266, 0.80553, 1.06676, 0.87514, 0.96017, 1, 1, 0.87514, 0.96017, 0.86865, 0.87396, 0.96017, 0.8588, 0.93859, 0.8588, 0.93859, 0.8588, 0.93859, 0.867, 0.84759, 0.72421, 0.95493, 1, 1, 0.72421, 0.95493, 0.69866, 0.746, 0.69866, 0.746, 1, 1, 0.69866, 0.746, 1, 1, 0.77598, 0.88417, 0.77598, 1.12658, 0.85991, 0.94578, 0.85991, 0.94578, 0.85991, 0.94578, 0.85991, 0.94578, 0.85991, 0.94578, 0.85991, 0.94578, 0.87832, 0.979, 0.77512, 0.882, 0.77512, 0.8562, 0.83, 0.8562, 0.83, 0.8562, 0.83, 1, 0.88465, 0.85158, 0.93859, 0.773, 0.8075, 0.85751, 0.8562, 1, 1, 0.77598, 1.12658, 1.15012, 1.15012, 1.15012, 1.15012, 1.15012, 1.15313, 1.15012, 1.15012, 1.15012, 1.08106, 1.03901, 0.85158, 0.77025, 0.62264, 0.7646, 0.65351, 0.86026, 0.69461, 0.89947, 1.03075, 0.85158, 0.77812, 0.76449, 0.88836, 0.70466, 0.8562, 0.86822, 0.8588, 0.83864, 0.77212, 0.85308, 0.93637, 0.87514, 0.82352, 0.8588, 0.85701, 0.76013, 0.89058, 0.77598, 0.8156, 0.82565, 0.78112, 0.77899, 0.89386, 0.83864, 0.8156, 0.9486, 0.92388, 0.96186, 1.03075, 0.91123, 0.9486, 0.93298, 0.878, 0.93942, 0.92388, 0.84596, 0.96186, 0.95119, 1.03075, 0.922, 0.88787, 0.95829, 0.88, 0.93559, 0.93859, 0.78815, 0.93758, 1, 0.89217, 1.03737, 0.91123, 0.93969, 0.77487, 0.85769, 0.86799, 1.03075, 0.91123, 0.93859, 0.91123, 0.86799, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.87832, 0.979, 0.87832, 0.979, 0.87832, 0.979, 0.77512, 0.882, 0.9219, 1, 0.89903, 1, 1, 1, 0.87321, 0.87321, 0.87321, 1, 1.027, 1.027, 1.027, 0.86847, 0.86847, 0.79121, 1, 1.124, 1, 1, 0.73572, 0.73572, 1, 1, 0.85034, 1, 1, 1, 1, 0.88465, 1, 1, 1, 0.669, 1, 1.36145, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.04828, 1, 0.74948, 0.75187, 1.02058, 0.98391, 1.02119, 1, 1, 1.06233, 1.08595, 1.08595, 1, 1.08595, 1.08595, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.05233, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const MyriadProItalicMetrics = {
  lineHeight: 1.2,
  lineGap: 0.2
};
const MyriadProRegularFactors = [1.36898, 1, 1, 0.76305, 0.82784, 0.94935, 0.89364, 0.92241, 0.89073, 0.90706, 0.98472, 0.85283, 0.85283, 1.0664, 1.02058, 0.74505, 0.9219, 0.74505, 1.23456, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.92241, 0.74505, 0.74505, 1.02058, 1.02058, 1.02058, 0.73002, 0.72601, 0.91755, 0.8126, 0.80314, 0.92222, 0.73764, 0.79726, 0.83051, 0.90284, 0.86023, 0.74, 0.8126, 0.84869, 0.96518, 0.91115, 0.8858, 0.79761, 0.8858, 0.74498, 0.73914, 0.81363, 0.89591, 0.83659, 0.89633, 0.85608, 0.8111, 0.90531, 1.0222, 1.22736, 1.0222, 1.27014, 0.89903, 0.90088, 0.86667, 1.0231, 0.896, 1.01411, 0.90083, 1.05099, 1.00512, 0.99793, 1.05326, 1.09377, 0.938, 1.06226, 1.00119, 0.99793, 0.98714, 1.0231, 1.01231, 0.98196, 0.792, 1.19137, 0.99074, 0.962, 1.01915, 0.926, 0.942, 0.856, 0.85034, 0.92006, 0.85034, 1.02058, 0.69067, 0.92241, 0.92241, 0.92241, 0.92241, 0.92006, 0.9332, 0.90088, 0.91882, 0.93484, 0.75339, 1.02058, 0.56866, 0.54324, 0.79519, 1.08595, 1, 1, 0.90088, 1, 0.95325, 0.74505, 0.90088, 1, 0.97198, 0.75339, 0.91009, 0.91009, 0.91009, 0.66466, 0.91755, 0.91755, 0.91755, 0.91755, 0.91755, 0.91755, 0.788, 0.80314, 0.73764, 0.73764, 0.73764, 0.73764, 0.86023, 0.86023, 0.86023, 0.86023, 0.92915, 0.91115, 0.8858, 0.8858, 0.8858, 0.8858, 0.8858, 1.02058, 0.8858, 0.89591, 0.89591, 0.89591, 0.89591, 0.8111, 0.79611, 0.89713, 0.86667, 0.86667, 0.86667, 0.86667, 0.86667, 0.86667, 0.86936, 0.896, 0.90083, 0.90083, 0.90083, 0.90083, 0.84224, 0.84224, 0.84224, 0.84224, 0.97276, 0.99793, 0.98714, 0.98714, 0.98714, 0.98714, 0.98714, 1.08595, 0.89876, 0.99074, 0.99074, 0.99074, 0.99074, 0.942, 1.0231, 0.942, 0.91755, 0.86667, 0.91755, 0.86667, 0.91755, 0.86667, 0.80314, 0.896, 0.80314, 0.896, 0.80314, 0.896, 0.80314, 0.896, 0.92222, 0.93372, 0.92915, 1.01411, 0.73764, 0.90083, 0.73764, 0.90083, 0.73764, 0.90083, 0.73764, 0.90083, 0.73764, 0.90083, 0.83051, 1.00512, 0.83051, 1.00512, 0.83051, 1.00512, 1, 1, 0.90284, 0.99793, 0.90976, 0.99793, 0.86023, 0.84224, 0.86023, 0.84224, 0.86023, 0.84224, 0.86023, 1.05326, 0.86023, 0.84224, 0.82873, 1.07469, 0.74, 1.09377, 1, 1, 0.938, 0.84869, 1.06226, 1, 1, 0.84869, 0.83704, 0.84869, 0.81441, 0.85588, 1.08927, 0.91115, 0.99793, 1, 1, 0.91115, 0.99793, 0.91887, 0.90991, 0.99793, 0.8858, 0.98714, 0.8858, 0.98714, 0.8858, 0.98714, 0.894, 0.91434, 0.74498, 0.98196, 1, 1, 0.74498, 0.98196, 0.73914, 0.792, 0.73914, 0.792, 1, 1, 0.73914, 0.792, 1, 1, 0.81363, 0.904, 0.81363, 1.19137, 0.89591, 0.99074, 0.89591, 0.99074, 0.89591, 0.99074, 0.89591, 0.99074, 0.89591, 0.99074, 0.89591, 0.99074, 0.89633, 1.01915, 0.8111, 0.942, 0.8111, 0.90531, 0.856, 0.90531, 0.856, 0.90531, 0.856, 1, 0.92241, 0.91755, 0.86667, 0.788, 0.86936, 0.8858, 0.89876, 1, 1, 0.81363, 1.19137, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90088, 0.90388, 1.03901, 0.92138, 0.78105, 0.7154, 0.86169, 0.80513, 0.94007, 0.82528, 0.98612, 1.06226, 0.91755, 0.8126, 0.81884, 0.92819, 0.73764, 0.90531, 0.90284, 0.8858, 0.86023, 0.8126, 0.91172, 0.96518, 0.91115, 0.83089, 0.8858, 0.87791, 0.79761, 0.89297, 0.81363, 0.88157, 0.89992, 0.85608, 0.81992, 0.94307, 0.86023, 0.88157, 0.95308, 0.98699, 0.99793, 1.06226, 0.95817, 0.95308, 0.97358, 0.928, 0.98088, 0.98699, 0.92761, 0.99793, 0.96017, 1.06226, 0.986, 0.944, 0.95978, 0.938, 0.96705, 0.98714, 0.80442, 0.98972, 1, 0.89762, 1.04552, 0.95817, 0.99007, 0.87064, 0.91879, 0.88888, 1.06226, 0.95817, 0.98714, 0.95817, 0.88888, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.89633, 1.01915, 0.89633, 1.01915, 0.89633, 1.01915, 0.8111, 0.942, 0.9219, 1, 0.89903, 1, 1, 1, 0.93173, 0.93173, 0.93173, 1, 1.06304, 1.06304, 1.06904, 0.89903, 0.89903, 0.80549, 1, 1.156, 1, 1, 0.76575, 0.76575, 1, 1, 0.72458, 1, 1, 1, 1, 0.92241, 1, 1, 1, 0.619, 1, 1.36145, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.07257, 1, 0.74705, 0.71119, 1.02058, 1.024, 1.02119, 1, 1, 1.1536, 1.08595, 1.08595, 1, 1.08595, 1.08595, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.05638, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const MyriadProRegularMetrics = {
  lineHeight: 1.2,
  lineGap: 0.2
};

;// CONCATENATED MODULE: ./src/core/segoeui_factors.js
const SegoeuiBoldFactors = [1.76738, 1, 1, 0.99297, 0.9824, 1.04016, 1.06497, 1.03424, 0.97529, 1.17647, 1.23203, 1.1085, 1.1085, 1.16939, 1.2107, 0.9754, 1.21408, 0.9754, 1.59578, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 1.03424, 0.81378, 0.81378, 1.2107, 1.2107, 1.2107, 0.71703, 0.97847, 0.97363, 0.88776, 0.8641, 1.02096, 0.79795, 0.85132, 0.914, 1.06085, 1.1406, 0.8007, 0.89858, 0.83693, 1.14889, 1.09398, 0.97489, 0.92094, 0.97489, 0.90399, 0.84041, 0.95923, 1.00135, 1, 1.06467, 0.98243, 0.90996, 0.99361, 1.1085, 1.56942, 1.1085, 1.2107, 0.74627, 0.94282, 0.96752, 1.01519, 0.86304, 1.01359, 0.97278, 1.15103, 1.01359, 0.98561, 1.02285, 1.02285, 1.00527, 1.02285, 1.0302, 0.99041, 1.0008, 1.01519, 1.01359, 1.02258, 0.79104, 1.16862, 0.99041, 0.97454, 1.02511, 0.99298, 0.96752, 0.95801, 0.94856, 1.16579, 0.94856, 1.2107, 0.9824, 1.03424, 1.03424, 1, 1.03424, 1.16579, 0.8727, 1.3871, 1.18622, 1.10818, 1.04478, 1.2107, 1.18622, 0.75155, 0.94994, 1.28826, 1.21408, 1.21408, 0.91056, 1, 0.91572, 0.9754, 0.64663, 1.18328, 1.24866, 1.04478, 1.14169, 1.15749, 1.17389, 0.71703, 0.97363, 0.97363, 0.97363, 0.97363, 0.97363, 0.97363, 0.93506, 0.8641, 0.79795, 0.79795, 0.79795, 0.79795, 1.1406, 1.1406, 1.1406, 1.1406, 1.02096, 1.09398, 0.97426, 0.97426, 0.97426, 0.97426, 0.97426, 1.2107, 0.97489, 1.00135, 1.00135, 1.00135, 1.00135, 0.90996, 0.92094, 1.02798, 0.96752, 0.96752, 0.96752, 0.96752, 0.96752, 0.96752, 0.93136, 0.86304, 0.97278, 0.97278, 0.97278, 0.97278, 1.02285, 1.02285, 1.02285, 1.02285, 0.97122, 0.99041, 1, 1, 1, 1, 1, 1.28826, 1.0008, 0.99041, 0.99041, 0.99041, 0.99041, 0.96752, 1.01519, 0.96752, 0.97363, 0.96752, 0.97363, 0.96752, 0.97363, 0.96752, 0.8641, 0.86304, 0.8641, 0.86304, 0.8641, 0.86304, 0.8641, 0.86304, 1.02096, 1.03057, 1.02096, 1.03517, 0.79795, 0.97278, 0.79795, 0.97278, 0.79795, 0.97278, 0.79795, 0.97278, 0.79795, 0.97278, 0.914, 1.01359, 0.914, 1.01359, 0.914, 1.01359, 1, 1, 1.06085, 0.98561, 1.06085, 1.00879, 1.1406, 1.02285, 1.1406, 1.02285, 1.1406, 1.02285, 1.1406, 1.02285, 1.1406, 1.02285, 0.97138, 1.08692, 0.8007, 1.02285, 1, 1, 1.00527, 0.83693, 1.02285, 1, 1, 0.83693, 0.9455, 0.83693, 0.90418, 0.83693, 1.13005, 1.09398, 0.99041, 1, 1, 1.09398, 0.99041, 0.96692, 1.09251, 0.99041, 0.97489, 1.0008, 0.97489, 1.0008, 0.97489, 1.0008, 0.93994, 0.97931, 0.90399, 1.02258, 1, 1, 0.90399, 1.02258, 0.84041, 0.79104, 0.84041, 0.79104, 0.84041, 0.79104, 0.84041, 0.79104, 1, 1, 0.95923, 1.07034, 0.95923, 1.16862, 1.00135, 0.99041, 1.00135, 0.99041, 1.00135, 0.99041, 1.00135, 0.99041, 1.00135, 0.99041, 1.00135, 0.99041, 1.06467, 1.02511, 0.90996, 0.96752, 0.90996, 0.99361, 0.95801, 0.99361, 0.95801, 0.99361, 0.95801, 1.07733, 1.03424, 0.97363, 0.96752, 0.93506, 0.93136, 0.97489, 1.0008, 1, 1, 0.95923, 1.16862, 1.15103, 1.15103, 1.01173, 1.03959, 0.75953, 0.81378, 0.79912, 1.15103, 1.21994, 0.95161, 0.87815, 1.01149, 0.81525, 0.7676, 0.98167, 1.01134, 1.02546, 0.84097, 1.03089, 1.18102, 0.97363, 0.88776, 0.85134, 0.97826, 0.79795, 0.99361, 1.06085, 0.97489, 1.1406, 0.89858, 1.0388, 1.14889, 1.09398, 0.86039, 0.97489, 1.0595, 0.92094, 0.94793, 0.95923, 0.90996, 0.99346, 0.98243, 1.02112, 0.95493, 1.1406, 0.90996, 1.03574, 1.02597, 1.0008, 1.18102, 1.06628, 1.03574, 1.0192, 1.01932, 1.00886, 0.97531, 1.0106, 1.0008, 1.13189, 1.18102, 1.02277, 0.98683, 1.0016, 0.99561, 1.07237, 1.0008, 0.90434, 0.99921, 0.93803, 0.8965, 1.23085, 1.06628, 1.04983, 0.96268, 1.0499, 0.98439, 1.18102, 1.06628, 1.0008, 1.06628, 0.98439, 0.79795, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.09466, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.97278, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.02065, 1, 1, 1, 1, 1, 1, 1.06467, 1.02511, 1.06467, 1.02511, 1.06467, 1.02511, 0.90996, 0.96752, 1, 1.21408, 0.89903, 1, 1, 0.75155, 1.04394, 1.04394, 1.04394, 1.04394, 0.98633, 0.98633, 0.98633, 0.73047, 0.73047, 1.20642, 0.91211, 1.25635, 1.222, 1.02956, 1.03372, 1.03372, 0.96039, 1.24633, 1, 1.12454, 0.93503, 1.03424, 1.19687, 1.03424, 1, 1, 1, 0.771, 1, 1, 1.15749, 1.15749, 1.15749, 1.10948, 0.86279, 0.94434, 0.86279, 0.94434, 0.86182, 1, 1, 1.16897, 1, 0.96085, 0.90137, 1.2107, 1.18416, 1.13973, 0.69825, 0.9716, 2.10339, 1.29004, 1.29004, 1.21172, 1.29004, 1.29004, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.42603, 1, 0.99862, 0.99862, 1, 0.87025, 0.87025, 0.87025, 0.87025, 1.18874, 1.42603, 1, 1.42603, 1.42603, 0.99862, 1, 1, 1, 1, 1, 1.2886, 1.04315, 1.15296, 1.34163, 1, 1, 1, 1.09193, 1.09193, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const SegoeuiBoldMetrics = {
  lineHeight: 1.33008,
  lineGap: 0
};
const SegoeuiBoldItalicFactors = [1.76738, 1, 1, 0.98946, 1.03959, 1.04016, 1.02809, 1.036, 0.97639, 1.10953, 1.23203, 1.11144, 1.11144, 1.16939, 1.21237, 0.9754, 1.21261, 0.9754, 1.59754, 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 1.036, 0.81378, 0.81378, 1.21237, 1.21237, 1.21237, 0.73541, 0.97847, 0.97363, 0.89723, 0.87897, 1.0426, 0.79429, 0.85292, 0.91149, 1.05815, 1.1406, 0.79631, 0.90128, 0.83853, 1.04396, 1.10615, 0.97552, 0.94436, 0.97552, 0.88641, 0.80527, 0.96083, 1.00135, 1, 1.06777, 0.9817, 0.91142, 0.99361, 1.11144, 1.57293, 1.11144, 1.21237, 0.74627, 1.31818, 1.06585, 0.97042, 0.83055, 0.97042, 0.93503, 1.1261, 0.97042, 0.97922, 1.14236, 0.94552, 1.01054, 1.14236, 1.02471, 0.97922, 0.94165, 0.97042, 0.97042, 1.0276, 0.78929, 1.1261, 0.97922, 0.95874, 1.02197, 0.98507, 0.96752, 0.97168, 0.95107, 1.16579, 0.95107, 1.21237, 1.03959, 1.036, 1.036, 1, 1.036, 1.16579, 0.87357, 1.31818, 1.18754, 1.26781, 1.05356, 1.21237, 1.18622, 0.79487, 0.94994, 1.29004, 1.24047, 1.24047, 1.31818, 1, 0.91484, 0.9754, 1.31818, 1.1349, 1.24866, 1.05356, 1.13934, 1.15574, 1.17389, 0.73541, 0.97363, 0.97363, 0.97363, 0.97363, 0.97363, 0.97363, 0.94385, 0.87897, 0.79429, 0.79429, 0.79429, 0.79429, 1.1406, 1.1406, 1.1406, 1.1406, 1.0426, 1.10615, 0.97552, 0.97552, 0.97552, 0.97552, 0.97552, 1.21237, 0.97552, 1.00135, 1.00135, 1.00135, 1.00135, 0.91142, 0.94436, 0.98721, 1.06585, 1.06585, 1.06585, 1.06585, 1.06585, 1.06585, 0.96705, 0.83055, 0.93503, 0.93503, 0.93503, 0.93503, 1.14236, 1.14236, 1.14236, 1.14236, 0.93125, 0.97922, 0.94165, 0.94165, 0.94165, 0.94165, 0.94165, 1.29004, 0.94165, 0.97922, 0.97922, 0.97922, 0.97922, 0.96752, 0.97042, 0.96752, 0.97363, 1.06585, 0.97363, 1.06585, 0.97363, 1.06585, 0.87897, 0.83055, 0.87897, 0.83055, 0.87897, 0.83055, 0.87897, 0.83055, 1.0426, 1.0033, 1.0426, 0.97042, 0.79429, 0.93503, 0.79429, 0.93503, 0.79429, 0.93503, 0.79429, 0.93503, 0.79429, 0.93503, 0.91149, 0.97042, 0.91149, 0.97042, 0.91149, 0.97042, 1, 1, 1.05815, 0.97922, 1.05815, 0.97922, 1.1406, 1.14236, 1.1406, 1.14236, 1.1406, 1.14236, 1.1406, 1.14236, 1.1406, 1.14236, 0.97441, 1.04302, 0.79631, 1.01582, 1, 1, 1.01054, 0.83853, 1.14236, 1, 1, 0.83853, 1.09125, 0.83853, 0.90418, 0.83853, 1.19508, 1.10615, 0.97922, 1, 1, 1.10615, 0.97922, 1.01034, 1.10466, 0.97922, 0.97552, 0.94165, 0.97552, 0.94165, 0.97552, 0.94165, 0.91602, 0.91981, 0.88641, 1.0276, 1, 1, 0.88641, 1.0276, 0.80527, 0.78929, 0.80527, 0.78929, 0.80527, 0.78929, 0.80527, 0.78929, 1, 1, 0.96083, 1.05403, 0.95923, 1.16862, 1.00135, 0.97922, 1.00135, 0.97922, 1.00135, 0.97922, 1.00135, 0.97922, 1.00135, 0.97922, 1.00135, 0.97922, 1.06777, 1.02197, 0.91142, 0.96752, 0.91142, 0.99361, 0.97168, 0.99361, 0.97168, 0.99361, 0.97168, 1.23199, 1.036, 0.97363, 1.06585, 0.94385, 0.96705, 0.97552, 0.94165, 1, 1, 0.96083, 1.1261, 1.31818, 1.31818, 1.31818, 1.31818, 1.31818, 1.31818, 1.31818, 1.31818, 1.31818, 0.95161, 1.27126, 1.00811, 0.83284, 0.77702, 0.99137, 0.95253, 1.0347, 0.86142, 1.07205, 1.14236, 0.97363, 0.89723, 0.86869, 1.09818, 0.79429, 0.99361, 1.05815, 0.97552, 1.1406, 0.90128, 1.06662, 1.04396, 1.10615, 0.84918, 0.97552, 1.04694, 0.94436, 0.98015, 0.96083, 0.91142, 1.00356, 0.9817, 1.01945, 0.98999, 1.1406, 0.91142, 1.04961, 0.9898, 1.00639, 1.14236, 1.07514, 1.04961, 0.99607, 1.02897, 1.008, 0.9898, 0.95134, 1.00639, 1.11121, 1.14236, 1.00518, 0.97981, 1.02186, 1, 1.08578, 0.94165, 0.99314, 0.98387, 0.93028, 0.93377, 1.35125, 1.07514, 1.10687, 0.93491, 1.04232, 1.00351, 1.14236, 1.07514, 0.94165, 1.07514, 1.00351, 0.79429, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.09097, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.93503, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.96609, 1, 1, 1, 1, 1, 1, 1.06777, 1.02197, 1.06777, 1.02197, 1.06777, 1.02197, 0.91142, 0.96752, 1, 1.21261, 0.89903, 1, 1, 0.75155, 1.04745, 1.04745, 1.04745, 1.04394, 0.98633, 0.98633, 0.98633, 0.72959, 0.72959, 1.20502, 0.91406, 1.26514, 1.222, 1.02956, 1.03372, 1.03372, 0.96039, 1.24633, 1, 1.09125, 0.93327, 1.03336, 1.16541, 1.036, 1, 1, 1, 0.771, 1, 1, 1.15574, 1.15574, 1.15574, 1.15574, 0.86364, 0.94434, 0.86279, 0.94434, 0.86224, 1, 1, 1.16798, 1, 0.96085, 0.90068, 1.21237, 1.18416, 1.13904, 0.69825, 0.9716, 2.10339, 1.29004, 1.29004, 1.21339, 1.29004, 1.29004, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.42603, 1, 0.99862, 0.99862, 1, 0.87025, 0.87025, 0.87025, 0.87025, 1.18775, 1.42603, 1, 1.42603, 1.42603, 0.99862, 1, 1, 1, 1, 1, 1.2886, 1.04315, 1.15296, 1.34163, 1, 1, 1, 1.13269, 1.13269, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const SegoeuiBoldItalicMetrics = {
  lineHeight: 1.33008,
  lineGap: 0
};
const SegoeuiItalicFactors = [1.76738, 1, 1, 0.98946, 1.14763, 1.05365, 1.06234, 0.96927, 0.92586, 1.15373, 1.18414, 0.91349, 0.91349, 1.07403, 1.17308, 0.78383, 1.20088, 0.78383, 1.42531, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.78383, 0.78383, 1.17308, 1.17308, 1.17308, 0.77349, 0.94565, 0.94729, 0.85944, 0.88506, 0.9858, 0.74817, 0.80016, 0.88449, 0.98039, 0.95782, 0.69238, 0.89898, 0.83231, 0.98183, 1.03989, 0.96924, 0.86237, 0.96924, 0.80595, 0.74524, 0.86091, 0.95402, 0.94143, 0.98448, 0.8858, 0.83089, 0.93285, 1.0949, 1.39016, 1.0949, 1.45994, 0.74627, 1.04839, 0.97454, 0.97454, 0.87207, 0.97454, 0.87533, 1.06151, 0.97454, 1.00176, 1.16484, 1.08132, 0.98047, 1.16484, 1.02989, 1.01054, 0.96225, 0.97454, 0.97454, 1.06598, 0.79004, 1.16344, 1.00351, 0.94629, 0.9973, 0.91016, 0.96777, 0.9043, 0.91082, 0.92481, 0.91082, 1.17308, 0.95748, 0.96927, 0.96927, 1, 0.96927, 0.92481, 0.80597, 1.04839, 1.23393, 1.1781, 0.9245, 1.17308, 1.20808, 0.63218, 0.94261, 1.24822, 1.09971, 1.09971, 1.04839, 1, 0.85273, 0.78032, 1.04839, 1.09971, 1.22326, 0.9245, 1.09836, 1.13525, 1.15222, 0.70424, 0.94729, 0.94729, 0.94729, 0.94729, 0.94729, 0.94729, 0.85498, 0.88506, 0.74817, 0.74817, 0.74817, 0.74817, 0.95782, 0.95782, 0.95782, 0.95782, 0.9858, 1.03989, 0.96924, 0.96924, 0.96924, 0.96924, 0.96924, 1.17308, 0.96924, 0.95402, 0.95402, 0.95402, 0.95402, 0.83089, 0.86237, 0.88409, 0.97454, 0.97454, 0.97454, 0.97454, 0.97454, 0.97454, 0.92916, 0.87207, 0.87533, 0.87533, 0.87533, 0.87533, 0.93146, 0.93146, 0.93146, 0.93146, 0.93854, 1.01054, 0.96225, 0.96225, 0.96225, 0.96225, 0.96225, 1.24822, 0.8761, 1.00351, 1.00351, 1.00351, 1.00351, 0.96777, 0.97454, 0.96777, 0.94729, 0.97454, 0.94729, 0.97454, 0.94729, 0.97454, 0.88506, 0.87207, 0.88506, 0.87207, 0.88506, 0.87207, 0.88506, 0.87207, 0.9858, 0.95391, 0.9858, 0.97454, 0.74817, 0.87533, 0.74817, 0.87533, 0.74817, 0.87533, 0.74817, 0.87533, 0.74817, 0.87533, 0.88449, 0.97454, 0.88449, 0.97454, 0.88449, 0.97454, 1, 1, 0.98039, 1.00176, 0.98039, 1.00176, 0.95782, 0.93146, 0.95782, 0.93146, 0.95782, 0.93146, 0.95782, 1.16484, 0.95782, 0.93146, 0.84421, 1.12761, 0.69238, 1.08132, 1, 1, 0.98047, 0.83231, 1.16484, 1, 1, 0.84723, 1.04861, 0.84723, 0.78755, 0.83231, 1.23736, 1.03989, 1.01054, 1, 1, 1.03989, 1.01054, 0.9857, 1.03849, 1.01054, 0.96924, 0.96225, 0.96924, 0.96225, 0.96924, 0.96225, 0.92383, 0.90171, 0.80595, 1.06598, 1, 1, 0.80595, 1.06598, 0.74524, 0.79004, 0.74524, 0.79004, 0.74524, 0.79004, 0.74524, 0.79004, 1, 1, 0.86091, 1.02759, 0.85771, 1.16344, 0.95402, 1.00351, 0.95402, 1.00351, 0.95402, 1.00351, 0.95402, 1.00351, 0.95402, 1.00351, 0.95402, 1.00351, 0.98448, 0.9973, 0.83089, 0.96777, 0.83089, 0.93285, 0.9043, 0.93285, 0.9043, 0.93285, 0.9043, 1.31868, 0.96927, 0.94729, 0.97454, 0.85498, 0.92916, 0.96924, 0.8761, 1, 1, 0.86091, 1.16344, 1.04839, 1.04839, 1.04839, 1.04839, 1.04839, 1.04839, 1.04839, 1.04839, 1.04839, 0.81965, 0.81965, 0.94729, 0.78032, 0.71022, 0.90883, 0.84171, 0.99877, 0.77596, 1.05734, 1.2, 0.94729, 0.85944, 0.82791, 0.9607, 0.74817, 0.93285, 0.98039, 0.96924, 0.95782, 0.89898, 0.98316, 0.98183, 1.03989, 0.78614, 0.96924, 0.97642, 0.86237, 0.86075, 0.86091, 0.83089, 0.90082, 0.8858, 0.97296, 1.01284, 0.95782, 0.83089, 1.0976, 1.04, 1.03342, 1.2, 1.0675, 1.0976, 0.98205, 1.03809, 1.05097, 1.04, 0.95364, 1.03342, 1.05401, 1.2, 1.02148, 1.0119, 1.04724, 1.0127, 1.02732, 0.96225, 0.8965, 0.97783, 0.93574, 0.94818, 1.30679, 1.0675, 1.11826, 0.99821, 1.0557, 1.0326, 1.2, 1.0675, 0.96225, 1.0675, 1.0326, 0.74817, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.03754, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.87533, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.98705, 1, 1, 1, 1, 1, 1, 0.98448, 0.9973, 0.98448, 0.9973, 0.98448, 0.9973, 0.83089, 0.96777, 1, 1.20088, 0.89903, 1, 1, 0.75155, 0.94945, 0.94945, 0.94945, 0.94945, 1.12317, 1.12317, 1.12317, 0.67603, 0.67603, 1.15621, 0.73584, 1.21191, 1.22135, 1.06483, 0.94868, 0.94868, 0.95996, 1.24633, 1, 1.07497, 0.87709, 0.96927, 1.01473, 0.96927, 1, 1, 1, 0.77295, 1, 1, 1.09836, 1.09836, 1.09836, 1.01522, 0.86321, 0.94434, 0.8649, 0.94434, 0.86182, 1, 1, 1.083, 1, 0.91578, 0.86438, 1.17308, 1.18416, 1.14589, 0.69825, 0.97622, 1.96791, 1.24822, 1.24822, 1.17308, 1.24822, 1.24822, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.42603, 1, 0.99862, 0.99862, 1, 0.87025, 0.87025, 0.87025, 0.87025, 1.17984, 1.42603, 1, 1.42603, 1.42603, 0.99862, 1, 1, 1, 1, 1, 1.2886, 1.04315, 1.15296, 1.34163, 1, 1, 1, 1.10742, 1.10742, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const SegoeuiItalicMetrics = {
  lineHeight: 1.33008,
  lineGap: 0
};
const SegoeuiRegularFactors = [1.76738, 1, 1, 0.98594, 1.02285, 1.10454, 1.06234, 0.96927, 0.92037, 1.19985, 1.2046, 0.90616, 0.90616, 1.07152, 1.1714, 0.78032, 1.20088, 0.78032, 1.40246, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.96927, 0.78032, 0.78032, 1.1714, 1.1714, 1.1714, 0.80597, 0.94084, 0.96706, 0.85944, 0.85734, 0.97093, 0.75842, 0.79936, 0.88198, 0.9831, 0.95782, 0.71387, 0.86969, 0.84636, 1.07796, 1.03584, 0.96924, 0.83968, 0.96924, 0.82826, 0.79649, 0.85771, 0.95132, 0.93119, 0.98965, 0.88433, 0.8287, 0.93365, 1.08612, 1.3638, 1.08612, 1.45786, 0.74627, 0.80499, 0.91484, 1.05707, 0.92383, 1.05882, 0.9403, 1.12654, 1.05882, 1.01756, 1.09011, 1.09011, 0.99414, 1.09011, 1.034, 1.01756, 1.05356, 1.05707, 1.05882, 1.04399, 0.84863, 1.21968, 1.01756, 0.95801, 1.00068, 0.91797, 0.96777, 0.9043, 0.90351, 0.92105, 0.90351, 1.1714, 0.85337, 0.96927, 0.96927, 0.99912, 0.96927, 0.92105, 0.80597, 1.2434, 1.20808, 1.05937, 0.90957, 1.1714, 1.20808, 0.75155, 0.94261, 1.24644, 1.09971, 1.09971, 0.84751, 1, 0.85273, 0.78032, 0.61584, 1.05425, 1.17914, 0.90957, 1.08665, 1.11593, 1.14169, 0.73381, 0.96706, 0.96706, 0.96706, 0.96706, 0.96706, 0.96706, 0.86035, 0.85734, 0.75842, 0.75842, 0.75842, 0.75842, 0.95782, 0.95782, 0.95782, 0.95782, 0.97093, 1.03584, 0.96924, 0.96924, 0.96924, 0.96924, 0.96924, 1.1714, 0.96924, 0.95132, 0.95132, 0.95132, 0.95132, 0.8287, 0.83968, 0.89049, 0.91484, 0.91484, 0.91484, 0.91484, 0.91484, 0.91484, 0.93575, 0.92383, 0.9403, 0.9403, 0.9403, 0.9403, 0.8717, 0.8717, 0.8717, 0.8717, 1.00527, 1.01756, 1.05356, 1.05356, 1.05356, 1.05356, 1.05356, 1.24644, 0.95923, 1.01756, 1.01756, 1.01756, 1.01756, 0.96777, 1.05707, 0.96777, 0.96706, 0.91484, 0.96706, 0.91484, 0.96706, 0.91484, 0.85734, 0.92383, 0.85734, 0.92383, 0.85734, 0.92383, 0.85734, 0.92383, 0.97093, 1.0969, 0.97093, 1.05882, 0.75842, 0.9403, 0.75842, 0.9403, 0.75842, 0.9403, 0.75842, 0.9403, 0.75842, 0.9403, 0.88198, 1.05882, 0.88198, 1.05882, 0.88198, 1.05882, 1, 1, 0.9831, 1.01756, 0.9831, 1.01756, 0.95782, 0.8717, 0.95782, 0.8717, 0.95782, 0.8717, 0.95782, 1.09011, 0.95782, 0.8717, 0.84784, 1.11551, 0.71387, 1.09011, 1, 1, 0.99414, 0.84636, 1.09011, 1, 1, 0.84636, 1.0536, 0.84636, 0.94298, 0.84636, 1.23297, 1.03584, 1.01756, 1, 1, 1.03584, 1.01756, 1.00323, 1.03444, 1.01756, 0.96924, 1.05356, 0.96924, 1.05356, 0.96924, 1.05356, 0.93066, 0.98293, 0.82826, 1.04399, 1, 1, 0.82826, 1.04399, 0.79649, 0.84863, 0.79649, 0.84863, 0.79649, 0.84863, 0.79649, 0.84863, 1, 1, 0.85771, 1.17318, 0.85771, 1.21968, 0.95132, 1.01756, 0.95132, 1.01756, 0.95132, 1.01756, 0.95132, 1.01756, 0.95132, 1.01756, 0.95132, 1.01756, 0.98965, 1.00068, 0.8287, 0.96777, 0.8287, 0.93365, 0.9043, 0.93365, 0.9043, 0.93365, 0.9043, 1.08571, 0.96927, 0.96706, 0.91484, 0.86035, 0.93575, 0.96924, 0.95923, 1, 1, 0.85771, 1.21968, 1.11437, 1.11437, 0.93109, 0.91202, 0.60411, 0.84164, 0.55572, 1.01173, 0.97361, 0.81818, 0.81818, 0.96635, 0.78032, 0.72727, 0.92366, 0.98601, 1.03405, 0.77968, 1.09799, 1.2, 0.96706, 0.85944, 0.85638, 0.96491, 0.75842, 0.93365, 0.9831, 0.96924, 0.95782, 0.86969, 0.94152, 1.07796, 1.03584, 0.78437, 0.96924, 0.98715, 0.83968, 0.83491, 0.85771, 0.8287, 0.94492, 0.88433, 0.9287, 1.0098, 0.95782, 0.8287, 1.0625, 0.98248, 1.03424, 1.2, 1.01071, 1.0625, 0.95246, 1.03809, 1.04912, 0.98248, 1.00221, 1.03424, 1.05443, 1.2, 1.04785, 0.99609, 1.00169, 1.05176, 0.99346, 1.05356, 0.9087, 1.03004, 0.95542, 0.93117, 1.23362, 1.01071, 1.07831, 1.02512, 1.05205, 1.03502, 1.2, 1.01071, 1.05356, 1.01071, 1.03502, 0.75842, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.03719, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.9403, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.04021, 1, 1, 1, 1, 1, 1, 0.98965, 1.00068, 0.98965, 1.00068, 0.98965, 1.00068, 0.8287, 0.96777, 1, 1.20088, 0.89903, 1, 1, 0.75155, 1.03077, 1.03077, 1.03077, 1.03077, 1.13196, 1.13196, 1.13196, 0.67428, 0.67428, 1.16039, 0.73291, 1.20996, 1.22135, 1.06483, 0.94868, 0.94868, 0.95996, 1.24633, 1, 1.07497, 0.87796, 0.96927, 1.01518, 0.96927, 1, 1, 1, 0.77295, 1, 1, 1.10539, 1.10539, 1.11358, 1.06967, 0.86279, 0.94434, 0.86279, 0.94434, 0.86182, 1, 1, 1.083, 1, 0.91578, 0.86507, 1.1714, 1.18416, 1.14589, 0.69825, 0.97622, 1.9697, 1.24822, 1.24822, 1.17238, 1.24822, 1.24822, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1.42603, 1, 0.99862, 0.99862, 1, 0.87025, 0.87025, 0.87025, 0.87025, 1.18083, 1.42603, 1, 1.42603, 1.42603, 0.99862, 1, 1, 1, 1, 1, 1.2886, 1.04315, 1.15296, 1.34163, 1, 1, 1, 1.10938, 1.10938, 1, 1, 1, 1.05425, 1.09971, 1.09971, 1.09971, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
const SegoeuiRegularMetrics = {
  lineHeight: 1.33008,
  lineGap: 0
};

;// CONCATENATED MODULE: ./src/core/xfa_fonts.js








const getXFAFontMap = getLookupTableFactory(function (t) {
  t["MyriadPro-Regular"] = t["PdfJS-Fallback-Regular"] = {
    name: "LiberationSans-Regular",
    factors: MyriadProRegularFactors,
    baseWidths: LiberationSansRegularWidths,
    baseMapping: LiberationSansRegularMapping,
    metrics: MyriadProRegularMetrics
  };
  t["MyriadPro-Bold"] = t["PdfJS-Fallback-Bold"] = {
    name: "LiberationSans-Bold",
    factors: MyriadProBoldFactors,
    baseWidths: LiberationSansBoldWidths,
    baseMapping: LiberationSansBoldMapping,
    metrics: MyriadProBoldMetrics
  };
  t["MyriadPro-It"] = t["MyriadPro-Italic"] = t["PdfJS-Fallback-Italic"] = {
    name: "LiberationSans-Italic",
    factors: MyriadProItalicFactors,
    baseWidths: LiberationSansItalicWidths,
    baseMapping: LiberationSansItalicMapping,
    metrics: MyriadProItalicMetrics
  };
  t["MyriadPro-BoldIt"] = t["MyriadPro-BoldItalic"] = t["PdfJS-Fallback-BoldItalic"] = {
    name: "LiberationSans-BoldItalic",
    factors: MyriadProBoldItalicFactors,
    baseWidths: LiberationSansBoldItalicWidths,
    baseMapping: LiberationSansBoldItalicMapping,
    metrics: MyriadProBoldItalicMetrics
  };
  t.ArialMT = t.Arial = t["Arial-Regular"] = {
    name: "LiberationSans-Regular",
    baseWidths: LiberationSansRegularWidths,
    baseMapping: LiberationSansRegularMapping
  };
  t["Arial-BoldMT"] = t["Arial-Bold"] = {
    name: "LiberationSans-Bold",
    baseWidths: LiberationSansBoldWidths,
    baseMapping: LiberationSansBoldMapping
  };
  t["Arial-ItalicMT"] = t["Arial-Italic"] = {
    name: "LiberationSans-Italic",
    baseWidths: LiberationSansItalicWidths,
    baseMapping: LiberationSansItalicMapping
  };
  t["Arial-BoldItalicMT"] = t["Arial-BoldItalic"] = {
    name: "LiberationSans-BoldItalic",
    baseWidths: LiberationSansBoldItalicWidths,
    baseMapping: LiberationSansBoldItalicMapping
  };
  t["Calibri-Regular"] = {
    name: "LiberationSans-Regular",
    factors: CalibriRegularFactors,
    baseWidths: LiberationSansRegularWidths,
    baseMapping: LiberationSansRegularMapping,
    metrics: CalibriRegularMetrics
  };
  t["Calibri-Bold"] = {
    name: "LiberationSans-Bold",
    factors: CalibriBoldFactors,
    baseWidths: LiberationSansBoldWidths,
    baseMapping: LiberationSansBoldMapping,
    metrics: CalibriBoldMetrics
  };
  t["Calibri-Italic"] = {
    name: "LiberationSans-Italic",
    factors: CalibriItalicFactors,
    baseWidths: LiberationSansItalicWidths,
    baseMapping: LiberationSansItalicMapping,
    metrics: CalibriItalicMetrics
  };
  t["Calibri-BoldItalic"] = {
    name: "LiberationSans-BoldItalic",
    factors: CalibriBoldItalicFactors,
    baseWidths: LiberationSansBoldItalicWidths,
    baseMapping: LiberationSansBoldItalicMapping,
    metrics: CalibriBoldItalicMetrics
  };
  t["Segoeui-Regular"] = {
    name: "LiberationSans-Regular",
    factors: SegoeuiRegularFactors,
    baseWidths: LiberationSansRegularWidths,
    baseMapping: LiberationSansRegularMapping,
    metrics: SegoeuiRegularMetrics
  };
  t["Segoeui-Bold"] = {
    name: "LiberationSans-Bold",
    factors: SegoeuiBoldFactors,
    baseWidths: LiberationSansBoldWidths,
    baseMapping: LiberationSansBoldMapping,
    metrics: SegoeuiBoldMetrics
  };
  t["Segoeui-Italic"] = {
    name: "LiberationSans-Italic",
    factors: SegoeuiItalicFactors,
    baseWidths: LiberationSansItalicWidths,
    baseMapping: LiberationSansItalicMapping,
    metrics: SegoeuiItalicMetrics
  };
  t["Segoeui-BoldItalic"] = {
    name: "LiberationSans-BoldItalic",
    factors: SegoeuiBoldItalicFactors,
    baseWidths: LiberationSansBoldItalicWidths,
    baseMapping: LiberationSansBoldItalicMapping,
    metrics: SegoeuiBoldItalicMetrics
  };
  t["Helvetica-Regular"] = t.Helvetica = {
    name: "LiberationSans-Regular",
    factors: HelveticaRegularFactors,
    baseWidths: LiberationSansRegularWidths,
    baseMapping: LiberationSansRegularMapping,
    metrics: HelveticaRegularMetrics
  };
  t["Helvetica-Bold"] = {
    name: "LiberationSans-Bold",
    factors: HelveticaBoldFactors,
    baseWidths: LiberationSansBoldWidths,
    baseMapping: LiberationSansBoldMapping,
    metrics: HelveticaBoldMetrics
  };
  t["Helvetica-Italic"] = {
    name: "LiberationSans-Italic",
    factors: HelveticaItalicFactors,
    baseWidths: LiberationSansItalicWidths,
    baseMapping: LiberationSansItalicMapping,
    metrics: HelveticaItalicMetrics
  };
  t["Helvetica-BoldItalic"] = {
    name: "LiberationSans-BoldItalic",
    factors: HelveticaBoldItalicFactors,
    baseWidths: LiberationSansBoldItalicWidths,
    baseMapping: LiberationSansBoldItalicMapping,
    metrics: HelveticaBoldItalicMetrics
  };
});
function getXfaFontName(name) {
  const fontName = normalizeFontName(name);
  const fontMap = getXFAFontMap();
  return fontMap[fontName];
}
function getXfaFontWidths(name) {
  const info = getXfaFontName(name);
  if (!info) {
    return null;
  }
  const {
    baseWidths,
    baseMapping,
    factors
  } = info;
  const rescaledBaseWidths = !factors ? baseWidths : baseWidths.map((w, i) => w * factors[i]);
  let currentCode = -2;
  let currentArray;
  const newWidths = [];
  for (const [unicode, glyphIndex] of baseMapping.map((charUnicode, index) => [charUnicode, index]).sort(([unicode1], [unicode2]) => unicode1 - unicode2)) {
    if (unicode === -1) {
      continue;
    }
    if (unicode === currentCode + 1) {
      currentArray.push(rescaledBaseWidths[glyphIndex]);
      currentCode += 1;
    } else {
      currentCode = unicode;
      currentArray = [rescaledBaseWidths[glyphIndex]];
      newWidths.push(unicode, currentArray);
    }
  }
  return newWidths;
}
function getXfaFontDict(name) {
  const widths = getXfaFontWidths(name);
  const dict = new Dict(null);
  dict.set("BaseFont", Name.get(name));
  dict.set("Type", Name.get("Font"));
  dict.set("Subtype", Name.get("CIDFontType2"));
  dict.set("Encoding", Name.get("Identity-H"));
  dict.set("CIDToGIDMap", Name.get("Identity"));
  dict.set("W", widths);
  dict.set("FirstChar", widths[0]);
  dict.set("LastChar", widths.at(-2) + widths.at(-1).length - 1);
  const descriptor = new Dict(null);
  dict.set("FontDescriptor", descriptor);
  const systemInfo = new Dict(null);
  systemInfo.set("Ordering", "Identity");
  systemInfo.set("Registry", "Adobe");
  systemInfo.set("Supplement", 0);
  dict.set("CIDSystemInfo", systemInfo);
  return dict;
}

;// CONCATENATED MODULE: ./src/core/ps_parser.js



class PostScriptParser {
  constructor(lexer) {
    this.lexer = lexer;
    this.operators = [];
    this.token = null;
    this.prev = null;
  }
  nextToken() {
    this.prev = this.token;
    this.token = this.lexer.getToken();
  }
  accept(type) {
    if (this.token.type === type) {
      this.nextToken();
      return true;
    }
    return false;
  }
  expect(type) {
    if (this.accept(type)) {
      return true;
    }
    throw new FormatError(`Unexpected symbol: found ${this.token.type} expected ${type}.`);
  }
  parse() {
    this.nextToken();
    this.expect(PostScriptTokenTypes.LBRACE);
    this.parseBlock();
    this.expect(PostScriptTokenTypes.RBRACE);
    return this.operators;
  }
  parseBlock() {
    while (true) {
      if (this.accept(PostScriptTokenTypes.NUMBER)) {
        this.operators.push(this.prev.value);
      } else if (this.accept(PostScriptTokenTypes.OPERATOR)) {
        this.operators.push(this.prev.value);
      } else if (this.accept(PostScriptTokenTypes.LBRACE)) {
        this.parseCondition();
      } else {
        return;
      }
    }
  }
  parseCondition() {
    const conditionLocation = this.operators.length;
    this.operators.push(null, null);
    this.parseBlock();
    this.expect(PostScriptTokenTypes.RBRACE);
    if (this.accept(PostScriptTokenTypes.IF)) {
      this.operators[conditionLocation] = this.operators.length;
      this.operators[conditionLocation + 1] = "jz";
    } else if (this.accept(PostScriptTokenTypes.LBRACE)) {
      const jumpLocation = this.operators.length;
      this.operators.push(null, null);
      const endOfTrue = this.operators.length;
      this.parseBlock();
      this.expect(PostScriptTokenTypes.RBRACE);
      this.expect(PostScriptTokenTypes.IFELSE);
      this.operators[jumpLocation] = this.operators.length;
      this.operators[jumpLocation + 1] = "j";
      this.operators[conditionLocation] = endOfTrue;
      this.operators[conditionLocation + 1] = "jz";
    } else {
      throw new FormatError("PS Function: error parsing conditional.");
    }
  }
}
const PostScriptTokenTypes = {
  LBRACE: 0,
  RBRACE: 1,
  NUMBER: 2,
  OPERATOR: 3,
  IF: 4,
  IFELSE: 5
};
class PostScriptToken {
  static get opCache() {
    return shadow(this, "opCache", Object.create(null));
  }
  constructor(type, value) {
    this.type = type;
    this.value = value;
  }
  static getOperator(op) {
    return PostScriptToken.opCache[op] ||= new PostScriptToken(PostScriptTokenTypes.OPERATOR, op);
  }
  static get LBRACE() {
    return shadow(this, "LBRACE", new PostScriptToken(PostScriptTokenTypes.LBRACE, "{"));
  }
  static get RBRACE() {
    return shadow(this, "RBRACE", new PostScriptToken(PostScriptTokenTypes.RBRACE, "}"));
  }
  static get IF() {
    return shadow(this, "IF", new PostScriptToken(PostScriptTokenTypes.IF, "IF"));
  }
  static get IFELSE() {
    return shadow(this, "IFELSE", new PostScriptToken(PostScriptTokenTypes.IFELSE, "IFELSE"));
  }
}
class PostScriptLexer {
  constructor(stream) {
    this.stream = stream;
    this.nextChar();
    this.strBuf = [];
  }
  nextChar() {
    return this.currentChar = this.stream.getByte();
  }
  getToken() {
    let comment = false;
    let ch = this.currentChar;
    while (true) {
      if (ch < 0) {
        return EOF;
      }
      if (comment) {
        if (ch === 0x0a || ch === 0x0d) {
          comment = false;
        }
      } else if (ch === 0x25) {
        comment = true;
      } else if (!isWhiteSpace(ch)) {
        break;
      }
      ch = this.nextChar();
    }
    switch (ch | 0) {
      case 0x30:
      case 0x31:
      case 0x32:
      case 0x33:
      case 0x34:
      case 0x35:
      case 0x36:
      case 0x37:
      case 0x38:
      case 0x39:
      case 0x2b:
      case 0x2d:
      case 0x2e:
        return new PostScriptToken(PostScriptTokenTypes.NUMBER, this.getNumber());
      case 0x7b:
        this.nextChar();
        return PostScriptToken.LBRACE;
      case 0x7d:
        this.nextChar();
        return PostScriptToken.RBRACE;
    }
    const strBuf = this.strBuf;
    strBuf.length = 0;
    strBuf[0] = String.fromCharCode(ch);
    while ((ch = this.nextChar()) >= 0 && (ch >= 0x41 && ch <= 0x5a || ch >= 0x61 && ch <= 0x7a)) {
      strBuf.push(String.fromCharCode(ch));
    }
    const str = strBuf.join("");
    switch (str.toLowerCase()) {
      case "if":
        return PostScriptToken.IF;
      case "ifelse":
        return PostScriptToken.IFELSE;
      default:
        return PostScriptToken.getOperator(str);
    }
  }
  getNumber() {
    let ch = this.currentChar;
    const strBuf = this.strBuf;
    strBuf.length = 0;
    strBuf[0] = String.fromCharCode(ch);
    while ((ch = this.nextChar()) >= 0) {
      if (ch >= 0x30 && ch <= 0x39 || ch === 0x2d || ch === 0x2e) {
        strBuf.push(String.fromCharCode(ch));
      } else {
        break;
      }
    }
    const value = parseFloat(strBuf.join(""));
    if (isNaN(value)) {
      throw new FormatError(`Invalid floating point number: ${value}`);
    }
    return value;
  }
}

;// CONCATENATED MODULE: ./src/core/image_utils.js


class BaseLocalCache {
  constructor(options) {
    this._onlyRefs = options?.onlyRefs === true;
    if (!this._onlyRefs) {
      this._nameRefMap = new Map();
      this._imageMap = new Map();
    }
    this._imageCache = new RefSetCache();
  }
  getByName(name) {
    if (this._onlyRefs) {
      unreachable("Should not call `getByName` method.");
    }
    const ref = this._nameRefMap.get(name);
    if (ref) {
      return this.getByRef(ref);
    }
    return this._imageMap.get(name) || null;
  }
  getByRef(ref) {
    return this._imageCache.get(ref) || null;
  }
  set(name, ref, data) {
    unreachable("Abstract method `set` called.");
  }
}
class LocalImageCache extends BaseLocalCache {
  set(name, ref = null, data) {
    if (typeof name !== "string") {
      throw new Error('LocalImageCache.set - expected "name" argument.');
    }
    if (ref) {
      if (this._imageCache.has(ref)) {
        return;
      }
      this._nameRefMap.set(name, ref);
      this._imageCache.put(ref, data);
      return;
    }
    if (this._imageMap.has(name)) {
      return;
    }
    this._imageMap.set(name, data);
  }
}
class LocalColorSpaceCache extends BaseLocalCache {
  set(name = null, ref = null, data) {
    if (typeof name !== "string" && !ref) {
      throw new Error('LocalColorSpaceCache.set - expected "name" and/or "ref" argument.');
    }
    if (ref) {
      if (this._imageCache.has(ref)) {
        return;
      }
      if (name !== null) {
        this._nameRefMap.set(name, ref);
      }
      this._imageCache.put(ref, data);
      return;
    }
    if (this._imageMap.has(name)) {
      return;
    }
    this._imageMap.set(name, data);
  }
}
class LocalFunctionCache extends BaseLocalCache {
  constructor(options) {
    super({
      onlyRefs: true
    });
  }
  set(name = null, ref, data) {
    if (!ref) {
      throw new Error('LocalFunctionCache.set - expected "ref" argument.');
    }
    if (this._imageCache.has(ref)) {
      return;
    }
    this._imageCache.put(ref, data);
  }
}
class LocalGStateCache extends BaseLocalCache {
  set(name, ref = null, data) {
    if (typeof name !== "string") {
      throw new Error('LocalGStateCache.set - expected "name" argument.');
    }
    if (ref) {
      if (this._imageCache.has(ref)) {
        return;
      }
      this._nameRefMap.set(name, ref);
      this._imageCache.put(ref, data);
      return;
    }
    if (this._imageMap.has(name)) {
      return;
    }
    this._imageMap.set(name, data);
  }
}
class LocalTilingPatternCache extends BaseLocalCache {
  constructor(options) {
    super({
      onlyRefs: true
    });
  }
  set(name = null, ref, data) {
    if (!ref) {
      throw new Error('LocalTilingPatternCache.set - expected "ref" argument.');
    }
    if (this._imageCache.has(ref)) {
      return;
    }
    this._imageCache.put(ref, data);
  }
}
class RegionalImageCache extends BaseLocalCache {
  constructor(options) {
    super({
      onlyRefs: true
    });
  }
  set(name = null, ref, data) {
    if (!ref) {
      throw new Error('RegionalImageCache.set - expected "ref" argument.');
    }
    if (this._imageCache.has(ref)) {
      return;
    }
    this._imageCache.put(ref, data);
  }
}
class GlobalImageCache {
  static NUM_PAGES_THRESHOLD = 2;
  static MIN_IMAGES_TO_CACHE = 10;
  static MAX_BYTE_SIZE = 5 * MAX_IMAGE_SIZE_TO_CACHE;
  #decodeFailedSet = new RefSet();
  constructor() {
    this._refCache = new RefSetCache();
    this._imageCache = new RefSetCache();
  }
  get #byteSize() {
    let byteSize = 0;
    for (const imageData of this._imageCache) {
      byteSize += imageData.byteSize;
    }
    return byteSize;
  }
  get #cacheLimitReached() {
    if (this._imageCache.size < GlobalImageCache.MIN_IMAGES_TO_CACHE) {
      return false;
    }
    if (this.#byteSize < GlobalImageCache.MAX_BYTE_SIZE) {
      return false;
    }
    return true;
  }
  shouldCache(ref, pageIndex) {
    let pageIndexSet = this._refCache.get(ref);
    if (!pageIndexSet) {
      pageIndexSet = new Set();
      this._refCache.put(ref, pageIndexSet);
    }
    pageIndexSet.add(pageIndex);
    if (pageIndexSet.size < GlobalImageCache.NUM_PAGES_THRESHOLD) {
      return false;
    }
    if (!this._imageCache.has(ref) && this.#cacheLimitReached) {
      return false;
    }
    return true;
  }
  addDecodeFailed(ref) {
    this.#decodeFailedSet.put(ref);
  }
  hasDecodeFailed(ref) {
    return this.#decodeFailedSet.has(ref);
  }
  addByteSize(ref, byteSize) {
    const imageData = this._imageCache.get(ref);
    if (!imageData) {
      return;
    }
    if (imageData.byteSize) {
      return;
    }
    imageData.byteSize = byteSize;
  }
  getData(ref, pageIndex) {
    const pageIndexSet = this._refCache.get(ref);
    if (!pageIndexSet) {
      return null;
    }
    if (pageIndexSet.size < GlobalImageCache.NUM_PAGES_THRESHOLD) {
      return null;
    }
    const imageData = this._imageCache.get(ref);
    if (!imageData) {
      return null;
    }
    pageIndexSet.add(pageIndex);
    return imageData;
  }
  setData(ref, data) {
    if (!this._refCache.has(ref)) {
      throw new Error('GlobalImageCache.setData - expected "shouldCache" to have been called.');
    }
    if (this._imageCache.has(ref)) {
      return;
    }
    if (this.#cacheLimitReached) {
      warn("GlobalImageCache.setData - cache limit reached.");
      return;
    }
    this._imageCache.put(ref, data);
  }
  clear(onlyData = false) {
    if (!onlyData) {
      this.#decodeFailedSet.clear();
      this._refCache.clear();
    }
    this._imageCache.clear();
  }
}

;// CONCATENATED MODULE: ./src/core/function.js






class PDFFunctionFactory {
  constructor({
    xref,
    isEvalSupported = true
  }) {
    this.xref = xref;
    this.isEvalSupported = isEvalSupported !== false;
  }
  create(fn) {
    const cachedFunction = this.getCached(fn);
    if (cachedFunction) {
      return cachedFunction;
    }
    const parsedFunction = PDFFunction.parse({
      xref: this.xref,
      isEvalSupported: this.isEvalSupported,
      fn: fn instanceof Ref ? this.xref.fetch(fn) : fn
    });
    this._cache(fn, parsedFunction);
    return parsedFunction;
  }
  createFromArray(fnObj) {
    const cachedFunction = this.getCached(fnObj);
    if (cachedFunction) {
      return cachedFunction;
    }
    const parsedFunction = PDFFunction.parseArray({
      xref: this.xref,
      isEvalSupported: this.isEvalSupported,
      fnObj: fnObj instanceof Ref ? this.xref.fetch(fnObj) : fnObj
    });
    this._cache(fnObj, parsedFunction);
    return parsedFunction;
  }
  getCached(cacheKey) {
    let fnRef;
    if (cacheKey instanceof Ref) {
      fnRef = cacheKey;
    } else if (cacheKey instanceof Dict) {
      fnRef = cacheKey.objId;
    } else if (cacheKey instanceof BaseStream) {
      fnRef = cacheKey.dict?.objId;
    }
    if (fnRef) {
      const localFunction = this._localFunctionCache.getByRef(fnRef);
      if (localFunction) {
        return localFunction;
      }
    }
    return null;
  }
  _cache(cacheKey, parsedFunction) {
    if (!parsedFunction) {
      throw new Error('PDFFunctionFactory._cache - expected "parsedFunction" argument.');
    }
    let fnRef;
    if (cacheKey instanceof Ref) {
      fnRef = cacheKey;
    } else if (cacheKey instanceof Dict) {
      fnRef = cacheKey.objId;
    } else if (cacheKey instanceof BaseStream) {
      fnRef = cacheKey.dict?.objId;
    }
    if (fnRef) {
      this._localFunctionCache.set(null, fnRef, parsedFunction);
    }
  }
  get _localFunctionCache() {
    return shadow(this, "_localFunctionCache", new LocalFunctionCache());
  }
}
function toNumberArray(arr) {
  if (!Array.isArray(arr)) {
    return null;
  }
  if (!isNumberArray(arr, null)) {
    return arr.map(x => +x);
  }
  return arr;
}
class PDFFunction {
  static getSampleArray(size, outputSize, bps, stream) {
    let i, ii;
    let length = 1;
    for (i = 0, ii = size.length; i < ii; i++) {
      length *= size[i];
    }
    length *= outputSize;
    const array = new Array(length);
    let codeSize = 0;
    let codeBuf = 0;
    const sampleMul = 1.0 / (2.0 ** bps - 1);
    const strBytes = stream.getBytes((length * bps + 7) / 8);
    let strIdx = 0;
    for (i = 0; i < length; i++) {
      while (codeSize < bps) {
        codeBuf <<= 8;
        codeBuf |= strBytes[strIdx++];
        codeSize += 8;
      }
      codeSize -= bps;
      array[i] = (codeBuf >> codeSize) * sampleMul;
      codeBuf &= (1 << codeSize) - 1;
    }
    return array;
  }
  static parse({
    xref,
    isEvalSupported,
    fn
  }) {
    const dict = fn.dict || fn;
    const typeNum = dict.get("FunctionType");
    switch (typeNum) {
      case 0:
        return this.constructSampled({
          xref,
          isEvalSupported,
          fn,
          dict
        });
      case 1:
        break;
      case 2:
        return this.constructInterpolated({
          xref,
          isEvalSupported,
          dict
        });
      case 3:
        return this.constructStiched({
          xref,
          isEvalSupported,
          dict
        });
      case 4:
        return this.constructPostScript({
          xref,
          isEvalSupported,
          fn,
          dict
        });
    }
    throw new FormatError("Unknown type of function");
  }
  static parseArray({
    xref,
    isEvalSupported,
    fnObj
  }) {
    if (!Array.isArray(fnObj)) {
      return this.parse({
        xref,
        isEvalSupported,
        fn: fnObj
      });
    }
    const fnArray = [];
    for (const fn of fnObj) {
      fnArray.push(this.parse({
        xref,
        isEvalSupported,
        fn: xref.fetchIfRef(fn)
      }));
    }
    return function (src, srcOffset, dest, destOffset) {
      for (let i = 0, ii = fnArray.length; i < ii; i++) {
        fnArray[i](src, srcOffset, dest, destOffset + i);
      }
    };
  }
  static constructSampled({
    xref,
    isEvalSupported,
    fn,
    dict
  }) {
    function toMultiArray(arr) {
      const inputLength = arr.length;
      const out = [];
      let index = 0;
      for (let i = 0; i < inputLength; i += 2) {
        out[index++] = [arr[i], arr[i + 1]];
      }
      return out;
    }
    function interpolate(x, xmin, xmax, ymin, ymax) {
      return ymin + (x - xmin) * ((ymax - ymin) / (xmax - xmin));
    }
    let domain = toNumberArray(dict.getArray("Domain"));
    let range = toNumberArray(dict.getArray("Range"));
    if (!domain || !range) {
      throw new FormatError("No domain or range");
    }
    const inputSize = domain.length / 2;
    const outputSize = range.length / 2;
    domain = toMultiArray(domain);
    range = toMultiArray(range);
    const size = toNumberArray(dict.getArray("Size"));
    const bps = dict.get("BitsPerSample");
    const order = dict.get("Order") || 1;
    if (order !== 1) {
      info("No support for cubic spline interpolation: " + order);
    }
    let encode = toNumberArray(dict.getArray("Encode"));
    if (!encode) {
      encode = [];
      for (let i = 0; i < inputSize; ++i) {
        encode.push([0, size[i] - 1]);
      }
    } else {
      encode = toMultiArray(encode);
    }
    let decode = toNumberArray(dict.getArray("Decode"));
    decode = !decode ? range : toMultiArray(decode);
    const samples = this.getSampleArray(size, outputSize, bps, fn);
    return function constructSampledFn(src, srcOffset, dest, destOffset) {
      const cubeVertices = 1 << inputSize;
      const cubeN = new Float64Array(cubeVertices);
      const cubeVertex = new Uint32Array(cubeVertices);
      let i, j;
      for (j = 0; j < cubeVertices; j++) {
        cubeN[j] = 1;
      }
      let k = outputSize,
        pos = 1;
      for (i = 0; i < inputSize; ++i) {
        const domain_2i = domain[i][0];
        const domain_2i_1 = domain[i][1];
        const xi = Math.min(Math.max(src[srcOffset + i], domain_2i), domain_2i_1);
        let e = interpolate(xi, domain_2i, domain_2i_1, encode[i][0], encode[i][1]);
        const size_i = size[i];
        e = Math.min(Math.max(e, 0), size_i - 1);
        const e0 = e < size_i - 1 ? Math.floor(e) : e - 1;
        const n0 = e0 + 1 - e;
        const n1 = e - e0;
        const offset0 = e0 * k;
        const offset1 = offset0 + k;
        for (j = 0; j < cubeVertices; j++) {
          if (j & pos) {
            cubeN[j] *= n1;
            cubeVertex[j] += offset1;
          } else {
            cubeN[j] *= n0;
            cubeVertex[j] += offset0;
          }
        }
        k *= size_i;
        pos <<= 1;
      }
      for (j = 0; j < outputSize; ++j) {
        let rj = 0;
        for (i = 0; i < cubeVertices; i++) {
          rj += samples[cubeVertex[i] + j] * cubeN[i];
        }
        rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]);
        dest[destOffset + j] = Math.min(Math.max(rj, range[j][0]), range[j][1]);
      }
    };
  }
  static constructInterpolated({
    xref,
    isEvalSupported,
    dict
  }) {
    const c0 = toNumberArray(dict.getArray("C0")) || [0];
    const c1 = toNumberArray(dict.getArray("C1")) || [1];
    const n = dict.get("N");
    const diff = [];
    for (let i = 0, ii = c0.length; i < ii; ++i) {
      diff.push(c1[i] - c0[i]);
    }
    const length = diff.length;
    return function constructInterpolatedFn(src, srcOffset, dest, destOffset) {
      const x = n === 1 ? src[srcOffset] : src[srcOffset] ** n;
      for (let j = 0; j < length; ++j) {
        dest[destOffset + j] = c0[j] + x * diff[j];
      }
    };
  }
  static constructStiched({
    xref,
    isEvalSupported,
    dict
  }) {
    const domain = toNumberArray(dict.getArray("Domain"));
    if (!domain) {
      throw new FormatError("No domain");
    }
    const inputSize = domain.length / 2;
    if (inputSize !== 1) {
      throw new FormatError("Bad domain for stiched function");
    }
    const fns = [];
    for (const fn of dict.get("Functions")) {
      fns.push(this.parse({
        xref,
        isEvalSupported,
        fn: xref.fetchIfRef(fn)
      }));
    }
    const bounds = toNumberArray(dict.getArray("Bounds"));
    const encode = toNumberArray(dict.getArray("Encode"));
    const tmpBuf = new Float32Array(1);
    return function constructStichedFn(src, srcOffset, dest, destOffset) {
      const clip = function constructStichedFromIRClip(v, min, max) {
        if (v > max) {
          v = max;
        } else if (v < min) {
          v = min;
        }
        return v;
      };
      const v = clip(src[srcOffset], domain[0], domain[1]);
      const length = bounds.length;
      let i;
      for (i = 0; i < length; ++i) {
        if (v < bounds[i]) {
          break;
        }
      }
      let dmin = domain[0];
      if (i > 0) {
        dmin = bounds[i - 1];
      }
      let dmax = domain[1];
      if (i < bounds.length) {
        dmax = bounds[i];
      }
      const rmin = encode[2 * i];
      const rmax = encode[2 * i + 1];
      tmpBuf[0] = dmin === dmax ? rmin : rmin + (v - dmin) * (rmax - rmin) / (dmax - dmin);
      fns[i](tmpBuf, 0, dest, destOffset);
    };
  }
  static constructPostScript({
    xref,
    isEvalSupported,
    fn,
    dict
  }) {
    const domain = toNumberArray(dict.getArray("Domain"));
    const range = toNumberArray(dict.getArray("Range"));
    if (!domain) {
      throw new FormatError("No domain.");
    }
    if (!range) {
      throw new FormatError("No range.");
    }
    const lexer = new PostScriptLexer(fn);
    const parser = new PostScriptParser(lexer);
    const code = parser.parse();
    if (isEvalSupported && FeatureTest.isEvalSupported) {
      const compiled = new PostScriptCompiler().compile(code, domain, range);
      if (compiled) {
        return new Function("src", "srcOffset", "dest", "destOffset", compiled);
      }
    }
    info("Unable to compile PS function");
    const numOutputs = range.length >> 1;
    const numInputs = domain.length >> 1;
    const evaluator = new PostScriptEvaluator(code);
    const cache = Object.create(null);
    const MAX_CACHE_SIZE = 2048 * 4;
    let cache_available = MAX_CACHE_SIZE;
    const tmpBuf = new Float32Array(numInputs);
    return function constructPostScriptFn(src, srcOffset, dest, destOffset) {
      let i, value;
      let key = "";
      const input = tmpBuf;
      for (i = 0; i < numInputs; i++) {
        value = src[srcOffset + i];
        input[i] = value;
        key += value + "_";
      }
      const cachedValue = cache[key];
      if (cachedValue !== undefined) {
        dest.set(cachedValue, destOffset);
        return;
      }
      const output = new Float32Array(numOutputs);
      const stack = evaluator.execute(input);
      const stackIndex = stack.length - numOutputs;
      for (i = 0; i < numOutputs; i++) {
        value = stack[stackIndex + i];
        let bound = range[i * 2];
        if (value < bound) {
          value = bound;
        } else {
          bound = range[i * 2 + 1];
          if (value > bound) {
            value = bound;
          }
        }
        output[i] = value;
      }
      if (cache_available > 0) {
        cache_available--;
        cache[key] = output;
      }
      dest.set(output, destOffset);
    };
  }
}
function isPDFFunction(v) {
  let fnDict;
  if (v instanceof Dict) {
    fnDict = v;
  } else if (v instanceof BaseStream) {
    fnDict = v.dict;
  } else {
    return false;
  }
  return fnDict.has("FunctionType");
}
class PostScriptStack {
  static MAX_STACK_SIZE = 100;
  constructor(initialStack) {
    this.stack = initialStack ? Array.from(initialStack) : [];
  }
  push(value) {
    if (this.stack.length >= PostScriptStack.MAX_STACK_SIZE) {
      throw new Error("PostScript function stack overflow.");
    }
    this.stack.push(value);
  }
  pop() {
    if (this.stack.length <= 0) {
      throw new Error("PostScript function stack underflow.");
    }
    return this.stack.pop();
  }
  copy(n) {
    if (this.stack.length + n >= PostScriptStack.MAX_STACK_SIZE) {
      throw new Error("PostScript function stack overflow.");
    }
    const stack = this.stack;
    for (let i = stack.length - n, j = n - 1; j >= 0; j--, i++) {
      stack.push(stack[i]);
    }
  }
  index(n) {
    this.push(this.stack[this.stack.length - n - 1]);
  }
  roll(n, p) {
    const stack = this.stack;
    const l = stack.length - n;
    const r = stack.length - 1;
    const c = l + (p - Math.floor(p / n) * n);
    for (let i = l, j = r; i < j; i++, j--) {
      const t = stack[i];
      stack[i] = stack[j];
      stack[j] = t;
    }
    for (let i = l, j = c - 1; i < j; i++, j--) {
      const t = stack[i];
      stack[i] = stack[j];
      stack[j] = t;
    }
    for (let i = c, j = r; i < j; i++, j--) {
      const t = stack[i];
      stack[i] = stack[j];
      stack[j] = t;
    }
  }
}
class PostScriptEvaluator {
  constructor(operators) {
    this.operators = operators;
  }
  execute(initialStack) {
    const stack = new PostScriptStack(initialStack);
    let counter = 0;
    const operators = this.operators;
    const length = operators.length;
    let operator, a, b;
    while (counter < length) {
      operator = operators[counter++];
      if (typeof operator === "number") {
        stack.push(operator);
        continue;
      }
      switch (operator) {
        case "jz":
          b = stack.pop();
          a = stack.pop();
          if (!a) {
            counter = b;
          }
          break;
        case "j":
          a = stack.pop();
          counter = a;
          break;
        case "abs":
          a = stack.pop();
          stack.push(Math.abs(a));
          break;
        case "add":
          b = stack.pop();
          a = stack.pop();
          stack.push(a + b);
          break;
        case "and":
          b = stack.pop();
          a = stack.pop();
          if (typeof a === "boolean" && typeof b === "boolean") {
            stack.push(a && b);
          } else {
            stack.push(a & b);
          }
          break;
        case "atan":
          b = stack.pop();
          a = stack.pop();
          a = Math.atan2(a, b) / Math.PI * 180;
          if (a < 0) {
            a += 360;
          }
          stack.push(a);
          break;
        case "bitshift":
          b = stack.pop();
          a = stack.pop();
          if (a > 0) {
            stack.push(a << b);
          } else {
            stack.push(a >> b);
          }
          break;
        case "ceiling":
          a = stack.pop();
          stack.push(Math.ceil(a));
          break;
        case "copy":
          a = stack.pop();
          stack.copy(a);
          break;
        case "cos":
          a = stack.pop();
          stack.push(Math.cos(a % 360 / 180 * Math.PI));
          break;
        case "cvi":
          a = stack.pop() | 0;
          stack.push(a);
          break;
        case "cvr":
          break;
        case "div":
          b = stack.pop();
          a = stack.pop();
          stack.push(a / b);
          break;
        case "dup":
          stack.copy(1);
          break;
        case "eq":
          b = stack.pop();
          a = stack.pop();
          stack.push(a === b);
          break;
        case "exch":
          stack.roll(2, 1);
          break;
        case "exp":
          b = stack.pop();
          a = stack.pop();
          stack.push(a ** b);
          break;
        case "false":
          stack.push(false);
          break;
        case "floor":
          a = stack.pop();
          stack.push(Math.floor(a));
          break;
        case "ge":
          b = stack.pop();
          a = stack.pop();
          stack.push(a >= b);
          break;
        case "gt":
          b = stack.pop();
          a = stack.pop();
          stack.push(a > b);
          break;
        case "idiv":
          b = stack.pop();
          a = stack.pop();
          stack.push(a / b | 0);
          break;
        case "index":
          a = stack.pop();
          stack.index(a);
          break;
        case "le":
          b = stack.pop();
          a = stack.pop();
          stack.push(a <= b);
          break;
        case "ln":
          a = stack.pop();
          stack.push(Math.log(a));
          break;
        case "log":
          a = stack.pop();
          stack.push(Math.log10(a));
          break;
        case "lt":
          b = stack.pop();
          a = stack.pop();
          stack.push(a < b);
          break;
        case "mod":
          b = stack.pop();
          a = stack.pop();
          stack.push(a % b);
          break;
        case "mul":
          b = stack.pop();
          a = stack.pop();
          stack.push(a * b);
          break;
        case "ne":
          b = stack.pop();
          a = stack.pop();
          stack.push(a !== b);
          break;
        case "neg":
          a = stack.pop();
          stack.push(-a);
          break;
        case "not":
          a = stack.pop();
          if (typeof a === "boolean") {
            stack.push(!a);
          } else {
            stack.push(~a);
          }
          break;
        case "or":
          b = stack.pop();
          a = stack.pop();
          if (typeof a === "boolean" && typeof b === "boolean") {
            stack.push(a || b);
          } else {
            stack.push(a | b);
          }
          break;
        case "pop":
          stack.pop();
          break;
        case "roll":
          b = stack.pop();
          a = stack.pop();
          stack.roll(a, b);
          break;
        case "round":
          a = stack.pop();
          stack.push(Math.round(a));
          break;
        case "sin":
          a = stack.pop();
          stack.push(Math.sin(a % 360 / 180 * Math.PI));
          break;
        case "sqrt":
          a = stack.pop();
          stack.push(Math.sqrt(a));
          break;
        case "sub":
          b = stack.pop();
          a = stack.pop();
          stack.push(a - b);
          break;
        case "true":
          stack.push(true);
          break;
        case "truncate":
          a = stack.pop();
          a = a < 0 ? Math.ceil(a) : Math.floor(a);
          stack.push(a);
          break;
        case "xor":
          b = stack.pop();
          a = stack.pop();
          if (typeof a === "boolean" && typeof b === "boolean") {
            stack.push(a !== b);
          } else {
            stack.push(a ^ b);
          }
          break;
        default:
          throw new FormatError(`Unknown operator ${operator}`);
      }
    }
    return stack.stack;
  }
}
class AstNode {
  constructor(type) {
    this.type = type;
  }
  visit(visitor) {
    unreachable("abstract method");
  }
}
class AstArgument extends AstNode {
  constructor(index, min, max) {
    super("args");
    this.index = index;
    this.min = min;
    this.max = max;
  }
  visit(visitor) {
    visitor.visitArgument(this);
  }
}
class AstLiteral extends AstNode {
  constructor(number) {
    super("literal");
    this.number = number;
    this.min = number;
    this.max = number;
  }
  visit(visitor) {
    visitor.visitLiteral(this);
  }
}
class AstBinaryOperation extends AstNode {
  constructor(op, arg1, arg2, min, max) {
    super("binary");
    this.op = op;
    this.arg1 = arg1;
    this.arg2 = arg2;
    this.min = min;
    this.max = max;
  }
  visit(visitor) {
    visitor.visitBinaryOperation(this);
  }
}
class AstMin extends AstNode {
  constructor(arg, max) {
    super("max");
    this.arg = arg;
    this.min = arg.min;
    this.max = max;
  }
  visit(visitor) {
    visitor.visitMin(this);
  }
}
class AstVariable extends AstNode {
  constructor(index, min, max) {
    super("var");
    this.index = index;
    this.min = min;
    this.max = max;
  }
  visit(visitor) {
    visitor.visitVariable(this);
  }
}
class AstVariableDefinition extends AstNode {
  constructor(variable, arg) {
    super("definition");
    this.variable = variable;
    this.arg = arg;
  }
  visit(visitor) {
    visitor.visitVariableDefinition(this);
  }
}
class ExpressionBuilderVisitor {
  constructor() {
    this.parts = [];
  }
  visitArgument(arg) {
    this.parts.push("Math.max(", arg.min, ", Math.min(", arg.max, ", src[srcOffset + ", arg.index, "]))");
  }
  visitVariable(variable) {
    this.parts.push("v", variable.index);
  }
  visitLiteral(literal) {
    this.parts.push(literal.number);
  }
  visitBinaryOperation(operation) {
    this.parts.push("(");
    operation.arg1.visit(this);
    this.parts.push(" ", operation.op, " ");
    operation.arg2.visit(this);
    this.parts.push(")");
  }
  visitVariableDefinition(definition) {
    this.parts.push("var ");
    definition.variable.visit(this);
    this.parts.push(" = ");
    definition.arg.visit(this);
    this.parts.push(";");
  }
  visitMin(max) {
    this.parts.push("Math.min(");
    max.arg.visit(this);
    this.parts.push(", ", max.max, ")");
  }
  toString() {
    return this.parts.join("");
  }
}
function buildAddOperation(num1, num2) {
  if (num2.type === "literal" && num2.number === 0) {
    return num1;
  }
  if (num1.type === "literal" && num1.number === 0) {
    return num2;
  }
  if (num2.type === "literal" && num1.type === "literal") {
    return new AstLiteral(num1.number + num2.number);
  }
  return new AstBinaryOperation("+", num1, num2, num1.min + num2.min, num1.max + num2.max);
}
function buildMulOperation(num1, num2) {
  if (num2.type === "literal") {
    if (num2.number === 0) {
      return new AstLiteral(0);
    } else if (num2.number === 1) {
      return num1;
    } else if (num1.type === "literal") {
      return new AstLiteral(num1.number * num2.number);
    }
  }
  if (num1.type === "literal") {
    if (num1.number === 0) {
      return new AstLiteral(0);
    } else if (num1.number === 1) {
      return num2;
    }
  }
  const min = Math.min(num1.min * num2.min, num1.min * num2.max, num1.max * num2.min, num1.max * num2.max);
  const max = Math.max(num1.min * num2.min, num1.min * num2.max, num1.max * num2.min, num1.max * num2.max);
  return new AstBinaryOperation("*", num1, num2, min, max);
}
function buildSubOperation(num1, num2) {
  if (num2.type === "literal") {
    if (num2.number === 0) {
      return num1;
    } else if (num1.type === "literal") {
      return new AstLiteral(num1.number - num2.number);
    }
  }
  if (num2.type === "binary" && num2.op === "-" && num1.type === "literal" && num1.number === 1 && num2.arg1.type === "literal" && num2.arg1.number === 1) {
    return num2.arg2;
  }
  return new AstBinaryOperation("-", num1, num2, num1.min - num2.max, num1.max - num2.min);
}
function buildMinOperation(num1, max) {
  if (num1.min >= max) {
    return new AstLiteral(max);
  } else if (num1.max <= max) {
    return num1;
  }
  return new AstMin(num1, max);
}
class PostScriptCompiler {
  compile(code, domain, range) {
    const stack = [];
    const instructions = [];
    const inputSize = domain.length >> 1,
      outputSize = range.length >> 1;
    let lastRegister = 0;
    let n, j;
    let num1, num2, ast1, ast2, tmpVar, item;
    for (let i = 0; i < inputSize; i++) {
      stack.push(new AstArgument(i, domain[i * 2], domain[i * 2 + 1]));
    }
    for (let i = 0, ii = code.length; i < ii; i++) {
      item = code[i];
      if (typeof item === "number") {
        stack.push(new AstLiteral(item));
        continue;
      }
      switch (item) {
        case "add":
          if (stack.length < 2) {
            return null;
          }
          num2 = stack.pop();
          num1 = stack.pop();
          stack.push(buildAddOperation(num1, num2));
          break;
        case "cvr":
          if (stack.length < 1) {
            return null;
          }
          break;
        case "mul":
          if (stack.length < 2) {
            return null;
          }
          num2 = stack.pop();
          num1 = stack.pop();
          stack.push(buildMulOperation(num1, num2));
          break;
        case "sub":
          if (stack.length < 2) {
            return null;
          }
          num2 = stack.pop();
          num1 = stack.pop();
          stack.push(buildSubOperation(num1, num2));
          break;
        case "exch":
          if (stack.length < 2) {
            return null;
          }
          ast1 = stack.pop();
          ast2 = stack.pop();
          stack.push(ast1, ast2);
          break;
        case "pop":
          if (stack.length < 1) {
            return null;
          }
          stack.pop();
          break;
        case "index":
          if (stack.length < 1) {
            return null;
          }
          num1 = stack.pop();
          if (num1.type !== "literal") {
            return null;
          }
          n = num1.number;
          if (n < 0 || !Number.isInteger(n) || stack.length < n) {
            return null;
          }
          ast1 = stack[stack.length - n - 1];
          if (ast1.type === "literal" || ast1.type === "var") {
            stack.push(ast1);
            break;
          }
          tmpVar = new AstVariable(lastRegister++, ast1.min, ast1.max);
          stack[stack.length - n - 1] = tmpVar;
          stack.push(tmpVar);
          instructions.push(new AstVariableDefinition(tmpVar, ast1));
          break;
        case "dup":
          if (stack.length < 1) {
            return null;
          }
          if (typeof code[i + 1] === "number" && code[i + 2] === "gt" && code[i + 3] === i + 7 && code[i + 4] === "jz" && code[i + 5] === "pop" && code[i + 6] === code[i + 1]) {
            num1 = stack.pop();
            stack.push(buildMinOperation(num1, code[i + 1]));
            i += 6;
            break;
          }
          ast1 = stack.at(-1);
          if (ast1.type === "literal" || ast1.type === "var") {
            stack.push(ast1);
            break;
          }
          tmpVar = new AstVariable(lastRegister++, ast1.min, ast1.max);
          stack[stack.length - 1] = tmpVar;
          stack.push(tmpVar);
          instructions.push(new AstVariableDefinition(tmpVar, ast1));
          break;
        case "roll":
          if (stack.length < 2) {
            return null;
          }
          num2 = stack.pop();
          num1 = stack.pop();
          if (num2.type !== "literal" || num1.type !== "literal") {
            return null;
          }
          j = num2.number;
          n = num1.number;
          if (n <= 0 || !Number.isInteger(n) || !Number.isInteger(j) || stack.length < n) {
            return null;
          }
          j = (j % n + n) % n;
          if (j === 0) {
            break;
          }
          stack.push(...stack.splice(stack.length - n, n - j));
          break;
        default:
          return null;
      }
    }
    if (stack.length !== outputSize) {
      return null;
    }
    const result = [];
    for (const instruction of instructions) {
      const statementBuilder = new ExpressionBuilderVisitor();
      instruction.visit(statementBuilder);
      result.push(statementBuilder.toString());
    }
    for (let i = 0, ii = stack.length; i < ii; i++) {
      const expr = stack[i],
        statementBuilder = new ExpressionBuilderVisitor();
      expr.visit(statementBuilder);
      const min = range[i * 2],
        max = range[i * 2 + 1];
      const out = [statementBuilder.toString()];
      if (min > expr.min) {
        out.unshift("Math.max(", min, ", ");
        out.push(")");
      }
      if (max < expr.max) {
        out.unshift("Math.min(", max, ", ");
        out.push(")");
      }
      out.unshift("dest[destOffset + ", i, "] = ");
      out.push(";");
      result.push(out.join(""));
    }
    return result.join("\n");
  }
}

;// CONCATENATED MODULE: ./src/core/bidi.js

const baseTypes = ["BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "S", "B", "S", "WS", "B", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "B", "B", "B", "S", "WS", "ON", "ON", "ET", "ET", "ET", "ON", "ON", "ON", "ON", "ON", "ES", "CS", "ES", "CS", "CS", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "CS", "ON", "ON", "ON", "ON", "ON", "ON", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "ON", "ON", "ON", "ON", "ON", "ON", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "ON", "ON", "ON", "ON", "BN", "BN", "BN", "BN", "BN", "BN", "B", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "BN", "CS", "ON", "ET", "ET", "ET", "ET", "ON", "ON", "ON", "ON", "L", "ON", "ON", "BN", "ON", "ON", "ET", "ET", "EN", "EN", "ON", "L", "ON", "ON", "ON", "EN", "L", "ON", "ON", "ON", "ON", "ON", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "ON", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "L", "ON", "L", "L", "L", "L", "L", "L", "L", "L"];
const arabicTypes = ["AN", "AN", "AN", "AN", "AN", "AN", "ON", "ON", "AL", "ET", "ET", "AL", "CS", "AL", "ON", "ON", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "AL", "AL", "", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "AN", "AN", "AN", "AN", "AN", "AN", "AN", "AN", "AN", "AN", "ET", "AN", "AN", "AL", "AL", "AL", "NSM", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "AL", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "AN", "ON", "NSM", "NSM", "NSM", "NSM", "NSM", "NSM", "AL", "AL", "NSM", "NSM", "ON", "NSM", "NSM", "NSM", "NSM", "AL", "AL", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "EN", "AL", "AL", "AL", "AL", "AL", "AL"];
function isOdd(i) {
  return (i & 1) !== 0;
}
function isEven(i) {
  return (i & 1) === 0;
}
function findUnequal(arr, start, value) {
  let j, jj;
  for (j = start, jj = arr.length; j < jj; ++j) {
    if (arr[j] !== value) {
      return j;
    }
  }
  return j;
}
function setValues(arr, start, end, value) {
  for (let j = start; j < end; ++j) {
    arr[j] = value;
  }
}
function reverseValues(arr, start, end) {
  for (let i = start, j = end - 1; i < j; ++i, --j) {
    const temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
  }
}
function createBidiText(str, isLTR, vertical = false) {
  let dir = "ltr";
  if (vertical) {
    dir = "ttb";
  } else if (!isLTR) {
    dir = "rtl";
  }
  return {
    str,
    dir
  };
}
const chars = [];
const types = [];
function bidi(str, startLevel = -1, vertical = false) {
  let isLTR = true;
  const strLength = str.length;
  if (strLength === 0 || vertical) {
    return createBidiText(str, isLTR, vertical);
  }
  chars.length = strLength;
  types.length = strLength;
  let numBidi = 0;
  let i, ii;
  for (i = 0; i < strLength; ++i) {
    chars[i] = str.charAt(i);
    const charCode = str.charCodeAt(i);
    let charType = "L";
    if (charCode <= 0x00ff) {
      charType = baseTypes[charCode];
    } else if (0x0590 <= charCode && charCode <= 0x05f4) {
      charType = "R";
    } else if (0x0600 <= charCode && charCode <= 0x06ff) {
      charType = arabicTypes[charCode & 0xff];
      if (!charType) {
        warn("Bidi: invalid Unicode character " + charCode.toString(16));
      }
    } else if (0x0700 <= charCode && charCode <= 0x08ac || 0xfb50 <= charCode && charCode <= 0xfdff || 0xfe70 <= charCode && charCode <= 0xfeff) {
      charType = "AL";
    }
    if (charType === "R" || charType === "AL" || charType === "AN") {
      numBidi++;
    }
    types[i] = charType;
  }
  if (numBidi === 0) {
    isLTR = true;
    return createBidiText(str, isLTR);
  }
  if (startLevel === -1) {
    if (numBidi / strLength < 0.3 && strLength > 4) {
      isLTR = true;
      startLevel = 0;
    } else {
      isLTR = false;
      startLevel = 1;
    }
  }
  const levels = [];
  for (i = 0; i < strLength; ++i) {
    levels[i] = startLevel;
  }
  const e = isOdd(startLevel) ? "R" : "L";
  const sor = e;
  const eor = sor;
  let lastType = sor;
  for (i = 0; i < strLength; ++i) {
    if (types[i] === "NSM") {
      types[i] = lastType;
    } else {
      lastType = types[i];
    }
  }
  lastType = sor;
  let t;
  for (i = 0; i < strLength; ++i) {
    t = types[i];
    if (t === "EN") {
      types[i] = lastType === "AL" ? "AN" : "EN";
    } else if (t === "R" || t === "L" || t === "AL") {
      lastType = t;
    }
  }
  for (i = 0; i < strLength; ++i) {
    t = types[i];
    if (t === "AL") {
      types[i] = "R";
    }
  }
  for (i = 1; i < strLength - 1; ++i) {
    if (types[i] === "ES" && types[i - 1] === "EN" && types[i + 1] === "EN") {
      types[i] = "EN";
    }
    if (types[i] === "CS" && (types[i - 1] === "EN" || types[i - 1] === "AN") && types[i + 1] === types[i - 1]) {
      types[i] = types[i - 1];
    }
  }
  for (i = 0; i < strLength; ++i) {
    if (types[i] === "EN") {
      for (let j = i - 1; j >= 0; --j) {
        if (types[j] !== "ET") {
          break;
        }
        types[j] = "EN";
      }
      for (let j = i + 1; j < strLength; ++j) {
        if (types[j] !== "ET") {
          break;
        }
        types[j] = "EN";
      }
    }
  }
  for (i = 0; i < strLength; ++i) {
    t = types[i];
    if (t === "WS" || t === "ES" || t === "ET" || t === "CS") {
      types[i] = "ON";
    }
  }
  lastType = sor;
  for (i = 0; i < strLength; ++i) {
    t = types[i];
    if (t === "EN") {
      types[i] = lastType === "L" ? "L" : "EN";
    } else if (t === "R" || t === "L") {
      lastType = t;
    }
  }
  for (i = 0; i < strLength; ++i) {
    if (types[i] === "ON") {
      const end = findUnequal(types, i + 1, "ON");
      let before = sor;
      if (i > 0) {
        before = types[i - 1];
      }
      let after = eor;
      if (end + 1 < strLength) {
        after = types[end + 1];
      }
      if (before !== "L") {
        before = "R";
      }
      if (after !== "L") {
        after = "R";
      }
      if (before === after) {
        setValues(types, i, end, before);
      }
      i = end - 1;
    }
  }
  for (i = 0; i < strLength; ++i) {
    if (types[i] === "ON") {
      types[i] = e;
    }
  }
  for (i = 0; i < strLength; ++i) {
    t = types[i];
    if (isEven(levels[i])) {
      if (t === "R") {
        levels[i] += 1;
      } else if (t === "AN" || t === "EN") {
        levels[i] += 2;
      }
    } else if (t === "L" || t === "AN" || t === "EN") {
      levels[i] += 1;
    }
  }
  let highestLevel = -1;
  let lowestOddLevel = 99;
  let level;
  for (i = 0, ii = levels.length; i < ii; ++i) {
    level = levels[i];
    if (highestLevel < level) {
      highestLevel = level;
    }
    if (lowestOddLevel > level && isOdd(level)) {
      lowestOddLevel = level;
    }
  }
  for (level = highestLevel; level >= lowestOddLevel; --level) {
    let start = -1;
    for (i = 0, ii = levels.length; i < ii; ++i) {
      if (levels[i] < level) {
        if (start >= 0) {
          reverseValues(chars, start, i);
          start = -1;
        }
      } else if (start < 0) {
        start = i;
      }
    }
    if (start >= 0) {
      reverseValues(chars, start, levels.length);
    }
  }
  for (i = 0, ii = chars.length; i < ii; ++i) {
    const ch = chars[i];
    if (ch === "<" || ch === ">") {
      chars[i] = "";
    }
  }
  return createBidiText(chars.join(""), isLTR);
}

;// CONCATENATED MODULE: ./src/core/font_substitutions.js



const NORMAL = {
  style: "normal",
  weight: "normal"
};
const BOLD = {
  style: "normal",
  weight: "bold"
};
const ITALIC = {
  style: "italic",
  weight: "normal"
};
const BOLDITALIC = {
  style: "italic",
  weight: "bold"
};
const substitutionMap = new Map([["Times-Roman", {
  local: ["Times New Roman", "Times-Roman", "Times", "Liberation Serif", "Nimbus Roman", "Nimbus Roman L", "Tinos", "Thorndale", "TeX Gyre Termes", "FreeSerif", "Linux Libertine O", "Libertinus Serif", "DejaVu Serif", "Bitstream Vera Serif", "Ubuntu"],
  style: NORMAL,
  ultimate: "serif"
}], ["Times-Bold", {
  alias: "Times-Roman",
  style: BOLD,
  ultimate: "serif"
}], ["Times-Italic", {
  alias: "Times-Roman",
  style: ITALIC,
  ultimate: "serif"
}], ["Times-BoldItalic", {
  alias: "Times-Roman",
  style: BOLDITALIC,
  ultimate: "serif"
}], ["Helvetica", {
  local: ["Helvetica", "Helvetica Neue", "Arial", "Arial Nova", "Liberation Sans", "Arimo", "Nimbus Sans", "Nimbus Sans L", "A030", "TeX Gyre Heros", "FreeSans", "DejaVu Sans", "Albany", "Bitstream Vera Sans", "Arial Unicode MS", "Microsoft Sans Serif", "Apple Symbols", "Cantarell"],
  path: "LiberationSans-Regular.ttf",
  style: NORMAL,
  ultimate: "sans-serif"
}], ["Helvetica-Bold", {
  alias: "Helvetica",
  path: "LiberationSans-Bold.ttf",
  style: BOLD,
  ultimate: "sans-serif"
}], ["Helvetica-Oblique", {
  alias: "Helvetica",
  path: "LiberationSans-Italic.ttf",
  style: ITALIC,
  ultimate: "sans-serif"
}], ["Helvetica-BoldOblique", {
  alias: "Helvetica",
  path: "LiberationSans-BoldItalic.ttf",
  style: BOLDITALIC,
  ultimate: "sans-serif"
}], ["Courier", {
  local: ["Courier", "Courier New", "Liberation Mono", "Nimbus Mono", "Nimbus Mono L", "Cousine", "Cumberland", "TeX Gyre Cursor", "FreeMono", "Linux Libertine Mono O", "Libertinus Mono"],
  style: NORMAL,
  ultimate: "monospace"
}], ["Courier-Bold", {
  alias: "Courier",
  style: BOLD,
  ultimate: "monospace"
}], ["Courier-Oblique", {
  alias: "Courier",
  style: ITALIC,
  ultimate: "monospace"
}], ["Courier-BoldOblique", {
  alias: "Courier",
  style: BOLDITALIC,
  ultimate: "monospace"
}], ["ArialBlack", {
  local: ["Arial Black"],
  style: {
    style: "normal",
    weight: "900"
  },
  fallback: "Helvetica-Bold"
}], ["ArialBlack-Bold", {
  alias: "ArialBlack"
}], ["ArialBlack-Italic", {
  alias: "ArialBlack",
  style: {
    style: "italic",
    weight: "900"
  },
  fallback: "Helvetica-BoldOblique"
}], ["ArialBlack-BoldItalic", {
  alias: "ArialBlack-Italic"
}], ["ArialNarrow", {
  local: ["Arial Narrow", "Liberation Sans Narrow", "Helvetica Condensed", "Nimbus Sans Narrow", "TeX Gyre Heros Cn"],
  style: NORMAL,
  fallback: "Helvetica"
}], ["ArialNarrow-Bold", {
  alias: "ArialNarrow",
  style: BOLD,
  fallback: "Helvetica-Bold"
}], ["ArialNarrow-Italic", {
  alias: "ArialNarrow",
  style: ITALIC,
  fallback: "Helvetica-Oblique"
}], ["ArialNarrow-BoldItalic", {
  alias: "ArialNarrow",
  style: BOLDITALIC,
  fallback: "Helvetica-BoldOblique"
}], ["Calibri", {
  local: ["Calibri", "Carlito"],
  style: NORMAL,
  fallback: "Helvetica"
}], ["Calibri-Bold", {
  alias: "Calibri",
  style: BOLD,
  fallback: "Helvetica-Bold"
}], ["Calibri-Italic", {
  alias: "Calibri",
  style: ITALIC,
  fallback: "Helvetica-Oblique"
}], ["Calibri-BoldItalic", {
  alias: "Calibri",
  style: BOLDITALIC,
  fallback: "Helvetica-BoldOblique"
}], ["Wingdings", {
  local: ["Wingdings", "URW Dingbats"],
  style: NORMAL
}], ["Wingdings-Regular", {
  alias: "Wingdings"
}], ["Wingdings-Bold", {
  alias: "Wingdings"
}]]);
const fontAliases = new Map([["Arial-Black", "ArialBlack"]]);
function getStyleToAppend(style) {
  switch (style) {
    case BOLD:
      return "Bold";
    case ITALIC:
      return "Italic";
    case BOLDITALIC:
      return "Bold Italic";
    default:
      if (style?.weight === "bold") {
        return "Bold";
      }
      if (style?.style === "italic") {
        return "Italic";
      }
  }
  return "";
}
function getFamilyName(str) {
  const keywords = new Set(["thin", "extralight", "ultralight", "demilight", "semilight", "light", "book", "regular", "normal", "medium", "demibold", "semibold", "bold", "extrabold", "ultrabold", "black", "heavy", "extrablack", "ultrablack", "roman", "italic", "oblique", "ultracondensed", "extracondensed", "condensed", "semicondensed", "normal", "semiexpanded", "expanded", "extraexpanded", "ultraexpanded", "bolditalic"]);
  return str.split(/[- ,+]+/g).filter(tok => !keywords.has(tok.toLowerCase())).join(" ");
}
function generateFont({
  alias,
  local,
  path,
  fallback,
  style,
  ultimate
}, src, localFontPath, useFallback = true, usePath = true, append = "") {
  const result = {
    style: null,
    ultimate: null
  };
  if (local) {
    const extra = append ? ` ${append}` : "";
    for (const name of local) {
      src.push(`local(${name}${extra})`);
    }
  }
  if (alias) {
    const substitution = substitutionMap.get(alias);
    const aliasAppend = append || getStyleToAppend(style);
    Object.assign(result, generateFont(substitution, src, localFontPath, useFallback && !fallback, usePath && !path, aliasAppend));
  }
  if (style) {
    result.style = style;
  }
  if (ultimate) {
    result.ultimate = ultimate;
  }
  if (useFallback && fallback) {
    const fallbackInfo = substitutionMap.get(fallback);
    const {
      ultimate: fallbackUltimate
    } = generateFont(fallbackInfo, src, localFontPath, useFallback, usePath && !path, append);
    result.ultimate ||= fallbackUltimate;
  }
  if (usePath && path && localFontPath) {
    src.push(`url(${localFontPath}${path})`);
  }
  return result;
}
function getFontSubstitution(systemFontCache, idFactory, localFontPath, baseFontName, standardFontName, type) {
  if (baseFontName.startsWith("InvalidPDFjsFont_")) {
    return null;
  }
  if ((type === "TrueType" || type === "Type1") && /^[A-Z]{6}\+/.test(baseFontName)) {
    baseFontName = baseFontName.slice(7);
  }
  baseFontName = normalizeFontName(baseFontName);
  const key = baseFontName;
  let substitutionInfo = systemFontCache.get(key);
  if (substitutionInfo) {
    return substitutionInfo;
  }
  let substitution = substitutionMap.get(baseFontName);
  if (!substitution) {
    for (const [alias, subst] of fontAliases) {
      if (baseFontName.startsWith(alias)) {
        baseFontName = `${subst}${baseFontName.substring(alias.length)}`;
        substitution = substitutionMap.get(baseFontName);
        break;
      }
    }
  }
  let mustAddBaseFont = false;
  if (!substitution) {
    substitution = substitutionMap.get(standardFontName);
    mustAddBaseFont = true;
  }
  const loadedName = `${idFactory.getDocId()}_s${idFactory.createFontId()}`;
  if (!substitution) {
    if (!validateFontName(baseFontName)) {
      warn(`Cannot substitute the font because of its name: ${baseFontName}`);
      systemFontCache.set(key, null);
      return null;
    }
    const bold = /bold/gi.test(baseFontName);
    const italic = /oblique|italic/gi.test(baseFontName);
    const style = bold && italic && BOLDITALIC || bold && BOLD || italic && ITALIC || NORMAL;
    substitutionInfo = {
      css: `"${getFamilyName(baseFontName)}",${loadedName}`,
      guessFallback: true,
      loadedName,
      baseFontName,
      src: `local(${baseFontName})`,
      style
    };
    systemFontCache.set(key, substitutionInfo);
    return substitutionInfo;
  }
  const src = [];
  if (mustAddBaseFont && validateFontName(baseFontName)) {
    src.push(`local(${baseFontName})`);
  }
  const {
    style,
    ultimate
  } = generateFont(substitution, src, localFontPath);
  const guessFallback = ultimate === null;
  const fallback = guessFallback ? "" : `,${ultimate}`;
  substitutionInfo = {
    css: `"${getFamilyName(baseFontName)}",${loadedName}${fallback}`,
    guessFallback,
    loadedName,
    baseFontName,
    src: src.join(","),
    style
  };
  systemFontCache.set(key, substitutionInfo);
  return substitutionInfo;
}

;// CONCATENATED MODULE: ./src/core/image_resizer.js

const MIN_IMAGE_DIM = 2048;
const MAX_IMAGE_DIM = 65537;
const MAX_ERROR = 128;
class ImageResizer {
  constructor(imgData, isMask) {
    this._imgData = imgData;
    this._isMask = isMask;
  }
  static needsToBeResized(width, height) {
    if (width <= this._goodSquareLength && height <= this._goodSquareLength) {
      return false;
    }
    const {
      MAX_DIM
    } = this;
    if (width > MAX_DIM || height > MAX_DIM) {
      return true;
    }
    const area = width * height;
    if (this._hasMaxArea) {
      return area > this.MAX_AREA;
    }
    if (area < this._goodSquareLength ** 2) {
      return false;
    }
    if (this._areGoodDims(width, height)) {
      this._goodSquareLength = Math.max(this._goodSquareLength, Math.floor(Math.sqrt(width * height)));
      return false;
    }
    this._goodSquareLength = this._guessMax(this._goodSquareLength, MAX_DIM, MAX_ERROR, 0);
    const maxArea = this.MAX_AREA = this._goodSquareLength ** 2;
    return area > maxArea;
  }
  static get MAX_DIM() {
    return shadow(this, "MAX_DIM", this._guessMax(MIN_IMAGE_DIM, MAX_IMAGE_DIM, 0, 1));
  }
  static get MAX_AREA() {
    this._hasMaxArea = true;
    return shadow(this, "MAX_AREA", this._guessMax(ImageResizer._goodSquareLength, this.MAX_DIM, MAX_ERROR, 0) ** 2);
  }
  static set MAX_AREA(area) {
    if (area >= 0) {
      this._hasMaxArea = true;
      shadow(this, "MAX_AREA", area);
    }
  }
  static setMaxArea(area) {
    if (!this._hasMaxArea) {
      this.MAX_AREA = area >> 2;
    }
  }
  static _areGoodDims(width, height) {
    try {
      const canvas = new OffscreenCanvas(width, height);
      const ctx = canvas.getContext("2d");
      ctx.fillRect(0, 0, 1, 1);
      const opacity = ctx.getImageData(0, 0, 1, 1).data[3];
      canvas.width = canvas.height = 1;
      return opacity !== 0;
    } catch {
      return false;
    }
  }
  static _guessMax(start, end, tolerance, defaultHeight) {
    while (start + tolerance + 1 < end) {
      const middle = Math.floor((start + end) / 2);
      const height = defaultHeight || middle;
      if (this._areGoodDims(middle, height)) {
        start = middle;
      } else {
        end = middle;
      }
    }
    return start;
  }
  static async createImage(imgData, isMask = false) {
    return new ImageResizer(imgData, isMask)._createImage();
  }
  async _createImage() {
    const data = this._encodeBMP();
    const blob = new Blob([data.buffer], {
      type: "image/bmp"
    });
    const bitmapPromise = createImageBitmap(blob);
    const {
      MAX_AREA,
      MAX_DIM
    } = ImageResizer;
    const {
      _imgData: imgData
    } = this;
    const {
      width,
      height
    } = imgData;
    const minFactor = Math.max(width / MAX_DIM, height / MAX_DIM, Math.sqrt(width * height / MAX_AREA));
    const firstFactor = Math.max(minFactor, 2);
    const factor = Math.round(10 * (minFactor + 1.25)) / 10 / firstFactor;
    const N = Math.floor(Math.log2(factor));
    const steps = new Array(N + 2).fill(2);
    steps[0] = firstFactor;
    steps.splice(-1, 1, factor / (1 << N));
    let newWidth = width;
    let newHeight = height;
    let bitmap = await bitmapPromise;
    for (const step of steps) {
      const prevWidth = newWidth;
      const prevHeight = newHeight;
      newWidth = Math.floor(newWidth / step) - 1;
      newHeight = Math.floor(newHeight / step) - 1;
      const canvas = new OffscreenCanvas(newWidth, newHeight);
      const ctx = canvas.getContext("2d");
      ctx.drawImage(bitmap, 0, 0, prevWidth, prevHeight, 0, 0, newWidth, newHeight);
      bitmap = canvas.transferToImageBitmap();
    }
    imgData.data = null;
    imgData.bitmap = bitmap;
    imgData.width = newWidth;
    imgData.height = newHeight;
    return imgData;
  }
  _encodeBMP() {
    const {
      width,
      height,
      kind
    } = this._imgData;
    let data = this._imgData.data;
    let bitPerPixel;
    let colorTable = new Uint8Array(0);
    let maskTable = colorTable;
    let compression = 0;
    switch (kind) {
      case ImageKind.GRAYSCALE_1BPP:
        {
          bitPerPixel = 1;
          colorTable = new Uint8Array(this._isMask ? [255, 255, 255, 255, 0, 0, 0, 0] : [0, 0, 0, 0, 255, 255, 255, 255]);
          const rowLen = width + 7 >> 3;
          const rowSize = rowLen + 3 & -4;
          if (rowLen !== rowSize) {
            const newData = new Uint8Array(rowSize * height);
            let k = 0;
            for (let i = 0, ii = height * rowLen; i < ii; i += rowLen, k += rowSize) {
              newData.set(data.subarray(i, i + rowLen), k);
            }
            data = newData;
          }
          break;
        }
      case ImageKind.RGB_24BPP:
        {
          bitPerPixel = 24;
          if (width & 3) {
            const rowLen = 3 * width;
            const rowSize = rowLen + 3 & -4;
            const extraLen = rowSize - rowLen;
            const newData = new Uint8Array(rowSize * height);
            let k = 0;
            for (let i = 0, ii = height * rowLen; i < ii; i += rowLen) {
              const row = data.subarray(i, i + rowLen);
              for (let j = 0; j < rowLen; j += 3) {
                newData[k++] = row[j + 2];
                newData[k++] = row[j + 1];
                newData[k++] = row[j];
              }
              k += extraLen;
            }
            data = newData;
          } else {
            for (let i = 0, ii = data.length; i < ii; i += 3) {
              const tmp = data[i];
              data[i] = data[i + 2];
              data[i + 2] = tmp;
            }
          }
          break;
        }
      case ImageKind.RGBA_32BPP:
        bitPerPixel = 32;
        compression = 3;
        maskTable = new Uint8Array(4 + 4 + 4 + 4 + 52);
        const view = new DataView(maskTable.buffer);
        if (FeatureTest.isLittleEndian) {
          view.setUint32(0, 0x000000ff, true);
          view.setUint32(4, 0x0000ff00, true);
          view.setUint32(8, 0x00ff0000, true);
          view.setUint32(12, 0xff000000, true);
        } else {
          view.setUint32(0, 0xff000000, true);
          view.setUint32(4, 0x00ff0000, true);
          view.setUint32(8, 0x0000ff00, true);
          view.setUint32(12, 0x000000ff, true);
        }
        break;
      default:
        throw new Error("invalid format");
    }
    let i = 0;
    const headerLength = 40 + maskTable.length;
    const fileLength = 14 + headerLength + colorTable.length + data.length;
    const bmpData = new Uint8Array(fileLength);
    const view = new DataView(bmpData.buffer);
    view.setUint16(i, 0x4d42, true);
    i += 2;
    view.setUint32(i, fileLength, true);
    i += 4;
    view.setUint32(i, 0, true);
    i += 4;
    view.setUint32(i, 14 + headerLength + colorTable.length, true);
    i += 4;
    view.setUint32(i, headerLength, true);
    i += 4;
    view.setInt32(i, width, true);
    i += 4;
    view.setInt32(i, -height, true);
    i += 4;
    view.setUint16(i, 1, true);
    i += 2;
    view.setUint16(i, bitPerPixel, true);
    i += 2;
    view.setUint32(i, compression, true);
    i += 4;
    view.setUint32(i, 0, true);
    i += 4;
    view.setInt32(i, 0, true);
    i += 4;
    view.setInt32(i, 0, true);
    i += 4;
    view.setUint32(i, colorTable.length / 4, true);
    i += 4;
    view.setUint32(i, 0, true);
    i += 4;
    bmpData.set(maskTable, i);
    i += maskTable.length;
    bmpData.set(colorTable, i);
    i += colorTable.length;
    bmpData.set(data, i);
    return bmpData;
  }
}
ImageResizer._goodSquareLength = MIN_IMAGE_DIM;

;// CONCATENATED MODULE: ./src/shared/murmurhash3.js
const SEED = 0xc3d2e1f0;
const MASK_HIGH = 0xffff0000;
const MASK_LOW = 0xffff;
class MurmurHash3_64 {
  constructor(seed) {
    this.h1 = seed ? seed & 0xffffffff : SEED;
    this.h2 = seed ? seed & 0xffffffff : SEED;
  }
  update(input) {
    let data, length;
    if (typeof input === "string") {
      data = new Uint8Array(input.length * 2);
      length = 0;
      for (let i = 0, ii = input.length; i < ii; i++) {
        const code = input.charCodeAt(i);
        if (code <= 0xff) {
          data[length++] = code;
        } else {
          data[length++] = code >>> 8;
          data[length++] = code & 0xff;
        }
      }
    } else if (ArrayBuffer.isView(input)) {
      data = input.slice();
      length = data.byteLength;
    } else {
      throw new Error("Invalid data format, must be a string or TypedArray.");
    }
    const blockCounts = length >> 2;
    const tailLength = length - blockCounts * 4;
    const dataUint32 = new Uint32Array(data.buffer, 0, blockCounts);
    let k1 = 0,
      k2 = 0;
    let h1 = this.h1,
      h2 = this.h2;
    const C1 = 0xcc9e2d51,
      C2 = 0x1b873593;
    const C1_LOW = C1 & MASK_LOW,
      C2_LOW = C2 & MASK_LOW;
    for (let i = 0; i < blockCounts; i++) {
      if (i & 1) {
        k1 = dataUint32[i];
        k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW;
        k1 = k1 << 15 | k1 >>> 17;
        k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW;
        h1 ^= k1;
        h1 = h1 << 13 | h1 >>> 19;
        h1 = h1 * 5 + 0xe6546b64;
      } else {
        k2 = dataUint32[i];
        k2 = k2 * C1 & MASK_HIGH | k2 * C1_LOW & MASK_LOW;
        k2 = k2 << 15 | k2 >>> 17;
        k2 = k2 * C2 & MASK_HIGH | k2 * C2_LOW & MASK_LOW;
        h2 ^= k2;
        h2 = h2 << 13 | h2 >>> 19;
        h2 = h2 * 5 + 0xe6546b64;
      }
    }
    k1 = 0;
    switch (tailLength) {
      case 3:
        k1 ^= data[blockCounts * 4 + 2] << 16;
      case 2:
        k1 ^= data[blockCounts * 4 + 1] << 8;
      case 1:
        k1 ^= data[blockCounts * 4];
        k1 = k1 * C1 & MASK_HIGH | k1 * C1_LOW & MASK_LOW;
        k1 = k1 << 15 | k1 >>> 17;
        k1 = k1 * C2 & MASK_HIGH | k1 * C2_LOW & MASK_LOW;
        if (blockCounts & 1) {
          h1 ^= k1;
        } else {
          h2 ^= k1;
        }
    }
    this.h1 = h1;
    this.h2 = h2;
  }
  hexdigest() {
    let h1 = this.h1,
      h2 = this.h2;
    h1 ^= h2 >>> 1;
    h1 = h1 * 0xed558ccd & MASK_HIGH | h1 * 0x8ccd & MASK_LOW;
    h2 = h2 * 0xff51afd7 & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xafd7ed55 & MASK_HIGH) >>> 16;
    h1 ^= h2 >>> 1;
    h1 = h1 * 0x1a85ec53 & MASK_HIGH | h1 * 0xec53 & MASK_LOW;
    h2 = h2 * 0xc4ceb9fe & MASK_HIGH | ((h2 << 16 | h1 >>> 16) * 0xb9fe1a85 & MASK_HIGH) >>> 16;
    h1 ^= h2 >>> 1;
    return (h1 >>> 0).toString(16).padStart(8, "0") + (h2 >>> 0).toString(16).padStart(8, "0");
  }
}

;// CONCATENATED MODULE: ./src/core/operator_list.js

function addState(parentState, pattern, checkFn, iterateFn, processFn) {
  let state = parentState;
  for (let i = 0, ii = pattern.length - 1; i < ii; i++) {
    const item = pattern[i];
    state = state[item] ||= [];
  }
  state[pattern.at(-1)] = {
    checkFn,
    iterateFn,
    processFn
  };
}
const InitialState = [];
addState(InitialState, [OPS.save, OPS.transform, OPS.paintInlineImageXObject, OPS.restore], null, function iterateInlineImageGroup(context, i) {
  const fnArray = context.fnArray;
  const iFirstSave = context.iCurr - 3;
  const pos = (i - iFirstSave) % 4;
  switch (pos) {
    case 0:
      return fnArray[i] === OPS.save;
    case 1:
      return fnArray[i] === OPS.transform;
    case 2:
      return fnArray[i] === OPS.paintInlineImageXObject;
    case 3:
      return fnArray[i] === OPS.restore;
  }
  throw new Error(`iterateInlineImageGroup - invalid pos: ${pos}`);
}, function foundInlineImageGroup(context, i) {
  const MIN_IMAGES_IN_INLINE_IMAGES_BLOCK = 10;
  const MAX_IMAGES_IN_INLINE_IMAGES_BLOCK = 200;
  const MAX_WIDTH = 1000;
  const IMAGE_PADDING = 1;
  const fnArray = context.fnArray,
    argsArray = context.argsArray;
  const curr = context.iCurr;
  const iFirstSave = curr - 3;
  const iFirstTransform = curr - 2;
  const iFirstPIIXO = curr - 1;
  const count = Math.min(Math.floor((i - iFirstSave) / 4), MAX_IMAGES_IN_INLINE_IMAGES_BLOCK);
  if (count < MIN_IMAGES_IN_INLINE_IMAGES_BLOCK) {
    return i - (i - iFirstSave) % 4;
  }
  let maxX = 0;
  const map = [];
  let maxLineHeight = 0;
  let currentX = IMAGE_PADDING,
    currentY = IMAGE_PADDING;
  for (let q = 0; q < count; q++) {
    const transform = argsArray[iFirstTransform + (q << 2)];
    const img = argsArray[iFirstPIIXO + (q << 2)][0];
    if (currentX + img.width > MAX_WIDTH) {
      maxX = Math.max(maxX, currentX);
      currentY += maxLineHeight + 2 * IMAGE_PADDING;
      currentX = 0;
      maxLineHeight = 0;
    }
    map.push({
      transform,
      x: currentX,
      y: currentY,
      w: img.width,
      h: img.height
    });
    currentX += img.width + 2 * IMAGE_PADDING;
    maxLineHeight = Math.max(maxLineHeight, img.height);
  }
  const imgWidth = Math.max(maxX, currentX) + IMAGE_PADDING;
  const imgHeight = currentY + maxLineHeight + IMAGE_PADDING;
  const imgData = new Uint8Array(imgWidth * imgHeight * 4);
  const imgRowSize = imgWidth << 2;
  for (let q = 0; q < count; q++) {
    const data = argsArray[iFirstPIIXO + (q << 2)][0].data;
    const rowSize = map[q].w << 2;
    let dataOffset = 0;
    let offset = map[q].x + map[q].y * imgWidth << 2;
    imgData.set(data.subarray(0, rowSize), offset - imgRowSize);
    for (let k = 0, kk = map[q].h; k < kk; k++) {
      imgData.set(data.subarray(dataOffset, dataOffset + rowSize), offset);
      dataOffset += rowSize;
      offset += imgRowSize;
    }
    imgData.set(data.subarray(dataOffset - rowSize, dataOffset), offset);
    while (offset >= 0) {
      data[offset - 4] = data[offset];
      data[offset - 3] = data[offset + 1];
      data[offset - 2] = data[offset + 2];
      data[offset - 1] = data[offset + 3];
      data[offset + rowSize] = data[offset + rowSize - 4];
      data[offset + rowSize + 1] = data[offset + rowSize - 3];
      data[offset + rowSize + 2] = data[offset + rowSize - 2];
      data[offset + rowSize + 3] = data[offset + rowSize - 1];
      offset -= imgRowSize;
    }
  }
  const img = {
    width: imgWidth,
    height: imgHeight
  };
  if (context.isOffscreenCanvasSupported) {
    const canvas = new OffscreenCanvas(imgWidth, imgHeight);
    const ctx = canvas.getContext("2d");
    ctx.putImageData(new ImageData(new Uint8ClampedArray(imgData.buffer), imgWidth, imgHeight), 0, 0);
    img.bitmap = canvas.transferToImageBitmap();
    img.data = null;
  } else {
    img.kind = ImageKind.RGBA_32BPP;
    img.data = imgData;
  }
  fnArray.splice(iFirstSave, count * 4, OPS.paintInlineImageXObjectGroup);
  argsArray.splice(iFirstSave, count * 4, [img, map]);
  return iFirstSave + 1;
});
addState(InitialState, [OPS.save, OPS.transform, OPS.paintImageMaskXObject, OPS.restore], null, function iterateImageMaskGroup(context, i) {
  const fnArray = context.fnArray;
  const iFirstSave = context.iCurr - 3;
  const pos = (i - iFirstSave) % 4;
  switch (pos) {
    case 0:
      return fnArray[i] === OPS.save;
    case 1:
      return fnArray[i] === OPS.transform;
    case 2:
      return fnArray[i] === OPS.paintImageMaskXObject;
    case 3:
      return fnArray[i] === OPS.restore;
  }
  throw new Error(`iterateImageMaskGroup - invalid pos: ${pos}`);
}, function foundImageMaskGroup(context, i) {
  const MIN_IMAGES_IN_MASKS_BLOCK = 10;
  const MAX_IMAGES_IN_MASKS_BLOCK = 100;
  const MAX_SAME_IMAGES_IN_MASKS_BLOCK = 1000;
  const fnArray = context.fnArray,
    argsArray = context.argsArray;
  const curr = context.iCurr;
  const iFirstSave = curr - 3;
  const iFirstTransform = curr - 2;
  const iFirstPIMXO = curr - 1;
  let count = Math.floor((i - iFirstSave) / 4);
  if (count < MIN_IMAGES_IN_MASKS_BLOCK) {
    return i - (i - iFirstSave) % 4;
  }
  let isSameImage = false;
  let iTransform, transformArgs;
  const firstPIMXOArg0 = argsArray[iFirstPIMXO][0];
  const firstTransformArg0 = argsArray[iFirstTransform][0],
    firstTransformArg1 = argsArray[iFirstTransform][1],
    firstTransformArg2 = argsArray[iFirstTransform][2],
    firstTransformArg3 = argsArray[iFirstTransform][3];
  if (firstTransformArg1 === firstTransformArg2) {
    isSameImage = true;
    iTransform = iFirstTransform + 4;
    let iPIMXO = iFirstPIMXO + 4;
    for (let q = 1; q < count; q++, iTransform += 4, iPIMXO += 4) {
      transformArgs = argsArray[iTransform];
      if (argsArray[iPIMXO][0] !== firstPIMXOArg0 || transformArgs[0] !== firstTransformArg0 || transformArgs[1] !== firstTransformArg1 || transformArgs[2] !== firstTransformArg2 || transformArgs[3] !== firstTransformArg3) {
        if (q < MIN_IMAGES_IN_MASKS_BLOCK) {
          isSameImage = false;
        } else {
          count = q;
        }
        break;
      }
    }
  }
  if (isSameImage) {
    count = Math.min(count, MAX_SAME_IMAGES_IN_MASKS_BLOCK);
    const positions = new Float32Array(count * 2);
    iTransform = iFirstTransform;
    for (let q = 0; q < count; q++, iTransform += 4) {
      transformArgs = argsArray[iTransform];
      positions[q << 1] = transformArgs[4];
      positions[(q << 1) + 1] = transformArgs[5];
    }
    fnArray.splice(iFirstSave, count * 4, OPS.paintImageMaskXObjectRepeat);
    argsArray.splice(iFirstSave, count * 4, [firstPIMXOArg0, firstTransformArg0, firstTransformArg1, firstTransformArg2, firstTransformArg3, positions]);
  } else {
    count = Math.min(count, MAX_IMAGES_IN_MASKS_BLOCK);
    const images = [];
    for (let q = 0; q < count; q++) {
      transformArgs = argsArray[iFirstTransform + (q << 2)];
      const maskParams = argsArray[iFirstPIMXO + (q << 2)][0];
      images.push({
        data: maskParams.data,
        width: maskParams.width,
        height: maskParams.height,
        interpolate: maskParams.interpolate,
        count: maskParams.count,
        transform: transformArgs
      });
    }
    fnArray.splice(iFirstSave, count * 4, OPS.paintImageMaskXObjectGroup);
    argsArray.splice(iFirstSave, count * 4, [images]);
  }
  return iFirstSave + 1;
});
addState(InitialState, [OPS.save, OPS.transform, OPS.paintImageXObject, OPS.restore], function (context) {
  const argsArray = context.argsArray;
  const iFirstTransform = context.iCurr - 2;
  return argsArray[iFirstTransform][1] === 0 && argsArray[iFirstTransform][2] === 0;
}, function iterateImageGroup(context, i) {
  const fnArray = context.fnArray,
    argsArray = context.argsArray;
  const iFirstSave = context.iCurr - 3;
  const pos = (i - iFirstSave) % 4;
  switch (pos) {
    case 0:
      return fnArray[i] === OPS.save;
    case 1:
      if (fnArray[i] !== OPS.transform) {
        return false;
      }
      const iFirstTransform = context.iCurr - 2;
      const firstTransformArg0 = argsArray[iFirstTransform][0];
      const firstTransformArg3 = argsArray[iFirstTransform][3];
      if (argsArray[i][0] !== firstTransformArg0 || argsArray[i][1] !== 0 || argsArray[i][2] !== 0 || argsArray[i][3] !== firstTransformArg3) {
        return false;
      }
      return true;
    case 2:
      if (fnArray[i] !== OPS.paintImageXObject) {
        return false;
      }
      const iFirstPIXO = context.iCurr - 1;
      const firstPIXOArg0 = argsArray[iFirstPIXO][0];
      if (argsArray[i][0] !== firstPIXOArg0) {
        return false;
      }
      return true;
    case 3:
      return fnArray[i] === OPS.restore;
  }
  throw new Error(`iterateImageGroup - invalid pos: ${pos}`);
}, function (context, i) {
  const MIN_IMAGES_IN_BLOCK = 3;
  const MAX_IMAGES_IN_BLOCK = 1000;
  const fnArray = context.fnArray,
    argsArray = context.argsArray;
  const curr = context.iCurr;
  const iFirstSave = curr - 3;
  const iFirstTransform = curr - 2;
  const iFirstPIXO = curr - 1;
  const firstPIXOArg0 = argsArray[iFirstPIXO][0];
  const firstTransformArg0 = argsArray[iFirstTransform][0];
  const firstTransformArg3 = argsArray[iFirstTransform][3];
  const count = Math.min(Math.floor((i - iFirstSave) / 4), MAX_IMAGES_IN_BLOCK);
  if (count < MIN_IMAGES_IN_BLOCK) {
    return i - (i - iFirstSave) % 4;
  }
  const positions = new Float32Array(count * 2);
  let iTransform = iFirstTransform;
  for (let q = 0; q < count; q++, iTransform += 4) {
    const transformArgs = argsArray[iTransform];
    positions[q << 1] = transformArgs[4];
    positions[(q << 1) + 1] = transformArgs[5];
  }
  const args = [firstPIXOArg0, firstTransformArg0, firstTransformArg3, positions];
  fnArray.splice(iFirstSave, count * 4, OPS.paintImageXObjectRepeat);
  argsArray.splice(iFirstSave, count * 4, args);
  return iFirstSave + 1;
});
addState(InitialState, [OPS.beginText, OPS.setFont, OPS.setTextMatrix, OPS.showText, OPS.endText], null, function iterateShowTextGroup(context, i) {
  const fnArray = context.fnArray,
    argsArray = context.argsArray;
  const iFirstSave = context.iCurr - 4;
  const pos = (i - iFirstSave) % 5;
  switch (pos) {
    case 0:
      return fnArray[i] === OPS.beginText;
    case 1:
      return fnArray[i] === OPS.setFont;
    case 2:
      return fnArray[i] === OPS.setTextMatrix;
    case 3:
      if (fnArray[i] !== OPS.showText) {
        return false;
      }
      const iFirstSetFont = context.iCurr - 3;
      const firstSetFontArg0 = argsArray[iFirstSetFont][0];
      const firstSetFontArg1 = argsArray[iFirstSetFont][1];
      if (argsArray[i][0] !== firstSetFontArg0 || argsArray[i][1] !== firstSetFontArg1) {
        return false;
      }
      return true;
    case 4:
      return fnArray[i] === OPS.endText;
  }
  throw new Error(`iterateShowTextGroup - invalid pos: ${pos}`);
}, function (context, i) {
  const MIN_CHARS_IN_BLOCK = 3;
  const MAX_CHARS_IN_BLOCK = 1000;
  const fnArray = context.fnArray,
    argsArray = context.argsArray;
  const curr = context.iCurr;
  const iFirstBeginText = curr - 4;
  const iFirstSetFont = curr - 3;
  const iFirstSetTextMatrix = curr - 2;
  const iFirstShowText = curr - 1;
  const iFirstEndText = curr;
  const firstSetFontArg0 = argsArray[iFirstSetFont][0];
  const firstSetFontArg1 = argsArray[iFirstSetFont][1];
  let count = Math.min(Math.floor((i - iFirstBeginText) / 5), MAX_CHARS_IN_BLOCK);
  if (count < MIN_CHARS_IN_BLOCK) {
    return i - (i - iFirstBeginText) % 5;
  }
  let iFirst = iFirstBeginText;
  if (iFirstBeginText >= 4 && fnArray[iFirstBeginText - 4] === fnArray[iFirstSetFont] && fnArray[iFirstBeginText - 3] === fnArray[iFirstSetTextMatrix] && fnArray[iFirstBeginText - 2] === fnArray[iFirstShowText] && fnArray[iFirstBeginText - 1] === fnArray[iFirstEndText] && argsArray[iFirstBeginText - 4][0] === firstSetFontArg0 && argsArray[iFirstBeginText - 4][1] === firstSetFontArg1) {
    count++;
    iFirst -= 5;
  }
  let iEndText = iFirst + 4;
  for (let q = 1; q < count; q++) {
    fnArray.splice(iEndText, 3);
    argsArray.splice(iEndText, 3);
    iEndText += 2;
  }
  return iEndText + 1;
});
class NullOptimizer {
  constructor(queue) {
    this.queue = queue;
  }
  _optimize() {}
  push(fn, args) {
    this.queue.fnArray.push(fn);
    this.queue.argsArray.push(args);
    this._optimize();
  }
  flush() {}
  reset() {}
}
class QueueOptimizer extends NullOptimizer {
  constructor(queue) {
    super(queue);
    this.state = null;
    this.context = {
      iCurr: 0,
      fnArray: queue.fnArray,
      argsArray: queue.argsArray,
      isOffscreenCanvasSupported: false
    };
    this.match = null;
    this.lastProcessed = 0;
  }
  set isOffscreenCanvasSupported(value) {
    this.context.isOffscreenCanvasSupported = value;
  }
  _optimize() {
    const fnArray = this.queue.fnArray;
    let i = this.lastProcessed,
      ii = fnArray.length;
    let state = this.state;
    let match = this.match;
    if (!state && !match && i + 1 === ii && !InitialState[fnArray[i]]) {
      this.lastProcessed = ii;
      return;
    }
    const context = this.context;
    while (i < ii) {
      if (match) {
        const iterate = (0, match.iterateFn)(context, i);
        if (iterate) {
          i++;
          continue;
        }
        i = (0, match.processFn)(context, i + 1);
        ii = fnArray.length;
        match = null;
        state = null;
        if (i >= ii) {
          break;
        }
      }
      state = (state || InitialState)[fnArray[i]];
      if (!state || Array.isArray(state)) {
        i++;
        continue;
      }
      context.iCurr = i;
      i++;
      if (state.checkFn && !(0, state.checkFn)(context)) {
        state = null;
        continue;
      }
      match = state;
      state = null;
    }
    this.state = state;
    this.match = match;
    this.lastProcessed = i;
  }
  flush() {
    while (this.match) {
      const length = this.queue.fnArray.length;
      this.lastProcessed = (0, this.match.processFn)(this.context, length);
      this.match = null;
      this.state = null;
      this._optimize();
    }
  }
  reset() {
    this.state = null;
    this.match = null;
    this.lastProcessed = 0;
  }
}
class OperatorList {
  static CHUNK_SIZE = 1000;
  static CHUNK_SIZE_ABOUT = this.CHUNK_SIZE - 5;
  constructor(intent = 0, streamSink) {
    this._streamSink = streamSink;
    this.fnArray = [];
    this.argsArray = [];
    this.optimizer = streamSink && !(intent & RenderingIntentFlag.OPLIST) ? new QueueOptimizer(this) : new NullOptimizer(this);
    this.dependencies = new Set();
    this._totalLength = 0;
    this.weight = 0;
    this._resolved = streamSink ? null : Promise.resolve();
  }
  set isOffscreenCanvasSupported(value) {
    this.optimizer.isOffscreenCanvasSupported = value;
  }
  get length() {
    return this.argsArray.length;
  }
  get ready() {
    return this._resolved || this._streamSink.ready;
  }
  get totalLength() {
    return this._totalLength + this.length;
  }
  addOp(fn, args) {
    this.optimizer.push(fn, args);
    this.weight++;
    if (this._streamSink) {
      if (this.weight >= OperatorList.CHUNK_SIZE) {
        this.flush();
      } else if (this.weight >= OperatorList.CHUNK_SIZE_ABOUT && (fn === OPS.restore || fn === OPS.endText)) {
        this.flush();
      }
    }
  }
  addImageOps(fn, args, optionalContent) {
    if (optionalContent !== undefined) {
      this.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
    }
    this.addOp(fn, args);
    if (optionalContent !== undefined) {
      this.addOp(OPS.endMarkedContent, []);
    }
  }
  addDependency(dependency) {
    if (this.dependencies.has(dependency)) {
      return;
    }
    this.dependencies.add(dependency);
    this.addOp(OPS.dependency, [dependency]);
  }
  addDependencies(dependencies) {
    for (const dependency of dependencies) {
      this.addDependency(dependency);
    }
  }
  addOpList(opList) {
    if (!(opList instanceof OperatorList)) {
      warn('addOpList - ignoring invalid "opList" parameter.');
      return;
    }
    for (const dependency of opList.dependencies) {
      this.dependencies.add(dependency);
    }
    for (let i = 0, ii = opList.length; i < ii; i++) {
      this.addOp(opList.fnArray[i], opList.argsArray[i]);
    }
  }
  getIR() {
    return {
      fnArray: this.fnArray,
      argsArray: this.argsArray,
      length: this.length
    };
  }
  get _transfers() {
    const transfers = [];
    const {
      fnArray,
      argsArray,
      length
    } = this;
    for (let i = 0; i < length; i++) {
      switch (fnArray[i]) {
        case OPS.paintInlineImageXObject:
        case OPS.paintInlineImageXObjectGroup:
        case OPS.paintImageMaskXObject:
          const arg = argsArray[i][0];
          if (!arg.cached && arg.data?.buffer instanceof ArrayBuffer) {
            transfers.push(arg.data.buffer);
          }
          break;
      }
    }
    return transfers;
  }
  flush(lastChunk = false, separateAnnots = null) {
    this.optimizer.flush();
    const length = this.length;
    this._totalLength += length;
    this._streamSink.enqueue({
      fnArray: this.fnArray,
      argsArray: this.argsArray,
      lastChunk,
      separateAnnots,
      length
    }, 1, this._transfers);
    this.dependencies.clear();
    this.fnArray.length = 0;
    this.argsArray.length = 0;
    this.weight = 0;
    this.optimizer.reset();
  }
}

;// CONCATENATED MODULE: ./src/core/image.js









function decodeAndClamp(value, addend, coefficient, max) {
  value = addend + value * coefficient;
  if (value < 0) {
    value = 0;
  } else if (value > max) {
    value = max;
  }
  return value;
}
function resizeImageMask(src, bpc, w1, h1, w2, h2) {
  const length = w2 * h2;
  let dest;
  if (bpc <= 8) {
    dest = new Uint8Array(length);
  } else if (bpc <= 16) {
    dest = new Uint16Array(length);
  } else {
    dest = new Uint32Array(length);
  }
  const xRatio = w1 / w2;
  const yRatio = h1 / h2;
  let i,
    j,
    py,
    newIndex = 0,
    oldIndex;
  const xScaled = new Uint16Array(w2);
  const w1Scanline = w1;
  for (i = 0; i < w2; i++) {
    xScaled[i] = Math.floor(i * xRatio);
  }
  for (i = 0; i < h2; i++) {
    py = Math.floor(i * yRatio) * w1Scanline;
    for (j = 0; j < w2; j++) {
      oldIndex = py + xScaled[j];
      dest[newIndex++] = src[oldIndex];
    }
  }
  return dest;
}
class PDFImage {
  constructor({
    xref,
    res,
    image,
    isInline = false,
    smask = null,
    mask = null,
    isMask = false,
    pdfFunctionFactory,
    localColorSpaceCache
  }) {
    this.image = image;
    const dict = image.dict;
    const filter = dict.get("F", "Filter");
    let filterName;
    if (filter instanceof Name) {
      filterName = filter.name;
    } else if (Array.isArray(filter)) {
      const filterZero = xref.fetchIfRef(filter[0]);
      if (filterZero instanceof Name) {
        filterName = filterZero.name;
      }
    }
    switch (filterName) {
      case "JPXDecode":
        ({
          width: image.width,
          height: image.height,
          componentsCount: image.numComps,
          bitsPerComponent: image.bitsPerComponent
        } = JpxImage.parseImageProperties(image.stream));
        image.stream.reset();
        this.jpxDecoderOptions = {
          numComponents: 0,
          isIndexedColormap: false,
          smaskInData: dict.has("SMaskInData")
        };
        break;
      case "JBIG2Decode":
        image.bitsPerComponent = 1;
        image.numComps = 1;
        break;
    }
    let width = dict.get("W", "Width");
    let height = dict.get("H", "Height");
    if (Number.isInteger(image.width) && image.width > 0 && Number.isInteger(image.height) && image.height > 0 && (image.width !== width || image.height !== height)) {
      warn("PDFImage - using the Width/Height of the image data, " + "rather than the image dictionary.");
      width = image.width;
      height = image.height;
    }
    if (width < 1 || height < 1) {
      throw new FormatError(`Invalid image width: ${width} or height: ${height}`);
    }
    this.width = width;
    this.height = height;
    this.interpolate = dict.get("I", "Interpolate");
    this.imageMask = dict.get("IM", "ImageMask") || false;
    this.matte = dict.get("Matte") || false;
    let bitsPerComponent = image.bitsPerComponent;
    if (!bitsPerComponent) {
      bitsPerComponent = dict.get("BPC", "BitsPerComponent");
      if (!bitsPerComponent) {
        if (this.imageMask) {
          bitsPerComponent = 1;
        } else {
          throw new FormatError(`Bits per component missing in image: ${this.imageMask}`);
        }
      }
    }
    this.bpc = bitsPerComponent;
    if (!this.imageMask) {
      let colorSpace = dict.getRaw("CS") || dict.getRaw("ColorSpace");
      const hasColorSpace = !!colorSpace;
      if (!hasColorSpace) {
        if (this.jpxDecoderOptions) {
          colorSpace = Name.get("DeviceRGBA");
        } else {
          switch (image.numComps) {
            case 1:
              colorSpace = Name.get("DeviceGray");
              break;
            case 3:
              colorSpace = Name.get("DeviceRGB");
              break;
            case 4:
              colorSpace = Name.get("DeviceCMYK");
              break;
            default:
              throw new Error(`Images with ${image.numComps} color components not supported.`);
          }
        }
      } else if (this.jpxDecoderOptions?.smaskInData) {
        colorSpace = Name.get("DeviceRGBA");
      }
      this.colorSpace = ColorSpace.parse({
        cs: colorSpace,
        xref,
        resources: isInline ? res : null,
        pdfFunctionFactory,
        localColorSpaceCache
      });
      this.numComps = this.colorSpace.numComps;
      if (this.jpxDecoderOptions) {
        this.jpxDecoderOptions.numComponents = hasColorSpace ? this.numComp : 0;
        this.jpxDecoderOptions.isIndexedColormap = this.colorSpace.name === "Indexed";
      }
    }
    this.decode = dict.getArray("D", "Decode");
    this.needsDecode = false;
    if (this.decode && (this.colorSpace && !this.colorSpace.isDefaultDecode(this.decode, bitsPerComponent) || isMask && !ColorSpace.isDefaultDecode(this.decode, 1))) {
      this.needsDecode = true;
      const max = (1 << bitsPerComponent) - 1;
      this.decodeCoefficients = [];
      this.decodeAddends = [];
      const isIndexed = this.colorSpace?.name === "Indexed";
      for (let i = 0, j = 0; i < this.decode.length; i += 2, ++j) {
        const dmin = this.decode[i];
        const dmax = this.decode[i + 1];
        this.decodeCoefficients[j] = isIndexed ? (dmax - dmin) / max : dmax - dmin;
        this.decodeAddends[j] = isIndexed ? dmin : max * dmin;
      }
    }
    if (smask) {
      this.smask = new PDFImage({
        xref,
        res,
        image: smask,
        isInline,
        pdfFunctionFactory,
        localColorSpaceCache
      });
    } else if (mask) {
      if (mask instanceof BaseStream) {
        const maskDict = mask.dict,
          imageMask = maskDict.get("IM", "ImageMask");
        if (!imageMask) {
          warn("Ignoring /Mask in image without /ImageMask.");
        } else {
          this.mask = new PDFImage({
            xref,
            res,
            image: mask,
            isInline,
            isMask: true,
            pdfFunctionFactory,
            localColorSpaceCache
          });
        }
      } else {
        this.mask = mask;
      }
    }
  }
  static async buildImage({
    xref,
    res,
    image,
    isInline = false,
    pdfFunctionFactory,
    localColorSpaceCache
  }) {
    const imageData = image;
    let smaskData = null;
    let maskData = null;
    const smask = image.dict.get("SMask");
    const mask = image.dict.get("Mask");
    if (smask) {
      if (smask instanceof BaseStream) {
        smaskData = smask;
      } else {
        warn("Unsupported /SMask format.");
      }
    } else if (mask) {
      if (mask instanceof BaseStream || Array.isArray(mask)) {
        maskData = mask;
      } else {
        warn("Unsupported /Mask format.");
      }
    }
    return new PDFImage({
      xref,
      res,
      image: imageData,
      isInline,
      smask: smaskData,
      mask: maskData,
      pdfFunctionFactory,
      localColorSpaceCache
    });
  }
  static createRawMask({
    imgArray,
    width,
    height,
    imageIsFromDecodeStream,
    inverseDecode,
    interpolate
  }) {
    const computedLength = (width + 7 >> 3) * height;
    const actualLength = imgArray.byteLength;
    const haveFullData = computedLength === actualLength;
    let data, i;
    if (imageIsFromDecodeStream && (!inverseDecode || haveFullData)) {
      data = imgArray;
    } else if (!inverseDecode) {
      data = new Uint8Array(imgArray);
    } else {
      data = new Uint8Array(computedLength);
      data.set(imgArray);
      data.fill(0xff, actualLength);
    }
    if (inverseDecode) {
      for (i = 0; i < actualLength; i++) {
        data[i] ^= 0xff;
      }
    }
    return {
      data,
      width,
      height,
      interpolate
    };
  }
  static async createMask({
    imgArray,
    width,
    height,
    imageIsFromDecodeStream,
    inverseDecode,
    interpolate,
    isOffscreenCanvasSupported = false
  }) {
    const isSingleOpaquePixel = width === 1 && height === 1 && inverseDecode === (imgArray.length === 0 || !!(imgArray[0] & 128));
    if (isSingleOpaquePixel) {
      return {
        isSingleOpaquePixel
      };
    }
    if (isOffscreenCanvasSupported) {
      if (ImageResizer.needsToBeResized(width, height)) {
        const data = new Uint8ClampedArray(width * height * 4);
        convertBlackAndWhiteToRGBA({
          src: imgArray,
          dest: data,
          width,
          height,
          nonBlackColor: 0,
          inverseDecode
        });
        return ImageResizer.createImage({
          kind: ImageKind.RGBA_32BPP,
          data,
          width,
          height,
          interpolate
        });
      }
      const canvas = new OffscreenCanvas(width, height);
      const ctx = canvas.getContext("2d");
      const imgData = ctx.createImageData(width, height);
      convertBlackAndWhiteToRGBA({
        src: imgArray,
        dest: imgData.data,
        width,
        height,
        nonBlackColor: 0,
        inverseDecode
      });
      ctx.putImageData(imgData, 0, 0);
      const bitmap = canvas.transferToImageBitmap();
      return {
        data: null,
        width,
        height,
        interpolate,
        bitmap
      };
    }
    return this.createRawMask({
      imgArray,
      width,
      height,
      inverseDecode,
      imageIsFromDecodeStream,
      interpolate
    });
  }
  get drawWidth() {
    return Math.max(this.width, this.smask?.width || 0, this.mask?.width || 0);
  }
  get drawHeight() {
    return Math.max(this.height, this.smask?.height || 0, this.mask?.height || 0);
  }
  decodeBuffer(buffer) {
    const bpc = this.bpc;
    const numComps = this.numComps;
    const decodeAddends = this.decodeAddends;
    const decodeCoefficients = this.decodeCoefficients;
    const max = (1 << bpc) - 1;
    let i, ii;
    if (bpc === 1) {
      for (i = 0, ii = buffer.length; i < ii; i++) {
        buffer[i] = +!buffer[i];
      }
      return;
    }
    let index = 0;
    for (i = 0, ii = this.width * this.height; i < ii; i++) {
      for (let j = 0; j < numComps; j++) {
        buffer[index] = decodeAndClamp(buffer[index], decodeAddends[j], decodeCoefficients[j], max);
        index++;
      }
    }
  }
  getComponents(buffer) {
    const bpc = this.bpc;
    if (bpc === 8) {
      return buffer;
    }
    const width = this.width;
    const height = this.height;
    const numComps = this.numComps;
    const length = width * height * numComps;
    let bufferPos = 0;
    let output;
    if (bpc <= 8) {
      output = new Uint8Array(length);
    } else if (bpc <= 16) {
      output = new Uint16Array(length);
    } else {
      output = new Uint32Array(length);
    }
    const rowComps = width * numComps;
    const max = (1 << bpc) - 1;
    let i = 0,
      ii,
      buf;
    if (bpc === 1) {
      let mask, loop1End, loop2End;
      for (let j = 0; j < height; j++) {
        loop1End = i + (rowComps & ~7);
        loop2End = i + rowComps;
        while (i < loop1End) {
          buf = buffer[bufferPos++];
          output[i] = buf >> 7 & 1;
          output[i + 1] = buf >> 6 & 1;
          output[i + 2] = buf >> 5 & 1;
          output[i + 3] = buf >> 4 & 1;
          output[i + 4] = buf >> 3 & 1;
          output[i + 5] = buf >> 2 & 1;
          output[i + 6] = buf >> 1 & 1;
          output[i + 7] = buf & 1;
          i += 8;
        }
        if (i < loop2End) {
          buf = buffer[bufferPos++];
          mask = 128;
          while (i < loop2End) {
            output[i++] = +!!(buf & mask);
            mask >>= 1;
          }
        }
      }
    } else {
      let bits = 0;
      buf = 0;
      for (i = 0, ii = length; i < ii; ++i) {
        if (i % rowComps === 0) {
          buf = 0;
          bits = 0;
        }
        while (bits < bpc) {
          buf = buf << 8 | buffer[bufferPos++];
          bits += 8;
        }
        const remainingBits = bits - bpc;
        let value = buf >> remainingBits;
        if (value < 0) {
          value = 0;
        } else if (value > max) {
          value = max;
        }
        output[i] = value;
        buf &= (1 << remainingBits) - 1;
        bits = remainingBits;
      }
    }
    return output;
  }
  async fillOpacity(rgbaBuf, width, height, actualHeight, image) {
    const smask = this.smask;
    const mask = this.mask;
    let alphaBuf, sw, sh, i, ii, j;
    if (smask) {
      sw = smask.width;
      sh = smask.height;
      alphaBuf = new Uint8ClampedArray(sw * sh);
      await smask.fillGrayBuffer(alphaBuf);
      if (sw !== width || sh !== height) {
        alphaBuf = resizeImageMask(alphaBuf, smask.bpc, sw, sh, width, height);
      }
    } else if (mask) {
      if (mask instanceof PDFImage) {
        sw = mask.width;
        sh = mask.height;
        alphaBuf = new Uint8ClampedArray(sw * sh);
        mask.numComps = 1;
        await mask.fillGrayBuffer(alphaBuf);
        for (i = 0, ii = sw * sh; i < ii; ++i) {
          alphaBuf[i] = 255 - alphaBuf[i];
        }
        if (sw !== width || sh !== height) {
          alphaBuf = resizeImageMask(alphaBuf, mask.bpc, sw, sh, width, height);
        }
      } else if (Array.isArray(mask)) {
        alphaBuf = new Uint8ClampedArray(width * height);
        const numComps = this.numComps;
        for (i = 0, ii = width * height; i < ii; ++i) {
          let opacity = 0;
          const imageOffset = i * numComps;
          for (j = 0; j < numComps; ++j) {
            const color = image[imageOffset + j];
            const maskOffset = j * 2;
            if (color < mask[maskOffset] || color > mask[maskOffset + 1]) {
              opacity = 255;
              break;
            }
          }
          alphaBuf[i] = opacity;
        }
      } else {
        throw new FormatError("Unknown mask format.");
      }
    }
    if (alphaBuf) {
      for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) {
        rgbaBuf[j] = alphaBuf[i];
      }
    } else {
      for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) {
        rgbaBuf[j] = 255;
      }
    }
  }
  undoPreblend(buffer, width, height) {
    const matte = this.smask?.matte;
    if (!matte) {
      return;
    }
    const matteRgb = this.colorSpace.getRgb(matte, 0);
    const matteR = matteRgb[0];
    const matteG = matteRgb[1];
    const matteB = matteRgb[2];
    const length = width * height * 4;
    for (let i = 0; i < length; i += 4) {
      const alpha = buffer[i + 3];
      if (alpha === 0) {
        buffer[i] = 255;
        buffer[i + 1] = 255;
        buffer[i + 2] = 255;
        continue;
      }
      const k = 255 / alpha;
      buffer[i] = (buffer[i] - matteR) * k + matteR;
      buffer[i + 1] = (buffer[i + 1] - matteG) * k + matteG;
      buffer[i + 2] = (buffer[i + 2] - matteB) * k + matteB;
    }
  }
  async createImageData(forceRGBA = false, isOffscreenCanvasSupported = false) {
    const drawWidth = this.drawWidth;
    const drawHeight = this.drawHeight;
    const imgData = {
      width: drawWidth,
      height: drawHeight,
      interpolate: this.interpolate,
      kind: 0,
      data: null
    };
    const numComps = this.numComps;
    const originalWidth = this.width;
    const originalHeight = this.height;
    const bpc = this.bpc;
    const rowBytes = originalWidth * numComps * bpc + 7 >> 3;
    const mustBeResized = isOffscreenCanvasSupported && ImageResizer.needsToBeResized(drawWidth, drawHeight);
    if (this.colorSpace.name === "DeviceRGBA") {
      imgData.kind = ImageKind.RGBA_32BPP;
      const imgArray = imgData.data = await this.getImageBytes(originalHeight * originalWidth * 4, {});
      if (isOffscreenCanvasSupported) {
        if (!mustBeResized) {
          return this.createBitmap(ImageKind.RGBA_32BPP, drawWidth, drawHeight, imgArray);
        }
        return ImageResizer.createImage(imgData, false);
      }
      return imgData;
    }
    if (!forceRGBA) {
      let kind;
      if (this.colorSpace.name === "DeviceGray" && bpc === 1) {
        kind = ImageKind.GRAYSCALE_1BPP;
      } else if (this.colorSpace.name === "DeviceRGB" && bpc === 8 && !this.needsDecode) {
        kind = ImageKind.RGB_24BPP;
      }
      if (kind && !this.smask && !this.mask && drawWidth === originalWidth && drawHeight === originalHeight) {
        const data = await this.getImageBytes(originalHeight * rowBytes, {});
        if (isOffscreenCanvasSupported) {
          if (mustBeResized) {
            return ImageResizer.createImage({
              data,
              kind,
              width: drawWidth,
              height: drawHeight,
              interpolate: this.interpolate
            }, this.needsDecode);
          }
          return this.createBitmap(kind, originalWidth, originalHeight, data);
        }
        imgData.kind = kind;
        imgData.data = data;
        if (this.needsDecode) {
          assert(kind === ImageKind.GRAYSCALE_1BPP, "PDFImage.createImageData: The image must be grayscale.");
          const buffer = imgData.data;
          for (let i = 0, ii = buffer.length; i < ii; i++) {
            buffer[i] ^= 0xff;
          }
        }
        return imgData;
      }
      if (this.image instanceof JpegStream && !this.smask && !this.mask && !this.needsDecode) {
        let imageLength = originalHeight * rowBytes;
        if (isOffscreenCanvasSupported && !mustBeResized) {
          let isHandled = false;
          switch (this.colorSpace.name) {
            case "DeviceGray":
              imageLength *= 4;
              isHandled = true;
              break;
            case "DeviceRGB":
              imageLength = imageLength / 3 * 4;
              isHandled = true;
              break;
            case "DeviceCMYK":
              isHandled = true;
              break;
          }
          if (isHandled) {
            const rgba = await this.getImageBytes(imageLength, {
              drawWidth,
              drawHeight,
              forceRGBA: true
            });
            return this.createBitmap(ImageKind.RGBA_32BPP, drawWidth, drawHeight, rgba);
          }
        } else {
          switch (this.colorSpace.name) {
            case "DeviceGray":
              imageLength *= 3;
            case "DeviceRGB":
            case "DeviceCMYK":
              imgData.kind = ImageKind.RGB_24BPP;
              imgData.data = await this.getImageBytes(imageLength, {
                drawWidth,
                drawHeight,
                forceRGB: true
              });
              if (mustBeResized) {
                return ImageResizer.createImage(imgData);
              }
              return imgData;
          }
        }
      }
    }
    const imgArray = await this.getImageBytes(originalHeight * rowBytes, {
      internal: true
    });
    const actualHeight = 0 | imgArray.length / rowBytes * drawHeight / originalHeight;
    const comps = this.getComponents(imgArray);
    let alpha01, maybeUndoPreblend;
    let canvas, ctx, canvasImgData, data;
    if (isOffscreenCanvasSupported && !mustBeResized) {
      canvas = new OffscreenCanvas(drawWidth, drawHeight);
      ctx = canvas.getContext("2d");
      canvasImgData = ctx.createImageData(drawWidth, drawHeight);
      data = canvasImgData.data;
    }
    imgData.kind = ImageKind.RGBA_32BPP;
    if (!forceRGBA && !this.smask && !this.mask) {
      if (!isOffscreenCanvasSupported || mustBeResized) {
        imgData.kind = ImageKind.RGB_24BPP;
        data = new Uint8ClampedArray(drawWidth * drawHeight * 3);
        alpha01 = 0;
      } else {
        const arr = new Uint32Array(data.buffer);
        arr.fill(FeatureTest.isLittleEndian ? 0xff000000 : 0x000000ff);
        alpha01 = 1;
      }
      maybeUndoPreblend = false;
    } else {
      if (!isOffscreenCanvasSupported || mustBeResized) {
        data = new Uint8ClampedArray(drawWidth * drawHeight * 4);
      }
      alpha01 = 1;
      maybeUndoPreblend = true;
      await this.fillOpacity(data, drawWidth, drawHeight, actualHeight, comps);
    }
    if (this.needsDecode) {
      this.decodeBuffer(comps);
    }
    this.colorSpace.fillRgb(data, originalWidth, originalHeight, drawWidth, drawHeight, actualHeight, bpc, comps, alpha01);
    if (maybeUndoPreblend) {
      this.undoPreblend(data, drawWidth, actualHeight);
    }
    if (isOffscreenCanvasSupported && !mustBeResized) {
      ctx.putImageData(canvasImgData, 0, 0);
      const bitmap = canvas.transferToImageBitmap();
      return {
        data: null,
        width: drawWidth,
        height: drawHeight,
        bitmap,
        interpolate: this.interpolate
      };
    }
    imgData.data = data;
    if (mustBeResized) {
      return ImageResizer.createImage(imgData);
    }
    return imgData;
  }
  async fillGrayBuffer(buffer) {
    const numComps = this.numComps;
    if (numComps !== 1) {
      throw new FormatError(`Reading gray scale from a color image: ${numComps}`);
    }
    const width = this.width;
    const height = this.height;
    const bpc = this.bpc;
    const rowBytes = width * numComps * bpc + 7 >> 3;
    const imgArray = await this.getImageBytes(height * rowBytes, {
      internal: true
    });
    const comps = this.getComponents(imgArray);
    let i, length;
    if (bpc === 1) {
      length = width * height;
      if (this.needsDecode) {
        for (i = 0; i < length; ++i) {
          buffer[i] = comps[i] - 1 & 255;
        }
      } else {
        for (i = 0; i < length; ++i) {
          buffer[i] = -comps[i] & 255;
        }
      }
      return;
    }
    if (this.needsDecode) {
      this.decodeBuffer(comps);
    }
    length = width * height;
    const scale = 255 / ((1 << bpc) - 1);
    for (i = 0; i < length; ++i) {
      buffer[i] = scale * comps[i];
    }
  }
  createBitmap(kind, width, height, src) {
    const canvas = new OffscreenCanvas(width, height);
    const ctx = canvas.getContext("2d");
    let imgData;
    if (kind === ImageKind.RGBA_32BPP) {
      imgData = new ImageData(src, width, height);
    } else {
      imgData = ctx.createImageData(width, height);
      convertToRGBA({
        kind,
        src,
        dest: new Uint32Array(imgData.data.buffer),
        width,
        height,
        inverseDecode: this.needsDecode
      });
    }
    ctx.putImageData(imgData, 0, 0);
    const bitmap = canvas.transferToImageBitmap();
    return {
      data: null,
      width,
      height,
      bitmap,
      interpolate: this.interpolate
    };
  }
  async getImageBytes(length, {
    drawWidth,
    drawHeight,
    forceRGBA = false,
    forceRGB = false,
    internal = false
  }) {
    this.image.reset();
    this.image.drawWidth = drawWidth || this.width;
    this.image.drawHeight = drawHeight || this.height;
    this.image.forceRGBA = !!forceRGBA;
    this.image.forceRGB = !!forceRGB;
    const imageBytes = await this.image.getImageData(length, this.jpxDecoderOptions);
    if (internal || this.image instanceof DecodeStream) {
      return imageBytes;
    }
    assert(imageBytes instanceof Uint8Array, 'PDFImage.getImageBytes: Unsupported "imageBytes" type.');
    return new Uint8Array(imageBytes);
  }
}

;// CONCATENATED MODULE: ./src/core/evaluator.js



























const DefaultPartialEvaluatorOptions = Object.freeze({
  maxImageSize: -1,
  disableFontFace: false,
  ignoreErrors: false,
  isEvalSupported: true,
  isOffscreenCanvasSupported: false,
  canvasMaxAreaInBytes: -1,
  fontExtraProperties: false,
  useSystemFonts: true,
  cMapUrl: null,
  standardFontDataUrl: null
});
const PatternType = {
  TILING: 1,
  SHADING: 2
};
const TEXT_CHUNK_BATCH_SIZE = 10;
const deferred = Promise.resolve();
function normalizeBlendMode(value, parsingArray = false) {
  if (Array.isArray(value)) {
    for (const val of value) {
      const maybeBM = normalizeBlendMode(val, true);
      if (maybeBM) {
        return maybeBM;
      }
    }
    warn(`Unsupported blend mode Array: ${value}`);
    return "source-over";
  }
  if (!(value instanceof Name)) {
    if (parsingArray) {
      return null;
    }
    return "source-over";
  }
  switch (value.name) {
    case "Normal":
    case "Compatible":
      return "source-over";
    case "Multiply":
      return "multiply";
    case "Screen":
      return "screen";
    case "Overlay":
      return "overlay";
    case "Darken":
      return "darken";
    case "Lighten":
      return "lighten";
    case "ColorDodge":
      return "color-dodge";
    case "ColorBurn":
      return "color-burn";
    case "HardLight":
      return "hard-light";
    case "SoftLight":
      return "soft-light";
    case "Difference":
      return "difference";
    case "Exclusion":
      return "exclusion";
    case "Hue":
      return "hue";
    case "Saturation":
      return "saturation";
    case "Color":
      return "color";
    case "Luminosity":
      return "luminosity";
  }
  if (parsingArray) {
    return null;
  }
  warn(`Unsupported blend mode: ${value.name}`);
  return "source-over";
}
function addLocallyCachedImageOps(opList, data) {
  if (data.objId) {
    opList.addDependency(data.objId);
  }
  opList.addImageOps(data.fn, data.args, data.optionalContent);
  if (data.fn === OPS.paintImageMaskXObject && data.args[0]?.count > 0) {
    data.args[0].count++;
  }
}
class TimeSlotManager {
  static TIME_SLOT_DURATION_MS = 20;
  static CHECK_TIME_EVERY = 100;
  constructor() {
    this.reset();
  }
  check() {
    if (++this.checked < TimeSlotManager.CHECK_TIME_EVERY) {
      return false;
    }
    this.checked = 0;
    return this.endTime <= Date.now();
  }
  reset() {
    this.endTime = Date.now() + TimeSlotManager.TIME_SLOT_DURATION_MS;
    this.checked = 0;
  }
}
class PartialEvaluator {
  constructor({
    xref,
    handler,
    pageIndex,
    idFactory,
    fontCache,
    builtInCMapCache,
    standardFontDataCache,
    globalImageCache,
    systemFontCache,
    options = null
  }) {
    this.xref = xref;
    this.handler = handler;
    this.pageIndex = pageIndex;
    this.idFactory = idFactory;
    this.fontCache = fontCache;
    this.builtInCMapCache = builtInCMapCache;
    this.standardFontDataCache = standardFontDataCache;
    this.globalImageCache = globalImageCache;
    this.systemFontCache = systemFontCache;
    this.options = options || DefaultPartialEvaluatorOptions;
    this.type3FontRefs = null;
    this._regionalImageCache = new RegionalImageCache();
    this._fetchBuiltInCMapBound = this.fetchBuiltInCMap.bind(this);
    ImageResizer.setMaxArea(this.options.canvasMaxAreaInBytes);
  }
  get _pdfFunctionFactory() {
    const pdfFunctionFactory = new PDFFunctionFactory({
      xref: this.xref,
      isEvalSupported: this.options.isEvalSupported
    });
    return shadow(this, "_pdfFunctionFactory", pdfFunctionFactory);
  }
  get parsingType3Font() {
    return !!this.type3FontRefs;
  }
  clone(newOptions = null) {
    const newEvaluator = Object.create(this);
    newEvaluator.options = Object.assign(Object.create(null), this.options, newOptions);
    return newEvaluator;
  }
  hasBlendModes(resources, nonBlendModesSet) {
    if (!(resources instanceof Dict)) {
      return false;
    }
    if (resources.objId && nonBlendModesSet.has(resources.objId)) {
      return false;
    }
    const processed = new RefSet(nonBlendModesSet);
    if (resources.objId) {
      processed.put(resources.objId);
    }
    const nodes = [resources],
      xref = this.xref;
    while (nodes.length) {
      const node = nodes.shift();
      const graphicStates = node.get("ExtGState");
      if (graphicStates instanceof Dict) {
        for (let graphicState of graphicStates.getRawValues()) {
          if (graphicState instanceof Ref) {
            if (processed.has(graphicState)) {
              continue;
            }
            try {
              graphicState = xref.fetch(graphicState);
            } catch (ex) {
              processed.put(graphicState);
              info(`hasBlendModes - ignoring ExtGState: "${ex}".`);
              continue;
            }
          }
          if (!(graphicState instanceof Dict)) {
            continue;
          }
          if (graphicState.objId) {
            processed.put(graphicState.objId);
          }
          const bm = graphicState.get("BM");
          if (bm instanceof Name) {
            if (bm.name !== "Normal") {
              return true;
            }
            continue;
          }
          if (bm !== undefined && Array.isArray(bm)) {
            for (const element of bm) {
              if (element instanceof Name && element.name !== "Normal") {
                return true;
              }
            }
          }
        }
      }
      const xObjects = node.get("XObject");
      if (!(xObjects instanceof Dict)) {
        continue;
      }
      for (let xObject of xObjects.getRawValues()) {
        if (xObject instanceof Ref) {
          if (processed.has(xObject)) {
            continue;
          }
          try {
            xObject = xref.fetch(xObject);
          } catch (ex) {
            processed.put(xObject);
            info(`hasBlendModes - ignoring XObject: "${ex}".`);
            continue;
          }
        }
        if (!(xObject instanceof BaseStream)) {
          continue;
        }
        if (xObject.dict.objId) {
          processed.put(xObject.dict.objId);
        }
        const xResources = xObject.dict.get("Resources");
        if (!(xResources instanceof Dict)) {
          continue;
        }
        if (xResources.objId && processed.has(xResources.objId)) {
          continue;
        }
        nodes.push(xResources);
        if (xResources.objId) {
          processed.put(xResources.objId);
        }
      }
    }
    for (const ref of processed) {
      nonBlendModesSet.put(ref);
    }
    return false;
  }
  async fetchBuiltInCMap(name) {
    const cachedData = this.builtInCMapCache.get(name);
    if (cachedData) {
      return cachedData;
    }
    let data;
    if (this.options.cMapUrl !== null) {
      const url = `${this.options.cMapUrl}${name}.bcmap`;
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`fetchBuiltInCMap: failed to fetch file "${url}" with "${response.statusText}".`);
      }
      data = {
        cMapData: new Uint8Array(await response.arrayBuffer()),
        compressionType: CMapCompressionType.BINARY
      };
    } else {
      data = await this.handler.sendWithPromise("FetchBuiltInCMap", {
        name
      });
    }
    if (data.compressionType !== CMapCompressionType.NONE) {
      this.builtInCMapCache.set(name, data);
    }
    return data;
  }
  async fetchStandardFontData(name) {
    const cachedData = this.standardFontDataCache.get(name);
    if (cachedData) {
      return new Stream(cachedData);
    }
    if (this.options.useSystemFonts && name !== "Symbol" && name !== "ZapfDingbats") {
      return null;
    }
    const standardFontNameToFileName = getFontNameToFileMap(),
      filename = standardFontNameToFileName[name];
    let data;
    if (this.options.standardFontDataUrl !== null) {
      const url = `${this.options.standardFontDataUrl}${filename}`;
      const response = await fetch(url);
      if (!response.ok) {
        warn(`fetchStandardFontData: failed to fetch file "${url}" with "${response.statusText}".`);
      } else {
        data = new Uint8Array(await response.arrayBuffer());
      }
    } else {
      try {
        data = await this.handler.sendWithPromise("FetchStandardFontData", {
          filename
        });
      } catch (e) {
        warn(`fetchStandardFontData: failed to fetch file "${filename}" with "${e}".`);
      }
    }
    if (!data) {
      return null;
    }
    this.standardFontDataCache.set(name, data);
    return new Stream(data);
  }
  async buildFormXObject(resources, xobj, smask, operatorList, task, initialState, localColorSpaceCache) {
    const dict = xobj.dict;
    const matrix = lookupMatrix(dict.getArray("Matrix"), null);
    const bbox = lookupNormalRect(dict.getArray("BBox"), null);
    let optionalContent, groupOptions;
    if (dict.has("OC")) {
      optionalContent = await this.parseMarkedContentProps(dict.get("OC"), resources);
    }
    if (optionalContent !== undefined) {
      operatorList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
    }
    const group = dict.get("Group");
    if (group) {
      groupOptions = {
        matrix,
        bbox,
        smask,
        isolated: false,
        knockout: false
      };
      const groupSubtype = group.get("S");
      let colorSpace = null;
      if (isName(groupSubtype, "Transparency")) {
        groupOptions.isolated = group.get("I") || false;
        groupOptions.knockout = group.get("K") || false;
        if (group.has("CS")) {
          const cs = group.getRaw("CS");
          const cachedColorSpace = ColorSpace.getCached(cs, this.xref, localColorSpaceCache);
          if (cachedColorSpace) {
            colorSpace = cachedColorSpace;
          } else {
            colorSpace = await this.parseColorSpace({
              cs,
              resources,
              localColorSpaceCache
            });
          }
        }
      }
      if (smask?.backdrop) {
        colorSpace ||= ColorSpace.singletons.rgb;
        smask.backdrop = colorSpace.getRgb(smask.backdrop, 0);
      }
      operatorList.addOp(OPS.beginGroup, [groupOptions]);
    }
    const args = group ? [matrix, null] : [matrix, bbox];
    operatorList.addOp(OPS.paintFormXObjectBegin, args);
    await this.getOperatorList({
      stream: xobj,
      task,
      resources: dict.get("Resources") || resources,
      operatorList,
      initialState
    });
    operatorList.addOp(OPS.paintFormXObjectEnd, []);
    if (group) {
      operatorList.addOp(OPS.endGroup, [groupOptions]);
    }
    if (optionalContent !== undefined) {
      operatorList.addOp(OPS.endMarkedContent, []);
    }
  }
  _sendImgData(objId, imgData, cacheGlobally = false) {
    const transfers = imgData ? [imgData.bitmap || imgData.data.buffer] : null;
    if (this.parsingType3Font || cacheGlobally) {
      return this.handler.send("commonobj", [objId, "Image", imgData], transfers);
    }
    return this.handler.send("obj", [objId, this.pageIndex, "Image", imgData], transfers);
  }
  async buildPaintImageXObject({
    resources,
    image,
    isInline = false,
    operatorList,
    cacheKey,
    localImageCache,
    localColorSpaceCache
  }) {
    const dict = image.dict;
    const imageRef = dict.objId;
    const w = dict.get("W", "Width");
    const h = dict.get("H", "Height");
    if (!(w && typeof w === "number") || !(h && typeof h === "number")) {
      warn("Image dimensions are missing, or not numbers.");
      return;
    }
    const maxImageSize = this.options.maxImageSize;
    if (maxImageSize !== -1 && w * h > maxImageSize) {
      const msg = "Image exceeded maximum allowed size and was removed.";
      if (this.options.ignoreErrors) {
        warn(msg);
        return;
      }
      throw new Error(msg);
    }
    let optionalContent;
    if (dict.has("OC")) {
      optionalContent = await this.parseMarkedContentProps(dict.get("OC"), resources);
    }
    const imageMask = dict.get("IM", "ImageMask") || false;
    let imgData, args;
    if (imageMask) {
      const interpolate = dict.get("I", "Interpolate");
      const bitStrideLength = w + 7 >> 3;
      const imgArray = image.getBytes(bitStrideLength * h);
      const decode = dict.getArray("D", "Decode");
      if (this.parsingType3Font) {
        imgData = PDFImage.createRawMask({
          imgArray,
          width: w,
          height: h,
          imageIsFromDecodeStream: image instanceof DecodeStream,
          inverseDecode: decode?.[0] > 0,
          interpolate
        });
        imgData.cached = !!cacheKey;
        args = [imgData];
        operatorList.addImageOps(OPS.paintImageMaskXObject, args, optionalContent);
        if (cacheKey) {
          const cacheData = {
            fn: OPS.paintImageMaskXObject,
            args,
            optionalContent
          };
          localImageCache.set(cacheKey, imageRef, cacheData);
          if (imageRef) {
            this._regionalImageCache.set(null, imageRef, cacheData);
          }
        }
        return;
      }
      imgData = await PDFImage.createMask({
        imgArray,
        width: w,
        height: h,
        imageIsFromDecodeStream: image instanceof DecodeStream,
        inverseDecode: decode?.[0] > 0,
        interpolate,
        isOffscreenCanvasSupported: this.options.isOffscreenCanvasSupported
      });
      if (imgData.isSingleOpaquePixel) {
        operatorList.addImageOps(OPS.paintSolidColorImageMask, [], optionalContent);
        if (cacheKey) {
          const cacheData = {
            fn: OPS.paintSolidColorImageMask,
            args: [],
            optionalContent
          };
          localImageCache.set(cacheKey, imageRef, cacheData);
          if (imageRef) {
            this._regionalImageCache.set(null, imageRef, cacheData);
          }
        }
        return;
      }
      const objId = `mask_${this.idFactory.createObjId()}`;
      operatorList.addDependency(objId);
      imgData.dataLen = imgData.bitmap ? imgData.width * imgData.height * 4 : imgData.data.length;
      this._sendImgData(objId, imgData);
      args = [{
        data: objId,
        width: imgData.width,
        height: imgData.height,
        interpolate: imgData.interpolate,
        count: 1
      }];
      operatorList.addImageOps(OPS.paintImageMaskXObject, args, optionalContent);
      if (cacheKey) {
        const cacheData = {
          objId,
          fn: OPS.paintImageMaskXObject,
          args,
          optionalContent
        };
        localImageCache.set(cacheKey, imageRef, cacheData);
        if (imageRef) {
          this._regionalImageCache.set(null, imageRef, cacheData);
        }
      }
      return;
    }
    const SMALL_IMAGE_DIMENSIONS = 200;
    if (isInline && w + h < SMALL_IMAGE_DIMENSIONS && !dict.has("SMask") && !dict.has("Mask")) {
      try {
        const imageObj = new PDFImage({
          xref: this.xref,
          res: resources,
          image,
          isInline,
          pdfFunctionFactory: this._pdfFunctionFactory,
          localColorSpaceCache
        });
        imgData = await imageObj.createImageData(true, false);
        operatorList.isOffscreenCanvasSupported = this.options.isOffscreenCanvasSupported;
        operatorList.addImageOps(OPS.paintInlineImageXObject, [imgData], optionalContent);
      } catch (reason) {
        const msg = `Unable to decode inline image: "${reason}".`;
        if (!this.options.ignoreErrors) {
          throw new Error(msg);
        }
        warn(msg);
      }
      return;
    }
    let objId = `img_${this.idFactory.createObjId()}`,
      cacheGlobally = false;
    if (this.parsingType3Font) {
      objId = `${this.idFactory.getDocId()}_type3_${objId}`;
    } else if (cacheKey && imageRef) {
      cacheGlobally = this.globalImageCache.shouldCache(imageRef, this.pageIndex);
      if (cacheGlobally) {
        assert(!isInline, "Cannot cache an inline image globally.");
        objId = `${this.idFactory.getDocId()}_${objId}`;
      }
    }
    operatorList.addDependency(objId);
    args = [objId, w, h];
    operatorList.addImageOps(OPS.paintImageXObject, args, optionalContent);
    if (cacheGlobally) {
      if (this.globalImageCache.hasDecodeFailed(imageRef)) {
        this.globalImageCache.setData(imageRef, {
          objId,
          fn: OPS.paintImageXObject,
          args,
          optionalContent,
          byteSize: 0
        });
        this._sendImgData(objId, null, cacheGlobally);
        return;
      }
      if (w * h > 250000 || dict.has("SMask") || dict.has("Mask")) {
        const localLength = await this.handler.sendWithPromise("commonobj", [objId, "CopyLocalImage", {
          imageRef
        }]);
        if (localLength) {
          this.globalImageCache.setData(imageRef, {
            objId,
            fn: OPS.paintImageXObject,
            args,
            optionalContent,
            byteSize: 0
          });
          this.globalImageCache.addByteSize(imageRef, localLength);
          return;
        }
      }
    }
    PDFImage.buildImage({
      xref: this.xref,
      res: resources,
      image,
      isInline,
      pdfFunctionFactory: this._pdfFunctionFactory,
      localColorSpaceCache
    }).then(async imageObj => {
      imgData = await imageObj.createImageData(false, this.options.isOffscreenCanvasSupported);
      imgData.dataLen = imgData.bitmap ? imgData.width * imgData.height * 4 : imgData.data.length;
      imgData.ref = imageRef;
      if (cacheGlobally) {
        this.globalImageCache.addByteSize(imageRef, imgData.dataLen);
      }
      return this._sendImgData(objId, imgData, cacheGlobally);
    }).catch(reason => {
      warn(`Unable to decode image "${objId}": "${reason}".`);
      if (imageRef) {
        this.globalImageCache.addDecodeFailed(imageRef);
      }
      return this._sendImgData(objId, null, cacheGlobally);
    });
    if (cacheKey) {
      const cacheData = {
        objId,
        fn: OPS.paintImageXObject,
        args,
        optionalContent
      };
      localImageCache.set(cacheKey, imageRef, cacheData);
      if (imageRef) {
        this._regionalImageCache.set(null, imageRef, cacheData);
        if (cacheGlobally) {
          this.globalImageCache.setData(imageRef, {
            objId,
            fn: OPS.paintImageXObject,
            args,
            optionalContent,
            byteSize: 0
          });
        }
      }
    }
  }
  handleSMask(smask, resources, operatorList, task, stateManager, localColorSpaceCache) {
    const smaskContent = smask.get("G");
    const smaskOptions = {
      subtype: smask.get("S").name,
      backdrop: smask.get("BC")
    };
    const transferObj = smask.get("TR");
    if (isPDFFunction(transferObj)) {
      const transferFn = this._pdfFunctionFactory.create(transferObj);
      const transferMap = new Uint8Array(256);
      const tmp = new Float32Array(1);
      for (let i = 0; i < 256; i++) {
        tmp[0] = i / 255;
        transferFn(tmp, 0, tmp, 0);
        transferMap[i] = tmp[0] * 255 | 0;
      }
      smaskOptions.transferMap = transferMap;
    }
    return this.buildFormXObject(resources, smaskContent, smaskOptions, operatorList, task, stateManager.state.clone(), localColorSpaceCache);
  }
  handleTransferFunction(tr) {
    let transferArray;
    if (Array.isArray(tr)) {
      transferArray = tr;
    } else if (isPDFFunction(tr)) {
      transferArray = [tr];
    } else {
      return null;
    }
    const transferMaps = [];
    let numFns = 0,
      numEffectfulFns = 0;
    for (const entry of transferArray) {
      const transferObj = this.xref.fetchIfRef(entry);
      numFns++;
      if (isName(transferObj, "Identity")) {
        transferMaps.push(null);
        continue;
      } else if (!isPDFFunction(transferObj)) {
        return null;
      }
      const transferFn = this._pdfFunctionFactory.create(transferObj);
      const transferMap = new Uint8Array(256),
        tmp = new Float32Array(1);
      for (let j = 0; j < 256; j++) {
        tmp[0] = j / 255;
        transferFn(tmp, 0, tmp, 0);
        transferMap[j] = tmp[0] * 255 | 0;
      }
      transferMaps.push(transferMap);
      numEffectfulFns++;
    }
    if (!(numFns === 1 || numFns === 4)) {
      return null;
    }
    if (numEffectfulFns === 0) {
      return null;
    }
    return transferMaps;
  }
  handleTilingType(fn, color, resources, pattern, patternDict, operatorList, task, localTilingPatternCache) {
    const tilingOpList = new OperatorList();
    const patternResources = Dict.merge({
      xref: this.xref,
      dictArray: [patternDict.get("Resources"), resources]
    });
    return this.getOperatorList({
      stream: pattern,
      task,
      resources: patternResources,
      operatorList: tilingOpList
    }).then(function () {
      const operatorListIR = tilingOpList.getIR();
      const tilingPatternIR = getTilingPatternIR(operatorListIR, patternDict, color);
      operatorList.addDependencies(tilingOpList.dependencies);
      operatorList.addOp(fn, tilingPatternIR);
      if (patternDict.objId) {
        localTilingPatternCache.set(null, patternDict.objId, {
          operatorListIR,
          dict: patternDict
        });
      }
    }).catch(reason => {
      if (reason instanceof AbortException) {
        return;
      }
      if (this.options.ignoreErrors) {
        warn(`handleTilingType - ignoring pattern: "${reason}".`);
        return;
      }
      throw reason;
    });
  }
  async handleSetFont(resources, fontArgs, fontRef, operatorList, task, state, fallbackFontDict = null, cssFontInfo = null) {
    const fontName = fontArgs?.[0] instanceof Name ? fontArgs[0].name : null;
    let translated = await this.loadFont(fontName, fontRef, resources, fallbackFontDict, cssFontInfo);
    if (translated.font.isType3Font) {
      try {
        await translated.loadType3Data(this, resources, task);
        operatorList.addDependencies(translated.type3Dependencies);
      } catch (reason) {
        translated = new TranslatedFont({
          loadedName: "g_font_error",
          font: new ErrorFont(`Type3 font load error: ${reason}`),
          dict: translated.font,
          evaluatorOptions: this.options
        });
      }
    }
    state.font = translated.font;
    translated.send(this.handler);
    return translated.loadedName;
  }
  handleText(chars, state) {
    const font = state.font;
    const glyphs = font.charsToGlyphs(chars);
    if (font.data) {
      const isAddToPathSet = !!(state.textRenderingMode & TextRenderingMode.ADD_TO_PATH_FLAG);
      if (isAddToPathSet || state.fillColorSpace.name === "Pattern" || font.disableFontFace || this.options.disableFontFace) {
        PartialEvaluator.buildFontPaths(font, glyphs, this.handler, this.options);
      }
    }
    return glyphs;
  }
  ensureStateFont(state) {
    if (state.font) {
      return;
    }
    const reason = new FormatError("Missing setFont (Tf) operator before text rendering operator.");
    if (this.options.ignoreErrors) {
      warn(`ensureStateFont: "${reason}".`);
      return;
    }
    throw reason;
  }
  async setGState({
    resources,
    gState,
    operatorList,
    cacheKey,
    task,
    stateManager,
    localGStateCache,
    localColorSpaceCache
  }) {
    const gStateRef = gState.objId;
    let isSimpleGState = true;
    const gStateObj = [];
    let promise = Promise.resolve();
    for (const key of gState.getKeys()) {
      const value = gState.get(key);
      switch (key) {
        case "Type":
          break;
        case "LW":
        case "LC":
        case "LJ":
        case "ML":
        case "D":
        case "RI":
        case "FL":
        case "CA":
        case "ca":
          gStateObj.push([key, value]);
          break;
        case "Font":
          isSimpleGState = false;
          promise = promise.then(() => this.handleSetFont(resources, null, value[0], operatorList, task, stateManager.state).then(function (loadedName) {
            operatorList.addDependency(loadedName);
            gStateObj.push([key, [loadedName, value[1]]]);
          }));
          break;
        case "BM":
          gStateObj.push([key, normalizeBlendMode(value)]);
          break;
        case "SMask":
          if (isName(value, "None")) {
            gStateObj.push([key, false]);
            break;
          }
          if (value instanceof Dict) {
            isSimpleGState = false;
            promise = promise.then(() => this.handleSMask(value, resources, operatorList, task, stateManager, localColorSpaceCache));
            gStateObj.push([key, true]);
          } else {
            warn("Unsupported SMask type");
          }
          break;
        case "TR":
          const transferMaps = this.handleTransferFunction(value);
          gStateObj.push([key, transferMaps]);
          break;
        case "OP":
        case "op":
        case "OPM":
        case "BG":
        case "BG2":
        case "UCR":
        case "UCR2":
        case "TR2":
        case "HT":
        case "SM":
        case "SA":
        case "AIS":
        case "TK":
          info("graphic state operator " + key);
          break;
        default:
          info("Unknown graphic state operator " + key);
          break;
      }
    }
    await promise;
    if (gStateObj.length > 0) {
      operatorList.addOp(OPS.setGState, [gStateObj]);
    }
    if (isSimpleGState) {
      localGStateCache.set(cacheKey, gStateRef, gStateObj);
    }
  }
  loadFont(fontName, font, resources, fallbackFontDict = null, cssFontInfo = null) {
    const errorFont = async () => {
      return new TranslatedFont({
        loadedName: "g_font_error",
        font: new ErrorFont(`Font "${fontName}" is not available.`),
        dict: font,
        evaluatorOptions: this.options
      });
    };
    let fontRef;
    if (font) {
      if (font instanceof Ref) {
        fontRef = font;
      }
    } else {
      const fontRes = resources.get("Font");
      if (fontRes) {
        fontRef = fontRes.getRaw(fontName);
      }
    }
    if (fontRef) {
      if (this.type3FontRefs?.has(fontRef)) {
        return errorFont();
      }
      if (this.fontCache.has(fontRef)) {
        return this.fontCache.get(fontRef);
      }
      try {
        font = this.xref.fetchIfRef(fontRef);
      } catch (ex) {
        warn(`loadFont - lookup failed: "${ex}".`);
      }
    }
    if (!(font instanceof Dict)) {
      if (!this.options.ignoreErrors && !this.parsingType3Font) {
        warn(`Font "${fontName}" is not available.`);
        return errorFont();
      }
      warn(`Font "${fontName}" is not available -- attempting to fallback to a default font.`);
      font = fallbackFontDict || PartialEvaluator.fallbackFontDict;
    }
    if (font.cacheKey && this.fontCache.has(font.cacheKey)) {
      return this.fontCache.get(font.cacheKey);
    }
    const {
      promise,
      resolve
    } = Promise.withResolvers();
    let preEvaluatedFont;
    try {
      preEvaluatedFont = this.preEvaluateFont(font);
      preEvaluatedFont.cssFontInfo = cssFontInfo;
    } catch (reason) {
      warn(`loadFont - preEvaluateFont failed: "${reason}".`);
      return errorFont();
    }
    const {
      descriptor,
      hash
    } = preEvaluatedFont;
    const fontRefIsRef = fontRef instanceof Ref;
    let fontID;
    if (hash && descriptor instanceof Dict) {
      const fontAliases = descriptor.fontAliases ||= Object.create(null);
      if (fontAliases[hash]) {
        const aliasFontRef = fontAliases[hash].aliasRef;
        if (fontRefIsRef && aliasFontRef && this.fontCache.has(aliasFontRef)) {
          this.fontCache.putAlias(fontRef, aliasFontRef);
          return this.fontCache.get(fontRef);
        }
      } else {
        fontAliases[hash] = {
          fontID: this.idFactory.createFontId()
        };
      }
      if (fontRefIsRef) {
        fontAliases[hash].aliasRef = fontRef;
      }
      fontID = fontAliases[hash].fontID;
    } else {
      fontID = this.idFactory.createFontId();
    }
    assert(fontID?.startsWith("f"), 'The "fontID" must be (correctly) defined.');
    if (fontRefIsRef) {
      this.fontCache.put(fontRef, promise);
    } else {
      font.cacheKey = `cacheKey_${fontID}`;
      this.fontCache.put(font.cacheKey, promise);
    }
    font.loadedName = `${this.idFactory.getDocId()}_${fontID}`;
    this.translateFont(preEvaluatedFont).then(translatedFont => {
      resolve(new TranslatedFont({
        loadedName: font.loadedName,
        font: translatedFont,
        dict: font,
        evaluatorOptions: this.options
      }));
    }).catch(reason => {
      warn(`loadFont - translateFont failed: "${reason}".`);
      resolve(new TranslatedFont({
        loadedName: font.loadedName,
        font: new ErrorFont(reason instanceof Error ? reason.message : reason),
        dict: font,
        evaluatorOptions: this.options
      }));
    });
    return promise;
  }
  buildPath(operatorList, fn, args, parsingText = false) {
    const lastIndex = operatorList.length - 1;
    if (!args) {
      args = [];
    }
    if (lastIndex < 0 || operatorList.fnArray[lastIndex] !== OPS.constructPath) {
      if (parsingText) {
        warn(`Encountered path operator "${fn}" inside of a text object.`);
        operatorList.addOp(OPS.save, null);
      }
      let minMax;
      switch (fn) {
        case OPS.rectangle:
          const x = args[0] + args[2];
          const y = args[1] + args[3];
          minMax = [Math.min(args[0], x), Math.min(args[1], y), Math.max(args[0], x), Math.max(args[1], y)];
          break;
        case OPS.moveTo:
        case OPS.lineTo:
          minMax = [args[0], args[1], args[0], args[1]];
          break;
        default:
          minMax = [Infinity, Infinity, -Infinity, -Infinity];
          break;
      }
      operatorList.addOp(OPS.constructPath, [[fn], args, minMax]);
      if (parsingText) {
        operatorList.addOp(OPS.restore, null);
      }
    } else {
      const opArgs = operatorList.argsArray[lastIndex];
      opArgs[0].push(fn);
      opArgs[1].push(...args);
      const minMax = opArgs[2];
      switch (fn) {
        case OPS.rectangle:
          const x = args[0] + args[2];
          const y = args[1] + args[3];
          minMax[0] = Math.min(minMax[0], args[0], x);
          minMax[1] = Math.min(minMax[1], args[1], y);
          minMax[2] = Math.max(minMax[2], args[0], x);
          minMax[3] = Math.max(minMax[3], args[1], y);
          break;
        case OPS.moveTo:
        case OPS.lineTo:
          minMax[0] = Math.min(minMax[0], args[0]);
          minMax[1] = Math.min(minMax[1], args[1]);
          minMax[2] = Math.max(minMax[2], args[0]);
          minMax[3] = Math.max(minMax[3], args[1]);
          break;
      }
    }
  }
  parseColorSpace({
    cs,
    resources,
    localColorSpaceCache
  }) {
    return ColorSpace.parseAsync({
      cs,
      xref: this.xref,
      resources,
      pdfFunctionFactory: this._pdfFunctionFactory,
      localColorSpaceCache
    }).catch(reason => {
      if (reason instanceof AbortException) {
        return null;
      }
      if (this.options.ignoreErrors) {
        warn(`parseColorSpace - ignoring ColorSpace: "${reason}".`);
        return null;
      }
      throw reason;
    });
  }
  parseShading({
    shading,
    resources,
    localColorSpaceCache,
    localShadingPatternCache
  }) {
    let id = localShadingPatternCache.get(shading);
    if (id) {
      return id;
    }
    let patternIR;
    try {
      const shadingFill = Pattern.parseShading(shading, this.xref, resources, this._pdfFunctionFactory, localColorSpaceCache);
      patternIR = shadingFill.getIR();
    } catch (reason) {
      if (reason instanceof AbortException) {
        return null;
      }
      if (this.options.ignoreErrors) {
        warn(`parseShading - ignoring shading: "${reason}".`);
        localShadingPatternCache.set(shading, null);
        return null;
      }
      throw reason;
    }
    id = `pattern_${this.idFactory.createObjId()}`;
    if (this.parsingType3Font) {
      id = `${this.idFactory.getDocId()}_type3_${id}`;
    }
    localShadingPatternCache.set(shading, id);
    if (this.parsingType3Font) {
      this.handler.send("commonobj", [id, "Pattern", patternIR]);
    } else {
      this.handler.send("obj", [id, this.pageIndex, "Pattern", patternIR]);
    }
    return id;
  }
  handleColorN(operatorList, fn, args, cs, patterns, resources, task, localColorSpaceCache, localTilingPatternCache, localShadingPatternCache) {
    const patternName = args.pop();
    if (patternName instanceof Name) {
      const rawPattern = patterns.getRaw(patternName.name);
      const localTilingPattern = rawPattern instanceof Ref && localTilingPatternCache.getByRef(rawPattern);
      if (localTilingPattern) {
        try {
          const color = cs.base ? cs.base.getRgb(args, 0) : null;
          const tilingPatternIR = getTilingPatternIR(localTilingPattern.operatorListIR, localTilingPattern.dict, color);
          operatorList.addOp(fn, tilingPatternIR);
          return undefined;
        } catch {}
      }
      const pattern = this.xref.fetchIfRef(rawPattern);
      if (pattern) {
        const dict = pattern instanceof BaseStream ? pattern.dict : pattern;
        const typeNum = dict.get("PatternType");
        if (typeNum === PatternType.TILING) {
          const color = cs.base ? cs.base.getRgb(args, 0) : null;
          return this.handleTilingType(fn, color, resources, pattern, dict, operatorList, task, localTilingPatternCache);
        } else if (typeNum === PatternType.SHADING) {
          const shading = dict.get("Shading");
          const objId = this.parseShading({
            shading,
            resources,
            localColorSpaceCache,
            localShadingPatternCache
          });
          if (objId) {
            const matrix = lookupMatrix(dict.getArray("Matrix"), null);
            operatorList.addOp(fn, ["Shading", objId, matrix]);
          }
          return undefined;
        }
        throw new FormatError(`Unknown PatternType: ${typeNum}`);
      }
    }
    throw new FormatError(`Unknown PatternName: ${patternName}`);
  }
  _parseVisibilityExpression(array, nestingCounter, currentResult) {
    const MAX_NESTING = 10;
    if (++nestingCounter > MAX_NESTING) {
      warn("Visibility expression is too deeply nested");
      return;
    }
    const length = array.length;
    const operator = this.xref.fetchIfRef(array[0]);
    if (length < 2 || !(operator instanceof Name)) {
      warn("Invalid visibility expression");
      return;
    }
    switch (operator.name) {
      case "And":
      case "Or":
      case "Not":
        currentResult.push(operator.name);
        break;
      default:
        warn(`Invalid operator ${operator.name} in visibility expression`);
        return;
    }
    for (let i = 1; i < length; i++) {
      const raw = array[i];
      const object = this.xref.fetchIfRef(raw);
      if (Array.isArray(object)) {
        const nestedResult = [];
        currentResult.push(nestedResult);
        this._parseVisibilityExpression(object, nestingCounter, nestedResult);
      } else if (raw instanceof Ref) {
        currentResult.push(raw.toString());
      }
    }
  }
  async parseMarkedContentProps(contentProperties, resources) {
    let optionalContent;
    if (contentProperties instanceof Name) {
      const properties = resources.get("Properties");
      optionalContent = properties.get(contentProperties.name);
    } else if (contentProperties instanceof Dict) {
      optionalContent = contentProperties;
    } else {
      throw new FormatError("Optional content properties malformed.");
    }
    const optionalContentType = optionalContent.get("Type")?.name;
    if (optionalContentType === "OCG") {
      return {
        type: optionalContentType,
        id: optionalContent.objId
      };
    } else if (optionalContentType === "OCMD") {
      const expression = optionalContent.get("VE");
      if (Array.isArray(expression)) {
        const result = [];
        this._parseVisibilityExpression(expression, 0, result);
        if (result.length > 0) {
          return {
            type: "OCMD",
            expression: result
          };
        }
      }
      const optionalContentGroups = optionalContent.get("OCGs");
      if (Array.isArray(optionalContentGroups) || optionalContentGroups instanceof Dict) {
        const groupIds = [];
        if (Array.isArray(optionalContentGroups)) {
          for (const ocg of optionalContentGroups) {
            groupIds.push(ocg.toString());
          }
        } else {
          groupIds.push(optionalContentGroups.objId);
        }
        return {
          type: optionalContentType,
          ids: groupIds,
          policy: optionalContent.get("P") instanceof Name ? optionalContent.get("P").name : null,
          expression: null
        };
      } else if (optionalContentGroups instanceof Ref) {
        return {
          type: optionalContentType,
          id: optionalContentGroups.toString()
        };
      }
    }
    return null;
  }
  getOperatorList({
    stream,
    task,
    resources,
    operatorList,
    initialState = null,
    fallbackFontDict = null
  }) {
    resources ||= Dict.empty;
    initialState ||= new EvalState();
    if (!operatorList) {
      throw new Error('getOperatorList: missing "operatorList" parameter');
    }
    const self = this;
    const xref = this.xref;
    let parsingText = false;
    const localImageCache = new LocalImageCache();
    const localColorSpaceCache = new LocalColorSpaceCache();
    const localGStateCache = new LocalGStateCache();
    const localTilingPatternCache = new LocalTilingPatternCache();
    const localShadingPatternCache = new Map();
    const xobjs = resources.get("XObject") || Dict.empty;
    const patterns = resources.get("Pattern") || Dict.empty;
    const stateManager = new StateManager(initialState);
    const preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
    const timeSlotManager = new TimeSlotManager();
    function closePendingRestoreOPS(argument) {
      for (let i = 0, ii = preprocessor.savedStatesDepth; i < ii; i++) {
        operatorList.addOp(OPS.restore, []);
      }
    }
    return new Promise(function promiseBody(resolve, reject) {
      const next = function (promise) {
        Promise.all([promise, operatorList.ready]).then(function () {
          try {
            promiseBody(resolve, reject);
          } catch (ex) {
            reject(ex);
          }
        }, reject);
      };
      task.ensureNotTerminated();
      timeSlotManager.reset();
      const operation = {};
      let stop, i, ii, cs, name, isValidName;
      while (!(stop = timeSlotManager.check())) {
        operation.args = null;
        if (!preprocessor.read(operation)) {
          break;
        }
        let args = operation.args;
        let fn = operation.fn;
        switch (fn | 0) {
          case OPS.paintXObject:
            isValidName = args[0] instanceof Name;
            name = args[0].name;
            if (isValidName) {
              const localImage = localImageCache.getByName(name);
              if (localImage) {
                addLocallyCachedImageOps(operatorList, localImage);
                args = null;
                continue;
              }
            }
            next(new Promise(function (resolveXObject, rejectXObject) {
              if (!isValidName) {
                throw new FormatError("XObject must be referred to by name.");
              }
              let xobj = xobjs.getRaw(name);
              if (xobj instanceof Ref) {
                const localImage = localImageCache.getByRef(xobj) || self._regionalImageCache.getByRef(xobj);
                if (localImage) {
                  addLocallyCachedImageOps(operatorList, localImage);
                  resolveXObject();
                  return;
                }
                const globalImage = self.globalImageCache.getData(xobj, self.pageIndex);
                if (globalImage) {
                  operatorList.addDependency(globalImage.objId);
                  operatorList.addImageOps(globalImage.fn, globalImage.args, globalImage.optionalContent);
                  resolveXObject();
                  return;
                }
                xobj = xref.fetch(xobj);
              }
              if (!(xobj instanceof BaseStream)) {
                throw new FormatError("XObject should be a stream");
              }
              const type = xobj.dict.get("Subtype");
              if (!(type instanceof Name)) {
                throw new FormatError("XObject should have a Name subtype");
              }
              if (type.name === "Form") {
                stateManager.save();
                self.buildFormXObject(resources, xobj, null, operatorList, task, stateManager.state.clone(), localColorSpaceCache).then(function () {
                  stateManager.restore();
                  resolveXObject();
                }, rejectXObject);
                return;
              } else if (type.name === "Image") {
                self.buildPaintImageXObject({
                  resources,
                  image: xobj,
                  operatorList,
                  cacheKey: name,
                  localImageCache,
                  localColorSpaceCache
                }).then(resolveXObject, rejectXObject);
                return;
              } else if (type.name === "PS") {
                info("Ignored XObject subtype PS");
              } else {
                throw new FormatError(`Unhandled XObject subtype ${type.name}`);
              }
              resolveXObject();
            }).catch(function (reason) {
              if (reason instanceof AbortException) {
                return;
              }
              if (self.options.ignoreErrors) {
                warn(`getOperatorList - ignoring XObject: "${reason}".`);
                return;
              }
              throw reason;
            }));
            return;
          case OPS.setFont:
            var fontSize = args[1];
            next(self.handleSetFont(resources, args, null, operatorList, task, stateManager.state, fallbackFontDict).then(function (loadedName) {
              operatorList.addDependency(loadedName);
              operatorList.addOp(OPS.setFont, [loadedName, fontSize]);
            }));
            return;
          case OPS.beginText:
            parsingText = true;
            break;
          case OPS.endText:
            parsingText = false;
            break;
          case OPS.endInlineImage:
            var cacheKey = args[0].cacheKey;
            if (cacheKey) {
              const localImage = localImageCache.getByName(cacheKey);
              if (localImage) {
                addLocallyCachedImageOps(operatorList, localImage);
                args = null;
                continue;
              }
            }
            next(self.buildPaintImageXObject({
              resources,
              image: args[0],
              isInline: true,
              operatorList,
              cacheKey,
              localImageCache,
              localColorSpaceCache
            }));
            return;
          case OPS.showText:
            if (!stateManager.state.font) {
              self.ensureStateFont(stateManager.state);
              continue;
            }
            args[0] = self.handleText(args[0], stateManager.state);
            break;
          case OPS.showSpacedText:
            if (!stateManager.state.font) {
              self.ensureStateFont(stateManager.state);
              continue;
            }
            var combinedGlyphs = [];
            var state = stateManager.state;
            for (const arrItem of args[0]) {
              if (typeof arrItem === "string") {
                combinedGlyphs.push(...self.handleText(arrItem, state));
              } else if (typeof arrItem === "number") {
                combinedGlyphs.push(arrItem);
              }
            }
            args[0] = combinedGlyphs;
            fn = OPS.showText;
            break;
          case OPS.nextLineShowText:
            if (!stateManager.state.font) {
              self.ensureStateFont(stateManager.state);
              continue;
            }
            operatorList.addOp(OPS.nextLine);
            args[0] = self.handleText(args[0], stateManager.state);
            fn = OPS.showText;
            break;
          case OPS.nextLineSetSpacingShowText:
            if (!stateManager.state.font) {
              self.ensureStateFont(stateManager.state);
              continue;
            }
            operatorList.addOp(OPS.nextLine);
            operatorList.addOp(OPS.setWordSpacing, [args.shift()]);
            operatorList.addOp(OPS.setCharSpacing, [args.shift()]);
            args[0] = self.handleText(args[0], stateManager.state);
            fn = OPS.showText;
            break;
          case OPS.setTextRenderingMode:
            stateManager.state.textRenderingMode = args[0];
            break;
          case OPS.setFillColorSpace:
            {
              const cachedColorSpace = ColorSpace.getCached(args[0], xref, localColorSpaceCache);
              if (cachedColorSpace) {
                stateManager.state.fillColorSpace = cachedColorSpace;
                continue;
              }
              next(self.parseColorSpace({
                cs: args[0],
                resources,
                localColorSpaceCache
              }).then(function (colorSpace) {
                stateManager.state.fillColorSpace = colorSpace || ColorSpace.singletons.gray;
              }));
              return;
            }
          case OPS.setStrokeColorSpace:
            {
              const cachedColorSpace = ColorSpace.getCached(args[0], xref, localColorSpaceCache);
              if (cachedColorSpace) {
                stateManager.state.strokeColorSpace = cachedColorSpace;
                continue;
              }
              next(self.parseColorSpace({
                cs: args[0],
                resources,
                localColorSpaceCache
              }).then(function (colorSpace) {
                stateManager.state.strokeColorSpace = colorSpace || ColorSpace.singletons.gray;
              }));
              return;
            }
          case OPS.setFillColor:
            cs = stateManager.state.fillColorSpace;
            args = cs.getRgb(args, 0);
            fn = OPS.setFillRGBColor;
            break;
          case OPS.setStrokeColor:
            cs = stateManager.state.strokeColorSpace;
            args = cs.getRgb(args, 0);
            fn = OPS.setStrokeRGBColor;
            break;
          case OPS.setFillGray:
            stateManager.state.fillColorSpace = ColorSpace.singletons.gray;
            args = ColorSpace.singletons.gray.getRgb(args, 0);
            fn = OPS.setFillRGBColor;
            break;
          case OPS.setStrokeGray:
            stateManager.state.strokeColorSpace = ColorSpace.singletons.gray;
            args = ColorSpace.singletons.gray.getRgb(args, 0);
            fn = OPS.setStrokeRGBColor;
            break;
          case OPS.setFillCMYKColor:
            stateManager.state.fillColorSpace = ColorSpace.singletons.cmyk;
            args = ColorSpace.singletons.cmyk.getRgb(args, 0);
            fn = OPS.setFillRGBColor;
            break;
          case OPS.setStrokeCMYKColor:
            stateManager.state.strokeColorSpace = ColorSpace.singletons.cmyk;
            args = ColorSpace.singletons.cmyk.getRgb(args, 0);
            fn = OPS.setStrokeRGBColor;
            break;
          case OPS.setFillRGBColor:
            stateManager.state.fillColorSpace = ColorSpace.singletons.rgb;
            args = ColorSpace.singletons.rgb.getRgb(args, 0);
            break;
          case OPS.setStrokeRGBColor:
            stateManager.state.strokeColorSpace = ColorSpace.singletons.rgb;
            args = ColorSpace.singletons.rgb.getRgb(args, 0);
            break;
          case OPS.setFillColorN:
            cs = stateManager.state.patternFillColorSpace;
            if (!cs) {
              args = [];
              fn = OPS.setFillTransparent;
              break;
            }
            if (cs.name === "Pattern") {
              next(self.handleColorN(operatorList, OPS.setFillColorN, args, cs, patterns, resources, task, localColorSpaceCache, localTilingPatternCache, localShadingPatternCache));
              return;
            }
            args = cs.getRgb(args, 0);
            fn = OPS.setFillRGBColor;
            break;
          case OPS.setStrokeColorN:
            cs = stateManager.state.patternStrokeColorSpace;
            if (!cs) {
              args = [];
              fn = OPS.setStrokeTransparent;
              break;
            }
            if (cs.name === "Pattern") {
              next(self.handleColorN(operatorList, OPS.setStrokeColorN, args, cs, patterns, resources, task, localColorSpaceCache, localTilingPatternCache, localShadingPatternCache));
              return;
            }
            args = cs.getRgb(args, 0);
            fn = OPS.setStrokeRGBColor;
            break;
          case OPS.shadingFill:
            var shadingRes = resources.get("Shading");
            if (!shadingRes) {
              throw new FormatError("No shading resource found");
            }
            var shading = shadingRes.get(args[0].name);
            if (!shading) {
              throw new FormatError("No shading object found");
            }
            const patternId = self.parseShading({
              shading,
              resources,
              localColorSpaceCache,
              localShadingPatternCache
            });
            if (!patternId) {
              continue;
            }
            args = [patternId];
            fn = OPS.shadingFill;
            break;
          case OPS.setGState:
            isValidName = args[0] instanceof Name;
            name = args[0].name;
            if (isValidName) {
              const localGStateObj = localGStateCache.getByName(name);
              if (localGStateObj) {
                if (localGStateObj.length > 0) {
                  operatorList.addOp(OPS.setGState, [localGStateObj]);
                }
                args = null;
                continue;
              }
            }
            next(new Promise(function (resolveGState, rejectGState) {
              if (!isValidName) {
                throw new FormatError("GState must be referred to by name.");
              }
              const extGState = resources.get("ExtGState");
              if (!(extGState instanceof Dict)) {
                throw new FormatError("ExtGState should be a dictionary.");
              }
              const gState = extGState.get(name);
              if (!(gState instanceof Dict)) {
                throw new FormatError("GState should be a dictionary.");
              }
              self.setGState({
                resources,
                gState,
                operatorList,
                cacheKey: name,
                task,
                stateManager,
                localGStateCache,
                localColorSpaceCache
              }).then(resolveGState, rejectGState);
            }).catch(function (reason) {
              if (reason instanceof AbortException) {
                return;
              }
              if (self.options.ignoreErrors) {
                warn(`getOperatorList - ignoring ExtGState: "${reason}".`);
                return;
              }
              throw reason;
            }));
            return;
          case OPS.moveTo:
          case OPS.lineTo:
          case OPS.curveTo:
          case OPS.curveTo2:
          case OPS.curveTo3:
          case OPS.closePath:
          case OPS.rectangle:
            self.buildPath(operatorList, fn, args, parsingText);
            continue;
          case OPS.markPoint:
          case OPS.markPointProps:
          case OPS.beginCompat:
          case OPS.endCompat:
            continue;
          case OPS.beginMarkedContentProps:
            if (!(args[0] instanceof Name)) {
              warn(`Expected name for beginMarkedContentProps arg0=${args[0]}`);
              operatorList.addOp(OPS.beginMarkedContentProps, ["OC", null]);
              continue;
            }
            if (args[0].name === "OC") {
              next(self.parseMarkedContentProps(args[1], resources).then(data => {
                operatorList.addOp(OPS.beginMarkedContentProps, ["OC", data]);
              }).catch(reason => {
                if (reason instanceof AbortException) {
                  return;
                }
                if (self.options.ignoreErrors) {
                  warn(`getOperatorList - ignoring beginMarkedContentProps: "${reason}".`);
                  operatorList.addOp(OPS.beginMarkedContentProps, ["OC", null]);
                  return;
                }
                throw reason;
              }));
              return;
            }
            args = [args[0].name, args[1] instanceof Dict ? args[1].get("MCID") : null];
            break;
          case OPS.beginMarkedContent:
          case OPS.endMarkedContent:
          default:
            if (args !== null) {
              for (i = 0, ii = args.length; i < ii; i++) {
                if (args[i] instanceof Dict) {
                  break;
                }
              }
              if (i < ii) {
                warn("getOperatorList - ignoring operator: " + fn);
                continue;
              }
            }
        }
        operatorList.addOp(fn, args);
      }
      if (stop) {
        next(deferred);
        return;
      }
      closePendingRestoreOPS();
      resolve();
    }).catch(reason => {
      if (reason instanceof AbortException) {
        return;
      }
      if (this.options.ignoreErrors) {
        warn(`getOperatorList - ignoring errors during "${task.name}" ` + `task: "${reason}".`);
        closePendingRestoreOPS();
        return;
      }
      throw reason;
    });
  }
  getTextContent({
    stream,
    task,
    resources,
    stateManager = null,
    includeMarkedContent = false,
    sink,
    seenStyles = new Set(),
    viewBox,
    lang = null,
    markedContentData = null,
    disableNormalization = false,
    keepWhiteSpace = false
  }) {
    resources ||= Dict.empty;
    stateManager ||= new StateManager(new TextState());
    if (includeMarkedContent) {
      markedContentData ||= {
        level: 0
      };
    }
    const textContent = {
      items: [],
      styles: Object.create(null),
      lang
    };
    const textContentItem = {
      initialized: false,
      str: [],
      totalWidth: 0,
      totalHeight: 0,
      width: 0,
      height: 0,
      vertical: false,
      prevTransform: null,
      textAdvanceScale: 0,
      spaceInFlowMin: 0,
      spaceInFlowMax: 0,
      trackingSpaceMin: Infinity,
      negativeSpaceMax: -Infinity,
      notASpace: -Infinity,
      transform: null,
      fontName: null,
      hasEOL: false
    };
    const twoLastChars = [" ", " "];
    let twoLastCharsPos = 0;
    function saveLastChar(char) {
      const nextPos = (twoLastCharsPos + 1) % 2;
      const ret = twoLastChars[twoLastCharsPos] !== " " && twoLastChars[nextPos] === " ";
      twoLastChars[twoLastCharsPos] = char;
      twoLastCharsPos = nextPos;
      return !keepWhiteSpace && ret;
    }
    function shouldAddWhitepsace() {
      return !keepWhiteSpace && twoLastChars[twoLastCharsPos] !== " " && twoLastChars[(twoLastCharsPos + 1) % 2] === " ";
    }
    function resetLastChars() {
      twoLastChars[0] = twoLastChars[1] = " ";
      twoLastCharsPos = 0;
    }
    const TRACKING_SPACE_FACTOR = 0.102;
    const NOT_A_SPACE_FACTOR = 0.03;
    const NEGATIVE_SPACE_FACTOR = -0.2;
    const SPACE_IN_FLOW_MIN_FACTOR = 0.102;
    const SPACE_IN_FLOW_MAX_FACTOR = 0.6;
    const VERTICAL_SHIFT_RATIO = 0.25;
    const self = this;
    const xref = this.xref;
    const showSpacedTextBuffer = [];
    let xobjs = null;
    const emptyXObjectCache = new LocalImageCache();
    const emptyGStateCache = new LocalGStateCache();
    const preprocessor = new EvaluatorPreprocessor(stream, xref, stateManager);
    let textState;
    function pushWhitespace({
      width = 0,
      height = 0,
      transform = textContentItem.prevTransform,
      fontName = textContentItem.fontName
    }) {
      textContent.items.push({
        str: " ",
        dir: "ltr",
        width,
        height,
        transform,
        fontName,
        hasEOL: false
      });
    }
    function getCurrentTextTransform() {
      const font = textState.font;
      const tsm = [textState.fontSize * textState.textHScale, 0, 0, textState.fontSize, 0, textState.textRise];
      if (font.isType3Font && (textState.fontSize <= 1 || font.isCharBBox) && !isArrayEqual(textState.fontMatrix, FONT_IDENTITY_MATRIX)) {
        const glyphHeight = font.bbox[3] - font.bbox[1];
        if (glyphHeight > 0) {
          tsm[3] *= glyphHeight * textState.fontMatrix[3];
        }
      }
      return Util.transform(textState.ctm, Util.transform(textState.textMatrix, tsm));
    }
    function ensureTextContentItem() {
      if (textContentItem.initialized) {
        return textContentItem;
      }
      const {
        font,
        loadedName
      } = textState;
      if (!seenStyles.has(loadedName)) {
        seenStyles.add(loadedName);
        textContent.styles[loadedName] = {
          fontFamily: font.fallbackName,
          ascent: font.ascent,
          descent: font.descent,
          vertical: font.vertical
        };
        if (self.options.fontExtraProperties && font.systemFontInfo) {
          const style = textContent.styles[loadedName];
          style.fontSubstitution = font.systemFontInfo.css;
          style.fontSubstitutionLoadedName = font.systemFontInfo.loadedName;
        }
      }
      textContentItem.fontName = loadedName;
      const trm = textContentItem.transform = getCurrentTextTransform();
      if (!font.vertical) {
        textContentItem.width = textContentItem.totalWidth = 0;
        textContentItem.height = textContentItem.totalHeight = Math.hypot(trm[2], trm[3]);
        textContentItem.vertical = false;
      } else {
        textContentItem.width = textContentItem.totalWidth = Math.hypot(trm[0], trm[1]);
        textContentItem.height = textContentItem.totalHeight = 0;
        textContentItem.vertical = true;
      }
      const scaleLineX = Math.hypot(textState.textLineMatrix[0], textState.textLineMatrix[1]);
      const scaleCtmX = Math.hypot(textState.ctm[0], textState.ctm[1]);
      textContentItem.textAdvanceScale = scaleCtmX * scaleLineX;
      const {
        fontSize
      } = textState;
      textContentItem.trackingSpaceMin = fontSize * TRACKING_SPACE_FACTOR;
      textContentItem.notASpace = fontSize * NOT_A_SPACE_FACTOR;
      textContentItem.negativeSpaceMax = fontSize * NEGATIVE_SPACE_FACTOR;
      textContentItem.spaceInFlowMin = fontSize * SPACE_IN_FLOW_MIN_FACTOR;
      textContentItem.spaceInFlowMax = fontSize * SPACE_IN_FLOW_MAX_FACTOR;
      textContentItem.hasEOL = false;
      textContentItem.initialized = true;
      return textContentItem;
    }
    function updateAdvanceScale() {
      if (!textContentItem.initialized) {
        return;
      }
      const scaleLineX = Math.hypot(textState.textLineMatrix[0], textState.textLineMatrix[1]);
      const scaleCtmX = Math.hypot(textState.ctm[0], textState.ctm[1]);
      const scaleFactor = scaleCtmX * scaleLineX;
      if (scaleFactor === textContentItem.textAdvanceScale) {
        return;
      }
      if (!textContentItem.vertical) {
        textContentItem.totalWidth += textContentItem.width * textContentItem.textAdvanceScale;
        textContentItem.width = 0;
      } else {
        textContentItem.totalHeight += textContentItem.height * textContentItem.textAdvanceScale;
        textContentItem.height = 0;
      }
      textContentItem.textAdvanceScale = scaleFactor;
    }
    function runBidiTransform(textChunk) {
      let text = textChunk.str.join("");
      if (!disableNormalization) {
        text = normalizeUnicode(text);
      }
      const bidiResult = bidi(text, -1, textChunk.vertical);
      return {
        str: bidiResult.str,
        dir: bidiResult.dir,
        width: Math.abs(textChunk.totalWidth),
        height: Math.abs(textChunk.totalHeight),
        transform: textChunk.transform,
        fontName: textChunk.fontName,
        hasEOL: textChunk.hasEOL
      };
    }
    async function handleSetFont(fontName, fontRef) {
      const translated = await self.loadFont(fontName, fontRef, resources);
      if (translated.font.isType3Font) {
        try {
          await translated.loadType3Data(self, resources, task);
        } catch {}
      }
      textState.loadedName = translated.loadedName;
      textState.font = translated.font;
      textState.fontMatrix = translated.font.fontMatrix || FONT_IDENTITY_MATRIX;
    }
    function applyInverseRotation(x, y, matrix) {
      const scale = Math.hypot(matrix[0], matrix[1]);
      return [(matrix[0] * x + matrix[1] * y) / scale, (matrix[2] * x + matrix[3] * y) / scale];
    }
    function compareWithLastPosition(glyphWidth) {
      const currentTransform = getCurrentTextTransform();
      let posX = currentTransform[4];
      let posY = currentTransform[5];
      if (textState.font?.vertical) {
        if (posX < viewBox[0] || posX > viewBox[2] || posY + glyphWidth < viewBox[1] || posY > viewBox[3]) {
          return false;
        }
      } else if (posX + glyphWidth < viewBox[0] || posX > viewBox[2] || posY < viewBox[1] || posY > viewBox[3]) {
        return false;
      }
      if (!textState.font || !textContentItem.prevTransform) {
        return true;
      }
      let lastPosX = textContentItem.prevTransform[4];
      let lastPosY = textContentItem.prevTransform[5];
      if (lastPosX === posX && lastPosY === posY) {
        return true;
      }
      let rotate = -1;
      if (currentTransform[0] && currentTransform[1] === 0 && currentTransform[2] === 0) {
        rotate = currentTransform[0] > 0 ? 0 : 180;
      } else if (currentTransform[1] && currentTransform[0] === 0 && currentTransform[3] === 0) {
        rotate = currentTransform[1] > 0 ? 90 : 270;
      }
      switch (rotate) {
        case 0:
          break;
        case 90:
          [posX, posY] = [posY, posX];
          [lastPosX, lastPosY] = [lastPosY, lastPosX];
          break;
        case 180:
          [posX, posY, lastPosX, lastPosY] = [-posX, -posY, -lastPosX, -lastPosY];
          break;
        case 270:
          [posX, posY] = [-posY, -posX];
          [lastPosX, lastPosY] = [-lastPosY, -lastPosX];
          break;
        default:
          [posX, posY] = applyInverseRotation(posX, posY, currentTransform);
          [lastPosX, lastPosY] = applyInverseRotation(lastPosX, lastPosY, textContentItem.prevTransform);
      }
      if (textState.font.vertical) {
        const advanceY = (lastPosY - posY) / textContentItem.textAdvanceScale;
        const advanceX = posX - lastPosX;
        const textOrientation = Math.sign(textContentItem.height);
        if (advanceY < textOrientation * textContentItem.negativeSpaceMax) {
          if (Math.abs(advanceX) > 0.5 * textContentItem.width) {
            appendEOL();
            return true;
          }
          resetLastChars();
          flushTextContentItem();
          return true;
        }
        if (Math.abs(advanceX) > textContentItem.width) {
          appendEOL();
          return true;
        }
        if (advanceY <= textOrientation * textContentItem.notASpace) {
          resetLastChars();
        }
        if (advanceY <= textOrientation * textContentItem.trackingSpaceMin) {
          if (shouldAddWhitepsace()) {
            resetLastChars();
            flushTextContentItem();
            pushWhitespace({
              height: Math.abs(advanceY)
            });
          } else {
            textContentItem.height += advanceY;
          }
        } else if (!addFakeSpaces(advanceY, textContentItem.prevTransform, textOrientation)) {
          if (textContentItem.str.length === 0) {
            resetLastChars();
            pushWhitespace({
              height: Math.abs(advanceY)
            });
          } else {
            textContentItem.height += advanceY;
          }
        }
        if (Math.abs(advanceX) > textContentItem.width * VERTICAL_SHIFT_RATIO) {
          flushTextContentItem();
        }
        return true;
      }
      const advanceX = (posX - lastPosX) / textContentItem.textAdvanceScale;
      const advanceY = posY - lastPosY;
      const textOrientation = Math.sign(textContentItem.width);
      if (advanceX < textOrientation * textContentItem.negativeSpaceMax) {
        if (Math.abs(advanceY) > 0.5 * textContentItem.height) {
          appendEOL();
          return true;
        }
        resetLastChars();
        flushTextContentItem();
        return true;
      }
      if (Math.abs(advanceY) > textContentItem.height) {
        appendEOL();
        return true;
      }
      if (advanceX <= textOrientation * textContentItem.notASpace) {
        resetLastChars();
      }
      if (advanceX <= textOrientation * textContentItem.trackingSpaceMin) {
        if (shouldAddWhitepsace()) {
          resetLastChars();
          flushTextContentItem();
          pushWhitespace({
            width: Math.abs(advanceX)
          });
        } else {
          textContentItem.width += advanceX;
        }
      } else if (!addFakeSpaces(advanceX, textContentItem.prevTransform, textOrientation)) {
        if (textContentItem.str.length === 0) {
          resetLastChars();
          pushWhitespace({
            width: Math.abs(advanceX)
          });
        } else {
          textContentItem.width += advanceX;
        }
      }
      if (Math.abs(advanceY) > textContentItem.height * VERTICAL_SHIFT_RATIO) {
        flushTextContentItem();
      }
      return true;
    }
    function buildTextContentItem({
      chars,
      extraSpacing
    }) {
      const font = textState.font;
      if (!chars) {
        const charSpacing = textState.charSpacing + extraSpacing;
        if (charSpacing) {
          if (!font.vertical) {
            textState.translateTextMatrix(charSpacing * textState.textHScale, 0);
          } else {
            textState.translateTextMatrix(0, -charSpacing);
          }
        }
        if (keepWhiteSpace) {
          compareWithLastPosition(0);
        }
        return;
      }
      const glyphs = font.charsToGlyphs(chars);
      const scale = textState.fontMatrix[0] * textState.fontSize;
      for (let i = 0, ii = glyphs.length; i < ii; i++) {
        const glyph = glyphs[i];
        const {
          category
        } = glyph;
        if (category.isInvisibleFormatMark) {
          continue;
        }
        let charSpacing = textState.charSpacing + (i + 1 === ii ? extraSpacing : 0);
        let glyphWidth = glyph.width;
        if (font.vertical) {
          glyphWidth = glyph.vmetric ? glyph.vmetric[0] : -glyphWidth;
        }
        let scaledDim = glyphWidth * scale;
        if (!keepWhiteSpace && category.isWhitespace) {
          if (!font.vertical) {
            charSpacing += scaledDim + textState.wordSpacing;
            textState.translateTextMatrix(charSpacing * textState.textHScale, 0);
          } else {
            charSpacing += -scaledDim + textState.wordSpacing;
            textState.translateTextMatrix(0, -charSpacing);
          }
          saveLastChar(" ");
          continue;
        }
        if (!category.isZeroWidthDiacritic && !compareWithLastPosition(scaledDim)) {
          if (!font.vertical) {
            textState.translateTextMatrix(scaledDim * textState.textHScale, 0);
          } else {
            textState.translateTextMatrix(0, scaledDim);
          }
          continue;
        }
        const textChunk = ensureTextContentItem();
        if (category.isZeroWidthDiacritic) {
          scaledDim = 0;
        }
        if (!font.vertical) {
          scaledDim *= textState.textHScale;
          textState.translateTextMatrix(scaledDim, 0);
          textChunk.width += scaledDim;
        } else {
          textState.translateTextMatrix(0, scaledDim);
          scaledDim = Math.abs(scaledDim);
          textChunk.height += scaledDim;
        }
        if (scaledDim) {
          textChunk.prevTransform = getCurrentTextTransform();
        }
        const glyphUnicode = glyph.unicode;
        if (saveLastChar(glyphUnicode)) {
          textChunk.str.push(" ");
        }
        textChunk.str.push(glyphUnicode);
        if (charSpacing) {
          if (!font.vertical) {
            textState.translateTextMatrix(charSpacing * textState.textHScale, 0);
          } else {
            textState.translateTextMatrix(0, -charSpacing);
          }
        }
      }
    }
    function appendEOL() {
      resetLastChars();
      if (textContentItem.initialized) {
        textContentItem.hasEOL = true;
        flushTextContentItem();
      } else {
        textContent.items.push({
          str: "",
          dir: "ltr",
          width: 0,
          height: 0,
          transform: getCurrentTextTransform(),
          fontName: textState.loadedName,
          hasEOL: true
        });
      }
    }
    function addFakeSpaces(width, transf, textOrientation) {
      if (textOrientation * textContentItem.spaceInFlowMin <= width && width <= textOrientation * textContentItem.spaceInFlowMax) {
        if (textContentItem.initialized) {
          resetLastChars();
          textContentItem.str.push(" ");
        }
        return false;
      }
      const fontName = textContentItem.fontName;
      let height = 0;
      if (textContentItem.vertical) {
        height = width;
        width = 0;
      }
      flushTextContentItem();
      resetLastChars();
      pushWhitespace({
        width: Math.abs(width),
        height: Math.abs(height),
        transform: transf || getCurrentTextTransform(),
        fontName
      });
      return true;
    }
    function flushTextContentItem() {
      if (!textContentItem.initialized || !textContentItem.str) {
        return;
      }
      if (!textContentItem.vertical) {
        textContentItem.totalWidth += textContentItem.width * textContentItem.textAdvanceScale;
      } else {
        textContentItem.totalHeight += textContentItem.height * textContentItem.textAdvanceScale;
      }
      textContent.items.push(runBidiTransform(textContentItem));
      textContentItem.initialized = false;
      textContentItem.str.length = 0;
    }
    function enqueueChunk(batch = false) {
      const length = textContent.items.length;
      if (length === 0) {
        return;
      }
      if (batch && length < TEXT_CHUNK_BATCH_SIZE) {
        return;
      }
      sink.enqueue(textContent, length);
      textContent.items = [];
      textContent.styles = Object.create(null);
    }
    const timeSlotManager = new TimeSlotManager();
    return new Promise(function promiseBody(resolve, reject) {
      const next = function (promise) {
        enqueueChunk(true);
        Promise.all([promise, sink.ready]).then(function () {
          try {
            promiseBody(resolve, reject);
          } catch (ex) {
            reject(ex);
          }
        }, reject);
      };
      task.ensureNotTerminated();
      timeSlotManager.reset();
      const operation = {};
      let stop,
        args = [];
      while (!(stop = timeSlotManager.check())) {
        args.length = 0;
        operation.args = args;
        if (!preprocessor.read(operation)) {
          break;
        }
        const previousState = textState;
        textState = stateManager.state;
        const fn = operation.fn;
        args = operation.args;
        switch (fn | 0) {
          case OPS.setFont:
            var fontNameArg = args[0].name,
              fontSizeArg = args[1];
            if (textState.font && fontNameArg === textState.fontName && fontSizeArg === textState.fontSize) {
              break;
            }
            flushTextContentItem();
            textState.fontName = fontNameArg;
            textState.fontSize = fontSizeArg;
            next(handleSetFont(fontNameArg, null));
            return;
          case OPS.setTextRise:
            textState.textRise = args[0];
            break;
          case OPS.setHScale:
            textState.textHScale = args[0] / 100;
            break;
          case OPS.setLeading:
            textState.leading = args[0];
            break;
          case OPS.moveText:
            textState.translateTextLineMatrix(args[0], args[1]);
            textState.textMatrix = textState.textLineMatrix.slice();
            break;
          case OPS.setLeadingMoveText:
            textState.leading = -args[1];
            textState.translateTextLineMatrix(args[0], args[1]);
            textState.textMatrix = textState.textLineMatrix.slice();
            break;
          case OPS.nextLine:
            textState.carriageReturn();
            break;
          case OPS.setTextMatrix:
            textState.setTextMatrix(args[0], args[1], args[2], args[3], args[4], args[5]);
            textState.setTextLineMatrix(args[0], args[1], args[2], args[3], args[4], args[5]);
            updateAdvanceScale();
            break;
          case OPS.setCharSpacing:
            textState.charSpacing = args[0];
            break;
          case OPS.setWordSpacing:
            textState.wordSpacing = args[0];
            break;
          case OPS.beginText:
            textState.textMatrix = IDENTITY_MATRIX.slice();
            textState.textLineMatrix = IDENTITY_MATRIX.slice();
            break;
          case OPS.showSpacedText:
            if (!stateManager.state.font) {
              self.ensureStateFont(stateManager.state);
              continue;
            }
            const spaceFactor = (textState.font.vertical ? 1 : -1) * textState.fontSize / 1000;
            const elements = args[0];
            for (let i = 0, ii = elements.length; i < ii; i++) {
              const item = elements[i];
              if (typeof item === "string") {
                showSpacedTextBuffer.push(item);
              } else if (typeof item === "number" && item !== 0) {
                const str = showSpacedTextBuffer.join("");
                showSpacedTextBuffer.length = 0;
                buildTextContentItem({
                  chars: str,
                  extraSpacing: item * spaceFactor
                });
              }
            }
            if (showSpacedTextBuffer.length > 0) {
              const str = showSpacedTextBuffer.join("");
              showSpacedTextBuffer.length = 0;
              buildTextContentItem({
                chars: str,
                extraSpacing: 0
              });
            }
            break;
          case OPS.showText:
            if (!stateManager.state.font) {
              self.ensureStateFont(stateManager.state);
              continue;
            }
            buildTextContentItem({
              chars: args[0],
              extraSpacing: 0
            });
            break;
          case OPS.nextLineShowText:
            if (!stateManager.state.font) {
              self.ensureStateFont(stateManager.state);
              continue;
            }
            textState.carriageReturn();
            buildTextContentItem({
              chars: args[0],
              extraSpacing: 0
            });
            break;
          case OPS.nextLineSetSpacingShowText:
            if (!stateManager.state.font) {
              self.ensureStateFont(stateManager.state);
              continue;
            }
            textState.wordSpacing = args[0];
            textState.charSpacing = args[1];
            textState.carriageReturn();
            buildTextContentItem({
              chars: args[2],
              extraSpacing: 0
            });
            break;
          case OPS.paintXObject:
            flushTextContentItem();
            if (!xobjs) {
              xobjs = resources.get("XObject") || Dict.empty;
            }
            var isValidName = args[0] instanceof Name;
            var name = args[0].name;
            if (isValidName && emptyXObjectCache.getByName(name)) {
              break;
            }
            next(new Promise(function (resolveXObject, rejectXObject) {
              if (!isValidName) {
                throw new FormatError("XObject must be referred to by name.");
              }
              let xobj = xobjs.getRaw(name);
              if (xobj instanceof Ref) {
                if (emptyXObjectCache.getByRef(xobj)) {
                  resolveXObject();
                  return;
                }
                const globalImage = self.globalImageCache.getData(xobj, self.pageIndex);
                if (globalImage) {
                  resolveXObject();
                  return;
                }
                xobj = xref.fetch(xobj);
              }
              if (!(xobj instanceof BaseStream)) {
                throw new FormatError("XObject should be a stream");
              }
              const type = xobj.dict.get("Subtype");
              if (!(type instanceof Name)) {
                throw new FormatError("XObject should have a Name subtype");
              }
              if (type.name !== "Form") {
                emptyXObjectCache.set(name, xobj.dict.objId, true);
                resolveXObject();
                return;
              }
              const currentState = stateManager.state.clone();
              const xObjStateManager = new StateManager(currentState);
              const matrix = lookupMatrix(xobj.dict.getArray("Matrix"), null);
              if (matrix) {
                xObjStateManager.transform(matrix);
              }
              enqueueChunk();
              const sinkWrapper = {
                enqueueInvoked: false,
                enqueue(chunk, size) {
                  this.enqueueInvoked = true;
                  sink.enqueue(chunk, size);
                },
                get desiredSize() {
                  return sink.desiredSize;
                },
                get ready() {
                  return sink.ready;
                }
              };
              self.getTextContent({
                stream: xobj,
                task,
                resources: xobj.dict.get("Resources") || resources,
                stateManager: xObjStateManager,
                includeMarkedContent,
                sink: sinkWrapper,
                seenStyles,
                viewBox,
                lang,
                markedContentData,
                disableNormalization,
                keepWhiteSpace
              }).then(function () {
                if (!sinkWrapper.enqueueInvoked) {
                  emptyXObjectCache.set(name, xobj.dict.objId, true);
                }
                resolveXObject();
              }, rejectXObject);
            }).catch(function (reason) {
              if (reason instanceof AbortException) {
                return;
              }
              if (self.options.ignoreErrors) {
                warn(`getTextContent - ignoring XObject: "${reason}".`);
                return;
              }
              throw reason;
            }));
            return;
          case OPS.setGState:
            isValidName = args[0] instanceof Name;
            name = args[0].name;
            if (isValidName && emptyGStateCache.getByName(name)) {
              break;
            }
            next(new Promise(function (resolveGState, rejectGState) {
              if (!isValidName) {
                throw new FormatError("GState must be referred to by name.");
              }
              const extGState = resources.get("ExtGState");
              if (!(extGState instanceof Dict)) {
                throw new FormatError("ExtGState should be a dictionary.");
              }
              const gState = extGState.get(name);
              if (!(gState instanceof Dict)) {
                throw new FormatError("GState should be a dictionary.");
              }
              const gStateFont = gState.get("Font");
              if (!gStateFont) {
                emptyGStateCache.set(name, gState.objId, true);
                resolveGState();
                return;
              }
              flushTextContentItem();
              textState.fontName = null;
              textState.fontSize = gStateFont[1];
              handleSetFont(null, gStateFont[0]).then(resolveGState, rejectGState);
            }).catch(function (reason) {
              if (reason instanceof AbortException) {
                return;
              }
              if (self.options.ignoreErrors) {
                warn(`getTextContent - ignoring ExtGState: "${reason}".`);
                return;
              }
              throw reason;
            }));
            return;
          case OPS.beginMarkedContent:
            flushTextContentItem();
            if (includeMarkedContent) {
              markedContentData.level++;
              textContent.items.push({
                type: "beginMarkedContent",
                tag: args[0] instanceof Name ? args[0].name : null
              });
            }
            break;
          case OPS.beginMarkedContentProps:
            flushTextContentItem();
            if (includeMarkedContent) {
              markedContentData.level++;
              let mcid = null;
              if (args[1] instanceof Dict) {
                mcid = args[1].get("MCID");
              }
              textContent.items.push({
                type: "beginMarkedContentProps",
                id: Number.isInteger(mcid) ? `${self.idFactory.getPageObjId()}_mc${mcid}` : null,
                tag: args[0] instanceof Name ? args[0].name : null
              });
            }
            break;
          case OPS.endMarkedContent:
            flushTextContentItem();
            if (includeMarkedContent) {
              if (markedContentData.level === 0) {
                break;
              }
              markedContentData.level--;
              textContent.items.push({
                type: "endMarkedContent"
              });
            }
            break;
          case OPS.restore:
            if (previousState && (previousState.font !== textState.font || previousState.fontSize !== textState.fontSize || previousState.fontName !== textState.fontName)) {
              flushTextContentItem();
            }
            break;
        }
        if (textContent.items.length >= sink.desiredSize) {
          stop = true;
          break;
        }
      }
      if (stop) {
        next(deferred);
        return;
      }
      flushTextContentItem();
      enqueueChunk();
      resolve();
    }).catch(reason => {
      if (reason instanceof AbortException) {
        return;
      }
      if (this.options.ignoreErrors) {
        warn(`getTextContent - ignoring errors during "${task.name}" ` + `task: "${reason}".`);
        flushTextContentItem();
        enqueueChunk();
        return;
      }
      throw reason;
    });
  }
  async extractDataStructures(dict, properties) {
    const xref = this.xref;
    let cidToGidBytes;
    const toUnicodePromise = this.readToUnicode(properties.toUnicode);
    if (properties.composite) {
      const cidSystemInfo = dict.get("CIDSystemInfo");
      if (cidSystemInfo instanceof Dict) {
        properties.cidSystemInfo = {
          registry: stringToPDFString(cidSystemInfo.get("Registry")),
          ordering: stringToPDFString(cidSystemInfo.get("Ordering")),
          supplement: cidSystemInfo.get("Supplement")
        };
      }
      try {
        const cidToGidMap = dict.get("CIDToGIDMap");
        if (cidToGidMap instanceof BaseStream) {
          cidToGidBytes = cidToGidMap.getBytes();
        }
      } catch (ex) {
        if (!this.options.ignoreErrors) {
          throw ex;
        }
        warn(`extractDataStructures - ignoring CIDToGIDMap data: "${ex}".`);
      }
    }
    const differences = [];
    let baseEncodingName = null;
    let encoding;
    if (dict.has("Encoding")) {
      encoding = dict.get("Encoding");
      if (encoding instanceof Dict) {
        baseEncodingName = encoding.get("BaseEncoding");
        baseEncodingName = baseEncodingName instanceof Name ? baseEncodingName.name : null;
        if (encoding.has("Differences")) {
          const diffEncoding = encoding.get("Differences");
          let index = 0;
          for (const entry of diffEncoding) {
            const data = xref.fetchIfRef(entry);
            if (typeof data === "number") {
              index = data;
            } else if (data instanceof Name) {
              differences[index++] = data.name;
            } else {
              throw new FormatError(`Invalid entry in 'Differences' array: ${data}`);
            }
          }
        }
      } else if (encoding instanceof Name) {
        baseEncodingName = encoding.name;
      } else {
        const msg = "Encoding is not a Name nor a Dict";
        if (!this.options.ignoreErrors) {
          throw new FormatError(msg);
        }
        warn(msg);
      }
      if (baseEncodingName !== "MacRomanEncoding" && baseEncodingName !== "MacExpertEncoding" && baseEncodingName !== "WinAnsiEncoding") {
        baseEncodingName = null;
      }
    }
    const nonEmbeddedFont = !properties.file || properties.isInternalFont,
      isSymbolsFontName = getSymbolsFonts()[properties.name];
    if (baseEncodingName && nonEmbeddedFont && isSymbolsFontName) {
      baseEncodingName = null;
    }
    if (baseEncodingName) {
      properties.defaultEncoding = getEncoding(baseEncodingName);
    } else {
      const isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
      const isNonsymbolicFont = !!(properties.flags & FontFlags.Nonsymbolic);
      encoding = StandardEncoding;
      if (properties.type === "TrueType" && !isNonsymbolicFont) {
        encoding = WinAnsiEncoding;
      }
      if (isSymbolicFont || isSymbolsFontName) {
        encoding = MacRomanEncoding;
        if (nonEmbeddedFont) {
          if (/Symbol/i.test(properties.name)) {
            encoding = SymbolSetEncoding;
          } else if (/Dingbats/i.test(properties.name)) {
            encoding = ZapfDingbatsEncoding;
          } else if (/Wingdings/i.test(properties.name)) {
            encoding = WinAnsiEncoding;
          }
        }
      }
      properties.defaultEncoding = encoding;
    }
    properties.differences = differences;
    properties.baseEncodingName = baseEncodingName;
    properties.hasEncoding = !!baseEncodingName || differences.length > 0;
    properties.dict = dict;
    properties.toUnicode = await toUnicodePromise;
    const builtToUnicode = await this.buildToUnicode(properties);
    properties.toUnicode = builtToUnicode;
    if (cidToGidBytes) {
      properties.cidToGidMap = this.readCidToGidMap(cidToGidBytes, builtToUnicode);
    }
    return properties;
  }
  _simpleFontToUnicode(properties, forceGlyphs = false) {
    assert(!properties.composite, "Must be a simple font.");
    const toUnicode = [];
    const encoding = properties.defaultEncoding.slice();
    const baseEncodingName = properties.baseEncodingName;
    const differences = properties.differences;
    for (const charcode in differences) {
      const glyphName = differences[charcode];
      if (glyphName === ".notdef") {
        continue;
      }
      encoding[charcode] = glyphName;
    }
    const glyphsUnicodeMap = getGlyphsUnicode();
    for (const charcode in encoding) {
      let glyphName = encoding[charcode];
      if (glyphName === "") {
        continue;
      }
      let unicode = glyphsUnicodeMap[glyphName];
      if (unicode !== undefined) {
        toUnicode[charcode] = String.fromCharCode(unicode);
        continue;
      }
      let code = 0;
      switch (glyphName[0]) {
        case "G":
          if (glyphName.length === 3) {
            code = parseInt(glyphName.substring(1), 16);
          }
          break;
        case "g":
          if (glyphName.length === 5) {
            code = parseInt(glyphName.substring(1), 16);
          }
          break;
        case "C":
        case "c":
          if (glyphName.length >= 3 && glyphName.length <= 4) {
            const codeStr = glyphName.substring(1);
            if (forceGlyphs) {
              code = parseInt(codeStr, 16);
              break;
            }
            code = +codeStr;
            if (Number.isNaN(code) && Number.isInteger(parseInt(codeStr, 16))) {
              return this._simpleFontToUnicode(properties, true);
            }
          }
          break;
        case "u":
          unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
          if (unicode !== -1) {
            code = unicode;
          }
          break;
        default:
          switch (glyphName) {
            case "f_h":
            case "f_t":
            case "T_h":
              toUnicode[charcode] = glyphName.replaceAll("_", "");
              continue;
          }
          break;
      }
      if (code > 0 && code <= 0x10ffff && Number.isInteger(code)) {
        if (baseEncodingName && code === +charcode) {
          const baseEncoding = getEncoding(baseEncodingName);
          if (baseEncoding && (glyphName = baseEncoding[charcode])) {
            toUnicode[charcode] = String.fromCharCode(glyphsUnicodeMap[glyphName]);
            continue;
          }
        }
        toUnicode[charcode] = String.fromCodePoint(code);
      }
    }
    return toUnicode;
  }
  async buildToUnicode(properties) {
    properties.hasIncludedToUnicodeMap = properties.toUnicode?.length > 0;
    if (properties.hasIncludedToUnicodeMap) {
      if (!properties.composite && properties.hasEncoding) {
        properties.fallbackToUnicode = this._simpleFontToUnicode(properties);
      }
      return properties.toUnicode;
    }
    if (!properties.composite) {
      return new ToUnicodeMap(this._simpleFontToUnicode(properties));
    }
    if (properties.composite && (properties.cMap.builtInCMap && !(properties.cMap instanceof IdentityCMap) || properties.cidSystemInfo?.registry === "Adobe" && (properties.cidSystemInfo.ordering === "GB1" || properties.cidSystemInfo.ordering === "CNS1" || properties.cidSystemInfo.ordering === "Japan1" || properties.cidSystemInfo.ordering === "Korea1"))) {
      const {
        registry,
        ordering
      } = properties.cidSystemInfo;
      const ucs2CMapName = Name.get(`${registry}-${ordering}-UCS2`);
      const ucs2CMap = await CMapFactory.create({
        encoding: ucs2CMapName,
        fetchBuiltInCMap: this._fetchBuiltInCMapBound,
        useCMap: null
      });
      const toUnicode = [],
        buf = [];
      properties.cMap.forEach(function (charcode, cid) {
        if (cid > 0xffff) {
          throw new FormatError("Max size of CID is 65,535");
        }
        const ucs2 = ucs2CMap.lookup(cid);
        if (ucs2) {
          buf.length = 0;
          for (let i = 0, ii = ucs2.length; i < ii; i += 2) {
            buf.push((ucs2.charCodeAt(i) << 8) + ucs2.charCodeAt(i + 1));
          }
          toUnicode[charcode] = String.fromCharCode(...buf);
        }
      });
      return new ToUnicodeMap(toUnicode);
    }
    return new IdentityToUnicodeMap(properties.firstChar, properties.lastChar);
  }
  async readToUnicode(cmapObj) {
    if (!cmapObj) {
      return null;
    }
    if (cmapObj instanceof Name) {
      const cmap = await CMapFactory.create({
        encoding: cmapObj,
        fetchBuiltInCMap: this._fetchBuiltInCMapBound,
        useCMap: null
      });
      if (cmap instanceof IdentityCMap) {
        return new IdentityToUnicodeMap(0, 0xffff);
      }
      return new ToUnicodeMap(cmap.getMap());
    }
    if (cmapObj instanceof BaseStream) {
      try {
        const cmap = await CMapFactory.create({
          encoding: cmapObj,
          fetchBuiltInCMap: this._fetchBuiltInCMapBound,
          useCMap: null
        });
        if (cmap instanceof IdentityCMap) {
          return new IdentityToUnicodeMap(0, 0xffff);
        }
        const map = new Array(cmap.length);
        cmap.forEach(function (charCode, token) {
          if (typeof token === "number") {
            map[charCode] = String.fromCodePoint(token);
            return;
          }
          if (token.length % 2 !== 0) {
            token = "\u0000" + token;
          }
          const str = [];
          for (let k = 0; k < token.length; k += 2) {
            const w1 = token.charCodeAt(k) << 8 | token.charCodeAt(k + 1);
            if ((w1 & 0xf800) !== 0xd800) {
              str.push(w1);
              continue;
            }
            k += 2;
            const w2 = token.charCodeAt(k) << 8 | token.charCodeAt(k + 1);
            str.push(((w1 & 0x3ff) << 10) + (w2 & 0x3ff) + 0x10000);
          }
          map[charCode] = String.fromCodePoint(...str);
        });
        return new ToUnicodeMap(map);
      } catch (reason) {
        if (reason instanceof AbortException) {
          return null;
        }
        if (this.options.ignoreErrors) {
          warn(`readToUnicode - ignoring ToUnicode data: "${reason}".`);
          return null;
        }
        throw reason;
      }
    }
    return null;
  }
  readCidToGidMap(glyphsData, toUnicode) {
    const result = [];
    for (let j = 0, jj = glyphsData.length; j < jj; j++) {
      const glyphID = glyphsData[j++] << 8 | glyphsData[j];
      const code = j >> 1;
      if (glyphID === 0 && !toUnicode.has(code)) {
        continue;
      }
      result[code] = glyphID;
    }
    return result;
  }
  extractWidths(dict, descriptor, properties) {
    const xref = this.xref;
    let glyphsWidths = [];
    let defaultWidth = 0;
    const glyphsVMetrics = [];
    let defaultVMetrics;
    if (properties.composite) {
      const dw = dict.get("DW");
      defaultWidth = typeof dw === "number" ? Math.ceil(dw) : 1000;
      const widths = dict.get("W");
      if (Array.isArray(widths)) {
        for (let i = 0, ii = widths.length; i < ii; i++) {
          let start = xref.fetchIfRef(widths[i++]);
          if (!Number.isInteger(start)) {
            break;
          }
          const code = xref.fetchIfRef(widths[i]);
          if (Array.isArray(code)) {
            for (const c of code) {
              const width = xref.fetchIfRef(c);
              if (typeof width === "number") {
                glyphsWidths[start] = width;
              }
              start++;
            }
          } else if (Number.isInteger(code)) {
            const width = xref.fetchIfRef(widths[++i]);
            if (typeof width !== "number") {
              continue;
            }
            for (let j = start; j <= code; j++) {
              glyphsWidths[j] = width;
            }
          } else {
            break;
          }
        }
      }
      if (properties.vertical) {
        const dw2 = dict.getArray("DW2");
        let vmetrics = isNumberArray(dw2, 2) ? dw2 : [880, -1000];
        defaultVMetrics = [vmetrics[1], defaultWidth * 0.5, vmetrics[0]];
        vmetrics = dict.get("W2");
        if (Array.isArray(vmetrics)) {
          for (let i = 0, ii = vmetrics.length; i < ii; i++) {
            let start = xref.fetchIfRef(vmetrics[i++]);
            if (!Number.isInteger(start)) {
              break;
            }
            const code = xref.fetchIfRef(vmetrics[i]);
            if (Array.isArray(code)) {
              for (let j = 0, jj = code.length; j < jj; j++) {
                const vmetric = [xref.fetchIfRef(code[j++]), xref.fetchIfRef(code[j++]), xref.fetchIfRef(code[j])];
                if (isNumberArray(vmetric, null)) {
                  glyphsVMetrics[start] = vmetric;
                }
                start++;
              }
            } else if (Number.isInteger(code)) {
              const vmetric = [xref.fetchIfRef(vmetrics[++i]), xref.fetchIfRef(vmetrics[++i]), xref.fetchIfRef(vmetrics[++i])];
              if (!isNumberArray(vmetric, null)) {
                continue;
              }
              for (let j = start; j <= code; j++) {
                glyphsVMetrics[j] = vmetric;
              }
            } else {
              break;
            }
          }
        }
      }
    } else {
      const widths = dict.get("Widths");
      if (Array.isArray(widths)) {
        let j = properties.firstChar;
        for (const w of widths) {
          const width = xref.fetchIfRef(w);
          if (typeof width === "number") {
            glyphsWidths[j] = width;
          }
          j++;
        }
        const missingWidth = descriptor.get("MissingWidth");
        defaultWidth = typeof missingWidth === "number" ? missingWidth : 0;
      } else {
        const baseFontName = dict.get("BaseFont");
        if (baseFontName instanceof Name) {
          const metrics = this.getBaseFontMetrics(baseFontName.name);
          glyphsWidths = this.buildCharCodeToWidth(metrics.widths, properties);
          defaultWidth = metrics.defaultWidth;
        }
      }
    }
    let isMonospace = true;
    let firstWidth = defaultWidth;
    for (const glyph in glyphsWidths) {
      const glyphWidth = glyphsWidths[glyph];
      if (!glyphWidth) {
        continue;
      }
      if (!firstWidth) {
        firstWidth = glyphWidth;
        continue;
      }
      if (firstWidth !== glyphWidth) {
        isMonospace = false;
        break;
      }
    }
    if (isMonospace) {
      properties.flags |= FontFlags.FixedPitch;
    } else {
      properties.flags &= ~FontFlags.FixedPitch;
    }
    properties.defaultWidth = defaultWidth;
    properties.widths = glyphsWidths;
    properties.defaultVMetrics = defaultVMetrics;
    properties.vmetrics = glyphsVMetrics;
  }
  isSerifFont(baseFontName) {
    const fontNameWoStyle = baseFontName.split("-", 1)[0];
    return fontNameWoStyle in getSerifFonts() || /serif/gi.test(fontNameWoStyle);
  }
  getBaseFontMetrics(name) {
    let defaultWidth = 0;
    let widths = Object.create(null);
    let monospace = false;
    const stdFontMap = getStdFontMap();
    let lookupName = stdFontMap[name] || name;
    const Metrics = getMetrics();
    if (!(lookupName in Metrics)) {
      lookupName = this.isSerifFont(name) ? "Times-Roman" : "Helvetica";
    }
    const glyphWidths = Metrics[lookupName];
    if (typeof glyphWidths === "number") {
      defaultWidth = glyphWidths;
      monospace = true;
    } else {
      widths = glyphWidths();
    }
    return {
      defaultWidth,
      monospace,
      widths
    };
  }
  buildCharCodeToWidth(widthsByGlyphName, properties) {
    const widths = Object.create(null);
    const differences = properties.differences;
    const encoding = properties.defaultEncoding;
    for (let charCode = 0; charCode < 256; charCode++) {
      if (charCode in differences && widthsByGlyphName[differences[charCode]]) {
        widths[charCode] = widthsByGlyphName[differences[charCode]];
        continue;
      }
      if (charCode in encoding && widthsByGlyphName[encoding[charCode]]) {
        widths[charCode] = widthsByGlyphName[encoding[charCode]];
        continue;
      }
    }
    return widths;
  }
  preEvaluateFont(dict) {
    const baseDict = dict;
    let type = dict.get("Subtype");
    if (!(type instanceof Name)) {
      throw new FormatError("invalid font Subtype");
    }
    let composite = false;
    let hash;
    if (type.name === "Type0") {
      const df = dict.get("DescendantFonts");
      if (!df) {
        throw new FormatError("Descendant fonts are not specified");
      }
      dict = Array.isArray(df) ? this.xref.fetchIfRef(df[0]) : df;
      if (!(dict instanceof Dict)) {
        throw new FormatError("Descendant font is not a dictionary.");
      }
      type = dict.get("Subtype");
      if (!(type instanceof Name)) {
        throw new FormatError("invalid font Subtype");
      }
      composite = true;
    }
    let firstChar = dict.get("FirstChar");
    if (!Number.isInteger(firstChar)) {
      firstChar = 0;
    }
    let lastChar = dict.get("LastChar");
    if (!Number.isInteger(lastChar)) {
      lastChar = composite ? 0xffff : 0xff;
    }
    const descriptor = dict.get("FontDescriptor");
    const toUnicode = dict.get("ToUnicode") || baseDict.get("ToUnicode");
    if (descriptor) {
      hash = new MurmurHash3_64();
      const encoding = baseDict.getRaw("Encoding");
      if (encoding instanceof Name) {
        hash.update(encoding.name);
      } else if (encoding instanceof Ref) {
        hash.update(encoding.toString());
      } else if (encoding instanceof Dict) {
        for (const entry of encoding.getRawValues()) {
          if (entry instanceof Name) {
            hash.update(entry.name);
          } else if (entry instanceof Ref) {
            hash.update(entry.toString());
          } else if (Array.isArray(entry)) {
            const diffLength = entry.length,
              diffBuf = new Array(diffLength);
            for (let j = 0; j < diffLength; j++) {
              const diffEntry = entry[j];
              if (diffEntry instanceof Name) {
                diffBuf[j] = diffEntry.name;
              } else if (typeof diffEntry === "number" || diffEntry instanceof Ref) {
                diffBuf[j] = diffEntry.toString();
              }
            }
            hash.update(diffBuf.join());
          }
        }
      }
      hash.update(`${firstChar}-${lastChar}`);
      if (toUnicode instanceof BaseStream) {
        const stream = toUnicode.str || toUnicode;
        const uint8array = stream.buffer ? new Uint8Array(stream.buffer.buffer, 0, stream.bufferLength) : new Uint8Array(stream.bytes.buffer, stream.start, stream.end - stream.start);
        hash.update(uint8array);
      } else if (toUnicode instanceof Name) {
        hash.update(toUnicode.name);
      }
      const widths = dict.get("Widths") || baseDict.get("Widths");
      if (Array.isArray(widths)) {
        const widthsBuf = [];
        for (const entry of widths) {
          if (typeof entry === "number" || entry instanceof Ref) {
            widthsBuf.push(entry.toString());
          }
        }
        hash.update(widthsBuf.join());
      }
      if (composite) {
        hash.update("compositeFont");
        const compositeWidths = dict.get("W") || baseDict.get("W");
        if (Array.isArray(compositeWidths)) {
          const widthsBuf = [];
          for (const entry of compositeWidths) {
            if (typeof entry === "number" || entry instanceof Ref) {
              widthsBuf.push(entry.toString());
            } else if (Array.isArray(entry)) {
              const subWidthsBuf = [];
              for (const element of entry) {
                if (typeof element === "number" || element instanceof Ref) {
                  subWidthsBuf.push(element.toString());
                }
              }
              widthsBuf.push(`[${subWidthsBuf.join()}]`);
            }
          }
          hash.update(widthsBuf.join());
        }
        const cidToGidMap = dict.getRaw("CIDToGIDMap") || baseDict.getRaw("CIDToGIDMap");
        if (cidToGidMap instanceof Name) {
          hash.update(cidToGidMap.name);
        } else if (cidToGidMap instanceof Ref) {
          hash.update(cidToGidMap.toString());
        } else if (cidToGidMap instanceof BaseStream) {
          hash.update(cidToGidMap.peekBytes());
        }
      }
    }
    return {
      descriptor,
      dict,
      baseDict,
      composite,
      type: type.name,
      firstChar,
      lastChar,
      toUnicode,
      hash: hash ? hash.hexdigest() : ""
    };
  }
  async translateFont({
    descriptor,
    dict,
    baseDict,
    composite,
    type,
    firstChar,
    lastChar,
    toUnicode,
    cssFontInfo
  }) {
    const isType3Font = type === "Type3";
    if (!descriptor) {
      if (isType3Font) {
        const bbox = lookupNormalRect(dict.getArray("FontBBox"), [0, 0, 0, 0]);
        descriptor = new Dict(null);
        descriptor.set("FontName", Name.get(type));
        descriptor.set("FontBBox", bbox);
      } else {
        let baseFontName = dict.get("BaseFont");
        if (!(baseFontName instanceof Name)) {
          throw new FormatError("Base font is not specified");
        }
        baseFontName = baseFontName.name.replaceAll(/[,_]/g, "-");
        const metrics = this.getBaseFontMetrics(baseFontName);
        const fontNameWoStyle = baseFontName.split("-", 1)[0];
        const flags = (this.isSerifFont(fontNameWoStyle) ? FontFlags.Serif : 0) | (metrics.monospace ? FontFlags.FixedPitch : 0) | (getSymbolsFonts()[fontNameWoStyle] ? FontFlags.Symbolic : FontFlags.Nonsymbolic);
        const properties = {
          type,
          name: baseFontName,
          loadedName: baseDict.loadedName,
          systemFontInfo: null,
          widths: metrics.widths,
          defaultWidth: metrics.defaultWidth,
          isSimulatedFlags: true,
          flags,
          firstChar,
          lastChar,
          toUnicode,
          xHeight: 0,
          capHeight: 0,
          italicAngle: 0,
          isType3Font
        };
        const widths = dict.get("Widths");
        const standardFontName = getStandardFontName(baseFontName);
        let file = null;
        if (standardFontName) {
          file = await this.fetchStandardFontData(standardFontName);
          properties.isInternalFont = !!file;
        }
        if (!properties.isInternalFont && this.options.useSystemFonts) {
          properties.systemFontInfo = getFontSubstitution(this.systemFontCache, this.idFactory, this.options.standardFontDataUrl, baseFontName, standardFontName, type);
        }
        const newProperties = await this.extractDataStructures(dict, properties);
        if (Array.isArray(widths)) {
          const glyphWidths = [];
          let j = firstChar;
          for (const w of widths) {
            const width = this.xref.fetchIfRef(w);
            if (typeof width === "number") {
              glyphWidths[j] = width;
            }
            j++;
          }
          newProperties.widths = glyphWidths;
        } else {
          newProperties.widths = this.buildCharCodeToWidth(metrics.widths, newProperties);
        }
        return new Font(baseFontName, file, newProperties);
      }
    }
    let fontName = descriptor.get("FontName");
    let baseFont = dict.get("BaseFont");
    if (typeof fontName === "string") {
      fontName = Name.get(fontName);
    }
    if (typeof baseFont === "string") {
      baseFont = Name.get(baseFont);
    }
    const fontNameStr = fontName?.name;
    const baseFontStr = baseFont?.name;
    if (!isType3Font && fontNameStr !== baseFontStr) {
      info(`The FontDescriptor's FontName is "${fontNameStr}" but ` + `should be the same as the Font's BaseFont "${baseFontStr}".`);
      if (fontNameStr && baseFontStr && (baseFontStr.startsWith(fontNameStr) || !isKnownFontName(fontNameStr) && isKnownFontName(baseFontStr))) {
        fontName = null;
      }
    }
    fontName ||= baseFont;
    if (!(fontName instanceof Name)) {
      throw new FormatError("invalid font name");
    }
    let fontFile, subtype, length1, length2, length3;
    try {
      fontFile = descriptor.get("FontFile", "FontFile2", "FontFile3");
    } catch (ex) {
      if (!this.options.ignoreErrors) {
        throw ex;
      }
      warn(`translateFont - fetching "${fontName.name}" font file: "${ex}".`);
      fontFile = new NullStream();
    }
    let isInternalFont = false;
    let glyphScaleFactors = null;
    let systemFontInfo = null;
    if (fontFile) {
      if (fontFile.dict) {
        const subtypeEntry = fontFile.dict.get("Subtype");
        if (subtypeEntry instanceof Name) {
          subtype = subtypeEntry.name;
        }
        length1 = fontFile.dict.get("Length1");
        length2 = fontFile.dict.get("Length2");
        length3 = fontFile.dict.get("Length3");
      }
    } else if (cssFontInfo) {
      const standardFontName = getXfaFontName(fontName.name);
      if (standardFontName) {
        cssFontInfo.fontFamily = `${cssFontInfo.fontFamily}-PdfJS-XFA`;
        cssFontInfo.metrics = standardFontName.metrics || null;
        glyphScaleFactors = standardFontName.factors || null;
        fontFile = await this.fetchStandardFontData(standardFontName.name);
        isInternalFont = !!fontFile;
        baseDict = dict = getXfaFontDict(fontName.name);
        composite = true;
      }
    } else if (!isType3Font) {
      const standardFontName = getStandardFontName(fontName.name);
      if (standardFontName) {
        fontFile = await this.fetchStandardFontData(standardFontName);
        isInternalFont = !!fontFile;
      }
      if (!isInternalFont && this.options.useSystemFonts) {
        systemFontInfo = getFontSubstitution(this.systemFontCache, this.idFactory, this.options.standardFontDataUrl, fontName.name, standardFontName, type);
      }
    }
    const fontMatrix = lookupMatrix(dict.getArray("FontMatrix"), FONT_IDENTITY_MATRIX);
    const bbox = lookupNormalRect(descriptor.getArray("FontBBox") || dict.getArray("FontBBox"), undefined);
    let ascent = descriptor.get("Ascent");
    if (typeof ascent !== "number") {
      ascent = undefined;
    }
    let descent = descriptor.get("Descent");
    if (typeof descent !== "number") {
      descent = undefined;
    }
    let xHeight = descriptor.get("XHeight");
    if (typeof xHeight !== "number") {
      xHeight = 0;
    }
    let capHeight = descriptor.get("CapHeight");
    if (typeof capHeight !== "number") {
      capHeight = 0;
    }
    let flags = descriptor.get("Flags");
    if (!Number.isInteger(flags)) {
      flags = 0;
    }
    let italicAngle = descriptor.get("ItalicAngle");
    if (typeof italicAngle !== "number") {
      italicAngle = 0;
    }
    const properties = {
      type,
      name: fontName.name,
      subtype,
      file: fontFile,
      length1,
      length2,
      length3,
      isInternalFont,
      loadedName: baseDict.loadedName,
      composite,
      fixedPitch: false,
      fontMatrix,
      firstChar,
      lastChar,
      toUnicode,
      bbox,
      ascent,
      descent,
      xHeight,
      capHeight,
      flags,
      italicAngle,
      isType3Font,
      cssFontInfo,
      scaleFactors: glyphScaleFactors,
      systemFontInfo
    };
    if (composite) {
      const cidEncoding = baseDict.get("Encoding");
      if (cidEncoding instanceof Name) {
        properties.cidEncoding = cidEncoding.name;
      }
      const cMap = await CMapFactory.create({
        encoding: cidEncoding,
        fetchBuiltInCMap: this._fetchBuiltInCMapBound,
        useCMap: null
      });
      properties.cMap = cMap;
      properties.vertical = properties.cMap.vertical;
    }
    const newProperties = await this.extractDataStructures(dict, properties);
    this.extractWidths(dict, descriptor, newProperties);
    return new Font(fontName.name, fontFile, newProperties);
  }
  static buildFontPaths(font, glyphs, handler, evaluatorOptions) {
    function buildPath(fontChar) {
      const glyphName = `${font.loadedName}_path_${fontChar}`;
      try {
        if (font.renderer.hasBuiltPath(fontChar)) {
          return;
        }
        handler.send("commonobj", [glyphName, "FontPath", font.renderer.getPathJs(fontChar)]);
      } catch (reason) {
        if (evaluatorOptions.ignoreErrors) {
          warn(`buildFontPaths - ignoring ${glyphName} glyph: "${reason}".`);
          return;
        }
        throw reason;
      }
    }
    for (const glyph of glyphs) {
      buildPath(glyph.fontChar);
      const accent = glyph.accent;
      if (accent?.fontChar) {
        buildPath(accent.fontChar);
      }
    }
  }
  static get fallbackFontDict() {
    const dict = new Dict();
    dict.set("BaseFont", Name.get("Helvetica"));
    dict.set("Type", Name.get("FallbackType"));
    dict.set("Subtype", Name.get("FallbackType"));
    dict.set("Encoding", Name.get("WinAnsiEncoding"));
    return shadow(this, "fallbackFontDict", dict);
  }
}
class TranslatedFont {
  constructor({
    loadedName,
    font,
    dict,
    evaluatorOptions
  }) {
    this.loadedName = loadedName;
    this.font = font;
    this.dict = dict;
    this._evaluatorOptions = evaluatorOptions || DefaultPartialEvaluatorOptions;
    this.type3Loaded = null;
    this.type3Dependencies = font.isType3Font ? new Set() : null;
    this.sent = false;
  }
  send(handler) {
    if (this.sent) {
      return;
    }
    this.sent = true;
    handler.send("commonobj", [this.loadedName, "Font", this.font.exportData(this._evaluatorOptions.fontExtraProperties)]);
  }
  fallback(handler) {
    if (!this.font.data) {
      return;
    }
    this.font.disableFontFace = true;
    PartialEvaluator.buildFontPaths(this.font, this.font.glyphCacheValues, handler, this._evaluatorOptions);
  }
  loadType3Data(evaluator, resources, task) {
    if (this.type3Loaded) {
      return this.type3Loaded;
    }
    if (!this.font.isType3Font) {
      throw new Error("Must be a Type3 font.");
    }
    const type3Evaluator = evaluator.clone({
      ignoreErrors: false
    });
    const type3FontRefs = new RefSet(evaluator.type3FontRefs);
    if (this.dict.objId && !type3FontRefs.has(this.dict.objId)) {
      type3FontRefs.put(this.dict.objId);
    }
    type3Evaluator.type3FontRefs = type3FontRefs;
    const translatedFont = this.font,
      type3Dependencies = this.type3Dependencies;
    let loadCharProcsPromise = Promise.resolve();
    const charProcs = this.dict.get("CharProcs");
    const fontResources = this.dict.get("Resources") || resources;
    const charProcOperatorList = Object.create(null);
    const fontBBox = Util.normalizeRect(translatedFont.bbox || [0, 0, 0, 0]),
      width = fontBBox[2] - fontBBox[0],
      height = fontBBox[3] - fontBBox[1];
    const fontBBoxSize = Math.hypot(width, height);
    for (const key of charProcs.getKeys()) {
      loadCharProcsPromise = loadCharProcsPromise.then(() => {
        const glyphStream = charProcs.get(key);
        const operatorList = new OperatorList();
        return type3Evaluator.getOperatorList({
          stream: glyphStream,
          task,
          resources: fontResources,
          operatorList
        }).then(() => {
          if (operatorList.fnArray[0] === OPS.setCharWidthAndBounds) {
            this._removeType3ColorOperators(operatorList, fontBBoxSize);
          }
          charProcOperatorList[key] = operatorList.getIR();
          for (const dependency of operatorList.dependencies) {
            type3Dependencies.add(dependency);
          }
        }).catch(function (reason) {
          warn(`Type3 font resource "${key}" is not available.`);
          const dummyOperatorList = new OperatorList();
          charProcOperatorList[key] = dummyOperatorList.getIR();
        });
      });
    }
    this.type3Loaded = loadCharProcsPromise.then(() => {
      translatedFont.charProcOperatorList = charProcOperatorList;
      if (this._bbox) {
        translatedFont.isCharBBox = true;
        translatedFont.bbox = this._bbox;
      }
    });
    return this.type3Loaded;
  }
  _removeType3ColorOperators(operatorList, fontBBoxSize = NaN) {
    const charBBox = Util.normalizeRect(operatorList.argsArray[0].slice(2)),
      width = charBBox[2] - charBBox[0],
      height = charBBox[3] - charBBox[1];
    const charBBoxSize = Math.hypot(width, height);
    if (width === 0 || height === 0) {
      operatorList.fnArray.splice(0, 1);
      operatorList.argsArray.splice(0, 1);
    } else if (fontBBoxSize === 0 || Math.round(charBBoxSize / fontBBoxSize) >= 10) {
      if (!this._bbox) {
        this._bbox = [Infinity, Infinity, -Infinity, -Infinity];
      }
      this._bbox[0] = Math.min(this._bbox[0], charBBox[0]);
      this._bbox[1] = Math.min(this._bbox[1], charBBox[1]);
      this._bbox[2] = Math.max(this._bbox[2], charBBox[2]);
      this._bbox[3] = Math.max(this._bbox[3], charBBox[3]);
    }
    let i = 0,
      ii = operatorList.length;
    while (i < ii) {
      switch (operatorList.fnArray[i]) {
        case OPS.setCharWidthAndBounds:
          break;
        case OPS.setStrokeColorSpace:
        case OPS.setFillColorSpace:
        case OPS.setStrokeColor:
        case OPS.setStrokeColorN:
        case OPS.setFillColor:
        case OPS.setFillColorN:
        case OPS.setStrokeGray:
        case OPS.setFillGray:
        case OPS.setStrokeRGBColor:
        case OPS.setFillRGBColor:
        case OPS.setStrokeCMYKColor:
        case OPS.setFillCMYKColor:
        case OPS.shadingFill:
        case OPS.setRenderingIntent:
          operatorList.fnArray.splice(i, 1);
          operatorList.argsArray.splice(i, 1);
          ii--;
          continue;
        case OPS.setGState:
          const [gStateObj] = operatorList.argsArray[i];
          let j = 0,
            jj = gStateObj.length;
          while (j < jj) {
            const [gStateKey] = gStateObj[j];
            switch (gStateKey) {
              case "TR":
              case "TR2":
              case "HT":
              case "BG":
              case "BG2":
              case "UCR":
              case "UCR2":
                gStateObj.splice(j, 1);
                jj--;
                continue;
            }
            j++;
          }
          break;
      }
      i++;
    }
  }
}
class StateManager {
  constructor(initialState = new EvalState()) {
    this.state = initialState;
    this.stateStack = [];
  }
  save() {
    const old = this.state;
    this.stateStack.push(this.state);
    this.state = old.clone();
  }
  restore() {
    const prev = this.stateStack.pop();
    if (prev) {
      this.state = prev;
    }
  }
  transform(args) {
    this.state.ctm = Util.transform(this.state.ctm, args);
  }
}
class TextState {
  constructor() {
    this.ctm = new Float32Array(IDENTITY_MATRIX);
    this.fontName = null;
    this.fontSize = 0;
    this.loadedName = null;
    this.font = null;
    this.fontMatrix = FONT_IDENTITY_MATRIX;
    this.textMatrix = IDENTITY_MATRIX.slice();
    this.textLineMatrix = IDENTITY_MATRIX.slice();
    this.charSpacing = 0;
    this.wordSpacing = 0;
    this.leading = 0;
    this.textHScale = 1;
    this.textRise = 0;
  }
  setTextMatrix(a, b, c, d, e, f) {
    const m = this.textMatrix;
    m[0] = a;
    m[1] = b;
    m[2] = c;
    m[3] = d;
    m[4] = e;
    m[5] = f;
  }
  setTextLineMatrix(a, b, c, d, e, f) {
    const m = this.textLineMatrix;
    m[0] = a;
    m[1] = b;
    m[2] = c;
    m[3] = d;
    m[4] = e;
    m[5] = f;
  }
  translateTextMatrix(x, y) {
    const m = this.textMatrix;
    m[4] = m[0] * x + m[2] * y + m[4];
    m[5] = m[1] * x + m[3] * y + m[5];
  }
  translateTextLineMatrix(x, y) {
    const m = this.textLineMatrix;
    m[4] = m[0] * x + m[2] * y + m[4];
    m[5] = m[1] * x + m[3] * y + m[5];
  }
  carriageReturn() {
    this.translateTextLineMatrix(0, -this.leading);
    this.textMatrix = this.textLineMatrix.slice();
  }
  clone() {
    const clone = Object.create(this);
    clone.textMatrix = this.textMatrix.slice();
    clone.textLineMatrix = this.textLineMatrix.slice();
    clone.fontMatrix = this.fontMatrix.slice();
    return clone;
  }
}
class EvalState {
  constructor() {
    this.ctm = new Float32Array(IDENTITY_MATRIX);
    this.font = null;
    this.textRenderingMode = TextRenderingMode.FILL;
    this._fillColorSpace = ColorSpace.singletons.gray;
    this._strokeColorSpace = ColorSpace.singletons.gray;
    this.patternFillColorSpace = null;
    this.patternStrokeColorSpace = null;
  }
  get fillColorSpace() {
    return this._fillColorSpace;
  }
  set fillColorSpace(colorSpace) {
    this._fillColorSpace = this.patternFillColorSpace = colorSpace;
  }
  get strokeColorSpace() {
    return this._strokeColorSpace;
  }
  set strokeColorSpace(colorSpace) {
    this._strokeColorSpace = this.patternStrokeColorSpace = colorSpace;
  }
  clone() {
    return Object.create(this);
  }
}
class EvaluatorPreprocessor {
  static get opMap() {
    return shadow(this, "opMap", Object.assign(Object.create(null), {
      w: {
        id: OPS.setLineWidth,
        numArgs: 1,
        variableArgs: false
      },
      J: {
        id: OPS.setLineCap,
        numArgs: 1,
        variableArgs: false
      },
      j: {
        id: OPS.setLineJoin,
        numArgs: 1,
        variableArgs: false
      },
      M: {
        id: OPS.setMiterLimit,
        numArgs: 1,
        variableArgs: false
      },
      d: {
        id: OPS.setDash,
        numArgs: 2,
        variableArgs: false
      },
      ri: {
        id: OPS.setRenderingIntent,
        numArgs: 1,
        variableArgs: false
      },
      i: {
        id: OPS.setFlatness,
        numArgs: 1,
        variableArgs: false
      },
      gs: {
        id: OPS.setGState,
        numArgs: 1,
        variableArgs: false
      },
      q: {
        id: OPS.save,
        numArgs: 0,
        variableArgs: false
      },
      Q: {
        id: OPS.restore,
        numArgs: 0,
        variableArgs: false
      },
      cm: {
        id: OPS.transform,
        numArgs: 6,
        variableArgs: false
      },
      m: {
        id: OPS.moveTo,
        numArgs: 2,
        variableArgs: false
      },
      l: {
        id: OPS.lineTo,
        numArgs: 2,
        variableArgs: false
      },
      c: {
        id: OPS.curveTo,
        numArgs: 6,
        variableArgs: false
      },
      v: {
        id: OPS.curveTo2,
        numArgs: 4,
        variableArgs: false
      },
      y: {
        id: OPS.curveTo3,
        numArgs: 4,
        variableArgs: false
      },
      h: {
        id: OPS.closePath,
        numArgs: 0,
        variableArgs: false
      },
      re: {
        id: OPS.rectangle,
        numArgs: 4,
        variableArgs: false
      },
      S: {
        id: OPS.stroke,
        numArgs: 0,
        variableArgs: false
      },
      s: {
        id: OPS.closeStroke,
        numArgs: 0,
        variableArgs: false
      },
      f: {
        id: OPS.fill,
        numArgs: 0,
        variableArgs: false
      },
      F: {
        id: OPS.fill,
        numArgs: 0,
        variableArgs: false
      },
      "f*": {
        id: OPS.eoFill,
        numArgs: 0,
        variableArgs: false
      },
      B: {
        id: OPS.fillStroke,
        numArgs: 0,
        variableArgs: false
      },
      "B*": {
        id: OPS.eoFillStroke,
        numArgs: 0,
        variableArgs: false
      },
      b: {
        id: OPS.closeFillStroke,
        numArgs: 0,
        variableArgs: false
      },
      "b*": {
        id: OPS.closeEOFillStroke,
        numArgs: 0,
        variableArgs: false
      },
      n: {
        id: OPS.endPath,
        numArgs: 0,
        variableArgs: false
      },
      W: {
        id: OPS.clip,
        numArgs: 0,
        variableArgs: false
      },
      "W*": {
        id: OPS.eoClip,
        numArgs: 0,
        variableArgs: false
      },
      BT: {
        id: OPS.beginText,
        numArgs: 0,
        variableArgs: false
      },
      ET: {
        id: OPS.endText,
        numArgs: 0,
        variableArgs: false
      },
      Tc: {
        id: OPS.setCharSpacing,
        numArgs: 1,
        variableArgs: false
      },
      Tw: {
        id: OPS.setWordSpacing,
        numArgs: 1,
        variableArgs: false
      },
      Tz: {
        id: OPS.setHScale,
        numArgs: 1,
        variableArgs: false
      },
      TL: {
        id: OPS.setLeading,
        numArgs: 1,
        variableArgs: false
      },
      Tf: {
        id: OPS.setFont,
        numArgs: 2,
        variableArgs: false
      },
      Tr: {
        id: OPS.setTextRenderingMode,
        numArgs: 1,
        variableArgs: false
      },
      Ts: {
        id: OPS.setTextRise,
        numArgs: 1,
        variableArgs: false
      },
      Td: {
        id: OPS.moveText,
        numArgs: 2,
        variableArgs: false
      },
      TD: {
        id: OPS.setLeadingMoveText,
        numArgs: 2,
        variableArgs: false
      },
      Tm: {
        id: OPS.setTextMatrix,
        numArgs: 6,
        variableArgs: false
      },
      "T*": {
        id: OPS.nextLine,
        numArgs: 0,
        variableArgs: false
      },
      Tj: {
        id: OPS.showText,
        numArgs: 1,
        variableArgs: false
      },
      TJ: {
        id: OPS.showSpacedText,
        numArgs: 1,
        variableArgs: false
      },
      "'": {
        id: OPS.nextLineShowText,
        numArgs: 1,
        variableArgs: false
      },
      '"': {
        id: OPS.nextLineSetSpacingShowText,
        numArgs: 3,
        variableArgs: false
      },
      d0: {
        id: OPS.setCharWidth,
        numArgs: 2,
        variableArgs: false
      },
      d1: {
        id: OPS.setCharWidthAndBounds,
        numArgs: 6,
        variableArgs: false
      },
      CS: {
        id: OPS.setStrokeColorSpace,
        numArgs: 1,
        variableArgs: false
      },
      cs: {
        id: OPS.setFillColorSpace,
        numArgs: 1,
        variableArgs: false
      },
      SC: {
        id: OPS.setStrokeColor,
        numArgs: 4,
        variableArgs: true
      },
      SCN: {
        id: OPS.setStrokeColorN,
        numArgs: 33,
        variableArgs: true
      },
      sc: {
        id: OPS.setFillColor,
        numArgs: 4,
        variableArgs: true
      },
      scn: {
        id: OPS.setFillColorN,
        numArgs: 33,
        variableArgs: true
      },
      G: {
        id: OPS.setStrokeGray,
        numArgs: 1,
        variableArgs: false
      },
      g: {
        id: OPS.setFillGray,
        numArgs: 1,
        variableArgs: false
      },
      RG: {
        id: OPS.setStrokeRGBColor,
        numArgs: 3,
        variableArgs: false
      },
      rg: {
        id: OPS.setFillRGBColor,
        numArgs: 3,
        variableArgs: false
      },
      K: {
        id: OPS.setStrokeCMYKColor,
        numArgs: 4,
        variableArgs: false
      },
      k: {
        id: OPS.setFillCMYKColor,
        numArgs: 4,
        variableArgs: false
      },
      sh: {
        id: OPS.shadingFill,
        numArgs: 1,
        variableArgs: false
      },
      BI: {
        id: OPS.beginInlineImage,
        numArgs: 0,
        variableArgs: false
      },
      ID: {
        id: OPS.beginImageData,
        numArgs: 0,
        variableArgs: false
      },
      EI: {
        id: OPS.endInlineImage,
        numArgs: 1,
        variableArgs: false
      },
      Do: {
        id: OPS.paintXObject,
        numArgs: 1,
        variableArgs: false
      },
      MP: {
        id: OPS.markPoint,
        numArgs: 1,
        variableArgs: false
      },
      DP: {
        id: OPS.markPointProps,
        numArgs: 2,
        variableArgs: false
      },
      BMC: {
        id: OPS.beginMarkedContent,
        numArgs: 1,
        variableArgs: false
      },
      BDC: {
        id: OPS.beginMarkedContentProps,
        numArgs: 2,
        variableArgs: false
      },
      EMC: {
        id: OPS.endMarkedContent,
        numArgs: 0,
        variableArgs: false
      },
      BX: {
        id: OPS.beginCompat,
        numArgs: 0,
        variableArgs: false
      },
      EX: {
        id: OPS.endCompat,
        numArgs: 0,
        variableArgs: false
      },
      BM: null,
      BD: null,
      true: null,
      fa: null,
      fal: null,
      fals: null,
      false: null,
      nu: null,
      nul: null,
      null: null
    }));
  }
  static MAX_INVALID_PATH_OPS = 10;
  constructor(stream, xref, stateManager = new StateManager()) {
    this.parser = new Parser({
      lexer: new Lexer(stream, EvaluatorPreprocessor.opMap),
      xref
    });
    this.stateManager = stateManager;
    this.nonProcessedArgs = [];
    this._isPathOp = false;
    this._numInvalidPathOPS = 0;
  }
  get savedStatesDepth() {
    return this.stateManager.stateStack.length;
  }
  read(operation) {
    let args = operation.args;
    while (true) {
      const obj = this.parser.getObj();
      if (obj instanceof Cmd) {
        const cmd = obj.cmd;
        const opSpec = EvaluatorPreprocessor.opMap[cmd];
        if (!opSpec) {
          warn(`Unknown command "${cmd}".`);
          continue;
        }
        const fn = opSpec.id;
        const numArgs = opSpec.numArgs;
        let argsLength = args !== null ? args.length : 0;
        if (!this._isPathOp) {
          this._numInvalidPathOPS = 0;
        }
        this._isPathOp = fn >= OPS.moveTo && fn <= OPS.endPath;
        if (!opSpec.variableArgs) {
          if (argsLength !== numArgs) {
            const nonProcessedArgs = this.nonProcessedArgs;
            while (argsLength > numArgs) {
              nonProcessedArgs.push(args.shift());
              argsLength--;
            }
            while (argsLength < numArgs && nonProcessedArgs.length !== 0) {
              if (args === null) {
                args = [];
              }
              args.unshift(nonProcessedArgs.pop());
              argsLength++;
            }
          }
          if (argsLength < numArgs) {
            const partialMsg = `command ${cmd}: expected ${numArgs} args, ` + `but received ${argsLength} args.`;
            if (this._isPathOp && ++this._numInvalidPathOPS > EvaluatorPreprocessor.MAX_INVALID_PATH_OPS) {
              throw new FormatError(`Invalid ${partialMsg}`);
            }
            warn(`Skipping ${partialMsg}`);
            if (args !== null) {
              args.length = 0;
            }
            continue;
          }
        } else if (argsLength > numArgs) {
          info(`Command ${cmd}: expected [0, ${numArgs}] args, ` + `but received ${argsLength} args.`);
        }
        this.preprocessCommand(fn, args);
        operation.fn = fn;
        operation.args = args;
        return true;
      }
      if (obj === EOF) {
        return false;
      }
      if (obj !== null) {
        if (args === null) {
          args = [];
        }
        args.push(obj);
        if (args.length > 33) {
          throw new FormatError("Too many arguments");
        }
      }
    }
  }
  preprocessCommand(fn, args) {
    switch (fn | 0) {
      case OPS.save:
        this.stateManager.save();
        break;
      case OPS.restore:
        this.stateManager.restore();
        break;
      case OPS.transform:
        this.stateManager.transform(args);
        break;
    }
  }
}

;// CONCATENATED MODULE: ./src/core/default_appearance.js








class DefaultAppearanceEvaluator extends EvaluatorPreprocessor {
  constructor(str) {
    super(new StringStream(str));
  }
  parse() {
    const operation = {
      fn: 0,
      args: []
    };
    const result = {
      fontSize: 0,
      fontName: "",
      fontColor: new Uint8ClampedArray(3)
    };
    try {
      while (true) {
        operation.args.length = 0;
        if (!this.read(operation)) {
          break;
        }
        if (this.savedStatesDepth !== 0) {
          continue;
        }
        const {
          fn,
          args
        } = operation;
        switch (fn | 0) {
          case OPS.setFont:
            const [fontName, fontSize] = args;
            if (fontName instanceof Name) {
              result.fontName = fontName.name;
            }
            if (typeof fontSize === "number" && fontSize > 0) {
              result.fontSize = fontSize;
            }
            break;
          case OPS.setFillRGBColor:
            ColorSpace.singletons.rgb.getRgbItem(args, 0, result.fontColor, 0);
            break;
          case OPS.setFillGray:
            ColorSpace.singletons.gray.getRgbItem(args, 0, result.fontColor, 0);
            break;
          case OPS.setFillCMYKColor:
            ColorSpace.singletons.cmyk.getRgbItem(args, 0, result.fontColor, 0);
            break;
        }
      }
    } catch (reason) {
      warn(`parseDefaultAppearance - ignoring errors: "${reason}".`);
    }
    return result;
  }
}
function parseDefaultAppearance(str) {
  return new DefaultAppearanceEvaluator(str).parse();
}
class AppearanceStreamEvaluator extends EvaluatorPreprocessor {
  constructor(stream, evaluatorOptions, xref) {
    super(stream);
    this.stream = stream;
    this.evaluatorOptions = evaluatorOptions;
    this.xref = xref;
    this.resources = stream.dict?.get("Resources");
  }
  parse() {
    const operation = {
      fn: 0,
      args: []
    };
    let result = {
      scaleFactor: 1,
      fontSize: 0,
      fontName: "",
      fontColor: new Uint8ClampedArray(3),
      fillColorSpace: ColorSpace.singletons.gray
    };
    let breakLoop = false;
    const stack = [];
    try {
      while (true) {
        operation.args.length = 0;
        if (breakLoop || !this.read(operation)) {
          break;
        }
        const {
          fn,
          args
        } = operation;
        switch (fn | 0) {
          case OPS.save:
            stack.push({
              scaleFactor: result.scaleFactor,
              fontSize: result.fontSize,
              fontName: result.fontName,
              fontColor: result.fontColor.slice(),
              fillColorSpace: result.fillColorSpace
            });
            break;
          case OPS.restore:
            result = stack.pop() || result;
            break;
          case OPS.setTextMatrix:
            result.scaleFactor *= Math.hypot(args[0], args[1]);
            break;
          case OPS.setFont:
            const [fontName, fontSize] = args;
            if (fontName instanceof Name) {
              result.fontName = fontName.name;
            }
            if (typeof fontSize === "number" && fontSize > 0) {
              result.fontSize = fontSize * result.scaleFactor;
            }
            break;
          case OPS.setFillColorSpace:
            result.fillColorSpace = ColorSpace.parse({
              cs: args[0],
              xref: this.xref,
              resources: this.resources,
              pdfFunctionFactory: this._pdfFunctionFactory,
              localColorSpaceCache: this._localColorSpaceCache
            });
            break;
          case OPS.setFillColor:
            const cs = result.fillColorSpace;
            cs.getRgbItem(args, 0, result.fontColor, 0);
            break;
          case OPS.setFillRGBColor:
            ColorSpace.singletons.rgb.getRgbItem(args, 0, result.fontColor, 0);
            break;
          case OPS.setFillGray:
            ColorSpace.singletons.gray.getRgbItem(args, 0, result.fontColor, 0);
            break;
          case OPS.setFillCMYKColor:
            ColorSpace.singletons.cmyk.getRgbItem(args, 0, result.fontColor, 0);
            break;
          case OPS.showText:
          case OPS.showSpacedText:
          case OPS.nextLineShowText:
          case OPS.nextLineSetSpacingShowText:
            breakLoop = true;
            break;
        }
      }
    } catch (reason) {
      warn(`parseAppearanceStream - ignoring errors: "${reason}".`);
    }
    this.stream.reset();
    delete result.scaleFactor;
    delete result.fillColorSpace;
    return result;
  }
  get _localColorSpaceCache() {
    return shadow(this, "_localColorSpaceCache", new LocalColorSpaceCache());
  }
  get _pdfFunctionFactory() {
    const pdfFunctionFactory = new PDFFunctionFactory({
      xref: this.xref,
      isEvalSupported: this.evaluatorOptions.isEvalSupported
    });
    return shadow(this, "_pdfFunctionFactory", pdfFunctionFactory);
  }
}
function parseAppearanceStream(stream, evaluatorOptions, xref) {
  return new AppearanceStreamEvaluator(stream, evaluatorOptions, xref).parse();
}
function getPdfColor(color, isFill) {
  if (color[0] === color[1] && color[1] === color[2]) {
    const gray = color[0] / 255;
    return `${numberToString(gray)} ${isFill ? "g" : "G"}`;
  }
  return Array.from(color, c => numberToString(c / 255)).join(" ") + ` ${isFill ? "rg" : "RG"}`;
}
function createDefaultAppearance({
  fontSize,
  fontName,
  fontColor
}) {
  return `/${escapePDFName(fontName)} ${fontSize} Tf ${getPdfColor(fontColor, true)}`;
}
class FakeUnicodeFont {
  constructor(xref, fontFamily) {
    this.xref = xref;
    this.widths = null;
    this.firstChar = Infinity;
    this.lastChar = -Infinity;
    this.fontFamily = fontFamily;
    const canvas = new OffscreenCanvas(1, 1);
    this.ctxMeasure = canvas.getContext("2d", {
      willReadFrequently: true
    });
    if (!FakeUnicodeFont._fontNameId) {
      FakeUnicodeFont._fontNameId = 1;
    }
    this.fontName = Name.get(`InvalidPDFjsFont_${fontFamily}_${FakeUnicodeFont._fontNameId++}`);
  }
  get fontDescriptorRef() {
    if (!FakeUnicodeFont._fontDescriptorRef) {
      const fontDescriptor = new Dict(this.xref);
      fontDescriptor.set("Type", Name.get("FontDescriptor"));
      fontDescriptor.set("FontName", this.fontName);
      fontDescriptor.set("FontFamily", "MyriadPro Regular");
      fontDescriptor.set("FontBBox", [0, 0, 0, 0]);
      fontDescriptor.set("FontStretch", Name.get("Normal"));
      fontDescriptor.set("FontWeight", 400);
      fontDescriptor.set("ItalicAngle", 0);
      FakeUnicodeFont._fontDescriptorRef = this.xref.getNewPersistentRef(fontDescriptor);
    }
    return FakeUnicodeFont._fontDescriptorRef;
  }
  get descendantFontRef() {
    const descendantFont = new Dict(this.xref);
    descendantFont.set("BaseFont", this.fontName);
    descendantFont.set("Type", Name.get("Font"));
    descendantFont.set("Subtype", Name.get("CIDFontType0"));
    descendantFont.set("CIDToGIDMap", Name.get("Identity"));
    descendantFont.set("FirstChar", this.firstChar);
    descendantFont.set("LastChar", this.lastChar);
    descendantFont.set("FontDescriptor", this.fontDescriptorRef);
    descendantFont.set("DW", 1000);
    const widths = [];
    const chars = [...this.widths.entries()].sort();
    let currentChar = null;
    let currentWidths = null;
    for (const [char, width] of chars) {
      if (!currentChar) {
        currentChar = char;
        currentWidths = [width];
        continue;
      }
      if (char === currentChar + currentWidths.length) {
        currentWidths.push(width);
      } else {
        widths.push(currentChar, currentWidths);
        currentChar = char;
        currentWidths = [width];
      }
    }
    if (currentChar) {
      widths.push(currentChar, currentWidths);
    }
    descendantFont.set("W", widths);
    const cidSystemInfo = new Dict(this.xref);
    cidSystemInfo.set("Ordering", "Identity");
    cidSystemInfo.set("Registry", "Adobe");
    cidSystemInfo.set("Supplement", 0);
    descendantFont.set("CIDSystemInfo", cidSystemInfo);
    return this.xref.getNewPersistentRef(descendantFont);
  }
  get baseFontRef() {
    const baseFont = new Dict(this.xref);
    baseFont.set("BaseFont", this.fontName);
    baseFont.set("Type", Name.get("Font"));
    baseFont.set("Subtype", Name.get("Type0"));
    baseFont.set("Encoding", Name.get("Identity-H"));
    baseFont.set("DescendantFonts", [this.descendantFontRef]);
    baseFont.set("ToUnicode", Name.get("Identity-H"));
    return this.xref.getNewPersistentRef(baseFont);
  }
  get resources() {
    const resources = new Dict(this.xref);
    const font = new Dict(this.xref);
    font.set(this.fontName.name, this.baseFontRef);
    resources.set("Font", font);
    return resources;
  }
  _createContext() {
    this.widths = new Map();
    this.ctxMeasure.font = `1000px ${this.fontFamily}`;
    return this.ctxMeasure;
  }
  createFontResources(text) {
    const ctx = this._createContext();
    for (const line of text.split(/\r\n?|\n/)) {
      for (const char of line.split("")) {
        const code = char.charCodeAt(0);
        if (this.widths.has(code)) {
          continue;
        }
        const metrics = ctx.measureText(char);
        const width = Math.ceil(metrics.width);
        this.widths.set(code, width);
        this.firstChar = Math.min(code, this.firstChar);
        this.lastChar = Math.max(code, this.lastChar);
      }
    }
    return this.resources;
  }
  static getFirstPositionInfo(rect, rotation, fontSize) {
    const [x1, y1, x2, y2] = rect;
    let w = x2 - x1;
    let h = y2 - y1;
    if (rotation % 180 !== 0) {
      [w, h] = [h, w];
    }
    const lineHeight = LINE_FACTOR * fontSize;
    const lineDescent = LINE_DESCENT_FACTOR * fontSize;
    return {
      coords: [0, h + lineDescent - lineHeight],
      bbox: [0, 0, w, h],
      matrix: rotation !== 0 ? getRotationMatrix(rotation, h, lineHeight) : undefined
    };
  }
  createAppearance(text, rect, rotation, fontSize, bgColor, strokeAlpha) {
    const ctx = this._createContext();
    const lines = [];
    let maxWidth = -Infinity;
    for (const line of text.split(/\r\n?|\n/)) {
      lines.push(line);
      const lineWidth = ctx.measureText(line).width;
      maxWidth = Math.max(maxWidth, lineWidth);
      for (const code of codePointIter(line)) {
        const char = String.fromCodePoint(code);
        let width = this.widths.get(code);
        if (width === undefined) {
          const metrics = ctx.measureText(char);
          width = Math.ceil(metrics.width);
          this.widths.set(code, width);
          this.firstChar = Math.min(code, this.firstChar);
          this.lastChar = Math.max(code, this.lastChar);
        }
      }
    }
    maxWidth *= fontSize / 1000;
    const [x1, y1, x2, y2] = rect;
    let w = x2 - x1;
    let h = y2 - y1;
    if (rotation % 180 !== 0) {
      [w, h] = [h, w];
    }
    let hscale = 1;
    if (maxWidth > w) {
      hscale = w / maxWidth;
    }
    let vscale = 1;
    const lineHeight = LINE_FACTOR * fontSize;
    const lineDescent = LINE_DESCENT_FACTOR * fontSize;
    const maxHeight = lineHeight * lines.length;
    if (maxHeight > h) {
      vscale = h / maxHeight;
    }
    const fscale = Math.min(hscale, vscale);
    const newFontSize = fontSize * fscale;
    const buffer = ["q", `0 0 ${numberToString(w)} ${numberToString(h)} re W n`, `BT`, `1 0 0 1 0 ${numberToString(h + lineDescent)} Tm 0 Tc ${getPdfColor(bgColor, true)}`, `/${this.fontName.name} ${numberToString(newFontSize)} Tf`];
    const {
      resources
    } = this;
    strokeAlpha = typeof strokeAlpha === "number" && strokeAlpha >= 0 && strokeAlpha <= 1 ? strokeAlpha : 1;
    if (strokeAlpha !== 1) {
      buffer.push("/R0 gs");
      const extGState = new Dict(this.xref);
      const r0 = new Dict(this.xref);
      r0.set("ca", strokeAlpha);
      r0.set("CA", strokeAlpha);
      r0.set("Type", Name.get("ExtGState"));
      extGState.set("R0", r0);
      resources.set("ExtGState", extGState);
    }
    const vShift = numberToString(lineHeight);
    for (const line of lines) {
      buffer.push(`0 -${vShift} Td <${stringToUTF16HexString(line)}> Tj`);
    }
    buffer.push("ET", "Q");
    const appearance = buffer.join("\n");
    const appearanceStreamDict = new Dict(this.xref);
    appearanceStreamDict.set("Subtype", Name.get("Form"));
    appearanceStreamDict.set("Type", Name.get("XObject"));
    appearanceStreamDict.set("BBox", [0, 0, w, h]);
    appearanceStreamDict.set("Length", appearance.length);
    appearanceStreamDict.set("Resources", resources);
    if (rotation) {
      const matrix = getRotationMatrix(rotation, w, h);
      appearanceStreamDict.set("Matrix", matrix);
    }
    const ap = new StringStream(appearance);
    ap.dict = appearanceStreamDict;
    return ap;
  }
}

;// CONCATENATED MODULE: ./src/core/name_number_tree.js


class NameOrNumberTree {
  constructor(root, xref, type) {
    this.root = root;
    this.xref = xref;
    this._type = type;
  }
  getAll() {
    const map = new Map();
    if (!this.root) {
      return map;
    }
    const xref = this.xref;
    const processed = new RefSet();
    processed.put(this.root);
    const queue = [this.root];
    while (queue.length > 0) {
      const obj = xref.fetchIfRef(queue.shift());
      if (!(obj instanceof Dict)) {
        continue;
      }
      if (obj.has("Kids")) {
        const kids = obj.get("Kids");
        if (!Array.isArray(kids)) {
          continue;
        }
        for (const kid of kids) {
          if (processed.has(kid)) {
            throw new FormatError(`Duplicate entry in "${this._type}" tree.`);
          }
          queue.push(kid);
          processed.put(kid);
        }
        continue;
      }
      const entries = obj.get(this._type);
      if (!Array.isArray(entries)) {
        continue;
      }
      for (let i = 0, ii = entries.length; i < ii; i += 2) {
        map.set(xref.fetchIfRef(entries[i]), xref.fetchIfRef(entries[i + 1]));
      }
    }
    return map;
  }
  get(key) {
    if (!this.root) {
      return null;
    }
    const xref = this.xref;
    let kidsOrEntries = xref.fetchIfRef(this.root);
    let loopCount = 0;
    const MAX_LEVELS = 10;
    while (kidsOrEntries.has("Kids")) {
      if (++loopCount > MAX_LEVELS) {
        warn(`Search depth limit reached for "${this._type}" tree.`);
        return null;
      }
      const kids = kidsOrEntries.get("Kids");
      if (!Array.isArray(kids)) {
        return null;
      }
      let l = 0,
        r = kids.length - 1;
      while (l <= r) {
        const m = l + r >> 1;
        const kid = xref.fetchIfRef(kids[m]);
        const limits = kid.get("Limits");
        if (key < xref.fetchIfRef(limits[0])) {
          r = m - 1;
        } else if (key > xref.fetchIfRef(limits[1])) {
          l = m + 1;
        } else {
          kidsOrEntries = kid;
          break;
        }
      }
      if (l > r) {
        return null;
      }
    }
    const entries = kidsOrEntries.get(this._type);
    if (Array.isArray(entries)) {
      let l = 0,
        r = entries.length - 2;
      while (l <= r) {
        const tmp = l + r >> 1,
          m = tmp + (tmp & 1);
        const currentKey = xref.fetchIfRef(entries[m]);
        if (key < currentKey) {
          r = m - 2;
        } else if (key > currentKey) {
          l = m + 2;
        } else {
          return xref.fetchIfRef(entries[m + 1]);
        }
      }
    }
    return null;
  }
}
class NameTree extends NameOrNumberTree {
  constructor(root, xref) {
    super(root, xref, "Names");
  }
}
class NumberTree extends NameOrNumberTree {
  constructor(root, xref) {
    super(root, xref, "Nums");
  }
}

;// CONCATENATED MODULE: ./src/core/cleanup_helper.js




function clearGlobalCaches() {
  clearPatternCaches();
  clearPrimitiveCaches();
  clearUnicodeCaches();
  JpxImage.cleanup();
}

;// CONCATENATED MODULE: ./src/core/file_spec.js



function pickPlatformItem(dict) {
  if (!(dict instanceof Dict)) {
    return null;
  }
  if (dict.has("UF")) {
    return dict.get("UF");
  } else if (dict.has("F")) {
    return dict.get("F");
  } else if (dict.has("Unix")) {
    return dict.get("Unix");
  } else if (dict.has("Mac")) {
    return dict.get("Mac");
  } else if (dict.has("DOS")) {
    return dict.get("DOS");
  }
  return null;
}
function stripPath(str) {
  return str.substring(str.lastIndexOf("/") + 1);
}
class FileSpec {
  #contentAvailable = false;
  constructor(root, xref, skipContent = false) {
    if (!(root instanceof Dict)) {
      return;
    }
    this.xref = xref;
    this.root = root;
    if (root.has("FS")) {
      this.fs = root.get("FS");
    }
    if (root.has("RF")) {
      warn("Related file specifications are not supported");
    }
    if (!skipContent) {
      if (root.has("EF")) {
        this.#contentAvailable = true;
      } else {
        warn("Non-embedded file specifications are not supported");
      }
    }
  }
  get filename() {
    let filename = "";
    const item = pickPlatformItem(this.root);
    if (item && typeof item === "string") {
      filename = stringToPDFString(item).replaceAll("\\\\", "\\").replaceAll("\\/", "/").replaceAll("\\", "/");
    }
    return shadow(this, "filename", filename || "unnamed");
  }
  get content() {
    if (!this.#contentAvailable) {
      return null;
    }
    this._contentRef ||= pickPlatformItem(this.root?.get("EF"));
    let content = null;
    if (this._contentRef) {
      const fileObj = this.xref.fetchIfRef(this._contentRef);
      if (fileObj instanceof BaseStream) {
        content = fileObj.getBytes();
      } else {
        warn("Embedded file specification points to non-existing/invalid content");
      }
    } else {
      warn("Embedded file specification does not have any content");
    }
    return content;
  }
  get description() {
    let description = "";
    const desc = this.root?.get("Desc");
    if (desc && typeof desc === "string") {
      description = stringToPDFString(desc);
    }
    return shadow(this, "description", description);
  }
  get serializable() {
    return {
      rawFilename: this.filename,
      filename: stripPath(this.filename),
      content: this.content,
      description: this.description
    };
  }
}

;// CONCATENATED MODULE: ./src/core/xml_parser.js

const XMLParserErrorCode = {
  NoError: 0,
  EndOfDocument: -1,
  UnterminatedCdat: -2,
  UnterminatedXmlDeclaration: -3,
  UnterminatedDoctypeDeclaration: -4,
  UnterminatedComment: -5,
  MalformedElement: -6,
  OutOfMemory: -7,
  UnterminatedAttributeValue: -8,
  UnterminatedElement: -9,
  ElementNeverBegun: -10
};
function isWhitespace(s, index) {
  const ch = s[index];
  return ch === " " || ch === "\n" || ch === "\r" || ch === "\t";
}
function isWhitespaceString(s) {
  for (let i = 0, ii = s.length; i < ii; i++) {
    if (!isWhitespace(s, i)) {
      return false;
    }
  }
  return true;
}
class XMLParserBase {
  _resolveEntities(s) {
    return s.replaceAll(/&([^;]+);/g, (all, entity) => {
      if (entity.substring(0, 2) === "#x") {
        return String.fromCodePoint(parseInt(entity.substring(2), 16));
      } else if (entity.substring(0, 1) === "#") {
        return String.fromCodePoint(parseInt(entity.substring(1), 10));
      }
      switch (entity) {
        case "lt":
          return "<";
        case "gt":
          return ">";
        case "amp":
          return "&";
        case "quot":
          return '"';
        case "apos":
          return "'";
      }
      return this.onResolveEntity(entity);
    });
  }
  _parseContent(s, start) {
    const attributes = [];
    let pos = start;
    function skipWs() {
      while (pos < s.length && isWhitespace(s, pos)) {
        ++pos;
      }
    }
    while (pos < s.length && !isWhitespace(s, pos) && s[pos] !== ">" && s[pos] !== "/") {
      ++pos;
    }
    const name = s.substring(start, pos);
    skipWs();
    while (pos < s.length && s[pos] !== ">" && s[pos] !== "/" && s[pos] !== "?") {
      skipWs();
      let attrName = "",
        attrValue = "";
      while (pos < s.length && !isWhitespace(s, pos) && s[pos] !== "=") {
        attrName += s[pos];
        ++pos;
      }
      skipWs();
      if (s[pos] !== "=") {
        return null;
      }
      ++pos;
      skipWs();
      const attrEndChar = s[pos];
      if (attrEndChar !== '"' && attrEndChar !== "'") {
        return null;
      }
      const attrEndIndex = s.indexOf(attrEndChar, ++pos);
      if (attrEndIndex < 0) {
        return null;
      }
      attrValue = s.substring(pos, attrEndIndex);
      attributes.push({
        name: attrName,
        value: this._resolveEntities(attrValue)
      });
      pos = attrEndIndex + 1;
      skipWs();
    }
    return {
      name,
      attributes,
      parsed: pos - start
    };
  }
  _parseProcessingInstruction(s, start) {
    let pos = start;
    function skipWs() {
      while (pos < s.length && isWhitespace(s, pos)) {
        ++pos;
      }
    }
    while (pos < s.length && !isWhitespace(s, pos) && s[pos] !== ">" && s[pos] !== "?" && s[pos] !== "/") {
      ++pos;
    }
    const name = s.substring(start, pos);
    skipWs();
    const attrStart = pos;
    while (pos < s.length && (s[pos] !== "?" || s[pos + 1] !== ">")) {
      ++pos;
    }
    const value = s.substring(attrStart, pos);
    return {
      name,
      value,
      parsed: pos - start
    };
  }
  parseXml(s) {
    let i = 0;
    while (i < s.length) {
      const ch = s[i];
      let j = i;
      if (ch === "<") {
        ++j;
        const ch2 = s[j];
        let q;
        switch (ch2) {
          case "/":
            ++j;
            q = s.indexOf(">", j);
            if (q < 0) {
              this.onError(XMLParserErrorCode.UnterminatedElement);
              return;
            }
            this.onEndElement(s.substring(j, q));
            j = q + 1;
            break;
          case "?":
            ++j;
            const pi = this._parseProcessingInstruction(s, j);
            if (s.substring(j + pi.parsed, j + pi.parsed + 2) !== "?>") {
              this.onError(XMLParserErrorCode.UnterminatedXmlDeclaration);
              return;
            }
            this.onPi(pi.name, pi.value);
            j += pi.parsed + 2;
            break;
          case "!":
            if (s.substring(j + 1, j + 3) === "--") {
              q = s.indexOf("-->", j + 3);
              if (q < 0) {
                this.onError(XMLParserErrorCode.UnterminatedComment);
                return;
              }
              this.onComment(s.substring(j + 3, q));
              j = q + 3;
            } else if (s.substring(j + 1, j + 8) === "[CDATA[") {
              q = s.indexOf("]]>", j + 8);
              if (q < 0) {
                this.onError(XMLParserErrorCode.UnterminatedCdat);
                return;
              }
              this.onCdata(s.substring(j + 8, q));
              j = q + 3;
            } else if (s.substring(j + 1, j + 8) === "DOCTYPE") {
              const q2 = s.indexOf("[", j + 8);
              let complexDoctype = false;
              q = s.indexOf(">", j + 8);
              if (q < 0) {
                this.onError(XMLParserErrorCode.UnterminatedDoctypeDeclaration);
                return;
              }
              if (q2 > 0 && q > q2) {
                q = s.indexOf("]>", j + 8);
                if (q < 0) {
                  this.onError(XMLParserErrorCode.UnterminatedDoctypeDeclaration);
                  return;
                }
                complexDoctype = true;
              }
              const doctypeContent = s.substring(j + 8, q + (complexDoctype ? 1 : 0));
              this.onDoctype(doctypeContent);
              j = q + (complexDoctype ? 2 : 1);
            } else {
              this.onError(XMLParserErrorCode.MalformedElement);
              return;
            }
            break;
          default:
            const content = this._parseContent(s, j);
            if (content === null) {
              this.onError(XMLParserErrorCode.MalformedElement);
              return;
            }
            let isClosed = false;
            if (s.substring(j + content.parsed, j + content.parsed + 2) === "/>") {
              isClosed = true;
            } else if (s.substring(j + content.parsed, j + content.parsed + 1) !== ">") {
              this.onError(XMLParserErrorCode.UnterminatedElement);
              return;
            }
            this.onBeginElement(content.name, content.attributes, isClosed);
            j += content.parsed + (isClosed ? 2 : 1);
            break;
        }
      } else {
        while (j < s.length && s[j] !== "<") {
          j++;
        }
        const text = s.substring(i, j);
        this.onText(this._resolveEntities(text));
      }
      i = j;
    }
  }
  onResolveEntity(name) {
    return `&${name};`;
  }
  onPi(name, value) {}
  onComment(text) {}
  onCdata(text) {}
  onDoctype(doctypeContent) {}
  onText(text) {}
  onBeginElement(name, attributes, isEmpty) {}
  onEndElement(name) {}
  onError(code) {}
}
class SimpleDOMNode {
  constructor(nodeName, nodeValue) {
    this.nodeName = nodeName;
    this.nodeValue = nodeValue;
    Object.defineProperty(this, "parentNode", {
      value: null,
      writable: true
    });
  }
  get firstChild() {
    return this.childNodes?.[0];
  }
  get nextSibling() {
    const childNodes = this.parentNode.childNodes;
    if (!childNodes) {
      return undefined;
    }
    const index = childNodes.indexOf(this);
    if (index === -1) {
      return undefined;
    }
    return childNodes[index + 1];
  }
  get textContent() {
    if (!this.childNodes) {
      return this.nodeValue || "";
    }
    return this.childNodes.map(function (child) {
      return child.textContent;
    }).join("");
  }
  get children() {
    return this.childNodes || [];
  }
  hasChildNodes() {
    return this.childNodes?.length > 0;
  }
  searchNode(paths, pos) {
    if (pos >= paths.length) {
      return this;
    }
    const component = paths[pos];
    if (component.name.startsWith("#") && pos < paths.length - 1) {
      return this.searchNode(paths, pos + 1);
    }
    const stack = [];
    let node = this;
    while (true) {
      if (component.name === node.nodeName) {
        if (component.pos === 0) {
          const res = node.searchNode(paths, pos + 1);
          if (res !== null) {
            return res;
          }
        } else if (stack.length === 0) {
          return null;
        } else {
          const [parent] = stack.pop();
          let siblingPos = 0;
          for (const child of parent.childNodes) {
            if (component.name === child.nodeName) {
              if (siblingPos === component.pos) {
                return child.searchNode(paths, pos + 1);
              }
              siblingPos++;
            }
          }
          return node.searchNode(paths, pos + 1);
        }
      }
      if (node.childNodes?.length > 0) {
        stack.push([node, 0]);
        node = node.childNodes[0];
      } else if (stack.length === 0) {
        return null;
      } else {
        while (stack.length !== 0) {
          const [parent, currentPos] = stack.pop();
          const newPos = currentPos + 1;
          if (newPos < parent.childNodes.length) {
            stack.push([parent, newPos]);
            node = parent.childNodes[newPos];
            break;
          }
        }
        if (stack.length === 0) {
          return null;
        }
      }
    }
  }
  dump(buffer) {
    if (this.nodeName === "#text") {
      buffer.push(encodeToXmlString(this.nodeValue));
      return;
    }
    buffer.push(`<${this.nodeName}`);
    if (this.attributes) {
      for (const attribute of this.attributes) {
        buffer.push(` ${attribute.name}="${encodeToXmlString(attribute.value)}"`);
      }
    }
    if (this.hasChildNodes()) {
      buffer.push(">");
      for (const child of this.childNodes) {
        child.dump(buffer);
      }
      buffer.push(`</${this.nodeName}>`);
    } else if (this.nodeValue) {
      buffer.push(`>${encodeToXmlString(this.nodeValue)}</${this.nodeName}>`);
    } else {
      buffer.push("/>");
    }
  }
}
class SimpleXMLParser extends XMLParserBase {
  constructor({
    hasAttributes = false,
    lowerCaseName = false
  }) {
    super();
    this._currentFragment = null;
    this._stack = null;
    this._errorCode = XMLParserErrorCode.NoError;
    this._hasAttributes = hasAttributes;
    this._lowerCaseName = lowerCaseName;
  }
  parseFromString(data) {
    this._currentFragment = [];
    this._stack = [];
    this._errorCode = XMLParserErrorCode.NoError;
    this.parseXml(data);
    if (this._errorCode !== XMLParserErrorCode.NoError) {
      return undefined;
    }
    const [documentElement] = this._currentFragment;
    if (!documentElement) {
      return undefined;
    }
    return {
      documentElement
    };
  }
  onText(text) {
    if (isWhitespaceString(text)) {
      return;
    }
    const node = new SimpleDOMNode("#text", text);
    this._currentFragment.push(node);
  }
  onCdata(text) {
    const node = new SimpleDOMNode("#text", text);
    this._currentFragment.push(node);
  }
  onBeginElement(name, attributes, isEmpty) {
    if (this._lowerCaseName) {
      name = name.toLowerCase();
    }
    const node = new SimpleDOMNode(name);
    node.childNodes = [];
    if (this._hasAttributes) {
      node.attributes = attributes;
    }
    this._currentFragment.push(node);
    if (isEmpty) {
      return;
    }
    this._stack.push(this._currentFragment);
    this._currentFragment = node.childNodes;
  }
  onEndElement(name) {
    this._currentFragment = this._stack.pop() || [];
    const lastElement = this._currentFragment.at(-1);
    if (!lastElement) {
      return null;
    }
    for (const childNode of lastElement.childNodes) {
      childNode.parentNode = lastElement;
    }
    return lastElement;
  }
  onError(code) {
    this._errorCode = code;
  }
}

;// CONCATENATED MODULE: ./src/core/metadata_parser.js

class MetadataParser {
  constructor(data) {
    data = this._repair(data);
    const parser = new SimpleXMLParser({
      lowerCaseName: true
    });
    const xmlDocument = parser.parseFromString(data);
    this._metadataMap = new Map();
    this._data = data;
    if (xmlDocument) {
      this._parse(xmlDocument);
    }
  }
  _repair(data) {
    return data.replace(/^[^<]+/, "").replaceAll(/>\\376\\377([^<]+)/g, function (all, codes) {
      const bytes = codes.replaceAll(/\\([0-3])([0-7])([0-7])/g, function (code, d1, d2, d3) {
        return String.fromCharCode(d1 * 64 + d2 * 8 + d3 * 1);
      }).replaceAll(/&(amp|apos|gt|lt|quot);/g, function (str, name) {
        switch (name) {
          case "amp":
            return "&";
          case "apos":
            return "'";
          case "gt":
            return ">";
          case "lt":
            return "<";
          case "quot":
            return '"';
        }
        throw new Error(`_repair: ${name} isn't defined.`);
      });
      const charBuf = [">"];
      for (let i = 0, ii = bytes.length; i < ii; i += 2) {
        const code = bytes.charCodeAt(i) * 256 + bytes.charCodeAt(i + 1);
        if (code >= 32 && code < 127 && code !== 60 && code !== 62 && code !== 38) {
          charBuf.push(String.fromCharCode(code));
        } else {
          charBuf.push("&#x" + (0x10000 + code).toString(16).substring(1) + ";");
        }
      }
      return charBuf.join("");
    });
  }
  _getSequence(entry) {
    const name = entry.nodeName;
    if (name !== "rdf:bag" && name !== "rdf:seq" && name !== "rdf:alt") {
      return null;
    }
    return entry.childNodes.filter(node => node.nodeName === "rdf:li");
  }
  _parseArray(entry) {
    if (!entry.hasChildNodes()) {
      return;
    }
    const [seqNode] = entry.childNodes;
    const sequence = this._getSequence(seqNode) || [];
    this._metadataMap.set(entry.nodeName, sequence.map(node => node.textContent.trim()));
  }
  _parse(xmlDocument) {
    let rdf = xmlDocument.documentElement;
    if (rdf.nodeName !== "rdf:rdf") {
      rdf = rdf.firstChild;
      while (rdf && rdf.nodeName !== "rdf:rdf") {
        rdf = rdf.nextSibling;
      }
    }
    if (!rdf || rdf.nodeName !== "rdf:rdf" || !rdf.hasChildNodes()) {
      return;
    }
    for (const desc of rdf.childNodes) {
      if (desc.nodeName !== "rdf:description") {
        continue;
      }
      for (const entry of desc.childNodes) {
        const name = entry.nodeName;
        switch (name) {
          case "#text":
            continue;
          case "dc:creator":
          case "dc:subject":
            this._parseArray(entry);
            continue;
        }
        this._metadataMap.set(name, entry.textContent.trim());
      }
    }
  }
  get serializable() {
    return {
      parsedData: this._metadataMap,
      rawData: this._data
    };
  }
}

;// CONCATENATED MODULE: ./src/core/decrypt_stream.js

const chunkSize = 512;
class DecryptStream extends DecodeStream {
  constructor(str, maybeLength, decrypt) {
    super(maybeLength);
    this.str = str;
    this.dict = str.dict;
    this.decrypt = decrypt;
    this.nextChunk = null;
    this.initialized = false;
  }
  readBlock() {
    let chunk;
    if (this.initialized) {
      chunk = this.nextChunk;
    } else {
      chunk = this.str.getBytes(chunkSize);
      this.initialized = true;
    }
    if (!chunk || chunk.length === 0) {
      this.eof = true;
      return;
    }
    this.nextChunk = this.str.getBytes(chunkSize);
    const hasMoreData = this.nextChunk?.length > 0;
    const decrypt = this.decrypt;
    chunk = decrypt(chunk, !hasMoreData);
    const bufferLength = this.bufferLength,
      newLength = bufferLength + chunk.length,
      buffer = this.ensureBuffer(newLength);
    buffer.set(chunk, bufferLength);
    this.bufferLength = newLength;
  }
}

;// CONCATENATED MODULE: ./src/core/crypto.js



class ARCFourCipher {
  constructor(key) {
    this.a = 0;
    this.b = 0;
    const s = new Uint8Array(256);
    const keyLength = key.length;
    for (let i = 0; i < 256; ++i) {
      s[i] = i;
    }
    for (let i = 0, j = 0; i < 256; ++i) {
      const tmp = s[i];
      j = j + tmp + key[i % keyLength] & 0xff;
      s[i] = s[j];
      s[j] = tmp;
    }
    this.s = s;
  }
  encryptBlock(data) {
    let a = this.a,
      b = this.b;
    const s = this.s;
    const n = data.length;
    const output = new Uint8Array(n);
    for (let i = 0; i < n; ++i) {
      a = a + 1 & 0xff;
      const tmp = s[a];
      b = b + tmp & 0xff;
      const tmp2 = s[b];
      s[a] = tmp2;
      s[b] = tmp;
      output[i] = data[i] ^ s[tmp + tmp2 & 0xff];
    }
    this.a = a;
    this.b = b;
    return output;
  }
  decryptBlock(data) {
    return this.encryptBlock(data);
  }
  encrypt(data) {
    return this.encryptBlock(data);
  }
}
const calculateMD5 = function calculateMD5Closure() {
  const r = new Uint8Array([7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21]);
  const k = new Int32Array([-680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426, -1473231341, -45705983, 1770035416, -1958414417, -42063, -1990404162, 1804603682, -40341101, -1502002290, 1236535329, -165796510, -1069501632, 643717713, -373897302, -701558691, 38016083, -660478335, -405537848, 568446438, -1019803690, -187363961, 1163531501, -1444681467, -51403784, 1735328473, -1926607734, -378558, -2022574463, 1839030562, -35309556, -1530992060, 1272893353, -155497632, -1094730640, 681279174, -358537222, -722521979, 76029189, -640364487, -421815835, 530742520, -995338651, -198630844, 1126891415, -1416354905, -57434055, 1700485571, -1894986606, -1051523, -2054922799, 1873313359, -30611744, -1560198380, 1309151649, -145523070, -1120210379, 718787259, -343485551]);
  function hash(data, offset, length) {
    let h0 = 1732584193,
      h1 = -271733879,
      h2 = -1732584194,
      h3 = 271733878;
    const paddedLength = length + 72 & ~63;
    const padded = new Uint8Array(paddedLength);
    let i, j;
    for (i = 0; i < length; ++i) {
      padded[i] = data[offset++];
    }
    padded[i++] = 0x80;
    const n = paddedLength - 8;
    while (i < n) {
      padded[i++] = 0;
    }
    padded[i++] = length << 3 & 0xff;
    padded[i++] = length >> 5 & 0xff;
    padded[i++] = length >> 13 & 0xff;
    padded[i++] = length >> 21 & 0xff;
    padded[i++] = length >>> 29 & 0xff;
    padded[i++] = 0;
    padded[i++] = 0;
    padded[i++] = 0;
    const w = new Int32Array(16);
    for (i = 0; i < paddedLength;) {
      for (j = 0; j < 16; ++j, i += 4) {
        w[j] = padded[i] | padded[i + 1] << 8 | padded[i + 2] << 16 | padded[i + 3] << 24;
      }
      let a = h0,
        b = h1,
        c = h2,
        d = h3,
        f,
        g;
      for (j = 0; j < 64; ++j) {
        if (j < 16) {
          f = b & c | ~b & d;
          g = j;
        } else if (j < 32) {
          f = d & b | ~d & c;
          g = 5 * j + 1 & 15;
        } else if (j < 48) {
          f = b ^ c ^ d;
          g = 3 * j + 5 & 15;
        } else {
          f = c ^ (b | ~d);
          g = 7 * j & 15;
        }
        const tmp = d,
          rotateArg = a + f + k[j] + w[g] | 0,
          rotate = r[j];
        d = c;
        c = b;
        b = b + (rotateArg << rotate | rotateArg >>> 32 - rotate) | 0;
        a = tmp;
      }
      h0 = h0 + a | 0;
      h1 = h1 + b | 0;
      h2 = h2 + c | 0;
      h3 = h3 + d | 0;
    }
    return new Uint8Array([h0 & 0xFF, h0 >> 8 & 0xFF, h0 >> 16 & 0xFF, h0 >>> 24 & 0xFF, h1 & 0xFF, h1 >> 8 & 0xFF, h1 >> 16 & 0xFF, h1 >>> 24 & 0xFF, h2 & 0xFF, h2 >> 8 & 0xFF, h2 >> 16 & 0xFF, h2 >>> 24 & 0xFF, h3 & 0xFF, h3 >> 8 & 0xFF, h3 >> 16 & 0xFF, h3 >>> 24 & 0xFF]);
  }
  return hash;
}();
class Word64 {
  constructor(highInteger, lowInteger) {
    this.high = highInteger | 0;
    this.low = lowInteger | 0;
  }
  and(word) {
    this.high &= word.high;
    this.low &= word.low;
  }
  xor(word) {
    this.high ^= word.high;
    this.low ^= word.low;
  }
  or(word) {
    this.high |= word.high;
    this.low |= word.low;
  }
  shiftRight(places) {
    if (places >= 32) {
      this.low = this.high >>> places - 32 | 0;
      this.high = 0;
    } else {
      this.low = this.low >>> places | this.high << 32 - places;
      this.high = this.high >>> places | 0;
    }
  }
  shiftLeft(places) {
    if (places >= 32) {
      this.high = this.low << places - 32;
      this.low = 0;
    } else {
      this.high = this.high << places | this.low >>> 32 - places;
      this.low <<= places;
    }
  }
  rotateRight(places) {
    let low, high;
    if (places & 32) {
      high = this.low;
      low = this.high;
    } else {
      low = this.low;
      high = this.high;
    }
    places &= 31;
    this.low = low >>> places | high << 32 - places;
    this.high = high >>> places | low << 32 - places;
  }
  not() {
    this.high = ~this.high;
    this.low = ~this.low;
  }
  add(word) {
    const lowAdd = (this.low >>> 0) + (word.low >>> 0);
    let highAdd = (this.high >>> 0) + (word.high >>> 0);
    if (lowAdd > 0xffffffff) {
      highAdd += 1;
    }
    this.low = lowAdd | 0;
    this.high = highAdd | 0;
  }
  copyTo(bytes, offset) {
    bytes[offset] = this.high >>> 24 & 0xff;
    bytes[offset + 1] = this.high >> 16 & 0xff;
    bytes[offset + 2] = this.high >> 8 & 0xff;
    bytes[offset + 3] = this.high & 0xff;
    bytes[offset + 4] = this.low >>> 24 & 0xff;
    bytes[offset + 5] = this.low >> 16 & 0xff;
    bytes[offset + 6] = this.low >> 8 & 0xff;
    bytes[offset + 7] = this.low & 0xff;
  }
  assign(word) {
    this.high = word.high;
    this.low = word.low;
  }
}
const calculateSHA256 = function calculateSHA256Closure() {
  function rotr(x, n) {
    return x >>> n | x << 32 - n;
  }
  function ch(x, y, z) {
    return x & y ^ ~x & z;
  }
  function maj(x, y, z) {
    return x & y ^ x & z ^ y & z;
  }
  function sigma(x) {
    return rotr(x, 2) ^ rotr(x, 13) ^ rotr(x, 22);
  }
  function sigmaPrime(x) {
    return rotr(x, 6) ^ rotr(x, 11) ^ rotr(x, 25);
  }
  function littleSigma(x) {
    return rotr(x, 7) ^ rotr(x, 18) ^ x >>> 3;
  }
  function littleSigmaPrime(x) {
    return rotr(x, 17) ^ rotr(x, 19) ^ x >>> 10;
  }
  const k = [0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2];
  function hash(data, offset, length) {
    let h0 = 0x6a09e667,
      h1 = 0xbb67ae85,
      h2 = 0x3c6ef372,
      h3 = 0xa54ff53a,
      h4 = 0x510e527f,
      h5 = 0x9b05688c,
      h6 = 0x1f83d9ab,
      h7 = 0x5be0cd19;
    const paddedLength = Math.ceil((length + 9) / 64) * 64;
    const padded = new Uint8Array(paddedLength);
    let i, j;
    for (i = 0; i < length; ++i) {
      padded[i] = data[offset++];
    }
    padded[i++] = 0x80;
    const n = paddedLength - 8;
    while (i < n) {
      padded[i++] = 0;
    }
    padded[i++] = 0;
    padded[i++] = 0;
    padded[i++] = 0;
    padded[i++] = length >>> 29 & 0xff;
    padded[i++] = length >> 21 & 0xff;
    padded[i++] = length >> 13 & 0xff;
    padded[i++] = length >> 5 & 0xff;
    padded[i++] = length << 3 & 0xff;
    const w = new Uint32Array(64);
    for (i = 0; i < paddedLength;) {
      for (j = 0; j < 16; ++j) {
        w[j] = padded[i] << 24 | padded[i + 1] << 16 | padded[i + 2] << 8 | padded[i + 3];
        i += 4;
      }
      for (j = 16; j < 64; ++j) {
        w[j] = littleSigmaPrime(w[j - 2]) + w[j - 7] + littleSigma(w[j - 15]) + w[j - 16] | 0;
      }
      let a = h0,
        b = h1,
        c = h2,
        d = h3,
        e = h4,
        f = h5,
        g = h6,
        h = h7,
        t1,
        t2;
      for (j = 0; j < 64; ++j) {
        t1 = h + sigmaPrime(e) + ch(e, f, g) + k[j] + w[j];
        t2 = sigma(a) + maj(a, b, c);
        h = g;
        g = f;
        f = e;
        e = d + t1 | 0;
        d = c;
        c = b;
        b = a;
        a = t1 + t2 | 0;
      }
      h0 = h0 + a | 0;
      h1 = h1 + b | 0;
      h2 = h2 + c | 0;
      h3 = h3 + d | 0;
      h4 = h4 + e | 0;
      h5 = h5 + f | 0;
      h6 = h6 + g | 0;
      h7 = h7 + h | 0;
    }
    return new Uint8Array([h0 >> 24 & 0xFF, h0 >> 16 & 0xFF, h0 >> 8 & 0xFF, h0 & 0xFF, h1 >> 24 & 0xFF, h1 >> 16 & 0xFF, h1 >> 8 & 0xFF, h1 & 0xFF, h2 >> 24 & 0xFF, h2 >> 16 & 0xFF, h2 >> 8 & 0xFF, h2 & 0xFF, h3 >> 24 & 0xFF, h3 >> 16 & 0xFF, h3 >> 8 & 0xFF, h3 & 0xFF, h4 >> 24 & 0xFF, h4 >> 16 & 0xFF, h4 >> 8 & 0xFF, h4 & 0xFF, h5 >> 24 & 0xFF, h5 >> 16 & 0xFF, h5 >> 8 & 0xFF, h5 & 0xFF, h6 >> 24 & 0xFF, h6 >> 16 & 0xFF, h6 >> 8 & 0xFF, h6 & 0xFF, h7 >> 24 & 0xFF, h7 >> 16 & 0xFF, h7 >> 8 & 0xFF, h7 & 0xFF]);
  }
  return hash;
}();
const calculateSHA512 = function calculateSHA512Closure() {
  function ch(result, x, y, z, tmp) {
    result.assign(x);
    result.and(y);
    tmp.assign(x);
    tmp.not();
    tmp.and(z);
    result.xor(tmp);
  }
  function maj(result, x, y, z, tmp) {
    result.assign(x);
    result.and(y);
    tmp.assign(x);
    tmp.and(z);
    result.xor(tmp);
    tmp.assign(y);
    tmp.and(z);
    result.xor(tmp);
  }
  function sigma(result, x, tmp) {
    result.assign(x);
    result.rotateRight(28);
    tmp.assign(x);
    tmp.rotateRight(34);
    result.xor(tmp);
    tmp.assign(x);
    tmp.rotateRight(39);
    result.xor(tmp);
  }
  function sigmaPrime(result, x, tmp) {
    result.assign(x);
    result.rotateRight(14);
    tmp.assign(x);
    tmp.rotateRight(18);
    result.xor(tmp);
    tmp.assign(x);
    tmp.rotateRight(41);
    result.xor(tmp);
  }
  function littleSigma(result, x, tmp) {
    result.assign(x);
    result.rotateRight(1);
    tmp.assign(x);
    tmp.rotateRight(8);
    result.xor(tmp);
    tmp.assign(x);
    tmp.shiftRight(7);
    result.xor(tmp);
  }
  function littleSigmaPrime(result, x, tmp) {
    result.assign(x);
    result.rotateRight(19);
    tmp.assign(x);
    tmp.rotateRight(61);
    result.xor(tmp);
    tmp.assign(x);
    tmp.shiftRight(6);
    result.xor(tmp);
  }
  const k = [new Word64(0x428a2f98, 0xd728ae22), new Word64(0x71374491, 0x23ef65cd), new Word64(0xb5c0fbcf, 0xec4d3b2f), new Word64(0xe9b5dba5, 0x8189dbbc), new Word64(0x3956c25b, 0xf348b538), new Word64(0x59f111f1, 0xb605d019), new Word64(0x923f82a4, 0xaf194f9b), new Word64(0xab1c5ed5, 0xda6d8118), new Word64(0xd807aa98, 0xa3030242), new Word64(0x12835b01, 0x45706fbe), new Word64(0x243185be, 0x4ee4b28c), new Word64(0x550c7dc3, 0xd5ffb4e2), new Word64(0x72be5d74, 0xf27b896f), new Word64(0x80deb1fe, 0x3b1696b1), new Word64(0x9bdc06a7, 0x25c71235), new Word64(0xc19bf174, 0xcf692694), new Word64(0xe49b69c1, 0x9ef14ad2), new Word64(0xefbe4786, 0x384f25e3), new Word64(0x0fc19dc6, 0x8b8cd5b5), new Word64(0x240ca1cc, 0x77ac9c65), new Word64(0x2de92c6f, 0x592b0275), new Word64(0x4a7484aa, 0x6ea6e483), new Word64(0x5cb0a9dc, 0xbd41fbd4), new Word64(0x76f988da, 0x831153b5), new Word64(0x983e5152, 0xee66dfab), new Word64(0xa831c66d, 0x2db43210), new Word64(0xb00327c8, 0x98fb213f), new Word64(0xbf597fc7, 0xbeef0ee4), new Word64(0xc6e00bf3, 0x3da88fc2), new Word64(0xd5a79147, 0x930aa725), new Word64(0x06ca6351, 0xe003826f), new Word64(0x14292967, 0x0a0e6e70), new Word64(0x27b70a85, 0x46d22ffc), new Word64(0x2e1b2138, 0x5c26c926), new Word64(0x4d2c6dfc, 0x5ac42aed), new Word64(0x53380d13, 0x9d95b3df), new Word64(0x650a7354, 0x8baf63de), new Word64(0x766a0abb, 0x3c77b2a8), new Word64(0x81c2c92e, 0x47edaee6), new Word64(0x92722c85, 0x1482353b), new Word64(0xa2bfe8a1, 0x4cf10364), new Word64(0xa81a664b, 0xbc423001), new Word64(0xc24b8b70, 0xd0f89791), new Word64(0xc76c51a3, 0x0654be30), new Word64(0xd192e819, 0xd6ef5218), new Word64(0xd6990624, 0x5565a910), new Word64(0xf40e3585, 0x5771202a), new Word64(0x106aa070, 0x32bbd1b8), new Word64(0x19a4c116, 0xb8d2d0c8), new Word64(0x1e376c08, 0x5141ab53), new Word64(0x2748774c, 0xdf8eeb99), new Word64(0x34b0bcb5, 0xe19b48a8), new Word64(0x391c0cb3, 0xc5c95a63), new Word64(0x4ed8aa4a, 0xe3418acb), new Word64(0x5b9cca4f, 0x7763e373), new Word64(0x682e6ff3, 0xd6b2b8a3), new Word64(0x748f82ee, 0x5defb2fc), new Word64(0x78a5636f, 0x43172f60), new Word64(0x84c87814, 0xa1f0ab72), new Word64(0x8cc70208, 0x1a6439ec), new Word64(0x90befffa, 0x23631e28), new Word64(0xa4506ceb, 0xde82bde9), new Word64(0xbef9a3f7, 0xb2c67915), new Word64(0xc67178f2, 0xe372532b), new Word64(0xca273ece, 0xea26619c), new Word64(0xd186b8c7, 0x21c0c207), new Word64(0xeada7dd6, 0xcde0eb1e), new Word64(0xf57d4f7f, 0xee6ed178), new Word64(0x06f067aa, 0x72176fba), new Word64(0x0a637dc5, 0xa2c898a6), new Word64(0x113f9804, 0xbef90dae), new Word64(0x1b710b35, 0x131c471b), new Word64(0x28db77f5, 0x23047d84), new Word64(0x32caab7b, 0x40c72493), new Word64(0x3c9ebe0a, 0x15c9bebc), new Word64(0x431d67c4, 0x9c100d4c), new Word64(0x4cc5d4be, 0xcb3e42b6), new Word64(0x597f299c, 0xfc657e2a), new Word64(0x5fcb6fab, 0x3ad6faec), new Word64(0x6c44198c, 0x4a475817)];
  function hash(data, offset, length, mode384 = false) {
    let h0, h1, h2, h3, h4, h5, h6, h7;
    if (!mode384) {
      h0 = new Word64(0x6a09e667, 0xf3bcc908);
      h1 = new Word64(0xbb67ae85, 0x84caa73b);
      h2 = new Word64(0x3c6ef372, 0xfe94f82b);
      h3 = new Word64(0xa54ff53a, 0x5f1d36f1);
      h4 = new Word64(0x510e527f, 0xade682d1);
      h5 = new Word64(0x9b05688c, 0x2b3e6c1f);
      h6 = new Word64(0x1f83d9ab, 0xfb41bd6b);
      h7 = new Word64(0x5be0cd19, 0x137e2179);
    } else {
      h0 = new Word64(0xcbbb9d5d, 0xc1059ed8);
      h1 = new Word64(0x629a292a, 0x367cd507);
      h2 = new Word64(0x9159015a, 0x3070dd17);
      h3 = new Word64(0x152fecd8, 0xf70e5939);
      h4 = new Word64(0x67332667, 0xffc00b31);
      h5 = new Word64(0x8eb44a87, 0x68581511);
      h6 = new Word64(0xdb0c2e0d, 0x64f98fa7);
      h7 = new Word64(0x47b5481d, 0xbefa4fa4);
    }
    const paddedLength = Math.ceil((length + 17) / 128) * 128;
    const padded = new Uint8Array(paddedLength);
    let i, j;
    for (i = 0; i < length; ++i) {
      padded[i] = data[offset++];
    }
    padded[i++] = 0x80;
    const n = paddedLength - 16;
    while (i < n) {
      padded[i++] = 0;
    }
    padded[i++] = 0;
    padded[i++] = 0;
    padded[i++] = 0;
    padded[i++] = 0;
    padded[i++] = 0;
    padded[i++] = 0;
    padded[i++] = 0;
    padded[i++] = 0;
    padded[i++] = 0;
    padded[i++] = 0;
    padded[i++] = 0;
    padded[i++] = length >>> 29 & 0xff;
    padded[i++] = length >> 21 & 0xff;
    padded[i++] = length >> 13 & 0xff;
    padded[i++] = length >> 5 & 0xff;
    padded[i++] = length << 3 & 0xff;
    const w = new Array(80);
    for (i = 0; i < 80; i++) {
      w[i] = new Word64(0, 0);
    }
    let a = new Word64(0, 0),
      b = new Word64(0, 0),
      c = new Word64(0, 0);
    let d = new Word64(0, 0),
      e = new Word64(0, 0),
      f = new Word64(0, 0);
    let g = new Word64(0, 0),
      h = new Word64(0, 0);
    const t1 = new Word64(0, 0),
      t2 = new Word64(0, 0);
    const tmp1 = new Word64(0, 0),
      tmp2 = new Word64(0, 0);
    let tmp3;
    for (i = 0; i < paddedLength;) {
      for (j = 0; j < 16; ++j) {
        w[j].high = padded[i] << 24 | padded[i + 1] << 16 | padded[i + 2] << 8 | padded[i + 3];
        w[j].low = padded[i + 4] << 24 | padded[i + 5] << 16 | padded[i + 6] << 8 | padded[i + 7];
        i += 8;
      }
      for (j = 16; j < 80; ++j) {
        tmp3 = w[j];
        littleSigmaPrime(tmp3, w[j - 2], tmp2);
        tmp3.add(w[j - 7]);
        littleSigma(tmp1, w[j - 15], tmp2);
        tmp3.add(tmp1);
        tmp3.add(w[j - 16]);
      }
      a.assign(h0);
      b.assign(h1);
      c.assign(h2);
      d.assign(h3);
      e.assign(h4);
      f.assign(h5);
      g.assign(h6);
      h.assign(h7);
      for (j = 0; j < 80; ++j) {
        t1.assign(h);
        sigmaPrime(tmp1, e, tmp2);
        t1.add(tmp1);
        ch(tmp1, e, f, g, tmp2);
        t1.add(tmp1);
        t1.add(k[j]);
        t1.add(w[j]);
        sigma(t2, a, tmp2);
        maj(tmp1, a, b, c, tmp2);
        t2.add(tmp1);
        tmp3 = h;
        h = g;
        g = f;
        f = e;
        d.add(t1);
        e = d;
        d = c;
        c = b;
        b = a;
        tmp3.assign(t1);
        tmp3.add(t2);
        a = tmp3;
      }
      h0.add(a);
      h1.add(b);
      h2.add(c);
      h3.add(d);
      h4.add(e);
      h5.add(f);
      h6.add(g);
      h7.add(h);
    }
    let result;
    if (!mode384) {
      result = new Uint8Array(64);
      h0.copyTo(result, 0);
      h1.copyTo(result, 8);
      h2.copyTo(result, 16);
      h3.copyTo(result, 24);
      h4.copyTo(result, 32);
      h5.copyTo(result, 40);
      h6.copyTo(result, 48);
      h7.copyTo(result, 56);
    } else {
      result = new Uint8Array(48);
      h0.copyTo(result, 0);
      h1.copyTo(result, 8);
      h2.copyTo(result, 16);
      h3.copyTo(result, 24);
      h4.copyTo(result, 32);
      h5.copyTo(result, 40);
    }
    return result;
  }
  return hash;
}();
function calculateSHA384(data, offset, length) {
  return calculateSHA512(data, offset, length, true);
}
class NullCipher {
  decryptBlock(data) {
    return data;
  }
  encrypt(data) {
    return data;
  }
}
class AESBaseCipher {
  constructor() {
    this._s = new Uint8Array([0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16]);
    this._inv_s = new Uint8Array([0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d]);
    this._mix = new Uint32Array([0x00000000, 0x0e090d0b, 0x1c121a16, 0x121b171d, 0x3824342c, 0x362d3927, 0x24362e3a, 0x2a3f2331, 0x70486858, 0x7e416553, 0x6c5a724e, 0x62537f45, 0x486c5c74, 0x4665517f, 0x547e4662, 0x5a774b69, 0xe090d0b0, 0xee99ddbb, 0xfc82caa6, 0xf28bc7ad, 0xd8b4e49c, 0xd6bde997, 0xc4a6fe8a, 0xcaaff381, 0x90d8b8e8, 0x9ed1b5e3, 0x8ccaa2fe, 0x82c3aff5, 0xa8fc8cc4, 0xa6f581cf, 0xb4ee96d2, 0xbae79bd9, 0xdb3bbb7b, 0xd532b670, 0xc729a16d, 0xc920ac66, 0xe31f8f57, 0xed16825c, 0xff0d9541, 0xf104984a, 0xab73d323, 0xa57ade28, 0xb761c935, 0xb968c43e, 0x9357e70f, 0x9d5eea04, 0x8f45fd19, 0x814cf012, 0x3bab6bcb, 0x35a266c0, 0x27b971dd, 0x29b07cd6, 0x038f5fe7, 0x0d8652ec, 0x1f9d45f1, 0x119448fa, 0x4be30393, 0x45ea0e98, 0x57f11985, 0x59f8148e, 0x73c737bf, 0x7dce3ab4, 0x6fd52da9, 0x61dc20a2, 0xad766df6, 0xa37f60fd, 0xb16477e0, 0xbf6d7aeb, 0x955259da, 0x9b5b54d1, 0x894043cc, 0x87494ec7, 0xdd3e05ae, 0xd33708a5, 0xc12c1fb8, 0xcf2512b3, 0xe51a3182, 0xeb133c89, 0xf9082b94, 0xf701269f, 0x4de6bd46, 0x43efb04d, 0x51f4a750, 0x5ffdaa5b, 0x75c2896a, 0x7bcb8461, 0x69d0937c, 0x67d99e77, 0x3daed51e, 0x33a7d815, 0x21bccf08, 0x2fb5c203, 0x058ae132, 0x0b83ec39, 0x1998fb24, 0x1791f62f, 0x764dd68d, 0x7844db86, 0x6a5fcc9b, 0x6456c190, 0x4e69e2a1, 0x4060efaa, 0x527bf8b7, 0x5c72f5bc, 0x0605bed5, 0x080cb3de, 0x1a17a4c3, 0x141ea9c8, 0x3e218af9, 0x302887f2, 0x223390ef, 0x2c3a9de4, 0x96dd063d, 0x98d40b36, 0x8acf1c2b, 0x84c61120, 0xaef93211, 0xa0f03f1a, 0xb2eb2807, 0xbce2250c, 0xe6956e65, 0xe89c636e, 0xfa877473, 0xf48e7978, 0xdeb15a49, 0xd0b85742, 0xc2a3405f, 0xccaa4d54, 0x41ecdaf7, 0x4fe5d7fc, 0x5dfec0e1, 0x53f7cdea, 0x79c8eedb, 0x77c1e3d0, 0x65daf4cd, 0x6bd3f9c6, 0x31a4b2af, 0x3fadbfa4, 0x2db6a8b9, 0x23bfa5b2, 0x09808683, 0x07898b88, 0x15929c95, 0x1b9b919e, 0xa17c0a47, 0xaf75074c, 0xbd6e1051, 0xb3671d5a, 0x99583e6b, 0x97513360, 0x854a247d, 0x8b432976, 0xd134621f, 0xdf3d6f14, 0xcd267809, 0xc32f7502, 0xe9105633, 0xe7195b38, 0xf5024c25, 0xfb0b412e, 0x9ad7618c, 0x94de6c87, 0x86c57b9a, 0x88cc7691, 0xa2f355a0, 0xacfa58ab, 0xbee14fb6, 0xb0e842bd, 0xea9f09d4, 0xe49604df, 0xf68d13c2, 0xf8841ec9, 0xd2bb3df8, 0xdcb230f3, 0xcea927ee, 0xc0a02ae5, 0x7a47b13c, 0x744ebc37, 0x6655ab2a, 0x685ca621, 0x42638510, 0x4c6a881b, 0x5e719f06, 0x5078920d, 0x0a0fd964, 0x0406d46f, 0x161dc372, 0x1814ce79, 0x322bed48, 0x3c22e043, 0x2e39f75e, 0x2030fa55, 0xec9ab701, 0xe293ba0a, 0xf088ad17, 0xfe81a01c, 0xd4be832d, 0xdab78e26, 0xc8ac993b, 0xc6a59430, 0x9cd2df59, 0x92dbd252, 0x80c0c54f, 0x8ec9c844, 0xa4f6eb75, 0xaaffe67e, 0xb8e4f163, 0xb6edfc68, 0x0c0a67b1, 0x02036aba, 0x10187da7, 0x1e1170ac, 0x342e539d, 0x3a275e96, 0x283c498b, 0x26354480, 0x7c420fe9, 0x724b02e2, 0x605015ff, 0x6e5918f4, 0x44663bc5, 0x4a6f36ce, 0x587421d3, 0x567d2cd8, 0x37a10c7a, 0x39a80171, 0x2bb3166c, 0x25ba1b67, 0x0f853856, 0x018c355d, 0x13972240, 0x1d9e2f4b, 0x47e96422, 0x49e06929, 0x5bfb7e34, 0x55f2733f, 0x7fcd500e, 0x71c45d05, 0x63df4a18, 0x6dd64713, 0xd731dcca, 0xd938d1c1, 0xcb23c6dc, 0xc52acbd7, 0xef15e8e6, 0xe11ce5ed, 0xf307f2f0, 0xfd0efffb, 0xa779b492, 0xa970b999, 0xbb6bae84, 0xb562a38f, 0x9f5d80be, 0x91548db5, 0x834f9aa8, 0x8d4697a3]);
    this._mixCol = new Uint8Array(256);
    for (let i = 0; i < 256; i++) {
      this._mixCol[i] = i < 128 ? i << 1 : i << 1 ^ 0x1b;
    }
    this.buffer = new Uint8Array(16);
    this.bufferPosition = 0;
  }
  _expandKey(cipherKey) {
    unreachable("Cannot call `_expandKey` on the base class");
  }
  _decrypt(input, key) {
    let t, u, v;
    const state = new Uint8Array(16);
    state.set(input);
    for (let j = 0, k = this._keySize; j < 16; ++j, ++k) {
      state[j] ^= key[k];
    }
    for (let i = this._cyclesOfRepetition - 1; i >= 1; --i) {
      t = state[13];
      state[13] = state[9];
      state[9] = state[5];
      state[5] = state[1];
      state[1] = t;
      t = state[14];
      u = state[10];
      state[14] = state[6];
      state[10] = state[2];
      state[6] = t;
      state[2] = u;
      t = state[15];
      u = state[11];
      v = state[7];
      state[15] = state[3];
      state[11] = t;
      state[7] = u;
      state[3] = v;
      for (let j = 0; j < 16; ++j) {
        state[j] = this._inv_s[state[j]];
      }
      for (let j = 0, k = i * 16; j < 16; ++j, ++k) {
        state[j] ^= key[k];
      }
      for (let j = 0; j < 16; j += 4) {
        const s0 = this._mix[state[j]];
        const s1 = this._mix[state[j + 1]];
        const s2 = this._mix[state[j + 2]];
        const s3 = this._mix[state[j + 3]];
        t = s0 ^ s1 >>> 8 ^ s1 << 24 ^ s2 >>> 16 ^ s2 << 16 ^ s3 >>> 24 ^ s3 << 8;
        state[j] = t >>> 24 & 0xff;
        state[j + 1] = t >> 16 & 0xff;
        state[j + 2] = t >> 8 & 0xff;
        state[j + 3] = t & 0xff;
      }
    }
    t = state[13];
    state[13] = state[9];
    state[9] = state[5];
    state[5] = state[1];
    state[1] = t;
    t = state[14];
    u = state[10];
    state[14] = state[6];
    state[10] = state[2];
    state[6] = t;
    state[2] = u;
    t = state[15];
    u = state[11];
    v = state[7];
    state[15] = state[3];
    state[11] = t;
    state[7] = u;
    state[3] = v;
    for (let j = 0; j < 16; ++j) {
      state[j] = this._inv_s[state[j]];
      state[j] ^= key[j];
    }
    return state;
  }
  _encrypt(input, key) {
    const s = this._s;
    let t, u, v;
    const state = new Uint8Array(16);
    state.set(input);
    for (let j = 0; j < 16; ++j) {
      state[j] ^= key[j];
    }
    for (let i = 1; i < this._cyclesOfRepetition; i++) {
      for (let j = 0; j < 16; ++j) {
        state[j] = s[state[j]];
      }
      v = state[1];
      state[1] = state[5];
      state[5] = state[9];
      state[9] = state[13];
      state[13] = v;
      v = state[2];
      u = state[6];
      state[2] = state[10];
      state[6] = state[14];
      state[10] = v;
      state[14] = u;
      v = state[3];
      u = state[7];
      t = state[11];
      state[3] = state[15];
      state[7] = v;
      state[11] = u;
      state[15] = t;
      for (let j = 0; j < 16; j += 4) {
        const s0 = state[j + 0];
        const s1 = state[j + 1];
        const s2 = state[j + 2];
        const s3 = state[j + 3];
        t = s0 ^ s1 ^ s2 ^ s3;
        state[j + 0] ^= t ^ this._mixCol[s0 ^ s1];
        state[j + 1] ^= t ^ this._mixCol[s1 ^ s2];
        state[j + 2] ^= t ^ this._mixCol[s2 ^ s3];
        state[j + 3] ^= t ^ this._mixCol[s3 ^ s0];
      }
      for (let j = 0, k = i * 16; j < 16; ++j, ++k) {
        state[j] ^= key[k];
      }
    }
    for (let j = 0; j < 16; ++j) {
      state[j] = s[state[j]];
    }
    v = state[1];
    state[1] = state[5];
    state[5] = state[9];
    state[9] = state[13];
    state[13] = v;
    v = state[2];
    u = state[6];
    state[2] = state[10];
    state[6] = state[14];
    state[10] = v;
    state[14] = u;
    v = state[3];
    u = state[7];
    t = state[11];
    state[3] = state[15];
    state[7] = v;
    state[11] = u;
    state[15] = t;
    for (let j = 0, k = this._keySize; j < 16; ++j, ++k) {
      state[j] ^= key[k];
    }
    return state;
  }
  _decryptBlock2(data, finalize) {
    const sourceLength = data.length;
    let buffer = this.buffer,
      bufferLength = this.bufferPosition;
    const result = [];
    let iv = this.iv;
    for (let i = 0; i < sourceLength; ++i) {
      buffer[bufferLength] = data[i];
      ++bufferLength;
      if (bufferLength < 16) {
        continue;
      }
      const plain = this._decrypt(buffer, this._key);
      for (let j = 0; j < 16; ++j) {
        plain[j] ^= iv[j];
      }
      iv = buffer;
      result.push(plain);
      buffer = new Uint8Array(16);
      bufferLength = 0;
    }
    this.buffer = buffer;
    this.bufferLength = bufferLength;
    this.iv = iv;
    if (result.length === 0) {
      return new Uint8Array(0);
    }
    let outputLength = 16 * result.length;
    if (finalize) {
      const lastBlock = result.at(-1);
      let psLen = lastBlock[15];
      if (psLen <= 16) {
        for (let i = 15, ii = 16 - psLen; i >= ii; --i) {
          if (lastBlock[i] !== psLen) {
            psLen = 0;
            break;
          }
        }
        outputLength -= psLen;
        result[result.length - 1] = lastBlock.subarray(0, 16 - psLen);
      }
    }
    const output = new Uint8Array(outputLength);
    for (let i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) {
      output.set(result[i], j);
    }
    return output;
  }
  decryptBlock(data, finalize, iv = null) {
    const sourceLength = data.length;
    const buffer = this.buffer;
    let bufferLength = this.bufferPosition;
    if (iv) {
      this.iv = iv;
    } else {
      for (let i = 0; bufferLength < 16 && i < sourceLength; ++i, ++bufferLength) {
        buffer[bufferLength] = data[i];
      }
      if (bufferLength < 16) {
        this.bufferLength = bufferLength;
        return new Uint8Array(0);
      }
      this.iv = buffer;
      data = data.subarray(16);
    }
    this.buffer = new Uint8Array(16);
    this.bufferLength = 0;
    this.decryptBlock = this._decryptBlock2;
    return this.decryptBlock(data, finalize);
  }
  encrypt(data, iv) {
    const sourceLength = data.length;
    let buffer = this.buffer,
      bufferLength = this.bufferPosition;
    const result = [];
    if (!iv) {
      iv = new Uint8Array(16);
    }
    for (let i = 0; i < sourceLength; ++i) {
      buffer[bufferLength] = data[i];
      ++bufferLength;
      if (bufferLength < 16) {
        continue;
      }
      for (let j = 0; j < 16; ++j) {
        buffer[j] ^= iv[j];
      }
      const cipher = this._encrypt(buffer, this._key);
      iv = cipher;
      result.push(cipher);
      buffer = new Uint8Array(16);
      bufferLength = 0;
    }
    this.buffer = buffer;
    this.bufferLength = bufferLength;
    this.iv = iv;
    if (result.length === 0) {
      return new Uint8Array(0);
    }
    const outputLength = 16 * result.length;
    const output = new Uint8Array(outputLength);
    for (let i = 0, j = 0, ii = result.length; i < ii; ++i, j += 16) {
      output.set(result[i], j);
    }
    return output;
  }
}
class AES128Cipher extends AESBaseCipher {
  constructor(key) {
    super();
    this._cyclesOfRepetition = 10;
    this._keySize = 160;
    this._rcon = new Uint8Array([0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d]);
    this._key = this._expandKey(key);
  }
  _expandKey(cipherKey) {
    const b = 176;
    const s = this._s;
    const rcon = this._rcon;
    const result = new Uint8Array(b);
    result.set(cipherKey);
    for (let j = 16, i = 1; j < b; ++i) {
      let t1 = result[j - 3];
      let t2 = result[j - 2];
      let t3 = result[j - 1];
      let t4 = result[j - 4];
      t1 = s[t1];
      t2 = s[t2];
      t3 = s[t3];
      t4 = s[t4];
      t1 ^= rcon[i];
      for (let n = 0; n < 4; ++n) {
        result[j] = t1 ^= result[j - 16];
        j++;
        result[j] = t2 ^= result[j - 16];
        j++;
        result[j] = t3 ^= result[j - 16];
        j++;
        result[j] = t4 ^= result[j - 16];
        j++;
      }
    }
    return result;
  }
}
class AES256Cipher extends AESBaseCipher {
  constructor(key) {
    super();
    this._cyclesOfRepetition = 14;
    this._keySize = 224;
    this._key = this._expandKey(key);
  }
  _expandKey(cipherKey) {
    const b = 240;
    const s = this._s;
    const result = new Uint8Array(b);
    result.set(cipherKey);
    let r = 1;
    let t1, t2, t3, t4;
    for (let j = 32, i = 1; j < b; ++i) {
      if (j % 32 === 16) {
        t1 = s[t1];
        t2 = s[t2];
        t3 = s[t3];
        t4 = s[t4];
      } else if (j % 32 === 0) {
        t1 = result[j - 3];
        t2 = result[j - 2];
        t3 = result[j - 1];
        t4 = result[j - 4];
        t1 = s[t1];
        t2 = s[t2];
        t3 = s[t3];
        t4 = s[t4];
        t1 ^= r;
        if ((r <<= 1) >= 256) {
          r = (r ^ 0x1b) & 0xff;
        }
      }
      for (let n = 0; n < 4; ++n) {
        result[j] = t1 ^= result[j - 32];
        j++;
        result[j] = t2 ^= result[j - 32];
        j++;
        result[j] = t3 ^= result[j - 32];
        j++;
        result[j] = t4 ^= result[j - 32];
        j++;
      }
    }
    return result;
  }
}
class PDF17 {
  checkOwnerPassword(password, ownerValidationSalt, userBytes, ownerPassword) {
    const hashData = new Uint8Array(password.length + 56);
    hashData.set(password, 0);
    hashData.set(ownerValidationSalt, password.length);
    hashData.set(userBytes, password.length + ownerValidationSalt.length);
    const result = calculateSHA256(hashData, 0, hashData.length);
    return isArrayEqual(result, ownerPassword);
  }
  checkUserPassword(password, userValidationSalt, userPassword) {
    const hashData = new Uint8Array(password.length + 8);
    hashData.set(password, 0);
    hashData.set(userValidationSalt, password.length);
    const result = calculateSHA256(hashData, 0, hashData.length);
    return isArrayEqual(result, userPassword);
  }
  getOwnerKey(password, ownerKeySalt, userBytes, ownerEncryption) {
    const hashData = new Uint8Array(password.length + 56);
    hashData.set(password, 0);
    hashData.set(ownerKeySalt, password.length);
    hashData.set(userBytes, password.length + ownerKeySalt.length);
    const key = calculateSHA256(hashData, 0, hashData.length);
    const cipher = new AES256Cipher(key);
    return cipher.decryptBlock(ownerEncryption, false, new Uint8Array(16));
  }
  getUserKey(password, userKeySalt, userEncryption) {
    const hashData = new Uint8Array(password.length + 8);
    hashData.set(password, 0);
    hashData.set(userKeySalt, password.length);
    const key = calculateSHA256(hashData, 0, hashData.length);
    const cipher = new AES256Cipher(key);
    return cipher.decryptBlock(userEncryption, false, new Uint8Array(16));
  }
}
class PDF20 {
  _hash(password, input, userBytes) {
    let k = calculateSHA256(input, 0, input.length).subarray(0, 32);
    let e = [0];
    let i = 0;
    while (i < 64 || e.at(-1) > i - 32) {
      const combinedLength = password.length + k.length + userBytes.length,
        combinedArray = new Uint8Array(combinedLength);
      let writeOffset = 0;
      combinedArray.set(password, writeOffset);
      writeOffset += password.length;
      combinedArray.set(k, writeOffset);
      writeOffset += k.length;
      combinedArray.set(userBytes, writeOffset);
      const k1 = new Uint8Array(combinedLength * 64);
      for (let j = 0, pos = 0; j < 64; j++, pos += combinedLength) {
        k1.set(combinedArray, pos);
      }
      const cipher = new AES128Cipher(k.subarray(0, 16));
      e = cipher.encrypt(k1, k.subarray(16, 32));
      const remainder = e.slice(0, 16).reduce((a, b) => a + b, 0) % 3;
      if (remainder === 0) {
        k = calculateSHA256(e, 0, e.length);
      } else if (remainder === 1) {
        k = calculateSHA384(e, 0, e.length);
      } else if (remainder === 2) {
        k = calculateSHA512(e, 0, e.length);
      }
      i++;
    }
    return k.subarray(0, 32);
  }
  checkOwnerPassword(password, ownerValidationSalt, userBytes, ownerPassword) {
    const hashData = new Uint8Array(password.length + 56);
    hashData.set(password, 0);
    hashData.set(ownerValidationSalt, password.length);
    hashData.set(userBytes, password.length + ownerValidationSalt.length);
    const result = this._hash(password, hashData, userBytes);
    return isArrayEqual(result, ownerPassword);
  }
  checkUserPassword(password, userValidationSalt, userPassword) {
    const hashData = new Uint8Array(password.length + 8);
    hashData.set(password, 0);
    hashData.set(userValidationSalt, password.length);
    const result = this._hash(password, hashData, []);
    return isArrayEqual(result, userPassword);
  }
  getOwnerKey(password, ownerKeySalt, userBytes, ownerEncryption) {
    const hashData = new Uint8Array(password.length + 56);
    hashData.set(password, 0);
    hashData.set(ownerKeySalt, password.length);
    hashData.set(userBytes, password.length + ownerKeySalt.length);
    const key = this._hash(password, hashData, userBytes);
    const cipher = new AES256Cipher(key);
    return cipher.decryptBlock(ownerEncryption, false, new Uint8Array(16));
  }
  getUserKey(password, userKeySalt, userEncryption) {
    const hashData = new Uint8Array(password.length + 8);
    hashData.set(password, 0);
    hashData.set(userKeySalt, password.length);
    const key = this._hash(password, hashData, []);
    const cipher = new AES256Cipher(key);
    return cipher.decryptBlock(userEncryption, false, new Uint8Array(16));
  }
}
class CipherTransform {
  constructor(stringCipherConstructor, streamCipherConstructor) {
    this.StringCipherConstructor = stringCipherConstructor;
    this.StreamCipherConstructor = streamCipherConstructor;
  }
  createStream(stream, length) {
    const cipher = new this.StreamCipherConstructor();
    return new DecryptStream(stream, length, function cipherTransformDecryptStream(data, finalize) {
      return cipher.decryptBlock(data, finalize);
    });
  }
  decryptString(s) {
    const cipher = new this.StringCipherConstructor();
    let data = stringToBytes(s);
    data = cipher.decryptBlock(data, true);
    return bytesToString(data);
  }
  encryptString(s) {
    const cipher = new this.StringCipherConstructor();
    if (cipher instanceof AESBaseCipher) {
      const strLen = s.length;
      const pad = 16 - strLen % 16;
      s += String.fromCharCode(pad).repeat(pad);
      const iv = new Uint8Array(16);
      if (typeof crypto !== "undefined") {
        crypto.getRandomValues(iv);
      } else {
        for (let i = 0; i < 16; i++) {
          iv[i] = Math.floor(256 * Math.random());
        }
      }
      let data = stringToBytes(s);
      data = cipher.encrypt(data, iv);
      const buf = new Uint8Array(16 + data.length);
      buf.set(iv);
      buf.set(data, 16);
      return bytesToString(buf);
    }
    let data = stringToBytes(s);
    data = cipher.encrypt(data);
    return bytesToString(data);
  }
}
class CipherTransformFactory {
  static #defaultPasswordBytes = new Uint8Array([0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, 0x64, 0x00, 0x4e, 0x56, 0xff, 0xfa, 0x01, 0x08, 0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80, 0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a]);
  #createEncryptionKey20(revision, password, ownerPassword, ownerValidationSalt, ownerKeySalt, uBytes, userPassword, userValidationSalt, userKeySalt, ownerEncryption, userEncryption, perms) {
    if (password) {
      const passwordLength = Math.min(127, password.length);
      password = password.subarray(0, passwordLength);
    } else {
      password = [];
    }
    const pdfAlgorithm = revision === 6 ? new PDF20() : new PDF17();
    if (pdfAlgorithm.checkUserPassword(password, userValidationSalt, userPassword)) {
      return pdfAlgorithm.getUserKey(password, userKeySalt, userEncryption);
    } else if (password.length && pdfAlgorithm.checkOwnerPassword(password, ownerValidationSalt, uBytes, ownerPassword)) {
      return pdfAlgorithm.getOwnerKey(password, ownerKeySalt, uBytes, ownerEncryption);
    }
    return null;
  }
  #prepareKeyData(fileId, password, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata) {
    const hashDataSize = 40 + ownerPassword.length + fileId.length;
    const hashData = new Uint8Array(hashDataSize);
    let i = 0,
      j,
      n;
    if (password) {
      n = Math.min(32, password.length);
      for (; i < n; ++i) {
        hashData[i] = password[i];
      }
    }
    j = 0;
    while (i < 32) {
      hashData[i++] = CipherTransformFactory.#defaultPasswordBytes[j++];
    }
    for (j = 0, n = ownerPassword.length; j < n; ++j) {
      hashData[i++] = ownerPassword[j];
    }
    hashData[i++] = flags & 0xff;
    hashData[i++] = flags >> 8 & 0xff;
    hashData[i++] = flags >> 16 & 0xff;
    hashData[i++] = flags >>> 24 & 0xff;
    for (j = 0, n = fileId.length; j < n; ++j) {
      hashData[i++] = fileId[j];
    }
    if (revision >= 4 && !encryptMetadata) {
      hashData[i++] = 0xff;
      hashData[i++] = 0xff;
      hashData[i++] = 0xff;
      hashData[i++] = 0xff;
    }
    let hash = calculateMD5(hashData, 0, i);
    const keyLengthInBytes = keyLength >> 3;
    if (revision >= 3) {
      for (j = 0; j < 50; ++j) {
        hash = calculateMD5(hash, 0, keyLengthInBytes);
      }
    }
    const encryptionKey = hash.subarray(0, keyLengthInBytes);
    let cipher, checkData;
    if (revision >= 3) {
      for (i = 0; i < 32; ++i) {
        hashData[i] = CipherTransformFactory.#defaultPasswordBytes[i];
      }
      for (j = 0, n = fileId.length; j < n; ++j) {
        hashData[i++] = fileId[j];
      }
      cipher = new ARCFourCipher(encryptionKey);
      checkData = cipher.encryptBlock(calculateMD5(hashData, 0, i));
      n = encryptionKey.length;
      const derivedKey = new Uint8Array(n);
      for (j = 1; j <= 19; ++j) {
        for (let k = 0; k < n; ++k) {
          derivedKey[k] = encryptionKey[k] ^ j;
        }
        cipher = new ARCFourCipher(derivedKey);
        checkData = cipher.encryptBlock(checkData);
      }
      for (j = 0, n = checkData.length; j < n; ++j) {
        if (userPassword[j] !== checkData[j]) {
          return null;
        }
      }
    } else {
      cipher = new ARCFourCipher(encryptionKey);
      checkData = cipher.encryptBlock(CipherTransformFactory.#defaultPasswordBytes);
      for (j = 0, n = checkData.length; j < n; ++j) {
        if (userPassword[j] !== checkData[j]) {
          return null;
        }
      }
    }
    return encryptionKey;
  }
  #decodeUserPassword(password, ownerPassword, revision, keyLength) {
    const hashData = new Uint8Array(32);
    let i = 0;
    const n = Math.min(32, password.length);
    for (; i < n; ++i) {
      hashData[i] = password[i];
    }
    let j = 0;
    while (i < 32) {
      hashData[i++] = CipherTransformFactory.#defaultPasswordBytes[j++];
    }
    let hash = calculateMD5(hashData, 0, i);
    const keyLengthInBytes = keyLength >> 3;
    if (revision >= 3) {
      for (j = 0; j < 50; ++j) {
        hash = calculateMD5(hash, 0, hash.length);
      }
    }
    let cipher, userPassword;
    if (revision >= 3) {
      userPassword = ownerPassword;
      const derivedKey = new Uint8Array(keyLengthInBytes);
      for (j = 19; j >= 0; j--) {
        for (let k = 0; k < keyLengthInBytes; ++k) {
          derivedKey[k] = hash[k] ^ j;
        }
        cipher = new ARCFourCipher(derivedKey);
        userPassword = cipher.encryptBlock(userPassword);
      }
    } else {
      cipher = new ARCFourCipher(hash.subarray(0, keyLengthInBytes));
      userPassword = cipher.encryptBlock(ownerPassword);
    }
    return userPassword;
  }
  #buildObjectKey(num, gen, encryptionKey, isAes = false) {
    const key = new Uint8Array(encryptionKey.length + 9);
    const n = encryptionKey.length;
    let i;
    for (i = 0; i < n; ++i) {
      key[i] = encryptionKey[i];
    }
    key[i++] = num & 0xff;
    key[i++] = num >> 8 & 0xff;
    key[i++] = num >> 16 & 0xff;
    key[i++] = gen & 0xff;
    key[i++] = gen >> 8 & 0xff;
    if (isAes) {
      key[i++] = 0x73;
      key[i++] = 0x41;
      key[i++] = 0x6c;
      key[i++] = 0x54;
    }
    const hash = calculateMD5(key, 0, i);
    return hash.subarray(0, Math.min(encryptionKey.length + 5, 16));
  }
  #buildCipherConstructor(cf, name, num, gen, key) {
    if (!(name instanceof Name)) {
      throw new FormatError("Invalid crypt filter name.");
    }
    const self = this;
    const cryptFilter = cf.get(name.name);
    const cfm = cryptFilter?.get("CFM");
    if (!cfm || cfm.name === "None") {
      return function () {
        return new NullCipher();
      };
    }
    if (cfm.name === "V2") {
      return function () {
        return new ARCFourCipher(self.#buildObjectKey(num, gen, key, false));
      };
    }
    if (cfm.name === "AESV2") {
      return function () {
        return new AES128Cipher(self.#buildObjectKey(num, gen, key, true));
      };
    }
    if (cfm.name === "AESV3") {
      return function () {
        return new AES256Cipher(key);
      };
    }
    throw new FormatError("Unknown crypto method");
  }
  constructor(dict, fileId, password) {
    const filter = dict.get("Filter");
    if (!isName(filter, "Standard")) {
      throw new FormatError("unknown encryption method");
    }
    this.filterName = filter.name;
    this.dict = dict;
    const algorithm = dict.get("V");
    if (!Number.isInteger(algorithm) || algorithm !== 1 && algorithm !== 2 && algorithm !== 4 && algorithm !== 5) {
      throw new FormatError("unsupported encryption algorithm");
    }
    this.algorithm = algorithm;
    let keyLength = dict.get("Length");
    if (!keyLength) {
      if (algorithm <= 3) {
        keyLength = 40;
      } else {
        const cfDict = dict.get("CF");
        const streamCryptoName = dict.get("StmF");
        if (cfDict instanceof Dict && streamCryptoName instanceof Name) {
          cfDict.suppressEncryption = true;
          const handlerDict = cfDict.get(streamCryptoName.name);
          keyLength = handlerDict?.get("Length") || 128;
          if (keyLength < 40) {
            keyLength <<= 3;
          }
        }
      }
    }
    if (!Number.isInteger(keyLength) || keyLength < 40 || keyLength % 8 !== 0) {
      throw new FormatError("invalid key length");
    }
    const ownerBytes = stringToBytes(dict.get("O")),
      userBytes = stringToBytes(dict.get("U"));
    const ownerPassword = ownerBytes.subarray(0, 32);
    const userPassword = userBytes.subarray(0, 32);
    const flags = dict.get("P");
    const revision = dict.get("R");
    const encryptMetadata = (algorithm === 4 || algorithm === 5) && dict.get("EncryptMetadata") !== false;
    this.encryptMetadata = encryptMetadata;
    const fileIdBytes = stringToBytes(fileId);
    let passwordBytes;
    if (password) {
      if (revision === 6) {
        try {
          password = utf8StringToString(password);
        } catch {
          warn("CipherTransformFactory: Unable to convert UTF8 encoded password.");
        }
      }
      passwordBytes = stringToBytes(password);
    }
    let encryptionKey;
    if (algorithm !== 5) {
      encryptionKey = this.#prepareKeyData(fileIdBytes, passwordBytes, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata);
    } else {
      const ownerValidationSalt = ownerBytes.subarray(32, 40);
      const ownerKeySalt = ownerBytes.subarray(40, 48);
      const uBytes = userBytes.subarray(0, 48);
      const userValidationSalt = userBytes.subarray(32, 40);
      const userKeySalt = userBytes.subarray(40, 48);
      const ownerEncryption = stringToBytes(dict.get("OE"));
      const userEncryption = stringToBytes(dict.get("UE"));
      const perms = stringToBytes(dict.get("Perms"));
      encryptionKey = this.#createEncryptionKey20(revision, passwordBytes, ownerPassword, ownerValidationSalt, ownerKeySalt, uBytes, userPassword, userValidationSalt, userKeySalt, ownerEncryption, userEncryption, perms);
    }
    if (!encryptionKey && !password) {
      throw new PasswordException("No password given", PasswordResponses.NEED_PASSWORD);
    } else if (!encryptionKey && password) {
      const decodedPassword = this.#decodeUserPassword(passwordBytes, ownerPassword, revision, keyLength);
      encryptionKey = this.#prepareKeyData(fileIdBytes, decodedPassword, ownerPassword, userPassword, flags, revision, keyLength, encryptMetadata);
    }
    if (!encryptionKey) {
      throw new PasswordException("Incorrect Password", PasswordResponses.INCORRECT_PASSWORD);
    }
    this.encryptionKey = encryptionKey;
    if (algorithm >= 4) {
      const cf = dict.get("CF");
      if (cf instanceof Dict) {
        cf.suppressEncryption = true;
      }
      this.cf = cf;
      this.stmf = dict.get("StmF") || Name.get("Identity");
      this.strf = dict.get("StrF") || Name.get("Identity");
      this.eff = dict.get("EFF") || this.stmf;
    }
  }
  createCipherTransform(num, gen) {
    if (this.algorithm === 4 || this.algorithm === 5) {
      return new CipherTransform(this.#buildCipherConstructor(this.cf, this.strf, num, gen, this.encryptionKey), this.#buildCipherConstructor(this.cf, this.stmf, num, gen, this.encryptionKey));
    }
    const key = this.#buildObjectKey(num, gen, this.encryptionKey, false);
    const cipherConstructor = function () {
      return new ARCFourCipher(key);
    };
    return new CipherTransform(cipherConstructor, cipherConstructor);
  }
}

;// CONCATENATED MODULE: ./src/core/writer.js







async function writeObject(ref, obj, buffer, {
  encrypt = null
}) {
  const transform = encrypt?.createCipherTransform(ref.num, ref.gen);
  buffer.push(`${ref.num} ${ref.gen} obj\n`);
  if (obj instanceof Dict) {
    await writeDict(obj, buffer, transform);
  } else if (obj instanceof BaseStream) {
    await writeStream(obj, buffer, transform);
  } else if (Array.isArray(obj) || ArrayBuffer.isView(obj)) {
    await writeArray(obj, buffer, transform);
  }
  buffer.push("\nendobj\n");
}
async function writeDict(dict, buffer, transform) {
  buffer.push("<<");
  for (const key of dict.getKeys()) {
    buffer.push(` /${escapePDFName(key)} `);
    await writeValue(dict.getRaw(key), buffer, transform);
  }
  buffer.push(">>");
}
async function writeStream(stream, buffer, transform) {
  let bytes = stream.getBytes();
  const {
    dict
  } = stream;
  const [filter, params] = await Promise.all([dict.getAsync("Filter"), dict.getAsync("DecodeParms")]);
  const filterZero = Array.isArray(filter) ? await dict.xref.fetchIfRefAsync(filter[0]) : filter;
  const isFilterZeroFlateDecode = isName(filterZero, "FlateDecode");
  const MIN_LENGTH_FOR_COMPRESSING = 256;
  if (bytes.length >= MIN_LENGTH_FOR_COMPRESSING || isFilterZeroFlateDecode) {
    try {
      const cs = new CompressionStream("deflate");
      const writer = cs.writable.getWriter();
      writer.write(bytes);
      writer.close();
      const buf = await new Response(cs.readable).arrayBuffer();
      bytes = new Uint8Array(buf);
      let newFilter, newParams;
      if (!filter) {
        newFilter = Name.get("FlateDecode");
      } else if (!isFilterZeroFlateDecode) {
        newFilter = Array.isArray(filter) ? [Name.get("FlateDecode"), ...filter] : [Name.get("FlateDecode"), filter];
        if (params) {
          newParams = Array.isArray(params) ? [null, ...params] : [null, params];
        }
      }
      if (newFilter) {
        dict.set("Filter", newFilter);
      }
      if (newParams) {
        dict.set("DecodeParms", newParams);
      }
    } catch (ex) {
      info(`writeStream - cannot compress data: "${ex}".`);
    }
  }
  let string = bytesToString(bytes);
  if (transform) {
    string = transform.encryptString(string);
  }
  dict.set("Length", string.length);
  await writeDict(dict, buffer, transform);
  buffer.push(" stream\n", string, "\nendstream");
}
async function writeArray(array, buffer, transform) {
  buffer.push("[");
  let first = true;
  for (const val of array) {
    if (!first) {
      buffer.push(" ");
    } else {
      first = false;
    }
    await writeValue(val, buffer, transform);
  }
  buffer.push("]");
}
async function writeValue(value, buffer, transform) {
  if (value instanceof Name) {
    buffer.push(`/${escapePDFName(value.name)}`);
  } else if (value instanceof Ref) {
    buffer.push(`${value.num} ${value.gen} R`);
  } else if (Array.isArray(value) || ArrayBuffer.isView(value)) {
    await writeArray(value, buffer, transform);
  } else if (typeof value === "string") {
    if (transform) {
      value = transform.encryptString(value);
    }
    buffer.push(`(${escapeString(value)})`);
  } else if (typeof value === "number") {
    buffer.push(numberToString(value));
  } else if (typeof value === "boolean") {
    buffer.push(value.toString());
  } else if (value instanceof Dict) {
    await writeDict(value, buffer, transform);
  } else if (value instanceof BaseStream) {
    await writeStream(value, buffer, transform);
  } else if (value === null) {
    buffer.push("null");
  } else {
    warn(`Unhandled value in writer: ${typeof value}, please file a bug.`);
  }
}
function writeInt(number, size, offset, buffer) {
  for (let i = size + offset - 1; i > offset - 1; i--) {
    buffer[i] = number & 0xff;
    number >>= 8;
  }
  return offset + size;
}
function writeString(string, offset, buffer) {
  for (let i = 0, len = string.length; i < len; i++) {
    buffer[offset + i] = string.charCodeAt(i) & 0xff;
  }
}
function computeMD5(filesize, xrefInfo) {
  const time = Math.floor(Date.now() / 1000);
  const filename = xrefInfo.filename || "";
  const md5Buffer = [time.toString(), filename, filesize.toString()];
  let md5BufferLen = md5Buffer.reduce((a, str) => a + str.length, 0);
  for (const value of Object.values(xrefInfo.info)) {
    md5Buffer.push(value);
    md5BufferLen += value.length;
  }
  const array = new Uint8Array(md5BufferLen);
  let offset = 0;
  for (const str of md5Buffer) {
    writeString(str, offset, array);
    offset += str.length;
  }
  return bytesToString(calculateMD5(array));
}
function writeXFADataForAcroform(str, newRefs) {
  const xml = new SimpleXMLParser({
    hasAttributes: true
  }).parseFromString(str);
  for (const {
    xfa
  } of newRefs) {
    if (!xfa) {
      continue;
    }
    const {
      path,
      value
    } = xfa;
    if (!path) {
      continue;
    }
    const nodePath = parseXFAPath(path);
    let node = xml.documentElement.searchNode(nodePath, 0);
    if (!node && nodePath.length > 1) {
      node = xml.documentElement.searchNode([nodePath.at(-1)], 0);
    }
    if (node) {
      node.childNodes = Array.isArray(value) ? value.map(val => new SimpleDOMNode("value", val)) : [new SimpleDOMNode("#text", value)];
    } else {
      warn(`Node not found for path: ${path}`);
    }
  }
  const buffer = [];
  xml.documentElement.dump(buffer);
  return buffer.join("");
}
async function updateAcroform({
  xref,
  acroForm,
  acroFormRef,
  hasXfa,
  hasXfaDatasetsEntry,
  xfaDatasetsRef,
  needAppearances,
  newRefs
}) {
  if (hasXfa && !hasXfaDatasetsEntry && !xfaDatasetsRef) {
    warn("XFA - Cannot save it");
  }
  if (!needAppearances && (!hasXfa || !xfaDatasetsRef || hasXfaDatasetsEntry)) {
    return;
  }
  const dict = acroForm.clone();
  if (hasXfa && !hasXfaDatasetsEntry) {
    const newXfa = acroForm.get("XFA").slice();
    newXfa.splice(2, 0, "datasets");
    newXfa.splice(3, 0, xfaDatasetsRef);
    dict.set("XFA", newXfa);
  }
  if (needAppearances) {
    dict.set("NeedAppearances", true);
  }
  const buffer = [];
  await writeObject(acroFormRef, dict, buffer, xref);
  newRefs.push({
    ref: acroFormRef,
    data: buffer.join("")
  });
}
function updateXFA({
  xfaData,
  xfaDatasetsRef,
  newRefs,
  xref
}) {
  if (xfaData === null) {
    const datasets = xref.fetchIfRef(xfaDatasetsRef);
    xfaData = writeXFADataForAcroform(datasets.getString(), newRefs);
  }
  const encrypt = xref.encrypt;
  if (encrypt) {
    const transform = encrypt.createCipherTransform(xfaDatasetsRef.num, xfaDatasetsRef.gen);
    xfaData = transform.encryptString(xfaData);
  }
  const data = `${xfaDatasetsRef.num} ${xfaDatasetsRef.gen} obj\n` + `<< /Type /EmbeddedFile /Length ${xfaData.length}>>\nstream\n` + xfaData + "\nendstream\nendobj\n";
  newRefs.push({
    ref: xfaDatasetsRef,
    data
  });
}
async function getXRefTable(xrefInfo, baseOffset, newRefs, newXref, buffer) {
  buffer.push("xref\n");
  const indexes = getIndexes(newRefs);
  let indexesPosition = 0;
  for (const {
    ref,
    data
  } of newRefs) {
    if (ref.num === indexes[indexesPosition]) {
      buffer.push(`${indexes[indexesPosition]} ${indexes[indexesPosition + 1]}\n`);
      indexesPosition += 2;
    }
    if (data !== null) {
      buffer.push(`${baseOffset.toString().padStart(10, "0")} ${Math.min(ref.gen, 0xffff).toString().padStart(5, "0")} n\r\n`);
      baseOffset += data.length;
    } else {
      buffer.push(`0000000000 ${Math.min(ref.gen + 1, 0xffff).toString().padStart(5, "0")} f\r\n`);
    }
  }
  computeIDs(baseOffset, xrefInfo, newXref);
  buffer.push("trailer\n");
  await writeDict(newXref, buffer);
  buffer.push("\nstartxref\n", baseOffset.toString(), "\n%%EOF\n");
}
function getIndexes(newRefs) {
  const indexes = [];
  for (const {
    ref
  } of newRefs) {
    if (ref.num === indexes.at(-2) + indexes.at(-1)) {
      indexes[indexes.length - 1] += 1;
    } else {
      indexes.push(ref.num, 1);
    }
  }
  return indexes;
}
async function getXRefStreamTable(xrefInfo, baseOffset, newRefs, newXref, buffer) {
  const xrefTableData = [];
  let maxOffset = 0;
  let maxGen = 0;
  for (const {
    ref,
    data
  } of newRefs) {
    let gen;
    maxOffset = Math.max(maxOffset, baseOffset);
    if (data !== null) {
      gen = Math.min(ref.gen, 0xffff);
      xrefTableData.push([1, baseOffset, gen]);
      baseOffset += data.length;
    } else {
      gen = Math.min(ref.gen + 1, 0xffff);
      xrefTableData.push([0, 0, gen]);
    }
    maxGen = Math.max(maxGen, gen);
  }
  newXref.set("Index", getIndexes(newRefs));
  const offsetSize = getSizeInBytes(maxOffset);
  const maxGenSize = getSizeInBytes(maxGen);
  const sizes = [1, offsetSize, maxGenSize];
  newXref.set("W", sizes);
  computeIDs(baseOffset, xrefInfo, newXref);
  const structSize = sizes.reduce((a, x) => a + x, 0);
  const data = new Uint8Array(structSize * xrefTableData.length);
  const stream = new Stream(data);
  stream.dict = newXref;
  let offset = 0;
  for (const [type, objOffset, gen] of xrefTableData) {
    offset = writeInt(type, sizes[0], offset, data);
    offset = writeInt(objOffset, sizes[1], offset, data);
    offset = writeInt(gen, sizes[2], offset, data);
  }
  await writeObject(xrefInfo.newRef, stream, buffer, {});
  buffer.push("startxref\n", baseOffset.toString(), "\n%%EOF\n");
}
function computeIDs(baseOffset, xrefInfo, newXref) {
  if (Array.isArray(xrefInfo.fileIds) && xrefInfo.fileIds.length > 0) {
    const md5 = computeMD5(baseOffset, xrefInfo);
    newXref.set("ID", [xrefInfo.fileIds[0], md5]);
  }
}
function getTrailerDict(xrefInfo, newRefs, useXrefStream) {
  const newXref = new Dict(null);
  newXref.set("Prev", xrefInfo.startXRef);
  const refForXrefTable = xrefInfo.newRef;
  if (useXrefStream) {
    newRefs.push({
      ref: refForXrefTable,
      data: ""
    });
    newXref.set("Size", refForXrefTable.num + 1);
    newXref.set("Type", Name.get("XRef"));
  } else {
    newXref.set("Size", refForXrefTable.num);
  }
  if (xrefInfo.rootRef !== null) {
    newXref.set("Root", xrefInfo.rootRef);
  }
  if (xrefInfo.infoRef !== null) {
    newXref.set("Info", xrefInfo.infoRef);
  }
  if (xrefInfo.encryptRef !== null) {
    newXref.set("Encrypt", xrefInfo.encryptRef);
  }
  return newXref;
}
async function incrementalUpdate({
  originalData,
  xrefInfo,
  newRefs,
  xref = null,
  hasXfa = false,
  xfaDatasetsRef = null,
  hasXfaDatasetsEntry = false,
  needAppearances,
  acroFormRef = null,
  acroForm = null,
  xfaData = null,
  useXrefStream = false
}) {
  await updateAcroform({
    xref,
    acroForm,
    acroFormRef,
    hasXfa,
    hasXfaDatasetsEntry,
    xfaDatasetsRef,
    needAppearances,
    newRefs
  });
  if (hasXfa) {
    updateXFA({
      xfaData,
      xfaDatasetsRef,
      newRefs,
      xref
    });
  }
  const buffer = [];
  let baseOffset = originalData.length;
  const lastByte = originalData.at(-1);
  if (lastByte !== 0x0a && lastByte !== 0x0d) {
    buffer.push("\n");
    baseOffset += 1;
  }
  const newXref = getTrailerDict(xrefInfo, newRefs, useXrefStream);
  newRefs = newRefs.sort((a, b) => a.ref.num - b.ref.num);
  for (const {
    data
  } of newRefs) {
    if (data !== null) {
      buffer.push(data);
    }
  }
  await (useXrefStream ? getXRefStreamTable(xrefInfo, baseOffset, newRefs, newXref, buffer) : getXRefTable(xrefInfo, baseOffset, newRefs, newXref, buffer));
  const totalLength = buffer.reduce((a, str) => a + str.length, originalData.length);
  const array = new Uint8Array(totalLength);
  array.set(originalData);
  let offset = originalData.length;
  for (const str of buffer) {
    writeString(str, offset, array);
    offset += str.length;
  }
  return array;
}

;// CONCATENATED MODULE: ./src/core/struct_tree.js





const MAX_DEPTH = 40;
const StructElementType = {
  PAGE_CONTENT: 1,
  STREAM_CONTENT: 2,
  OBJECT: 3,
  ANNOTATION: 4,
  ELEMENT: 5
};
class StructTreeRoot {
  constructor(rootDict, rootRef) {
    this.dict = rootDict;
    this.ref = rootRef instanceof Ref ? rootRef : null;
    this.roleMap = new Map();
    this.structParentIds = null;
  }
  init() {
    this.readRoleMap();
  }
  #addIdToPage(pageRef, id, type) {
    if (!(pageRef instanceof Ref) || id < 0) {
      return;
    }
    this.structParentIds ||= new RefSetCache();
    let ids = this.structParentIds.get(pageRef);
    if (!ids) {
      ids = [];
      this.structParentIds.put(pageRef, ids);
    }
    ids.push([id, type]);
  }
  addAnnotationIdToPage(pageRef, id) {
    this.#addIdToPage(pageRef, id, StructElementType.ANNOTATION);
  }
  readRoleMap() {
    const roleMapDict = this.dict.get("RoleMap");
    if (!(roleMapDict instanceof Dict)) {
      return;
    }
    roleMapDict.forEach((key, value) => {
      if (!(value instanceof Name)) {
        return;
      }
      this.roleMap.set(key, value.name);
    });
  }
  static async canCreateStructureTree({
    catalogRef,
    pdfManager,
    newAnnotationsByPage
  }) {
    if (!(catalogRef instanceof Ref)) {
      warn("Cannot save the struct tree: no catalog reference.");
      return false;
    }
    let nextKey = 0;
    let hasNothingToUpdate = true;
    for (const [pageIndex, elements] of newAnnotationsByPage) {
      const {
        ref: pageRef
      } = await pdfManager.getPage(pageIndex);
      if (!(pageRef instanceof Ref)) {
        warn(`Cannot save the struct tree: page ${pageIndex} has no ref.`);
        hasNothingToUpdate = true;
        break;
      }
      for (const element of elements) {
        if (element.accessibilityData?.type) {
          element.parentTreeId = nextKey++;
          hasNothingToUpdate = false;
        }
      }
    }
    if (hasNothingToUpdate) {
      for (const elements of newAnnotationsByPage.values()) {
        for (const element of elements) {
          delete element.parentTreeId;
        }
      }
      return false;
    }
    return true;
  }
  static async createStructureTree({
    newAnnotationsByPage,
    xref,
    catalogRef,
    pdfManager,
    newRefs
  }) {
    const root = pdfManager.catalog.cloneDict();
    const cache = new RefSetCache();
    cache.put(catalogRef, root);
    const structTreeRootRef = xref.getNewTemporaryRef();
    root.set("StructTreeRoot", structTreeRootRef);
    const structTreeRoot = new Dict(xref);
    structTreeRoot.set("Type", Name.get("StructTreeRoot"));
    const parentTreeRef = xref.getNewTemporaryRef();
    structTreeRoot.set("ParentTree", parentTreeRef);
    const kids = [];
    structTreeRoot.set("K", kids);
    cache.put(structTreeRootRef, structTreeRoot);
    const parentTree = new Dict(xref);
    const nums = [];
    parentTree.set("Nums", nums);
    const nextKey = await this.#writeKids({
      newAnnotationsByPage,
      structTreeRootRef,
      kids,
      nums,
      xref,
      pdfManager,
      cache
    });
    structTreeRoot.set("ParentTreeNextKey", nextKey);
    cache.put(parentTreeRef, parentTree);
    const buffer = [];
    for (const [ref, obj] of cache.items()) {
      buffer.length = 0;
      await writeObject(ref, obj, buffer, xref);
      newRefs.push({
        ref,
        data: buffer.join("")
      });
    }
  }
  async canUpdateStructTree({
    pdfManager,
    xref,
    newAnnotationsByPage
  }) {
    if (!this.ref) {
      warn("Cannot update the struct tree: no root reference.");
      return false;
    }
    let nextKey = this.dict.get("ParentTreeNextKey");
    if (!Number.isInteger(nextKey) || nextKey < 0) {
      warn("Cannot update the struct tree: invalid next key.");
      return false;
    }
    const parentTree = this.dict.get("ParentTree");
    if (!(parentTree instanceof Dict)) {
      warn("Cannot update the struct tree: ParentTree isn't a dict.");
      return false;
    }
    const nums = parentTree.get("Nums");
    if (!Array.isArray(nums)) {
      warn("Cannot update the struct tree: nums isn't an array.");
      return false;
    }
    const numberTree = new NumberTree(parentTree, xref);
    for (const pageIndex of newAnnotationsByPage.keys()) {
      const {
        pageDict
      } = await pdfManager.getPage(pageIndex);
      if (!pageDict.has("StructParents")) {
        continue;
      }
      const id = pageDict.get("StructParents");
      if (!Number.isInteger(id) || !Array.isArray(numberTree.get(id))) {
        warn(`Cannot save the struct tree: page ${pageIndex} has a wrong id.`);
        return false;
      }
    }
    let hasNothingToUpdate = true;
    for (const [pageIndex, elements] of newAnnotationsByPage) {
      const {
        pageDict
      } = await pdfManager.getPage(pageIndex);
      StructTreeRoot.#collectParents({
        elements,
        xref: this.dict.xref,
        pageDict,
        numberTree
      });
      for (const element of elements) {
        if (element.accessibilityData?.type) {
          element.parentTreeId = nextKey++;
          hasNothingToUpdate = false;
        }
      }
    }
    if (hasNothingToUpdate) {
      for (const elements of newAnnotationsByPage.values()) {
        for (const element of elements) {
          delete element.parentTreeId;
          delete element.structTreeParent;
        }
      }
      return false;
    }
    return true;
  }
  async updateStructureTree({
    newAnnotationsByPage,
    pdfManager,
    newRefs
  }) {
    const xref = this.dict.xref;
    const structTreeRoot = this.dict.clone();
    const structTreeRootRef = this.ref;
    const cache = new RefSetCache();
    cache.put(structTreeRootRef, structTreeRoot);
    let parentTreeRef = structTreeRoot.getRaw("ParentTree");
    let parentTree;
    if (parentTreeRef instanceof Ref) {
      parentTree = xref.fetch(parentTreeRef);
    } else {
      parentTree = parentTreeRef;
      parentTreeRef = xref.getNewTemporaryRef();
      structTreeRoot.set("ParentTree", parentTreeRef);
    }
    parentTree = parentTree.clone();
    cache.put(parentTreeRef, parentTree);
    let nums = parentTree.getRaw("Nums");
    let numsRef = null;
    if (nums instanceof Ref) {
      numsRef = nums;
      nums = xref.fetch(numsRef);
    }
    nums = nums.slice();
    if (!numsRef) {
      parentTree.set("Nums", nums);
    }
    const newNextkey = await StructTreeRoot.#writeKids({
      newAnnotationsByPage,
      structTreeRootRef,
      kids: null,
      nums,
      xref,
      pdfManager,
      cache
    });
    structTreeRoot.set("ParentTreeNextKey", newNextkey);
    if (numsRef) {
      cache.put(numsRef, nums);
    }
    const buffer = [];
    for (const [ref, obj] of cache.items()) {
      buffer.length = 0;
      await writeObject(ref, obj, buffer, xref);
      newRefs.push({
        ref,
        data: buffer.join("")
      });
    }
  }
  static async #writeKids({
    newAnnotationsByPage,
    structTreeRootRef,
    kids,
    nums,
    xref,
    pdfManager,
    cache
  }) {
    const objr = Name.get("OBJR");
    let nextKey = -Infinity;
    for (const [pageIndex, elements] of newAnnotationsByPage) {
      const {
        ref: pageRef
      } = await pdfManager.getPage(pageIndex);
      const isPageRef = pageRef instanceof Ref;
      for (const {
        accessibilityData,
        ref,
        parentTreeId,
        structTreeParent
      } of elements) {
        if (!accessibilityData?.type) {
          continue;
        }
        const {
          type,
          title,
          lang,
          alt,
          expanded,
          actualText
        } = accessibilityData;
        nextKey = Math.max(nextKey, parentTreeId);
        const tagRef = xref.getNewTemporaryRef();
        const tagDict = new Dict(xref);
        tagDict.set("S", Name.get(type));
        if (title) {
          tagDict.set("T", stringToAsciiOrUTF16BE(title));
        }
        if (lang) {
          tagDict.set("Lang", lang);
        }
        if (alt) {
          tagDict.set("Alt", stringToAsciiOrUTF16BE(alt));
        }
        if (expanded) {
          tagDict.set("E", stringToAsciiOrUTF16BE(expanded));
        }
        if (actualText) {
          tagDict.set("ActualText", stringToAsciiOrUTF16BE(actualText));
        }
        await this.#updateParentTag({
          structTreeParent,
          tagDict,
          newTagRef: tagRef,
          structTreeRootRef,
          fallbackKids: kids,
          xref,
          cache
        });
        const objDict = new Dict(xref);
        tagDict.set("K", objDict);
        objDict.set("Type", objr);
        if (isPageRef) {
          objDict.set("Pg", pageRef);
        }
        objDict.set("Obj", ref);
        cache.put(tagRef, tagDict);
        nums.push(parentTreeId, tagRef);
      }
    }
    return nextKey + 1;
  }
  static #collectParents({
    elements,
    xref,
    pageDict,
    numberTree
  }) {
    const idToElements = new Map();
    for (const element of elements) {
      if (element.structTreeParentId) {
        const id = parseInt(element.structTreeParentId.split("_mc")[1], 10);
        let elems = idToElements.get(id);
        if (!elems) {
          elems = [];
          idToElements.set(id, elems);
        }
        elems.push(element);
      }
    }
    const id = pageDict.get("StructParents");
    if (!Number.isInteger(id)) {
      return;
    }
    const parentArray = numberTree.get(id);
    const updateElement = (kid, pageKid, kidRef) => {
      const elems = idToElements.get(kid);
      if (elems) {
        const parentRef = pageKid.getRaw("P");
        const parentDict = xref.fetchIfRef(parentRef);
        if (parentRef instanceof Ref && parentDict instanceof Dict) {
          const params = {
            ref: kidRef,
            dict: pageKid
          };
          for (const element of elems) {
            element.structTreeParent = params;
          }
        }
        return true;
      }
      return false;
    };
    for (const kidRef of parentArray) {
      if (!(kidRef instanceof Ref)) {
        continue;
      }
      const pageKid = xref.fetch(kidRef);
      const k = pageKid.get("K");
      if (Number.isInteger(k)) {
        updateElement(k, pageKid, kidRef);
        continue;
      }
      if (!Array.isArray(k)) {
        continue;
      }
      for (let kid of k) {
        kid = xref.fetchIfRef(kid);
        if (Number.isInteger(kid) && updateElement(kid, pageKid, kidRef)) {
          break;
        }
        if (!(kid instanceof Dict)) {
          continue;
        }
        if (!isName(kid.get("Type"), "MCR")) {
          break;
        }
        const mcid = kid.get("MCID");
        if (Number.isInteger(mcid) && updateElement(mcid, pageKid, kidRef)) {
          break;
        }
      }
    }
  }
  static async #updateParentTag({
    structTreeParent,
    tagDict,
    newTagRef,
    structTreeRootRef,
    fallbackKids,
    xref,
    cache
  }) {
    let ref = null;
    let parentRef;
    if (structTreeParent) {
      ({
        ref
      } = structTreeParent);
      parentRef = structTreeParent.dict.getRaw("P") || structTreeRootRef;
    } else {
      parentRef = structTreeRootRef;
    }
    tagDict.set("P", parentRef);
    const parentDict = xref.fetchIfRef(parentRef);
    if (!parentDict) {
      fallbackKids.push(newTagRef);
      return;
    }
    let cachedParentDict = cache.get(parentRef);
    if (!cachedParentDict) {
      cachedParentDict = parentDict.clone();
      cache.put(parentRef, cachedParentDict);
    }
    const parentKidsRaw = cachedParentDict.getRaw("K");
    let cachedParentKids = parentKidsRaw instanceof Ref ? cache.get(parentKidsRaw) : null;
    if (!cachedParentKids) {
      cachedParentKids = xref.fetchIfRef(parentKidsRaw);
      cachedParentKids = Array.isArray(cachedParentKids) ? cachedParentKids.slice() : [parentKidsRaw];
      const parentKidsRef = xref.getNewTemporaryRef();
      cachedParentDict.set("K", parentKidsRef);
      cache.put(parentKidsRef, cachedParentKids);
    }
    const index = cachedParentKids.indexOf(ref);
    cachedParentKids.splice(index >= 0 ? index + 1 : cachedParentKids.length, 0, newTagRef);
  }
}
class StructElementNode {
  constructor(tree, dict) {
    this.tree = tree;
    this.dict = dict;
    this.kids = [];
    this.parseKids();
  }
  get role() {
    const nameObj = this.dict.get("S");
    const name = nameObj instanceof Name ? nameObj.name : "";
    const {
      root
    } = this.tree;
    if (root.roleMap.has(name)) {
      return root.roleMap.get(name);
    }
    return name;
  }
  parseKids() {
    let pageObjId = null;
    const objRef = this.dict.getRaw("Pg");
    if (objRef instanceof Ref) {
      pageObjId = objRef.toString();
    }
    const kids = this.dict.get("K");
    if (Array.isArray(kids)) {
      for (const kid of kids) {
        const element = this.parseKid(pageObjId, kid);
        if (element) {
          this.kids.push(element);
        }
      }
    } else {
      const element = this.parseKid(pageObjId, kids);
      if (element) {
        this.kids.push(element);
      }
    }
  }
  parseKid(pageObjId, kid) {
    if (Number.isInteger(kid)) {
      if (this.tree.pageDict.objId !== pageObjId) {
        return null;
      }
      return new StructElement({
        type: StructElementType.PAGE_CONTENT,
        mcid: kid,
        pageObjId
      });
    }
    let kidDict = null;
    if (kid instanceof Ref) {
      kidDict = this.dict.xref.fetch(kid);
    } else if (kid instanceof Dict) {
      kidDict = kid;
    }
    if (!kidDict) {
      return null;
    }
    const pageRef = kidDict.getRaw("Pg");
    if (pageRef instanceof Ref) {
      pageObjId = pageRef.toString();
    }
    const type = kidDict.get("Type") instanceof Name ? kidDict.get("Type").name : null;
    if (type === "MCR") {
      if (this.tree.pageDict.objId !== pageObjId) {
        return null;
      }
      const kidRef = kidDict.getRaw("Stm");
      return new StructElement({
        type: StructElementType.STREAM_CONTENT,
        refObjId: kidRef instanceof Ref ? kidRef.toString() : null,
        pageObjId,
        mcid: kidDict.get("MCID")
      });
    }
    if (type === "OBJR") {
      if (this.tree.pageDict.objId !== pageObjId) {
        return null;
      }
      const kidRef = kidDict.getRaw("Obj");
      return new StructElement({
        type: StructElementType.OBJECT,
        refObjId: kidRef instanceof Ref ? kidRef.toString() : null,
        pageObjId
      });
    }
    return new StructElement({
      type: StructElementType.ELEMENT,
      dict: kidDict
    });
  }
}
class StructElement {
  constructor({
    type,
    dict = null,
    mcid = null,
    pageObjId = null,
    refObjId = null
  }) {
    this.type = type;
    this.dict = dict;
    this.mcid = mcid;
    this.pageObjId = pageObjId;
    this.refObjId = refObjId;
    this.parentNode = null;
  }
}
class StructTreePage {
  constructor(structTreeRoot, pageDict) {
    this.root = structTreeRoot;
    this.rootDict = structTreeRoot ? structTreeRoot.dict : null;
    this.pageDict = pageDict;
    this.nodes = [];
  }
  parse(pageRef) {
    if (!this.root || !this.rootDict) {
      return;
    }
    const parentTree = this.rootDict.get("ParentTree");
    if (!parentTree) {
      return;
    }
    const id = this.pageDict.get("StructParents");
    const ids = pageRef instanceof Ref && this.root.structParentIds?.get(pageRef);
    if (!Number.isInteger(id) && !ids) {
      return;
    }
    const map = new Map();
    const numberTree = new NumberTree(parentTree, this.rootDict.xref);
    if (Number.isInteger(id)) {
      const parentArray = numberTree.get(id);
      if (Array.isArray(parentArray)) {
        for (const ref of parentArray) {
          if (ref instanceof Ref) {
            this.addNode(this.rootDict.xref.fetch(ref), map);
          }
        }
      }
    }
    if (!ids) {
      return;
    }
    for (const [elemId, type] of ids) {
      const obj = numberTree.get(elemId);
      if (obj) {
        const elem = this.addNode(this.rootDict.xref.fetchIfRef(obj), map);
        if (elem?.kids?.length === 1 && elem.kids[0].type === StructElementType.OBJECT) {
          elem.kids[0].type = type;
        }
      }
    }
  }
  addNode(dict, map, level = 0) {
    if (level > MAX_DEPTH) {
      warn("StructTree MAX_DEPTH reached.");
      return null;
    }
    if (!(dict instanceof Dict)) {
      return null;
    }
    if (map.has(dict)) {
      return map.get(dict);
    }
    const element = new StructElementNode(this, dict);
    map.set(dict, element);
    const parent = dict.get("P");
    if (!parent || isName(parent.get("Type"), "StructTreeRoot")) {
      if (!this.addTopLevelNode(dict, element)) {
        map.delete(dict);
      }
      return element;
    }
    const parentNode = this.addNode(parent, map, level + 1);
    if (!parentNode) {
      return element;
    }
    let save = false;
    for (const kid of parentNode.kids) {
      if (kid.type === StructElementType.ELEMENT && kid.dict === dict) {
        kid.parentNode = element;
        save = true;
      }
    }
    if (!save) {
      map.delete(dict);
    }
    return element;
  }
  addTopLevelNode(dict, element) {
    const obj = this.rootDict.get("K");
    if (!obj) {
      return false;
    }
    if (obj instanceof Dict) {
      if (obj.objId !== dict.objId) {
        return false;
      }
      this.nodes[0] = element;
      return true;
    }
    if (!Array.isArray(obj)) {
      return true;
    }
    let save = false;
    for (let i = 0; i < obj.length; i++) {
      const kidRef = obj[i];
      if (kidRef?.toString() === dict.objId) {
        this.nodes[i] = element;
        save = true;
      }
    }
    return save;
  }
  get serializable() {
    function nodeToSerializable(node, parent, level = 0) {
      if (level > MAX_DEPTH) {
        warn("StructTree too deep to be fully serialized.");
        return;
      }
      const obj = Object.create(null);
      obj.role = node.role;
      obj.children = [];
      parent.children.push(obj);
      const alt = node.dict.get("Alt");
      if (typeof alt === "string") {
        obj.alt = stringToPDFString(alt);
      }
      const lang = node.dict.get("Lang");
      if (typeof lang === "string") {
        obj.lang = stringToPDFString(lang);
      }
      for (const kid of node.kids) {
        const kidElement = kid.type === StructElementType.ELEMENT ? kid.parentNode : null;
        if (kidElement) {
          nodeToSerializable(kidElement, obj, level + 1);
          continue;
        } else if (kid.type === StructElementType.PAGE_CONTENT || kid.type === StructElementType.STREAM_CONTENT) {
          obj.children.push({
            type: "content",
            id: `p${kid.pageObjId}_mc${kid.mcid}`
          });
        } else if (kid.type === StructElementType.OBJECT) {
          obj.children.push({
            type: "object",
            id: kid.refObjId
          });
        } else if (kid.type === StructElementType.ANNOTATION) {
          obj.children.push({
            type: "annotation",
            id: `${AnnotationPrefix}${kid.refObjId}`
          });
        }
      }
    }
    const root = Object.create(null);
    root.children = [];
    root.role = "Root";
    for (const child of this.nodes) {
      if (!child) {
        continue;
      }
      nodeToSerializable(child, root);
    }
    return root;
  }
}

;// CONCATENATED MODULE: ./src/core/catalog.js











function isValidExplicitDest(dest) {
  if (!Array.isArray(dest) || dest.length < 2) {
    return false;
  }
  const [page, zoom, ...args] = dest;
  if (!(page instanceof Ref) && !Number.isInteger(page)) {
    return false;
  }
  if (!(zoom instanceof Name)) {
    return false;
  }
  const argsLen = args.length;
  let allowNull = true;
  switch (zoom.name) {
    case "XYZ":
      if (argsLen < 2 || argsLen > 3) {
        return false;
      }
      break;
    case "Fit":
    case "FitB":
      return argsLen === 0;
    case "FitH":
    case "FitBH":
    case "FitV":
    case "FitBV":
      if (argsLen > 1) {
        return false;
      }
      break;
    case "FitR":
      if (argsLen !== 4) {
        return false;
      }
      allowNull = false;
      break;
    default:
      return false;
  }
  for (const arg of args) {
    if (!(typeof arg === "number" || allowNull && arg === null)) {
      return false;
    }
  }
  return true;
}
function fetchDest(dest) {
  if (dest instanceof Dict) {
    dest = dest.get("D");
  }
  return isValidExplicitDest(dest) ? dest : null;
}
function fetchRemoteDest(action) {
  let dest = action.get("D");
  if (dest) {
    if (dest instanceof Name) {
      dest = dest.name;
    }
    if (typeof dest === "string") {
      return stringToPDFString(dest);
    } else if (isValidExplicitDest(dest)) {
      return JSON.stringify(dest);
    }
  }
  return null;
}
class Catalog {
  constructor(pdfManager, xref) {
    this.pdfManager = pdfManager;
    this.xref = xref;
    this._catDict = xref.getCatalogObj();
    if (!(this._catDict instanceof Dict)) {
      throw new FormatError("Catalog object is not a dictionary.");
    }
    this.toplevelPagesDict;
    this._actualNumPages = null;
    this.fontCache = new RefSetCache();
    this.builtInCMapCache = new Map();
    this.standardFontDataCache = new Map();
    this.globalImageCache = new GlobalImageCache();
    this.pageKidsCountCache = new RefSetCache();
    this.pageIndexCache = new RefSetCache();
    this.pageDictCache = new RefSetCache();
    this.nonBlendModesSet = new RefSet();
    this.systemFontCache = new Map();
  }
  cloneDict() {
    return this._catDict.clone();
  }
  get version() {
    const version = this._catDict.get("Version");
    if (version instanceof Name) {
      if (PDF_VERSION_REGEXP.test(version.name)) {
        return shadow(this, "version", version.name);
      }
      warn(`Invalid PDF catalog version: ${version.name}`);
    }
    return shadow(this, "version", null);
  }
  get lang() {
    const lang = this._catDict.get("Lang");
    return shadow(this, "lang", lang && typeof lang === "string" ? stringToPDFString(lang) : null);
  }
  get needsRendering() {
    const needsRendering = this._catDict.get("NeedsRendering");
    return shadow(this, "needsRendering", typeof needsRendering === "boolean" ? needsRendering : false);
  }
  get collection() {
    let collection = null;
    try {
      const obj = this._catDict.get("Collection");
      if (obj instanceof Dict && obj.size > 0) {
        collection = obj;
      }
    } catch (ex) {
      if (ex instanceof MissingDataException) {
        throw ex;
      }
      info("Cannot fetch Collection entry; assuming no collection is present.");
    }
    return shadow(this, "collection", collection);
  }
  get acroForm() {
    let acroForm = null;
    try {
      const obj = this._catDict.get("AcroForm");
      if (obj instanceof Dict && obj.size > 0) {
        acroForm = obj;
      }
    } catch (ex) {
      if (ex instanceof MissingDataException) {
        throw ex;
      }
      info("Cannot fetch AcroForm entry; assuming no forms are present.");
    }
    return shadow(this, "acroForm", acroForm);
  }
  get acroFormRef() {
    const value = this._catDict.getRaw("AcroForm");
    return shadow(this, "acroFormRef", value instanceof Ref ? value : null);
  }
  get metadata() {
    const streamRef = this._catDict.getRaw("Metadata");
    if (!(streamRef instanceof Ref)) {
      return shadow(this, "metadata", null);
    }
    let metadata = null;
    try {
      const stream = this.xref.fetch(streamRef, !this.xref.encrypt?.encryptMetadata);
      if (stream instanceof BaseStream && stream.dict instanceof Dict) {
        const type = stream.dict.get("Type");
        const subtype = stream.dict.get("Subtype");
        if (isName(type, "Metadata") && isName(subtype, "XML")) {
          const data = stringToUTF8String(stream.getString());
          if (data) {
            metadata = new MetadataParser(data).serializable;
          }
        }
      }
    } catch (ex) {
      if (ex instanceof MissingDataException) {
        throw ex;
      }
      info(`Skipping invalid Metadata: "${ex}".`);
    }
    return shadow(this, "metadata", metadata);
  }
  get markInfo() {
    let markInfo = null;
    try {
      markInfo = this._readMarkInfo();
    } catch (ex) {
      if (ex instanceof MissingDataException) {
        throw ex;
      }
      warn("Unable to read mark info.");
    }
    return shadow(this, "markInfo", markInfo);
  }
  _readMarkInfo() {
    const obj = this._catDict.get("MarkInfo");
    if (!(obj instanceof Dict)) {
      return null;
    }
    const markInfo = {
      Marked: false,
      UserProperties: false,
      Suspects: false
    };
    for (const key in markInfo) {
      const value = obj.get(key);
      if (typeof value === "boolean") {
        markInfo[key] = value;
      }
    }
    return markInfo;
  }
  get structTreeRoot() {
    let structTree = null;
    try {
      structTree = this._readStructTreeRoot();
    } catch (ex) {
      if (ex instanceof MissingDataException) {
        throw ex;
      }
      warn("Unable read to structTreeRoot info.");
    }
    return shadow(this, "structTreeRoot", structTree);
  }
  _readStructTreeRoot() {
    const rawObj = this._catDict.getRaw("StructTreeRoot");
    const obj = this.xref.fetchIfRef(rawObj);
    if (!(obj instanceof Dict)) {
      return null;
    }
    const root = new StructTreeRoot(obj, rawObj);
    root.init();
    return root;
  }
  get toplevelPagesDict() {
    const pagesObj = this._catDict.get("Pages");
    if (!(pagesObj instanceof Dict)) {
      throw new FormatError("Invalid top-level pages dictionary.");
    }
    return shadow(this, "toplevelPagesDict", pagesObj);
  }
  get documentOutline() {
    let obj = null;
    try {
      obj = this._readDocumentOutline();
    } catch (ex) {
      if (ex instanceof MissingDataException) {
        throw ex;
      }
      warn("Unable to read document outline.");
    }
    return shadow(this, "documentOutline", obj);
  }
  _readDocumentOutline() {
    let obj = this._catDict.get("Outlines");
    if (!(obj instanceof Dict)) {
      return null;
    }
    obj = obj.getRaw("First");
    if (!(obj instanceof Ref)) {
      return null;
    }
    const root = {
      items: []
    };
    const queue = [{
      obj,
      parent: root
    }];
    const processed = new RefSet();
    processed.put(obj);
    const xref = this.xref,
      blackColor = new Uint8ClampedArray(3);
    while (queue.length > 0) {
      const i = queue.shift();
      const outlineDict = xref.fetchIfRef(i.obj);
      if (outlineDict === null) {
        continue;
      }
      if (!outlineDict.has("Title")) {
        warn("Invalid outline item encountered.");
      }
      const data = {
        url: null,
        dest: null,
        action: null
      };
      Catalog.parseDestDictionary({
        destDict: outlineDict,
        resultObj: data,
        docBaseUrl: this.baseUrl,
        docAttachments: this.attachments
      });
      const title = outlineDict.get("Title");
      const flags = outlineDict.get("F") || 0;
      const color = outlineDict.getArray("C");
      const count = outlineDict.get("Count");
      let rgbColor = blackColor;
      if (isNumberArray(color, 3) && (color[0] !== 0 || color[1] !== 0 || color[2] !== 0)) {
        rgbColor = ColorSpace.singletons.rgb.getRgb(color, 0);
      }
      const outlineItem = {
        action: data.action,
        attachment: data.attachment,
        dest: data.dest,
        url: data.url,
        unsafeUrl: data.unsafeUrl,
        newWindow: data.newWindow,
        setOCGState: data.setOCGState,
        title: typeof title === "string" ? stringToPDFString(title) : "",
        color: rgbColor,
        count: Number.isInteger(count) ? count : undefined,
        bold: !!(flags & 2),
        italic: !!(flags & 1),
        items: []
      };
      i.parent.items.push(outlineItem);
      obj = outlineDict.getRaw("First");
      if (obj instanceof Ref && !processed.has(obj)) {
        queue.push({
          obj,
          parent: outlineItem
        });
        processed.put(obj);
      }
      obj = outlineDict.getRaw("Next");
      if (obj instanceof Ref && !processed.has(obj)) {
        queue.push({
          obj,
          parent: i.parent
        });
        processed.put(obj);
      }
    }
    return root.items.length > 0 ? root.items : null;
  }
  get permissions() {
    let permissions = null;
    try {
      permissions = this._readPermissions();
    } catch (ex) {
      if (ex instanceof MissingDataException) {
        throw ex;
      }
      warn("Unable to read permissions.");
    }
    return shadow(this, "permissions", permissions);
  }
  _readPermissions() {
    const encrypt = this.xref.trailer.get("Encrypt");
    if (!(encrypt instanceof Dict)) {
      return null;
    }
    let flags = encrypt.get("P");
    if (typeof flags !== "number") {
      return null;
    }
    flags += 2 ** 32;
    const permissions = [];
    for (const key in PermissionFlag) {
      const value = PermissionFlag[key];
      if (flags & value) {
        permissions.push(value);
      }
    }
    return permissions;
  }
  get optionalContentConfig() {
    let config = null;
    try {
      const properties = this._catDict.get("OCProperties");
      if (!properties) {
        return shadow(this, "optionalContentConfig", null);
      }
      const defaultConfig = properties.get("D");
      if (!defaultConfig) {
        return shadow(this, "optionalContentConfig", null);
      }
      const groupsData = properties.get("OCGs");
      if (!Array.isArray(groupsData)) {
        return shadow(this, "optionalContentConfig", null);
      }
      const groups = [];
      const groupRefs = new RefSet();
      for (const groupRef of groupsData) {
        if (!(groupRef instanceof Ref) || groupRefs.has(groupRef)) {
          continue;
        }
        groupRefs.put(groupRef);
        groups.push(this.#readOptionalContentGroup(groupRef));
      }
      config = this.#readOptionalContentConfig(defaultConfig, groupRefs);
      config.groups = groups;
    } catch (ex) {
      if (ex instanceof MissingDataException) {
        throw ex;
      }
      warn(`Unable to read optional content config: ${ex}`);
    }
    return shadow(this, "optionalContentConfig", config);
  }
  #readOptionalContentGroup(groupRef) {
    const group = this.xref.fetch(groupRef);
    const obj = {
      id: groupRef.toString(),
      name: null,
      intent: null,
      usage: {
        print: null,
        view: null
      }
    };
    const name = group.get("Name");
    if (typeof name === "string") {
      obj.name = stringToPDFString(name);
    }
    let intent = group.getArray("Intent");
    if (!Array.isArray(intent)) {
      intent = [intent];
    }
    if (intent.every(i => i instanceof Name)) {
      obj.intent = intent.map(i => i.name);
    }
    const usage = group.get("Usage");
    if (!(usage instanceof Dict)) {
      return obj;
    }
    const usageObj = obj.usage;
    const print = usage.get("Print");
    if (print instanceof Dict) {
      const printState = print.get("PrintState");
      if (printState instanceof Name) {
        switch (printState.name) {
          case "ON":
          case "OFF":
            usageObj.print = {
              printState: printState.name
            };
        }
      }
    }
    const view = usage.get("View");
    if (view instanceof Dict) {
      const viewState = view.get("ViewState");
      if (viewState instanceof Name) {
        switch (viewState.name) {
          case "ON":
          case "OFF":
            usageObj.view = {
              viewState: viewState.name
            };
        }
      }
    }
    return obj;
  }
  #readOptionalContentConfig(config, contentGroupRefs) {
    function parseOnOff(refs) {
      const onParsed = [];
      if (Array.isArray(refs)) {
        for (const value of refs) {
          if (!(value instanceof Ref)) {
            continue;
          }
          if (contentGroupRefs.has(value)) {
            onParsed.push(value.toString());
          }
        }
      }
      return onParsed;
    }
    function parseOrder(refs, nestedLevels = 0) {
      if (!Array.isArray(refs)) {
        return null;
      }
      const order = [];
      for (const value of refs) {
        if (value instanceof Ref && contentGroupRefs.has(value)) {
          parsedOrderRefs.put(value);
          order.push(value.toString());
          continue;
        }
        const nestedOrder = parseNestedOrder(value, nestedLevels);
        if (nestedOrder) {
          order.push(nestedOrder);
        }
      }
      if (nestedLevels > 0) {
        return order;
      }
      const hiddenGroups = [];
      for (const groupRef of contentGroupRefs) {
        if (parsedOrderRefs.has(groupRef)) {
          continue;
        }
        hiddenGroups.push(groupRef.toString());
      }
      if (hiddenGroups.length) {
        order.push({
          name: null,
          order: hiddenGroups
        });
      }
      return order;
    }
    function parseNestedOrder(ref, nestedLevels) {
      if (++nestedLevels > MAX_NESTED_LEVELS) {
        warn("parseNestedOrder - reached MAX_NESTED_LEVELS.");
        return null;
      }
      const value = xref.fetchIfRef(ref);
      if (!Array.isArray(value)) {
        return null;
      }
      const nestedName = xref.fetchIfRef(value[0]);
      if (typeof nestedName !== "string") {
        return null;
      }
      const nestedOrder = parseOrder(value.slice(1), nestedLevels);
      if (!nestedOrder || !nestedOrder.length) {
        return null;
      }
      return {
        name: stringToPDFString(nestedName),
        order: nestedOrder
      };
    }
    const xref = this.xref,
      parsedOrderRefs = new RefSet(),
      MAX_NESTED_LEVELS = 10;
    return {
      name: typeof config.get("Name") === "string" ? stringToPDFString(config.get("Name")) : null,
      creator: typeof config.get("Creator") === "string" ? stringToPDFString(config.get("Creator")) : null,
      baseState: config.get("BaseState") instanceof Name ? config.get("BaseState").name : null,
      on: parseOnOff(config.get("ON")),
      off: parseOnOff(config.get("OFF")),
      order: parseOrder(config.get("Order")),
      groups: null
    };
  }
  setActualNumPages(num = null) {
    this._actualNumPages = num;
  }
  get hasActualNumPages() {
    return this._actualNumPages !== null;
  }
  get _pagesCount() {
    const obj = this.toplevelPagesDict.get("Count");
    if (!Number.isInteger(obj)) {
      throw new FormatError("Page count in top-level pages dictionary is not an integer.");
    }
    return shadow(this, "_pagesCount", obj);
  }
  get numPages() {
    return this.hasActualNumPages ? this._actualNumPages : this._pagesCount;
  }
  get destinations() {
    const obj = this._readDests(),
      dests = Object.create(null);
    if (obj instanceof NameTree) {
      for (const [key, value] of obj.getAll()) {
        const dest = fetchDest(value);
        if (dest) {
          dests[stringToPDFString(key)] = dest;
        }
      }
    } else if (obj instanceof Dict) {
      obj.forEach(function (key, value) {
        const dest = fetchDest(value);
        if (dest) {
          dests[key] = dest;
        }
      });
    }
    return shadow(this, "destinations", dests);
  }
  getDestination(id) {
    const obj = this._readDests();
    if (obj instanceof NameTree) {
      const dest = fetchDest(obj.get(id));
      if (dest) {
        return dest;
      }
      const allDest = this.destinations[id];
      if (allDest) {
        warn(`Found "${id}" at an incorrect position in the NameTree.`);
        return allDest;
      }
    } else if (obj instanceof Dict) {
      const dest = fetchDest(obj.get(id));
      if (dest) {
        return dest;
      }
    }
    return null;
  }
  _readDests() {
    const obj = this._catDict.get("Names");
    if (obj?.has("Dests")) {
      return new NameTree(obj.getRaw("Dests"), this.xref);
    } else if (this._catDict.has("Dests")) {
      return this._catDict.get("Dests");
    }
    return undefined;
  }
  get pageLabels() {
    let obj = null;
    try {
      obj = this._readPageLabels();
    } catch (ex) {
      if (ex instanceof MissingDataException) {
        throw ex;
      }
      warn("Unable to read page labels.");
    }
    return shadow(this, "pageLabels", obj);
  }
  _readPageLabels() {
    const obj = this._catDict.getRaw("PageLabels");
    if (!obj) {
      return null;
    }
    const pageLabels = new Array(this.numPages);
    let style = null,
      prefix = "";
    const numberTree = new NumberTree(obj, this.xref);
    const nums = numberTree.getAll();
    let currentLabel = "",
      currentIndex = 1;
    for (let i = 0, ii = this.numPages; i < ii; i++) {
      const labelDict = nums.get(i);
      if (labelDict !== undefined) {
        if (!(labelDict instanceof Dict)) {
          throw new FormatError("PageLabel is not a dictionary.");
        }
        if (labelDict.has("Type") && !isName(labelDict.get("Type"), "PageLabel")) {
          throw new FormatError("Invalid type in PageLabel dictionary.");
        }
        if (labelDict.has("S")) {
          const s = labelDict.get("S");
          if (!(s instanceof Name)) {
            throw new FormatError("Invalid style in PageLabel dictionary.");
          }
          style = s.name;
        } else {
          style = null;
        }
        if (labelDict.has("P")) {
          const p = labelDict.get("P");
          if (typeof p !== "string") {
            throw new FormatError("Invalid prefix in PageLabel dictionary.");
          }
          prefix = stringToPDFString(p);
        } else {
          prefix = "";
        }
        if (labelDict.has("St")) {
          const st = labelDict.get("St");
          if (!(Number.isInteger(st) && st >= 1)) {
            throw new FormatError("Invalid start in PageLabel dictionary.");
          }
          currentIndex = st;
        } else {
          currentIndex = 1;
        }
      }
      switch (style) {
        case "D":
          currentLabel = currentIndex;
          break;
        case "R":
        case "r":
          currentLabel = toRomanNumerals(currentIndex, style === "r");
          break;
        case "A":
        case "a":
          const LIMIT = 26;
          const A_UPPER_CASE = 0x41,
            A_LOWER_CASE = 0x61;
          const baseCharCode = style === "a" ? A_LOWER_CASE : A_UPPER_CASE;
          const letterIndex = currentIndex - 1;
          const character = String.fromCharCode(baseCharCode + letterIndex % LIMIT);
          currentLabel = character.repeat(Math.floor(letterIndex / LIMIT) + 1);
          break;
        default:
          if (style) {
            throw new FormatError(`Invalid style "${style}" in PageLabel dictionary.`);
          }
          currentLabel = "";
      }
      pageLabels[i] = prefix + currentLabel;
      currentIndex++;
    }
    return pageLabels;
  }
  get pageLayout() {
    const obj = this._catDict.get("PageLayout");
    let pageLayout = "";
    if (obj instanceof Name) {
      switch (obj.name) {
        case "SinglePage":
        case "OneColumn":
        case "TwoColumnLeft":
        case "TwoColumnRight":
        case "TwoPageLeft":
        case "TwoPageRight":
          pageLayout = obj.name;
      }
    }
    return shadow(this, "pageLayout", pageLayout);
  }
  get pageMode() {
    const obj = this._catDict.get("PageMode");
    let pageMode = "UseNone";
    if (obj instanceof Name) {
      switch (obj.name) {
        case "UseNone":
        case "UseOutlines":
        case "UseThumbs":
        case "FullScreen":
        case "UseOC":
        case "UseAttachments":
          pageMode = obj.name;
      }
    }
    return shadow(this, "pageMode", pageMode);
  }
  get viewerPreferences() {
    const obj = this._catDict.get("ViewerPreferences");
    if (!(obj instanceof Dict)) {
      return shadow(this, "viewerPreferences", null);
    }
    let prefs = null;
    for (const key of obj.getKeys()) {
      const value = obj.get(key);
      let prefValue;
      switch (key) {
        case "HideToolbar":
        case "HideMenubar":
        case "HideWindowUI":
        case "FitWindow":
        case "CenterWindow":
        case "DisplayDocTitle":
        case "PickTrayByPDFSize":
          if (typeof value === "boolean") {
            prefValue = value;
          }
          break;
        case "NonFullScreenPageMode":
          if (value instanceof Name) {
            switch (value.name) {
              case "UseNone":
              case "UseOutlines":
              case "UseThumbs":
              case "UseOC":
                prefValue = value.name;
                break;
              default:
                prefValue = "UseNone";
            }
          }
          break;
        case "Direction":
          if (value instanceof Name) {
            switch (value.name) {
              case "L2R":
              case "R2L":
                prefValue = value.name;
                break;
              default:
                prefValue = "L2R";
            }
          }
          break;
        case "ViewArea":
        case "ViewClip":
        case "PrintArea":
        case "PrintClip":
          if (value instanceof Name) {
            switch (value.name) {
              case "MediaBox":
              case "CropBox":
              case "BleedBox":
              case "TrimBox":
              case "ArtBox":
                prefValue = value.name;
                break;
              default:
                prefValue = "CropBox";
            }
          }
          break;
        case "PrintScaling":
          if (value instanceof Name) {
            switch (value.name) {
              case "None":
              case "AppDefault":
                prefValue = value.name;
                break;
              default:
                prefValue = "AppDefault";
            }
          }
          break;
        case "Duplex":
          if (value instanceof Name) {
            switch (value.name) {
              case "Simplex":
              case "DuplexFlipShortEdge":
              case "DuplexFlipLongEdge":
                prefValue = value.name;
                break;
              default:
                prefValue = "None";
            }
          }
          break;
        case "PrintPageRange":
          if (Array.isArray(value) && value.length % 2 === 0) {
            const isValid = value.every((page, i, arr) => Number.isInteger(page) && page > 0 && (i === 0 || page >= arr[i - 1]) && page <= this.numPages);
            if (isValid) {
              prefValue = value;
            }
          }
          break;
        case "NumCopies":
          if (Number.isInteger(value) && value > 0) {
            prefValue = value;
          }
          break;
        default:
          warn(`Ignoring non-standard key in ViewerPreferences: ${key}.`);
          continue;
      }
      if (prefValue === undefined) {
        warn(`Bad value, for key "${key}", in ViewerPreferences: ${value}.`);
        continue;
      }
      if (!prefs) {
        prefs = Object.create(null);
      }
      prefs[key] = prefValue;
    }
    return shadow(this, "viewerPreferences", prefs);
  }
  get openAction() {
    const obj = this._catDict.get("OpenAction");
    const openAction = Object.create(null);
    if (obj instanceof Dict) {
      const destDict = new Dict(this.xref);
      destDict.set("A", obj);
      const resultObj = {
        url: null,
        dest: null,
        action: null
      };
      Catalog.parseDestDictionary({
        destDict,
        resultObj
      });
      if (Array.isArray(resultObj.dest)) {
        openAction.dest = resultObj.dest;
      } else if (resultObj.action) {
        openAction.action = resultObj.action;
      }
    } else if (Array.isArray(obj)) {
      openAction.dest = obj;
    }
    return shadow(this, "openAction", objectSize(openAction) > 0 ? openAction : null);
  }
  get attachments() {
    const obj = this._catDict.get("Names");
    let attachments = null;
    if (obj instanceof Dict && obj.has("EmbeddedFiles")) {
      const nameTree = new NameTree(obj.getRaw("EmbeddedFiles"), this.xref);
      for (const [key, value] of nameTree.getAll()) {
        const fs = new FileSpec(value, this.xref);
        if (!attachments) {
          attachments = Object.create(null);
        }
        attachments[stringToPDFString(key)] = fs.serializable;
      }
    }
    return shadow(this, "attachments", attachments);
  }
  get xfaImages() {
    const obj = this._catDict.get("Names");
    let xfaImages = null;
    if (obj instanceof Dict && obj.has("XFAImages")) {
      const nameTree = new NameTree(obj.getRaw("XFAImages"), this.xref);
      for (const [key, value] of nameTree.getAll()) {
        if (!xfaImages) {
          xfaImages = new Dict(this.xref);
        }
        xfaImages.set(stringToPDFString(key), value);
      }
    }
    return shadow(this, "xfaImages", xfaImages);
  }
  _collectJavaScript() {
    const obj = this._catDict.get("Names");
    let javaScript = null;
    function appendIfJavaScriptDict(name, jsDict) {
      if (!(jsDict instanceof Dict)) {
        return;
      }
      if (!isName(jsDict.get("S"), "JavaScript")) {
        return;
      }
      let js = jsDict.get("JS");
      if (js instanceof BaseStream) {
        js = js.getString();
      } else if (typeof js !== "string") {
        return;
      }
      js = stringToPDFString(js).replaceAll("\x00", "");
      if (js) {
        (javaScript ||= new Map()).set(name, js);
      }
    }
    if (obj instanceof Dict && obj.has("JavaScript")) {
      const nameTree = new NameTree(obj.getRaw("JavaScript"), this.xref);
      for (const [key, value] of nameTree.getAll()) {
        appendIfJavaScriptDict(stringToPDFString(key), value);
      }
    }
    const openAction = this._catDict.get("OpenAction");
    if (openAction) {
      appendIfJavaScriptDict("OpenAction", openAction);
    }
    return javaScript;
  }
  get jsActions() {
    const javaScript = this._collectJavaScript();
    let actions = collectActions(this.xref, this._catDict, DocumentActionEventType);
    if (javaScript) {
      actions ||= Object.create(null);
      for (const [key, val] of javaScript) {
        if (key in actions) {
          actions[key].push(val);
        } else {
          actions[key] = [val];
        }
      }
    }
    return shadow(this, "jsActions", actions);
  }
  async fontFallback(id, handler) {
    const translatedFonts = await Promise.all(this.fontCache);
    for (const translatedFont of translatedFonts) {
      if (translatedFont.loadedName === id) {
        translatedFont.fallback(handler);
        return;
      }
    }
  }
  async cleanup(manuallyTriggered = false) {
    clearGlobalCaches();
    this.globalImageCache.clear(manuallyTriggered);
    this.pageKidsCountCache.clear();
    this.pageIndexCache.clear();
    this.pageDictCache.clear();
    this.nonBlendModesSet.clear();
    const translatedFonts = await Promise.all(this.fontCache);
    for (const {
      dict
    } of translatedFonts) {
      delete dict.cacheKey;
    }
    this.fontCache.clear();
    this.builtInCMapCache.clear();
    this.standardFontDataCache.clear();
    this.systemFontCache.clear();
  }
  async getPageDict(pageIndex) {
    const nodesToVisit = [this.toplevelPagesDict];
    const visitedNodes = new RefSet();
    const pagesRef = this._catDict.getRaw("Pages");
    if (pagesRef instanceof Ref) {
      visitedNodes.put(pagesRef);
    }
    const xref = this.xref,
      pageKidsCountCache = this.pageKidsCountCache,
      pageIndexCache = this.pageIndexCache,
      pageDictCache = this.pageDictCache;
    let currentPageIndex = 0;
    while (nodesToVisit.length) {
      const currentNode = nodesToVisit.pop();
      if (currentNode instanceof Ref) {
        const count = pageKidsCountCache.get(currentNode);
        if (count >= 0 && currentPageIndex + count <= pageIndex) {
          currentPageIndex += count;
          continue;
        }
        if (visitedNodes.has(currentNode)) {
          throw new FormatError("Pages tree contains circular reference.");
        }
        visitedNodes.put(currentNode);
        const obj = await (pageDictCache.get(currentNode) || xref.fetchAsync(currentNode));
        if (obj instanceof Dict) {
          let type = obj.getRaw("Type");
          if (type instanceof Ref) {
            type = await xref.fetchAsync(type);
          }
          if (isName(type, "Page") || !obj.has("Kids")) {
            if (!pageKidsCountCache.has(currentNode)) {
              pageKidsCountCache.put(currentNode, 1);
            }
            if (!pageIndexCache.has(currentNode)) {
              pageIndexCache.put(currentNode, currentPageIndex);
            }
            if (currentPageIndex === pageIndex) {
              return [obj, currentNode];
            }
            currentPageIndex++;
            continue;
          }
        }
        nodesToVisit.push(obj);
        continue;
      }
      if (!(currentNode instanceof Dict)) {
        throw new FormatError("Page dictionary kid reference points to wrong type of object.");
      }
      const {
        objId
      } = currentNode;
      let count = currentNode.getRaw("Count");
      if (count instanceof Ref) {
        count = await xref.fetchAsync(count);
      }
      if (Number.isInteger(count) && count >= 0) {
        if (objId && !pageKidsCountCache.has(objId)) {
          pageKidsCountCache.put(objId, count);
        }
        if (currentPageIndex + count <= pageIndex) {
          currentPageIndex += count;
          continue;
        }
      }
      let kids = currentNode.getRaw("Kids");
      if (kids instanceof Ref) {
        kids = await xref.fetchAsync(kids);
      }
      if (!Array.isArray(kids)) {
        let type = currentNode.getRaw("Type");
        if (type instanceof Ref) {
          type = await xref.fetchAsync(type);
        }
        if (isName(type, "Page") || !currentNode.has("Kids")) {
          if (currentPageIndex === pageIndex) {
            return [currentNode, null];
          }
          currentPageIndex++;
          continue;
        }
        throw new FormatError("Page dictionary kids object is not an array.");
      }
      for (let last = kids.length - 1; last >= 0; last--) {
        const lastKid = kids[last];
        nodesToVisit.push(lastKid);
        if (currentNode === this.toplevelPagesDict && lastKid instanceof Ref && !pageDictCache.has(lastKid)) {
          pageDictCache.put(lastKid, xref.fetchAsync(lastKid));
        }
      }
    }
    throw new Error(`Page index ${pageIndex} not found.`);
  }
  async getAllPageDicts(recoveryMode = false) {
    const {
      ignoreErrors
    } = this.pdfManager.evaluatorOptions;
    const queue = [{
      currentNode: this.toplevelPagesDict,
      posInKids: 0
    }];
    const visitedNodes = new RefSet();
    const pagesRef = this._catDict.getRaw("Pages");
    if (pagesRef instanceof Ref) {
      visitedNodes.put(pagesRef);
    }
    const map = new Map(),
      xref = this.xref,
      pageIndexCache = this.pageIndexCache;
    let pageIndex = 0;
    function addPageDict(pageDict, pageRef) {
      if (pageRef && !pageIndexCache.has(pageRef)) {
        pageIndexCache.put(pageRef, pageIndex);
      }
      map.set(pageIndex++, [pageDict, pageRef]);
    }
    function addPageError(error) {
      if (error instanceof XRefEntryException && !recoveryMode) {
        throw error;
      }
      if (recoveryMode && ignoreErrors && pageIndex === 0) {
        warn(`getAllPageDicts - Skipping invalid first page: "${error}".`);
        error = Dict.empty;
      }
      map.set(pageIndex++, [error, null]);
    }
    while (queue.length > 0) {
      const queueItem = queue.at(-1);
      const {
        currentNode,
        posInKids
      } = queueItem;
      let kids = currentNode.getRaw("Kids");
      if (kids instanceof Ref) {
        try {
          kids = await xref.fetchAsync(kids);
        } catch (ex) {
          addPageError(ex);
          break;
        }
      }
      if (!Array.isArray(kids)) {
        addPageError(new FormatError("Page dictionary kids object is not an array."));
        break;
      }
      if (posInKids >= kids.length) {
        queue.pop();
        continue;
      }
      const kidObj = kids[posInKids];
      let obj;
      if (kidObj instanceof Ref) {
        if (visitedNodes.has(kidObj)) {
          addPageError(new FormatError("Pages tree contains circular reference."));
          break;
        }
        visitedNodes.put(kidObj);
        try {
          obj = await xref.fetchAsync(kidObj);
        } catch (ex) {
          addPageError(ex);
          break;
        }
      } else {
        obj = kidObj;
      }
      if (!(obj instanceof Dict)) {
        addPageError(new FormatError("Page dictionary kid reference points to wrong type of object."));
        break;
      }
      let type = obj.getRaw("Type");
      if (type instanceof Ref) {
        try {
          type = await xref.fetchAsync(type);
        } catch (ex) {
          addPageError(ex);
          break;
        }
      }
      if (isName(type, "Page") || !obj.has("Kids")) {
        addPageDict(obj, kidObj instanceof Ref ? kidObj : null);
      } else {
        queue.push({
          currentNode: obj,
          posInKids: 0
        });
      }
      queueItem.posInKids++;
    }
    return map;
  }
  getPageIndex(pageRef) {
    const cachedPageIndex = this.pageIndexCache.get(pageRef);
    if (cachedPageIndex !== undefined) {
      return Promise.resolve(cachedPageIndex);
    }
    const xref = this.xref;
    function pagesBeforeRef(kidRef) {
      let total = 0,
        parentRef;
      return xref.fetchAsync(kidRef).then(function (node) {
        if (isRefsEqual(kidRef, pageRef) && !isDict(node, "Page") && !(node instanceof Dict && !node.has("Type") && node.has("Contents"))) {
          throw new FormatError("The reference does not point to a /Page dictionary.");
        }
        if (!node) {
          return null;
        }
        if (!(node instanceof Dict)) {
          throw new FormatError("Node must be a dictionary.");
        }
        parentRef = node.getRaw("Parent");
        return node.getAsync("Parent");
      }).then(function (parent) {
        if (!parent) {
          return null;
        }
        if (!(parent instanceof Dict)) {
          throw new FormatError("Parent must be a dictionary.");
        }
        return parent.getAsync("Kids");
      }).then(function (kids) {
        if (!kids) {
          return null;
        }
        const kidPromises = [];
        let found = false;
        for (const kid of kids) {
          if (!(kid instanceof Ref)) {
            throw new FormatError("Kid must be a reference.");
          }
          if (isRefsEqual(kid, kidRef)) {
            found = true;
            break;
          }
          kidPromises.push(xref.fetchAsync(kid).then(function (obj) {
            if (!(obj instanceof Dict)) {
              throw new FormatError("Kid node must be a dictionary.");
            }
            if (obj.has("Count")) {
              total += obj.get("Count");
            } else {
              total++;
            }
          }));
        }
        if (!found) {
          throw new FormatError("Kid reference not found in parent's kids.");
        }
        return Promise.all(kidPromises).then(function () {
          return [total, parentRef];
        });
      });
    }
    let total = 0;
    const next = ref => pagesBeforeRef(ref).then(args => {
      if (!args) {
        this.pageIndexCache.put(pageRef, total);
        return total;
      }
      const [count, parentRef] = args;
      total += count;
      return next(parentRef);
    });
    return next(pageRef);
  }
  get baseUrl() {
    const uri = this._catDict.get("URI");
    if (uri instanceof Dict) {
      const base = uri.get("Base");
      if (typeof base === "string") {
        const absoluteUrl = createValidAbsoluteUrl(base, null, {
          tryConvertEncoding: true
        });
        if (absoluteUrl) {
          return shadow(this, "baseUrl", absoluteUrl.href);
        }
      }
    }
    return shadow(this, "baseUrl", this.pdfManager.docBaseUrl);
  }
  static parseDestDictionary({
    destDict,
    resultObj,
    docBaseUrl = null,
    docAttachments = null
  }) {
    if (!(destDict instanceof Dict)) {
      warn("parseDestDictionary: `destDict` must be a dictionary.");
      return;
    }
    let action = destDict.get("A"),
      url,
      dest;
    if (!(action instanceof Dict)) {
      if (destDict.has("Dest")) {
        action = destDict.get("Dest");
      } else {
        action = destDict.get("AA");
        if (action instanceof Dict) {
          if (action.has("D")) {
            action = action.get("D");
          } else if (action.has("U")) {
            action = action.get("U");
          }
        }
      }
    }
    if (action instanceof Dict) {
      const actionType = action.get("S");
      if (!(actionType instanceof Name)) {
        warn("parseDestDictionary: Invalid type in Action dictionary.");
        return;
      }
      const actionName = actionType.name;
      switch (actionName) {
        case "ResetForm":
          const flags = action.get("Flags");
          const include = ((typeof flags === "number" ? flags : 0) & 1) === 0;
          const fields = [];
          const refs = [];
          for (const obj of action.get("Fields") || []) {
            if (obj instanceof Ref) {
              refs.push(obj.toString());
            } else if (typeof obj === "string") {
              fields.push(stringToPDFString(obj));
            }
          }
          resultObj.resetForm = {
            fields,
            refs,
            include
          };
          break;
        case "URI":
          url = action.get("URI");
          if (url instanceof Name) {
            url = "/" + url.name;
          }
          break;
        case "GoTo":
          dest = action.get("D");
          break;
        case "Launch":
        case "GoToR":
          const urlDict = action.get("F");
          if (urlDict instanceof Dict) {
            const fs = new FileSpec(urlDict, null, true);
            const {
              rawFilename
            } = fs.serializable;
            url = rawFilename;
          } else if (typeof urlDict === "string") {
            url = urlDict;
          }
          const remoteDest = fetchRemoteDest(action);
          if (remoteDest && typeof url === "string") {
            url = url.split("#", 1)[0] + "#" + remoteDest;
          }
          const newWindow = action.get("NewWindow");
          if (typeof newWindow === "boolean") {
            resultObj.newWindow = newWindow;
          }
          break;
        case "GoToE":
          const target = action.get("T");
          let attachment;
          if (docAttachments && target instanceof Dict) {
            const relationship = target.get("R");
            const name = target.get("N");
            if (isName(relationship, "C") && typeof name === "string") {
              attachment = docAttachments[stringToPDFString(name)];
            }
          }
          if (attachment) {
            resultObj.attachment = attachment;
            const attachmentDest = fetchRemoteDest(action);
            if (attachmentDest) {
              resultObj.attachmentDest = attachmentDest;
            }
          } else {
            warn(`parseDestDictionary - unimplemented "GoToE" action.`);
          }
          break;
        case "Named":
          const namedAction = action.get("N");
          if (namedAction instanceof Name) {
            resultObj.action = namedAction.name;
          }
          break;
        case "SetOCGState":
          const state = action.get("State");
          const preserveRB = action.get("PreserveRB");
          if (!Array.isArray(state) || state.length === 0) {
            break;
          }
          const stateArr = [];
          for (const elem of state) {
            if (elem instanceof Name) {
              switch (elem.name) {
                case "ON":
                case "OFF":
                case "Toggle":
                  stateArr.push(elem.name);
                  break;
              }
            } else if (elem instanceof Ref) {
              stateArr.push(elem.toString());
            }
          }
          if (stateArr.length !== state.length) {
            break;
          }
          resultObj.setOCGState = {
            state: stateArr,
            preserveRB: typeof preserveRB === "boolean" ? preserveRB : true
          };
          break;
        case "JavaScript":
          const jsAction = action.get("JS");
          let js;
          if (jsAction instanceof BaseStream) {
            js = jsAction.getString();
          } else if (typeof jsAction === "string") {
            js = jsAction;
          }
          const jsURL = js && recoverJsURL(stringToPDFString(js));
          if (jsURL) {
            url = jsURL.url;
            resultObj.newWindow = jsURL.newWindow;
            break;
          }
        default:
          if (actionName === "JavaScript" || actionName === "SubmitForm") {
            break;
          }
          warn(`parseDestDictionary - unsupported action: "${actionName}".`);
          break;
      }
    } else if (destDict.has("Dest")) {
      dest = destDict.get("Dest");
    }
    if (typeof url === "string") {
      const absoluteUrl = createValidAbsoluteUrl(url, docBaseUrl, {
        addDefaultProtocol: true,
        tryConvertEncoding: true
      });
      if (absoluteUrl) {
        resultObj.url = absoluteUrl.href;
      }
      resultObj.unsafeUrl = url;
    }
    if (dest) {
      if (dest instanceof Name) {
        dest = dest.name;
      }
      if (typeof dest === "string") {
        resultObj.dest = stringToPDFString(dest);
      } else if (isValidExplicitDest(dest)) {
        resultObj.dest = dest;
      }
    }
  }
}

;// CONCATENATED MODULE: ./src/core/object_loader.js




function mayHaveChildren(value) {
  return value instanceof Ref || value instanceof Dict || value instanceof BaseStream || Array.isArray(value);
}
function addChildren(node, nodesToVisit) {
  if (node instanceof Dict) {
    node = node.getRawValues();
  } else if (node instanceof BaseStream) {
    node = node.dict.getRawValues();
  } else if (!Array.isArray(node)) {
    return;
  }
  for (const rawValue of node) {
    if (mayHaveChildren(rawValue)) {
      nodesToVisit.push(rawValue);
    }
  }
}
class ObjectLoader {
  constructor(dict, keys, xref) {
    this.dict = dict;
    this.keys = keys;
    this.xref = xref;
    this.refSet = null;
  }
  async load() {
    if (this.xref.stream.isDataLoaded) {
      return undefined;
    }
    const {
      keys,
      dict
    } = this;
    this.refSet = new RefSet();
    const nodesToVisit = [];
    for (const key of keys) {
      const rawValue = dict.getRaw(key);
      if (rawValue !== undefined) {
        nodesToVisit.push(rawValue);
      }
    }
    return this._walk(nodesToVisit);
  }
  async _walk(nodesToVisit) {
    const nodesToRevisit = [];
    const pendingRequests = [];
    while (nodesToVisit.length) {
      let currentNode = nodesToVisit.pop();
      if (currentNode instanceof Ref) {
        if (this.refSet.has(currentNode)) {
          continue;
        }
        try {
          this.refSet.put(currentNode);
          currentNode = this.xref.fetch(currentNode);
        } catch (ex) {
          if (!(ex instanceof MissingDataException)) {
            warn(`ObjectLoader._walk - requesting all data: "${ex}".`);
            this.refSet = null;
            const {
              manager
            } = this.xref.stream;
            return manager.requestAllChunks();
          }
          nodesToRevisit.push(currentNode);
          pendingRequests.push({
            begin: ex.begin,
            end: ex.end
          });
        }
      }
      if (currentNode instanceof BaseStream) {
        const baseStreams = currentNode.getBaseStreams();
        if (baseStreams) {
          let foundMissingData = false;
          for (const stream of baseStreams) {
            if (stream.isDataLoaded) {
              continue;
            }
            foundMissingData = true;
            pendingRequests.push({
              begin: stream.start,
              end: stream.end
            });
          }
          if (foundMissingData) {
            nodesToRevisit.push(currentNode);
          }
        }
      }
      addChildren(currentNode, nodesToVisit);
    }
    if (pendingRequests.length) {
      await this.xref.stream.manager.requestRanges(pendingRequests);
      for (const node of nodesToRevisit) {
        if (node instanceof Ref) {
          this.refSet.remove(node);
        }
      }
      return this._walk(nodesToRevisit);
    }
    this.refSet = null;
    return undefined;
  }
}

;// CONCATENATED MODULE: ./src/core/xfa/symbol_utils.js
const $acceptWhitespace = Symbol();
const $addHTML = Symbol();
const $appendChild = Symbol();
const $childrenToHTML = Symbol();
const $clean = Symbol();
const $cleanPage = Symbol();
const $cleanup = Symbol();
const $clone = Symbol();
const $consumed = Symbol();
const $content = Symbol("content");
const $data = Symbol("data");
const $dump = Symbol();
const $extra = Symbol("extra");
const $finalize = Symbol();
const $flushHTML = Symbol();
const $getAttributeIt = Symbol();
const $getAttributes = Symbol();
const $getAvailableSpace = Symbol();
const $getChildrenByClass = Symbol();
const $getChildrenByName = Symbol();
const $getChildrenByNameIt = Symbol();
const $getDataValue = Symbol();
const $getExtra = Symbol();
const $getRealChildrenByNameIt = Symbol();
const $getChildren = Symbol();
const $getContainedChildren = Symbol();
const $getNextPage = Symbol();
const $getSubformParent = Symbol();
const $getParent = Symbol();
const $getTemplateRoot = Symbol();
const $globalData = Symbol();
const $hasSettableValue = Symbol();
const $ids = Symbol();
const $indexOf = Symbol();
const $insertAt = Symbol();
const $isCDATAXml = Symbol();
const $isBindable = Symbol();
const $isDataValue = Symbol();
const $isDescendent = Symbol();
const $isNsAgnostic = Symbol();
const $isSplittable = Symbol();
const $isThereMoreWidth = Symbol();
const $isTransparent = Symbol();
const $isUsable = Symbol();
const $lastAttribute = Symbol();
const $namespaceId = Symbol("namespaceId");
const $nodeName = Symbol("nodeName");
const $nsAttributes = Symbol();
const $onChild = Symbol();
const $onChildCheck = Symbol();
const $onText = Symbol();
const $pushGlyphs = Symbol();
const $popPara = Symbol();
const $pushPara = Symbol();
const $removeChild = Symbol();
const $root = Symbol("root");
const $resolvePrototypes = Symbol();
const $searchNode = Symbol();
const $setId = Symbol();
const $setSetAttributes = Symbol();
const $setValue = Symbol();
const $tabIndex = Symbol();
const $text = Symbol();
const $toPages = Symbol();
const $toHTML = Symbol();
const $toString = Symbol();
const $toStyle = Symbol();
const $uid = Symbol("uid");

;// CONCATENATED MODULE: ./src/core/xfa/namespaces.js
const $buildXFAObject = Symbol();
const NamespaceIds = {
  config: {
    id: 0,
    check: ns => ns.startsWith("http://www.xfa.org/schema/xci/")
  },
  connectionSet: {
    id: 1,
    check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-connection-set/")
  },
  datasets: {
    id: 2,
    check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-data/")
  },
  form: {
    id: 3,
    check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-form/")
  },
  localeSet: {
    id: 4,
    check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-locale-set/")
  },
  pdf: {
    id: 5,
    check: ns => ns === "http://ns.adobe.com/xdp/pdf/"
  },
  signature: {
    id: 6,
    check: ns => ns === "http://www.w3.org/2000/09/xmldsig#"
  },
  sourceSet: {
    id: 7,
    check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-source-set/")
  },
  stylesheet: {
    id: 8,
    check: ns => ns === "http://www.w3.org/1999/XSL/Transform"
  },
  template: {
    id: 9,
    check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-template/")
  },
  xdc: {
    id: 10,
    check: ns => ns.startsWith("http://www.xfa.org/schema/xdc/")
  },
  xdp: {
    id: 11,
    check: ns => ns === "http://ns.adobe.com/xdp/"
  },
  xfdf: {
    id: 12,
    check: ns => ns === "http://ns.adobe.com/xfdf/"
  },
  xhtml: {
    id: 13,
    check: ns => ns === "http://www.w3.org/1999/xhtml"
  },
  xmpmeta: {
    id: 14,
    check: ns => ns === "http://ns.adobe.com/xmpmeta/"
  }
};

;// CONCATENATED MODULE: ./src/core/xfa/utils.js

const dimConverters = {
  pt: x => x,
  cm: x => x / 2.54 * 72,
  mm: x => x / (10 * 2.54) * 72,
  in: x => x * 72,
  px: x => x
};
const measurementPattern = /([+-]?\d+\.?\d*)(.*)/;
function stripQuotes(str) {
  if (str.startsWith("'") || str.startsWith('"')) {
    return str.slice(1, -1);
  }
  return str;
}
function getInteger({
  data,
  defaultValue,
  validate
}) {
  if (!data) {
    return defaultValue;
  }
  data = data.trim();
  const n = parseInt(data, 10);
  if (!isNaN(n) && validate(n)) {
    return n;
  }
  return defaultValue;
}
function getFloat({
  data,
  defaultValue,
  validate
}) {
  if (!data) {
    return defaultValue;
  }
  data = data.trim();
  const n = parseFloat(data);
  if (!isNaN(n) && validate(n)) {
    return n;
  }
  return defaultValue;
}
function getKeyword({
  data,
  defaultValue,
  validate
}) {
  if (!data) {
    return defaultValue;
  }
  data = data.trim();
  if (validate(data)) {
    return data;
  }
  return defaultValue;
}
function getStringOption(data, options) {
  return getKeyword({
    data,
    defaultValue: options[0],
    validate: k => options.includes(k)
  });
}
function getMeasurement(str, def = "0") {
  def ||= "0";
  if (!str) {
    return getMeasurement(def);
  }
  const match = str.trim().match(measurementPattern);
  if (!match) {
    return getMeasurement(def);
  }
  const [, valueStr, unit] = match;
  const value = parseFloat(valueStr);
  if (isNaN(value)) {
    return getMeasurement(def);
  }
  if (value === 0) {
    return 0;
  }
  const conv = dimConverters[unit];
  if (conv) {
    return conv(value);
  }
  return value;
}
function getRatio(data) {
  if (!data) {
    return {
      num: 1,
      den: 1
    };
  }
  const ratio = data.trim().split(/\s*:\s*/).map(x => parseFloat(x)).filter(x => !isNaN(x));
  if (ratio.length === 1) {
    ratio.push(1);
  }
  if (ratio.length === 0) {
    return {
      num: 1,
      den: 1
    };
  }
  const [num, den] = ratio;
  return {
    num,
    den
  };
}
function getRelevant(data) {
  if (!data) {
    return [];
  }
  return data.trim().split(/\s+/).map(e => ({
    excluded: e[0] === "-",
    viewname: e.substring(1)
  }));
}
function getColor(data, def = [0, 0, 0]) {
  let [r, g, b] = def;
  if (!data) {
    return {
      r,
      g,
      b
    };
  }
  const color = data.trim().split(/\s*,\s*/).map(c => Math.min(Math.max(0, parseInt(c.trim(), 10)), 255)).map(c => isNaN(c) ? 0 : c);
  if (color.length < 3) {
    return {
      r,
      g,
      b
    };
  }
  [r, g, b] = color;
  return {
    r,
    g,
    b
  };
}
function getBBox(data) {
  const def = -1;
  if (!data) {
    return {
      x: def,
      y: def,
      width: def,
      height: def
    };
  }
  const bbox = data.trim().split(/\s*,\s*/).map(m => getMeasurement(m, "-1"));
  if (bbox.length < 4 || bbox[2] < 0 || bbox[3] < 0) {
    return {
      x: def,
      y: def,
      width: def,
      height: def
    };
  }
  const [x, y, width, height] = bbox;
  return {
    x,
    y,
    width,
    height
  };
}
class HTMLResult {
  static get FAILURE() {
    return shadow(this, "FAILURE", new HTMLResult(false, null, null, null));
  }
  static get EMPTY() {
    return shadow(this, "EMPTY", new HTMLResult(true, null, null, null));
  }
  constructor(success, html, bbox, breakNode) {
    this.success = success;
    this.html = html;
    this.bbox = bbox;
    this.breakNode = breakNode;
  }
  isBreak() {
    return !!this.breakNode;
  }
  static breakNode(node) {
    return new HTMLResult(false, null, null, node);
  }
  static success(html, bbox = null) {
    return new HTMLResult(true, html, bbox, null);
  }
}

;// CONCATENATED MODULE: ./src/core/xfa/fonts.js



class FontFinder {
  constructor(pdfFonts) {
    this.fonts = new Map();
    this.cache = new Map();
    this.warned = new Set();
    this.defaultFont = null;
    this.add(pdfFonts);
  }
  add(pdfFonts, reallyMissingFonts = null) {
    for (const pdfFont of pdfFonts) {
      this.addPdfFont(pdfFont);
    }
    for (const pdfFont of this.fonts.values()) {
      if (!pdfFont.regular) {
        pdfFont.regular = pdfFont.italic || pdfFont.bold || pdfFont.bolditalic;
      }
    }
    if (!reallyMissingFonts || reallyMissingFonts.size === 0) {
      return;
    }
    const myriad = this.fonts.get("PdfJS-Fallback-PdfJS-XFA");
    for (const missing of reallyMissingFonts) {
      this.fonts.set(missing, myriad);
    }
  }
  addPdfFont(pdfFont) {
    const cssFontInfo = pdfFont.cssFontInfo;
    const name = cssFontInfo.fontFamily;
    let font = this.fonts.get(name);
    if (!font) {
      font = Object.create(null);
      this.fonts.set(name, font);
      if (!this.defaultFont) {
        this.defaultFont = font;
      }
    }
    let property = "";
    const fontWeight = parseFloat(cssFontInfo.fontWeight);
    if (parseFloat(cssFontInfo.italicAngle) !== 0) {
      property = fontWeight >= 700 ? "bolditalic" : "italic";
    } else if (fontWeight >= 700) {
      property = "bold";
    }
    if (!property) {
      if (pdfFont.name.includes("Bold") || pdfFont.psName?.includes("Bold")) {
        property = "bold";
      }
      if (pdfFont.name.includes("Italic") || pdfFont.name.endsWith("It") || pdfFont.psName?.includes("Italic") || pdfFont.psName?.endsWith("It")) {
        property += "italic";
      }
    }
    if (!property) {
      property = "regular";
    }
    font[property] = pdfFont;
  }
  getDefault() {
    return this.defaultFont;
  }
  find(fontName, mustWarn = true) {
    let font = this.fonts.get(fontName) || this.cache.get(fontName);
    if (font) {
      return font;
    }
    const pattern = /,|-|_| |bolditalic|bold|italic|regular|it/gi;
    let name = fontName.replaceAll(pattern, "");
    font = this.fonts.get(name);
    if (font) {
      this.cache.set(fontName, font);
      return font;
    }
    name = name.toLowerCase();
    const maybe = [];
    for (const [family, pdfFont] of this.fonts.entries()) {
      if (family.replaceAll(pattern, "").toLowerCase().startsWith(name)) {
        maybe.push(pdfFont);
      }
    }
    if (maybe.length === 0) {
      for (const [, pdfFont] of this.fonts.entries()) {
        if (pdfFont.regular.name?.replaceAll(pattern, "").toLowerCase().startsWith(name)) {
          maybe.push(pdfFont);
        }
      }
    }
    if (maybe.length === 0) {
      name = name.replaceAll(/psmt|mt/gi, "");
      for (const [family, pdfFont] of this.fonts.entries()) {
        if (family.replaceAll(pattern, "").toLowerCase().startsWith(name)) {
          maybe.push(pdfFont);
        }
      }
    }
    if (maybe.length === 0) {
      for (const pdfFont of this.fonts.values()) {
        if (pdfFont.regular.name?.replaceAll(pattern, "").toLowerCase().startsWith(name)) {
          maybe.push(pdfFont);
        }
      }
    }
    if (maybe.length >= 1) {
      if (maybe.length !== 1 && mustWarn) {
        warn(`XFA - Too many choices to guess the correct font: ${fontName}`);
      }
      this.cache.set(fontName, maybe[0]);
      return maybe[0];
    }
    if (mustWarn && !this.warned.has(fontName)) {
      this.warned.add(fontName);
      warn(`XFA - Cannot find the font: ${fontName}`);
    }
    return null;
  }
}
function selectFont(xfaFont, typeface) {
  if (xfaFont.posture === "italic") {
    if (xfaFont.weight === "bold") {
      return typeface.bolditalic;
    }
    return typeface.italic;
  } else if (xfaFont.weight === "bold") {
    return typeface.bold;
  }
  return typeface.regular;
}
function fonts_getMetrics(xfaFont, real = false) {
  let pdfFont = null;
  if (xfaFont) {
    const name = stripQuotes(xfaFont.typeface);
    const typeface = xfaFont[$globalData].fontFinder.find(name);
    pdfFont = selectFont(xfaFont, typeface);
  }
  if (!pdfFont) {
    return {
      lineHeight: 12,
      lineGap: 2,
      lineNoGap: 10
    };
  }
  const size = xfaFont.size || 10;
  const lineHeight = pdfFont.lineHeight ? Math.max(real ? 0 : 1.2, pdfFont.lineHeight) : 1.2;
  const lineGap = pdfFont.lineGap === undefined ? 0.2 : pdfFont.lineGap;
  return {
    lineHeight: lineHeight * size,
    lineGap: lineGap * size,
    lineNoGap: Math.max(1, lineHeight - lineGap) * size
  };
}

;// CONCATENATED MODULE: ./src/core/xfa/text.js

const WIDTH_FACTOR = 1.02;
class FontInfo {
  constructor(xfaFont, margin, lineHeight, fontFinder) {
    this.lineHeight = lineHeight;
    this.paraMargin = margin || {
      top: 0,
      bottom: 0,
      left: 0,
      right: 0
    };
    if (!xfaFont) {
      [this.pdfFont, this.xfaFont] = this.defaultFont(fontFinder);
      return;
    }
    this.xfaFont = {
      typeface: xfaFont.typeface,
      posture: xfaFont.posture,
      weight: xfaFont.weight,
      size: xfaFont.size,
      letterSpacing: xfaFont.letterSpacing
    };
    const typeface = fontFinder.find(xfaFont.typeface);
    if (!typeface) {
      [this.pdfFont, this.xfaFont] = this.defaultFont(fontFinder);
      return;
    }
    this.pdfFont = selectFont(xfaFont, typeface);
    if (!this.pdfFont) {
      [this.pdfFont, this.xfaFont] = this.defaultFont(fontFinder);
    }
  }
  defaultFont(fontFinder) {
    const font = fontFinder.find("Helvetica", false) || fontFinder.find("Myriad Pro", false) || fontFinder.find("Arial", false) || fontFinder.getDefault();
    if (font?.regular) {
      const pdfFont = font.regular;
      const info = pdfFont.cssFontInfo;
      const xfaFont = {
        typeface: info.fontFamily,
        posture: "normal",
        weight: "normal",
        size: 10,
        letterSpacing: 0
      };
      return [pdfFont, xfaFont];
    }
    const xfaFont = {
      typeface: "Courier",
      posture: "normal",
      weight: "normal",
      size: 10,
      letterSpacing: 0
    };
    return [null, xfaFont];
  }
}
class FontSelector {
  constructor(defaultXfaFont, defaultParaMargin, defaultLineHeight, fontFinder) {
    this.fontFinder = fontFinder;
    this.stack = [new FontInfo(defaultXfaFont, defaultParaMargin, defaultLineHeight, fontFinder)];
  }
  pushData(xfaFont, margin, lineHeight) {
    const lastFont = this.stack.at(-1);
    for (const name of ["typeface", "posture", "weight", "size", "letterSpacing"]) {
      if (!xfaFont[name]) {
        xfaFont[name] = lastFont.xfaFont[name];
      }
    }
    for (const name of ["top", "bottom", "left", "right"]) {
      if (isNaN(margin[name])) {
        margin[name] = lastFont.paraMargin[name];
      }
    }
    const fontInfo = new FontInfo(xfaFont, margin, lineHeight || lastFont.lineHeight, this.fontFinder);
    if (!fontInfo.pdfFont) {
      fontInfo.pdfFont = lastFont.pdfFont;
    }
    this.stack.push(fontInfo);
  }
  popFont() {
    this.stack.pop();
  }
  topFont() {
    return this.stack.at(-1);
  }
}
class TextMeasure {
  constructor(defaultXfaFont, defaultParaMargin, defaultLineHeight, fonts) {
    this.glyphs = [];
    this.fontSelector = new FontSelector(defaultXfaFont, defaultParaMargin, defaultLineHeight, fonts);
    this.extraHeight = 0;
  }
  pushData(xfaFont, margin, lineHeight) {
    this.fontSelector.pushData(xfaFont, margin, lineHeight);
  }
  popFont(xfaFont) {
    return this.fontSelector.popFont();
  }
  addPara() {
    const lastFont = this.fontSelector.topFont();
    this.extraHeight += lastFont.paraMargin.top + lastFont.paraMargin.bottom;
  }
  addString(str) {
    if (!str) {
      return;
    }
    const lastFont = this.fontSelector.topFont();
    const fontSize = lastFont.xfaFont.size;
    if (lastFont.pdfFont) {
      const letterSpacing = lastFont.xfaFont.letterSpacing;
      const pdfFont = lastFont.pdfFont;
      const fontLineHeight = pdfFont.lineHeight || 1.2;
      const lineHeight = lastFont.lineHeight || Math.max(1.2, fontLineHeight) * fontSize;
      const lineGap = pdfFont.lineGap === undefined ? 0.2 : pdfFont.lineGap;
      const noGap = fontLineHeight - lineGap;
      const firstLineHeight = Math.max(1, noGap) * fontSize;
      const scale = fontSize / 1000;
      const fallbackWidth = pdfFont.defaultWidth || pdfFont.charsToGlyphs(" ")[0].width;
      for (const line of str.split(/[\u2029\n]/)) {
        const encodedLine = pdfFont.encodeString(line).join("");
        const glyphs = pdfFont.charsToGlyphs(encodedLine);
        for (const glyph of glyphs) {
          const width = glyph.width || fallbackWidth;
          this.glyphs.push([width * scale + letterSpacing, lineHeight, firstLineHeight, glyph.unicode, false]);
        }
        this.glyphs.push([0, 0, 0, "\n", true]);
      }
      this.glyphs.pop();
      return;
    }
    for (const line of str.split(/[\u2029\n]/)) {
      for (const char of line.split("")) {
        this.glyphs.push([fontSize, 1.2 * fontSize, fontSize, char, false]);
      }
      this.glyphs.push([0, 0, 0, "\n", true]);
    }
    this.glyphs.pop();
  }
  compute(maxWidth) {
    let lastSpacePos = -1,
      lastSpaceWidth = 0,
      width = 0,
      height = 0,
      currentLineWidth = 0,
      currentLineHeight = 0;
    let isBroken = false;
    let isFirstLine = true;
    for (let i = 0, ii = this.glyphs.length; i < ii; i++) {
      const [glyphWidth, lineHeight, firstLineHeight, char, isEOL] = this.glyphs[i];
      const isSpace = char === " ";
      const glyphHeight = isFirstLine ? firstLineHeight : lineHeight;
      if (isEOL) {
        width = Math.max(width, currentLineWidth);
        currentLineWidth = 0;
        height += currentLineHeight;
        currentLineHeight = glyphHeight;
        lastSpacePos = -1;
        lastSpaceWidth = 0;
        isFirstLine = false;
        continue;
      }
      if (isSpace) {
        if (currentLineWidth + glyphWidth > maxWidth) {
          width = Math.max(width, currentLineWidth);
          currentLineWidth = 0;
          height += currentLineHeight;
          currentLineHeight = glyphHeight;
          lastSpacePos = -1;
          lastSpaceWidth = 0;
          isBroken = true;
          isFirstLine = false;
        } else {
          currentLineHeight = Math.max(glyphHeight, currentLineHeight);
          lastSpaceWidth = currentLineWidth;
          currentLineWidth += glyphWidth;
          lastSpacePos = i;
        }
        continue;
      }
      if (currentLineWidth + glyphWidth > maxWidth) {
        height += currentLineHeight;
        currentLineHeight = glyphHeight;
        if (lastSpacePos !== -1) {
          i = lastSpacePos;
          width = Math.max(width, lastSpaceWidth);
          currentLineWidth = 0;
          lastSpacePos = -1;
          lastSpaceWidth = 0;
        } else {
          width = Math.max(width, currentLineWidth);
          currentLineWidth = glyphWidth;
        }
        isBroken = true;
        isFirstLine = false;
        continue;
      }
      currentLineWidth += glyphWidth;
      currentLineHeight = Math.max(glyphHeight, currentLineHeight);
    }
    width = Math.max(width, currentLineWidth);
    height += currentLineHeight + this.extraHeight;
    return {
      width: WIDTH_FACTOR * width,
      height,
      isBroken
    };
  }
}

;// CONCATENATED MODULE: ./src/core/xfa/som.js


const namePattern = /^[^.[]+/;
const indexPattern = /^[^\]]+/;
const operators = {
  dot: 0,
  dotDot: 1,
  dotHash: 2,
  dotBracket: 3,
  dotParen: 4
};
const shortcuts = new Map([["$data", (root, current) => root.datasets ? root.datasets.data : root], ["$record", (root, current) => (root.datasets ? root.datasets.data : root)[$getChildren]()[0]], ["$template", (root, current) => root.template], ["$connectionSet", (root, current) => root.connectionSet], ["$form", (root, current) => root.form], ["$layout", (root, current) => root.layout], ["$host", (root, current) => root.host], ["$dataWindow", (root, current) => root.dataWindow], ["$event", (root, current) => root.event], ["!", (root, current) => root.datasets], ["$xfa", (root, current) => root], ["xfa", (root, current) => root], ["$", (root, current) => current]]);
const somCache = new WeakMap();
function parseIndex(index) {
  index = index.trim();
  if (index === "*") {
    return Infinity;
  }
  return parseInt(index, 10) || 0;
}
function parseExpression(expr, dotDotAllowed, noExpr = true) {
  let match = expr.match(namePattern);
  if (!match) {
    return null;
  }
  let [name] = match;
  const parsed = [{
    name,
    cacheName: "." + name,
    index: 0,
    js: null,
    formCalc: null,
    operator: operators.dot
  }];
  let pos = name.length;
  while (pos < expr.length) {
    const spos = pos;
    const char = expr.charAt(pos++);
    if (char === "[") {
      match = expr.slice(pos).match(indexPattern);
      if (!match) {
        warn("XFA - Invalid index in SOM expression");
        return null;
      }
      parsed.at(-1).index = parseIndex(match[0]);
      pos += match[0].length + 1;
      continue;
    }
    let operator;
    switch (expr.charAt(pos)) {
      case ".":
        if (!dotDotAllowed) {
          return null;
        }
        pos++;
        operator = operators.dotDot;
        break;
      case "#":
        pos++;
        operator = operators.dotHash;
        break;
      case "[":
        if (noExpr) {
          warn("XFA - SOM expression contains a FormCalc subexpression which is not supported for now.");
          return null;
        }
        operator = operators.dotBracket;
        break;
      case "(":
        if (noExpr) {
          warn("XFA - SOM expression contains a JavaScript subexpression which is not supported for now.");
          return null;
        }
        operator = operators.dotParen;
        break;
      default:
        operator = operators.dot;
        break;
    }
    match = expr.slice(pos).match(namePattern);
    if (!match) {
      break;
    }
    [name] = match;
    pos += name.length;
    parsed.push({
      name,
      cacheName: expr.slice(spos, pos),
      operator,
      index: 0,
      js: null,
      formCalc: null
    });
  }
  return parsed;
}
function searchNode(root, container, expr, dotDotAllowed = true, useCache = true) {
  const parsed = parseExpression(expr, dotDotAllowed);
  if (!parsed) {
    return null;
  }
  const fn = shortcuts.get(parsed[0].name);
  let i = 0;
  let isQualified;
  if (fn) {
    isQualified = true;
    root = [fn(root, container)];
    i = 1;
  } else {
    isQualified = container === null;
    root = [container || root];
  }
  for (let ii = parsed.length; i < ii; i++) {
    const {
      name,
      cacheName,
      operator,
      index
    } = parsed[i];
    const nodes = [];
    for (const node of root) {
      if (!node.isXFAObject) {
        continue;
      }
      let children, cached;
      if (useCache) {
        cached = somCache.get(node);
        if (!cached) {
          cached = new Map();
          somCache.set(node, cached);
        }
        children = cached.get(cacheName);
      }
      if (!children) {
        switch (operator) {
          case operators.dot:
            children = node[$getChildrenByName](name, false);
            break;
          case operators.dotDot:
            children = node[$getChildrenByName](name, true);
            break;
          case operators.dotHash:
            children = node[$getChildrenByClass](name);
            children = children.isXFAObjectArray ? children.children : [children];
            break;
          default:
            break;
        }
        if (useCache) {
          cached.set(cacheName, children);
        }
      }
      if (children.length > 0) {
        nodes.push(children);
      }
    }
    if (nodes.length === 0 && !isQualified && i === 0) {
      const parent = container[$getParent]();
      container = parent;
      if (!container) {
        return null;
      }
      i = -1;
      root = [container];
      continue;
    }
    root = isFinite(index) ? nodes.filter(node => index < node.length).map(node => node[index]) : nodes.flat();
  }
  if (root.length === 0) {
    return null;
  }
  return root;
}
function createDataNode(root, container, expr) {
  const parsed = parseExpression(expr);
  if (!parsed) {
    return null;
  }
  if (parsed.some(x => x.operator === operators.dotDot)) {
    return null;
  }
  const fn = shortcuts.get(parsed[0].name);
  let i = 0;
  if (fn) {
    root = fn(root, container);
    i = 1;
  } else {
    root = container || root;
  }
  for (let ii = parsed.length; i < ii; i++) {
    const {
      name,
      operator,
      index
    } = parsed[i];
    if (!isFinite(index)) {
      parsed[i].index = 0;
      return root.createNodes(parsed.slice(i));
    }
    let children;
    switch (operator) {
      case operators.dot:
        children = root[$getChildrenByName](name, false);
        break;
      case operators.dotDot:
        children = root[$getChildrenByName](name, true);
        break;
      case operators.dotHash:
        children = root[$getChildrenByClass](name);
        children = children.isXFAObjectArray ? children.children : [children];
        break;
      default:
        break;
    }
    if (children.length === 0) {
      return root.createNodes(parsed.slice(i));
    }
    if (index < children.length) {
      const child = children[index];
      if (!child.isXFAObject) {
        warn(`XFA - Cannot create a node.`);
        return null;
      }
      root = child;
    } else {
      parsed[i].index = index - children.length;
      return root.createNodes(parsed.slice(i));
    }
  }
  return null;
}

;// CONCATENATED MODULE: ./src/core/xfa/xfa_object.js






const _applyPrototype = Symbol();
const _attributes = Symbol();
const _attributeNames = Symbol();
const _children = Symbol("_children");
const _cloneAttribute = Symbol();
const _dataValue = Symbol();
const _defaultValue = Symbol();
const _filteredChildrenGenerator = Symbol();
const _getPrototype = Symbol();
const _getUnsetAttributes = Symbol();
const _hasChildren = Symbol();
const _max = Symbol();
const _options = Symbol();
const _parent = Symbol("parent");
const _resolvePrototypesHelper = Symbol();
const _setAttributes = Symbol();
const _validator = Symbol();
let uid = 0;
const NS_DATASETS = NamespaceIds.datasets.id;
class XFAObject {
  constructor(nsId, name, hasChildren = false) {
    this[$namespaceId] = nsId;
    this[$nodeName] = name;
    this[_hasChildren] = hasChildren;
    this[_parent] = null;
    this[_children] = [];
    this[$uid] = `${name}${uid++}`;
    this[$globalData] = null;
  }
  get isXFAObject() {
    return true;
  }
  get isXFAObjectArray() {
    return false;
  }
  createNodes(path) {
    let root = this,
      node = null;
    for (const {
      name,
      index
    } of path) {
      for (let i = 0, ii = isFinite(index) ? index : 0; i <= ii; i++) {
        const nsId = root[$namespaceId] === NS_DATASETS ? -1 : root[$namespaceId];
        node = new XmlObject(nsId, name);
        root[$appendChild](node);
      }
      root = node;
    }
    return node;
  }
  [$onChild](child) {
    if (!this[_hasChildren] || !this[$onChildCheck](child)) {
      return false;
    }
    const name = child[$nodeName];
    const node = this[name];
    if (node instanceof XFAObjectArray) {
      if (node.push(child)) {
        this[$appendChild](child);
        return true;
      }
    } else {
      if (node !== null) {
        this[$removeChild](node);
      }
      this[name] = child;
      this[$appendChild](child);
      return true;
    }
    let id = "";
    if (this.id) {
      id = ` (id: ${this.id})`;
    } else if (this.name) {
      id = ` (name: ${this.name} ${this.h.value})`;
    }
    warn(`XFA - node "${this[$nodeName]}"${id} has already enough "${name}"!`);
    return false;
  }
  [$onChildCheck](child) {
    return this.hasOwnProperty(child[$nodeName]) && child[$namespaceId] === this[$namespaceId];
  }
  [$isNsAgnostic]() {
    return false;
  }
  [$acceptWhitespace]() {
    return false;
  }
  [$isCDATAXml]() {
    return false;
  }
  [$isBindable]() {
    return false;
  }
  [$popPara]() {
    if (this.para) {
      this[$getTemplateRoot]()[$extra].paraStack.pop();
    }
  }
  [$pushPara]() {
    this[$getTemplateRoot]()[$extra].paraStack.push(this.para);
  }
  [$setId](ids) {
    if (this.id && this[$namespaceId] === NamespaceIds.template.id) {
      ids.set(this.id, this);
    }
  }
  [$getTemplateRoot]() {
    return this[$globalData].template;
  }
  [$isSplittable]() {
    return false;
  }
  [$isThereMoreWidth]() {
    return false;
  }
  [$appendChild](child) {
    child[_parent] = this;
    this[_children].push(child);
    if (!child[$globalData] && this[$globalData]) {
      child[$globalData] = this[$globalData];
    }
  }
  [$removeChild](child) {
    const i = this[_children].indexOf(child);
    this[_children].splice(i, 1);
  }
  [$hasSettableValue]() {
    return this.hasOwnProperty("value");
  }
  [$setValue](_) {}
  [$onText](_) {}
  [$finalize]() {}
  [$clean](builder) {
    delete this[_hasChildren];
    if (this[$cleanup]) {
      builder.clean(this[$cleanup]);
      delete this[$cleanup];
    }
  }
  [$indexOf](child) {
    return this[_children].indexOf(child);
  }
  [$insertAt](i, child) {
    child[_parent] = this;
    this[_children].splice(i, 0, child);
    if (!child[$globalData] && this[$globalData]) {
      child[$globalData] = this[$globalData];
    }
  }
  [$isTransparent]() {
    return !this.name;
  }
  [$lastAttribute]() {
    return "";
  }
  [$text]() {
    if (this[_children].length === 0) {
      return this[$content];
    }
    return this[_children].map(c => c[$text]()).join("");
  }
  get [_attributeNames]() {
    const proto = Object.getPrototypeOf(this);
    if (!proto._attributes) {
      const attributes = proto._attributes = new Set();
      for (const name of Object.getOwnPropertyNames(this)) {
        if (this[name] === null || this[name] instanceof XFAObject || this[name] instanceof XFAObjectArray) {
          break;
        }
        attributes.add(name);
      }
    }
    return shadow(this, _attributeNames, proto._attributes);
  }
  [$isDescendent](parent) {
    let node = this;
    while (node) {
      if (node === parent) {
        return true;
      }
      node = node[$getParent]();
    }
    return false;
  }
  [$getParent]() {
    return this[_parent];
  }
  [$getSubformParent]() {
    return this[$getParent]();
  }
  [$getChildren](name = null) {
    if (!name) {
      return this[_children];
    }
    return this[name];
  }
  [$dump]() {
    const dumped = Object.create(null);
    if (this[$content]) {
      dumped.$content = this[$content];
    }
    for (const name of Object.getOwnPropertyNames(this)) {
      const value = this[name];
      if (value === null) {
        continue;
      }
      if (value instanceof XFAObject) {
        dumped[name] = value[$dump]();
      } else if (value instanceof XFAObjectArray) {
        if (!value.isEmpty()) {
          dumped[name] = value.dump();
        }
      } else {
        dumped[name] = value;
      }
    }
    return dumped;
  }
  [$toStyle]() {
    return null;
  }
  [$toHTML]() {
    return HTMLResult.EMPTY;
  }
  *[$getContainedChildren]() {
    for (const node of this[$getChildren]()) {
      yield node;
    }
  }
  *[_filteredChildrenGenerator](filter, include) {
    for (const node of this[$getContainedChildren]()) {
      if (!filter || include === filter.has(node[$nodeName])) {
        const availableSpace = this[$getAvailableSpace]();
        const res = node[$toHTML](availableSpace);
        if (!res.success) {
          this[$extra].failingNode = node;
        }
        yield res;
      }
    }
  }
  [$flushHTML]() {
    return null;
  }
  [$addHTML](html, bbox) {
    this[$extra].children.push(html);
  }
  [$getAvailableSpace]() {}
  [$childrenToHTML]({
    filter = null,
    include = true
  }) {
    if (!this[$extra].generator) {
      this[$extra].generator = this[_filteredChildrenGenerator](filter, include);
    } else {
      const availableSpace = this[$getAvailableSpace]();
      const res = this[$extra].failingNode[$toHTML](availableSpace);
      if (!res.success) {
        return res;
      }
      if (res.html) {
        this[$addHTML](res.html, res.bbox);
      }
      delete this[$extra].failingNode;
    }
    while (true) {
      const gen = this[$extra].generator.next();
      if (gen.done) {
        break;
      }
      const res = gen.value;
      if (!res.success) {
        return res;
      }
      if (res.html) {
        this[$addHTML](res.html, res.bbox);
      }
    }
    this[$extra].generator = null;
    return HTMLResult.EMPTY;
  }
  [$setSetAttributes](attributes) {
    this[_setAttributes] = new Set(Object.keys(attributes));
  }
  [_getUnsetAttributes](protoAttributes) {
    const allAttr = this[_attributeNames];
    const setAttr = this[_setAttributes];
    return [...protoAttributes].filter(x => allAttr.has(x) && !setAttr.has(x));
  }
  [$resolvePrototypes](ids, ancestors = new Set()) {
    for (const child of this[_children]) {
      child[_resolvePrototypesHelper](ids, ancestors);
    }
  }
  [_resolvePrototypesHelper](ids, ancestors) {
    const proto = this[_getPrototype](ids, ancestors);
    if (proto) {
      this[_applyPrototype](proto, ids, ancestors);
    } else {
      this[$resolvePrototypes](ids, ancestors);
    }
  }
  [_getPrototype](ids, ancestors) {
    const {
      use,
      usehref
    } = this;
    if (!use && !usehref) {
      return null;
    }
    let proto = null;
    let somExpression = null;
    let id = null;
    let ref = use;
    if (usehref) {
      ref = usehref;
      if (usehref.startsWith("#som(") && usehref.endsWith(")")) {
        somExpression = usehref.slice("#som(".length, -1);
      } else if (usehref.startsWith(".#som(") && usehref.endsWith(")")) {
        somExpression = usehref.slice(".#som(".length, -1);
      } else if (usehref.startsWith("#")) {
        id = usehref.slice(1);
      } else if (usehref.startsWith(".#")) {
        id = usehref.slice(2);
      }
    } else if (use.startsWith("#")) {
      id = use.slice(1);
    } else {
      somExpression = use;
    }
    this.use = this.usehref = "";
    if (id) {
      proto = ids.get(id);
    } else {
      proto = searchNode(ids.get($root), this, somExpression, true, false);
      if (proto) {
        proto = proto[0];
      }
    }
    if (!proto) {
      warn(`XFA - Invalid prototype reference: ${ref}.`);
      return null;
    }
    if (proto[$nodeName] !== this[$nodeName]) {
      warn(`XFA - Incompatible prototype: ${proto[$nodeName]} !== ${this[$nodeName]}.`);
      return null;
    }
    if (ancestors.has(proto)) {
      warn(`XFA - Cycle detected in prototypes use.`);
      return null;
    }
    ancestors.add(proto);
    const protoProto = proto[_getPrototype](ids, ancestors);
    if (protoProto) {
      proto[_applyPrototype](protoProto, ids, ancestors);
    }
    proto[$resolvePrototypes](ids, ancestors);
    ancestors.delete(proto);
    return proto;
  }
  [_applyPrototype](proto, ids, ancestors) {
    if (ancestors.has(proto)) {
      warn(`XFA - Cycle detected in prototypes use.`);
      return;
    }
    if (!this[$content] && proto[$content]) {
      this[$content] = proto[$content];
    }
    const newAncestors = new Set(ancestors);
    newAncestors.add(proto);
    for (const unsetAttrName of this[_getUnsetAttributes](proto[_setAttributes])) {
      this[unsetAttrName] = proto[unsetAttrName];
      if (this[_setAttributes]) {
        this[_setAttributes].add(unsetAttrName);
      }
    }
    for (const name of Object.getOwnPropertyNames(this)) {
      if (this[_attributeNames].has(name)) {
        continue;
      }
      const value = this[name];
      const protoValue = proto[name];
      if (value instanceof XFAObjectArray) {
        for (const child of value[_children]) {
          child[_resolvePrototypesHelper](ids, ancestors);
        }
        for (let i = value[_children].length, ii = protoValue[_children].length; i < ii; i++) {
          const child = proto[_children][i][$clone]();
          if (value.push(child)) {
            child[_parent] = this;
            this[_children].push(child);
            child[_resolvePrototypesHelper](ids, ancestors);
          } else {
            break;
          }
        }
        continue;
      }
      if (value !== null) {
        value[$resolvePrototypes](ids, ancestors);
        if (protoValue) {
          value[_applyPrototype](protoValue, ids, ancestors);
        }
        continue;
      }
      if (protoValue !== null) {
        const child = protoValue[$clone]();
        child[_parent] = this;
        this[name] = child;
        this[_children].push(child);
        child[_resolvePrototypesHelper](ids, ancestors);
      }
    }
  }
  static [_cloneAttribute](obj) {
    if (Array.isArray(obj)) {
      return obj.map(x => XFAObject[_cloneAttribute](x));
    }
    if (typeof obj === "object" && obj !== null) {
      return Object.assign({}, obj);
    }
    return obj;
  }
  [$clone]() {
    const clone = Object.create(Object.getPrototypeOf(this));
    for (const $symbol of Object.getOwnPropertySymbols(this)) {
      try {
        clone[$symbol] = this[$symbol];
      } catch {
        shadow(clone, $symbol, this[$symbol]);
      }
    }
    clone[$uid] = `${clone[$nodeName]}${uid++}`;
    clone[_children] = [];
    for (const name of Object.getOwnPropertyNames(this)) {
      if (this[_attributeNames].has(name)) {
        clone[name] = XFAObject[_cloneAttribute](this[name]);
        continue;
      }
      const value = this[name];
      clone[name] = value instanceof XFAObjectArray ? new XFAObjectArray(value[_max]) : null;
    }
    for (const child of this[_children]) {
      const name = child[$nodeName];
      const clonedChild = child[$clone]();
      clone[_children].push(clonedChild);
      clonedChild[_parent] = clone;
      if (clone[name] === null) {
        clone[name] = clonedChild;
      } else {
        clone[name][_children].push(clonedChild);
      }
    }
    return clone;
  }
  [$getChildren](name = null) {
    if (!name) {
      return this[_children];
    }
    return this[_children].filter(c => c[$nodeName] === name);
  }
  [$getChildrenByClass](name) {
    return this[name];
  }
  [$getChildrenByName](name, allTransparent, first = true) {
    return Array.from(this[$getChildrenByNameIt](name, allTransparent, first));
  }
  *[$getChildrenByNameIt](name, allTransparent, first = true) {
    if (name === "parent") {
      yield this[_parent];
      return;
    }
    for (const child of this[_children]) {
      if (child[$nodeName] === name) {
        yield child;
      }
      if (child.name === name) {
        yield child;
      }
      if (allTransparent || child[$isTransparent]()) {
        yield* child[$getChildrenByNameIt](name, allTransparent, false);
      }
    }
    if (first && this[_attributeNames].has(name)) {
      yield new XFAAttribute(this, name, this[name]);
    }
  }
}
class XFAObjectArray {
  constructor(max = Infinity) {
    this[_max] = max;
    this[_children] = [];
  }
  get isXFAObject() {
    return false;
  }
  get isXFAObjectArray() {
    return true;
  }
  push(child) {
    const len = this[_children].length;
    if (len <= this[_max]) {
      this[_children].push(child);
      return true;
    }
    warn(`XFA - node "${child[$nodeName]}" accepts no more than ${this[_max]} children`);
    return false;
  }
  isEmpty() {
    return this[_children].length === 0;
  }
  dump() {
    return this[_children].length === 1 ? this[_children][0][$dump]() : this[_children].map(x => x[$dump]());
  }
  [$clone]() {
    const clone = new XFAObjectArray(this[_max]);
    clone[_children] = this[_children].map(c => c[$clone]());
    return clone;
  }
  get children() {
    return this[_children];
  }
  clear() {
    this[_children].length = 0;
  }
}
class XFAAttribute {
  constructor(node, name, value) {
    this[_parent] = node;
    this[$nodeName] = name;
    this[$content] = value;
    this[$consumed] = false;
    this[$uid] = `attribute${uid++}`;
  }
  [$getParent]() {
    return this[_parent];
  }
  [$isDataValue]() {
    return true;
  }
  [$getDataValue]() {
    return this[$content].trim();
  }
  [$setValue](value) {
    value = value.value || "";
    this[$content] = value.toString();
  }
  [$text]() {
    return this[$content];
  }
  [$isDescendent](parent) {
    return this[_parent] === parent || this[_parent][$isDescendent](parent);
  }
}
class XmlObject extends XFAObject {
  constructor(nsId, name, attributes = {}) {
    super(nsId, name);
    this[$content] = "";
    this[_dataValue] = null;
    if (name !== "#text") {
      const map = new Map();
      this[_attributes] = map;
      for (const [attrName, value] of Object.entries(attributes)) {
        map.set(attrName, new XFAAttribute(this, attrName, value));
      }
      if (attributes.hasOwnProperty($nsAttributes)) {
        const dataNode = attributes[$nsAttributes].xfa.dataNode;
        if (dataNode !== undefined) {
          if (dataNode === "dataGroup") {
            this[_dataValue] = false;
          } else if (dataNode === "dataValue") {
            this[_dataValue] = true;
          }
        }
      }
    }
    this[$consumed] = false;
  }
  [$toString](buf) {
    const tagName = this[$nodeName];
    if (tagName === "#text") {
      buf.push(encodeToXmlString(this[$content]));
      return;
    }
    const utf8TagName = utf8StringToString(tagName);
    const prefix = this[$namespaceId] === NS_DATASETS ? "xfa:" : "";
    buf.push(`<${prefix}${utf8TagName}`);
    for (const [name, value] of this[_attributes].entries()) {
      const utf8Name = utf8StringToString(name);
      buf.push(` ${utf8Name}="${encodeToXmlString(value[$content])}"`);
    }
    if (this[_dataValue] !== null) {
      if (this[_dataValue]) {
        buf.push(` xfa:dataNode="dataValue"`);
      } else {
        buf.push(` xfa:dataNode="dataGroup"`);
      }
    }
    if (!this[$content] && this[_children].length === 0) {
      buf.push("/>");
      return;
    }
    buf.push(">");
    if (this[$content]) {
      if (typeof this[$content] === "string") {
        buf.push(encodeToXmlString(this[$content]));
      } else {
        this[$content][$toString](buf);
      }
    } else {
      for (const child of this[_children]) {
        child[$toString](buf);
      }
    }
    buf.push(`</${prefix}${utf8TagName}>`);
  }
  [$onChild](child) {
    if (this[$content]) {
      const node = new XmlObject(this[$namespaceId], "#text");
      this[$appendChild](node);
      node[$content] = this[$content];
      this[$content] = "";
    }
    this[$appendChild](child);
    return true;
  }
  [$onText](str) {
    this[$content] += str;
  }
  [$finalize]() {
    if (this[$content] && this[_children].length > 0) {
      const node = new XmlObject(this[$namespaceId], "#text");
      this[$appendChild](node);
      node[$content] = this[$content];
      delete this[$content];
    }
  }
  [$toHTML]() {
    if (this[$nodeName] === "#text") {
      return HTMLResult.success({
        name: "#text",
        value: this[$content]
      });
    }
    return HTMLResult.EMPTY;
  }
  [$getChildren](name = null) {
    if (!name) {
      return this[_children];
    }
    return this[_children].filter(c => c[$nodeName] === name);
  }
  [$getAttributes]() {
    return this[_attributes];
  }
  [$getChildrenByClass](name) {
    const value = this[_attributes].get(name);
    if (value !== undefined) {
      return value;
    }
    return this[$getChildren](name);
  }
  *[$getChildrenByNameIt](name, allTransparent) {
    const value = this[_attributes].get(name);
    if (value) {
      yield value;
    }
    for (const child of this[_children]) {
      if (child[$nodeName] === name) {
        yield child;
      }
      if (allTransparent) {
        yield* child[$getChildrenByNameIt](name, allTransparent);
      }
    }
  }
  *[$getAttributeIt](name, skipConsumed) {
    const value = this[_attributes].get(name);
    if (value && (!skipConsumed || !value[$consumed])) {
      yield value;
    }
    for (const child of this[_children]) {
      yield* child[$getAttributeIt](name, skipConsumed);
    }
  }
  *[$getRealChildrenByNameIt](name, allTransparent, skipConsumed) {
    for (const child of this[_children]) {
      if (child[$nodeName] === name && (!skipConsumed || !child[$consumed])) {
        yield child;
      }
      if (allTransparent) {
        yield* child[$getRealChildrenByNameIt](name, allTransparent, skipConsumed);
      }
    }
  }
  [$isDataValue]() {
    if (this[_dataValue] === null) {
      return this[_children].length === 0 || this[_children][0][$namespaceId] === NamespaceIds.xhtml.id;
    }
    return this[_dataValue];
  }
  [$getDataValue]() {
    if (this[_dataValue] === null) {
      if (this[_children].length === 0) {
        return this[$content].trim();
      }
      if (this[_children][0][$namespaceId] === NamespaceIds.xhtml.id) {
        return this[_children][0][$text]().trim();
      }
      return null;
    }
    return this[$content].trim();
  }
  [$setValue](value) {
    value = value.value || "";
    this[$content] = value.toString();
  }
  [$dump](hasNS = false) {
    const dumped = Object.create(null);
    if (hasNS) {
      dumped.$ns = this[$namespaceId];
    }
    if (this[$content]) {
      dumped.$content = this[$content];
    }
    dumped.$name = this[$nodeName];
    dumped.children = [];
    for (const child of this[_children]) {
      dumped.children.push(child[$dump](hasNS));
    }
    dumped.attributes = Object.create(null);
    for (const [name, value] of this[_attributes]) {
      dumped.attributes[name] = value[$content];
    }
    return dumped;
  }
}
class ContentObject extends XFAObject {
  constructor(nsId, name) {
    super(nsId, name);
    this[$content] = "";
  }
  [$onText](text) {
    this[$content] += text;
  }
  [$finalize]() {}
}
class OptionObject extends ContentObject {
  constructor(nsId, name, options) {
    super(nsId, name);
    this[_options] = options;
  }
  [$finalize]() {
    this[$content] = getKeyword({
      data: this[$content],
      defaultValue: this[_options][0],
      validate: k => this[_options].includes(k)
    });
  }
  [$clean](builder) {
    super[$clean](builder);
    delete this[_options];
  }
}
class StringObject extends ContentObject {
  [$finalize]() {
    this[$content] = this[$content].trim();
  }
}
class IntegerObject extends ContentObject {
  constructor(nsId, name, defaultValue, validator) {
    super(nsId, name);
    this[_defaultValue] = defaultValue;
    this[_validator] = validator;
  }
  [$finalize]() {
    this[$content] = getInteger({
      data: this[$content],
      defaultValue: this[_defaultValue],
      validate: this[_validator]
    });
  }
  [$clean](builder) {
    super[$clean](builder);
    delete this[_defaultValue];
    delete this[_validator];
  }
}
class Option01 extends IntegerObject {
  constructor(nsId, name) {
    super(nsId, name, 0, n => n === 1);
  }
}
class Option10 extends IntegerObject {
  constructor(nsId, name) {
    super(nsId, name, 1, n => n === 0);
  }
}

;// CONCATENATED MODULE: ./src/core/xfa/html_utils.js






function measureToString(m) {
  if (typeof m === "string") {
    return "0px";
  }
  return Number.isInteger(m) ? `${m}px` : `${m.toFixed(2)}px`;
}
const converters = {
  anchorType(node, style) {
    const parent = node[$getSubformParent]();
    if (!parent || parent.layout && parent.layout !== "position") {
      return;
    }
    if (!("transform" in style)) {
      style.transform = "";
    }
    switch (node.anchorType) {
      case "bottomCenter":
        style.transform += "translate(-50%, -100%)";
        break;
      case "bottomLeft":
        style.transform += "translate(0,-100%)";
        break;
      case "bottomRight":
        style.transform += "translate(-100%,-100%)";
        break;
      case "middleCenter":
        style.transform += "translate(-50%,-50%)";
        break;
      case "middleLeft":
        style.transform += "translate(0,-50%)";
        break;
      case "middleRight":
        style.transform += "translate(-100%,-50%)";
        break;
      case "topCenter":
        style.transform += "translate(-50%,0)";
        break;
      case "topRight":
        style.transform += "translate(-100%,0)";
        break;
    }
  },
  dimensions(node, style) {
    const parent = node[$getSubformParent]();
    let width = node.w;
    const height = node.h;
    if (parent.layout?.includes("row")) {
      const extra = parent[$extra];
      const colSpan = node.colSpan;
      let w;
      if (colSpan === -1) {
        w = extra.columnWidths.slice(extra.currentColumn).reduce((a, x) => a + x, 0);
        extra.currentColumn = 0;
      } else {
        w = extra.columnWidths.slice(extra.currentColumn, extra.currentColumn + colSpan).reduce((a, x) => a + x, 0);
        extra.currentColumn = (extra.currentColumn + node.colSpan) % extra.columnWidths.length;
      }
      if (!isNaN(w)) {
        width = node.w = w;
      }
    }
    style.width = width !== "" ? measureToString(width) : "auto";
    style.height = height !== "" ? measureToString(height) : "auto";
  },
  position(node, style) {
    const parent = node[$getSubformParent]();
    if (parent?.layout && parent.layout !== "position") {
      return;
    }
    style.position = "absolute";
    style.left = measureToString(node.x);
    style.top = measureToString(node.y);
  },
  rotate(node, style) {
    if (node.rotate) {
      if (!("transform" in style)) {
        style.transform = "";
      }
      style.transform += `rotate(-${node.rotate}deg)`;
      style.transformOrigin = "top left";
    }
  },
  presence(node, style) {
    switch (node.presence) {
      case "invisible":
        style.visibility = "hidden";
        break;
      case "hidden":
      case "inactive":
        style.display = "none";
        break;
    }
  },
  hAlign(node, style) {
    if (node[$nodeName] === "para") {
      switch (node.hAlign) {
        case "justifyAll":
          style.textAlign = "justify-all";
          break;
        case "radix":
          style.textAlign = "left";
          break;
        default:
          style.textAlign = node.hAlign;
      }
    } else {
      switch (node.hAlign) {
        case "left":
          style.alignSelf = "start";
          break;
        case "center":
          style.alignSelf = "center";
          break;
        case "right":
          style.alignSelf = "end";
          break;
      }
    }
  },
  margin(node, style) {
    if (node.margin) {
      style.margin = node.margin[$toStyle]().margin;
    }
  }
};
function setMinMaxDimensions(node, style) {
  const parent = node[$getSubformParent]();
  if (parent.layout === "position") {
    if (node.minW > 0) {
      style.minWidth = measureToString(node.minW);
    }
    if (node.maxW > 0) {
      style.maxWidth = measureToString(node.maxW);
    }
    if (node.minH > 0) {
      style.minHeight = measureToString(node.minH);
    }
    if (node.maxH > 0) {
      style.maxHeight = measureToString(node.maxH);
    }
  }
}
function layoutText(text, xfaFont, margin, lineHeight, fontFinder, width) {
  const measure = new TextMeasure(xfaFont, margin, lineHeight, fontFinder);
  if (typeof text === "string") {
    measure.addString(text);
  } else {
    text[$pushGlyphs](measure);
  }
  return measure.compute(width);
}
function layoutNode(node, availableSpace) {
  let height = null;
  let width = null;
  let isBroken = false;
  if ((!node.w || !node.h) && node.value) {
    let marginH = 0;
    let marginV = 0;
    if (node.margin) {
      marginH = node.margin.leftInset + node.margin.rightInset;
      marginV = node.margin.topInset + node.margin.bottomInset;
    }
    let lineHeight = null;
    let margin = null;
    if (node.para) {
      margin = Object.create(null);
      lineHeight = node.para.lineHeight === "" ? null : node.para.lineHeight;
      margin.top = node.para.spaceAbove === "" ? 0 : node.para.spaceAbove;
      margin.bottom = node.para.spaceBelow === "" ? 0 : node.para.spaceBelow;
      margin.left = node.para.marginLeft === "" ? 0 : node.para.marginLeft;
      margin.right = node.para.marginRight === "" ? 0 : node.para.marginRight;
    }
    let font = node.font;
    if (!font) {
      const root = node[$getTemplateRoot]();
      let parent = node[$getParent]();
      while (parent && parent !== root) {
        if (parent.font) {
          font = parent.font;
          break;
        }
        parent = parent[$getParent]();
      }
    }
    const maxWidth = (node.w || availableSpace.width) - marginH;
    const fontFinder = node[$globalData].fontFinder;
    if (node.value.exData && node.value.exData[$content] && node.value.exData.contentType === "text/html") {
      const res = layoutText(node.value.exData[$content], font, margin, lineHeight, fontFinder, maxWidth);
      width = res.width;
      height = res.height;
      isBroken = res.isBroken;
    } else {
      const text = node.value[$text]();
      if (text) {
        const res = layoutText(text, font, margin, lineHeight, fontFinder, maxWidth);
        width = res.width;
        height = res.height;
        isBroken = res.isBroken;
      }
    }
    if (width !== null && !node.w) {
      width += marginH;
    }
    if (height !== null && !node.h) {
      height += marginV;
    }
  }
  return {
    w: width,
    h: height,
    isBroken
  };
}
function computeBbox(node, html, availableSpace) {
  let bbox;
  if (node.w !== "" && node.h !== "") {
    bbox = [node.x, node.y, node.w, node.h];
  } else {
    if (!availableSpace) {
      return null;
    }
    let width = node.w;
    if (width === "") {
      if (node.maxW === 0) {
        const parent = node[$getSubformParent]();
        width = parent.layout === "position" && parent.w !== "" ? 0 : node.minW;
      } else {
        width = Math.min(node.maxW, availableSpace.width);
      }
      html.attributes.style.width = measureToString(width);
    }
    let height = node.h;
    if (height === "") {
      if (node.maxH === 0) {
        const parent = node[$getSubformParent]();
        height = parent.layout === "position" && parent.h !== "" ? 0 : node.minH;
      } else {
        height = Math.min(node.maxH, availableSpace.height);
      }
      html.attributes.style.height = measureToString(height);
    }
    bbox = [node.x, node.y, width, height];
  }
  return bbox;
}
function fixDimensions(node) {
  const parent = node[$getSubformParent]();
  if (parent.layout?.includes("row")) {
    const extra = parent[$extra];
    const colSpan = node.colSpan;
    let width;
    if (colSpan === -1) {
      width = extra.columnWidths.slice(extra.currentColumn).reduce((a, w) => a + w, 0);
    } else {
      width = extra.columnWidths.slice(extra.currentColumn, extra.currentColumn + colSpan).reduce((a, w) => a + w, 0);
    }
    if (!isNaN(width)) {
      node.w = width;
    }
  }
  if (parent.layout && parent.layout !== "position") {
    node.x = node.y = 0;
  }
  if (node.layout === "table") {
    if (node.w === "" && Array.isArray(node.columnWidths)) {
      node.w = node.columnWidths.reduce((a, x) => a + x, 0);
    }
  }
}
function layoutClass(node) {
  switch (node.layout) {
    case "position":
      return "xfaPosition";
    case "lr-tb":
      return "xfaLrTb";
    case "rl-row":
      return "xfaRlRow";
    case "rl-tb":
      return "xfaRlTb";
    case "row":
      return "xfaRow";
    case "table":
      return "xfaTable";
    case "tb":
      return "xfaTb";
    default:
      return "xfaPosition";
  }
}
function toStyle(node, ...names) {
  const style = Object.create(null);
  for (const name of names) {
    const value = node[name];
    if (value === null) {
      continue;
    }
    if (converters.hasOwnProperty(name)) {
      converters[name](node, style);
      continue;
    }
    if (value instanceof XFAObject) {
      const newStyle = value[$toStyle]();
      if (newStyle) {
        Object.assign(style, newStyle);
      } else {
        warn(`(DEBUG) - XFA - style for ${name} not implemented yet`);
      }
    }
  }
  return style;
}
function createWrapper(node, html) {
  const {
    attributes
  } = html;
  const {
    style
  } = attributes;
  const wrapper = {
    name: "div",
    attributes: {
      class: ["xfaWrapper"],
      style: Object.create(null)
    },
    children: []
  };
  attributes.class.push("xfaWrapped");
  if (node.border) {
    const {
      widths,
      insets
    } = node.border[$extra];
    let width, height;
    let top = insets[0];
    let left = insets[3];
    const insetsH = insets[0] + insets[2];
    const insetsW = insets[1] + insets[3];
    switch (node.border.hand) {
      case "even":
        top -= widths[0] / 2;
        left -= widths[3] / 2;
        width = `calc(100% + ${(widths[1] + widths[3]) / 2 - insetsW}px)`;
        height = `calc(100% + ${(widths[0] + widths[2]) / 2 - insetsH}px)`;
        break;
      case "left":
        top -= widths[0];
        left -= widths[3];
        width = `calc(100% + ${widths[1] + widths[3] - insetsW}px)`;
        height = `calc(100% + ${widths[0] + widths[2] - insetsH}px)`;
        break;
      case "right":
        width = insetsW ? `calc(100% - ${insetsW}px)` : "100%";
        height = insetsH ? `calc(100% - ${insetsH}px)` : "100%";
        break;
    }
    const classNames = ["xfaBorder"];
    if (isPrintOnly(node.border)) {
      classNames.push("xfaPrintOnly");
    }
    const border = {
      name: "div",
      attributes: {
        class: classNames,
        style: {
          top: `${top}px`,
          left: `${left}px`,
          width,
          height
        }
      },
      children: []
    };
    for (const key of ["border", "borderWidth", "borderColor", "borderRadius", "borderStyle"]) {
      if (style[key] !== undefined) {
        border.attributes.style[key] = style[key];
        delete style[key];
      }
    }
    wrapper.children.push(border, html);
  } else {
    wrapper.children.push(html);
  }
  for (const key of ["background", "backgroundClip", "top", "left", "width", "height", "minWidth", "minHeight", "maxWidth", "maxHeight", "transform", "transformOrigin", "visibility"]) {
    if (style[key] !== undefined) {
      wrapper.attributes.style[key] = style[key];
      delete style[key];
    }
  }
  wrapper.attributes.style.position = style.position === "absolute" ? "absolute" : "relative";
  delete style.position;
  if (style.alignSelf) {
    wrapper.attributes.style.alignSelf = style.alignSelf;
    delete style.alignSelf;
  }
  return wrapper;
}
function fixTextIndent(styles) {
  const indent = getMeasurement(styles.textIndent, "0px");
  if (indent >= 0) {
    return;
  }
  const align = styles.textAlign === "right" ? "right" : "left";
  const name = "padding" + (align === "left" ? "Left" : "Right");
  const padding = getMeasurement(styles[name], "0px");
  styles[name] = `${padding - indent}px`;
}
function setAccess(node, classNames) {
  switch (node.access) {
    case "nonInteractive":
      classNames.push("xfaNonInteractive");
      break;
    case "readOnly":
      classNames.push("xfaReadOnly");
      break;
    case "protected":
      classNames.push("xfaDisabled");
      break;
  }
}
function isPrintOnly(node) {
  return node.relevant.length > 0 && !node.relevant[0].excluded && node.relevant[0].viewname === "print";
}
function getCurrentPara(node) {
  const stack = node[$getTemplateRoot]()[$extra].paraStack;
  return stack.length ? stack.at(-1) : null;
}
function setPara(node, nodeStyle, value) {
  if (value.attributes.class?.includes("xfaRich")) {
    if (nodeStyle) {
      if (node.h === "") {
        nodeStyle.height = "auto";
      }
      if (node.w === "") {
        nodeStyle.width = "auto";
      }
    }
    const para = getCurrentPara(node);
    if (para) {
      const valueStyle = value.attributes.style;
      valueStyle.display = "flex";
      valueStyle.flexDirection = "column";
      switch (para.vAlign) {
        case "top":
          valueStyle.justifyContent = "start";
          break;
        case "bottom":
          valueStyle.justifyContent = "end";
          break;
        case "middle":
          valueStyle.justifyContent = "center";
          break;
      }
      const paraStyle = para[$toStyle]();
      for (const [key, val] of Object.entries(paraStyle)) {
        if (!(key in valueStyle)) {
          valueStyle[key] = val;
        }
      }
    }
  }
}
function setFontFamily(xfaFont, node, fontFinder, style) {
  if (!fontFinder) {
    delete style.fontFamily;
    return;
  }
  const name = stripQuotes(xfaFont.typeface);
  style.fontFamily = `"${name}"`;
  const typeface = fontFinder.find(name);
  if (typeface) {
    const {
      fontFamily
    } = typeface.regular.cssFontInfo;
    if (fontFamily !== name) {
      style.fontFamily = `"${fontFamily}"`;
    }
    const para = getCurrentPara(node);
    if (para && para.lineHeight !== "") {
      return;
    }
    if (style.lineHeight) {
      return;
    }
    const pdfFont = selectFont(xfaFont, typeface);
    if (pdfFont) {
      style.lineHeight = Math.max(1.2, pdfFont.lineHeight);
    }
  }
}
function fixURL(str) {
  const absoluteUrl = createValidAbsoluteUrl(str, null, {
    addDefaultProtocol: true,
    tryConvertEncoding: true
  });
  return absoluteUrl ? absoluteUrl.href : null;
}

;// CONCATENATED MODULE: ./src/core/xfa/layout.js


function createLine(node, children) {
  return {
    name: "div",
    attributes: {
      class: [node.layout === "lr-tb" ? "xfaLr" : "xfaRl"]
    },
    children
  };
}
function flushHTML(node) {
  if (!node[$extra]) {
    return null;
  }
  const attributes = node[$extra].attributes;
  const html = {
    name: "div",
    attributes,
    children: node[$extra].children
  };
  if (node[$extra].failingNode) {
    const htmlFromFailing = node[$extra].failingNode[$flushHTML]();
    if (htmlFromFailing) {
      if (node.layout.endsWith("-tb")) {
        html.children.push(createLine(node, [htmlFromFailing]));
      } else {
        html.children.push(htmlFromFailing);
      }
    }
  }
  if (html.children.length === 0) {
    return null;
  }
  return html;
}
function addHTML(node, html, bbox) {
  const extra = node[$extra];
  const availableSpace = extra.availableSpace;
  const [x, y, w, h] = bbox;
  switch (node.layout) {
    case "position":
      {
        extra.width = Math.max(extra.width, x + w);
        extra.height = Math.max(extra.height, y + h);
        extra.children.push(html);
        break;
      }
    case "lr-tb":
    case "rl-tb":
      if (!extra.line || extra.attempt === 1) {
        extra.line = createLine(node, []);
        extra.children.push(extra.line);
        extra.numberInLine = 0;
      }
      extra.numberInLine += 1;
      extra.line.children.push(html);
      if (extra.attempt === 0) {
        extra.currentWidth += w;
        extra.height = Math.max(extra.height, extra.prevHeight + h);
      } else {
        extra.currentWidth = w;
        extra.prevHeight = extra.height;
        extra.height += h;
        extra.attempt = 0;
      }
      extra.width = Math.max(extra.width, extra.currentWidth);
      break;
    case "rl-row":
    case "row":
      {
        extra.children.push(html);
        extra.width += w;
        extra.height = Math.max(extra.height, h);
        const height = measureToString(extra.height);
        for (const child of extra.children) {
          child.attributes.style.height = height;
        }
        break;
      }
    case "table":
      {
        extra.width = Math.min(availableSpace.width, Math.max(extra.width, w));
        extra.height += h;
        extra.children.push(html);
        break;
      }
    case "tb":
      {
        extra.width = Math.min(availableSpace.width, Math.max(extra.width, w));
        extra.height += h;
        extra.children.push(html);
        break;
      }
  }
}
function getAvailableSpace(node) {
  const availableSpace = node[$extra].availableSpace;
  const marginV = node.margin ? node.margin.topInset + node.margin.bottomInset : 0;
  const marginH = node.margin ? node.margin.leftInset + node.margin.rightInset : 0;
  switch (node.layout) {
    case "lr-tb":
    case "rl-tb":
      if (node[$extra].attempt === 0) {
        return {
          width: availableSpace.width - marginH - node[$extra].currentWidth,
          height: availableSpace.height - marginV - node[$extra].prevHeight
        };
      }
      return {
        width: availableSpace.width - marginH,
        height: availableSpace.height - marginV - node[$extra].height
      };
    case "rl-row":
    case "row":
      const width = node[$extra].columnWidths.slice(node[$extra].currentColumn).reduce((a, x) => a + x);
      return {
        width,
        height: availableSpace.height - marginH
      };
    case "table":
    case "tb":
      return {
        width: availableSpace.width - marginH,
        height: availableSpace.height - marginV - node[$extra].height
      };
    case "position":
    default:
      return availableSpace;
  }
}
function getTransformedBBox(node) {
  let w = node.w === "" ? NaN : node.w;
  let h = node.h === "" ? NaN : node.h;
  let [centerX, centerY] = [0, 0];
  switch (node.anchorType || "") {
    case "bottomCenter":
      [centerX, centerY] = [w / 2, h];
      break;
    case "bottomLeft":
      [centerX, centerY] = [0, h];
      break;
    case "bottomRight":
      [centerX, centerY] = [w, h];
      break;
    case "middleCenter":
      [centerX, centerY] = [w / 2, h / 2];
      break;
    case "middleLeft":
      [centerX, centerY] = [0, h / 2];
      break;
    case "middleRight":
      [centerX, centerY] = [w, h / 2];
      break;
    case "topCenter":
      [centerX, centerY] = [w / 2, 0];
      break;
    case "topRight":
      [centerX, centerY] = [w, 0];
      break;
  }
  let x, y;
  switch (node.rotate || 0) {
    case 0:
      [x, y] = [-centerX, -centerY];
      break;
    case 90:
      [x, y] = [-centerY, centerX];
      [w, h] = [h, -w];
      break;
    case 180:
      [x, y] = [centerX, centerY];
      [w, h] = [-w, -h];
      break;
    case 270:
      [x, y] = [centerY, -centerX];
      [w, h] = [-h, w];
      break;
  }
  return [node.x + x + Math.min(0, w), node.y + y + Math.min(0, h), Math.abs(w), Math.abs(h)];
}
function checkDimensions(node, space) {
  if (node[$getTemplateRoot]()[$extra].firstUnsplittable === null) {
    return true;
  }
  if (node.w === 0 || node.h === 0) {
    return true;
  }
  const ERROR = 2;
  const parent = node[$getSubformParent]();
  const attempt = parent[$extra]?.attempt || 0;
  const [, y, w, h] = getTransformedBBox(node);
  switch (parent.layout) {
    case "lr-tb":
    case "rl-tb":
      if (attempt === 0) {
        if (!node[$getTemplateRoot]()[$extra].noLayoutFailure) {
          if (node.h !== "" && Math.round(h - space.height) > ERROR) {
            return false;
          }
          if (node.w !== "") {
            if (Math.round(w - space.width) <= ERROR) {
              return true;
            }
            if (parent[$extra].numberInLine === 0) {
              return space.height > ERROR;
            }
            return false;
          }
          return space.width > ERROR;
        }
        if (node.w !== "") {
          return Math.round(w - space.width) <= ERROR;
        }
        return space.width > ERROR;
      }
      if (node[$getTemplateRoot]()[$extra].noLayoutFailure) {
        return true;
      }
      if (node.h !== "" && Math.round(h - space.height) > ERROR) {
        return false;
      }
      if (node.w === "" || Math.round(w - space.width) <= ERROR) {
        return space.height > ERROR;
      }
      if (parent[$isThereMoreWidth]()) {
        return false;
      }
      return space.height > ERROR;
    case "table":
    case "tb":
      if (node[$getTemplateRoot]()[$extra].noLayoutFailure) {
        return true;
      }
      if (node.h !== "" && !node[$isSplittable]()) {
        return Math.round(h - space.height) <= ERROR;
      }
      if (node.w === "" || Math.round(w - space.width) <= ERROR) {
        return space.height > ERROR;
      }
      if (parent[$isThereMoreWidth]()) {
        return false;
      }
      return space.height > ERROR;
    case "position":
      if (node[$getTemplateRoot]()[$extra].noLayoutFailure) {
        return true;
      }
      if (node.h === "" || Math.round(h + y - space.height) <= ERROR) {
        return true;
      }
      const area = node[$getTemplateRoot]()[$extra].currentContentArea;
      return h + y > area.h;
    case "rl-row":
    case "row":
      if (node[$getTemplateRoot]()[$extra].noLayoutFailure) {
        return true;
      }
      if (node.h !== "") {
        return Math.round(h - space.height) <= ERROR;
      }
      return true;
    default:
      return true;
  }
}

;// CONCATENATED MODULE: ./src/core/xfa/template.js










const TEMPLATE_NS_ID = NamespaceIds.template.id;
const SVG_NS = "http://www.w3.org/2000/svg";
const MAX_ATTEMPTS_FOR_LRTB_LAYOUT = 2;
const MAX_EMPTY_PAGES = 3;
const DEFAULT_TAB_INDEX = 5000;
const HEADING_PATTERN = /^H(\d+)$/;
const MIMES = new Set(["image/gif", "image/jpeg", "image/jpg", "image/pjpeg", "image/png", "image/apng", "image/x-png", "image/bmp", "image/x-ms-bmp", "image/tiff", "image/tif", "application/octet-stream"]);
const IMAGES_HEADERS = [[[0x42, 0x4d], "image/bmp"], [[0xff, 0xd8, 0xff], "image/jpeg"], [[0x49, 0x49, 0x2a, 0x00], "image/tiff"], [[0x4d, 0x4d, 0x00, 0x2a], "image/tiff"], [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61], "image/gif"], [[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a], "image/png"]];
function getBorderDims(node) {
  if (!node || !node.border) {
    return {
      w: 0,
      h: 0
    };
  }
  const borderExtra = node.border[$getExtra]();
  if (!borderExtra) {
    return {
      w: 0,
      h: 0
    };
  }
  return {
    w: borderExtra.widths[0] + borderExtra.widths[2] + borderExtra.insets[0] + borderExtra.insets[2],
    h: borderExtra.widths[1] + borderExtra.widths[3] + borderExtra.insets[1] + borderExtra.insets[3]
  };
}
function hasMargin(node) {
  return node.margin && (node.margin.topInset || node.margin.rightInset || node.margin.bottomInset || node.margin.leftInset);
}
function _setValue(templateNode, value) {
  if (!templateNode.value) {
    const nodeValue = new Value({});
    templateNode[$appendChild](nodeValue);
    templateNode.value = nodeValue;
  }
  templateNode.value[$setValue](value);
}
function* getContainedChildren(node) {
  for (const child of node[$getChildren]()) {
    if (child instanceof SubformSet) {
      yield* child[$getContainedChildren]();
      continue;
    }
    yield child;
  }
}
function isRequired(node) {
  return node.validate?.nullTest === "error";
}
function setTabIndex(node) {
  while (node) {
    if (!node.traversal) {
      node[$tabIndex] = node[$getParent]()[$tabIndex];
      return;
    }
    if (node[$tabIndex]) {
      return;
    }
    let next = null;
    for (const child of node.traversal[$getChildren]()) {
      if (child.operation === "next") {
        next = child;
        break;
      }
    }
    if (!next || !next.ref) {
      node[$tabIndex] = node[$getParent]()[$tabIndex];
      return;
    }
    const root = node[$getTemplateRoot]();
    node[$tabIndex] = ++root[$tabIndex];
    const ref = root[$searchNode](next.ref, node);
    if (!ref) {
      return;
    }
    node = ref[0];
  }
}
function applyAssist(obj, attributes) {
  const assist = obj.assist;
  if (assist) {
    const assistTitle = assist[$toHTML]();
    if (assistTitle) {
      attributes.title = assistTitle;
    }
    const role = assist.role;
    const match = role.match(HEADING_PATTERN);
    if (match) {
      const ariaRole = "heading";
      const ariaLevel = match[1];
      attributes.role = ariaRole;
      attributes["aria-level"] = ariaLevel;
    }
  }
  if (obj.layout === "table") {
    attributes.role = "table";
  } else if (obj.layout === "row") {
    attributes.role = "row";
  } else {
    const parent = obj[$getParent]();
    if (parent.layout === "row") {
      attributes.role = parent.assist?.role === "TH" ? "columnheader" : "cell";
    }
  }
}
function ariaLabel(obj) {
  if (!obj.assist) {
    return null;
  }
  const assist = obj.assist;
  if (assist.speak && assist.speak[$content] !== "") {
    return assist.speak[$content];
  }
  if (assist.toolTip) {
    return assist.toolTip[$content];
  }
  return null;
}
function valueToHtml(value) {
  return HTMLResult.success({
    name: "div",
    attributes: {
      class: ["xfaRich"],
      style: Object.create(null)
    },
    children: [{
      name: "span",
      attributes: {
        style: Object.create(null)
      },
      value
    }]
  });
}
function setFirstUnsplittable(node) {
  const root = node[$getTemplateRoot]();
  if (root[$extra].firstUnsplittable === null) {
    root[$extra].firstUnsplittable = node;
    root[$extra].noLayoutFailure = true;
  }
}
function unsetFirstUnsplittable(node) {
  const root = node[$getTemplateRoot]();
  if (root[$extra].firstUnsplittable === node) {
    root[$extra].noLayoutFailure = false;
  }
}
function handleBreak(node) {
  if (node[$extra]) {
    return false;
  }
  node[$extra] = Object.create(null);
  if (node.targetType === "auto") {
    return false;
  }
  const root = node[$getTemplateRoot]();
  let target = null;
  if (node.target) {
    target = root[$searchNode](node.target, node[$getParent]());
    if (!target) {
      return false;
    }
    target = target[0];
  }
  const {
    currentPageArea,
    currentContentArea
  } = root[$extra];
  if (node.targetType === "pageArea") {
    if (!(target instanceof PageArea)) {
      target = null;
    }
    if (node.startNew) {
      node[$extra].target = target || currentPageArea;
      return true;
    } else if (target && target !== currentPageArea) {
      node[$extra].target = target;
      return true;
    }
    return false;
  }
  if (!(target instanceof ContentArea)) {
    target = null;
  }
  const pageArea = target && target[$getParent]();
  let index;
  let nextPageArea = pageArea;
  if (node.startNew) {
    if (target) {
      const contentAreas = pageArea.contentArea.children;
      const indexForCurrent = contentAreas.indexOf(currentContentArea);
      const indexForTarget = contentAreas.indexOf(target);
      if (indexForCurrent !== -1 && indexForCurrent < indexForTarget) {
        nextPageArea = null;
      }
      index = indexForTarget - 1;
    } else {
      index = currentPageArea.contentArea.children.indexOf(currentContentArea);
    }
  } else if (target && target !== currentContentArea) {
    const contentAreas = pageArea.contentArea.children;
    index = contentAreas.indexOf(target) - 1;
    nextPageArea = pageArea === currentPageArea ? null : pageArea;
  } else {
    return false;
  }
  node[$extra].target = nextPageArea;
  node[$extra].index = index;
  return true;
}
function handleOverflow(node, extraNode, space) {
  const root = node[$getTemplateRoot]();
  const saved = root[$extra].noLayoutFailure;
  const savedMethod = extraNode[$getSubformParent];
  extraNode[$getSubformParent] = () => node;
  root[$extra].noLayoutFailure = true;
  const res = extraNode[$toHTML](space);
  node[$addHTML](res.html, res.bbox);
  root[$extra].noLayoutFailure = saved;
  extraNode[$getSubformParent] = savedMethod;
}
class AppearanceFilter extends StringObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "appearanceFilter");
    this.id = attributes.id || "";
    this.type = getStringOption(attributes.type, ["optional", "required"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class Arc extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "arc", true);
    this.circular = getInteger({
      data: attributes.circular,
      defaultValue: 0,
      validate: x => x === 1
    });
    this.hand = getStringOption(attributes.hand, ["even", "left", "right"]);
    this.id = attributes.id || "";
    this.startAngle = getFloat({
      data: attributes.startAngle,
      defaultValue: 0,
      validate: x => true
    });
    this.sweepAngle = getFloat({
      data: attributes.sweepAngle,
      defaultValue: 360,
      validate: x => true
    });
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.edge = null;
    this.fill = null;
  }
  [$toHTML]() {
    const edge = this.edge || new Edge({});
    const edgeStyle = edge[$toStyle]();
    const style = Object.create(null);
    if (this.fill?.presence === "visible") {
      Object.assign(style, this.fill[$toStyle]());
    } else {
      style.fill = "transparent";
    }
    style.strokeWidth = measureToString(edge.presence === "visible" ? edge.thickness : 0);
    style.stroke = edgeStyle.color;
    let arc;
    const attributes = {
      xmlns: SVG_NS,
      style: {
        width: "100%",
        height: "100%",
        overflow: "visible"
      }
    };
    if (this.sweepAngle === 360) {
      arc = {
        name: "ellipse",
        attributes: {
          xmlns: SVG_NS,
          cx: "50%",
          cy: "50%",
          rx: "50%",
          ry: "50%",
          style
        }
      };
    } else {
      const startAngle = this.startAngle * Math.PI / 180;
      const sweepAngle = this.sweepAngle * Math.PI / 180;
      const largeArc = this.sweepAngle > 180 ? 1 : 0;
      const [x1, y1, x2, y2] = [50 * (1 + Math.cos(startAngle)), 50 * (1 - Math.sin(startAngle)), 50 * (1 + Math.cos(startAngle + sweepAngle)), 50 * (1 - Math.sin(startAngle + sweepAngle))];
      arc = {
        name: "path",
        attributes: {
          xmlns: SVG_NS,
          d: `M ${x1} ${y1} A 50 50 0 ${largeArc} 0 ${x2} ${y2}`,
          vectorEffect: "non-scaling-stroke",
          style
        }
      };
      Object.assign(attributes, {
        viewBox: "0 0 100 100",
        preserveAspectRatio: "none"
      });
    }
    const svg = {
      name: "svg",
      children: [arc],
      attributes
    };
    const parent = this[$getParent]()[$getParent]();
    if (hasMargin(parent)) {
      return HTMLResult.success({
        name: "div",
        attributes: {
          style: {
            display: "inline",
            width: "100%",
            height: "100%"
          }
        },
        children: [svg]
      });
    }
    svg.attributes.style.position = "absolute";
    return HTMLResult.success(svg);
  }
}
class Area extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "area", true);
    this.colSpan = getInteger({
      data: attributes.colSpan,
      defaultValue: 1,
      validate: n => n >= 1 || n === -1
    });
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.relevant = getRelevant(attributes.relevant);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.x = getMeasurement(attributes.x, "0pt");
    this.y = getMeasurement(attributes.y, "0pt");
    this.desc = null;
    this.extras = null;
    this.area = new XFAObjectArray();
    this.draw = new XFAObjectArray();
    this.exObject = new XFAObjectArray();
    this.exclGroup = new XFAObjectArray();
    this.field = new XFAObjectArray();
    this.subform = new XFAObjectArray();
    this.subformSet = new XFAObjectArray();
  }
  *[$getContainedChildren]() {
    yield* getContainedChildren(this);
  }
  [$isTransparent]() {
    return true;
  }
  [$isBindable]() {
    return true;
  }
  [$addHTML](html, bbox) {
    const [x, y, w, h] = bbox;
    this[$extra].width = Math.max(this[$extra].width, x + w);
    this[$extra].height = Math.max(this[$extra].height, y + h);
    this[$extra].children.push(html);
  }
  [$getAvailableSpace]() {
    return this[$extra].availableSpace;
  }
  [$toHTML](availableSpace) {
    const style = toStyle(this, "position");
    const attributes = {
      style,
      id: this[$uid],
      class: ["xfaArea"]
    };
    if (isPrintOnly(this)) {
      attributes.class.push("xfaPrintOnly");
    }
    if (this.name) {
      attributes.xfaName = this.name;
    }
    const children = [];
    this[$extra] = {
      children,
      width: 0,
      height: 0,
      availableSpace
    };
    const result = this[$childrenToHTML]({
      filter: new Set(["area", "draw", "field", "exclGroup", "subform", "subformSet"]),
      include: true
    });
    if (!result.success) {
      if (result.isBreak()) {
        return result;
      }
      delete this[$extra];
      return HTMLResult.FAILURE;
    }
    style.width = measureToString(this[$extra].width);
    style.height = measureToString(this[$extra].height);
    const html = {
      name: "div",
      attributes,
      children
    };
    const bbox = [this.x, this.y, this[$extra].width, this[$extra].height];
    delete this[$extra];
    return HTMLResult.success(html, bbox);
  }
}
class Assist extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "assist", true);
    this.id = attributes.id || "";
    this.role = attributes.role || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.speak = null;
    this.toolTip = null;
  }
  [$toHTML]() {
    return this.toolTip?.[$content] || null;
  }
}
class Barcode extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "barcode", true);
    this.charEncoding = getKeyword({
      data: attributes.charEncoding ? attributes.charEncoding.toLowerCase() : "",
      defaultValue: "",
      validate: k => ["utf-8", "big-five", "fontspecific", "gbk", "gb-18030", "gb-2312", "ksc-5601", "none", "shift-jis", "ucs-2", "utf-16"].includes(k) || k.match(/iso-8859-\d{2}/)
    });
    this.checksum = getStringOption(attributes.checksum, ["none", "1mod10", "1mod10_1mod11", "2mod10", "auto"]);
    this.dataColumnCount = getInteger({
      data: attributes.dataColumnCount,
      defaultValue: -1,
      validate: x => x >= 0
    });
    this.dataLength = getInteger({
      data: attributes.dataLength,
      defaultValue: -1,
      validate: x => x >= 0
    });
    this.dataPrep = getStringOption(attributes.dataPrep, ["none", "flateCompress"]);
    this.dataRowCount = getInteger({
      data: attributes.dataRowCount,
      defaultValue: -1,
      validate: x => x >= 0
    });
    this.endChar = attributes.endChar || "";
    this.errorCorrectionLevel = getInteger({
      data: attributes.errorCorrectionLevel,
      defaultValue: -1,
      validate: x => x >= 0 && x <= 8
    });
    this.id = attributes.id || "";
    this.moduleHeight = getMeasurement(attributes.moduleHeight, "5mm");
    this.moduleWidth = getMeasurement(attributes.moduleWidth, "0.25mm");
    this.printCheckDigit = getInteger({
      data: attributes.printCheckDigit,
      defaultValue: 0,
      validate: x => x === 1
    });
    this.rowColumnRatio = getRatio(attributes.rowColumnRatio);
    this.startChar = attributes.startChar || "";
    this.textLocation = getStringOption(attributes.textLocation, ["below", "above", "aboveEmbedded", "belowEmbedded", "none"]);
    this.truncate = getInteger({
      data: attributes.truncate,
      defaultValue: 0,
      validate: x => x === 1
    });
    this.type = getStringOption(attributes.type ? attributes.type.toLowerCase() : "", ["aztec", "codabar", "code2of5industrial", "code2of5interleaved", "code2of5matrix", "code2of5standard", "code3of9", "code3of9extended", "code11", "code49", "code93", "code128", "code128a", "code128b", "code128c", "code128sscc", "datamatrix", "ean8", "ean8add2", "ean8add5", "ean13", "ean13add2", "ean13add5", "ean13pwcd", "fim", "logmars", "maxicode", "msi", "pdf417", "pdf417macro", "plessey", "postauscust2", "postauscust3", "postausreplypaid", "postausstandard", "postukrm4scc", "postusdpbc", "postusimb", "postusstandard", "postus5zip", "qrcode", "rfid", "rss14", "rss14expanded", "rss14limited", "rss14stacked", "rss14stackedomni", "rss14truncated", "telepen", "ucc128", "ucc128random", "ucc128sscc", "upca", "upcaadd2", "upcaadd5", "upcapwcd", "upce", "upceadd2", "upceadd5", "upcean2", "upcean5", "upsmaxicode"]);
    this.upsMode = getStringOption(attributes.upsMode, ["usCarrier", "internationalCarrier", "secureSymbol", "standardSymbol"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.wideNarrowRatio = getRatio(attributes.wideNarrowRatio);
    this.encrypt = null;
    this.extras = null;
  }
}
class Bind extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "bind", true);
    this.match = getStringOption(attributes.match, ["once", "dataRef", "global", "none"]);
    this.ref = attributes.ref || "";
    this.picture = null;
  }
}
class BindItems extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "bindItems");
    this.connection = attributes.connection || "";
    this.labelRef = attributes.labelRef || "";
    this.ref = attributes.ref || "";
    this.valueRef = attributes.valueRef || "";
  }
}
class Bookend extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "bookend");
    this.id = attributes.id || "";
    this.leader = attributes.leader || "";
    this.trailer = attributes.trailer || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class BooleanElement extends Option01 {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "boolean");
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
  [$toHTML](availableSpace) {
    return valueToHtml(this[$content] === 1 ? "1" : "0");
  }
}
class Border extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "border", true);
    this.break = getStringOption(attributes.break, ["close", "open"]);
    this.hand = getStringOption(attributes.hand, ["even", "left", "right"]);
    this.id = attributes.id || "";
    this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
    this.relevant = getRelevant(attributes.relevant);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.corner = new XFAObjectArray(4);
    this.edge = new XFAObjectArray(4);
    this.extras = null;
    this.fill = null;
    this.margin = null;
  }
  [$getExtra]() {
    if (!this[$extra]) {
      const edges = this.edge.children.slice();
      if (edges.length < 4) {
        const defaultEdge = edges.at(-1) || new Edge({});
        for (let i = edges.length; i < 4; i++) {
          edges.push(defaultEdge);
        }
      }
      const widths = edges.map(edge => edge.thickness);
      const insets = [0, 0, 0, 0];
      if (this.margin) {
        insets[0] = this.margin.topInset;
        insets[1] = this.margin.rightInset;
        insets[2] = this.margin.bottomInset;
        insets[3] = this.margin.leftInset;
      }
      this[$extra] = {
        widths,
        insets,
        edges
      };
    }
    return this[$extra];
  }
  [$toStyle]() {
    const {
      edges
    } = this[$getExtra]();
    const edgeStyles = edges.map(node => {
      const style = node[$toStyle]();
      style.color ||= "#000000";
      return style;
    });
    const style = Object.create(null);
    if (this.margin) {
      Object.assign(style, this.margin[$toStyle]());
    }
    if (this.fill?.presence === "visible") {
      Object.assign(style, this.fill[$toStyle]());
    }
    if (this.corner.children.some(node => node.radius !== 0)) {
      const cornerStyles = this.corner.children.map(node => node[$toStyle]());
      if (cornerStyles.length === 2 || cornerStyles.length === 3) {
        const last = cornerStyles.at(-1);
        for (let i = cornerStyles.length; i < 4; i++) {
          cornerStyles.push(last);
        }
      }
      style.borderRadius = cornerStyles.map(s => s.radius).join(" ");
    }
    switch (this.presence) {
      case "invisible":
      case "hidden":
        style.borderStyle = "";
        break;
      case "inactive":
        style.borderStyle = "none";
        break;
      default:
        style.borderStyle = edgeStyles.map(s => s.style).join(" ");
        break;
    }
    style.borderWidth = edgeStyles.map(s => s.width).join(" ");
    style.borderColor = edgeStyles.map(s => s.color).join(" ");
    return style;
  }
}
class Break extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "break", true);
    this.after = getStringOption(attributes.after, ["auto", "contentArea", "pageArea", "pageEven", "pageOdd"]);
    this.afterTarget = attributes.afterTarget || "";
    this.before = getStringOption(attributes.before, ["auto", "contentArea", "pageArea", "pageEven", "pageOdd"]);
    this.beforeTarget = attributes.beforeTarget || "";
    this.bookendLeader = attributes.bookendLeader || "";
    this.bookendTrailer = attributes.bookendTrailer || "";
    this.id = attributes.id || "";
    this.overflowLeader = attributes.overflowLeader || "";
    this.overflowTarget = attributes.overflowTarget || "";
    this.overflowTrailer = attributes.overflowTrailer || "";
    this.startNew = getInteger({
      data: attributes.startNew,
      defaultValue: 0,
      validate: x => x === 1
    });
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.extras = null;
  }
}
class BreakAfter extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "breakAfter", true);
    this.id = attributes.id || "";
    this.leader = attributes.leader || "";
    this.startNew = getInteger({
      data: attributes.startNew,
      defaultValue: 0,
      validate: x => x === 1
    });
    this.target = attributes.target || "";
    this.targetType = getStringOption(attributes.targetType, ["auto", "contentArea", "pageArea"]);
    this.trailer = attributes.trailer || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.script = null;
  }
}
class BreakBefore extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "breakBefore", true);
    this.id = attributes.id || "";
    this.leader = attributes.leader || "";
    this.startNew = getInteger({
      data: attributes.startNew,
      defaultValue: 0,
      validate: x => x === 1
    });
    this.target = attributes.target || "";
    this.targetType = getStringOption(attributes.targetType, ["auto", "contentArea", "pageArea"]);
    this.trailer = attributes.trailer || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.script = null;
  }
  [$toHTML](availableSpace) {
    this[$extra] = {};
    return HTMLResult.FAILURE;
  }
}
class Button extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "button", true);
    this.highlight = getStringOption(attributes.highlight, ["inverted", "none", "outline", "push"]);
    this.id = attributes.id || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.extras = null;
  }
  [$toHTML](availableSpace) {
    const parent = this[$getParent]();
    const grandpa = parent[$getParent]();
    const htmlButton = {
      name: "button",
      attributes: {
        id: this[$uid],
        class: ["xfaButton"],
        style: {}
      },
      children: []
    };
    for (const event of grandpa.event.children) {
      if (event.activity !== "click" || !event.script) {
        continue;
      }
      const jsURL = recoverJsURL(event.script[$content]);
      if (!jsURL) {
        continue;
      }
      const href = fixURL(jsURL.url);
      if (!href) {
        continue;
      }
      htmlButton.children.push({
        name: "a",
        attributes: {
          id: "link" + this[$uid],
          href,
          newWindow: jsURL.newWindow,
          class: ["xfaLink"],
          style: {}
        },
        children: []
      });
    }
    return HTMLResult.success(htmlButton);
  }
}
class Calculate extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "calculate", true);
    this.id = attributes.id || "";
    this.override = getStringOption(attributes.override, ["disabled", "error", "ignore", "warning"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.extras = null;
    this.message = null;
    this.script = null;
  }
}
class Caption extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "caption", true);
    this.id = attributes.id || "";
    this.placement = getStringOption(attributes.placement, ["left", "bottom", "inline", "right", "top"]);
    this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
    this.reserve = Math.ceil(getMeasurement(attributes.reserve));
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.extras = null;
    this.font = null;
    this.margin = null;
    this.para = null;
    this.value = null;
  }
  [$setValue](value) {
    _setValue(this, value);
  }
  [$getExtra](availableSpace) {
    if (!this[$extra]) {
      let {
        width,
        height
      } = availableSpace;
      switch (this.placement) {
        case "left":
        case "right":
        case "inline":
          width = this.reserve <= 0 ? width : this.reserve;
          break;
        case "top":
        case "bottom":
          height = this.reserve <= 0 ? height : this.reserve;
          break;
      }
      this[$extra] = layoutNode(this, {
        width,
        height
      });
    }
    return this[$extra];
  }
  [$toHTML](availableSpace) {
    if (!this.value) {
      return HTMLResult.EMPTY;
    }
    this[$pushPara]();
    const value = this.value[$toHTML](availableSpace).html;
    if (!value) {
      this[$popPara]();
      return HTMLResult.EMPTY;
    }
    const savedReserve = this.reserve;
    if (this.reserve <= 0) {
      const {
        w,
        h
      } = this[$getExtra](availableSpace);
      switch (this.placement) {
        case "left":
        case "right":
        case "inline":
          this.reserve = w;
          break;
        case "top":
        case "bottom":
          this.reserve = h;
          break;
      }
    }
    const children = [];
    if (typeof value === "string") {
      children.push({
        name: "#text",
        value
      });
    } else {
      children.push(value);
    }
    const style = toStyle(this, "font", "margin", "visibility");
    switch (this.placement) {
      case "left":
      case "right":
        if (this.reserve > 0) {
          style.width = measureToString(this.reserve);
        }
        break;
      case "top":
      case "bottom":
        if (this.reserve > 0) {
          style.height = measureToString(this.reserve);
        }
        break;
    }
    setPara(this, null, value);
    this[$popPara]();
    this.reserve = savedReserve;
    return HTMLResult.success({
      name: "div",
      attributes: {
        style,
        class: ["xfaCaption"]
      },
      children
    });
  }
}
class Certificate extends StringObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "certificate");
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class Certificates extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "certificates", true);
    this.credentialServerPolicy = getStringOption(attributes.credentialServerPolicy, ["optional", "required"]);
    this.id = attributes.id || "";
    this.url = attributes.url || "";
    this.urlPolicy = attributes.urlPolicy || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.encryption = null;
    this.issuers = null;
    this.keyUsage = null;
    this.oids = null;
    this.signing = null;
    this.subjectDNs = null;
  }
}
class CheckButton extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "checkButton", true);
    this.id = attributes.id || "";
    this.mark = getStringOption(attributes.mark, ["default", "check", "circle", "cross", "diamond", "square", "star"]);
    this.shape = getStringOption(attributes.shape, ["square", "round"]);
    this.size = getMeasurement(attributes.size, "10pt");
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.border = null;
    this.extras = null;
    this.margin = null;
  }
  [$toHTML](availableSpace) {
    const style = toStyle("margin");
    const size = measureToString(this.size);
    style.width = style.height = size;
    let type;
    let className;
    let groupId;
    const field = this[$getParent]()[$getParent]();
    const items = field.items.children.length && field.items.children[0][$toHTML]().html || [];
    const exportedValue = {
      on: (items[0] !== undefined ? items[0] : "on").toString(),
      off: (items[1] !== undefined ? items[1] : "off").toString()
    };
    const value = field.value?.[$text]() || "off";
    const checked = value === exportedValue.on || undefined;
    const container = field[$getSubformParent]();
    const fieldId = field[$uid];
    let dataId;
    if (container instanceof ExclGroup) {
      groupId = container[$uid];
      type = "radio";
      className = "xfaRadio";
      dataId = container[$data]?.[$uid] || container[$uid];
    } else {
      type = "checkbox";
      className = "xfaCheckbox";
      dataId = field[$data]?.[$uid] || field[$uid];
    }
    const input = {
      name: "input",
      attributes: {
        class: [className],
        style,
        fieldId,
        dataId,
        type,
        checked,
        xfaOn: exportedValue.on,
        xfaOff: exportedValue.off,
        "aria-label": ariaLabel(field),
        "aria-required": false
      }
    };
    if (groupId) {
      input.attributes.name = groupId;
    }
    if (isRequired(field)) {
      input.attributes["aria-required"] = true;
      input.attributes.required = true;
    }
    return HTMLResult.success({
      name: "label",
      attributes: {
        class: ["xfaLabel"]
      },
      children: [input]
    });
  }
}
class ChoiceList extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "choiceList", true);
    this.commitOn = getStringOption(attributes.commitOn, ["select", "exit"]);
    this.id = attributes.id || "";
    this.open = getStringOption(attributes.open, ["userControl", "always", "multiSelect", "onEntry"]);
    this.textEntry = getInteger({
      data: attributes.textEntry,
      defaultValue: 0,
      validate: x => x === 1
    });
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.border = null;
    this.extras = null;
    this.margin = null;
  }
  [$toHTML](availableSpace) {
    const style = toStyle(this, "border", "margin");
    const ui = this[$getParent]();
    const field = ui[$getParent]();
    const fontSize = field.font?.size || 10;
    const optionStyle = {
      fontSize: `calc(${fontSize}px * var(--scale-factor))`
    };
    const children = [];
    if (field.items.children.length > 0) {
      const items = field.items;
      let displayedIndex = 0;
      let saveIndex = 0;
      if (items.children.length === 2) {
        displayedIndex = items.children[0].save;
        saveIndex = 1 - displayedIndex;
      }
      const displayed = items.children[displayedIndex][$toHTML]().html;
      const values = items.children[saveIndex][$toHTML]().html;
      let selected = false;
      const value = field.value?.[$text]() || "";
      for (let i = 0, ii = displayed.length; i < ii; i++) {
        const option = {
          name: "option",
          attributes: {
            value: values[i] || displayed[i],
            style: optionStyle
          },
          value: displayed[i]
        };
        if (values[i] === value) {
          option.attributes.selected = selected = true;
        }
        children.push(option);
      }
      if (!selected) {
        children.splice(0, 0, {
          name: "option",
          attributes: {
            hidden: true,
            selected: true
          },
          value: " "
        });
      }
    }
    const selectAttributes = {
      class: ["xfaSelect"],
      fieldId: field[$uid],
      dataId: field[$data]?.[$uid] || field[$uid],
      style,
      "aria-label": ariaLabel(field),
      "aria-required": false
    };
    if (isRequired(field)) {
      selectAttributes["aria-required"] = true;
      selectAttributes.required = true;
    }
    if (this.open === "multiSelect") {
      selectAttributes.multiple = true;
    }
    return HTMLResult.success({
      name: "label",
      attributes: {
        class: ["xfaLabel"]
      },
      children: [{
        name: "select",
        children,
        attributes: selectAttributes
      }]
    });
  }
}
class Color extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "color", true);
    this.cSpace = getStringOption(attributes.cSpace, ["SRGB"]);
    this.id = attributes.id || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.value = attributes.value ? getColor(attributes.value) : "";
    this.extras = null;
  }
  [$hasSettableValue]() {
    return false;
  }
  [$toStyle]() {
    return this.value ? Util.makeHexColor(this.value.r, this.value.g, this.value.b) : null;
  }
}
class Comb extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "comb");
    this.id = attributes.id || "";
    this.numberOfCells = getInteger({
      data: attributes.numberOfCells,
      defaultValue: 0,
      validate: x => x >= 0
    });
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class Connect extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "connect", true);
    this.connection = attributes.connection || "";
    this.id = attributes.id || "";
    this.ref = attributes.ref || "";
    this.usage = getStringOption(attributes.usage, ["exportAndImport", "exportOnly", "importOnly"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.picture = null;
  }
}
class ContentArea extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "contentArea", true);
    this.h = getMeasurement(attributes.h);
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.relevant = getRelevant(attributes.relevant);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.w = getMeasurement(attributes.w);
    this.x = getMeasurement(attributes.x, "0pt");
    this.y = getMeasurement(attributes.y, "0pt");
    this.desc = null;
    this.extras = null;
  }
  [$toHTML](availableSpace) {
    const left = measureToString(this.x);
    const top = measureToString(this.y);
    const style = {
      left,
      top,
      width: measureToString(this.w),
      height: measureToString(this.h)
    };
    const classNames = ["xfaContentarea"];
    if (isPrintOnly(this)) {
      classNames.push("xfaPrintOnly");
    }
    return HTMLResult.success({
      name: "div",
      children: [],
      attributes: {
        style,
        class: classNames,
        id: this[$uid]
      }
    });
  }
}
class Corner extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "corner", true);
    this.id = attributes.id || "";
    this.inverted = getInteger({
      data: attributes.inverted,
      defaultValue: 0,
      validate: x => x === 1
    });
    this.join = getStringOption(attributes.join, ["square", "round"]);
    this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
    this.radius = getMeasurement(attributes.radius);
    this.stroke = getStringOption(attributes.stroke, ["solid", "dashDot", "dashDotDot", "dashed", "dotted", "embossed", "etched", "lowered", "raised"]);
    this.thickness = getMeasurement(attributes.thickness, "0.5pt");
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.color = null;
    this.extras = null;
  }
  [$toStyle]() {
    const style = toStyle(this, "visibility");
    style.radius = measureToString(this.join === "square" ? 0 : this.radius);
    return style;
  }
}
class DateElement extends ContentObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "date");
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
  [$finalize]() {
    const date = this[$content].trim();
    this[$content] = date ? new Date(date) : null;
  }
  [$toHTML](availableSpace) {
    return valueToHtml(this[$content] ? this[$content].toString() : "");
  }
}
class DateTime extends ContentObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "dateTime");
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
  [$finalize]() {
    const date = this[$content].trim();
    this[$content] = date ? new Date(date) : null;
  }
  [$toHTML](availableSpace) {
    return valueToHtml(this[$content] ? this[$content].toString() : "");
  }
}
class DateTimeEdit extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "dateTimeEdit", true);
    this.hScrollPolicy = getStringOption(attributes.hScrollPolicy, ["auto", "off", "on"]);
    this.id = attributes.id || "";
    this.picker = getStringOption(attributes.picker, ["host", "none"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.border = null;
    this.comb = null;
    this.extras = null;
    this.margin = null;
  }
  [$toHTML](availableSpace) {
    const style = toStyle(this, "border", "font", "margin");
    const field = this[$getParent]()[$getParent]();
    const html = {
      name: "input",
      attributes: {
        type: "text",
        fieldId: field[$uid],
        dataId: field[$data]?.[$uid] || field[$uid],
        class: ["xfaTextfield"],
        style,
        "aria-label": ariaLabel(field),
        "aria-required": false
      }
    };
    if (isRequired(field)) {
      html.attributes["aria-required"] = true;
      html.attributes.required = true;
    }
    return HTMLResult.success({
      name: "label",
      attributes: {
        class: ["xfaLabel"]
      },
      children: [html]
    });
  }
}
class Decimal extends ContentObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "decimal");
    this.fracDigits = getInteger({
      data: attributes.fracDigits,
      defaultValue: 2,
      validate: x => true
    });
    this.id = attributes.id || "";
    this.leadDigits = getInteger({
      data: attributes.leadDigits,
      defaultValue: -1,
      validate: x => true
    });
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
  [$finalize]() {
    const number = parseFloat(this[$content].trim());
    this[$content] = isNaN(number) ? null : number;
  }
  [$toHTML](availableSpace) {
    return valueToHtml(this[$content] !== null ? this[$content].toString() : "");
  }
}
class DefaultUi extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "defaultUi", true);
    this.id = attributes.id || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.extras = null;
  }
}
class Desc extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "desc", true);
    this.id = attributes.id || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.boolean = new XFAObjectArray();
    this.date = new XFAObjectArray();
    this.dateTime = new XFAObjectArray();
    this.decimal = new XFAObjectArray();
    this.exData = new XFAObjectArray();
    this.float = new XFAObjectArray();
    this.image = new XFAObjectArray();
    this.integer = new XFAObjectArray();
    this.text = new XFAObjectArray();
    this.time = new XFAObjectArray();
  }
}
class DigestMethod extends OptionObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "digestMethod", ["", "SHA1", "SHA256", "SHA512", "RIPEMD160"]);
    this.id = attributes.id || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class DigestMethods extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "digestMethods", true);
    this.id = attributes.id || "";
    this.type = getStringOption(attributes.type, ["optional", "required"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.digestMethod = new XFAObjectArray();
  }
}
class Draw extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "draw", true);
    this.anchorType = getStringOption(attributes.anchorType, ["topLeft", "bottomCenter", "bottomLeft", "bottomRight", "middleCenter", "middleLeft", "middleRight", "topCenter", "topRight"]);
    this.colSpan = getInteger({
      data: attributes.colSpan,
      defaultValue: 1,
      validate: n => n >= 1 || n === -1
    });
    this.h = attributes.h ? getMeasurement(attributes.h) : "";
    this.hAlign = getStringOption(attributes.hAlign, ["left", "center", "justify", "justifyAll", "radix", "right"]);
    this.id = attributes.id || "";
    this.locale = attributes.locale || "";
    this.maxH = getMeasurement(attributes.maxH, "0pt");
    this.maxW = getMeasurement(attributes.maxW, "0pt");
    this.minH = getMeasurement(attributes.minH, "0pt");
    this.minW = getMeasurement(attributes.minW, "0pt");
    this.name = attributes.name || "";
    this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
    this.relevant = getRelevant(attributes.relevant);
    this.rotate = getInteger({
      data: attributes.rotate,
      defaultValue: 0,
      validate: x => x % 90 === 0
    });
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.w = attributes.w ? getMeasurement(attributes.w) : "";
    this.x = getMeasurement(attributes.x, "0pt");
    this.y = getMeasurement(attributes.y, "0pt");
    this.assist = null;
    this.border = null;
    this.caption = null;
    this.desc = null;
    this.extras = null;
    this.font = null;
    this.keep = null;
    this.margin = null;
    this.para = null;
    this.traversal = null;
    this.ui = null;
    this.value = null;
    this.setProperty = new XFAObjectArray();
  }
  [$setValue](value) {
    _setValue(this, value);
  }
  [$toHTML](availableSpace) {
    setTabIndex(this);
    if (this.presence === "hidden" || this.presence === "inactive") {
      return HTMLResult.EMPTY;
    }
    fixDimensions(this);
    this[$pushPara]();
    const savedW = this.w;
    const savedH = this.h;
    const {
      w,
      h,
      isBroken
    } = layoutNode(this, availableSpace);
    if (w && this.w === "") {
      if (isBroken && this[$getSubformParent]()[$isThereMoreWidth]()) {
        this[$popPara]();
        return HTMLResult.FAILURE;
      }
      this.w = w;
    }
    if (h && this.h === "") {
      this.h = h;
    }
    setFirstUnsplittable(this);
    if (!checkDimensions(this, availableSpace)) {
      this.w = savedW;
      this.h = savedH;
      this[$popPara]();
      return HTMLResult.FAILURE;
    }
    unsetFirstUnsplittable(this);
    const style = toStyle(this, "font", "hAlign", "dimensions", "position", "presence", "rotate", "anchorType", "border", "margin");
    setMinMaxDimensions(this, style);
    if (style.margin) {
      style.padding = style.margin;
      delete style.margin;
    }
    const classNames = ["xfaDraw"];
    if (this.font) {
      classNames.push("xfaFont");
    }
    if (isPrintOnly(this)) {
      classNames.push("xfaPrintOnly");
    }
    const attributes = {
      style,
      id: this[$uid],
      class: classNames
    };
    if (this.name) {
      attributes.xfaName = this.name;
    }
    const html = {
      name: "div",
      attributes,
      children: []
    };
    applyAssist(this, attributes);
    const bbox = computeBbox(this, html, availableSpace);
    const value = this.value ? this.value[$toHTML](availableSpace).html : null;
    if (value === null) {
      this.w = savedW;
      this.h = savedH;
      this[$popPara]();
      return HTMLResult.success(createWrapper(this, html), bbox);
    }
    html.children.push(value);
    setPara(this, style, value);
    this.w = savedW;
    this.h = savedH;
    this[$popPara]();
    return HTMLResult.success(createWrapper(this, html), bbox);
  }
}
class Edge extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "edge", true);
    this.cap = getStringOption(attributes.cap, ["square", "butt", "round"]);
    this.id = attributes.id || "";
    this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
    this.stroke = getStringOption(attributes.stroke, ["solid", "dashDot", "dashDotDot", "dashed", "dotted", "embossed", "etched", "lowered", "raised"]);
    this.thickness = getMeasurement(attributes.thickness, "0.5pt");
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.color = null;
    this.extras = null;
  }
  [$toStyle]() {
    const style = toStyle(this, "visibility");
    Object.assign(style, {
      linecap: this.cap,
      width: measureToString(this.thickness),
      color: this.color ? this.color[$toStyle]() : "#000000",
      style: ""
    });
    if (this.presence !== "visible") {
      style.style = "none";
    } else {
      switch (this.stroke) {
        case "solid":
          style.style = "solid";
          break;
        case "dashDot":
          style.style = "dashed";
          break;
        case "dashDotDot":
          style.style = "dashed";
          break;
        case "dashed":
          style.style = "dashed";
          break;
        case "dotted":
          style.style = "dotted";
          break;
        case "embossed":
          style.style = "ridge";
          break;
        case "etched":
          style.style = "groove";
          break;
        case "lowered":
          style.style = "inset";
          break;
        case "raised":
          style.style = "outset";
          break;
      }
    }
    return style;
  }
}
class Encoding extends OptionObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "encoding", ["adbe.x509.rsa_sha1", "adbe.pkcs7.detached", "adbe.pkcs7.sha1"]);
    this.id = attributes.id || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class Encodings extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "encodings", true);
    this.id = attributes.id || "";
    this.type = getStringOption(attributes.type, ["optional", "required"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.encoding = new XFAObjectArray();
  }
}
class Encrypt extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "encrypt", true);
    this.id = attributes.id || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.certificate = null;
  }
}
class EncryptData extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "encryptData", true);
    this.id = attributes.id || "";
    this.operation = getStringOption(attributes.operation, ["encrypt", "decrypt"]);
    this.target = attributes.target || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.filter = null;
    this.manifest = null;
  }
}
class Encryption extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "encryption", true);
    this.id = attributes.id || "";
    this.type = getStringOption(attributes.type, ["optional", "required"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.certificate = new XFAObjectArray();
  }
}
class EncryptionMethod extends OptionObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "encryptionMethod", ["", "AES256-CBC", "TRIPLEDES-CBC", "AES128-CBC", "AES192-CBC"]);
    this.id = attributes.id || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class EncryptionMethods extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "encryptionMethods", true);
    this.id = attributes.id || "";
    this.type = getStringOption(attributes.type, ["optional", "required"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.encryptionMethod = new XFAObjectArray();
  }
}
class Event extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "event", true);
    this.activity = getStringOption(attributes.activity, ["click", "change", "docClose", "docReady", "enter", "exit", "full", "indexChange", "initialize", "mouseDown", "mouseEnter", "mouseExit", "mouseUp", "postExecute", "postOpen", "postPrint", "postSave", "postSign", "postSubmit", "preExecute", "preOpen", "prePrint", "preSave", "preSign", "preSubmit", "ready", "validationState"]);
    this.id = attributes.id || "";
    this.listen = getStringOption(attributes.listen, ["refOnly", "refAndDescendents"]);
    this.name = attributes.name || "";
    this.ref = attributes.ref || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.extras = null;
    this.encryptData = null;
    this.execute = null;
    this.script = null;
    this.signData = null;
    this.submit = null;
  }
}
class ExData extends ContentObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "exData");
    this.contentType = attributes.contentType || "";
    this.href = attributes.href || "";
    this.id = attributes.id || "";
    this.maxLength = getInteger({
      data: attributes.maxLength,
      defaultValue: -1,
      validate: x => x >= -1
    });
    this.name = attributes.name || "";
    this.rid = attributes.rid || "";
    this.transferEncoding = getStringOption(attributes.transferEncoding, ["none", "base64", "package"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
  [$isCDATAXml]() {
    return this.contentType === "text/html";
  }
  [$onChild](child) {
    if (this.contentType === "text/html" && child[$namespaceId] === NamespaceIds.xhtml.id) {
      this[$content] = child;
      return true;
    }
    if (this.contentType === "text/xml") {
      this[$content] = child;
      return true;
    }
    return false;
  }
  [$toHTML](availableSpace) {
    if (this.contentType !== "text/html" || !this[$content]) {
      return HTMLResult.EMPTY;
    }
    return this[$content][$toHTML](availableSpace);
  }
}
class ExObject extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "exObject", true);
    this.archive = attributes.archive || "";
    this.classId = attributes.classId || "";
    this.codeBase = attributes.codeBase || "";
    this.codeType = attributes.codeType || "";
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.extras = null;
    this.boolean = new XFAObjectArray();
    this.date = new XFAObjectArray();
    this.dateTime = new XFAObjectArray();
    this.decimal = new XFAObjectArray();
    this.exData = new XFAObjectArray();
    this.exObject = new XFAObjectArray();
    this.float = new XFAObjectArray();
    this.image = new XFAObjectArray();
    this.integer = new XFAObjectArray();
    this.text = new XFAObjectArray();
    this.time = new XFAObjectArray();
  }
}
class ExclGroup extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "exclGroup", true);
    this.access = getStringOption(attributes.access, ["open", "nonInteractive", "protected", "readOnly"]);
    this.accessKey = attributes.accessKey || "";
    this.anchorType = getStringOption(attributes.anchorType, ["topLeft", "bottomCenter", "bottomLeft", "bottomRight", "middleCenter", "middleLeft", "middleRight", "topCenter", "topRight"]);
    this.colSpan = getInteger({
      data: attributes.colSpan,
      defaultValue: 1,
      validate: n => n >= 1 || n === -1
    });
    this.h = attributes.h ? getMeasurement(attributes.h) : "";
    this.hAlign = getStringOption(attributes.hAlign, ["left", "center", "justify", "justifyAll", "radix", "right"]);
    this.id = attributes.id || "";
    this.layout = getStringOption(attributes.layout, ["position", "lr-tb", "rl-row", "rl-tb", "row", "table", "tb"]);
    this.maxH = getMeasurement(attributes.maxH, "0pt");
    this.maxW = getMeasurement(attributes.maxW, "0pt");
    this.minH = getMeasurement(attributes.minH, "0pt");
    this.minW = getMeasurement(attributes.minW, "0pt");
    this.name = attributes.name || "";
    this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
    this.relevant = getRelevant(attributes.relevant);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.w = attributes.w ? getMeasurement(attributes.w) : "";
    this.x = getMeasurement(attributes.x, "0pt");
    this.y = getMeasurement(attributes.y, "0pt");
    this.assist = null;
    this.bind = null;
    this.border = null;
    this.calculate = null;
    this.caption = null;
    this.desc = null;
    this.extras = null;
    this.margin = null;
    this.para = null;
    this.traversal = null;
    this.validate = null;
    this.connect = new XFAObjectArray();
    this.event = new XFAObjectArray();
    this.field = new XFAObjectArray();
    this.setProperty = new XFAObjectArray();
  }
  [$isBindable]() {
    return true;
  }
  [$hasSettableValue]() {
    return true;
  }
  [$setValue](value) {
    for (const field of this.field.children) {
      if (!field.value) {
        const nodeValue = new Value({});
        field[$appendChild](nodeValue);
        field.value = nodeValue;
      }
      field.value[$setValue](value);
    }
  }
  [$isThereMoreWidth]() {
    return this.layout.endsWith("-tb") && this[$extra].attempt === 0 && this[$extra].numberInLine > 0 || this[$getParent]()[$isThereMoreWidth]();
  }
  [$isSplittable]() {
    const parent = this[$getSubformParent]();
    if (!parent[$isSplittable]()) {
      return false;
    }
    if (this[$extra]._isSplittable !== undefined) {
      return this[$extra]._isSplittable;
    }
    if (this.layout === "position" || this.layout.includes("row")) {
      this[$extra]._isSplittable = false;
      return false;
    }
    if (parent.layout?.endsWith("-tb") && parent[$extra].numberInLine !== 0) {
      return false;
    }
    this[$extra]._isSplittable = true;
    return true;
  }
  [$flushHTML]() {
    return flushHTML(this);
  }
  [$addHTML](html, bbox) {
    addHTML(this, html, bbox);
  }
  [$getAvailableSpace]() {
    return getAvailableSpace(this);
  }
  [$toHTML](availableSpace) {
    setTabIndex(this);
    if (this.presence === "hidden" || this.presence === "inactive" || this.h === 0 || this.w === 0) {
      return HTMLResult.EMPTY;
    }
    fixDimensions(this);
    const children = [];
    const attributes = {
      id: this[$uid],
      class: []
    };
    setAccess(this, attributes.class);
    if (!this[$extra]) {
      this[$extra] = Object.create(null);
    }
    Object.assign(this[$extra], {
      children,
      attributes,
      attempt: 0,
      line: null,
      numberInLine: 0,
      availableSpace: {
        width: Math.min(this.w || Infinity, availableSpace.width),
        height: Math.min(this.h || Infinity, availableSpace.height)
      },
      width: 0,
      height: 0,
      prevHeight: 0,
      currentWidth: 0
    });
    const isSplittable = this[$isSplittable]();
    if (!isSplittable) {
      setFirstUnsplittable(this);
    }
    if (!checkDimensions(this, availableSpace)) {
      return HTMLResult.FAILURE;
    }
    const filter = new Set(["field"]);
    if (this.layout.includes("row")) {
      const columnWidths = this[$getSubformParent]().columnWidths;
      if (Array.isArray(columnWidths) && columnWidths.length > 0) {
        this[$extra].columnWidths = columnWidths;
        this[$extra].currentColumn = 0;
      }
    }
    const style = toStyle(this, "anchorType", "dimensions", "position", "presence", "border", "margin", "hAlign");
    const classNames = ["xfaExclgroup"];
    const cl = layoutClass(this);
    if (cl) {
      classNames.push(cl);
    }
    if (isPrintOnly(this)) {
      classNames.push("xfaPrintOnly");
    }
    attributes.style = style;
    attributes.class = classNames;
    if (this.name) {
      attributes.xfaName = this.name;
    }
    this[$pushPara]();
    const isLrTb = this.layout === "lr-tb" || this.layout === "rl-tb";
    const maxRun = isLrTb ? MAX_ATTEMPTS_FOR_LRTB_LAYOUT : 1;
    for (; this[$extra].attempt < maxRun; this[$extra].attempt++) {
      if (isLrTb && this[$extra].attempt === MAX_ATTEMPTS_FOR_LRTB_LAYOUT - 1) {
        this[$extra].numberInLine = 0;
      }
      const result = this[$childrenToHTML]({
        filter,
        include: true
      });
      if (result.success) {
        break;
      }
      if (result.isBreak()) {
        this[$popPara]();
        return result;
      }
      if (isLrTb && this[$extra].attempt === 0 && this[$extra].numberInLine === 0 && !this[$getTemplateRoot]()[$extra].noLayoutFailure) {
        this[$extra].attempt = maxRun;
        break;
      }
    }
    this[$popPara]();
    if (!isSplittable) {
      unsetFirstUnsplittable(this);
    }
    if (this[$extra].attempt === maxRun) {
      if (!isSplittable) {
        delete this[$extra];
      }
      return HTMLResult.FAILURE;
    }
    let marginH = 0;
    let marginV = 0;
    if (this.margin) {
      marginH = this.margin.leftInset + this.margin.rightInset;
      marginV = this.margin.topInset + this.margin.bottomInset;
    }
    const width = Math.max(this[$extra].width + marginH, this.w || 0);
    const height = Math.max(this[$extra].height + marginV, this.h || 0);
    const bbox = [this.x, this.y, width, height];
    if (this.w === "") {
      style.width = measureToString(width);
    }
    if (this.h === "") {
      style.height = measureToString(height);
    }
    const html = {
      name: "div",
      attributes,
      children
    };
    applyAssist(this, attributes);
    delete this[$extra];
    return HTMLResult.success(createWrapper(this, html), bbox);
  }
}
class Execute extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "execute");
    this.connection = attributes.connection || "";
    this.executeType = getStringOption(attributes.executeType, ["import", "remerge"]);
    this.id = attributes.id || "";
    this.runAt = getStringOption(attributes.runAt, ["client", "both", "server"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class Extras extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "extras", true);
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.boolean = new XFAObjectArray();
    this.date = new XFAObjectArray();
    this.dateTime = new XFAObjectArray();
    this.decimal = new XFAObjectArray();
    this.exData = new XFAObjectArray();
    this.extras = new XFAObjectArray();
    this.float = new XFAObjectArray();
    this.image = new XFAObjectArray();
    this.integer = new XFAObjectArray();
    this.text = new XFAObjectArray();
    this.time = new XFAObjectArray();
  }
}
class Field extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "field", true);
    this.access = getStringOption(attributes.access, ["open", "nonInteractive", "protected", "readOnly"]);
    this.accessKey = attributes.accessKey || "";
    this.anchorType = getStringOption(attributes.anchorType, ["topLeft", "bottomCenter", "bottomLeft", "bottomRight", "middleCenter", "middleLeft", "middleRight", "topCenter", "topRight"]);
    this.colSpan = getInteger({
      data: attributes.colSpan,
      defaultValue: 1,
      validate: n => n >= 1 || n === -1
    });
    this.h = attributes.h ? getMeasurement(attributes.h) : "";
    this.hAlign = getStringOption(attributes.hAlign, ["left", "center", "justify", "justifyAll", "radix", "right"]);
    this.id = attributes.id || "";
    this.locale = attributes.locale || "";
    this.maxH = getMeasurement(attributes.maxH, "0pt");
    this.maxW = getMeasurement(attributes.maxW, "0pt");
    this.minH = getMeasurement(attributes.minH, "0pt");
    this.minW = getMeasurement(attributes.minW, "0pt");
    this.name = attributes.name || "";
    this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
    this.relevant = getRelevant(attributes.relevant);
    this.rotate = getInteger({
      data: attributes.rotate,
      defaultValue: 0,
      validate: x => x % 90 === 0
    });
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.w = attributes.w ? getMeasurement(attributes.w) : "";
    this.x = getMeasurement(attributes.x, "0pt");
    this.y = getMeasurement(attributes.y, "0pt");
    this.assist = null;
    this.bind = null;
    this.border = null;
    this.calculate = null;
    this.caption = null;
    this.desc = null;
    this.extras = null;
    this.font = null;
    this.format = null;
    this.items = new XFAObjectArray(2);
    this.keep = null;
    this.margin = null;
    this.para = null;
    this.traversal = null;
    this.ui = null;
    this.validate = null;
    this.value = null;
    this.bindItems = new XFAObjectArray();
    this.connect = new XFAObjectArray();
    this.event = new XFAObjectArray();
    this.setProperty = new XFAObjectArray();
  }
  [$isBindable]() {
    return true;
  }
  [$setValue](value) {
    _setValue(this, value);
  }
  [$toHTML](availableSpace) {
    setTabIndex(this);
    if (!this.ui) {
      this.ui = new Ui({});
      this.ui[$globalData] = this[$globalData];
      this[$appendChild](this.ui);
      let node;
      switch (this.items.children.length) {
        case 0:
          node = new TextEdit({});
          this.ui.textEdit = node;
          break;
        case 1:
          node = new CheckButton({});
          this.ui.checkButton = node;
          break;
        case 2:
          node = new ChoiceList({});
          this.ui.choiceList = node;
          break;
      }
      this.ui[$appendChild](node);
    }
    if (!this.ui || this.presence === "hidden" || this.presence === "inactive" || this.h === 0 || this.w === 0) {
      return HTMLResult.EMPTY;
    }
    if (this.caption) {
      delete this.caption[$extra];
    }
    this[$pushPara]();
    const caption = this.caption ? this.caption[$toHTML](availableSpace).html : null;
    const savedW = this.w;
    const savedH = this.h;
    let marginH = 0;
    let marginV = 0;
    if (this.margin) {
      marginH = this.margin.leftInset + this.margin.rightInset;
      marginV = this.margin.topInset + this.margin.bottomInset;
    }
    let borderDims = null;
    if (this.w === "" || this.h === "") {
      let width = null;
      let height = null;
      let uiW = 0;
      let uiH = 0;
      if (this.ui.checkButton) {
        uiW = uiH = this.ui.checkButton.size;
      } else {
        const {
          w,
          h
        } = layoutNode(this, availableSpace);
        if (w !== null) {
          uiW = w;
          uiH = h;
        } else {
          uiH = fonts_getMetrics(this.font, true).lineNoGap;
        }
      }
      borderDims = getBorderDims(this.ui[$getExtra]());
      uiW += borderDims.w;
      uiH += borderDims.h;
      if (this.caption) {
        const {
          w,
          h,
          isBroken
        } = this.caption[$getExtra](availableSpace);
        if (isBroken && this[$getSubformParent]()[$isThereMoreWidth]()) {
          this[$popPara]();
          return HTMLResult.FAILURE;
        }
        width = w;
        height = h;
        switch (this.caption.placement) {
          case "left":
          case "right":
          case "inline":
            width += uiW;
            break;
          case "top":
          case "bottom":
            height += uiH;
            break;
        }
      } else {
        width = uiW;
        height = uiH;
      }
      if (width && this.w === "") {
        width += marginH;
        this.w = Math.min(this.maxW <= 0 ? Infinity : this.maxW, this.minW + 1 < width ? width : this.minW);
      }
      if (height && this.h === "") {
        height += marginV;
        this.h = Math.min(this.maxH <= 0 ? Infinity : this.maxH, this.minH + 1 < height ? height : this.minH);
      }
    }
    this[$popPara]();
    fixDimensions(this);
    setFirstUnsplittable(this);
    if (!checkDimensions(this, availableSpace)) {
      this.w = savedW;
      this.h = savedH;
      this[$popPara]();
      return HTMLResult.FAILURE;
    }
    unsetFirstUnsplittable(this);
    const style = toStyle(this, "font", "dimensions", "position", "rotate", "anchorType", "presence", "margin", "hAlign");
    setMinMaxDimensions(this, style);
    const classNames = ["xfaField"];
    if (this.font) {
      classNames.push("xfaFont");
    }
    if (isPrintOnly(this)) {
      classNames.push("xfaPrintOnly");
    }
    const attributes = {
      style,
      id: this[$uid],
      class: classNames
    };
    if (style.margin) {
      style.padding = style.margin;
      delete style.margin;
    }
    setAccess(this, classNames);
    if (this.name) {
      attributes.xfaName = this.name;
    }
    const children = [];
    const html = {
      name: "div",
      attributes,
      children
    };
    applyAssist(this, attributes);
    const borderStyle = this.border ? this.border[$toStyle]() : null;
    const bbox = computeBbox(this, html, availableSpace);
    const ui = this.ui[$toHTML]().html;
    if (!ui) {
      Object.assign(style, borderStyle);
      return HTMLResult.success(createWrapper(this, html), bbox);
    }
    if (this[$tabIndex]) {
      if (ui.children?.[0]) {
        ui.children[0].attributes.tabindex = this[$tabIndex];
      } else {
        ui.attributes.tabindex = this[$tabIndex];
      }
    }
    if (!ui.attributes.style) {
      ui.attributes.style = Object.create(null);
    }
    let aElement = null;
    if (this.ui.button) {
      if (ui.children.length === 1) {
        [aElement] = ui.children.splice(0, 1);
      }
      Object.assign(ui.attributes.style, borderStyle);
    } else {
      Object.assign(style, borderStyle);
    }
    children.push(ui);
    if (this.value) {
      if (this.ui.imageEdit) {
        ui.children.push(this.value[$toHTML]().html);
      } else if (!this.ui.button) {
        let value = "";
        if (this.value.exData) {
          value = this.value.exData[$text]();
        } else if (this.value.text) {
          value = this.value.text[$getExtra]();
        } else {
          const htmlValue = this.value[$toHTML]().html;
          if (htmlValue !== null) {
            value = htmlValue.children[0].value;
          }
        }
        if (this.ui.textEdit && this.value.text?.maxChars) {
          ui.children[0].attributes.maxLength = this.value.text.maxChars;
        }
        if (value) {
          if (this.ui.numericEdit) {
            value = parseFloat(value);
            value = isNaN(value) ? "" : value.toString();
          }
          if (ui.children[0].name === "textarea") {
            ui.children[0].attributes.textContent = value;
          } else {
            ui.children[0].attributes.value = value;
          }
        }
      }
    }
    if (!this.ui.imageEdit && ui.children?.[0] && this.h) {
      borderDims = borderDims || getBorderDims(this.ui[$getExtra]());
      let captionHeight = 0;
      if (this.caption && ["top", "bottom"].includes(this.caption.placement)) {
        captionHeight = this.caption.reserve;
        if (captionHeight <= 0) {
          captionHeight = this.caption[$getExtra](availableSpace).h;
        }
        const inputHeight = this.h - captionHeight - marginV - borderDims.h;
        ui.children[0].attributes.style.height = measureToString(inputHeight);
      } else {
        ui.children[0].attributes.style.height = "100%";
      }
    }
    if (aElement) {
      ui.children.push(aElement);
    }
    if (!caption) {
      if (ui.attributes.class) {
        ui.attributes.class.push("xfaLeft");
      }
      this.w = savedW;
      this.h = savedH;
      return HTMLResult.success(createWrapper(this, html), bbox);
    }
    if (this.ui.button) {
      if (style.padding) {
        delete style.padding;
      }
      if (caption.name === "div") {
        caption.name = "span";
      }
      ui.children.push(caption);
      return HTMLResult.success(html, bbox);
    } else if (this.ui.checkButton) {
      caption.attributes.class[0] = "xfaCaptionForCheckButton";
    }
    if (!ui.attributes.class) {
      ui.attributes.class = [];
    }
    ui.children.splice(0, 0, caption);
    switch (this.caption.placement) {
      case "left":
        ui.attributes.class.push("xfaLeft");
        break;
      case "right":
        ui.attributes.class.push("xfaRight");
        break;
      case "top":
        ui.attributes.class.push("xfaTop");
        break;
      case "bottom":
        ui.attributes.class.push("xfaBottom");
        break;
      case "inline":
        ui.attributes.class.push("xfaLeft");
        break;
    }
    this.w = savedW;
    this.h = savedH;
    return HTMLResult.success(createWrapper(this, html), bbox);
  }
}
class Fill extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "fill", true);
    this.id = attributes.id || "";
    this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.color = null;
    this.extras = null;
    this.linear = null;
    this.pattern = null;
    this.radial = null;
    this.solid = null;
    this.stipple = null;
  }
  [$toStyle]() {
    const parent = this[$getParent]();
    const grandpa = parent[$getParent]();
    const ggrandpa = grandpa[$getParent]();
    const style = Object.create(null);
    let propName = "color";
    let altPropName = propName;
    if (parent instanceof Border) {
      propName = "background-color";
      altPropName = "background";
      if (ggrandpa instanceof Ui) {
        style.backgroundColor = "white";
      }
    }
    if (parent instanceof Rectangle || parent instanceof Arc) {
      propName = altPropName = "fill";
      style.fill = "white";
    }
    for (const name of Object.getOwnPropertyNames(this)) {
      if (name === "extras" || name === "color") {
        continue;
      }
      const obj = this[name];
      if (!(obj instanceof XFAObject)) {
        continue;
      }
      const color = obj[$toStyle](this.color);
      if (color) {
        style[color.startsWith("#") ? propName : altPropName] = color;
      }
      return style;
    }
    if (this.color?.value) {
      const color = this.color[$toStyle]();
      style[color.startsWith("#") ? propName : altPropName] = color;
    }
    return style;
  }
}
class Filter extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "filter", true);
    this.addRevocationInfo = getStringOption(attributes.addRevocationInfo, ["", "required", "optional", "none"]);
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.version = getInteger({
      data: this.version,
      defaultValue: 5,
      validate: x => x >= 1 && x <= 5
    });
    this.appearanceFilter = null;
    this.certificates = null;
    this.digestMethods = null;
    this.encodings = null;
    this.encryptionMethods = null;
    this.handler = null;
    this.lockDocument = null;
    this.mdp = null;
    this.reasons = null;
    this.timeStamp = null;
  }
}
class Float extends ContentObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "float");
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
  [$finalize]() {
    const number = parseFloat(this[$content].trim());
    this[$content] = isNaN(number) ? null : number;
  }
  [$toHTML](availableSpace) {
    return valueToHtml(this[$content] !== null ? this[$content].toString() : "");
  }
}
class template_Font extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "font", true);
    this.baselineShift = getMeasurement(attributes.baselineShift);
    this.fontHorizontalScale = getFloat({
      data: attributes.fontHorizontalScale,
      defaultValue: 100,
      validate: x => x >= 0
    });
    this.fontVerticalScale = getFloat({
      data: attributes.fontVerticalScale,
      defaultValue: 100,
      validate: x => x >= 0
    });
    this.id = attributes.id || "";
    this.kerningMode = getStringOption(attributes.kerningMode, ["none", "pair"]);
    this.letterSpacing = getMeasurement(attributes.letterSpacing, "0");
    this.lineThrough = getInteger({
      data: attributes.lineThrough,
      defaultValue: 0,
      validate: x => x === 1 || x === 2
    });
    this.lineThroughPeriod = getStringOption(attributes.lineThroughPeriod, ["all", "word"]);
    this.overline = getInteger({
      data: attributes.overline,
      defaultValue: 0,
      validate: x => x === 1 || x === 2
    });
    this.overlinePeriod = getStringOption(attributes.overlinePeriod, ["all", "word"]);
    this.posture = getStringOption(attributes.posture, ["normal", "italic"]);
    this.size = getMeasurement(attributes.size, "10pt");
    this.typeface = attributes.typeface || "Courier";
    this.underline = getInteger({
      data: attributes.underline,
      defaultValue: 0,
      validate: x => x === 1 || x === 2
    });
    this.underlinePeriod = getStringOption(attributes.underlinePeriod, ["all", "word"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.weight = getStringOption(attributes.weight, ["normal", "bold"]);
    this.extras = null;
    this.fill = null;
  }
  [$clean](builder) {
    super[$clean](builder);
    this[$globalData].usedTypefaces.add(this.typeface);
  }
  [$toStyle]() {
    const style = toStyle(this, "fill");
    const color = style.color;
    if (color) {
      if (color === "#000000") {
        delete style.color;
      } else if (!color.startsWith("#")) {
        style.background = color;
        style.backgroundClip = "text";
        style.color = "transparent";
      }
    }
    if (this.baselineShift) {
      style.verticalAlign = measureToString(this.baselineShift);
    }
    style.fontKerning = this.kerningMode === "none" ? "none" : "normal";
    style.letterSpacing = measureToString(this.letterSpacing);
    if (this.lineThrough !== 0) {
      style.textDecoration = "line-through";
      if (this.lineThrough === 2) {
        style.textDecorationStyle = "double";
      }
    }
    if (this.overline !== 0) {
      style.textDecoration = "overline";
      if (this.overline === 2) {
        style.textDecorationStyle = "double";
      }
    }
    style.fontStyle = this.posture;
    style.fontSize = measureToString(0.99 * this.size);
    setFontFamily(this, this, this[$globalData].fontFinder, style);
    if (this.underline !== 0) {
      style.textDecoration = "underline";
      if (this.underline === 2) {
        style.textDecorationStyle = "double";
      }
    }
    style.fontWeight = this.weight;
    return style;
  }
}
class Format extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "format", true);
    this.id = attributes.id || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.extras = null;
    this.picture = null;
  }
}
class Handler extends StringObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "handler");
    this.id = attributes.id || "";
    this.type = getStringOption(attributes.type, ["optional", "required"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class Hyphenation extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "hyphenation");
    this.excludeAllCaps = getInteger({
      data: attributes.excludeAllCaps,
      defaultValue: 0,
      validate: x => x === 1
    });
    this.excludeInitialCap = getInteger({
      data: attributes.excludeInitialCap,
      defaultValue: 0,
      validate: x => x === 1
    });
    this.hyphenate = getInteger({
      data: attributes.hyphenate,
      defaultValue: 0,
      validate: x => x === 1
    });
    this.id = attributes.id || "";
    this.pushCharacterCount = getInteger({
      data: attributes.pushCharacterCount,
      defaultValue: 3,
      validate: x => x >= 0
    });
    this.remainCharacterCount = getInteger({
      data: attributes.remainCharacterCount,
      defaultValue: 3,
      validate: x => x >= 0
    });
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.wordCharacterCount = getInteger({
      data: attributes.wordCharacterCount,
      defaultValue: 7,
      validate: x => x >= 0
    });
  }
}
class Image extends StringObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "image");
    this.aspect = getStringOption(attributes.aspect, ["fit", "actual", "height", "none", "width"]);
    this.contentType = attributes.contentType || "";
    this.href = attributes.href || "";
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.transferEncoding = getStringOption(attributes.transferEncoding, ["base64", "none", "package"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
  [$toHTML]() {
    if (this.contentType && !MIMES.has(this.contentType.toLowerCase())) {
      return HTMLResult.EMPTY;
    }
    let buffer = this[$globalData].images && this[$globalData].images.get(this.href);
    if (!buffer && (this.href || !this[$content])) {
      return HTMLResult.EMPTY;
    }
    if (!buffer && this.transferEncoding === "base64") {
      buffer = stringToBytes(atob(this[$content]));
    }
    if (!buffer) {
      return HTMLResult.EMPTY;
    }
    if (!this.contentType) {
      for (const [header, type] of IMAGES_HEADERS) {
        if (buffer.length > header.length && header.every((x, i) => x === buffer[i])) {
          this.contentType = type;
          break;
        }
      }
      if (!this.contentType) {
        return HTMLResult.EMPTY;
      }
    }
    const blob = new Blob([buffer], {
      type: this.contentType
    });
    let style;
    switch (this.aspect) {
      case "fit":
      case "actual":
        break;
      case "height":
        style = {
          height: "100%",
          objectFit: "fill"
        };
        break;
      case "none":
        style = {
          width: "100%",
          height: "100%",
          objectFit: "fill"
        };
        break;
      case "width":
        style = {
          width: "100%",
          objectFit: "fill"
        };
        break;
    }
    const parent = this[$getParent]();
    return HTMLResult.success({
      name: "img",
      attributes: {
        class: ["xfaImage"],
        style,
        src: URL.createObjectURL(blob),
        alt: parent ? ariaLabel(parent[$getParent]()) : null
      }
    });
  }
}
class ImageEdit extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "imageEdit", true);
    this.data = getStringOption(attributes.data, ["link", "embed"]);
    this.id = attributes.id || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.border = null;
    this.extras = null;
    this.margin = null;
  }
  [$toHTML](availableSpace) {
    if (this.data === "embed") {
      return HTMLResult.success({
        name: "div",
        children: [],
        attributes: {}
      });
    }
    return HTMLResult.EMPTY;
  }
}
class Integer extends ContentObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "integer");
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
  [$finalize]() {
    const number = parseInt(this[$content].trim(), 10);
    this[$content] = isNaN(number) ? null : number;
  }
  [$toHTML](availableSpace) {
    return valueToHtml(this[$content] !== null ? this[$content].toString() : "");
  }
}
class Issuers extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "issuers", true);
    this.id = attributes.id || "";
    this.type = getStringOption(attributes.type, ["optional", "required"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.certificate = new XFAObjectArray();
  }
}
class Items extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "items", true);
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
    this.ref = attributes.ref || "";
    this.save = getInteger({
      data: attributes.save,
      defaultValue: 0,
      validate: x => x === 1
    });
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.boolean = new XFAObjectArray();
    this.date = new XFAObjectArray();
    this.dateTime = new XFAObjectArray();
    this.decimal = new XFAObjectArray();
    this.exData = new XFAObjectArray();
    this.float = new XFAObjectArray();
    this.image = new XFAObjectArray();
    this.integer = new XFAObjectArray();
    this.text = new XFAObjectArray();
    this.time = new XFAObjectArray();
  }
  [$toHTML]() {
    const output = [];
    for (const child of this[$getChildren]()) {
      output.push(child[$text]());
    }
    return HTMLResult.success(output);
  }
}
class Keep extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "keep", true);
    this.id = attributes.id || "";
    const options = ["none", "contentArea", "pageArea"];
    this.intact = getStringOption(attributes.intact, options);
    this.next = getStringOption(attributes.next, options);
    this.previous = getStringOption(attributes.previous, options);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.extras = null;
  }
}
class KeyUsage extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "keyUsage");
    const options = ["", "yes", "no"];
    this.crlSign = getStringOption(attributes.crlSign, options);
    this.dataEncipherment = getStringOption(attributes.dataEncipherment, options);
    this.decipherOnly = getStringOption(attributes.decipherOnly, options);
    this.digitalSignature = getStringOption(attributes.digitalSignature, options);
    this.encipherOnly = getStringOption(attributes.encipherOnly, options);
    this.id = attributes.id || "";
    this.keyAgreement = getStringOption(attributes.keyAgreement, options);
    this.keyCertSign = getStringOption(attributes.keyCertSign, options);
    this.keyEncipherment = getStringOption(attributes.keyEncipherment, options);
    this.nonRepudiation = getStringOption(attributes.nonRepudiation, options);
    this.type = getStringOption(attributes.type, ["optional", "required"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class Line extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "line", true);
    this.hand = getStringOption(attributes.hand, ["even", "left", "right"]);
    this.id = attributes.id || "";
    this.slope = getStringOption(attributes.slope, ["\\", "/"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.edge = null;
  }
  [$toHTML]() {
    const parent = this[$getParent]()[$getParent]();
    const edge = this.edge || new Edge({});
    const edgeStyle = edge[$toStyle]();
    const style = Object.create(null);
    const thickness = edge.presence === "visible" ? edge.thickness : 0;
    style.strokeWidth = measureToString(thickness);
    style.stroke = edgeStyle.color;
    let x1, y1, x2, y2;
    let width = "100%";
    let height = "100%";
    if (parent.w <= thickness) {
      [x1, y1, x2, y2] = ["50%", 0, "50%", "100%"];
      width = style.strokeWidth;
    } else if (parent.h <= thickness) {
      [x1, y1, x2, y2] = [0, "50%", "100%", "50%"];
      height = style.strokeWidth;
    } else if (this.slope === "\\") {
      [x1, y1, x2, y2] = [0, 0, "100%", "100%"];
    } else {
      [x1, y1, x2, y2] = [0, "100%", "100%", 0];
    }
    const line = {
      name: "line",
      attributes: {
        xmlns: SVG_NS,
        x1,
        y1,
        x2,
        y2,
        style
      }
    };
    const svg = {
      name: "svg",
      children: [line],
      attributes: {
        xmlns: SVG_NS,
        width,
        height,
        style: {
          overflow: "visible"
        }
      }
    };
    if (hasMargin(parent)) {
      return HTMLResult.success({
        name: "div",
        attributes: {
          style: {
            display: "inline",
            width: "100%",
            height: "100%"
          }
        },
        children: [svg]
      });
    }
    svg.attributes.style.position = "absolute";
    return HTMLResult.success(svg);
  }
}
class Linear extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "linear", true);
    this.id = attributes.id || "";
    this.type = getStringOption(attributes.type, ["toRight", "toBottom", "toLeft", "toTop"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.color = null;
    this.extras = null;
  }
  [$toStyle](startColor) {
    startColor = startColor ? startColor[$toStyle]() : "#FFFFFF";
    const transf = this.type.replace(/([RBLT])/, " $1").toLowerCase();
    const endColor = this.color ? this.color[$toStyle]() : "#000000";
    return `linear-gradient(${transf}, ${startColor}, ${endColor})`;
  }
}
class LockDocument extends ContentObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "lockDocument");
    this.id = attributes.id || "";
    this.type = getStringOption(attributes.type, ["optional", "required"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
  [$finalize]() {
    this[$content] = getStringOption(this[$content], ["auto", "0", "1"]);
  }
}
class Manifest extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "manifest", true);
    this.action = getStringOption(attributes.action, ["include", "all", "exclude"]);
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.extras = null;
    this.ref = new XFAObjectArray();
  }
}
class Margin extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "margin", true);
    this.bottomInset = getMeasurement(attributes.bottomInset, "0");
    this.id = attributes.id || "";
    this.leftInset = getMeasurement(attributes.leftInset, "0");
    this.rightInset = getMeasurement(attributes.rightInset, "0");
    this.topInset = getMeasurement(attributes.topInset, "0");
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.extras = null;
  }
  [$toStyle]() {
    return {
      margin: measureToString(this.topInset) + " " + measureToString(this.rightInset) + " " + measureToString(this.bottomInset) + " " + measureToString(this.leftInset)
    };
  }
}
class Mdp extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "mdp");
    this.id = attributes.id || "";
    this.permissions = getInteger({
      data: attributes.permissions,
      defaultValue: 2,
      validate: x => x === 1 || x === 3
    });
    this.signatureType = getStringOption(attributes.signatureType, ["filler", "author"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class Medium extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "medium");
    this.id = attributes.id || "";
    this.imagingBBox = getBBox(attributes.imagingBBox);
    this.long = getMeasurement(attributes.long);
    this.orientation = getStringOption(attributes.orientation, ["portrait", "landscape"]);
    this.short = getMeasurement(attributes.short);
    this.stock = attributes.stock || "";
    this.trayIn = getStringOption(attributes.trayIn, ["auto", "delegate", "pageFront"]);
    this.trayOut = getStringOption(attributes.trayOut, ["auto", "delegate"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class Message extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "message", true);
    this.id = attributes.id || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.text = new XFAObjectArray();
  }
}
class NumericEdit extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "numericEdit", true);
    this.hScrollPolicy = getStringOption(attributes.hScrollPolicy, ["auto", "off", "on"]);
    this.id = attributes.id || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.border = null;
    this.comb = null;
    this.extras = null;
    this.margin = null;
  }
  [$toHTML](availableSpace) {
    const style = toStyle(this, "border", "font", "margin");
    const field = this[$getParent]()[$getParent]();
    const html = {
      name: "input",
      attributes: {
        type: "text",
        fieldId: field[$uid],
        dataId: field[$data]?.[$uid] || field[$uid],
        class: ["xfaTextfield"],
        style,
        "aria-label": ariaLabel(field),
        "aria-required": false
      }
    };
    if (isRequired(field)) {
      html.attributes["aria-required"] = true;
      html.attributes.required = true;
    }
    return HTMLResult.success({
      name: "label",
      attributes: {
        class: ["xfaLabel"]
      },
      children: [html]
    });
  }
}
class Occur extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "occur", true);
    this.id = attributes.id || "";
    this.initial = attributes.initial !== "" ? getInteger({
      data: attributes.initial,
      defaultValue: "",
      validate: x => true
    }) : "";
    this.max = attributes.max !== "" ? getInteger({
      data: attributes.max,
      defaultValue: 1,
      validate: x => true
    }) : "";
    this.min = attributes.min !== "" ? getInteger({
      data: attributes.min,
      defaultValue: 1,
      validate: x => true
    }) : "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.extras = null;
  }
  [$clean]() {
    const parent = this[$getParent]();
    const originalMin = this.min;
    if (this.min === "") {
      this.min = parent instanceof PageArea || parent instanceof PageSet ? 0 : 1;
    }
    if (this.max === "") {
      if (originalMin === "") {
        this.max = parent instanceof PageArea || parent instanceof PageSet ? -1 : 1;
      } else {
        this.max = this.min;
      }
    }
    if (this.max !== -1 && this.max < this.min) {
      this.max = this.min;
    }
    if (this.initial === "") {
      this.initial = parent instanceof Template ? 1 : this.min;
    }
  }
}
class Oid extends StringObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "oid");
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class Oids extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "oids", true);
    this.id = attributes.id || "";
    this.type = getStringOption(attributes.type, ["optional", "required"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.oid = new XFAObjectArray();
  }
}
class Overflow extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "overflow");
    this.id = attributes.id || "";
    this.leader = attributes.leader || "";
    this.target = attributes.target || "";
    this.trailer = attributes.trailer || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
  [$getExtra]() {
    if (!this[$extra]) {
      const parent = this[$getParent]();
      const root = this[$getTemplateRoot]();
      const target = root[$searchNode](this.target, parent);
      const leader = root[$searchNode](this.leader, parent);
      const trailer = root[$searchNode](this.trailer, parent);
      this[$extra] = {
        target: target?.[0] || null,
        leader: leader?.[0] || null,
        trailer: trailer?.[0] || null,
        addLeader: false,
        addTrailer: false
      };
    }
    return this[$extra];
  }
}
class PageArea extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "pageArea", true);
    this.blankOrNotBlank = getStringOption(attributes.blankOrNotBlank, ["any", "blank", "notBlank"]);
    this.id = attributes.id || "";
    this.initialNumber = getInteger({
      data: attributes.initialNumber,
      defaultValue: 1,
      validate: x => true
    });
    this.name = attributes.name || "";
    this.numbered = getInteger({
      data: attributes.numbered,
      defaultValue: 1,
      validate: x => true
    });
    this.oddOrEven = getStringOption(attributes.oddOrEven, ["any", "even", "odd"]);
    this.pagePosition = getStringOption(attributes.pagePosition, ["any", "first", "last", "only", "rest"]);
    this.relevant = getRelevant(attributes.relevant);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.desc = null;
    this.extras = null;
    this.medium = null;
    this.occur = null;
    this.area = new XFAObjectArray();
    this.contentArea = new XFAObjectArray();
    this.draw = new XFAObjectArray();
    this.exclGroup = new XFAObjectArray();
    this.field = new XFAObjectArray();
    this.subform = new XFAObjectArray();
  }
  [$isUsable]() {
    if (!this[$extra]) {
      this[$extra] = {
        numberOfUse: 0
      };
      return true;
    }
    return !this.occur || this.occur.max === -1 || this[$extra].numberOfUse < this.occur.max;
  }
  [$cleanPage]() {
    delete this[$extra];
  }
  [$getNextPage]() {
    if (!this[$extra]) {
      this[$extra] = {
        numberOfUse: 0
      };
    }
    const parent = this[$getParent]();
    if (parent.relation === "orderedOccurrence") {
      if (this[$isUsable]()) {
        this[$extra].numberOfUse += 1;
        return this;
      }
    }
    return parent[$getNextPage]();
  }
  [$getAvailableSpace]() {
    return this[$extra].space || {
      width: 0,
      height: 0
    };
  }
  [$toHTML]() {
    if (!this[$extra]) {
      this[$extra] = {
        numberOfUse: 1
      };
    }
    const children = [];
    this[$extra].children = children;
    const style = Object.create(null);
    if (this.medium && this.medium.short && this.medium.long) {
      style.width = measureToString(this.medium.short);
      style.height = measureToString(this.medium.long);
      this[$extra].space = {
        width: this.medium.short,
        height: this.medium.long
      };
      if (this.medium.orientation === "landscape") {
        const x = style.width;
        style.width = style.height;
        style.height = x;
        this[$extra].space = {
          width: this.medium.long,
          height: this.medium.short
        };
      }
    } else {
      warn("XFA - No medium specified in pageArea: please file a bug.");
    }
    this[$childrenToHTML]({
      filter: new Set(["area", "draw", "field", "subform"]),
      include: true
    });
    this[$childrenToHTML]({
      filter: new Set(["contentArea"]),
      include: true
    });
    return HTMLResult.success({
      name: "div",
      children,
      attributes: {
        class: ["xfaPage"],
        id: this[$uid],
        style,
        xfaName: this.name
      }
    });
  }
}
class PageSet extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "pageSet", true);
    this.duplexImposition = getStringOption(attributes.duplexImposition, ["longEdge", "shortEdge"]);
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.relation = getStringOption(attributes.relation, ["orderedOccurrence", "duplexPaginated", "simplexPaginated"]);
    this.relevant = getRelevant(attributes.relevant);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.extras = null;
    this.occur = null;
    this.pageArea = new XFAObjectArray();
    this.pageSet = new XFAObjectArray();
  }
  [$cleanPage]() {
    for (const page of this.pageArea.children) {
      page[$cleanPage]();
    }
    for (const page of this.pageSet.children) {
      page[$cleanPage]();
    }
  }
  [$isUsable]() {
    return !this.occur || this.occur.max === -1 || this[$extra].numberOfUse < this.occur.max;
  }
  [$getNextPage]() {
    if (!this[$extra]) {
      this[$extra] = {
        numberOfUse: 1,
        pageIndex: -1,
        pageSetIndex: -1
      };
    }
    if (this.relation === "orderedOccurrence") {
      if (this[$extra].pageIndex + 1 < this.pageArea.children.length) {
        this[$extra].pageIndex += 1;
        const pageArea = this.pageArea.children[this[$extra].pageIndex];
        return pageArea[$getNextPage]();
      }
      if (this[$extra].pageSetIndex + 1 < this.pageSet.children.length) {
        this[$extra].pageSetIndex += 1;
        return this.pageSet.children[this[$extra].pageSetIndex][$getNextPage]();
      }
      if (this[$isUsable]()) {
        this[$extra].numberOfUse += 1;
        this[$extra].pageIndex = -1;
        this[$extra].pageSetIndex = -1;
        return this[$getNextPage]();
      }
      const parent = this[$getParent]();
      if (parent instanceof PageSet) {
        return parent[$getNextPage]();
      }
      this[$cleanPage]();
      return this[$getNextPage]();
    }
    const pageNumber = this[$getTemplateRoot]()[$extra].pageNumber;
    const parity = pageNumber % 2 === 0 ? "even" : "odd";
    const position = pageNumber === 0 ? "first" : "rest";
    let page = this.pageArea.children.find(p => p.oddOrEven === parity && p.pagePosition === position);
    if (page) {
      return page;
    }
    page = this.pageArea.children.find(p => p.oddOrEven === "any" && p.pagePosition === position);
    if (page) {
      return page;
    }
    page = this.pageArea.children.find(p => p.oddOrEven === "any" && p.pagePosition === "any");
    if (page) {
      return page;
    }
    return this.pageArea.children[0];
  }
}
class Para extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "para", true);
    this.hAlign = getStringOption(attributes.hAlign, ["left", "center", "justify", "justifyAll", "radix", "right"]);
    this.id = attributes.id || "";
    this.lineHeight = attributes.lineHeight ? getMeasurement(attributes.lineHeight, "0pt") : "";
    this.marginLeft = attributes.marginLeft ? getMeasurement(attributes.marginLeft, "0pt") : "";
    this.marginRight = attributes.marginRight ? getMeasurement(attributes.marginRight, "0pt") : "";
    this.orphans = getInteger({
      data: attributes.orphans,
      defaultValue: 0,
      validate: x => x >= 0
    });
    this.preserve = attributes.preserve || "";
    this.radixOffset = attributes.radixOffset ? getMeasurement(attributes.radixOffset, "0pt") : "";
    this.spaceAbove = attributes.spaceAbove ? getMeasurement(attributes.spaceAbove, "0pt") : "";
    this.spaceBelow = attributes.spaceBelow ? getMeasurement(attributes.spaceBelow, "0pt") : "";
    this.tabDefault = attributes.tabDefault ? getMeasurement(this.tabDefault) : "";
    this.tabStops = (attributes.tabStops || "").trim().split(/\s+/).map((x, i) => i % 2 === 1 ? getMeasurement(x) : x);
    this.textIndent = attributes.textIndent ? getMeasurement(attributes.textIndent, "0pt") : "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.vAlign = getStringOption(attributes.vAlign, ["top", "bottom", "middle"]);
    this.widows = getInteger({
      data: attributes.widows,
      defaultValue: 0,
      validate: x => x >= 0
    });
    this.hyphenation = null;
  }
  [$toStyle]() {
    const style = toStyle(this, "hAlign");
    if (this.marginLeft !== "") {
      style.paddingLeft = measureToString(this.marginLeft);
    }
    if (this.marginRight !== "") {
      style.paddingight = measureToString(this.marginRight);
    }
    if (this.spaceAbove !== "") {
      style.paddingTop = measureToString(this.spaceAbove);
    }
    if (this.spaceBelow !== "") {
      style.paddingBottom = measureToString(this.spaceBelow);
    }
    if (this.textIndent !== "") {
      style.textIndent = measureToString(this.textIndent);
      fixTextIndent(style);
    }
    if (this.lineHeight > 0) {
      style.lineHeight = measureToString(this.lineHeight);
    }
    if (this.tabDefault !== "") {
      style.tabSize = measureToString(this.tabDefault);
    }
    if (this.tabStops.length > 0) {}
    if (this.hyphenatation) {
      Object.assign(style, this.hyphenatation[$toStyle]());
    }
    return style;
  }
}
class PasswordEdit extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "passwordEdit", true);
    this.hScrollPolicy = getStringOption(attributes.hScrollPolicy, ["auto", "off", "on"]);
    this.id = attributes.id || "";
    this.passwordChar = attributes.passwordChar || "*";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.border = null;
    this.extras = null;
    this.margin = null;
  }
}
class template_Pattern extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "pattern", true);
    this.id = attributes.id || "";
    this.type = getStringOption(attributes.type, ["crossHatch", "crossDiagonal", "diagonalLeft", "diagonalRight", "horizontal", "vertical"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.color = null;
    this.extras = null;
  }
  [$toStyle](startColor) {
    startColor = startColor ? startColor[$toStyle]() : "#FFFFFF";
    const endColor = this.color ? this.color[$toStyle]() : "#000000";
    const width = 5;
    const cmd = "repeating-linear-gradient";
    const colors = `${startColor},${startColor} ${width}px,${endColor} ${width}px,${endColor} ${2 * width}px`;
    switch (this.type) {
      case "crossHatch":
        return `${cmd}(to top,${colors}) ${cmd}(to right,${colors})`;
      case "crossDiagonal":
        return `${cmd}(45deg,${colors}) ${cmd}(-45deg,${colors})`;
      case "diagonalLeft":
        return `${cmd}(45deg,${colors})`;
      case "diagonalRight":
        return `${cmd}(-45deg,${colors})`;
      case "horizontal":
        return `${cmd}(to top,${colors})`;
      case "vertical":
        return `${cmd}(to right,${colors})`;
    }
    return "";
  }
}
class Picture extends StringObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "picture");
    this.id = attributes.id || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class Proto extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "proto", true);
    this.appearanceFilter = new XFAObjectArray();
    this.arc = new XFAObjectArray();
    this.area = new XFAObjectArray();
    this.assist = new XFAObjectArray();
    this.barcode = new XFAObjectArray();
    this.bindItems = new XFAObjectArray();
    this.bookend = new XFAObjectArray();
    this.boolean = new XFAObjectArray();
    this.border = new XFAObjectArray();
    this.break = new XFAObjectArray();
    this.breakAfter = new XFAObjectArray();
    this.breakBefore = new XFAObjectArray();
    this.button = new XFAObjectArray();
    this.calculate = new XFAObjectArray();
    this.caption = new XFAObjectArray();
    this.certificate = new XFAObjectArray();
    this.certificates = new XFAObjectArray();
    this.checkButton = new XFAObjectArray();
    this.choiceList = new XFAObjectArray();
    this.color = new XFAObjectArray();
    this.comb = new XFAObjectArray();
    this.connect = new XFAObjectArray();
    this.contentArea = new XFAObjectArray();
    this.corner = new XFAObjectArray();
    this.date = new XFAObjectArray();
    this.dateTime = new XFAObjectArray();
    this.dateTimeEdit = new XFAObjectArray();
    this.decimal = new XFAObjectArray();
    this.defaultUi = new XFAObjectArray();
    this.desc = new XFAObjectArray();
    this.digestMethod = new XFAObjectArray();
    this.digestMethods = new XFAObjectArray();
    this.draw = new XFAObjectArray();
    this.edge = new XFAObjectArray();
    this.encoding = new XFAObjectArray();
    this.encodings = new XFAObjectArray();
    this.encrypt = new XFAObjectArray();
    this.encryptData = new XFAObjectArray();
    this.encryption = new XFAObjectArray();
    this.encryptionMethod = new XFAObjectArray();
    this.encryptionMethods = new XFAObjectArray();
    this.event = new XFAObjectArray();
    this.exData = new XFAObjectArray();
    this.exObject = new XFAObjectArray();
    this.exclGroup = new XFAObjectArray();
    this.execute = new XFAObjectArray();
    this.extras = new XFAObjectArray();
    this.field = new XFAObjectArray();
    this.fill = new XFAObjectArray();
    this.filter = new XFAObjectArray();
    this.float = new XFAObjectArray();
    this.font = new XFAObjectArray();
    this.format = new XFAObjectArray();
    this.handler = new XFAObjectArray();
    this.hyphenation = new XFAObjectArray();
    this.image = new XFAObjectArray();
    this.imageEdit = new XFAObjectArray();
    this.integer = new XFAObjectArray();
    this.issuers = new XFAObjectArray();
    this.items = new XFAObjectArray();
    this.keep = new XFAObjectArray();
    this.keyUsage = new XFAObjectArray();
    this.line = new XFAObjectArray();
    this.linear = new XFAObjectArray();
    this.lockDocument = new XFAObjectArray();
    this.manifest = new XFAObjectArray();
    this.margin = new XFAObjectArray();
    this.mdp = new XFAObjectArray();
    this.medium = new XFAObjectArray();
    this.message = new XFAObjectArray();
    this.numericEdit = new XFAObjectArray();
    this.occur = new XFAObjectArray();
    this.oid = new XFAObjectArray();
    this.oids = new XFAObjectArray();
    this.overflow = new XFAObjectArray();
    this.pageArea = new XFAObjectArray();
    this.pageSet = new XFAObjectArray();
    this.para = new XFAObjectArray();
    this.passwordEdit = new XFAObjectArray();
    this.pattern = new XFAObjectArray();
    this.picture = new XFAObjectArray();
    this.radial = new XFAObjectArray();
    this.reason = new XFAObjectArray();
    this.reasons = new XFAObjectArray();
    this.rectangle = new XFAObjectArray();
    this.ref = new XFAObjectArray();
    this.script = new XFAObjectArray();
    this.setProperty = new XFAObjectArray();
    this.signData = new XFAObjectArray();
    this.signature = new XFAObjectArray();
    this.signing = new XFAObjectArray();
    this.solid = new XFAObjectArray();
    this.speak = new XFAObjectArray();
    this.stipple = new XFAObjectArray();
    this.subform = new XFAObjectArray();
    this.subformSet = new XFAObjectArray();
    this.subjectDN = new XFAObjectArray();
    this.subjectDNs = new XFAObjectArray();
    this.submit = new XFAObjectArray();
    this.text = new XFAObjectArray();
    this.textEdit = new XFAObjectArray();
    this.time = new XFAObjectArray();
    this.timeStamp = new XFAObjectArray();
    this.toolTip = new XFAObjectArray();
    this.traversal = new XFAObjectArray();
    this.traverse = new XFAObjectArray();
    this.ui = new XFAObjectArray();
    this.validate = new XFAObjectArray();
    this.value = new XFAObjectArray();
    this.variables = new XFAObjectArray();
  }
}
class Radial extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "radial", true);
    this.id = attributes.id || "";
    this.type = getStringOption(attributes.type, ["toEdge", "toCenter"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.color = null;
    this.extras = null;
  }
  [$toStyle](startColor) {
    startColor = startColor ? startColor[$toStyle]() : "#FFFFFF";
    const endColor = this.color ? this.color[$toStyle]() : "#000000";
    const colors = this.type === "toEdge" ? `${startColor},${endColor}` : `${endColor},${startColor}`;
    return `radial-gradient(circle at center, ${colors})`;
  }
}
class Reason extends StringObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "reason");
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class Reasons extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "reasons", true);
    this.id = attributes.id || "";
    this.type = getStringOption(attributes.type, ["optional", "required"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.reason = new XFAObjectArray();
  }
}
class Rectangle extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "rectangle", true);
    this.hand = getStringOption(attributes.hand, ["even", "left", "right"]);
    this.id = attributes.id || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.corner = new XFAObjectArray(4);
    this.edge = new XFAObjectArray(4);
    this.fill = null;
  }
  [$toHTML]() {
    const edge = this.edge.children.length ? this.edge.children[0] : new Edge({});
    const edgeStyle = edge[$toStyle]();
    const style = Object.create(null);
    if (this.fill?.presence === "visible") {
      Object.assign(style, this.fill[$toStyle]());
    } else {
      style.fill = "transparent";
    }
    style.strokeWidth = measureToString(edge.presence === "visible" ? edge.thickness : 0);
    style.stroke = edgeStyle.color;
    const corner = this.corner.children.length ? this.corner.children[0] : new Corner({});
    const cornerStyle = corner[$toStyle]();
    const rect = {
      name: "rect",
      attributes: {
        xmlns: SVG_NS,
        width: "100%",
        height: "100%",
        x: 0,
        y: 0,
        rx: cornerStyle.radius,
        ry: cornerStyle.radius,
        style
      }
    };
    const svg = {
      name: "svg",
      children: [rect],
      attributes: {
        xmlns: SVG_NS,
        style: {
          overflow: "visible"
        },
        width: "100%",
        height: "100%"
      }
    };
    const parent = this[$getParent]()[$getParent]();
    if (hasMargin(parent)) {
      return HTMLResult.success({
        name: "div",
        attributes: {
          style: {
            display: "inline",
            width: "100%",
            height: "100%"
          }
        },
        children: [svg]
      });
    }
    svg.attributes.style.position = "absolute";
    return HTMLResult.success(svg);
  }
}
class RefElement extends StringObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "ref");
    this.id = attributes.id || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class Script extends StringObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "script");
    this.binding = attributes.binding || "";
    this.contentType = attributes.contentType || "";
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.runAt = getStringOption(attributes.runAt, ["client", "both", "server"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class SetProperty extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "setProperty");
    this.connection = attributes.connection || "";
    this.ref = attributes.ref || "";
    this.target = attributes.target || "";
  }
}
class SignData extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "signData", true);
    this.id = attributes.id || "";
    this.operation = getStringOption(attributes.operation, ["sign", "clear", "verify"]);
    this.ref = attributes.ref || "";
    this.target = attributes.target || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.filter = null;
    this.manifest = null;
  }
}
class Signature extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "signature", true);
    this.id = attributes.id || "";
    this.type = getStringOption(attributes.type, ["PDF1.3", "PDF1.6"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.border = null;
    this.extras = null;
    this.filter = null;
    this.manifest = null;
    this.margin = null;
  }
}
class Signing extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "signing", true);
    this.id = attributes.id || "";
    this.type = getStringOption(attributes.type, ["optional", "required"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.certificate = new XFAObjectArray();
  }
}
class Solid extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "solid", true);
    this.id = attributes.id || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.extras = null;
  }
  [$toStyle](startColor) {
    return startColor ? startColor[$toStyle]() : "#FFFFFF";
  }
}
class Speak extends StringObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "speak");
    this.disable = getInteger({
      data: attributes.disable,
      defaultValue: 0,
      validate: x => x === 1
    });
    this.id = attributes.id || "";
    this.priority = getStringOption(attributes.priority, ["custom", "caption", "name", "toolTip"]);
    this.rid = attributes.rid || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class Stipple extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "stipple", true);
    this.id = attributes.id || "";
    this.rate = getInteger({
      data: attributes.rate,
      defaultValue: 50,
      validate: x => x >= 0 && x <= 100
    });
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.color = null;
    this.extras = null;
  }
  [$toStyle](bgColor) {
    const alpha = this.rate / 100;
    return Util.makeHexColor(Math.round(bgColor.value.r * (1 - alpha) + this.value.r * alpha), Math.round(bgColor.value.g * (1 - alpha) + this.value.g * alpha), Math.round(bgColor.value.b * (1 - alpha) + this.value.b * alpha));
  }
}
class Subform extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "subform", true);
    this.access = getStringOption(attributes.access, ["open", "nonInteractive", "protected", "readOnly"]);
    this.allowMacro = getInteger({
      data: attributes.allowMacro,
      defaultValue: 0,
      validate: x => x === 1
    });
    this.anchorType = getStringOption(attributes.anchorType, ["topLeft", "bottomCenter", "bottomLeft", "bottomRight", "middleCenter", "middleLeft", "middleRight", "topCenter", "topRight"]);
    this.colSpan = getInteger({
      data: attributes.colSpan,
      defaultValue: 1,
      validate: n => n >= 1 || n === -1
    });
    this.columnWidths = (attributes.columnWidths || "").trim().split(/\s+/).map(x => x === "-1" ? -1 : getMeasurement(x));
    this.h = attributes.h ? getMeasurement(attributes.h) : "";
    this.hAlign = getStringOption(attributes.hAlign, ["left", "center", "justify", "justifyAll", "radix", "right"]);
    this.id = attributes.id || "";
    this.layout = getStringOption(attributes.layout, ["position", "lr-tb", "rl-row", "rl-tb", "row", "table", "tb"]);
    this.locale = attributes.locale || "";
    this.maxH = getMeasurement(attributes.maxH, "0pt");
    this.maxW = getMeasurement(attributes.maxW, "0pt");
    this.mergeMode = getStringOption(attributes.mergeMode, ["consumeData", "matchTemplate"]);
    this.minH = getMeasurement(attributes.minH, "0pt");
    this.minW = getMeasurement(attributes.minW, "0pt");
    this.name = attributes.name || "";
    this.presence = getStringOption(attributes.presence, ["visible", "hidden", "inactive", "invisible"]);
    this.relevant = getRelevant(attributes.relevant);
    this.restoreState = getStringOption(attributes.restoreState, ["manual", "auto"]);
    this.scope = getStringOption(attributes.scope, ["name", "none"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.w = attributes.w ? getMeasurement(attributes.w) : "";
    this.x = getMeasurement(attributes.x, "0pt");
    this.y = getMeasurement(attributes.y, "0pt");
    this.assist = null;
    this.bind = null;
    this.bookend = null;
    this.border = null;
    this.break = null;
    this.calculate = null;
    this.desc = null;
    this.extras = null;
    this.keep = null;
    this.margin = null;
    this.occur = null;
    this.overflow = null;
    this.pageSet = null;
    this.para = null;
    this.traversal = null;
    this.validate = null;
    this.variables = null;
    this.area = new XFAObjectArray();
    this.breakAfter = new XFAObjectArray();
    this.breakBefore = new XFAObjectArray();
    this.connect = new XFAObjectArray();
    this.draw = new XFAObjectArray();
    this.event = new XFAObjectArray();
    this.exObject = new XFAObjectArray();
    this.exclGroup = new XFAObjectArray();
    this.field = new XFAObjectArray();
    this.proto = new XFAObjectArray();
    this.setProperty = new XFAObjectArray();
    this.subform = new XFAObjectArray();
    this.subformSet = new XFAObjectArray();
  }
  [$getSubformParent]() {
    const parent = this[$getParent]();
    if (parent instanceof SubformSet) {
      return parent[$getSubformParent]();
    }
    return parent;
  }
  [$isBindable]() {
    return true;
  }
  [$isThereMoreWidth]() {
    return this.layout.endsWith("-tb") && this[$extra].attempt === 0 && this[$extra].numberInLine > 0 || this[$getParent]()[$isThereMoreWidth]();
  }
  *[$getContainedChildren]() {
    yield* getContainedChildren(this);
  }
  [$flushHTML]() {
    return flushHTML(this);
  }
  [$addHTML](html, bbox) {
    addHTML(this, html, bbox);
  }
  [$getAvailableSpace]() {
    return getAvailableSpace(this);
  }
  [$isSplittable]() {
    const parent = this[$getSubformParent]();
    if (!parent[$isSplittable]()) {
      return false;
    }
    if (this[$extra]._isSplittable !== undefined) {
      return this[$extra]._isSplittable;
    }
    if (this.layout === "position" || this.layout.includes("row")) {
      this[$extra]._isSplittable = false;
      return false;
    }
    if (this.keep && this.keep.intact !== "none") {
      this[$extra]._isSplittable = false;
      return false;
    }
    if (parent.layout?.endsWith("-tb") && parent[$extra].numberInLine !== 0) {
      return false;
    }
    this[$extra]._isSplittable = true;
    return true;
  }
  [$toHTML](availableSpace) {
    setTabIndex(this);
    if (this.break) {
      if (this.break.after !== "auto" || this.break.afterTarget !== "") {
        const node = new BreakAfter({
          targetType: this.break.after,
          target: this.break.afterTarget,
          startNew: this.break.startNew.toString()
        });
        node[$globalData] = this[$globalData];
        this[$appendChild](node);
        this.breakAfter.push(node);
      }
      if (this.break.before !== "auto" || this.break.beforeTarget !== "") {
        const node = new BreakBefore({
          targetType: this.break.before,
          target: this.break.beforeTarget,
          startNew: this.break.startNew.toString()
        });
        node[$globalData] = this[$globalData];
        this[$appendChild](node);
        this.breakBefore.push(node);
      }
      if (this.break.overflowTarget !== "") {
        const node = new Overflow({
          target: this.break.overflowTarget,
          leader: this.break.overflowLeader,
          trailer: this.break.overflowTrailer
        });
        node[$globalData] = this[$globalData];
        this[$appendChild](node);
        this.overflow.push(node);
      }
      this[$removeChild](this.break);
      this.break = null;
    }
    if (this.presence === "hidden" || this.presence === "inactive") {
      return HTMLResult.EMPTY;
    }
    if (this.breakBefore.children.length > 1 || this.breakAfter.children.length > 1) {
      warn("XFA - Several breakBefore or breakAfter in subforms: please file a bug.");
    }
    if (this.breakBefore.children.length >= 1) {
      const breakBefore = this.breakBefore.children[0];
      if (handleBreak(breakBefore)) {
        return HTMLResult.breakNode(breakBefore);
      }
    }
    if (this[$extra]?.afterBreakAfter) {
      return HTMLResult.EMPTY;
    }
    fixDimensions(this);
    const children = [];
    const attributes = {
      id: this[$uid],
      class: []
    };
    setAccess(this, attributes.class);
    if (!this[$extra]) {
      this[$extra] = Object.create(null);
    }
    Object.assign(this[$extra], {
      children,
      line: null,
      attributes,
      attempt: 0,
      numberInLine: 0,
      availableSpace: {
        width: Math.min(this.w || Infinity, availableSpace.width),
        height: Math.min(this.h || Infinity, availableSpace.height)
      },
      width: 0,
      height: 0,
      prevHeight: 0,
      currentWidth: 0
    });
    const root = this[$getTemplateRoot]();
    const savedNoLayoutFailure = root[$extra].noLayoutFailure;
    const isSplittable = this[$isSplittable]();
    if (!isSplittable) {
      setFirstUnsplittable(this);
    }
    if (!checkDimensions(this, availableSpace)) {
      return HTMLResult.FAILURE;
    }
    const filter = new Set(["area", "draw", "exclGroup", "field", "subform", "subformSet"]);
    if (this.layout.includes("row")) {
      const columnWidths = this[$getSubformParent]().columnWidths;
      if (Array.isArray(columnWidths) && columnWidths.length > 0) {
        this[$extra].columnWidths = columnWidths;
        this[$extra].currentColumn = 0;
      }
    }
    const style = toStyle(this, "anchorType", "dimensions", "position", "presence", "border", "margin", "hAlign");
    const classNames = ["xfaSubform"];
    const cl = layoutClass(this);
    if (cl) {
      classNames.push(cl);
    }
    attributes.style = style;
    attributes.class = classNames;
    if (this.name) {
      attributes.xfaName = this.name;
    }
    if (this.overflow) {
      const overflowExtra = this.overflow[$getExtra]();
      if (overflowExtra.addLeader) {
        overflowExtra.addLeader = false;
        handleOverflow(this, overflowExtra.leader, availableSpace);
      }
    }
    this[$pushPara]();
    const isLrTb = this.layout === "lr-tb" || this.layout === "rl-tb";
    const maxRun = isLrTb ? MAX_ATTEMPTS_FOR_LRTB_LAYOUT : 1;
    for (; this[$extra].attempt < maxRun; this[$extra].attempt++) {
      if (isLrTb && this[$extra].attempt === MAX_ATTEMPTS_FOR_LRTB_LAYOUT - 1) {
        this[$extra].numberInLine = 0;
      }
      const result = this[$childrenToHTML]({
        filter,
        include: true
      });
      if (result.success) {
        break;
      }
      if (result.isBreak()) {
        this[$popPara]();
        return result;
      }
      if (isLrTb && this[$extra].attempt === 0 && this[$extra].numberInLine === 0 && !root[$extra].noLayoutFailure) {
        this[$extra].attempt = maxRun;
        break;
      }
    }
    this[$popPara]();
    if (!isSplittable) {
      unsetFirstUnsplittable(this);
    }
    root[$extra].noLayoutFailure = savedNoLayoutFailure;
    if (this[$extra].attempt === maxRun) {
      if (this.overflow) {
        this[$getTemplateRoot]()[$extra].overflowNode = this.overflow;
      }
      if (!isSplittable) {
        delete this[$extra];
      }
      return HTMLResult.FAILURE;
    }
    if (this.overflow) {
      const overflowExtra = this.overflow[$getExtra]();
      if (overflowExtra.addTrailer) {
        overflowExtra.addTrailer = false;
        handleOverflow(this, overflowExtra.trailer, availableSpace);
      }
    }
    let marginH = 0;
    let marginV = 0;
    if (this.margin) {
      marginH = this.margin.leftInset + this.margin.rightInset;
      marginV = this.margin.topInset + this.margin.bottomInset;
    }
    const width = Math.max(this[$extra].width + marginH, this.w || 0);
    const height = Math.max(this[$extra].height + marginV, this.h || 0);
    const bbox = [this.x, this.y, width, height];
    if (this.w === "") {
      style.width = measureToString(width);
    }
    if (this.h === "") {
      style.height = measureToString(height);
    }
    if ((style.width === "0px" || style.height === "0px") && children.length === 0) {
      return HTMLResult.EMPTY;
    }
    const html = {
      name: "div",
      attributes,
      children
    };
    applyAssist(this, attributes);
    const result = HTMLResult.success(createWrapper(this, html), bbox);
    if (this.breakAfter.children.length >= 1) {
      const breakAfter = this.breakAfter.children[0];
      if (handleBreak(breakAfter)) {
        this[$extra].afterBreakAfter = result;
        return HTMLResult.breakNode(breakAfter);
      }
    }
    delete this[$extra];
    return result;
  }
}
class SubformSet extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "subformSet", true);
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.relation = getStringOption(attributes.relation, ["ordered", "choice", "unordered"]);
    this.relevant = getRelevant(attributes.relevant);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.bookend = null;
    this.break = null;
    this.desc = null;
    this.extras = null;
    this.occur = null;
    this.overflow = null;
    this.breakAfter = new XFAObjectArray();
    this.breakBefore = new XFAObjectArray();
    this.subform = new XFAObjectArray();
    this.subformSet = new XFAObjectArray();
  }
  *[$getContainedChildren]() {
    yield* getContainedChildren(this);
  }
  [$getSubformParent]() {
    let parent = this[$getParent]();
    while (!(parent instanceof Subform)) {
      parent = parent[$getParent]();
    }
    return parent;
  }
  [$isBindable]() {
    return true;
  }
}
class SubjectDN extends ContentObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "subjectDN");
    this.delimiter = attributes.delimiter || ",";
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
  [$finalize]() {
    this[$content] = new Map(this[$content].split(this.delimiter).map(kv => {
      kv = kv.split("=", 2);
      kv[0] = kv[0].trim();
      return kv;
    }));
  }
}
class SubjectDNs extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "subjectDNs", true);
    this.id = attributes.id || "";
    this.type = getStringOption(attributes.type, ["optional", "required"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.subjectDN = new XFAObjectArray();
  }
}
class Submit extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "submit", true);
    this.embedPDF = getInteger({
      data: attributes.embedPDF,
      defaultValue: 0,
      validate: x => x === 1
    });
    this.format = getStringOption(attributes.format, ["xdp", "formdata", "pdf", "urlencoded", "xfd", "xml"]);
    this.id = attributes.id || "";
    this.target = attributes.target || "";
    this.textEncoding = getKeyword({
      data: attributes.textEncoding ? attributes.textEncoding.toLowerCase() : "",
      defaultValue: "",
      validate: k => ["utf-8", "big-five", "fontspecific", "gbk", "gb-18030", "gb-2312", "ksc-5601", "none", "shift-jis", "ucs-2", "utf-16"].includes(k) || k.match(/iso-8859-\d{2}/)
    });
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.xdpContent = attributes.xdpContent || "";
    this.encrypt = null;
    this.encryptData = new XFAObjectArray();
    this.signData = new XFAObjectArray();
  }
}
class Template extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "template", true);
    this.baseProfile = getStringOption(attributes.baseProfile, ["full", "interactiveForms"]);
    this.extras = null;
    this.subform = new XFAObjectArray();
  }
  [$finalize]() {
    if (this.subform.children.length === 0) {
      warn("XFA - No subforms in template node.");
    }
    if (this.subform.children.length >= 2) {
      warn("XFA - Several subforms in template node: please file a bug.");
    }
    this[$tabIndex] = DEFAULT_TAB_INDEX;
  }
  [$isSplittable]() {
    return true;
  }
  [$searchNode](expr, container) {
    if (expr.startsWith("#")) {
      return [this[$ids].get(expr.slice(1))];
    }
    return searchNode(this, container, expr, true, true);
  }
  *[$toPages]() {
    if (!this.subform.children.length) {
      return HTMLResult.success({
        name: "div",
        children: []
      });
    }
    this[$extra] = {
      overflowNode: null,
      firstUnsplittable: null,
      currentContentArea: null,
      currentPageArea: null,
      noLayoutFailure: false,
      pageNumber: 1,
      pagePosition: "first",
      oddOrEven: "odd",
      blankOrNotBlank: "nonBlank",
      paraStack: []
    };
    const root = this.subform.children[0];
    root.pageSet[$cleanPage]();
    const pageAreas = root.pageSet.pageArea.children;
    const mainHtml = {
      name: "div",
      children: []
    };
    let pageArea = null;
    let breakBefore = null;
    let breakBeforeTarget = null;
    if (root.breakBefore.children.length >= 1) {
      breakBefore = root.breakBefore.children[0];
      breakBeforeTarget = breakBefore.target;
    } else if (root.subform.children.length >= 1 && root.subform.children[0].breakBefore.children.length >= 1) {
      breakBefore = root.subform.children[0].breakBefore.children[0];
      breakBeforeTarget = breakBefore.target;
    } else if (root.break?.beforeTarget) {
      breakBefore = root.break;
      breakBeforeTarget = breakBefore.beforeTarget;
    } else if (root.subform.children.length >= 1 && root.subform.children[0].break?.beforeTarget) {
      breakBefore = root.subform.children[0].break;
      breakBeforeTarget = breakBefore.beforeTarget;
    }
    if (breakBefore) {
      const target = this[$searchNode](breakBeforeTarget, breakBefore[$getParent]());
      if (target instanceof PageArea) {
        pageArea = target;
        breakBefore[$extra] = {};
      }
    }
    if (!pageArea) {
      pageArea = pageAreas[0];
    }
    pageArea[$extra] = {
      numberOfUse: 1
    };
    const pageAreaParent = pageArea[$getParent]();
    pageAreaParent[$extra] = {
      numberOfUse: 1,
      pageIndex: pageAreaParent.pageArea.children.indexOf(pageArea),
      pageSetIndex: 0
    };
    let targetPageArea;
    let leader = null;
    let trailer = null;
    let hasSomething = true;
    let hasSomethingCounter = 0;
    let startIndex = 0;
    while (true) {
      if (!hasSomething) {
        mainHtml.children.pop();
        if (++hasSomethingCounter === MAX_EMPTY_PAGES) {
          warn("XFA - Something goes wrong: please file a bug.");
          return mainHtml;
        }
      } else {
        hasSomethingCounter = 0;
      }
      targetPageArea = null;
      this[$extra].currentPageArea = pageArea;
      const page = pageArea[$toHTML]().html;
      mainHtml.children.push(page);
      if (leader) {
        this[$extra].noLayoutFailure = true;
        page.children.push(leader[$toHTML](pageArea[$extra].space).html);
        leader = null;
      }
      if (trailer) {
        this[$extra].noLayoutFailure = true;
        page.children.push(trailer[$toHTML](pageArea[$extra].space).html);
        trailer = null;
      }
      const contentAreas = pageArea.contentArea.children;
      const htmlContentAreas = page.children.filter(node => node.attributes.class.includes("xfaContentarea"));
      hasSomething = false;
      this[$extra].firstUnsplittable = null;
      this[$extra].noLayoutFailure = false;
      const flush = index => {
        const html = root[$flushHTML]();
        if (html) {
          hasSomething ||= html.children?.length > 0;
          htmlContentAreas[index].children.push(html);
        }
      };
      for (let i = startIndex, ii = contentAreas.length; i < ii; i++) {
        const contentArea = this[$extra].currentContentArea = contentAreas[i];
        const space = {
          width: contentArea.w,
          height: contentArea.h
        };
        startIndex = 0;
        if (leader) {
          htmlContentAreas[i].children.push(leader[$toHTML](space).html);
          leader = null;
        }
        if (trailer) {
          htmlContentAreas[i].children.push(trailer[$toHTML](space).html);
          trailer = null;
        }
        const html = root[$toHTML](space);
        if (html.success) {
          if (html.html) {
            hasSomething ||= html.html.children?.length > 0;
            htmlContentAreas[i].children.push(html.html);
          } else if (!hasSomething && mainHtml.children.length > 1) {
            mainHtml.children.pop();
          }
          return mainHtml;
        }
        if (html.isBreak()) {
          const node = html.breakNode;
          flush(i);
          if (node.targetType === "auto") {
            continue;
          }
          if (node.leader) {
            leader = this[$searchNode](node.leader, node[$getParent]());
            leader = leader ? leader[0] : null;
          }
          if (node.trailer) {
            trailer = this[$searchNode](node.trailer, node[$getParent]());
            trailer = trailer ? trailer[0] : null;
          }
          if (node.targetType === "pageArea") {
            targetPageArea = node[$extra].target;
            i = Infinity;
          } else if (!node[$extra].target) {
            i = node[$extra].index;
          } else {
            targetPageArea = node[$extra].target;
            startIndex = node[$extra].index + 1;
            i = Infinity;
          }
          continue;
        }
        if (this[$extra].overflowNode) {
          const node = this[$extra].overflowNode;
          this[$extra].overflowNode = null;
          const overflowExtra = node[$getExtra]();
          const target = overflowExtra.target;
          overflowExtra.addLeader = overflowExtra.leader !== null;
          overflowExtra.addTrailer = overflowExtra.trailer !== null;
          flush(i);
          const currentIndex = i;
          i = Infinity;
          if (target instanceof PageArea) {
            targetPageArea = target;
          } else if (target instanceof ContentArea) {
            const index = contentAreas.indexOf(target);
            if (index !== -1) {
              if (index > currentIndex) {
                i = index - 1;
              } else {
                startIndex = index;
              }
            } else {
              targetPageArea = target[$getParent]();
              startIndex = targetPageArea.contentArea.children.indexOf(target);
            }
          }
          continue;
        }
        flush(i);
      }
      this[$extra].pageNumber += 1;
      if (targetPageArea) {
        if (targetPageArea[$isUsable]()) {
          targetPageArea[$extra].numberOfUse += 1;
        } else {
          targetPageArea = null;
        }
      }
      pageArea = targetPageArea || pageArea[$getNextPage]();
      yield null;
    }
  }
}
class Text extends ContentObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "text");
    this.id = attributes.id || "";
    this.maxChars = getInteger({
      data: attributes.maxChars,
      defaultValue: 0,
      validate: x => x >= 0
    });
    this.name = attributes.name || "";
    this.rid = attributes.rid || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
  [$acceptWhitespace]() {
    return true;
  }
  [$onChild](child) {
    if (child[$namespaceId] === NamespaceIds.xhtml.id) {
      this[$content] = child;
      return true;
    }
    warn(`XFA - Invalid content in Text: ${child[$nodeName]}.`);
    return false;
  }
  [$onText](str) {
    if (this[$content] instanceof XFAObject) {
      return;
    }
    super[$onText](str);
  }
  [$finalize]() {
    if (typeof this[$content] === "string") {
      this[$content] = this[$content].replaceAll("\r\n", "\n");
    }
  }
  [$getExtra]() {
    if (typeof this[$content] === "string") {
      return this[$content].split(/[\u2029\u2028\n]/).reduce((acc, line) => {
        if (line) {
          acc.push(line);
        }
        return acc;
      }, []).join("\n");
    }
    return this[$content][$text]();
  }
  [$toHTML](availableSpace) {
    if (typeof this[$content] === "string") {
      const html = valueToHtml(this[$content]).html;
      if (this[$content].includes("\u2029")) {
        html.name = "div";
        html.children = [];
        this[$content].split("\u2029").map(para => para.split(/[\u2028\n]/).reduce((acc, line) => {
          acc.push({
            name: "span",
            value: line
          }, {
            name: "br"
          });
          return acc;
        }, [])).forEach(lines => {
          html.children.push({
            name: "p",
            children: lines
          });
        });
      } else if (/[\u2028\n]/.test(this[$content])) {
        html.name = "div";
        html.children = [];
        this[$content].split(/[\u2028\n]/).forEach(line => {
          html.children.push({
            name: "span",
            value: line
          }, {
            name: "br"
          });
        });
      }
      return HTMLResult.success(html);
    }
    return this[$content][$toHTML](availableSpace);
  }
}
class TextEdit extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "textEdit", true);
    this.allowRichText = getInteger({
      data: attributes.allowRichText,
      defaultValue: 0,
      validate: x => x === 1
    });
    this.hScrollPolicy = getStringOption(attributes.hScrollPolicy, ["auto", "off", "on"]);
    this.id = attributes.id || "";
    this.multiLine = getInteger({
      data: attributes.multiLine,
      defaultValue: "",
      validate: x => x === 0 || x === 1
    });
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.vScrollPolicy = getStringOption(attributes.vScrollPolicy, ["auto", "off", "on"]);
    this.border = null;
    this.comb = null;
    this.extras = null;
    this.margin = null;
  }
  [$toHTML](availableSpace) {
    const style = toStyle(this, "border", "font", "margin");
    let html;
    const field = this[$getParent]()[$getParent]();
    if (this.multiLine === "") {
      this.multiLine = field instanceof Draw ? 1 : 0;
    }
    if (this.multiLine === 1) {
      html = {
        name: "textarea",
        attributes: {
          dataId: field[$data]?.[$uid] || field[$uid],
          fieldId: field[$uid],
          class: ["xfaTextfield"],
          style,
          "aria-label": ariaLabel(field),
          "aria-required": false
        }
      };
    } else {
      html = {
        name: "input",
        attributes: {
          type: "text",
          dataId: field[$data]?.[$uid] || field[$uid],
          fieldId: field[$uid],
          class: ["xfaTextfield"],
          style,
          "aria-label": ariaLabel(field),
          "aria-required": false
        }
      };
    }
    if (isRequired(field)) {
      html.attributes["aria-required"] = true;
      html.attributes.required = true;
    }
    return HTMLResult.success({
      name: "label",
      attributes: {
        class: ["xfaLabel"]
      },
      children: [html]
    });
  }
}
class Time extends StringObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "time");
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
  [$finalize]() {
    const date = this[$content].trim();
    this[$content] = date ? new Date(date) : null;
  }
  [$toHTML](availableSpace) {
    return valueToHtml(this[$content] ? this[$content].toString() : "");
  }
}
class TimeStamp extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "timeStamp");
    this.id = attributes.id || "";
    this.server = attributes.server || "";
    this.type = getStringOption(attributes.type, ["optional", "required"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class ToolTip extends StringObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "toolTip");
    this.id = attributes.id || "";
    this.rid = attributes.rid || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class Traversal extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "traversal", true);
    this.id = attributes.id || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.extras = null;
    this.traverse = new XFAObjectArray();
  }
}
class Traverse extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "traverse", true);
    this.id = attributes.id || "";
    this.operation = getStringOption(attributes.operation, ["next", "back", "down", "first", "left", "right", "up"]);
    this.ref = attributes.ref || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.extras = null;
    this.script = null;
  }
  get name() {
    return this.operation;
  }
  [$isTransparent]() {
    return false;
  }
}
class Ui extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "ui", true);
    this.id = attributes.id || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.extras = null;
    this.picture = null;
    this.barcode = null;
    this.button = null;
    this.checkButton = null;
    this.choiceList = null;
    this.dateTimeEdit = null;
    this.defaultUi = null;
    this.imageEdit = null;
    this.numericEdit = null;
    this.passwordEdit = null;
    this.signature = null;
    this.textEdit = null;
  }
  [$getExtra]() {
    if (this[$extra] === undefined) {
      for (const name of Object.getOwnPropertyNames(this)) {
        if (name === "extras" || name === "picture") {
          continue;
        }
        const obj = this[name];
        if (!(obj instanceof XFAObject)) {
          continue;
        }
        this[$extra] = obj;
        return obj;
      }
      this[$extra] = null;
    }
    return this[$extra];
  }
  [$toHTML](availableSpace) {
    const obj = this[$getExtra]();
    if (obj) {
      return obj[$toHTML](availableSpace);
    }
    return HTMLResult.EMPTY;
  }
}
class Validate extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "validate", true);
    this.formatTest = getStringOption(attributes.formatTest, ["warning", "disabled", "error"]);
    this.id = attributes.id || "";
    this.nullTest = getStringOption(attributes.nullTest, ["disabled", "error", "warning"]);
    this.scriptTest = getStringOption(attributes.scriptTest, ["error", "disabled", "warning"]);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.extras = null;
    this.message = null;
    this.picture = null;
    this.script = null;
  }
}
class Value extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "value", true);
    this.id = attributes.id || "";
    this.override = getInteger({
      data: attributes.override,
      defaultValue: 0,
      validate: x => x === 1
    });
    this.relevant = getRelevant(attributes.relevant);
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.arc = null;
    this.boolean = null;
    this.date = null;
    this.dateTime = null;
    this.decimal = null;
    this.exData = null;
    this.float = null;
    this.image = null;
    this.integer = null;
    this.line = null;
    this.rectangle = null;
    this.text = null;
    this.time = null;
  }
  [$setValue](value) {
    const parent = this[$getParent]();
    if (parent instanceof Field) {
      if (parent.ui?.imageEdit) {
        if (!this.image) {
          this.image = new Image({});
          this[$appendChild](this.image);
        }
        this.image[$content] = value[$content];
        return;
      }
    }
    const valueName = value[$nodeName];
    if (this[valueName] !== null) {
      this[valueName][$content] = value[$content];
      return;
    }
    for (const name of Object.getOwnPropertyNames(this)) {
      const obj = this[name];
      if (obj instanceof XFAObject) {
        this[name] = null;
        this[$removeChild](obj);
      }
    }
    this[value[$nodeName]] = value;
    this[$appendChild](value);
  }
  [$text]() {
    if (this.exData) {
      if (typeof this.exData[$content] === "string") {
        return this.exData[$content].trim();
      }
      return this.exData[$content][$text]().trim();
    }
    for (const name of Object.getOwnPropertyNames(this)) {
      if (name === "image") {
        continue;
      }
      const obj = this[name];
      if (obj instanceof XFAObject) {
        return (obj[$content] || "").toString().trim();
      }
    }
    return null;
  }
  [$toHTML](availableSpace) {
    for (const name of Object.getOwnPropertyNames(this)) {
      const obj = this[name];
      if (!(obj instanceof XFAObject)) {
        continue;
      }
      return obj[$toHTML](availableSpace);
    }
    return HTMLResult.EMPTY;
  }
}
class Variables extends XFAObject {
  constructor(attributes) {
    super(TEMPLATE_NS_ID, "variables", true);
    this.id = attributes.id || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
    this.boolean = new XFAObjectArray();
    this.date = new XFAObjectArray();
    this.dateTime = new XFAObjectArray();
    this.decimal = new XFAObjectArray();
    this.exData = new XFAObjectArray();
    this.float = new XFAObjectArray();
    this.image = new XFAObjectArray();
    this.integer = new XFAObjectArray();
    this.manifest = new XFAObjectArray();
    this.script = new XFAObjectArray();
    this.text = new XFAObjectArray();
    this.time = new XFAObjectArray();
  }
  [$isTransparent]() {
    return true;
  }
}
class TemplateNamespace {
  static [$buildXFAObject](name, attributes) {
    if (TemplateNamespace.hasOwnProperty(name)) {
      const node = TemplateNamespace[name](attributes);
      node[$setSetAttributes](attributes);
      return node;
    }
    return undefined;
  }
  static appearanceFilter(attrs) {
    return new AppearanceFilter(attrs);
  }
  static arc(attrs) {
    return new Arc(attrs);
  }
  static area(attrs) {
    return new Area(attrs);
  }
  static assist(attrs) {
    return new Assist(attrs);
  }
  static barcode(attrs) {
    return new Barcode(attrs);
  }
  static bind(attrs) {
    return new Bind(attrs);
  }
  static bindItems(attrs) {
    return new BindItems(attrs);
  }
  static bookend(attrs) {
    return new Bookend(attrs);
  }
  static boolean(attrs) {
    return new BooleanElement(attrs);
  }
  static border(attrs) {
    return new Border(attrs);
  }
  static break(attrs) {
    return new Break(attrs);
  }
  static breakAfter(attrs) {
    return new BreakAfter(attrs);
  }
  static breakBefore(attrs) {
    return new BreakBefore(attrs);
  }
  static button(attrs) {
    return new Button(attrs);
  }
  static calculate(attrs) {
    return new Calculate(attrs);
  }
  static caption(attrs) {
    return new Caption(attrs);
  }
  static certificate(attrs) {
    return new Certificate(attrs);
  }
  static certificates(attrs) {
    return new Certificates(attrs);
  }
  static checkButton(attrs) {
    return new CheckButton(attrs);
  }
  static choiceList(attrs) {
    return new ChoiceList(attrs);
  }
  static color(attrs) {
    return new Color(attrs);
  }
  static comb(attrs) {
    return new Comb(attrs);
  }
  static connect(attrs) {
    return new Connect(attrs);
  }
  static contentArea(attrs) {
    return new ContentArea(attrs);
  }
  static corner(attrs) {
    return new Corner(attrs);
  }
  static date(attrs) {
    return new DateElement(attrs);
  }
  static dateTime(attrs) {
    return new DateTime(attrs);
  }
  static dateTimeEdit(attrs) {
    return new DateTimeEdit(attrs);
  }
  static decimal(attrs) {
    return new Decimal(attrs);
  }
  static defaultUi(attrs) {
    return new DefaultUi(attrs);
  }
  static desc(attrs) {
    return new Desc(attrs);
  }
  static digestMethod(attrs) {
    return new DigestMethod(attrs);
  }
  static digestMethods(attrs) {
    return new DigestMethods(attrs);
  }
  static draw(attrs) {
    return new Draw(attrs);
  }
  static edge(attrs) {
    return new Edge(attrs);
  }
  static encoding(attrs) {
    return new Encoding(attrs);
  }
  static encodings(attrs) {
    return new Encodings(attrs);
  }
  static encrypt(attrs) {
    return new Encrypt(attrs);
  }
  static encryptData(attrs) {
    return new EncryptData(attrs);
  }
  static encryption(attrs) {
    return new Encryption(attrs);
  }
  static encryptionMethod(attrs) {
    return new EncryptionMethod(attrs);
  }
  static encryptionMethods(attrs) {
    return new EncryptionMethods(attrs);
  }
  static event(attrs) {
    return new Event(attrs);
  }
  static exData(attrs) {
    return new ExData(attrs);
  }
  static exObject(attrs) {
    return new ExObject(attrs);
  }
  static exclGroup(attrs) {
    return new ExclGroup(attrs);
  }
  static execute(attrs) {
    return new Execute(attrs);
  }
  static extras(attrs) {
    return new Extras(attrs);
  }
  static field(attrs) {
    return new Field(attrs);
  }
  static fill(attrs) {
    return new Fill(attrs);
  }
  static filter(attrs) {
    return new Filter(attrs);
  }
  static float(attrs) {
    return new Float(attrs);
  }
  static font(attrs) {
    return new template_Font(attrs);
  }
  static format(attrs) {
    return new Format(attrs);
  }
  static handler(attrs) {
    return new Handler(attrs);
  }
  static hyphenation(attrs) {
    return new Hyphenation(attrs);
  }
  static image(attrs) {
    return new Image(attrs);
  }
  static imageEdit(attrs) {
    return new ImageEdit(attrs);
  }
  static integer(attrs) {
    return new Integer(attrs);
  }
  static issuers(attrs) {
    return new Issuers(attrs);
  }
  static items(attrs) {
    return new Items(attrs);
  }
  static keep(attrs) {
    return new Keep(attrs);
  }
  static keyUsage(attrs) {
    return new KeyUsage(attrs);
  }
  static line(attrs) {
    return new Line(attrs);
  }
  static linear(attrs) {
    return new Linear(attrs);
  }
  static lockDocument(attrs) {
    return new LockDocument(attrs);
  }
  static manifest(attrs) {
    return new Manifest(attrs);
  }
  static margin(attrs) {
    return new Margin(attrs);
  }
  static mdp(attrs) {
    return new Mdp(attrs);
  }
  static medium(attrs) {
    return new Medium(attrs);
  }
  static message(attrs) {
    return new Message(attrs);
  }
  static numericEdit(attrs) {
    return new NumericEdit(attrs);
  }
  static occur(attrs) {
    return new Occur(attrs);
  }
  static oid(attrs) {
    return new Oid(attrs);
  }
  static oids(attrs) {
    return new Oids(attrs);
  }
  static overflow(attrs) {
    return new Overflow(attrs);
  }
  static pageArea(attrs) {
    return new PageArea(attrs);
  }
  static pageSet(attrs) {
    return new PageSet(attrs);
  }
  static para(attrs) {
    return new Para(attrs);
  }
  static passwordEdit(attrs) {
    return new PasswordEdit(attrs);
  }
  static pattern(attrs) {
    return new template_Pattern(attrs);
  }
  static picture(attrs) {
    return new Picture(attrs);
  }
  static proto(attrs) {
    return new Proto(attrs);
  }
  static radial(attrs) {
    return new Radial(attrs);
  }
  static reason(attrs) {
    return new Reason(attrs);
  }
  static reasons(attrs) {
    return new Reasons(attrs);
  }
  static rectangle(attrs) {
    return new Rectangle(attrs);
  }
  static ref(attrs) {
    return new RefElement(attrs);
  }
  static script(attrs) {
    return new Script(attrs);
  }
  static setProperty(attrs) {
    return new SetProperty(attrs);
  }
  static signData(attrs) {
    return new SignData(attrs);
  }
  static signature(attrs) {
    return new Signature(attrs);
  }
  static signing(attrs) {
    return new Signing(attrs);
  }
  static solid(attrs) {
    return new Solid(attrs);
  }
  static speak(attrs) {
    return new Speak(attrs);
  }
  static stipple(attrs) {
    return new Stipple(attrs);
  }
  static subform(attrs) {
    return new Subform(attrs);
  }
  static subformSet(attrs) {
    return new SubformSet(attrs);
  }
  static subjectDN(attrs) {
    return new SubjectDN(attrs);
  }
  static subjectDNs(attrs) {
    return new SubjectDNs(attrs);
  }
  static submit(attrs) {
    return new Submit(attrs);
  }
  static template(attrs) {
    return new Template(attrs);
  }
  static text(attrs) {
    return new Text(attrs);
  }
  static textEdit(attrs) {
    return new TextEdit(attrs);
  }
  static time(attrs) {
    return new Time(attrs);
  }
  static timeStamp(attrs) {
    return new TimeStamp(attrs);
  }
  static toolTip(attrs) {
    return new ToolTip(attrs);
  }
  static traversal(attrs) {
    return new Traversal(attrs);
  }
  static traverse(attrs) {
    return new Traverse(attrs);
  }
  static ui(attrs) {
    return new Ui(attrs);
  }
  static validate(attrs) {
    return new Validate(attrs);
  }
  static value(attrs) {
    return new Value(attrs);
  }
  static variables(attrs) {
    return new Variables(attrs);
  }
}

;// CONCATENATED MODULE: ./src/core/xfa/bind.js






const bind_NS_DATASETS = NamespaceIds.datasets.id;
function createText(content) {
  const node = new Text({});
  node[$content] = content;
  return node;
}
class Binder {
  constructor(root) {
    this.root = root;
    this.datasets = root.datasets;
    this.data = root.datasets?.data || new XmlObject(NamespaceIds.datasets.id, "data");
    this.emptyMerge = this.data[$getChildren]().length === 0;
    this.root.form = this.form = root.template[$clone]();
  }
  _isConsumeData() {
    return !this.emptyMerge && this._mergeMode;
  }
  _isMatchTemplate() {
    return !this._isConsumeData();
  }
  bind() {
    this._bindElement(this.form, this.data);
    return this.form;
  }
  getData() {
    return this.data;
  }
  _bindValue(formNode, data, picture) {
    formNode[$data] = data;
    if (formNode[$hasSettableValue]()) {
      if (data[$isDataValue]()) {
        const value = data[$getDataValue]();
        formNode[$setValue](createText(value));
      } else if (formNode instanceof Field && formNode.ui?.choiceList?.open === "multiSelect") {
        const value = data[$getChildren]().map(child => child[$content].trim()).join("\n");
        formNode[$setValue](createText(value));
      } else if (this._isConsumeData()) {
        warn(`XFA - Nodes haven't the same type.`);
      }
    } else if (!data[$isDataValue]() || this._isMatchTemplate()) {
      this._bindElement(formNode, data);
    } else {
      warn(`XFA - Nodes haven't the same type.`);
    }
  }
  _findDataByNameToConsume(name, isValue, dataNode, global) {
    if (!name) {
      return null;
    }
    let generator, match;
    for (let i = 0; i < 3; i++) {
      generator = dataNode[$getRealChildrenByNameIt](name, false, true);
      while (true) {
        match = generator.next().value;
        if (!match) {
          break;
        }
        if (isValue === match[$isDataValue]()) {
          return match;
        }
      }
      if (dataNode[$namespaceId] === NamespaceIds.datasets.id && dataNode[$nodeName] === "data") {
        break;
      }
      dataNode = dataNode[$getParent]();
    }
    if (!global) {
      return null;
    }
    generator = this.data[$getRealChildrenByNameIt](name, true, false);
    match = generator.next().value;
    if (match) {
      return match;
    }
    generator = this.data[$getAttributeIt](name, true);
    match = generator.next().value;
    if (match?.[$isDataValue]()) {
      return match;
    }
    return null;
  }
  _setProperties(formNode, dataNode) {
    if (!formNode.hasOwnProperty("setProperty")) {
      return;
    }
    for (const {
      ref,
      target,
      connection
    } of formNode.setProperty.children) {
      if (connection) {
        continue;
      }
      if (!ref) {
        continue;
      }
      const nodes = searchNode(this.root, dataNode, ref, false, false);
      if (!nodes) {
        warn(`XFA - Invalid reference: ${ref}.`);
        continue;
      }
      const [node] = nodes;
      if (!node[$isDescendent](this.data)) {
        warn(`XFA - Invalid node: must be a data node.`);
        continue;
      }
      const targetNodes = searchNode(this.root, formNode, target, false, false);
      if (!targetNodes) {
        warn(`XFA - Invalid target: ${target}.`);
        continue;
      }
      const [targetNode] = targetNodes;
      if (!targetNode[$isDescendent](formNode)) {
        warn(`XFA - Invalid target: must be a property or subproperty.`);
        continue;
      }
      const targetParent = targetNode[$getParent]();
      if (targetNode instanceof SetProperty || targetParent instanceof SetProperty) {
        warn(`XFA - Invalid target: cannot be a setProperty or one of its properties.`);
        continue;
      }
      if (targetNode instanceof BindItems || targetParent instanceof BindItems) {
        warn(`XFA - Invalid target: cannot be a bindItems or one of its properties.`);
        continue;
      }
      const content = node[$text]();
      const name = targetNode[$nodeName];
      if (targetNode instanceof XFAAttribute) {
        const attrs = Object.create(null);
        attrs[name] = content;
        const obj = Reflect.construct(Object.getPrototypeOf(targetParent).constructor, [attrs]);
        targetParent[name] = obj[name];
        continue;
      }
      if (!targetNode.hasOwnProperty($content)) {
        warn(`XFA - Invalid node to use in setProperty`);
        continue;
      }
      targetNode[$data] = node;
      targetNode[$content] = content;
      targetNode[$finalize]();
    }
  }
  _bindItems(formNode, dataNode) {
    if (!formNode.hasOwnProperty("items") || !formNode.hasOwnProperty("bindItems") || formNode.bindItems.isEmpty()) {
      return;
    }
    for (const item of formNode.items.children) {
      formNode[$removeChild](item);
    }
    formNode.items.clear();
    const labels = new Items({});
    const values = new Items({});
    formNode[$appendChild](labels);
    formNode.items.push(labels);
    formNode[$appendChild](values);
    formNode.items.push(values);
    for (const {
      ref,
      labelRef,
      valueRef,
      connection
    } of formNode.bindItems.children) {
      if (connection) {
        continue;
      }
      if (!ref) {
        continue;
      }
      const nodes = searchNode(this.root, dataNode, ref, false, false);
      if (!nodes) {
        warn(`XFA - Invalid reference: ${ref}.`);
        continue;
      }
      for (const node of nodes) {
        if (!node[$isDescendent](this.datasets)) {
          warn(`XFA - Invalid ref (${ref}): must be a datasets child.`);
          continue;
        }
        const labelNodes = searchNode(this.root, node, labelRef, true, false);
        if (!labelNodes) {
          warn(`XFA - Invalid label: ${labelRef}.`);
          continue;
        }
        const [labelNode] = labelNodes;
        if (!labelNode[$isDescendent](this.datasets)) {
          warn(`XFA - Invalid label: must be a datasets child.`);
          continue;
        }
        const valueNodes = searchNode(this.root, node, valueRef, true, false);
        if (!valueNodes) {
          warn(`XFA - Invalid value: ${valueRef}.`);
          continue;
        }
        const [valueNode] = valueNodes;
        if (!valueNode[$isDescendent](this.datasets)) {
          warn(`XFA - Invalid value: must be a datasets child.`);
          continue;
        }
        const label = createText(labelNode[$text]());
        const value = createText(valueNode[$text]());
        labels[$appendChild](label);
        labels.text.push(label);
        values[$appendChild](value);
        values.text.push(value);
      }
    }
  }
  _bindOccurrences(formNode, matches, picture) {
    let baseClone;
    if (matches.length > 1) {
      baseClone = formNode[$clone]();
      baseClone[$removeChild](baseClone.occur);
      baseClone.occur = null;
    }
    this._bindValue(formNode, matches[0], picture);
    this._setProperties(formNode, matches[0]);
    this._bindItems(formNode, matches[0]);
    if (matches.length === 1) {
      return;
    }
    const parent = formNode[$getParent]();
    const name = formNode[$nodeName];
    const pos = parent[$indexOf](formNode);
    for (let i = 1, ii = matches.length; i < ii; i++) {
      const match = matches[i];
      const clone = baseClone[$clone]();
      parent[name].push(clone);
      parent[$insertAt](pos + i, clone);
      this._bindValue(clone, match, picture);
      this._setProperties(clone, match);
      this._bindItems(clone, match);
    }
  }
  _createOccurrences(formNode) {
    if (!this.emptyMerge) {
      return;
    }
    const {
      occur
    } = formNode;
    if (!occur || occur.initial <= 1) {
      return;
    }
    const parent = formNode[$getParent]();
    const name = formNode[$nodeName];
    if (!(parent[name] instanceof XFAObjectArray)) {
      return;
    }
    let currentNumber;
    if (formNode.name) {
      currentNumber = parent[name].children.filter(e => e.name === formNode.name).length;
    } else {
      currentNumber = parent[name].children.length;
    }
    const pos = parent[$indexOf](formNode) + 1;
    const ii = occur.initial - currentNumber;
    if (ii) {
      const nodeClone = formNode[$clone]();
      nodeClone[$removeChild](nodeClone.occur);
      nodeClone.occur = null;
      parent[name].push(nodeClone);
      parent[$insertAt](pos, nodeClone);
      for (let i = 1; i < ii; i++) {
        const clone = nodeClone[$clone]();
        parent[name].push(clone);
        parent[$insertAt](pos + i, clone);
      }
    }
  }
  _getOccurInfo(formNode) {
    const {
      name,
      occur
    } = formNode;
    if (!occur || !name) {
      return [1, 1];
    }
    const max = occur.max === -1 ? Infinity : occur.max;
    return [occur.min, max];
  }
  _setAndBind(formNode, dataNode) {
    this._setProperties(formNode, dataNode);
    this._bindItems(formNode, dataNode);
    this._bindElement(formNode, dataNode);
  }
  _bindElement(formNode, dataNode) {
    const uselessNodes = [];
    this._createOccurrences(formNode);
    for (const child of formNode[$getChildren]()) {
      if (child[$data]) {
        continue;
      }
      if (this._mergeMode === undefined && child[$nodeName] === "subform") {
        this._mergeMode = child.mergeMode === "consumeData";
        const dataChildren = dataNode[$getChildren]();
        if (dataChildren.length > 0) {
          this._bindOccurrences(child, [dataChildren[0]], null);
        } else if (this.emptyMerge) {
          const nsId = dataNode[$namespaceId] === bind_NS_DATASETS ? -1 : dataNode[$namespaceId];
          const dataChild = child[$data] = new XmlObject(nsId, child.name || "root");
          dataNode[$appendChild](dataChild);
          this._bindElement(child, dataChild);
        }
        continue;
      }
      if (!child[$isBindable]()) {
        continue;
      }
      let global = false;
      let picture = null;
      let ref = null;
      let match = null;
      if (child.bind) {
        switch (child.bind.match) {
          case "none":
            this._setAndBind(child, dataNode);
            continue;
          case "global":
            global = true;
            break;
          case "dataRef":
            if (!child.bind.ref) {
              warn(`XFA - ref is empty in node ${child[$nodeName]}.`);
              this._setAndBind(child, dataNode);
              continue;
            }
            ref = child.bind.ref;
            break;
          default:
            break;
        }
        if (child.bind.picture) {
          picture = child.bind.picture[$content];
        }
      }
      const [min, max] = this._getOccurInfo(child);
      if (ref) {
        match = searchNode(this.root, dataNode, ref, true, false);
        if (match === null) {
          match = createDataNode(this.data, dataNode, ref);
          if (!match) {
            continue;
          }
          if (this._isConsumeData()) {
            match[$consumed] = true;
          }
          this._setAndBind(child, match);
          continue;
        } else {
          if (this._isConsumeData()) {
            match = match.filter(node => !node[$consumed]);
          }
          if (match.length > max) {
            match = match.slice(0, max);
          } else if (match.length === 0) {
            match = null;
          }
          if (match && this._isConsumeData()) {
            match.forEach(node => {
              node[$consumed] = true;
            });
          }
        }
      } else {
        if (!child.name) {
          this._setAndBind(child, dataNode);
          continue;
        }
        if (this._isConsumeData()) {
          const matches = [];
          while (matches.length < max) {
            const found = this._findDataByNameToConsume(child.name, child[$hasSettableValue](), dataNode, global);
            if (!found) {
              break;
            }
            found[$consumed] = true;
            matches.push(found);
          }
          match = matches.length > 0 ? matches : null;
        } else {
          match = dataNode[$getRealChildrenByNameIt](child.name, false, this.emptyMerge).next().value;
          if (!match) {
            if (min === 0) {
              uselessNodes.push(child);
              continue;
            }
            const nsId = dataNode[$namespaceId] === bind_NS_DATASETS ? -1 : dataNode[$namespaceId];
            match = child[$data] = new XmlObject(nsId, child.name);
            if (this.emptyMerge) {
              match[$consumed] = true;
            }
            dataNode[$appendChild](match);
            this._setAndBind(child, match);
            continue;
          }
          if (this.emptyMerge) {
            match[$consumed] = true;
          }
          match = [match];
        }
      }
      if (match) {
        this._bindOccurrences(child, match, picture);
      } else if (min > 0) {
        this._setAndBind(child, dataNode);
      } else {
        uselessNodes.push(child);
      }
    }
    uselessNodes.forEach(node => node[$getParent]()[$removeChild](node));
  }
}

;// CONCATENATED MODULE: ./src/core/xfa/data.js

class DataHandler {
  constructor(root, data) {
    this.data = data;
    this.dataset = root.datasets || null;
  }
  serialize(storage) {
    const stack = [[-1, this.data[$getChildren]()]];
    while (stack.length > 0) {
      const last = stack.at(-1);
      const [i, children] = last;
      if (i + 1 === children.length) {
        stack.pop();
        continue;
      }
      const child = children[++last[0]];
      const storageEntry = storage.get(child[$uid]);
      if (storageEntry) {
        child[$setValue](storageEntry);
      } else {
        const attributes = child[$getAttributes]();
        for (const value of attributes.values()) {
          const entry = storage.get(value[$uid]);
          if (entry) {
            value[$setValue](entry);
            break;
          }
        }
      }
      const nodes = child[$getChildren]();
      if (nodes.length > 0) {
        stack.push([-1, nodes]);
      }
    }
    const buf = [`<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">`];
    if (this.dataset) {
      for (const child of this.dataset[$getChildren]()) {
        if (child[$nodeName] !== "data") {
          child[$toString](buf);
        }
      }
    }
    this.data[$toString](buf);
    buf.push("</xfa:datasets>");
    return buf.join("");
  }
}

;// CONCATENATED MODULE: ./src/core/xfa/config.js





const CONFIG_NS_ID = NamespaceIds.config.id;
class Acrobat extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "acrobat", true);
    this.acrobat7 = null;
    this.autoSave = null;
    this.common = null;
    this.validate = null;
    this.validateApprovalSignatures = null;
    this.submitUrl = new XFAObjectArray();
  }
}
class Acrobat7 extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "acrobat7", true);
    this.dynamicRender = null;
  }
}
class ADBE_JSConsole extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "ADBE_JSConsole", ["delegate", "Enable", "Disable"]);
  }
}
class ADBE_JSDebugger extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "ADBE_JSDebugger", ["delegate", "Enable", "Disable"]);
  }
}
class AddSilentPrint extends Option01 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "addSilentPrint");
  }
}
class AddViewerPreferences extends Option01 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "addViewerPreferences");
  }
}
class AdjustData extends Option10 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "adjustData");
  }
}
class AdobeExtensionLevel extends IntegerObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "adobeExtensionLevel", 0, n => n >= 1 && n <= 8);
  }
}
class Agent extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "agent", true);
    this.name = attributes.name ? attributes.name.trim() : "";
    this.common = new XFAObjectArray();
  }
}
class AlwaysEmbed extends ContentObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "alwaysEmbed");
  }
}
class Amd extends StringObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "amd");
  }
}
class config_Area extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "area");
    this.level = getInteger({
      data: attributes.level,
      defaultValue: 0,
      validate: n => n >= 1 && n <= 3
    });
    this.name = getStringOption(attributes.name, ["", "barcode", "coreinit", "deviceDriver", "font", "general", "layout", "merge", "script", "signature", "sourceSet", "templateCache"]);
  }
}
class Attributes extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "attributes", ["preserve", "delegate", "ignore"]);
  }
}
class AutoSave extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "autoSave", ["disabled", "enabled"]);
  }
}
class Base extends StringObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "base");
  }
}
class BatchOutput extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "batchOutput");
    this.format = getStringOption(attributes.format, ["none", "concat", "zip", "zipCompress"]);
  }
}
class BehaviorOverride extends ContentObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "behaviorOverride");
  }
  [$finalize]() {
    this[$content] = new Map(this[$content].trim().split(/\s+/).filter(x => x.includes(":")).map(x => x.split(":", 2)));
  }
}
class Cache extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "cache", true);
    this.templateCache = null;
  }
}
class Change extends Option01 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "change");
  }
}
class Common extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "common", true);
    this.data = null;
    this.locale = null;
    this.localeSet = null;
    this.messaging = null;
    this.suppressBanner = null;
    this.template = null;
    this.validationMessaging = null;
    this.versionControl = null;
    this.log = new XFAObjectArray();
  }
}
class Compress extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "compress");
    this.scope = getStringOption(attributes.scope, ["imageOnly", "document"]);
  }
}
class CompressLogicalStructure extends Option01 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "compressLogicalStructure");
  }
}
class CompressObjectStream extends Option10 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "compressObjectStream");
  }
}
class Compression extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "compression", true);
    this.compressLogicalStructure = null;
    this.compressObjectStream = null;
    this.level = null;
    this.type = null;
  }
}
class Config extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "config", true);
    this.acrobat = null;
    this.present = null;
    this.trace = null;
    this.agent = new XFAObjectArray();
  }
}
class Conformance extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "conformance", ["A", "B"]);
  }
}
class ContentCopy extends Option01 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "contentCopy");
  }
}
class Copies extends IntegerObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "copies", 1, n => n >= 1);
  }
}
class Creator extends StringObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "creator");
  }
}
class CurrentPage extends IntegerObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "currentPage", 0, n => n >= 0);
  }
}
class Data extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "data", true);
    this.adjustData = null;
    this.attributes = null;
    this.incrementalLoad = null;
    this.outputXSL = null;
    this.range = null;
    this.record = null;
    this.startNode = null;
    this.uri = null;
    this.window = null;
    this.xsl = null;
    this.excludeNS = new XFAObjectArray();
    this.transform = new XFAObjectArray();
  }
}
class Debug extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "debug", true);
    this.uri = null;
  }
}
class DefaultTypeface extends ContentObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "defaultTypeface");
    this.writingScript = getStringOption(attributes.writingScript, ["*", "Arabic", "Cyrillic", "EastEuropeanRoman", "Greek", "Hebrew", "Japanese", "Korean", "Roman", "SimplifiedChinese", "Thai", "TraditionalChinese", "Vietnamese"]);
  }
}
class Destination extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "destination", ["pdf", "pcl", "ps", "webClient", "zpl"]);
  }
}
class DocumentAssembly extends Option01 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "documentAssembly");
  }
}
class Driver extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "driver", true);
    this.name = attributes.name ? attributes.name.trim() : "";
    this.fontInfo = null;
    this.xdc = null;
  }
}
class DuplexOption extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "duplexOption", ["simplex", "duplexFlipLongEdge", "duplexFlipShortEdge"]);
  }
}
class DynamicRender extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "dynamicRender", ["forbidden", "required"]);
  }
}
class Embed extends Option01 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "embed");
  }
}
class config_Encrypt extends Option01 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "encrypt");
  }
}
class config_Encryption extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "encryption", true);
    this.encrypt = null;
    this.encryptionLevel = null;
    this.permissions = null;
  }
}
class EncryptionLevel extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "encryptionLevel", ["40bit", "128bit"]);
  }
}
class Enforce extends StringObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "enforce");
  }
}
class Equate extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "equate");
    this.force = getInteger({
      data: attributes.force,
      defaultValue: 1,
      validate: n => n === 0
    });
    this.from = attributes.from || "";
    this.to = attributes.to || "";
  }
}
class EquateRange extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "equateRange");
    this.from = attributes.from || "";
    this.to = attributes.to || "";
    this._unicodeRange = attributes.unicodeRange || "";
  }
  get unicodeRange() {
    const ranges = [];
    const unicodeRegex = /U\+([0-9a-fA-F]+)/;
    const unicodeRange = this._unicodeRange;
    for (let range of unicodeRange.split(",").map(x => x.trim()).filter(x => !!x)) {
      range = range.split("-", 2).map(x => {
        const found = x.match(unicodeRegex);
        if (!found) {
          return 0;
        }
        return parseInt(found[1], 16);
      });
      if (range.length === 1) {
        range.push(range[0]);
      }
      ranges.push(range);
    }
    return shadow(this, "unicodeRange", ranges);
  }
}
class Exclude extends ContentObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "exclude");
  }
  [$finalize]() {
    this[$content] = this[$content].trim().split(/\s+/).filter(x => x && ["calculate", "close", "enter", "exit", "initialize", "ready", "validate"].includes(x));
  }
}
class ExcludeNS extends StringObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "excludeNS");
  }
}
class FlipLabel extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "flipLabel", ["usePrinterSetting", "on", "off"]);
  }
}
class config_FontInfo extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "fontInfo", true);
    this.embed = null;
    this.map = null;
    this.subsetBelow = null;
    this.alwaysEmbed = new XFAObjectArray();
    this.defaultTypeface = new XFAObjectArray();
    this.neverEmbed = new XFAObjectArray();
  }
}
class FormFieldFilling extends Option01 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "formFieldFilling");
  }
}
class GroupParent extends StringObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "groupParent");
  }
}
class IfEmpty extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "ifEmpty", ["dataValue", "dataGroup", "ignore", "remove"]);
  }
}
class IncludeXDPContent extends StringObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "includeXDPContent");
  }
}
class IncrementalLoad extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "incrementalLoad", ["none", "forwardOnly"]);
  }
}
class IncrementalMerge extends Option01 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "incrementalMerge");
  }
}
class Interactive extends Option01 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "interactive");
  }
}
class Jog extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "jog", ["usePrinterSetting", "none", "pageSet"]);
  }
}
class LabelPrinter extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "labelPrinter", true);
    this.name = getStringOption(attributes.name, ["zpl", "dpl", "ipl", "tcpl"]);
    this.batchOutput = null;
    this.flipLabel = null;
    this.fontInfo = null;
    this.xdc = null;
  }
}
class Layout extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "layout", ["paginate", "panel"]);
  }
}
class Level extends IntegerObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "level", 0, n => n > 0);
  }
}
class Linearized extends Option01 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "linearized");
  }
}
class Locale extends StringObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "locale");
  }
}
class LocaleSet extends StringObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "localeSet");
  }
}
class Log extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "log", true);
    this.mode = null;
    this.threshold = null;
    this.to = null;
    this.uri = null;
  }
}
class MapElement extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "map", true);
    this.equate = new XFAObjectArray();
    this.equateRange = new XFAObjectArray();
  }
}
class MediumInfo extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "mediumInfo", true);
    this.map = null;
  }
}
class config_Message extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "message", true);
    this.msgId = null;
    this.severity = null;
  }
}
class Messaging extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "messaging", true);
    this.message = new XFAObjectArray();
  }
}
class Mode extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "mode", ["append", "overwrite"]);
  }
}
class ModifyAnnots extends Option01 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "modifyAnnots");
  }
}
class MsgId extends IntegerObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "msgId", 1, n => n >= 1);
  }
}
class NameAttr extends StringObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "nameAttr");
  }
}
class NeverEmbed extends ContentObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "neverEmbed");
  }
}
class NumberOfCopies extends IntegerObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "numberOfCopies", null, n => n >= 2 && n <= 5);
  }
}
class OpenAction extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "openAction", true);
    this.destination = null;
  }
}
class Output extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "output", true);
    this.to = null;
    this.type = null;
    this.uri = null;
  }
}
class OutputBin extends StringObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "outputBin");
  }
}
class OutputXSL extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "outputXSL", true);
    this.uri = null;
  }
}
class Overprint extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "overprint", ["none", "both", "draw", "field"]);
  }
}
class Packets extends StringObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "packets");
  }
  [$finalize]() {
    if (this[$content] === "*") {
      return;
    }
    this[$content] = this[$content].trim().split(/\s+/).filter(x => ["config", "datasets", "template", "xfdf", "xslt"].includes(x));
  }
}
class PageOffset extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "pageOffset");
    this.x = getInteger({
      data: attributes.x,
      defaultValue: "useXDCSetting",
      validate: n => true
    });
    this.y = getInteger({
      data: attributes.y,
      defaultValue: "useXDCSetting",
      validate: n => true
    });
  }
}
class PageRange extends StringObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "pageRange");
  }
  [$finalize]() {
    const numbers = this[$content].trim().split(/\s+/).map(x => parseInt(x, 10));
    const ranges = [];
    for (let i = 0, ii = numbers.length; i < ii; i += 2) {
      ranges.push(numbers.slice(i, i + 2));
    }
    this[$content] = ranges;
  }
}
class Pagination extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "pagination", ["simplex", "duplexShortEdge", "duplexLongEdge"]);
  }
}
class PaginationOverride extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "paginationOverride", ["none", "forceDuplex", "forceDuplexLongEdge", "forceDuplexShortEdge", "forceSimplex"]);
  }
}
class Part extends IntegerObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "part", 1, n => false);
  }
}
class Pcl extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "pcl", true);
    this.name = attributes.name || "";
    this.batchOutput = null;
    this.fontInfo = null;
    this.jog = null;
    this.mediumInfo = null;
    this.outputBin = null;
    this.pageOffset = null;
    this.staple = null;
    this.xdc = null;
  }
}
class Pdf extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "pdf", true);
    this.name = attributes.name || "";
    this.adobeExtensionLevel = null;
    this.batchOutput = null;
    this.compression = null;
    this.creator = null;
    this.encryption = null;
    this.fontInfo = null;
    this.interactive = null;
    this.linearized = null;
    this.openAction = null;
    this.pdfa = null;
    this.producer = null;
    this.renderPolicy = null;
    this.scriptModel = null;
    this.silentPrint = null;
    this.submitFormat = null;
    this.tagged = null;
    this.version = null;
    this.viewerPreferences = null;
    this.xdc = null;
  }
}
class Pdfa extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "pdfa", true);
    this.amd = null;
    this.conformance = null;
    this.includeXDPContent = null;
    this.part = null;
  }
}
class Permissions extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "permissions", true);
    this.accessibleContent = null;
    this.change = null;
    this.contentCopy = null;
    this.documentAssembly = null;
    this.formFieldFilling = null;
    this.modifyAnnots = null;
    this.plaintextMetadata = null;
    this.print = null;
    this.printHighQuality = null;
  }
}
class PickTrayByPDFSize extends Option01 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "pickTrayByPDFSize");
  }
}
class config_Picture extends StringObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "picture");
  }
}
class PlaintextMetadata extends Option01 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "plaintextMetadata");
  }
}
class Presence extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "presence", ["preserve", "dissolve", "dissolveStructure", "ignore", "remove"]);
  }
}
class Present extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "present", true);
    this.behaviorOverride = null;
    this.cache = null;
    this.common = null;
    this.copies = null;
    this.destination = null;
    this.incrementalMerge = null;
    this.layout = null;
    this.output = null;
    this.overprint = null;
    this.pagination = null;
    this.paginationOverride = null;
    this.script = null;
    this.validate = null;
    this.xdp = null;
    this.driver = new XFAObjectArray();
    this.labelPrinter = new XFAObjectArray();
    this.pcl = new XFAObjectArray();
    this.pdf = new XFAObjectArray();
    this.ps = new XFAObjectArray();
    this.submitUrl = new XFAObjectArray();
    this.webClient = new XFAObjectArray();
    this.zpl = new XFAObjectArray();
  }
}
class Print extends Option01 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "print");
  }
}
class PrintHighQuality extends Option01 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "printHighQuality");
  }
}
class PrintScaling extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "printScaling", ["appdefault", "noScaling"]);
  }
}
class PrinterName extends StringObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "printerName");
  }
}
class Producer extends StringObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "producer");
  }
}
class Ps extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "ps", true);
    this.name = attributes.name || "";
    this.batchOutput = null;
    this.fontInfo = null;
    this.jog = null;
    this.mediumInfo = null;
    this.outputBin = null;
    this.staple = null;
    this.xdc = null;
  }
}
class Range extends ContentObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "range");
  }
  [$finalize]() {
    this[$content] = this[$content].trim().split(/\s*,\s*/, 2).map(range => range.split("-").map(x => parseInt(x.trim(), 10))).filter(range => range.every(x => !isNaN(x))).map(range => {
      if (range.length === 1) {
        range.push(range[0]);
      }
      return range;
    });
  }
}
class Record extends ContentObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "record");
  }
  [$finalize]() {
    this[$content] = this[$content].trim();
    const n = parseInt(this[$content], 10);
    if (!isNaN(n) && n >= 0) {
      this[$content] = n;
    }
  }
}
class Relevant extends ContentObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "relevant");
  }
  [$finalize]() {
    this[$content] = this[$content].trim().split(/\s+/);
  }
}
class Rename extends ContentObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "rename");
  }
  [$finalize]() {
    this[$content] = this[$content].trim();
    if (this[$content].toLowerCase().startsWith("xml") || new RegExp("[\\p{L}_][\\p{L}\\d._\\p{M}-]*", "u").test(this[$content])) {
      warn("XFA - Rename: invalid XFA name");
    }
  }
}
class RenderPolicy extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "renderPolicy", ["server", "client"]);
  }
}
class RunScripts extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "runScripts", ["both", "client", "none", "server"]);
  }
}
class config_Script extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "script", true);
    this.currentPage = null;
    this.exclude = null;
    this.runScripts = null;
  }
}
class ScriptModel extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "scriptModel", ["XFA", "none"]);
  }
}
class Severity extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "severity", ["ignore", "error", "information", "trace", "warning"]);
  }
}
class SilentPrint extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "silentPrint", true);
    this.addSilentPrint = null;
    this.printerName = null;
  }
}
class Staple extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "staple");
    this.mode = getStringOption(attributes.mode, ["usePrinterSetting", "on", "off"]);
  }
}
class StartNode extends StringObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "startNode");
  }
}
class StartPage extends IntegerObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "startPage", 0, n => true);
  }
}
class SubmitFormat extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "submitFormat", ["html", "delegate", "fdf", "xml", "pdf"]);
  }
}
class SubmitUrl extends StringObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "submitUrl");
  }
}
class SubsetBelow extends IntegerObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "subsetBelow", 100, n => n >= 0 && n <= 100);
  }
}
class SuppressBanner extends Option01 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "suppressBanner");
  }
}
class Tagged extends Option01 {
  constructor(attributes) {
    super(CONFIG_NS_ID, "tagged");
  }
}
class config_Template extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "template", true);
    this.base = null;
    this.relevant = null;
    this.startPage = null;
    this.uri = null;
    this.xsl = null;
  }
}
class Threshold extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "threshold", ["trace", "error", "information", "warning"]);
  }
}
class To extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "to", ["null", "memory", "stderr", "stdout", "system", "uri"]);
  }
}
class TemplateCache extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "templateCache");
    this.maxEntries = getInteger({
      data: attributes.maxEntries,
      defaultValue: 5,
      validate: n => n >= 0
    });
  }
}
class Trace extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "trace", true);
    this.area = new XFAObjectArray();
  }
}
class Transform extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "transform", true);
    this.groupParent = null;
    this.ifEmpty = null;
    this.nameAttr = null;
    this.picture = null;
    this.presence = null;
    this.rename = null;
    this.whitespace = null;
  }
}
class Type extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "type", ["none", "ascii85", "asciiHex", "ccittfax", "flate", "lzw", "runLength", "native", "xdp", "mergedXDP"]);
  }
}
class Uri extends StringObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "uri");
  }
}
class config_Validate extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "validate", ["preSubmit", "prePrint", "preExecute", "preSave"]);
  }
}
class ValidateApprovalSignatures extends ContentObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "validateApprovalSignatures");
  }
  [$finalize]() {
    this[$content] = this[$content].trim().split(/\s+/).filter(x => ["docReady", "postSign"].includes(x));
  }
}
class ValidationMessaging extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "validationMessaging", ["allMessagesIndividually", "allMessagesTogether", "firstMessageOnly", "noMessages"]);
  }
}
class Version extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "version", ["1.7", "1.6", "1.5", "1.4", "1.3", "1.2"]);
  }
}
class VersionControl extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "VersionControl");
    this.outputBelow = getStringOption(attributes.outputBelow, ["warn", "error", "update"]);
    this.sourceAbove = getStringOption(attributes.sourceAbove, ["warn", "error"]);
    this.sourceBelow = getStringOption(attributes.sourceBelow, ["update", "maintain"]);
  }
}
class ViewerPreferences extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "viewerPreferences", true);
    this.ADBE_JSConsole = null;
    this.ADBE_JSDebugger = null;
    this.addViewerPreferences = null;
    this.duplexOption = null;
    this.enforce = null;
    this.numberOfCopies = null;
    this.pageRange = null;
    this.pickTrayByPDFSize = null;
    this.printScaling = null;
  }
}
class WebClient extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "webClient", true);
    this.name = attributes.name ? attributes.name.trim() : "";
    this.fontInfo = null;
    this.xdc = null;
  }
}
class Whitespace extends OptionObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "whitespace", ["preserve", "ltrim", "normalize", "rtrim", "trim"]);
  }
}
class Window extends ContentObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "window");
  }
  [$finalize]() {
    const pair = this[$content].trim().split(/\s*,\s*/, 2).map(x => parseInt(x, 10));
    if (pair.some(x => isNaN(x))) {
      this[$content] = [0, 0];
      return;
    }
    if (pair.length === 1) {
      pair.push(pair[0]);
    }
    this[$content] = pair;
  }
}
class Xdc extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "xdc", true);
    this.uri = new XFAObjectArray();
    this.xsl = new XFAObjectArray();
  }
}
class Xdp extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "xdp", true);
    this.packets = null;
  }
}
class Xsl extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "xsl", true);
    this.debug = null;
    this.uri = null;
  }
}
class Zpl extends XFAObject {
  constructor(attributes) {
    super(CONFIG_NS_ID, "zpl", true);
    this.name = attributes.name ? attributes.name.trim() : "";
    this.batchOutput = null;
    this.flipLabel = null;
    this.fontInfo = null;
    this.xdc = null;
  }
}
class ConfigNamespace {
  static [$buildXFAObject](name, attributes) {
    if (ConfigNamespace.hasOwnProperty(name)) {
      return ConfigNamespace[name](attributes);
    }
    return undefined;
  }
  static acrobat(attrs) {
    return new Acrobat(attrs);
  }
  static acrobat7(attrs) {
    return new Acrobat7(attrs);
  }
  static ADBE_JSConsole(attrs) {
    return new ADBE_JSConsole(attrs);
  }
  static ADBE_JSDebugger(attrs) {
    return new ADBE_JSDebugger(attrs);
  }
  static addSilentPrint(attrs) {
    return new AddSilentPrint(attrs);
  }
  static addViewerPreferences(attrs) {
    return new AddViewerPreferences(attrs);
  }
  static adjustData(attrs) {
    return new AdjustData(attrs);
  }
  static adobeExtensionLevel(attrs) {
    return new AdobeExtensionLevel(attrs);
  }
  static agent(attrs) {
    return new Agent(attrs);
  }
  static alwaysEmbed(attrs) {
    return new AlwaysEmbed(attrs);
  }
  static amd(attrs) {
    return new Amd(attrs);
  }
  static area(attrs) {
    return new config_Area(attrs);
  }
  static attributes(attrs) {
    return new Attributes(attrs);
  }
  static autoSave(attrs) {
    return new AutoSave(attrs);
  }
  static base(attrs) {
    return new Base(attrs);
  }
  static batchOutput(attrs) {
    return new BatchOutput(attrs);
  }
  static behaviorOverride(attrs) {
    return new BehaviorOverride(attrs);
  }
  static cache(attrs) {
    return new Cache(attrs);
  }
  static change(attrs) {
    return new Change(attrs);
  }
  static common(attrs) {
    return new Common(attrs);
  }
  static compress(attrs) {
    return new Compress(attrs);
  }
  static compressLogicalStructure(attrs) {
    return new CompressLogicalStructure(attrs);
  }
  static compressObjectStream(attrs) {
    return new CompressObjectStream(attrs);
  }
  static compression(attrs) {
    return new Compression(attrs);
  }
  static config(attrs) {
    return new Config(attrs);
  }
  static conformance(attrs) {
    return new Conformance(attrs);
  }
  static contentCopy(attrs) {
    return new ContentCopy(attrs);
  }
  static copies(attrs) {
    return new Copies(attrs);
  }
  static creator(attrs) {
    return new Creator(attrs);
  }
  static currentPage(attrs) {
    return new CurrentPage(attrs);
  }
  static data(attrs) {
    return new Data(attrs);
  }
  static debug(attrs) {
    return new Debug(attrs);
  }
  static defaultTypeface(attrs) {
    return new DefaultTypeface(attrs);
  }
  static destination(attrs) {
    return new Destination(attrs);
  }
  static documentAssembly(attrs) {
    return new DocumentAssembly(attrs);
  }
  static driver(attrs) {
    return new Driver(attrs);
  }
  static duplexOption(attrs) {
    return new DuplexOption(attrs);
  }
  static dynamicRender(attrs) {
    return new DynamicRender(attrs);
  }
  static embed(attrs) {
    return new Embed(attrs);
  }
  static encrypt(attrs) {
    return new config_Encrypt(attrs);
  }
  static encryption(attrs) {
    return new config_Encryption(attrs);
  }
  static encryptionLevel(attrs) {
    return new EncryptionLevel(attrs);
  }
  static enforce(attrs) {
    return new Enforce(attrs);
  }
  static equate(attrs) {
    return new Equate(attrs);
  }
  static equateRange(attrs) {
    return new EquateRange(attrs);
  }
  static exclude(attrs) {
    return new Exclude(attrs);
  }
  static excludeNS(attrs) {
    return new ExcludeNS(attrs);
  }
  static flipLabel(attrs) {
    return new FlipLabel(attrs);
  }
  static fontInfo(attrs) {
    return new config_FontInfo(attrs);
  }
  static formFieldFilling(attrs) {
    return new FormFieldFilling(attrs);
  }
  static groupParent(attrs) {
    return new GroupParent(attrs);
  }
  static ifEmpty(attrs) {
    return new IfEmpty(attrs);
  }
  static includeXDPContent(attrs) {
    return new IncludeXDPContent(attrs);
  }
  static incrementalLoad(attrs) {
    return new IncrementalLoad(attrs);
  }
  static incrementalMerge(attrs) {
    return new IncrementalMerge(attrs);
  }
  static interactive(attrs) {
    return new Interactive(attrs);
  }
  static jog(attrs) {
    return new Jog(attrs);
  }
  static labelPrinter(attrs) {
    return new LabelPrinter(attrs);
  }
  static layout(attrs) {
    return new Layout(attrs);
  }
  static level(attrs) {
    return new Level(attrs);
  }
  static linearized(attrs) {
    return new Linearized(attrs);
  }
  static locale(attrs) {
    return new Locale(attrs);
  }
  static localeSet(attrs) {
    return new LocaleSet(attrs);
  }
  static log(attrs) {
    return new Log(attrs);
  }
  static map(attrs) {
    return new MapElement(attrs);
  }
  static mediumInfo(attrs) {
    return new MediumInfo(attrs);
  }
  static message(attrs) {
    return new config_Message(attrs);
  }
  static messaging(attrs) {
    return new Messaging(attrs);
  }
  static mode(attrs) {
    return new Mode(attrs);
  }
  static modifyAnnots(attrs) {
    return new ModifyAnnots(attrs);
  }
  static msgId(attrs) {
    return new MsgId(attrs);
  }
  static nameAttr(attrs) {
    return new NameAttr(attrs);
  }
  static neverEmbed(attrs) {
    return new NeverEmbed(attrs);
  }
  static numberOfCopies(attrs) {
    return new NumberOfCopies(attrs);
  }
  static openAction(attrs) {
    return new OpenAction(attrs);
  }
  static output(attrs) {
    return new Output(attrs);
  }
  static outputBin(attrs) {
    return new OutputBin(attrs);
  }
  static outputXSL(attrs) {
    return new OutputXSL(attrs);
  }
  static overprint(attrs) {
    return new Overprint(attrs);
  }
  static packets(attrs) {
    return new Packets(attrs);
  }
  static pageOffset(attrs) {
    return new PageOffset(attrs);
  }
  static pageRange(attrs) {
    return new PageRange(attrs);
  }
  static pagination(attrs) {
    return new Pagination(attrs);
  }
  static paginationOverride(attrs) {
    return new PaginationOverride(attrs);
  }
  static part(attrs) {
    return new Part(attrs);
  }
  static pcl(attrs) {
    return new Pcl(attrs);
  }
  static pdf(attrs) {
    return new Pdf(attrs);
  }
  static pdfa(attrs) {
    return new Pdfa(attrs);
  }
  static permissions(attrs) {
    return new Permissions(attrs);
  }
  static pickTrayByPDFSize(attrs) {
    return new PickTrayByPDFSize(attrs);
  }
  static picture(attrs) {
    return new config_Picture(attrs);
  }
  static plaintextMetadata(attrs) {
    return new PlaintextMetadata(attrs);
  }
  static presence(attrs) {
    return new Presence(attrs);
  }
  static present(attrs) {
    return new Present(attrs);
  }
  static print(attrs) {
    return new Print(attrs);
  }
  static printHighQuality(attrs) {
    return new PrintHighQuality(attrs);
  }
  static printScaling(attrs) {
    return new PrintScaling(attrs);
  }
  static printerName(attrs) {
    return new PrinterName(attrs);
  }
  static producer(attrs) {
    return new Producer(attrs);
  }
  static ps(attrs) {
    return new Ps(attrs);
  }
  static range(attrs) {
    return new Range(attrs);
  }
  static record(attrs) {
    return new Record(attrs);
  }
  static relevant(attrs) {
    return new Relevant(attrs);
  }
  static rename(attrs) {
    return new Rename(attrs);
  }
  static renderPolicy(attrs) {
    return new RenderPolicy(attrs);
  }
  static runScripts(attrs) {
    return new RunScripts(attrs);
  }
  static script(attrs) {
    return new config_Script(attrs);
  }
  static scriptModel(attrs) {
    return new ScriptModel(attrs);
  }
  static severity(attrs) {
    return new Severity(attrs);
  }
  static silentPrint(attrs) {
    return new SilentPrint(attrs);
  }
  static staple(attrs) {
    return new Staple(attrs);
  }
  static startNode(attrs) {
    return new StartNode(attrs);
  }
  static startPage(attrs) {
    return new StartPage(attrs);
  }
  static submitFormat(attrs) {
    return new SubmitFormat(attrs);
  }
  static submitUrl(attrs) {
    return new SubmitUrl(attrs);
  }
  static subsetBelow(attrs) {
    return new SubsetBelow(attrs);
  }
  static suppressBanner(attrs) {
    return new SuppressBanner(attrs);
  }
  static tagged(attrs) {
    return new Tagged(attrs);
  }
  static template(attrs) {
    return new config_Template(attrs);
  }
  static templateCache(attrs) {
    return new TemplateCache(attrs);
  }
  static threshold(attrs) {
    return new Threshold(attrs);
  }
  static to(attrs) {
    return new To(attrs);
  }
  static trace(attrs) {
    return new Trace(attrs);
  }
  static transform(attrs) {
    return new Transform(attrs);
  }
  static type(attrs) {
    return new Type(attrs);
  }
  static uri(attrs) {
    return new Uri(attrs);
  }
  static validate(attrs) {
    return new config_Validate(attrs);
  }
  static validateApprovalSignatures(attrs) {
    return new ValidateApprovalSignatures(attrs);
  }
  static validationMessaging(attrs) {
    return new ValidationMessaging(attrs);
  }
  static version(attrs) {
    return new Version(attrs);
  }
  static versionControl(attrs) {
    return new VersionControl(attrs);
  }
  static viewerPreferences(attrs) {
    return new ViewerPreferences(attrs);
  }
  static webClient(attrs) {
    return new WebClient(attrs);
  }
  static whitespace(attrs) {
    return new Whitespace(attrs);
  }
  static window(attrs) {
    return new Window(attrs);
  }
  static xdc(attrs) {
    return new Xdc(attrs);
  }
  static xdp(attrs) {
    return new Xdp(attrs);
  }
  static xsl(attrs) {
    return new Xsl(attrs);
  }
  static zpl(attrs) {
    return new Zpl(attrs);
  }
}

;// CONCATENATED MODULE: ./src/core/xfa/connection_set.js


const CONNECTION_SET_NS_ID = NamespaceIds.connectionSet.id;
class ConnectionSet extends XFAObject {
  constructor(attributes) {
    super(CONNECTION_SET_NS_ID, "connectionSet", true);
    this.wsdlConnection = new XFAObjectArray();
    this.xmlConnection = new XFAObjectArray();
    this.xsdConnection = new XFAObjectArray();
  }
}
class EffectiveInputPolicy extends XFAObject {
  constructor(attributes) {
    super(CONNECTION_SET_NS_ID, "effectiveInputPolicy");
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class EffectiveOutputPolicy extends XFAObject {
  constructor(attributes) {
    super(CONNECTION_SET_NS_ID, "effectiveOutputPolicy");
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class Operation extends StringObject {
  constructor(attributes) {
    super(CONNECTION_SET_NS_ID, "operation");
    this.id = attributes.id || "";
    this.input = attributes.input || "";
    this.name = attributes.name || "";
    this.output = attributes.output || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class RootElement extends StringObject {
  constructor(attributes) {
    super(CONNECTION_SET_NS_ID, "rootElement");
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class SoapAction extends StringObject {
  constructor(attributes) {
    super(CONNECTION_SET_NS_ID, "soapAction");
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class SoapAddress extends StringObject {
  constructor(attributes) {
    super(CONNECTION_SET_NS_ID, "soapAddress");
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class connection_set_Uri extends StringObject {
  constructor(attributes) {
    super(CONNECTION_SET_NS_ID, "uri");
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class WsdlAddress extends StringObject {
  constructor(attributes) {
    super(CONNECTION_SET_NS_ID, "wsdlAddress");
    this.id = attributes.id || "";
    this.name = attributes.name || "";
    this.use = attributes.use || "";
    this.usehref = attributes.usehref || "";
  }
}
class WsdlConnection extends XFAObject {
  constructor(attributes) {
    super(CONNECTION_SET_NS_ID, "wsdlConnection", true);
    this.dataDescription = attributes.dataDescription || "";
    this.name = attributes.name || "";
    this.effectiveInputPolicy = null;
    this.effectiveOutputPolicy = null;
    this.operation = null;
    this.soapAction = null;
    this.soapAddress = null;
    this.wsdlAddress = null;
  }
}
class XmlConnection extends XFAObject {
  constructor(attributes) {
    super(CONNECTION_SET_NS_ID, "xmlConnection", true);
    this.dataDescription = attributes.dataDescription || "";
    this.name = attributes.name || "";
    this.uri = null;
  }
}
class XsdConnection extends XFAObject {
  constructor(attributes) {
    super(CONNECTION_SET_NS_ID, "xsdConnection", true);
    this.dataDescription = attributes.dataDescription || "";
    this.name = attributes.name || "";
    this.rootElement = null;
    this.uri = null;
  }
}
class ConnectionSetNamespace {
  static [$buildXFAObject](name, attributes) {
    if (ConnectionSetNamespace.hasOwnProperty(name)) {
      return ConnectionSetNamespace[name](attributes);
    }
    return undefined;
  }
  static connectionSet(attrs) {
    return new ConnectionSet(attrs);
  }
  static effectiveInputPolicy(attrs) {
    return new EffectiveInputPolicy(attrs);
  }
  static effectiveOutputPolicy(attrs) {
    return new EffectiveOutputPolicy(attrs);
  }
  static operation(attrs) {
    return new Operation(attrs);
  }
  static rootElement(attrs) {
    return new RootElement(attrs);
  }
  static soapAction(attrs) {
    return new SoapAction(attrs);
  }
  static soapAddress(attrs) {
    return new SoapAddress(attrs);
  }
  static uri(attrs) {
    return new connection_set_Uri(attrs);
  }
  static wsdlAddress(attrs) {
    return new WsdlAddress(attrs);
  }
  static wsdlConnection(attrs) {
    return new WsdlConnection(attrs);
  }
  static xmlConnection(attrs) {
    return new XmlConnection(attrs);
  }
  static xsdConnection(attrs) {
    return new XsdConnection(attrs);
  }
}

;// CONCATENATED MODULE: ./src/core/xfa/datasets.js



const DATASETS_NS_ID = NamespaceIds.datasets.id;
class datasets_Data extends XmlObject {
  constructor(attributes) {
    super(DATASETS_NS_ID, "data", attributes);
  }
  [$isNsAgnostic]() {
    return true;
  }
}
class Datasets extends XFAObject {
  constructor(attributes) {
    super(DATASETS_NS_ID, "datasets", true);
    this.data = null;
    this.Signature = null;
  }
  [$onChild](child) {
    const name = child[$nodeName];
    if (name === "data" && child[$namespaceId] === DATASETS_NS_ID || name === "Signature" && child[$namespaceId] === NamespaceIds.signature.id) {
      this[name] = child;
    }
    this[$appendChild](child);
  }
}
class DatasetsNamespace {
  static [$buildXFAObject](name, attributes) {
    if (DatasetsNamespace.hasOwnProperty(name)) {
      return DatasetsNamespace[name](attributes);
    }
    return undefined;
  }
  static datasets(attributes) {
    return new Datasets(attributes);
  }
  static data(attributes) {
    return new datasets_Data(attributes);
  }
}

;// CONCATENATED MODULE: ./src/core/xfa/locale_set.js



const LOCALE_SET_NS_ID = NamespaceIds.localeSet.id;
class CalendarSymbols extends XFAObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "calendarSymbols", true);
    this.name = "gregorian";
    this.dayNames = new XFAObjectArray(2);
    this.eraNames = null;
    this.meridiemNames = null;
    this.monthNames = new XFAObjectArray(2);
  }
}
class CurrencySymbol extends StringObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "currencySymbol");
    this.name = getStringOption(attributes.name, ["symbol", "isoname", "decimal"]);
  }
}
class CurrencySymbols extends XFAObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "currencySymbols", true);
    this.currencySymbol = new XFAObjectArray(3);
  }
}
class DatePattern extends StringObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "datePattern");
    this.name = getStringOption(attributes.name, ["full", "long", "med", "short"]);
  }
}
class DatePatterns extends XFAObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "datePatterns", true);
    this.datePattern = new XFAObjectArray(4);
  }
}
class DateTimeSymbols extends ContentObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "dateTimeSymbols");
  }
}
class Day extends StringObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "day");
  }
}
class DayNames extends XFAObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "dayNames", true);
    this.abbr = getInteger({
      data: attributes.abbr,
      defaultValue: 0,
      validate: x => x === 1
    });
    this.day = new XFAObjectArray(7);
  }
}
class Era extends StringObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "era");
  }
}
class EraNames extends XFAObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "eraNames", true);
    this.era = new XFAObjectArray(2);
  }
}
class locale_set_Locale extends XFAObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "locale", true);
    this.desc = attributes.desc || "";
    this.name = "isoname";
    this.calendarSymbols = null;
    this.currencySymbols = null;
    this.datePatterns = null;
    this.dateTimeSymbols = null;
    this.numberPatterns = null;
    this.numberSymbols = null;
    this.timePatterns = null;
    this.typeFaces = null;
  }
}
class locale_set_LocaleSet extends XFAObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "localeSet", true);
    this.locale = new XFAObjectArray();
  }
}
class Meridiem extends StringObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "meridiem");
  }
}
class MeridiemNames extends XFAObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "meridiemNames", true);
    this.meridiem = new XFAObjectArray(2);
  }
}
class Month extends StringObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "month");
  }
}
class MonthNames extends XFAObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "monthNames", true);
    this.abbr = getInteger({
      data: attributes.abbr,
      defaultValue: 0,
      validate: x => x === 1
    });
    this.month = new XFAObjectArray(12);
  }
}
class NumberPattern extends StringObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "numberPattern");
    this.name = getStringOption(attributes.name, ["full", "long", "med", "short"]);
  }
}
class NumberPatterns extends XFAObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "numberPatterns", true);
    this.numberPattern = new XFAObjectArray(4);
  }
}
class NumberSymbol extends StringObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "numberSymbol");
    this.name = getStringOption(attributes.name, ["decimal", "grouping", "percent", "minus", "zero"]);
  }
}
class NumberSymbols extends XFAObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "numberSymbols", true);
    this.numberSymbol = new XFAObjectArray(5);
  }
}
class TimePattern extends StringObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "timePattern");
    this.name = getStringOption(attributes.name, ["full", "long", "med", "short"]);
  }
}
class TimePatterns extends XFAObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "timePatterns", true);
    this.timePattern = new XFAObjectArray(4);
  }
}
class TypeFace extends XFAObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "typeFace", true);
    this.name = attributes.name | "";
  }
}
class TypeFaces extends XFAObject {
  constructor(attributes) {
    super(LOCALE_SET_NS_ID, "typeFaces", true);
    this.typeFace = new XFAObjectArray();
  }
}
class LocaleSetNamespace {
  static [$buildXFAObject](name, attributes) {
    if (LocaleSetNamespace.hasOwnProperty(name)) {
      return LocaleSetNamespace[name](attributes);
    }
    return undefined;
  }
  static calendarSymbols(attrs) {
    return new CalendarSymbols(attrs);
  }
  static currencySymbol(attrs) {
    return new CurrencySymbol(attrs);
  }
  static currencySymbols(attrs) {
    return new CurrencySymbols(attrs);
  }
  static datePattern(attrs) {
    return new DatePattern(attrs);
  }
  static datePatterns(attrs) {
    return new DatePatterns(attrs);
  }
  static dateTimeSymbols(attrs) {
    return new DateTimeSymbols(attrs);
  }
  static day(attrs) {
    return new Day(attrs);
  }
  static dayNames(attrs) {
    return new DayNames(attrs);
  }
  static era(attrs) {
    return new Era(attrs);
  }
  static eraNames(attrs) {
    return new EraNames(attrs);
  }
  static locale(attrs) {
    return new locale_set_Locale(attrs);
  }
  static localeSet(attrs) {
    return new locale_set_LocaleSet(attrs);
  }
  static meridiem(attrs) {
    return new Meridiem(attrs);
  }
  static meridiemNames(attrs) {
    return new MeridiemNames(attrs);
  }
  static month(attrs) {
    return new Month(attrs);
  }
  static monthNames(attrs) {
    return new MonthNames(attrs);
  }
  static numberPattern(attrs) {
    return new NumberPattern(attrs);
  }
  static numberPatterns(attrs) {
    return new NumberPatterns(attrs);
  }
  static numberSymbol(attrs) {
    return new NumberSymbol(attrs);
  }
  static numberSymbols(attrs) {
    return new NumberSymbols(attrs);
  }
  static timePattern(attrs) {
    return new TimePattern(attrs);
  }
  static timePatterns(attrs) {
    return new TimePatterns(attrs);
  }
  static typeFace(attrs) {
    return new TypeFace(attrs);
  }
  static typeFaces(attrs) {
    return new TypeFaces(attrs);
  }
}

;// CONCATENATED MODULE: ./src/core/xfa/signature.js


const SIGNATURE_NS_ID = NamespaceIds.signature.id;
class signature_Signature extends XFAObject {
  constructor(attributes) {
    super(SIGNATURE_NS_ID, "signature", true);
  }
}
class SignatureNamespace {
  static [$buildXFAObject](name, attributes) {
    if (SignatureNamespace.hasOwnProperty(name)) {
      return SignatureNamespace[name](attributes);
    }
    return undefined;
  }
  static signature(attributes) {
    return new signature_Signature(attributes);
  }
}

;// CONCATENATED MODULE: ./src/core/xfa/stylesheet.js


const STYLESHEET_NS_ID = NamespaceIds.stylesheet.id;
class Stylesheet extends XFAObject {
  constructor(attributes) {
    super(STYLESHEET_NS_ID, "stylesheet", true);
  }
}
class StylesheetNamespace {
  static [$buildXFAObject](name, attributes) {
    if (StylesheetNamespace.hasOwnProperty(name)) {
      return StylesheetNamespace[name](attributes);
    }
    return undefined;
  }
  static stylesheet(attributes) {
    return new Stylesheet(attributes);
  }
}

;// CONCATENATED MODULE: ./src/core/xfa/xdp.js



const XDP_NS_ID = NamespaceIds.xdp.id;
class xdp_Xdp extends XFAObject {
  constructor(attributes) {
    super(XDP_NS_ID, "xdp", true);
    this.uuid = attributes.uuid || "";
    this.timeStamp = attributes.timeStamp || "";
    this.config = null;
    this.connectionSet = null;
    this.datasets = null;
    this.localeSet = null;
    this.stylesheet = new XFAObjectArray();
    this.template = null;
  }
  [$onChildCheck](child) {
    const ns = NamespaceIds[child[$nodeName]];
    return ns && child[$namespaceId] === ns.id;
  }
}
class XdpNamespace {
  static [$buildXFAObject](name, attributes) {
    if (XdpNamespace.hasOwnProperty(name)) {
      return XdpNamespace[name](attributes);
    }
    return undefined;
  }
  static xdp(attributes) {
    return new xdp_Xdp(attributes);
  }
}

;// CONCATENATED MODULE: ./src/core/xfa/xhtml.js





const XHTML_NS_ID = NamespaceIds.xhtml.id;
const $richText = Symbol();
const VALID_STYLES = new Set(["color", "font", "font-family", "font-size", "font-stretch", "font-style", "font-weight", "margin", "margin-bottom", "margin-left", "margin-right", "margin-top", "letter-spacing", "line-height", "orphans", "page-break-after", "page-break-before", "page-break-inside", "tab-interval", "tab-stop", "text-align", "text-decoration", "text-indent", "vertical-align", "widows", "kerning-mode", "xfa-font-horizontal-scale", "xfa-font-vertical-scale", "xfa-spacerun", "xfa-tab-stops"]);
const StyleMapping = new Map([["page-break-after", "breakAfter"], ["page-break-before", "breakBefore"], ["page-break-inside", "breakInside"], ["kerning-mode", value => value === "none" ? "none" : "normal"], ["xfa-font-horizontal-scale", value => `scaleX(${Math.max(0, Math.min(parseInt(value) / 100)).toFixed(2)})`], ["xfa-font-vertical-scale", value => `scaleY(${Math.max(0, Math.min(parseInt(value) / 100)).toFixed(2)})`], ["xfa-spacerun", ""], ["xfa-tab-stops", ""], ["font-size", (value, original) => {
  value = original.fontSize = getMeasurement(value);
  return measureToString(0.99 * value);
}], ["letter-spacing", value => measureToString(getMeasurement(value))], ["line-height", value => measureToString(getMeasurement(value))], ["margin", value => measureToString(getMeasurement(value))], ["margin-bottom", value => measureToString(getMeasurement(value))], ["margin-left", value => measureToString(getMeasurement(value))], ["margin-right", value => measureToString(getMeasurement(value))], ["margin-top", value => measureToString(getMeasurement(value))], ["text-indent", value => measureToString(getMeasurement(value))], ["font-family", value => value], ["vertical-align", value => measureToString(getMeasurement(value))]]);
const spacesRegExp = /\s+/g;
const crlfRegExp = /[\r\n]+/g;
const crlfForRichTextRegExp = /\r\n?/g;
function mapStyle(styleStr, node, richText) {
  const style = Object.create(null);
  if (!styleStr) {
    return style;
  }
  const original = Object.create(null);
  for (const [key, value] of styleStr.split(";").map(s => s.split(":", 2))) {
    const mapping = StyleMapping.get(key);
    if (mapping === "") {
      continue;
    }
    let newValue = value;
    if (mapping) {
      newValue = typeof mapping === "string" ? mapping : mapping(value, original);
    }
    if (key.endsWith("scale")) {
      style.transform = style.transform ? `${style[key]} ${newValue}` : newValue;
    } else {
      style[key.replaceAll(/-([a-zA-Z])/g, (_, x) => x.toUpperCase())] = newValue;
    }
  }
  if (style.fontFamily) {
    setFontFamily({
      typeface: style.fontFamily,
      weight: style.fontWeight || "normal",
      posture: style.fontStyle || "normal",
      size: original.fontSize || 0
    }, node, node[$globalData].fontFinder, style);
  }
  if (richText && style.verticalAlign && style.verticalAlign !== "0px" && style.fontSize) {
    const SUB_SUPER_SCRIPT_FACTOR = 0.583;
    const VERTICAL_FACTOR = 0.333;
    const fontSize = getMeasurement(style.fontSize);
    style.fontSize = measureToString(fontSize * SUB_SUPER_SCRIPT_FACTOR);
    style.verticalAlign = measureToString(Math.sign(getMeasurement(style.verticalAlign)) * fontSize * VERTICAL_FACTOR);
  }
  if (richText && style.fontSize) {
    style.fontSize = `calc(${style.fontSize} * var(--scale-factor))`;
  }
  fixTextIndent(style);
  return style;
}
function checkStyle(node) {
  if (!node.style) {
    return "";
  }
  return node.style.trim().split(/\s*;\s*/).filter(s => !!s).map(s => s.split(/\s*:\s*/, 2)).filter(([key, value]) => {
    if (key === "font-family") {
      node[$globalData].usedTypefaces.add(value);
    }
    return VALID_STYLES.has(key);
  }).map(kv => kv.join(":")).join(";");
}
const NoWhites = new Set(["body", "html"]);
class XhtmlObject extends XmlObject {
  constructor(attributes, name) {
    super(XHTML_NS_ID, name);
    this[$richText] = false;
    this.style = attributes.style || "";
  }
  [$clean](builder) {
    super[$clean](builder);
    this.style = checkStyle(this);
  }
  [$acceptWhitespace]() {
    return !NoWhites.has(this[$nodeName]);
  }
  [$onText](str, richText = false) {
    if (!richText) {
      str = str.replaceAll(crlfRegExp, "");
      if (!this.style.includes("xfa-spacerun:yes")) {
        str = str.replaceAll(spacesRegExp, " ");
      }
    } else {
      this[$richText] = true;
    }
    if (str) {
      this[$content] += str;
    }
  }
  [$pushGlyphs](measure, mustPop = true) {
    const xfaFont = Object.create(null);
    const margin = {
      top: NaN,
      bottom: NaN,
      left: NaN,
      right: NaN
    };
    let lineHeight = null;
    for (const [key, value] of this.style.split(";").map(s => s.split(":", 2))) {
      switch (key) {
        case "font-family":
          xfaFont.typeface = stripQuotes(value);
          break;
        case "font-size":
          xfaFont.size = getMeasurement(value);
          break;
        case "font-weight":
          xfaFont.weight = value;
          break;
        case "font-style":
          xfaFont.posture = value;
          break;
        case "letter-spacing":
          xfaFont.letterSpacing = getMeasurement(value);
          break;
        case "margin":
          const values = value.split(/ \t/).map(x => getMeasurement(x));
          switch (values.length) {
            case 1:
              margin.top = margin.bottom = margin.left = margin.right = values[0];
              break;
            case 2:
              margin.top = margin.bottom = values[0];
              margin.left = margin.right = values[1];
              break;
            case 3:
              margin.top = values[0];
              margin.bottom = values[2];
              margin.left = margin.right = values[1];
              break;
            case 4:
              margin.top = values[0];
              margin.left = values[1];
              margin.bottom = values[2];
              margin.right = values[3];
              break;
          }
          break;
        case "margin-top":
          margin.top = getMeasurement(value);
          break;
        case "margin-bottom":
          margin.bottom = getMeasurement(value);
          break;
        case "margin-left":
          margin.left = getMeasurement(value);
          break;
        case "margin-right":
          margin.right = getMeasurement(value);
          break;
        case "line-height":
          lineHeight = getMeasurement(value);
          break;
      }
    }
    measure.pushData(xfaFont, margin, lineHeight);
    if (this[$content]) {
      measure.addString(this[$content]);
    } else {
      for (const child of this[$getChildren]()) {
        if (child[$nodeName] === "#text") {
          measure.addString(child[$content]);
          continue;
        }
        child[$pushGlyphs](measure);
      }
    }
    if (mustPop) {
      measure.popFont();
    }
  }
  [$toHTML](availableSpace) {
    const children = [];
    this[$extra] = {
      children
    };
    this[$childrenToHTML]({});
    if (children.length === 0 && !this[$content]) {
      return HTMLResult.EMPTY;
    }
    let value;
    if (this[$richText]) {
      value = this[$content] ? this[$content].replaceAll(crlfForRichTextRegExp, "\n") : undefined;
    } else {
      value = this[$content] || undefined;
    }
    return HTMLResult.success({
      name: this[$nodeName],
      attributes: {
        href: this.href,
        style: mapStyle(this.style, this, this[$richText])
      },
      children,
      value
    });
  }
}
class A extends XhtmlObject {
  constructor(attributes) {
    super(attributes, "a");
    this.href = fixURL(attributes.href) || "";
  }
}
class B extends XhtmlObject {
  constructor(attributes) {
    super(attributes, "b");
  }
  [$pushGlyphs](measure) {
    measure.pushFont({
      weight: "bold"
    });
    super[$pushGlyphs](measure);
    measure.popFont();
  }
}
class Body extends XhtmlObject {
  constructor(attributes) {
    super(attributes, "body");
  }
  [$toHTML](availableSpace) {
    const res = super[$toHTML](availableSpace);
    const {
      html
    } = res;
    if (!html) {
      return HTMLResult.EMPTY;
    }
    html.name = "div";
    html.attributes.class = ["xfaRich"];
    return res;
  }
}
class Br extends XhtmlObject {
  constructor(attributes) {
    super(attributes, "br");
  }
  [$text]() {
    return "\n";
  }
  [$pushGlyphs](measure) {
    measure.addString("\n");
  }
  [$toHTML](availableSpace) {
    return HTMLResult.success({
      name: "br"
    });
  }
}
class Html extends XhtmlObject {
  constructor(attributes) {
    super(attributes, "html");
  }
  [$toHTML](availableSpace) {
    const children = [];
    this[$extra] = {
      children
    };
    this[$childrenToHTML]({});
    if (children.length === 0) {
      return HTMLResult.success({
        name: "div",
        attributes: {
          class: ["xfaRich"],
          style: {}
        },
        value: this[$content] || ""
      });
    }
    if (children.length === 1) {
      const child = children[0];
      if (child.attributes?.class.includes("xfaRich")) {
        return HTMLResult.success(child);
      }
    }
    return HTMLResult.success({
      name: "div",
      attributes: {
        class: ["xfaRich"],
        style: {}
      },
      children
    });
  }
}
class I extends XhtmlObject {
  constructor(attributes) {
    super(attributes, "i");
  }
  [$pushGlyphs](measure) {
    measure.pushFont({
      posture: "italic"
    });
    super[$pushGlyphs](measure);
    measure.popFont();
  }
}
class Li extends XhtmlObject {
  constructor(attributes) {
    super(attributes, "li");
  }
}
class Ol extends XhtmlObject {
  constructor(attributes) {
    super(attributes, "ol");
  }
}
class P extends XhtmlObject {
  constructor(attributes) {
    super(attributes, "p");
  }
  [$pushGlyphs](measure) {
    super[$pushGlyphs](measure, false);
    measure.addString("\n");
    measure.addPara();
    measure.popFont();
  }
  [$text]() {
    const siblings = this[$getParent]()[$getChildren]();
    if (siblings.at(-1) === this) {
      return super[$text]();
    }
    return super[$text]() + "\n";
  }
}
class Span extends XhtmlObject {
  constructor(attributes) {
    super(attributes, "span");
  }
}
class Sub extends XhtmlObject {
  constructor(attributes) {
    super(attributes, "sub");
  }
}
class Sup extends XhtmlObject {
  constructor(attributes) {
    super(attributes, "sup");
  }
}
class Ul extends XhtmlObject {
  constructor(attributes) {
    super(attributes, "ul");
  }
}
class XhtmlNamespace {
  static [$buildXFAObject](name, attributes) {
    if (XhtmlNamespace.hasOwnProperty(name)) {
      return XhtmlNamespace[name](attributes);
    }
    return undefined;
  }
  static a(attributes) {
    return new A(attributes);
  }
  static b(attributes) {
    return new B(attributes);
  }
  static body(attributes) {
    return new Body(attributes);
  }
  static br(attributes) {
    return new Br(attributes);
  }
  static html(attributes) {
    return new Html(attributes);
  }
  static i(attributes) {
    return new I(attributes);
  }
  static li(attributes) {
    return new Li(attributes);
  }
  static ol(attributes) {
    return new Ol(attributes);
  }
  static p(attributes) {
    return new P(attributes);
  }
  static span(attributes) {
    return new Span(attributes);
  }
  static sub(attributes) {
    return new Sub(attributes);
  }
  static sup(attributes) {
    return new Sup(attributes);
  }
  static ul(attributes) {
    return new Ul(attributes);
  }
}

;// CONCATENATED MODULE: ./src/core/xfa/setup.js









const NamespaceSetUp = {
  config: ConfigNamespace,
  connection: ConnectionSetNamespace,
  datasets: DatasetsNamespace,
  localeSet: LocaleSetNamespace,
  signature: SignatureNamespace,
  stylesheet: StylesheetNamespace,
  template: TemplateNamespace,
  xdp: XdpNamespace,
  xhtml: XhtmlNamespace
};

;// CONCATENATED MODULE: ./src/core/xfa/unknown.js


class UnknownNamespace {
  constructor(nsId) {
    this.namespaceId = nsId;
  }
  [$buildXFAObject](name, attributes) {
    return new XmlObject(this.namespaceId, name, attributes);
  }
}

;// CONCATENATED MODULE: ./src/core/xfa/builder.js







class Root extends XFAObject {
  constructor(ids) {
    super(-1, "root", Object.create(null));
    this.element = null;
    this[$ids] = ids;
  }
  [$onChild](child) {
    this.element = child;
    return true;
  }
  [$finalize]() {
    super[$finalize]();
    if (this.element.template instanceof Template) {
      this[$ids].set($root, this.element);
      this.element.template[$resolvePrototypes](this[$ids]);
      this.element.template[$ids] = this[$ids];
    }
  }
}
class Empty extends XFAObject {
  constructor() {
    super(-1, "", Object.create(null));
  }
  [$onChild](_) {
    return false;
  }
}
class Builder {
  constructor(rootNameSpace = null) {
    this._namespaceStack = [];
    this._nsAgnosticLevel = 0;
    this._namespacePrefixes = new Map();
    this._namespaces = new Map();
    this._nextNsId = Math.max(...Object.values(NamespaceIds).map(({
      id
    }) => id));
    this._currentNamespace = rootNameSpace || new UnknownNamespace(++this._nextNsId);
  }
  buildRoot(ids) {
    return new Root(ids);
  }
  build({
    nsPrefix,
    name,
    attributes,
    namespace,
    prefixes
  }) {
    const hasNamespaceDef = namespace !== null;
    if (hasNamespaceDef) {
      this._namespaceStack.push(this._currentNamespace);
      this._currentNamespace = this._searchNamespace(namespace);
    }
    if (prefixes) {
      this._addNamespacePrefix(prefixes);
    }
    if (attributes.hasOwnProperty($nsAttributes)) {
      const dataTemplate = NamespaceSetUp.datasets;
      const nsAttrs = attributes[$nsAttributes];
      let xfaAttrs = null;
      for (const [ns, attrs] of Object.entries(nsAttrs)) {
        const nsToUse = this._getNamespaceToUse(ns);
        if (nsToUse === dataTemplate) {
          xfaAttrs = {
            xfa: attrs
          };
          break;
        }
      }
      if (xfaAttrs) {
        attributes[$nsAttributes] = xfaAttrs;
      } else {
        delete attributes[$nsAttributes];
      }
    }
    const namespaceToUse = this._getNamespaceToUse(nsPrefix);
    const node = namespaceToUse?.[$buildXFAObject](name, attributes) || new Empty();
    if (node[$isNsAgnostic]()) {
      this._nsAgnosticLevel++;
    }
    if (hasNamespaceDef || prefixes || node[$isNsAgnostic]()) {
      node[$cleanup] = {
        hasNamespace: hasNamespaceDef,
        prefixes,
        nsAgnostic: node[$isNsAgnostic]()
      };
    }
    return node;
  }
  isNsAgnostic() {
    return this._nsAgnosticLevel > 0;
  }
  _searchNamespace(nsName) {
    let ns = this._namespaces.get(nsName);
    if (ns) {
      return ns;
    }
    for (const [name, {
      check
    }] of Object.entries(NamespaceIds)) {
      if (check(nsName)) {
        ns = NamespaceSetUp[name];
        if (ns) {
          this._namespaces.set(nsName, ns);
          return ns;
        }
        break;
      }
    }
    ns = new UnknownNamespace(++this._nextNsId);
    this._namespaces.set(nsName, ns);
    return ns;
  }
  _addNamespacePrefix(prefixes) {
    for (const {
      prefix,
      value
    } of prefixes) {
      const namespace = this._searchNamespace(value);
      let prefixStack = this._namespacePrefixes.get(prefix);
      if (!prefixStack) {
        prefixStack = [];
        this._namespacePrefixes.set(prefix, prefixStack);
      }
      prefixStack.push(namespace);
    }
  }
  _getNamespaceToUse(prefix) {
    if (!prefix) {
      return this._currentNamespace;
    }
    const prefixStack = this._namespacePrefixes.get(prefix);
    if (prefixStack?.length > 0) {
      return prefixStack.at(-1);
    }
    warn(`Unknown namespace prefix: ${prefix}.`);
    return null;
  }
  clean(data) {
    const {
      hasNamespace,
      prefixes,
      nsAgnostic
    } = data;
    if (hasNamespace) {
      this._currentNamespace = this._namespaceStack.pop();
    }
    if (prefixes) {
      prefixes.forEach(({
        prefix
      }) => {
        this._namespacePrefixes.get(prefix).pop();
      });
    }
    if (nsAgnostic) {
      this._nsAgnosticLevel--;
    }
  }
}

;// CONCATENATED MODULE: ./src/core/xfa/parser.js




class XFAParser extends XMLParserBase {
  constructor(rootNameSpace = null, richText = false) {
    super();
    this._builder = new Builder(rootNameSpace);
    this._stack = [];
    this._globalData = {
      usedTypefaces: new Set()
    };
    this._ids = new Map();
    this._current = this._builder.buildRoot(this._ids);
    this._errorCode = XMLParserErrorCode.NoError;
    this._whiteRegex = /^\s+$/;
    this._nbsps = /\xa0+/g;
    this._richText = richText;
  }
  parse(data) {
    this.parseXml(data);
    if (this._errorCode !== XMLParserErrorCode.NoError) {
      return undefined;
    }
    this._current[$finalize]();
    return this._current.element;
  }
  onText(text) {
    text = text.replace(this._nbsps, match => match.slice(1) + " ");
    if (this._richText || this._current[$acceptWhitespace]()) {
      this._current[$onText](text, this._richText);
      return;
    }
    if (this._whiteRegex.test(text)) {
      return;
    }
    this._current[$onText](text.trim());
  }
  onCdata(text) {
    this._current[$onText](text);
  }
  _mkAttributes(attributes, tagName) {
    let namespace = null;
    let prefixes = null;
    const attributeObj = Object.create({});
    for (const {
      name,
      value
    } of attributes) {
      if (name === "xmlns") {
        if (!namespace) {
          namespace = value;
        } else {
          warn(`XFA - multiple namespace definition in <${tagName}>`);
        }
      } else if (name.startsWith("xmlns:")) {
        const prefix = name.substring("xmlns:".length);
        if (!prefixes) {
          prefixes = [];
        }
        prefixes.push({
          prefix,
          value
        });
      } else {
        const i = name.indexOf(":");
        if (i === -1) {
          attributeObj[name] = value;
        } else {
          let nsAttrs = attributeObj[$nsAttributes];
          if (!nsAttrs) {
            nsAttrs = attributeObj[$nsAttributes] = Object.create(null);
          }
          const [ns, attrName] = [name.slice(0, i), name.slice(i + 1)];
          const attrs = nsAttrs[ns] ||= Object.create(null);
          attrs[attrName] = value;
        }
      }
    }
    return [namespace, prefixes, attributeObj];
  }
  _getNameAndPrefix(name, nsAgnostic) {
    const i = name.indexOf(":");
    if (i === -1) {
      return [name, null];
    }
    return [name.substring(i + 1), nsAgnostic ? "" : name.substring(0, i)];
  }
  onBeginElement(tagName, attributes, isEmpty) {
    const [namespace, prefixes, attributesObj] = this._mkAttributes(attributes, tagName);
    const [name, nsPrefix] = this._getNameAndPrefix(tagName, this._builder.isNsAgnostic());
    const node = this._builder.build({
      nsPrefix,
      name,
      attributes: attributesObj,
      namespace,
      prefixes
    });
    node[$globalData] = this._globalData;
    if (isEmpty) {
      node[$finalize]();
      if (this._current[$onChild](node)) {
        node[$setId](this._ids);
      }
      node[$clean](this._builder);
      return;
    }
    this._stack.push(this._current);
    this._current = node;
  }
  onEndElement(name) {
    const node = this._current;
    if (node[$isCDATAXml]() && typeof node[$content] === "string") {
      const parser = new XFAParser();
      parser._globalData = this._globalData;
      const root = parser.parse(node[$content]);
      node[$content] = null;
      node[$onChild](root);
    }
    node[$finalize]();
    this._current = this._stack.pop();
    if (this._current[$onChild](node)) {
      node[$setId](this._ids);
    }
    node[$clean](this._builder);
  }
  onError(code) {
    this._errorCode = code;
  }
}

;// CONCATENATED MODULE: ./src/core/xfa/factory.js








class XFAFactory {
  constructor(data) {
    try {
      this.root = new XFAParser().parse(XFAFactory._createDocument(data));
      const binder = new Binder(this.root);
      this.form = binder.bind();
      this.dataHandler = new DataHandler(this.root, binder.getData());
      this.form[$globalData].template = this.form;
    } catch (e) {
      warn(`XFA - an error occurred during parsing and binding: ${e}`);
    }
  }
  isValid() {
    return this.root && this.form;
  }
  _createPagesHelper() {
    const iterator = this.form[$toPages]();
    return new Promise((resolve, reject) => {
      const nextIteration = () => {
        try {
          const value = iterator.next();
          if (value.done) {
            resolve(value.value);
          } else {
            setTimeout(nextIteration, 0);
          }
        } catch (e) {
          reject(e);
        }
      };
      setTimeout(nextIteration, 0);
    });
  }
  async _createPages() {
    try {
      this.pages = await this._createPagesHelper();
      this.dims = this.pages.children.map(c => {
        const {
          width,
          height
        } = c.attributes.style;
        return [0, 0, parseInt(width), parseInt(height)];
      });
    } catch (e) {
      warn(`XFA - an error occurred during layout: ${e}`);
    }
  }
  getBoundingBox(pageIndex) {
    return this.dims[pageIndex];
  }
  async getNumPages() {
    if (!this.pages) {
      await this._createPages();
    }
    return this.dims.length;
  }
  setImages(images) {
    this.form[$globalData].images = images;
  }
  setFonts(fonts) {
    this.form[$globalData].fontFinder = new FontFinder(fonts);
    const missingFonts = [];
    for (let typeface of this.form[$globalData].usedTypefaces) {
      typeface = stripQuotes(typeface);
      const font = this.form[$globalData].fontFinder.find(typeface);
      if (!font) {
        missingFonts.push(typeface);
      }
    }
    if (missingFonts.length > 0) {
      return missingFonts;
    }
    return null;
  }
  appendFonts(fonts, reallyMissingFonts) {
    this.form[$globalData].fontFinder.add(fonts, reallyMissingFonts);
  }
  async getPages() {
    if (!this.pages) {
      await this._createPages();
    }
    const pages = this.pages;
    this.pages = null;
    return pages;
  }
  serializeData(storage) {
    return this.dataHandler.serialize(storage);
  }
  static _createDocument(data) {
    if (!data["/xdp:xdp"]) {
      return data["xdp:xdp"];
    }
    return Object.values(data).join("");
  }
  static getRichTextAsHtml(rc) {
    if (!rc || typeof rc !== "string") {
      return null;
    }
    try {
      let root = new XFAParser(XhtmlNamespace, true).parse(rc);
      if (!["body", "xhtml"].includes(root[$nodeName])) {
        const newRoot = XhtmlNamespace.body({});
        newRoot[$appendChild](root);
        root = newRoot;
      }
      const result = root[$toHTML]();
      if (!result.success) {
        return null;
      }
      const {
        html
      } = result;
      const {
        attributes
      } = html;
      if (attributes) {
        if (attributes.class) {
          attributes.class = attributes.class.filter(attr => !attr.startsWith("xfa"));
        }
        attributes.dir = "auto";
      }
      return {
        html,
        str: root[$text]()
      };
    } catch (e) {
      warn(`XFA - an error occurred during parsing of rich text: ${e}`);
    }
    return null;
  }
}

;// CONCATENATED MODULE: ./src/core/annotation.js















class AnnotationFactory {
  static createGlobals(pdfManager) {
    return Promise.all([pdfManager.ensureCatalog("acroForm"), pdfManager.ensureDoc("xfaDatasets"), pdfManager.ensureCatalog("structTreeRoot"), pdfManager.ensureCatalog("baseUrl"), pdfManager.ensureCatalog("attachments")]).then(([acroForm, xfaDatasets, structTreeRoot, baseUrl, attachments]) => {
      return {
        pdfManager,
        acroForm: acroForm instanceof Dict ? acroForm : Dict.empty,
        xfaDatasets,
        structTreeRoot,
        baseUrl,
        attachments
      };
    }, reason => {
      warn(`createGlobals: "${reason}".`);
      return null;
    });
  }
  static async create(xref, ref, annotationGlobals, idFactory, collectFields, pageRef) {
    const pageIndex = collectFields ? await this._getPageIndex(xref, ref, annotationGlobals.pdfManager) : null;
    return annotationGlobals.pdfManager.ensure(this, "_create", [xref, ref, annotationGlobals, idFactory, collectFields, pageIndex, pageRef]);
  }
  static _create(xref, ref, annotationGlobals, idFactory, collectFields = false, pageIndex = null, pageRef = null) {
    const dict = xref.fetchIfRef(ref);
    if (!(dict instanceof Dict)) {
      return undefined;
    }
    const {
      acroForm,
      pdfManager
    } = annotationGlobals;
    const id = ref instanceof Ref ? ref.toString() : `annot_${idFactory.createObjId()}`;
    let subtype = dict.get("Subtype");
    subtype = subtype instanceof Name ? subtype.name : null;
    const parameters = {
      xref,
      ref,
      dict,
      subtype,
      id,
      annotationGlobals,
      collectFields,
      needAppearances: !collectFields && acroForm.get("NeedAppearances") === true,
      pageIndex,
      evaluatorOptions: pdfManager.evaluatorOptions,
      pageRef
    };
    switch (subtype) {
      case "Link":
        return new LinkAnnotation(parameters);
      case "Text":
        return new TextAnnotation(parameters);
      case "Widget":
        let fieldType = getInheritableProperty({
          dict,
          key: "FT"
        });
        fieldType = fieldType instanceof Name ? fieldType.name : null;
        switch (fieldType) {
          case "Tx":
            return new TextWidgetAnnotation(parameters);
          case "Btn":
            return new ButtonWidgetAnnotation(parameters);
          case "Ch":
            return new ChoiceWidgetAnnotation(parameters);
          case "Sig":
            return new SignatureWidgetAnnotation(parameters);
        }
        warn(`Unimplemented widget field type "${fieldType}", ` + "falling back to base field type.");
        return new WidgetAnnotation(parameters);
      case "Popup":
        return new PopupAnnotation(parameters);
      case "FreeText":
        return new FreeTextAnnotation(parameters);
      case "Line":
        return new LineAnnotation(parameters);
      case "Square":
        return new SquareAnnotation(parameters);
      case "Circle":
        return new CircleAnnotation(parameters);
      case "PolyLine":
        return new PolylineAnnotation(parameters);
      case "Polygon":
        return new PolygonAnnotation(parameters);
      case "Caret":
        return new CaretAnnotation(parameters);
      case "Ink":
        return new InkAnnotation(parameters);
      case "Highlight":
        return new HighlightAnnotation(parameters);
      case "Underline":
        return new UnderlineAnnotation(parameters);
      case "Squiggly":
        return new SquigglyAnnotation(parameters);
      case "StrikeOut":
        return new StrikeOutAnnotation(parameters);
      case "Stamp":
        return new StampAnnotation(parameters);
      case "FileAttachment":
        return new FileAttachmentAnnotation(parameters);
      default:
        if (!collectFields) {
          if (!subtype) {
            warn("Annotation is missing the required /Subtype.");
          } else {
            warn(`Unimplemented annotation type "${subtype}", ` + "falling back to base annotation.");
          }
        }
        return new Annotation(parameters);
    }
  }
  static async _getPageIndex(xref, ref, pdfManager) {
    try {
      const annotDict = await xref.fetchIfRefAsync(ref);
      if (!(annotDict instanceof Dict)) {
        return -1;
      }
      const pageRef = annotDict.getRaw("P");
      if (pageRef instanceof Ref) {
        try {
          const pageIndex = await pdfManager.ensureCatalog("getPageIndex", [pageRef]);
          return pageIndex;
        } catch (ex) {
          info(`_getPageIndex -- not a valid page reference: "${ex}".`);
        }
      }
      if (annotDict.has("Kids")) {
        return -1;
      }
      const numPages = await pdfManager.ensureDoc("numPages");
      for (let pageIndex = 0; pageIndex < numPages; pageIndex++) {
        const page = await pdfManager.getPage(pageIndex);
        const annotations = await pdfManager.ensure(page, "annotations");
        for (const annotRef of annotations) {
          if (annotRef instanceof Ref && isRefsEqual(annotRef, ref)) {
            return pageIndex;
          }
        }
      }
    } catch (ex) {
      warn(`_getPageIndex: "${ex}".`);
    }
    return -1;
  }
  static generateImages(annotations, xref, isOffscreenCanvasSupported) {
    if (!isOffscreenCanvasSupported) {
      warn("generateImages: OffscreenCanvas is not supported, cannot save or print some annotations with images.");
      return null;
    }
    let imagePromises;
    for (const {
      bitmapId,
      bitmap
    } of annotations) {
      if (!bitmap) {
        continue;
      }
      imagePromises ||= new Map();
      imagePromises.set(bitmapId, StampAnnotation.createImage(bitmap, xref));
    }
    return imagePromises;
  }
  static async saveNewAnnotations(evaluator, task, annotations, imagePromises) {
    const xref = evaluator.xref;
    let baseFontRef;
    const dependencies = [];
    const promises = [];
    const {
      isOffscreenCanvasSupported
    } = evaluator.options;
    for (const annotation of annotations) {
      if (annotation.deleted) {
        continue;
      }
      switch (annotation.annotationType) {
        case AnnotationEditorType.FREETEXT:
          if (!baseFontRef) {
            const baseFont = new Dict(xref);
            baseFont.set("BaseFont", Name.get("Helvetica"));
            baseFont.set("Type", Name.get("Font"));
            baseFont.set("Subtype", Name.get("Type1"));
            baseFont.set("Encoding", Name.get("WinAnsiEncoding"));
            const buffer = [];
            baseFontRef = xref.getNewTemporaryRef();
            await writeObject(baseFontRef, baseFont, buffer, xref);
            dependencies.push({
              ref: baseFontRef,
              data: buffer.join("")
            });
          }
          promises.push(FreeTextAnnotation.createNewAnnotation(xref, annotation, dependencies, {
            evaluator,
            task,
            baseFontRef
          }));
          break;
        case AnnotationEditorType.HIGHLIGHT:
          if (annotation.quadPoints) {
            promises.push(HighlightAnnotation.createNewAnnotation(xref, annotation, dependencies));
          } else {
            promises.push(InkAnnotation.createNewAnnotation(xref, annotation, dependencies));
          }
          break;
        case AnnotationEditorType.INK:
          promises.push(InkAnnotation.createNewAnnotation(xref, annotation, dependencies));
          break;
        case AnnotationEditorType.STAMP:
          if (!isOffscreenCanvasSupported) {
            break;
          }
          const image = await imagePromises.get(annotation.bitmapId);
          if (image.imageStream) {
            const {
              imageStream,
              smaskStream
            } = image;
            const buffer = [];
            if (smaskStream) {
              const smaskRef = xref.getNewTemporaryRef();
              await writeObject(smaskRef, smaskStream, buffer, xref);
              dependencies.push({
                ref: smaskRef,
                data: buffer.join("")
              });
              imageStream.dict.set("SMask", smaskRef);
              buffer.length = 0;
            }
            const imageRef = image.imageRef = xref.getNewTemporaryRef();
            await writeObject(imageRef, imageStream, buffer, xref);
            dependencies.push({
              ref: imageRef,
              data: buffer.join("")
            });
            image.imageStream = image.smaskStream = null;
          }
          promises.push(StampAnnotation.createNewAnnotation(xref, annotation, dependencies, {
            image
          }));
          break;
      }
    }
    return {
      annotations: await Promise.all(promises),
      dependencies
    };
  }
  static async printNewAnnotations(annotationGlobals, evaluator, task, annotations, imagePromises) {
    if (!annotations) {
      return null;
    }
    const {
      options,
      xref
    } = evaluator;
    const promises = [];
    for (const annotation of annotations) {
      if (annotation.deleted) {
        continue;
      }
      switch (annotation.annotationType) {
        case AnnotationEditorType.FREETEXT:
          promises.push(FreeTextAnnotation.createNewPrintAnnotation(annotationGlobals, xref, annotation, {
            evaluator,
            task,
            evaluatorOptions: options
          }));
          break;
        case AnnotationEditorType.HIGHLIGHT:
          if (annotation.quadPoints) {
            promises.push(HighlightAnnotation.createNewPrintAnnotation(annotationGlobals, xref, annotation, {
              evaluatorOptions: options
            }));
          } else {
            promises.push(InkAnnotation.createNewPrintAnnotation(annotationGlobals, xref, annotation, {
              evaluatorOptions: options
            }));
          }
          break;
        case AnnotationEditorType.INK:
          promises.push(InkAnnotation.createNewPrintAnnotation(annotationGlobals, xref, annotation, {
            evaluatorOptions: options
          }));
          break;
        case AnnotationEditorType.STAMP:
          if (!options.isOffscreenCanvasSupported) {
            break;
          }
          const image = await imagePromises.get(annotation.bitmapId);
          if (image.imageStream) {
            const {
              imageStream,
              smaskStream
            } = image;
            if (smaskStream) {
              imageStream.dict.set("SMask", smaskStream);
            }
            image.imageRef = new JpegStream(imageStream, imageStream.length);
            image.imageStream = image.smaskStream = null;
          }
          promises.push(StampAnnotation.createNewPrintAnnotation(annotationGlobals, xref, annotation, {
            image,
            evaluatorOptions: options
          }));
          break;
      }
    }
    return Promise.all(promises);
  }
}
function getRgbColor(color, defaultColor = new Uint8ClampedArray(3)) {
  if (!Array.isArray(color)) {
    return defaultColor;
  }
  const rgbColor = defaultColor || new Uint8ClampedArray(3);
  switch (color.length) {
    case 0:
      return null;
    case 1:
      ColorSpace.singletons.gray.getRgbItem(color, 0, rgbColor, 0);
      return rgbColor;
    case 3:
      ColorSpace.singletons.rgb.getRgbItem(color, 0, rgbColor, 0);
      return rgbColor;
    case 4:
      ColorSpace.singletons.cmyk.getRgbItem(color, 0, rgbColor, 0);
      return rgbColor;
    default:
      return defaultColor;
  }
}
function getPdfColorArray(color) {
  return Array.from(color, c => c / 255);
}
function getQuadPoints(dict, rect) {
  const quadPoints = dict.getArray("QuadPoints");
  if (!isNumberArray(quadPoints, null) || quadPoints.length === 0 || quadPoints.length % 8 > 0) {
    return null;
  }
  const newQuadPoints = new Float32Array(quadPoints.length);
  for (let i = 0, ii = quadPoints.length; i < ii; i += 8) {
    const [x1, y1, x2, y2, x3, y3, x4, y4] = quadPoints.slice(i, i + 8);
    const minX = Math.min(x1, x2, x3, x4);
    const maxX = Math.max(x1, x2, x3, x4);
    const minY = Math.min(y1, y2, y3, y4);
    const maxY = Math.max(y1, y2, y3, y4);
    if (rect !== null && (minX < rect[0] || maxX > rect[2] || minY < rect[1] || maxY > rect[3])) {
      return null;
    }
    newQuadPoints.set([minX, maxY, maxX, maxY, minX, minY, maxX, minY], i);
  }
  return newQuadPoints;
}
function getTransformMatrix(rect, bbox, matrix) {
  const [minX, minY, maxX, maxY] = Util.getAxialAlignedBoundingBox(bbox, matrix);
  if (minX === maxX || minY === maxY) {
    return [1, 0, 0, 1, rect[0], rect[1]];
  }
  const xRatio = (rect[2] - rect[0]) / (maxX - minX);
  const yRatio = (rect[3] - rect[1]) / (maxY - minY);
  return [xRatio, 0, 0, yRatio, rect[0] - minX * xRatio, rect[1] - minY * yRatio];
}
class Annotation {
  constructor(params) {
    const {
      dict,
      xref,
      annotationGlobals
    } = params;
    this.setTitle(dict.get("T"));
    this.setContents(dict.get("Contents"));
    this.setModificationDate(dict.get("M"));
    this.setFlags(dict.get("F"));
    this.setRectangle(dict.getArray("Rect"));
    this.setColor(dict.getArray("C"));
    this.setBorderStyle(dict);
    this.setAppearance(dict);
    this.setOptionalContent(dict);
    const MK = dict.get("MK");
    this.setBorderAndBackgroundColors(MK);
    this.setRotation(MK, dict);
    this.ref = params.ref instanceof Ref ? params.ref : null;
    this._streams = [];
    if (this.appearance) {
      this._streams.push(this.appearance);
    }
    const isLocked = !!(this.flags & AnnotationFlag.LOCKED);
    const isContentLocked = !!(this.flags & AnnotationFlag.LOCKEDCONTENTS);
    if (annotationGlobals.structTreeRoot) {
      let structParent = dict.get("StructParent");
      structParent = Number.isInteger(structParent) && structParent >= 0 ? structParent : -1;
      annotationGlobals.structTreeRoot.addAnnotationIdToPage(params.pageRef, structParent);
    }
    this.data = {
      annotationFlags: this.flags,
      borderStyle: this.borderStyle,
      color: this.color,
      backgroundColor: this.backgroundColor,
      borderColor: this.borderColor,
      rotation: this.rotation,
      contentsObj: this._contents,
      hasAppearance: !!this.appearance,
      id: params.id,
      modificationDate: this.modificationDate,
      rect: this.rectangle,
      subtype: params.subtype,
      hasOwnCanvas: false,
      noRotate: !!(this.flags & AnnotationFlag.NOROTATE),
      noHTML: isLocked && isContentLocked,
      isEditable: false
    };
    if (params.collectFields) {
      const kids = dict.get("Kids");
      if (Array.isArray(kids)) {
        const kidIds = [];
        for (const kid of kids) {
          if (kid instanceof Ref) {
            kidIds.push(kid.toString());
          }
        }
        if (kidIds.length !== 0) {
          this.data.kidIds = kidIds;
        }
      }
      this.data.actions = collectActions(xref, dict, AnnotationActionEventType);
      this.data.fieldName = this._constructFieldName(dict);
      this.data.pageIndex = params.pageIndex;
    }
    this._isOffscreenCanvasSupported = params.evaluatorOptions.isOffscreenCanvasSupported;
    this._fallbackFontDict = null;
    this._needAppearances = false;
  }
  _hasFlag(flags, flag) {
    return !!(flags & flag);
  }
  _isViewable(flags) {
    return !this._hasFlag(flags, AnnotationFlag.INVISIBLE) && !this._hasFlag(flags, AnnotationFlag.NOVIEW);
  }
  _isPrintable(flags) {
    return this._hasFlag(flags, AnnotationFlag.PRINT) && !this._hasFlag(flags, AnnotationFlag.HIDDEN) && !this._hasFlag(flags, AnnotationFlag.INVISIBLE);
  }
  mustBeViewed(annotationStorage, _renderForms) {
    const noView = annotationStorage?.get(this.data.id)?.noView;
    if (noView !== undefined) {
      return !noView;
    }
    return this.viewable && !this._hasFlag(this.flags, AnnotationFlag.HIDDEN);
  }
  mustBePrinted(annotationStorage) {
    const noPrint = annotationStorage?.get(this.data.id)?.noPrint;
    if (noPrint !== undefined) {
      return !noPrint;
    }
    return this.printable;
  }
  mustBeViewedWhenEditing(isEditing, modifiedIds = null) {
    return isEditing ? !this.data.isEditable : !modifiedIds?.has(this.data.id);
  }
  get viewable() {
    if (this.data.quadPoints === null) {
      return false;
    }
    if (this.flags === 0) {
      return true;
    }
    return this._isViewable(this.flags);
  }
  get printable() {
    if (this.data.quadPoints === null) {
      return false;
    }
    if (this.flags === 0) {
      return false;
    }
    return this._isPrintable(this.flags);
  }
  _parseStringHelper(data) {
    const str = typeof data === "string" ? stringToPDFString(data) : "";
    const dir = str && bidi(str).dir === "rtl" ? "rtl" : "ltr";
    return {
      str,
      dir
    };
  }
  setDefaultAppearance(params) {
    const {
      dict,
      annotationGlobals
    } = params;
    const defaultAppearance = getInheritableProperty({
      dict,
      key: "DA"
    }) || annotationGlobals.acroForm.get("DA");
    this._defaultAppearance = typeof defaultAppearance === "string" ? defaultAppearance : "";
    this.data.defaultAppearanceData = parseDefaultAppearance(this._defaultAppearance);
  }
  setTitle(title) {
    this._title = this._parseStringHelper(title);
  }
  setContents(contents) {
    this._contents = this._parseStringHelper(contents);
  }
  setModificationDate(modificationDate) {
    this.modificationDate = typeof modificationDate === "string" ? modificationDate : null;
  }
  setFlags(flags) {
    this.flags = Number.isInteger(flags) && flags > 0 ? flags : 0;
    if (this.flags & AnnotationFlag.INVISIBLE && this.constructor.name !== "Annotation") {
      this.flags ^= AnnotationFlag.INVISIBLE;
    }
  }
  hasFlag(flag) {
    return this._hasFlag(this.flags, flag);
  }
  setRectangle(rectangle) {
    this.rectangle = lookupNormalRect(rectangle, [0, 0, 0, 0]);
  }
  setColor(color) {
    this.color = getRgbColor(color);
  }
  setLineEndings(lineEndings) {
    throw new Error("Not implemented: setLineEndings");
  }
  setRotation(mk, dict) {
    this.rotation = 0;
    let angle = mk instanceof Dict ? mk.get("R") || 0 : dict.get("Rotate") || 0;
    if (Number.isInteger(angle) && angle !== 0) {
      angle %= 360;
      if (angle < 0) {
        angle += 360;
      }
      if (angle % 90 === 0) {
        this.rotation = angle;
      }
    }
  }
  setBorderAndBackgroundColors(mk) {
    if (mk instanceof Dict) {
      this.borderColor = getRgbColor(mk.getArray("BC"), null);
      this.backgroundColor = getRgbColor(mk.getArray("BG"), null);
    } else {
      this.borderColor = this.backgroundColor = null;
    }
  }
  setBorderStyle(borderStyle) {
    this.borderStyle = new AnnotationBorderStyle();
    if (!(borderStyle instanceof Dict)) {
      return;
    }
    if (borderStyle.has("BS")) {
      const dict = borderStyle.get("BS");
      if (dict instanceof Dict) {
        const dictType = dict.get("Type");
        if (!dictType || isName(dictType, "Border")) {
          this.borderStyle.setWidth(dict.get("W"), this.rectangle);
          this.borderStyle.setStyle(dict.get("S"));
          this.borderStyle.setDashArray(dict.getArray("D"));
        }
      }
    } else if (borderStyle.has("Border")) {
      const array = borderStyle.getArray("Border");
      if (Array.isArray(array) && array.length >= 3) {
        this.borderStyle.setHorizontalCornerRadius(array[0]);
        this.borderStyle.setVerticalCornerRadius(array[1]);
        this.borderStyle.setWidth(array[2], this.rectangle);
        if (array.length === 4) {
          this.borderStyle.setDashArray(array[3], true);
        }
      }
    } else {
      this.borderStyle.setWidth(0);
    }
  }
  setAppearance(dict) {
    this.appearance = null;
    const appearanceStates = dict.get("AP");
    if (!(appearanceStates instanceof Dict)) {
      return;
    }
    const normalAppearanceState = appearanceStates.get("N");
    if (normalAppearanceState instanceof BaseStream) {
      this.appearance = normalAppearanceState;
      return;
    }
    if (!(normalAppearanceState instanceof Dict)) {
      return;
    }
    const as = dict.get("AS");
    if (!(as instanceof Name) || !normalAppearanceState.has(as.name)) {
      return;
    }
    const appearance = normalAppearanceState.get(as.name);
    if (appearance instanceof BaseStream) {
      this.appearance = appearance;
    }
  }
  setOptionalContent(dict) {
    this.oc = null;
    const oc = dict.get("OC");
    if (oc instanceof Name) {
      warn("setOptionalContent: Support for /Name-entry is not implemented.");
    } else if (oc instanceof Dict) {
      this.oc = oc;
    }
  }
  loadResources(keys, appearance) {
    return appearance.dict.getAsync("Resources").then(resources => {
      if (!resources) {
        return undefined;
      }
      const objectLoader = new ObjectLoader(resources, keys, resources.xref);
      return objectLoader.load().then(function () {
        return resources;
      });
    });
  }
  async getOperatorList(evaluator, task, intent, annotationStorage) {
    const {
      hasOwnCanvas,
      id,
      rect
    } = this.data;
    let appearance = this.appearance;
    const isUsingOwnCanvas = !!(hasOwnCanvas && intent & RenderingIntentFlag.DISPLAY);
    if (isUsingOwnCanvas && (rect[0] === rect[2] || rect[1] === rect[3])) {
      this.data.hasOwnCanvas = false;
      return {
        opList: new OperatorList(),
        separateForm: false,
        separateCanvas: false
      };
    }
    if (!appearance) {
      if (!isUsingOwnCanvas) {
        return {
          opList: new OperatorList(),
          separateForm: false,
          separateCanvas: false
        };
      }
      appearance = new StringStream("");
      appearance.dict = new Dict();
    }
    const appearanceDict = appearance.dict;
    const resources = await this.loadResources(["ExtGState", "ColorSpace", "Pattern", "Shading", "XObject", "Font"], appearance);
    const bbox = lookupRect(appearanceDict.getArray("BBox"), [0, 0, 1, 1]);
    const matrix = lookupMatrix(appearanceDict.getArray("Matrix"), IDENTITY_MATRIX);
    const transform = getTransformMatrix(rect, bbox, matrix);
    const opList = new OperatorList();
    let optionalContent;
    if (this.oc) {
      optionalContent = await evaluator.parseMarkedContentProps(this.oc, null);
    }
    if (optionalContent !== undefined) {
      opList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
    }
    opList.addOp(OPS.beginAnnotation, [id, rect, transform, matrix, isUsingOwnCanvas]);
    await evaluator.getOperatorList({
      stream: appearance,
      task,
      resources,
      operatorList: opList,
      fallbackFontDict: this._fallbackFontDict
    });
    opList.addOp(OPS.endAnnotation, []);
    if (optionalContent !== undefined) {
      opList.addOp(OPS.endMarkedContent, []);
    }
    this.reset();
    return {
      opList,
      separateForm: false,
      separateCanvas: isUsingOwnCanvas
    };
  }
  async save(evaluator, task, annotationStorage) {
    return null;
  }
  get hasTextContent() {
    return false;
  }
  async extractTextContent(evaluator, task, viewBox) {
    if (!this.appearance) {
      return;
    }
    const resources = await this.loadResources(["ExtGState", "Font", "Properties", "XObject"], this.appearance);
    const text = [];
    const buffer = [];
    let firstPosition = null;
    const sink = {
      desiredSize: Math.Infinity,
      ready: true,
      enqueue(chunk, size) {
        for (const item of chunk.items) {
          if (item.str === undefined) {
            continue;
          }
          firstPosition ||= item.transform.slice(-2);
          buffer.push(item.str);
          if (item.hasEOL) {
            text.push(buffer.join("").trimEnd());
            buffer.length = 0;
          }
        }
      }
    };
    await evaluator.getTextContent({
      stream: this.appearance,
      task,
      resources,
      includeMarkedContent: true,
      keepWhiteSpace: true,
      sink,
      viewBox
    });
    this.reset();
    if (buffer.length) {
      text.push(buffer.join("").trimEnd());
    }
    if (text.length > 1 || text[0]) {
      const appearanceDict = this.appearance.dict;
      const bbox = lookupRect(appearanceDict.getArray("BBox"), null);
      const matrix = lookupMatrix(appearanceDict.getArray("Matrix"), null);
      this.data.textPosition = this._transformPoint(firstPosition, bbox, matrix);
      this.data.textContent = text;
    }
  }
  _transformPoint(coords, bbox, matrix) {
    const {
      rect
    } = this.data;
    bbox ||= [0, 0, 1, 1];
    matrix ||= [1, 0, 0, 1, 0, 0];
    const transform = getTransformMatrix(rect, bbox, matrix);
    transform[4] -= rect[0];
    transform[5] -= rect[1];
    coords = Util.applyTransform(coords, transform);
    return Util.applyTransform(coords, matrix);
  }
  getFieldObject() {
    if (this.data.kidIds) {
      return {
        id: this.data.id,
        actions: this.data.actions,
        name: this.data.fieldName,
        strokeColor: this.data.borderColor,
        fillColor: this.data.backgroundColor,
        type: "",
        kidIds: this.data.kidIds,
        page: this.data.pageIndex,
        rotation: this.rotation
      };
    }
    return null;
  }
  reset() {
    for (const stream of this._streams) {
      stream.reset();
    }
  }
  _constructFieldName(dict) {
    if (!dict.has("T") && !dict.has("Parent")) {
      warn("Unknown field name, falling back to empty field name.");
      return "";
    }
    if (!dict.has("Parent")) {
      return stringToPDFString(dict.get("T"));
    }
    const fieldName = [];
    if (dict.has("T")) {
      fieldName.unshift(stringToPDFString(dict.get("T")));
    }
    let loopDict = dict;
    const visited = new RefSet();
    if (dict.objId) {
      visited.put(dict.objId);
    }
    while (loopDict.has("Parent")) {
      loopDict = loopDict.get("Parent");
      if (!(loopDict instanceof Dict) || loopDict.objId && visited.has(loopDict.objId)) {
        break;
      }
      if (loopDict.objId) {
        visited.put(loopDict.objId);
      }
      if (loopDict.has("T")) {
        fieldName.unshift(stringToPDFString(loopDict.get("T")));
      }
    }
    return fieldName.join(".");
  }
}
class AnnotationBorderStyle {
  constructor() {
    this.width = 1;
    this.style = AnnotationBorderStyleType.SOLID;
    this.dashArray = [3];
    this.horizontalCornerRadius = 0;
    this.verticalCornerRadius = 0;
  }
  setWidth(width, rect = [0, 0, 0, 0]) {
    if (width instanceof Name) {
      this.width = 0;
      return;
    }
    if (typeof width === "number") {
      if (width > 0) {
        const maxWidth = (rect[2] - rect[0]) / 2;
        const maxHeight = (rect[3] - rect[1]) / 2;
        if (maxWidth > 0 && maxHeight > 0 && (width > maxWidth || width > maxHeight)) {
          warn(`AnnotationBorderStyle.setWidth - ignoring width: ${width}`);
          width = 1;
        }
      }
      this.width = width;
    }
  }
  setStyle(style) {
    if (!(style instanceof Name)) {
      return;
    }
    switch (style.name) {
      case "S":
        this.style = AnnotationBorderStyleType.SOLID;
        break;
      case "D":
        this.style = AnnotationBorderStyleType.DASHED;
        break;
      case "B":
        this.style = AnnotationBorderStyleType.BEVELED;
        break;
      case "I":
        this.style = AnnotationBorderStyleType.INSET;
        break;
      case "U":
        this.style = AnnotationBorderStyleType.UNDERLINE;
        break;
      default:
        break;
    }
  }
  setDashArray(dashArray, forceStyle = false) {
    if (Array.isArray(dashArray)) {
      let isValid = true;
      let allZeros = true;
      for (const element of dashArray) {
        const validNumber = +element >= 0;
        if (!validNumber) {
          isValid = false;
          break;
        } else if (element > 0) {
          allZeros = false;
        }
      }
      if (dashArray.length === 0 || isValid && !allZeros) {
        this.dashArray = dashArray;
        if (forceStyle) {
          this.setStyle(Name.get("D"));
        }
      } else {
        this.width = 0;
      }
    } else if (dashArray) {
      this.width = 0;
    }
  }
  setHorizontalCornerRadius(radius) {
    if (Number.isInteger(radius)) {
      this.horizontalCornerRadius = radius;
    }
  }
  setVerticalCornerRadius(radius) {
    if (Number.isInteger(radius)) {
      this.verticalCornerRadius = radius;
    }
  }
}
class MarkupAnnotation extends Annotation {
  constructor(params) {
    super(params);
    const {
      dict
    } = params;
    if (dict.has("IRT")) {
      const rawIRT = dict.getRaw("IRT");
      this.data.inReplyTo = rawIRT instanceof Ref ? rawIRT.toString() : null;
      const rt = dict.get("RT");
      this.data.replyType = rt instanceof Name ? rt.name : AnnotationReplyType.REPLY;
    }
    let popupRef = null;
    if (this.data.replyType === AnnotationReplyType.GROUP) {
      const parent = dict.get("IRT");
      this.setTitle(parent.get("T"));
      this.data.titleObj = this._title;
      this.setContents(parent.get("Contents"));
      this.data.contentsObj = this._contents;
      if (!parent.has("CreationDate")) {
        this.data.creationDate = null;
      } else {
        this.setCreationDate(parent.get("CreationDate"));
        this.data.creationDate = this.creationDate;
      }
      if (!parent.has("M")) {
        this.data.modificationDate = null;
      } else {
        this.setModificationDate(parent.get("M"));
        this.data.modificationDate = this.modificationDate;
      }
      popupRef = parent.getRaw("Popup");
      if (!parent.has("C")) {
        this.data.color = null;
      } else {
        this.setColor(parent.getArray("C"));
        this.data.color = this.color;
      }
    } else {
      this.data.titleObj = this._title;
      this.setCreationDate(dict.get("CreationDate"));
      this.data.creationDate = this.creationDate;
      popupRef = dict.getRaw("Popup");
      if (!dict.has("C")) {
        this.data.color = null;
      }
    }
    this.data.popupRef = popupRef instanceof Ref ? popupRef.toString() : null;
    if (dict.has("RC")) {
      this.data.richText = XFAFactory.getRichTextAsHtml(dict.get("RC"));
    }
  }
  setCreationDate(creationDate) {
    this.creationDate = typeof creationDate === "string" ? creationDate : null;
  }
  _setDefaultAppearance({
    xref,
    extra,
    strokeColor,
    fillColor,
    blendMode,
    strokeAlpha,
    fillAlpha,
    pointsCallback
  }) {
    let minX = Number.MAX_VALUE;
    let minY = Number.MAX_VALUE;
    let maxX = Number.MIN_VALUE;
    let maxY = Number.MIN_VALUE;
    const buffer = ["q"];
    if (extra) {
      buffer.push(extra);
    }
    if (strokeColor) {
      buffer.push(`${strokeColor[0]} ${strokeColor[1]} ${strokeColor[2]} RG`);
    }
    if (fillColor) {
      buffer.push(`${fillColor[0]} ${fillColor[1]} ${fillColor[2]} rg`);
    }
    let pointsArray = this.data.quadPoints;
    if (!pointsArray) {
      pointsArray = Float32Array.from([this.rectangle[0], this.rectangle[3], this.rectangle[2], this.rectangle[3], this.rectangle[0], this.rectangle[1], this.rectangle[2], this.rectangle[1]]);
    }
    for (let i = 0, ii = pointsArray.length; i < ii; i += 8) {
      const [mX, MX, mY, MY] = pointsCallback(buffer, pointsArray.subarray(i, i + 8));
      minX = Math.min(minX, mX);
      maxX = Math.max(maxX, MX);
      minY = Math.min(minY, mY);
      maxY = Math.max(maxY, MY);
    }
    buffer.push("Q");
    const formDict = new Dict(xref);
    const appearanceStreamDict = new Dict(xref);
    appearanceStreamDict.set("Subtype", Name.get("Form"));
    const appearanceStream = new StringStream(buffer.join(" "));
    appearanceStream.dict = appearanceStreamDict;
    formDict.set("Fm0", appearanceStream);
    const gsDict = new Dict(xref);
    if (blendMode) {
      gsDict.set("BM", Name.get(blendMode));
    }
    if (typeof strokeAlpha === "number") {
      gsDict.set("CA", strokeAlpha);
    }
    if (typeof fillAlpha === "number") {
      gsDict.set("ca", fillAlpha);
    }
    const stateDict = new Dict(xref);
    stateDict.set("GS0", gsDict);
    const resources = new Dict(xref);
    resources.set("ExtGState", stateDict);
    resources.set("XObject", formDict);
    const appearanceDict = new Dict(xref);
    appearanceDict.set("Resources", resources);
    const bbox = this.data.rect = [minX, minY, maxX, maxY];
    appearanceDict.set("BBox", bbox);
    this.appearance = new StringStream("/GS0 gs /Fm0 Do");
    this.appearance.dict = appearanceDict;
    this._streams.push(this.appearance, appearanceStream);
  }
  static async createNewAnnotation(xref, annotation, dependencies, params) {
    let oldAnnotation;
    if (annotation.ref) {
      oldAnnotation = (await xref.fetchIfRefAsync(annotation.ref)).clone();
    } else {
      annotation.ref = xref.getNewTemporaryRef();
    }
    const annotationRef = annotation.ref;
    const ap = await this.createNewAppearanceStream(annotation, xref, params);
    const buffer = [];
    let annotationDict;
    if (ap) {
      const apRef = xref.getNewTemporaryRef();
      annotationDict = this.createNewDict(annotation, xref, {
        apRef,
        oldAnnotation
      });
      await writeObject(apRef, ap, buffer, xref);
      dependencies.push({
        ref: apRef,
        data: buffer.join("")
      });
    } else {
      annotationDict = this.createNewDict(annotation, xref, {
        oldAnnotation
      });
    }
    if (Number.isInteger(annotation.parentTreeId)) {
      annotationDict.set("StructParent", annotation.parentTreeId);
    }
    buffer.length = 0;
    await writeObject(annotationRef, annotationDict, buffer, xref);
    return {
      ref: annotationRef,
      data: buffer.join("")
    };
  }
  static async createNewPrintAnnotation(annotationGlobals, xref, annotation, params) {
    const ap = await this.createNewAppearanceStream(annotation, xref, params);
    const annotationDict = this.createNewDict(annotation, xref, {
      ap
    });
    const newAnnotation = new this.prototype.constructor({
      dict: annotationDict,
      xref,
      annotationGlobals,
      evaluatorOptions: params.evaluatorOptions
    });
    if (annotation.ref) {
      newAnnotation.ref = newAnnotation.refToReplace = annotation.ref;
    }
    return newAnnotation;
  }
}
class WidgetAnnotation extends Annotation {
  constructor(params) {
    super(params);
    const {
      dict,
      xref,
      annotationGlobals
    } = params;
    const data = this.data;
    this._needAppearances = params.needAppearances;
    data.annotationType = AnnotationType.WIDGET;
    if (data.fieldName === undefined) {
      data.fieldName = this._constructFieldName(dict);
    }
    if (data.actions === undefined) {
      data.actions = collectActions(xref, dict, AnnotationActionEventType);
    }
    let fieldValue = getInheritableProperty({
      dict,
      key: "V",
      getArray: true
    });
    data.fieldValue = this._decodeFormValue(fieldValue);
    const defaultFieldValue = getInheritableProperty({
      dict,
      key: "DV",
      getArray: true
    });
    data.defaultFieldValue = this._decodeFormValue(defaultFieldValue);
    if (fieldValue === undefined && annotationGlobals.xfaDatasets) {
      const path = this._title.str;
      if (path) {
        this._hasValueFromXFA = true;
        data.fieldValue = fieldValue = annotationGlobals.xfaDatasets.getValue(path);
      }
    }
    if (fieldValue === undefined && data.defaultFieldValue !== null) {
      data.fieldValue = data.defaultFieldValue;
    }
    data.alternativeText = stringToPDFString(dict.get("TU") || "");
    this.setDefaultAppearance(params);
    data.hasAppearance ||= this._needAppearances && data.fieldValue !== undefined && data.fieldValue !== null;
    const fieldType = getInheritableProperty({
      dict,
      key: "FT"
    });
    data.fieldType = fieldType instanceof Name ? fieldType.name : null;
    const localResources = getInheritableProperty({
      dict,
      key: "DR"
    });
    const acroFormResources = annotationGlobals.acroForm.get("DR");
    const appearanceResources = this.appearance?.dict.get("Resources");
    this._fieldResources = {
      localResources,
      acroFormResources,
      appearanceResources,
      mergedResources: Dict.merge({
        xref,
        dictArray: [localResources, appearanceResources, acroFormResources],
        mergeSubDicts: true
      })
    };
    data.fieldFlags = getInheritableProperty({
      dict,
      key: "Ff"
    });
    if (!Number.isInteger(data.fieldFlags) || data.fieldFlags < 0) {
      data.fieldFlags = 0;
    }
    data.readOnly = this.hasFieldFlag(AnnotationFieldFlag.READONLY);
    data.required = this.hasFieldFlag(AnnotationFieldFlag.REQUIRED);
    data.hidden = this._hasFlag(data.annotationFlags, AnnotationFlag.HIDDEN) || this._hasFlag(data.annotationFlags, AnnotationFlag.NOVIEW);
  }
  _decodeFormValue(formValue) {
    if (Array.isArray(formValue)) {
      return formValue.filter(item => typeof item === "string").map(item => stringToPDFString(item));
    } else if (formValue instanceof Name) {
      return stringToPDFString(formValue.name);
    } else if (typeof formValue === "string") {
      return stringToPDFString(formValue);
    }
    return null;
  }
  hasFieldFlag(flag) {
    return !!(this.data.fieldFlags & flag);
  }
  _isViewable(flags) {
    return true;
  }
  mustBeViewed(annotationStorage, renderForms) {
    if (renderForms) {
      return this.viewable;
    }
    return super.mustBeViewed(annotationStorage, renderForms) && !this._hasFlag(this.flags, AnnotationFlag.NOVIEW);
  }
  getRotationMatrix(annotationStorage) {
    let rotation = annotationStorage?.get(this.data.id)?.rotation;
    if (rotation === undefined) {
      rotation = this.rotation;
    }
    if (rotation === 0) {
      return IDENTITY_MATRIX;
    }
    const width = this.data.rect[2] - this.data.rect[0];
    const height = this.data.rect[3] - this.data.rect[1];
    return getRotationMatrix(rotation, width, height);
  }
  getBorderAndBackgroundAppearances(annotationStorage) {
    let rotation = annotationStorage?.get(this.data.id)?.rotation;
    if (rotation === undefined) {
      rotation = this.rotation;
    }
    if (!this.backgroundColor && !this.borderColor) {
      return "";
    }
    const width = this.data.rect[2] - this.data.rect[0];
    const height = this.data.rect[3] - this.data.rect[1];
    const rect = rotation === 0 || rotation === 180 ? `0 0 ${width} ${height} re` : `0 0 ${height} ${width} re`;
    let str = "";
    if (this.backgroundColor) {
      str = `${getPdfColor(this.backgroundColor, true)} ${rect} f `;
    }
    if (this.borderColor) {
      const borderWidth = this.borderStyle.width || 1;
      str += `${borderWidth} w ${getPdfColor(this.borderColor, false)} ${rect} S `;
    }
    return str;
  }
  async getOperatorList(evaluator, task, intent, annotationStorage) {
    if (intent & RenderingIntentFlag.ANNOTATIONS_FORMS && !(this instanceof SignatureWidgetAnnotation) && !this.data.noHTML && !this.data.hasOwnCanvas) {
      return {
        opList: new OperatorList(),
        separateForm: true,
        separateCanvas: false
      };
    }
    if (!this._hasText) {
      return super.getOperatorList(evaluator, task, intent, annotationStorage);
    }
    const content = await this._getAppearance(evaluator, task, intent, annotationStorage);
    if (this.appearance && content === null) {
      return super.getOperatorList(evaluator, task, intent, annotationStorage);
    }
    const opList = new OperatorList();
    if (!this._defaultAppearance || content === null) {
      return {
        opList,
        separateForm: false,
        separateCanvas: false
      };
    }
    const isUsingOwnCanvas = !!(this.data.hasOwnCanvas && intent & RenderingIntentFlag.DISPLAY);
    const matrix = [1, 0, 0, 1, 0, 0];
    const bbox = [0, 0, this.data.rect[2] - this.data.rect[0], this.data.rect[3] - this.data.rect[1]];
    const transform = getTransformMatrix(this.data.rect, bbox, matrix);
    let optionalContent;
    if (this.oc) {
      optionalContent = await evaluator.parseMarkedContentProps(this.oc, null);
    }
    if (optionalContent !== undefined) {
      opList.addOp(OPS.beginMarkedContentProps, ["OC", optionalContent]);
    }
    opList.addOp(OPS.beginAnnotation, [this.data.id, this.data.rect, transform, this.getRotationMatrix(annotationStorage), isUsingOwnCanvas]);
    const stream = new StringStream(content);
    await evaluator.getOperatorList({
      stream,
      task,
      resources: this._fieldResources.mergedResources,
      operatorList: opList
    });
    opList.addOp(OPS.endAnnotation, []);
    if (optionalContent !== undefined) {
      opList.addOp(OPS.endMarkedContent, []);
    }
    return {
      opList,
      separateForm: false,
      separateCanvas: isUsingOwnCanvas
    };
  }
  _getMKDict(rotation) {
    const mk = new Dict(null);
    if (rotation) {
      mk.set("R", rotation);
    }
    if (this.borderColor) {
      mk.set("BC", getPdfColorArray(this.borderColor));
    }
    if (this.backgroundColor) {
      mk.set("BG", getPdfColorArray(this.backgroundColor));
    }
    return mk.size > 0 ? mk : null;
  }
  amendSavedDict(annotationStorage, dict) {}
  async save(evaluator, task, annotationStorage) {
    const storageEntry = annotationStorage?.get(this.data.id);
    let value = storageEntry?.value,
      rotation = storageEntry?.rotation;
    if (value === this.data.fieldValue || value === undefined) {
      if (!this._hasValueFromXFA && rotation === undefined) {
        return null;
      }
      value ||= this.data.fieldValue;
    }
    if (rotation === undefined && !this._hasValueFromXFA && Array.isArray(value) && Array.isArray(this.data.fieldValue) && value.length === this.data.fieldValue.length && value.every((x, i) => x === this.data.fieldValue[i])) {
      return null;
    }
    if (rotation === undefined) {
      rotation = this.rotation;
    }
    let appearance = null;
    if (!this._needAppearances) {
      appearance = await this._getAppearance(evaluator, task, RenderingIntentFlag.SAVE, annotationStorage);
      if (appearance === null) {
        return null;
      }
    } else {}
    let needAppearances = false;
    if (appearance?.needAppearances) {
      needAppearances = true;
      appearance = null;
    }
    const {
      xref
    } = evaluator;
    const originalDict = xref.fetchIfRef(this.ref);
    if (!(originalDict instanceof Dict)) {
      return null;
    }
    const dict = new Dict(xref);
    for (const key of originalDict.getKeys()) {
      if (key !== "AP") {
        dict.set(key, originalDict.getRaw(key));
      }
    }
    const xfa = {
      path: this.data.fieldName,
      value
    };
    dict.set("V", Array.isArray(value) ? value.map(stringToAsciiOrUTF16BE) : stringToAsciiOrUTF16BE(value));
    this.amendSavedDict(annotationStorage, dict);
    const maybeMK = this._getMKDict(rotation);
    if (maybeMK) {
      dict.set("MK", maybeMK);
    }
    const buffer = [];
    const changes = [{
      ref: this.ref,
      data: "",
      xfa,
      needAppearances
    }];
    if (appearance !== null) {
      const newRef = xref.getNewTemporaryRef();
      const AP = new Dict(xref);
      dict.set("AP", AP);
      AP.set("N", newRef);
      const resources = this._getSaveFieldResources(xref);
      const appearanceStream = new StringStream(appearance);
      const appearanceDict = appearanceStream.dict = new Dict(xref);
      appearanceDict.set("Subtype", Name.get("Form"));
      appearanceDict.set("Resources", resources);
      appearanceDict.set("BBox", [0, 0, this.data.rect[2] - this.data.rect[0], this.data.rect[3] - this.data.rect[1]]);
      const rotationMatrix = this.getRotationMatrix(annotationStorage);
      if (rotationMatrix !== IDENTITY_MATRIX) {
        appearanceDict.set("Matrix", rotationMatrix);
      }
      await writeObject(newRef, appearanceStream, buffer, xref);
      changes.push({
        ref: newRef,
        data: buffer.join(""),
        xfa: null,
        needAppearances: false
      });
      buffer.length = 0;
    }
    dict.set("M", `D:${getModificationDate()}`);
    await writeObject(this.ref, dict, buffer, xref);
    changes[0].data = buffer.join("");
    return changes;
  }
  async _getAppearance(evaluator, task, intent, annotationStorage) {
    const isPassword = this.hasFieldFlag(AnnotationFieldFlag.PASSWORD);
    if (isPassword) {
      return null;
    }
    const storageEntry = annotationStorage?.get(this.data.id);
    let value, rotation;
    if (storageEntry) {
      value = storageEntry.formattedValue || storageEntry.value;
      rotation = storageEntry.rotation;
    }
    if (rotation === undefined && value === undefined && !this._needAppearances) {
      if (!this._hasValueFromXFA || this.appearance) {
        return null;
      }
    }
    const colors = this.getBorderAndBackgroundAppearances(annotationStorage);
    if (value === undefined) {
      value = this.data.fieldValue;
      if (!value) {
        return `/Tx BMC q ${colors}Q EMC`;
      }
    }
    if (Array.isArray(value) && value.length === 1) {
      value = value[0];
    }
    assert(typeof value === "string", "Expected `value` to be a string.");
    value = value.trim();
    if (this.data.combo) {
      const option = this.data.options.find(({
        exportValue
      }) => value === exportValue);
      value = option?.displayValue || value;
    }
    if (value === "") {
      return `/Tx BMC q ${colors}Q EMC`;
    }
    if (rotation === undefined) {
      rotation = this.rotation;
    }
    let lineCount = -1;
    let lines;
    if (this.data.multiLine) {
      lines = value.split(/\r\n?|\n/).map(line => line.normalize("NFC"));
      lineCount = lines.length;
    } else {
      lines = [value.replace(/\r\n?|\n/, "").normalize("NFC")];
    }
    const defaultPadding = 1;
    const defaultHPadding = 2;
    let totalHeight = this.data.rect[3] - this.data.rect[1];
    let totalWidth = this.data.rect[2] - this.data.rect[0];
    if (rotation === 90 || rotation === 270) {
      [totalWidth, totalHeight] = [totalHeight, totalWidth];
    }
    if (!this._defaultAppearance) {
      this.data.defaultAppearanceData = parseDefaultAppearance(this._defaultAppearance = "/Helvetica 0 Tf 0 g");
    }
    let font = await WidgetAnnotation._getFontData(evaluator, task, this.data.defaultAppearanceData, this._fieldResources.mergedResources);
    let defaultAppearance, fontSize, lineHeight;
    const encodedLines = [];
    let encodingError = false;
    for (const line of lines) {
      const encodedString = font.encodeString(line);
      if (encodedString.length > 1) {
        encodingError = true;
      }
      encodedLines.push(encodedString.join(""));
    }
    if (encodingError && intent & RenderingIntentFlag.SAVE) {
      return {
        needAppearances: true
      };
    }
    if (encodingError && this._isOffscreenCanvasSupported) {
      const fontFamily = this.data.comb ? "monospace" : "sans-serif";
      const fakeUnicodeFont = new FakeUnicodeFont(evaluator.xref, fontFamily);
      const resources = fakeUnicodeFont.createFontResources(lines.join(""));
      const newFont = resources.getRaw("Font");
      if (this._fieldResources.mergedResources.has("Font")) {
        const oldFont = this._fieldResources.mergedResources.get("Font");
        for (const key of newFont.getKeys()) {
          oldFont.set(key, newFont.getRaw(key));
        }
      } else {
        this._fieldResources.mergedResources.set("Font", newFont);
      }
      const fontName = fakeUnicodeFont.fontName.name;
      font = await WidgetAnnotation._getFontData(evaluator, task, {
        fontName,
        fontSize: 0
      }, resources);
      for (let i = 0, ii = encodedLines.length; i < ii; i++) {
        encodedLines[i] = stringToUTF16String(lines[i]);
      }
      const savedDefaultAppearance = Object.assign(Object.create(null), this.data.defaultAppearanceData);
      this.data.defaultAppearanceData.fontSize = 0;
      this.data.defaultAppearanceData.fontName = fontName;
      [defaultAppearance, fontSize, lineHeight] = this._computeFontSize(totalHeight - 2 * defaultPadding, totalWidth - 2 * defaultHPadding, value, font, lineCount);
      this.data.defaultAppearanceData = savedDefaultAppearance;
    } else {
      if (!this._isOffscreenCanvasSupported) {
        warn("_getAppearance: OffscreenCanvas is not supported, annotation may not render correctly.");
      }
      [defaultAppearance, fontSize, lineHeight] = this._computeFontSize(totalHeight - 2 * defaultPadding, totalWidth - 2 * defaultHPadding, value, font, lineCount);
    }
    let descent = font.descent;
    if (isNaN(descent)) {
      descent = BASELINE_FACTOR * lineHeight;
    } else {
      descent = Math.max(BASELINE_FACTOR * lineHeight, Math.abs(descent) * fontSize);
    }
    const defaultVPadding = Math.min(Math.floor((totalHeight - fontSize) / 2), defaultPadding);
    const alignment = this.data.textAlignment;
    if (this.data.multiLine) {
      return this._getMultilineAppearance(defaultAppearance, encodedLines, font, fontSize, totalWidth, totalHeight, alignment, defaultHPadding, defaultVPadding, descent, lineHeight, annotationStorage);
    }
    if (this.data.comb) {
      return this._getCombAppearance(defaultAppearance, font, encodedLines[0], fontSize, totalWidth, totalHeight, defaultHPadding, defaultVPadding, descent, lineHeight, annotationStorage);
    }
    const bottomPadding = defaultVPadding + descent;
    if (alignment === 0 || alignment > 2) {
      return `/Tx BMC q ${colors}BT ` + defaultAppearance + ` 1 0 0 1 ${numberToString(defaultHPadding)} ${numberToString(bottomPadding)} Tm (${escapeString(encodedLines[0])}) Tj` + " ET Q EMC";
    }
    const prevInfo = {
      shift: 0
    };
    const renderedText = this._renderText(encodedLines[0], font, fontSize, totalWidth, alignment, prevInfo, defaultHPadding, bottomPadding);
    return `/Tx BMC q ${colors}BT ` + defaultAppearance + ` 1 0 0 1 0 0 Tm ${renderedText}` + " ET Q EMC";
  }
  static async _getFontData(evaluator, task, appearanceData, resources) {
    const operatorList = new OperatorList();
    const initialState = {
      font: null,
      clone() {
        return this;
      }
    };
    const {
      fontName,
      fontSize
    } = appearanceData;
    await evaluator.handleSetFont(resources, [fontName && Name.get(fontName), fontSize], null, operatorList, task, initialState, null);
    return initialState.font;
  }
  _getTextWidth(text, font) {
    return font.charsToGlyphs(text).reduce((width, glyph) => width + glyph.width, 0) / 1000;
  }
  _computeFontSize(height, width, text, font, lineCount) {
    let {
      fontSize
    } = this.data.defaultAppearanceData;
    let lineHeight = (fontSize || 12) * LINE_FACTOR,
      numberOfLines = Math.round(height / lineHeight);
    if (!fontSize) {
      const roundWithTwoDigits = x => Math.floor(x * 100) / 100;
      if (lineCount === -1) {
        const textWidth = this._getTextWidth(text, font);
        fontSize = roundWithTwoDigits(Math.min(height / LINE_FACTOR, textWidth > width ? width / textWidth : Infinity));
        numberOfLines = 1;
      } else {
        const lines = text.split(/\r\n?|\n/);
        const cachedLines = [];
        for (const line of lines) {
          const encoded = font.encodeString(line).join("");
          const glyphs = font.charsToGlyphs(encoded);
          const positions = font.getCharPositions(encoded);
          cachedLines.push({
            line: encoded,
            glyphs,
            positions
          });
        }
        const isTooBig = fsize => {
          let totalHeight = 0;
          for (const cache of cachedLines) {
            const chunks = this._splitLine(null, font, fsize, width, cache);
            totalHeight += chunks.length * fsize;
            if (totalHeight > height) {
              return true;
            }
          }
          return false;
        };
        numberOfLines = Math.max(numberOfLines, lineCount);
        while (true) {
          lineHeight = height / numberOfLines;
          fontSize = roundWithTwoDigits(lineHeight / LINE_FACTOR);
          if (isTooBig(fontSize)) {
            numberOfLines++;
            continue;
          }
          break;
        }
      }
      const {
        fontName,
        fontColor
      } = this.data.defaultAppearanceData;
      this._defaultAppearance = createDefaultAppearance({
        fontSize,
        fontName,
        fontColor
      });
    }
    return [this._defaultAppearance, fontSize, height / numberOfLines];
  }
  _renderText(text, font, fontSize, totalWidth, alignment, prevInfo, hPadding, vPadding) {
    let shift;
    if (alignment === 1) {
      const width = this._getTextWidth(text, font) * fontSize;
      shift = (totalWidth - width) / 2;
    } else if (alignment === 2) {
      const width = this._getTextWidth(text, font) * fontSize;
      shift = totalWidth - width - hPadding;
    } else {
      shift = hPadding;
    }
    const shiftStr = numberToString(shift - prevInfo.shift);
    prevInfo.shift = shift;
    vPadding = numberToString(vPadding);
    return `${shiftStr} ${vPadding} Td (${escapeString(text)}) Tj`;
  }
  _getSaveFieldResources(xref) {
    const {
      localResources,
      appearanceResources,
      acroFormResources
    } = this._fieldResources;
    const fontName = this.data.defaultAppearanceData?.fontName;
    if (!fontName) {
      return localResources || Dict.empty;
    }
    for (const resources of [localResources, appearanceResources]) {
      if (resources instanceof Dict) {
        const localFont = resources.get("Font");
        if (localFont instanceof Dict && localFont.has(fontName)) {
          return resources;
        }
      }
    }
    if (acroFormResources instanceof Dict) {
      const acroFormFont = acroFormResources.get("Font");
      if (acroFormFont instanceof Dict && acroFormFont.has(fontName)) {
        const subFontDict = new Dict(xref);
        subFontDict.set(fontName, acroFormFont.getRaw(fontName));
        const subResourcesDict = new Dict(xref);
        subResourcesDict.set("Font", subFontDict);
        return Dict.merge({
          xref,
          dictArray: [subResourcesDict, localResources],
          mergeSubDicts: true
        });
      }
    }
    return localResources || Dict.empty;
  }
  getFieldObject() {
    return null;
  }
}
class TextWidgetAnnotation extends WidgetAnnotation {
  constructor(params) {
    super(params);
    const {
      dict
    } = params;
    if (dict.has("PMD")) {
      this.flags |= AnnotationFlag.HIDDEN;
      this.data.hidden = true;
      warn("Barcodes are not supported");
    }
    this.data.hasOwnCanvas = this.data.readOnly && !this.data.noHTML;
    this._hasText = true;
    if (typeof this.data.fieldValue !== "string") {
      this.data.fieldValue = "";
    }
    let alignment = getInheritableProperty({
      dict,
      key: "Q"
    });
    if (!Number.isInteger(alignment) || alignment < 0 || alignment > 2) {
      alignment = null;
    }
    this.data.textAlignment = alignment;
    let maximumLength = getInheritableProperty({
      dict,
      key: "MaxLen"
    });
    if (!Number.isInteger(maximumLength) || maximumLength < 0) {
      maximumLength = 0;
    }
    this.data.maxLen = maximumLength;
    this.data.multiLine = this.hasFieldFlag(AnnotationFieldFlag.MULTILINE);
    this.data.comb = this.hasFieldFlag(AnnotationFieldFlag.COMB) && !this.hasFieldFlag(AnnotationFieldFlag.MULTILINE) && !this.hasFieldFlag(AnnotationFieldFlag.PASSWORD) && !this.hasFieldFlag(AnnotationFieldFlag.FILESELECT) && this.data.maxLen !== 0;
    this.data.doNotScroll = this.hasFieldFlag(AnnotationFieldFlag.DONOTSCROLL);
  }
  get hasTextContent() {
    return !!this.appearance && !this._needAppearances;
  }
  _getCombAppearance(defaultAppearance, font, text, fontSize, width, height, hPadding, vPadding, descent, lineHeight, annotationStorage) {
    const combWidth = width / this.data.maxLen;
    const colors = this.getBorderAndBackgroundAppearances(annotationStorage);
    const buf = [];
    const positions = font.getCharPositions(text);
    for (const [start, end] of positions) {
      buf.push(`(${escapeString(text.substring(start, end))}) Tj`);
    }
    const renderedComb = buf.join(` ${numberToString(combWidth)} 0 Td `);
    return `/Tx BMC q ${colors}BT ` + defaultAppearance + ` 1 0 0 1 ${numberToString(hPadding)} ${numberToString(vPadding + descent)} Tm ${renderedComb}` + " ET Q EMC";
  }
  _getMultilineAppearance(defaultAppearance, lines, font, fontSize, width, height, alignment, hPadding, vPadding, descent, lineHeight, annotationStorage) {
    const buf = [];
    const totalWidth = width - 2 * hPadding;
    const prevInfo = {
      shift: 0
    };
    for (let i = 0, ii = lines.length; i < ii; i++) {
      const line = lines[i];
      const chunks = this._splitLine(line, font, fontSize, totalWidth);
      for (let j = 0, jj = chunks.length; j < jj; j++) {
        const chunk = chunks[j];
        const vShift = i === 0 && j === 0 ? -vPadding - (lineHeight - descent) : -lineHeight;
        buf.push(this._renderText(chunk, font, fontSize, width, alignment, prevInfo, hPadding, vShift));
      }
    }
    const colors = this.getBorderAndBackgroundAppearances(annotationStorage);
    const renderedText = buf.join("\n");
    return `/Tx BMC q ${colors}BT ` + defaultAppearance + ` 1 0 0 1 0 ${numberToString(height)} Tm ${renderedText}` + " ET Q EMC";
  }
  _splitLine(line, font, fontSize, width, cache = {}) {
    line = cache.line || line;
    const glyphs = cache.glyphs || font.charsToGlyphs(line);
    if (glyphs.length <= 1) {
      return [line];
    }
    const positions = cache.positions || font.getCharPositions(line);
    const scale = fontSize / 1000;
    const chunks = [];
    let lastSpacePosInStringStart = -1,
      lastSpacePosInStringEnd = -1,
      lastSpacePos = -1,
      startChunk = 0,
      currentWidth = 0;
    for (let i = 0, ii = glyphs.length; i < ii; i++) {
      const [start, end] = positions[i];
      const glyph = glyphs[i];
      const glyphWidth = glyph.width * scale;
      if (glyph.unicode === " ") {
        if (currentWidth + glyphWidth > width) {
          chunks.push(line.substring(startChunk, start));
          startChunk = start;
          currentWidth = glyphWidth;
          lastSpacePosInStringStart = -1;
          lastSpacePos = -1;
        } else {
          currentWidth += glyphWidth;
          lastSpacePosInStringStart = start;
          lastSpacePosInStringEnd = end;
          lastSpacePos = i;
        }
      } else if (currentWidth + glyphWidth > width) {
        if (lastSpacePosInStringStart !== -1) {
          chunks.push(line.substring(startChunk, lastSpacePosInStringEnd));
          startChunk = lastSpacePosInStringEnd;
          i = lastSpacePos + 1;
          lastSpacePosInStringStart = -1;
          currentWidth = 0;
        } else {
          chunks.push(line.substring(startChunk, start));
          startChunk = start;
          currentWidth = glyphWidth;
        }
      } else {
        currentWidth += glyphWidth;
      }
    }
    if (startChunk < line.length) {
      chunks.push(line.substring(startChunk, line.length));
    }
    return chunks;
  }
  getFieldObject() {
    return {
      id: this.data.id,
      value: this.data.fieldValue,
      defaultValue: this.data.defaultFieldValue || "",
      multiline: this.data.multiLine,
      password: this.hasFieldFlag(AnnotationFieldFlag.PASSWORD),
      charLimit: this.data.maxLen,
      comb: this.data.comb,
      editable: !this.data.readOnly,
      hidden: this.data.hidden,
      name: this.data.fieldName,
      rect: this.data.rect,
      actions: this.data.actions,
      page: this.data.pageIndex,
      strokeColor: this.data.borderColor,
      fillColor: this.data.backgroundColor,
      rotation: this.rotation,
      type: "text"
    };
  }
}
class ButtonWidgetAnnotation extends WidgetAnnotation {
  constructor(params) {
    super(params);
    this.checkedAppearance = null;
    this.uncheckedAppearance = null;
    this.data.checkBox = !this.hasFieldFlag(AnnotationFieldFlag.RADIO) && !this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON);
    this.data.radioButton = this.hasFieldFlag(AnnotationFieldFlag.RADIO) && !this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON);
    this.data.pushButton = this.hasFieldFlag(AnnotationFieldFlag.PUSHBUTTON);
    this.data.isTooltipOnly = false;
    if (this.data.checkBox) {
      this._processCheckBox(params);
    } else if (this.data.radioButton) {
      this._processRadioButton(params);
    } else if (this.data.pushButton) {
      this.data.hasOwnCanvas = true;
      this.data.noHTML = false;
      this._processPushButton(params);
    } else {
      warn("Invalid field flags for button widget annotation");
    }
  }
  async getOperatorList(evaluator, task, intent, annotationStorage) {
    if (this.data.pushButton) {
      return super.getOperatorList(evaluator, task, intent, false, annotationStorage);
    }
    let value = null;
    let rotation = null;
    if (annotationStorage) {
      const storageEntry = annotationStorage.get(this.data.id);
      value = storageEntry ? storageEntry.value : null;
      rotation = storageEntry ? storageEntry.rotation : null;
    }
    if (value === null && this.appearance) {
      return super.getOperatorList(evaluator, task, intent, annotationStorage);
    }
    if (value === null || value === undefined) {
      value = this.data.checkBox ? this.data.fieldValue === this.data.exportValue : this.data.fieldValue === this.data.buttonValue;
    }
    const appearance = value ? this.checkedAppearance : this.uncheckedAppearance;
    if (appearance) {
      const savedAppearance = this.appearance;
      const savedMatrix = lookupMatrix(appearance.dict.getArray("Matrix"), IDENTITY_MATRIX);
      if (rotation) {
        appearance.dict.set("Matrix", this.getRotationMatrix(annotationStorage));
      }
      this.appearance = appearance;
      const operatorList = super.getOperatorList(evaluator, task, intent, annotationStorage);
      this.appearance = savedAppearance;
      appearance.dict.set("Matrix", savedMatrix);
      return operatorList;
    }
    return {
      opList: new OperatorList(),
      separateForm: false,
      separateCanvas: false
    };
  }
  async save(evaluator, task, annotationStorage) {
    if (this.data.checkBox) {
      return this._saveCheckbox(evaluator, task, annotationStorage);
    }
    if (this.data.radioButton) {
      return this._saveRadioButton(evaluator, task, annotationStorage);
    }
    return null;
  }
  async _saveCheckbox(evaluator, task, annotationStorage) {
    if (!annotationStorage) {
      return null;
    }
    const storageEntry = annotationStorage.get(this.data.id);
    let rotation = storageEntry?.rotation,
      value = storageEntry?.value;
    if (rotation === undefined) {
      if (value === undefined) {
        return null;
      }
      const defaultValue = this.data.fieldValue === this.data.exportValue;
      if (defaultValue === value) {
        return null;
      }
    }
    const dict = evaluator.xref.fetchIfRef(this.ref);
    if (!(dict instanceof Dict)) {
      return null;
    }
    if (rotation === undefined) {
      rotation = this.rotation;
    }
    if (value === undefined) {
      value = this.data.fieldValue === this.data.exportValue;
    }
    const xfa = {
      path: this.data.fieldName,
      value: value ? this.data.exportValue : ""
    };
    const name = Name.get(value ? this.data.exportValue : "Off");
    dict.set("V", name);
    dict.set("AS", name);
    dict.set("M", `D:${getModificationDate()}`);
    const maybeMK = this._getMKDict(rotation);
    if (maybeMK) {
      dict.set("MK", maybeMK);
    }
    const buffer = [];
    await writeObject(this.ref, dict, buffer, evaluator.xref);
    return [{
      ref: this.ref,
      data: buffer.join(""),
      xfa
    }];
  }
  async _saveRadioButton(evaluator, task, annotationStorage) {
    if (!annotationStorage) {
      return null;
    }
    const storageEntry = annotationStorage.get(this.data.id);
    let rotation = storageEntry?.rotation,
      value = storageEntry?.value;
    if (rotation === undefined) {
      if (value === undefined) {
        return null;
      }
      const defaultValue = this.data.fieldValue === this.data.buttonValue;
      if (defaultValue === value) {
        return null;
      }
    }
    const dict = evaluator.xref.fetchIfRef(this.ref);
    if (!(dict instanceof Dict)) {
      return null;
    }
    if (value === undefined) {
      value = this.data.fieldValue === this.data.buttonValue;
    }
    if (rotation === undefined) {
      rotation = this.rotation;
    }
    const xfa = {
      path: this.data.fieldName,
      value: value ? this.data.buttonValue : ""
    };
    const name = Name.get(value ? this.data.buttonValue : "Off");
    const buffer = [];
    let parentData = null;
    if (value) {
      if (this.parent instanceof Ref) {
        const parent = evaluator.xref.fetch(this.parent);
        parent.set("V", name);
        await writeObject(this.parent, parent, buffer, evaluator.xref);
        parentData = buffer.join("");
        buffer.length = 0;
      } else if (this.parent instanceof Dict) {
        this.parent.set("V", name);
      }
    }
    dict.set("AS", name);
    dict.set("M", `D:${getModificationDate()}`);
    const maybeMK = this._getMKDict(rotation);
    if (maybeMK) {
      dict.set("MK", maybeMK);
    }
    await writeObject(this.ref, dict, buffer, evaluator.xref);
    const newRefs = [{
      ref: this.ref,
      data: buffer.join(""),
      xfa
    }];
    if (parentData) {
      newRefs.push({
        ref: this.parent,
        data: parentData,
        xfa: null
      });
    }
    return newRefs;
  }
  _getDefaultCheckedAppearance(params, type) {
    const width = this.data.rect[2] - this.data.rect[0];
    const height = this.data.rect[3] - this.data.rect[1];
    const bbox = [0, 0, width, height];
    const FONT_RATIO = 0.8;
    const fontSize = Math.min(width, height) * FONT_RATIO;
    let metrics, char;
    if (type === "check") {
      metrics = {
        width: 0.755 * fontSize,
        height: 0.705 * fontSize
      };
      char = "\x33";
    } else if (type === "disc") {
      metrics = {
        width: 0.791 * fontSize,
        height: 0.705 * fontSize
      };
      char = "\x6C";
    } else {
      unreachable(`_getDefaultCheckedAppearance - unsupported type: ${type}`);
    }
    const xShift = numberToString((width - metrics.width) / 2);
    const yShift = numberToString((height - metrics.height) / 2);
    const appearance = `q BT /PdfJsZaDb ${fontSize} Tf 0 g ${xShift} ${yShift} Td (${char}) Tj ET Q`;
    const appearanceStreamDict = new Dict(params.xref);
    appearanceStreamDict.set("FormType", 1);
    appearanceStreamDict.set("Subtype", Name.get("Form"));
    appearanceStreamDict.set("Type", Name.get("XObject"));
    appearanceStreamDict.set("BBox", bbox);
    appearanceStreamDict.set("Matrix", [1, 0, 0, 1, 0, 0]);
    appearanceStreamDict.set("Length", appearance.length);
    const resources = new Dict(params.xref);
    const font = new Dict(params.xref);
    font.set("PdfJsZaDb", this.fallbackFontDict);
    resources.set("Font", font);
    appearanceStreamDict.set("Resources", resources);
    this.checkedAppearance = new StringStream(appearance);
    this.checkedAppearance.dict = appearanceStreamDict;
    this._streams.push(this.checkedAppearance);
  }
  _processCheckBox(params) {
    const customAppearance = params.dict.get("AP");
    if (!(customAppearance instanceof Dict)) {
      return;
    }
    const normalAppearance = customAppearance.get("N");
    if (!(normalAppearance instanceof Dict)) {
      return;
    }
    const asValue = this._decodeFormValue(params.dict.get("AS"));
    if (typeof asValue === "string") {
      this.data.fieldValue = asValue;
    }
    const yes = this.data.fieldValue !== null && this.data.fieldValue !== "Off" ? this.data.fieldValue : "Yes";
    const exportValues = normalAppearance.getKeys();
    if (exportValues.length === 0) {
      exportValues.push("Off", yes);
    } else if (exportValues.length === 1) {
      if (exportValues[0] === "Off") {
        exportValues.push(yes);
      } else {
        exportValues.unshift("Off");
      }
    } else if (exportValues.includes(yes)) {
      exportValues.length = 0;
      exportValues.push("Off", yes);
    } else {
      const otherYes = exportValues.find(v => v !== "Off");
      exportValues.length = 0;
      exportValues.push("Off", otherYes);
    }
    if (!exportValues.includes(this.data.fieldValue)) {
      this.data.fieldValue = "Off";
    }
    this.data.exportValue = exportValues[1];
    const checkedAppearance = normalAppearance.get(this.data.exportValue);
    this.checkedAppearance = checkedAppearance instanceof BaseStream ? checkedAppearance : null;
    const uncheckedAppearance = normalAppearance.get("Off");
    this.uncheckedAppearance = uncheckedAppearance instanceof BaseStream ? uncheckedAppearance : null;
    if (this.checkedAppearance) {
      this._streams.push(this.checkedAppearance);
    } else {
      this._getDefaultCheckedAppearance(params, "check");
    }
    if (this.uncheckedAppearance) {
      this._streams.push(this.uncheckedAppearance);
    }
    this._fallbackFontDict = this.fallbackFontDict;
    if (this.data.defaultFieldValue === null) {
      this.data.defaultFieldValue = "Off";
    }
  }
  _processRadioButton(params) {
    this.data.buttonValue = null;
    const fieldParent = params.dict.get("Parent");
    if (fieldParent instanceof Dict) {
      this.parent = params.dict.getRaw("Parent");
      const fieldParentValue = fieldParent.get("V");
      if (fieldParentValue instanceof Name) {
        this.data.fieldValue = this._decodeFormValue(fieldParentValue);
      }
    }
    const appearanceStates = params.dict.get("AP");
    if (!(appearanceStates instanceof Dict)) {
      return;
    }
    const normalAppearance = appearanceStates.get("N");
    if (!(normalAppearance instanceof Dict)) {
      return;
    }
    for (const key of normalAppearance.getKeys()) {
      if (key !== "Off") {
        this.data.buttonValue = this._decodeFormValue(key);
        break;
      }
    }
    const checkedAppearance = normalAppearance.get(this.data.buttonValue);
    this.checkedAppearance = checkedAppearance instanceof BaseStream ? checkedAppearance : null;
    const uncheckedAppearance = normalAppearance.get("Off");
    this.uncheckedAppearance = uncheckedAppearance instanceof BaseStream ? uncheckedAppearance : null;
    if (this.checkedAppearance) {
      this._streams.push(this.checkedAppearance);
    } else {
      this._getDefaultCheckedAppearance(params, "disc");
    }
    if (this.uncheckedAppearance) {
      this._streams.push(this.uncheckedAppearance);
    }
    this._fallbackFontDict = this.fallbackFontDict;
    if (this.data.defaultFieldValue === null) {
      this.data.defaultFieldValue = "Off";
    }
  }
  _processPushButton(params) {
    const {
      dict,
      annotationGlobals
    } = params;
    if (!dict.has("A") && !dict.has("AA") && !this.data.alternativeText) {
      warn("Push buttons without action dictionaries are not supported");
      return;
    }
    this.data.isTooltipOnly = !dict.has("A") && !dict.has("AA");
    Catalog.parseDestDictionary({
      destDict: dict,
      resultObj: this.data,
      docBaseUrl: annotationGlobals.baseUrl,
      docAttachments: annotationGlobals.attachments
    });
  }
  getFieldObject() {
    let type = "button";
    let exportValues;
    if (this.data.checkBox) {
      type = "checkbox";
      exportValues = this.data.exportValue;
    } else if (this.data.radioButton) {
      type = "radiobutton";
      exportValues = this.data.buttonValue;
    }
    return {
      id: this.data.id,
      value: this.data.fieldValue || "Off",
      defaultValue: this.data.defaultFieldValue,
      exportValues,
      editable: !this.data.readOnly,
      name: this.data.fieldName,
      rect: this.data.rect,
      hidden: this.data.hidden,
      actions: this.data.actions,
      page: this.data.pageIndex,
      strokeColor: this.data.borderColor,
      fillColor: this.data.backgroundColor,
      rotation: this.rotation,
      type
    };
  }
  get fallbackFontDict() {
    const dict = new Dict();
    dict.set("BaseFont", Name.get("ZapfDingbats"));
    dict.set("Type", Name.get("FallbackType"));
    dict.set("Subtype", Name.get("FallbackType"));
    dict.set("Encoding", Name.get("ZapfDingbatsEncoding"));
    return shadow(this, "fallbackFontDict", dict);
  }
}
class ChoiceWidgetAnnotation extends WidgetAnnotation {
  constructor(params) {
    super(params);
    const {
      dict,
      xref
    } = params;
    this.indices = dict.getArray("I");
    this.hasIndices = Array.isArray(this.indices) && this.indices.length > 0;
    this.data.options = [];
    const options = getInheritableProperty({
      dict,
      key: "Opt"
    });
    if (Array.isArray(options)) {
      for (let i = 0, ii = options.length; i < ii; i++) {
        const option = xref.fetchIfRef(options[i]);
        const isOptionArray = Array.isArray(option);
        this.data.options[i] = {
          exportValue: this._decodeFormValue(isOptionArray ? xref.fetchIfRef(option[0]) : option),
          displayValue: this._decodeFormValue(isOptionArray ? xref.fetchIfRef(option[1]) : option)
        };
      }
    }
    if (!this.hasIndices) {
      if (typeof this.data.fieldValue === "string") {
        this.data.fieldValue = [this.data.fieldValue];
      } else if (!this.data.fieldValue) {
        this.data.fieldValue = [];
      }
    } else {
      this.data.fieldValue = [];
      const ii = this.data.options.length;
      for (const i of this.indices) {
        if (Number.isInteger(i) && i >= 0 && i < ii) {
          this.data.fieldValue.push(this.data.options[i].exportValue);
        }
      }
    }
    this.data.combo = this.hasFieldFlag(AnnotationFieldFlag.COMBO);
    this.data.multiSelect = this.hasFieldFlag(AnnotationFieldFlag.MULTISELECT);
    this._hasText = true;
  }
  getFieldObject() {
    const type = this.data.combo ? "combobox" : "listbox";
    const value = this.data.fieldValue.length > 0 ? this.data.fieldValue[0] : null;
    return {
      id: this.data.id,
      value,
      defaultValue: this.data.defaultFieldValue,
      editable: !this.data.readOnly,
      name: this.data.fieldName,
      rect: this.data.rect,
      numItems: this.data.fieldValue.length,
      multipleSelection: this.data.multiSelect,
      hidden: this.data.hidden,
      actions: this.data.actions,
      items: this.data.options,
      page: this.data.pageIndex,
      strokeColor: this.data.borderColor,
      fillColor: this.data.backgroundColor,
      rotation: this.rotation,
      type
    };
  }
  amendSavedDict(annotationStorage, dict) {
    if (!this.hasIndices) {
      return;
    }
    let values = annotationStorage?.get(this.data.id)?.value;
    if (!Array.isArray(values)) {
      values = [values];
    }
    const indices = [];
    const {
      options
    } = this.data;
    for (let i = 0, j = 0, ii = options.length; i < ii; i++) {
      if (options[i].exportValue === values[j]) {
        indices.push(i);
        j += 1;
      }
    }
    dict.set("I", indices);
  }
  async _getAppearance(evaluator, task, intent, annotationStorage) {
    if (this.data.combo) {
      return super._getAppearance(evaluator, task, intent, annotationStorage);
    }
    let exportedValue, rotation;
    const storageEntry = annotationStorage?.get(this.data.id);
    if (storageEntry) {
      rotation = storageEntry.rotation;
      exportedValue = storageEntry.value;
    }
    if (rotation === undefined && exportedValue === undefined && !this._needAppearances) {
      return null;
    }
    if (exportedValue === undefined) {
      exportedValue = this.data.fieldValue;
    } else if (!Array.isArray(exportedValue)) {
      exportedValue = [exportedValue];
    }
    const defaultPadding = 1;
    const defaultHPadding = 2;
    let totalHeight = this.data.rect[3] - this.data.rect[1];
    let totalWidth = this.data.rect[2] - this.data.rect[0];
    if (rotation === 90 || rotation === 270) {
      [totalWidth, totalHeight] = [totalHeight, totalWidth];
    }
    const lineCount = this.data.options.length;
    const valueIndices = [];
    for (let i = 0; i < lineCount; i++) {
      const {
        exportValue
      } = this.data.options[i];
      if (exportedValue.includes(exportValue)) {
        valueIndices.push(i);
      }
    }
    if (!this._defaultAppearance) {
      this.data.defaultAppearanceData = parseDefaultAppearance(this._defaultAppearance = "/Helvetica 0 Tf 0 g");
    }
    const font = await WidgetAnnotation._getFontData(evaluator, task, this.data.defaultAppearanceData, this._fieldResources.mergedResources);
    let defaultAppearance;
    let {
      fontSize
    } = this.data.defaultAppearanceData;
    if (!fontSize) {
      const lineHeight = (totalHeight - defaultPadding) / lineCount;
      let lineWidth = -1;
      let value;
      for (const {
        displayValue
      } of this.data.options) {
        const width = this._getTextWidth(displayValue, font);
        if (width > lineWidth) {
          lineWidth = width;
          value = displayValue;
        }
      }
      [defaultAppearance, fontSize] = this._computeFontSize(lineHeight, totalWidth - 2 * defaultHPadding, value, font, -1);
    } else {
      defaultAppearance = this._defaultAppearance;
    }
    const lineHeight = fontSize * LINE_FACTOR;
    const vPadding = (lineHeight - fontSize) / 2;
    const numberOfVisibleLines = Math.floor(totalHeight / lineHeight);
    let firstIndex = 0;
    if (valueIndices.length > 0) {
      const minIndex = Math.min(...valueIndices);
      const maxIndex = Math.max(...valueIndices);
      firstIndex = Math.max(0, maxIndex - numberOfVisibleLines + 1);
      if (firstIndex > minIndex) {
        firstIndex = minIndex;
      }
    }
    const end = Math.min(firstIndex + numberOfVisibleLines + 1, lineCount);
    const buf = ["/Tx BMC q", `1 1 ${totalWidth} ${totalHeight} re W n`];
    if (valueIndices.length) {
      buf.push("0.600006 0.756866 0.854904 rg");
      for (const index of valueIndices) {
        if (firstIndex <= index && index < end) {
          buf.push(`1 ${totalHeight - (index - firstIndex + 1) * lineHeight} ${totalWidth} ${lineHeight} re f`);
        }
      }
    }
    buf.push("BT", defaultAppearance, `1 0 0 1 0 ${totalHeight} Tm`);
    const prevInfo = {
      shift: 0
    };
    for (let i = firstIndex; i < end; i++) {
      const {
        displayValue
      } = this.data.options[i];
      const vpadding = i === firstIndex ? vPadding : 0;
      buf.push(this._renderText(displayValue, font, fontSize, totalWidth, 0, prevInfo, defaultHPadding, -lineHeight + vpadding));
    }
    buf.push("ET Q EMC");
    return buf.join("\n");
  }
}
class SignatureWidgetAnnotation extends WidgetAnnotation {
  constructor(params) {
    super(params);
    this.data.fieldValue = null;
    this.data.hasOwnCanvas = this.data.noRotate;
    this.data.noHTML = !this.data.hasOwnCanvas;
  }
  getFieldObject() {
    return {
      id: this.data.id,
      value: null,
      page: this.data.pageIndex,
      type: "signature"
    };
  }
}
class TextAnnotation extends MarkupAnnotation {
  constructor(params) {
    const DEFAULT_ICON_SIZE = 22;
    super(params);
    this.data.noRotate = true;
    this.data.hasOwnCanvas = this.data.noRotate;
    this.data.noHTML = false;
    const {
      dict
    } = params;
    this.data.annotationType = AnnotationType.TEXT;
    if (this.data.hasAppearance) {
      this.data.name = "NoIcon";
    } else {
      this.data.rect[1] = this.data.rect[3] - DEFAULT_ICON_SIZE;
      this.data.rect[2] = this.data.rect[0] + DEFAULT_ICON_SIZE;
      this.data.name = dict.has("Name") ? dict.get("Name").name : "Note";
    }
    if (dict.has("State")) {
      this.data.state = dict.get("State") || null;
      this.data.stateModel = dict.get("StateModel") || null;
    } else {
      this.data.state = null;
      this.data.stateModel = null;
    }
  }
}
class LinkAnnotation extends Annotation {
  constructor(params) {
    super(params);
    const {
      dict,
      annotationGlobals
    } = params;
    this.data.annotationType = AnnotationType.LINK;
    this.data.noHTML = false;
    const quadPoints = getQuadPoints(dict, this.rectangle);
    if (quadPoints) {
      this.data.quadPoints = quadPoints;
    }
    this.data.borderColor ||= this.data.color;
    Catalog.parseDestDictionary({
      destDict: dict,
      resultObj: this.data,
      docBaseUrl: annotationGlobals.baseUrl,
      docAttachments: annotationGlobals.attachments
    });
  }
}
class PopupAnnotation extends Annotation {
  constructor(params) {
    super(params);
    const {
      dict
    } = params;
    this.data.annotationType = AnnotationType.POPUP;
    this.data.noHTML = false;
    if (this.data.rect[0] === this.data.rect[2] || this.data.rect[1] === this.data.rect[3]) {
      this.data.rect = null;
    }
    let parentItem = dict.get("Parent");
    if (!parentItem) {
      warn("Popup annotation has a missing or invalid parent annotation.");
      return;
    }
    this.data.parentRect = lookupNormalRect(parentItem.getArray("Rect"), null);
    const rt = parentItem.get("RT");
    if (isName(rt, AnnotationReplyType.GROUP)) {
      parentItem = parentItem.get("IRT");
    }
    if (!parentItem.has("M")) {
      this.data.modificationDate = null;
    } else {
      this.setModificationDate(parentItem.get("M"));
      this.data.modificationDate = this.modificationDate;
    }
    if (!parentItem.has("C")) {
      this.data.color = null;
    } else {
      this.setColor(parentItem.getArray("C"));
      this.data.color = this.color;
    }
    if (!this.viewable) {
      const parentFlags = parentItem.get("F");
      if (this._isViewable(parentFlags)) {
        this.setFlags(parentFlags);
      }
    }
    this.setTitle(parentItem.get("T"));
    this.data.titleObj = this._title;
    this.setContents(parentItem.get("Contents"));
    this.data.contentsObj = this._contents;
    if (parentItem.has("RC")) {
      this.data.richText = XFAFactory.getRichTextAsHtml(parentItem.get("RC"));
    }
    this.data.open = !!dict.get("Open");
  }
}
class FreeTextAnnotation extends MarkupAnnotation {
  constructor(params) {
    super(params);
    this.data.hasOwnCanvas = this.data.noRotate;
    this.data.isEditable = !this.data.noHTML;
    this.data.noHTML = false;
    const {
      evaluatorOptions,
      xref
    } = params;
    this.data.annotationType = AnnotationType.FREETEXT;
    this.setDefaultAppearance(params);
    this._hasAppearance = !!this.appearance;
    if (this._hasAppearance) {
      const {
        fontColor,
        fontSize
      } = parseAppearanceStream(this.appearance, evaluatorOptions, xref);
      this.data.defaultAppearanceData.fontColor = fontColor;
      this.data.defaultAppearanceData.fontSize = fontSize || 10;
    } else {
      this.data.defaultAppearanceData.fontSize ||= 10;
      const {
        fontColor,
        fontSize
      } = this.data.defaultAppearanceData;
      if (this._contents.str) {
        this.data.textContent = this._contents.str.split(/\r\n?|\n/).map(line => line.trimEnd());
        const {
          coords,
          bbox,
          matrix
        } = FakeUnicodeFont.getFirstPositionInfo(this.rectangle, this.rotation, fontSize);
        this.data.textPosition = this._transformPoint(coords, bbox, matrix);
      }
      if (this._isOffscreenCanvasSupported) {
        const strokeAlpha = params.dict.get("CA");
        const fakeUnicodeFont = new FakeUnicodeFont(xref, "sans-serif");
        this.appearance = fakeUnicodeFont.createAppearance(this._contents.str, this.rectangle, this.rotation, fontSize, fontColor, strokeAlpha);
        this._streams.push(this.appearance);
      } else {
        warn("FreeTextAnnotation: OffscreenCanvas is not supported, annotation may not render correctly.");
      }
    }
  }
  get hasTextContent() {
    return this._hasAppearance;
  }
  static createNewDict(annotation, xref, {
    apRef,
    ap,
    oldAnnotation
  }) {
    const {
      color,
      fontSize,
      rect,
      rotation,
      user,
      value
    } = annotation;
    const freetext = oldAnnotation || new Dict(xref);
    freetext.set("Type", Name.get("Annot"));
    freetext.set("Subtype", Name.get("FreeText"));
    if (oldAnnotation) {
      freetext.set("M", `D:${getModificationDate()}`);
      freetext.delete("RC");
    } else {
      freetext.set("CreationDate", `D:${getModificationDate()}`);
    }
    freetext.set("Rect", rect);
    const da = `/Helv ${fontSize} Tf ${getPdfColor(color, true)}`;
    freetext.set("DA", da);
    freetext.set("Contents", stringToAsciiOrUTF16BE(value));
    freetext.set("F", 4);
    freetext.set("Border", [0, 0, 0]);
    freetext.set("Rotate", rotation);
    if (user) {
      freetext.set("T", stringToAsciiOrUTF16BE(user));
    }
    if (apRef || ap) {
      const n = new Dict(xref);
      freetext.set("AP", n);
      if (apRef) {
        n.set("N", apRef);
      } else {
        n.set("N", ap);
      }
    }
    return freetext;
  }
  static async createNewAppearanceStream(annotation, xref, params) {
    const {
      baseFontRef,
      evaluator,
      task
    } = params;
    const {
      color,
      fontSize,
      rect,
      rotation,
      value
    } = annotation;
    const resources = new Dict(xref);
    const font = new Dict(xref);
    if (baseFontRef) {
      font.set("Helv", baseFontRef);
    } else {
      const baseFont = new Dict(xref);
      baseFont.set("BaseFont", Name.get("Helvetica"));
      baseFont.set("Type", Name.get("Font"));
      baseFont.set("Subtype", Name.get("Type1"));
      baseFont.set("Encoding", Name.get("WinAnsiEncoding"));
      font.set("Helv", baseFont);
    }
    resources.set("Font", font);
    const helv = await WidgetAnnotation._getFontData(evaluator, task, {
      fontName: "Helv",
      fontSize
    }, resources);
    const [x1, y1, x2, y2] = rect;
    let w = x2 - x1;
    let h = y2 - y1;
    if (rotation % 180 !== 0) {
      [w, h] = [h, w];
    }
    const lines = value.split("\n");
    const scale = fontSize / 1000;
    let totalWidth = -Infinity;
    const encodedLines = [];
    for (let line of lines) {
      const encoded = helv.encodeString(line);
      if (encoded.length > 1) {
        return null;
      }
      line = encoded.join("");
      encodedLines.push(line);
      let lineWidth = 0;
      const glyphs = helv.charsToGlyphs(line);
      for (const glyph of glyphs) {
        lineWidth += glyph.width * scale;
      }
      totalWidth = Math.max(totalWidth, lineWidth);
    }
    let hscale = 1;
    if (totalWidth > w) {
      hscale = w / totalWidth;
    }
    let vscale = 1;
    const lineHeight = LINE_FACTOR * fontSize;
    const lineAscent = (LINE_FACTOR - LINE_DESCENT_FACTOR) * fontSize;
    const totalHeight = lineHeight * lines.length;
    if (totalHeight > h) {
      vscale = h / totalHeight;
    }
    const fscale = Math.min(hscale, vscale);
    const newFontSize = fontSize * fscale;
    let firstPoint, clipBox, matrix;
    switch (rotation) {
      case 0:
        matrix = [1, 0, 0, 1];
        clipBox = [rect[0], rect[1], w, h];
        firstPoint = [rect[0], rect[3] - lineAscent];
        break;
      case 90:
        matrix = [0, 1, -1, 0];
        clipBox = [rect[1], -rect[2], w, h];
        firstPoint = [rect[1], -rect[0] - lineAscent];
        break;
      case 180:
        matrix = [-1, 0, 0, -1];
        clipBox = [-rect[2], -rect[3], w, h];
        firstPoint = [-rect[2], -rect[1] - lineAscent];
        break;
      case 270:
        matrix = [0, -1, 1, 0];
        clipBox = [-rect[3], rect[0], w, h];
        firstPoint = [-rect[3], rect[2] - lineAscent];
        break;
    }
    const buffer = ["q", `${matrix.join(" ")} 0 0 cm`, `${clipBox.join(" ")} re W n`, `BT`, `${getPdfColor(color, true)}`, `0 Tc /Helv ${numberToString(newFontSize)} Tf`];
    buffer.push(`${firstPoint.join(" ")} Td (${escapeString(encodedLines[0])}) Tj`);
    const vShift = numberToString(lineHeight);
    for (let i = 1, ii = encodedLines.length; i < ii; i++) {
      const line = encodedLines[i];
      buffer.push(`0 -${vShift} Td (${escapeString(line)}) Tj`);
    }
    buffer.push("ET", "Q");
    const appearance = buffer.join("\n");
    const appearanceStreamDict = new Dict(xref);
    appearanceStreamDict.set("FormType", 1);
    appearanceStreamDict.set("Subtype", Name.get("Form"));
    appearanceStreamDict.set("Type", Name.get("XObject"));
    appearanceStreamDict.set("BBox", rect);
    appearanceStreamDict.set("Resources", resources);
    appearanceStreamDict.set("Matrix", [1, 0, 0, 1, -rect[0], -rect[1]]);
    const ap = new StringStream(appearance);
    ap.dict = appearanceStreamDict;
    return ap;
  }
}
class LineAnnotation extends MarkupAnnotation {
  constructor(params) {
    super(params);
    const {
      dict,
      xref
    } = params;
    this.data.annotationType = AnnotationType.LINE;
    this.data.hasOwnCanvas = this.data.noRotate;
    this.data.noHTML = false;
    const lineCoordinates = lookupRect(dict.getArray("L"), [0, 0, 0, 0]);
    this.data.lineCoordinates = Util.normalizeRect(lineCoordinates);
    if (!this.appearance) {
      const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0];
      const strokeAlpha = dict.get("CA");
      const interiorColor = getRgbColor(dict.getArray("IC"), null);
      const fillColor = interiorColor ? getPdfColorArray(interiorColor) : null;
      const fillAlpha = fillColor ? strokeAlpha : null;
      const borderWidth = this.borderStyle.width || 1,
        borderAdjust = 2 * borderWidth;
      const bbox = [this.data.lineCoordinates[0] - borderAdjust, this.data.lineCoordinates[1] - borderAdjust, this.data.lineCoordinates[2] + borderAdjust, this.data.lineCoordinates[3] + borderAdjust];
      if (!Util.intersect(this.rectangle, bbox)) {
        this.rectangle = bbox;
      }
      this._setDefaultAppearance({
        xref,
        extra: `${borderWidth} w`,
        strokeColor,
        fillColor,
        strokeAlpha,
        fillAlpha,
        pointsCallback: (buffer, points) => {
          buffer.push(`${lineCoordinates[0]} ${lineCoordinates[1]} m`, `${lineCoordinates[2]} ${lineCoordinates[3]} l`, "S");
          return [points[0] - borderWidth, points[2] + borderWidth, points[7] - borderWidth, points[3] + borderWidth];
        }
      });
    }
  }
}
class SquareAnnotation extends MarkupAnnotation {
  constructor(params) {
    super(params);
    const {
      dict,
      xref
    } = params;
    this.data.annotationType = AnnotationType.SQUARE;
    this.data.hasOwnCanvas = this.data.noRotate;
    this.data.noHTML = false;
    if (!this.appearance) {
      const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0];
      const strokeAlpha = dict.get("CA");
      const interiorColor = getRgbColor(dict.getArray("IC"), null);
      const fillColor = interiorColor ? getPdfColorArray(interiorColor) : null;
      const fillAlpha = fillColor ? strokeAlpha : null;
      if (this.borderStyle.width === 0 && !fillColor) {
        return;
      }
      this._setDefaultAppearance({
        xref,
        extra: `${this.borderStyle.width} w`,
        strokeColor,
        fillColor,
        strokeAlpha,
        fillAlpha,
        pointsCallback: (buffer, points) => {
          const x = points[4] + this.borderStyle.width / 2;
          const y = points[5] + this.borderStyle.width / 2;
          const width = points[6] - points[4] - this.borderStyle.width;
          const height = points[3] - points[7] - this.borderStyle.width;
          buffer.push(`${x} ${y} ${width} ${height} re`);
          if (fillColor) {
            buffer.push("B");
          } else {
            buffer.push("S");
          }
          return [points[0], points[2], points[7], points[3]];
        }
      });
    }
  }
}
class CircleAnnotation extends MarkupAnnotation {
  constructor(params) {
    super(params);
    const {
      dict,
      xref
    } = params;
    this.data.annotationType = AnnotationType.CIRCLE;
    if (!this.appearance) {
      const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0];
      const strokeAlpha = dict.get("CA");
      const interiorColor = getRgbColor(dict.getArray("IC"), null);
      const fillColor = interiorColor ? getPdfColorArray(interiorColor) : null;
      const fillAlpha = fillColor ? strokeAlpha : null;
      if (this.borderStyle.width === 0 && !fillColor) {
        return;
      }
      const controlPointsDistance = 4 / 3 * Math.tan(Math.PI / (2 * 4));
      this._setDefaultAppearance({
        xref,
        extra: `${this.borderStyle.width} w`,
        strokeColor,
        fillColor,
        strokeAlpha,
        fillAlpha,
        pointsCallback: (buffer, points) => {
          const x0 = points[0] + this.borderStyle.width / 2;
          const y0 = points[1] - this.borderStyle.width / 2;
          const x1 = points[6] - this.borderStyle.width / 2;
          const y1 = points[7] + this.borderStyle.width / 2;
          const xMid = x0 + (x1 - x0) / 2;
          const yMid = y0 + (y1 - y0) / 2;
          const xOffset = (x1 - x0) / 2 * controlPointsDistance;
          const yOffset = (y1 - y0) / 2 * controlPointsDistance;
          buffer.push(`${xMid} ${y1} m`, `${xMid + xOffset} ${y1} ${x1} ${yMid + yOffset} ${x1} ${yMid} c`, `${x1} ${yMid - yOffset} ${xMid + xOffset} ${y0} ${xMid} ${y0} c`, `${xMid - xOffset} ${y0} ${x0} ${yMid - yOffset} ${x0} ${yMid} c`, `${x0} ${yMid + yOffset} ${xMid - xOffset} ${y1} ${xMid} ${y1} c`, "h");
          if (fillColor) {
            buffer.push("B");
          } else {
            buffer.push("S");
          }
          return [points[0], points[2], points[7], points[3]];
        }
      });
    }
  }
}
class PolylineAnnotation extends MarkupAnnotation {
  constructor(params) {
    super(params);
    const {
      dict,
      xref
    } = params;
    this.data.annotationType = AnnotationType.POLYLINE;
    this.data.hasOwnCanvas = this.data.noRotate;
    this.data.noHTML = false;
    this.data.vertices = null;
    const rawVertices = dict.getArray("Vertices");
    if (!isNumberArray(rawVertices, null)) {
      return;
    }
    const vertices = this.data.vertices = Float32Array.from(rawVertices);
    if (!this.appearance) {
      const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0];
      const strokeAlpha = dict.get("CA");
      const borderWidth = this.borderStyle.width || 1,
        borderAdjust = 2 * borderWidth;
      const bbox = [Infinity, Infinity, -Infinity, -Infinity];
      for (let i = 0, ii = vertices.length; i < ii; i += 2) {
        bbox[0] = Math.min(bbox[0], vertices[i] - borderAdjust);
        bbox[1] = Math.min(bbox[1], vertices[i + 1] - borderAdjust);
        bbox[2] = Math.max(bbox[2], vertices[i] + borderAdjust);
        bbox[3] = Math.max(bbox[3], vertices[i + 1] + borderAdjust);
      }
      if (!Util.intersect(this.rectangle, bbox)) {
        this.rectangle = bbox;
      }
      this._setDefaultAppearance({
        xref,
        extra: `${borderWidth} w`,
        strokeColor,
        strokeAlpha,
        pointsCallback: (buffer, points) => {
          for (let i = 0, ii = vertices.length; i < ii; i += 2) {
            buffer.push(`${vertices[i]} ${vertices[i + 1]} ${i === 0 ? "m" : "l"}`);
          }
          buffer.push("S");
          return [points[0], points[2], points[7], points[3]];
        }
      });
    }
  }
}
class PolygonAnnotation extends PolylineAnnotation {
  constructor(params) {
    super(params);
    this.data.annotationType = AnnotationType.POLYGON;
  }
}
class CaretAnnotation extends MarkupAnnotation {
  constructor(params) {
    super(params);
    this.data.annotationType = AnnotationType.CARET;
  }
}
class InkAnnotation extends MarkupAnnotation {
  constructor(params) {
    super(params);
    this.data.hasOwnCanvas = this.data.noRotate;
    this.data.noHTML = false;
    const {
      dict,
      xref
    } = params;
    this.data.annotationType = AnnotationType.INK;
    this.data.inkLists = [];
    const rawInkLists = dict.getArray("InkList");
    if (!Array.isArray(rawInkLists)) {
      return;
    }
    for (let i = 0, ii = rawInkLists.length; i < ii; ++i) {
      if (!Array.isArray(rawInkLists[i])) {
        continue;
      }
      const inkList = new Float32Array(rawInkLists[i].length);
      this.data.inkLists.push(inkList);
      for (let j = 0, jj = rawInkLists[i].length; j < jj; j += 2) {
        const x = xref.fetchIfRef(rawInkLists[i][j]),
          y = xref.fetchIfRef(rawInkLists[i][j + 1]);
        if (typeof x === "number" && typeof y === "number") {
          inkList[j] = x;
          inkList[j + 1] = y;
        }
      }
    }
    if (!this.appearance) {
      const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0];
      const strokeAlpha = dict.get("CA");
      const borderWidth = this.borderStyle.width || 1,
        borderAdjust = 2 * borderWidth;
      const bbox = [Infinity, Infinity, -Infinity, -Infinity];
      for (const inkList of this.data.inkLists) {
        for (let i = 0, ii = inkList.length; i < ii; i += 2) {
          bbox[0] = Math.min(bbox[0], inkList[i] - borderAdjust);
          bbox[1] = Math.min(bbox[1], inkList[i + 1] - borderAdjust);
          bbox[2] = Math.max(bbox[2], inkList[i] + borderAdjust);
          bbox[3] = Math.max(bbox[3], inkList[i + 1] + borderAdjust);
        }
      }
      if (!Util.intersect(this.rectangle, bbox)) {
        this.rectangle = bbox;
      }
      this._setDefaultAppearance({
        xref,
        extra: `${borderWidth} w`,
        strokeColor,
        strokeAlpha,
        pointsCallback: (buffer, points) => {
          for (const inkList of this.data.inkLists) {
            for (let i = 0, ii = inkList.length; i < ii; i += 2) {
              buffer.push(`${inkList[i]} ${inkList[i + 1]} ${i === 0 ? "m" : "l"}`);
            }
            buffer.push("S");
          }
          return [points[0], points[2], points[7], points[3]];
        }
      });
    }
  }
  static createNewDict(annotation, xref, {
    apRef,
    ap
  }) {
    const {
      color,
      opacity,
      paths,
      outlines,
      rect,
      rotation,
      thickness
    } = annotation;
    const ink = new Dict(xref);
    ink.set("Type", Name.get("Annot"));
    ink.set("Subtype", Name.get("Ink"));
    ink.set("CreationDate", `D:${getModificationDate()}`);
    ink.set("Rect", rect);
    ink.set("InkList", outlines?.points || paths.map(p => p.points));
    ink.set("F", 4);
    ink.set("Rotate", rotation);
    if (outlines) {
      ink.set("IT", Name.get("InkHighlight"));
    }
    const bs = new Dict(xref);
    ink.set("BS", bs);
    bs.set("W", thickness);
    ink.set("C", Array.from(color, c => c / 255));
    ink.set("CA", opacity);
    const n = new Dict(xref);
    ink.set("AP", n);
    if (apRef) {
      n.set("N", apRef);
    } else {
      n.set("N", ap);
    }
    return ink;
  }
  static async createNewAppearanceStream(annotation, xref, params) {
    if (annotation.outlines) {
      return this.createNewAppearanceStreamForHighlight(annotation, xref, params);
    }
    const {
      color,
      rect,
      paths,
      thickness,
      opacity
    } = annotation;
    const appearanceBuffer = [`${thickness} w 1 J 1 j`, `${getPdfColor(color, false)}`];
    if (opacity !== 1) {
      appearanceBuffer.push("/R0 gs");
    }
    const buffer = [];
    for (const {
      bezier
    } of paths) {
      buffer.length = 0;
      buffer.push(`${numberToString(bezier[0])} ${numberToString(bezier[1])} m`);
      if (bezier.length === 2) {
        buffer.push(`${numberToString(bezier[0])} ${numberToString(bezier[1])} l S`);
      } else {
        for (let i = 2, ii = bezier.length; i < ii; i += 6) {
          const curve = bezier.slice(i, i + 6).map(numberToString).join(" ");
          buffer.push(`${curve} c`);
        }
        buffer.push("S");
      }
      appearanceBuffer.push(buffer.join("\n"));
    }
    const appearance = appearanceBuffer.join("\n");
    const appearanceStreamDict = new Dict(xref);
    appearanceStreamDict.set("FormType", 1);
    appearanceStreamDict.set("Subtype", Name.get("Form"));
    appearanceStreamDict.set("Type", Name.get("XObject"));
    appearanceStreamDict.set("BBox", rect);
    appearanceStreamDict.set("Length", appearance.length);
    if (opacity !== 1) {
      const resources = new Dict(xref);
      const extGState = new Dict(xref);
      const r0 = new Dict(xref);
      r0.set("CA", opacity);
      r0.set("Type", Name.get("ExtGState"));
      extGState.set("R0", r0);
      resources.set("ExtGState", extGState);
      appearanceStreamDict.set("Resources", resources);
    }
    const ap = new StringStream(appearance);
    ap.dict = appearanceStreamDict;
    return ap;
  }
  static async createNewAppearanceStreamForHighlight(annotation, xref, params) {
    const {
      color,
      rect,
      outlines: {
        outline
      },
      opacity
    } = annotation;
    const appearanceBuffer = [`${getPdfColor(color, true)}`, "/R0 gs"];
    appearanceBuffer.push(`${numberToString(outline[4])} ${numberToString(outline[5])} m`);
    for (let i = 6, ii = outline.length; i < ii; i += 6) {
      if (isNaN(outline[i]) || outline[i] === null) {
        appearanceBuffer.push(`${numberToString(outline[i + 4])} ${numberToString(outline[i + 5])} l`);
      } else {
        const curve = outline.slice(i, i + 6).map(numberToString).join(" ");
        appearanceBuffer.push(`${curve} c`);
      }
    }
    appearanceBuffer.push("h f");
    const appearance = appearanceBuffer.join("\n");
    const appearanceStreamDict = new Dict(xref);
    appearanceStreamDict.set("FormType", 1);
    appearanceStreamDict.set("Subtype", Name.get("Form"));
    appearanceStreamDict.set("Type", Name.get("XObject"));
    appearanceStreamDict.set("BBox", rect);
    appearanceStreamDict.set("Length", appearance.length);
    const resources = new Dict(xref);
    const extGState = new Dict(xref);
    resources.set("ExtGState", extGState);
    appearanceStreamDict.set("Resources", resources);
    const r0 = new Dict(xref);
    extGState.set("R0", r0);
    r0.set("BM", Name.get("Multiply"));
    if (opacity !== 1) {
      r0.set("ca", opacity);
      r0.set("Type", Name.get("ExtGState"));
    }
    const ap = new StringStream(appearance);
    ap.dict = appearanceStreamDict;
    return ap;
  }
}
class HighlightAnnotation extends MarkupAnnotation {
  constructor(params) {
    super(params);
    const {
      dict,
      xref
    } = params;
    this.data.annotationType = AnnotationType.HIGHLIGHT;
    const quadPoints = this.data.quadPoints = getQuadPoints(dict, null);
    if (quadPoints) {
      const resources = this.appearance?.dict.get("Resources");
      if (!this.appearance || !resources?.has("ExtGState")) {
        if (this.appearance) {
          warn("HighlightAnnotation - ignoring built-in appearance stream.");
        }
        const fillColor = this.color ? getPdfColorArray(this.color) : [1, 1, 0];
        const fillAlpha = dict.get("CA");
        this._setDefaultAppearance({
          xref,
          fillColor,
          blendMode: "Multiply",
          fillAlpha,
          pointsCallback: (buffer, points) => {
            buffer.push(`${points[0]} ${points[1]} m`, `${points[2]} ${points[3]} l`, `${points[6]} ${points[7]} l`, `${points[4]} ${points[5]} l`, "f");
            return [points[0], points[2], points[7], points[3]];
          }
        });
      }
    } else {
      this.data.popupRef = null;
    }
  }
  static createNewDict(annotation, xref, {
    apRef,
    ap
  }) {
    const {
      color,
      opacity,
      rect,
      rotation,
      user,
      quadPoints
    } = annotation;
    const highlight = new Dict(xref);
    highlight.set("Type", Name.get("Annot"));
    highlight.set("Subtype", Name.get("Highlight"));
    highlight.set("CreationDate", `D:${getModificationDate()}`);
    highlight.set("Rect", rect);
    highlight.set("F", 4);
    highlight.set("Border", [0, 0, 0]);
    highlight.set("Rotate", rotation);
    highlight.set("QuadPoints", quadPoints);
    highlight.set("C", Array.from(color, c => c / 255));
    highlight.set("CA", opacity);
    if (user) {
      highlight.set("T", stringToAsciiOrUTF16BE(user));
    }
    if (apRef || ap) {
      const n = new Dict(xref);
      highlight.set("AP", n);
      n.set("N", apRef || ap);
    }
    return highlight;
  }
  static async createNewAppearanceStream(annotation, xref, params) {
    const {
      color,
      rect,
      outlines,
      opacity
    } = annotation;
    const appearanceBuffer = [`${getPdfColor(color, true)}`, "/R0 gs"];
    const buffer = [];
    for (const outline of outlines) {
      buffer.length = 0;
      buffer.push(`${numberToString(outline[0])} ${numberToString(outline[1])} m`);
      for (let i = 2, ii = outline.length; i < ii; i += 2) {
        buffer.push(`${numberToString(outline[i])} ${numberToString(outline[i + 1])} l`);
      }
      buffer.push("h");
      appearanceBuffer.push(buffer.join("\n"));
    }
    appearanceBuffer.push("f*");
    const appearance = appearanceBuffer.join("\n");
    const appearanceStreamDict = new Dict(xref);
    appearanceStreamDict.set("FormType", 1);
    appearanceStreamDict.set("Subtype", Name.get("Form"));
    appearanceStreamDict.set("Type", Name.get("XObject"));
    appearanceStreamDict.set("BBox", rect);
    appearanceStreamDict.set("Length", appearance.length);
    const resources = new Dict(xref);
    const extGState = new Dict(xref);
    resources.set("ExtGState", extGState);
    appearanceStreamDict.set("Resources", resources);
    const r0 = new Dict(xref);
    extGState.set("R0", r0);
    r0.set("BM", Name.get("Multiply"));
    if (opacity !== 1) {
      r0.set("ca", opacity);
      r0.set("Type", Name.get("ExtGState"));
    }
    const ap = new StringStream(appearance);
    ap.dict = appearanceStreamDict;
    return ap;
  }
}
class UnderlineAnnotation extends MarkupAnnotation {
  constructor(params) {
    super(params);
    const {
      dict,
      xref
    } = params;
    this.data.annotationType = AnnotationType.UNDERLINE;
    const quadPoints = this.data.quadPoints = getQuadPoints(dict, null);
    if (quadPoints) {
      if (!this.appearance) {
        const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0];
        const strokeAlpha = dict.get("CA");
        this._setDefaultAppearance({
          xref,
          extra: "[] 0 d 0.571 w",
          strokeColor,
          strokeAlpha,
          pointsCallback: (buffer, points) => {
            buffer.push(`${points[4]} ${points[5] + 1.3} m`, `${points[6]} ${points[7] + 1.3} l`, "S");
            return [points[0], points[2], points[7], points[3]];
          }
        });
      }
    } else {
      this.data.popupRef = null;
    }
  }
}
class SquigglyAnnotation extends MarkupAnnotation {
  constructor(params) {
    super(params);
    const {
      dict,
      xref
    } = params;
    this.data.annotationType = AnnotationType.SQUIGGLY;
    const quadPoints = this.data.quadPoints = getQuadPoints(dict, null);
    if (quadPoints) {
      if (!this.appearance) {
        const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0];
        const strokeAlpha = dict.get("CA");
        this._setDefaultAppearance({
          xref,
          extra: "[] 0 d 1 w",
          strokeColor,
          strokeAlpha,
          pointsCallback: (buffer, points) => {
            const dy = (points[1] - points[5]) / 6;
            let shift = dy;
            let x = points[4];
            const y = points[5];
            const xEnd = points[6];
            buffer.push(`${x} ${y + shift} m`);
            do {
              x += 2;
              shift = shift === 0 ? dy : 0;
              buffer.push(`${x} ${y + shift} l`);
            } while (x < xEnd);
            buffer.push("S");
            return [points[4], xEnd, y - 2 * dy, y + 2 * dy];
          }
        });
      }
    } else {
      this.data.popupRef = null;
    }
  }
}
class StrikeOutAnnotation extends MarkupAnnotation {
  constructor(params) {
    super(params);
    const {
      dict,
      xref
    } = params;
    this.data.annotationType = AnnotationType.STRIKEOUT;
    const quadPoints = this.data.quadPoints = getQuadPoints(dict, null);
    if (quadPoints) {
      if (!this.appearance) {
        const strokeColor = this.color ? getPdfColorArray(this.color) : [0, 0, 0];
        const strokeAlpha = dict.get("CA");
        this._setDefaultAppearance({
          xref,
          extra: "[] 0 d 1 w",
          strokeColor,
          strokeAlpha,
          pointsCallback: (buffer, points) => {
            buffer.push(`${(points[0] + points[4]) / 2} ` + `${(points[1] + points[5]) / 2} m`, `${(points[2] + points[6]) / 2} ` + `${(points[3] + points[7]) / 2} l`, "S");
            return [points[0], points[2], points[7], points[3]];
          }
        });
      }
    } else {
      this.data.popupRef = null;
    }
  }
}
class StampAnnotation extends MarkupAnnotation {
  constructor(params) {
    super(params);
    this.data.annotationType = AnnotationType.STAMP;
    this.data.hasOwnCanvas = this.data.noRotate;
    this.data.noHTML = false;
  }
  static async createImage(bitmap, xref) {
    const {
      width,
      height
    } = bitmap;
    const canvas = new OffscreenCanvas(width, height);
    const ctx = canvas.getContext("2d", {
      alpha: true
    });
    ctx.drawImage(bitmap, 0, 0);
    const data = ctx.getImageData(0, 0, width, height).data;
    const buf32 = new Uint32Array(data.buffer);
    const hasAlpha = buf32.some(FeatureTest.isLittleEndian ? x => x >>> 24 !== 0xff : x => (x & 0xff) !== 0xff);
    if (hasAlpha) {
      ctx.fillStyle = "white";
      ctx.fillRect(0, 0, width, height);
      ctx.drawImage(bitmap, 0, 0);
    }
    const jpegBufferPromise = canvas.convertToBlob({
      type: "image/jpeg",
      quality: 1
    }).then(blob => blob.arrayBuffer());
    const xobjectName = Name.get("XObject");
    const imageName = Name.get("Image");
    const image = new Dict(xref);
    image.set("Type", xobjectName);
    image.set("Subtype", imageName);
    image.set("BitsPerComponent", 8);
    image.set("ColorSpace", Name.get("DeviceRGB"));
    image.set("Filter", Name.get("DCTDecode"));
    image.set("BBox", [0, 0, width, height]);
    image.set("Width", width);
    image.set("Height", height);
    let smaskStream = null;
    if (hasAlpha) {
      const alphaBuffer = new Uint8Array(buf32.length);
      if (FeatureTest.isLittleEndian) {
        for (let i = 0, ii = buf32.length; i < ii; i++) {
          alphaBuffer[i] = buf32[i] >>> 24;
        }
      } else {
        for (let i = 0, ii = buf32.length; i < ii; i++) {
          alphaBuffer[i] = buf32[i] & 0xff;
        }
      }
      const smask = new Dict(xref);
      smask.set("Type", xobjectName);
      smask.set("Subtype", imageName);
      smask.set("BitsPerComponent", 8);
      smask.set("ColorSpace", Name.get("DeviceGray"));
      smask.set("Width", width);
      smask.set("Height", height);
      smaskStream = new Stream(alphaBuffer, 0, 0, smask);
    }
    const imageStream = new Stream(await jpegBufferPromise, 0, 0, image);
    return {
      imageStream,
      smaskStream,
      width,
      height
    };
  }
  static createNewDict(annotation, xref, {
    apRef,
    ap
  }) {
    const {
      rect,
      rotation,
      user
    } = annotation;
    const stamp = new Dict(xref);
    stamp.set("Type", Name.get("Annot"));
    stamp.set("Subtype", Name.get("Stamp"));
    stamp.set("CreationDate", `D:${getModificationDate()}`);
    stamp.set("Rect", rect);
    stamp.set("F", 4);
    stamp.set("Border", [0, 0, 0]);
    stamp.set("Rotate", rotation);
    if (user) {
      stamp.set("T", stringToAsciiOrUTF16BE(user));
    }
    if (apRef || ap) {
      const n = new Dict(xref);
      stamp.set("AP", n);
      if (apRef) {
        n.set("N", apRef);
      } else {
        n.set("N", ap);
      }
    }
    return stamp;
  }
  static async createNewAppearanceStream(annotation, xref, params) {
    const {
      rotation
    } = annotation;
    const {
      imageRef,
      width,
      height
    } = params.image;
    const resources = new Dict(xref);
    const xobject = new Dict(xref);
    resources.set("XObject", xobject);
    xobject.set("Im0", imageRef);
    const appearance = `q ${width} 0 0 ${height} 0 0 cm /Im0 Do Q`;
    const appearanceStreamDict = new Dict(xref);
    appearanceStreamDict.set("FormType", 1);
    appearanceStreamDict.set("Subtype", Name.get("Form"));
    appearanceStreamDict.set("Type", Name.get("XObject"));
    appearanceStreamDict.set("BBox", [0, 0, width, height]);
    appearanceStreamDict.set("Resources", resources);
    if (rotation) {
      const matrix = getRotationMatrix(rotation, width, height);
      appearanceStreamDict.set("Matrix", matrix);
    }
    const ap = new StringStream(appearance);
    ap.dict = appearanceStreamDict;
    return ap;
  }
}
class FileAttachmentAnnotation extends MarkupAnnotation {
  constructor(params) {
    super(params);
    const {
      dict,
      xref
    } = params;
    const file = new FileSpec(dict.get("FS"), xref);
    this.data.annotationType = AnnotationType.FILEATTACHMENT;
    this.data.hasOwnCanvas = this.data.noRotate;
    this.data.noHTML = false;
    this.data.file = file.serializable;
    const name = dict.get("Name");
    this.data.name = name instanceof Name ? stringToPDFString(name.name) : "PushPin";
    const fillAlpha = dict.get("ca");
    this.data.fillAlpha = typeof fillAlpha === "number" && fillAlpha >= 0 && fillAlpha <= 1 ? fillAlpha : null;
  }
}

;// CONCATENATED MODULE: ./src/core/dataset_reader.js



function decodeString(str) {
  try {
    return stringToUTF8String(str);
  } catch (ex) {
    warn(`UTF-8 decoding failed: "${ex}".`);
    return str;
  }
}
class DatasetXMLParser extends SimpleXMLParser {
  constructor(options) {
    super(options);
    this.node = null;
  }
  onEndElement(name) {
    const node = super.onEndElement(name);
    if (node && name === "xfa:datasets") {
      this.node = node;
      throw new Error("Aborting DatasetXMLParser.");
    }
  }
}
class DatasetReader {
  constructor(data) {
    if (data.datasets) {
      this.node = new SimpleXMLParser({
        hasAttributes: true
      }).parseFromString(data.datasets).documentElement;
    } else {
      const parser = new DatasetXMLParser({
        hasAttributes: true
      });
      try {
        parser.parseFromString(data["xdp:xdp"]);
      } catch {}
      this.node = parser.node;
    }
  }
  getValue(path) {
    if (!this.node || !path) {
      return "";
    }
    const node = this.node.searchNode(parseXFAPath(path), 0);
    if (!node) {
      return "";
    }
    const first = node.firstChild;
    if (first?.nodeName === "value") {
      return node.children.map(child => decodeString(child.textContent));
    }
    return decodeString(node.textContent);
  }
}

;// CONCATENATED MODULE: ./src/core/xref.js






class XRef {
  #firstXRefStmPos = null;
  constructor(stream, pdfManager) {
    this.stream = stream;
    this.pdfManager = pdfManager;
    this.entries = [];
    this._xrefStms = new Set();
    this._cacheMap = new Map();
    this._pendingRefs = new RefSet();
    this._newPersistentRefNum = null;
    this._newTemporaryRefNum = null;
    this._persistentRefsCache = null;
  }
  getNewPersistentRef(obj) {
    if (this._newPersistentRefNum === null) {
      this._newPersistentRefNum = this.entries.length || 1;
    }
    const num = this._newPersistentRefNum++;
    this._cacheMap.set(num, obj);
    return Ref.get(num, 0);
  }
  getNewTemporaryRef() {
    if (this._newTemporaryRefNum === null) {
      this._newTemporaryRefNum = this.entries.length || 1;
      if (this._newPersistentRefNum) {
        this._persistentRefsCache = new Map();
        for (let i = this._newTemporaryRefNum; i < this._newPersistentRefNum; i++) {
          this._persistentRefsCache.set(i, this._cacheMap.get(i));
          this._cacheMap.delete(i);
        }
      }
    }
    return Ref.get(this._newTemporaryRefNum++, 0);
  }
  resetNewTemporaryRef() {
    this._newTemporaryRefNum = null;
    if (this._persistentRefsCache) {
      for (const [num, obj] of this._persistentRefsCache) {
        this._cacheMap.set(num, obj);
      }
    }
    this._persistentRefsCache = null;
  }
  setStartXRef(startXRef) {
    this.startXRefQueue = [startXRef];
  }
  parse(recoveryMode = false) {
    let trailerDict;
    if (!recoveryMode) {
      trailerDict = this.readXRef();
    } else {
      warn("Indexing all PDF objects");
      trailerDict = this.indexObjects();
    }
    trailerDict.assignXref(this);
    this.trailer = trailerDict;
    let encrypt;
    try {
      encrypt = trailerDict.get("Encrypt");
    } catch (ex) {
      if (ex instanceof MissingDataException) {
        throw ex;
      }
      warn(`XRef.parse - Invalid "Encrypt" reference: "${ex}".`);
    }
    if (encrypt instanceof Dict) {
      const ids = trailerDict.get("ID");
      const fileId = ids?.length ? ids[0] : "";
      encrypt.suppressEncryption = true;
      this.encrypt = new CipherTransformFactory(encrypt, fileId, this.pdfManager.password);
    }
    let root;
    try {
      root = trailerDict.get("Root");
    } catch (ex) {
      if (ex instanceof MissingDataException) {
        throw ex;
      }
      warn(`XRef.parse - Invalid "Root" reference: "${ex}".`);
    }
    if (root instanceof Dict) {
      try {
        const pages = root.get("Pages");
        if (pages instanceof Dict) {
          this.root = root;
          return;
        }
      } catch (ex) {
        if (ex instanceof MissingDataException) {
          throw ex;
        }
        warn(`XRef.parse - Invalid "Pages" reference: "${ex}".`);
      }
    }
    if (!recoveryMode) {
      throw new XRefParseException();
    }
    throw new InvalidPDFException("Invalid Root reference.");
  }
  processXRefTable(parser) {
    if (!("tableState" in this)) {
      this.tableState = {
        entryNum: 0,
        streamPos: parser.lexer.stream.pos,
        parserBuf1: parser.buf1,
        parserBuf2: parser.buf2
      };
    }
    const obj = this.readXRefTable(parser);
    if (!isCmd(obj, "trailer")) {
      throw new FormatError("Invalid XRef table: could not find trailer dictionary");
    }
    let dict = parser.getObj();
    if (!(dict instanceof Dict) && dict.dict) {
      dict = dict.dict;
    }
    if (!(dict instanceof Dict)) {
      throw new FormatError("Invalid XRef table: could not parse trailer dictionary");
    }
    delete this.tableState;
    return dict;
  }
  readXRefTable(parser) {
    const stream = parser.lexer.stream;
    const tableState = this.tableState;
    stream.pos = tableState.streamPos;
    parser.buf1 = tableState.parserBuf1;
    parser.buf2 = tableState.parserBuf2;
    let obj;
    while (true) {
      if (!("firstEntryNum" in tableState) || !("entryCount" in tableState)) {
        if (isCmd(obj = parser.getObj(), "trailer")) {
          break;
        }
        tableState.firstEntryNum = obj;
        tableState.entryCount = parser.getObj();
      }
      let first = tableState.firstEntryNum;
      const count = tableState.entryCount;
      if (!Number.isInteger(first) || !Number.isInteger(count)) {
        throw new FormatError("Invalid XRef table: wrong types in subsection header");
      }
      for (let i = tableState.entryNum; i < count; i++) {
        tableState.streamPos = stream.pos;
        tableState.entryNum = i;
        tableState.parserBuf1 = parser.buf1;
        tableState.parserBuf2 = parser.buf2;
        const entry = {};
        entry.offset = parser.getObj();
        entry.gen = parser.getObj();
        const type = parser.getObj();
        if (type instanceof Cmd) {
          switch (type.cmd) {
            case "f":
              entry.free = true;
              break;
            case "n":
              entry.uncompressed = true;
              break;
          }
        }
        if (!Number.isInteger(entry.offset) || !Number.isInteger(entry.gen) || !(entry.free || entry.uncompressed)) {
          throw new FormatError(`Invalid entry in XRef subsection: ${first}, ${count}`);
        }
        if (i === 0 && entry.free && first === 1) {
          first = 0;
        }
        if (!this.entries[i + first]) {
          this.entries[i + first] = entry;
        }
      }
      tableState.entryNum = 0;
      tableState.streamPos = stream.pos;
      tableState.parserBuf1 = parser.buf1;
      tableState.parserBuf2 = parser.buf2;
      delete tableState.firstEntryNum;
      delete tableState.entryCount;
    }
    if (this.entries[0] && !this.entries[0].free) {
      throw new FormatError("Invalid XRef table: unexpected first object");
    }
    return obj;
  }
  processXRefStream(stream) {
    if (!("streamState" in this)) {
      const streamParameters = stream.dict;
      const byteWidths = streamParameters.get("W");
      let range = streamParameters.get("Index");
      if (!range) {
        range = [0, streamParameters.get("Size")];
      }
      this.streamState = {
        entryRanges: range,
        byteWidths,
        entryNum: 0,
        streamPos: stream.pos
      };
    }
    this.readXRefStream(stream);
    delete this.streamState;
    return stream.dict;
  }
  readXRefStream(stream) {
    const streamState = this.streamState;
    stream.pos = streamState.streamPos;
    const [typeFieldWidth, offsetFieldWidth, generationFieldWidth] = streamState.byteWidths;
    const entryRanges = streamState.entryRanges;
    while (entryRanges.length > 0) {
      const [first, n] = entryRanges;
      if (!Number.isInteger(first) || !Number.isInteger(n)) {
        throw new FormatError(`Invalid XRef range fields: ${first}, ${n}`);
      }
      if (!Number.isInteger(typeFieldWidth) || !Number.isInteger(offsetFieldWidth) || !Number.isInteger(generationFieldWidth)) {
        throw new FormatError(`Invalid XRef entry fields length: ${first}, ${n}`);
      }
      for (let i = streamState.entryNum; i < n; ++i) {
        streamState.entryNum = i;
        streamState.streamPos = stream.pos;
        let type = 0,
          offset = 0,
          generation = 0;
        for (let j = 0; j < typeFieldWidth; ++j) {
          const typeByte = stream.getByte();
          if (typeByte === -1) {
            throw new FormatError("Invalid XRef byteWidths 'type'.");
          }
          type = type << 8 | typeByte;
        }
        if (typeFieldWidth === 0) {
          type = 1;
        }
        for (let j = 0; j < offsetFieldWidth; ++j) {
          const offsetByte = stream.getByte();
          if (offsetByte === -1) {
            throw new FormatError("Invalid XRef byteWidths 'offset'.");
          }
          offset = offset << 8 | offsetByte;
        }
        for (let j = 0; j < generationFieldWidth; ++j) {
          const generationByte = stream.getByte();
          if (generationByte === -1) {
            throw new FormatError("Invalid XRef byteWidths 'generation'.");
          }
          generation = generation << 8 | generationByte;
        }
        const entry = {};
        entry.offset = offset;
        entry.gen = generation;
        switch (type) {
          case 0:
            entry.free = true;
            break;
          case 1:
            entry.uncompressed = true;
            break;
          case 2:
            break;
          default:
            throw new FormatError(`Invalid XRef entry type: ${type}`);
        }
        if (!this.entries[first + i]) {
          this.entries[first + i] = entry;
        }
      }
      streamState.entryNum = 0;
      streamState.streamPos = stream.pos;
      entryRanges.splice(0, 2);
    }
  }
  indexObjects() {
    const TAB = 0x9,
      LF = 0xa,
      CR = 0xd,
      SPACE = 0x20;
    const PERCENT = 0x25,
      LT = 0x3c;
    function readToken(data, offset) {
      let token = "",
        ch = data[offset];
      while (ch !== LF && ch !== CR && ch !== LT) {
        if (++offset >= data.length) {
          break;
        }
        token += String.fromCharCode(ch);
        ch = data[offset];
      }
      return token;
    }
    function skipUntil(data, offset, what) {
      const length = what.length,
        dataLength = data.length;
      let skipped = 0;
      while (offset < dataLength) {
        let i = 0;
        while (i < length && data[offset + i] === what[i]) {
          ++i;
        }
        if (i >= length) {
          break;
        }
        offset++;
        skipped++;
      }
      return skipped;
    }
    const gEndobjRegExp = /\b(endobj|\d+\s+\d+\s+obj|xref|trailer\s*<<)\b/g;
    const gStartxrefRegExp = /\b(startxref|\d+\s+\d+\s+obj)\b/g;
    const objRegExp = /^(\d+)\s+(\d+)\s+obj\b/;
    const trailerBytes = new Uint8Array([116, 114, 97, 105, 108, 101, 114]);
    const startxrefBytes = new Uint8Array([115, 116, 97, 114, 116, 120, 114, 101, 102]);
    const xrefBytes = new Uint8Array([47, 88, 82, 101, 102]);
    this.entries.length = 0;
    this._cacheMap.clear();
    const stream = this.stream;
    stream.pos = 0;
    const buffer = stream.getBytes(),
      bufferStr = bytesToString(buffer),
      length = buffer.length;
    let position = stream.start;
    const trailers = [],
      xrefStms = [];
    while (position < length) {
      let ch = buffer[position];
      if (ch === TAB || ch === LF || ch === CR || ch === SPACE) {
        ++position;
        continue;
      }
      if (ch === PERCENT) {
        do {
          ++position;
          if (position >= length) {
            break;
          }
          ch = buffer[position];
        } while (ch !== LF && ch !== CR);
        continue;
      }
      const token = readToken(buffer, position);
      let m;
      if (token.startsWith("xref") && (token.length === 4 || /\s/.test(token[4]))) {
        position += skipUntil(buffer, position, trailerBytes);
        trailers.push(position);
        position += skipUntil(buffer, position, startxrefBytes);
      } else if (m = objRegExp.exec(token)) {
        const num = m[1] | 0,
          gen = m[2] | 0;
        const startPos = position + token.length;
        let contentLength,
          updateEntries = false;
        if (!this.entries[num]) {
          updateEntries = true;
        } else if (this.entries[num].gen === gen) {
          try {
            const parser = new Parser({
              lexer: new Lexer(stream.makeSubStream(startPos))
            });
            parser.getObj();
            updateEntries = true;
          } catch (ex) {
            if (ex instanceof ParserEOFException) {
              warn(`indexObjects -- checking object (${token}): "${ex}".`);
            } else {
              updateEntries = true;
            }
          }
        }
        if (updateEntries) {
          this.entries[num] = {
            offset: position - stream.start,
            gen,
            uncompressed: true
          };
        }
        gEndobjRegExp.lastIndex = startPos;
        const match = gEndobjRegExp.exec(bufferStr);
        if (match) {
          const endPos = gEndobjRegExp.lastIndex + 1;
          contentLength = endPos - position;
          if (match[1] !== "endobj") {
            warn(`indexObjects: Found "${match[1]}" inside of another "obj", ` + 'caused by missing "endobj" -- trying to recover.');
            contentLength -= match[1].length + 1;
          }
        } else {
          contentLength = length - position;
        }
        const content = buffer.subarray(position, position + contentLength);
        const xrefTagOffset = skipUntil(content, 0, xrefBytes);
        if (xrefTagOffset < contentLength && content[xrefTagOffset + 5] < 64) {
          xrefStms.push(position - stream.start);
          this._xrefStms.add(position - stream.start);
        }
        position += contentLength;
      } else if (token.startsWith("trailer") && (token.length === 7 || /\s/.test(token[7]))) {
        trailers.push(position);
        const startPos = position + token.length;
        let contentLength;
        gStartxrefRegExp.lastIndex = startPos;
        const match = gStartxrefRegExp.exec(bufferStr);
        if (match) {
          const endPos = gStartxrefRegExp.lastIndex + 1;
          contentLength = endPos - position;
          if (match[1] !== "startxref") {
            warn(`indexObjects: Found "${match[1]}" after "trailer", ` + 'caused by missing "startxref" -- trying to recover.');
            contentLength -= match[1].length + 1;
          }
        } else {
          contentLength = length - position;
        }
        position += contentLength;
      } else {
        position += token.length + 1;
      }
    }
    for (const xrefStm of xrefStms) {
      this.startXRefQueue.push(xrefStm);
      this.readXRef(true);
    }
    const trailerDicts = [];
    let isEncrypted = false;
    for (const trailer of trailers) {
      stream.pos = trailer;
      const parser = new Parser({
        lexer: new Lexer(stream),
        xref: this,
        allowStreams: true,
        recoveryMode: true
      });
      const obj = parser.getObj();
      if (!isCmd(obj, "trailer")) {
        continue;
      }
      const dict = parser.getObj();
      if (!(dict instanceof Dict)) {
        continue;
      }
      trailerDicts.push(dict);
      if (dict.has("Encrypt")) {
        isEncrypted = true;
      }
    }
    let trailerDict, trailerError;
    for (const dict of [...trailerDicts, "genFallback", ...trailerDicts]) {
      if (dict === "genFallback") {
        if (!trailerError) {
          break;
        }
        this._generationFallback = true;
        continue;
      }
      let validPagesDict = false;
      try {
        const rootDict = dict.get("Root");
        if (!(rootDict instanceof Dict)) {
          continue;
        }
        const pagesDict = rootDict.get("Pages");
        if (!(pagesDict instanceof Dict)) {
          continue;
        }
        const pagesCount = pagesDict.get("Count");
        if (Number.isInteger(pagesCount)) {
          validPagesDict = true;
        }
      } catch (ex) {
        trailerError = ex;
        continue;
      }
      if (validPagesDict && (!isEncrypted || dict.has("Encrypt")) && dict.has("ID")) {
        return dict;
      }
      trailerDict = dict;
    }
    if (trailerDict) {
      return trailerDict;
    }
    if (this.topDict) {
      return this.topDict;
    }
    throw new InvalidPDFException("Invalid PDF structure.");
  }
  readXRef(recoveryMode = false) {
    const stream = this.stream;
    const startXRefParsedCache = new Set();
    while (this.startXRefQueue.length) {
      try {
        const startXRef = this.startXRefQueue[0];
        if (startXRefParsedCache.has(startXRef)) {
          warn("readXRef - skipping XRef table since it was already parsed.");
          this.startXRefQueue.shift();
          continue;
        }
        startXRefParsedCache.add(startXRef);
        stream.pos = startXRef + stream.start;
        const parser = new Parser({
          lexer: new Lexer(stream),
          xref: this,
          allowStreams: true
        });
        let obj = parser.getObj();
        let dict;
        if (isCmd(obj, "xref")) {
          dict = this.processXRefTable(parser);
          if (!this.topDict) {
            this.topDict = dict;
          }
          obj = dict.get("XRefStm");
          if (Number.isInteger(obj) && !this._xrefStms.has(obj)) {
            this._xrefStms.add(obj);
            this.startXRefQueue.push(obj);
            this.#firstXRefStmPos ??= obj;
          }
        } else if (Number.isInteger(obj)) {
          if (!Number.isInteger(parser.getObj()) || !isCmd(parser.getObj(), "obj") || !((obj = parser.getObj()) instanceof BaseStream)) {
            throw new FormatError("Invalid XRef stream");
          }
          dict = this.processXRefStream(obj);
          if (!this.topDict) {
            this.topDict = dict;
          }
          if (!dict) {
            throw new FormatError("Failed to read XRef stream");
          }
        } else {
          throw new FormatError("Invalid XRef stream header");
        }
        obj = dict.get("Prev");
        if (Number.isInteger(obj)) {
          this.startXRefQueue.push(obj);
        } else if (obj instanceof Ref) {
          this.startXRefQueue.push(obj.num);
        }
      } catch (e) {
        if (e instanceof MissingDataException) {
          throw e;
        }
        info("(while reading XRef): " + e);
      }
      this.startXRefQueue.shift();
    }
    if (this.topDict) {
      return this.topDict;
    }
    if (recoveryMode) {
      return undefined;
    }
    throw new XRefParseException();
  }
  get lastXRefStreamPos() {
    return this.#firstXRefStmPos ?? (this._xrefStms.size > 0 ? Math.max(...this._xrefStms) : null);
  }
  getEntry(i) {
    const xrefEntry = this.entries[i];
    if (xrefEntry && !xrefEntry.free && xrefEntry.offset) {
      return xrefEntry;
    }
    return null;
  }
  fetchIfRef(obj, suppressEncryption = false) {
    if (obj instanceof Ref) {
      return this.fetch(obj, suppressEncryption);
    }
    return obj;
  }
  fetch(ref, suppressEncryption = false) {
    if (!(ref instanceof Ref)) {
      throw new Error("ref object is not a reference");
    }
    const num = ref.num;
    const cacheEntry = this._cacheMap.get(num);
    if (cacheEntry !== undefined) {
      if (cacheEntry instanceof Dict && !cacheEntry.objId) {
        cacheEntry.objId = ref.toString();
      }
      return cacheEntry;
    }
    let xrefEntry = this.getEntry(num);
    if (xrefEntry === null) {
      this._cacheMap.set(num, xrefEntry);
      return xrefEntry;
    }
    if (this._pendingRefs.has(ref)) {
      this._pendingRefs.remove(ref);
      warn(`Ignoring circular reference: ${ref}.`);
      return CIRCULAR_REF;
    }
    this._pendingRefs.put(ref);
    try {
      xrefEntry = xrefEntry.uncompressed ? this.fetchUncompressed(ref, xrefEntry, suppressEncryption) : this.fetchCompressed(ref, xrefEntry, suppressEncryption);
      this._pendingRefs.remove(ref);
    } catch (ex) {
      this._pendingRefs.remove(ref);
      throw ex;
    }
    if (xrefEntry instanceof Dict) {
      xrefEntry.objId = ref.toString();
    } else if (xrefEntry instanceof BaseStream) {
      xrefEntry.dict.objId = ref.toString();
    }
    return xrefEntry;
  }
  fetchUncompressed(ref, xrefEntry, suppressEncryption = false) {
    const gen = ref.gen;
    let num = ref.num;
    if (xrefEntry.gen !== gen) {
      const msg = `Inconsistent generation in XRef: ${ref}`;
      if (this._generationFallback && xrefEntry.gen < gen) {
        warn(msg);
        return this.fetchUncompressed(Ref.get(num, xrefEntry.gen), xrefEntry, suppressEncryption);
      }
      throw new XRefEntryException(msg);
    }
    const stream = this.stream.makeSubStream(xrefEntry.offset + this.stream.start);
    const parser = new Parser({
      lexer: new Lexer(stream),
      xref: this,
      allowStreams: true
    });
    const obj1 = parser.getObj();
    const obj2 = parser.getObj();
    const obj3 = parser.getObj();
    if (obj1 !== num || obj2 !== gen || !(obj3 instanceof Cmd)) {
      throw new XRefEntryException(`Bad (uncompressed) XRef entry: ${ref}`);
    }
    if (obj3.cmd !== "obj") {
      if (obj3.cmd.startsWith("obj")) {
        num = parseInt(obj3.cmd.substring(3), 10);
        if (!Number.isNaN(num)) {
          return num;
        }
      }
      throw new XRefEntryException(`Bad (uncompressed) XRef entry: ${ref}`);
    }
    xrefEntry = this.encrypt && !suppressEncryption ? parser.getObj(this.encrypt.createCipherTransform(num, gen)) : parser.getObj();
    if (!(xrefEntry instanceof BaseStream)) {
      this._cacheMap.set(num, xrefEntry);
    }
    return xrefEntry;
  }
  fetchCompressed(ref, xrefEntry, suppressEncryption = false) {
    const tableOffset = xrefEntry.offset;
    const stream = this.fetch(Ref.get(tableOffset, 0));
    if (!(stream instanceof BaseStream)) {
      throw new FormatError("bad ObjStm stream");
    }
    const first = stream.dict.get("First");
    const n = stream.dict.get("N");
    if (!Number.isInteger(first) || !Number.isInteger(n)) {
      throw new FormatError("invalid first and n parameters for ObjStm stream");
    }
    let parser = new Parser({
      lexer: new Lexer(stream),
      xref: this,
      allowStreams: true
    });
    const nums = new Array(n);
    const offsets = new Array(n);
    for (let i = 0; i < n; ++i) {
      const num = parser.getObj();
      if (!Number.isInteger(num)) {
        throw new FormatError(`invalid object number in the ObjStm stream: ${num}`);
      }
      const offset = parser.getObj();
      if (!Number.isInteger(offset)) {
        throw new FormatError(`invalid object offset in the ObjStm stream: ${offset}`);
      }
      nums[i] = num;
      offsets[i] = offset;
    }
    const start = (stream.start || 0) + first;
    const entries = new Array(n);
    for (let i = 0; i < n; ++i) {
      const length = i < n - 1 ? offsets[i + 1] - offsets[i] : undefined;
      if (length < 0) {
        throw new FormatError("Invalid offset in the ObjStm stream.");
      }
      parser = new Parser({
        lexer: new Lexer(stream.makeSubStream(start + offsets[i], length, stream.dict)),
        xref: this,
        allowStreams: true
      });
      const obj = parser.getObj();
      entries[i] = obj;
      if (obj instanceof BaseStream) {
        continue;
      }
      const num = nums[i],
        entry = this.entries[num];
      if (entry && entry.offset === tableOffset && entry.gen === i) {
        this._cacheMap.set(num, obj);
      }
    }
    xrefEntry = entries[xrefEntry.gen];
    if (xrefEntry === undefined) {
      throw new XRefEntryException(`Bad (compressed) XRef entry: ${ref}`);
    }
    return xrefEntry;
  }
  async fetchIfRefAsync(obj, suppressEncryption) {
    if (obj instanceof Ref) {
      return this.fetchAsync(obj, suppressEncryption);
    }
    return obj;
  }
  async fetchAsync(ref, suppressEncryption) {
    try {
      return this.fetch(ref, suppressEncryption);
    } catch (ex) {
      if (!(ex instanceof MissingDataException)) {
        throw ex;
      }
      await this.pdfManager.requestRange(ex.begin, ex.end);
      return this.fetchAsync(ref, suppressEncryption);
    }
  }
  getCatalogObj() {
    return this.root;
  }
}

;// CONCATENATED MODULE: ./src/core/document.js




















const DEFAULT_USER_UNIT = 1.0;
const LETTER_SIZE_MEDIABOX = [0, 0, 612, 792];
class Page {
  constructor({
    pdfManager,
    xref,
    pageIndex,
    pageDict,
    ref,
    globalIdFactory,
    fontCache,
    builtInCMapCache,
    standardFontDataCache,
    globalImageCache,
    systemFontCache,
    nonBlendModesSet,
    xfaFactory
  }) {
    this.pdfManager = pdfManager;
    this.pageIndex = pageIndex;
    this.pageDict = pageDict;
    this.xref = xref;
    this.ref = ref;
    this.fontCache = fontCache;
    this.builtInCMapCache = builtInCMapCache;
    this.standardFontDataCache = standardFontDataCache;
    this.globalImageCache = globalImageCache;
    this.systemFontCache = systemFontCache;
    this.nonBlendModesSet = nonBlendModesSet;
    this.evaluatorOptions = pdfManager.evaluatorOptions;
    this.resourcesPromise = null;
    this.xfaFactory = xfaFactory;
    const idCounters = {
      obj: 0
    };
    this._localIdFactory = class extends globalIdFactory {
      static createObjId() {
        return `p${pageIndex}_${++idCounters.obj}`;
      }
      static getPageObjId() {
        return `p${ref.toString()}`;
      }
    };
  }
  _getInheritableProperty(key, getArray = false) {
    const value = getInheritableProperty({
      dict: this.pageDict,
      key,
      getArray,
      stopWhenFound: false
    });
    if (!Array.isArray(value)) {
      return value;
    }
    if (value.length === 1 || !(value[0] instanceof Dict)) {
      return value[0];
    }
    return Dict.merge({
      xref: this.xref,
      dictArray: value
    });
  }
  get content() {
    return this.pageDict.getArray("Contents");
  }
  get resources() {
    const resources = this._getInheritableProperty("Resources");
    return shadow(this, "resources", resources instanceof Dict ? resources : Dict.empty);
  }
  _getBoundingBox(name) {
    if (this.xfaData) {
      return this.xfaData.bbox;
    }
    const box = lookupNormalRect(this._getInheritableProperty(name, true), null);
    if (box) {
      if (box[2] - box[0] > 0 && box[3] - box[1] > 0) {
        return box;
      }
      warn(`Empty, or invalid, /${name} entry.`);
    }
    return null;
  }
  get mediaBox() {
    return shadow(this, "mediaBox", this._getBoundingBox("MediaBox") || LETTER_SIZE_MEDIABOX);
  }
  get cropBox() {
    return shadow(this, "cropBox", this._getBoundingBox("CropBox") || this.mediaBox);
  }
  get userUnit() {
    let obj = this.pageDict.get("UserUnit");
    if (typeof obj !== "number" || obj <= 0) {
      obj = DEFAULT_USER_UNIT;
    }
    return shadow(this, "userUnit", obj);
  }
  get view() {
    const {
      cropBox,
      mediaBox
    } = this;
    if (cropBox !== mediaBox && !isArrayEqual(cropBox, mediaBox)) {
      const box = Util.intersect(cropBox, mediaBox);
      if (box && box[2] - box[0] > 0 && box[3] - box[1] > 0) {
        return shadow(this, "view", box);
      }
      warn("Empty /CropBox and /MediaBox intersection.");
    }
    return shadow(this, "view", mediaBox);
  }
  get rotate() {
    let rotate = this._getInheritableProperty("Rotate") || 0;
    if (rotate % 90 !== 0) {
      rotate = 0;
    } else if (rotate >= 360) {
      rotate %= 360;
    } else if (rotate < 0) {
      rotate = (rotate % 360 + 360) % 360;
    }
    return shadow(this, "rotate", rotate);
  }
  _onSubStreamError(reason, objId) {
    if (this.evaluatorOptions.ignoreErrors) {
      warn(`getContentStream - ignoring sub-stream (${objId}): "${reason}".`);
      return;
    }
    throw reason;
  }
  getContentStream() {
    return this.pdfManager.ensure(this, "content").then(content => {
      if (content instanceof BaseStream) {
        return content;
      }
      if (Array.isArray(content)) {
        return new StreamsSequenceStream(content, this._onSubStreamError.bind(this));
      }
      return new NullStream();
    });
  }
  get xfaData() {
    return shadow(this, "xfaData", this.xfaFactory ? {
      bbox: this.xfaFactory.getBoundingBox(this.pageIndex)
    } : null);
  }
  #replaceIdByRef(annotations, deletedAnnotations, existingAnnotations) {
    for (const annotation of annotations) {
      if (annotation.id) {
        const ref = Ref.fromString(annotation.id);
        if (!ref) {
          warn(`A non-linked annotation cannot be modified: ${annotation.id}`);
          continue;
        }
        if (annotation.deleted) {
          deletedAnnotations.put(ref, ref);
          continue;
        }
        existingAnnotations?.put(ref);
        annotation.ref = ref;
        delete annotation.id;
      }
    }
  }
  async saveNewAnnotations(handler, task, annotations, imagePromises) {
    if (this.xfaFactory) {
      throw new Error("XFA: Cannot save new annotations.");
    }
    const partialEvaluator = new PartialEvaluator({
      xref: this.xref,
      handler,
      pageIndex: this.pageIndex,
      idFactory: this._localIdFactory,
      fontCache: this.fontCache,
      builtInCMapCache: this.builtInCMapCache,
      standardFontDataCache: this.standardFontDataCache,
      globalImageCache: this.globalImageCache,
      systemFontCache: this.systemFontCache,
      options: this.evaluatorOptions
    });
    const deletedAnnotations = new RefSetCache();
    const existingAnnotations = new RefSet();
    this.#replaceIdByRef(annotations, deletedAnnotations, existingAnnotations);
    const pageDict = this.pageDict;
    const annotationsArray = this.annotations.filter(a => !(a instanceof Ref && deletedAnnotations.has(a)));
    const newData = await AnnotationFactory.saveNewAnnotations(partialEvaluator, task, annotations, imagePromises);
    for (const {
      ref
    } of newData.annotations) {
      if (ref instanceof Ref && !existingAnnotations.has(ref)) {
        annotationsArray.push(ref);
      }
    }
    const savedDict = pageDict.get("Annots");
    pageDict.set("Annots", annotationsArray);
    const buffer = [];
    await writeObject(this.ref, pageDict, buffer, this.xref);
    if (savedDict) {
      pageDict.set("Annots", savedDict);
    }
    const objects = newData.dependencies;
    objects.push({
      ref: this.ref,
      data: buffer.join("")
    }, ...newData.annotations);
    for (const deletedRef of deletedAnnotations) {
      objects.push({
        ref: deletedRef,
        data: null
      });
    }
    return objects;
  }
  save(handler, task, annotationStorage) {
    const partialEvaluator = new PartialEvaluator({
      xref: this.xref,
      handler,
      pageIndex: this.pageIndex,
      idFactory: this._localIdFactory,
      fontCache: this.fontCache,
      builtInCMapCache: this.builtInCMapCache,
      standardFontDataCache: this.standardFontDataCache,
      globalImageCache: this.globalImageCache,
      systemFontCache: this.systemFontCache,
      options: this.evaluatorOptions
    });
    return this._parsedAnnotations.then(function (annotations) {
      const newRefsPromises = [];
      for (const annotation of annotations) {
        if (!annotation.mustBePrinted(annotationStorage)) {
          continue;
        }
        newRefsPromises.push(annotation.save(partialEvaluator, task, annotationStorage).catch(function (reason) {
          warn("save - ignoring annotation data during " + `"${task.name}" task: "${reason}".`);
          return null;
        }));
      }
      return Promise.all(newRefsPromises).then(function (newRefs) {
        return newRefs.filter(newRef => !!newRef);
      });
    });
  }
  loadResources(keys) {
    this.resourcesPromise ||= this.pdfManager.ensure(this, "resources");
    return this.resourcesPromise.then(() => {
      const objectLoader = new ObjectLoader(this.resources, keys, this.xref);
      return objectLoader.load();
    });
  }
  getOperatorList({
    handler,
    sink,
    task,
    intent,
    cacheKey,
    annotationStorage = null,
    modifiedIds = null
  }) {
    const contentStreamPromise = this.getContentStream();
    const resourcesPromise = this.loadResources(["ColorSpace", "ExtGState", "Font", "Pattern", "Properties", "Shading", "XObject"]);
    const partialEvaluator = new PartialEvaluator({
      xref: this.xref,
      handler,
      pageIndex: this.pageIndex,
      idFactory: this._localIdFactory,
      fontCache: this.fontCache,
      builtInCMapCache: this.builtInCMapCache,
      standardFontDataCache: this.standardFontDataCache,
      globalImageCache: this.globalImageCache,
      systemFontCache: this.systemFontCache,
      options: this.evaluatorOptions
    });
    const newAnnotsByPage = !this.xfaFactory ? getNewAnnotationsMap(annotationStorage) : null;
    const newAnnots = newAnnotsByPage?.get(this.pageIndex);
    let newAnnotationsPromise = Promise.resolve(null);
    let deletedAnnotations = null;
    if (newAnnots) {
      const annotationGlobalsPromise = this.pdfManager.ensureDoc("annotationGlobals");
      let imagePromises;
      const missingBitmaps = new Set();
      for (const {
        bitmapId,
        bitmap
      } of newAnnots) {
        if (bitmapId && !bitmap && !missingBitmaps.has(bitmapId)) {
          missingBitmaps.add(bitmapId);
        }
      }
      const {
        isOffscreenCanvasSupported
      } = this.evaluatorOptions;
      if (missingBitmaps.size > 0) {
        const annotationWithBitmaps = newAnnots.slice();
        for (const [key, annotation] of annotationStorage) {
          if (!key.startsWith(AnnotationEditorPrefix)) {
            continue;
          }
          if (annotation.bitmap && missingBitmaps.has(annotation.bitmapId)) {
            annotationWithBitmaps.push(annotation);
          }
        }
        imagePromises = AnnotationFactory.generateImages(annotationWithBitmaps, this.xref, isOffscreenCanvasSupported);
      } else {
        imagePromises = AnnotationFactory.generateImages(newAnnots, this.xref, isOffscreenCanvasSupported);
      }
      deletedAnnotations = new RefSet();
      this.#replaceIdByRef(newAnnots, deletedAnnotations, null);
      newAnnotationsPromise = annotationGlobalsPromise.then(annotationGlobals => {
        if (!annotationGlobals) {
          return null;
        }
        return AnnotationFactory.printNewAnnotations(annotationGlobals, partialEvaluator, task, newAnnots, imagePromises);
      });
    }
    const pageListPromise = Promise.all([contentStreamPromise, resourcesPromise]).then(([contentStream]) => {
      const opList = new OperatorList(intent, sink);
      handler.send("StartRenderPage", {
        transparency: partialEvaluator.hasBlendModes(this.resources, this.nonBlendModesSet),
        pageIndex: this.pageIndex,
        cacheKey
      });
      return partialEvaluator.getOperatorList({
        stream: contentStream,
        task,
        resources: this.resources,
        operatorList: opList
      }).then(function () {
        return opList;
      });
    });
    return Promise.all([pageListPromise, this._parsedAnnotations, newAnnotationsPromise]).then(function ([pageOpList, annotations, newAnnotations]) {
      if (newAnnotations) {
        annotations = annotations.filter(a => !(a.ref && deletedAnnotations.has(a.ref)));
        for (let i = 0, ii = newAnnotations.length; i < ii; i++) {
          const newAnnotation = newAnnotations[i];
          if (newAnnotation.refToReplace) {
            const j = annotations.findIndex(a => a.ref && isRefsEqual(a.ref, newAnnotation.refToReplace));
            if (j >= 0) {
              annotations.splice(j, 1, newAnnotation);
              newAnnotations.splice(i--, 1);
              ii--;
            }
          }
        }
        annotations = annotations.concat(newAnnotations);
      }
      if (annotations.length === 0 || intent & RenderingIntentFlag.ANNOTATIONS_DISABLE) {
        pageOpList.flush(true);
        return {
          length: pageOpList.totalLength
        };
      }
      const renderForms = !!(intent & RenderingIntentFlag.ANNOTATIONS_FORMS),
        isEditing = !!(intent & RenderingIntentFlag.IS_EDITING),
        intentAny = !!(intent & RenderingIntentFlag.ANY),
        intentDisplay = !!(intent & RenderingIntentFlag.DISPLAY),
        intentPrint = !!(intent & RenderingIntentFlag.PRINT);
      const opListPromises = [];
      for (const annotation of annotations) {
        if (intentAny || intentDisplay && annotation.mustBeViewed(annotationStorage, renderForms) && annotation.mustBeViewedWhenEditing(isEditing, modifiedIds) || intentPrint && annotation.mustBePrinted(annotationStorage)) {
          opListPromises.push(annotation.getOperatorList(partialEvaluator, task, intent, annotationStorage).catch(function (reason) {
            warn("getOperatorList - ignoring annotation data during " + `"${task.name}" task: "${reason}".`);
            return {
              opList: null,
              separateForm: false,
              separateCanvas: false
            };
          }));
        }
      }
      return Promise.all(opListPromises).then(function (opLists) {
        let form = false,
          canvas = false;
        for (const {
          opList,
          separateForm,
          separateCanvas
        } of opLists) {
          pageOpList.addOpList(opList);
          form ||= separateForm;
          canvas ||= separateCanvas;
        }
        pageOpList.flush(true, {
          form,
          canvas
        });
        return {
          length: pageOpList.totalLength
        };
      });
    });
  }
  async extractTextContent({
    handler,
    task,
    includeMarkedContent,
    disableNormalization,
    sink
  }) {
    const contentStreamPromise = this.getContentStream();
    const resourcesPromise = this.loadResources(["ExtGState", "Font", "Properties", "XObject"]);
    const langPromise = this.pdfManager.ensureCatalog("lang");
    const [contentStream,, lang] = await Promise.all([contentStreamPromise, resourcesPromise, langPromise]);
    const partialEvaluator = new PartialEvaluator({
      xref: this.xref,
      handler,
      pageIndex: this.pageIndex,
      idFactory: this._localIdFactory,
      fontCache: this.fontCache,
      builtInCMapCache: this.builtInCMapCache,
      standardFontDataCache: this.standardFontDataCache,
      globalImageCache: this.globalImageCache,
      systemFontCache: this.systemFontCache,
      options: this.evaluatorOptions
    });
    return partialEvaluator.getTextContent({
      stream: contentStream,
      task,
      resources: this.resources,
      includeMarkedContent,
      disableNormalization,
      sink,
      viewBox: this.view,
      lang
    });
  }
  async getStructTree() {
    const structTreeRoot = await this.pdfManager.ensureCatalog("structTreeRoot");
    if (!structTreeRoot) {
      return null;
    }
    await this._parsedAnnotations;
    const structTree = await this.pdfManager.ensure(this, "_parseStructTree", [structTreeRoot]);
    return structTree.serializable;
  }
  _parseStructTree(structTreeRoot) {
    const tree = new StructTreePage(structTreeRoot, this.pageDict);
    tree.parse(this.ref);
    return tree;
  }
  async getAnnotationsData(handler, task, intent) {
    const annotations = await this._parsedAnnotations;
    if (annotations.length === 0) {
      return annotations;
    }
    const annotationsData = [],
      textContentPromises = [];
    let partialEvaluator;
    const intentAny = !!(intent & RenderingIntentFlag.ANY),
      intentDisplay = !!(intent & RenderingIntentFlag.DISPLAY),
      intentPrint = !!(intent & RenderingIntentFlag.PRINT);
    for (const annotation of annotations) {
      const isVisible = intentAny || intentDisplay && annotation.viewable;
      if (isVisible || intentPrint && annotation.printable) {
        annotationsData.push(annotation.data);
      }
      if (annotation.hasTextContent && isVisible) {
        partialEvaluator ||= new PartialEvaluator({
          xref: this.xref,
          handler,
          pageIndex: this.pageIndex,
          idFactory: this._localIdFactory,
          fontCache: this.fontCache,
          builtInCMapCache: this.builtInCMapCache,
          standardFontDataCache: this.standardFontDataCache,
          globalImageCache: this.globalImageCache,
          systemFontCache: this.systemFontCache,
          options: this.evaluatorOptions
        });
        textContentPromises.push(annotation.extractTextContent(partialEvaluator, task, [-Infinity, -Infinity, Infinity, Infinity]).catch(function (reason) {
          warn(`getAnnotationsData - ignoring textContent during "${task.name}" task: "${reason}".`);
        }));
      }
    }
    await Promise.all(textContentPromises);
    return annotationsData;
  }
  get annotations() {
    const annots = this._getInheritableProperty("Annots");
    return shadow(this, "annotations", Array.isArray(annots) ? annots : []);
  }
  get _parsedAnnotations() {
    const promise = this.pdfManager.ensure(this, "annotations").then(async annots => {
      if (annots.length === 0) {
        return annots;
      }
      const annotationGlobals = await this.pdfManager.ensureDoc("annotationGlobals");
      if (!annotationGlobals) {
        return [];
      }
      const annotationPromises = [];
      for (const annotationRef of annots) {
        annotationPromises.push(AnnotationFactory.create(this.xref, annotationRef, annotationGlobals, this._localIdFactory, false, this.ref).catch(function (reason) {
          warn(`_parsedAnnotations: "${reason}".`);
          return null;
        }));
      }
      const sortedAnnotations = [];
      let popupAnnotations, widgetAnnotations;
      for (const annotation of await Promise.all(annotationPromises)) {
        if (!annotation) {
          continue;
        }
        if (annotation instanceof WidgetAnnotation) {
          (widgetAnnotations ||= []).push(annotation);
          continue;
        }
        if (annotation instanceof PopupAnnotation) {
          (popupAnnotations ||= []).push(annotation);
          continue;
        }
        sortedAnnotations.push(annotation);
      }
      if (widgetAnnotations) {
        sortedAnnotations.push(...widgetAnnotations);
      }
      if (popupAnnotations) {
        sortedAnnotations.push(...popupAnnotations);
      }
      return sortedAnnotations;
    });
    return shadow(this, "_parsedAnnotations", promise);
  }
  get jsActions() {
    const actions = collectActions(this.xref, this.pageDict, PageActionEventType);
    return shadow(this, "jsActions", actions);
  }
}
const PDF_HEADER_SIGNATURE = new Uint8Array([0x25, 0x50, 0x44, 0x46, 0x2d]);
const STARTXREF_SIGNATURE = new Uint8Array([0x73, 0x74, 0x61, 0x72, 0x74, 0x78, 0x72, 0x65, 0x66]);
const ENDOBJ_SIGNATURE = new Uint8Array([0x65, 0x6e, 0x64, 0x6f, 0x62, 0x6a]);
const FINGERPRINT_FIRST_BYTES = 1024;
const EMPTY_FINGERPRINT = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
function find(stream, signature, limit = 1024, backwards = false) {
  const signatureLength = signature.length;
  const scanBytes = stream.peekBytes(limit);
  const scanLength = scanBytes.length - signatureLength;
  if (scanLength <= 0) {
    return false;
  }
  if (backwards) {
    const signatureEnd = signatureLength - 1;
    let pos = scanBytes.length - 1;
    while (pos >= signatureEnd) {
      let j = 0;
      while (j < signatureLength && scanBytes[pos - j] === signature[signatureEnd - j]) {
        j++;
      }
      if (j >= signatureLength) {
        stream.pos += pos - signatureEnd;
        return true;
      }
      pos--;
    }
  } else {
    let pos = 0;
    while (pos <= scanLength) {
      let j = 0;
      while (j < signatureLength && scanBytes[pos + j] === signature[j]) {
        j++;
      }
      if (j >= signatureLength) {
        stream.pos += pos;
        return true;
      }
      pos++;
    }
  }
  return false;
}
class PDFDocument {
  constructor(pdfManager, stream) {
    if (stream.length <= 0) {
      throw new InvalidPDFException("The PDF file is empty, i.e. its size is zero bytes.");
    }
    this.pdfManager = pdfManager;
    this.stream = stream;
    this.xref = new XRef(stream, pdfManager);
    this._pagePromises = new Map();
    this._version = null;
    const idCounters = {
      font: 0
    };
    this._globalIdFactory = class {
      static getDocId() {
        return `g_${pdfManager.docId}`;
      }
      static createFontId() {
        return `f${++idCounters.font}`;
      }
      static createObjId() {
        unreachable("Abstract method `createObjId` called.");
      }
      static getPageObjId() {
        unreachable("Abstract method `getPageObjId` called.");
      }
    };
  }
  parse(recoveryMode) {
    this.xref.parse(recoveryMode);
    this.catalog = new Catalog(this.pdfManager, this.xref);
  }
  get linearization() {
    let linearization = null;
    try {
      linearization = Linearization.create(this.stream);
    } catch (err) {
      if (err instanceof MissingDataException) {
        throw err;
      }
      info(err);
    }
    return shadow(this, "linearization", linearization);
  }
  get startXRef() {
    const stream = this.stream;
    let startXRef = 0;
    if (this.linearization) {
      stream.reset();
      if (find(stream, ENDOBJ_SIGNATURE)) {
        stream.skip(6);
        let ch = stream.peekByte();
        while (isWhiteSpace(ch)) {
          stream.pos++;
          ch = stream.peekByte();
        }
        startXRef = stream.pos - stream.start;
      }
    } else {
      const step = 1024;
      const startXRefLength = STARTXREF_SIGNATURE.length;
      let found = false,
        pos = stream.end;
      while (!found && pos > 0) {
        pos -= step - startXRefLength;
        if (pos < 0) {
          pos = 0;
        }
        stream.pos = pos;
        found = find(stream, STARTXREF_SIGNATURE, step, true);
      }
      if (found) {
        stream.skip(9);
        let ch;
        do {
          ch = stream.getByte();
        } while (isWhiteSpace(ch));
        let str = "";
        while (ch >= 0x20 && ch <= 0x39) {
          str += String.fromCharCode(ch);
          ch = stream.getByte();
        }
        startXRef = parseInt(str, 10);
        if (isNaN(startXRef)) {
          startXRef = 0;
        }
      }
    }
    return shadow(this, "startXRef", startXRef);
  }
  checkHeader() {
    const stream = this.stream;
    stream.reset();
    if (!find(stream, PDF_HEADER_SIGNATURE)) {
      return;
    }
    stream.moveStart();
    stream.skip(PDF_HEADER_SIGNATURE.length);
    let version = "",
      ch;
    while ((ch = stream.getByte()) > 0x20 && version.length < 7) {
      version += String.fromCharCode(ch);
    }
    if (PDF_VERSION_REGEXP.test(version)) {
      this._version = version;
    } else {
      warn(`Invalid PDF header version: ${version}`);
    }
  }
  parseStartXRef() {
    this.xref.setStartXRef(this.startXRef);
  }
  get numPages() {
    let num = 0;
    if (this.catalog.hasActualNumPages) {
      num = this.catalog.numPages;
    } else if (this.xfaFactory) {
      num = this.xfaFactory.getNumPages();
    } else if (this.linearization) {
      num = this.linearization.numPages;
    } else {
      num = this.catalog.numPages;
    }
    return shadow(this, "numPages", num);
  }
  _hasOnlyDocumentSignatures(fields, recursionDepth = 0) {
    const RECURSION_LIMIT = 10;
    if (!Array.isArray(fields)) {
      return false;
    }
    return fields.every(field => {
      field = this.xref.fetchIfRef(field);
      if (!(field instanceof Dict)) {
        return false;
      }
      if (field.has("Kids")) {
        if (++recursionDepth > RECURSION_LIMIT) {
          warn("_hasOnlyDocumentSignatures: maximum recursion depth reached");
          return false;
        }
        return this._hasOnlyDocumentSignatures(field.get("Kids"), recursionDepth);
      }
      const isSignature = isName(field.get("FT"), "Sig");
      const rectangle = field.get("Rect");
      const isInvisible = Array.isArray(rectangle) && rectangle.every(value => value === 0);
      return isSignature && isInvisible;
    });
  }
  get _xfaStreams() {
    const acroForm = this.catalog.acroForm;
    if (!acroForm) {
      return null;
    }
    const xfa = acroForm.get("XFA");
    const entries = {
      "xdp:xdp": "",
      template: "",
      datasets: "",
      config: "",
      connectionSet: "",
      localeSet: "",
      stylesheet: "",
      "/xdp:xdp": ""
    };
    if (xfa instanceof BaseStream && !xfa.isEmpty) {
      entries["xdp:xdp"] = xfa;
      return entries;
    }
    if (!Array.isArray(xfa) || xfa.length === 0) {
      return null;
    }
    for (let i = 0, ii = xfa.length; i < ii; i += 2) {
      let name;
      if (i === 0) {
        name = "xdp:xdp";
      } else if (i === ii - 2) {
        name = "/xdp:xdp";
      } else {
        name = xfa[i];
      }
      if (!entries.hasOwnProperty(name)) {
        continue;
      }
      const data = this.xref.fetchIfRef(xfa[i + 1]);
      if (!(data instanceof BaseStream) || data.isEmpty) {
        continue;
      }
      entries[name] = data;
    }
    return entries;
  }
  get xfaDatasets() {
    const streams = this._xfaStreams;
    if (!streams) {
      return shadow(this, "xfaDatasets", null);
    }
    for (const key of ["datasets", "xdp:xdp"]) {
      const stream = streams[key];
      if (!stream) {
        continue;
      }
      try {
        const str = stringToUTF8String(stream.getString());
        const data = {
          [key]: str
        };
        return shadow(this, "xfaDatasets", new DatasetReader(data));
      } catch {
        warn("XFA - Invalid utf-8 string.");
        break;
      }
    }
    return shadow(this, "xfaDatasets", null);
  }
  get xfaData() {
    const streams = this._xfaStreams;
    if (!streams) {
      return null;
    }
    const data = Object.create(null);
    for (const [key, stream] of Object.entries(streams)) {
      if (!stream) {
        continue;
      }
      try {
        data[key] = stringToUTF8String(stream.getString());
      } catch {
        warn("XFA - Invalid utf-8 string.");
        return null;
      }
    }
    return data;
  }
  get xfaFactory() {
    let data;
    if (this.pdfManager.enableXfa && this.catalog.needsRendering && this.formInfo.hasXfa && !this.formInfo.hasAcroForm) {
      data = this.xfaData;
    }
    return shadow(this, "xfaFactory", data ? new XFAFactory(data) : null);
  }
  get isPureXfa() {
    return this.xfaFactory ? this.xfaFactory.isValid() : false;
  }
  get htmlForXfa() {
    return this.xfaFactory ? this.xfaFactory.getPages() : null;
  }
  async loadXfaImages() {
    const xfaImagesDict = await this.pdfManager.ensureCatalog("xfaImages");
    if (!xfaImagesDict) {
      return;
    }
    const keys = xfaImagesDict.getKeys();
    const objectLoader = new ObjectLoader(xfaImagesDict, keys, this.xref);
    await objectLoader.load();
    const xfaImages = new Map();
    for (const key of keys) {
      const stream = xfaImagesDict.get(key);
      if (stream instanceof BaseStream) {
        xfaImages.set(key, stream.getBytes());
      }
    }
    this.xfaFactory.setImages(xfaImages);
  }
  async loadXfaFonts(handler, task) {
    const acroForm = await this.pdfManager.ensureCatalog("acroForm");
    if (!acroForm) {
      return;
    }
    const resources = await acroForm.getAsync("DR");
    if (!(resources instanceof Dict)) {
      return;
    }
    const objectLoader = new ObjectLoader(resources, ["Font"], this.xref);
    await objectLoader.load();
    const fontRes = resources.get("Font");
    if (!(fontRes instanceof Dict)) {
      return;
    }
    const options = Object.assign(Object.create(null), this.pdfManager.evaluatorOptions);
    options.useSystemFonts = false;
    const partialEvaluator = new PartialEvaluator({
      xref: this.xref,
      handler,
      pageIndex: -1,
      idFactory: this._globalIdFactory,
      fontCache: this.catalog.fontCache,
      builtInCMapCache: this.catalog.builtInCMapCache,
      standardFontDataCache: this.catalog.standardFontDataCache,
      options
    });
    const operatorList = new OperatorList();
    const pdfFonts = [];
    const initialState = {
      get font() {
        return pdfFonts.at(-1);
      },
      set font(font) {
        pdfFonts.push(font);
      },
      clone() {
        return this;
      }
    };
    const fonts = new Map();
    fontRes.forEach((fontName, font) => {
      fonts.set(fontName, font);
    });
    const promises = [];
    for (const [fontName, font] of fonts) {
      const descriptor = font.get("FontDescriptor");
      if (!(descriptor instanceof Dict)) {
        continue;
      }
      let fontFamily = descriptor.get("FontFamily");
      fontFamily = fontFamily.replaceAll(/[ ]+(\d)/g, "$1");
      const fontWeight = descriptor.get("FontWeight");
      const italicAngle = -descriptor.get("ItalicAngle");
      const cssFontInfo = {
        fontFamily,
        fontWeight,
        italicAngle
      };
      if (!validateCSSFont(cssFontInfo)) {
        continue;
      }
      promises.push(partialEvaluator.handleSetFont(resources, [Name.get(fontName), 1], null, operatorList, task, initialState, null, cssFontInfo).catch(function (reason) {
        warn(`loadXfaFonts: "${reason}".`);
        return null;
      }));
    }
    await Promise.all(promises);
    const missingFonts = this.xfaFactory.setFonts(pdfFonts);
    if (!missingFonts) {
      return;
    }
    options.ignoreErrors = true;
    promises.length = 0;
    pdfFonts.length = 0;
    const reallyMissingFonts = new Set();
    for (const missing of missingFonts) {
      if (!getXfaFontName(`${missing}-Regular`)) {
        reallyMissingFonts.add(missing);
      }
    }
    if (reallyMissingFonts.size) {
      missingFonts.push("PdfJS-Fallback");
    }
    for (const missing of missingFonts) {
      if (reallyMissingFonts.has(missing)) {
        continue;
      }
      for (const fontInfo of [{
        name: "Regular",
        fontWeight: 400,
        italicAngle: 0
      }, {
        name: "Bold",
        fontWeight: 700,
        italicAngle: 0
      }, {
        name: "Italic",
        fontWeight: 400,
        italicAngle: 12
      }, {
        name: "BoldItalic",
        fontWeight: 700,
        italicAngle: 12
      }]) {
        const name = `${missing}-${fontInfo.name}`;
        const dict = getXfaFontDict(name);
        promises.push(partialEvaluator.handleSetFont(resources, [Name.get(name), 1], null, operatorList, task, initialState, dict, {
          fontFamily: missing,
          fontWeight: fontInfo.fontWeight,
          italicAngle: fontInfo.italicAngle
        }).catch(function (reason) {
          warn(`loadXfaFonts: "${reason}".`);
          return null;
        }));
      }
    }
    await Promise.all(promises);
    this.xfaFactory.appendFonts(pdfFonts, reallyMissingFonts);
  }
  async serializeXfaData(annotationStorage) {
    return this.xfaFactory ? this.xfaFactory.serializeData(annotationStorage) : null;
  }
  get version() {
    return this.catalog.version || this._version;
  }
  get formInfo() {
    const formInfo = {
      hasFields: false,
      hasAcroForm: false,
      hasXfa: false,
      hasSignatures: false
    };
    const acroForm = this.catalog.acroForm;
    if (!acroForm) {
      return shadow(this, "formInfo", formInfo);
    }
    try {
      const fields = acroForm.get("Fields");
      const hasFields = Array.isArray(fields) && fields.length > 0;
      formInfo.hasFields = hasFields;
      const xfa = acroForm.get("XFA");
      formInfo.hasXfa = Array.isArray(xfa) && xfa.length > 0 || xfa instanceof BaseStream && !xfa.isEmpty;
      const sigFlags = acroForm.get("SigFlags");
      const hasSignatures = !!(sigFlags & 0x1);
      const hasOnlyDocumentSignatures = hasSignatures && this._hasOnlyDocumentSignatures(fields);
      formInfo.hasAcroForm = hasFields && !hasOnlyDocumentSignatures;
      formInfo.hasSignatures = hasSignatures;
    } catch (ex) {
      if (ex instanceof MissingDataException) {
        throw ex;
      }
      warn(`Cannot fetch form information: "${ex}".`);
    }
    return shadow(this, "formInfo", formInfo);
  }
  get documentInfo() {
    const docInfo = {
      PDFFormatVersion: this.version,
      Language: this.catalog.lang,
      EncryptFilterName: this.xref.encrypt ? this.xref.encrypt.filterName : null,
      IsLinearized: !!this.linearization,
      IsAcroFormPresent: this.formInfo.hasAcroForm,
      IsXFAPresent: this.formInfo.hasXfa,
      IsCollectionPresent: !!this.catalog.collection,
      IsSignaturesPresent: this.formInfo.hasSignatures
    };
    let infoDict;
    try {
      infoDict = this.xref.trailer.get("Info");
    } catch (err) {
      if (err instanceof MissingDataException) {
        throw err;
      }
      info("The document information dictionary is invalid.");
    }
    if (!(infoDict instanceof Dict)) {
      return shadow(this, "documentInfo", docInfo);
    }
    for (const key of infoDict.getKeys()) {
      const value = infoDict.get(key);
      switch (key) {
        case "Title":
        case "Author":
        case "Subject":
        case "Keywords":
        case "Creator":
        case "Producer":
        case "CreationDate":
        case "ModDate":
          if (typeof value === "string") {
            docInfo[key] = stringToPDFString(value);
            continue;
          }
          break;
        case "Trapped":
          if (value instanceof Name) {
            docInfo[key] = value;
            continue;
          }
          break;
        default:
          let customValue;
          switch (typeof value) {
            case "string":
              customValue = stringToPDFString(value);
              break;
            case "number":
            case "boolean":
              customValue = value;
              break;
            default:
              if (value instanceof Name) {
                customValue = value;
              }
              break;
          }
          if (customValue === undefined) {
            warn(`Bad value, for custom key "${key}", in Info: ${value}.`);
            continue;
          }
          if (!docInfo.Custom) {
            docInfo.Custom = Object.create(null);
          }
          docInfo.Custom[key] = customValue;
          continue;
      }
      warn(`Bad value, for key "${key}", in Info: ${value}.`);
    }
    return shadow(this, "documentInfo", docInfo);
  }
  get fingerprints() {
    function validate(data) {
      return typeof data === "string" && data.length > 0 && data !== EMPTY_FINGERPRINT;
    }
    function hexString(hash) {
      const buf = [];
      for (const num of hash) {
        const hex = num.toString(16);
        buf.push(hex.padStart(2, "0"));
      }
      return buf.join("");
    }
    const idArray = this.xref.trailer.get("ID");
    let hashOriginal, hashModified;
    if (Array.isArray(idArray) && validate(idArray[0])) {
      hashOriginal = stringToBytes(idArray[0]);
      if (idArray[1] !== idArray[0] && validate(idArray[1])) {
        hashModified = stringToBytes(idArray[1]);
      }
    } else {
      hashOriginal = calculateMD5(this.stream.getByteRange(0, FINGERPRINT_FIRST_BYTES), 0, FINGERPRINT_FIRST_BYTES);
    }
    return shadow(this, "fingerprints", [hexString(hashOriginal), hashModified ? hexString(hashModified) : null]);
  }
  async _getLinearizationPage(pageIndex) {
    const {
      catalog,
      linearization,
      xref
    } = this;
    const ref = Ref.get(linearization.objectNumberFirst, 0);
    try {
      const obj = await xref.fetchAsync(ref);
      if (obj instanceof Dict) {
        let type = obj.getRaw("Type");
        if (type instanceof Ref) {
          type = await xref.fetchAsync(type);
        }
        if (isName(type, "Page") || !obj.has("Type") && !obj.has("Kids") && obj.has("Contents")) {
          if (!catalog.pageKidsCountCache.has(ref)) {
            catalog.pageKidsCountCache.put(ref, 1);
          }
          if (!catalog.pageIndexCache.has(ref)) {
            catalog.pageIndexCache.put(ref, 0);
          }
          return [obj, ref];
        }
      }
      throw new FormatError("The Linearization dictionary doesn't point to a valid Page dictionary.");
    } catch (reason) {
      warn(`_getLinearizationPage: "${reason.message}".`);
      return catalog.getPageDict(pageIndex);
    }
  }
  getPage(pageIndex) {
    const cachedPromise = this._pagePromises.get(pageIndex);
    if (cachedPromise) {
      return cachedPromise;
    }
    const {
      catalog,
      linearization,
      xfaFactory
    } = this;
    let promise;
    if (xfaFactory) {
      promise = Promise.resolve([Dict.empty, null]);
    } else if (linearization?.pageFirst === pageIndex) {
      promise = this._getLinearizationPage(pageIndex);
    } else {
      promise = catalog.getPageDict(pageIndex);
    }
    promise = promise.then(([pageDict, ref]) => {
      return new Page({
        pdfManager: this.pdfManager,
        xref: this.xref,
        pageIndex,
        pageDict,
        ref,
        globalIdFactory: this._globalIdFactory,
        fontCache: catalog.fontCache,
        builtInCMapCache: catalog.builtInCMapCache,
        standardFontDataCache: catalog.standardFontDataCache,
        globalImageCache: catalog.globalImageCache,
        systemFontCache: catalog.systemFontCache,
        nonBlendModesSet: catalog.nonBlendModesSet,
        xfaFactory
      });
    });
    this._pagePromises.set(pageIndex, promise);
    return promise;
  }
  async checkFirstPage(recoveryMode = false) {
    if (recoveryMode) {
      return;
    }
    try {
      await this.getPage(0);
    } catch (reason) {
      if (reason instanceof XRefEntryException) {
        this._pagePromises.delete(0);
        await this.cleanup();
        throw new XRefParseException();
      }
    }
  }
  async checkLastPage(recoveryMode = false) {
    const {
      catalog,
      pdfManager
    } = this;
    catalog.setActualNumPages();
    let numPages;
    try {
      await Promise.all([pdfManager.ensureDoc("xfaFactory"), pdfManager.ensureDoc("linearization"), pdfManager.ensureCatalog("numPages")]);
      if (this.xfaFactory) {
        return;
      } else if (this.linearization) {
        numPages = this.linearization.numPages;
      } else {
        numPages = catalog.numPages;
      }
      if (!Number.isInteger(numPages)) {
        throw new FormatError("Page count is not an integer.");
      } else if (numPages <= 1) {
        return;
      }
      await this.getPage(numPages - 1);
    } catch (reason) {
      this._pagePromises.delete(numPages - 1);
      await this.cleanup();
      if (reason instanceof XRefEntryException && !recoveryMode) {
        throw new XRefParseException();
      }
      warn(`checkLastPage - invalid /Pages tree /Count: ${numPages}.`);
      let pagesTree;
      try {
        pagesTree = await catalog.getAllPageDicts(recoveryMode);
      } catch (reasonAll) {
        if (reasonAll instanceof XRefEntryException && !recoveryMode) {
          throw new XRefParseException();
        }
        catalog.setActualNumPages(1);
        return;
      }
      for (const [pageIndex, [pageDict, ref]] of pagesTree) {
        let promise;
        if (pageDict instanceof Error) {
          promise = Promise.reject(pageDict);
          promise.catch(() => {});
        } else {
          promise = Promise.resolve(new Page({
            pdfManager,
            xref: this.xref,
            pageIndex,
            pageDict,
            ref,
            globalIdFactory: this._globalIdFactory,
            fontCache: catalog.fontCache,
            builtInCMapCache: catalog.builtInCMapCache,
            standardFontDataCache: catalog.standardFontDataCache,
            globalImageCache: catalog.globalImageCache,
            systemFontCache: catalog.systemFontCache,
            nonBlendModesSet: catalog.nonBlendModesSet,
            xfaFactory: null
          }));
        }
        this._pagePromises.set(pageIndex, promise);
      }
      catalog.setActualNumPages(pagesTree.size);
    }
  }
  fontFallback(id, handler) {
    return this.catalog.fontFallback(id, handler);
  }
  async cleanup(manuallyTriggered = false) {
    return this.catalog ? this.catalog.cleanup(manuallyTriggered) : clearGlobalCaches();
  }
  async #collectFieldObjects(name, fieldRef, promises, annotationGlobals, visitedRefs) {
    const {
      xref
    } = this;
    if (!(fieldRef instanceof Ref) || visitedRefs.has(fieldRef)) {
      return;
    }
    visitedRefs.put(fieldRef);
    const field = await xref.fetchAsync(fieldRef);
    if (!(field instanceof Dict)) {
      return;
    }
    if (field.has("T")) {
      const partName = stringToPDFString(await field.getAsync("T"));
      name = name === "" ? partName : `${name}.${partName}`;
    } else {
      let obj = field;
      while (true) {
        obj = obj.getRaw("Parent");
        if (obj instanceof Ref) {
          if (visitedRefs.has(obj)) {
            break;
          }
          obj = await xref.fetchAsync(obj);
        }
        if (!(obj instanceof Dict)) {
          break;
        }
        if (obj.has("T")) {
          const partName = stringToPDFString(await obj.getAsync("T"));
          name = name === "" ? partName : `${name}.${partName}`;
          break;
        }
      }
    }
    if (!promises.has(name)) {
      promises.set(name, []);
    }
    promises.get(name).push(AnnotationFactory.create(xref, fieldRef, annotationGlobals, null, true, null).then(annotation => annotation?.getFieldObject()).catch(function (reason) {
      warn(`#collectFieldObjects: "${reason}".`);
      return null;
    }));
    if (!field.has("Kids")) {
      return;
    }
    const kids = await field.getAsync("Kids");
    if (Array.isArray(kids)) {
      for (const kid of kids) {
        await this.#collectFieldObjects(name, kid, promises, annotationGlobals, visitedRefs);
      }
    }
  }
  get fieldObjects() {
    if (!this.formInfo.hasFields) {
      return shadow(this, "fieldObjects", Promise.resolve(null));
    }
    const promise = Promise.all([this.pdfManager.ensureDoc("annotationGlobals"), this.pdfManager.ensureCatalog("acroForm")]).then(async ([annotationGlobals, acroForm]) => {
      if (!annotationGlobals) {
        return null;
      }
      const visitedRefs = new RefSet();
      const allFields = Object.create(null);
      const fieldPromises = new Map();
      for (const fieldRef of await acroForm.getAsync("Fields")) {
        await this.#collectFieldObjects("", fieldRef, fieldPromises, annotationGlobals, visitedRefs);
      }
      const allPromises = [];
      for (const [name, promises] of fieldPromises) {
        allPromises.push(Promise.all(promises).then(fields => {
          fields = fields.filter(field => !!field);
          if (fields.length > 0) {
            allFields[name] = fields;
          }
        }));
      }
      await Promise.all(allPromises);
      return allFields;
    });
    return shadow(this, "fieldObjects", promise);
  }
  get hasJSActions() {
    const promise = this.pdfManager.ensureDoc("_parseHasJSActions");
    return shadow(this, "hasJSActions", promise);
  }
  async _parseHasJSActions() {
    const [catalogJsActions, fieldObjects] = await Promise.all([this.pdfManager.ensureCatalog("jsActions"), this.pdfManager.ensureDoc("fieldObjects")]);
    if (catalogJsActions) {
      return true;
    }
    if (fieldObjects) {
      return Object.values(fieldObjects).some(fieldObject => fieldObject.some(object => object.actions !== null));
    }
    return false;
  }
  get calculationOrderIds() {
    const acroForm = this.catalog.acroForm;
    if (!acroForm?.has("CO")) {
      return shadow(this, "calculationOrderIds", null);
    }
    const calculationOrder = acroForm.get("CO");
    if (!Array.isArray(calculationOrder) || calculationOrder.length === 0) {
      return shadow(this, "calculationOrderIds", null);
    }
    const ids = [];
    for (const id of calculationOrder) {
      if (id instanceof Ref) {
        ids.push(id.toString());
      }
    }
    if (ids.length === 0) {
      return shadow(this, "calculationOrderIds", null);
    }
    return shadow(this, "calculationOrderIds", ids);
  }
  get annotationGlobals() {
    return shadow(this, "annotationGlobals", AnnotationFactory.createGlobals(this.pdfManager));
  }
}

;// CONCATENATED MODULE: ./src/core/pdf_manager.js





function parseDocBaseUrl(url) {
  if (url) {
    const absoluteUrl = createValidAbsoluteUrl(url);
    if (absoluteUrl) {
      return absoluteUrl.href;
    }
    warn(`Invalid absolute docBaseUrl: "${url}".`);
  }
  return null;
}
class BasePdfManager {
  constructor(args) {
    this._docBaseUrl = parseDocBaseUrl(args.docBaseUrl);
    this._docId = args.docId;
    this._password = args.password;
    this.enableXfa = args.enableXfa;
    args.evaluatorOptions.isOffscreenCanvasSupported &&= FeatureTest.isOffscreenCanvasSupported;
    this.evaluatorOptions = Object.freeze(args.evaluatorOptions);
  }
  get docId() {
    return this._docId;
  }
  get password() {
    return this._password;
  }
  get docBaseUrl() {
    return this._docBaseUrl;
  }
  get catalog() {
    return this.pdfDocument.catalog;
  }
  ensureDoc(prop, args) {
    return this.ensure(this.pdfDocument, prop, args);
  }
  ensureXRef(prop, args) {
    return this.ensure(this.pdfDocument.xref, prop, args);
  }
  ensureCatalog(prop, args) {
    return this.ensure(this.pdfDocument.catalog, prop, args);
  }
  getPage(pageIndex) {
    return this.pdfDocument.getPage(pageIndex);
  }
  fontFallback(id, handler) {
    return this.pdfDocument.fontFallback(id, handler);
  }
  loadXfaFonts(handler, task) {
    return this.pdfDocument.loadXfaFonts(handler, task);
  }
  loadXfaImages() {
    return this.pdfDocument.loadXfaImages();
  }
  serializeXfaData(annotationStorage) {
    return this.pdfDocument.serializeXfaData(annotationStorage);
  }
  cleanup(manuallyTriggered = false) {
    return this.pdfDocument.cleanup(manuallyTriggered);
  }
  async ensure(obj, prop, args) {
    unreachable("Abstract method `ensure` called");
  }
  requestRange(begin, end) {
    unreachable("Abstract method `requestRange` called");
  }
  requestLoadedStream(noFetch = false) {
    unreachable("Abstract method `requestLoadedStream` called");
  }
  sendProgressiveData(chunk) {
    unreachable("Abstract method `sendProgressiveData` called");
  }
  updatePassword(password) {
    this._password = password;
  }
  terminate(reason) {
    unreachable("Abstract method `terminate` called");
  }
}
class LocalPdfManager extends BasePdfManager {
  constructor(args) {
    super(args);
    const stream = new Stream(args.source);
    this.pdfDocument = new PDFDocument(this, stream);
    this._loadedStreamPromise = Promise.resolve(stream);
  }
  async ensure(obj, prop, args) {
    const value = obj[prop];
    if (typeof value === "function") {
      return value.apply(obj, args);
    }
    return value;
  }
  requestRange(begin, end) {
    return Promise.resolve();
  }
  requestLoadedStream(noFetch = false) {
    return this._loadedStreamPromise;
  }
  terminate(reason) {}
}
class NetworkPdfManager extends BasePdfManager {
  constructor(args) {
    super(args);
    this.streamManager = new ChunkedStreamManager(args.source, {
      msgHandler: args.handler,
      length: args.length,
      disableAutoFetch: args.disableAutoFetch,
      rangeChunkSize: args.rangeChunkSize
    });
    this.pdfDocument = new PDFDocument(this, this.streamManager.getStream());
  }
  async ensure(obj, prop, args) {
    try {
      const value = obj[prop];
      if (typeof value === "function") {
        return value.apply(obj, args);
      }
      return value;
    } catch (ex) {
      if (!(ex instanceof MissingDataException)) {
        throw ex;
      }
      await this.requestRange(ex.begin, ex.end);
      return this.ensure(obj, prop, args);
    }
  }
  requestRange(begin, end) {
    return this.streamManager.requestRange(begin, end);
  }
  requestLoadedStream(noFetch = false) {
    return this.streamManager.requestAllChunks(noFetch);
  }
  sendProgressiveData(chunk) {
    this.streamManager.onReceiveData({
      chunk
    });
  }
  terminate(reason) {
    this.streamManager.abort(reason);
  }
}

;// CONCATENATED MODULE: ./src/shared/message_handler.js

const CallbackKind = {
  UNKNOWN: 0,
  DATA: 1,
  ERROR: 2
};
const StreamKind = {
  UNKNOWN: 0,
  CANCEL: 1,
  CANCEL_COMPLETE: 2,
  CLOSE: 3,
  ENQUEUE: 4,
  ERROR: 5,
  PULL: 6,
  PULL_COMPLETE: 7,
  START_COMPLETE: 8
};
function wrapReason(reason) {
  if (!(reason instanceof Error || typeof reason === "object" && reason !== null)) {
    unreachable('wrapReason: Expected "reason" to be a (possibly cloned) Error.');
  }
  switch (reason.name) {
    case "AbortException":
      return new AbortException(reason.message);
    case "MissingPDFException":
      return new MissingPDFException(reason.message);
    case "PasswordException":
      return new PasswordException(reason.message, reason.code);
    case "UnexpectedResponseException":
      return new UnexpectedResponseException(reason.message, reason.status);
    case "UnknownErrorException":
      return new UnknownErrorException(reason.message, reason.details);
    default:
      return new UnknownErrorException(reason.message, reason.toString());
  }
}
class MessageHandler {
  constructor(sourceName, targetName, comObj) {
    this.sourceName = sourceName;
    this.targetName = targetName;
    this.comObj = comObj;
    this.callbackId = 1;
    this.streamId = 1;
    this.streamSinks = Object.create(null);
    this.streamControllers = Object.create(null);
    this.callbackCapabilities = Object.create(null);
    this.actionHandler = Object.create(null);
    this._onComObjOnMessage = event => {
      const data = event.data;
      if (data.targetName !== this.sourceName) {
        return;
      }
      if (data.stream) {
        this.#processStreamMessage(data);
        return;
      }
      if (data.callback) {
        const callbackId = data.callbackId;
        const capability = this.callbackCapabilities[callbackId];
        if (!capability) {
          throw new Error(`Cannot resolve callback ${callbackId}`);
        }
        delete this.callbackCapabilities[callbackId];
        if (data.callback === CallbackKind.DATA) {
          capability.resolve(data.data);
        } else if (data.callback === CallbackKind.ERROR) {
          capability.reject(wrapReason(data.reason));
        } else {
          throw new Error("Unexpected callback case");
        }
        return;
      }
      const action = this.actionHandler[data.action];
      if (!action) {
        throw new Error(`Unknown action from worker: ${data.action}`);
      }
      if (data.callbackId) {
        const cbSourceName = this.sourceName;
        const cbTargetName = data.sourceName;
        new Promise(function (resolve) {
          resolve(action(data.data));
        }).then(function (result) {
          comObj.postMessage({
            sourceName: cbSourceName,
            targetName: cbTargetName,
            callback: CallbackKind.DATA,
            callbackId: data.callbackId,
            data: result
          });
        }, function (reason) {
          comObj.postMessage({
            sourceName: cbSourceName,
            targetName: cbTargetName,
            callback: CallbackKind.ERROR,
            callbackId: data.callbackId,
            reason: wrapReason(reason)
          });
        });
        return;
      }
      if (data.streamId) {
        this.#createStreamSink(data);
        return;
      }
      action(data.data);
    };
    comObj.addEventListener("message", this._onComObjOnMessage);
  }
  on(actionName, handler) {
    const ah = this.actionHandler;
    if (ah[actionName]) {
      throw new Error(`There is already an actionName called "${actionName}"`);
    }
    ah[actionName] = handler;
  }
  send(actionName, data, transfers) {
    this.comObj.postMessage({
      sourceName: this.sourceName,
      targetName: this.targetName,
      action: actionName,
      data
    }, transfers);
  }
  sendWithPromise(actionName, data, transfers) {
    const callbackId = this.callbackId++;
    const capability = Promise.withResolvers();
    this.callbackCapabilities[callbackId] = capability;
    try {
      this.comObj.postMessage({
        sourceName: this.sourceName,
        targetName: this.targetName,
        action: actionName,
        callbackId,
        data
      }, transfers);
    } catch (ex) {
      capability.reject(ex);
    }
    return capability.promise;
  }
  sendWithStream(actionName, data, queueingStrategy, transfers) {
    const streamId = this.streamId++,
      sourceName = this.sourceName,
      targetName = this.targetName,
      comObj = this.comObj;
    return new ReadableStream({
      start: controller => {
        const startCapability = Promise.withResolvers();
        this.streamControllers[streamId] = {
          controller,
          startCall: startCapability,
          pullCall: null,
          cancelCall: null,
          isClosed: false
        };
        comObj.postMessage({
          sourceName,
          targetName,
          action: actionName,
          streamId,
          data,
          desiredSize: controller.desiredSize
        }, transfers);
        return startCapability.promise;
      },
      pull: controller => {
        const pullCapability = Promise.withResolvers();
        this.streamControllers[streamId].pullCall = pullCapability;
        comObj.postMessage({
          sourceName,
          targetName,
          stream: StreamKind.PULL,
          streamId,
          desiredSize: controller.desiredSize
        });
        return pullCapability.promise;
      },
      cancel: reason => {
        assert(reason instanceof Error, "cancel must have a valid reason");
        const cancelCapability = Promise.withResolvers();
        this.streamControllers[streamId].cancelCall = cancelCapability;
        this.streamControllers[streamId].isClosed = true;
        comObj.postMessage({
          sourceName,
          targetName,
          stream: StreamKind.CANCEL,
          streamId,
          reason: wrapReason(reason)
        });
        return cancelCapability.promise;
      }
    }, queueingStrategy);
  }
  #createStreamSink(data) {
    const streamId = data.streamId,
      sourceName = this.sourceName,
      targetName = data.sourceName,
      comObj = this.comObj;
    const self = this,
      action = this.actionHandler[data.action];
    const streamSink = {
      enqueue(chunk, size = 1, transfers) {
        if (this.isCancelled) {
          return;
        }
        const lastDesiredSize = this.desiredSize;
        this.desiredSize -= size;
        if (lastDesiredSize > 0 && this.desiredSize <= 0) {
          this.sinkCapability = Promise.withResolvers();
          this.ready = this.sinkCapability.promise;
        }
        comObj.postMessage({
          sourceName,
          targetName,
          stream: StreamKind.ENQUEUE,
          streamId,
          chunk
        }, transfers);
      },
      close() {
        if (this.isCancelled) {
          return;
        }
        this.isCancelled = true;
        comObj.postMessage({
          sourceName,
          targetName,
          stream: StreamKind.CLOSE,
          streamId
        });
        delete self.streamSinks[streamId];
      },
      error(reason) {
        assert(reason instanceof Error, "error must have a valid reason");
        if (this.isCancelled) {
          return;
        }
        this.isCancelled = true;
        comObj.postMessage({
          sourceName,
          targetName,
          stream: StreamKind.ERROR,
          streamId,
          reason: wrapReason(reason)
        });
      },
      sinkCapability: Promise.withResolvers(),
      onPull: null,
      onCancel: null,
      isCancelled: false,
      desiredSize: data.desiredSize,
      ready: null
    };
    streamSink.sinkCapability.resolve();
    streamSink.ready = streamSink.sinkCapability.promise;
    this.streamSinks[streamId] = streamSink;
    new Promise(function (resolve) {
      resolve(action(data.data, streamSink));
    }).then(function () {
      comObj.postMessage({
        sourceName,
        targetName,
        stream: StreamKind.START_COMPLETE,
        streamId,
        success: true
      });
    }, function (reason) {
      comObj.postMessage({
        sourceName,
        targetName,
        stream: StreamKind.START_COMPLETE,
        streamId,
        reason: wrapReason(reason)
      });
    });
  }
  #processStreamMessage(data) {
    const streamId = data.streamId,
      sourceName = this.sourceName,
      targetName = data.sourceName,
      comObj = this.comObj;
    const streamController = this.streamControllers[streamId],
      streamSink = this.streamSinks[streamId];
    switch (data.stream) {
      case StreamKind.START_COMPLETE:
        if (data.success) {
          streamController.startCall.resolve();
        } else {
          streamController.startCall.reject(wrapReason(data.reason));
        }
        break;
      case StreamKind.PULL_COMPLETE:
        if (data.success) {
          streamController.pullCall.resolve();
        } else {
          streamController.pullCall.reject(wrapReason(data.reason));
        }
        break;
      case StreamKind.PULL:
        if (!streamSink) {
          comObj.postMessage({
            sourceName,
            targetName,
            stream: StreamKind.PULL_COMPLETE,
            streamId,
            success: true
          });
          break;
        }
        if (streamSink.desiredSize <= 0 && data.desiredSize > 0) {
          streamSink.sinkCapability.resolve();
        }
        streamSink.desiredSize = data.desiredSize;
        new Promise(function (resolve) {
          resolve(streamSink.onPull?.());
        }).then(function () {
          comObj.postMessage({
            sourceName,
            targetName,
            stream: StreamKind.PULL_COMPLETE,
            streamId,
            success: true
          });
        }, function (reason) {
          comObj.postMessage({
            sourceName,
            targetName,
            stream: StreamKind.PULL_COMPLETE,
            streamId,
            reason: wrapReason(reason)
          });
        });
        break;
      case StreamKind.ENQUEUE:
        assert(streamController, "enqueue should have stream controller");
        if (streamController.isClosed) {
          break;
        }
        streamController.controller.enqueue(data.chunk);
        break;
      case StreamKind.CLOSE:
        assert(streamController, "close should have stream controller");
        if (streamController.isClosed) {
          break;
        }
        streamController.isClosed = true;
        streamController.controller.close();
        this.#deleteStreamController(streamController, streamId);
        break;
      case StreamKind.ERROR:
        assert(streamController, "error should have stream controller");
        streamController.controller.error(wrapReason(data.reason));
        this.#deleteStreamController(streamController, streamId);
        break;
      case StreamKind.CANCEL_COMPLETE:
        if (data.success) {
          streamController.cancelCall.resolve();
        } else {
          streamController.cancelCall.reject(wrapReason(data.reason));
        }
        this.#deleteStreamController(streamController, streamId);
        break;
      case StreamKind.CANCEL:
        if (!streamSink) {
          break;
        }
        new Promise(function (resolve) {
          resolve(streamSink.onCancel?.(wrapReason(data.reason)));
        }).then(function () {
          comObj.postMessage({
            sourceName,
            targetName,
            stream: StreamKind.CANCEL_COMPLETE,
            streamId,
            success: true
          });
        }, function (reason) {
          comObj.postMessage({
            sourceName,
            targetName,
            stream: StreamKind.CANCEL_COMPLETE,
            streamId,
            reason: wrapReason(reason)
          });
        });
        streamSink.sinkCapability.reject(wrapReason(data.reason));
        streamSink.isCancelled = true;
        delete this.streamSinks[streamId];
        break;
      default:
        throw new Error("Unexpected stream case");
    }
  }
  async #deleteStreamController(streamController, streamId) {
    await Promise.allSettled([streamController.startCall?.promise, streamController.pullCall?.promise, streamController.cancelCall?.promise]);
    delete this.streamControllers[streamId];
  }
  destroy() {
    this.comObj.removeEventListener("message", this._onComObjOnMessage);
  }
}

;// CONCATENATED MODULE: ./src/core/worker_stream.js

class PDFWorkerStream {
  constructor(msgHandler) {
    this._msgHandler = msgHandler;
    this._contentLength = null;
    this._fullRequestReader = null;
    this._rangeRequestReaders = [];
  }
  getFullReader() {
    assert(!this._fullRequestReader, "PDFWorkerStream.getFullReader can only be called once.");
    this._fullRequestReader = new PDFWorkerStreamReader(this._msgHandler);
    return this._fullRequestReader;
  }
  getRangeReader(begin, end) {
    const reader = new PDFWorkerStreamRangeReader(begin, end, this._msgHandler);
    this._rangeRequestReaders.push(reader);
    return reader;
  }
  cancelAllRequests(reason) {
    this._fullRequestReader?.cancel(reason);
    for (const reader of this._rangeRequestReaders.slice(0)) {
      reader.cancel(reason);
    }
  }
}
class PDFWorkerStreamReader {
  constructor(msgHandler) {
    this._msgHandler = msgHandler;
    this.onProgress = null;
    this._contentLength = null;
    this._isRangeSupported = false;
    this._isStreamingSupported = false;
    const readableStream = this._msgHandler.sendWithStream("GetReader");
    this._reader = readableStream.getReader();
    this._headersReady = this._msgHandler.sendWithPromise("ReaderHeadersReady").then(data => {
      this._isStreamingSupported = data.isStreamingSupported;
      this._isRangeSupported = data.isRangeSupported;
      this._contentLength = data.contentLength;
    });
  }
  get headersReady() {
    return this._headersReady;
  }
  get contentLength() {
    return this._contentLength;
  }
  get isStreamingSupported() {
    return this._isStreamingSupported;
  }
  get isRangeSupported() {
    return this._isRangeSupported;
  }
  async read() {
    const {
      value,
      done
    } = await this._reader.read();
    if (done) {
      return {
        value: undefined,
        done: true
      };
    }
    return {
      value: value.buffer,
      done: false
    };
  }
  cancel(reason) {
    this._reader.cancel(reason);
  }
}
class PDFWorkerStreamRangeReader {
  constructor(begin, end, msgHandler) {
    this._msgHandler = msgHandler;
    this.onProgress = null;
    const readableStream = this._msgHandler.sendWithStream("GetRangeReader", {
      begin,
      end
    });
    this._reader = readableStream.getReader();
  }
  get isStreamingSupported() {
    return false;
  }
  async read() {
    const {
      value,
      done
    } = await this._reader.read();
    if (done) {
      return {
        value: undefined,
        done: true
      };
    }
    return {
      value: value.buffer,
      done: false
    };
  }
  cancel(reason) {
    this._reader.cancel(reason);
  }
}

;// CONCATENATED MODULE: ./src/core/worker.js










class WorkerTask {
  constructor(name) {
    this.name = name;
    this.terminated = false;
    this._capability = Promise.withResolvers();
  }
  get finished() {
    return this._capability.promise;
  }
  finish() {
    this._capability.resolve();
  }
  terminate() {
    this.terminated = true;
  }
  ensureNotTerminated() {
    if (this.terminated) {
      throw new Error("Worker task was terminated");
    }
  }
}
class WorkerMessageHandler {
  static setup(handler, port) {
    let testMessageProcessed = false;
    handler.on("test", function (data) {
      if (testMessageProcessed) {
        return;
      }
      testMessageProcessed = true;
      handler.send("test", data instanceof Uint8Array);
    });
    handler.on("configure", function (data) {
      setVerbosityLevel(data.verbosity);
    });
    handler.on("GetDocRequest", function (data) {
      return WorkerMessageHandler.createDocumentHandler(data, port);
    });
  }
  static createDocumentHandler(docParams, port) {
    let pdfManager;
    let terminated = false;
    let cancelXHRs = null;
    const WorkerTasks = new Set();
    const verbosity = getVerbosityLevel();
    const {
      docId,
      apiVersion
    } = docParams;
    const workerVersion = "4.6.60";
    if (apiVersion !== workerVersion) {
      throw new Error(`The API version "${apiVersion}" does not match ` + `the Worker version "${workerVersion}".`);
    }
    const workerHandlerName = docId + "_worker";
    let handler = new MessageHandler(workerHandlerName, docId, port);
    function ensureNotTerminated() {
      if (terminated) {
        throw new Error("Worker was terminated");
      }
    }
    function startWorkerTask(task) {
      WorkerTasks.add(task);
    }
    function finishWorkerTask(task) {
      task.finish();
      WorkerTasks.delete(task);
    }
    async function loadDocument(recoveryMode) {
      await pdfManager.ensureDoc("checkHeader");
      await pdfManager.ensureDoc("parseStartXRef");
      await pdfManager.ensureDoc("parse", [recoveryMode]);
      await pdfManager.ensureDoc("checkFirstPage", [recoveryMode]);
      await pdfManager.ensureDoc("checkLastPage", [recoveryMode]);
      const isPureXfa = await pdfManager.ensureDoc("isPureXfa");
      if (isPureXfa) {
        const task = new WorkerTask("loadXfaFonts");
        startWorkerTask(task);
        await Promise.all([pdfManager.loadXfaFonts(handler, task).catch(reason => {}).then(() => finishWorkerTask(task)), pdfManager.loadXfaImages()]);
      }
      const [numPages, fingerprints] = await Promise.all([pdfManager.ensureDoc("numPages"), pdfManager.ensureDoc("fingerprints")]);
      const htmlForXfa = isPureXfa ? await pdfManager.ensureDoc("htmlForXfa") : null;
      return {
        numPages,
        fingerprints,
        htmlForXfa
      };
    }
    function getPdfManager({
      data,
      password,
      disableAutoFetch,
      rangeChunkSize,
      length,
      docBaseUrl,
      enableXfa,
      evaluatorOptions
    }) {
      const pdfManagerArgs = {
        source: null,
        disableAutoFetch,
        docBaseUrl,
        docId,
        enableXfa,
        evaluatorOptions,
        handler,
        length,
        password,
        rangeChunkSize
      };
      const pdfManagerCapability = Promise.withResolvers();
      let newPdfManager;
      if (data) {
        try {
          pdfManagerArgs.source = data;
          newPdfManager = new LocalPdfManager(pdfManagerArgs);
          pdfManagerCapability.resolve(newPdfManager);
        } catch (ex) {
          pdfManagerCapability.reject(ex);
        }
        return pdfManagerCapability.promise;
      }
      let pdfStream,
        cachedChunks = [];
      try {
        pdfStream = new PDFWorkerStream(handler);
      } catch (ex) {
        pdfManagerCapability.reject(ex);
        return pdfManagerCapability.promise;
      }
      const fullRequest = pdfStream.getFullReader();
      fullRequest.headersReady.then(function () {
        if (!fullRequest.isRangeSupported) {
          return;
        }
        pdfManagerArgs.source = pdfStream;
        pdfManagerArgs.length = fullRequest.contentLength;
        pdfManagerArgs.disableAutoFetch ||= fullRequest.isStreamingSupported;
        newPdfManager = new NetworkPdfManager(pdfManagerArgs);
        for (const chunk of cachedChunks) {
          newPdfManager.sendProgressiveData(chunk);
        }
        cachedChunks = [];
        pdfManagerCapability.resolve(newPdfManager);
        cancelXHRs = null;
      }).catch(function (reason) {
        pdfManagerCapability.reject(reason);
        cancelXHRs = null;
      });
      let loaded = 0;
      const flushChunks = function () {
        const pdfFile = arrayBuffersToBytes(cachedChunks);
        if (length && pdfFile.length !== length) {
          warn("reported HTTP length is different from actual");
        }
        try {
          pdfManagerArgs.source = pdfFile;
          newPdfManager = new LocalPdfManager(pdfManagerArgs);
          pdfManagerCapability.resolve(newPdfManager);
        } catch (ex) {
          pdfManagerCapability.reject(ex);
        }
        cachedChunks = [];
      };
      new Promise(function (resolve, reject) {
        const readChunk = function ({
          value,
          done
        }) {
          try {
            ensureNotTerminated();
            if (done) {
              if (!newPdfManager) {
                flushChunks();
              }
              cancelXHRs = null;
              return;
            }
            loaded += value.byteLength;
            if (!fullRequest.isStreamingSupported) {
              handler.send("DocProgress", {
                loaded,
                total: Math.max(loaded, fullRequest.contentLength || 0)
              });
            }
            if (newPdfManager) {
              newPdfManager.sendProgressiveData(value);
            } else {
              cachedChunks.push(value);
            }
            fullRequest.read().then(readChunk, reject);
          } catch (e) {
            reject(e);
          }
        };
        fullRequest.read().then(readChunk, reject);
      }).catch(function (e) {
        pdfManagerCapability.reject(e);
        cancelXHRs = null;
      });
      cancelXHRs = function (reason) {
        pdfStream.cancelAllRequests(reason);
      };
      return pdfManagerCapability.promise;
    }
    function setupDoc(data) {
      function onSuccess(doc) {
        ensureNotTerminated();
        handler.send("GetDoc", {
          pdfInfo: doc
        });
      }
      function onFailure(ex) {
        ensureNotTerminated();
        if (ex instanceof PasswordException) {
          const task = new WorkerTask(`PasswordException: response ${ex.code}`);
          startWorkerTask(task);
          handler.sendWithPromise("PasswordRequest", ex).then(function ({
            password
          }) {
            finishWorkerTask(task);
            pdfManager.updatePassword(password);
            pdfManagerReady();
          }).catch(function () {
            finishWorkerTask(task);
            handler.send("DocException", ex);
          });
        } else if (ex instanceof InvalidPDFException || ex instanceof MissingPDFException || ex instanceof UnexpectedResponseException || ex instanceof UnknownErrorException) {
          handler.send("DocException", ex);
        } else {
          handler.send("DocException", new UnknownErrorException(ex.message, ex.toString()));
        }
      }
      function pdfManagerReady() {
        ensureNotTerminated();
        loadDocument(false).then(onSuccess, function (reason) {
          ensureNotTerminated();
          if (!(reason instanceof XRefParseException)) {
            onFailure(reason);
            return;
          }
          pdfManager.requestLoadedStream().then(function () {
            ensureNotTerminated();
            loadDocument(true).then(onSuccess, onFailure);
          });
        });
      }
      ensureNotTerminated();
      getPdfManager(data).then(function (newPdfManager) {
        if (terminated) {
          newPdfManager.terminate(new AbortException("Worker was terminated."));
          throw new Error("Worker was terminated");
        }
        pdfManager = newPdfManager;
        pdfManager.requestLoadedStream(true).then(stream => {
          handler.send("DataLoaded", {
            length: stream.bytes.byteLength
          });
        });
      }).then(pdfManagerReady, onFailure);
    }
    handler.on("GetPage", function (data) {
      return pdfManager.getPage(data.pageIndex).then(function (page) {
        return Promise.all([pdfManager.ensure(page, "rotate"), pdfManager.ensure(page, "ref"), pdfManager.ensure(page, "userUnit"), pdfManager.ensure(page, "view")]).then(function ([rotate, ref, userUnit, view]) {
          return {
            rotate,
            ref,
            refStr: ref?.toString() ?? null,
            userUnit,
            view
          };
        });
      });
    });
    handler.on("GetPageIndex", function (data) {
      const pageRef = Ref.get(data.num, data.gen);
      return pdfManager.ensureCatalog("getPageIndex", [pageRef]);
    });
    handler.on("GetDestinations", function (data) {
      return pdfManager.ensureCatalog("destinations");
    });
    handler.on("GetDestination", function (data) {
      return pdfManager.ensureCatalog("getDestination", [data.id]);
    });
    handler.on("GetPageLabels", function (data) {
      return pdfManager.ensureCatalog("pageLabels");
    });
    handler.on("GetPageLayout", function (data) {
      return pdfManager.ensureCatalog("pageLayout");
    });
    handler.on("GetPageMode", function (data) {
      return pdfManager.ensureCatalog("pageMode");
    });
    handler.on("GetViewerPreferences", function (data) {
      return pdfManager.ensureCatalog("viewerPreferences");
    });
    handler.on("GetOpenAction", function (data) {
      return pdfManager.ensureCatalog("openAction");
    });
    handler.on("GetAttachments", function (data) {
      return pdfManager.ensureCatalog("attachments");
    });
    handler.on("GetDocJSActions", function (data) {
      return pdfManager.ensureCatalog("jsActions");
    });
    handler.on("GetPageJSActions", function ({
      pageIndex
    }) {
      return pdfManager.getPage(pageIndex).then(function (page) {
        return pdfManager.ensure(page, "jsActions");
      });
    });
    handler.on("GetOutline", function (data) {
      return pdfManager.ensureCatalog("documentOutline");
    });
    handler.on("GetOptionalContentConfig", function (data) {
      return pdfManager.ensureCatalog("optionalContentConfig");
    });
    handler.on("GetPermissions", function (data) {
      return pdfManager.ensureCatalog("permissions");
    });
    handler.on("GetMetadata", function (data) {
      return Promise.all([pdfManager.ensureDoc("documentInfo"), pdfManager.ensureCatalog("metadata")]);
    });
    handler.on("GetMarkInfo", function (data) {
      return pdfManager.ensureCatalog("markInfo");
    });
    handler.on("GetData", function (data) {
      return pdfManager.requestLoadedStream().then(function (stream) {
        return stream.bytes;
      });
    });
    handler.on("GetAnnotations", function ({
      pageIndex,
      intent
    }) {
      return pdfManager.getPage(pageIndex).then(function (page) {
        const task = new WorkerTask(`GetAnnotations: page ${pageIndex}`);
        startWorkerTask(task);
        return page.getAnnotationsData(handler, task, intent).then(data => {
          finishWorkerTask(task);
          return data;
        }, reason => {
          finishWorkerTask(task);
          throw reason;
        });
      });
    });
    handler.on("GetFieldObjects", function (data) {
      return pdfManager.ensureDoc("fieldObjects");
    });
    handler.on("HasJSActions", function (data) {
      return pdfManager.ensureDoc("hasJSActions");
    });
    handler.on("GetCalculationOrderIds", function (data) {
      return pdfManager.ensureDoc("calculationOrderIds");
    });
    handler.on("SaveDocument", async function ({
      isPureXfa,
      numPages,
      annotationStorage,
      filename
    }) {
      const globalPromises = [pdfManager.requestLoadedStream(), pdfManager.ensureCatalog("acroForm"), pdfManager.ensureCatalog("acroFormRef"), pdfManager.ensureDoc("startXRef"), pdfManager.ensureDoc("xref"), pdfManager.ensureDoc("linearization"), pdfManager.ensureCatalog("structTreeRoot")];
      const promises = [];
      const newAnnotationsByPage = !isPureXfa ? getNewAnnotationsMap(annotationStorage) : null;
      const [stream, acroForm, acroFormRef, startXRef, xref, linearization, _structTreeRoot] = await Promise.all(globalPromises);
      const catalogRef = xref.trailer.getRaw("Root") || null;
      let structTreeRoot;
      if (newAnnotationsByPage) {
        if (!_structTreeRoot) {
          if (await StructTreeRoot.canCreateStructureTree({
            catalogRef,
            pdfManager,
            newAnnotationsByPage
          })) {
            structTreeRoot = null;
          }
        } else if (await _structTreeRoot.canUpdateStructTree({
          pdfManager,
          xref,
          newAnnotationsByPage
        })) {
          structTreeRoot = _structTreeRoot;
        }
        const imagePromises = AnnotationFactory.generateImages(annotationStorage.values(), xref, pdfManager.evaluatorOptions.isOffscreenCanvasSupported);
        const newAnnotationPromises = structTreeRoot === undefined ? promises : [];
        for (const [pageIndex, annotations] of newAnnotationsByPage) {
          newAnnotationPromises.push(pdfManager.getPage(pageIndex).then(page => {
            const task = new WorkerTask(`Save (editor): page ${pageIndex}`);
            return page.saveNewAnnotations(handler, task, annotations, imagePromises).finally(function () {
              finishWorkerTask(task);
            });
          }));
        }
        if (structTreeRoot === null) {
          promises.push(Promise.all(newAnnotationPromises).then(async newRefs => {
            await StructTreeRoot.createStructureTree({
              newAnnotationsByPage,
              xref,
              catalogRef,
              pdfManager,
              newRefs
            });
            return newRefs;
          }));
        } else if (structTreeRoot) {
          promises.push(Promise.all(newAnnotationPromises).then(async newRefs => {
            await structTreeRoot.updateStructureTree({
              newAnnotationsByPage,
              pdfManager,
              newRefs
            });
            return newRefs;
          }));
        }
      }
      if (isPureXfa) {
        promises.push(pdfManager.serializeXfaData(annotationStorage));
      } else {
        for (let pageIndex = 0; pageIndex < numPages; pageIndex++) {
          promises.push(pdfManager.getPage(pageIndex).then(function (page) {
            const task = new WorkerTask(`Save: page ${pageIndex}`);
            return page.save(handler, task, annotationStorage).finally(function () {
              finishWorkerTask(task);
            });
          }));
        }
      }
      const refs = await Promise.all(promises);
      let newRefs = [];
      let xfaData = null;
      if (isPureXfa) {
        xfaData = refs[0];
        if (!xfaData) {
          return stream.bytes;
        }
      } else {
        newRefs = refs.flat(2);
        if (newRefs.length === 0) {
          return stream.bytes;
        }
      }
      const needAppearances = acroFormRef && acroForm instanceof Dict && newRefs.some(ref => ref.needAppearances);
      const xfa = acroForm instanceof Dict && acroForm.get("XFA") || null;
      let xfaDatasetsRef = null;
      let hasXfaDatasetsEntry = false;
      if (Array.isArray(xfa)) {
        for (let i = 0, ii = xfa.length; i < ii; i += 2) {
          if (xfa[i] === "datasets") {
            xfaDatasetsRef = xfa[i + 1];
            hasXfaDatasetsEntry = true;
          }
        }
        if (xfaDatasetsRef === null) {
          xfaDatasetsRef = xref.getNewTemporaryRef();
        }
      } else if (xfa) {
        warn("Unsupported XFA type.");
      }
      let newXrefInfo = Object.create(null);
      if (xref.trailer) {
        const infoObj = Object.create(null);
        const xrefInfo = xref.trailer.get("Info") || null;
        if (xrefInfo instanceof Dict) {
          xrefInfo.forEach((key, value) => {
            if (typeof value === "string") {
              infoObj[key] = stringToPDFString(value);
            }
          });
        }
        newXrefInfo = {
          rootRef: catalogRef,
          encryptRef: xref.trailer.getRaw("Encrypt") || null,
          newRef: xref.getNewTemporaryRef(),
          infoRef: xref.trailer.getRaw("Info") || null,
          info: infoObj,
          fileIds: xref.trailer.get("ID") || null,
          startXRef: linearization ? startXRef : xref.lastXRefStreamPos ?? startXRef,
          filename
        };
      }
      return incrementalUpdate({
        originalData: stream.bytes,
        xrefInfo: newXrefInfo,
        newRefs,
        xref,
        hasXfa: !!xfa,
        xfaDatasetsRef,
        hasXfaDatasetsEntry,
        needAppearances,
        acroFormRef,
        acroForm,
        xfaData,
        useXrefStream: isDict(xref.topDict, "XRef")
      }).finally(() => {
        xref.resetNewTemporaryRef();
      });
    });
    handler.on("GetOperatorList", function (data, sink) {
      const pageIndex = data.pageIndex;
      pdfManager.getPage(pageIndex).then(function (page) {
        const task = new WorkerTask(`GetOperatorList: page ${pageIndex}`);
        startWorkerTask(task);
        const start = verbosity >= VerbosityLevel.INFOS ? Date.now() : 0;
        page.getOperatorList({
          handler,
          sink,
          task,
          intent: data.intent,
          cacheKey: data.cacheKey,
          annotationStorage: data.annotationStorage,
          modifiedIds: data.modifiedIds
        }).then(function (operatorListInfo) {
          finishWorkerTask(task);
          if (start) {
            info(`page=${pageIndex + 1} - getOperatorList: time=` + `${Date.now() - start}ms, len=${operatorListInfo.length}`);
          }
          sink.close();
        }, function (reason) {
          finishWorkerTask(task);
          if (task.terminated) {
            return;
          }
          sink.error(reason);
        });
      });
    });
    handler.on("GetTextContent", function (data, sink) {
      const {
        pageIndex,
        includeMarkedContent,
        disableNormalization
      } = data;
      pdfManager.getPage(pageIndex).then(function (page) {
        const task = new WorkerTask("GetTextContent: page " + pageIndex);
        startWorkerTask(task);
        const start = verbosity >= VerbosityLevel.INFOS ? Date.now() : 0;
        page.extractTextContent({
          handler,
          task,
          sink,
          includeMarkedContent,
          disableNormalization
        }).then(function () {
          finishWorkerTask(task);
          if (start) {
            info(`page=${pageIndex + 1} - getTextContent: time=` + `${Date.now() - start}ms`);
          }
          sink.close();
        }, function (reason) {
          finishWorkerTask(task);
          if (task.terminated) {
            return;
          }
          sink.error(reason);
        });
      });
    });
    handler.on("GetStructTree", function (data) {
      return pdfManager.getPage(data.pageIndex).then(function (page) {
        return pdfManager.ensure(page, "getStructTree");
      });
    });
    handler.on("FontFallback", function (data) {
      return pdfManager.fontFallback(data.id, handler);
    });
    handler.on("Cleanup", function (data) {
      return pdfManager.cleanup(true);
    });
    handler.on("Terminate", function (data) {
      terminated = true;
      const waitOn = [];
      if (pdfManager) {
        pdfManager.terminate(new AbortException("Worker was terminated."));
        const cleanupPromise = pdfManager.cleanup();
        waitOn.push(cleanupPromise);
        pdfManager = null;
      } else {
        clearGlobalCaches();
      }
      if (cancelXHRs) {
        cancelXHRs(new AbortException("Worker was terminated."));
      }
      for (const task of WorkerTasks) {
        waitOn.push(task.finished);
        task.terminate();
      }
      return Promise.all(waitOn).then(function () {
        handler.destroy();
        handler = null;
      });
    });
    handler.on("Ready", function (data) {
      setupDoc(docParams);
      docParams = null;
    });
    return workerHandlerName;
  }
  static initializeFromPort(port) {
    const handler = new MessageHandler("worker", "main", port);
    WorkerMessageHandler.setup(handler, port);
    handler.send("ready", null);
  }
}
function isMessagePort(maybePort) {
  return typeof maybePort.postMessage === "function" && "onmessage" in maybePort;
}
if (typeof window === "undefined" && !isNodeJS && typeof self !== "undefined" && isMessagePort(self)) {
  WorkerMessageHandler.initializeFromPort(self);
}

;// CONCATENATED MODULE: ./src/pdf.worker.js

const pdfjsVersion = "4.6.60";
const pdfjsBuild = "10a846417";

var __webpack_exports__WorkerMessageHandler = __webpack_exports__.WorkerMessageHandler;
export { __webpack_exports__WorkerMessageHandler as WorkerMessageHandler };
PK
!<%���d	d	-chrome/pdfjs/content/web/cmaps/78-EUC-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE���?�A��] �g` ^�ga��?�F�A]�y�"
�W�	���0�&R�J�-U��*�s��H �# �Dq���e�Q�g�J�n��"�x�R1��S�3�T�8�U�Aq
���C�V	�M�W�X�X�\�Z�`�['�e�\�q���!�]�I�^4�+�_�a�`�{�a�~q����b��a�!��(�c�D�d�J�e�R�1�U�f�Yq
�� �]�g�r��h�
� ��i�"a��	�;�j�F�k=�[q����l��m�#�2�?�n�H�o�f�p�m�q�t�r�vq���w�s�{�t��u��v�%�w�)�x�,�
�2�y�;�z�H�{�P�|a���U�}�i�~<�vq��	�3�
�>�	�J��U�]�o������a��1���D��X�	�eq	���o��v�	�y�
�{�D�a��&�M�
�)�v����q	��	�+�	�6�4�A��x�
�~q
���	���
���(��>��H��Mq	���g�-�q�� ��'� �0q���E�!�G�""�g�#	��$��&�q���#�(�n�+�)�<�*�O�+�R�,�b�-�lq¡��.��/
�.�0�=�1
�D�2�P�3�W�5�^qá�_�6�f�7�i�9
�m�;1�|�<�/�=�4�>�;qġ�=�?	�Z�@�e�A�g�B�j�C�l�K�q�5"�xqš��D��E�"�F�)�G�1�H�:�I�C�J�F�K�M�_�R�M�_�P�i�6�n�N�sqơ�y�O��P��Q�.�S�5�U�:�V�@�X
�C�m�Oq
ǡ�W�Y�`�Z�p�[��\�	�]� �^�$q
ȡ�5�_�9�`�C�a"�E�b�i�c�w�d�
qɡ��e
��g�;�#�h�%�i	�(�j�3�k�A�l,�Dq	ʡ!�q�m��n��o�+�p�Jqˡ�O�q�Q�r �X�s�z�3��t��,�/�(q̡+�-�u
�Z�v�f�w�l�x�o�5�y��z�	�{q͡��|��}�;�~�?�4
�E��Ta
Ρ)�i-��D�#�H�X�]�`�M4�z�''�0a϶�wa���.��%���q	ѡ�X��u�����	0�aҡ]�6q	ӡ��
�$�"�.�
�R��_a��'aԡ"�r��w�6�"�P$�m��"�.[�0�"]��"�jY�n�">�H
�
�� �"�&�?0�Sa	����x�%��>����;���L��q
ۡ#���)��.�"�<��O�h�Q�	�Xa޹�m;�aܡ]�b�"]�@�"��7#�N�s�"]�|�"�Z7�`��0�"	�8�CL�Ia����U�8� �5�4�!�"q	�	��$;�!�n�^�Q�`�%�ea�(�t�&��'�(�T�2q	��R�(�k�\�x�)��*� a��u7� a��a��0�+�I�,	�K�-7�V�"$��.7�4�"B�l�/�0�0�5�1�>�"�J�2�^�37�e�4	��"�(
�**�6#�bq	����
�6�
�70�#�8�Uq	��d�9)�v��!�:�*�;�:a���*a
�+�B�o	�	��",� �N�l�n�"�~��&�5�"]�\a���<
�=�Y�>�?�@�:�A�B�Cq
�#�:�F
�_�G�k�H�q�1�I�a�]�q
��v�J
��K��w�L$�$�)	�Ja�/�T�M*��N�1PK
!<8�uH��-chrome/pdfjs/content/web/cmaps/78-EUC-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�78-EUC-Ha���O
�Q	�S�V�[A��m�?�2a���PK
!<�U+K	K	)chrome/pdfjs/content/web/cmaps/78-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE!!��]a!!]�y�"
�W�	���0�&R�J�-U��*�s��H �# �Dq0!�e�Q�g�J�n��"�x�R1��S�3�T�8�U�Aq
1!�C�V	�M�W�X�X�\�Z�`�['�e�\�q2!�!�]�I�^4�+�_�a�`�{�a�~q3!��b��a�!��(�c�D�d�J�e�R�1�U�f�Yq
4! �]�g�r��h�
� ��i�"a5!	�;�j�F�k=�[q6!��l��m�#�2�?�n�H�o�f�p�m�q�t�r�vq7!�w�s�{�t��u��v�%�w�)�x�,�
�2�y�;�z�H�{�P�|a8!�U�}�i�~<�vq9!	�3�
�>�	�J��U�]�o������a:!1���D��X�	�eq	;!�o��v�	�y�
�{�D�a<!&�M�
�)�v����q	=!	�+�	�6�4�A��x�
�~q
>!�	���
���(��>��H��Mq	?!�g�-�q�� ��'� �0q@!�E�!�G�""�g�#	��$��&�qA!�#�(�n�+�)�<�*�O�+�R�,�b�-�lqB!��.��/
�.�0�=�1
�D�2�P�3�W�5�^qC!�_�6�f�7�i�9
�m�;1�|�<�/�=�4�>�;qD!�=�?	�Z�@�e�A�g�B�j�C�l�K�q�5"�xqE!��D��E�"�F�)�G�1�H�:�I�C�J�F�K�M�_�R�M�_�P�i�6�n�N�sqF!�y�O��P��Q�.�S�5�U�:�V�@�X
�C�m�Oq
G!�W�Y�`�Z�p�[��\�	�]� �^�$q
H!�5�_�9�`�C�a"�E�b�i�c�w�d�
qI!��e
��g�;�#�h�%�i	�(�j�3�k�A�l,�Dq	J!!�q�m��n��o�+�p�JqK!�O�q�Q�r �X�s�z�3��t��,�/�(qL!+�-�u
�Z�v�f�w�l�x�o�5�y��z�	�{qM!��|��}�;�~�?�4
�E��Ta
N!)�i-��D�#�H�X�]�`�M4�z�''�0aO6�waNK�.��%���q	Q!�X��u�����	0�aR!]�6q	S!��
�$�"�.�
�R��_aYx�'aT!"�r��w�6�"�P$�m��"�.[�0�"]��"�jY�n�">�H
�
�� �"�&�?0�Sa	TD��x�%��>����;���L��q
[!#���)��.�"�<��O�h�Q�	�Xa^9�m;�a\!]�b�"]�@�"��7#�N�s�"]�|�"�Z7�`��0�"	�8�CL�Ia^P��U�8� �5�4�!�"q	b!	��$;�!�n�^�Q�`�%�eac!(�t�&��'�(�T�2q	d!�R�(�k�\�x�)��*� ai"�u7� ai.�ae!�0�+�I�,	�K�-7�V�"$��.7�4�"B�l�/�0�0�5�1�>�"�J�2�^�37�e�4	��"�(
�**�6#�bq	j!���
�6�
�70�#�8�Uq	k!�d�9)�v��!�:�*�;�:alM�*a
l!+�B�o	�	��",� �N�l�n�"�~��&�5�"]�\ali�<
�=�Y�>�?�@�:�A�B�Cq
p!#�:�F
�_�G�k�H�q�1�I�aq!]�q
r!�v�J
��K��w�L$�$�)	�Jas!/�T�M*��N�1PK
!<�n�^	^	.chrome/pdfjs/content/web/cmaps/78-RKSJ-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@�<�?�@�< �g` ^�ga
�@>�y,�8�"	���0R�JN>��\�s�i �#�D�Sq���e�Q�g�J�n��"�x�R1��S�3�T�8�U�Aq�@�C�V	�M�W�X�X�\�Z�`�[�eq
��
��\��]�I�^4�+�_�a�`�{�a�~a�@��b��a�!��(q���>�c�D�d�J�e�R�1�U�f$�Y�g�r��h�
� ��i�"a�@	�;�j�F�k�[q���z�l��m�#�2�?�n�H�o�f�p�m�q�t�r�vq�@�w�s�{�t��u��v�%�w�)�x�,�
�2q
���6�y�;�z�H�{�P�|�U�}�i�~<�vq	�@	�3�
�>�	�J��U�]�oq
���r�����6���D��X�	�eq	�@�o��v�	�y�
�{�%�a��E�.�v��C	�+	�6(�Aa���)a���
��m�
�q���j��x��~���
���(��>��H��Ma�@�g�-�q�� q����'� �0�!�G�""�g�#	��$��&�q�@�#�(�n�+�)�<�*�O�+�R�,q���b�-0�l�.��/
�.�0�=�1
�D�2�P�3�W�5�^q	�@�_�6�f�7�i�9
�m�;!�|q����<�/�=�4�>�;�?	�Z�@�e�A�g�B�j�C�l�K�q�5"�xq�@��D��E�"�F�)�G�1�H�:�I�C�J�F�K�M�_�Rq���Z�M�_�P�i�6�n�N"�s�O��P��Q�.�S�5�U�:�V�@�X
�C�m�Oa�@�W�Y�`�Z�p�[�q����\�	�]� �^�$�_�9�`�C�a"�E�b�i�c�w�d�
q�@��e
��g�;�#�h�%�i	�(�j�3�k�A�l
�Dq	��@�R�m��n��o�+�p�Ja�@�O�q�Q�r �X�s�zq��	��3��t��,�/0�(�u
�Z�v�f�w�l�x�o�5�y��z�	�{q	�@��|��}�;�~�?�4�Ea���J>�T-��DD�H�X�]�`,4�z�''�0a�U�wa���?�.�F���q	�@�X��u�����	�a���wa��|�C��$"�.�R
�S5�_��6C�P!�m��[�0C>�!�KY�nC>�Ha	�O�
	�#�

�6���&����q
���
��
��'� ��?�0�Sa���a�@#��)�.�"�<
�C�O�h�Qg�XC>�@6��m�7#�N�sa�d��!���9�`�?�Fa�@>�|#�;7�`��0C	�8�C-�Ia���5a��8� l�!�"q	�(�w�$;�!�n�^�Q�`�%�ea�@(�t�&��'�(�T�2q	�6�3�(�k�\�x�)��*� a�@�0�+�I�,	�K�-�VC�o�.7�4C>�lq
��+�/�0�0�5�1�>�2�^�37�e�4	�a�@�(�u
�*�*�6� �bq	�!�g��
�6�
�70�#�8�Ua�@�d�9)�v��!q��#�:�*�;3�:�*�o�<	��=	�a�@,� �>�Nq��_�?�l�@�n�A��B��C&�5a�@>�\q
�B��F
�_�G�k�H�q�1�I�a�@>�q
�*�W�J
��K��w�L$�$�)	�Ja�@/�T�M
���N�1PK
!<�@i��.chrome/pdfjs/content/web/cmaps/78-RKSJ-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�	78-RKSJ-Ha�A�O
�Q	�S�V�[A���m�S a���PK
!<a�bة�)chrome/pdfjs/content/web/cmaps/78-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�78-Ha!"�O
�Q	�S�V�[A!a�m�?�2a%u�PK
!<jM��[
[
0chrome/pdfjs/content/web/cmaps/78ms-RKSJ-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@�<�?�@�< �g` ]�g�wa�@>�y,�8�e�m�t��R	���0R�JN>��\�s�i �#�D�SQ ���7	w	WE$$a�@�q
�_�!�f�$�h�&�j�(�l�+�k�.�o�1a�~�q	���8�w
�=�z�y��H��Oq���e�Q�g�J�n��"�x�R1��S�3�T�8�U�Aq�@�C�V	�M�W�X�X�\�Z�`�[�eq
��
��\��]�I�^4�+�_�a�`�{�a�~a�@��b��a�!��(q���>�c�D�d�J�e�R�1�U�f$�Y�g�r��h�
� ��i�"a�@	�;�j�F�k�[q���z�l��m�#�2�?�n�H�o�f�p�m�q�t�r�vq�@�w�s�{�t��u��v�%�w�)�x�,�
�2q
���6�y�;�z�H�{�P�|�U�}�i�~<�vq	�@	�3�
�>�	�J��U�]�oq
���r�����6���D��X�	�eq	�@�o��v�	�y�
�{�%�a��E�.�v��C	�+	�6(�Aa���)a���
��m�
�q���j��x��~���
���(��>��H��Ma�@�g�-�q�� q����'� �0�!�G�""�g�#	��$��&�q�@�#�(�n�+�)�<�*�O�+�R�,q���b�-0�l�.��/
�.�0�=�1
�D�2�P�3�W�5�^q	�@�_�6�f�7�i�9
�m�;!�|q����<�/�=�4�>�;�?	�Z�@�e�A�g�B�j�C�l�K�q�5"�xq�@��D��E�"�F�)�G�1�H�:�I�C�J�F�K�M�_�Rq���Z�M�_�P�i�6�n�N"�s�O��P��Q�.�S�5�U�:�V�@�X
�C�m�Oa�@�W�Y�`�Z�p�[�q����\�	�]� �^�$�_�9�`�C�a"�E�b�i�c�w�d�
q�@��e
��g�;�#�h�%�i	�(�j�3�k�A�l
�Dq	��@�R�m��n��o�+�p�Ja�@�O�q�Q�r �X�s�zq��	��3��t��,�/0�(�u
�Z�v�f�w�l�x�o�5�y��z�	�{q	�@��|��}�;�~�?�4�Ea���J>�T-��DD�H�X�]�`,4�z�''�0a�U�wa���?�.�F���q	�@�X��u�����	�a���wa��|�C��$"�.�R
�S5�_��6C�P!�m��[�0C>�!�KY�nC>�Ha	�O�
	�#�

�6���&����q
���
��
��'� ��?�0�Sa���a�@#��)�.�"�<
�C�O�h�Qg�XC>�@6��m�7#�N�sa�d��!���9�`�?�Fa�@>�|#�;7�`��0C	�8�C-�Ia���5a��8� l�!�"q	�(�w�$;�!�n�^�Q�`�%�ea�@(�t�&��'�(�T�2q	�6�3�(�k�\�x�)��*� a�@�0�+�I�,	�K�-�VC�o�.7�4C>�lq
��+�/�0�0�5�1�>�2�^�37�e�4	�a�@�(�u
�*�*�6� �bq	�!�g��
�6�
�70�#�8�Ua�@�d�9)�v��!q��#�:�*�;3�:�*�o�<	��=	�a�@,� �>�Nq��_�?�l�@�n�A��B��C&�5a�@>�\q
�B��F
�_�G�k�H�q�1�I�a�@>�q
�*�W�J
��K��w�L$�$�)	�Ja��oa��Ia�@/�T�M
���N�1�\�>�'3�fG�C>�bl�!	��Eq	�@	�	��o�E�B�:�w�"�'a��O�J�I+�C>�F|�C�PK
!<!��8""0chrome/pdfjs/content/web/cmaps/78ms-RKSJ-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�78ms-RKSJ-Ha���c�b�`a�A�O
�Q	�S�V�[�ma�C�L�RA���N�r�AS a���Q ���9	>1>s"	F1N9/B%$7Bq�_��	���	�������a���PK
!<�Ll��0chrome/pdfjs/content/web/cmaps/83pv-RKSJ-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@�<�?�@�<� ` ^aa�@>�y,�8�e�m�t��R	���0R�JN>��\�s�i �#�D�SQ ���7	w	WE$$a
�@>�h��(>�G�C>�&�e�'�w�q�z�y��a/��]�eC>�C|�C>�|�>C>�;|�zC>�w|�6C>�3|�rC>�o|�.C>�+|�jC>�g|�&C>�#|�bC>�_|�C>�|�ZC>�W|�C>�|�RC>�O|�C>�|�JC2�G,]�zC>�X|�C>�|�SC>�P|�C>�|�KC>�H|�C>�|�CC>�@|�a��K�7R��!
�8�H�O`�?�Fa�@>�||�;a�@>�8|�wC>�t|�3C>�0|�oC>�l|�+C>�(|�gC>�d|�#C>� |�_C>�\|�C>�|�WC>�T"�a��\q�@�y�O�|�Q��S��V��[�4a��8�m*�:�e�m�t��R	���0Q	쟽n�G�F�E�D�C�B�A�@q��S�s�m�t�
�u��v��w�Q	�@�x�5�4�3�2�1�0�/�.a�I�&�}�@q
��\�~�`��b��d��k�a��s��9�z�y��a�@��1
�:�H�Oa�_��`���d|PK
!<#a���0chrome/pdfjs/content/web/cmaps/90ms-RKSJ-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@�<�?�@�< �g` ]�g�wa�@>�y,�8�e�m�t��R	���0R�JN>��\�s�i �#�D�SQ ���7	w	WE$$a�@�q
�_�!�f�$�h�&�j�(�l�+�k�.�o�1a�~�q	���8�w
�=�z�y��H��Oa��]�eC>�C|�a*�@>�|�>C>�;|�zC>�w|�6C>�3|�rC>�o|�.C>�+|�jC>�g|�&C>�#|�bC>�_|�C>�|�ZC>�W|�C>�|�RC>�O|�C>�|�JC2�G,]�zC>�X|�C>�|�SC>�P|�C>�|�KC>�H|�C>�|�Ca�@>�@|�`�?�Fa��oa��Ia�@>�||�;C>�8|�wC>�t|�3C>�0|�oC>�l|�+C>�(|�gC>�d|�#C>� |�_C>�\|�C>�|�WC>�T"��\�>�'3�fG�C>�bl�!	��Eq	�@	�	��o�E�B�:�w�"�'a��O�J�I+�C>�F|�C�PK
!<��Q�""0chrome/pdfjs/content/web/cmaps/90ms-RKSJ-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�90ms-RKSJ-Ha���c�b�`a�A�O
�Q	�S�V�[�ma�C�L�RA���N�r�AS a���Q ���9	>1>s"	F1N9/B%$7Bq�_��	���	�������a���PK
!<pNd��1chrome/pdfjs/content/web/cmaps/90msp-RKSJ-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@�<�?�@�< ` ^a�@>�y,�8�e�m�t��R	���0R�JN>��\�s�i �#�D�SQ ���7	w	WE$$a�@�q
�_�!�f�$�h�&�j�(�l�+�k�.�o�1a�~�q	���8�w
�=�z�y��H��Oa��]�eC>�C|�a*�@>�|�>C>�;|�zC>�w|�6C>�3|�rC>�o|�.C>�+|�jC>�g|�&C>�#|�bC>�_|�C>�|�ZC>�W|�C>�|�RC>�O|�C>�|�JC2�G,]�zC>�X|�C>�|�SC>�P|�C>�|�KC>�H|�C>�|�Ca�@>�@|�`�?�Fa��oa��Ia�@>�||�;C>�8|�wC>�t|�3C>�0|�oC>�l|�+C>�(|�gC>�d|�#C>� |�_C>�\|�C>�|�WC>�T"��\�>�'3�fG�C>�bl�!	��Eq	�@	�	��o�E�B�:�w�"�'a��O�J�I+�C>�F|�C�PK
!<�^G�##1chrome/pdfjs/content/web/cmaps/90msp-RKSJ-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�90msp-RKSJ-Ha���c�b�`a�A�O
�Q	�S�V�[�ma�C�L�RA���N�r�AS a���Q ���9	>1>s"	F1N9/B%$7Bq�_��	���	�������a���PK
!<L�gV��0chrome/pdfjs/content/web/cmaps/90pv-RKSJ-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@�<�?�@�<� ` ^aa�@>�y,�8�e�m�t��R	���0R�JN>��\�s�i �#�D�SQ ���7	w	WE$$a�@�K	�a�^�	�}	��0a�|�^�a#�!�g�jQ�@�1���B�0�q�>�I�B�C�va�L�5�e�X�q�d�b�_�rq	���:�s�R�P�S����a���z�x�t��u�N�M�L�K�i�Q�G�Vjw�
�$�+���"		a�V�K�N8�}�=Q
���Z�2�C@��C�}����a����j�(�hQ���&
�|�����A���pa/��]�eC>�C|�C>�|�>C>�;|�zC>�w|�6C>�3|�rC>�o|�.C>�+|�jC>�g|�&C>�#|�bC>�_|�C>�|�ZC>�W|�C>�|�RC>�O|�C>�|�JC2�G,]�zC>�X|�C>�|�SC>�P|�C>�|�KC>�H|�C>�|�CC>�@|�a���EX�H�M�8a���s�q&��v�k��y`�?�Fa�@>�||�;C>�8|�wC>�t|�3C>�0|�oC>�l|�+C>�(|�gC>�d|�#C>� |�_C>�\|�C>�|�WC>�T"�a�A�O
�Q	�S�V�[a��\A끽m�S a��`���d|PK
!<��'�0chrome/pdfjs/content/web/cmaps/90pv-RKSJ-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�90pv-RKSJ-Ha�A�O
�Q	�S�V�[A���m�S a���q����	���
�
����	��������a����!�$�&�%8�PK
!<��;s	s	*chrome/pdfjs/content/web/cmaps/Add-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE!!��]a!!]�y�"
�W�e�m�t���1	���0�&R�J��*U��*�s��H �# �DQ (!�7	w	WE$$A0!�e�Vq
0#�g��w�R1��S�3��6�T�8�U�Aq1!�C�V�M��V�W�X��\�Z�`�['�e�\�q2!�!�]�)�^4�+�_�a��h�`�{q	3!��bG��d�J�e�R�f�Ya4! �]�g	��h�
�i�"q	5!	�;�j�F�k �[��}��q6!��l��m�#� �;�n�H�o�f�p�m�q�t�r�vq	7!�w�t��w�)�z
�H�!�|a8!�U�}�i�~<�vq	9!	�3�@�>������a:!1���D��X�	�eq;!�o��v�	�y�
�{���"<�a<!&�M�
�u��q	=!	�+�	�6�4�A��w�
�~q>!�	��� ���>��H��Ma?!�g�-�q�#� ��'q@!�E�!B�G�#	��$��&�$��%�"q
A!�#�(�*�)�<�*�O�+�R�,�b�-�lqB!��'��.��/
�.�0�=�1
�D�2�P�5�^qC!�_�6�f�7�h�9
�m�;1�|�<�/�=�4�>�;q	D!�=�?	�Z�@�e�A�g�C.�lqE!��D��E�"�F�)�G�1�H�:�I�C�J�F�(�L�M�M�_�N�sq
F! �y�P��Q�-�S	�4�V�@�X
�C�@�OqG!�W�Y�)
�a�Z�p�[��\��]� �^�$q
H!�5�_�9�`�C�a�E�*�U�b�i�d�
q
I!��e
��g�"�h�%�i	�(�j�3�k/�AqJ!!�q�m��n��+�$�o�+�p�Jq	K!�O�r �X�s#�z�t��,�'q
L!+�-�u
�Z�v�f�w�l�x�o�y��z�	q	M!��|��}�;�~�?��TaN!X�i�E�#�H�X�`�M]�z�"�X�Z�u0��"�6E�NaNz��%������	aN|�-�%�.��/qS!��
�$��.�0�<�
�R��1�`a*T!"�r%��>�"�P$�m��(�"�.Y�0�$]��"�jY�n�"�H6�P
���"�&?�?��"#�$�)�O�"]�b�"?�@��".��N�T�t�"Q�|
�O�"�Z7�`��"	�8R�C�"M��e�"(�t��($�-aTD��x�%��>����;���L���%��d��"��2�8� �K�!�C�%�Z�&	�'a
TE�2&�3��4��5�)�7�Q�8�f�9�t�:�;�b�<q	d!�R�(�k�="�r�)��*� aw(��z�ya(e!�0�I	�K7�V�"'�4�7�"B�l�0�5�>�"�J�e0�w�"\�(�#�0�#�U�"�d2�v�*�"G�B��",� /�N�"�~�@��"]�\�"/�:,�k�"7�$�Q�"�v
��/�$�"/�T)��1�"�2ae9�+�,
�-��>��/�0�1�H�3�?�0�@�=�A1�8�B�93�:��<�d�>�Z�A�B��G��C�S�J�K�L��M�D*�N�'�\�z�E�H
�Iq
w<�1�T�7�U�4	�X��b��cawW�eqwY�$�!�f�"�g�&�j�(�,�k�.�+�l�/�nawt�u�:�{�|�*	�}���	���"�0
��J�D�L�C�O�B�T
�X�fq}!�O�L�R	�Q�Z�S�X�U�[�n�Ha}m�`�vPK
!<5�Pm	m	/chrome/pdfjs/content/web/cmaps/Add-RKSJ-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@�<�?�@�< �g` ^�ga�@>�y,�8�e�m�t��R	���0R�J�K>��\�s�i �#�D�SQ ���7	w	WE$$A���e�Vq
���g��w�R1��S�3��6�T�8�U�Aq
�@�C�V�M��V�W�X��\�Z�`�[�eq
��
��\��]�)�^4�+�_�a��h�`�{a�@��b<�q
��
�>�d�J�e�R�f$�Y�g	��h�
�i�"a�@	�;�j�F�k�[q���z��}���l��m�#� �;�n�H�o�f�p�m�q�t�r�va�@�w�t��w�)q
���6�z
�H�!�|�U�}�i�~<�va�@	�3�3�>q
���r�����6���D��X�	�eq�@�o��v�	�y�
�{���"�a
��E�.�
�u��C	�+�	�6�(�Aq���j��w��~��� ���>��H��Ma�@�g�-�q�#� q
����'�!B�G�#	��$��&�$��%�"q
�@�#�(�*�)�<�*�O�+�R�,q���b�-*�l�'��.��/
�.�0�=�1
�D�2�P�5�^q	�@�_�6�f�7�h�9
�m�;!�|q����<�/�=�4�>�;�?	�Z�@�e�A�g�C.�lq�@��D��E�"�F�)�G�1�H�:�I�C�J�F�(�L�Mq���Z�M�_�N&�s�P��Q�-�S	�4�V�@�X
�C�@�Oa�@�W�Y�)
�a�Z�p�[�q����\��]� �^�$�_�9�`�C�a�E�*�U�b�i�d�
q
�@��e
��g�"�h�%�i	�(�j�3�k�Aq��@�R�m��n��+�$�o�+�p�Ja�@�O�r �X�s�zq����t��,1�'�u
�Z�v�f�w�l�x�o�y��z�	a�@���;
�?�Jm�T�ED�H�X�`,]�zC�X�Z�u�5�E�Na
�Q�|�}�~�n�F������	a���-�F�.t�/q	�@��
�$��.�0�<�
�Rq	��
�S��14�`��2%��3�>a�@�P!�m���(Y�0E>�!�KY�nC�H6�P
�)�?�?�C#��)
�Cp�OC>�@a	�\�&�����]��*��,�&�a���4b�5�J�7�0�8q	����9K���N�:�T�;�t`�?�Fa
�@>�|�;�O7�`�C	�83�Cl�w�eC(�t��(�-a���8� l�!�"�%{�&	�'a�x�<q	�6�3�(�k�="�r�)��*� a�@�0�I	�K�VF�o4�7C>�la�X�+�,
�-a���>q��+�/�0�0�5�1%�>�3�e�?0�wa�G��z�ya�@>�(�g�0�#�UC�d,�v�#_�*�C,� �N&�_�@�C>�\N�,�kC7��Q*�W
��/�$C/�T���1a��@�A1�8c�94�:`�<��>9�A�B��G�(�C2�J�K�L�#�M�D+�N�\��E�H
�Iq
�[�1�T�7�U�4	�X��b��ca�v�e�$�!�f�"�gq
��&�j�(�,�k�.�+�l�/�na��u�:�{�|	�}���	��C�0
��J�D�L�C�O�B�T�X
�[�fq�@�O�L�R	�Q�Z�S�X�U�[�n�Ha��`�vPK
!<��)/chrome/pdfjs/content/web/cmaps/Add-RKSJ-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
Add-RKSJ-Ha�A�O�L�R�Q	�S�V�Z�S�X�U�[A
���na���HA
�@�x a�����a���	��
�Q쀾	�������a���&�%�!�$��|�
PK
!<���v*chrome/pdfjs/content/web/cmaps/Add-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�Add-Ha!"�O�L�R�Q	�S�V�Z�S�X�U�[A
$!�na$u�HA
%!�xa%u���b���	��
�Qw`�	�������awk��&�%�!�$��|�
PK
!<�H�==1chrome/pdfjs/content/web/cmaps/Adobe-CNS1-0.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�q8��������������������������������������������������������������������������������������������������������������PK
!<h`�ss1chrome/pdfjs/content/web/cmaps/Adobe-CNS1-1.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��qD�������������������������������������������������������������������������������������������������������������������������������������������PK
!<1HEYxx1chrome/pdfjs/content/web/cmaps/Adobe-CNS1-2.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��qE��������������������������������������������������������������������������������������������������������������������������������������������@��PK
!<����1chrome/pdfjs/content/web/cmaps/Adobe-CNS1-3.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��qJ�������������������������������������������������������������������������������������������������������������������������������������������������������������PK
!<�a���1chrome/pdfjs/content/web/cmaps/Adobe-CNS1-4.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��qK���������������������������������������������������������������������������������������������������������������������������������������������������������������PK
!<QFC��1chrome/pdfjs/content/web/cmaps/Adobe-CNS1-5.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��qK����������������������������������������������������������������������������������������������������������������������������������������������������������������PK
!<K��–�1chrome/pdfjs/content/web/cmaps/Adobe-CNS1-6.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��qK��������������������������������������������������������������������������������������������������������������������������������������������������������������S��PK
!<�Nd���4chrome/pdfjs/content/web/cmaps/Adobe-CNS1-UCS2.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�����_�V��p�y�2��i�	g���0��=�#��|��s��7��l��E��D��/�E�� 5��_��`�)��6�q96n}K8�L�=���7�I��	��>�
��P��}��"`�fK��"���>�$�M��[�f�q��!����)X[Xq�@��X��4��D
�[�3ā�)��P���H�*%���n�)�$��@%p?� �&�����	��C�N(�
4�B&(�|`���z�4�<�h�6 �$�V�b�F�|��R$!��<�0�1~2P
Z�J
�h�bR��rV4�V�@�P�9,^$�<��l��
2�v�R"���[S�H,$*p�V��]��f��r�y>"�Z�c<�	�N��|JT�<���SD0J��<x�L�x$*h����*�_ÉZ(�@�.(V�.>�n�,�"fH�l�|"� �<��eD"�,�	N��L�N�"�*�*S
�}�T.�f&	�>�v.��<Z�p�V�
;^s�,�Z���&���Lg-�j�R�N�`�(�$b�bu��b` \�X ���j�,�rNi^�2�O;�z>6D�L��R��H0��T	

	��*
��Y�
	��[��`6�D�HN�^�4�z�n��b�$�:�d��g	f�V�,�4�
��lP
��pp�\�@�t�r��z�&d�
@������X�(,�6�B��_�j�HUL
K�"O= 18c<1D+"�bN�9R%��AR��l��N>1NO<!(
O@#�%ZT 6
�z
�<#
!")�f��<*,#4�n��[��8$�2�\��<@��X4�(�<F
�N�d$-$98
<GPUL�d�p��T��g\
+2��R�d6t1��l�*c*A6IZ	+
#"!,-�f�	�px�R�F�&�*�:�v��X�f�:�l��2�.�xD�z�Z�l����n<� ("��G��^ 	�J�x
�J	��
^8.�,0OoZ!5'\!'$?"41 /�2&�~z�x�,(�$_S��6@=8+	&?*q
D �V�.�>X��YD�R07D7<�2f�|Z��\�-,
�H	
�LB��^�d�"�_�@84
?&+
!�tR|�0a�
�b�A(&%
1HSL��b�5B�^�P(z2
	
�
�B:ITS*
�g"7H/%�`�
k�&��40e�)l�JoR7'P%Ve	J
#=�'
�h�Gr8<�,�0
�.�>�Yv�z^�`.��Bby��b���*n�r�h+!�j0�J-	&=2+�,D��)���D�
�F� �J�6F	�l\�R���a�O�Y
@5B+SB.O�>t�Z	�~
��F<�� &1J#%&',3	
%.�~�(VA7�	�	L94=!4
�Zr�6�	\M��HJ
�:D
�&HnD�__��/2HD
9P
�8�4r&'-
"8�x
�J"H�!�$�Y:;	6m�

W;J[,(�P�.0\8��R�L,93H'F�}m67M�% O8�~���h��s�$4�L$B�>��vx��v�v �(J"��yF��y˂f�z�~��b�*`� H�>�(��(

�,Z�6E:?8o2K,�P�$:�*�$f�
���&��0�F	�B���h��
&�b�BB
�Ld�l���4�5P#il[`'!j;/
&&o^-K|YXE�d�$UQ�T
�
��(>��Njw/&2WN7		�A"�{W�"�
�
�N�YZ�l	Of
	3�B�X�Q
���8#
�r|�~
��@�+.	
(4 �`�V(�!J'
*'��c(H��e�N�x

	�l��g�,5"W��9h)��6!	34eP�Z�V�4	l&��rTC"=rO87)2D#A\�j#�R~���L67�j�H�		;u��$H2�<�P	Hw(�� 7,%$�D
�#�V� X�n�.*	�<Hh`	�&J�b %(1"(�z&� 	��(��^L%.)4;?P4� j>/ ;,�&�	��}	�<	�lb�	΍w�hJ�B�


�8�	�^�
�R�.	�a

�8�L��v^���}�vC+	#6 Q>Q��>�\p�4�r�Dt�?
S>��p�mF	E2=3H
MhYZala*�`�*J	M*d}�L��&8U	J)�J�,��0�
^]#.+!��*�%
r^��l>(V
	�DF5,D/bS
LA60OB'�,
�bH�.kF]!`"snA
.axI 5%"�D�q�^� D�"
�e΂�4�E��JZBmt!0[�/O?�^��(
�l+�DZl5O\+.,}9BG^4�|a��KH '"
E5�[L�$�p��~�4
�Z�z�$�)u
~<�R2u�(��
�6
�`Q�,��,� 
�F�`Z�! +$!��@	b�Z	�P3�:d�(
ARL%3,S�Q���@�Z��W��8 52��-D3�	�4
		�L ��~�.	�@�r	#$�&�2�lL
	�>�@�ٖl�&R���Z��J*��3+&#*�J���&�Ri�h�|��*LW$-2-^�C�f1@gB%�f�B!�X!	�
"Y`�,D7$��@
�1\1�49$�j�t��?^��n`�#D_��P_RvS4K�f�o81$"�R�2/�PA ' �	icv�z�l�q<�H�te�<�|B/� �g�8KZM*"O>1~	� �&�g�	�4Jd�$�l,�6g h;U~ib.��w0',M@���ah(?Z��q&(n�%�^n	�j#� !#��t4)�	�uk�n
�,�v�@N�d�:�^�:
�.��J!�
�p.'U�#	��_�4
��d�
+@�:�2([7�
�Z�D�56�`Q�ZUQTg�v�
&3)�p�;
T���"$�HHE$%
	>�@X
�"	�%+�h�>�"	
�&�H�R�
��#5(#�	
	�v�	
���7"xT�2�
���<
��*���T�

�P�%.�n
�B�	
�S/�^+$1+�
�U�+9�4
�X^
,?@G.!�6�2!�l
�8X����$�VpSYNE�
5`�dTA#;P�*d�Jd �L�Q2f�%&�j� O'8/@��"q:JS
�VT�&R�HF;eRAFJw
NU�27:
501B%�F.�H
�`+;2��uv�! ��w[*!
��>B��x�()�t�T�_�<
��{w��A��:5.!i�$��rl	�bZ�X'�؁f�<��tu�ED#?V
O�.�22�����
	/����
�f,3x_&1{R�rN��=!��% �6!
�� 
�($�0#"10'� �(�>�Z>?8!0';(
4�z�~�`��V�>:�8
�^�#u���X��0���s,'
>G4�p� ��\�v� 4	5,�!�U�</5�B�<#�~N�
�n%�"�P
��[�
Z�(�B�&�|fvR��V�n�a_8�h�8_	�Y34S*gp�l\j�
�|'V'Ha6!6A<M8A�J�L�~?-(?h(=;>G!,T
*�0�=}(MT�89
�V�&�Z�&
�,��*5��@=�>E
�Fy��@�p��b-B
Pc�h�[}�)�`}�R-18H7��Gq߁|	X�^�H$X�zW&-\'$�V(�hG<39x=�+bW�l5"-& 
!�D��&��kZ	�L�.��x�z
�,�fY$7.Y(��╣�h`
Z�v�`���"�n�H������|��
	
�
Q�,�R��J��vR8}�!X�<(/"��6-,	�J�96]���
�NP�
�#X
$P/4;:%!2�~�t
H&U>%&/6#"' ��qf��b	o089F/1F�|�*�(�o?xg(%b3%8>)d��2�~�Z��d�"B�|�@��v�>R�R
�~
,3&�\
�f�|$!��{�CV�z�*&G&�t0�vnrx�큠
�|JGY���"QpM�v-
"!��d���b*_jX�
,J/B�`:�f�5.���b�7'2�6�D�
�p
�*�	d
G*P}�t�$�e�	�,X�"�,�,
�	�:�>}�':d�,
�J(��M��R��
		�TRd�f�>�J"!&&��P�\,��n:�v�NC.?�/�N�(	�:�d�-$-�j�"!�<"'?:-	%R/� :��x#!B?85!
&�9(��sh�f��"�X�t�"�|�zF
�\�<n�4
� ~
$)�Pp�
-�r��$�#�;�C
F�H6+�&�
�	�~&
I�.X��

�\J�@�'	�
�h�
�r�:�*}t	M\O**0+�B����Tp
	�O��fb�.	+�V.
�$�|t�P�-�x	�p��e ��0�
�B

�*�x`�&�d�@�`�<7�L�2	
��f��R&!:
|qK0�v�"VZ�0�Q�o��_�V)0�l)�( �R�l�:	�:�
	
��
"�J�X�N�
(Y�~	
�  1"&	'!�(�\�JK�p�$R@�N�pM
:4))@=6��0 =<7�^�\�B#*3�`f�	�X�@�r

�~�����p�R�`h�5�W45g�J%#(-
�����T:�
�d�d�^�|��\����ƞہD��S�d0�T�>�H�
���
�d��~�H��,�
�k_0l�2,��q�
�|�b.�*�
�T�
�y�>�b�	�
�.~^	�\�`�v�,t�MF3�j
%�JV��f �H�h�&�T��r	� >�Ї 1�x
�
�
�|��&�L�`�=�l�P�d���xfk� �~h,��s�b�)zX�$��4��I�
�� ���Z���*�0�F�0�J�"�.�r��|8
�T�
�~s�"`�>� +�����)�B�D4	 �ˋF$�\�b(#
�>
�<�ۑ��d!9�I��+ �f�.\�r�t<�D
�@���f�4#�ZQ �J	@��y�|�N�6r�p�$�\�:	����"�:�T�&�$��6\�*�/���c
��f�2��(./	�'A�d�*z�n	
!�F�v�b,�+
�F��&��,��:
�Vvd�^�7jV��"�.�@�\�<�p�&�"�n�2���\�(�R�x�|�^�4*'��� �l
�*�l�&���f
�l���2��N�b�Z� ��6��H�"R���Q<��j�(�v�`�H�J�,�^�N�F�J�F��X�<�B�$���ێ���L�v�/���R�R�~���\1�.�8nR����R�D��F��^�2�Z��*�b�0�
�&���X��W��ц��r�(��G�.��b�%wםpn�ZP�`�?�F�H�R�.
	��R�\�j��?��^���D�����@�>�`��X�n��P��Q�j�~�8�"�0��~��4�_�|�8b���G�T�4��e����R��Y��a2�0�H�N�wt�
��,��I�n�s>�H\��F�^��L�DP�F�\�B�"0��T���V	�b�$^�D�Y.�"�Pp� �v��~��@		�d�2�Z�"�&��!&o<G (78!:�<�B�*�vZx�x�2�	��	�,�|�,�&�\~�@�*�z.�2�@�
	
�"�n��HZ�
pq�X�
s��6�(�j�6�L��`�4����	��7L3 MP+=,W
B�ZR�D�:S	�2�Q?T0RYH"3	D�L�6�fr�J&%.
�X�*�D	�R�x	�n.�4�68
%,�
���b�- #
�v�"		(!���lx#0=
`[2m*$�J
�P�6
���s��N��v��v�l�@�f�-�x� �>���u�H9#(W�[^C.!$U�^:�l�RF+��$S<�*�d
;3L=nk�$�j


�2AWb
(�p�^(92
$K)$�4Z�\�L	(
 
�`��Pv_TZ	�@�0OPU63+:7$�nP�^8')&9	%��T$�&�J&�t3B98%!0%H-
�G�g�� �4�>|�j�0+G:Ora0LASU$;�:=�^�G
/�U21�')./�L�V$
�$	�~�5u>��,����
�d�~�r��n�2<(�d�~>+#2=6
0=<)��:H�V�v
�4���mC,$=N)9
d�.�l�t|4�B3R����-NA�	�T�a�56��V]��2�W�1
�<0�
�")8
%9+:�t�F�	#��\� 0���^ ��BLN^�`4:)3"8w>&�
�b�z-01*)��b�45� �L(�l+ 3	 ;,�#g�`UBCxA1JU.5�C(0}�mLC=n<�
:g�L?�8�2b>�%Fm(8[a�0_(@A#8H�,<i`s0I23�L1<�mp��.� qrl�62	�B�
lY$�@~�
�u����'���x
�l
� �b�$�	�|z��,p	`��^ʀD�@#0
:%
3�bB�d�H?.W�H�+: ,#


	2
��0�R`�T�`� i*!����a3+M|�*P
G�rl�8Q�
�$�?RU�T����	hWj7;J)0�WT�Z�]ẂJ�;1	/�v��4rZ
&(�pt
�,�$�.$#
�^�~
	�l�jM�@5"#65"
	!(�X�V:�
U��c6�@�W"'.F	Y2)L3</�	�~��e��(1�\�'�h;s4+0<!/"
<'+�p�(�u�:z�(�/�mv�S�&`uB	�B�aQlO;D*�SpD�]�X4�%�Z�/�*p�$�@j�


�"
5T	
�Is�Nu!r�  
	�|�& %�ew"�v$

�D�
}y��P	�q�z�� n�b	�0�t	`�*N�	
�Z�
,WnY`/7
>gEf o	,
:k<I@��
%/#+>
%���m� ��r!&�*�hFP�$x�4�T	
�>�	!��	
	�V�,����8�L�v���a�`
"(U2#.�^2�LPF
Z9
�|�4�N�2�\�t]U< 9L YBsJd/+7�|�f0e&5.'!$1In3X%&S278�.�"Mn;
3 )2,'�
�!)$FE)2�*	�$�0�'"
)670-@= 	��x��_6�D�T
n�9�`�(+/RI$�bdH~?!
&;?.E�uL_�eN�S�FO		;�D�r�.	

�j�@�"h��T�OfizG14-#nU8(/4Y*�p�?k��Ck��:�*+��xW|EVq.1L�(Ym�0U^m4'
B?RM*Q�|$->G.9&#8)'(�^� �r�:
!�R�Z	
/&�6�T��u��|
��J��w41*
�$M<�&��yc�2|�$�z�	�H
7"&

=�`�"3.
��}J'�tb
b�M��
�*V	�""�\L
��6$7'`a4&I<(Y
 ?LO0cL?Lq�D�P.
&1� L�Ñ O�Rl$
!	A�f�&	�4�4� o�}N�.�

�,
�
 ��$3
!(�
� ��ԑ
 ���X�,)(��V ���
��f
��T�2�@�b��y
'.Akf!�.�h�z�$�R,m��GJiB#"O2O*'�z�8=	g�$G.�J�7KN	MZUP/0�o�
):]�;zF�NU6E.+N/#�|�,�
�:!$*)�E.#!:7{�(#�|�8:,Y�2�US**C8�V�C<1&#N3�0h
L�1!ve�B�R!��s`Q�}:s@"8;%Jwv�N�y�8ES�9QX)D�
�!�h�Ghe(V�=�b�.	Nd�!�l0�DR|�%!�nE8o&6*�/,. -qT^an 
e/D"}� )$EFO&�A!�nZ�
 id'"?�^Z
�H"S�B+
"�f~�t�z��"1wD��f�4"=x\ �"�"�(��J.'�8	�l,! 7:#3(1#�"u�h�r�"|��U�\'�V�"���Y"��?j�
�&RR�GNRW.
2�DI�IL=-<Y~
cd�	`=Hg0P�sPPUlIp�D�2'% /�l�H8=

�q��
�"�N)>3�%"�R�Br	�	�



#%�R'�7#��
��,JQ
�:�@4E,/"'DK2% �H"�l�#R���
Z�v�0�B��t�$�#cN��1*
-&
% 2'�<�D�>#zU�.$;&!HU$��
RK/�"�|(	+	J5	=J��#-,)S�
�#�\&�z34
��<	�r
	�p�\��#�a+2		./>M,�>�����@V
H,I@�-#�d20
KJ79If$'�@D��d
9�N�
O*&e
Hc(@�#$&i5lsN�k��	hS\Wb=/dQ#)H?>G��
�2�$Kk��Bts`C�E$Vn� +GT%8OP;0		,:8
g,�F ?6
?*+H#�hz�Z

�t�$�tR
/�$�tN�:>�6�i�V>.+�66N�n�$�wR'&�d�L$
 �$�x�=>3*7�
	�.�.$�z�$	��t,'��0

&'!(�4�%lr�`%�!�	,7�#�x.W8�vV	�V�e,>*Q)#�
?o8^G4-�R�SO
�
oh�
$9�!�b9$+b o(
8�*o�'#h*u3h2=�t(%I<�.�+*,� �#%{���h*�

�=En
�4>�%�����%����:!=0
�"�%���'�$�#%ђHEVgbH�+6	L; sTD'BII�X�
%���
lV�z�8�&����j�n�^�
�0�x��/% 1#0�n�M&%R���v�t%
-$7"#D3
�&%,#1
0�\�X�`"!(!$# 	�.�P�~�<$ )
�&t^S�
�^V��j	08$�&�ar
&AJ?U\�$�~B,�D&�d}~�1fI#!\�X%"�vX��J:�$SZq2	W056(3^7 ah9A,t�b!@��&�k��L	"B[|ydK��;&�o'DSK8Vx�u|$!�6h}�;Az7% -0�J)*�X��^�* �"�!'5u}$�
	�F�)"#�\UX�f�'Xy��4<5��!'iz�2&%
$��)��(E�p�X?,Q`�'�}�GJ;N�R�O'���
p�X
��4&-G!,<g&3dc
Z}:0_lW?��"!�W%!N/(M"UN�'�8)*?�J!�'�!
	�b��%�L�(��
�|�d!�D(��*
�<
�b]HCJ*;0 e:1F��A$K0/	
SB%�=�`�:�0H���(e��*L�Rb�(x�_�*�b��QXq�j(�R��0
/"5#"
�>� &#	�:�~

�.�b	�	�,�1�N�P�0n3"6.)O��r�J7"	,	o�3�:�s(�f�� 
 e$8IvC'p?Pk	J;!�0qE("##:&-3^?'B�t�&�4�|7�.|i	U'jWvA*[8Axm�;�@"-P{nE**e& �l)(!
1
�H�z
�)ms]
�H�)vt�	��l�L�
)�v��b
�%&
�h)�xɃ 	
�K��p�&%B? +��V9!B)�,j~
�v�v	�|p�$,YP7"+8C^c1jqD=*QX1R Y@Qt�*��<K*�d�f�"*�w�R�]7	5 "%-V
'�`�T�*7��0�	�D4�"*I��63$+6�H��*

�p	
�3
�
*m�
�"
�*|�s�	�6
	��=*���0G0B% %
#D[J=
.3,-=V��?%�^�f`b
�.^
����*,�*љ��'*י��Lp�(F	��|��6+Q��*�X�!)0�H�X
� 

"
� �x	�(+8^f�r�J�`�B%\#"%Y�

ir..5�2�J�J�#+bf�%"�,� b;:kjU<7Sb?P
)4
IB�+�jF�� �'+�k�x�L:QF"
�U�J?VE�6�?H!-L=B=�(:�	+�q�%49�+�si�+�sl
�(4
�R�h�\�T�f�
+�w��
	�+�x�+�y��>�\	
�L K*
'�d�,~�,~/ �<�,)zlzt��		!��0,?�V#J=
1
:G@=6K8*G>A"VM �l�~	(?6=�	,q��3#�(�,|�'
�"�\�,���la Q*,!�,����H�pl�
�,��A#�z�p��fp�,����,,����.-,3�T�C##�3?��"G05xeBKR	-4�J �C,�VTl��6x
�b�h��"
tv� -41�8(%
�P�-3���-7��.T��/	��<�-DV��(�4��H-[]��h�&�T� 

�0jF�n�t+F9*	
/%H	'%L+
��"�6�G�A 
&+
�p� �	-�t�472�-�u�h�|�-�vG�8�����-�x�$
�L�f	
�F�-�|;	$5,1
�n-�|
�:�
�(�!<=D9�r�lr�X�b�L.%+.QPO.OCl!9@A
B��*).+		4
+(
-@Y� 7�T�8.[�

,'
*�h@J�H�j�<
	�v��r�B
	�=V�hI2[�0.��`B�:EB
53�Z
�l	�f	�n8�
	�B�.Ǚ*�N�@
�.ۙ��t~��"=6�Jc�.��1(�.��>��n�8D."@Hb���_�/V�	�,���.�/"a�
+$�*�.��/3f��XF7'�`�T��v

�"/Ko�TU$�l

�p��"��,\�*�"� /oy	�6�d�x!!
�V
	�B�/��u�5/��Ђ�*"

)6�"�Y�T�z	
&!2+N�j
�,/ˉI�@	"1"#,�dF�L
�6
	+�/��K�8�/��>�R�S
H9):36# 
?FA@#�0�уBF��1��8�
�

bB�0:����(	�r6V�-0M�)(�v	4;�<	 !�f�$B*
F�0|R��@�x�l�Zl�#0�]ÂX��v�	�j�2��)&49 ���6�(�	0�p)%�^�	0�r��R
�$��	0�w�X�V
�"�0�|3�`��Dp`�0�x�<j�0�
	)(

A<#�:
�
1�	�>�1�W		\\~�2$	�VfF�15�	�D����1E�S�@��`�`/.PAax92#"[��*�1e�����1q�\
�N�	�1~�	�Z�T�1���
	9(!!�8
/�1��}%�1��p6%'
?�V\v`�1ßM��-�t�^�B�f�:���
 �1�p<
 *�1�r��(�z�`&\�<H�d�2y$��v�2
z��"�P��r��r�B�
2�"G87	�6�2.�	��$z�P
��.	�(�2D�U�d�j�j�!2N�1H#-,.1	1#�e-4�.�2q���H�92{�P�<�2	��r#5�s �*

'
6-'�2�����X	n��/�<�V�2�[H��B��:�L
�2�pJ�2�r�H�0�$�J�8�H�<�.�%2�~��~�Z�h�X�t"	�<�D	��|g�	3��4�<�"�P�$3!���
	&&�f�"�B��H
�.�D���3G�F.�0�3Q�$�*

#
0�3b��6/	<327�B�Z,8,Z��3�
�J�
3~]�	�@�\
	�P���
3�r��*�0�*~��|�3�|W�2�>�zn�,��3��3
�j	��x�

�*�~
�3ď`�H��	3ДF�p�F��B��d�=3ۚL�~�"4�`
�H	!<A�0r�PR��K�6���4���,��A�b�4e)	�`�(�,4 k�t� ��>�X�F�$�\�,�
�b	����x�&�|��0�J
�4N��*�4T�R

�X	)"+�(�	4u�*6B�4�V�
��<�~�4�k�	��n�^�D�|�f�X�b�F�*�h*�4����@�P�4���@�~:�p�l��		�B		�4��	�6�zN��e�2��<�����^��F�D��>4鎥��*�P�X�
�:��&&z`��7����j�f�8�`�T��
�<�D&�2��y�"�d�&�<�z�>��5)���(��p�
5/�$�`�`���,��H�6�2�5;�i�`��&�F�j�z����j��D�5Ir)��t�2k�����D_������4�p��|��|����~�/��y ��*��2�6��@�́6�NZ�6��@܊�6�R�c��<��Q��P�6��]��Z6����\��;��\�7��>�n�U��)�,�(��[��M""��1��4"��)��*��-�7���S�N�7+�O�ׁ7,W��7-�[�Ӂ7.q>�7/�U��70i��71�b��72[t�(�74�V����n�76ze.�78�Vݬ�79z��?�7>�Y܍�7?A�����w��7E�W��7F{�Y�7I�T���^�7K{�g�X�7N�Vߴ���7P{�� ���+�7T�W� �7U����W�1��{��n�7Z�W��7[|D|�7]�R܂�7^|��7_�Q�x�7`|�<��S��j�b���7h�[�D�7i}]�7j�[�ց7k}�B�m�zD�7p�P�W��"�7r}���#��B�7u�E��7v}�7w�`�m�7x���7y�X�!p�7{~n��c��Z����>�#�7��X�Ё7�����7��X�Q�7���7��E�a��s�7�E\�7��M�f���&�7���7��Y܉�7��[��'(��>���7��h܇�	�7��a�7��I��7�I��7��Y�&ЁЄ�7�g%��7��b�H�7��$��U�7��I��7�l�7��Yܘ�7�d�7��E܊�7���7��F�^�7�jS�7��R�e^�7�Dz��\�7��Z�R؃)�7�O��7��E��7�����7��~ݏ��Q��o��{��7�����7��~ݔ�7�A���^�w��7��C���:�7�6��7��L�}��8�7Ȃ{�Lj�7��Oދ�.��=�"��7�=���:��c��0��u�7��[ݥ�7փ��� ��U�7��[�ԁ7�jW���	�7��[�B�7ބX�7��V��7�q��=� ���D�e��7�7��Zߖ��@i�7����<��Y��c��d� �7��Wܑ�7�B@�7��W��	7�EC��`�������;��P�,�7��a�%*�7����7��\܈�8��8�E݂������H�8Ej��z��A�8�Fܢ�8S��8	�\ߚ�8
�~��|�8�h��8
��8�K�'�8��Ph��]���-���8�]���O�T�8�^z����R�8!�]��8"����j��-(�8(�b�%�8)��8*�^�$�0�8,����T�]�80�^�Y�81z�82�^�:�83G�84�^�8�85q|�$�88�U�0�h�8:�?��z��a�I�8>�R�z�8?���Y�8B�E�߁8CF��r��8F�_�Tt����8I7%�8J�_�S�8K�փ8L�_ݘH�8N��8P�F��8Q����h���8U�~�ׁ8V���8W�`���]�j�8Z;|�8[�`ݼ��a�8]z��
� �w&�8d�`�e��X�8f���:�8h�hޟ����8j�����8l�b����C����8o^��8p�L�b�8q�����8s�H�%�8t9��8u�[�ׁ8v�7�8w�a�<��}�8y�a�8z�a�l�<�8|���8}�a��8~�ă8�a��8�����n���4�1�8��b�3��U�8����_��B@�8��Uݹ����W���`�c���U�8��,|�8��b����8�ZÃ8��b���8�Ie��<�8��b������u�	8��s�N�?�@��r��U��{�8��a����9�b�8�I���x��<4��U��l�8��[�Ձ8�a���[��q�8��b��8�����c�W�8��[ܸ��	��6؂<j���)�y��h�8��1�_�f��O�%��8��R��8�pu�i��2��B�8��d�Ձ8̗W���8��d��8ϗ_�u��+�8��Lܷ�8ԗ�*""�8��U�l�8ܗ���%�8��e�3�8�����8��e���G�8�J���D.4��1�8��\�u�8�=Q�8��A�0��V��R�8��' ���8��[�'���)�8�U���`
�i��8��R�)�8�Kr�8��f�W�Z�8���,\W��9�U�%�96ă9�dܱ��F�9���9
�f��9����M��
0�9�a��9P4��~��c�9�L�}�9�P!�9�g�>�9ZE�9�F�c�9���f�9�g�h�9�ԃ9�g߷��40���v�9%�~�9'�h�4�9(���9*�hݓ����K���n�9.�9�9/�hܹ�t�91����]��j�98�P�d�99����C�w�n�(�i�9?�c�+�9@�
��e���9D�[ݢ�9E��9F�~��9G�9��5+��t��9L�d܋�9M���g��z��)����b��q��1�9U�R�u�
�9Wqw�9X�R���a���9[s��9\�b����A�9^y��9_�b�~��n�9a�i��9c�b�D�9d���(��9g�b�l���g�9ir��Y��� �g�9n�Q�s��w�9pq~�9q�E��9rp��9s�Fܾ��4�9u>ǃ9v�F܅��R��o�9y7"�9z�E���z�9|6�9}�E�t��8���r�9�7#�9��E��9�W[�9��R�%�O�-�:��^�9���9��Mަ�9�����Q�9��Rݔ� �i��T��C�,�9�D�9��M����\�o�9�g���
�B�"��9��F�"�9�h��s�9��M�?��B�-��"�9�T��$�9��Yޱ�9�V��9��C����o��H�9����9��b��9�����I�d�%�9��Q�6�9�N��9��A�e�9�����|�9��U�Q�9�Zh�9��@ݫ�>�9�9��9��@�
���9�45�f�9��@����d���}�9Šڃ9��@��9�N��h~�W�?�9��R���	�	9�OB�Vz(�k0�d�F�r�9��Mݕ��F�
�9�mr�9��F܄Ȉ�9�Q�4�9��A��9�D݃9��Aܣ#�9�z�9��bޜ����9�RY��9��B�s�9�R���I��x��"�9��Pߌ�Y����9�iу9��C��9���@��t���}���9��R޼��_�9�S�����9��P�Ɂ9�U��9��@�E�:T̓:�B�Ƒ	:W��~�,��/�!������:�b�F���h�?�:Z,�i�
� �{�:�V�F��'�%Јf�:6����z�E�:�Wރ�:Z��:�b�����	�: ���)�:"�D��:#q��'��*�:&�b��:'����;��z�~�~-�:-�Fݖ5��b�:0^��}���:5�Fܠ�:66݃:7�E��:86���,�::�b�G�؂��n���j�:>_�:@�F�'��y�:BZk�:C�E�;�:D[D�:E�]��:F�`��E�:H�J�`��k�:J_���G�:L�Iݯ����+�:Pa��:Q�@�>ȇ(�:Ta��G�:W�Kܛ�V�����p�:[dq�:\�Q�e��w�:^:)�:_�J�"��Z�Ȃ2��e�:cc7�|��>�:g�R����y�:ib��:j�Kܡ�:kd;�^���}�:o�L܎�<��6��=�:sU
�:t�L�������J�:wf΃:x�Lމ�-�:z:�:{�U݄�:�'��'��<�m�:�x�:��L���3���:�4d�:��L�����:�f��:��[�$�:�fk�1�8�:��^�p����:�fc�:��L���:�f�:��V�r�:�8у:��N�:��}�:�;��:��Mߢ��I�:�t���u�:��Q�*�:�h���s����r�:��M����b�:�j3<�lv�:��F��:�e��f��k��D�8�:��O��M�:����]�:��b���?�:����:��`���z�:��g���J���:��O��:�=���h�:��P�<�:�Z=���8�:��:��P��I�:�p��<l�:��Qߏ�U�:�Qy�:��Rޤ���s�:�tz�:��O���3��T�g���:�?�+�:��R��������]�:�`���i��0�!��!��1�:��[��:�EQ��b��A�:��S��X�:�?X��x�:�:��i�Ɓ:���T�:��b�����C;�:�:��"�:��R�N�:�>��:��R���%�:��������:��b�����:�P߃:��T�!u�;wx�;�L�2�;w{�;�Qޗ��-�;:^�;�R��;	t8�D��9�;�R޺�;@ȃ;�Rޖ��.�;��;�U݁�;x�\����;�]�A��=�;A��#��N�q�; �O��;!z:�
��
�;$�E��;%A2���	�;(�C�L��~���g��@��;-5��&�w�r�;1�@����p�;3z����;5�I��r'�;8Z
�;9�@�[�;:x��
�;<�V�Ɓ;=z�����F��9�;B�V߳��-�;E|̓;F�R���;H|�%b�y�RTi�t�;P�X�a���V�;S}��;T�Wނ�;UBj�;V�Z�u���?�;Xgփ;Y�@�N��;[Wă;\�Y���5�;^��m�;`�B�,���x��2�;c{�;d�W��;e���C�:���;i�M�?��n��j�;l��;m�Yݠ�;n���;o�\�'��-�;qD�����k���p�;w�Yߴ�;x�Ã;y�Z�B�;z�b�;|�Z�Q�;}�S�;~�[ݧ�;��;��\��;�Z���;��F�@�;�[+�;��Fܡ�;�Z�;��F�؁;����;��~ݼ���[�;��-�;��]�"�;�Z��V��S��nL�|1�;��Uރ�;��T�;��]߅��`9{�G�;�>���j��]M�;��C�;��e�H�;�_M��:�;��Pބ�;�e��(�K�;��F�E�;��m���;��_�܁;�Y��P�'��9��*�x�;��S�	�;��02��-��F�D�;��H�!�;�H���:�]�;��E�ځ;�����i�;��R�/�;Đ����x�W��h��;��b�K�;�@b�;��b��;͔'�;��c�:�;Є��
�Z!�;��c�4�;�tE���;��R����1�;�>�;��L�%�;ޖ��;��c����;�>̃;��R������;�t�0��_���;��dܰ�;�hG�U�;��dܓ��i�;���+�;��b݉��8��o�c�;�����S��.��[��x�;��P��;�����B���;��C�&�;�]��<�b�����'�<��<�e�ځ<���<�e�ׁ<����K�<�b�P���S���x�<�܃<�b�E�<
?��R����\�L"�<�^��<=�����b��t�b�<�b�e�;�<j���"��G��`�<�f��< K��<!�f�H�<"����M��J���<'�D��<(X"�<)�Rݸ�!�<+xD�<,�I���0�<.h�����4��c��P�<3�I߁��R�<5a��N�
K�#��<=�g�-�<>���<?�h��o�<A�!��g�<C�P��<D���Q��6�<G�hݴ�
��	�<K���Q�<M�[����;�<P�����<R�[ݤ�>�)�<U� ��<W�[��S���<Zy�<[�Mݜ�F��:�<^=b��p�;��<b�@޿�<cx��z�*��S�I��F�<i�U�]�<jx=,�B�W�<n�E�W��b��n�<q6��.��N��9��@��<x�Pݵ�<yW��n�S�����8���<��Pݬ�<�q�<��[�@��S�<����<��Eߵ��&�<�a�}���<��Q܈�<�7,��<��b��<����<��F����s�<��r�<��[����[؋L��)��4�<�Y��<��OݷІ�<�}��<��X�[�o��W]���\�<������s���<��b�c��	�<�>�<��R޳�<�j��4��K�<��R�>�*�w������x��<�tH�<��R��<�p��<��R�v�<����=�p�<��A��<��1�<��b���q�<����<��b���%�<�I5�<��bނ�<�qk�<��R�C��	�<�V��<��A��� ���<�U���<��E��<�}��<��E����}�<�E.��$��o�<��b����'�<�Y��<��Fܿy����<�Z����<��O�[�<�6���\��M�<��bށ��?�<�t��O�<��B޴�<�J���R�;�<��Dߜ�<�qu��u�<��`��<�n�<��d��<�D���V�<��_�O�<�pg�q��3�<��O����<�n���;�<��@��
<�uQ��+�i�n�M�,�$��R��a���=�c���9�=
X���e�=
�E�R�=p��=�P�b������g���=h��Z�=�Dߘ�������+���F���[��9��H�=j�=�Mޭ��=!�>��?#�=$�[�&T��6�=(�����=+�E�:�=,��=-�[܀��$���q�=0Y��p�=2�E߳�=3Za�=5�F�Ѓl�=77-���=9�E�<�=:6�������m�=>�Pޥ�=?Zn��=A�Pޓ�=Bj+�=C�O��x�7��#�=Gq�=H�P�Y����=JO��=K�[�(�=L\ƒ=M�Q���|��!�=Pj�=Q�P�V�%�=S����j�=U�P��=V=���r�=X�~�%�=YY��`�=[�L��=\|Ӄ=]�^���}ȀB�=`�l�m�U�U�K�V�n��Y��m�.=mN!��.��[��:�j�M��V�9�O�k��*�t��}�<�q�i����l��e�]�]�Y�-�x�[�4��2����T�$��&����"��1���N�t�.��j���,�l�=��^�y�=����=��Rݺ�=����=��@�F�=�g��=��]�W��k��6���I�=�H��N�=��bޚ���?�����w�=�Q҃=��A��=�Y��=��Gި�=�;��=��O��=�9��=��a����@�=�7b�=��H�Á=��^�=��b�N�=������=��C�x���=������B�=��O�J��8�=�U��=��Z�R�=�4s�=��]�2�=�G�=��R�?�=�Pf�W�=��L�́=����|�=��b�H��q�=���=��_��=ҐV�=��Sߚ�=ԋb���=��Z��=���=��F��=ي�=��R����=�K��=��I��=ޔe�=��U��=�a��]�=��~�����K�=�Nj�=��R�4�=�V���=��[ܽ�=�w�=��Y��=����=��d����_�=����@���=��Y�i�ȁ�=�y��/�=��M��=�����$��T�R��_��5�=��Iݍ�><T��X�>�H�z�>�0�>�R�Ё>l9�>�C��>�'��>	�J�&�������>n��>
�R�*��>9���O����l�>�gܭ���>�C��I�>�E�
�>9��>�M�/���p���G�>q��p�>�dݨ�> ����r�>"�\ܯ��v���c�>%{C���>(�h�߁>)j�>*�`��>+S��>,�[��>-������L�>0�`�o��u������>4]��>5�c�0�>6W���>8�b�I�>9]��>:�R݈�>;e��,��>>�Rޥ��I�>@<��H�>B�Gߡ�
>C5��^��{�����G'�0��w���>M�e��>N���#�q��j�S�K���>U�^ܝ�>V7:��{�>X�`�$��M�>Z=�>[�OܷȆ@�>]V�����C�`��C��d��'�>e�gݘ�>fL}�H	��T��)�>k�O�@�>l�p�3��
�;����o�T���'��*�\��%���J�7��'��n��?�[�>��Cޝ�>�5���l)�~�>��`��>�6-���	m�G�4�>��C�A�(�>�^Ѓ>��C�v��V���>�����#��;�>��D�u�
>�b�����3��p�/�~��u���+�>��J�C�>��r�>��K޳�>�Q��w�>��Cݧ�>�Q��J� ��$��>��Z܊�
>�u�����p��m�#�J��a��.���!��h�.�>��C��>�rf��1�>��_��>�@���D��}�Y�>��Q�����6�>���>��]ݣ�>��+�>��X�H�>�q��T���3��t���>��Q��>Ђ-��E������B�>��gެ�>�M	��F����:�>��g�ށ>ۄ\�>��\޲�>�c.�l��w��:��y�1�u�\�>��A܎�>�v@�!�>��C޶�>�xz�>��_�.�>�X��Q�x����/�>�>��i�4�>���I��I��z��:�>��P��>��s��R�>��g��	>�R4���a����M�D��7�h�~�?�e���7�?���?�C�(��?	���b�?�G��?]���h�?�G�ё2?���k�[�D��c�"�w����a�4�Y��������3���f���V��O��
��4��M�Z�`�*�#�@�4��j�D���g����
����v�%��^�n����
���5��>�?A�E�m�?B����O��U��a�N�K��`��y�?J�cޗ�?Kc��s��x�)�k��@�?S�W�!�?T����
����g�?X�C�ԑ?Y�����B��J�j����B�*�O�D�?d�@��?eO9�D��?��8��N��	����j��{��6��R�P��S�
�
�ZI��a��Z��n�:�A����2�?~�i�J�?�w�6��n��o�t��7���?��`�}�?��(��E�+�M�]�x�A��<��1��'�?��h޲�?�xS�p�U��v�W�\�r�?��R޻�?��/��k��J��S���?��[��?��2��i�?��I��%?��5���R��^�X�-�@�5���~��
���h���5��6��W���p��c�+��"��3��a��"��l�[��y��<��
�q�r�:��a�����?��c�9�?��^��t��X��)��
�?��C�;�?�@e����Q��R��M�?��W��?�|ڊ[�p�-�'�B�u��*�?��]܆�?�[�W�$�?�H�?��Y�̑?�uS�*H�
��x�F�U�1�g�B�Q��f��J��G��&��v��?��P��
?�t����4�%�n����)��r��;��S�@�~�"�@	`��W�@�A�G�@�ۃ@
�A���J�@�L��/��r��u�Y�@�R�B���B�@�D�@�F�ہ@��@�O�ȁ@x<�q�K�@�R�3���l����Є$���]�@"O��@#�I��@$P�@%�[�)��4���	��`Ȇ=�@*���@+�@�~�@,_��@-�@߀s�@/nۃ@0�A��@1P�D��&��:��S��$�@8�Rݤ���;�@:Q`�@;�Lߴ�@<Qj�@=�B���x��/���E؂H��a�@D[���t�@F�Aݳ�J�@HQ�
��v��W
�k�l�@P�Rަ�@QQ���m��^��>��@W�M�-�
@X_{�a4*�p�=xH���X��E��X�@e�Z����*���@hUI��@j�O߀�Y�.�@mS3��m�@o�B��@pl˃@q�E�&�@rh�r���A�=�@w�E��@xq���w��@|�aޫ�@}S~�@~�~�2��9�x�@�w���'�@��bޛ�@�S��p�@��U�r�	@�?Y���7���h�K�]�@��Bߏ�@�S�@��O�����@�S��@��C�w�@�T�J��V��]�@��Z�S���?�@�U]�(�}�@��E�
��a�@�=��@��C�M���\��I�@�UG�5��N�#D��Y�@��F���D�@�ER�@��Q�5�@�f��@��Dܴ�@�V7�*�@��Lފ�@�f���O����B����*���#�@��b�f�@�6#�@��D�O�@�WF�@��Pݥ�@�ln�8���#�@��[�~�@�W�@��E��@�X�@��E�T،�@�X&�@��R��@�X�����|	�@��D�<�@�X���y�@��h�P���1�@����e��z���G��*�@��E�>�@�Z$��O�@��Eޒ�@���y�@��C�N���d�P����@�Yك@��E�����f�@�mq�@��F�(��'�@�Y��@��[�E�@�Z����{�@��Rݩ�@���������%�A�[ߡ������A72�A�E޸�A^��g�J�"��A
�^��A@�0�A�@��A[ՃA�F�4��.���O�A[��-��h�A�F�D�A\�t�u�A�Gܥ����A\I�Y�N�F
�6�A%�G�z�A']Z�A*�Gޤ�A+\��8��H���A/�R�Ђq�R�A2����#���V�6����A9�Qܼ�S�A;S��_��u��T�A?�E�q�A@^	��#�AB�a܂�AC^���7��6���8���AJ�@���$�AL:ރAM�D�:�AN_:�AO�Z܈���q��@�AR_c�AS�[�n�AT_r���AV�b�6�AW_��c��/�AZ�T�P�M������!�A^�փA_�@ޞ����Aa`1�&��E�F���,�Ag�Iߴ�Ahyq��c��:��C��X�Am�R݂��Apt����|�E�Au�~ܦ���)�Awa���Ay�A�V�Aza��;�A|�O߷��1�A~aӃA�bߝ��~�A�a���=�A��J݀��A�`#�p��'�A��@��A�bŃA��E�p�A�bՃA��K�
�A�cl�A��R�߁A�:��@��A��Dߎ��Z�A�o��A��K�6�A����A��U��A�d���*��+��&��G��j��A��R�(�A�e�1��E��n�A��R�@�A�K7�2��s�A��F�)�A�����	�A��M��A�fV�A��C�u�A�fg�A��T��A�fs��C�A��L�1������Y���A�wŃA��J��AÙ���E�A��Pߜ���A�i��A��M�����0�A�gg�A��P�����>�A�g���]��p�Z��V��w�A��L����r�A�h]�A��M�o�A�i�R�A��b�߁A�is�a�A��M�́A�i���A��M�<�A�;���V�h�A��b�J�A�B���r�B��o�A��@�ɁA�c��'�P�A��M�Y��_�A�jE�A��M��A�j���U��z�,�A��dݜ�A�<
���A��B�#�A�`��,|�A��I�́A�n��A��N�ہB���B�F�X�B7@�@�B�N�Z�Bk�B�O��Bk��4�B	�I܋ȁJ��>�BlZ��[�B�Oݼ�BDŃB�Oݽ��L��N��o�B6��
�B�C�2�B���B�L���Q�B��1�B�[�%�v��B!m�u�>��B%�R���u؇X�B(�3�B)�[�t�B*Q������B.�b�!��L���3�B1tS�B2�O߂�B3y��{�}�B6�L�K�B7o���W���B:�O�0�B;n��B<�Eܗ�J�B>EU��4��Y.����J�BD�d�p�BE=;��F�BG�P�D�BHoӃBI�Pܑ��9��y���BOQ߃BR�P�@��8�
BTpKdP��U��$��7�9��=�B`�P�w�Baq+2�{����Bf�P�e�BgqO��$�Bi�P��Bjq,�Bk�Q�Z���Bnq��Bo�b��Bpp�� ��f��[�	��b��?��8�Bx�U��Byr."�B{�R�t�B|h��.���B��L�D�B�h
�^�����Z�B��R�#����B�H�B��b��B�s(
�@K�B��C�:���f�B�s��~�B��P���w�x�B�f#��=�B��RݷU�Z�B�s�:�%�B��R�&�B�t9��E�B��~�/����B�t`�B��c޲�
B�tG�G�"���x���*��_�B��Rތ��[��2���*�B�[F�B��c���K�B�tȃB��F݈�B�u�B��c�����B�[׃B��cެ�B����q8�B��S߂�B�?�B��S��
B�u��c�*�RH>��F�B��X��B�v��<��o�B��Z��B�v�"(�B��[ߟ�Bڄb�B��Tܝ�>�C�B�w2�#�B��T� �B�wX�B��Lެ�B�w��B��b�d��O�B�w���o���H.�"�o�B��U��B�x��8��B��]��B����i�B��I��B�y��r�}�z�B��V�W�B�y��B��^�9�Cy<�X��C�\�&�C>���:�C�d�
�Cy���/��N��Q�C�C�B�$���*�CU��C�K�ƁC9�C�@�A�C�F�C�Sݸ���X�C���C�`ܾ�Cwz�C�K�8�C:4�@�C�`�]��W���Cd݃C�C�|�n�?�C"������`�C%�Cޖ�C&���C'�C�d�����X���C+���C,�C�F�����I�C/G�C0�Sާ��K�C2�����C4�e��C5?��O�C7�T������C9���C:�K�D�C;�n�3��N��6��9��l�#��M�CC�J�ʁCD����z��}�CG�Y��v���CJd�CK�T�R����CM�h��(�CO�b�L���7�CQ�����CS�@ݩ�CT?��p�CV�Kݍ�CW���CX�Q��������4����C\����v��y��x��{��l�Cb�J�f���Cd����Cf�Cݜ�Cgcy��w�Ci�I�u�Cj����?�4��s��
�Co�i����q��
�Cr���Ct�D��Ѓr�Cv9��f�Cx�`ޛ�Cy5x�Cz�W�I���C|���C~�Kܲ�ЉZ�C�4����C��S�;�C����
C��]�t���S�����$�����'�
�Ј���W���C�:�C��D�����T��A���C�����$�C��]�^�C�q@����0�1�����*�d�v�{B�"�C��~�(�C�5S�\�G��E������4�w����I��,�C��D�{�C����C��D�ӁC����C��D��C�����v���&���=��v��S��H�,��l�6,�'��	��$z�C��Q��C�s9��2��9�C��Sޥ�C�?ׁ�5����I�� ��m����"�J4��c��}��A��$��
��<�v��j��)��&���C��`݉�C鎰����?Z���@��y�|[�0��7��)�����e�|����6�:��
��w�	D_���p�y_�
�F�I�&�D��@��D�1ŃD��@��	��8�D�1ȃD��@�����d���l�D�Q�D��@܆�D�N[�>�{��H�Z�d����"�P��t�b�|�����D��R��D�R�/�z�2�D��X�j�D�4��D��X�K�D���z�D��Y���|�ES]���8�E�^ܲ�E���x�N�d�P�E�c��E����0��E�f��E�|�D�H��+��Q��^�E�@܇�E���M��T��=
�E#.�
�E0.��E4�hߩ���I�E6e
���>��`�_�E;�]�5�E<O�~�N��x�$$���&T�b�EQ\��V�h��h
�@�P�Z�p�\L`��(�>�,�nF�
Ek�n�;�T� ���#���+�Eu�B��Ev���N��u��v����E|�g�؇Q�E~�
�E�h�3�	E�5���0�F�K����4D���%�E��a�l��E��uȄ*�(E�N$�6nV��W�`�k�t�
N��>�U@�g��,�A�B�1�<�d�m��" ,<P�!2��.��%�E��_݄�E�@S�	E��D��S�؂x��G�3��+��b���E�@��E��K�ɁE�VR�E��C�1���T�����f�j�E�5��E��T�����/��G�E�?��	E��C�0�S�����<����<��n�x�E�?��E��Iޖ�E�d2�E��C߭ȁ��?��{�*���B�E�T���n���E��g�ÁE�&�a�E��K��E����!�E��Y�r�E�4��;�E��`ܽ�E�E��C��W�N���^�E�6	�E��C�ցE�V��E��Iߵ��Y��s�N��~���|�؆e��9��,��Y�FZT�q�F	�C��Ђ��p��1�u����F7�F�C߶�����.���H���G�AЄx����FX��c��F�U�=�F{����Q�V��t����n�%�F$�V�tЈo�VF&U�����,�h��[��((��3���:��wl���J�1��9��Z.��/��f�p��[�p�]��<0��y��h��<����\�����&��C��>��a��f��)��B����br��%��r�0�qt5� ��s�� ��-��X��O��p<B�~P�h��?��l>�4��%��h�F �8�F|�b��F}n�L��H��7��]�F��O�A�
F�o�x��)���=���B��7��|4�F��@�%�F�C��!�F��C�؁F�6N�<�F��W�e�F�f}�l�O����F��S߸���W���x�F�4~�F��K����
�F�F��q��a�j�$�F��Oߏ�F��L�F��S�ɁF�I*�F��h���1�w�F�:���<�F��I�z���Z�;�F�h��F��H�[�F��b�9�F��@��F��f�/�w�F��i�[�F�A���|���w�F��R��F�;��x�F��R�b�F�9���:��%���R��]��R�F��\�	�F�p��F��R�O�F�~e��m�y�F��R�]�}�F�}����C�F��N��M�F�W~�F��B����y�F�?�F��Gߞ���.�F�p�F��d�[�F�]p�<�F��b�Y���F�O���<�/N��1���F��R��F�s���/���#F�>�
��0�����24��#���L��1��:�e�K��8��G��J���U��x��]���T(�B�G�U�5�Gy3��S���h��?����x��k������$�,��e2��@��!��V��I��|�'�G4�C�ϑG5}�	X�l�+����D��A�V�,b��y��<��'���8�GOD��P��/��n��m��"D�^����L�&��Q��L�"�I�!�\F��	��rD,.N2��/�Gm��S��t>�8�*�� ��,� H�X�Gy�^�g�#Gz�8�6>�8�:R��	��T��-����M�?�}��,&��L��
��d���k��^$�
 F�-G����
�P��Q��

 ��)��RH�f�h��w��|��|��6����x��=��(��A��.��[��(:J��e��(��Q��<�$�2�f&����d��c�G��F�‘G͝I�h��]��X|�
��t�U�
��G��@ܔ�D�P�GߔÃG��A�ׁG��G��A��~�@���G�WW�6�G��B���b���v���a�G�T��G��B���~.", NN��*v��p�G�u���>L"�H6G�H�Dܿ�^�H
Sd�H�D���"���B�R�Hp��
H�F�-�x�.��H�
v�6~�Hq��H�H�E�Hi���C�H!�H�|�4D��^���:.V�$J`��v\،`؋A�HE�X�b�.��Ld�p�|���L�v�H@X��
HA�Nާ��R�N��>�(<L�B��Z�"�HKW��HL�Q�t�t��D�P�2�T�HRYe�HS�S߆��J�HUW?�HV�Tޙ��~XN� <�@��*�J�T�4��x�HdW��^�	Hf�X��^���r��X�j�HoV��Hp�Y��Hq6�Hr�Yߙ��(!�P�<$�d�L������8��d
�4�H����H��]�P�H�6��H��]�����H�V֓H��^��>���4��D2Ht�Fr@�>�T�VP�4��:��H�c�	H��b�V�B����h�v��LFV�H�=��H��d�^���)�H��ГH��e���r��<�n@�0�H�J����H�~��H��g�T�H�L���D�H��hޓ�H�q��H��h����H�M��H��Cܜ�HƏ�	�H��Iܰ�Hɏ��H��Rޓ�H�N�H��b�,�H͎��H��E���lXȃU��J��A��@�H�N��H��Wށ���)�4��*��Q�������s�H�=ƃH��W�؁H�N�H��C���CЅ`���H�U���N�H��D���H�[��H��Zܓ���?���t������G��J���;�D�ІX���{��j�>��/��h��i�E�D��2�H�N-���C� �1��h�I�R�n�IW��I�Vޕ���9��V��0���b������h���QЇx�'��,���%��c�IW��I�R�c�������n����I��I�R�>�I>�I�[�#���O����6�I"Wz�A�>�	I%�R�w��D��J������:�I����؉y����I.65�I/�b�-�I0R ��I2�O�����|��K��[���|�O�� ��ЁB���1���.��q�I@Eb�x�IB�X�L�IC�P�U�IE�X�k�	IFQ�>�T�#����4�e���IO�F�w�IP`���`�^�IS�P܌�����IV;+��IX�@ߵ�IYp��K�I[�A�ÁI\70�I]�D�v�6���0ЊQ�;Ia���"�� �%��s�z��[��0��/��0�mlA�X�]�`�}Ubg�}�D�I�@�5�<�A�p�c�2�7�8�A�q��t��s��t�/�lBg@�E�7�$�|A�I�.�P8��.��I��g�s�I�����;��
�I��P�N�I�pw��+�I��T�́I�5Y�I��W�0�I�a"�I��b�2�I������K�q�@�I��Lށ��
�I�<��I��F݀�I�K��&�E��)�I��a��I�@	��2�I��h޺�I�B#��V�K�I��f�G��;�IƗU���S�I��H�~�Iʓ��7�I��b��I͟�����u����N�� ��i��,��sz�I��Gݶ�I�^��P�I��I��I�e��I��L��I�i�R�I��O�c�I�l���I��Q��I�s���c�I��R��I�[�F��I��Wܤ���I�z��0�,�P��<�"�3�2�c�b�9�a����I��cݹ��u�J@q�`�n����{��k���0�J�R�{�J	���J
�\�
�JL��J�[�t�J
]{�J�Z���P�J[���0��)��W��_�;]�J�\�g�JMw�	�J�_ܱ�JL�J�_�ŁJ ;��@��J%�P޿��Q��t�����J*LW�J+�h�Q�J,GO�k�T�J/�_�l�J0M�G��J3�Z�#�J4r%�J5�V�T��c�D�4�J9fM�%��2��q�J=�b߹�J>=���~�J@�^��JA[ۃJB�G�^�JCZ����JE�gް�JFZ��J��b�H�JK�f�E�JLtav�JN�B�o�JOf��^�d���t�JT�L�V���JVC��6�<�JY�N��JZ�����M�J]�M�Q�J^]x��r��7���Jb�P����JdLw��]�Jg�@ݤ��8�Ji����1�Jk�@�9�JlY���X�R�Jo�f��Jp_;�Jq�Bߟ��B��V�JtA�p�Jv�d�y�Jw?��V�Jy�hݵ�Jz@��E�@�J}�[�F�J~A|�J�a޲��g�J�Em�3�J��Uܚ�J�Ea�
�Z��t��E�J��R��J�6a�xC�!�^ 0	�P�T��5���70�90�;0
�=0�?0�A0�C�Y  0��?��I�M�K�_	"f�b")!���i3�3�%�%m%�%q	�	!`0!�!�A����$1$`$f	$t	!p$R[P]�N�N�QmRR�_*N�N�Q�Q�S*^bSg*u(u0NNRORSpYY7[W[�be�f�l_lM��OPQ�R)R�T5%[Z\@\�2gNl�p|	u7:N�,Sw$YG\E^�_&_�"b�
b�gokf
l�r,u�y@BN�a\N_�_m=r�u�u�v�xy�y���(��K
PQ�$WYWNc*eHgl'4u�v�wP�* �����b.S??\\^�%b,e�"k�(q	t�ueu�R�{Q�v�Rt&X0X�
\
^�_�#c�	ebf� l.$qt5u%	v{,�4��;������P�
R�	U�U�W4b!df�Qt[v�x�z�i�y��r��=�������#V
![�abny�|�}��G��&������AQR�V/\d9ewk�(v!{����,�����5���>d�:q�uw�zM|���X�h�.���[Oe�o�Q�qJ������d�
j�
q�w�zaz�~T.�� �>X�)|>~i
���N����I|��'Q77�8�f��Q�=/�Z��vq���|�}�w8W"D\z&lKr�
��R�S�Qb�ls�u���-R2S
$Ws2_"PgxGu?5�m:T�	W�2\�^!bB
cg#g�
l %p�rI uGww�x{|x|�^O�Q�R^T�WZ-4cDe�gh2'mc/p�s�t�t�wy�
z�q�C�u ��	�NPcUut^�`�9hm"k7k�m�(r{ u�w1yiy�$}>���M�����\��
����
��Ve�1h�k�nFn<%n5AwFwKxd4>e��O���<Y���W�D%��7��N�R{>[�aId"-iH#k�k�n�EtJtOw_x�z.g�`��#���h������n#���w�
R�M^XaRdrDk�k�o;u!y�z�!}��O�&���h�D���>�`��QQR�je�ss^t�v�
x�h���c"���"�&
��)������=���'�6^g(f�#jMk�'q�	q�r�sfw�
x�x�|�~{�U0��	�-�����<�5��,��C����V�[�Hsn	uvHx�z�{�n��8�P0�+���.�BQ-\i^ma�f�j�po�"y ���5�P,�F�:���P�-Q4V�[=#p!	r�	w�	zg���
�Z��O���+���������t�@�V]�^�_�f�j�ry'zl�h
��V��!�.���b��9��V�pCpHpE~�%�z	�D$��(��[KkpUr
zp�4���J�H	�S=bk,�����	�nf������h��tkw����F�y��>�s�f
��pi^ 0�0�0>0A0�U0�6!�d�%z�C@�;}�E���:2�F��;w�g���A��L��Lݤ�BP�QpՃ%^ ^ 0�1�1�1�1��8<.�.�.�.�[��f�E��C�~&�D��D��F�n�qtW�Y�o0��H��b��I�#�r��PK
!<�#��0chrome/pdfjs/content/web/cmaps/Adobe-GB1-0.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�q�����������������������������������������������������������$�PK
!<��{���0chrome/pdfjs/content/web/cmaps/Adobe-GB1-1.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�q'����������������������������������������������������������������������������(�PK
!<�?���0chrome/pdfjs/content/web/cmaps/Adobe-GB1-2.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��qW�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������n��PK
!<[�Q]��0chrome/pdfjs/content/web/cmaps/Adobe-GB1-3.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��qX����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������P��PK
!<ɮ5YY0chrome/pdfjs/content/web/cmaps/Adobe-GB1-4.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��qr�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������PK
!<�GOqq0chrome/pdfjs/content/web/cmaps/Adobe-GB1-5.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��qw�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������K��PK
!<r������3chrome/pdfjs/content/web/cmaps/Adobe-GB1-UCS2.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE������
c��"�?��4�c��m��n�0��J>��|��"4C\;���b�s�]d+	A��	�",���!��$���>�� 0���\�^uK$�o�,�
�!��~K��]Y�Y�/��j��:�]��h�K �� �A�X�]dUbg�}�D�I�@�5�<�A�p�c�2�7�8�A%�L�Z�w�`�N���͑�C�A�X�]dUbg�}�D�I�@�5�<�A�p�c�2�7�8�A%�L�Z�w�`�N��P��h��y�H�5t��� ��^���c�9��L�h��K�?����?���e�J�Y���+�8�*��$��%�d�H�P�r�C�u�Kr��<��7��p�a�>��l�T��Y���y��4�-�U��]�\�q�-��h���t�
�6��s�p�(����5�:�1��\��3�Y��V�6��q�.�*�F�_��d����	��,�
���3����[��<�h��9�����)�&����&���/�B�?�Cu��`�y� ���)��t��F�Z�"��/��4��v�����q��`�"��|��O����1�T�\�L�Q�+�
�b�f��!�'�t��2��=���x�6�"�i�G�*��W��0�u��2Q���B��
�;��Z��Z���A��|�}�>�
�U�U��'t�&�W��^�d���"���H�g�����'��/���d�]��K�V��4�A�f�M�5��P��=�s�:��T���'�j�B�2�P��%��j�H��c���(�m�x�:��=��d��\����$��=�,��9��=�&�N����9�	���i����_�H�|���T��7���D�#��o�=�h�2��p��C�Z��
��u�
����i�o�"����P�&��1��9�D�4������@��=�6�L��)�!��F����2���W�v��[�8�&��o�h��Q���V�#���1��o�.��|��+��7�<��E����a��d�E�4�*��7��<�X��O��C��`�g�Q���|��	��(�k���o�b��R�?�A�.�q�X��p��I�O�8��\�q�.�Y�?�n��e��`�g��i��6��!G�l���<�x�����D���J��1��\���*��E�<�
�B��;��0��C�M��v�P�kN��H��)�:� �)��Z��E�D�2�����V�v�d��_�x�w�d�O��]��-�\�*��S�U��*��_�����W�q�g�j��
�f�	��	��v�=��k�'�Q��2��n��f��s�>�$�;�7�S���Z��Y�� �U�W�-�Y�C�d�-� ��>�G��&�Y��
��P����8�q��6��D����A��Q�N�d�x��K��"��q�����}�G�D��H��m��6�w������,�y�AA�X�N�{���?��o��>��_�-�J��j��=��/�A�t��}�&�A�K��"�-����d�}���=�� �?���D��a���e��h�C��
�>������0��j�����!��'�J�,�l�A��[�?��Z����4�X�.�C��'����
���l��C�5�D�i�&�a�D��r����7� ��H�5�#�n�B���
��$��a��F�M��i�t���8����
��@���k����M�!�������0��7��p�?���u��&>�?��,��{�����i�\�7��P��-��8����0�I��)� �e�n��$�d�x��-�D����f��?��@��	�K��F��?��������`��W�t�0��G�F� �c�f�&�#��A��3���#�J�u��z��%�#��P�u�`�W��d���S��~��y�f�V��{��x�s��w��$��;��2��7�0����Z��c�b�+��`��1�t�9���}���H�
��u� �R�,��%��4���?�e�H��g��Z�>��i�K�����X��_�������2���/��h�@v�H�B��5�&�#��T��-��X��k�B��k��f�F����,���}��[����M��J��'��,�@��{��$���J�Q��,��Y���,���/�J�%��Z�:�0��/�L�I����7�"���U�D�S��7�.��p�����w��o��b�#��k�4�L��s��h��5��O��l��?�N�8�$�Q���2��(�A��J��C�5��<��o��x�Q�e�*�9���X���� ���|�N�U�h�[��^��a��n��W�@�~��k�B��P����x�+��e��n��;��3�'����_��J�:�c�-�9��p��	����3��Z��� �@��~��Y�r�M���H�q��B�C�H�m�C�8�D��M�j��6�W�&��/��.�g���K�%�&�7�S�m�M���k��q����a���n�1��a�<��8�����^����#����&�I��w���r��}��G�+����4�i�_�F�g�c�x�g�5�z�x��+���6����b��A��2��A�b�i�N��2�.�/�
��e�o�d���l�p����d�7�&�U��w��"��y��.�q�Y�)��l����:��Y�&�@�
��+�7����3�5���q�E��h�Z�]�\�8�I��h��$�I�r�i�_��L��E�n�%�.�g��r��5��^�[�R��S������
�j�Z�?�$��9����c����
�n��e�{�(��,��
��n���8�a�A��/�
�*��o����=���F���H��b��m�D�[�$�=�,�
��0���G���E�V|�5��+�k��N��c�J��
�E��"���C���v�M�_��{��v�~�z���J�l�0�Y��s�H�v��k��x>���n�,��p�	�p��y�4�0���*��:��	��R��3��Z��?��D�+��<��W�j�*���<���-��p��e��&{��C�Z�}�
��-��r�^�����o��h���|����{�t�(�o�v�F�r��M�%��X���q���D�O�)�u�)�z�<��'�x�7���k��0��	�a�X���o�\����?��:��?�h�=�[��L�R�~�O�L��G�%��6��%�� �6�	�o�\�7���%��J����x��{����T���
��h�s�`�N�
�-��?�2��6�m��1��T�S��@�b�<�<�����,�]�I�s�f��Y�"���'���l�I�3��[�)��2�
��C�f��O��X���4�r�q�|��C��D�d�.��Q�j��K�i�w���&��I��V�g�K�����:�f�6��u�� ��o��0�_��-�P��^��1����o��=�\�P�]��R���,��Q��d��7��h��S�N�*�(��o�D�P��
����F�q��f�_�����P��W�^�V��6�
�c��J�� ��}�f����P��d��I�s�� ����T�;�J�E��	��@�&��-���G��-���4���~�>�c��0����Z���U�k�� �P���p�=�>�%�j��|��)�~�P�v�k��d�Z��X��v��S��F�Y�
�>��
��=��	��S�^�L��6���Y�D��E�I���U�y�$��{�~�9�N���b�� ��I�� ��Y��>��2�s��1��>����]��;��\��y�p����M���+�J���O��d������C�6�?�B��-�v��-����#�
���2�/�0��{�����`�s��g�H�S��D��C�V�&��g����m�f�	�;�>�c�N�e����o�$��W�\�<��+�p����#�y����L��_�i�H���`�^��!�#�s�X�O�@��0����;�s��Q�d�q��z�.��Q�k����5��1����q�;�C�{���"�P��[�� ��7��N�5���T�:�V�"��O�$����8�
��g�`��+��K�n�^�z����B���#�!��,��9�@��a��x��S��.��/��A�v��@������T�u�K��T�H�9�>�<��E���D��'�a��J��I�`�F�|�������F���P��~����0��A��n�X�+�l��F��m�����G��
��V���e��D�{����:��H���q�z� �	��v�K��	��D��k�p�-���T�b�"���J��[�x��C�J�_��`��1��
��?����u�B�A	��f�v�E�I�v�G�4��3�h�c�v�c�	�\��	�����+�(���;�8�G�F�N�u�n�S�w�E��T�U��)��d��A�J��R*��O�	�f�7��j��!��b��o��~�I��b�/�5��z�/�0�s�?�u��F�%�2�(���~�T��/��$��a�T���V��a��D�#�T��`�$��K��N�o��\��e�|�O�/�/�@������>��[�d� ��+�k���T���4�7��o��7�X�s���-�v��,�(��A��v�>��6�8���h��}�1�C���n�I�9�%�~�a�A����_�� ��m��A�^��\����a�t�i���T��7�b�~����^��[�h���Z��Y���2��q�8��u�4�3�m�j�$�
�n��<��+�5���{����,�;�'�d��2���c��>����i�*�`��+��x��G�i��<��y�F��&����H��S�t�S�D��<��v����`��G�)���u�
�;��L�>��A��d�����6���;�n�z���F�J�l�x�]���c�*��o�N�!��N�?�D�r�c�k�	����)���z��c�@���?�Z�u����/�~�@�S��r�i�T��}�.�~�n�)�Q��a�l���D�P�V�w�+�=�
�<���a�S��B��C��t�>�*�;��I�b�I���R�� �W�C��|�%�B�t�`�;����Q�����D�L��b�G��X�=�g��2��v��k��H��G�
��T�K��q��n�����-�>�
�C�:�S��(�M��U��,��
�.���&��5�
�����k�]�4�����Z���3�-���t��P��i��~�4�q���=�>�c�T�n���)�f�e�4�I�y�?�q���i�x�3�(�	�>�|�1���~��s�`���5��k�I�D�Z�B�t��I�V��^���r�J���7�A��n���Nl����z���?����Q��*�G��M�����=�\��G�t�&P�?���p���g��:��{�N�H��b���]�@���'�&�|��`��Y�V��W�=��z��
��x��a�4�&�D��c���f�e��!��(�O�T�A�F�� �/��3�I��
�o�J�v�W��b���c�j�t�Y��g��0�]��Q��`��
��
��#���)��x��O��P��#��>��(�Q��Q���<��I��.��%�C��B�/�p��i�H��8�M�F��	��*��'�l�"�O�k�]���u�� ��f�C��b���
�H�9�f��W�$��2��Y��P��C���@�/��.�/��R� �M�R�s�=�{��0��w�b��h�^�6���N�q��\��}�R�q��~��|�I�Y��b�>���|��i�@�~�*�u�(��K��x��%�n�G��Z�}�Y�X��
��_�S�8�$��w���L����>�.��m�R�p�x��n�@�E�1�#�8�Z�|�T��!��6�]��_� ��L��9�]�B��������0��#�)�|�M�J��'��v��K�S���Q��W��N���s
�f��E���n�h�~��	��q���d��u�#��H�O�-�O�W�F�F�a��O�\
�g�W��V�eM��e��d��s�H�/�x�c��.��?��p��a��b�k�n�m��<�?��E��N��)��^��G���N��W��T���+�P�i�B��J���=�^�'���*��[�n��x�J��C���?��B���?�W�����l��5�/����S��\�� �,�q������z��z��S��N��=�\��=��0���$��� ����?�e�%���#�`�r��9�=��*�s��6��I��?��N��!�`���X�X��A��z���=�.�?��X�%�5�?�L�F���7�:����/�n�C��j����r��m��~���m��`p��3�v�u�C��"�*�����|���Y�:�:�a���
�h�9��J��U�Z����*�T�H��
�
�V�
�I�O��D�3�"�<���i�m�X��K�0���Xy��
��Q�y�~��Z��i�,�\�G�x��>����H�^��~K��E��D�?�#�S�f���"�Q�:�t����
��}��B��]�$�i�9��p�"�:���~��W��:���#�B�T�M����7�7���\�����)���S�V�a�
�~�%�
�A�"�!�)�a��N��k����Q�p����W�#�L�,���7�_�~���$����:�w��O����q��_�c���	��I�:�C��4��/���z���&����%�E���y�p�W�
��T�)�*�(�*��E� �� �U�}�0��k�Z����Z�+�.���"�;�s�D�j�e�)��j��;��T��&����|�W��5�8�v��a�%����W�@��U�N��#�r�@��)�x��@�{��h���1�#��^�K��$�L�$���u�k�&��Z��^��O��6�f�������>�J���L���f�m�@����f���}��n�,�F����>�'�#��r�m�v���u�r�B�1�����9�(���C�(�v�k�S�,�K�%�Z�u��*��#�@�^�V��f��5��U�=�z����7����\�[�+��k���P��O�
�#�G�x��&����N�+��5�^�y�P����$�/�����F�:���l�q�^��7��t����M�����E�4�P�y�X��N��A�_���q��Y��V�!�;�t��I�Q�5�b��T��s��_��t��I��o��O�#���-��&�D�Q�$�Y�o��d�_���g�M�E�����R�%����u��L��u�F�h��8��q�'� �g�	�e��h��=��0���s�V��x��y��4��^���H���o�,�q����c�vl�L��#��0�w�8��S��n��W�kq�E��F��M�
�y�_�
�8�r�,��~�-�
��5�,�Q�)��b����a�`����u��s�8��^�C�q����*�g��	�X��f�M����D��W��N�i�$��}�&�=��q���_��T�v���v��<�p��M�
�`�2�3��k��j�U��k��K���j��O��f���N�"�"�4���u�`��R��[�+��"�I���
���U��*��i�o��6�[�����U�M�,��W��\�z��S����6�����w�H�$� �s�<���H����L���H��y��J�b���+������y�{��b�P���N���o��B��/�v����!��5�>�_�A��z�)�"���]��&�\�"��!�E��N��,��1�/��*��?�y�6�x�.��_�*�e�p�}�4��4��)�'��0�X��3��>�9��#��
�?��M�i�h�N�v��O�.�3����}��(�S���z��1�u�r���b�i�3����Z���j��{���Q�k�6s����ra�}�Lj���,��v���s�9��,���M��P��k�6��t��A����{��v��9��&� ��0��g�>��Y�5�/��H��U���
���>�w�!��(��#��U�F�0�)�s�q��@�W�K��s���x�"����|�� ���}N�E��n�1��S�f��
��=�-��f�y�.��	��V��[���>�z��g�&��`��{�P��l��5�`�r�e��J�a�i�U��V�j����
���\�l�L�R�|��,�+�C���\�3��
��S�M�C�>��:��E�(��@��Q�+�Z�p�_�����%�/��r��e�j�Fp�B��e�`�9�E��<�[����*��O��N��O���9�P����>��A�q��4�m�t�i�h�����a�4�o�@�^��67�i�8����A��v�1�u�n��8��/��j�=��\�z��u�!�F��|��]�a�^�^��B��%�7�^�m��r�r�,�!�G�D�;�}��T���a�d�
�|���Z�$�5�.�C�5� �M��Z��U�1��D�a�Q�n��}�B�=��a��'�V�6�%����*����P���N���"�_�z��p����G����7����-�M��z�P�)�"�����f�]�~�7�D�&�D�R��E�\�F� �x��1��.�Z�{��+�:��\��O��>��f�I�1�>��!�I��]�}��L�g�x�>��m��v��1�O��_�W�q�v��4��6��)�z��C��8��O�n��%��G��\`���t�m�G�,��K��.�K��|�Q�_�w�b���7H����o�j�&�2�O��9��}���<�1�D��N��	�m�`�U�p�;�h�/�=�$�U�H��q�m��6�M���,�Z��O���0�h��:��Q�,�[�U���*��y��R�)�;�K�����.�a��Q��,�Q�K��q��>�
����a�
�p�*�<�����v�	�e�#�\��6�����Q����/��P�U���2�;�m��V��]��(��'��H��r����C�z�"�&��C�x��\�j�o�l�s����(���R��A��T����-��	�}�f�,�5��f�a�\�u�a��u��&��
����"��w�*��J���B�c���d��	�T��0��C�P��`��iR��h��[�_�D��
��l����4���:��m�C

<
���RL"��$��e�#��"��3
JxQ\��_^=Bd� �=Kp8
#\]^1�"K(0`D�3p;<_:!0�*w"
:R 6^3
0�]�>�K�P��"��5�Z��
����c��/�$�7&��r��o�K�Z�	�k�j�G��tj��_��<�z�%������
c��t���&

�<RSi ��D$

&0�3	$$<"$	

:	��s�h�A��R���_
2�\��<�3���d��s��0�W�G�8�8z�Q
�H�W\CL654'r1J8QCh5v)Z!!$:TI�( 5��N�`��5��r:I
"
.0�<����1'HUxN%'TW.E
f#Qz)����h����-�R{_>	6?S�gd���4��q�"8��//rYt{8
"~9��#�� ]>O�
6Oe^�\��K�Jm�t2��i�z#yD*�;v�&*S*J.;�P��T��5467y�8#>	
6�1�@\PC-h%/fD%4DNEF'J��}���j��E��$�.:h6,fY�"�R��&J�|EC�j
1<w2!���	m)2xJ/P9��d�@
L6�G����e�")$,!��TV'Ejm�";
�T�!ldG(:W�"Tr`b5�
-T�pxipG,C�9U*xNJ�YFY&- )<�LU3V\��Zc:C�.�-�F�
^U�� ]�*�"3T�qU�#��^��m-DKL
T?:W@(!bE
��V�6H
&

$�d>��^7�+%$
��\�0'4#
:"32�,�]'
1(x5\#&%A<,��*��y�Z��J�����r�*
�s	/&,)<�8��m����E��@�$�t
�1^� 
0	��>������mf+*G2�N_�@66Ch=�+^`{qN$0$!5
� cV%&M�V
#_8<Tl1$8@�1��*�
������N,��$�U�><%�0=��ltDLO�#�l�5KZF{B6t%7l�(8B?7hC�:E\���m\63�l�'0^�@`W	x�W�@�=�x�A8��'L��n2<�4]3���{*	>G4�dG��Do$9j(P�C>�>)
�,E"<!T:	2�= "$�l��\��U�!������,��\884+	�Q�-(�"V�P.!��E��t
��w�;�!�H�Q�b��*��%����q��|���[�zY�d?
ZAN3�4�Y�TK@$PG"!XL;3J$��l��I�Xs>9N7X6�(�E��$�"��z

�B/ �15��w��I�D�,�8^5	8��D��eCIn84`k#L4: :�Et�$,T1�%QgL-@v=;HedE)�f0cbCs�6gIHORD{RV�v�zhN&=><�Mn KJOA�M76Q�,�V�e��h�Ti4�*�iX�@qL=4L3(.A�	�.��i΁G*B&!LG(���'�b^���je�g�"
�B�!M�0+y+^F(0%&�4��k�&��
��������t�q�?�
f
�0�_"+���5,� �O'>4@�*f���
�%�3`��Ir��R�z��0�V�5�al!
�{>*�BM�b����P�e�d
�~��3(��
�����O�x��I1)2*@���q#���m
��5�N��$
�y�B�M�F
7�$$���Ց9�k��g��`��G����}P�R6
:
l=�
0�/GXQZ:4~$"F�y�V��$�w
��y;
�(�yS$�E�V@M.��4��Z
h�r�Y�D�
�r�(x�M�8/
H<�:x&R:`'()\!D
$#< 
*��L�E�Xv�$)@&�gwG	,
2!�uw��
$,�(�P�A�c.��(���*

(
*
&
"��:�'�8�i.4��v6H2��z�U�k�Q

�
���f�
<�2
�
]�q	
��C�1iu�'$&*	6% !4@5>'2 
(�4�I�5�z�
 
	
�`>F0)(P=)*`
#RH(�a�2�g�N�j
�Ԁ)MR,�"�h
�昏��M. X7@	/*0=2
7h���
G�k*+b	2.;)F)��A�/1�{7IB8q01Z6R}N-)Nrs.-&# N+ 
.�
�b��-�h{@9n#(#Ar+^S 3<��{�GH
8! ��{�B$&	"�z�	��1�.�/���>
�*��|��	���m�g���|�
�"�|�" �
�
�,	�w�|\�0�&��8	� >�
�M�
���d
$0���
�K�P�
d2�&�i�#��+J�,��*/H,
	.�+D�JJ

 	�M$	��s��t	
��N�X�U	,6��u����)�|���'�@�%>2�jV�>w�\X�&��z#��N8!f�M�̗�4	�x�@�E�ߚсf�SH@L�	��~;���y��
��d��c�D��{��S��n�x��:�J�0�'��p�y��
��r�<�A�}��&�3��[���{�8��t�Y�A�7�*��g�J�����>�Q��R���I�`�P�[�I�?�A��J��E��Y�H��+��^��U�I�B��I��*��A��x#�^�K���=��d�=�}��*�G��!�t�I�Y�n�����%�=��`�b����y��z���o�;��>��1��0��1���b���#�	��z�	�}���Z�@���<��)�<�W��{�"��}�*�r��l���9��
�G��E��Z�h��C��D�	�P�^��z�Y��%�y���C�T�7��=����I�M�����2��G��t���Z�W��8��+�{�C�t�G�r�<��e�,��"�}�t���)��`����b�I8�v�R�E�=��#�9��%��.���
�.�X�>��I�L�V��{�+��4�
��G�y��\�!�e��u��b����V���3�o�f�h�s�x���4�H�
���r�E��/��0�A��Q�t�j�r�b��{��|�*��;��D���v�3�@��!��P�]��{��l�;��;��h�,�`�1��%��6�u��u�
��@���5��(��w���9�Q��a��4��Z��[�V��c�%��`��j��s��Z�9�N��������V�/��F�i�9�?�U��`��U��4�J�����<�5��c�x��$�i�<�6��m����x����
�	��Q��Z��u�3���&�7�@��9��\�3�E�?�`�����}��s�f�j�7��(�Z���h�
�C�Q��r�+�u�E����s��P��?�y����F��Q�n����6��G�`�=��
��E�3�(���2��y�|�B��1��8�W��,��Y�3�R���3�� ��M��G�!�n�F�a���\� ��5�����:�������:�!�+�^����Y�v� �/�6��C��f�p�m�H��A��(�"���H�/��!��8�����_��:��y�k���1��P�[�N�#�b�i��5��(���l��i��V����(��{��F�r�
���9�\� ��7�y��*�W��b��	��Z�O��a�\��?�_�,��S�� ���J��a�G�U�P���`��7��
�e�j���r�r��7��P��K�{��j�i�J��4��e��P�|�h���=�$�4�y��|����T���}��b��!�r�&��7�d��L�)�:�[�&����:�J��k�����-��n���^��H��C�%�:�Z�p�?�����F��d�H�$�[�^�
�R�C��?��(���$�� �I�H��E���J���+�&��:�/�u��X����!��I�X�"�B�h��'��P�Z��:�G��L��y�|�R��7��z���K��B��#��V�����)X��J�5��z��9�YY�G�V�y�*��!�$��k��j�+��M�x�G��r�H�_��;��:�	��w�n���N��x��\�y�\��,�]����#�e�
���V�a��Y��n�'�+��)�9��u���	��
��K��n��+�D��F��x�y�
��k��d�3�x�@�I�u�6����;�r��w�&��h��0��=��j��C�?�7��N�i��X����d��U�[���q��+�&�F�r��Q��~��X�}��U�%��r��q�0�|��)�>�[��4��	�7�r�l��B��W�X�.�R�-��K�"����i�x�
�8�]�{��,�2�!�q��=��L��L�0��U�h�?��\�u���c��*�9�~�����p��W�2�a�&��O��$��_��b���s���B�d�Q���S�t�5��2����:��U��L�5�`�e�'��f�S�*�5��9�H���'���@�[���W���X�_� ]�3�;��B�@��d��9��|��_�0��
�s�3�#��$�'���P�R�k��L�B�Y�l� ���o��<�p������� �)�v��/����H�.�IV��w��~��9�(��Z�K�u�0�m���H��x�p��;�(�� �$�O�{�'�E��5�\��r�%�������'�&�������������	��A�|��?�z�j�:���;��0�e��r�X�1��r�h�:���Y��N�z��_�p��S�-�
�1��W��h��]�5�7�R��B��y�:�&�N�	�s�`�a�|�/�?�r�
��5��~��j��U�;����/��P�C�"����j<�����|�6�����>�3�	������8���9�a�d��^��7��N��O�i��b�y�W����)�6�B�r�|�U��u��t�=��3�b�d�r��i�d�m�f��a�b���m�����a��U�p���d����9��)������.��d���B�L�0���3�
����B�$��[�p�$�8�Z��M����g�c�b���x�n�)��D��~�9�|��/�B����&�'��O���<"�{�>��Q�`�F��K��8�Jz���~��t��-�$�*�e��"��u�D�o��
�x���p�g�>�+��X�C�w�|��Q���f�h��G�H��I�k�D�����A�U��'���w��P�-�)�x�4�|�d�}�;���O�g�1�X����:�(�w����"�\���s��$���]� �����S��.��]�T�
�7�^�@�@�o�/�p��m�]4��f�;��=����u�1��i�*�|'"N�M�pWR�c��T��.����L�c�7�@	�:�KS6
f�<(�p��$"���"	+~P';!��f�
4P�gT��=M>�+�,a�����|�M�
�s�Zsvk���Ns�D��>�N��t�y��7�,�j�7� rb�I��d��y�%�]"^�?�R��2�9��z6�	�B�
�P�Y�LI5^��c��f�P��,� "?<�W�E�E��GW�^2O�"�X�R�g�J�%��b��WC�b�vP)�!�'�6I�t��To�$��,mP_^(	��F�$��$�#���(6�j#��L*����~��O��	Y��|�.�[��[v���/�j��^�u
�0q��"�d�U	��7�U�9�k�H��@�/�l�s���M�T�6G��P$h���I�e�t��Q��}=P�'�4��K�&*#��t
�8H��h�Q��7x+/T'�$�O*�W	�}$ ~��]L�<�):H$j	 !M-,"B�^�y2UN&G�V�		&�]6P�k�1���t���}�8Z
��|��e�~�%z�\�M�v.�-�u�p�A�,�t�B�j��m�NU%:�f�	^�

��H,�P�W#�z�Q&�-$��,$��$�H�w���T#��%$�*	&�G&:3�q>�V�c�\���D�;�Z�a�%$�
�
���u�,5�R4B�bE6��w�|F�B�
����b��0-	��b��{u�:��$���B!�B�#:<iJG@KrV_	B1J�.%�?�@�a0?�|�M^�?�f�A�,���#jG��E)�R��~�_Q��G�\�5��J�eY@0�%C��6V�D�H&0(�%Q�
�=\E�
u&�>��C�i>3�,�U�E%j�� 7�M�Z�%�2��0�>`1(�K�;�� 8�F��q�
�m�R�S"�6���{�@�/bS2J70
�:�(
&%@��o�K�%�vIW8�%�vi�(#�R[R-V�Y�j�	�"�s%Ș!��7��H ,>#�D��8[�y�h�W����-$��Q�{��mk� }���<�K�g3�0��d�o�$�l�c{�
1K�}�	�e�F1�
	
�u�w�^�-��R��Y��
��8'���t�/�p�-&�ojO�l&=��+0A��+@"
 (�I
�b�Y��o�
�(�
�2�!��a�������U��6��)��X�O��d��P�3��7�"�l����\��s�2�O���z��L����Y��T��3�{�P�/���W�.�F�E��b���G��H���<�=��]��P������V�W���>�/���u��p&���r��\�X8�DL�i�x� �zD��K�b��x��C�V�m"n�4(
&
@
,V
&		,v2F
B	:  	
$,F

F	"&	(



 40" 	

@$$*
 0	&	2!l>

$$ \
6,>	*

B<*
"
%^=�<>	0<&	.	p
(	(L0
%f0(<
 2
(

((
!h&	"



,*:	,J
8
(B
":F
4,J&"
 :(
,,04
*(

	
 
�	3�d�
��b3�d�


	 
*(6


&>	 @>B",

 

 


0	 X
.	2	

* #X@F&>>R
$\"'b)f0.
282(
4
$

 
&	&
L
b
$

6"X$
@
 

D0�
$<:


*8 6'f
2<	 6
<
 4F>
2
D
"
0
 ..	 6D	 *B
)j0


J0
04
" 

0@
(
B&rJ6
	(.<$$j&b.
2'j0	$$
 V,44
(	\>	
">PPD0D
.	 *	"
,B	,08,
�	C�~� 
0��:C�C	$

6(4$
2,
*"(	
"
	4 

$
.h
&
F3� 4

 &>	(	 6@"d
 >	6
,2"
($P,
	(B
42	 ,L&<>"Z
((&$
 
"
 ",&
"


$^*p�
K*��2<(�8Kr��&"
F	"l
3�

>	$T

$jR^�	M�^*
��M��
,84


(
0"
2
$ n
	"
,(LD

.2
<.& (
($0D"
 0\�O��(@

��[P��	&$:	"
*
",

.&"6
*,0,
$.,:0$ "bF@>#V
 

$(		**#P
86	
(
$$04v$&5�8
 N 5�
D:& 	,
,!���H��P�V �@܇��V#.��\Y���P��-�v��,�-��8��M�&�G�V7;N�>�b�s�V;�E�ׁV<�����V��c��	�VE�Jݏ�VFC��c�B�p�+��X��-
�$ ��xd�VZI�	6���V_�P��	V`L�YT���_���'��p�y�;W0A�X�]dUbg�}�D�I�@�5�<�A�p�c�2�7�8�A%�L�Z�w�`�N�����������4�'�(
	
4?�
	�Wm0�2=�"�W�.�c�d�d� ���1��������4��m^ 0
  0000"'"n"d 2��!�$�$t	$`	2 !`!f�X�R0AH0�0�������5�9�?�=�A0�;�7�36 $1%-%]!�C���2dx���s�$�0��OR?���������������������*�.�2�5<R�W.H����[�m��"�����d�TRT�T�T�T�
U"UuU�U�U�U�V{^;\�],r�r��g����``x+����
��lhl�#m�nSoFD�5�Q"Y�Z4�w����������~�~�~�~�~�~�~�!*/1t�t���%h2h`ii�jk�k�k��q�u�y����e�f��2�E�H����r~l���
�Y�|����9bHy[(xxx9xV��wwPw�������������������������������������	�
���*�1�6�>�D�N�S�V�^�a�d�q�(�9�A�F�K�Z�f
u�1zx5�%�������������}/D{{�{���!�3�	|�|�"�N�}�����������A�Q+������������������������������������������
�=�c���$���Hj�C}}�-��%���l.�&���EvFvm�s��l	!p�!�"f#%P%�%�%�%�00!3�3�3�0�0�0�	�I�T
�Y�h/�NNN.N@NZNbNgNj	NtNN�N�N�N�N�N�N�N�N�N�N�OOOOO(O,O>ODOGOaOjOmOqOwO�O�O�O�O�O�O�O�O�O�O�O�O�O�O�PPPPP"
P/P?PDPJPPPV�	({P]PfPmPxP|P�P�P�P���D(�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�P�QQQQ"Q%Q+Q3Q8Q=QNQWQ]QcQfQ~Q�Q�Q�Q�Q�Q�Q�Q�Q�Q�Q�Q�Q�Q�Q�RRRRRR!R%R1R4RERHRNRRRWR_RbRkRpRvR�R�R�R�R�	R�R�R�R�R�
R�R�R�R�SS	SSSS$S'S+S2S<SKSXSlS{S�S�S�S�S�S�S�S�S�S�S�S�S�S�TT$T6TATDTLT]TiTyT~T�T�T�T�T�T�T�T�T�T�UU
UUUU%U(U4U8UGUKUQUWU_UbUhUoUyU�U�U�U�U�U�U�U�U�U�U�U�U�U�U�VV
VVV V%V*V<	VBVOVUVZV^VmV}V�V�V�V�V�V�V�V�V�V�V�V�V�V�V�V�V�V�V�V�V�WWWWWW W%W1W4W<WCWHWRWXWbWpWtWxW}W�W�W�W�W�W�W�W�W�W�W�W�W�W�W�W�W�W�XXXXXXX"X%X+X2
X6XEXUXYX_XfXmXvXzX�X�X�X�X�X�X�X�X�X�X�X�X�X�X�X�X�X�YYYYYY Y2Y5Y?YEYLYRY[YcYfYoYzY~Y�Y�Y�Y�Y�Y�Y�Y�Y�Y�Y�Y�Y�Y�Y�Y�Y�Y�Y�Z
Z
ZZZZ!Z&Z*Z7Z=ZBZG	ZKZVZ[Zc�
.ZhZkZnZxZ{Z�Z�
Z�Z�Z��+.?Z�Z�Z�Z�Z�Z�Z�Z�Z�Z�Z�Z�Z�Z�[[
[["[+[5[9
[A[M[`[g[m[v[{[�[�[�[�[�[�[�[�[�[�[�[�\\\�
/
\(\-\2\5\C\F\L\R\V\Z��|/+\i\r\{\�\�\�\�\�\�\�\�\�\�\�\�\�\�\�\�\�\�\�]]]]]]]*]/]5]?]H]M]Q]Y
]^]m]p]u]�]�]�]�]�]�]�]�]�]�]�]�]�]�]�]�^	^
^^^(^/^4^9^>^F^M^Y^\^d^m^�^�^�^�^�^�^�	^�^�^�^�^�^�^�^�^�_____!_+_6_=_A_I_Z_^_g_n_t_}_�_�_�_�_�_�_�_�_�_�_�_�_�_�_�_�_�_�_�_�``````"`,`0`6`=`D`N`S`V`[`^`e`q`t`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�`�aa
aaaa!a(a,a5a8a<a@aOaRaVa[a`aealaq	axa�a�a�a�a�a�a�a�a�a�a�a�a�a�a�a�a�bbb(b5b8bDbObUbYb\bdbqbtbwbzb�b�b�b�b�b�b�b�b�b�b�b�b�b�cc
cccc&c,c0c3c;c?cGcQcVcdcocscxc|c�c�c�c�c�c�c�c�c�c�c�c�c�c�c�dddddd"d'd.d7d;dBdKdUdYd`djdpdtd|d�d�d�d�d�	d�d�d�d�d�d�d�eee
eee&e0e<e@eFeJeMeRe_edegemeye�e�e�e�e�e�e�e�e�e�e�e�e�e�e�e�e�ffffff!f)f2f7f?fDfMfPfXf[fbfifqfxf{ff�f�f�f�f�f�f�f�f�f�f�f�f�f�f�f�f�f�f�f�f�f�gggggg g2g6g;g>gDgJgTgWgbgfgkgxg�g�g�g�g�g�g�	g�g�g�g�g�g�g�hhhhh"h+h4h:	hVhlhxh�h�h�h�h�h�h�h�	h�h�h�h�h�h�h�h�h�h�h�iiii!i%i.i1i5i:i@iCiKiUiXi[iaidiiioirizi}i�i�i�i�	i�i�i�i�i�i�i�i�i�i�i�i�i�i�jjjjjj$j+j2j6j;j?jEjIjLjQj\jc
jfjrjzj}j�j�j�j�j�j�j�j�j�j�j�j�j�j�j�j�	7�j�j�j�j�kkkkk�M7�k%	k(k3k?kDkJkMkQkZkhkkksk}k�k�k�k�k�k�k�k�k�k�k�k�k�k�k�k�k�k�k�ll	ll6l9l>lClKlQlXlblelklwlzll�l�l�l�l�l�l�l�l�l�l�l�l�l�l�l�mmmmmmm m(m,m/m6m?mU�	9mamdmgmkmpmumzm}m��98m�m�m��	9Fm�m�m�m�m�m�m�m�mҡE9cm�m�m�m�m�nnnnnn'n0n5n;n?nEnOnYn\
n`nl
npn�n�n�n�n�n�n�n�n�n�n�n�n�n�n�n�n�n�n�ooo
oooo%o4o9oBoHoNoRoUoZo_ogouo}o�o�o�o�o�o�o�o�o��
:o�o�o�o�o�o�o�o�o�o�o�o�o�7:�o�ppppp!p$p)p3p6p:p?pEpMpRpVpYp_pephpqpyp�p�p�p�p�p�p�p�p�p�p�p�p�p�p�p�p�
p�qqqq"q'q2
q7qFqOqSq_qjqoqtq{q~q�q�q�q�q�q�q�q�q�q�q�q�q�q�q�q�q�q�q�q�q�q�q�rr	r	rr-r3r@rIrNrSrcrjrprsrvr{r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�r�ssssss#s&s/s2s<sBsFsNsSsXsasss�s�s�s�s�s�s�s�s�s�s�s�s�s�s�s�s�s�s�s�
s�s�ttttt#t1t7t=tBtLt`tdtktntqtxt�t�
t�t�t�t�t�t�t�t�t�t�t�t�t�uuuuu u&u<uAuFuIuPuUu^ucuguluzu�u�u�u�u�u�u�u�u�u�u�u�u�u�u�u�u�u�vvvvv.v1v6v9vDvJvNvYv`vsvyvv�v�v�v�v�v�v�v�v�v�v�v�v�v�v�v�v�v�v�ww
www#w*w0w=wDwHwRw\w_wiwmwzw�w�w�w�w�w�w�w�w�w�w�w�w�w�w�w�w�xx
xx x*x.x1x5xAxHxSxXx^xexpxxx}x�x�x�x�x�x�x�x��@x�x�x�x�x�x�x�x�x�x�x�
@Dx�x�x�x�yyyyyy��V@ky'y-y2y5yByJyTyXycyiypy{y�y�y�	y�
y�y�y�y�y�y�y�y�y�y�y�y�y�y�zzzzzzzz!	z$z/z4zAzGzOzRzXzczlzqz{z�z�z�z�z�z�z�z�z�z�	z�z�z�z�z�z�z�z�z�{{{{{{!{/{4{?{M{^{c{h{o{s{|{�{�{�{�{�{�{�{�{�{�{�{�{�{�{�{�{�{�{�{�{�{�|||||$|(|,|9|D
|N|]|a|e|o|u
|~|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�|�}}}}#}(},}4}=}G}J}Q}_}c}i}o}t}z}�}�}�}�}�}�}�}�}�}�}�}�}�}�}�}�}�}�}�~~~ ~$~(~/~?~B	~H~V~[~_~c~q~t~z~~�~�~�~�~�	;FR[ckuz�����������������������#�/�9�@�D�G�N�[	�_�k�z����������������������������-�3�:�?�W�[�b�r�u�����������������������������������$�<�?�E�L�P�[�`�i�l�u�{�������������������������������
��	��)�=�A�D�K�U�p�y�~����������������������������������������Fe����)�-�2�6�9�>�G�J�S�7F��]�g�{������������������������������
���	��
����$�'�.�>�D�K�O�Z�_�e�k�n�u�|�������������������������������
G�������������#�"G��*	�.�=�@�R�V�`�c�h�o�r�������������������������������������������	HT�&�*�/�2�5�8�<�@�J��#Hp�O�Z�a�g�q�y����������������������������������������������������$�,�3�7�=�A�G�N�U�^�f�s�z����������������������������������������������������"�&�,�9�<�B�E
�H�W�a�g�m�p�s�|�������������������������������������������� �&�+�.�2�7�;�?�B�I�V�Y�\�_�d�g�v�z�}�������������������������������������������"�)�.�4�:	�?�J�P�J��]�c�g�m�q�u�x�~�������K%���
K4�8�B�J�M�Q�V�[�c�l�t�{�����Ks��������������������������Q�h�
K��n�q�x���������������������K���������������������
��� �$�2�6�;�>�E�M�S�V�Z�d�g�j�w�}�����	L|�����������������ϡL�����������������	L��� �'�+�0�4�9�<�@�AL��G�J�O�U	����������������������������$�'�0�3�9�?�E�H�Y�f�j�o��������������������������������������
����$�(�7�;�D�S�X�[�_�f�z���	M���������������������\N������
�������
����(�1�5�;�A�F�J�N�R�X�\�_�c�g�n�u�|������������������������	��������������	����#�)�,�0�4�9�D�H�N�U�Y�\�f�m�q�w�{�����������������������������������������!�)�/�6�;�@�E�M
�U�a�f�n�s�z�����t�x���������������������������������#�+�/�7�N�Q�V�Y�e�m�y�~��	����������������������������
�����(�+�.�3�:�?�E�I�T�W�\�c�f�j�w�}��������������������������������������"�'�.�1�9�>�G�O�U�\�_�c�h�m��������������������������Q�������"�)�+�/�4�8�^R�@�F�M�X�_�x��
����������
����	������������1�9�F�K�P�X�\�`�f������������������������������	���� �*�3�=�J�R�U�[�p�u�x	������
����������������������
���!�&�*�.�<�?�F�J�Y�`�h	�n�}�������	T0������������������JTK��
�
�� �$�)	�1�<�@
�C�T�^�b�m�p�s����	������������
����	�����������
��;�R�_�n�v������������������������������������������������#�-�0�?�E�L�U�Z�]�d�g�	U��m�s�x�������������V�
���#�'��.�I�I�L�M^ -!/O;0�03081*1��
W�.�.�.�	.�.�.�.�.�.�.�.�
.�'.˱3XF4*4H�4t�5b5�6
6�h6�7�89T9_9o
9�(9�i:	�:tC;
�;;Ob<p<o+<�=
�>
�?
H@
�6@WPA�.A`�B'CsC8C�*C�4CށBD<DׁE7FFM�3FbGG$QG*G}�G��H,I1IHI{I~�mfI�I�I�lI��J%�K%QL%&LxnL�M{M2M��q�w���x��x��x��x�x6��PK
!<�
&��3chrome/pdfjs/content/web/cmaps/Adobe-Japan1-0.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�q!���������������������������������������������������������������[�PK
!<��V��3chrome/pdfjs/content/web/cmaps/Adobe-Japan1-1.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�q!����������������������������������������������������������������&�PK
!<��}��3chrome/pdfjs/content/web/cmaps/Adobe-Japan1-2.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�q#��������������������������������������������������������������������PK
!<�!�}��3chrome/pdfjs/content/web/cmaps/Adobe-Japan1-3.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�q%������������������������������������������������������������������������	�PK
!<�h/�QQ3chrome/pdfjs/content/web/cmaps/Adobe-Japan1-4.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�q=�����������������������������������������������������������������������������������������������������������������������S�PK
!<DZ���3chrome/pdfjs/content/web/cmaps/Adobe-Japan1-5.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��qP����������������������������������������������������������������������������������������������������������������������������������������������������������������������������\��PK
!<�Ɣ���3chrome/pdfjs/content/web/cmaps/Adobe-Japan1-6.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��q[���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������PK
!<qW�����6chrome/pdfjs/content/web/cmaps/Adobe-Japan1-UCS2.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�����<�NS�
�*�{�v�9�
�e�W�n�c�L�
u��V�E�T�c�����
�%�$89R�a9�,�S�r�1V� ��4�i�e�v�
��>�L.��I��I�<�h!�0@��}��0�
�����`N��"���7� ��e��b�#rIC�0M�(0r�=0��"�	W�h!A>�b��3:0
5

9����#�G��
|���X��9=�d�i�N�U��~�����2�
�0��O��XB��n��u�����;:�	���FJ>�5����	��"*���!��$��=�C��*���<uK$��,���!��~�<�����JV8��X�!�2�
�"DA.��w�|��i��p���] ���TeN��r�X��v���4��e��V�[�3��M�l��q�p����E�Y�r�a�-���T�p�%�6���q��.��]�V�&��`��|�
��}�UH���V�P��8X��*^�@�D�b�h�(@��b��&��(�<�$H���M�r��$��%��_�`�`��*�
�j�;�F��]�\�F�|�4�2��8���p��h^�4��a1�&��Z��J��s��X��E��S���P�-��)�0�x�@��-�y�x�j��d���c��r�L�G�x��W�(�r��\�$� �f�tj�b�X�
�J��R����w�p�<��t��?��4��N��I�C�O��:�&�^��b����(�r�
��v�R��h�4�
��t�~��e�$�:���Y�hl�4�6�>�z�^�x��,�j�(�*=�j��G�h�H��o��T���!�T���@�z�`�~�J�F��Q��Q�L1�F]�X��2�
�b�hT�^��(2�*�L�n�,�`�z��RRP�d�n9�$�����|�z�N��)��
�F�P��B�R�.�������������V�~�`�^|�Re�Z��z�P����7���p�6�r�\�^�&�"��c��Y��.�G�~�D�"�2�X�:�z��4�h� ����v���m�6��~�������.�	��l��`�\n�0�.�R�j�`�(������\�c���k�&���,��]�[�T�$�v�����h�f��a��H���H�<����5���L�\�R�^��c����
�D�V���;��x�?��Q��8�b���&�C��v�J��,�R�v��4�8��\��L�(���h�%�T�N�N�f�B�~���r�X��n�(�0�<�0�� ��Z�(�!��A�d��&�8�D�^�'���>�j����/�D�T��"���t��v�b��B��G�x�V�4�_�(�"�J���K�8�~���"��6��(� ��V�Z��y�x��$��$� �V��J�`�j�B�"��f��	��:�*��/
�N�P���.��e��.��3�Z��n�j�j��]X���p�Z�j�H�"�V�V�^�`�(��dn�2�"9�N��f�A��D�Rp��r�,�V�4��o�H��f��e��O�O��`�j��\����:g�:�V��,�:�*$�H@8�b�r�b�j�T����
�P�R�:�.���U�X��&�V�[�b��&�s�`��y�<�D�l��N���|2�l�v��|�\�$�d�>�2�.�~��e��@��G�0�\�W�8�|�$�x��,��7�D����]����	�8��f�z��'�P����-�����@�'�o�A��z�
�W�0�?��4��u�P���N�_�P�&��w��B����v�$���2�x�?�
� B�`�����b��$� �$�,�z� ��b�>�Dv��t�4��,�8�h�/�*�2��C�.�F�x��0��1�a��2�2�>�6�^�D��7�%�'|�,�vT��N�j��Z�n�
�t�|�R��&\��J�4��[�8�B���^� �"��4�z�H��k�6&��T�h�h�f�r��f��r�*���x�0�.�z�,�$��r�v� ��R�L�r�r�R�'~��:�p�@���>�V���t�t��`�8(�z��L��L�8
�$�0��e��T��K�.d�R�dD�d2��6�Z�t�� �4�,b�*�,8�@�r�r����&���`�p�f�j�f�&�|�
�Z�(�(�H�$�@H$�
�$�F%�V�Dt�4�N�r(�r��2��T�v�e�x�N�P�n�&�|�D��A�� ��B�P��h�2��]�^��d���l�P�b���w�L�?�)�8�����@��1��<��s�J�f�2�6��<��d�n�
�:�H�f�&��O�h�p�x�P�l��:�2���
�o���,��m�@�d�o�(�8�h�p�F�&�6�j�(�h����( ��?�B�V�H��"��o��N�2�l��[���N��a��R���6�%��0��H��=�Z��d�R�V�@��"����'�� ��i��A��X�N�,�nn���\�N����
����r�H��Y�[���R�\�l�R� �v��F�0�f�T�$�Z�.�8�.��I�|�>�m�J��t�z
�`�n��>j� �x�hP�V�
�R�N�'�$T�h�0�&�j��n�l@"�l�>�`�D�J�B�0(�^�^���I�`� ���D��p��<�R�!��|�,�X�p�"L�l�R�����\��a���$��c�z��W��:��Q�Z�
�r�.�4�H�j��n�V����@�A��]��P��y��P�Y�^��k�T�F��z���"��0�Z�.�X�F�O�D���4�~�0�P�p�,�v�D�
��G�\��}�4�b��J�0�<�l��x�n�\���	��$�(��`�d�&��M��2�\�2�X�a�$�x�f��l���4b�`�j�V�:�F�t��2@�2�U��9��j�v�"�t�
�X��.��L�j��_��J�"�� �'��;�@�K��0�8��M�`�R��l�p�D��s�F���2�R�D��2�*�g��&�"�|��Al�D� ���I�6�>�u�l�����^�2�6�����~��[�N�z�L�H�v�:+�J� �*�z�p� �R
�*���,�`+�@���F�x*�����F-��>�r�6�F�F��^`�*�"�8�~�h�z�&�*�V8.<�Z�x�
��.�2��v��*p�~�0�:�J�.�*�*��p�D�Z�b�x�^�(�r�
�N��_�����#�>��,�L�D�t�f�p��6��m��PY�"�0�x�2��@��4�D�X�8�p��
�J�|���^��t�~��`�^�,�T�R����b�l�<��;�� �v�t�:���R�p�M��_���\��=��D���*�,�R�(��"�"�v�l��c��J��{�B��b��
�d��[�4��V�"�/��u���E��*�`�]�W�b�V�`�Z�J�P�\h�T�(�z�9��B�X�X�v�D�.��M����@n�"�h�~��!��n�h���\�z��\�v�D�2�~�`��B�n�x�z�m��E�"��8�U��(�U�x�V�z�i�*�x��a�\�t8��<&��@�P�D@�(�v]�	��b\�e�f�Z�X�^�:�"�@:�X��>�L��j9�b�T��C�l�f�z���8��:�K����,��&�8�|�j�~���n�F��X��5�|��s��,*�3�@��@�Z�e�0��
�PL�	�v�"�6��-�R^�.���9��>��4��^g��N�&��|��T� �V�R��j����l���B�$�.��;�`�$�V�v�2�*���V�����V��w�������A��W$�<������
��
�@��'�,���D�Z�f�A��T��F��+�d�n��"��?�z�*�4�T�P��`�Z�^�`�(�l�X�Z�6� ���q��l���@�|�X��9��)�`��$�
h���d�h�@��o�x�
��#�� ���G���I��
����i�X�(��K��8�������d��o�<�Z�>�<�8��N�$�r�"�~��t� �����
��q�F�l�\�P�R�h����G����k�T��(�J�&�J�&�
�F�X�Z�U�[�8�.�d��O�P�F��+�<��'��L��8�|�h�l����$�H�8��s���x�^�.��]���~�R�@8��$@j��z�<� �t� �F�N�~�r�|�z�8�P�Z�L�8��D��A�"�8�?�d��j�T�����b�z��`�8��|���N���q�P�)��D�+�e�>��+�`�
��h��~��A��0� ���@�0���4��-�6�j��n.�N�X�P��*�<� ���x�(�L�t�F�:��H�h�T�j��S���$�|��,��N��/���T��P�>�,���d�z�V��F�&�g�>�J�p��E��9�B�b�|����[��HB�(�f�b����2�F��L�P��Q(��!�v��(�:���"@d���|�z�@�(�`�0�.��"�pD�n��,�)�&�0�k��<�"��4����d�
�t�R�j���f��8�l��1����l�j��0�l�~�(�b����b��K��L���.6�P�8�R�6�H�%�2���U�:�S����U��V���M����W�M�l�.�<�J��L�"���j��
����4��=�q�8��\�h�m�=�� ��S��"�a���A���|���
�#�^�&��0��O����^��u�J�e�b�8�x��\��g����~�Y���w���
�u�{��
�<�P�(��+�p�c� �u��2��^j��d�7�g��a�*�D��2��!�T�6�n�z�]��\�n�|��s��2�h��.�&�o�~%�\�F���V�0�d�Z�`�0�r����Z��#�5�7�{���u�j�h�d��t�d�,��@��L����J�(��*�H��S��f��
���A���T�w��k��l��-�Z�8�$��5��>�)�d�*���_�<���.���^�J��T�J�H�Z�|�r�L�z�L�X�T�x�~�K���;��P�8��e�V��r�V�d�T��	L�(�8��h�j�,^b�H��n�J�v�D��d�V�Z�
����<�x��!�X��?�H�^�0� �8�&��X��c���=��"��B����l�&�3��9��8�F�j��+�y�^��h�%�Z��o�h�\�:��>�
�~�X�F��5�^�j��� m�-� �~��Y�T�>��p�"V�N��Y�L��S����t�J�I�4�0�r�>�^��~��^�R��|���
��NN��-�x�t�n���z��O�"��(�]�8���>�r�I���R��|�#�=�z�D�S��2��P���h�p�|�`��V�Y��4�(x�(��S�,���6�~��D�,�r�=��,��"�Q���
�
�(� ����~�V���o�r�z�~�@�"2�p����>�.��V��!��r�$��O�"�&��`�n�%��G�f��h�F�<�b�R��
W���T�.�&�$r�X�4�j�,�t�"��.�p�p�d�(�0�>�*��.�>�6�V�T�j�p��;�"�V��f�:�(��1jb?�h�L�h������z�.�d�|�f��%����+������d�|�>�>��G�:�7�
��q�.���5��H�;�|��V�L��a�Z�Z�d�f�K�^�	�����(��Y��&��k�o�x�H�`�b�~��0��/��~��"��q�4�:�T�N��A�l��4��%�B�S�M��*�%��{�f��_��f�y�����|�z�v��r��-��O�g�N�^� � �.�@�6���d�1�q�v�jX�j�.���P�@��}�4�v�~�,�~�H���O�j��`��W��G��
��C�,��b��C�L�(�h���E�,�J�:�v�.��S�x��=�h��N��2���G�t�H���`�0�4����=�0�8��2�.�D�B�&�d� ��7��ou1�<���b��F��o��q�� ���t�j� ��G��M�D�|�B>��4�Z��d�T�:�&��4�j��~�n�>�P�4�.����$�@� �`�.^�y��h�z��'�����9�
�6�X��s��4�f�L�B�>�r��}�J�*�P�2�:�x�<�X"�T�*�U��[�v����]���9�n�4�X��"�L�f�U��`��/�n�,��,��-N�V�s�T�B�T�N�t�D�D�T�\���D�R�$�J�D��{��j��M�X�|�f��D�h��p�f����?�.�*�\��u�"�(�V�r��B�*��,�@�|&����y��;��H�r��s���$�J�h�j�t�|�h���U��X��9�j�p�X���}��r�$�H.�*�H�$�2�`�&�x����C��J����L�9��S�<��H���2�{��m���"��3��n�K�U�3�Q�z�0�z���y(
&R��J��C������)N�.	(."� UT
!\";%@E,��	#�:P)*A�S�2T]Bx�v�]"	*2+\�ZP�&	&�	gQ
0�rQR
��*�	yQn"�*�
��Q�	 ��Q�"��h��Y

0
*��R^*C,��,��R�����$�/�S��^��W

�]�b�o��R�;	&

�����u�8�b�
v�"�T<`�50_"
B/�K*'.A,=26�U/1�,D/=Z/�O,U8Rv3�#�4(I2
eJE@b+27.!~;<1:E�(��W	
dkx6
**

,
��(�}WN'2�,qO:$

$�%�X
iN�(�#�/H��]+vf5FG,
��Y
����Y2

��&��{6���n&B��	�Z%
' JVG�
�Zb^BJ'�
�[6�E�2 �[e��
D"	&		

"�#\


�K*\A�=�t��&$�s�
"&�<�E@�{	f
	@ >[L
*�
w^D;��^z����{@N
��^�

�&�#
$?B�>�_Y
,2H/&)�*�iB|�07D-
*/T>3P1+	*A@ER%N�2�I,-,��`�(9�*SN?�	�#D�hq�a
:M�	aG-D!\�(ak%BFH1
!2:Z_ �Z�Y0$
�	Db
�ObA&
�T\b�z�# �
-&^s��&!YZ�=jK
3
�4%���TkX)
**��(TS
85Fe�":]$�L�
 3�.Q�6�+"Cf:8-\�B��b��lxm
4/<�
�e7�$�
(
�
Ӌ���_
��e���Q�n�	�f41d8��f_$
&F:
�C�L.��8�� .��!g�3p[%B49bnwnsjcV
k(@�h�}N;,76�H�Z&>!:31�T�k��+2�(��oza��T$%�@I[�Y3p!�7�d76/R!kto6S`F/�	B#"+�M�B$E9��;	<R209��#:�)�XcLS<�\�=!`q�Jy�c4=!-�Z$9�$�j��~�9bk^ � �9.��4��%0�H�G��h��O	
��k�
��k�	����m�glt.32;-<fX=-2%._	@&- h�Z�/"?6C
�,^_<Ah��?�f6�+"<]J%6-hC(|��+�0'C7���S]F&U�4�E4�N%`%6-(%�xln�*8G�Ba�t�A
��|���M�bW9�#)�.�
(
Ij`�9�!(9h,10'Z#,!#8�M�P <"j8��`)"	^<�!dE	-*D�V�o $T=&
"
��rF6
" �
�r���Q��"
,
	0�1
�؁�$
�+4'H!��;�R�[r4->
14$D�G�\�@t�
"+&�^ut*"
<6�wu�# 	*	*	��vg��v���v�	��iH
��w��wG$!2(
"

�<�w�

�&�
��K�5":,"B-BD$$(>L$��F��C�y�"H�
z �?�`&�&zi��f��[$<1
��y
�	@z�"	X�M{,2R�m���^!��^{l\%evb7",N]�t�AA>/X." &*

�0�	2t
; X;F
?&^]f%:<""%�"�7XI3 N
8#F0<%-,F>W
X�
�~y$�]�N�`�_�~�

�R�g �p�
1�
�D�-�4@�,		


*$��;��6\3*	d5R
6�~�(�+�h�v�t:�	f*
!"����,���
$0
���.��"��~06h	B�0�!7NUXa�6=6,-	�F׃�(�g�Tp�T/<�<Q;\
Wd�jO"4(�G�t���b�"�:�-�&3G5��	�(�Q�,
Ar%<k�~E�8�,1�D!��oTF9:wL��G(K"p(-,8 	6*��q��|B�D[��(-0�2�qP	&D*) R-�
0YJ; 
+
�4�G;X6�
�W0#&f%\�%��y|%&*>Sn�(6++,�Ȉԑˈ�6
"^K�ۉA&_�
���ڑ���$
	 2fDF!�0��@. Di(3`$sh
$�n�$-0 �L���O�:	�a��16��w��$"DD�v�
�z�w�
�� 
.P		(	!����0�F��D* 	mp@	
 *+0!$
	2	�⏜D�.�' ��) 6��1�t.
�b�m
0X��9��$L020>
�5��

@�*?�?(./	�%49��-�
��h-bJ+�-+,
@61�2�k��H�*o��+26j
!J)0 ,	��
GB�����
��)��
|���B	V7
@$�ǖ��̖���`qF

��D
$��-��0
�������  �B�;�'�,2V)*
	>��tlD.
�,�$	&	�,D	�J��Gl",	��o��	

"�}�%"6<!,#�7���!�0�H

R',+&6
�\0;.F	��	H_\�֝d'0,Fy>"
,
H%����,����	��T�	������	v���:"�
*�l<��c�.��"�k��E��3IkTkId4
�0<��3�G�	��l�Y��29��;���>��U^��*��%����	��,��K�i���c�ݑ�VB����9�J��c��	�Z�*��a��7�P��W���B��]���O�<�u��t��!���~���\Q��D�g�5���V�t��~k�4���c���P&�J�J�������B��,�)���j��{��F�X�!�y�3�`����~�ܑ&p|�H�R��e��'��/����n����V��.�-�o�M��D�c��y�D�t\�`��E��(��Y�<�(�d�j�'�6��/�T��K��%�6�~܄�7����K��Z���@�8�+�7��h����j����6����m��h��p��K�J�~�w�6K�ߤ��+��0��*��.�M���"�a�+�m�m���M���k����4�O��>��i�i����H��C�� ���j��W��[�1�y�X����B�D����$��(����7�G�V���p������t����~���Q����~���w��U9
�`��d�J�d���F���\[��d�"�8�6�@�D�&�@�>���P�����/�s΄j�z�P�h��V��	�l:�X$�h�2�<�� �"�BL���n�H�\�D�0\��,�8_��!��p�r�3�v �d��P�4�n�b�
� >����x�O��
��u����[����F2>$2>�3IkTkId4
�0<�=�����+�U�^�O�*���I��8�G��a��Z�U��6�{����V�x�
��i��&��T���N�b�~��4��T�
��p�l�hy��&��l�;�J�0��r�
�z�X3��	�4�b3�w�}�i%>f+w`*S�D��=�X�N-��o�}0.��2Q�$�2C

	

�j	
7*%$301�[��_��h��g�	�8��&�����D��sec����k�%�2��R�7�����A�1�,��r�_��5��^�c3?�+��	 J�"�O�� �F��D��S��I� W 
0��P�x� gXIII� hXIV� iXV� lxiii� mxiv� nxv� o!�k� rTB� s!;��}� x!�!�� }Y'�N��e��� �g	�POy>˗�����v� �3{� �g	�POy>˗�����v�w �334Y#Z4�(F:+?.w:8-��0�"�V��Y��'��b��,��`��}�m�;�p4j8v
p�d=G<NR]�"�>9�b2p@ZP�24H8�@<T����%<�*��$�6��q���&�0�
�*�6�R�\�/��&tbV����`T�\!]Ѕ $d�L�|N�&�]<J�N�t�$��Tx`�~�b(H!
�����@J"t&��(�,�%�����y��(��)����tKX�F�b�P8R/�TR�1L*DP
xDk(��X~@�8J�F�.�!^�A��$!_q�x�d�J����@�
#h%`fL*�B�Z�$2����\��/��bX**�j�!�y���s��~�j*��h��c�d��|��+&�4/j�v�h�2��x��;�@8�\�"�V�<
"��f��_�!���Z�8H�Z,@����Y�8V�`:�F��X��)�!��g�l����#"�d�!ƒ
^
	"1Z~�'!Ԓ�!��,��%�"	M��X��?:7�Z3�B.2p&��f��D��"���!��'�����^��08B��^����)�n��4�!�"L�S�
�*�{�v�9�
�e�W�n�c�L�
"���V�E�T�c�"���"�
�%�$89R�a9�,�S�r�1V� ��4�i�e�v�"���	"�`.��I��I��"�#2��#T >��}��b���eh!A>�#d��3:0
5

9�G��v�!�#�0�
���#�`��b�#rIC�#�0M�$0r�$0��"�$1[�h��eL��"�
�r��M�
$�R~�1r�R~�1r�$� p�G�g�$�E
�$���[�$�I
�$�(�P�$�O
�$���K
�J�g�$�e
�$���[�$�1
�$�)�P�$�o
�
$�ԁK
�N�>�g�/�Z�$�Z�$�s�w�Y�R,
��% �S�
�*�{�v�9�
�e�W�n�c�L�
%X��V�E�T�c�%d��%h
�%�$89R�a9�,�S�r�1V� ��4�i�e�v�%���
%�`.��I��I���v�r�%��
%�R~�1r�R~�1r�%� p�G�g�%�E
�%���[�%�I
�%�(�P�%�O
�%���K
�J�g�&e
�&��[�&1
�&)�P�&o
�
&��K
��#��#��h�&40/3�&7��&>1/7����~����~����~����~����~�����}����~����~����~����~����~�
&N1/10�������~�������~�������~�������������~������~������~������~������~������~������~������~�&[10/11�&\1/12�������~�������~�&_11/12��������&a0/3����|����~��������~��������~����~����~�������~��������~����~����~����~����~���������~����~����~���������~����~����~����~����~�
&}1/10�������~�������~�������~�������������~������~������~������~������~������~������~������~�&�10/11�&�1/12�������~�������~�&�11/12��������&�0��d�Y&�00��n��l��l��l��l��l��l��l�&�100�'xiii�'xiv�'xv�'XIII�'XIV�'XV�'.0B�'40M�
'H0r6�'d0��'x0��
'�SAN���"��0�U�N�˧�9�'�U��]�D�9�����='�0c��R��
�p���"��0�U�N��F��u�p���"��0�U�N��F��u�p���"��0�U�N��s��P����"��0�U�N��F��u�p���"��0�U�N��F���!�>5�'�()���D(32��l��l��l��l��l��l�(H100�(}0B�(�0M�(�0r:�X
�8��\��A��V���M���(�2��D�=��d�c�������^��_��d)00��l��l��l��l��l��l��l��l��l�)l100�)�0B�)�0M�
)�0r6�)�0��)�0��
*0��b�D�D�o���P��e�!������y��?�*
00�*1�*01�*2�*02�*3�*03�*4�*04�*5�*05�*6�*06�*7�*07�*8�*08�*9�[*09��l��l��l��l��l��l��l��l��l�*z100�*�0B�*�0M�
*�0r6�*�0��*�0��+e�D�D�o���P��e�����b������[���c�+00�+1�+ 01�+!2�+"02�+#3�+$03�+%4�+&04�+'5�+(05�+)6�+*06�++7�+,07�+-8�+.08�+/9�[+009��l��l��l��l��l��l��l��l��l�+�100�+�0B�+�0M�
+�0r6�+�0��,
0��, e�D�D�o���P��e�!������W��a�,,00�,-1�,.01�,/2�,002�,13�,203�,34�,404�,55�,605�,76�,806�,97�,:07�,;8�,<08�,=9�[,>09��l��l��l��l��l��l��l��l��l�,�100�,�0B�,�0M�
,�0r6�-0��-0��-.e�D�D�o���P��e�!������7�-900�-:1�-;01�-<2�-=02�->3�-?03�-@4�-A04�-B5�-C05�-D6�-E06�-F7�-G07�-H8�-I08�-J9�[-K09��l��l��l��l��l��l��l��l��l�-�100�-�0B�-�0M�
-�0r6�.0��.%0��.;e�D�D�o���P��e�!������W�B�o�.IPH�.J3��.NVS�.O!��.Qc/c�	.R3���"ML��r�.[m/m����~�._0U0X��^�.d3�.f0�0�0��.g3�.h0�0�0��.i3�.k0�0�0�0�0�0�0�0�.l3	�.m0�0�0�0�.n3�.o0�0�0ׁ.p3�.w0�0�0�0�0�0�.x3�.y0�0�0�0�.z3�.{0�0�0�0�0Ɂ.�3$�.�0�0��.�3%�.�0�0�.�0�0�0�0ޅ.�0�0�0ā.�3-�.�0�0�0�0�.�0�0�0�.�32�.�0�0�0�0������|�.�3<�.�0�0�0ȍ.�0�0�0�0�0�0�0�.�0�0�0��.�37�.�0�0��.�38�.�0�0�0�.�0�0Ɂ.�3R�.�0�0�0����P�.�3S�.�0�0ȁ.�3�.�0�0�0��.�3�.�0�0�0��.�3�.�0�0�0�0�0�0�0�0�.�3	�.�0�0�0�0�.�3�.�0�0�0ׁ.�3�.�0�0�0�0�0�0�.�3�.�0�0�0�0�.�3�.�0�0�0�0�0Ɂ.�3$�.�0�0��.�3%�.�0�0�.�0�0�0�0ޅ.�0�0�0ā.�3-�.�0�0�0�0�.�0�0�0�.�32�.�0�0�0�0������|�.�3<�.�0�0�0ȍ.�0�0�0�0�0�0�0�.�0�0�0��.�37�.�0�0��.�38�/0�0�0�/0�0Ɂ/3R�/0�0�0����P�/3ST�/
S;vBl�N��������~��������e������~����ڝ��j������ʶ������v�������������x���ߡ���{�������~��������e������~����ڝ��j������ʶ������v�������������x�	/-��
�x� ��W�/8JAS�/9!5�K�r�w��5�>
�/A��t�/D&l��%������Q��4��/U\\�/V"�;�.����p�/�'�tW���Z��
�4��[�!3y�/�!�w�/�%�R�S�P�S�P�S�P�S�P�S�p�=�
/�%��^�'�/�%�4/�[�f�/�0S�>=G�J
U�CD�N
K��c��`�-!�D�1�0A
">�1�0�C�+�
2>0�>��V��y�r�2J��
2MR~�1r�R~�1r�2] p�G�g�2pE
�2q��[�2sI
�2t(�P�2vO
�2w��K
�J�g�2|e
�2}��[�21
�2�)�P�2�o
�
2�ԁK
�N�)�/�Z�2�Z�2�[�c�@�R,
��2��S�
�*�{�v�9�
�e�W�n�c�L�
3��V�E�T�c�3 ��3$
�%�$89R�a9�,�S�r�1V� ��4�i�e�v�3>��
3~`.��I��I���v�r�3���
3�R~�1r�R~�1r�3� p�G�g�3�E
�3���[�3�I
�3�(�P�3�O
�3���K
�J�g�3�e
�3���[�3�1
�3�)�P�3�o
�3���K
��#��#����
�x� �4'�w����>��1��3+��V���)3��g��k\���8���w�I�8&3�4'�Q,��\�l�e�4-�~�E��%4.�V
?��L��GJ��.��
N��*��k��%��H��M��N$I(����K*0G#����y
��b��W��(��;����_�e�|�M��F����j�`��A������r����.���Z�O��3��J�J��Y��D�c��K���6�5�v���%�h�2�p�3����?����C��6�=��O�m��D���(��x��S�U���X��b�H�p� ��-�d��W�d�,��&���9��@��7�.�^�[�c��@��/�V�0�R�d�.��)�&�l�U�"��%��
��a�?��f�r�|��
����	��~���Y��,�y��H��A�,��t�Z�Z�c���i�Z�^�H�"�(�g��}�N�<�S�T�%u��[��&�v�Q��s���4��B���4�S@��0��a�.��:�\�6��p��2�T�~�H��R�L���D�T��z�w�l�8��n��Z'~��J�X�V�J�n�-�J�<�v�8��R�.�pd�p&
�8lT.�H�D�~��6�Nr6l\�Z�N�|�{�`�	�BJ^
�x�@�HD �4�L����Z4�,��p��/�J���&�bH��^��5��$��\��]�(�z��w�z�5X�~��
5YqY��t��?�y������t��;��4�B�(�J�5f�~ݕ�5g�����1�h�{�h�5o�~��5pi��)��Q���5t�~�"�5u�D����g�:�p�����w��O�_��v�H�v�~��.�T�5��B߷�
5�U���H�j��y�Z�X�h�e�R���5��g�K���N�5�_:�*�`�5��M�:�5�����.��-��1��N��A��n��e��L�I���Y�����V�b��L��{��>�Y����~�v�5��~ܬ�5�gC�J�^��5��B�d�5������ �>��i�N�����L�p�B�F�5��~�� 5�n/�J�|�z��@��{��n��?�T��e����_��n� �R��Q�z�Q��.��U��~��\�0��M�5��~��5�s��5��Bߟ�5�ch�f���5��~�(�5�r5�5��~�!�5�L��7�m�6�`�5��~�?�5�}B��f�T��E���8����&��k�n��-�P�\�6�~�s�6\�\�(�p�6�@��6[��6�~�R�6`ţ#����c���s�����\�N��=��@�6�~�G�6�2��o�6!�@ݢ�	6"R���|�t�+�"��G��6+�~ܲ�$6,vۥt�R�>T� �/��h�]��R�~�+�� ��.�|��U�K�4�f�\�� �V���-��(�+���K�6P�O��6Q�Z�6� ��i����~��"���e�(�6\�~��6]�����e���K��7��D�|��?�l��N�6h�~ܚ�6i_��x�T�6l�~��6m����?�s��@8��a��p��7�(�Z��H�y��e�[��
�5��3�6��D�=��8�+6�U����?�>�|�@��E����k�-�<��B��-�2��~��"��%�r��v��A��R�L�!�V��!��2��>��{�^�J�v��W�2�v��#��������t�s�6��~�b�"6��U��y�V�?� �l�b��'��p��[��,������0�V�p���u�F��2�L�p�d�Z�2�,�\���6�!6�b?�6��,�"��5����V�L��E�y��k������"�o�u� �.��g�i��B�H�9��h�y���'�s�q�6��~���6�R���X�6��U��6��J��]��K����}^�r��P��N��
��j��z���$�f���I��L��Z��m��x�T�7�Yް�7P��r�����
��7�@܊��B�75>�@�8��`��T�p�D�J�f�7)�~�m�7*\��e���Z��4�f�71�~ܶ�72b���-�B�76�Zݙ�77c��"�7<�~�ۑ'7=h�!�P�a�=�$� ��u��]�`��0����^�ZH�
�l�B��;��p|�,$��n��;�(�a���a��V�Z�7d�~�l�7e~"J'���7n�X�p�7q��.��]��@�	7x���U��@�
�G�l����B�X�7��@ܰ�+7��7����p����.�l�:�D6�0J��g��*�H�.�F,4��p�7��b݇�7����<�7��c��7����R�8�b.
v
�x�,
�P��R�8Lv�0�7��i���Y�7ˉ��)��m������7��~��7�js�7��Oܾ�[�7׊��g�7�ND*HD,8�KJ�7�O�L Z�7�PP_�&
^2
0��4B
�8Rj.XhN�8S>*|\bJ00
�8(�8:U�
�r\,
J,.&J8F66Z�8`W�,"�PH
D.b�8�8vZ,F >X>0&t�8�[��0d
$�B^8�n."�	8�^*R�@h�8�_"
H$.40Z:<
48�8�a�> "28j&2
�Zn@�8�cedX"b"�8�d�""Z>2xZr>�9faXFF0>(Rh�E,v�9%h[,@:
"�92h�(B
6f�
$ N
�9Rj�b
  BvD<D<8f�!9ol�68P8x
$p\�j\8L4
�9�o�F4~"&B�9�p�T0 000@.R8"�
9�r\`>$fX"P"�9�s�$
�9�t$�9�t9

�9�t�"�^T�:u�^(�:	v2�:v�>nJ�	:wr.(v"6�:*x0djR*j�:>y�>P
 N�:VzƁ$*"<:Nb 
4��:t|��	:{}
:"
8�:�}�<
L42�V�:�a^Z 
�:��&&F P�:��Bh^4R: "|>n�`
 �:Ӄ�HpVb ZNR`N,C�H,P $�:�
*d, �:���@(&rf"�;��T�;�'x�0*
V`�hT.�$;+�T��P<`h,
8�. >*fT@.6:H:<�^�;Q��2"R.�"D
,D($
R�;o��f@ �B�&;��Z>$Z<#..."H**�
Z8�F,V>P�;��~��;��� � L�	;���**$�;��Y$�8�4�".p&"�D8�<;�s
Z2
6"�~6
$F"@�tB*" 4��e�W�<�~��<V �T��m���<!�M�:�<"k$�(�'�����B�4�X��%��^����V���
,�$�d�:�X��b�,�x��H�F�<>�L��<?s���k�1�`��<D�M�ā<E����T��W��8�q��K��x�O�<M�g�=�<NFe�E��4�M�<S�I�	�)<W�z���j�9�*�c�v�A�/<A	gV8��G�2qDA.X?�i�|�=M.�p�<�0K0���~��~��~��~V���~��~��~��~��~��~�~��~
���~�=k0S�>�=��v=u��1�&�78
(�m�*�;8�V�Q
(�D�CQ�%lg
h.�%lg
d.�j�#&N�G&N�2%e8+$T[ �5�Z]b�-� XI��(@c<4M%tQx�v/'�r�3�Z�a�=�����y�=�%;@)
�#�
!�&�>�
>
j �>"
6�
>!"�
��e�
�>�p��9>/��1p8
(�mp8
(Q�%lg
h.�%lg
d.�7&N�G&N�>l0�j�A�>�0K0���~��~��~��~V���~��~��~��~��~��~�~��~
���~�??0S�>����5��&�1�p��
?Q0K0���~��~��~��~���~��~��~��~��~��~�~��~�?^���\�?v1�0��?� >����&�w��+��U�X�8<2����?�1�0��
?�0K0���~��~��~��~���~��~��~��~��~��~�~��~�?�1�0��
?�0K0���~��~��~��~���~��~��~��~��~��~�~��~�@1�0��@)��3�
@0K0���~��~��~��~���~��~��~��~��~��~�~��~�@51�0��@@0S�>�@L1�0��&@S0S�>�'�*�c�<A	gV8��G�2qDA.X?�i�|�=M.�p���v@���1�&�78
(�m�*�;8�V�Q
(�D�CQ�%lg
h.�%lg
d.�j�#&N�G&N�2%e8+$T[ �5�Z]b�-� XI��(@c<4M%tQx�v/'�r�3�Z�a�A����y�A%;@)
�#�
!�&�>�
A0j �A?"
6�
AD"�
��e�
�>�p��^AR��1p8
(�mp8
(Q�%lg
h.�%lg
d.�7&N�G&N��4,�BP�`Ejb>�E�(.�h�hH@�r�h�X�`V@�X�D��A��D��A�W�(�~H�A��E�n�A�X���@��m`��&~�A��Fܽ�A�[}*�|��-�A��E޴�A�\z(J�A��G�4�*A�]
rrDF�$�4@2,P�8"�*l�� �Pn(�L�J�P�&��0�f�81(�A��L�đA�f�Z>�<r"�@@�
�L|�B	�M�āB
iBB�R��3��J�B�M�?�Bj;�B�M�c�5Bj� ��-�� t�RN
�\F�Dk�T
0v*`�L2�$��8&6J�:L\~�D,8d� ��-��6�BJ�Q��BKs'P*v�46p>j.��K��6f
�686.
"0�Biwz
&��Bm�U܎�BnxCT��Bq�U��Brx��$6"
"d��Bz�U�q�B{y��.�B��V�āB�z�\(4f�"b�B��Wݡ�B�|m*h�6TLD6�J�\��2�$*$�X�B��m���y��&�B��Z��B����B���**NBjRN>�0 "06�B��[�@�
B…2) h*"*PB"�BΆ�B��\��
BІ><����.�����;��>
�z�B��]ބ�Bމ2�&�B� *&�4�j$f�V<�B
�2z�B��`�w�B�HD��jFL�B��`�́B���
2H�R6NH0��Z�.�.C��vH\,0�8 (��G��Z���<�0"�t� �`�T�zV
��c��*`:
&^,$T�CA�e�:nR)�CK�hݐ�CL��>�, �N�CQ�@܉�CRN,0�CX�@ܢ�CYNQ��C[�@ܤ�C\Nif�c�D#,@�[2�Ck�@��CnO��@i$
�Q�@b~7�C�P�(�C��@�+�C�P�$V�C��@߁�C�P΃C��@�q�C�P���C��@��C�Q�C��A�J�C�Q`�C��A�	�C�Qs��6�M�C��A�ցC�Q�<�C��A�O�C�RU�C��B��C�R��C��B�:�C�R�J�C��Bܹ�C�R�z�C��B�|@�C�Sg$R�C��B�ӁC�S��C��B��C�S�e� ^�u�(<P
�C��C�E�C�T�*d�7�@�?�v�C��C��C�U}����C��CޕQ�C�U������C��C�d�C�U�|�C��C�_�C�V>��y��,��Q�� $"�D�D��&�DW)�D	�D�{�DWM�D
�D�t�DWh�D�D���DW�\�D�D��DW̃D�D�6�
DW�"��M��Z2"�D)�D�āD*XI4J�D6�E�m�D7X�(T�D@�E�ׁDA6���V��I��X��a�DF�[�)�DGY_�DH�E�G�	DKY|D �DT�E�v�	DV6ρ�z
n(  �DaZ�4$8.
*"�Du�F�ÁDv[�*�	Dz[���U��
����J�D��G�V�D�\_ �D��G�-.d-�D�\����D��Gݡ�D�\��D��Gݒ�D�\��D��Gݷ�D�\ɃD��G���$�D�]4 ��#��p��m��v�D��G��D�7�������,0�D��G�v�D�]�
�D��G��D�]���u��<<��[��r��k�D��H�{�D�^�$�D��H��D�^��D��H߭�	D�_P6 $H�	D�_� ��9������b�$�D��I��D�`� (46����"2�D��J�[�D�a��D��Jܫ�D�a�.�D��Jݏ�	D�b#\,
Jv�E�J޸�Eb�
B�E�J�F�EcY�E�K���o�EclX�E�K�$�Ec�PD�Ec�V
T*�E,d��E.�K��E/d�4��#��@��C��T&",
N��}��D04��I�EN�L��b�EQfj�
ETf{��	��<��7���R �Ea�L�r�Ebg�Ec�L���EegM�Eh�L��&�Ekgt�El�L�ځEmg��En�L�߁Eo�c�'�6�Ev�M�J
�Eyh�E|�M�e�E}h3��A��f

��S�E�h�.~�E��M���j�	E�h�$���E��Mݔ�
E�;���pD.��]��b2 �E��M�9�E�i���M��L$�E��M��E�j?`Y"�E��M��E�j���7��`�E��M�d�E�j�"	�E��M�1�E�j��E��N�$�E�k�E��N�=�E�k��c��4&2$�E��Nޘ�E�k�8&&N�E��O��E�lM",��
�� *(��a���F�O��FmF�{�<2�F�O�@�rM�
Fm�
��&
 ,~1����@�F+nO�F.nW����h&�b� 
�F?�O�~�F@oRN
N?
���FMo�FN�Pܖ�FOo�0�FS�P��FTp:��c�� �F_�P�ƁF`p�"�Fb�P��Fcp�Eb����\@����-4�Fw�P߼�	Fxq�T����\(�F��Q�)�F�rx�F��Qޥ�F�r� ��C��V��M���F�>���j(��}��,�F��Rܖ�F�sq&


*
"23�"'F�F��R�M�F�t�6$�F��R�V�F�t��F��R�o�F�u 
��'�F��S��F�u@>"��/��X��W��p"(#��C�F��S��F�v�F��S�7�F�v%&�F��S�j�F�?���n�F��Sދ�F�vI��}��,L�G�T�J�GvɃG�T�U�Gv�G	�T�"�G
w$�G�Tݩ�G@9�G�T��1�GwX�G�T��Gw|�G�T�L�	G@X��~
�G!�U�.�G"x$4D�G)�U�ف	G*x�62 .�G5�Uݧ�G6yH��'��BD�GFy����GH�Uߩ�GJAO��t
��q��(
6�G\�V�ԁG]A�������6�Gd�V���Gg{='�
GkA���}��'��LF+�Gu�V߲�GvB������L�G~�W�K0�G�{�
$l
�G��W�.N�G�|��G��W�b�j�G�|ăG��W�G�|̓G��W��t�G�|�G��W�\�G�|���{���G��W���G�}@�G��X���#�G�B���
��'��B�G��X��G�}��G��X�`�	G�}�,��I�G��X��	G��X��
0^�G�C+��\��/��p���G��X�p*�G�mD��+�G��O��G��
 �G��Y��G��&.*$D(: �G��Y�~�
G�� L1
����/��]��f�G��Y��
G��=��k��@��%��P��G��j2$�H�Z�݁H
��H�Z��H�� �H�Z�o�H��H�Z�݁H��H�Z��H�<	�H�Z�X�HDv��8�H�Zތ�H �\�H"�Z޷�
H#D���V<
J
�H4��s$$��3*C�	HA��V&6�HJ�[�s�HK�B*�HS�[�ݑ	HT�e$��i��.��m�H_�ӃH`�[�e�Ha��@F>��Y���Hp�[ߔ�Hq�{�Hy�[��H|���
H��� �H��\�
�H��9�H��\�9�H��@$@�H�����]��
�H��r�H��\���H���=�H��\��H�E���0�H��]��H��� �H��]�I�H���	Hć�P(
��[��t�H��]��Hψi�H��]�1�Hшo`6�H��]ޓ�Hֈ���%��~�H��]��Hۉ7�H��]�#�H݉B�H��]�R�H�bL @$����"��!��B�H��^݅�H�!&�*=L>H�H��^ބ�H��
 ��C��.�I
�9�I�^߳�I�=�I
�^߾�I�E"�I�_ܸ�I����k��lt�I�_ݠ�I��I�_��I�	�r d����~�I'�`܊�I(� �I-�`ܻ�I.H��>4P
,
�I9�`ނ�I:HN�I;�`��I<�D(�IB�a��IC���IE�a�U�IF����:��5
6
 T�IR�a�k�IS�C2�	IW�� ��Y��t
�I`�a�ׁIa���Ic�a��Id��	+0"
0:4Z�I{�b�I�I}��(�I��b�k�I��B�I��b݈�I��
&,V�I��b��I���b�I��b�q]�I��Q 
Z�I��bޙf�I����,�I��b���I���U(Bl�I��b�Z�I��m��I��c��@�IĕȃI��c�F�Iǖ,�I��c�6�I˖<H�I��cމ�IΖ��I��c��IЖ��I��c�2�I�I������I��c��
Iؖ���3����u��
�I��dޠ �I痖�I뗾$��5�I��eܐ�I��%B<��J�e�ρ
J��*(���J�e��Pl�J�1�J�A"�, �J'�f�ƑJ(��
 $��-��X��K��X��K��L�J9�f�r�J:��D
"��;��T.�JP��
�JR�g�ۑ	JS��

h7�J\�g��h��J_��Jf�g�āJg�1
,�Jk�g���Jo�T$�V0�J�g�ׁJ���J��h�/�J�LăJ��h��J��2��C��p(�J��h��o�J��s�J���(: 
��9���<�J��H��J���*�J��hߌ�J����J��i�7�J���>����l�J��i��J��]�J��i��J��i�J��i޲�J�.��N�0�<��B�"�Z�r�P�T�J�Ny�r0"�$20�~$(,@H�	J�Q3vf�J�Q�J@:
�J�R��("N* �KS�d,� KTL�4$6""4(P  �K;V,&`2 (*�KOW�DKTW_&,L"J0L",F�Dj>:.V"�r
bH,�|>|D �^Fd6H�b\JN&�.K�_M�<z8�$�&p&N
,:4F
6Rx*.b2B&:�K�c�K�d

$(6�K�d�rT4,b<
�<� K�f��*>T$�R�:�HB6D�j�0"z�Lkn�Lk��<:Hd:�4xl�(Zd�L0o&�,* �*$P�.�L>qE�X�DF�LFrZ
�6$�3LTsM�x^4:<t8( (
(�6"&

.�L�w� .�
HB2*p$�!L�y?", \0J.Fv
@�X�Pn
<$�L�|r,6�B h(
,(�L�~/T�v:�L��,�L�
 4�
�0&0L..X�M���&M�>P,0
>2�>B�~�6t:z�N$�R�r�&F: �M6��  z"
&�MI��$�7MN�&HP0�f,>$ f42�VN|2&�"DD"�M���4�M��M"t, �#M����<,&8(""L(>h$4t2�$.@�MҒ��<BL` 2$�M�.:�v0:�$M��z,>
.�(.

j2
&$.<�N�� x>$�T$:�N$��N)�6$$.
$l20"�X �R��
NH��f,
�NT�4J(\�
NY�@�����Z����B`�$|�p�Nc5���C�Nh�h޲�NiN.�Nk�@�X�NlOs�D�,�z�Np�Aݱ�NqQ�<�Ns�A��NtRd�4(�V�Nz�C�X�N{U��:�H">�N�L6�j�n�~0l�N��V�̑N�^C�Fv�(�Pv���
��)&�N��K�B�N�d�� ��5�N��K��N�e��N��^ݴ�"N�e�����*�
�$4��/��t�@�Dz�t@�L�,��S���p��b�"N��g��,�R�0
�~�N��P�K�%N�r>��}�?��P�d�>�<8\�6�<�\��P��B�^�p�P��D`�H�<d>�DH�<tr�@�N��[ܞ�
N�Q"�V�|&N�nh��(
��{�N��_�<�N��j� �8����H��B��O���H�6��*�\�$�8�Z�.�,j� 
�6,r�`�2�j��O&�N�=�0O'{�3���A���w�*�Q�b�,��G����h���&��E��+�l����~��5��r��7�)�	���2��U�6�P�H��	��j��k��@��k���0�J�OW�X�L�OX��N�R�O[�g�=��8�2O_�!��V��
*6g:�; 6�H	�C�W�:�- 6�l�.�O�%ʑ2O��!��V��
*6g:�; 6�H	�C�W�:�- 6�l�.�O�%���	F=89tut7:��
��6��u�PCL�PKCL�����PAS
�Pk���T	��_�
P+0ׇ�~��~��~��~��~��~��~��~��~�PI^t��M��4�9�D�^�a�G�|�j�o��O�PU0{0K�	PVR��`��T��s�X���X�y�P_PV���PaS̃PbSSE�Pf0ǃPgHV�Ph �<Pm00��l��l��l��l��l�dP�'00��l��l��l��l��l��l��l��l��l�dQ
00��l��l��l��l��l��l��l��l��l�2Qq100��l��l��l��l�Q�.0���Q�ppb�Q�'S���<��~�
����r�Q�Jr.������Q�0�0�Q�0�0�0��Q�0�0�0�0ʉQ�0�0�0�0�0ȇQ�0�0�0�0�������~����~�Q�0�0�0�����~��~�Q�0�0�0�0�Q�0�0��Q�0�0�0ɉQ�0�0�0�0�0ȃQ�0�0�Q�0�0�0��Q�0�0�0�0ʉQ�0�0�0�0�0ȇQ�0�0�0�0�������~����~�Q�0�0�0�����~��~�Q�0�0�0�0�Q�0�0��Q�0�0�0ɉQ�0�0�0�0�0ȁQ���Q�00
�2Q��!��V��
*6g:�; 6�H	�C�W�:�- 6�l�.�R%ʑ2R�!��V��
*6g:�; 6�H	�C�W�:�- 6�l�.�	RN%ʂ�.��EA��C��L�RYN�.V

6�RjOR 

2�R}O�

$�R�P5

:$"L�R�Q/.`@D$@�R�R��R�R�*�R�S08:$&"�R�S�4.(
�
ST�T**�SU2L&:$@
2�	S9Vu6$�SGW�!SLW*($$
"&>(OT" $�SoX�:
H,�S�Y? "
T

�S�ZH8(BZ�$.�S�[�0�	S�[�.H�	S�\{(D,�S�] 6	 �S�]
.Z
$T�S�^\&:&4
4$�T_.,$8�T_�
$�T-`q
��{"�T?`�,
D

�TSa� f
�
Taa�"0,@�Tqb��-Twb�6"*88&
>,J4B
(�T�d�"$�T�eC:4D�T�f
�T�f

(
�T�f��<�T�f�
0(6L.�T�g�$:,�Uhy0D�Uh�
4&.�Ui{"&,j8$:(,�U*j�0.F
�U8k	<,("�UHk�l\
�*UUl.6. 4*$lT>
Dl,
H�U�n��*U�o>B,$(D(2.|0�

B��b*�U�q�:�	U�r*
�U�r�(^<
<@�U�si$0
 �U�t6�@&�U�t�(�V	u�:�. 
,(V2B*":�V.w�(D*
@
�1VBxcV0
&.">2
�VuzH0
T"F
�V�{j2
$�	V�|
�V�|F
$R&(JF
 
(�V�~=B 

^�V�?82,>L�!V�.
>&n6" 6*,8�W
��4 &�W��r
f
�W3�p(
"�W�(`�Y�*

(�WR��J�WW�
 �	Wg�y(�Ws��.
84>>�W���

�?�v>�W��E&�W���,
"�WÈK
 <"�WЈ�*�W։ 
�Wꉠ&@"H
�X��

�X��$"

**�P
$	�X/�f.�X9���J2
 i�XX�B4:B.
("
(&

�X��en4
 42�X��?D
B,L�X���'j

2
�X���02*
J 
�X��&
0
�Y���Y
��

:
*
*�t
�Y&��<|(4�Y4�n$F4(P�YF�{@
�"YX��<

0�:2 ."~ :�Y��$ "�Y��T��Y���:(
�Y��x

&P*(
8�vD$�YΝ:>:( &�`*$�Y���0"�Z�G"�?; ]� 9�  
�������� "9 ]?�`0�0�0�0�0�0�0K%0000J0j0~0�0�0p0s0v0y0|	�0�0�0�0�0
  �0	0	"f 2�!�"�"�"'"j"+  �	��!�AR0AU0��������'*6�T�M�\N��O_؁1s6�oN�)O�P�Q	Q?Qi	Q�Q�Q�RKR�R�/T,"T�UVOW7W�%X�Y,Y�	Z5
Z�
[Z\\8K^6
^u^�^�^�_V>`�`�a
aX(b	b2b�Td�e4
e�
e�f5	fgg&gc�j�$kxkk�lgn�xr;r?r�
sN1t�u�u�u�v vGvavhv�v�w%w7w�<y�z
zaz�z�{{p�~Y
~�~�~�LP_h�
�4�e������j�w"�F�8����D��%�����*������0���������������F�b���O�Q1����*����*���.�����=�����o��=�Q���������������"�'�.�M������7�]�����	�����	�_�f
K%$`	!`3�3�2�21")�0��L0
�0�G	00�00�=3�3�$�$t	!p	2R$�$#�#�3�2*+&&!j%�%�%m%�%q0�	  'v!z
!�0��]�\qF%�� �$�����w'�*; ]� 9�  
��������	!1* ]�?�`0�0�0�0�0�0�0J0j0~0�0�0p0s0v0y0|000K% 2�
![!S t	 �"YZ <]� 9�  
��������
 2�
![!S t	 �	0(.:	0(.:!S!U![H	0\	!p!z!`A0J0j0~0�0�0�0�0�0�0�	2 	0	0?$`$eE$�$�0J0j0~0�0�.2�	2�2�2�uaA0J0j0~0�0�0�0�0�0�0�{aA0J0j0~0�0�0�0�0�0�0�0�}aA0J0j0~0�0�0�0�0�0�0�zaA0J0j0~0�0�0�0�0�0�0�yaA0J0j0~0�0�0�0�0�0�0�!!3�3
3333(3.34
3>3C3K3O3U3
3333(3.34
3>3C3K3O3U000�0  &h03�	/Y"r000�_000�_��3/p#�#�#�0  !�!�!�!�%�
%�000
0�	0A0K0M0QA0S0�	0�0�0�A0�0�0�	0A0K0Q@0S0�	0�0�0�A0�0��E0�0��0�0	0
0A0LB0R
0�0�0�60�0�
 2�
![!S t	 �"YZ; ]� 9�  
��������
 2�
![!S t	 �	0(.:	0(.:000�0  �[�b.��F_�&s6pP�.�`NN/N@O}PRS
U`VVqV�W3W�Y�[~^^k^�_acBd�f[h0h�ii�jPj�l�!o�p]p�rUss�s�tt/t�u�vv�w_	x-x�y[z�|�}	}�R���������������C$������2��	�R�2���-���3t�`0�0�"f"�"�"�"�#"'"�"%"j"+"vU0A0�M0�	1�1�0�"�>�v�:b
�"r
>�90�0�U0A30�!0�	1�1�0�0)4)�	$�&	1�1�#�%� G1�"v"�"�"�#�&r0�0	1�1�#�
	1�1�
	1�1�	1�1�0�	1�1�"f"�"�"�"�#"'"�"%"j"+"v"�>�v�:b
�"r
>��'fwlwM5�g	��!�4���"�d.�O�(OwP�P�%R�5]U.V�WEWsW�X�X�X�YYuZ�7a\g6^m_�_�a��E�J�O�/Ec;c�c�d�&fl9h�h�	h�i�j2j�kk�k�m&m�nHnKnSo�(q�rns	sn-u=u}u�u�
vv�w�x�y�#{.(|�}=!~�
c}Q�����
�Q�Y���$�a�Hz�[��H�����d��D�� �!�IU�a�ȡIh���F����|�I��bݺ��I����	���3��
�2�������:1�k�v�	�I�i�m��v5N#Q	Q�R1RxS�T4 VWW=D^�.c�c�d�f� j�k}n�p�rOs:3w�y'!|R|[}�����&�����7���@��#���
��$�b���
��
�*!Rq)e]g��R�2"d�2"d���

R^0�0�0�0�A
1	0	0"$��t�2"d�2"d	NtOKO�O�P2PQP�P�P�P�QQ'Q,R�R�S'S+S�S�Tm
UUZU�VCVa	V�WW$!X�X�YY5ZD[�[�[�[�	\t	\�]_]y^P_+_t_�_�`V`�`�a�a�a�a�a�
b�b�b�-d�ee<e�e�ff�f�g�hph�ipj�j�j�j�kk�k�k�k�l&*n�n�*q�r	r{r�s]s�tgt�u{v9v�v�wwVwsx`1z4{?{�{�{�	|9}�}�~��!�������
��M������\�_	���
�X�`���<���H�������+�v�z���c�u������7�]������ �R�]�(�3������������$�.�5�\�n�������������%�9�V�w����"�������J������9�V�m�3�S������������C��PK
!<�9��3chrome/pdfjs/content/web/cmaps/Adobe-Korea1-0.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�q%�����������������������������������������������������������������������t�PK
!<��=��3chrome/pdfjs/content/web/cmaps/Adobe-Korea1-1.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��qG��������������������������������������������������������������������������������������������������������������������������������������������������j��PK
!<��ȯ�3chrome/pdfjs/content/web/cmaps/Adobe-Korea1-2.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��qH�����������������������������������������������������������������������������������������������������������������������������������������������������/��PK
!<:@.�Z�Z6chrome/pdfjs/content/web/cmaps/Adobe-Korea1-UCS2.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�����_��V�3�O��p���q��4�[��5��~��J>��P�*��	��$N���偙8�A��X�!�2��W�&�uK$�U�	�0�sDA.[<��⁇^V���N+ �C�D�u:��l;��W��H�3� �Z��&e	��\K#�
�%��|�3��Q��!��(�	�&l�$�G�
�`�K�>*�o�C��!�(%	w	WE$$a�
N!�V
�E�^#*��3�!
$-��M�v�S�r�1F�9������TCl��4�i<�P9��DJ��t�3�} ���6..

*.(
&$.>

6f. > P>.&>D(.T
..6F&66.

,&v
&&6
�1��&&�?�+>�G�c&(�?Z��
>
6.�
.&&N&6nD6L&
..L.&�	�o 4�6��&>P
6L.F�������8���T6&�*���
* T
	66&&6�	*���5�P*FV(	�|6(�
z���4(��3���
>.@
.&&L.PL"f6
&&>&6X"L.D
....

*

*..&> 6 &>L
(& L&(&N&Pn66L..&L. �	g�D�r�o�	|ͧ&.�"��LPL	6&>�	��t(&�,���(L	6&(L*6�=ѳ.6�.J�0&>
.	�.&���(D.���|
��ԋ(D�,��P
&(L&
�

'֐�
3���
@��
jכ��>j�&�b�L��
�^�F�n�h� �M
zg�8�j�~�r�<P�$`�Z�"��V�T�\��5�p��J�r�.�$��>.� ��A��f�r�Z���p�
�J�$�.�l�x�R�$�l�B�v� �r�N��Q�b�2�x�\��Z�h��2��!�j�6�B�$�<�D��\�d�,�x�4�^���h��
ɟ���e��
�P�H�.��{�X�&��j�>��X�N�4�2�$z�4���^�^�2�@� �r��yV�&�J�:�NR� �`�L�&�@��`�:�J�6�F���B�#��B�
�v���$��D�R�6�V�p��\���<��d��u�n�\�0�v�� �2�D�d�Z�j���d�<��o���2�&���S�����U�@��8�b��v��0�d���n\�.�h�f�v�B�P�\�Z���`�2�v�R�P��O��$�v�z�h��A�L��>��t�b�`�$�,�l�Hj�B�:��p�D�^�z�V�Z
H��$�~�d,���R�6�t��\�~��Bh�B���M� �L�,�"�D�b�2�^�Zn��B�V�*�\�z�^ ��0�:��j��A�@L��,�$�,�^��~X�Z��"�v�|�&�Z��(�\�:�Z�,,�d� �^��`�l�"�0�>�`�t��M�Z�,�8�`�l�P��a�f�X�h�T6�`�R�j�
��y��~��� �Z�h� �T8��j�&�:�@�P�|�X��;�\�L�&�~�h��fT�j�J�x�t��g�(�,�Z���n�&�l�,���D�P��\�h�t�T�`���u�\���*��q�|�.�n��x�.�B�N�&�r�@�R��k�h���+�R��&��x�P�~��`��e���0�Z��w�X�4�~�>�&��F�$�R�*�X�@�\�4�L�Z� �F��v�p�"�*��'X.�R�R�
���D�P�
�@X�H�~��V�6�p�L`�B��jJ�*�4�4�<�|�X�vR����d�(�� ��� �>�d�"�6�d�F�Z���#�h���*���3��x��h�f�&��C����c�H�Jh�>�:��
�"�~�|� �T�|�.�p�f��%�b� ��<��7��X�x�<�|��)���v���#��@�n�h�\�4�h�p�l�2�F��d��[��~�0�V�"��
��~v�.�p�8��F��)�<~�<�@�(�
�|��Z�b���.�n����t�|�x�J�v�V�"щ_���7�|�4���N������r�$��]�N�6��$�|�.d��`��z�D���f�,��g^�X�4�4�P�2�� ��.@�(��Z�"�
yz�4�T�r��zL�<��l�p�\�R�z�\��>�e�X����)�$�H�J��x��M�c�B��P^��F�K��Q�~����)T�G�n� �$��*�H��Q��+�F�@�N�>��;��J�^�+�l�6�d�)!�[��h�P���n���v�d����h��O�-��v��U�(�X���P�M�t�A��6��{�x��N�R�\��@\�2�h�.�>��\�~�X��>�P��A��v�z�6��}�l�X�2�d�2�r�"��l�$�r����$����~��t����b�*��H�,��*�>�8�h���H5�p�J�F���\�X�"�� �X�"� ��-�b�v�C�Z^�J�<B�l�"�*�(�4�V�R�(�n��V�j��*��d��2�:�8�n���H��
�(�t |�R��P�J���f�"��
�t�6�H�l�H��/�:�<�,���"�T� �p��M��&��I�@��l�4��<��|�:�~�l�b��\�B��S�t�l��\�V�$� �*��}�� �2�LZ�:��m�u�v�\�d��4�>�D���S�\�d�6�r�t�z�,��i�z���6�X���_�d��V�8�"�`��%��,��'�(��R�(�.�0�`�,�0��i����+��$�"��R�$�6��1��$��R��'�P���o�.�D�>�n��8j��D��f��3��z��R����(�F�V�b���h�Xl��g�T�`�J��l�>��e�^�D�~�J�~�T�t�|�n�x�L���2��P�v�r��s�p�V�&�v���#�"���~�B�*�t�Xf�n�.�*��"����-��6��
���3�Rv���r�n�,�2��*�$�*�X�x��t��S�d�@�P�t�t&�{��o�H�D�~�N� �:��;��r�N�n.2���c�&�&�N��h�f�T�8�b�~�T�J��k�P�@�`��h�V�f��L�2�V�L����@���4�d�r�,�p����b��c�����E�(�Z� ��b��?�p�$��\��i��p��_!�"�z�B�d���7��\�l�|�,��L�2��(�l��x�<��|� �
"�T�"�L��Y�@�"��B�^�p�f����f�N�h�Z�U�<�(�b�S���V���N�L��;�l��v��8���
�6�P�v�&�4�V�$�V�n�t��L�N�.�b�j��?�8�b�~�:�@���3�D�F\g�,�B�z�0�"�@�t���Z�0�z�,�d�T��(� �V�:��{���f�H��
�f��,'�X��E��P��
��f�~��^6�<X�j�$��a���Y�N�^� ���n�>�"�
�T�8��\�6�e��)�F��L�l�.�F�f��B� �V��>�l�D�v�H��tN�F��I�N�,��n�>��D��U�$�a��d��U�F�
�
�^�H�|��`�V�~� ���"�8|�x�F�h�p��H�*�*�J�P�.�F�N�p�6�N�r��s��^���x�X�&�n�\��^�V�j�\��a�*�&�m�L�F���B�v��X�0��R�4�p��P�R�$��{�|�,�:�F�h
l�:�^R�.�d����*�f��g�x�d��D���"�j�:�j��
�\�V��L�����*��`�>�&� �b�T�R�(�L��.�&T`�2�J�f�f�z�8��Y� � �|�X�T��J�`��(��A�R�x�:�~�<�^��|�t��� �J��6��*�T��Z�T�:�X���:��5�
�<��j�8�&�*�R��
w����F�8��w��T�f�$�e��D��G�P�<�T�*�,��J�d����|�@�h��	�H�\���&�(�|�]�H����p�,�|��\� �^��,��7�D���:�4��K��~��F�|��_�P�d�J�6�L6���Z����&��p�N��W���n�l�V�P��L��z�dl��n���
�t��l�*�F��R�j�H�d�|�d�R�X��M�m�� �n�#�cg�T�T�H��~��*�>�^�@��M��,�*� 0�l�R���t�f4�,��2�^��l� �^�0�l�>���J�0��2�8�
(�p�
�Z�f�~N�(���!�#�D��|�@2� �H��B�Z�6�V��(x� �(�\��+�v�N��3�B��B��P��/��v(L�b�<�H�T�.�|^�,�l�>�X��$Pl��x��.�|��T��*��0��^�N�� �d��:��x�>���z��>�L�v�X�(F���rV�N��U�J�@�P��izr�H��>�>�d
��8�n��l�\�2�\n�Z���f$��b��:z��J�v�x�v>�@�N�Z�4�&����j�.�`�|�|���x�"�D�z��'��y�"��b�b�h�0�r�r��6�n��s�4�N�F���A�~�H�P��p�$��i��� �8��K�X.��L�F�X�P�0��.�"*B�`�:��l�H�~�n�X��D�<��|�Z�x�z��}�<�~��m��2����X
�f��)�j��$L�2��x�X���<�:�6��:��~�N�6�d�X�8��&���P�$���.�~�\�n�<�8�~�h�2�&�j�8��>��%�`�z�v��.�F�l��L����n�X�z�^�t�$�"p�
�@�"��Z�(�0�&�N�@��
�P��^�H��@��>�b�x�~�x��
�.�p�F�N��j�2���V�H�A��0�t�$�*�~���BJ�T�R6�d�t�b��y���H�Z�f�l�~��	��B�F�2� �:�� �0�
���L�^��Z�&�P��n���h0�d��2�X�0�&�&��|���A�(�d�b��X��~���i�"���d�6���5�y��F�>���4���+�2�x��M�L��S�P�J�8�N�t��
�D(�H�6�z�x�&�2��� �R�H�.�j����T�f�t�<�,�$��*8�h�x���<���V�2�>�N���P�RD�"��_���R���x���
�R�4�,��"����.��n��9�JD�.�R�J�8�F�j�V�z�^�4�Z�@���p�F�f�\�|�B��,�N�Z��Q���8�h��=>��j�z��'�/�&�Fv��)r��j�R�n��B�6�0��#��8�(�0�&*�h��b�r�b$�vj�N�
�.��p���n�fH��
�>�t�>�z�>��
�N����J�@<��
��>�2�H�.��f�n�&�l�T�(�4�(�h�"F�h��@�^R��)�d��D�AB�n�z�^�2��(���m���.���c�J�����P��F�X�&�4��B�
��j�&��'�P���8f��@�D��8�"�r��L��]�\� �
�p�f������}��>��`���f�X�<�B�h�N�$��i�P�"��2��q�z�@�V��F��|��'�X��$��t�0�:�`�2�V���n�2�6�.����%� �D�T��w�6�t�J�8�x�r��v�.��;�$�@���R�N��=�h�<�*��z�(�T�b�0�N�j�T�Jt�J�x�2�r��X�x���
�8�4��|�J�n��X�"��Q�b�4�v�p���X�j�D�l�r��}��2��9��n��H����O�}� �P�8�z�.�@�`z�f�
:�t��e�p�2�2�.��j�,�(��m�� L�(��2+��n��B�<��,D�R"�r>�
�Z��:�"�v�4��c�~�V��~��	�J�t�B�~�
�6�P�R�.��s�x���}�X�p�\�Z�Th��t���0�&�@�� �T�p�f�
��:�\�Z�r� ��/�N�:�0�"�r�8�~�h�D��\H�"��2�Z��>�x��y�p�v�P�*�j�L��.�X�f��t�T��P�H��d�0�6�8�J�T��6��(��y�n���`���r�r��V�h����v�XL�4� �&�n���]�~�|ׁ�f�<�D��%��
�<�D�
�R0�T�"��Xx��Z�B�D��C�a�r��@�@��=�*�L��1�*�@�.�R��U�&�j�z�P���x�h�|�x�T�:�V�l�H�FL�.,�B�^�|�"�<�pL�"��)�f��T�
���A�6�R�^�~��^�|��$�l�4��:��
��� �����!��H�f�:N�&�� �j�N�b�L�v�P���N��l����F��Z�h�4�XURֲ �
��z�X�2����s���b�p��>�4�n��8��C�,��C��r��r���6��b� �nP�|�H�,�h� �f�p:�>��.�r�^��4��j���'���&��#�v�(�$�6�X��L�>�8��0�F�&��.�0�>�v�x�R�$�j���9�:��.��r� T��^��B|�H�f� �
�<�Z� �P��l�t\�(�@�4� �r�8��"�"�v��~�H��K��jP��c�R��T��x�D��n�!�c���H��[��:�^���`��1�
�����t��1���Z��i�V�`�|��9�x�J�6�d�^�X�0�~��"�
�t�T�V�N�0�f�t��?�0H�X�`��P~�
�8�L��8�Z� L� �8�^�V�&�X.��+�$�~^�:��A��X��D�4���n�@�v��:�r�>��o�2F�t�V�x��*�n���}�`�:��N�$�t�f��=�<�o�6�V��R��W�J�Z�@j� ��H���(�.�`�z��R�2�H�z�\�b�H�0���a�:��\�x�&v�x���|�f�b���j�`�B�F��2�^�0�j��J��)���&�f�x�4�V� �`�,�J�F���P�d���t�(�� �L�D�B�l�`t��j�0��H���v�<��8��X�*�R�*�R�F�>�>�N���C�B�z���'�F����>�~�p�P�,�V�|� �$�t�x�b�"�:�>�F�&�.�"�<�*� ��y�L�,�@�*�8�|��~���b�`�@�6�^�<�2�x�z��^�N��l�p�h�(8�l4�F�N��
�~�(�D�V��P�8�"�0�@�2|B0��<��j�&����< �d��/�D�Z���J�j�j��-�:�0�@��<�p�x���s�(��v��k�&.�\0�$�t�,�J�X����h�h�.�R�
�R�@�2�8�~Nl|�~��X�\�x�@�V��x�b���p�t�N�f�8���J�&�J�
�J���1�4�T�N�p��X��<�^��|�@�X��t���,l�B��z��y�n��:��B�,�|�D�6�.�pF�~r�J�.�0�p�z��~��6���h���H�V�,�f�P�^�T�2�N�<��j�.�2���-�n�V���W�b�q����m�^��"�t�R��(�~�&�2�(�r�|��2���~�*���|��_�&~����L��5�*� ���v��&�~p�z��.��R�P�L��jR�j�x�v�>��T�2�P�z�8�b��/�V��7�l���L��:�F�8�v�*�>�D�f�|�|�&�*�f�}��)���&���!�:�a�*�p��9�^�^��Z�f�j��J� �|��{�<���,�R
�N�~�p�"�D*�\�zr�"�p~�F���>�`�R2�@�f�n�r�f�}�H:�8�D��a��@�r�d�L�B�Z�P�@��6�4p�r�:�Nx�X�4�D&�:�f�N�D�,�2���t�j�&���X�p�<��h�n�n�T�P� ��x�B�@���j�=�>�
�"����#�x�"��a���R�*��|�d��L��\��F��K�h���T��T��u�|�l�b�R��^�!�J��^�d�$v��'�J������*�j��.�a��b�X�p��.�&�v��F���R�ZR�V�T�l�Z�F��b�J�>�x�J���t�N��n���L��JZ��W�T�@�*��Q�l���.��s�n�J��8�p�<�d�L�&�R�*�|�6��9�F�(� �f�N��`�2�T�6��z�V�L�^�r~�H���r�&�d�T�F�V�TZ�N��Y��N�x�>�>�L�(���I�>�.�@�4�n�@�`�>�*��?������
���'�v�*�2�`�
�^�j��o�B�P�V�^�,��^��4�8�&�l�F�t��B�8�f�~�p��8PD��W��6�R�P�n���:�t��,��
�n�4�\�8�<�8�d�h�:��+��N�V�I�@�T�J�R�J��� �^�Z�J�0�(�(p� �f���4�,��y����8�,�n�H�*�(4��.��W�2����f��Z��/�4�j�6�T2��������_�D�t��t�0�*�r�V�L�j�J�*�$��}�a�6�4�`��9�6�~�h�
�
�\�L��r�*>�X�p\�zf�d�
�h�d�X�R�<���z��N���O��f�#�X�d�d�^�D��j�<�q�A��
�+��2�^�l��D��>�<�>��`V��c�L�`�n�6���b�FZ�d*�<�@��O�h�>�B�"�f��(��2��W����J�h�X��-�0��M�v�:�r��;�F�r�j�T�t�R�~�Z�^�
�b�R���U�8�'�d�m����\��_�,�H�l�<�P��)�
�d�T�*�p���<�(�P��r�4���6��8��k�H�Z�*�D��x�T�6�Z��z��j�Z�v��g�:�x��4�
�x�T�0����<�G�4�"���F��`�2�^�*��a�@�T�\��5���b�
�~�@��F�>�V�G����x�8���k�j��|N�p�l���"��'�x�l�f��~�$�*�F�����V� �\�� �4��p�<�$�&���\P�f�h��#��X�P�~�
�P�>�
�~� ��|����:���F�$�:�
��e�\��v�x�&�������~�*P�t�n�<����X��c�� �P�p�N��B�B�\�2���l��	�L��.��>��E�*H�F�J���l�t�4��hߖP���C��"���Z��TL�`�r�D��j�4���c�~���"�~^���u�L�H�n� �4��*�&��P��D��4��A�^�:�>����(�\�~�V�*�R��h�4�T���u�r�E�B�,�n���C�J�8�n�\�R�����
���v�*�����,����D�X���T�T�z��+�>�b�"�D��~�0h��TF�j<��&�4��L�J��=�D����k�Y�f�R�|��t�~��4�X�@���6��r�,�>��l��<�D�,�~�@�.�|�L�2����u�P����J�`�H�6��{��R�0H��n�b�"��8��z�J��B�6��f�d�(h�
�d��Z�&����&� �(��d��&�H��)���@��;�h�N�������8�h���&�
�h�,�Z�X�f�$�\��K�j�p�h�`��.`�d�z���L���l��zk��g�B�j�*�(�N�n�
�y�Z�
���.�v�H�v��U�*�X����5�6�6�H�Pr6���i��6�:�R�0�R��T�r�J�T��r�v�j��)�\�8��.b�D�\�8�0�"�~��r�~�&�j�>�&�6����\�9�(��0��-�.�,�Tx��*�Zp�h�p��.��=�R��fZ�v�r��`�P�l�N��U.�`�R�j��V�h�D���>���8�
�L��Q��N�Q�F�`�4�z�R�V�@��[�P� �4�����R�R��c�\�"�Z��0��y��h�x��%�j����m8��n��.�d��e��z��B�<�8���0�X��d�sr��~�0�b�z���q�N�����h��%Xb��|�E��~� ((������� [��@�9�j�q	�	����[��B��+��B��K���u�K���B�z��5��X��[� Z��������4�������	 i1 އ�~��~��~��~��~��~��~��~� r[10]��~��~��~��~��~��~��~��~��~����~� }���p�q�	 �1 އ�~��~��~��~��~��~��~��~� �[10]��~��~��~��~��~��~��~��~��~����~� �(A)��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~�	 �1 އ�~��~��~��~��~��~��~��~� �[10]��~��~��~��~��~��~��~��~��~����~�	 �1 އ�~��~��~��~��~��~��~��~� �[10]��~��~��~��~��~��~��~��~��~����~�!"��	%�1F�6�
!:0 އ�~��~��~��~��~��~��~��~��~�4!NA)��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~�!�!����"�!�(27)��~��~����~�!�  �!�    ���!�      �	!���	��.��1��.��1�n�!�%� ����~����!�&�!�%� �������~������	!�'	�OG�G�
�`�!�(21)��~��~��~��~��~�!�&B�3��We�?�T2��p�<�}�"� ݁"#�e��Z��q��b�O�"'"�8�	"+"��w�\��K���V��E�"92��E�"<!?��~�"?"Č��7�
74/%�H�/_�"S%��L�"��
"\(21)��~��~��~��~��~��~��~��~����~�"f!�x�~:�S��S		
d[	��"�A.��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~��~����!������~�縀�������~��������~�~�Ȁ�����~������~����~�ˀ����~��~�����؀���~���~��Ȁ��������~�Lj����~������~����~�������~�����������~����~����������~������������~��؀������~�È���~������~���������~�����Ѐ�����~���~���~��~���~�����|������~��~ɿ�~ɿ�~��~ɿ�~��~��~��~��Ѐ�������~�������~��������~�~�Ȁ�����~�������~ק�~��~��������~���з�~���������#W(21)��~��~��~��~��~�#]�p ݃����������ϗ�~�����ԧ�~�~���~���~��~���~��~��~���~���~���~��~��������~�������~���~�������~�������~�#x2��#yS� ݕ����~������~�����������~�������~�����~�����~���#�2��0#�N� ݍ��~��~��~�Ȁ��~���~���~���ɿ�~��~����~������~ɿ�~ɿ�~��~ɿ�~��~��~��~��~���������~���������~���~���~��~���~��~��~���~���~��~���~���~������������~����ɿ�~��~ɿ�~���~�#�(27)��~��~����~�#�'��w�f�$
�� ݃���~������~���������~�����~��~��~��~��~��~��~��~�$[10]��~��~��~��~��~��~��~��~��~����~�
$%N ވ�~�������~������~�Ѐ���~��Ο�~�
$/[SAN]��~�������~������~�Ѐ���~��������
$9N ވ�~�������~������~�Ѐ���~��Ο�~�
$C[SAN]��~�������~������~�Ѐ���~��������$Me� ����~����~ø������~�����$T2��$[e� ����~����~ø������~������$f!�����x�O�-��@��A��~��k��*	��**:#X6	6$
 (j26A�"+n%X
6;�
:9�p�f�x-j
 8�68�E�"62r�J6=�
$;�B�/nH�"!X,j66C�6��T6�,n26:.j6266G�"22I�&22+j
&>�2>FH�"2
&�
�<*2=�26P
r�Z�P2)\	 6v���R6�3�66X$6P
?�"62JH62,ja�la�^�v�.2�$6
F $
H(.:222;�"2( j
6/r2;�2,j(jG�",n&X
6G�&b�^�y�*7�6	,^$6D�"G�",j H&X)j,j22F�&02-nw�D�"[�Z26A�6E�"F�-jk�+n
6H�&6*j6d�Z$b�^20n22A�22)n6622:@�"6'je���3�O��p�^�c�E�!^ 0 %   0	0"d 2��!�!�"j"+"�"�"'&`%�%�
  &i	:� �=211)1e	!p	!`���ñ/%%!%&%)%-%1%5%9%=%@%C3���7Q3�	3�3�3�3�	3�3�3�3�3�3�3�3�2`$�$`!S![2$�$t� �R0AU0�6�����,�/�8�p�w������������������������������,�4�D�l�s�{����������������0�7�@�E�L�\�_�h�x�{������������,�<�A�H�\�d���������������D�S�|������������������������#�4�7�@�P�T�x������������h�t��������������������������T�[�^�d�p�������������(�P�`���������$�'�0�@�C�K�\�_���������������������(�/�8�T�p�|���������������������$�4�7�\�l	�������<�X�h�t��������������������������8�H�M�S�d�g�p�����������4�;�D�O���������$�0�@�C�L������������������������H�X����������	���D�T�Y�`�p�s�{����������������@�P��
�h��������������������������#�&�,�/�8�H�K�T�d�g����������������(�1�P�l�|ˆ¤¬´������������$�(�h�x�|���������<�d�t����(�8�D�H�L�S�W�]�`�p�s�|ŌŗŴŸŻ��������������$�,�3�@�P�S�\�xƔƤưƸ������������� �0�<�L�Q�t�|ǃLJǐǖǠǣǬǼǿ�������,�H�L�pȀȅȋȼ��������P�`Ɉɘ�������������������L�\�_ʼ����� �H�X�x���!�'�8�;�D�T�W̴̨̘̫����	�l͔ͤ	�� �0�X�_�h�t΄ΐΠΣά����������,�/�T�d�p	�����4�D�l�|Фд��������������0�@�C�L�\ѠѰ�,�<ҀҐҸҿ�����������(�8�;�D�|ӌӏӘӨ��������x�Ԉ��<�L�X�h�tՄՇ����������8�H�T�p֌
�����(�4�P�V�`�|׈טg�M�Q��~"gyG��>_�FdxF}
e�h��
v}e\�#�P>kփ$���_z�|��X^|aias6�laX�.���hx�fC�q�00	0���[] 	00
0((00  �< }")0!
00(00/	'�$$�>"�"�"p"v"�$`V09	'v$"%"f"�"n-$�!�!�R!ā$��!�k2�0!������!�%�2�:�=	�A�N�U�Y�]�r�u�{���������������������������������������	%%����!�*�.�6�9�=�%O�J�Q�U�Y�d�n�q�w�
%v������
�������������ɡ'%�����������������*�2�5�;�G�Q�W�b�f�j�m�q�~��.����������
����������	���	���.�5�&��D�J
�Q�^�f�w�z�������������������'F����
�������������
��	��)�F�'��O�Z�^�~��������������������������!���
���&�)�-�(V�:�B�E�I�R�V�Y�]�a�z�}�(���������
��"��������������������	�
��)��!�5�=�Y�]�a	�j�v�}�����������)z�������������������������	�6��Y�`�l�r�u�y�	*������
�����������ɡ*U�ѱ	*\��������������!�*��,�5�R�U�Y�	*��f�m���������������+��������������������������+�2�5�9�F�N�Q�U=�b�������������ű+���
����"������&�-�5�I�e2�i��������!����������-���!��*�-�1�<�E�I�M	�V�a�e�i�v�~����������������
��!����������
�
���&�)�-�:�A�E�H�T�^�a�e�r�y�}�������������������������������!�>�A�E�M�R�Z�]�a�n�v�y�}������������������������������	!��:�=�C�O�V�Y�]�j�r�u	�y����������������!����������!�,�9�?�J�Q�U�Y�0n�d�m������������������������#�������0����� �*�.�2�5�9�F�J�N�Q%�^�����������������������������������1��
���%�-�A�J�M�Q�Z�b�e�i�������2'���2+��
����������������������2�����#� �F�I�O�\�b�e�k�v�~�����2���
��&���������������
�
�
3c!��B�E�I�R=�V�������������ݱ
4Y���=�R�Y�]�a%�j�������#4������������������������������	���!�(�2�:�=�A�N�V�Y�]�j�q�u�y�������5t����������������������������5�����������!�5�I�R�U�Z�a�f�n�q�u‚Š‘™ž¦©®!º��������6{���
�����&�*!�F�j�m�
6��s�z�~ÅÉÍ"Ý����ݱ7?���������	��%�-�1�5	�>�I�f�i�m�v�zāĕĝĹĽ)Ŀ������	86�	��
��
��*�-�1�58j�>�F�O�Z�b�e�i�v�~ŁŅňŒřŝš	ŪŶſ�������������	�
���&�)�1�<�B�E�I�V�^
�a�m�r�z�}ƁƎƖƙƝƪƲƵƻ�9d����������������������	�
�&9���"�%�)�8�>�A�E�Y�]�a�i�l�v�y�NjǒǛǧǮDZǵ����������������������!�%�	:\�2�9�=�A�J�N�U�r�u�<:��{ȈȎȕȢȩȾ�������������������-�5�R�U�Y�d�m�q�u
�}Ɋɍɑ!ɞ�����������������������*	�B�N�Q�U�b�i�~ʅ"ʙʾ��ű<|����
������	����"�B�J�M�Q�Z�^�e�l!�z˝˹����=v!�������#�*�1�?�F�I�M�Z�a�i�q �v̶̡̝̮̹̽̚�
>"��������������
�
��>h��%�)�-!�:�]�a�e�r�y
͉͖͙ͪ͝ͱ������������	?(���	�
���"�%�)�?S!�6�Z�]�b�n�v�y�}ΊΒΕΙΦή!������������	���!�%�2�9�V�Y�]�@F�j�r�u�yρφύϢϩϱ��������@��������.�6�9�=�A�
A�J�Q�U�Y
�a�n�q�u�~!ЂЦЩЭ�Abк�����������������!��2�5�;�F�N�Q�U�
A��b�i�m�}тхщѢѥѩ�B*Ѷѽ����
��
��.�1�5�B��B�I�]�e҂҅҉ҒҖҝҡҥҪҭҲҺҽ�3C����������	����������"�&�*�-�1�>5�F�~ӁӅӒӚӝӡӮӵӹӽ������������������	!��A�E�]�a�e�p�z�}ԃ�D~Ԏԕ!Ԫ������������������ D����	�

��#�>�A�E�R�Z�]�a�f�n�v�y�}ՊՑ!զ������������������	�	E����!�%	�.�:�=�A�F�E��N�R�V�Y	�]�j�m�r
�uֆ֎ֱּ֑֢֭֕֩���������ձ
FT������������������!�F��.�6�9�=�E�J�R�Z�
F��f�j�m�q�u�~ׂ׊׍ב�F�מ I5] PK
!<Lv�M>>)chrome/pdfjs/content/web/cmaps/B5-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@��> �P` ^�Pa�s�@c�/!�"T�"�x�w�yA>�"]�?A>�"�\�w�>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"]�#A>�"]�@A>�"]�]A>�;"\�z�A>�W"]�A>�t"]�3A>�".�P.�A>�/"]�nA>�L"]�A>�i"]�(A>�"]�EA>�#"]�bA>�@"]�A>�]"]�A>�z"]�9A>�"]�VA>�4"]�sA>�Q"]�A>�n"&�-6�UA>�"]�KA>�)"]�hA�F�T+�X"]�A>�b"]�!A>�"]�>A>�"	�[S�fA>�:")�y�e2�#A �V�x"��/�.C�0A�t�w'�
"]�2A>�"]�OA>�-�A	�l�t �v�"�+�-�H�wA>�	"U�H�v�A>�%"]�dA>�B"]�A>�_"]�A>�|"]�;A>�"]�XA>�6"]�uA>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"*�#�T1�OA9��9�;"]�?A>�"]�\A>�:"]�yA>�W"=��N�UA>�t"�3W�:A>�"]�QA>�/"Z�n��IA>�K"]�
A>�h"]�'A>�"]�DA>�""]�aA>�?"]�~A>�\"]�A>�y"]�8A>�"]�UA>�3"]�rA>�P"]�A>�m"�,[�/A5��B"]�KA>�)"]�hA�F#�b"O��.�VA>�c"<�"�A�_A>�"�>T�HA>�"I�\��&A>�9"]�xA�V�G'�l")��a2�>A"�q��`�"]�0A'��o�6"!�L;�oA4�+	�a"]�kA%�I�p"�	�nF��eA>�g"]�&A"��d�'"]�BA>� "]�_A>�="]�|A6�Z�""���=�I
�>�JPK
!<J#nŎ�)chrome/pdfjs/content/web/cmaps/B5-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�B5-Ha�K�N�/�1���
�����h�OPK
!<%��KK+chrome/pdfjs/content/web/cmaps/B5pc-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@��>� ` ^=a�t�@c�/!�"T�"�x�w�yA>�"]�?A>�"�\�w �2_>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"]�#A>�"]�@A>�"]�]A>�;"\�z�A>�W"]�A>�t"]�3A>�".�P.�A>�/"]�nA>�L"]�A>�i"]�(A>�"]�EA>�#"]�bA>�@"]�A>�]"]�A>�z"]�9A>�"]�VA>�4"]�sA>�Q"]�A>�n"&�-6�UA>�"]�KA>�)"]�hA�F�T+�X"]�A>�b"]�!A>�"]�>A>�"	�[S�fA>�:")�y�e2�#A �V�x"��/�.C�0A�t�w'�
"]�2A>�"]�OA>�-�A	�l�t �v�"�+�-�H�wA>�	"U�H�v�A>�%"]�dA>�B"]�A>�_"]�A>�|"]�;A>�"]�XA>�6"]�uA>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"*�#�T1�OA9��9�;"]�?A>�"]�\A>�:"]�yA>�W"=��N�UA>�t"�3W�:A>�"]�QA>�/"Z�n��IA>�K"]�
A>�h"]�'A>�"]�DA>�""]�aA>�?"]�~A>�\"]�A>�y"]�8A>�"]�UA>�3"]�rA>�P"]�A>�m"�,[�/A5��B"]�KA>�)"]�hA�F#�b"O��.�VA>�c"<�"�A�_A>�"�>T�HA>�"I�\��&A>�9"]�xA�V�G'�l")��a2�>A"�q��`�"]�0A'��o�6"!�L;�oA4�+	�a"]�kA%�I�p"�	�nF��eA>�g"]�&A"��d�'"]�BA>� "]�_A>�="]�|A6�Z�""���=�I
�>�J`�`PK
!<9k,���+chrome/pdfjs/content/web/cmaps/B5pc-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�B5pc-Ha�K�N�/�1���
�����h�OPK
!<��L��.chrome/pdfjs/content/web/cmaps/CNS-EUC-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�������]��"��]��"��]����] �P` ^�Pc����]c�"]�A�"-��R]�M�"K�+�w�0�zs2�����S��T��Z��[��_�n�`�b� �c�o�!�d�"�p�#�f�y�u�z�$�|�%��v��&���'��*�w�P�U�,�V�-�Z�
�a��e�
�j�Vs�����W�^�b�.�c�2�k�v��z�~�������g��0�� ��1�
�&�us
�����3�x�4�y�a�;�`��{��	�2�
c�
��¡ �2�_]�S�"]�1�"]��"]�m�"]�K�"]�)�"]��"]�e�"]�C�"]�!�"]��"]�]�"]�;�"]��"]�w�"]�U�"]�3�"]��"]�o�"]�M�"]�+�"]�	�"]�g�"]�E�"]�#�"]��"]�_�"]�=�"]��"]�y�"]�W�"]�5�"]��"]�q�"]�O�"]�-�"]��"]�i�"]�G�"]�%�"]��"]�a�"]�?�"]��"]�{�"]�Y�"]�7�"]��"]�s�"]�Q�"]�/�"]�
�"]�k�"]�I�"]�'�"]��"]�c�"*�A��U]�l�"]�J�"]�(�"]��"]�d�"]�B�"]� �"]�~�"]�\�"]�:�"]��"]�v�"]�T�"]�2�"]��"]�n�"]�L�"]�*�"]��"]�f�"]�D�"]�"�"]��"]�^�"]�<�"]��"]�x�"]�V�"]�4�"]��"]�p�"]�N�"]�,�"]�
�"]�h�"]�F�"]�$�"]��"]�`�"]�>�"]��"]�z�"]�X�"]�6�"]��"]�r�"]�P�"]�.�"]��"]�j�"]�H�"]�&�"]��"]�b�"]�@�"]��"]�|�"]�Z�"]�8�"]��"]�t�"]�R�"]�0�"]��"]�l�"]�J�"]�(�"]��"]�d�"]�B�"]� �"]�~�"]�\�"]�:�"]��"]�v�"]�T�"]�2�"]��"]�n�"]�L�"#�*a��]c�"]�A�"-��R]�M�"K�+�w�0�zq2���S��T��Z��[��_�n�`�b� �c�o�!�d�"�p�#�f�y�u�z�$�|�%��v��&���'��*�w�P�U�,�V�-�Z�
�a��e�
�j�Vq���W�^�b�.�c�2�k�v��z�~�������g��0�� ��1�
�&�uq
���3�x�4�y�a�;�`��{��	�2�
a;¡ �2�_]�S�"]�1�"]��"]�m�"]�K�"]�)�"]��"]�e�"]�C�"]�!�"]��"]�]�"]�;�"]��"]�w�"]�U�"]�3�"]��"]�o�"]�M�"]�+�"]�	�"]�g�"]�E�"]�#�"]��"]�_�"]�=�"]��"]�y�"]�W�"]�5�"]��"]�q�"]�O�"]�-�"]��"]�i�"]�G�"]�%�"]��"]�a�"]�?�"]��"]�{�"]�Y�"]�7�"]��"]�s�"]�Q�"]�/�"]�
�"]�k�"]�I�"]�'�"]��"]�c�"*�APK
!<�D��.chrome/pdfjs/content/web/cmaps/CNS-EUC-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�������]��"��]��"��]����] �P` ^�Ps����
c�N
o|~~�����
�
���������"�c����"�A�O9�e�"-��R]�M�"K�+�w�0�zs2�����S��T��Z��[��_�n�`�b� �c�o�!�d�"�p�#�f�y�u�z�$�|�%��v��&���'��*�w�P�U�,�V�-�Z�
�a��e�
�j�Vs�����W�^�b�.�c�2�k�v��z�~�������g��0�� ��1�
�&�us
�����3�x�4�y�a�;�`��{��	�2�
c�
��¡ �2�_]�S�"]�1�"]��"]�m�"]�K�"]�)�"]��"]�e�"]�C�"]�!�"]��"]�]�"]�;�"]��"]�w�"]�U�"]�3�"]��"]�o�"]�M�"]�+�"]�	�"]�g�"]�E�"]�#�"]��"]�_�"]�=�"]��"]�y�"]�W�"]�5�"]��"]�q�"]�O�"]�-�"]��"]�i�"]�G�"]�%�"]��"]�a�"]�?�"]��"]�{�"]�Y�"]�7�"]��"]�s�"]�Q�"]�/�"]�
�"]�k�"]�I�"]�'�"]��"]�c�"*�A��U]�l�"]�J�"]�(�"]��"]�d�"]�B�"]� �"]�~�"]�\�"]�:�"]��"]�v�"]�T�"]�2�"]��"]�n�"]�L�"]�*�"]��"]�f�"]�D�"]�"�"]��"]�^�"]�<�"]��"]�x�"]�V�"]�4�"]��"]�p�"]�N�"]�,�"]�
�"]�h�"]�F�"]�$�"]��"]�`�"]�>�"]��"]�z�"]�X�"]�6�"]��"]�r�"]�P�"]�.�"]��"]�j�"]�H�"]�&�"]��"]�b�"]�@�"]��"]�|�"]�Z�"]�8�"]��"]�t�"]�R�"]�0�"]��"]�l�"]�J�"]�(�"]��"]�d�"]�B�"]� �"]�~�"]�\�"]�:�"]��"]�v�"]�T�"]�2�"]��"]�n�"]�L�"#�*q��
c�N
o|~~�����
�
���������"�a��"�A�O9�e�"-��R]�M�"K�+�w�0�zq2���S��T��Z��[��_�n�`�b� �c�o�!�d�"�p�#�f�y�u�z�$�|�%��v��&���'��*�w�P�U�,�V�-�Z�
�a��e�
�j�Vq���W�^�b�.�c�2�k�v��z�~�������g��0�� ��1�
�&�uq
���3�x�4�y�a�;�`��{��	�2�
a;¡ �2�_]�S�"]�1�"]��"]�m�"]�K�"]�)�"]��"]�e�"]�C�"]�!�"]��"]�]�"]�;�"]��"]�w�"]�U�"]�3�"]��"]�o�"]�M�"]�+�"]�	�"]�g�"]�E�"]�#�"]��"]�_�"]�=�"]��"]�y�"]�W�"]�5�"]��"]�q�"]�O�"]�-�"]��"]�i�"]�G�"]�%�"]��"]�a�"]�?�"]��"]�{�"]�Y�"]�7�"]��"]�s�"]�Q�"]�/�"]�
�"]�k�"]�I�"]�'�"]��"]�c�"*�APK
!<����+chrome/pdfjs/content/web/cmaps/CNS1-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE!!��]a!!]c�"]�A�"-��R]�M�"K�+�w�0�zq2'!�S��T��Z��[��_�n�`�b� �c�o�!�d�"�p�#�f�y�u�z�$�|�%��v��&���'��*�w�P�U�,�V�-�Z�
�a��e�
�j�Vq(!�W�^�b�.�c�2�k�v��z�~�������g��0�� ��1�
�&�uq
)!�3�x�4�y�a�;�`��{��	�2�
a;B! �2�_]�S�"]�1�"]��"]�m�"]�K�"]�)�"]��"]�e�"]�C�"]�!�"]��"]�]�"]�;�"]��"]�w�"]�U�"]�3�"]��"]�o�"]�M�"]�+�"]�	�"]�g�"]�E�"]�#�"]��"]�_�"]�=�"]��"]�y�"]�W�"]�5�"]��"]�q�"]�O�"]�-�"]��"]�i�"]�G�"]�%�"]��"]�a�"]�?�"]��"]�{�"]�Y�"]�7�"]��"]�s�"]�Q�"]�/�"]�
�"]�k�"]�I�"]�'�"]��"]�c�"*�APK
!<�[P��+chrome/pdfjs/content/web/cmaps/CNS1-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�CNS1-Ha!,�N|~���
������h�OPK
!<(TY���+chrome/pdfjs/content/web/cmaps/CNS2-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE!!��]aR!!]�l�"]�J�"]�(�"]��"]�d�"]�B�"]� �"]�~�"]�\�"]�:�"]��"]�v�"]�T�"]�2�"]��"]�n�"]�L�"]�*�"]��"]�f�"]�D�"]�"�"]��"]�^�"]�<�"]��"]�x�"]�V�"]�4�"]��"]�p�"]�N�"]�,�"]�
�"]�h�"]�F�"]�$�"]��"]�`�"]�>�"]��"]�z�"]�X�"]�6�"]��"]�r�"]�P�"]�.�"]��"]�j�"]�H�"]�&�"]��"]�b�"]�@�"]��"]�|�"]�Z�"]�8�"]��"]�t�"]�R�"]�0�"]��"]�l�"]�J�"]�(�"]��"]�d�"]�B�"]� �"]�~�"]�\�"]�:�"]��"]�v�"]�T�"]�2�"]��"]�n�"]�L�"#�*PK
!<����]]+chrome/pdfjs/content/web/cmaps/CNS2-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�CNS2-HPK
!<2W�jJJ.chrome/pdfjs/content/web/cmaps/ETHK-B5-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@��> A�~�!�2A
�i�UA*�t�w���ss�,2�}n�pn��$�U�B��WA�o�h�y�M�+�P���m�xH�1�A�
�C��6�qST���
� �m	A�t�s�r�s�t�s
E��Y�X�w�v�q�p�o�n)
�E�D�C�B�SX�~���������� �!V;�y�Bxwc�8!�o78�x�y(26`J�<$�	�*	a����6���c
�b�3W�2�Y�^
�+��,"��%{�c�bZ��uA"Z2Q�f}�7�6i�/��2�o(Q	����J�>���?�]��e�dA���_��s�d��M�m�~�u���K���
�V�a�E�h��;�8�GA�x�%e�\�W\�t�AY���w�"�r�M�	��"�x� �m��o��8�^�8�<�@�':��E�(�M�T��15P}�jm�FA�h�$�'X�q��N���os�l�S�-�H�d�W��d�R�%A�v�P�8J�){�((�v�t�L���G�l�$��$�j�t�M��|�|��L��-�j�`�XVA�f�Eq�p�
�[q�8KAr��a	F	)'U

3	J	"
T

	
&#6-DH+��"+	
(>�A�*�`�j��&��0r�d�e�#A)�ҁ�MCHI?/.
iv&o�z 	-
	{+�_:�-�S�Z ?V�u���{�3A��Á�q 	FZ^=dc<"	/"uR$$	
H
	,
OE6
)F%
G'D
F

&
�|�\�a�14
|	V
Z xz
` ^a�Z����c!�"T�"�yA>�"]�?A>�"�\�w�>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"]�#A>�"]�@A>�"]�]A>�;"\�zB>�W"]�A>�t"]�3A>�".�P.�A>�/"]�nA>�L"]�A>�i"]�(A>�"]�EA>�#"]�bA>�@"]�A>�]"]�A>�z"]�9A>�"]�VA>�4"]�sA>�Q"]�A>�n"&�-6�UA>�"]�KA>�)"]�hA�F+�X"]�A>�b"]�!A>�"]�>A>�"	�[S�fA>�:")�y2�#A �V�x"�C�0A�t'�
"]�2A>�"]�OA>�-"�z��h	�l �v�"�+-�H�wA>�	"U�H�A>�%"]�dA>�B"]�A>�_"]�A>�|"]�;A>�"]�XA>�6"]�uA>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"*�#1�OA9��;"]�?A>�"]�\A>�:"]�yA>�W"=��UA>�t"�3W�:A>�"]�QA>�/"Z�n�IA>�K"]�
A>�h"]�'A>�"]�DA>�""]�aA>�?"]�~A>�\"]�A>�y"]�8A>�"]�UA>�3"]�rA>�P"]�A>�m"�,[�/A5��B"]�KA>�)"]�hA�F#�b"O��VA>�c"<�"�_A>�"�>T�HA>�"I�\�&A>�9"]�xA�V'�l")�2�>A"�q��"]�0A'��6"!�L;�oA4�+	�a"]�kA%�I�p"�	F��eA>�g"]�&A"��'"]�BA>� "]�_A>�="]�|A6�Z�""�
�>�Ja�����%W	������#��'��,��:��@'��G��O��R��X��a��o��t��~��[��
���o�+"�0�V�Y�_�d#�f�k�p�y�}�
���(�1�8A�@�Y�^�g	�l�w"�|��!�&�3�<D�V�l
�s	��
"��"�'�K�`A$�m��%�*�G5�PA���+�4"
�E�S�i�r��A'��E"�[�d!�{�� �"A�50�<�n"(�s2�A�Q�^�j�o"$��3$�FA�k�p�}�"2�*�]
�v�A>�"]�FA�$(�*�TH�_�c��N��U�c����/�#�%�+�4
�=�A�I�M�Q�SC�U�X�]�e�m�s�w�{$�~��	����!�$	�'�1�4�6R�FW�P�U�\�^�b�e�j!�m�A���1�4�:(�?�A�F�H�L�P�R�T�Z�]�_�u�w�zE������#
�*"�8�L�Q�U�`�h
�l�w�B���#�1�:�D�G$�K�O	�l�v�{����	r�/�{�3�:A>�Y"]�A>�v"2�5�(�hA����+��2"��J��T��g��o(��A��(	��1��<��W"��g��j����$����8��>A	��C��M��Q��S��c��m��t"��}��
����'	��=��G��JA��W��`��"����-&��0��X��fA��r����!��)"��/1��8��k��xa�:�@%����;��">��`��I(��a"��
�����4��7��<,��?"��l
��s
��v��	��������
��#��1�W��B��E��RD��\��b��e��i��n��t"��
��_��|B!��!��C"��_��d������P��XC��r��)��+��0��3��:��>"��@��D��F��R5��^��l���o��/�e��N/��Q��S��U"��Y
��[��f��n	��s��}������	������D���� ��'��+��2��9��>��A��G��I(��R��Y��\��_��b��q��s��x��z������
	��A	����&��2��8��>��F#��N��Q��T��[��b��k��t��z������������A��������'

��.��<��>*��B��E��G��I	��N��V��X��\	��a��d��hL��m��r��z.����������"��D��������#��'��)��0��C#��G	��M��O��S��X��Z��]	��`���h��k��r0��y	��}��������	��������R�����~��0��7��:�U��F����%��-	���*��F���R� ��]
��_PK
!<h(-��.chrome/pdfjs/content/web/cmaps/ETHK-B5-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�	ETHK-B5-Ha�]���
�����a�K�N�/�1��Oa���PK
!<ӡ0�ee.chrome/pdfjs/content/web/cmaps/ETen-B5-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@��> �P` ^�Pa�}�@c�/!�"T�"�x�w�yA>�"]�?A>�"�\�w�>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"]�#A>�"]�@A>�"]�]A>�;"\�z�A>�W"]�A>�t"]�3A>�".�P.�A>�/"]�nA>�L"]�A>�i"]�(A>�"]�EA>�#"]�bA>�@"]�A>�]"]�A>�z"]�9A>�"]�VA>�4"]�sA>�Q"]�A>�n"&�-6�UA>�"]�KA>�)"]�hA�F�T+�X"]�A>�b"]�!A>�"]�>A>�"	�[S�fA>�:")�y�e2�#A �V�x"��/�.C�0A�t�w'�
"]�2A>�"]�OA>�-"�z��3��:A>�Y"]�A>�v"2�5l	�l�t �v�"�+�-�H�wA>�	"U�H�v�A>�%"]�dA>�B"]�A>�_"]�A>�|"]�;A>�"]�XA>�6"]�uA>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"*�#�T1�OA9��9�;"]�?A>�"]�\A>�:"]�yA>�W"=��N�UA>�t"�3W�:A>�"]�QA>�/"Z�n��IA>�K"]�
A>�h"]�'A>�"]�DA>�""]�aA>�?"]�~A>�\"]�A>�y"]�8A>�"]�UA>�3"]�rA>�P"]�A>�m"�,[�/A5��B"]�KA>�)"]�hA�F#�b"O��.�VA>�c"<�"�A�_A>�"�>T�HA>�"I�\��&A>�9"]�xA�V�G'�l")��a2�>A"�q��`�"]�0A'��o�6"!�L;�oA4�+	�a"]�kA%�I�p"�	�nF��eA>�g"]�&A"��d�'"]�BA>� "]�_A>�="]�|A6�Z�""���=�I
�>�J(�hPK
!<7S-E��.chrome/pdfjs/content/web/cmaps/ETen-B5-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�	ETen-B5-Ha�]���
�����a�K�N�/�1��Oa���PK
!<�bt�ee0chrome/pdfjs/content/web/cmaps/ETenms-B5-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�	ETen-B5-H` ^PK
!<�T�ݬ�0chrome/pdfjs/content/web/cmaps/ETenms-B5-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�ETenms-B5-HA�K�N��C	��}��ha�]���
������"��
�?�PK
!<�wBB*chrome/pdfjs/content/web/cmaps/EUC-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE���?�A��] �g` ^�ga��?�F�A]�y�"
�W�e�m�t���1	���0�&R�J�-U��*�s��H �# �DQ ���7	w	WE$$aF��]�e�"]�C�"]�!�"]��"]�]�"]�;�"]��"]�w�"]�U�"]�3�"]��"]�o�"]�M�"]�+�"]�	�"]�g�"]�E�"]�#�"]��"]�_�"]�=�"]��"]�y�"]�W�"]�5�"]��"]�q�"]�O�"]�-�"]��"]�i�"2�G�M]�z�"]�X�"]�6�"]��"]�r�"]�P�"]�.�"]��"]�j�"]�H�"]�&�"]��"]�b�"]�@�"]��"]�|�"]�Z�"]�8�"]��"]�t�"]�R�"]�0�"]��"]�l�"]�J�"]�(�"]��"]�d�"]�B�"]� �"]�~�"]�\�"]�:�"]��"]�v�"]�T�"�2�\PK
!<��ͪ�*chrome/pdfjs/content/web/cmaps/EUC-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�EUC-Ha���O
�Q	�S�V�[A��m�?�2a���PK
!<b_��	�	*chrome/pdfjs/content/web/cmaps/Ext-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE!!��]a!!G�y�B�"
�W�	���0�&R�J�-U��*�s��H �# �D�/>�h��(�">�G��"P�&�'�w�q�z�y��a!i�6�:K�7�1��!	�8�H�Oa-_�q
0!�e�Q�g�"�x�R1��S�3�T�8�U�Aq
1!�C�V	�M�W�X�X�\�Y�`�['�e�\�q2!�!�]�I�^4�+�_�a�`�{�a�~q3!��b��a�!��(�c�D�d�J�e�R�1�U�f�Yq
4! �]�g�r��h�
� ��i�"a5!	�;�j�F�k=�[q6!��l��m�#�2�?�n�H�o�f�p�m�q�t�r�vq7!�w�s�{�t��u��v�%�w�)�x�,�
�2�y�;�z�H�{�P�|a8!�U�}�i�~<�vq9!	�3�
�>�	�J��U�]�o������a:!1���D��X�	�eq	;!�o��v�	�y�
�{�D�q
<!�M��e�
�)�v����q	=!	�+�	�6�4�A��x�
�~q
>!�	���
���(��>��H��Mq	?!�g�-�q�� ��'� �0q@!�E�!�G�""�g�#	��$��&�qA!�#�(�n�+�)�<�*�O�+�R�,�b�-�lqB!��.��/
�.�0�=�1
�D�2�P�3�W�5�^qC!�_�6�f�7�i�9
�m�;1�|�<�/�=�4�>�;q
D!�=�?	�Z�@�e�A�g�B�j�C
�l�5"�xqE!��D��E�"�F�)�G�1�H�:�I�C�J�F�K�M�_�R�M�_�P�i�6�n�N�sqF!�y�O��P��Q�.�S�5�U�:�V�@�X
�C�m�Oq
G!�W�Y�`�Z�p�[��\�	�]� �^�$q
H!�5�_�9�`�C�a"�E�b�i�c�w�d�
qI!��e
��g�;�#�h�%�i	�(�j�3�k�A�l,�Dq	J!!�q�m��n��o�+�p�Jq
K!�O�q�Q�r �X�s�z�3��t��/�(qL!+�-�u
�Z�v�f�w�l�x�o�5�y��z�	�{qM!��|��}�;�~�?�4
�E��Ta
N!)�i-��D�#�H�X�]�`�M4�z�''�0aO6�waNK�.��%���q	Q!�X��u�����	0�aR!]�6qS!��
�$��.��D�
�R��_aYx�'aT!"�r��w�6�"�P$�m��"�.N�0��"]��"�jY�n�">�H
�
�� �"�&�?0�Sa
TD��x�%��>�O��1��;���L��q
[!#���)��.�"�<��O�h�Q�	�Xa^9�m;�a\!]�b�"]�@�"��7#�N�s�"Q�|
�O�"�Z7�`��0�"	�8�CL�Ia^P��"��2�8� �5�4�!�"q	b!	��$;�!�n�^�Q�`�%�eac!(�t�&��'�(�T�2q	d!�R�(�k�\�x�)��*� ae!�0�+�I�,	�K�-7�V�"$��.7�4�"B�l�/�0�0�5�1�>�"�J�2�^�37�e�4	�q	i!�(�u
�*��6�5�D� #�bq	j!���
�6�
�70�#�8�Uq	k!�d�9)�v��!�:�*�;�:alM�*al!+�B�o	�	��",� �N�l�n�"�~��&�5�"C�\�!a	li�<
�=�Y�>�?�@�:�A�B�C�
�Dqp!�:�E�M�F
�_�G�k�H�q�1�I�aq!]�q
r!�v�J
��K��w�L$�$�)	�Ja|{�oaz6�Ias!/�T�M*��N�1�"]�'�"�G��"]�b�"M�@	��EPK
!<����	�	/chrome/pdfjs/content/web/cmaps/Ext-RKSJ-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@�<�?�@�< �g` ^�ga�@>�y�8"�B�"	���0R�JN>��\�s�i �#�D�S�.>�h��(>�G�C>�&�e�'�w�q�z�y��a���6�K�7R��!
�8�H�Oa�~�q
���e�Q�g�"�x�R1��S�3�T�8�U�Aq�@�C�V	�M�W�X�X�\�Y�`�[�eq
��
��\��]�I�^4�+�_�a�`�{�a�~a�@��b��a�!��(q���>�c�D�d�J�e�R�1�U�f$�Y�g�r��h�
� ��i�"a�@	�;�j�F�k�[q���z�l��m�#�2�?�n�H�o�f�p�m�q�t�r�vq�@�w�s�{�t��u��v�%�w�)�x�,�
�2q
���6�y�;�z�H�{�P�|�U�}�i�~<�vq	�@	�3�
�>�	�J��U�]�oq
���r�����6���D��X�	�eq	�@�o��v�	�y�
�{�%�q
��5�.��e�
�)�v����a�@	�+�	�6�(�Aq���j��x��~���
���(��>��H��Ma�@�g�-�q�� q����'� �0�!�G�""�g�#	��$��&�q�@�#�(�n�+�)�<�*�O�+�R�,q���b�-0�l�.��/
�.�0�=�1
�D�2�P�3�W�5�^q	�@�_�6�f�7�i�9
�m�;!�|q����<�/�=�4�>�;�?	�Z�@�e�A�g�B�j�C
�l�5"�xq�@��D��E�"�F�)�G�1�H�:�I�C�J�F�K�M�_�Rq���Z�M�_�P�i�6�n�N"�s�O��P��Q�.�S�5�U�:�V�@�X
�C�m�Oa�@�W�Y�`�Z�p�[�q����\�	�]� �^�$�_�9�`�C�a"�E�b�i�c�w�d�
q�@��e
��g�;�#�h�%�i	�(�j�3�k�A�l
�Dq	��@�R�m��n��o�+�p�Ja�@�O�q�Q�r �X�s�zq��	��3��t��/0�(�u
�Z�v�f�w�l�x�o�5�y��z�	�{q	�@��|��}�;�~�?�4�Ea���J>�T-��DD�H�X�]�`,4�z�''�0a�U�wa���?�.�F���q	�@�X��u�����	�a��|�q	�@��
�$��.��D�
�Ra���wa��
�S5�_��6C�P!�m��N�0�C>�!�KY�nC>�Ha���6���&��O��1�q
���
��
��'� ��?�0�Sa���a�@#��)�.�"�<
�C�O�h�Qg�XC>�@6��m�7#�N�sa�d��!���9�`�?�Fa�@>�|q	��;��O�7�`� ��5�0a�@	�8�!�C�"-�Iq	�(�w�$;�!�n�^�Q�`�%�ea�@(�t�&��'�(�T�2q	�6�3�(�k�\�x�)��*� a�@�0�+�I�,	�K�-�VC�o�.7�4C>�lq
��+�/�0�0�5�1�>�2�^�37�e�4	�q	�@�(�u
�*��6�5�D� �bq	�!�g��
�6�
�70�#�8�Ua�@�d�9)�v��!q��#�:�*�;3�:�*�o�<	��=	�a�@,� �>�Nq��_�?�l�@�n�A��B��C&�5a�@>�\q���D*�!�E�M�F
�_�G�k�H�q�1�I�a�@>�q
�*�W�J
��K��w�L$�$�)	�Ja��oa��Ia
�@/�T�M
���N�1�!>�'3�fG�C>�bl�!	��EPK
!<"D����/chrome/pdfjs/content/web/cmaps/Ext-RKSJ-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
Ext-RKSJ-Ha�A�O�L�R�P�O�S�V�Y�T�W�V�[A���M�r�AS a����H��PK
!<U/����*chrome/pdfjs/content/web/cmaps/Ext-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�Ext-Ha!"�O�L�R�P�O�S�V�Y�T�W�V�[A!k�M�@�r�A�2a%u��I��PK
!<ܥ�8%%-chrome/pdfjs/content/web/cmaps/GB-EUC-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�����] �$` �$]�.aX��]`�21�>	�p�z�$]��"R�d�-U�7�*�
4�%�+ �Z �{�/�%�<�9K�b�1]�.�"��`]�,�"]�
�"]�h�"]�F�"]�$�"]��"]�`�"]�>�"]��"]�z�"]�X�"]�6�"]��"]�r�"]�P�"]�.�"]��"]�j�"]�H�"]�&�"]��"]�b�"]�@�"]��"]�|�"]�Z�"]�8�"]��"]�t�"]�R�"]�0�"]��"]�l�"]�J�"]�(�"]��"]�d�"]�B�"]� �"X�~�']�W�"]�5�"]��"]�q�"]�O�"]�-�"]��"]�i�"]�G�"]�%�"]��"]�a�"]�?�"]��"]�{�"]�Y�"]�7�"]��"]�s�"]�Q�"]�/�"]�
�"]�k�"]�I�"]�'�"]��"]�c�"]�A�"]��"]�}�"]�[�"]�9PK
!<fk1��-chrome/pdfjs/content/web/cmaps/GB-EUC-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�GB-EUC-Ha���?�>�V��W
�F>��"�B�D�=��@��C���X�T�U�PK
!<	��)chrome/pdfjs/content/web/cmaps/GB-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE!!��]aX!!]`�21�>	�p�z�$]��"R�d�-U�7�*�
4�%�+ �Z �{�/�%�<�9K�b�1]�.�"��`]�,�"]�
�"]�h�"]�F�"]�$�"]��"]�`�"]�>�"]��"]�z�"]�X�"]�6�"]��"]�r�"]�P�"]�.�"]��"]�j�"]�H�"]�&�"]��"]�b�"]�@�"]��"]�|�"]�Z�"]�8�"]��"]�t�"]�R�"]�0�"]��"]�l�"]�J�"]�(�"]��"]�d�"]�B�"]� �"X�~�']�W�"]�5�"]��"]�q�"]�O�"]�-�"]��"]�i�"]�G�"]�%�"]��"]�a�"]�?�"]��"]�{�"]�Y�"]�7�"]��"]�s�"]�Q�"]�/�"]�
�"]�k�"]�I�"]�'�"]��"]�c�"]�A�"]��"]�}�"]�[�"]�9PK
!<�7n���)chrome/pdfjs/content/web/cmaps/GB-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�GB-Ha!"�?�>�V��W
�F>��"�B�D�=��@��C���X�T�U�PK
!<M��d9d9.chrome/pdfjs/content/web/cmaps/GBK-EUC-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@��> �$` �$]�.a
�@8�X�Y���qe��r��]�
q�@��
�%�5�3�L�D��I�b�Kq��� �L��N�\�Z�!�n�e���M�'�
�>�(��0�.�4��K�l�8��9q�@�<�h�=�T�@�9�B�X�E�_�K�B�N��T�,�S
�Z��e��j�O�k��l�dq����m�V�r�P�N�t��v�v�|�1����R��W�U�Q��e
��%+�)�O�Uq	�@�]�Y�m�H�
�c���q&����
��+�%�J��(�O�1�O�6�:�4�I��5�u�7�>�R�M�_�)�`�f�a�'��b�2�d�#�p��s�u�uq
�@��E�^��F��o��P�$q���<�A�C�d�F�y�L��O�B�P�Z�Y�"K�ha�@	�4�m�>�G�G�l�Zq���p���I��p'���7�^�8�*�9�,	�;�X�@�E�o�H�!�N�y�S�r	�Xq�@�j�b�|�e�0�i�z�t�j�w�l�L�s�x�O�h�u�q�v�i�w�t�|�o��p�q4����v�s����t��5��[�r��n��7�"�u�&��+�
�.�k�9�9�:��?�}�u�B��C�{�~�L�J�[�L�j�	�N��O�!�S��=�o��pa�@�F�H�t�~�v�7�wq��9�/�*�i��z���%��,�
�.���8��s�q�@
�%�c�9�0�+�1�-�2��5�'�=�/�>�}�K�V	�N�B�Xq#��
�Z�2�e�_�r�y�v�Y�}�]�~�g	��m���&�)��R�;�(��'��+��.��I�"�8�$�	�<q�@�F�|�G�[�^�^�"�_�z�`�-�za
��c��?�cA�}���y/��|�5q���9�i�E�w�V�v�j�}�o�x�s�z�z�~�(	�{�������!��"
�q	�@�+�{	�/�Z�9�e�@�W!�Dq��
�f�e�q�/�#�!�r�T�e�s�
�7�y��&�h��Z�9��\��P�g�*�t�+�>�.��Ma�@2�R�	��7��4�q������
���*�
'�D��l�q�m��o��~�
q�@���
���3��<��i�(�U�-�
�.q���$�<�C�V�)�Y�w�]�'�_��c�
�d���r��v�D�w�2��~��s��`�q�@�+��x�.�v�1�=�E�C�x�Iq���c�u�i�u�j�n�v�0�w��z��{�|�~�E��!��� �	�&�|%�0a�@>�Vq����4�~�2�c�M��;�R�9�U�(�=�j�5�z�:�|�3�q�@�	�b��^��]��7��/�_��2�N��R��<�!�k	�"�2�,�?�3��<�>�6q���8�.�<�{�D�	�G�1�H�D�K��N�^�#�6
�[��^�i�`�l�8�o��xQ
�п~�[��0���[�Z�=�{q���}�a��#��$��s��0�a�@>�q
��5�\�Q���)�]�*�	�-�
�Z�7�
q	�@�T��d�X�p�[��6�q����[��`�+�a�2�@�3��?�_�B�^�D�j�E��M��U�_�\�,�T�_��c�@�b�eQ	���`����<�v�E�D�a���k�g�2�m�)�q�Lq�@�t�r�x���R�$����m��e��-�R�f��z��c���
�<�Q	�r�v�@�g�f�_�^�e�d�{a�{�q���Y�V�c��)�|�#�.�;�<�L�B�M��C
�V�T
�a�o�l�W�sa�@1��-�Cq���O��R�V�(�l�y��Z��'���&��N��)�'�g�,��5�<�;�\a�|�L�o�ua�V�D��j�ma�@�@$�V�{~�}A&�|�#�'�(q���8�k�_�N�N�S�r�X�s-�s�~�!�~�)q�@�0�u�5�
�8�o �=�h�^�
�_q���j�v�k�'�W�t�|��!
��*���l��>�)�L�,�:�0��2�C�@�w�A�t�J�]�Lq�@�X�h�[��\�r�|�5��x�	���
q-��������!����8�%��4��n�:�{��q�;��p�B�i�C��D�o�J��N� �P�u�X�t�]�}�a�y�c�~�f�z	�mq�@	�w�c��-�
�K��S��Y�$��3�(q���/�\�7��:��>�
�C�	��E��G��[�J�/$�P�@�u�m�z�A�}�J��g�B�a�@>�P�]�\	�.�[!�8q�@�Z�^�m�D�b�r�T�s���f�q����c��]�/�k�<�5�Z�&�^�c��t�=�x�A�y�K��q�@��$�	�H�`��w��:��1�#�A��)�8�-�v�/��2��7�J�8q-��	�9�>�C�4�E�W�H��O�a�l�V�j�Z�c��^�&�	�b��l�e�s�d�u�b�x�^�|�D
�~�6�)
�	�+��%����v��m�q!�@� ��%��P�'��/�Z�0�S�1�i�d�7�U�9�f�:�_�=�O�A�Y�C��E�l�k�F�X�n�Kq���M��R�m�S�z�\�V�h	�`��j��m�f�o�o�s�	�X�w�g<�y�#�6a�@
�?�z�M�%�mq���|�"��V��:� �n�"�U�$�(�@�W�C�|�V��_�X�a�:�f��nq�@�o�l�r�Y�t�'�v�_�z���]��<�
�Z��I��F	�q����$��4��6�f�7�C�I�L
�^�@	�l�Q�v�,�z�/�	a��]`�21�>	�p�z�$]��"R�d�-U�7�*�
4�%�+ �Z �{�/�%�<�9K�ba���a��	�)�>�3�r�*�� �"�#�Bq
�@��6�=!�&�5�H��I��L�?�*�Nq	���Q�R�k���l�"�m]�.q
�@�d�n�+�p��9�q�_�A��r�0�ta���a�F�@*�`
�\�]�%�ja�� �%��F)�L	�v����q�@� �a�)�[�=�b�?�T
�E�_	�Pq	���Z�l�\�]�`�c�c�d�ha�@�w�%��p�q	����4�B�8�N�=�1
�E�a�@#�P�F�t�.�
q
���
��	��2���+	��3�$�(�&q	�@�'�/�(�1	�)�,�3�N�7Q	�W��P�K�d���	��1q	�`�4�/�e�7�<�i�C�%�Sa���T�@�k��l]�,q
�@
�s�n�~�=���2��C+�a���-��D]�
q	�@�M�o$�N�n�s�t�x�	�~a��]�h�"]�Fa���p�
� �(�+,�(�i�e�za
����� ��&+�)
�U�c�k�n�oq
�@���H���d���f�w���k���'��1�la����9�k��<�c��A�j��J]�$q
�@��W���X�'	��a�f��k�g��l��n�h��ra�����f�\��+]�q
�@��.�"��@�]��B���E�[�8!��Fa�� ��h]�`q�@
��	�������w	�� �$�i�+��*��[��:a�����@�}��A]�>q�@��_� ���b�m��c�9��i�j��l�$�a��t�8�`��u�b��v�A��a��]��"]�za���G�M�A�d�!�3�oa���������3��C��S��l��o��q�@���$���h���c��"���$�f��*�g��3�A	��<q
����F�j��J���K�l��L�k��P�P���`]�Xq�@�w��a�

��i�i��w�n�:��y���|�e�m���	���e���#��a�� ��]�6A��6�u�|��H�$��NQ	�a��p�C�B�y�x�E�D�ya�j��XQ	�m�H��$��A��@��~��#�	�&a�v�����^���_�'��aQ
���#�����'��&��/�9�s�Pq	����e�s	��h�1�&�%� ��r]�q"�@��u��$��w�(��x�*�)��z���{�>��|�1��}�,���5���+�/���h�d���!������|�J�-���Tq���� �Q��!�3��(�/��)�.��+�0�[��,�X��1�B��4]�ra�@��9�9��<�6��=��9��@Q	�Q�E�b�K��f��C�d��dq�Z��G�_�8�2�t��J��1���K�5��O�;��Q�7�3��S�T�^��X�4��Y�=�	�<q����[�n�X��^�?�E��_�I��`�A��b�G�V��c���e���g�C�?��h�#��i�B��j�>��l]�Pq�@�&��m�N�F�K�D��y�'�L��}����G�I�H���B��
�n��
����J�\��Q	�v�;�(����L�K���
q�����:�m���R�Q	���U��&�	���'�T��+]�.q'�@�{��.�@��1�&�a���7�
�V�Y�X��8�.��:���?�z�M�W��A�S��I� ��J�_���K�n��L�$�p��O�:��P�Z��Q���Sa����U�b��p���q]�q�@��t�j���,	���b�q���r���r��$���&a����,�.
��A]�jq	�@
��L�`��Z�B��p�A��q�(��a€���
��Q
“��o�N���+�O�X���/a¡]�H�"]�&a �y��8�U7�R�G�ya���!��6�� ��W��Z��^��tq�@��y�#���E���I���������w�����*�%��,qĀ��0���1�-�H�(��@���D�E��E�J��F]�q�@��J�p��N�F��O�]�^��S�,��X�a��d�;�Y�K�I��gaŀ���a���o�����]�bq�@���p����� �x��"�"
��=�6��Kaƀ ��W]�@q	�@%��x�+���?��&�}��,�3��0aǀ ��3]�q�@��T�M��U�4��b�q��d���w�I��~�qȀ��
�F	���G������!��#�b��&]�|q
�@��)�M�H��8�`��U�E��V���\aɀ��c��5
��r�<��}]�Zq�@
���o���Q��� ���8������B��*�O��-�=��1�D�1��4�Kq
ʀ��5�;��6�P��=�4�&��A�K��C�U��J]�8q�@��P�>��S�t��T�0��U���X�Z��Z�J��b���g�	��h�?��r�t�C��qˀ���������Y���T���2��]�q�@�&�W�� �X�B�S��'�;��*�Z��6�V��9�7��<�y
��D�S��O�a��Rq
̀
��S���a�I��f�B��g�h��m]�ta͡]�Ra͐�Z�W�N�.
�2a�@>��p��/��?��F�&��N	��u��q
΀������\���l��$�&��&]�0q�@
��(�`��6�{��<�^��>�a��A�U��P�k��V�[��W�	��Y�Y��\q
π��^���_�)�V��f�]�_��n�o��w�X]�q�@��x�)���[���v���������?������
�����-aЀ ��.]�lq�@��O�q��d���g�X�,��n�'���v��aр���a�;��%]�Jq�@�>��'�]��)�=
��2�*��@�<�:��F�?��I���M�k��N���P�-��Rq
Ҁ	��[���e�2��h���k�6��m�8��n�:]�(q�@��v�<��z�d��}�9��~�=���>���?���R���}��
�;�-���	��$�
��%���&aӀ��(�_�K�
��-Q	Ӌ�w��p����
��!�� ������'qӔ��4�j��6�b�K��7�x��8�b��:�r]�q%�@��;�P��<�S��?�e��@�r��D���F�]��I�T��K�g��L���O�f��T�h��X�d��^�j��_�k��`�i�=��a�j��c�=��e�aԀ��g�v�g���i�7��kQ
Ԋ�,������w�v��t���?��n��L��}aԗ��p�p��u�n��w]�dq�@��x�m��l�b��{�~��~�y���/���6���w��Q�Z�=����
�f��$��K
��O��p��Uq�e���G���E���\������u������F���aՀ���4��QՄ�i��l��G��F��o��n��]��\��Q��P��Q�+qՐ��%�`���(�~�G��+���,�u��/]�Bq�@���0�
�S��1���4����5�_��7�	��8Q�R��}�0��8����~��g��f��!�� ��_�l�]��L���La�b��?�W��B���E���FQ
�o�����u�5��(��s�|�[��N��ka�y��L���Oqր��Q�
��T��
���V�c��[�:��`�x��d]� q�@��k���n��k��r���x�0��h��y�D��z�������	�U���J���@���S��q׀���I�����!�m�x��$�{���%���*���+X�~a�@��4�H��A�}*��Fq؀��q�;�����Y�$���:�e�p�d�0�a��]�WQ�@����K���8��3�� �����N���B��=�k�|�l��:��!�m�H�?9�+�*��6��+�q����Qa�]���2��Q���4��Q�l�3��J��u��t������O�t�i��;����%�Va�z����5�xqـ�T���s��%�)�7�(�	�G��*�.�1��/�f��1�[�D��2�]�5q
�@��3�*�1��4�=��9��\#��=���a�z��daڡ]��"]�qaڅ�n�x�Q��0�}���,�aڀ��k��p��x���
��*��A��G��K��Q��]���e��jQ	�O�|�a�j��`��M��L��a
q�X��u���z���~�#�������p��q܀���{�g�6�V���`�� ���!�
��'���*�]�Oq �@��.���4���:�������?���@�1��D���E���F�X���G���N�	�f��O�&��Y���Tq݀��X�;���Y�:�[��[�p��_� ��b�;��f���h�p�7��j�R��l�3]�-q�@�E�!��m�(��o�|�0��r�"	��w�F�������
���0����K�J�*��aހ���{��/�r��;]�q�@�~��<�	��=�;
��G�S��U�>�&���V�l��Y�>��Z�J��[�3��_���eA	�w�O�$����1��0����~��m�=q	߁��j�q��l�F��m�c�s��p]�iq�@������3
��"� ��-�G��/�<��7���8�Q��9q���>�;��E���J�-�+��K���L���M���O]�Gq
�@��X�! ��Z�U��{�
��~��$��	q�����X���z���y���4���s�x���w�H�v������E�{��#]�%q!�@��$�z��%�m
��&�}��1�y��2�~�M��3����8���:����=�]�%��@����A�z�&��C�U��K���Oq������P���V���W��1��Y����	��[��4����]�]�q"�@��a����c���f�V��i�
��l�S��m�z�N��o���q�U��t�
��w���|�*�������-���	�2���{q�	���/���,���(������/���<������5��!�
�&��"]�aq'�@�4�*��1���#�%��&�M��+�Z��,�r�>��-�O��1�C��2�W��3�<��4�)��7�!��9�	��;�?��E�D��F�#��:�E��I�=q���J�A��K�� �<�;�8��M�@��Q�M��R���U�K��Y�G�O��]]�?q)�@��^�0��d�F��e�L��g��R�0�I��i�Q�J��l�R��m�8��p�y��q�+��s�M�%��u�L���w�m��y�7��z�J�8���N���|�T��q����<���U���V��
�h���V���W�S���H��]�q�@�\���Y��!��U��%�}��(�~��+�|��.�Z��/�^��1�4	��3�^��=�`��>�"�T��GA怾A�y�cyR�+�@A�{��Iq���M� ��O�e��P�a��R�b��X�l��[�m� ��^�_�j]�{q�@��`�n��c�9�\��g�h�k�o��j�D�g��k�[��l���o�+��{�q��|�w��}�"���3��
q����*���6�s�o����x���p���u���A�]���]�Yq�@��$�z���'�w��(���*�$��+�{��.�#��2�y��7�f��8���=�|
��>���I�}��K�r��L�t��Rq��N��T�F��U���Y�v��Z�d�?��^��\]�7q
�@��m�u��y�}���?����@��Q�]�E��~��[�V����	�}���������vq�i��	�i���C����y�k���5�
��q��G�I�F���O���K���s��!�w�O�N�J�M��"�P��%�l��'�A��(�n�Q]�q�@�x��)�S��,�R�T���/�,��4�U��5�q���7�E$��9q����^���a���c���i�~�M��j���n�Y]�sq�@��w�2��x�R��{�y	��}�W���?�����
�=������%	���V���D����W���
���"a���'�P��,�C��0]�Qq�@��F�(��L�
��[���^���_�2��b���o�C��va���~�#	��]�/q�@�����$�^��5�]��:���<�_��>�
�}��?�@��E�e��J�g��L�f��Ma���RQ��I��2��5��>��t��5�	�|�S��������/��.��Ca�]�
q �@�G�A�
�/�\��h�?�4��k�I��n�H��r���t�K��u�}��{�~��}�L�C���9������Z���s�aq��M���N�v���K�Q���@������P�� �R��"�*��#�r�S��&]�kq�@�>�T�D��(�O�s��)���/�L��4���5�x��6�N��8�O��=�8��D�P��E�Q	��F�t��P���R�~��Sq���U����W�!��X�q��Y���[�"��\�K�6�5��c�-��g�S��h]�Iq"�@��j�#���k�g��l�$��d��n�8��r�%��t���v�C��x�.��~����&��
����5�'���(���u���)q��+�k�,���j�y���f���g���l�� �-	��#]�'q�@��-�m�B���?���A��i��B�W
��N�%��J��\����]A�~�Q
����4��]��\��9��8��=��<��i�8a�	��c�?��m�g��p]�q�@��x�W��}���~�����G�
������7���p��!����"�p��#���q���(�f��-�R����q��.�
��2�	��4���5�'��:�|��;]�cq�@�A���=���@�N��B����C���G�)��W�a��i�$�t�c�b��ja���o�u��{�L]�Aq�@���z	��� ���h��#�P��$���%�L��&�H��*�>��+�e��;�d��A�9a���B�J��V���Z���\]�q�@��`� ��e�!	��f�"��p�'�9�%��q�)��x�.�+
��z�*�-���(�9��q�����6�0���8���
�7���E��&�:��'�C]�}q�@��+�@��,�=�A��/�>��0�?�@��2�;��3�B��7�D��:�9��B�L��C�G�K��K�I��N�H��P�J���Qq����X�M��]�4��_�Q��`�P�R��b�O��eQ���26��(��#��"��i��h��o��n��+a��]�[q�@��l�T��n�X��t�U�M��v�[��~�Z���Y�.�/���F���&���\���,	���#���1a�� ��]�9q�@��=���?���B�\��D����J���K����Z�n
��[���f�C��k���l���mq����o���p�}��r�o��s�
�	��y�G������
q�@
���P������`������ ���(�6��)���+�v��-q����<���A���B���K���O���Pq�@��X���Z���]�L��b�D��f�`� ��l���o���p�l
��q�"���.����#��q
����
�	�%��
�$���&����'�"�)��q�@��#�(��,���4�!�*��8����9���R��{��U��t��Va����W�
��g�q��r�w��tq�@��u���y���}�Z��
�A���3���j���
�� �i��"���$a����*���-�s��3q�@��I�F�k��[�S���^���_���c���e���0��g�q��h���j���m���n�r��s���wq����x�7���3���"�m���4�����S���a�@�Y=�� ��NPK
!<��޴�.chrome/pdfjs/content/web/cmaps/GBK-EUC-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�	GBK-EUC-Ha���?�>�V��W
�F>��"�B�D�=��@��C���X�T�U�PK
!<�p��L�L,chrome/pdfjs/content/web/cmaps/GBK2K-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�0�0��	�@��> C
�0�6��R���:�x��]�@��r��-�
S
�9�0�&�]��a��l�I�Z�S
�9�0�o��-:�*�+��&�
�S
�9�0�;�)�V�C�9�
� �5�;�pS
�9�0�0�z�	���g�`�}�<�1S
�9�0�-� �u��/�6�(��a�6S
�9�0�n�#�U��X��K�p�D�m�;S
�9�0�@�M�$�{��I�$��S
�9�0�=���|�j���=�z�!S
�9�0�i�f�%�:��H�~��]�NS
�9�0�E�Z�h�M��M��<��;�@�S
�9�0�"�c�:��j����M�S
�9�0�\���k�b�r�7��&�%S
�9�0��C�9��c��>�|�}�S
�9�0�d��z��e�K�q�Z��$��
�}S
�9�0�}�9�S�8t�T�m�l�5S
�9�0�{�_�U��`��i�(��F��}�3�fS
�9�0�}��v��U��4��
���DS
�9�0�q����UF�B�8�S
�9�0�Z�"�F�k�w�5�d�;�V�QS
�9�0�q�C��j��{�q����g�n�XC�9�0�0C�9�1�F��`��C�9�0��W"�~���A�>�y�Y�.f��Tk�.�,�m�i�z�<G�n"�_�6���
�^�1�h�?�+E_�V�7�<�s;�$�S�L�%��|�y�_�)�

	�1	�t�=�|		�Y�~,�-Y� �\�K
�&�<�c	�j�~�L�y��aI�
�c�6x��I�\�`�%�F_��O�N	�/�@ �`�;�(�S�b	�Q���	�M�H-�!�'�L(�K[�i�
�)�N�#�b�.K��"���
��W�V�S�0

��5�$�o�z�I��e	���
�e����B�
�"E�l�s�R�?�
�>�S�tT�^�*�]N�
�e�0
�)�^
�'2�>�y�
�
�M�<�/�P�X�y��K�\�oL��<�{��Q�dj�]_�c�T0�'��-
�0�?�S�0
�C��.����z�d�_�7�'�L��}�C�D��S��m�Qy�d�%�x�4�k�`(�A�,
G�;�
�'�V�x�]�C5��!�:�|�m�%�$�e�>�Xe�L��j�qt�&�d!�x���$���I�b�
��D�n�m�7�b�J�#�(J�#�	�2���I�y�R�#
��g�6�@��!���N�[�
�m��$	�oA�U�_eA�S��;��i�n',X-�?S�A�~�Kc�6 E8ZA���4/TzbCvA���7|`4*B$@K��rH~,xb.A���z�D�l.R��4n�LsX$=j$�B�d(HA�L�/JQ
�п~�[��0���[�Z�=�{A���a}�c��C@�k��!�g�Q�b�O��K�H��C��_�|�i�.� ��g�1�f�n�BA���A����]�0�V�"Q	���`����<�v�E�D�A��g�k�DE�5�J�
�"�� ��H�1�A�=A�S��*A�T�	Q	�r�v�@�g�f�_�^�e�d�{A�$���Y���u�$���
	�I�7� �K�1��*�Z��"�_�@��<�A�1�4�	�TR�AW�N%�q�j�:�k�n'��#�F�.�k�M�l�W�7!�p�Q
�j	��^�H	�7��K�L�]�e�Z���x�l	�/P�k�8 �X�{��[�p	�(�/�C��5�%+"'*!"�C�W�|�		U�/m
�:�q	�
�
�A�/�V���i�&%� �'�&�o8�4�k�M
v�{�J�:���s�x�r
�e����H���m�C�]�9�.�S���a�4�6��7�T���D�%�6�k�z�+�Q�:
�O���z�	�5�b���
�J�+�lG�Y����z��*�� �!���.'*�>�C	�g�6"
�E�w�4��M�c�=�	XS T"��f
�9��L�[�\�7	�k� �=�<E�$�X�e��~��<�:
�#��
��n�E�8�o�f
�_�4��m�d9��6�<�s"�n�B�-�+�1��m� �}��P�7J�B�0�x�3*�>
	���w�4�l
��
�g�`
�B�
�k�<���;M�C�t�N�[�|
�b�7�&
���$�
�=A���L��A	�m�'��l
�C�`m�<�$�,|�RA�[�r��
`0-J�LTA	���nc62V�\�"(D�#A����A�のQQ	�W��P�K�d���	��1A;�b�/��y�	�h�1Y�cs�Z�_�`,�V�(�kJ��E�e
�#��v�K�+	�W
�|V�M?�r�<�N�-�q�8�+�P��!�~�B5�3
��
�x�|�}�<
�u�M�
*�!X�	�!�h�'�!�e��g� M�U�Xc�6�h�S�NA,�d�72���)�+%�0�/�8�f4�	�K
	�F;�4�
��}�6
�'�K
�z�J�	�V�b		�J�H�]�j�c�]A�~�S.�3$�n��L�@�R\�_r��Z�&�t�CB"�c�4Ar(�F>Q	�a��p�C�B�y�x�E�D�yQ	�m�H��$��A��@��~��#�	�&A�x��4����*Q
���#�����'��&��/�9�s�PA���s
�z�#�$
�rR�	�

�p�v��>�te�m�=�N��-�'�l�WA���&�&
�4
A
�D��w	"	�4 Q	�Q�E�b�K��f��C�d��dA/�]�_�0
�}�1�(�Y�^
�	�v�?��*�i�d��-3�
�y�n�u��^��X	�9�<	�-�a�n;H	�{�n�
�)�W��]A�@�&A�a��J
�@2Q	�v�;�(����L�K���
A���:��6�g�'7
T�PF�V�A�H�c�j�U�w�S�+�}
�j�d�O�8�.��`A���R
�&)�8�	g�H+�-!��n�B�S�~�oaA����&�7 "#>�D@��<*Q
“��o�N���+�O�X���/A� �y���87�x�L�-�-���?��<�f�Y�t��J�)�+�X��e�h�2�E�,�Q
�l�%!�^�R&�s��Y��j�E�M
�L��2�z�}�t
�i�c�1�T�Q�H�g�
�8�9(�c�.�;�L%'2!(�9�bH��(/��v�-�^�s�j
�n��
�c�"	��:�!��`�-� �1�<	?�}�2�e��f��J�I�

�)�e�r�6�(�!:�.�u�.�
��4�@
�1�0�!��[��i�&:��%�v�g�8��Z�/�I�"�b�2�%�%T�!�3��)�AÄ�G�G=	�)	�P�#	AŜ�o�$�W�`�4e�| �D�M�n�F	

�?�n�t�?�jc �w�!�V��EAĀ��0&�)W��"A�@��TR��[l�
�<�C:$>j�`h&�j�H�s�0�ML��2�o�
'>�22Q	Ӌ�w��p����
��!�� ������'Aӛ�x�-�&�`�C1�)AӖ�j�n�/�L� �E�(�
�z��$�K�F	�u��Y�X[p�\�?A	ӗ��6� $Q
Ԋ�,������w�v��t���?��n��L��}A�F�b�6�AԜ�p�#���m�AԠ��w�/Q�Z�=����
�f��$��K
��O��p��UA�n�\�O_A�h�G�v�S�N�A�m��QՄ�i��l��G��F��o��n��]��\��Q��P��Q�+AՓ�`�3�3�.AՔ�/�  �o�^A՚��+� Q�R��}�0��8����~��g��f��!�� ��_�l�]��L���LA�e�W�T����iQ
�o�����u�5��(��s�|�[��N��kA+�|����
a�S�z�)���W�P�I�H�[�I
�&����j�[
�F��{�:�5��<���g�g��*�+�k�f�i�`Aׇ�A�Q��x7T
�{�,
Q�@����K���8��3�� �����N���B��=�k�|�l��:��!�m�H�?9�+�*��6��+�q����QA�c�2�/�����AQ�l�3��J��u��t������O�t�i��;����%�VA�|�3�I�<�j��@�z-��P�_� �(�[�Q�$��J
��Z�5�6A	�}�5�*K��u
�,Aٜ��1� Q	�O�|�a�j��`��M��L��a
A�'�f�#�
�k�V�c�>��=�7�,
�Q�L
��8��=��@�W��i�Ps�	��"�DY��U�r�!�X�%�>,�� �i�K
�=�.�+�1�N�:�]��/�_��$X�M��=�6�W�8��E�+�W�X�3�h�>�
�-��5��b	�*
!��
�x�Q�l�x�I�Y�n�w�$N�>{�g��W�V�#�	�>	��i�/�U�x�3�&�s�(��z�Ef�G�^� �/��Q�G��7	��_�`�
�m�k�2�.�-^��5��b�
�Lf�n
�y�O
�$�%�.�
��J��
�R��b	�s�N�G��3�c�(/�J�P�s�#)�`
�V
�!�t��Ow�4�C�<�S���Y�XA�ܐ��%	
b�"c

�1�N	�!
		&	#%�!*
&
	%.� "+
05
:C,;6
	�*

E0		-$�%�.
	�$ 	PI
yR	�"kD	a�

	�+ x	1�(8�?�,
'*�Q�PA
�]��&�f�t��b�f�l�!�4AS܏�� �H<
&�;(Ap� 0�q�"�r�
�
�E*!0



�."
�&	,�6 �H4(�A*
�LLQ�]�E��~��[�V����	�}���������vA@�l�i�MKg��(�x�o�v�1�.	�'�D��3�4�y�&�P�I�&&�W�
�^�Q�=��f	�Q� �0�>�3
�:�N�>�EQ�%�g�
���z��;�H�m� �A�O�R�!�{�HA
�x�y"�:H�&�s�1�W�ohgA�v��
�2=h�0
�i��	�>Q��I��2��5��>��t��5�	�|�S��������/��.��CA:�A�A�o�7�X�D��2�#���j��O�0�%�(�(���=�q�"��u�\�g�n�	�X	�'�+�|��5�#�^�9�^�g�&�=�P�+�~���	��?��(���}�F�%��7�A(�@�G�"�A�U	
�m�D�C�3	�r�u�`�2
A�V��t (�*
	�".D�FB 4Q
����4��]��\��9��8��=��<��i�8A1�?�N�,�!�)���
�7�p@�I�(�s��_�V��?�0�c�(�!�w��U���G�(Q�8�y�
�j	��_�@�#�~�Y�=�*�A2�t�a��$�8
Jq��(

	� $38A�F��}+F
�++L�br,�HF(,�%Q���26��(��#��"��i��h��o��n��+Ay�B�T���W�,Aja
�C�+�F�I�/�
�j�)�Y��	��4
�U�^�-�{��i�`�C�B�E�&	�)�%��6�	�g�j�i�8.56'&���0E45�r*�G�_�N�K�B�
�$�<�k�3�l>C��0�G�[�H�N���E��;�G�H
�A�v�#��s�e�� �gA����SA�W��~,�T\ �bj0�F>"�cj!8!:�OT�h|
!�i` ^c!�8�8���v	����v��(�x	��1�v	��;�v	��E�v	��O�v	��Y�v	��c�v	��m�v	��w�y��{��~�|���{���v	��
�v���x	���v	���v	���v	�� �v	��*�v	��4�v	��>�v	��H�v	��R�v	��\�v	��f�v	��p�v	��z�v	���v	���v	��a�@8�X��e���
c�x�0�0	��"�v	��,�v	��6�v	��@�v	��J�v	��T�v	��^�v	��h�v	��r�v	��|�v	���v	���v	���v	��$�v	��.�v	��8�v	��B�v	��L�v	��V�v	��`�v	��j�v	��t�v	��~�v	���v	���v	���v	��&�v	��0�v	��:�v	��D�v	��N�v	��X�v	��b�v	��l�v	��v�v	���v	��
�v	���v	���v	��(�v	��2�v	��<�v	��F�v	��P�v	��Z�v	��d�v	��n�v	��x�v	���v	���v	���v	�� �v	��*�v	��4�v	��>�v	��H�v	��R�v	��\�v	��f�v	��p�v	��z�v	���v	���v	���v	��"�v	��,�v	��6�v	��@�v	��J�v	��T�v	��^�v	��h�v	��r�v	��|�v	���v	���v	���v	��$�v	��.�v	��8�v	��B�v	��L�v	��V�v	��`�v	��j�v	��t�v	��~�v	���v	���v	���v	��&�v	��0�v	��:�v	��D�v	��N�v	��X�v	��b�v	��l�v	��v�v	���v	��
�v	���v	���v	��(�v	��2�v	��<�v	��F�v	��P�v	��Z�v	��d�v	��n�v	��x�v	���v	���v	���v	�� �v	��*�v	��4�v	��>�v	��H�v	��R�v	��\�v	��f�v	��p�v	��z�v	����v	���v	���v	��"�v	��,�v	��6�v	��@�v	��J�v	��T�v	��^�v	��h�v	��r�v	��|�v	���v	���v	���v	��$�v	��.�v	��8�v	��B�v	��L�v	��V�v	��`�v	��j�v	��t�v	��~�v	���v	���v	���v	��&�v	��0�v	��:�v	��D�v	��N�v	��X�v	��b�v	��l�v	��v�v	���v	��
�v	���v	���v	��(�v	��2�v	��<�v	��F�v	��P�v	��Z�v	��d�v	��n�v	��x�v	���v	���v	���v	�� �v	��*�v	��4�v	��>�v	��H�v	��R�v	��\�v	��f�v	��p�v	��z�v	���v	���v	���v	��"�v	��,�v	��6�v	��@�v	��J�v	��T�v	��^�v	��h�v	��r�v	��|�v	���v	���v	���v	��$�v	��.�v	��8�v	��B�v	��L�v	��V�v	��`�v	��j�v	��t�v	��~�v	���v	���v	���v	��&�v	��0�v	��:�v	��D�v	��N�v	��X�v	��b�v	��l�v	��v�v	���v	��
�v	���v	���v	��(�v	��2�v	��<�v	��F�v	��P�v	��Z�v	��d�v	��n�v	��x�v	���v	���v	���v	�� �v	��*�v	��4�v	��>�v	��H�v	��R�v	��\�v	��f�v	��p��v	��z�v	���v	���v	���v	��"�v	��,�v	��6�v	��@�v	��J�v	��T�v	��^�v	��h�v	��r�v	��|�v	���v	���v	���v	��$�v	��.�v	��8�v	��B�v	��L�v	��V�v	��`�v	��j�v	��t�v	��~�v	���v	���v	���v	��&�v	��0�v	��:�v	��D�v	��N�v	��X�v	��b�v	��l�v	��v�v	���v	��
�v	���v	���v	��(�v	��2�v	��<�v	��F�v	��P�v	��Z�v	��d�v	��n�v	��x�v	���v	���v	���v	�� �v	��*�v	��4�v	��>�v	��H�v	��R�v	��\�v	��f�v	��p�v	��z�v	���v	���v	���v	��"�v	��,�v	��6�v	��@�v	��J�v	��T�v	��^�v	��h�v	��r�v	��|�v	���v	���v	���v	��$�v	��.�v	��8�v	��B�v	��L�v	��V�v	��`�v	��j�v	��t�v	��~�v	���v	���v	���v	��&�v	��0�v	��:�v	��D�v	��N�v	��X�v	��b�v	��l�v	��v�v	���v	��
�v	���v	���v	��(�v	��2�v	��<�v	��F�v	��P�v	��Z�v	��d�v	��n�v	��x�v	���v	���v	���v	�� �v	��*�v	��4�v	��>�v	��H�v	��R�v	��\��v	��f�v	��p�v	��z�v	���v	���v	���v	��"�v	��,�v	��6�v	��@�v	��J�v	��T�v	��^�v	��h�v	��r�v	��|�v	���v	���v	���v	��$�v	��.�v	��8�v	��B�v	��L�v	��V�v	��`�v	��j�v	��t�v	��~�v	���v	���v	���v	��&�v	��0�v	��:�v	��D�v	��N�v	��X�v	��b�v	��l�v	��v�v	���v	��
�v	���v	���v	��(�v	��2�v	��<�v	��F�v	��P�v	��Z�v	��d�v	��n�v	��x�v	���v	���v	���v	�� �v	��*�v	��4�v	��>�v	��H�v	��R�v	��\�v	��f�v	��p�v	��z�v	���v	���v	���v	��"�v	��,�v	��6�v	��@�v	��J�v	��T�v	��^�v	��h�v	��r�v	��|�v	���v	���v	���v	��$�v	��.�v	��8�v	��B�v	��L�v	��V�v	��`�v	��j�v	��t�v	��~�v	���v	���v	���v	��&�v	��0�v	��:�v	��D�v	��N�v	��X�v	��b�v	��l�v	��v�v	���v	��
�v	���v	���v	��(�v	��2�v	��<�v	��F�v	��P�v	��Z�v	��d�v	��n�v	��x�v	���v	���v	���v	�� �v	��*�v	��4�v	��>�v	��H��v	��R�v	��\�v	��f�v	��p�v	��z�v	���v	���v	���v	��"�v	��,�v	��6�v	��@�v	��J�v	��T�v	��^�v	��h�v	��r�v	��|�v	���v	���v	���v	��$�v	��.�v	��8�v	��B�v	��L�v	��V�v	��`�v	��j�v	��t�v	��~�v	���v	���v	���v	��&�v	��0�v	��:�v	��D�v	��N�v	��X�v	��b�v	��l�v	��v�v	���v	��
�v	���v	���v	��(�v	��2�v	��<�v	��F�v	��P�v	��Z�v	��d�v	��n�v	��x�v	���v	���v	���v	�� �v	��*�v	��4�v	��>�v	��H�v	��R�v	��\�v	��f�v	��p�v	��z�v	���v	���v	���v	��"�v	��,�v	��6�v	��@�v	��J�v	��T�v	��^�v	��h�v	��r�v	��|�v	���v	���v	���v	��$�v	��.�v	��8�v	��B�v	��L�v	��V�v	��`�v	��j�v	��t�v	��~�v	���v	���v	���v	��&�v	��0�v	��:�v	��D�v	��N�v	��X�v	��b�v	��l�v	��v�v	���v	��
�v	���v	���v	��(�v	��2�v	��<�v	��F�v	��P�v	��Z�v	��d�v	��n�v	��x�v	���v	���v	���v	�� �v	��*�v	��4��v	��>�v	��H�v	��R�v	��\�v	��f�v	��p�v��z�z���v	���v	���v	��#�v	��-�v	��7�v	��A�v	��K�v	��U�v	��_�v	��i�v	��s�v	��}�v	���v	���v	���v	��%�v	��/�v	��9�v	��C�v	��M�v	��W�v	��a�v	��k�v	��u�v	���v	��	�v	���v	���v	��'�v	��1�v	��;�v	��E�v	��O�v	��Y�v	��c�v	��m�v	��w�v	���v	���v	���v	���v	��)�v	��3�v	��=�v	��G�v	��Q�v	��[�v	��e�v	��o�v	��y�v	���v	��
�v	���v	��!�v	��+�v	��5�v	��?�v	��I�v	��S�v	��]�v	��g�v	��q�v	��{�v	���v	���v	���v	��#�v	��-�v	��7�v	��A�v	��K�v	��U�v	��_�v	��i�v	��s�v	��}�v	���v	���v	���v	��%�v	��/�v	��9�v	��C�v	��M�v	��W�v	��a�v	��k�v	��u�v	���v	��	�v	���v	���v	��'�v	��1�v	��;�v	��E�v	��O�v	��Y�v	��c�v	��m�v	��w�v	����v	���v	���v	���v	��)�v	��3�v	��=�v	��G�v	��Q�v	��[�v	��e�v	��o�v	��y�v	���v��
�y���v	���v	��(�v	��2�v	��<�v��FaX��]`�21�>	�p�z�$]��"R�d�-U�7�*�
4�%�+ �Z �{�/�%�<�9K�b�1]�.�"��`]�,�"]�
�"]�h�"]�F�"]�$�"]��"]�`�"]�>�"]��"]�z�"]�X�"]�6�"]��"]�r�"]�P�"]�.�"]��"]�j�"]�H�"]�&�"]��"]�b�"]�@�"]��"]�|�"]�Z�"]�8�"]��"]�t�"]�R�"]�0�"]��"]�l�"]�J�"]�(�"]��"]�d�"]�B�"]� �"X�~�']�W�"]�5�"]��"]�q�"]�O�"]�-�"]��"]�i�"]�G�"]�%�"]��"]�a�"]�?�"]��"]�{�"]�Y�"]�7�"]��"]�s�"]�Q�"]�/�"]�
�"]�k�"]�I�"]�'�"]��"]�c�"]�A�"]��"]�}�"]�[�"]�9a�N�ya���x�)a�v���a	�A�/�4����s�U��6�O�-�hI�A�8�La�N�;�'�a��	�)�>�3�r�*�� �#�Ba��@�
�%�3�D�I�L�N�Z!�n��(�0�4�9C�=�@�B�E�K�N�T
�Z�e	�m�r�t�v�|����
�+�)�UA�]�m�
���
��%�(�1�5�7�R�b�d�p�s�uA����$�<�C�F�L�P�YK�hA	�4�>�G�Z�p��'��9	�;�E�H�N�S	�XB�b�e�j�l�s�w�|������"�&�+
�.�:�?�C�J�L�O�S�pC�t7�w9�/�i�z���
���A
�%�2�5�>�K	�N�X
�Z�e�r�v�~	�������"�$	�<C�G�`�zc�a�'���cA�}�/��5�9�E�V�j�o�s	�{����
�A�+	�/�9�@!�D
�f�s�y���+�.�MA2�R����
��*'�D�m�o�~B��
��(
�.�<�V�Y�]�_
�d�r�w�~��A�+�=�C�I�c�j�w�{�~�� 	�&%�0A>�V��2�M�R�U�j�z�|�A�	������	�"�,�3�6�8�<�D�H�K�N
�[�i�l�o�x
�}����A>�5�\��*	�-�7B�T�d�p�����+�3�?�B�E�M�U�\�_�c�e	�k�m�qB�t�x�����
�	���#�;�M
�V
�a�l�sA1��C�O�R�l����'�,�5�;B�@$�V�{~�}a�
�@&�|�#�(�8�N�S�X-�s�!�)A�0�5�8 �=
�_�k�t�
����)�,�0�2�A�J�LA�X�\�|��	�
������%�4�;�D�J�N�P�X�]�a�c�f	�mA	�w��
��$�(�/�7�:�>�C�E�G�J$�P�u�z�}�A>�P�]	�.!�8A�Z�m�s�����/�<�Z�^�t�y�B��	���#�)�-�/�2	�9�C�E�H�O�V�Z�^	�b�l�s�u�x�|
�~
�	����C� �%�'�1�7�:�=�A�C�F�K�M�S�\	�`�j�m�o�s�w<�y�6A
�?�M�m�|��� �"�$�@�C�V�_�a�fC�o�r�t�v�z����	��$�4�7�I
�^	�l�v�z�	�A�!�&�I�L�N�Q�'�n�r0�t �%��F)�L	�v������ �)�=�?
�E	�P�Z�\�`�c�h��w���4�8�=
�E� #�P�t�
�
	�	��$�&	�)�3�7�<�C�T�l�
�s+��-�D�!$�N�s�x	�~��� ��&+�)
�U�c�k�o�����������1��9��<��A��J�!��X	��a��k��n��r����+���.��@��B!��F ��h�
��	����	�� ��*��:��A���_��c��i��l��v���������3��C��S��l��o��� ������"��$��*��3	��<��F��L��P�#��a
��i��w��y��|���� �����6��H��N	��X��_��a
��e	��h��r���u��x��}������������!��)��,��1��4���9��=��@	��G��K��O��Q��S��Y��[��`��c��e��j�"��m��y��}����
��
��
����	����'��+� ��.��1��8��:��?��A��L��Q��S��U��q���t��	������$��&��,
��A�
��L��Z��q��
��
���!��6�� ��W��Z��^��t���y��������������*��,��1��@��F���J��O��S��X��d��g�������!���� ��"
��=��K ��W�%��x����&��,��0 ��3�!��U��b��d��w��~��
	������#��&���)��8��V��\��c
��r��}�
��������������*��-��1��6��=��A��C��J���P��U��X��Z��b	��h��r�������������!�� ��'��*��6��9��<
��D��O
��S��a��g��m�>��p��/��?��F�&��N	��u��������$��&�
��(��6��<��>��A��P��W��Y��\��_��f��n�"��x������������
�� ��.���O��d��g��n������%� ��'��)
��2��@��F��I��N��P��R	��[��e��h��k��n� ��v��z��~������������&��(��-	��4��8�$��<��@��D��F��I��L��O��T��X��a��c��e��g��i��k
��p��u�!��x��{��~����������������������%��(��,�%��1��5��8��?��B��F
��L��O��Q��T��V��[��`��d���k��n��r��z����	��������������!��%��+���4��A*��F��q���C����������%��*��/�(��4��9#��=��a��d��k��p��x���
��*��A��G��K��Q��]���e��j	��u��z��~������������!��'��*� ��.��4��:��@��G��O��T��Y��[��_��b��f��h��j�$��m��o��r	��w
����������/�$	��=
��G��V��[��_��e
��j��m��p�����
��"��-��/��9��>��E��M��O���X ��Z��{
��~��	����
�����&
��&��3��8��:��=��A��C��K��P��W��Y��[��]� ��a��c��f��i��m��o��q��t��w��|����		���������)��#��&��-��4��7��9	��;��F	��K��M��R��U��Y�#��^��e��g��i��m��q��s��u��w��z��������
�������"����!��%��(��+��/��1	��3��>��G��M��P��R��X��[��^�!��`��c��g	��l��o��}����
������������� ��$��(��+��.��2��8
��>��I��L��R��U��Z��\���m��y������	������������"��%�&��)��,��/��5��7$��9��^��a��c��j��n�"��x��{	��}����	��������"��'��,��0���F��L��[��_��b��o��v��~	�������$��5��:��<��?��E��J��M��R�4��h��k��n��r��u��{��}���������������� ��#��&�%��)��/��6��8��=	��F��P��S��U��Y��\��c��h�$��l��n��r��t��v��x��~����
������������ 	��#���-��?��B
��N��]	��c��m��p���x��~����������#��(��.��2��5��;�!��=��@��C��G��W��j��o��{� ��	������&��+��;��B��V��Z��\���`	��f��q��x
��z����������'�"��,��0��3��7��:��C��K��N��Q��X��]��`��b��e�*��l��n��t��v������	�� �����=��?��B��D��K
��[��f��m��p��s	��y����� 
�������� ��)��+��-��<��B��K��P���X��Z��]��b��f��l
��q������
��
���������#��,��4��9��R��W
��g��r�!��u��y��}��
������ ��"��$��*��-��3���I��[��_��c��e��h��j��n��s��x���������$=�� ��NPK
!<@%���,chrome/pdfjs/content/web/cmaps/GBK2K-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�GBK2K-Ha���?�>�V��W
�F>��"�B�D�=��@A���C(	A��� A����W�2
�qPK
!<�o�^9^9/chrome/pdfjs/content/web/cmaps/GBKp-EUC-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@��> ` ^a
�@8�X�Y���qe��r��]�
q�@��
�%�5�3�L�D��I�b�Kq��� �L��N�\�Z�!�n�e���M�'�
�>�(��0�.�4��K�l�8��9q�@�<�h�=�T�@�9�B�X�E�_�K�B�N��T�,�S
�Z��e��j�O�k��l�dq����m�V�r�P�N�t��v�v�|�1����R��W�U�Q��e
��%+�)�O�Uq	�@�]�Y�m�H�
�c���q&����
��+�%�J��(�O�1�O�6�:�4�I��5�u�7�>�R�M�_�)�`�f�a�'��b�2�d�#�p��s�u�uq
�@��E�^��F��o��P�$q���<�A�C�d�F�y�L��O�B�P�Z�Y�"K�ha�@	�4�m�>�G�G�l�Zq���p���I��p'���7�^�8�*�9�,	�;�X�@�E�o�H�!�N�y�S�r	�Xq�@�j�b�|�e�0�i�z�t�j�w�l�L�s�x�O�h�u�q�v�i�w�t�|�o��p�q4����v�s����t��5��[�r��n��7�"�u�&��+�
�.�k�9�9�:��?�}�u�B��C�{�~�L�J�[�L�j�	�N��O�!�S��=�o��pa�@�F�H�t�~�v�7�wq��9�/�*�i��z���%��,�
�.���8��s�q�@
�%�c�9�0�+�1�-�2��5�'�=�/�>�}�K�V	�N�B�Xq#��
�Z�2�e�_�r�y�v�Y�}�]�~�g	��m���&�)��R�;�(��'��+��.��I�"�8�$�	�<q�@�F�|�G�[�^�^�"�_�z�`�-�za
��c��?�cA�}���y/��|�5q���9�i�E�w�V�v�j�}�o�x�s�z�z�~�(	�{�������!��"
�q	�@�+�{	�/�Z�9�e�@�W!�Dq��
�f�e�q�/�#�!�r�T�e�s�
�7�y��&�h��Z�9��\��P�g�*�t�+�>�.��Ma�@2�R�	��7��4�q������
���*�
'�D��l�q�m��o��~�
q�@���
���3��<��i�(�U�-�
�.q���$�<�C�V�)�Y�w�]�'�_��c�
�d���r��v�D�w�2��~��s��`�q�@�+��x�.�v�1�=�E�C�x�Iq���c�u�i�u�j�n�v�0�w��z��{�|�~�E��!��� �	�&�|%�0a�@>�Vq����4�~�2�c�M��;�R�9�U�(�=�j�5�z�:�|�3�q�@�	�b��^��]��7��/�_��2�N��R��<�!�k	�"�2�,�?�3��<�>�6q���8�.�<�{�D�	�G�1�H�D�K��N�^�#�6
�[��^�i�`�l�8�o��xQ
�п~�[��0���[�Z�=�{q���}�a��#��$��s��0�a�@>�q
��5�\�Q���)�]�*�	�-�
�Z�7�
q	�@�T��d�X�p�[��6�q����[��`�+�a�2�@�3��?�_�B�^�D�j�E��M��U�_�\�,�T�_��c�@�b�eQ	���`����<�v�E�D�a���k�g�2�m�)�q�Lq�@�t�r�x���R�$����m��e��-�R�f��z��c���
�<�Q	�r�v�@�g�f�_�^�e�d�{a�{�q���Y�V�c��)�|�#�.�;�<�L�B�M��C
�V�T
�a�o�l�W�sa�@1��-�Cq���O��R�V�(�l�y��Z��'���&��N��)�'�g�,��5�<�;�\a�|�L�o�ua�V�D��j�ma�@�@$�V�{~�}A&�|�#�'�(q���8�k�_�N�N�S�r�X�s-�s�~�!�~�)q�@�0�u�5�
�8�o �=�h�^�
�_q���j�v�k�'�W�t�|��!
��*���l��>�)�L�,�:�0��2�C�@�w�A�t�J�]�Lq�@�X�h�[��\�r�|�5��x�	���
q-��������!����8�%��4��n�:�{��q�;��p�B�i�C��D�o�J��N� �P�u�X�t�]�}�a�y�c�~�f�z	�mq�@	�w�c��-�
�K��S��Y�$��3�(q���/�\�7��:��>�
�C�	��E��G��[�J�/$�P�@�u�m�z�A�}�J��g�B�a�@>�P�]�\	�.�[!�8q�@�Z�^�m�D�b�r�T�s���f�q����c��]�/�k�<�5�Z�&�^�c��t�=�x�A�y�K��q�@��$�	�H�`��w��:��1�#�A��)�8�-�v�/��2��7�J�8q-��	�9�>�C�4�E�W�H��O�a�l�V�j�Z�c��^�&�	�b��l�e�s�d�u�b�x�^�|�D
�~�6�)
�	�+��%����v��m�q!�@� ��%��P�'��/�Z�0�S�1�i�d�7�U�9�f�:�_�=�O�A�Y�C��E�l�k�F�X�n�Kq���M��R�m�S�z�\�V�h	�`��j��m�f�o�o�s�	�X�w�g<�y�#�6a�@
�?�z�M�%�mq���|�"��V��:� �n�"�U�$�(�@�W�C�|�V��_�X�a�:�f��nq�@�o�l�r�Y�t�'�v�_�z���]��<�
�Z��I��F	�q����$��4��6�f�7�C�I�L
�^�@	�l�Q�v�,�z�/�	a��]`�21�>	�p�z�$]��"R�d�-U�7�*�
4�%�+ �Z �{�/�%�<�9K�ba���a��	�)�>�3�r�*�� �"�#�Bq
�@��6�=!�&�5�H��I��L�?�*�Nq	���Q�R�k���l�"�m]�.q
�@�d�n�+�p��9�q�_�A��r�0�ta���a�F�@*�`
�\�]�%�ja�� �%��F)�L	�v����q�@� �a�)�[�=�b�?�T
�E�_	�Pq	���Z�l�\�]�`�c�c�d�ha�@�w�%��p�q	����4�B�8�N�=�1
�E�a�@#�P�F�t�.�
q
���
��	��2���+	��3�$�(�&q	�@�'�/�(�1	�)�,�3�N�7Q	�W��P�K�d���	��1q	�`�4�/�e�7�<�i�C�%�Sa���T�@�k��l]�,q
�@
�s�n�~�=���2��C+�a���-��D]�
q	�@�M�o$�N�n�s�t�x�	�~a��]�h�"]�Fa���p�
� �(�+,�(�i�e�za
����� ��&+�)
�U�c�k�n�oq
�@���H���d���f�w���k���'��1�la����9�k��<�c��A�j��J]�$q
�@��W���X�'	��a�f��k�g��l��n�h��ra�����f�\��+]�q
�@��.�"��@�]��B���E�[�8!��Fa�� ��h]�`q�@
��	�������w	�� �$�i�+��*��[��:a�����@�}��A]�>q�@��_� ���b�m��c�9��i�j��l�$�a��t�8�`��u�b��v�A��a��]��"]�za���G�M�A�d�!�3�oa���������3��C��S��l��o��q�@���$���h���c��"���$�f��*�g��3�A	��<q
����F�j��J���K�l��L�k��P�P���`]�Xq�@�w��a�

��i�i��w�n�:��y���|�e�m���	���e���#��a�� ��]�6A��6�u�|��H�$��NQ	�a��p�C�B�y�x�E�D�ya�j��XQ	�m�H��$��A��@��~��#�	�&a�v�����^���_�'��aQ
���#�����'��&��/�9�s�Pq	����e�s	��h�1�&�%� ��r]�q"�@��u��$��w�(��x�*�)��z���{�>��|�1��}�,���5���+�/���h�d���!������|�J�-���Tq���� �Q��!�3��(�/��)�.��+�0�[��,�X��1�B��4]�ra�@��9�9��<�6��=��9��@Q	�Q�E�b�K��f��C�d��dq�Z��G�_�8�2�t��J��1���K�5��O�;��Q�7�3��S�T�^��X�4��Y�=�	�<q����[�n�X��^�?�E��_�I��`�A��b�G�V��c���e���g�C�?��h�#��i�B��j�>��l]�Pq�@�&��m�N�F�K�D��y�'�L��}����G�I�H���B��
�n��
����J�\��Q	�v�;�(����L�K���
q�����:�m���R�Q	���U��&�	���'�T��+]�.q'�@�{��.�@��1�&�a���7�
�V�Y�X��8�.��:���?�z�M�W��A�S��I� ��J�_���K�n��L�$�p��O�:��P�Z��Q���Sa����U�b��p���q]�q�@��t�j���,	���b�q���r���r��$���&a����,�.
��A]�jq	�@
��L�`��Z�B��p�A��q�(��a€���
��Q
“��o�N���+�O�X���/a¡]�H�"]�&a �y��8�U7�R�G�ya���!��6�� ��W��Z��^��tq�@��y�#���E���I���������w�����*�%��,qĀ��0���1�-�H�(��@���D�E��E�J��F]�q�@��J�p��N�F��O�]�^��S�,��X�a��d�;�Y�K�I��gaŀ���a���o�����]�bq�@���p����� �x��"�"
��=�6��Kaƀ ��W]�@q	�@%��x�+���?��&�}��,�3��0aǀ ��3]�q�@��T�M��U�4��b�q��d���w�I��~�qȀ��
�F	���G������!��#�b��&]�|q
�@��)�M�H��8�`��U�E��V���\aɀ��c��5
��r�<��}]�Zq�@
���o���Q��� ���8������B��*�O��-�=��1�D�1��4�Kq
ʀ��5�;��6�P��=�4�&��A�K��C�U��J]�8q�@��P�>��S�t��T�0��U���X�Z��Z�J��b���g�	��h�?��r�t�C��qˀ���������Y���T���2��]�q�@�&�W�� �X�B�S��'�;��*�Z��6�V��9�7��<�y
��D�S��O�a��Rq
̀
��S���a�I��f�B��g�h��m]�ta͡]�Ra͐�Z�W�N�.
�2a�@>��p��/��?��F�&��N	��u��q
΀������\���l��$�&��&]�0q�@
��(�`��6�{��<�^��>�a��A�U��P�k��V�[��W�	��Y�Y��\q
π��^���_�)�V��f�]�_��n�o��w�X]�q�@��x�)���[���v���������?������
�����-aЀ ��.]�lq�@��O�q��d���g�X�,��n�'���v��aр���a�;��%]�Jq�@�>��'�]��)�=
��2�*��@�<�:��F�?��I���M�k��N���P�-��Rq
Ҁ	��[���e�2��h���k�6��m�8��n�:]�(q�@��v�<��z�d��}�9��~�=���>���?���R���}��
�;�-���	��$�
��%���&aӀ��(�_�K�
��-Q	Ӌ�w��p����
��!�� ������'qӔ��4�j��6�b�K��7�x��8�b��:�r]�q%�@��;�P��<�S��?�e��@�r��D���F�]��I�T��K�g��L���O�f��T�h��X�d��^�j��_�k��`�i�=��a�j��c�=��e�aԀ��g�v�g���i�7��kQ
Ԋ�,������w�v��t���?��n��L��}aԗ��p�p��u�n��w]�dq�@��x�m��l�b��{�~��~�y���/���6���w��Q�Z�=����
�f��$��K
��O��p��Uq�e���G���E���\������u������F���aՀ���4��QՄ�i��l��G��F��o��n��]��\��Q��P��Q�+qՐ��%�`���(�~�G��+���,�u��/]�Bq�@���0�
�S��1���4����5�_��7�	��8Q�R��}�0��8����~��g��f��!�� ��_�l�]��L���La�b��?�W��B���E���FQ
�o�����u�5��(��s�|�[��N��ka�y��L���Oqր��Q�
��T��
���V�c��[�:��`�x��d]� q�@��k���n��k��r���x�0��h��y�D��z�������	�U���J���@���S��q׀���I�����!�m�x��$�{���%���*���+X�~a�@��4�H��A�}*��Fq؀��q�;�����Y�$���:�e�p�d�0�a��]�WQ�@����K���8��3�� �����N���B��=�k�|�l��:��!�m�H�?9�+�*��6��+�q����Qa�]���2��Q���4��Q�l�3��J��u��t������O�t�i��;����%�Va�z����5�xqـ�T���s��%�)�7�(�	�G��*�.�1��/�f��1�[�D��2�]�5q
�@��3�*�1��4�=��9��\#��=���a�z��daڡ]��"]�qaڅ�n�x�Q��0�}���,�aڀ��k��p��x���
��*��A��G��K��Q��]���e��jQ	�O�|�a�j��`��M��L��a
q�X��u���z���~�#�������p��q܀���{�g�6�V���`�� ���!�
��'���*�]�Oq �@��.���4���:�������?���@�1��D���E���F�X���G���N�	�f��O�&��Y���Tq݀��X�;���Y�:�[��[�p��_� ��b�;��f���h�p�7��j�R��l�3]�-q�@�E�!��m�(��o�|�0��r�"	��w�F�������
���0����K�J�*��aހ���{��/�r��;]�q�@�~��<�	��=�;
��G�S��U�>�&���V�l��Y�>��Z�J��[�3��_���eA	�w�O�$����1��0����~��m�=q	߁��j�q��l�F��m�c�s��p]�iq�@������3
��"� ��-�G��/�<��7���8�Q��9q���>�;��E���J�-�+��K���L���M���O]�Gq
�@��X�! ��Z�U��{�
��~��$��	q�����X���z���y���4���s�x���w�H�v������E�{��#]�%q!�@��$�z��%�m
��&�}��1�y��2�~�M��3����8���:����=�]�%��@����A�z�&��C�U��K���Oq������P���V���W��1��Y����	��[��4����]�]�q"�@��a����c���f�V��i�
��l�S��m�z�N��o���q�U��t�
��w���|�*�������-���	�2���{q�	���/���,���(������/���<������5��!�
�&��"]�aq'�@�4�*��1���#�%��&�M��+�Z��,�r�>��-�O��1�C��2�W��3�<��4�)��7�!��9�	��;�?��E�D��F�#��:�E��I�=q���J�A��K�� �<�;�8��M�@��Q�M��R���U�K��Y�G�O��]]�?q)�@��^�0��d�F��e�L��g��R�0�I��i�Q�J��l�R��m�8��p�y��q�+��s�M�%��u�L���w�m��y�7��z�J�8���N���|�T��q����<���U���V��
�h���V���W�S���H��]�q�@�\���Y��!��U��%�}��(�~��+�|��.�Z��/�^��1�4	��3�^��=�`��>�"�T��GA怾A�y�cyR�+�@A�{��Iq���M� ��O�e��P�a��R�b��X�l��[�m� ��^�_�j]�{q�@��`�n��c�9�\��g�h�k�o��j�D�g��k�[��l���o�+��{�q��|�w��}�"���3��
q����*���6�s�o����x���p���u���A�]���]�Yq�@��$�z���'�w��(���*�$��+�{��.�#��2�y��7�f��8���=�|
��>���I�}��K�r��L�t��Rq��N��T�F��U���Y�v��Z�d�?��^��\]�7q
�@��m�u��y�}���?����@��Q�]�E��~��[�V����	�}���������vq�i��	�i���C����y�k���5�
��q��G�I�F���O���K���s��!�w�O�N�J�M��"�P��%�l��'�A��(�n�Q]�q�@�x��)�S��,�R�T���/�,��4�U��5�q���7�E$��9q����^���a���c���i�~�M��j���n�Y]�sq�@��w�2��x�R��{�y	��}�W���?�����
�=������%	���V���D����W���
���"a���'�P��,�C��0]�Qq�@��F�(��L�
��[���^���_�2��b���o�C��va���~�#	��]�/q�@�����$�^��5�]��:���<�_��>�
�}��?�@��E�e��J�g��L�f��Ma���RQ��I��2��5��>��t��5�	�|�S��������/��.��Ca�]�
q �@�G�A�
�/�\��h�?�4��k�I��n�H��r���t�K��u�}��{�~��}�L�C���9������Z���s�aq��M���N�v���K�Q���@������P�� �R��"�*��#�r�S��&]�kq�@�>�T�D��(�O�s��)���/�L��4���5�x��6�N��8�O��=�8��D�P��E�Q	��F�t��P���R�~��Sq���U����W�!��X�q��Y���[�"��\�K�6�5��c�-��g�S��h]�Iq"�@��j�#���k�g��l�$��d��n�8��r�%��t���v�C��x�.��~����&��
����5�'���(���u���)q��+�k�,���j�y���f���g���l�� �-	��#]�'q�@��-�m�B���?���A��i��B�W
��N�%��J��\����]A�~�Q
����4��]��\��9��8��=��<��i�8a�	��c�?��m�g��p]�q�@��x�W��}���~�����G�
������7���p��!����"�p��#���q���(�f��-�R����q��.�
��2�	��4���5�'��:�|��;]�cq�@�A���=���@�N��B����C���G�)��W�a��i�$�t�c�b��ja���o�u��{�L]�Aq�@���z	��� ���h��#�P��$���%�L��&�H��*�>��+�e��;�d��A�9a���B�J��V���Z���\]�q�@��`� ��e�!	��f�"��p�'�9�%��q�)��x�.�+
��z�*�-���(�9��q�����6�0���8���
�7���E��&�:��'�C]�}q�@��+�@��,�=�A��/�>��0�?�@��2�;��3�B��7�D��:�9��B�L��C�G�K��K�I��N�H��P�J���Qq����X�M��]�4��_�Q��`�P�R��b�O��eQ���26��(��#��"��i��h��o��n��+a��]�[q�@��l�T��n�X��t�U�M��v�[��~�Z���Y�.�/���F���&���\���,	���#���1a�� ��]�9q�@��=���?���B�\��D����J���K����Z�n
��[���f�C��k���l���mq����o���p�}��r�o��s�
�	��y�G������
q�@
���P������`������ ���(�6��)���+�v��-q����<���A���B���K���O���Pq�@��X���Z���]�L��b�D��f�`� ��l���o���p�l
��q�"���.����#��q
����
�	�%��
�$���&����'�"�)��q�@��#�(��,���4�!�*��8����9���R��{��U��t��Va����W�
��g�q��r�w��tq�@��u���y���}�Z��
�A���3���j���
�� �i��"���$a����*���-�s��3q�@��I�F�k��[�S���^���_���c���e���0��g�q��h���j���m���n�r��s���wq����x�7���3���"�m���4�����S���a�@�Y=�� ��NPK
!<�ԏJ��/chrome/pdfjs/content/web/cmaps/GBKp-EUC-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
GBKp-EUC-Ha���?�>�V��W
�F>��"�B�D�=��@��C���X�T�U�PK
!<o㛱zz.chrome/pdfjs/content/web/cmaps/GBT-EUC-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�����] �$` �$]�.a��]`�21�>	�p�z�$]��"R�d�-U�7�*�
4�%�+ �Z �{�/�%�<�9K�b�1]�.�"�q���,�%�4�&�6�'	�:�)�E�*�L�+�_�,�a�-�f�.�h�/�p�0�y�2�{�3��4��5�q'���
�6��8��9��:��;��=�#�@�+�A�4�B�:�D�<�E�?�F�K�I�N�J�R�L�T�M�X�N�[�O�a�S�gq���h�T�o�V�t�W�}�X��Y�	�Z��d�&�e�(�f
�+�h�7	�i�Dq0���s�H�u�M�y�O�z�T�{�[�|�_�}�d�~�h��j��l��s��u��x��������	���
����
�������!q&����&��*��/��3��8��<��C��F��K��N��Q� �S�!�X�#�^�$�`�%�q�&�s�'�|�(�q#����)��*�
�.��/��1��6��7��:�(�;�-�<�2�=�5�>�A�A�E�B�I�D�O�E�Y�G�_q1���`�H�e�J�g�K�i�L�k�M�p�O�s�P�w�Q��T��U��V�
�W��Y��[��\��]��^�!�_�)�`�,�a�/�c�1�d�6�e�9�f�<q(���h�A�k�E�l�N�n�Q�o�U�q�W�r�b�t�g�u�j�v�m�w�o�x�v�z�z�{��~�������
����q#����	�$�
�0��6�
�8��;��?��C��E��K��R��V��`��e��g��o��r�!�tq���z�"��#��$��%�
�&��'
��)� �*�%�+�*�,�2Q	�۾-�q�p�o�n�m�l�k�jq���=�2�@�3�F�7�H�8�K�9�O�<�R�=�T�>�Wq���X�?�_�@
�c�A	�r�B�}�C�
�D
��F��G� �H�$�e�+�I�0q!���6�J�:�K�<�L�@�N�D�P�F�Q�I�R�M�U�P�W�g�X	�j�Z�}�c��d��e�
�h��i�q/����j��k��m��o��p�!�q�,�t�/�u�5�v�7�w�;�y�>�z�A�|�D�}�J��L��O��Q��U��X��\�	�`��g��pq���r��y��~� ��$��%��&��)��-��0�!�2�)�3�,�4�3�6�=�7�J�<�Oq*���=�R�?�U�@�X�A�Z�B�]�C�a�D�g�G�j�H�p�I	�y�J��K�	�L�
�M��N��O��P��Q��R�%�S�(�U�,q!���.�W�3�X�8�Y�;�Z�S�[�U�\�\�]�`�_�g�`�r�c�w�d�y�e�{�f��g��h��i�q"���k�
�l��m��n��o��q�"�u�0��9��@��B��D��I��L�	�U�
�X��]��fq#���j��o��w	���%��'��(��)��*��+��,�%�/�(�0�/�2�3�3�6�5�=�6�?�7�Gq!¡�H�;�P�B�[�K�`�L�e�N�g�O�i�P�o�S�x�Y�z�Z��b�
�g��j��o��p��t�"q#á�x�'�y�0�z�3�{�y�:�|�D�}�M��R��T��\��_��f��k��s��v�	�|���
�qġ�������*��-��5��:��=��F��I��P��U��X��\�!�`�#qš�$�d�&�k�+�s�,�w�-�{�0�}�1
��2��3
��4�$�5�)�6	�6qơ�@�7�N�9�V�;�[�<�^�=�`�>�d�@�m�C�s�D�w�E��G��H��I��J��K�q-ǡ��L�!�M�$�O�'�Q�)�R�-�T�3�W�8�Y�<�[�>�\�@�]�C�^�F�`�J�a�M�b�S�d�U�e�\�f�a�i�l�n�v�p�y�q�{q#ȡ�|�r��s��u��v��w
��x�"�|�$�}�)�~�+��.��5�
�:��I��O��T��V�	�Xq/ɡ�
�[��_��b��f��k��n��s��|��~��������������������"� �*�!�.�#�0�$�3�&q'ʡ�8�'�>�*�@�+�C�-�I�.�N�1	�S�2�^�3�b�4�f�6�l�8�q�9�v�:�x�;�|�<��>�
�@��A��B�q'ˡ��C��D�!�E�#�F�)�G�,�I�.�J�5�K�=�L�B�P�F�R�I�S�K�T�V�V�Z�W�]�Y�`�Z�f�\�l�^�nq%̡�_�v�a�|�b��c��h��j��k
��l� �m�"�n�$�o�*�p�.�q�0�r�4�s�6�t�9�u�I�v�N�xq͡�R�{�_�|�e�}�i�~�n��q��w�	�y�����������
	���$��*q&�0�
�5��7��=��A��C��M��P��V��X��Z��a�!�j�$�l�%
�o�'�{�(�}�)��*��+q0ϡ��-��.��/�!�0�&�2�(�3�+�4�/�7�3�8�5�9�9�;�<�=�>�>�F�C�H�D�J�E�M�F�P�G�U�H�X�I�Z�J�\�K�b�L�h�NqС�l�O�q�P�z�R��U��W��X��Y��Z�.�[�1�\�7�]�9�^�E�a�Hq(ѡ�b�L�d�N�e�Q�f�U�g�[�j�a�m�c�n�f�p�r�s�u�t�y�v��x��y��{��}��~�������#q,ҡ�(��*��-��1��7�	�;�
�>��E�
�G��K��M��O��W��]��b��m��s��w��y��}�����q$ӡ�	� ��*��+��,��/��0�!�1�+�2�-�3�2�6�;�7�F�9�I�:�L�;�R�=�U�>�Z�?�b�@q(ԡ�d�A�h�B�l�E�v�I�z�K�|�L��M��O��P��R�
�S��W��X��Y�$�^�(�_�*�`�2�a�8�f�=�gq*ա�h�D�j�K�k�M�l�P�m�S�n�Z�r�]�s�_�t�b�u�g�v�i�w�m�y�o�z�v�{��}��~��������k�q'֡�	�$�	�0��4��;�
�=��@��D��J��M��S��W��Z��_��a��e��h��o��r� �v�"qס�~�%��&��)�
�*��.��0��1	��6�%�7	�(�8�3�9�<�=�E�>�Hqء"�W�@�{�A�~�B��C��E��G�
�H��I��K�(�L�.q١�5�N�B�O�D�P�H�S�\�T�e�X�r�Y�v�Z�y�[�{�\��]�
q
ڡ�:�_	�R��]��j��l�qۡ�q��t��w� �{�"��#��$� �%�"�&�,�'�/�(�3�)�5�*�<�+�?�,�B�-�Hqܡ�O�0�k�1�n�2�q�3�y�6��7��8��9��;��=� �>�%�?�(�@qݡ�B�.�C�4�H�8�J�?�K�B�L�H�N�`�O�h�P�k�Q�q�R�x�S�~�T��V�	�Wqޡ��X��Y��Z
�%�[�1�\�:�]�=�_�C�`�M�a�S�b�W�c�^�d�gq'ߡ�i�e�l�g
�n�h�}�i��k��l��n��o��p��q�"�s�%�t�)�u�+�v�/�w�2�x�4�y�8�z�>�{�Bq��G�|�^�~�f��o��u��z�������q"�	�%��0�	�2�
�5��9��@�
�B��F��N�	�U��`�*��g��l��t��{��}��q������	��E�%�1�.�3�/�<�0�G�2�I�3�N�6�[�7�`q��a�;�c�<�f�=�l�>�u�?��V�$�W�.�X�3�Z�9�[q��?�\�D�^�J�_�P�b
�U�d�a�e�d�f�h�g�x�h�{�i�}�j��k��m
��nq���o�-�p�6�q�D�r�F�s �K�t�m�u�o�v�zq��{�x�~�y��{�	�|��~���$��(�q
�9�!��[��]� �^�(�_�/q)��7�b�?�c�C�d�M�e�Q�h�W�j�\�k�`�n�e�o�g�p�j�q�t�r�v�s�z�u�|�v�~�w��x��y��z��{�q��}
��~�"��*��,��9��;��>��K��O��Y��\�
�^��`�q���x�#�{�%	��&��'��(� �)�&�*�Bq��Q�@�X�A�[�B�c�C�j�D�|�F�~�G��H��I��J�(q��/�K�2�L�=�R�@�S�D�U�N�W�P�X�V�Y�Z�Z�q�[��\��]�q��^��_��`��b�!�c�#�d�%�e�'�f�,�g�0�i�:�k�@�l�D�m�U�n	�[�o�ga��k�q��r�
:�s�"P�.�q��'�
�**��`�+�d�-�f�.�p�/�s�0�v�1�~�2��3�q"���4�
�5��6��7��8�!�9�%�:�.�;�2�<�5�>�?�?�A�@�I�A�Q�B�T�C�\�D�^�Eq��H�p�U�u�W�|�X��Y��Z��\��]��^�(�_�1�`�:�a�@q��A�b�`�c�g�d�i�e�w�f
�z�g��i��j�
�k��l
��m�q���n�$�o�.�p
�4�q�@�r�F�s�O�u�U�v�i�w�n�x�tq���}�y��{��}�!�~�%��,��/��6��9��D��L��O�	�Yq���[�
�^��a��c�
�f��k��y������/�q���N�H�]�L�_�N�`�V�a�\�b�_�d�d�f�i�h�w�9�z�i��j�q9���k�L�
��q��N�s�v�t�S�~�v�D�w�<�y�C�{�+��N�i����q����Z��z
��Z�]�j�l�r�����>��{��p��#��=�Y�"�{�	�$a���!�%�:�&�U�(�}�PK
!<�Q���.chrome/pdfjs/content/web/cmaps/GBT-EUC-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�	GBT-EUC-Ha���?�>�V��W
�F>��"�B�D�=��@��C���X�T�U�PK
!<�d�ee*chrome/pdfjs/content/web/cmaps/GBT-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE!!��]a!!]`�21�>	�p�z�$]��"R�d�-U�7�*�
4�%�+ �Z �{�/�%�<�9K�b�1]�.�"�q0!�,�%�4�&�6�'	�:�)�E�*�L�+�_�,�a�-�f�.�h�/�p�0�y�2�{�3��4��5�q'1!�
�6��8��9��:��;��=�#�@�+�A�4�B�:�D�<�E�?�F�K�I�N�J�R�L�T�M�X�N�[�O�a�S�gq2!�h�T�o�V�t�W�}�X��Y�	�Z��d�&�e�(�f
�+�h�7	�i�Dq03!�s�H�u�M�y�O�z�T�{�[�|�_�}�d�~�h��j��l��s��u��x��������	���
����
�������!q&4!��&��*��/��3��8��<��C��F��K��N��Q� �S�!�X�#�^�$�`�%�q�&�s�'�|�(�q#5!��)��*�
�.��/��1��6��7��:�(�;�-�<�2�=�5�>�A�A�E�B�I�D�O�E�Y�G�_q16!�`�H�e�J�g�K�i�L�k�M�p�O�s�P�w�Q��T��U��V�
�W��Y��[��\��]��^�!�_�)�`�,�a�/�c�1�d�6�e�9�f�<q(7!�h�A�k�E�l�N�n�Q�o�U�q�W�r�b�t�g�u�j�v�m�w�o�x�v�z�z�{��~�������
����q#8!��	�$�
�0��6�
�8��;��?��C��E��K��R��V��`��e��g��o��r�!�tq9!�z�"��#��$��%�
�&��'
��)� �*�%�+�*�,�2Q	9[�-�q�p�o�n�m�l�k�jq9d�=�2�@�3�F�7�H�8�K�9�O�<�R�=�T�>�Wq:!�X�?�_�@
�c�A	�r�B�}�C�
�D
��F��G� �H�$�e�+�I�0q!;!�6�J�:�K�<�L�@�N�D�P�F�Q�I�R�M�U�P�W�g�X	�j�Z�}�c��d��e�
�h��i�q/<!��j��k��m��o��p�!�q�,�t�/�u�5�v�7�w�;�y�>�z�A�|�D�}�J��L��O��Q��U��X��\�	�`��g��pq=!�r��y��~� ��$��%��&��)��-��0�!�2�)�3�,�4�3�6�=�7�J�<�Oq*>!�=�R�?�U�@�X�A�Z�B�]�C�a�D�g�G�j�H�p�I	�y�J��K�	�L�
�M��N��O��P��Q��R�%�S�(�U�,q!?!�.�W�3�X�8�Y�;�Z�S�[�U�\�\�]�`�_�g�`�r�c�w�d�y�e�{�f��g��h��i�q"@!�k�
�l��m��n��o��q�"�u�0��9��@��B��D��I��L�	�U�
�X��]��fq#A!�j��o��w	���%��'��(��)��*��+��,�%�/�(�0�/�2�3�3�6�5�=�6�?�7�Gq!B!�H�;�P�B�[�K�`�L�e�N�g�O�i�P�o�S�x�Y�z�Z��b�
�g��j��o��p��t�"q#C!�x�'�y�0�z�3�{�y�:�|�D�}�M��R��T��\��_��f��k��s��v�	�|���
�qD!�������*��-��5��:��=��F��I��P��U��X��\�!�`�#qE!�$�d�&�k�+�s�,�w�-�{�0�}�1
��2��3
��4�$�5�)�6	�6qF!�@�7�N�9�V�;�[�<�^�=�`�>�d�@�m�C�s�D�w�E��G��H��I��J��K�q-G!��L�!�M�$�O�'�Q�)�R�-�T�3�W�8�Y�<�[�>�\�@�]�C�^�F�`�J�a�M�b�S�d�U�e�\�f�a�i�l�n�v�p�y�q�{q#H!�|�r��s��u��v��w
��x�"�|�$�}�)�~�+��.��5�
�:��I��O��T��V�	�Xq/I!�
�[��_��b��f��k��n��s��|��~��������������������"� �*�!�.�#�0�$�3�&q'J!�8�'�>�*�@�+�C�-�I�.�N�1	�S�2�^�3�b�4�f�6�l�8�q�9�v�:�x�;�|�<��>�
�@��A��B�q'K!��C��D�!�E�#�F�)�G�,�I�.�J�5�K�=�L�B�P�F�R�I�S�K�T�V�V�Z�W�]�Y�`�Z�f�\�l�^�nq%L!�_�v�a�|�b��c��h��j��k
��l� �m�"�n�$�o�*�p�.�q�0�r�4�s�6�t�9�u�I�v�N�xqM!�R�{�_�|�e�}�i�~�n��q��w�	�y�����������
	���$��*q&N!�0�
�5��7��=��A��C��M��P��V��X��Z��a�!�j�$�l�%
�o�'�{�(�}�)��*��+q0O!��-��.��/�!�0�&�2�(�3�+�4�/�7�3�8�5�9�9�;�<�=�>�>�F�C�H�D�J�E�M�F�P�G�U�H�X�I�Z�J�\�K�b�L�h�NqP!�l�O�q�P�z�R��U��W��X��Y��Z�.�[�1�\�7�]�9�^�E�a�Hq(Q!�b�L�d�N�e�Q�f�U�g�[�j�a�m�c�n�f�p�r�s�u�t�y�v��x��y��{��}��~�������#q,R!�(��*��-��1��7�	�;�
�>��E�
�G��K��M��O��W��]��b��m��s��w��y��}�����q$S!�	� ��*��+��,��/��0�!�1�+�2�-�3�2�6�;�7�F�9�I�:�L�;�R�=�U�>�Z�?�b�@q(T!�d�A�h�B�l�E�v�I�z�K�|�L��M��O��P��R�
�S��W��X��Y�$�^�(�_�*�`�2�a�8�f�=�gq*U!�h�D�j�K�k�M�l�P�m�S�n�Z�r�]�s�_�t�b�u�g�v�i�w�m�y�o�z�v�{��}��~��������k�q'V!�	�$�	�0��4��;�
�=��@��D��J��M��S��W��Z��_��a��e��h��o��r� �v�"qW!�~�%��&��)�
�*��.��0��1	��6�%�7	�(�8�3�9�<�=�E�>�HqX!"�W�@�{�A�~�B��C��E��G�
�H��I��K�(�L�.qY!�5�N�B�O�D�P�H�S�\�T�e�X�r�Y�v�Z�y�[�{�\��]�
q
Z!�:�_	�R��]��j��l�q[!�q��t��w� �{�"��#��$� �%�"�&�,�'�/�(�3�)�5�*�<�+�?�,�B�-�Hq\!�O�0�k�1�n�2�q�3�y�6��7��8��9��;��=� �>�%�?�(�@q]!�B�.�C�4�H�8�J�?�K�B�L�H�N�`�O�h�P�k�Q�q�R�x�S�~�T��V�	�Wq^!��X��Y��Z
�%�[�1�\�:�]�=�_�C�`�M�a�S�b�W�c�^�d�gq'_!�i�e�l�g
�n�h�}�i��k��l��n��o��p��q�"�s�%�t�)�u�+�v�/�w�2�x�4�y�8�z�>�{�Bq`!�G�|�^�~�f��o��u��z�������q"a!	�%��0�	�2�
�5��9��@�
�B��F��N�	�U��`�*��g��l��t��{��}��qb!�����	��E�%�1�.�3�/�<�0�G�2�I�3�N�6�[�7�`qc!�a�;�c�<�f�=�l�>�u�?��V�$�W�.�X�3�Z�9�[qd!�?�\�D�^�J�_�P�b
�U�d�a�e�d�f�h�g�x�h�{�i�}�j��k��m
��nqe!��o�-�p�6�q�D�r�F�s �K�t�m�u�o�v�zqf!�{�x�~�y��{�	�|��~���$��(�q
g!9�!��[��]� �^�(�_�/q)h!�7�b�?�c�C�d�M�e�Q�h�W�j�\�k�`�n�e�o�g�p�j�q�t�r�v�s�z�u�|�v�~�w��x��y��z��{�qi!�}
��~�"��*��,��9��;��>��K��O��Y��\�
�^��`�qj!��x�#�{�%	��&��'��(� �)�&�*�Bqk!�Q�@�X�A�[�B�c�C�j�D�|�F�~�G��H��I��J�(ql!�/�K�2�L�=�R�@�S�D�U�N�W�P�X�V�Y�Z�Z�q�[��\��]�qm!�^��_��`��b�!�c�#�d�%�e�'�f�,�g�0�i�:�k�@�l�D�m�U�n	�[�o�gan!�k�q��r�
:�s�"P�.�qp!�'�
�**��`�+�d�-�f�.�p�/�s�0�v�1�~�2��3�q"q!��4�
�5��6��7��8�!�9�%�:�.�;�2�<�5�>�?�?�A�@�I�A�Q�B�T�C�\�D�^�Eqr!�H�p�U�u�W�|�X��Y��Z��\��]��^�(�_�1�`�:�a�@qs!�A�b�`�c�g�d�i�e�w�f
�z�g��i��j�
�k��l
��m�qt!��n�$�o�.�p
�4�q�@�r�F�s�O�u�U�v�i�w�n�x�tqu!�}�y��{��}�!�~�%��,��/��6��9��D��L��O�	�Yqv!�[�
�^��a��c�
�f��k��y������/�qw!�N�H�]�L�_�N�`�V�a�\�b�_�d�d�f�i�h�w�9�z�i��j�q9x!�k�L�
��q��N�s�v�t�S�~�v�D�w�<�y�C�{�+��N�i����q����Z��z
��Z�]�j�l�r�����>��{��p��#��=�Y�"�{�	�$ay!�!�%�:�&�U�(�}�PK
!<��S���*chrome/pdfjs/content/web/cmaps/GBT-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�GBT-Ha!"�?�>�V��W
�F>��"�B�D�=��@��C���X�T�U�PK
!<i.���0chrome/pdfjs/content/web/cmaps/GBTpc-EUC-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�����]� ` ^� a��]`�21�>	�p�z�$]��"R�d�-U�7�*�
4�%�+ �Z �{�/�%�<�9K�b�1]�.�"�q���,�%�4�&�6�'	�:�)�E�*�L�+�_�,�a�-�f�.�h�/�p�0�y�2�{�3��4��5�q'���
�6��8��9��:��;��=�#�@�+�A�4�B�:�D�<�E�?�F�K�I�N�J�R�L�T�M�X�N�[�O�a�S�gq���h�T�o�V�t�W�}�X��Y�	�Z��d�&�e�(�f
�+�h�7	�i�Dq0���s�H�u�M�y�O�z�T�{�[�|�_�}�d�~�h��j��l��s��u��x��������	���
����
�������!q&����&��*��/��3��8��<��C��F��K��N��Q� �S�!�X�#�^�$�`�%�q�&�s�'�|�(�q#����)��*�
�.��/��1��6��7��:�(�;�-�<�2�=�5�>�A�A�E�B�I�D�O�E�Y�G�_q1���`�H�e�J�g�K�i�L�k�M�p�O�s�P�w�Q��T��U��V�
�W��Y��[��\��]��^�!�_�)�`�,�a�/�c�1�d�6�e�9�f�<q(���h�A�k�E�l�N�n�Q�o�U�q�W�r�b�t�g�u�j�v�m�w�o�x�v�z�z�{��~�������
����q#����	�$�
�0��6�
�8��;��?��C��E��K��R��V��`��e��g��o��r�!�tq���z�"��#��$��%�
�&��'
��)� �*�%�+�*�,�2Q	�۾-�q�p�o�n�m�l�k�jq���=�2�@�3�F�7�H�8�K�9�O�<�R�=�T�>�Wq���X�?�_�@
�c�A	�r�B�}�C�
�D
��F��G� �H�$�e�+�I�0q!���6�J�:�K�<�L�@�N�D�P�F�Q�I�R�M�U�P�W�g�X	�j�Z�}�c��d��e�
�h��i�q/����j��k��m��o��p�!�q�,�t�/�u�5�v�7�w�;�y�>�z�A�|�D�}�J��L��O��Q��U��X��\�	�`��g��pq���r��y��~� ��$��%��&��)��-��0�!�2�)�3�,�4�3�6�=�7�J�<�Oq*���=�R�?�U�@�X�A�Z�B�]�C�a�D�g�G�j�H�p�I	�y�J��K�	�L�
�M��N��O��P��Q��R�%�S�(�U�,q!���.�W�3�X�8�Y�;�Z�S�[�U�\�\�]�`�_�g�`�r�c�w�d�y�e�{�f��g��h��i�q"���k�
�l��m��n��o��q�"�u�0��9��@��B��D��I��L�	�U�
�X��]��fq#���j��o��w	���%��'��(��)��*��+��,�%�/�(�0�/�2�3�3�6�5�=�6�?�7�Gq!¡�H�;�P�B�[�K�`�L�e�N�g�O�i�P�o�S�x�Y�z�Z��b�
�g��j��o��p��t�"q#á�x�'�y�0�z�3�{�y�:�|�D�}�M��R��T��\��_��f��k��s��v�	�|���
�qġ�������*��-��5��:��=��F��I��P��U��X��\�!�`�#qš�$�d�&�k�+�s�,�w�-�{�0�}�1
��2��3
��4�$�5�)�6	�6qơ�@�7�N�9�V�;�[�<�^�=�`�>�d�@�m�C�s�D�w�E��G��H��I��J��K�q-ǡ��L�!�M�$�O�'�Q�)�R�-�T�3�W�8�Y�<�[�>�\�@�]�C�^�F�`�J�a�M�b�S�d�U�e�\�f�a�i�l�n�v�p�y�q�{q#ȡ�|�r��s��u��v��w
��x�"�|�$�}�)�~�+��.��5�
�:��I��O��T��V�	�Xq/ɡ�
�[��_��b��f��k��n��s��|��~��������������������"� �*�!�.�#�0�$�3�&q'ʡ�8�'�>�*�@�+�C�-�I�.�N�1	�S�2�^�3�b�4�f�6�l�8�q�9�v�:�x�;�|�<��>�
�@��A��B�q'ˡ��C��D�!�E�#�F�)�G�,�I�.�J�5�K�=�L�B�P�F�R�I�S�K�T�V�V�Z�W�]�Y�`�Z�f�\�l�^�nq%̡�_�v�a�|�b��c��h��j��k
��l� �m�"�n�$�o�*�p�.�q�0�r�4�s�6�t�9�u�I�v�N�xq͡�R�{�_�|�e�}�i�~�n��q��w�	�y�����������
	���$��*q&�0�
�5��7��=��A��C��M��P��V��X��Z��a�!�j�$�l�%
�o�'�{�(�}�)��*��+q0ϡ��-��.��/�!�0�&�2�(�3�+�4�/�7�3�8�5�9�9�;�<�=�>�>�F�C�H�D�J�E�M�F�P�G�U�H�X�I�Z�J�\�K�b�L�h�NqС�l�O�q�P�z�R��U��W��X��Y��Z�.�[�1�\�7�]�9�^�E�a�Hq(ѡ�b�L�d�N�e�Q�f�U�g�[�j�a�m�c�n�f�p�r�s�u�t�y�v��x��y��{��}��~�������#q,ҡ�(��*��-��1��7�	�;�
�>��E�
�G��K��M��O��W��]��b��m��s��w��y��}�����q$ӡ�	� ��*��+��,��/��0�!�1�+�2�-�3�2�6�;�7�F�9�I�:�L�;�R�=�U�>�Z�?�b�@q(ԡ�d�A�h�B�l�E�v�I�z�K�|�L��M��O��P��R�
�S��W��X��Y�$�^�(�_�*�`�2�a�8�f�=�gq*ա�h�D�j�K�k�M�l�P�m�S�n�Z�r�]�s�_�t�b�u�g�v�i�w�m�y�o�z�v�{��}��~��������k�q'֡�	�$�	�0��4��;�
�=��@��D��J��M��S��W��Z��_��a��e��h��o��r� �v�"qס�~�%��&��)�
�*��.��0��1	��6�%�7	�(�8�3�9�<�=�E�>�Hqء"�W�@�{�A�~�B��C��E��G�
�H��I��K�(�L�.q١�5�N�B�O�D�P�H�S�\�T�e�X�r�Y�v�Z�y�[�{�\��]�
q
ڡ�:�_	�R��]��j��l�qۡ�q��t��w� �{�"��#��$� �%�"�&�,�'�/�(�3�)�5�*�<�+�?�,�B�-�Hqܡ�O�0�k�1�n�2�q�3�y�6��7��8��9��;��=� �>�%�?�(�@qݡ�B�.�C�4�H�8�J�?�K�B�L�H�N�`�O�h�P�k�Q�q�R�x�S�~�T��V�	�Wqޡ��X��Y��Z
�%�[�1�\�:�]�=�_�C�`�M�a�S�b�W�c�^�d�gq'ߡ�i�e�l�g
�n�h�}�i��k��l��n��o��p��q�"�s�%�t�)�u�+�v�/�w�2�x�4�y�8�z�>�{�Bq��G�|�^�~�f��o��u��z�������q"�	�%��0�	�2�
�5��9��@�
�B��F��N�	�U��`�*��g��l��t��{��}��q������	��E�%�1�.�3�/�<�0�G�2�I�3�N�6�[�7�`q��a�;�c�<�f�=�l�>�u�?��V�$�W�.�X�3�Z�9�[q��?�\�D�^�J�_�P�b
�U�d�a�e�d�f�h�g�x�h�{�i�}�j��k��m
��nq���o�-�p�6�q�D�r�F�s �K�t�m�u�o�v�zq��{�x�~�y��{�	�|��~���$��(�q
�9�!��[��]� �^�(�_�/q)��7�b�?�c�C�d�M�e�Q�h�W�j�\�k�`�n�e�o�g�p�j�q�t�r�v�s�z�u�|�v�~�w��x��y��z��{�q��}
��~�"��*��,��9��;��>��K��O��Y��\�
�^��`�q���x�#�{�%	��&��'��(� �)�&�*�Bq��Q�@�X�A�[�B�c�C�j�D�|�F�~�G��H��I��J�(q��/�K�2�L�=�R�@�S�D�U�N�W�P�X�V�Y�Z�Z�q�[��\��]�q��^��_��`��b�!�c�#�d�%�e�'�f�,�g�0�i�:�k�@�l�D�m�U�n	�[�o�ga��k�q��r�
:�s�"P�.�q��'�
�**��`�+�d�-�f�.�p�/�s�0�v�1�~�2��3�q"���4�
�5��6��7��8�!�9�%�:�.�;�2�<�5�>�?�?�A�@�I�A�Q�B�T�C�\�D�^�Eq��H�p�U�u�W�|�X��Y��Z��\��]��^�(�_�1�`�:�a�@q��A�b�`�c�g�d�i�e�w�f
�z�g��i��j�
�k��l
��m�q���n�$�o�.�p
�4�q�@�r�F�s�O�u�U�v�i�w�n�x�tq���}�y��{��}�!�~�%��,��/��6��9��D��L��O�	�Yq���[�
�^��a��c�
�f��k��y������/�q���N�H�]�L�_�N�`�V�a�\�b�_�d�d�f�i�h�w�9�z�i��j�q9���k�L�
��q��N�s�v�t�S�~�v�D�w�<�y�C�{�+��N�i����q����Z��z
��Z�]�j�l�r�����>��{��p��#��=�Y�"�{�	�$a���!�%�:�&�U�(�}�`��!PK
!<4X�O��0chrome/pdfjs/content/web/cmaps/GBTpc-EUC-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�GBTpc-EUC-Ha���?�>�V��W
�F>��"�B�D�=��@��C���X�T�U�PK
!<�g��--/chrome/pdfjs/content/web/cmaps/GBpc-EUC-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�����]� ` ^� aX��]`�21�>	�p�z�$]��"R�d�-U�7�*�
4�%�+ �Z �{�/�%�<�9K�b�1]�.�"��`]�,�"]�
�"]�h�"]�F�"]�$�"]��"]�`�"]�>�"]��"]�z�"]�X�"]�6�"]��"]�r�"]�P�"]�.�"]��"]�j�"]�H�"]�&�"]��"]�b�"]�@�"]��"]�|�"]�Z�"]�8�"]��"]�t�"]�R�"]�0�"]��"]�l�"]�J�"]�(�"]��"]�d�"]�B�"]� �"X�~�']�W�"]�5�"]��"]�q�"]�O�"]�-�"]��"]�i�"]�G�"]�%�"]��"]�a�"]�?�"]��"]�{�"]�Y�"]�7�"]��"]�s�"]�Q�"]�/�"]�
�"]�k�"]�I�"]�'�"]��"]�c�"]�A�"]��"]�}�"]�[�"]�9`��!PK
!<s�cf��/chrome/pdfjs/content/web/cmaps/GBpc-EUC-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
GBpc-EUC-Ha���?�>�V��W
�F>��"�B�D�=��@��C���X�T�U�PK
!<��))&chrome/pdfjs/content/web/cmaps/H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE!!��]a!!]�y�"
�W�e�m�t���1	���0�&R�J�-U��*�s��H �# �DQ (!�7	w	WE$$aF0!]�e�"]�C�"]�!�"]��"]�]�"]�;�"]��"]�w�"]�U�"]�3�"]��"]�o�"]�M�"]�+�"]�	�"]�g�"]�E�"]�#�"]��"]�_�"]�=�"]��"]�y�"]�W�"]�5�"]��"]�q�"]�O�"]�-�"]��"]�i�"2�G�M]�z�"]�X�"]�6�"]��"]�r�"]�P�"]�.�"]��"]�j�"]�H�"]�&�"]��"]�b�"]�@�"]��"]�|�"]�Z�"]�8�"]��"]�t�"]�R�"]�0�"]��"]�l�"]�J�"]�(�"]��"]�d�"]�B�"]� �"]�~�"]�\�"]�:�"]��"]�v�"]�T�"�2�\PK
!<R�Ǿ^
^
/chrome/pdfjs/content/web/cmaps/HKdla-B5-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@��> ` ^a�s�@c�/!�"T�"�x�w�yA>�"]�?A>�"�\�w�>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"]�#A>�"]�@A>�"]�]A>�;"\�z�A>�W"]�A>�t"]�3A>�".�P.�A>�/"]�nA>�L"]�A>�i"]�(A>�"]�EA>�#"]�bA>�@"]�A>�]"]�A>�z"]�9A>�"]�VA>�4"]�sA>�Q"]�A>�n"&�-6�UA>�"]�KA>�)"]�hA�F�T+�X"]�A>�b"]�!A>�"]�>A>�"	�[S�fA>�:")�y�e2�#A �V�x"��/�.C�0A�t�w'�
"]�2A>�"]�OA>�-�A	�l�t �v�"�+�-�H�wA>�	"U�H�v�A>�%"]�dA>�B"]�A>�_"]�A>�|"]�;A>�"]�XA>�6"]�uA>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"*�#�T1�OA9��9�;"]�?A>�"]�\A>�:"]�yA>�W"=��N�UA>�t"�3W�:A>�"]�QA>�/"Z�n��IA>�K"]�
A>�h"]�'A>�"]�DA>�""]�aA>�?"]�~A>�\"]�A>�y"]�8A>�"]�UA>�3"]�rA>�P"]�A>�m"�,[�/A5��B"]�KA>�)"]�hA�F#�b"O��.�VA>�c"<�"�A�_A>�"�>T�HA>�"I�\��&A>�9"]�xA�V�G'�l")��a2�>A"�q��`�"]�0A'��o�6"!�L;�oA4�+	�a"]�kA%�I�p"�	�nF��eA>�g"]�&A"��d�'"]�BA>� "]�_A>�="]�|A6�Z�""���=�I
�>�JQ>�A��I�M?�� �%�P�"����T�L�U�J�W�l��K�-��q�t�/�y�|�#�0�e� �G��x�T�Q�T��c�]�j�{
�e��3�0�V�F�k��Q�n)�g�t�3��sQ����u�U�3�%�|���t��~�q�C�X�N�Q+�a���QJ����l�S��#�-`�p��v �R�[� �&�;��z��#��\�o��|��+�#�8�W�O��h�c�a��%��\��b�[��J�q�f�C=�&�+0����>�g�V�6�&�
�e�"�W�V�9�;�Q6�@�:�~���?�o����p�/�5�|�\�>�g�r�A�:����v�c�D�M8�J�a�5��(�
�>G���}�j�(�a�
��E�%�R�?:�z�Ps�Y�]a�v�dA�x�=�P�-�"�m	�9Q^���&�3�b�u��J�-�&B�F�1o�f�5H�n�m�p�y�c�2�m�-��~�-�m�
�5�l.�r�k�~�i��_� �;�G�b�X��e������d��U��&1�s�*��\�?�,��O� �a�,���Si�s�8��x�2�o�\
�[��1��V�^��y���u�,��$�kQ?�@���1�<�;�*�/�F��{����~�
�<� �=�Z�Y�l�$��A�21�
�T�q�?�L�:	��M��P��_��,�G�)�3��c�x�]	�
��!���j$�e�`�&�/�[tjt���c�nQ���K�"��}��H��(�>�a�Z�YK��	���P����x�b�U�2�qa����^Q����k�K�(�;�.��G�3�S�fC�m�
�l�+��M�V�&�j�e���S��2�sa����@Q)��}����&�w�0I��W�`� ����%��
�V��0�U�f�g�N?�`�\�a�s�����X��y��bI�Q�;Q?�@�~�G��0����&��:�j��$�V��A�B�0
�Q��8�1�6��]�$��P�i�r�f�C��1�,�� 	�?�0�/�l�F��	/���I����tiQ^���8�7�B�$q��P�I�j��U�:�1	��{� �a�'�~�u�����/��J�g�~��6�_�@�U�l�A�,�%6�;�P�_��	��)�e����6�M��N�����K��A"�L�<�	�D�S�~�,�G�&�)�|�<�y��
�xQ(�@��J�%�z��u�D�0�K�0�	��+(7�O�r��k
	�)�'�H�-�9�P��J�S�XC�
�ha�h�kQ�j��~�g�s�3�,�E�8�(����@�nW�i�p�)Q���R�4�0�#�.�J�g�\�A
�:�=�>�=�.��B�^�5a���fQH����5���4�)��(��,�D��O�n�'���b�;�$�#���>�/�4��`�)�x4��1�6�?�V��N�c�q��J�S��x�
���4�k�L:�E�h�i��}��P�\�!�|�k�j�;�'�ZPK
!<}���/chrome/pdfjs/content/web/cmaps/HKdla-B5-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
HKdla-B5-Ha�K�N�/�1���
�����h�OPK
!<dXLn	n	/chrome/pdfjs/content/web/cmaps/HKdlb-B5-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@��> ` ^Q?�@�w�X��,�=�8�S�(��x��;� ��:��z�X�3�sC��*�C�P�a� �>��
�,<�1�V�t�c��v�i�i�$�{�&�E�Q�{*�=h�k�H�Y�6�SY���Q'Q$���~��&��t�o�$�p�x�y�L�c�1�f�k�E�>�p�=�=��j�$�%��u�p�0��c�H��M	�ka���tQ8�ǁ�t�Q�%�k��F�}���0�I���i�j�;��i�H�I���+�H�U�p�W�n�}��t��?��m�V��2�w���/�'�(�M�$��/�����MiQ?�@�d�K�s�P�l�U��XE�P�G��U�T���z��^�A�H�	�,�W��
��(J�n�6��j�#�9V\�	�h�;�7�"��D�V�&�9�JX�6�w�B�5�p��Q�{�^�/Q^���2�9�D�s�)�\�0�t��
�@�[�*�#�G�b�c�	�}�n�&�f�Q�>��
��!�o�D�Q�o���p�5S��M����&�+H�d�!�Z�q��_�2�
z��>�i�P�/�(|��L��1�:e���m�|�=�;^�Y�3�h�	�g��{�J�{�3�Z�)���o�H�g�mWQ?�@��}�d���m� ��?�:�Y�:�+�?��C�ZH�e�es�,m�o��v�5�2�|���+�M�.�Z��fi��4�s�$�c�x��?�&�nK�S�`��{���U�V��1Q^���z�L�A�f�/�?�i�F�_�>�'�PN�)�e�~��Y���_�,�60
��"�-�;��I���]�%�X�y�)�d�
�?��<�5�b��f�F��O�	��)�;�7�f�)��M��\�5�>�=�.�=�'�^�9�_��U�7��s�f�W��4��n�'Q?�@�9�n�g�x�&�f�S�L���k�@�\�
�j�;�'��%��+��x�����[�X�4�-��B�U6��Y�$��^�P��h�5�8�Q�P��;�5��i�~�u�D�)�M�)�s�}��<�B��*�#Q���&�=�l�A�5��!���h�?�&��R����%��D�I�6�	+�:�e�Z���~�W�_a����^Q���v�3�R�m;�.�J��J�t�b���-�b��O��^�I�
�t�3Q(��4�.�e��	�V~�A�^��z�4�?�/��f�C��!��X�q�X��k�E�,L�9�}�R�-�^�$�
��P�l�j�5Q&�@�*�F��E�V�p�s���D��g�j�&|�+�G�2�-���Y�f�V��]�L�`�u�M�d�3��]�l��ya�s�@c�/!�"T�"�x�w�yA>�"]�?A>�"�\�w�>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"]�#A>�"]�@A>�"]�]A>�;"\�z�A>�W"]�A>�t"]�3A>�".�P.�A>�/"]�nA>�L"]�A>�i"]�(A>�"]�EA>�#"]�bA>�@"]�A>�]"]�A>�z"]�9A>�"]�VA>�4"]�sA>�Q"]�A>�n"&�-6�UA>�"]�KA>�)"]�hA�F�T+�X"]�A>�b"]�!A>�"]�>A>�"	�[S�fA>�:")�y�e2�#A �V�x"��/�.C�0A�t�w'�
"]�2A>�"]�OA>�-�A	�l�t �v�"�+�-�H�wA>�	"U�H�v�A>�%"]�dA>�B"]�A>�_"]�A>�|"]�;A>�"]�XA>�6"]�uA>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"*�#�T1�OA9��9�;"]�?A>�"]�\A>�:"]�yA>�W"=��N�UA>�t"�3W�:A>�"]�QA>�/"Z�n��IA>�K"]�
A>�h"]�'A>�"]�DA>�""]�aA>�?"]�~A>�\"]�A>�y"]�8A>�"]�UA>�3"]�rA>�P"]�A>�m"�,[�/A5��B"]�KA>�)"]�hA�F#�b"O��.�VA>�c"<�"�A�_A>�"�>T�HA>�"I�\��&A>�9"]�xA�V�G'�l")��a2�>A"�q��`�"]�0A'��o�6"!�L;�oA4�+	�a"]�kA%�I�p"�	�nF��eA>�g"]�&A"��d�'"]�BA>� "]�_A>�="]�|A6�Z�""���=�I
�>�JPK
!<�委�/chrome/pdfjs/content/web/cmaps/HKdlb-B5-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
HKdlb-B5-Ha�K�N�/�1���
�����h�OPK
!<O�z���0chrome/pdfjs/content/web/cmaps/HKgccs-B5-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@��> ` ^a�~�!�0���aC�@�+$�0	�U�_#�f!�k�
�(�1A�@�Y�^�e"#�|� �! �3�TA�V�r	��i�
"��E�'A$�m��#�)[�*A>�"
�E1�S��A'��E"�[�d!�{��"A�57�;"]�sA�Q�^#�j"$�7�3A>�k"2�*)�]A>�"]�FA4�$�Y�Z(�[�\�]�^�_�a�b�ca�@>��"D��G[������������������A!��e	F	)'U

3a���A
��		Ja���a�k�jaC�^�����!"�"�#�%�)�*�+�3�4�;�<�=�?�@�A�D�E�F�G�H�I�M�O�P�Q�SC�U�X�[�\�]�d�e�j�k�l�m�p�q�r�s�w�z�{�}"�~����	�����$	�'�1�4�6�BH�C�D�FA	�O�H��a�u�@c!�"T�"�x�w�yA>�"]�?A>�"�\�w�>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"]�#A>�"]�@A>�"]�]A>�;"\�z�A>�W"]�A>�t"]�3A>�".�P.�A>�/"]�nA>�L"]�A>�i"]�(A>�"]�EA>�#"]�bA>�@"]�A>�]"]�A>�z"]�9A>�"]�VA>�4"]�sA>�Q"]�A>�n"&�-6�UA>�"]�KA>�)"]�hA�F�T+�X"]�A>�b"]�!A>�"]�>A>�"	�[S�fA>�:")�y�e2�#A �V�x"��/�.C�0A�t�w'�
"]�2A>�"]�OA>�-�A	�l�t �v�"�+�-�H�wA>�	"U�H�v�A>�%"]�dA>�B"]�A>�_"]�A>�|"]�;A>�"]�XA>�6"]�uA>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"*�#�T1�OA9��9�;"]�?A>�"]�\A>�:"]�yA>�W"=��N�UA>�t"�3W�:A>�"]�QA>�/"Z�n��IA>�K"]�
A>�h"]�'A>�"]�DA>�""]�aA>�?"]�~A>�\"]�A>�y"]�8A>�"]�UA>�3"]�rA>�P"]�A>�m"�,[�/A5��B"]�KA>�)"]�hA�F#�b"O��.�VA>�c"<�"�A�_A>�"�>T�HA>�"I�\��&A>�9"]�xA�V�G'�l")��a2�>A"�q��`�"]�0A'��o�6"!�L;�oA4�+	�a"]�kA%�I�p"�	�nF��eA>�g"]�&A"��d�'"]�BA>� "]�_A>�="]�|A6�Z�""���=�I
�>�J�y�/i�_�p� a�Y�/ab���P�T�U�X�Y�Z�[�\�^�`�b�d�e�j!�m�A"��4�6�7�8�9�:�<�=�>"�?�A�D�F�H�K�L�N�O�P�R�T�X�Z�\�]�_�w�z�|A�����#
�*"�8�L�Q�T!�U�w�B���#�:�G"�J�K�O	�l�v����Y>��"]��JA>��("��g��0��A	��C��M��Q��S��c��t"��}��%��	��=��GA��W4��`"P����fA,��r	����)"��/2��8��k��xPK
!<]??ϕ�0chrome/pdfjs/content/web/cmaps/HKgccs-B5-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�HKgccs-B5-Ha�K�N�/�1���
�����h�OPK
!<��#y��0chrome/pdfjs/content/web/cmaps/HKm314-B5-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@��> ` ^a_�@c�/!�"T�"�x�w�yA>�"]�?A>�"�\�w�>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"]�#A>�"]�@A>�"]�]A>�;"\�z�A>�W"]�A>�t"]�3A>�".�P.�A>�/"]�nA>�L"]�A>�i"]�(A>�"]�EA>�#"]�bA>�@"]�A>�]"]�A>�z"]�9A>�"]�VA>�4"]�sA>�Q"]�A>�n"&�-6�UA>�"]�KA>�)"]�hA�F�T+�X"]�A>�b"]�!A>�"]�>A>�"	�[S�fA>�:")�y�e2�#A �V�x"��/�.C�0A�t�w'�
"]�2A>�"]�OA>�-Q�@�m�S�(�)�7�:��:�s�t�S�T�y�Ta�O��HQ!�Q��K�$��m�O�8��S�+�:�4�'� ��3��^;�(�C�;�R�4�!�9�aa�r�(Q�t�.�.�&��x�C�G9�
�|Qǡ�$#�D�5�4�aQ !��7�t�@�a�z�3�2�b�q�j�x�+��~aǾ��1Q���L�
��l�G�?��w�&��w�^�1�Q��`�la����<Q���?�&�I�b�k�z�Q���U�u�f��e�b�C�j�s�h�Y�j�a���BA��(�j/�K7R�uQ&�@�u�~
�:RI�M��o�~�W�|�uu���f�b�'�n�-�*�F�G�K�0�� ��a�Ka�f��K�2��
Q�l�_�P�7�@��~�;n�P�g�g�N��Y��g��Qȡ�F�K�8�(�y�e,�L�5��(�'�&��m�j�k�0�+�y&<�8�G�BaȽ��Q	��1�n�t�W�A~w�U�la����g�^��i�>��cQ��l�w
�0�'�
�A�V�p�7�&�a�]�	�&�N�O�q�����o���*����q��f�"�~�9�!��r�0�)�<�6��:a��@	�l�t �v�"�+�-�H�wA>�	"U�H�v�A>�%"]�dA>�B"]�A>�_"]�A>�|"]�;A>�"]�XA>�6"]�uA>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"*�#�T1�OA9��9�;"]�?A>�"]�\A>�:"]�yA>�W"=��N�UA>�t"�3W�:A>�"]�QA>�/"Z�n��IA>�K"]�
A>�h"]�'A>�"]�DA>�""]�aA>�?"]�~A>�\"]�A>�y"]�8A>�"]�UA>�3"]�rA>�P"]�A>�m"�,[�/A5��B"]�KA>�)"]�hA�F#�b"O��.�VA>�c"<�"�A�_A>�"�>T�HA>�"I�\��&A>�9"]�xA�V�G'�l")��a2�>A"�q��`�"]�0A'��o�6"!�L;�oA4�+	�a"]�kA%�I�p"�	�nF��eA>�g"]�&A"��d�'"]�BA>� "]�_A>�="]�|A6�Z�""���=�I
�>�JPK
!<N����0chrome/pdfjs/content/web/cmaps/HKm314-B5-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�HKm314-B5-Ha�K�N�/�1���
�����h�OPK
!<�$+v{{0chrome/pdfjs/content/web/cmaps/HKm471-B5-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@��> ` ^a�s�@c�/!�"T�"�x�w�yA>�"]�?A>�"�\�w�>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"]�#A>�"]�@A>�"]�]A>�;"\�z�A>�W"]�A>�t"]�3A>�".�P.�A>�/"]�nA>�L"]�A>�i"]�(A>�"]�EA>�#"]�bA>�@"]�A>�]"]�A>�z"]�9A>�"]�VA>�4"]�sA>�Q"]�A>�n"&�-6�UA>�"]�KA>�)"]�hA�F�T+�X"]�A>�b"]�!A>�"]�>A>�"	�[S�fA>�:")�y�e2�#A �V�x"��/�.C�0A�t�w'�
"]�2A>�"]�OA>�-�A	�l�t �v�"�+�-�H�wA>�	"U�H�v�A>�%"]�dA>�B"]�A>�_"]�A>�|"]�;A>�"]�XA>�6"]�uA>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"*�#�T1�OA9��9�;"]�?A>�"]�\A>�:"]�yA>�W"=��N�UA>�t"�3W�:A>�"]�QA>�/"Z�n��IA>�K"]�
A>�h"]�'A>�"]�DA>�""]�aA>�?"]�~A>�\"]�A>�y"]�8A>�"]�UA>�3"]�rA>�P"]�A>�m"�,[�/A5��B"]�KA>�)"]�hA�F#�b"O��.�VA>�c"<�"�A�_A>�"�>T�HA>�"I�\��&A>�9"]�xA�V�G'�l")��a2�>A"�q��`�"]�0A'��o�6"!�L;�oA4�+	�a"]�kA%�I�p"�	�nF��eA>�g"]�&A"��d�'"]�BA>� "]�_A>�="]�|A6�Z�""���=�I
�>�JQ�@�m�S�(���(��I�:��:�sq�L�L���E����4���#�&�� ��Q��H��K��O��T��x�
��!��Z�$�=��#�?��\��_��c��$��zQ�m��}�+�n�5�4�[�2� ��3��^�6�e�Q����(�=��;�R�4�!�9�J�-a���(Q.���.�.�&��x�C�G9�
�|$�*�O�D�5�4�a�*�} !��7�t�@�a�z�3�2�b��g�z�j�x�	��7��~a����1Q�߁�L�
���l�G�?�B�]�w�\5�}�^�w�^�1�Q��`�l�H�9�,�=a����7A����#Q0�@��'�"�!� �K�2�K�`�!� �	��_�=��&�I�b�L�;�k�z�M�z����u�k�u�R�m��e�b�C�j�s�`�Y�j�d��a�p��BQ
�r��V�]�j%�K�x�1Ra�|�t�""�r�y�m��DQ.���2�&�5�:R�$�o�M�f�>�%�D�/�d�o�~�W�|�uu�$�;�@�#�f�W�8�'�n�-�*�F�G�K�0�� a����I��5��K�2��
Q���h�c�Z��7�@��~�;�>�M�L�Qq
����R��X�s��`����-�s�U��UA����Y��MQ4�@��l�g�2�3�W�S�}��4�=
��r�Y�8�Y�L�(�y�k 	�L�5��(�'�&��m�j��C�B�
�0��H��y
<a�t��`Q	�v�0�b�k�A�B�D�y�A���a���Q���1��O��0�u�t�W�M�"���_�B�w�U�la����g�^��i�>��cQ���l�w�\��]�\�g�m�0� �I�
�A�V�p�7�&�a�]�|��n�I�N��<�+�(�a����m�}�Q�߁�o�U�x�2�&�
�?���.�k�E�>�?��v�I�d�q�Na����rQ	���0��$�
�z�{�0�H��wPK
!<S�#��0chrome/pdfjs/content/web/cmaps/HKm471-B5-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�HKm471-B5-Ha�K�N�/�1���
�����h�OPK
!<f�dmUU/chrome/pdfjs/content/web/cmaps/HKscs-B5-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@��> A�~�!�2A
�i�UA*�t�w���ss�,2�}n�pn��$�U�B��WA�o�h�y�M�+�P���m�xH�1�A�
�C��6�qST���
� �m	A�t�s�r�s�t�s
E��Y�X�w�v�q�p�o�n)
�E�D�C�B�SX�~���������� �!V;�y�Bxwc�8!�o78�x�y(26`J�<$�	�*	a����6���c
�b�3W�2�Y�^
�+��,"��%{�c�bZ��uA"Z2Q�f}�7�6i�/��2�o(Q	����J�>���?�]��e�dA���_��s�d��M�m�~�u���U�#�s���
�V�a�E�h��;�8�GA�x�%e�\�W\�t�AY���w�"�r�M�	��"�x� �m��o�g�>�8�<�@�':��E�(�M�T��15P}�jm�FA�h�$�'X�q��N���os�l�S�-�H�d�W��d�R�%A�v�P�8J�){�((�v�t�L���G�l�$��$�j�t�M��|�|��L��-�j�`�XVA�f�Eq�p�
�[q�8KAr��a	F	)'U

3	J	"
T

	
&#6-DH+��"+	
(>�A�*�`�j��&��0r�d�e�#A)�ҁ�MCHI?/.
iv&o�z 	-
	{+�_:�-�S�Z ?V�u���{�3A��Á�q 	FZ^=dc<"	/"uR$$	
H
	,
OE6
)F%
G'D
F

&
�|�\�a�14
|	V
Z xz
` ^a�[����c!�"T�"�yA>�"]�?A>�"�\�w�>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"]�#A>�"]�@A>�"]�]A>�;"\�zB>�W"]�A>�t"]�3A>�".�P.�A>�/"]�nA>�L"]�A>�i"]�(A>�"]�EA>�#"]�bA>�@"]�A>�]"]�A>�z"]�9A>�"]�VA>�4"]�sA>�Q"]�A>�n"&�-6�UA>�"]�KA>�)"]�hA�F+�X"]�A>�b"]�!A>�"]�>A>�"	�[S�fA>�:")�y2�#A �V�x"�C�0A�t'�
"]�2A>�"]�OA>�-"�z��*�m	�l �v�"�+-�H�wA>�	"U�H�A>�%"]�dA>�B"]�A>�_"]�A>�|"]�;A>�"]�XA>�6"]�uA>�S"]�A>�p"]�/A>�
"]�LA>�*"]�iA>�G"]�A>�d"*�#1�OA9��;"]�?A>�"]�\A>�:"]�yA>�W"=��UA>�t"�3W�:A>�"]�QA>�/"Z�n�IA>�K"]�
A>�h"]�'A>�"]�DA>�""]�aA>�?"]�~A>�\"]�A>�y"]�8A>�"]�UA>�3"]�rA>�P"]�A>�m"�,[�/A5��B"]�KA>�)"]�hA�F#�b"O��VA>�c"<�"�_A>�"�>T�HA>�"I�\�&A>�9"]�xA�V'�l")�2�>A"�q��"]�0A'��6"!�L;�oA4�+	�a"]�kA%�I�p"�	F��eA>�g"]�&A"��'"]�BA>� "]�_A>�="]�|A6�Z�""�
�>�Ja�����%W	������#��'��,��:��@'��G��O��R��X��a��o��t��~��[��
���o�+"�0�V�Y�_�d#�f�k�p�y�}�
���(�1�8A�@�Y�^�g	�l�w"�|��!�&�3�<D�V�l
�s	��
"��"�'�K�`A$�m��%�*�G5�PA���+�4"
�E�S�i�r��A'��E"�[�d!�{�� �"A�50�<�n"(�s2�A�Q�^�j�o"$��3$�FA�k�p�}�"2�*�]
�v�A>�"]�FA�$(�*�TH�_�c��N��U�c����/�#�%�+�4
�=�A�I�M�Q�SC�U�X�]�e�m�s�w�{$�~��	����!�$	�'�1�4�6R�FW�P�U�\�^�b�e�j!�m�A���1�4�:(�?�A�F�H�L�P�R�T�Z�]�_�u�w�zE������#
�*"�8�L�Q�U�`�h
�l�w�B���#�1�:�D�G$�K�O	�l�v�{����	r�/�{�3�:A>�Y"]�A>�v"�5(�a�(�hA����+��2"��J��T��g��o(��A��(	��1��<��W"��g��j����$����8��>A	��C��M��Q��S��c��m��t"��}��
����'	��=��G��JA��W��`��"����-&��0��X��fA��r����!��)"��/1��8��k��xa�:�@%����;��">��`��I(��a"��
�����4��7��<,��?"��l
��s
��v��	��������
��#��1�W��B��E��RD��\��b��e��i��n��t"��
��_��|B!��!��C"��_��d������P��XC��r��)��+��0��3��:��>"��@��D��F��R5��^��l���o��/�e��N/��Q��S��U"��Y
��[��f��n	��s��}������	������D���� ��'��+��2��9��>��A��G��I(��R��Y��\��_��b��q��s��x��z������
	��A	����&��2��8��>��F#��N��Q��T��[��b��k��t��z������������A��������'

��.��<��>*��B��E��G��I	��N��V��X��\	��a��d��hL��m��r��z.����������"��D��������#��'��)��0��C#��G	��M��O��S��X��Z��]	��`���h��k��r0��y	��}��������	��������R�����~��0��7��:�U��F����%��-	���*��F���R� ��]
��_PK
!<����/chrome/pdfjs/content/web/cmaps/HKscs-B5-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
HKscs-B5-Ha�]���
�����a�K�N�/�1��Oa���PK
!<iF{τ�,chrome/pdfjs/content/web/cmaps/Hankaku.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�`
 ?�g�g�(�G	��V�>�G��PK
!<J�F||-chrome/pdfjs/content/web/cmaps/Hiragana.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�` ��G	��V,���;�>PK
!<o��88.chrome/pdfjs/content/web/cmaps/KSC-EUC-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�����] �` ^�a?��]e�"D�C�;]��"2�f)��"	�C	�M�W�o�(C��<N�K�1���M�'�"]�u�"R�S�-U�&�* �| ��/]�>�"]��"]�z�"]�X�"]�6�"]��"]�r�"]�P�"]�.�"]��"]�j�"]�H�"]�&�"]��"]�b�"]�@�"]��"]�|�"]�Z�"]�8�"]��"]�t�"]�R�"]�0�"]��"]�l�".�J��y�^�~��"]�%�"-���1�I�"�_�ZP�k�"Y�<�>�a���
a���\q	С��j��j�/�|�F�# �Rqѡ2�s��<�&��(��*�(��"�+�#�,�'�-�*�.�,�/�/�2�3�5�6�9q'ҡ�6�:�7�<�C�:�D�;�H
�?��J�!�%�M�&�,�N�-�0�7�O�=�P�A�S�C�F�T�T�V�[�`�U�|��Y��Za>ӡ]�`�"D�>�^��(��"��(��+�#�`O�%�"�u�G��L0� �")�Q�0�{��"]�-�"]��"]�i�"#�G��k�:�	�"�#X�'�"��W.��u�3	�Q�"Z�[�P�6�w�"�7,�I�r�v�m�|�"O��q�b�"�o�T>�{�E�:�"]�K�"S�)�}�#��.T�
a���aܥ�^�N�7�>�+�A�a�+�Jq��b�r�o�I�q�K�w�M�x�O��S��T�	�U�
�W"�
�Z�?�0�\q6��_�1�a�3�b�5�e�:�g�;�j�<�k�?�o�B�@�I�q�J�A�L�t�S�u�V�v�X�B�Z�w�\�x�]�z�y�{�_�|�`�~�/�a��c��T�d��C�F�f��gq#���l��p�	��r�G�
�s�
�u���g����������
�
��������%�/q
�E�6�G�|�I�T��K��(�	�L�
�Mq	���P��Q��S)�5�4�_q��f�k"��a
�)�W�7�c�9�e�=�fq��>�h�A�j�C�l�X�D�n�S�p�T�q�s�W�t�Z�w�^�x�0�`�{%�eq%������
��>��[��������� ���#�\	�%��/� �6�"�8�$�:�%
�<�'�J�*�Ka	��-�
�_�B�7�C��*
�t�N�fC�<�B�ga���g�N�c<��u�`a'�I�O��"9�)�^"�c�"]��"]�d�"]�B�"� ;�<�x�"�|L��"�Y�TE�_��%�"]�5�"R��f�c�g�"�o�	�*��"9�J��
��"]�&�"�B��G�"]�_�"�=T�E�"]�PK
!<��w��.chrome/pdfjs/content/web/cmaps/KSC-EUC-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�	KSC-EUC-Ha���x�z��{�~�-��5��
�������PK
!<k,�''*chrome/pdfjs/content/web/cmaps/KSC-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE!!��]a?!!]e�"D�C�;]��"2�f)��"	�C	�M�W�o�(C��<N�K�1���M�'�"]�u�"R�S�-U�&�* �| ��/]�>�"]��"]�z�"]�X�"]�6�"]��"]�r�"]�P�"]�.�"]��"]�j�"]�H�"]�&�"]��"]�b�"]�@�"]��"]�|�"]�Z�"]�8�"]��"]�t�"]�R�"]�0�"]��"]�l�".�J��y�^�~��"]�%�"-���1�I�"�_�ZP�k�"Y�<�>�aKg�
aMh�\q	P!��j��j�/�|�F�# �RqQ!2�s��<�&��(��*�(��"�+�#�,�'�-�*�.�,�/�/�2�3�5�6�9q'R!�6�:�7�<�C�:�D�;�H
�?��J�!�%�M�&�,�N�-�0�7�O�=�P�A�S�C�F�T�T�V�[�`�U�|��Y��Za>S!]�`�"D�>�^��(��"��(��+�#�`O�%�"�u�G��L0� �")�Q�0�{��"]�-�"]��"]�i�"#�G��k�:�	�"�#X�'�"��W.��u�3	�Q�"Z�[�P�6�w�"�7,�I�r�v�m�|�"O��q�b�"�o�T>�{�E�:�"]�K�"S�)�}�#��.T�
aWd�a\%�^�N�7�>�+�A�a�+�Jqe!�b�r�o�I�q�K�w�M�x�O��S��T�	�U�
�W"�
�Z�?�0�\q6f!�_�1�a�3�b�5�e�:�g�;�j�<�k�?�o�B�@�I�q�J�A�L�t�S�u�V�v�X�B�Z�w�\�x�]�z�y�{�_�|�`�~�/�a��c��T�d��C�F�f��gq#g!��l��p�	��r�G�
�s�
�u���g����������
�
��������%�/q
h!E�6�G�|�I�T��K��(�	�L�
�Mq	i!��P��Q��S)�5�4�_qj!�f�k"��a
�)�W�7�c�9�e�=�fqk!�>�h�A�j�C�l�X�D�n�S�p�T�q�s�W�t�Z�w�^�x�0�`�{%�eq%l!�����
��>��[��������� ���#�\	�%��/� �6�"�8�$�:�%
�<�'�J�*�Ka	m!�-�
�_�B�7�C��*
�t�N�fC�<�B�gamn�g�N�c<��u�`a'm$I�O��"9�)�^"�c�"]��"]�d�"]�B�"� ;�<�x�"�|L��"�Y�TE�_��%�"]�5�"R��f�c�g�"�o�	�*��"9�J��
��"]�&�"�B��G�"]�_�"�=T�E�"]�PK
!<�KR�A�A0chrome/pdfjs/content/web/cmaps/KSC-Johab-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��A��=�2�M�2�M ` ^a�B�f�n�w�y�A�a�___�ea'�a�>�u�@�w�A�y�E�F�~�M�P��R��S��T�U��V��Y��[��\���]�!�^�"�_�(�`�+�a�.
�6q	���b�A�d�C�e�E�g�F�ha'���i�K�j�L�nC�q�N�r�Q�s�T�t�u�[�v�\�y�b�|�c�}�e���l��m��p��r��u��x���	�q���
�	���
�
�����a��������q	�����������ad����#��$��%� �+�!�.�"�1�#�9�$�;C�&�A�(�C�)�F�*�+�M�,�N�-�O�.�U�/�X�0�[�1�c�2�d�3�l�5�n�6�p�:�<�t�=�u�>�v�@�{�B�}�C���D��F��!�G�#�H�+�J�-�K�0�L�M�7�N�8C�O�@�P�C�Q�F
�N�R�Y�T�[�U�]�X�Y�c�Z�d�[�e�\�k
�{q	���]��_��`�
�b��ca	���d��e��f��h���gq	�a�i��l��m��n��oa;�s�p� �q�!�t�$�u�&�w�(�x�+�y�z�2�{�3�~�9��?
�G�R
�c��n��o��r���y��zC���

���
�����������$��&��*��,
�<q	����G��I��J��K�aK����R��S� �T�#�X�%�^�f�&�i�(�o�~�*�C�+��,��-��.�/��0��1�
�.�2�9�4�;�5�>�6�8�D�9�E�:�F�<�K�=�R�Z�>�]�@�c�B�e�C�h�D�E�o�F�q�G�x�H�{�I�~�J�K	�C�L�
�q�a�M�*�O�,�P�-�Q�.�R�/�Sa�A�ia�s�U�V�W�X%�Y�[�\�]�^�_�`�)�a�d�e�i�j�k�p�q�r�t�u�v�w�x	�{�}�~��a�t�3�4�5�8�:
�K�V�X�[�b�c�d�&�j�k�m�r�s�t�u�w�z����
�
���
�/q	����:��;��>��?�a"���	�C�
�D�
C��I��K��N���U��V��\��^��a���h��j��l� �o�!�r
�~q	���"�	�$��%��&��'af���(��)��*��+�-��.��/� �(�0�+�2
�CC�1�N�2�Q�3�T�4�5�[�6�\�7�d�9�f�:�i�;�q�<�r�=�s�>�y�@�{�A�}�C�D��E��F��G���H��I�&
�6�J�A�K�D�L�G�M�N	�NC�O�X�Q�^�R�S�e�T�h�U�n�W�p�X�s�[�\�x�]�y�^�z�`�}�a�~�b��c�
�q	���d��f��g��h��ia�A�la���j�k�l�m�$�n�q�r�w�y�z�������	�	a���"�#�$�(�&�)�*�,�/�0�3�5�8�?�@�F
�V�a
�rq	���
�}�
�~�����q
������������
a�A����
��������!��"�!�#�$�,�$�/�&�5�'�8
�Dq
���(�O�*�Q�+�S�-�T�.�UA���/Q	���0��N��O��N��O��N��O��N��OaH���\�5�^�6�a�7�d
�l�8�w��9�
C�:��;��<��=�>��?��@�'
�7�A�B�C�D�D�G�E�F�N�G�O�H�P�I�V�f�J�i�K�p��L��M�
�N�
�O��P��Q��R�C�S� �T�#�U�&�V�-�W�1q	�a�X�7�Z�9�[�;�]�<�^a�A�ma�s�_�`�a	�b�c�e�f�h�i�j�(�n�p�q�r�s�t�w�x�z�{�|�}�~a�t�A�B�C�I
�Y�d�f�h�o�p�%�u�w�z����	����
�+�6
�Gq	����R��T��W��X�a�A�n��va�����	�C�
�����	��*���
�	���_� �!;�"�$�%�&�()�)�*�+�,�-�.�/i�0�2�3�5�6�7�8�9�:�;�<
�=�>�?�@�A�B�C�)�D�F�G�H�I�J�N�P�R�S�T�U�V	�Y�[�\�])�^�`�a�b�c�d�gC�h�j�k�l�m�n�o	�p�r�s�t�u�v	�y�z
�{�|�}������	����J�	�
���
��	���	���	�������	��
� �!�#�$�%�&�'I�(�*�+�,�-�.�/	�0�2�3�4�5�6�7�9$�;�=�>�?�@�A�B�)�C�E�F�Ka�!���\�]F�b�d�g�n�o�u���
� �+�-�0�8�<�B�I
�Q�\
�lD�w�z
��
�"�-�/�2�8�<�B
�S�^
�n�y�|���	C�
� �+�-�/�6�7�?�B�E	�L�V�Y�\�c�d�e�%�k�m�p�w�x�{�}���������%
�6�A�C�F�M�NF�S�U�X�_�`�a�g�i�l�s�t�z�}�	�
������ �&�)�5�9�?�P�SD�Z�]�`�g�h�i�o�r�u�}�~����
�����)�,�3
�C�N�P�S�Z�\�]E�c�e�h�o�p�q�w�y�|�����
��$�&�)�0�1�2�%�8�:�;q	�s�L�@�M�A�N�B�P�D�Qa���R�E�T�G�U�J�V�W�Q�X�R�\�W�^�]�e�_�i�o
�q	���`��b�
�c��d��ea ���f��g��h��jC�k��m��n�!�o�p�(�q�)�t�/�v�1�w�4�<�x�>�{�?�|�C
�Sq	���}�^��_��b��c�a,����h��i��j��p��s��	��
�C��$��'�
�*��2��3��4��:��=��@��H��I��Q��R��T�q	����Y� �Z�!�[�"�^�#a*���$�_�%�b�&�e�'�m�(�n�)�v
��*��+��,�
�C�-�*�.�-�/�0�0�7�1�9�2�A�3�D�4�G�5�N�6�P�X
�iq	���7�t�9�v�:�x�<�y�=a�A�wa���>�?�B�C�%�D�I�N�O�P�Q�R�S�U�V�W�X�Y�\�]�_
�`a���~����(���	�
�������� �"�$	�0�:
�Kq	���a�V�c�X�d�Z�f�[�ga����h�`�i�a�j�bC�l�g�n�i�o�k�q�r�r�s�s�v�y�x�{�y�~�z��{��~�
����
���&��'��*���1��2�	�3�
�9��<�H��K�
�R�b��eC��l��n��q��	�x����
������� ���%� �&�!�'�"�*�$�+�%�2�:�&�=�'�D
�T�(�_�*�a�+�d�l�,�pC�-�v�.�y�/�|�0��1��2��3��5��6��7�8��9��!
�2q	���:�=�<�?�=�B�>�C�?a���@�H�A�I�B�J��xq	�a�E�N�G�P�H�S�I�T�Ja�A�zac�s�K�L�O�P�R�S�T�U�V	�Y�[/�\�^�_�a�bI�e�f	�g�i�j�k)�n�p�q�r�s�t�	�u�v�w	�x�z�{�|�}�~�)��	�����-���	�
���
�)����������� �!�"	�%�'�(�)�*�+�,	�-�.�/�0�1	�2�7�9�;�<�=�@D�A�C�D�E�F�G	�J�L�M�N�O�P	�S�T�U
�Val�t�Y�Z�_�a�d�k�l
�r
��
��'�)�+�2�4D�:�J�N
�T�b�c�i
�z���
���
�+�6
�GD�R
�b�m�}���	�����
�-�8
�I�T
�eD�p���
�
�	��!
�2�=�?�B�I�J�K�%�Q�R�T�Y�Z�]�_�a�d�k�l�r�t�w�~����	������ �$�%�)E�*�,�/�6�7�=�?�B�I�J�P�S�V�^�bq	���W�h�Z�i�[�l�\�m�]a9���^�r�_�s�`�t�a�w�b�y�d�{�e�~��f�
�g��h��i��j��k�C�m�&�n�)�o�,�p�q�3�r�4�s�<�u�>�v�A�w�x�H�y�I�z�J�{�P�}�R�~�TA	�����2��3��2��3��2��3��2a�A�{a1�����
�	���
�	�������I�����	��� �"�#�$�%)�&�(�)�+�-�.�/�0�$�1�3�5�6�7�8�:�;�=�>�?�@�A�Ca2���_�`�p�s�z�|���
������E�$�*�1�3�4�:�<�?�E�F�G�M
�^�i�k�m�s�t�u�y�%�z�{�~�������� �1�5�;
�Lq	���D�W�F�Y�G�\�H�]�Ia���J�b�K�dC�M�j�N�m�O�p
�x�
���P�#
�/q	���Q�:�S�<�T�>�V�?�Wa8���X�D�Y�G�Z�M�\�O�[�]�^�^�e�u�_�xC�`��a��b��c�d	��e�
�&�f�1�h�3�i�6�j�k�=�l�@�m�F�V�n�Y�o�`
�p�p�{�q�~
�
C��&�r�*q	�a�s�0�u�2�v�5�w�6�xa�A�|a�s�z
�{�|�}�~��������)��	�����������a�t	�:�D�G�J
�Q�\�^�a�h�i�j�%�p�r�s�w�x�{�|�~���	q	��� ��"��#��$��%q	���&��'��(��)��*a"���+� �,�#�-�&�.	�.�/�8�1�:�3�;�7�8�@�>�A�?�BC�@�C�B�E�C�H�D�E�O�F�P�G�Qq	�a�H�W�K�X�L�[�M�\�Oa�s�P�`�U�X�c�Y�f�Z�i�[�\�p�]�qq	���_�x�a�z�b�}�e�~�fav���h��i��j��k��l��n�	�o��p�q��r��u��w
��x�'�y�)�z�*C�{�0�}�2�~�5���<��=��>��D��F��I���P�	�Q�
�R��X�
�Z��]���b��c��d��j��l��o���v��x��~��� ��!�"�
�#�
�$��&��'��(�)��*� �+�!C�,�'�.�)�/�,�0�1�3�2�4�3�5�4�6q	�a�5�:�7�<�8�?�9�D�:a�A�}a�s�;�<�=�D�E�F�G�H�I�K�L�O�Q�R�Va
�t�E�F�G�J�M�T�V�^�`�c�g�h�kq	�a�W�l�Y�n�Z�o�]�p�^a�s�_�u�`�v�d�{�f�}�g��h�i��j�q	���m��o��p��q��ra
����s��t�#�u�&�v�)
�1q	���w�<�y�>�z�A�{�B�|a!���}�G�~�H��IC��N��P��S���Z��[��\�	�b�
�e��h��
�o��q��w
�q	�����������aa�����������"��(� �0�!�1�"�2�#�8�H�$�KC�&�Q�'�T�(�W�)�*�^�+�_�,�`�-�f�/�h�t�0�x�1�~�3��4��7�8��9�	�:�
�;�� �<�#�=�*
�:�>�E�@�G�A�J�B�C�Q�D�RC�E�Z�F�]�G�`�H
�g�I�r�K�t�L�w�M�N�~�O��P��
�q	���Q�"�S�$�T�&�V�'�Wa	���X�,�Y�-�Z�.�\�0��~q�a�^�1�`�3�a�4�b�5�c�7�da�A�a=�s�e�f	�i�k�l�m�n�o	�r�s�t)�u�w�x�y�z�{I�~�	��*�������	�
��
�
��J�����#�	������	��)�!_�"�#�$�%�&)�'�)�*�+�,�-�/aH�t�;�<�B�D�G�N�O�U�X�d�h�n
��
����D��-�1�7�G�J�Q
�b�m�o�r�y�z�{�|������(�+D�2�5�8	�?�I�Z�^�d�f�i�p�s�y�	��
�#�.
�>D�I
�Y�d�s�u�v�|
�
����$�'q	�a�0�+�2�-�3�.�4�/�5a�s�6�6�7�7�;�<�=�>�>�A�?�@�H�A�Iq	���D�O�E�R�F�S�G�T�Ha�A���a�4���I)�J�L�M�N�O�PI�S�U�V�W�X�Y�Z	�[�\�]
�^�_�`	�a�c�d�e�f�g�h	�i�j�k
�li�m�n�o�p�q�r�s	�t�u�v�x�y�z�{�|�}	�~�
���������I�	�
���
	�������)��������)� �"�#�$�%�&�'	�(�*�+�,�-�.	�1�3)�4�6�7�9�:�;I�>�@�A�B�C�D�E	�F�G�H�I�J�K	�N�O�Q�R�S�T�U�V	�W�Y�Z�[�\	�]�^I�_�`�a�b�d�e�f�g�h�i	�j�k�l
�m	�n�o	�p�r�s�t�u�v�wI�x�y�z�{�|�~����)������	�
�)��
����	������	��a�:���[�_�e
�v����
�E���� �!�"�(�+�7�:�A�D�P�T�Z�\�_�f�g�h�n�q�t�|��
�D�"�%�(�/�0�1�7
�F�Q�S�V�]�^�_�e�u�x��
�����&�'�(D�.�1�4�;�?�E�G�J�Q�R�S�Y
�j�u�w�y����%��	������� �'�(�.�=�A�G
�X�c�e�g�n�oE�u�w�z��������	����������
��-��8��:��=��D��E��F��L��N��Q��X��\��b��r��vD��|��
����
��&��1��3��6��=��>��?��E��H��K��S��W��]��m��q��w��y��|������D������
����#��%��(��/��2��8
��I��T��V��Y��`��a��b�%��h��j��m��s��t��z��|��������
����!��'
��8q	��� ��C�"��E�#��H�$��I�%aj���&��N�'��OC�*��U�,��W�-��Z�.�/��a�0��b�1��c�2��i�3��l��x�4��{�5���6��
���7���9���:��!�;�<��(�=��)�>��*�?��.�@��/�A��2
��>�B��I
��YC�C��d�D��g��s�E��u�F��v�G��|
���H���J���K���L�M��#�N��$�O��%�P��+��;�Q��>�R��E
��U�S��`�U��b�V��e�W�X��l�Y��oC�Z��u�[��x�\��{�]���^��q	�a�_���a���b���d���ea�A�a�s�f�g�h�i�j�k�l
�m�o�p�q�r�s�ta�t��������"��%	��,��6��8��;��B��C��Dq	�a�u��J�x��K�y��N�z��O�{ad�s�|��T�}��U���X���Z���\���_����f���g�
��m
��|��
�����#���%���(����/���0C���6���8���;����B���C���D���J���M���P� �!��W�"��Y�$��_�%��f�&��n�'��o�(��w�*��y�+��|�,�-���.���/���0�����1����%
��6C�2��A�3��D
��P�4��[�5��^�6��a�7��i�8��jq	΁�9��r�;��t�<��v�>��w�?a�A�aΓ�@�A�B	�C�D)�E�F�G�H�IK�J�K�L�M�N�O	�P�Q�R�S�T�U+�V�X�Y�Z�[�\�]a Δ��|��}��~��������
��/��:��=��@��G��ID��Q��T��W��^��`��a��g��j��m��t��u��}
����������%��&��'q	�a�^��-�`��/�a��2�b��6�ca�s�d��8�e��9�f��:�g��@�i��B�j��E�k�l��L�m��M�p��S��c�q��g��m
��~q	���r��	�t���u���v���wa&���x���y���z��C�{���}���~��!����(���)���*���0���2���5����<�	��=���C�
��F���I�	��Qq	ѡ���[���]���`���d�a2ѳ���f���g���h���k���m���o���r��z���|���}� ���"�����#���$��C�%���'���(���)��'�*��(�+��)�,��/�-��2�.��5�/��=�0��>q	ҁ�1��F�3��H�4��K�5��O�6aVҕ�7�8	�9�:�;�<�=	�>�@�A
�B	�C�E�F�G�H�I�JI�K�M�N�O�P�Q	�R�T�U�Y�Z�[�\�]�^�_�`�a�b�c	�d�f�g�h�i�j�k�yMeT�3KM��V4)�2	�C	�M�W
�o	�}C�LM�K����M�'2M�ub�C=M�&�t �| ��?M�l>�:��y�^�~�2M�%=�s��1�I2�_�Z@�ki�,�>�a���
��\a+ғ��Q��S��T��Z��]��`��g��k��q��s��v��~������
��
������E������!��(��*��+��1��3��4��:��;��<��?��A��D��G��N��Q��W��Y��\��c��d��eq	�1��j��j�/�|�F�#�Rq�B�c��<�&��(��*�(��"�+�#�,�'�-�*�.�,�/�/�2�3�5�6�9q"�1�6�:�7�<�C�:�D�;�H
�?��J�!�%�M�&�,�N�-�0�7�O�=�P�A�S�C�F�T�T�V�[�`�Ua	��X�|��Y�c�Z2D�>�^�q	���(��(��+�#�`O�%a�1�u�G��L � 9�A�0�{�2M�-m�{2M�i3�7��k�:�	2�#H�'�p�W.��u�3	�Q2M�[a���a�5�^�>�7q��)�P�6�w�7�+,�I�r�v�m�|a�1M��`�q�b�T>�{�E�:2M�Kc��}3��.D�
a��aa�1�Jq��R�r�o�I�q�K�w�M�x�O��S��T�	�U�
�W"�
�Z�?�0�\q+�1�_�1�a�3�b�5�e�:�g�;�j�<�k�?�o�B�@�I�q�J�A�L�t�S�u�V�v�X�B�Z�w�\�x�]�z�y�{�_�|�`�~�/�aq.���c��T�d��C�F�f��g��l��p�	��r�G�
�s�
�u���g����������
�
��������%�/a�1E�6�G�|q���I�T��K��(�	�L�
�M��P��Q��S)�5�4�_a�1�f�k"��a�)q ��2�W�7�c�9�e�=�f�>�h�A�j�C�l�X�D�n�S�p�T�q�s�W�t�Z�w�^�x�0�`�{%�eq!�1�����
��>��[��������� ���#�\	�%��/� �6�"�8�$�:�%�<q	��B�'�J�*�K�-I�O�g�a��_a�M�cN�a�19�)�^�cm�v2M�dm�22� 0�<
�m�xL�2�Y�TE�_�m�%2M�q���a�7�f�c�g���*	��t*�a�2�fC�<�B�ga���`a�19�J���k�2�B��Gm�O2�=D�Em�
PK
!<VK�Ǧ�0chrome/pdfjs/content/web/cmaps/KSC-Johab-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�KSC-Johab-Ha�2�x�z��{�~�-��5��
������-�PK
!<ul�C��*chrome/pdfjs/content/web/cmaps/KSC-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�KSC-Ha!"�x�z��{�~�-��5��
�������PK
!<�Q�
�
0chrome/pdfjs/content/web/cmaps/KSCms-UHC-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��A��= ` ^a?��]e�"D�C�;]��"2�f)��"	�C	�M�W�o�(C��<N�K�1���M�'�"]�u�"R�S�-U�&�* �| ��/]�>�"]��"]�z�"]�X�"]�6�"]��"]�r�"]�P�"]�.�"]��"]�j�"]�H�"]�&�"]��"]�b�"]�@�"]��"]�|�"]�Z�"]�8�"]��"]�t�"]�R�"]�0�"]��"]�l�".�J��y�^�~��"]�%�"-���1�I�"�_�ZP�k�"Y�<�>�a���
a���\a�A�u�}�)a]�A�'�A}�[B�Y�s}�
B��%}�?B�=�W}�qB�o�	}�#B�!�;}�UB�S�m}�B��}�9B�7�Q}�kB�i�}�B��5}�OB�M�g}�B��}�3B�1�K}�eB�c�}}�B��/}�IB�G�a}�{B�y�}�-B�+�E}�_B�]�w}�B��)}�CB�A�[}�uB�s�
}�'B�%�?}�YB�W�q}�B�	�#}�=B�;�U}�oB�m�}�!B��9}�SB�Q�k}�B��}�7ap�A�5�O�i� �	�#�=� �]�w�� �1�K�e� ���9� �Y�s�
� �-�G�a� ���5� �U�o�	� �)�C�]� �}��1� �Q�k�� �%�?�Y� �y��-� �M�g�� �!�;�U� �u����)� ��I��c��}� ����7��Q� ��q����%� ��E��_��y� ����3��M� ��m����!� ��A��[��u� ����/��I� ��i����� ��=��W��q� ����+��E� ��e����� ��9��S��m� ��
��'��A� ��a��{��� ��5��O��i� ��	��#��=� ��]��w��� ��1��K��e� ������9� ��Yq	С��j��j�/�|�F�# �Rqѡ2�s��<�&��(��*�(��"�+�#�,�'�-�*�.�,�/�/�2�3�5�6�9q'ҡ�6�:�7�<�C�:�D�;�H
�?��J�!�%�M�&�,�N�-�0�7�O�=�P�A�S�C�F�T�T�V�[�`�U�|��Y��Za>ӡ]�`�"D�>�^��(��"��(��+�#�`O�%�"�u�G��L0� �")�Q�0�{��"]�-�"]��"]�i�"#�G��k�:�	�"�#X�'�"��W.��u�3	�Q�"Z�[�P�6�w�"�7,�I�r�v�m�|�"O��q�b�"�o�T>�{�E�:�"]�K�"S�)�}�#��.T�
a���aܥ�^�N�7�>�+�A�a�+�Jq��b�r�o�I�q�K�w�M�x�O��S��T�	�U�
�W"�
�Z�?�0�\q6��_�1�a�3�b�5�e�:�g�;�j�<�k�?�o�B�@�I�q�J�A�L�t�S�u�V�v�X�B�Z�w�\�x�]�z�y�{�_�|�`�~�/�a��c��T�d��C�F�f��gq#���l��p�	��r�G�
�s�
�u���g����������
�
��������%�/q
�E�6�G�|�I�T��K��(�	�L�
�Mq	���P��Q��S)�5�4�_q��f�k"��a
�)�W�7�c�9�e�=�fq��>�h�A�j�C�l�X�D�n�S�p�T�q�s�W�t�Z�w�^�x�0�`�{%�eq%������
��>��[��������� ���#�\	�%��/� �6�"�8�$�:�%
�<�'�J�*�Ka	��-�
�_�B�7�C��*
�t�N�fC�<�B�ga���g�N�c<��u�`a'�I�O��"9�)�^"�c�"]��"]�d�"]�B�"� ;�<�x�"�|L��"�Y�TE�_��%�"]�5�"R��f�c�g�"�o�	�*��"9�J��
��"]�&�"�B��G�"]�_�"�=T�E�"]�PK
!<�.d�
�
3chrome/pdfjs/content/web/cmaps/KSCms-UHC-HW-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��A��= �` ^�a?��]e�"D�C�;]��"2�f)��"	�C	�M�W�o�(C��<N�K�1���M�'�"]�u�"R�S�-U�&�* �| ��/]�>�"]��"]�z�"]�X�"]�6�"]��"]�r�"]�P�"]�.�"]��"]�j�"]�H�"]�&�"]��"]�b�"]�@�"]��"]�|�"]�Z�"]�8�"]��"]�t�"]�R�"]�0�"]��"]�l�".�J��y�^�~��"]�%�"-���1�I�"�_�ZP�k�"Y�<�>�a���
a���\a�A�u�}�)a]�A�'�A}�[B�Y�s}�
B��%}�?B�=�W}�qB�o�	}�#B�!�;}�UB�S�m}�B��}�9B�7�Q}�kB�i�}�B��5}�OB�M�g}�B��}�3B�1�K}�eB�c�}}�B��/}�IB�G�a}�{B�y�}�-B�+�E}�_B�]�w}�B��)}�CB�A�[}�uB�s�
}�'B�%�?}�YB�W�q}�B�	�#}�=B�;�U}�oB�m�}�!B��9}�SB�Q�k}�B��}�7ap�A�5�O�i� �	�#�=� �]�w�� �1�K�e� ���9� �Y�s�
� �-�G�a� ���5� �U�o�	� �)�C�]� �}��1� �Q�k�� �%�?�Y� �y��-� �M�g�� �!�;�U� �u����)� ��I��c��}� ����7��Q� ��q����%� ��E��_��y� ����3��M� ��m����!� ��A��[��u� ����/��I� ��i����� ��=��W��q� ����+��E� ��e����� ��9��S��m� ��
��'��A� ��a��{��� ��5��O��i� ��	��#��=� ��]��w��� ��1��K��e� ������9� ��Yq	С��j��j�/�|�F�# �Rqѡ2�s��<�&��(��*�(��"�+�#�,�'�-�*�.�,�/�/�2�3�5�6�9q'ҡ�6�:�7�<�C�:�D�;�H
�?��J�!�%�M�&�,�N�-�0�7�O�=�P�A�S�C�F�T�T�V�[�`�U�|��Y��Za>ӡ]�`�"D�>�^��(��"��(��+�#�`O�%�"�u�G��L0� �")�Q�0�{��"]�-�"]��"]�i�"#�G��k�:�	�"�#X�'�"��W.��u�3	�Q�"Z�[�P�6�w�"�7,�I�r�v�m�|�"O��q�b�"�o�T>�{�E�:�"]�K�"S�)�}�#��.T�
a���aܥ�^�N�7�>�+�A�a�+�Jq��b�r�o�I�q�K�w�M�x�O��S��T�	�U�
�W"�
�Z�?�0�\q6��_�1�a�3�b�5�e�:�g�;�j�<�k�?�o�B�@�I�q�J�A�L�t�S�u�V�v�X�B�Z�w�\�x�]�z�y�{�_�|�`�~�/�a��c��T�d��C�F�f��gq#���l��p�	��r�G�
�s�
�u���g����������
�
��������%�/q
�E�6�G�|�I�T��K��(�	�L�
�Mq	���P��Q��S)�5�4�_q��f�k"��a
�)�W�7�c�9�e�=�fq��>�h�A�j�C�l�X�D�n�S�p�T�q�s�W�t�Z�w�^�x�0�`�{%�eq%������
��>��[��������� ���#�\	�%��/� �6�"�8�$�:�%
�<�'�J�*�Ka	��-�
�_�B�7�C��*
�t�N�fC�<�B�ga���g�N�c<��u�`a'�I�O��"9�)�^"�c�"]��"]�d�"]�B�"� ;�<�x�"�|L��"�Y�TE�_��%�"]�5�"R��f�c�g�"�o�	�*��"9�J��
��"]�&�"�B��G�"]�_�"�=T�E�"]�PK
!<�*|���3chrome/pdfjs/content/web/cmaps/KSCms-UHC-HW-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�KSCms-UHC-HW-Ha���x�z��{�~�-��5��
�������PK
!<;"oۦ�0chrome/pdfjs/content/web/cmaps/KSCms-UHC-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�KSCms-UHC-Ha���x�z��{�~�-��5��
�������PK
!<m�7���0chrome/pdfjs/content/web/cmaps/KSCpc-EUC-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��A��=� ` ^`a@��eZh�"D�C�;]��"2�f)��"	�C	�M�W�o�(C��<N�K�1���M�'�"]�u�"R�S�-U�&�* �| ��/]�>�"]��"]�z�"]�X�"]�6�"]��"]�r�"]�P�"]�.�"]��"]�j�"]�H�"]�&�"]��"]�b�"]�@�"]��"]�|�"]�Z�"]�8�"]��"]�t�"]�R�"]�0�"]��"]�l�".�J��y�^�~��"]�%�"-���1�I�"�_�ZP�k�"Y�<�>�a���
a(�A<��<�V�[��]��iB<��?�!<�^��!<�:
�wN��D<�	�H�R�Y�_E�aB<�{�8P�WB<�f�#�!3�B�L<�v�3T
�RB<�]�W�9�rC<�=�z*�!� J<�%#�b�7�\q	С��j��j�/�|�F�# �Rqѡ2�s��<�&��(��*�(��"�+�#�,�'�-�*�.�,�/�/�2�3�5�6�9q'ҡ�6�:�7�<�C�:�D�;�H
�?��J�!�%�M�&�,�N�-�0�7�O�=�P�A�S�C�F�T�T�V�[�`�U�|��Y��Za>ӡ]�`�"D�>�^��(��"��(��+�#�`O�%�"�u�G��L0� �")�Q�0�{��"]�-�"]��"]�i�"#�G��k�:�	�"�#X�'�"��W.��u�3	�Q�"Z�[�P�6�w�"�7,�I�r�v�m�|�"O��q�b�"�o�T>�{�E�:�"]�K�"S�)�}�#��.T�
a���aܥ�^�N�7�>�+�A�a�+�Jq��b�r�o�I�q�K�w�M�x�O��S��T�	�U�
�W"�
�Z�?�0�\q6��_�1�a�3�b�5�e�:�g�;�j�<�k�?�o�B�@�I�q�J�A�L�t�S�u�V�v�X�B�Z�w�\�x�]�z�y�{�_�|�`�~�/�a��c��T�d��C�F�f��gq#���l��p�	��r�G�
�s�
�u���g����������
�
��������%�/q
�E�6�G�|�I�T��K��(�	�L�
�Mq	���P��Q��S)�5�4�_q��f�k"��a
�)�W�7�c�9�e�=�fq��>�h�A�j�C�l�X�D�n�S�p�T�q�s�W�t�Z�w�^�x�0�`�{%�eq%������
��>��[��������� ���#�\	�%��/� �6�"�8�$�:�%
�<�'�J�*�Ka	��-�
�_�B�7�C��*
�t�N�fC�<�B�ga���g�N�c<��u�`a'�I�O��"9�)�^"�c�"]��"]�d�"]�B�"� ;�<�x�"�|L��"�Y�TE�_��%�"]�5�"R��f�c�g�"�o�	�*��"9�J��
��"]�&�"�B��G�"]�_�"�=T�E�"]�`�cPK
!<{�s��0chrome/pdfjs/content/web/cmaps/KSCpc-EUC-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�KSCpc-EUC-Ha���x�z��{�~�-��5��
�������PK
!<����dd-chrome/pdfjs/content/web/cmaps/Katakana.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�` ?�F�PK
!<ٚ�  &chrome/pdfjs/content/web/cmaps/LICENSE%%Copyright: -----------------------------------------------------------
%%Copyright: Copyright 1990-2009 Adobe Systems Incorporated.
%%Copyright: All rights reserved.
%%Copyright:
%%Copyright: Redistribution and use in source and binary forms, with or
%%Copyright: without modification, are permitted provided that the
%%Copyright: following conditions are met:
%%Copyright:
%%Copyright: Redistributions of source code must retain the above
%%Copyright: copyright notice, this list of conditions and the following
%%Copyright: disclaimer.
%%Copyright:
%%Copyright: Redistributions in binary form must reproduce the above
%%Copyright: copyright notice, this list of conditions and the following
%%Copyright: disclaimer in the documentation and/or other materials
%%Copyright: provided with the distribution. 
%%Copyright:
%%Copyright: Neither the name of Adobe Systems Incorporated nor the names
%%Copyright: of its contributors may be used to endorse or promote
%%Copyright: products derived from this software without specific prior
%%Copyright: written permission. 
%%Copyright:
%%Copyright: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
%%Copyright: CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
%%Copyright: INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
%%Copyright: MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
%%Copyright: DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
%%Copyright: CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
%%Copyright: SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
%%Copyright: NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
%%Copyright: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
%%Copyright: HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
%%Copyright: CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
%%Copyright: OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
%%Copyright: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
%%Copyright: -----------------------------------------------------------
PK
!<�^��
�
*chrome/pdfjs/content/web/cmaps/NWP-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE!!��]a)!>�h��(�">�G��"P�&�'�wa!!G�y�B�"
�W�e�m�t���1	���0�&R�J�-U��*�s��H �# �D�~�z�y��a!i�6�:K�7�1��!	�8�H�Oq!.!��!��H�K�M��"�N�4�|�5�}�	���;�9�<��O��&�G�'��
��0�(��)�	a/!�{�.Q/$�z�^�#rI�W�a/5�&Q/7�)q/E�F�N�Q�T�W�Z�`�b�d�k�o��l�j�qQ/e�p�a/u�L�O�R�U�Xq
0!�e�Q�g�"�x�R1��S�3�T�8�U�Aq
1!�C�V	�M�W�X�X�\�Y�`�['�e�\�q2!�!�]�I�^4�+�_�a�`�{�a�~q3!��b��a�!��(�c�D�d�J�e�R�1�U�f�Yq
4! �]�g�r��h�
� ��i�"a5!	�;�j�F�k=�[q6!��l��m�#�2�?�n�H�o�f�p�m�q�t�r�vq7!�w�s�{�t��u��v�%�w�)�x�,�
�2�y�;�z�H�{�P�|a8!�U�}�i�~<�vq9!	�3�
�>�	�J��U�]�o������a:!1���D��X�	�eq	;!�o��v�	�y�
�{�D�q
<!�M��e�
�)�v����q	=!	�+�	�6�4�A��x�
�~q
>!�	���
���(��>��H��Mq	?!�g�-�q�� ��'� �0q@!�E�!�G�""�g�#	��$��&�qA!�#�(�n�+�)�<�*�O�+�R�,�b�-�lqB!��.��/
�.�0�=�1
�D�2�P�3�W�5�^qC!�_�6�f�7�i�9
�m�;1�|�<�/�=�4�>�;q
D!�=�?	�Z�@�e�A�g�B�j�C
�l�5"�xqE!��D��E�"�F�)�G�1�H�:�I�C�J�F�K�M�_�R�M�_�P�i�6�n�N�sqF!�y�O��P��Q�.�S�5�U�:�V�@�X
�C�m�Oq
G!�W�Y�`�Z�p�[��\�	�]� �^�$q
H!�5�_�9�`�C�a"�E�b�i�c�w�d�
qI!��e
��g�;�#�h�%�i	�(�j�3�k�A�l,�Dq	J!!�q�m��n��o�+�p�Jq
K!�O�q�Q�r �X�s�z�3��t��/�(qL!+�-�u
�Z�v�f�w�l�x�o�5�y��z�	�{qM!��|��}�;�~�?�4
�E��Ta
N!)�i-��D�#�H�X�]�`�M4�z�''�0aO6�waNK�.��%���q	Q!�X��u�����	0�aR!]�6qS!��
�$��.��D�
�R��_aYx�'aT!"�r��w�6�"�P$�m��"�.N�0��"]��"�jY�n�">�H
�
�� �"�&�?0�Sa
TD��x�%��>�O��1��;���L��q
[!#���)��.�"�<��O�h�Q�	�Xa^9�m;�a\!]�b�"]�@�"��7#�N�s�"Q�|
�O�"�Z7�`��0�"	�8�CL�Ia^P��"��2�8� �5�4�!�"q	b!	��$;�!�n�^�Q�`�%�eac!(�t�&��'�(�T�2q	d!�R�(�k�\�x�)��*� ae!�0�+�I�,	�K�-7�V�"$��.7�4�"B�l�/�0�0�5�1�>�"�J�2�^�37�e�4	�q	i!�(�u
�*��6�5�D� #�bq	j!���
�6�
�70�#�8�Uq	k!�d�9)�v��!�:�*�;�:alM�*al!+�B�o	�	��",� �N�l�n�"�~��&�5�"C�\�!a	li�<
�=�Y�>�?�@�:�A�B�C�
�Dqp!�:�E�M�F
�_�G�k�H�q�1�I�aq!]�q
r!�v�J
��K��w�L$�$�)	�Jas!/�T�M*��N�1PK
!<j�B���*chrome/pdfjs/content/web/cmaps/NWP-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�NWP-Ha!"�O�L�R�P�O�S�V�Y�T�W�V�[A!k�M�@�r�A�2a%u��I���?�P�g�O�x�~�}�S-�P�OPK
!<��CA+chrome/pdfjs/content/web/cmaps/RKSJ-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE��@�<�?�@�< �g` ^�ga�@>�y,�8�e�m�t��R	���0R�JN>��\�s�i �#�D�SQ ���7	w	WE$$a��]�eC>�C|�a*�@>�|�>C>�;|�zC>�w|�6C>�3|�rC>�o|�.C>�+|�jC>�g|�&C>�#|�bC>�_|�C>�|�ZC>�W|�C>�|�RC>�O|�C>�|�JC2�G,]�zC>�X|�C>�|�SC>�P|�C>�|�KC>�H|�C>�|�Ca�@>�@|�`�?�Fa�@>�||�;a�@>�8|�wC>�t|�3C>�0|�oC>�l|�+C>�(|�gC>�d|�#C>� |�_C>�\|�C>�|�WC>�T"�a��\PK
!<L3���+chrome/pdfjs/content/web/cmaps/RKSJ-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�RKSJ-Ha�A�O
�Q	�S�V�[A���m�S a���PK
!<��,�``*chrome/pdfjs/content/web/cmaps/Roman.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�` ^�gPK
!<i�阼��2chrome/pdfjs/content/web/cmaps/UniCNS-UCS2-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE����!a ^#�A���%�J�	�C�<�O�H
A��3�Z��[AA3���d
.%
.%U "!":a*.)aA %*s;<

	a��!�2�9�J�Iy{�&�(a�p�j�q��a���n��
��p��A
 "h|"�D���A!�f
A ���Aa!`	�W&�x�u�w�v�y�|�{��U�\�i�h�~�}�e�m�n�p�o�d�]
�[�^�Y=�g�ja!p	���2�R>	�z
	�a!��--�,A%�7	Q%P�B��%%%%%��i��%��[��%%%��e��%a%m�>�A�@�J
�"A%��>
A	%��0A%��a%��FA%�I�t6A'=�@A.���H�&a.����� ��!A.��\A.Ɗ	A.���#a.��vO�'�Lce�3��������*�a�[$�Q�y�A�j��a0�:9R�A	�7U��=�5�2�ea.���.��0��1��3�<��FA3��"		OFAN�SA�W45�=
�R#�E�~�Z�T��D���F�!�
�VL�\
��S�6�Z�}�b	�h�M�sz�)�l�
$�6	��<�;	���A�^�e��\�Z��x�u�>�A� �9��t�0�-�u�@�p�5�I6��U�D�u��o��T��_�|�V��=���s3�D�`�;�
�|�g�m"�g�F�CB�P�
� 0?�_	�f��y�p�J7�|�'�(hD��,�O�|�c�	�r�
��5�{�(�,C�E2�t�%�n�Q�"�A��
	� �<�/��)����o�Q>�Nx�7�:��,�'�I	���5�,�1
�\�w�"
��e�0�
�-�&�k�y�%�:�M���
B�(��]�;3�B��1���~��G�X��0�b�;�!�j���u�d��g�.�j�YQ"�>	�G\�P�9<	�
R�c
�Z�B��S�=�8�)�L�0�w	�#9�o�h�<J��~�y	�t.�!)�FJ�t�U�F7�#�h�n.�S��BC�k�+w�	$�d�k��1�5��[�
���X�=���E�z�y
H�%	��6�	���?
�`���g
�p�c�<%�ZEM��.N�C�2$�7�Y�&5�h-�_)�*�s�^(DQ��)�
4�R�p�N��{�{��f���L�)���6=/0
�Px�t�r�s�-7�l7�'�5� �x�'�P�)��B%��C�<	�W,�^�G!�g0�I�6)�"�n�+�#�E\�~A4���{|)�;A�4L��0�&��+%ef+�Z�o�_�?
X�v�/�&�Q�]2�e#�6�s�O:�#,�>�#'�=w|B�&�$�#6
;.)��
GK%2�P�M&�p�o�
��wa�X#B�B�%#
��}�,��,�	�bA�`�. 
D(�gH�lK�#�b
�-�0K�!M�6](�?!�>
��Y�O(�`)�M+�^a
 R�{�6&�*�%��T�K6�Pu�vb���M<�*<=�!<� �.4�5C�@,� �.�6�h(�
�N�grQN�q���=�J�KAN�r	��;aN�nAN&�(�!�$R�|�y�$T�8�5x,P�8AN.�{$�J�KGAN!�m�>�m�6
F>��<aNR�pA
NV�)��;0	�	�n
� �AN\�m"�ANZ�1�f�
�/�~�%aN��aAN��z�� �#.P�,�p�j��,V�(�@AN��tAN����1� aN��.�$AN��2�/�

AN��c�MAN���<��\�k�<a	N��#�&�%�z�y����~aN���~����aN��bAN�x�.��p�u�7Q	N��l�u�~�o�t��`A	N��AO�;AO�HaO�}AO�{�dA	O�E	�nAO��<
��Q
O6�.��F��]������{�AOC�%.5*+AOA�-�2AOB�K��7aOP� QOR�+	����&�+
+� ���y�|AOg�*�[��3�2�?QOr�r�����~��n�q��'����}���n��}AO��2�%AO���$

AO��G�l�9�1�QOǴg�����Y�X�^�k�CAOӴ\QOִh�k�d�W�Z	�k	A
O�f�-�I�A-��B��;�0aO��(AO��9(AP�AO��3�d�^�eaP�3AP�1�KQP�A�<�7���\�A�DAP��=�Z�K�0�WQ
P%�8�j�Q&�,�5��|�;AP3�%�T�E�S��QPE�F
��~�y�~�w�|�uAPQ�Q-�i��H��9�Q	PZ��~�	�|	aPc�JAPe���Q	Ph�8�a#"����~��PAPt�	�\		�:
APr�> �V

�f
AP��N��a�[aP��AAP��@

�|
�zAP��j	�T�VAP��E�i�x�D�i�f�QP������u��\��W�l�yaQ����AQ�
�RAQ��zAQ
�F�Z�
aQ-����7�u�z�hAQ?�\.j�2��V��jAQ;�<AQ9�F��QQY��0�1�h�g�B�x��;�X�YAQe�]��@�t��h��=�aQm�,AQq�
�P�$��kAQ���RAQt��?�c� �SaQ��AQ���^�$�e� ��*AQ��3��g�AQ��#�8aQ��SAQ���L�AQ���jAQ��&�	�Z�+�*aQ��7��I�4��K��[�6QQƐX��"��e��$��-�9�D��$��u��t�A�B�#A
Qܢ�
�e�F�>�2�D�}�0AQ��u�X�DAQہ���{�\�[�FaQ���L�`�u�1�
��aR	���HaQ����
�3��!��YAR�	�t�#AR�G�ZAR�`��-�aR)�;�R�Q��AR6�S	�X	�6AR5��2�/�.�	AR4�{�>�saR^�=ARi�u�G�<ARa�:
�$AR`�)�^
�-aRt�s�K�L�J�
���aRx�w�v�x�#�v�	aRy��#AR��b�R�AR����jAR��?�:��E���0�2�;�:aR��?�>�A�8AR��V�h��n�s�D�,AR���,
� �,AR���\��N�?�p�yaR��PAR�� �*zAR�O	���F� �nAR��%�J�h�xaR��5�y�
��:�AS
�K��k�&�.����T�8�d�v�[�,�HA
S�B�z�!Pd
���<��Y�*AS'���Q�2�8�,aS?�ASA�d$���2��f�X�)�,�0��s�hASL�d�\�sASB��G��	�5�D�{�XaSp��C�D�\�M�`�=aSr�<	�r�p�M�=aSt��{����}AS��N�&AS���,�H�/AS����3QS��A�����s��v�B�5�|�;�3��lAS���_�>�W�
�q"��>�Z�&�J�!�@AS��~,�oAS���"�/�4�5�-�����o�/�2QS�	A
S��"��f���O��@��C
	QT�(�J�S	
��\�CAT�Q
T�G��>��'�f�.��PQT#��#��/
�{�~�s�\�G5 ��(��[�E��.��OaT5�XAT8�V
'&AT7�@	ATK�QUQT`�.�u�x�{��z�_��?��hQ
To�,�0�-�2�O��
	Q	Tz�%�{�^�q�pAT��j��\��[�bQT��u	�r�i��R��g�d�m��v���e��J��7AT��r��f�QT����@�
��Q�I% !�P
�A�V�U��D��[��"QT���S��(��g�+�F�Y�Z�G)�D�?aT��ATǍX
ATϴ{AT��#��Q�T	�nQT�W�O�(��v��o�C�&�I�6AT�o��r��5�Q�<aT��UAT��l�N�Y�(QU�f��J��g�M�.�;�B���7�A'�T��B��yA	U�F��L��E��8�
�K�����rQU*�a��p��S�z��r�}��o�X�{��|AU;�p'�#��8��q�{Q	UC�&������y��r���AUO�*�.�/AUM�^	AUS��.	���c�.��0aUu�[AUw�G��*Q
U{�{�&���3�"�
�$QU���.��Q��q)��f�_����
�� AU��&QU��W
�/��T��W	�j�yAU��		
AU��M	�n	-AU��u��V�C�
�aU��`QUǛY�d�S�6"�o�`�G�V�c�X��N���9�c�8�G�N�&��AU�_�TaU��[AU�b�o�h�AU��AU�|�U�j�Q
U��7	�d�s�3	�J�K�<AV�5���Y
�&aV
�Q	V�.�!��\�C�9�(�Q	V��8�f����@�^�
�>AV%�x��!����!aV/�Q
V2����n�e��0�7���AV?��pAV=�	
�
�

�rAVC�~	�z�"��'�m�OQ
Vh�	�Z	�W�P�U�`	�]AVv�"�\�o��Z�bjQ
V~�G�����AV���(�?�aV��BAV��9��VAV��A�"AV��&�9�D�GaV��AV��w�Y�AV��		�AV���w��^aV��JAVچ-�~�p	�*�ZAV��k� nP�*�4��HAV��]�)�^����
�		�7�:a
W�[�y�t�6�8�s�7�Y��dAW�zAW�!�FAW��H�/�f�^�{aW"�AW(�/	�	AW*�R�zAW2�y�6�"-Q
WI�T�_�^�W
�^AWa�}AWb�AAWT���@�I�x�)aWs�?AWw�z
�L�]�\
AWu�=�&


AWz�_�x�+��!�k�>�BaW��
A	W��i
��#�AW���2
�H�3A	W���d�^	�	���\�^�QWܼy��v���%������AW�QW������L�S�m�x�y	�j*��"QX�{$��� ��{�5���v�AX��z�)�J�
�|QX �������`�m
�AX,�m�%aX0�Q
X2�^���1�L��	AX=��q�4�pQ	XG�B�A�h�Q�PAXQ�g�@�O�ZQ	XW�i�f�e�f��T��]�k��pAXb�p	�fAXc� �:
AXi�e���\QXy�'�2�!
�4�;�6�EAX��(�~
�>�C
�~�8�A#X��<

!
�J	
�j�l�rA
X����T�1�V�g�6��%�:� aX��xAX�{�
�ZAX�z�&�"
�tAX�Y��
�p�C�]aX�� AX��"�q�@�:���@�"�g�>�.��Y�X�c�b�ZAY�AX���\�Z��	�/aY�4AY���N�]��RAY,�,AY��c�PaY7�6�T�p�X��E�A	YI��T�w�X�/�PaYW�AYZ��<�;�Z�v�t�zAY\��2	�8AYe��RQ
Yp��F�,��)�d��p��q�N�CQY{�X�7�:
�/�<AY��{
AY��]AY��0
�/�SQY��q��t��)	�S�8�]AY��QY�������m���>�;��
�x��
�AY��SQYòO��h��]���.��5�t�y�r�u	AY؍uAYֲP�4	#AYف�qQY�x�f�c��:���N%��.�
��!��>�Z��)�K�L�u�T�G����)AZ	�	AZ
�u
A	Z�	�#�K���M��caZ-�pA	Z3�w�H�-�l�E$�E��f�)QZ@�K	�Z!�\���D�EAZL�/QZP�(��@��W6!�8��M�4!�8��M��Z��1�/��:�
�O��n�C	�BAZp�0�Q	Zw�#�$�T�] ���YA	Z���&�a��B��IQ
Z��+�8�Z�|���D1����Q
Z��
��M�>+��~�-�QAZ��y��4�l��QZ��|�r�Y�n	�i�f�y��4�eQ
Z��.�o�v�_�z�&�XAZӁ�QZ��P�9�L
�O�J�A���o�=AZ��]�W�<
�0Q
Z��u�A�*�}��v��A
[�/�r�8�5�HA!Z��,�`�g
�V�&�+�d�a�>�c		�A[
���a[=�	A	[@���p��q��f�q�J�K�a[P���F�3�?�a[V�\a[K�|���1A[\�~�2�N� A[b�W�>�~A[f��N��}Q	[p�Q�0������w��$�W�kA[z�F��\�7�p�U�T�9A[{�XA[|���a[��AA
[��D�z�,A[��c�hA[���i�'a[���OA
[���(	A[��y�
�A[���{Q	[��V�F�I����#A[̘,A[���PA[Ձ��Ra	[��8�8�@�`�:�>�a�?�;A	[�9�h�0�1�n�{a[��YA[�����(�|A[��{�,�RA[�qta\
�/Q\��#�T�S���0��r���/A\� �9��*��E�X�*�&^�.�Y�$A\�:�O�NA\��j��>�<a\@�q\D�[�"�\�!���\�
�4�	���a\T�~��}��;�\�[�B�4�H�n�v�J��#�"�G�`a\c�b�7��a\S�
�gA\��A	\|�_�A\���!a\��	A\��l�I�H��6�wQ\���Z��a��~��
�u��w���t�	A\��*
�JA\��`�0

A\���+��Y�a\��AA\َ�.	
�p	
	A\ֵ@
�n�w�x
�p	A\偌7�+ �"�#�cQ
].�K�����4�U�J-$
A	]G�5���dA5]<�("),
�.�M
�<�[�T�P
�A]>��)��
�a�a�k��k�w�^�1�@Q]��,
��!	
	A]��LA]��5�Z�~��PA]���X�*�_a]��MA]ɬ�6�$j�#�@��4��/� �G�^�,�4�:A]��N�^�*A]ׁ�,a	]��	�K�a�
�I�6���9A
^�I��B�<A^�$��x�6A^	��@�m�Fa^!�JA^%��4����A
^$�N�p
�	�B�6�XA^.��5�T�ia^X�rQ	^[�E��J��e���eA^f�8a^g�6�D�]�M�\�A^o�9�&���Z��0�5Q	^x�1��3�H�^�(�R�'�xA^��A^��&|�"A^����a^��5A^��3�:�2A
^��R�f�uA^��Na	^��Q�O�s�r�p�P��=�t�=Q^Ę?����$�D�qQ^Ё�=�
�e�d�_�V
���!�w����!A^��>�{a^��OA^�)��K��
|�^�*��l�{�x�l�gA^�B�F�$�6A	^��P�^�\�&���a
_�N�?�>�g�M�z��s��0�8A_)�:�6�.��	�A	_(�r�F�l��x�ZA_:��NQ
_H�A��?�"�c��,��E�$��Y��|A_T�v�@�A�n�W��I�T��l�+�����f�h�`�&�K�F�G�J�t�wA_o�A_\�Z�n�ya	_��;�w��1��X����A_���&�	�A_��V�2A_���,�Ta_��BA_���z�i�~A	_��2�@�t��~�:�^A_���a_��QA_ÅP�^�2����`	�A�H�G�BA_ɯ'��&�'� �m�j�u
A_ā�j�A�HQ`	��M�?�
�]�X��tA`�~�m��a�JQ` �!�-�x��x��=��4�c�.�/	�p�A`5�G
�D	�	A`2��.�Z�[		�L�]�X�eA`1��a	�b�?�Q`b�(���
�j�L�U� ���f�e�'A
`��
���A`r�\
�p��{��s�r�
A`u�V�\�}�D�S�KQ`��\�=��H��I�t�<�'�>�/�#� �,Q
`ýj��[����C�Z���/A`јEa`��^A`ՕQ`ׁ�5��K�"�� �1�(�P��s�j�^�|�)A`�	��t�Q`�N�N�_�w�Z�U�~�)�g��b�L�M
�e�nQa�D�K��8��M�u�J�x��a�	�h�|Aa�W�r��~���x��z�Aa�?

�x�I�T�O�p�e�F�YAa�v��|�aaI�U�R����^�2�AaT���Q
aX�X��B��G�p�j��}�x�_��j��qaab�TAad��s�E��1�k��
�zQ
an�G��p��i�r
�i�V�eA
a|�K	�L�?�<�3	Aay�P��
�|�a	�Z�_�X�SAa}�=�r
�5��ZQa��O�f�'�T����~�G�`�L�2�[��
�[�d�kAa��a�[�,���o�Q	a��E�k�9�2�7�<�h�/A	aρ�:�s�1�$��n�yQ
a�E��k�(�#��	aa�� Aa��$�IQ
a���(�|�}�r�?�@�A�$f�<ab�Q�A�Q�P�S�"�!�Q�P��X�ab	�(
�� �aab��3A
b*�Z�z�<�:�v�w�	�t���4Ab#�c�d�Ab,��<
�E�1abB�lQ	bF�!�?�,�~�b�q��0��7�AbP�)�IabS�BAbX�V	�(AbY�r
		�LAbh�Qby�-�P�U�^�_�P�_�b��Z��q�W��X��5�8
�cAb��ab��Q	b���@��
�:�?Ab���[�.Q
b��c�t�s$�B�gAb���U�Z���q�b�{Qb‹Z��h��o��b��5
��q"�v��\�sab��_���nAb؋m
�$��V�@Qb�t�q���P�?��d��z�
��h��dA
c�8��%�z�X�Iac�{�~�1��A�S�/�QAc,�%��N��+Qc1�e�q�m
��H��]�t�S�;�j�-�=Ac@�<�M�<acD�&QcF�K�T�a�>�/
�RAcU�I�.&AcT�3,
�j"	"Acd�G�Q	cu�~�+��J��@��c�#Ac�K��3�*1��6Qc��z�
���0��[�\%���3Ac��L�_�0�)Qc��(�,"�W&!�v�P�IQc���&��	� Ac���W�VQc��]����	#��
���l ��Z�g����	�Qc��Y�g��{�f��
�e��6��=��J����7�g�g�xac��gAc�j�t	Ac��R,�z�
�$$ ?Ac����^�M�Ha
d�(�r���$��p�l��uAd*�Ad$�m&	Ad+�Q
d2��Y��W��.�+��8�=�R�eAd=�!	��k�^Ad@�Ad?�.Q
dX�\�(� �O���Adg�c�j�i�hAde�2��}�bAdq�[adr�Q
dt�	(�)�v�u��
�A
d��[�
��Ad�-�|�\�U�'�0�Ad���#�>Q
d��h��t����%��$Ad��dad��;Q	d��S������(�/
A	dǥ?�0�=�*�)�(�9Ad��RAd�	�4Qd�Z��|��u��Z��}���w	�@�?�Ad��h��h��
�$ad��VAd��T�Z�[�Z�&Ad��.		�L	�.�%Ae
��6Qe�R�?��(��}�i�*�1A	e/�U�S�T�B�t�A	e*�{L��m�T�>�xAe)��R�
�.��&aeH�VA
eO�=		�pAeJ�>��FAeM��
aeb�rAef�q�r�v�<�1Aed�o�4�T�xAek�l�na	ew�o�s�r�t�[�Z�o�n�aA
e��V�8�}�*�-�;��RAe���O�XAe���#�H�{�ae��tA	e��,�v�!�Z�Z��~�d�lAe��"�@Ae��5�L�Mae��uAe��Y�6��(Ae��Y��@�|Ae���'�o�4�^ae��A�Z�@�EAe;
�
�d�$�C�y�6�	�W�X�"Ae��q�|�N�Aeс�*��
ae��ZAe�:�vA
e�x�L�	Ae���JQf�{�\	�g�L	�I�T�e�d�a�b�_Af��1Q
f�	
����z������Af+�
Q
f-�G�������rAfA�b�R�VAf9��n����`A	fD��4�7�:�&�a��P�]�^Q	fv�z�(�-��z��B��;Af��|���\�-af��0A
f��/
�s�j	
�p�
Af���J�Af��	��!	�xQf���H��/��	�
�t����6�1�_�6A	f��c�v�w�+�"�+af��[�\�;�O�c�N��a	f��d�c�Z����u�|	�1af��`�f��8�w�b�r�3�1��Z�Y���Ag�p�
�5��HAg��p�Ag��D	�E�ag��H�dAg�f�R�d�0�|�{Ag����0�VAg��O�	�Yag*�JA	g,�I	�0�JAg3�
�HAg6�A
�2agN�=A	gS�G
	�")AgU�&
�Aga�f�Hq	go����	�<��7�
�FA
gz�?�C�X�e�h�_
Qg���p����]�Z�A�b�A
g��
�V�[�f��r�,�]Qg��4�;������
���}�'A	g��z�;(�?�"5ag��8Q
g˶<�/Q
g�X�j��e�o�.;�e�~Ag�f���*Qg�Y�B7��8��&�9��g�"�;��t�?Ag��P�VAg��0�XAg���U��i��=�V�+�KQ	h�K��L��=��J��q4�}��PA
h%�d�c�6ah2�MQh4�[
��0�\��k�l)�/	��$Qh@�w#�@�?#�����D�����=�^�7AhS�k��B�y����]ahm�A	ho���AQh{�6"�1�*�7(��n��i�l��@��	
Ah��T%A
h��%>!/2'(Ah��*Qh���d���z��x���i�N���Ah���NQhÁ�\���� �'*���&��3���"Q	h���9�fR�Q�
�Ahڙ�Xah��'Ahߙ
	���x4�9�&M
Qh��� �E�s�.�?� �*�
Qi��_�8�?��1�w�D1����kA�]�f�	�t$Ai0�>Ai�$
U�p+$CAi�"E��t	�\Qi;�71�X�u$)��p��Q)��"aiH�$AiJ�@�n;BQ+iQ�<�u���S�"#�S�`�g	�&�9��P��%��$��c)�|��)�X!�9�H�_�?�,�3��"�Z�M�q�h�q�b�O�:8A
i��>���^��Z��/��Z�W*1Q
i��W�A�p� 4��E�?Qi��Z��;2�,��a��E��3�4�A���2
�r�A�;�JE�Ai��RQ
i��w�	���Y�X�?�Ai��aQ	i��>(�?�(�7��hAiӟs�A
i��Q�Z		&#&Ai�<�B
�
�Qi���TM�c�;� �
��P��5�zB
�9��4��sAj��7Qj�}�$>G6�E��F�)�:�;�%��Aj#�u�FAj%�'�L		Aj+�B�P��wQ
j8�N	�L�i�@�I�HAjD�T��6�S�_��'��&ajM�Q
jO�|�b�s�_�,���HAj_�Z		�4Aj]�o$
#"
�$Aje��`�i�j�[Qj��+�8��[�(
�/�0�Qj���e�?�!���o���%�,�:�EAj���g���@aj��b�S�K�\�=��h�:Aj��`�(�5�,�T��Q	j��4�p��J�[Aj��$�)��>Q
j�*��-�=�l�5�<
�IAj��&��w�aj��bAj��xA
j��]�r�m�f	�"Aj���ja
k��F�� �}���C�N�LAk �^��l�A	k%�I�f�P�$�%� Ak�O�P�	�ak7�=A
k9�2�-�,�1�0�$Q
kE�C�q�v��@�E�@�=Akb�_�Z�,�HAkP��.�&�f�DA	kQ�$��K�F�I�h�Nakf�Akj�i��A�"����J�]�X�f�)�(�)��[�Akv�"Ako���3�b�xak��AA	k��@�l�5�.�3�2�ak��e�'�&��%�(��Ak��q�h�$� �=�&��L�n�2�[Ak���8Ak���lak��h�Ak˅a�R�{�v�D��@�Y�<�'�&�!��P�l�\Ak���b�@Ak܁��Nak��i�!�;�<�L�K�y�IQ
k��O�4��H�e�fAl�d�V��qAl�s�0:�,Al	��h�N�<a	l��o�����k�$�"al�.�M�D�t�Dal�+Al0�>�HQ	l3��=����	��8��o��f�T��A
l=����[�0�5�p�@�*����alK��pAlP�l
�HAlO�AlX�:�"al_�jAla�n�"�3�>�K�<Q
lm�:����'�C�>�3�B�A����Alx�0��:��u�XQl}�[����-,�,�E�4al��OA	l��W/A	l��b�]�0�%�JAl��	�2Ql���o��	�2�G�������P��M�tAl��S�S�R�Gal��'Q	l��6�^�k�N�Y�NQlɎp�m��j�����P��K)*�e�L�Q"QlٳR�v�+�D��b��1Al�%Q
l�V�k���>��{�+��%�P�yAl���VQm�U��0��-��0��)-�9��g�*!Am�y
Am�Y*Am�Qm$�C����z��e��!&�01�	��O�a�8��8�1�"�1�am=�{AmA�t�4A	m?�K"
�H
	AmN��X�K�P�
amc�vAme�Amg�#Amq��u�=Q%mt��A�U�6@
��B��Q�s�R�s
�n�s�R�+��v��3�W'��.��3Am��'&Am��FAm���v�{�zQ
m��M���x��9�}�\��hAm¾aQ
mĖ
+�2"�G
�lQ
mϾb�U�F/ &�C1*�&am��WQ	mޕ|�,�C�4:�o�D�Qm��~���'�n�6�+�6�o�V�5��Am��}	��}�VAm��?�1/�
An�z�y�\�Qn�B�-	�n�e�Z��<�q��s�
�>�a�v�c�J�/an5�lq	n8�&�_�:�S�E�>�J�Q�KanC�<�D�G�@AnI�C�1�HQ
nM�9��b�G�.�w��kQnX�1��h�q�}�N��(#��D��X�#Ann�4�(Ank�^!�	�:K�N50Anv�S�	�.�yan��TQn��_�&�3���.��g�]�.�=�H�)��}An��c
Q)n��t�G����0��W��X�i�y��@��E�~;��"����p&�g�>����'�v��n�hAn��h��*�!��� �
an���An���W�>�a�6�Qn��F@�[��t�:	�
�j�S�>�j�3�s�jC�x��Q�yAo�{�i"���Z�+ao�vAo�Q
o������X�)�|�mQo)�	"�j�5�T�c#��~�}�;�r/�x��q�|�r�RAoC�
�jQoN�u��k�f�}�^�&�!�SQoZ�9�Q��^�y�(�{�T#4�uAof�
Aog�J�Aot��CQ
ov�;,���V��S�|��|5>�'���Qo���X�S�@�-�2�@�1�?Ao��K3"-�EQo���9�9�P��7�)�e�z�o�r�\�S
#���
��t��}Q
o��(��t�.�I�V�C��pQ
o��1�H �p�C)A
oӁ�H�q�!��~��w�2
ao��vAo�~

�jAo��
�*�/	�K�J	Ao���7
�Tap�CAp�H�7Q
p�G�q�:
�G�2�;�T�O��N��W�@ap!�/Ap(�� Ap$�4�Q�<�x��nAp,��apL�z�HapC�X�]�[�Z��	apK��T����ApX�IQpZ�#�^��Q�J���-�\�5api�GApk�f��fApq�
�pApl��q�@��"ap|�gAp��C
�Ap��w

�Z	Ap~��U�z�
�A�]�N��!��6�ap��kA
p��e�/��4��A���s�S�����ap��'ApȒ!Qpʒ���r��e����#
ap���YApؒ

�lA	p׺!!

�"Ap߁�[�'Qp�t	��^��I�S�2�
�y�C��p�/Aq�~�^�Wa	q	��}�q�m��u�y�o�HQ	q�|�[��n�/�G�TAq%�v�Y�^�\�nQ	q.�s�M�P�6�-�#��AAqF�rAq:�{�~
)Aq;�E�/�R�mQqI�j��r�?�-�6�K���?��8��[ %AqV�t��9�HQq\�l���*		�!�.���1Aqn�N�(A
qp��<	
	Aqq�@�L�u�$�S��_�t	�*�9� Qq���_�}��v��w�n
��t�>�s�QAq��!�$
Aq��d�,Aq����X�;aq��5�1�wAqȥq
A	q��0	Aq��=aq��@Aqܥv�	
�LAq��
	�fAq݁�x
�o	�X�)aq��nAq��p�@�A	q��T�P�M�LAr	�;�j�ka	r�t��s�S�i�a�`�{�_�
A	r"�$�N�R�K�F��D��E��ar,�EAr0��t�I�<�6�"��Z��J�@�|�A�G�:ArD�"Ar.��yarI�oArK�}�[�x�v��'�XQrU��}��7��6��W�Q�$�%��Z��M�,�1�p�4Arg�I��Arj�z�l�xArf�;ar{�A
r}��N�c�s��R��e�`�ar��Qr����l�q�Q�z�H�M�y�t�Ar��g�	ar��B��:�?��>�=�;A
r��|�r�0���K�����5�4��
ar��Ar��K�;	�<Ar��K	�h�l�s	
�rAŕ�Jar��A
r���7
	�E�H�zQ
r��5���%��,�5��BAs�As��v�	�x�	�rAs�5�hQ
s%�T�p��~��i��2�(�'�M��|Q
s0��i�r�y�F��f�?�mAs>�z�`�e�V�s�rAs@��$��"�~AsA��l�e�Sash�yas^�j�o�rase�H�E�D�J�I�G�#Q	sp������$�b��C�B�G�DQsz���.�T�Z�
�6��#�L�'�h�\�o�2�As����,��)as��OAs��o�DAs��M�z	As��v�&�_Q
s���c�f��|��m��x��u�n	As��
A	s��As��=Qsŷ����o�f�E��|�`�kAsҺB��\��oas��GAsٺ:�+Qs�.����I��J��U�|�y�#�,As�+��FQs�+������P�E�(�)	�a�hQt�"�^�e�b�c�h�r�kAt�`At�
At��l�
�e�L�]�
�-at��tAt ��{��
Q
t(�b�t��v
�e�f

�
at5�\At7�aN�(����Qt?��<�A�F�j��x�e�|�_�6atJ��&�#�!A	tQ��&��U��n�-�;
at[�At]��F����F�o�}��(Qtg�0�z���q��p���q��m�f�z�x�w�nAt~�)A	ty�|�$�)Atz�Q�4at��tAt��'�(At��(�3�(�3At��!�v�U�,Q	t��{��h�	�5�B�C�0At���
�E
�D�At��*�b�L�Y�D�;�JAt��%��/�QtӁ�z��y�&�+�,�+���k�hat��JQ
t�&��S�2�b���k��H��a�]At���,��at��'�1�at��L� �+�*�)�2�3�~�}�R�1�.at��t�^��7Au�v�9�����#�d�IAu�_�J�<Au���au%�cq
u(�\��q�p� ��!�^���Za
u7�r�S�R�{�U����M�@�A
uK�$��tAu[�O�AuJ��?��X�
�aue�+Auj�f�o�,�H�^Aul�#����&�<Aug��@�x�y�x�_�UQ	u��L�U�^��X�u��-��	�	au��W�V�(�&�%�9au��T����Qau���G�-Au���H��-�HQu��?�4�/�>�)��(���;Auǒ8����au��#�4� �"�&�%�!�0Qu��+��t�{��J��M�y�x�y�p��Au��'�,Qu��0�Q�Z�U�X	�O
��~�/Av	�6�lAv�8���	Av�av!�1A	v$�/�{�r
�(� Av#��
�6Av,��J�5�javH�4Q	vL���V�!�2�S�N��%�T�A	vV�w�F��6�A	vW�e
�H�I��`�$Avf�*�|�Favq�#�k���C�/q
vx�*����n�c�u���S�V�tav��Y�+Av��@�h�v�Av��(���
�"�Av���T�	av��	Av��d�8�`���h�*�&A
v��7�^�&�6��T�|�<��&�M�Av���Ua
v��C�7�)�6�U�.�8�s�@�Av�8�v� �.�3�d�CQ
v��i�&��/��B�w�r�s�vAv���Q�T�Kaw�9�5�6�Faw����]�a�_�^�b�c�\�[aw�b���^Aw(�HAw"�e�"��
Aw$�h�l�Yaw1�-Qw3�0�q�X�Y�h�q�j�oAw@��`�R��3awF�/�4�6AwO�t�:AwM�2�	AwX��caw_�BAwa�=�AQwe��f�Y�T�_
�b�nAwr�kQww��)�S��S��~�'�	������Aw��7Aw���<�Aw���aw����a�dAw��
�"�F�O�@�?Q	w���e���H	�G�JAw��s�aw��|�z�{���%a
w��j�H�F�~���e�)��Saw����1��A��A	wڮS�Y�$�z�&��I��L��Faw��Aw�IQ	w��9�	�P�~�D��p��3��~Aw��^�8A
w���Aw��U	�ax�=�'�&�%�"Ax�<� 
Ax�*
�
Ax���Qx%�P�T�a�@	
�/�6�C�P�?�8�G�6Ax7�O
�f
�hAxE�<	�A
x9��V�y�a�M�L�R�Q�P��Uaxd�;Axf��Q
xh�D��n���A
xy�W�"��m�N�K�`��%ax��SAx��$	Ax��MAx��ax��!A
x��?�E�@�v�Ax��X��y�z		�Ax���l���ax��Axţ<Qxǁ�m�+�;�0�#�$��.�/�/Axׁ�r�M���D
ax��s�pAx��n�;�<Q
x�
�H���?��.��m
�C��"Ax���T�Uax��EAy�#�4Ax��C	�JAy�Oay�mAy��Ay�NAy#�	ay'�Ay:�j�hAy*�%�`�Ay)��:���f�O�9�V�g��B�oay@�`AyB�Q	yD�/�_�TQyO�v�A�F	�?���d��w�.�9�X�p�w	�yAyg�?�yayi�@Aym�D�HA
yk�C��.�~Ayq��h��*�
��ay��V�F�DQy��_�z��L�A�<Ay��^�Qy��u�P���(�R�d��9�N�L�iAy��P�a�P�u��r�?ay��CAy��-���V�HAy��E	�I�Ay�M�n�%ay��EAyؒdAyշ0�	Ayԁ�
�[ay��{��~�'�"�HAy��E�n�nA
y��G��4Az��az�aA
z�/
�,�|	Az�h�|	�Az-�Z�s�RQ
zC�l�m�t��?�J�v�%�azM�A	zT�S�W�V	�J�gaza��6�Q�"�!��
�Azt�l��T�F�6A
zu�� �$� 
Az}�7�
az��Az��F�pAz��J��6Az���Xaz��4�k�j�f�J�I�gQz��J��`�}�1�*Az����F�N��]az��V�;�AzǬ(�,�%��&�I�l�H��#��6�E�:�;�8�g�^�z�3Az�
��� �!�b�-Y�_�k�^AzɁ�p�fa{�8A{�h
�F
A
{�
	�A{�F�8�&
�MQ{"�R�	��$��-��|�e����A{8�]� �A�Q{B�mn�!
�-��'������
A{T�		�fA{X�Z�:
A{U�G�>Q
{l��(��q�o��$�+	�C�R�	�A{{��)�a�E�8�=�&Q	{��l�;�B�?�2�HA
{��S�~��z�s�=�2	A{��o
�b
	A{��K��a{��OA	{ɦ�*
�+�.�X��Q{���m�Z�U�^�A�%�Z�M�R�M�D�S�Va{��iA{��ZQ
{��+�t�A� �u��q�{�bA|�X�Q
|	�c�i�h"�)��$A|��a�SQ	|��Y�G
�i�b�_�h�mQ	|%��o�n�a� �7A	|0��"�
��e�^�]a|>�#A|s�wA|@�(��(�`�H�rA|B����N��*�I�D�)�V���P�s��a|x�;�:��-�I�L��=A
|��j�J�v�X�4A|��
	��v�s�t�>�~A	|��\��+
�J	�!�c�.�	�Ja|��YA|΂!A|ʣTA	|��{�6�O�`�a�\�[�$a|����\��A|٨3			�2A|���"�k�V�tA|�Y�a|��-Q	|��!�m�p�	�����K��~A|��{��b���Q#}�O�d�k�d�]	�^�c�:�r�c�R
�i�X
�e�\�m�h�U�P�k	A	}+��{
		A	}(�r
A}%�^a}>�nA	}@�Y�$���-�2Q
}M�]�[�/�:�5�"A}[�C�i
�	�~�H
A}X�`

�F	�A}Z��U+
�NQ	}��]�P�K�\�a�f�]�4�*Q}��'�:�#�R���N
�a����
A}���-�F�Q�fa}��^A
}��g�8�;�P�ia}��
A}́�n�O�Z����D�NQ}��o�o�?	�,�,��c�B�Y�FA	}�]��(�u�7�*�9Q
}��@�S�N�7�H�e��:�oA}��d�,�G��>�S�+Q~�M��W�|
�q�b
A~��u�n�ka~�A	~!��u��4�	�sQ~-�n�s�h�{	�n�c�p�	�t�e�x�g
�b�o�`�m���~�k�dA~L�y�
�q�ta~T�A~Y�A
~V�	
�@�A~e��Ra~i�,A~k�)�� �9Q~r�a�g�f�c�h�f�G�H�v�u�IA~��pa~��mA~��l�Y�Z�.��Q~��"�v��7�L�Q��
�A6�y�4�H�NA9�A	~���;�A
&0a>�q�AC�&�A�pQG���_��*�	�c�|�8�Y�{�V��F��#�YA	X�C�R�a�`�ae�s��EAn�D�R�O�:�Ak�	��V�LAq���a{�'A}��JQ��/�n�x�{�Z��7�^�6�a��A	��D�T��V��A�p�
�.��G�&a��u�b�vA��u�$��}��Q��C�,��k�8�J��F�T�Y�;�n�m�L�4�_���a�6�k�fAŒ|�a��d�e�f�	�q�oa��x�z�~�{�x���a��^Q���`�<�=�G�>�3�za��+�G���Ha������ia��Q
���K�q������)�$��A���A�2a��HA
��W�5��H�O�2A��J�P���#�0��e��|�0��A�9��F�RA� ��<a
�;���4���i���h�zA�R��V�L�2A�Q�y
�F�^�@A�T��@�7��a�q�IA
�s�M��x�#�T�=�]�X�L�e�`A�u��2�\A����~a���A���~�`
�Y�^A���(��			�JA����v�-��(�#�oa���^A�̏`A	�ŷS

A��tQ���}�K�{�~�q�f�aA��a�Z�2�lA��N
�4���(		�t�P�KA�過D�7	�k���$� �9��*�~�d�u
�~Q
�J��9
�4�?�6��#�6�-��Ta�Y��MA	�e�R
�S�JA
�[�

A�m��Da�y�VA��z	�nA
��� 
�\A�|��P�a���jA���m�	A���P	�B

	
A����S�_�eQ���2�E�j
�q����	�n�y�x�D��3�fA����a�ta���A	���m�s�P�O�J�2�^�#a���]Q	����� ��A���4�j��A���d���-��i�>��ra���A���}�h�U�\�<�4�N�H�h�H��#�\�<�(�$A���^�<��q�"�^A��n�vQ	��a��/�D�v�S�k�D�`�oA�%�?����g��*a�*�A�,���h��%Q	�2��K�@�?�@a�<�A�G�Z�LA�?��:	�^	A�>���(a�U�<�4A�f�6A�Z��
�4A�b�za�h�A�k�&��P���n�(�Q
�t��j����W�H����7�i��a���eA����jA���`�x�[A����2Q���z�|�w�`
�}�t�}��`��{
�z��a�t�i
Q���y�
�u	�t�=�6�wA���0��B�lQ�ρ�d�G��
�\�m��L��uA�ۏmQ�ޏz�r�6��;���vQ��t��<��7�j�}�p��Q���c&
��v�7��a��v��q�a	$�J�V�=A��6��
�gQ	��S�S��>��|�i	��iA� �UQ
�$�T,�g�T�e�;A�/�WQ
�1�,�0�7�X�k�~�N��0A�?�G�9�8�=�&.Q�G�Y�}�|	�O	�B�M�z�{A�w�A�V�F�0&9A�W��]
�O�h��a�{�A�}�)
'Q���S���b�e�^	�O�V�"��]�L�S�p�y�X�&��>�C�kA���Q���z�@ 
�a�(
�c��0A���9��bQ���|��8�s�?�v��P01�W�L�U��.�[/A���6�B��%�X*7�RQ���B�}�:�O+�f2CQ����O�38�`�a�
	�d�u�b!A���0&A���BKR=	A���_Q�	�,�e
 �T%&3B�.A
���7�{�s�b�=�	�}��z�s��Q�+�c�o�J�4�;+�;�X&1�C�n3�	�&*�o���cQ
�B�Q0-�C�b��t��m���+72Q�P�ga@�H�1�J�5�q��H�?�5��l�3 �{��d��e��p��W�-�2J�oQ�k�a�v��d�%�C�$�-�Z/(A���[
�dA�}�=FQ	JO�0#A��N�>�f	��.Q����o:�}�\�m�\�c�B:?$�D�oQ���9�<�=$-�]A���1&Q����H
�W��x�.��C�J
A�ġ�(�-�pQ�ɡ�f4�O�r�{�vA	���C�X�U�<�t�ar�@a���A��{A���/A���w	&-
Q����h�?;�y�BA��}�Q���k��[��NJ?���a�z��j��.�5A��g&7H�!Q	�#�x�F�A�
9&5a�,�tA
�.�| �L�i�f��Y�:Q�@�F�0��O�
�+
����Q�Q�L�8�M.
-�F�=���F�A
�]�Z�7�.'

&a�h�9�>�A�]�=�S��kA�~�\A	�q�Q�6'A�s�xQ���.7�m�$�	��w�d��#<�)A���_�jQ
���1�u	�,;'A���e�p�
�~A���A���&
Q
����H�G
�A�ͪ�0A���"�:�3� A��ma���8A���Q���w��v
�B�%Q
���%�W��	��0�M�<A��;�A��&�t�K�	�
�3�.A���>Q
���A�4�#�2��	A�,�*�I�n�>�Y�a�4�'A�8�A�6�,��NA�B�A�Ha�F�_A�M���j�^�0�~�H�n�/A�R�c�2A�K�>�Q�~��(�p�`�ha�m�FA�q�
�j�\A�o�E�p�d
A�r��o�KQ���x!����}�	A���7A���wA����zQ����`�e����5�w�ba���J�MQ���=�e�h��f�e�r�cA�˗�n�s��wQ
���m�;�u�|�y�~�wA��N�F
A���E�>

	A�qa��vQ��i�}
��s�\	A��pA��A
��n()
4a�&�hA�(�Q�m�
0
!�q���
�!�
��Q�L��L�`	���{�j����&
�?�,$a�c�Q
�e�(�K�R�Q�P!��"��CQ	�s��)�T�U� ��L�
A�}��EQ	���h�S��b	��	A����b�g��W�<a���oA���BA���f
'"	A���Q���l�m��
�*��I�:��-&�E��(�w�A
�Ȩo��-�"�T���Q���)� �?�o�P��X�8
&A
���3��,�"��U� �Xa���
A���>���(Q	���?�$��&�J��g�$A���!��&a��,�/�?A��0���4	Q���v�{�~�P�.�3�U�V�M�D�:�)E>�Y�XA�@�A�6�(:�lA�5��^.�pa�C�z�A�H�S��b�v��w���P��r�H�1�Z�i�j�{�n��A�Z�/A�J���PS�T�v�
�]a	�u�}�9�=��<����:A���	�xA����!	
			�A����w�O�i�
�(a���A
���\	�j�oa���R� �1�b��x�~�yA���j�W�wQ�ԝ|�f�A�1�~�}�t�A	���� �E��0�#�(a���qQ����R�#��&�+�$��y��3�	A
��0�U�Z�U�B�]�Xa���2��8�4A��#�LA��wA�$��wa�-�zA�2�G�TA�/��"
	�d
A�C���a	�P�I�S�D��B����Q�\��3�0	�6�a��"�CA�j��A�i�
�F�A�w�ja�y�bA4�{�e��
�b�:��T�3��t�	���7�(����8�y�l�|�c��2��5�T�>�_�n�&�c��H��3�J�W��r�{�>A���i
�A�����^/�k�Da���gA����EQ�����z
�>�OA����~�a���YA
��)�n�$�c��H�=�H�R��9a��A���v
�L

�|A��
��BA��#��)Q
�8��z��������h�C�#a�D�pA�F�`		A�H�sA�I�pa�W�nA�[�^	�v	A�Y�l�@A�g�a�r�A�y��0
�T�M�/�2A�t�	
�r	A�~�%�m��Q	���(	����!�*A���A��I	��D��9Q	���D�G�\�o�|
�_�^A�¤

A���OA���a���GQ
�֤�V�^��A�^�n
A��M
		A���

A��Ya���A	���[��j��s�z�a��VA��~�@Q�
�{�H�*�!�_�T�G�<�?�0�K�F�G�b�c�\�Y�:�6��wA�(�*A�"�a�X
A�-�Q�9�+�f�H�+�C�N�
A�E���� �9�8a�L�;�IQ�P�"�Q
	A�\�E�b���a�X�<�7Q	�e�:�i�h�|��[	�lA�o�G��a�z�A�}��	bBA�~�6��A���
a����-�gA�7�A���T��V��M�8��)�T�b��%�>�A�����B#Q
�E�A��k�x��:�b�k�`�a��}��A�T�c�-�t��Ea�\�m�x�e�����
A	�j�4�P�q�p�F�H�9�6A�{��F�A�o�p�d�a	�������6�5�U��A���_�$Q
���d��$�E��+�v��D��Ea���LA���.�xA���
�F
�A���qQ���"�s� ���	�"�	�pa����� ��
�8A�ӡ7���e�|�{Q	���X�l��G�X�i�^�cq
���,�)�V�(�\�Y�.�/��*A��`�J�TA
���	�2�bA����Ea��3A��M�x�A�	�5�@�x�o�HA���s�;a
��Y�o�B�/����F��0�/�!A	�k�:�l�C�x�	�LA�l��(�]��F��BA�z�a�a���%�*�#�+�(A	���<�$�)�H�Y�T�YQ��� �,�-�3���?��4A	����x�F�A����H�0
�NA���	
�@�.Q����z�q
�%��#A�ߞ'�
���
����
������Q���=�,
�R�i
A�	�Q�
����M�f�w
�L�M	�Ta�&�kA�)�9�ZA�.�e�
A�(��)�l�0a�<�(A�>�#�
Q
�D�a��V�1�_�\��A�S�w�|��nQ�Y�0�
�u�l�s�v�{�z�8�1�A�l�S�zA�m�:A�q��g�=�H�Q���@�e�^�[�Z�g���
�A���A�M�H�I�Ba���B�C�?�!�A���IQ���k@�T�}'���x��A���/��p	�G�r���BA�Ѽ�P	A	����i���g�$�>�w�D�a���~��	��
���A���	��\��o��`Q	����#�$�'A���2��!�	a�	�1�3�?�A�>�@a��>�Aa�
� ���zQ	��v�V�W�q
�nA
�%�F	�H�LA�#�y	�t	�A�-�c�&�a�F�w�7�8�zq	�M�8�W�C�B�A�E�V�D�BQ
�X�E	�*�(�)��Z�O�N�~a�f��iA
�n��k$���=�X�d��>��%��F��uQ���X�,�N��z�B��h�I��-��D�-��$a���kA�Š�l
�A
���i	�B�t
A����F�s�<	�GQ��Y�x
�
��&��3���r���{�~�}A�
�6	A��A��`	�%Q��5�~�	�
�	�A�.��jA�-��JA�3���5Q�I�E���2���~���*A
�X�D

�A�g�A�a�y�\�nQ�r�p����U�F��	��
�B�T�Ya����^a
����:��9�;�Y����a���}�8�GA���A����	A���|Q	����]��x�A����A����r�}�A���D�
a���A�ݓ`�FA�ۼ%	A��a���
�F����
A��	�Q���(� �!�?��2
�E�J�;A��M
��Q��K���4�1�,
A�&��q�Xa�)�Q
�+��?�-�r
�k�j�h�a�5�;���|�~�H�G�Iq�C�H���F�^� ���d�c�*A�R�b�J��vA�P�)�R	�<	�bA�Q�I	�F�Sa�n�OA�p�8Q	�r�,�9�����8A�|�o�Ca���S�P�Q�Ra����
��
�@�A�>a����l�NA���t�Q����O�T�Y�R	�QA�����a���<�J�\��I�[�LA�Ɗ!�f�h�Q�z�P��q� �JA���P��.2"��'�,�.�XA���K�F�K�,�L��8�L�p�2�6�ia���A��N	A��A���xQ���?�\�a�U��D��O�DA��;�O��f��K�J��*a��A��A��I��oA�(����QQ�0�S$3��� ��1�tA	�<��R��	��t���Q�H�Q�w���4�-�&�+�z2�>�3�E��A�Z�[Q�]��W���L�G�+�A�k����s�N�GQ�v�1�d��#�N�5�L�Q��V��Q�>��G�b�^�]%�xA���^Q���Z�4�U�F�K�d#�/�fQ
���4"$�_�b1�MA���_	
A���0	1	A�����_��T�ra���Q�ŤT�4�%�
�d�o
4���j�yA	����p�c#
Q
�く�����

����qA���?�:��n�6a����Q����Y�C�>"�Y��F�w1���UA���@�?
A
��F@!
;A��Q��T#�!�"�[�]��I�|�I��*�����
A�3�x�@
�S�$()Q�@��U�B�{�J��E�K�z�&��e�j�y�a�P�A�R�Q	�T�)�d!.��RQ�^�#�.�M�
�	3�U��4�/����r��]�`�"A�p�'�nQ�s��
���b�0�?�%�0'�,��Q���� ���V�.�I�{�A��A����k�H�K#Q���?� ����~�F�G�:�S�
�2��#	�t�Q
���E��8��=�
�	��+�./A�ë`A����,!!A����L�Q�Q�
�Q���W��'�[�l!�e����o�X�a���bA�߫_A���K�(�
	�A��?�TQ
���Z�%�#,�'�~"�pA��S��jQ��k��/2&#�
��'�
A���H�:�+Q�$�C�g[�R�m����v��A�0�'
�	Q�5��
��������
A�B��r��-�Z	a�H�N�I�G�e�K�H�`A�`�A	�U�H�J�#�-	�,�MA�T��*a�h�&Q�j�1�f�0�#�E�D�I�H6�s�n�:A�{����ya�}�]�d�9A�w����JA	���8�v�?�P�L�`�|�A�����z�U�
6M�$0�oQ
����b�[�f�e	A���U��q��j�?Q
���r�f��������|�`a���`A
�����E�%�&�)��|a���lA���
�PA���i
A���)	a	���*�j�L���J�M�K�cA����"�pA�ܫk�R�;�&�X� �H�A
���l��6�H(�-�,�?�)�ha�K�#A�P�"�A�N�	�0A�V�ga�b�kA�d�p�@	� �'	A�k�&A�i��^a�v�WA	�x�T��.���X�d�i�Wa���#��!�%� �^a���N�P�v�ta���^A���]�A���w�4�FA���-�~a���A����L�x�w�|�#�&�z�n���V�YA���r�z�`A���5a���+�U�bA�˞a���.Q�ҡi��t�U�N�^�+�"�#�,�)�"A
��N�<��v�"�p��nA	���X�H�2�LA���e��6�a��cA�	�e�N	A��:A��t�zQ��I��/���|���)�*��w�r�qa
�+�g�o�V�@�#�1�B�!�2�bA�B�2	�%�A�X�U�LA
�?�Q�B���
h�x�/a�[�A	�^�"����V�z�S
�A�j�*�\�JA�]�o�A
��7a�w�~Q�z�W�c�T�cA	����@��(�/�+�,�+�Q���x�K�F�|���6��C��p�9�2�9A���V�A���,�T�W��
�P�I���H�I�HA����5
�0�1a���LA�ː(�A�Ʈ5@	�C�vA
���,���@�0�;�pa���nA��)��R�4A���Z�M�:�4�\�E���[�K�tA�恊q	���s�$�N�+�_�+�0�\�2a�
�nq
��p���k�o�m��R��<�b�nA
��l�^�_�X	A��H�n�A���h
�t�Q
�C�C	���>�I��J��a�P�7A
�S�\�.��z�{�v��a�b�vA�e�&�TH,A�d�5�b�'�<A�l��5a�s�'��
2�,�]��cA���p�0�;��
Q���7��n�k�z��7�2��~�a���yA�ĬW�U�
�|�tA��2�r�b
�L�
A��x��r�u�n�3�&�H
����9��+a���sA��r�p�u�L�WA��f�FA���ma��OA��q�\A�����A���<�a�+�EA�.�a�S�NA�-�K�h��{�&�+A�7��=a�>�^A�E�wA�A�;�~�
A�@�4��
�OQ
�P�{�E�N�*�_�$�'�"�yA���/A�\�vF�YA�[�W�L�l��@3��G�@��Y�4��fa���|A���ZQ����Y��m���'�"�'�$A���v�xA
���k	
�NA���K�,�(�a���UQ	�դw�B�C�����8�AA�ߤt�Z
A	���	
A����>Q	���S�V�Q
a���YA���:�.�JQ	�	�@���=�4��A��
�
Q��z�
�F�O�,�1�b�YQ
�)��U�P
�*���LQ�4��I�N�S�V�b
�~���#�,�1�*�-�(�-�(A	�H�]�e�\a�U�h���7�W�Xa�S�Y�O�P�.��,�-a�X���gA	�h�<�)�8�"��!�*a���wA���q��)�`Q
����p��m�R�P�L�I�W��b�u��r�I�LA���
�l�/�j�]a���A�֮O�1���ZA�߼3A	���/�t�C�|�/�Xa���{A���&�|A���c�^�A���
�a��KA��fQ��J�*�'�)�$�j�mA��
�a��E�c�k�QA�%�x	A�"�8.�'�T��A
�$�/�E��k�R�{�y�v���^�q�8a�>�t��zA�C�e�M�J�zQ	�J�O�O��X��M�4A�T�-�r�[�l���V�Ta�`�vA�o�A
�d�n	A�i��EQ
�z� �@�K�4�+�xA
���)�&�d�&A,����&�'	��P�~
A���n�h��&y7�1�j�q
��a���A
����v��\�k�U��\Q
��

�8�S���y�pA��_� A���`	A��:�F�x�s�N�Ia�(�OA�-�/zA�+�J�H	A�.�"�7�Q	�9�l�"���~��v�'�,A�D�k�
�Q	�H�i�{�T	�0�AA�T�>A�R�T

�2�<A�S�7	�=a�w�`A
�y�A�d�2h��a�x�`�/�(a���yA��~��T		A���{�p
�f
	
�,A��;	�k	j
wa�.�sA�;�?A
�0�w

A�4��J�9a�B�yA�Q�oA�E�x�P
A�D�+�B�)�_Q�V�e	�]��z�	�s�Q
�g�q��_�T�U��N��	
�K�ba�t�/A�w�"Q�{�5��#�n�Y#$�^�)�M�Fa���+A���$� �Q���3	#!�Ta���4A���1A���.�pA���0a���`Q���]�t�m�S��9�G�P��Q���W�*�C�@�'
�@
�
��Z�7�!�

A���f���c�F�W�D
a���;�^��Q�A�8�@�=A��7
Q
�	�?�|����^�]62Q	��1�$��1��4�}�9��PA�u�c�z��a	�|�=�d���z�|�v�r�1A���0�"��X�W�RQ���1�i�q�h�j���m��@�c�f�m��z��-�Jr�lQ���J��K��<���p��a��`�6�B���g�A����2�D�xQ
���f�0��*����b�u�t���m��0A�̮Q�+��t��fA����X�kA�Ɓ�V�?a���5�0��F�DA�ީC�\�`�$A	���o��`�0A�⁏Xa���sQ���>2�m��n��u�,��n�[��v�
�P�A��u�m���2��=a��~A��|A��s�A��8�r�$�+��Q�"��R�8��S���z�5�0�h�,�-�(�b�A�;��p�ea�@�AQ�B�
�^�@�#�tb2����^��^��A�R�	��l�i�Xa�V�DQ
�Y�C�>�I���9�4�3��N��S�a
�f�9�P�t��s�~�}�|�A�FA�w�@���p�A
�x�����b�*���~�A����s�|�-��Z�4aq���
����������#��%��+��-��0��8��;��=��F��P��W
��e��g��j��o��q��w��|������	����������$��'��1��4��6��9��E��G��I��N��P��R��U��[��_��g��j��m��r��v��y��}��������
����������%��*��/��9��?��B��J��M��O��Q��S��V��Z��_��O��g��m��u��y��|������	����
��������S����!��T��(��,��0��2��5��8��:��U��=��B��E��H��L��T��W��Z��]��a��e��j��nA	��q�0�/a2�
��	������������%��)��.��0��2��6��:��<��D��G��I��P��`��f��i��k��o��x��{������	����������[��!��#��.��3��6��9��<��B��D
��O��S��Y��[��b��d��f��]A��+A��qA��_a���4�8�>�E�I�N�T���Z�]�_	�h�j�p�u�w�y	���	��
�����!�%�'�� �)�+�-��!�1�4��"�8�>�C�F�K�U�Y�]��$�_��%�g�p�r��&�|����	��	��!�(�*�0�2��+�4�8�>�B�F�J�L�P��-�W�[�d�h�l�p��/�s�u�w�z�}����
���	��4�'�0�4�8�F�K�N�R�[�^�a�i�k�s�{�}���
���5�����6���'��8�*�/�8�?�D�L�U�X�\�_�c�g�n�q�s�v�z�}���	�����%�'�+�0�2��;�6�8�;�?�C�I
�T�X�\�^�b�f�k�p�x�}��
�����"�&�-�2�5�7�:�@�C�E�H�L�Q�W�\�_��?�g�j�o�t�x�{���
�������"�(�/�3�6��D�<�?�F�L�O�R�Y�c�g�l�o�t�v�{�~�������� �$�(�1�5�9�<�B�F�P�T�V�Y�\��G�`�d�i�j�m�o�w�y�|�~�����	���A
�$
ae���N�V�Y�]�_�c�j�m�q�r�z�~���������!�'�)�,�3�=�?�C�G�M�R�W�[�]�b�i�n�x����������&�)�.�6�8�<�>�A�D�F�I�N�P�V�Y�^�b�f�l�o�q�t�x�}
��
����$��O�+�-�2�5�9�>�A�C�H�K�M�Q�U�X��Q��U��Z�[��[��]��`��c��fA	�]��7�#4S�e�$a>�=��� �#�%�)�*�+�.�0�2�4�7�9�<�=�A�D�F�H�K�N�O�Q�S�V�X�Z�[�]�_�b�d�f�i�k�n	�t�x	�����	�
�����"�$�(�*�,�0�1�5�8�:�>�B%�Ma����0��2|��P�M��ha_���\��`��b��h��k��n��s��t��z��������������
����������!��'��*��+��,��-
��.��<��>��A��B��D��E��G��I��L��M��N��Q��S��T��V��X��Z��[��\��_��`��a��f��g��h��j��k��l��m	��p��r��t��u��v��w��x��y��z��|��}��~������������	������
���������������� ��!��"��#��&��'��)��,��/
��0��<A
�y��@�o�p�ma9���O�Y��Q��R�[��S��V�\��W��X��Z��\��]�e��_�k��a�����d��
� �"�%�,��g��h��j��k��q��r�:��u��v�=��w��x�B��y�E��{�G��|
�O�������U��	���Z��
�\����A
�_�e�@�7�6�7�6a�������A#���7�&���2�:�%�$����
�?�>�5

��a
�ba��t�#mza�
�ap���1��4��7��:��B��E�K��I��m��o������T��4
��;C��u��|����	1��5������������������7��%��8��'��,��0��4��7��9��:��:��;��C��<��=��G��K��>��O��S��V��X��?��b��f��i��o��A��t��x��z��C��~������D��E��I��N��R��X��Z����\����^��_����`����a����e����h��k��n��p��r��t��v
��x����	������$��a��d��u��z��|�����������!��|	�������4�/�2�4�7q
�3�/�1���
�����a�I�G�K�I�2prt� �M�_�q�l�d�.���/�c��0�Pd�Qg�	�Mji�V�X�Wk�	�m�>��?�4�Ea�A��x�q}�a�bPK
!<k5'���2chrome/pdfjs/content/web/cmaps/UniCNS-UCS2-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
UniCNS-UCS2-Ha
 xzm�b������
��9�1�8�Q��PK
!<L�K����3chrome/pdfjs/content/web/cmaps/UniCNS-UTF16-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE���������!AE���%�%�D�(�	�C�<�O�H�g�^*�g�h��
'
�"�D����Um�,
{
Y*!
3(/�y�1�F�^�7	A	��3�Z��[A�x�xO@�D�(
�EwA8���d
.%
.%U "!":a*.)aA %*s;<

	�3Y<;<�j�Q%P�B��%%%%%��i��%��[��%%%��e��%A(%o�A!
�w
�&�t6�=���"��	�}�$"		OFA/�Ss|}�����$�+�@�A�\�S�`:�e�UBA/��A/��"A/��xA/ҥ	A/�n
$H�>A/��gA/��3A/��`A/��A/��
A%t�0�H�>A�_%���;�Caj��.
�R#�E�~�Z�T��D���F�!�
�VL�\
��S�6�Z�}�b	�h�M�sz�)�l�
�g�	��<�;	���A�^�e��\�Z��x�u�>�A� �9��t�0�-�u�@�p�5�I6��U�D�u��o��T��_�|�V��=���s3�D�`�;�
�|�g�m"�g�F�CB�P�
� 0?�_	�f��y�p�J7�|�'�(hD��,�O�|�c�	�r�
��5�{�(�,C�E2�t�%�n�Q�"�A��
	� �<�/��)����o�Q>�Nx�7�:��,�'�I	���5�,�1
�\�w�"
��e�0�
�-�&�k�y�%�:�M���
B�(��]�;3�B��1���~��G�X��0�b�;�!�j���u�d��g�.�j�YQ"�>	�G\�P�9<	�
R�c
�Z�B��S�=�8�)�L�0�w	�#9�o�h�<J��~�y	�t.�!)�FJ�t�U�F7�#�h�n.�S��BC�k�+w�	�w
�Z�k��1�5��[�
���X�=���E�z�y
H�%��6�	���?
�`���g
�p�&�6<�{%�ZEM��.N�C�2$�7�Y�&5�h-�_)�*�s�^(DQ��)�
�B�p�N��{�{��f���L�)���6=/0
�Px�t�r�s�-7�l7�'�5� �x�'�P�)��B%��C�<	�W,�^�G!�g0�I�6)�"�n�+�#�E\�~A�~.���H�&
�r�s�d�e�X�I�{�)���'0�&��;$�lW��r��%ef+�Z�o�_�?
X�v�/
�R�-�Q�]�f�M#�6�s�O:�#,�>�#'�=>�8�B�&�^%�/
�6
*�`�[.)��
GK%2�P�M�#�#�o�
��w�G�%�r�
?�#"�L��%#
�����)�#�R�'�	�bA�`�. 	�h�c	�:�;�z�yD(�g4�x�
K��L�k
�-�0�^B��<�r	
�1'�:5�7�<��!�>
�P5�<�-�2�3
�g�8c�)(�`�4&�+�^��a
 �bwr�!�a��B]�[�lD�	
�	�'�T-& �6�e�I�	�z��;>�7

H�WT�K6�Pu�vb���M<�T-�<=�!<� n�x?�u+���`�C�@,��&�7�P�C	�4D�yD�a(�
�J$"n�.�[�T�grQN�q���=�J�KAN�r	�p�!AN!�mAN��};�'Q
N*�W��Y����=�J��,��y�n�s�<A"N6��$T�8�5x,P�8�d��;0	�	�n
� ��Gz�� �#.P�,�pAN<�G
"�
�AN7��>��<�;�f�
�/�~�%�$�1� Q	N��-�j��,V��^��7A
N��
�6�/�


�&3A
N��c�M,	A	N���<��\�k�<�]�P�u�7Q	N��l�u�~�o�t��`AN��Q	N����*��I��k�p����A
O	�x	�dAO�=	�nAO��S�/
��Q
O6�.��F��]������{�AOC�%.5*+AOA�-�2AOB�K��7QOR�+	����&�+
+� ���y�|AOg�*�[��3�2�?QOr�r�����~��n�q��'����}���n��}AO��2�%AO���$

AO��G�l�9�1�>�-QOǴg�����Y�X�^�k�CAOӴ\QOִh�k�d�W�Z	�k	AO�L-%(�AO�l�n
$AO�f�-
�~�Q�k�d�^�eQP�A�<�7���\�A�DAP��=�Z�K�0�WQ
P%�8�j�Q&�,�5��|�;A	P3�%�T�E��t��I��QPE�F
��~�y�~�w�|�uAPQ�Q-�i��H��9�Q	PZ��~�	�|	APe���Q	Ph�8�a#"����~��PA&Pt�	�\		�:


�|
�zA2Pr�> �V

�f

	�T�VAP��N��a�[�"��i�x�D�i�f�QP������u��\��W�l�yAQ?�\.j�2��V��jAQ�
�R�z�AQ
��z�Z�a�z�\�_��n��QQY��0�1�h�g�B�x��;�X�YAQe�]��@�t�S��<�P�$��k
�`�^�$�e� ��*�m�L��vAQ���R�r��g���j�Z�8AQj��<	��c� �S�X�8
�5��B�i�+�*l
�]QQƐX��"��e��$��-�9�D��$��u��t�A�B�#A�;Qܢ�
�e�F�>�2�D�}�0��/�B����h�g	�H�U�T�{�(�
	�	�X�V�/�!�N�Y�v�A�D�K�@�=�6
��G���<�0����c�R��f��(���2�K�J�O�p�[���~��s�D�,	���F� �S�*z��W�F�_�l�S�N�9�&�.��(P��t�g�d��8�r��v�[�,�D�*�)�s$���2�d�e�f�X�)�,�0�L�A�s�>�W�J�s��H�%��g���,�O��q�@�EAQ��u� A
Q��"�D�%�d�#�J�h;�3�<AR�3��o��]�%?�ARY�dJ<A7Qہ���l��[�F�D�#�,��s��-��O�>�(�'�i�^
�-�>�I�:��m�2�O��N�?�p �U"��U�Q�2�8�,���	�5�D�{�X�
�	�3QS��A�����s��v�B�5�|�;�3��lAS���_�>�W�
�q"��>�Z�&�J�!�@AS��~,�oAS���"�/�4�5�-�����o�/�2QS�	A
S��"��f���O��@��C
	QT�(�J�S	
��\�CAT�Q
T�G��>��'�f�.��PQT#��#��/
�{�~�s�\�G5 ��(��[�E��.��O��AT8�V
'&AT7�@	ATK�Q�N�%QT`�.�u�x�{��z�_��?��hQ
To�,�0�-�2�O��
	Q	Tz�%�{�^�q�pAT��j��\��[�bQT��u	�r�i��R��g�d�m��v���e��J��7AT��r��f�QT����@�
��Q�I% !�P
�A�V�U��D��[��"QT���S��(��g�+�F�Y�Z�G)�D�?ATǍX
ATϴ{AT��#�|�E�T	�nQT�W�O�(��v��o�C�&�I�6AT�o	AT�RAT,QU�f��J��g�M�.�;�B���7�A'�T��B��yA	U�F��L��E��8�
�K�����rQU*�a��p��S�z��r�}��o�X�{��|AU;�p'�#��8��q�{Q	UC�&������y��r���AUO�*�.�/AUM�^	�6AUS��J	�U��c�.��0�pQ
U{�{�&���3�"�
�$QU���.��Q��q)��f�_����
�� AU��&QU��W
�/��T��W	�j�yAU��		
AU��M	�n	-AU��u��J�7�
�QUǛY�d�S�6"�o�`�G�V�c�X��N���9�c�8�G�N��H��%AU�_�o�h�AU��

AU�|�U�j�Q
U��7	�d�s�3	�J�K�<AV�5���Y
�&Q	V�.�!��\�C�9�(�Q	V��8�f����@�^�
�>AV%�x��!����!Q
V2����n�e��0�7���AV?��pAV=�	
�
�

�rAVC�~	�z�"��'�m�OQ
Vh�	�Z	�W�P�U�`	�]AVv�"�\�o��Z�bjQ
V~�G�����A>V��9��V�Y��:� nP�*��~�H�M�H�Y�X�]�R�Y� �y�Z��<��M��+�<�C�@�?	�J�K	�R�SAW�t�J�nA5V���(�?��M�K�"��9�D�G�		�l��o�J�%�)�^����
�			�t�-�:��/�f�^�{�f
��c�"-Q
WI�T�_�^�W
�^AWa�}
�L�]�\

��#�A)Wb�A�&


�2
�H�3AWT���@�I�x�)�l�
�+��!�k�>�B�6�^�b�m���\�^�QWܼy��v���%������AW�QW������L�S�m�x�y	�j*��"QX�{$��� ��{�5���v�AX��z�)��
��AQX�e�
�|�p��o������`�m
�AX,�m�%Q
X2�^���1�L��	AX=��q�4�pQ	XG�B�A�h�Q�PAXQ�g�@�O�ZQ	XW�i�f�e�f��T��]�k��pAXb�p	�fAXc� �:
AXi�e���\QXy�'�2�!
�4�;�6�EAX��(�~
�>�C
�~AX��<

!
�J	
�jAX���W���T�1�V�g�6��%�:Q	XΧB����/�����A@XةM�Z�&�"�K�
�Z�V��q�@�:���@�"�g�>�.��Y�X�c�b�Z�7��N�]����R�Z�[
�F�I�N�'�~��T�w�X�/�P�&�"�g��U�Z�v�:�G�zAX����
)�AX܁�Z�^�C�]�B�Z��	�/��3�P0�Q
Yp��F�,��)�d��p��q�N�CQY{�X�7�:
�/�<AY��{
AY��]AY��0
�/�SQY��q��t��)	�S�8�]AY��QY�������m���>�;��
�x��
���H��3QYòO��h��]���.��5�t�y�r�u	AY؍uAYֲP�4	#AYف�qQY�x�f�c��:���N%��.�
��!��>�Z��)�K�L�u�T�G����)AZ	�	�p
A
Z
�u
�H�@�E$AZ�	�#�K�\�O���M��c�`QZ@�K	�Z!�\���D�EAZL�/QZP�(��@��W6!�8��M�4!�8��M��Z��1�/��:�
�O��n�C	�BAZp�0�Q	Zw�#�$�T�] ���YA	Z���&�a��B��IQ
Z��+�8�Z�|���D1����Q
Z��
��M�>+��~�-�QAZ���C����4�l��QZ��|�r�Y�n	�i�f�y��4�eQ
Z��.�o�v�_�z�&�mAZс�F�	QZ��P�9�L
�O�J�A���o�=AZ��]�W�<
�0Q
Z��u�A�*�}��v��A
[S���X��2�N� A[�/�r�8�5�H���t�>�~A1Z��,�`�g
�����V�&�+��
�2�7�d�a�>�c		��^�v�q�J�K��u�\�\�8��}Q	[p�Q�0������w��$�W�kA[z�F��\�7�p�U�T�9�z�B�A�@�hA[{�XA[|���
�HQ	[��Z��n��q��pA
[���(	A[��y�
�A[���{�Q	[��V�F�I����#A%[���C�>�G�@�I�@�P��>�M�D�E
�h�0�1�n�{��,�!���j�C�|A[Ձ��R�MqtA[ہ�AQ\��#�T�S���0��r���/A:\� �2��m��*��E��E��i�&^�.�Y�$�&�w�P�R�W�(�B�C�>�W�P�A�L�>�(�_�J��Y�	�8�7�.��=�4�I�HA\m�A\��j��>�<%���5
�7�f�u�T�wQ\���Z��a��~��
�u��w���t�	A\��*
�J�.	
�p	
	A)\��`�0


�n�w�x
�p	A\���+��Y���C�+�
�<�C�"�#�cQ
].�K�����4�U�J-$
A]G�5�A]<�("),
�.�M
�<�[�TA]>��)��
�a�a�k��kQ]w�i��h��]��J��?A]��C�dA]��f
�A]��4�^�1�@Q]��,
��!	
	AJ]��L��6�$j�#�@��4��/� �G�^�,�4�:��w��v�'�U�v6�7�P�I�H��~���	�
��<�x��n�9��<�A��&�'�h�i�d�m�"���X�WA]��5�Z�~��P��^�*A]���X�*�_�X��<�m�F��T�iQ	^[�E��J��e���eA^r�
�Z�A^k�M	�,A^f�8�i�0�G�&Q	^x�1��3�H�^�(�R�'�xA^���F�:�2�A^��&|�"
�2�f�u�|A^�����#�=Q^Ę?����$�D�qQ^��1
�e�d�_�V
���!�w����!A.^��>�{��3��K��
|�^�P�'�J�6��l�{�x�l�g�\�P�5�X�M�:�q�6�~�l�?�:�/�:�;�:�)� �Z�cA^��P�^�\�A^���C�L�E�BQ
_H�A��?�"�c��,��E�$��Y��|Aj_T�v�@�A�n�W��I�T��l�+����|�e�f�h�`�&�K�F�G�J�t�w�r�=�|�y	
�&�L�7�0�)	�2�/�b�Y�Z�[����t��x�}�i�b�c��^��}�^�L���&�'�5���`�t�u��j�u�C�B
�W�J���U�N��A_\�Z�n�y�0 ��T	��	��A�HA_�\Q`	��M�?�
�]�X��tA`�~�m��a�JQ` �!�-�x��x��=��4�c�.�/	�p�A`5�G
�D	�	A`2��.�Z�[		�L�]�X�eA`1��a	�b�?�Q`b�(���
�j�L�U� ���f�e�'A
`��
���A`r�\
�p��{��s�r�
A`u�V�\�}�D�S�KQ`��\�=�����t�<�'�>�/�#� �,Q
`ýj��[����C�Z���/A`јE�iQ`ׁ�5��K�"�� �1�(��,��O�j�^�|�)A`�	��t��DQ`�N�N�_�w�Z�U�~�)�g��b�L�M
�e�nQa�D�K��8��M�u�J�x��a�	�h�|Aa�W�r��~���x��z����pAa�?

�x�I�T�O�p�e�F�Y�^Aa�v�n
�o�|�
�Q
aX�X��B��G�p�j��}�x�_��j��qAad��s�E��1�k��
�zQ
an�G��p��i�r
�i�V�eA
a|�K	�L�?�<�3	Aay�P��
�|�a	�Z�_�X�SAa}�=�r
�5��ZQa��O�f�'�T����~�G�`�L�2�[��
�[�d�kAa��a�[�,���o�Q	a��E�k�9�2�7�<�h�/A	aρ�:�s�1�$��n�yQ
a�E��k�(�#��	Aa��$�IQ
a���(�|�}�r�?�@�A�$f�<A!b�Q�,�O���^�����d�y�z���<�:�v�w�	�t���4Ab�3Ab,��<
�E�1Q	bF�!�?�,�~�b�q��0��7�A
bQ�E�"	�(AbP�)�
		�LAbh�Qby�-�P�U�^�_�P�_�b��Z��q�W��X��5�8
�cAb��Q	b���@��
�:�?Ab���[�.Q
b��c�t�s$�B�gAb���U�Z���q�b�{Qb���Q��o��h��o��b��5
��q"�v��\�sAbՁ���?
�$��V�@Qb�t�q���P�?��d��z�
��h��dAc�8��%�BA
c�p�X�K�`Ac��A�IQc1�e�q�m
��H��]�t�S�;�j�-�=Ac@�<�M�<QcF�K�T�a�>�/
�RAcU�I�.&AcT�3,
�j"	"Acd�G�Q	cu�~�+��J��@��c�#Ac�K��3�*1��6Qc��z�
���0��[�\%���3Ac��L�_�0�)Qc��(�,"�W&!�v�P�IQc���&��	� Ac���W�VQc��]����	#��
���l ��Z�g����	�Qc��Y�g��{�f��
�e��6��=��J����7�g�g�xAc�j�t	
Ac��R,�z�
�$$ ?
+	45&�<�'	Ac����^�M�HQ
d2��Y��W��.�+��8�=�R�eAd=�!	��k�^Ad@�Ad?�.Q
dX�\�(� �O���Adg�c�j�i�hAde�2��}�bAdq�[Q
dt�	(�)�v�u��
�A
d��[�
��Ad�-�|�\�U�'�0�Ad���#�>Q
d��h��t����%��$Ad��dQ	d��S������(�/
A	dǥ?�0�=�*�)�(�9Ad��RAd�	�4Qd�Z��|��u��Z��}���w	�@�?�Ad�[�h
	�Z�[�Z�&Ad��h�#�&		�L	�.�%Ae
��6Qe�R�?��(��}�i�*�1Ae)��?L�D�
��k�,�X��[�T�<�{�P�]��~��x�	�$�	�j�s�p�|��v��:�E��]�1��L�(�N���8�}�*�H�w�N����R�T���f��	�Z�Z�Z�@��~�d��l��/�b��e��]��~�W�R�S�J��
�d�$�C��|�N���W�6�	�W�X�"�>�L�I�L�Y�d	�cAe0�b��
�s�n�H�{�+�[�Y�\��
�6Ae���^Qf�{�\	�g�L	�I�T�e�d�a�b�_Af��1Q
f�	
����z������Af+�
Q
f-�G�������rAfA�bAf9��nAfD��4�7�:��]�aQfY�����n��F��5�x��
����X��]A
fj���#����t��Q	fv�z�(�-��z��B��;Af��3	
�s�j	
�p�
Af��|��J�Af���b�3��!	�xQf���H��/��	�
�t����6�1�_�6AZfĥJ�6�l�T�(�6��C�>�j�E�&�T�_� �
�5��&�_	�F�R�d�0�|�{�V	�0�<�A�0�A�2�H�=�<�I�N�G�<�I
�8
�U�N�M	�"�6�a"!�d�Q�D�[�h�C�X�e�h�_
Ag��p�K���0A f��c�v�w�"��+���J�j�m��L�~	�E��Z�F�Q�I�n�B
�2�k�H�r��HQg���p����]�Z�A�b�A
g��
�V�[�f��r�,�]Qg��4�;������
���}�'A	g��z�;(�?�"5Q
g˶<�/Q
g�X�j��e�o�.;�e�~Ag�f���*Qg�Y�B7��8��&�9��g�"�;��t�?Ag��P�VAg��0�XAg���U��i��=�V�+�KQ	h�K��L��=��J��q4�}��PA
h%�d�c�6Qh4�[
��0�\��k�l)�/	��$Qh@�w#�@�?#�����D�����=�^�7AhS�k!�PA	hk�)AAhU��y��Qh{�6"�1�*�7(��n��i�l��@��	
Ah��T%A
h��%>!/2'(Ah��*Qh���d���z��x���i�N���Ah���NQhÁ�\���� �'*���&��3���"Q	h���9�fR�Q�
�Ahڙ�X�w	���x4�9�&M
Qh��� �E�s�.�?� �*�
Qi��_�8�?��1�w�D1����kA�]�f�	�t$Ai0�>Ai�$
U�p+$CAi�"�z�A��t	�\Qi;�71�X�u$)��p��Q)��"AiJ�@�n;BQ+iQ�<�u���S�"#�S�`�g	�&�9��P��%��$��c)�|��)�X!�9�H�_�?�,�3��"�Z�M�q�h�q�b�O�:8A
i��>���^��Z��/��Z�W*1Q
i��W�A�p� 4��E�?Qi��Z��;2�,��a��E��3�4�A���2
�r�A�;�JE�Ai��RQ
i��w�	���Y�X�?�Ai��aQ	i��>(�?�(�7��hAiӟs�A
i��Q�Z		&#&Ai�<�B
�
�Qi���TM�c�;� �
��P��5�zB
�9��4��sAj��7Qj�}�$>G6�E��F�)�:�;�%��Aj#�u�FAj%�'�L		Aj)��*�Q�P��wQ
j8�N	�L�i�@�I�HA	jC��a����6�S�_��'��&Q
jO�|�b�s�_�,���HQj]�o$�Q�J�U��R��J�o"
�QAj~�lA	jm�p�$Ajq��b�i�j�[Qj��+�8��[�(
�/�0�Qj���e�?�!���o���%�,�:�EAj��]Aj��>Aj���g�)��8�Q	j��4�p��J�[Aj��$�)��>Q
j�*��-�=�l�5�<
�IAk �^��l��hAj��(
�8�l�
�
�f�P�$�%� �l�$Aj����w�r��)�m�f	�"�E�4�L��P�	�Q
kE�C�q�v��@�E�@�=AIkP��.�&�f�D�Q�Z�,�H�>��A�"����J�]�X�f�)�(�)��[��l�5�.�3�2��	�T�h�$� �=�&��L�n�2�[
�W�R�{�v�D��@�Y�<�'�&�!��P�l�\��2�AkQ�$�H�~�h�N�+�w�D�~�8�3�b�@AkR�n��L�3�b�x?�K�N�iQ
k��O�4��H�e�fAl�d�V��q�p��$�J�pAl�s�0:�,��L��rAl	��h�N�<�D�nQ	l3��=����	��8��o��f�T��Al>�T�p
�?
�H�=�RA
l?�-�*�v
Al=���i�'�"Q
lm�:����'�C�>�3�B�A����Alx�0��:��u�XQl}�[����-,�,�E�4A	l��W/A	l��b�]�0�%�JAl��	�2Ql���o��	�2�G�������P��M�tAl��S�S�R�GQ	l��6�^�k�N�Y�NQlɎp�m��j�����P��K)*�e�L�Q"QlٳR�v�+�D��b��1Al�%Q
l�V�k���>��{�+��%�P�yAl���VQl���c����0��-��0��)-�9��g�*!Am�y
Am�Y*Am�Qm$�C����z��e��!&�01�	��O�a�8��8�1�"�1�A	mA�t�4
Am?�K"
�H
	#AmN��X�K�P�
�[�=Q%mt��A�U�6@
��B��Q�s�R�s
�n�s�R�+��v��3�W'��.��3Am��'&Am��FAm���v�{�zQ
m��M���x��9�}�\��hAm¾aQ
mĖ
+�2"�G
�lQ
mϾb�U�F/ &�C1*�&Q	mޕ|�,�C�4:�o�D�Qm��~���'�n�6�+�6�o�V�5��Am��}	��}�VAm��?�1/�
Am���"�Q�y�\�Qn�B�-	�n�e�Z��<�q��s�
�>�a�v�c�J�/An8�&�p�K�0�+�
��	�1�HQnM�9��b�G�.�w��k��8��?��h�q�}�N��(#��D��X�#Ann�4�(Ank�^!�	�:K�N50Anv�S�	�.�yQn��_�&�3���.��g�]�.�=�H�)��}An��c
Q)n��t�G����0��W��X�i�y��@��E�~;��"����p&�g�>����'�v��n�hAn�An��h�>)Anف�i�*�!��
Qn��F@�[��t�:	�
�j�S�>�j�3�s�jC�x��Q�yAo�{�i"���Z�+*Q
o������X�)�|�mQo)�	"�j�5�T�c#��~�}�;�r/�x��q�|�r�RAoC�
�jQoN�u��k�f�}�^�&�!�SQoZ�9�Q��^�y�(�{�T#4�uAof�
Aog�J�Aot��CQ
ov�;,���V��S�|��|5>�'���Qo���X�S�@�-�2�@�1�?Ao��K3"-�EQo���9�9�P��7�)�e�z�o�r�\�S
#���
��t��}Q
o��(��t�.�I�V�C��pQ
o��1�H �p�C)Aoը


�jAo��

�|	�K�J	AoӁ�H�)��"
�TQ
p�G�q�:
�G�2�;�T�O��N��W�@Ap(�� 
�:�Ap$�4�Q�<�x��n�^�bAp,���u�d�oQpZ�#�^��Q�J���-�\�5Apk�f��f�2
�
�.Apq�
�p�d

�Z	Apl��q�@���@��v�z�
�A�]�N��!��0�u�[�P�s�S��Qpʒ���r��e����#
Apؒ

�lA	p׺!!

�"Ap߁�[�'Qp�t	��^��I�S�2�
�y�C��p�/Aq�~�^�W	"Q	q�|�[��n�/�G�TAq%�v�Y�^�\�nQ	q.�s�M�P�6�-�#��AAqF�rAq:�{�~
)Aq;�E�/�R�mQqI�j��r�?�-�6�K���?��8��[ %AqV�t��V��;�9�HQq\�l���*		�!�.���1Aqn�N�(A
qp��<	
	Aqq�@�L�u�$�S��_�t	�*�9� �lQq���_�}��v��w�n
��t�>�s�QAq��!�$
	
�	
�L�@�
�6A*q��d�,	�:
	�f�P�M�L�TAq����X�;�c�t
�o	�X�)	��S�j�k�l�A*Q
r"�$�N�R�z�G�F��D��E��Ar0��t�I�<�6�"��Z��J�@�|�h�+�G�:�h�[�x�/�XArP�IAr.��yQrU��}��7��6��W�Q�$�%��Z��M�,�1�p�4A
rg�I���f�r�s�nArj�z�l�x�"�|Arf�;��sQr����l�q�Q�z�H�M�y�t�A*r��#�`�N���5�4	�4	�w�|�9�2�7�<�h�l�s�o�h	
�[�\�a�h�r
	�E�H�zAr��g�.�p�0�Ar���:
�
�}�&�gQ
r��5���%��,�5��BAs�As��v�	�x�	�rAs�5�hQ
s%�T�p��~��i��2�(�'�M��|Q
s0��i�r�y�F��f�?�mAs>�z�`�e�V�s�r�&As@��$��"�~�*	
AsA��l�e�SQ	sp������$�b��C�B�G�DQsz���.�T�Z�
�6��#�L�'�h�\�o�2���nAs��o�DAs���	�z	As���e�_�&�_Q
s���c�f��|��m��x��u�n	Qs���L�W�f��b��5�DAs��	�UQs¶��P��e����o�f�E��|�`�kAsޒ0A	sҺBAsՁ�sQs�.����I��J��U�|�y�#�,As�+��FQs�+������P�E�(�)	�a�h��N��3�^�e�b�c�h�r�kAt�`

At�
At��l�
�e�L�]�
�-Q
t(�b�t��v
�e�f

�
At7�aN�(����Qt?��<�A�F�j��x�e�|�_�6AtU�}
AtM�&AtL��,�w�yQ	t]��F����F�R��C�}��(Qtg�0�z���q��p���q��m�f�z�x�w�nAt~�)A
ty�|�$�)�Z�3Atz�Q�4	��vQt��%�S��l�O�z��!��$���Q	t��{��h�	�5�B�C�0At���
�E
�D�At��*�b�L�Y�D�;�JAt��%����/�QtӁ�z��y�&�+�,�+���k�hQ
t�&��S�2�b���k��H��a�]Au�1�/�����s�^�
���t�p�o�,�HA&t�� ����(�<�Z�Q��<�o�=��� 	��|�z������&�<�/At�f��(-�0
���c�$�?�"��|��X�
���x�y�x�_�UQ	u��L�U�^��X�u��-��	�	Au��V
�	�Au���~�x
Au���G�5�4Qu��?�4�/�>�)��(���;A	uǒ8�����V	Qu��+��t�{��J��M�y�x�y�p��Au��'�,Qu��0�Q�Z�U�X	�O
��~�/Av	�6�l�{�r
�(� Av�8���	�
�6Av�$�t�5�jQ	vL���V�!�2�S�N��%�T�AOvV�w�Z�[�R
�	�P�Q��|�}�6�H�-���4�4��)�t�T�e�"�4�
�E�H���3�h�b�
�y�t�y���0�T�^�&�6��e�^�'�`��|��P��&��/�h�*�J�K�V�1�h�b�g�n�m�t���v� �.�3�d�CAvs�kAvf�*�|�F�j�e�		��jQ
v��i�&��/��B�w�r�s�vA
v��4	�"Av��
�
�"��
Aw�b�v�m�l�YQw3�0�q�X�Y�h�q�j�oAwO�t�:�N�AAwD�1�	Aw@��`�R�OQwe��f�Y�T�_
�b�nAwr�kQww��)�S��S��~�'�	������Aw��7��"Aw���<��<
Aw���Q	w���e���H	�G�JAwۆg�$�%��L�VAw��z�D�B�HZAw��s��J�O��S�p� �)�F��J�|��-�&�hQ	w��9�	�P�~�D��p��3��~Aw��^�8� 
Aw���
�
Aw��U	���Qx%�P�T�a�@	
�/�6�C�P�?�8�G�6Ax7�O
�f
�hAxE�<	�Ax9��V�y�a�M�L�R�Q�P��U�VQ
xh�D��n���Ax|�%			�.�E�@�v��zAxy�W�b�i	��y�z		�A	xz�i��X!�.���Qxǁ�m�+�;�0�#�$��.�/�/AxڦAx��o
Axׁ�r�M�ZQ
x�
�H���?��.��m
�C��"Ay:�j�hAx���4
�@��`�A$x��J	�
�A�8�n	�:���f�O�9�V�g��B�oQ	yD�/�_�TQyO�v�A�F	�?���d��w�.�9�X�p�w	�yAyh�C�H�4Ayg�?��.�~Ayq��h��*�
��Qy��_�z��L�A�<Ay��^�Qy��u�P���(�R�d��9�N�L�iAy��k�Ay�� �\�D�$�1�fAy��P�p��r�v�nQ	yȌc��\����V����hAyؒd�B�n�nAyշ0�		���4�|Ayԁ�
�[�g�vQ	z*��C��G���+��'�6�5��>A	z7�G�6�z��3	����Q
zC�l�m�t��?�J�v�%�AzW�&�6�>�k�H�/�N�{�|�$�]�X�Y�6�@
�I�<AzT�S	�J��Y�X�
Azr��=Qz��J����%��N��G�	�p�"Az��l�v�9Qz��J��`�}�1�*A+z���"��,�%��&�I�l�H��#��6�E�:�;�8�g�^�z�3��O�P	�G�V��� �)��
Az��9�I	�R
��� �!�b�-Y�_�k�^�o�8�&
�MAz���!�c�f �bQ{"�R�	��$��-��|�e����A{8�]� �A�Q{B�mn�!
�-��'������
A{T�		�fA{X�Z�:
A{U�G�>Q
{l��(��q�o��$�+	�C�R�	�A{{��)�a�E�8�=�&Q	{��l�;�B�?�2�HA{��S�~��z�s�=�2	��A{��o
�b
	
�6A{��K�
�^�Q� 	�+�Q{���m�Z�U�^�A�%�Z�M�R�M�D�S�V�FA{��ZQ
{��+�t�A� �u��q�{�bA|�X�Q
|	�c�i�h"�)��$A|��a�SQ	|��Y�G
�i�b�_�h�mQ	|%��o�n�a� �7A|s�wZ�-Av|0��"�
��e�^�]��f��c��^��M�(��l��*�I�D��]`�P�V���%��t��u����O�r�(���{���}�

�����a�Z�X�c�����#��v�!�"�s�+���c��L��������4�H�6�O�`�a�\���<�$�o���;��J��U		��`��/�B�V�tA|t���  
�~)�-Q	|��!�m�p�	�����K��~A|��{��b���Q#}�O�d�k�d�]	�^�c�:�r�c�R
�i�X
�e�\�m�h�U�P�k	A}+��{
		A
}(�r
A}%�^Q
}M�]�[�/�:�5�"A}[�C�i
�	�~�H
A}X�`

�F	�A}Z��U+�T	�H�QQ	}��]�P�K�\�a�f�]�4�*Q}��'�:�#�R���N
�a����
A}��i	A}��A}́�n��S�NQ}��o�o�?	�,�,��c�B�Y�FA	}�]��(�u�7�*�9Q
}��@�S�N�7�H�e��:�oA}��d�,�G��>�S�+Q~�M��W�|
�q�b
A~�#�,A~��&	A~'�Q~-�n�s�h�{	�n�c�p�	�t�e�x�g
�b�o�`�m���~�k�dA~R�	�0A~L�y�
	
�@��.�9A~[��	��<Q~r�a�g�f�c�h�f�G�H�v�u�IA~��p	�Y�Z�.��Q~��"�v��7�L�Q��
�A6�y�4��M�N�HAC�&�0A	~���;�A
&0QG���_��*�	�c�|�8�Y�{�V��F��#�YA_�z��R�O�:�AX�C�R��8	��V�L�0�JAq���Q��/�n�x�{�Z��7�^�6�a��A��{�N�NA	��D�T��
��z�>A���[�+Q��C�,��k�8�J��F�T�Y�;�n�m�L�4�_���a�6�k�fAŒ|�N�Aǿ}�r�BA�^Q���`�<�=�G�>�3�zA���{�v�v�W��r��s�Q
���K�q������)�$��Ak���A�2�5���b�a�~�7�2�0��e��|�0�\��i�.���`�R�c�^�_�|�7�2�;� �5�V�L�R�U�2�@�E��u�(�\��#�T�=�]�X�L�e�`�h��=�4�9�2�1�6�=�`�v�{�v	�i
�Y�^�p��H
�o�x�u�lA��+� ��@�w�g�A	� ��<"+��n��#Q���}�K�{�~�q�f�aA��a�Z�2�lA��N
�4���(		�t�P�KA�過D�7	�k���$� �9��*�~�d�u
�~Q
�J��9
�4�?�6��#�6�-��TA�[�

�
�Q
�k�U��.�S��c�$�-�"��SA
�x�Q�P	�n�	A�v�$�
�\	�B

	
A�|��P���_�eQ���2�E�j
�q����	�n�y�x�D��3�fA�ͪ�2��<A���A����0�^�2�^Q	����� ��A���4�j��A����>�*�h�U�\�<�4�N�H�h�H��#�\�<�(�$A	����f�;�<��D�7�"�^A�Q�a!�oQ	��a��/�D�v�S�k�D�`�oA�%�?����g��*��+��h��%Q	�2��K�@�?�@A�G�Z�L�h�U�n�(�A�?��:	�^	�n�
�4����+��PA�>���(Q
�t��j����W�H����7�i��A����jA���`�x�[A����2Q���z�|�w�`
�}�t�}��`��{
�z��a�t�i
Q���y�
�u	�t�=�6�w����3A�´?��B�lQ�ρ�d�G��
�\�m��L��uA�ۏmQ�ޏz�r�6��;���vQ ��t��<��7�j�}�p���t��&
��v�7��a��v��q�a	$�J�V�=A��6��
�gQ	��S�S��>��|�i	��iA� �UQ
�$�T,�g�T�e�;A�/�WQ
�1�,�0�7�X�k�~�N��0A�?�G�9�8�=�&.Q�G�Y�}�|	�O	�B�M�z�{A�w�A�V�F�0&9
'A�W��]
�O�h��Q���S���b�e�^	�O�V�"��]�L�S�p�y�X�&��>�C�kA���Q���z�@ 
�a�(
�c��0A���9��bQ���|��8�s�?�v��P01�W�L�U��.�[/A���6�B��%�X*7�RQ���B�}�:�O+�f2CQ����O�38�`�a�
	�d�u�b!A���0&A���BKR=	A���_Q�	�,�e
 �T%&3B�.A
���7�{�s�b�=�	�}��z�s��Q�+�c�o�J�4�;+�;�X&1�C�n3�	�&*�o���cQ
�B�Q0-�C�b��t��m���+72Q�P�ga@�H�1�J�5�q��H�?�5��l�3 �{��d��e��p��W�-�2J�oQ�k�a�v��d�%�C�$�-�Z/(A�}�=F�X�t��]����M��PQ���5JO�M�|��.�e�b�.�A:�}�\�m�\�c�B:?$�D�oQ���9�<�=$-�]A���1&Q����H
�W��x�.��C�J
A�ġ�(�-�pQ�ɡ�f4�O�r�{�vA��{A���C
�V	&�-�~
A��u��t�ar�@Q����h�?;�y�BQ��}���A��[��NJ?���a�z��j��.�5A��g&7H�!Q	�#�x�F�A�
9&5A
�.�| �L�i�f��Y�:Q�@�F�0��O�
�+
����Q�Q�L�8�M.
-�F�=���F�Q�]�Z�7��t�G'

&A�j�>�<A�k�A6�6'A�s�xQ���.7�m�$�	��w�d��#<�)Q����J��W�j���u	�,;'A���e�p�
�~A���A���&
Q
����H�G
�A�ͪ�0A���"�:�3� A�ց�	�5Q���w��v
�B�%Q
���%�W��	��0�M�<A��;�A��&�t�K�	�
�3�.A���B�@�KQ
���A�4�#�2��	A$�-�
�
���j�F�g�0�f�g�H�n�/�j�u�d�u�t�d�u�x�q�bA�,�*�Y�>�Y���"�U��Q�Q�~��(�p�`�hA�r��oQ���x!����}�	A���7A���wA����zQ����`�e����5�w�bQ���=�e�h��f�e�r�cA�˗�n�s��wQ
���m�;�u�|�y�~�wA��N�F
A���E�>

	A�qQ��i�}
��s�\	A��pA	��
	
A��n()
45
0
!,

Q�L��L�`	���{�j����&
�?�,$Q
�e�(�K�R�Q�P!��"��tQ	�s��)�T�U� ��L�
A�}��EQ	���h�S��b	�n�uA���DA
���c
'"	A����b�g�OQ���l�m��
�*��I�:��-&�E��(�w�A
�Ȩo��-�"�T���Q���)� �?�o�P��X�8
&A��$�0A���3�"�/�A���.Q	���?�$��&�J��g�$A�
�=A
����E�4	A��zQ���v�{�~�P�.�3�U�V�M�D�:�)E>�Y�XA)�6�(:�o�Z�(��b�v��w���P��r�H�1�Z�i�j�{�n�������]�rA�Z�/A�5��^.�p���PS�T�v�
�]�P�OQ	�����
������?�}�Q���Q�q�	
�~���-���	A����x�ZA���^	�		�*
A����u�I�c� �Q�ԝ|�f�A�1�~�}�t�A	���� �E��0�#�(Q����R�#��&�+�$��y��3�	A��
�[�L�T�A%��0
�|�"
	�d
�g�`�&A�$��w�C��Q�\��3�0	�6�a��"�CA@�i�
����|�q�6�;�J�V��
�b�:��T�3��t�	��-�(����8�y�l�|�c��2��7�T�>�_�n�&�c��H��3�J�W��r�{�>�D�EA�w�j�|
��JA����	�=�/�k�DQ�����z
�>�OA��
�x�v
�L

�|A����~��7�n�$�c��H�=�H�R�E
�	�4�s�P�QA�)��eQ
�8��z��������h�C�#A"�F�`		
	�v	
�0
�T�M�/�2A�H�s
�@	
�r	A�I�p�E,�m��Q	���(	����!�*A���A��I	��D��9Q	���D�G�\�o�|
�_�^A�¤

A���O��A�́�iQ
�֤�V�^��A�^�n
A��M
		��j	�XA���

�A��Y�Q�
�{�H�*�!�_�T�G�<�?�0�K�F�G�b�c�\�Y�:�6��wA�(�*A�"�a�X
A�-�Q�9�+�f�H�+�C�N�
Q	�E���� �9�8��aQ�P�"�Q
	A�\�E�b���a�X�<�7Q	�e�:�i�h�|��[	�lA�o�G��Q
�}��H�G��h�+�d�iA�7�A���@�m�h���'L��V��M�8��)�T�b��%�>�A����?�u�B#Q
�E�A��k�x��:�b�k�`�a��}��A�U�
A�T�c�H��E��'�j�~�Q�P�q�p�F�H�9��F��'�l��2�RA�o�p�d��7��2Q
���d��$�E��+�v��D��EA���.�xA���
�F
�A���qQ���"�s� ���	�"�	�pA	�ʞ��p��-���e�|�{Q	���X�l��G�X�i�^�cA ��)	�`�J�T�:�x��8H�A�@��l�C�x�	�L
��lA2���V
�(�Z�	�.�}�b�N�S��@��x�o�H�d�[R�I�(�]��F�Z�_��N�S�B�F� A�����uQ��� �,�-�3���?��4A	����x�F�A����H�0
�NA���	
�@�.Q����z�q
�%��#A�ߞ'�
���
����
������Q���=�,
�R�i
A�	�Q�
����M�f�w
�L�M	�TA�)�9�ZA	�.�e�
A�(��)�l�0Q
�D�a��V�1�_�\��A�S�w�|��nQ�Y�0�
�u�l�s�v�{�z�8�1�A�l�S�zA�m�:A�q��g�=�H�PQ���@�e�^�[�Z�g���
�A
���A�M�H�I�B�
�	�B�1Q���k@�T�}'���x��A���/��p	�G�r���BA�Ѽ�P	�4	A����i��.�/�g�$�>�w�D��Q	����#�$�'A���2�A���:	A�
� Q	��v�V�W�q
�nA�%�F	�H�L�L�<A�#�y	�t	��|��|A�-�c�&��Q
�X�E	�*�(�)��Z�O�N�~A���A����d��RA�n��k$��=�]�Q���X�,�N��z�B��h�I��-��D�-��$A����F��;��F��oQ�ɱh��J��	�����}��T���=�p�y�|A	��A��

A��KQ��Y�x
�
��&��3���r���{�~�}A�
�6	A��A��`	�%Q��5�~�	�
�	�A�.��jA�-��JA�3���5�tQ�I�E���2���~���*A
�X�D

�A�g�A�a�y�\�nQ�r�p����U�F��	��
�B�T�YA�����A���:�M�H� �$	�	A���}���hQ	����]��x�A������F�A����r�}�	�V�{�zA���D�
Q���(� �!�?��2
�E�J�;A��M
��Q��K���4�1�,
A�&��q�XQ
�+��?�-�r
�k�j�h�A�9��
�}�|�
�J��vA�P�)�R	�<	�bA�8��|���*�N	�	�F�S�jQ	�r�,�9�����8A���S�BA����j	A�|�o�x�=Q����O�T�Y�R	�QA�Ɗ!�f�h�Q�z�P��q� �J	A���\�h��.2"��'�,�.�XA����������F�K�,�L��8�L�p�2�6�i�T�sQ���?�\�a�U��D��O�DA�
�	A��;��oA����$�U�O��QQ�0�S$3��� ��1�tA	�<��R��	��t���Q�H�Q�w���4�-�&�+�z2�>�3�E��A�Z�[Q�]��W���L�G�+�A�k����s�N�GQ�v�1�d��#�N�5�L�Q��V��Q�>��G�b�^�]%�xA���^Q���Z�4�U�F�K�d#�/�fQ
���4"$�_�b1�MA���_	
A���0	1	A�����_��T�rQ�ŤT�4�%�
�d�o
4���j�yA	����p�c#
Q
�く�����

����qA���?�:��n�6Q����Y�C�>"�Y��F�w1���UA���@�?
A
��F@!
;A��Q��T#�!�"�[�]��I�|�I��*�����
A�3�x�@
�S�$()Q�@��U�B�{�J��E�K�z�&��e�j�y�A�R�Q	�T�)�d!.��RQ�^�#�.�M�
�	3�U��4�/����r��]�`�*A�p�'�nQ�s��
���b�0�?�%�0'�,��Q���� ���V�.�I�{�A��A����k�H�K#Q���?� ����~�F�G�:�S�
�2��#	�t�Q
���E��8��=�
�	��+�./A�ë`A����,!!A����L�Q�Q�
�Q���W��'�[�l!�e����o�X�A�߫_A���K	A��� �<�TQ���b�N�a�%�#,�'�~"�pA��S��jQ��k��/2&#�
��'�
A���H�:�+Q�$�C�g[�R�m����v�����u
�	Q�5��
��������
A�D�_\A�B��r�Q	�:�5�~�J�#�-	�,�MA�T��*Q�j�1�f�0�#�E�D�I�H6�s�n�:A�w����JA�|�_A�{���G��z�U�
6M�$0�o��O�P�L�`�L�Q�Q
����b�[�f�e	A���U��q��j�?Q
���r�f��������|�`A����3�%�&�)��|�Q�F
�r�}�r�w�x��=�$�+�>��1��6��+�4�X��� ������b�y�t�w�t�y�x	�k�j�K���z��@�(���'�"��'	���X�W�L�_�*�5��V�F�v�U�N��`�i�w�|�#�&�z�n���V�Y�V�i���.A�{�,
A�8�Q�)�-*�<
�QA����=�h�H(�-�,}#);�_Q�ҡi��t�U�N�^�+�"�#�,�)�"A��N�<��v�"�p��n�N	A���e�
�!�H�2�L�r�zA����Q��I��/���|���)�*��w�r�qA�0�V��	�%��
����V�z�S
�A�X�U�L�%�\�JA�1�@�|�� �!�<�B���
�f��x�D�u�D�A
��fQ�z�W�c�T�cA	����@��(�/�+�,�+�Q���x�K�F�|���6��C��p�9�2�9A�ː(!
A���V�� @�O�
�v�	�R�4�b�e�v�|�^�_�X	AE���,�T�<���
�P�I���6�o�I�H�:����@�0�;�p�L�M�:�4�<�a�E���[�K�t�.�G�`�H�� �-�5�J�~�w�^�g�X�k�L�Q�Q
�C�C	���>�I��J��A�S�\�.A�W�t�A�V��nQ	�d�5�������:�_�[��A�o�H,6�C��vA�r�r6�+�L�
A�u��
>�5Q���7��n�k�z��7�2��~�A�ĬW�U�
�|�t��p�u�L�W�\��S�N�,A&���7�<�&�o�r�b
�L�
�M�F	�\����h�~�
A��x��>
����9��+�
������,�D��
�OQ
�P�{�E�N�*�_�$�'�"�yA���/A�[�W�v�Hl5�{�@�K�4��[�nA�]��>�_7�v�Q����Y��m���'�"�'�$A���v�xA
���k	
�NA���K�,�(�Q	�դw�B�C�����8�AQ	�ߤt���m��G�L�<�3A����I�D�KQ	���S�V�Q
A���:�.�JQ	�	�@���=�4��A��
�
Q��z�
�F�O�,�1�b�YQ
�)��U�P
�*���LQ�4��I�N�S�V�b
�~���#�,�1�*�-�(�-�(A���vA�M�iXB>A�H�]�\�f�g��U�B�,�w�"�u�J��)�`Q
����g��[�R�P�L�I�W��b�u��r�I�LA�ϭ,�V]�H�1���Z
�|A�߼3A���
�l�<��t�C�|�/�X�R�u��p�o�bQ��J�*�'�)�$�j�mA�%�x	A	��k�.�'�T���SA��
��6
��E��k�R�{�y�v���^�q�8�^�a�zQ	�J�O�O��X��M�4A�T�-��1�>A�U�g��G�T�j	A�i��EQ
�z� �@�K�4�+�xA���)�&�d�&�!A0����&�'	��P�~
�tA���n�h��&y7�1�j�q
���*�^
�[Q
��

�8�S���y�pA��_� zA���`	�H	A	��:�F�x�s�N�I�	�7�Q	�9�l�"���~��v�'�,A�D�k�
�Q	�H�i�{�T	�0�AA�T�>��1�,	���T		�"�\AO�R�T�D�;
�F��<��V�dj�3�`�V�p
����3�&�/
	�4�+�,
�4�1
�n�a�t�g�P
�R�EA�|���L�t�)Q�V�e	�]��z�	�s�Q
�g�q��_�T�U��N��	
�K�bA�w�"Q�{�5��#�n�Y#$�^�)�M�FA���$� �Q���3	#!�TA���1A���.�pA���0Q���]�t�m�S��9�G�P��Q���W�*�C�@�'
�@
�
��Z�7�!�

A��A���f�c�F�W�D
�D
A����QQ
�	�?�|����^�]62Q	��1�$��1��4�}�9��PA�u�c�z�{�*�A	�z�j�3�T�f�"�X�W�RA�{��x�S�L�	Q���1�i�q�h�j���m��@�c�f�m��z��-�Jr�lQ���J��K��<���p��a��`�6�B���g�A����2�D�xQ
���f�0��*����b�u�t���m��0A�̮Q�+��t��f�*�\�`�$A
����X�k��P��`�0A�Ɓ�V�?�@Q���>2�m��n��u�,��n�[��v�
�P�A��{A��?
��A��u��2�C�r�$�+��Q�"��R�8��S���z�5�0�h�,�-�(�b�A�;��p�eQ�B�
�^�@�#�tb2����^��^��A�R�	��l�i�XQ
�Y�C�>�I���9�4�3��"��'�A�j�t����p�A�i�P��b�����b�*���~A����>�#�|Q	����*�fP>A
�����=�N0zC�@܊�2AC�f�@�!�u�I���/�|���7�"�3�d�V�N�j��#�P�h��{�G$�~$�	6�*�%�@�6&�w!�n�c)����=�G
�fE�}!��)�X�t�;H�Fj�$
��5�@�c
�f%�:�K�=%��7�-"�XG�'��2�;F�u�"�`�B0��<�y>�
c�x�Z�V�U�0�Z�K�d��
��
�<�{��|�3�k�7� !��\�E�)�5���m��&�	�|�
"�K�,�~�
�a���M�XW��S�t�K�K �,&�x�?�f#�Q�)�&�iO*�s��4�`�x�4!��[�29
z�	�W�X),�?+�8�M�B���^�4�VS�
	�{U(�^�-&�V�]��� �t
�;-�2�5�S�$�k1�D�m���zs�O�N*�L"�=0�@%8�
�L�1�9�	�k�B�\�9�W�%���X�-�D�
�Q�j��$�+�?�z���6��
��N��]�#T�a�\����0�F�B��#�
�N�j�S��N�L�O�T���F�:��E�z��e��}�V�J�7�$�>�U�% �1
�.
�K�h�;�U�2�{�H�)�(#�'�(�d
�D�R�@�x�N�x�C��`�j�)�)�$�
�*"�n�oQ�?�D�uf�4Z�6�3��'�ZP�,�(4�{-�`H�}�_H�D������>�x�� �n"[�q%
��2#�8�!�g\
�H�_<�&/�i\�i�!��cU�x�m2�f�*����kL�0�;5z.�sD�+��e�<hR�% �&�_�J���W�m���j�}8�s$�z�*�E@�o<�B"�%%�Z�i�n(A�i�F'�I�/�#��`�\�1�G�6(�;F�\y�y�0�
�!/�T�)�,	�*��y��}-�)&�� ��b�i$@
;:�	�9�)�v�a��q����*>���l�7r�43�@�):��A�L0���0�;�r��"�7F��*1
�
�:)�5�{�y&�%�r �[�`�M�p���b���<��m�!k�(��>�{�/�E��z�v5�G@�p�"�Y�.'��[�)�"�{�@%�i:4�z�!��Y�O�3��W �L�`�G�!�j8�W�J4��Z�W�d14�#��<%�
Q�i�'�j�`�� �'��3��W�z�-�j�
�0�7��="�F
�E/�X�=��)�Z���&�
�9�y:�,��0��s�2�U�L�T�C�z/�a�!-�[�j��7�Z&�w�2 �m�o'�x�u�6���<��	#�t�M�/�h�N0�G� �Y�8M�
V�O�pO��e:�1�P<�YP�fiP�j���3�^�Sm�T��'�B�t�K
�F	�;�C�Z�2�o
=�>�q�F�0�h�M�D�X�q�g��1��o�\�O�x�E�V�����R	�~�]�j�z�	�>
�
�O�b�Q��#�l�a�%�Z�$�!C[�@܆��dfB�:��|��YL��P�aH�$��;��Q�%�V����a7�C�v�}pOM+xb:q*@�('
,�!�O2'%[b��8�'~GOFi$H<�C�[0���T%�	�,��Z���i���P�S�*I���r�DE�u��X�j�9�>��Me�5�,w��n�"s;�
W�I5��v��M��6����|��:k�?MH��g�+�S�>����roQw�g�^Cg�@ܔ��\�"�h��6�}?`��K
�q/��?''�B;	dcUb+��
(o�'R>���!� U�T��e�<�B�|���g�E;�?��/�6�Z"��/��F�]+�A%0��;.P	�!�
�1W��f2�8�>���&�{����i�'��_�T&a��m�Q��:��b�h�C_�@�=�5�R��{�'Fs��Fv��5�)�u���W��|�Y�,8�@.bG>8�='*��S)�E(Q�OB�L���H�B�,�Q����D�;L�i�H�4;��b�z��>��#l�H
�W�Sl����@�f�B�P��#�UJ��x�'�=�b��n��	�;��]�6[���k��"�Q�s��h��W�zi�\[�M_�U�|S�_eV��y���H�oc�a4�Z/�7O�R%�M[snni�N��*�K_�a�w�bA�Y��n��E���{��p$$�d��U�S
�R�!��F�L��b�#�2c��M�"C�>�R�>�.�)� �/>�8�o�[��Q	��Y��e�^���@
��\�"�	�J�O�N�;� �K
�(	�!
�6	�'�����?��L9�m,�6�$�1P�Hi��A�4�F%�%����7�nJ�a�
bQch�E�w8�D�I.�6v�8F�]�l���	��;�D�n�F�a�2�a�0�/
)�c�j�e�@$�sK�1�<_�w��c�T�)L�l�5��^��u�:�SU�
�Md�>e�9�4�O�99T�I?�i�Z�;���5�o�G.�1<!!"��J:�*8�M�q"�N]�(0�3��,���F�g�*�]2��y|�K8�D�Q�.�z�o�n�d��}l�X6�4�e���G�`�V�.�K�#
�P��
x��
�(�B
�S-�R*�[r�I�d��;�H�G��N�#�E<�T;��-��26
�.�p(�X��2!��p�Q3�"�/6���)�i�>=���:��4
�Q�`�+�}
�2C�X�j��z�`��F��>C�S�끋td��-d��t���d��6��0�U��L`��)O�38C�R�O��Q
��kQ�n��|�p��/���B4�a'CS�R�w��%�7,�s	�6�X,�`^���/W�o�Jw�Y"�n+��i�E�����K�+w����
,'+�R��
�7�N�j_�2�1�����j$�kR�f�}�@@�_�K�Z^e��dC�pY��h�*�G�=U�8�)��H�fa�g~�uV�S �'�8�:�`��5�5G9�	�,�o�F�GF��6��^�$�l3�k^�@2�`�&
H7�qA��#�V(]�S	�[�"���)�&�u�c�.�2�C�]��8C�U�[�Q�%N�^��7�q�
�{�<�mB�7�x���o
�?/�a�� (�2�&��TM�D��V�,�
�N�>L�r�2*�^�-<�9�3��r�{�84�_&�D�A*�2��L�)=�E�I�%�,��6�g�'���a���m�@.�<�[�T�>
�%�_�
�S�7�&�D
�W�z%���j�,���WU�n%�Lm�y/�i�*��T
�8�w�5�H���W��
�>�+�
���p�@�	:$�Bp�<�5�^�j�J[���M�<�B��z)�w�$s'�W�n2�[�&=�rD�F�g5�7!�;*�,�[����E`�H�n�9�.{�r��_
�)�o�|,�+
Q,��p�&����R�Z�
�"�i�@�w��c��8��s��w�N`�/�n
�g�P�\�3�H�<�75�.=�*��2�C#�f��Lt��P�d�MA�F�G�f�{�6
��e�l�6�-�c�:�9�V(�V�-�F'��E�v�}�H�Z�}��=
�z	�y���*�W�u�i�.�g�j�3�d��a�yd�x��g�V+�W0�R�b3�G%�p
�5�� �#��@�e�\�I�N#�{�k� �p?�Y"�R+��/,�)��1$�.�Z��>��
� �q��
�L�+��3����wL�.E�i�z�C)�S�<?�Y+�@��n�7�G�O��W���}=�X�=m�(&�{:�AC�n�
�m&�t��(�1�2'�J�{�f$�;�nA��J*�G�4*�)k��V�I*�n��T���:�0B�'��*�K���}.a�R&�'
�L,�J�O�._�S�	�
�C�`C�\���}���� ���|o�h��;	��8p�It0P�����"�}��
��e�T�L�P��o�M�	�&�k�?�N�%C�[ޙ��&��oP��w=�:��i��R����D��sq�!b��$EC�^�g��yC�~�;��kCs�[�t��9�-�r�+��3D�sz�l�B���!a�rG#�$:?V�eh�1��2�3*�X/�Yk�D�,�0��P�u���B�u�Y����Y�^�	�}��|#D%i�g6�;�<���=$:c9 _j�k(Z��]�A�@\���}�8H�u�$J�%*�T+�#"�f'���<@�M�'�D�%T,�X�%�
��4�/�.��/��3�r�#+!���6�8�9�y��(�v�qA�&��! Xd�e��d�T�f�U@��G�*���V�V�-��]�s&�~D�}Q�P��y�Q�]^��U�8�@�SA#��:�4�a�E�h�:��G�!(�M�mb�r�15P<�j�I2�J�'�i
��t�w�E�s�n:�C2�}-�pL��$�y��p�T��WA�@�h��M�m�^�.�m�2H�k�A^�
�

+�}�|��4
�1�0V�U�T�y	�xR�$�	�	 �������c
�b�3�2�Y�^
�	�k��%:�c�b8��4"2/@D<�7�6G�/�]�2�oQ	酁�J�>���?�]��e�dA!��.�%��#�6���-��(�3>�Y�X�U�2
�/�X�Y�:{��<�g~�$~A��_�#�s���)�mA�^���jA랥i*�4A�B�%C�\3�aF�;�"�f�'A���$�'|�^���)s�t�yA�n�Y:�(A�@�PA�{�a����K�h�c�_�T�U�T�U�`�	H�#�d�e�d��W�V�]�"�#�:����'�T�U�T
�S��3�2�=�<�;&�
�X�Y�h��y�b���b��}���)�g�h�8�=
	�k�<�=�<�=�<�U	�i�\�]�l�m�H�M��		�,�-�,�-�,�!�a�d�>�7�2
�%�*�+�*���+�*�+�(�)�(�)�,�K�J�#�,�!� �!	�$�����
���
�	���}����Y�`
�#�(�_�K�8�3�2�3�6
�;�{�D�E�N�O�v�o�n�i�n�o�n�o�v�o�v�q�t)�c�f�g�p�q�v�s�r�s�r�g�j�c�f�e	�p�_�f�c�j�5�4��f	�."��
�K�J

�/	�.

�
��M	�T�w�z�qS2���
�
�	A�t�s�r�s�t�s
�A�X�w�v�q�p�o�n
�E�D�C�B�S���������� �!V;z�t2

F�378�x�y26`�}�H�K��
�5�w!�F�+�7�q��@�2�b3�K�#�XZa�c ^#��m�!�2�9�J�N�&�(�B	�W	��y��e;�Y��2@	�z
	��o�>�J
�"Y�F���[��d�z�|����'��*�P�V�Z�a�e�j�V�.�c�k�v�z�~����� ��
�&�u�y�a�;�{��2,e�������*�a�j�J$�Q�d����n2�pW�.�$�#�&�z�~+�}>� �$�(�3U�JM�AT�-�h4�,��S�7�63��`�1��;�*�=�s��?�83�P�5�
�3�/��\�=�%�X��0�U|�[N�`�[(� ��P�[�y
�d�O�?)�
���,�x� �4�6���T�p�!��?�+�A\�;#�/4��"��\�4�`�	A�A��	�9�Jq�5 �p�=Q�N�s�8X�;��B�7�T�(�Q���l�B9�C�_�n;�{�Q�&�'�g'�(�#�;=�VL�V�r�o	�a?�A�E�Z��0i�\
���H�d�J"�=��FO�8g�M�z�b�6�]�$�i,�.�D��"��p�j'�O3�'~�{$�v�z�v��g6�k�'A��H�(�w6�n/�E�oi�*��1�OB�G\�\$���J�L�'��c�\�^�r���+-�T�W�(�&�9 �0K�1N�#�n	�+D�C�#��c�F}��|*�#�=�"��!�'�`L�D)�C�E�{`��(��4#�:�8�D�Ov�#8�;�=;�Y��-�L�^���,�~�Ei�
�o�+�Ha�I�6�^�4�V �j��,�4)�e�v��/�t:�9z�8��F��v��?-�z0�}
�:>�RO��n�n��
�VK�I�Q�L$��8�,�*(�Y�i��Y�@�s�*[�<g��F�bs�`*�]�)�` �*~�#�k�W
�#
�^��+�b;�c0�!�b��%�_�0	�k�n�d�s?�^��S�w"��{Q�z�4�`��;��=^�D��9a^��Q��^���H�=T�A�M�W���N�#�d�-�6�@��H�F�n�\�u,�x�{�i�J�M��m��~�	�
�
�v�aGN��a����n�x�#	�	�K�8�~�r�o�U��N�uN��)�t�u�)�'i�$�T�e"�h-�<�L�I�8�l�E�@K�T��v�m��@��!�1�3�B�/�6�B��;�S��V��a��g��
�q�q%�s�7�v9��k�z�&�B��h�h�%�y"�q�g�gh�p�n��
��b�!$�%�(�R��>��\�O��P�t�c�~����c��t �v��ya�"�j�q��h�-�K�:9R�A	�7U��5�.��z�V�B��J�~�	�|�$�Yv��[�M��6��O�P�Q�+� ��E�`�1�Z��^�g�:6�b���L���`�C�/�X�]�[�	�G�U�5�@7�t	�
}�B�;�7�j�E�#��t��.�5�4R�	��d%�F��S�i��s�p�E�m��>�Q��N�V�.�i�^��M�f�m�r�'z��i�R�
�]s�<��}��K�'�_��+�od�
�,��4�z!�I��bw�Y�`�G�����g�w�s�3��k�(X�B�u�z.�w�C�B�Q�
�;�|�;��	�> �L�����v�N�I�&�9�7�l��c�O�g�o��L�n�sQ�7�v�'L�yS�O�Ey�|+�U(�YT�Y�-��K�E�Q�I�<�O��s�y0�/�+�4�`E�=��1M�5�s�~/�A�D�~�Faz��;�|��y�a�E�m�aw�a���kap���Ya.�����!>��.��1�C��F�#��I��R��X��]�O��O�F��ian����4��t�9��M���k���a#���a����c�C�~��\�����V�����2����5Q��2��]��Pȁ��"a����a���
������G�K�Ipt� �M�_���	�M�m���a���z��*a���3�:�n�JaL��+"�0�V�Y�_�d�f�k�p�y�}�
���(�1�8�Y�^�g	�l�w���!�&�3�<�V�l
�s	��
�"�'�K$�`����*�G9�P��+�4�S�i
�r�}�3��E�d!�{�� �"0�<�n&�u?��^�jC�o�3(�F�p	�r�}F��]
�vk���a�r�a������+��2��T��g��o0��	��1��<��W��j������
$����8��>��M��Q��S��c��m ��t��
����'	��=��G��J��`)����-&��0��X��f����!��)1��8��k��x�o6�q(�*�T&�_�c��N��U��A��
�!�#�%�+�4
�=�A�I�M�Q�S�U�X�]�e�m�s�w�{�}��	����!�$	�'�1�4�6�F5�P�U�\�^�b�e�j!�m���1�4�:�>�A�F�H�L�P�R�T�Z�]�_�u�w�z�~�����#!�*�L�Q�U�`�h
�l�w����#�1�:�D�G�K�O	�l�v�{����	���%	������#��'��,��:��@��G��O��R��X��a��o��t��~����
��a�2�G��F�K��R�Z��]J���)��/�M��N/��Q��S��U
��[��f��n	��s��}������	���������� ��'��+��2��9��>��B��G��I��R��Y��\��_��b��q��s��x��z������
����&��2��8��>��F��N��Q��T��[��b��k��t��z������������������'

��.��<��>��B��E��G��I	��N��V��X��\	��a��d��h��m��r��z����������"����������#��'��)��0��C��G	��M��O��S��X��Z��]	��`K��h��k��r��y	��}��������	�������������y��0��7��:�K��I0��a��T��4��7��<1��?
��s
��v��	��������
��#��1s��B��E	��R��\��b��e��i��n��r4��t��_��|��#��*��5��=��@��D��L��g��l��}��
��F��)��+��0��3��:��>��D��F��R5��^���m��F����%��-	��PK
!<wr���3chrome/pdfjs/content/web/cmaps/UniCNS-UTF16-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�UniCNS-UTF16-HA x��)�����Wa0������
��r�PK
!<�o�����3chrome/pdfjs/content/web/cmaps/UniCNS-UTF32-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�#CE���%�%�D�(�	�C�<�O�H�g�^*�g�h��
'
�"�D����Um�,
{
Y*!
3(/�y�1�F�^�7	C	��3�Z��[A�x�xO@�D�(
�EwC8���d
.%
.%U "!":a*.)aA %*s;<

	�3Y<;<�j�S%P�B��%%%%%��i��%��[��%%%��e��%C(%o�A!
�w
�&�t6�=���"��	�}�$"		OFC/�Ss|}�����$�+�@�A�\�S�`:�e�UBC/��C/��"C/��xC/ҥ	C/�n
$H�>C/��gC/��3C/��`C/��C/��
C%t�0�H�>C�_%���;�Caj��.
�R#�E�~�Z�T��D���F�!�
�VL�\
��S�6�Z�}�b	�h�M�sz�)�l�
�g�	��<�;	���A�^�e��\�Z��x�u�>�A� �9��t�0�-�u�@�p�5�I6��U�D�u��o��T��_�|�V��=���s3�D�`�;�
�|�g�m"�g�F�CB�P�
� 0?�_	�f��y�p�J7�|�'�(hD��,�O�|�c�	�r�
��5�{�(�,C�E2�t�%�n�Q�"�A��
	� �<�/��)����o�Q>�Nx�7�:��,�'�I	���5�,�1
�\�w�"
��e�0�
�-�&�k�y�%�:�M���
B�(��]�;3�B��1���~��G�X��0�b�;�!�j���u�d��g�.�j�YQ"�>	�G\�P�9<	�
R�c
�Z�B��S�=�8�)�L�0�w	�#9�o�h�<J��~�y	�t.�!)�FJ�t�U�F7�#�h�n.�S��BC�k�+w�	�w
�Z�k��1�5��[�
���X�=���E�z�y
H�%��6�	���?
�`���g
�p�&�6<�{%�ZEM��.N�C�2$�7�Y�&5�h-�_)�*�s�^(DQ��)�
�B�p�N��{�{��f���L�)���6=/0
�Px�t�r�s�-7�l7�'�5� �x�'�P�)��B%��C�<	�W,�^�G!�g0�I�6)�"�n�+�#�E\�~C�~.���H�&
�r�s�d�e�X�I�{�)���'0�&��;$�lW��r��%ef+�Z�o�_�?
X�v�/
�R�-�Q�]�f�M#�6�s�O:�#,�>�#'�=>�8�B�&�^%�/
�6
*�`�[.)��
GK%2�P�M�#�#�o�
��w�G�%�r�
?�#"�L��%#
�����)�#�R�'�	�bA�`�. 	�h�c	�:�;�z�yD(�g4�x�
K��L�k
�-�0�^B��<�r	
�1'�:5�7�<��!�>
�P5�<�-�2�3
�g�8c�)(�`�4&�+�^��a
 �bwr�!�a��B]�[�lD�	
�	�'�T-& �6�e�I�	�z��;>�7

H�WT�K6�Pu�vb���M<�T-�<=�!<� n�x?�u+���`�C�@,��&�7�P�C	�4D�yD�a(�
�J$"n�.�[�T�grSN�q���=�J�KCN�r	�p�!CN!�mCN��};�'S
N*�W��Y����=�J��,��y�n�s�<C"N6��$T�8�5x,P�8�d��;0	�	�n
� ��Gz�� �#.P�,�pCN<�G
"�
�CN7��>��<�;�f�
�/�~�%�$�1� S	N��-�j��,V��^��7C
N��
�6�/�


�&3C
N��c�M,	C	N���<��\�k�<�]�P�u�7S	N��l�u�~�o�t��`CN��S	N����*��I��k�p����C
O	�x	�dCO�=	�nCO��S�/
��S
O6�.��F��]������{�COC�%.5*+COA�-�2COB�K��7SOR�+	����&�+
+� ���y�|COg�*�[��3�2�?SOr�r�����~��n�q��'����}���n��}CO��2�%CO���$

CO��G�l�9�1�>�-SOǴg�����Y�X�^�k�CCOӴ\SOִh�k�d�W�Z	�k	CO�L-%(�CO�l�n
$CO�f�-
�~�Q�k�d�^�eSP�A�<�7���\�A�DCP��=�Z�K�0�WS
P%�8�j�Q&�,�5��|�;C	P3�%�T�E��t��I��SPE�F
��~�y�~�w�|�uCPQ�Q-�i��H��9�S	PZ��~�	�|	CPe���S	Ph�8�a#"����~��PC&Pt�	�\		�:


�|
�zC2Pr�> �V

�f

	�T�VCP��N��a�[�"��i�x�D�i�f�SP������u��\��W�l�yCQ?�\.j�2��V��jCQ�
�R�z�CQ
��z�Z�a�z�\�_��n��SQY��0�1�h�g�B�x��;�X�YCQe�]��@�t�S��<�P�$��k
�`�^�$�e� ��*�m�L��vCQ���R�r��g���j�Z�8CQj��<	��c� �S�X�8
�5��B�i�+�*l
�]SQƐX��"��e��$��-�9�D��$��u��t�A�B�#C�;Qܢ�
�e�F�>�2�D�}�0��/�B����h�g	�H�U�T�{�(�
	�	�X�V�/�!�N�Y�v�A�D�K�@�=�6
��G���<�0����c�R��f��(���2�K�J�O�p�[���~��s�D�,	���F� �S�*z��W�F�_�l�S�N�9�&�.��(P��t�g�d��8�r��v�[�,�D�*�)�s$���2�d�e�f�X�)�,�0�L�A�s�>�W�J�s��H�%��g���,�O��q�@�ECQ��u� C
Q��"�D�%�d�#�J�h;�3�<CR�3��o��]�%?�CRY�dJ<C7Qہ���l��[�F�D�#�,��s��-��O�>�(�'�i�^
�-�>�I�:��m�2�O��N�?�p �U"��U�Q�2�8�,���	�5�D�{�X�
�	�3SS��A�����s��v�B�5�|�;�3��lCS���_�>�W�
�q"��>�Z�&�J�!�@CS��~,�oCS���"�/�4�5�-�����o�/�2SS�	C
S��"��f���O��@��C
	ST�(�J�S	
��\�CCT�S
T�G��>��'�f�.��PST#��#��/
�{�~�s�\�G5 ��(��[�E��.��O��CT8�V
'&CT7�@	CTK�Q�N�%ST`�.�u�x�{��z�_��?��hS
To�,�0�-�2�O��
	S	Tz�%�{�^�q�pCT��j��\��[�bST��u	�r�i��R��g�d�m��v���e��J��7CT��r��f�ST����@�
��Q�I% !�P
�A�V�U��D��[��"ST���S��(��g�+�F�Y�Z�G)�D�?CTǍX
CTϴ{CT��#�|�E�T	�nST�W�O�(��v��o�C�&�I�6CT�o	CT�RCT,SU�f��J��g�M�.�;�B���7�A'�T��B��yC	U�F��L��E��8�
�K�����rSU*�a��p��S�z��r�}��o�X�{��|CU;�p'�#��8��q�{S	UC�&������y��r���CUO�*�.�/CUM�^	�6CUS��J	�U��c�.��0�pS
U{�{�&���3�"�
�$SU���.��Q��q)��f�_����
�� CU��&SU��W
�/��T��W	�j�yCU��		
CU��M	�n	-CU��u��J�7�
�SUǛY�d�S�6"�o�`�G�V�c�X��N���9�c�8�G�N��H��%CU�_�o�h�CU��

CU�|�U�j�S
U��7	�d�s�3	�J�K�<CV�5���Y
�&S	V�.�!��\�C�9�(�S	V��8�f����@�^�
�>CV%�x��!����!S
V2����n�e��0�7���CV?��pCV=�	
�
�

�rCVC�~	�z�"��'�m�OS
Vh�	�Z	�W�P�U�`	�]CVv�"�\�o��Z�bjS
V~�G�����C>V��9��V�Y��:� nP�*��~�H�M�H�Y�X�]�R�Y� �y�Z��<��M��+�<�C�@�?	�J�K	�R�SCW�t�J�nC5V���(�?��M�K�"��9�D�G�		�l��o�J�%�)�^����
�			�t�-�:��/�f�^�{�f
��c�"-S
WI�T�_�^�W
�^CWa�}
�L�]�\

��#�C)Wb�A�&


�2
�H�3CWT���@�I�x�)�l�
�+��!�k�>�B�6�^�b�m���\�^�SWܼy��v���%������CW�SW������L�S�m�x�y	�j*��"SX�{$��� ��{�5���v�CX��z�)��
��ASX�e�
�|�p��o������`�m
�CX,�m�%S
X2�^���1�L��	CX=��q�4�pS	XG�B�A�h�Q�PCXQ�g�@�O�ZS	XW�i�f�e�f��T��]�k��pCXb�p	�fCXc� �:
CXi�e���\SXy�'�2�!
�4�;�6�ECX��(�~
�>�C
�~CX��<

!
�J	
�jCX���W���T�1�V�g�6��%�:S	XΧB����/�����C@XةM�Z�&�"�K�
�Z�V��q�@�:���@�"�g�>�.��Y�X�c�b�Z�7��N�]����R�Z�[
�F�I�N�'�~��T�w�X�/�P�&�"�g��U�Z�v�:�G�zCX����
)�CX܁�Z�^�C�]�B�Z��	�/��3�P0�S
Yp��F�,��)�d��p��q�N�CSY{�X�7�:
�/�<CY��{
CY��]CY��0
�/�SSY��q��t��)	�S�8�]CY��SY�������m���>�;��
�x��
���H��3SYòO��h��]���.��5�t�y�r�u	CY؍uCYֲP�4	#CYف�qSY�x�f�c��:���N%��.�
��!��>�Z��)�K�L�u�T�G����)CZ	�	�p
C
Z
�u
�H�@�E$CZ�	�#�K�\�O���M��c�`SZ@�K	�Z!�\���D�ECZL�/SZP�(��@��W6!�8��M�4!�8��M��Z��1�/��:�
�O��n�C	�BCZp�0�S	Zw�#�$�T�] ���YC	Z���&�a��B��IS
Z��+�8�Z�|���D1����S
Z��
��M�>+��~�-�QCZ���C����4�l��SZ��|�r�Y�n	�i�f�y��4�eS
Z��.�o�v�_�z�&�mCZс�F�	SZ��P�9�L
�O�J�A���o�=CZ��]�W�<
�0S
Z��u�A�*�}��v��C
[S���X��2�N� C[�/�r�8�5�H���t�>�~C1Z��,�`�g
�����V�&�+��
�2�7�d�a�>�c		��^�v�q�J�K��u�\�\�8��}S	[p�Q�0������w��$�W�kC[z�F��\�7�p�U�T�9�z�B�A�@�hC[{�XC[|���
�HS	[��Z��n��q��pC
[���(	C[��y�
�C[���{�S	[��V�F�I����#C%[���C�>�G�@�I�@�P��>�M�D�E
�h�0�1�n�{��,�!���j�C�|C[Ձ��R�MqtC[ہ�AS\��#�T�S���0��r���/C:\� �2��m��*��E��E��i�&^�.�Y�$�&�w�P�R�W�(�B�C�>�W�P�A�L�>�(�_�J��Y�	�8�7�.��=�4�I�HC\m�C\��j��>�<%���5
�7�f�u�T�wS\���Z��a��~��
�u��w���t�	C\��*
�J�.	
�p	
	C)\��`�0


�n�w�x
�p	C\���+��Y���C�+�
�<�C�"�#�cS
].�K�����4�U�J-$
C]G�5�C]<�("),
�.�M
�<�[�TC]>��)��
�a�a�k��kS]w�i��h��]��J��?C]��C�dC]��f
�C]��4�^�1�@S]��,
��!	
	CJ]��L��6�$j�#�@��4��/� �G�^�,�4�:��w��v�'�U�v6�7�P�I�H��~���	�
��<�x��n�9��<�A��&�'�h�i�d�m�"���X�WC]��5�Z�~��P��^�*C]���X�*�_�X��<�m�F��T�iS	^[�E��J��e���eC^r�
�Z�C^k�M	�,C^f�8�i�0�G�&S	^x�1��3�H�^�(�R�'�xC^���F�:�2�C^��&|�"
�2�f�u�|C^�����#�=S^Ę?����$�D�qS^��1
�e�d�_�V
���!�w����!C.^��>�{��3��K��
|�^�P�'�J�6��l�{�x�l�g�\�P�5�X�M�:�q�6�~�l�?�:�/�:�;�:�)� �Z�cC^��P�^�\�C^���C�L�E�BS
_H�A��?�"�c��,��E�$��Y��|Cj_T�v�@�A�n�W��I�T��l�+����|�e�f�h�`�&�K�F�G�J�t�w�r�=�|�y	
�&�L�7�0�)	�2�/�b�Y�Z�[����t��x�}�i�b�c��^��}�^�L���&�'�5���`�t�u��j�u�C�B
�W�J���U�N��C_\�Z�n�y�0 ��T	��	��A�HC_�\S`	��M�?�
�]�X��tC`�~�m��a�JS` �!�-�x��x��=��4�c�.�/	�p�C`5�G
�D	�	C`2��.�Z�[		�L�]�X�eC`1��a	�b�?�S`b�(���
�j�L�U� ���f�e�'C
`��
���C`r�\
�p��{��s�r�
C`u�V�\�}�D�S�KS`��\�=�����t�<�'�>�/�#� �,S
`ýj��[����C�Z���/C`јE�iS`ׁ�5��K�"�� �1�(��,��O�j�^�|�)C`�	��t��DS`�N�N�_�w�Z�U�~�)�g��b�L�M
�e�nSa�D�K��8��M�u�J�x��a�	�h�|Ca�W�r��~���x��z����pCa�?

�x�I�T�O�p�e�F�Y�^Ca�v�n
�o�|�
�S
aX�X��B��G�p�j��}�x�_��j��qCad��s�E��1�k��
�zS
an�G��p��i�r
�i�V�eC
a|�K	�L�?�<�3	Cay�P��
�|�a	�Z�_�X�SCa}�=�r
�5��ZSa��O�f�'�T����~�G�`�L�2�[��
�[�d�kCa��a�[�,���o�S	a��E�k�9�2�7�<�h�/C	aρ�:�s�1�$��n�yS
a�E��k�(�#��	Ca��$�IS
a���(�|�}�r�?�@�A�$f�<C!b�Q�,�O���^�����d�y�z���<�:�v�w�	�t���4Cb�3Cb,��<
�E�1S	bF�!�?�,�~�b�q��0��7�C
bQ�E�"	�(CbP�)�
		�LCbh�Sby�-�P�U�^�_�P�_�b��Z��q�W��X��5�8
�cCb��S	b���@��
�:�?Cb���[�.S
b��c�t�s$�B�gCb���U�Z���q�b�{Sb���Q��o��h��o��b��5
��q"�v��\�sCbՁ���?
�$��V�@Sb�t�q���P�?��d��z�
��h��dCc�8��%�BC
c�p�X�K�`Cc��A�ISc1�e�q�m
��H��]�t�S�;�j�-�=Cc@�<�M�<ScF�K�T�a�>�/
�RCcU�I�.&CcT�3,
�j"	"Ccd�G�S	cu�~�+��J��@��c�#Cc�K��3�*1��6Sc��z�
���0��[�\%���3Cc��L�_�0�)Sc��(�,"�W&!�v�P�ISc���&��	� Cc���W�VSc��]����	#��
���l ��Z�g����	�Sc��Y�g��{�f��
�e��6��=��J����7�g�g�xCc�j�t	
Cc��R,�z�
�$$ ?
+	45&�<�'	Cc����^�M�HS
d2��Y��W��.�+��8�=�R�eCd=�!	��k�^Cd@�Cd?�.S
dX�\�(� �O���Cdg�c�j�i�hCde�2��}�bCdq�[S
dt�	(�)�v�u��
�C
d��[�
��Cd�-�|�\�U�'�0�Cd���#�>S
d��h��t����%��$Cd��dS	d��S������(�/
C	dǥ?�0�=�*�)�(�9Cd��RCd�	�4Sd�Z��|��u��Z��}���w	�@�?�Cd�[�h
	�Z�[�Z�&Cd��h�#�&		�L	�.�%Ce
��6Se�R�?��(��}�i�*�1Ce)��?L�D�
��k�,�X��[�T�<�{�P�]��~��x�	�$�	�j�s�p�|��v��:�E��]�1��L�(�N���8�}�*�H�w�N����R�T���f��	�Z�Z�Z�@��~�d��l��/�b��e��]��~�W�R�S�J��
�d�$�C��|�N���W�6�	�W�X�"�>�L�I�L�Y�d	�cCe0�b��
�s�n�H�{�+�[�Y�\��
�6Ce���^Sf�{�\	�g�L	�I�T�e�d�a�b�_Cf��1S
f�	
����z������Cf+�
S
f-�G�������rCfA�bCf9��nCfD��4�7�:��]�aSfY�����n��F��5�x��
����X��]C
fj���#����t��S	fv�z�(�-��z��B��;Cf��3	
�s�j	
�p�
Cf��|��J�Cf���b�3��!	�xSf���H��/��	�
�t����6�1�_�6CZfĥJ�6�l�T�(�6��C�>�j�E�&�T�_� �
�5��&�_	�F�R�d�0�|�{�V	�0�<�A�0�A�2�H�=�<�I�N�G�<�I
�8
�U�N�M	�"�6�a"!�d�Q�D�[�h�C�X�e�h�_
Cg��p�K���0C f��c�v�w�"��+���J�j�m��L�~	�E��Z�F�Q�I�n�B
�2�k�H�r��HSg���p����]�Z�A�b�C
g��
�V�[�f��r�,�]Sg��4�;������
���}�'C	g��z�;(�?�"5S
g˶<�/S
g�X�j��e�o�.;�e�~Cg�f���*Sg�Y�B7��8��&�9��g�"�;��t�?Cg��P�VCg��0�XCg���U��i��=�V�+�KS	h�K��L��=��J��q4�}��PC
h%�d�c�6Sh4�[
��0�\��k�l)�/	��$Sh@�w#�@�?#�����D�����=�^�7ChS�k!�PC	hk�)AChU��y��Sh{�6"�1�*�7(��n��i�l��@��	
Ch��T%C
h��%>!/2'(Ch��*Sh���d���z��x���i�N���Ch���NShÁ�\���� �'*���&��3���"S	h���9�fR�Q�
�Chڙ�X�w	���x4�9�&M
Sh��� �E�s�.�?� �*�
Si��_�8�?��1�w�D1����kA�]�f�	�t$Ci0�>Ci�$
U�p+$CCi�"�z�A��t	�\Si;�71�X�u$)��p��Q)��"CiJ�@�n;BS+iQ�<�u���S�"#�S�`�g	�&�9��P��%��$��c)�|��)�X!�9�H�_�?�,�3��"�Z�M�q�h�q�b�O�:8C
i��>���^��Z��/��Z�W*1S
i��W�A�p� 4��E�?Si��Z��;2�,��a��E��3�4�A���2
�r�A�;�JE�Ci��RS
i��w�	���Y�X�?�Ci��aS	i��>(�?�(�7��hCiӟs�C
i��Q�Z		&#&Ci�<�B
�
�Si���TM�c�;� �
��P��5�zB
�9��4��sCj��7Sj�}�$>G6�E��F�)�:�;�%��Cj#�u�FCj%�'�L		Cj)��*�Q�P��wS
j8�N	�L�i�@�I�HC	jC��a����6�S�_��'��&S
jO�|�b�s�_�,���HSj]�o$�Q�J�U��R��J�o"
�QCj~�lC	jm�p�$Cjq��b�i�j�[Sj��+�8��[�(
�/�0�Sj���e�?�!���o���%�,�:�ECj��]Cj��>Cj���g�)��8�S	j��4�p��J�[Cj��$�)��>S
j�*��-�=�l�5�<
�ICk �^��l��hCj��(
�8�l�
�
�f�P�$�%� �l�$Cj����w�r��)�m�f	�"�E�4�L��P�	�S
kE�C�q�v��@�E�@�=CIkP��.�&�f�D�Q�Z�,�H�>��A�"����J�]�X�f�)�(�)��[��l�5�.�3�2��	�T�h�$� �=�&��L�n�2�[
�W�R�{�v�D��@�Y�<�'�&�!��P�l�\��2�CkQ�$�H�~�h�N�+�w�D�~�8�3�b�@CkR�n��L�3�b�x?�K�N�iS
k��O�4��H�e�fCl�d�V��q�p��$�J�pCl�s�0:�,��L��rCl	��h�N�<�D�nS	l3��=����	��8��o��f�T��Cl>�T�p
�?
�H�=�RC
l?�-�*�v
Cl=���i�'�"S
lm�:����'�C�>�3�B�A����Clx�0��:��u�XSl}�[����-,�,�E�4C	l��W/C	l��b�]�0�%�JCl��	�2Sl���o��	�2�G�������P��M�tCl��S�S�R�GS	l��6�^�k�N�Y�NSlɎp�m��j�����P��K)*�e�L�Q"SlٳR�v�+�D��b��1Cl�%S
l�V�k���>��{�+��%�P�yCl���VSl���c����0��-��0��)-�9��g�*!Cm�y
Cm�Y*Cm�Sm$�C����z��e��!&�01�	��O�a�8��8�1�"�1�C	mA�t�4
Cm?�K"
�H
	#CmN��X�K�P�
�[�=S%mt��A�U�6@
��B��Q�s�R�s
�n�s�R�+��v��3�W'��.��3Cm��'&Cm��FCm���v�{�zS
m��M���x��9�}�\��hCm¾aS
mĖ
+�2"�G
�lS
mϾb�U�F/ &�C1*�&S	mޕ|�,�C�4:�o�D�Sm��~���'�n�6�+�6�o�V�5��Cm��}	��}�VCm��?�1/�
Cm���"�Q�y�\�Sn�B�-	�n�e�Z��<�q��s�
�>�a�v�c�J�/Cn8�&�p�K�0�+�
��	�1�HSnM�9��b�G�.�w��k��8��?��h�q�}�N��(#��D��X�#Cnn�4�(Cnk�^!�	�:K�N50Cnv�S�	�.�ySn��_�&�3���.��g�]�.�=�H�)��}Cn��c
S)n��t�G����0��W��X�i�y��@��E�~;��"����p&�g�>����'�v��n�hCn�Cn��h�>)Cnف�i�*�!��
Sn��F@�[��t�:	�
�j�S�>�j�3�s�jC�x��Q�yCo�{�i"���Z�+*S
o������X�)�|�mSo)�	"�j�5�T�c#��~�}�;�r/�x��q�|�r�RCoC�
�jSoN�u��k�f�}�^�&�!�SSoZ�9�Q��^�y�(�{�T#4�uCof�
Cog�J�Cot��CS
ov�;,���V��S�|��|5>�'���So���X�S�@�-�2�@�1�?Co��K3"-�ESo���9�9�P��7�)�e�z�o�r�\�S
#���
��t��}S
o��(��t�.�I�V�C��pS
o��1�H �p�C)Coը


�jCo��

�|	�K�J	CoӁ�H�)��"
�TS
p�G�q�:
�G�2�;�T�O��N��W�@Cp(�� 
�:�Cp$�4�Q�<�x��n�^�bCp,���u�d�oSpZ�#�^��Q�J���-�\�5Cpk�f��f�2
�
�.Cpq�
�p�d

�Z	Cpl��q�@���@��v�z�
�A�]�N��!��0�u�[�P�s�S��Spʒ���r��e����#
Cpؒ

�lC	p׺!!

�"Cp߁�[�'Sp�t	��^��I�S�2�
�y�C��p�/Cq�~�^�W	"S	q�|�[��n�/�G�TCq%�v�Y�^�\�nS	q.�s�M�P�6�-�#��ACqF�rCq:�{�~
)Cq;�E�/�R�mSqI�j��r�?�-�6�K���?��8��[ %CqV�t��V��;�9�HSq\�l���*		�!�.���1Cqn�N�(C
qp��<	
	Cqq�@�L�u�$�S��_�t	�*�9� �lSq���_�}��v��w�n
��t�>�s�QCq��!�$
	
�	
�L�@�
�6C*q��d�,	�:
	�f�P�M�L�TCq����X�;�c�t
�o	�X�)	��S�j�k�l�A*S
r"�$�N�R�z�G�F��D��E��Cr0��t�I�<�6�"��Z��J�@�|�h�+�G�:�h�[�x�/�XCrP�ICr.��ySrU��}��7��6��W�Q�$�%��Z��M�,�1�p�4C
rg�I���f�r�s�nCrj�z�l�x�"�|Crf�;��sSr����l�q�Q�z�H�M�y�t�C*r��#�`�N���5�4	�4	�w�|�9�2�7�<�h�l�s�o�h	
�[�\�a�h�r
	�E�H�zCr��g�.�p�0�Cr���:
�
�}�&�gS
r��5���%��,�5��BCs�Cs��v�	�x�	�rCs�5�hS
s%�T�p��~��i��2�(�'�M��|S
s0��i�r�y�F��f�?�mCs>�z�`�e�V�s�r�&Cs@��$��"�~�*	
CsA��l�e�SS	sp������$�b��C�B�G�DSsz���.�T�Z�
�6��#�L�'�h�\�o�2���nCs��o�DCs���	�z	Cs���e�_�&�_S
s���c�f��|��m��x��u�n	Ss���L�W�f��b��5�DCs��	�USs¶��P��e����o�f�E��|�`�kCsޒ0C	sҺBCsՁ�sSs�.����I��J��U�|�y�#�,Cs�+��FSs�+������P�E�(�)	�a�h��N��3�^�e�b�c�h�r�kCt�`

Ct�
Ct��l�
�e�L�]�
�-S
t(�b�t��v
�e�f

�
Ct7�aN�(����St?��<�A�F�j��x�e�|�_�6CtU�}
CtM�&CtL��,�w�yS	t]��F����F�R��C�}��(Stg�0�z���q��p���q��m�f�z�x�w�nCt~�)C
ty�|�$�)�Z�3Ctz�Q�4	��vSt��%�S��l�O�z��!��$���S	t��{��h�	�5�B�C�0Ct���
�E
�D�Ct��*�b�L�Y�D�;�JCt��%����/�StӁ�z��y�&�+�,�+���k�hS
t�&��S�2�b���k��H��a�]Cu�1�/�����s�^�
���t�p�o�,�HC&t�� ����(�<�Z�Q��<�o�=��� 	��|�z������&�<�/Ct�f��(-�0
���c�$�?�"��|��X�
���x�y�x�_�US	u��L�U�^��X�u��-��	�	Cu��V
�	�Cu���~�x
Cu���G�5�4Su��?�4�/�>�)��(���;C	uǒ8�����V	Su��+��t�{��J��M�y�x�y�p��Cu��'�,Su��0�Q�Z�U�X	�O
��~�/Cv	�6�l�{�r
�(� Cv�8���	�
�6Cv�$�t�5�jS	vL���V�!�2�S�N��%�T�COvV�w�Z�[�R
�	�P�Q��|�}�6�H�-���4�4��)�t�T�e�"�4�
�E�H���3�h�b�
�y�t�y���0�T�^�&�6��e�^�'�`��|��P��&��/�h�*�J�K�V�1�h�b�g�n�m�t���v� �.�3�d�CCvs�kCvf�*�|�F�j�e�		��jS
v��i�&��/��B�w�r�s�vC
v��4	�"Cv��
�
�"��
Cw�b�v�m�l�YSw3�0�q�X�Y�h�q�j�oCwO�t�:�N�ACwD�1�	Cw@��`�R�OSwe��f�Y�T�_
�b�nCwr�kSww��)�S��S��~�'�	������Cw��7��"Cw���<��<
Cw���S	w���e���H	�G�JCwۆg�$�%��L�VCw��z�D�B�HZCw��s��J�O��S�p� �)�F��J�|��-�&�hS	w��9�	�P�~�D��p��3��~Cw��^�8� 
Cw���
�
Cw��U	���Sx%�P�T�a�@	
�/�6�C�P�?�8�G�6Cx7�O
�f
�hCxE�<	�Cx9��V�y�a�M�L�R�Q�P��U�VS
xh�D��n���Cx|�%			�.�E�@�v��zCxy�W�b�i	��y�z		�C	xz�i��X!�.���Sxǁ�m�+�;�0�#�$��.�/�/CxڦCx��o
Cxׁ�r�M�ZS
x�
�H���?��.��m
�C��"Cy:�j�hCx���4
�@��`�C$x��J	�
�A�8�n	�:���f�O�9�V�g��B�oS	yD�/�_�TSyO�v�A�F	�?���d��w�.�9�X�p�w	�yCyh�C�H�4Cyg�?��.�~Cyq��h��*�
��Sy��_�z��L�A�<Cy��^�Sy��u�P���(�R�d��9�N�L�iCy��k�Cy�� �\�D�$�1�fCy��P�p��r�v�nS	yȌc��\����V����hCyؒd�B�n�nCyշ0�		���4�|Cyԁ�
�[�g�vS	z*��C��G���+��'�6�5��>C	z7�G�6�z��3	����S
zC�l�m�t��?�J�v�%�CzW�&�6�>�k�H�/�N�{�|�$�]�X�Y�6�@
�I�<CzT�S	�J��Y�X�
Czr��=Sz��J����%��N��G�	�p�"Cz��l�v�9Sz��J��`�}�1�*C+z���"��,�%��&�I�l�H��#��6�E�:�;�8�g�^�z�3��O�P	�G�V��� �)��
Cz��9�I	�R
��� �!�b�-Y�_�k�^�o�8�&
�MCz���!�c�f �bS{"�R�	��$��-��|�e����C{8�]� �A�S{B�mn�!
�-��'������
C{T�		�fC{X�Z�:
C{U�G�>S
{l��(��q�o��$�+	�C�R�	�C{{��)�a�E�8�=�&S	{��l�;�B�?�2�HC{��S�~��z�s�=�2	��C{��o
�b
	
�6C{��K�
�^�Q� 	�+�S{���m�Z�U�^�A�%�Z�M�R�M�D�S�V�FC{��ZS
{��+�t�A� �u��q�{�bC|�X�S
|	�c�i�h"�)��$C|��a�SS	|��Y�G
�i�b�_�h�mS	|%��o�n�a� �7C|s�wZ�-Cv|0��"�
��e�^�]��f��c��^��M�(��l��*�I�D��]`�P�V���%��t��u����O�r�(���{���}�

�����a�Z�X�c�����#��v�!�"�s�+���c��L��������4�H�6�O�`�a�\���<�$�o���;��J��U		��`��/�B�V�tC|t���  
�~)�-S	|��!�m�p�	�����K��~C|��{��b���S#}�O�d�k�d�]	�^�c�:�r�c�R
�i�X
�e�\�m�h�U�P�k	C}+��{
		C
}(�r
C}%�^S
}M�]�[�/�:�5�"C}[�C�i
�	�~�H
C}X�`

�F	�C}Z��U+�T	�H�QS	}��]�P�K�\�a�f�]�4�*S}��'�:�#�R���N
�a����
C}��i	C}��C}́�n��S�NS}��o�o�?	�,�,��c�B�Y�FC	}�]��(�u�7�*�9S
}��@�S�N�7�H�e��:�oC}��d�,�G��>�S�+S~�M��W�|
�q�b
C~�#�,C~��&	C~'�S~-�n�s�h�{	�n�c�p�	�t�e�x�g
�b�o�`�m���~�k�dC~R�	�0C~L�y�
	
�@��.�9C~[��	��<S~r�a�g�f�c�h�f�G�H�v�u�IC~��p	�Y�Z�.��S~��"�v��7�L�Q��
�C6�y�4��M�N�HCC�&�0C	~���;�A
&0SG���_��*�	�c�|�8�Y�{�V��F��#�YC_�z��R�O�:�CX�C�R��8	��V�L�0�JCq���S��/�n�x�{�Z��7�^�6�a��C��{�N�NC	��D�T��
��z�>C���[�+S��C�,��k�8�J��F�T�Y�;�n�m�L�4�_���a�6�k�fCŒ|�N�Cǿ}�r�BC�^S���`�<�=�G�>�3�zC���{�v�v�W��r��s�S
���K�q������)�$��Ck���A�2�5���b�a�~�7�2�0��e��|�0�\��i�.���`�R�c�^�_�|�7�2�;� �5�V�L�R�U�2�@�E��u�(�\��#�T�=�]�X�L�e�`�h��=�4�9�2�1�6�=�`�v�{�v	�i
�Y�^�p��H
�o�x�u�lC��+� ��@�w�g�C	� ��<"+��n��#S���}�K�{�~�q�f�aC��a�Z�2�lC��N
�4���(		�t�P�KC�過D�7	�k���$� �9��*�~�d�u
�~S
�J��9
�4�?�6��#�6�-��TC�[�

�
�S
�k�U��.�S��c�$�-�"��SC
�x�Q�P	�n�	C�v�$�
�\	�B

	
C�|��P���_�eS���2�E�j
�q����	�n�y�x�D��3�fC�ͪ�2��<C���C����0�^�2�^S	����� ��A���4�j��C����>�*�h�U�\�<�4�N�H�h�H��#�\�<�(�$C	����f�;�<��D�7�"�^C�Q�a!�oS	��a��/�D�v�S�k�D�`�oC�%�?����g��*��+��h��%S	�2��K�@�?�@C�G�Z�L�h�U�n�(�C�?��:	�^	�n�
�4����+��PC�>���(S
�t��j����W�H����7�i��C����jC���`�x�[C����2S���z�|�w�`
�}�t�}��`��{
�z��a�t�i
S���y�
�u	�t�=�6�w����3C�´?��B�lS�ρ�d�G��
�\�m��L��uC�ۏmS�ޏz�r�6��;���vS ��t��<��7�j�}�p���t��&
��v�7��a��v��q�a	$�J�V�=C��6��
�gS	��S�S��>��|�i	��iC� �US
�$�T,�g�T�e�;C�/�WS
�1�,�0�7�X�k�~�N��0C�?�G�9�8�=�&.S�G�Y�}�|	�O	�B�M�z�{C�w�C�V�F�0&9
'C�W��]
�O�h��S���S���b�e�^	�O�V�"��]�L�S�p�y�X�&��>�C�kC���S���z�@ 
�a�(
�c��0C���9��bS���|��8�s�?�v��P01�W�L�U��.�[/C���6�B��%�X*7�RS���B�}�:�O+�f2CS����O�38�`�a�
	�d�u�b!C���0&C���BKR=	C���_S�	�,�e
 �T%&3B�.C
���7�{�s�b�=�	�}��z�s��S�+�c�o�J�4�;+�;�X&1�C�n3�	�&*�o���cS
�B�Q0-�C�b��t��m���+72S�P�ga@�H�1�J�5�q��H�?�5��l�3 �{��d��e��p��W�-�2J�oS�k�a�v��d�%�C�$�-�Z/(C�}�=F�X�t��]����M��PS���5JO�M�|��.�e�b�.�A:�}�\�m�\�c�B:?$�D�oS���9�<�=$-�]C���1&S����H
�W��x�.��C�J
C�ġ�(�-�pS�ɡ�f4�O�r�{�vC��{C���C
�V	&�-�~
C��u��t�ar�@S����h�?;�y�BS��}���A��[��NJ?���a�z��j��.�5C��g&7H�!S	�#�x�F�A�
9&5C
�.�| �L�i�f��Y�:S�@�F�0��O�
�+
����S�Q�L�8�M.
-�F�=���F�S�]�Z�7��t�G'

&C�j�>�<C�k�A6�6'C�s�xS���.7�m�$�	��w�d��#<�)S����J��W�j���u	�,;'C���e�p�
�~C���C���&
S
����H�G
�C�ͪ�0C���"�:�3� C�ց�	�5S���w��v
�B�%S
���%�W��	��0�M�<C��;�C��&�t�K�	�
�3�.C���B�@�KS
���A�4�#�2��	C$�-�
�
���j�F�g�0�f�g�H�n�/�j�u�d�u�t�d�u�x�q�bC�,�*�Y�>�Y���"�U��Q�Q�~��(�p�`�hC�r��oS���x!����}�	C���7C���wC����zS����`�e����5�w�bS���=�e�h��f�e�r�cC�˗�n�s��wS
���m�;�u�|�y�~�wC��N�F
C���E�>

	C�qS��i�}
��s�\	C��pC	��
	
C��n()
45
0
!,

S�L��L�`	���{�j����&
�?�,$S
�e�(�K�R�Q�P!��"��tS	�s��)�T�U� ��L�
C�}��ES	���h�S��b	�n�uC���DC
���c
'"	C����b�g�OS���l�m��
�*��I�:��-&�E��(�w�C
�Ȩo��-�"�T���S���)� �?�o�P��X�8
&C��$�0C���3�"�/�C���.S	���?�$��&�J��g�$C�
�=C
����E�4	C��zS���v�{�~�P�.�3�U�V�M�D�:�)E>�Y�XC)�6�(:�o�Z�(��b�v��w���P��r�H�1�Z�i�j�{�n�������]�rC�Z�/C�5��^.�p���PS�T�v�
�]�P�OS	�����
������?�}�S���Q�q�	
�~���-���	C����x�ZC���^	�		�*
C����u�I�c� �S�ԝ|�f�A�1�~�}�t�C	���� �E��0�#�(S����R�#��&�+�$��y��3�	C��
�[�L�T�C%��0
�|�"
	�d
�g�`�&C�$��w�C��S�\��3�0	�6�a��"�CC@�i�
����|�q�6�;�J�V��
�b�:��T�3��t�	��-�(����8�y�l�|�c��2��7�T�>�_�n�&�c��H��3�J�W��r�{�>�D�EC�w�j�|
��JC����	�=�/�k�DS�����z
�>�OC��
�x�v
�L

�|C����~��7�n�$�c��H�=�H�R�E
�	�4�s�P�QC�)��eS
�8��z��������h�C�#C"�F�`		
	�v	
�0
�T�M�/�2C�H�s
�@	
�r	C�I�p�E,�m��S	���(	����!�*C���A��I	��D��9S	���D�G�\�o�|
�_�^C�¤

C���O��C�́�iS
�֤�V�^��A�^�n
C��M
		��j	�XC���

�C��Y�S�
�{�H�*�!�_�T�G�<�?�0�K�F�G�b�c�\�Y�:�6��wC�(�*C�"�a�X
C�-�S�9�+�f�H�+�C�N�
S	�E���� �9�8��aS�P�"�Q
	C�\�E�b���a�X�<�7S	�e�:�i�h�|��[	�lC�o�G��S
�}��H�G��h�+�d�iC�7�C���@�m�h���'L��V��M�8��)�T�b��%�>�C����?�u�B#S
�E�A��k�x��:�b�k�`�a��}��C�U�
C�T�c�H��E��'�j�~�Q�P�q�p�F�H�9��F��'�l��2�RC�o�p�d��7��2S
���d��$�E��+�v��D��EC���.�xC���
�F
�C���qS���"�s� ���	�"�	�pC	�ʞ��p��-���e�|�{S	���X�l��G�X�i�^�cC ��)	�`�J�T�:�x��8H�A�@��l�C�x�	�L
��lC2���V
�(�Z�	�.�}�b�N�S��@��x�o�H�d�[R�I�(�]��F�Z�_��N�S�B�F� C�����uS��� �,�-�3���?��4C	����x�F�C����H�0
�NC���	
�@�.S����z�q
�%��#C�ߞ'�
���
����
������S���=�,
�R�i
C�	�S�
����M�f�w
�L�M	�TC�)�9�ZC	�.�e�
C�(��)�l�0S
�D�a��V�1�_�\��C�S�w�|��nS�Y�0�
�u�l�s�v�{�z�8�1�C�l�S�zC�m�:C�q��g�=�H�PS���@�e�^�[�Z�g���
�C
���A�M�H�I�B�
�	�B�1S���k@�T�}'���x��C���/��p	�G�r���BC�Ѽ�P	�4	C����i��.�/�g�$�>�w�D��S	����#�$�'C���2�C���:	C�
� S	��v�V�W�q
�nC�%�F	�H�L�L�<C�#�y	�t	��|��|C�-�c�&��S
�X�E	�*�(�)��Z�O�N�~C���C����d��RC�n��k$��=�]�S���X�,�N��z�B��h�I��-��D�-��$C����F��;��F��oS�ɱh��J��	�����}��T���=�p�y�|C	��C��

C��KS��Y�x
�
��&��3���r���{�~�}C�
�6	C��C��`	�%S��5�~�	�
�	�C�.��jC�-��JC�3���5�tS�I�E���2���~���*C
�X�D

�C�g�C�a�y�\�nS�r�p����U�F��	��
�B�T�YC�����C���:�M�H� �$	�	C���}���hS	����]��x�C������F�C����r�}�	�V�{�zC���D�
S���(� �!�?��2
�E�J�;C��M
��S��K���4�1�,
C�&��q�XS
�+��?�-�r
�k�j�h�C�9��
�}�|�
�J��vC�P�)�R	�<	�bC�8��|���*�N	�	�F�S�jS	�r�,�9�����8C���S�BC����j	C�|�o�x�=S����O�T�Y�R	�QC�Ɗ!�f�h�Q�z�P��q� �J	C���\�h��.2"��'�,�.�XC����������F�K�,�L��8�L�p�2�6�i�T�sS���?�\�a�U��D��O�DC�
�	C��;��oC����$�U�O��QS�0�S$3��� ��1�tC	�<��R��	��t���S�H�Q�w���4�-�&�+�z2�>�3�E��C�Z�[S�]��W���L�G�+�C�k����s�N�GS�v�1�d��#�N�5�L�Q��V��Q�>��G�b�^�]%�xC���^S���Z�4�U�F�K�d#�/�fS
���4"$�_�b1�MC���_	
C���0	1	C�����_��T�rS�ŤT�4�%�
�d�o
4���j�yC	����p�c#
S
�く�����

����qC���?�:��n�6S����Y�C�>"�Y��F�w1���UC���@�?
C
��F@!
;C��S��T#�!�"�[�]��I�|�I��*�����
C�3�x�@
�S�$()S�@��U�B�{�J��E�K�z�&��e�j�y�C�R�S	�T�)�d!.��RS�^�#�.�M�
�	3�U��4�/����r��]�`�*C�p�'�nS�s��
���b�0�?�%�0'�,��S���� ���V�.�I�{�A��C����k�H�K#S���?� ����~�F�G�:�S�
�2��#	�t�S
���E��8��=�
�	��+�./C�ë`C����,!!C����L�Q�Q�
�S���W��'�[�l!�e����o�X�C�߫_C���K	C��� �<�TS���b�N�a�%�#,�'�~"�pC��S��jS��k��/2&#�
��'�
C���H�:�+S�$�C�g[�R�m����v�����u
�	S�5��
��������
C�D�_\C�B��r�Q	�:�5�~�J�#�-	�,�MC�T��*S�j�1�f�0�#�E�D�I�H6�s�n�:C�w����JC�|�_C�{���G��z�U�
6M�$0�o��O�P�L�`�L�Q�S
����b�[�f�e	C���U��q��j�?S
���r�f��������|�`C����3�%�&�)��|�Q�F
�r�}�r�w�x��=�$�+�>��1��6��+�4�X��� ������b�y�t�w�t�y�x	�k�j�K���z��@�(���'�"��'	���X�W�L�_�*�5��V�F�v�U�N��`�i�w�|�#�&�z�n���V�Y�V�i���.C�{�,
C�8�Q�)�-*�<
�QC����=�h�H(�-�,}#);�_S�ҡi��t�U�N�^�+�"�#�,�)�"C��N�<��v�"�p��n�N	C���e�
�!�H�2�L�r�zC����S��I��/���|���)�*��w�r�qC�0�V��	�%��
����V�z�S
�C�X�U�L�%�\�JC�1�@�|�� �!�<�B���
�f��x�D�u�D�A
��fS�z�W�c�T�cC	����@��(�/�+�,�+�S���x�K�F�|���6��C��p�9�2�9C�ː(!
C���V�� @�O�
�v�	�R�4�b�e�v�|�^�_�X	CE���,�T�<���
�P�I���6�o�I�H�:����@�0�;�p�L�M�:�4�<�a�E���[�K�t�.�G�`�H�� �-�5�J�~�w�^�g�X�k�L�Q�S
�C�C	���>�I��J��C�S�\�.C�W�t�C�V��nS	�d�5�������:�_�[��C�o�H,6�C��vC�r�r6�+�L�
C�u��
>�5S���7��n�k�z��7�2��~�C�ĬW�U�
�|�t��p�u�L�W�\��S�N�,C&���7�<�&�o�r�b
�L�
�M�F	�\����h�~�
C��x��>
����9��+�
������,�D��
�OS
�P�{�E�N�*�_�$�'�"�yC���/C�[�W�v�Hl5�{�@�K�4��[�nC�]��>�_7�v�S����Y��m���'�"�'�$C���v�xC
���k	
�NC���K�,�(�S	�դw�B�C�����8�AS	�ߤt���m��G�L�<�3C����I�D�KS	���S�V�Q
C���:�.�JS	�	�@���=�4��C��
�
S��z�
�F�O�,�1�b�YS
�)��U�P
�*���LS�4��I�N�S�V�b
�~���#�,�1�*�-�(�-�(C���vC�M�iXB>C�H�]�\�f�g��U�B�,�w�"�u�J��)�`S
����g��[�R�P�L�I�W��b�u��r�I�LC�ϭ,�V]�H�1���Z
�|C�߼3C���
�l�<��t�C�|�/�X�R�u��p�o�bS��J�*�'�)�$�j�mC�%�x	C	��k�.�'�T���SC��
��6
��E��k�R�{�y�v���^�q�8�^�a�zS	�J�O�O��X��M�4C�T�-��1�>C�U�g��G�T�j	C�i��ES
�z� �@�K�4�+�xC���)�&�d�&�!C0����&�'	��P�~
�tC���n�h��&y7�1�j�q
���*�^
�[S
��

�8�S���y�pC��_� zC���`	�H	C	��:�F�x�s�N�I�	�7�S	�9�l�"���~��v�'�,C�D�k�
�S	�H�i�{�T	�0�AC�T�>��1�,	���T		�"�\CO�R�T�D�;
�F��<��V�dj�3�`�V�p
����3�&�/
	�4�+�,
�4�1
�n�a�t�g�P
�R�EC�|���L�t�)S�V�e	�]��z�	�s�S
�g�q��_�T�U��N��	
�K�bC�w�"S�{�5��#�n�Y#$�^�)�M�FC���$� �S���3	#!�TC���1C���.�pC���0S���]�t�m�S��9�G�P��S���W�*�C�@�'
�@
�
��Z�7�!�

C��C���f�c�F�W�D
�D
C����QS
�	�?�|����^�]62S	��1�$��1��4�}�9��PC�u�c�z�{�*�C	�z�j�3�T�f�"�X�W�RC�{��x�S�L�	S���1�i�q�h�j���m��@�c�f�m��z��-�Jr�lS���J��K��<���p��a��`�6�B���g�C����2�D�xS
���f�0��*����b�u�t���m��0C�̮Q�+��t��f�*�\�`�$C
����X�k��P��`�0C�Ɓ�V�?�@S���>2�m��n��u�,��n�[��v�
�P�C��{C��?
��C��u��2�C�r�$�+��S�"��R�8��S���z�5�0�h�,�-�(�b�C�;��p�eS�B�
�^�@�#�tb2����^��^��C�R�	��l�i�XS
�Y�C�>�I���9�4�3��"��'�C�j�t����p�C�i�P��b�����b�*���~C����>�#�|S	����*�fP>C#��:�4�a�E�h�:��G�!(�M�mb�r�15P<�j�I2�J�'�i
��t�w�E�s�n:�C2�}-�pL��$�y��p�T��WC�@�h��M�m�^�.�m�2H�k�Ch�����=�N0z��Y�#
�

+�}�|��4
�1�0V�U�T�y	�xR�$�	�	 �������c
�b�3�2�Y�^
�	�k��%:�c�b8��4"2/@D<�7�6G�/�]�2�oS	酁�J�>���?�]��e�dC�0m6���-��(�3>�Y�X�U�2
�/�X�Y�:{��<�g~�$~C��.�%�C즋f�+C�(�(C��_C�^�C��C랥iC�ɨC�B�%�>�BC�|�C뺴dC놸C��C���C���$�=�8C�n�6�)sC�[�AC�n�YC��.C�@�PC�E�mC���jC���zx�@�kC�4�a
�T�_	�XH�#�V�W�V�]�$�'�T�U�T
�S�hv&�
�h�i�b�c�b ��f�g�h
	�k�h	�i�H�M		
�&�#�b�W

	
	
#6-D�J�K�|�{�N�O	\�"	
(>(�o7��-I�[O�8K	�T�S2�!_	A
�A4h.-�v�
V;�37826`�e�{�w!�F�+�7�q�kk�ZZ��3�H�I��!�1AK�0���7�"�3�d�V�N�j��#�P�h��{�G$�~$�	6�*�%�@�6&�w!�n�c)��=�G
�fE�}!��)�X�t�;H�Fj�$
��5�@�c
�f%�:�K�=%��7�-"�XG�'�2�;F�u�"�`�B0��<�y>�
c�x�Z�V�U�0�Z�K�d
��
�<�{��|�3�k�7� !��\�E�)�5���m��&�	�|�
"�K�,�~�
�a���M�XW��S�t�K�K �,&�x�?�f#�Q�)�&�iO*�s4�`�x�4!��[�29
z�	�W�X),�?+�8�M�B���^�4�VS�
	�{U(�^�-&�V�]� �t
�;-�2�5�S�$�k1�D�m���zs�O�N*�L"�=0�@%8�
�L�1�9�	�k�B�\�9�W�%���X�-�D�
�Q�j��$�+�?�z���6��
��N��]�#T�a�\����0�F�B#�
�N�j�S��N�L�O�T���F�:��E�z��e��}�V�J�7�$�>�U�% �1
�.
�K�h�;�U�2�{�H�)�(#�'�(�d
�D�R�@�x�N�x�C�`�j�)�)�$�
�*"�n�oQ�?�D�uf�4Z�6�3�'�ZP�,�(4�{-�`H�}�_H�D������>�x� �n"[�q%
��2#�8�!�g\
�H�_<�&/�i\�i�!��cU�x�m2�f�*���kL�0�;5z.�sD�+��e�<hR�% �&�_�J�W�m���j�}8�s$�z�*�E@�o<�B"�%%�Z�i�n(A�i�F'�I�/�#`�\�1�G�6(�;F�\y�y�0�
�!/�T�)�,	�*��y��}-�)&�� ��b�i$@
;:�	�9�)�v�a��q��*>���l�7r�43�@�):��A�L0���0�;�r��"�7F��*1
�
�:)�5�{�y&�%�r �[�`�M�p�b���<��m�!k�(�>�{�/�E��z�v5�G@�p�"�Y�.'��[�)�"�{�@%�i:4�z�!��Y�O�3��W �L�`�G�!�j8�W�J4��Z�W�d14�#��<%�
Q�i�'�j�` �'��3��W�z�-�j�
�0�7��="�F
�E/�X�=��)�Z���&�
�9�y:�,��0��s�2�U�L�T�C�z/�a�!-�[�j��7�Z&�w�2 �m�o'�x�u�6�<��	#�t�M�/�h�N0�G� �Y�8M�
V�O�pO��e:�1�P<�YP�fiP�j�3�^�Sm�T��'�B�t�K
�F	�;�C�Z�2�o
=�>�q�F�0�h�M�D�X�q�g��1��o�\�O�x�E�V�����R	�~�]�j�z�	�>
�
�O�b�Q��#�l�a�%�Z�$�!C�F锁�q 	(B=d"<"	
""8$	

	

#6
)
'"



��s=j*)2
2�	�|2

_�-�Z�@�K���~ef�5��X�_��o�$�}�m�YL �D�rFs�Z��k�t�!�,?�\�
�^��#�Rm�%��0�7�i
?���	�=�,/�m/�l�"�#��}�|Y�O�%�W&�q�p;�~��:q �
�1��('
�B*�q�K!�O1��=%[�b�Gb�^-�@*a�s�	�
�~�
(�e�"�]i$:� �g�C�[0��TQ�OB9�R>� �H�B��!��!� U�1�bV��w�n6��}�$;L�i�H�4%�E�F�,�G�>�]�+�Tg�E1�8	�9��$�H
�W�S�X�0��
�w?��yI�\�8�T�\�#E�u�f�:�#�Z"j�HJ��x�-�+�9]�$�b�sk�`�;Mem�<�G�=�<+�A�;�:%0��2�.�]>�^	9�oH�p
��m�`6�S��,�#��"Wf2�4[�b�[�,�jk��"�Q��w�>�A��h�W�z�CS�7�#�P�Q\�z�|S�_[�I	���R�O�|�H�oQ�{�4�Z/�7O�R
�k�[s0�+=�I��b-��|�N_�,�=4�D�b�+�,�'�os�\.�A&am�Q.�4�n�3��6(�7�b�V�3�r�>�"$#�+��d^�i�^%�2��1S
J!��F�L��b�#�2c��M�"C�>J>�.�)� �/>�8�o�[��Q	��Y��e�^���@
��\�"�	�J�O�N�;� �K
�(	�!
�6	�'���?��L9�m,�6�$�1P�Hi��A�4�F%�%���7�nJ�a�
bQch�E�w8�D�I.�6v�8F�]�l��	��;�D�n�F�a�2�a�0�/
)�c�j�e�@$�sK�1�<_�w��c�T�)L�l�5��^u�:�SU�
�Md�>e�9�4�O�99T�I?�i�Z�;�5�o�G.�1<!!"��J:�*8�M�q"�N]�(0�3��,�F�g�*�]2��y|�K8�D�Q�.�z�o�n�d��}l�X6�4�e�G�`�V�.�K�#
�P��
x��
�(�B
�S-�R*�[r�I�d�;�H�G��N�#�E<�T;��-��26
�.�p(�X��2!��p�Q3�"�/6�)�i�>=���:��4
�Q�`�+�}
�2Cbj��z�`�F�>CM끋td�-d��t��d�6�0�U�L`�)O�38CJO��Q
�kQ�n�|�p�/��B4�a'CSJw��%�7,�s	�6�X,�`^��/W�o�Jw�Y"�n+i�E�����K�+w���
,'+�R��
�7�N�j_�2�1����j$�kR�f�}�@@�_�K�Z^edC�pY��h�*�G�=U�8�)�H�fa�g~�uV�S �'�8�:�`�5�5G9�	�,�o�F�GF��6�^�$�l3�k^�@2�`�&
H7�qA�#�V(]�S	m"���)�&�u�c�.�2�Cv�8C�UmQ�%N�^��7�q�
�{�<�mB�7�x���o
�?/�a�� (�2�&��TM�D��V�,�
�N�>L�r�2*�^�-<�9�3r�{�84�_&�D�A*�2��L�)=�E�I�%�,��6�g�'��a���m�@.�<�[�T�>
�%�_�
�S�7�&�D
�W�z%��j�,���WU�n%�Lm�y/�i�*��T
�8�w�5�H���W��
�>�+�
�p�@�	:$�Bp�<�5�^�j�J[�M�<�B��z)�w�$s'�W�n2�[�&=�rD�F�g5�7!�;*�,�[���E`�H�n�9�.{�r��_
�)�o�|,�+
Q,�p�&����R�Z�
�"�i�@�w��c��8��s��w�N`�/�n
�g�P�\�3�H�<�75�.=�*��2�C#�f��Lt��P�d�MA�F�G�f�{�6
��e�l�6�-�c�:�9�V(�V�-�F'��E�v�}�H�Z�}��=
�z	�y�*�W�u�i�.�g�j�3�d��a�yd�x��g�V+�W0�R�b3�G%�p
�5� �#��@�e�\�I�N#�{�k� �p?�Y"�R+��/,�)�1$�.�Z��>��
� �q��
�L�+��3���wL�.E�i�z�C)�S�<?�Y+�@��n�7�G�O�W���}=�X�=m�(&�{:�AC�n�
�m&�t�(�1�2'�J�{�f$�;�nA��J*�G�4*�)k��V�I*�n�T���:�0B�'���*�K���}.a�R&�'
�L,�J�O�._�S�	�
�C�`Cp��}��� ��|o�h�;	�8p�It0P���"�}�
�e�T�L�P�o�M�	�&�k�?��N�%Cn���&�oP�w=�:�i��R��D�sq�!b�$ECyg��yC�;��kCsmt��9�-�r�+��3D�sz�l�B��!a�rG#�$:?V�eh�1��2�3*�X/�Yk�D�,�0P�u���B�u�Y���Y�^�	�}|#D%i�g6�;�<��=$:c9 _j�k(Z�]�A�@\��}�8H�u�$J�%*�T+�#"�f'���<@�M�'�D�%T,�X�%�
4�/�.��/�3�r�#+!��6�8�9�y�(�v�qA�&��! Xd�e�d�T�f�U@��G�*��V�V�-��]�s&�~D�}Q�P�y�Q�]^�U�8��@�Sc ^c�s���m�!�2�9�J�N�&�(�B	�W	��y��e;�Y��2@	�z
	��o�>�J
�"Y�F���[��d�z�|����'��*�P�V�Z�a�e�j�V�.�c�k�v�z�~����� ��
�&�u�y�a�;�{1e�������*�a�j�J$�Q�d����n2�pW�.�$�#�&�z�~+�}>� �:�3�$�A�9�,��S�7A��`�1��;I�s3�?7�P�5�
8�/��\�;�X��`�[(��t�y
�d���G� �4�6���w��?�+�A\�;#�/4��"��\2�	�_�	�9��5 �p�=Q�N�8X�;��B�7�T�(�Q��0�B~�_�nR�Q�A�g'�(�0�V�rg�E�Z��0i�\
��d�J"�=��u���i:��"�p�j'�O3�'~�{�=�g���H��E�2��S�\$���'-�c�\�^�r,�+2�W�(�&�9 �0�%�n	�+D�C�?�F�k�=�	�!�'�`w�C�E�L�4�H�Ei��5�6�^�4�V�/��O���v�}�:>�R�?�n��-�L$��K��5�#�k�W
�#
�^0�+�b�7�_�0	�k�k�s��S��]�z��*�R���
������G�K�Ipt� �M�_���	�M�m���c�/���2�!�(��-�h��6l�*�='�#�8[���=�5�0�U�6��P�[�O�?)�
�>�x�M�p�5�4�`W�A�L�J�~�s��lJ���{/�&�u�;=�V{�o	�a?�A�J��Hd�FO�8g�M�z�b��R�.�D)���v�z�v�S�k�'�{�w6�nL�oi��]�OB�G��J�L�1��K�T��1N�#���c���|*�(�"�{�D[�{`��(�7�:�8�D�Ov�#8�;�=;�Y��-�L�^���,�u�o�+�Ha�I�(�j�:�4)�e�'�t:�9z�8��F�%�?�|��m�VK�I��8�,�*(�Y�i��Y���<�/�bs�`*�]�)�` �*�Z�]�c0�!�b��:�n�%�^��S�w"��{Q�z�4�`��;��=^�D��9cPc�J��[�L�z0�}�b�s�*c^��Q��^���H�=T�A�M�W���N�#�d�-�6�@��H�F�n�\�u,�x�{�i�J�M��m��~�	�
�
�v�cFN��a����n�x�#	�	�K�8�~�r�o�U��N�uN��)�t�>�'i�$�T�e"�h-�<�L�I�8�l�E�@K�T��v�m��@��!�1�3�B�/�6�B��;�S��V��a��g��
�q�q%�s�7�v9��k�z�&�B��h�h�%�y"�q�g�gh�p�n��
��b�!$�%�(�R��>��\�O��P�t�c�~����c��t �v��yce��ucs^�j�(�t��	�$�c.V��B�c�Y�t�6�a�`���[���5�@�A�B�G�E�#� �.�5�4�X�d�;�s�p�E�C�V�.�i�^��M��Z�'�X�<���z�+�o��4�z�%�G�����k�(�X�z��
�;J��	�>�1��t�l�[�O�;�U�1�1M�58�~cgQ-��z�p��J�~�	�|���[�M�%��O�P�Q�+� ��U�1�Z��G�:6�b���L�r�C�/�X�]�[�	�G�(�t	�
��;�"�F��S�9�m��>�Q����m�n��i�R�
�]���J�'�_�-�
�,�>�I��bw�Y����g�l�3��B��%�w�C�B�b�|�l�L���v�N�I�&�9�P��c�O�g�o��L�n�sQ�7�v�'L�yh�Ey�|U�YT�Y�-��K�E�Q�I�<�O��s�y0�/�+�4�`E�=�p�sL�A�D�~�Fc_�j�q��h�-�K�:9R�A	�7U��5����2�;�|��y���m���[�+"�0�V�Y�_�d�f�k�p�y�}�
���(�1�8�Y�^�g	�l�w���!�&�3�<�V�l
�s	��
�"�'�K$�`����*�G9�P��+�4�S�i
�r�}�3��E�d!�{�� �"0�<�n&�u?��^�jC�o�3(�F�p	�r�}F��]
�vk��h�3�:�n�J(�a�r���p�2c�#p���Y�d�k��C����+��2��T��g��o0��	��1��<��W��j������
$����8��>��M��Q��S��c��m ��t��
����'	��=��G��J��`)����-&��0��X��f����!��)1��8��k��x�o6�q(�*�T&�_�c��N��U��A��
�!�#�%�+�4
�=�A�I�M�Q�S�U�X�]�e�m�s�w�{�}��	����!�$	�'�1�4�6�F5�P�U�\�^�b�e�j!�m���1�4�:�>�A�F�H�L�P�R�T�Z�]�_�u�w�z�~�����#!�*�L�Q�U�`�h
�l�w����#�1�:�D�G�K�O	�l�v�{����	���%	������#��'��,��:��@��G��O��R��X��a��o��t��~����
����9��5Q��2�]��Pc�H#����*����!>��.��1�C��F�#��I��R��X��]�O��O�H���4��t�9��M���k�u��i����8������F�K��R�Z��]J���)��/�M��N/��Q��S��U
��[��f��n	��s��}������	���������� ��'��+��2��9��>��B��G��I��R��Y��\��_��b��q��s��x��z������
����&��2��8��>��F��N��Q��T��[��b��k��t��z������������������'

��.��<��>��B��E��G��I	��N��V��X��\	��a��d��h��m��r��z����������"����������#��'��)��0��C��G	��M��O��S��X��Z��]	��`K��h��k��r��y	��}��������	�������������y��0��7��:�K��I0��a��T��4��7��<1��?
��s
��v��	��������
��#��1s��B��E	��R��\��b��e��i��n��r4��t��_��|��#��*��5��=��@��D��L��g��l��}��
��F��)��+��0��3��:��>��D��F��R5��^���m��F����%��-	����5��\����V����p��"PK
!<�B����3chrome/pdfjs/content/web/cmaps/UniCNS-UTF32-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�UniCNS-UTF32-HC x��)�����Wc0������
��r�PK
!< �T�}�}�2chrome/pdfjs/content/web/cmaps/UniCNS-UTF8-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE���?���?����? A ��%�%�D�(�	�C�_�<�O�H�g�^�j�g�hA¨�3�Z�Y�[��x�x�@A3À��d
.%
.%�CU "!"�_:a*.)�aA %*�s;<
�M	B4–y
'
�"�D����U�m�,
�{
Y*!�U
�s(�o�y�q�F�^�7	B№�f
�wBẾ��n<�@;<��*�R═�B��%%%%%��i��%��[��%%%��e��%B(╯�A�X!
�w
�H�&�_�t�v��}���"��	�}�$�b		OFB⼀�Ss|}�����$�+�@�A�Q�\�S�`�z�e��UBB⾢�B⾮�"B⾾�xB⿒�	B⼐�n
�R$H�>�QB⾘�gB⾽�3B⿋�`B⿌�B⿕�
B╴�0���~B�_▓���{�C�!j��.�J�R#�E�~�Z�X�T��D��K��F�!��J�V��\
��S�6�Z�K�}�b	�h�M�\�sz�)�l�
�^�g�	��<�;�I���A�^�_�e��\�Z��x�D�u�>�A� �9��t�0�-�u�@�p�5�I�K6��U�D�u��o��T��_�|�E�V���}���s�s�D�`�;�]�
�|��g�m�b�g�F�C��P�
� �p?�\�_	�f��y�p�J�w�|�'�(hD�B��,�O�|�c�	�r�]�
��5�{�(�,��E�r�t�%�n�R���"�A�C��
	� �<�/�C��)���X��o�Q�~�N�x�7�:��,��g�I	���P�5�,�1
�\�w�"
��Q�e�0�
�-�G�&�k�y�%�:�M���
�B�(��]�;�s�B��1���~�B��G�X��0�b�;�!�j���u�d��g�@�.�j�Y�"�>	�G�\�P�9<��I�
��c
�Z����S�H�=�8�)�L�H�0�w	�#�y�o�h�<�J��~�y	�t�W�n�!)�F�
�t�U�F�w�#�h�n�n�S��B�^C�k�O�+�7�	�V�w
�Z�k��1�H�5��[�
���X�=���E�T�z�y
H�%��6�	���C�?
�`���g
�p�Z�&�6�|�{�e�Z�M��E�.��C�2$�7�]�Y�&�u�h-�_�i�*�s�^�^(�VD����i�
�[�B�p�N��{�K�{��f���T�L�)��V��6�}�o0
�P�x�t�r�s�X�-�w�l�w�'�5� �x�O�'�P�)��B%��A���<	�W,�^�_�G!�g�p�I�6�i�"�n�[�+�#�E�\�~B�~⺀��H�&
�r�s�G�d�e�X�	�{��W)���'0��f��[�;$�l���R�r���e�Aef+�Z�o�_�?�MX�v�/
�R�-�Q�Q�]�f�_�M#�6�s�N�O�z�#,��~�#�g�=�~��x���&��^%�/�M�6�M*�`�P�[.�i���J��%�r�P�M��c�#�o�
��H�w�Y����e��r�
��#�b�L��S�%#
�O����X��)�#�R�'��I�b��� �.�`	�h�c	�:�;�L�z�y�(�V�g�t�x�
K���O�L�k�K
�-�0�^����|�r�I
�1'�:�u�7�<�D��!�>�Q
�P�u��|�-�2�3�Q
��g�8�#�)�h�`�4�f�+�^��P��a
 �b�Ewr��a�a��B]�[�\�lD�	
�	�'�T�m�f �6�X�e�I�	�z���{�~�7�M
��W��R�K6�P�u�v�b���M<�T-�T��|�}�!�|� �n�x��u�k���`���@�l��&�7�A�P�C	�4D�E�yD�a�h�
�J�d"�.�.�[�Q�T��grR万�q���=�J�KB且�r	�p�!B両�mB业��};�'R
个�W��Y����=�J��,��y�n�s�<B"丶��$T�8�5�Cx,P�8�d��;0	�	�n
� �C��Gz�� �#.P�,�pB丼��EG
"��M�B丷��>�C��<�;�f�
�/�G�~�%�$�1� R	亞�-�j��,V��^��7B
亨�
�6�/�E�


�&3B
亶�c�K�M,	B	亷��<��\�k�<�N�]�P�u�7R	仰��l�u�~�o�t��`B任��A	�dB伀�;	�nB仾���D�'��/
��R
伶�.��F��]������{�B佃�%.5*+B佁�-�2B佂�K��7R佒�+	����&�+
+� ���y�|B佧�*�[��3�2�?R佲�r�����~��n�qR侀�y�'����}���n��}B侍�2�%�BB侐��$
�D
B侢�G�l�9�1�>�-R俇�g�����Y�X�^�k�CB俓�\R俖�h�k�d�W�Z	�k	B修�L-%��A(�B俬�l�n��O
$B俤�f�-
�~�Q�k�d��E�^�eR們�A�<�7���\�A�DB倞��=�Z�K�0�WR
倥�8�j�Q&�,�5��|�;B	倳�%�T�E��t��I�C��R偅�F
��~�y�~�w�|�uB偑�Q-�i��H��9�R	做��~�	�|	B健���R	偨�8�a#"����~��PBH側�	�B�\		�:

�F
�|
�z�D
��R�z��?�@.j�2��V��jBG偲�> �N�V

�f

�A	�T�V�C�v�z���n�B傁�N��a�[�f�u�i�x�D�i�f��/�D�>�Y�Z�
�j�VB傼��QR兙��0�1�h�g�B�x��;�X�YB入�]��@�t�S��<�P�$��E�k
�`�^�$�e� ��*�m�L��vB冀��R�r��g���j�Z�8B兪��<	��c�L� �S�X�8
�5��B�i�+�*l�J�]R准�X��"��e��$��-�9�D��$��u��t�A�B�#B�=凜��
�e�F�>�2�D�}�0��E�/�B����h�g	�H�U�T�{�(�
	�	�E�X�V�/�!�N�Y�v�A�D�K�@�=�6
��G���<�0�B����c�R��f��(���A�2�K�J�O�p�[���~��s�D�,	���F� �S�*z��K�W�F�_�l�S�N�9�&�.��(P��t�g�d��8�r��v�[�,�D�*�)�@�q$���2�d�e�f�X�)�,�0�L�A�s�>�W�J�s��H�%�B��g���,�O��q�@�EB凗�u� B
凘�"�D�e��$�#�J�h�{�3�<B刂�3��o����%��B剙�d�
�|B7凛����l��[�F�D�#�F�,��s��-��O�>�R�(�'�i�^
�-�>�U�I�:��m�2�O�K��N�?�p�`�U"��U�Q�2�8�,�F���	�5�D�{�X�
�	�T�3R厤�A�����s��v�B�5�|�;�3��lB厲��_�>�W�G�
�q"��>�Z�&�J�!�@B厴�~�l�oB厰��"�P�/�4�5�-�����o�/�2R叨�	B
叵�"��f���O��@�B��C
	R吆�(�J�S	
��\�CB吖�R
吘�G��>��'�f�.��PR吣��#��/
�{�~�s�\�G5 ��(��[�E��.��O��B吸�V
'�A&B吷�@�C	B呋�Q�N�%R呠�.�u�x�{��z�_��?��hR
呯�,�0�-�2�O��
	B呻�h�BB呺�%�A	B咅��YR咋�u	�r�i��R��g�d�m��v���e��J��7B咚�r��f�R咠���@�
��Q�I% !�P
�A�V�U��D��[��"R
咶��S��(��g�+�F�Y�Z�GB哀�Q
B哃�|B哋�#�|�E�T	�nR哠�W�O�(��v��o�C�&�I�6B哭�o	B哱�RB哯��,R唁�f��J��g�M�.�;�B���7�A'�T��B��yB	唗�F��L��E��8�
�K�����rR唪�a��p��S�z��r�}��o�X�{��|B唻�p'�#��8�@��q�{R	啃�&������y��r���B問�*�.�/�2&�A-

�$B啍�^	�6�AB啓��J	�U��c�.��0�pR喆��.��Q��q)��f�_����
�� B喘�&R喡�W
�/��T��W	�j�yB喱�		
B喵�M	�n�@	-B喰�u��J�7�
�D�R嗇�Y�d�S�6"�o�`�G�V�c�X��N���9�c�8�G�N��H��%B嗡�_�o�h�
	�A
B嗢�

�R	
�A
B嗪�|�U�j��A�I�IR	嘏�.�!��\�C�9�(�R	嘛��8�f����@�^�
�>B嘥�x��!����!R
嘲����n�e��0�7���B嘿��N�pB嘽��A	
�
�

�rB噃�~	�z�"��'�m�OR
器�	�Z	�W�P�U�`	�]BD噶�"�F�D	��V�Y��C�:� nP�*��~�H�M�H�Y�X�]�R�Y� �y�Z�H��<��M��+�<�C�@�?	�J�K�A	�R�SB圇�t�J�nB@噷��f�bj�M�A�F�?��M�K�"��9�D�G�		�l��o�J�%�C�)�^����
�			�t�-�B�:��/�f�^�{�f
��c�B�"-R
坉�T�_�^�W
�^B坡�}
�E�L�]�\

�S��#�B)坢�A�B�&


�2�A
�H�3B坔���@�I�x�)�l�
�+�H��!�k�>�B�6�^�b�m���E�\�^�R埜�y��v���%������B埩�R埬������L�S�m�x�y	�j*��"R堀�{$��� ��{�5���v�B堐��z�)��
��AR堛�e�
�|�p��o������`�m
�B堬�m�%R
堲�^���1�L��	B堽��q�@�4�pR	塇�B�A�h�Q�PB塑�g�@�O�ZR	塗�i�f�e�f��T��]�k��pB塢�p	�f
�A�z�{�~
�>�C
�~�BB*塣� �:
	�A

!
�J	
�j�BB塩�e���\�^����T�1�V�g�6��%�O�:R	壎�B����/�����B@壘�M�Z�&�"�K�
�Z�V��q�@�:�B���@�"�g�>�.��Y�X�c�b�Z�7��N�]����R�Z�[
�F�I�A�N�'�~��T�w�X�/�P�&�"�g��U�Z�v�:�G�zB壚���
�i�B壜��Z�^�C�]�B�H�Z��	�/��3�P�p�R
奰��F�,��)�d��p��q�N�CB好�;�C�
B奻�X�@
�
B妉�0
�/�SR妝�q��t��)	�S�8�]B妬�R妮������m���>�;��
�x��
���HB姀�SR姃�O��h��]���.��5�t�y�r�u	B姘�uB姖�P�4	#B姙��qR姨�x�f�c��:���N%��.�
��!��>�Z��)�K�L�uB威�~�,	�p
B娀�!	�&
�H�@�E$B娂�
�v�#�K�\�O���M��c�`R婀�K	�Z!�\���D�EB婌�/R婐�(��@��W6!�8��M�4!�8��M��Z��1�/��:�
�O��n�C	�BB婰�0�R	婷�#�$�T�] ���YB	媁��&�a��B��IR
媎�+�8�Z�|���D1����R
媙�
��M�>+��~�-�QB媤��C����4�l��R媮�|�r�Y�n	�i�fB嫀�0�y��4�eR
嫆�.�o�v�_�z�&�mB嫑��F�	R嫕�P�9�L
�O�J�A���o�=B嫨�]�W�<
�0R
嫲�u�A�*�}��v��B
孓���X��2�N� B嬈�/�r�8�5�H�G���t�>�~B1嫽�,�`�g�A
�����V�&�+��
�2�7�d�a�>�c		��D�^�v�q�J�K��u�\�\�8��}R	孰�Q�0������w��$�W�kB孺�F��\�@�7�p�U�T�9�z�B�A�@�hB孻�XB孼���E�
�HR	宕�Z��n��q��pB>客��l�
��G	�x�@�F�I�z�C�>�G�@�I�@�P��>�M�D�E
�h�0�1�n�{��,�!��F��j�C�|B宪��{�X�-�V�R�Mq�EtB宷��G�c
R尌��#�T�S���0��r���/B:尚� �2��m��*��E��E��i�&^�.�Y�$�D�&�w�P�R�W�(�B�C�>�W�P�A�L�>�(�_�J��Y�	�8�7�.�G��=�4�I�HB屭�B尜��j��>�<�e���5
�7�f�L�u�T�wR岜��Z��a��~��
�u��w���t�	B岳�*
�Y�J�.	�C
�p	
	B)岵�`�O�0


�n�w�x�@
�p	B岺��+�F��Y���C�+�
�<�S�C�"�#�cR
崮�K�����4�U�J-$
B嵇�5�B崼�(�@"),
�.�M
�<�[�TB崾��)�G��
�a�a�k��kR	嵷�i��h��]��J��?B嶄�C�dB嶀�d
�B嶅�4�^�1�@R嶧�,
��!	
	BJ嶸�L�K��6�$j�#�@��4��/� �G�^�,�4�:��w��v�'�U�v�D6�7�P�I�H��~���	�
��<�x��n�9��<�A��&�A�'�h�i�d�m�"���X�WB嶴�5�Z�H�~��P��^�*B嶶��X�*�G�_�X��S�<�m�F��S�T�iR	幛�E��J��e���eB1幫�M��Z��0�5�L��3�H�^�(�R�'�@�x|�+�L�I�D�S�F�>�=�:�|��2�4�?�>�?�8�u�|�9�:�9B幦�8�i�0�G�&B広����#�=R廄�?����$�D�qR廐��1
�e�d�_�V
���!�w����!B.廥�>�{��3��K��
|�A�^�P�'�J�6��l�{�x�l�g�\�P�5�X�M�:�q�6�~�l�?�:�/�:�;�:�)�C� �Z�cB廱�P�^�\�S�B廹��C�P�L�E�BR
彈�A��?�"�c��,��E�$��Y��|Bj彔�v�@�A�n�W��I�T��l�+����|�e�f�h�`�&�K�F�G�J�t�w�B�r�=�|�y	
�&�L�7�0�)	�2�/�b�Y�Z�[����t��x�}�i�b�c��^�C��}�^�L���&�'�5���`�t�u��j�u�C�B
�W�J���U�N���@�B彜�Z�n�y�G�0 ��T	���I��A�HB忂��\R怉��M�?�
�]�X��tB怙�~�m��a�JR怠�!�-�x��x��=��4�c�.�/	�p�B怵�G�M�D	�	B怲��.�F�Z�[		�L�]�X�eB怱��a	�b�N�?�R恢�(���
�j�L�U� ���f�e�'B
恿��D�
���B恲�\�M�p��{��s�r�
B恵�V�\�}�_�D�S�KR悰�\�=�����t�<�'�>�/�#� B惀�l�,R
惃�j��[����C�Z���/B惑�E�iR惗��5��K�"�� �1�(��,��O�j�^�|�)B惦�	��t��DR惰�N�N�_�w�Z�U�~�)�g��b�L�M
B愀�V�nR愃�D�K��8��M�u�J�x��a�	�h�|B愒�W�r��~���x�D��z����pB愓�?

�x�I�T�O�p�D�e�F�Y�^B愙�v�n
�o�|�D�
�R
慘�X��B��G�p�j��}�x�_��j��qB慤��s�E��1�k��
�zR
慮�G��p��i�r
�i�V�eB
慼�K�C	�L�?�<�3	B慹�P��E�
�|�a	�Z�_�X�SB慽�=�C�r
�5��ZR憧�O�f�'�T����~�G�`�L�2�[��
�[�d�kB憼�a�[�,�@���o�R	懅�E�k�9�2�7�<�h�/B	懏��:�s�1�$��n�yR
懢�E��k�(�#��	B懰�$�IR懵��(�|�}�r�?�@�A�$B"戀�x�O�,�O���^�����d�y�z���<�:�v�w�	�t���@�4B戁��6B戬��<
�E�1R	扆�!�?�,�~�b�q��0��7�B扑�E�"	�(
�PB扐�)�
		�L	
B扨�R技�&�b��Z��q�W��X��5�8
�cB抌�R	抐��@��
�:�?B抝��[�.R
抨�c�t�s$�B�gB抳��U�Z���q�b�{R拁��Q��o��h��o��b��5
��q"�v��\�sB拕����?
�$��V�@R拫�t�q���P�?��d��z�
�B	持�5��%�BB挀�z�X�K�`B挘��A�IR挱�e�q�m
��H��]�t�S�;�j�-�=B捀�<�M�<R捆�K�T�a�>�/
�RB捕�I�.&B捔�3,
�j"	"B捤�G�R	捵�~�+��J��@��c�#B捿�K�@��3�*1��6R掇�z�
���0��[�\%���3B掔�L�_�0�)R掛�(�,"�W&!�v�P�IR控��&��	� B掹��W�VR揀�]����	#��
���l ��Z�g����	�R揕�Y�g��{�f��
�e��6��=��J����7�g�g�xB援�j�L�t	
B揯�R,�z�
�R�$$ ?
+	45&�<�'	B揸���^�M�H�HR
搲��Y��W��.�+��8�=�R�eB搽�!	�R��k�^B摀�B搿�.�KR
摘�\�(� �O���B摧�c�j�i�hB摥�2��}�bB摱�[R
摴�	(�)�v�u��
�B
撇�[�
��B摿�-�B�|�\�U�'�0�B撍��#�>R
撫�h��t����%��$B撾�E�A
�0�=�*�)�(�9B	撽�S�EB撶�d�I�@�	�4R擠�Z��|��u��Z��}���w	�@�?�B擰�[�h
	�A�Z�[�Z�&B擯�h�#�&	�A	�L	�.�%B攊��6R攛�R�?��(��}�i�*�1B攩��?L�D�
��k�,�X��[�T�<�{�P�]��A�~��x�	�$�	�j�s�p�|��v��:�E��]�1��L�@�(�N���8�}�*�H�w�N����R�T���f��	�Z�Z�Z�@��~�d��l��/�b��e��]��~�A�W�R�S�J��
�d�$�C��|�N���W�6�	�W�X�"�>�L�I�L�Y�d	�B�cB攰�b��W�
�s�n�W�H�{�+�[�Y�R�\��
�6B斋��^R昂�{�\	�g�L	�I�T�e�d�a�b�_B昘��1R
昜�	
����z������B昫�
R
昭�G�������rB晁�bB昹��L�nB晄��4�7�:��]�aR晙�����n��F��5�x��
����X��]B
晪���#����t��R	晶�z�(�-��z��B��;B暄�3	
�s�j	
�p�
B暀�|��J�B暅��b�3��!	�xR暭��H��/��	�
�t����6�1�_�6BZ曄�J�6�l�T�(�6��C�>�j�E�&�T�_�C� �
�5��&�_	�F�R�d�0�|�{�V	�0�<�A�0�A�2�E�H�=�<�I�N�G�<�I
�8
�U�N�M	�"�6�a"!�d�Q�D�[�h�C�X�e�A�h�_
B朁��p�K���0B 暽�c�v�@�w�"��+���J�j�m��L�P�~	�E��Z�F�Q�I�n�B�M�2�k�H�r��S�HR枋��p����]�Z�A�b�B
林�
�V�[�f��r�,�]R枮�4�;������
���}�'B	枿�z�@�;(�?�"5R
柋�<�/R
柖�X�j��e�o�.;�e�~B柢�f���*R柩�Y�B7��8��&�9��g�"�;��t�?B柿�P�S�VB柼�0�U�XB柾��U�A��i��=�V�+�KR	栚�K��L��=��J��q4�}��PB
栥�d�c�6R栴�[
��0�\��k�l)�/	��$R桀�w#�@�?#�����D�����=�^�7B桓�k!�PB
桫�)A@"B桕��y��R
梀�,�7(��n��i�l��@��	
B梓�T%B
梏�%>!/2'(B梘�*R梦��d���z��x���i�N���B梹��NR棃��\���� �'*���&��3���"R	棐��9�fR�Q�
�B棚��X�w	���x4�9�&M
R森��� �E�s�.�?� �*�
R椀��_�8�?��1�w�D1����kA�]�f�	�t$B椰�>�YB椗�$
U�p+$C.1�@$) )4;BB	椘�"�z�A��t	�\�#�D��UR+楑�<�u���S�"#�S�`�g	�&�9��P��%��$��c)�|��)�X!�9�H�_�?�,�3��"�Z�M�q�h�q�b�O�:8B
榀�>���^��Z��/��Z�W*1R
榓�W�A�p� 4��E�?R榞�Z��;2�,��a��E��3�4�A���2
�r�A�;�JE�B榻�w�E
B榹�R	�B !$B槀�]R	槉�>(�?�(�7��hB槓�s�B
槔�Q�Z		&#&B槕�<�B
�
�R
槶��TM�c�;� R樀�~�
��P��5�zB
�9��4��sB樏��7R樓�}�$>G6�E��F�)�:�;�%��B
樣�u�F	�F	B樥�'�L		'�@�I�H;B権��*�Q�P��w�O�x�c�R
橏�|�b�s�_�,���HR橝�o$�Q�J�U��R��J�o"
�QB橾�l�AB	橭�p�$�AB橱��b�i�G�j�[R檌�+�8��[�(
�/�0�R檙��e�?�!���o���%�,�:�EB檬�]�FB檭�>B檫��g�)��8�R	櫅�4�p��J�[B櫏�$�)��>R
櫘�*��-�=�l�5�<
�IB欠�^��l��hB櫥��(
�8�H�l�
�
�f�P�$�%� �l�A�$B櫧���w�r��)�m�f�C	�"�E�4�L��P�	�R
歅�C�q�v��@�E�@�=B~歐��>�H�]�Z�5��7��A�Q�Z�,�H�>��A�,�����J�]�@�X�f�)�(�)��[��l�5�.�3�2��<�A�<�D�~�5�h��_� �=�&��L�n�2�[�@��b�@���R�{�v�D��@�Y�<�'�&�!��P�l�\��2��y�4��@�H�e�f�?�0�h�N�<��I�V��Z:�	�4�E�Z�=�$�Z�_�|�3�p�2B歗�.��B歒�n�L�3�b�F�x��K�N�i�v-R	氳��=����	��8��o��f�T��B氾�T�A�p
�?
�H�=�RB
氿�-�C�*�v
B氽���K�i�'�"R
汭�:����'�C�>�3�B�A����B決�T�B!,/B汸�0 �D�D�c�L�]�0�%�JB汹���Q�_�D�2R沪��o��	�2�G�������P��M�tB沶�S�S�R�G�@�^�k�N�Y�NR泉�p�m��j�����P��K)*�e�L�Q"R泙�R�v�+�D��b��1B泥�%R
泧�V�k���>��{�+��%�P�yB泵��V��nR洀�U��0��-��0��)-�9��g�*!B洗�y
B洘�Y*B洖�R洤�C����z��e��!&�01�	��O�a�8��8�1�"�1�B	流�t�4
B洿�K�@"
�H
	#B济��X�K�P�
�[�=R浴��A�U�6@
R涀���B��Q�s�R�s
�n�s�R�+��v��3�W'��.��3B涪�'&B涫�FB涤��v�{�zR	涷�M���x��9�}�\�B淀�E6R
淄�
+�2"�G
�lR
淏�b�U�F/ &�C1*�&R	淞�|�,�C�4:�o�D�R淨��~���'�n�6�+�6�o�V�5��B淹�}�I��}�VB淼�?�1�B/�
B淾��"�C�Q�y�\�R渟�B�-	�n�e�Z��<�q��s�
�>�a�v�c�J�/B游�&�p�K�0�+��@
��	�1�HR湍�9��b�G�.�w��k��8��?��h�q�}�N��(#��D��X�#B湮�4�V�(B湫�^!�	�:�NK�N50B湶�S��I�.�yR準�_�&�3���.��g�]�.�=�H�)��}B溪�c
R溮�t�G����0��W��X�i�y��@��E�~R滀�R��"����p&�g�>����'�v��n�hB滬�B滘�h�>)B滙��i�*�!��
R滴��F@�[��t�:	�
R漀�F�S�>�j�3�s�jC�x��Q�yB漒�{�i"���Z�+*R
漞������X�)�|�mR漩�	"�j�5�T�c#��~�}�;�r/�x��q�|B潀��R�G�jR潎�u��k�f�}�^�&�!�SR潚�9�Q��^�y�(�{�T#4�uB潦�
B潧�J�B潴��CR
潶�;,���V��S�|��|5>B澀�|���R澄��X�S�@�-�2�@�1�?B澐�K3"-�ER澝��9�9�P��7�)�e�z�o�r�\�S
#���
��t��}B澹�h�FB澸�(�DB澻�;�R
濆�1�H �p�C)B濕�


�j��GB濔�

�|	��@�K�J	B濓��H�)��"��J�TR
瀔�G�q�:
�G�2�;�T�O��N��W�@B瀨�� �M�:�B瀤�4�Q�<�x��n�@�^�bB瀬���^�u�d�oR灚�#�^��Q�J���-�\�5B火�f��f�Q�2
��M�.B灱�
�p�G�d

�Z	B灬��q�@���@��v�z�A�
�A�]�N��!��0�u�[�P�s�S�B��R烊����r��e����#
B烘�

�lB	烗�!!

�"B烟��[�'R
烳�t	��^��I�S�2�
�y�C��p�/B焀�z�^�W	"R	焛�|�[��n�/�G�TB焥�v�Y�^�\�nR	焮�s�M�P�6�-�#��AB煆�rB焺�{�F�~
)B焻�E�/�A�R�mR煉�j��r�?�-�6�K���?��8��[ %B煖�t��V��;�9�HR煜�l���*		�!�.���1B煮�N�(�FB
煰��<�D	
	B煱�@�L�u�$�S��_�t�I�*�9� �lR熖��_�}��v��w�n
��t�>�s�QB熨�!�$
�I
�	
�L�G�@�
�6B*熧�d�,�B	�:
	�f�A�P�M�L�TB熭���X�;�V�c�t
�o	�X�)�I��S�j�k�l�A*R
爢�$�N�R�z�G�F��D��E��B爰��t�I�<�6�"��Z��J�A�@�|�h�+�G�:�h�[�x�/�XB牐�IB爮��y�QR牕��}��7��6��W�Q�$�%��Z��M�,�1�p�4B
牧�I���f�B�r�s�nB牪�z�l�x�"�E�|B牦�;�[��sR犋���l�q�Q�z�H�M�y�t�B*犛�#�`�N���5�4	�4	�@�w�|�9�2�7�<�h�l�s�o�h	
�[�\�a�h�r
	�E�H�zB犚�g�.�p�0�B犟��:
�
�}�Z�&�gR
狶�5���%��,B猓�B猀�1��v�	�x�	�rB猂��K�hR
猥�T�p��~��i��2�(�'�M��|R
猰��i�r�y�F��f�?�mB猾�z�D�`�e�V�s�r�&B獀��$��"�~�*	
B獁��l�e�SR	獰������$�b��C�B�G�DB獺���.�T�ZR
玀�J�6��#�L�'�h�\�o�2���nB玖�o�DB玎��	�z	B玏��e�_�&�_R
玤��c�f��|��m��x��u�n	R玲��L�W�f��b��5�DB玾�	�@�UR珂���P��e����o�f�E��|�`�kB珞�0B	珒�BB珕��sR珠�.����I��J��U�|�y�#�,B班�+��FR
珳�+������P�E�(�)	�a�hR琀���N��3�^�e�b�c�h�r�kB琛�`

B琖�
B琑��l�
�e�L�]�
�-R
琨�b�t��v
�e�f

�
B琷�aN�(������=R
瑀� �A�F�j��x�e�|�_�6B瑕�}
B瑍�&B瑌��,�w�yR	瑝��F����F�R��C�}��(R瑧�0�z���q��p���q��m�f�z�x�w�nB瑾�)�AB
瑹�|�$�A�)�Z�3B瑺�Q�G�4	��vR璗�%�S��l�O�z��!��$���R	璣�{��h�	�5�B�C�0B環��
�E�J�D�B璭�*�b�L�Y�D�;�A�JB璴�%��L���/�R瓓��z��y�&�+�,�+���k�hR
瓠�&��S�2�b���k��H��a�]B甄�1�/�����s�^��M���t�p�o�,�HB(瓬�� ��@���(�<�Z�Q��<�o�=��� 	��|�@�|������A�&�<�/B瓰�f��(�L-�0
���c�$�?�F�"��|��X�
���x�y�x�_�H�UR	疊�L�U�^��X�u��-��	�	B
疝�V
�	��g�C�tB疘��~�x
�@	�	B疞��G�5�4�N�k�ZR痗�+��t�{��J��M�y�x�y�p��B痦�'�,R痯�0�Q�Z�U�X	�OB瘀��X�l�{�r
�(�G� B瘃�2�
���	�
�E�6B瘂��I�u$�t�5�D�jR	癌���V�!�2�S�N��%�T�BO癖�w�Z�[�R
�	�P�Q��|�}�6�H�-���4�4��)�t�T�e�"�4�A�
�E�H���3�h�b�
�y�t�y���0�T�^�&�6��e�^�'�`��|��P��&��/�B�h�*�J�K�V�1�h�b�g�n�m�t���v� �.�3�d�CB癳�kB癦�*�|�F�j�E�e�	�T	��jR
盬�i�&��/��B�w�r�s�vB
相�4�B	�"B盷��L
�
�"��
B県�b�v�m�l�YR眳�0�q�X�Y�h�q�j�oB睏�t�:�N�AB睄�1�	B着��`�R�OR睥��f�Y�T�_
�b�nB睲�kR	睷��)�S��S��~�'�	���B瞄�:�x��"B瞀�D
��<��<
B瞘��R	瞯��e���H	�G�JB矛�g�$�%��L�VB瞽�z�G�D�B�HZB瞹�s��J�C�O��S�p� �)�F��J�|��-�&�hR	矬�9�	�P�~�D��p��3��~B矽�^�D�8� 
B矷��F�
�
B矾�U�I���R砥�P�T�a�@	
�/�6�C�P�?�8�G�6B砷�O�J�f
�hB硅�<	�B砹��V�y�a�D�M�L�R�Q�P��U�VR
硨�D��n���B硼�%�I		�.�E�@�v�B��zB硹�W�b�A�i	��y�z		�B	硺�i�F��X!�.���R磇��m�+�;�0�#�$��.�/�/B磚�B磛�o
B磗��r�M�ZR
磬�
�H���?��.��m
�C��"B示�j�hB磷��F�4
�@��`��BB$磹�J	�B�
�A�8�n	�:���f�O�9�V�g��B�oR	祄�/�_�TR祏�v�A�F	�?���d��w�.�9�X�p�w	�yB票�C�H�A�4B祧�?��.�D�~B祱��h��A�*�
��R禐�_�z��L�A�<B禟�^�R禤�u�P���(�R�d��9�N�L�iB禾�k��@B禱� �\�D�$�1�G�fB禰�P�p��r�L�v�nR	秈�c��\����V����hB秘�d�B�D�n�nB秕�0�		��G��4�|B秔��
�[�g�Q�vR	稪��C��G���+��'�6�5��>B	稷�G�6�z��3	�����@R
穃�l�m�t��?�J�v�%�B穗�&�6�>�k�H�/�N�{�|�$�]�@�X�Y�6�@
�I�<B穔�S	�J��Y�X�E�
B穲��=R窏�J����%��N��G�	�p�"B窞�l�v�9R窮�J��`�}�1�*B+窺��"�G��,�%��&�I�l�H��#��6�E�:�;�8�g�^�z�3�D��O�P	�G�V��� �)��
B窻�9�I�I�R
��� �!�b�-Y�_�k�^�L�o�8�&
�MB窼��!�L�c�f �bR笢�R�	��$��-��|�e����B笸�]� �A�D�R筂�mn�!
�-��'������
B答�		�fB筘�Z�:
B筕�G�>R
筬��(��q�o��$�+	�C�R�	�B筻��)�F�a�E�8�=�&R	箊�l�;�B�?�2�HB箔�S�~��z�s�G�=�2	��B箖�o
�b
	�K
�6B箢�K�
�^�Q�R� 	�+�R篘��m�Z�U�^�A�%�Z�M�R�M�D�S�V�FB篲�ZR	篷�+�t�A� �u��q�{�bB簀�[�R
簉�c�i�h"�)��$B簕��a�SR	簛��Y�G
�i�b�_�h�mR	簥��o�n�a� �7B米�w�Z�-Bw簰��"�
��e�^�]�B��f��c��^��M�(��l��*�I�D��]`�P�V���%��t��u����O�r�(���{����;�C�<�

�����a�Z�X�c�����#��v�!�"�s�+���c��L��������4�H�B�6�O�`�a�\���<�$�o���;��J��U		��`��/�B�V�tB籴��� �`�J�~)�-R	糱�!�m�p�	�����K��~B系�{��b���R#紀�O�d�k�d�]	�^�c�:�r�c�R
�i�X
�e�\�m�h�U�P�k	B紫��{
		�CB
紨�r
�CB紥�^R
絍�]�[�/�:�5�"B絛�C�i
�	�~�H�G
B絘�`

�F�@	�B絚��U�k�T	�H�QR	綜�]�P�K�\�a�f�]�4�*R綦�'�:�#�R���N
�a����
B綸�i	�GB綷��CB緍��n��S�NR緖�o�o�?	�,�,��c�B�Y�FB	緣�]��(�u�7�*�9R
緮�@�S�N�7�H�e��:�oB緹�d�,�G��>�S�D�+R縇�M��W�|
�q�b
B縛�#�,B縚��&	B縧�R縭�n�s�h�{	�n�c�p�	�t�e�x�g
R
繀�v�o�`�m���~�k�dB繒�	�0B繌�y�
	
�@��.�9B繛��	��<R繲�a�g�f�c�h�f�G�H�vB
纀��I�z	�Y�Z�.��R纑�"�v��7�L�Q��
�B缶�y�4��M�N�F�HB罃�&�0B	纟��;�A
�L�f0R罇���_��*�	�c�|�8�Y�{�V��F��#�YB罟�z��R�O�:�B罘�C�R��8	��V�L�0�C�JB罱���R羅�/�n�x�{�Z��7�^�6�a��B羔�{�N�NB	羑�D�T��
��z�>B羓��[�+R羬�C�,��k�8�J��F�T�Y�;�n�m�L�4�_���aB翁�}�N�B	翀�2��r�BB翝�^R翥��`�<�=�G�>�3�zB	翲��{�v�v�W��r��s�� R	耀�4�q������)�$��Bk耋��A�2�5���b�a�~�7�2�0��e��|�0�\��i�.���`�R�c�^�_�C�|�7�2�;� �5�V�L�R�U�2�@�E��u�(�\��#�B�T�=�]�X�L�e�`�h��=�4�9�2�1�6�=�`�v�{�v	�i
�Y�^�p��G�H
�o�x�u�lB耝��k� ����w�g�H�B	耠��<�b�k��n��#R胔�}�K�{�~�q�f�aB胡�a�Z�D�2�l�GB胠�N
�4��A��(		�t�E�P�KB胩��D�7	�k�L���$� �9��*�~�d�u�J�~R
腊��9
�4�?�6��#�6�-��TB腛�

�
�R
腫�U��.�S��c�$�-�"��SB腸�Q�P�@	�n�	�@B腶�$�K�
�\	�B

	
�&
B腼��P�G���_�eR臀�R����	�n�y�x�D��3�fB臍��2��<B臌�B臐��0�^�2�^R	臢���� ��A���4�j��B臬���>�*�h�U�\�A�<�4�N�H�h�H��#�\�<�(�$B	臮��f�;�A�<��D�7�"�^B臯��Q�a�a�oR	舚�a��/�D�v�S�k�D�`�oB舥�?����g��*��+��h��%R	舲��K�@�?�@B艇�Z�L�h�U�n�(�B舿��@�:	�^	�n�
�4����+��PB舾���U�(R艴��j����W�H����7�i��B芋��jB芀���x�[B芇��2R芝�z�|�w�`
�}�t�}��`��{
�z��a�t�i
R
芳�y�
�u	�t�=�6�w��B苀�0��B�lR苏��d�G��
�\�m��L��uB苛�mR苞�z�r�6��;���vR苪�t��<��7�j�}�p���t��&
��v�7��a��vR
茀�f�a	$�J�V�=B茋�6��
�gR	茖�S�S��>��|�i	��iB茠�UR
茤�T,�g�T�e�;B茯�WR
茱�,�0�7�X�k�~�N��0B茿�G�@�9�8�=�&.R荇�Y�}�|	�O	�B�M�z�{B荷�B荖�F�0&9
�A'B荗��]
�O�h��R莅�S���b�e�^	�O�V�"��]�L�S�p�y�X�&��>�C�kB莠�R莢�z�@ 
�a�(
�c��0B莮�9��b��W��8�sR菀��?�v��P01�W�L�U��.�[/B菑�6�B��%�X*7�RR菛�B�}�:�O+�f2CR菧��O�38�`�a�
	�d�u�b!B菸�0�E&B菹�BKR=�A	B萅��_R萉�,�e
 �T%&3B�.B
萖��7�{�s�b�=�	�}��z�s��R萫�c�o�J�4�;+�;�X&1�C�n3�	�&*�o���cB葀�8R
葂�Q0-�C�b��t��m���+72R葐�ga@�H�1�J�5�q��H�?�5��l�3 �{��d��e��p��W�-�2J�oR葫�a�v��d�%�C�$�-�Z/(B葽�=F�X�@�t��]����M��PR蒍�5JO�M�|��.�e�b�.�A:�}�\�m�\�c�B:?$�D�oR蒧�9�<�=$-�]B蒸��@B	蒴�1&+
�E
�%�DB蒽�L�.R蓉��f4�O�r�{�vB蓬�{B蓖�C
�V	&�-�~
B蓚�u��t�ar�@R
蓶��h�?;�yB蔀�_R蔂�}���A��[��NJ?���a�z��j��.�5B蔜�g&7H�!R	蔣�x�F�A�
9&5B
蔮�| �L�i�f��Y�:R蕀�F�0��O�
�+
����R蕑�L�8�M.
-�F�=���F�R蕝�Z�7��t�G'

&B蕪�>�<B蕫�A6�6'B蕳�xR薀�.7�m�$�	��w�d��#<�)R薓��J��W�j���u	�,;'B薦�e�p�
�~B薧�B
薱�&
R
藀��H�G
�B藍��0B藋�"�:�3� B藖��	�5R藨�w��v
�B�%R
藶�%�W��	��0�MB蘆�;�B蘀�$�<�5�t�K�	�
�3�.B蘏��B�@�KR
蘞��A�4�#�2��	B$蘭�
��M���j�F�g�0�f�g�H�n�/�j�u�d�u�t�H�d�u�x�q�bB蘬�*�Y�>�Y���A�"�U��Q�Q�~��(�p�`�hB虲��oR蚐�x!����}�	B蚜�7B蚝�wB蚠��zR蚯��`�e����5�w�bB蚿�=R
蛀��h��f�e�r�cB蛋��n�s��wR
蛖�m�;�u�|�y�~�wB蛤�N�F
�AB蛢�E�>

	�FB蛯��qR蜄�i�}
��s�\	B蜓�pB	蜘�
	
B蜑�n()
45
0
!,

�AR蝌��L�`	���{�j����&
�?�,$R
蝥�(�K�R�Q�P!��"��tR	蝳��)�T�U� ��L�
B蝽��ER	螁�h�S��b	�n�uB融�DB
螏�c
'"	B螋��b�g�OR螩�l�m��
�*��I�:��-&B蟀�gB	蟂�A	�nB蟁�|R蟖��)� �?�o�P��X�8
&B蟬�$�0�FB蟪�3�"�/��@��&
��E�4	B蟵�.�N��R蠟��v�{�~�P�.�3�U�V�M�D�:�)E>�Y�XB)蠶�(:�D�o�Z�(��b�v��w���P��r�H�1�Z�i�j�{�n�������@�]�rB衚�/B蠵��^.�B�p���PS�T�v�
�]�U�P�OR	袋���
������?�}�R袕�Q�q�	
�~���-���	B被��x�X�ZB袤�^	�		�A�*
B袮��u�I�E�c� �R裔�|�f�A�1�~�}�t�B	裡��� �E��0�#�(R
裳���R�#��&�+�$�B褂��^�[�L�T�F�B&褁�a�
�|�"
	�B�d
�g�`�&B褀��z#�x�^�C��R襜��3�0	�6�a��"�CB@襩�
����|�q�6�;�J�V��
�A�b�:��T�3��t�	��-�(����8�y�l�|�c��2��7�T�>�_�n�&�c��H��3�J�@�W��r�{�>�D�EB襷�j�Q�|
��JB覀��	�=��o�k�DR觟���z
�>�OBB觴�)�2�&�C��x�v
�L

�|�z�B�{		
	�v	
�H�0
�T�M�/�2B)觫��~��R�
��B��D�@�C

�@�A	
�r	B觶�!�$�6�H��R�\�x��)�P�1�K��E,�Q�m��R	誣�(	����!�*B
誰�	�B

B誯�A�{
�A��B誴��|�W�XR
論��V�^��A�^�n
B諦�M
		��j�A	�XB諤�

�E�B諪�Y�R謊�{�H�*�!�_�T�G�<�?�0�K�F�G�b�c�\�Y�:�6��wB謨�*�G�0B謢�a�X
�H�@�+B謭��U�0R	譅���� �9�8��aR譐�"�Q
	B譜�E�b���a�X�<�7R	譥�:�i�h�|��[	�lB谷�B譯�G��B�	bBL
�"�3�AB譸��H�A�"�+�d�i���
�=8�
�B�c���{�T�b��C��R
豅�A��k�x��:�b�k�`�a��}��B豕�
B豔�c�H��E��'�j�~�Q�P�q�p�F�H�9��F�B��'�l��2�RB豯�p�d��W�7��2R
貗�d��$�E��+�v��D��EB貧�.�xB貣�
�F
�B貭�qR貲�"�s� ���	�"�B賀�m�p�0B賌� �XB賍��H�gR	賙�X�l��G�X�i�^�cB 賤�)	�`�J�G�T�:�x��8��A�@��l�C�x��I�L
��lB2賥�V
�(�Z�	�.�}�b�A�N�S��@��x�o�H�d�[��I�(�]��F�Z�_�B��N�S�B�F� B賲���X���uR趥� �,�-�3���?��4B	足��x�F�G�B趵��H�A�0
�NB趲�	�M�@�.R跓��z�q
�%��#B
跟�'
�"B跠�-	�6
�@
B踁�R踍����M�f�w
�L�M	�TB踩�9�Z�HB	踮�e�
�@B踨��)�l�0R
蹄�a��V�1�_�\��B蹓�w�|��nR蹙�0�
�u�l�s�v�{�z�8�1�B蹬�S�D�zB蹭�:B蹱��g�=�H�A�PR躄�@�e�^�[�Z�g���
�B
躐�A�M�H�I�B�
�	�B�1R躣�k@�T�}'���x��B躲�/��E�p	�G�r���BB軑��P	�4	B躰��i��.�/�g�D�$�>�w�D��R	軴��#�$�'B軾�2�D�B軿�:�@	B輍� R	輘�v�V�W�q
�nB輥�F	�H�L�B�L�<B輣�y	�t	�F��|��|B輭�c�&�P��R
轘�E	�*�(�)��Z�O�N�~B辛�B辜��d��RB轮��k�d��=�]�R辭�X�,�N��z�B��h�I��-��D�-��$B达��F��;�A��F��oR迉�h��J��	�����}��T���=�p�y�|B	迢�B迠�

B迚�KR迴�Y�x
�
��&��3���rB退�W
�@	B适��LB逈�`	�%R這�5�~�	�
�	�B逮��j�BB逭��J�DB逳���5�N�tR遉�E���2���~���*B
遘�D

�B遧�B遡�y�\�nR遲�p����U�F��	��
�B�T�YB邑���B邀��D�M�H� �$	�	B邅�}���hR	邮��]��x�B
邸��H���F���B邽��E�r�}�	�V�{�z�<� �!B邻�D�H�
R
鄀�+�E�J�;B鄋�M
��R鄖�K���4�1�,
B鄦��q�XR
鄫��?�-�r
�k�j�h�B鄹��N�
�}�|�
�J��vB酐�)�R	�<	�bB鄸��|��@��*�N	�	�F�S�jR	酲�,�9�����8B醃�S�BB醀��j	B酼�o�O�x�=R醙��O�T�Y�R	�QB釆�!�f�h�Q�z�P��q� �J	B醮�\�h��E�.2"��'�,�.�X�FB醥��������F�K�,�L��8�C�L�p�2�6�i�T�s�R鈀�<�U��D��O�DB鈍�	B鈌�;��oB鈎���$�U�O��QR鈰�S$3��� ��1�tB	鈼��R��	�@��t���R鉈�Q�w���4�-�&�+�z2�>�3�E��B鉚�[R鉝��W���L�G�+�B鉫����s�N�GR
鉶�1�d��#�N�5�LR銀�U��V��Q�>��G�b�^�]%�xB銑�^R銓�Z�4�U�F�K�d#�/�fR
銠�4"$�_�b1�MB銲�_	
�DB銴�0	1�IB銮���_��T�rR鋅�T�4�%�
�d�o
4���j�yB	鋗��p�c#
R
鋣�������

����qB鋮�?�:��n�6R
鋶��Y�C�>"�Y��F�wB錄�}�@�?
B錀�f1)@!
;B錃�g�RR錝�T#�!�"�[�]��I�|�I��*�����
B錳�x�@
�S�$()R鍀��U�B�{�J��E�K�z�&��e�j�y�B鍒�R	鍔�)�d!.��RR鍞�#�.�M�
�	3�U��4�/����r��]�`�*B鍰�'�nR鍳��
���b�0�?�%�0'�,��R鎀�� ���V�.�I�{�A��B鎌��k�H�K#R鎔�?� ����~�F�G�:�S�
�2��#	�t�R
鎬�E��8��=�
�	��+�./B鏃�`B鎷��H�,!!B鎸��L�Q�Q�
�F�R鏊�W��'�[�l!�e����o�X�B鏟�_B鏞�K	B鏠� �<�TR
鏳�b�N�a�%�#,�'�~"B鐀�[�p����jR鐆�k��/2&#�
��'�
B鐛��H�:�+R鐤�C�g[�R�m����v�����u
�	R鐵��
��������
B鑄�_\B鑀�,�
�Q	�:�5�~�J�#�-	�,�MB鑔��*R鑪�1�f�0�#�E�D�I�H6�s�n�:B長��H���JB鑼�_B鑻���F�G��z�U�^�
�vM�Y�$0�o��O�P�L�`�L�E�Q�R
開��b�[�f�e	B閖�U��q��j�?R
閞�r�f��������|�`B�閫�3�%�&�)��|�Q�F
�@�r�}�r�w�x��=�$�+�>��1���v��+�4�X��� ������b�y�t�w�@�t�y�x	�k�j�K���z��@�(���'�"��'	���X�A�W�L�_�*�5��V�F�v�U�N��`�i�w�|�#�&�z�n�@���V�Y�V�i���.B陻�,�Z
B阸�Q�H�)�-�j�<
�QB閪���}�h�H�h�-�,�Q}#)�{�_R雒�i��t�U�N�^�+�"�#�,�)�"B離�N�<��v�"�p�D��n�N	B雡�e�
�!�H�2�B�L�r�zB雴��N�R霛�I��/���|���)�*��w�r�qB青�!�B霰�V��O�
�	�z��=�z�O��H�vB-霱�@�|�� �!�<�@�B���
�f��x�D�u�o�L�d�A
�K�\�z�y��`���2
�@�D��(�/�tR鞗�x�K�F�|���6��C��p�9�2�9B韋�(!�MB鞭�V�S�� @�O�
�v�	�R�4�b�H�e�v�|�^�_�X	BE鞨�,�T�<���
�P�I���6�o�I�@�H�:����@�0�;�p�L�M�:�4�<�a�E���[�K�t�E�.�G�`�H�� �-�5�J�~�w�^�g�X�k�L�Q�K�R
顃�C	���>�I��J��B顓�\�.B顗�t�B顖��nR	顤�5�������:�_�[��B顯�H,�v�C��vB顲�r�v�+�L�
B页��
�~�5R
颶�7��n�k�z��7�2��~B飄�W�U�
�|�t�F��p�u�L�W�\��S�N�G�,B'飀�8�<�&�o�r�b
�L�
�E�M�F	�\����h�D�~�
B飃�x��>
����9��+�X�
������,�D�D��
�OR
饐�{�E�N�*�_�$�'�"�yB首�/B饛�W�v�Hl�u�{�@�K�4��[�nB饝��>�_�w�v�R馪��Y��m���'�"�'�$B駁�v�xB
馹�k�D	
�NB馸�K�,�G�(�R	駕�w�B�C�����8�AR	駟�t���m��G�L�<�3B駩��I�D�KR	駴�S�V�Q
B駿�:�A�.�JR	騉�@���=�4��B騔�
�
R騙�z�
�F�O�,�1�b�YR
騩��U�P
�*���LR騴��I�N�S�V�b
�~���#�,B骨�vB
驀�*�XB>B驁�@�>�\�f�g��U�B�,�w��b�u�J��)�`R
骶��g��[�R�P�L�I�W��b�u�B髁�d
��V]�H�1���Z
�G�|B髟�3B髀�H���l�<��t�C�|�/�X�R�u��p�A�o�bR鬈�J�*�'�)�$�j�mB鬥�x	B	鬚�k�.�'�T���R�SB鬔�
��6
��E��k�R�{�y�v���^�q�8�D�^�a�zR	魊�O�O��X��M�4B魚�aB魔�-��q�Y�P�&�Q�d�&�!BZ魕�g��G�T�j�.�-	�j�@�@�K�4�+�x��&�'��h��&����^�c�0�#�
�;�A�R�G�~
�R�e�T�M�v��\�k��BR
鰅�

�8�S���y�pB鰓�_� z	B鰒��`	�H		�@�'�,B鰐�:�F�x�s�N�I�	�7���J�dR	鱈�i�{�T	�0�AB鱔�>��1�,	��L��T		�"�Q�\BO鱒�T�D�;
�F��<��V�d�j�3�`�V�p
�@����3�&�/
	�4�+�,
�4�1
�n�a�A�t�g�P
�R�EB鱼���L�t�)R鵖�e	�]��z�	�s�R
鵧�q��_�T�U��N��	
�K�bB鵷�"��#�n�YR鶀�($�^�)�M�FB鶐�$� �R鶖�3	#!�TB鶯�1�IB鶨�.�p�S�A�H
��B鶫�0�>�B�=R鷇�W�*�C�@�'
�@
�
��Z�7�!�

B鷥�B鷡�f�c�F�W�D
�D�B
B鷼��QR
鸉�?�|����^�]62R	鸗�1�$��1��4�}�9��PB鹵�c�z�{�B�*�B	鹺�j�E�3�T�f�"�X�W�RB鹻��x�E�S�L�	R麐�1�i�q�h�j���m��@�c�f�m��z��-�Jr�lR麤�J��K��<���p��a��`�6�B���g�B麴��2�y�0��D��*�+��t��f�*�\�`�$B麶��x�j�@�u�t��Y�X�k��P��`�0B麽��U�F���?�@R黴��>2�m��n��u�,��n�[��v�
B鼎�{B鼇�?
��B鼀�q����2�C�r�$�+��R鼢��R�8��S���z�5�0�h�,�-�(�b�B鼻��p�eR齂�
�^�@�#�tb2����^��^��B齒�	��l�i�XR
齙�C�>�I���9�4�3��"��'�B齪�t��U���p�B齩�P��b���O���b�*���~B齿���V�>�#�|R	龤��*�fP>B#�:��t�a�E�h���z��Z���!(�M�N�m�"�r�1�uP�|�j�I�r��
�'�T�i
��t�w��E�s�n�z�C�r�}-�p�L��$�y��p�T��WB�h��M�m�^�.�m�rH�k�Bh龯���=�N0�Sz���#
�
�Z�M+�}�O�|���t�J�_�1�0��^�U�T�y	�x��@�$�	�K��[	�`�����T���c
�b�X�3�2�Y�^�J�	��k���e�z�c�b�x��\��t�W"�Y2�o��D<�7�L�6��X�/��2�o�KR	��J�>���?�]��e�dB#�.�e��#�
6�@'�T�X�-��(�3>�Y�X�U�2
�/�X�Y�:{�[��<�g�~�$~B�_�#�s���i�mB���jB�i�j�4B�%��\3�a�F�;�"�&�'B�$�'�|�^���is��t�yB�Y�z�(B�PB��a����K�h�c�_�R�@�U�T�U�T�U�`�	H�#�d�e�d��W�V�]�"�E�#�:����'�T�U�T�D�(�)
�S��3�2�=�<�;&�
�X�Y�L�h��y�b���b��}���)�g�h�8�=
	�k�D�<�=�<�=�<�U	�i�\�]�l�m�H�M�B��		�,�-�,�-�,�V�!�a�d�>�7�2
�%�*�+�*���+�*�+�G�(�)�(�)�,�K�J�#�,�!� �!	�$���E���
���
�	���}����N�Y�`
�B�%�(�_�K�8�3�2�3�6
�;�{�D�E�N�O�D�v�o�n�i�n�o�n�o�v�o�v�q�t�i�[�c�f�g�p�q�v�s�r�s�r�g�j�c�f�e�I�p�_�f�c�j�5�D�*��f	�.�b��
�K�K�J

�/�>�H�n
��I��
��M	�T��w�z�qS�r���
��M�	A�t�s�r�s�t�s
�A�K�X�w�v�q�p�o�n�G
�E�D�C�B�X�m�p�S���������� �!V;�F�:�t�G2
�@
��378�d�B�e�x�y2�v`�}�H�G�y�
�5�w�a�F�+�w�q��@�Z�r�b3�K�c�XZC𠂊�2�C�f𠀡�u�I�G���o�|���7�U�"�3�d�Q�V���j���c�P�h��{�@�G$�~�d�	�v�*�%�@�6�A&�w�a�n�c)��]�=�G
�f��}�a��)���t�;�H��j�$
��V�5�@�c
�f�e�:�K�=�e��w�-"�X��'�r�;��u�b�`�B�p��|�y�~�
�#�x�Z�V�U�]�0�Z�K�d�M��
�<�{�F��|�3�k�7�]� !��Z�\�E�)�5�U���m��S�&�	�|�
"�K�,�~�_�
��a���M�X���S�t�[�K�K �,�f�x�?�f�c�Q�)�&�P�i��j�s��t�� �x�4�a��[�R�29
z�	��X�R),�?�k�8��
�B�C���^�4�V�S�
	�{U�h�^�-&�V�]�T� �t
�;�m�2��u�S�$�k�q�D�m�D��z�s�O�N*�L�b�=�p�@%8�G�
�L�1�9�	�k�B�\�9�W�%���N�X�-�D�
�Q�j�Z��$�+�?�z����v��
��N��]��cT�a�\����0�F�B�c�
�N�j�S��F�N�L�O�T���F�^�:��E�z��e��}�V�J�Q�7�$�>�U�% �1�J�.
�K�h�;�U�2�{�H�)�\�(#�'�F�(�d
���R���x�V�N�x�C� �j�)�)�V�$�
�*�b�n�o�Q�?�D�u�f�4���v�3��g�Z���l�(4�{�m�`��}�_�H��������>�R�x� �Y�n"��q�e
��2�c��x�!�g�\
�H�_�|�&�o�i��i�!�]��c��x�K�m2�f�X�*���k��0�S�;5z�n�s��+�T��%�<�h��%�`�&��_�J���m���X�j�}�x�s$�z�*�O�E��o�|�B"�%�e�Z��i�n(��i�R�F'�I�o�#��`�\�1�G�P�6(�;�F�\�9�y�D�0�T�
�!/�T�U�)�,	�*��y��}�m�)�f�� ��b�i�d@
;:�^�	�9�)�v�W�a��q��A�*>��P��l�7�r�4�s�@��i�z��A�L�p����p�;�r�C��"�7���*�q
�
�T�:)�5�{�y�f�%�Z�r �[�E�`�M�p��"��G�<��m�!�k�(�~�{�/�E��P�z�v5�G�@�p�"�Y�P�.'��[�)�"�{�@�e�i:�t�z�!��Y��O�3��W�W �L�`�G�!�j�x�W�T�J4��@�Z�W�d14�#�H��<%�
Q�i�'�j�`��`�'��3��W�z�Y��m�j�
�0��w��=�b�F
�E/�X�@�=���i�Z���W�&�
�9�^�y�z�,��0��K�s�2�U�L�[�T�C�z/�a�_�!�m�[�j��7�Z�f�w�2 �m�C�o'�x�u�6��|��	�c�t�M�/�S�h�N0�G� �Q�Y�8�
�
���O�p���e�z��q�P�|�Y��f�i��j��s��^�S�-�T��'�B�@�t�K
�F	�;�C�Z�2�o
=�@�>�q�F�0�h�M�D�X�B�q�g��1��o�\�O�x�E�V�����R	�~�]�j�z��I�>
�
�O�b�Q��#�l�a�%�Z�$�R�!C[𠂆��df���z��|���L�P�a��$�{��Q�e�V���a7��v�}�pO�
+�xb�zq*@�P�('
�l�!�O�r'%�[b��x�'~�GOFi$�<�G�C�[�p��%�	�,���F�i��P��*�	���r�D��u��j�[�9�>�Me�5�lw�n�"�s;�
���	5��v��
��v���|�zk�K?�
H��'�+�S�>���r�oQ�w�g�^Cg𠂔��\�"�h�v�}�� �K
�1�K/��W�P'�g���U�{	�$�c��b+��R�Q
(�o�g��~��a� U�HT�%�|��W�|��'���{�[���o�6�Z"�o��]�k��e0�L�{.�P	�!�J�1�W��&�r�x�~���f�{��i�'��_��^&�a�m���z�b�(�YC_𠃮��=�u�R��{�'�s�Fv�5�)�u��Z��W�|�Y�l8�.�"G�~8�}'*��S)�(��B����B�l�Q�����{L�i�H�4�{��"�z�~��cl�H
���S�l�����f�B�P�#�U�
��x�D�'�=�]�b��n�	�;���6�[����k���b�Q�3��h�W�z�)�\�[�M��U�V�|S�_�eV�y���o�c�a4�Z/�w�O�R%�M�[s�.n�i�N��j�K���a�w�b��Y��n�E���{�p$�d�d�V�U�S
𤨡��F�L��b�#�2c��M�"C�>𤨾�.�C�)� �/>�8�o�[��Q	��Y��e�Q�^���@
��\�"�	�J�O�N�;� �K�J�(	�!
�6	�'���S�?��L�y�m�l�6�$��q��H�i��A�t�F�e�%����w�n�
�a�
b�c�h�E�w�x�D�I�n�6�v�8��]�l��	���{�D�n�F�a�2�a�[�0�/
)�c�[�j�e�@�d�s���q�<��w��O�c�T�)���l�5��^�u�:�S�U�
�M�d�>�%�9�_�4��9�yT�	?�i�Q�Z�;�\�5��o�G�n�1<!�a�b��
�z�*�x�M�q"�N�]�(�p�3��,����g�^�*�]�r��y�|�K�x�D����n��z�o��n��$��[�}�,�X�v�4�e�W�G�`���.�_�K�#
�P��
�x��
��h�B
�S�m�R�j�[�2�I�W�d�{�H�G��N�#�E�|�T�{��S�-��r6
�.�p�h�X��2!��L�p�Q3�"�L�/6��i�i�^�>=���@�:��4
�Q�`�B�+�}
�2C𦉪��z�`��~C𤷫��t�d��-�d��t��$�v��p�U�`�iO�s8C𤩏��Q
�kQ�.��|�p�o���4�a'CS𤩷��%�[�7�l�s	�6�X�l�`���oW�o�J�w�Y"�n�X+��)�E�[�����+�w���
�l'�k�R��
�7�^��j�_�2�q����j�d�k��f�}�@��_�K�Z�e�]d��p����h�*�G�=��8�)���f�a�g�~�u��� �'�x�:�`�u�5��y�	�,�o�F�G��\�6�R^��d�l�s�k���r�`�&
H�w�q��c�V(��S	𦴢���)�&�u�c�.�2�C𧘇�8C�U𦵑�%��^��7�q�
�{�<�m��7�x��O��o
�?�o�a�� (�2�&��L�T�
�D��V�,�
���>��r�2�j�^�-�|�9�3��r�{�8�t�_&�D�]�A*�2�S��L�)�}�E�I�e�,��6�g�'���a���-�@�n��|�[�T�>�M�%�_�
�]�S�w�&�D�J�W�z�e��*��l���W��n�e�L�m�y/�i�T�*��T�J�8�w��u�H�Q���W���M�>�+�
�]�p��	�z$�^�B�p�<�5��j�
[��H�M�<�B��z�i�w�$�s'�W�Y�n2��[�&�}�r����g�u�7!�;�j�,�[��T�E� �H�N�n�9��n�{�r��K�_
�)��o�|�l�+
Q�l�p�&���T��R�Z��J�"�i�@�w��_�c��8��s��w�N�Y`�/�n�M�g�P�\�3�H�<�7�u�.=�*��2�C�c�f��Lt��P�d�MA�F�G�f�O�{�6
��e�l�\�6�-�c�:�9�V�h�V�-�F�g��E�v�}�H�Z�}��=
�z	�y�C�*�W�u�i�.�g�j�3�d�N��!�y�d�x��g�V�k�W�p�R�b3�G�e�p
�5��`�#��@�e�\�I�N�N#�{�k� �_�p��Y"�R�k��/�l�)�q$�.�H�Z��>��
� �q��
�L�k��s���w��.��i�z�S�C)��XS�<��Y�k�@��n�w�G�����}�}�X�[�=�m�(&�{�z�A��n�
�m�f�t��h�1�2�g�J�{�f�d�;�[�n���J�j�G�4�j�)�k��V�I�j�n���Q��z�0B�W�'���j�K���}.�a�R�f�'
�L�l�J�O�.�_�S�	�
�W���`C𧀎��}��� ��|o�U�h�{	��8p�I�40�P���b�}��
�%�T�L�P��/�M�	�&�k�?���%C𦺙��&��oP��w=�:�Ci��R�����sq�!b�dEC𧥧��yC吆��kCs𦵴���y��m�r�k��3��sz�,�B��L�!�!�rG�c�$�z?��e�h�1��2�X�3*�X�o�Y�k�D�,�0�P�u���B�u�Y���L���^�	�}�|#�%�S�)�g�v�;�<��U�=�d�z�#�y�`��j�k(�Z���@�\��}�8��u�$�
�%�j�T�k�#"�f�g���<��M�'�D�X�%T�l�X�H�%�
�t�/�.�K�/��3��r��c+�a��v�8�9�y�^(�v�H�q��&��! �X�$�e�$�T�f�U������j���V�V�-��]�s&�~��}�Q�P�y�Q�^��8��@�S` ^a
¢��m�!�2�9�@�H�J��j�q	��@�b�n‘�&�(�	�W	��\�y��e�{�Y�X�2�	�z
	��o�>�J�M�"��F�]��[��d�z�|����'��*�P�B�V�Z�a�e�j�V�.�c�k�v�z�~�@������ ��
�&�u�C�y�a�;�{��qe�������*�a�j�J$�Q�d�����n�r�p��.�$�W�#�&�z�~�k�}�~� ��z�3�$�A�y�,�Z��S�7���E�`�1��;�	�s�s�?�w�P�5�E�
�)��\�{�X�N�`�[�h��t�y
�d���� �^�4�6�N���w��?��k�A��;�c�/�t��"��\�r�	��	�N�9��5 �p�H�=��N�8�X�;��B��w�T�h�Q���p�B�~�_�n��Q��g�g�(�p�V�r�g�E�Z�Y�0�)�\
��T�d�J�b�=��u���i�z��"�]�p�j�g�O3�'�~�{��}�g�K��H��E�r���\�d���'�m�c�\�^�r�l�+�r�W�(�&�9�`�0�%�n�I�+��C���F�k�=�	�!�g�`�7�C�V�E��4��E�i���u��v�^�4�V�o����E�v�}�:�~�R���.��m�L�d�����u�#�k�W�M�#
�^�p�+�b�w�_�0	�k�+�s��S����z�@�	��*����
���B���G�K�Ipt� �M�_��\�	�M�m�F���b\⿐��2��s�-�h�y��S�#�8�[���=���q���?�i�x��4�`�f�s��i��+�;�}�V�{�o�I�a�4�F�t�b�U���.�p���v��b�w6�n�w��]�O�w���T��1��#�k��|�Q�D�}���a���O�6�#�}�Y�V��-��^�U��,�u�o�+���I�h�j�z�4�i�e�'�t�z�9�z�8�n�?�|��m�V��I��8�,�*�h�Y��i��Y���<�o�b�s�`*�]�i�`�`�*���]�c0�!�b�\��z�n�%�^�Z���w�b��{�Q�z�4�`��;��=�^�D��9b&俶�(��L�6��=�e�0�U�H�[��
�
�p�d�A��J��^�l��{�o�&�}�A�
��H�u�8�'�M�l�D��v���k�O�'��o��G��J�L�Q��z��c���h�"��{�^��s�8�6�;�F�>���H��Fb偣�J��[��L�z0�}��b��s�*b庲�Q��_�^����=�T�A�
�W����N�#�$�-�v�@��H��n�\�u�l�x�{��)�J�M�]�m��~��	�
�
�v�bF亃�a�����.�x�C�#	�	��8�~�r��o�U���u���i�t�~�'�i�$��e�b�h-�<�L�I�x�l�E�H�@��T��v��m����!�q�3��/�6�B��;�_�S��V��a��g�^�
�1�q�e�s��w�v�y����k�z�&����h�h�e�y"�q�g�g�h�p�n��
�Z��"�!�d�%�(���^�>����O�X�P�t�#�~�T���c��t�`�v��yb斲�ub獞�j�h�t��	�d�b.嚌�B�#�Y�t�6��a�`������T�5�@��B��E�#�`�.�u�4��d�{�s�p�E�C�V�.�i�^��
���'��X�<���z��k�o��4�z�e�G��[���k�(�X�z���
�;�J��	�>�q��4�l�[�O�{�U�q�1�
�5�x�~bg儭��z�p��J�~�	�K�|���[�M�%���O�P�Q��+� �T���1�Z���:�v�b�X���L��r�C�/�_�X�]�[�	�G�h�t	�
��;�b�F��S�y�m��~�Q���U�m�.��i��R�
�]���
�'�P�_�-�
�X�,�~�I��b�w�Y���X��g�l�3��B��e�w�C�B��"�|�H��,�L�\��v�N�I�&�V�9���c��g�o�T�L�n�s�Q�7�v�'�L�y�(�E�y�|��Y�T�Y�-�\�K�E�Q�	��|�O��s�R�y0�/�U�+�4�`�E�=�0�s�L�A�D�~�Fb�↸�-��K�:�y>�A�@�	�7��@6�3�5��V��Y�w��r�;�|���9��H��-���k��C����+��2�@��K��T��g��o���@$��	��1��<�@
��K��W��j�����@��
$����8
��>�@��I��M��Q��S��c��m��t�@����
����'��=�@��A��G��J��`�B)����-��0�@��?��X��f�@��~����!��)��8�@.��;��k�B��x�+"�0�V�A�Y�_�d�f�k�p�y�}�
��A��(�1�8�@�R�Y�^�g	�l�w��@��!�&�3�<�@�L�V�l
�s��@��
��'�@�E�K$�`�@����*�@�A�G0�P�@���+�4�@�A�S�i
�r�@�}�)��@	�;�E�d�@�y!�{�� �"�@�60�<�n�@&�u��@(�5�^�j�o�@?�s�A�3(�F�p�@	�r�}��@*�2�]�@�q
�v+��@?�1�@6�q�*�@!�1�T�f�_�c��N�Z��U������J�!�#�%�+�4
�=�F�A�I�M�Q�S�U�X�]�e�@�g�m�s�w�{�}��	��C���!�$	�'�1�4�6�Q�F�u�P�U�\�^�b�e�j�m�@�q���A�1�4�:�>�A�F�H�L�P�C�R�T�Z�]�_�u�w�z�~�B�����#�*�@�;�L�Q�U�`�h
�l�A�w����#�B�2�:�D�G�K�O�B�m�v�{����	����%�V	������#��'��,��:��@��G�@��K��O��R��X��a��o��t��~���[��
���}�3�:�@?�J�@?�
�@?�J�@.�
�h�a�r�b�I⏚���j����!�~��.��1����F�c��I��R��X��]����O������t��t��y��M���k�u��i�����x����X��F���R���]�
���i��/�
��N�o��Q��S��U
��[�A��f��n	��s��}������	���I������ ��'��+��2��9��>�B��B��G��I��R��Y��\��_��b��q��s�B��x��z������
����&�@��,��2��8��>��F��N��Q��T��[�@��]��b��k��t��z���������B����������'

��.��<�C��>��B��E��G��I	��N��V�C��X��\	��a��d��h��m�Q��r��z�������R�����c����������#��'��)��0��C�@��E��G	��M��O��S��X��Z��]	��`�K��h��k��r��y	��}�����B����	���������Q�����y��0��7��:��K��I&��a�@	�������4��7��<
��?�@#��M
��s
��v�B��	��������
��#��1�s��B�P��E	��R��\��b��e��i��n�@��r4��t��_�@��g��|��#�D��*��5��=��@��D��L�_��l��}��
�����)��+�D��1��3��:��>��D��F��R	��^�@+��h���m��F����%��-�E��c𠵾��\��]������L�2����5���2��]��P����"PK
!<��<ܝ�2chrome/pdfjs/content/web/cmaps/UniCNS-UTF8-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
UniCNS-UTF8-HB–x��i�����Wb〈������
��r�PK
!<gYYf�f�1chrome/pdfjs/content/web/cmaps/UniGB-UCS2-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE����!a ^A	��'�vG%�O9A�
&

1!bA���Ra��
��%�67�`�Z�a���m�7�Tikmol	�*�#�8�G�%�,I�z$�:�9�<a��3
�5�6�"�6�7�8�9�O�:�;� N	�)�<a ���QA"�	��t��k
��F��g��f��ka"'�A")�9
��\
��Wa"d��)��Il"��M	�f
�R�>��5�4�7�6�3�2�/�1�05�.�-9�!� a%K�ba	"f�D-�)�F�#�G
�k�z&�}$�#�q.���������#����&��"��'��,	��$��-��.q.���0��:��<��=��>��@��A��B��E��C��G��H
��J��R(��XQ�V/�B�@��r��-�
�;�]��a��l�I�Z��.��-:�*�+��&�
��C�)�V�C�9�
� �5�;�p��z�	���g�`�}�<�1�^� �u��/�6�(��a�6�i�#�U��X��K�p�D�m�;��M�$�{��I�$������|�j���=�z�!�j�f�%�:��H�~��]�N��Z�h�M��M��<��;�@�R�c�:��j����M��)���k�b�r�7��&�%�H�C�9��c��>�|�}��q��z��e�K�q�Z��$��
�}�H�9�S�8t�T�m�l�5�(�_�U��`��i�(��F��}�3�f���v��U��4��
���D�9����UF�B�8��
�"�F�k�w�5�d�;�V�Q� �C��j��{�q����g�n�X�5�*��`��a/��L`gq	0h�(�s}��=q{a0AR�d
U�7c	$�<�v	�pa0���K\�$�)]�#�&�2�!q��j���"�	���a03��{��~��UT��gb��v/��s��qj4F����%*��H��$���s�����)`��
��~��+
����*�d�����|���|��|��/T����._��i��1��4
��I��2��Wr��w��3���jM��v��7�0��Dm��u��8p��c��9��T���s���s���sU��s��>�(��I^��r��?���Q���q6��q��As��(��G����F*�� ��I!��K�U��m��J(��C���lK��l��L��8��K���L"��j��N��
��OQ����P��d��Qq��t���fF��f��S1��-��T��_��U��a��V��e��X��f��[��z��Z��}��]��\G�����[���[v��[��d&��R��a��e��`[��y��U��f���h��m��|aN�B�`�X�~�YQN�#��%^�L�P�-�������1�m��
��c��.�K�$��TaN�_QN"�i��p�9�b�T�����g�j�h�aN.�eQN0�~��P��m��l�
��o�n�#�)�f�x�h��3�8aN@�lQNC�.��{�z��]�d��	�	�<�9�S�z�`�5��`�\��
����	�PqNZ�u�u�t��9��T�w��{�v�}��y��D	��)��Y�QN���~���t�y�9�2�1�w�"���x��/aN���^��p�t�QN��q�}��9�A�(�!�a��M�*�����x�q	N���T��!��"�&��&QN��a�1�I�xI�k�H�/��&��?�^�w�3aN��+Q	Nє�Z��!����x�y�aN��.Q	Nݥn�������k�r�Fq'N��3�#�5�N�%�L�6��9�X�(�:�+�q�N�;�)�>�z�?�_�@��A�.�H�H�x�2��B�(�J�Y�*��v�'�OQ
O�Z�H�#��D�Q�P��N�]�\aO(�S�@�/�UQO/�u�F��|�C�B��K��J�W�V�m�l�E�D�E�xaO>�^�I�c��eQOM�J�9��$�3�d�g�f��O��N�or�W�Z�M��>	�_qOa�n��*�q�p�1�q�&�r�;�t��f�v�D�:�x�9�y�@�B�}�?�P�~QO��=�������p��7�6�C�B���qO���D�	�]�p�
��
�G��-�Q
O��i�P�{�n�M�:���"�!� �7�)qO���S��L�#��\�,�H�%�.�)�r�K�i�L�,Q
OכL�e��,�Q�P�O��r��N�K�D�SqO��4�E�6�F�r�G�7�f�)�$�9��:�Q�;�N�?�>�@�V�C�L�D��IQP	�b�P�W��s��X�s�r��K��f�YqP�O��)�W�R�W�S�9�U�P�VQ
P%�Y��|���2�qlP/
�Z�O�e�4�f�`�g�\�k�L�^��n�P�p�_�J�q�]�v�S�z�T�{�r��`�	�e��}�f��a��[�J���0��g����M�'�
�>�(��y�0�b�1�c�%�d�2�b�.�4�L��K�l�8��9�e�n�;�h�=�T�@�9�B�X�E�^�J�_�K�B�f�N��Q��T�,�j�g�S�Z�i�h�k�]�P�a�z�b��e�F�fQQ��2�7�6�1�0��n�)�8�qQ�n�V�r�@�s�P�N�t��v�v�|�G�}�1����R��W�U�Q�QQ?�8�@���O��1�\�8�A�u��4�}�|�aqQN���e��t������t� �S�%�F�'QQh��<���'�Z�z�f�f��9��>��u�H�k�B�k��>����6q"Q~�-�0�z��/�=�1�O��3�(��5�5�9�f��:�;��<�+�j�=�3�@��A�l��B��k�GQ	Q���M�Z�
�4��F�
�h�SaQ��J�r���M�j�OQQė�$�s�r�~�#�"���:�:�
��[��Z��}q
Q��W�&�`�`�a�+�O�c��eQQ��T����e�5�p�&�����+�o���G�&�`��i����!x�n�CqR�q�s�M�
�s�<�t���v��C�x�n��>�5�P�7�{��|�w�~��Q	R(��i�:��W��V�m�Q��<��QaR1���	Q	R6�E�]��@�9���b��}��|q?R?��&�4�=�H�
�c��d���-��)�t��1��i���������	� ��!�:�g�"�+�,�%�i�'�J��(��/��0���O�1�O�=�6�:�4�I��5��u�7��8Q	R��o��i�)���r��cqR��@�n�y�m�*��D�c�Q�8	�G��]�Q�>�R�q�S�'�V�h�W�T�X��\�@�]QRսM�u���C�T�l�u�t�u�I�Iq8R��b�2�d�W
�e��#�p��s�u�|��u�
���x��U�|�$�}�}��5���f�D���|����	��|��(����h��E��^��F��D�u�D�Q
S>���N�U�.�s�r���S�~��waSK� Q
SN�?�p�T���NK�f��(�,aSX�$QSZ�p��j��Q��P��3�V�;�Q�:�n�g��=�O�
�/�.�7aSl�+Q
Sn�j�o�$�)�t��
�N�/�j��%�H�Z�9qS{�0�a�4�L�6�1�O�e�7�b�9�b�
�}�:�%�A�P�A�(�CQ
S��~��+�f�B�q�`���!�.��W�0q$S��I�y��L��O�B�P��Q��U�E�X�Y��Z�Y�;�-�#�6� �Q�]�?�`�|�b�|�s�M�;�c��dQSߧ�m����w��h�D��b�U��3�0�O��f�H�~�[�P�b�I���l�:�0�W�]�TaS��kQT�m�'�&��k�p��x�[�Z��U�,���4�)�;�-�h�&�V�@��~���2�UaT�sQ	T�,�����(��)�<�Z�WaT$�xQT&��u��6����/�X�_��'�N�)�J�\��3aT6�}Q	T8��'���S�_��0�!� �7q3TA����w��o��a���(���|��
�t��
�7�g��;������[���"�E��~�����$�+��!��#��$��%��&QT��G���J��_�L�O��^���;�q�8q
T��-�E��/��0�#�-�5���&�+Q
T��g�s�V�6�����3�2��G�aT��9�9�<QT��%��u��1�i�F��F��G��F��E�Z�k�qBT��A�'�Y�0�/��o�� �$�C�>�D�(�E�*�.�0��F�G�G�b�w�2��q�'�J�k�e�L�}�6�O��U��4�V��W�
�X�l�Z�i�@�[�1�\�9��:�a�5�c�1�i�3�m�7�R�n�;�pQ
U*�<�j��u��t���*�}��H�QaU4�u�G�xQU<�F�j�e�r�p�w�v��C��:�uq UG��@�,���E��I��C�H�	�N��p��e��@�o��=����z�A�Y��I�p�Q
U{�Z�G�L����Y�B���%qU�� ��T�$�g�O�"�>�%�Y�&�^�(�0�_�+���-�V�.�E�5��7�^�8�*�9QU��R�2�]�x�y�x�)�(�U�T�E�DqU��\�@�j�,�X�@�E�b�F�o�o�H�d�P�]�g�J�X�KQU��!�X�_��J�i�h�m�l�3�t	�R�C��3q-U��e�S�k�U�m�W�r�r�a�X�I�{�]�|�x�^�j�b�v�A�c�w�|�=�{�e�0�i�z�t�5�j��w�l�u�c�m�z�y�p�}�r�L�sQV,�	�r�S�0�g�A��>�Y�N��
��S�l�[qV<�x��{�t	�|���4�������
��o�Q
V`�p�@����V�>�{��i�:�YaVm�QVo��,��P�D�E�D��9��8�}�,�Nq6V{�
��n���	�7�"�u�&�v�:�'��*��+��.�f�7�k�9�9�:���?�}�u�B���C��G�{�~�L�J�[�L�j�	�5�N��O�!�S��U��WQVژq��>��O�|�g�6�?�F��qV��[��a�s�b��T�d�6��f��%�i�S�m��kQW�s�L�&�s���4�B�b�Aq9W�p�F�H�t�~�v��w��z�r�|�<���'�)��(�@�,�9��*��+�F�/��
�
�6��0��(��X�� ��?�J�U�`��v��T��.�^�z�u��a�QWd����'�&�S
�1�2�Z�S�R��UqWp�#�:�&�9�M�(�6�5�+�<��I�2�4�0��=�4�H�B�9�!�@QW��CC�#�8����M�B��I�H����~��qTW��H�>�K�A�L�I�O�z�.�X��_�l�?�a�H�c�q�D�d�E�G�F�f�N�h�W��*�i�M�k�R�s�K�t�J�x��L�!��z�Q�|�S�~�����L��%�O��P��2��(�,�
�U��.�T��W�
��-��=����8��s�
��V�+QXJ�c�*��f���
�A�6��quXU�3���5�V�:�'�=�/�X�>�A�Y�C�}�K�V�N�#�R�]�Z�S�b�T��U�[�B�X���_�:�\�d�2�e�9�1�f���n�_�r�y�v�Y�$�}�]�~�C��%��g��&�
�m��u���&�)��R�;�(��'����+��V�$���.�2��T��/� �C�I�"�8�$�&��(�)�)�!�+�/�0�1�1�{���5���7�@�8��:QY"��t�C�F�������_��^�/�)�E����q;Y2�A�h�C�K�q�C� �E�/�F�|�G�0��I��J��1��L�y�M�	�=�O��P�%�2��*�4�R�3�S�M�X��Y�M�[�[�^�^�"�_�z�`�o�l�d�/�e�\�r�f�z�iQ
Y��u�g�f��z�=�+�*��:��q�XY��n�w�p�	�>�t�&�{�v�s�w�-�~�z�z���~�^�x�}��@�����H�
�}���
���|�S��4��� �>���
��-��{���	�$�<�%�S�'��*��6�}�7�(�:�/�;��M����
�<��>��B��C�R�G�+�J��(�L��N�/�O�i�R�N��Y��Z�}�[��`��?�c�d�g�"�	�i��s�~�w��~����"��y��	��-����
�A��B�#�I�
�*�|�5��7�!��>�$�?�$�A�i��E�Q�b�G�(�M�@�O�"�T�#�U�(�w�*�V�+�Y�%�6�\�&�]�)�_�>�a� �b�'�g�v�j�}�o�x�s�z�,�z�~�(�{�.�-��������!��/����0�"��1
�$�{�/Q[P�0��t�u�)�\�>���t��w��}���Aq[`�5�9�1��5�P�7�Y�v�Z�a�9��j�<�6�=��>�e�@�A�AQ[}�X��T�Y�S���D��'�z�E�n�e�I�e�����.a[��G�!�IQ[��1�4�9�8�-��d�}�r�`��x�����<�%�V�;��7q [��O�I��R��V�]��i�E�X�3�8�Y�z�_�S�\�8�^�7��a�i�_�6�c�G�f�e�e�gQ[ݘ[��?��x��
�t��u�M�:��'� � �=�_��w�v�uq[��u�
�7�y�j��7�Y��z�p�|��}Q\���B���B��3�~�4�.�MC��(��_a\�Q
\�:�n�;��\��C��B��y��x�'�Yq\��6��-�7��)�
�8��|��9�Q\7�\�3�~��B�E�i�`�Y�!��;q\C����y�>�=�l��Q�`�h���Z�"�i�%Q\^���:��q��F�K�v�y��z�mq�M\i�+�>�.�t�~�/�v�0�`�,�7�Q�;�_��<�0�A�+�-�D�.�3�F�4�H�/�U�1�4��5�I��7�<�M�{�R�:�9�4�S�;�V��W�6�X�>�A�Y�8�]��^�=�_�?�a�B�g�@�m�D�s�,�z�A�C�t�|�*�}�]�~�J����	��7��-�
�4��
�D��K������a��k���L��J�!�I�&�G�F�'�)�(��Y�H�*�O�/�P�N�7�"�?�W�V�3�A�
�D�M�K�R�M�T
�N�X�Y�U�Q�Z�S�\�Y�`��Z�l�q�m��o��~�[��\��
���
�^���3��<��_�#�i�(�0�)�U�-��.��4�+�\�5�[�W�7�}�P�F�
��9�f�:�5��$�-�X�=�X�<�\�>Q]��w�s�����V�I����S�)��z��a^	�G��J��k�!�LQ
^� �=���~��)�H�{�Y��q0^�O�C�{� �V�)�Y��z�Z�"�\�w�]�'�%�H�_�#�6�a��c�b��&��d�d�j�'��q���r�(�t�)��v�*�4�D�w�2��~Q^r�D�.���j��5��4��O�j��w�F'��o�hq	^�����i�<�	�5�3�Q^��=�Q�8�6��?�6�'�v�S����;�u��.��O��N��{�Q�q?^���5��V��`��V��:��8��B��7� �9	�$�w�3�.�=�<��4��6�>�:��x�.�v�1�=�?�A�@�B�E�C�x��I�|�S�J�y�L�-�[�7��s��k�O�.�R�@�v�SQ
_�T���;�:��#��"��)��(��s�q
_�Z��\�=�f�_�[�?�6�c�o�n�dQ	_-�p�j��#��"��/��.�g�f�iq_6�j��<�m�r�n�>�q�n�v�0�wQ_L��l�e�d�3�2��u�a��T��5��,q_Z�~�T��n���_�����T��[�.�	�d���`��a��b��^�4�Q
_��*�Z��b��_�K��(�e�!� �;�P�hqK_���7�E�n��f��$��g��h��!���^� �i��V�&�j�1�(�k�.�#�/�|�0�l�j�2��B�A�k�7�C�9�<�'�;�E���w�<�D�=�-�+�?��B�Q�I�D�J�F�,�H�N�y�J�O�G�K�e�M�Q�P�P�RQ
_����
��O�:�!��l�
q?`�L�U�Y�X�V�8�U�Z�n�\����]�Q�_�T�x�1�`��[�b�$�S��|�W�/�X�e�P�h�R�m�9�
�r�Z�t��`�h�u�~�|�e�}�c�����������k��?�Q
`g��w�?�T�a�$�	����}�cq)`q��_��0��\�_�^�8�b��&��g�A����i�n��h�!�o�"�c�#�d�j�&�f�'��+�(�Q�*��,�]�-Q`����:���k��j��+��0���m�f��{q`��3�l� �5�f�p�=�Y� �@��E�;�HQ
`جo�4�3�2��o�2�:��)�
�`a`��MQ`�L��w�0��<��_��'�h�T�#�Z�J�G�<�4��cq�
`��S�"��9�U�x��Y��]�_�I�^��y�i�a�u�f�C�(�=�j�K�s�l�v�|�n�t�
�o�z�r�5�z�:�|�3�{��K�
�{�b�Z��0���� ��^��]����7��$�/�_��p�;��2�N��R��N��<�!�|�k�C	�"�2�,�?�X�3�:�5��<�>�6�~�7�.�<��>�{�D��`��F�	�}�G�1�H�D�K���N�m�T�m�W�^��#�N��6�[��,�_��`��^�i�`�l�t�n�8�o��xQa���v�[��0���[�Z�=�}�ab�}Qb���y��8���z��E�?�d�m�d�Y���A��/�d�9��J���|ab�Q	b�|������Mab(�Qb*�)��F����S��R�9�8�A�Yjqb5��<��t�s�\�n�K��u���v��m�:��=��j�R�u�.���"�E�$�h�+Qbf�!�"��r��3�L�5�l�g�g�P�qbq�.�m�0��2�;�4�2�6�s�u�/�7�R�:� �\�>Qb��]�G�����;�e� �j����G�jqb��F�<�f�|�=�I�9��J�+�L�<�z�N��R�7�U��X�=�c��Y�w�ZQb����S�_��>h�i�J�<��U�"�9��h��a��`��)�z�a����A�~��6�F��qb��a�E�c�J�c�e��$�b�m�4�fQb���L�I����?��>��C�*���O�sqb��k�S��G�&�o�{�+�p�7�N�?�t�s�x�G�z��~Qc�>�����I�Q���G�\�Y�Y�q@c&��/��s�o����O�
�D�W���Q��u�F��b�����E�Q�k��V���}��7�e�%�d�P��&�6��P�)�]�*�M��+�I��-�:�.�0�J�P�2�3�6�A�
�Z�7Qc���&�9�r�R��%��$�_�W���:��kac��<�;�?�!�@Qc��z����o�g��T��I��H��I�"��+��6�y�r�wac��L�W�L�NQ
c��?�o����+��*��~�m�l�
q�c��T�W�W�[�X�;�Z�>�Y�m�4�]�,�^�E�a��d�X�g�U�d�h�*�i�8�m�n�#�X�p�S�s�q�t�H�u�T�v�*�z�V�:�d�N�{��}�_�b�[��r�'��m�_��6��`�F�
�^��a�d��c����l�q��6��[��z��2� �Q�!�^�"��]�e�(�`�$��%�`�Z�+��,�K�/�a�2�e�@�3�`�:�	�;�g�>��?�_�B�^�D��i�j�E��K�f��L�Q��M�j�Q��U�/�h�V�l�X��_�\�,�\�T�_Qd����*�5��9�~��l��7��6�I�B���-��,ad��jQd��g�u�d�m��z���A�|�I��>�E���Z�ad��kQdʾg�k�t�������~��-��,�ad��q�p�R�s�L�tQ	dޛH��\��
�
�����eqd��{���R�$����m��e��-�R�f��z���	�c���
�s��<�Qe���l�A�@�g�f��y��^�e�d�{q$e��Y�V�c�t��)��|�#�u�&�$�	�f�l�(�2�?�)�~�*�b��,�#�1�r�K�3�1�5�y�7�w�8Q
eU�g�?�J���=��<������}�q	e_�>�M�[�A��C�h�c�GQ	ep���j������_�>���y�bq	ey�M��C�V�S�Y�L�\�\Q
e��A�/��h�9�8����W��V������5ae��d��f�b�gQ
e��@�s��B������'��&��
�C��:��}�<q�ae��m�W�s�Z�t�D�@�v��y�G�F�j�E�z�H�H�~�I�B��J��K���m��;�
�0�
�x�;�*��W�<�
�l��H���+����E�����
�"�D�#�|�}�$�^���'��*��-���.�=�2�F�O�3�r�7�^�8��9����;�f�@�A�A��-�c�C�K��J��L�r�k����!�N��P��R��S� �T�}�V�s�W�d�X�t�_�	�]�d�`�V�"�a�F�c�	�f� �i�#�j�+�V�(�l�$�n��r�p�@�v�&�{�y��%�]��Z������[��'��'���&��N��)�'��)�(�w�*�g�,�*�1�+�3��5�~�8�w�>�w�9��<�#�;�#�=�;�9�K�>�\�@��5�s�4�D��E��G�,��J�5�K�,�N�T�O�y�PQg&�,�.�G��h��'�o�K�0��,������[q*g2�Y�r�#�[��_�+�a�j�c���d�F�f�T�u�g�Q�i�Y���h�j�R�k�i�m��r�S��W�H�s��v��U�X�xQgm�s����I�,�<�{�m�B�?�>�7qgx�}�b��>�p������W��(��`�	��
Q
g������e�t�n��/��.��� �pag��Qg��w�b��Z���{�v��G�m��2�w�v��c�zqg���m��k�O�B�q��t�'�p�c	�(�c��G���<�2Qgذe���W�F�9��F��1��0�qg��8�0�:�g�=�`�?�O�c�x�A�A�8�B�u�J�X�o�KQh�E����y���o�r�L�;�!��Z������kqh�S�{�`�U��Z�n�]��i�d�v�k�"��t�nQh<�l�T�Z�T�Y�s�6�,��m��b��]��V��+��*�s�r��5�}��0��Q�<�&ahV	�v�xQ
hb�{�K�L�w�|����h�eq�yhl���	�a��
���~��<�{��
��
� �|�j�#�u�'�m�(��*�S�o�Q�-�h�1�0��2�x�4�		�5�_�?��@�c�F��G�P�H��L�5�k�_��N�0�O�N��S��U�r�X�f�^��M�_�P�b��0�e��f�i��j�U�m��r�1��s��s��t�������3�
�&����-��>����~�!�~�s�)��+�#�-� �N�/��0�-�2�,�4�u�5�"�7�
��8��:�o�=�9�%�.�>�L�@�#�C�@�D�!�E�A�(�+�F�;�I�1�9�O�$�Q�/�|	�S�0�F�]�h�2�^�3��_�5�a�:�c��e�1�h�v�k�6�l�7�'�'�W�*�t�F�u�4�x�j�~�8�:��|��>��B�!��"��<��*���l��K�!�>�)�?�L�,�:�b�0��1��2�L�6��7�=�;��<�H�?�C�@�w�A�K�D�C�C�H�>�t�J�]�L�F�P�M�m�W�G�X�h�[�]��\�A
�^��i�J�p�N�r��u��x�r�|�I��P�O��5��R��x�	�E����
�Q��S�O������!����8�%��4��n�:�{��q�;��p�B�i�C��D�o�J��N� �P�T�u�X�t�]�}�a�y�c�~�f�z�m�4�Q�K��1�n�x	�p�`�z�2�~�3�z��c�{��4��5�u��5��c�
�-�
�6�Q	k_�K���75�!��\�[q
kh����Y�$��3�x�(�5�R�)�VQk���
�&��c�6�*��%�G�E��.�'q-k��0�Z�4�2�6�\�7�]�8��:�_�<��>�`�
�C�	��E��=�z�
�G�z�H��[�J�O�M�n�?�N�j�/�P�r�T��U�D�VQ
kҋ}�b�}���0��?��>��Sqk��Z�R�_�T�x�g�
�j�U�m�W�n�X�q�V�t�@�u�Y�[�Z�m�zQl�A�K�@��/��k����)�X�
�a��"al�^�r�Ql���V�E��T�O�@��S��E�J��4���u�F�j��@�C�B��I�vq0l6��%�
�o��R�,�p��t�x��q�"���� ���p��]�!�E��~�}�j� ��"�(�%�%��*�&�+�*�,�+�-�%�/��w�2Q	l��\�h�Y����!�����q
l��6�'�8��%�:��&�<�l�@�'�AQl����j��	����E�K�#�$�E��N��3�e�(��l��/�Oq.l��H�;�5�
�J�m�2�N�M�t��U��O��s�S��{�V�_�W�.�Z�<�/�E�7�4�\�W�_�c�a�6��o��b�C�c��e�L�9�fQ	l�:��l��h����
��{�fal��1Q	l��j��e�g�G�&���X��wq*m�m�E�o�D�q��A�t�G�u�R�x�!�|�F�L��}�s��C��@�� �H��J�
�<�]�P��K�
�?�����
�QmA�@��$��#��"��I�g �h��"��c�w��X���'�,����/��K�Jq-mU���0��X��U��Y��[�!�u�#�}�6�%��R�(�d�,�Z�O�\�.�Z�0�p�5�\�7�[�h��8�$�:�>�;�Q�=�V�<�>Qm��!��B��	�&�5�4�k��H��]�E�� ��o�-�^���q,m��F�j�m�H�A�J�d�L��N�a�N�T�*�^�j�]�W�%��Z�~�^�:�b�'�f�c�e�_�f�b��g�>�i�d�j�^��f�m�I�nQm�'����_��^�[�:�C��`��y�:�)�@qdm��s�v�b�{�k��i�2�`��v�c��K��g�1��\��J��{��
�?�z�u���Q�
�k�f�K��V��t��W��3��q��c��	�v��u�m�%�r�)�5�*�S�+�`�-�]�p
�/�n�:�l�k
�<��A�J��M�w�q�N�C�P��w�U�5�Z�y�[�8�&�^��_�n�a�}�c�~�d��fQ	n��R��.��e��v��Q��P��k�qn��m�J��o��p�!��s�c��t�B�uQ
nˤ)�&�t�o�{��h��q��p����
��$��san��|�O�~QnޣL�T�3�4����������I�Lq$n���K����$�	�/�
�H�`�
��w��:�N������P��W����1�#��(�A���)Qo)��[�$�{��b��?��>��3�@�#q�o4�/��1��2�#��7�J�8�	�<��?�:�@�>�C�4�E��W�H�.�J�a�K��N��O��P�	�V�a�l�V��W��X�j��Z��[��\�c��^�s�a�&��_��b���-�d��l�(�p�e�+�s�d�u��w�b�x�^�|�D�~�O�� �����6�!�)�	����]��+��%����$�v�%�"��m���%�#�&��P�'��/�Z�0�S�1�2�i�d�7�U�9�f�:�_�=�x�>�O�AQ	p�Y�R��9��4�a�`�3qIp!�F�(�H�X�n�K��R�m�S�+�U�*�X�z�\�V�h�`�9�g�,�i��j��m�f�o�o�-�s�	�X�w�g�y��o�u�|�'�g�}�2�1��s��#����L���
�V�9�
���Y��
�H�N��J��M�O�Q	p��S�}�x�o�a��r��#��"��qp��'�R�I�D�*�P���+�Q�-�.�Q�-��1�V�2�#�6��>�>�q�?�u�BQp�K��t��m�z��=����Q�#�(��H��eq�p��I�z�M��O�,�P��S�U�r�o�^�W�c�X�e�U�Y�f�p�#�v�g�%�m�
�q�Z��[�y�
�}�_��"�a��b��X��V��]��p��:� �?�!�q�v�^�n�"�U�$�r�%�`�\�(�b�,�c�-�x�/��5�'�:�U�>�(�@�6�A�W�d��C��h�H�g�O�I�R�|�V�e�W�f�X�r�[��_�X�a��b�:�f��k��n�j�	�o�l�r�Y�t�'�i�v�2�_�k�z����]��l��<�
�Z�����I��F	���$�m	�%�n�/��0��1�l�9��4Qr5�#�a��+�p�Z�?����+��q	r@�7�K�q�>��i�A�j�EQ
rV�k��8��5��>��K��J��
����m��l��i�?qOrc�M�E�$�P�~�Q�B�F�U�5�W� �Y�+�
�\�L�G��R�^�I�`�~�J�e�K�f�L�g�M�i�@�l�C�t�Q�v�,�z�	�o�~�U�p��q���r�1��/�u�~�t�3�	� ��w�v��3��x������_�����y�{�"Q	r��\�_�t�C�q�4qEr��$�~�U�6��2�=�&�(��)��*��0��3��7�
�E��8�R�	�	��;���=�
�?��l�-��B�D�C��E�&�G�5�T���H��I�@�v�K��L�?�*�N��U��W�U�X��\��dQsh�R�0�+��C��n��-���ass�nQsu�+��9�7�n�%�B�0�.��7��6�5qVs��t�I�w�q�y�V�z�&�{��}���p�����
�����:���L�C��*�!�� ��,�j��"��� �$�#�s�%��'��F��(�&�/�+�3�h�4�%�8�*�9�)�:�k�=�-
�A�@�L�M�o�P�0�X�Q�9�(�S�.�k�W�8�7�`��fQt%�2��l��k��j��u��t��i��h��m��l��qqt1�m��V�>��o�j�t�`�v�9�w�\�QtU�<������������ ��.q5t`��]��%�j��?��>��'�>��@�#�C�$�p�%�E�(�a�)�F�*�B�,�J
�-�A�8�G�9�[�=�b�K�H�?�T�E�M�N�_�P�l�\�]�`�L�b�c�cQ
t��d�������o��n��o��n��9�5�H��"��9q"t��m�\��t��x�^�L�y�����%������
���E�x�.��C��3��p��4�Qu(�(���~��u�$���`��Q�e�V�I�0��r��+��*��W�S��(��O�mq1u<� ��"�y�#�N�'�|�)�{�8�+�z�F�,��0�=��}�>��4�B�8�{��:�I�N�=�~�C��D�1�E���F��K��N���P�F�SQu����,��
���l��m��l��m�'����c�(au��ZQ	u��d��n��U�`�����o��Fqu��^�f�e�c�2��l�k�B�g�F�d�i��f�.��%�hQ	u¶n������	�o�@au��l�p�nQ
uҠ ��"��O�s�8����s��Z��au��u�E�wQ
u�j���
��	����Q�Bq!u��|��x��z��=��y��z�|�d�.�w�
�}����~���{���������Qv�2�E�n��!��"��5���l�\��M�5�R��,��#q	v.�����o�
� ��"Q	v;�3�[��:��+��6�{�5qvD�&�/�(�1�)�4��+��1��2�,�3��6��N�7Qvb��o�>�K�d���	���K�q:vm�4��/�e�7�<�H�A�(�i�b�d�C�0�F�%�G�%�_�-�H�Y�J�.�L�3�N�/�O��Q�0�%�S�1�\�C�e�d�H�g�I�h�@�k��l�x�p�@�r�P�)�t��u�m�vQv͵���?�I���`��g��f��o�A�Tav��z�9�|Q
v��n�=��Z���w�Z�M��f�{qiv���"�a��X�)��0�	�M�[�������8��Y��\�A��A��Z
��`�^�"��d�&�_�'�b�*�D�L�+�a�.�U�/�c���d�4�J�5��6�"��9�h�:�g�=��e�D�i�U�L�o�N�$�k�|�P�l�#�Q�j�R�/��T��`�o�p�m�p�c�n��f�q�n�&�l�r��n�n�s�t�s�x��yQ	w��R��P��q��p�����$��-�>qw��~�u��\��v��Q�p�w��~�	�
����&�� �1�J� Qw�O�����y��x������A��@��)��!�+�hqnw��)�F�,�"�.�#�/��L��$��i�3�%�9��@�;�(�9�>�y�?��&�@��A�+�B�0�C�4�F�2�:�	�G�5�H�3�*�J�*�L�e�O�"��-�1�Q�d�R�$�S�(�U�A�V�<�W�=�[�6�\�;�]��^�+�8�`�b�e�i�k�e�n�>�;��7��z�o�b�v�1	�z�B���P���\�8�^�����	�@��
��C��
�A�?�C��Qx��[��h��������������=��<�q�x����d���7�a�F��1���f�G�w���k���N���H��!���$�K�E�k��'���+�I�Q��.�J�'��1�l��9�k���<�L��?�c��A�M��C�O��D� ��F�\��H�j��J��L�	��M�N��P���X�P��\�'��a�R��e�Q��j�f��k�g��l��n�h��r�S��t�U�x�\��y� ��z�y�
��{�z����{���~��	�C�����|��*�c���
����V����P���3��������������L��#�b���%���)�f�\�	��+�
��5�"�	��@�]��B���E�[�8��F�
��GQy��R�"���j��e�/����?�y��6��Gqy���O�L��Q�e��S�l��T�U��U�S�p��X���Z�]��\�?��bQy��O��X��!�����?��>����������/�Oq%y���j�&��q�M��s�u��t���v�$�#��w�!��y�q��}�
�%��~�&���i���H���%��
����	��
�������W��Q
z7�(��h��m��l���Zy�����,qzA�� �%��%�$�i�+��*�)��,�`��1��[��:���@�}��A�,��E�Q��H�n�k��b��IQ
z�,�^�a��@��y�x��~��c��b��cqz���P�L��T�+�N��W��"�3�l��Y�_�`��\�l�!��]Q
z���0����}�x5�6���qFz���c�%��g�9��i�j��l�G��p�$�a��t�8�`��u�b�k	��v����_���i�h�c���/���`���	�A���0���
���G���p�c���d���H���f���U���e���i�f�� �k��#�q��$�3�h��&�o��)�2��*�w��,�r��-Q{$�t�7��.������	�_��l��q{/��3�q��5�u��6�m��:�V��;�F��<�v��=�z�A�l��C�)��D�?�|��EQ{O�A�v�~�5��F��)��(��E��D��#��"��#���2��q<{^��L�~��N���O�d��S���Y����[�{��]�w��^���_���`�&��b�e��c���g���l���m���o�t���r�X��s�
�	��w�5���z��
�����p��}�Q���������3���Q	{Ľo��<��	���
�W�V�Q�+q�{�������O���X�������^�� �c���"����$�%��'�T��)�f��*�2��-���1���2�g��3�[��9���A��<���=���C�j���J���K�l��L�!�^��N� �k��P�"��\�P���$�#��`�w��a�
�!
��i�i��w�n�:��y���|�e�m���	���e���#�\�u���K�J�.
���x���L�l���h���Y�T�� �N�M�O�O��#�P��%��_��&�}��*���-��Q�[��/�e��4�R�Z��6�T��8�Y��;�U�X��<�4��=�V��>�]��Z��E�!��F�X�u�|�&�D��H�$�[��NQ
|�u�J�p�C�B�y�x�E�D��a�f�x��ma|���YQ	|��H��$��A��@��~��#�	�&q	}���X���^���_�'��aQ
}�#�����'��&��/�9�s�PqL}��e�M�s�l��h�E��l�,��o�F�1�&�%� ��r��$��w�(��x�*�)��z���{�>��|�1��}�,���5���+�/���h�d���!������@���|�J�-���T�h�� �Q��!�3��(�/��)�.��+�0�[��,�X��1�B��4�9��<�6��=��9��@�i��BQ
}��E�b�K�Q��6��C�d��dqN}���G�_�8�2�t��J��1���K�5��O�;��Q�7�3��S�T�^��X�4��Y�=�	�<��[�n�X��^�?�E��_�I��`�A��b�G�V��c���e���g�C�?��h�#��i�B��j�>��l�&��m��q�N�F�K�D��y�'�L��}����G�I�H���B��
�n��
����J�\��Q~1�;�(����L�K���
���5��4��9�q8~?���N���R�Q�k	���U��&�	���'�T��+�{��.�@��1�&�a���7�
�V�Y�X��8�.��:���?�z�M�W��A�H��D�S��I� ��J�_���K�n��L�$�p��O�:��P�Z��Q�l���SQ	~��S�I�H�c�b�;�:�/�-a~��WQ~��@�X�(�,��v��'�.�[�3�.��R��9�s���O�iq~���W�g�[�L�\��F�)�%�.�T�_�x�`Q~͚�@�5�
�I�t��`���n��k�S��2�.�/�O��a~��fQ~��$��R��}��I�P��L�L��b��1q~��j�E�l�7�;�e�o�*���\�p�8�a�r�s��s�]�j�-�v���]�x�n��z��^�{Q�O�!�Z�I�f��
�	�
�m��*��=�q:!���j�	��
���^��`�R��a���b�_��i�`��j�b��p���q�7�)��r��h��t����u�@��v���z��`��{��w�L���B���j��������,������
���	��Q
��b��>�?�>��o��n��5��4��qP����\���0���D�*�E��!�H��"��r��$�F��%���&�G��+�I��,��y��1�m�U��3�^��4�[��5���8���<�u��=�_��>�.��A�U�`��B�I��D�5�g�b��J�a�c��M�d��O�e��S�i�J��T�f��U�`��Z�J�t��[�2�:��^�I�S����_�W�7���cQ
��0��h��5��4��5�-�,�%��Wq)���g�N��i�O��l�P��m�Q�U�T���o�B��p�A��q�V��s�9��t�X�5�1�?��u���w���x�{��y�W�Y��{�Z��}�
�D�/�[��Q	�R�\��N��#��"��;��:��_��^��{a�[���	���]�R��Q�o��o��N���+�O�X���/a�z���y�8����Q
���U��^�?���L��G��F��/�ma����"�q��(Q���o��t������K��8�O��D��{�I�,�����9Ua����.Q���Z��4��t��u�g�.��*��{�'�� ��wa����4�y��6Q
���k����
��L�Q�c��q�p��p��aa����:���x��?Q	�֗�V�����C�:���a����G�1��I�m�G��K�zQ���Y�\����/�2�n�K�@��������W��V�������q����Q��S�+��U�R�c��W��,��Y�
Q
��#�5�%�.�O�@��"��G��F�������`q���^���`���m�'��o����p���s�|�y��t�A��xQ�F�F��p��S��R��K�F�.��R��=��<��1��4��W�A�V�@a�W�������I��Q
�e���e��V��a��`��s��r��_���x��Y�lq)�r������w�4�F�H��-�y�}���&���%���y���[��"�'��#�U��)������*�%��,�)��.�0��0�3��*��1�~��9Q
���A�X�� ��%�4�A�S�^�W�.q&����@�c��B��-��D�+�E��E�J��F�p��N�F��O�]�^��S�a��U��,��X�1��Y�)���Z�@��_�a�
�y��d�%��e�/�&Q	���f��}�]��l�E���:q����h�b��j�}��k�E��l��)��r�w�Z��s�+��tQ�(�0����	�x��~�����y�/��:q8�3�4�%�w�<�2�/��{�3��|�6��~�7���X���8���9���:��
�G�a�;���<���o������=���p���\��[��d��q�� �x��"�a�\��#�b�7�c��%�+��'�g��(Q���x��d��
���&��;�0	����	�q����.�j�s�k��2�n�$��4�}��5�{��6Q�����4��J��%$!�{�?�H�Y�J�e��v��A�@q����:�t�m�X��<�"��=�h�n��>�x��@�~��B�<�p��D�q�vQ�͉�F�k��t����)�4��b��5�:a����GQ
�ےN�p����	��%����1"�k�E�q����K�t��N�&��Q���R���T���V�Z��W���XQ
���\��{�W�C�t�A�B$/q�
��]���_�T���`���a���c�&��d��.�	��f�^�G��pQ�+�(�u�r����%��$��-��,���K���
���T����7q�=��w�%��z�#��|�R�"��~�$���!�i��Y���^���G�Q�]��
��W�%��j��/�q�^�7�8�s�nq,�m�2�0����5���B�=�@���<�m���]�+���h��!��:��$�?��&�C��'��;��)�D��*�8��+�}��,�3�E�?�9�
��0�4��t�6Q	����4��m�q�t�i�p��\��]�7qe����6�^�H��8�]��;���<���>�)��?���A�V��E�Q��F�K��G��O��J�W�k�a��K�J��N�o�\��Q�M�`�+�c��U�Z��Z�P��]��_�I��`�Y�$��a�U�4��b�q�N�M�[�=�X��d�T��e�M��k�R���n��_�a�S��t���w�r��{�I��~�S���l����m�����
�c�F���w���e�d�k�b���n���l�G�����Q�i�n��d��c�f���\������!�� ��}��|��;�L�d�o��t��{q�{��'�:��.�g�j��2��p��3�q��4�M�H��8���:�N��;�W��=�~��A���N�p��P���QQ
����R��.��i�Q��8��5��#qr����V�9��X�3�z���\�u��]�v�g��_�y��`�6��b�!��h�.�}��i���k��w��l�/��q��5�
��r��<��}�
��~���
�t���"�o������D���Q�	������ ���8���K�v����;���
��5�7��'�B��*���+�O��-���J��.�=��1�D�1��4�K���5�;���6���:�P��=���>��4�&��A�K��C���G���H�U��J�����K�D��MQ	���
�*��b��o�D��(��?��>��Iqw����U���W���X�Z��Z�O���`�J��b���f���g�����h���#��k�"��q�!�?��r�%��x�6���t�C��o���$���{���&����,����G�Y���T���2���&�W�(�� �'��#�X�)�B�S��'�;��*���/�+��3�Z��6�V��9�7��<�(��@�*��A�y	��D�[��N�S��O�,��P�a��R�p�7�Y�q�n��_�q���a�4��e�I��f�B�B��g�m��i�h��m��r��p�s��q�D��sQ
�y��,�;���S��qZ����z�R�z��y���������j�|��
�~�,���}�x�����{���	�������������z��!���$�v���%�
����&������(�l��*��
��-�j��.����0�m��4�
��8�� ��;�~�����<��!�Z��?�)��B���C����D�W������F���HQ
��z�0��p��%��$��_���J��S��R��_��^��]q�H���Q��.� ��S�#��T�*��W�$��[�)��]�(��_�+��a�!��d�&�,��f��'�<��m�4��o�v��p�7��t�.��u�]��v�;��w�2��}�8�:�<�2���5���3���2��
�\������1�9�/���-�F�\�C���B���6���5���=���E���l�}��$�&��&�?��+�G�I�D��-�>��.�K��1�J��2��`��6�N��7�P��9�{��<�A��=�^��>�Q�L��?�a��A�O�@�M��C�R��J�T��N�U��P�H��Q�S��U�k��V�U�[��W�	��Y�Y��\���^���_�X��a��b�
��e�)�V��f�Y��i�]�_��n�W��o�?�V��p�Z��t�o��w�[�C�X��x�)�
���[���\��
�v�]���S���*������������?�;���'���l����������m�I�&���"Q�h�V� ��x��W�!��v��?��>������a�s��(�S��,���-Q	�}�)�(�)��
���v��
��M�fq_����2�@��4���6���7���;�/��>���C�,��H���I�$��O���P���S�.��X�/��\�
���`�
�-��b�0�q��d�2���g�
��i�h�f��j�A���l�X�,��n�B��o�1�3�p�4��q�8��s�:�5��z�
�"��{�
�U��~�7�'�9�����,���v���?��	�
����=���<�6�>���;���G���{�A�� Q	�0�9��T���2�w��H��G��F��Qq@�9��'�]��)�C��.�R��0�=
��2�*��@�<�C�D�:��F�?��I�D��J���M�k��N���P�-��R�E��Z���]�1��^�_��_�-��a���e�2��h���k�6��m�8��n�:��v�<��z�d��}�9��~�=���>���?���R��Q
���}�������u��,���<����q"���;���>���
�W���Y���Z���X���3���[�(���\���]��!�@�	��$�
��%���&�}�_�K�
��-Q
��~�p��p����
��!�� ������'q;���4�j��6�b�K��7�x��8�b��:�r��;�P��<�S��?�e��@�r��D���F�]��I�T��K�g��L���O�^��R�f��T�h��X���Y�d��^�j��_�k��`�i�=��a�j��c�=��e���g�v�g���i�7��kQ
�i�,������w�v��t���?��n��L��}q�v��p�S��s�p��u�n��w�m��l�b��{�c�/��|�~��~�y�[���/���6���w��Q���=����
�f��$��K
��O��p��Uq�����G���E���\������u������F������4��Q���i��l��G��F��o��n��]��\��Q��P��Q�+q����%�`���(�~�G��+���,�u��/���0�
�S��1���4����5�_��7�	��8Q����}�0��8����~��g��f��!�� ��_�l�]��L���L��L��a���@�W��B���E���FQ
������u�5��(��s�|�[��N��kq8���L���O�_��S�
��T��
���V�c��[�:��`�x	��d���n��k��r���x�0��h��y�D��z�^����H������	�U���J���@���S���I�����!�m�x��$�{���%Q
�����6��'��&��)�=�#��d�%a���Q���)��v�}�����"�#�K��d��U�a���Q���q�V�K��&��V��U�u�,��
�xa���Q�ė_�H�o��B��K�m�v���#�(����}q���#�k�&�B�'�t�n�)�E��*�G�X�o�,�>�W�6�-Q'�ꁖ1��k�\�P�4�[�Z�m��l�4�X���
�{�m�f��b�2�)�(�?�>�[��@�]��L���;�J�9��(�{qU��<�L�>�>�C�A��B�Z�D��3�E��,��H�(�I�H�&�J��
�L�)�v�N�1�P�!��4���=�x�v�H�w��A���C�}��F���J���N�`�T��T�w�h��[�]��b�P��9��f�Q��m�S��o��R��r�7��s�U��z�T��}�;�����Y�$���:�e�p�d�0�a��Q���"���8��3�� �����N���B��=�k�|�l��:��!�m�H�?9�+�*��6��+�q����Qa�����2��Q���4��Q���3��J��u��t������O�t�i��;����%�Vq������5�x�T���s��%�)�7�(�	�G��*��+�.�1��/Q	��f����-�P��Z��;��:���a���4�=��9Q�����Z�-����g�{�J�s�'��E�|�k�\�]���N��y�\a�2�,Q�4�L��B�a�r��@�>�q�p�O�+�x�n��q�E�2�6�4��.��7���P��>Q�S�;�1�(�1��>��W�F�a�e�b
�E��>����*����x��O�0qp�h��C���F�u��G�<��I�o�+�u�J���K�h��T�p�Q��V��t��Z�r��_�q���a�z��d���i���l�n��p�s�x�@����x���z���{���|�9���}�� ���(���Z�Y���%������!���#�'��
��$�u���,�/���t���K��)���^���K���l���*�-�$�.���1�������$�0� ��'���(�w���*�2��-�3��4��5�2�7��7Q	�)��L��
��������
��q!�2��?�0�<��A�;�9��D�=��F�:�@��H�6��I�;��?�D�v�}��K�B��O���Q���T���]�-��_���aQ�l�&�w��n��7��<������9��8��Aq	�w��g���j�G��k�]��m�-��nQ���J��L��'��&��m�a�j��`��M��L��S�l
�oqf����u���z�N��{�O��|���~�#�����&����x���"������p���{�g�6�V�x���`�� ���!�
��'���*���.���4���:�������?���@��A�1��D���E���F�X���G���N�	�f��O�&��Y���T�;���Y�:�[��[�p��_� ��b�;��f���h�p�7��j�R��l�3�E�!��m�(��o�|�0��r�"	��wQ�_�F��t���(��X��e��d��S�L�y�|��~��G�5�4�K��[q
�q�c�`�f�i�h�j�^�l��m����nQ
��� ���F�R�-��7�F��&a���sQ������L���p���Z�:�o�n�W�o��d��}�a����	����0���OQ	������}��|��g�%�Q�@�Lq#�����=���H����r���%�=����a���V������"��M��#�>�R�?��$�N���&�I�y�4�u�~��'Q��I��>��O��J��I�'�l�����F��T��?q����-�E�{��/�Y�.���1���4�|�B�a�&�H�G�I��6Q�	�J*�P��\��Y��X��!�
�6���v����K�)����{�K�9��|��A�M�.�@�<��"q'�$��=�J��?�Q��R��E�;��G�O��I�d��J��/��L�G��M��a��O�S��P�[��R�S��U�R�>�3�V�T�!�&���V�L�X��WQ�[�Z�"��X��7��6��!�� ���U��X��ea�f��]�W�3��_�%�{��bQ�r���@�����.��/�$����1��0�������I�<��+�ha����j�q�_��l�F�a��mQ���c��)�� ��	����5��4��������������a����v�l��*��y�y��{Q���{����'��&��!�� �	��"��%�3�4��"��!�m�h�qH�����u���Y���w�t���x��	�
���4�s�v�"���y���}���~�|�y������1�{���z�
�������R������`�3��"���$�{���(��)�4��+���,� ��-�G��/�<��7���8�Q��9�2��;���?����B�;��EQ
�-������J�O�H��>��]��\��[q �7��M����O�	��W�
��Y�!�4�x�r� �%�z�y��Z�u��[�:��_�p��a��}��c�~�}�_�|�K��eQ�i����T��3��2���J�u�|��J��K�O�<��N�^a�z��k��S��nQ	�����T��i��h��U��T��7�Xq����v�	����x�U��{�V����~������$���	����
���Q����x��z��9��8��=��<������A�+��5�x�A�_��"��3qx���s�x���w�H�v���%����E�{��#�z��%�m
��&�}��1�y��2�~�M��3����8���:����=�]�%��@����A�z�&��C�U��K���O�����P���V���W��1��Y����	��[��4����]���a����c���f�V��i�
��l�S��m�z�N��o���q�U��t�
��w���|�k���*�������-���	�2���{������/���,���(������/���<�����Q���5��V��)�0��v��]"+����Eq�����$�%��&�M��+�Z��,�r�>��-�O��1�C��2�W��3�<���4�)��7�!��9�	��;�?��E�D��F�#��:�E��I�=��J�A��K�� �<�;�8��M�@��Q�M��R���U��X�K��Y�G�O��]�0��d�F��e�L��g��R�0�I��i�Q�J��l�R��m�8��p�y��q�+��s�M�%��u�L���w�m��y�7��z����J�8���N���|�T���<���U���V��
�h���V���W�S���H����\���Y��!��U��%�}��(�~��+�|��.�Z��/�^��1�4��3���7�^��=�`��>�"�T��GQ���cy��B��7R��b��_��^������Qqv����M� ��O�e��P�a��R�b��X�l��[�m� ��^���_�_�j��`�n��c��9�\��g�h�k�o��j�D�g��k�[��l���o�+��{�q��|�w��}���"���3��
�*���6�s�o����x���p���u���A�]�����$�z���'�w��(���*�$��+��{��.�#��2�y��7�f��8���=�|
��>���I�}��K�r��L���Q�t��R�N��T�F��U���Y�v��Z�d�?��^��\���c����"�Q	����a��)�m�~��������Ea�����S�R�)��Q�Q
����a�Q�z�9�&�wq	���#�+�%�,�&�n�'�G�,Q
���K�D�}�G�5�t���c��bq;���0��e�7��f�:�>�=��h�?�^�h�@�B�C� �D��i�F�I�H�J�p��K��F�@�M��P�|�Q�k��j�R�}�T�g�m�V�U��Q�W�-�Y��
�[�L�3�^��c�p�/�d��k�eQ
� ��l��A�6�C��.���L��s�lq=�*�i�l�f�o��Q�m��n�o�w�p�?���s��o�t��C�v��p�x���q�{�Z��r�|�[�}�_����s���t��d�	���u�
���C��v��v���w�O�u��y�I�}���?����@��Q���E��~��[�V����	�}���������vq5����	�i���C����y�k���5�
���G�I�F���O���K���s��!�w�O�N�J�M��"�P��%�l��'�A��(�n�Q�x��)�S��,�R�T���/�,��4�U��5�q���7�E��9Q
��J�t����h��{�4�G�,�r���ca���	Q���K�n��k��l�!� ��^��q�R�4q%��
����=��t����B�&���>����?���@�2�T��A���B�V�U��C�X��J�I��M�W�]��|��(��OQ�;�E�(��r��M�,����s��r��;��L3�I�h��r����a�N��W�K��Y�]�{��\���^Q
�[�F��2����������W�
��Ea�e��c�_��B�$��e�`��fQ	�p������8�9�_�h�B�a�y��j���nQ���L�	��X��%��$��;���8��'��/�Ra����xQ	���o�D��P��-�W�P��0���a��	��}�b�W���?��Q���f�p��Z���p�� ��A��@��u�9��,��Wq�7���z�4�������&�n�"�&������I��|� ���}���R�V���D����W�����!�
���"�O�a�R��&�c��*�P�b��,�,�B��.���/�C��0�|��2�3�e��3�c��5�d��f�>��6�	�h�g��9�A��<�!��>���C�)��D�(��L�i��N�j��P�k��R�]��S�_�:��X�
�l��[���^���_�2��b�]�`��k�g��m�a��F��o�d��q�L�W�k��r���t�C�j��v�J�N���[���H���I��
�w���;���#���J���L���G���~�� �O��"���$�N��(�G��)�M��*�P��,�Q��-�^��5�]��:���<�_��>�
�}��?�@��E�e��J�g��L�f��M�3�(��T�b�N�q��U�{��Z����[Q���I��2��5��>��t��5�	�|�S��������/��.q5��F�A�
�/�\��h�?�4��k�I��n�H��r���t�K��u�}��{�~��}�L�C���9������Z���s�a�M���N�v���K�Q���@������P�� �R��"�*��#�r�S��&Q�o�>�*�!��F��3�9�s�-��p��g��`��i���e�"�@�w�'?�Z���7��@a���dQ����*��	�9�m��,��g�\����-�	�D��nq|���h�~�-�j�@�l��.�C�n����/�L��4���5�x��6�N��8�O��=�8��D�P��E�Q��F���K�7��M�:��N�M�;�t��P�f�J���R�~��S��g����W�!��X�q��Y���[�"��\�K�6�5��c��d�-��g�S��h�#���k�g�h��l��$��d��n�8��r�%��t���v�C��x�.��~�i������&��
����5�'���(���u���)�k�,���j�y���k�j���f���g���l�� �-���#���$�Q�m�V�V��^�/�Z��P���S�F�
q
�x��&���(�f�&��)�5�'�<��*Q���(�C�#�d�;��>���[��^������eq���,��.�n�n�y�P
��/�+��:�^��<�m�B���?���A��i��B�W
��N�%��J��\����]Q���	��4��]��\��9��8��=��<��i�8q:��	��c�?��m�g��p�W��}���~�����G�
������7���p��!����"�p��#�����(�f��-�R����q��.�
��2�	��4���5�'��:�|��;�A���=���@�N��B����CQ
�j���t��g�(�I�(�5�r�����R�&�Qq	�w�;��=���?�P�>�@Q�����f��u�d�k�_�H����
��4q*����K�2�E��F��M�H�L�I��O�`�J�5�M�	�O�g�Q��P�R� ��Q�)�S�R��W�V�T�X��[�W��\�X��]�Z�Y�\��`�[��b�]��gQ
���a�������,�\����!q4����k�l��q�m��r�n��v�p��z�r�u�o��{�s���q����	�4���L���t���z���u���v��� ���w��!�h�x��#�P��$���%�L��&�H���*�>�`��+�s�F��3Q	�A�
��B�w�r��,��+q_�J��8�e�e�I��;�f��<�
��>�d��A�9��B�J��V���Z���\� ��e�!	��f�"��p�'�9�%��q�)��x�.�+
��z�*�-���(�9���6�0���8���
�7���E��&�:��'�C��+�@��,�=�A��/�>��0�?�@��2�;��3�B��7�D��:�9��B�L��C�G�K��K�I��N�H��P�J���Q�M��]�4��_�Q��`�P�R��b�O��eQ�1�26��(��#��"��i��h��o��n��+q�:�<��l�T��n�X��t�U�M��v�[��~�Z���Y�.�/���F���&���\���,	���#���1���H���	���Z�
�� ���"���$����%���&���'��7��+��[���,�$��-�%��.�'�W��/�/��0�U�3��5�7��7�>�W�A��9�D�$�F��;�G��=���?���B�\��D����J���K��T����Z�n
��[���f�C��k���l���m���p�}��r�o��s�
�	��y�G������

���P������`������ ���(�6��)���+�v��-���A���B���K���O�	��P���Z���]�L��b�D��f�`� ��l���o���p�l
��q�"���.����#	���	�%��
�$���&����'�"�)���� �(��,���4Q��!����Q�u�@�9�8�q��t���T��*a�(�7Q�-�e��*��M��L���%��$��u�tq�T�9�A��>�f�C���@�D��F�+�G��K��A�M�5��B�N��C�O��F�P�U��G�Q��H�T��I�U���M�V�^��N�
�]��P�_��Q���R��{��U��t��`��V�|��X�}��\���^��[��d���g���l���m�q���r�w��t���y�n���}�m������y���[���x���Z�	�S�*��
����*�������A�����	�������3�
�
������j���
�� �i��"�U��#�V��v���$���-�w��.�x�e��/�t��0���2�s��3�q��4�_��5�
��;���<���E���G���N���O�0��P���Q���V�F�k��[�S�
�B���^���_���c���e���0��g�q��h���j���m���n�r��s���w��m��~�n�-�o�~�u�7���3���"�m���C��T�4���C�s��
a���&X�
j��(�c�	a�0�	�+�5
�9�G�v��a��� ��(��0��5��;��E��M��^��n�G���`��������������PK
!<�aZ��1chrome/pdfjs/content/web/cmaps/UniGB-UCS2-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�UniGB-UCS2-Ha �V�W�~��[�?�>�H�R��F�P��i�B�D�=��@A	���3�2�
	��PK
!<Vd�6�6�2chrome/pdfjs/content/web/cmaps/UniGB-UTF16-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE���������!A^��J�vG98�<
&
�Q�Z1&#bW	e�-�7�vO@�mj�T�	z
!�<�%�e{tg

9
7	IA"D�
J59A��!�/�&�6�X�Y�O�H�}�r�:	.B�)}�IxA���R��S��{

Q�V/�B�@��r��-�
�;�]��a��l�I�Z��.��-:�*�+��&�
��C�)�V�C�9�
� �5�;�p��z�	���g�`�}�<�1�^� �u��/�6�(��a�6�i�#�U��X��K�p�D�m�;��M�$�{��I�$������|�j���=�z�!�j�f�%�:��H�~��]�N��Z�h�M��M��<��;�@�R�c�:��j����M��)���k�b�r�7��&�%�H�C�9��c��>�|�}��q��z��e�K�q�Z��$��
�}�H�9�S�8t�T�m�l�5�(�_�U��`��i�(��F��}�3�f���v��U��4��
���D�9����UF�B�8��
�"�F�k�w�5�d�;�V�Q� �C��j��{�q����g�n�X�5�*��`��A0g��~�#
�Z��*���<��Q��~�4q!�}
"	�*�5�E��n�5A.0?��UT"`2�L�)+�*o�}U`��Z�q�u��Wt
+�x�u�AR�92
�?*�
AI���eQN�#��%^�L�P�-�������1�m��
��c��.�K�$��TQN"�i��p�9�b�T�����g�j�h�QN0�~��P��m��l�
��o�n�#�)�f�x�h��3�8QNC�.��{�z��]�d��	�	�<�9�S�z�`�5��`�\��
����	�PAN\�u��8�N�9l�)�j�;A�
�7AN��YANr�QN���~���t�y�9�2�1�w�"���x��/AN��^�t�S�yQN��q�}��9�A�(�!�a��M�*�����x�AN��T�`�6�3�E�pQN��a�1�I�xI�k�H�/��&��?�^�w�3Q	Nє�Z��!����x�y�Q	Nݥn�������k�r�FA	N��qG�)7�$�3�/�AN�#�+�,�3�$�p���/�6�M�!�^�`�_�xAN��5Q
O�Z�H�#��D�Q�P��N�]�\AO*�@�\QO/�u�F��|�C�B��K��J�W�V�m�l�E�D�E�xAOC�I{QOM�J�9��$�3�d�g�f��O��N�or�W�Z�M��>	�_AOp�fAOc������W�:�+�j�_AOf�pQO��=�������p��7�6�C�B���AO��D��Y�[�"���
�5Q
O��i�P�{�n�M�:���"�!� �7�)AO��S	�����8�EAOťHRMAO��Q
OכL�e��,�Q�P�O��r��N�K�D�SAO�r���Z�)AO�E�=�NAO��6�o�mQP	�b�P�W��s��X�s�r��K��f�YAP��e�Z�t�w�=�6��Q
P%�Y��|���2�A+P:�O�H�)�v�!�"�+�$���E�Z	�G�P�t
�#��7�l�I�j�P�}�|��-�0�o�^��1�6���7�-�API�*�_�6 ����^�1�h�?�+_�V�7�<�s;�$�S�L�%AP;�e
;X
�Q�`"#,QQ��2�7�6�1�0��n�)�8�AQ�@�sA
Q�V
	�1
�t�=�|	AQ�r�e�tQQ?�8�@���O��1�\�8�A�u��4�}�|�aAQQ��D��a�>�<�dAQR�eAQS�QQh��<���'�Z�z�f�f��9��>��u�H�k�B�k��>����6AQ��0��]��a�`�&��|�o�A	Q��z�;
��n�+�*�S�RAQ��;Q	Q���M�Z�
�4��F�
�h�SAQ��r�>�OQQė�$�s�r�~�#�"���:�:�
��[��Z��}AQ۔&�r��k�9xQQ��T����e�5�p�&�����+�o���G�&�`��i����!x�n�CAR�s�2�~�#��{�o�)�*H�l�K�3;�8�MAR�nAR	�sQ	R(��i�:��W��V�m�Q��<��QAR3�Q	R6�E�]��@�9���b��}��|ARA�&���L�\�M�	��x��X���F�'�wx
�\�y�z�9�:A
RD�H�K�&�<�c
�j�~�L�y��aIA
RK�	�"




Q	R��o��i�)���r��cAR��n����p�%�L
�.�g�Y�j��V�z�%AR��>AR��QQRսM�u���C�T�l�u�t�u�I�IAR�W�x�1�Y��b��	���0�n�m�3�E�U�B��a�!�n�V�:�=�<
��`�AR��2�`�%�F4��O�NAR��d#.		
Q
S>���N�U�.�s�r���S�~��wQ
SN�?�p�T���NK�f��(�,QSZ�p��j��Q��P��3�V�;�Q�:�n�g��=�O�
�/�.�7Q
Sn�j�o�$�)�t��
�N�/�j��%�H�Z�9AS�a�+�H�:�U�~N�d�1�T�QAS��AAS��6Q
S��~��+�f�B�q�`���!�.��W�0AS���4�9��Y�	�J��j�$�-�%�x��l�M�Z�SAS��y�S�b�QAS��O
QSߧ�m����w��h�D��b�U��3�0�O��f�H�~�[�P�b�I���l�:�0�W�]�TQT�m�'�&��k�p��x�[�Z��U�,���4�)�;�-�h�&�V�@��~���2�UQ	T�,�����(��)�<�Z�WQT&��u��6����/�X�_��'�N�)�J�\��3Q	T8��'���S�_��0�!� �7ATF�w��b��&�n�{�^�Y�>
�-��@��s�*�w�$A
TC�



 
ATG�
QT��G���J��_�L�O��^���;�q�8A	T��E��R�/��-�qQ
T��g�s�V�6�����3�2��G�AT��9QT��%��u��1�i�F��F��G��F��E�Z�k�A'T̪'��S�|-�U�Z�M�V�/�o�(�t�?�E�j�y�r�.�p�o�,�:�G�	�I�S���A�@	�C�PAT��G"�HA
T��C"Q
U*�<�j��u��t���*�}��H�QAU7�GQU<�F�j�e�r�p�w�v��C��:�uAUI�@�)�0
�S�4�\�%�>�}�t��Q	�,AUO��'
�LAUT�

Q
U{�Z�G�L����Y�B���%AU���t�a{�N#4�]�\��A�,�#AU��[�iAU��%
QU��R�2�]�x�y�x�)�(�U�T�E�DAUĪj�}�j)�g�AU�X�N�#AU��EQU��!�X�_��J�i�h�m�l�3�t	�R�C��3AU�k#�1�b	�k�j�u�z�
�?�<%,AU��r��"���
��WAU��W




QV,�	�r�S�0�g�A��>�Y�N��
��S�l�[AV?�AVL�	��&
AV@�{��Q
V`�p�@����V�>�{��i�:�YQVo��,��P�D�E�D��9��8�}�,�NAV��v��V��
�FAV��
$AV��n�o�z�`�Q�I����J�E���	
�e���<��o�mQVژq��>��O�|�g�6�?�F��AV��}� ZAV��I�{�>�W�VAV��aQW�s�L�&�s���4�B�b�AA!W��W�m�@��Q��`�K�P�E�P�r��!��?��k��k*�E�b�N�!�6��@�kAW�F�AW�v	QWd����'�&�S
�1�2�Z�S�R��UAWw�M
�#�4�b
��OAW��AWs�:QW��CC�#�8����M�B��I�H����~��A*W��>	���R�Y�$�/�$�o�-��W�'��{�
�v�{�=��X��>����(A	W��*�?
�
�>�S�tAW��K.

QXJ�c�*��f���
�A�6��A,XX��}��/�.mr�q�^��1�O�:�B�G��G�u��C�B�c�J�t�e�V�=�=�K�&�0�g�����Q��Z�E�@�YAXW�
N��e�0�)�^�'2�>�y��
�M�<�/�P�X�y�
�K�\AXc�=(.


QY"��t�C�F�������_��^�/�)�E����AY4�h�D�5�]�G��I��{�)�M%��f�S�_��a�N��M��
�B{�'�*�qAY>�|*�<�{��QAY;�E
	
Q
Y��u�g�f��z�=�+�*��:��AjY��w�]�h�N�(��	�o�U�<�{��s�h��U�B�S�?�1�F�A�E��;�e���,�s�`��T�sA��{�n�O�%��P�E�r�H�~�%�2	�_�z�d
�1�2	�e	�L��Y�^�;��$
��
��� �u�.�B
�_�^�W�B�G�FAY��-�#�]$�c�T9�'��-�0�?��0AY��v!&$
"~�F*0
(Q[P�0��t�u�)�\�>���t��w��}���AA
[b�9��-�2�K�o�8�T�?�N��I�\A[k�Z�A[r�<Q[}�X��T�Y�S���D��'�z�E�n�e�I�e�����.A[��!Q[��1�4�9�8�-��d�}�r�`��x�����<�%�V�;��7A[��I�	�(�{�d�O�I�d�}�7�f�7��A�q�g� �E�~A[��3A[��X

Q[ݘ[��?��x��
�t��u�M�:��'� � �=�_��w�v�uA[��j�P�F�B�y�$�cA[��
�'A[��yQ\���B���B��3�~�4�.�MC��(��_Q
\�:�n�;��\��C��B��y��x�'�YA	\"�6�(�=��(�G��y�xQ\7�\�3�~��B�E�i�`�Y�!��;A\E��A\H�y��Z�7�c�L�$A\P�hQ\^���:��q��F�K�v�y��z�mAb\n�t�m��R��7�e��>��t�5�<�3�2��|���q�d�{�l�e�l�y�1�(���d�'�
�T
�:��v�O��@�;�^�\�&��G�2	


�_
�H�U�`��	�J�$��s�
�<�c�R�e�T�7�K�A\l�>4�QR�d�%�x�4	�k�`#0�A�,
�;��'�V�x�]A\m�.&: 3J
$" 4
Q]��w�s�����V�I����S�)��z��A^���g�jQ
^� �=���~��)�H�{�Y��A^&�{�H�	�E�N�;�ZV��!�����mA^%�C5��!	�:�|�m�%�$A^,�Y	Q^r�D�.���j��5��4��O�j��w�F'��o�hA^���v��?�[�p�zQ^��=�Q�8�6��?�6�'�v�S����;�u��.��O��N��{�Q�A"^��5�]���F�K�#�6�0
��	��O��L	�Q�9�S�5�f�%�I�J�U�5�]��]�jA^��`3�L��j�qt�&�dA^��
4B	Q
_�T���;�:��#��"��)��(��s�A	_��z�P��F���X�iQ	_-�p�j��#��"��/��.�g�f�iA_9��V��`�w��^��
Q_L��l�e�d�3�2��u�a��T��5��,A_i�{��UA_]�T�2�+�u�\�A�j���A_c�Q
_��*�Z��b��_�K��(�e�!� �;�P�hA+_��7��n��F�<���'�&�s�r����H�d��R�.��T�:��)���!��.�e�n�=�B�+�*�E�V�|A_��E�I�b��A_��Q
_����
��O�:�!��l�
A`�8��X�1�y�`�p�_�B�t�8��@�P�#�U�3�z��v�W�&A`
�Y			
�t�g�l�c�bA`�\%(Q
`g��w�?�T�a�$�	����}�cA
`s�_�_��Z�K��G�
�"�R�5�h�SA
`z�_
	A`w�Q`����:���k��j��+��0���m�f��{A`��l��
��/��1�dQ
`جo�4�3�2��o�2�:��)�
�`Q`�L��w�0��<��_��'�h�T�#�Z�J�G�<�4��cA`��"�d��-�A
�2�q�� �Z�a��*%�}�y�i��$��W�B�5��*�k�:�A9`��9��8�;	�d�(�
�t�q�L�	�
�>��^�[�I�y�R��\��g�6�@���^�+�B�y�0�`��f�[��)�:���6�7�5	��]�Q	�oAa�] #6
&%.Qa���v�[��0���[�Z�=�}�Qb���y��8���z��E�?�d�m�d�Y���A��/�d�9��J���|Q	b�|������MQb*�)��F����S��R�9�8�A�YjAb7�<�A�"�G�j�V��]�`�'�1�D�p�$�yDAb=�tAbB�Qbf�!�"��r��3�L�5�l�g�g�P�A	bs�m�J�N��{�r�;��	Ab~�sAb}�6Qb��]�G�����;�e� �j����G�jAb��f�*��	�Q�2 �z�U�L�&�.�+�PAb��<Ab��IQb����S�_��>h�i�J�<��U�"�9��h��a��`��)�z�a����A�~��6�F��A	bߖE�w�O�����m�z�k�Qb���L�I����?��>��C�*���O�sAb��S�`�{�C�W�^��q�f�&�Ac�NAc�oQc�>�����I�Q���G�\�Y�Y�A$c(�/��	�D�x��[�
�.� �I�#��T�i�2�@�m��5�r�Z��)�q�:�2��x��f�6�u�D�eAc>�Q)�k��H�k�gAc)� 	Qc���&�9�r�R��%��$�_�W���:��kAc��;����=Qc��z����o�g��T��I��H��I�"��+��6�y�r�wAc��W�hQ
c��?�o����+��*��~�m�l�
AGcĩW�A��4�Y���0�$�c�u����^�E�,�U�V�9�R�R�y��a�h�
�b��5�Q���S���y��<�g���q���B�.��b�2��P�:�)� ��d	�O�L�:�p�;�gAc���O�	�K�H�
�C��_�|�i�.
� ��g�1Ac��W

	
		


Qd����*�5��9�~��l��7��6�I�B���-��,Qd��g�u�d�m��z���A�|�I��>�E���Z�Qdʾg�k�t�������~��-��,�Adשp�B�@�OQ	dޛH��\��
�
�����eAe�Ad��
�"��d�E�n�q�H�&�Y�P��O�Ad��	Qe���l�A�@�g�f��y��^�e�d�{Ae%�t�+�*��u�
�g|�9�x�y����Ae"�Y���u�$�g�8�HAe-�#	Q
eU�g�?�J���=��<������}�Aeb�M��	� �Q	ep���j������_�>���y�bAe���7�a�p�aQ
e��A�/��h�9�8����W��V������5Ae����>��	Q
e��@�s��B������'��&��
�C��:��}�<APe��Z�J�W�W�:�r�o�B�e�h�M�Z��#�'7�!�6�
�P�M���n��@�7
��
�7���;�)��K�=�N�v+�p�J�g	�<����+�R�K��^�{�,�k	�M�+�a�8�$�f������)�~�.��"�
�!��3� �o�l�1�HA&e��D)�s



	��'
/�< Ae��W�
�*�Z*��"�_�9�x��<�A�1�4	��TR
�AAe��s	

	
 "

A^	
Qg&�,�.�G��h��'�o�K�0��,������[Ag4�r��V�6��V�'�{�\�9��'�8��K��q�`�S�nAgH�T

AgA�c
Qgm�s����I�,�<�{�m�B�?�>�7Ag~�>��Z��*�8Ag|�b
Ag}�Q
g������e�t�n��/��.��� �pQg��w�b��Z���{�v��G�m��2�w�v��c�zA	g��O�?
��d��r�.DAg��m
	Ag��
Qgذe���W�F�9��F��1��0�Ag�0�^�\�P�o�
�>Ag�g	"
Ag��AQh�E����y���o�r�L�;�!��Z������kA	h�{�7�<�#�2�?��A�Qh<�l�T�Z�T�Y�s�6�,��m��b��]��V��+��*�s�r��5�}��0��Q�<�&Q
hb�{�K�L�w�|����h�eAHhv�a
�=�p��
��q�6�=�,����3��@�i�#�j�-� �L
�c�3�{�A
�H�6�@��|�V�
 �h�!�T�+�$��
�!�k���,�
�F!��P�,�9�]�^��#�^��T�$'�s�H�G�r�~�'
�N�M��r��%AKht�!	
 		
 
%+		!

(
	
x�:	A!h��u9�R�#�Fb�i���78� �3�^%�m
��Kp�e�Z���x�G�k�81�!
�*�*��57�N�W�|2#mAh��j68XA'
!4
@
%+"'*!"			Aj��A,hu�	$,F .	


#..H*)6.H
1N3R Q	k_�K���75�!��\�[Akj���
�A�w��^��g�GQk���
�&��c�6�*��%�G�E��.�'A
k��2�q�[�X�W�<�v�q�/�/Ak��\�V�S�N�O�R��
�"�i�9�^Ak��6	
Q
kҋ}�b�}���0��?��>��SAk�R��9�(�Ak��@�'Ak��m	Ql�A�K�@��/��k����)�X�
�a��"Al�rQl���V�E��T�O�@��S��E�J��4���u�F�j��@�C�B��I�vAl8�%��;�2�y��y��L�1�!�X�1�{�j��X�/�"�K�5�|��GAlJ�"	AlH�
Q	l��\�h�Y����!�����Al��'�K�D�r�s�u��&��3Ql����j��	����E�K�#�$�E��N��3�e�(��l��/�OAl��
�;��4�3�5�t�)�{�#"��F�5"�t�_�V�1�v�gA	l��;

Al��NQ	l�:��l��h����
��{�fQ	l��j��e�g�G�&���X��wA
m����+�3��G�I�@�5��_�+Am�E



Am
�t


QmA�@��$��#��"��I�g �h��"��c�w��X���'�,����/��K�JAmY��)�N�M�q�p�5�j�]��h��U�)����M�$�+Amy�\
Am[�&0Qm��!��B��	�&�5�4�k��H��]�E�� ��o�-�^���Am��j�{�&�;L�*�'�6�f�i�d�q�n�?�	�X�|
�)�^�J�I�5�DAm�^Am��bQm�'����_��^�[�:�C��`��y�:�)�@A2n�b��z�o�Z�U�X�1�6�m�T�%��g�l��t�I�2�A�k�:�;�6��.�X��n�q�:���-�+�g�/�r�]������3��OAn&��s�x�r�e&��An�&,DQ	n��R��.��e��v��Q��P��k�An��J�S�@An���8�HAn��oQ
nˤ)�&�t�o�{��h��q��p����
��$��sAnڎOQnޣL�T�3�4����������I�LAn�/�:�b�$��s�\�

�w�rA
n�K��]�9�.�S���a�4An��	 ,Qo)��[�$�{��b��?��>��3�@�#A'o6��Y�X�-
�]�B��L�k�j�g��O)�j�S
���d�� ��	��!�]A'o8��T���D
�%
�6�k�z�+�Q�:�O���z�	�5�b���
�J�+�l�Y����z��*�� �!Ao7�1
	
"*	H`
Q	p�Y�R��9��4�a�`�3Ap#�(�c�d�Q�R�u��~��|�}�^���b�s�{�;��O�f��
�	�Ap'�X*�>�C�g�6"�E�w�4��M�c�Ap1�R,$Q	p��S�}�x�o�a��r��#��"��Ap��I��t�o�H��D�m�}�@�d�yAp��R	�Ap��*Qp�K��t��m�z��=����Q�#�(��H��eA"p���]�M�V�
�J�e�$�&	��s�i�j�.�c�l�v�u�8�"Z�?���u�
��p�P�F�#�Z��A6p��z�G,�h
����h	�s�8�'��L�k	
��\�g�(�/�(� �=	�<�_��X�e�}�j�i�f��c��:�#�
�
�'
��FAp��O*3N&*
)60Qr5�#�a��+�p�Z�?����+��ArG�K�5�*�BQ
rV�k��8��5��>��K��J��
����m��l��i�?A)rf�E�C�2��#�+��B��l���u�J�5�4�A�A��o�j��'�,���Q�^�<���BAr}�L�f�_�4�A	rh�P#(.Q	r��\�_�t�C�q�4A#r��~�SG�
�U�N��|�e��~�s�.��@���U�Z���j
�6��Ar��6�s8�n�B�-�+As�(Qsh�R�0�+��C��n��-���Qsu�+��9�7�n�%�B�0�.��7��6�5A/s��I�1�H�a�b�S�@��	�;�>��l�3�l��n	
�a�"�.�/�0��x�}��}�N�@�\
�	��3As��@As��y.$Qt%�2��l��k��j��u��t��i��h��m��l��qA	t3��n�1u�J�j�*��{	�DQtU�<������������ ��.Atp�>�P�oAtc�]��w�W	�6�7	
�&�/�	��"�g�`�#�,At�#
5RQ
t��d�������o��n��o��n��9�5�H��"��9At�\�L�M�%�p�{�x���d��(�!�x�yAu�%At��x$
Qu(�(���~��u�$���`��Q�e�V�I�0��r��+��*��W�S��(��O�mA
uE�N�R��m�'�@�A���}	�s�~
�\Au>��D�"���!�d�e�W�V��Au?�"
%.Qu����,��
���l��m��l��m�'����c�(Q	u��d��n��U�`�����o��FA	u��f�i�$�x�y��C�[�9Au��eAu��cQ	u¶n������	�o�@AuͶpQ
uҠ ��"��O�s�8����s��Z��AuޗEQ
u�j���
��	����Q�BAu���{�LAu�x
�b�o
����	Av�
Qv�2�E�n��!��"��5���l�\��M�5�R��,��#Av0��C�:�Q	v;�3�[��:��+��6�{�5AvL�4AvI�1�A�,�1�vAvH�(Qvb��o�>�K�d���	���K�Avx�H�A�
���~�
�
�M�V�9�@
�Y���O�TOAvo��.��"	��y�h	�i
�,�l�MAv��F 0
Qv͵���?�I���`��g��f��o�A�TAvۚ9Q
v��n�=��Z���w�Z�M��f�{A9v�"��l�_��G���i�8�@�1��0
�/�G�t�=�q�(���E�M��5�r�:�� �)���_�^���w�C�f�X��x��/�2���)�lAwO��+9�uAv�	
	

!,9LQ	w��R��P��q��p�����$��-�>Aw��\�h�
	�c�Aw��u�r�s���[�-Aw��	
Qw�O�����y��x������A��@��)��!�+�hA#w�F	��y�f�"�,	�)�+�p�~��w�z�?t�x�d�c��8�
�9�l
�O�6��a��C��I�J���3Aw��"
�l�Y�Z	�O��qAw��. *	Qx��[��h��������������=��<�A!x��7�-�Z�=	�8�
���5�D�{�l�Q�	�X8��s��'
�l��-�p�d�
�;	�/�\�~��*
�	A5x��d�=�>�?�^��A
�8��?�,�-�4�9���,�+�(��+�M�v�+�7	

�<�j�)�2�3�&���G�]Ax���5H$:
5NQy��R�"���j��e�/����?�y��6��GAyƍL�O��P��8'�BAyնAyʁ�SQy��O��X��!�����?��>����������/�OAy��&�L�1�0
�;�6�.�y�<�m�Az�$
	�R
�
�+Ay���s
Q
z7�(��h��m��l���Zy�����,AzF�%�t�`�G�x�A	zL�$�w���*�I�~�B�#Azj��@
Q
z�,�^�a��@��y�x��~��c��b��cA	z��L�<�D�Y�_�p���iAz��
Az���\Q
z���0����}�x5�6���Az��G�9
�J��m�t�i�`�a�M	�J�Q�g� ��}�|Az��%�&�	�
�x�,�1�}��N�u�I
AzƁ�t$	
	
Q{$�t�7��.������	�_��l��A{:�V�^�;�*A{1�q��+A{2��5
Q{O�A�v�~�5��F��)��(��E��D��#��"��#���2��A{w�w�G�"���I�<�	�G�$�1�@A{`�~�D�K	�t�		�V�CA{a��N	
Q	{Ľo��<��	���
�W�V�Q�+A#{ӔO�o�u�\�:�/��3
�6%�t�0�U�D�m�f�v��t��h�4�E��S�m�h�M
�/�x�s�l��EA6{ѹ��%�s���
��N�K���^�8����Z�g�s�&�U�6�i�0�]�I�6�z	�3
	�4�s�N�A{ҁ�&
((>(:
"*Q
|�u�J�p�C�B�y�x�E�D��a�f�x��mQ	|��H��$��A��@��~��#�	�&A}	��O�����*Q
}�#�����'��&��/�9�s�PA} �M<�O�L�M>�rA'}!�s�z�h�
��+R��t�o�j�y�p�v��>�te�D�3�Y�P�=�v�+��-�l�x�Q�T�!A
};��w	"	 Q
}��E�b�K�Q��6��C�d��dA/}��_�0
�}�1�(�Y�^
�	�v�?��*�i�d��-3�
�y�n�u��^��X	�9�<	�a�n;H	�{�n�
�)�W��]A}��&A}���J
!2Q~1�;�(����L�K���
���5��4��9�A~A�NA!~E�R�M
�R��g�z�3��5��v�'��U�V�A�$��g��J�/�j��m�>�]�$A~S��& "Q	~��S�I�H�c�b�;�:�/�-Q~��@�X�(�,��v��'�.�[�3�.��R��9�s���O�iA~ʇxA~��g�H��
�;�v�JA~��[
Q~͚�@�5�
�I�t��`���n��k�S��2�.�/�O��Q~��$��R��}��I�P��L�L��b��1A~�E�b��-�w�V�B�/�]�F�u��{�#�A~��o	A~���\Q�O�!�Z�I�f��
�	�
�m��*��=�A(��E�Q�Q�n���r�$
�Q�b�h�m�8�v���E�H��(��N�G��|�J�IAL�b�nA7��`		Q
��b��>�?�>��o��n��5��4��A��\�&�r�E�g�p�+�6�u�+�\�A��)���?	�~�R��q�c�l��@A��D�X�Y��	�9�`
�
����r
�s�xA
���!

"
Q
��0��h��5��4��5�-�,�%��WA��N�}�~�;�<�G�v�e�}��n�6��l�T�VA�,�BA�!��l	Q	�R�\��N��#��"��;��:��_��^��{A�^�
�y�Q�o��o��N���+�O�X���/A�}�y��5Q
���U��^�?���L��G��F��/�mA���qQ���o��t������K��8�O��D��{�I�,�����9UQ���Z��4��t��u�g�.��*��{�'�� ��wA���yQ
���k����
��L�Q�c��q�p��p��aA�̈�J�Q	�֗�V�����C�:���A��1�v�2��Q���Y�\����/�2�n�K�@��������W��V�������A��+�L�_�r�cQ
��#�5�%�.�O�@��"��G��F�������`A�/�'�4�
�A��
	�NA�0��oQ�F�F��p��S��R��K�F�.��R��=��<��1��4��W�A�V�@A�`��VQ
�e���e��V��a��`��s��r��_���x��Y�lA�y�4�"�}�H��y	�v�B�
��#�
�?
�A�t��:	�#�<�<�y�P�_A����

Q
���A�X�� ��%�4�A�S�^�W�.A��a	n�>A�Ơc�v�e�2��5�*�S��9�R�w�n
�n�/�4�mA�ˁ�D
Q	���f��}�]��l�E���:A��}��$�_��DA��bA���j	Q�(�0����	�x��~�����y�/��:A�5�%�]��
�P�#��U��D�R�!��
�W�V�q�vA�8�2	�L�M�d�/�7�d�)�V�^A�:��{
&
Q���x��d��
���&��;�0	����	�A	���j�{�j����o��n��uQ�����4��J��%$!�{�?�H�Y�J�e��v��A�@A
���t�+��

��fA���"A����<Q�͉�F�k��t����)�4��b��5�:Q
�ےN�p����	��%����1"�k�E�A
��t���T�����$��y��x��Q
���\��{�W�C�t�A�B$/A��T�m�/A��
./<3A�
��_Q�+�(�u�r����%��$��-��,���K���
���T����7A�F�R�]��!��/A�@�%A�H��~

Q�]��
��W�%��j��/�q�^�7�8�s�nA�o�0�a�h	��!��b>K8

�Y�L�-�*A���+�Y��jA����&Q	����4��m�q�t�i�p��\��]�7A3���^-(��ZH�Q�(
�s�x�Y�j/�7�X�k�
�X�k�`���B�4	��6�}��<?0�C�B
�Y�Z�s�l�i�A	��M�L�!�2�z�}�t�iA�Ɓ�;	!(
Gf	Q�i�n��d��c�f���\������!�� ��}��|��;�L�d�o��t��{A���:�^�U�^�g��L
�O�O�A���MA����2
*Q
����R��.��i�Q��8��5��#A1�ğ9�
���"��T�g�
��.�!�%�>�}��m	�n�T
�w��/��<
���	
���)A�˾"�
�8
�d	�=�c�.�;�L%'2!	(�9�bH	�A�́�\
 
$Q	���
�*��b��o�D��(��?��>��IA%�����
�%�*�5�<�_�/��h�S�T�u�K�@�|�I�J�� �k�B�.��
��o�pA"����M
�^�s�j�n���c�"	��:��`	�-� �1�<	?
�}�2�e
�c�F�f��+�t�mA����W$ 02D*Q
�y��,�;���S��A���R�#�(�&���	�B�s��4�@�8�{	�d�:�;�q�<�A'���z					

	
�|��z�{A����Q
��z�0��p��%��$��_���J��S��R��_��^��]A�!�%�l�+�r�3W�-�0�q�s>�z	���y���R��)��'�&�I�A[�"�.

�l�g
�j�{�T�[0�*�3�L�r�O
�*�%�T�u�8��(�%���.�-�
�%���s� �X�{���(�)�x
�_�9���2�3��X�p�#�0�!�EA�$��S(6!.
"
$Q�h�V� ��x��W�!��v��?��>������A�w�S��0��AQ	�}�)�(�)��
���v��
��M�fA����8�b�@�=�j�G�l�y�+��d�x�E�z�%
�I�2�Q�1�kS
�@��|�fA���@�)	���2��,�&�U�#
�^�]�x�o
	
A����6"&2

Q	�0�9��T���2�w��H��G��F��QA�D�R���T�	A�;�]�5�r�'�"�s�l�w��F�Y�Z�Q2�l�I�"�b��1�(�[A�e��IQ
���}�������u��,���<����A�ґ4��(A�ֺW�;��m��)�A�́�
Q
��~�p��p����
��!�� ������'A�H�A��j�n�/�'�-��E�{�"�g�X�z��$�K�I�	�u��Y�X[�?�.�\�?A
���6
Q
�i�,������w�v��t���?��n��L��}A�y�S�a�i�)A
�|�p����6���A����{
Q���=����
�f��$��K
��O��p��UA���\�O_A���G�v�S�N�A����Q���i��l��G��F��o��n��]��\��Q��P��Q�+A��`�3�.A���/ �o�^A�݁�+Q����}�0��8����~��g��f��!�� ��_�l�]��L���L��L��A�
�W�T����iQ
������u�5��(��s�|�[��N��kA�f�^�RA�!��O�Z��
a�S�z
���W�P�I�H�[�I�&����j�[	��"�]��{�:A�'��S/H8TQ
�����6��'��&��)�=�#��d�%Q���)��v�}�����"�#�K��d��U�Q���q�V�K��&��V��U�u�,��
�xQ�ė_�H�o��B��K�m�v���#�(����}A�ډt� �m�n��A�՚kSA�֦&�q�t�#�,�x�+Q'�ꁖ1��k�\�P�4�[�Z�m��l�4�X���
�{�m�f��b�2�)�(�?�>�[��@�]��L���;�J�9��(�{A"��L���a�`�Q�R�}�J�Q��A�@��:�6��C�t�!	�@��6���;�`�h��J��\A�G�v�"�#�
�Q��~		�L��g�g��*�+�k�f�i�`A���3l|Q���"���8��3�� �����N���B��=�k�|�l��:��!�m�H�?9�+�*��6��+�q����QA���2�/�����AQ���3��J��u��t������O�t�i��;����%�VA	���3�I�<�j��@�z-A���5A����*Q	��f����-�P��Z��;��:���A��=Q�����Z�-����g�{�J�s�'��E�|�k�\�]���N��y�\Q�4�L��B�a�r��@�>�q�p�O�+�x�n��A�G�6�?�S�W�f�m�w�mQ�S�;�1�(�1��>��W�F�a�e�b
�E��>����*����x��O�0A �k��^�#��W�	�O/�\��R�?��	�p�q�O�|���5�|�S�$�X�?��v��J�O�BA"�m�u
�D�J��w��9/ 1,�F�EA
�l��FLf

Q	�)��L��
��������
��A�D�6�;�8�A�4�0�i���p�M	�V�WA�C��HQ�l�&�w��n��7��<������9��8��AA�z���T��G�U����Q���J��L��'��&��m�a�j��`��M��L��S�l
�oA���&�G�h�-�rA7����g�j�E�H�/	�k�V�c�>�=�N�V��"�k�n�M�P��~�%�8��p�
=�@�E�@�W�^�K�i�Ps�4�?�"�6�sY��bA
����z5H)<
/:Q�_�F��t���(��X��e��d��S�L�y�|��~��G�5�4�K��[A	�t�`����#�"���JQ
��� ���F�R�-��7�F��&Q������L���p���Z�:�o�n�W�o��d��}�A����&��:Q	������}��|��g�%�Q�@�LA���H��A�d�.�K���
�j�v��w�v�h�u�^���oA���=A�Á�
Q��I��>��O��J��I�'�l�����F��T��?A	���Y�W�N�z�1�u�C��;A��EA���{Q�	�J*�P��\��Y��X��!�
�6���v����K�)����{�K�9��|��A�M�.�@�<��"A�&�J�!� �]�=�/�.�W��b�q��@�D�k�T�A�2�;�.�+�1�NA�7��I
	Q�[�Z�"��X��7��6��!�� ���U��X��eA�h�W�6��*Q�r���@�����.��/�$����1��0�������I�<��+�hA���q�%����M�KQ���c��)�� ��	����5��4��������������A���l�%�a�cQ���{����'��&��!�� �	��"��%�3�4��"��!�m�h�A!���u�9�:�W�L�|�)�,��n��a�	�p�[�Z�A�L�f�
���!�"A����W�X�3�h�>��-A�Ɓ�


Q
�-������J�O�H��>��]��\��[A�9�	�-��Z�w�a���>�'�=�VA�:�
�SA�G��Y	Q�i����T��3��2���J�u�|��J��K�O�<��N�^A��SQ	�����T��i��h��U��T��7�XA���V�ZA���A
���	��

��z�Q�#Q����x��z��9��8��=��<������A�+��5�x�A�_��"��3A�܍%��
AD���x�_�Z�m�w�j��	�c�t	�_�g��:��V�#�	�j&	�7�2#�5�<�'�*��l�u�3�&��w�h�A�f���!�(�o�w�\�x	�9�D�g�@A�ց�

&*"0


Q���5��V��)�0��v��]"+����EA'���M�Q�G��7�u	�x�_�`�
�m�k�.�-^��5��b�
�Lf
�{�h	�y�O
�$�%�%�.��J���]�.��bA)���%
05
:C,;6
	

E0	�>�m$�%�.	 	PIA����+"

	
, Q���cy��B��7R��b��_��^������QA����S�|A>��� �	��7�2�[�nD�y�(�D��e�

	�+ ��x	�9�H1��>�i�`�F�;�f�1�*'�'�P�Q�P�U�V�M��O�,�%�4�8�}
�A����O("*

Q	����a��)�m�~��������EA�����|�S��dQ
����a�Q�z�9�&�wA���+�r�s�r�q�0Q
���K�D�}�G�5�t���c��bA�ܜ^�m�2�E�a��Y�d�
�|�x�#�]�R�u��b�6�U��}�L��#�|A�ص>

A�ρ�e
 Q
� ��l��A�6�C��.���L��s�lA�-�f��B�~�Z�g�z��s�y�;
�B�b	�'d�0�
A�,�lBM�>���Y�XA
�3��n		
Q���E��~��[�V����	�}���������vA���i�MKg��(�x�o�v�1�.	�'�D�3�4�y�&�P�I�&A���y!HA����
Q
��J�t����h��{�4�G�,�r���cQ���K�n��k��l�!� ��^��q�R�4A���'�{�F!�I�B�����u�~�<�.�YA��A���=Q�;�E�(��r��M�,����s��r��;��L3�I�h��r����A�P�K�"�E�<Q
�[�F��2����������W�
��EA�g�_�'��=����Q	�p������8�9�_�h�B�A�}�Q���L�	��X��%��$��;���8��'��/�RQ	���o�D��P��-�W�P��0���A���b�h��^��Q���f�p��Z���p�� ��A��@��u�9��,��WA6���4�<�p�M���y�;�*��b�x�{�M�U�o�`�l�!�Z�'�k�n�A�z���}�4�D�m
�7�:�N�&�,�1	�>��g�6�y
���o8�V��
���,�A4�ɶ�>�0%�g�
�1���c�X�]�@�=���B�C�>�9
�%�T
��w	�2�/�
��hg�%�!�{�HA����$$$"

Q���I��2��5��>��t��5�	�|�S��������/��.A��A�o�7�X�D��2�#���j��O�0�%�(�(���=�qA
��IA�%��t (Q�o�>�*�!��F��3�9�s�-��p��g��`��i���e�"�@�w�'?�Z���7��@Q����*��	�9�m��,��g�\����-�	�D��nA���~�#�[�{�|&�}	��O�F0�mT�A7����.A�U�/�.�/�r�L�D�I��j�a�X��/�+�|��5��A�9��v��g�&�X��P�+��d�S�b�g������v�w��!A����.

1DQ�m�V�V��^�/�Z��P���S�F�
A�z������~����i�b�WQ���(�C�#�d�;��>���[��^������eA�����~��S�A
���+��(���}�F�%��B�{�n�A����A 4Q���	��4��]��\��9��8��=��<��i�8A��A ��?�N
�!�n��
�7�p@�I�(�s��_�V��?�0�c�(�w��U��A���}+F
Q
�j���t��g�(�I�(�5�r�����R�&�QA�y��r�y�w�p�_�ZQ�����f��u�d�k�_�H����
��4A���2�$�o�r�y�x�S�(�Y�:�R�enA���)�-A����OQ
���a�������,�\����!A�<�FA�߻l��
	�{�.�1�
��R�S�`�a�.�
�j	�[�D�=�$A	��q
Q	�A�
��B�w�r��,��+A�O�I�A3�M�e�~��z�W�_�(	
�]�V�_�x
�W�R	��$��38A
�P��;
(F(,Q�1�26��(��#��"��i��h��o��n��+A	�|�H�]�
�D�9�	
�z�}�AR�>�T���W�,Aja
�Q
$3B�(�F�I�/�
�j�)�Y��	��4
�U�^�{��i�`�C�B�E�&	
�%��6�	�g�j�i�8.
56'&��EA�S��~,		;4 
Cj0'>"Q��!����Q�u�@�9�8�q��t���T��*Q�-�e��*��M��L���%��$��u�tA�=�f�p�p�[�H�7�O�"|�k�Z�m��0�75�U�n�X�5�2.��T� 
�w��A�p�
X�ZF�	�Z��YAU�>�C	��	�P*�G�_�N�=�J���Z�
�<�Q�@�E�X�a�,�s�N�S�6>C�)��E�D	�G�t�I	
�^�H�N�#���E��;�G�H�U��v�#��3�>A%�@��@",($
Ht  C�@܇�� B��<T�� ��9G��7��n2A疄W0�>�0�m�$�g��90��
���R�aYy���f��w��v��-A����SA�l��Q�?��D��a, ^��
��%�6F�Z�a��Hmo�#�,�z$�:��;���p	�f
�R�>dK�b�4`s}q{)R�d
U�7$�<�v	�p��c�=�P�}�@�P�X�D�T�F�R�J�H�L�<�X�
��(a���Oadx�a��a�$�a���@aR?�aR����,��vG�E��2D�6C�Z�
�g�����#�'�*�-'�<�>�E�J�L�N�Pa�>TR�E�
�+(� �(G�7Q�IE�\
�o�e�m��
�,�xZ���1h�D�7�#�H�Lq�\�N�v�m�j�5�
�j�(�U�v,�x�=�f�(��?�h�V�Z�]|�^M�(�
�1��V�>�r�q�	�6�G7�rA���C&�H�	�g�R�\G�eM�sv�&�+�-�8��|���	�f�!�=��U�R�V�Z�W�\�`�f�j�l�p�s�v�x�{��	�
�`i�Gt�Q�?�z"�
J�!���&�+�4�<��
�/�K�;�>�`�,�2�4��%�!�*H�@�B��c�f�j�n
�s�%�O�T�{�z-�#�	��U���	��
�#�'�,
�0�7�:�@�D�F�K�M�R�W�Y�[�^�e
�i�m�p�t�x�}����
����	�
����!�z�S�JW�N��d�h�j�l�n*�7�;��#�,�.�a�;�@�F�J�M�O'�]�8�������%�'�/�3�7�>�A�D�J�7�A�D�G�K
�Q	�V�}4�y��V@�F�oa���ya���\�)a}��:�a
vF�/%�4�/��2�s����O�S�hH�A�f�F��La���;a�;��3�$	�)�<�L�D�h#�G
�k�z&�}$��P��C�8�L!��q�$�)^�&���2��/��V��X���aq��f�j�Y�_�e�l�u�w�{�}	�������"�&�+	�.	�3�6�;�A�H�J�O
�S�U�^�c�e�n�r�t�v�y�~��
��
���#�%�)�,
�4�7�;�@�D�I�O�S�V

�Z�g�k�n�q�v�{��	�������(�2�4�9�;�=�@�B�E�K�N�Q�T�Z�]�b�f�n�t�v�}������� �%�'�-�/�1�3�5�9�=�B�G	�J�M�O�W�a�c�e�q�t�v�x�|�~�	��	�
�������"�%�(�1�5�8	�@�D	�G�S�X�]�b
�e�p�s�u�x�}���	�����
� 
�$�+
�0�4�7�:�A�C
�I�L�Q�U�Y�]�`�d�k�s	�x�}	���
����!�&�-�0�9�<�A�G�J�L�O�X�\�a�c�i�n�p
�u�x����	�����
� �"�&�(�+�.�5�9�@�F�H�K�S�U�X�^�c�e�j�m�p�s�x	�|���
�
����"�'�+�.�7�:�?�C�G�J�L�O�S�U�W�[�b�d�f�i�k�p�t�w�z�|�	���������#�&�(�+�0�4�9�@�H�L�O�X�_�a�d�f�i�k�t�x�z�|�����
�����
��+�3�5�:�>�C�K�N�U�X�_�f�n�r�v�~�������� �"�$�&�)�+�1�5�8�:�A�C�G�J�M�P�S�Y�[�`�f�i
�n�p�t�w�z�~�����
�����%�'�*�7�<�>�C�G�J�L�O�R�[�`�c�g	�i�s�w�~�����
��#
�*�5�7�?�A�E�G�M�O�V�Y�]�_�b�g�j�o�s�{�����
�$�/�5�7�9�>�A�G�I�O�R�V�Y�\�_�c�g�u�z�}�
��
��������"�%�+�0�7�<�A�D�F	�I�M�S�Y�_�a�g�m�s�z�~�����
������!�(�*�/�7�?�A�D�K
�N�Z�\�`�m�o����
��#�)�.�5�7�:�<�>�G�J�L
�O�V�Z�]�_�a�d�j�r�t�w�~��	����� 	�$�.�4�6�:�=�C�J�L�O�S
�Z�\�_�d	�j�n�q�w�~���	��
���� �&�(�0�2�7�9�<�?�B�D�F�H�K�M�P�R�U�X�Z�]�`�b�e�h�m�r�u�}�����
��
�����#�(�*�-�3�5�=�@�E�H
�M�S�U�Y�^�a�f�j�l�o�r�z�|���������	�"�,�3�7�<�>�D�H�K�N�T�W�[�`�i�l�o�x�}�	�������"�$�+�.�0�2�4�7�:�>�F�J�L�N�R�U�Z�a�c�f�k�p�t�x�z�~����
������&�+�-�0�2�7�<�@�L�N
�T�X�Z�^�a�d�i�n�p�v�{�}���������"�%�,�/�3�;�?�B�E�M�V�X�\�_�k�q�t	�{����	�
�����$�*�,�1�3�5�8
�>�A�C�G	�M�V�Y�\
�d�g
�m�t�v�z����
�������$�'�*�.�3�9�;�A�C�J�L�N�P�T�X�]�a�c�f�j�l�n�r�v�{���������'�*�,�1�3�5�9�;�>�@�E�G�K�P�Y�[�_�a�d�g�k�m�s�v�x�}���	�
���	�(�2�8�:�=�?�B�K�S�U�Z�]�d�k�n	�v��
��� �#�(�*�-�2	�5�@�H�L�O�S�U�X�_�b�f�j�m�t����
�����!�)�+�-�0�2�5�8�:�>�@
�F�I�O�Q	�S�_�a�c�e�h�l�u�x�~�������!�)�,�2�7�<�?�A�C�H�J�L�P�X�\
�^�i�p�r�u�x�|����	�
������%�4�;�D�J�N�P�T�X�]�a�c�f�n	�p�z�����
�
�	���$�)
�0�4�8�:�<�>�C�E�H�J�N�P�V
�Z�_�g�j�n�q�u�z���
������ �"�%�-�/�2	�6�8�:�<�A�H�J�O�S�W�Z�\�_�c�f�m�o�q�u�x�}����
�
����!�#�%�(�,�.�0�5�8�;�>�F�H�J�L�N�T�W�Z�^�c�g�j�n�s�v�{�����������%�+�-
�/�:
�<�J�N�P�U�[�_�a�d�f	�m�p�u
�|�~����
�������#�)�/�2�8�<�@�C�E�H�K�P�X�\�^�b�d�l�p�s�u�x�|����	��������%�'�2�7�:�>�A	�F�H�K�S�U�X�\�`�g�j�m�o�s�w�y�}���
�
����	�'�+�-�2�6�?�B�I�M�P�S�U�^�c�g�m�q�y
�}�����"�%�(�-�/�5�:�>�A�C�H�O�R�X�[�_�b�f�k�o�r�t�v�z������	�	�%�1�4�7�>�A�E
�M�Q�U�W�Y�\�^�`�g�i�l�t�v�z���	�����"	�$�&�*�0�3�8�;�=�?�C�E�I�L�N�U�X�\�d�n�t�w�{�}����
������ �%�(�/�4�:�=
�A�M�Q�S�W�`�f�m�o�t�w��������%�*
�-�9�=�?�E�N�P�\�`�c
�m�t�y������� �#�'�)�,�0�4�8�:�=�F�K�N�P�S�Z	�^�d�f�h	�l�n
�u�w
�|����������� �"	�&�)�+�3�7�<�A�C�H�J�L�O�Q�S�\�e�h�l�p�r�v�z�|
��	���
��"�'�+�/�6�:�=�D�L�N�R�T�`�c�f�l�n�s�y	�~���	���� �)�,�/�3�9�;�C�H�J�L�O
�S�W�^�`�e�k�o�v	�z������
������������!��$��'��+��.��1��9��<��?��A��D��F��H��J��M��P��X��\��a��e��k��n��r��t��{����	��	����������#��%��)	��+
��5��@��B��G��O��Q��U��X��Z��\��b��j��q��t��w��y��~��������	��
����
�� ��%��*��,��1��:��A��E��I
��P��T��W��Y��]
��c��g��i��l��p	��v��������	����������	�� ��$��&��*��-��3��6��=��E��L��O��S��Y��[��`��c��g��m��o��s��w��z��}��������	���������� ��"��$��'��*��-��3��9��=��C��L��N��P��\��a
��i��w��y��|����
�������� ��#��&��*��-��/��4��6��8	��>��F��H��N
��Y��_��a
��e��h��l��o��r��x��}��������������!��)��,��1��4��=��@��B
��G��K��O��Q��S��Y��[��`��c��e��j��m��q��y��}����
��
������	����'��+��.��1��8��:��?��A��D��L��Q��S��W~��b��j��r��v��{����������
��������"��&��,��1��5��8��>��B��D��J��M��O��U��[��_��c
��g��i��m
��q��u��y��{��}��	��	��������
��"��(��.��4��6
��:��?	��G��I��Q��S��U��W��Y��^��`��m��p��t��x������
����������#��*��,��.��1��9
��@��B��F��O��S��U��Z��_��h��l	��t��|��~������
���������� ��#��%��(��.��2��6��:��>��@��B��G
��K��N��R��T��X
��]��a��d	��f��p��w��z��|����������������!��$��'��,��0��6��8��<��?��A��G��K��N��Q��U��Z��]��b��e��k��n��t��w��{��~��������
��������'��.��4��8��;��=��A��N��Q
��V��X��]��`��b��i��l
��r��~��
��������������'��+��.��1��6��:��>��A��C��H��K��M	��U��X��Z��`��b��h��k��r��x���������������� ��#��'��*��/��3��6��9��<��A	��D��P��R��_��a��g��i��m��q��s
��z������
������������!��&��(��*��.��0��4��8��<��?��D��F��H
��Q��T��W��[��]��_��a��d��f��m��p��w��}��������������������$��&��+��.��2��7��9��?��A��C��J��N��Q��W��Y��\��_��b��f��i��p��t��x������������������"��(��-	��2��4��7��;��>��C��I��P��S��X��\��`��b��d��g��j��l��o��q��s��{��~��������	���������� 	��'��)��.��0
��2��@��F��J��N��P��R��Z��_��a��e��h��k��n��v��z��~����������
����������!��&��-
��4��8��<��@��D��F��I��L��O��R��T��Y��a��c��e��g��i��k
��p��s��u��w��|��~����������������������%��(��,��1��5��8��@��B��F
��L��O��T��V��[��`	��d��n��r��z������	������������!��%���4��=��A��C��F��J��N��T��[��b��f��m��o��s��z��}��������������%��+��/	��4��96��>��C��G��I��K��T��V��Z��_��a��d��i��l��p��x��}��������������������$��(��*��-��5��7	��?��A��D��F��I��K��O��Q��T��]��_��a��g��k��n��u��|��~����������������!��'��*��.��4��:��A��G��O��T��Y��[��_��b��f��h��j��m��o��r	��wA��	��
������������$��'��-��/��1��4��6��=��?��E��G��J��M��P��R��W��]��_��b��j��m��v��y��{��������	��������������"��$��)��-��/��9��;��?��B��E
��M��O��W
��[��_��a��c��e��k��n	��v��x��{��~����	��
��������#
��&��3��8��:��=��A��C��K��P��W��Y��[��]��a��c��f��i��m��o��q��t��w��|������	������������$��&��-��4��7��9	��;��F��K��M��R��U��Y��]��e��g��i��m��q��s��u��w��z��������
����������!��%��(��+��/��1��3��7��>��G��M��P��R��X��[��`��c��g	��l��o��}������
������������$��(��+��.��2��8
��>��I��L��R��U��Z��\N��f���w��y������	������������"��%��)��,��/��5��7��9;��C��J��M��O��W��Y��\��^
��c��f	��j��n��x		��}����������������"��&��*��,��0��3��6��9��<��>��D��L��N��P��S��X��[��_��b��k��m��o��r��t��v������
������ ��"��$��*��-��5��:��<��?��E��J��M��U��[��h��k��n��r��u��{��}���������������� ��#��&:��/��6��8��=��F��K��N��P��S	��Y��\��d��h��l��n��r��t��v��x��~������
���������� ��&��*
��/��:��<��?��B
��N��]	��c��m��p��~����������#��(��.��2��5��;��=��@��C#��K��M��Q��W��]��`��b��g
��k��r��v��{����	������������!��&��+��3	��8��<��>��B��V��Z��\	��f��q��x
��z��������'��,��0��3��7��:��C��K��N��Q��]��`��b��e��l��n��t��v������	������ ��"��'$��0��5��7��9��;��=��?��B��D��K��T
��[��f��m��p��s	��y����
�������� ��)��+��-��B��K	��P��Z��]��b��f��l
��q��	����
�������� ��,��4!��>��C
��I��N��R��V��X��\��^��d��g��m��r��t��y��}���������������������� 	��$��0��5��<��E��G��Q��V��[��_��c��e��h��j��n��s��w��~����������
��5��/�K!O���(����
�������	�+�5
�9�Ga.�������"	��$��.��0��<��>��A��E��G
��J(��X�?��{��~�o��s���HF��*��H���sa5���a5�`��
��~
���d��a7���|a8���|a9��|T��_��i
��I��Wr��wa:t���ja;M��v�0��Da<m��ua<op��ca<���T���sa>���sa?���sa@U��s�(��IaA^��r���QaB���qaC6��qs��(aC���*�� !��K�U��maD�(��C���laFK��l��8���LaG"��j��
Q����dq��taH���fa	IF��f1��-��_��a��f��z��}G�����[aK���[aLv��[aLx&��R[��y��U���haM���|��J���a����a����a����a����a��6��PK
!<1#�X��2chrome/pdfjs/content/web/cmaps/UniGB-UTF16-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
UniGB-UTF16-HA �V�Z1�6��m�1
�:�3�2�
	��a0�H�R�F�P��p�D�@PK
!<1T縪���2chrome/pdfjs/content/web/cmaps/UniGB-UTF32-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�#C^��J�vG98�<
&
�Q�Z1&#bW	e�-�7�vO@�mj�T�	z
!�<�%�e{tg

9
7	IA"D�
J59C��!�/�&�6�X�Y�O�H�}�r�:	.B�)}�IxC���R��S��{

S�V/�B�@��r��-�
�;�]��a��l�I�Z��.��-:�*�+��&�
��C�)�V�C�9�
� �5�;�p��z�	���g�`�}�<�1�^� �u��/�6�(��a�6�i�#�U��X��K�p�D�m�;��M�$�{��I�$������|�j���=�z�!�j�f�%�:��H�~��]�N��Z�h�M��M��<��;�@�R�c�:��j����M��)���k�b�r�7��&�%�H�C�9��c��>�|�}��q��z��e�K�q�Z��$��
�}�H�9�S�8t�T�m�l�5�(�_�U��`��i�(��F��}�3�f���v��U��4��
���D�9����UF�B�8��
�"�F�k�w�5�d�;�V�Q� �C��j��{�q����g�n�X�5�*��`��C0g��~�#
�Z��*���<��Q��~�4q!�}
"	�*�5�E��n�5C.0?��UT"`2�L�)+�*o�}U`��Z�q�u��Wt
+�x�u�AR�92
�?*�
CI���eSN�#��%^�L�P�-�������1�m��
��c��.�K�$��TSN"�i��p�9�b�T�����g�j�h�SN0�~��P��m��l�
��o�n�#�)�f�x�h��3�8SNC�.��{�z��]�d��	�	�<�9�S�z�`�5��`�\��
����	�PCN\�u��8�N�9l�)�j�;A�
�7CN��YCNr�SN���~���t�y�9�2�1�w�"���x��/CN��^�t�S�ySN��q�}��9�A�(�!�a��M�*�����x�CN��T�`�6�3�E�pSN��a�1�I�xI�k�H�/��&��?�^�w�3S	Nє�Z��!����x�y�S	Nݥn�������k�r�FC	N��qG�)7�$�3�/�CN�#�+�,�3�$�p���/�6�M�!�^�`�_�xCN��5S
O�Z�H�#��D�Q�P��N�]�\CO*�@�\SO/�u�F��|�C�B��K��J�W�V�m�l�E�D�E�xCOC�I{SOM�J�9��$�3�d�g�f��O��N�or�W�Z�M��>	�_COp�fCOc������W�:�+�j�_COf�pSO��=�������p��7�6�C�B���CO��D��Y�[�"���
�5S
O��i�P�{�n�M�:���"�!� �7�)CO��S	�����8�ECOťHRMCO��S
OכL�e��,�Q�P�O��r��N�K�D�SCO�r���Z�)CO�E�=�NCO��6�o�mSP	�b�P�W��s��X�s�r��K��f�YCP��e�Z�t�w�=�6��S
P%�Y��|���2�C+P:�O�H�)�v�!�"�+�$���E�Z	�G�P�t
�#��7�l�I�j�P�}�|��-�0�o�^��1�6���7�-�CPI�*�_�6 ����^�1�h�?�+_�V�7�<�s;�$�S�L�%CP;�e
;X
�Q�`"#,SQ��2�7�6�1�0��n�)�8�CQ�@�sC
Q�V
	�1
�t�=�|	CQ�r�e�tSQ?�8�@���O��1�\�8�A�u��4�}�|�aCQQ��D��a�>�<�dCQR�eCQS�SQh��<���'�Z�z�f�f��9��>��u�H�k�B�k��>����6CQ��0��]��a�`�&��|�o�C	Q��z�;
��n�+�*�S�RCQ��;S	Q���M�Z�
�4��F�
�h�SCQ��r�>�OSQė�$�s�r�~�#�"���:�:�
��[��Z��}CQ۔&�r��k�9xSQ��T����e�5�p�&�����+�o���G�&�`��i����!x�n�CCR�s�2�~�#��{�o�)�*H�l�K�3;�8�MCR�nCR	�sS	R(��i�:��W��V�m�Q��<��QCR3�S	R6�E�]��@�9���b��}��|CRA�&���L�\�M�	��x��X���F�'�wx
�\�y�z�9�:C
RD�H�K�&�<�c
�j�~�L�y��aIC
RK�	�"




S	R��o��i�)���r��cCR��n����p�%�L
�.�g�Y�j��V�z�%CR��>CR��QSRսM�u���C�T�l�u�t�u�I�ICR�W�x�1�Y��b��	���0�n�m�3�E�U�B��a�!�n�V�:�=�<
��`�CR��2�`�%�F4��O�NCR��d#.		
S
S>���N�U�.�s�r���S�~��wS
SN�?�p�T���NK�f��(�,SSZ�p��j��Q��P��3�V�;�Q�:�n�g��=�O�
�/�.�7S
Sn�j�o�$�)�t��
�N�/�j��%�H�Z�9CS�a�+�H�:�U�~N�d�1�T�QCS��ACS��6S
S��~��+�f�B�q�`���!�.��W�0CS���4�9��Y�	�J��j�$�-�%�x��l�M�Z�SCS��y�S�b�QCS��O
SSߧ�m����w��h�D��b�U��3�0�O��f�H�~�[�P�b�I���l�:�0�W�]�TST�m�'�&��k�p��x�[�Z��U�,���4�)�;�-�h�&�V�@��~���2�US	T�,�����(��)�<�Z�WST&��u��6����/�X�_��'�N�)�J�\��3S	T8��'���S�_��0�!� �7CTF�w��b��&�n�{�^�Y�>
�-��@��s�*�w�$C
TC�



 
CTG�
ST��G���J��_�L�O��^���;�q�8C	T��E��R�/��-�qS
T��g�s�V�6�����3�2��G�CT��9ST��%��u��1�i�F��F��G��F��E�Z�k�C'T̪'��S�|-�U�Z�M�V�/�o�(�t�?�E�j�y�r�.�p�o�,�:�G�	�I�S���A�@	�C�PCT��G"�HC
T��C"S
U*�<�j��u��t���*�}��H�QCU7�GSU<�F�j�e�r�p�w�v��C��:�uCUI�@�)�0
�S�4�\�%�>�}�t��Q	�,CUO��'
�LCUT�

S
U{�Z�G�L����Y�B���%CU���t�a{�N#4�]�\��A�,�#CU��[�iCU��%
SU��R�2�]�x�y�x�)�(�U�T�E�DCUĪj�}�j)�g�CU�X�N�#CU��ESU��!�X�_��J�i�h�m�l�3�t	�R�C��3CU�k#�1�b	�k�j�u�z�
�?�<%,CU��r��"���
��WCU��W




SV,�	�r�S�0�g�A��>�Y�N��
��S�l�[CV?�CVL�	��&
CV@�{��S
V`�p�@����V�>�{��i�:�YSVo��,��P�D�E�D��9��8�}�,�NCV��v��V��
�FCV��
$CV��n�o�z�`�Q�I����J�E���	
�e���<��o�mSVژq��>��O�|�g�6�?�F��CV��}� ZCV��I�{�>�W�VCV��aSW�s�L�&�s���4�B�b�AC!W��W�m�@��Q��`�K�P�E�P�r��!��?��k��k*�E�b�N�!�6��@�kCW�F�CW�v	SWd����'�&�S
�1�2�Z�S�R��UCWw�M
�#�4�b
��OCW��CWs�:SW��CC�#�8����M�B��I�H����~��C*W��>	���R�Y�$�/�$�o�-��W�'��{�
�v�{�=��X��>����(C	W��*�?
�
�>�S�tCW��K.

SXJ�c�*��f���
�A�6��C,XX��}��/�.mr�q�^��1�O�:�B�G��G�u��C�B�c�J�t�e�V�=�=�K�&�0�g�����Q��Z�E�@�YCXW�
N��e�0�)�^�'2�>�y��
�M�<�/�P�X�y�
�K�\CXc�=(.


SY"��t�C�F�������_��^�/�)�E����CY4�h�D�5�]�G��I��{�)�M%��f�S�_��a�N��M��
�B{�'�*�qCY>�|*�<�{��QCY;�E
	
S
Y��u�g�f��z�=�+�*��:��CjY��w�]�h�N�(��	�o�U�<�{��s�h��U�B�S�?�1�F�A�E��;�e���,�s�`��T�sA��{�n�O�%��P�E�r�H�~�%�2	�_�z�d
�1�2	�e	�L��Y�^�;��$
��
��� �u�.�B
�_�^�W�B�G�FCY��-�#�]$�c�T9�'��-�0�?��0CY��v!&$
"~�F*0
(S[P�0��t�u�)�\�>���t��w��}���AC
[b�9��-�2�K�o�8�T�?�N��I�\C[k�Z�C[r�<S[}�X��T�Y�S���D��'�z�E�n�e�I�e�����.C[��!S[��1�4�9�8�-��d�}�r�`��x�����<�%�V�;��7C[��I�	�(�{�d�O�I�d�}�7�f�7��A�q�g� �E�~C[��3C[��X

S[ݘ[��?��x��
�t��u�M�:��'� � �=�_��w�v�uC[��j�P�F�B�y�$�cC[��
�'C[��yS\���B���B��3�~�4�.�MC��(��_S
\�:�n�;��\��C��B��y��x�'�YC	\"�6�(�=��(�G��y�xS\7�\�3�~��B�E�i�`�Y�!��;C\E��C\H�y��Z�7�c�L�$C\P�hS\^���:��q��F�K�v�y��z�mCb\n�t�m��R��7�e��>��t�5�<�3�2��|���q�d�{�l�e�l�y�1�(���d�'�
�T
�:��v�O��@�;�^�\�&��G�2	


�_
�H�U�`��	�J�$��s�
�<�c�R�e�T�7�K�C\l�>4�QR�d�%�x�4	�k�`#0�A�,
�;��'�V�x�]C\m�.&: 3J
$" 4
S]��w�s�����V�I����S�)��z��C^���g�jS
^� �=���~��)�H�{�Y��C^&�{�H�	�E�N�;�ZV��!�����mC^%�C5��!	�:�|�m�%�$C^,�Y	S^r�D�.���j��5��4��O�j��w�F'��o�hC^���v��?�[�p�zS^��=�Q�8�6��?�6�'�v�S����;�u��.��O��N��{�Q�C"^��5�]���F�K�#�6�0
��	��O��L	�Q�9�S�5�f�%�I�J�U�5�]��]�jC^��`3�L��j�qt�&�dC^��
4B	S
_�T���;�:��#��"��)��(��s�C	_��z�P��F���X�iS	_-�p�j��#��"��/��.�g�f�iC_9��V��`�w��^��
S_L��l�e�d�3�2��u�a��T��5��,C_i�{��UC_]�T�2�+�u�\�A�j���C_c�S
_��*�Z��b��_�K��(�e�!� �;�P�hC+_��7��n��F�<���'�&�s�r����H�d��R�.��T�:��)���!��.�e�n�=�B�+�*�E�V�|C_��E�I�b��C_��S
_����
��O�:�!��l�
C`�8��X�1�y�`�p�_�B�t�8��@�P�#�U�3�z��v�W�&C`
�Y			
�t�g�l�c�bC`�\%(S
`g��w�?�T�a�$�	����}�cC
`s�_�_��Z�K��G�
�"�R�5�h�SC
`z�_
	C`w�S`����:���k��j��+��0���m�f��{C`��l��
��/��1�dS
`جo�4�3�2��o�2�:��)�
�`S`�L��w�0��<��_��'�h�T�#�Z�J�G�<�4��cC`��"�d��-�A
�2�q�� �Z�a��*%�}�y�i��$��W�B�5��*�k�:�C9`��9��8�;	�d�(�
�t�q�L�	�
�>��^�[�I�y�R��\��g�6�@���^�+�B�y�0�`��f�[��)�:���6�7�5	��]�Q	�oCa�] #6
&%.Sa���v�[��0���[�Z�=�}�Sb���y��8���z��E�?�d�m�d�Y���A��/�d�9��J���|S	b�|������MSb*�)��F����S��R�9�8�A�YjCb7�<�A�"�G�j�V��]�`�'�1�D�p�$�yDCb=�tCbB�Sbf�!�"��r��3�L�5�l�g�g�P�C	bs�m�J�N��{�r�;��	Cb~�sCb}�6Sb��]�G�����;�e� �j����G�jCb��f�*��	�Q�2 �z�U�L�&�.�+�PCb��<Cb��ISb����S�_��>h�i�J�<��U�"�9��h��a��`��)�z�a����A�~��6�F��C	bߖE�w�O�����m�z�k�Sb���L�I����?��>��C�*���O�sCb��S�`�{�C�W�^��q�f�&�Cc�NCc�oSc�>�����I�Q���G�\�Y�Y�C$c(�/��	�D�x��[�
�.� �I�#��T�i�2�@�m��5�r�Z��)�q�:�2��x��f�6�u�D�eCc>�Q)�k��H�k�gCc)� 	Sc���&�9�r�R��%��$�_�W���:��kCc��;����=Sc��z����o�g��T��I��H��I�"��+��6�y�r�wCc��W�hS
c��?�o����+��*��~�m�l�
CGcĩW�A��4�Y���0�$�c�u����^�E�,�U�V�9�R�R�y��a�h�
�b��5�Q���S���y��<�g���q���B�.��b�2��P�:�)� ��d	�O�L�:�p�;�gCc���O�	�K�H�
�C��_�|�i�.
� ��g�1Cc��W

	
		


Sd����*�5��9�~��l��7��6�I�B���-��,Sd��g�u�d�m��z���A�|�I��>�E���Z�Sdʾg�k�t�������~��-��,�Cdשp�B�@�OS	dޛH��\��
�
�����eCe�Cd��
�"��d�E�n�q�H�&�Y�P��O�Cd��	Se���l�A�@�g�f��y��^�e�d�{Ce%�t�+�*��u�
�g|�9�x�y����Ce"�Y���u�$�g�8�HCe-�#	S
eU�g�?�J���=��<������}�Ceb�M��	� �S	ep���j������_�>���y�bCe���7�a�p�aS
e��A�/��h�9�8����W��V������5Ce����>��	S
e��@�s��B������'��&��
�C��:��}�<CPe��Z�J�W�W�:�r�o�B�e�h�M�Z��#�'7�!�6�
�P�M���n��@�7
��
�7���;�)��K�=�N�v+�p�J�g	�<����+�R�K��^�{�,�k	�M�+�a�8�$�f������)�~�.��"�
�!��3� �o�l�1�HC&e��D)�s



	��'
/�< Ce��W�
�*�Z*��"�_�9�x��<�A�1�4	��TR
�ACe��s	

	
 "

A^	
Sg&�,�.�G��h��'�o�K�0��,������[Cg4�r��V�6��V�'�{�\�9��'�8��K��q�`�S�nCgH�T

CgA�c
Sgm�s����I�,�<�{�m�B�?�>�7Cg~�>��Z��*�8Cg|�b
Cg}�S
g������e�t�n��/��.��� �pSg��w�b��Z���{�v��G�m��2�w�v��c�zC	g��O�?
��d��r�.DCg��m
	Cg��
Sgذe���W�F�9��F��1��0�Cg�0�^�\�P�o�
�>Cg�g	"
Cg��ASh�E����y���o�r�L�;�!��Z������kC	h�{�7�<�#�2�?��A�Sh<�l�T�Z�T�Y�s�6�,��m��b��]��V��+��*�s�r��5�}��0��Q�<�&S
hb�{�K�L�w�|����h�eCHhv�a
�=�p��
��q�6�=�,����3��@�i�#�j�-� �L
�c�3�{�A
�H�6�@��|�V�
 �h�!�T�+�$��
�!�k���,�
�F!��P�,�9�]�^��#�^��T�$'�s�H�G�r�~�'
�N�M��r��%CKht�!	
 		
 
%+		!

(
	
x�:	C!h��u9�R�#�Fb�i���78� �3�^%�m
��Kp�e�Z���x�G�k�81�!
�*�*��57�N�W�|2#mCh��j68XA'
!4
@
%+"'*!"			Cj��C,hu�	$,F .	


#..H*)6.H
1N3R S	k_�K���75�!��\�[Ckj���
�A�w��^��g�GSk���
�&��c�6�*��%�G�E��.�'C
k��2�q�[�X�W�<�v�q�/�/Ck��\�V�S�N�O�R��
�"�i�9�^Ck��6	
S
kҋ}�b�}���0��?��>��SCk�R��9�(�Ck��@�'Ck��m	Sl�A�K�@��/��k����)�X�
�a��"Cl�rSl���V�E��T�O�@��S��E�J��4���u�F�j��@�C�B��I�vCl8�%��;�2�y��y��L�1�!�X�1�{�j��X�/�"�K�5�|��GClJ�"	ClH�
S	l��\�h�Y����!�����Cl��'�K�D�r�s�u��&��3Sl����j��	����E�K�#�$�E��N��3�e�(��l��/�OCl��
�;��4�3�5�t�)�{�#"��F�5"�t�_�V�1�v�gC	l��;

Cl��NS	l�:��l��h����
��{�fS	l��j��e�g�G�&���X��wC
m����+�3��G�I�@�5��_�+Cm�E



Cm
�t


SmA�@��$��#��"��I�g �h��"��c�w��X���'�,����/��K�JCmY��)�N�M�q�p�5�j�]��h��U�)����M�$�+Cmy�\
Cm[�&0Sm��!��B��	�&�5�4�k��H��]�E�� ��o�-�^���Cm��j�{�&�;L�*�'�6�f�i�d�q�n�?�	�X�|
�)�^�J�I�5�DCm�^Cm��bSm�'����_��^�[�:�C��`��y�:�)�@C2n�b��z�o�Z�U�X�1�6�m�T�%��g�l��t�I�2�A�k�:�;�6��.�X��n�q�:���-�+�g�/�r�]������3��OCn&��s�x�r�e&��Cn�&,DS	n��R��.��e��v��Q��P��k�Cn��J�S�@Cn���8�HCn��oS
nˤ)�&�t�o�{��h��q��p����
��$��sCnڎOSnޣL�T�3�4����������I�LCn�/�:�b�$��s�\�

�w�rC
n�K��]�9�.�S���a�4Cn��	 ,So)��[�$�{��b��?��>��3�@�#C'o6��Y�X�-
�]�B��L�k�j�g��O)�j�S
���d�� ��	��!�]C'o8��T���D
�%
�6�k�z�+�Q�:�O���z�	�5�b���
�J�+�l�Y����z��*�� �!Co7�1
	
"*	H`
S	p�Y�R��9��4�a�`�3Cp#�(�c�d�Q�R�u��~��|�}�^���b�s�{�;��O�f��
�	�Cp'�X*�>�C�g�6"�E�w�4��M�c�Cp1�R,$S	p��S�}�x�o�a��r��#��"��Cp��I��t�o�H��D�m�}�@�d�yCp��R	�Cp��*Sp�K��t��m�z��=����Q�#�(��H��eC"p���]�M�V�
�J�e�$�&	��s�i�j�.�c�l�v�u�8�"Z�?���u�
��p�P�F�#�Z��C6p��z�G,�h
����h	�s�8�'��L�k	
��\�g�(�/�(� �=	�<�_��X�e�}�j�i�f��c��:�#�
�
�'
��FCp��O*3N&*
)60Sr5�#�a��+�p�Z�?����+��CrG�K�5�*�BS
rV�k��8��5��>��K��J��
����m��l��i�?C)rf�E�C�2��#�+��B��l���u�J�5�4�A�A��o�j��'�,���Q�^�<���BCr}�L�f�_�4�C	rh�P#(.S	r��\�_�t�C�q�4C#r��~�SG�
�U�N��|�e��~�s�.��@���U�Z���j
�6��Cr��6�s8�n�B�-�+Cs�(Ssh�R�0�+��C��n��-���Ssu�+��9�7�n�%�B�0�.��7��6�5C/s��I�1�H�a�b�S�@��	�;�>��l�3�l��n	
�a�"�.�/�0��x�}��}�N�@�\
�	��3Cs��@Cs��y.$St%�2��l��k��j��u��t��i��h��m��l��qC	t3��n�1u�J�j�*��{	�DStU�<������������ ��.Ctp�>�P�oCtc�]��w�W	�6�7	
�&�/�	��"�g�`�#�,Ct�#
5RS
t��d�������o��n��o��n��9�5�H��"��9Ct�\�L�M�%�p�{�x���d��(�!�x�yCu�%Ct��x$
Su(�(���~��u�$���`��Q�e�V�I�0��r��+��*��W�S��(��O�mC
uE�N�R��m�'�@�A���}	�s�~
�\Cu>��D�"���!�d�e�W�V��Cu?�"
%.Su����,��
���l��m��l��m�'����c�(S	u��d��n��U�`�����o��FC	u��f�i�$�x�y��C�[�9Cu��eCu��cS	u¶n������	�o�@CuͶpS
uҠ ��"��O�s�8����s��Z��CuޗES
u�j���
��	����Q�BCu���{�LCu�x
�b�o
����	Cv�
Sv�2�E�n��!��"��5���l�\��M�5�R��,��#Cv0��C�:�S	v;�3�[��:��+��6�{�5CvL�4CvI�1�A�,�1�vCvH�(Svb��o�>�K�d���	���K�Cvx�H�A�
���~�
�
�M�V�9�@
�Y���O�TOCvo��.��"	��y�h	�i
�,�l�MCv��F 0
Sv͵���?�I���`��g��f��o�A�TCvۚ9S
v��n�=��Z���w�Z�M��f�{C9v�"��l�_��G���i�8�@�1��0
�/�G�t�=�q�(���E�M��5�r�:�� �)���_�^���w�C�f�X��x��/�2���)�lCwO��+9�uCv�	
	

!,9LS	w��R��P��q��p�����$��-�>Cw��\�h�
	�c�Cw��u�r�s���[�-Cw��	
Sw�O�����y��x������A��@��)��!�+�hC#w�F	��y�f�"�,	�)�+�p�~��w�z�?t�x�d�c��8�
�9�l
�O�6��a��C��I�J���3Cw��"
�l�Y�Z	�O��qCw��. *	Sx��[��h��������������=��<�C!x��7�-�Z�=	�8�
���5�D�{�l�Q�	�X8��s��'
�l��-�p�d�
�;	�/�\�~��*
�	C5x��d�=�>�?�^��A
�8��?�,�-�4�9���,�+�(��+�M�v�+�7	

�<�j�)�2�3�&���G�]Cx���5H$:
5NSy��R�"���j��e�/����?�y��6��GCyƍL�O��P��8'�BCyնCyʁ�SSy��O��X��!�����?��>����������/�OCy��&�L�1�0
�;�6�.�y�<�m�Cz�$
	�R
�
�+Cy���s
S
z7�(��h��m��l���Zy�����,CzF�%�t�`�G�x�C	zL�$�w���*�I�~�B�#Czj��@
S
z�,�^�a��@��y�x��~��c��b��cC	z��L�<�D�Y�_�p���iCz��
Cz���\S
z���0����}�x5�6���Cz��G�9
�J��m�t�i�`�a�M	�J�Q�g� ��}�|Cz��%�&�	�
�x�,�1�}��N�u�I
CzƁ�t$	
	
S{$�t�7��.������	�_��l��C{:�V�^�;�*C{1�q��+C{2��5
S{O�A�v�~�5��F��)��(��E��D��#��"��#���2��C{w�w�G�"���I�<�	�G�$�1�@C{`�~�D�K	�t�		�V�CC{a��N	
S	{Ľo��<��	���
�W�V�Q�+C#{ӔO�o�u�\�:�/��3
�6%�t�0�U�D�m�f�v��t��h�4�E��S�m�h�M
�/�x�s�l��EC6{ѹ��%�s���
��N�K���^�8����Z�g�s�&�U�6�i�0�]�I�6�z	�3
	�4�s�N�C{ҁ�&
((>(:
"*S
|�u�J�p�C�B�y�x�E�D��a�f�x��mS	|��H��$��A��@��~��#�	�&C}	��O�����*S
}�#�����'��&��/�9�s�PC} �M<�O�L�M>�rC'}!�s�z�h�
��+R��t�o�j�y�p�v��>�te�D�3�Y�P�=�v�+��-�l�x�Q�T�!C
};��w	"	 S
}��E�b�K�Q��6��C�d��dC/}��_�0
�}�1�(�Y�^
�	�v�?��*�i�d��-3�
�y�n�u��^��X	�9�<	�a�n;H	�{�n�
�)�W��]C}��&C}���J
!2S~1�;�(����L�K���
���5��4��9�C~A�NC!~E�R�M
�R��g�z�3��5��v�'��U�V�A�$��g��J�/�j��m�>�]�$C~S��& "S	~��S�I�H�c�b�;�:�/�-S~��@�X�(�,��v��'�.�[�3�.��R��9�s���O�iC~ʇxC~��g�H��
�;�v�JC~��[
S~͚�@�5�
�I�t��`���n��k�S��2�.�/�O��S~��$��R��}��I�P��L�L��b��1C~�E�b��-�w�V�B�/�]�F�u��{�#�C~��o	C~���\S�O�!�Z�I�f��
�	�
�m��*��=�C(��E�Q�Q�n���r�$
�Q�b�h�m�8�v���E�H��(��N�G��|�J�ICL�b�nC7��`		S
��b��>�?�>��o��n��5��4��C��\�&�r�E�g�p�+�6�u�+�\�A��)���?	�~�R��q�c�l��@C��D�X�Y��	�9�`
�
����r
�s�xC
���!

"
S
��0��h��5��4��5�-�,�%��WC��N�}�~�;�<�G�v�e�}��n�6��l�T�VC�,�BC�!��l	S	�R�\��N��#��"��;��:��_��^��{C�^�
�y�S�o��o��N���+�O�X���/C�}�y��5S
���U��^�?���L��G��F��/�mC���qS���o��t������K��8�O��D��{�I�,�����9US���Z��4��t��u�g�.��*��{�'�� ��wC���yS
���k����
��L�Q�c��q�p��p��aC�̈�J�S	�֗�V�����C�:���C��1�v�2��S���Y�\����/�2�n�K�@��������W��V�������C��+�L�_�r�cS
��#�5�%�.�O�@��"��G��F�������`C�/�'�4�
�C��
	�NC�0��oS�F�F��p��S��R��K�F�.��R��=��<��1��4��W�A�V�@C�`��VS
�e���e��V��a��`��s��r��_���x��Y�lC�y�4�"�}�H��y	�v�B�
��#�
�?
�C�t��:	�#�<�<�y�P�_C����

S
���A�X�� ��%�4�A�S�^�W�.C��a	n�>C�Ơc�v�e�2��5�*�S��9�R�w�n
�n�/�4�mC�ˁ�D
S	���f��}�]��l�E���:C��}��$�_��DC��bC���j	S�(�0����	�x��~�����y�/��:C�5�%�]��
�P�#��U��D�R�!��
�W�V�q�vC�8�2	�L�M�d�/�7�d�)�V�^C�:��{
&
S���x��d��
���&��;�0	����	�C	���j�{�j����o��n��uS�����4��J��%$!�{�?�H�Y�J�e��v��A�@C
���t�+��

��fC���"C����<S�͉�F�k��t����)�4��b��5�:S
�ےN�p����	��%����1"�k�E�C
��t���T�����$��y��x��S
���\��{�W�C�t�A�B$/C��T�m�/C��
./<3C�
��_S�+�(�u�r����%��$��-��,���K���
���T����7C�F�R�]��!��/C�@�%C�H��~

S�]��
��W�%��j��/�q�^�7�8�s�nC�o�0�a�h	��!��b>K8

�Y�L�-�*C���+�Y��jC����&S	����4��m�q�t�i�p��\��]�7C3���^-(��ZH�Q�(
�s�x�Y�j/�7�X�k�
�X�k�`���B�4	��6�}��<?0�C�B
�Y�Z�s�l�i�C	��M�L�!�2�z�}�t�iC�Ɓ�;	!(
Gf	S�i�n��d��c�f���\������!�� ��}��|��;�L�d�o��t��{C���:�^�U�^�g��L
�O�O�C���MC����2
*S
����R��.��i�Q��8��5��#C1�ğ9�
���"��T�g�
��.�!�%�>�}��m	�n�T
�w��/��<
���	
���)C�˾"�
�8
�d	�=�c�.�;�L%'2!	(�9�bH	�C�́�\
 
$S	���
�*��b��o�D��(��?��>��IC%�����
�%�*�5�<�_�/��h�S�T�u�K�@�|�I�J�� �k�B�.��
��o�pC"����M
�^�s�j�n���c�"	��:��`	�-� �1�<	?
�}�2�e
�c�F�f��+�t�mC����W$ 02D*S
�y��,�;���S��C���R�#�(�&���	�B�s��4�@�8�{	�d�:�;�q�<�C'���z					

	
�|��z�{C����S
��z�0��p��%��$��_���J��S��R��_��^��]C�!�%�l�+�r�3W�-�0�q�s>�z	���y���R��)��'�&�I�C[�"�.

�l�g
�j�{�T�[0�*�3�L�r�O
�*�%�T�u�8��(�%���.�-�
�%���s� �X�{���(�)�x
�_�9���2�3��X�p�#�0�!�EC�$��S(6!.
"
$S�h�V� ��x��W�!��v��?��>������C�w�S��0��AS	�}�)�(�)��
���v��
��M�fC����8�b�@�=�j�G�l�y�+��d�x�E�z�%
�I�2�Q�1�kS
�@��|�fC���@�)	���2��,�&�U�#
�^�]�x�o
	
C����6"&2

S	�0�9��T���2�w��H��G��F��QC�D�R���T�	C�;�]�5�r�'�"�s�l�w��F�Y�Z�Q2�l�I�"�b��1�(�[C�e��IS
���}�������u��,���<����C�ґ4��(C�ֺW�;��m��)�C�́�
S
��~�p��p����
��!�� ������'C�H�C��j�n�/�'�-��E�{�"�g�X�z��$�K�I�	�u��Y�X[�?�.�\�?C
���6
S
�i�,������w�v��t���?��n��L��}C�y�S�a�i�)C
�|�p����6���C����{
S���=����
�f��$��K
��O��p��UC���\�O_C���G�v�S�N�C����S���i��l��G��F��o��n��]��\��Q��P��Q�+C��`�3�.C���/ �o�^C�݁�+S����}�0��8����~��g��f��!�� ��_�l�]��L���L��L��C�
�W�T����iS
������u�5��(��s�|�[��N��kC�f�^�RC�!��O�Z��
a�S�z
���W�P�I�H�[�I�&����j�[	��"�]��{�:C�'��S/H8TS
�����6��'��&��)�=�#��d�%S���)��v�}�����"�#�K��d��U�S���q�V�K��&��V��U�u�,��
�xS�ė_�H�o��B��K�m�v���#�(����}C�ډt� �m�n��C�՚kSC�֦&�q�t�#�,�x�+S'�ꁖ1��k�\�P�4�[�Z�m��l�4�X���
�{�m�f��b�2�)�(�?�>�[��@�]��L���;�J�9��(�{C"��L���a�`�Q�R�}�J�Q��A�@��:�6��C�t�!	�@��6���;�`�h��J��\C�G�v�"�#�
�Q��~		�L��g�g��*�+�k�f�i�`C���3l|S���"���8��3�� �����N���B��=�k�|�l��:��!�m�H�?9�+�*��6��+�q����QC���2�/�����AS���3��J��u��t������O�t�i��;����%�VC	���3�I�<�j��@�z-C���5C����*S	��f����-�P��Z��;��:���C��=S�����Z�-����g�{�J�s�'��E�|�k�\�]���N��y�\S�4�L��B�a�r��@�>�q�p�O�+�x�n��C�G�6�?�S�W�f�m�w�mS�S�;�1�(�1��>��W�F�a�e�b
�E��>����*����x��O�0C �k��^�#��W�	�O/�\��R�?��	�p�q�O�|���5�|�S�$�X�?��v��J�O�BC"�m�u
�D�J��w��9/ 1,�F�EC
�l��FLf

S	�)��L��
��������
��C�D�6�;�8�C�4�0�i���p�M	�V�WC�C��HS�l�&�w��n��7��<������9��8��AC�z���T��G�U����S���J��L��'��&��m�a�j��`��M��L��S�l
�oC���&�G�h�-�rC7����g�j�E�H�/	�k�V�c�>�=�N�V��"�k�n�M�P��~�%�8��p�
=�@�E�@�W�^�K�i�Ps�4�?�"�6�sY��bC
����z5H)<
/:S�_�F��t���(��X��e��d��S�L�y�|��~��G�5�4�K��[C	�t�`����#�"���JS
��� ���F�R�-��7�F��&S������L���p���Z�:�o�n�W�o��d��}�C����&��:S	������}��|��g�%�Q�@�LC���H��A�d�.�K���
�j�v��w�v�h�u�^���oC���=C�Á�
S��I��>��O��J��I�'�l�����F��T��?C	���Y�W�N�z�1�u�C��;C��EC���{S�	�J*�P��\��Y��X��!�
�6���v����K�)����{�K�9��|��A�M�.�@�<��"C�&�J�!� �]�=�/�.�W��b�q��@�D�k�T�C�2�;�.�+�1�NC�7��I
	S�[�Z�"��X��7��6��!�� ���U��X��eC�h�W�6��*S�r���@�����.��/�$����1��0�������I�<��+�hC���q�%����M�KS���c��)�� ��	����5��4��������������C���l�%�a�cS���{����'��&��!�� �	��"��%�3�4��"��!�m�h�C!���u�9�:�W�L�|�)�,��n��a�	�p�[�Z�A�L�f�
���!�"C����W�X�3�h�>��-C�Ɓ�


S
�-������J�O�H��>��]��\��[C�9�	�-��Z�w�a���>�'�=�VC�:�
�SC�G��Y	S�i����T��3��2���J�u�|��J��K�O�<��N�^C��SS	�����T��i��h��U��T��7�XC���V�ZC���C
���	��

��z�Q�#S����x��z��9��8��=��<������A�+��5�x�A�_��"��3C�܍%��
CD���x�_�Z�m�w�j��	�c�t	�_�g��:��V�#�	�j&	�7�2#�5�<�'�*��l�u�3�&��w�h�A�f���!�(�o�w�\�x	�9�D�g�@C�ց�

&*"0


S���5��V��)�0��v��]"+����EC'���M�Q�G��7�u	�x�_�`�
�m�k�.�-^��5��b�
�Lf
�{�h	�y�O
�$�%�%�.��J���]�.��bC)���%
05
:C,;6
	

E0	�>�m$�%�.	 	PIC����+"

	
, S���cy��B��7R��b��_��^������QC����S�|C>��� �	��7�2�[�nD�y�(�D��e�

	�+ ��x	�9�H1��>�i�`�F�;�f�1�*'�'�P�Q�P�U�V�M��O�,�%�4�8�}
�C����O("*

S	����a��)�m�~��������EC�����|�S��dS
����a�Q�z�9�&�wC���+�r�s�r�q�0S
���K�D�}�G�5�t���c��bC�ܜ^�m�2�E�a��Y�d�
�|�x�#�]�R�u��b�6�U��}�L��#�|C�ص>

C�ρ�e
 S
� ��l��A�6�C��.���L��s�lC�-�f��B�~�Z�g�z��s�y�;
�B�b	�'d�0�
C�,�lBM�>���Y�XC
�3��n		
S���E��~��[�V����	�}���������vC���i�MKg��(�x�o�v�1�.	�'�D�3�4�y�&�P�I�&C���y!HC����
S
��J�t����h��{�4�G�,�r���cS���K�n��k��l�!� ��^��q�R�4C���'�{�F!�I�B�����u�~�<�.�YC��C���=S�;�E�(��r��M�,����s��r��;��L3�I�h��r����C�P�K�"�E�<S
�[�F��2����������W�
��EC�g�_�'��=����S	�p������8�9�_�h�B�C�}�S���L�	��X��%��$��;���8��'��/�RS	���o�D��P��-�W�P��0���C���b�h��^��S���f�p��Z���p�� ��A��@��u�9��,��WC6���4�<�p�M���y�;�*��b�x�{�M�U�o�`�l�!�Z�'�k�n�A�z���}�4�D�m
�7�:�N�&�,�1	�>��g�6�y
���o8�V��
���,�C4�ɶ�>�0%�g�
�1���c�X�]�@�=���B�C�>�9
�%�T
��w	�2�/�
��hg�%�!�{�HC����$$$"

S���I��2��5��>��t��5�	�|�S��������/��.C��A�o�7�X�D��2�#���j��O�0�%�(�(���=�qC
��IC�%��t (S�o�>�*�!��F��3�9�s�-��p��g��`��i���e�"�@�w�'?�Z���7��@S����*��	�9�m��,��g�\����-�	�D��nC���~�#�[�{�|&�}	��O�F0�mT�C7����.A�U�/�.�/�r�L�D�I��j�a�X��/�+�|��5��A�9��v��g�&�X��P�+��d�S�b�g������v�w��!C����.

1DS�m�V�V��^�/�Z��P���S�F�
C�z������~����i�b�WS���(�C�#�d�;��>���[��^������eC�����~��S�C
���+��(���}�F�%��B�{�n�C����A 4S���	��4��]��\��9��8��=��<��i�8C��C ��?�N
�!�n��
�7�p@�I�(�s��_�V��?�0�c�(�w��U��C���}+F
S
�j���t��g�(�I�(�5�r�����R�&�QC�y��r�y�w�p�_�ZS�����f��u�d�k�_�H����
��4C���2�$�o�r�y�x�S�(�Y�:�R�enC���)�-C����OS
���a�������,�\����!C�<�FC�߻l��
	�{�.�1�
��R�S�`�a�.�
�j	�[�D�=�$C	��q
S	�A�
��B�w�r��,��+C�O�I�C3�M�e�~��z�W�_�(	
�]�V�_�x
�W�R	��$��38C
�P��;
(F(,S�1�26��(��#��"��i��h��o��n��+C	�|�H�]�
�D�9�	
�z�}�CR�>�T���W�,Aja
�Q
$3B�(�F�I�/�
�j�)�Y��	��4
�U�^�{��i�`�C�B�E�&	
�%��6�	�g�j�i�8.
56'&��EC�S��~,		;4 
Cj0'>"S��!����Q�u�@�9�8�q��t���T��*S�-�e��*��M��L���%��$��u�tC0�=�f�p�p�[�H�7�O�v�U|�k�8�_�m��0�75�U��/�&�Z�+�5�2.��T� 
�w���^��u�90�>�0���,��90�R�aYy��p�5CY�>�C	��	�P*�G�_�N�=�J���Z�
�<�Q�@�E�X�a�,�s�N�S�6>C�)��E�D	�G�t�I	
�^�H�N�#���E��;�G�H�U��v�#��3�>��J�.�H� �1C����SC2�@��@",($
Ht  ��0�D�?��D����nB�<T� �9G�7�n2c, ^��
��%�6F�Z�a��Hmo�#�,�z$�:��;���p	�f
�R�>dK�b�4`s}q{)R�d
U�7$�<�v	�p��c�=�P�}�@�P�X�D�T�F�R�J�H�L�<�X�
��(c���Ocdx�c��c�$�c���@cR?�cR����,��vG�E��2D�6C�Z�
�g�����#�'�*�-'�<�>�E�J�L�N�Pc�>TR�E�
�+(� �(G�7Q�IE�\
�o�e�m��
�,�xZ���1h�D�7�#�H�Lq�\�N�v�m�j�5�
�j�(�U�v,�x�=�f�(��?�h�V�Z�]|�^M�(�
�1��V�>�r�q�	�6�G7�rA���C&�H�	�g�R�\G�eM�sv�&�+�-�8��|���	�f�!�=��U�R�V�Z�W�\�`�f�j�l�p�s�v�x�{��	�
�`i�Gt�Q�?�z"�
J�!���&�+�4�<��
�/�K�;�>�`�,�2�4��%�!�*H�@�B��c�f�j�n
�s�%�O�T�{�z-�#�	��U���	��
�#�'�,
�0�7�:�@�D�F�K�M�R�W�Y�[�^�e
�i�m�p�t�x�}����
����	�
����!�z�S�JW�N��d�h�j�l�n*�7�;��#�,�.�a�;�@�F�J�M�O'�]�8�������%�'�/�3�7�>�A�D�J�7�A�D�G�K
�Q	�V�}4�y��V@�F�oc���yc���\�)c}��:�c
vF�/%�4�/��2�s����O�S�hH�A�f�F��Lc���;c�;��3�$	�)�<�L�D�h#�G
�k�z&�}$��P��C�8�L!��q�$�)^�&���2��/��V��X���aq��f�j�Y�_�e�l�u�w�{�}	�������"�&�+	�.	�3�6�;�A�H�J�O
�S�U�^�c�e�n�r�t�v�y�~��
��
���#�%�)�,
�4�7�;�@�D�I�O�S�V

�Z�g�k�n�q�v�{��	�������(�2�4�9�;�=�@�B�E�K�N�Q�T�Z�]�b�f�n�t�v�}������� �%�'�-�/�1�3�5�9�=�B�G	�J�M�O�W�a�c�e�q�t�v�x�|�~�	��	�
�������"�%�(�1�5�8	�@�D	�G�S�X�]�b
�e�p�s�u�x�}���	�����
� 
�$�+
�0�4�7�:�A�C
�I�L�Q�U�Y�]�`�d�k�s	�x�}	���
����!�&�-�0�9�<�A�G�J�L�O�X�\�a�c�i�n�p
�u�x����	�����
� �"�&�(�+�.�5�9�@�F�H�K�S�U�X�^�c�e�j�m�p�s�x	�|���
�
����"�'�+�.�7�:�?�C�G�J�L�O�S�U�W�[�b�d�f�i�k�p�t�w�z�|�	���������#�&�(�+�0�4�9�@�H�L�O�X�_�a�d�f�i�k�t�x�z�|�����
�����
��+�3�5�:�>�C�K�N�U�X�_�f�n�r�v�~�������� �"�$�&�)�+�1�5�8�:�A�C�G�J�M�P�S�Y�[�`�f�i
�n�p�t�w�z�~�����
�����%�'�*�7�<�>�C�G�J�L�O�R�[�`�c�g	�i�s�w�~�����
��#
�*�5�7�?�A�E�G�M�O�V�Y�]�_�b�g�j�o�s�{�����
�$�/�5�7�9�>�A�G�I�O�R�V�Y�\�_�c�g�u�z�}�
��
��������"�%�+�0�7�<�A�D�F	�I�M�S�Y�_�a�g�m�s�z�~�����
������!�(�*�/�7�?�A�D�K
�N�Z�\�`�m�o����
��#�)�.�5�7�:�<�>�G�J�L
�O�V�Z�]�_�a�d�j�r�t�w�~��	����� 	�$�.�4�6�:�=�C�J�L�O�S
�Z�\�_�d	�j�n�q�w�~���	��
���� �&�(�0�2�7�9�<�?�B�D�F�H�K�M�P�R�U�X�Z�]�`�b�e�h�m�r�u�}�����
��
�����#�(�*�-�3�5�=�@�E�H
�M�S�U�Y�^�a�f�j�l�o�r�z�|���������	�"�,�3�7�<�>�D�H�K�N�T�W�[�`�i�l�o�x�}�	�������"�$�+�.�0�2�4�7�:�>�F�J�L�N�R�U�Z�a�c�f�k�p�t�x�z�~����
������&�+�-�0�2�7�<�@�L�N
�T�X�Z�^�a�d�i�n�p�v�{�}���������"�%�,�/�3�;�?�B�E�M�V�X�\�_�k�q�t	�{����	�
�����$�*�,�1�3�5�8
�>�A�C�G	�M�V�Y�\
�d�g
�m�t�v�z����
�������$�'�*�.�3�9�;�A�C�J�L�N�P�T�X�]�a�c�f�j�l�n�r�v�{���������'�*�,�1�3�5�9�;�>�@�E�G�K�P�Y�[�_�a�d�g�k�m�s�v�x�}���	�
���	�(�2�8�:�=�?�B�K�S�U�Z�]�d�k�n	�v��
��� �#�(�*�-�2	�5�@�H�L�O�S�U�X�_�b�f�j�m�t����
�����!�)�+�-�0�2�5�8�:�>�@
�F�I�O�Q	�S�_�a�c�e�h�l�u�x�~�������!�)�,�2�7�<�?�A�C�H�J�L�P�X�\
�^�i�p�r�u�x�|����	�
������%�4�;�D�J�N�P�T�X�]�a�c�f�n	�p�z�����
�
�	���$�)
�0�4�8�:�<�>�C�E�H�J�N�P�V
�Z�_�g�j�n�q�u�z���
������ �"�%�-�/�2	�6�8�:�<�A�H�J�O�S�W�Z�\�_�c�f�m�o�q�u�x�}����
�
����!�#�%�(�,�.�0�5�8�;�>�F�H�J�L�N�T�W�Z�^�c�g�j�n�s�v�{�����������%�+�-
�/�:
�<�J�N�P�U�[�_�a�d�f	�m�p�u
�|�~����
�������#�)�/�2�8�<�@�C�E�H�K�P�X�\�^�b�d�l�p�s�u�x�|����	��������%�'�2�7�:�>�A	�F�H�K�S�U�X�\�`�g�j�m�o�s�w�y�}���
�
����	�'�+�-�2�6�?�B�I�M�P�S�U�^�c�g�m�q�y
�}�����"�%�(�-�/�5�:�>�A�C�H�O�R�X�[�_�b�f�k�o�r�t�v�z������	�	�%�1�4�7�>�A�E
�M�Q�U�W�Y�\�^�`�g�i�l�t�v�z���	�����"	�$�&�*�0�3�8�;�=�?�C�E�I�L�N�U�X�\�d�n�t�w�{�}����
������ �%�(�/�4�:�=
�A�M�Q�S�W�`�f�m�o�t�w��������%�*
�-�9�=�?�E�N�P�\�`�c
�m�t�y������� �#�'�)�,�0�4�8�:�=�F�K�N�P�S�Z	�^�d�f�h	�l�n
�u�w
�|����������� �"	�&�)�+�3�7�<�A�C�H�J�L�O�Q�S�\�e�h�l�p�r�v�z�|
��	���
��"�'�+�/�6�:�=�D�L�N�R�T�`�c�f�l�n�s�y	�~���	���� �)�,�/�3�9�;�C�H�J�L�O
�S�W�^�`�e�k�o�v	�z������
������������!��$��'��+��.��1��9��<��?��A��D��F��H��J��M��P��X��\��a��e��k��n��r��t��{����	��	����������#��%��)	��+
��5��@��B��G��O��Q��U��X��Z��\��b��j��q��t��w��y��~��������	��
����
�� ��%��*��,��1��:��A��E��I
��P��T��W��Y��]
��c��g��i��l��p	��v��������	����������	�� ��$��&��*��-��3��6��=��E��L��O��S��Y��[��`��c��g��m��o��s��w��z��}��������	���������� ��"��$��'��*��-��3��9��=��C��L��N��P��\��a
��i��w��y��|����
�������� ��#��&��*��-��/��4��6��8	��>��F��H��N
��Y��_��a
��e��h��l��o��r��x��}��������������!��)��,��1��4��=��@��B
��G��K��O��Q��S��Y��[��`��c��e��j��m��q��y��}����
��
������	����'��+��.��1��8��:��?��A��D��L��Q��S��W~��b��j��r��v��{����������
��������"��&��,��1��5��8��>��B��D��J��M��O��U��[��_��c
��g��i��m
��q��u��y��{��}��	��	��������
��"��(��.��4��6
��:��?	��G��I��Q��S��U��W��Y��^��`��m��p��t��x������
����������#��*��,��.��1��9
��@��B��F��O��S��U��Z��_��h��l	��t��|��~������
���������� ��#��%��(��.��2��6��:��>��@��B��G
��K��N��R��T��X
��]��a��d	��f��p��w��z��|����������������!��$��'��,��0��6��8��<��?��A��G��K��N��Q��U��Z��]��b��e��k��n��t��w��{��~��������
��������'��.��4��8��;��=��A��N��Q
��V��X��]��`��b��i��l
��r��~��
��������������'��+��.��1��6��:��>��A��C��H��K��M	��U��X��Z��`��b��h��k��r��x���������������� ��#��'��*��/��3��6��9��<��A	��D��P��R��_��a��g��i��m��q��s
��z������
������������!��&��(��*��.��0��4��8��<��?��D��F��H
��Q��T��W��[��]��_��a��d��f��m��p��w��}��������������������$��&��+��.��2��7��9��?��A��C��J��N��Q��W��Y��\��_��b��f��i��p��t��x������������������"��(��-	��2��4��7��;��>��C��I��P��S��X��\��`��b��d��g��j��l��o��q��s��{��~��������	���������� 	��'��)��.��0
��2��@��F��J��N��P��R��Z��_��a��e��h��k��n��v��z��~����������
����������!��&��-
��4��8��<��@��D��F��I��L��O��R��T��Y��a��c��e��g��i��k
��p��s��u��w��|��~����������������������%��(��,��1��5��8��@��B��F
��L��O��T��V��[��`	��d��n��r��z������	������������!��%���4��=��A��C��F��J��N��T��[��b��f��m��o��s��z��}��������������%��+��/	��4��96��>��C��G��I��K��T��V��Z��_��a��d��i��l��p��x��}��������������������$��(��*��-��5��7	��?��A��D��F��I��K��O��Q��T��]��_��a��g��k��n��u��|��~����������������!��'��*��.��4��:��A��G��O��T��Y��[��_��b��f��h��j��m��o��r	��wA��	��
������������$��'��-��/��1��4��6��=��?��E��G��J��M��P��R��W��]��_��b��j��m��v��y��{��������	��������������"��$��)��-��/��9��;��?��B��E
��M��O��W
��[��_��a��c��e��k��n	��v��x��{��~����	��
��������#
��&��3��8��:��=��A��C��K��P��W��Y��[��]��a��c��f��i��m��o��q��t��w��|������	������������$��&��-��4��7��9	��;��F��K��M��R��U��Y��]��e��g��i��m��q��s��u��w��z��������
����������!��%��(��+��/��1��3��7��>��G��M��P��R��X��[��`��c��g	��l��o��}������
������������$��(��+��.��2��8
��>��I��L��R��U��Z��\N��f���w��y������	������������"��%��)��,��/��5��7��9;��C��J��M��O��W��Y��\��^
��c��f	��j��n��x		��}����������������"��&��*��,��0��3��6��9��<��>��D��L��N��P��S��X��[��_��b��k��m��o��r��t��v������
������ ��"��$��*��-��5��:��<��?��E��J��M��U��[��h��k��n��r��u��{��}���������������� ��#��&:��/��6��8��=��F��K��N��P��S	��Y��\��d��h��l��n��r��t��v��x��~������
���������� ��&��*
��/��:��<��?��B
��N��]	��c��m��p��~����������#��(��.��2��5��;��=��@��C#��K��M��Q��W��]��`��b��g
��k��r��v��{����	������������!��&��+��3	��8��<��>��B��V��Z��\	��f��q��x
��z��������'��,��0��3��7��:��C��K��N��Q��]��`��b��e��l��n��t��v������	������ ��"��'$��0��5��7��9��;��=��?��B��D��K��T
��[��f��m��p��s	��y����
�������� ��)��+��-��B��K	��P��Z��]��b��f��l
��q��	����
�������� ��,��4!��>��C
��I��N��R��V��X��\��^��d��g��m��r��t��y��}���������������������� 	��$��0��5��<��E��G��Q��V��[��_��c��e��h��j��n��s��w��~����������
��5��/�K!O���(����
�������	�+�5
�9�Gc.�������"	��$��.��0��<��>��A��E��G
��J(��X�?��{��~�o��s���HF��*��H���sc5���c5�`��
��~
���d��c7���|c8���|c9��|T��_��i
��I��Wr��wc:t���jc;M��v�0��Dc<m��uc<op��cc<���T���sc>���sc?���sc@U��s�(��IcA^��r���QcB���qcC6��qs��(cC���*�� !��K�U��mcD�(��C���lcFK��l��8���LcG"��j��
Q����dq��tcH���fc	IF��f1��-��_��a��f��z��}G�����[cK���[cLv��[cLx&��R[��y��U���hcM���|��J���c����c����c����c����c��6��PK
!<�9�L��2chrome/pdfjs/content/web/cmaps/UniGB-UTF32-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
UniGB-UTF32-HC �V�Z1�6��m�1
�:�3�2�
	��c0�H�R�F�P��p�D�@PK
!<F`PO����1chrome/pdfjs/content/web/cmaps/UniGB-UTF8-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE���?���?����? A, �J�vG9�_8�<
&
�Q�Z�D1�X&#�b�W	�e�-�w�v�@A©�!�o�&A¥��RB2ḿ�7��T�	z
�a�<�e�e�{�tg

9�J�w	�	A�bD�
J�R�u�yB‐�"�Y�O�H�}�r�:	�n��)}�IxB€��Q�S��{

�NR@⼀�B�@��r��-�
�;�]��a��l�I�Z��.��-:�*�+��&�
��C�)�V�C�9�
� �5�;�p��z�	���g�`�}�<�1�^� �u��/�6�(��a�6�i�#�U��X��KR@⽀�&�D�m�;��M�$�{��I�$������|�j���=�z�!�j�f�%�:��H�~��]�N��Z�h�M��M��<��;�@�R�c�:��j����M��)���k�b�r�7��&�%R@⾀��C�9��c��>�|�}��q��z��e�K�q�Z��$��
�}�H�9�S�8t�T�m�l�5�(�_�U��`��i�(��F��}�3�f���v��U��4��
���D�9����UF�B�8��
�"�F�kR⿀�`�5�d�;�V�Q� �C��j��{�q����g�n�X�5�*��`��B〃g��~�#
�Z��*���|��Q��~�t�q!�}
�b	��j�5�E��n�5B.〿��U�T"� 2�L�)+�j�o�}��`��Z��q��u���t
�k�x�u���P�y2�]
��j�
B䦄��eR万�#��%^�L�P�-�������1�m��
��c��.�K�$��TR丢�i��p�9�b�T�����g�j�h�R丰�~��P��m��l�
��o�n�#�)�f�x�h��3�8R乃�.��{�z��]�d��	�	�<�9�S�z�`�5��`�\��
����	�PB乜�u��8�N�9l�)�j�;A�
�7B亂�YB乲�R了��~���t�y�9�2�1�w�"���x��/B亘�^�t�S�yR亞�q�}��9�A�(�!�a��M�*�����x�B亲�T�`�6�3�E�p�}R什�I�I�xI�k�H�/��&��?�^�w�3R	仑��Z��!����x�y�R	仝�n�������k�r�FB	件�qG�)7�N�$�3�/�B仨�#�+�,�3�$�p���E�/�6�M�!�^�`�_�xB仩�5�AR
伞�Z�H�#��D�Q�P��N�]�\B伪�@�\R伯�u�F��|�C�B��K��J�W�V�m�l�E�D�E�xB佃�I{R位�J�9��$�3�d�g�f��O��N�or�W�Z�M��>	�_B佰�fB佣������W�:�+�j�_B佦�pR侃�=�������p��7�6�C�B���B侔�D��Y�[�"���
�5R
侣�i�P�{�n�M�:���"�!� �7�)B侵�S	��C����8�EB俅�HRMB係�R
俗�L�e��,�Q�P�O��r��N�K�D�SB俩�r���Z�)B俦�E�=�NB俧�6��@�o�mR倉�b�P�W��s��X�s�r��K��f�YB倘��e�Z�t�w�=�6��R
倥�Y��|���2�B+债�O�H�)�D�v�!�"�+�$���E�Z	�G�P�t
�#�@��7�l�I�j�P�}�|��-�0�o�S�^��1�6���7�-�B偉�*�_�6�`����^�1�h�?�+_�C�V�7�<�s;�$�S�L�%B倻�e�M
�{X
�Q�`�P#,R儀��2�7�6�1�0��n�)�8�B儒�@�s�B
儐�V
	�1
�t�=�|	B儑�r�e�tR兀�Y���O��1�\�8�A�u��4�}�|�aB兑��D��a�>�<�dB兒�eB兓�R全��<���'�Z�z�f�f��9��>��u�H�k�B�k��>����6B冀�0��]��a�`�&��|�o�B	冁�z�;
��n�+�*�S�RB冘�;R	冯��M�Z�
�4��F�
�h�SB冻�r�>�B�OR凄��$�s�r�~�#�"���:�:�
��[��Z��}B凛�&�r��k�9xR凫��T����e�5�p�&�����+�o���G�&�`��i���B刀�x�n�C�q�2�~�#��{�o�)�*H�l�K�3;�8�MB刖�nB刉�sR	刨��i�:��W��V�m�Q��<��QB刳�R
制�E�]��@�9���b��}��|�wB剀��Y���L�\�M�	��x��X���F�'�wx
�\�y�A�z�9�:B
剄�H�K�&�<�c�M�j�~�L�y��aIB
剋�	�"



�A
R	力�o��i�)���r��cB动�n����p�%�L
�.�g�C�Y�j��V�z�%B勁�>B勀�QR動�M�u���C�T�l�u�t�u�I�IB勤�W�x�1�Y��b�@��	���0�n�m�3�E�U�B��a�!�n�V�:�=�<
��`���B勢�2�`�%�F�t��O�NB勣�d�c.		
R區�o�U�.�s�r���S�~��wR
华�?�p�T���NK�f��(�,R博�p��j��Q��P��3�V�;�Q�:�n�g��=�O�
�/�.�7R
卮�j�o�$�)�t��
�N�/�j��%�H�Z�9B卿�a�B�+�H�:�U�~N�d�1�T�QB厙�AB厃�6R
厝�~��+�f�B�q�`���!�.��W�0B厮��4�9��A�Y�	�J��j�$�-�%�x��l�M�Z�SB厭�y�S�b�N�QB厳�O�JR叟��m����w��h�D��b�U��3�0�O��f�H�~�[�P�b�I���l�:�0�W�]�TR吀�m�'�&��k�p��x�[�Z��U�,���4�)�;�-�h�&�V�@��~���2�UR	君�,�����(��)�<�Z�WR否��u��6����/�X�_��'�N�)�J�\��3B吸��'�:�_��A�j�_��b��&�n�{�^�Y�>
�-��@��s�*�B�w�$B
呃�



 
�JB吺��G
�LR咋�G���J��_�L�O��^���;�q�8B	咙�E��R�/��-�qR
咫�g�s�V�6�����3�2��G�B咸�9�V��u��1R
哀�2�F��F��G��F��E�Z�k�B'哌�'��S�|-�U�Z�M�V�/�o�(�t�?�E�j�y�r�.�p�o�,�:�G�A�	�I�S���A�@	�C�PB員�G�b�HB
哖�C�A"R
唪�<�j��u��t���*�}��H�QB.唷�G�y�r�A�K��9�t�)�0
�S�4�\�%�>�}�t��Q	�,�R�G�>�A�I�B���%�M�t�a{�N#4�]�\��A�,�#B問��'
�L��K[�iB
唽�|�B

�P

R喱�R�2�]�x�y�x�)�(�U�T�E�DB嗄�j�}�j)�g�B嗆�X�N�#B喿�@�HR嗚�!�X�_��J�i�h�m�l�3�t	�R�C��3B嗯�k#�1�b�A	�k�j�u�z�
�?�<%,B嗶�r�O��"���
��WB嗴�W

�G


R嘬�	�r�S�0�g�A��>�Y�N��
��S�l�[B嘿�B噌�	��&
B噀�{��R
噠�p�@����V�>�{��i�:�YR噯��,��P�D�E�D��9��8�}�,�NB嚎�v��V���M�FB嚅�
�dB嚀�n�o�z�`�Q�I����J�E����I
�e���<��o�mR囚�q��>��O�|�g�6�?�F��B困��}� ZB囫��I�{�>�W�VB囬�aR圃�s�L�&�s���4�B�b�AB!圜��W�m�@��Q��`�K�P�E�A�P�r��!��?��k��k*�E�b�N�!�6��@�kB園�F�B圗�v�A	R坤����'�&�S
�1�2�Z�S�R��UB坷�M�J�#�4�b
��OB型�B坳�:�HR垠�CC�#�8����M�B��I�H����~��B*垲�>�I���R�Y�$�/�$�o�-��W�'��B�{�
�v�{�=��X��>����N�(B	埡�*�?�M�
�>�S�tB垳�K�_.
�R
R塊�c�*��f���
�A�6��B,塘��}��/�.m�Ar�q�^��1�O�:�B�G��G�u��D�C�B�c�J�t�e�V�=�=�K�&�H�0�g�����Q��Z�E�@�YB塗�
N��e�0�P�)�^�'2�>�y�H��
�M�<�/�P�X�y�
�K�\B塣�=(�B.�@
�U
R夢��t�C�F�������_��^�/�)�E����B头�hR	夷�K�5�]�G��H�-�,��B奁�0�I��{�)�M%��f�S�_��a�N��M��
�B{�'�*�qB奩�[�{��QB
奀�H
	
R妀�k�m�g�f��z�=�+�*��:��Bj妍�w�]�h�N�(��	�o�U�<�{��s�h��G�U�B�S�?�1�F�A�E��;�e���,�s�`��T�A�sA��{�n�O�%��P�E�r�H�~�%�2�C	�_�z�d
�1�2	�e	�L��Y�^�;�R��$
��
��B�� �u�.�B
�_�^�W�B�W�G�F�HB妝�-�c�]$�c�T�y�'�Y��D�-�0�?��S�0B妘�v!&�Y
"�E�~�F
�@�_0
(R子�0��t�u�)�\�>���t��w��}���AB孢�9��-�2�K�o�8�T�?�N��I�\�SB孫�Z��B孲�<R
宀�.���D��'�z�E�n�e�I�e�����.B宏�!R宓�1�4�9�8�-��d�}�r�`��x�����<�%�V�;��7B宪�I�	�(�{�d�O�I�d�}�7�f�B�7��A�q�g� �E�~B宸�3B宷�X�K

R寝�[��?��x��
�t��u�M�:��'� � �=�_��w�v�uB寸�j�P�F�B�y�$�A�cB寵�
�'B寷�y�HR射���B���B��3�~�4�.�MC��(��_R
尔�:�n�;��\��C��B��y��x�'�YB	尢�6�(�=��(�G��y�xR	尷�\�3�~��B�E�i�`�YB层�*B局����l���\�7�c�L�$B屐�hR属���:��q��F�K�v�y��z�mBc屮�t�m��R��7�A�e��>��t�5�<�3�2��|���q�d�{�l�e�A�l�y�1�(���d�'�
�T�J�:��v�O��@�;�^�\�K�G�2	
�M�M�_
�H�U�`��	�J�$��s�
�<�c�R�e�T�7�K��4�sB嵇�"B屬�>�t�Q��d�%�x�O�4	�k�`�c�p�A�,
�;��N�'�V�x�]B屭�.�O�A&:�X �\*
�U"" 4�P
R	帀�D���V�I����S�)��z��B希���g�jR
帔� �=���~��)�H�{�Y��B带�{�H�	�E�N�;�Z�DV��!�����mB帥�C5��!�I�:�|�m�%�$B帬�Y�N	R干�D�.���j��5��4��O�j��w�F'��oB庀�1�K�v��?�[�p�zR序�=�Q�8�6��?�6�'�v�S����;�u��.��O��N��{�Q�B"庥�5�]���F�K�#�6�0�J��	��O��L	�Q�9�S�5�f�%�@�I�J�U�5�]��]�jB庫�`�s�L��j�qt�&�dB庬�

�i8	�SR
式�T���;�:��#��"��)��(��s�B	弛��z�P��F���X�iR	弭�p�j��#��"��/��.�g�f�iB弹��V��`�w�C��^��
R彌��l�e�d�3�2��u�a��T��5��,B彩�{��UB彝�T�2�+�u�\�A�j���B彣�R
往�*�Z��b��_�K��(�e�!� �;�P�hB\徐�7��n��F�<���'�&�s�r����E�H�d��R�.��T�:��)���!��.�e�n�=�B�+�*�E�V�|��C�:�!��@��l�
 �=�8�O�X�1�y��9�p�_�0�o�\�i�8�4�Q�P���3�&�g�A�l�c�q�U�3�z��v�v�G�N�)B徑�E�I�b��B
徖��U��V�e(R
恧��w�?�T�a�$�	����}�cB
恳�_�_��Z�D�K��G�
�"�R�5�h�SB
恺�_�E
	B恷��TR悫���:���k��j��+��0���m�f��{B悻�l��H�
��/��1�dR
惘�o�4�3�2��o�2�:��)�
�`R惦�L��w�0��<��_��'�h�T�#�Z�J�G�<�4��cB惹�"�d�F��-�A
�2�q�� �Z�H�a��*%�}�y�i��$��S�W�B�5�C��*�k�:�B9惻�9�D��8�;	�d�(�
�t�K�q�L�	�
�>��^�[�I�y�R��\�K��g�6�@���^�+�B�y�0�`��f�[��)�O�:���6�7�5	��]�Q	�oB愇�] �c6
�Z&�SR懵��v�[��0���[�Z�=�}B戀�!R戆���y��8���z��E�?�d�m�d�Y���A��/�d�9��J���|R	戟�|������MR截�)��F����S��R�9�8�A�YjB户�<�A�@�"�G�j�V��]�`�'�1�D�p�$�yDB戽�t�DB扂�R扦�!�"��r��3�L�5�l�g�g�P�B	扳�m�J�N��{�@�r�;��	B找�sB扽�6R抑�]�G�����;�e� �j����G�jB抠�f�*��	�Q�2 �z�U�L�&�.�+�PB抟�<B抣�IR拂�����S�_��>h�i�J�<��U�"�9��h��a��`��)�z�a����A�~��6�F��B	拟�E�w�O�����m�z�k�R括���L�I����?��>��C�*���O�sB拼�S�`�{�C�A�W�^��q�f�&�B挈�NB挀�oR挚�>�����I�Q���G�\�Y�Y�B$挨�/��	�D�x��[�
�D�.� �I�#��T�i�2�@�m��5�r�Z��)�q�:�2��x��f�6�u�D�D�eB挾�Q�i�k��H�P�k�gB挩��D	�TR掇��&�9�r�R��%��$�_�W���:��kB掖�;����=R掠�z����o�g��T��I��H��I�"��+��6�y�r�wB掳�W�hR	掷�?�o����+��*��~�m�lBG揄�W�A��4�Y���0�$�c�u����^�E�,�U�V�@�9�R�R�y��a�h�
�b��5�Q���S���y��<�A�g���q���B�.��b�2��P�:�G�)� ��d	�O�L�:�p�;�gB揀�
�b�O�^�	�K�H�Z�
�C��_�|�i�L�.
� ��g�1B揅�W

�O	
�A		

�K
R撩���*�5��9�~��l��7��6�I�B���-��,B撺�m��C��K�dB撻�`x��A�<�M�4B撹�g�ER擊�g�k�t�������~��-��,�B擗�p�B�@�OR	擞�H��\��
�
�����eB攀�B擬��
�"��d�E�n�q�H�&�Y�E�P��O�B擳�	�ER攒���l�A�@�g�f��y��^�e�d�{B攥�t�+�*��u�
�g|�9�x�E�y����B攢�Y���u�$�g�8�S�HB攭�#	�UR
敕�g�?�J���=��<������}�B敢�M��	� �R	数���j������_�>���y�bB斂��7�a�p�aR
斐�A�/��h�9�8����W��V������5B斟���>��	R
斤�@�s��B������'��&��
�C��:��}�<BP方�Z�J�C�W�W�:�r�o�B�e�h�M�Z��#�'7�!�6�
�P�G�M���n��@�7
��
�7���;�)��K�D�=�N�v+�p�J�g	�<����+�R�K��^�{�C�,�k	�M�+�a�8��d�f������)�~�.��@�"�
�!��3� �o�l�1�HB&於�D�F�i�s
�J

�L	���g
/�R�< B斷�W�J�*�Z�j��"�_�9�x�X��<�A�1�4	��TR�J�AB 斸�s�G
�_	
�@
	
�C;T�E�N	
R朦�,�.�G��h��'�o�K�0��,������[B朴�r��V�6�B��V�'�{�\�9��'�8��K��q�`�S�nB杈�T

B杁�c
R杭�s����I�,�<�{�m�B�?�>�7B松�>��A�Z��*�8B杼�b�JB杽��BR
枕�����e�t�n��/��.��� �pR枢�w�b��Z���{�v��G�m��2�w�v��c�zB	架�O�L�?
��d��r�.DB枳�m
�H	B枴��KR柘�e���W�F�9��F��1��0�B查�0�^�\�P�o�
�>B柩�g	"�B
B柲�AR栅�E����y���o�r�L�;�!��Z������kB	栖�{�7	��t��A��TB栝�B栿�pR桀��s�6�,��m��b��]��V��+��*�s�r��5�}��0��Q�<�&R
桢�{�K�L�w�|����h�eBH桶�a�J�=�p��
��q�6�=�,����L�3��@�i�#�j�-� �L
�c�3�{�A�J�H�6�@��|�V�
 �T�h�!�T�+�$�E��
�!�k���,�X�
�F!��L�P�,�9�]�^�U��#�^��N�T�$'��s�H�G�r�~�'
�N�M��H�r��%BK桴��K!	�L
 	�D	
 �H
%�F+		�E!

�h�E
	
�Q�x�:�IB!條�u�y�R�#�F�b�i���7�x� �\�3�^%�m�M��Kp�e�Z���x�Q�G�k�8�q�!
�*�*��G�57�N�H�W�|2#�RmB梘�j�v�x���g
�]!4�M�
�_%+"'*!"�V			B檯�B/桵�	�J$�L�D,F�`.	
�J
#.�S.H�A*�Z&�nH
�qN�sR �H
R	歟�K���75�!��\�[B	歪���
�A�w��^��g�G�C��2R殃��
�&��c�6�*��%�G�E��.�'B
殖�2�q�[�X�W�A�<�v�q�/�/B殘�\�V�S�N�O�R��
�"�i�F�9�^B殗�6�I
R
毒�}�b�}���0��?��>��SB毡�R��9�(��GB毿�@�H�'B毴�m	R氌�A�K�@��/��k����)�X�
�a��"B氛�rR氟���V�E��T�O�@��S��E�J��4���u�F�j��@�C�B��I�vB永�%��B�;�2�y��y��L�1�!�X�1�{�j��X�/�"�K�5�|��GB汊�"	B汈�
	
R
沀�3��/�h�Y����!�����B沌�'�K�D�r�s�u��&��3R沟���j��	����E�K�#�$�E��N��3�e�(��l��/�OB河�
�;��4�3�5�t�)�D�{�#"��F�5"�t�_�V�1�v�gB	沱�;
�]
B沺�N�PR	泮�:��l��h����
��{�fB泻��G�&�C�	�T���+�3��G�I�@�5��_�+B泺�8
�E



B	泹�j�@


R浀���%��$��#��"��I�g �h��"��c�w��X���'�,����/��K�JB浙��)�N�M�q�p�5�j�]��h��E�U�)����M�$�+B浹�\�MB浛��f0R涛�!��B��	�&�5�4�k��H��]�E�� ��o�-�^���B涮�j�{�&�;L�*�@�'�6�f�i�d�q�n�?�	�X�|
�)�^�J�I�5�DB淪�^B淗�bR深�'����_��^�[�:�C��`��y�:�)�@B2清�b��z�o�Z�U�X�1�6�m�T�%��g�l��t�I�2�A�k�:�;�6��H�.�X��n�q�:���-�+�C�g�/�r�]������3��OB渦��s�x�_�r�e�f��B渋��U�lDR	溯�R��.��e��v��Q��P��k�B溺�J�F�S�@B溻��D�8�HB溼�o�CR
滋�)�&�t�o�{��h��q��p����
��$��sB滚�OR滞�L�T�3�4����������I�LB滴�/�:�H�b�$��s�\�

�w�rB
滬�K��]�9�.�S�A���a�4B滳�	�L ,R漩��[�$�{��b��?��>��3�@�#B'漶��Y�L�X�-
�]�B��L�k�j�g��G�O)�j�S
���d�� �F��	����a�]B'漸��A�T���D
�%
�6�k�z�@�+�Q�:�O���z�	�5�O�b���
�J�+�l�Y����z���F�*�� �!B漷�1�H
�I
"*	�`��JR	瀘�Y�R��9��4�a�`�3B瀣�(�R�c�d�Q�R�u��~��|�}�^���@�b�s�{�;��O�f��
�	�B瀧�X*�>�C�g�D�6"�E�w�4��M�c�B瀱�R
�P$�UR	炫�S�}�x�o�a��r��#��"��B炸�I��t�o�C�H��D�m�}�@�d�yB炷�R�D	�B為�*�NR烤�K��t��m�z��=����Q�#�(��H��eB"烷��]�M�K�V�
�J�e�$�&	��s�U�i�j�.�c�l�v�u�F�8�"Z�?���u�V�
��p�P�F�W�#�Z��B6烴�z�[�G,�h
��S���h	�s�8�'��L�k	
�W��\�g�(�/�(�B� �=	�<�_��X�e�}�j�i�f��c��:�N�#�
�
�'
��FB烸�O�[*�sN�Y&*
�@)6�\0R爵�#�a��+�p�Z�?����+��B片�K�5�*�BR
牖�k��8��5��>��K��J��
����m��l��i�?B)牦�E�C�2��#�+��B�E��l���u�J�5�4�A�A�G��o�j��'�,���Q�^�<���BB牽�L�X�f�_�4�X�B	牨�P�c(.�YR	独��\�_�t�C�q�4B#狷�~�SG�D�
�U�N��|�e��~�s�.��@���U�Z���j�M�6��B狹�6�s�x�n�B�E�-�+B猀�(�ER獨�R�0�+��C��n��-���R獵�+��9�7�n�%�B�0�.��7��6B/玄�I�1�H�a�b�S�@��	�;�>��l�3�l��D�n	
�a�"�.�/�0��x�}��P�}�N�@�\
�	��3B玀��=�3B玈�y.�O$�DR琥�2��l��k��j��u��t��i��h��m��l��qB	琳��n�1u�J�j�@�*��{	�DR瑕�<������������ ��.B瑰�>�P�L�oB瑣�]��w�W	�H�6�7	
�&�/�	��"�L�g�`�#�,B瑿�#�B
�uRR
瓚�d�������o��n��o��n��9�5�H��"��9B瓮�\�L�M�%�p�D�{�x���d��(�!�x�yB甌�%B瓵�x�X$
R用�(���~��u�$���`��Q�e�V�I�0��r��+��*��W�S��(��O�mB
畅�N�R��m�'�@�A���}	�s�~�M�\B甾��A�D�"���!�d�e�W�C�V��B甿�"�M%.R疏���,��
���l��m��l��m�'����c�(R	疝�d��n��U�`�����o��FB	疫�f�i�$�x�y��C�[�9B疬�eB疭�cR	痂�n������	�o�@B痍�pR
痒� ��"��O�s�8����s��Z��B痞�ER
痢�j���
��	����Q�BB痰���{�G�LB痱�x
�@�b�o
����	B瘄�
R瘞�2�E�n��!��"��5���l�\��M�5�R��,��#B瘴�o�C�S�5B瘰��J�[�@�<�5�D�A�,�1�vB瘽�$�CR癢��o�>�K�d���	���K�B癸�H�A�
�C���~�
�
�M�V�9�@
�Y�B���O�TOB癯��.��"	��K�y�h	�i
�,�l�MB癿�C�C 0�MR盍����?�I���`��g��f��o�A�TB盛�9R
盞�n�=��Z���w�Z�M��f�{B9目�"��l�_��G���i�B�8�@�1��0
�/�G�t�=�q�(���E�M��5�r�C�:�� �)���_�^���w�C�f�X��x�@��/�2���)�lB睏��+�y�uB盰�	
�@	

�F!,�yLR	瞥�R��P��q��p�����$��-�>B瞳�\�h�Q�
	�c�B瞰�u�r�s��G��[�-B瞴�	
R矢�O�����y��x������A��@��)��!�+�hB#石�F	��y�f�A�"�,	�)�+�p�~��w�z�?t�x�d�c�A��8�
�9�l
�O�6��a��L�C��I�J���3B矶�"�G
�F�l�Y�Z	�O��W�qB矷�.�[�D �^*	R碟�[��h��������������=��<�B1碰�7�-�Z�=	�8�B�
���5�D�{�l�Q�	�F�X8��s��B�'
�l��-�p�d�
�;	�/�\�G�~��*
�	)�f�"���/�A�T�y�p�?�O��P��8'�BB6碭�d�=�>�?�^��L�A
�8��?�,�-�4�G�9���,�+�(��+�M�v�+�7�D	
�J�<�j�)�2�3�&���G�]�a�$B碻���@2D�L$:�G
�uN	
�BR秣��O��X��!�����?��>����������/�OB秸�&�L�1�B�0
�;�6�.�y�<�m�B稂�$
	�R
�
�+B秼��s�D
R	稷�(��h��m��l���Zy����B穆�%�t�`�G�x��mB
穀�w�'�w���*�I�~�B�#B穪��@
R	窀��a��@��y�x��~��c��b��cB	窍�L�<�D�Y�_�p���iB窕�
B窞��\R
窥��0����}�x5�6���B窿�G�K�9
�J��m�t�i�`�a�M	�J�Q�F�g� ��}�|B窳�%�&��I�
�x�,�1�}��N�u�I�JB竆��t$	
�F	
R笤�t�7��.������	�_��l��B笺�V�^�L�;�*B笱�q�F��+B笲��5�H
R筏�A�v�~�5��F��)��(��E��D��#��"��#���2��B筷�w�G�"�A���I�<�	�G�$�1�@B筠�~�D�K�I�t�		�G�V�CB筡��N�L	
R	範�o��<��	���
�W�V�Q�+B#篓�O�o�u�\�:�O�/��3�M�6%�t�0�U�D�K�m�f�v��t��h�4�E��S�m�h�K�M
�/�x�s�l��EB6篑���%�s���A�
��N�K���^�8����Z�g�A�s�&�U�6�i�0�]�I�6�z	�3�T
�N	�4�s�N�B篒��&
�N�_((>�h:
 �@R
糯�u�J�p�C�B�y�x�E�D��a�f�x��mB紊�XB
糾�H�A��B�#�	�&�;�4�*B糿��[�AR
紓�#�����'��&��/�9�s�PB素�M<�O�L�M�~�rB'紡�s�z�h�
��@�+R��t�o�j�y�p�v��>�te�D�3�Y�P�G�=�v�+��-�l�x�Q�T�!B紻��w�E	"�G R
綫�E�b�K�Q��6��C�d��dB/綸�_�0
�}�1�(�Y�D�^
�	�v�?��*�i�d��-3�
�y�n�u��^��X	�9�<	�N�a�n;H	�{�n�
�)�W��]B緻�&B綼��J�V
�a2R縱�;�(����L�K���
���5��4��9���RB繁�NB!繅�R�M
�R��g�z�3��5��v�'��U�V�A�$�C��g��J�/�j��m�>�]�$B	繀�� �IR	纟�S�I�H�c�b�;�:�/�-R纪�@�X�(�,��v��'�.�[�3�.��R��9�s���O�iB绊�xB纽�g�H�C��
�;�v�JB纾�[�JR绍��@�5�
�I�t��`���n��k�S��2�.�/�O��R绢��$��R��}��I�P��L�L��b��1B绰�E�b��-�w�V�B�/�]�@�F�u��{�#�B绶�o�N	B绹��\�PR缓�O�!�Z�I�f��
�	�
�m��*��=�B缨��E�Q�Q�n���r�G�$
�Q�b�h�m�8�v���E�H��(��N�G��|�J�I�BB罌�b�nB
缷��`�I	�@R
羅�b��>�?�>��o��n��5��4��B羔�\�&�r�E�g�p�+�6�C�u�+�\�A��)���?	�~�R��C��q�c�l��@B羝�D�X�Y��	�9�`�J�
����r��J�s�xB
羠��!
�A
"
��GR
耐�0��h��5��4��5�-�,�%��WB耜�N�}�~�;�<�G�v�e�}��n�B�6��l�T�VB耬�BB耡��l	R	聒�\��N��#��"��;��:��_��^��{B聞�
�y�R聯��o��N���+�O�X���/B聽�y��5�@R
肃�U��^�?���L��G��F��/�mB肓�qR肖�o��t������K��8�O��D��{�I�,�����9UR肩�Z��4��t��u�g�.��*��{�'�� ��wB	肺�k��L�@�Q�c�m�}�[�6B肷�y�DB肻��8�IR	胖��V�����C�:���B胡�1�v�2��R胫��Y�\����/�2�n�K�@��������W��V�������B脂�+�L�_�r�cR
脏�#�5�%�.�O�@��"��G��F�������`B脯�'�4�
�B脞�
	�NB脰��oR腆�F��p��S��R��K�F�.��R��=��<��1��4��W�A�V�@B腠��VR
腥���e��V��a��`��s��r��_���x��Y�lB腹�4�"�}�H��@�y	�v�B�
��#�
�?
��L��w�.s���1
�`�#�C�jB腴��:�I�#�<�<�y�P�_�S�X�|�4�A�A�s�^�e�2��5�*�S��9�R�
�n�yB
膁��

�N
R	舃��f��}�]��l�E���:B舒�}��$�_��DB舐�bB舑��j	R舨�0����	�x��~�����y�/��:B舵�%�]���M�P�#��U��D�R�!��
�W�V�B�q�vB舸�2�E	�L�M�d�/�7�d�)�V�^B舺��{�H&�JR芈�x��d��
���&��;�0	����	�B	芗�j�{�j����o��n��uR芤���4��J��%$!�{�?�H�Y�J�e��v��A�@R	芷�t�+��F��5��4��+�
��B苀��?��
��f��&��'R苍��F�k��t����)�4��b��5�:R
苛�N�p����	��%����1"�k�E�B
苫�t���T�����$��y��x��R
茀��\��{�W�C�t�A�B$/B茎�T�m�/B茌�
./<3B茍��_R茫�(�u�r����%��$��-��,���K���
���T����7B荆�R�]��!��/B荀�%B荈��~

R荝��
��W�%��j��/�q�^�7�8�s�nB药�0�a�h�G	��!��b>K8

�Y�L�-�*B莊�+�Y��jB莗��&R	莵��4��m�q�t�i�p��\��]�7B3菀�^-(��ZH�Q�(
�s�x�Y�j/�7�X�k�
�X�C�k�`���B�4	��6�}��<?0�C�H�B
�Y�Z�s�l�i�B	華�M�W�L�!�2�z�S�}�t�iB菆��;	�a(
�f	R葩�n��d��c�f���\������!�� ��}��|��;�L�d�o��t��{BC蒂�:�^�U�^�g��L
�O�O��W�R	�A�w�
���"��T�g�
��.�L�!�%�>�}��m	�n�T
�w��/��E�<
���	
���)B蒓�M'�Q�C�H
�g"�
�8
�V�d	�=�c�.�;�S�L%'2!	(�9�bH	�B蒊��2
*�M
�S 
�]"R	薄�
�*��b��o�D��(��?��>��IB.薛�O��	�Y��/���`�K�@�U�G&�*�U�I�B�.��
��o�p�M�t��A�9���|�#�(�&��[��	�B�s��4�@�8�{	�d�:�;�q�A�<�B薏�
�G"�EBM薑��M
�^�s�j�n�R���c�"	��:��`�I�-� �1�<	?
�}�2�e�M�c�F�f��+�t�m�K
			�I	

	
�|�H��z�{B薐��W�G
 0�rD�_*�f*�Y�AR
蜒�z�0��p��%��$��_���J��S��R��_��^��]B蜡��e�l�+�r�3W�-�V�0�q�s�~�z�I���y�]���R��)��'�&�I�B[蜢�.

�H�l�g
�j�{�T�[�E0�*�3�L�r�O
�*�%�B�T�u�8��(�%���.�-�
�%���s�F� �X�{���(�)�x
�_�9���2�3�G��X�p�#�0�!�EB蜤��S�h6�L!.�H
"�D
$�PR表�V� ��x��W�!��v��?��>������B衷�S�I�I�I�f�8�b�@�=�j�G�l�y�O�+��d�x�E�z�%
�I�2�Q�1�k�HS
�@��|�fB$衹��G�(�)�B�&�)�I���2��,�&�U�#
�^�]�H�x�o
	
B衸��,�G"�S

�QR	褰�9��T���2�w��H��G��F��QB襄�R���A�T�	B褻�]�E�5�r�'�"�s�l�w��F�Y�Z�Q�G2�l�I�"�b��1�(�[T�zB襀��-$6�TR
觀�-��u��,���<����B角�4��Y�(B觖�W�;��m�H��)�B觍��
R
訇�~�p��p����
��!�� ������'B詈�B訓�j�n�/�'�-��E�{�"�g�X�z��$�K�I�B�	�u��Y�X[�?�.�\�?B訔��6	�@
R
詩�,������w�v��t���?��n��L��}B詹�S�O�a�i�)B
詼�p�D����6���B誈��{
R語�=����
�f��$��K
��O��p��UB課�\�O_B説�G�v�S�E�N�B誱���QR談�i��l��G��F��o��n��]��\��Q��P��Q�+B論�`�3�.B諗�/ �o�^B諝��+R
諶��}�0��8����~��g��f��!�� B謇�<B謀��l�]M�L��T�B謃��>
R
謔�����u�5��(��s�|�[��N��kB警�^�RB謡��O�Z��
a�S�z�J���W�P�I�H�[�I�&����j�B�[	��"�]��{�:B謧��S �W&�xTR
讜���6��'��&��)�=�#��d�%R讨�)��v�}�����"�#�K��d��U�R
讶�q�V�K��&��V��U�u�,�B诀�&�xR评�_�H�o��B��K�m�v���#�(����}B诚�t� �m�n��B试�kSB诖�&�q�t�#�,�x�+R诪��1��k�\�P�4�[�Z�m��l�4�X���
�{�m�f��b�2R谀�6�)�(�?�>�[��@�]��L���;�J�9��(�{B"谓�L���a�`�Q�R�}�J�Q��A�@��:�6��C�t�!�I�@��6���;�`�h��J�N��\B豇�v�"�#�
�Q��~�I	�L��g�g��*�+�k�f�i�`B谞��3�a�
jR貯�"���8��3�� �����N���B��=�k�|�l��:��!R賀�E�H�?9�+�*��6��+�q����QB賑�2�/�����AR賚�3��J��u��t������O�t�i��;����%�VB	質�3�I�<�j��@�z�F-B賫�5�HB賿��*R	贈�f����-�P��Z��;��:���B贖�=R贛����Z�-����g�{�J�s�'��E�|�k�\�]���N��y�\R贴�L��B�a�r��@�>�q�p�O�+B赁�'n�n�S�W�z�w�mB赃�"�B赀�1R赓�;�1�(�1��>��W�F�a�e�b
�E��>����*����x��O�0B 赫��^�#��W��I�O/�\��R�?��	�p�D�q�O�|���5�|�S�$�X�?��O�v��J�O�BB"赭�u
�P�D�J��w��9�D/ �G1,�F�EB赬��F�S8L�G

�NR	踩��L��
��������
��B蹄�6�;�8�B踴�0�i�B���p�M	�V�WB蹃��HR蹬�&�w��n��7��<������9��8��AB蹺���T��G�U�@����R躅�J��L��'��&��m�a�j��`��M��L��S�l
�oB身�&�G�h�-�rB7躚��g�j�E�H�U�/	�k�V�c�>�=�N�V��"�k�n�D�M�P��~�%�8��p�
=�@�E�@�W�^�K�i�Ps�B�4�?�"�6�sY��bB
躛��z�uH)<�D
�o:R轟�F��t���(��X��e��d��S�L�y�|��~��G�5�4�K��[B	轴�`����#�"��@��JR
较� ���F�R�-��7�F��&R辐����L���p���Z�:�o�n�W�o��d��}�B辣��&��:R	辪����}��|��g�%�Q�@�LB辶�=�k��A��LR	迀����k�.��:�����t��
�
B迎��v��w�v�h�u�^���oB迓�>B迏��"R迢�I��>��O��J��I�'�l�����F��T��?B	迷�Y�W�N�z��B�1�u�C��;B迳�E��PB迴�{R选�J*�P��\��Y��X��!�
�6���v����K�)����{�K�9��|��A�M�.�@�<��"B逦�J�!� �]�=�/�.�B�W��b�q��@�D�k�T�B進�;�X�.�+�1�NB逷��I�@
	R遛�Z�"��X��7��6��!�� ���U��X��eB遨�W�6��*R遲���@�����.��/�$����1��0�������IB
邀�$��+�h�6�%����M�KR邏�c��)�� ��	����5��4��������������B邡�l�%�a�cR邪�{����'��&��!�� �	��"��%�3�4��"��!�m�h�B!邾�u�B�9�:�W�L�|�)�,��n��a�	�p�[�Z�A�L�f�
�C���!�"B郟��W�P�X�3�h�>��-B
邿���@

�C
R
鄭������J�O�H��>��]��\��[B鄹��I�-��Z�w�a���>�'�=�VB鄺��M
�SB酇��Y	R酩����T��3��2���J�u�|��J��K�O�<��N�^B酿�SR	醅���T��i��h��U��T��7�XB醚�V�ZB醒�B
醐�	��

��z�Q�#R釀��x��z��9��8��=��<������A�+��5�x�A�_��"��3B釜�%��
BD釕�x�_�Z�m�w�j��	�c�E�t	�_�g��:��V�#�	�j&	�7�2#�5�<�@�'�*��l�u�3�&��w�h�A�f���@�!�(�o�w�\�x	�9�D�g�@B釖��
�V
�f*"0�J

R銣�5��V��)�0��v��]"+����EB銱�%�1��:��#��"��u�R	鋀��0��C��B��]��\��7��6��o�uB 鋒��_�`�
�m�k�S�.�-^��5��b�
�Lf
�{�B�h	�y�O
�$�%�%�.��J�G���]�.��bB&鋌�)
:C,;6�J	

E0	�>�m�G$�%�.	 	P�EIB鋞��E
�H
�B	
,�CR鎦�cy��B��7R��b��_��^������QB针��S�|B>鎳� �	�C��7�2�[�nD�y�(�D��e�

�I�+ ��x	�9�H1��>�i�`�F�;�f�D�1�*'�'�P�Q�P�U�V�M��O�,�%�4�8�}�J�B鎴��O�T�X("*�Q

R	钑��a��)�m�~��������EB钝���|�S��dR
钥��a�Q�z�9�&�wB$钱�+�}�0�y�A�H�G�5�t�h�c�V�m�2�E�a��Y�d�
�|�x�#�]�A�R�u��b�6�U��}�L��#�|B钲�%
�D

�BB铇��d
�`R
锠��l��A�6�C��.���L��s�lB锭�f��B�~�Z�g�D�z��s�y�;
�B�b	�'d�0�
B锬�lBM�E�>�H���Y�XB
锳��n	�E	
�RR閉�E��~��[�V����	�}���������vB閘�i�MKg��(�x�o�v�1�.	�C�'�D�3�4�y�&�P�I�&B閤�y�aHB関���L
R
门�J�t����h��{�4�G�,�r���cR	闷�K�n��k��l�!� ��^B阀�D�R�4�z�'�{�F!�I�B�����u�~�<�.�Y�8�(�YB阆�B阇��= R陀�����s��r��;��L3�I�h��r����B限�K�"�E�<R
陛�F��2����������W�
��EB陧�_�'��=����R	陰������8�9�_�h�B�B陽�R隅�L�	��X��%��$��;���8��'��/�RR	隔�o�D��P��-�W�P��0���B隧�b�h��^��R隰�f�p��Z���p�� ��A��@��u�9��,��WB6难�4�A�<�p�M���y�;�*��b�x�{�M�U�o�`�A�l�!�Z�'�k�n�A�z���}�4�X�D�m
�7�:�N�&�,�1	�>��g�T�6�y
���o�x�V��
���,�B5雉��>�0%�g�
�1���c�X�]�@�E�=���B�C�C�>�9
�%�T
��w�H	�2�/�
���Ehg�%�!�{�H �;B隿���H�E
�C$�V$$�V"

R頀��c��5��>��t��5�	�|�S��������/��.B預�A�o�7�X�D��2�#���j��H�O�0�%�(�(���=�qB
頜�I�VB頥��t �](R顯�>�*�!��F��3�9�s�-��p��g��`��i�R颀�a�e�"�@�w�'?�Z���7��@R颎��*��	�9�m��,��g�\����-�	�D��nB颜�~�#�[�{�|�f�}	��O�F�p�m��B7風��.A�U�G�/�.�/�r�L�D�I��j�a�X��/�+�|�D��5��A�9��v��g�&�X��P�+��d�S�b�C�g������v�w��!B颣��.
�R
�D1D�FR饭�V�V��^�/�Z��P���S�F�
B饺������~����i�@�b�WR馄�(�C�#�d�;��>���[��^������eB馒���~��S�B
馥�+��(���}�F�L�%��B�{�n�B馲��A�MR駘�	��4��]��\��9��8��=��<��i�8B騁�B 駭�?�N
�!�E�n��
�7�p@�I�(�s��_�A�V��?�0�c�(�w��U��B騀��}+F�N
R
驪���t��g�(�I�(�5�r�����R�&�QB驹��r�y�w�p�_�ZR骂���f��u�d�k�_�H����
��4B骏�2�$�o�r�y�x�S�(�Y�:�R�enB骯�)�-�CB骙��OR
髏�a�������,�\����!B鬼�FB髟�l��
	�G�{�.�1�
��R�S�`�a�.�
�j	�[�D�=�$B	髠��q
�LR
魀��6��Y��B�w�r��,��+B魏�I�B3魍�e�~��z�W�_�(�I
�]�V�_�Q�x
�W�R	��$�H��38B魐��;
�fD(�[,�RR鰱�26��(��#��"��i��h��o��n��+B	鱼�H�D�]�
�D�9�	�J�z�}�BR鰾�T�F���W�,Aja
�Q�B
$�sB�(�F�I�/�
�j�O�)�Y��	��4
�U�B�^�{��i�`�C�B�E�T�&	
�%��6��C	�g�j�i�8.
56'&���QEB鰿��n�S,�D		�{4�R 
�j�\0�g>"R鸚�!����Q�u�@�9�8�q��t���T��*R鸭�e��*��M��L���%��$��u�tB2鸽�f�p�C�p�[�H�7�O�v�U�S|�H�k�8�_�m��0�7�u�U��/�&�Z�+�5�T�2.��D�T� 
�w���^��u�9�p�>��0��R�,��90
�@��G�y��p�5BY鸾�C�E	��	�P*�G�_�N�=�C�J���Z�
�<�Q�@�E�S�X�a�,�s�N�S�6>C�)��E�D�I�G�t�I	�J�^�H�N�#���E��;�G�H�H�U��v�#��3�>��
�.�� �qB秊��SB,鹀��@�b,(�P�O$�S
�t  ��p�D������_�C𠂇�� ��|T� ��yG��w��.2` ^a
ˊ�3��
��%�@�4�6��Z�a	��@�b‘mo�#�l�z�d�:���{���p	�f
�R�@�^�>��d`s}q{�i>�d�@�#
�7�@6�V�i	�p��W�:�
�@�E��(b�=�P��}�@�P�X�D�T�F�R�J�B�Lb─?�b�@�"��y$�<b辨�Ob摸�b舌�b萤�b至�@b劬���,��[�v�����2��6��Z�M�g����L��#�'�*�-�g�<�>�E�J�L�N�Pb�>呒���
�+�h� �(��7��I��\�M�o�e�m��
�l�x�Z���1�h�D�7�#��H�L�1�\�N�v�-�j�u�
�j�(��v�l�x�}��f�(��?�h�V�Z�]�|�^�
�(�
�1�Z�V�~�r�q�	��6�G�w�r���D�C&�H�	�g�R�\��e�
�s�v�&�+�-�Z�8��|���I�f�!��}���R�N�V��W�V�\�`�f�j�l�p�E�s�v�x�{��	�
�P�`�)�G��t�Q��z�b�
�
�!��C�&�+�4�|��
�/�K�;�>� �,�Q�2�4��%�!�*�H�@�B��c�f�j�F�n
�s��%�O�Z�T�{�z-��c�	��U���	��
�#�'�,�J�0�7�:�@�D�F�K�M�R�H�W�Y�[�^�e
�i�m�p�t�D�x�}����
����	�L�
����!�z�S�J��N�_�d�h�j�l�n�j�7�;���c�,�.�!�;�G�@�F�J�M�O�g�]�x�������%�'�/�F�3�7�>�A�D�
�7�A�F�D�G�K
�Q	�V�Z�}4�y�\��V���F�ob誑�yb飪��\�)b紇���z�b
癆�/%�4��o���r�s���Y�O��h��A�f�F��Lb鯪�;b�Iⅰ	�)�\�<��D�h#�G�M�k�z&�}�d����C�x�L��a���q�$�)��&�O���r���o��V��X���a�q��f�j�Y�_�e�P�l�u�w�{�}	��B������"�&�P�+	�.	�3�6�;�G�A�H�J�O
�S�U�^�@�`�c�e�n�r�t�v�y�E�~��
��
���A�#�%�)�,
�4�7�;�@��C�D�I�O�S�V

�Z�F�h�k�n�q�v�{��	���C�����(�2�4�9�;�B�>�@�B�E�K�N�Q�T�Z�]�b�f�L�n�t�v�}�����O��� �%�'�-�C�/�1�3�5�9�=�B�G	�J�M�A�O�W�a�c�e�Y�q�t�v�x�|�~�	��	�O�
�������"�%�(�G�1�5�8	�@�D	�G�F�S�X�]�b
�e�p�s�u�C�x�}���	������M� 
�$�+
�0�A�4�7�:�A�C
�I�L�Q�U�E�Y�]�`�d�k�X�s	�x�}�I���
����!�G�&�-�0�9�<�O�A�G�J�L�O�H�X�\�a�c�i�n�p
�u�x�K����	������J� �"�&�(�+�.�5�9�O�A�F�H�K�S�U�X�E�^�c�e�j�m�p�s�x�C	�|���
�
���A��"�'�+�.�7�:�?�C�G�C�J�L�O�S�U�W�[�b�d�f�i�C�k�p�t�w�z�|�	����E������#�&�(�+�@�.�0�4�9�@�H�L�O�@�V�X�_�a�d�f�i�k�t�x�z�|�C�����
�����	��@�'�+�3�5�:�>�C�K�N�H�U�X�_�f�n�r�v�G�������� �"�$�@�&�)�+�1�5�8�:�A�C�N�J�M�P�S�Y�[�`�f�i�K�n�p�t�w�z�~�����B������%�'�*�7�K�<�>�C�G�J�L�O�R�[�`�B�c�g	�i�s�w�~�����
�A��#
�*�5�7�?�A�F�G�M�O�V�Y�]�_�b�g�j�o�A�s�{������A
�$�/�5�7�9�>�A�P�G�I�O�R�V�Y�C�\�_�c�g�u�z�C�}�
��
����L�����"�%�+�0�7�D�<�A�D�F	�I�M�S�Y�E�_�a�g�m�s�z�~�����@�
������!�(�*�/�C�8�?�A�D�K
�N�Z�\
�`�E�m�o����
��@��#�)�.�5�7�:�<�>�L�G�J�L
�O�V�Z�]�_�a�F�d�j�r�t�w�~�O��	����� �B�%�.�4�6�:�=�C�J�L�G�O�S
�Z�\�_�d	�j�n�A�q�w�~���	���M���� �&�(�0�2�@�4�7�9�<�?�B�D�F�H�K�M�P�R��L�U�X�Z�]�`�b�e�h�m�r�E�u�}�����
���J�����#�(�*�-�3�5�@�8�=�@�E�H
�M�S�U�B�Y�^�a�f�j�l�o�r�z�|�B����������"�@�*�,�3�7�<�>�D�H�K�N�B�U�W�[�`�i�l�o�x�L�}�	����G����"�$�+�.�0�2�4�E�7�:�>�F�J�L�N�R�U�G�Z�a�c�f�k�G�p�t�x�z�~����
��C�����&�+�-�0�2�E�7�<�@�L�N�J�T�X�Z�^�a�d�i�n�p�v�F�{�}���������E�"�%�,�/�3�;�?�B�E�@�I�M�V�X�\�_�_�k�q�t	�{����G�	�
�����$�*�B�,�1�3�5�8
�>�A�C�G	�M�@�T�V�Y�\
�d�g
�m�t�v�G�z����
�����D���$�'�*�.�3�9�;�H�C�J�L�N�P�T�X�]�a�c�B�g�j�l�n�r�v�{�������B���'�*�,�1�3�5�9�;�E�>�@�E�G�K�P�Y�[�_�a�D�d�g�k�m�s�v�x�}�F���	�
����E	�(�2�8�:�=�?�B�D�K�S�U�Z�]�d�k�n�Z	�v��
�G��� �#�(�*�-�2	�5�C�@�H�L�O�S�U�X�_�b�f�B�j�m�t����
���C���!�)�+�-�0�2�5�8�:�>�@�J�F�I�O�Q	�S�_�a�c�e�h�C�l�u�x�~�����F���!�)�,�2�7�<�?�A�C�D�H�J�L�P�X�\
�^�i�p�r�A�u�x�|����	�
������@�"�%�4�;�D�J�N�P�@�T�X�]�a�c�f�n	�p�z��C����
�
�	���$�)�N�0�4�8�:�<�>�C�E�H�J�D�N�P�V
�Z�_�g�j�n�q�C�u�z���
��C����� �"�%�-�/�M�6�8�:�<�A�H�J�H�O�S�W�Z�\�_�c�f�T�m�o�q�u�x�}����
�
�\���!�#�%�(�,�.�0�@�3�5�8�;�>�F�H�J�L�N�B�T�W�Z�^�c�g�j�n�s�@�v�{���������B���%�+�-
�/�:
�<�B�J�N�P�U�[�_�a�d�f	�m�p�H�u
�|�~����
��D������#�)�/�2�D�8�<�@�C�E�H�K�P�X�\�B�^�b�d�l�p�s�u�x�|����D�	��������%�'��F�2�7�:�>�A	�F�H�K�S�U�X�B�]�`�g�j�m�o�s�w�y�}��E��
�
����	�'�+�D�-�2�6�?�B�I�M�P�S�@�U�^�c�g�m�q�y�}�@������"�%�(�-�/�@�1�5�:�>�A�C�H�O�R�X�[�F�b�f�k�o�r�t�v�z����D���	�	�%�1�4�K�7�>�A�E
�M�Q�U�W�Y�\�E�^�`�g�i�l�t�v�z���E�	�����"	�$�&�D�*�0�3�8�;�=�?�C�E�I�D�L�N�U�X�\�d�n�L�t�w�{�}����
������C� �%�(�/�4�:�=
�A�B�M�Q�S�W�`�f�m�o�t�C�w��������F�%�*
�-�9�=�?�E�N�P�@	�R�\�`�c
�m�t�y�A������� �C�#�'�)�,�0�4�8�:�=�F�A�K�N�P�S�Z	�^�d�f�D�i	�l�n
�u�w
�|�����G������� �"�I�&�)�+�3�7�<�A�E�D�H�J�L�O�Q�S�\�e�h�l�A�p�r�v�z�|
��	��F��
��"�'�+�/�6�D�:�=�D�L�N�R�T�`�D�c�f�l�n�s�y	�~���E�	���� �)�,�/�F�3�9�;�C�H�J�L�O�J�S�W�^�`�e�k�o�v�z�@�}������
���������J��!��$��'��+��.��1��9��<��?��A��D��F��H��J�B��M��P��X��\��a��e��k��n��r��t�H��{����	��	���������B��#��%��)	��+
��5��@��B��G�K��O��Q��U��X��Z��\��b��j��q��t�D��w��y��~��������	��
�����J�� ��%��*��,��1��:��A��E��I�J��P��T��W��Y��]
��c��g��i��l�A��p	��v��������	���������C��	�� ��$��&��*��-��3��6�H��>��E��L��O��S��Y��[��`�C��c��g��m��o��s��w��z��}�������B��	���������� ��"��$��'��*��-�E��3��9��=��C��L��N��P��\�G��a
��i��w��y��|�������@��
������ ��#��&��*��-��/��4�F��8	��>��F��H��N
��Y�P��_��a
��e��h��l��o��r��x�H��}��������������!�D��)��,��1��4��=��@��B
��G�H��K��O��Q��S��Y��[��`��c��e��j��m�@��q��y��}����
��
���Q��	����'��+��.��1��8��:��?�D��B��D��L��Q��S��W�~��b�@��g��j��r��v��{�������D��
��������"��&��,��1�F��5��8��>��B��D��J��M��O��U��[��G��_��c
��g��i��m
��q��u�E��y��{��}��	��	�������D��
��"��(��.��4��6�M��:��?	��G��I��Q�@��S��U��W��Y��^��`��m��p��t�B��y������
�����K������#��*��,��.��1��9�J��@��B��F��O��S��U��Z��_�T��h��l	��t��|�B��������
���������� ��#�C��%��(��.��2��6��:�K��@��B��G
��K��N��R��T��X�J��]��a��d	��f��p��w�A��z��|�����������@��������!��$��'��,��0��6�B��8��<��?��A��G��K��N��Q��U��Z��]�H��b��e��k��n��t��w��{��~�����@������
��������'�@��,��.��4��8��;��=��A��N��Q�J��V��X��]��`��b��i��l
��r�D��~��
��������������'�D��+��.��1��6��:��>��A��C��H��K�B��N	��U��X��Z��`��b��h��k��r�B��x���������������� �A��#��'��*��/��3��6��9��<��A	��D��P�A��R��_��a��g��i��m��q��s�J��z������
�����������A��!��&��(��*��.��0��4��8��<��?�F��D��F��H
��Q��T��W��[��]��_��a��d�B��f��m��p��w��}�������E��������������$��&��+��.��2��7�A��9��?��A��C��J��N��Q��W��Y��\��_�B��b��f��i��p��t��x�������A������������"��(��-�I��2��4��7��;��>��C��I��P��S��X��\�C��`��b��d��g��j��l��o��q��s��{��~�D��������	���������� 	��'��)�B��.��0
��2��@��F��J��N��P��R��Z�E��_��a��e��h��k��n��v��z��~���������V��
����������!��&�D��-
��4��8��<��@��D��F��I��L��O�D��T��Y��a��c��e��g��i��k
��p��s��u�A��w��|��~�����������������A������%��(��,��1��5��8�R��@��B��F
��L��O��T��V��[��`�B��e��n��r��z������	�������A������!��%���4�B��=��A��C��F��J��N��T��[��b��f�@��k��m��o��s��z��}�����\����������%�F��+��/	��4��9�v��>��C��G��I��K�B��T��V��Z��_��a��d��i��l��p��x�H��}�����������������@����$��(��*��-��5��7	��?��A��D��F�E��I��K��O��Q��T��]��_��a��g��k�C��n��u��|��~�����������A������!��'��*��.��4��:�H��A��G��O��T��Y��[��_��b��f��h�B��j��m��o��r	��w���	��
�������L����$��'��-��/��1��4��G��6��=��?��E��G��J�J��P��R��W��]��_��b�S��j��m��v��y��{���D����	��������������"��$�C��)��-��/��9��;��?��B��E
��M��O�@��T��W
��[��_��a��c��e��k�C��n	��v��x��{��~����	��
���Z������#
��&��3�B��8��:��=��A��C��K��P��W��Y��[�D��]��a��c��f��i��m��o��q��t��w��|�����B��	������������$��&��-�I��4��7��9	��;��F��K��M��R��U�B��Y��]��e��g��i��m��q��s��u��w��z�F��������
����������!��%��(�B��,��/��1��3��7��>��G��M��P��R�A��X��[��`��c��g	��l��o��}�@������
������������$��(��+�B��.��2��8
��>��I��L��R��U��Z�D��\���f���w��y�E������	�����������G��"��%��)��,��/��5��7��9�{��C��J��M��O�S��W��Y��\��^
��c��f	��j��n�@��p��x		��}���R��������������"��&��*��,��0�D��3��6��9��<��>��D��L��N��P��S��X�C��\��_��b��k��m��o��r��t��v�����@����
������ ��"��$��*��-��5�@��7��:��<��?��E��J��M��U��[�U��h��k��n��r��u��{��}�����@�������������� ��#��&�z��/��6��8��=�@��@��F��K��N��P��S	��Y��\�D��d��h��l��n��r��t��v��x��~������
�D���������� ��&�H��*
��/��:��<��?
��B�B
��N��]	��c��m��p�C��~����������#��(�H��.��2��5��;��=��@��C�c��K��M��Q��W��]�C��`��b��g
��k��r��v��{���@��	������������!��&��+��3�J��8��<��>��B��V��Z��\�D	��f��q��x
��z���B������'��,��0��3��7��:�C��C��K��N��Q��]��`��b��e��l�B��o��t��v������	�����D�� ��"��'$��0�@��2��5��7��9��;��=��?��B��D��K�@��T
��[��f��m��p��s	��y���A��
�������� ��)��+��-�@��:��B��K	��P��Z��]��b��f��l�E
��q��	����
�������@�� ��,��4!��>�U��C
��I��N��R�G��V��X��\��^��d��g��m��r��t��y��}���E�������������������� �I��$��0��5��<��E��G�I��R��V��[��_��c��e��h��j��n��s��w�B��~����������
��5��o�K�a*���@$��J��h����
�������_	�+�5
�9�Gb�3⺂������"	��$��.��0��<��>��A��E��G��J�@	��N(��X����{��~�o���s���H?���@��A*��H��s�@?���@?��?�@?���@?��?�@�� ���@?��>�@
��~
��$���@?��<�@?��|�@?��<�@?��|�@?��<�@?��|�@?��<�@?��|�@?��<�@?��|�@?��<�@��|&���@-��;��i�@?��z�@��:
��I��W�@?��w�@2��7��j�@?��v�@?��6�@?��v�@
��60��D�@?��u�@?��5�@?��u�@-��5��c�@?��t�@��4��T�@?��s�@?��3�@?��s�@?��3�@?��s�@?��3�@?��s�@?��3�@?��s�@?��3�@?��s�@?��3��@?��s�@��3(��I�@?��r�@?��2�@?��r�@��2��Q�@?��q�@?��1�@?��q�@?��1�@?��q�@?��1�@6��q��(�@?��0�@+��p��
�� �@��.!��K�@?��m�@?��-�@?��m�@��-(��C�@?��l�@?��,�@?��l�@?��,�@?��l�@��,��8��L�@?��j�@?��*�@"��j��
���@;��(��d�@��g1��t�@?��&�@?��f�@?��&�@?��f�@?��&�@?��f�@��&1��-��_��a�@��c��f��z��}���@?���@?��[�@?���@?��[�@?���@?��[�@?���@?��[�@?���@?��[�@6����R�@��Z��y�@?���@��U%��h�@?���@-��N��|��J?���@?��H�@?���@?��H�@?���@?��H�@?���@?��H�@?���@?��H�@?���@?��H�@?���@?��H�@?���@?��H�@?���@?��H�@��/���@��EPK
!<�P�A��1chrome/pdfjs/content/web/cmaps/UniGB-UTF8-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�UniGB-UTF8-HB—�V��Z1�6��m�1
�:�3�2�
�[	��b〈�H�R�F�P��p�D�@PK
!<��_c_c2chrome/pdfjs/content/web/cmaps/UniJIS-UCS2-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE����!a ;a!>"eQ�k]J�X��_?V3�q�����
u�� �{�!~�$��*�7��;��A��G�8��XA1�
	
�A��?EA&�<[��NcP]-(
S
	=A'�<a_��b����	��	@�
a	a�v/�s���7�)�#�*�Ka��:�@	�?A
Q�J�0�G�"8��F.�A��5A[�<a �x� y�w��	��Dn�^�Eha p�!�"	�(a <�O�PA ݆%��E�<A ��
\�3�/�?
�x�&A!�O	�P%a!��a�`�ca!S��	��!	��j�oD�va!��)�7�,�*A!҅p+	'c��h�5A!�u�Q'�
A!��.
4!�;4a"'�m�l�k�A
"-��w�~�O�H��2��=
�|a"f�=�{�Y�i�gA"��uI�3"�2A"��N.�A
"��_D�B�Oa$`��K�7a$t��~�04� e�;
�<�>�=�7�:�9�E
�&�4�3�2�1�0�/�.�5a$��c�IA%��YA%��O	�A%��O
U	�9a%���R�Qa%��?�B�A�
�x
�a%��^+�A&@�BA&a�QA&�@a&j���a'v�^"�a&h�B�D���Q;�c�,�a� ����	A	.��q�h�i� N�.:�K�A.��j�M�A.��a.��aA.��TA	.��M�Y����_(�A�KA.����U�4�=�<a.��uA.���,�d�[�5�J��|�O�r�]A.��.�y�.�{�A.�
	�@�7�Q/�0��i�� �w��w�8�5�R�t
�{�v�\a/�MQ/�S��h�>�
�c�r�/�qa/!�:Q�/#�&�?�
�C�Z��c��c�T�c{�i�7�&&�S�����o���(�_�4�u��w��$��n��B�S�^�n�}�H�a�$�/�7�R�4��	�w�>� .�Y�O�@�4�<�{�0��P��S�_�(��VKp��A��X��]�X�`�-�t�G�W��-�e�"��N�O�x���e?�K�j8�]�/���8�G�U�F��
��a��h�(�.��\�z(����$��X��[�o�p��%�t�:�+�T�}�R�a� �G���6]�C�3�!�Y�"�o5�n a/��Q/��N���<�7�J�a��r��C�8��w��z�a0�y��	�*�_�d�$�$R�J��
U��~��a/��~a/��!�0.�t�8�9�z�:�L�y]�b�y�%	����BQ23�Op}��$
lya2�	�]�hQ2��g�

�D�5�,�=��2
���^q2��v�{�z�=�Z�]�_�c�Y�d�[�Xa2�.�-�p�bQ3�j�r�[�Z�q3�q�"�i�g�x�h�z�|�s��f�k���,�&��t�.q 3-���q����(���n��o��� ��m�#�r�'�!�/�(�$��*�)�.�2�/�3�la3|�G�F�E�4�1�7a
3{��v�_��c�e�X�Z�z�T�U�{�Va3q�U�X
�YA3Ļ6A	3��|	�=�L�A�&�%A3��Pa3��]AN�0��+A3��KA4��0!�`�vR#C�1�h�N�	T� ��(.e�y��jO�V�i�a�no�`�_.�^�xF��&�.�jH�9aN�XAN�*�9�!��#��D�"�U�t�^�i��O�2�w�l�i�T�3AN(�3AN�Z�aN/�\A	N1�~����:��	�(�7�XaN@�^A$NB��/�u�ve�	�R�B�g�f��N�� �z�3�
�i�Z�j��9�{�:�	��~�W�d�Q���}ANU�PAND�`�e�d$
aN���b��&�+� ��APN��~�
������I�@�G�F�5�K��r�#�y�w��-�F�7��9�z�P�A�,��g�j���Y��M�'����L�x��2�o�=^��X���'�\�u�C�R�A�_�D�u�j���b
�U�Q��0�AN�45A
N��e��
	%

aO}�qA%O��i��u�K�>����3	�$�[��q���/�5�l�C�p�(�C�F���{�XAO��;.!"�%AO��s&�raO��8A
O�.�.��B�0����~��c�vaP�wA
P�?�i�9�2�Z�s�a�H�%� �iAP�BAP�G
�b
QP!�B�z��i�d�o��f��
�I�T�;��(A%P6�^�N	��y��X��q��A�(�t�'�F���e�O����?�n�R	�E�$�q��)AP@�?
)#AP;�$aP��XAP͙��G�>�@�/�.����AP�HAP���V�W�RaQ�eAQ�/
�`�NA
Q	�d
qt�9U�AQ�
aQ?�pAQA�8QQC�i�1�h�d�;�^�C�:��d�?�)AQP�G�T�9��B�?�AAQd�JAQ_�
�aQi�wAQk�@�)�h�.�7�Z�m�[�
�M�`��s�x�#����#�"AQ��[AQy�yaQ��A
Q��Q�3�$�Q��T���w�v�saQ���Y�
�A!Q��
�o�t�1�j�D���Y�X�/��[�~��+�*��5�R�p�3�2���s���z�4�F�}AQ��L�a�`AQ���D�EaR�AR���=�}�A�j���C��:���y�x�Q�P�)�?�I�.��-��B�;�QAR�NAR�5aRK�%A#RM�2�`�	�]�`�9��(��y�7�>�a�R�3�8��&�x�}�r�3�U�U�p�{�$�7AR]�^>�`	ARa�,aR��7AR��-F�P���Y�Z�o�Y�n�O�$�i�h�/�0�m�h���i�`�o�n�3
�8�|AR��}�Y�G�FAR���e�daR��EA
R���1�t�P�?�K�b�
��aS
�AS
�J�!�x��B�g�#�S�Z�A�@�{�m�o�Z�x��
AS$�UAS>�Q	SE�V��m��
����
A<SQ�p�9r�]�J�/�h�.�<��� �+�^�<�;�^��S�N�{�|�I�H��U�
�p��H�J�y�x�A�n�P��{�c�8���UN{�<�&�.��Z�Eq�5ASr�V *ASa�!
�[�4!�$.QS�p�#�w�P�H�W��E��#AS��&��1�R�>�2�!�Q
T�y�/�0�i���/�^�E�AT��l�C�8���j��Y�CaT,�vA<T.�y��b�*�t	�;�:�W��}�]�V�t�
��/�B�'��� �v�W��.	@�1��|�=�8�]�$�0	
�5�@�=�LAT��\AT3�@
�0��

aT��AT�
���m�n����J���o��5�}�:���+�2�7�.�m�=AT��^AU�4	aUV�*�(�.�Q�8A*Uc�%	����z�o�t�
��O�f	�O�`�M�c~�&��

�4pi
�M�P�1�WAU��_AU��:yaV�=AV�E�o�w�h
�Y�l�c�X	�g	�6� 
�k�v�S�V	AVS�A	V ��=	aVq�GAVt�~�#�<��k�l�Y�\AV��fAVv�3�UaV��KA.V��_�_�)��Q�o�

�R�1�� ��e��V�}�~�O��d�K�J��*�y�rAVʼZA	V��M%
�%�$aW3�U�{A W;�~�W��6�.��y��_�6����;�4�c�b�%�1�V�y�AWY�`FAWL�W#aW��A	Wԣ�K��6�����qaW��^ASW��e�Z�O�\�V�{�y�@�D	�c�b�)�7�O�~�_�Z�Y��%�.�c�L�?�B�54�\�I�a�@��.�V	����g�v
�S�s�D��M�L�U�P
�#�� �O��>�,�c�d�)�t�|�:�)�_�P�G�|AX/�21�(<�>AW��`	I($aX��7AY�:�3�2�k�j�-�1����:�
o�|�q�R�#�PAY�iAY�laY,�@A:Y.���6�]�\��P����c�^�c�d�x�O�T�[�\�y�z�Y�T���&�t�3�2�u�4�T��|��8�i�z�O�a��E�"��3�.��s�A�|�$�uAYS�j@AY/�n	�'�\aY��[�Y�s�tA	Y�Z� �� �B�_��?AY�X��]�z�/�2�gAY��-��m�laZ5�fAZ<��|�!�W�d�x�g�%�;�>�1�$AZ@�c-
!A
ZG�y

	,aZ��uA[	�.�	�G�J�	�#�$
�A�B���w�A[V�pA[�a[Z�A[\�(�_�o�t��DA	[_�^�NNOA[|�a[~�	AH[�������$�[�h�O�J�k�}�p�>��,�b�/��D�O�R�z�5��W�����C�;���G�t��:���e��J�O��B�W�M��o�X�+�D�)�(�G�B�/�}�L���/�/�L�A[��qA[���ta\�!A\	�����1�k��'�s��u�t�S�ZA\�uA\�*�?	�a\8�(Ad\:��R�2�e�b�i�/�P�C�B�k�`�o�`��s�n�w�E�h�#�J���	�[�d�
��� �;�2�
��w��G�>�[�_��:��K�F�O�J	�� �_�h
�s�r	�!��w�r	�'�>�e�d�u�H8		A	\[��C�P:1A\c�N�O
�N/G�a]��~A#]��.�n���5�K��l�U�X�Y�e�D�	�W�^�y�s�j�e�+�x�7�.�T�q�4��A]��A]���[�Z�}�|a^�#A^�=��=�
���o�A^�rA^�%a^6�uA^8�W��q�0 �;�"A
^@�y
A^X�'a^k�(�o��e�A^x�>��v�7�C���2�2�31�
��-�g�	�w�C�g���`�o�^A^z� A^��0�ra^��,�A
^��&��4��G��H���La^����2����
�A^����5A
^����]�\��.�`�A_�0Q	_	����M��L��+�`�&A_�w���F�Y�i�a_�2A_��^�$�D
�3�^�5�z��3�B��~�wA_!��!A_"�4
�=�<a_V�,A7_Y�. �1��;�X�O�t�d�)�i��i���-�`�F�7�6���-�R�h�e�}��F�G�F�!��2�e�i�-�L�*�w�h�F�W�P�H�;A_g�OA_X�:�#���a_��hAG_܊ �J�?�6��|c�?�:�m�v

�m�t��v�C���&�#�4���
�+�2�S�J
�7�8�g�0��>!�!�F�#�o�A�>�o�:��(�~	���I�2�kA_��~'A	_��D-a`��lA#`��i�!�p�G�m�f����"Z����&�?�>�	�z�
�[�B�/�4�+��K�F�>�S�FA`��
A`��Ma`��~A
`��R���^�#�&
�kaa
���
�SAa�|�%���"���/�e�l��J�QAa �Aa�VaaX�A=a]� �i�q�R����5�6��
��v�e��S��P	�Q�i�a�
�7�A���z�5�.�y�z�i�n�=�PAa��Aa|�Wab�BAb
�:��w��Y*���X�,�#�"�m�l�o�nAb�	�[Ab�]ab2�MA!b4�7�m�V��m�4�w�B�A�3�$�S��M��)�
�(�3�<�y����r�=�AbH�Ab6�=�_� .�=ab��ZACb��_�w�n�/�x�T�K�m�J�r�Q�{�*�|����s�d	�	�z�M�F�-���5�p�A��A�y�M
<�B�	�=�D�
�k�h�f�3�N���Y�L�U�q<�
���
�"�2��1Ab��Ab��d7

�c�bacB�jAjcI�
�#�~	�
�1�w
�X�#���U�V�?�<L�,�?�w�<�G�8�s�d��y��#�k�h���s�D�E�j��A�z��2�I�L�g�p�5�I�|�q6�{��2�+�W�
�9�>�q�|
=	4��c�X�q��>�8	�}�t����)��V	�C�:�q�7�E�l��M��F�P�/�Ac���O<,� <Ace�l
2
�1	�N�`1
ad��|A	d��4�n	����Ad��Ad��ad��0Ad�!�"��v�1�:�E�@MPAB�Ae"�Ad��~ae4�BAe6�E����s�#�
��� ���?�@�/��V�#�4�(�w��\�|AeN�AeG�ae��QAe���=�&�m�`E�!����"�H�@��h�9�&��i� �,�E�d�9A
e���	Ae��<ae��`Ae�7�8�A�]�H�n�{�e�Z���;�6�����P�e��,�p�-�Y�j�h�e��L�<Af�	�[Ae���9af5�hA
f<�)�>5�G��D�a�Z�_Af;��<AfL�af[�
A	f]�q��2��3�����V�=afg�sA-fi�i�R�g�,�C�c�T�#�p�c�Z�� �{�f�q�d�Q�R^�2���!� �%��2��1�@�Afs�"%Afk�,!�{�z#Q
f���'�P��m�	�[�p�Ag�K��$��=�<�Y�n�h�w�S�K��3Ag�(Ag�
ag&�Ag(�fm5�/�F�N����J�1���'�"�X�{�c�A
g.�Ag3��A�@agc�ALge�R�$�3�|�,�y�x�}�&�L�}�b�"�9�$
�g�N�~�
��>��t�]�X�A�V�A�:

��P�N�K�F�%��m�F�#�H�P�6��2�%�u�d�}��n�o�T}�1�v�_�g�F�Y�bAgf�*T@�OA
gn�q�P4$2ah0�#AJh2�A�U�]�d�i�,�~�	5�
�*�g�j�{x��X�>�9�j�5��I�^�?�& !�}��u��%�&�}�~	�[�,�E��H�w��6�J�)�"�[�]�p�=��C�N�g�f�i��
�I�ZAhD�/
;�!9�$�Ah[�%	 ah��0A9h�W�&�e�x�D��|�+�2�q�t�d�M�^�C�
�8+& �5�|,�
��� ��(�}�h�w�r���q�t�)�\�L�u�b�=�=�Aih�2Ah��2!ai�:Ai���c��1�*�}��1�,8=$��(�}�LAi��4.�Ai��<	ai��?A+i؞7�?�n�i��l
�A�J
�k�n�d�{�d�:�-�g�2�,�W��'�+�6��Ai��5MA	i��o�
�
	ajP�HAjX�f��u�N� 
�i��j�=�8
�Ajk�7
AjT�4�U'aj��O�<�>A:j��K	�o�j/0
�{�~�3�(��"�c�bad�y�z�1���S
�H���l�)�B��L�P�G�H�m�V�� ��n�O��rAj��;Aj��R
1	�!��akx�d�.�fAk���=�!�
�,Ak��iAk��[ak��oAk��q��D�X����u�@�A�r�y�2�o�2�(��4�#�Ak��=Ak��\
�1�q� al�AJl�>��3�~��X��
�!�<��X��E�^�v�m��l�Q�K�(�'����]�(�2�3��z�@��c� �j��P� �(��4�X
�'�H�H���&�4����%��B�i�lAl?�>SAl3�`	
�?%�>	al��mA^l� �O�6�Q��h�%��'��(��g�D�(�k�r�E�@�Q�l�5�x�2�e�\�)�j�
�{�p�l��|�`��\�2
��sX�
�;��g{�l
�A�J�]�^�=�6
��"
��~��3��/�2�%	�5�'�r�T��]�U�,*�1��^A	m�Cj""Al��o�
�(�5��v�w�Q�P-�mQ	n�X��t��f��U�S�@�rA4n)�9�6��~�w��E�>�/�P��2�q�t�C�F
�?���f	�=�R�{�t�n�W�G�4�{�t
�a�T�y�z
�+��+�X�g�8An9�LbAn4�
�H�sY�e�dan��jA9nݖL%
�p
�)�41��0��E��.�e�8
��e��+���
�

�c�~��S�7�R�D��v��bM

��r�	�$AoQ�`6�`An�8)�E&
�%�$ao��A.o���1�"�5�>�f�9�D
�5�B�.�1�r��
+2	�C�P�y�z�o�M�	�B
uv�_Ao��R?�_�^�	� Ao��#?
	ap]�Apc�/�?�:�W�t����C�A�H�y�|�w�;
�>�v�_Ap��W%�WApd�!ap��"Ap٫;�E�V
�_�b�{�v�{�%�
�Aq�Z
 �{Ap��$*aqF�\A>qI�C��Q�F�q�j�[�P�'�h�B��@�V���� 	�e�d�S�T�O�u�D�o�f�	�|���Y�G� �Y��R#�W�V
�K�L�1��DAq\�[<�}'�<A
qJ�)
 )
ar=�X�P�"�[�
�Iar;�d�f�h�iarA�6�7�8A&rX�j��>�!��J�5�
�?��!�	�(�Q�R
	�3�2�7��0�1�,�	�
�i�h�3�@�gAr��aAr\�:0�%�$ar��|A
r�{�E��_	��+�&����I�as�@As
��C�D�s�x�)�\��ZAs$�cAs(�Bas6��
��D��0�Asc�M�E�A	sW��"�'Asl�Eas��H�p�D�&	�J�b�KAs���$�L����*�t�����|4+�c�e�>�q As��fAs��N�`�a	�O�N	
�H�I
at�ZA
t!�:��I��
��w��^�]�^�]at/�_At2�#����!�F	��_�t�i�`�����Atb�q�yAt9�b
at��mAt��1)(�g� �D�3�4���3�2At��rA
t��p		�Wat��>ABt�=�q�v
�w�z��.�8�&�]�)�[�^�!��0��<��Z�
�~��E�H����c�>>�4�4�q�!�h�>�l�E�#�f�W�Z�u�|�~�-�(�5�@Au�t-�?�Au"�^+�:�|�U�Va	u��\�`�_��D�C��Y�Au��x��q�1���?�g�"���}Au��b
	
Au��/�cau��a
u��{�z�w�u�x��}�~��au���	�
��	�
Av$�	
�w�zAv&�-Av-�
avL�avG�		�
��
����avK��S�Avl��+�s��c�z�f�o�?� �
�O�PAv��vAvm�av��}a	v��!�y�w�x�z	�#�$��%av��Av��|���;�`��^��P	�x��]��
�<��2�N�pAv­(
�%�&Av��aw:�K��#aw%�9�6
�<
�|�>�?�Baw4��%��Awa�-�\�L��|
�[�{�Z�z�{�zAwb�EAwj�U�
��aw��PAw��O	�'�&�7�6��&�{�*�>�O��J�[	�Z��T
�0���=Ax!�~Aw��#	ax-�(A)x2�R�#�a�G�b�[
��G�y�6�T���#�&�#��G�Y��6�X��=�V�y��9�:
��|AxN�A	x0�*��:�!5)ax��2Ax�t�K�F�	����g�h=:
�Q�8�g�~�c�g3�^�X�J�{�rAy0�A	x��1
5ay[�<Ay]�U�`�t�?�W�p�O	�T�$*��$�Y��k��:
�,�i�h�[�ZAy���uAy��>
ay��Ay��Q�}��W�	��S�Tm�q�.�1�.�S
�-�D
� �S��IAyɮAy��B(�%�$az�Az��v�i�O	�>��fV�b���	��A	z�
Az�Haza�$Azc�_�e�$��X�d��C�?
�t�g�k
�w�t
�YAzi�&
Azm�O
'az��TA
z��6�E�@���m�nAz��	Az��Vaz��>�}�@��7�AAz�a�\�d�;�:���Q�0�7�y�7���
�Az��
A{�Wa{�KA	{�%�S�F�j��;�h�M�A{�M	
A{'�Y	Q
{K�R�����'��k�0��N��SA	{V�f���f��	��������a{p�\An{t�Z
�g�� �}��U�.	�;�j�5�(�p�o�g�L�����"
��Z�.�-�<	��

�{�r�u�v����#�&/.�y�x�K�Qp�(�!�-��0�5�#�E�B�'�'�*�s�6�B��A{��
�%A{r�`'1


�C@�Ba|��rA
|ʏ�f�v��/�+�l�;��C��t�KA|ү&A|��t
a}�xA5}
�3���d�q��g�I�0�c��T�Q�L����x��=�Z�_�`��5�.�	��.�R
��,��t�S�t�/��{�r���k��Y�TA}H�A	}�{	a}��AQ}��F�9�V�h�1�H	
�S��P�s��a�2��"��(�l�u�X�#�P�?�M�k�`��	�k�;��k�r�/�*��d�2
	�c�w�Z
�%��{�	
5�3�N���w��j��tA}��{A}��y�x
&�Oa~Y�uA~m�(�O�A~]�r�8�Q�|
�	A~b�a~��5�&�a~���'��	�����
���
����������a~�����+�
��
�A"j�Q�Q�T�d���z�4�M�L�!��G�H�!� �?�6���?�6A��A��-a��/A̞L�-
�p
�Aʰ1	A�a�� A�	
���<�}�@W�6�'�A�7
A��"��a��>A�3�V�D�y�=�R�1A
��@A�$�&#Q	�o�F���6��;���>��9�IA�~�^�K�Q�$�O	��@�%�z�Z��I�$�#�i�T�l�Y�4��'A
�y�QA���-a���0A�ޜT����=�~�y��n�~�e���l��O�e�v�k�,A�ٰ_	
 A��2�5�4��a�e�tA�k��%��F��,�3
�h��
�.
�s�v��A�n�s		
A���7/a���A	��	�A�T�k��>��8��Ja����S��A��5�r�M�%�"�7�-��	��W
�)�v�o��q�f
�\�A��A��<a�j�-�!��0�m��1A���6
�D�M�Y�#��u�i�|�E�?�U�h��<�k�p�?	�n�W� �f�m�P�	��A�~�3
	�(�-A�y�u�7a��UA��o�

�V�
�S�����5W"�:�J�@A�#�]

��m�p�
;4	
*A�C�I
0a���QA-�ŔA�C�h�u�@�e�	�d�I�^� ���e�x��v3d�y�[�4�W�	�|�Z�i�v�[�	�C��W�I��(�N�D�U�B�C�^�1�\�A;���p�T�a	
�f�W��!
%	�*�!	
-�4�!	
�,�C
NA��S$8+1-a��A��X�0�_�~� �M�_���h��w�T�s�1�P�+A�� 
�~�w�*�#�h�i�r�CJ�v�_	
A�4�_)0Q	���1�y�*��r��F��?��LA���t�+�I�J
�I�pW%�( �+�;�J��d�/�
�J�)�HA���>?@�m��5�6

	A���b%�G�H(��
�Ka
���Y�l�m�U��n
�V�x�o�WA�ǒ�t�'�

�`�V�	�#�e�X��>,�Y�(	�_�)�d�
�A1���b
	

				
 


				A���q
	2a���|A���PA���
 
��A���~ �va���A����&D�J�n�)�h�	�G��1����x�/j�b
S�Z�y�A+���+�8��@�=��	��

			A��93a���K�>�w	�j��4�0�ka���D�F�H�I�K
�Pa���
�	����A��@� �a;�\�S�A
��Q�.�7A��a���(�a�W�X��YA�V�(�\�F�L�<��q�;�3�d�z��d�+�A�6�^
�^�[A�0�
<�[a���y�/���{�|A-��r�v�u�D�U�\�>�k��!�r�}�,�/�g�`�k��@�Y�)�f�?�X�m��p��n����#�V�q�:�[�>�s�6�>���A��~
�H�M�@	�A		�4�9	A	���G�F+0a���A����4�i�!�L�O�d�
�K�/�$�`�&�+	��d�	�~��H�0�o�-�,	�
�=��0�
�7�A"���+�-�,���	��	
A���8I�Z*a�C�)A�I�=�C�D�K�N�'�"�M���F�G�F�w��h�A�S�0+A�T�+a���J�L�MA	�7�i�(�8�S�[��
�-�A�:�O	A���,�(a���_A���a�1�n�.��S��Q
���A�E���/�R�V�g�na���hA���H� �5�
��z��B�a��Y�"�O�J�b�kd�
�(�u�t�_�<A
���k	
 �A��2a���r�j�q�5�tA���CX�.����H�)
��+�>�<
� )�y�R�]A��w�r�qP�b
�c A
��6Ma���O�S�+�C�a��������a���@A�D��;���i�S�8�q�|�0�J�!�1�a��4
�P�U���E�<�A�@�]�AG��

�`�a�j�k
A� �A* 	��
$a�b�_A����m�8�'�c�A���b	�M�LA���Na���OA���%�k�A�,�7�2��S�7��
�Z�<��#�bA���g"A��Q�q�pa���pA���e�K���#�"�G�vd�O�l�k��N�/�4
��� �;�Z�]��J�O�`��
�
A��o!

A��U)a�O�~A5�S��e�u�j��p�W�@�=�Y�o�V�z�	�T��m�"�S�D�/
�*�[�x�h�-�6�i�`�Y�,/e��Wi��E�#�&
�j�1�+0��B�0�M��8��A0�V��Z�[�d�e�-�jH%�>�G�>�?
�>�=
�ZA���X""
)	a
���m�.�a�o�1�0�b�2�p�3A�ƚd�C��<�;�T�2�u���t�TA�ɶ5A���@a���D��t�B�q�=A	�
�7�-%�"�K�>�1�T�s
�7A���<�
�	
���
��z��|�}�~���u�A
���r 

a�w�RA�~�v�5�f��K�>�w�MA	���*
�3

��wA���T�Na���}A�Ҝ]�1�R�s���=�8	�	�p�#�1�V�S�t�C�x�I�e�9�e�}�@�H
�e	�.��T�A&�϶T�	
�{�0�/��	���x�
�
��z�
�z�
�tA��$�K�J	�Y�X-
a���iA���_"�A���h	
�h�eA���
a���mA��86�"�n�U��h�#�c�:��F��W�hA,�ضq�h�e
�r
�_	�V�W�L	�S��,�-A
��$
�a���A	����C�x�2��g�tC�.
�A���;A�����
Ba�.�1A �2�w�U�K��f�]�w
�<�%�4�1�c��1�6�h�%�T��>�)��}��q�T��
�$�z�
�6A�B�3
��$�^	�Y�s�n�Z�UA�A�+a���E�H�I�4�JA4���m�B��A��o�(�g�|���-�8��O�]�&�I�I�y�r�K�H�K�$�A�|�+�1�Z�	��C�6�C�5�v�5�4�!��A�3�rA��$
a�=�bA
�R�h�4�5�:� �G�R�K�S�A�B�g��!���6�7A�Z�+
a���xA
���L��D�k�3���g��a���~�1A�ӌ��=�k�d�:��	A�|�`����e�e�&�K��k�%� � �A�ø�H�GA
��3
��a�M�9��<A�T�%���$�\�1A�W�y�c
�b�KA�Y�>a�o�A"�s�3�a�b�3�`��G�*�E�0�g�Q���6�q�z�s�r�?�E�T�,�q�pA���A���@a�(�.�:>��s����Ca��*�-�)�,�}�.�/�0�2�4�3�6�9�5�7�:A�;��=�>a�2�E�G�H�I�JQ�K�LA���3��e�V���sA���@A��Na���F�HA���c�-�^�-�
A���K		A��O�+
�*
a�-�SA�0�)�M�$A�7�R
�L�#�/AA�6�Ua����7�ta	���e�f�g�h�i���k�ma���YA��E�$A��o
A���[	a�"�{�}�~��T��)�N��"���1�a�-�<a�3�^A�T��w�j��pA
�O��p�q�l
�k�l�mA�s�`a����A����C#��A	����b�c�\�cA���ba���A�֐x���}��b�o�		�*�i�"	�d��
��f	�&�`�Q%�Y'���k� �MA:�Թ 

�H�3		
		+,n		
A��e�M�L

�
?a�]�TA�`��|
�K"�]$�=�b�I�]A�d�V�f�Y�T�_	)2
�`�e�2A
�c�w# a��sA
���V���
��;�:a���{A���bW)�_�k�2��d�t�-�}�|A����x�d�eA����ya���A�Ґ�>�IA���
��}�|A��a�����	�A����z�A
��
�/�`��6A
��
a�_�%�8�$�(A���~A�j�+�����n�2�1�)A�h�
q��e�{�!�1�3�^�d�{������"�$�.�6�8�<�X�_�o�{�~�a�pa
�0�Z�T�R�[�a�]�k�e�c�ga���q!���G�L�H�K�M�F�"�N�4�|�5�}�	���;�9�<��O��&��'��
��0�(��)�a�a>�G@�I�o�	�E�G�CPK
!< ��_ww5chrome/pdfjs/content/web/cmaps/UniJIS-UCS2-HW-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
UniJIS-UCS2-Ha ;�g�!�$&�#PK
!<bq%��5chrome/pdfjs/content/web/cmaps/UniJIS-UCS2-HW-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
UniJIS-UCS2-Ha
 ;�g�!�$&�#
�M�_�U�T�W�Y�WA!��bA %�Z�n�6�Y;�m�2A!��6a%�9�7�=�;�A�?Q%�G(q%#�j�o�m�l�n�q�p�r�[�]�\�`�S�U�T�Xa%=�wQ	%@�ua%I�~a	&�������_�O	�c�N�]a'�A0�Vz!O�0�/a0���S���6Q3��J�W�V�mq3�E���
�L��N�P�!�T�	��W�Y��	�\�$�q 3-�_�b�&�e��h��n�p��q��j�r�t�s��w�%�{���|���~������a3����[A��L
�K�PK
!<�RP͘�2chrome/pdfjs/content/web/cmaps/UniJIS-UCS2-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
UniJIS-UCS2-Ha��M�_�U�T�W�Y�WA!��bA %�Z�n�6�Y;�m�2A!��6a%�9�7�=�;�A�?Q%�G(q%#�j�o�m�l�n�q�p�r�[�]�\�`�S�U�T�Xa%=�wQ	%@�ua%I�~a	&�������_�O	�c�N�]a'�A0�Vz!O�0�/a0���S���6Q3��J�W�V�mq3�E���
�L��N�P�!�T�	��W�Y��	�\�$�q 3-�_�b�&�e��h��n�p��q��j�r�t�s��w�%�{���|���~������a3����[A��L
�K�PK
!<�i����3chrome/pdfjs/content/web/cmaps/UniJIS-UTF16-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE���������!A\a
!�GQ�k]>�d��_?V3�A���M�T�+ 2!+�V�S�L�IQ��tA
�~$�c���
Q
��n$�c�U�P
�&%$)A$�)
�f�7�![Q .��s"'��A��"�}
�";�9�6"��B��;
��|�,"�m����Q/P���'
��j�>"�kOL
i��O��fO�>��_c�0
�m��("��[��<��SA�i��P.Q��2��j���	�
���A�����k9Q
P�X��"��"O(
��zA
^�W-*'0#rQl�6B''�$�c�bAx��1TI�6��7�ZQ��94
M�-�|3��)F�Am�b	>M#=:B�@�;	���J�<O@�0�G�"8��F.��q.�J	�N
M�y�u�>�:�E�<fG>+	'c���h	W~�9�2	
�|C	l�
;ls9'�
��V�h�i� N�.:�K� �e�,�d�[�5�J��|�O�r�]A3 Z�Z�+�.�/�?
�x�vC	F
�Q'�

�j�w�
�*�"e4
�?	�^(��D@�����
�9�zAA��c��,
�`��2�H�j&�_;/1� !�f	�P�c�Z�w
65
�h6

4!�;4/Q�fB��.+�T��*�
U	�9�.��53�6�?C��$<(�#��Aa��J��#�"k
l	�)�@�]VI�b0#
8%#
#'`�p�|�k�`-�k]�FI�[-�&�3�w;<;� y�e�5�@T�
K�9G�$��+��C�U��p�t�m�Z�$�;�M�
�v��3�\�7��V�=�/�j�U�_(�A�K�v�9�y�.�@�7��#�A.���eA.���:A���K
�_�!�W�:�r�Q/�0��i�� �w��w�8�5�R�t
�{�v�\Q/�S��h�>�
�c�r�/�qQ�/#�&�?�
�C�Z��c��c�T�c{�i�7�&&�S�����o���(�_�4�u��w��$��n��B�S�^�n�}�H�a�$�/�7�R�4��	�w�>� .�Y�O�@�4�<�{�0��P��S�_�(��VKp��A��X��]�X�`�-�t�G�W��-�e�"��N�O�x���e?�K�j8�]�/���8�G�U�F��
��a��h�(�.��\�z(����$��X��[�o�p��%�t�:�+�T�}�R�a� �G���6]�C�3�!�Y�"�o5�n Q/��N���<�7�J�a��r��C�8��w��z�A
/Ӟ~/�_�H
�+��<��D�w*�3�`A00�:
�a�aA0<�Bc^Q23�Op}��$
ly�nA2Q�Q2��g�

�D�5�,�=��2
���^A3�pA2��ZA2��{Q3�j�r�[�Z�AN�0��+A23�"��
��
��*�?�
�#�F�?�#�,�y�`'�>�M�;�:�J	�=�K��A�&�w�P�y�{A3�x


	�5
&A4�$�&�b9�S�6�m�h�N�	T� ���
�`�(��2�n$�Q�@"�Q�cA4��lR#�.�g
O�8o��x�m�jH�9A�'4��Z%=,'0"�k�z�p�k�H
7(
�p�a�5�5F>5$�LK&&.	?��i�GT4�\�#�]�r>
�K4##P\	8
�9	�P" �Z�A��8>F�2�i5%"�$�09�g��
�B^�g�6"
�\�_�,HBP
&�7J$(�*V
8�C�n
!4
3<	5&Y�1��<&��C$�`�j0�Q_ 
\(�pD�G��$%�	�f@�z�y6�d	$�.~.B�dC�D��&�r�}v%Qo�
�3QN�*�9�!��#����}�D��R��1�U��AVN�L�^�i��O�2�w�l�i�@�m�3�2���M�(�7�X�/�u�ve�	�R�\��g�f��N�� �z�3�
�i�Z�j��9�{�:�	��~�W�d�Q���}�x�X�S�w�h��N�
������I�@�G�F�5�K��r�#�yA
N�Z�
�a��e�d$
"��
AN)��U�x�y	
�t�u�T�"�y�x�w�nQ	N݅����g��N��/�F�7AN��9�z�P�A�,AN끆b�F�GAN聤]Q	N��Z�4�d�l�m�g���p��SQO��a��	���4�<��k�Y��M�'��\AO�q���L�xAO�kAO��f�x�3QO3��d��)��(��w��z��k��G��.��Q���PAO@��f��u����N�;QOM��\�u�C�R��6��y����a�r�/�����j���b��p�AAOi�)�Q��0��\�i�AOz�pAOc��k�	�5�:��>�?Q
O��%����k�!��
��W��V���������r��oAO��.����3	�$�[��q���/�5�l�CAO��<A
O��s��M�L��<�L��z�Q
Oȁ�~��%���*��������}��|���AOז�F���{�X
�g�.��B�0���
AO�l�(AO܁��#	�"�?�$	���(�u�dQP	�T��`����2����x����p��e�a��>��wQ
P�*��6�m��O� ��~�z����yQP!�B�z��i�d�o��f��
�I�T�;��(A,P6�^�N	��y��X��q��A�(�t�'�F���e�O����?�n�R	�E�$�q��)�~��G�>�@�/�.AP@�?
)#CA2P0���� �t�u�5�{�$�y�x�y�5�{�&�'�&�}	�|�7�6��-�0�5�}�0�1�(�;�:��)��
�o� �1�9�lQ	P၇
�4��������L��k���+Q
P�_��J�R�T��j���I��AP��b���E�h�	�qt�E�D�9U�AQ�
AP���+�����8	�)�(�C��(QQA�8����!�1�h�d�;�^�C�:��d�?�)��r��gAQR�r�9��B�?�A��)�h�.�7�Z�m�[�
�M�`��sAQd�JAQS��;�I�F�_��T�_�
��W�>�H�S�QQ��~�#��P��3������6�7�#�"A!Q��Q�3�+�D�w�v�s�g�f�o�t�1�j�D���Y�X�/��[�~��+�*��5�R�p�3�2AQ��K �a�`AQ�����	
��1�`��j���.�-�<�=�'�P�gQQ����p�Q���u�s��L��E��zATQ��5�F�}�
��=�}�A�j���C��:���y�x�Q�P�)�?�I�.��-��B�;�Q�$�`�	�]�`�9��(��y�7�>�a�R�3�8��&�x�}�r�3�U�U�p�{�$�7�jF�P���Y�Z�o�Y�n�O�$�i�h�/�0AR�NG�a>�`	�X�Y�GA1R��o�#���N	�1	�$�g���Z�'�+�P�'�C��*�+�N�M�N�#�I��P	�O�N��$�!�z�g�
�Z�}QR�R����)�h����v��a�`�o��*��=�3��n�A�@AR��|�{�1�t�K�b�
AS�SAR쁥a�
�H�B�g�#�B��,�E�DQ
S�M�!�x��V��u�B��0������K��s�SAS#�O�A�@AS$�UAS%��i�K�T�U�)QS8�S�{�m�o���U�Q���x���T��KQ	SE�V��m��
����
ASQ�p�9r�]�J�/�h�.�<��� ASa�!ASY��q�#�J�%Q
Sl�"�J��W�+�^�X��;�^A#Sw�`�S�N�{�|�I�H��U�
�p��H�J�y�x�A�n�P��{�c�8���UN{�<�&AS��WASy��	�'��(�R�I�L�Y�U�*��*�U�T�-���~�W�V��(
�V�{�7QS�Y��J��'��L��]�Z�Eq�5��
�6��#�#�w�P�H�W��E��#��p�7��1�R�>AS��r����?�QT�y�/�0�i���/�^�E���`�`A&T��l�C�8���C�<��b�*�t	�;�:�W��}�]�V�t�
��/�B�'��� �vA	T��4�O
�0��
AT����"�^�_�#��]�\����G�TQT���%�X��m�y�����Y����AT���H�7�%��p��7��CQT����t������z��	��<�6��5AT������QT���L��}��B�8����=�8��2���$�0��p��{A%TǢ
�5�@�=�L�+���m�n����J���o��5�}�:���+�2�7�.�m�=�*
AT��^^�A'T��2�D�E�T�n	�o���o�n�o��E�\�a�J�K�^�j��
���O�N�s��p�Q�6�q�Q
U{�4��H��S��Z��g��l�n��K�A)U��<��z�o�t�
��O�f	�O�`�M�c~�&��

�4pi
�M�P�1�W
�~�o�w�h
�YAU��_A,U��:�Z���r�s�r�u�	����s����}�|
������j�k��i�b�m��z�%� �QV/�U��8���X��<��C��X��Y��\��[��TAVB�_	�6� 
�k�v�S�V	�1�#�<��k�l�Y�\AVS�E�kA*V;��*��{�i�h�x��k�v�w�v	��i�f�g�d�

��u��~��g�d�%�*�r�}�%� �a�b�%�*�t�}Q
VȢg��L�i��H�w���3��8�t��/AVӢjQVעk�_����=���J�e��9����
��ACV�
�R�1�� ��e��V�}�~�O��d�K�J��*�y�r
�`�W��6�.��y��_�6����;�4�c�b�%�1�V�y��v�K��6��_AWY�`FAFV��P�d��u�t�t�k�x�p���m�h��y�{�p�m�h��u�t���t��o�r�I�H�y�	�I�H�{��x��z��y�z���	����W�R�����c�-�~���Q
W��e��x���O��l�����v���3A9X�
�y�@�D	�c�b�)�7�O�~�_�Z�Y��%�.�c�L�?�B�54�\�I�a�@��.�V	����g�v
�S�s�D��M�L�U�P
�#�� �O��>AX/�21�(<�>A2X��a�y�x���
���~�	�v�o�~�	��~	�}�f�	��]�p�|�
���|�}�|�a�t��z��e���
�`���z��#QX��h��y������z��_��z����vA7Xܣ1�)�t�|�:�)�_�P�G�|�3�2�k�j�-�1����:�
o�|�q�R�#�P�Y��6�]�\��P����c�^�c�d�x�O�T�[AY�iGAX݁�y����"�"�|�/�.�/�w�$�%�$�;�8����=�<��1Q	Yg�M�y�z�Y��J��w��B��[��AsYs��&�t�3�2�u�4�T��|��8�i�z�O�a��E�"��3�.��s�A�|�$�u��� �X��]
�o� �B�_�r�/�2�O�J�g�%�t��#�!�D�� �=�<�;�g�*�
 ��K�L��
�=�V	����	�G�J�	�#�$
�A�B���w��a�_�H�9�t��!� �]�\NOAY��n�AY��q
�
��m�l#

	,4�?�>A;Yy��j�?�%�`�?�<�?�'�&�8�9�6�3�7�67�=�:	�;�:�����K�J��������
�aA)Yr��	*

!
-IQ[����H�G����H��I��J��[������e�[�h��*��{Q[���C�l��5��*���}�p�>��,�bA[��n��D�O�R�z�5��W�����CA[���w
�A[���=
Q	[��S�:��"��g��G�t��:A�![ɤ���e��J�O��B�W�M��o�X�+�D�)�(�G�B�/�}�L���/�/�L��}����1�k��'�s��u�t�S�ZI�R�2�e�b�i�/�P�C�B�k�`�o�`��s�n�w�E�h�#�J���	�[�d�
��� �;�2�
��w��G�>�[�_��:��K�F�O�J	�� �_�h
�s�r	�!��w�r	�'�>�e�d�u�H8		�o�n���5�KA[�sE<�G�C�P:1bA\�*�?	�2N�O
�N/G�%�A-[΁�z�{�z5	�
��������5���

�%�&�-�0�5�2�5�6A[ׁ�z�$2�%
	"o#2$CA&[́�D
$ 	

"#


Q
]����c��������o�X�Y�e��PA0]�m�	�W�^�y�s�j�e�+�x�7�.�T�q�4���m�h�[�=�
���o��O��"�� �q�b�G�B���a�`
�!�>�UA]�� �}�|)A]���
�P�}�|�U	�-
�z
�y�&�T�M	�v�RQ^w����)��$�/�7�C�$����)A&^��?�2�2�31�
��-�g��n�g�C�g���`�o�^
�U��&�n�K�L�9�/�l	A^��0�r �d�_A^����N�}�z�U�'�&�'�x�U�'�zQ^������t���5�j��2��W�����W��V��w�Q_���-�,��U���M��L��+�`�&A
_�w���F�Y�i����s��t��}Q	_!��d�F�^���$��h��%A_-�$
�3�^�5�z��3�B��~�w�z �1��;�X�O�t�d�)�i��iA_4��!!� A_.���-�=�<�B�f�+�*�	��C�@�A�(��!�	�H��Q
_|�t��L�b��1�-�`�F�7�6A_��9��-�R�h�e�}��F�GA_��<�"�#�"�!A_���Q	_��>���!���f�}��9�e��dA_���-�L�*�w�h�F�W�P�H�;�	�J�?�6��|c�?�:�m�v

A_��&A_��8����3��� ��5�,��5�4���9
�*��Q`�V��������p�*��'��z��s�C�A5` �5�&�#�4���
�+�2�S�J
�7�8�g�0��>!�!�F�#�o�A�>�o�:��(�~	���I�2�k�^�!�pA`]�'A`��&���;�(
��������)�(	��E�D�-�,�)�)Q	`���7��
��8�R��{�f��^��_�Q	`��O�"��Y�"Z�����A`��c�&�?�>�	�z�A`��
A`���!��.�1��'�"Q`إz��R��/����E��f��5�c�4����GA`�g�K�F�>�S�F�_�^�#�&
�k�]�D�%���"A`��A`偨@�7�)�\�=�<�?�>�;�z�>�;�%�$�8�A��D�C�B�?QaD���j�;��I�����}�/�e�l�A
aS�!�Q�L�i�q�R���Aa^��(�u�tAal��LQan��6����\��q
��v��nA5a~�Ge��S��P	�Q�i�a�
�7�A���z�5�.�y�z�i�n�=�P	���w��Y*��Aa��zA%a{��*�'�&�L�w	�2�B�O�)�v�E�3�v�E
�r���4�p�q�F��~�Y�V�W�V�S�R�a
�/Q
b�p�,��8���9�#��Z��9��:�$A?b&��l�o�n�+�m�V��m�4�w�
�I�A�3�$�S��M��)�
�(�3�<�y����r�=��,�w�n�/�x�T�K�m�~�5�r�Q�{�*�|����s�d	A
b)�^�C�_� .�=>�<Ab'��f�e�`�_�^���a�-�,�n�u�\�i�q�p
�lQb̦i�	��"��)�M�F�-����H�1���5�p��v��9��AA;b��M
<�B�	�=�D�
�k�h�f�3�N���Y�L�U�q<�
���
�"�2��1	R�#�~	�
�1�w
�X�#���U�V�?�<L�,�?�w�<�G�8�s�d��yAb��e

�c�b+A*bꁨx�{�q�h�k�j��~��~�k�h
�e�b�|�}�e�`�}�|�c�b
�a�`���7�2	�a�^�e�dQc��r��]�#�k�h����������D�E�j��A��VA�9c����2�I�L�g�p�5�I�|�q6�{��2�+�W�
�9�>�q�|
=	4��c�X�q��>�8	�}�t����)��V	�C�:�q�7�E�l��M��F�P�/��c�n	������"��v�1�:�E�@MPAB��:����s�#�
��� ���?�@�/��V�#�4�(�w��\�|���E�&�m�`E�!���1�0�7�"�H�@��
�h�9�&�2��i� �^�5�.�u�x���O�P�O�8�A�]�H�n�{�e�Z���;�6�����P�e��,Ac���O<,� <0S�+�~�1Ac��s
�1	�N�`1
N�=�@-< ��Ac偃l�JXsA&c���"+*			
5		'	
$AcꁗJ'
9*1		A-c���%!	#
"4
Qf�k��6�o��w�Y��<�+�(��W�}��R��k�e��N��+�S��O�l�!��R��Af4�j�*�/�>5�G��D�a�Z�_�BAf3��v	
�.�'�W�~�Af7��L
Q
f]�q��6���3�����V�=A-fi�i�R�g�,�C�c�T�#�p�c�Z�� �{�f�q�d�Q�R^�2���!� �%��2��1�@�Afs�"%A fj��Q�	����S�L�}�5�4����(�)�?�f�7�z��[�Z�E�D�E�D��Qf���'�P��m�	�[�p���NA.g�K��$��=�<�Y�n�h�w�S�K��3�Zm5�/�B�}�N�.�I��6�9�J�1���'�"��-�&�#�"���P�$Ag�(WAg��
�����i
�7�6�f�a�`�S�O���Qgl��i��;��H��M�,�y�x������`��;A	g|�$�}�b�"�9�$
�gAg{��2�k
�j��YAg���j	Q	g���� ��#��L�T��1��Z��o�>Ag��n�t�]��x��!Qg����0�8�9��]����[�:

Ag��(�P�N�K�F�%��m�F�#�H�P�6Ag��+A
g���{�h��~��~��)�&�|Q
g�6��h��o����e�%���$�Ag��*�d�}��n�o�T}�1�v�_�g�F�Y�b�U�]�dAg��
�'Ag���J�%�z���~�i�h�~�}�5�0�~�}Qh;��~��-�,��~�x��{�	5�
��� ��A"hH�
�j�{x��X�>�9�j�5��I�^�?�& !�}��u��%�&�}�~	�[�,�E�AhR�,;�!AhI���v�w�r�3�@�C�4�5�4�w�A�&��u�h��9�6Q
h��K��v��o��6����Q�����)A	h��Q�[�]�p�=��C�NAh�/Ah���Qh��0�v�6��5��4��O������|��gAhߘi�
�I�Z�-�&�e�x�DAh��.Ahꁪ��A�@��]�pQi�� ��s���d�]��
��h����2�q�t�d��t�=����A@i�@�c�
� 
�e
���}�S�

��L�\�'�$�=K��1�.�//�c�(�}
� �?!�Y�
�B�/�n��d��g�2	�V�	�+�6
�}�W�N�K�
�UD�\�3��B�
�J���S
�H�e�l�)
�Y�L�uAqi�p+& 7,

 8=$	


&
18

��		�o�j/0
	
	ad	Aih�2/.��M$
cA i5�5�s�v	2�[�Z
�
	�X�U'J
1A2iB��
�4�;�4
�?
�>	�E�D�I	�L�Q�P�U�Z	�g�h�i�j	

�
�
Ai��"	B5X
	=H�R
�O7Ai��72#

Q	k_�b��n��o�m�V���p��Q��nAki�2�n�O��r��t�E�=�!�
�.��A	kl��c������
�Akk��?Q
k���I�[���o��H��G��DA5k��u����u�@�A�r�y�2�o�2�(��4�#����3�~��X��
�!�<��X��E�^�v�m��l�Q�K�(�'��Ak��=hA/k���j�
�1�q� � �#�`
�;�'�p�u�&�L�u�t�O�N�O�+�^�k�p�k�&�>��s�r�A�@�u�y�("��A�3�y�j�u�wQ	l��
���^�)�M��4����<Al��1�2Ql���3���-��u�� �;��m�@��2Al���c� �j��P� �(��4�X
�'�H�H���&Al��IA	l���\�w�E�:�y
�E�;�vQ	lρ�`�o�n�}����&��7��6��'A6l٪!��%��B�i�l�O�6�Q��h�%��'��(��g�D�(�k�r�E�@�Q�l�5�x�2�e�\�)�j�
�{�p�l��|�`��\�2Al��B)jA*l܁��-�j�?�+�f��F�#�^�k�h�i�h	�C�B�m�.�O�r�s�L�M�e�d�J�1�h�9�*�]�h�9�*�]��NQ	m����.��{��p��g� ��L�1��_Am��mX�
�;��g{�l
�A�J�]�^�=�6
��"
��~Am��G"Am���)�`�"���o�H�I�J�K�J�U�w�Q�,�j��mQm�K��N���K��C������X��	�T�#�%�,An�Z�'�r�T��]�U�,*�1�An�FAn��uQn�^��
����t��f��U�S�@�rA/n)�9�6��~�w��E�>�/�P��2�q�t�C�F
�?���f	�=�R�{�t�n�W�G�4�{�t
�a�T�y�z
�+An9�LbA"n2��&�)� �&��h�v	�{�[�r������
�%�����M�B�$���+�*�)�Q	n�����C��8��e�X���	�A#nыH�8	�3%
�p
�)�41��0��E��.�e�8
��e��+���
�

�c�@�CAn�8)�"
�m�l���q
�p�f�[�X�Y�\A	o��	
QoW��A��o��L�U���S��j������!�SAHod�k	�8X�a��|�	�>�.�k�B	�~�%�T�)�s�M�	+�j�_�:�W�t����C�A�V��;
�>�g)��	�h�|�%�
��(�M
�
��O�h�Q�@�C	�x
�C��u�Z�	�|�K�G�F��}#�m
�_��D�A��"���	Agof�DM




�A�B+2	
uv.
/		

#

Ao��Q,?�_�^�	� \%�WH�X
 �{+�|<�}'�<A(o}�

�%�$#�?�! *
 �n�m)
Ao~��3

%]&.?9"2	PA.oh��C'	*	" 
!6*,Ao���3U(�0M�1�lA"#A(ol��	">E
!
	1

	
Q
rW����+���<����d��Q��Z��}����'�J��JAErg��
�?�~�	�X�Y�~���[
�(�I�@�g
���_
�p�`���V
�1�\���n�N�8�E��~�Y�=
�v��$�?�j�*�	�
��Y�e�N �E����!#�E�_��Z�q�v,�Q� �i���H�u��.�8AWrt�k	

		7	4+	
)("
Ar��aeRE5�w$�xaA8r��;�%�$3,(�`�a	�O�N	
�H�I
�@�=
		�WAr���I�(;�
85A6rx��
&
	,	#	Arh��HD
*
�
</A/rk��A	/ 
	@ 	Qu�J�]��V�*���-����]�^��d�����k��/�0A
u0�>�<��Z�
�~��E�HAu/�rAu6���E�!�dQ
uF�S��^�g�������X��=��d��'��&AuT�^��JQ
uY�y�4�q�!��*�H��
����OA	ud�V�E�#�f�W�Z��D��EQuo�u�-��h��k�~��d��m�-�(��D�.AGu�@�@��'�U��[�j�e�V�Q�P��t�k��|�?�g�"��������O�N	
�w�z�[�V�a�d
�+�s��c�zABu��~
�2�3�B���
�5�4	�
�	�����	��K�D�)�(���I�(����!�+�H��C�D�E�P�]�\�S�B�o�U�B�s�Y�B�i�h�E�w�P	�h�+��Z�[A
u���	WQ	v������O�7�o��`��!� �
AHv��w�
����;�`��^��P	�x��]��
�<��2�N�p�g�Y� �m�\�H>�I�{�Z�'�fp�a�&�{�*����'�T���=
�R�#�a�G�
��G�y�6�1�d�{�P�Y��6�;�=�#�L�0�D�A�� AVv��

�%�&	
	


$.	
		=:Av��y�=��>�o�>�p,�5A v��7%
��
��;	
��:�!5)
5Av���^
`Kb*>OA'v���

."#?	"	
	A)v���r
	
E$!	8A4v���!		=
" +


Q	y:�T��H������y��@��C�c��$A1yG�Z3�^�X�J�{�r�[�`�t�?�W�p�O	�T�$*��$�Y��k��:�(��i�h�[�\�	�y�}��W��:�;�S�T�6�%AyE�;�v�	�N�O�J�K��T�K	�~��|�F�G��~�D�E��m	�n��AyD��Y
Qy݁�g�W��k��@��)��(��)�m����G�Ay��f�;
�-�D�$���Y��I��v�
���SAyꁙ&�S�P�E�%�$��8�C���>Ay�k
Q
z7� ��\��>�����t�VAzB�6�T�?�@�]�	���b���s�t�Q��P�yAzE�K
A
zD��+�7�H�K��@�E
Q
z}�*����1������d��)�?��2�ZAz��+��Z�>Q	z���Z�>��5�g����A	z��x	�n�i�w�t�j�G�JAz��QAz���{
�A�&�#�"�#Q
zËT�@��B��=�������^��O����
A
z��	�	�n�������#�-Q	z��d���.�6��1�J��d��wAz�z��Q�0�7�y�7���
�	�X�N�#Az�c�C�\�5
�V�WAz�	#Q	{%� �j������h�9���U��vA{3�J�K�V�o�M�A{1�Z	�T�B�'A{0��Q{K�R�����'��k�0��N��S��v��A{]�i!
�g�� A
{`�]�(�+�L�M�&�|�	�-A{j��	Q{��)���2��	�.��$��)	�X�W��"��_A{��{�5�(�p�o�g�LA{��bA	{����3�0�+�*�+�.Q	{Ěc��0�����b��y��F��%AQ{Ϯn
��Z�.�-�<	��
�z�q�{�r�u�v����#�&/.�y�x�K�Qp�(�!�-��0�5�#�E�B�'�'�*�s�6�B���;�fA{��c1


�C@�BA'{ԁ�
�h�i�f�i�z�l�m�l�m�l�q���n�u�t	�w	�t�u��n	�g�f�eA{ہ�&		)
Q	|ү&��H�H�C��3��z�����,A|ܯ'�E�/�+��1�2�u�t��C��V�c�K�(��A|��v�H�I	�J
A|遭1Q}��2�����'�K�������k�I�0�c��T���HA} �I�L���*�nQ
}*��6�����z����=����3�_A}5�9��5��XQ
}?�8��Z�Q����.�R
��X��1A }K�5��,��t�S�t�/��{�r���k��Y�T�9�V�h�1�H�x�
A}M��N���
�T�5�!�T	�W�X�>�{�z��Z�>�A�<��BA}Q��8
Q}���6��O�S��P�s���b��E�2��T��M�{�����A!}��N��(�l�u�X�#�P�?��*�y�k�`��	�k�;��k�r�/�*��d�2
	A}���8�I	���;	�a��9�i�h�i�fA}G	Q	~�1��2����Z��P��Y
Ai~&�N��{�	
5�3�N���w��<�S��t�8�Q�|��O�j�m�b�T�I
�)����
����Q�T�d���z�4�M�L�!��G�H�!� �b�_�?�6���?�6��K�-�x
���+1�Z�[�A~G��O�N�+
/-
A#~'��B�[�Z�,
�1)�0�1�0�1�0�+� �&�%�"�����
�
���A~-��L/�)	&	Q	��>�<��j��i�6�w�r��b�Q�
��c��O�O��.����g��d��O��N�������A��@
�Y�D���
�Q�P���3�2A
���n��|��*��|�}�|A���f	Q	�o�F���6��;���>��9�IA+�y�Q�i�h�9�Q�$�O�4�;�@�x��z�Z��I�$�T�u�i�T�l�Y�z�G��'�N	����8A�{��j�

�
��~��~��~�/�.�
�/�.A����p7Q�큙t���!�������5����F�����������}��A%��7��n�~�e���l��O�e�v�k�,�;�%��F��,�3
�h��
�.
�s�v���q�A�j��>��8�A+�	�d 		
A6���y	�����
�G�F�z��	���
�
�?�F�C�8
���t�u�t�w��N�L�O�@���u�O�N�v�u�p��(�p��,A���y
	Q
���M�r�X��\���&�M��0��eA��	Q
���c�-��x�p��Q�	��\��]�WA�!�=�0�v��e�y�x��Q	�2�>��7��6��-��q�\�w��NA�@�$���?�@��^�<��7�@�{�z�9�M��a�#A�<���n�m�5�0�1�6�w�v	�^�W�c�\�V�Y�I� �Y
�A�C��
Q����+�R��?�k��~��u������(��-��,Q	���E��%�?��0��k��j��	��6�JA	�ŋ_��R��k�p�$�e�^A��G�JA�Ё�	�Q�ޱE��L��S��h��i��P��+�W� ��tA��)�m�
	�(�y�J�U��T�=�B�;�Z�QA�ꁚ�S��M�L��A�큮%3Q�+�T��,�u�t��3��H��U��>��E�S����'�h��BA�@�O�J�
�A�<��9�a�`�A�=��/Q
�O�c�Z��p��K��,��U��A��A�w�YWA�b��m�p�
;4A�Z�z�h��/�Q�~�UQ	���O�N��?��P�l�m��m�A7���[�O�@�*�_�2�w�h�*�!�@�b�I��N�k�V�!�^� �0�f�W�7�>��!�3�<�#��+�6�Y�\%�M�\�*�=d�y�[A����5�K�d�������i
��+�*�K�M�f!�m�l�"�A����9	)0Q	�_��;���E�P�)���g��?��$Q	�i���������d��5A�u�n�$�A�4��	�C��W�,-�]�A
�s��=�M�z�3
�B��5�4��A�t��JQ���`�Z��b��[�=��X�������	��A8�ė�r�/��m�v�;�>�,�)�^�(�Y�
�9��f��
�~�S�Z�;�~� �B�#�w�r�S��h�i�P�i��t�r�CJ�a�w�T�v�_	
A�Ӂ�_���z�
��G�J�K�G��u�n�
�H
�J
�K�TA�́�R%	Q	���,��t�e����r��q��p�EA����PA���:A���a�(�b�c�b�EQ
���-��z������w�y�*��r��F��?��LAO���>���9�:�
��C�&�q��'�"?@��r�m�
�W� �6
�	�;�D�{��d�/�L�O�N��)�H�b�
��=�R
	
�5�6�C�'���
�	�3�,�Y��t��*	�	�A���b%�G�H(��
�K�JaA-����|
�k�n�s�r�s�r�o�n�m�l�s�t�/�`�5�
�x'��(�%
�A"����m	
"
#	Q
��X�*��V�s��m��l��m��|A���>,�Y�(	�_�)�d�
�,T>��&A-�
�r
 


				
 
��+�8AP���#�n�o�a�n�o�\��
�a�l�Z�i�h�]�
�c�b�h�k��b
�U�l��
�3�=�t�v�h�c�b�]�t�}�|���d�[�x�z�y�\�e�d�_�^�_�^�i�h�c�b�a�`�]�	�b�o�n�k�*�)�f�=�+Q
�
���|��q��,�k���CA���=��	�U�V�g�)�z��)A��A���<�x�}�|	�m�n�o�j�k�j��m�j��k�j�Q�U��C�w��c��h��9��&��!�� ��
�b��.�x��?����HA/�h�/��x�/j�b
S�Z�y�	�
�d�	�d��W�	�t�W� �a;�\�S�C�c�\�F�L�<��q�;�3�d�z��d�+��={�v�u�D�U�\AI�k�3

				
	�.�7�$�

�^�[
A�����
* 
<�[S�Z�G�FA,�i��O�j�i0�h�[�r�d�e$�h�a�`�i�y�x�`�a�`�e�
��`
�a�`�a�`�c��A$�q��G#

 Q��|��H��=��T��
�C��N���A�W�!A��Q
����X��e��d����A��b��]��\A�*�Z�/�g�`�k��@�YA�6��@	�AA�/��y������#�j�I�#Q	�P�.�f�P��b��u�X���I�IA5�[�
�!��6�G��n�8�M���#�V�J�9�
�:�[�>�r�g�6�>�r�u��d�u��-��4�i�!�L�O��9��%�K�/��y�`�&A�]��d�j
�k�j�o�n	�K�J�M�|�N�M�L�s�r�OA�\��	Q	�ڴ�3�� ��s��A��#���+��J���U��H�0�o�-�,�\�[�bA��&A�쁚c�h�i�S�:�7�L�Q�8�7�LQ	��`����.����2�i��k�aAF�&�5���7�<�h�e�C�D�K�N�'�"�M���F�G�F�T�U�w��h��� �G�J�%�&�o�n�I�[�"�5�� �Q�P�U�X�1�n�.��SA+�-��	�3
�~�
�4�J�}�z�4
�5�~�K�J�O�N�O�7�|�}���K�C�z��A�@��B�I�H�G��9��HA�0��
�(Q
���A�E���/�R�V�g�nA	���H�D�%�5��r�
��]��^Q
���u���?�a����
��w�X����I��RA"�Ε*�J�b�kd�
�(�u�t�_�<�E�0�CX�.����H�)
��+�>�<
� )�y�R�]��y�Q�.�	4�MA+�ʹ}��	
�r�qP�b
�c 
A8�ҁ�v�7�Q
��5�4���9�6�
�L�
�
���		��	��'�&��F�G�`�&�'�b
�G��	��E�D�E�D�M	�Q�^�0�=	�<�9�}A�ρ�<

e
	
 !Q
�G� ��D��A��V����YA�_�|��A�U�*
A�T��1�V��?�>��^�a���7�Z
�D�#�"�G�F��]�\�Q	������G�U�V��B�+��E�*A�ʒ�q�|�0�J�!�1�a��4
�P�UA���>�`�a�j�k
A���H�\�i��D�]�\	�C�@��"���G�T�
�f�#�E
�j�O�&�S�|Q�%��!�����(��O���:�H�W�~��uQ	�3�S��J�+�/�X��]�����#Q
�>�X�[��&�v��I����7���A9�I�Z�[�^�A�9�&�$���]�^�M�L�	�c��`��k�A�,�7�2��S�7����Z�8�}��|�!�"�A�\�1�K���\����G�&A�J��@�N�_�^�QK�R�?
�j�]�^
�u�N� �u�t�u�0�c�$A�O��|7
Q	��d�J�#��H��]�k��AP��f�N�/�4
��� �;�Z�]��J�O�`��
�
�b�e�u�j��p�W�@�=�Y�o�V�z�	�T��m�"�S�D�/
�*�[�x�h�-�6�i�`�Y�,/e��Wi��E�#�&
�j�1�+0��B�0�M��8��4
��C��<�;�T�2�uA@��v

�Z�[�d�e�-�jH%�>�G�>�?
�>�=
�Z�[�d�aA�,�VC""
)			A�-��~i
'$NA8�/��0�?�:�=�<�=�<�=�:�=�8�9�8�9�6�5�(�/	�$�%� �!���
����A�?��"!&	"	
Q
�ֶ8��G��.��m�	�K�t�^�A��|��%��-%�"�KA��:�
�	
���
��zA�쁒}�t��6�7��
��a���,�b
�i�/
�&Q
�B������0���'��_��t��mA:�N�L�}�~���u�{�\�	���	�G��5�f��*�w�B
��w�z
��G	�N��u�z	�+��{�Q�s���=�:�/��_�b�S�:�K�NA�O�zR�O�N$�K�JA2�M��M�������	��������z�w���{����	������)�vQ���o���B��?�r��� ��)�#��n����L�$��G�tA�/�h�x�I�e�9�eA
�.�[
�
��z�A�3��W�4�5�4�_��m��6�'�9�^�9�o�&Q	�i��x�c�6��A����E��$��+��~A�u�d�H
�e	�.��T��
"��_6�"�n�U��h�#�c�:��F��W�hA5�|�e
�t�}	
�h�e�h�e
�r
�_	�V�W�L	�S��,�-A5�s��%�h�e�d�e�E�D
�+�
�$�#�H�I� �Y�A�	� �Y	�d�#�N�O� ��@�G�$�Y�I��L�M
���#
���L�M���L�L�A"�t��z

		
�yQ������}����'�C�x�2���w�xA?����A�D�5�.�I�H6�� �7�B�U�K���)�,���
�<�D�q�4�1�c�L�A�B�u�p�;�h�%�f���>�)��}��R�$�k�TA&����Y���
�~���K�J�����4�`�"�S�R�O�N�)�(�)�V�P�U�T�1�0A����&>
Q
���4���r��s�
��*���z�HA)���E�6�D�s�n�Z�U�+��B��A��o�(�g�|���-�8��O�]�&�I�I�y�r�K�H�K�$�A�|�+�1A���"�|�}�0�S�c�f�i
�t�uA����8#Q	��)�&��Q��P��Q�	��$��iA=�
�R�C�5�v�5�4�!�B�=��"���!���4�5�~�E�D�6�]�$�q�r�!� �m�l�G�@�W
�~�Z�3�6���&A"���\�B�C�F�G
�}�l�q�o�n�o�,�?�o�*�E��>�1�p�>�1	�0�I
�H�I��	A���?,
"
 Q	�Ƹ������J�N����P�<�
A�ӌ��=�k�d�:��	A�|�`A�ܸA�Ё�R�K�H�I�y�j�T�A�.�9�H
��9�y�D	�M�6�1Q	����L�y�Y�?���e��~AE��B�&�K��k�%� � ��K���$�\�1@�02�C�3�G
�h�5�Q���6�q��4�E�T�C�P.�D>�H�Y�/�P�[�T�^��e�V���s)�f�-�^�-�
�.)�M�$�i�8�� �$N���D�C�w�j��pA�ܰ9�?�B
	�(Ao�!��H�G�J�c
�b�K5"	� �	H��			
�L�#�/A�0	�'

�p�q�l
�k�l�mA�9�3�&�7�'�BA��8KZQ8
�"	RA�V��*�p�*�=
+$0A<���u!D
(B
V	
"
A�&���<	�*�/
�>�A	AM���YC
?
L

Q	����P�]���H��;�C��*��O�A�ɏ%�$���}��bA����c�\�c


�H�3		
		A%����2�?�L�M�L�M�:�!��s��/�b�3�x�-�d�e�f	�]�(�~�)���$�A�.�)�8Q
�!�4��^�M�����+�*�;�qA��-�;�h�s�z�U�(��>�	�d��.l�O�N�i�p	�E�`�Q�8�a�X�1�F�m�f�_�Z�S�k��6
��l�|� �f�U�z�T�_�S
�Z	)2�M�R
�`�e��|�G�]�(�2�/V���
��;�:�7�6���;�:���x�d�e�	�2��d�t�-�}�6�;�D�
��}�M�I�A���A�6�n
�
?# �:A�e��A�]7&�A?�1��g� �!� �!
���	�k�x�o
�n�m�h�e�d�a	�L	�M]�H�I�F�E!�@�E�>A&�=��C
{"

p	Q
����\��`�a��u��x��c��<�aA� �O�
���/�`��6
�W�V�]�`��A'��
���
��8�;�8�=�<�D�C
�A�B�<�=�G�n��}�|�q�L���FA���}
C
�F����5��#�Q��h;�>��^)N�"�s=2�ZC�<��}C�<��i4LC�~�E�-C�B��SCB�@��~�%�H�q�A����Q�R�{��[�_�E�:^�C���l���t��q�0?���o�r��%�?��j���W�3��J�OG�b��E�3�� �
G�N���Y��O�D����g�/��K�	���V��_�[
�*%�)�2�).����A�d���	��t�Z�
�>2�8
�t�C&�>�e�h�
$�,�I/�^@�
!�W%X$�
(�}:�C�I�	�S��t+��;;��I�o�oC��@܉��Q�n�,Ew
��P�>�L�x��72~
�B �5I��'$��'�I��!,S
	[�W�X
��(��h�ol�Q�;��z�)��f���V		(R��i�2W�����B��9���E.��gO
�c�(�
_��v�<.��TB�-0�,�|"]	��e~2u9/�5s�R
�D("�Y#�l���$
�Z��f�
�(1�	�%�N��l�B7�=(��l{
�K�{��$�$�64���&�wB(2 ��>
�L�#8-��a_�iJ�v4�q���I�w7�
����6��@���<�m��'\:8w��
H��&��{�{"���?�m@93*G�w��)�<I�i�b�%$�<�.c��{�U�v+� H"��8�JR�g�nz.��2�~�.
��p
�go��y0
�;�s
�p�Y���>�H��k"��K0!
�$
-'3�\-�� `�	#RaF�E
��'��^ �>� (6��u �+"��h093@9
�d&��B
Rv���{�2��*�9�/C,�@���Yb"��X�:�[X��M��iB�(
��*��Y��8�]�m��Yh�����Q�S������,�6�A��I�|��t�g��g�f��8�I�*�,F��	��}�0R>�xA���)(uA�z�W�	�[�7�f�e(��g�N-�w&�dA�(��OQ
��Z�G��c��>��_,A����"��6Q+�0�F-8	_N�?�$Cd5�&�oD
_",a�w�~��e�?nA5Z9:P�L�}/Q�]�w�I3"P!�l�GjK�[�~�8�A���	5qpq2Fw�S?	B!��*�Mz�	A��L�g�N�h�\A��a ;>%e��!�$�*�;�A�G�X���0na��s���F�#�*�K�P��D�\�a��m�9�=�{�i�g�!�*�U�y�	�*�$+R�J��
U��aN���� �9a�'!`	��v��K�7�H�M�:���!�0�[�Bq�=�e�4�1���C�8�c�X6�e<�p(�w*�����%_�7J�E�2�v�7�o�*�_�{���'�7.�@�+�[Z�f�C�u^��+�!/�(�|�u=�J���l�,��h�<�l^�~�I��-�B(�M^�Z�L�0R�BL�Q\�`S�h0�s�=�;��=�>�T�d�f)�og��?�j�e�d�f��|T��� �>��\j�{
�u �$�	��.�!�%j�9�<��P�{�]�G�$v�>�A5�KV�\�g�u-�	��
�/��
��[�/Q�>�K�tx���g�-�1��U�~��r�Y�W�K��K�F�IP�Y��y�|�G��k�J�M�r�_#�hF�r�t�d����C�_��pT�~�m�33�=�5�i(�m�@���1��E�J��bP�x"�~�:��+�*�0�7D�;�>>�F�H�`�i	�k�m>�{�~��G��0��
�T�;�so�{A�%��	�V�%�(a!j�!	��jH�v�a�r�n�F��~�0�7�7�E
�&�5L�?���X�^����`�y�/�"	�&�)�_��V��~��\��D�B��Ra t�"	�(�I��a!V�9a
$��c�I	��+	�2� 	�]�h�v�.�-a!��,�*�X�Y�)�p�x�C�B�.�a�L�K�b�q�|����� �#�(�*�3��]a.��	aW��sh�|��k.���v���q�e�O��p�rN�m)�v��a�u�C�X)�\�^�;�q��w������=a�G9�K��U�@�^�l�t��	��#U�(Q�,\�2�t�S�-�j�z�|��
�S�#�>�0�
�:U�?x�HK�O�K�m�:��4�t�"��8�-�@�H�Ky�Z�_S�m�
����
��9��L�(�1�2y�<�^�T��r=�x���E��� �g�0�f�Q�f�o��|��
1�&��(�)�p�O�v�m��}�h�1��<�^�E��Lr�S��Y|�^a G��g�8�}�(	�_�Q�B�iZ�:�6�I�D�O��E��F�u�P	�l�wa�5]��=���x�@��E�R��l���~&��P��_�D��k\��sE��z@��+���n���J��T���9�c��"��M!��R��
,��'���k��-��0b��:��<n��I�(��_�F�����=e���|��R��V�"��p�'��q��H$������K���Y1��*���R	��y��m���
��8�����5B��?���'��T��Zk��H��k��o�>��X���F��'��)��,���.�l��K���<�W��u���D���(�� ��R2���M��M>��V@��^��d:��p�
���$��g=��P�����2,���#��D�`��i�"��@��BX�����$�6��X���G�[��S��V��e|��i�g��t��4�����)��)^��2���?�;��N��Ps��]T��g;��nu��*��L����F��.1��4L��B�3��Fq��L����[��.��*���^��*�!��8��h"��"��p{��yQ��d��	=��P���#���'��b��5�t��@C��k`��H�l�����i��x.��W��3��
$���������"N��'\��L	��N� ��`.��l��?��t��w�(����BW���
��E�%��R���da
���]��L!��T��Z4
��_��jB
��l��w����+��ka�=Nt��W�U��hn��x��{p��	��0����
��+��%>��.	��1��3�g��N��Un��j��mZ��uI��}���
���L��!{��)j��3��6���B��E!��J�a��m��qo��|!���
��"�J��8��;��?&��F���Q{��\l��d��h�U��x�Y��	G��:�������+q��;��=�0��O��Q;��W��Y��_���n&��r��u�)��$��(1��/�6��?��A��D���V%��[�x��n�1��j��
������((��+��.��1/��6���D��F��J	��La��R���>���P��3G��8q��CX��H���R���jH��q�H���<��Q����p��"@��)��,�k��@�R��s�	��y��8����>��$���@��D�N��U���g�O��
�
����n��&��(-��1�Z��N��P9��U[��e\��pM��yI����R��
���E��$�A��A���NH��T���hg��q%��wI����W���
��-��0��3�|��V1��Z$��^#��a?��hJ��q��t0��}	���I�����#���9��->��3��6��;��>��A��C��F��I%��O��R3��[6��e�"��~2��
���W��$���,��/��2���DK��M1��V�q��z��}���s���� 3��&��)��.�D��L��Q=��W��Z;��`:��g{��l\��vC����L��
c�<�����A��E��ā%��z��P��U��p��a
�`�T�/����$�{/�%�$p���O�Tq	�5�[�a�]�k�e�c�g�_�_a��"	����0�c>�G@�IPK
!<<؃�3chrome/pdfjs/content/web/cmaps/UniJIS-UTF16-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�UniJIS-UTF16-HA!��bA	 �U�?�dA��M��6Q#��t&+"'�+�B�9Q%�G(A%%�o/Q	%@�uA &���z�wz!O�0�/
]��A'�A0��KQ3��J�W�V�mA3�


��K?�A3�1$1
)')��q
A3�L


	a%�9�7�=�;�A�?�j�]�`�U�X�w	�~�6�O	�c�]�H^����[a0�k�g�6�E�P�T�\�b�h�t�w�|�~����ma#��^�d�I�X	�M�XPK
!<���`[�[�3chrome/pdfjs/content/web/cmaps/UniJIS-UTF32-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�#C\a
!�GS�k]>�d��_?V3�C���M�T�+ 2!+�V�S�L�IS��tA
�~$�c���
S
��n$�c�U�P
�&%$)C$�)
�f�7�![S .��s"'��A��"�}
�";�9�6"��B��;
��|�,"�m����S/P���'
��j�>"�kOL
i��O��fO�>��_c�0
�m��("��[��<��SC�i��P.S��2��j���	�
���C�����k9S
P�X��"��"O(
��zC
^�W-*'0#rSl�6B''�$�c�bCx��1TI�6��7�ZS��94
M�-�|3��)F�Cm�b	>M#=:B�@�;	���J�<O@�0�G�"8��F.��q.�J	�N
M�y�u�>�:�E�<fG>+	'c���h	W~�9�2	
�|C	l�
;ls9'�
��V�h�i� N�.:�K� �e�,�d�[�5�J��|�O�r�]C3 Z�Z�+�.�/�?
�x�vC	F
�Q'�

�j�w�
�*�"e4
�?	�^(��D@�����
�9�zCA��c��,
�`��2�H�j&�_;/1� !�f	�P�c�Z�w
65
�h6

4!�;4/Q�fB��.+�T��*�
U	�9�.��53�6�?C��$<(�#��Ca��J��#�"k
l	�)�@�]VI�b0#
8%#
#'`�p�|�k�`-�k]�FI�[-�&�3�w;<;� y�e�5�@T�
K�9G�$��+��C�U��p�t�m�Z�$�;�M�
�v��3�\�7��V�=�/�j�U�_(�A�K�v�9�y�.�@�7��#�C.���eC.���:C���K
�_�!�W�:�r�S/�0��i�� �w��w�8�5�R�t
�{�v�\S/�S��h�>�
�c�r�/�qS�/#�&�?�
�C�Z��c��c�T�c{�i�7�&&�S�����o���(�_�4�u��w��$��n��B�S�^�n�}�H�a�$�/�7�R�4��	�w�>� .�Y�O�@�4�<�{�0��P��S�_�(��VKp��A��X��]�X�`�-�t�G�W��-�e�"��N�O�x���e?�K�j8�]�/���8�G�U�F��
��a��h�(�.��\�z(����$��X��[�o�p��%�t�:�+�T�}�R�a� �G���6]�C�3�!�Y�"�o5�n S/��N���<�7�J�a��r��C�8��w��z�C
/Ӟ~/�_�H
�+��<��D�w*�3�`C00�:
�a�aC0<�Bc^S23�Op}��$
ly�nC2Q�S2��g�

�D�5�,�=��2
���^C3�pC2��ZC2��{S3�j�r�[�Z�CN�0��+C23�"��
��
��*�?�
�#�F�?�#�,�y�`'�>�M�;�:�J	�=�K��A�&�w�P�y�{C3�x


	�5
&C4�$�&�b9�S�6�m�h�N�	T� ���
�`�(��2�n$�Q�@"�Q�cC4��lR#�.�g
O�8o��x�m�jH�9C�'4��Z%=,'0"�k�z�p�k�H
7(
�p�a�5�5F>5$�LK&&.	?��i�GT4�\�#�]�r>
�K4##P\	8
�9	�P" �Z�A��8>F�2�i5%"�$�09�g��
�B^�g�6"
�\�_�,HBP
&�7J$(�*V
8�C�n
!4
3<	5&Y�1��<&��C$�`�j0�Q_ 
\(�pD�G��$%�	�f@�z�y6�d	$�.~.B�dC�D��&�r�}v%Qo�
�3SN�*�9�!��#����}�D��R��1�U��CVN�L�^�i��O�2�w�l�i�@�m�3�2���M�(�7�X�/�u�ve�	�R�\��g�f��N�� �z�3�
�i�Z�j��9�{�:�	��~�W�d�Q���}�x�X�S�w�h��N�
������I�@�G�F�5�K��r�#�yC
N�Z�
�a��e�d$
"��
CN)��U�x�y	
�t�u�T�"�y�x�w�nS	N݅����g��N��/�F�7CN��9�z�P�A�,CN끆b�F�GCN聤]S	N��Z�4�d�l�m�g���p��SSO��a��	���4�<��k�Y��M�'��\CO�q���L�xCO�kCO��f�x�3SO3��d��)��(��w��z��k��G��.��Q���PCO@��f��u����N�;SOM��\�u�C�R��6��y����a�r�/�����j���b��p�ACOi�)�Q��0��\�i�COz�pCOc��k�	�5�:��>�?S
O��%����k�!��
��W��V���������r��oCO��.����3	�$�[��q���/�5�l�CCO��<C
O��s��M�L��<�L��z�S
Oȁ�~��%���*��������}��|���COז�F���{�X
�g�.��B�0���
CO�l�(CO܁��#	�"�?�$	���(�u�dSP	�T��`����2����x����p��e�a��>��wS
P�*��6�m��O� ��~�z����ySP!�B�z��i�d�o��f��
�I�T�;��(C,P6�^�N	��y��X��q��A�(�t�'�F���e�O����?�n�R	�E�$�q��)�~��G�>�@�/�.CP@�?
)#CC2P0���� �t�u�5�{�$�y�x�y�5�{�&�'�&�}	�|�7�6��-�0�5�}�0�1�(�;�:��)��
�o� �1�9�lS	P၇
�4��������L��k���+S
P�_��J�R�T��j���I��CP��b���E�h�	�qt�E�D�9U�CQ�
CP���+�����8	�)�(�C��(SQA�8����!�1�h�d�;�^�C�:��d�?�)��r��gCQR�r�9��B�?�A��)�h�.�7�Z�m�[�
�M�`��sCQd�JCQS��;�I�F�_��T�_�
��W�>�H�S�SQ��~�#��P��3������6�7�#�"C!Q��Q�3�+�D�w�v�s�g�f�o�t�1�j�D���Y�X�/��[�~��+�*��5�R�p�3�2CQ��K �a�`CQ�����	
��1�`��j���.�-�<�=�'�P�gSQ����p�Q���u�s��L��E��zCTQ��5�F�}�
��=�}�A�j���C��:���y�x�Q�P�)�?�I�.��-��B�;�Q�$�`�	�]�`�9��(��y�7�>�a�R�3�8��&�x�}�r�3�U�U�p�{�$�7�jF�P���Y�Z�o�Y�n�O�$�i�h�/�0CR�NG�a>�`	�X�Y�GC1R��o�#���N	�1	�$�g���Z�'�+�P�'�C��*�+�N�M�N�#�I��P	�O�N��$�!�z�g�
�Z�}SR�R����)�h����v��a�`�o��*��=�3��n�A�@CR��|�{�1�t�K�b�
CS�SCR쁥a�
�H�B�g�#�B��,�E�DS
S�M�!�x��V��u�B��0������K��s�SCS#�O�A�@CS$�UCS%��i�K�T�U�)SS8�S�{�m�o���U�Q���x���T��KS	SE�V��m��
����
CSQ�p�9r�]�J�/�h�.�<��� CSa�!CSY��q�#�J�%S
Sl�"�J��W�+�^�X��;�^C#Sw�`�S�N�{�|�I�H��U�
�p��H�J�y�x�A�n�P��{�c�8���UN{�<�&CS��WCSy��	�'��(�R�I�L�Y�U�*��*�U�T�-���~�W�V��(
�V�{�7SS�Y��J��'��L��]�Z�Eq�5��
�6��#�#�w�P�H�W��E��#��p�7��1�R�>CS��r����?�ST�y�/�0�i���/�^�E���`�`C&T��l�C�8���C�<��b�*�t	�;�:�W��}�]�V�t�
��/�B�'��� �vC	T��4�O
�0��
CT����"�^�_�#��]�\����G�TST���%�X��m�y�����Y����CT���H�7�%��p��7��CST����t������z��	��<�6��5CT������ST���L��}��B�8����=�8��2���$�0��p��{C%TǢ
�5�@�=�L�+���m�n����J���o��5�}�:���+�2�7�.�m�=�*
CT��^^�C'T��2�D�E�T�n	�o���o�n�o��E�\�a�J�K�^�j��
���O�N�s��p�Q�6�q�S
U{�4��H��S��Z��g��l�n��K�C)U��<��z�o�t�
��O�f	�O�`�M�c~�&��

�4pi
�M�P�1�W
�~�o�w�h
�YCU��_C,U��:�Z���r�s�r�u�	����s����}�|
������j�k��i�b�m��z�%� �SV/�U��8���X��<��C��X��Y��\��[��TCVB�_	�6� 
�k�v�S�V	�1�#�<��k�l�Y�\CVS�E�kC*V;��*��{�i�h�x��k�v�w�v	��i�f�g�d�

��u��~��g�d�%�*�r�}�%� �a�b�%�*�t�}S
VȢg��L�i��H�w���3��8�t��/CVӢjSVעk�_����=���J�e��9����
��CCV�
�R�1�� ��e��V�}�~�O��d�K�J��*�y�r
�`�W��6�.��y��_�6����;�4�c�b�%�1�V�y��v�K��6��_CWY�`FCFV��P�d��u�t�t�k�x�p���m�h��y�{�p�m�h��u�t���t��o�r�I�H�y�	�I�H�{��x��z��y�z���	����W�R�����c�-�~���S
W��e��x���O��l�����v���3C9X�
�y�@�D	�c�b�)�7�O�~�_�Z�Y��%�.�c�L�?�B�54�\�I�a�@��.�V	����g�v
�S�s�D��M�L�U�P
�#�� �O��>CX/�21�(<�>C2X��a�y�x���
���~�	�v�o�~�	��~	�}�f�	��]�p�|�
���|�}�|�a�t��z��e���
�`���z��#SX��h��y������z��_��z����vC7Xܣ1�)�t�|�:�)�_�P�G�|�3�2�k�j�-�1����:�
o�|�q�R�#�P�Y��6�]�\��P����c�^�c�d�x�O�T�[CY�iGCX݁�y����"�"�|�/�.�/�w�$�%�$�;�8����=�<��1S	Yg�M�y�z�Y��J��w��B��[��CsYs��&�t�3�2�u�4�T��|��8�i�z�O�a��E�"��3�.��s�A�|�$�u��� �X��]
�o� �B�_�r�/�2�O�J�g�%�t��#�!�D�� �=�<�;�g�*�
 ��K�L��
�=�V	����	�G�J�	�#�$
�A�B���w��a�_�H�9�t��!� �]�\NOCY��n�CY��q
�
��m�l#

	,4�?�>C;Yy��j�?�%�`�?�<�?�'�&�8�9�6�3�7�67�=�:	�;�:�����K�J��������
�aC)Yr��	*

!
-IS[����H�G����H��I��J��[������e�[�h��*��{S[���C�l��5��*���}�p�>��,�bC[��n��D�O�R�z�5��W�����CC[���w
�C[���=
S	[��S�:��"��g��G�t��:C�![ɤ���e��J�O��B�W�M��o�X�+�D�)�(�G�B�/�}�L���/�/�L��}����1�k��'�s��u�t�S�ZI�R�2�e�b�i�/�P�C�B�k�`�o�`��s�n�w�E�h�#�J���	�[�d�
��� �;�2�
��w��G�>�[�_��:��K�F�O�J	�� �_�h
�s�r	�!��w�r	�'�>�e�d�u�H8		�o�n���5�KC[�sE<�G�C�P:1bC\�*�?	�2N�O
�N/G�%�C-[΁�z�{�z5	�
��������5���

�%�&�-�0�5�2�5�6C[ׁ�z�$2�%
	"o#2$CC&[́�D
$ 	

"#


S
]����c��������o�X�Y�e��PC0]�m�	�W�^�y�s�j�e�+�x�7�.�T�q�4���m�h�[�=�
���o��O��"�� �q�b�G�B���a�`
�!�>�UC]�� �}�|)C]���
�P�}�|�U	�-
�z
�y�&�T�M	�v�RS^w����)��$�/�7�C�$����)C&^��?�2�2�31�
��-�g��n�g�C�g���`�o�^
�U��&�n�K�L�9�/�l	C^��0�r �d�_C^����N�}�z�U�'�&�'�x�U�'�zS^������t���5�j��2��W�����W��V��w�S_���-�,��U���M��L��+�`�&C
_�w���F�Y�i����s��t��}S	_!��d�F�^���$��h��%C_-�$
�3�^�5�z��3�B��~�w�z �1��;�X�O�t�d�)�i��iC_4��!!� C_.���-�=�<�B�f�+�*�	��C�@�A�(��!�	�H��S
_|�t��L�b��1�-�`�F�7�6C_��9��-�R�h�e�}��F�GC_��<�"�#�"�!C_���S	_��>���!���f�}��9�e��dC_���-�L�*�w�h�F�W�P�H�;�	�J�?�6��|c�?�:�m�v

C_��&C_��8����3��� ��5�,��5�4���9
�*��S`�V��������p�*��'��z��s�C�C5` �5�&�#�4���
�+�2�S�J
�7�8�g�0��>!�!�F�#�o�A�>�o�:��(�~	���I�2�k�^�!�pC`]�'C`��&���;�(
��������)�(	��E�D�-�,�)�)S	`���7��
��8�R��{�f��^��_�S	`��O�"��Y�"Z�����C`��c�&�?�>�	�z�C`��
C`���!��.�1��'�"S`إz��R��/����E��f��5�c�4����GC`�g�K�F�>�S�F�_�^�#�&
�k�]�D�%���"C`��C`偨@�7�)�\�=�<�?�>�;�z�>�;�%�$�8�A��D�C�B�?SaD���j�;��I�����}�/�e�l�C
aS�!�Q�L�i�q�R���Ca^��(�u�tCal��LSan��6����\��q
��v��nC5a~�Ge��S��P	�Q�i�a�
�7�A���z�5�.�y�z�i�n�=�P	���w��Y*��Ca��zC%a{��*�'�&�L�w	�2�B�O�)�v�E�3�v�E
�r���4�p�q�F��~�Y�V�W�V�S�R�a
�/S
b�p�,��8���9�#��Z��9��:�$C?b&��l�o�n�+�m�V��m�4�w�
�I�A�3�$�S��M��)�
�(�3�<�y����r�=��,�w�n�/�x�T�K�m�~�5�r�Q�{�*�|����s�d	C
b)�^�C�_� .�=>�<Cb'��f�e�`�_�^���a�-�,�n�u�\�i�q�p
�lSb̦i�	��"��)�M�F�-����H�1���5�p��v��9��AC;b��M
<�B�	�=�D�
�k�h�f�3�N���Y�L�U�q<�
���
�"�2��1	R�#�~	�
�1�w
�X�#���U�V�?�<L�,�?�w�<�G�8�s�d��yCb��e

�c�b+C*bꁨx�{�q�h�k�j��~��~�k�h
�e�b�|�}�e�`�}�|�c�b
�a�`���7�2	�a�^�e�dSc��r��]�#�k�h����������D�E�j��A��VC�9c����2�I�L�g�p�5�I�|�q6�{��2�+�W�
�9�>�q�|
=	4��c�X�q��>�8	�}�t����)��V	�C�:�q�7�E�l��M��F�P�/��c�n	������"��v�1�:�E�@MPAB��:����s�#�
��� ���?�@�/��V�#�4�(�w��\�|���E�&�m�`E�!���1�0�7�"�H�@��
�h�9�&�2��i� �^�5�.�u�x���O�P�O�8�A�]�H�n�{�e�Z���;�6�����P�e��,Cc���O<,� <0S�+�~�1Cc��s
�1	�N�`1
N�=�@-< ��Cc偃l�JXsC&c���"+*			
5		'	
$CcꁗJ'
9*1		C-c���%!	#
"4
Sf�k��6�o��w�Y��<�+�(��W�}��R��k�e��N��+�S��O�l�!��R��Cf4�j�*�/�>5�G��D�a�Z�_�BCf3��v	
�.�'�W�~�Cf7��L
S
f]�q��6���3�����V�=C-fi�i�R�g�,�C�c�T�#�p�c�Z�� �{�f�q�d�Q�R^�2���!� �%��2��1�@�Cfs�"%C fj��Q�	����S�L�}�5�4����(�)�?�f�7�z��[�Z�E�D�E�D��Sf���'�P��m�	�[�p���NC.g�K��$��=�<�Y�n�h�w�S�K��3�Zm5�/�B�}�N�.�I��6�9�J�1���'�"��-�&�#�"���P�$Cg�(WCg��
�����i
�7�6�f�a�`�S�O���Sgl��i��;��H��M�,�y�x������`��;C	g|�$�}�b�"�9�$
�gCg{��2�k
�j��YCg���j	S	g���� ��#��L�T��1��Z��o�>Cg��n�t�]��x��!Sg����0�8�9��]����[�:

Cg��(�P�N�K�F�%��m�F�#�H�P�6Cg��+C
g���{�h��~��~��)�&�|S
g�6��h��o����e�%���$�Cg��*�d�}��n�o�T}�1�v�_�g�F�Y�b�U�]�dCg��
�'Cg���J�%�z���~�i�h�~�}�5�0�~�}Sh;��~��-�,��~�x��{�	5�
��� ��C"hH�
�j�{x��X�>�9�j�5��I�^�?�& !�}��u��%�&�}�~	�[�,�E�ChR�,;�!ChI���v�w�r�3�@�C�4�5�4�w�A�&��u�h��9�6S
h��K��v��o��6����Q�����)C	h��Q�[�]�p�=��C�NCh�/Ch���Sh��0�v�6��5��4��O������|��gChߘi�
�I�Z�-�&�e�x�DCh��.Chꁪ��A�@��]�pSi�� ��s���d�]��
��h����2�q�t�d��t�=����C@i�@�c�
� 
�e
���}�S�

��L�\�'�$�=K��1�.�//�c�(�}
� �?!�Y�
�B�/�n��d��g�2	�V�	�+�6
�}�W�N�K�
�UD�\�3��B�
�J���S
�H�e�l�)
�Y�L�uCqi�p+& 7,

 8=$	


&
18

��		�o�j/0
	
	ad	Cih�2/.��M$
cC i5�5�s�v	2�[�Z
�
	�X�U'J
1C2iB��
�4�;�4
�?
�>	�E�D�I	�L�Q�P�U�Z	�g�h�i�j	

�
�
Ci��"	B5X
	=H�R
�O7Ci��72#

S	k_�b��n��o�m�V���p��Q��nCki�2�n�O��r��t�E�=�!�
�.��C	kl��c������
�Ckk��?S
k���I�[���o��H��G��DC5k��u����u�@�A�r�y�2�o�2�(��4�#����3�~��X��
�!�<��X��E�^�v�m��l�Q�K�(�'��Ck��=hC/k���j�
�1�q� � �#�`
�;�'�p�u�&�L�u�t�O�N�O�+�^�k�p�k�&�>��s�r�A�@�u�y�("��A�3�y�j�u�wS	l��
���^�)�M��4����<Cl��1�2Sl���3���-��u�� �;��m�@��2Cl���c� �j��P� �(��4�X
�'�H�H���&Cl��IC	l���\�w�E�:�y
�E�;�vS	lρ�`�o�n�}����&��7��6��'C6l٪!��%��B�i�l�O�6�Q��h�%��'��(��g�D�(�k�r�E�@�Q�l�5�x�2�e�\�)�j�
�{�p�l��|�`��\�2Cl��B)jC*l܁��-�j�?�+�f��F�#�^�k�h�i�h	�C�B�m�.�O�r�s�L�M�e�d�J�1�h�9�*�]�h�9�*�]��NS	m����.��{��p��g� ��L�1��_Cm��mX�
�;��g{�l
�A�J�]�^�=�6
��"
��~Cm��G"Cm���)�`�"���o�H�I�J�K�J�U�w�Q�,�j��mSm�K��N���K��C������X��	�T�#�%�,Cn�Z�'�r�T��]�U�,*�1�Cn�FCn��uSn�^��
����t��f��U�S�@�rC/n)�9�6��~�w��E�>�/�P��2�q�t�C�F
�?���f	�=�R�{�t�n�W�G�4�{�t
�a�T�y�z
�+Cn9�LbC"n2��&�)� �&��h�v	�{�[�r������
�%�����M�B�$���+�*�)�S	n�����C��8��e�X���	�C#nыH�8	�3%
�p
�)�41��0��E��.�e�8
��e��+���
�

�c�@�CCn�8)�"
�m�l���q
�p�f�[�X�Y�\C	o��	
SoW��A��o��L�U���S��j������!�SCHod�k	�8X�a��|�	�>�.�k�B	�~�%�T�)�s�M�	+�j�_�:�W�t����C�A�V��;
�>�g)��	�h�|�%�
��(�M
�
��O�h�Q�@�C	�x
�C��u�Z�	�|�K�G�F��}#�m
�_��D�A��"���	Cgof�DM




�A�B+2	
uv.
/		

#

Co��Q,?�_�^�	� \%�WH�X
 �{+�|<�}'�<C(o}�

�%�$#�?�! *
 �n�m)
Co~��3

%]&.?9"2	PC.oh��C'	*	" 
!6*,Co���3U(�0M�1�lA"#C(ol��	">E
!
	1

	
S
rW����+���<����d��Q��Z��}����'�J��JCErg��
�?�~�	�X�Y�~���[
�(�I�@�g
���_
�p�`���V
�1�\���n�N�8�E��~�Y�=
�v��$�?�j�*�	�
��Y�e�N �E����!#�E�_��Z�q�v,�Q� �i���H�u��.�8CWrt�k	

		7	4+	
)("
Cr��aeRE5�w$�xaC8r��;�%�$3,(�`�a	�O�N	
�H�I
�@�=
		�WCr���I�(;�
85C6rx��
&
	,	#	Crh��HD
*
�
</C/rk��A	/ 
	@ 	Su�J�]��V�*���-����]�^��d�����k��/�0C
u0�>�<��Z�
�~��E�HCu/�rCu6���E�!�dS
uF�S��^�g�������X��=��d��'��&CuT�^��JS
uY�y�4�q�!��*�H��
����OC	ud�V�E�#�f�W�Z��D��ESuo�u�-��h��k�~��d��m�-�(��D�.CGu�@�@��'�U��[�j�e�V�Q�P��t�k��|�?�g�"��������O�N	
�w�z�[�V�a�d
�+�s��c�zCBu��~
�2�3�B���
�5�4	�
�	�����	��K�D�)�(���I�(����!�+�H��C�D�E�P�]�\�S�B�o�U�B�s�Y�B�i�h�E�w�P	�h�+��Z�[C
u���	WS	v������O�7�o��`��!� �
CHv��w�
����;�`��^��P	�x��]��
�<��2�N�p�g�Y� �m�\�H>�I�{�Z�'�fp�a�&�{�*����'�T���=
�R�#�a�G�
��G�y�6�1�d�{�P�Y��6�;�=�#�L�0�D�A�� CVv��

�%�&	
	


$.	
		=:Cv��y�=��>�o�>�p,�5C v��7%
��
��;	
��:�!5)
5Cv���^
`Kb*>OC'v���

."#?	"	
	C)v���r
	
E$!	8C4v���!		=
" +


S	y:�T��H������y��@��C�c��$C1yG�Z3�^�X�J�{�r�[�`�t�?�W�p�O	�T�$*��$�Y��k��:�(��i�h�[�\�	�y�}��W��:�;�S�T�6�%CyE�;�v�	�N�O�J�K��T�K	�~��|�F�G��~�D�E��m	�n��CyD��Y
Sy݁�g�W��k��@��)��(��)�m����G�Cy��f�;
�-�D�$���Y��I��v�
���SCyꁙ&�S�P�E�%�$��8�C���>Cy�k
S
z7� ��\��>�����t�VCzB�6�T�?�@�]�	���b���s�t�Q��P�yCzE�K
C
zD��+�7�H�K��@�E
S
z}�*����1������d��)�?��2�ZCz��+��Z�>S	z���Z�>��5�g����C	z��x	�n�i�w�t�j�G�JCz��QCz���{
�A�&�#�"�#S
zËT�@��B��=�������^��O����
C
z��	�	�n�������#�-S	z��d���.�6��1�J��d��wCz�z��Q�0�7�y�7���
�	�X�N�#Cz�c�C�\�5
�V�WCz�	#S	{%� �j������h�9���U��vC{3�J�K�V�o�M�C{1�Z	�T�B�'C{0��S{K�R�����'��k�0��N��S��v��C{]�i!
�g�� C
{`�]�(�+�L�M�&�|�	�-C{j��	S{��)���2��	�.��$��)	�X�W��"��_C{��{�5�(�p�o�g�LC{��bC	{����3�0�+�*�+�.S	{Ěc��0�����b��y��F��%CQ{Ϯn
��Z�.�-�<	��
�z�q�{�r�u�v����#�&/.�y�x�K�Qp�(�!�-��0�5�#�E�B�'�'�*�s�6�B���;�fC{��c1


�C@�BC'{ԁ�
�h�i�f�i�z�l�m�l�m�l�q���n�u�t	�w	�t�u��n	�g�f�eC{ہ�&		)
S	|ү&��H�H�C��3��z�����,C|ܯ'�E�/�+��1�2�u�t��C��V�c�K�(��C|��v�H�I	�J
C|遭1S}��2�����'�K�������k�I�0�c��T���HC} �I�L���*�nS
}*��6�����z����=����3�_C}5�9��5��XS
}?�8��Z�Q����.�R
��X��1C }K�5��,��t�S�t�/��{�r���k��Y�T�9�V�h�1�H�x�
C}M��N���
�T�5�!�T	�W�X�>�{�z��Z�>�A�<��BC}Q��8
S}���6��O�S��P�s���b��E�2��T��M�{�����C!}��N��(�l�u�X�#�P�?��*�y�k�`��	�k�;��k�r�/�*��d�2
	C}���8�I	���;	�a��9�i�h�i�fC}G	S	~�1��2����Z��P��Y
Ci~&�N��{�	
5�3�N���w��<�S��t�8�Q�|��O�j�m�b�T�I
�)����
����Q�T�d���z�4�M�L�!��G�H�!� �b�_�?�6���?�6��K�-�x
���+1�Z�[�C~G��O�N�+
/-
C#~'��B�[�Z�,
�1)�0�1�0�1�0�+� �&�%�"�����
�
���C~-��L/�)	&	S	��>�<��j��i�6�w�r��b�S�
��c��O�O��.����g��d��O��N�������C��@
�Y�D���
�Q�P���3�2C
���n��|��*��|�}�|C���f	S	�o�F���6��;���>��9�IC+�y�Q�i�h�9�Q�$�O�4�;�@�x��z�Z��I�$�T�u�i�T�l�Y�z�G��'�N	����8C�{��j�

�
��~��~��~�/�.�
�/�.C����p7S�큙t���!�������5����F�����������}��C%��7��n�~�e���l��O�e�v�k�,�;�%��F��,�3
�h��
�.
�s�v���q�A�j��>��8�C+�	�d 		
C6���y	�����
�G�F�z��	���
�
�?�F�C�8
���t�u�t�w��N�L�O�@���u�O�N�v�u�p��(�p��,C���y
	S
���M�r�X��\���&�M��0��eC��	S
���c�-��x�p��Q�	��\��]�WC�!�=�0�v��e�y�x��S	�2�>��7��6��-��q�\�w��NC�@�$���?�@��^�<��7�@�{�z�9�M��a�#C�<���n�m�5�0�1�6�w�v	�^�W�c�\�V�Y�I� �Y
�C�C��
S����+�R��?�k��~��u������(��-��,S	���E��%�?��0��k��j��	��6�JC	�ŋ_��R��k�p�$�e�^C��G�JC�Ё�	�S�ޱE��L��S��h��i��P��+�W� ��tC��)�m�
	�(�y�J�U��T�=�B�;�Z�QC�ꁚ�S��M�L��C�큮%3S�+�T��,�u�t��3��H��U��>��E�S����'�h��BC�@�O�J�
�C�<��9�a�`�C�=��/S
�O�c�Z��p��K��,��U��A��C�w�YWC�b��m�p�
;4C�Z�z�h��/�Q�~�US	���O�N��?��P�l�m��m�C7���[�O�@�*�_�2�w�h�*�!�@�b�I��N�k�V�!�^� �0�f�W�7�>��!�3�<�#��+�6�Y�\%�M�\�*�=d�y�[C����5�K�d�������i
��+�*�K�M�f!�m�l�"�C����9	)0S	�_��;���E�P�)���g��?��$S	�i���������d��5C�u�n�$�A�4��	�C��W�,-�]�C
�s��=�M�z�3
�B��5�4��C�t��JS���`�Z��b��[�=��X�������	��C8�ė�r�/��m�v�;�>�,�)�^�(�Y�
�9��f��
�~�S�Z�;�~� �B�#�w�r�S��h�i�P�i��t�r�CJ�a�w�T�v�_	
C�Ӂ�_���z�
��G�J�K�G��u�n�
�H
�J
�K�TC�́�R%	S	���,��t�e����r��q��p�EC����PC���:C���a�(�b�c�b�ES
���-��z������w�y�*��r��F��?��LCO���>���9�:�
��C�&�q��'�"?@��r�m�
�W� �6
�	�;�D�{��d�/�L�O�N��)�H�b�
��=�R
	
�5�6�C�'���
�	�3�,�Y��t��*	�	�C���b%�G�H(��
�K�JaC-����|
�k�n�s�r�s�r�o�n�m�l�s�t�/�`�5�
�x'��(�%
�C"����m	
"
#	S
��X�*��V�s��m��l��m��|C���>,�Y�(	�_�)�d�
�,T>��&C-�
�r
 


				
 
��+�8CP���#�n�o�a�n�o�\��
�a�l�Z�i�h�]�
�c�b�h�k��b
�U�l��
�3�=�t�v�h�c�b�]�t�}�|���d�[�x�z�y�\�e�d�_�^�_�^�i�h�c�b�a�`�]�	�b�o�n�k�*�)�f�=�+S
�
���|��q��,�k���CC���=��	�U�V�g�)�z��)C��C���<�x�}�|	�m�n�o�j�k�j��m�j��k�j�S�U��C�w��c��h��9��&��!�� ��
�b��.�x��?����HC/�h�/��x�/j�b
S�Z�y�	�
�d�	�d��W�	�t�W� �a;�\�S�C�c�\�F�L�<��q�;�3�d�z��d�+��={�v�u�D�U�\CI�k�3

				
	�.�7�$�

�^�[
C�����
* 
<�[S�Z�G�FC,�i��O�j�i0�h�[�r�d�e$�h�a�`�i�y�x�`�a�`�e�
��`
�a�`�a�`�c��C$�q��G#

 S��|��H��=��T��
�C��N���A�W�!C��S
����X��e��d����A��b��]��\C�*�Z�/�g�`�k��@�YC�6��@	�AC�/��y������#�j�I�#S	�P�.�f�P��b��u�X���I�IC5�[�
�!��6�G��n�8�M���#�V�J�9�
�:�[�>�r�g�6�>�r�u��d�u��-��4�i�!�L�O��9��%�K�/��y�`�&C�]��d�j
�k�j�o�n	�K�J�M�|�N�M�L�s�r�OC�\��	S	�ڴ�3�� ��s��C��#���+��J���U��H�0�o�-�,�\�[�bC��&C�쁚c�h�i�S�:�7�L�Q�8�7�LS	��`����.����2�i��k�aCF�&�5���7�<�h�e�C�D�K�N�'�"�M���F�G�F�T�U�w��h��� �G�J�%�&�o�n�I�[�"�5�� �Q�P�U�X�1�n�.��SC+�-��	�3
�~�
�4�J�}�z�4
�5�~�K�J�O�N�O�7�|�}���K�C�z��A�@��B�I�H�G��9��HC�0��
�(S
���A�E���/�R�V�g�nC	���H�D�%�5��r�
��]��^S
���u���?�a����
��w�X����I��RC"�Ε*�J�b�kd�
�(�u�t�_�<�E�0�CX�.����H�)
��+�>�<
� )�y�R�]��y�Q�.�	4�MC+�ʹ}��	
�r�qP�b
�c 
C8�ҁ�v�7�Q
��5�4���9�6�
�L�
�
���		��	��'�&��F�G�`�&�'�b
�G��	��E�D�E�D�M	�Q�^�0�=	�<�9�}C�ρ�<

e
	
 !S
�G� ��D��A��V����YC�_�|��C�U�*
C�T��1�V��?�>��^�a���7�Z
�D�#�"�G�F��]�\�S	������G�U�V��B�+��E�*C�ʒ�q�|�0�J�!�1�a��4
�P�UC���>�`�a�j�k
C���H�\�i��D�]�\	�C�@��"���G�T�
�f�#�E
�j�O�&�S�|S�%��!�����(��O���:�H�W�~��uS	�3�S��J�+�/�X��]�����#S
�>�X�[��&�v��I����7���C9�I�Z�[�^�A�9�&�$���]�^�M�L�	�c��`��k�A�,�7�2��S�7����Z�8�}��|�!�"�A�\�1�K���\����G�&C�J��@�N�_�^�QK�R�?
�j�]�^
�u�N� �u�t�u�0�c�$C�O��|7
S	��d�J�#��H��]�k��CP��f�N�/�4
��� �;�Z�]��J�O�`��
�
�b�e�u�j��p�W�@�=�Y�o�V�z�	�T��m�"�S�D�/
�*�[�x�h�-�6�i�`�Y�,/e��Wi��E�#�&
�j�1�+0��B�0�M��8��4
��C��<�;�T�2�uC@��v

�Z�[�d�e�-�jH%�>�G�>�?
�>�=
�Z�[�d�aC�,�VC""
)			C�-��~i
'$NC8�/��0�?�:�=�<�=�<�=�:�=�8�9�8�9�6�5�(�/	�$�%� �!���
����C�?��"!&	"	
S
�ֶ8��G��.��m�	�K�t�^�C��|��%��-%�"�KC��:�
�	
���
��zC�쁒}�t��6�7��
��a���,�b
�i�/
�&S
�B������0���'��_��t��mC:�N�L�}�~���u�{�\�	���	�G��5�f��*�w�B
��w�z
��G	�N��u�z	�+��{�Q�s���=�:�/��_�b�S�:�K�NC�O�zR�O�N$�K�JC2�M��M�������	��������z�w���{����	������)�vS���o���B��?�r��� ��)�#��n����L�$��G�tC�/�h�x�I�e�9�eC
�.�[
�
��z�C�3��W�4�5�4�_��m��6�'�9�^�9�o�&S	�i��x�c�6��A����E��$��+��~C�u�d�H
�e	�.��T��
"��_6�"�n�U��h�#�c�:��F��W�hC5�|�e
�t�}	
�h�e�h�e
�r
�_	�V�W�L	�S��,�-C5�s��%�h�e�d�e�E�D
�+�
�$�#�H�I� �Y�A�	� �Y	�d�#�N�O� ��@�G�$�Y�I��L�M
���#
���L�M���L�L�C"�t��z

		
�yS������}����'�C�x�2���w�xC?����A�D�5�.�I�H6�� �7�B�U�K���)�,���
�<�D�q�4�1�c�L�A�B�u�p�;�h�%�f���>�)��}��R�$�k�TC&����Y���
�~���K�J�����4�`�"�S�R�O�N�)�(�)�V�P�U�T�1�0C����&>
S
���4���r��s�
��*���z�HC)���E�6�D�s�n�Z�U�+��B��A��o�(�g�|���-�8��O�]�&�I�I�y�r�K�H�K�$�A�|�+�1C���"�|�}�0�S�c�f�i
�t�uC����8#S	��)�&��Q��P��Q�	��$��iC=�
�R�C�5�v�5�4�!�B�=��"���!���4�5�~�E�D�6�]�$�q�r�!� �m�l�G�@�W
�~�Z�3�6���&C"���\�B�C�F�G
�}�l�q�o�n�o�,�?�o�*�E��>�1�p�>�1	�0�I
�H�I��	C���?,
"
 S	�Ƹ������J�N����P�<�
C�ӌ��=�k�d�:��	A�|�`C�ܸC�Ё�R�K�H�I�y�j�T�A�.�9�H
��9�y�D	�M�6�1S	����L�y�Y�?���e��~CE��B�&�K��k�%� � ��K���$�\�1@�02�C�3�G
�h�5�Q���6�q��4�E�T�C�P.�D>�H�Y�/�P�[�T�^��e�V���s)�f�-�^�-�
�.)�M�$�i�8�� �$N���D�C�w�j��pC�ܰ9�?�B
	�(Co�!��H�G�J�c
�b�K5"	� �	H��			
�L�#�/A�0	�'

�p�q�l
�k�l�mC�9�3�&�7�'�BC��8KZQ8
�"	RC�V��*�p�*�=
+$0C<���u!D
(B
V	
"
C�&���<	�*�/
�>�A	CM���YC
?
L

S	����P�]���H��;�C��*��O�C�ɏ%�$���}��bC����c�\�c


�H�3		
		C%����2�?�L�M�L�M�:�!��s��/�b�3�x�-�d�e�f	�]�(�~�)���$�A�.�)�8S
�!�4��^�M�����+�*�;�qC��-�;�h�s�z�U�(��>�	�d��.l�O�N�i�p	�E�`�Q�8�a�X�1�F�m�f�_�Z�S�k��6
��l�|� �f�U�z�T�_�S
�Z	)2�M�R
�`�e��|�G�]�(�2�/V���
��;�:�7�6���;�:���x�d�e�	�2��d�t�-�}�6�;�D�
��}�M�I�C���C�6�n
�
?# �:C�e��A�]7&�C?�1��g� �!� �!
���	�k�x�o
�n�m�h�e�d�a	�L	�M]�H�I�F�E!�@�E�>C&�=��C
{"

p	S
����\��`�a��u��x��c��<�aC� �O�
���/�`��6
�W�V�]�`����x�")(uC7��
���
��8�;�8�=�<�D�C
�A�B�<�=�G�n��}�|�q�L���F��-�K�	�[�7�f��(��g�N-�w&�dC���}
S
��Z�G��c��>��_,C����"��6S+�0�F-8	_N�?�$Cd5�&�oD
_",a�w�~��e�?nA5Z9:P�L�}/S�]�w�I3"P!�l�GjK�[�~�8�C��CC���	5qpq2Fw�S?	B!��*�MzC��Y�N�h�\��n��Y�5�#�Q�h;�>���^)N�"�s=2�ZC��LC��C��i4LCD�~�%�H�q�A���Q�R�{�e�
u��_�E�:^�C��l��t�q�0?��o�r�%�?�j��W�3�J�OG�b�E�3� �
G�N��Y�O�D��g�/�K�	��V�_�[
�*%�)�2�).��A�d��	��t�Z�
�>2�8
�t�C&�>�e�h�
$�,�I/�^@�
!�W#�	�`$�
(�}:�C&	�S�t+�;;��I�o�oC����Q�n�,Ew
P�>�L�x�72~
�B �5I�'$��'�I�!,S
	[�W�X
�(��h�ol�Q�;�z�)��f��V		(R��i�2W����B��9��E.�gO
�c�(�
_v�<.�TB�-0�,�|"]	e~2u9/�5s�R
�D("�Y#�l�$
�Z�f�
�(1�	�%�N�l�B7�=(�l{
�K�{�$�$�64��&�wB(2 �>
�L�#8-�a_�iJ�v4�q���I�w7�
���6��@��<�m��'\:8w
H��&�{�{"��?�m@93*G�w�)�<I�i�b�%$�<�.c�{�U�v+� H"8�JR�g�nz.�2�~�.
�p
�go�y0
�;�s
�p�Y�>�H��k"�K0!
�$
-'3�\-� `�	#RaF�E
�'�^ �>� (6�u �+"�h093@9
�d&B
Rv���{�2�*�9�/C,���Yb"�X�:�[X�M�iB�(
�*�Y�8�]�m�Yh���Q�S����,�6�A�I�|�t�g�g�f�8�I�*�,F	�}�0R>�xc ;>%e��!�$�*�;�A�G�X���0n��Fpc��s���F�#�*�K�P��D�\�a��m�9�=�{�i�g�!�*�U�y�	�*�$+R�J��
U��cN���� �9c�K!`	��!	��jH�v�a�r�n�2���~�0JK�7!�7�E
�&�5L�?���X�^����M�:���!�0�>�`�y�/��B	�&H�=�\�_��4�1�V�x��C�8�c�X6�e<�p(�w*�����%_�7J�E�2�v�7�o�*�_�{���'�7.�@�+�[Z�f�C�u^��+�!/�(�~�~|�u=�J���l�,��h�<�l^�~�I��-�B(�M^�Z�L�0R�BL�Q\�`S�h0�s�=�;��=�>�T�d�f)�og��?�j�p�\�s�d�f��|T��� �>��\j�{
�u �$�	��.�!�%j�9�<��P�{�]�G�$v�>�A5�KV�\�g�u-�	��
�/��
��[�/Q�>�K�tx���g�-�1��U�~��r�Y�W�K��K�F�IP�Y��y�|�G��k�J�M�r�_#�hF�r�t�d����C�_��pT�~�m�3"�D�B�=��R�3�i(�m�@���1��E�J��bP�x"�~�:��+�*�0�7D�;�>>�F�H�`�i	�k�m>�{�~��G��0��
�T�;�so�{A�%��	�V�%�(��*����$�{�e�O�Tc t�"	�(�I����$�c!V�9c
$��c�I	��+	�2� 	�]�h�v�.�-c!��,�*�X�Y�)�p�x�C�B�.�a�L�K�b�q�|����� �#�(�*�3��]c�[�%c.��	cX��sh�|��k.���v���q�e�O��p�rN�m)�v��a�u�C�X)�\�^�;�q��w������=a�G9�K��U�@�^�l�t��	��#U�(Q�,\�2�t�S�-�j�z�|��
�S�#�>�0�
�:U�?x�HK�O�K�m�:��4�t�"��8�-�@�H�Ky�Z�_S�m�
����
��9��L�(�1�2y�<�^�T��r=�x���E��� �g�0�f�Q�f�o��|��
1�&��(�)�p�O�v�m��}�h�1��<�^�E��Lr�S��Y|�^��+�Tc G��g�8�}�(	�_�Q�B�iZ�:�6�I�D�O��E��F�u�P	�l�wc�5]��=���x�@��E�R��l���~&��P��_�D��k\��sE��z@��+���n���J��T���9�c��"��M!��R��
,��'���k��-��0b��:��<n��I�(��_�F�����=e���|��R��V�"��p�'��q��H$������K���Y1��*���R	��y��m���
��8�����5B��?���'��T��Zk��H��k��o�>��X���F��'��)��,���.�l��K���<�W��u���D���(�� ��R2���M��M>��V@��^��d:��p�
���$��g=��P�����2,���#��D�`��i�"��@��BX�����$�6��X���G�[��S��V��e|��i�g��t��4�����)��)^��2���?�;��N��Ps��]T��g;��nu��*��L����F��.1��4L��B�3��Fq��L����[��.��*���^��*�!��8��h"��"��p{��yQ��d��	=��P���#���'��b��5�t��@C��k`��H�l�����i��x.��W��3��
$���������"N��'\��L	��N� ��`.��l��?��t��w�(����BW���
��E�%��R���dc
���]��L!��T��Z4
��_��jB
��l��w����+��kc�=Nt��W�U��hn��x��{p��	��0����
��+��%>��.	��1��3�g��N��Un��j��mZ��uI��}���
���L��!{��)j��3��6���B��E!��J�a��m��qo��|!���
��"�J��8��;��?&��F���Q{��\l��d��h�U��x�Y��	G��:�������+q��;��=�0��O��Q;��W��Y��_���n&��r��u�)��$��(1��/�6��?��A��D���V%��[�x��n�1��j��
������((��+��.��1/��6���D��F��J	��La��R���>���P��3G��8q��CX��H���R���jH��q�H���<��Q����p��"@��)��,�k��@�R��s�	��y��8����>��$���@��D�N��U���g�O��
�
����n��&��(-��1�Z��N��P9��U[��e\��pM��yI����R��
���E��$�A��A���NH��T���hg��q%��wI����W���
��-��0��3�|��V1��Z$��^#��a?��hJ��q��t0��}	���I�����#���9��->��3��6��;��>��A��C��F��I%��O��R3��[6��e�"��~2��
���W��$���,��/��2���DK��M1��V�q��z��}���s���� 3��&��)��.�D��L��Q=��W��Z;��`:��g{��l\��vC����L��
s	�5�[�a�]�k�e�c�g�_�_c��"	����0>�G@�Ic�_�c��/����Ac+O����%��z�P��U�p��PK
!<��?��3chrome/pdfjs/content/web/cmaps/UniJIS-UTF32-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�UniJIS-UTF32-HC!��bC	 �U�?�dC��M��6S#��t&+"'�+�B�9S%�G(C%%�o/S	%@�uC &���z�wz!O�0�/
]��C'�C0��KS3��J�W�V�mC3�


��K?�C3�1$1
)')��q
C3�L


	c%�9�7�=�;�A�?�j�]�`�U�X�w	�~�6�O	�c�]�H^����[c0�k�g�6�E�P�T�\�b�h�t�w�|�~����mc#��^�d�I�X	�M�XPK
!<���ߢߢ2chrome/pdfjs/content/web/cmaps/UniJIS-UTF8-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE���?���?����? @\a
A Q¤k]>�d��_?V3�A´��M�T�+ 2!+�F�V�S�L�IQĀ��tA
�~$�c���
Q
Ė��n$�c�U�P
�&%$)AĤ�)
�f�7�![QĮ��s"'��A��"�}
�";�9�6"��BQŀ��j��;
��|�,"�m����Q/���'
��j�>"�kOL
i��O��fO�>��_c�0
�m��("��[��<��SAƒi��P�nQǍ�2��j���	�
���Aǵ����k�yQ
ɐ�X��"��"O(
��zA
ɞ�W-*'0#rQɬ�6B''�$�c�bAɸ��1TI�A�6��7�ZQʈ�94
M�-�|3��)F�Aʻb�I>M�c=:B�@�;	���J�<�@A+ʔ�J��#�"k
l�I�)�@�	��]VI�b�p#
8%#
�c'�`�p
�I
AΆ��K
�YB[ �g�"8��F.��q.�J	�N
M�y�E�u�~�:�E�<�fG�~�k	'c���h	W~�9�2�T	
�|�	�l�
;�Hl�Us�y'�
�^�V�h�i� N�.:�K��`�e�,�d�[�5�J��|�O�r�]BoẼ�5�r���j�L�m��{/�q� �a�f�E��G�?
�x�&�Z�
�T�
,�?�I�
�h�q�i�r
�7�[��;�e��
�j�w�T�����G�X��.+��O�L�P�
�X�?�~
U�3�H�.�3��f�	�8�g�M�D������
�f�s�6�?�C��$�@<(�#�a�	�x�B<‿�i�B�`-�k�]�F�	�[�m�&�s�w�{<�{� �y�e�u�@��
K�yG�d��+��C�U��p�t�m�Z�$�;�M�
�v��3�\�7��V�=�/�j�U�_(�A�K�B�P�$�9�y�.�@�7��#�B⺞��eB⺬��:B℮���W�z�2�R⼀�0��i�� �w��w�8�5�R�t
�{�v�\R⼖�S��h�>�
�c�r�/�qR⼣�&�?�
�C�Z��c��c�T�c{�i�7�&&�S�����o���(R@⽀�'�4�u��w��$��n��B�S�^�n�}�H�a�$�/�7�R�4��	�w�>� .�Y�O�@�4�<�{�0��P��S�_�(��VKp��A��X��]�X�`�-�t�G�W��-�e�"��N�OR@⾀�S���e?�K�j8�]�/���8�G�U�F��
��a��h�(�.��\�z(����$��X��[�o�p��%�t�:�+�T�}�R�a� �G���6]�C�3�!�Y�"�o5�n 
R⿀��m���<�7�J�a��r��C�8��w��z�B
⿓�~��o�_�H
�+��<���w*�s�`B〰�:
�a��aB〼�B�c�R
㈳�Op}��$
B㉀�Vly�n�OR㊐�g�

�D�5�,�=��2
���^B㌀�pB㊩�ZB㊢�{R㌃�j�r�[�Z�B一�0��+B2㌔�"��
��
��*�?�
�F�#�F�?�#�,�y�`�P'�>�M�;�:�J	�=�P�K��A�&�w�P�y�{B㌗�x


�B	�5�[
�fB㐂�$�&�b�y�S�v�m�h�N�	�� ���M�`�(���r�n$��"��cB㐅��l�#��n�g
���x�o��x�-�j��yB�'㐆��Z%�},�g0"�k�z�D�p�k�H
�w(�\
�p�a�P��u�5�>�u$�L�K&&�B.�I����i�G��t�\�c�]�Q�r�~
�K4#�c�\	�O8
�9�L	�P" �Z�[�A��\8�~F��r�i�u�e"�$��p9�g�@���Z
�^�g��v"�Z
����_�^�lH��
�f�S�wJ�d(�W�*V�W
�x�C�n�V
!4
�U�s<	�u&��1��<�f��C�d�`�j�p�Q� 
�\(�p��G��X�$�e�	��&@�z�y�v�d�I$�.�~.��d�C����&�r�K}v�eQ�/�J�3R万�*�9�!��#����}�D��R��1�U��BV且�L�^�i��O�2�w�l�i�@�m�3�2���M�(�7�X�B�/�u�ve�	�R�\��g�f��N�� �z�3�
�i�A�Z�j��9�{�:�	��~�W�d�Q���}�x�X�S�w�h��N�
���E����I�@�G�F�5�K��r�#�yB
丟�Z�
�a�L��e�d$�M"��T�
B丩��U�x�Q�y	
�t�u�T�]�"�y�x�w�F�nR	仝�����g��N��/�F�7B仭��9�z�P�A�,��E�gB仼�5�CB
仨��]�w�v�u�F�G�{�l�m�B�lR伈��a��	���4�<��k�Y��M�'��\B会�q���L�xB伕�kB伖��f�x�3R伳��d��)��(��w��z��k��G��.��Q���PB佀��f��u����N�;R位��\�u�C�R��6��y����a�r�/�����j���b��p�AB佩�)�Q��0��\�C�i�B佺�pB佣��k�	�5�:�G��>�?R
侈�%����k�!��
��W��V���������r��oB侖�.����3	�$�[��q���/�B�5�l�CB侚�<B
侗�s��M�L��<�L��F�z�R
俈��~��%���*��������}��|���B俗��F���{�X
�g�.��B�0�����F
B俠�l�(B俜���#	�"�?�$	����D�(�u�dR倉�T��`����2����x����p��e�a��>��wR
倖�*��6�m��O� ��~�z����yR倡�B�z��i�d�o��f��
�I�T�;��(B,倶�^�N�I��y��X��q��A�(�t�'�F��B��e�O����?�n�R	�E�$�q�C��)�~��G�>�@�/�.B偀�?
)�c�B2倰����E� �t�u�5�{�$�y�x�y�5�{�&�'�&�}�I�|�7�6��-�0�5�}�0�1�(�;�:�B��)��
�o� �1�9�lR	僡��
�4��������L��k���+R
僭�_��J�R�T��j���I��B價�b��D��E�h�	�qt�E�D�9U�B儈�
B僺��+��D����8	�)�(�C��(R兀�q�s����!�1�h�d�;�^�C�:��d�?�)��r��gB兒�r�9��B�?�A��)�h�.�7�Z�m�[�
�M�C�`��sB兤�JB兓��;�I�F�_��T�_�
��W�>�H�D�S�R冉�~�#��P��3������6�7�#�"B!冗�Q�3�+�D�w�v�s�g�f�o�t�1�j�D�F���Y�X�/��[�~��+�*��5�R�p�3�2B冝�K �]�a�`B冘����	
��1�`�B��j���.�-�<�=�'�P�gR凰����p�Q���u�s��L��E��zBT函�5�F�A�}�
��=�}�A�j���C��:���y�x�Q�P�)�?�I�.��-�G��B�;�Q�$�`�	�]�`�9��(��y�7�>�a�R�3�8�C��&�x�}�r�3�U�U�p�{�$�7�jF�P���Y�B�Z�o�Y�n�O�$�i�h�/�0B刕�N��a�~�`	�X�P�Y�GB1刅��o�#���N	�1	�H�$�g���Z�'�+�P�'�C��*�+�H�N�M�N�#�I��P	�O�N�F��$�!�z�g�
�Z�}R勛�R����)�h����v��a�`�o��*��=�3��n�A�@B勲��|�{�1�t�A�K�b�
B匀�SB勬��a�
�H�B�g�#�G�B��,�E�DR
匕�M�!�x��V��u�B��0������K��s�SB
匣�O�A�@�{�m�o�Z�@�x��
B匤�UB	匥��i�K�T�U�)�|�U�Q�C�&R	卅�V��m��
����
B卑�p�9r�]�J�/�h�.�<��� B卡�!B卙��q�#�J�%R
卬�"�J��W�+�^�X��;�^B#卷�`�S�N�{�B�|�I�H��U�
�p��H�J�y�x�A�F�n�P��{�c�8���UN{�<�&B厓�WB卹��	�'��(�D�R�I�L�Y�U�*��*�U�T�-���~�W�V�B��(
�V�{�7R叝�Y��J��'��L��]�Z�Eq�5��
�6��#�#�w�P�H�W��E��#��p�7��1�R�>B叺�r�F����?�R合�y�/�0�i���/�^�E���`�`B&君��l�C�8���C�<��b�*�t	�;�A�:�W��}�]�V�t�
��/�B�'��� �B�vB	吞��4�O�M�0��
B吚����"�^�_�Q�#��]�\����G�A�TR咈��%�X��m�y�����Y����B咕��H�7�%��p��7��CR咤���t������z��	��<�6��5B咱�����R	咷��L��}��B�8����=�8��2BX哀�i�$�0	
�5�@�=�L�+���m�n�F����J���o��5�}�:��A��+�2�7�.�m�=�*
	�A����z�o�t�
��O�f	�O�`�M�c~�&��
�M�4pi
�M�P�G�1�W
�~�o�w�h
�YB哿�^�^��g�B'哃��N�9�8�U�E	�@�D�G�\�]�F�C�o�n�K�H�q�x�I�}�|
�]�i�h�)� B1哊���o�n	�o�n�o�n�F�o�j�k�j�K�q�p��Y�t�O�u�r�s�r�u
�N�r�s�p�q�r
�s�j�E�k�l�m�l�iR嘯�U��8���X��<��C��X��Y��\��[��TB噂�_	�6� 
�k�v�S�V	�1�#�<�E��k�l�Y�\�CB噓���kB*嘻��*��{�i�@�h�x��k�v�w�v	��i�f�g�d�
�M��u��~��g�d�%�*�r�}�%� �a�b�%�*�t�F�}R
囈�g��L�i��H�w���3��8�t��/B囓�jR囗�k�_����=���J�e��9����
��BC団�
�R�1�� ��e��V�@�}�~�O��d�K�J��*�y�r
�`�D�W��6�.��y��_�6����B�;�4�c�b�%�1�V�L�y��v�K��6��_B坙�`��ZBF囤�P�d��U�u�t�t�k�x�p���m�h��y�{�p�L�m�h��u�t���t��o�r�I�H�y�	�I�H�B�{��x��z��y�z���	���N��W�R�����c�-�~���R	執�e��x���O��l�����v�B:堀��{�y�@�D	�c�b�)�7�O�~�_�Z�Y��B�%�.�c�L�?�B�54�\�I�a�@��.�V	����D�g�v
�S�s�D��M�L�U�P
�#�B�� �O��>B堯�2�q�(�|�>B2堃��a�y�x���
���~�	�v�o�~�	��~�I�}�f�	��]�p�|�
���|�@�}�|�a�t��z��e���
�`�E���z��#R壐�h��y������z��_��z����vB7壜�1�)�t�|�:�)�_�P�G�|�G�3�2�k�j�-�1����:�
o�|�q�R�#�P�Y��6�]�\�E��P����c�^�c�d�x�O�T�[B夋�i�B壝��y�����b�"�|�/�.�/�w�$�%�$�;�8��C���=�<��1R	奧�M�y�z�Y��J��w��B��[��Bs女��&�t�3�C�2�u�4�T��|��8�i�z�O�a��E�"��3�.�G��s�A�|�$�u��� �X��]
�o� �B�A�_�r�/�2�O�J�g�%�t�C��#�!�D�� �=�<�;�R�g�*�
 ��B�K�L��
�=�V	���_��	�G�J�	�#�$�A
�A�B���w��a�_�H�9�t��!� �]�\NOB妤�n�[B姊�q
�
�W��m�l�c

	�_�l�_�S4�?�>B;她��j�?�N�%�`�?�<�?�F�'�&�8�9�6�T�3�7�6�w�=�:�I�;�:�����D�K�J����Q�����S�
�aB)奲���P�I*�H
�W
�T!�J-�C�	R宀���H�G����H��I��J��[������e�[�h��*��{R宓��C�l��5��*���}�p�>��,�bB�6実�n��D�O�R�z�5��W�����C�;�B���G�t��:���e��J�O��B�W�M��o�X�+�D�)�(�G�B�/�}�L��A��/�/�L��}����1�k��'�s��u�t�S�ZI�R�2�e�b�i�@�/�P�C�B�k�`�o�`��s�n�w�E�h�#�J���	�[�d�R�
��� �;�2�
��w��G�>�F�[�_��:��K�F�O�J�I�� �_�h
�s�r	��a��w�r	�'�>�e�d�K�u�H8		�o�n�K���5�KB
寀�q��|�G���P�z�q�Z�bB尔�*�?	��r�N�O
�N�o�\���e�B.宬��w�a�E�{�z�u	��M��������S��u���
�M�%�&�P�-�0�5�2�V�5�6B宷��y�_�~
	�b�o�c2�[$�D�B寬��NB+宭��=
�D�W
�d �I

�b�c�H�J�W

R
巠���c��������o�X�Y�e��PB0巫�m�	�W�^�y�s�j�e�+�x�7�C�.�T�q�4���m�h�[�=�
���o��O��B�"�� �q�b�G�B���a�`
�!�>�UB巸� �}�@�|�iB巹���M�P�}�|�U	�-�J�z
�y�&�T�M	�v�RR	幷����)��$�/�7�C�$B'庁�?��2�2�31�
��-�g��n�g�C�g���`�o�^�J�U��&�n�K�L�9�/�l	B庙�0�r�`�d�_B庀��
�N�}�z�U�'�&�H�'�x�U�'�zR
延�����t���5�j��2��W�B开��V��W��V��w�R弆���-�,��U���M��L��+�`�&B
弓�w���F�Y�i����s��t��}R	弡��d�F�^���$��h��%B6弭�$
�3�^�5�z��3�B�B��~�w�z �1��;�X�O�t�d�)�i��i���@�-�`�F�7�6���-�R�h�e�}��F�GB弴��P�!!� B!弮���-�=�<�B�f�@�+�*�	��C�@�A�(��!�	�H���u�b�J�#�2���5�4��!R	徧�>���!���f�}��9�e��dB徳��-�L�*�w�E�h�F�W�P�H�;�	�J�?�6��|c�?�:�m�v��N

B德��fB徵�8��K���3��� ��5�,��5�4���9��J�*��R怒�V��������p�*��'��z��s�C�B5怠�5�&�#�4���
�+�2�S�J
�F�7�8�g�0��>!�!�F�#�o�A�>�o�:��(�~�I���I�2�k�^�!�pB恝��gB怟��&���;�(�J��������)�(	��E�B�D�-�,�)�)R	悢��7��
��8�R��{�f��^��_�R	悰�O�"��Y�"Z�����B悼�c�&�G�?�>�	�z�B惕�
B悻��!��C�.�1��'�"R惘�z��R��/����E��f��5�c�4����GB惧�g�K�F�>�S�F�_�^�D�#�&
�k�]�D�%���"�BB惲��^B惥��@�7�)�\�=�D�<�?�>�;�z�>�;�%�$�8�A��D�C�B�?�FR慄���j�;��I�����}�/�e�l�B
慓�!�Q�L�i�q�R���B慞��(�u�tB慬��LR慮��6����\��q
��v��nB5慾�G�Ce��S��P	�Q�i�a�
�7�A���D�z�5�.�y�z�i�n�=�P�@	���w��Y*��B憘��zB%慻��*�'�&�K�L�w	�2�B�O�)�v�E�3�v�E
�r��E��4�p�q�F��~�Y�V�W�V�S�K�R�a
�/R
戚�p�,��8���9�#��Z��9��:�$B?戦��l�o�n�+�m�V��@�m�4�w�
�I�A�3�$�S��M��)�
�(�3�<�y�@����r�=��,�w�n�/�x�T�K�m�~�5�r�Q�{�*�|���D��s�d	B
戩�^�C�_�D� �n�=�~�<B戧��f�e�D�`�_�^���a�-�,�n�u�\�R�i�q�p
�lR拌�i�	��"��)�M�F�-����H�1���5�p��v��9��AB;拠��M
<�B�	�=�D�
�k�h�f�A�3�N���Y�L�U�q<�
���
�"�2��1�IR�#�~	�
�1�w
�X�#���U�V�?�<L�D�,�?�w�<�G�8�s�d��yB拼�e�M
�c�b�k�FB*拪��x�{�q�E�h�k�j��~��~�k�h�J�e�b�|�}�e�`�}�|�c�b
�a�`���7�2�B	�a�^�e�dR掞�r��]�#�k�h����������D�E�j��A��VB�9掲���2�I�L�A�g�p�5�I�|�q6�{��2�+�W�
�9�K�>�q�|
=	4��c�X�q��C�>�8	�}�t����)�H��V	�C�:�q�7�E�l��M��F�P�/��D�c�n	������"��v�1�:�E�A�@MPAB��:����s�E�#�
��� ���?�@�/��V�#�4�(�w��\�|�N���E�&�m�`E�!���1�0�7�"�H�@��
�h�9�&�2��i� �C�^�5�.�u�x���O�P�O�8�A�]�H�n�{�e�Z���F�;�6�����P�e��,B揵��^�O�|,� �|�p���k�~�qB揑�s
�1	�N�Q�`�q
�N�=�@�H�m�Y�| ��B揥��l��J��sB&掽���b�k�j	�I	
�u�O	�Q�I'�I
$�GB揪��J�g
�R�[�y�j�Z�q	�IB-揁���V%�Z�a	�K#�K
�H�Z�b�t
R昜�k��6�o��w�Y��<�+�(��W�}��R��k�e��N��+�S��O�l�!��R��B昴�j�*�/�>�A5�G��D�a�Z�_�BB昳��v	�J�.�'�W�~�B昷��L�F
R
晝�q��6���3�����V�=B-晩�i�R�g�,�C�c�T�F�#�p�c�Z�� �{�f�q�d�Q�R^�2�B���!� �%��2��1�@�B晳�"�eB 晪��Q�	����S�L�@�}�5�4����(�)�?�D�f�7�z��[�Z�E�D�E�D��R	曷��'�P��m�	�[�pB/最�7�&��$��=�<�Y�n�h�w�S�K��3�Zm5�/�B�}�N�.�I��6�A�9�J�1���'�"��-�&�#�"���P�$B朎�(�B朁��_��
�����i
�7�6�f�D�a�`�S�O���R杬��i��;��H��M�,�y�x������`��;B	杼�$�}�b�E�"�9�$
�gB杻��D�2�k
�j��YB构��j	R	枕��� ��#��L�T��1��Z��o�>B枠�n�t�]��x��!R枯���0�8�9��]����[�:

B柁�(�P�N�K�F�%��m�F�#�H�P�6B枻�+�DB
枾��{�C�h��~��~��)�&�|R
柮�6��h��o����e�%���$�B<査�*�d�}�B��n�o�T}�1�v�_�g�F�Y�b�U�]�d�i�,�B�~�	5�
�*�g�j�{x��X�>�9�j�5��I�^�?�& !�A�}��u��%�&�}�~	�[�,�E�B柺��J�'�~�N
�{�!B-柹��J�D�%�z���~�i�h�~�}�5�0�~�}�x�E�y�v�w�r�3�@�C�4�5�4�w�E�A�&��u�h��9�6R
梭�K��v��o��6����Q�����)B	梹�Q�[�G�]�p�=��C�NB棈�/B梻���GR棏�0�v�6��5��4��O������|��gB棟�i�
�I�Z�-�&�e�E�x�DB棨�.B棪����A�@��]�pR椃�� ��s���d�]��
��h����2�q�t�d��t�=����B%椙�@�^�C�
�8+& �5�|,��J��� ��(�}�h�w�r���q�t�)�\�L�uB楨�2B椖���%�$�%����F�&��_�;��=���$�p�"�a�]R	楷�u��f�`���=���~B�榁��c��1�*�}��1�,8=$�A��(�}�L�)�?�n�i��l�B
�A�J
�k�n�d�{�d�:�-�g�2�,�W��'�+�6�F�����u�N� 
�i�@��j�=�8
��		�o�j�E/0
�{�~�3�(��"�H�c�bad�y�z�1���S
�H���l�)�D�B��L�P�G�HB
榘�4�n���
�U$
�cB榀�;�w�v	�r�[�Z�J�
	�F�X�U�g�

�q�PB+榒��)�?
�>	�C�E�D�I	�L�Q�P�R�U�Z�I�P�g�h�i�j�H	
�M�
��Z
B榨���u�X
	�}��R�J�O7B榍���L�w�r�V�W#�E
�V
R	歟�b��n��o�m�V���p��Q��nB歩�2�n�O��r��n�@�E�=�!�
�.��B	歬��c�����F��
�B歫��?�TR
殬��I�[���o��H��G��DB5殷�u����u�@�@�A�r�y�2�o�2�(��4�T�#����3�~��X�A��
�!�<��X��E�^�v�m��l�Q�K�(�'��B毖�=�(�\B/殽��j��M�1�q� � �#�`
�;�'�p�u�B�&�L�u�t�O�N�O�+�^�k�p�k�&�>��N�s�r�A�@�u�y�("��A�3�y�j�u�wR	沁�
���^�)�M��4����<B沌�1�2R沒��3���-��u�� �;��m�@��2B没��c� �j��P� �(��4�X
�'�A�H�H���&B沪�IB	沟��\�w�E�:�y�M�E�;�vR	泏��`�o�n�}����&��7��6��'B6泙�!��%��B�i�l�O�6�Q�W��h�%��'��(��g�D�(�k�r�E�@�Q�B�l�5�x�2�e�\�)�j�
�{�p�l��|�`�K��\�2B泚�B�i�*�WB*泜���-�j�?�+�f��D�F�#�^�k�h�i�h	�C�B�m�.�O�r�s�L�M�W�e�d�J�1�h�9�*�]�h�9�*�E�]��NR	涑���.��{��p��g� ��L�1��_B涛�mX�
�;��g�C{�l
�A�J�]�^�=�6
��"
��~B涬�G�bB涪��)�`�"���o�B�H�I�J�K�J�U�w�Q�,�j��mR淮�K��N���K��C������X��	�T�#�%�,B清�Z�'�r�T��]�U�,*�1�B渗�FB渀��uR渝�^��
����t��f��U�S�@�rB/温�9�6��~�w��D�E�>�/�P��2�q�t�C�F
�?��@��f	�=�R�{�t�n�W�G�4�{�t
�a�T�y�z�D
�+B渹�L�_�"B"渲��&�)� �&�F��h�v	�{�[�r������
�%�K�����M�B�$���+�C�*�)�R	滇����C��8��e�X���	�B#滑�H�8	�3%
�p
�)�41�A��0��E��.�e�8
��e��+���
�
�A
�c�@�CB滙�8)�"
�m�L�l���q
�p�f�[�X�Y�\�UB	漄��	
�SR潗��A��o��L�U���S��j������!�SBH潤�k	�8X�S�a��|�N�	�>�.�k�B	�~���e�T�)�s�M�	�k�j�_�:�W�t���K��C�A�V��;�M�>�g)��Z�	�h�|�%�
��L�(�M
�
��O�h�Q�@�L�C	�x
�C��Q�u�Z�	�|�K�G�F��W�}#�m
�_��D�A��H�"���	Bi潦�DM�C


�H

�A�B+2��B	�Muv�n�R
�K�o		�W
�J#�M
�@B澈�Q,��_��N�^�	� �\%�W�H�X
 �{�k�|�|�}�g�<B(潽�
�M�%�$�c�����W�B!�`�j�X
�X �n�W�m�i
�HB潾��3�M�J%��]&�n��y�b�\2�I�B.潨��C�g	�j��C	�H�O�X�V"�`
�a�v�j,�HB澇��3��U�h�0�
�1�T�,��b�cB*潬���V	�T"��Y�L�~��J!
�H	�q
�@
�Z	
�UR
牗����+���<����d��Q��Z��}����'�J��JBE牧��
�?�~�	�B�X�Y�~���K�[
�(�I�@�g
���_
�p�`��]��V
�1�\���D�n�N�8�E�Q��~�Y�=
�v��$�O�?�j�*�	�
��Y�D�e�N �E����!�c�E�_��Z�q�O�v,�Q� �^�i���H�Z�u��.�8BW牴�k	�B

�I�P	�X�w�D	4+�L�A	
�L)(�b�JB犱�a�e���K�Q�u�w�d�x�aB8犍�;�K�%�$3�l�Q(�F�`�a	�O�N�R	
�H�I
�F�@�=�J�N		�H�WB犛��I��h�{��Z
�x�_5�WB6牸���N�P
&�L
�T�O	�E�F�l	�c	�ZB牨��H�L��M�j
�
�|�o�ZB/牫��A�W	�o�N�`�F
�X	�@��`�]�IR甞�J�]��V�*���-����]�^��d�����k��/�0B
田�>�<��Z�
�~��E�H�GB甯�rB甶���E�@�!�dR
畆�S��^�g�������X��=��d��'��&B畔�^��JR
留�y�4�q�!��*�H��
����OB	畤�V�E�#�f�W�Z��D��ER畯�u�-��h��k�~��d��m�-�(��D�.BG畿�@�B�@��'�U��[�j�e�V�Q�P��C�t�k��|�?�g�"��������O�N�D	
�F�w�z�[�V�a�d
�+�s��c�zBD疁�~
�2�3�B���
�5�4�@��	�����	��K�D�)�(���I�F�(����!�+�H��C�D�E�P�]�\�S�B�o�U�B�s�D�Y�B�i�h�E�w�P	�h�+��Z�[B
疅��	�W�VR	皀�����O�7�o��`��!� �
Bi皐�w�
��C���;�`��^��P	�x��]��B�
�<��2�N�p�g�Y�C� �m�\�H�~�I�{�Z��g�fp�a�&�{�*���N��'�T���=
�R�#�a�G�]�
��G�y�6�N�1�d�{�P�Y��6�;�=�#�F�L�0�D�A�Q�� +�c�8�g�B�c�g3�^�X�1�c�`�2�W�p�O	�T�I�E�$�Y�K��:�;	���}��@�W�	��S�TmBh皋��M
�%�&�H�]	
�K�G	

�O
�d.�I�F
�I	=:�S

�@*	+	�UB皛�y�=�N�>�o�~�p�l�u�c�uB硏�B睪�U�~B'盅��w�e
�W�{	�Q
�[� �!�u�i
�u	�G�
�NB皝��^�W
�X� �E��b*�~�O�Q2�NB2皕��

�Y�n�b#�]��I�Z"�_	�Y�Q
	�d

�b
�MB0皭��r�[�F
	�F�W�J��d!�Y�U	�x�B�n�XBB皟���a	�O	�D�}�M"�U�`�k
�G

�F�N�M�_R秝��g�W��k��@��)��(��)�m����G�B秩��f�;
�-�D�D�$���Y��I��v�
���SB秪��&�S�T�P�E�%�$��8�C���>B秱��k�JR	稷� ��\��>�����t�B穀��b���	���\�e�$��X�d�A��C�?B穃�!
�JB穄��+�A��H�S�R�K�
���@�E����N�A��)�3�Z�)�>R	窐��Z�>��5�g����B	窟�x	�n�i�w�t�j�G�JB窠�QB窞��{
�A�&�#�"�#R
竃�T�@��B��=�������^��O����
B
竑�	�	�n�������#�-R	童��d���.�6��1�J��d��wB端�z��Q�0�7�y�7�B���
�	�X�N�#B竴��c�C�\�H�5
�V�WB竱��	�cR	笥� �j������h�9���U��vB笳�J�K�K�V�o�M�B笱�Z	�T�B�C�'B笰��
�@R筋�R�����'��k�0��N��S��v��B筝�i!
�K�g�� B
筠�]�(�+�L�M�&�|�	�P�-B筪��	�YR箔�)���2��	�.��$��)	�X�W��"��_B箪�{�5�(�p�o�G�g�LB箯�bB	箥���3�0�+�*�+�.�DR	範�c��0�����b��y��F��%BQ篏�n
��Z�.�-�<�H	��
�z�q�{�r�u�v���@��#�&/.�y�x�K�B�Qp�(�!�-��0�5�#�E�B�'�'�*�s�6�B��A��;�fB篗�c�q
�Z

�C��BB'篔��
�h�i�f�B�i�z�l�m�l�m�N�l�q���n�u�t�I�w	�t�u��n�I�g�f�eB篛���f	�Q�I)
�SR	糒�&��H�H�C��3��z�����,B糜�'�E�/�+��1�2�u�t��C�A��V�c�K�(��B糝�v�H�I	�J�MB糩��1R紏��2�����'�K�������k�I�0�c��T���HB素�I�L���*�nR
紪��6�����z����=����3�_B紵�9��5��X��+R	絀��&�Q����.�R
��X��1B 絋�5��,��t�S�t�/��{�r���k��Y�T�K�9�V�h�1�H�x�
B絍��N���
�T�5�!�T	�W�X�B�>�{�z��Z�>�A�<��BB絑��8
�ER綪��6��O�S��P�s���b��E�2��T��M�{�����B!綽�N��(�G�l�u�X�#�P�?��*�y�k�`��	�k�;��k�r�/�*�E��d�2
	B緀��8�I	���;	�a��9�i�N�h�i�fB緢��G�E	R	縛�1��2����Z��P��Y
Bi縦�N��{�	
5�3�B�N���w��<�S��t�8�Q�|��O�j�B�m�b�T�I
�)�����J����Q�T�d���z�4�M�H�L�!��G�H�!� �b�_�?�6���?�C�6��K�-�x
���+1�Z�[�B繇��O�N�Y�k
�o-�N
B%縧��B�[�Z�,�M�1)�0�1�0�1�B�0�+� �&�%�D�"��
�@
���
�
��U��B縭��L�N�o�)�\	�f	�CR	耀�>�<��j��i�6�w�r��b�R耊��c��O�O��.����g��d��O��N�������B耜�@
�Y�D���
�F�Q�P���3�2B
耞��n��|��*��F�|�}�|B耝��f	�AR	聯�F���6��;���>��9�IB+聹�Q�i�h�D�9�Q�$�O�4�;�@�x��z�Z��I�$�T�u�i�T�l�H�Y�z�G��'�N	����8B聻��j�L�

�
��~��~��~�L�/�.�
�/�.B肁��p7�NR胭��t���!�������5����F�����������}��B%脂�7��n�~�e���l��O�T�e�v�k�,�;�%��F��,�O�3
�h��
�.
�s�v�F���q�A�j��>��8�B+脉�d�G �K		
�A�FB6脃��y	�����
�G�F�z��	��C��
�
�?�F�C�8�M���t�u�t�w��N�E�L�O�@���u�O�N�v�u�p��(�@�p��,B脋��y�H
�N�V	R
與��M�r�X��\���&�M��0��eB舒�	R
舖��c�-��x�p��Q�	��\��]�WB舡�=�0�v��e�y�x��R	舲�>��7��6��-��q�\�w��NB艀�$���?�@��^�<��7�@�L�{�z�9�M��a�#B舼���G�n�m�5�0�1�6�w�v	�^�W�c�\�C�V�Y�I� �Y
�B艃��
�KR芩��+�R��?�k��~��u������(��-��,R	芷�E��%�?��0��k��j��	��6�JB	苅�_��R��k�p�$�e�^B苆�G�JB苐��	�R苞�E��L��S��h��i��P��+�W� ��tB苫�)�m�
	�E�(�y�J�U��T�=�B�;�Z�QB苪���S��M�A�L��B苭��%�sR茫�T��,�u�t��3��H��U��>��E�S����'�h��BB荀�O�J�
�B茼��9�F�a�`�B茽��/�DR
荏�c�Z��p��K��,��U��A��B荷�YWB荢��m�p�E�
;4B荚�z�h��/�Q�B�~�UR	莘�O�N��?��P�l�m��m�B7莢�[�O�@�*�C�_�2�w�h�*�!�@�b�I��N�k�V�!�^� �0�f�W�E�7�>��!�3�<�#��+�6�Y�\%�M�H�\�*�=d�y�[B莧��5�K�@�`�������i
��+�*�D�K�M�f!�P�m�l�"�B莦��9	�i�p�UR	葟��;���E�P�)���g��?��$R	葩���������d��5BN葵�n�$�A�G�4��	�C��W�,-�]��}�Z�=�2�A�s�r�/��m�v�;�>�,�)�^�(�@�Y�
�9��f��
�~�S�Z�;�~� �B�A�#�w�r�S��h�i�P�i��t�r�CJ�a�w�T�v�_	
�AB/葳��=�M�z�D�3
�B��5�4���C�B��A�����z�
�N��G�J�K�G�Q��u�n�
�H
�J
�K�T�AB葴��J�T�v%�O�P	R	薄�,��t�e����r��q��p�EB薗��PB薐�:B薏�a�(�b�c�b�ER
薤�-��z������w�y�*��r��F��?��LBO薹�>�F���9�:�
��C�&�q��'�"�C?@��r�m�
�W� �6�M�	�;�D�{��d�/�L�O�N��)�N�H�b�
��=�R
	�M�5�6�C�'���
�	�3�,�Y��t��*	�	�A�B薷�b�V%�G�]�H�h��
�K�O�J�!B-薼��|�J�k�n�s�r�s�@�r�o�n�m�l�s�t�C�/�`�5�
�x�O'��G�(�%
�B"薴��m�I
�\�_�Q"
#	�ER
蜂�X�*��V�s��m��l��m��|B蜘��>�l�Y�(	�_�)�d��M�,T�~��&B-蜍�r
 

�L
			�I
�B 
���F+�8BP蜎��#�n�o�a�n�o�\��
�a�l�Z�i�h�]�
�c�A�b�h�k��b
�U�l���J�3�=�t�v�h�c�b�]�t�}�|���d�[�x�z�A�y�\�e�d�_�^�_�^�i�h�c�b�a�`�]�	�b�o�n�A�k�*�)�f�=�+R
蠍���|��q��,�k���CB蠟��=��	�D�U�V�g�)�z��)B蠜�B蠘��<�x�}�|	�m�n�o�j�k�j��m�D�j��k�j�R衕��C�w��c��h��9��&��!�� ��
�b��.�x��?����HB/表�/��x�/�Hj�b
S�Z�y��I�
�d�	�d��W�	�t�W� �a;�H�\�S���c�\�F�L�A�<��q�;�3�d�z��d�+��^�={�v�Y�u�D�U�\BK衫�3�B

				�T
	�.�7�E�$�
�E
�^�[�D�@�JB袘��[��
�j 
�|�D�[��Z�O�G�FB,衩��O�j�i�p�C�h�[�r�d�Q�e$�h�a�`�i�y�F�x�`�a�`�L�e�
��`
�a�`�a�`�X�c��B$衱��G�D�c�U�K
�O
�`R討�|��H��=��T��
�C��N���A�W�!B訛�R
訝���X��e��d����A��b��]��\B訪�Z�/�g�`�k��@�YB訶��@�I�AB訯��y����A���#�j�I�#R	詐�.�f�P��b��u�X���I�IB5詛�
�!��6�G��n�8�M���#�V�J�9�E�
�:�[�>�r�g�6�>�r�u��d�u��-��4�i�!�L�O��9�B��%�K�/��y�`�&B詝��d�j
�k�j�o�F�n	�K�J�M�|�N�M�Q�L�s�r�OB詜���Z	�GR	諚��3�� ��s��B諤�#���+��J���U��H�A�0�o�-�,�\�[�bB諴�&B諬��c�h�i�S�:�E�7�L�Q�8�7�LR	謙�`����.����2�i��k�aBF謦�5���7�<�B�h�e�C�D�K�N�'�"�M���F�G�F�T�@�U�w��h��� �G�J�A�%�&�o�n�I�[�"�5�� �Q�P�E�U�X�1�n�.��SB+謭��	�3�M�~�
�4�J�}�z�4
�5�~�K�J�B�O�N�O�7�|�}���K�G�C�z��A�@��B�I�H�G��V�9��HB謰���E�J�(�R�LR
貧�A�E���/�R�V�g�nB
貴�H�D�%�5��r�
��]��^�eR賀�l��?�a����
��w�X����I��RB"賎�*�J�b�kd�
�(�u�t�_�<�E�K�0�C��.����H�)�M��+�>�<
� �i�y�R�]��y�Q�T�.�	�t�MB+賍�}���I
�r�q��b�J�c �C�P
�LB8賒��v�7�Q
��5�4��B��9�6�
���
�
�U���		��	��'�&�S��F�G�`�&�'�b
�G��	��E�C�D�E�D�M	�Q�^�0�=	�<�9�}B賏��<

�e�B
�C	
�`!R
蹇� ��D��A��V����YB蹟�|��X�B蹕�*
�DB蹔��1�V��?�>��^�a���7�Z�M�D�#�"�G�F��]�\�R	躩����G�U�V��B�+��E�*B車��q�|�0�J�!�1�a�E��4
�P�UB躾�>�A�`�a�j�k�F
B躳�H�\�i��D�]�E�\	�C�@��"���G�T�E�
�f�#�E
�j�O�&�S�|R輥��!�����(��O���:�H�W�~��uR	輳�S��J�+�/�X��]�����#B?輾�X�[�B�V���[�^�A��y�&�$���]�^�M�L�	�c��`��k�A�,�B�7�2��S�7����Z�8�}��|�!�"�A�\�1��B�K���\����G�&B轀��?�L�O�N�_�^�Q��R�?
�j�L�]�^
�u�N� �u�t��F�u�0�c�$B轁��{
�w�A
R	逓�d�J�#��H��]�k��BP逝�f�N�/�4
��� �;�Z�D�]��J�O�`��
�
�b�e�u�j��p�W�@�=�Y�o�V�z�	�T��D�m�"�S�D�/
�*�H�[�x�h�-�6�i�`�Y�,�oe�[��Wi��E�#�&
�j�1�+0��N�B�0�M��8��4�M��C��<�;�T�2�uB@逞�v
�J�Z�[�d�e�B�-�jH�e�>�G�]�>�?
�>�=�W
�N�Z�[�d�a�NB逬�V�W��b�b�J�i			B逭��~�i
�H'�d�NB8逯��0�?�K�:�=�<�K�=�<�=�:�=�8�9�8�E�9�6�5�(�/�I�$�%� �!�]����J�����DB逿���b�a�f	�b	�F
�ER
釖�8��G��.��m�	�K�t�^�B釣�|���e��-%�"�KB釡�:�F�
�	
���
��@�zB釬��}�t��6�7��
��B�a���,�b
�i�/
�&R
鉂������0���'��_��t��mB:鉎�L�}�~���u�{�\�	���	�G��A�5�f��*�w�B
��w�z
��G	�N�U��u�z	�+��{�Q�s���=�:�/��_�b�B�S�:�K�NB鉏�z��O�N�N�d�K�JB4鉍��M��������I��������z�w���@�v�{����	�����H��)�vR錜��o���B��?�r��� ��)�#��n����L�$��G�tB錯�h�x�X�I�e�9�eB
錮�[
�H�
��z�B錳��W�4�5�4�O�_��m��6�'�9�^�9�o�&R	鍩��x�c�6��A����E��$��+��~B鍵�d�H�M�e	�.��T��
�b��_�v�"�n�U�X��h�#�c�H�:��F��W�hB5鍼�e�W
�t�}�I
�h�e�h�e
�r�J�_	�V�W�F�L	�S�A��,�-B5鍳��%�h�B�e�d�e�E�D
�+�
�$�E�#�H�I� �Y�A�	� �Y	�d�#�N�O�D� ��@�G�$�Y�I��L�M�J���#
���L�M���L�L�L�B"鍴��z�O
�]
�I	
�E�T�9�ER閟����}����'�C�x�2���w�xB?閭��A�D�D�5�.�I�H�v�� �7�B�U�K�@���)�,���
�<�D�q�4�1�c�L�A�B�u�p�;�h�%�f���>�G�)��}��R�$�k�TB&閫��Y���
�~���F�K�J������t�`�"�S�R�O�N�C�)�(�)�V�P�A�U�T�1�0B閺��&�^�~�Z
�RR
隔�4���r��s�
��*���z�HB)隠�E�6�D�s�n�Z�U�+��B�C��A��o�(�g�|���-�8��O�]�&�I�I�y�r�K�H�K�$�A�|�+�D�1B隤�"�|�}�0�S�T�c�f�i
�t�uB隟��8�cR	霂�)�&��Q��P��Q�	��$��iB=霍�R�C�5�v�5�4�!�B�=��"�F���!���4�5�~�E�D�6�]�$�q�r�!� �m�l�D�G�@�W
�~�Z�3�6���U�&B"霔��\�B�C�F�G�J�}�l�q�o�n�o�,�?�o�*�U�E��>�1�p�>�1	�0�I
�H�I��B�	B霚��?�l
"�D
 R	韆�������J�N����P�<�
B韓���=�k�d�A�:��	A�|�`B韜�B韐��R�K�H�I�y�j�T�A�.�9�H
��9�y�D�I�M�6�1R	頌���L�y�Y�?���e��~BE頗�B�&�K��k�%� � �O��K���$�\�1��0�r�C�3�G
�h�5�Q���6�q�F��4�E�T�C�P�n�D�~�H�Y�/�P�[�T�L�^��e�V���s)�f�N�-�^�-�
�.�i��
�$�i�K�8�� �$���D��D�C�w�j�^��pB飜�9�?�
	�(Bo頡��H�G�H�J�c
�b�K�u�L"�W	� ��S	����T	�E		�A
�L�#�/��C�0	�'�J�P
�p�q�l�M�k�l�mB頹�3�f�7�g�BB頞�8�Z��H�Z��W�x
�Q�"�I�B顖��*��p�j�}
�k$�p�]B<頖��u�a��J�T(�O��C�M�R��L	
�b�MB頦����P�|�R	��j�]�o
�>�A�R�W	BM頙��Y�N��]�P�M��]
�\�Q��H�@
�T
�ER	鮦��P�]���H��;�C��*��O�B鯉�%�$���}�W��bB鮱��c�\�D�c

�M�H�3		
		B%鮰��2�?�L�M�L�M�:�A�!��s��/�b�3�x�-�d�e�f	�]�(�~�)��B��$�A�.�)�8R
鰡�4��^�M�����+�*�;�qB�鰭�;�h�s�z�U�(��>�G�	�d��.�l�O�N�i�p	�E�`�Q�L�8�a�X�1�F�m�f�_�Z�S�k��6�A
��l�|� �f�U�z�T�_�L�S
�Z	)2�M�R�E
�`�e��|�G�]�(�[�2�/����
��;�A�:�7�6���;�:���x�d�e�	�2��d�t�-�D�}�6�;�D�
��}�M�I�B黃�B鰶�n�J�J��N#�Q�`��zB鱥��A�]�w�C&�_�B?鰱��g� �!�L� �!
���	��k�X�x�o
�n�L�m�h�e�d�H�a�I�L	�N�M��H�I�D�F�E�a�@�E�B�>B&鰽��C�E
�{�b
�H�]�[
�O�p�R	R
鼎���\��`�a��u��x��c��<�aB鼠�O�
���K�/�`��6
�W�V�U�]�`����x�"�i(uB8鼙�
���
��8�C�;�8�=�<�D�C
�A�B�<�=�G�Z�n��}�|�q�L�2�D�7��F��-�K�I�[�7�f���h��g�R�N�m�w�f�dB鼚��}�G�Q
R
﨎�Z�G��c��>��_,B福���"��6R侮�F-8	_N�?�$Cd5�&�oDR懲�9_",a�w�~��e�?nA5Z9:P�L�}/R艹�w�I3"P!�l�GjK�[�~�8�B!��	5qpq2Fw�S?�@	B!��*�Mz�	B	︐�L�g �@��D�Bff�C
𡨚���u��#�Q��h;�>��X���L)��"�s=�r�ZC🄀�}C🈂�i4LC眞�-C𠤎�SCB𠀋��~�%�H�q�A���Q�R�{�[��E�z^�C���l��N�t�1�0����o�r�e�?��*��W�3��
�O��b��3�`�
��N���Y��O�D���'�/�K�	���V��_�[
�*%�)�Q�2�).��Z���d���	��t�Z�
�>�R2�8
�t�C�f�>�e�h�
�d�,�I/�F�^@�
!�W�eX$�
�h�}�z�C𢘉�S��4+�{;��I�/�oC�𠂉��Q�n�,��O�7
�P�~��x�w2�~
��`�u�	�g$���g�	��a,�
	�[�]�W�X�M��(��h��o�,��Q��{�:�)�E�f���W�Y		�h���i�2�����\�B��9��.�g�
�c�(�M��v�|.��B�L�-0�,�|"�]	�e�~2�u�y�o�5�3�R�M�("�Y�c�l��d
�Z�&�@
�(�q�	�%�N��W�l�7�}(�l�{
��{�d�$�v4��f�wB(�r�`��~
���c�x�m�!�_�i�
�v�t�q���	�w�w�
�O��6���X�|�m���g�\�z�x�w��P
���&�U�{�;"�^���m��y�s*��w�i�<�	��i�b�%$�<�n�#��{�U�X�v+�`H"�Q�x�
�R�g�n�z�n�r�~�.
�H�0
�g�o��y0
�{�s�J��0�Y��~����k"�K0!
�\�$
�Y-�g�s�\-�`� �	�c�R�!��
��g�^ �~� �h�v�u �+"�h0�y�s��y
�d&��
�R�6�V��{�2�*�y�P�/C,𠃵��Y�b"��z�[X�M��iB�(
��*��Y�x�]�-��Yh�����S�����,�v�A��	�|��t�g�g�f��x�I�*��E,�YF�\	��}��p��~�x` ;>a¡e��!�A�$�*�;�A�G�X�A���s���@����#�*	�K�@�UaǸ�s�h��|��k�n���v�a΄��]��L!��T�\��Z�t
��_��j�
��l��wb†��Dn�U�a��m��y�=�{�V�i�g�!�*��U�y�	�*�$�k>�J�@�	��
��@6�<���Bp��"	����F�0�G�@�f��Ib享���`�9b�OⅠ	��!	��j�H�v�!�r�n�r���@��~�0�J?�7�@�w!�7�E�M�&�5��?�\����^�F���M�:�n�!�0��~�� �y�o��B�_	�&��=�\�_��4�1�V��x���8��c�X�v�e�&�w�j����X�%��7�
�E�r�v�w��o�*��{�Y��'�7�n�@�k�[��f��u�^��k�!/�(�~�~�|�u�}��J����l�,��h��|�l��~�U��	��m�B(�M�^�Z��0��B�L�Q��`��h�p�s�}��{��}�>��d�p�o�g���j��p�\�3�d�c�|���V�� �>�T�\�*�{
�u�`��d�	���n�!�%�j�9�<��P�{��]���$�v�>�A�u�K��\�g�u�m�	��
�o��
���[�/���>��t�x���E��'�-�1�^�U�~��r�Y�W���K�F�I��Y�p�|���k�J�M�r�_#�h��r�H�t�$��\����_��p��T�~�m�3"�D�B�=��R�s�i�h�m����1��E�J��b�P�x"�~�z��k�*�0�R�7��;�>�~�F�H�`�i	�k�m�~�{�~��]�����p��
�T�{�s�o�{��%���I���%�(��j����$�{�e�O�T�[�a�]�k�e�B�g�_b⁴�"�F	�(�	����d�b⅖�9bⒶ	�c�@�m�I	���k	��r��`	�]�h�v��P.�-b↖�,�*��Y�i�p�x��B��n�a�L�K�b�q�|����� �C�#�(�*�3��]���cb﹅�_b者�%b⺌�	bNḾ�q��e����L�p�r��m�i�v��a�u��D�X)�\�O�^�;�q��A�w������=�!�G�y�K��U��^�l�t��	�T�#��(��,�\�2��t�S�m�j�:�|�[�
��#�~�0�d�?�x�H��O��m�z���t��t�"��8�m�@��H�K�y�Z�_�S�m�
����
��y���(�q�2�y�<��T�K�r�}�x������ ��g�0��o��|��
�q��f��h�)�0�O��v�m�\�}�(�1�[�<��E��L�r�S��Y�|�^��k�Tb⁇��'�8�}�@��(	�_�Q���i��:�v�I��O��Z�E��F�u��	�l�wb�㕝��=���x����E���l��Y��~&���P��_���k���s���z���+�H���.���J��T���9�#���b��M!��R�F��
,���g���k��-��0�"��:�P��<�.��I�h��_������=�e���|��R��V��b��p�'���q��H�d������K�^��Y�q��*���R	��y�R��m���
���x�����5���?�T��'��T�[��Z�+���H��k��o�~���X�����'��)��,���.�l��K����<���u�_��D���h�� ��R�r���
��M�~��V�`��d�z��p�
���d��g�}��������2�l���c��D� ��i�b��@��B������$�v��X�O��G���S��V�~��i��g���t���t�����)��)�^��2���?�{��N��P�s��]���g�{��n�u���j��L������.�q��4���B�s��F�q��L�������n��*�]�����*��a��8��h�b���b��p�{��y����$��	�}��P�g���g��b��5�t��@���k� ��H�l�����i���x�n������s��
�d���������"�N��'���L	��N�`��`�n��l��?��t��w�h����B����J��E�%��R�T��eb≤���k��kb�;乴��W�U��h�.��x�B��{��0��	�]���p����
���k��%�~��.	��1��3�g��N��U�n��j��m�Z��u�	��}���
�]�����!�{��)�j��3��6���B�Y��E!��J�a��m��q�o��|!���M��"�
��8��;��?�f��F���Q�{��\�l��d��h�U��x���	����z���^������+�q��;��=�p��O��Q�{��W��Y��_�_��n&��r��u�i��$�]��(1��/�v��?��A�Q��D���V�e��[�8��n�q���j��
����T��((��+��.�U��1�o��6���D��F��J�I��L�!��R����~����Y��8�1��C�X��H���R���j���q����|���Q�����p��"���)��,�k��@���s����x�����~��$�H��@��D���U����g���
�M�����n��&��(�m��1���N�\��P�y��U���e���p�M��y�	�������
����E��$���A���N���T���h�g��q%��w�	�����W���
��-��0�O��3�|��V1��Z�d��^�c��a���h�
��q��t�p��}	����	�����c���y��-�~��3��6�Y��;��>��A��C��F��I�e��O��R�s��[�v��e�b��~2���M�����$���,��/�[��2���D�K��M1��V�q��z��}���s���P�� 3��&�[��)��.���L�^��Q�}��W��Z�{��`�z��g�{��l���v����P�����
c
🄐���@	�%��A�@	�Q������%��z��P��U�0��PK
!<���}��2chrome/pdfjs/content/web/cmaps/UniJIS-UTF8-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
UniJIS-UTF8-HA°�MB←�bB	‐�U��dB′�Q�P6R⎛�t&+"'�+�B�9R┌�G(B┥�o/R	╀�uB ☜����z�wz�aO�_�0�/�Y
]�C�B✂�B゠�KR㌃��J�W�V�mB㌔�


�R��?�[�B㌕�1$1
�F)')��Lq
B㌗�L


�B	b─�9�7�=�;�A�?�j�]�`�U�X�w�I�~��v�O	�c�]��H����Q�[b〘�k�g�6�E�P�T�\�b�h�t�C�w�|�~����mb⎰�^��d�I�	�M�XPK
!<��Sbn�n�7chrome/pdfjs/content/web/cmaps/UniJIS2004-UTF16-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE���������!A\a
!�GQ�k]>�d��_?V3�A���M�T�+ 2!+�V�S�L�IQ��tA
�~$�c���
Q
��n$�c�U�P
�&%$)A$�)
�f�7�![Q .��s"'��A��"�}
�";�9�6"��B��;
��|�,"�m����Q/P���'
��j�>"�kOL
i��O��fO�>��_c�0
�m��("��[��<��SA�i��P.Q��2��j���	�
���A�����k9Q
P�X��"��"O(
��zA
^�W-*'0#rQl�6B''�$�c�bAx��1TI�6��7�ZQ��94
M�-�|3��)F�Am�b	>M#=:B�@�;	���J�<O@�0�G�"8��F.��q.�J	�N
M�y�u�>�:�E�<fG>+	'c���h	W~�9�2	
�|C	l�
;ls9'�
��V�h�i� N�.:�K� �e�,�d�[�5�J��|�O�r�]A3 Z�Z�+�.�/�?
�x�vC	F
�Q'�

�j�w�
�*�"e4
�?	�^(��D@�����
�9�zAA��c��,
�`��2�H�j&�_;/1� !�f	�P�c�Z�w
65
�h6

4!�;4/Q�fB��.+�T��*�
U	�9�.��53�6�?C��$<(�#��Aa��J��#�"k
l	�)�@�]VI�b0#
8%#
#'`�p�|�k�`-�k]�FI�[-�&�3�w;<;� y�e�5�@T�
K�9G�$��+��C�U��p�t�m�Z�$�;�M�
�v��3�\�7��V�=�/�j�U�_(�A�K�v�9�y�.�@�7��#�A.���eA.���:A���K
�_�!�W�:�r�Q/�0��i�� �w��w�8�5�R�t
�{�v�\Q/�S��h�>�
�c�r�/�qQ�/#�&�?�
�C�Z��c��c�T�c{�i�7�&&�S�����o���(�_�4�u��w��$��n��B�S�^�n�}�H�a�$�/�7�R�4��	�t�-� .�Y�O�@�4�<�{�0��P��S�_�(��VKp��A��X��]�X�`�-�t�G�W��-�e�"��N�O�x���e?�K�j8�]�/���8�G�U�F��
��a��h�(�.��\�z(����$��X��[�o�p��%�t�:�+�T�}�R�a� �G���6]�C�3�!�Y�"�o5�n Q/��N���<�7�J�a��r��C�8��w��z�A
/Ӟ~/�_�H
�+��<��D�w*�3�`A00�:
�a�aA0<�Bc^Q23�Op}��$
ly�nA2Q�Q2��g�

�D�5�,�=��2
���^A3�pA2��ZA2��{Q3�j�r�[�Z�AN�0��+A23�"��
��
��*�?�
�#�F�?�#�,�y�`'�>�M�;�:�J	�=�K��A�&�w�P�y�{A3�x


	�5
&A4�$�&�b9�S�6�m�h�N�	T� ���
�`�(��2�n$�Q�@"�Q�cA4��lR#�.�g
O�8o��x�m�jH�9A�'4��Z%=,'0"�k�z�p�k�H
7(
�p�a�5�5F>5$�LK&&.	?��i�GT4�\�#�]�r>
�K4##P\	8
�9	�P" �Z�A��8>F�2�i5%"�$�09�g��
�B^�g�6"
�\�_�,HBP
&�7J$(�*V
8�C�n
!4
3<	5&Y�1��<&��C$�`�j0�Q_ 
\(�pD�G��$%�	�f@�z�y6�d	$�.~.B�dC�D��&�r�}v%Qo�
�3QN�*�9�!��#����}�D��R��1�U��AVN�L�^�i��O�2�w�l�i�@�m�3�2���M�(�7�X�/�u�ve�	�R�\��g�f��N�� �z�3�
�i�Z�j��9�{�:�	��~�W�d�Q���}�x�X�S�w�h��N�
������I�@�G�F�5�K��r�#�yA
N�Z�
�a��e�d$
"��
AN)��U�x�y	
�t�u�T�"�y�x�w�nQ	N݅����g��N��/�F�7AN��9�z�P�A�,AN끆b�F�GAN聤]Q	N��Z�4�d�l�m�g���p��SQO��a��	���4�<��k�Y��M�'��\AO�q���L�xAO�kAO��f�x�3QO3��d��)��(��w��z��k��G��.��Q���PAO@��f��u����N�;QOM��\�u�C�R��6��y����a�r�/�����j���b��p�AAOi�)�Q��0��\�i�AOz�pAOc��k�	�5�:��>�?Q
O��%����k�!��
��W��V���������r��oAO��.����3	�$�[��q���/�5�l�CAO��<A
O��s��M�L��<�L��z�Q
Oȁ�~��%���*��������}��|���AOז�F���{�X
�g�.��B�0���
AO�l�(AO܁��#	�"�?�$	���(�u�dQP	�T��`����2����x����p��e�a��>��wQ
P�*��6�m��O� ��~�z����yQP!�B�z��i�d�v���
�I�T�;��(A+P6�^�N	��y��X��q��A�(�t�'�F���e�O����?�n�R	�E�$�q�
�)��G�>�@�/�.AP@�?
)#0�1�2A2P0���� �t�u�5�{�$�y�x�y�5�{�&�'�&�}	�|�7�6��-�0�5�}�0�1�(�;�:��)��
�o� �1�9�lQ	P၇
�4��������L��k���+Q
P�_��J�R�T��j���I��AP��b���E�h�	�qt�E�D�9�:AQ2�vAP���+����-�
�8�E�F�)�(�C��(QQA�8����!�1�h�d�;�^�C�:��d�?��P�x��gAQR�r�9��B�?�A��)�h�.�7�Z�m�[�
�M�`��sAQd�JAQS��;�I�F�_��T�_�
��W�>�H�S�QQ��~�#��P��3������6�7�#�"A Q��Q�3�+�D�{�v�s�g�f�o�t�1�j�D���Y�X�/��[�~��+�*��5�R�p�3�2AQ��K���a�`AQ�����	
��1�`��j���.�-�<�=�'�P�gQQ����p�Q���u�s��L��E��zATQ��5�F�}�
��=�}�A�j���C��:���y�x�Q�P�)�?�I�.��-��B�;�Q�$�`�	�]�`�9��(��y�7�>�a�R�3�8��&�x�}�r�3�U�U�p�{�$�7�jF�P���Y�Z�o�Y�n�O�$�i�h�/�0AR�NG�a>�`	�X�Y�GA1R��o�#���N	�1	�$�g���Z�'�+�P�'�C��*�+�N�M�N�#�I��P	�O�N��$�!�z�g�
�Z�}QR�R����)�h����v��a�`�o��*��=�3��n�A�@AR��|�{�1�t�K�b�
AS�SAR쁥a�
�H�B�g�#�B��,�E�DQ
S�M�!�x��V��u�B��0������K��s�SAS#�O�A�@AS$�UAS%��i�K�T�U�)QS8�S�{�m�o���U�Q���x���T��KQ	SE�V��m��
����
ASQ�p�9r�]�J�/�h�.�<��� ASa�!ASY��q�#�J�%Q
Sl�"�J��W�+�^�X��;�^ASw�`�S�N�I�H��U�
�p��K�J�y�x�A�n�P��]�8���UN{�<AS�m�R(�]ASy��	�'��(�R�I�L�Y�U�*��n�E�U�T�-���~�W�V��(�P��{�7QS�Y��J�A�f��]�Z�Eq�5��
�6��#�#�w�P�H�W��E��#��p�7��1�R�>AS��r����?�QT�y�/�0�i���/�^�E���`�`A&T��l�C�8���C�<��b�*�t	�;�:�W��}�]�V�t�
��/�B�'��� �vA	T��4�O
�0��
AT����"�^�_�#��]�\����G�TQT���%�X��m�y�����Y����AT���H�7�%��p��7��CQT����t������z����@�
�6��5AT������QT���L��}��B�8����=�8��2���$�0��p��{A$TǢ
�5�@�=�L���m�n����J���o��5�}�:���+�2�7�.�m�=�*
AT��^�A'T��2�D�E�T�n	�o���o�n�o��E�\�a�J�K�^�j��
���O�N�s��p�Q�6�q�Q
U{�4��H��S��Z��g��l�n��K�A(U��<��z�o�t�
��O�f	�O�`�M�c~�&�%
�4pi
�M�P�1�W
�~�o�w�h
�YAU��_)�_A,U��:�Z���r�s�r�u�	����s����}�|
������j�k��i�b�m��z�%� �QV/�U��8���P��D��C��X��Y��\��[��TAVN�L
�k�v�S�V	�1�#�<��k�l�Y�\AVB�Z	��bE�kA+V;��*��{�i�h�x��k�v�w�v	��i�f�g�d�

��u��~��g�d�%�*�r�}�%� �a�b�%�*�t�5�6Q
VȢg��L�i��H�w���3��8�t��/AVӢjQVעk�_����=���J�e��9����
��ACV�
�R�1�� ��e��V�}�~�O��d�K�J��*�y�r
�`�W��6�.��y��_�6����;�4�c�b�%�1�V�y��v�K��6��_AWY�`FAFV��P�d��u�t�t�k�x�p���m�h��y�{�p�m�h��u�t���t��o�r�I�H�y�	�I�H�{��x��z��y�z���	����W�R�����c�-�~���Q
W��e��x���O��l�����v���3A8X�
�y�@�D	�c�b�)�7�O�~�_�}��%�.�c�L�?�B�54�\�I�a�@��.�V	����g�v
�S�s�D��M�L�U�P
�#�� �O��>AX/�2�,+<�>A2X��a�y�x���
���~�	�v�o�~�	��~	�}�f�	��]�p�|�
���|�}�|�a�t��z��e���
�`���z��#QX��h��y������z��_��z����vA7Xܣ1�)�t�|�:�)�_�P�G�|�3�2�k�j�-�1����:�
o�|�q�R�#�P�Y��6�]�\��P����c�^�c�d�x�O�T�[AY�iGAX݁�y����� �|��"�|�/�.�/�w�$�%�$�;�8����=�<��1Q	Yg�M�y�z�Y��J��w��B��[��ArYs��&�t�3�2�u�4�T��|��8�i�z�O�a��E�"��3�.��s�A�|�$�u��� �X��]
�o� �B�_�r�/�2�O�J	��t��#�!�D�� �=�<�;�g�*�
 ��K�L��
�=�V	����	�G�J�	�#�$
�A�B���w��a�_�H�9�t��!� �]�\NOAY��nn��,�AY��q
�
��m�l#

	,4�?�>A;Yy��j�?�%�`�?�<�?�'�&�8�9�6�3�7�67�=�:	�;�:�����K�J��������
�aA)Yr��	*

!
-IQ[����H�G����H��I��J��[������e�[�h��*��{Q[���C�l��5��*���}�p�>��,�bA[��n��D�O�R�z�5��W�����CA[���w
�A[���=
Q	[��S�:��"��g��G�t��:A�[ɤ���e��J�O��B�W�M��o�X�+�D�)�(�G�B�/�}�L���/�/�L��}����1�k��'�s��u�t�S�ZI�R�2�e�b�i�/�P�C�B�k�`�o�`��w�E�9�J���	�[�d�
��� �;�2�
��w��G�>�[�_��:��K�F�O�J	�� �_�h
�s�r	�!��w�r	�'�>�e�d�u�H8		�o�n���5�KA[�sE2�	�>�{C�P:1bA\�*�?	�2N�O
�N/G�%�A-[΁�z�{�z5	�
��������5���

�%�&�-�0�5�2�5�6A[ׁ�z�$2�%
	"o#2$CA&[́�D
$ 	

"#


Q
]����c��������o�X�Y�e��PA/]�m�	�W�^�y�s�j��x�7�.�T�q�4���m�h�[�=�
���o��O��"�� �q�b�G�B���a�`
�!�>�UA]��A]�� �T�S�|�$�'�"�|�U�M�L�-
�z
�y�)�N�T�M	�v�RQ^w����)��$�/�7�C�$����)A$^��?�2�2�31�
T�g��n�g�C�g���`�o�^
�U��&�n�K�L�g�l	A^��pH
A^����N
���}�-�*�z�U�'�&�'�K�B�U�Q�(�zQ^������t����0��{��2��W�����W��V��w�Q_���-�,��U���M��L��+�`�&A
_�w���F�Y�i����s��t��}Q	_!��d�F�^���$��h��%A_-�$
�3�^�5�z��3�B��~�w�z �1��;�X�O�t�d�)�i��iA_4��!!� A_.���-�=�<�B�f�+�*�	��C�@�A�(��!�	�H��Q
_|�t��L�b��1�-�`�F�7�6A_��9��-�R�h�e�}��D�GA_��<�"�Y�4�"�!A_���Q	_��>���!���f�}��9�e��dA_���-�L�*�
�F�W�P�H�;�	�J�?�6��|c�?�:�m�v

A_���5 �4A_��8����3��� ��5�,��5�4���9
�*��Q`�V��������p�*��'��z��s�C�A4` �5�&�#�4���
�+�2�S�J
�7�8�g�0��>!&�#�o�A�>�o�:��(�~	���I�2�k�^�!�pA`]�'A`��&���;�(
��������)�B�	��E�D�-�,�)�(Q	`���7��
��8�R��{�f��^��_�Q	`��O�"��Y�"Z�����A`��c�&�?�>�	�z�A`��
A`���!��.�1��'�"Q`إz��R��/����E��f��5�c�4����GA`�g�K�F�>�S�F�_�^�#�&
�m�]�D�%���"A`���%�$A`偨@�7�)�\�=�<�?�>�;�z�>�;�%�$�8�A��D�C�B�?QaD���j�;��I�����}�/�e�l�A
aS�!�Q�L�i�q�R���Aa^��(�u�tAal��LQan��6����\��q
��v��nA5a~�Ge��S��P	�Q�i�a�
�7�A���z�5�.�y�z�i�n�=�P	���w��Y*��Aa��zA%a{��*�'�&�L�w	�2�B�O�)�v�E�3�v�E
�r���4�p�q�F��~�Y�V�W�V�S�R�a
�/Q
b�p�,��8���9�#��Z��9��:�$A?b&��l�o�n�+�m�V��m��G�
�I�A�3�$�S��M��)�
�(�3�<�y����r�=��,�w�n�/�x�T�K�m�~�5�r�Q�{�*�|����s�d	A
b)�^�C�_� .�=>�<Ab'��f�e�`�_�^���a�-�,�n�u�\�i�q�p
�lQb̦i�	��"��)�M�F�-����H�1���5�p��v��9��AA7b��M
<�B�	�=�D�
�k�h�f�3�N���Y�L�U�q<�
����v�1	R�#�~	�
�'
�X�#����?�<L�,�?�w�<�G�8�s�d��yAc=�bG�A7bꁨx�{�/�<�h�)�(�)�<�j��+�c
�
��~�k�h�-�(�c�b�|�}�e�`�}�|�c�b	�/�.�a�`��-�,��7�{�,�/�,�-�J�^�e�dQc��r��]�#�k�h����������D�E�j��A��VA�3c����2�I�L
�5�I�|�q6�{��2�+�W�
�9�>�q�|
=	4��c�X�q��>�8	�}�t���
�E�V	�C�:�q�7�E�l��M���/��c�n	������v�1�:�E�@MPAB��:����s�#�
��� ���?�@�/��V�#�4�(�w��\�|���E�&�m�`E�!���1�0�7�"�H�H�
�h�9�&�2��i� �^�5�.�u�x���O�P�O�8�A�]�H�n�{�e�Z���;�6�����P�e��,Acþ'1�V�O<,� �g"�f�c�b�#?�"+�~�1Ac��s
�1	�N�`1
N�=�@-< ��A,c���"�_�b+*�q�p		
$�w�x	
��
	��	
$AcꁗJ'
9*1		Ado��(�70A-c���%!	#
"4
Qf�k��6�o��w�Y��<�+�(��W�}��R��k�e��N��+�S��O�l�!��R��Af4�j�*�/�>5�G��D�a�Z�_�BAf3��v	
�.�'�W�~�Af7��L
Q
f]�q��6���3�����V�yA-fi�i�R�g�,�C�c�T�#�p�c�Z�� �{�f�q�d�Q�R^�2���!� �%��2��1�@�Afs�"%A fj��Q�	����S�L�}�5�4����(�T�=�f�7�z��[�Z�E�D�E�D��Qf���'�P��m�	�[�p���NA,g�K��$��=�<�Y�n�h�w�S�K��3�Zm5�/�B�}�N�.�I��6�9�J�1����-�&�#�"���P�$Ag�(D�3�4Ag��
�����i
�7�6�f�a�`�S�O���
�'Qgl��i��;��H��M�,�y�x������`��;A	g|�$�}�b�"�9�$
�gAg{��2�k
�j��YAg���j	Q	g���� ��#��L�T��1��Z��o�>Ag��n�t�]��x��!Qg����0�8�9��]����[�:

Ag��(�P�N�K�F�%��m�F�#�H�P�6Ag��+A
g���{�h��~��~��)�&�|Q
g�6��h��o����e�%���$�Ag��*�d�}��n�o�T}�1�v�_�g�F�Y�b�U�]�dAg��
�'Ag���J�%�z���~�i�h�~�}�5�0�~�}Qh;��~��-�,��~�x��{�	5�
��� ��A!hH�
�j�{x��X�>�9�j�5��I�^�?�& !�}��u��%�&	�[�,�E�AhR�,;�!AhI���v�w�r�3�@�C�4�5�4�w�A�&��u�V�O�h��9�6Q
h��K��v��o��6����Q�����)A	h��Q�[�]�p�=��C�NAh�/Ah���Qh��0�v�6��5��4��O������|��gAhߘi�
�I�Z�-�&�e�x�DAh��.Ahꁪ��A�@��]�pQi�� ��s���d�]��
��h����2�q�t�d��t�=����A7i�@�c�
� 
�e
���}�S�
��*�'�$�=K�)�/�c�,
� �?!�Y���n��d��g�2	�V�	�+�:�W�N�K�
�UD�\�#�B�
�J���S
�H�e�l�)
�Y�XAqi�p+& 7,

 8=$	


&
18

��		�o�j/0
	
	ad	Aib�X�2!�Y	�z�^.���h(�#$�"�y�z$
\��A"i5�5�I�H�s�v	2�[�Z
�
	�X�U'J
1�{�zA2iB��
�4�;�4
�?
�>	�E�D�I	�L�Q�P�U�Z	�g�h�i�j	

�
�
Ai��"	B5X
	=H�R
�O7Ai��72#

Q	k_�b��n��o�m�V���p��Q��nAki�2�n�O��r��t�E�=�!�
�.��A	kl��c������
�Akk��?Q
k���I�[���o��H��G��DA4k��u����u�@�A�r�y�2�o�2�(��4�#����3�~��X��
�!�<��X��E�^�v�m��l�Q�^�'��Ak��=h�GA/k���j�
�1�q� � �#�`
�;�'�p�u�&�L�u�t�O�N�O�+�^�k�p�k�&�>��s�r�A�@�u�y�("��A�3�y�j�u�wQ	l��
���^�)�M��4����<Al��1�2Ql���3���-��u�� �;��m�@��2Al���c� �j��P� �(��4�X
�'�H�H���&Al��IA	l���\�w�E�:�y
�E�;�vQ	lρ�`�o�n�}����&��7��6��'A6l٪!��%��B�i�l�O�6�Q��h�%��'��(��g�D�(�k�r�E�@�Q�l�5�x�2�e�\�)�j�
�{�p�l��|�`��\�2Al��B)jA*l܁��-�j�?�+�f��F�#�^�k�h�i�h	�C�B�m�.�O�r�s�L�M�e�d�J�1�h�9�*�]�h�9�*�]��NQ	m����.��{��p��g� ��L�1��_Am��mX�X�g�;��g{�l
�A�J�
�i�^�=�6
��"
��+Am���)�`��]�H�I�J�K�J�U�w�Q�,�j	�KAm���n"Qm�K��N���K��C������X��	�T�#�%�,An�Z�'�r�T��]�U�,*�1�An�FAn��uQn�^��
����t��f��U�S�@�rA3n)�9�6��~�w��X�e�h�{�E�>�/�P��*�y�q�t�C�F
�?���f	�=�R�{�t�n�W�G�4�F�M
�a�T�B�A�R�E
�+An2��&�)� �Y�h�v	�{�[�^���r	�w�M�B��r�}�An;��w
$Q	n�����C��8��e�X���	�A#nыH�8	�3%
�p
�)�41��0��E��.�e�8
��e��)���
�

�c�@�CAn�8)�"
�m�l���q
�p�f�[�X�Y�\A	o��	
QoW��A��o��L�U���S��j������!�SA=od�k	�8X�a��|�	�>�.�k�B	�~�7�	>�s�:�W�|��C�A�V��;
�>�g)��	�h�|�%�
��A
��O�h�Q�	�x
�C��u�Z�	�|�K�G�F��}#�m
�_��{��"�	�%Agof�DM




�A�B+2	
uv.
/		

#

Ao��Q,?�_�^�	�
�U5)�0/�#��%�WH�X
 �{�J�9
�h �o�p'�<M�A'o}�

�%�$#�?�! * �n�m)
Ao~��3

%]&.?9"2	PA.oh��C'	*	" 
!6*,Ao���3U(�0-I�i�lA"��A)ol��	">E
!
	1

	
Q
rW����+�d��P����d��Q��Z��}����'�J��JAErg��
�?�~�	�X�Y�~���[
�(�I�@�g
���_
�p�`���V
�1�\����n�N�8�E��~�Y�=
�v��$�?�j�*�	�
��Y�e�N �E����!#�E�_��Z�q�v,�Q� �i���H!�	�.�8AWrt�k	

	
	7	4+	
)("
Ar��aeRE5�w$�xaA9r��;�%�$3,��(�`�a	�O�N	
�H�I
�@�=
		�WAr���I�(;�
85A6rx��
&
	,	#	Arh��HD�T�U
*
�
</�>#�A/rk��A	/ 
	@ 	Qu�J�]��V�*���-����]��>������k��/�0A
u0�>�<��Z�
�~��E�HAu/�rAu6���E�!�dQ
uF�S��^�g�������X��=��d��'��&AuT�^��JQ
uY�y�4�q�!��*�H��
����OA	ud�V�E�#�f�W�Z��D��EQuo�u�-��h��k�~��d��m�-�(��D�.AFu�@�@��'�U��[�j�e�V�Q�L��t�k��|�?�g�"��������O�N	
�w�z�[�V�a�d
�+�s��c�zAu��~#/�c�b	��

�w�vA8u���	�c�b�S�R�c��
�\�{�Y�	�����	���������!���@�=��B�7�4�E�o�n�s�8�3�2�I�i�h
�i�.�G�+	�BQ	v������O�7�o��`��!� �
AGv��w�
����;�`��^��P	�x��]��
�<��2�N�p�g�Y� �m�\�HE�C�Z�'�fp�a�&�{�*����'�T���=
�R�#�a�G�
��G�y�6�1�d�{�P�Y��6�;�=�#�L�0�D�A�� AVv��

�%�&	
	


$.	
		=:A
v��y�=��>�oB�/{�,�5A v��7%
��
��;	
��:�!5)
5Av���^
`Kb*>OA'v���

."#?	"	
	A)v���r
	
E$!	8A4v���!		=
" +


Q	y:�T��H������y��@��C�j��VA1yG�k�U�^�X�J�{�r�[�`�t�?�W�p�O	�T�$*��$�Y��k��:�(��i�h��	�y�}��W��:�;�S�T�6�%AyE�;�v�	�N�O�J�K��T�K	�~��|�F�G��~�D�E��m	�n��AyD��Y
Qy݁�g�W��k��@��)��(��)�����G�Ay��f�;
�-�D�$���Y��X�'�v�
���SAyꁙ&�S�P�E�%�$��8�C���>Ay�k
Q
z7� ��\��>�����t�VAzB�6�T�?�@�]�	���b���s�t�Q��P�yAzE�K
A
zD��+�7�H�K��@�E
Q
z}�*����'������d��)�?��2�ZAz��+��Z�>Q	z���Z�>��5�g����A	z��x	�n�i�w�t�j�G�JAz��QAz���{
�A�&�#�"�#Q
zËT�@��B��=���|�����^��O����
A
z��	�	�n�������#�-Q	z��d���.�6��1�J��d��wAz�z��Q�0�7�y�7��	�X�N�#A{�WA	z��;�:��\	�j	�I�|Q	{%� �j������h�9���U��vA{3�J�K�V�o�M�A{1�Z	�T�B�'A{0��Q{K�R�����'��k�0��N��S��v��A{���A{]�i!&A{`�]�(�+�T�U�L�M�&�j�o�	�8�+�-Q{��)���2��	�.��$��)	�X�W��"��_A{��{�T�a�p�^�5�LA{��bA	{����3�0�+�*�+�.Q	{Ěc��0���t��R��y��F��%AN{Ϯn
���Z�.�-�<	��
�z�q�{�r�u�v���#�&/.�y�x���!�-��0�5�#�E�B�'�'�*�s�6�B���;�fA{��c1


�C@�BA*{ԁ�
�h�i�f�i�z�l�m�l�m
�@�U�q���n�u�t�|+�S�w	�t�u��n	�g�f�eA{ہ�&		)
Q	|ү&��H�H�C��3��z�����,A|ܯ'�E�/�+��1�2�u�t��C��V�c�K�(��A|��v�H�I	�J
A|遭1Q}��2�����'�K�������k�I�0�c��T���HA} �I�L���*�nQ
}*��6�����z����=����3�_A}5�9��5��XQ
}?�8��Z�Q����.�R
��X��1A }K�5��,��t�S�t�/��{�r���k��Y�T�9�V�h�1�H�x�
A}M��N���
�T�5�!�T	�W�X�>�{�z��Z�>�A�<��BA}Q��8
Q}���6��O�S��P�s���b��E�2��T��M�{�����A!}��N��(�l�u�X�#�P�?��*�y�k�`��	�k�;��k�r�/�*��d�2
	A}���8�I	���;	�a��9�i�h�i�fA}G	Q	~�1��2����Z��P��Y
Ai~&�N��{�	
5�3�N���w��<�S��t�8�Q�|��O�j�m�b�T�I
�)����
����Q�T�d���z�4�M�L�!��G�H�!� �b�_�?�6���?�6��K�-�x
���d�c�[�A~G��O�N�+
/-
A#~'��B�[�Z�,
�1)�0�1�0�1�0�+� �&�%�"�����
�
���A~-��L/�)	&	Q	��>�<��j��i�6�w�r��b�Q�
��c��O�O��.����g��d��O��N�������A��@
�Y�D���
�Q�P���3�2A
���n��|��*��|�}�|A���f	Q	�o�F���6��;���>��9�IA+�y�Q�i�h�9�Q�$�O�4�;�@�x��z�Z��I�$�T�u�i�T�l�Y�z�G��'�N	����8A�{��j�

�
��~��~��~�/�.�
�/�.A����p7Q�큙t���!�������5����F�����������}��A$��7��n�~�e���l��O�e�v�k�,�;�%��F��
�h��
�.
�s�v���q�A�j��>��8�A+�	�d 
�r�m		
A
��2�5�4��4/)	A=���y�~�	�
�~����z���{�z
��?�D��C�8�j�k�
�{�z�����}�t���M�L�z�K�@��	�u�O�N�v�u�v�u�p��	��}�p�oQ
���M�r�X��\���&�M��0��eA��	Q
���c�-��x�p��Q�	��\��]�WA�!�=�0�v��e�y�x��Q	�2�>��7��6��-��q�\�w��NA
�G���*�^�I�7�9
�D�M�YA
�@�$�BA%�<���t��m�5�0�1�$�%�$�q�U�V�w�v�j�
�W�c�D�i�V���Y�I� �^�9�f�sQ����+�R��?�k��~��u������(��-��,Q	���E��%�?��0��k��j��	��6�JA	�ŋ_��R��k�p�$�e�^A��G�JA�Ё�	�Q�ޱE��L��S��h��i��P��+�W� ��tA��)�m�
	�(�y�J�U��T�=�B�;�Z�xA�ꁚ�S��M�L��A�큮%3Q�+�T��,�u�t��3��H��U��>��E�S����'�h��BA�@�O�J�
�A�<��9�a�`�A�=��/Q
�O�c�Z��p��K��,��U��A��A�w�YWA�b��m�p�
;4A�Z�z�h��/�Q�~�UQ	���O�N��?��P�l�m��m�A7���[�O�@�*�_�2�w�h�*�!�@�b�I��N�k�V�!�^� �0�f�W�7�>��!�3�<�#��+�6�Y�\%�M�\�*�=d�y�ZA����5�K�d�������i
��+�*�K�M�f!�m�l�"�A����9	)0Q	�_��;���E�P�)���g��?��$Q	�i���������d��5A�u�n�$�A�4��	�C��W�,-�]�A
�s��=�M�z�3
�B��5�4��A�t��JQ���`�Z��b��[�=��X�������	��A7�ė�r�/��m�v�;�>�,�s�W�(�Y���f��
�~�S�Z�;�~�T��#�w�r�S��h�i�P�i��t�r�CJ�a�w�T�v�_	
A�Ӂ�_���z�
�C	�P�G�J�K�G��u�n�
�H
�J
�K�TA�́�R%	Q	���,��t�e����r��q��p�EA����PA���:A���a�(�b�c�b�EQ
���-��z������w�,�{��r��F��?��AN���>���9�:�
��C�&�@�%�'�"?@��r�m�
�W� �6
�	�;�J��d�/�L�O�N��)�H�b�
��=�R
	
�5�6�C�'���
�	�3�,�Y��t��	�	�A���b%�G�H(��
�K�JaA.����|
�k�n�s�r�s�r�o�n�m�l�s�t�/�`�5�B�C�
�x'��(�%
�A"����m	
"
#	Q
��X��^�"�s��m��l��m��|A���>,�Y�(��d�
�,T>��&A-�
�r
 


�,�7			
 
���AQ���#�n�o�a�n�o�\��
�a�l�Z�i�h�]�
�c�b�h�k��b
�U�l��
�3�=�t�v�h�c�b�]�t�}�|���d�[�x�z�y�\�e�d�_�^�_�^�i�h�c�b�a�`�]�	�b�o�n�k�*#��f�=�+Q
�
���|��q��,�k���CA���=��	�U�V�g�)�z��)A��A���<�x�}�|	�m�n�o�j�k�j��m�j��k�j�Q�U��C�w��c��h��9��&��!�� ��
�b��.�x��?����HA-�h�/��x�/j�b
S�Z�y�	�
�d�	�d��W�	�t�W� �a;�\�S�L��F�L�<��q�;�3�d�z��d�+��={�v�u�D�UAJ�k�3

				
	�.�7�$�

�^��u
A�����
* 
<�[S�Z�G�F�yA,�i��O�j�i0�h�[�r�d�e$�h�a�`�i�y�x�`�a�`�e�
��`
�a�`�a�`�c��A$�q��G#

 Q��|��H��=��T��
�C��N���A�W�!A��Q
���,���e��d����A��b��]��\A�*�Z�/�g�`�k��A�6��@�[�gA�/��y������#�j�I�#Q	�P�.�f�P��b��u�X���I�IA4�[�
�!��6�G��n�8�0���#�V�J�9�
�:�[�>�r�g�6�>�r�u��d�u��-��4�i�!	�~��9��%�K�/��y�`�&A�]��d�j
�k�j�o�n	�K�J�M�|�N�M��P�s�r�OA�\��	Q	�ڴ�3�� ��s��A��#���+��J���U�~�I�0�o�-�,�\�T�MA��&A�쁚c�h�i�S�:�7�L�Q�8�7�LQ	��`����.����2�i��k�aAD�&�5�d�m�7�<�h�e�C�D�K�N�'�"�M���F�G�F�w��h��� �G�J�%�&�o�n�I�[�"�5�� �U�X�1�n�.��SA	�7�(G�(+�I�HA:�-���4�m��l�;�J��4�8�o�~�K�J�l�m�H��8�k�O�8
�u���l�9�4�1�G��A�.�o�A�.��H�T��~�A�<Q
���A�E���/�R�V�g�nA	���H�D�%�5��r�
��]��^Q
���u���?�a����
��w�X����I��RA!�Ε*�J�b�kd�
�(�u�t�_��0�CX�.����H�)
��+�>�<
� )�y�R�]��y�Q�.�	4�MA,�ʹ}�:�J�	
�r�qP�b
�c 
A8�ҁ�v�7�Q
��5�4���9�6�
�L�
�
���		��	��'�&��F�G�`�&�'�b
�G��	��E�D�E�D�M	�Q�^�0�=	�<�9�}A�ρ�<

e
	
 !Q
�G� ��D��A��V����YA�_�|��A�U�*
A�T��1�V��?�>��^�a���7�Z
�D�#�"�G�F��]�\�Q	������G�U�V��B�+��E�*A�ʒ�q�|�0�J�!�1�a��4
�P�UA���>�`�a�j�k
A���H�\�i��D�]�\	�C�@��"���G�T�
�f�#�E
�j�O�&�S�|Q�%��!�����(��O���:�H�W�~��uQ	�3�S��J�+�/�X��]�����#Q
�>�X�[��&�v��I����7���A9�I�Z�[�^�A�9�&�$���]�^�M�L�	�c��`��J�w�L�C�*��S�7���j�#�8�}��|�!�"�A�\�1�K���\����G�&A�J��@�N�_�^�QK�R�?
�j�]�^
�u�N� �u�t�u�0�c�$A�O��|7
Q	��d�J�N��V��_�i��AI��f�N�
��� �;	�z�J�O�`��
�
�b�e�u
�Z��@�=�Y�o�V�z�	�T��m�"�S�D�/
�*�[�x�h�-�6�i�`�Y�,K�8�?��E�#�&
�j�1�+0��B�0�M��8��4
��C��<�;�T�2�uAF��v�*�#
�T�G
�Z�r�O�D��e�-�jH%�>�G�>�?
�>�u�I�`
�a
�Z�[�d�aA�,�VC""
)			A�-��~i
'$NA9�/��0�?	�Z�Y�:�=�<�=�<�=�:�=�8�9�8�9�6�5�(�/	�$�%� �!���
����A�?��"!&	"	
Q
�ֶ8��G��.��m�	����c�^�A��|��%��-%�"�KA��:�
�	
���
��zA�쁒}�t��6�7��
��a���,�b
�i�/
�&Q
�B������0���'��_��t��mA:�N�L�}�~���u�{�\�	���	�G��5�f��*�w�B
��w�z
��G	�N��u�z	�+��{�Q�s���=�:�/��_�b�3�e�K�NA�O�zR�O�N$�K�JA2�M��M�������	��������z�w���{����	������)�vQ���o���B��?�r��� ��)�#��n����L�$��G�tA�/�h�x�I�e�9�eA
�.�[
�
��z�A�3��W�4�5�4�_��m��6�'�9�^�9�o�&Q	�i��x�c�6��A����E��$��+��~A�~�I
�e	�.��E�
"��_6�"�n�U��%��:��F��W�hA7�|�e
�*	�H�}	
�h�e�h�e
�r
�_	�V�W�L	�S�j�U��,�-A6�s��%�	�5�e�d�e�E�D
�+�
�$�#�H�I� �Y�A�	� �Y	�d�#�N�O� ��@�G�$�Y�I��L�M
���#
���L�M���L�L�A"�t��z

		
�yQ������}����'�C�x�2���w�xA?����A�D�5�.�I�H6�� �7�B�U�K���)�,���
�<�D�q�4�1�c�L�A�B�u�p�;�h�%�f���>�)��}��R�$�k�TA&����Y���
�~���K�J�����4�`�"�S�R�O�N�)�(�)�V�P�U�T�1�0A����&>
Q
���4���r��s��`�C���z�HA)���E�6�D�s�n�Z�U�+��B��A��o�(�g�|���-�8��O�]�&�I�I�y�r�K�H�K�$�A�|�+�1A���"�|�}�0�S�c�f�i
�t�uA����8#Q	��)�&��Q��P��Q�	��$��iA;�
�R�C�5�v�5�4�!�B�=��"���!���4�5�~�E�D�6�]�$�q�r�!�K�l�`�g�W
��E�3�6��A$���\�B�C�F�G
�}�l�q�o�n�o�,�?�o�k�j�*�E��>�1�p�>�1	�0�r�=
�H�I��	A���?,
"
 Q	�Ƹ������J�N����P�<�
A�ӌ��=�k�d�:��	A�|�`A�ܸA�Ё�R�K�H�I�y�j�T�A�.�9�H
��9�y�D	�M�6�1Q	����L�y�Y�?���e��~A?��B�&�K��k�%� � ��K���$�\�1@�02�C�3�G
�h�5�Q	��6�q�>�1�Pm��Y�/�P�[�T�/�e�V���s)�f�-�^�-�
�.)�M�$�i�8�� �$N���D�C�w�j��pA�ܰ9�?�B
	�(As�!��H�G�J�c
�b�K5"�\�]�$�%�x�y	� �	�JF�<�			�j�e
�L�#�/A�0	�'

�p�q�l
�k�l�mA�9�3�&�7+�
�{�NA��8KZQ8
�"	RA�V��*�p�*�=
+$0A\���u�(�+D�&�%
��������{�x�y�x	�{B�:�;�j�i
�b�e�`
�a�`�a�^�_V	
�F�K�H�I�
���A�G
�F�E	�D
�E�B	AL���YC
?
L

Q	����P�]���H��;��6�O��O�A�ɏ%�<��}��bA����c�\�c
�P
�Q

�H�3		
		A%����2�?�L�M�L�M�:�!��s��/�b�3�x�-�d�e�f	�]�(�~�)���$�A�.�)�8Q
�!�4��^�M�����+�*�;�qA+�-�;�]�i�"�l�
��f	�&�`�Q%�Y'���k� �M�j
�K"�]$�=�b�I�]}�k�d�W)�_�k�2��d�t�-�}�|�8�
�M�IAb�.�0�F�?+,
�z�un			
�R�[�f�Y�T�_	)2
�`�e�2�/V�
��x�d�e�
�A�6�n
�
�i8�h# ��y#�xA�e��A�]7&�A?�1��g� �!� �!
���	�k�x�o
�n�m�h�e�d�a	�L	�M]�H�I�F�E!�@�E�>A&�=��C
{"

p	Q
����\��`�a��u��x��c��<�aA� �O�
���/�`��6
�W�V�]�`��A'��
���
��8�;�8�=�<�D�C
�A�B�<�=�G�n��}�|�q�L���FA���}
C
�F����5��#�Q��h;�>��^)N�"�s=2�ZC�<��}C�<��i4LC�~�E�-C�B��SCB�@��~�%�H�q�A����Q�R�{��[�_�E�:^�C���l���t��q�0?���o�r��%�?��j���W�3��J�OG�b��E�3�� �
G�N���Y��O�D����g�/��K�	���V��_�[
�*%�)�2�).����A�d���	��t�Z�
�>2�8
�t�C&�>�e�h�
$�,�I/�^@�
!�W%X$�
(�}:�C�I�	�S��t+��;;��I�o�oC��@܉��Q�n�,Ew
��P�>�L�x��72~
�B �5I��'$��'�I��!,S
	[�W�X
��(��h�ol�Q�;��z�)��f���V		(R��i�2W�����B��9���E.��gO
�c�(�
_��v�<.��TB�-0�,�|"]	��e~2u9/�5s�R
�D("�Y#�l���$
�Z��f�
�(1�	�%�N��l�B7�=(��l{
�K�{��$�$�64���&�wB(2 ��>
�L�#8-��a_�iJ�v4�q���I�w7�
����6��@���<�m��'\:8w��
H��&��{�{"���?�m@93*G�w��)�<I�i�b�%$�<�.c��{�U�v+� H"��8�JR�g�nz.��2�~�.
��p
�go��y0
�;�s
�p�Y���>�H��k"��K0!
�$
-'3�\-�� `�	#RaF�E
��'��^ �>� (6��u �+"��h093@9
�d&��B
Rv���{�2��*�9�/C,�@���Yb"��X�:�[X��M��iB�(
��*��Y��8�]�m��Yh�����Q�S������,�6�A��I�|��t�g��g�f��8�I�*�,F��	��}�0R>�xA���)(uA�z�W�	�[�7�f�e(��g�N-�w&�dA�(��OQ
��Z�G��c��>��_,Q����\��Y��"��6A�,�~Q+�0�F-8	_N�?�$Cd5�&�oD�8�_",a�w�~��e�?nA5Z9:P�L�}/Q�]�w�I3"P!�l�GjK�[�~�8�A���	5qpq2Fw�S?	B!��*�Mz�	A��L�g�N�h�\A��a ;>%e��!�$�*�;�A�G�X���0na��s���F�#�*�K�P��D�\�a��m�9�=�{�i�g�!�*�U�y�	�*�$+R�J��
U��aN���� �9a_��ha�$!`	��v��K�7�H�M�:���!�0�[�Bq�=�e�4�1���C�8�c�X6�e<�p(�w*�����%_�7J�E�2�v�7�o�*�_�{���'�7.�@�+�[Z�f�C�u^��+�!/�(�|�u=�J���l�,�>�l^�~�I��-�B(�M^�Z�L�0R�BL�Q\�`S�h0�s�=�;��=�>�T�d�f)�og��?�j�e�d�f�
�� �>��\j�{
�u �$�	��.�!�%j�9�<��P�{�]�G�$v�>�A5�KV�\�g�u-�	��
�/��
��[�/Q�>�K�tx���g�-�1��U�~��r�Y�W�K��K�F�IP�Y��y�|�G��k�J�M�r�_#�hF�r�t�d����C�_��pT�~�m�33�=�5�i(�m�@���1��E�J��bP�x"�~�:��+�*�0�7D�;�>>�F�H�`�i	�k�m>�{�~��G��0��
�T�;�so�{A�%��	�V�%�(a!j�!	��jH�v�a�r�n�F��~�0�7�7�E
�&�5L�?���X�^����`�y�/�"	�&�)�_��V��~��\��D�B��Ra t�"	�(�I��a!V�9a
$��c�I	��+	�2� 	�]�h�v�.�-a!��,�*�X�Y�)�p�x�C�B�.�a�L�K�b�q�|����� �#�(�*�3��]a.��	aW��sh�|��k.���v���q�e�O��p�rN�m)�v��a�u�C�X)�\�^�;�q��w������=a�G9�K��U�@�^�l�t��	��#U�(Q�,\�2�t�S�-�j�z�|��
�S�#�>�0�
�:U�?x�HK�O�K�m�:��4�t�"��8�-�@�H�Ky�Z�_S�m�
����
��9��L�(�1�2y�<�^�T��r=�x���E��� �g�0�f�Q�f�o��|��
1�&��(�)�p�O�v�m��}�h�1��<�^�E��Lr�S��Y|�^a G��g�8�}�(	�_�Q�B�iZ�:�6�I�D�O��E��F�u�P	�l�wa�$���]��L!��T��Z4
��_��jB
��l��w����+��k�J��=���x�@��E�R��l���~&��P��_�D��k\��sE��z@��+���n���J��T���9�c��"��M!��R��
,��'���k��-��0b��:���I�(��_�F�����=e���|��R��V�"��p�'��q��H$������K���Y1��*���R	��y��m���
��8�����5B��?���'��T��Zk��H��k��o�>��X���F��'��)��,���.�l��K���<�W��u���D���(�� ��R2���M��M>��V@��^��d:��p�
���$��g=��P�����2,���#��D�`��i�"��@��BX�����$�6��X���G�[��S��V��e|��i�g��t��4�����)��)^��2���?�;��N��Ps��]T��g;��nu��*��L����F��.1��4L��B�3��Fq��L����[��.��*���^��*�!��8��h"��"��p{��yQ��d��	=��P���#���'��b��5�t��@C��k`��H�l�����i��x.��W��3��
$���������"N��'\��L	��N� ��`.��l��?��t��w�(����BW���
��E�%��R���da�>Nt��W�U��hn��x��{p��	��0����
��+��%>��.	��1��3�g��N��Un��j��mZ��uI��}���
���L��!{��)j��3��6���B��E!��J�a��m��qo��|!���
��"�J��8��;��?&��F���Q{��\l��d��h�U��x�Y��	G��:�������+q��;��=�0��O��Q;��W��Y��_���n&��r��u�)��$��(1��/�6��?��A��D���V%��[�x��n�1��j��
������((��+��.��1/��6���D��F��J	��La��R���>���P��3G��8q��CX��H���R���jH��q�H���<��Q����p��"@��)��,�k��@�R��s�	��y��8����>��$���@��D�N��U���g�O��
�
����n��&��(-��1�Z��N��P9��U[��e\��pM��yI����R��
���E��$�A��A���NH��T���hg��q%��wI����W���
��-��0��3�|��V1��Z$��^#��a?��hJ��q��t0��}	���I�����#���9��->��3��6��;��>��A��C��F��I%��O��R3��[6��e�"��~2��
���W��$���,��/��2���DK��M1��V�q��z��}��K���&���� 3��&��)��.�D��L��Q=��W��Z;��`:��g{��l\��vC����L��
c�<�����A��E��ā%��z��P��U��p��a	�`�T�/����{/�%�$p���O�Tq	�5�[�a�]�k�e�c�g�_�_a��"	����0�c>�G@�IPK
!<��Er��7chrome/pdfjs/content/web/cmaps/UniJIS2004-UTF16-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�UniJIS2004-UTF16-HA!��bA	 �U�?�dA��M��6Q#��t&+"'�+�B�9Q%�G(A%%�o/Q	%@�uA &���z�wz!O�0�/
]��A'�A0��KQ3��J�W�V�mA3�


��K?�A3�1$1
)')��q
A3�L


	a%�9�7�=�;�A�?�j�]�`�U�X�w	�~�6�O	�c�]�H^����[a0�k�g�6�E�P�T�\�b�h�t�w�|�~����ma#��^�d�I�X	�M�XPK
!<:�嶞��7chrome/pdfjs/content/web/cmaps/UniJIS2004-UTF32-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�#C\a
!�GS�k]>�d��_?V3�C���M�T�+ 2!+�V�S�L�IS��tA
�~$�c���
S
��n$�c�U�P
�&%$)C$�)
�f�7�![S .��s"'��A��"�}
�";�9�6"��B��;
��|�,"�m����S/P���'
��j�>"�kOL
i��O��fO�>��_c�0
�m��("��[��<��SC�i��P.S��2��j���	�
���C�����k9S
P�X��"��"O(
��zC
^�W-*'0#rSl�6B''�$�c�bCx��1TI�6��7�ZS��94
M�-�|3��)F�Cm�b	>M#=:B�@�;	���J�<O@�0�G�"8��F.��q.�J	�N
M�y�u�>�:�E�<fG>+	'c���h	W~�9�2	
�|C	l�
;ls9'�
��V�h�i� N�.:�K� �e�,�d�[�5�J��|�O�r�]C3 Z�Z�+�.�/�?
�x�vC	F
�Q'�

�j�w�
�*�"e4
�?	�^(��D@�����
�9�zCA��c��,
�`��2�H�j&�_;/1� !�f	�P�c�Z�w
65
�h6

4!�;4/Q�fB��.+�T��*�
U	�9�.��53�6�?C��$<(�#��Ca��J��#�"k
l	�)�@�]VI�b0#
8%#
#'`�p�|�k�`-�k]�FI�[-�&�3�w;<;� y�e�5�@T�
K�9G�$��+��C�U��p�t�m�Z�$�;�M�
�v��3�\�7��V�=�/�j�U�_(�A�K�v�9�y�.�@�7��#�C.���eC.���:C���K
�_�!�W�:�r�S/�0��i�� �w��w�8�5�R�t
�{�v�\S/�S��h�>�
�c�r�/�qS�/#�&�?�
�C�Z��c��c�T�c{�i�7�&&�S�����o���(�_�4�u��w��$��n��B�S�^�n�}�H�a�$�/�7�R�4��	�t�-� .�Y�O�@�4�<�{�0��P��S�_�(��VKp��A��X��]�X�`�-�t�G�W��-�e�"��N�O�x���e?�K�j8�]�/���8�G�U�F��
��a��h�(�.��\�z(����$��X��[�o�p��%�t�:�+�T�}�R�a� �G���6]�C�3�!�Y�"�o5�n S/��N���<�7�J�a��r��C�8��w��z�C
/Ӟ~/�_�H
�+��<��D�w*�3�`C00�:
�a�aC0<�Bc^S23�Op}��$
ly�nC2Q�S2��g�

�D�5�,�=��2
���^C3�pC2��ZC2��{S3�j�r�[�Z�CN�0��+C23�"��
��
��*�?�
�#�F�?�#�,�y�`'�>�M�;�:�J	�=�K��A�&�w�P�y�{C3�x


	�5
&C4�$�&�b9�S�6�m�h�N�	T� ���
�`�(��2�n$�Q�@"�Q�cC4��lR#�.�g
O�8o��x�m�jH�9C�'4��Z%=,'0"�k�z�p�k�H
7(
�p�a�5�5F>5$�LK&&.	?��i�GT4�\�#�]�r>
�K4##P\	8
�9	�P" �Z�A��8>F�2�i5%"�$�09�g��
�B^�g�6"
�\�_�,HBP
&�7J$(�*V
8�C�n
!4
3<	5&Y�1��<&��C$�`�j0�Q_ 
\(�pD�G��$%�	�f@�z�y6�d	$�.~.B�dC�D��&�r�}v%Qo�
�3SN�*�9�!��#����}�D��R��1�U��CVN�L�^�i��O�2�w�l�i�@�m�3�2���M�(�7�X�/�u�ve�	�R�\��g�f��N�� �z�3�
�i�Z�j��9�{�:�	��~�W�d�Q���}�x�X�S�w�h��N�
������I�@�G�F�5�K��r�#�yC
N�Z�
�a��e�d$
"��
CN)��U�x�y	
�t�u�T�"�y�x�w�nS	N݅����g��N��/�F�7CN��9�z�P�A�,CN끆b�F�GCN聤]S	N��Z�4�d�l�m�g���p��SSO��a��	���4�<��k�Y��M�'��\CO�q���L�xCO�kCO��f�x�3SO3��d��)��(��w��z��k��G��.��Q���PCO@��f��u����N�;SOM��\�u�C�R��6��y����a�r�/�����j���b��p�ACOi�)�Q��0��\�i�COz�pCOc��k�	�5�:��>�?S
O��%����k�!��
��W��V���������r��oCO��.����3	�$�[��q���/�5�l�CCO��<C
O��s��M�L��<�L��z�S
Oȁ�~��%���*��������}��|���COז�F���{�X
�g�.��B�0���
CO�l�(CO܁��#	�"�?�$	���(�u�dSP	�T��`����2����x����p��e�a��>��wS
P�*��6�m��O� ��~�z����ySP!�B�z��i�d�v���
�I�T�;��(C+P6�^�N	��y��X��q��A�(�t�'�F���e�O����?�n�R	�E�$�q�
�)��G�>�@�/�.CP@�?
)#0�1�2C2P0���� �t�u�5�{�$�y�x�y�5�{�&�'�&�}	�|�7�6��-�0�5�}�0�1�(�;�:��)��
�o� �1�9�lS	P၇
�4��������L��k���+S
P�_��J�R�T��j���I��CP��b���E�h�	�qt�E�D�9�:CQ2�vCP���+����-�
�8�E�F�)�(�C��(SQA�8����!�1�h�d�;�^�C�:��d�?��P�x��gCQR�r�9��B�?�A��)�h�.�7�Z�m�[�
�M�`��sCQd�JCQS��;�I�F�_��T�_�
��W�>�H�S�SQ��~�#��P��3������6�7�#�"C Q��Q�3�+�D�{�v�s�g�f�o�t�1�j�D���Y�X�/��[�~��+�*��5�R�p�3�2CQ��K���a�`CQ�����	
��1�`��j���.�-�<�=�'�P�gSQ����p�Q���u�s��L��E��zCTQ��5�F�}�
��=�}�A�j���C��:���y�x�Q�P�)�?�I�.��-��B�;�Q�$�`�	�]�`�9��(��y�7�>�a�R�3�8��&�x�}�r�3�U�U�p�{�$�7�jF�P���Y�Z�o�Y�n�O�$�i�h�/�0CR�NG�a>�`	�X�Y�GC1R��o�#���N	�1	�$�g���Z�'�+�P�'�C��*�+�N�M�N�#�I��P	�O�N��$�!�z�g�
�Z�}SR�R����)�h����v��a�`�o��*��=�3��n�A�@CR��|�{�1�t�K�b�
CS�SCR쁥a�
�H�B�g�#�B��,�E�DS
S�M�!�x��V��u�B��0������K��s�SCS#�O�A�@CS$�UCS%��i�K�T�U�)SS8�S�{�m�o���U�Q���x���T��KS	SE�V��m��
����
CSQ�p�9r�]�J�/�h�.�<��� CSa�!CSY��q�#�J�%S
Sl�"�J��W�+�^�X��;�^CSw�`�S�N�I�H��U�
�p��K�J�y�x�A�n�P��]�8���UN{�<CS�m�R(�]CSy��	�'��(�R�I�L�Y�U�*��n�E�U�T�-���~�W�V��(�P��{�7SS�Y��J�A�f��]�Z�Eq�5��
�6��#�#�w�P�H�W��E��#��p�7��1�R�>CS��r����?�ST�y�/�0�i���/�^�E���`�`C&T��l�C�8���C�<��b�*�t	�;�:�W��}�]�V�t�
��/�B�'��� �vC	T��4�O
�0��
CT����"�^�_�#��]�\����G�TST���%�X��m�y�����Y����CT���H�7�%��p��7��CST����t������z����@�
�6��5CT������ST���L��}��B�8����=�8��2���$�0��p��{C$TǢ
�5�@�=�L���m�n����J���o��5�}�:���+�2�7�.�m�=�*
CT��^�C'T��2�D�E�T�n	�o���o�n�o��E�\�a�J�K�^�j��
���O�N�s��p�Q�6�q�S
U{�4��H��S��Z��g��l�n��K�C(U��<��z�o�t�
��O�f	�O�`�M�c~�&�%
�4pi
�M�P�1�W
�~�o�w�h
�YCU��_)�_C,U��:�Z���r�s�r�u�	����s����}�|
������j�k��i�b�m��z�%� �SV/�U��8���P��D��C��X��Y��\��[��TCVN�L
�k�v�S�V	�1�#�<��k�l�Y�\CVB�Z	��bE�kC+V;��*��{�i�h�x��k�v�w�v	��i�f�g�d�

��u��~��g�d�%�*�r�}�%� �a�b�%�*�t�5�6S
VȢg��L�i��H�w���3��8�t��/CVӢjSVעk�_����=���J�e��9����
��CCV�
�R�1�� ��e��V�}�~�O��d�K�J��*�y�r
�`�W��6�.��y��_�6����;�4�c�b�%�1�V�y��v�K��6��_CWY�`FCFV��P�d��u�t�t�k�x�p���m�h��y�{�p�m�h��u�t���t��o�r�I�H�y�	�I�H�{��x��z��y�z���	����W�R�����c�-�~���S
W��e��x���O��l�����v���3C8X�
�y�@�D	�c�b�)�7�O�~�_�}��%�.�c�L�?�B�54�\�I�a�@��.�V	����g�v
�S�s�D��M�L�U�P
�#�� �O��>CX/�2�,+<�>C2X��a�y�x���
���~�	�v�o�~�	��~	�}�f�	��]�p�|�
���|�}�|�a�t��z��e���
�`���z��#SX��h��y������z��_��z����vC7Xܣ1�)�t�|�:�)�_�P�G�|�3�2�k�j�-�1����:�
o�|�q�R�#�P�Y��6�]�\��P����c�^�c�d�x�O�T�[CY�iGCX݁�y����� �|��"�|�/�.�/�w�$�%�$�;�8����=�<��1S	Yg�M�y�z�Y��J��w��B��[��CrYs��&�t�3�2�u�4�T��|��8�i�z�O�a��E�"��3�.��s�A�|�$�u��� �X��]
�o� �B�_�r�/�2�O�J	��t��#�!�D�� �=�<�;�g�*�
 ��K�L��
�=�V	����	�G�J�	�#�$
�A�B���w��a�_�H�9�t��!� �]�\NOCY��nn��,�CY��q
�
��m�l#

	,4�?�>C;Yy��j�?�%�`�?�<�?�'�&�8�9�6�3�7�67�=�:	�;�:�����K�J��������
�aC)Yr��	*

!
-IS[����H�G����H��I��J��[������e�[�h��*��{S[���C�l��5��*���}�p�>��,�bC[��n��D�O�R�z�5��W�����CC[���w
�C[���=
S	[��S�:��"��g��G�t��:C�[ɤ���e��J�O��B�W�M��o�X�+�D�)�(�G�B�/�}�L���/�/�L��}����1�k��'�s��u�t�S�ZI�R�2�e�b�i�/�P�C�B�k�`�o�`��w�E�9�J���	�[�d�
��� �;�2�
��w��G�>�[�_��:��K�F�O�J	�� �_�h
�s�r	�!��w�r	�'�>�e�d�u�H8		�o�n���5�KC[�sE2�	�>�{C�P:1bC\�*�?	�2N�O
�N/G�%�C-[΁�z�{�z5	�
��������5���

�%�&�-�0�5�2�5�6C[ׁ�z�$2�%
	"o#2$CC&[́�D
$ 	

"#


S
]����c��������o�X�Y�e��PC/]�m�	�W�^�y�s�j��x�7�.�T�q�4���m�h�[�=�
���o��O��"�� �q�b�G�B���a�`
�!�>�UC]��C]�� �T�S�|�$�'�"�|�U�M�L�-
�z
�y�)�N�T�M	�v�RS^w����)��$�/�7�C�$����)C$^��?�2�2�31�
T�g��n�g�C�g���`�o�^
�U��&�n�K�L�g�l	C^��pH
C^����N
���}�-�*�z�U�'�&�'�K�B�U�Q�(�zS^������t����0��{��2��W�����W��V��w�S_���-�,��U���M��L��+�`�&C
_�w���F�Y�i����s��t��}S	_!��d�F�^���$��h��%C_-�$
�3�^�5�z��3�B��~�w�z �1��;�X�O�t�d�)�i��iC_4��!!� C_.���-�=�<�B�f�+�*�	��C�@�A�(��!�	�H��S
_|�t��L�b��1�-�`�F�7�6C_��9��-�R�h�e�}��D�GC_��<�"�Y�4�"�!C_���S	_��>���!���f�}��9�e��dC_���-�L�*�
�F�W�P�H�;�	�J�?�6��|c�?�:�m�v

C_���5 �4C_��8����3��� ��5�,��5�4���9
�*��S`�V��������p�*��'��z��s�C�C4` �5�&�#�4���
�+�2�S�J
�7�8�g�0��>!&�#�o�A�>�o�:��(�~	���I�2�k�^�!�pC`]�'C`��&���;�(
��������)�B�	��E�D�-�,�)�(S	`���7��
��8�R��{�f��^��_�S	`��O�"��Y�"Z�����C`��c�&�?�>�	�z�C`��
C`���!��.�1��'�"S`إz��R��/����E��f��5�c�4����GC`�g�K�F�>�S�F�_�^�#�&
�m�]�D�%���"C`���%�$C`偨@�7�)�\�=�<�?�>�;�z�>�;�%�$�8�A��D�C�B�?SaD���j�;��I�����}�/�e�l�C
aS�!�Q�L�i�q�R���Ca^��(�u�tCal��LSan��6����\��q
��v��nC5a~�Ge��S��P	�Q�i�a�
�7�A���z�5�.�y�z�i�n�=�P	���w��Y*��Ca��zC%a{��*�'�&�L�w	�2�B�O�)�v�E�3�v�E
�r���4�p�q�F��~�Y�V�W�V�S�R�a
�/S
b�p�,��8���9�#��Z��9��:�$C?b&��l�o�n�+�m�V��m��G�
�I�A�3�$�S��M��)�
�(�3�<�y����r�=��,�w�n�/�x�T�K�m�~�5�r�Q�{�*�|����s�d	C
b)�^�C�_� .�=>�<Cb'��f�e�`�_�^���a�-�,�n�u�\�i�q�p
�lSb̦i�	��"��)�M�F�-����H�1���5�p��v��9��AC7b��M
<�B�	�=�D�
�k�h�f�3�N���Y�L�U�q<�
����v�1	R�#�~	�
�'
�X�#����?�<L�,�?�w�<�G�8�s�d��yCc=�bG�C7bꁨx�{�/�<�h�)�(�)�<�j��+�c
�
��~�k�h�-�(�c�b�|�}�e�`�}�|�c�b	�/�.�a�`��-�,��7�{�,�/�,�-�J�^�e�dSc��r��]�#�k�h����������D�E�j��A��VC�3c����2�I�L
�5�I�|�q6�{��2�+�W�
�9�>�q�|
=	4��c�X�q��>�8	�}�t���
�E�V	�C�:�q�7�E�l��M���/��c�n	������v�1�:�E�@MPAB��:����s�#�
��� ���?�@�/��V�#�4�(�w��\�|���E�&�m�`E�!���1�0�7�"�H�H�
�h�9�&�2��i� �^�5�.�u�x���O�P�O�8�A�]�H�n�{�e�Z���;�6�����P�e��,Ccþ'1�V�O<,� �g"�f�c�b�#?�"+�~�1Cc��s
�1	�N�`1
N�=�@-< ��C,c���"�_�b+*�q�p		
$�w�x	
��
	��	
$CcꁗJ'
9*1		Cdo��(�70C-c���%!	#
"4
Sf�k��6�o��w�Y��<�+�(��W�}��R��k�e��N��+�S��O�l�!��R��Cf4�j�*�/�>5�G��D�a�Z�_�BCf3��v	
�.�'�W�~�Cf7��L
S
f]�q��6���3�����V�yC-fi�i�R�g�,�C�c�T�#�p�c�Z�� �{�f�q�d�Q�R^�2���!� �%��2��1�@�Cfs�"%C fj��Q�	����S�L�}�5�4����(�T�=�f�7�z��[�Z�E�D�E�D��Sf���'�P��m�	�[�p���NC,g�K��$��=�<�Y�n�h�w�S�K��3�Zm5�/�B�}�N�.�I��6�9�J�1����-�&�#�"���P�$Cg�(D�3�4Cg��
�����i
�7�6�f�a�`�S�O���
�'Sgl��i��;��H��M�,�y�x������`��;C	g|�$�}�b�"�9�$
�gCg{��2�k
�j��YCg���j	S	g���� ��#��L�T��1��Z��o�>Cg��n�t�]��x��!Sg����0�8�9��]����[�:

Cg��(�P�N�K�F�%��m�F�#�H�P�6Cg��+C
g���{�h��~��~��)�&�|S
g�6��h��o����e�%���$�Cg��*�d�}��n�o�T}�1�v�_�g�F�Y�b�U�]�dCg��
�'Cg���J�%�z���~�i�h�~�}�5�0�~�}Sh;��~��-�,��~�x��{�	5�
��� ��C!hH�
�j�{x��X�>�9�j�5��I�^�?�& !�}��u��%�&	�[�,�E�ChR�,;�!ChI���v�w�r�3�@�C�4�5�4�w�A�&��u�V�O�h��9�6S
h��K��v��o��6����Q�����)C	h��Q�[�]�p�=��C�NCh�/Ch���Sh��0�v�6��5��4��O������|��gChߘi�
�I�Z�-�&�e�x�DCh��.Chꁪ��A�@��]�pSi�� ��s���d�]��
��h����2�q�t�d��t�=����C7i�@�c�
� 
�e
���}�S�
��*�'�$�=K�)�/�c�,
� �?!�Y���n��d��g�2	�V�	�+�:�W�N�K�
�UD�\�#�B�
�J���S
�H�e�l�)
�Y�XCqi�p+& 7,

 8=$	


&
18

��		�o�j/0
	
	ad	Cib�X�2!�Y	�z�^.���h(�#$�"�y�z$
\��C"i5�5�I�H�s�v	2�[�Z
�
	�X�U'J
1�{�zC2iB��
�4�;�4
�?
�>	�E�D�I	�L�Q�P�U�Z	�g�h�i�j	

�
�
Ci��"	B5X
	=H�R
�O7Ci��72#

S	k_�b��n��o�m�V���p��Q��nCki�2�n�O��r��t�E�=�!�
�.��C	kl��c������
�Ckk��?S
k���I�[���o��H��G��DC4k��u����u�@�A�r�y�2�o�2�(��4�#����3�~��X��
�!�<��X��E�^�v�m��l�Q�^�'��Ck��=h�GC/k���j�
�1�q� � �#�`
�;�'�p�u�&�L�u�t�O�N�O�+�^�k�p�k�&�>��s�r�A�@�u�y�("��A�3�y�j�u�wS	l��
���^�)�M��4����<Cl��1�2Sl���3���-��u�� �;��m�@��2Cl���c� �j��P� �(��4�X
�'�H�H���&Cl��IC	l���\�w�E�:�y
�E�;�vS	lρ�`�o�n�}����&��7��6��'C6l٪!��%��B�i�l�O�6�Q��h�%��'��(��g�D�(�k�r�E�@�Q�l�5�x�2�e�\�)�j�
�{�p�l��|�`��\�2Cl��B)jC*l܁��-�j�?�+�f��F�#�^�k�h�i�h	�C�B�m�.�O�r�s�L�M�e�d�J�1�h�9�*�]�h�9�*�]��NS	m����.��{��p��g� ��L�1��_Cm��mX�X�g�;��g{�l
�A�J�
�i�^�=�6
��"
��+Cm���)�`��]�H�I�J�K�J�U�w�Q�,�j	�KCm���n"Sm�K��N���K��C������X��	�T�#�%�,Cn�Z�'�r�T��]�U�,*�1�Cn�FCn��uSn�^��
����t��f��U�S�@�rC3n)�9�6��~�w��X�e�h�{�E�>�/�P��*�y�q�t�C�F
�?���f	�=�R�{�t�n�W�G�4�F�M
�a�T�B�A�R�E
�+Cn2��&�)� �Y�h�v	�{�[�^���r	�w�M�B��r�}�Cn;��w
$S	n�����C��8��e�X���	�C#nыH�8	�3%
�p
�)�41��0��E��.�e�8
��e��)���
�

�c�@�CCn�8)�"
�m�l���q
�p�f�[�X�Y�\C	o��	
SoW��A��o��L�U���S��j������!�SC=od�k	�8X�a��|�	�>�.�k�B	�~�7�	>�s�:�W�|��C�A�V��;
�>�g)��	�h�|�%�
��A
��O�h�Q�	�x
�C��u�Z�	�|�K�G�F��}#�m
�_��{��"�	�%Cgof�DM




�A�B+2	
uv.
/		

#

Co��Q,?�_�^�	�
�U5)�0/�#��%�WH�X
 �{�J�9
�h �o�p'�<M�C'o}�

�%�$#�?�! * �n�m)
Co~��3

%]&.?9"2	PC.oh��C'	*	" 
!6*,Co���3U(�0-I�i�lA"��C)ol��	">E
!
	1

	
S
rW����+�d��P����d��Q��Z��}����'�J��JCErg��
�?�~�	�X�Y�~���[
�(�I�@�g
���_
�p�`���V
�1�\����n�N�8�E��~�Y�=
�v��$�?�j�*�	�
��Y�e�N �E����!#�E�_��Z�q�v,�Q� �i���H!�	�.�8CWrt�k	

	
	7	4+	
)("
Cr��aeRE5�w$�xaC9r��;�%�$3,��(�`�a	�O�N	
�H�I
�@�=
		�WCr���I�(;�
85C6rx��
&
	,	#	Crh��HD�T�U
*
�
</�>#�C/rk��A	/ 
	@ 	Su�J�]��V�*���-����]��>������k��/�0C
u0�>�<��Z�
�~��E�HCu/�rCu6���E�!�dS
uF�S��^�g�������X��=��d��'��&CuT�^��JS
uY�y�4�q�!��*�H��
����OC	ud�V�E�#�f�W�Z��D��ESuo�u�-��h��k�~��d��m�-�(��D�.CFu�@�@��'�U��[�j�e�V�Q�L��t�k��|�?�g�"��������O�N	
�w�z�[�V�a�d
�+�s��c�zCu��~#/�c�b	��

�w�vC8u���	�c�b�S�R�c��
�\�{�Y�	�����	���������!���@�=��B�7�4�E�o�n�s�8�3�2�I�i�h
�i�.�G�+	�BS	v������O�7�o��`��!� �
CGv��w�
����;�`��^��P	�x��]��
�<��2�N�p�g�Y� �m�\�HE�C�Z�'�fp�a�&�{�*����'�T���=
�R�#�a�G�
��G�y�6�1�d�{�P�Y��6�;�=�#�L�0�D�A�� CVv��

�%�&	
	


$.	
		=:C
v��y�=��>�oB�/{�,�5C v��7%
��
��;	
��:�!5)
5Cv���^
`Kb*>OC'v���

."#?	"	
	C)v���r
	
E$!	8C4v���!		=
" +


S	y:�T��H������y��@��C�j��VC1yG�k�U�^�X�J�{�r�[�`�t�?�W�p�O	�T�$*��$�Y��k��:�(��i�h��	�y�}��W��:�;�S�T�6�%CyE�;�v�	�N�O�J�K��T�K	�~��|�F�G��~�D�E��m	�n��CyD��Y
Sy݁�g�W��k��@��)��(��)�����G�Cy��f�;
�-�D�$���Y��X�'�v�
���SCyꁙ&�S�P�E�%�$��8�C���>Cy�k
S
z7� ��\��>�����t�VCzB�6�T�?�@�]�	���b���s�t�Q��P�yCzE�K
C
zD��+�7�H�K��@�E
S
z}�*����'������d��)�?��2�ZCz��+��Z�>S	z���Z�>��5�g����C	z��x	�n�i�w�t�j�G�JCz��QCz���{
�A�&�#�"�#S
zËT�@��B��=���|�����^��O����
C
z��	�	�n�������#�-S	z��d���.�6��1�J��d��wCz�z��Q�0�7�y�7��	�X�N�#C{�WC	z��;�:��\	�j	�I�|S	{%� �j������h�9���U��vC{3�J�K�V�o�M�C{1�Z	�T�B�'C{0��S{K�R�����'��k�0��N��S��v��C{���C{]�i!&C{`�]�(�+�T�U�L�M�&�j�o�	�8�+�-S{��)���2��	�.��$��)	�X�W��"��_C{��{�T�a�p�^�5�LC{��bC	{����3�0�+�*�+�.S	{Ěc��0���t��R��y��F��%CN{Ϯn
���Z�.�-�<	��
�z�q�{�r�u�v���#�&/.�y�x���!�-��0�5�#�E�B�'�'�*�s�6�B���;�fC{��c1


�C@�BC*{ԁ�
�h�i�f�i�z�l�m�l�m
�@�U�q���n�u�t�|+�S�w	�t�u��n	�g�f�eC{ہ�&		)
S	|ү&��H�H�C��3��z�����,C|ܯ'�E�/�+��1�2�u�t��C��V�c�K�(��C|��v�H�I	�J
C|遭1S}��2�����'�K�������k�I�0�c��T���HC} �I�L���*�nS
}*��6�����z����=����3�_C}5�9��5��XS
}?�8��Z�Q����.�R
��X��1C }K�5��,��t�S�t�/��{�r���k��Y�T�9�V�h�1�H�x�
C}M��N���
�T�5�!�T	�W�X�>�{�z��Z�>�A�<��BC}Q��8
S}���6��O�S��P�s���b��E�2��T��M�{�����C!}��N��(�l�u�X�#�P�?��*�y�k�`��	�k�;��k�r�/�*��d�2
	C}���8�I	���;	�a��9�i�h�i�fC}G	S	~�1��2����Z��P��Y
Ci~&�N��{�	
5�3�N���w��<�S��t�8�Q�|��O�j�m�b�T�I
�)����
����Q�T�d���z�4�M�L�!��G�H�!� �b�_�?�6���?�6��K�-�x
���d�c�[�C~G��O�N�+
/-
C#~'��B�[�Z�,
�1)�0�1�0�1�0�+� �&�%�"�����
�
���C~-��L/�)	&	S	��>�<��j��i�6�w�r��b�S�
��c��O�O��.����g��d��O��N�������C��@
�Y�D���
�Q�P���3�2C
���n��|��*��|�}�|C���f	S	�o�F���6��;���>��9�IC+�y�Q�i�h�9�Q�$�O�4�;�@�x��z�Z��I�$�T�u�i�T�l�Y�z�G��'�N	����8C�{��j�

�
��~��~��~�/�.�
�/�.C����p7S�큙t���!�������5����F�����������}��C$��7��n�~�e���l��O�e�v�k�,�;�%��F��
�h��
�.
�s�v���q�A�j��>��8�C+�	�d 
�r�m		
C
��2�5�4��4/)	C=���y�~�	�
�~����z���{�z
��?�D��C�8�j�k�
�{�z�����}�t���M�L�z�K�@��	�u�O�N�v�u�v�u�p��	��}�p�oS
���M�r�X��\���&�M��0��eC��	S
���c�-��x�p��Q�	��\��]�WC�!�=�0�v��e�y�x��S	�2�>��7��6��-��q�\�w��NC
�G���*�^�I�7�9
�D�M�YC
�@�$�BC%�<���t��m�5�0�1�$�%�$�q�U�V�w�v�j�
�W�c�D�i�V���Y�I� �^�9�f�sS����+�R��?�k��~��u������(��-��,S	���E��%�?��0��k��j��	��6�JC	�ŋ_��R��k�p�$�e�^C��G�JC�Ё�	�S�ޱE��L��S��h��i��P��+�W� ��tC��)�m�
	�(�y�J�U��T�=�B�;�Z�xC�ꁚ�S��M�L��C�큮%3S�+�T��,�u�t��3��H��U��>��E�S����'�h��BC�@�O�J�
�C�<��9�a�`�C�=��/S
�O�c�Z��p��K��,��U��A��C�w�YWC�b��m�p�
;4C�Z�z�h��/�Q�~�US	���O�N��?��P�l�m��m�C7���[�O�@�*�_�2�w�h�*�!�@�b�I��N�k�V�!�^� �0�f�W�7�>��!�3�<�#��+�6�Y�\%�M�\�*�=d�y�ZC����5�K�d�������i
��+�*�K�M�f!�m�l�"�C����9	)0S	�_��;���E�P�)���g��?��$S	�i���������d��5C�u�n�$�A�4��	�C��W�,-�]�C
�s��=�M�z�3
�B��5�4��C�t��JS���`�Z��b��[�=��X�������	��C7�ė�r�/��m�v�;�>�,�s�W�(�Y���f��
�~�S�Z�;�~�T��#�w�r�S��h�i�P�i��t�r�CJ�a�w�T�v�_	
C�Ӂ�_���z�
�C	�P�G�J�K�G��u�n�
�H
�J
�K�TC�́�R%	S	���,��t�e����r��q��p�EC����PC���:C���a�(�b�c�b�ES
���-��z������w�,�{��r��F��?��CN���>���9�:�
��C�&�@�%�'�"?@��r�m�
�W� �6
�	�;�J��d�/�L�O�N��)�H�b�
��=�R
	
�5�6�C�'���
�	�3�,�Y��t��	�	�C���b%�G�H(��
�K�JaC.����|
�k�n�s�r�s�r�o�n�m�l�s�t�/�`�5�B�C�
�x'��(�%
�C"����m	
"
#	S
��X��^�"�s��m��l��m��|C���>,�Y�(��d�
�,T>��&C-�
�r
 


�,�7			
 
���CQ���#�n�o�a�n�o�\��
�a�l�Z�i�h�]�
�c�b�h�k��b
�U�l��
�3�=�t�v�h�c�b�]�t�}�|���d�[�x�z�y�\�e�d�_�^�_�^�i�h�c�b�a�`�]�	�b�o�n�k�*#��f�=�+S
�
���|��q��,�k���CC���=��	�U�V�g�)�z��)C��C���<�x�}�|	�m�n�o�j�k�j��m�j��k�j�S�U��C�w��c��h��9��&��!�� ��
�b��.�x��?����HC-�h�/��x�/j�b
S�Z�y�	�
�d�	�d��W�	�t�W� �a;�\�S�L��F�L�<��q�;�3�d�z��d�+��={�v�u�D�UCJ�k�3

				
	�.�7�$�

�^��u
C�����
* 
<�[S�Z�G�F�yC,�i��O�j�i0�h�[�r�d�e$�h�a�`�i�y�x�`�a�`�e�
��`
�a�`�a�`�c��C$�q��G#

 S��|��H��=��T��
�C��N���A�W�!C��S
���,���e��d����A��b��]��\C�*�Z�/�g�`�k��C�6��@�[�gC�/��y������#�j�I�#S	�P�.�f�P��b��u�X���I�IC4�[�
�!��6�G��n�8�0���#�V�J�9�
�:�[�>�r�g�6�>�r�u��d�u��-��4�i�!	�~��9��%�K�/��y�`�&C�]��d�j
�k�j�o�n	�K�J�M�|�N�M��P�s�r�OC�\��	S	�ڴ�3�� ��s��C��#���+��J���U�~�I�0�o�-�,�\�T�MC��&C�쁚c�h�i�S�:�7�L�Q�8�7�LS	��`����.����2�i��k�aCD�&�5�d�m�7�<�h�e�C�D�K�N�'�"�M���F�G�F�w��h��� �G�J�%�&�o�n�I�[�"�5�� �U�X�1�n�.��SC	�7�(G�(+�I�HC:�-���4�m��l�;�J��4�8�o�~�K�J�l�m�H��8�k�O�8
�u���l�9�4�1�G��A�.�o�A�.��H�T��~�A�<S
���A�E���/�R�V�g�nC	���H�D�%�5��r�
��]��^S
���u���?�a����
��w�X����I��RC!�Ε*�J�b�kd�
�(�u�t�_��0�CX�.����H�)
��+�>�<
� )�y�R�]��y�Q�.�	4�MC,�ʹ}�:�J�	
�r�qP�b
�c 
C8�ҁ�v�7�Q
��5�4���9�6�
�L�
�
���		��	��'�&��F�G�`�&�'�b
�G��	��E�D�E�D�M	�Q�^�0�=	�<�9�}C�ρ�<

e
	
 !S
�G� ��D��A��V����YC�_�|��C�U�*
C�T��1�V��?�>��^�a���7�Z
�D�#�"�G�F��]�\�S	������G�U�V��B�+��E�*C�ʒ�q�|�0�J�!�1�a��4
�P�UC���>�`�a�j�k
C���H�\�i��D�]�\	�C�@��"���G�T�
�f�#�E
�j�O�&�S�|S�%��!�����(��O���:�H�W�~��uS	�3�S��J�+�/�X��]�����#S
�>�X�[��&�v��I����7���C9�I�Z�[�^�A�9�&�$���]�^�M�L�	�c��`��J�w�L�C�*��S�7���j�#�8�}��|�!�"�A�\�1�K���\����G�&C�J��@�N�_�^�QK�R�?
�j�]�^
�u�N� �u�t�u�0�c�$C�O��|7
S	��d�J�N��V��_�i��CI��f�N�
��� �;	�z�J�O�`��
�
�b�e�u
�Z��@�=�Y�o�V�z�	�T��m�"�S�D�/
�*�[�x�h�-�6�i�`�Y�,K�8�?��E�#�&
�j�1�+0��B�0�M��8��4
��C��<�;�T�2�uCF��v�*�#
�T�G
�Z�r�O�D��e�-�jH%�>�G�>�?
�>�u�I�`
�a
�Z�[�d�aC�,�VC""
)			C�-��~i
'$NC9�/��0�?	�Z�Y�:�=�<�=�<�=�:�=�8�9�8�9�6�5�(�/	�$�%� �!���
����C�?��"!&	"	
S
�ֶ8��G��.��m�	����c�^�C��|��%��-%�"�KC��:�
�	
���
��zC�쁒}�t��6�7��
��a���,�b
�i�/
�&S
�B������0���'��_��t��mC:�N�L�}�~���u�{�\�	���	�G��5�f��*�w�B
��w�z
��G	�N��u�z	�+��{�Q�s���=�:�/��_�b�3�e�K�NC�O�zR�O�N$�K�JC2�M��M�������	��������z�w���{����	������)�vS���o���B��?�r��� ��)�#��n����L�$��G�tC�/�h�x�I�e�9�eC
�.�[
�
��z�C�3��W�4�5�4�_��m��6�'�9�^�9�o�&S	�i��x�c�6��A����E��$��+��~C�~�I
�e	�.��E�
"��_6�"�n�U��%��:��F��W�hC7�|�e
�*	�H�}	
�h�e�h�e
�r
�_	�V�W�L	�S�j�U��,�-C6�s��%�	�5�e�d�e�E�D
�+�
�$�#�H�I� �Y�A�	� �Y	�d�#�N�O� ��@�G�$�Y�I��L�M
���#
���L�M���L�L�C"�t��z

		
�yS������}����'�C�x�2���w�xC?����A�D�5�.�I�H6�� �7�B�U�K���)�,���
�<�D�q�4�1�c�L�A�B�u�p�;�h�%�f���>�)��}��R�$�k�TC&����Y���
�~���K�J�����4�`�"�S�R�O�N�)�(�)�V�P�U�T�1�0C����&>
S
���4���r��s��`�C���z�HC)���E�6�D�s�n�Z�U�+��B��A��o�(�g�|���-�8��O�]�&�I�I�y�r�K�H�K�$�A�|�+�1C���"�|�}�0�S�c�f�i
�t�uC����8#S	��)�&��Q��P��Q�	��$��iC;�
�R�C�5�v�5�4�!�B�=��"���!���4�5�~�E�D�6�]�$�q�r�!�K�l�`�g�W
��E�3�6��C$���\�B�C�F�G
�}�l�q�o�n�o�,�?�o�k�j�*�E��>�1�p�>�1	�0�r�=
�H�I��	C���?,
"
 S	�Ƹ������J�N����P�<�
C�ӌ��=�k�d�:��	A�|�`C�ܸC�Ё�R�K�H�I�y�j�T�A�.�9�H
��9�y�D	�M�6�1S	����L�y�Y�?���e��~C?��B�&�K��k�%� � ��K���$�\�1@�02�C�3�G
�h�5�Q	��6�q�>�1�Pm��Y�/�P�[�T�/�e�V���s)�f�-�^�-�
�.)�M�$�i�8�� �$N���D�C�w�j��pC�ܰ9�?�B
	�(Cs�!��H�G�J�c
�b�K5"�\�]�$�%�x�y	� �	�JF�<�			�j�e
�L�#�/A�0	�'

�p�q�l
�k�l�mC�9�3�&�7+�
�{�NC��8KZQ8
�"	RC�V��*�p�*�=
+$0C\���u�(�+D�&�%
��������{�x�y�x	�{B�:�;�j�i
�b�e�`
�a�`�a�^�_V	
�F�K�H�I�
���A�G
�F�E	�D
�E�B	CL���YC
?
L

S	����P�]���H��;��6�O��O�C�ɏ%�<��}��bC����c�\�c
�P
�Q

�H�3		
		C%����2�?�L�M�L�M�:�!��s��/�b�3�x�-�d�e�f	�]�(�~�)���$�A�.�)�8S
�!�4��^�M�����+�*�;�qC+�-�;�]�i�"�l�
��f	�&�`�Q%�Y'���k� �M�j
�K"�]$�=�b�I�]}�k�d�W)�_�k�2��d�t�-�}�|�8�
�M�ICb�.�0�F�?+,
�z�un			
�R�[�f�Y�T�_	)2
�`�e�2�/V�
��x�d�e�
�C�6�n
�
�i8�h# ��y#�xC�e��A�]7&�C?�1��g� �!� �!
���	�k�x�o
�n�m�h�e�d�a	�L	�M]�H�I�F�E!�@�E�>C&�=��C
{"

p	S
����\��`�a��u��x��c��<�aC� �O�
���/�`��6
�W�V�]�`����x�")(uC7��
���
��8�;�8�=�<�D�C
�A�B�<�=�G�n��}�|�q�L���F��-�K�	�[�7�f��(��g�N-�w&�dC���}
S
��Z�G��c��>��_,S����\��Y��"��6C�,�~S+�0�F-8	_N�?�$Cd5�&�oD�8�_",a�w�~��e�?nA5Z9:P�L�}/S�]�w�I3"P!�l�GjK�[�~�8�C��CC���	5qpq2Fw�S?	B!��*�MzC��Y�N�h�\��n��Y�5�#�Q�h;�>���^)N�"�s=2�ZC��LC��C��i4LCD�~�%�H�q�A���Q�R�{�e�
u��_�E�:^�C��l��t�q�0?��o�r�%�?�j��W�3�J�OG�b�E�3� �
G�N��Y�O�D��g�/�K�	��V�_�[
�*%�)�2�).��A�d��	��t�Z�
�>2�8
�t�C&�>�e�h�
$�,�I/�^@�
!�W#�	�`$�
(�}:�C&	�S�t+�;;��I�o�oC����Q�n�,Ew
P�>�L�x�72~
�B �5I�'$��'�I�!,S
	[�W�X
�(��h�ol�Q�;�z�)��f��V		(R��i�2W����B��9��E.�gO
�c�(�
_v�<.�TB�-0�,�|"]	e~2u9/�5s�R
�D("�Y#�l�$
�Z�f�
�(1�	�%�N�l�B7�=(�l{
�K�{�$�$�64��&�wB(2 �>
�L�#8-�a_�iJ�v4�q���I�w7�
���6��@��<�m��'\:8w
H��&�{�{"��?�m@93*G�w�)�<I�i�b�%$�<�.c�{�U�v+� H"8�JR�g�nz.�2�~�.
�p
�go�y0
�;�s
�p�Y�>�H��k"�K0!
�$
-'3�\-� `�	#RaF�E
�'�^ �>� (6�u �+"�h093@9
�d&B
Rv���{�2�*�9�/C,���Yb"�X�:�[X�M�iB�(
�*�Y�8�]�m�Yh���Q�S����,�6�A�I�|�t�g�g�f�8�I�*�,F	�}�0R>�xc ;>%e��!�$�*�;�A�G�X���0n��Fpc��s���F�#�*�K�P��D�\�a��m�9�=�{�i�g�!�*�U�y�	�*�$+R�J��
U��cN���� �9c_��hc�G!`	��!	��jH�v�a�r�n�2���~�0JK�7!�7�E
�&�5L�?���X�^����M�:���!�0�>�`�y�/��B	�&H�=�\�_��4�1�V�x��C�8�c�X6�e<�p(�w*�����%_�7J�E�2�v�7�o�*�_�{���'�7.�@�+�[Z�f�C�u^��+�!/�(�~�~|�u=�J���l�,�>�l^�~�I��-�B(�M^�Z�L�0R�BL�Q\�`S�h0�s�=�;��=�>�T�d�f)�og��?�j�p�\�s�d�f�
�� �>��\j�{
�u �$�	��.�!�%j�9�<��P�{�]�G�$v�>�A5�KV�\�g�u-�	��
�/��
��[�/Q�>�K�tx���g�-�1��U�~��r�Y�W�K��K�F�IP�Y��y�|�G��k�J�M�r�_#�hF�r�t�d����C�_��pT�~�m�3"�D�B�=��R�3�i(�m�@���1��E�J��bP�x"�~�:��+�*�0�7D�;�>>�F�H�`�i	�k�m>�{�~��G��0��
�T�;�so�{A�%��	�V�%�(��*����{�e�O�Tc t�"	�(�I����$�c!V�9c
$��c�I	��+	�2� 	�]�h�v�.�-c!��,�*�X�Y�)�p�x�C�B�.�a�L�K�b�q�|����� �#�(�*�3��]c�[�%c.��	cX��sh�|��k.���v���q�e�O��p�rN�m)�v��a�u�C�X)�\�^�;�q��w������=a�G9�K��U�@�^�l�t��	��#U�(Q�,\�2�t�S�-�j�z�|��
�S�#�>�0�
�:U�?x�HK�O�K�m�:��4�t�"��8�-�@�H�Ky�Z�_S�m�
����
��9��L�(�1�2y�<�^�T��r=�x���E��� �g�0�f�Q�f�o��|��
1�&��(�)�p�O�v�m��}�h�1��<�^�E��Lr�S��Y|�^��+�Tc G��g�8�}�(	�_�Q�B�iZ�:�6�I�D�O��E��F�u�P	�l�wc�$���]��L!��T��Z4
��_��jB
��l��w����+��k�J��=���x�@��E�R��l���~&��P��_�D��k\��sE��z@��+���n���J��T���9�c��"��M!��R��
,��'���k��-��0b��:���I�(��_�F�����=e���|��R��V�"��p�'��q��H$������K���Y1��*���R	��y��m���
��8�����5B��?���'��T��Zk��H��k��o�>��X���F��'��)��,���.�l��K���<�W��u���D���(�� ��R2���M��M>��V@��^��d:��p�
���$��g=��P�����2,���#��D�`��i�"��@��BX�����$�6��X���G�[��S��V��e|��i�g��t��4�����)��)^��2���?�;��N��Ps��]T��g;��nu��*��L����F��.1��4L��B�3��Fq��L����[��.��*���^��*�!��8��h"��"��p{��yQ��d��	=��P���#���'��b��5�t��@C��k`��H�l�����i��x.��W��3��
$���������"N��'\��L	��N� ��`.��l��?��t��w�(����BW���
��E�%��R���dc�>Nt��W�U��hn��x��{p��	��0����
��+��%>��.	��1��3�g��N��Un��j��mZ��uI��}���
���L��!{��)j��3��6���B��E!��J�a��m��qo��|!���
��"�J��8��;��?&��F���Q{��\l��d��h�U��x�Y��	G��:�������+q��;��=�0��O��Q;��W��Y��_���n&��r��u�)��$��(1��/�6��?��A��D���V%��[�x��n�1��j��
������((��+��.��1/��6���D��F��J	��La��R���>���P��3G��8q��CX��H���R���jH��q�H���<��Q����p��"@��)��,�k��@�R��s�	��y��8����>��$���@��D�N��U���g�O��
�
����n��&��(-��1�Z��N��P9��U[��e\��pM��yI����R��
���E��$�A��A���NH��T���hg��q%��wI����W���
��-��0��3�|��V1��Z$��^#��a?��hJ��q��t0��}	���I�����#���9��->��3��6��;��>��A��C��F��I%��O��R3��[6��e�"��~2��
���W��$���,��/��2���DK��M1��V�q��z��}��K���&���� 3��&��)��.�D��L��Q=��W��Z;��`:��g{��l\��vC����L��
s	�5�[�a�]�k�e�c�g�_�_c��"	����0>�G@�Ic�_�c��/����Ac+O����%��z�P��U�p��PK
!<>z���7chrome/pdfjs/content/web/cmaps/UniJIS2004-UTF32-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�UniJIS2004-UTF32-HC!��bC	 �U�?�dC��M��6S#��t&+"'�+�B�9S%�G(C%%�o/S	%@�uC &���z�wz!O�0�/
]��C'�C0��KS3��J�W�V�mC3�


��K?�C3�1$1
)')��q
C3�L


	c%�9�7�=�;�A�?�j�]�`�U�X�w	�~�6�O	�c�]�H^����[c0�k�g�6�E�P�T�\�b�h�t�w�|�~����mc#��^�d�I�X	�M�XPK
!<���3�3�6chrome/pdfjs/content/web/cmaps/UniJIS2004-UTF8-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE���?���?����? @\a
A Q¤k]>�d��_?V3�A´��M�T�+ 2!+�F�V�S�L�IQĀ��tA
�~$�c���
Q
Ė��n$�c�U�P
�&%$)AĤ�)
�f�7�![QĮ��s"'��A��"�}
�";�9�6"��BQŀ��j��;
��|�,"�m����Q/���'
��j�>"�kOL
i��O��fO�>��_c�0
�m��("��[��<��SAƒi��P�nQǍ�2��j���	�
���Aǵ����k�yQ
ɐ�X��"��"O(
��zA
ɞ�W-*'0#rQɬ�6B''�$�c�bAɸ��1TI�A�6��7�ZQʈ�94
M�-�|3��)F�Aʻb�I>M�c=:B�@�;	���J�<�@A+ʔ�J��#�"k
l�I�)�@�	��]VI�b�p#
8%#
�c'�`�p
�I
AΆ��K
�YB[ �g�"8��F.��q.�J	�N
M�y�E�u�~�:�E�<�fG�~�k	'c���h	W~�9�2�T	
�|�	�l�
;�Hl�Us�y'�
�^�V�h�i� N�.:�K��`�e�,�d�[�5�J��|�O�r�]BoẼ�5�r���j�L�m��{/�q� �a�f�E��G�?
�x�&�Z�
�T�
,�?�I�
�h�q�i�r
�7�[��;�e��
�j�w�T�����G�X��.+��O�L�P�
�X�?�~
U�3�H�.�3��f�	�8�g�M�D������
�f�s�6�?�C��$�@<(�#�a�	�x�B<‿�i�B�`-�k�]�F�	�[�m�&�s�w�{<�{� �y�e�u�@��
K�yG�d��+��C�U��p�t�m�Z�$�;�M�
�v��3�\�7��V�=�/�j�U�_(�A�K�B�P�$�9�y�.�@�7��#�B⺞��eB⺬��:B℮���W�z�2�R⼀�0��i�� �w��w�8�5�R�t
�{�v�\R⼖�S��h�>�
�c�r�/�qR⼣�&�?�
�C�Z��c��c�T�c{�i�7�&&�S�����o���(R@⽀�'�4�u��w��$��n��B�S�^�n�}�H�a�$�/�7�R�4��	�t�-� .�Y�O�@�4�<�{�0��P��S�_�(��VKp��A��X��]�X�`�-�t�G�W��-�e�"��N�OR@⾀�S���e?�K�j8�]�/���8�G�U�F��
��a��h�(�.��\�z(����$��X��[�o�p��%�t�:�+�T�}�R�a� �G���6]�C�3�!�Y�"�o5�n 
R⿀��m���<�7�J�a��r��C�8��w��z�B
⿓�~��o�_�H
�+��<���w*�s�`B〰�:
�a��aB〼�B�c�R
㈳�Op}��$
B㉀�Vly�n�OR㊐�g�

�D�5�,�=��2
���^B㌀�pB㊩�ZB㊢�{R㌃�j�r�[�Z�B一�0��+B2㌔�"��
��
��*�?�
�F�#�F�?�#�,�y�`�P'�>�M�;�:�J	�=�P�K��A�&�w�P�y�{B㌗�x


�B	�5�[
�fB㐂�$�&�b�y�S�v�m�h�N�	�� ���M�`�(���r�n$��"��cB㐅��l�#��n�g
���x�o��x�-�j��yB�'㐆��Z%�},�g0"�k�z�D�p�k�H
�w(�\
�p�a�P��u�5�>�u$�L�K&&�B.�I����i�G��t�\�c�]�Q�r�~
�K4#�c�\	�O8
�9�L	�P" �Z�[�A��\8�~F��r�i�u�e"�$��p9�g�@���Z
�^�g��v"�Z
����_�^�lH��
�f�S�wJ�d(�W�*V�W
�x�C�n�V
!4
�U�s<	�u&��1��<�f��C�d�`�j�p�Q� 
�\(�p��G��X�$�e�	��&@�z�y�v�d�I$�.�~.��d�C����&�r�K}v�eQ�/�J�3R万�*�9�!��#����}�D��R��1�U��BV且�L�^�i��O�2�w�l�i�@�m�3�2���M�(�7�X�B�/�u�ve�	�R�\��g�f��N�� �z�3�
�i�A�Z�j��9�{�:�	��~�W�d�Q���}�x�X�S�w�h��N�
���E����I�@�G�F�5�K��r�#�yB
丟�Z�
�a�L��e�d$�M"��T�
B丩��U�x�Q�y	
�t�u�T�]�"�y�x�w�F�nR	仝�����g��N��/�F�7B仭��9�z�P�A�,��E�gB仼�5�CB
仨��]�w�v�u�F�G�{�l�m�B�lR伈��a��	���4�<��k�Y��M�'��\B会�q���L�xB伕�kB伖��f�x�3R伳��d��)��(��w��z��k��G��.��Q���PB佀��f��u����N�;R位��\�u�C�R��6��y����a�r�/�����j���b��p�AB佩�)�Q��0��\�C�i�B佺�pB佣��k�	�5�:�G��>�?R
侈�%����k�!��
��W��V���������r��oB侖�.����3	�$�[��q���/�B�5�l�CB侚�<B
侗�s��M�L��<�L��F�z�R
俈��~��%���*��������}��|���B俗��F���{�X
�g�.��B�0�����F
B俠�l�(B俜���#	�"�?�$	����D�(�u�dR倉�T��`����2����x����p��e�a��>��wR
倖�*��6�m��O� ��~�z����yR倡�B�z��i�d�v���
�I�T�;��(B+倶�^�N�I��y��X��q��A�(�t�'�F��B��e�O����?�n�R	�E�$�q�C�
�)��G�>�@�/�.B偀�?
)�c�p�1�2B2倰����E� �t�u�5�{�$�y�x�y�5�{�&�'�&�}�I�|�7�6��-�0�5�}�0�1�(�;�:�B��)��
�o� �1�9�lR	僡��
�4��������L��k���+R
僭�_��J�R�T��j���I��B價�b��D��E�h�	�qt�E�D�9�:B儲�vB僺��+��D���-�
�8�E�F�)�(�C��(R兀�q�s����!�1�h�d�;�^�C�:��d�?��P�x��gB兒�r�9��B�?�A��)�h�.�7�Z�m�[�
�M�C�`��sB兤�JB兓��;�I�F�_��T�_�
��W�>�H�D�S�R冉�~�#��P��3������6�7�#�"B 冗�Q�3�+�D�{�v�s�g�f�o�t�1�j�D�F���Y�X�/��[�~��+�*��5�R�p�3�2B冝�K���]�a�`B冘����	
��1�`�B��j���.�-�<�=�'�P�gR凰����p�Q���u�s��L��E��zBT函�5�F�A�}�
��=�}�A�j���C��:���y�x�Q�P�)�?�I�.��-�G��B�;�Q�$�`�	�]�`�9��(��y�7�>�a�R�3�8�C��&�x�}�r�3�U�U�p�{�$�7�jF�P���Y�B�Z�o�Y�n�O�$�i�h�/�0B刕�N��a�~�`	�X�P�Y�GB1刅��o�#���N	�1	�H�$�g���Z�'�+�P�'�C��*�+�H�N�M�N�#�I��P	�O�N�F��$�!�z�g�
�Z�}R勛�R����)�h����v��a�`�o��*��=�3��n�A�@B勲��|�{�1�t�A�K�b�
B匀�SB勬��a�
�H�B�g�#�G�B��,�E�DR
匕�M�!�x��V��u�B��0������K��s�SB
匣�O�A�@�{�m�o�Z�@�x��
B匤�UB	匥��i�K�T�U�)�|�U�Q�C�&R	卅�V��m��
����
B卑�p�9r�]�J�/�h�.�<��� B卡�!B卙��q�#�J�%R
卬�"�J��W�+�^�X��;�^B卷�`�S�N�F�I�H��U�
�p��K�J�y�x�A�F�n�P��]�8���UN{�<B卿�m�S�R�h�]B卹��	�'��(�D�R�I�L�Y�U�*��n�E�U�T�-���~�W�V�B��(�P��{�7R叝�Y��J�A�f��]�Z�Eq�5��
�6��#�#�w�P�H�W��E��#��p�7��1�R�>B叺�r�F����?�R合�y�/�0�i���/�^�E���`�`B&君��l�C�8���C�<��b�*�t	�;�A�:�W��}�]�V�t�
��/�B�'��� �B�vB	吞��4�O�M�0��
B吚����"�^�_�Q�#��]�\����G�A�TR咈��%�X��m�y�����Y����B咕��H�7�%��p��7��CR咤���t������z����@�
�6��5B咱�����R	咷��L��}��B�8����=�8��2BV哀�i�$�0	
�5�@�=�L���m�n�F����J���o��5�}�:��A��+�2�7�.�m�=�*
	�A����z�o�t�
��O�f	�O�`�M�c~�&�%�M�4pi
�M�P�G�1�W
�~�o�w�h
�YB哨���^��g�)�_B'哃��N�9�8�U�E	�@�D�G�\�]�F�C�o�n�K�H�q�x�I�}�|
�]�i�h�)� B1哊���o�n	�o�n�o�n�F�o�j�k�j�K�q�p��Y�t�O�u�r�s�r�u
�N�r�s�p�q�r
�s�j�E�k�l�m�l�iR嘯�U��8���P��D��C��X��Y��\��[��TB噎�L
�k�v�S�V	�1�#�<�E��k�l�Y�\�DB噂�Z	��b��kB+嘻��*��{�i�@�h�x��k�v�w�v	��i�f�g�d�
�M��u��~��g�d�%�*�r�}�%� �a�b�%�*�t�A�5�6R
囈�g��L�i��H�w���3��8�t��/B囓�jR囗�k�_����=���J�e��9����
��BC団�
�R�1�� ��e��V�@�}�~�O��d�K�J��*�y�r
�`�D�W��6�.��y��_�6����B�;�4�c�b�%�1�V�L�y��v�K��6��_B坙�`��ZBF囤�P�d��U�u�t�t�k�x�p���m�h��y�{�p�L�m�h��u�t���t��o�r�I�H�y�	�I�H�B�{��x��z��y�z���	���N��W�R�����c�-�~���R	執�e��x���O��l�����v�B9堀��{�y�@�D	�c�b�)�7�O�~�_�}��B�%�.�c�L�?�B�54�\�I�a�@��.�V	����D�g�v
�S�s�D��M�L�U�P
�#�B�� �O��>B堯�2�,�k�|�>B2堃��a�y�x���
���~�	�v�o�~�	��~�I�}�f�	��]�p�|�
���|�@�}�|�a�t��z��e���
�`�E���z��#R壐�h��y������z��_��z����vB7壜�1�)�t�|�:�)�_�P�G�|�G�3�2�k�j�-�1����:�
o�|�q�R�#�P�Y��6�]�\�E��P����c�^�c�d�x�O�T�[B夋�i�B壝��y�����[� �|��"�|�/�.�/�w�$�%�$�;�8��C���=�<��1R	奧�M�y�z�Y��J��w��B��[��Br女��&�t�3�C�2�u�4�T��|��8�i�z�O�a��E�"��3�.�G��s�A�|�$�u��� �X��]
�o� �B�A�_�r�/�2�O�J	��t�C��#�!�D�� �=�<�;�R�g�*�
 ��B�K�L��
�=�V	���_��	�G�J�	�#�$�A
�A�B���w��a�_�H�9�t��!� �]�\NOB妤�n�n��l�B姊�q
�
�W��m�l�c

	�_�l�_�S4�?�>B;她��j�?�N�%�`�?�<�?�F�'�&�8�9�6�T�3�7�6�w�=�:�I�;�:�����D�K�J����Q�����S�
�aB)奲���P�I*�H
�W
�T!�J-�C�	R宀���H�G����H��I��J��[������e�[�h��*��{R宓��C�l��5��*���}�p�>��,�bB�4実�n��D�O�R�z�5��W�����C�;�B���G�t��:���e��J�O��B�W�M��o�X�+�D�)�(�G�B�/�}�L��A��/�/�L��}����1�k��'�s��u�t�S�ZI�R�2�e�b�i�@�/�P�C�B�k�`�o�`��w�E�9�J���	�[�d�R�
��� �;�2�
��w��G�>�F�[�_��:��K�F�O�J�I�� �_�h
�s�r	��a��w�r	�'�>�e�d�K�u�H8		�o�n�K���5�KB寀�q��r�	�>�{��P�z�q�Z�bB尔�*�?	��r�N�O
�N�o�\���e�B.宬��w�a�E�{�z�u	��M��������S��u���
�M�%�&�P�-�0�5�2�V�5�6B宷��y�_�~
	�b�o�c2�[$�D�B寬��NB+宭��=
�D�W
�d �I

�b�c�H�J�W

R
巠���c��������o�X�Y�e��PB/巫�m�	�W�^�y�s�j��x�7�C�.�T�q�4���m�h�[�=�
���o��O��B�"�� �q�b�G�B���a�`
�!�>�UB巷�B巸� �T�S�@�|�$�'�"�|�U�M�L�-�J�z
�y�)�N�T�M	�v�RR	幷����)��$�/�7�C�$B%庁�?��2�2�31�
T�g��n�g�C�g���`�o�^�J�U��&�n�K�L�g�l	B庖�p�
B庀��
�N
���}�-�*�z�U�'�&�H�'�K�B�U�Q�(�zR
延�����t����0��{��2��W�B开��V��W��V��w�R弆���-�,��U���M��L��+�`�&B
弓�w���F�Y�i����s��t��}R	弡��d�F�^���$��h��%B5弭�$
�3�^�5�z��3�B�B��~�w�z �1��;�X�O�t�d�)�i��i���@�-�`�F�7�6���-�R�h�e�}��D�GB弴��P�!!� B"弮���-�=�<�B�f�@�+�*�	��C�@�A�(��!�	�H���u�b�J�#�2���k�4�4��!R	徧�>���!���f�}��9�e��dB徳��-�L�*�F�
�F�W�P�H�;�	�J�?�6��|c�?�:�m�v��N

B德��5�`�4B徵�8��K���3��� ��5�,��5�4���9��J�*��R怒�V��������p�*��'��z��s�C�B4怠�5�&�#�4���
�+�2�S�J
�F�7�8�g�0��>!&�#�o�A�>�o�:��(�~�I���I�2�k�^�!�pB恝��gB怟��&���;�(�J��������)�B�	��E�B�D�-�,�)�(R	悢��7��
��8�R��{�f��^��_�R	悰�O�"��Y�"Z�����B悼�c�&�G�?�>�	�z�B惕�
B悻��!��C�.�1��'�"R惘�z��R��/����E��f��5�c�4����GB惧�g�K�F�>�S�F�_�^�D�#�&
�m�]�D�%���"�BB惲��U�%�$B惥��@�7�)�\�=�D�<�?�>�;�z�>�;�%�$�8�A��D�C�B�?�FR慄���j�;��I�����}�/�e�l�B
慓�!�Q�L�i�q�R���B慞��(�u�tB慬��LR慮��6����\��q
��v��nB5慾�G�Ce��S��P	�Q�i�a�
�7�A���D�z�5�.�y�z�i�n�=�P�@	���w��Y*��B憘��zB%慻��*�'�&�K�L�w	�2�B�O�)�v�E�3�v�E
�r��E��4�p�q�F��~�Y�V�W�V�S�K�R�a
�/R
戚�p�,��8���9�#��Z��9��:�$B?戦��l�o�n�+�m�V��@�m��G�
�I�A�3�$�S��M��)�
�(�3�<�y�@����r�=��,�w�n�/�x�T�K�m�~�5�r�Q�{�*�|���D��s�d	B
戩�^�C�_�D� �n�=�~�<B戧��f�e�D�`�_�^���a�-�,�n�u�\�R�i�q�p
�lR拌�i�	��"��)�M�F�-����H�1���5�p��v��9��AB7拠��M
<�B�	�=�D�
�k�h�f�A�3�N���Y�L�U�q<�
����v�1�IR�#�~	�
�'
�X�#����?�<L�D�,�?�w�<�G�8�s�d��yB挽�b�YG�B7拪��x�{�/�<�E�h�)�(�)�<�j��+�c
�
��~�k�h�-�(�F�c�b�|�}�e�`�}�|�c�b	�/�.�a�`��-�,��7�{�,�B�/�,�-�J�^�e�dR掞�r��]�#�k�h����������D�E�j��A��VB�3掲���2�I�L�A
�5�I�|�q6�{��2�+�W�
�9�K�>�q�|
=	4��c�X�q��C�>�8	�}�t����J�E�V	�C�:�q�7�E�l��M���/��D�c�n	������v�1�:�E�A�@MPAB��:����s�E�#�
��� ���?�@�/��V�#�4�(�w��\�|�N���E�&�m�`E�!���1�0�7�"�H�H�
�h�9�&�2��i� �C�^�5�.�u�x���O�P�O�8�A�]�H�n�{�e�Z���F�;�6�����P�e��,B揃�'1�V�^�O�|,� �g�b�f�c�]�b�#��"�k�~�qB揑�s
�1	�N�Q�`�q
�N�=�@�H�m�Y�| ��B,掽���b�_�b�k�j�q�p�I	
�d�w�x�O	
��
�Q�I���I
$�GB揪��J�g
�R�[�y�j�Z�q	�IB摯��(�w0B-揁���V%�Z�a	�K#�K
�H�Z�b�t
R昜�k��6�o��w�Y��<�+�(��W�}��R��k�e��N��+�S��O�l�!��R��B昴�j�*�/�>�A5�G��D�a�Z�_�BB昳��v	�J�.�'�W�~�B昷��L�F
R
晝�q��6���3�����V�yB-晩�i�R�g�,�C�c�T�F�#�p�c�Z�� �{�f�q�d�Q�R^�2�B���!� �%��2��1�@�B晳�"�eB 晪��Q�	����S�L�@�}�5�4����(�T�=�D�f�7�z��[�Z�E�D�E�D��R	曷��'�P��m�	�[�pB-最�7�&��$��=�<�Y�n�h�w�S�K��3�Zm5�/�B�}�N�.�I��6�A�9�J�1����-�&�#�"���P�$B朎�(��3�4B朁��_��
�����i
�7�6�f�D�a�`�S�O���
�'R杬��i��;��H��M�,�y�x������`��;B	杼�$�}�b�E�"�9�$
�gB杻��D�2�k
�j��YB构��j	R	枕��� ��#��L�T��1��Z��o�>B枠�n�t�]��x��!R枯���0�8�9��]����[�:

B柁�(�P�N�K�F�%��m�F�#�H�P�6B枻�+�DB
枾��{�C�h��~��~��)�&�|R
柮�6��h��o����e�%���$�B;査�*�d�}�B��n�o�T}�1�v�_�g�F�Y�b�U�]�d�i�,�B�~�	5�
�*�g�j�{x��X�>�9�j�5��I�^�?�& !�A�}��u��%�&	�[�,�E�B柺��J�'�~�N
�{�!B.柹��J�D�%�z���~�i�h�~�}�5�0�~�}�x�E�y�v�w�r�3�@�C�4�5�4�w�E�A�&��u�V�O�h��9�6R
梭�K��v��o��6����Q�����)B	梹�Q�[�G�]�p�=��C�NB棈�/B梻���GR棏�0�v�6��5��4��O������|��gB棟�i�
�I�Z�-�&�e�E�x�DB棨�.B棪����A�@��]�pR椃�� ��s���d�]��
��h����2�q�t�d��t�=����B#椙�@�^�C�
�8+& �5�|,��J��� ��(�}�h�w�r�q�t�K�L�uB楢�X�2B椖���%�$�%����F�&��_�;��=���$�p�"�a�'�HR	楷�u��f�`���=���~B榁��c�)�*�1�,8=$�A��,�L�)�?�n�i��l�B


�k�n�d�{�d�:�-�g�2�,�W��'�+�I�:����u�N� 
�i�@��j�=�8
��		�o�j�E/0
�{�~	��"�H�c�bad�y�z�1���S
�H���l�)�D�B���G�HB榊�	�z�^�n���h�h�#$�"�y�H�z$
�\��B榀�;�w�v	�r�[�Z�J�
	�F�X�U�g�

�q�P�{�zB+榒��)�?
�>	�C�E�D�I	�L�Q�P�R�U�Z�I�P�g�h�i�j�H	
�M�
��Z
B榨���u�X
	�}��R�J�O7B榍���L�w�r�V�W#�E
�V
R	歟�b��n��o�m�V���p��Q��nB歩�2�n�O��r��n�@�E�=�!�
�.��B	歬��c�����F��
�B歫��?�TR
殬��I�[���o��H��G��DB4殷�u����u�@�@�A�r�y�2�o�2�(��4�T�#����3�~��X�A��
�!�<��X��E�^�v�m��l�Q�^�'��B毖�=�(�\�GB/殽��j��M�1�q� � �#�`
�;�'�p�u�B�&�L�u�t�O�N�O�+�^�k�p�k�&�>��N�s�r�A�@�u�y�("��A�3�y�j�u�wR	沁�
���^�)�M��4����<B沌�1�2R沒��3���-��u�� �;��m�@��2B没��c� �j��P� �(��4�X
�'�A�H�H���&B沪�IB	沟��\�w�E�:�y�M�E�;�vR	泏��`�o�n�}����&��7��6��'B6泙�!��%��B�i�l�O�6�Q�W��h�%��'��(��g�D�(�k�r�E�@�Q�B�l�5�x�2�e�\�)�j�
�{�p�l��|�`�K��\�2B泚�B�i�*�WB*泜���-�j�?�+�f��D�F�#�^�k�h�i�h	�C�B�m�.�O�r�s�L�M�W�e�d�J�1�h�9�*�]�h�9�*�E�]��NR	涑���.��{��p��g� ��L�1��_B涛�mX�X�g�;��g�C{�l
�A�J�
�i�^�=�6
��"
��+B涪��)�`��]�B�H�I�J�K�J�U�w�Q�,�j	�KB涷��n�bR淮�K��N���K��C������X��	�T�#�%�,B清�Z�'�r�T��]�U�,*�1�B渗�FB渀��uR渝�^��
����t��f��U�S�@�rB3温�9�6��~�w��X�e�h�{�D�E�>�/�P��*�y�q�t�C�F
�?��@��f	�=�R�{�t�n�W�G�4�F�M
�a�T�B�A�R�B�E
�+B渲��&�)� �K�Y�h�v	�{�[�^���r�K	�w�M�B��r�D�}�B渻��w�V
�d�DR	滇����C��8��e�X���	�B#滑�H�8	�3%
�p
�)�41�A��0��E��.�e�8
��e��)���
�
�A
�c�@�CB滙�8)�"
�m�L�l���q
�p�f�[�X�Y�\�UB	漄��	
�SR潗��A��o��L�U���S��j������!�SB=潤�k	�8X�S�a��|�N�	�>�.�k�B	�~���w�	�~�s�:�W�|�K��C�A�V��;�M�>�g)��Z�	�h�|�%�
��\�A
��O�h�Q�[�	�x
�C��Q�u�Z�	�|�K�G�F��W�}#�m
�_��{��H�"�	�%Bi潦�DM�C


�H

�A�B+2��B	�Muv�n�R
�K�o		�W
�J#�M
�@B澈�Q,��_��N�^�	�
�U5)�0�o�#��H�%�W�H�X
 �{�X�J�9
�h �o�[�p�g�<�M�B'潽�
�M�%�$�c�����W�B!�`�j�X�X �n�W�m�i
�HB潾��3�M�J%��]&�n��y�b�\2�I�B.潨��C�g	�j��C	�H�O�X�V"�`
�a�v�j,�HB澇��3��U�h�0-I�_�i�T�,��b��H�B+潬���V	�T"��Y�L�~��J!
�H	�q
�@
�Z	
�UR
牗����+�d��P����d��Q��Z��}����'�J��JBE牧��
�?�~�	�B�X�Y�~���K�[
�(�I�@�g
���_
�p�`��]��V
�1�\����D�n�N�8�E�Q��~�Y�=
�v��$�O�?�j�*�	�
��Y�D�e�N �E����!�c�E�_��Z�q�O�v,�Q� �^�i���H�a�	�.�8BW牴�k	�B

�I
�P	�X�w�D	4+�L�A	
�L)(�b�JB犱�a�e���K�Q�u�w�d�x�aB9犍�;�K�%�$3�l��K�(�F�`�a	�O�N�R	
�H�I
�F�@�=�J�N		�H�WB犛��I��h�{��Z
�x�_5�WB6牸���N�P
&�L
�T�O	�E�F�l	�c	�ZB牨��H�L��T�U�M�j
�
�|�o�U�>#�B/牫��A�W	�o�N�`�F
�X	�@��`�]�IR甞�J�]��V�*���-����]��>������k��/�0B
田�>�<��Z�
�~��E�H�GB甯�rB甶���E�@�!�dR
畆�S��^�g�������X��=��d��'��&B畔�^��JR
留�y�4�q�!��*�H��
����OB	畤�V�E�#�f�W�Z��D��ER畯�u�-��h��k�~��d��m�-�(��D�.BF畿�@�B�@��'�U��[�j�e�V�Q�L��C�t�k��|�?�g�"��������O�N�D	
�F�w�z�[�V�a�d
�+�s��c�zB疁�~#�o�c�b�F	��
�M�w�vB:疅��	�c�b�S�R�c��
�\�{�c�@��	�����	�����P�����!���@�=��B�7�4�E�o�n�s�C�8�3�2�I�i�h
�i�.�G�+	�BR	皀�����O�7�o��`��!� �
Be皐�w�
��C���;�`��^��P	�x��]��B�
�<��2�N�p�g�Y�C� �m�\�H��C�Z��g�fp�a�&�{�*���N��'�T���=
�R�#�a�G�]�
��G�y�6�N�1�d�{�P�Y��6�;�=�#�F�L�0�D�A�Q�� +�c�8�g�I�{�^�X�1�c�`�2�W�p�O	�T�I�E�$�Y�K��:�;x�}��@�W�	��S�TmBh皋��M
�%�&�H�]	
�K�G	

�O
�d.�I�F
�I	=:�S

�@*	+	�UB皛�y�=�N�>�o��/�{��l�u�P���4�_B硏�B睪�U�~B'盅��w�e
�W�{	�Q
�[� �!�u�i
�u	�G�
�NB皝��^�W
�X� �E��b*�~�O�Q2�NB2皕��

�Y�n�b#�]��I�Z"�_	�Y�Q
	�d

�b
�MB0皭��r�[�F
	�F�W�J��d!�Y�U	�x�B�n�XBB皟���a	�O	�D�}�M"�U�`�k
�G

�F�N�M�_R秝��g�W��k��@��)��(��)�����G�B秩��f�;
�-�D�D�$���Y��X�'�v�
���SB秪��&�S�T�P�E�%�$��8�C���>B秱��k�JR	稷� ��\��>�����t�B穀��b���	���\�e�$��X�F�n�C�?B	穃�!
�t�H�uB穄��+�A��H�S�R�K�
���@�E����N�A��)�3�Z�)�>R	窐��Z�>��5�g����B	窟�x	�n�i�w�t�j�G�JB窠�QB窞��{
�A�&�#�"�#R
竃�T�@��B��=���|�����^��O����
B
竑�	�	�n�������#�-R	童��d���.�6��1�J��d��wB端�z��Q�0�7�y�7�B��	�X�N�#B笇�WB	竱���;�:��\�I�j	�I�|R	笥� �j������h�9���U��vB笳�J�K�K�V�o�M�B笱�Z	�T�B�C�'B笰��
�@R筋�R�����'��k�0��N��S��v��B箆��B筝�i!�P&B筠�]�(�+�T�U�L�M�&�j�o�	�D�8�+�-R箔�)���2��	�.��$��)	�X�W��"��_B箪�{�T�a�p�^�G�5�LB箯�bB	箥���3�0�+�*�+�.�DR	範�c��0���t��R��y��F��%BN篏�n
���Z�.�-�<�H	��
�z�q�{�r�u�v��@��#�&/.�y�x�K���!�-��0�5�#�E�B�'�'�*�s�6�B��A��;�fB篗�c�q
�Z

�C��BB*篔��
�h�i�f�B�i�z�l�m�l�m
�@�C�U�q���n�u�t�|�C+�S�w	�t�u��n�I�g�f�eB篛���f	�Q�I)
�SR	糒�&��H�H�C��3��z�����,B糜�'�E�/�+��1�2�u�t��C�A��V�c�K�(��B糝�v�H�I	�J�MB糩��1R紏��2�����'�K�������k�I�0�c��T���HB素�I�L���*�nR
紪��6�����z����=����3�_B紵�9��5��X��+R	絀��&�Q����.�R
��X��1B 絋�5��,��t�S�t�/��{�r���k��Y�T�K�9�V�h�1�H�x�
B絍��N���
�T�5�!�T	�W�X�B�>�{�z��Z�>�A�<��BB絑��8
�ER綪��6��O�S��P�s���b��E�2��T��M�{�����B!綽�N��(�G�l�u�X�#�P�?��*�y�k�`��	�k�;��k�r�/�*�E��d�2
	B緀��8�I	���;	�a��9�i�N�h�i�fB緢��G�E	R	縛�1��2����Z��P��Y
Bi縦�N��{�	
5�3�B�N���w��<�S��t�8�Q�|��O�j�B�m�b�T�I
�)�����J����Q�T�d���z�4�M�H�L�!��G�H�!� �b�_�?�6���?�C�6��K�-�x
���d�c�[�B繇��O�N�Y�k
�o-�N
B%縧��B�[�Z�,�M�1)�0�1�0�1�B�0�+� �&�%�D�"��
�@
���
�
��U��B縭��L�N�o�)�\	�f	�CR	耀�>�<��j��i�6�w�r��b�R耊��c��O�O��.����g��d��O��N�������B耜�@
�Y�D���
�F�Q�P���3�2B
耞��n��|��*��F�|�}�|B耝��f	�AR	聯�F���6��;���>��9�IB+聹�Q�i�h�D�9�Q�$�O�4�;�@�x��z�Z��I�$�T�u�i�T�l�H�Y�z�G��'�N	����8B聻��j�L�

�
��~��~��~�L�/�.�
�/�.B肁��p7�NR胭��t���!�������5����F�����������}��B$脂�7��n�~�e���l��O�T�e�v�k�,�;�%��F��T�
�h��
�.
�s�v�F���q�A�j��>��8�B+脉�d�G 
�r�@�m		
�A�FB
脖�2�5�4�F���t/�Z)�IB=脃��y�~�	�
�~����z���{�G�z
��?�D��C�8�j�k��M�{�z�����}�t���M�E�L�z�K�@��	�u�O�N�v�u�v�u�p��	��}�@�p�oR
與��M�r�X��\���&�M��0��eB舒�	R
舖��c�-��x�p��Q�	��\��]�WB舡�=�0�v��e�y�x��R	舲�>��7��6��-��q�\�w��NB
艇���*�^�I�7�T�9
�D�M�YB
艀�$�N�BB%舼���F�t��m�5�0�1�$�%�$�q�U�V�w�v�j�
�W�c�D�i�C�V���Y�I� �^�9�f�sR芩��+�R��?�k��~��u������(��-��,R	芷�E��%�?��0��k��j��	��6�JB	苅�_��R��k�p�$�e�^B苆�G�JB苐��	�R苞�E��L��S��h��i��P��+�W� ��tB苫�)�m�
	�E�(�y�J�U��T�=�B�;�Z�xB苪���S��M�A�L��B苭��%�sR茫�T��,�u�t��3��H��U��>��E�S����'�h��BB荀�O�J�
�B茼��9�F�a�`�B茽��/�DR
荏�c�Z��p��K��,��U��A��B荷�YWB荢��m�p�E�
;4B荚�z�h��/�Q�B�~�UR	莘�O�N��?��P�l�m��m�B7莢�[�O�@�*�C�_�2�w�h�*�!�@�b�I��N�k�V�!�^� �0�f�W�E�7�>��!�3�<�#��+�6�Y�\%�M�H�\�*�=d�y�ZB莧��5�K�@�`�������i
��+�*�D�K�M�f!�P�m�l�"�B莦��9	�i�p�UR	葟��;���E�P�)���g��?��$R	葩���������d��5BM葵�n�$�A�G�4��	�C��W�,-�]��}�Z�=�2�A�s�r�/��m�v�;�>�,�s�W�(�@�Y���f��
�~�S�Z�;�~�T��A�#�w�r�S��h�i�P�i��t�r�CJ�a�w�T�v�_	
�AB0葳��=�M�z�D�3
�B��5�4���C�B��A�����z�
�N�C	�P�G�J�K�G�Q��u�n�
�H
�J
�K�T�AB葴��J�T�v%�O�P	R	薄�,��t�e����r��q��p�EB薗��PB薐�:B薏�a�(�b�c�b�ER
薤�-��z������w�,�{��r��F��?��BN薹�>�F���9�:�
��C�&�@�%�'�"�C?@��r�m�
�W� �6�M�	�;�J��d�/�L�O�N��)�N�H�b�
��=�R
	�M�5�6�C�'���
�	�3�,�Y��t��	�	�A�B薷�b�V%�G�]�H�h��
�K�O�J�!B.薼��|�J�k�n�s�r�s�@�r�o�n�m�l�s�t�C�/�`�5�B�C�
�x�O'��G�(�%
�B"薴��m�I
�\�_�Q"
#	�ER
蜂�X��^�"�s��m��l��m��|B蜘��>�l�Y�(��d��M�,T�~��&B-蜍�r
 

�L
�,�7		�I
�B 
���H�BQ蜎��#�n�o�a�n�o�\��
�a�l�Z�i�h�]�
�c�A�b�h�k��b
�U�l���J�3�=�t�v�h�c�b�]�t�}�|���d�[�x�z�A�y�\�e�d�_�^�_�^�i�h�c�b�a�`�]�	�b�o�n�A�k�*#��f�=�+R
蠍���|��q��,�k���CB蠟��=��	�D�U�V�g�)�z��)B蠜�B蠘��<�x�}�|	�m�n�o�j�k�j��m�D�j��k�j�R衕��C�w��c��h��9��&��!�� ��
�b��.�x��?����HB-表�/��x�/�Hj�b
S�Z�y��I�
�d�	�d��W�	�t�W� �a;�H�\�S����F�L�A�<��q�;�3�d�z��d�+��^�={�v�Y�u�D�UBL衫�3�B

				�T
	�.�7�E�$�
�E
�^��u�D�@�JB袘��[��
�j 
�|�D�[��Z�O�G�F�yB,衩��O�j�i�p�C�h�[�r�d�Q�e$�h�a�`�i�y�F�x�`�a�`�L�e�
��`
�a�`�a�`�X�c��B$衱��G�D�c�U�K
�O
�`R討�|��H��=��T��
�C��N���A�W�!B訛�R
訝��,���e��d����A��b��]��\B訪�Z�/�g�`�k��B訶��@�[�E�gB訯��y����A���#�j�I�#R	詐�.�f�P��b��u�X���I�IB4詛�
�!��6�G��n�8�0���#�V�J�9�E�
�:�[�>�r�g�6�>�r�u��d�u��-��4�i�!	�~��9�B��%�K�/��y�`�&B詝��d�j
�k�j�o�F�n	�K�J�M�|�N�M��O�P�s�r�OB詜���Z	�GR	諚��3�� ��s��B諤�#���+��J���U�~�I�A�0�o�-�,�\�T�MB諴�&B諬��c�h�i�S�:�E�7�L�Q�8�7�LR	謙�`����.����2�i��k�aBD謦�5�d�m�7�<�B�h�e�C�D�K�N�'�"�M���F�G�F�B�w��h��� �G�J�A�%�&�o�n�I�[�"�5�� �E�U�X�1�n�.��SB	謷�(�\��h�k�I�HB:謭���4�E�m��l�;�J��4�8�o�~�K�J�l�m�H�A��8�k�O�8
�u���l�9�4�F�1�G��A�.�o�A�.��H�T��~�L�A�<R
貧�A�E���/�R�V�g�nB
貴�H�D�%�5��r�
��]��^�eR賀�l��?�a����
��w�X����I��RB!賎�*�J�b�kd�
�(�u�t�_��K�0�C��.����H�)�M��+�>�<
� �i�y�R�]��y�Q�T�.�	�t�MB,賍�}�:�J��I
�r�q��b�J�c �C�P
�LB8賒��v�7�Q
��5�4��B��9�6�
���
�
�U���		��	��'�&�S��F�G�`�&�'�b
�G��	��E�C�D�E�D�M	�Q�^�0�=	�<�9�}B賏��<

�e�B
�C	
�`!R
蹇� ��D��A��V����YB蹟�|��X�B蹕�*
�DB蹔��1�V��?�>��^�a���7�Z�M�D�#�"�G�F��]�\�R	躩����G�U�V��B�+��E�*B車��q�|�0�J�!�1�a�E��4
�P�UB躾�>�A�`�a�j�k�F
B躳�H�\�i��D�]�E�\	�C�@��"���G�T�E�
�f�#�E
�j�O�&�S�|R輥��!�����(��O���:�H�W�~��uR	輳�S��J�+�/�X��]�����#B?輾�X�[�B�V���[�^�A��y�&�$���]�^�M�L�	�c��`��J�w�L�B�C�*��S�7���j�#�8�}��|�!�"�A�\�1��B�K���\����G�&B轀��?�L�O�N�_�^�Q��R�?
�j�L�]�^
�u�N� �u�t��F�u�0�c�$B轁��{
�w�A
R	逓�d�J�N��V��_�i��BI逝�f�N�
��� �;�I�z�J�O�`��
�
�b�e�u
�Z��@�=�Y�o�V�z�	�T��D�m�"�S�D�/
�*�H�[�x�h�-�6�i�`�Y�,�K�8�?��E�#�&
�j�1�+0��N�B�0�M��8��4�M��C��<�;�T�2�uBF逞�v�*�#
�T�G�J�Z�r�O�D��e�B�-�jH�e�>�G�]�>�?
�>�u�I�W�`
�a
�N�Z�[�d�a�NB逬�V�W��b�b�J�i			B逭��~�i
�H'�d�NB9逯��0�?�I�Z�Y�:�=�<�K�=�<�=�:�=�8�9�8�E�9�6�5�(�/�I�$�%� �!�]����J�����DB逿���b�a�f	�b	�F
�ER
釖�8��G��.��m�	����c�^�B釣�|���e��-%�"�KB釡�:�F�
�	
���
��@�zB釬��}�t��6�7��
��B�a���,�b
�i�/
�&R
鉂������0���'��_��t��mB:鉎�L�}�~���u�{�\�	���	�G��A�5�f��*�w�B
��w�z
��G	�N�U��u�z	�+��{�Q�s���=�:�/��_�b�B�3�e�K�NB鉏�z��O�N�N�d�K�JB4鉍��M��������I��������z�w���@�v�{����	�����H��)�vR錜��o���B��?�r��� ��)�#��n����L�$��G�tB錯�h�x�X�I�e�9�eB
錮�[
�H�
��z�B錳��W�4�5�4�O�_��m��6�'�9�^�9�o�&R	鍩��x�c�6��A����E��$��+��~B鍾�I�M�e	�.��E�
�b��_�v�"�n�U�X��%��H�:��F��W�hB7鍼�e�W
�*	�H�}�I
�h�e�h�e
�r�J�_	�V�W�F�L	�S�j�U�A��,�-B6鍳��%�	�5�B�e�d�e�E�D
�+�
�$�E�#�H�I� �Y�A�	� �Y	�d�#�N�O�D� ��@�G�$�Y�I��L�M�J���#
���L�M���L�L�L�B"鍴��z�O
�]
�I	
�E�T�9�ER閟����}����'�C�x�2���w�xB?閭��A�D�D�5�.�I�H�v�� �7�B�U�K�@���)�,���
�<�D�q�4�1�c�L�A�B�u�p�;�h�%�f���>�G�)��}��R�$�k�TB&閫��Y���
�~���F�K�J������t�`�"�S�R�O�N�C�)�(�)�V�P�A�U�T�1�0B閺��&�^�~�Z
�RR
隔�4���r��s��`�C���z�HB)隠�E�6�D�s�n�Z�U�+��B�C��A��o�(�g�|���-�8��O�]�&�I�I�y�r�K�H�K�$�A�|�+�D�1B隤�"�|�}�0�S�T�c�f�i
�t�uB隟��8�cR	霂�)�&��Q��P��Q�	��$��iB;霍�R�C�5�v�5�4�!�B�=��"�F���!���4�5�~�E�D�6�]�$�q�r�!�K�l�D�`�g�W
��E�3�6��W�B$霔��\�B�C�F�G�J�}�l�q�o�n�o�,�?�o�k�j�*�U�E��>�1�p�>�1	�0�r�=
�H�I��B�	B霚��?�l
"�D
 R	韆�������J�N����P�<�
B韓���=�k�d�A�:��	A�|�`B韜�B韐��R�K�H�I�y�j�T�A�.�9�H
��9�y�D�I�M�6�1R	頌���L�y�Y�?���e��~B?頗�B�&�K��k�%� � �O��K���$�\�1��0�r�C�3�G
�h�5�Q	��6�q�K�>�1�P�m��Y�/�P�[�T�O�/�e�V���s)�f�N�-�^�-�
�.�i��
�$�i�K�8�� �$���D��D�C�w�j�^��pB飜�9�?�
	�(Bs頡��H�G�H�J�c
�b�K�u�L"�\�N�]�$�%�x�y	� ��S	�J��<��T	�E		�j�e�A
�L�#�/��C�0	�'�J�P
�p�q�l�M�k�l�mB頹�3�f�7�k�
�{�NB頞�8�Z��H�Z��W�x
�]�"�I�B顖��*��p�j�}
�k$�p�]B\頖��u�(�X�+��&�%�J���N������K�{�x�y�x	�{��A�:�;�j�i�M�b�e�`
�a�O�`�a�^�_��L	
�W�F�K�H�I�
���A�G�J�F�E	�D
�E�B�W	BL頙��Y�N��]�P�M��]
�\�Q��H�@
�T
�ER	鮦��P�]���H��;��6�O��O�B鯉�%�<��}�W��bB鮱��c�\�D�c
�P
�Q
�M�H�3		
		B%鮰��2�?�L�M�L�M�:�A�!��s��/�b�3�x�-�d�e�f	�]�(�~�)��B��$�A�.�)�8R
鰡�4��^�M�����+�*�;�qB+鰭�;�]�i�L�"�l�
��f	�&�`�Q�P%�Y'���k��`�M�j
�K�b�]$��}�b�I�]�}�k�d�S�W)�_�k�2��d�t�-�D�}�|�8�
�M�IBb鰮�0�F�?+,�G
�z�u�n	�R		�A
�R�[�f�Y�T�_�L	)2�E
�`�e�[�2�/��
��C�x�d�e�S�
�B鰶�n�J�J�i�x�h�N#�Q�`��V�y#�xB鱥��A�]�w�C&�_�B?鰱��g� �!�L� �!
���	��k�X�x�o
�n�L�m�h�e�d�H�a�I�L	�N�M��H�I�D�F�E�a�@�E�B�>B&鰽��C�E
�{�b
�H�]�[
�O�p�R	R
鼎���\��`�a��u��x��c��<�aB鼠�O�
���K�/�`��6
�W�V�U�]�`����x�"�i(uB8鼙�
���
��8�C�;�8�=�<�D�C
�A�B�<�=�G�Z�n��}�|�q�L�2�D�7��F��-�K�I�[�7�f���h��g�R�N�m�w�f�dB鼚��}�G�Q
R
﨎�Z�G��c��>��_,R福���\��Y��"��6B館�~R侮�F-8	_N�?�$Cd5�&�oDR懲��P�_",a�w�~��e�?nA5Z9:P�L�}/R艹�w�I3"P!�l�GjK�[�~�8�B!��	5qpq2Fw�S?�@	B!��*�Mz�	B	︐�L�g �@��D�Bff�C
𡨚���u��#�Q��h;�>��X���L)��"�s=�r�ZC🄀�}C🈂�i4LC眞�-C𠤎�SCB𠀋��~�%�H�q�A���Q�R�{�[��E�z^�C���l��N�t�1�0����o�r�e�?��*��W�3��
�O��b��3�`�
��N���Y��O�D���'�/�K�	���V��_�[
�*%�)�Q�2�).��Z���d���	��t�Z�
�>�R2�8
�t�C�f�>�e�h�
�d�,�I/�F�^@�
!�W�eX$�
�h�}�z�C𢘉�S��4+�{;��I�/�oC�𠂉��Q�n�,��O�7
�P�~��x�w2�~
��`�u�	�g$���g�	��a,�
	�[�]�W�X�M��(��h��o�,��Q��{�:�)�E�f���W�Y		�h���i�2�����\�B��9��.�g�
�c�(�M��v�|.��B�L�-0�,�|"�]	�e�~2�u�y�o�5�3�R�M�("�Y�c�l��d
�Z�&�@
�(�q�	�%�N��W�l�7�}(�l�{
��{�d�$�v4��f�wB(�r�`��~
���c�x�m�!�_�i�
�v�t�q���	�w�w�
�O��6���X�|�m���g�\�z�x�w��P
���&�U�{�;"�^���m��y�s*��w�i�<�	��i�b�%$�<�n�#��{�U�X�v+�`H"�Q�x�
�R�g�n�z�n�r�~�.
�H�0
�g�o��y0
�{�s�J��0�Y��~����k"�K0!
�\�$
�Y-�g�s�\-�`� �	�c�R�!��
��g�^ �~� �h�v�u �+"�h0�y�s��y
�d&��
�R�6�V��{�2�*�y�P�/C,𠃵��Y�b"��z�[X�M��iB�(
��*��Y�x�]�-��Yh�����S�����,�v�A��	�|��t�g�g�f��x�I�*��E,�YF�\	��}��p��~�x` ;>a¡e��!�A�$�*�;�A�G�X�A���s���@����#�*	�K�@�UaǸ�s�h��|��k�n���v�a΄��]��L!��T�\��Z�t
��_��j�
��l��wb†��Dn�U�a��m��y�=�{�V�i�g�!�*��U�y�	�*�$�k>�J�@�	��
��@6�<���Bp��"	����F�0�G�@�f��Ib享���`�9b忘�hb�KⅠ	��!	��j�H�v�!�r�n�r���@��~�0�J?�7�@�w!�7�E�M�&�5��?�\����^�F���M�:�n�!�0��~�� �y�o��B�_	�&��=�\�_��4�1�V��x���8��c�X�v�e�&�w�j����X�%��7�
�E�r�v�w��o�*��{�Y��'�7�n�@�k�[��f��u�^��k�!/�(�~�~�|�u�}��J����l�,��~�l��~�U��	��m�B(�M�^�Z��0��B�L�Q��`��h�p�s�}��{��}�>��d�p�o�g���j��p�\�3�d�Q�� �>�T�\�*�{
�u�`��d�	���n�!�%�j�9�<��P�{��]���$�v�>�A�u�K��\�g�u�m�	��
�o��
���[�/���>��t�x���E��'�-�1�^�U�~��r�Y�W���K�F�I��Y�p�|���k�J�M�r�_#�h��r�H�t�$��\����_��p��T�~�m�3"�D�B�=��R�s�i�h�m����1��E�J��b�P�x"�~�z��k�*�0�R�7��;�>�~�F�H�`�i	�k�m�~�{�~��]�����p��
�T�{�s�o�{��%���I���%�(��j����{�e�O�T�[�a�]�k�e�B�g�_b⁴�"�F	�(�	����d�b⅖�9bⒶ	�c�@�m�I	���k	��r��`	�]�h�v��P.�-b↖�,�*��Y�i�p�x��B��n�a�L�K�b�q�|����� �C�#�(�*�3��]���cb﹅�_b者�%b⺌�	bNḾ�q��e����L�p�r��m�i�v��a�u��D�X)�\�O�^�;�q��A�w������=�!�G�y�K��U��^�l�t��	�T�#��(��,�\�2��t�S�m�j�:�|�[�
��#�~�0�d�?�x�H��O��m�z���t��t�"��8�m�@��H�K�y�Z�_�S�m�
����
��y���(�q�2�y�<��T�K�r�}�x������ ��g�0��o��|��
�q��f��h�)�0�O��v�m�\�}�(�1�[�<��E��L�r�S��Y�|�^��k�Tb⁇��'�8�}�@��(	�_�Q���i��:�v�I��O��Z�E��F�u��	�l�wb�≤���k��k��
��=���x����E���l��Y��~&���P��_���k���s���z���+�H���.���J��T���9�#���b��M!��R�F��
,���g���k��-��0�"��:���I�h��_������=�e���|��R��V��b��p�'���q��H�d������K�^��Y�q��*���R	��y�R��m���
���x�����5���?�T��'��T�[��Z�+���H��k��o�~���X�����'��)��,���.�l��K����<���u�_��D���h�� ��R�r���
��M�~��V�`��d�z��p�
���d��g�}��������2�l���c��D� ��i�b��@��B������$�v��X�O��G���S��V�~��i��g���t���t�����)��)�^��2���?�{��N��P�s��]���g�{��n�u���j��L������.�q��4���B�s��F�q��L�������n��*�]�����*��a��8��h�b���b��p�{��y����$��	�}��P�g���g��b��5�t��@���k� ��H�l�����i���x�n������s��
�d���������"�N��'���L	��N�`��`�n��l��?��t��w�h����B����J��E�%��R�T��eb�<乴��W�U��h�.��x�B��{��0��	�]���p����
���k��%�~��.	��1��3�g��N��U�n��j��m�Z��u�	��}���
�]�����!�{��)�j��3��6���B�Y��E!��J�a��m��q�o��|!���M��"�
��8��;��?�f��F���Q�{��\�l��d��h�U��x���	����z���^������+�q��;��=�p��O��Q�{��W��Y��_�_��n&��r��u�i��$�]��(1��/�v��?��A�Q��D���V�e��[�8��n�q���j��
����T��((��+��.�U��1�o��6���D��F��J�I��L�!��R����~����Y��8�1��C�X��H���R���j���q����|���Q�����p��"���)��,�k��@���s����x�����~��$�H��@��D���U����g���
�M�����n��&��(�m��1���N�\��P�y��U���e���p�M��y�	�������
����E��$���A���N���T���h�g��q%��w�	�����W���
��-��0�O��3�|��V1��Z�d��^�c��a���h�
��q��t�p��}	����	�����c���y��-�~��3��6�Y��;��>��A��C��F��I�e��O��R�s��[�v��e�b��~2���M�����$���,��/�[��2���D�K��M1��V�q��z��}���K���&���P�� 3��&�[��)��.���L�^��Q�}��W��Z�{��`�z��g�{��l���v����P�����
c
🄐���@	�%��A�@	�Q������%��z��P��U�0��PK
!<9�Ǿ��6chrome/pdfjs/content/web/cmaps/UniJIS2004-UTF8-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�UniJIS2004-UTF8-HA°�MB←�bB	‐�U��dB′�Q�P6R⎛�t&+"'�+�B�9R┌�G(B┥�o/R	╀�uB ☜����z�wz�aO�_�0�/�Y
]�C�B✂�B゠�KR㌃��J�W�V�mB㌔�


�R��?�[�B㌕�1$1
�F)')��Lq
B㌗�L


�B	b─�9�7�=�;�A�?�j�]�`�U�X�w�I�~��v�O	�c�]��H����Q�[b〘�k�g�6�E�P�T�\�b�h�t�C�w�|�~����mb⎰�^��d�I�	�M�XPK
!<&h:���8chrome/pdfjs/content/web/cmaps/UniJISPro-UCS2-HW-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
UniJIS-UCS2-Ha
 ;�g�!�$&�#
�M�_�U�T�W�
�A!��bA %�Z�n�6�Y;�m�2A!��6a%�9�7�=�;�A�?Q%�G(q%#�j�o�m�l�n�q�p�r�[�]�\�`�S�U�T�Xa%=�wQ	%@�ua%I�~a	&�������_�O	�c�N�]a'�A0�Vz!O�0�/a0���S���6Q3��J�W�V�mq3�E���
�L��N�P�!�T�	��W�Y��	�\�$�q 3-�_�b�&�e��h��n�p��q��j�r�t�s��w�%�{���|���~������a3{���
�	����[A	��m�A��L
A��EPK
!<��O��5chrome/pdfjs/content/web/cmaps/UniJISPro-UCS2-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
UniJIS-UCS2-Ha��M�_�U�T�W�
�A!��bA %�Z�n�6�Y;�m�2A!��6a%�9�7�=�;�A�?Q%�G(q%#�j�o�m�l�n�q�p�r�[�]�\�`�S�U�T�Xa%=�wQ	%@�ua%I�~a	&�������_�O	�c�N�]a'�A0�Vz!O�0�/a0���S���6Q3��J�W�V�mq3�E���
�L��N�P�!�T�	��W�Y��	�\�$�q 3-�_�b�&�e��h��n�p��q��j�r�t�s��w�%�{���|���~������a3{���
�	����[A	��m�A��L
A��EPK
!<-M���5chrome/pdfjs/content/web/cmaps/UniJISPro-UTF8-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
UniJIS-UTF8-Ha°�Mb‐�U�T�W�
�B←�bB‥�Z�n�P6�Y�{�m�rB↕�6b─�9�7�=�;�A�?R┌�G(r┣�j�o�m�l�n�q�p�r�[�]�\�`�S�U�T�Xb┽�wR	╀�ub╉�~b	☜��������_�O	�c�N�]b✂�B〜�Vz�aO�_�0�/�Ybヵ��S�C��6R㌃��J�W�V�mr㌎�E���
�L��N�P�!�T�	��W�Y��	�\�$�r㌭�_�b�&�e��h��n�p��q��j�r�tr㍀�v�s��w�%�{���|���~������b㍻���
�	���H�[B	=�m�[�B,�L
B:�EPK
!<5�P2E�E�8chrome/pdfjs/content/web/cmaps/UniJISX0213-UTF32-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�#C\a
!�GS�k]>2&?V3C�>S 2!+S��tA
�~$�c���
S
��n$�c�U�P
�&%$)C$�)
�f�7�![S .��s"'��A��"�}
�";�9�6"��B��;
��|�,"�m����S/P���'
��j�>"�kOL
i��O��fO�>��_c�0
�m��("��[��<��SC�i��P.S��2��j���	�
���C�����k9S
P�X��"��"O(
��zC
^�W-*'0#rSl�6B''�$�c�bCx��1TI�6��7�ZS��94
M�-�|3��)F�CX�b	>M#=:B�@�;	���J�<O@�0�G�"�W�F.��q.�J	�N
M�y�u�>�:�E�<fG~�E"#�U�"�
;ls9'�
��V�h�i� N�.:�K� �e�,�d�[�5�J��|�O�r�]C. Z�Z�+�.�/�?
�x�vC	F
�Q��(e4
�?	�^(��D@�����
�9�zC5��c��,
�`��2�H�j&�_;/1� !�f �)�d
65A�t
�6$��.+�T��*�
U	�9�.��53�6�?C��$<(�#��C���J��#�"k
l	�)�@�]VI�b0#
8%#
#'`�p�|�k�`-�k]�F1�]
�*>�1=;+ =�N�g�f�U�J�
)�^-�IR
	
QL5��?�
X�5�5�@T�
K�9G�$��+��C�U��p�t�m�Z�$�;�M�
�v��3�\�7��V�=�/�j�U�_(�A�K�v�9�y�.�@�7��#�C.���eC.���:C���K
�_�!�W�:�r�S/�0��i�� �w��w�8�5�R�t
�{�v�\S/�S��h�>�
�c�r�/�qS�/#�&�?�
�C�Z��c��c�T�c{�i�7�&&�S�����o���(�_�4�u��w��$��n��B�S�^�n�}�H�a�$�/�7�R�4��	�w�>� .�Y�O�@�4�<�{�0��P��S�_�(��VKp��A��X��]�X�`�-�t�G�W��-�e�"��N�O�x���e?�K�j8�]�/���8�G�U�F��
��a��h�(�.��\�z(����$��X��[�o�p��%�t�:�+�T�}�R�a� �G���6]�C�3�!�Y�"�o5�n S/��N���<�7�J�a��r��C�8��w��z�C
/Ӟ~/�_�H
�+��<��D�w*�3�`C00�:
�a�aC0<�Bc^S23�Op}��$
ly�nC2Q�S2��g�

�D�5�,�=��2
���^C3�pC2��ZC2��{S3�j�r�[�Z�CN�0��+C23�"��
��
��*�?�
�#�F�?�#�,�y�`'�>�M�;�:�J	�=�K��A�&�w�P�y�{C3�x


	�5
&C4�$�&�b9�S�6�m�h�N�	T� ���
�`�(��2�n$�Q�@"�Q�cC4��lR#�.�g
O�8o��x�m�jH�9C�'4��Z%=,'0"�k�z�p�k�H
7(
�p�a�5�5F>5$�LK&&.	?��i�GT4�\�#�]�r>
�K4##P\	8
�9	�P" �Z�A��8>F�2�i5%"�$�09�g��
�B^�g�6"
�\�_�,HBP
&�7J$(�*V
8�C�n
!4
3<	5&Y�1��<&��C$�`�j0�Q_ 
\(�pD�G��$%�	�f@�z�y6�d	$�.~.B�dC�D��&�r�}v%Qo�
�3SN�*�9�!��#����}�D��R��1�U��CVN�L�^�i��O�2�w�l�i�@�m�3�2���M�(�7�X�/�u�ve�	�R�\��g�f��N�� �z�3�
�i�Z�j��9�{�:�	��~�W�d�Q���}�x�X�S�w�h��N�
������I�@�G�F�5�K��r�#�yC
N�Z�
�a��e�d$
"��
CN)��U�x�y	
�t�u�T�"�y�x�w�nS	N݅����g��N��/�F�7CN��9�z�P�A�,CN끆b�F�GCN聤]S	N��Z�4�d�l�m�g���p��SSO��a��	���4�<��k�Y��M�'��\CO�q���L�xCO�kCO��f�x�3SO3��d��)��(��w��z��k��G��.��Q���PCO@��f��u����N�;SOM��\�u�C�R��6��y����a�r�/�����j���b��p�ACOi�)�Q��0��\�i�COz�pCOc��k�	�5�:��>�?S
O��%����k�!��
��W��V���������r��oCO��.����3	�$�[��q���/�5�l�CCO��<C
O��s��M�L��<�L��z�S
Oȁ�~��%���*��������}��|���COז�F���{�X
�g�.��B�0���
CO�l�(CO܁��#	�"�?�$	���(�u�dSP	�T��`����2����x����p��e�a��>��wS
P�*��6�m��O� ��~�z����ySP!�B�z��i�d�o��f��
�I�T�;��(C,P6�^�N	��y��X��q��A�(�t�'�F���e�O����?�n�R	�E�$�q��)�~��G�>�@�/�.CP@�?
)#CC2P0���� �t�u�5�{�$�y�x�y�5�{�&�'�&�}	�|�7�6��-�0�5�}�0�1�(�;�:��)��
�o� �1�9�lS	P၇
�4��������L��k���+S
P�_��J�R�T��j���I��CP��b���E�h�	�qt�E�D�9U�CQ�
CP���+�����8	�)�(�C��(SQA�8����!�1�h�d�;�^�C�:��d�?�)��r��gCQR�r�9��B�?�A��)�h�.�7�Z�m�[�
�M�`��sCQd�JCQS��;�I�F�_��T�_�
��W�>�H�S�SQ��~�#��P��3������6�7�#�"C!Q��Q�3�+�D�w�v�s�g�f�o�t�1�j�D���Y�X�/��[�~��+�*��5�R�p�3�2CQ��K �a�`CQ�����	
��1�`��j���.�-�<�=�'�P�gSQ����p�Q���u�s��L��E��zCTQ��5�F�}�
��=�}�A�j���C��:���y�x�Q�P�)�?�I�.��-��B�;�Q�$�`�	�]�`�9��(��y�7�>�a�R�3�8��&�x�}�r�3�U�U�p�{�$�7�jF�P���Y�Z�o�Y�n�O�$�i�h�/�0CR�NG�a>�`	�X�Y�GC1R��o�#���N	�1	�$�g���Z�'�+�P�'�C��*�+�N�M�N�#�I��P	�O�N��$�!�z�g�
�Z�}SR�R����)�h����v��a�`�o��*��=�3��n�A�@CR��|�{�1�t�K�b�
CS�SCR쁥a�
�H�B�g�#�B��,�E�DS
S�M�!�x��V��u�B��0������K��s�SCS#�O�A�@CS$�UCS%��i�K�T�U�)SS8�S�{�m�o���U�Q���x���T��KS	SE�V��m��
����
CSQ�p�9r�]�J�/�h�.�<��� CSa�!CSY��q�#�J�%S
Sl�"�J��W�+�^�X��;�^C#Sw�`�S�N�{�|�I�H��U�
�p��H�J�y�x�A�n�P��{�c�8���UN{�<�&CS��WCSy��	�'��(�R�I�L�Y�U�*��*�U�T�-���~�W�V��(
�V�{�7SS�Y��J��'��L��]�Z�Eq�5��
�6��#�#�w�P�H�W��E��#��p�7��1�R�>CS��r����?�ST�y�/�0�i���/�^�E���`�`C&T��l�C�8���C�<��b�*�t	�;�:�W��}�]�V�t�
��/�B�'��� �vC	T��4�O
�0��
CT����"�^�_�#��]�\����G�TST���%�X��m�y�����Y����CT���H�7�%��p��7��CST����t������z��	��<�6��5CT������ST���L��}��B�8����=�8��2���$�0��p��{C%TǢ
�5�@�=�L�+���m�n����J���o��5�}�:���+�2�7�.�m�=�*
CT��^^�C'T��2�D�E�T�n	�o���o�n�o��E�\�a�J�K�^�j��
���O�N�s��p�Q�6�q�S
U{�4��H��S��Z��g��l�n��K�C)U��<��z�o�t�
��O�f	�O�`�M�c~�&��

�4pi
�M�P�1�W
�~�o�w�h
�YCU��_C,U��:�Z���r�s�r�u�	����s����}�|
������j�k��i�b�m��z�%� �SV/�U��8���X��<��C��X��Y��\��[��TCVB�_	�6� 
�k�v�S�V	�1�#�<��k�l�Y�\CVS�E�kC*V;��*��{�i�h�x��k�v�w�v	��i�f�g�d�

��u��~��g�d�%�*�r�}�%� �a�b�%�*�t�}S
VȢg��L�i��H�w���3��8�t��/CVӢjSVעk�_����=���J�e��9����
��CCV�
�R�1�� ��e��V�}�~�O��d�K�J��*�y�r
�`�W��6�.��y��_�6����;�4�c�b�%�1�V�y��v�K��6��_CWY�`FCFV��P�d��u�t�t�k�x�p���m�h��y�{�p�m�h��u�t���t��o�r�I�H�y�	�I�H�{��x��z��y�z���	����W�R�����c�-�~���S
W��e��x���O��l�����v���3C9X�
�y�@�D	�c�b�)�7�O�~�_�Z�Y��%�.�c�L�?�B�54�\�I�a�@��.�V	����g�v
�S�s�D��M�L�U�P
�#�� �O��>CX/�21�(<�>C2X��a�y�x���
���~�	�v�o�~�	��~	�}�f�	��]�p�|�
���|�}�|�a�t��z��e���
�`���z��#SX��h��y������z��_��z����vC7Xܣ1�)�t�|�:�)�_�P�G�|�3�2�k�j�-�1����:�
o�|�q�R�#�P�Y��6�]�\��P����c�^�c�d�x�O�T�[CY�iGCX݁�y����"�"�|�/�.�/�w�$�%�$�;�8����=�<��1S	Yg�M�y�z�Y��J��w��B��[��CsYs��&�t�3�2�u�4�T��|��8�i�z�O�a��E�"��3�.��s�A�|�$�u��� �X��]
�o� �B�_�r�/�2�O�J�g�%�t��#�!�D�� �=�<�;�g�*�
 ��K�L��
�=�V	����	�G�J�	�#�$
�A�B���w��a�_�H�9�t��!� �]�\NOCY��n�CY��q
�
��m�l#

	,4�?�>C;Yy��j�?�%�`�?�<�?�'�&�8�9�6�3�7�67�=�:	�;�:�����K�J��������
�aC)Yr��	*

!
-IS[����H�G����H��I��J��[������e�[�h��*��{S[���C�l��5��*���}�p�>��,�bC[��n��D�O�R�z�5��W�����CC[���w
�C[���=
S	[��S�:��"��g��G�t��:C�![ɤ���e��J�O��B�W�M��o�X�+�D�)�(�G�B�/�}�L���/�/�L��}����1�k��'�s��u�t�S�ZI�R�2�e�b�i�/�P�C�B�k�`�o�`��s�n�w�E�h�#�J���	�[�d�
��� �;�2�
��w��G�>�[�_��:��K�F�O�J	�� �_�h
�s�r	�!��w�r	�'�>�e�d�u�H8		�o�n���5�KC[�sE<�G�C�P:1bC\�*�?	�2N�O
�N/G�%�C-[΁�z�{�z5	�
��������5���

�%�&�-�0�5�2�5�6C[ׁ�z�$2�%
	"o#2$CC&[́�D
$ 	

"#


S
]����c��������o�X�Y�e��PC0]�m�	�W�^�y�s�j�e�+�x�7�.�T�q�4���m�h�[�=�
���o��O��"�� �q�b�G�B���a�`
�!�>�UC]�� �}�|)C]���
�P�}�|�U	�-
�z
�y�&�T�M	�v�RS^w����)��$�/�7�C�$����)C&^��?�2�2�31�
��-�g��n�g�C�g���`�o�^
�U��&�n�K�L�9�/�l	C^��0�r �d�_C^����N�}�z�U�'�&�'�x�U�'�zS^������t���5�j��2��W�����W��V��w�S_���-�,��U���M��L��+�`�&C
_�w���F�Y�i����s��t��}S	_!��d�F�^���$��h��%C_-�$
�3�^�5�z��3�B��~�w�z �1��;�X�O�t�d�)�i��iC_4��!!� C_.���-�=�<�B�f�+�*�	��C�@�A�(��!�	�H��S
_|�t��L�b��1�-�`�F�7�6C_��9��-�R�h�e�}��F�GC_��<�"�#�"�!C_���S	_��>���!���f�}��9�e��dC_���-�L�*�w�h�F�W�P�H�;�	�J�?�6��|c�?�:�m�v

C_��&C_��8����3��� ��5�,��5�4���9
�*��S`�V��������p�*��'��z��s�C�C5` �5�&�#�4���
�+�2�S�J
�7�8�g�0��>!�!�F�#�o�A�>�o�:��(�~	���I�2�k�^�!�pC`]�'C`��&���;�(
��������)�(	��E�D�-�,�)�)S	`���7��
��8�R��{�f��^��_�S	`��O�"��Y�"Z�����C`��c�&�?�>�	�z�C`��
C`���!��.�1��'�"S`إz��R��/����E��f��5�c�4����GC`�g�K�F�>�S�F�_�^�#�&
�k�]�D�%���"C`��C`偨@�7�)�\�=�<�?�>�;�z�>�;�%�$�8�A��D�C�B�?SaD���j�;��I�����}�/�e�l�C
aS�!�Q�L�i�q�R���Ca^��(�u�tCal��LSan��6����\��q
��v��nC5a~�Ge��S��P	�Q�i�a�
�7�A���z�5�.�y�z�i�n�=�P	���w��Y*��Ca��zC%a{��*�'�&�L�w	�2�B�O�)�v�E�3�v�E
�r���4�p�q�F��~�Y�V�W�V�S�R�a
�/S
b�p�,��8���9�#��Z��9��:�$C?b&��l�o�n�+�m�V��m�4�w�
�I�A�3�$�S��M��)�
�(�3�<�y����r�=��,�w�n�/�x�T�K�m�~�5�r�Q�{�*�|����s�d	C
b)�^�C�_� .�=>�<Cb'��f�e�`�_�^���a�-�,�n�u�\�i�q�p
�lSb̦i�	��"��)�M�F�-����H�1���5�p��v��9��AC;b��M
<�B�	�=�D�
�k�h�f�3�N���Y�L�U�q<�
���
�"�2��1	R�#�~	�
�1�w
�X�#���U�V�?�<L�,�?�w�<�G�8�s�d��yCb��e

�c�b+C*bꁨx�{�q�h�k�j��~��~�k�h
�e�b�|�}�e�`�}�|�c�b
�a�`���7�2	�a�^�e�dSc��r��]�#�k�h����������D�E�j��A��VC�9c����2�I�L�g�p�5�I�|�q6�{��2�+�W�
�9�>�q�|
=	4��c�X�q��>�8	�}�t����)��V	�C�:�q�7�E�l��M��F�P�/��c�n	������"��v�1�:�E�@MPAB��:����s�#�
��� ���?�@�/��V�#�4�(�w��\�|���E�&�m�`E�!���1�0�7�"�H�@��
�h�9�&�2��i� �^�5�.�u�x���O�P�O�8�A�]�H�n�{�e�Z���;�6�����P�e��,Cc���O<,� <0S�+�~�1Cc��s
�1	�N�`1
N�=�@-< ��Cc偃l�JXsC&c���"+*			
5		'	
$CcꁗJ'
9*1		C-c���%!	#
"4
Sf�k��6�o��w�Y��<�+�(��W�}��R��k�e��N��+�S��O�l�!��R��Cf4�j�*�/�>5�G��D�a�Z�_�BCf3��v	
�.�'�W�~�Cf7��L
S
f]�q��6���3�����V�=C-fi�i�R�g�,�C�c�T�#�p�c�Z�� �{�f�q�d�Q�R^�2���!� �%��2��1�@�Cfs�"%C fj��Q�	����S�L�}�5�4����(�)�?�f�7�z��[�Z�E�D�E�D��Sf���'�P��m�	�[�p���NC.g�K��$��=�<�Y�n�h�w�S�K��3�Zm5�/�B�}�N�.�I��6�9�J�1���'�"��-�&�#�"���P�$Cg�(WCg��
�����i
�7�6�f�a�`�S�O���Sgl��i��;��H��M�,�y�x������`��;C	g|�$�}�b�"�9�$
�gCg{��2�k
�j��YCg���j	S	g���� ��#��L�T��1��Z��o�>Cg��n�t�]��x��!Sg����0�8�9��]����[�:

Cg��(�P�N�K�F�%��m�F�#�H�P�6Cg��+C
g���{�h��~��~��)�&�|S
g�6��h��o����e�%���$�Cg��*�d�}��n�o�T}�1�v�_�g�F�Y�b�U�]�dCg��
�'Cg���J�%�z���~�i�h�~�}�5�0�~�}Sh;��~��-�,��~�x��{�	5�
��� ��C"hH�
�j�{x��X�>�9�j�5��I�^�?�& !�}��u��%�&�}�~	�[�,�E�ChR�,;�!ChI���v�w�r�3�@�C�4�5�4�w�A�&��u�h��9�6S
h��K��v��o��6����Q�����)C	h��Q�[�]�p�=��C�NCh�/Ch���Sh��0�v�6��5��4��O������|��gChߘi�
�I�Z�-�&�e�x�DCh��.Chꁪ��A�@��]�pSi�� ��s���d�]��
��h����2�q�t�d��t�=����C@i�@�c�
� 
�e
���}�S�

��L�\�'�$�=K��1�.�//�c�(�}
� �?!�Y�
�B�/�n��d��g�2	�V�	�+�6
�}�W�N�K�
�UD�\�3��B�
�J���S
�H�e�l�)
�Y�L�uCqi�p+& 7,

 8=$	


&
18

��		�o�j/0
	
	ad	Cih�2/.��M$
cC i5�5�s�v	2�[�Z
�
	�X�U'J
1C2iB��
�4�;�4
�?
�>	�E�D�I	�L�Q�P�U�Z	�g�h�i�j	

�
�
Ci��"	B5X
	=H�R
�O7Ci��72#

S	k_�b��n��o�m�V���p��Q��nCki�2�n�O��r��t�E�=�!�
�.��C	kl��c������
�Ckk��?S
k���I�[���o��H��G��DC5k��u����u�@�A�r�y�2�o�2�(��4�#����3�~��X��
�!�<��X��E�^�v�m��l�Q�K�(�'��Ck��=hC/k���j�
�1�q� � �#�`
�;�'�p�u�&�L�u�t�O�N�O�+�^�k�p�k�&�>��s�r�A�@�u�y�("��A�3�y�j�u�wS	l��
���^�)�M��4����<Cl��1�2Sl���3���-��u�� �;��m�@��2Cl���c� �j��P� �(��4�X
�'�H�H���&Cl��IC	l���\�w�E�:�y
�E�;�vS	lρ�`�o�n�}����&��7��6��'C6l٪!��%��B�i�l�O�6�Q��h�%��'��(��g�D�(�k�r�E�@�Q�l�5�x�2�e�\�)�j�
�{�p�l��|�`��\�2Cl��B)jC*l܁��-�j�?�+�f��F�#�^�k�h�i�h	�C�B�m�.�O�r�s�L�M�e�d�J�1�h�9�*�]�h�9�*�]��NS	m����.��{��p��g� ��L�1��_Cm��mX�
�;��g{�l
�A�J�]�^�=�6
��"
��~Cm��G"Cm���)�`�"���o�H�I�J�K�J�U�w�Q�,�j��mSm�K��N���K��C������X��	�T�#�%�,Cn�Z�'�r�T��]�U�,*�1�Cn�FCn��uSn�^��
����t��f��U�S�@�rC/n)�9�6��~�w��E�>�/�P��2�q�t�C�F
�?���f	�=�R�{�t�n�W�G�4�{�t
�a�T�y�z
�+Cn9�LbC"n2��&�)� �&��h�v	�{�[�r������
�%�����M�B�$���+�*�)�S	n�����C��8��e�X���	�C#nыH�8	�3%
�p
�)�41��0��E��.�e�8
��e��+���
�

�c�@�CCn�8)�"
�m�l���q
�p�f�[�X�Y�\C	o��	
SoW��A��o��L�U���S��j������!�SCHod�k	�8X�a��|�	�>�.�k�B	�~�%�T�)�s�M�	+�j�_�:�W�t����C�A�V��;
�>�g)��	�h�|�%�
��(�M
�
��O�h�Q�@�C	�x
�C��u�Z�	�|�K�G�F��}#�m
�_��D�A��"���	Cgof�DM




�A�B+2	
uv.
/		

#

Co��Q,?�_�^�	� \%�WH�X
 �{+�|<�}'�<C(o}�

�%�$#�?�! *
 �n�m)
Co~��3

%]&.?9"2	PC.oh��C'	*	" 
!6*,Co���3U(�0M�1�lA"#C(ol��	">E
!
	1

	
S
rW����+���<����d��Q��Z��}����'�J��JCErg��
�?�~�	�X�Y�~���[
�(�I�@�g
���_
�p�`���V
�1�\���n�N�8�E��~�Y�=
�v��$�?�j�*�	�
��Y�e�N �E����!#�E�_��Z�q�v,�Q� �i���H�u��.�8CWrt�k	

		7	4+	
)("
Cr��aeRE5�w$�xaC8r��;�%�$3,(�`�a	�O�N	
�H�I
�@�=
		�WCr���I�(;�
85C6rx��
&
	,	#	Crh��HD
*
�
</C/rk��A	/ 
	@ 	Su�J�]��V�*���-����]�^��d�����k��/�0C
u0�>�<��Z�
�~��E�HCu/�rCu6���E�!�dS
uF�S��^�g�������X��=��d��'��&CuT�^��JS
uY�y�4�q�!��*�H��
����OC	ud�V�E�#�f�W�Z��D��ESuo�u�-��h��k�~��d��m�-�(��D�.CGu�@�@��'�U��[�j�e�V�Q�P��t�k��|�?�g�"��������O�N	
�w�z�[�V�a�d
�+�s��c�zCBu��~
�2�3�B���
�5�4	�
�	�����	��K�D�)�(���I�(����!�+�H��C�D�E�P�]�\�S�B�o�U�B�s�Y�B�i�h�E�w�P	�h�+��Z�[C
u���	WS	v������O�7�o��`��!� �
CHv��w�
����;�`��^��P	�x��]��
�<��2�N�p�g�Y� �m�\�H>�I�{�Z�'�fp�a�&�{�*����'�T���=
�R�#�a�G�
��G�y�6�1�d�{�P�Y��6�;�=�#�L�0�D�A�� CVv��

�%�&	
	


$.	
		=:Cv��y�=��>�o�>�p,�5C v��7%
��
��;	
��:�!5)
5Cv���^
`Kb*>OC'v���

."#?	"	
	C)v���r
	
E$!	8C4v���!		=
" +


S	y:�T��H������y��@��C�c��$C1yG�Z3�^�X�J�{�r�[�`�t�?�W�p�O	�T�$*��$�Y��k��:�(��i�h�[�\�	�y�}��W��:�;�S�T�6�%CyE�;�v�	�N�O�J�K��T�K	�~��|�F�G��~�D�E��m	�n��CyD��Y
Sy݁�g�W��k��@��)��(��)�m����G�Cy��f�;
�-�D�$���Y��I��v�
���SCyꁙ&�S�P�E�%�$��8�C���>Cy�k
S
z7� ��\��>�����t�VCzB�6�T�?�@�]�	���b���s�t�Q��P�yCzE�K
C
zD��+�7�H�K��@�E
S
z}�*����1������d��)�?��2�ZCz��+��Z�>S	z���Z�>��5�g����C	z��x	�n�i�w�t�j�G�JCz��QCz���{
�A�&�#�"�#S
zËT�@��B��=�������^��O����
C
z��	�	�n�������#�-S	z��d���.�6��1�J��d��wCz�z��Q�0�7�y�7���
�	�X�N�#Cz�c�C�\�5
�V�WCz�	#S	{%� �j������h�9���U��vC{3�J�K�V�o�M�C{1�Z	�T�B�'C{0��S{K�R�����'��k�0��N��S��v��C{]�i!
�g�� C
{`�]�(�+�L�M�&�|�	�-C{j��	S{��)���2��	�.��$��)	�X�W��"��_C{��{�5�(�p�o�g�LC{��bC	{����3�0�+�*�+�.S	{Ěc��0�����b��y��F��%CQ{Ϯn
��Z�.�-�<	��
�z�q�{�r�u�v����#�&/.�y�x�K�Qp�(�!�-��0�5�#�E�B�'�'�*�s�6�B���;�fC{��c1


�C@�BC'{ԁ�
�h�i�f�i�z�l�m�l�m�l�q���n�u�t	�w	�t�u��n	�g�f�eC{ہ�&		)
S	|ү&��H�H�C��3��z�����,C|ܯ'�E�/�+��1�2�u�t��C��V�c�K�(��C|��v�H�I	�J
C|遭1S}��2�����'�K�������k�I�0�c��T���HC} �I�L���*�nS
}*��6�����z����=����3�_C}5�9��5��XS
}?�8��Z�Q����.�R
��X��1C }K�5��,��t�S�t�/��{�r���k��Y�T�9�V�h�1�H�x�
C}M��N���
�T�5�!�T	�W�X�>�{�z��Z�>�A�<��BC}Q��8
S}���6��O�S��P�s���b��E�2��T��M�{�����C!}��N��(�l�u�X�#�P�?��*�y�k�`��	�k�;��k�r�/�*��d�2
	C}���8�I	���;	�a��9�i�h�i�fC}G	S	~�1��2����Z��P��Y
Ci~&�N��{�	
5�3�N���w��<�S��t�8�Q�|��O�j�m�b�T�I
�)����
����Q�T�d���z�4�M�L�!��G�H�!� �b�_�?�6���?�6��K�-�x
���+1�Z�[�C~G��O�N�+
/-
C#~'��B�[�Z�,
�1)�0�1�0�1�0�+� �&�%�"�����
�
���C~-��L/�)	&	S	��>�<��j��i�6�w�r��b�S�
��c��O�O��.����g��d��O��N�������C��@
�Y�D���
�Q�P���3�2C
���n��|��*��|�}�|C���f	S	�o�F���6��;���>��9�IC+�y�Q�i�h�9�Q�$�O�4�;�@�x��z�Z��I�$�T�u�i�T�l�Y�z�G��'�N	����8C�{��j�

�
��~��~��~�/�.�
�/�.C����p7S�큙t���!�������5����F�����������}��C%��7��n�~�e���l��O�e�v�k�,�;�%��F��,�3
�h��
�.
�s�v���q�A�j��>��8�C+�	�d 		
C6���y	�����
�G�F�z��	���
�
�?�F�C�8
���t�u�t�w��N�L�O�@���u�O�N�v�u�p��(�p��,C���y
	S
���M�r�X��\���&�M��0��eC��	S
���c�-��x�p��Q�	��\��]�WC�!�=�0�v��e�y�x��S	�2�>��7��6��-��q�\�w��NC�@�$���?�@��^�<��7�@�{�z�9�M��a�#C�<���n�m�5�0�1�6�w�v	�^�W�c�\�V�Y�I� �Y
�C�C��
S����+�R��?�k��~��u������(��-��,S	���E��%�?��0��k��j��	��6�JC	�ŋ_��R��k�p�$�e�^C��G�JC�Ё�	�S�ޱE��L��S��h��i��P��+�W� ��tC��)�m�
	�(�y�J�U��T�=�B�;�Z�QC�ꁚ�S��M�L��C�큮%3S�+�T��,�u�t��3��H��U��>��E�S����'�h��BC�@�O�J�
�C�<��9�a�`�C�=��/S
�O�c�Z��p��K��,��U��A��C�w�YWC�b��m�p�
;4C�Z�z�h��/�Q�~�US	���O�N��?��P�l�m��m�C7���[�O�@�*�_�2�w�h�*�!�@�b�I��N�k�V�!�^� �0�f�W�7�>��!�3�<�#��+�6�Y�\%�M�\�*�=d�y�[C����5�K�d�������i
��+�*�K�M�f!�m�l�"�C����9	)0S	�_��;���E�P�)���g��?��$S	�i���������d��5C�u�n�$�A�4��	�C��W�,-�]�C
�s��=�M�z�3
�B��5�4��C�t��JS���`�Z��b��[�=��X�������	��C8�ė�r�/��m�v�;�>�,�)�^�(�Y�
�9��f��
�~�S�Z�;�~� �B�#�w�r�S��h�i�P�i��t�r�CJ�a�w�T�v�_	
C�Ӂ�_���z�
��G�J�K�G��u�n�
�H
�J
�K�TC�́�R%	S	���,��t�e����r��q��p�EC����PC���:C���a�(�b�c�b�ES
���-��z������w�y�*��r��F��?��LCO���>���9�:�
��C�&�q��'�"?@��r�m�
�W� �6
�	�;�D�{��d�/�L�O�N��)�H�b�
��=�R
	
�5�6�C�'���
�	�3�,�Y��t��*	�	�C���b%�G�H(��
�K�JaC-����|
�k�n�s�r�s�r�o�n�m�l�s�t�/�`�5�
�x'��(�%
�C"����m	
"
#	S
��X�*��V�s��m��l��m��|C���>,�Y�(	�_�)�d�
�,T>��&C-�
�r
 


				
 
��+�8CP���#�n�o�a�n�o�\��
�a�l�Z�i�h�]�
�c�b�h�k��b
�U�l��
�3�=�t�v�h�c�b�]�t�}�|���d�[�x�z�y�\�e�d�_�^�_�^�i�h�c�b�a�`�]�	�b�o�n�k�*�)�f�=�+S
�
���|��q��,�k���CC���=��	�U�V�g�)�z��)C��C���<�x�}�|	�m�n�o�j�k�j��m�j��k�j�S�U��C�w��c��h��9��&��!�� ��
�b��.�x��?����HC/�h�/��x�/j�b
S�Z�y�	�
�d�	�d��W�	�t�W� �a;�\�S�C�c�\�F�L�<��q�;�3�d�z��d�+��={�v�u�D�U�\CI�k�3

				
	�.�7�$�

�^�[
C�����
* 
<�[S�Z�G�FC,�i��O�j�i0�h�[�r�d�e$�h�a�`�i�y�x�`�a�`�e�
��`
�a�`�a�`�c��C$�q��G#

 S��|��H��=��T��
�C��N���A�W�!C��S
����X��e��d����A��b��]��\C�*�Z�/�g�`�k��@�YC�6��@	�AC�/��y������#�j�I�#S	�P�.�f�P��b��u�X���I�IC5�[�
�!��6�G��n�8�M���#�V�J�9�
�:�[�>�r�g�6�>�r�u��d�u��-��4�i�!�L�O��9��%�K�/��y�`�&C�]��d�j
�k�j�o�n	�K�J�M�|�N�M�L�s�r�OC�\��	S	�ڴ�3�� ��s��C��#���+��J���U��H�0�o�-�,�\�[�bC��&C�쁚c�h�i�S�:�7�L�Q�8�7�LS	��`����.����2�i��k�aCF�&�5���7�<�h�e�C�D�K�N�'�"�M���F�G�F�T�U�w��h��� �G�J�%�&�o�n�I�[�"�5�� �Q�P�U�X�1�n�.��SC+�-��	�3
�~�
�4�J�}�z�4
�5�~�K�J�O�N�O�7�|�}���K�C�z��A�@��B�I�H�G��9��HC�0��
�(S
���A�E���/�R�V�g�nC	���H�D�%�5��r�
��]��^S
���u���?�a����
��w�X����I��RC"�Ε*�J�b�kd�
�(�u�t�_�<�E�0�CX�.����H�)
��+�>�<
� )�y�R�]��y�Q�.�	4�MC+�ʹ}��	
�r�qP�b
�c 
C8�ҁ�v�7�Q
��5�4���9�6�
�L�
�
���		��	��'�&��F�G�`�&�'�b
�G��	��E�D�E�D�M	�Q�^�0�=	�<�9�}C�ρ�<

e
	
 !S
�G� ��D��A��V����YC�_�|��C�U�*
C�T��1�V��?�>��^�a���7�Z
�D�#�"�G�F��]�\�S	������G�U�V��B�+��E�*C�ʒ�q�|�0�J�!�1�a��4
�P�UC���>�`�a�j�k
C���H�\�i��D�]�\	�C�@��"���G�T�
�f�#�E
�j�O�&�S�|S�%��!�����(��O���:�H�W�~��uS	�3�S��J�+�/�X��]�����#S
�>�X�[��&�v��I����7���C9�I�Z�[�^�A�9�&�$���]�^�M�L�	�c��`��k�A�,�7�2��S�7����Z�8�}��|�!�"�A�\�1�K���\����G�&C�J��@�N�_�^�QK�R�?
�j�]�^
�u�N� �u�t�u�0�c�$C�O��|7
S	��d�J�#��H��]�k��CP��f�N�/�4
��� �;�Z�]��J�O�`��
�
�b�e�u�j��p�W�@�=�Y�o�V�z�	�T��m�"�S�D�/
�*�[�x�h�-�6�i�`�Y�,/e��Wi��E�#�&
�j�1�+0��B�0�M��8��4
��C��<�;�T�2�uC@��v

�Z�[�d�e�-�jH%�>�G�>�?
�>�=
�Z�[�d�aC�,�VC""
)			C�-��~i
'$NC8�/��0�?�:�=�<�=�<�=�:�=�8�9�8�9�6�5�(�/	�$�%� �!���
����C�?��"!&	"	
S
�ֶ8��G��.��m�	�K�t�^�C��|��%��-%�"�KC��:�
�	
���
��zC�쁒}�t��6�7��
��a���,�b
�i�/
�&S
�B������0���'��_��t��mC:�N�L�}�~���u�{�\�	���	�G��5�f��*�w�B
��w�z
��G	�N��u�z	�+��{�Q�s���=�:�/��_�b�S�:�K�NC�O�zR�O�N$�K�JC2�M��M�������	��������z�w���{����	������)�vS���o���B��?�r��� ��)�#��n����L�$��G�tC�/�h�x�I�e�9�eC
�.�[
�
��z�C�3��W�4�5�4�_��m��6�'�9�^�9�o�&S	�i��x�c�6��A����E��$��+��~C�u�d�H
�e	�.��T��
"��_6�"�n�U��h�#�c�:��F��W�hC5�|�e
�t�}	
�h�e�h�e
�r
�_	�V�W�L	�S��,�-C5�s��%�h�e�d�e�E�D
�+�
�$�#�H�I� �Y�A�	� �Y	�d�#�N�O� ��@�G�$�Y�I��L�M
���#
���L�M���L�L�C"�t��z

		
�yS������}����'�C�x�2���w�xC?����A�D�5�.�I�H6�� �7�B�U�K���)�,���
�<�D�q�4�1�c�L�A�B�u�p�;�h�%�f���>�)��}��R�$�k�TC&����Y���
�~���K�J�����4�`�"�S�R�O�N�)�(�)�V�P�U�T�1�0C����&>
S
���4���r��s�
��*���z�HC)���E�6�D�s�n�Z�U�+��B��A��o�(�g�|���-�8��O�]�&�I�I�y�r�K�H�K�$�A�|�+�1C���"�|�}�0�S�c�f�i
�t�uC����8#S	��)�&��Q��P��Q�	��$��iC=�
�R�C�5�v�5�4�!�B�=��"���!���4�5�~�E�D�6�]�$�q�r�!� �m�l�G�@�W
�~�Z�3�6���&C"���\�B�C�F�G
�}�l�q�o�n�o�,�?�o�*�E��>�1�p�>�1	�0�I
�H�I��	C���?,
"
 S	�Ƹ������J�N����P�<�
C�ӌ��=�k�d�:��	A�|�`C�ܸC�Ё�R�K�H�I�y�j�T�A�.�9�H
��9�y�D	�M�6�1S	����L�y�Y�?���e��~CE��B�&�K��k�%� � ��K���$�\�1@�02�C�3�G
�h�5�Q���6�q��4�E�T�C�P.�D>�H�Y�/�P�[�T�^��e�V���s)�f�-�^�-�
�.)�M�$�i�8�� �$N���D�C�w�j��pC�ܰ9�?�B
	�(Co�!��H�G�J�c
�b�K5"	� �	H��			
�L�#�/A�0	�'

�p�q�l
�k�l�mC�9�3�&�7�'�BC��8KZQ8
�"	RC�V��*�p�*�=
+$0C<���u!D
(B
V	
"
C�&���<	�*�/
�>�A	CM���YC
?
L

S	����P�]���H��;�C��*��O�C�ɏ%�$���}��bC����c�\�c


�H�3		
		C%����2�?�L�M�L�M�:�!��s��/�b�3�x�-�d�e�f	�]�(�~�)���$�A�.�)�8S
�!�4��^�M�����+�*�;�qC��-�;�h�s�z�U�(��>�	�d��.l�O�N�i�p	�E�`�Q�8�a�X�1�F�m�f�_�Z�S�k��6
��l�|� �f�U�z�T�_�S
�Z	)2�M�R
�`�e��|�G�]�(�2�/V���
��;�:�7�6���;�:���x�d�e�	�2��d�t�-�}�6�;�D�
��}�M�I�C���C�6�n
�
?# �:C�e��A�]7&�C?�1��g� �!� �!
���	�k�x�o
�n�m�h�e�d�a	�L	�M]�H�I�F�E!�@�E�>C&�=��C
{"

p	S
����\��`�a��u��x��c��<�aC� �O�
���/�`��6
�W�V�]�`����x�")(uC7��
���
��8�;�8�=�<�D�C
�A�B�<�=�G�n��}�|�q�L���F��-�K�	�[�7�f��(��g�N-�w&�dC���}
S
��Z�G��c��>��_,C����"��6S+�0�F-8	_N�?�$Cd5�&�oD
_",a�w�~��e�?nA5Z9:P�L�}/S�]�w�I3"P!�l�GjK�[�~�8�C��CC���	5qpq2Fw�S?	B!��*�MzC��Y�N�h�\��n��Y�5�#�Q�h;�>���^)N�"�s=2�ZC��LC��C��i4LCD�~�%�H�q�A���Q�R�{�e�
u��_�E�:^�C��l��t�q�0?��o�r�%�?�j��W�3�J�OG�b�E�3� �
G�N��Y�O�D��g�/�K�	��V�_�[
�*%�)�2�).��A�d��	��t�Z�
�>2�8
�t�C&�>�e�h�
$�,�I/�^@�
!�W#�	�`$�
(�}:�C&	�S�t+�;;��I�o�oC����Q�n�,Ew
P�>�L�x�72~
�B �5I�'$��'�I�!,S
	[�W�X
�(��h�ol�Q�;�z�)��f��V		(R��i�2W����B��9��E.�gO
�c�(�
_v�<.�TB�-0�,�|"]	e~2u9/�5s�R
�D("�Y#�l�$
�Z�f�
�(1�	�%�N�l�B7�=(�l{
�K�{�$�$�64��&�wB(2 �>
�L�#8-�a_�iJ�v4�q���I�w7�
���6��@��<�m��'\:8w
H��&�{�{"��?�m@93*G�w�)�<I�i�b�%$�<�.c�{�U�v+� H"8�JR�g�nz.�2�~�.
�p
�go�y0
�;�s
�p�Y�>�H��k"�K0!
�$
-'3�\-� `�	#RaF�E
�'�^ �>� (6�u �+"�h093@9
�d&B
Rv���{�2�*�9�/C,���Yb"�X�:�[X�M�iB�(
�*�Y�8�]�m�Yh���Q�S����,�6�A�I�|�t�g�g�f�8�I�*�,F	�}�0R>�xc ;>%e��!�$�*�;�A�G�X���0n��Fpc��s���F�#�*�K�P��n�a��*�U�y�	�*�$+R�J��
U��cN���� �9c�K!`	��!	��jH�v�a�r�n�2���~�0JK�7!�7�E
�&�5L�?���X�^����M�:���!�0�>�`�y�/��B	�&H�=�\�_��4�1�V�x��C�8�c�X6�e<�p(�w*�����%_�7J�E�2�v�7�o�*�_�{���'�7.�@�+�[Z�f�C�u^��+�!/�(�~�~|�u=�J���l�,��h�<�l^�~�I��-�B(�M^�Z�L�0R�BL�Q\�`S�h0�s�=�;��=�>�T�d�f)�og��?�j�p�\�s�d�f��|T��� �>��\j�{
�u �$�	��.�!�%j�9�<��P�{�]�G�$v�>�A5�KV�\�g�u-�	��
�/��
��[�/Q�>�K�tx���g�-�1��U�~��r�Y�W�K��K�F�IP�Y��y�|�G��k�J�M�r�_#�hF�r�t�d����C�_��pT�~�m�3"�D�B�=��R�3�i(�m�@���1��E�J��bP�x"�~�:��+�*�0�7D�;�>>�F�H�`�i	�k�m>�{�~��G��0��
�T�;�so�{A�%��	�V�%�(��*����$�{�e�O�Tc 2�@�"	�(�I����$�c!V�9c
$��c�I	��+	�2� 	�]�h�v�.�-c!��,�*��p�x�C�B�.�a�L�K�b�q�|����� �#�(�*�3��]c�[�%c.��	c`��sh�|��k.���v���q�e��x�9�a�	��
�l�p�j�r	�~B�m)�v��a�u�C�X)�\�^�;�q��w������=a�G9�K��U�@�^�l�t��	��#U�(Q�,\�2�t�S�-�j�z�|��
�S�#�>�0�
�:U�?x�HK�O�K�m�:��4�t�"��8�-�@�H�Ky�Z�_S�m�
����
��9��L�(�1�2y�<�^�T��r=�x���E��� �g�0�f�Q�f�o��|��
1�&��(�)�p�O�v�m��}�h�1��<�^�E��Lr�S��Y|�^��+�Tc G��g�8�}�(	�_�Q�B�iZ�:�6�I�D�O��E��F�u�P	�l�wc�5]��=���x�@��E�R��l���~&��P��_�D��k\��sE��z@��+���n���J��T���9�c��"��M!��R��
,��'���k��-��0b��:��<n��I�(��_�F�����=e���|��R��V�"��p�'��q��H$������K���Y1��*���R	��y��m���
��8�����5B��?���'��T��Zk��H��k��o�>��X���F��'��)��,���.�l��K���<�W��u���D���(�� ��R2���M��M>��V@��^��d:��p�
���$��g=��P�����2,���#��D�`��i�"��@��BX�����$�6��X���G�[��S��V��e|��i�g��t��4�����)��)^��2���?�;��N��Ps��]T��g;��nu��*��L����F��.1��4L��B�3��Fq��L����[��.��*���^��*�!��8��h"��"��p{��yQ��d��	=��P���#���'��b��5�t��@C��k`��H�l�����i��x.��W��3��
$���������"N��'\��L	��N� ��`.��l��?��t��w�(����BW���
��E�%��R���dc
���]��L!��T��Z4
��_��jB
��l��w����+��kc�=Nt��W�U��hn��x��{p��	��0����
��+��%>��.	��1��3�g��N��Un��j��mZ��uI��}���
���L��!{��)j��3��6���B��E!��J�a��m��qo��|!���
��"�J��8��;��?&��F���Q{��\l��d��h�U��x�Y��	G��:�������+q��;��=�0��O��Q;��W��Y��_���n&��r��u�)��$��(1��/�6��?��A��D���V%��[�x��n�1��j��
������((��+��.��1/��6���D��F��J	��La��R���>���P��3G��8q��CX��H���R���jH��q�H���<��Q����p��"@��)��,�k��@�R��s�	��y��8����>��$���@��D�N��U���g�O��
�
����n��&��(-��1�Z��N��P9��U[��e\��pM��yI����R��
���E��$�A��A���NH��T���hg��q%��wI����W���
��-��0��3�|��V1��Z$��^#��a?��hJ��q��t0��}	���I�����#���9��->��3��6��;��>��A��C��F��I%��O��R3��[6��e�"��~2��
���W��$���,��/��2���DK��M1��V�q��z��}���s���� 3��&��)��.�D��L��Q=��W��Z;��`:��g{��l\��vC����L��
s	�5�[�a�]�k�e�c�g�_�_c��"	����0>�G@�Ic�_�c��/����Ac+O����%��z�P��U�p��PK
!<UybK��8chrome/pdfjs/content/web/cmaps/UniJISX0213-UTF32-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�UniJISX0213-UTF32-HC!��bC �T�?�dC!�wS#��t&+"'�+�B�9S%�G(C%%�o/S	%@�uC &���z�wz!O�0�/
]��C'�C0��KS3��J�W�V�mC3�


��K?�C3�1$1
)')��q
C3�L


	&��vc%�9�7�=�;�A�?�j�]�`�U�X�w	�~�6�O	�c�]�H^����[c0�k�g�6�E�P�T�\�b�h�t�w�|�~����mc#��^�d�I�X	�M�XPK
!<��j�����<chrome/pdfjs/content/web/cmaps/UniJISX02132004-UTF32-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�#C\a
!�GS�k]>2&?V3C�>S 2!+S��tA
�~$�c���
S
��n$�c�U�P
�&%$)C$�)
�f�7�![S .��s"'��A��"�}
�";�9�6"��B��;
��|�,"�m����S/P���'
��j�>"�kOL
i��O��fO�>��_c�0
�m��("��[��<��SC�i��P.S��2��j���	�
���C�����k9S
P�X��"��"O(
��zC
^�W-*'0#rSl�6B''�$�c�bCx��1TI�6��7�ZS��94
M�-�|3��)F�CX�b	>M#=:B�@�;	���J�<O@�0�G�"�W�F.��q.�J	�N
M�y�u�>�:�E�<fG~�E"#�U�"�
;ls9'�
��V�h�i� N�.:�K� �e�,�d�[�5�J��|�O�r�]C. Z�Z�+�.�/�?
�x�vC	F
�Q��(e4
�?	�^(��D@�����
�9�zC5��c��,
�`��2�H�j&�_;/1� !�f �)�d
65A�t
�6$��.+�T��*�
U	�9�.��53�6�?C��$<(�#��C���J��#�"k
l	�)�@�]VI�b0#
8%#
#'`�p�|�k�`-�k]�F1�]
�*>�1=;+ =�N�g�f�U�J�
)�^-�IR
	
QL5��?�
X�5�5�@T�
K�9G�$��+��C�U��p�t�m�Z�$�;�M�
�v��3�\�7��V�=�/�j�U�_(�A�K�v�9�y�.�@�7��#�C.���eC.���:C���K
�_�!�W�:�r�S/�0��i�� �w��w�8�5�R�t
�{�v�\S/�S��h�>�
�c�r�/�qS�/#�&�?�
�C�Z��c��c�T�c{�i�7�&&�S�����o���(�_�4�u��w��$��n��B�S�^�n�}�H�a�$�/�7�R�4��	�t�-� .�Y�O�@�4�<�{�0��P��S�_�(��VKp��A��X��]�X�`�-�t�G�W��-�e�"��N�O�x���e?�K�j8�]�/���8�G�U�F��
��a��h�(�.��\�z(����$��X��[�o�p��%�t�:�+�T�}�R�a� �G���6]�C�3�!�Y�"�o5�n S/��N���<�7�J�a��r��C�8��w��z�C
/Ӟ~/�_�H
�+��<��D�w*�3�`C00�:
�a�aC0<�Bc^S23�Op}��$
ly�nC2Q�S2��g�

�D�5�,�=��2
���^C3�pC2��ZC2��{S3�j�r�[�Z�CN�0��+C23�"��
��
��*�?�
�#�F�?�#�,�y�`'�>�M�;�:�J	�=�K��A�&�w�P�y�{C3�x


	�5
&C4�$�&�b9�S�6�m�h�N�	T� ���
�`�(��2�n$�Q�@"�Q�cC4��lR#�.�g
O�8o��x�m�jH�9C�'4��Z%=,'0"�k�z�p�k�H
7(
�p�a�5�5F>5$�LK&&.	?��i�GT4�\�#�]�r>
�K4##P\	8
�9	�P" �Z�A��8>F�2�i5%"�$�09�g��
�B^�g�6"
�\�_�,HBP
&�7J$(�*V
8�C�n
!4
3<	5&Y�1��<&��C$�`�j0�Q_ 
\(�pD�G��$%�	�f@�z�y6�d	$�.~.B�dC�D��&�r�}v%Qo�
�3SN�*�9�!��#����}�D��R��1�U��CVN�L�^�i��O�2�w�l�i�@�m�3�2���M�(�7�X�/�u�ve�	�R�\��g�f��N�� �z�3�
�i�Z�j��9�{�:�	��~�W�d�Q���}�x�X�S�w�h��N�
������I�@�G�F�5�K��r�#�yC
N�Z�
�a��e�d$
"��
CN)��U�x�y	
�t�u�T�"�y�x�w�nS	N݅����g��N��/�F�7CN��9�z�P�A�,CN끆b�F�GCN聤]S	N��Z�4�d�l�m�g���p��SSO��a��	���4�<��k�Y��M�'��\CO�q���L�xCO�kCO��f�x�3SO3��d��)��(��w��z��k��G��.��Q���PCO@��f��u����N�;SOM��\�u�C�R��6��y����a�r�/�����j���b��p�ACOi�)�Q��0��\�i�COz�pCOc��k�	�5�:��>�?S
O��%����k�!��
��W��V���������r��oCO��.����3	�$�[��q���/�5�l�CCO��<C
O��s��M�L��<�L��z�S
Oȁ�~��%���*��������}��|���COז�F���{�X
�g�.��B�0���
CO�l�(CO܁��#	�"�?�$	���(�u�dSP	�T��`����2����x����p��e�a��>��wS
P�*��6�m��O� ��~�z����ySP!�B�z��i�d�v���
�I�T�;��(C+P6�^�N	��y��X��q��A�(�t�'�F���e�O����?�n�R	�E�$�q�
�)��G�>�@�/�.CP@�?
)#0�1�2C2P0���� �t�u�5�{�$�y�x�y�5�{�&�'�&�}	�|�7�6��-�0�5�}�0�1�(�;�:��)��
�o� �1�9�lS	P၇
�4��������L��k���+S
P�_��J�R�T��j���I��CP��b���E�h�	�qt�E�D�9�:CQ2�vCP���+����-�
�8�E�F�)�(�C��(SQA�8����!�1�h�d�;�^�C�:��d�?��P�x��gCQR�r�9��B�?�A��)�h�.�7�Z�m�[�
�M�`��sCQd�JCQS��;�I�F�_��T�_�
��W�>�H�S�SQ��~�#��P��3������6�7�#�"C Q��Q�3�+�D�{�v�s�g�f�o�t�1�j�D���Y�X�/��[�~��+�*��5�R�p�3�2CQ��K���a�`CQ�����	
��1�`��j���.�-�<�=�'�P�gSQ����p�Q���u�s��L��E��zCTQ��5�F�}�
��=�}�A�j���C��:���y�x�Q�P�)�?�I�.��-��B�;�Q�$�`�	�]�`�9��(��y�7�>�a�R�3�8��&�x�}�r�3�U�U�p�{�$�7�jF�P���Y�Z�o�Y�n�O�$�i�h�/�0CR�NG�a>�`	�X�Y�GC1R��o�#���N	�1	�$�g���Z�'�+�P�'�C��*�+�N�M�N�#�I��P	�O�N��$�!�z�g�
�Z�}SR�R����)�h����v��a�`�o��*��=�3��n�A�@CR��|�{�1�t�K�b�
CS�SCR쁥a�
�H�B�g�#�B��,�E�DS
S�M�!�x��V��u�B��0������K��s�SCS#�O�A�@CS$�UCS%��i�K�T�U�)SS8�S�{�m�o���U�Q���x���T��KS	SE�V��m��
����
CSQ�p�9r�]�J�/�h�.�<��� CSa�!CSY��q�#�J�%S
Sl�"�J��W�+�^�X��;�^CSw�`�S�N�I�H��U�
�p��K�J�y�x�A�n�P��]�8���UN{�<CS�m�R(�]CSy��	�'��(�R�I�L�Y�U�*��n�E�U�T�-���~�W�V��(�P��{�7SS�Y��J�A�f��]�Z�Eq�5��
�6��#�#�w�P�H�W��E��#��p�7��1�R�>CS��r����?�ST�y�/�0�i���/�^�E���`�`C&T��l�C�8���C�<��b�*�t	�;�:�W��}�]�V�t�
��/�B�'��� �vC	T��4�O
�0��
CT����"�^�_�#��]�\����G�TST���%�X��m�y�����Y����CT���H�7�%��p��7��CST����t������z����@�
�6��5CT������ST���L��}��B�8����=�8��2���$�0��p��{C$TǢ
�5�@�=�L���m�n����J���o��5�}�:���+�2�7�.�m�=�*
CT��^�C'T��2�D�E�T�n	�o���o�n�o��E�\�a�J�K�^�j��
���O�N�s��p�Q�6�q�S
U{�4��H��S��Z��g��l�n��K�C(U��<��z�o�t�
��O�f	�O�`�M�c~�&�%
�4pi
�M�P�1�W
�~�o�w�h
�YCU��_)�_C,U��:�Z���r�s�r�u�	����s����}�|
������j�k��i�b�m��z�%� �SV/�U��8���P��D��C��X��Y��\��[��TCVN�L
�k�v�S�V	�1�#�<��k�l�Y�\CVB�Z	��bE�kC+V;��*��{�i�h�x��k�v�w�v	��i�f�g�d�

��u��~��g�d�%�*�r�}�%� �a�b�%�*�t�5�6S
VȢg��L�i��H�w���3��8�t��/CVӢjSVעk�_����=���J�e��9����
��CCV�
�R�1�� ��e��V�}�~�O��d�K�J��*�y�r
�`�W��6�.��y��_�6����;�4�c�b�%�1�V�y��v�K��6��_CWY�`FCFV��P�d��u�t�t�k�x�p���m�h��y�{�p�m�h��u�t���t��o�r�I�H�y�	�I�H�{��x��z��y�z���	����W�R�����c�-�~���S
W��e��x���O��l�����v���3C8X�
�y�@�D	�c�b�)�7�O�~�_�}��%�.�c�L�?�B�54�\�I�a�@��.�V	����g�v
�S�s�D��M�L�U�P
�#�� �O��>CX/�2�,+<�>C2X��a�y�x���
���~�	�v�o�~�	��~	�}�f�	��]�p�|�
���|�}�|�a�t��z��e���
�`���z��#SX��h��y������z��_��z����vC7Xܣ1�)�t�|�:�)�_�P�G�|�3�2�k�j�-�1����:�
o�|�q�R�#�P�Y��6�]�\��P����c�^�c�d�x�O�T�[CY�iGCX݁�y����� �|��"�|�/�.�/�w�$�%�$�;�8����=�<��1S	Yg�M�y�z�Y��J��w��B��[��CrYs��&�t�3�2�u�4�T��|��8�i�z�O�a��E�"��3�.��s�A�|�$�u��� �X��]
�o� �B�_�r�/�2�O�J	��t��#�!�D�� �=�<�;�g�*�
 ��K�L��
�=�V	����	�G�J�	�#�$
�A�B���w��a�_�H�9�t��!� �]�\NOCY��nn��,�CY��q
�
��m�l#

	,4�?�>C;Yy��j�?�%�`�?�<�?�'�&�8�9�6�3�7�67�=�:	�;�:�����K�J��������
�aC)Yr��	*

!
-IS[����H�G����H��I��J��[������e�[�h��*��{S[���C�l��5��*���}�p�>��,�bC[��n��D�O�R�z�5��W�����CC[���w
�C[���=
S	[��S�:��"��g��G�t��:C�[ɤ���e��J�O��B�W�M��o�X�+�D�)�(�G�B�/�}�L���/�/�L��}����1�k��'�s��u�t�S�ZI�R�2�e�b�i�/�P�C�B�k�`�o�`��w�E�9�J���	�[�d�
��� �;�2�
��w��G�>�[�_��:��K�F�O�J	�� �_�h
�s�r	�!��w�r	�'�>�e�d�u�H8		�o�n���5�KC[�sE2�	�>�{C�P:1bC\�*�?	�2N�O
�N/G�%�C-[΁�z�{�z5	�
��������5���

�%�&�-�0�5�2�5�6C[ׁ�z�$2�%
	"o#2$CC&[́�D
$ 	

"#


S
]����c��������o�X�Y�e��PC/]�m�	�W�^�y�s�j��x�7�.�T�q�4���m�h�[�=�
���o��O��"�� �q�b�G�B���a�`
�!�>�UC]��C]�� �T�S�|�$�'�"�|�U�M�L�-
�z
�y�)�N�T�M	�v�RS^w����)��$�/�7�C�$����)C$^��?�2�2�31�
T�g��n�g�C�g���`�o�^
�U��&�n�K�L�g�l	C^��pH
C^����N
���}�-�*�z�U�'�&�'�K�B�U�Q�(�zS^������t����0��{��2��W�����W��V��w�S_���-�,��U���M��L��+�`�&C
_�w���F�Y�i����s��t��}S	_!��d�F�^���$��h��%C_-�$
�3�^�5�z��3�B��~�w�z �1��;�X�O�t�d�)�i��iC_4��!!� C_.���-�=�<�B�f�+�*�	��C�@�A�(��!�	�H��S
_|�t��L�b��1�-�`�F�7�6C_��9��-�R�h�e�}��D�GC_��<�"�Y�4�"�!C_���S	_��>���!���f�}��9�e��dC_���-�L�*�
�F�W�P�H�;�	�J�?�6��|c�?�:�m�v

C_���5 �4C_��8����3��� ��5�,��5�4���9
�*��S`�V��������p�*��'��z��s�C�C4` �5�&�#�4���
�+�2�S�J
�7�8�g�0��>!&�#�o�A�>�o�:��(�~	���I�2�k�^�!�pC`]�'C`��&���;�(
��������)�B�	��E�D�-�,�)�(S	`���7��
��8�R��{�f��^��_�S	`��O�"��Y�"Z�����C`��c�&�?�>�	�z�C`��
C`���!��.�1��'�"S`إz��R��/����E��f��5�c�4����GC`�g�K�F�>�S�F�_�^�#�&
�m�]�D�%���"C`���%�$C`偨@�7�)�\�=�<�?�>�;�z�>�;�%�$�8�A��D�C�B�?SaD���j�;��I�����}�/�e�l�C
aS�!�Q�L�i�q�R���Ca^��(�u�tCal��LSan��6����\��q
��v��nC5a~�Ge��S��P	�Q�i�a�
�7�A���z�5�.�y�z�i�n�=�P	���w��Y*��Ca��zC%a{��*�'�&�L�w	�2�B�O�)�v�E�3�v�E
�r���4�p�q�F��~�Y�V�W�V�S�R�a
�/S
b�p�,��8���9�#��Z��9��:�$C?b&��l�o�n�+�m�V��m��G�
�I�A�3�$�S��M��)�
�(�3�<�y����r�=��,�w�n�/�x�T�K�m�~�5�r�Q�{�*�|����s�d	C
b)�^�C�_� .�=>�<Cb'��f�e�`�_�^���a�-�,�n�u�\�i�q�p
�lSb̦i�	��"��)�M�F�-����H�1���5�p��v��9��AC7b��M
<�B�	�=�D�
�k�h�f�3�N���Y�L�U�q<�
����v�1	R�#�~	�
�'
�X�#����?�<L�,�?�w�<�G�8�s�d��yCc=�bG�C7bꁨx�{�/�<�h�)�(�)�<�j��+�c
�
��~�k�h�-�(�c�b�|�}�e�`�}�|�c�b	�/�.�a�`��-�,��7�{�,�/�,�-�J�^�e�dSc��r��]�#�k�h����������D�E�j��A��VC�3c����2�I�L
�5�I�|�q6�{��2�+�W�
�9�>�q�|
=	4��c�X�q��>�8	�}�t���
�E�V	�C�:�q�7�E�l��M���/��c�n	������v�1�:�E�@MPAB��:����s�#�
��� ���?�@�/��V�#�4�(�w��\�|���E�&�m�`E�!���1�0�7�"�H�H�
�h�9�&�2��i� �^�5�.�u�x���O�P�O�8�A�]�H�n�{�e�Z���;�6�����P�e��,Ccþ'1�V�O<,� �g"�f�c�b�#?�"+�~�1Cc��s
�1	�N�`1
N�=�@-< ��C,c���"�_�b+*�q�p		
$�w�x	
��
	��	
$CcꁗJ'
9*1		Cdo��(�70C-c���%!	#
"4
Sf�k��6�o��w�Y��<�+�(��W�}��R��k�e��N��+�S��O�l�!��R��Cf4�j�*�/�>5�G��D�a�Z�_�BCf3��v	
�.�'�W�~�Cf7��L
S
f]�q��6���3�����V�yC-fi�i�R�g�,�C�c�T�#�p�c�Z�� �{�f�q�d�Q�R^�2���!� �%��2��1�@�Cfs�"%C fj��Q�	����S�L�}�5�4����(�T�=�f�7�z��[�Z�E�D�E�D��Sf���'�P��m�	�[�p���NC,g�K��$��=�<�Y�n�h�w�S�K��3�Zm5�/�B�}�N�.�I��6�9�J�1����-�&�#�"���P�$Cg�(D�3�4Cg��
�����i
�7�6�f�a�`�S�O���
�'Sgl��i��;��H��M�,�y�x������`��;C	g|�$�}�b�"�9�$
�gCg{��2�k
�j��YCg���j	S	g���� ��#��L�T��1��Z��o�>Cg��n�t�]��x��!Sg����0�8�9��]����[�:

Cg��(�P�N�K�F�%��m�F�#�H�P�6Cg��+C
g���{�h��~��~��)�&�|S
g�6��h��o����e�%���$�Cg��*�d�}��n�o�T}�1�v�_�g�F�Y�b�U�]�dCg��
�'Cg���J�%�z���~�i�h�~�}�5�0�~�}Sh;��~��-�,��~�x��{�	5�
��� ��C!hH�
�j�{x��X�>�9�j�5��I�^�?�& !�}��u��%�&	�[�,�E�ChR�,;�!ChI���v�w�r�3�@�C�4�5�4�w�A�&��u�V�O�h��9�6S
h��K��v��o��6����Q�����)C	h��Q�[�]�p�=��C�NCh�/Ch���Sh��0�v�6��5��4��O������|��gChߘi�
�I�Z�-�&�e�x�DCh��.Chꁪ��A�@��]�pSi�� ��s���d�]��
��h����2�q�t�d��t�=����C7i�@�c�
� 
�e
���}�S�
��*�'�$�=K�)�/�c�,
� �?!�Y���n��d��g�2	�V�	�+�:�W�N�K�
�UD�\�#�B�
�J���S
�H�e�l�)
�Y�XCqi�p+& 7,

 8=$	


&
18

��		�o�j/0
	
	ad	Cib�X�2!�Y	�z�^.���h(�#$�"�y�z$
\��C"i5�5�I�H�s�v	2�[�Z
�
	�X�U'J
1�{�zC2iB��
�4�;�4
�?
�>	�E�D�I	�L�Q�P�U�Z	�g�h�i�j	

�
�
Ci��"	B5X
	=H�R
�O7Ci��72#

S	k_�b��n��o�m�V���p��Q��nCki�2�n�O��r��t�E�=�!�
�.��C	kl��c������
�Ckk��?S
k���I�[���o��H��G��DC4k��u����u�@�A�r�y�2�o�2�(��4�#����3�~��X��
�!�<��X��E�^�v�m��l�Q�^�'��Ck��=h�GC/k���j�
�1�q� � �#�`
�;�'�p�u�&�L�u�t�O�N�O�+�^�k�p�k�&�>��s�r�A�@�u�y�("��A�3�y�j�u�wS	l��
���^�)�M��4����<Cl��1�2Sl���3���-��u�� �;��m�@��2Cl���c� �j��P� �(��4�X
�'�H�H���&Cl��IC	l���\�w�E�:�y
�E�;�vS	lρ�`�o�n�}����&��7��6��'C6l٪!��%��B�i�l�O�6�Q��h�%��'��(��g�D�(�k�r�E�@�Q�l�5�x�2�e�\�)�j�
�{�p�l��|�`��\�2Cl��B)jC*l܁��-�j�?�+�f��F�#�^�k�h�i�h	�C�B�m�.�O�r�s�L�M�e�d�J�1�h�9�*�]�h�9�*�]��NS	m����.��{��p��g� ��L�1��_Cm��mX�X�g�;��g{�l
�A�J�
�i�^�=�6
��"
��+Cm���)�`��]�H�I�J�K�J�U�w�Q�,�j	�KCm���n"Sm�K��N���K��C������X��	�T�#�%�,Cn�Z�'�r�T��]�U�,*�1�Cn�FCn��uSn�^��
����t��f��U�S�@�rC3n)�9�6��~�w��X�e�h�{�E�>�/�P��*�y�q�t�C�F
�?���f	�=�R�{�t�n�W�G�4�F�M
�a�T�B�A�R�E
�+Cn2��&�)� �Y�h�v	�{�[�^���r	�w�M�B��r�}�Cn;��w
$S	n�����C��8��e�X���	�C#nыH�8	�3%
�p
�)�41��0��E��.�e�8
��e��)���
�

�c�@�CCn�8)�"
�m�l���q
�p�f�[�X�Y�\C	o��	
SoW��A��o��L�U���S��j������!�SC=od�k	�8X�a��|�	�>�.�k�B	�~�7�	>�s�:�W�|��C�A�V��;
�>�g)��	�h�|�%�
��A
��O�h�Q�	�x
�C��u�Z�	�|�K�G�F��}#�m
�_��{��"�	�%Cgof�DM




�A�B+2	
uv.
/		

#

Co��Q,?�_�^�	�
�U5)�0/�#��%�WH�X
 �{�J�9
�h �o�p'�<M�C'o}�

�%�$#�?�! * �n�m)
Co~��3

%]&.?9"2	PC.oh��C'	*	" 
!6*,Co���3U(�0-I�i�lA"��C)ol��	">E
!
	1

	
S
rW����+�d��P����d��Q��Z��}����'�J��JCErg��
�?�~�	�X�Y�~���[
�(�I�@�g
���_
�p�`���V
�1�\����n�N�8�E��~�Y�=
�v��$�?�j�*�	�
��Y�e�N �E����!#�E�_��Z�q�v,�Q� �i���H!�	�.�8CWrt�k	

	
	7	4+	
)("
Cr��aeRE5�w$�xaC9r��;�%�$3,��(�`�a	�O�N	
�H�I
�@�=
		�WCr���I�(;�
85C6rx��
&
	,	#	Crh��HD�T�U
*
�
</�>#�C/rk��A	/ 
	@ 	Su�J�]��V�*���-����]��>������k��/�0C
u0�>�<��Z�
�~��E�HCu/�rCu6���E�!�dS
uF�S��^�g�������X��=��d��'��&CuT�^��JS
uY�y�4�q�!��*�H��
����OC	ud�V�E�#�f�W�Z��D��ESuo�u�-��h��k�~��d��m�-�(��D�.CFu�@�@��'�U��[�j�e�V�Q�L��t�k��|�?�g�"��������O�N	
�w�z�[�V�a�d
�+�s��c�zCu��~#/�c�b	��

�w�vC8u���	�c�b�S�R�c��
�\�{�Y�	�����	���������!���@�=��B�7�4�E�o�n�s�8�3�2�I�i�h
�i�.�G�+	�BS	v������O�7�o��`��!� �
CGv��w�
����;�`��^��P	�x��]��
�<��2�N�p�g�Y� �m�\�HE�C�Z�'�fp�a�&�{�*����'�T���=
�R�#�a�G�
��G�y�6�1�d�{�P�Y��6�;�=�#�L�0�D�A�� CVv��

�%�&	
	


$.	
		=:C
v��y�=��>�oB�/{�,�5C v��7%
��
��;	
��:�!5)
5Cv���^
`Kb*>OC'v���

."#?	"	
	C)v���r
	
E$!	8C4v���!		=
" +


S	y:�T��H������y��@��C�j��VC1yG�k�U�^�X�J�{�r�[�`�t�?�W�p�O	�T�$*��$�Y��k��:�(��i�h��	�y�}��W��:�;�S�T�6�%CyE�;�v�	�N�O�J�K��T�K	�~��|�F�G��~�D�E��m	�n��CyD��Y
Sy݁�g�W��k��@��)��(��)�����G�Cy��f�;
�-�D�$���Y��X�'�v�
���SCyꁙ&�S�P�E�%�$��8�C���>Cy�k
S
z7� ��\��>�����t�VCzB�6�T�?�@�]�	���b���s�t�Q��P�yCzE�K
C
zD��+�7�H�K��@�E
S
z}�*����'������d��)�?��2�ZCz��+��Z�>S	z���Z�>��5�g����C	z��x	�n�i�w�t�j�G�JCz��QCz���{
�A�&�#�"�#S
zËT�@��B��=���|�����^��O����
C
z��	�	�n�������#�-S	z��d���.�6��1�J��d��wCz�z��Q�0�7�y�7��	�X�N�#C{�WC	z��;�:��\	�j	�I�|S	{%� �j������h�9���U��vC{3�J�K�V�o�M�C{1�Z	�T�B�'C{0��S{K�R�����'��k�0��N��S��v��C{���C{]�i!&C{`�]�(�+�T�U�L�M�&�j�o�	�8�+�-S{��)���2��	�.��$��)	�X�W��"��_C{��{�T�a�p�^�5�LC{��bC	{����3�0�+�*�+�.S	{Ěc��0���t��R��y��F��%CN{Ϯn
���Z�.�-�<	��
�z�q�{�r�u�v���#�&/.�y�x���!�-��0�5�#�E�B�'�'�*�s�6�B���;�fC{��c1


�C@�BC*{ԁ�
�h�i�f�i�z�l�m�l�m
�@�U�q���n�u�t�|+�S�w	�t�u��n	�g�f�eC{ہ�&		)
S	|ү&��H�H�C��3��z�����,C|ܯ'�E�/�+��1�2�u�t��C��V�c�K�(��C|��v�H�I	�J
C|遭1S}��2�����'�K�������k�I�0�c��T���HC} �I�L���*�nS
}*��6�����z����=����3�_C}5�9��5��XS
}?�8��Z�Q����.�R
��X��1C }K�5��,��t�S�t�/��{�r���k��Y�T�9�V�h�1�H�x�
C}M��N���
�T�5�!�T	�W�X�>�{�z��Z�>�A�<��BC}Q��8
S}���6��O�S��P�s���b��E�2��T��M�{�����C!}��N��(�l�u�X�#�P�?��*�y�k�`��	�k�;��k�r�/�*��d�2
	C}���8�I	���;	�a��9�i�h�i�fC}G	S	~�1��2����Z��P��Y
Ci~&�N��{�	
5�3�N���w��<�S��t�8�Q�|��O�j�m�b�T�I
�)����
����Q�T�d���z�4�M�L�!��G�H�!� �b�_�?�6���?�6��K�-�x
���d�c�[�C~G��O�N�+
/-
C#~'��B�[�Z�,
�1)�0�1�0�1�0�+� �&�%�"�����
�
���C~-��L/�)	&	S	��>�<��j��i�6�w�r��b�S�
��c��O�O��.����g��d��O��N�������C��@
�Y�D���
�Q�P���3�2C
���n��|��*��|�}�|C���f	S	�o�F���6��;���>��9�IC+�y�Q�i�h�9�Q�$�O�4�;�@�x��z�Z��I�$�T�u�i�T�l�Y�z�G��'�N	����8C�{��j�

�
��~��~��~�/�.�
�/�.C����p7S�큙t���!�������5����F�����������}��C$��7��n�~�e���l��O�e�v�k�,�;�%��F��
�h��
�.
�s�v���q�A�j��>��8�C+�	�d 
�r�m		
C
��2�5�4��4/)	C=���y�~�	�
�~����z���{�z
��?�D��C�8�j�k�
�{�z�����}�t���M�L�z�K�@��	�u�O�N�v�u�v�u�p��	��}�p�oS
���M�r�X��\���&�M��0��eC��	S
���c�-��x�p��Q�	��\��]�WC�!�=�0�v��e�y�x��S	�2�>��7��6��-��q�\�w��NC
�G���*�^�I�7�9
�D�M�YC
�@�$�BC%�<���t��m�5�0�1�$�%�$�q�U�V�w�v�j�
�W�c�D�i�V���Y�I� �^�9�f�sS����+�R��?�k��~��u������(��-��,S	���E��%�?��0��k��j��	��6�JC	�ŋ_��R��k�p�$�e�^C��G�JC�Ё�	�S�ޱE��L��S��h��i��P��+�W� ��tC��)�m�
	�(�y�J�U��T�=�B�;�Z�xC�ꁚ�S��M�L��C�큮%3S�+�T��,�u�t��3��H��U��>��E�S����'�h��BC�@�O�J�
�C�<��9�a�`�C�=��/S
�O�c�Z��p��K��,��U��A��C�w�YWC�b��m�p�
;4C�Z�z�h��/�Q�~�US	���O�N��?��P�l�m��m�C7���[�O�@�*�_�2�w�h�*�!�@�b�I��N�k�V�!�^� �0�f�W�7�>��!�3�<�#��+�6�Y�\%�M�\�*�=d�y�ZC����5�K�d�������i
��+�*�K�M�f!�m�l�"�C����9	)0S	�_��;���E�P�)���g��?��$S	�i���������d��5C�u�n�$�A�4��	�C��W�,-�]�C
�s��=�M�z�3
�B��5�4��C�t��JS���`�Z��b��[�=��X�������	��C7�ė�r�/��m�v�;�>�,�s�W�(�Y���f��
�~�S�Z�;�~�T��#�w�r�S��h�i�P�i��t�r�CJ�a�w�T�v�_	
C�Ӂ�_���z�
�C	�P�G�J�K�G��u�n�
�H
�J
�K�TC�́�R%	S	���,��t�e����r��q��p�EC����PC���:C���a�(�b�c�b�ES
���-��z������w�,�{��r��F��?��CN���>���9�:�
��C�&�@�%�'�"?@��r�m�
�W� �6
�	�;�J��d�/�L�O�N��)�H�b�
��=�R
	
�5�6�C�'���
�	�3�,�Y��t��	�	�C���b%�G�H(��
�K�JaC.����|
�k�n�s�r�s�r�o�n�m�l�s�t�/�`�5�B�C�
�x'��(�%
�C"����m	
"
#	S
��X��^�"�s��m��l��m��|C���>,�Y�(��d�
�,T>��&C-�
�r
 


�,�7			
 
���CQ���#�n�o�a�n�o�\��
�a�l�Z�i�h�]�
�c�b�h�k��b
�U�l��
�3�=�t�v�h�c�b�]�t�}�|���d�[�x�z�y�\�e�d�_�^�_�^�i�h�c�b�a�`�]�	�b�o�n�k�*#��f�=�+S
�
���|��q��,�k���CC���=��	�U�V�g�)�z��)C��C���<�x�}�|	�m�n�o�j�k�j��m�j��k�j�S�U��C�w��c��h��9��&��!�� ��
�b��.�x��?����HC-�h�/��x�/j�b
S�Z�y�	�
�d�	�d��W�	�t�W� �a;�\�S�L��F�L�<��q�;�3�d�z��d�+��={�v�u�D�UCJ�k�3

				
	�.�7�$�

�^��u
C�����
* 
<�[S�Z�G�F�yC,�i��O�j�i0�h�[�r�d�e$�h�a�`�i�y�x�`�a�`�e�
��`
�a�`�a�`�c��C$�q��G#

 S��|��H��=��T��
�C��N���A�W�!C��S
���,���e��d����A��b��]��\C�*�Z�/�g�`�k��C�6��@�[�gC�/��y������#�j�I�#S	�P�.�f�P��b��u�X���I�IC4�[�
�!��6�G��n�8�0���#�V�J�9�
�:�[�>�r�g�6�>�r�u��d�u��-��4�i�!	�~��9��%�K�/��y�`�&C�]��d�j
�k�j�o�n	�K�J�M�|�N�M��P�s�r�OC�\��	S	�ڴ�3�� ��s��C��#���+��J���U�~�I�0�o�-�,�\�T�MC��&C�쁚c�h�i�S�:�7�L�Q�8�7�LS	��`����.����2�i��k�aCD�&�5�d�m�7�<�h�e�C�D�K�N�'�"�M���F�G�F�w��h��� �G�J�%�&�o�n�I�[�"�5�� �U�X�1�n�.��SC	�7�(G�(+�I�HC:�-���4�m��l�;�J��4�8�o�~�K�J�l�m�H��8�k�O�8
�u���l�9�4�1�G��A�.�o�A�.��H�T��~�A�<S
���A�E���/�R�V�g�nC	���H�D�%�5��r�
��]��^S
���u���?�a����
��w�X����I��RC!�Ε*�J�b�kd�
�(�u�t�_��0�CX�.����H�)
��+�>�<
� )�y�R�]��y�Q�.�	4�MC,�ʹ}�:�J�	
�r�qP�b
�c 
C8�ҁ�v�7�Q
��5�4���9�6�
�L�
�
���		��	��'�&��F�G�`�&�'�b
�G��	��E�D�E�D�M	�Q�^�0�=	�<�9�}C�ρ�<

e
	
 !S
�G� ��D��A��V����YC�_�|��C�U�*
C�T��1�V��?�>��^�a���7�Z
�D�#�"�G�F��]�\�S	������G�U�V��B�+��E�*C�ʒ�q�|�0�J�!�1�a��4
�P�UC���>�`�a�j�k
C���H�\�i��D�]�\	�C�@��"���G�T�
�f�#�E
�j�O�&�S�|S�%��!�����(��O���:�H�W�~��uS	�3�S��J�+�/�X��]�����#S
�>�X�[��&�v��I����7���C9�I�Z�[�^�A�9�&�$���]�^�M�L�	�c��`��J�w�L�C�*��S�7���j�#�8�}��|�!�"�A�\�1�K���\����G�&C�J��@�N�_�^�QK�R�?
�j�]�^
�u�N� �u�t�u�0�c�$C�O��|7
S	��d�J�N��V��_�i��CI��f�N�
��� �;	�z�J�O�`��
�
�b�e�u
�Z��@�=�Y�o�V�z�	�T��m�"�S�D�/
�*�[�x�h�-�6�i�`�Y�,K�8�?��E�#�&
�j�1�+0��B�0�M��8��4
��C��<�;�T�2�uCF��v�*�#
�T�G
�Z�r�O�D��e�-�jH%�>�G�>�?
�>�u�I�`
�a
�Z�[�d�aC�,�VC""
)			C�-��~i
'$NC9�/��0�?	�Z�Y�:�=�<�=�<�=�:�=�8�9�8�9�6�5�(�/	�$�%� �!���
����C�?��"!&	"	
S
�ֶ8��G��.��m�	����c�^�C��|��%��-%�"�KC��:�
�	
���
��zC�쁒}�t��6�7��
��a���,�b
�i�/
�&S
�B������0���'��_��t��mC:�N�L�}�~���u�{�\�	���	�G��5�f��*�w�B
��w�z
��G	�N��u�z	�+��{�Q�s���=�:�/��_�b�3�e�K�NC�O�zR�O�N$�K�JC2�M��M�������	��������z�w���{����	������)�vS���o���B��?�r��� ��)�#��n����L�$��G�tC�/�h�x�I�e�9�eC
�.�[
�
��z�C�3��W�4�5�4�_��m��6�'�9�^�9�o�&S	�i��x�c�6��A����E��$��+��~C�~�I
�e	�.��E�
"��_6�"�n�U��%��:��F��W�hC7�|�e
�*	�H�}	
�h�e�h�e
�r
�_	�V�W�L	�S�j�U��,�-C6�s��%�	�5�e�d�e�E�D
�+�
�$�#�H�I� �Y�A�	� �Y	�d�#�N�O� ��@�G�$�Y�I��L�M
���#
���L�M���L�L�C"�t��z

		
�yS������}����'�C�x�2���w�xC?����A�D�5�.�I�H6�� �7�B�U�K���)�,���
�<�D�q�4�1�c�L�A�B�u�p�;�h�%�f���>�)��}��R�$�k�TC&����Y���
�~���K�J�����4�`�"�S�R�O�N�)�(�)�V�P�U�T�1�0C����&>
S
���4���r��s��`�C���z�HC)���E�6�D�s�n�Z�U�+��B��A��o�(�g�|���-�8��O�]�&�I�I�y�r�K�H�K�$�A�|�+�1C���"�|�}�0�S�c�f�i
�t�uC����8#S	��)�&��Q��P��Q�	��$��iC;�
�R�C�5�v�5�4�!�B�=��"���!���4�5�~�E�D�6�]�$�q�r�!�K�l�`�g�W
��E�3�6��C$���\�B�C�F�G
�}�l�q�o�n�o�,�?�o�k�j�*�E��>�1�p�>�1	�0�r�=
�H�I��	C���?,
"
 S	�Ƹ������J�N����P�<�
C�ӌ��=�k�d�:��	A�|�`C�ܸC�Ё�R�K�H�I�y�j�T�A�.�9�H
��9�y�D	�M�6�1S	����L�y�Y�?���e��~C?��B�&�K��k�%� � ��K���$�\�1@�02�C�3�G
�h�5�Q	��6�q�>�1�Pm��Y�/�P�[�T�/�e�V���s)�f�-�^�-�
�.)�M�$�i�8�� �$N���D�C�w�j��pC�ܰ9�?�B
	�(Cs�!��H�G�J�c
�b�K5"�\�]�$�%�x�y	� �	�JF�<�			�j�e
�L�#�/A�0	�'

�p�q�l
�k�l�mC�9�3�&�7+�
�{�NC��8KZQ8
�"	RC�V��*�p�*�=
+$0C\���u�(�+D�&�%
��������{�x�y�x	�{B�:�;�j�i
�b�e�`
�a�`�a�^�_V	
�F�K�H�I�
���A�G
�F�E	�D
�E�B	CL���YC
?
L

S	����P�]���H��;��6�O��O�C�ɏ%�<��}��bC����c�\�c
�P
�Q

�H�3		
		C%����2�?�L�M�L�M�:�!��s��/�b�3�x�-�d�e�f	�]�(�~�)���$�A�.�)�8S
�!�4��^�M�����+�*�;�qC+�-�;�]�i�"�l�
��f	�&�`�Q%�Y'���k� �M�j
�K"�]$�=�b�I�]}�k�d�W)�_�k�2��d�t�-�}�|�8�
�M�ICb�.�0�F�?+,
�z�un			
�R�[�f�Y�T�_	)2
�`�e�2�/V�
��x�d�e�
�C�6�n
�
�i8�h# ��y#�xC�e��A�]7&�C?�1��g� �!� �!
���	�k�x�o
�n�m�h�e�d�a	�L	�M]�H�I�F�E!�@�E�>C&�=��C
{"

p	S
����\��`�a��u��x��c��<�aC� �O�
���/�`��6
�W�V�]�`����x�")(uC7��
���
��8�;�8�=�<�D�C
�A�B�<�=�G�n��}�|�q�L���F��-�K�	�[�7�f��(��g�N-�w&�dC���}
S
��Z�G��c��>��_,S����\��Y��"��6C�,�~S+�0�F-8	_N�?�$Cd5�&�oD�8�_",a�w�~��e�?nA5Z9:P�L�}/S�]�w�I3"P!�l�GjK�[�~�8�C��CC���	5qpq2Fw�S?	B!��*�MzC��Y�N�h�\��n��Y�5�#�Q�h;�>���^)N�"�s=2�ZC��LC��C��i4LCD�~�%�H�q�A���Q�R�{�e�
u��_�E�:^�C��l��t�q�0?��o�r�%�?�j��W�3�J�OG�b�E�3� �
G�N��Y�O�D��g�/�K�	��V�_�[
�*%�)�2�).��A�d��	��t�Z�
�>2�8
�t�C&�>�e�h�
$�,�I/�^@�
!�W#�	�`$�
(�}:�C&	�S�t+�;;��I�o�oC����Q�n�,Ew
P�>�L�x�72~
�B �5I�'$��'�I�!,S
	[�W�X
�(��h�ol�Q�;�z�)��f��V		(R��i�2W����B��9��E.�gO
�c�(�
_v�<.�TB�-0�,�|"]	e~2u9/�5s�R
�D("�Y#�l�$
�Z�f�
�(1�	�%�N�l�B7�=(�l{
�K�{�$�$�64��&�wB(2 �>
�L�#8-�a_�iJ�v4�q���I�w7�
���6��@��<�m��'\:8w
H��&�{�{"��?�m@93*G�w�)�<I�i�b�%$�<�.c�{�U�v+� H"8�JR�g�nz.�2�~�.
�p
�go�y0
�;�s
�p�Y�>�H��k"�K0!
�$
-'3�\-� `�	#RaF�E
�'�^ �>� (6�u �+"�h093@9
�d&B
Rv���{�2�*�9�/C,���Yb"�X�:�[X�M�iB�(
�*�Y�8�]�m�Yh���Q�S����,�6�A�I�|�t�g�g�f�8�I�*�,F	�}�0R>�xc ;>%e��!�$�*�;�A�G�X���0n��Fpc��s���F�#�*�K�P��n�a��*�U�y�	�*�$+R�J��
U��cN���� �9c_��hc�G!`	��!	��jH�v�a�r�n�2���~�0JK�7!�7�E
�&�5L�?���X�^����M�:���!�0�>�`�y�/��B	�&H�=�\�_��4�1�V�x��C�8�c�X6�e<�p(�w*�����%_�7J�E�2�v�7�o�*�_�{���'�7.�@�+�[Z�f�C�u^��+�!/�(�~�~|�u=�J���l�,�>�l^�~�I��-�B(�M^�Z�L�0R�BL�Q\�`S�h0�s�=�;��=�>�T�d�f)�og��?�j�p�\�s�d�f�
�� �>��\j�{
�u �$�	��.�!�%j�9�<��P�{�]�G�$v�>�A5�KV�\�g�u-�	��
�/��
��[�/Q�>�K�tx���g�-�1��U�~��r�Y�W�K��K�F�IP�Y��y�|�G��k�J�M�r�_#�hF�r�t�d����C�_��pT�~�m�3"�D�B�=��R�3�i(�m�@���1��E�J��bP�x"�~�:��+�*�0�7D�;�>>�F�H�`�i	�k�m>�{�~��G��0��
�T�;�so�{A�%��	�V�%�(��*����{�e�O�Tc 2�@�"	�(�I����$�c!V�9c
$��c�I	��+	�2� 	�]�h�v�.�-c!��,�*��p�x�C�B�.�a�L�K�b�q�|����� �#�(�*�3��]c�[�%c.��	c`��sh�|��k.���v���q�e��x�9�a�	��
�l�p�j�r	�~B�m)�v��a�u�C�X)�\�^�;�q��w������=a�G9�K��U�@�^�l�t��	��#U�(Q�,\�2�t�S�-�j�z�|��
�S�#�>�0�
�:U�?x�HK�O�K�m�:��4�t�"��8�-�@�H�Ky�Z�_S�m�
����
��9��L�(�1�2y�<�^�T��r=�x���E��� �g�0�f�Q�f�o��|��
1�&��(�)�p�O�v�m��}�h�1��<�^�E��Lr�S��Y|�^��+�Tc G��g�8�}�(	�_�Q�B�iZ�:�6�I�D�O��E��F�u�P	�l�wc�$���]��L!��T��Z4
��_��jB
��l��w����+��k�J��=���x�@��E�R��l���~&��P��_�D��k\��sE��z@��+���n���J��T���9�c��"��M!��R��
,��'���k��-��0b��:���I�(��_�F�����=e���|��R��V�"��p�'��q��H$������K���Y1��*���R	��y��m���
��8�����5B��?���'��T��Zk��H��k��o�>��X���F��'��)��,���.�l��K���<�W��u���D���(�� ��R2���M��M>��V@��^��d:��p�
���$��g=��P�����2,���#��D�`��i�"��@��BX�����$�6��X���G�[��S��V��e|��i�g��t��4�����)��)^��2���?�;��N��Ps��]T��g;��nu��*��L����F��.1��4L��B�3��Fq��L����[��.��*���^��*�!��8��h"��"��p{��yQ��d��	=��P���#���'��b��5�t��@C��k`��H�l�����i��x.��W��3��
$���������"N��'\��L	��N� ��`.��l��?��t��w�(����BW���
��E�%��R���dc�>Nt��W�U��hn��x��{p��	��0����
��+��%>��.	��1��3�g��N��Un��j��mZ��uI��}���
���L��!{��)j��3��6���B��E!��J�a��m��qo��|!���
��"�J��8��;��?&��F���Q{��\l��d��h�U��x�Y��	G��:�������+q��;��=�0��O��Q;��W��Y��_���n&��r��u�)��$��(1��/�6��?��A��D���V%��[�x��n�1��j��
������((��+��.��1/��6���D��F��J	��La��R���>���P��3G��8q��CX��H���R���jH��q�H���<��Q����p��"@��)��,�k��@�R��s�	��y��8����>��$���@��D�N��U���g�O��
�
����n��&��(-��1�Z��N��P9��U[��e\��pM��yI����R��
���E��$�A��A���NH��T���hg��q%��wI����W���
��-��0��3�|��V1��Z$��^#��a?��hJ��q��t0��}	���I�����#���9��->��3��6��;��>��A��C��F��I%��O��R3��[6��e�"��~2��
���W��$���,��/��2���DK��M1��V�q��z��}��K���&���� 3��&��)��.�D��L��Q=��W��Z;��`:��g{��l\��vC����L��
s	�5�[�a�]�k�e�c�g�_�_c��"	����0>�G@�Ic�_�c��/����Ac+O����%��z�P��U�p��PK
!<Z�o��<chrome/pdfjs/content/web/cmaps/UniJISX02132004-UTF32-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�UniJISX02132004-UTF32-HC!��bC �T�?�dC!�wS#��t&+"'�+�B�9S%�G(C%%�o/S	%@�uC &���z�wz!O�0�/
]��C'�C0��KS3��J�W�V�mC3�


��K?�C3�1$1
)')��q
C3�L


	&��vc%�9�7�=�;�A�?�j�]�`�U�X�w	�~�6�O	�c�]�H^����[c0�k�g�6�E�P�T�\�b�h�t�w�|�~����mc#��^�d�I�X	�M�XPK
!<��B��d�d1chrome/pdfjs/content/web/cmaps/UniKS-UCS2-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE����!a
 ^"�P�V�k��0�
��KA��GXM;>��O�A ��J�O��-	
�6	�3�4	�7�6�9�8�9�8�=�8�C�8�5�8A��Sa$��W�h�o�7��|��$�#�Amort�ui	�X��8�M
�N�O~�
�W	�N�
����'�m�q	�M	�C�+�*�-a
 �.�-���$�;�7�/�8�:a 5�n�lA!��w��T a!��@A!ҁC+]\]>6EYA!��pN2�B1(A!��fa"%��@�?�>�6A".�S�V8?3
+&A"7��dA"P�#!<'a"d��<�:�Ij"�a$`�]�;�!�Ca"f��)�!�)�+���,�+�&�(	��&�7�/�
�,��2
��Z�W�R.�DA%�Q%�	H3:%>)<'(q%�/��1���$�3��5���7��#�9���;� �%�=���?�!�A�&�C�a%��h
�%�$�e�iA%��m��b��s��J��a��b��I��D��c��h��W���^��i��>��=a%��fA&��&!�AA%��G�?<A&�a&`�^�b�]�`�a�o�}��|�el	x�/v+R�S
U�&h52�fe)�q��C�'���sa&o��&�7�6"��=	�a
	��~�|��_�}�z�T�Ud�9a0�j�\�r�
�U�T�x�q3��d��`�~�K�O	�U�Q�g��	�i�x�Q
3‚� �
r�-�D`q�lA3؂A3τc`A�LN�<�f�&�
��}�X���o�	�l��v��.�a�^�U�V��'�	�N�K�|�u�_�F�p�k�5��u�x�v��<�e�&�B�L�i�$�1l�4�1��/�@�&�o�:�5�P��>{�%�J�O�U��A�@�l�W��=�L�/�.��#�f���%�\�
���sS�6�O�0�Q�y���d��n��'�?�8�U�%��#�8�p�Y�f��!��%���r�@�/�u�!�b�(�m��P�)�$�G�F�S�4�B�"�Q�i��q�^�=��/�_�
�K��s�$�$�q�A�#�P�+�9�x�z��M�U��}�`�Z�g�~�g�M�N��|��o�V�
�I�D�!�hb�Q�6�Q�f�i��7�\�_
�j�_�x�B�"�L�o�9�A��]�{�b�3�N�U�a�0�s�>��w� �(�m��N�"�=�-�q�j��7�
�%��j�w�X�H�t�t�?�_�
�#�/�p��Q�i��D��Q�X�%���C�Y�^�,��2�	�+�C�b�%�t�S�4�D��F�~�G�L�'�M�x�c�4��P�Y�;�>�k�@�#	�v�Ah�8�E�U�b��%� �n�Z�'��k�D����6�k�|��v��u��n�?�^�&�+�O�b�{�f�n�]�o�D�#��6�4��	�v��L�M�S�
��p�P��L�W�D�U�8�5�T�y��
���;�;����q�C�\�
�`�|�A�A�_�R��L�?�*�<�m�-�|�,�J�E�d�%�/�T��]�h��*�+�O�T�n�1�:���o�j�n	�	�1�A��@�?�1�\�(�!�W��`�`�X�?�J�z]�x�1�z��Z�?��6�E��w���0�a�f�3�l�;
�G�P�e��a�J�� �.�)�)�F��-�Y�X�Y���B��7�,�A�M�p�q
�&��F��v�[�:�&�3�"�	�]�X���gJ��{�l��_�v�y��2�E�8�;�}�<��n��Y�f�r�v�s�x�0����PE�m�=�(��{��u�}�x�s�2�#�x	]��~�w�u��.��u�0��a�[�|�0�&�{�]�b�&�W��y
�s��a�2�q	���%�i����_��;�
�D��H�S�$�8�{
��0�F��&�1�P�	��	�^��;�p�s�
�o�\�;��y�d��e��m�P�a�5�H;���l�_�#�

�{�z�@�3�H�+����h�g�d�l���X�F�E�Q��-�G�G�b�E��,�+�z�2���g�>��
�_�0�7�Z�%��e�b�!�L�K��
�A�5��1�m�*�@�
�"��C��E��#��O�J�c�}�Q�N�J	�E�>��(�
��n�i��>�i��V�K��A��j�O�%�J�~�c�|�	��x�}�
�|�9�f�6�s�A��Y�A�"�2���k��"��S��&�R��p�l�!�N���<��i�D��E�f�a�o�M�g�J��]�S�Z�=�,�s�t�*��4�
�jp�=�/�l�/��[��Z�l��_�z�q�p�.�|�a	�e�TJ��=
�W�!�d�
�@�9N�'�,�?�G�:�1��C�	+�3���k���`�f�F�c�6�I�F�^�w�	���b�<�<�b�y��,�a�2�p�9�u2�d�A�(�t�s��I��s�"�m�P�}�T�	�z�X��1��R�?�5�>��d��X�@�S�S�b�:����j�n�I��]�O���?�!�|�7�V�-�I�#�^�Z��X�7�(��s�
��q�;�6�`�4�!��	�I�H��^�U�:�]�K(���_
�D�[��/�.����4�C�Q�S�t�r�
�w�o�d���B� �;�.�9�d�<�[�&�Y�T�o�y�&��6�q�R�I�0�;�D��na\��hA]�T47��s�wA2\��5�b�E�3�Y�$�?
�f�c!���r��i�b�,�Y�B�1
��M�b�F�*�q�z
�o�
�g�$�T�A�>	�:�0�e)	��w��{�x�{��%
�3��e�%�hA\�7�y�,�gZ�3m�R�3�|�k�B�
�w�T�M�_�U��;�,�[�4��k�
�^�fa^|�-A/^~�n�*�2B
�U�X�6�Q�=
�N�Y
��;��o�=�D�p��0
��$�_�{�n�S�k�>�k�9�
�-�B�)�,��W�@�&�>�D�� �?��yA*^��h�B�D�!�s�H��P�3�r�F�)�`�A+�z�!A�0�y�>%�y
�k�l�n��j�
o�`��e�Z�Q�
�f�	�}	�^�6�[A^ȻD�'��
�v
�m����x��=���&
�T�	�6�a_��ZA_��C	��s��U �LV�a�n�g9�r�?�4��r
L)��yA_��&�H
�w��a�$� �}t��l	�@�k��
�A�V
�w��%���g�l�=�$��	��	�FA_�
��,�U	�m,�0�Q���;�L���N�[�|�sr�i�u�1��aa�2Aa�:�2�O�0Aa
�6��NAa�]�?�1�0�W� �U���'��h�\�4�aaX�-A7ac�n�C�B�Z���0��	X
�B��|�?��^�#�V�?�o	�9�d�o�J	�v2�2�a>��f�#�'�!���F��f�Y�� =��S��P*
�h��Y�v�m�o�&�a)�Aab�CH�
w>#�A�Fy�
�|�6+TR`�ST�R<�U?�((<�}A4ap�
�yN�u\��b�:�4��9�1�*�u�Z.�%�'�S�"�II��&�K�D�-)��!��Q]
�1�2�A�L�f�|�
���|�q�)�$��9�8O�~�oAEa]�/%�(�s�N�j��8�t�o�l?)��|���d��9�V�[
�A�@�'�V+�i�
��=�DXY�{�8��n�Q�~�;#�7�x
��*
�	�zj!�[��z�X��j�=%/
�B�\3�o
�p��m�c�p�eadx�"A%d�� �?�$A�X�gV�Z�f�k�6��Q;�T�!�
�-�W�F	q�F��F�
��L���'��V
�n��c�jAIdz�.�P�'��x��e�F�3��y�`�J�F�
�O	���O�F�$�a�1�P�9�;��1�J�A�P
�q�y�J�@�+�	���?�7��?���C�3
�B�F�{�V�?�R�~�;�4�1�r��a	�4��V�3�X�N�C�w��|�+�%Ad�E6�<ae��Ae��b�C
�Ae��1�s�A�H�]�2�(�?	�S�b���9��W���'�p��(�v�Af
�D�Z�afC�gAfi�E�S�r�?�h�F�e
�
��
��]�s��7�a�.��<A
f_�i�G�23'� �!�#�A�:A/fI�I�1	��z�C�(�y�>X�y�)�p��;�L	���<�)x	��"�@�?�K�L��d��2	��9�p�� r�>�oX��Yag�sAg&�>�	�r��M����g�V�{
���Z�i�!�"�(�_�6�S�c�O�\�E�N��}Ag��pAg-�(�>�]����Y�B���R�o�b�w�?��ag��xAg��I���YI�0�1�,��@�vh�!*��^
�+�7D~�h�g
�`�|�=�%�t�\�1�6AEg��J�%
s�5�>�?� �`��q�N���Z�m�h�q�!
�
�6��|�Q���p��P�$�i
��x�E�]�*��M��0�Q�i�3�|�o�P�[�)��c�
�&�B�I�_�I�4�>�p
�
�P�9� ��B�mAhS�SK�aia�9Alic�{���I�4�s�3�^�]�^�H�e
�`�X�)�	� �M�h�U]�o�X�^�#�T�G�k��`�:�%�]�2�l�7�h�V�}��G�p�!�A�<�-�T�u�s�(�U�#�*
�y�`�U�Y�]� ��\�[�w�?��]
�*�_�A�
�k� �H�t�M���e�>���+�~�9�"�~�{}��:�9�7�L�X�_�z�G�o�h��k�_y�-�|�A	iw�
C�BL�p�!�:�P�u7,A
i��i�_0�(0��2�_�Tak��MA�kۥ(�6�m�j�m��+�}�0�;��K�d���
��O�6�G��x�-�&�K�P�?�t�m�"�Q�r�\�4���v��e�x�M� �e�p�Z�4�i�*�F�}�h�x�m�T�f�O�O�R�{�6��'�H�+�2�p�-�|��Z���3��:�I�~�)�s���\�8�mU�2�%�]�D�'��|��l�<��M�+�r�<�#�d�H�~���p�$�f����c��PS��^��W�V�,�#�
�C��L��j�9�T�:���t�G�6�m��)�p�&�y�i�|�(��k�0�������n�0�+�F�R�	�,�!�R��	���mh�j�h�o�g�B�i�6�G�K�>�V�>��z�e����(�h��3�-��\�s�	�|�s��K��z�J�o�B���t�t�S�c�J��L�	����1�[�O�>�x
�+�:�I�&�L�5�X�7�!�7�m�	�l�
��|�=���'�5��}	�T��(�W�`�^��I�r�s�N�S�p��w�N�Y�%�!�i�	�p�I�o�
�M��d�2��w��1��T�{��@�>���V�A�\�O��j�9�F�k�<���)�|�J�y
�q�b�S�v�*��b��g�@��'�n�e� �8�o�1���^�e�X�X�U�L�]�h�O�
�#�v�'�n�	�c�p�_���@�!�r�
�b�B	�#)�X����Q�
�'�N��)�5�P�$�/��x�6�W�V����u��[�-	��)aq��qAr}�6.��	WA:q��p5�S�J�&�X�
�	R� �y�k
��Y�
�K
�w�P�1�y.�8�W�F��`�a�b��|�V�%��S�hv�
$�{ �o�|��t	�7
��i��#
�(�7�$�N�u�!�B�	Aq��1'�3�d
�J�}�9%�9�<�s9�{&�0-�(�M(q��%as6�?A�os>�]�;��I
�x�C�Y��V�����E�l�;�a� `�6�J�C�^�� ��u�a�E�W�2�A�E�&�V�v�Q��pg�&���������(	�9�$�F�^��C�V� ��c	�r�R���N�B��u�t�Z�h�0��l��=�
�T��%��I�3�4�!�B	�\�
�i���z�i�8��C���*�m�a��� �H��F�s��!�y�\�!�=�x�n���y�0�]�%��h��,��#�L�y�b�c�3�e�(	�M��>�����'{z�$�<�C�B�2�
�m�H�y�&��K�M�j���R��P�J�A�>�_�z��r�U�F�M�'�S�R�S��h�B�U�f����P�[�>�W�8�=�,�@�c�|�8��+�Z�F�-�5�p�
�#�^��C�a�^�i�f�BG���^�	�h�C�B�C
�"�)��@�(�x���lav}�Av��t�f�E�]��'C+�N+�)�E1�A�d�%�^��
�*�U�l&�s�:!�+A.v���b�'�r�0�3�c�v�#� {�Z�a�6�O�S�r�sL�$�1�I�8���F�L�-�<�>�P�#���O"�V�Y�X�?��L�9�O���Av��c��?�C����g�,-�z�O
�R�W�v�^
�a��/�lz��V?�<�x
�r�(�)�Qax��GAx��3�P�5�w�b���:�G�,�r�%�
�$�a�1Ax��cAx˷
5�`ayG�Ay[��|�L�K,��+�u�
�3�G6�w�8�s��8*�z/�EJ�O�&�qAyP�F
�)�%�)�!�Z�i�3���W�y�I�.�n�-0�D�+�Q	�Q�>^��A)yI�2�9�8�9�t�1
�J�_��U�@�m�+Y�K�p�s,�+�4
�C�R�i��-�h�b�
F�(�#�h�%�H�}�m�>�G
E�(�Waz��UAGz��~�
�Y
�<�V�6�h�+��L�]�v���X�-��6�u�P�Q�%�,����A��A��<	��R�+�f�o�V�j�%
�
��o�
4�i�	�Z���/��,�T�.U�}�b	��)�)�B��,�%�l��2�P�H�.A(zķ�u��-�N� �-�R
�;��_�^�F�4��#
��{���&'�$�K=�!
�\�3��W�}�R2�k�e��3
�� �
"�A{�,�1�e�"�?:>�J�a}
�jA!}�!�)
�J
Q
�L��6�<�Y��9�V�!� �=��`0 �_���i?�V	�	�b�Y)]���[�<)��x(�u�<A`}
�6B�|�i��,�J�q�2�9�<�p�;�5�7��j�W�x�_�^��O�S�h�
�Q�r
�nT��B
��!&�T��'	�X�b�C�B�L�*"�'	��{�.��,�]�`��I�1�y��b�_�$	�O�O�p�{�8�	�%�e�1	\�^�=�d�v�>�q��i�n�e��C�Q�Y��y��*

�:�3�A
�0�Jg��0Al}�4�!���5�\��K�J�A�$�T�}f
�NN
�G�i�L�	��d�a�\���}�f�<
�?�u
�N�$�r�s�2�
�
�E�F�0�e�3
�N	�g��=�8���.��B�=�n�!�Z��m�x�w�w�.�~�b��}�j�g�3�(��q�p�]��G��W�&�8�x���R���?�:�5�l	�|�n��s�2�>�u�t�_
��&�	��MA~j��G�^"+�^�
Cja�P�
A�g�T�`��o�=�Z�a�s���F�L�
�[�`�u
�J�)�^���T
�;�"�3�i��o�l�(�~�,
�s�F�S�B��k�v�l�M��+�j��T�U�2�X�
�f�U�X�D�;�}���B�GG�@�u�@��a�J�B�\
��H��V�W��b�*�Q�d�T�o�>�b��^�Y�+�<��B��Q��j�=�.�+�I�R�w�N�t�!�/�J�!�o���[��Y��J	��;� �g�[�x�3�^�7
~�"��t��B�#�1� �s�;��5�J�;Y�& �c�
�?
�%��s�o�
�d�\�
�
�9H�~�W�x�
~��N���|�)�,�2�J�����z�h�^�#��A�Z�g�I�
�@�A�0�#�B�t�m�]��Z�W�P�#��=�R�c�O�(��W�/�P�1��s�0�S�~�q�o�B��t���7�8��j�c�p�	�.�X
�0�1�1��p�-�n�	�1��p�S�t�gI�
�K��%
�J�z�]�^�G�:	�}��v�g��V�1�S�h�
�D�U�:�R�c�z�o�^�c�`�\�I��8�-#�|��o����F��\�+��j�(�C��2�%�s�0�c� �w���3�f�9���~�9�1�A�Z�"�-�T�;�\�Y�-	�/
�B��"�#�	�h�%���t���[�<���A
�m�*�o�|�� ��P�E�G�0��\�6��T�.�'�\�
�.a�~�OA����3�Fk�p�I/
�$�*	�I�2�6�#�

�A����2)�O�9�X�:A���
Fa���uA�F����0	�1�U�4�� �S�H��m�%�f�_�J�q�J�<�4�9�z��.�p�7�P�0�&
�?�,�p��c�L��K�|�I�@�[�n�
��Z�t�3�,L�Y�9���M�a� ��\�,�!�/�4�i��l��3�Q�8�Y�z���z�J�m�_�I�(��M��2��G�D�c�i�E�K� �8��
�&�m�,��z�RC�r�\�i�/�4�K�\�/�B�"� ��(�P�s�a�>�>�.�q�<�g�)�o�
��L�	�\$�v�U��9�@�
��L�E�n�{��F�|�M�\�q��7�	�?�^�>���<��}	���
�Q�P'��I]��7�_�*��)�_��Z�
���X�#~�H�{��D�?�2� �y���@�	�5�J���u��t�p�w��T�3�]��|�A�O�L�f�Y�_�R��q��1��G�:�Q�r� �m�&��,�m�~����}M��{���
�`�
�^�w�2	�rc
��-�H�(��V�_�t�i�@�S�C��=�J�K�V
�Y�h�o�n�o�T�;�d�|�P�k�%�t�.�g
�,��,�o�'�.�w?	�V�%�O?�p�!��B�y�:�k�v��F���?�F�J��C�F���
������b�5�R�=��f�/��=�&8���x�2�=�
��j�	��x�#��
�t�q�N���!�w�0�X�z�p�a�s��6�Q�P�9�i�"�U�C�X�9� �$����(�x�<�'�o
�^�y�d�w�4�x�c��H�@�z�O�|��L�R�K�1�R��O�A�:�O�S�=�d�j�L�	�fq�f�k��}��F�9�M�F�K�P�m�K�\��.�K�[���v�V�C�W��a�r�t�y�W�"��D�_�J��0��d�
���U�:��$�s�{�j�(�]�k��V�s�E�:�~�1��	�z�K�4�@�Z�[�Y�;
����S��d�=�1��q�4�>�	�f�M�$�U
���K�D�q��a�L�X�"�
��p
�}�(�V��M��p�k�b�C���I�1�$��s�p�&�Z�p�M�H�n
�W��z�k�=�/�.��~���p�:�o�@���/�X�0�q�<�o�f�
�s�N�o�n�p�i�H�w�A�V
�{�}��T�8
�&��}�	�`�L�{a�Q�GA	�[�!�,�w�Y�u�\�0�y�Ha���]A
���+F
y�(,3�-p�'@�P�7�A����z�W4�~9�j�i�h�y�&�
�o�c�:
�'�vA$���s�	�a
�4	�W�
�k?��?�z���&
V
�Q�z�/�8�\�I�:�W��h��C�
�?����@�#a���tA�ǝ3A���*�z�]A���0�	�~�g��p�#�>�~�m�7�7�� �i�,�N�aa���SA���5�,[A]��U�M��j�%^�@��N�i�@�7
�#�B�-�r�m�R���A�y
�b	�I�<�;�(
�f��8�%��i�VS�H�K�N��Y�1�^�'
��z
��tk��3�uG�:+�[�3�0�m�/�2�c�B�c�f�i�4�,�
�6�u�
�(�a�d�_�z�A�3�1�Y�>�g�B�9�5�Q�l�*��&�oA1���
��	�y�B�#�N�� 
��!�#�Q�B+��v�%�Q�<��0�e�Y
�G
��
�\8�=�/�%�C��$�
�-�
�A�S
�`��E��Z�f�a��A[�+��p��W�i�y��;��=�=*�`��n�E+�^�S�s��hl�%w�>�S/��	�(�Y�-�'�h��2�Y�S�
���p�m	��5�
�^(��@�G�F
�4<��R�)�0"��l�sV��|�s�1�0�����
�0�%�B�a��v�u!�V�n�
�u�F�i%��x�
�S�7�(�?�@A
�1�kp>��V�~5�GV�`���d�d/r;,�	>A��g�c�\	�n	�Y�#	�pN��SU��tN�/D�]6�$�*��Ix�I=�c�T��=D�*�
�4
�uj�Uq�3��>�u�@�w�A�y�E�~�M��R��S��T��V��Y��[��\	��]�!�^�"�_�(�`�+�a�.�b�A�d�C�e�E�g�F�h�K�j�L�n�N�r�Q�s�T�t�[�v�\�y�b�|�c�}�e��l��m��p��r��u��x���	��
�	���
�
��������������������#��$��%� �+�!�.�"�1�#�9�$�;�&�A�(�C�)�F�*�M�,�N�-�O�.�U�/�X�0�[�1�c�2�d�3�l�5�n�6�p�:�t�=�u�>�v�@�{�B�}�C
��D��F��G�#�H�+�J�-�K�0�L�7�N�8�O�@�P�C�Q�F�R�Y�T�[�U�V�]�X�c�Z�d�[�e�\�k�]��_��`�
�b��c��e��f��h��i��l��m��n��o� �q�!�t�$�u�&�w�(�x�+�y�2�{�3�~�9�.�?��n��o��r��y��z���
���
�������������&��*��,��G��I��J��K��R��S� �T�#�X�%
�^�&�i�(�o�*��+��,��-��.��0��1��2�9�4�;�5�>�6�D�9�E�:�F�<�K�=
�R�>�]�@�c�B�e�C�h�D�o�F�q�G�x�H�{�I�~�J	��L��M�*�O�,�P�-�Q�.�R�/�S�3�V�4�W�5�X�8�Y�V�[�X�\�[�]�b�_�c�`�d�a�j�d�k�e�m�i�r�k�s�p�t�q�u�t�w�u�z�v��x��{��}�
�~�
�����2��:��;��>��?��C�
�D�
�I��K��N��U��V��\��^��a��h��j��l� �o�!�r�"�	�$��%��&��'��)��*��+��.��/
� �0"�+�1�N�2�Q�3�T�4�[�6�\�7�d�9�f�:�i�;�q�<�r�=�s�u�>�y�@�{�A�}�C��E��F��G��H��I�&�J�A�K�D�L�G�M	�N�O�X�Q�^�R�e�T�h�U�n�W�p�X�s�[�x�]�y�^�z�`�}�a�~�b��c��d��f��g��h��i�"�k�#�l�$�m�(�n�)�q�*�r�,�w�/�z�0���3��5��8��?��@�	6�F�
�}�
�~��������������
����
�������!��"�!�#
�$�$�/�&�5�'�8�(�O�*�Q�+�S�-�T�.�U�/�X�1�Y�2�Z�3�[�4�\�5�^�6�a�7�d�8�w�z�9�
�:��;��<��=��?��@�'�A�B�C�D�D�G�E�N�G�O�H�P�I�V�J�i�K�p�L��M�
�N�
�O��P��Q��R��S� �T�#�U�&�V�-�W�1�X�7�Z�9�[�;�]�<�^�A�`�B�a�C�b�I�P�c�d�e�f�f�h�h�o�j�p�n�u�p�w�q�z�r��t��w��z�	�{��|��~=���R��T��W��X��\�	�]��b��d��g��n��o��u�����+��-��0��<��B��I��\� �w�!2�z�"�-�$�/�%�2�&�8�(!�<�)�^�*�y�+�|�,��-��/!�	�0�+�2�-�3�/�5�6�7�7�8�?�9�B�:�E�;	�L�=�V�>�Y�?�\�@�c�B�d�C�e�D�k�F�m�G�p�H�w�J�x�N�{�R�}�S��T��V��Y��[
��\��]!��^�A�`�C�a�F�b�M�d�e�N�g�S�j�U�k�X�l�_�n�`�o�a�p�g�r�i�s�l�t�s�v�t�y�z�z�}�{�	�|�
�}����������� ��&��)��9��S�	�Z�
�]��`��g��h��i��o��r��u��}��~��������
�����������,� �3�!�N�#�P�$�S�%�Z�&�\�'�]�(�c�*�e�+�h�,�o�.�p�/�q�0�w�2�y�3�|�4��6��7��9��;�$�=�&�>�)�?�0�A�1�B�2�C�8�E�:�F�;�K�@�M�A�N�B�P�D�Q�E�T�G�U�J�V�Q�X�R�\�W�^�]�_!�i�`��b�
�c��d��e��g��h��j��m��n�!�o�(�q�)�t�/�v�1�w	�4�x�>�{�?�|�C�}�^��_��b��c��h��i��j��p��s�	!���$��'�
�*��2��3��4��:��=��@��H��I��Q��R��T��Y� �Z�!�[�"�^�#�_�%�b�&�e�'�m�(�n�)�v�*��+��,��-�*�.�-�/�0�0�7�1�9�2�A�3�D�4�G�5�N�6#�P�7�t�9�v�:�;�x�<�y�=�~�?��B��C��D��I��N�	�P�
�Q��R��S��U��V��W��Y��\� �]�"�_�$�`%�0�a�V�c�X�d�Z�f�[�g�`�i�a�j�b�l�g�n�i�o�k�q�r�s�s�v�y�x�{�y�~�z��{��~�
������&��'��*�-��1��2�	�3�
�9��<��K�
�R��e��l��n��q�	�x���������� ��%� �&�!�'�"�*�$�+�%
�2�&�=�'�D�(�_�*�a�+�d�,�p�-�v�.�y�/�|�0��1��2��3��5��6��7��9#��:�=�<�?�=�B�>�C�?�H�A�I�B�J�E�N�G�P�H�S�I�T�J�Y�L�Z�O�_�R�a�S�d�T�k�V�l�Y
�r�[&��\�'�^�)�_�+�a�2�b�4�e�:�M�f�N�g
�T�i�b�k!�c�n��p��q�
�r��t=��u�R�v�m�w��x��z�	�{��|��}��~�;�4��p����
��
���%���=��?�	�B�
�I��J�
�K��Q��R��T��Y��Z��]��_��a��d� �k�"�l�%�r�'�t�(�w�)�~�+��,��-��.�	�/��0��1��2��7��9� �;�$�=�%�@�)�A�*�C�,�D�/�E�6�G�7�J�=�L�?�M�B�N�I�P�J�S�P�T�S�U�V�V�b�W�h�Z�i�[�l�\�m�]�r�_�s�`�t�a�w�b�y�d�{�e�~�f�
�g��h��i��j��k��m�&�n�)�o�,�p�3�r�4�s�<�u�>�v�A�w�H�y�I�z�J�{�P�}�R�~�T��[��\��]��^��_��`��s�	�z��|���
���
��������������$��*��1��3��4��:��<� �?�"�E�$�F�%!�G�&�i�(�k�)�m�+�s�.�t�/�u�0�y�1�z�3�{�5�~�6��8��:��=��>��?��A��C!�5�D�W�F�Y�G�\�H�]�I�b�K�d�M�j�N�m�O2�p�P�#�Q�:�S�<�T�>�V�?�W�D�Y�G�Z�M�\�O�V�]�^�^�e�_�x�`��a��b��c	��e��f�1�h�3�i�6�j�=�l�@�m�F�n�Y�o�`�p�{�q+�~�r�*�s�0�u�2�v�5�w�6�x�y	�:�{�D�|�G�}�J�~
�Q��\��^��a��h��i��j��p�	�r��s��w��x��{��|��~������	� ��"��#��$��%��'��(��)��*� �,�#�-�&�.	�.�/�8�1�:�3�;�7�@�>�A�?�B�@�C�B�E�C�H�D�O�F�P�G�Q�H�W�K�X�L�[�M�\�O�S�`�U�c�Y�f�Z�i�[�p�]�q�_�x�a�z�b�}�e�~�f��i��j��k��l��n�	�o��p��r��u��w
��x�'�y�)�z�*�{�0�}�2�~�5��<��=��>��D��F��I��P�	�Q�
�R��X�
�Z��]��b��c��d��j��l��o��v��x��~��� ��!�
�#�
��$��&��'��(��*� �+�!�,�'�.�)�/�,�0�3�2�4�3�5�4�6�5�:�7�<�8�?�9�D�:�E�<�F�=�G�E�J�F�M�G�T�H�V�I�^�K�`�L�c�O�g�R�h�V�k�W�l�Y�n�Z�o�]�p�^�u�`�v�d�{�f�}�g��h��j��m��o��p��q��r��s��t�#�u�&�v�)�w�<�y�>�z�A�{�B�|�G�~�H��I��N��P��S��Z��[��\�	�b�
�e��h��o��q��w����������������"��(� �0�!�1�"�2�#�8�$�K�&�Q�'�T�(�W�)�^�+�_�,�`�-�f�/�h�0�x�1�~�3��4��7��9�	�:�
�;��<�#�=�*�>�E�@�G�A�J�B�Q�D�R�E�Z�F�]�G�`�H
�g�I�r�K�t�L�w�M�~�O��P!��Q�"�S�$�T�&�V�'�W�,�Y�-�Z�.�\�0�^�1�`�3�a�4�b�5�c�7�d�;�f�<�i�B�k�D�l�G�m�N�o�O�r�U�s�X�t!�h�u�
�w��x��y��{��~���1��7�"�J��m��o��r��y��z�	�{�
�|���
����
��#��+��2��5��8��?��^��d��f��i��p��s��y�!��!�.�"�I�#�d�$�s�%�u�&�v��'��)��*��+�$�-�'�/�+�2�-�3�.�4�/�5�6�7�7�;�<�=�>�>�A�?�H�A�I�D�O�E�R�F�S�G�T�H�[�I!�_�J��L��M��N�
�P��S��U��V��W� �Y�!�Z�"�[�(�\�+�]�:�^�A�_�D�S�`�T�a�Z�c�\�d�_�e�f�g�g�h�h�i�n�j�q�k�t�l!��m�"�n�%�o�(�p�/�r�0�s�1�t�7�u
�F�v�Q�x�S�y�V�z�]�|�^�}�_�~�e��x������������&��'��(�*�	�.�
�1��4��;�
�?��E��G��J��Q��R�!�S��u��w��y������� ��"�	�#��$��&��'��(��*��+� �,�'�.�(�1�.�3!�A�4�c�6�e�7�g�9�n�;�o�>�u�@�w�A�z�B���D���E���F��	�G���H���I���K���N���O��8�Q��:�R��=�S��D�U��E�V��F�W��L�Y��N�Z��Q�[��X�\��\�]��b�^��v�_��|�`���a���b��1�d��3�e��6�f��=�h��>�i��?�j��E�k��H�l��K�m��W�n��]�o��q�p��w�r��y�s��|�t���v���w���x���y���z���{
���|��#�~��%���(���/�!��2���T���V���Y���`�	��a�
��b���h�
��j���m���s���t���z���|������������
���!��!� ��C�"��E�#��H�$��I�%��N�'��O�*��U�,��W�-��Z�.��a�0��b�1��c�2��i�3��l�4��{�5���6���7���9���:��!�;��(�=��)�>��*�?��.�@��/�A��2�B��I�C��d�D��g��n�E��u�F��v�G��|�H���J���K���L��#�N��$�O��%�P��+�Q��>�R��E�S��`�U��b�V��e�W��l�Y��o�Z��u�[��x�\��{�]���^���_���a���b���d���e���g���h���i��"�j��%�k	��,�m��6�o��8�p��;�q��B�s��C�t��D�u��J�x��K�y��N�z��O�{��T�}��U���X���Z���\���_���f���g�
5��m���#���%���(���/���0���6���8���;���B���C���D���J���M���P� ��W�"��Y�$��_�%��f�&��n�'��o�(��w�*��y�+��|�,���.���/���0���1!���2��A�3��D�4��[�5��^�6��a�7��i�8��j�9��r�;��t�<��v�>��w�?��|�A��}�B��~�C���D!���E��:�F��=�G��@�H��G�I��I�J��Q�K��T�L��W�M��^�N��`�O��a��c�P��g�Q��j�R��m�S��t�U#��u�V���X���Y���Z��%�\��&�]��'�^��-�`��/�a��2�b��6�c��8�e��9�f��:�g��@�i��B�j��E�k��L�m��M�p��S�q!��g�r��	�t���u���v���w���y���z���{���}���~��!���(���)���*���0���2���5���<�	��=���C�
��F���I�	��Q���[���]���`���d���f���g���h���k���m���o�	��r���|���}� ���"
���#���$���%���'���(���)��'�*��(�+��)�,��/�-��2�.��5�/��=�0��>�1��F�3��H�4��K�5��O�6��Q�7��S�8��T�9��Z�:��]�;��`�<��g�=��k�>��q�@��s�A��v�B���C���E��
�F��
�G���I���J���K���M���N��!�O��(�P��*�Q��+�R��1�T��3�U��4�Y��:�[��;�\��<�]��?�^��A�_��D�`��G�a��N�c��Q�d��W�f��Y�g��\�h��c�j��d�k��eQ
���m�\��.��FV�]�L�d�5q�
��(��"�*�/�3�6�9�C�H��!�%�,�0�7�=�A�F�T�V�[�`�|�Q�Z�^�m�~�{h3�F�N�s�K�F�:��O�3�f�s�t��;��6�P�9�qO�u�I�M�S�W�Z�?�\�_�a�e�g�o�@�q�A�t�B�w�z�y�{�~�/��T��C�F����G�
�g������%�G�T�K�(�L�P�S�4�k�a�W�c�e�h�j�l�X�n�s�w�0�{���
�>��[������\�� �$�*�-Q���g�l�wN�Y�h��K�V�$��>�i�x�o�*�Ta	�:�p �Dq���B�e��CPK
!<�e�Ʋ�1chrome/pdfjs/content/web/cmaps/UniKS-UCS2-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�UniKS-UCS2-Ha �{�}�z�[�x	�����k��
��������~��PK
!<�W���f�f2chrome/pdfjs/content/web/cmaps/UniKS-UTF16-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE���������!A��
wa�r�YM�Q
��t��J�v�O�e�z�?AZƅ	�1�:�6	�g�r�3�4	�7�6�9�8�9�8�=�8�C�8�5�8�_�s�#�lO@�>��P
y8�`
)�]YX�	�l�
�|�kf6�8m+]\]>6EYT(�V8?3
+&C	IW"V�m�`A-��S�^K�b3�h�I9�?�3�V �N2�B1(�C�d�Z!<'�g
���z�_I�o�b
/A
 5�n�)�
Q%�	H3:%>)<'(A8% �(F�i
���jebm��	5�&!�A �A!/�<�!@:B�z�;�v�(��o�F�y�:�)�&�}�=�0�l����U�:�3�w�D��Q�F�i�4A%�� V�)��-��_?�
�&H"5@Q/B�h�W��4��X�'�6�+�L�_A	/O�V�j�I�J�w�N�Q�� Q
/Z�a��V��
�y�G�j�'�p�d�4�bA/i��]F�>�Q�P�C�"��v��G�fA/u�tA/j�0��gQ
/�y�A�`� �;�j�J����A/��<�U�RA/���,�o��X��9A/��L_�)�Q���Y�~}�g�"�#�H�YK�.�+��"�%Q
/��q�6�E�
�~�Y�X�c�Y)Q
/���x�(�y
��~�=�pA0l��g�h�7�6b��A/Ν6�F�i�'�SA
/ʹf,�?�x
;:�E�h�4�=V�|�9	��Q
3‚� �
r�-�D`q�lA3؂A3τc`A�|N�<�f�&�
��}�X���o�	�l��v��.�a�^�U�V��'�	�N�K�|�u�_�F�p�k�5��u�x�v��<�e�&�B�L�i�$�1l�4�1��/�@�&�o�:�5�P��>{�%�J�O�U��A�@�l�W��=�L�/�.��#�f���%�\�
���sS�6�O�0�Q�y���d��n��'�?�8�U�%��#�8�p�Y�f��!��%���r�@�/�u�!�b�(�m��P�)�$�G�F�S�4�B�"�Q�i��q�^�=��/�_�
�K��s�$�$�q�A�#�P�+�9�x�z��M�U��}�`�Z�g�~�g�M�N��|��o�V�
�I�D�!�hb�Q�6�Q�f�i��7�\�_
�j�_�x�B�"�L�o�9�A��]�{�b�3�N�U�a�0�s�>��w� �(�m��N�"�=�-�q�j��7�
�%��j�w�X�H�t�t�?�_�
�#�/�p��Q�i��D��Q�X�%���C�Y�^�,��2�	�+�C�b�%�t�S�4�D��F�~�G�L�'�M�x�c�4��P�Y�;�>�k�@�#	�v�Ah�8�E�U�b��%� �n�Z�'��k�D����6�k�|��v��u��n�?�^�&�+�O�b�{�f�n�]�o�D�#��6�4��	�v��L�M�S�
��p�P��L�W�D�U�8�5�T�y��
���;�;����q�C�\�
�`�|�A�A�_�R��L�?�*�<�m�-�|�,�J�E�d�%�/�T��]�h��*�+�O�T�n�1�:���o�j�n	�	�1�A��@�?�1�\�(�!�W��`�`�X�?�J�z]�x�1�z��Z�?��6�E��w���0�a�f�3�l�;
�G�P�e��a�J�� �.�)�)�F��-�Y�X�Y���B��7�,�A�M�p�q
�&��F��v�[�:�&�3�"�	�]�X���gJ��{�l��_�v�y��2�E�8�;�}�<��n��Y�f�r�v�s�x�0����PE�m�=�(��{��u�}�x�s�2�#�x	]��~�w�u��.��u�0��a�[�|�0�&�{�]�b�&�W��y
�s��a�2�q	���%�i����_��;�
�D��H�S�$�8�{
��0�F��&�1�P�	��	�^��;�p�s�
�o�\�;��y�d��e��m�P�a�5�H;���l�_�#�

�{�z�@�3�H�+����h�g�d�l���X�F�E�Q��-�G�G�b�E��,�+�z�2���g�>��
�_�0�7�Z�%��e�b�!�L�K��
�A�5��1�m�*�@�
�"��C��E��#��O�J�c�}�Q�N�J	�E�>��(�
��n�i��>�i��V�K��A��j�O�%�J�~�c�|�	��x�}�
�|�9�f�6�s�A��Y�A�"�2���k��"��S��&�R��p�l�!�N���<��i�D��E�f�a�o�M�g�J��]�S�Z�=�,�s�t�*��4�
�jp�=�/�l�/��[��Z�l��_�z�q�p�.�|�a	�e�TJ��=
�W�!�d�
�@�9N�'�,�?�G�:�1��C�	+�3���k���`�f�F�c�6�I�F�^�w�	���b�<�<�b�y��,�a�2�p�9�u2�d�A�(�t�s��I��s�"�m�P�}�T�	�z�X��1��R�?�5�>��d��X�@�S�S�b�:����j�n�I��]�O���?�!�|�7�V�-�I�#�^�Z��X�7�(��s�
��q�;�6�`�4�!��	�I�H��^�U�:�]�K(���_
�D�[��/�.����4�C�Q�S�t�r�
�w�o�d���B� �;�.�9�d�<�[�&�Y�T�o�y�&��6�q�R�I�0�;�D��n�D��
�,	�W�E�3�Y�<���>
�f�c!��{�j�r����b�,�Y�B�1
��M�b�F�*�q�z�`�3��
�g�p�M�=��r�5�H��L�
��/�^�t�[�.�M�_�U�[�X�E�w��`�]�x�{��[�)�\���Q	�d�Q�e�:�/� �U�h�H��f�A�@��2B��B�!�X�6�Q�"�a�>�s��Y����F���o�=�D��3�y�j�n�]�0��)�`�K�H�%�_�p�1+���!��r�7�0���i�H�>�u�<��9�>�k�9���S���l�n��-�7�^�?�
�C�R�^�q�p��5�N�b��Z�I�W�@�&�>�D�N�c��$��d�%�
�'�,�v�u�b���6�[�N
�	�!�L�	�H��f�e��r�k���$�K�U�>�}t	�8�'�T�q�l�}�~�C�\�;�L�]�5�n�g���}�A�V�N�N�[�=��%��<�s��X�g�3�F���r�D�A�9	���$�~��f����u�	�|�7�i�b�(�?�1�0�W�5�2� �U��~���'�[�Z�h�+��4��u�%�5�+�C�H���9�l�y����|�/�u��G��v�t�o	��o�	�8�a�h�8�%�=��|�?�`�+��A�Q�.�Q�L���S�p��?�o�f�$�1�*�u�Z�+�B��_�o�@�[� �V��:�E�j�\�8�a� �u��B�V�}�%�Z��&�K���e�h�7�-�N��=�DXY�{�g�f�6�Y�K��f�f+�a�'�!�:�A�.�/�{�V�~�
�a�<�w�h�	�f�Y�&��t�i�o��x
��e��
�	�z�=�4�p�3�L����M*�|�|��%�*�G�\�I�Y�v�
�X��5��u�6�S�.�/�L�R�w�q�(�S�u�"���9�8�'�V�~��"�r	�5�$��m�c�p�Q�j
�q
��l�'���$�\��e�F�3��y�M�,�U��F�O�.�Y�Z�f�D�1�6��o�l�?�n�F�@��a�1�P�9�;�Y�!�D�+�j�1�i�2��F��P�E�n��E�J��N�@�+��x�3�
�B�A�B�?�7��?�[�\�B�[�C�3�g��'�2�F�{�}�R�?�R�~�;�4��\�r��a�q�$��V��c�B�C��1�~�C�w��|�+�%���a�l�A�R��]�*�y�A�h�?�f�;�b���9��W��x�[�'�p��(�v��~�1	��z�C�(���>X�y�)�U�D�E�U��z�;�W�V�L	���x��<��7�(x�]�e�@��9�Z�@�?�y�w�
��	�]�0���~��#�T	��i�.�g�a�4���� r�>�oX�}�A�:��u�<�\��	�r��M���>�]����,��V�{��Y�B���+��Z
�2��!�"�(�~�b�C�6� �u�J�/�O�\�E��E�������E��%
s�5�O��?� �`�?�YI��:�q�N���Z�m�h�q�!	�+�4�6��|�9���!�:�{�v�j�p�u�^�`�c�P�$��^�4	�a�l�x��X��s�*��M��0�[~��i�3
���o��g�N�[�3��
�=�J�q�����^�B�I�_�I�4�>�p
�
�P�9� �
�x�B�m�����I�4�s�3�.�Q�]�^�H�e��=�X�)�:�E� �M�0�I�U]�o�X�^�#�T�G�k��`�(�o�%�]�2�l�7�h�P�{�}��G�p�!�A�<�-�T	�V�M�s�(�U�#�*�@�;�`�U�x�S�]� ��\�8��w�?��]	�P�'��w�A�D�Y�X�]�i�r�S�H�>�K�M���e�>���+�~�9�"�~�|�y}��:�9�7�L�X�_�z�G�o�h�p�u�k�_y�-�|��I�6�m�j�m��+�}�0�;��K�d���
��O�6�G��x�-�&�K�P�?�t�m�"�Q�r�\�4���v��e�x�M� �e�p�Z�4�i�*�F�}�h�x�m�T�f�O�O�R�{�6��'�H�+�2�p�-�|��Z���3��:�I�~�)�s���\�8�mU�2�%�]�D�'��|��l�<��M�+�r�<�#�d�H�~���p�$�f����c��PS��^��W�V�,�#�
�C��L��j�9�T�:���t�G�6�m��)�p�&�y�i�|�(��k�0�������n�0�+�F�R�	�,�!�R��	���mh�j�h�o�g�B�i�6�G�K�>�V�>��z�e����(�h��3�-��\�s�	�|�s��K��z�J�o�B���t�t�S�c�J��L�	����1�[�O�>�x
�+�:�I�&�L�5�X�7�!�7�m�	�l�
��|�=���'�5��}	�T��(�W�`�^��I�r�s�N�S�p��w�N�Y�%�!�i�	�p�I�o�
�M��d�2��w��1��T�{��@�>���V�A�\�O��j�9�F�k�<���)�|�J�y
�q�b�S�v�*��b��g�@��'�n�e� �8�o�1���^�e�X�X�U�L�]�h�O�
�#�v�'�n�	�c�p�_���@�!�r�
�b�B	�#)�X����Q�
�'�N��)�5�P�$�/��x�6�W�V����u��[�-	��)��5�S�J�&�X�
�	R� �
��h�U���Y�
�P�9	�e
�w�P�N��y.�8�W�F�X�k�`�a�b�9�{��|�V�%��S�hv��l�u�*�{ �o�4�y�>��A�4	�7�
�w�p�I�^���-�*��\��$�N�u�!�B�	�(�;��I
�x�C�Y��V�����E�l�;�a� `�6�J�C�^�� ��u�a�E�W�2�A�E�&�V�v�Q��pg�&���������(	�9�$�F�^��C�V� ��c	�r�R���N�B��u�t�Z�h�0��l��=�
�T��%��I�3�4�!�B	�\�
�i���z�i�8��C���*�m�a��� �H��F�s��!�y�\�!�=�x�n���y�0�]�%��h��,��#�L�y�b�c�3�e�(	�M��>�����'{z�$�<�C�B�2�
�m�H�y�&��K�M�j���R��P�J�A�>�_�z��r�U�F�M�'�S�R�S��h�B�U�f����P�[�>�W�8�=�,�@�c�|�8��+�Z�F�-�5�p�
�#�^��C�a�^�i�f�BG���^�	�h�C�B�C
�"�)��@�(�x���l�6�_�v���Z�C��+�b�'�r�0�@��y�l�O�p��2�%�f�#� {��/�a�6�O�S�p��9�f�#�t
�m�B�P�C�8�^
�a�k��:�@
�'�E�`����F�L���d�,�S�^�l�?�#�`�l�V�5�*���v���:�<�x�C�4��;	�n
�B
���b�?�k��E�L�9�O��i�d��g�^��5���b���:�G�,���%�
�$�a�1�T�Y��8�9���1��%��l�_�O�L�K�`�U�u�Z�i�@�u�=�+�|��w�`�G��y�I�w�L��:�Q��p�s��@�+�4�{�)�^�R�i��-�h�i�J�
F�(�o�J�s��8*�z�x�%�=�Q	�Q�>^����iJ�O�|�>��N�
	�F�(�W�Q�
�Y	�r�s�<�>�i�6�N�g�+�b�N��L�]�v����-��5�-�P�R�C�6�N�E�P��U�%��m���d�F�)�	�Y��A��<	��,�#�9�+�f���V�2�I�.��&��3�l�$�K�9
��o�
�
�\�]���_�_�t���/�^�}�S�$�y�T�.U�}�b�r�e�	��3���A�B�j�x�y�,�Z��l��2�P�0�i��U���CB�|�i��4�9�,�J�q�2�X����<�p�6�s�#�p��K�e� 	�s
�L�l�B�U��$�O�"�{��
�U�<�|�m�k�$��h	��B�M�|�Ni�7�S�T��g��`�3�r
�n�2�_�|��r�1���}�=�"�E�
�?�u
�N�K�n�r�s�2��+��E�F�I�x�e�=�b�C�f
�N�
�$�Y�*��y�!�F�=�k�"	�o�R��[���.��,�h�{�<�]�P�x�!�{�T�U��I� �S�y��r�S�@�5�T�$�c���`�A�/��w��.�-�8�	�j	�s�b���}�j�c�z�3�S��z�-�m��;�#��h�'��]��!�(�v�>�q�F�_�i�B�U�|�&�8�E�Bx�/�C�r��1�Y	�Z��7���7�?�D���v��O�5�l�=�8�9�O�t��s���\�.�q�8�>�u�t�K�j
��/�<�Pg�*�	��W��Y�)��o�=�Z�a�s���F�L�
�[�`�u
�J�)�^���T
�;�"�3�i��o�l�(�~�,
�s�F�S�B��k�v�l�M��+�j��T�U�2�X�
�f�U�X�D�;�}���B�GG�@�u�@��a�J�B�\
��H��V�W��b�*�Q�d�T�o�>�b��^�Y�+�<��B��Q��j�=�.�+�I�R�w�N�t�!�/�J�!�o���[��Y��J	��;� �g�[�x�3�^�7
~�"��t��B�#�1� �s�;��5�J�;Y�& �c�
�?
�%��s�o�
�d�\�
�
�9H�~�W�x�
~��N���|�)�,�2�J�����z�h�^�#��A�Z�g�I�
�@�A�0�#�B�t�m�]��Z�W�P�#��=�R�c�O�(��W�/�P�1��s�0�S�~�q�o�B��t���7�8��j�c�p�	�.�X
�0�1�1��p�-�n�	�1��p�S�t�gI�
�K��%
�J�z�]�^�G�:	�}��v�g��V�1�S�h�
�D�U�:�R�c�z�o�^�c�`�\�I��8�-#�|��o����F��\�+��j�(�C��2�%�s�0�c� �w���3�f�9���~�9�1�A�Z�"�-�T�;�\�Y�-	�/
�B��"�#�	�h�%���t���[�<���A
�m�*�o�|�� ��P�E�G�0��\�6��T�.�'�\�
�.	��Q�3�F�L�9�p�d�/�T���i�*��X�'�2�,�w�#�

��!�0	�1�U�4�� �S�H��m�%�f�_�J�q�J�<�4�9�z��.�p�7�P�0�&
�?�,�p��c�L��K�|�I�@�[�n�
��Z�t�3�,L�Y�9���M�a� ��\�,�!�/�4�i��l��3�Q�8�Y�z���z�J�m�_�I�(��M��2��G�D�c�i�E�K� �8��
�&�m�,��z�RC�r�\�i�/�4�K�\�/�B�"� ��(�P�s�a�>�>�.�q�<�g�)�o�
��L�	�\$�v�U��9�@�
��L�E�n�{��F�|�M�\�q��7�	�?�^�>���<��}	���
�Q�P'��I]��7�_�*��)�_��Z�
���X�#~�H�{��D�?�2� �y���@�	�5�J���u��t�p�w��T�3�]��|�A�O�L�f�Y�_�R��q��1��G�:�Q�r� �m�&��,�m�~����}M��{���
�`�
�^�w�2	�rc
��-�H�(��V�_�t�i�@�S�C��=�J�K�V
�Y�h�o�n�o�T�;�d�|�P�k�%�t�.�g
�,��,�o�'�.�w?	�V�%�O?�p�!��B�y�:�k�v��F���?�F�J��C�F���
������b�5�R�=��f�/��=�&8���x�2�=�
��j�	��x�#��
�t�q�N���!�w�0�X�z�p�a�s��6�Q�P�9�i�"�U�C�X�9� �$����(�x�<�'�o
�^�y�d�w�4�x�c��H�@�z�O�|��L�R�K�1�R��O�A�:�O�S�=�d�j�L�	�fq�f�k��}��F�9�M�F�K�P�m�K�\��.�K�[���v�V�C�W��a�r�t�y�W�"��D�_�J��0��d�
���U�:��$�s�{�j�(�]�k��V�s�E�:�~�1��	�z�K�4�@�Z�[�Y�;
����S��d�=�1��q�4�>�	�f�M�$�U
���K�D�q��a�L�X�"�
��p
�}�(�V��M��p�k�b�C���I�1�$��s�p�&�Z�p�M�H�n
�W��z�k�=�/�.��~���p�:�o�@���/�X�0�q�<�o�f�
�s�N�o�n�p�i�H�w�A�V
�{�}��T�8
�&��}�	�`�L�{�O!�,�w�Y�u�\�0�y�H�i�L�
y�>�G�Q�x�|	�a
�4	�W�K�B�q��/9�j�B��?�	����&�/�y�&��p
�Q�z�/�8�\�I�:�W�}�h�h�C�c�2�y�4�
�/�'���O�7�d��G��#������g��p�#�>���m�7�7�� �i�,�N�a�S�]�L�M��j�%^�@�`�C�T�	�y��i�@�7�4�Y�B�-�r�(��R�����A�y�X�w���I�>��;�(�s�r�l�Q�7�x��8�%��r�v�%�Q�a��GS�H�K�N��Y�1�0�S�'�(�I�b�i
��*�7k��"�W�b�Y�J�\8�=�3���}�3�0�m�/
�R�!�c�B�c�<�W�i�b�/�,�|��6�@�7�
�(�a��9�_�z��$>�Y�X�S�9��s�>�,�E�9�B�9��K�Q�=�(��]
�B�c
�F�!�o�J��p�@�-�W�^�}�j�U�z�u��8�u��x�e=�=��9�d�q�n�E+�^�S�6�
�%��"�;l�%	�d�]�>�S�z�L
�5�	�(�Y�
�;�$�|���T��=�Y�S�
��
�
��m	��5�
��P
�s�Z�o�@�G�F
�4�x6��R�Z��0�n�T�'��l�sV��|��s�1�0�����
�0�%��	�=�a�P�W�v�u�>�;	�'�V�n��j�a�F�i�,�	��K��x�|�}
�S�7�(�?�@�g�U"



3

(
"

*
#




;



O
'7"&

&












(

&
#3(

*


(
#+B>
Z3

 







&
	(&3

,

&$"



















&
&
'
(
7&
&


"




&



(&






&


&






O

""
(



&













A'��~*,F
&
5P( Q�7V,J

"Dv*Dr
A���DA�I�,5Z
Cp"Jp
Q�
*"A%���x%<

DzJvM�7^Q�,B
5X""J�"A0�:�67X$,5\"$*(Q�($S�$$5V
J|
 *#>
Q�(A��x
"Jz
 &(>A
���%h�4$.R

"q�B(A���A#���I	"$,>$(<
Q~("Jx*"5Xv�Bo�BAL��5@d
"#4

$( *$$ Qv$5@
"9^
"Hv
"5X5PQ�7X,L

$S�o�DA ��uDn
"Br$7b$Q�5XQ�5R5X$$S�$&7ZA��=Q�m�6"$J�
$Q�
L�5ZA���
7V

"S�$5T(A!�n��i
o�D&7`"$J�
$$7R(,&(*Q�(5NQ
���m�\��.��FV�]�L�d�5A��(�s(�,LQ�Z�^�m�~�{h3�F�N�s�K�F�:��O�3�f�s�t��;��6�P�9�A)�~�W�1�8
�_�n��|
�	��
�l���)�D�_�jA���/�7� ��w�A���g�Q���g�l�wN�Y�h��K�V�$��>�i�x�o�*�TA�.�C�4��]!�� �D�)�ba ^a�u��K�]�W�h�o�F�|��$�Cmrt�ui�M�O�N�m�q	�M	�C�+�-��@�67��<�:�X�]�;�!�C4�/�1�3�5�7�9�;�=�?�A�CY�i*�f��^�}�e	xv+R�S
U�&:2�f)�q�D�'��s�d��`�~�K	�U�Q�g��	�i�x��t�x��G�-�>�A�E�M�T�V�Y6�b�e�h�j�n�t�v�y�}��"�
�
�����$�&�*&�3�6�:�>�@�D"�H�L&�R�V�X&�]�`�c�f�i
�o�q�u�y�{�~6�
���-�#��� �#�&�(*�.&�2
�6�:�>�@�D�J&�M
�S&�Y�]
�a�e�i�k�q�v�x�{6�����
�
�����"�"�'�+G�4
�7�>�A�CR�M
�O�R
�U�X�[�^!�d�i
�n�r�w�z���>�
���
����$"�(�+�/R�=&�A�Ez�X�[�^&�c�f�h�j�n�r�t�w�|�~>����	���>��
�"
�&S�-&�0�3�5�;�@
�D�H�J�N�T�V�Y6�^�b�e�g�l
�p�t�v"�}�R�&��B�!�(�,
�0�4�7�9�;�?
�C�F�K�N�Q�V�X�\6�`�e�h�j�o�q�t�x"�}���	Z���	�#�'�7�=�?�D�I�N
�S�W�Y�]6�a�d�g�j�l�o�q�s�v�{"�
�B��&����"9�(6�3�7&�:�?�B�E�J�L�O�T�V�Y6�\�_
�b"�g�i�k"�n�rz�x��&��

������� �"�%�)&�2�7�9�;�=�A�E�G�J�N�P"�W
�]
�b-�k�p
�s�w
�{�~���	��
��� �"&�&�)�+
�1�3�6�8�:�?�A>�D�I�KZ�Q�T�W
�ZF�c&�f�jz�s�y&��
��	�������� �%	�*�/�1�3�7�@�D
�H�M�O�S�U�[�]�_�b�f
�l�p�r�u�{�
��
���
�����!
�$�(
�,�0
�5�:�=�I�L�O�R�W�Z�^�`�d�h�j�m6�w�|�����"����.�$�)
�-�1�4�7B�>�B&�I�M&�Q�T�W�Z�\�^�d�f�i�m�o>�u�y�{Z��
�F�&�����'�+�-�/�5�7�;�?�A>�J�N�P�S�WB�a�eR�p&�v�zR�&��&���
� �$
�(�,�.�16�4�7�9�;�>�B�I�K"�O�S
�Wn�b�fB�p�t&�|�&��
�������>� �%�'�*�.B�7�;z�H�LB�S�W&�_�b�e�k
�m�q
�u
�{�}����
6������ �""�(�,z�9�<�?�
�S&�V�Z
�^�c
�g�k�m>�r�w
�{�
���	"��
�� �%6�16�>�C�G
�K�R�U�Y�a
�d�h�g:� �D��ag�s�'��5�O����[�\a2_��Z��"���
�j�����"�*�6�9�C�!�%�,�0�7�=�A�V�[�|��I�M�S�Z�a�g�q�t�w�{�~���
���G�L�P�e�n�s�w��� �$�-av}��u�ua\��h�e�M�w�
a���taz��Ua^|�-�
�2�W�9�S�?�0�SaaX�-a���]afC�g�u�Gaq��qa z�/�:�q�!�)��L�
�,�Z�D�:	���a 9�$�j�?��)%�#�/�2�W�	�aa!��@a$��Ra�_0�j�j�U��r�u�w�y������	��"�(�+�.�A�C�F�L�N�Q�T�\�c�e�m�p�r�u�x��	�������%�+�.�1�9�;�A�C�F�O�U�X�[�d�l�n�p�v�{�}
����#�+�-�0�8�@�C�F�Y�[�]�e�k�������!�$�&�(�+�3�9.�?�o�r�z�
��
������&�*�,�G�K�T�X
�^�i�o��������9�;�>�F�K
�R�]�c�e�h�o�q�x�{�~	���*�/�5�8�V�X�[�d�k�m�u�w�z���
�
���2�;�?�D�I�K�N�V�\�^�a�h�j�l�o�r�	�����
� "�+�N�Q�T�\�d�f�i�s�u�y�{�}����&�A�D�G	�N�X�^�e�h�n�p�s�z�~������$�*�,	�0�3�5�8�@6�F�~����
����!
�$�/�5�8�O�Q�U
�\�^�a�d�w�z�
�����'�B�D�G�P�V�i�p��
�
��� �#�&�-�1�7�9�<�C�I�P�d�f�h�p�u�w�z���	�=��R�T�X�]�b�d�g�o�u���+�-�0�<�B�I�\�w2�z�-�/�2�8!�<�^�y�|��!�	�+�-�/�7�?�B�E	�L�V�Y�\�e�k�m�p�x�{�}���
�!��A�C�F�N�S�U�X�a�g�i�l�t�z�}�
���� �&�)�9�S�Z�]�`�i�o�r�u����
����,�3�N�P�S�Z�]�c�e�h�q�w�y�|���$�&�)�2�8�;�B�E�G�J�R�W�]!�i��
�����!�)�/�1	�4�?�C�_�c�j�p�s!��$�'�*�4�:�=�@�I�R�T�[�_�b�e�n�v����*�-�0�7�9�A�D�G�N#�P�t�v�y
���������� �"�$%�0�V�X�[�b�g�i�k�s�y�{�~��
���'�*�-�3�9�<�K�R�e�l�n�q	�x����� �'�+
�2�=�D�_�a�d�p�v�y�|�����#��=�?�C�J�N�P�T�Z�_�a�d�l
�r&��'�)�+�2�4�:�N
�T!�c���
�=��R�m���	���;�4�p��
�
�%��=�?�B�K�R�T�Z�]�_�a�d�l�r�t�w���	����� �%�*�,�/�7�=�?�B�J�P�S�V�b�i�m�t�w�y�{�~�
������&�)�,�4�<�>�A�J�P�R�T
�`�s�z�|���
�����$�*�1�4�:�<�?!�G�i�k�m�u�{�~�������!�5�W�Y�]�b�d�j�m2�p�#�:�<�?�D�G�M�O�V�^�e�x���	���1�3�6�=�@�F�Y�`�{+�~�*�0�2�6	�:�D�G�J
�Q�\�^�a�j�p�s�x�|�~��	������ �#�&	�.�8�;�C�E�H�Q�X�\�`�c�f�i�q�x�z�~	���	���
��'�*�0�2�5�>�D�F�I�R�X�Z�]�d�j�l�o�v�x�~���
�
�����!�'�)�,�6�:�<�?�G�J�M�T�V�^�`�c�h�l�p�v�{�}�������#�&�)�<�>�B�I�N�P�S�\�b�e�h�o�q�w�����"�(�2�8�K�Q�T�W�`�f�h�x�~���
��#�*�E�G�J�R�Z�]�`
�g�r�t�w!��"�$�'�.�1�5�7�<�B�D�G�O�U�X!�h�
�����1�7"�J�m�o�r�|�
��
��#�+�2�5�8�?�^�d�f�i�p�s�y!��.�I�d�s�v�����$�'�+�/�7�<�>�A�I�O�T�[!�_��������"�(�+�:�A�D�T�Z�\�_�h�n�q�t!��"�%�(�1�7
�F�Q�S�V�_�e�x������(�*�.�1�4�;�?�E�G�J!�S�u�w�y���	����� �(�.!�A�c�e�g�o�u�w�z����	����������8��:��=��F��L��N��Q��X��\��b��v��|������1��3��6��?��E��H��K��W��]��q��w��y��|��������
����#��%��(��/!��2��T��V��Y��b��h��j��m��t��z��|������
��!��!��C��E��I��O��U��W��Z��c��i��l��{����������!��*��/��2��I��d��g��n��v��|��������%��+��>��E��`��b��e��l��o��u��x��{����������������"��%	��,��6��8��;��D��K��O��U��X��Z��\��_��g5��m��#��%��(��0��6��8��;��D��J��M��P��W��Y��_��f��o��w��y��|����!����A��D��[��^��a��j��r��t��w��~��!����:��=��@��G��I��Q��T��W��^��a��c��g��j��m#��u��������'��-��/��2��6��:��@��B��E��M��S!��g��	������������!��*��0��2��5��=��C��F��I	��Q��[��]��`��d��h��k��m��o	��r��}��
������������)��/��2��5��>��F��H��K��O��Q��T��Z��]��`��g��k��q��s��v������
��
��������!��(��+��1��4��<��?��A��D��G��N��Q��W��Y��\��ePK
!<.}�<��2chrome/pdfjs/content/web/cmaps/UniKS-UTF16-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
UniKS-UTF16-HA �}�m ��m
,7�<a �{�l�x	����r�
�;�PK
!<���SgSg2chrome/pdfjs/content/web/cmaps/UniKS-UTF32-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�#C��
wa�r�YM�S
��t��J�v�O�e�z�?CZƅ	�1�:�6	�g�r�3�4	�7�6�9�8�9�8�=�8�C�8�5�8�_�s�#�lO@�>��P
y8�`
)�]YX�	�l�
�|�kf6�8m+]\]>6EYT(�V8?3
+&C	IW"V�m�`C-��S�^K�b3�h�I9�?�3�V �N2�B1(�C�d�Z!<'�g
���z�_I�o�b
/C
 5�n�)�
S%�	H3:%>)<'(C8% �(F�i
���jebm��	5�&!�A �C!/�<�!@:B�z�;�v�(��o�F�y�:�)�&�}�=�0�l����U�:�3�w�D��Q�F�i�4C%�� V�)��-��_?�
�&H"5@S/B�h�W��4��X�'�6�+�L�_C	/O�V�j�I�J�w�N�Q�� S
/Z�a��V��
�y�G�j�'�p�d�4�bC/i��]F�>�Q�P�C�"��v��G�fC/u�tC/j�0��gS
/�y�A�`� �;�j�J����C/��<�U�RC/���,�o��X��9C/��L_�)�Q���Y�~}�g�"�#�H�YK�.�+��"�%S
/��q�6�E�
�~�Y�X�c�Y)S
/���x�(�y
��~�=�pC0l��g�h�7�6b��C/Ν6�F�i�'�SC
/ʹf,�?�x
;:�E�h�4�=V�|�9	��S
3‚� �
r�-�D`q�lC3؂C3τc`C�|N�<�f�&�
��}�X���o�	�l��v��.�a�^�U�V��'�	�N�K�|�u�_�F�p�k�5��u�x�v��<�e�&�B�L�i�$�1l�4�1��/�@�&�o�:�5�P��>{�%�J�O�U��A�@�l�W��=�L�/�.��#�f���%�\�
���sS�6�O�0�Q�y���d��n��'�?�8�U�%��#�8�p�Y�f��!��%���r�@�/�u�!�b�(�m��P�)�$�G�F�S�4�B�"�Q�i��q�^�=��/�_�
�K��s�$�$�q�A�#�P�+�9�x�z��M�U��}�`�Z�g�~�g�M�N��|��o�V�
�I�D�!�hb�Q�6�Q�f�i��7�\�_
�j�_�x�B�"�L�o�9�A��]�{�b�3�N�U�a�0�s�>��w� �(�m��N�"�=�-�q�j��7�
�%��j�w�X�H�t�t�?�_�
�#�/�p��Q�i��D��Q�X�%���C�Y�^�,��2�	�+�C�b�%�t�S�4�D��F�~�G�L�'�M�x�c�4��P�Y�;�>�k�@�#	�v�Ah�8�E�U�b��%� �n�Z�'��k�D����6�k�|��v��u��n�?�^�&�+�O�b�{�f�n�]�o�D�#��6�4��	�v��L�M�S�
��p�P��L�W�D�U�8�5�T�y��
���;�;����q�C�\�
�`�|�A�A�_�R��L�?�*�<�m�-�|�,�J�E�d�%�/�T��]�h��*�+�O�T�n�1�:���o�j�n	�	�1�A��@�?�1�\�(�!�W��`�`�X�?�J�z]�x�1�z��Z�?��6�E��w���0�a�f�3�l�;
�G�P�e��a�J�� �.�)�)�F��-�Y�X�Y���B��7�,�A�M�p�q
�&��F��v�[�:�&�3�"�	�]�X���gJ��{�l��_�v�y��2�E�8�;�}�<��n��Y�f�r�v�s�x�0����PE�m�=�(��{��u�}�x�s�2�#�x	]��~�w�u��.��u�0��a�[�|�0�&�{�]�b�&�W��y
�s��a�2�q	���%�i����_��;�
�D��H�S�$�8�{
��0�F��&�1�P�	��	�^��;�p�s�
�o�\�;��y�d��e��m�P�a�5�H;���l�_�#�

�{�z�@�3�H�+����h�g�d�l���X�F�E�Q��-�G�G�b�E��,�+�z�2���g�>��
�_�0�7�Z�%��e�b�!�L�K��
�A�5��1�m�*�@�
�"��C��E��#��O�J�c�}�Q�N�J	�E�>��(�
��n�i��>�i��V�K��A��j�O�%�J�~�c�|�	��x�}�
�|�9�f�6�s�A��Y�A�"�2���k��"��S��&�R��p�l�!�N���<��i�D��E�f�a�o�M�g�J��]�S�Z�=�,�s�t�*��4�
�jp�=�/�l�/��[��Z�l��_�z�q�p�.�|�a	�e�TJ��=
�W�!�d�
�@�9N�'�,�?�G�:�1��C�	+�3���k���`�f�F�c�6�I�F�^�w�	���b�<�<�b�y��,�a�2�p�9�u2�d�A�(�t�s��I��s�"�m�P�}�T�	�z�X��1��R�?�5�>��d��X�@�S�S�b�:����j�n�I��]�O���?�!�|�7�V�-�I�#�^�Z��X�7�(��s�
��q�;�6�`�4�!��	�I�H��^�U�:�]�K(���_
�D�[��/�.����4�C�Q�S�t�r�
�w�o�d���B� �;�.�9�d�<�[�&�Y�T�o�y�&��6�q�R�I�0�;�D��n�D��
�,	�W�E�3�Y�<���>
�f�c!��{�j�r����b�,�Y�B�1
��M�b�F�*�q�z�`�3��
�g�p�M�=��r�5�H��L�
��/�^�t�[�.�M�_�U�[�X�E�w��`�]�x�{��[�)�\���Q	�d�Q�e�:�/� �U�h�H��f�A�@��2B��B�!�X�6�Q�"�a�>�s��Y����F���o�=�D��3�y�j�n�]�0��)�`�K�H�%�_�p�1+���!��r�7�0���i�H�>�u�<��9�>�k�9���S���l�n��-�7�^�?�
�C�R�^�q�p��5�N�b��Z�I�W�@�&�>�D�N�c��$��d�%�
�'�,�v�u�b���6�[�N
�	�!�L�	�H��f�e��r�k���$�K�U�>�}t	�8�'�T�q�l�}�~�C�\�;�L�]�5�n�g���}�A�V�N�N�[�=��%��<�s��X�g�3�F���r�D�A�9	���$�~��f����u�	�|�7�i�b�(�?�1�0�W�5�2� �U��~���'�[�Z�h�+��4��u�%�5�+�C�H���9�l�y����|�/�u��G��v�t�o	��o�	�8�a�h�8�%�=��|�?�`�+��A�Q�.�Q�L���S�p��?�o�f�$�1�*�u�Z�+�B��_�o�@�[� �V��:�E�j�\�8�a� �u��B�V�}�%�Z��&�K���e�h�7�-�N��=�DXY�{�g�f�6�Y�K��f�f+�a�'�!�:�A�.�/�{�V�~�
�a�<�w�h�	�f�Y�&��t�i�o��x
��e��
�	�z�=�4�p�3�L����M*�|�|��%�*�G�\�I�Y�v�
�X��5��u�6�S�.�/�L�R�w�q�(�S�u�"���9�8�'�V�~��"�r	�5�$��m�c�p�Q�j
�q
��l�'���$�\��e�F�3��y�M�,�U��F�O�.�Y�Z�f�D�1�6��o�l�?�n�F�@��a�1�P�9�;�Y�!�D�+�j�1�i�2��F��P�E�n��E�J��N�@�+��x�3�
�B�A�B�?�7��?�[�\�B�[�C�3�g��'�2�F�{�}�R�?�R�~�;�4��\�r��a�q�$��V��c�B�C��1�~�C�w��|�+�%���a�l�A�R��]�*�y�A�h�?�f�;�b���9��W��x�[�'�p��(�v��~�1	��z�C�(���>X�y�)�U�D�E�U��z�;�W�V�L	���x��<��7�(x�]�e�@��9�Z�@�?�y�w�
��	�]�0���~��#�T	��i�.�g�a�4���� r�>�oX�}�A�:��u�<�\��	�r��M���>�]����,��V�{��Y�B���+��Z
�2��!�"�(�~�b�C�6� �u�J�/�O�\�E��E�������E��%
s�5�O��?� �`�?�YI��:�q�N���Z�m�h�q�!	�+�4�6��|�9���!�:�{�v�j�p�u�^�`�c�P�$��^�4	�a�l�x��X��s�*��M��0�[~��i�3
���o��g�N�[�3��
�=�J�q�����^�B�I�_�I�4�>�p
�
�P�9� �
�x�B�m�����I�4�s�3�.�Q�]�^�H�e��=�X�)�:�E� �M�0�I�U]�o�X�^�#�T�G�k��`�(�o�%�]�2�l�7�h�P�{�}��G�p�!�A�<�-�T	�V�M�s�(�U�#�*�@�;�`�U�x�S�]� ��\�8��w�?��]	�P�'��w�A�D�Y�X�]�i�r�S�H�>�K�M���e�>���+�~�9�"�~�|�y}��:�9�7�L�X�_�z�G�o�h�p�u�k�_y�-�|��I�6�m�j�m��+�}�0�;��K�d���
��O�6�G��x�-�&�K�P�?�t�m�"�Q�r�\�4���v��e�x�M� �e�p�Z�4�i�*�F�}�h�x�m�T�f�O�O�R�{�6��'�H�+�2�p�-�|��Z���3��:�I�~�)�s���\�8�mU�2�%�]�D�'��|��l�<��M�+�r�<�#�d�H�~���p�$�f����c��PS��^��W�V�,�#�
�C��L��j�9�T�:���t�G�6�m��)�p�&�y�i�|�(��k�0�������n�0�+�F�R�	�,�!�R��	���mh�j�h�o�g�B�i�6�G�K�>�V�>��z�e����(�h��3�-��\�s�	�|�s��K��z�J�o�B���t�t�S�c�J��L�	����1�[�O�>�x
�+�:�I�&�L�5�X�7�!�7�m�	�l�
��|�=���'�5��}	�T��(�W�`�^��I�r�s�N�S�p��w�N�Y�%�!�i�	�p�I�o�
�M��d�2��w��1��T�{��@�>���V�A�\�O��j�9�F�k�<���)�|�J�y
�q�b�S�v�*��b��g�@��'�n�e� �8�o�1���^�e�X�X�U�L�]�h�O�
�#�v�'�n�	�c�p�_���@�!�r�
�b�B	�#)�X����Q�
�'�N��)�5�P�$�/��x�6�W�V����u��[�-	��)��5�S�J�&�X�
�	R� �
��h�U���Y�
�P�9	�e
�w�P�N��y.�8�W�F�X�k�`�a�b�9�{��|�V�%��S�hv��l�u�*�{ �o�4�y�>��A�4	�7�
�w�p�I�^���-�*��\��$�N�u�!�B�	�(�;��I
�x�C�Y��V�����E�l�;�a� `�6�J�C�^�� ��u�a�E�W�2�A�E�&�V�v�Q��pg�&���������(	�9�$�F�^��C�V� ��c	�r�R���N�B��u�t�Z�h�0��l��=�
�T��%��I�3�4�!�B	�\�
�i���z�i�8��C���*�m�a��� �H��F�s��!�y�\�!�=�x�n���y�0�]�%��h��,��#�L�y�b�c�3�e�(	�M��>�����'{z�$�<�C�B�2�
�m�H�y�&��K�M�j���R��P�J�A�>�_�z��r�U�F�M�'�S�R�S��h�B�U�f����P�[�>�W�8�=�,�@�c�|�8��+�Z�F�-�5�p�
�#�^��C�a�^�i�f�BG���^�	�h�C�B�C
�"�)��@�(�x���l�6�_�v���Z�C��+�b�'�r�0�@��y�l�O�p��2�%�f�#� {��/�a�6�O�S�p��9�f�#�t
�m�B�P�C�8�^
�a�k��:�@
�'�E�`����F�L���d�,�S�^�l�?�#�`�l�V�5�*���v���:�<�x�C�4��;	�n
�B
���b�?�k��E�L�9�O��i�d��g�^��5���b���:�G�,���%�
�$�a�1�T�Y��8�9���1��%��l�_�O�L�K�`�U�u�Z�i�@�u�=�+�|��w�`�G��y�I�w�L��:�Q��p�s��@�+�4�{�)�^�R�i��-�h�i�J�
F�(�o�J�s��8*�z�x�%�=�Q	�Q�>^����iJ�O�|�>��N�
	�F�(�W�Q�
�Y	�r�s�<�>�i�6�N�g�+�b�N��L�]�v����-��5�-�P�R�C�6�N�E�P��U�%��m���d�F�)�	�Y��A��<	��,�#�9�+�f���V�2�I�.��&��3�l�$�K�9
��o�
�
�\�]���_�_�t���/�^�}�S�$�y�T�.U�}�b�r�e�	��3���A�B�j�x�y�,�Z��l��2�P�0�i��U���CB�|�i��4�9�,�J�q�2�X����<�p�6�s�#�p��K�e� 	�s
�L�l�B�U��$�O�"�{��
�U�<�|�m�k�$��h	��B�M�|�Ni�7�S�T��g��`�3�r
�n�2�_�|��r�1���}�=�"�E�
�?�u
�N�K�n�r�s�2��+��E�F�I�x�e�=�b�C�f
�N�
�$�Y�*��y�!�F�=�k�"	�o�R��[���.��,�h�{�<�]�P�x�!�{�T�U��I� �S�y��r�S�@�5�T�$�c���`�A�/��w��.�-�8�	�j	�s�b���}�j�c�z�3�S��z�-�m��;�#��h�'��]��!�(�v�>�q�F�_�i�B�U�|�&�8�E�Bx�/�C�r��1�Y	�Z��7���7�?�D���v��O�5�l�=�8�9�O�t��s���\�.�q�8�>�u�t�K�j
��/�<�Pg�*�	��W��Y�)��o�=�Z�a�s���F�L�
�[�`�u
�J�)�^���T
�;�"�3�i��o�l�(�~�,
�s�F�S�B��k�v�l�M��+�j��T�U�2�X�
�f�U�X�D�;�}���B�GG�@�u�@��a�J�B�\
��H��V�W��b�*�Q�d�T�o�>�b��^�Y�+�<��B��Q��j�=�.�+�I�R�w�N�t�!�/�J�!�o���[��Y��J	��;� �g�[�x�3�^�7
~�"��t��B�#�1� �s�;��5�J�;Y�& �c�
�?
�%��s�o�
�d�\�
�
�9H�~�W�x�
~��N���|�)�,�2�J�����z�h�^�#��A�Z�g�I�
�@�A�0�#�B�t�m�]��Z�W�P�#��=�R�c�O�(��W�/�P�1��s�0�S�~�q�o�B��t���7�8��j�c�p�	�.�X
�0�1�1��p�-�n�	�1��p�S�t�gI�
�K��%
�J�z�]�^�G�:	�}��v�g��V�1�S�h�
�D�U�:�R�c�z�o�^�c�`�\�I��8�-#�|��o����F��\�+��j�(�C��2�%�s�0�c� �w���3�f�9���~�9�1�A�Z�"�-�T�;�\�Y�-	�/
�B��"�#�	�h�%���t���[�<���A
�m�*�o�|�� ��P�E�G�0��\�6��T�.�'�\�
�.	��Q�3�F�L�9�p�d�/�T���i�*��X�'�2�,�w�#�

��!�0	�1�U�4�� �S�H��m�%�f�_�J�q�J�<�4�9�z��.�p�7�P�0�&
�?�,�p��c�L��K�|�I�@�[�n�
��Z�t�3�,L�Y�9���M�a� ��\�,�!�/�4�i��l��3�Q�8�Y�z���z�J�m�_�I�(��M��2��G�D�c�i�E�K� �8��
�&�m�,��z�RC�r�\�i�/�4�K�\�/�B�"� ��(�P�s�a�>�>�.�q�<�g�)�o�
��L�	�\$�v�U��9�@�
��L�E�n�{��F�|�M�\�q��7�	�?�^�>���<��}	���
�Q�P'��I]��7�_�*��)�_��Z�
���X�#~�H�{��D�?�2� �y���@�	�5�J���u��t�p�w��T�3�]��|�A�O�L�f�Y�_�R��q��1��G�:�Q�r� �m�&��,�m�~����}M��{���
�`�
�^�w�2	�rc
��-�H�(��V�_�t�i�@�S�C��=�J�K�V
�Y�h�o�n�o�T�;�d�|�P�k�%�t�.�g
�,��,�o�'�.�w?	�V�%�O?�p�!��B�y�:�k�v��F���?�F�J��C�F���
������b�5�R�=��f�/��=�&8���x�2�=�
��j�	��x�#��
�t�q�N���!�w�0�X�z�p�a�s��6�Q�P�9�i�"�U�C�X�9� �$����(�x�<�'�o
�^�y�d�w�4�x�c��H�@�z�O�|��L�R�K�1�R��O�A�:�O�S�=�d�j�L�	�fq�f�k��}��F�9�M�F�K�P�m�K�\��.�K�[���v�V�C�W��a�r�t�y�W�"��D�_�J��0��d�
���U�:��$�s�{�j�(�]�k��V�s�E�:�~�1��	�z�K�4�@�Z�[�Y�;
����S��d�=�1��q�4�>�	�f�M�$�U
���K�D�q��a�L�X�"�
��p
�}�(�V��M��p�k�b�C���I�1�$��s�p�&�Z�p�M�H�n
�W��z�k�=�/�.��~���p�:�o�@���/�X�0�q�<�o�f�
�s�N�o�n�p�i�H�w�A�V
�{�}��T�8
�&��}�	�`�L�{�O!�,�w�Y�u�\�0�y�H�i�L�
y�>�G�Q�x�|	�a
�4	�W�K�B�q��/9�j�B��?�	����&�/�y�&��p
�Q�z�/�8�\�I�:�W�}�h�h�C�c�2�y�4�
�/�'���O�7�d��G��#������g��p�#�>���m�7�7�� �i�,�N�a�S�]�L�M��j�%^�@�`�C�T�	�y��i�@�7�4�Y�B�-�r�(��R�����A�y�X�w���I�>��;�(�s�r�l�Q�7�x��8�%��r�v�%�Q�a��GS�H�K�N��Y�1�0�S�'�(�I�b�i
��*�7k��"�W�b�Y�J�\8�=�3���}�3�0�m�/
�R�!�c�B�c�<�W�i�b�/�,�|��6�@�7�
�(�a��9�_�z��$>�Y�X�S�9��s�>�,�E�9�B�9��K�Q�=�(��]
�B�c
�F�!�o�J��p�@�-�W�^�}�j�U�z�u��8�u��x�e=�=��9�d�q�n�E+�^�S�6�
�%��"�;l�%	�d�]�>�S�z�L
�5�	�(�Y�
�;�$�|���T��=�Y�S�
��
�
��m	��5�
��P
�s�Z�o�@�G�F
�4�x6��R�Z��0�n�T�'��l�sV��|��s�1�0�����
�0�%��	�=�a�P�W�v�u�>�;	�'�V�n��j�a�F�i�,�	��K��x�|�}
�S�7�(�?�@�g�U"



3

(
"

*
#




;



O
'7"&

&












(

&
#3(

*


(
#+B>
Z3

 







&
	(&3

,

&$"



















&
&
'
(
7&
&


"




&



(&






&


&






O

""
(



&













C'��~*,F
&
5P( Q�7V,J

"Dv*Dr
C���DC�I�,5Z
Cp"Jp
Q�
*"C%���x%<

DzJvM�7^Q�,B
5X""J�"C0�:�67X$,5\"$*(Q�($S�$$5V
J|
 *#>
Q�(C��x
"Jz
 &(>C
���%h�4$.R

"q�B(C���C#���I	"$,>$(<
Q~("Jx*"5Xv�Bo�BCL��5@d
"#4

$( *$$ Qv$5@
"9^
"Hv
"5X5PQ�7X,L

$S�o�DC ��uDn
"Br$7b$Q�5XQ�5R5X$$S�$&7ZC��=Q�m�6"$J�
$Q�
L�5ZC���
7V

"S�$5T(C!�n��i
o�D&7`"$J�
$$7R(,&(*Q�(5NS
���m�\��.��FV�]�L�d�5C��(�s(�,LS�Z�^�m�~�{h3�F�N�s�K�F�:��O�3�f�s�t��;��6�P�9�C)�~�W�1�8
�_�n��|
�	��
�l���)�D�_�jC���/�7� ��w�C���g�S���g�l�wN�Y�h��K�V�$��>�i�x�o�*�TC�.�C�4��]!�� �D�)�bc ^c�u��K�]�W�h�o�F�|��$�Cmrt�ui�M�O�N�m�q	�M	�C�+�-��@�67��<�:�X�]�;�!�C4�/�1�3�5�7�9�;�=�?�A�CY�i*�f��^�}�e	xv+R�S
U�&:2�f)�q�D�'��s�d��`�~�K	�U�Q�g��	�i�x��t�x��G�-�>�A�E�M�T�V�Y6�b�e�h�j�n�t�v�y�}��"�
�
�����$�&�*&�3�6�:�>�@�D"�H�L&�R�V�X&�]�`�c�f�i
�o�q�u�y�{�~6�
���-�#��� �#�&�(*�.&�2
�6�:�>�@�D�J&�M
�S&�Y�]
�a�e�i�k�q�v�x�{6�����
�
�����"�"�'�+G�4
�7�>�A�CR�M
�O�R
�U�X�[�^!�d�i
�n�r�w�z���>�
���
����$"�(�+�/R�=&�A�Ez�X�[�^&�c�f�h�j�n�r�t�w�|�~>����	���>��
�"
�&S�-&�0�3�5�;�@
�D�H�J�N�T�V�Y6�^�b�e�g�l
�p�t�v"�}�R�&��B�!�(�,
�0�4�7�9�;�?
�C�F�K�N�Q�V�X�\6�`�e�h�j�o�q�t�x"�}���	Z���	�#�'�7�=�?�D�I�N
�S�W�Y�]6�a�d�g�j�l�o�q�s�v�{"�
�B��&����"9�(6�3�7&�:�?�B�E�J�L�O�T�V�Y6�\�_
�b"�g�i�k"�n�rz�x��&��

������� �"�%�)&�2�7�9�;�=�A�E�G�J�N�P"�W
�]
�b-�k�p
�s�w
�{�~���	��
��� �"&�&�)�+
�1�3�6�8�:�?�A>�D�I�KZ�Q�T�W
�ZF�c&�f�jz�s�y&��
��	�������� �%	�*�/�1�3�7�@�D
�H�M�O�S�U�[�]�_�b�f
�l�p�r�u�{�
��
���
�����!
�$�(
�,�0
�5�:�=�I�L�O�R�W�Z�^�`�d�h�j�m6�w�|�����"����.�$�)
�-�1�4�7B�>�B&�I�M&�Q�T�W�Z�\�^�d�f�i�m�o>�u�y�{Z��
�F�&�����'�+�-�/�5�7�;�?�A>�J�N�P�S�WB�a�eR�p&�v�zR�&��&���
� �$
�(�,�.�16�4�7�9�;�>�B�I�K"�O�S
�Wn�b�fB�p�t&�|�&��
�������>� �%�'�*�.B�7�;z�H�LB�S�W&�_�b�e�k
�m�q
�u
�{�}����
6������ �""�(�,z�9�<�?�
�S&�V�Z
�^�c
�g�k�m>�r�w
�{�
���	"��
�� �%6�16�>�C�G
�K�R�U�Y�a
�d�h�g:� �D��cg�s�'��5�O����[�\c2_��Z��"���
�j�����"�*�6�9�C�!�%�,�0�7�=�A�V�[�|��I�M�S�Z�a�g�q�t�w�{�~���
���G�L�P�e�n�s�w��� �$�-cv}��u�uc\��h�e�M�w�
c���tcz��Uc^|�-�
�2�W�9�S�?�0�ScaX�-c���]cfC�g�u�Gcq��qc z�/�:�q�!�)��L�
�,�Z�D�:	���c 9�$�j�?��)%�#�/�2�W�	�ac!��@c$��Rc�_0�j�j�U��r�u�w�y������	��"�(�+�.�A�C�F�L�N�Q�T�\�c�e�m�p�r�u�x��	�������%�+�.�1�9�;�A�C�F�O�U�X�[�d�l�n�p�v�{�}
����#�+�-�0�8�@�C�F�Y�[�]�e�k�������!�$�&�(�+�3�9.�?�o�r�z�
��
������&�*�,�G�K�T�X
�^�i�o��������9�;�>�F�K
�R�]�c�e�h�o�q�x�{�~	���*�/�5�8�V�X�[�d�k�m�u�w�z���
�
���2�;�?�D�I�K�N�V�\�^�a�h�j�l�o�r�	�����
� "�+�N�Q�T�\�d�f�i�s�u�y�{�}����&�A�D�G	�N�X�^�e�h�n�p�s�z�~������$�*�,	�0�3�5�8�@6�F�~����
����!
�$�/�5�8�O�Q�U
�\�^�a�d�w�z�
�����'�B�D�G�P�V�i�p��
�
��� �#�&�-�1�7�9�<�C�I�P�d�f�h�p�u�w�z���	�=��R�T�X�]�b�d�g�o�u���+�-�0�<�B�I�\�w2�z�-�/�2�8!�<�^�y�|��!�	�+�-�/�7�?�B�E	�L�V�Y�\�e�k�m�p�x�{�}���
�!��A�C�F�N�S�U�X�a�g�i�l�t�z�}�
���� �&�)�9�S�Z�]�`�i�o�r�u����
����,�3�N�P�S�Z�]�c�e�h�q�w�y�|���$�&�)�2�8�;�B�E�G�J�R�W�]!�i��
�����!�)�/�1	�4�?�C�_�c�j�p�s!��$�'�*�4�:�=�@�I�R�T�[�_�b�e�n�v����*�-�0�7�9�A�D�G�N#�P�t�v�y
���������� �"�$%�0�V�X�[�b�g�i�k�s�y�{�~��
���'�*�-�3�9�<�K�R�e�l�n�q	�x����� �'�+
�2�=�D�_�a�d�p�v�y�|�����#��=�?�C�J�N�P�T�Z�_�a�d�l
�r&��'�)�+�2�4�:�N
�T!�c���
�=��R�m���	���;�4�p��
�
�%��=�?�B�K�R�T�Z�]�_�a�d�l�r�t�w���	����� �%�*�,�/�7�=�?�B�J�P�S�V�b�i�m�t�w�y�{�~�
������&�)�,�4�<�>�A�J�P�R�T
�`�s�z�|���
�����$�*�1�4�:�<�?!�G�i�k�m�u�{�~�������!�5�W�Y�]�b�d�j�m2�p�#�:�<�?�D�G�M�O�V�^�e�x���	���1�3�6�=�@�F�Y�`�{+�~�*�0�2�6	�:�D�G�J
�Q�\�^�a�j�p�s�x�|�~��	������ �#�&	�.�8�;�C�E�H�Q�X�\�`�c�f�i�q�x�z�~	���	���
��'�*�0�2�5�>�D�F�I�R�X�Z�]�d�j�l�o�v�x�~���
�
�����!�'�)�,�6�:�<�?�G�J�M�T�V�^�`�c�h�l�p�v�{�}�������#�&�)�<�>�B�I�N�P�S�\�b�e�h�o�q�w�����"�(�2�8�K�Q�T�W�`�f�h�x�~���
��#�*�E�G�J�R�Z�]�`
�g�r�t�w!��"�$�'�.�1�5�7�<�B�D�G�O�U�X!�h�
�����1�7"�J�m�o�r�|�
��
��#�+�2�5�8�?�^�d�f�i�p�s�y!��.�I�d�s�v�����$�'�+�/�7�<�>�A�I�O�T�[!�_��������"�(�+�:�A�D�T�Z�\�_�h�n�q�t!��"�%�(�1�7
�F�Q�S�V�_�e�x������(�*�.�1�4�;�?�E�G�J!�S�u�w�y���	����� �(�.!�A�c�e�g�o�u�w�z����	����������8��:��=��F��L��N��Q��X��\��b��v��|������1��3��6��?��E��H��K��W��]��q��w��y��|��������
����#��%��(��/!��2��T��V��Y��b��h��j��m��t��z��|������
��!��!��C��E��I��O��U��W��Z��c��i��l��{����������!��*��/��2��I��d��g��n��v��|��������%��+��>��E��`��b��e��l��o��u��x��{����������������"��%	��,��6��8��;��D��K��O��U��X��Z��\��_��g5��m��#��%��(��0��6��8��;��D��J��M��P��W��Y��_��f��o��w��y��|����!����A��D��[��^��a��j��r��t��w��~��!����:��=��@��G��I��Q��T��W��^��a��c��g��j��m#��u��������'��-��/��2��6��:��@��B��E��M��S!��g��	������������!��*��0��2��5��=��C��F��I	��Q��[��]��`��d��h��k��m��o	��r��}��
������������)��/��2��5��>��F��H��K��O��Q��T��Z��]��`��g��k��q��s��v������
��
��������!��(��+��1��4��<��?��A��D��G��N��Q��W��Y��\��ePK
!<>9Ѩ�2chrome/pdfjs/content/web/cmaps/UniKS-UTF32-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�
UniKS-UTF32-HC �}�m ��m
,7�<c �{�l�x	����r�
�;�PK
!<>�`�l�l1chrome/pdfjs/content/web/cmaps/UniKS-UTF8-H.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE���?���?����? A �
wa�r�YM�Q
¶�t��J�v�O�e�z�?A	×��O�AÆ�	
�6	�R�3�4	�7�6�9�@�8�9�8�=�8�C�8�5�8�Y�~�@Aʼ�SB4‐a�P
y�x�`
�i�]�YX�	�l�
�|�k�f6��xm�k]\]>6EYT(�V8?�T3
+&�	�	W�bV�m�`B,‛�.�b3�h�E�I9�?�s�V �C�N�r�B1(�C�K�d�Z!<'�g�J���z�_�	�o�W�b
/B
‵�n�i�
R┌�	H3:%>)<'(B8┠�(�B��i
���je�Bbm��	�u�&�a�A �B!⼀�<�!@:B�z�;�v�(��o�F�y�:�)�&�}�=�0�l����U�:�3�w�D��Q�F�i�@�4B▱� V�C�)���m��_��
�fH"5�X@R⽂�h�W��4��X�'�6�+�L�_B	⽏�V�j�I�J�w�N�Q�� R
⽚�a��V��
�y�G�j�'�p�d�4�bB⽩��]F�>�Q�P�C�"��v��G�fB⽵�tB⽪�0��gR⾀�`�A�`� �;�j�J����B⾒�<�U�RB⾖��,�o��X��9B⾎�L_�)�Q���Y�~}�g�"�#�H�YK�.�+��"�%R
⾳�q�6�E�
�~�Y�X�c�Y)R
⿁��x�(�y
��~�=�pB〃l��'��h�w�6�"�X�B⿎�6�F�i�'�SB
⿍�f,����x
;:��h�t�=�V�|�9	��R
㏂�� �
r�-�D`q�lB㏘�B㏏�c`B�一�<�f�&�
��}�X���o�	�l��v��.�a�^�U�V�F��'�	�N�K�|�u�_�F�p�k�5��u�x�v��<�e�C�&�B�L�i�$�1l�4�1��/�@�&�o�:�5�P��>{�%�J�O�U��E�A�@�l�W��=�L�/�.��#�f���%�\�
���sS�6�E�O�0�Q�y���d��n��'�?�8�U�%�E��#�8�p�Y�f��!��%���r�@�/�u�!�b�(�m��A�P�)�$�G�F�S�4�B�"�Q�i��q�^�=��/�_�
�B�K��s�$�$�q�A�#�P�+�9�x�z��M�U��}�`�Z�g�~��G�g�M�N��|��o�V�
�I�D�!�hb�Q�6�Q�f�i��7�\�_
��Gj�_�x�B�"�L�o�9�A��]�{�b�G�3�N�U�a�0�s�>��w� �(�m��F�N�"�=�-�q�j��7�
�%��j�w�X�H�@�t�t�?�_�
�#�/�p��Q�i��C�D��Q�X�%���C�Y�^�,��2�	�+�C�b�%�t�S�4�D��F�~�G�L�'�M�x�c�C�4��P�Y�;�>�k�@�#	�v�Ah�F�8�E�U�b��%� �n�Z�'��k�D����6�B�k�|��v��u��n�?�^�&�+�O�b�{�f�n�]�o�D�#��G�6�4��	�v��L�M�S�
��p�P��L�W�D�U�C�8�5�T�y��
���;�;����q�C�\�B�
�`�|�A�A�_�R��L�?�*�<�m�-�|�,�J�E�d�%�/�T��]�h�E��*�+�O�T�n�1�:���o�j�n	�	�1�@�A��@�?�1�\�(�!�W��`�`�X�?�J�z]�x�1�z��Z�?��6�E��w�D���0�a�f�3�l�;
�G�G�P�e��a�J�� �.�)�)�F��-�Y�X�Y���B��7�,�A�M�p�q�J�&��F��v�[�:�&�3�"�	�]�X���gJ��{�l��_�v�y�C��2�E�8�;�}�<��n��Y�B�f�r�v�s�x�0����PE�m�B�=�(��{��u�}�x�s�2�#�x�I]��~�w�u��.��u�0�E��a�[�|�0�&�{�C�]�b�&�W��y
�s��a�2�q	���%�i���V��_��;�
�D��H�S�$�G�8�{
��0�F��&�1�\�P�	��	�Q�^��;�p�s��M�o�\�;��y�d��e��m�H�P�a�5�H;���l�_�#�

�{�D�z�@�3�H�+����h�g�d�l���X�F�E�E�Q��-�G�G�b�_�E��,�+�z�2���g�>��
�C�_�0�7�Z�%��e�b�!�L�K��
�A�5��1�m�O�*�@�
�"��C��E��#��O�J�c�}�D�Q�N�J	�E�>��(�
��n�i��>�i�B��V�K��A��j�O�%�J�~�c�|�	��x�}�
�Q�|�9�f�6�s�A��Y�A�"�2���k��"��E�S��&�R��p�l�!�N���<��i�D��E�f�a�o�M�g�J�D��]�S�Z�=�,�s�t�*��4�
�jp�D�=�/�l�/��[��Z�l��_�z�q�p�.�|�a	�e�TJ�A��=
�W�!�d�
�@�9N�'�,�?�D�G�:�1��C�	�k�3���k�\���`�f�F�c�6�I�[�F�^�w�	���U�b�<�<�b�y��,�a�2�p�9�u2�d�A�(�t�s��I��H�s�"�m�P�}�T�	�z�X��1��R�?�5�>��d��X�@�S�S�b�:����j�n�I�@��]�O���?�!�|�7�V�-�I�#�^�Z��X�7�(��s�
��q�;�6�`�4�!��	�F�I�H��^�U�:�]�K(���_
�D�[��/�.����4�C�@�Q�S�t�r�
�w�o�d���B� �;�.�9�d�<�V�[�&�Y�T�o�y�&��6�q�R�I�0�A�;�D��n�D��
�,�I�W�E�3�Y�<���>
�f�c�a��{�j�r���W��b�,�Y�B�1
��M�b�F�O�*�q�z�`�3��
�g�p�M�=��r�5�H��L�
��/�C�^�t�[�.�M�_�U�[�X�E�w��`�]�x�{��@�[�)�\���Q	�d�Q�e�:�/� �U�h�H��f�A�E�@��2B��B�!�X�6�Q�"�a�>�s��Y���C��F���o�=�D��3�y�j�n�]�0��)�`�K�H�E�%�_�p�1+���!��r�7�0���i�H�>�u�<�K��9�>�k�9���S���l�n��-�7�^�?�
�C�R�^�q�@�p��5�N�b��Z�I�W�@�&�>�D�N�c��$��d�%�
�'�,�v�E�u�b���6�[�N
�	�!�L�	��O�H��f�e��r�k���$�K�U�Q�>�}t	�8�'�T�q�l�}�~�C�\�;�L�]�5�n�g���T�}�A�V�N�N�[�=��%��<�s��X�g�3�F���r�D�G�A�9	���$�~��f����u�	�|�E�7�i�b�(�?�1�0�W�5�2� �U��~���B�'�[�Z�h�+��4��u�%�5�+�C�H���9�l�y����B�|�/�u��G��v�t�o	��o�	�8�a�h�8�%�=��D�|�?�`�+��A�Q�.�Q�L���S�p�@��?�o�f�$�1�*�u�Z�+�B��_�o�@�[� �V��:�E�j�@�\�8�a� �u��B�V�}�%�Z��&�K�@���e�h�7�-�N��=�DXY�{�g�f�6�D�Y�K��f�f+�a�'�!�:�A�.�/�{�V�~�
�a�<�w�h�	�f�Y�&��A�t�i�o��x
��e���J�	�z�=�4�p�3�L����M*�|�C�|��%�*�G�\�I�Y�v�
�X��5��u�6�U�S�.�/�L�R�w�q�(�S�u�"���9�U�8�'�V�~��"�r	�5�$��m�Y�c�p�Q�j
�q�M��l�'���$�\��e�F�3��y�M�E�,�U��F�O�.�Y�Z�f�D�1�6��o�l�?�n�F�@��a�A�1�P�9�;�Y�!�D�+�j�1�i�2��F��P�E�E�n��E�J��N�@�+��x�3�
�B�A�B�?�7��?�C�[�\�B�[�C�3�g��'�2�F�{�}�R�?�R�~�;�4��\�r��C�a�q�$��V��c�B�C��1�~�C�w��|�+�%���F�a�l�A�R��]�*�y�A�h�?�f�;�b���9��W��x�[�'�p��(�E�v��~�1	��z�C�(���>X�y�)�U�D�E�U��z�;�W�V�L�I���x��<��7�(x�]�e�@��9�Z�@�?�y�w�
�E��	�]�0���~��#�T	��i�.�g�a�4���� �@r�>�oX�}�A�:��u�<�\��	�r��M���>�]����,�H��V�{��Y�B���+��Z
�2��!�"�(�~�b�C�6� �G�u�J�/�O�\�E��E�������E��E�%
s�5�O��?� �`�?�YI��:�q�N���Z�S�m�h�q�!	�+�4�6��|�9���B�!�:�{�v�j�p�u�^�`�c�P�$��A�^�4	�a�l�x��X��s�*��M��0�[~��i�3�M���o��g�N�[�3��
�=�J�q�����E�^�B�I�_�I�4�>�p�J�
�P�9� �
�x�B�m�����I�4�s�3�.�Q�]�[�^�H�e��=�X�)�:�E�E� �M�0�I�U]�o�X�^�#�B�T�G�k��`�(�o�%�]�2�l�7�h�P�{�F�}��G�p�!�A�<�-�T	�V�T�M�s�(�U�#�*�@�;�`�U�x�F�S�]� ��\�8��H�w�?��]	�P�'��w�A�D�Y�G�X�]�i�r�S�H�>�K�M���e�>���+�~�9�C�"�~�|�y}��:�9�7�L�X�_�z�G�o�h�A�p�u�k�_y�-�|��I�6�m�[�j�m��+�}�0�;��K�A�d���
��O�6�G��x�-�&�K�P�?�t�m�"�Q�r�\�B�4���v��e�x�M� �e�p�Z�4�i�*�F�}�h�x�m�T�f�O�O�R�{�A�6��'�H�+�2�p�-�|��Z���3��:�I�~�)�W�s���\�8�mU�2�%�]�D�'��|��l�<��M�B�+�r�<�#�d�H�~���p�$�f����E�c��PS��^��W�V�,�#��J�C��L��j�9�T�:���t�G�6�m��)�p�&�y�i�|�(��k�0�[�������n�0�+�F�R�	�,�!�R��	���mh�j�h�D�o�g�B�i�6�G�K�>�V�>��z�e����(�U�h��3�-��\�s�	�|�s��K��G�z�J�o�B���t�t�S�c�J��L�	��A���1�[�O�>�x
�+�:�I�&�L�5�X�7�!�A�7�m�	�l�
��|�=���'�5��}	�T��(�W�B�`�^��I�r�s�N�S�p��w�N�Y�%�!�i�A�	�p�I�o�
�M��d�2��w��1��T�{���B�@�>���V�A�\�O��j�9�F�k�<���)�|�J�y�M�q�b�S�v�*��b��g�@��G�'�n�e� �8�o�1���^�e�X�X�U�L�]�h�N�O�
�#�v�'�n�	�c�p�_�F���@�!�r�
�b�B	�#)�P�X����Q�
�'�N��)�5�P�$�/��x�6�W�F�V����u��[�-	��)��B�5�S�J�&�X�
�	R� �
��h�U���Y�
�@�P�9	�e
�w�P�N��y.�8�W�F�X�k�`�A�a�b�9�{��|�V�%��S�hv��l�u�B�*�{ �o�4�y�>�P��A�4	�7�
�w�p�I�^���-�*�L��\��$�N�u�!�B�	�(�;�D��I
�x�C�Y��V�����E�l�H�;�a� `�6�J�C�^�� ��u�a�E�W�2�F�A�E�&�V�v�Q��pg�&���������(	�9�$�F�^�B��C�V� ��c	�r�R���N�B��u�t�Z�h�0��l��=�
�T��%��@�I�3�4�!�B	�\�
�i���z�i�8��C���*�m�a��C�� �H��F�s��!�y�\�!�=�x�n���y�0�]�%��F�h��,��#�L�y�b�c�3�e�(�I�M��>�����'{z�$�<�C�B�2�
�m�H�y�&��L�K�M�j���R��P�J�A�>�_�z��r�U�F�M�'�S�R�F�S��h�B�U�f����P�[�>�W�8�=�,�@�C�c�|�8��+�Z�F�-�5�p�
�#�^��C�a�C�^�i�f�BG���^��F	�h�C�B�C
�"�)��@�(�x���l�G�6�_�v���Z�C��+�B�b�'�r�0�@��y�l�O�p��2�%�f�#� {��/�a�6�B�O�S�p��9�f�#�t
�m�B�P�C�C�8�^
�a�k��:�@
�'�E�`�K����F�L���d�,�S�^�W�l�?�#�`�l�V�5�*���v�N���:�<�x�C�4��P�;	�n
�B
���b�?�D�k��E�L�9�O��i�d��g�D�^��5���b���:�G�,�E���%�
�$�a�A�1�T�Y��8�9���1��%��l�_�O�L�A�K�`�U�u�Z�i�@�u�=�+�|��w�`�G�@��y�I�w�L��:�Q��p�s��@�+�D�4�{�)�^�R�i��-�h�i�J�
F�(�o�J�s��8�@*�z�x�%�=�Q	�Q�>^����iJ�O�|�>�A��N�
	�F�(�W�Q�
�Y�I�r�s�<�>�i�6�N�g�+�b�N��L�]�v����-�O��5�-�P�R�C�6�N�E�P�L��U�%��m���d�F�)�	�Y��A��<�I��,�#�9�+�f���V�2�I�.��G�&��3�l�$�K�9
��o�
�]�
�\�]���_�_�t���C�/�^�}�S�$�y�T�O�.U�}�b�r�e�	��3���A�B�j�K�x�y�,�Z��l��2�A�P�0�i��U���CB�|�i��4�9�,�J�q�2�X����<�p�6�s�#�G�p��K�e� 	�s
�L�l�B�U��$�O�"�{��
�U�<�N�|�m�k�$��h	��B�M�|�Ni�7�S�T��g��G�`�3�r
�n�2�_�|��r�1���}�=�"�E��M�?�u
�N�K�n�r�s�2��+��E�F�I�x�e�B�=�b�C�f
�N�
�$�Y�*��y�!�F�=�k�E�"	�o�R��[���.��,�h�{�Q�<�]�P�x�!�{�T�U��I� �S�K�y��r�S�@�5�T�$�c���`�A�/��C�w��.�-�8�	�j	�s�b���}�j�c�z��C�3�S��z�-�m��;�#��h�'��]��!�C�(�v�>�q�F�_�i�B�U�|�&�8�E�B�Dx�/�C�r��1�Y	�Z��7���7�?�D���v��O�H�5�l�=�8�9�O�t��s���\�.�q�D�8�>�u�t�K�j
��/�<�Pg�*�	��W�L��Y�)��o�=�Z�a�s���F�L�@�
�[�`�u
�J�)�^���T
�;�"�3�i��o�@�l�(�~�,
�s�F�S�B��k�v�l�M��+�j��T�F�U�2�X�
�f�U�X�D�;�}���B�GG�@�u�@��F�a�J�B�\
��H��V�W��b�*�Q�L�d�T�o�>�b��^�Y�+�<��B��Q��j�=�.�+�I�R�w�N�t�Q�!�/�J�!�o���[��Y��J	��;� �B�g�[�x�3�^�7
~�"��t��B�#�1� �s�F�;��5�J�;Y�& �c�
�?�M�%��s�o�
�d�\�
�
�9H�C�~�W�x�
~��N���|�)�,�2�J���E���z�h�^�#��A�Z�g�I�K�
�@�A�0�#�B�t�m�]��Z�U�W�P�#��=�R�c�O�(��@�W�/�P�1��s�0�S�~�q�o�B��T�t���7�8��j�c�p�	�.�X
�0�1�A�1��p�-�n�	�1��p�S�t�V�gI�
�K��%
�J�z�]�^�G�:	�}�F��v�g��V�1�S�h�
�D�U�:�R�c�z�o�B�^�c�`�\�I��8�-#�N�|��o����F��\�+�P��j�(�C��2�]�%�s�0�c� �w���3�A�f�9���~�9��q�A�Z�"�-�T�;�\�Y�-�I�/
�B��"�#��I�h�%���t���[�F�<���A
�m�*�o�D�|�� ��P�E�G�0��\�6��T�.�'�\�
�.�I��Q�3�F�L�9�p�d�/�T��L��i�*��X�'�2�,�w�#�

��!�0�I�1�U�4�� �S�H��m�%�f�E�_�J�q�J�<�4�9�A�z��.�p�7�P�0�&
�?�,�p��c�L�B��K�|�I�@�G�[�n�
��Z�t�3�,L�Y�9���M�a� ��\�,�!�/�4�i�T��l��3�Q�8�Y�z���z�J�m�_�I�(��K�M��2��G�D�c�i�E�K� �8��
�&�m�,��z�RC�r�B�\�i�/�4�K�\�/�B�"� ��(�P�s�a�>�>�.�q�<�g�)�o�A�
��L�	�\$�v�U��9�@�
��L�E�n�{��F�G�|�M�\�q��7�	�?�^�>���<��B�}	���
�Q�P'��I]�A��7�_�*��)�_��Z�
���G�X�#~�H�{��D�?�2� �y���@�	�5�J���u��t�p�w��@�T�3�]��|�A�O�L�f�Y�_�R��q��1��G�:�Q�r� �m�&�F��,�m�~����}�
��{���
�`��M�^�w�2	�rc
��-�H�G�(��V�_�t�i�@�S�C��=�V�J�K�V
�Y�h�o�n�L�o�T�;�d�|�P�k�%�t�.�g�J�,��,�o�'�.�w�S?	�V�%�O?�p�!��B�y�:�D�k�v��F���?�F�J��C�F���
������b�5�D�R�=��f�/��=�&�x���x�2�=�
��j�	��O�x�#��
�t�q�N���!�w�0�X�z��B�p�a�s��6�Q�P�9�i�"�U�C�X�9� �$����(�x�<�'�o
�^�y�d�w�4�x�c�B��H�@�z�O�|��L�R�K�1�R��O�A�:�O�S�=�d�j�L�	�fq�f�k��}��F�@�9�M�F�K�P�m�K�\��.�K�[���v�V�C�W��a�r�t�H�y�W�"��D�_�J��D�0��d�
���U�V�:��$�s�{�j�(�]�k��V�s�E�N�:�~�1��	�z�K�4�@�Z�[�Y�;�J����S��d�=�1��q�4�>�	�f�M�$�U
��W��K�D�q��a�L�X�"�@�
��p
�}�(�V��M��p�Y�k�b�C���I�1�$��s�p�&�R�Z�p�M�H�n
�W�G��z�k�=�/�.��~���p�:�o�@�X���/�X�0�q�<�o�f��M�s�N�o�n�p�i�H�w�A�R�V
�{�}��T�8�M�&��}�	�`�L�K�{�O!�,�w�Y�H�u�\�0�y�H�i�L�
y�>�G�Q�x�|	�a�J�4	�W�K�B�q��/�y�j�B��?�	���@��&�/�y�&��p
�Q�z�/�8�\�I�:�W�}�h�h�C�c�2�y�4�G�
�/�'���O�7�d��G��#������D�g��p�#�>���m�7�7�� �i�,�N�a�S�]�L�M��j�%^�@�`�D�C�T�	�y��i�@�7�4�Y�B�-�r�(�D��R�����A�y�X�w���I�P�>��;�(�s�r�l�X�Q�7�x��8�%��r�@�v�%�Q�a��GS�H�K�N��Y�1�0�S�'�(�I�b�i�J��*�7k��"�W�b�Y�J�\�x�=�3��R��}�3�0�m�/
�R�!�c�B�c�<�W�D�i�b�/�,�|��6�@�7�
�\�(�a��9�_�z��$�~�Y�X�S�9��s�>�,�E�9�B�G�9��K�Q�=�(��]
�B�c
�F�A�!�o�J��p�@�-�H�W�^�}�j�U�z�u��8�u��x�e�}�=��Z�9�d�q�n�E�k�^�S�6�
�%�D��"�;l�%	�d�]�>�S�^�z�L
�5�	�(�Z�Y�
�;��d�|���T��=�Y�K�S�
��
�
��m	��5�
�R��P
�s�Z�U�o�@�G�F
�4�x�v��R�Z�O��0�n�T�'��_�l�s���|��s�R�1�0�����
�0�%��D�	�=�a�P�W�v�u�>�;	�'�V�R�n��j�a�F�i�N�,�	��K��x�|�}
�S�U�7�(�?�@��g�U"�C
�U�T�L�J�L�L
�K�H
�[�s�P
�P
(�F�C
��K�_�b

�W�j�W�C
�c
�J�[�L

�W�D
�{�S
�[�S
�_
�S�F
�_�W��W
�g�S�w�b�L&�H
�J�N&
�L


�S

�P�J

�F�_


�J(
�A
�[&�C
�N#�F3�C�N�[�H(�@
�P
�j�[�J�S�[�_�G

�h
�U#�k�T��~�T
���T�s
�E
�F �C
�L
�F
�L

�S

�A
�f
�I�h&�Q�s

�O�S�F�l
�L
�f$�W�b

�W�H
�C



�N

�J


�H�U
�L�W
�C�H�[

�J
�_
�O
�f�C
�f
�F�g
�S�h
�w�[�f�W
�f�J�P

�b
�O
�S
�F

�f

�J
�h&�[

�_

�G�S
��C
�J�L&
�J
�T&�U
�G
�C�N
�A�_
�@�J

�O
�S
�A�b�@�b�F
�h


�S
�f

�J
�C

�N
�L�J


�G
�L

B(갘�~�]*,F�H
�E �P
�uP( �A���wV,J�H

"�v�N*�r
�AB꿊�DB끉�,�uZ
�S�p"�Jp
��
*�Y"B%늢�x�e<

�[�z�R�
v�
��w^���lB
�uX�[""�
��["B1뜺�6�wX�Y$�],5\�["$�[*(�Q�($�S�$�Y$5V�A
�J|
 �_*#>
�Q�(�FB밁�x
�Y"�
z
 �[&(>�NB
붒�%�h�4$�nR

�["�q�B(B뿠�B#삢�I	�["$�l>�R$�h<
�~�[("�
x�A*"�uX�v�B�o�BBO쓹�5�d�N
�["#4�K
�d(�W *�Y$$�Y �v$�T.8�F
"�N�y^
"�v�H
"�uX�uP���wX�lL

�[$���o�D�m�BB"쯨�u�n
�V �r�[$�wb$���uX���uR�uX�Y$$�]05Z�[$&�wZB쿲��=��Q��wX5\�Y"$�
��H
$��
�L�5Z�AB틁��
�wV

"�S�$�uT(�AB"푮��i�R
�/�D�Y&�w`"�Y$�
�
$�Y$�wR(�[,&(�]*���[(�[($R
豈��m�\��.��FV�]�L�d�5B	樂�(�s(�,�P* R讀�^�m�~�{h3�F�N�s�K�F�:��O�3�f�s�t��;��6�P�9�B,量�W�@�9�8
�_�n��|
�	��
�l�E���)�D�_�j�B咽�/�7� �F��w��/�l�wN�B瑩�g�V�R切��K�V�$��>�i�x�o�*�TB郞�C�4��]�a�� �D�)�b` ^a
²�K�]�W�h�o�@�~���|�	�$�@�.bE–mrt�ui��M�O��m�q	�M	�C�V�+�-��@�6�w��\�<�:��]�;�@�G�!�Z�C�t�/�1�3�5�7�9�;�=�?�A�A�C��i�j�f��^�}��Ue	xv�k>�S�@�
�&�@6�E�z�f�@#�u��@�4�q���'�D�s�d��`�~�K	�U�Q�g��	�i�x�@���:��D�@�G��b�,架�x��Y�G��m�>�A�E�M�T�V�Y�v�b�e�G�h�j�n�t�v�y�}���b�
�
����N��$�&�N�*&�3�6�:�C�>�@�D�b�H�L&�R�F�V�X&�]�`�G�c�f�i
�o�q�u�y�{�F�~6��M����m�#���C� �#�&�(�j�.&�2�M�6�:�>�@��N�D�J�f�M
�S&�Y�N�]
�a�e�i�k�q�N�v�x�{�v�����
�
���F���"�"�N�'�+��4
�7�Z�>�A�C��M
�O�N�R
�U�X�[�^�a�d�i
�n�r�w�z�D����~�
���
��N���$�b�(�+�/��=�f�A�E�z�X�[�^�f�c�f�h�j�n�N�r�t�w�|�~�~����	��N���~��
�"
�&�S�-&�0�3�5�Z�;�@
�D�N�H�J�N�T�V�Y�v�^�b�B�e�g�l
�p�t�v�b�}��R�&����!�Z�(�,
�0�N�4�7�9�;�?�J�C�F�K�N�Q�V�X�E�\6�`�N�e�h�j�o�q�t�Q�x"�}���^�	����E�	�#�'�7�N�=�?�D�I�N
�S�N�W�Y�]�v�a�d�g�j�l�o�q�s�v�Q�{"��M�����f����"�y�(�v�3�7�f�:�?�B�E�J�L�O�N�T�V�Y�v�\�_
�b�b�g�i�k�b�n�r�z�x����f��

�����B��� �"�%�)�f�2�7�9�;�=�A�N�E�G�J�N�P�b�W
�]
�b�m�k�p
�s�N�w
�{�~���]�	���J��� �"�f�&�)�+
�1�3�K�6�8�:�?�A�~�D�I�K�Z�Q�T�W
�Z��c�f�f�j�z�s�N�y&���J��	�������� �N�%	�*�/�1�3�E�7�@�D
�H�M�O�@�S�U�[�]�_�b�f�J�l�p�r�u�{�N�
��
���E�
�����!�J�$�(
�,�0
�5�N�:�=�I�L�D�O�R�W�Z�^�`�d�h�B�k�m�v�w�|����Z��"��N���.�$�V�)
�-�1�F�4�7��>�B�f�I�M�f�Q�T�W�Z�\�^�d�f�i�N�m�o�~�u�y�{���N�
����f����R�'�+�-�/�5�7�E�;�?�A�~�J�N�P�S�N�W��a�e��p�f�v�z���f���f���
� �N�$
�(�,�.�1�v�4�7�9�;�F�>�B�I�K�b�O�S
�W�n�b�f���p�N�t&�|��f���J��������~� �N�%�'�*�.��7�;�z�H�L�B�S�W&�_�N�e�k
�m�N�q
�u
�{�}����F�
6��N�����Z� �""�(�,�z�9�N�?�
�S&�V�N�Z
�^�c
�g�N�k�m�~�r�w
�{��J���	"��N�
�� �Z�%�v�16�>�Z�C�G
�K�Z�R�U�Y�a�J�d�hb朞�s��g���5�O��N����\b1忘�Z���"����M�j�����"�*�6�9�C�!�%�,�0�A�7�=�A�V�[�|��I�M�S�F�a�g�q�t�w�{�~���
���G�C�L�P�e�n�s�w��� �$�-b白���u�ub峯�h��e�M��w�
b隧�tb窩�Ub幼�-��J�2��9���?��p�Sb慘�-b閑�]b晃�g��5�Gb熹�qb⁺�/�:�q�!�)��D���
�,�	�D�@�N�z	����b‹�$�j����)�e�#�/�r�W�	�ab⇄�@b⒃�Rb� 〞�j�j�U��r�u�w�y�������A	��"�(�+�.�A�C�F�G�L�N�Q�T�\�c�e�m�B�p�r�u�x��	�����A���%�+�.�1�9�;�A�C�F�@�I�O�U�X�[�d�l�n�p�H�v�{�}
����#�B�+�-�0�8�@�C�F�Y�[�C�]�e�k����G����!�$�&�(�+�3�@�5�9.�?�E�o�r�z�
��
�����@��&�*�,�G�K�H�T�X
�^�i�o��A�������9�;�A�>�F�K
�R�]�c�e�h��B�o�q�x�{�~	���@�&�*�/�5�8�V�A�X�[�d�k�m�u�w�z�@�}���
�
���@�2�;�?�D�I�K�N�V�B�\�^�a�h�j�l�o�r�	��C����
� �+�@�B�N�Q�T�\�d�f�i�s�@�u�y�{�}����&�@�)�A�D�G	�N�X�^�@�a�e�h�n�p�s�z�~�
��@�����$�*�,	�0�C�3�5�8�@"�F�@�i�~����
��F���!
�$�/�5�8�@�K�O�Q�U
�\�^�a�d�w�@�z�
����
�'�@�2�B�D�G�P�V�A�i�p��
�
���A� �#�&�-�1�7�9�<�C�I�@�P�d�f�h�p�u�w�z�F���	���@�2�R�T�X�]�b�A�d�g�o�u��@��+�-�0�<�B
�I�@�T�\�w�z�@��-�/�2�8
�<�@�J�^�y�|��B�!�	�+�-�/�7�@�;�?�B�E	�L�V�Y�\�e�k�A�m�p�x�{�}�����@�!��A�C�F�F�N�S�U�X�a�g�i�l�t�A�z�}�
���� �&�)�@�,�9�S�Z�]�`�F�i�o�r�u����
��@���,�3�N�A�P�S�Z�]�c�e�h�q�w�y�|�@����$�&�)�F�2�8�;�B�E�G�J�R�B�W�]!�i��
�C�����!�)�/�1�4�@�;�?�C�_�c�j�@�l�p�s!��$�A�'�*�4�:�=�@�I�R�E�U�[�_�b�e�n�v�@�	����*�-�0�7�9�A�A�D�G�N#�P�t�v�D�y
���������F�� �"�$!�0�@�R�V�X�[�b�g�i�k�s�y�A�{�~��
���'�*�@�-�3�9�<�K�R�A�e�l�n�q	�x���B��� �'�+
�2�=�D�@�K�_�a�d�p�v�y�|�A�������@�9�=�?�C�J�N�P�T�Z�_�A�a�d�l
�r��@��'�)�+�2�4�:�B�N
�T!�c�B���
�)��@�>�R�m�@�|���	�����@;�4�p�@�s��
�
���@�-�=�?�B�K�R�T�F�Z�]�_�a�d�l�r�t�w��A��	����� �%�*�,�A�/�7�=�?�B�J�P�S�V�@�]�b�i�m�t�w�y�{�~�
�@�������&�)�,�4�<�A�>�A�J�P�R�T
�`�@�k�s�z�|���
����F��$�*�1�4�:�<�?�G�@�M�i�k�m�u�{�A�~��������@�0!�5�W�Y�]�b�d�@�f�j�m2�p�A�#�:�<�?�D�G�M�O�@�V�^�e�x�����@���1�3�6�=�@�A�F�Y�`�{�~�@(��*�0�2�6�C	�:�D�G�J
�Q�\�^�a�j�@�l�p�s�x�|�~��	��A����� �#�&	�.�8�H�<�C�E�H�Q�X�\�F�`�c�f�i�q�x�z�~	��B��	���
��'�*�0�2�A�5�>�D�F�I�R�X�Z�]�F�d�j�l�o�v�x�~���
�
�@�����!�'�)�,�6�:�A�<�?�G�J�M�T�V�^�`�D�d�h�l�p�v�{�}��F������#�&�)�B�<�>�B�I�N�P�S�\�b�e�A�h�o�q�w����H��"�(�2�8�K�@�M�Q�T�W�`�f�h�x�~�A���
��#
�*�@�5�E�G�J�R�Z�]�`�g�@�j�r�t�w!��B�"�$�'�.�1�5�7�<�B�D�A�G�O�U�X�h�@�~�
�����1�@�3�7"�J�m�A�o�r�|�
��
��@�#�+�2�5�8�?�B�^�d�f�i�p�s�y��@��.�I�@�P�d�s�v�@�����$�'�+�/�H�8�<�>�A�I�O�T�[	�_�@�i��������@��"�(�+�:�A�D�B�T�Z�\�_�h�n�q�t��@��"�%�(�1�7�@�>
�F�Q�S�V�_�e�@�t�x������(�@�*�.�1�4�;�?�E�G�J	�S�@�]�u�w�y���	��@����� �(�.�B!�A�c�e�g�o�B�u�w�z����	���������@��$��8��:��=��F��L��N��Q�A��X��\��b��v��|���@������1��3��6��?��E��A��H��K��W��]��q��w��y��|�@����������
����#��%��(��/�B!��2��T��V��Y��b�B��h��j��m��t��z��|������
�@��!��!��C��E��I�F��O��U��W��Z��c��i��l��{�@��~����������!��*��/�A��2��I��d��g�@��n��v��|�������F��%��+��>��E�B��`��b��e��l��o��u��x��{���������D��������"��%	��,��6��8��;�F��D��K��O��U��X��Z��\��_��g�@��i5��m��#�A��%��(��0��6��8��;��D��J��M��P�@��S��W��Y��_��f��o��w��y��|�F����!���A��A��D��[��^��a��j��r��t�D��w��~�����@��.��:��=��@��G��I��Q��T��W��^��a�@��c��g��j��m#��u���A������'��-��/��2��6��:��@��B��E�@��H��M��S��g�@����	������������!��*�B��0��2��5��=��C��F��I	��Q��[��]�A��`��d��h��k��m��o	��r��}��
���@������������)��/��2��5��>�@��B��F��H��K��O��Q��T��Z��]��`��g��k��q�A��s��v������
��
��������!�@��$��(��+��1��4��<��?��A��D��G��N�B��Q��W��Y��\��ePK
!<���B��1chrome/pdfjs/content/web/cmaps/UniKS-UTF8-V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�UniKS-UTF8-HB‖�}��m ��m
,�^7�<b–�{��l�x	����r�
��{�PK
!<��5e��&chrome/pdfjs/content/web/cmaps/V.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�Ha!"�O
�Q	�S�V�[A!a�m�?�2a%u�PK
!<�!�ų�.chrome/pdfjs/content/web/cmaps/WP-Symbol.bcmap�RCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSE�p�x�z���}	��z�T�{�V�|�e�X�}��[�V��c�[��0��W`a��y�PK
!<̵�,u	u	%chrome/pdfjs/content/web/debugger.css/* Copyright 2014 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

:root {
  --panel-width: 300px;
}

#PDFBug,
#PDFBug :is(input, button, select) {
  font: message-box;
}
#PDFBug {
  background-color: rgb(255 255 255);
  border: 1px solid rgb(102 102 102);
  position: fixed;
  top: 32px;
  right: 0;
  bottom: 0;
  font-size: 10px;
  padding: 0;
  width: var(--panel-width);
}
#PDFBug .controls {
  background: rgb(238 238 238);
  border-bottom: 1px solid rgb(102 102 102);
  padding: 3px;
}
#PDFBug .panels {
  inset: 27px 0 0;
  overflow: auto;
  position: absolute;
}
#PDFBug .panels > div {
  padding: 5px;
}
#PDFBug button.active {
  font-weight: bold;
}
.debuggerShowText,
.debuggerHideText:hover {
  background-color: rgb(255 255 0 / 0.25);
}
#PDFBug .stats {
  font-family: courier;
  font-size: 10px;
  white-space: pre;
}
#PDFBug .stats .title {
  font-weight: bold;
}
#PDFBug table {
  font-size: 10px;
  white-space: pre;
}
#PDFBug table.showText {
  border-collapse: collapse;
  text-align: center;
}
#PDFBug table.showText,
#PDFBug table.showText :is(tr, td) {
  border: 1px solid black;
  padding: 1px;
}
#PDFBug table.showText td.advance {
  color: grey;
}

#viewer.textLayer-visible .textLayer {
  opacity: 1;
}

#viewer.textLayer-visible .canvasWrapper {
  background-color: rgb(128 255 128);
}

#viewer.textLayer-visible .canvasWrapper canvas {
  mix-blend-mode: screen;
}

#viewer.textLayer-visible .textLayer span {
  background-color: rgb(255 255 0 / 0.1);
  color: rgb(0 0 0);
  border: solid 1px rgb(255 0 0 / 0.5);
  box-sizing: border-box;
}

#viewer.textLayer-visible .textLayer span[aria-owns] {
  background-color: rgb(255 0 0 / 0.3);
}

#viewer.textLayer-hover .textLayer span:hover {
  background-color: rgb(255 255 255);
  color: rgb(0 0 0);
}

#viewer.textLayer-shadow .textLayer span {
  background-color: rgb(255 255 255 / 0.6);
  color: rgb(0 0 0);
}
PK
!<��SFSF%chrome/pdfjs/content/web/debugger.mjs/* Copyright 2012 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

const { OPS } = globalThis.pdfjsLib || (await import("pdfjs-lib"));

const opMap = Object.create(null);
for (const key in OPS) {
  opMap[OPS[key]] = key;
}

const FontInspector = (function FontInspectorClosure() {
  let fonts;
  let active = false;
  const fontAttribute = "data-font-name";
  function removeSelection() {
    const divs = document.querySelectorAll(`span[${fontAttribute}]`);
    for (const div of divs) {
      div.className = "";
    }
  }
  function resetSelection() {
    const divs = document.querySelectorAll(`span[${fontAttribute}]`);
    for (const div of divs) {
      div.className = "debuggerHideText";
    }
  }
  function selectFont(fontName, show) {
    const divs = document.querySelectorAll(
      `span[${fontAttribute}=${fontName}]`
    );
    for (const div of divs) {
      div.className = show ? "debuggerShowText" : "debuggerHideText";
    }
  }
  function textLayerClick(e) {
    if (
      !e.target.dataset.fontName ||
      e.target.tagName.toUpperCase() !== "SPAN"
    ) {
      return;
    }
    const fontName = e.target.dataset.fontName;
    const selects = document.getElementsByTagName("input");
    for (const select of selects) {
      if (select.dataset.fontName !== fontName) {
        continue;
      }
      select.checked = !select.checked;
      selectFont(fontName, select.checked);
      select.scrollIntoView();
    }
  }
  return {
    // Properties/functions needed by PDFBug.
    id: "FontInspector",
    name: "Font Inspector",
    panel: null,
    manager: null,
    init() {
      const panel = this.panel;
      const tmp = document.createElement("button");
      tmp.addEventListener("click", resetSelection);
      tmp.textContent = "Refresh";
      panel.append(tmp);

      fonts = document.createElement("div");
      panel.append(fonts);
    },
    cleanup() {
      fonts.textContent = "";
    },
    enabled: false,
    get active() {
      return active;
    },
    set active(value) {
      active = value;
      if (active) {
        document.body.addEventListener("click", textLayerClick, true);
        resetSelection();
      } else {
        document.body.removeEventListener("click", textLayerClick, true);
        removeSelection();
      }
    },
    // FontInspector specific functions.
    fontAdded(fontObj, url) {
      function properties(obj, list) {
        const moreInfo = document.createElement("table");
        for (const entry of list) {
          const tr = document.createElement("tr");
          const td1 = document.createElement("td");
          td1.textContent = entry;
          tr.append(td1);
          const td2 = document.createElement("td");
          td2.textContent = obj[entry].toString();
          tr.append(td2);
          moreInfo.append(tr);
        }
        return moreInfo;
      }

      const moreInfo = fontObj.css
        ? properties(fontObj, ["baseFontName"])
        : properties(fontObj, ["name", "type"]);

      const fontName = fontObj.loadedName;
      const font = document.createElement("div");
      const name = document.createElement("span");
      name.textContent = fontName;
      let download;
      if (!fontObj.css) {
        download = document.createElement("a");
        if (url) {
          url = /url\(['"]?([^)"']+)/.exec(url);
          download.href = url[1];
        } else if (fontObj.data) {
          download.href = URL.createObjectURL(
            new Blob([fontObj.data], { type: fontObj.mimetype })
          );
        }
        download.textContent = "Download";
      }

      const logIt = document.createElement("a");
      logIt.href = "";
      logIt.textContent = "Log";
      logIt.addEventListener("click", function (event) {
        event.preventDefault();
        console.log(fontObj);
      });
      const select = document.createElement("input");
      select.setAttribute("type", "checkbox");
      select.dataset.fontName = fontName;
      select.addEventListener("click", function () {
        selectFont(fontName, select.checked);
      });
      if (download) {
        font.append(select, name, " ", download, " ", logIt, moreInfo);
      } else {
        font.append(select, name, " ", logIt, moreInfo);
      }
      fonts.append(font);
      // Somewhat of a hack, should probably add a hook for when the text layer
      // is done rendering.
      setTimeout(() => {
        if (this.active) {
          resetSelection();
        }
      }, 2000);
    },
  };
})();

// Manages all the page steppers.
const StepperManager = (function StepperManagerClosure() {
  let steppers = [];
  let stepperDiv = null;
  let stepperControls = null;
  let stepperChooser = null;
  let breakPoints = Object.create(null);
  return {
    // Properties/functions needed by PDFBug.
    id: "Stepper",
    name: "Stepper",
    panel: null,
    manager: null,
    init() {
      const self = this;
      stepperControls = document.createElement("div");
      stepperChooser = document.createElement("select");
      stepperChooser.addEventListener("change", function (event) {
        self.selectStepper(this.value);
      });
      stepperControls.append(stepperChooser);
      stepperDiv = document.createElement("div");
      this.panel.append(stepperControls, stepperDiv);
      if (sessionStorage.getItem("pdfjsBreakPoints")) {
        breakPoints = JSON.parse(sessionStorage.getItem("pdfjsBreakPoints"));
      }
    },
    cleanup() {
      stepperChooser.textContent = "";
      stepperDiv.textContent = "";
      steppers = [];
    },
    enabled: false,
    active: false,
    // Stepper specific functions.
    create(pageIndex) {
      const debug = document.createElement("div");
      debug.id = "stepper" + pageIndex;
      debug.hidden = true;
      debug.className = "stepper";
      stepperDiv.append(debug);
      const b = document.createElement("option");
      b.textContent = "Page " + (pageIndex + 1);
      b.value = pageIndex;
      stepperChooser.append(b);
      const initBreakPoints = breakPoints[pageIndex] || [];
      const stepper = new Stepper(debug, pageIndex, initBreakPoints);
      steppers.push(stepper);
      if (steppers.length === 1) {
        this.selectStepper(pageIndex, false);
      }
      return stepper;
    },
    selectStepper(pageIndex, selectPanel) {
      pageIndex |= 0;
      if (selectPanel) {
        this.manager.selectPanel(this);
      }
      for (const stepper of steppers) {
        stepper.panel.hidden = stepper.pageIndex !== pageIndex;
      }
      for (const option of stepperChooser.options) {
        option.selected = (option.value | 0) === pageIndex;
      }
    },
    saveBreakPoints(pageIndex, bps) {
      breakPoints[pageIndex] = bps;
      sessionStorage.setItem("pdfjsBreakPoints", JSON.stringify(breakPoints));
    },
  };
})();

// The stepper for each page's operatorList.
class Stepper {
  // Shorter way to create element and optionally set textContent.
  #c(tag, textContent) {
    const d = document.createElement(tag);
    if (textContent) {
      d.textContent = textContent;
    }
    return d;
  }

  #simplifyArgs(args) {
    if (typeof args === "string") {
      const MAX_STRING_LENGTH = 75;
      return args.length <= MAX_STRING_LENGTH
        ? args
        : args.substring(0, MAX_STRING_LENGTH) + "...";
    }
    if (typeof args !== "object" || args === null) {
      return args;
    }
    if ("length" in args) {
      // array
      const MAX_ITEMS = 10,
        simpleArgs = [];
      let i, ii;
      for (i = 0, ii = Math.min(MAX_ITEMS, args.length); i < ii; i++) {
        simpleArgs.push(this.#simplifyArgs(args[i]));
      }
      if (i < args.length) {
        simpleArgs.push("...");
      }
      return simpleArgs;
    }
    const simpleObj = {};
    for (const key in args) {
      simpleObj[key] = this.#simplifyArgs(args[key]);
    }
    return simpleObj;
  }

  constructor(panel, pageIndex, initialBreakPoints) {
    this.panel = panel;
    this.breakPoint = 0;
    this.nextBreakPoint = null;
    this.pageIndex = pageIndex;
    this.breakPoints = initialBreakPoints;
    this.currentIdx = -1;
    this.operatorListIdx = 0;
    this.indentLevel = 0;
  }

  init(operatorList) {
    const panel = this.panel;
    const content = this.#c("div", "c=continue, s=step");
    const table = this.#c("table");
    content.append(table);
    table.cellSpacing = 0;
    const headerRow = this.#c("tr");
    table.append(headerRow);
    headerRow.append(
      this.#c("th", "Break"),
      this.#c("th", "Idx"),
      this.#c("th", "fn"),
      this.#c("th", "args")
    );
    panel.append(content);
    this.table = table;
    this.updateOperatorList(operatorList);
  }

  updateOperatorList(operatorList) {
    const self = this;

    function cboxOnClick() {
      const x = +this.dataset.idx;
      if (this.checked) {
        self.breakPoints.push(x);
      } else {
        self.breakPoints.splice(self.breakPoints.indexOf(x), 1);
      }
      StepperManager.saveBreakPoints(self.pageIndex, self.breakPoints);
    }

    const MAX_OPERATORS_COUNT = 15000;
    if (this.operatorListIdx > MAX_OPERATORS_COUNT) {
      return;
    }

    const chunk = document.createDocumentFragment();
    const operatorsToDisplay = Math.min(
      MAX_OPERATORS_COUNT,
      operatorList.fnArray.length
    );
    for (let i = this.operatorListIdx; i < operatorsToDisplay; i++) {
      const line = this.#c("tr");
      line.className = "line";
      line.dataset.idx = i;
      chunk.append(line);
      const checked = this.breakPoints.includes(i);
      const args = operatorList.argsArray[i] || [];

      const breakCell = this.#c("td");
      const cbox = this.#c("input");
      cbox.type = "checkbox";
      cbox.className = "points";
      cbox.checked = checked;
      cbox.dataset.idx = i;
      cbox.onclick = cboxOnClick;

      breakCell.append(cbox);
      line.append(breakCell, this.#c("td", i.toString()));
      const fn = opMap[operatorList.fnArray[i]];
      let decArgs = args;
      if (fn === "showText") {
        const glyphs = args[0];
        const charCodeRow = this.#c("tr");
        const fontCharRow = this.#c("tr");
        const unicodeRow = this.#c("tr");
        for (const glyph of glyphs) {
          if (typeof glyph === "object" && glyph !== null) {
            charCodeRow.append(this.#c("td", glyph.originalCharCode));
            fontCharRow.append(this.#c("td", glyph.fontChar));
            unicodeRow.append(this.#c("td", glyph.unicode));
          } else {
            // null or number
            const advanceEl = this.#c("td", glyph);
            advanceEl.classList.add("advance");
            charCodeRow.append(advanceEl);
            fontCharRow.append(this.#c("td"));
            unicodeRow.append(this.#c("td"));
          }
        }
        decArgs = this.#c("td");
        const table = this.#c("table");
        table.classList.add("showText");
        decArgs.append(table);
        table.append(charCodeRow, fontCharRow, unicodeRow);
      } else if (fn === "restore" && this.indentLevel > 0) {
        this.indentLevel--;
      }
      line.append(this.#c("td", " ".repeat(this.indentLevel * 2) + fn));
      if (fn === "save") {
        this.indentLevel++;
      }

      if (decArgs instanceof HTMLElement) {
        line.append(decArgs);
      } else {
        line.append(this.#c("td", JSON.stringify(this.#simplifyArgs(decArgs))));
      }
    }
    if (operatorsToDisplay < operatorList.fnArray.length) {
      const lastCell = this.#c("td", "...");
      lastCell.colspan = 4;
      chunk.append(lastCell);
    }
    this.operatorListIdx = operatorList.fnArray.length;
    this.table.append(chunk);
  }

  getNextBreakPoint() {
    this.breakPoints.sort(function (a, b) {
      return a - b;
    });
    for (const breakPoint of this.breakPoints) {
      if (breakPoint > this.currentIdx) {
        return breakPoint;
      }
    }
    return null;
  }

  breakIt(idx, callback) {
    StepperManager.selectStepper(this.pageIndex, true);
    this.currentIdx = idx;

    const listener = evt => {
      switch (evt.keyCode) {
        case 83: // step
          document.removeEventListener("keydown", listener);
          this.nextBreakPoint = this.currentIdx + 1;
          this.goTo(-1);
          callback();
          break;
        case 67: // continue
          document.removeEventListener("keydown", listener);
          this.nextBreakPoint = this.getNextBreakPoint();
          this.goTo(-1);
          callback();
          break;
      }
    };
    document.addEventListener("keydown", listener);
    this.goTo(idx);
  }

  goTo(idx) {
    const allRows = this.panel.getElementsByClassName("line");
    for (const row of allRows) {
      if ((row.dataset.idx | 0) === idx) {
        row.style.backgroundColor = "rgb(251,250,207)";
        row.scrollIntoView();
      } else {
        row.style.backgroundColor = null;
      }
    }
  }
}

const Stats = (function Stats() {
  let stats = [];
  function clear(node) {
    node.textContent = ""; // Remove any `node` contents from the DOM.
  }
  function getStatIndex(pageNumber) {
    for (const [i, stat] of stats.entries()) {
      if (stat.pageNumber === pageNumber) {
        return i;
      }
    }
    return false;
  }
  return {
    // Properties/functions needed by PDFBug.
    id: "Stats",
    name: "Stats",
    panel: null,
    manager: null,
    init() {},
    enabled: false,
    active: false,
    // Stats specific functions.
    add(pageNumber, stat) {
      if (!stat) {
        return;
      }
      const statsIndex = getStatIndex(pageNumber);
      if (statsIndex !== false) {
        stats[statsIndex].div.remove();
        stats.splice(statsIndex, 1);
      }
      const wrapper = document.createElement("div");
      wrapper.className = "stats";
      const title = document.createElement("div");
      title.className = "title";
      title.textContent = "Page: " + pageNumber;
      const statsDiv = document.createElement("div");
      statsDiv.textContent = stat.toString();
      wrapper.append(title, statsDiv);
      stats.push({ pageNumber, div: wrapper });
      stats.sort(function (a, b) {
        return a.pageNumber - b.pageNumber;
      });
      clear(this.panel);
      for (const entry of stats) {
        this.panel.append(entry.div);
      }
    },
    cleanup() {
      stats = [];
      clear(this.panel);
    },
  };
})();

// Manages all the debugging tools.
class PDFBug {
  static #buttons = [];

  static #activePanel = null;

  static tools = [FontInspector, StepperManager, Stats];

  static enable(ids) {
    const all = ids.length === 1 && ids[0] === "all";
    const tools = this.tools;
    for (const tool of tools) {
      if (all || ids.includes(tool.id)) {
        tool.enabled = true;
      }
    }
    if (!all) {
      // Sort the tools by the order they are enabled.
      tools.sort(function (a, b) {
        let indexA = ids.indexOf(a.id);
        indexA = indexA < 0 ? tools.length : indexA;
        let indexB = ids.indexOf(b.id);
        indexB = indexB < 0 ? tools.length : indexB;
        return indexA - indexB;
      });
    }
  }

  static init(container, ids) {
    this.loadCSS();
    this.enable(ids);
    /*
     * Basic Layout:
     * PDFBug
     *  Controls
     *  Panels
     *    Panel
     *    Panel
     *    ...
     */
    const ui = document.createElement("div");
    ui.id = "PDFBug";

    const controls = document.createElement("div");
    controls.setAttribute("class", "controls");
    ui.append(controls);

    const panels = document.createElement("div");
    panels.setAttribute("class", "panels");
    ui.append(panels);

    container.append(ui);
    container.style.right = "var(--panel-width)";

    // Initialize all the debugging tools.
    for (const tool of this.tools) {
      const panel = document.createElement("div");
      const panelButton = document.createElement("button");
      panelButton.textContent = tool.name;
      panelButton.addEventListener("click", event => {
        event.preventDefault();
        this.selectPanel(tool);
      });
      controls.append(panelButton);
      panels.append(panel);
      tool.panel = panel;
      tool.manager = this;
      if (tool.enabled) {
        tool.init();
      } else {
        panel.textContent =
          `${tool.name} is disabled. To enable add "${tool.id}" to ` +
          "the pdfBug parameter and refresh (separate multiple by commas).";
      }
      this.#buttons.push(panelButton);
    }
    this.selectPanel(0);
  }

  static loadCSS() {
    const { url } = import.meta;

    const link = document.createElement("link");
    link.rel = "stylesheet";
    link.href = url.replace(/\.mjs$/, ".css");

    document.head.append(link);
  }

  static cleanup() {
    for (const tool of this.tools) {
      if (tool.enabled) {
        tool.cleanup();
      }
    }
  }

  static selectPanel(index) {
    if (typeof index !== "number") {
      index = this.tools.indexOf(index);
    }
    if (index === this.#activePanel) {
      return;
    }
    this.#activePanel = index;
    for (const [j, tool] of this.tools.entries()) {
      const isActive = j === index;
      this.#buttons[j].classList.toggle("active", isActive);
      tool.active = isActive;
      tool.panel.hidden = !isActive;
    }
  }
}

globalThis.FontInspector = FontInspector;
globalThis.StepperManager = StepperManager;
globalThis.Stats = Stats;

export { PDFBug };
PK
!<��t��/chrome/pdfjs/content/web/images/altText_add.svg<svg width="12" height="13" viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg">
   <path d="M5.375 7.625V11.875C5.375 12.0408 5.44085 12.1997 5.55806 12.3169C5.67527 12.4342 5.83424 12.5 6 12.5C6.16576 12.5 6.32473 12.4342 6.44194 12.3169C6.55915 12.1997 6.625 12.0408 6.625 11.875V7.625L7.125 7.125H11.375C11.5408 7.125 11.6997 7.05915 11.8169 6.94194C11.9342 6.82473 12 6.66576 12 6.5C12 6.33424 11.9342 6.17527 11.8169 6.05806C11.6997 5.94085 11.5408 5.875 11.375 5.875H7.125L6.625 5.375V1.125C6.625 0.95924 6.55915 0.800269 6.44194 0.683058C6.32473 0.565848 6.16576 0.5 6 0.5C5.83424 0.5 5.67527 0.565848 5.55806 0.683058C5.44085 0.800269 5.375 0.95924 5.375 1.125V5.375L4.875 5.875H0.625C0.45924 5.875 0.300269 5.94085 0.183058 6.05806C0.065848 6.17527 0 6.33424 0 6.5C0 6.66576 0.065848 6.82473 0.183058 6.94194C0.300269 7.05915 0.45924 7.125 0.625 7.125H4.762L5.375 7.625Z" fill="black"/>
</svg> 
PK
!<d��RE
E
6chrome/pdfjs/content/web/images/altText_disclaimer.svg<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.49073 1.3015L3.30873 2.1505C3.29349 2.22246 3.25769 2.28844 3.20568 2.34045C3.15368 2.39246 3.08769 2.42826 3.01573 2.4435L2.16673 2.6255C1.76473 2.7125 1.76473 3.2865 2.16673 3.3725L3.01573 3.5555C3.08769 3.57074 3.15368 3.60654 3.20568 3.65855C3.25769 3.71056 3.29349 3.77654 3.30873 3.8485L3.49073 4.6975C3.57773 5.0995 4.15173 5.0995 4.23773 4.6975L4.42073 3.8485C4.43598 3.77654 4.47177 3.71056 4.52378 3.65855C4.57579 3.60654 4.64178 3.57074 4.71373 3.5555L5.56173 3.3725C5.96373 3.2855 5.96373 2.7115 5.56173 2.6255L4.71273 2.4435C4.64083 2.42814 4.57491 2.3923 4.52292 2.34031C4.47093 2.28832 4.43509 2.2224 4.41973 2.1505L4.23773 1.3015C4.15073 0.8995 3.57673 0.8995 3.49073 1.3015ZM10.8647 13.9995C10.4853 14.0056 10.1158 13.8782 9.82067 13.6397C9.52553 13.4013 9.32347 13.0667 9.24973 12.6945L8.89273 11.0275C8.83676 10.7687 8.70738 10.5316 8.52009 10.3445C8.3328 10.1574 8.09554 10.0282 7.83673 9.9725L6.16973 9.6155C5.38873 9.4465 4.86473 8.7975 4.86473 7.9995C4.86473 7.2015 5.38873 6.5525 6.16973 6.3845L7.83673 6.0275C8.09551 5.97135 8.33267 5.84193 8.51992 5.65468C8.70716 5.46744 8.83658 5.23028 8.89273 4.9715L9.25073 3.3045C9.41773 2.5235 10.0667 1.9995 10.8647 1.9995C11.6627 1.9995 12.3117 2.5235 12.4797 3.3045L12.8367 4.9715C12.9507 5.4995 13.3647 5.9135 13.8927 6.0265L15.5597 6.3835C16.3407 6.5525 16.8647 7.2015 16.8647 7.9995C16.8647 8.7975 16.3407 9.4465 15.5597 9.6145L13.8927 9.9715C13.6337 10.0275 13.3963 10.157 13.209 10.3445C13.0217 10.5319 12.8925 10.7694 12.8367 11.0285L12.4787 12.6945C12.4054 13.0667 12.2036 13.4014 11.9086 13.6399C11.6135 13.8784 11.2441 14.0057 10.8647 13.9995ZM10.8647 3.2495C10.7667 3.2495 10.5337 3.2795 10.4727 3.5655L10.1147 5.2335C10.0081 5.72777 9.76116 6.18082 9.40361 6.53837C9.04606 6.89593 8.59301 7.14283 8.09873 7.2495L6.43173 7.6065C6.14573 7.6685 6.11473 7.9015 6.11473 7.9995C6.11473 8.0975 6.14573 8.3305 6.43173 8.3925L8.09873 8.7495C8.59301 8.85617 9.04606 9.10307 9.40361 9.46062C9.76116 9.81817 10.0081 10.2712 10.1147 10.7655L10.4727 12.4335C10.5337 12.7195 10.7667 12.7495 10.8647 12.7495C10.9627 12.7495 11.1957 12.7195 11.2567 12.4335L11.6147 10.7665C11.7212 10.272 11.9681 9.81878 12.3256 9.46103C12.6832 9.10329 13.1363 8.85624 13.6307 8.7495L15.2977 8.3925C15.5837 8.3305 15.6147 8.0975 15.6147 7.9995C15.6147 7.9015 15.5837 7.6685 15.2977 7.6065L13.6307 7.2495C13.1365 7.14283 12.6834 6.89593 12.3259 6.53837C11.9683 6.18082 11.7214 5.72777 11.6147 5.2335L11.2567 3.5655C11.1957 3.2795 10.9627 3.2495 10.8647 3.2495ZM3.30873 12.1505L3.49073 11.3015C3.57673 10.8995 4.15073 10.8995 4.23773 11.3015L4.41973 12.1505C4.43509 12.2224 4.47093 12.2883 4.52292 12.3403C4.57491 12.3923 4.64083 12.4281 4.71273 12.4435L5.56173 12.6255C5.96373 12.7115 5.96373 13.2855 5.56173 13.3725L4.71273 13.5545C4.64083 13.5699 4.57491 13.6057 4.52292 13.6577C4.47093 13.7097 4.43509 13.7756 4.41973 13.8475L4.23773 14.6965C4.15173 15.0985 3.57773 15.0985 3.49073 14.6965L3.30873 13.8475C3.29337 13.7756 3.25754 13.7097 3.20555 13.6577C3.15356 13.6057 3.08764 13.5699 3.01573 13.5545L2.16673 13.3725C1.76473 13.2865 1.76473 12.7125 2.16673 12.6255L3.01573 12.4435C3.08769 12.4283 3.15368 12.3925 3.20568 12.3405C3.25769 12.2884 3.29349 12.2225 3.30873 12.1505Z" fill="black"/>
</svg>
PK
!<��+??0chrome/pdfjs/content/web/images/altText_done.svg<svg width="12" height="13" viewBox="0 0 12 13" fill="none" xmlns="http://www.w3.org/2000/svg">
  <path d="M6 0.5C5.21207 0.5 4.43185 0.655195 3.7039 0.956723C2.97595 1.25825 2.31451 1.70021 1.75736 2.25736C1.20021 2.81451 0.758251 3.47595 0.456723 4.2039C0.155195 4.93185 0 5.71207 0 6.5C0 7.28793 0.155195 8.06815 0.456723 8.7961C0.758251 9.52405 1.20021 10.1855 1.75736 10.7426C2.31451 11.2998 2.97595 11.7417 3.7039 12.0433C4.43185 12.3448 5.21207 12.5 6 12.5C7.5913 12.5 9.11742 11.8679 10.2426 10.7426C11.3679 9.61742 12 8.0913 12 6.5C12 4.9087 11.3679 3.38258 10.2426 2.25736C9.11742 1.13214 7.5913 0.5 6 0.5ZM5.06 8.9L2.9464 6.7856C2.85273 6.69171 2.80018 6.56446 2.80033 6.43183C2.80048 6.29921 2.85331 6.17207 2.9472 6.0784C3.04109 5.98473 3.16834 5.93218 3.30097 5.93233C3.43359 5.93248 3.56073 5.98531 3.6544 6.0792L5.3112 7.7368L8.3464 4.7008C8.44109 4.6109 8.56715 4.56153 8.69771 4.56322C8.82827 4.56492 8.95301 4.61754 9.04534 4.70986C9.13766 4.80219 9.19028 4.92693 9.19198 5.05749C9.19367 5.18805 9.1443 5.31411 9.0544 5.4088L5.5624 8.9H5.06Z" fill="#FBFBFE"/>
</svg> 
PK
!<������3chrome/pdfjs/content/web/images/altText_spinner.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg id="loading-svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" width="12" height="12">
  <style>
    @keyframes loadingSVGRotate {
      from { rotate: 0; } to { rotate: 360deg }
    }
    #loading-svg {
      animation: loadingSVGRotate 1.2s linear infinite;
      transform-origin: 50% 50%;
    }
  </style>
  <path d="M8.9 3.8c-.2-.2-.1-.5.1-.7.2-.1.6-.1.7.2.5.7.8 1.6.8 2.5 0 2.5-2 4.5-4.5 4.5l0 1.5c0 .2-.2.3-.3.1l-2-1.9 0-.4 1.9-1.9c.2-.2.4-.1.4.1l0 1.5c1.9 0 3.5-1.6 3.5-3.5 0-.7-.2-1.4-.6-2z"/>
  <path d="M3.1 8.2c.2.2.1.5-.1.7-.2.1-.6.1-.7-.2-.5-.7-.8-1.6-.8-2.5 0-2.5 2-4.5 4.5-4.5L6 .2c0-.2.2-.3.3-.1l2 1.9 0 .4-2 2c-.1.1-.3 0-.3-.2l0-1.5c-1.9 0-3.5 1.6-3.5 3.5 0 .7.2 1.4.6 2z"/>
</svg>
PK
!<|�C��3chrome/pdfjs/content/web/images/altText_warning.svg<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
    <path fill-rule="evenodd" clip-rule="evenodd" d="M8.78182 2.63903C8.58882 2.28803 8.25782 2.25003 8.12482 2.25003C7.99019 2.24847 7.85771 2.28393 7.74185 2.35253C7.62599 2.42113 7.5312 2.52023 7.46782 2.63903L1.97082 12.639C1.90673 12.7528 1.87406 12.8816 1.87617 13.0122C1.87828 13.1427 1.91509 13.2704 1.98282 13.382C2.04798 13.4951 2.14207 13.5888 2.25543 13.6535C2.36879 13.7182 2.49732 13.7515 2.62782 13.75H13.6218C13.7523 13.7515 13.8809 13.7182 13.9942 13.6535C14.1076 13.5888 14.2017 13.4951 14.2668 13.382C14.3346 13.2704 14.3714 13.1427 14.3735 13.0122C14.3756 12.8816 14.3429 12.7528 14.2788 12.639L8.78182 2.63903ZM6.37282 2.03703C6.75182 1.34603 7.43882 1.00003 8.12482 1.00003C8.48341 0.997985 8.83583 1.09326 9.14454 1.2757C9.45325 1.45814 9.70668 1.72092 9.87782 2.03603L15.3748 12.036C16.1078 13.369 15.1438 15 13.6228 15H2.62782C1.10682 15 0.141823 13.37 0.875823 12.037L6.37282 2.03703ZM8.74982 9.06203C8.74982 9.22779 8.68397 9.38676 8.56676 9.50397C8.44955 9.62118 8.29058 9.68703 8.12482 9.68703C7.95906 9.68703 7.80009 9.62118 7.68288 9.50397C7.56566 9.38676 7.49982 9.22779 7.49982 9.06203V5.62503C7.49982 5.45927 7.56566 5.3003 7.68288 5.18309C7.80009 5.06588 7.95906 5.00003 8.12482 5.00003C8.29058 5.00003 8.44955 5.06588 8.56676 5.18309C8.68397 5.3003 8.74982 5.45927 8.74982 5.62503V9.06203ZM7.74982 12L7.49982 11.75V11L7.74982 10.75H8.49982L8.74982 11V11.75L8.49982 12H7.74982Z" fill="black"/>
</svg>
PK
!<,ͽ���4chrome/pdfjs/content/web/images/annotation-check.svg<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns="http://www.w3.org/2000/svg"
   width="40"
   height="40"
   viewBox="0 0 40 40">
  <path
     d="M 1.5006714,23.536225 6.8925879,18.994244 14.585721,26.037937 34.019683,4.5410479 38.499329,9.2235032 14.585721,35.458952 z"
     id="path4"
     style="fill:#ffff00;fill-opacity:1;stroke:#000000;stroke-width:1.25402856;stroke-opacity:1" />
</svg>
PK
!<h�ss6chrome/pdfjs/content/web/images/annotation-comment.svg<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns="http://www.w3.org/2000/svg"
   height="40"
   width="40"
   viewBox="0 0 40 40">
  <rect
     style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
     width="33.76017"
     height="33.76017"
     x="3.119915"
     y="3.119915" />
  <path
     d="m 20.677967,8.54499 c -7.342801,0 -13.295293,4.954293 -13.295293,11.065751 0,2.088793 0.3647173,3.484376 1.575539,5.150563 L 6.0267418,31.45501 13.560595,29.011117 c 2.221262,1.387962 4.125932,1.665377 7.117372,1.665377 7.3428,0 13.295291,-4.954295 13.295291,-11.065753 0,-6.111458 -5.952491,-11.065751 -13.295291,-11.065751 z"
     style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.93031836;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/>
</svg>
PK
!<�ڰjxx3chrome/pdfjs/content/web/images/annotation-help.svg<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns="http://www.w3.org/2000/svg"
   width="40"
   height="40"
   viewBox="0 0 40 40">
  <g
     transform="translate(0,-60)"
     id="layer1">
    <rect
       width="36.460953"
       height="34.805603"
       x="1.7695236"
       y="62.597198"
       style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.30826771;stroke-opacity:1" />
    <g
       transform="matrix(0.88763677,0,0,0.88763677,2.2472646,8.9890584)">
      <path
         d="M 20,64.526342 C 11.454135,64.526342 4.5263421,71.454135 4.5263421,80 4.5263421,88.545865 11.454135,95.473658 20,95.473658 28.545865,95.473658 35.473658,88.545865 35.473658,80 35.473658,71.454135 28.545865,64.526342 20,64.526342 z m -0.408738,9.488564 c 3.527079,0 6.393832,2.84061 6.393832,6.335441 0,3.494831 -2.866753,6.335441 -6.393832,6.335441 -3.527079,0 -6.393832,-2.84061 -6.393832,-6.335441 0,-3.494831 2.866753,-6.335441 6.393832,-6.335441 z"
         style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:1.02768445;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
      <path
         d="m 7.2335209,71.819938 4.9702591,4.161823 c -1.679956,2.581606 -1.443939,6.069592 0.159325,8.677725 l -5.1263071,3.424463 c 0.67516,1.231452 3.0166401,3.547686 4.2331971,4.194757 l 3.907728,-4.567277 c 2.541952,1.45975 5.730694,1.392161 8.438683,-0.12614 l 3.469517,6.108336 c 1.129779,-0.44367 4.742234,-3.449633 5.416358,-5.003859 l -5.46204,-4.415541 c 1.44319,-2.424098 1.651175,-5.267515 0.557303,-7.748623 l 5.903195,-3.833951 C 33.14257,71.704996 30.616217,69.018606 29.02952,67.99296 l -4.118813,4.981678 C 22.411934,71.205099 18.900853,70.937534 16.041319,72.32916 l -3.595408,-5.322091 c -1.345962,0.579488 -4.1293881,2.921233 -5.2123901,4.812869 z m 8.1010311,3.426672 c 2.75284,-2.446266 6.769149,-2.144694 9.048998,0.420874 2.279848,2.56557 2.113919,6.596919 -0.638924,9.043185 -2.752841,2.446267 -6.775754,2.13726 -9.055604,-0.428308 -2.279851,-2.565568 -2.107313,-6.589485 0.64553,-9.035751 z"
         style="fill:#000000;fill-opacity:1;stroke:none" />
    </g>
  </g>
</svg>
PK
!<�w2���5chrome/pdfjs/content/web/images/annotation-insert.svg<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns="http://www.w3.org/2000/svg"
   width="64"
   height="64"
   viewBox="0 0 64 64">
  <path
     d="M 32.003143,1.4044602 57.432701,62.632577 6.5672991,62.627924 z"
     style="fill:#ffff00;fill-opacity:0.94117647;fill-rule:nonzero;stroke:#000000;stroke-width:1.00493038;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</svg>
PK
!<����2chrome/pdfjs/content/web/images/annotation-key.svg<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns="http://www.w3.org/2000/svg"
   width="64"
   height="64"
   viewBox="0 0 64 64">
  <path
     d="M 25.470843,9.4933766 C 25.30219,12.141818 30.139101,14.445969 34.704831,13.529144 40.62635,12.541995 41.398833,7.3856498 35.97505,5.777863 31.400921,4.1549155 25.157674,6.5445892 25.470843,9.4933766 z M 4.5246282,17.652051 C 4.068249,11.832873 9.2742983,5.9270407 18.437379,3.0977088 29.751911,-0.87185184 45.495663,1.4008022 53.603953,7.1104009 c 9.275765,6.1889221 7.158128,16.2079421 -3.171076,21.5939521 -1.784316,1.635815 -6.380222,1.21421 -7.068351,3.186186 -1.04003,0.972427 -1.288046,2.050158 -1.232864,3.168203 1.015111,2.000108 -3.831548,1.633216 -3.270553,3.759574 0.589477,5.264544 -0.179276,10.53738 -0.362842,15.806257 -0.492006,2.184998 1.163456,4.574232 -0.734888,6.610642 -2.482919,2.325184 -7.30604,2.189143 -9.193497,-0.274767 -2.733688,-1.740626 -8.254447,-3.615254 -6.104247,-6.339626 3.468112,-1.708686 -2.116197,-3.449897 0.431242,-5.080274 5.058402,-1.39256 -2.393215,-2.304318 -0.146889,-4.334645 3.069198,-0.977415 2.056986,-2.518352 -0.219121,-3.540397 1.876567,-1.807151 1.484149,-4.868919 -2.565455,-5.942205 0.150866,-1.805474 2.905737,-4.136876 -1.679967,-5.20493 C 10.260902,27.882167 4.6872697,22.95045 4.5245945,17.652051 z"
     id="path604"
     style="fill:#ffff00;fill-opacity:1;stroke:#000000;stroke-width:1.72665179;stroke-opacity:1" />
</svg>
PK
!<�g{��;chrome/pdfjs/content/web/images/annotation-newparagraph.svg<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns="http://www.w3.org/2000/svg"
   width="64"
   height="64"
   viewBox="0 0 64 64">
  <path
     d="M 32.003143,10.913072 57.432701,53.086929 6.567299,53.083723 z"
     id="path2985"
     style="fill:#ffff00;fill-opacity:0.94117647;fill-rule:nonzero;stroke:#000000;stroke-width:0.83403099;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</svg>
PK
!<FJcX��5chrome/pdfjs/content/web/images/annotation-noicon.svg<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns="http://www.w3.org/2000/svg"
   width="40"
   height="40"
   viewBox="0 0 40 40">
</svg>
PK
!<�ZS3chrome/pdfjs/content/web/images/annotation-note.svg<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns="http://www.w3.org/2000/svg"
   width="40"
   height="40"
   viewBox="0 0 40 40">
  <rect
     width="36.075428"
     height="31.096582"
     x="1.962286"
     y="4.4517088"
     id="rect4"
     style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.23004246;stroke-opacity:1" />
  <rect
     width="27.96859"
     height="1.5012145"
     x="6.0157046"
     y="10.285"
     id="rect6"
     style="fill:#000000;fill-opacity:1;stroke:none" />
  <rect
     width="27.96859"
     height="0.85783684"
     x="6.0157056"
     y="23.21689"
     id="rect8"
     style="fill:#000000;fill-opacity:1;stroke:none" />
  <rect
     width="27.96859"
     height="0.85783684"
     x="5.8130345"
     y="28.964394"
     id="rect10"
     style="fill:#000000;fill-opacity:1;stroke:none" />
  <rect
     width="27.96859"
     height="0.85783684"
     x="6.0157046"
     y="17.426493"
     id="rect12"
     style="fill:#000000;fill-opacity:1;stroke:none" />
</svg>
PK
!<��F((8chrome/pdfjs/content/web/images/annotation-paperclip.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" height="40" width="40">
  <path d="M9 3.5a1.5 1.5 0 0 0-3-.001v7.95C6 12.83 7.12 14 8.5 14s2.5-1.17 2.5-2.55V5.5a.5.5 0 0 1 1 0v6.03C11.955 13.427 10.405 15 8.5 15S5.044 13.426 5 11.53V3.5a2.5 2.5 0 0 1 5 0v7.003a1.5 1.5 0 0 1-3-.003v-5a.5.5 0 0 1 1 0v5a.5.5 0 0 0 1 0Z"/>
</svg>
PK
!<	]J�ww8chrome/pdfjs/content/web/images/annotation-paragraph.svg<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
   xmlns="http://www.w3.org/2000/svg"
   width="40"
   height="40"
   viewBox="0 0 40 40">
  <rect
     width="33.76017"
     height="33.76017"
     x="3.119915"
     y="3.119915"
     style="fill:#ffff00;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
  <path
     d="m 17.692678,34.50206 0,-16.182224 c -1.930515,-0.103225 -3.455824,-0.730383 -4.57593,-1.881473 -1.12011,-1.151067 -1.680164,-2.619596 -1.680164,-4.405591 0,-1.992435 0.621995,-3.5796849 1.865988,-4.7617553 1.243989,-1.1820288 3.06352,-1.7730536 5.458598,-1.7730764 l 9.802246,0 0,2.6789711 -2.229895,0 0,26.3251486 -2.632515,0 0,-26.3251486 -3.45324,0 0,26.3251486 z"
     style="font-size:29.42051125px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1.07795751;stroke-opacity:1;font-family:Arial;-inkscape-font-specification:Arial" />
</svg>
PK
!<�
�J__6chrome/pdfjs/content/web/images/annotation-pushpin.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" height="40" width="40">
  <path d="M8.156 12.5a.99.99 0 0 0 .707-.294l.523-2.574L10.5 8.499l1.058-1.04 2.65-.601a.996.996 0 0 0 0-1.414l-3.657-3.658a.996.996 0 0 0-1.414 0l-.523 2.576L7.5 5.499 6.442 6.535l-2.65.6a.996.996 0 0 0 0 1.413l3.657 3.658a.999.999 0 0 0 .707.295z"/>
  <path d="M9.842.996c-.386 0-.77.146-1.06.44a.5.5 0 0 0-.136.251l-.492 2.43-1.008 1.03-.953.933-2.511.566a.5.5 0 0 0-.243.133 1.505 1.505 0 0 0-.002 2.123l1.477 1.477-2.768 2.767a.5.5 0 0 0 0 .707.5.5 0 0 0 .708 0l2.767-2.767 1.475 1.474a1.494 1.494 0 0 0 2.123-.002.5.5 0 0 0 .135-.254l.492-2.427 1.008-1.024.953-.937 2.511-.57a.5.5 0 0 0 .243-.132c.586-.58.583-1.543.002-2.125l-3.659-3.656A1.501 1.501 0 0 0 9.842.996Zm.05 1.025a.394.394 0 0 1 .305.12l3.658 3.657c.18.18.141.432.002.627l-2.41.545a.5.5 0 0 0-.24.131L10.15 8.142a.5.5 0 0 0-.007.006L9.029 9.283a.5.5 0 0 0-.133.25l-.48 2.36c-.082.053-.165.109-.26.109a.492.492 0 0 1-.353-.149L4.145 8.195c-.18-.18-.141-.432-.002-.627l2.41-.545a.5.5 0 0 0 .238-.13L7.85 5.857a.5.5 0 0 0 .007-.008l1.114-1.138a.5.5 0 0 0 .133-.25l.472-2.323a.619.619 0 0 1 .317-.117Z"/>
</svg>
PK
!<�16xx>chrome/pdfjs/content/web/images/cursor-editorFreeHighlight.svg<svg width="18" height="19" viewBox="0 0 18 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.2 3.09C12.28 3.01 12.43 3 12.43 3C12.48 3 12.58 3.02 12.66 3.1L14.45 4.89C14.58 5.02 14.58 5.22 14.45 5.35L11.7713 8.02872L9.51628 5.77372L12.2 3.09ZM13.2658 5.12L11.7713 6.6145L10.9305 5.77372L12.425 4.27921L13.2658 5.12Z" fill="#FBFBFE"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.98 9.32L8.23 11.57L10.7106 9.08938L8.45562 6.83438L5.98 9.31V9.32ZM8.23 10.1558L9.29641 9.08938L8.45562 8.24859L7.38921 9.315L8.23 10.1558Z" fill="#FBFBFE"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.1526 13.1816L16.2125 7.1217C16.7576 6.58919 17.05 5.8707 17.05 5.12C17.05 4.36931 16.7576 3.65084 16.2126 3.11834L14.4317 1.33747C13.8992 0.79242 13.1807 0.5 12.43 0.5C11.6643 0.5 10.9529 0.812929 10.4329 1.33289L3.68289 8.08289C3.04127 8.72452 3.00459 9.75075 3.57288 10.4363L1.29187 12.7239C1.09186 12.9245 0.990263 13.1957 1.0007 13.4685L1 14.5C0.447715 14.5 0 14.9477 0 15.5V17.5C0 18.0523 0.447715 18.5 1 18.5H16C16.5523 18.5 17 18.0523 17 17.5V15.5C17 14.9477 16.5523 14.5 16 14.5H10.2325C9.83594 14.5 9.39953 13.9347 10.1526 13.1816ZM4.39 9.85L4.9807 10.4407L2.39762 13.0312H6.63877L7.10501 12.565L7.57125 13.0312H8.88875L15.51 6.41C15.86 6.07 16.05 5.61 16.05 5.12C16.05 4.63 15.86 4.17 15.51 3.83L13.72 2.04C13.38 1.69 12.92 1.5 12.43 1.5C11.94 1.5 11.48 1.7 11.14 2.04L4.39 8.79C4.1 9.08 4.1 9.56 4.39 9.85ZM16 17.5V15.5H1V17.5H16Z" fill="#FBFBFE"/>
<path d="M15.1616 6.05136L15.1616 6.05132L15.1564 6.05645L8.40645 12.8064C8.35915 12.8537 8.29589 12.88 8.23 12.88C8.16411 12.88 8.10085 12.8537 8.05355 12.8064L7.45857 12.2115L7.10501 11.8579L6.75146 12.2115L6.03289 12.93H3.20465L5.33477 10.7937L5.6873 10.4402L5.33426 10.0871L4.74355 9.49645C4.64882 9.40171 4.64882 9.23829 4.74355 9.14355L11.4936 2.39355C11.7436 2.14354 12.0779 2 12.43 2C12.7883 2 13.1179 2.13776 13.3614 2.38839L13.3613 2.38843L13.3664 2.39355L15.1564 4.18355L15.1564 4.18359L15.1616 4.18864C15.4122 4.43211 15.55 4.76166 15.55 5.12C15.55 5.47834 15.4122 5.80789 15.1616 6.05136ZM7.87645 11.9236L8.23 12.2771L8.58355 11.9236L11.0642 9.44293L11.4177 9.08938L11.0642 8.73582L8.80918 6.48082L8.45562 6.12727L8.10207 6.48082L5.62645 8.95645L5.48 9.10289V9.31V9.32V9.52711L5.62645 9.67355L7.87645 11.9236ZM11.4177 8.38227L11.7713 8.73582L12.1248 8.38227L14.8036 5.70355C15.1288 5.37829 15.1288 4.86171 14.8036 4.53645L13.0136 2.74645C12.8186 2.55146 12.5792 2.5 12.43 2.5H12.4134L12.3967 2.50111L12.43 3C12.3967 2.50111 12.3966 2.50112 12.3965 2.50112L12.3963 2.50114L12.3957 2.50117L12.3947 2.50125L12.3924 2.50142L12.387 2.50184L12.3732 2.50311C12.3628 2.50416 12.3498 2.50567 12.3346 2.50784C12.3049 2.51208 12.2642 2.51925 12.2178 2.53146C12.1396 2.55202 11.9797 2.60317 11.8464 2.73645L9.16273 5.42016L8.80918 5.77372L9.16273 6.12727L11.4177 8.38227ZM1.5 16H15.5V17H1.5V16Z" stroke="#15141A"/>
</svg>
PK
!<ֹ����9chrome/pdfjs/content/web/images/cursor-editorFreeText.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2.75H12.5V2.25V1V0.5H12H10.358C9.91165 0.5 9.47731 0.625661 9.09989 0.860442L9.09886 0.861087L8 1.54837L6.89997 0.860979L6.89911 0.860443C6.5218 0.625734 6.08748 0.5 5.642 0.5H4H3.5V1V2.25V2.75H4H5.642C5.66478 2.75 5.6885 2.75641 5.71008 2.76968C5.71023 2.76977 5.71038 2.76986 5.71053 2.76995L6.817 3.461C6.81704 3.46103 6.81709 3.46105 6.81713 3.46108C6.81713 3.46108 6.81713 3.46108 6.81714 3.46109C6.8552 3.48494 6.876 3.52285 6.876 3.567V8V12.433C6.876 12.4771 6.85523 12.515 6.81722 12.5389C6.81715 12.5389 6.81707 12.539 6.817 12.539L5.70953 13.23C5.70941 13.2301 5.70929 13.2302 5.70917 13.2303C5.68723 13.2438 5.6644 13.25 5.641 13.25H4H3.5V13.75V15V15.5H4H5.642C6.08835 15.5 6.52269 15.3743 6.90011 15.1396L6.90086 15.1391L8 14.4526L9.10003 15.14L9.10089 15.1406C9.47831 15.3753 9.91265 15.501 10.359 15.501H12H12.5V15.001V13.751V13.251H12H10.358C10.3352 13.251 10.3115 13.2446 10.2899 13.2313C10.2897 13.2312 10.2896 13.2311 10.2895 13.231L9.183 12.54C9.18298 12.54 9.18295 12.54 9.18293 12.54C9.18291 12.5399 9.18288 12.5399 9.18286 12.5399C9.14615 12.5169 9.125 12.4797 9.125 12.434V8V3.567C9.125 3.52266 9.14603 3.48441 9.18364 3.4606C9.18377 3.46052 9.1839 3.46043 9.18404 3.46035L10.2895 2.76995C10.2896 2.76985 10.2898 2.76975 10.2899 2.76966C10.3119 2.75619 10.3346 2.75 10.358 2.75H12Z" fill="black" stroke="white"/>
</svg>
PK
!<e�b�++4chrome/pdfjs/content/web/images/cursor-editorInk.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.0189877 13.6645L0.612989 10.4635C0.687989 10.0545 0.884989 9.6805 1.18099 9.3825L9.98199 0.5805C10.756 -0.1925 12.015 -0.1945 12.792 0.5805L14.42 2.2085C15.194 2.9835 15.194 4.2435 14.42 5.0185L5.61599 13.8215C5.31999 14.1165 4.94599 14.3125 4.53799 14.3875L1.33599 14.9815C1.26599 14.9935 1.19799 15.0005 1.12999 15.0005C0.832989 15.0005 0.544988 14.8835 0.330988 14.6695C0.0679874 14.4055 -0.0490122 14.0305 0.0189877 13.6645Z" fill="white"/>
<path d="M0.0189877 13.6645L0.612989 10.4635C0.687989 10.0545 0.884989 9.6805 1.18099 9.3825L9.98199 0.5805C10.756 -0.1925 12.015 -0.1945 12.792 0.5805L14.42 2.2085C15.194 2.9835 15.194 4.2435 14.42 5.0185L5.61599 13.8215C5.31999 14.1165 4.94599 14.3125 4.53799 14.3875L1.33599 14.9815C1.26599 14.9935 1.19799 15.0005 1.12999 15.0005C0.832989 15.0005 0.544988 14.8835 0.330988 14.6695C0.0679874 14.4055 -0.0490122 14.0305 0.0189877 13.6645ZM12.472 5.1965L13.632 4.0365L13.631 3.1885L11.811 1.3675L10.963 1.3685L9.80299 2.5285L12.472 5.1965ZM4.31099 13.1585C4.47099 13.1285 4.61799 13.0515 4.73399 12.9345L11.587 6.0815L8.91899 3.4135L2.06599 10.2655C1.94899 10.3835 1.87199 10.5305 1.84099 10.6915L1.36699 13.2485L1.75199 13.6335L4.31099 13.1585Z" fill="black"/>
</svg>
PK
!<f�?>chrome/pdfjs/content/web/images/cursor-editorTextHighlight.svg<svg width="29" height="32" viewBox="0 0 29 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M28 16.75C28.2761 16.75 28.5 16.5261 28.5 16.25V15C28.5 14.7239 28.2761 14.5 28 14.5H26.358C25.9117 14.5 25.4773 14.6257 25.0999 14.8604L25.0989 14.8611L24 15.5484L22.9 14.861L22.8991 14.8604C22.5218 14.6257 22.0875 14.5 21.642 14.5H20C19.7239 14.5 19.5 14.7239 19.5 15V16.25C19.5 16.5261 19.7239 16.75 20 16.75H21.642C21.6648 16.75 21.6885 16.7564 21.7101 16.7697C21.7102 16.7698 21.7104 16.7699 21.7105 16.77L22.817 17.461C22.817 17.461 22.8171 17.4611 22.8171 17.4611C22.8171 17.4611 22.8171 17.4611 22.8171 17.4611C22.8552 17.4849 22.876 17.5229 22.876 17.567V22.625V27.683C22.876 27.7271 22.8552 27.765 22.8172 27.7889C22.8171 27.7889 22.8171 27.789 22.817 27.789L21.7095 28.48C21.7094 28.4801 21.7093 28.4802 21.7092 28.4803C21.6872 28.4938 21.6644 28.5 21.641 28.5H20C19.7239 28.5 19.5 28.7239 19.5 29V30.25C19.5 30.5261 19.7239 30.75 20 30.75H21.642C22.0883 30.75 22.5227 30.6243 22.9001 30.3896L22.9009 30.3891L24 29.7026L25.1 30.39L25.1009 30.3906C25.4783 30.6253 25.9127 30.751 26.359 30.751H28C28.2761 30.751 28.5 30.5271 28.5 30.251V29.001C28.5 28.7249 28.2761 28.501 28 28.501H26.358C26.3352 28.501 26.3115 28.4946 26.2899 28.4813C26.2897 28.4812 26.2896 28.4811 26.2895 28.481L25.183 27.79C25.183 27.79 25.183 27.79 25.1829 27.79C25.1829 27.7899 25.1829 27.7899 25.1829 27.7899C25.1462 27.7669 25.125 27.7297 25.125 27.684V22.625V17.567C25.125 17.5227 25.146 17.4844 25.1836 17.4606C25.1838 17.4605 25.1839 17.4604 25.184 17.4603L26.2895 16.77C26.2896 16.7699 26.2898 16.7698 26.2899 16.7697C26.3119 16.7562 26.3346 16.75 26.358 16.75H28Z" fill="black" stroke="#FBFBFE" stroke-linejoin="round"/>
<path d="M24.625 17.567C24.625 17.35 24.735 17.152 24.918 17.037L26.026 16.345C26.126 16.283 26.24 16.25 26.358 16.25H28V15H26.358C26.006 15 25.663 15.099 25.364 15.285L24.256 15.978C24.161 16.037 24.081 16.113 24 16.187C23.918 16.113 23.839 16.037 23.744 15.978L22.635 15.285C22.336 15.099 21.993 15 21.642 15H20V16.25H21.642C21.759 16.25 21.874 16.283 21.974 16.345L23.082 17.037C23.266 17.152 23.376 17.35 23.376 17.567V22.625V27.683C23.376 27.9 23.266 28.098 23.082 28.213L21.973 28.905C21.873 28.967 21.759 29 21.641 29H20V30.25H21.642C21.994 30.25 22.337 30.151 22.636 29.965L23.744 29.273C23.84 29.213 23.919 29.137 24 29.064C24.081 29.137 24.161 29.213 24.256 29.273L25.365 29.966C25.664 30.152 26.007 30.251 26.359 30.251H28V29.001H26.358C26.241 29.001 26.126 28.968 26.026 28.906L24.918 28.214C24.734 28.099 24.625 27.901 24.625 27.684V22.625V17.567Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.2 2.59C12.28 2.51 12.43 2.5 12.43 2.5C12.48 2.5 12.58 2.52 12.66 2.6L14.45 4.39C14.58 4.52 14.58 4.72 14.45 4.85L11.7713 7.52872L9.51628 5.27372L12.2 2.59ZM13.2658 4.62L11.7713 6.1145L10.9305 5.27372L12.425 3.77921L13.2658 4.62Z" fill="#FBFBFE"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.98 8.82L8.23 11.07L10.7106 8.58938L8.45562 6.33438L5.98 8.81V8.82ZM8.23 9.65579L9.29641 8.58938L8.45562 7.74859L7.38921 8.815L8.23 9.65579Z" fill="#FBFBFE"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.1526 12.6816L16.2125 6.6217C16.7576 6.08919 17.05 5.3707 17.05 4.62C17.05 3.86931 16.7576 3.15084 16.2126 2.61834L14.4317 0.837474C13.8992 0.29242 13.1807 0 12.43 0C11.6643 0 10.9529 0.312929 10.4329 0.832893L3.68289 7.58289C3.04127 8.22452 3.00459 9.25075 3.57288 9.93634L1.29187 12.2239C1.09186 12.4245 0.990263 12.6957 1.0007 12.9685L1 14C0.447715 14 0 14.4477 0 15V17C0 17.5523 0.447715 18 1 18H16C16.5523 18 17 17.5523 17 17V15C17 14.4477 16.5523 14 16 14H10.2325C9.83594 14 9.39953 13.4347 10.1526 12.6816ZM4.39 9.35L4.9807 9.9407L2.39762 12.5312H6.63877L7.10501 12.065L7.57125 12.5312H8.88875L15.51 5.91C15.86 5.57 16.05 5.11 16.05 4.62C16.05 4.13 15.86 3.67 15.51 3.33L13.72 1.54C13.38 1.19 12.92 1 12.43 1C11.94 1 11.48 1.2 11.14 1.54L4.39 8.29C4.1 8.58 4.1 9.06 4.39 9.35ZM16 17V15H1V17H16Z" fill="#FBFBFE"/>
<path d="M15.1616 5.55136L15.1616 5.55132L15.1564 5.55645L8.40645 12.3064C8.35915 12.3537 8.29589 12.38 8.23 12.38C8.16411 12.38 8.10085 12.3537 8.05355 12.3064L7.45857 11.7115L7.10501 11.3579L6.75146 11.7115L6.03289 12.43H3.20465L5.33477 10.2937L5.6873 9.94019L5.33426 9.58715L4.74355 8.99645C4.64882 8.90171 4.64882 8.73829 4.74355 8.64355L11.4936 1.89355C11.7436 1.64354 12.0779 1.5 12.43 1.5C12.7883 1.5 13.1179 1.63776 13.3614 1.88839L13.3613 1.88843L13.3664 1.89355L15.1564 3.68355L15.1564 3.68359L15.1616 3.68864C15.4122 3.93211 15.55 4.26166 15.55 4.62C15.55 4.97834 15.4122 5.30789 15.1616 5.55136ZM5.48 8.82V9.02711L5.62645 9.17355L7.87645 11.4236L8.23 11.7771L8.58355 11.4236L11.0642 8.94293L11.4177 8.58938L11.0642 8.23582L8.80918 5.98082L8.45562 5.62727L8.10207 5.98082L5.62645 8.45645L5.48 8.60289V8.81V8.82ZM11.4177 7.88227L11.7713 8.23582L12.1248 7.88227L14.8036 5.20355C15.1288 4.87829 15.1288 4.36171 14.8036 4.03645L13.0136 2.24645C12.8186 2.05146 12.5792 2 12.43 2H12.4134L12.3967 2.00111L12.43 2.5C12.3967 2.00111 12.3966 2.00112 12.3965 2.00112L12.3963 2.00114L12.3957 2.00117L12.3947 2.00125L12.3924 2.00142L12.387 2.00184L12.3732 2.00311C12.3628 2.00416 12.3498 2.00567 12.3346 2.00784C12.3049 2.01208 12.2642 2.01925 12.2178 2.03146C12.1396 2.05202 11.9797 2.10317 11.8464 2.23645L9.16273 4.92016L8.80918 5.27372L9.16273 5.62727L11.4177 7.88227ZM1.5 16.5V15.5H15.5V16.5H1.5Z" stroke="#15141A"/>
</svg>
PK
!<u�R��9chrome/pdfjs/content/web/images/editor-toolbar-delete.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
    <path fill-rule="evenodd" clip-rule="evenodd"
        d="M11 3H13.6C14 3 14.3 3.3 14.3 3.6C14.3 3.9 14 4.2 13.7 4.2H13.3V14C13.3 15.1 12.4 16 11.3 16H4.80005C3.70005 16 2.80005 15.1 2.80005 14V4.2H2.40005C2.00005 4.2 1.80005 4 1.80005 3.6C1.80005 3.2 2.00005 3 2.40005 3H5.00005V2C5.00005 0.9 5.90005 0 7.00005 0H9.00005C10.1 0 11 0.9 11 2V3ZM6.90005 1.2L6.30005 1.8V3H9.80005V1.8L9.20005 1.2H6.90005ZM11.4 14.7L12 14.1V4.2H4.00005V14.1L4.60005 14.7H11.4ZM7.00005 12.4C7.00005 12.7 6.70005 13 6.40005 13C6.10005 13 5.80005 12.7 5.80005 12.4V7.6C5.70005 7.3 6.00005 7 6.40005 7C6.80005 7 7.00005 7.3 7.00005 7.6V12.4ZM10.2001 12.4C10.2001 12.7 9.90006 13 9.60006 13C9.30006 13 9.00006 12.7 9.00006 12.4V7.6C9.00006 7.3 9.30006 7 9.60006 7C9.90006 7 10.2001 7.3 10.2001 7.6V12.4Z"
        fill="black" />
</svg>PK
!<��BB6chrome/pdfjs/content/web/images/findbarButton-next.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.999 8.352L5.534 13.818C5.41551 13.9303 5.25786 13.9918 5.09466 13.9895C4.93146 13.9872 4.77561 13.9212 4.66033 13.8057C4.54505 13.6902 4.47945 13.5342 4.47752 13.3709C4.47559 13.2077 4.53748 13.0502 4.65 12.932L9.585 7.998L4.651 3.067C4.53862 2.94864 4.47691 2.79106 4.47903 2.62786C4.48114 2.46466 4.54692 2.30874 4.66233 2.19333C4.77774 2.07792 4.93366 2.01215 5.09686 2.01003C5.26006 2.00792 5.41763 2.06962 5.536 2.182L11 7.647L10.999 8.352Z" fill="black"/>
</svg>
PK
!<��BB:chrome/pdfjs/content/web/images/findbarButton-previous.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.001 8.352L10.466 13.818C10.5845 13.9303 10.7421 13.9918 10.9053 13.9895C11.0685 13.9872 11.2244 13.9212 11.3397 13.8057C11.4549 13.6902 11.5205 13.5342 11.5225 13.3709C11.5244 13.2077 11.4625 13.0502 11.35 12.932L6.416 7.999L11.349 3.067C11.4614 2.94864 11.5231 2.79106 11.521 2.62786C11.5189 2.46466 11.4531 2.30874 11.3377 2.19333C11.2223 2.07792 11.0663 2.01215 10.9031 2.01003C10.7399 2.00792 10.5824 2.06962 10.464 2.182L5 7.647L5.001 8.352Z" fill="black"/>
</svg>
PK
!<����	�	0chrome/pdfjs/content/web/images/loading-icon.gifGIF89a�����������ర���莎���Ȝ����ب��������vvv������hhh!�NETSCAPE2.0!�Created with ajaxload.info!�	,�  �$AeZ
�<䠒�ÌQ46�<�A�
��Ha��:��ID0�F��a\xG�3��!�	�O:-��Rj��TJ��*		�
t
��������~�"ds]�
�)t��-"�i;H>�n�Qg]_*�
�R�3��GI?
�˴�v$ý�j3!!�	,�  �$�0eZy�0��q ��P�УW�)";�qX�^�D50���	Ո<H3!��k-na� �(�i���d�$P@yw`�J��#
?�
y
�����o���g��
f���'8��{'Cp`jn�"�
2�{�`x�jy�4�C,�4��o#n�$��ß��!!�	,�  �$� eZ�$�2����q���ҢE� ҉�p$H@D/���G�DÄj8v#��P((Dƶ�� �N�(3ܴ�#y�(@	gUx*
kK�)?K

���������$��"
�*���K��
�W����x��?�G��#�Wк��n�h�K,����+���*!!�	,�  �$ eZY$1��Q(c����Ң�O'"�������� 1
��q؍d"�A
�V�x8p��4988MRC�@	e*3@
iI�)
'
?I���@�������,����#
�����5�,�����"�E��z�?��@�E���@�����)�����*!!�	,�  �$�(e�$ÐĠ�
C�E1
	;���('2$��!��DS%!����)e[��TE50�p�Ũ� �F�
�{V8,�%`3
gI�w�3
���*������h�"��)	q4��)#�g��#S�$��"�$
��>��%�`���r�J{
1���$ʈ��!!�	,�  �$@e�6$��Ơ��`�3*�=��
��� P�\"F�����`P�-����d5V�"�2�|?n"!(
�����)e��4xyc?		
��3������
�#wyJl%
o�^[b_0	VT[0m�
$�4�>�'VZ
�c��3��$X���%!!�	,�  �$`e�����:D3
�H0�,'j0�Q�s��L(2HM��j#�ȉ�B�\O�i`u��=Y���EVL=I		
��>������
�suIWJm|	\"_�b0B��cV"d]*K1"	H|@B?�I4��#�S$�-|�|!!�	,�  �$�4e��a��:D�h�����I ��/�K�$W-�	0(`3���F��=��pf@�t�Q����
{f~*���y��S*mg)	�enu		E^Z^	g@	kw(b&	-w#"�xW"�t
#�#�%U$�`�t�o!!�	,�  �$�4e��a��:*����1����v/�Kd��z���<�p6%tP5���S|���H(�F���c���`05xz*|~�v�G�0t#
	Fh�0
#Cd
1
�I�#(i	-��	�	uEL	q��"	h%�$�$<��q!!�	,�  �$�4e��a��:*����1����v/�Kd��z����p6%tP��t����5ũ3��n��G$�
�@a���wy{hoFS>k#	FY"�%E
Cb
A�I4$	(z�:2��
mI		L�l#�#
�F�#�#�>�F!!�	,�  �$�4e��a��:*����1����v/�Kd���V�tKG��22��7�D"����$)����Q����qp8y
l
|~���6z�w2j#F
�"	�%
V�C�
]�6a$�
Q�:2	\�EF	I�&�x	�"͓�F4$�]�#�x!!�	,�  �$�4eZi䠒J�16�<���B�
�?$r��T�HzDP'"l�(�1�5�y���8tg���p,�q�M��*		
q
��������"}�#	b?y{		{)�s	-s�:�9>e�E,C\3
��^�3[�
������S��|���?!;PK
!<Qe#]+chrome/pdfjs/content/web/images/loading.svg<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16" style="animation:spinLoadingIcon 1s steps(12,end) infinite"><style>@keyframes spinLoadingIcon{to{transform:rotate(360deg)}}</style><path d="M7 3V1s0-1 1-1 1 1 1 1v2s0 1-1 1-1-1-1-1z"/><path d="M4.63 4.1l-1-1.73S3.13 1.5 4 1c.87-.5 1.37.37 1.37.37l1 1.73s.5.87-.37 1.37c-.87.57-1.37-.37-1.37-.37z" fill-opacity=".93"/><path d="M3.1 6.37l-1.73-1S.5 4.87 1 4c.5-.87 1.37-.37 1.37-.37l1.73 1s.87.5.37 1.37c-.5.87-1.37.37-1.37.37z" fill-opacity=".86"/><path d="M3 9H1S0 9 0 8s1-1 1-1h2s1 0 1 1-1 1-1 1z" fill-opacity=".79"/><path d="M4.1 11.37l-1.73 1S1.5 12.87 1 12c-.5-.87.37-1.37.37-1.37l1.73-1s.87-.5 1.37.37c.5.87-.37 1.37-.37 1.37z" fill-opacity=".72"/><path d="M3.63 13.56l1-1.73s.5-.87 1.37-.37c.87.5.37 1.37.37 1.37l-1 1.73s-.5.87-1.37.37c-.87-.5-.37-1.37-.37-1.37z" fill-opacity=".65"/><path d="M7 15v-2s0-1 1-1 1 1 1 1v2s0 1-1 1-1-1-1-1z" fill-opacity=".58"/><path d="M10.63 14.56l-1-1.73s-.5-.87.37-1.37c.87-.5 1.37.37 1.37.37l1 1.73s.5.87-.37 1.37c-.87.5-1.37-.37-1.37-.37z" fill-opacity=".51"/><path d="M13.56 12.37l-1.73-1s-.87-.5-.37-1.37c.5-.87 1.37-.37 1.37-.37l1.73 1s.87.5.37 1.37c-.5.87-1.37.37-1.37.37z" fill-opacity=".44"/><path d="M15 9h-2s-1 0-1-1 1-1 1-1h2s1 0 1 1-1 1-1 1z" fill-opacity=".37"/><path d="M14.56 5.37l-1.73 1s-.87.5-1.37-.37c-.5-.87.37-1.37.37-1.37l1.73-1s.87-.5 1.37.37c.5.87-.37 1.37-.37 1.37z" fill-opacity=".3"/><path d="M9.64 3.1l.98-1.66s.5-.874 1.37-.37c.87.5.37 1.37.37 1.37l-1 1.73s-.5.87-1.37.37c-.87-.5-.37-1.37-.37-1.37z" fill-opacity=".23"/></svg>PK
!<��	rDD<chrome/pdfjs/content/web/images/messageBar_closingButton.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
    <path d="M7.85822 8.84922L4.85322 11.8542C4.75891 11.9453 4.63261 11.9957 4.50151 11.9946C4.37042 11.9934 4.24501 11.9408 4.15231 11.8481C4.0596 11.7554 4.00702 11.63 4.00588 11.4989C4.00474 11.3678 4.05514 11.2415 4.14622 11.1472L7.15122 8.14222V7.85922L4.14622 4.85322C4.05514 4.75891 4.00474 4.63261 4.00588 4.50151C4.00702 4.37042 4.0596 4.24501 4.15231 4.15231C4.24501 4.0596 4.37042 4.00702 4.50151 4.00588C4.63261 4.00474 4.75891 4.05514 4.85322 4.14622L7.85822 7.15122H8.14122L11.1462 4.14622C11.2405 4.05514 11.3668 4.00474 11.4979 4.00588C11.629 4.00702 11.7544 4.0596 11.8471 4.15231C11.9398 4.24501 11.9924 4.37042 11.9936 4.50151C11.9947 4.63261 11.9443 4.75891 11.8532 4.85322L8.84822 7.85922V8.14222L11.8532 11.1472C11.9443 11.2415 11.9947 11.3678 11.9936 11.4989C11.9924 11.63 11.9398 11.7554 11.8471 11.8481C11.7544 11.9408 11.629 11.9934 11.4979 11.9946C11.3668 11.9957 11.2405 11.9453 11.1462 11.8542L8.14122 8.84922L8.14222 8.85022L7.85822 8.84922Z" fill="black"/>
</svg>
PK
!<�ٴA++6chrome/pdfjs/content/web/images/messageBar_warning.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
    <path d="M14.8748 12.037L9.37782 2.037C8.99682 1.346 8.31082 1 7.62482 1C6.93882 1 6.25282 1.346 5.87282 2.037L0.375823 12.037C-0.358177 13.37 0.606823 15 2.12782 15H13.1228C14.6428 15 15.6078 13.37 14.8748 12.037ZM8.24982 11.75L7.99982 12H7.24982L6.99982 11.75V11L7.24982 10.75H7.99982L8.24982 11V11.75ZM8.24982 9.062C8.24982 9.22776 8.18398 9.38673 8.06677 9.50394C7.94955 9.62115 7.79058 9.687 7.62482 9.687C7.45906 9.687 7.30009 9.62115 7.18288 9.50394C7.06567 9.38673 6.99982 9.22776 6.99982 9.062V5.625C6.99982 5.45924 7.06567 5.30027 7.18288 5.18306C7.30009 5.06585 7.45906 5 7.62482 5C7.79058 5 7.94955 5.06585 8.06677 5.18306C8.18398 5.30027 8.24982 5.45924 8.24982 5.625V9.062Z" fill="black"/>
</svg>
PK
!<.E����Mchrome/pdfjs/content/web/images/secondaryToolbarButton-documentProperties.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 1.5C4.41015 1.5 1.5 4.41015 1.5 8C1.5 11.5899 4.41015 14.5 8 14.5C11.5899 14.5 14.5 11.5899 14.5 8C14.5 4.41015 11.5899 1.5 8 1.5ZM0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8ZM8.75 4V5.5H7.25V4H8.75ZM8.75 12V7H7.25V12H8.75Z" fill="black"/>
</svg>
PK
!<�7O�Dchrome/pdfjs/content/web/images/secondaryToolbarButton-firstPage.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 3.5H2V5H14V3.5ZM8 8.811L12.939 13.75L14.001 12.689L8.531 7.219C8.238 6.926 7.763 6.926 7.47 7.219L2 12.689L3.061 13.75L8 8.811Z" fill="black"/>
</svg>
PK
!<�ws�@@Cchrome/pdfjs/content/web/images/secondaryToolbarButton-handTool.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.75 2.125C7.75 1.78021 8.03021 1.5 8.375 1.5C8.71979 1.5 9 1.78021 9 2.125V3.125V8H10.5V3.125C10.5 2.78021 10.7802 2.5 11.125 2.5C11.4698 2.5 11.75 2.78021 11.75 3.125V4.625V8H13.25V4.625C13.25 4.28021 13.5302 4 13.875 4C14.2198 4 14.5 4.28021 14.5 4.625V12.0188L13.3802 13.6628C13.2954 13.7872 13.25 13.9344 13.25 14.085V16H14.75V14.3162L15.8698 12.6722C15.9546 12.5478 16 12.4006 16 12.25V4.625C16 3.45179 15.0482 2.5 13.875 2.5C13.6346 2.5 13.4035 2.53996 13.188 2.6136C12.959 1.68724 12.1219 1 11.125 1C10.8235 1 10.5366 1.06286 10.2768 1.17618C9.9281 0.478968 9.20726 0 8.375 0C7.54274 0 6.8219 0.478968 6.47323 1.17618C6.21337 1.06286 5.9265 1 5.625 1C4.45179 1 3.5 1.95179 3.5 3.125V7.25317C2.66504 6.54282 1.41035 6.58199 0.621672 7.37067C-0.208221 8.20056 -0.208221 9.54644 0.621672 10.3763L0.62188 10.3765L5.499 15.2498V16H6.999V14.939C6.999 14.74 6.9199 14.5491 6.77912 14.4085L1.68233 9.31567C1.43823 9.07156 1.43823 8.67544 1.68233 8.43133C1.92644 8.18722 2.32257 8.18722 2.56667 8.43133L3.71967 9.58433C3.93417 9.79883 4.25676 9.863 4.53701 9.74691C4.81727 9.63082 5 9.35735 5 9.054V3.125C5 2.78021 5.28022 2.5 5.625 2.5C5.96921 2.5 6.24906 2.77927 6.25 3.12326V8H7.75L7.75 3.125L7.75 3.12178V2.125Z" fill="black"/>
</svg>
PK
!<c���Cchrome/pdfjs/content/web/images/secondaryToolbarButton-lastPage.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 8.189L12.939 3.25L14 4.311L8.531 9.781C8.238 10.074 7.763 10.074 7.47 9.781L2 4.311L3.061 3.25L8 8.189ZM14 13.5V12H2V13.5H14Z" fill="black"/>
</svg>
PK
!<1�TTTDchrome/pdfjs/content/web/images/secondaryToolbarButton-rotateCcw.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.4105 4.83612L4.77001 6.19601C5.06701 6.49201 4.85701 7.00001 4.43701 7.00001H0.862006C0.602006 7.00001 0.391006 6.78901 0.391006 6.52901V2.95401C0.391006 2.53401 0.899006 2.32401 1.19601 2.62101L2.32796 3.75328C3.67958 1.78973 5.9401 0.5 8.5 0.5C12.636 0.5 16 3.864 16 8C16 12.136 12.636 15.5 8.5 15.5C4.704 15.5 1.566 12.663 1.075 9H2.59C3.068 11.833 5.532 14 8.5 14C11.809 14 14.5 11.309 14.5 8C14.5 4.691 11.809 2 8.5 2C6.35262 2 4.46893 3.13503 3.4105 4.83612Z" fill="black"/>
</svg>
PK
!<左�@@Cchrome/pdfjs/content/web/images/secondaryToolbarButton-rotateCw.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.5895 4.83613L11.23 6.19601C10.933 6.49201 11.143 7.00001 11.563 7.00001H15.138C15.398 7.00001 15.609 6.78901 15.609 6.52901V2.95401C15.609 2.53401 15.101 2.32401 14.804 2.62101L13.672 3.75328C12.3204 1.78973 10.0599 0.5 7.5 0.5C3.364 0.5 0 3.864 0 8C0 12.136 3.364 15.5 7.5 15.5C11.296 15.5 14.434 12.663 14.925 9H13.41C12.932 11.833 10.468 14 7.5 14C4.191 14 1.5 11.309 1.5 8C1.5 4.691 4.191 2 7.5 2C9.64738 2 11.5311 3.13503 12.5895 4.83613Z" fill="black"/>
</svg>
PK
!<��1���Kchrome/pdfjs/content/web/images/secondaryToolbarButton-scrollHorizontal.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 3.78C3 2.7621 2.13279 2.11834 1.25 2.01476V2H1V3.5C1.18133 3.5 1.32279 3.5609 1.40708 3.63029C1.48961 3.69823 1.5 3.75458 1.5 3.78V11.72C1.5 11.7454 1.48961 11.8018 1.40708 11.8697C1.32279 11.9391 1.18133 12 1 12V13.5H1.25V13.4852C2.13279 13.3817 3 12.7379 3 11.72V3.78ZM10.5 4C10.5 3.72386 10.2761 3.5 10 3.5H6.5C6.22386 3.5 6 3.72386 6 4V11.5C6 11.7761 6.22386 12 6.5 12H10C10.2761 12 10.5 11.7761 10.5 11.5V4ZM10 2C11.1046 2 12 2.89543 12 4V11.5C12 12.6046 11.1046 13.5 10 13.5H6.5C5.39543 13.5 4.5 12.6046 4.5 11.5V4C4.5 2.89543 5.39543 2 6.5 2H10ZM15.5 2H15.25V2.01476C14.3672 2.11834 13.5 2.7621 13.5 3.78V11.72C13.5 12.7379 14.3672 13.3817 15.25 13.4852V13.5H15.5V12C15.3187 12 15.1772 11.9391 15.0929 11.8697C15.0104 11.8018 15 11.7454 15 11.72V3.78C15 3.75458 15.0104 3.69823 15.0929 3.63029C15.1772 3.5609 15.3187 3.5 15.5 3.5V2Z" fill="black"/>
</svg>
PK
!<K����Echrome/pdfjs/content/web/images/secondaryToolbarButton-scrollPage.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.5 2C3.5 1.72421 3.72421 1.5 4 1.5H12C12.2758 1.5 12.5 1.72421 12.5 2V14C12.5 14.2758 12.2758 14.5 12 14.5H4C3.72421 14.5 3.5 14.2758 3.5 14V2ZM4 0C2.89579 0 2 0.895786 2 2V14C2 15.1042 2.89579 16 4 16H12C13.1042 16 14 15.1042 14 14V2C14 0.895786 13.1042 0 12 0H4ZM5.89301 6H7.25V10H5.89301C5.54301 10 5.36801 10.423 5.61501 10.67L7.72101 12.776C7.87401 12.929 8.12301 12.929 8.27601 12.776L10.383 10.669C10.63 10.422 10.455 9.99902 10.105 9.99902H8.75V6H10.106C10.456 6 10.632 5.577 10.383 5.331L8.27601 3.224C8.12301 3.071 7.87401 3.071 7.72101 3.224L5.61501 5.33C5.36801 5.577 5.54301 6 5.89301 6Z" fill="black"/>
</svg>
PK
!<�ՙ_��Ichrome/pdfjs/content/web/images/secondaryToolbarButton-scrollVertical.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 1V1.25H2.01476C2.11834 2.13279 2.7621 3 3.78 3H11.72C12.7379 3 13.3817 2.13279 13.4852 1.25H13.5V1H12C12 1.18133 11.9391 1.32279 11.8697 1.40708C11.8018 1.48961 11.7454 1.5 11.72 1.5H3.78C3.75458 1.5 3.69823 1.48961 3.63029 1.40708C3.5609 1.32279 3.5 1.18133 3.5 1H2ZM4 6C3.72386 6 3.5 6.22386 3.5 6.5V10C3.5 10.2761 3.72386 10.5 4 10.5H11.5C11.7761 10.5 12 10.2761 12 10V6.5C12 6.22386 11.7761 6 11.5 6H4ZM2 6.5C2 5.39543 2.89543 4.5 4 4.5H11.5C12.6046 4.5 13.5 5.39543 13.5 6.5V10C13.5 11.1046 12.6046 12 11.5 12H4C2.89543 12 2 11.1046 2 10V6.5ZM3.78 13.5C2.7621 13.5 2.11834 14.3672 2.01476 15.25H2V15.5H3.5C3.5 15.3187 3.5609 15.1772 3.63029 15.0929C3.69823 15.0104 3.75458 15 3.78 15H11.72C11.7454 15 11.8018 15.0104 11.8697 15.0929C11.9391 15.1772 12 15.3187 12 15.5H13.5V15.25H13.4852C13.3817 14.3672 12.7379 13.5 11.72 13.5H3.78Z" fill="black"/>
</svg>
PK
!<������Hchrome/pdfjs/content/web/images/secondaryToolbarButton-scrollWrapped.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.5 1C2.5 1.27579 2.72421 1.5 3 1.5H5C5.27579 1.5 5.5 1.27579 5.5 1H7C7 2.10421 6.10421 3 5 3H3C1.89579 3 1 2.10421 1 1H2.5ZM2.5 6C2.5 5.72421 2.72421 5.5 3 5.5H5C5.27579 5.5 5.5 5.72421 5.5 6V10C5.5 10.2758 5.27579 10.5 5 10.5H3C2.72421 10.5 2.5 10.2758 2.5 10V6ZM3 4C1.89579 4 1 4.89579 1 6V10C1 11.1042 1.89579 12 3 12H5C6.10421 12 7 11.1042 7 10V6C7 4.89579 6.10421 4 5 4H3ZM10 6C10 5.72421 10.2242 5.5 10.5 5.5H12.5C12.7758 5.5 13 5.72421 13 6V10C13 10.2758 12.7758 10.5 12.5 10.5H10.5C10.2242 10.5 10 10.2758 10 10V6ZM10.5 4C9.39579 4 8.5 4.89579 8.5 6V10C8.5 11.1042 9.39579 12 10.5 12H12.5C13.6042 12 14.5 11.1042 14.5 10V6C14.5 4.89579 13.6042 4 12.5 4H10.5ZM3 14.5C2.72421 14.5 2.5 14.7242 2.5 15H1C1 13.8958 1.89579 13 3 13H5C6.10421 13 7 13.8958 7 15H5.5C5.5 14.7242 5.27579 14.5 5 14.5H3ZM10 15C10 14.7242 10.2242 14.5 10.5 14.5H12.5C12.7758 14.5 13 14.7242 13 15H14.5C14.5 13.8958 13.6042 13 12.5 13H10.5C9.39579 13 8.5 13.8958 8.5 15H10ZM10.5 1.5C10.2242 1.5 10 1.27579 10 1H8.5C8.5 2.10421 9.39579 3 10.5 3H12.5C13.6042 3 14.5 2.10421 14.5 1H13C13 1.27579 12.7758 1.5 12.5 1.5H10.5Z" fill="black"/>
</svg>
PK
!<��%==Echrome/pdfjs/content/web/images/secondaryToolbarButton-selectTool.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0.371588 2.93131C-0.203366 1.33422 1.3342 -0.20335 2.93129 0.371603L2.93263 0.372085L12.0716 3.68171C12.0718 3.68178 12.0714 3.68163 12.0716 3.68171C13.4459 4.17758 13.8478 5.9374 12.8076 6.9776L11.8079 7.97727L14.6876 10.8569C15.4705 11.6398 15.4705 12.9047 14.6876 13.6876L13.6476 14.7276C12.8647 15.5105 11.5998 15.5105 10.8169 14.7276L7.93725 11.8479L6.97758 12.8076C5.93739 13.8478 4.17779 13.4465 3.68192 12.0722C3.68184 12.072 3.682 12.0724 3.68192 12.0722L0.371588 2.93131ZM1.78292 2.42323C1.78298 2.4234 1.78286 2.42305 1.78292 2.42323L5.09281 11.5629C5.21725 11.9082 5.65728 12.0066 5.91692 11.7469L7.93725 9.72661L11.8776 13.6669C12.0747 13.864 12.3898 13.864 12.5869 13.6669L13.6269 12.6269C13.824 12.4298 13.824 12.1147 13.6269 11.9176L9.68659 7.97727L11.7469 5.91694C12.0066 5.65729 11.9081 5.21727 11.5629 5.09283L11.5619 5.09245L2.42321 1.78293C2.42304 1.78287 2.42339 1.783 2.42321 1.78293C2.02067 1.63847 1.63846 2.02069 1.78292 2.42323Z" fill="black"/>
</svg>
PK
!<�Echrome/pdfjs/content/web/images/secondaryToolbarButton-spreadEven.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M2 3.5C1.72421 3.5 1.5 3.72421 1.5 4V12.5C1.5 12.7758 1.72421 13 2 13H7.25V3.5H2ZM14 13H8.75V3.5H14C14.2758 3.5 14.5 3.72421 14.5 4V12.5C14.5 12.7758 14.2758 13 14 13ZM0 4C0 2.89579 0.895786 2 2 2H14C15.1042 2 16 2.89579 16 4V12.5C16 13.6042 15.1042 14.5 14 14.5H2C0.895786 14.5 0 13.6042 0 12.5V4ZM10 6.5H11.5V7.5H10V9H11.5V10H10V11.5H12.25C12.6642 11.5 13 11.1642 13 10.75V5.75C13 5.33579 12.6642 5 12.25 5H10V6.5ZM4.5 6.5H3V5H5.25C5.66421 5 6 5.33579 6 5.75V7.75C6 8.03408 5.8395 8.29378 5.58541 8.42082L4.5 8.96353V10H6V11.5H3.75C3.33579 11.5 3 11.1642 3 10.75V8.5C3 8.21592 3.1605 7.95622 3.41459 7.82918L4.5 7.28647V6.5Z" fill="black"/>
</svg>
PK
!<
[X��Echrome/pdfjs/content/web/images/secondaryToolbarButton-spreadNone.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 1.5C3.72421 1.5 3.5 1.72421 3.5 2V14C3.5 14.2758 3.72421 14.5 4 14.5H12C12.2758 14.5 12.5 14.2758 12.5 14V2C12.5 1.72421 12.2758 1.5 12 1.5H4ZM2 2C2 0.895786 2.89579 0 4 0H12C13.1042 0 14 0.895786 14 2V14C14 15.1042 13.1042 16 12 16H4C2.89579 16 2 15.1042 2 14V2Z" fill="black"/>
</svg>
PK
!<"�����Dchrome/pdfjs/content/web/images/secondaryToolbarButton-spreadOdd.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.5 4C1.5 3.72421 1.72421 3.5 2 3.5H7.25V13H2C1.72421 13 1.5 12.7758 1.5 12.5V4ZM8.75 13V3.5H14C14.2758 3.5 14.5 3.72421 14.5 4V12.5C14.5 12.7758 14.2758 13 14 13H8.75ZM2 2C0.895786 2 0 2.89579 0 4V12.5C0 13.6042 0.895786 14.5 2 14.5H14C15.1042 14.5 16 13.6042 16 12.5V4C16 2.89579 15.1042 2 14 2H2ZM4.75 5H3V6.5H4V11.5H5.5V5.75C5.5 5.33579 5.16421 5 4.75 5ZM10 6.5H11.5V7.28647L10.4146 7.82918C10.1605 7.95622 10 8.21592 10 8.5V10.75C10 11.1642 10.3358 11.5 10.75 11.5H13V10H11.5V8.96353L12.5854 8.42082C12.8395 8.29378 13 8.03408 13 7.75V5.75C13 5.33579 12.6642 5 12.25 5H10V6.5Z" fill="black"/>
</svg>
PK
!<����]]:chrome/pdfjs/content/web/images/toolbarButton-bookmark.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2 3.5C1.72421 3.5 1.5 3.72421 1.5 4V12C1.5 12.2758 1.72421 12.5 2 12.5H14C14.2758 12.5 14.5 12.2758 14.5 12V4C14.5 3.72421 14.2758 3.5 14 3.5H2ZM0 4C0 2.89579 0.895786 2 2 2H14C15.1042 2 16 2.89579 16 4V12C16 13.1042 15.1042 14 14 14H2C0.895786 14 0 13.1042 0 12V4ZM8.75 8.75H7.25V7.25H8.75V8.75ZM8.00001 4.625C5.91142 4.625 4.14736 5.94291 3.45159 7.77847L3.36761 8L3.45159 8.22153C4.14736 10.0571 5.91142 11.375 8.00001 11.375C10.0886 11.375 11.8527 10.0571 12.5484 8.22153L12.6324 8L12.5484 7.77847C11.8527 5.94291 10.0886 4.625 8.00001 4.625ZM8.00001 10.125C6.53912 10.125 5.28508 9.25455 4.71282 8C5.28508 6.74545 6.53912 5.875 8.00001 5.875C9.4609 5.875 10.7149 6.74545 11.2872 8C10.7149 9.25455 9.4609 10.125 8.00001 10.125Z" fill="black"/>
</svg>
PK
!<<��f__Dchrome/pdfjs/content/web/images/toolbarButton-currentOutlineItem.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.803 4.74998V6.02436C10.803 6.39302 10.3571 6.57793 10.0967 6.31753L7.87716 4.098C7.71566 3.93649 7.71566 3.67434 7.87716 3.51283L10.0967 1.29329C10.3571 1.0329 10.8036 1.21722 10.8036 1.58588V3.24998H15V4.74998H10.803ZM8 1.24998H3V2.74998H6.5L8 1.24998ZM6.5 5.24998H3V6.74998H8L6.5 5.24998ZM3 13.25H15V14.75H3V13.25ZM6 9.24998H15V10.75H6V9.24998ZM1.5 5.24998H0V6.74998H1.5V5.24998ZM0 13.25H1.5V14.75H0V13.25ZM1.5 1.24998H0V2.74998H1.5V1.24998ZM3 9.24998H4.5V10.75H3V9.24998Z" fill="black"/>
</svg>
PK
!<�&g:chrome/pdfjs/content/web/images/toolbarButton-download.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.79407 7.31811H7.86307C7.41807 7.31811 7.19407 7.85711 7.50907 8.17211L10.1911 10.8541C10.3861 11.0491 10.7031 11.0491 10.8981 10.8541L13.5801 8.17211C13.8951 7.85711 13.6721 7.31811 13.2261 7.31811H11.2941V4.38211H11.2961V3.13211H11.2941V2.30811H9.79407V3.13211H9.79107V4.38211H9.79507V7.31811H9.79407Z" fill="black"/>
<path d="M14 3.13208H12.796V4.38208H14C14.345 4.38208 14.625 4.66208 14.625 5.00708V13.0071C14.625 13.3521 14.345 13.6321 14 13.6321H2C1.655 13.6321 1.375 13.3521 1.375 13.0071V3.00708C1.375 2.66208 1.655 2.38208 2 2.38208H5.643C5.82 2.38208 5.989 2.45808 6.108 2.58908L7.536 4.17508C7.654 4.30708 7.823 4.38208 8 4.38208H8.291V3.13208H8.278L7.036 1.75208C6.681 1.35808 6.173 1.13208 5.642 1.13208H2C0.966 1.13208 0.125 1.97308 0.125 3.00708V13.0071C0.125 14.0411 0.966 14.8821 2 14.8821H14C15.034 14.8821 15.875 14.0411 15.875 13.0071V5.00708C15.875 3.97308 15.034 3.13208 14 3.13208Z" fill="black"/>
</svg>
PK
!<���L��@chrome/pdfjs/content/web/images/toolbarButton-editorFreeText.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3 2.5C2.72421 2.5 2.5 2.72421 2.5 3V4.75H1V3C1 1.89579 1.89579 1 3 1H13C14.1042 1 15 1.89579 15 3V4.75H13.5V3C13.5 2.72421 13.2758 2.5 13 2.5H3Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11 15H5V13.5H11V15Z" fill="black"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.75 2.25V14.25H7.25V2.25H8.75Z" fill="black"/>
</svg>
PK
!<��ݎ�Achrome/pdfjs/content/web/images/toolbarButton-editorHighlight.svg<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
    <g>
      <path fill-rule="evenodd" clip-rule="evenodd" d="M7.10918 11.66C7.24918 11.8 7.43918 11.88 7.63918 11.88C7.83918 11.88 8.02918 11.8 8.16918 11.66L14.9192 4.91C15.2692 4.57 15.4592 4.11 15.4592 3.62C15.4592 3.13 15.2692 2.67 14.9192 2.33L13.1292 0.54C12.7892 0.19 12.3292 0 11.8392 0C11.3492 0 10.8892 0.2 10.5492 0.54L3.79918 7.29C3.50918 7.58 3.50918 8.06 3.79918 8.35L4.38988 8.9407L1.40918 11.93H5.64918L6.51419 11.065L7.10918 11.66ZM7.63918 10.07L5.38918 7.82V7.81L7.8648 5.33438L10.1198 7.58938L7.63918 10.07ZM11.1805 6.52872L13.8592 3.85C13.9892 3.72 13.9892 3.52 13.8592 3.39L12.0692 1.6C11.9892 1.52 11.8892 1.5 11.8392 1.5C11.8392 1.5 11.6892 1.51 11.6092 1.59L8.92546 4.27372L11.1805 6.52872Z" fill="#000"/>
      <path d="M0.40918 14H15.4092V16H0.40918V14Z" fill="#000"/>
    </g>
  </svg>PK
!<W����;chrome/pdfjs/content/web/images/toolbarButton-editorInk.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.49913 12.6251C2.61913 12.6251 2.73913 12.6051 2.85713 12.5661L6.29013 11.4201L13.2891 4.4221C14.0191 3.6911 14.0191 2.5011 13.2891 1.7701L12.2291 0.710098C11.4971 -0.0199023 10.3091 -0.0199023 9.57713 0.710098L2.57813 7.7091L1.43313 11.1451C1.29813 11.5511 1.40213 11.9931 1.70513 12.2951C1.92113 12.5101 2.20613 12.6251 2.49913 12.6251ZM10.4611 1.5951C10.7031 1.3511 11.1021 1.3511 11.3441 1.5951L12.4051 2.6561C12.6491 2.8991 12.6491 3.2961 12.4051 3.5391L11.3401 4.6051L9.39513 2.6601L10.4611 1.5951ZM3.67013 8.3851L8.51013 3.5451L10.4541 5.4891L5.61413 10.3301L2.69713 11.3031L3.67013 8.3851Z" fill="black"/>
<path d="M14.8169 13.314L13.0229 13.862C12.3309 14.073 11.5909 14.111 10.8859 13.968L8.80391 13.551C7.58491 13.308 6.29791 13.48 5.18491 14.036C3.95291 14.652 2.46691 14.412 1.49191 13.436L1.44091 13.385L0.60791 14.321C1.46291 15.175 2.59991 15.625 3.75291 15.625C4.42891 15.625 5.10991 15.471 5.74391 15.153C6.60891 14.721 7.60891 14.586 8.55891 14.777L10.6409 15.194C11.5509 15.376 12.5009 15.327 13.3879 15.056L15.1819 14.508L14.8169 13.314Z" fill="black"/>
</svg>
PK
!<:����=chrome/pdfjs/content/web/images/toolbarButton-editorStamp.svg<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="black">
  <path d="M3 1a2 2 0 0 0-2 2l0 10a2 2 0 0 0 2 2l10 0a2 2 0 0 0 2-2l0-10a2 2 0 0 0-2-2L3 1zm10.75 12.15-.6.6-10.3 0-.6-.6 0-10.3.6-.6 10.3 0 .6.6 0 10.3z"/>
  <path d="m11 12-6 0a1 1 0 0 1-1-1l0-1.321a.75.75 0 0 1 .218-.529L6.35 7.005a.75.75 0 0 1 1.061-.003l2.112 2.102.612-.577a.75.75 0 0 1 1.047.017l.6.605a.75.75 0 0 1 .218.529L12 11a1 1 0 0 1-1 1z"/>
  <path d="m11.6 5-1.2 0-.4.4 0 1.2.4.4 1.2 0 .4-.4 0-1.2z"/>
</svg>
PK
!<���P��;chrome/pdfjs/content/web/images/toolbarButton-menuArrow.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.23336 10.4664L11.8474 6.85339C11.894 6.8071 11.931 6.75203 11.9563 6.69136C11.9816 6.63069 11.9946 6.56562 11.9946 6.49989C11.9946 6.43417 11.9816 6.3691 11.9563 6.30843C11.931 6.24776 11.894 6.19269 11.8474 6.14639C11.7536 6.05266 11.6264 6 11.4939 6C11.3613 6 11.2341 6.05266 11.1404 6.14639L7.99236 9.29339L4.84736 6.14739C4.75305 6.05631 4.62675 6.00592 4.49566 6.00706C4.36456 6.0082 4.23915 6.06078 4.14645 6.15348C4.05374 6.24619 4.00116 6.37159 4.00002 6.50269C3.99888 6.63379 4.04928 6.76009 4.14036 6.85439L7.75236 10.4674L8.23336 10.4664Z" fill="black"/>
</svg>
PK
!<��?߽�:chrome/pdfjs/content/web/images/toolbarButton-pageDown.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.35176 10.9989L13.8178 5.53391C13.876 5.47594 13.9222 5.40702 13.9537 5.33113C13.9851 5.25524 14.0013 5.17387 14.0012 5.0917C14.0011 5.00954 13.9848 4.9282 13.9531 4.85238C13.9215 4.77656 13.8751 4.70775 13.8168 4.64991C13.6991 4.53309 13.5401 4.46753 13.3743 4.46753C13.2085 4.46753 13.0494 4.53309 12.9318 4.64991L7.99776 9.58491L3.06776 4.65091C2.9494 4.53853 2.79183 4.47682 2.62863 4.47894C2.46542 4.48106 2.3095 4.54683 2.19409 4.66224C2.07868 4.77765 2.01291 4.93357 2.01079 5.09677C2.00868 5.25997 2.07039 5.41754 2.18276 5.53591L7.64776 10.9999L8.35176 10.9989Z" fill="black"/>
</svg>
PK
!<�Y���8chrome/pdfjs/content/web/images/toolbarButton-pageUp.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.35179 5.001L13.8178 10.466C13.876 10.524 13.9222 10.5929 13.9537 10.6688C13.9852 10.7447 14.0013 10.826 14.0012 10.9082C14.0011 10.9904 13.9848 11.0717 13.9531 11.1475C13.9215 11.2234 13.8751 11.2922 13.8168 11.35C13.6991 11.4668 13.5401 11.5324 13.3743 11.5324C13.2085 11.5324 13.0494 11.4668 12.9318 11.35L7.99879 6.416L3.06679 11.349C2.94842 11.4614 2.79085 11.5231 2.62765 11.521C2.46445 11.5189 2.30853 11.4531 2.19312 11.3377C2.07771 11.2223 2.01193 11.0663 2.00982 10.9031C2.0077 10.7399 2.06941 10.5824 2.18179 10.464L7.64779 5L8.35179 5.001Z" fill="black"/>
</svg>
PK
!<�����Bchrome/pdfjs/content/web/images/toolbarButton-presentationMode.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.5 3C1.5 2.72421 1.72421 2.5 2 2.5H14C14.2758 2.5 14.5 2.72421 14.5 3V11C14.5 11.2758 14.2758 11.5 14 11.5H2C1.72421 11.5 1.5 11.2758 1.5 11V3ZM2 1C0.895786 1 0 1.89579 0 3V11C0 12.1042 0.895786 13 2 13H2.64979L1.35052 15.2499L2.64949 16L4.38194 13H11.6391L13.3715 16L14.6705 15.2499L13.3712 13H14C15.1042 13 16 12.1042 16 11V3C16 1.89579 15.1042 1 14 1H2ZM5.79501 4.64401V9.35601C5.79501 9.85001 6.32901 10.159 6.75701 9.91401L10.88 7.55801C11.312 7.31201 11.312 6.68901 10.88 6.44201L6.75701 4.08601C6.32801 3.84101 5.79501 4.15001 5.79501 4.64401Z" fill="black"/>
</svg>
PK
!<�ąt��7chrome/pdfjs/content/web/images/toolbarButton-print.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13 4H12V2C12 1.46957 11.7893 0.960859 11.4142 0.585786C11.0391 0.210714 10.5304 0 10 0L6 0C5.46957 0 4.96086 0.210714 4.58579 0.585786C4.21071 0.960859 4 1.46957 4 2V4H3C2.46957 4 1.96086 4.21071 1.58579 4.58579C1.21071 4.96086 1 5.46957 1 6V11C1 11.5304 1.21071 12.0391 1.58579 12.4142C1.96086 12.7893 2.46957 13 3 13H4V14C4 14.5304 4.21071 15.0391 4.58579 15.4142C4.96086 15.7893 5.46957 16 6 16H10C10.5304 16 11.0391 15.7893 11.4142 15.4142C11.7893 15.0391 12 14.5304 12 14V13H13C13.5304 13 14.0391 12.7893 14.4142 12.4142C14.7893 12.0391 15 11.5304 15 11V6C15 5.46957 14.7893 4.96086 14.4142 4.58579C14.0391 4.21071 13.5304 4 13 4V4ZM10.75 14.15L10.15 14.75H5.85L5.25 14.15V10H10.75V14.15ZM10.75 4H5.25V1.85L5.85 1.25H10.15L10.75 1.85V4V4ZM13 7.6L12.6 8H11.4L11 7.6V6.4L11.4 6H12.6L13 6.4V7.6Z" fill="black"/>
</svg>
PK
!<a��r��8chrome/pdfjs/content/web/images/toolbarButton-search.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.089 10.973L13.934 14.817C13.9918 14.8754 14.0605 14.9218 14.1364 14.9534C14.2122 14.9851 14.2936 15.0013 14.3757 15.0012C14.4579 15.0011 14.5392 14.9847 14.6149 14.9529C14.6907 14.9211 14.7594 14.8746 14.817 14.816C14.875 14.7579 14.921 14.6889 14.9523 14.613C14.9836 14.5372 14.9997 14.4559 14.9996 14.3738C14.9995 14.2917 14.9833 14.2104 14.9518 14.1346C14.9203 14.0588 14.8741 13.99 14.816 13.932L10.983 10.1L10.989 9.67299C11.489 8.96674 11.8152 8.15249 11.9413 7.29642C12.0674 6.44034 11.9897 5.5666 11.7145 4.74621C11.4394 3.92582 10.9745 3.18192 10.3578 2.57498C9.74104 1.96804 8.98979 1.51519 8.16509 1.25322C7.34039 0.991255 6.46551 0.927572 5.61157 1.06735C4.75763 1.20712 3.94871 1.54641 3.25057 2.05764C2.55243 2.56887 1.98476 3.23761 1.59371 4.0095C1.20265 4.7814 0.999236 5.63468 1 6.49999C1 7.95868 1.57946 9.35763 2.61091 10.3891C3.64236 11.4205 5.04131 12 6.5 12C7.689 12 8.788 11.62 9.687 10.978L10.089 10.973V10.973ZM6.5 10.75C4.157 10.75 2.25 8.84299 2.25 6.49999C2.25 4.15699 4.157 2.24999 6.5 2.24999C8.843 2.24999 10.75 4.15699 10.75 6.49999C10.75 8.84299 8.843 10.75 6.5 10.75Z" fill="black"/>
</svg>
PK
!<�cZ988Hchrome/pdfjs/content/web/images/toolbarButton-secondaryToolbarToggle.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M2.53406 13.818L7.99906 8.35203L8.00006 7.64703L2.53606 2.18203C2.41769 2.06965 2.26012 2.00795 2.09692 2.01006C1.93372 2.01218 1.7778 2.07795 1.66239 2.19336C1.54698 2.30877 1.48121 2.46469 1.47909 2.62789C1.47697 2.79109 1.53868 2.94867 1.65106 3.06703L6.58506 7.99803L1.65006 12.932C1.53754 13.0503 1.47565 13.2078 1.47758 13.371C1.47951 13.5342 1.54511 13.6902 1.66039 13.8057C1.77567 13.9213 1.93152 13.9872 2.09472 13.9895C2.25792 13.9918 2.41557 13.9303 2.53406 13.818ZM8.53406 13.818L13.9991 8.35203L14.0001 7.64703L8.53606 2.18203C8.4177 2.06965 8.26012 2.00795 8.09692 2.01006C7.93372 2.01218 7.7778 2.07795 7.66239 2.19336C7.54698 2.30877 7.48121 2.46469 7.47909 2.62789C7.47697 2.79109 7.53868 2.94867 7.65106 3.06703L12.5851 7.99803L7.65006 12.932C7.53754 13.0503 7.47565 13.2078 7.47758 13.371C7.47951 13.5342 7.54511 13.6902 7.66039 13.8057C7.77567 13.9213 7.93152 13.9872 8.09472 13.9895C8.25792 13.9918 8.41557 13.9303 8.53406 13.818Z" fill="black"/>
</svg>
PK
!<�[��?chrome/pdfjs/content/web/images/toolbarButton-sidebarToggle.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M16 4V12.25C16 12.7804 15.7893 13.2891 15.4142 13.6642C15.0391 14.0393 14.5304 14.25 14 14.25H2C1.46957 14.25 0.960859 14.0393 0.585786 13.6642C0.210714 13.2891 0 12.7804 0 12.25V4C0 3.46957 0.210714 2.96086 0.585786 2.58579C0.960859 2.21071 1.46957 2 2 2H14C14.5304 2 15.0391 2.21071 15.4142 2.58579C15.7893 2.96086 16 3.46957 16 4ZM1.25 3.85V12.4L1.85 13H6.75V3.25H1.85L1.25 3.85ZM14.15 13H8V3.25H14.15L14.75 3.85V12.4L14.15 13ZM5.35355 10.1464C5.44732 10.2402 5.5 10.3674 5.5 10.5C5.5 10.6326 5.44732 10.7598 5.35355 10.8536C5.25979 10.9473 5.13261 11 5 11H3C2.86739 11 2.74021 10.9473 2.64645 10.8536C2.55268 10.7598 2.5 10.6326 2.5 10.5C2.5 10.3674 2.55268 10.2402 2.64645 10.1464C2.74021 10.0527 2.86739 10 3 10H5C5.13261 10 5.25979 10.0527 5.35355 10.1464ZM5.5 8C5.5 7.86739 5.44732 7.74021 5.35355 7.64645C5.25979 7.55268 5.13261 7.5 5 7.5H3C2.86739 7.5 2.74021 7.55268 2.64645 7.64645C2.55268 7.74021 2.5 7.86739 2.5 8C2.5 8.13261 2.55268 8.25979 2.64645 8.35355C2.74021 8.44732 2.86739 8.5 3 8.5H5C5.13261 8.5 5.25979 8.44732 5.35355 8.35355C5.44732 8.25979 5.5 8.13261 5.5 8ZM5.35355 5.14645C5.44732 5.24021 5.5 5.36739 5.5 5.5C5.5 5.63261 5.44732 5.75979 5.35355 5.85355C5.25979 5.94732 5.13261 6 5 6H3C2.86739 6 2.74021 5.94732 2.64645 5.85355C2.55268 5.75979 2.5 5.63261 2.5 5.5C2.5 5.36739 2.55268 5.24021 2.64645 5.14645C2.74021 5.05268 2.86739 5 3 5H5C5.13261 5 5.25979 5.05268 5.35355 5.14645Z" fill="black"/>
</svg>
PK
!<}���::Achrome/pdfjs/content/web/images/toolbarButton-viewAttachments.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.5 3.75C3.5 1.67879 5.17879 0 7.25 0C9.32121 0 11 1.67879 11 3.75V10.25C11 11.4922 9.99221 12.5 8.75 12.5C7.50779 12.5 6.5 11.4922 6.5 10.25V3.5H8V10.25C8 10.6638 8.33621 11 8.75 11C9.16379 11 9.5 10.6638 9.5 10.25V3.75C9.5 2.50721 8.49279 1.5 7.25 1.5C6.00721 1.5 5 2.50721 5 3.75V10.75C5 12.8208 6.67921 14.5 8.75 14.5C10.8208 14.5 12.5 12.8208 12.5 10.75V3.5H14V10.75C14 13.6492 11.6492 16 8.75 16C5.85079 16 3.5 13.6492 3.5 10.75V3.75Z" fill="black"/>
</svg>
PK
!< |�k��<chrome/pdfjs/content/web/images/toolbarButton-viewLayers.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.36645 2.34562C8.13878 2.21813 7.86122 2.21813 7.63355 2.34562L1.38355 5.84562C1.14669 5.97826 1 6.22853 1 6.5C1 6.77147 1.14669 7.02174 1.38355 7.15438L7.63355 10.6544C7.86122 10.7819 8.13878 10.7819 8.36645 10.6544L14.6165 7.15438C14.8533 7.02174 15 6.77147 15 6.5C15 6.22853 14.8533 5.97826 14.6165 5.84562L8.36645 2.34562ZM8 9.14041L3.28499 6.5L8 3.85959L12.715 6.5L8 9.14041ZM1.63647 9.0766L7.99999 12.6404L14.3255 9.09761L15.0585 10.4063L8.36649 14.1543C8.13881 14.2818 7.86122 14.2819 7.63353 14.1543L0.903534 10.3853L1.63647 9.0766Z" fill="black"/>
</svg>
PK
!<�&�xLL=chrome/pdfjs/content/web/images/toolbarButton-viewOutline.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 1.25H15V2.75H3V1.25ZM15 5.25H3V6.75H15V5.25ZM15 13.25H3V14.75H15V13.25ZM15 9.25H6V10.75H15V9.25ZM0 5.25H1.5V6.75H0V5.25ZM1.5 13.25H0V14.75H1.5V13.25ZM0 1.25H1.5V2.75H0V1.25ZM4.5 9.25H3V10.75H4.5V9.25Z" fill="black"/>
</svg>
PK
!<,�gtt?chrome/pdfjs/content/web/images/toolbarButton-viewThumbnail.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3.5 2C3.5 1.72421 3.72421 1.5 4 1.5H5.25C5.52579 1.5 5.75 1.72421 5.75 2V5.25C5.75 5.52579 5.52579 5.75 5.25 5.75H4C3.72421 5.75 3.5 5.52579 3.5 5.25V2ZM4 0C2.89579 0 2 0.895786 2 2V5.25C2 6.35421 2.89579 7.25 4 7.25H5.25C6.35421 7.25 7.25 6.35421 7.25 5.25V2C7.25 0.895786 6.35421 0 5.25 0H4ZM3.5 10.75C3.5 10.4742 3.72421 10.25 4 10.25H5.25C5.52579 10.25 5.75 10.4742 5.75 10.75V14C5.75 14.2758 5.52579 14.5 5.25 14.5H4C3.72421 14.5 3.5 14.2758 3.5 14V10.75ZM4 8.75C2.89579 8.75 2 9.64579 2 10.75V14C2 15.1042 2.89579 16 4 16H5.25C6.35421 16 7.25 15.1042 7.25 14V10.75C7.25 9.64579 6.35421 8.75 5.25 8.75H4ZM10.75 1.5C10.4742 1.5 10.25 1.72421 10.25 2V5.25C10.25 5.52579 10.4742 5.75 10.75 5.75H12C12.2758 5.75 12.5 5.52579 12.5 5.25V2C12.5 1.72421 12.2758 1.5 12 1.5H10.75ZM8.75 2C8.75 0.895786 9.64579 0 10.75 0H12C13.1042 0 14 0.895786 14 2V5.25C14 6.35421 13.1042 7.25 12 7.25H10.75C9.64579 7.25 8.75 6.35421 8.75 5.25V2ZM10.25 10.75C10.25 10.4742 10.4742 10.25 10.75 10.25H12C12.2758 10.25 12.5 10.4742 12.5 10.75V14C12.5 14.2758 12.2758 14.5 12 14.5H10.75C10.4742 14.5 10.25 14.2758 10.25 14V10.75ZM10.75 8.75C9.64579 8.75 8.75 9.64579 8.75 10.75V14C8.75 15.1042 9.64579 16 10.75 16H12C13.1042 16 14 15.1042 14 14V10.75C14 9.64579 13.1042 8.75 12 8.75H10.75Z" fill="black"/>
</svg>
PK
!<��J��8chrome/pdfjs/content/web/images/toolbarButton-zoomIn.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.00488 9.75V14C7.00488 14.1658 7.07073 14.3247 7.18794 14.4419C7.30515 14.5592 7.46412 14.625 7.62988 14.625C7.79564 14.625 7.95461 14.5592 8.07183 14.4419C8.18904 14.3247 8.25488 14.1658 8.25488 14V9.75L8.75488 9.25H13.0049C13.1706 9.25 13.3296 9.18415 13.4468 9.06694C13.564 8.94973 13.6299 8.79076 13.6299 8.625C13.6299 8.45924 13.564 8.30027 13.4468 8.18306C13.3296 8.06585 13.1706 8 13.0049 8H8.75488L8.25488 7.5V3.25C8.25488 3.08424 8.18904 2.92527 8.07183 2.80806C7.95461 2.69085 7.79564 2.625 7.62988 2.625C7.46412 2.625 7.30515 2.69085 7.18794 2.80806C7.07073 2.92527 7.00488 3.08424 7.00488 3.25V7.5L6.50488 8H2.25488C2.08912 8 1.93015 8.06585 1.81294 8.18306C1.69573 8.30027 1.62988 8.45924 1.62988 8.625C1.62988 8.79076 1.69573 8.94973 1.81294 9.06694C1.93015 9.18415 2.08912 9.25 2.25488 9.25H6.39188L7.00488 9.75Z" fill="black"/>
</svg>
PK
!<2�B^��9chrome/pdfjs/content/web/images/toolbarButton-zoomOut.svg<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.375 9.25C13.5408 9.25 13.6997 9.18415 13.8169 9.06694C13.9342 8.94973 14 8.79076 14 8.625C14 8.45924 13.9342 8.30027 13.8169 8.18306C13.6997 8.06585 13.5408 8 13.375 8H2.625C2.45924 8 2.30027 8.06585 2.18306 8.18306C2.06585 8.30027 2 8.45924 2 8.625C2 8.79076 2.06585 8.94973 2.18306 9.06694C2.30027 9.18415 2.45924 9.25 2.625 9.25H13.375Z" fill="black"/>
</svg>
PK
!<:�*�]]6chrome/pdfjs/content/web/images/treeitem-collapsed.svg<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M13 9L6 5v8z"/></svg>PK
!<]{ɵ^^5chrome/pdfjs/content/web/images/treeitem-expanded.svg<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path d="M10 13l4-7H6z"/></svg>PK
!<���IsIs9chrome/pdfjs/content/web/standard_fonts/FoxitDingbats.pfbChromDingbatsOTF%�����������8�i�����n?�
"&*.159=AEILPTX\`dhlptw{�������������������������������� #'+/37;?CGKNRVZ^bfjnrvy}���������������������������������������"%(+.147:=?BEHKNQTWZ]_behknqtwz}�����������������������a1a10a100a101a102a103a104a105a106a107a108a109a11a110a111a112a117a118a119a12a120a121a122a123a124a125a126a127a128a129a13a130a131a132a133a134a135a136a137a138a139a14a140a141a142a143a144a145a146a147a148a149a15a150a151a152a153a154a155a156a157a158a159a16a160a161a162a163a164a165a166a167a168a169a17a170a171a172a173a174a175a176a177a178a179a18a180a181a182a183a184a185a186a187a188a189a19a190a191a192a193a194a195a196a197a198a199a2a20a200a201a202a203a204a205a206a21a22a23a24a25a26a27a28a29a3a30a31a32a33a34a35a36a37a38a39a4a40a41a42a43a44a45a46a47a48a49a5a50a51a52a53a54a55a56a57a58a59a6a60a61a62a63a64a65a66a67a68a69a7a70a71a72a73a74a75a76a77a78a79a8a81a82a83a84a85a86a87a88a89a9a90a91a92a93a94a95a96a97a98a99                                          Chrom Dingbats OTFNormal�����J��%q��/��	N�	]�'t�U�6��q"�!^�o�^�-��E��4�
>�5��V��E�� B �!!W!�!�!�""_"�##i#�#�$_$�$�%%+%@%r%�%�%�&8&�&�'<'�'�("(g(�)?)�)�*(*M*�*�*�+N+�+�,G,�--�.7.x.�/�/�/�00-0�141�2�33F3~3�66�8R9?;�<<�<�==`=�=�@K@v@�AA�A�BB�CE�E�GG�G�HUI�J�K�L�NgO6QIQoT�U^X�Y�Z�[�\�]�^�`�aEb
b8b�b�b�b�c	c c6c�c�dd$d2dBdrd�d�d�eete�e�e�ff;f�f�g)glg�g�����������q��j�d������e�����b���\��4�3�������js��w��2�����4�c�`VRx�Td��4�d�>�B��8�=��µ��������,������4�e�b�EgOM��Poq`g�v�P�1�`��E������,�U���4�����,��vrlhqn�st���T������2��znjlzm�Tqp���m������������������'�v�K�����IT
�=�a��K��K�a%�E�F�a!�I!�I�a�F�#�L��������#�����#M�IO��
���9�"�"�=�"9�"�)�����
���I��?s
��$�e���x�h
�1TBDS�j���������������vaYjJ�k�v���h
1TBDS�j��������������vaYiJ�k�v �8��w�%w�����/e��i���������@}��r�A�"3�B�M�:=������_�X4a2{k��}���v�dw�����Ҫ�̨ө����'���%�x�7�Q�j�n���D��5G�R������T�EGRTE��:
�����;�ȥͨ����b�?JcUR|�_�L:fNBE�=�q�p�o�I��8F�R������S�EFRTD��v��w�[������-�����9�
�@,%l�o:��
:2�.�5�l�)T�Y�3�av�v��w�w�ޖ�#��u�:�9�o�,�,���*�*�}�5�5�Z�C�C�r���t��\���ؕ�4����@�v���T
���Y��6{>D2+�;쳯����ƈ�����������g�c�a�i�:ƌ�J��k`�^(9@'3�B�|<���J��������+�`���~�6ҵ�P��4����������¶����r�V`kqkq��������q�	�P��P�D�-�,�k�q�]�^�p�
H�Y�~��������l�I3�_�'�6�b�E!��<���"�;�1�8[�u�r�z���v�J@[BEd�x�j�W�y�fi�m��gt�����������|i�t�������P��#���Ɛ�������0�|�toYg[Y����������<�7�7�8K�
�D�w��t�Ǡ�ߞ���������u|�{}x�w{{�e�gj����������Ś����������������xz�|������������y}~wyj��d�������mm�;oj~suwummn�l��������������~�j�k����������s�x����������������~��pwYu��x�`�g�k�t�s�hzmt~w�x=�r������������~orrkh�q���������ˊ����w�����������������������ag�������]�}zm�kXn����i�v�zw����n��_�p��� ��uK��d��ͨ�t�o�m�h�y�a�W�y�����y�W>a\xvermkqovkc/�n����˗����� p!_J�
�.v���Cөw�%���nB�����RԹ������������������v�s���?���{�������o��;�{���|�t�e�s�t��yv������3�"U����\������z�x�~��g�"����g����ք���|���������������}ol�l{|��|������������������u4�Ny��Ĝ�����f�M������������� ��3�����xn�hhb�c�h�X�|�����������x�u��%�v�Mw��}���%���л���I�6:L=?z�vR�3:JI93�B�J�#��#���v��w�f���������9�"������-�%�"q�9����q�9%�"-P�����P�"��9L�v�Э��w��f����_�q��H��X������O�@FRXH��{�����͔����O�@@OO@G�R͂U����{΃R�F@OO@@�O��ľΓ���q#_K�)�3v����9�w��
�7���������3���n��d�0����v��g�4h��x�������f�4����n��g�6���i�6��~m�f�jiul��g�Y���c�0t[�#�ß������������w�����_�����ڟ���ߍ��r{��y��������s�{��������w}z�u߿�~�{vg�y�������˰�u��y��ߠ��������߿v��~������������y�4�������Ϧ����)�����=`fH]�Yt��ixtn|r{���Sk������1�����������{|vv{z�����Tx���������K�\m��g~�w�z���@a�������R�������������rr��~�|t������k���������m�q���������������������������o/ْ��Co�p{s{scpfm�n;Ixthvj��z|����������jק���[�1s�|�����}����E������������Z�����)�a�ꝟ�̈́�ɣ��v��џ�����������|}��X|��|�Ⱦ���x�qmapWTO��N�ؙLK�N�\PS���������{��p/�#�X�?��	������������D":kf\i�b����(����^�s�z�Xzq�xgr��z����2�2�<)*�<�2�2����H�w�q�~y���n�L���������~���������w���������������������������}x+�;�5�3��螏��������������������������������U�f�D��������7�Z�T������������������X���������}~��������1��Xr�v������i�bbivr/�����������������{����������~��1���iF�m�,vѥ�=�x������w����W���\ބ�t��������ȓ���]�����������t�j����s��F���
��ui�fe�b�a�g���������u���W�������V�����~��u�%��2��;tn_O��Ftx�x����EQrZn��L�{�������������
�5��2���{���}�x�7���|��cU|�|�ĝ�P?�m�ws�o`n^�W��|�������x�o�rr{�u�xF�#��YQ}�z����czwx�}���CL
�������7
l3
�^k
���W�e�����VgZdoM�z����a��t�yr�Z�}�h��p�
hL
h�S��\�Q�Qh���"
f�0k
�Խ�´��˻��|�\{j{x��qvwqlx��������������h1�.�m�X��`�����e��I}��w"
�������ǮhOTfFOxm�������y�lhwwhh�q�{z����������������<�HU*yEo�y����������������]`P[jTu}��=Z��L
�����"
���UxH{�n�w��n�q�y�T�������(��3�V��Ξ��c�c~�ݯ�l����L
�C��3��4�#
�@
A
�(
�$�Hw�G�%�$�H�Cv�z�����äVZWjMPyk�������q�okyrmC�s������;�;f]�rm�
��ʆ��������ec�dQD��M}���"
�ux��ũ���V9(8L,�N�������tGn{qlqw�������e�x,��	A��U�X�ƤĿ�v�RLcWP}��k��L
�r����Ǯ�"
�'��������P �$g\XFMe�o����������λЫ��2��}�ޱ�0��L
�.�q��`�-������Ul^jF/�j�����\�N��@������I�QBG]<Z�n�t�[�a�������@��t`iyon{HH�������o�vjTbkWS\�´�����֎7
3
}�ެj�&���w�r��L
�-�L�$�#�%��ϔ.
�[�Z%
�Z�[*
�@
�%�H�G/
�G�H6
�1��L�"�z�2wi�������T�x�rmxrnH�r�ͽ�IJ������
I��פ*DO#9�X�ϔ������G�£��פ��WZSpQLPr��}����L
�Bߺ��뼮�����"��������z�#��y� ����7����8df��3WU��2��H�!
�k
�\���Z�����+To[iuX�{����d��v��x�l���X��v�|����
����W�����™���������������j��|�����������Ի���u�����n�l�t��[��v�twvp�[�Vq�y�������mk�jz����t�rtx�!���h�)��u}�������n�x��e~[v���k����Z����������}�}yra{n�`��}�w�r�l�a������w���������������w�)|~����������w��;���������������|�p��Ū���y�]�a~v�[���������������V\�|��\qr������������v���������dIce\�xq�rpq��z}�����&
�t����������������^J�Е�����9J��Ɇ��j}����C�C�X��K�?�?�#
�C������ȸǸ���|�V{eyw~��}nstn�iv�������g%/CcBhTrBctQ�u���������\}�w��8�U����<�<�S�.�$��\�7�7�#
�����ɿ�̻t�Uizv����x�ptrplv�������t1JamSz��~��������~��� �v�bZ��U(ed��ij�k��������tk~��~q���ʹ���e�Hyy��y}��T��������)
�$
�'
�
���m������U�Lx������k���Ҕ�������+��+��{�1�w�����.�.�W�&��F��;
}�,
���Wt���}�ڂ�������ryXx}ofG�_#����������r��q�a]}���L#M��۬�����zix��~q���̯��p�Mgmqpv}�wӹ]�?����)��Uj�T�:�:�@#
��v��ì���I1�4M"IR��ce����˓ϭ�ϵĦ����r@kzohnu�������@�c�vV|m^x|Z�E�P�3N�O�ͩ�ſr�NHcUK}����8&
�8���O�%�DhNV?Bq�lwdrqd_s����״��
�4�kv��q�yT}����?�a�4�,�>�#
���`�u���ͽ���l>Sosap�j�c: )P*5*��׼�ȭ��������f�^fjfe_��zX4�aun][P�\�Ĺ�Ǽ`�f�}�>^������6�6�Q�U��-�#
�^�^jTvT0M����'��G�JeRIaQw?�O-�ի�����sp}���p������[�R�P�Ѫ���f�OEqNN}�/�L�G(�/���U�����g�#
��������������r�gk�W�Ď�����>V��†��f������
��.�*���<#�~�/��)��"�
�`��H�Ċ�K��E���gT��Wd��vä����������=g�y�p�2���;��{�����ϵ��ܼ���{�������pɭė�g�x�������2��h�s���������$��T�a��|:� @�%�Xz���4X�sf�S_\�K�g�<��=����y����������i�������x�s�Y�KxӲ��)�zkyt�{�� ������������Ǒ���DT�zDu�}����������<���6�����Z�9���y�{D�$}�|�j��Lp���{{�����wB*}Om�eYCiJ�>��|�yʍѨ�cܼyjcxel_��n�{�o�����������{{�����dv}��R��Jt
�����"
��k
�jQ~JqpM�v�}����t
�?�G�B�"
�Fk
����~������׾ػ��D�58JM��džգ��ƭ^R=PfQeThNXE}�޿���L
�%�[�A��"
�K0���c�����q�T���������G�@@P\?����������kVS^qXr�c�Ȓ�oBRfoSVd�’}��d��t
�����"
��	�d�
�
�<��X��h����,�u�u}�޽�p���L
�T�^��A�7���"
�T�B��\������U�5��fc�ix�"�v�������������ɨ`THwT=_d���}�޹�g����L
�7�S�E�O���"
�o���Ἓpa���ρ_�FSSeXsu[�TWY�L�^X��l�����Z�8st�xw�|z�t���;����ˤ]SVvSMKm��}�����t
�;��>�"
�rk
���.�����U��)�H�#r�.}��7��2��L
�D�d�5�^�D��`��U|hdP9�Y��Զ��m�T����������P�CCI[>\�c�|{�������oZXkmXVr���`z�t����ĴtORitQVc�����!
}�޽���g��L
�B�g��9�S�C��`"
�V� J��f���¢���ѷ�q���`2RA6;�Gྲྀ���C��)��gp����	��No��Ƞ��ĩSXRp\M}�ݸ���t
�:ȼ�D���&M
ē���NS��";5���S4��0����#��y�%��}�!'�� 
�:k
�jX�Vei[�m�Ӡv�Z��Qw�,����s$����t�7�7uu�9�9o��,�,���+�+}h�6�5[Z�\����������}r
���&
��k
��*�Ҍ�����x}��~���T�T�\�,�W�#
�\k
��οŰ®����r�TTy^F��3�������J,"7\=Wt|jw�q�zJ}�
�6�%����+�W&
�`@܌N�aό������f�X\���������y�_P~kY�9��μ���V:\yh`u�q�nO0>U6?9��}�����N���^��#
�+�	��_�����H7�
�8�
�����V}�
������L&
�i�ڝa��f�Л�Żt�TZwna�D����H�p-�������D*'B:&<I�݈}�
�N�����L�.&
����������G41M=-� l������׿a@�?v����ZEx6S���X�\�š���y�QYo__}r
�s��&
��k
��,��!�����M2�=�s�3}�
�*�����Y�3�$��Y�#
���d�r���Խ���V:[vj^x�o�mM4=S83?�������b�s������u�^[vu`�|�p\�p������o�ZVnrX}�
��M����W�2�Y&
�j�יl��p�ԙ�Žsrg�h0T���������X�;r^RlTlKEW�х�h\�i�á���q�SN�Q]}�	�	r
#���O�S�)�n�r���9��������x���,
��������!���!�z�%� �f�3��Y��&�Α�*����AF��Z��I�l�|�������z�������l��xv��|���h����w��z����'e~£v���w�������Ě��������u
���䉊���Ě��Av�K��JT
����aWeM�F����%���J�L�>�a��I
�����K�D�x�D�9�E�Av�K��Jw�M
�e�E�5��k�]���aWdM�F�����%�)�J�L�=�b�����Ɉ�L3&E%g�#v�Ew�m����L�(�&������M�=la\���a�eЎ4�E�g�e$E)5Jʊ֫����X�M�F���v����w�����������M��u
�M���������ۥq���������<z�WuX}�����������Z�������0�����������������/�F������������S�g�mP���pf��x�������������~���v�|�8�|w������MMvt}tjW�d������0�~�������wr�jPVdaVo�p�x�L�HQVzJG�}��*v�\��\T
��������\���m���l�\���)v�4��3T
��������4���n���m�3���Cv��w4
�}���d��%�����X���.������a�]{{�~{��!6�2�j1M�NA������������������������������mm�����rkpksj=$a8j���
n&[{�x������!�*��������nehm���,��Dv�D�T
�˺�������$�x�S�x�S$��$�D�D=8�D��D\2�D��Dr��v��T
�ӫƾ���,�[�:!����������d��!�:)����A2�����X0�����k,�����t��v�xT
����]��*��������*��I
������p�D��D�=
��z
�����g����v���8T
����v
#��%�R��c���N�o�\b��������v�7���T
�����ŧ���$v
%��V^�bd_]�n�O�=v�T
�����.���Q���Q�.���v��w�B���8�.���~�q�w��=��)�2�$�-����vl
v��������]��K�1�(��������(��1����������5������}
�Pv�W�G�����w��4
�o�����+�;���������okhn�0�:����+�����J��	�$���o�R�����ki���.�3o>L�PD���N�s���D��d��Q�����������(�u����H������k�����������D�n���M�v�#�v����T
��x��
��>q�L�������"��������������r�������v�����w����A�Nf�P�|������0���1<���K�����N�S�N�K�<�v�Cw��<����R�+�.�N�N�+�.�R�R�+�-�N�N�+�-�R�<�����������
������v�
�ҫҭ�
w�:���o�����Y�v�}���}���v�Y*�����
��;���2�;��4��7����4<4������-qi�\�j��δͲH�I�c�in�T�m�-�d�3�����������������%v�a��aw�J�s��#�����Ҷ���،��VS�1�P�������0�E�~d���X/ON��[e�pȆ����0�=�i5lV7�3��v�d�[��|w�����~��`q�EEbct�{ur������������ٶ��;�#�I��q��x�u�ru�{�t�c�D�q�����/dx}tr����������v�����w����^q�U5qMx�Xfb�~���������ݷ8�B�L��o�z\_pbc�a�z�l�
Z�q�����+TRAmd�K����_�������_��s�u
�s�����
7�v�bw��C�f����{t�������q��wqmz|v~���zY�'�]�}�}��������������������U3wcg����������s��u
�s�������
ڑ�0������u
����8���
������u
��0�v��w�����c�8�/�n���O���������-�n�s���l
�����l��]����1�����������1����������5������}
G�v���w�����~�=��c�m����Ϡ��Ew;|D��f�V���/�D�c�=~���n�;�6�A���n͒������n��v��9�mw�F�����^��n������xh�hdZ�Y�a���m�r�}�������Ċ:�I�f�(����h�
����O�NقCQhbWcSXJ��e0�v�Mw�w��� ����u
�T���
�,�n�d�6V@�u��,�pG�v���w����f���+i�tZ�f��������~�v�lώ�c�]i�c ��Wb�;����n�=~�T����n��w�>����n��v����w�?����L��̨�ÿch��U=�WHnQ��ɓ��Åz�Y�{�(_pNC�:L�W�V����������mqayYZd�h�h�ts�sIOn^\��v��w�mw�����}c�D��i�]�H����u����������_�Q���2�����n��˺�z��M����]�M+���PDw�U����
���9����������������]�]Qh^Yxz]�{_s����u�z�RX��d������l�i�W�T�Y�U�u���1��4���1����G�T����������znjjtlpp�����������xjnvwnnc���P�(������������������:
������i�t�x���k���㢤�O�]���������ky���~�q�_�q�v���k������������M�iv�xr~{jd��W_�j�`�#�v���T
��x���)��"������>Lq�>5C���������������r���v����w�����8��0�����|��OV`�PFG�p���G�����N�K�N�S���k�뮱w�2�e�������6������x������Y�]gjwsrhjz�[��a��1�v�P�g�q�PM��O������y�u�a�Z�d�a���=��8��B�c,��,�#�E���k������zjnszpmj���ng�������������������\������qlrwvrnf��>�����o������o�خ�������(����������;����������z
���
���������m
f
����T�?��4�[��_�3�=G����e�ހ��8�V�B�'=[�A8
�����'������'��>��?��N����֠�������������A�@�sd��~��˕����i��B�E@��<�L�����[�8}��cN?&<9Y��/�Po���h^�x�x���e�l�4�������g^�M�\mp=�v�!��w��S����ꚯ������������X�N�td��~��������f��2�OI���1�M����c��z��lZB*UUF�.�n�}s�;8�y�y�z�f�k�G�$���|��g]�2�Pmp��\v�mw�P�������4�g�������������������������y���������{�����������������g���1�A?�*�������{��������y}�����z���TI<=%bClVzs~��������������������������������6�v�bw�_�d��v����&�X�Z�S�^�����������������������������������������������(�K�K�p���|������������������Y���[�ept�
�E�����~}�~������}���}~~}�z�/�5�7�;�[~�{�~���������z���������������?���z�ܰ�w����l��k���������4�����.������5������
�������:����3��<�v�|�z�zw���|��|�x�|�|�|�{�z�{�z�|�z�x;�v�����w��	������������� ������������ � �� 6�v��m��w���p���{��q��}�m�}���q���{�{�m�m�p�mV{v���j�������w�˭����i�߀����S{c{izezldl�x�y����������������������������y�y�|�|������������}�|x��w�����������������������������yu�lZjtw]n~i�^�������������������~�q�~�y���������������y�~�qy~xl^�j�b�k�f�X������S�d�i��dl�clx}qy�~�y���������������y�~q�������������������������������������������������������������zw{�}}�����z�Y�Y����������������{������������������������@{���yy��������w���y�����~�}yx���������������������������@�������~��������Y�Y���y�����~���yy��{����������������������|����������������!��RBw�X����
���<��������������\�^Qg_Yxy]z_t�� ��u�z�RX��e������j�j�W�T�Y�U�v���6��:���3�����K�U����������zniltjqo�����������xjnwxnnc�����Z��[�tzz�y�y��]�I�������u��&�1�1���������������������X�
�Z�T�����V���X��Z�T����R�~�d�zx�wR]]RR�^�Ĺ�ğ�����ؔ������ʘE��і������=�z�xwS�]�Ĺ�����]�Sxx��{=ّ�����͗�K��������������Ĺ���]�R��S\^Rx�x�z>@������yL������A�������]�RR^^RƦR�]Ğ�����?������Hy�~����������������wppwwppw���n�h��������uqqutqqv���m�o�ҥ�����uqqvvqqu���C�C���i
���C���i
�����v�w�l�i�H���������I�j�k�M�����������rm�l<JK<<�J����۪��y�QU��������x�xr�ll<�K������J�<mm�ys�H�v���������x���������K�<�R�<JI<m�m�sxyz�qsw��z������J�<<JK<�H�<�Jک�����y�{qo�|ww�R��N�eB
����B
Z��m�"�������0�f�9�c�r�/�l壧o�1����w�$�n�q���0� 8�h�5�Y�&����/���~
�M�y
������x�|{{z�z����x
��{�|�x������z�{�(�(x
��{�|�x������z�{�tDT��x�}jqtlp�q�4T胐������������w�T�m�r�����������������DT�#����DT胪p�j�$�}t������ǡ���ċ�����v}j�q�������������������������r�lw������������$��s�jmurj�Q�{�w�T�^�t�q��Ȋ��Rl����������v�l�T����������������q�jkprmv�����������~
m}pml�u�������i�NoozzE�e���������t�mrpyo�������������wlrpjj�q������~
�������i�|��T������������č�q�r�Ih0�y
������x�|{{z�z�����P
���KQ
�X�VD
�V�XD
�Y�VD
]�v����w�����W� �\� �\�o�%�o�%� �\� �\�o�%����Ȥ����rOQs����R�sOŤ������Ns�����M������Qr����rTȣ�!>��ţ����sTNr����^x�T��b���X��k�'�s�h��n�"�!�_��_�q�"�~���6������3�y��x��7�~����3ȚyR�5U�x�z�3U�MO�O�7w�y�\�2t:
��x�E}�{�=�|�=1���|�A��6��5�����~�A{�v�L���T
���F}�}�-�}�-E���i�O���'��'����x�`�Oc����q�;�����������x�;��g�DY�v�E��T
��
�����[�7�3�Y�[�4�6�Z�Z�4�5�Z�]�4�5�\�2���~�S�$���t�t���Y�$�~�V�%��v�S���O��w�����P���I}�~�S���S������&�̉6��;��������&��������fXYcZVX_��{�v�{�J����w��J��E5
��Y�b������b�YYbbY�ƨ�\Ǟᳯuw�9w���Υ��j������������{{s�~���q������еۯ��Ǣ��������R�~~Msh;;h4_������������Lu��m�����������������������������x��������o�~�&fjBBjG\����{y��y��~��y���~�}�\��)��|�M���������������,�������������z�z�)�	�����z������}�w�f~m{x�awrxw{�r�t�T�y���~s}x����z�k�uw��w��������������gk]]l��~�D�v����t�Ty����v�Uy����wSw����w�@�u����s�Qw����u�Px����x�Pv����s������ٔ�������������~�nz"M����~~�����������Xx����t�Qv����t�Sy����x�Tw����rBH��/�|�|��������|����� ���������������ؒ������������������3�����������p��x��y������������������v������w�J5
n���?_����n�n����Lt�v�u�������w�BO
�2
�B��$O�)��4�c�0c�0�4��5��v�?���w�8��?}���D���D%���q�:��,��*�����}�:�,�8����������a��i�$��$��b��a��u���ߒv�G���w����C��
v�G��5���,���t�2N��s7����2�����v�AU�|���S�"���������_�"���b�8,�v�Kw������	�-�{��h������������p������|v�8�q�6�]��v���?��w�����<���� �/����O������������o�������.zv�a�q�Z�\�e�d������d]\gd\[f��1�v�0w��1�����E��������y����	���������H�����������������	t�v������E���l�b�o�`�����wyhvj~��E�����d�p|j��`����o�t�fZ�v��w�w�Fw�<��1���\������1�s�������\�����s�0���������y�-���/�s|��������{��-�s�0\x`
��~g
�w��H�ק�w�`
��wg
��H�ק�R�U@���s�����������z�vxa�l��I��N������y�y�{�O�eÊώ�d��d������x������̎sp]h^a��k
�R�!@����������͗ȿ����y��y�y�{mOSeG��������������R�T�������R�!@�d�J��R�T��p�]h^yakk��䂹I@������s�b��FyI@�W�Dqs�yx�������m�S�G����g�`{{��~�Q�U@�oi�in
MVdJ��Q�U@�������yk����"�U@�zv�xalsb�INWDq�s�x��������ñό��d�dg`{�{�~xo�iiM�V̈�������R�U@��������~�eLynpn
��±��������s�B�Y��J�@������R��@��ub���������y�n��R�!@�~�TY����������Ԋ�Ȃ�S@�{�xx`uZby��������ʝ��Y~TeY��������R�U@�`�^���R�!@�{x�x`Z��y����~e�L�pY�e½��������R�U@`s^B�Y�9��������p�x�~sn
r�w�$�v�dw�����>���������>�Z�D�_�D��<Q��N�����^�DYL��j���w�Q��|�yy[�m��������������������y������zr�nLFVa_ZX�(@nm����������}�������������������w��������ȱ��q������������������sT�l|}��J`2�\JYFA�r9���~e�g�n��r�t�����?������̸��������|�������M:�������y�y{~��~Tf7�nL��}n�~�s��J7�~����������L�v�UT
�V����b�}~�����������L��S����������^���{_����������S��Q�������|����wb��n�������{����I��L�������z���q�t���z��������P��O�����������D�v�VT
�O��X�l�p�|�2�=�2�=r�|�on�=�1�9�1�kn��n�2�=�2�=��n�l��<�1\:
��5�H�[�A���H��A��[�?�Ic���B��A����Pb��G��[�GZ�C����I�J����JV�D\;�v�v��|
�w�w�_w��|
n�����v�}�I�c���������������y��������y����������������I�c�������c�I��������������u
��y��������y���䀙����������c�I}�v����I�c�����z�p�}�3������z�o�z�����2��}~q�y������I�c�}vv}��c�I��u���z�p�}��3�
����z�o�z������2�}�q�z�������������rmmrrmmr����v�Ƞ����w�᠉�����G������������������������������t|q|pq^mZWn�a�������������������������u�q�p�e�d�u�r�m����m�r�s�d�c�r�r�v��������������������������ԙ���g�W~g{s�p}q|r}r}u������������������������������KftZm��j�]]n�or���������}�|�{�t�s�u�r�U�pppp�U�r�s�r�t�|�|�����������qm�l^�_�l��l�[qh��v�d�c���
�cw���
��ޤ��k������׶��}yn?E<s�Y�������������������������a�a�cw��y������w�x�d�b�_�������������������������Y�r>D<lz}���������kimYo��q�qpt�tuy�yw~����������z�y�q��xk�ot}}to�k�x�f�{�q����~vt�yrs�rut��vo�Wni���������rllrrllr���1?
���//�Ю�I�R֖��஻����V{]{csM�vs[�k�з����g�|�y����n�W�]�J샼��m�H� �`�d���ɒǔՔ���J�K{^S;+or~��˘������Ģ�c�g�_flZl�g�p�h�SP^�g�o�a�P�8Y�Є́�����pgu^*}E�gy1��Ç��|�W۠����w��W�|��w곇��̰�����f�e�h�袱�������[
b���X
d��Y
����
�7�C
q
N
���W
i�h<
�����������{
��7ǥ�����ldhj�u�u�xt�t�0��������������alrz`my~��{��Ǖyv�w�������{gv_b~��������U
G
R[�a
�x�#�wn�m~���~�����������t}�~|r�p�x��K��t��}����c`cjbxr���������{��q�&����������}n���w
H
�����������]
��f�yx~xu|�vwht��������~~������������/�������~u�|�~�|���qi|ufa�������������~�v�o��������w�Ow��e���1��� �G[
�����e�"������S
����c
�u���������
\
w�w9
�@q
���b
ޠR
�z�xd
T�_`�lfi�g�lJ
�}��ez;E
����zexca|������ U
�@G
ޠa
�������� ��}nw
H
������������]
s������z���uw�z��w�ʵǧ��������}�}�����f�K-���ǵ���B[
c���X
Y
����
C
�+�q
N
W
h�i<
�����������{
�������^jq��s�k�on�m�/���������������������linf_m���|�}x�j������U��+��xl�l����k���������n|�|{s�q�x����ʨ�Ϫ��C�]�[���������m��������������X�ZK��������ȞΛlLnUGl��ӂ�`�Y�yx�yX_��a���r�oo��d�f�ei�l��������_QNxH{�����s��~�������opf{lbt]���������{��O�i�qu|m�}��{�j�W�����������}��y�������/���������u�{�}�|���kYqoex��������������K���u�y������y�uuyyu��v�6�Gw�\��\�6�8�8�G+�G�9,�9Y�V
�w�^
�V
�^
�������W�p���������������t��
��z���t�s�r�V}?�Ca�i��������������������V`����������s�t~uxjqjca�j������������������V������*�V���z���t�s�re
V��?�p
�����X�f�w�y�y����V���y�����V���������������~x��q������d�[p
ss��t�po�p��6V��u�����`��������������ז�o
�g�aWqXfr~w|y{y��VV���}{����������������������j�aWpd[o
s�s�t�p�op��u�����������������}��CaigaW�q�r�~�|�{���|����������s�t�u��jj�cajja�,����������V���������V���zxeQzn�l�n}o|O��������������6V�����p
��{sm~v~q}nm�l�-����������������V����kr{smv��qn�m�l���v�v�r�r�d����VV�������Qn��l�n�o��V��*��e
������`��������z�*�V���e
�v�yz~���
��vv�y~���V��k���s�t�xr��V`�x�rgxzzxezŨ���}�|���e
�{{�{|{��|�{z�{qg��o
������~~��}����-���l�k�l�{�z{�����q�gkr�������������Z���svtvxrrxrdgxz���Ŝ��������Y�v�v�m��i��zw����k���,��U���v~�����n��P�����T�u����{���O���V�]�z~�^�u�N�|���T�q_�}�|^��K� ��Ƽ��ƻ\PPZ[PP[����Ò֮�j�w���d���h���w�j��w�w�֒��э�ȡ��s���e�����t�m���M���ȴ�����3��0�������������������������S����cVg`�����X������������������������������zh�l�k����������쓎�������������������������^�p�n�|��������3���L�l��°���������~|�W�s�l|yxtr~n�i�{qjc�s�ʼ�ĕ��������������������'�S������v�fco}xwj�m������t�i�g�i�^�}�z������ڎI������G�UKTf_`��꓃���{�����铣����������������m�dLZJR��|�|{o�q�p�����{�~z��ԓ�����j�s�������f�k_r[d_�^�o�l�~�����zmX�tVx��v����{hgqf���3�����O������z�w�x�������������������s���������3�������q��w���������wx��xz�|�}����������������>�������������W�3������z~||~~|�{�������h����������myy�����m�^�g���������ܹ��~z~~��j~ie�g��:�~�X\wzvy�����/�����{�r����b�Q��������zz����l�a�x�!�?�����fs:]��ғ{z���������������pncdsi�����������z����������,�v�Jw�گ�
����ëp+����ܺ��.+)�q��JNL�r���U��N���C��+�����\����p��k�7��w�ԝ��6vS�죂��i:��ܹ�i��*�ì�v��B���h�s7Sk��h�f�:\|����q�EC���q�VUV�qr�LJE�qq�).|�:�f�h��+S�s�h��CBx�h��j*s�g���]y:]���g�sSj6��h�x�#�v�@w�q�гd�ϰg������ݺ�'�}��-II�p���R��I���U�����\�1��v�ٟ��1s9���2�ݺ�s��=���e�s19\��r�7U���n�RRR�np�I-7�r}�'9�s�e�>=v�d��\2]o�]9\1��d�v���F�ҷ�8�[Aϲ�dAd��G����_��v���d�8�[�
A�ղ�dX�1��ϲ=�*�v�Fw�ҿ��}�⬞t5�~��Ե��6('�g��eKK�f���W��K���L��(�����a����t�x�4��y�Ҟ~�3tj��~��gB�u
Զ�g��5����t��D���Y�s4jx��Z�f�Ba�����g�LL���f�VWV�ff�KeL�gg�'6��B�f�Z~�5j�s�Y}�DDw�X㢬x5t�Z���`�
B`��~Z�tjx3�~X�wV�v�v�ܪ��w�
���0����=��v���}�������{��������=`�������������������v������������v@������}{��v�������=���������5������=�v��{}��W����������=���
�� �� �������������x�����������v������*���=������}���v�/�����{�����W�v�v�Iw��N��5��U��x�U���	���v���	|�l������Ug�������w�www������_������������_L����	��|Ulx��������U���������	C������U�xlU|�_�_�������U�����~�ʷ��ʷ���vww�x������p�����������_������F���U�����	��U�x�N���l�|�	������v��C�B�w��E�B"���D���5fkkaY�d������k�e��0�>�����Z�b�������e�Zovxw����v���������b�[^ac^������=�1������f�YYfcYa�k���1��������c�Z�YefY\�b��������?�?�vw�lZg_\Y�i������~�����[�v���q���w���q��9������Ǜ�������������������������������������ogVv|ocnhel������x����ɠ����������������������������������w�~�au�v�v�k�kjq�q�q�eɀ�v�l~��t��������������������������������_uv��v�kk�jqq��q[���g�w�{�����y���|�{�~�~���������������������o�g�|�o�n�e�������a�W�[�O�n�m�r�}������������������������������V�v�c�h�l�������[���M�[�U���tc�p~��������~�����������������v��~�����������������u�Mv[lU~ttcp~���������~������������������������������������x���Wg[wO{n�m�ry~��{�z��~�������������������������������������������������������K��N�"�	K�=�F���)�(�
�I�������*�*�(�(�%/�%��U:
����3�rz}z�si��m��������.��d�fe��a������e�f����2p��t������}�w��F����Qm��ņ��Q�`��Og���D�
�����r�ll~|t{�
�@y���M����P]xiF���Mx���)�{y�ljotie�~�x�0&�x�N�`�Q�NNƔ���O�[:
��f;
�\�7�4�[�Z�6�6�Z�Z�6�5�Z�[�7�3�\�}�����������u�I��j�k�U��{�w�R��i�i�O��v��H�(A
�F�%�'�F�F�%>
�I�%�#�H==
�Gz
�G�H�G>��n���������x����*���&���x���������;�������������
�����
������m
��v�UT
��z
���0�U��v�VT
���R}�0�V��:
��c;
������������T?
��]������!�-�-�-�/�-�.�_�_�,�,�/�%�-�5�����-�1�0�1�0�/�_%�-�,�/�.�+�.�‹��ۢ�ڪ��������ܩ����A�S���Q��+�S�+��Q�������!��+�"�+��!���������*:�*�<��%:
���}�f�2�5�\�\�7�2�a�c�Q=
�z
�HF��Z
��c�H�c�<=
��z
��H��f
�d�~�8�V�B�'ٻ0�?��4�[|����=�-L7.�ei8�A=
��z
����'��������:
�����"5��#�����u��"3��:
�����u�#����#�vw�G���U:
����}���%�W�2�!��8�!����RT� �Z�[���IY���U��i����f��f������![��
�\ ���t����e"��������t���1��"���t���������"c)���U:
�|������P����[�Y���R���� [�7��!V�2�$Z��Z
��&����"%����Z
����$��#����=
�4�L�T�j�����+:)L�����.��8
��7z
������K�	;�(���*��l�U���8
����=����.��8�ޝ��$׋���ag9�|vlu�ph��>dma����oc��=h�i�y�҉���8
��z
��܋���������=������a�}�����>���k��t;�ga~ًTV7E�V�y9z�VE7U�U=�Ss
�&�����:���z�#K�[6�!������q�u�~�z�|��x��p�cq�������Ss
��&���x���#�[�P�$+QBDS�j���������������vaYgJ�k�v�?��:���_
s�v���|�z��z��res�����������_
r�v���}�z��y��qfr��������}�����9c�	�
������a,09>CKRZ_dix��$>BFUY^din��������)7AKXei��������� /8AJQ^kw�����������������	$(0
3
 
���!
���,
�[%
�[�5�5�[�Z�5�5#
�Z�5�5�Z�H-
.
�Z�5�5�Z�[�5�5F
$
'
�%1
;
+
-
�%,
�@
A
�H/
�H>
�H�%�~�>��2��2�����~�>h�[�l�X��||�u�u�|��X��l�G�)�G6
�������������_Mqukpzne��������q�.�#�}�z�w���ze��}/�R�aPh����O
2
0�%�%�H0
�v�Hw����ĺ�&�d�����������?
�M
�kJ
�}��dz<E
8
��%�G�v�cw�G�%�%�G������vK
��������wK
\
x�v9
P
Q
R\Y�o����{z�z+
�Z�n�me�s{r������j
�|{�|j
u�u��������Sxw�x�x�-��v��w��T
�����������wv�w��|ppvwppw����������b
R
��~�>�~�>1��}����������}}��}�������������x�y�z��w�����������������-��wȒ�ĸ^�.��p���.���y�yd
U�^`�lf������S
����c
�u�����=
�c�'������������z����������tcgb~�p���b���Y���R�w�z�K�[6�!��������ϯ�^�
�
�"8ƃ�tN��������rn�R_UXjr�����q�y�����g�Z�V���lv��w��I�"��Q�
]�_����[�S�*���vqquuqqu�J�k��Jr����r�J������䂹U@���V����U�u�v�x~��x��x�k�������|��|~}��[s��i�{�������������������������>������5�T����PK
!<�L_ǽD�D6chrome/pdfjs/content/web/standard_fonts/FoxitFixed.pfbChromFixedOTF#��*�+�+�o��������l�>k
"+4>EKMQ]e��EuroGcaronIJIdotScedillaarrowbotharrowdownarrowleftarrowrightarrowupgcaronijliraprescriptionscedilla                             h            Chrom Fixed OTF"�������#$�%&������'(�)*������+,-�./�0�������1234��5�6����789:��;�B��}���������?_!�C=]\^<>��tD���a~
�gEpq�����F����y�o�`GmnecH��|jxklI�J�����KLM���N����O�P�������������Qs	
rz��R {viwAuhS��T��fbU�����V����@WXYZ��d[��N���"A��;�>y���
W�<q��2[x���9[��z�		9	K	m	�

G
�'���
&
_
�
�%Fa��Zx��s������[y����_�1���a���.O��7w���s��5X��[q���6����8p��k��,A{� J [ � � �!!#!g!}!�!�!�!�""-"�"�##%#J#�$$6$�$�$�%
%6%v%�&U&�&�''3'S'�(((A(�))3)�)�*?*�*�++>+q+�+�+�,R,w,�--f-�...&.H.P.^.l.z.�.�/f/�00�1:1�2	2'2y2�2�3%3c3�44�55V5�5�66d6{6�6�6�6�7%7e7�7�868�8�99Z9����������c��`��c�3������-U�`�V�_����U��F�ZN�J�:��������¬�[���
������Z�5����:���.T(�,�[�S��:UT:�[�,%�0�,U��C�YT�D�:�����M
�xwT
�$
������j���$M
�/�T
�-���&��"����$�,=�%�,��$
��������	��cs���y�`���r
�$�	r
�K��-
�9
M
T
�%
@���������}y
�$M
��۾��c�����`���^�GX�e������b�XXbbX��������yuuyyuuy�������H$
�������ݼT
�1��������~|�y��x�ϭʱ�X�s�rfeuz��u�wr�cR[]Vx#�!$
���[�\�����t�������K�K�������4����U�Z>����_�-��$24yU�#���X�]�|67.Jy��~�������dV9lG�	*�2�?�����dY�1��}UN�VL�V�1���-�9�!�A�詸��v�?��]�����|����s����ūz�o����٣��s�fUGrF�)�(�C�����n_�5�fRR�CY�T�3!��$���(p�B�������}v}�|}{���F
������z�!�4�{�b��9�zT�X>��X��1�G�+�51C�,���\��]��������f�OS����\�9�oR8��]����P�fU�[9G
�X�����i���%��/
N
�zwa
�6���-��)����+�,9�+�+D�/
N
��“���<�������#�5�)��6�&��@
��.
G
�/
�'�������$iB���V��c���������5���h�3�7�rU�c=V�V:��V�M��M�c��7�L�-�;AD�1���'���+���%���W��	��=�0�ܦ��q�eSDrD 5��k�����������������������sd�>�SPZ�VT�>�(3�nNuV�p�u��U��_�V���5��������_�58�rT7�5�V���H��T�X=y�9��������|
{jHyT�#7���D�C
#��7�!�S
y�;��������|
vKg~T�#8���D�C
$��7�!�S
h�7
��Z�[��������p�2�Z���Z3T�o�>�X��eT�[���[��fT�X>F
�����,�F�X�8�T�6�X�Cy�h���
�"��y�b�@�h��\S�hB������D����$������LT����o��EqZ�
l
����H�����h���%D�2
l
����%���.��0����1�,5�/�,��
2
l
������ v
�1�v
6��2
���^�����?����,��C�^B
�^�C�5��v
l
������2
��-��&�����%y��r�������L��y��1������T�Z��6�G�P5�
���Y����G�Y��q�2�8���
�-�3�&��^�L�D�M�B��YT����s�s��fT�Y<F
�,��������T�T���X���T��X!u
�-��������S�[���N�C���CD�j����U���7iU߭�8 F
������:�N�X��&���#���XRT�4�\�X��%������)T��XZF
���������A���x�����T�
�D���{�3T�X9���[�׿�������������E���y�����V��G���|�3V�[9�.���������xy�z���Үʳ�W�r�rhdq{��q�wr�dPY]Uxy�|���"�����8��2�6��;�K�C�W�E=�8�!��+�-�-��.+�='�
�	9�!���O�l������¨����`�J�LTKD�[��/'�.���dC��+��$�V���2S#�g��$�4�>���d
�mwW
�Q�����j���%�� 
d
�vW
�2�I
A�� 
d
�	����������b
�&�	b
�(�v 
d
�IwW
��� 
�/��E�����zv��{œw��-����k��l��y��6��8�|�Z���`�VD�eY�H�E4�1�!8�?�Uԓ�����V�Ib�F��]c��ke�i�x���0�.��zq�d
̼��W
�<��������|y�{��~�ϭƲ�X�s�uies{��u�xr�aR[RUxA�� 
��������������-������P�,��U�Y=��R�����j('7lB�	v��V������&��1�^�������������������v�}vq�rrt��s�rq�n||��|��������*���B�2?�+�J�3*�*�3�$��fpp_piyk���������J�PIH���O��U�`�������n�9�U��\��G��$j�O�c�ΐ��[���T�X:����`�&��t< $�Aih�iw�vv���~w��n���g�jӁ³�`�؄���	�
��(�B�.���̵����uh�2��MUm��dP�W�#L:7��g߀݀ۀ:JJ]#jLK��Z�Tw�sv���w���Fw��m���b�n�҂þ�@_��y����
�	��.�D�1���ͽ�����og�;�PTb�@�cU�W�&K(6��s�؃�~?ISa"n�KJ��Y�S�]�Y7
�v�G�H�I���W����c���ҧ���s�nȁ�?�������zwz�x~}���|`|���������r�w���	�����?�?��@��9z��옺��kj�Q�QPO�ZR�M�<M$�"�B������҆C3�~cFP��W�P���PT���F�G���&����X�G��P���P����F�X������B����@���������������_������U�%U�+����������sfhts��tn�h�y��n��ſ�������>�,�������|T���Bi<!\������k
�zwU
�R�����k���%�5� 4
k
[
��*0
�!�I
k
��	[
��1�1
��O
���b
�"�	b
k
�zw�ƿ���4
��0y
�%�v�����w�1�t��~��\����tT��E�a�C�a��wT��v�cw����Z�7�[�������
�������[T�c�6�
�b��^�8��ZT�F
��~�"�*�X�&�X-T�s�J�F���2�h��ST���?��?��ZT��2�h�H��Ju
��_�ù�[�'����C�F����[U��%�v�(�v��_U��J���C����]��xw�
�'6
������i���$���]����
��C�5�+��6d��6
u
�
������@�_T�)�����]�� �Q�������[��c��
������@�xV�A�����f�� �h��1�����d�R����!�3c�� ��#�dx��3���������ҩϡ��ߊ�k�(�^[�]�
 \)J�E���´�7��7���I��@;nq\���OXFU9L^���ݠ����>
�0�����i���&Z�D#
>
��t�-�
�*�
���,�18�-�1��'#
n
�a���d�^�J�$|�j�+����ſ���������5�,`
M��(
��=
)
*
|��B�
���N����X��c��������ke~g.b������v��Sjqbp��}�+TX~{_�V�����ҙhSp���u�4�fɥĐ����Tˏ���֑IW����!udIKcl�����>
�����$�����(A��#
��d�>��_��‘����Y�*�r���������
`{Cqc�"�^��i�����™���tz����n]�^@BhB_�\�d P�8kP�4�����n�usnkTY[��û���A
�8���F����X��]�g������d�YYdg]�������}yz{|xx{���/��,
���o�$�]p
�U�
�^�z�K�zx
�Y]
|������f�M�u�K�{^���.��X����/}
�ap
�����]
}
s�����
�^�y�K�yx
��������������.���K�u�M�u�o��0�v��w�0�ئ�����N�S�q�*�^�c�Xh-ZS,D�[����q��Y�������wx�z��{�Ͷ���_�w}^Weqs��o�jd�SLk^bh��������7���`��ȵ*�	�$�y� W��(@��)��wG�&o|�ޱ�h������$��
����@n��f����#��$�G-��1�:���D�Ч��w�vn,ZA���/���3��6�>g3XY�ٶ���_Z�vr�l=Y**^�Vδ�����$����Ƚ�gyJd0Qi~��|�_�4��ǽ�����������������}|�y��x�˼�ÞX�r�ngfpx��t�vs�eWUgJu��M,
|�`�������x���$��{��I���
��+'��OLry���$T�x��4�%�=�����6*��$�4E18,�����d�{����7��^p�yv�|wh
�������|X�x��������@���r�u+�PI�F��N�b���M̊�:�ܴ����\���Ȏ��nNA�P+wu@r�x����h
��@�DW�����P뜡٤�@�w���P�n�VȈ�x[�\E�X�b:f�OJ�M[bhN�=w�����i��i=�A��
��
��A=�������isU�A�n�AU�
�����-��;��o���Ɲam�ddU`c��x�.v��wh
�������X����X����@���@��E�R������S�CDRSE|����������vt4R;�S�����Ŀsg�F��MU[�^\�Q�>E�0�(�7�$�˫���������������1d���
��ew�!v�@�����-�]��������D����qrm]�t�}evy���������s�x��ޏͨ´u�vr<T:�1V������~i�A�BT^�\[�Q�CF�5��:���+���������{�����������q�w��Y�=�������~�z|�z|{���ov�"��������Z�"Ѝӧ��u�kWVsK�dҾ�����q�U��<UVa�n�e��2S�2B�*R���Q߆�������6�q��������3@��3��0�z
�PR
��R
�n�7�n���~.y����۷�������8���;�5���C�K�!�
�2�/�&��J����0�!����"���"����"�Coqd�eOQ���úɦ��u����j]�d3>?) �O߼����׺�Һ�һ���o��c����������L��K�������}�m���l�ML�f]�[YY{mgL�ll�Lh_�_c[�[�g��������>9*@M83C��|�a��������|�G���9��MT��p�^@�T�-'�����*�Ψų|��%�����+F�!E@$�O�����9w�����<�K���9T�9�T�������!�R��!���'�'�`�����`��"N�"��O�`������v���w���Q,�:�����X�%>;*������=fIY[MF]������'���'�����U$�������������v�>�>�3�Q
��Q
"v� ��K��w�w�����b������s�{ă� � ������� �H�f�4���ˤ����|p�X��4U[�t�`�k��T K)d$�C�b�p�v�tPGQeEOS��l�U�q�������q��$t
��s
��D��[U�$��G��<������f�.�H��5�!/��
��C�ߙ��z�dA9�\\^��eg�m����Ɠ����&rZ
�F�����i���(Z��"
Z
�,�}�,�
�)�
���,�1:�-�1���"
^
�V
���Y
M�R"
|�
��V
�1����$�����(A�m"
|����~���a���a��V��u~E\+0�;������K�g�쪖����^�";I'J�P�w�h�.������TGGLT==M�����û���[IISUGGS��|������H�l�l������r�egonh�]l�l������s�eeonh�Zl�l������s�egpnh�{�����{����{��V�{�V��V����7���7�7���7�7|�������o�Y���\�:�~�gO�X�6D�
(��7��:��
���e�,����q��Bf�S�a�y\�����~;Q=������,�"M&?H#�A��|��dw���,����I���7�s��l�k������m�k``til�v�g�������k�p������q�dctoj����{�!_������X���
�l��b�b�������Ɂ�����jE�c� �oj��6"T��"�����
�����7���D3�	��� �
�V��#��VH
J
|���@������l������	���8�ck�va�`������s�������[5+Ba6�U�Q�X�����&Íw���������!��%S�!��TH
�J
�#�K�-�����7wP��,��������#������������O�TR�dfba�K�O��R�Q��w�Yx�����w����O����S���4���4�4���̑�d����R�f�5�f�w�
�Pd��1��7����w�������<�YP
A�pC�D
p1H,��W��������i
�1���7�����w���������C�RP
U��jI�D
o1�H�,�W���������i
�C��7
|�c��������§�����wޡ���ˈVY^�Ur^w_rXM�o�x�Y��]�OOpH�E�A�t�uTm��y�����8�@�O�X�������Ģ����5�+�:ok�1:T���:n
� �����I�&]F�����D������"�CM��+��(F�"�F{
�����~�P���=�!�=�!x��!�P�y�P���@�!�=�u�{
���}�P�}�Py\�=�!�=�!�Q\�w�P�s�PsZ�=��@�!��+���+�����P���f�(�f�'q���/���F����P���Ppb�g�'�c�(�������������+����˷��YT��6T�v�6���n�JNf^V���$T�4���C������q
}�+q
�{���{���������
��~
�6�%�.�C��]T�$��Fo
�1�����i���'Y��5
��݂
h
���y�)�
�'�
���)�0:�)�"5
������:��~���5
�r�oY
o
�����%�����'@�45
�1��v�������~
`��U�?�T��%T��w����	A��z|U������<�2Q��2�bPV���l�1��{��	�(�����	A�p�z�S������ݽ��<��V����2SbIV[��h������
���#�Zŵ�j�2�LRT�m�,M�K�?�3���rT��H���#T�4����h
s
�D��kT�4���G�������m�"�C���+��(��"�Fv��7���>��9�֪�W��ج�������z���}������v�zaohi���l�us�|jo�zg÷�������������'��-�������Ƨ���rY����pP�/"N?6{�x�l.O��o�i�~�S��LSZFY����T��T���[���>M������s
�E������U���xS�A���UT���r�F������<�,���
�K�������Е@����
�J������ɐTu���T���j�=Vnhho�|b�U^oonn��U����������������|��L�|�L��L�v�,�b�b��������:�o����ˮů�9���9��V��Zh�%�Ib�����V����v���e�E�C�D�B���D�C�D�Di��I�F�C�Bed�C�C����������s�7����й���~G��8T�p�9���Z�!EE^bj�T��7|�J��������|�����|�b-�#?' (�/�ټ����Z�4=�>����TpYXJCX��ū���->�����[�ǻ���� T���������������|�|��~�ɶ�žY�r�nigvz��x�sq�_XVtJuD���s�8����о���I��8W�o�9���b�*EGe_X�� V����6���5��;=���m�#��m���m�+��5�2��oX��o�%��oW��o U���5'U�����5�$��5z�������g��2�����"$��'4�%������M��
CS�"=�c
X
�L�����k������!
c
�bwX
�2�}�"�� ����"�'?�#�'R��!
c
�X
�����<�T��9'�7!
|�,�����Y��K����X��g���������oeh?U���쌂�,�HfZm}�vc�K�t�!��Q�	{��ˣ���Gˁ���ќ;W�ER���ٜ5IG��$1����+��x���g{njga�s��������|�u�y{�������c
X
�6����$�����)��\!
���w�����-�@���qXT�:����J]�_v��|���w��^�n�o���Ff������%���%sU���&�����X�j��"�峰��i�FegxisL��������liY$9ON^�^v��G���w�̽�����WTU�+�]���]�UQ��fʜ����$~�m�w�Gc�������-���sUߙ��'����wh
��@�����'���%qS���%�����������Y�'�u�>_[z|i�[�������jX~�us�s&vJi\�Z³������_rfjdpv��������|�����u��1�D��1�4��c���
�U�4K:Y��ŭ��њ;kEc`]_]���iv�3��wȿ��lȣ�b���i������)�~�l���g�WX��`W�T�-5"�U�T�eڍ�����w�iKPZ �de��lc�s�~������r�c
ɽ������������������vy��|��~��ƹ�\�s�peiw|��|ܞqr�agJv]��!
f
����ܒ!X����1�_g��b�����+�3Mp`e�� X�x���������L22?C++>������ѷٶ��=�����������n�8<O�
M�*�U���n�j���VQ1N�)�'&����N�S��:�����n����j�����)�'�[�E�U��1��:E�\7|��"� ���������]�5�<x��6�8�X�"e
dk��K
S�e
:dk��K
|�z
�e�v������k�ceksb�Q��r�0�r��i�i������m�\_hld|�!���!���}�����%������I�Q�����b�3>ZOI������jdirh]ak�����:
����:
'��F�Gu��F�G�v�Z��Bwh
����V�Z��Z�T��T�BU�B�V��v�Fwh
�����O�L�L�FT�F�OT�O�O���>�>|�\�h������|�m�:�\��m�N�OGG�g�ǘt�w�z��#��������g�JJp�m�t�ϐ��[���T�g:����h�&��r:�$�Bf
�����!X���6�x� JܶeM�3�+8���.��ô���_���2>C++?��������L2|��4��
�������D��̏����,�'OMyi]'���������\J)�|B}d��L
�1��v��	�
�������	l�n������p�ajjsj��R@w�g�!�P��Ǜ���UD{hc�c�oӰ��Ԙ�T��w�X����b��w
�7��w
�m
�E
��~�w����)���F�~.J�~�E\
��m
��E
��_
����D\
��_
�n����\
�_
�n���\
��w�����������9����c���JT������Y�Q�¶����i�Ő��j�T,?6WT��JU����y�������/��
������7���;�6���C�;���3�/�(��J�����0�"����"���!��
��"�/�0�����=�������O�R$��-����qtegxm�c������n�˽�n��]�g������g�]]gg]�������}yy}}yy}��~�+�� ���Z����i���¸t��u������(�2�_�0�����zp�P��2Uc�^U�S�8\53�rΉ����OE fLK��[�T|‹�'����+�‹����������*�2`��(��-�
awv�+��t��k������?�D�D��,{��昷���pp�Y�3TY�]T�Q�AV4�
�*�݈��ߑG@��eKR��Z�S�+�ZV��"��)���Z���l�zޅ��x�să�J��������~{|�z}}���}h����������s�w�u���ڴ��O�C�E��=���摷���pp�\�.S[�]U�P�A]8��.�݈���߃HD��eJS��Z�S>�������t����3�S�����������z�t��|���㞌Ƣ��r�q��g�3t���������6!��3�Gih��nn�zpml�v�w�zm�YkRi�m�y�e�2�v�}���vj�I��?��,�}�z�������1��~�zu|�aH�v���������a���+}�PL
�v���������U���%�(��|���J������4���r�9��7����8���b�5�����B�=[]cn�9¦�����V=Qb3,4[�؇;�Y����M�y��Fc�v�p�d�q��%�����
��Wx�õ���Ȯl������v�znvhtn�_�P�ni�wf��������������a�s������ݿ�ؾ�gZ����pR�1�J8/i�g�j2Q���s�stHmQZ[|���w�*������y��ը����wWAhG?b���������U�C�1�����������#V���5�ed��c�������1Kg^f���$V�<��������T!EL *9��|��~�|��+�k���p��|����3�e������1�2MJpgZ�X�������t=UI>0�P�Ҋ�y/5Bf:SO��ab�Zv�
��G����D�j��d������'XPT�4�Z���Z�VN��eΚ���2�${�l���G`�����|��}��ɳ�t�o��������]�Nca{um�W�������{hirtG�V��͋Yhgzcea��w��������c��B��|��|��˳��s�o�ॢ����`�H`^zsk�]�������{giorE�Uн�ˈYcg{afb��u�}�������������wy�|��}�ɻ�ÞY��rneb�sz��z�tt�cXWfJu��v��������J������������������9V�|U�|:�0�����׷�X_���w���L���%�$Q2���m�D���;�7Kf?aP��Ķ�ݻ]/?/?nrBLAFBM������S����D�����8�	躳��d�;`\�JtU�Ã�����khU�&S^|�b�������������1�Y㲿���x?��9��U�q�ZbTKOBb�����?
�A�����j���&��%+
?
����y�'��&����'�1<�w��+
|�ag
�	�����ܪ�����r
�(�	�	6�%��8
;
�3
?
�,����$�����(�1��+
�����@Y���v����h�%�i�����E���iT��'��(����hT��v����9���4�1�����������4T�9��4��\5��3����9T�t
��[��P��m�C����CU�o�?�@�>�(�(��PU��������[U��(�'�@�?B�1��u�j
��!X���2���u̾�KX��=���4��;�XX��S��%�6�1����j
�>�����i���!"�V���2������KV��<���5�����XV��T��$�;�1��f����Xk���K��w�6�
�4�
�RY���2���f̼�KZ��<���5��ͼ�XZ��T��$�1�����Z���`����_�<��� �����
�G�Z��_U���+��+��`U��C�Z�	T�E�U�>�t
�������'�.U'��������+������g
������������'�2b��&�
�(�bwy��'�1T%�������2��������}������������)��=�@�E��@�&�x�K�A�At�I����� ������x��
�u��y��>��
����1��9c�	�
����c=p�������.8BUqv������,3Zis}��������$:Ncv����������$18AJSZ`ft���������������$-6?HQX^di�7��0�4��7�Z�_�`�C5�1� ���1�+�,��=>�G���=����+�*�%��� /�%�#3�$Ŋ�����8.9K/��
O���6�E��;�$$���%�G�র�z�_;<y[*$����Ǔ����.^�&
|)
�*
%
<
-
'
(
=
�`�W�`����U� �H�]O�N�E�����Ë!k�^Y�Y�<U3P6��8���Y�E:wsO��NXF\8L`����˸����8
x;
�3
&
)
�*
�c�2������,U�89�oR8�8�^���Q��U�]8@
.
�1
��O
���'����|T���B]<1K�,��C�\B
�\�CXcWTKBc���������00
�.�C��\T�$��G����C�E����\U��%�x�'�x��]U��I���C���*�4`��'�
�,�
bx��.�X⳾���'
@<
H�Q�����b�3>ZOJÊ������jehqh]al��=��8��T��������I�D��ҷ��A
�����|�ag
���������QS���]|�_�4���>��$U�@����g[�@�jTI�eO�@�7<�*="20�(�/�ա���	3Q��~1�~��~2��X�N
�xwa
���!���!��Ξ�t���'�$-���&�;�'��rq�t<Nb0C!V���!S�F�R������Q�FFRQFm�n������r�cempj����������]��^���������z�����ɾ�n�t������t�nnttnd�u������k�ddkud�1�`�Ԡ���A�����c�K�`�ƿ�������(������6�+`
^
V
U
������~/�0�o��y�M|�
���~�n�7��4��8����	8|���y��|�������kddkkd�1����ɾ\������2X:>P)'@����X�*���Ky�k����\��~�#���#���C�N�w����h
�����.��s�>�_��	:���.�������Z��1����:�Mof�0���&�����y�"�y��4�^�6����s����B��T�.�����3�PK
!<K�'�F�F:chrome/pdfjs/content/web/standard_fonts/FoxitFixedBold.pfbChromFixedOTF-Bold$��*�+�,�����]������@�	%)-9BIS\e���IdotIJijScedillascedillaGcarongcaronliraEuroprescriptionarrowleftarrowuparrowrightarrowdownarrowboth                                          Chrom Fixed OTF BoldChrom Fixed OTFh	7|B gd�f���j�������}�sr���x���{������������������������������������������������������������e�~�����o�Auiwvptykc����m�4`�B�Rh��&9Ng~��/��w�s����_�=��	"	i	�	�

&
W
�
�
�-v�2��
&
d
�
�Eo�����s�|�C��CV��9����)Q��?��b��d�)A���)D��K��v���%_��d� ` � � �!#!L!�!�"{"�"�"�#1#I#h#�#�$$$�$�$�%3%Z%�&&&5&]&�&�'&'�'�'�(&(�(�))�*,*F*_*�*�*�*�*�+:+�,5,T,p,�,�-
-)-�-�-�..Z.�.�/!/K/\/�00N0{0�1e22�3`3�4W4�4�5O5�5�666J6a6�6�7	717D7W7n7�7�7�7�7�7�8"8C8�949M9k9�::�:�;4;A;c;q;�;�;�<<j|�1�Dw�~����K����+u��_�h������_�UU_i_��v��w�(������Y
�"�C����Y
�#^v�u���Tw�$����^���`�|�`���`��$����0��b1��b$��b2��b�0��#'0�������#"v��T�V�S �W�����:��܌�������%�$������7� �b�&���ٔ��|�P�S(l{�z�x���'&F�,g�!�v�i���VU%�ofg��h�'�v�_������	�d�
���qB�L������R�46NHCҌ����뺨lg]dte^p����	��C�J�E�����R�56LIBҌ������jh\fve]o���k��F�co�G�j|�;�
�(���h���/��Mm[OEF�1�ۭ���t�c�7�o���������'�swR|q0�
|�e��������sy����fK�O8=c.Z�d�{�C�j
�twm}lTx�������L
�}�������Y
�&:v�bw�G���%�S�>��R�+��)2'T'������4:v�bw���G%���4����T�2�)��V�+�R>�S4����"���N����3��i�Y��k
����g(� j�v�A�@g
��h�H�A�A�H�H�@)�@�H1E
�9���"Y
�=�_��$�_�$��$|�Nm
�X�a�Ƽ���Z�PPZaXSv�Sw�(�]�l��4E�|�Q���|��B����\�`�����x���S�f���?�ƍ��9����<P;|�( �����w�����^�G����V0�=��1�J��,�4�����o�x��J�4,�J�L��>���B�9CiMW4��ᐦʶhHX�(�p�E|�P�7���D��ܳy��f��$��#�^�[�𺡡���*�#@jofO�B�ӟ��ȰwP>3�N.���9MOkFWV��@�����w����F��7#-���+���.��$�������,�s�s|�u���	�l�կ{��h��1��?�
gc�~a�������R������Њ�dAL[[6HM��Q|�i����j��Q�������
��������ss�u�1l@F_Yh20�!�&�0����E�Vutb������ܥZSH^bQCd���v������]��\�����7�H��n|�S�"���2�l�6��=��XihWM��	N����l�U�췬����4�
�>C1T�\�g�������k]QTlVPZ����¸�ްW]Oim7>`��|��h���m�߁������<��(�������m�B�N�1-&��#�������
o;\�ut��s�����o<7P\���ʦ��BP�|�D�Dm
��7
��7
1v��D�v�l�1���)���Fe�;7
^
�\�]�M����*���(��]�L�
����^���^�^���^�^^
��]�M�	�]�L-���(���*��*�����~�A���p�E��������+�
S>}wV�#��������Ɗ�vT�@+z2}�e��b�i������d�[[eiby����w����������;j������&���r��W/�0�-�#�]���K�x��Ĥ�o�T=Lt?�-g��ɢ�?�L��D#<iQc}w���������RF�y�t~�}�c�:5�r������"�åԼ��t�pw�O{�������h�����1�����>50���R�b�k��0��?�N�|�<�@�@��.�����N�����I���x�?��������Z�-��0�9�F�����gnM>�k����.��
�;JU�2y�3�������� tj@`7�R�.����wp�"�+a�}U�4�:�
��?��O�u������2
�������0�9�u�G�	�E��z
�G����9&�AO�e��0�X��Z0�����w����X(�"���*�J�p(J�� ��� �Z���0�>��(�X1�w����׭���#�(�K�p)J��!���!��]��+ی�;y���l�����F
��!���A
��M�b�3-�6X
��#�&���p�����=�#�p�#>0���>���0�&�p�&��0�>2
���`
�?�t
��By�"������z��X��������$/�C��I�S.=Px
2
������9���4�*�E�4� ]�L�T�D�3��0��>�-�-��z
2
��������(�7������0�&2
�������,��������20���M����R ��%���_0�{�J���������<�����.�u��.�����"�J.�=y�0��������\�%*��B�v�!/�1�M�B"�%�0�������D8Z���
M������P���}��������ܽ��_�6x��z
�I�T�P� ��$&%����	v��I�$�����
F�F�ǥ������������ª���Q�hf~�ysp��k�&����Z�k�u��'�V� �/�b@���.���X��$%P�,!`A;,��*���1���z�����;���	�M�_�%�3��*������'����0�6�M�s�1�I��919��(���w��A��E�������r�����$�*�!��r�I�����ܲ��}�.���c0vʙjd�b�"B2=�"�������xb[dh+�X\��a�02
����8��������`���`����y� ������j���(�E�%���!����/�5s[$��������v�k�~���������X�k��0��(������0��v�k����
�������k��x0�i��!��E#��g����y0�2
����E����M0���G�7�_�#�E��w0�4�2���z0��%�E�9�_G2
���������5����0�"�1"�1��0��4����2
�����q�p*�����*�U�a��l��%%���M��M%�z�����zSv�Sw�C>Ҩ��6Dn%������M�/�z�b�z/�����v�w���ӱ����:�N�f�0�l�f�Gk�hE1#��@Y����v�-w�<����N���{�|�:�����4
���������u\�gh�l�3+H--�W��ɞ��z\�F�|8�:�n�)1(guX����`[OhG`b���מ�ƶ���|�;������L(��z���K��h��{���
����US}gZ�w�L-v�L7�H�ָ���4Y�>KJ4]"��|������:���h+x�he�e�I8���+�5�����U�bRTh<99�����󲳁z�|�:�������*�Dz�/��B�<|���r-���eR�S�$'�����#��������N�����L>�
�zi:G��|������"�E�"�:�)��7��<�2���A�窶�_�lJDn=�k̭���ŠΩ���xF�|
����D���P�z���z�z����۽�������5M�Z$SQ$p�.�����&���q�>�i
�P
�������<n�Z
�	�6�!�����gQ�^B
c3CD���������H*�w�����=�a��”�Øji�Y=-���8�R�q�N[uqY�s�H-��J�8����#���)���y
k
�6�&�R�A��+����C�&�7��$��
5���s
�m�Is0��ā��c����,�0����F�f1UM��j|
�������F�>���(p-����A�%�7���-��9���F-�J;��J@
����-�/�J�A�������s���=�{�������jw��8�B�s�������m���5�F�r�Z�8baylq�rf�slmtx��@-��=���;���H�u������:�g��ϗ�ђUd�BF-���A�B�`�RQyoaЬ�H-��:|��K
��m�@�/O���)�9�1����;�1�������P7(6d4<1���"����@���C,��ڜU3����}��j����
�(�!�%VR{mc�F3�+��������^8<CX>_�����U3����}uUj=�'�
�(�!���{m���F38�+0�����b=E^8<�Xط������;��(����!�K�5��������q����w�"AK^cU��j-����z���H+���6�6��@���z�r��������!�5��+�D���吢�€r��[�H(i�nP�\. d%��*�ք耯��ciGySDT��s��)|��w�����W��w��ݫ��z�ze�_G9����B����(�G|�<����C,�"�B+�+�C������N��N��¦��qa�C�t<��B-��Vlie`M8�Ģ���C�v�������<����-���������-��v���y�m�d�g�$��h�h������g-�O�W6�oI=�oN�W��m-�|
�����b���>d-���7�(�����u-�ORI���-���	�"�6�"�)���U3���H���)��3���U��U��3��;��5���-�Q�E.���z�l��K�Q*�,�Q�����<�E���A��������k���ed��iu�����(�j�M�ɮ����(�����������[O�y]VvuR\�	��I"|-�|�I��	\�R�vy�Nj��yv�|n
���|1�����,/�Ƌ�����ź�	�����"����	�u�V��\P�[.��������rt�(C�9�hMhj8B�(t�rt��gg�d�E�b�@�g�Q�������uz��������ï�N�u}kRqst��q@�kg�]GeESj�v�J�1�~��t�&�+w�"�`�
_�i������_�UU_h_ov��������Z�Εů²^�aA`sJ�}���گ�|z�T�D-l{�r�w��*%�rS.1�$�\�~�v���4���]��߯�K��ΰ�����������Ӳ�q
pyd
u�|xa�������������_�t�����l
�|^�չ�p]�c
v�f�w=>��v�vu�IfI`j���������R��q���������Q��L��������r���Q�NPh�Z�[�_�`jrN�OP�Qsj`aa�`�k��
������QFEQRCCR��������J������"����%��%��%�� �4�J��4�+�*���4��6�J�$V�&`�&U�&S��.v��n
���1���1E�.�@��@����`���E���ͤԱ}�u�������t�k����A���������.L��@��I6x&n�t�xs{frPY�q�k�^��}����~�z�:�l%����7������������H�
�����qh�z��y� �����y�� ��j� �� �y����ո�������9���9�:���9�9���:�9���9��!���!�!���!�!���!�!���!�	IqsasdFZ����ˮ�yu����j_�^7;C)5�5��ɷ���X�q��'�
�H�	��`�	�Y�#ކ�\Y|wm�G��Ɣ���~b�wu�v$t=[D�JΧ�����c�kVgm�����������{
��������w�R��-��+�T��T�N�l�R��(��&�S��v�]��E������]��^y���������/����$����9���9�:���9�9���:�9���9�� ��� �"��� � ���"� ��� �/�'������T�������B�����)�ީ�__d�A����<��<�<���S���[���A$�8������6��58#�����ĶaUV`aRS`����J� g
����H�'�'�J�J� +� �H���^�^�z�9�&>�����-�z���8��������n�TWsVl�F�����Ƥ��dx�|y|�L�'�r���'w���P;�N��,��q˲�����ήz�m�藗����P�JWTxji�K��������ppu�p�{�cB�䶜trnsud^�f�g���v�-w�N�����k�{$�
v�6�=����E*��F*�*�D��"�Qw�����ơ��X���D��<��F1��VkqUr\�����E1��Zv��������E����ȩ���`!O���W�[��9�N�X��X�a�ĺ���\�RR\aX�Mv�bw���-��������w}~�{�|t���pSy����ɱ���`�{���z��w�:�E;��-�z���#���Eu?���m��X�n��'�?��'�!�5����	�S�)*T$2ω����ŦL]If`[Sm��{
������w�R�t�NTL�+��-���M�l�R�i�NSL�&��(�dv��Zv��CЋ�O�w���߀����*_?R�Y�^���^�AB�-�R���?�q�?�e
P�������-���7xG��R'Q�3�Gϊы�R�w�w��� �Q���"�
뢪��q�)]bugmC���������iy�~�A�$��O�����4����.xFߐ�U+�e
P�^�Xv�v��<����͑w�w^�����)��_AR�V�]���]�FE�-�V���=���_qǯ���ɪ|�p�������V�O[W{mk�V��������yqv�sUK���vuquvi�a�i�i���>�e
]P��&��.��D|���YW�f���N�
�ٚ���$)?�om�nHU������P*�d�`�i�P�����d�ZZdi`����z�owD
��<��@����
�M��"
����m�|wD
� 
�`�����b�j
����m�zwD
� ���,�+%ǯ�6�!*�9� 3�"
����m���tޏw�������q�������x{��~���ٽ�ʚ5�u�}afmv��u�ts�f8^IV|/�"
����i��D
����k
�K�����,��"
����~�����M����M�QT�^���\�RQ\^T�������zvvyzuuz�����Q"
��1�@~�H�P��A1����ڝ���|���>����3��[��@:��9��"�Z�P<Za�*���8�A�{��1��@�H���4�9�9�Mv�T�.����"������.��������|��}}y��}oRq����¹�ʘ��O����ϲӹR�r`Jc6�!Y��/����xq�!�*`�jU�N��'*�^�!��uH
���5
�����@�w
H
�������^(���8
����`��1�<�+�����a��j
��&��zw�	�����+���-�-%ȯ�7�!'�;� Y�5
��$����	�O���@����������V
������^)���-
��_
����1
����@�w
��_
����P�����`��j
0�,1
���zg
��)���.�-%ȯ�8�!(�<� ��1
�����-�|�~������V
?���R��0
��0�1��������/� �u�99�N��0�1CH�08�G�0���1���8'�FD�����������2�����2�y��/�����'�L/�<���q�������yz�`}���׻�˚8�u�|_ho{��y�ws�\:_HU|a
�rT
���/
�+.�&
��*��@����a
�rT
���%
�����a����
a
�nT
�+���,�,$Ư�5�")�:�!H�%
a
��sߐw������q�������}}��}��y�پ�˚6�u�|`fj|��t֘xr�_9^IU{E�%
y����������S
�K�S
��|%
�v�Kw���N�2�3�2�3��1�2�0�0O�3�0�2�3NO�3�3|�>v���w���l���]���m��|��@�v�x�a���K�bX��aV�L�D�"�$�08�K�Z��w����������z��T�������j�Tl8X��ej��rO
� �;��A����
�G��'
O
�M�����b���j
�4�`'
y��{R
�&���*�(%�2�",�7�!9�G'
y�����m��|�������H���k
�%���'
��	�{w���������4����1�!�A$�A��1��4���������b����	ۿ��*�������	�����)�{(6����0�"�P��	��ЌPHG�j|�9������F+�x�r�z���F�Vܫ��׿�pnU}bufwcnZ8�q�o����biZsNfB��sn4gȳ�������=�B�[�_��������Ʀ����Q��@�:sJ��9-v��z99
�xw;
�����?���B��Q
�p[�!
9
�xw;
�B�u���a��T�~Q
�p[�!
9
�tw;
��r�,�-#Ǯ�6�$'�:�#��e������p[!
9
��w4
��2�z�������z{�}����㭖�5�ax�vnwt��t��mk�l(l=_y��b����=
z�M
{+
�)
9
����`�,�F��������N�S
e��?
��=
~@M
~�+
��)
9
��Z;
�F��U�b�ȼ���Z�NNZgU��������vux~rsv���'��Q
�p[�!
|�?v�S�@�����)�����R��{��ƙ��k�sMb�t7}Ѡ���������]�
_[xhn�[�T�H�>xnt�9�������zM�^�sx�f�\1D=�N�ӻ������hȏ���ĖGu����noisbyo������Þ�����Mv�[�����!�����Ez���̫�����Z��՗Э·U�bQUj<9:���ʾ󲳋|�0�\+��he�e�I8���!�9�	��.��������|}��}zw���U
�ww�����?����^�$
U
�ww�M�v���`�j
q�$
U
�sw�#�s�,�,$ǭ�6�$(�9�#��$
U
��$�����r
�K�r
��[$
W
�����@����*�)3
W
�H�w���a�j
<��3
���w������g�1�1%ɮ�;�"$�0�%3
����� ���r��������K����L��R��C��~0����Bp��nw����Q�H���b�G���mw�L�99�����7�:���D�
�Y��l��No�<�c�pO���{�};e����۲��IL0=eE@=�����1�Uw��F+�r����q�����������w~��~��㭗5�by�vnyw��w�kj�j*m=`y<�����;�V��Ρ�Бld�Q=/���=�f�C�1SQppa��F/���:{��uwK
�$����@���
�0�Z#
b
�uwK
�S�v���a��
���#
b
�qwK
�*�r�0�/#ǭ�8�%$�;�#R��#
b
��w�������q�������~}�{��z���䭕�3�by�vmtq��p�on�o&k=_zQ��#
b
��������p
�P�p
�
�6#
�������y�(�\
�2�
I
��I
z�;v������x����V��������r��|��)�
���w�i�������S���wln�`V�R�)�
��S�Y�bmoyz��
�x�������ꧧ���EJ6;GK�Z�������u�pn;=K,oq��t<
�����=�����/��(
<
�C�v���b�j
��f(
J
�6
��q�*�)"Ʈ�4�%+�7�$N�J(
J
���B.�Z��)�A-�-�C����o
�G�o
�����@.
x d�C�x�:��A0�@,
�"�&�ww�Y����b�����4���3:
��5��"��������I)�����V2���,�}��j��!���+�#�$QQ{mb�]�I2��������	����]8;CV<\����"�&���1������k
�J���k
�	�=5���=:
��5�����N�#����f
�?��t
��B�=��y
k
|
@
��-����Ay�"�������|y���:�"��0�"<��tx��Z��������/���I�SV[�x
�#�2�1�w�$��%��)��
����y
Z�k
V�V,�w�L�����%+i���!���s
�$�Is0���{��c����,��0���F�f;d}��j2
��������(�A���.�6���6]�6���0�P6sL��&��J@
�h����g����-�;�O�	iK�	��M�A��0�>��O��C0���o�֥����"��+�X�O@]g����7�C�����C�C�*��(w��>@��<�|�\]�y#X����蛲�f|�����}�:����D����a�/�#\`xhj�jX�W�O"�I��6�9Ѻ���j��h�ľ���s�sMS�t8}֟�������ϓHv��?Q����ӎ�gg�):G~��Mv�}��
���0��.��9����������~�����������x|~~}�t�x���nUy��~�Ю����W�����������/��(�?���풣��su�T��g c�bU�Y'�]�
�%�5ۅ�����NbCoO>R��o��Mv�Y���F.���5�5��#��?��͂����������������|~��}�v�z���q]|����̫�����[�����������6��/�E���吢��wy��\�F(i�eY�]. d$��*�Ն��XiHsSDX��q��'u�'��拶m��<w��6�6��=�� �u����q�����>�:��S��@~����涵|�"�s%s�g_�^�2:&;�.�H��
�� Ç�y`[`i�TW��^��@%�@�b�5��>�$L��5$�1�Iiz��ߋ�:�A/�}���7�7��@�� ���y��8�%M��."�.�Lgp��{��u�������6��0�D���@�搡��xz��@^�A(j�dZ�]. e(��*�Յ� ���YjGtSEV��p�)�������+��(��������4�����.�"�=#�=��.��4�����b������H��������Gw�����q�n)�����&x�U�_��m��"�O����6�"Q��0%�+�Mi��/�C0��Lw��y����w��6�&N��,!�.�Lf��
�K�N*-�Q�����=�C���@������	����lq/������ީ�������哭����������hg�f��K_{�"%x.�O��=}y~QsZ��sy����&拯�Bw����5���F
��!����@A
��M�b�37�6��X
d����)��@>
�&���=�k�>㋲�Bwi
׀P
�����@��<nπZ
���	�<�!�����gQ�TB
i3CD�����C
��v�Bw�5�w� �� �ì�)�!.�,� ��v�Bw����C
�h�8�7@�C��0�!�2����.�czpc<@=f����y� �z� �z�y� � � �u�Y�Z����Z��V�^�Ĺ���]�RT]^U�������ytuyysuy���[�[�]�2�Q���}�8hB]�a���������ys9`��ޭ���;��r�������~}�}��|��߫�:�dx�vnxv��u�on�n-oAb{��v�Cw��������v���%(��9�.�v���%(��_��j�_�j�j�_�����_����v��w����
+���.L
�B���
�Y
�0�
E
�B�"�
�Y
�/L
�3��]
6��]
L
������Y
�-��N
�
E
���"�
�Y
�.��N
��������E�)�$�$�'��$)�$Zv�S��0w�����
�&�S�S�#�#��#�#�0(�0�&)�& �&�������t���;�'';:''�:�����|�����p�J�l�i������u�^Zwfn�bl�i������u�^[vfn�bl�i������v�][vfn|���������t��أ�2����&�C�J������O�-/Jh
���kg\cucXp����C�K������P�,/KIB،������kg]duaZp�����C�J������O�,/Lh
���kg\duaZp���[��U�Rl�V�Q{
�!�����P��K��K�
Z�{
�!���R�RZL�K�
�K��L���Pdv�ew������W�u�4D��v�_Ժ�+���ǯ�K��찻����������Ө�q
zrd
k�|zP��|�������E�P�������X�Z�����l
�r^�ޯ�pW�c
��w�sGB��u�y��AB��YcMZp�������������L����M�
�᪼�S�ce:yHF�D�m��@���n����������{����գժ����y�cG�f)\�^V�T�
�C�jCpB�y�rO|��6���{��|���;���]�]�k[U�X���|����"�2���D�\J]�������(����.�6�L�y�6�J��7.9��)��v��������X����z�������������2R��Q��4�;�����ݼ�iZ�u
�3v
�~�y[
���������[�E˽�1�P�/�M�X��u
n���~G
�y�v����$�>�/�M�1�PK�WE�[5�V\����s��8v
��G
��[
�_��^\
����sw���B/��'���//����,���B1���/��1���+����+��߇���!+��`+1�+�����w��=�1������,����.�L.��.���-����-�������������� �
�� ^+.�+y��K�򝟕���&��9c�	�
�����]5CGz���4Le}��������$6CLlqx���������
.6>CIWaqu~������������*16;EOSW`ir{���������������'�����J*1���Q�b�b��1��?�N�z�/�G�G*
}M
~+
�)
 
�9�*Q�
��)�3�$��2�!�=�+�����R;4([AG'���#��2��C��?�+��!�A�면�_�mK;mA�iǮ���àΤ���zK�/
�..�&
�L�B"�!�+�������G:Z���
O������8�%�������0�5tW%��������.
x�d�C�z:��A0�,
a[OiG`b�����ƌnjͅ���gc�f���xK�>�
ڰ���8�7�]���fz��|�OucRqY�Z�`�����B��8
����`��1�<�H��T�
�������V�%-��A�o�?��6�?1�9��C�R�0
����R�C��~0����B���,�F���^)�-
w��B.�"�A-�-�C�Z�d�ǻ���[�OO[dZ�'�K�l(K%�|�<��������&��v4���T��T��{4��:4
�J
��6
��p[*
S�� �� �Qh��������`
�Bɯum�-�)\�bf�A�2��
B9��d����ͺ���_?BS��)�>
����v��w��F�2�bd�g�6EW�L�P�0�M�0XL�[��'�_
�	���f�l������j�bbjmd|�<��������E
^�F����	�Y
�.y��}R
�tu2y��{��P�?
�w������w���|������N�������g
��Ҝ���(�����fZ�V�#'0�ʻX�M�0�_�^��^�	+Y
�,�v�zw�]��|wf
y�$�|����E+,�g�|��{�v�V����Rw��IB׌������/�<���������T�l�Tw�������������S�wq����
�$�
�<�L0�?���n�&�#��E���
���)��#0�8���PK
!<_����J�J@chrome/pdfjs/content/web/standard_fonts/FoxitFixedBoldItalic.pfbChromFixedOTF-BoldItalic$��*�+�,��
���I�������D.
"+4>EKMQ]e���EuroGcaronIJIdotScedillaarrowbotharrowdownarrowleftarrowrightarrowupgcaronijliraprescriptionscedilla                                          Chrom Fixed OTF Bold ItalicChroh Fixed OTF"�������#$�%&������'(�)*������+,-�./�0�������1234��5�6����789:��;�B��}���������?_!�C=]\^<>��tD���a~
�gEpq�����F����y�o�`GmnecH��|jxklI�J�����KLM���N����O�P�������������Qs	
rz��R {viwAuhS��T��fbU�����V����@WXYZ��d[��D��&@v�)x�7����n�:�^���8n��E���	n	�
6
R
u
�
�<��H�

�+\�3Y���
V��K}�Hg�����)b����2~�L�!;�,Lp���)D��]z��Z�9j��?��
]s���c���� L } � �!\!�"	"m"�"�#/#�$8$L$l$�$�$�$�%M%u%�%�%�%�&'&@&�&�'E'g'�'�(
(D(�(�(�)=)j)�*%*�+-+o+�+�+�,J,x,�,�-.-�-�./.m.�/2/�/�/�020`00�0�11^1�2&2�2�33%3F3p3�3�3�3�4
4�4�5A5�6s77A7e7�7�7�8/8}8�9D9�:Q:�:�;:;�;�;�<&<J<a<r<�<�=,=x=�>>v>�??B����h�c��4
1�����>46
��B
P��k��K
���}
L���<�	�A��A��1�@~�R�P��A1���
����=���j�3��X��@<�z9���,��Z\
��P<�Zd��
��z8\
��A�o�w1����J���4��9i�9@
�zw�������w��
��.#
@
�vw�����B���ͮ��#f
�U�"�q�#
@
��W�m
����f�Z
��L
���#
@
�zwv 
�1�D��:����@
��\��?]�g���ɺi�WMIXHΕ������}yspwuy|���)�I#
@
�w�;w�P��r�������}}�{��{����ʦ?�v�uaiz{��{�pp�i=UIVs�e�#
��6���d�<��w��������w�j�n��ȫ����f���6
�t
9�y����� ȣpq�D#�q�C����6���MO8nmy�����z�ve8a9�[��A�$��¼wo�v"����z
�`��\�4�M�9�%�Y����L������Mv�V��w����Fq�����ʵա��d����׺��`�r[Bc7�u��+�.��yp�v$�ؼ��z
�`�oX�N�B�J%����.�zp.���������~�|�}��|:
�x�U���W�=�@�'��6
�t
8�H���YF�(-�����91� ��u����Yf
s!����*��{@V
���N
|@���
��v%�
��U��K
�t
CT
�zw��������w��
���.(
T
�vw��s���D���Ϯ��#N
�X�"�R�(
T
�
��4��
��
L
�f�
m
��
e
����(
��'䡋����u3
*
�����@������0�1����p���6�T�
L�(��6
�j�1M~H�k�08�H��0������1��WD�(9��3�������{�w�D�#��C�%�����a�whGc=EZ��}�2���]�������j���qͮdz���oj�~H���f1�\�lY�^�"@�UHcC��q�y��T���/�!������u�������(�~KV
��pN
|Je
��!�
p�V
��m��K
�;}
;y��V������I�2|?xeV�X:?��3��	ɪ�m�w-�
��nf
�e�qb�A�8�P�#�d�+�<�������'�U
��y���p�p�����<�2|DzgY�Z7;��)���	ɪxo��w4�
��pf
�`�gl�A�B�F�*�W�!�A���������������H
��#�&���r�d��4
:��#�rn�#;y0��4
>��B
��6
�m�&�r��&�����6
�?}
=:
�g���T���A��?4
�Q6
�A;�C
y�"�Ay����.�"�4
��6
�;�"6��rv��^����
�[
��y/�%S��@|�\^s����������N
h
�������x��
�w�.+
u
�vwb
�k���I��
�Ϯ��!f
�V� �$�+
u
�b
�4�J
�i�J
����+
����b
��J
����U���@��>���Qx3�A;�C
h
�+
�V����>����y�"���s�m�v��
^��Ȍ
�[
�)y/�LS��@|g\:EP�כ��N
:
���h��4
8����p�f�q�V�X�X�44
 {�v�u�~�f����g�<�4
��n
��a�6��6�4
��n
�t
7:
��������'k�7����4
��6
�;}
&:
�������{
f�A����7�4���6c��.�4
��6
�f�G0tK�n�&:
N�w4
>������+��[��Az0�qB
U��B
�a�2��[���_K
�.}
S��O���
(��J�.��ul
�N��>�-~
�<���sߏw�O��[
'��L�)��ol
�N��>�#~
�;�b���r�������y{��}��~����˦<�vt`gwy��xМrp�j9RHUqy�������W�$�F��O�1����E�-�]��I�O����$��MF#>�4�,/a����0�=��O��B0�f�������+��X���OA�]e����{8���B���0��z��B��t��=@�z=�.�%������f�P��\�g�z0d��w
�qwX
�������v��
�r�a$
w
�mwX
�?��f�F�
"̯��#,���$
w
�X
�8�Z
��L
�e�Z
�������$
y�!��������=��@��������$
|�>v���w���l��Y���m��|��b��,�{����z���W�VX��hY�L��.�n�/[�b�j����������#����z���������x�tt'A�3�-gl��vw
���wX
�,��q������������z��w����˦?�v�s`itz��v�ws�h<SHVr��E$
����Y��}����[
�F
��;���:�
��x/�;�8�o�I��Y���sQ +�=�	v��@������E�G�˨����~~�����ì���_�g`|�xsv��o�?��(�'�^��=�7�C��l��H�sib����;�"���@:��)7GB�����8�������[
=�����j�g�h�"[
<.�
�
�����;�&��x/�
;�7�w�j��8��Ń[,%~Yu�4�����u������g�p�A�����3�L�T���ʥv{��y3���o$�i�iU�_�E,A�4�M����UXLh�T[��a��$u�+���n��u���y��o��:���A�1�b�����;Ხu|�|6�
��iz
�i�kf�a�6"<�1�eۂ����QYEi5Vd��b��z
���<H
��W����}��B/��������~|�{{��zYLo�����ѹܤ��`����0�����<�+�_�����Ӻ洱v}�z;���^%�k�hd�^�@�@�5�g�†��T[@k/Sa��`��%]�X����~�x��:
���4
���u�V
��`��b�`�
���=}
����	���&�����������	��(�E�6��������z0�F}
��s���	��ϊ`<8�ly� ������j�T���w�yz���V����.����\
����x/�W��5zj\%5z�����������[
��y
�~wY
�a�����w��
�
�_%
y
�zwY
�:���A��!ͯ��#,�U�"1�F%
y
�Y
�������
�b�����
�G��%
y
Y
��`%
����>�����k��+�U��+�����m�4
��6
��j�@��4
��6
��v�^w�������I������9�k�4
��6
�.��Y��G�4������4
��6
�:
e��4
;���Ln
��4
G��U�K�O�4
�vn
���
K�
�4
��6
���O�b�UG:
���B
����h���B
��6
���1A�1�4
��K
����p��������������i���F
��y1���-C�-�F
��y1\
���m���>�����w�������8�;�����������i��ڔ
��y.�� �=E�=������
����q����˒
��L
�b�����
:
����t��pf
l����.�*���X]�a�
���n�(�%��
������h��m,q����#�'���M^�_����j� �#������P�#X��B!��Fi|�5�������|���D��:��?��������o=,qnB�:��֜��Њa������gd�e��l�%h�N�L�ܵ���|���_QHkGfd���ힼƱ���A
�uw��|���w���w��
���!
��������~��a�s�?�"̮��#f
�P�"�g��"
;
��,
�5���5�����z�#A
���|�5�������j�����	��!
|���9������q��\�ü���}�tGd�s6�˞�����������l�+`Vxhg�wr�GB-f���-�������l����}����ry�g�@,4M��\��ĺ����_ƚ���Ň>v����njc}cyx������㖫���A
�uw��|�t����;����
�,!
|�9������|�ڰ�c�E?
���������?
�*�tlDvq<�"s����������t�v���dK�M,1L4d�l�}�9iJ>4�N�L�ڵ���b�trk~l_{�������A
�Kw��|����^�h���ʹe�UIHTLϑ������}yrpupw{�����!
���s���#ɿH��,Kj
J����
�v��w����_�6X�8@��ZD���_Z�I\���i�&�#ɿQ��������Ȏ
���i�0i����-Nj
I����;�-��-�т��^�Bͺ�	�?�_�7�V����������ױ���ص:�?�f��A�j�x�\[�VF%#�:�,v��w�ih��-�������vz������ҭ߾�X�u|eTats��sh�kl�qDYHUa�N��N�o�����R��|�V��L
x���U'�j "y���;�����������;j��������%��;�S�"�u"���P��Ĥ�}�T2Gt?�k����c�K��U9:T,Y}y����������HF�y�x~�}%/�0Q�y��������:p�b�O������䫣�t��A
�w�w��~����q���������z�y|��{��㭝9�bp}voyy��x�oo�k)\=`r�h��"
�;
~���,
|�:�������x���G���p��j��Z���B�ST�i[��m�ID
�x2�M:�[����
�HX�5-A4c8������7�����ը�[�rAn�sv�|w���������|3�4��q��`a��Et��\�R�vy�Nj���dc��kv�������	�ˌ�������t�c�Ю�ݘӧ�(�������������ZQ�yWRwgQ�\u�	v�wJ�{���x�ŋ��ğ�œ���	�������:������Ѣ����Y��`O�Zy.��������r�tx�	�K������c�S�qFhY9~Co�(�t�qs��dg�d%���%�|�������[
�|%����x/�{�,�b�|w/�����h��D�/�"�1���/=dA�p<q����.v��w�9�,�������P�g����P���������H�Z������W�<#18(|�������.bFLj8>D�����
���|�w0���h{
�x�ie�b�d��'� ��H�
�����y��y����E�[��6%��Kh�Mv�X�����#�F{�����ť��b���ݘڭϷ`�aFLk8?C��������{�w1���f'�x�id�b�b��&���J��p1��������}|y�y{{����bѴ�����]�Fy������ʸk�����=k$���������xx�u~���ov���N�w���CZ���ѕ̤ʲl�`7[rI3v�����毮�|�|I�
��Nz
�lx�v�x���f
p��#l9+�	��n�����-�w�7�
$ʭ��
/�H�|�A�A�o��n�d��üȬl�QHNVWS��n�c�ȿ���������p�LJKXV�������?����`���?y����ո��������m��$�!�x��D�H�%�$�M�K��w���5�/� �0��0��2�(��5�4�	��wrobucNi�Ê���Ү�zv����ra�]+"6�	B�J��ǯ����5v���w���l���R���o���������\�Q��X������������^�@P��k_�^\Z}sg�Z�CP�QgcpQSn�p�r�������˷]P;:F=K`��|�9�����x���F?
<��s�
�p��jS�S�2���$�$�B��ƥ��x��� ����ٺhR���e_DT�����;w�:��:E���&���&��;{
i�;�&x)�%��	�/w�K���.d�T���Tp
��
��p
��
��/f
h�/�*w*�*w �.���S���^��9�O�!��
�J�-�
�-����Ϳ�j^NQWMZj���y� �����y���
��v� ���
���+����+�W�#M�#���)O
�m�^s
�	��O
"v��J�u�I �-�W���N�Ψ����{����n�%���$Ռ����1�1�h�C�������q��d���I'�b��y�y�Ρ�$�w&<��T��<�l�m��ւVV��oei��l��&�y� �x�	�x�y�	��
�	
�U��U��>
����o
�X��C
|������e��&�H�#������4���I5�\&�9�0�竲�m�fDIy>�t�����Ѫ٦���lI�_
���v���w��4�2
a
�swv
�\�s�@�"̭��$f
�R�#���5
�I�
�1
a
�v
�%��m
���
�f�Z
��e
(�g2
_
�n����>����
�!��5
�J�
�<�*�٠����/
|�I�>���n�E�V��G��FbTI=,�_�=����{�`��ɰ����E�#� )%a�g�n�����աgj�KGfQX`���F���Զ�Ҫlg�FB^NH^��|���!�!�!��`
�z�cTn_g�.P
�bP
�_�u�u�_�����_���o��_�o���o�
�����d���e���dU���d���ep��
�������B���b�J���mx�D�\���,1�<�5���,�A�9�c���t��-Fl�?�`�OX���w�}"]5�������XU#�[9>D��|�.�Ew�4�]���H����!��_�y���ëk�VC_[_�v�K�0���]����i�m�Ը���W�jNTfT<����"{
����������IU
�w����x�
�w՜����������>�R�\�X�e�fxUm|Z�X�o!w.�U��!�u�t���&!�h���7
)���H<
�^�~f
��1��7
(��~�7
(�ə��|�������m�V)<
�^�~N
|�~����7���}��f��C���U�j\�wu�����
�K���qǻ���˽aR?KP-IO��S�����w�����
'�����
�4�L{
��.����'������'�����~��������n�"�V'D
�[��{
���������m;.��������������e
�����������^��e�C\"_{h d,����>mt�Pqp��t����w����<��y@��
���
1���
5��������b�}[�}a�Aa��W��4K��&���o�E����sb1y��}��P�����\
���=�kبj_�X�.���'�C������,w�j�	A3��g�����Թ`N�3A],JS���&���p����jb8z��~��P�����\
���=�f�j_�X�.�'�.�I������4w�l�	A3��g�z����ԹcS�9Aa,JS���e����T�$W��F^
|�9�������|���E�Vߛ������jk@wt�M}\VFO�t�s��ʁo�iMdMfb��pFFg°x�����F�L�q�M�������͠ɡ��w��?�/\U�t�u;o
�|Y��8�L�{�L���cL���P��v�zw���k�K��
�%�Mz-��*�
�(���������Q
��KQ
�v�w���
���L���N�^�RSM� ����L���N�L�RSM�����������Qɿ�?��r�e�����M��Q�z�OKV�=��
�����U
<��W��Ĕ���{v�����f�O;o
��?
:��H������w�OWvqU��}�FD
�1�J:�|�A��4������x���#1��4�.�x���#1��_��%�_�%���%����+����i
���&�T��
���w,�Y���Dx
�
�~�w���x���H��.
x
�ow�_�t�7�	#Ȯ��"0�H�!���.
x
��'�������
�^������a�.
x
�
�s����>�������).
�&�5�+�%�%� i
�a�Yw,����A����9w,�1�%�i�����%�
���J^0���}��K��
�gn
�D��E{lfaW����&�9��%���������%���J^0����f��
�n
��D��E{jf;W]��^��������D��=���r�h�do�
����!j�D�h��*��U
��o
� C���Do
�2�J<�
�����T��>
������
�01�J�
�v�zw��\�%�M���*�	�(��j�K�v�v�F�����?��R
|=
|���������G���>�������'���:�˫G
?>��;x8�l�n��'x8��ogVCZ���������i�Y�����c�
�����mU
�L��s����k�����v-�Fd�E�j~J��`�W�K������v����4��{�������wx�����Q���5��=��s�������o��O�����S��r������e�Fb_ylj�{f�ullss���;y,\
Z��=����;���;���;�_��c�_s
�
v�*�B��Ш�"���H}��{�̸����W�D��;�ލ
c�W�h\bpP_t�����������
��N���N�R�5��5���3�P�0[�S�1��4AO��3���;�����?
9��]��Ӣ���zr�����d�W<D
��?
;��m��������M�>RKooZ����Fx,�X��;|��p���w���������Z�W�(���b���	��
.��8�������j�Z�H�#���SB7Vi���۴��=K������8���q�������z{�}��~��㯜=�bq~unxw��w�rr�o-]=`r�D������=��V��ѡ���|u����f�Q?x/����@��f������P�BUKop^���@y/�
Z��>�)���������^���V�V�
���V�4
!���4
�0��b0R�b%�b2S�b�6
�k�(6
�������k�|��������<��[�1�1�M�K�5�.�?"�I������QT��X3T,��]
���w���w���e�)
c
�qwE
�+���g�E�����%N
���0
��:-
c
�E
�!��r
�h�r
�i�f)
|�����v����i��h�ü���~�tFb�sJw���������������l�,\Zxhc�q\�X�2)�2�2D�9������b���ſ�}?u��Q�±�7껖fkA`�8`t���[��Y��Ɋx.V@d�l��������{�dqr~����约�]
�m����>��������)
�������U�
�B�����Vx0�?�8�1�G]�Ov��ϊ��w��}������]�������������{�;_^uhh�L���������uy~sy~�A���w_�X�
�5M��
������9����"n~FԚe�
2\�Zv��H��w������_R�R�7��c�����c��FL�A�
ݛ��|:�R�x�X�	�4L��	�����8����n~GҚf�
2�p��w����)�p����{
��;j{?�`�x!�X��ڴ�����`���S��"������Q�SUN{}g�<��×���wl��ut�s�d2KQ�Z˫������cv`Wci������������X�n���X���5�6��
��^�+�:�&#ٙų��ĢKaAT@LVrĸ}�7v��w����l���Wƻ�p������F�N����x���]�V`��h\�M�)�: �Hc�j�p� ����d�}�{{��M9vn��rS�~�������竤���c
�w�wE
����q��������~�z��x��㭜=�bp~vnvw��v�sr�p,]=`s���)
�"��v���@�����g�
��
��}��t��9���$�;�TDrl[씶>
y3�
5�+�)������κjV9/:=c2���������E��z���z���x�������N�UY�*G�I�J�:v�bw���[%�n�p��F��#�+��#+�&�FH�2�,:v�bw����%���#�&�F�~�Z�*�E�* �F��#&�|��������ܛ����Z9
��9
�i��^�Jz�_�L|�N�R�o�R�h�_��̲ϳm�MKL`K�9�N�V�h�V��l�\��żųr�LIQUU|�����?٫��׭נ�9�?�WI
�:�
B<5ٕ���ȵ�rS
?���w�Oz�y�P�g�(8
�6�8
�h�@w�b��h�Jh�=V
��=�K?
�J��@N
h�@�K��C�(w���������(�H���H��(N
i�(�Gx:�G�����\���\|��=�����ܰ�W���|�}�|�
U
:}���T�FFe��
�����9�#��D
�9�4D
��?
;������b��=��ƂZ*#}X�"����?����J�
��;�-\
��>
^حi\�S�1�!��)�;����u����������IV��{^FV��}�,����T���=�J������<��S<}wRn�$��������ƽzb0�y(|(��j�v�ج���{�LJ[\X�&��,���R��x��Y�η���j�[De]f��>K��1J�3�[��ߙ����f
}I�nh�nQ[�������f
��w���������q
� �|��Z
�q
� �"�����"�	�
�1�����Wq
�2�F�����m
cq
�-���m
dq
�.��v��w���
�����
�Sq
�2�����
�3��������Z
eq
�,��v��w�$�
�$���
�Vq
�5�
v��w�$��$�"��
�4��v��w�b��b��m
��� 
�+���,?
�V��+��������r��ˋ�"A@ScN���k�
�X���
y���7���������n��%�$�L�H�;�Q�%�%�O�H�?�S���2�4� �+��.��1�4��*�6��$�������n�������S�NA��*������oUa~l�u����W����W��^�h����˶i�ZIOTMɖ������zqvsov}��z��٠�I+���v�ܩz���v��y������3�@���[�����|u��[V
��IN
�i�f_�\�&[$+�pԅ���Wg>�RE]��q��{
z�W�@��l����y��M�%X��=���Hg�.����{��u��$���L�8�&�O���勢��sz��^���@.�k�h]�^(�r��*�	kЅ���`jJsUHW��t��-�b��W��G���&�ީz������~��k&����������~�~�~��|bUs�����ųС��g����+������I�4�!�L���ꊣ��sy��\V
��GN
�i�f[�\#�
r�	�&�iԅ���^hGrSDU��sޕ�N
E�b�����E����ߨ��|��������}�t��l�A���������<y5���H���27v8o�u�xVvh`\k�h�l�B��}����{���E��$����������������0�
�����qa�z�1v���D���m����n�_��żƮn�NERXW�8�E��]���;�v�9�H���������Jf�HV
����|�t�ߊw���H������3إ��ui�[�]��g�P�� �0������[�Vsu]e�����ӛS`@FYQAe�ə�w��i�]�l�k�2I��v�v���3��!�6��R
t=
{}��~������i�
����������G
!>�z�z�z;�
���.Y3CZ|��w���b�
�{�~~)�{��櫦���|_�_CSv���������@���
����#o�D�"����������Vw2����
��|��`��-���$�
��0UR�l\��^�Ex2�����.�������gI/.G6d8�܊|�+�[��<�?��y��f���+�1�k�f�貟����D�+?GheG�B�۱����|`)�_P�x-���P?;\DWm��4^�Xv�	�=���K�WIw�w������^R�R�9��b�����b��FJ�A�
ܛ��}<�V�{�U��4M�����q������������y��������k�Q_awrd�Q���������xnqs\�~J���zxmprgep��n�r���%w�����oŰ����������t�������b�FWZtoZ�K��������y�iikX|B���yvhipb�`i��i����N���s�������}{�|��{��߭�>�cqwoxx��w�rq�o._@ar��������C�n��j�z�����������O�����z
������.�����������w�Z��w�U���z���I��5-}H�N�.������Q�
9Ew6<y3�����oWSljEW��t�p����P����p�����8��������u�+UUrb_{;�����������oq�xqy�l�'|�:�������g�M�t�vy7�o޾����`�a�C��;��
x,�e�
�kc[cMTz���������C
g
�x�u���v���G�S�g�[�x�&
W
�kw��R�p�D���̰��$N
�W�#*�7'
W
������
���
�b��
��L
�K��'
g
�]����;�����u��'
���@�Y�����v����M��M������?
���
��4��D���U
��D
��v����$�	�g��g��G���?
�gD
�+�M_�eH��er�M�U
�nD
����
��9�k��U
l���Hoo
��?
6���0��?
�yD
�:H\��?
��D
���:�3�"�)�����gUv3����E��)\
���w3��<�M8�M�����w3\
������"�%�����������v������k
��%ل
��w4��@�JM
�����"�&��
�����$���	��
��k�
���
����<k
��&؄
�w4��A�KM
�����⾽ü�J�������
��p
���)��p
���"�_�J\
����w4���A������x4���J�&�Zp
|S�
�Yp
X�
�����l���N��P)D
�U�����?g�FV
���C���������]����z��M�$X��? ��Fg���=��<-z>�I������-k�-����:���|�O�����P+� ������
�a���Y�����T��1V>X�s�;���y��K������&��9c�	�
�����y;M���Q]el�����.;DY^rw|��������
"'<IU\afw{������������'.4:@EIRZft�������������������� %*/3=GPX\ejoty~�������0�����?3<
��7
Q��d��<
���|
N���:�
�=��="
�;
|���,
�AB
<��6������F�F.�otQ�<������{i������hd�g��n� d 
�(�C��D�4�	���H�)�W�*�/�b�������3F�*=�'�#)]���T���s�uw-�U�
��H����F
��y1�W��6yk^$;u�����������F
��z{C�`ݿ����p�b�C7
;���
6
�e�c�lc[dMTz���������C
�g�[�x�&
3
�*
0
&�D-
{BV
���N
|AL
���
r�V
��b��y1�
=|
7�U7
�@�
�>7
�Q<
�A;�
>
aSIjHfe�����ű���������TV �U5S-�����3���54
��%���MK
mD<t?�������Ĭհ���nJ���[��I�I�H�2�2�<<�*�ࡵ�z�/
5
�J�
�1
����^z
s��
��'����!������0�	�x0��I
�:�
B=5ٕ���ȵ�sS
M�W������X�7')?5ܑ���Ǻ�rlVRp^bo�����R�Q�ڶ���x1������Ž��_�wnirgzv��v�uu�v�C������d�|�;�������>
x-�������տ�s\���xi��"��S�%W��E^
O�X������\d
�e
y0�=�K��
��w4\
�)k�s������r�jZg^Y`
�z�cTn_g�b�U��&��&�V����K��ͱ������mUVp]as�����62�����|�>������������c
�uwE
"��Eia
�wwv
q�r�����|������g�|�����*W
�ow�u
�zwb
��Z
��+��L�f�.��2MW�v4���
F������
�w0w-�(��d
��
�c���c<}
��
��f�y�%����y��+(�
��Ly.�?����Uq
��J�x.����(�y-�������.�����Ey2�
e�f�.�BUw3���sw�(�B*������ �VPK
!<i�X�:I:I<chrome/pdfjs/content/web/standard_fonts/FoxitFixedItalic.pfbChromFixedOTF-Italic#��*�+�,�[���+������Bx	%)-9BIS\e���IdotIJijScedillascedillaGcarongcaronliraEuroprescriptionarrowleftarrowuparrowrightarrowdownarrowboth                                          Chrom Fixed OTF ItalicChrom Fixed OTFh	7|B gd�f���j�������}�sr���x���{�������������������������������������������������������������e�~�����o�Auiwvptykc����m�/b�U�<P���!/CRg��"��g�Wo����[�2��	.	w	�	�
(
H
�
�H���

�
�=�>^�����E��e�
k��0S�)p�?��9���R��+E��w�g����#���`s��1���'H���  � �!�"	"'"N"{"�"�#/#�$"$@$c$�$�$�%%>%m%�&I&g&�&�&�'1'`'�'�((?(a(�(�)[)q)�)�**6*+-+�+�+�,,J,e,�,�,�-Q-�..#.I.�.�.�/j/�/�/�/�0K0�11@1f1�2)2e2�33�494�5p66-6q6�7
7(7I7k7�7�7�7�8.8o8�8�8�8�8�8�99$9@9p9�9�9�:/:N:n:�;;�<<S<t<�<�<�==,=�>|�������I��=�"��n�t������r�hfeii��~�>�}۵�~����Ȩd
��I�~�U��d
��I�~�^�4��u�*���9�L���i�Y�i���i���'��4���3��u[T�u�
��u[Q�u%�W�d�4/�W�ʿ��4�f�4"v�+��@����(�Ü����w�}��m�*Q
��+����E�G�R�/����٬̺�ru��fj
��)U�Tp�n�i���q
w#W��!q�E�l�r�t�lYQ>o)Y^��m��R
|�(����Ĭ��ū���km
�B5GD;Ĕ������pr
����a�Cņ��`�E�*�4m
�B5G;;Ĕ������gr
|�g�8�m����|���X�+���x������������`rTgc!�b��z������ϯ�}r����m]�]9.`=�e�g�g�9yKE�W�P��ǣ��v�un`dT`h�����»���v��w�������{
4v�nw�>��n��u�q���O�3��"zy�z��7@�)�F,�6�O4v�nw�
��>�������7�)�F�t�q�X�P�4+�O)�3��"����������k���ʫJ��)���$^��#Ft���oI�n�y��y��y�Ue�MQ
��M�TM
�T��Oq
e�O�U0v�~w�U��u
�{��*�{�G
�|��x�$�x�5
;�Y����O�w�U�Dc�|�w�����F/�&��x�����g��Z@���+����n�ߝAO�F;�C�
9r����T��w�����*6
�@�|X�S�B�2z
�G��T�k��4������1��R
~C�����z���	�9�7>JCI|CQ
�ۘ���eSx����
�M�|�c����1�Ӣq�������M�m���̻��8�3�KIyja�X��ˣ���vSJ9A ��KȎ��C22Y&PW��Z����������8��w� `
��<
;����Ɖ
��0���ؒ����Q��|������]�¥o��~��'��"�E�:\T�r]��S��<
��Q������˝�дcT-#P UT��b|��v�����������%��N��J�����]�/�|�(�O����4�8NPgbl�F��ɳ��nDENA/6c�ؖ�v�������2��������f�)�����|�m�_������`��d�.J1+�X��%���Z�m�������T�#��D��_�k�w������ϹcbABW9m;��I����
����a[.(bGb:��}�A����%����}�	��U�x�X��( �B��F��ʓ��f�&�����.��dqLbBC^�������0<�|��,��Z�$�Z�5
��5
����2��$�2���*��.��@5
�v������l���B����-��'��H�B������V���Ua���T���U�����Y�Ե�H�B����BzM���-��'|�
�D�#�����H��R�D���ڛ�$��C�,PKszY�v����������^e-�Z6~6��p��{������u�ibcgd�}�߯�n���������>n��i�����,��X�!�4�M!�!�2�穲���X)BzI G���?�Z��Z -K�Iqt����������fsY…k�~-7#�j�n��������J~P,Qq������ʰ�bz����������e�H�`��e*
2�����,
��2
X
O�l�:�?�������P�g�����b�����������X�X��Ҟ����,�*��E
�/�Zk
�Q����g���@.$b7�E����P����L0�qAy�}��w���n�dMBpF"Q��Z����k\�x0j
���sR
NйJ]�U�T�
�8�a��'�/�ߨ��>
�������H�G���7�����E
�^
:��
�Z����>�B#��J���[a
�����Ob
p�����[�9z:Q
��mb
y9�9��]�
p����P��E
�/�Zk
���[a
��T�������U
��[�:}CQ
��[b
|B�:��]��n�#���Y��E
�/�Z�
y�9�����+�4
=
���`��X�����p�j*
;��`��c�`<-
�h*
<�
�*
�h-
�d�X����X�*
�j-
�1�Z�
>
��.G
�D�
�@G
�*E
�C*X
�Cy��k����{�e��v��"�|
�6
�8
�[K��:{_M-q������q
>
����{�m*
9��-�ݥ_�A�8�3�**
�	v�e�b��v������O�C
������*
�k-
�&X
:>
��������q
h�Q���
�*
��-
�^
�>
����c�<G
K�
����s��^
O_
�6*
\�
�G
�(�^��H���+_
�^
[��Y�^���6
(�A�T�x���6
�{8
�5�D�S�{�1V
�1�Y:y�|���8���l��&��K��O�P�\��3�Z��\�8‰��\�C��.<�$��@� P�����1�����������*
�1��1��1��'�\���-
�.X
<�G�g������kR� nJ��A��T�~���/�\�?�T�������|�������������{�so}puu��u�tt�p|z��z<�������_���J�d�^�3�O��]�L#�>�njpjzfz����+��G�%��L6�`�&��@9�����E��u�������l*
;��C��\��G�*
$s�`�m����3�Y�)��-
�^
:�J�y��u�$ֺl`�&��Bw�{���������e��u������-�1�=�-�����üoh�z;���Ob
�c�hX�T�:-: ��
m�}Ȁ߂CG$g/BA��c��P>
�T����*
��
�Cm�-j
��c��b�cj
��-�C2X
�y��m������
V���Fh"�G����-������6
�|g
����w"x#w"�s<f=2f�ۿ��I���6
���v����1~�
�����6
�p8
����d:�d�F
�w8
��v������~��K�����2���F
�]V
���6X�c�O���8�6
�]V
�>
q�v�	�jq�v*
+�S�P��P5-
�j*
J��{�_�s�*
�U-
��7�I,�I�*
�dE
��	�s�y�{I>
���*
���B����*
�[-
��T�w'�w�C
����k�B�>
�����8��_Vn�)���F�]���f�P������F�[�������N*
����*
�N#�`�3v�Yw`��
���`�Z�q�\z�����'U�U�N�"�n�N-
��������'�ܦ���ĹO�S�q��;�|�x�X@X-<T�C���@�Y����e
�a���F%���1y
� ��6
6�������Z�%F6wsK9
��Tw�\�s�_Z�Z�EA8?R�V��گ����PK>]8Vf�����@
|�_�������x���!���{��@��3���/�2�0�FVru���f
�x$��5�H�E������LH���'&::��|���������q�	vm1Q:�\��P���ľ|g�|<j
�MR
�\�c]�Q�[,�)���J��ث��|�a������x���6
9����X8
� e�W�dF�T�3���,�*�8��جɻx�������SR��
��
�&_��|��1�!��W�;�������Q��5���=5�G�!�奯���`28y\�0����Ɵ���!��r�������&���6
�q���q<
�q�����	������jI�a=1�?yq���S�J��W
�1��8������ʿ?
0
����.���r6
8�����ҷ�ߐYT�Z��78
�o6
:���ʗ��
E@f^O���f
�%��{
����������D���%�-6
�B��[8
�%K���G�1�����
�P��T
�#��vY���������E��
��9��	aF\Q\��h������� ��Z͵�j�1�MS8
�jF
+m�m�g�O��G��w
�p�T��_��f
�&��6������-6
�C����k8
�4)���G�����~w���G
I���������mr}���L���*
I��������wrx�t�xP���*
S������m�LUZigf��n�Uith����_
������m������؟�r*
7����������yj�����Z��8-
�p*
8���������b�@E?c^O���8
�M��7|�������;��A��9���,�]�+�(�!��8’����
�@Q�	+)��Y���1��
��ſj�����j!�X��x
�0��Me��n�� ��
�'�M�!3P`_]����W�%�o��������MU�+5-7L���1����;c����k"�W����5�w���� xBزuS�6����$'�F��Ҙ��e�N����Ӽ��*�I?��L2@S�������2����*
�F��d�������i���y�Q*>LT?���L_
�J���|����
����|�<
r��l������R�S�T��<���ꇿĿqp��ZQ
��0q
�[�bX�Q�6g*B�!`օ��ڃ[0��bJP��j��b
|���w�����T���=��}��ݨ����wQ7hEQ]�������w
����q
q�B|�c������
X�����E�eײ����`|@�*
:�
U
�U�^�z�Y[MUMVb�������������v���R������w
�jg
��h��8���w
�rg
��v���܁���������D��w
�28
��#��k��[���q������6g
�����z�hG
H�;���L-
�iG
;�!�8�H�/����Uh
���0�����^h
��
�.�d�9>�1��u�l!X��x
2�5�u�x
�S�X��|��!���x
�_�X����"�7������-��R
v;����֕��k����������p����S��
���s�x�St�m�ez;�UeL�{�{��Y�g�������������~�n��¡����_�������#t]�qYo�rn8j�yv�|w�<��<����h�|X�q�������u��X�x���������ۓ���e�2��V����im�Y|:�r�]aa�b�u0Tu��]�_fkz7V�#���q�����ެ�Yȑ��Y�������xz�{��|�Ϳ���d�wxOafnw��u�pn�SKb^gb�v�g����#����q�w������t�f[hic>���
�]������O��VZ���#ӏǧǭ��kPKrI)f»����{l��Yj
��6U�Xl�j�f��� R
p�!4��,�.�Xӄv���q��3�a�辪�W�������������S
t{��{�wy�pig�|i���Ȍ��n�������B
l�m�n,�R�#d?:K׹`v���w�߼l���e���d���������\�M��X���������z���l�BP��lb�ZYU{na�W�fl�LWQu]L�h�n�q�������RH�!70?Q�������[����*
����G
����F
��_�[�*
�d-
��E�@"�@�*
�e-
���[�8
�~G�E
�{=��L�������Y�I����Y=������q��=���Ĭ��~�h�㊧ް�z�u��e�L���������?u&���0�PH$�5p�v�jZJVRs�j�y�N�9��w{y}}m�Z��b
�����|����������"�	�}�t|uXE���������'�d$���$y����׷��������f��+�#�Y�<�8�]� ��A�W�5�e���/� �.�C��9��7�.�'�1�
.���}kiczhQe�����Ӫ�|w����fn�]�0' 9�Wҽţ��������������V��%������\�X[Tz|m�P��������a���{�uq�q�nC]b�e��������_j^pbqx�����������v�w�����K�M�����Y�$n��9�S�>�M�����R�$l��v�[����h�[j
����>y���7�����޽����t��#�'�H�M�5�]�!� �:�^�*�b����"�;�?��3��0�3�,�+�2��2�����!�m�	������c�^#��!��j
��y{Yq�e���������������|�������(?�M����_�(�'#�ʚ����У]gOF1COh�����y��Gw������&���H�O�
�O��GR
g�G�O�V�O�����A�
�A��������'����<
�=��ڻ��h�H_JtXs�R��������uq\bcht�6�������
�^��0��|��{���ޮy�u�计����e�T`\wud�W�������}q^ZrK�y�x�y����𩧁njqiKfg��oe
�B�����{���$�"�u��x��߫ʼ{9�*
9��h
�p�5{Q��Kj�Ɩ���h
�#��j
����(�ڶ�(=��R����R����R������o�4�+L�!S�>�5�Q��p�3�p��n�r������s�cX_e]������v����}������x�|���\sA����������xy{}|����������>����<
)��&r�S�^��)���i����m���,�k����Z�96�@���ѿ��ϝ]h�<JVN\_���v�w��������S�K�Mk\���Y�$�H�\���S�>�MeZ��`�\v��]���w������gZ_
� v
a�����`��eR
�0�e��v���x�l��Gi�����n��6���s�Uۙb��5_�]v��|���w�e�\�q�l��Gg������pv
4���t�Uڙd��4��s�$�kv
�#��ƨ��r�Mecxil�L��������snbhjmt��a�\v��B��v�w�g�e�������XY�U�v
b�����a��WV�,�fǚ�x���%~�n���Gj�����}��w�������z�x��������l�]ifyuk�X�������}r�dioW��V����jmscYmn��v�1��3�
��Ež���s�x������u�hhaib��A8z�^�"1�^��ɚ�����U}D{fa�c/q�����Ӗ��R
��������O
� 
�C�}��&���{y
�������
r�t�Z�q������w��$��#
��������fw�O
�z��y�;��
�����,<�+��#
~
Ӽ��qO
�l� ���������{{�z��z�йʱ�[�s}iffv~��z�yu�aRQ\Vm��� #
~
��O
�U��\
8�<��\
9��#
��� ����������c������=]�q�ȼ�ū�JLWXNƓ������o�uw{v�q����E#
���a
q�V�����q�V*
5��k�9����-R
x(�*��\��R���;W`
9��\�+x&j
��/�*-
���X
W�[�9�>��R���+��v������������z��¢˫|�w����ܾ��{�fMEZD�C�	�C�D����m`�w6Q
��eR
~Q�I`�U�T�"�=�D"�0�|xB�����������un}~���[
��R�Ƹ�(
��������$oB[
�
��U�ǹ�������x��$���(
[
���T�
�y�E�������,6���(
���uv��w�����=�#�\
4�F����
4����(
���\„
�t��/
�P�.��&����y
���]�ww�l�������x��#�d��-*
�C�]�@F
�)~T�B-z
�D���]��t��?��z�G������
�.6���/
���_�bw�t��5�"���	1�N�	���	1����/
���W��c������o�V�I��"(����-
�c�c?W�d�W�
���W�U���T��c��*�`��!�'�:~��N�߼��a~������7�Z�k��������
�4�;�X�o�4W�0�N{
�l�����������xz�|��}�һ˲�X�s}mddp|��{�xv�^PN]Uly��{��l}
���d��'����#����%
y��z���E��������x���%�h�H%
y��{��Z}
�K��z�@�������-�
���%
y��}�Ѽ����6��.�*���������zz�{��{�ѻʲ�X�s~kefs~��}�yv�[RO]Um��V%
y��~��	��@��ޞ��+����	7�?�	���	{
����%
�v�?w���]�[�=��=��� �>�i�Ac��g�A��@]d��=��]v���`��������g���h�YW�l_�J�?��+�q{;�A�XLH�l���m��z��1�
��2�U��s0��_g��q���W�L�cc�W��r�����+�)��{q�Y
���&
�<�/��&���{y
Y
�l�����z���%�Q�&
p
�M�P
�B��=��
�����.<�C�.��"&
p
P
���!���
k
�;�
�
��
k
�v��&
���]��
�G��1
�4�����y���$���a
�������M
�!�������2�F�7�$���v
��_
�:X
#�D���]���}Z(Ad8|�`�������4�_�|����vљ�����T(�i��#A�s�|��v�ttC�&mmY_��ikSm��q��3���\�\�S�>��|�j]�%�H��	+5o�y�p{
�S�L��k
K
������$�����)�7!
;
�awJ
~�<���y�C�������1:��%!
K
����r���)�l� ��!
;
ƽ���vJ
���!�����������qw�~������̾�«�Z�s|hfer}��~}��uz�UVLdJg����"
��:
}�����+
;
�J
~����\
6�B��\
6�
�!
;
��HjvJ
�����c�m��Ǽŵj�[MQYOɕw�������}rtus{|�����"
�:
{���+
|��9�������X��c���������jb~g>l�̍���������������o�3Teqbh����+U_~{\V�������xj���s�|�r�vu�v,��Y�H�m�����K˛���Շ>Y����u{XIKhv���קŌ������v�:����w���&�|���������x�}����ө˳��vl1U8�g�����ži�{@Q
ذ�Aq
�`�b]�P�X&��&�M�wD�������~�wy}ww���|�4�
��}����%�����'�9�k'
|�3�
��x�����w��&#��'
|�4�,������;���y�A�
��
���	�0�
�{�Y'
|��8�2v������������n
3�E��n
3�)�P'
o
�w������%���u�&�a�43
o
�w��F
�:��Og
�N���;�/�K���p���&o
�mw�6���y�<�
�	�
����0>���"3
o
��,���n
2�K��n
1�X�3
|��ƿ�w��/����P���]�(�}�iL�S�f#��"�U��n�&�5�l�7��x��Kf�b�]�^]���y�1_�/��������\R"�
*�'D������ۻ�������E���}�������yz�|��}��ƹ©Y�s}^bes~��|�zx�bVNeKh�F���v��5��|��ڷ����_�����X��7�
�s��7��������Ōg�8D>e_M���!�V�K��6Z
�}����#���s�*�s��)
Z
�8)
������{��*]
�bwI
�C���y�8�������1?���~)
]
ɽ��I
�,�����������|{�z��x��Ż¨[�s}cegu{��|�uv�iYPgJi,��)
]
�I
�������
�8����:�b�k)
�v�U�Ew���.l
�uqopm�\�G�>���>��dl
�uqorm�cv�.Žw`���b˾�i������)�����r�ȼo�LX��eZ�S�-!"�s�U�T�eۍ����n�f�e~]h}R.�ef��oi�x�������������r�N
��w����u����!���|�&�e��$
N
��w����w�����w�� �;�,$
N
��w����9���z�=�������2=����$
N
����������k
�=����{
�V��$
�1��r������p�����v�� ���U��*
1�6�r�M
�R_
�����&���M
�_-
�����0�1��������:��#�V���
�/��de��Y��"���*�7�,4Iq^^����V�� �<��������<R�
�<4-L���1��d��������3�K����1���\Y����0�6�d����QZ�����$������_�Z�����0���>���D
�>7
�>�E�������1���������-*
�C��[_
�$K���Fz�iv����#���hz�VG
I�k�G
�NE
�*�kF���xg��c���|
�6
�f8
�K��:{�M6q[�����q
�1�����
���T
���������������E�%�
�9�t.�i\�l�|�s�����U�<��N����F
�>8
������T
>
������q
_�[����N�Sі�SD��j�*
��E
�\��0i�U�k�8W
������W���V����.���-V����V
�SX���3U�U�2�Y�s�W���[�\�������[�`
���:U8
<��\�*x'j
��.���?�=�!�j��+�2����,R
w)�b�%W��>�,���|�8�����e��W����W��h��������mb}hLd�֙������������x�6F_Zmv��i�K�/P�\��〨�ˢ��N˛���т=Y��W��1��~)L~Gu�	#M����v�p�6�:ȑw���������Ǻu�l؅x8��������}}�mhzy��lP���x��ɵ�~�s���� ���<�4�<��������sn�~R��EM�T��[N�J��P0����߈�uZ5�"�V@S��[��K�v�G������w�
����������z�v͂{G����������sl|{���p\���|��¬�����v�����ذ���D�=�E����������xs��]��#U�_��aT�R�#[A!���Չ�ycF��\IY��`��b
w�}‹��Hw�������x
_��z��	�
��!�8�:�E�%���Ͳ��qh�z:���Pq
�c��iE�V�3@L���t�y�}�laINV�J>��`��b
���X��K�6b��<�
��
]x|‹�%����*�‹��Fw�������
�I�2d��=���^w�,�+�6
t��k������F�@�G��
����ÿup��X���4U�Y�bW�R�'V8�	���Ԉ�x_>��]J[��b��R
���]��	���,���	5�D�	���	4�U��1
��c‹���������A��cVk�,���O�f���&c�UQ
�����N�d����H
���‹�Gw��~L
�(��+��1R
w%����ܕ�`�m�.���������#�O�-�7gP�u����������W
������������ae�n��K�!U�zQ���� cU~�y‹�9���‹��Fw��4
�=
��H
�1���8��������Gw��?
�0
�o��L
����������y�6�������3@�����������;�1g��2��	�bw����+���+��<��n��ȼƨ`m{\dU`d����q����q���{
�c�ʽ�l�;�l��g�o������o�bW\\V���������}wyxv{����(��w�y���dxgeao�v��������}�s�z����������}�������������xy�{��|��ŻéZ�q}]egu{��x�vs�jXXgIhe
�����w�R�"U���/�w�V�"R��{��{�W*
�W�{���{��*
����������rd
2��w�V��V����*d
0�w�V��V��u
��w�h�������rd
2��~�sd
1��t
����!d
5���~��#d
5�t
���!d
4���~��"d
4���6w�@��@=��O����
��6Pi�6��V���d�,w���d�I���I������d������,Kk�,�
�Q�_�d�
�+v��w�=�|R�^������_�L<@E:|�����p��A
�\A
�$A
|�!���!���W�w�*���n.
����l�8���l�:���Q.
��.
�Rw��������O���E�������Rw���!���O���Ql\�A���� gv�fw�n�o���Jk�v�(���C��6����W��擛����������S
qy��y�zz�snm��nȰ�Ž��H���\�������f���w�B
��{�xJ}S��j�i�~){R�Ucg:K���"�ȿ�+������V����<��ڧ��}�gREqJ,I���k����������������������sf�{=���SX�Y�c[�G��2�\TmW��s�v�{Z|�I��|�����|�y6
7��H��n�W�V@D�m���y�z�|���W
x���\�QTw�w�z����7�W�#��8
�*�g4�V���|�,ټk_�+�#�?��v��������`��n������������O�����0������1�����������q�_�s
����/������������N�v�w����8�����&X����I��TUz7�
���s
p������/����*�J���N����v��w��O���	�������~��t���J�v�2��2w���,�J���N�S�%�X1����(�J��N���X���|��|�W��W������������������D�m�	���
����h
�M��!��V����W
�����W
���������������q^�xZC~8ws) h
�N��"����)����������6
!����'V
�%��W
��T����W
�����W
���������������qo�o0XZE~y6�g
�M�� y��>��
����1��9c�	�
����j)8im��;����FS����,1?DNYdi��������	#+2<QV^fnrv����������������������	!'-37BMX`einrvz��������������cM
3�����,
��2
z
O�k�E�@����"
�:
~���+
�<
7������ˊ^�"F6wrK9
��3�I�_Z�Z�QL,? 
�Z���~��E�gֲ����x|=�6
:�U
�S�^�w�Y[MKLNl��������������&��M��M�O�_��5�`��\�9“��U�<�� :���@��R���U���k�mp0�Q���0����<
�{`
�P��c
vST#2e�ܝ�������<
���8�������Q��4��	�H,�A�,�娰���^27wZD&�㛋�����Ơ��	�"��r����Qq
p�����]�9{9Q
��ob
z8�8��^�
q����Q��E
�/�]k
 �C��8�	��.�[�*�+��	�;ɚ����4O(,!�	�g����NK>\8Wd�����@
+-
�`*
W!�UR�^�����l�=8CB@Ē������slaj_Ufq��D
�\7
�\�E�qS�<�3�	�	"9�;�!�֘��x 4yRo�HK��Y������LK,%O#1O����*
���C�~���*
�Z-
��R�x&�x�C
����i�C���-
�
��i
�;���w
�OD��:V
���^�7r�vFdT�S��t�*��h[�|?Q
��jR
~Iq�s������u�g``ic���AF
�+8
�B,�T�U����Q�T��ڶ��|�_�3�����bM�F�?�+�$�|��!�=�ٟ����B�6
�����Z��<vY���z���������|L�����p�q������s�leghe����uY�œ�ym�	�#2'�*
�_-
�/���E�U������@�3e��3���_x��������;
�swJ
~��F�3e��=���_w��|�`����c�J�`�Ŀ�T�����}�wSvg���
=�Tc
�Zp
�xwP
]
�twI
�a
�^���
|���0X
�U�S��]�R��~��v�Cw�!8
�T�V���9t�v������}W�[����ݽi������y��m�Sq_laUhq���v�2��4ww�
������)d
/��i
���$�]8�ݛ��w���:����������qw������W��xw���Z�;PK
!<j�r�
L
L6chrome/pdfjs/content/web/standard_fonts/FoxitSerif.pfbChromSerifOTF&������<���|����D�/>Euro                                          Chrom Serif OTF{	$,JT`hq�������%3NUhz�������4<HYcq��������� #*ISq���������*0DWbi{������������
"(-;?LY^bntw���������������!!
x_l*��."���������k}��tt6&
>z
p�l|��ltufn������+�*
+��;M7N�M�"�9+
-
���W
$@
�)p
�i�a�4
2q:^
Ltryxz.
��
�$�mI�ə��&}���O��!�s�.��|P׆�vA��O��W������P����ؓ��x��$e��%-��9���[
�jw�
��)����F��������-O?�I��N
��#���
��pu|p~ufn�����+�M
}��=
9���e�$�ފ�}�;�����D�-;��9
��=o&_Ym��)�;���'�(��7
��
�����㛲��ڕ>[��\DW���������DtBunJ����D�����w�?�Θ��#�g�I�����	;
����l�/|{c�W��vUm��,�8������o�y�u�d�u�e�Mr�
�z���x�z�}��|Rcz}�p|<
�~�����*5
��ns|n�Q
j�q����������d
G
�
C
�\C
��ox���u�v�(�ru
���������/{������q�mm������o�7_UuBi�y�������sqe\rPy����/s�xч�p ���~����~�c�s�m�_�m�ٝ�e
�����3
�oovb���)�����
����������)c{�o������������X��t�3�����x�vj�`��uT�2�@�<�
�t���A+�5{
�w�w�������l�[�5w������Br�RK��*8yx�����*��T�8?�j)�k������ ��?�2>]Zo���<��i�z�7uo}�Xif`C7F��'窰�o�t������t�ootto��,�������˝�������җ�ב���xՅ�wA��V�F]�I�I�&p�u������t�pputp[zZ}[o���
o�}����t�ooutoo�v��too�}����w�ooub~{��m�x�p%��%��Ϗ���x`
�#�,��fvw��xl}����#��$w���
mlCD����h��i������x�p¸���^�Tv�Fw���x*`ISA�f�hu���(�v��������\mmlYYS��vn�vf�|�d�Z����������Q�T��iv��|to��d�����<v�*w.o�}����c�v��w��(�����`�Xv�c��������s�0v��ws�Zv��ws��v�?ws�0�����s�nntsn�=
w��������B��[�v���h	7|B gd�f���j�������}�sr���x���{���������������������������������������������������������e~�����o�Auiwvptykc���m�Ga�Q������4~�q�r�}��>]�	~	�
J
~
�
�T�	_��
L
g
�
�0��DX�0��'>`��T�k��
��Lj��U�_��n�(y��^����_u�D^�� 	 @ { � � �!/!^!q!�!�""4"Y"�"�# #=#X#k#�#�#�$�%%'%<%O%�%�%�%�&&&f&�&�&�&�&�'#'['�(2(D(Z(�(�(�)r)�)�**1*\*�+e+�+�+�,,-,9,D,P,j,�-)-Z-�-�-�...`.�.�.�//6/Q/�/�/�/�0"0�1h1�2,2h2�2�333#3J3[3�3�3�44
4474N4o4�4�4�5u636Q6�7:7P7p7�88�8�9\9�  s����w�� �D�D���P���ʂ�]_�XN=����N]�n�s������q�nnssn��Xv��w�������CD
�=D
�v�m��!��aw�������m�k�m���m��&��!��%��aUl�a���aTk�a�Z�u�!�Y�`Ƚ��!�x�!Iv���w���?��&���z��i�4�����K�������p�����_E�\�iL�X9D)�W�X��-T��u|�M�vdO�`�����������D�d�kD%:|j��p�nv�ĩ������w��$���#�w��4��t�(�)j������[<�%�����`�'�%`&:Ԍ�����ɔ&X[�$MM������<�%�����`�'�%`%:Ԍ�����ɔ&XZ�$NM���9~�W�w������ڋ�1����4w�w��ɰ����iY81\p}���m��N������|�mswtWAGܙ��-�þ�����lv���|eUM<neX�^�g�˩���L�B&W6C[�j�N#O0F#��q��î�r��QFZ�tDQ���Թ���;�2�E�E�Xv��w���CD
������E���$�i�f����������///�*�@�s�'�,���^���5�{�/���@�s/��,�{�>�
��^%�q�'�1qy�e����v�w���s�,���\���OgUlr�}������o�����qɢ>�������@l:������a��s�fqGfJ����������q�m�d_�~:~�,��^p�p�Q��`��7Q:�L��v������,�������c�v��������|�|�|��|�z��z�~a�~�| �8v���x?
y`_�x��@����8s�V���V���� ���ѶC<�z����}����Ff}���������ύ�������G<���������+�
��t�L�L��t�
�
a�w�L�La�w�
�v�8w�i�����@w�������H0}�����qv�OA_Q�����������~�Ldl�f�w�<�D�����1�03,[�+m��������
�/X@`14.�F�Q}�������Q��-��Ѝ���x�j�+r@bR.ns��u�tu�t{t�oW↣�1��"�6�a�`Ͷ����p�H�l�c�������խVX/,c0n�v�1�[w�����1���1�1��%�[_��[��������}���������y���N������ڂ�|�x�q�a$����������Ei�4�ASJpv��u��vt�lc�n�g�s�`�2���䀞`�@P�-�;�}����w��w��.�
����F�������>�@�_�f�@�
�F��3�D�!Sd�g^u�!ǐ����������1$�dWL@C��<�v�����A�@���h�"��P�'��������c}�����E�`�L��l���9�mAERaL:]��濲��q�2G^l4=�9��:����z����׵��C�5&U&:�i�A��g�.������ظUT3Mfer�v�t�������u����'�Q�i�s�c��dT�N��<#�8�#���������sX�x��b���TJ�[ v�ק���������ͭ[R��V�Y�h<������w��&C��C<�v�(v�w���������xt������P�v�[�Tf�y�����������9
y��l�r�������r�llrrl����~����������Q�l�Q�k������c���c�������������������������������]�Q�k�Q�l��w�c�Ͼ�_��w�8��ᥳ�����Ѿ�D�F-V&i�n�������������������جHOcxFyci=sFPa�n�t���������}��
���z��7���
�
������
�ӻ��taZ}c~kNrjmoho���"R~��Q����=�$��L�Z�;�%�l���W���٫���qRMq2�B���#�r��
�-�K�+��5Z�
:^������F�[2�~�]�M�>E�π�Ź���\��Z�vQ��������'�}�{_�`tfx���sh��w����w�w��xq|ms\l{.
�ʋ����������:�����r����;�4�������/�C�4��xڜfw��lg7��Z��뫆���{�j9��
�IQy�����p�����������x����o��}��9w�a������w�W��-��;"����uY�L�����������X��rg�|li?��R��|����
����{K�[���{
��������b
�����.�ފ�}�;o
D��s2�.�h���3xʆ�qk�}dtnK���{w�������������D�w���e�%�֊���7o
@�yw9�%�h�����N
�#��xʆ�qk�aEq@��}��ʝ��������Z��φ�{I�
gFtG�/#��I���'�� �(C����guo�j��k]�&�h��;�<���*��j��ř�Sҥ��������Ϸ��w��������=�����M��M@�k7�x���=�����6g�L��Lg�1@�k7�s�v�*B
�j���w�D��܆�sD�SW|}su������~�onzmx}�{�y��~���!��V��ՙ�ِ�����
������=����E���"�i�$luz�V�x�ŞR�g�K�}���R�P�Ѡ�ǐ���x����pinp�v�_�Wՙ�ِ���xφ�z=�:Cq=������w��w�]���Y�s�YhtkK����
�����_
�������������WN~i=�x���K�t���Fڣ�ʐ��[�q���{���\xՇ��=� x
��v�����3�x_
���:
[
�
��p
�i�a�a
�D���2q:r�p�������m
���7f������xԄ�mt�Y�
����������&�t��F��2���������
����3�!�'y�2��"�S�>����$�p�z�-��8�8�-�	�y�i�
.�r*���y�����:�y�o/2��/�o�ʋ���������~�����;�����OÍ�����5�b�s�u��b��������.�9�2��xф�oo�cO�j:��O�������������#�*�7��[}�������y��}�����������������#����8�8�7�7��ɾ���19����}w��q�wt�qU
pt���v������4���7��������y�����>����>���������M�k0�n
����-c?�'I��v�5w��=�������`��������ax���~b�:�>�B�y�����Î���x���w�o�ߕv�5w����3�����.�;���f�#��U�`�N��������Zx���}dv�m�v����'�}���������x���{�8�C ���)������������xĆ�d�8�'�����-�������}�df�����������t�Uylp~Yx���Ww��d�T���&�@�ѥ�����x��lxzwtn����$y���������xŠz�*���*�����pT��v�*r
���L�(�l�������ox��u�l�(�hu
������x���~�*��J�DF�o)�{
�ޱ���?����0�g�?Z����4����ۤ�g<�#v�?z�~�8��|��`��=���r�g��gr���c�4}�m^��*w�����#���#����H�M�����@Y������s�^�����$�r~�
�y�}
O��J�������/]anUfn������+���kC���/
@
�)�>��������CX�7K4_'H�f���������sw��w��-����ϝ��$.I{��rD��������klm��f�p��M�2�R��
?VQt�������0�'c�ym�_��s��������m��r[�g��8�8�2�J��̶�������xw��w����g�dsaIC�)�ϼú�kG������������H��������[zZ}Yo���
lm�&�jm�`�,���`�#w�հ���������[�7X�M�"�/+
=s�v�6��a�������/������������¡�zy�w��w������_�S�H��ES8k��>zxI��n�&����n���n�Eئ��'�lfywlk�d�	︿�Z�A�-�v�[����;�3�ϐϾ�z�6gduGq��p�q�������O�j����஭�|�������d8�t�wZ�$dS~h��w�cbxt��������Ao��:S1&?��������v�*��sw��N���l�Z�x���{�����¡dH�P>x|Z�|�h�Y�����Q����OZkYh�����[zY}^~q����
vm�WB�zU��
�����7��1WxTxWz|�������P�h=z{M����d<�n������+����������P�9�RYx���~�~�vxv|tf�x����	��]��WwWyW{��vd�v�Vw��w����~�w�j�u���?���L�z���~~��x|�n�p�d�X�-�V���������`}�������|}ciPTgl���4��VzV}Zo������q^�pi�yH��
�?w������Q�z����]
�gTwtP��9�v�,���L�L�8��r�h�j�������п�dI�cJt~^�|�t�b�l���������ӌPX�]K{Y�|�o�q�_�����M�}�#<NOsv��l�JUbk^P���]zY|\~z���SL��Hmc��v�*�s
����~
�nPq~����oF
��5������z��"e��%-��9 �������7�
9LX���3����u��3�_v�����[x[z[|{�������zc�^`�lI�z���C{���cZ
�Xv�c����{����\z���^�y������Qh�dt�X�)���+�D��ѵ����DY}j@����k�{Wx�yz�|2d��
����Dos�v�!�U���А���X�i���k��������������������w�ieiwD\����ZwYyZ{{���SP�~K{�T��}��<��w��.�W�ؾ��������������������ܵ��hf
Է�v%�������|������{�}|�jثy�skR\�
z<���������~�yukX�ƪ���&�����fV|keduuu���������'�p��������$�
����������2
�uR
},��������v�dw��G���{�m�D�r�M`�����������ˣƒ�����|���xtgD�&i+%��������������X��v�dw��H����G���t�[���i�������������g�����������䚰����p��|���}s�p�r1�h;�j|�����������_|������0�[]�P�	��������H�v�Yw��%��]��%�zv��������������|v�}l�|�]�z�p�e��N���������2|����v���N3x�bȝ������c|�����N���/k]}p��
��w����h�h�
W`E
���J
=�h;
�a��F�j
{l!�V�
�G�G�����x�_K�
��w��D�w���8�~�=��ٝ����8П�қ�$Mw��;K{aFy�1�*v�Fz��U��?���b�
�p���>��ȟ��8˜�ОF�z���;�N�$��{�aF�
D�d�y�=z~dD�
FxaD{�L�K˗��K�������yy�w��w�ɬ���g�htrbcmn��i�id�\Hg[aus�Yv����#�����]��HsZ__a������I��]]�m�s������s�mmssm��R����s�����
�������������~�hE]bmMt|��x�g��{������k�`���h_���x{�{�4��h?�<�S��\�v����޵��������o�7��Z��1�&p���+�W�F�l��l����̐���vuJ/kS��M�펬�Ɗ�����ć�����◷�W;j�i������]�7"1B�7k�}�v ^��`�i�n�d�m�o�}}�}Y^rW`�y������� �����������{\[uy��������]���]��
���������LCASL@�?R��G����t��~�������3��4�褧����~�u���`�24�q_�``_tq4�^`�2�tr~b`^�^�p34�v�J���w�R�����R�t���A��A����:��&�B�ί�����Wx���ws{�~�|��f�
�g�������������x���_�8�7�$c�;�}?�Bc�BNS�_D��1�zٷ����_�F���_�)��t���w��g�+�����ჷ���b�[���r�������ǹ�ry�������~}�n�������N�KBFe7H�h�]��8�WQAZ�Q�V�&��{aYhnZTx�������|�jqoygR�V�����q�I��?�V������P_iqfL�	��s����������n�[n�'}��.�����%������������>
i
�J8]whcI%a��쌶��٧Ra����6�d�,J�8�!�R�̰�����:�3v��������2��H��������wy}s{�����ϡ�$_��������������~�������*@ihk}�z�����������zT-iPvO�^�u������y
��wP
�������x�QP
�����c�v�h�c��h�^�������_��������'}�}�I��C��C��0�~�o�,�6�Y����ڻ-ˤQ�˓x�l�q�R�������S�P�E|����p��`��Y���0�F���mRVweZ��>
�i
s��������������n�IJ�d��Ź��ŹXQQ]XQQ]��d;�L������L�;;LL;�c���w�H�T��>w���U��������{���?�}��}�>c�>�{`�{R������M������V
g��( R�����`w
y|��}�~~�}c
�N��$
�Yv�d�"w��G�k�V������O����������b�S�������wv}�|k�����3��r`[X�Oo����3� �Ms<O\�s�������mڅ��Wv���z���z�l������˦��� =O���P�0 �pv�w���Cs�k�-�w�N��#����������uhp�lvt��y}l������Ϸ��h�`������hR����w����5
��is�n�\���l���,����?�M�����M�6*UJ<Ԥ������g?LbbjG��y
���X
}y\Mj]xp|u������TX
~y^Rj^um{s���v�v����w��:�~S
��[�5h��G���Qv������w��j�~�}h��G��=����V
q��) ��v�������S
�[�5h���lw
z|��}�~~�|c
�N�n��������\��NdZcY_T\S@%�K���𭁨covszy�������~jboa>j��P��Н��������M�n�t��������v�s��5w��wT�G��Z Y�b'\��wL�G����!
y_k*�3�b$
\�|wT�G ���b%[��Z���G� 
Xw�d�x_l*���{����������|��{�ٝ�e
czo�r|u��s�ut�wT
\����Z����� g��j��[j[�o�:�����:� �W��g
����
mg�gm�������r������w���[�[��������[�~�I��ؓ������D�"D{xS�z�x����<s�Rhw"?I����k�%�\V���~vD�|q���z��������)���!����wȨ�s��������nhFy~h������0�������Vv�]������������
̌Ω��T�R���2����y�w�$�˲���ş���1"����Y�f3�������
Z�xw
�����#��P'Z�xw
�|�c!���,
Z�tw
�*8
���
,
{
����������]�������6{&
>z
��#��l�\ls�
��B
�L�P-
���W
s�
��w���c!�r�����A�q`��Ԏ���xI
s�
��w���8
��
�˞A�l`��Ԏ���xއ�uA�Dxp6�s�v��
��z�����Y
�����x�I
��m�[m�����������X�qA�L4_�XEm?�R���&��&�K����
�����K[����v�v�Iw��w����3�~_
���:
~�L�iL
�g�tu�vT
0���'0� �$
[
�fwt
�b8
����)[
��
��p
�i�a�a
�D���:q2r�p��1L
��\u�vT
[
��{����ʚ
���p
�i�a�4
3q9^
�R0
�\0
�c�v�v�qw����o�Y�Y`�a�a���a�b�Y�Xo��Y�Y�X�Ypn�W�W�Pvͯ����wt
�o���X�Z�A.�p%9�WW��er�n�z���x���x]��R�#��j��z��I�&��i�[�E��ZC �VN�M�8�7!�z��$�P|
�����יqA��J��N������G����،��|xъ�m ���O1��"���җ�ڌ�
��:s��{l���W
|
�����c!���t2n
��w���b8
���2�}����
����z��˙�.���|�PO?�G
��
���Z�J��
��r
���?~K^��P$
�[���%�����4wm
���f�!�.;�皗ڒ���xԄ�D�;�
�J��������&�t��w�����E�\�x��%�����⻳_-�8��inĥ�e��}�`#8�FTz������������|�rqxwnu�Y����!��F��x������,�C�H��:L��Js�f�O�nw��9�0��4�gDc{�o������������7
ˉ
���m�F'O�nw��<�4�ODc{�o��������7
�f�鱾����F�'�(�������o�tx���.*O�jw�
��%�*)
O��
���������Et���\w�vHmM_��z��)
O����F�D���:�؀����(�>��<��k��J����+w�ͭ����d�=��ܘ��@����$܀@
�)�\HW�����@������WC{O
��
����@�9��g
����
nggn���ʁ�[�p���0s���J��m���q��R���&��VjY_T���
�����|��"Xmjb�sn�I5C^Me������������͉W$��5JAm1a�K���~�}R]dTz¥ʤ����W�D�r��e�����Ӑ7c�Vv�a���%n���1���
�Ϫ��T�R��͕¸��c�ym��_��s������3�N��8�8�0�Jډe.�������
��O��
���w��h�&|=^�d'����nw��F�(
��d$
����jw��e�(
���d%��X�������w����(
��y��K
�
�:'
�3��'�
�:'
���$
�
�6'
k��%�
�����ߖ�#
r��k
�[k
q
�vw�������ÿ���=�/�#HeX�s�L�����`i�a�8����$e�2��^�
.��j�,Yb�W�P�au���m�n�
K�m�̾Z�T�O�v�)���s
����~
�[?q|����XF
����������y��~��~�٘�e
^�z�uwr��t�fv�tT
q
�nw���/������4�
FOX�v�7��;��;�t��
�W
q
�nw�����{��!e��%-��9!�������0�
;SX���A���$
q
�jwL������:�
DIX�x�:�s��%q
�L��¿���
HeX�t�L�i��������~qѰqRzc|zq��QP�vT
q
����y���w��/������=�
HFX�t�5��y��0
�[0
�c�v�w��
��U���w�v�'��|����������l
�Ll
0v�2��w����G��5���`�OK�#HeXgp��w��@��}�����,���f�N��dR��nk�h�9��6�B�a��lƃݶ�¿ʫ�~t�v���.���
"
W�9'v�E��!��-%
��$w���
�%�
�@%
���R'���U���I���%
�����0
�Z0
�
�š�qw���@xA
�(�F$
��sw��u���[y���C{���'_u�������kvm�J`�lI��.��Z
�
���P'������@lA
�����K
�
�`'
�������]���@�/���/,�P�s��4UZ��3z
�
���w������Q�x����ؿ�>V�{]
�@<V_ڿ��WwtP�����l�������m�k������{�������,�������m�Z����~u*}���g���������)���!��df��n��nw���J�$���d���)�����g���������<sR�^{[���gw��'��V}\7�#[��=�4��"��TY���q�f�����s�3�~��L��k���Ϊ��nv`PK���2�����6�
�b@j^lt�TJ�n�Cm�HP�>�N�Տ����X�̩����<R��q?=���L�������ܼ�P[p�[}�}v���fw��}�|�~��������i��#���2�5�:�=��ɾ����19��u�r_�OU
t|�a��1
����v�0w����w��w���v���������������������޵��ff
��v%��������|������|��{|�X�skRZ�
{v���1
���
���i����h
���E~<KL^����o�5wb�[o�5wb{
�w��tw�����?���T1
⋩���twj
zl!�V�8��1
�Q���������
c�8�y0P���������}�u~j�ge�k���C��ړ���������������~{�u������l�W�&l�oY��	�
���+��;I�
���1
s����w��,n�������nN�\`SKf�ȁs����
��
��6
s������[i����U�^�yS`]U������а�mggnne^x��s�$v�9w��r{ladm�eŴ��š{�l�h{nsy�������s������������l�wRzc|yq��C^�vT
s���?�����,"��(��,"�
�������
�|��|������%Q
k�q����������d
����E<
�|������Ts�v�-v�v�w����x<
�~���\
�E��w���8�%H�|H�Zv�����EH
��xH
�v��8v�����xF�3
�|�x�������Q�trj|`c�~���\
�v���Mw������)���#��잲a�������������������t�yr}��vy�u�c�’���������u�sqvurq�~�q�u�lT_�s�x��y~�uytlh��������������]�+a`�d�*���Wv�8�~v�P�8w�TO�O�w�T����v
������]bRc�m�r�������bČ�x_jiZ�������g�����Ư��m�~����x�_jiZ���ij؀�v�nnvrm�c�R�]v
�����c~umv؀�`�PUU�Pg`��v�u�c��mv��w���@�O������O�@?PO@������p��p�p��e��
r�
��e��
q�
��e�Mq�
��ov�ͧ������w��Է�ԩ� �w���t�'�)k���y�W>��(�(`);Ԍ�����Ȓ(Y]�%NP�����=�(�����`�)�'`(<��=��(�(`(<��������Ȓ'Z]�%OM���������ɒ'Z]�%NN��sy
�wB������������sy
Ƭ�g�Q�^�-�7Q~zaUj^tkyq��R�z�<���}�V�F]�Y�F���W��l��&�/����:���*��&������w�_VYYQ���������������������������Q,��&�Id�#��h�oM�|c��m�n�m�|c�����v���;��������	���?�W���������lf����3���������[��W���w��^�����e�|�)����,����r�[��W�w�>�W������������$�4��.���
w��wm���_�p_��c�p����p�������6��d���O���q�H��������	�������e��O�oH�|�p�X�{������������j`�f�1�΋����������zx�t��r������N�J� B6�(�Fk��QxtZ���6��b���P���t�a�n����P��Mj}i�|�k�Q���������]s}|~��v�|x�qa�x�j�Dk��ixgY��-�G剉����xy�y��z�������!}��V��^��n��9c�	�
�����ߑ�
�����{6?Oilu����������07Ceny~�����)4<R_x�������(4GRt{��������(/6>S_ex�����������&5DMTZ_hkx�����������������	#,28=AE��Z�v\������������5�}�j{fj�fx���_�}�k���� 
w�d�2
�kR
s,�ǹ��1�/T|���SP�r=z{M�!�
"
.o
D�ws2�$�F—�3x`
�<w��#
&=(�>�<��WC���/
��$���{�ft`G2I:��E��#:r��{l{��]�����������������~�vo�5ub���;i�$�
�i�O������2zщ�lc����8a
�D����S�e�x����}��
Tt�������|~o�u��A�����CNO
�c%����x{^Ia�L������	��ʒ��xφ�y��؉��u�?x���Y��Cx
���������������b}���|�r?
|`c��u�oo�S�*�-�V�S�)�-�S�S�)�/�S�V�*�/�S��5��#�7�4��"�6������Q�trjn����WNjE
���J
w������Y
���xI
o�v������uU��¡���t�xwtspo���T~l�p]�e�q�k�ַ���.�"�D�]Peh]Z���]z\|^~z���SL��Pzt\��B�����җ�ڑF3
އ�uA�Dp6������|���wx�~k�p6
�[6
�������Ev�*
@
�)��{2���7K4i'HB����������m�9ID8�~������j����upm�1��H1����T��\�_������D��DHmM_}
10S�(�_�V�'5�iRUcR;�X��x��z�|s�s��
�������R�H@Z}�}
�g�Q�]�,�8QA�s`��Ԏ����ç����6t�B1xjT\���}������T��VzV}Vo������{^r�p���B�v��ʆ�{<�8�8�7���9u~xz[Ȉ��)�ݼ�V�j��������͵��n�Z���f��������T�_�yT_^T�j�ƞ1�~�4��"�2�6��"�4����
��y�={o�v��A�����x�ssxxss�x���{�����>�z�}��w��|t�i�&���2�w���h
��D���j�W�}{�>�
�������Zj�_pxzqq�z���ƈ�jXr~bO�vr?��v�wtxK�����n
��w��������ƝgN������#{������p�mm��������F�#��t
�eO>�`����������f���]���]��f���O����pC�j7��N�[�����<�vw��F�s��v�;w��4v��w����mggm�{l��{i)x��n�7��T�Aw�8rk�PK
!<I����K�K:chrome/pdfjs/content/web/standard_fonts/FoxitSerifBold.pfbChromSerifOTF-Bold&������<���|�
g��D�/CREuro                                          Chrom Serif OTF BoldChrom Serif OTF{OTbmx��������8ESZks~����������!38AKp�������� )07;HSay�������������	-5AGM^oy������������ +6AHMQT^hr|�����������:
 
m��U�1<
q)Jx�����m�����g�|br+�m�����r�"���]��S��Ml�R��"
21
-���Q:)
�
�|�|'i���8
?�l��ѐ����{}tpig|����^�������]i����T��xxrreT�����P"N
�).
g�t
����na
na4
9d������Ti����^.�f.g�o������no��J��(
3�1�����Z�''Z13�Ћம�6FF�6hh����H�]��o��a���������=
T�c���dϻ����\
��w'
��;
�$
*
�����*s�txh_P,u��NLG�������;
$
e�E
�%_��������h���`���K���1���K�����F
�ge�t@����pS:Rox*�f8.�6������L�&&L6.���ﵵ�'YY�'aa�����{�=�8s�F�3]g�"`�c#�������Rq�N]�5�?9�L�sH~�(��_��wd�L�
�?�i��{�x���©��
��E�����i�����t�_}�O��4�����\�M>YVex���������qhTV|l�T
�ZwI��w�
M
��+i����k����������fs{f{����/x1h��x����&��h����Iz��kfQ
��r
�Z
�h��y�?fe��E
�*_]�����������;�c/e3i~������͖Oz��ph��E���E��'�1�'�U�U�5�a�a�)�������wn�]Sngc�\[gc^\�df������e�?�?�� � ���c������q�r\bc]v�B�5�w
�w
��M��E�u�~fr��vp���g�m������p�ggoogg��pag�t
����poa
oa��4����Z�*�0�Z�Z�)��w�A�v���-�b]�s{��x�54���b��WOC����]����������`�HI�0�p������&��W��i���\�l���y~���_�8�x�������Ż���[�QQZ[P.
�6�0�1���8t
����oa
����v�Dw���
��������Al���!�_�v��v����Si��֋��������@����.v��w�loITvi�U�ZÓNj��Ԑ(7S��9��)��y���'v��w������������h��+Hu��s�gcv�Mw��w�h	7|B gd�f���j�������}�sr���x���{���������������������������������������������������������e~�����o�Auiwvptykc���m�Kx�t�a����>QYt��L��Q��\��(Eg��	�	�
J
�
�L�#���
P
u
� }�Px�F�=_e������6��Iv����P��d��#��c���l��%t��!p�� ������� y � � �!0!Q!m!�"("2"W"�"�"�##`#�$$$/$A$|$�$�%x&&&"&6&`&h&o&|&�&�'5'W'j'�'�((5(�(�(�(�)):)�*�*�*�*�+(+F+�,C,�,�,�,�--)-H-m-�..Q.�.�.�.�//8/�/�/�00;0x0�0�0�11F1�2q2�33O3a3�44!494d4�4�5595R5`5p5�5�5�5�5�66�7787[7�7�7�7�8�9/9?9�:�!�!��~�=��w�>��\�d�\[fc^�7���ǖ��ݟ���i�XWjgKT�p�8�_�O�3/�=v��w�����(n
t���C��n
�t���C�v�f�J� �E�fw�����f�!]�f���f�̌�5��+���G��fI[�f�"��fI\�f�
�K�k�+(�J�����+�%i�+=v��w��������p�j�xQ.Ip��s��n��ψ(��x��&�,����k�{�"�d��!o�h�+��T=7�&T�	�
�P�h��T�^�g�l��;����=��ɻ�����}����x�����w���V��	�T���8A�"��A�v�N{�>�Ѭ���$���$|�o�\��b������~������w�uw{r\l��a�Ж��䜦������fi����f[__kd�+�j̡���a��4%�,�+�Ik�L���P���ũ�t��wpkxl-H�Ϸ������?�YT��s�Yξ����ĭAYR`{p���=v��w\���(��Ӧ���r�ccsf`t���C����� *�Y����˶���Tr�a���9��t�|���B���!p¤�a����o�i�|�o�P�N����KU8S��v�v�v�v��w�]�����Uo�x�}�jv�di�v�������{�~��������������z���}�vv��������VN�y�x�y��������������������y�jc�mdk}z{u{��������������q�qssxml�|�s�z�ug��o�}���s��X~l�fh������������������syx�z��~{�{njzg��q��q������������>�v�t�sw���?��v�n�v�v�r�r�qK�q�n�!�3�
�w
�4�w���=
]gmWL�{���pS7Pox��?���?������!~�
�b��v�Kw�w��x���x�Z;~�����2��2�J�r�yދ�λ�Q�+�y<��CW{�#�2��]�$�� �9�H����"&�#�����"w�N�(��
�(�����z���q�������_r�M�e����v������4��giGo��!�!�����ר�,��M�!P�'Rw�߰ɑ��'o�
�
��6�A}�Y�(�m������-���q�a�1Wn[mt��rp�qn�`kmtjL�n�H�
�� �`�<�������C�+�
>1Ln������Ԭ_P09r[|�v�$���v�w���#��$���$�%�$�L�E11���;2�"���c�������N�(�S���	�����ӆ׆�m�t�YN9Iuilm��mx�lk�h`xkoR�l�J���P�^�H�M�0���������~������w��1��,�j�*���m�"�T�J���R�H��>��@��F[y��s��L������������Л?�$<�LNM|�Y��Ì��v�ov��'���8�4_�r�ࡽ���O~�����
���9������:���������ՖDXOystl�)�Hv8d���Q��#���K�5��ܩ��W�,�4T6.8�N�a�y��^�`9q�02Jt��Ԡ����v��������,��1�~����"�T�J����1�He�>U�@�eЮ����X�Q�	X'w������p�{F{�$ڐ��ɕB�Yt�c�{��~�:�/�8�=��c�\[gf^���
�\[gi^��3v��,��9�=V���2�u3�]gmW<ҋ�词���S:Rox��
�\[gh^�
�k���s��8�S�8�M����s>���G�����������������
����s����s?�7�K�7�P~�=����i�=p�)�s�{�⚐޾������3�7�l�8m%bh�a������������������ٓ/aQ{ezjqYme�D�F�4\�f������e�][fc^��m���f�����#���%���� ���
�Ѽ��u`��%}NE_io���#K~��O����B�(��R�_�A�)�r��
�]���ѧ��~�pPUu0�A�
�%�(�x��*�L��(�	�3Y�=`������D�c�}��[�J�6>��r��Ȑ����K��_e��:
� 
�m��U��1����������4�0�9��@���̈�|�#N~<�ay�����c������ܡYE%ub����	���g���"�7���������0�`�S��S�qNk�S��x�}
�D�E�+��%�,.88m=�(d�F��=��1�&��	4���j����|�l\�N�f�.�0�_�O��8�}������h��߷�6���B��������8�L�.0�B����N�mfwnL����m��U0x�
����������6�6����<
{)Jx���|�m�����q�|Xr+�c�����|�"���]��h����I�mMl�R�g������v�v���6�-�������E�c���}��B9����r;}|F��m�����@�V#���]��h����I�xMl�R��x�����Pv�`��w��E���0��0��Ї�}K8`{m3�Q����@��.�%��	4���j������l[�J�l�(�6�Y�B��E�������ݣ�ɒ�����v�ں�����6�����V�f���p���pNf�V�h��V�h���d̸������h����I�Y���Y͹������h����J~
Pk�V�
\
'
+�����c�#���ĉ�G�^l�SJo{�������������Y�unauTz���*����^Ы�ď����N�	���V�h���N���J�m�Y�{1�h��w�}�|����<�@������h���jUU�m�d�jᭃ���h���~I�YNk�T������`B��bϪ�����h��{I�bNa�_�
��������J����/ؙ�~�8�y���d�����������xIn�O�h��I�u}��KᫍÏ����\�l�\�l��h҆�}W�[Q~|=�
֎v�v��v�	��C�|r�7
Q��E��H
�Hx
�{<�!^
�!^��ng�v�î����6�:�@���F�x���N�:�@��#�=�J�;��h����b�m1d�^�����������ݯo��^n�
���D�R�Z
������h�������-m/�U��+���B�n�5� ^��{��3�5[N��y�Gf�Gx
�|e�|�֠v��h���6�K�?�����������۴j&�Wu��������I�u���N��c���a�w��}�X������<�h�E���hć��`�b:w�G�0x�����
�|���G��
wu��sؔpn�nF����av�=����0���A�s����Ї�4���]��]���4�я��7s�B�k
c
��Å��7���M�P��&��-��͢�C
sN��K���D�$�����㪉͎���֎v�'���y��x�աØ�’��ihĈ��d�'��!��w�����ʏ���h�����O���v�}v�P���|��J�h��<�h��\�䗰������Jh����b�
����􃙗��������h�����u�5�	����������������h�����[�N��?����h�U��� �V��F�{�po�W�h��o�{�V�J���2�r������h����lp����2��<������Ə���h���r�l�Z���9�gbU[�r��N���6��i���Bf
�zn�8��B�
9
����)�~�;Z�����Ɩ��ʶ�~��
v�!v�Zw�V���N��D��6��!`�~��~`��P��j�wYI��v�w�����������F�;��@Y����
�R��&
}�ZP
��&����f���2&1
#���[:�m��Z����ՏUXSD�&0}��v�D��_w��7�'|�~����^����9���F��Fgeyz���Vi����\|��{��������P��1hXOT~���_}Ά����0�psqnR=M�� ���������m؏7�D-�"b�Y����ͳ̹0}��=���(�7���Jɜ������R������ki‡��V�A�si�O-(B�B�&�-�֧���؈��fP];������áN�I�
�8�������}�]��f���{�B�B�+�΋����6l��ؙ���J�z�����4��������{|u{p�w�i������@�N�I��*R_��Vt�i��b�:�-����l�����<�	�	���i�z �cS�gqcy S�d��������Z�y@��q������싋��Ӣ����m���z �����cg�g�I7;C�Z�p�`AdLW�|�{z �}�A��|gD��cz.�ˡ����t����ʐ��͋3^R�ELP���0��_w���,�
�A
���v
W`uS[���Ti����\��Zz�g�
]
��/��/�&��-
�`�i������i�`^jga��_�5��/�,�'���?����T~
a�ic�n�������������y�bawloA�o�K��(��_�^�,`�i������h�`^kga0���?w�wъ
��
[����������w�{o�}i�~�������V����������������jj����w�)�.�C�Oi��\��~s�gc����w�����d����ڍ
�~��N�Lv�w��S
Λ���d����������‹Xk�|t�l[�i�~�e������������Wl�|s�lZ�i���Y�������<�TQ^oP^�|r�0�7_Os}�����W��Zu�j�0�Lv�w���ʠ����Y��ToO
����v
�6`Os}�Ƴ���^��Zz�g�
e
��'�%�'��'�@�|x���ȜMd
9F;����'��p
�o
�p��|
w���(!�(��
��Tuȋz��X
z���Ui���|e�u�
0�Lv�S��%���&�6�
����zQVIo�ޑ�޹�ccB��h���g�x����|=R�gl�W�*6��
�?�B屮����/^y�T��_�����%Ev�v�w������N����ZԷ����y{��~��������|�G�J^Olv��Qs����Y��Yu�k���}�v��v���w�� �v�~���������������{������R�R�O�M������Ĵ`D����%k���y����~��zu�q/FP3A�f�n�l�v[msgY���e������=w������v�zln�c������-�=rG*^_V^p���v��-��ж�0}�.w̅�q
�%�v�M���d��}��4�������M8�m3�l��������H֒v��v�@w���x}���������/���������i����kuX�p?�y�a���������~i�����v����iG�?Y�i͠�������ri�����J�v�?���j��=�z�[���������6�l�~k�u�i���~������(�n���������<i����v}��A,|�SϜ�������i�����q��O��?��_��a�#��w��~yVu�+
����
e�k�b���'����"������
��~�(�������x�^I�?�
�}��:�r���?�z�*��읜���GѤ�ܚ��=w��GIwl4x�?�)v�@v�Zw��>��>��F��4���7�ٙ��?͟��4�w���G�
=���}�]F�GD�q�y�*yzcD�?Erd:|��A�4��b�A�������~|�x��v�ɬ���g�htrfcnh��f�dc�jHg_au��Jv���=�=)���5]�d������g�[\eg[��7�D�W�bu sqNK�h������s�u��������v�'v��v�"��w��!���u�w�������ۜ�m��S��������������y�txopOkp��u�z}�����ɋ˴p�g���c_��xw�x��9�;��B�l}�l�#v���$��Mm�����[��q�Tƭ�����Ցt`�{o?l��X�ƊŇ�����h�?�������i=�7��r��������.�-\�
<e�e�e;J9��]�Q�\�|��q&�FpĈ�ҩ���c���q�~`Uzf��������z|��8�[���1����ʻ��λWLJ[RHJ[��9����x��|�������3��3�������}�x���W�55�oe�^bc�xo5�WY�3xq|c``�e�o33��������M����߮K�h�����������������������ag����d!���U�Q���������g���t�o���(Y��vR�Y�d9f�J��?�v�]v��w��>��5��I�Z��H������-w�jw��V�\�������$����8����������}���������|q�l������g���?HY8Q�d�_���b�*��=�k�$�M�|beqlSQ����������������u�_\|^yF�j��������a�R��A�W��������3���d��pr}jbkl��oH�u�������?
�x�����v�����J���q�0�Z�[�)�1U
��9�8����@<XtdcO.j����Ҧ\a������{��pq�W�;R�6��I����������������6v�����6����C�J{��r��������zz����0�Q�C6bgeb��������w��������kmm�1t7sM�Z�y����������|{uv����˝���
�o����u�
���}�����w���{�X��
���}���u
��{>�v�s��T�D����G�s�����x�W�R~�Q�v����>������q�4�Z�[�)�-U
��9�8��"���J�@_��\�������:��5�ܖ��;������V�M�Uy����e��_�f��\�����?�<���wP]|`\��������������&��j����i��ĸ��ĸYRR^YRR^��b;�L������L�;;LL;>��Q�;�1w���>�������1�o�o�1I�1�oI�o�o�������������L�������{by��F/�%��ZD<<"#��v���D�Z���~@
p�V�`��J�
�V��������q�y�s�qt0�Mv�/w��,�������w��~������d��~�������������|�v�������}wj~xol�����	V�b�j�d�pt]�k��������������� �Tv�ٱu��������i��������zᗤ������ g�N�3�D�&�!���
��b��Yv�nw�]��3`+O`�����K�Rz���������w��������K�����F
�e�t@���!�����������痬���j/>�[__���*;�J������N�55NL8�
�%��%�
���������L
�Xme5 n���������L
�v�v����v�v��w��*���S�t7#P8s
�����9i
��>��Dv������w���~�i
��>��v������qby��F?����͋��ZD<<"#�v��{��X�Z����@i
���Z@
o�[���J��1t5&R5s
�]����:�)p�=i�������>YjVhXjWh@�3�7���8��x�XwbWp�������~fY��=��ϡ�������z��'�7]�d������f�[]ej[K��w��v$�w�� K��w���v"�&� K��w�N�v)���& T
��ـ��@��E������~��|��D
�Tkw��wИtu�iP�7�� K��N��ދ��N��n�fn��� T
�_w���������	������O�]�}�������qjjqrjjr������ ����h���������D�4���b�Y�f����u�R�`d}uA�h����gt~`Dkl\a^b�!O����}���V�(���jo����	�������D�I1���]�k����l�����xh��g������5������Yv�3���}
�W����7{f{������ܼd�X~������������m�.8@m=�*^�E��=��1�&��	4���j{l��a��pk�h�f�$�0�_�D��0�fzh<����������rsU�!��S&
U���v"��!Q
��r
�@�v)���&!���������Қ�6,��/���!��/��*�e85�Z�S&
5�S(\
��w'
��S)\
Қ��b�6b�����4��h�K
�Md�U����@��������6���B��������g��i0�
��0������.�L�.0�L���N�g0\�fwxL֎v�'����D�r7
�7�hE0�����~��|�B
tv��r�tq�jPQ�|wW�Hx
�{X�!^
�!^��n��!&
Q�|w��E��0
���&(QW�Ix
�zX� ^
�"^��n�N�!)Q��n�\�h��y�>f�Ix
�ze��E
�+_� ^
�"�U�
�:�5E������~��}��D
�Tuv��sܗtp�iPQ���E����0
�N�pm
�em
>�v�nw���\�T�V�T�V���T�Q�T�U[��Q�R�Q�R[^�S�T�Vv¬��wZ
�A���k��z��a�)��f�U�9��YL.�VL�H�
�
�!�L��vϊܵ�&��>� ʴhU����e���8�S�@L4��y�?Fa��rk
��wc
��!
� �&
k
��wc
��v"�A��!
k
��wc
�p�v)���!
k
Қ�3d�������!
�^�*�g8�N��w��i���Lf
��d�2�����{�=�'8s�F���X(g�v�v�A�}����h�A�=�9�w�6�&�6�)�@�?�@�����d���`������Pݯm����^p�
R`���d����F�x������P�<��>��%�`�`�y��I�ܞ�Е�`���h������7�F1p�R�0�n�(v�	�������"�&���]������.����1�*U;KZ2z��h�S�6[QWh��}�}�n�nn��y>����������ru���}�������~�������.g�I���֑����m�bU�Wr�^���P
R���*
������;q��&
�P
�v�MwR�7��"��	7�;�P
�{wR���)�#���������6�P
��n�R���E������}��|��D
�Tqs��sΘqr�qP�}��#�����������;
�$
�P
�R��?
���#���������6�v���T��[�8������&i�8��9���
�}�1��7;��������qkkqqkkq���}�L�T���Qk���)������
m����7�φ��α�v�rwi\B����������A���9Kespq�vf�)'GRG_�w���������������������ۉWn;�ER5o#F�g��Ƽ��h�lny�x^����˧ž��ˋ�Ͽ�g2j�_�Yv�cˆ��N��D+O�����I�T|��ӏDz��y�psqnR=N� ����ꚟ�m�3�DJ-�N�Y��'��Lx�[��y
�+��&
L��	`��#�
�+v��(I׃��
M
��[��$�
4
9���)I��
!���M
}�[��y
+����@>
��[z�g����)
�
�|�|>
��[z�g����V�"�������k,
]
��Њ
g��V��)���D/F�T3
��v���g�u�i��I
�W��l�fle
�nw]��'��@��x��ȜZd
,F;����<�U�sk�j�4"�������&�6N�.;��Z�1]U�Q�[�]r���x�l�	P�l�ñf�\�T0���n��S
ܠA
����v
6`Os}�����^��Zz�g���E������~��}�B
m�
mPe
ט]�@��x��Ȝ_d
'F;����'u��p
�o
�p������VI�d�p��l|��e�
�u�{e
�v�M2
�'��(e
�v�D2
��`
e
��n�]�%
���E������~�B
l�
nPe
���'0��0�'�%
��	->l�'�*��@��,'��e�-��Z��q������]��l
�hl
Dv٪��v�
��w��'�%�'�1�'��A�#���������խ�xb��!���!����z��������i�U��]O&�ng�d�4��>�I�^Է�"����r�gg��;Fdw���0}�.w���S
�I�
�(
[�p&
0}�.w���S
q
'l���|8
�,�p(0}�.w���S
/���p)0}�z��&��)��/����m�fm�a�#��w�v�Mw����d�+�+���~@r/
r������tM�6
��(|
�_w��6�(�
�oj��{z��X
|����Vi����\�M�
�a�#����V����~@r+
آ�^-]
Њ
I
�����`8��Y����?�jǪ�ю���h���~I��:ZNܼ%bm�S�
����wΊ
��
d��������*;�k�
�h2CY���x�����p�xv����s���>���/��l�h��R!44����	��g�5�Q0�/�>��F3����j�s�e��������C�B.���]�07y��y��W��,�y���^�ç���B��gp[�3G,z+G����}�m�O��6���%����~�P�����ďN&��P��z��l���Խ�w�jnniL���̍����d��(N]ovp�{j�8�5���
��!⮪��@��K|FW^n��g�>�����f+0x��vv�M��s�
|����G��
�}��{�qh�ZF�Y��5
��}�v�Y����x�~����������������������8�~t�����ľ`D���%u���y�{��w�yv�u/HP3�.���/mvgY�s�ox��5
�N��h�L�6y��i���B�K�����b
��d�*��B�-�
��w���9
���f5
�����
o�a�b��{�'����"������
��~�%�/��5
�v�����m�������~�*1�ets��������p�pvj~^j�_�T��#�%���Ɠ��������̞�~��ywv�o�x�����`�}�*�1s�TA��������`
�������R
Q�(��R����?�7@��G!�R�����_�l�|S7@0Ƨ��������f�n������o�hgoog����9�	�	�	�9�:�	�	�	�8�ǔ
$Ż���\�Q�QY[PH�k�q������q�kkppk��A�(w���1�[_x^^F�}��ɦ��u�}u~}tcr�����������n��{���@�������{~������B
tt��u�hl�z/x/h�����M~����%#
�1�"
�I��I�������I��I�|�|��
�
��G�L���1[
�MԹ����]�o�h
��
{
�G
��3{
�2�uG
���5�Bw��G�L��G�L�e�1W
�h
��W
�h
�
h���_
�?��u_
�?�3h���2V
�?��V
�?�v����Iw�V�F������.��������M���%�������}������|�fqy}oz�v�m�������𙭞���_�~a}jqg���b�x�rem�x�{��p|�kf|rro�n�������������&Sr������������`cUck�o������c���g������\���������o�v���g������u�jjuokc�U�`g
�ivmox�g�][[�]\g�x�m�ig
���ov��w���<�N������N�<?KL>��~�=�=�8�=�8�=p��J
��J
��J
��}��v�ᣊv�v�ȣ�w��
��
��
����80�p0������0k�Gv�΂
��u�
���~���u
�����y~���_�8�΂
�&�
���������Y
��{�����~m�%�b�t�v�Pw��K�PS�M�P֋�>���v�4���;�����*��1������w�dQLxQ�,������������������������� �����J,���&�IK�#��u��M4|c��}�|}|�|�{;�����~v��s��2����������V�W���������nh���3���������c��X���j��\�����[�n� ����'����h�c��W�j�V�W�������������
�+���"���j���k���Y��\�>�e���e����0�����\�������u���~v��s�n]�j
�e������VI�W!���ෛx���s\�����Ȍ��:�}�l��8I�z
0�v�5��Y��w�S
�u���x�k]�j
Y������x\m�GN���Rr�+�z
�X�?�Ƈ�ݮ��{�~y}x�|��)x��a��_�����9c�	�
������z-nt���������@Xfns�����!,8FO_���������	 (;GLX`ox���������'06<G[fqx�������������	,9>BNZ`fq|���������������	������u�k�Kpjy�M�h�خ[���~�����o����Å��7���M�0P��6��6��͢�C
}N��K~��D�$������͎����"#
�������l,
D&�@�yx��ȜPd
6F;���'��p
�o
�p��$����4��hK
Md�U�k%V��1��w�ve#�����/
������M6
�y�x�qt,��[z�g����D/rOi����������^O�X}k�]�����)�H
�Dx
�<�%^
�^��n|�ll������w]%
�
-
�0-�(�?�A��ī��R
R�(��S<�w:�c��������H�Q��������Ӕ��kh܅�tO�É��?�gr���F��dIo�O�c��D�J�ι���������gW�f)��s������v�b�����֢�����=����_�i�[������ՅUXS����do�+L%��n�B]
��Њ
��
,��r�c������n�havcr�fr�c������m�hawcr�{�r;[jzkv|��z�{x�qv�x}wU䊔�	��߸���Y��ToO
D
T“[ᇗ�箔`��E�f�)��8Jv�������q|_
�?�E�h��y�Df�3
\�d������e�\[ge^����I~
Y
��{�����}m�%�b�8�������V��|f}zj�j
Zv���Tk��������v�v�����D�����6��Z�Z�*�1�[��9���1�1��uCnWL�z�[
�MԹ����]�o�4j��p����9�6(�7;m^j�_�8��b�:\��Vm�J�W��N��v�aw�f�n�5CmWL�{�N
�%~�gg���[���3�ַ�.��}��=��S�����b
]b�\rqtpp�t�����wu����ħ���K�PSi���x����o�u������u�oouuog��ong��Ӧ���r�ccsn`��-����J�
�kw�6�,�����>��@�n����w��|�<�T�LG��&4�f����~�B�BR_��Wv�g��
�w
�0�Lv�T�
v���Rv�`��w��E�dm}tf���v�B�B�j�����ø�v�wv��w��3K�[����mcx��|t\�e������e����4�b�:Ğ�������Gw�S��Ri��\�w�����t��x�ov������tme3 nb��]�
�=��=��
O�]�>�v��w��U�5��n�������PK
!<��@�P�P@chrome/pdfjs/content/web/standard_fonts/FoxitSerifBoldItalic.pfbChromSerifOTF-BoldItalic)�����}�\���n���M�Jd/JYEuro                                          Chrom Serif OTF Bold ItalicChrom Serif OTFq0CPZam������
29Jov������&5GY_lu~���������.6>DKRlz������������
#049IYhqx~��������������� *.7@IMSW]bf�m����6mmf�����%�!
\y�������?
\i�^�,
�X�#�x�i�(��t��o�vuvtpq�'��y�0{
�&D
��zJo
'
ɋ`k].��8
�x����U
'C�Q����c�=�:�!�	����ܶ��tvko2�il�y?au�����╭���/���mi`S�����ꭞv��+
�n�y{W�}���տ�v��Z�'j�*�޾޷��&�ppysvv�z��@���C
��g��#G{-�c�p�e�q%
t=��_s�`�յ�پ���p�/k�'x�f�U�Q�p����,��g��HZ��m�ff���Vr��F�b�c~fyp�j����=�����d�	�m�jkslr��+��r����?�-��(�L� v�-��FP^�U��;���E���V�B��]�x���5N
lSUs�2
�߮�bx��ti���˥����������(<A����R��h�q�a������l�e^vase�n������m�eenmeb�E��}�!t2�m
�j������jB�hB���^{Rv�`��	�k�wp
�������J
Q�e!nd��#e�2��<�.��6�BS��l�!r����b�*}q��W{�����"��V�/�X�O`Tp<�!�J�U��V
�GwN�X����u���Vs��F�a�)x����$�*��a
��dh~�Xv�F�������B���������w�w�g[
��,;X��wwR��o�_����{iu{�W4��&L&0�Y��G�H�Q��P����T�aGbbdq�~;���k�e�Wh�����_�y��
���v�p~�ys~yp��d����n~���b
���h�����T��x�i�(��twM�05���^�f�,}�v����������#v��������%���T�Q�V�v�w���Q������J���ٺ��u�}N�A�m
����i(0�Y�n�a��_w�w������������eE�b`;���v������5�����~m{m
���h�@�i�y������w�
��
�)�"v���a��/�����!�v�v��������Nj�nv���v�Swu~��3�����h	7|B gd�f���j�������}�sr���x���{���������������������������������������������������������e~�����o�Auiwvptykc���m�^�����I%Q����D�G�.�:����	W

;
�Wv�Z�

t
���l~�A��Y�Md����m�8p���r���E��d��-�1S��j�y1� 0 _!(!B!�"M"�"�#U#i#�#�#�$2$D$�%% %W%�%�%�&.&y&�'4'Q'c'o'�'�( (�)])�)�**6*X*�*�*�+)+�+�+�+�+�+�,%,�,�,�-$-Y-�..�.�.�//E/g/�0�111$1<1_1n1�1�1�212�2�2�2�2�33G3�3�3�44"4M44�4�55P66�7/7�888e8�8�9
959I9p9�9�:	::,:Y::�:�;;[;�<�<�=4=g=|=�=�>0>�>�?�@G�0�0��r�$��$��w�w�(��;�h���9�/���������q�kPk8a�z���w$'�/hd�l������j�`jdp\ �7v��w��"��N������~�fephe����j��N������{�kephe��v�f�L��L�fw�u�f+����.%����f�;�f��f�;�f�wL0�K�.�wL*6�fD�f�6�f�����.�<v���w��%���b�b�Jǵ�"���.�W�h�� �r�������������wC���i�sa�v�����3�P�y�F�F2�"P�)o&W�*���i����|EhH[h�q�dM�����h�h��?}��v��ȥ�w�� ��� �����};
�	��;
o�����O��H��y�E�Nvʯ���t����
��u�?���O�s��e�?��Ĵ�q�O@pu�r\R��s�
������������hh����jhaakcn�\��������]�.�J+�j�k�p�M�u�$?��0�#x��ْ��o����tlz`0g��ìƩ?���!�C�Y��������Ȓ�ô�[kfxY/[��7v��w�!��;�"�N�������ddqid�v�j�
���>�G��p�_�&���&���y����Ll�j��Q�/�R�
�E�_�3����e߂�WL����"��+�^��0�G�	��v����w�i�����Tnx|w|zv{wekrr�����ǎ�������{�c�r�W�f��������oq��l�p��������������������ult�}�x�x�u`���������������l{[\�^�p�n�v{���������}c_^��l��px��p���h�v�x�p�~so{qlrr��������������/�v�p�qw���;��p�l�p�p�t�t�qK�q�l�0�5b
m�O�
�b�p�bnlam�}�О~��uS(Wx���:���:�������0~�(��(��<
����y��SE��S}����
���c0����8�$���~�K�2�G����b�L��բ�����kZG2�~���%�-�����w���S`��������:��I{R}�'u�t��������t���������aL�^����w�:���p��SrMisy:�)�X�;������Q�"�I"Po�������6x>Y?REZP���K�K~��w������4������f'.X1Qhv��w�x{�tzc�V��J��0���j�_��񾜷��:�J#LFKc�~�����ɢec�	�%c_��v�*�Gw�P��
��y�*b�*���*���F��GI�<�@o"����=���_~�c�����������*�8%@R6=m��{x�}z�hol{_m�]�)�%�<�"� �$��������|�����w���4��{�-����(�����[�+���J?�� �2����0�K�Jnm��s+��ǡؠ�ޫ���ʋXw?Y���\yrslS����v����h�$��1�4�R�Ȯ�������~��
����g���K��Yz�^�k���I�	���P�p���تͶ����U�89C>�C�h�s��1�qOLeJG9y׷꾽Ų��[�a�������eEKeehf�v��w�>��E��������������\n��.c%7k|�l|Y���K�����
���*�W��A4�/�(�G榛���U�	!,�	k��~�&�G�'��(T�(��C�b`lkc���C�b`ljc��6v��w�[�'�mL�(r�3�����y�x�z�{��������z<��D7|����������kb`jlb_m��/�v��w����}����U�8�U����}S/����g�����Z�ZY���Z��Z/�v��w���u��^�}G��U�8�UG~�(���Xl��(����;�k���ƚ�����Ƽ��e�)�TMGg�m����������������������{IUqY<.N?�Y�j3�.<
�>y���f�\w�w�������
���������u`�"@E_jo���"L~�O�
��@�'��P�\�@�(�p���Z���ѧ��~�pPWv0�?��%�$�w��(�K��&��4Y�
>l����������D�e�}��[�K�5?����s�ǺÛ�Q@�(��_v�������?
[i�^,
h��t�M����Ʃ����>�-%�,�'�s���'�:��1�8�X���͛���
��E�P��h���c��vzMo�h��������v�_r��et����������u������џUU�8��;�y������.���-/BKq<}���G�E�5��I2�~�~���~u���|�pe�T���D�y�r�I�"D����ؾNj������'�R���n��,,%`����a}�����w
���s]
w�r�
�7u=k�j�Q
p7U}�������~
��oL~�}�{*
��v����~����]q�����������lN~�}�|L
7_u�"�s������I�6G����R��h����z�
�����gl�c��y�����ӯ��0�7������e������h`���t}`~_j���?�g�Y�4��;&����ql���wo~���|�sh�G���4���\�E�9�9��������I�Г���������������su^�n�����������L���kyz?�h�Ѯi�d���������y�ɤ���a
���{t�����R�[����[�Ǩ�Í�
���{t�������y�bj�f����v��ku`�n��������!�w����Vņ�u�z��������w�ap�`�E�?��w]�����w����|s�
�"�-�k]as���������������n�hhoo`N�d������-ОĚ��������v�1wv���dp��������y����}�t�t��Q�h���q�t�}��1���l�P��������gh����ur�x�DdT���q�ŧ������h���|t|yKp�h�SU~���������r�Ǩ�Î�
���{u|{Rv�`��w��qv�`w��w�n�c�Py���������p�����ʍ���G�{PoxS�h�̮Uz���������Q�ɤ�����h��qo
U�q�qh����l��9c��Z��p�d�Qy�������� `Y
�������rko�j�qd��W�m!��U~�y���M
�?�*��-�L�!v�/��FP\�U��;���C���V�B���o�]�}��X�v�֫����g�&����o#TU�#�������Zx���������M�'�c��9� �!��h����y�
���[zMjjc��d�]�Fi���M
\�?�*��-�L�#v�-��FP^���#���s�ĭ����{~�������� ��ȷv�XkQsNnl��pW�^�i�f�u����������Y���d�B��]�}��8�'�l�����������@�#��v���l�+�����z�����˩VO%;P�����fq��������t�����^�[�y�u�8��������%�D�O��h����w�������p{Rw�_�!y�����؍y�����������������0���T�Q�K�G��������S�����]mr~p��|z��tؓtp�n�8+&;�^�c�c�fKw~�&�a���@n�XX
�����Qv������������)����T��e�?���Ҵ��&��xFb�^��y����Ƀ�kk]#��8
�����U
�������hr�����;��!oe��#(s�ų����>��v�Sw�w�\�����{y������������Th����l���1o
X�����������a
lj���Q�w�v��w�=��py��{�z��z����ϫ˪�����Oh����t}�z�w�.��o
j���������������i����*�(��o
d���������������iˀ���C�X
s�j�t�W���B�O�c� `ik�g�h�Ȯ?�~�p�A�w�[�W��������_h���{{������8�:|�gڞ���Ƒ���h�����Y��OL��GPux��l�XX
��֮]�l���yj
Ĉ��x���~�/�jH�\�������������jO�byKr�Q�XX
>
��3��{�f�3�o��;v���2�$ƙ�������\��v�4���1�2��P��1��y�d��^�[�\���p�^����u�1�!Q}��f/��v�w�����������F�;��@Y����Iw����{
��'��z�ppxuvv�x��X��wt
h���.�q
��D
�zMo
�����O��3/���ߔ�9
~��
��w���Z�����Bwe�-��nw�c�;�,�>�=�[�MLdc]i2������Xz6J�q��p���n~���4j������!Qcfk^n_����m┙�~���������~Єg�<��$�<�-��`���˸~�Zv�C���wv�x����l�y{N�����ⲣu���UKv�����<�h�]���m
���s|r5u8�}��`��7�U�/�_�8��ټ��%���Ⱈ]�nG��,c����n~�!����?�P���G�h�7��U���i5
�ڞ��=
e�Wp:�h��a��ٵ�p���+�v;c�Wp�	�n|V[���������������f�~toylO�s��C��g�%����/�������������������n�v��������3�I��hB�_��Lv�3�tw���W�{�@�	�ٓ�L{HzB?�p�?����D�=�0�~������풒�������������������sg�[�(?%>T�d�xKhky^`�{���}��cVO2�^@u���Զ������*֯�hp[h�E_������wp~�
��
�����ڵ��|�}pDsAxOyNh�j�o�ݸ�˴u�w�SM�
����š��У֟�S�uYPo�&)����.@|S�>��f��MSUs���2��k4��A��Nv�����+���r#�96u|]g��������������e��srvko�Z����=���JE|{�����LA��av�Uw��wpt���P������&�е۷�u�vt`qi}�2l���΢�����ch���th�9em����[L8M��f�XlTUs������ő��%��
��l\�"�TO}�V�۷�η��v�v��`}�
�����M�.�A���8���ˎ�������x}k8r<zV}Wn�J���۹��u�s�WXv������à��ΤԮ�s�T9P7:X��������N��2J'2Q���NF{r�!�m
���n`j^�R�e�`x
�m.0mwOxPl�vj�bav������Ġ��ͣԭO
`�
~��5�R�˿�����}Y@O��+W�����+�V�4��@�)��	�e�2��@�.�YxkCD��OW����D}d�8�m
���ruJ��O��i�m�
m����㓬�a��/���~w����<f���hm�����7��'�}To
諆��K��"�`�j���ԸŹ���O�w�mm�W������gݧ�ptTJ��.b������v�bwv�
�蕻��������������������U��IP�Qh�FHzl�%�m
���rkZ�<V�H���v�Q��w���-뤨܅
�������������o�l�f�b������ƤFY����y
{������~�rJ<g2b�_�`�\�\`rxme�C|
f�4��%w�_�!y�UNr�������C��޵C��%hG!?ld|h�_�2[�*b��O�ټ�Űd
�w�,VMs����1
c�¯��D��qnxPyOxMxKhG
�n�v�ow��	�~��>�2��)��h�osmsjv�}��}��you`#��N��u�O|c�T�p���j������v�_w��w�����~��
�"����]��g�r����z����f�sjtkpw�}�}�}�}yb7*hbk��pR0]1X0��~�w�^�]�K�p���i�����v�w�w�w�A�"�
��f���߱�v�hvuuxx|���k�0���֮���������������t�jWij"A{ق�w��;sp��������H���.4U|su��������{hwls{�_����ݱy���E
��|�~}�y8j��������uz
��Rv�F�yt�������yx�u��s�v
������������~o���zz�r�Q��
�+p���
��`��i�CG�~�����/��w�G��񝦥�Ҹ�G�Ѯ����7w�l]�GzIol/x�}�y`�dN�ARK�o�#�N�;v�S����]�A��y��G�
��m���C���Ѹ�?�Ӧ���G�w����/��~�G����#�mKRN�>c`�z�|.xq^yI]�?�
k8|�/�E�Hv�v�ߏwP�A�������|{��y��x�ɬ���g�htrfcnh��g0�dc�iHg_au���Lv���$��)��$�U��L�9:�/{m�z�z|]�h�ƞ�������2��_�-c�k������k�cckkc�v�+v�O��w���j�c������hޠ����)�?]����ݘ�ȵ�p�k[dc\������������y�h���hb�������&�.�2��a��l�#�B�,v�w�w�C���k�O��0�n��k����rg{pFeJ��|���ޕ�������^�������ss������g�|������O�Xg�7}��Y"�O��V�M�\#��x}�k]]mP�^�d�ʢ���`���j�{efui��������y��v��e��w��]����ʾ��ͺULJ[THJ[��9����x��|�������3��4�������}�x���W�45�ng�_bb�xo5�WY�3xp|d``�e�o33����ٽ�¤��֤Y�l��������,���,���*����0�}�����\rǡ|y������+��t�V�T��������r�����d���}Y�"u=�!}Y�!p,bq�L��N>v�v��w�w�Ww��������C�x������G�v�w�������x����ѩ؞�����
��������ż��b�x�&�g�����ï�|���������wp�q������b�'7IU:K�V�����~m���n�+(���4�wcNO}pu_�������������f�wonvd��R�[������d�w�;���KϾ������P`ixrnz��V����������K
�hK
�y����w������X��������W�+�1�U�U�+�1�X�X�+�.�U�U�+�/�X��8��!�1�1�� �8�8��#�1�2��#�7�Y8zCpnf�H�-�!���2����������Dz��p�?/a>( �C�̺���� �$�rv�����p�T�o�{���l��������~��on����������� ?��jo
����d9,�3�w�Q�����+�����zc^�$R�x��r��mq���������A�
�_
�M�q����A�
�ܯ����y`b{l
S�v�]�H��e�C�����D�]�����y���N�=�����:��'�����X�+�1�U�U�+�-�X�X�+�2�U�U�+�/�W��8�� �2�2��$�8�8���2�1��#�8��L�:�Z�����0��@��w�n�7�	������T�M�K{����b��a��Z��
�>�=���xQVwd[����������������6v��w��<�I������K�<<KM<��Q�[�ź���\�QQ]^Q/��w�6�<�8w���>�����,�p�p�8J�8�oM�o�o�����������������^�����R
mfakh��������v���f�N���u̮iOifOi�����g��t�n��ci
�Iw�����+!5�Nv�c�Mv�w��wO�����l�|zm�k�ں�õv��q�kgy��������X�8�bB.Sn���������_��Td`�y����������z������Ҵ��Wv�ܱ��dB�,���l������������i�~��������d��0���l���0���(��(���<
��n���I��wP��I*��}
r|ln{�
�y���ʿ^�g}~���������v�v��w���
bq������!��^�f�,}�v���q|E�{np}f�s���$���������_���;ҕ��m^d�8Mo���:�R�k������?�r$4$.r��I�������~��@p
�������J
�������~���w�wp���3�p3�pH
�9�7a��*�
]~��������n�pk
��Mv�w����w�wx�
}a��$�<^�����R
cfakh�����9���
_|�������n�qk
��w���[[��w�\�N�y�!}a�p�[̮iO\lae~����g��t�n���c��i
y���+�3�p3�yH
�a�l�X���(����(x���sP|l`o�4PZ=\�*�)���ϯq�hknrix���������vjzold�������ג���O�.<
Q��w�<�g{
��&��x�0����3
Q��w��g"����3
Q�~Z�Z�$V
�MwՃ^���|螓�����������}��ޯ�bx��th|s��p�po�p(y��5�|�#h��t�MV
�Yw���
�������s��� �h+
����$Q�������������O�]�ƻ���[�PP\[^
�#��$�����s������K��������[������m�X���^�Rs��9�<V��v�J������B��r���w���L�������o�}��+Ϻ����P�X��������Ź�,�T��*�~���q�~�k�W�b����������n��ov�������.������/��������rpt}lnz��tuj���z�����X�e��������	��ӻm�.CIr=}���G�E�5��M*�������~t~w��o�sl�c���D�t�q�@�6�Q
s
�<�g'���7W{�������~
��qO}�{�yL
:`|r�'e
�s�@���C
Q
s
��g��+�����%�`��7U}�������~
��pM~�|�zL
`:|q�'�e
�s�1Q
�xZ�2�7V|��������{��nI�~�}*
Q
�����p7V|�������~
��pM~�|�z*
����Fh
s
kuJ�������A
ƛ~z�|G��D*h
s
�T�g{
������%���N{�������A
ƛy�|Gh
�xw��g����,;����N{�������F
h
���A�cA���J�������F
Nj����������'���������w��[%`���a}�����w
���n]
~�uZ�J%_��x�\g�o-m�j��Ռ�p�d�Rw��������pY
 xjo�h�od��/�c���Y���YP
����������=|s��p�qn�pT3�=��*3��&
e�j%
����[
��*~enjM
�:����P
����쀣���=|r��p�qo�pTe����$���2�$�:����.
��g-/�v�nw���_�T�T�S�T���U�R�U�VY��P�R�R�R]\�Q�Q�#v�����wM
�J��������)�I���~���������t�oy�.�"�Grt��x�#�7����������E���V�V�G��ZV/�pg�p�o�W�}��F�6�ar
s
���)Þzhn�h�pJ,o�������>���*r
s
��2�g"�J�p)Þyho�h�pJ/l�������>r
�xw����gB
��)��{kn�f�nJ0k�������>r
������)ĝxgo�j�pJ-n�������>�c�.
�i-XX
s
��֮Xp���������Ij
¦~y��~�|�/�iK�\�������jU�NyKltQ����D&
X���'����k�;�?�&�y�oo3b@&zx��w���o���L������������������ �![�壏��،���h��w|�x�u��KzMt�c��\�ʭ����B��"����~������L�=���ʢ����O�6�.C�3�#qI��m�f\��qz|���������������k�~U�\�g�d�
�
�I��+���¹��GuMy!+�j𶅪_:[m�F?zx���X��w��4
o�bbv����D
�xMo
�'
0
�t�%*[��4
n�_cw?�0
�
�%&
[��4
q
��(�0
��%B
[�v�j(
�n�^cx?�0
��:P
��������~�={q��n�rp�rT[�t
��
o(
�n�_cw?�0
��m.
:
X�f���Z�9����8[�t
��7����:Y�d�(
�dq
��(0
���ƺ����ƻ[PP[\P��P\���Ժ�k�q������q�kkqqk�~�L�Zv�
w�w�u�m��w���b�������[��:�����o�z�`CDQ�Ť��������5�����`�Shc�Va����9�(�vM����M��(�^�!&�m��߷��3�\���cװ�qfZI��+g}�������������$���o}�
�"Wo�n�n��+�w�v��4j�s���T;��}
t|lny�
�y������I�s���������˴�o��QcSk^n_����X┙�~��������~z��`�<��0�.�-#�L�/
�*����_/
����Z
h�x"
[������.�5N
h���i���/�� 
�\�
g
�eg
f�V<t�{
�_f�VlRU�
��$
��&
f�OlTUs�2
�������.�4N
f�������l�!q�QVt����$
�q�a.
�e-~��5n
�.�
�k{}Y@Q��+W��
��w���zo�s�(��.�.+�D�/��E�l�f�R���i� Rd�]�\�_p���s�h�K�l���_�VV!�v�v�bw��px
���u3�oD�vh�`cw�����h�O
p�
�+��P
��������=|t��p�qm�pT~��3���7
��*~��3n
�
��{Y@Q��6
�@�)���K�v�&
��z7
��B
�׌N�#
��#P
�����쀢��=|t��q�pm�pT����������#
��V.
�:
/n�'�(��6��('��g�(��Z��h������b��f
�df
)v���w���\�
���������Q��o
a@C���������4��@�)�b�b��fP#�yq���2��A�.M�_�t���U�������@Q��+w���d
��w,TN�
��1
y������-
��*d
��w)
�����-
h��&
d
��w)
�����-
���B
d
��,�
)
�����-
��E.
:
y��w5UKJp{~��~�||�w8o�~�����rz
[�'&
���wYtkCD��OZ����Dw~��^L��M{u�
w����Ǔ��n�a��ew��w��
���E
��}�}~�x8l�~������tz
�'�o.
�:
f�k<SV}���������M�(ޠ�*:�g�ǧ�Î�
���yt�����>��'VuE��i�{Rv�`�f�Xw�f�!q�RV�
����ő��aẞ�5\��
���lvo(q/=`xEضe�m�p}�V�۽�η����s��ĭ���s���!����ފ��H��k�}����p�rTqZ���p������D�0M����S��xm��q��ps���$������<�5�tՌ���Vp�Fg@)�Lp����������NzseK������P��ċKt�g���~�n����	p�9�n�jү����|p����9d �B\�����Ž�o�XjpeO`u��������2��
�P�TI`ioh�zp�B�I��\"�9����Ж���S����������ÌVv�VQ��(]���!y����w���܍y��������~��������/���M�M�N�O��������S������]mqo��{v��sܒur�p�:+&>�W�[�V�[Gw�!�a���@�n�������*�BS�x���v�N��w��w�@�؅
�������������n�l�f�c������ƦFY����y
z���ؐ�}�sJ>g2N�e�L�k�^ip}wf�H|
�j��`X�������t����֤dg���������g�R�������dr���|ty�tzr��NK�\~���������r�����T�O�lyKr�Q��+��FXX
�xw>
��@`
��Rv�C�wru�������xx�u��t�v
~������������~q���|z�s�L��
�.p���s��`
�0�'�)���(m��<��� �?�"�=Ui��w�|���������������~�d\�b}H�r������.����
������������������w�f�~�ˋ���\�O�Ad�T@r �Bw���[
��0~�Bw�;��`����C�6�:�W��e������0�7���
������W��7��K
������������O�\�Ż���[�QP[\^
��A�(wc���c_o^^F�}��ɦ��q��y~�tcr�����������0v�v��A�0����H����������������ٟ֫�fz��vl~t��r�tr�s03z#�����I����-!��Z
�F�O���F�U�U��F�N��F�S�S��i��5�0��)\
�{����|�z��y�w��z��i�&m��I
�bnkak�}����
��5b
�mІ�2�sI
�bnkao�|��О|��sT
i��5�0��5�0���)c
�y�z��y�z��z��c
�|�{��x�w��zi�m�m��@
mal�{�ԝ~���
��s@
mam�|�؞}��uT
�5b
�m�m�[�2�s@
laj�|�Ԝ���
���s@
lal�|�؝~��uT
�v���L��nw��0�%�����M����{ξ��������������|��������Tsz~�s��zw�n��������������]ivshl�o�q�r�s�rj�u�x�Жvy�p�b�Wr�t��������������gl8V_��
�Wv�W��Ww������J�s@;YQl�s�����������������a������q�n�lP^X������p�y��·���t�{���������X�������p�k]g_�Z��۽��u�l]p^ai�jiz�z�{�U^�Vmutnl�u���Ƹ�zXks[v�x�uhT_o`x�v�zi���bI��ijvxij�x��������Xv��w�w���<�L������J�=<NL=��~�(��*�I�*�I�*p��b�}jic��b�}jic��b�j������hS
��}��v��ƥ�w���+�t3+�i��+h�0�����HO��r��mq����B�
�_
��r�0I����������v�Mw�}�E�K�O�E�M����`��i���&�������A�=������m�Sc�YW�U�(������������������՗����Q.��"�cy�,R�wU�XI�~b��n�a�n*~b����v���<��������
���F�X�����ɼ��mg����3���������]��X���v��^�����w�|�&����+����y�]��W�v�F�X�������������1��*���w��vl���\��^�S�e�M���e����!�a��Kv�4��,m��+�/�/�m~i]y��������������s�n�g�Y`�k���՜�/�*F���t�oqe�k�̵���u��_Ht������������v��r�
�#�����������������q�o������H�G�J\��	c?!�a��Jv�4��v���+�/�&�r|Kaz��������������y�o�\�WA׃��Ѻ�7��&�'<�������h�n�׵�Ѹw�z�dQr}���������+��(�P�l�Ea��
fA�S㠫����~�����������s����m�y��T��c����b��`�����9c�	�
�����*�q)/;Xa����������(.Xbv�������
AJQ]es}��������	(2<X`hmt{�����������$27HSblpvz���������������	$)3=GQZ^gmsy��P���G�h�7��K���_5
�ڞ��=
r�Wp-�h�,!w���?� 
��
��{{Y@Q��6
�<�)���K2���k4wM
�?�,��)�L�!v�-��FP^�U��;���E���V�B��]�y��"����O��)/�����.,TM�
��1
y�L
`:|q�'�e
�s�1 W
N�|�9eP{�n���D���qQ@�JFG
 h�"
���ߔ�9
������
l t<^:s~N,c���$
�X�`u�������H� ��wZi�^W
M�}�9gPx�o�kwt
l(
���⾪n�frgUUw\+W�����+�T�4�w���b�#
5��W��e��"���aAE��4}m���d-�
���_�B�@� D�Q���r�����䕮���ָ��{vep6ch�F]b�}lic����ԛ��"�������Vp��M�k$E�F����hR�F��ͺ��$��F��H�!��wI
�bn��[�ʦ�V[
��.;k��yzMt�c���̙���5SKLpy}A
ǚ~y�}Gt�T�׵�������^��]�������H�:���X�8u
�b�p����ǽ��U�R�Z�g�n������n�ggnng����o���$��$�B:�W�s4U8!C���Ny<(�up���q����������ƫ�����e�G6`Mgy�������ƋRgj�b`licS(Xx���W����A�h�Į����������#����������eh!
��~�|��D8�;�;�H��O��hɂ��jP�������qkkqqkkq��ܯ����yaa|l
Lx���hv��w\
�{�����!��w�s��o�u������u�oouuof�n����6nmf��X
�������ntL/zn��R�����W|Y�,g�e{h�svYg:OYgp���\�PI4:g�o�`bw��y���wv����Dתì�g�yovpt�����������
������-nz���|gb<_6���|p�u�:�������k�q�{<|Y�;�m
��������`�2�su
���h�
��2v��w�~�������uuj���if���hrX�9X�SvT
��:v��w�����t��@����t�˽PK
!<���R�R<chrome/pdfjs/content/web/standard_fonts/FoxitSerifItalic.pfbChromSerifOTF-Italic*������ZO�<���q���<���F�/ETEuro                                          Chrom Serif OTF ItalicChrom Serif OTFh	7|B gd�f���j�������}�sr���x���{���������������������������������������������������������e~�����o�Auiwvptykc���m�J����s���I`m��p�*z��}��Fq��	,	�
.
�
�.��n�
0
�:�b�L�M��u�OY����
&c���n��3�����*��S��V���u��6�� �!!�!�"�"�#L#�$$%%%*%b%�%�%�&&�&�&�'!'s'�'�((�(�)1)e)�)�)�*)*�+/+�+�+�,,3,@,^,t,�,�-�-�-�-�-�..a.�/////\/�0C0�1	1151]1t1�2b2�2�3,3P3s3�3�3�3�4O4�4�4�4�5535d5�5�5�6$6I6�6�7>7T7�7�8�989}9�:f:x:�;L;g;�;�;�<<A<^<~<�<�<�<�==`=�=�>u?N?z?�?�@@'@<@�A`AmB6C  s���w����I���2����������n�|�qwmd�o�v�i�5:� n��
tsn�Nv��w�$��2���9�������vwq�
�G�������zrv�
�v�_�d��-�c��hw�d����_�B@�_��_�Ζ�&�J���+�hb;�h�A�hf;�h"�c�I�J+�d����J�BG�JGv�v�v���w�������8a�d���Ū�������]�mH���l�R����e�{u0����^�V����^�*����}�v)����`�P�x���izLD�+X�D�M�\F��*��̂�{��p�
��
��
�����	0
���0
o�T�3��H�H\�9y�[����b���.��~�]��m��Z��������{vyib`��f�Ҩ����������Mx��{paqj_V`�k��ǩ����a�P0E;�~���N�R�U���u�۳���x�a]R}hHL��	�6Ȍ�E���Qo�������”�ě��S8\nOe�#�Nv��w����9�������uwq~r��k���Ts�4v��w���!�I��L�	��S��!�|��~��J�/=�0�9K��*�4s�6v��w�m���9�|`�(���:�Q�p�C�	x��+��z�u�GC�J )�7�	fv�v�w�w�������R�s�q�s�l~o|lr�y��������~���������~�~�e��l������b�L�y�v�v�������Ü����z�xo{oopppfq�������������z�zyx{pv�z�j�x�sjo�x�|��f��kt�w}^���|�����xpyq�t�V}f�_��t�������������Ҡv����sw���S����y������}��}�sa�s�y �v�zw��
��z�
�^au�������zfTfw~s�T���}��T�n���m �������
<�v�@ww�wy�*�@_�+�@����������0���)�%m�C5��kT��<�#� �<��|�Z�p�/���:�^���v�8w�������^l��������*�������������m�\|��������v��}����v^~Ry]�����������z�gzqpI�H��T�T������S�!r���0U�������عOMj�Q� �)�f�m��e����������[��� ����f� �-iiov��vt�vv�tf�ov^�}�4�M�"��F�q�����C�AR@_Or���������sH��_'}�v�<�Qw��r�{������(����L��]�<���:���,0��Qb�5�M����������;�r��-4B�)ow��x�yz�uxvno�o�W��.��1�����z����#�x������w�����[�J����J�D����M���b�8�V�>�=�"&�Glm�~o*�������׵Q91U�@�)����v����\ڃ���,���:���������h�������ϳ�-�x���u��s��?X�%m���G�����c�@��������I-4Z$W�\�J�{��'�nJ+=dU5V���¥���,ዪ�α���ܥH[8PmTq�v����������y�.��B���(;�$�	�����>෷�����Q��T�_���/�%��dp{�rd`oSzl'���s���|�����ཷ�
���
s���)�������z��
vl|as��������|fTfw~���l�r������r�llrrl�Ҙv�v��w�3�Y��������N�o�N�o���������c������������������Ҙv��w߃��N�o�N�o�����y���W�*��[�D����ֵ������T�+DIdDn�w����������������̋�ԒPrDb^[XWUQ4A�H��n�t�訢��بt�n�nttn��y��}�������
��
�������ӻ���ua*�@�ahp���!S��Q��
��;�#��K�X�:�$�i���V���٫���qRMq3�A���!�q���,�J�)��5[�9_������F�\�~��\�M�<E�ρ�Ŋ����Z
X�O���<X�
]~�������p���t�cqwW{���Q��Ȁ*��q���YnpTeE
����r��������Q�!�W�|�������E�V����ٶ��+�#��{߁QR|��G]gsg�|�J����7���:�^=(R<�!nm������3뢃���ְgG� �
{�
��y��
�� �e�
~t��mАup�l�
�\�&E��&��o
��������
��M�5�z��{�~EY}��5Mzpuc�_�0��0����IJ
Yy��������������l�
��؋
tb�����������y�{�7�}c@
���5��7����>�og|�y�vc
œpuw�t�t��:Gyd{p}
���
�d�����I������i�ѕOv�t�}���~y�,]z��-��5䣋���(�~L}�}�}����-��{›umx�t�v��*u:��J�
�������\����qi������{v�nttnr�X�>k��8��]�j�����Y|�}�}wd|p��g�pk�g�j�S�Y�y`��k���顳���E˜��������v�����w���������������hm���������~��P�vKyUwh�
dk���������<������
��slx�v�wb�.����.������
��tlx�v�v��:Mzguis�
������Y}���������즢���
��vrv�r�t �(px|\�y�l�"��w������Ývmx�t�u��[w��iij��������p�u�qnztlN�p������٢�������ʠv���w��_����R~��������o����Z�p�lss{yS{���]t��o�B����v�������r{���zr?KG[�$&����1ࣥ���
à{pz�v�s��/8t�uRn
XY��������
�O͝����
ǜyn{�w�u��NMzjl�p�
y��a�I� ��y�
U}�������������������EW}�W2�
Mz���������Oʜ�����;��}�T�}�I{žyv��l�c!��jk}j�ʑv�v�2w��w�Z�xw�
T{�����������x����$���
�����
{Úwmy|^�g3����b���5{��xf���5�fuy_�
�
�s�R�X�t�S���]�<$��G���r�q�]����Z�G0�0��_����}
�ů����������Y}���������K������\��+�;�_��{��ypv�o�o"�Ky~XK�����
����aA�(p:pr��x��J�s�/j�����
\�.�|�������~����������(���|�bcOh&ur��o<Z�S�U����
��O
�f�q�5�D�|*6��DX������Z�E0�+��_���)�}
�����w�~���[x��������ĕ����(�Wt��~3�~��#��$�@�g��{��{ry�t�t��0Ly�bO�����y�������t5@f=�$p���sy�v�rv���w0�
����������m
H��[tyx�ts{��r��{w�pd
�[�v�������Iw���������|���<�}�|����7��a�.���Ž����|Iy`[Q�
w��
�[����{gY(��|
�=��)���������
{��z_y=���l&oV�	�K@���ꚸ������
���v�3w����y����������N{��uyin[}s�b���N��������������{��u���p�v�3w���hy��r�Z���Z������������M{��suy�z���X��\�$�����������{Ǘt^��P�>�����a��������������{��_i����
n�@n�j�au��������+�@�G�����syvQ�
`g��r%���M�h�������d{��}x~�{~|�%�;P�'������������{¢v?��w�2�L}|zzSSbrz�[�
�
Xm���������a�o��������
��}r�����zd��^Ss�j�������������
���R���R�^IxUyg�[���ٯP
�
����-�P��]kh����4�:���������A<�v� v�KwT��k�(��A��f�
�����p�A�Y���Q�p����`��5�:y�}ye�.w�v�I�A`�����#���#����I�O���@�Y����s��v�@w�����
��"�
y�V
�z�
������Xԋ���a
p�������8
E�zO��m�_
6
��n[)/�b!pj����������Ow���~�����ʕUl�7,�g�\��������~v{�u�s����u�l��-�;�A�2�Z�E1P8Wj������D����W�X�Y� ����r���cg\bONm�����
��������{�
��-�!��r���֫��ov����w������i���bt�)�S,N�Ŝ�Y��h�ttxv�r��̺ζ�[awtw������
�>��B��X�X�X�z��|ry�t�re�)������S8��>�H�+�W�H޽Ý��‸����x���Z�������nv����ba�u�R�����l�X��!�2\
s
aW�ϟ����<�c��Ы��������8�.c�q�K��������������q�}h�c��e�v��	�5������������O����������������p������{�C�T�C6q/�R��Ӡ����̢�+�j����7TjuRT�S������d�_�O�E��������������՟�������N|`�P9�Q�F�s�{it`nlx��������c������z^YZ^1|*�݊���Ǥ��r�η�����nd8Y+I^z���
��w��p�����¹��������z~9��q�p�x�Ͷɿ�~�[eslw����8�����]�u1%�.Yl���
�K��WV�W�{���O�zQ�m�
����ְ��r�J
�����d2yk�{|vo@K
A��n��
vsn<�c�t�$���m���$nj��a3��>xr#X��������������x�w�wf�h�Z�|����4��/���d+yh�Oo�t������t�ootto�v�v�$w�w��Sp����H���m�3�t�P��t����ӱ|�Yn�yuqx��?C�����8��K{���xl�+T`����Y��W6|x{���q|s:�g<�v�5w�w���x�
|���������+�׆�W=|q�{���qv�!��v�
��v�v��`�����ͳ��������pxF��q*����ʻ��������|w=��p�}�h�ý�Ŵ|�\eqkx��������t�gI:3�E�����ޙ�v�e7$�/Zm���Z��V�V�U�{��uu`u=��{I�
���p�
��
�Q
{��v|pvJ�tl����.��
��8�L��-��G��j��&�&�3�'�m�
�Y���]W >�a�Yk���Hv�gv�-�H
s����������/u��{���yv)��p�k���
au�є���Ӕ��Pv�Z��
�����*�U{���n\����0��B}_���{�S��5�R�G�R�������;��^}lyTd�����O誨v^)��i"hv����v�Mw�����������������������������r�tL9�Fp�w����[���[/w|z�������zss�m�r����v�C��w�Թ�l�
��������������p�n�k�i������̨IZ���
}������~}�s!};le�`�a�a�bj�
�
� ��w����j�	krh\w��������t���9������������ohXSkzkzu�m�E���{xN|m������ؾ��kv�X�
����w�M
r
���������B
�|���������Y7|{P]
`�
�v�_w��<��c��Ƀ�2i��������r}~������4��Jnz�~�P��������)�F��s�jw{}|s��������uY;�RP�ʎv�^w�w�
�8�I���R����y�������)sw}Ĝ�0��T�?�/}��j�����@��ڣ�������{��}���������/�W��{�fn�yzz��������yW>*3�k��܍����������S����!wp�n�b�D��V��}������}�mpwfvw������i����
����������������u�xW\N9W���y|�ukIs�p�|�������sl�������1qiz����������uy{s�x�������������Mv�w��
�~]�~~�s��
�L�
��/z
��������z�
p
v��������t�
�Ov�U��
��qt�x��y��
����������~{��t��u�_P�k�����
������
�ւ��G�:k�v��г�a��i�x���Ϝ������<�����/P|�iZ�>yKw^Ey�}�xm�RI�IZT�p�9�'v�?�
��<��l��b�
���B�Ї�:���ɚ����>�ˠ�ѝ`�������I��l�-����s]�cI�abc�{����GztdxC^�<1sVik�L�O�\v�ˏw��bh�K�������}{��y��w�ɬ���g�htrfcil��lh�gf�_Hg_au��Lv���l��l�8n�s������s�nnssn��Q�2X�
_��x�}|u�v������������5���v�<������#�
����ۥ�{�diWaWp����������}����������
�t�>��ma�P���2�p��c���q�~����;�{�ө��a������4��c��vIt_yY�w������Nֳ���咍��xpff�hVU��j�쑤��������ɣ��������~l�m��}������k�P�>V�vB���a�a���l�gqsi���������|����������
��������LCASL@?R��G����s���������3��4���������s���`�24�q_�``_tq4�^`�2sq__`�`�q34�v�J���w����Sj��������������H���H�ݑ��>���+�2�t������
��|t�����wd5�\Sr�a�������������
���G��o� c�7��t;�8c�9s9HwPzj9��
�6����e�]�@���^�)��x�����8|���Iİȵū�����������Ϲ���p�}���*|����ʺ����|{��������{�y�q������\�8ABa7^�`�X���vp��UWZ=`�V�V�4�Z�voZfcGgk�������������s�uR�Q�\�Q��ھ߾v�a���n�������J��I�nqX_xuR ��s�����
���#
�'y������������������Q�)�,�T�R�'�,�Q�Q�'�.�R�T�)�.�Q��3��"�5�2��!�4�3�� �1�4�� �3�GB^xfZI&b��댵��ئ]a������{��qr�Y�*I�9� �S��������������:�*���w��&���z�p�~}��{������p��oq~����������:��V�d���z�e5'�3y�VѸ��ʷ����Ҙ��d�Z�W�J�w���
��lh
�x�u��������������ܙ��i
�6�h
�v�s��������������٘��i
��v������m�����b��������'y���|�5�5�G�>�>�;�BQ�q���_�6���N�.��t����t�;��l��xRVxeZ�����Q�)�.�T�R�'�/�Q�t�Q�'�+�R���T�)�,�Q��t�3�� �5�2���4�3��#�1���4��"�3��G�4�t�Y�����t�.ʤG���t�x�m�q�R��l������T�P�C|����_��`��Z�s����������������n��d�� ��Ź��Ź\QQ]TQQ]��d;�J������L�;;LN;�ҋ�i�T��<w���S�������������{�?��?�z��z�<`�<�{R����������
w~`)�
�Fy6�u
R���d�$���Ok����n�T��}��y7GNwu|��������p�l��
{���j�
fZrg{�
s��v�>w�H�
�3�
����}�v�v}�
�Pv�[�wm����O�����̹������w�h�u��������vq{�|g������3G��rzSTXOw�����36�
�kh|j}lsfZr�v�˒��������:�Wv����l�������������������z��\�DL��z� �[�����
s�m��Iw���L!����������tok|hxw��|zn~�����Ģ˵g�d�������R����v��w������I�h~�������������������.x�nx��������������L�cs;��O{\����]�����)�������ؤYc@4"#Ck��ԁl�t�ϴ�����pH[�Q�
����sI
�6I
�
���w��Shg
��
����1�
��)�I�qt�����������������
�������r}�2���xqi����av����w����$����v���
���8�
�~`��
�Fy6�u
�<���H�rn����]��-������
������~��/���xri��
��O�n�n�xg
�
����1�
���O��~2GNwuz������}h��
w�t�
fZrg{�
�����,�U����s&T_`h&9jlEA�V��ͲҨ|�s�m�m���������\J��B�Ƥֺ������Ɠ��d�n��
ssn�
X�
_}������
�p�����fttS'
�N�
��'�
y�V
��
�
��V
�����
dx��������
�����axqO'
Z
�uwX���F
�O���
cy��������
�����gqpV5
�h|�v��L
Z
߬
�me
s��q�sr�ql
���m�
dx��������
�����aprW'
Z
�����
X�
ew��������
����`rrU'
���#
Z
��:�
�9X�O�(�i�9�
�:���T�^�� ¸���^�T�T^^T��
�@�
����
ew��������
����`rrU'
�����]���
���p�
nj����譺�R]�4c{T�{���8y��BIr�!me������6���롄]{�z�{���|y�.`|��-�r������N�v*����-��|����r}zx|y��wqkzws����������/����Xv�]��
�!����������tng}gzy�
�����ģʵg�d����������o
��ظ
v��p�sn�j�
�O�:��
�sw�$��6��
hm����������<
��/t
�P�v*A
�B�D�
��"�
w�V
�z�
�
�sw�ڠ��V�
�8�
��G
|��
�y��4
�������������
�$��6���F
�6��4
�
�����
�2
��/t
�P�v*A
����#
s�
�*
���Dw
s�
�f�V�
�!�
��G
y��
���*
s�
��
��E�F
����*
s�
����
�*
�%��#
����������X���
�M�5�z��{ĝvmz�y�x]�81a�]�8&o�^H�����-���-��9����̲
Uz��������ʑv�v��/�,w��V�wbw�
V|���������&���x����,���ۜ�×Z�
b{��tmx�v�w)��Z�b���5b{��xf���Wv?xmT����]`
����Z����q
wt��qT�sq�q�
�
�f�
�w$
��qw
�
�f�
�3�V^
���$
�
����l����=�
��F
�6�$
�
������w�
��m`
���������C
xt��r�sp�pl
����$
�
���������b���w$
�@��X
�%
�Ҩv�v�Vw�w��&����p�W�SX�W�U���W�V�\�Z�n��[�Z�X�Xpn�V�X�7v���è
�R�r��������<���{���������o�9b�����Deq��z�&�)��~�����
���]�e�S���_W=�jg�f�G�x�n�q!�K�h�
w��w�w�
��)
���
 �"ܢyu�V
�z�
�
w��w�
�3�V^
�R�t)
�
��
�
��F
��)
�
����
��H�
��:
��
�7
��
����!
�[�
�
Yi�������������/�o���������
���gb��,^Ss�l��������������
���L���Q�^IxWyf���F�
�?��
��G
���
}
�>����m�9�f�����U~���������������-���+�;�_S�ɔ�����؛��{���~f}�m�t��3yK{X��k�W�
����`A�0r8op��u�c�s�ۡ�%���b����,vu7L}�������������t���lty�je�u����D����>����D���p[M{?Kkt�z��|���|�����������@a�I:~���������������qsw{mX�{��(�K��^�<���ɡ���d�(�"H� �tX�
3
�(
���x
�
����
$��"
�(
v
���=w��|�CU
}��"
�(
v
ڬ
����L��e
t��qj�sq�ql
��"
�(
v
������
~3
�(
�T#
v
������:���8�
�:v@3
�(
��vT�_��@¸���^�T�T_^T���
���
�ʀ��
�w���S�N����� ������I�M������HHX�vB�֨������
�� ���P�udbvde����D�x`����[��+�j�\�P����Ӽ�Mg��o陭�T' �t)Xi���������ͻ��|kVY@�o�m�ܭî���������(���������~rni}gyx�
�����Ţ˴i�e������ѣ�{�cg\bONi�����
�����������
�"�-�!�
�jDž‸������sw����Z��-
�h��x
‸�
��zw��{����
�:�
��G
~��
���U��=
зp�X[
T
‸�󢕢���̋w��{��'U
�A�c��-
‸�
�{������
��Z���-
��#
�
�7�
�r�,
���
��#�
y�V
�{�
�
�7�
��
�:�
��G
}��
H��,
�
�4�
�U
���,
�
��������r�,
�I�ZX
�] 
���F��nw�������
�[���_W <�c�Ym�������}j�f��(�5�'8�N��-��C�\�g�Y��k�(`d�[�c�hz�}�k�l�U�n�	��^�Z�E�
ڬ
���j�
�
�Q
{��v|vvK�tl���
���W
wr��pj�tr�r�
�
��&
�����
��&�
{�V
�~�
�
���
�3�
��G
w�
���&
���������̋w�
�9U
����&
���!��
�����=��`
���������C
wt��q֕sq�ql
�"�&
���%���������>����&
�=X
�%
�Ҁ���S������#���z��������/Ԏ
��N�
���(������ �x������b��5�(�R��g�qX|d���G`yK����P�a��T��o�D��iT� �;�$�4�%S�L�w�
��w;
����
��%�
z�V
�}�
�
����~
;�8;
��ov�O�=�
���9����9
��FS
�.
p�
��ov�������
���wS
�+
|]
b
�(��#
�b���w��w��
�|_�~y={�[�
�O�
��2z
��������y�
pp
s��������w�
o�:~
�Lv�kv�-���wH
w�����Ύ����X�X�Y�z��}uz�v�uj�	��dv5k���
tf����哯�Mv�w���#�
T���j�?�}^�~y�=|[���
�C�
��&z
���������}�
tp
u��������u�
�
�yX
�%
�
�X�
�r�?
d�8{e�>
n
T^������"ޙ��#6��1����Ȕ�
ßxn{�w�vO�j(P~[��\�>zMy~Z��
�Jw���t�
~����������콗�*Y����WW�W�{���q�o�n�Cg]ӯb�(k���
�߃�s����
x�������t���zy�7d{��9�w��������i8���*�{q��o��nm�s�^�S�^����'������t��������:{�I[VB�7Fq���������R��z^�q���f��r��mrK!��� ��E����BʵlXz~b�p���v�wv�A���X���b��p���ϱ���_RclYyT�Ϡ�����������������Ia\�@<��s�2��)�7�8��c�ծ����\���W�Òan�fZ���F�֥���������ptz�\T`jrs�Duy��v���w����l�
���������m
���[tx�z�xxu��z��y}�^d
l����R
��v�C��pw�ԋ�(%���
�������������m�m�l�l�������̨QR��
{���~��~�v!};lc�]�`�c�ek�
�e��R
�[�v��
���
��
Q�l��������a�o�����������Q�{����s�����|d��^Ss�n���������������{�����R��K�^xIg}\��?���!
�[���ٯ�pwP
��FR
�Tv�P�}w����
��yw�t��r��
����������~{��x��qW�z�I������
����8�R
�J��������������g���~�9O�~�������������|�s�d�Zm�i���r����������4������������������r�}������Y�m�P� � w�s��v�
�����#�3�=Zs��v�
�
���m��{
f�"/�es����w�	��	�a�������m��k)((��s����c��c��X
s���i�9�
�9�/�h�;����9j����/��T�_��¹���]�TD�T_^T�������mgglng���
s�=��9ww�ųnqZDbY�v��貴�u�ovCrt}�����ť�s����
����W
xu��r�rp�p�
s���>��
�6�
��G
z��
��-^
�Y�i���Y���������Y�e���Y�
���
s�
�?�K���y
��������~�~���
s�
�D�K��4�H�
Ȝ
�i�\�av�������yfThw~s��z�
���k
t��������{�
�[�
�:�K���K���y
����~�~�䀔����
�ey
����}�~�؀�����
�[�
�F�
�
�4�HY
u����ؗ���z�
�nzY
w����Ԙ���x�
�[��z�
�
��k
u��������z�
�ek
s���������{�
�>��ow����1�3���f�������������������������������|�lr~��z�zu�_�Ý���������w�vtrwgz��~���|x�o�f`u��y�z~�rl|wwv�v�����������EsUec�� vVa�x�Wv�>�tv�v�X�>w��9���0y{[LJ[y�x����۫����¸o������x�xfgqP�����������г�ô���������ɱq������w�yf[o\���̻�}�xlvs;�k�k�mT^�gvx|v�w�{�����{cxafr�y�yxFc(Sbϗx�|tz�z�{Me�h�ww|vx�z�������hv�v��w�w�������XG�T������U�GGVVH�����5�;�,��,�I��ݲ�
���
��o�u������t�oovuo��
��
u�
��
��B�	0
�'�0
��j
��/
n�S�3����Has����z��lh
�c�l������i
s�
�r�Z�s��wlzD
��'�N�R�v�Bw����6�B\���e��k��N�������A�:����߿z�Sc&dW�X��1�����������������՗�!���R.��"�cy�,R�DS�XI(~e��n�o�nJ~e�����v���;��������	���?�W���������lf����3���������[��W���w��^�����e�|�)����,����r�[��W�w�>�W������������$�4��.���
w��wm���_�p_����p������c�s��v�,��{���� ��uS��t�U�p"Tw��������x��������pp~h|z�^�	�4�����/���������n S�Rfl�w����Ƶ{�x{aYx������Β�������������{�{u�o�*������������������}p�}������U�]�7X�1+o4�`��Sv�)��|��!��l�@ҝ��5����}���}�����������g�$��k��y_�id�NN~p*Wu��������������|�wk�lw~�[����G����:z5L�gSj�x������{�ptgay����0��������������������������}��{�{z�}�.J��k0y��?��h����d��l��9c�	�
�������14ag�����',NSYkr�����!*7Wcn��������7Sbq~����,2?DLQUajq��������
-@S^dl�������&6FUdmv{�����������
"-6HZ_gou����������������"&-3:AFKYgu������������					(	4	:	>	B	M	U	`	k	o	v	}	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�	�




%
+
1
7
;
A
F
J
N
Rp�u������u�ppuup 
%
a
q������8
G�{R��j�_
!
�R�T�
��O
�n�q����^�H�.+��_�����\ 
8�N��-��I��l��&�(�5�'ߊ��v���}�Z<�~�Ym��5
�h|E
6
��|M.,�g!pj��:
�
7
�
���X�|����������ᐠΔ�
���voy�v�v��s6�wJ�r
p�؝�B
y�������V�Y�Y�?
�d,yq>
=
�p�X[
T
+
]
�M�X�:�'�Z��Lֵ�eoNV�O>j{��j
/
�9
N
il����������<
��"
2
~��.t
�/�}O~�}�zA
{���Q����,��q���Z5vn�2������kk��p�	{��z_y/�ւl_~X��K@���藹������_^wr|�
����鎗���#�3�=Zņ�gY'��|
�-���������ƖS
.
xb
���B�7��Z@
������d����ba�u���6����{|vo?K
J
���ֈ�{�{�|���|y��u�x�}luc
ۏ]Py�	�@zMvzZ���Ad uLZAWiL-`���Ζov��te������ɧ����������_�{��|V�~�����v��L
�T1
��|�v����	������ϥŹ�̌Qp�1�N�rr��}d������4�:�/�m�29RBhutxw{z@:}t}D
��Z�Z�s�t�wQbnw���������\y�_�÷�Ʋ�)������t9w7y|�f���������6{�(D`e�U�]�<���G�x��������<x�$Z\k�8�+�w����z]�&��Э���-�5�s��n�m����ν}�\frkw�}�������t�]8/�A\���e��U�U�U�{
g�""1�f�M
s
W_�Ș������1
�u}|wz��q
 
�
ǜ
�i�^�a}
�v��*w�!�6\
�<�#L�=���盖}���npB��_�
�;�
��G
��
`��-�O�'������{N�utz�_�|���������-��{.<H*J����'�h�lKYh9-1S�䓌���w�f
���W
w`
�����d�'�L�I����Q�H�Ea�<k"������U�yz��=����������FMoklpWentE�P�����zY
AzGe������w�.�5�~���
�"R���[����w�������Gw��&I:��
z�\[ED��T��'�\�#��o����f��w�fq�v}�C
}�_hohx�bYaiU��@ՠ���-h���������gm|�g/2����pv�A��
��"�
x�V
�z�
�
��#�
x�V
�{�
��RoBOH`�k��epaZrnci~�{��7�=&��T����v�
�8�
��G
|��
�
�:�
��G
~��
�����pfTiw~���������~cQ1qR�l�s������r�llsrlt������s�nn��A��
wZ
�xwX����������ð��m�r������r�mmrrm�N
��[�BX�������
�����x�ssxxss�x�o�u������u�oouuo�
�]v�zw�������{��T��g���������؞���dvzzva���t�}�����z�������
��w�v�!�O���r�������ov�7�
��v
�sw��x��f
�
�r�7�(���.��sw�
r~l��������`�b
V<�`Ew�
w��>�
��
����O�w���
�t�X�����r��t}z��v��̟�w������5�M�^�E��&Nj���������mggmngj{[S:{緄{o�i�ɲ�ؾ�y���[v��{n�|������������������}��{�z}r��k���T�­���v�v���
�����K��{�ڀ�v�~��������}ycA��s���{�y�������j�D�
ʜ
�iϢ����Y�[���l����
��N����{�v�w�����G��=w����LZvor�﯉w��y�E�q�Ul
<�v�������v�v�fV{�a��E����|�0y�mx��0v��w��}zn~�I�9w��v�@w����-����0��Uw���Z�gn��{����
��Q{����ħ���[��PK
!<�ygYAYA7chrome/pdfjs/content/web/standard_fonts/FoxitSymbol.pfbChromSymbolOTF&���������H�����=��>��
"&+1357>@CHKRUX[`dinuy|�������������������������� *3<JV`my��������������	!-2:CJS^fqz�����������"&+2@M[do{����������(4@N\jv�����AlphaBetaGammaEpsilonZetaEtaThetaIotaKappaLambdaMuNuXiOmicronPiRhoSigmaTauUpsilonPhiChiPsialphabetagammadeltaepsilonzetaetathetaiotakappalambdanuxiomicronpirhosigma1sigmatauupsilonphichipsiomegatheta1Upsilon1phi1omega1minutesecondEuroIfrakturweierstrassRfrakturOmegaalepharrowleftarrowuparrowrightarrowdownarrowbothcarriagereturnarrowdblleftarrowdbluparrowdblrightarrowdbldownarrowdblbothuniversalpartialdiffexistentialemptysetDeltagradientelementnotelementsuchthatproductsummationasteriskmathradicalproportionalinfinityanglelogicalandlogicalorintersectionunionintegralthereforesimilarcongruentapproxequalnotequalequivalencelessequalgreaterequalpropersubsetpropersupersetnotsubsetreflexsubsetreflexsupersetcirclepluscirclemultiplyperpendiculardotmathintegraltpintegralbtangleleftanglerightlozengespadeclubheartdiamondcopyrightserifregisterseriftrademarkserifradicalexarrowvertexarrowhorizexregistersanscopyrightsanstrademarksansparenlefttpparenleftexparenleftbtbracketlefttpbracketleftexbracketleftbtbracelefttpbraceleftmidbraceleftbtbraceexintegralexparenrighttpparenrightexparenrightbtbracketrighttpbracketrightexbracketrightbtbracerighttpbracerightmidbracerightbtapple                                          Chrom Symbol OTF	<>@\������e�1ty�c���A�;�h&\��'s��e��Sz�Vm��y���-y���	n	�	�
<
�
L����

o
���i��.�@���
��M�a�b�q�.��G��$������8�X  �!r""K"y"�"�#1#S#�#�$$N$�$�%F%k%�&&3&t&�'1'�'�'�(�))�)�*
*&*I**�++T+�+�,F,�,�,�,�,�-$-z-�-�.1.}.�.�.�/-/E/_/�/�0�0�0�1U1�2�2�2�2�3E3�3�444:4W4h4�4�4�4�4�5555C5k5�5�5�5�66!6��E�E��z���w���B�d����������������i�uui�J��"
�K�v�d��J��cw��a�{���d�o�d���d���$��J���%��c\o�c���c\n�c'�]�r�J$�]�����J�s�J�*]����|��p��w��p���y�߀�?g��M�GTf�3k\S�l����qy�c���#�@�e��%�������������������i��v��o:E�'f~����͕�~�}���~;�^��*����v�5�+� �ր��ӊƙ\`88�&l}���y�P��]����׋�8���>�v��<��۲�ج{�fadwsOVªl�ן����������gy��}qRH�
u{M�`�i��ի��M�I'MT$�V�o�[�D:_�
��[����
��|�y��������jRQ<jWm�����NSHDR���ұ���;�[�$P
�����S����`�5�:�@��/�����r��*���z�"�H�uP
�X���S�"�H�z����*���z�6��/�@�:`�5�0��������������������T����E�v�$�"���øk�v������������TQbsy��������������������������e�g_wlo�Ez��в"
�)�v�,wy��f�,_�Kz���������ԋ�������B8��������(���w�L�L��w��f�w�L�L`�w��K;
�i��	���~;����y�62yĠ���~w�dD8�~�K�����������~�Ldn�d�w�X�\���
A�"3-Z�+m���������0\7]4F@�T�f�Kz�������e��%���\�7b<.nt��v�vw�th}~nX���� ��'�6�]��͸����p�H,k�c�������լVX/@[,q�����K�v�3�\w�������3���3�3��4�\Y��[���������Kz���������3���HY�4�BTJpx��w�xw�mluzkW倽�2���-0��W����y��|���n�d�H�K}�����w����Q�A����>�J�_�f�,�
�F��3�=�"TtlR����H����������'$�]WL@H�<������K�v����C{��i�%���N�4��������m�Ky�����E�r�U��c���8�mAE]aL:`��泲��o�4Jea4=�5��:����|����Ѽ��L�5,U&:�i�@޽h�0������حUT3Vfds�K�v���������w����*�H�u�;*�
��<��9���������pT�m��c���ijayj!u��ݢ����OJv�i�x�)z�����ܲ7
�"
�)�v�;v�v������޵r�t����������������o{q{z||z�~�������������������������j�]jqugԔ��"
�v��w��z���z��E�[>
�!�����:
��:
�v��w���zQ
�D�[�D�[M��z��ë�B����t�;���í�������w��2�[2Ji�h���������������������VAKvVuQwSwH�MY�"
N
���/�i�<dy���%����ڤ�iN
�=���r�i��ir���sf�%fysd�K�����b]���M
�N��|�ҋ'�S9�m��ZRm���S�(�[������Sũ����&?iq5�S'D�R�w�0v��w���E��EM
�k��2r�ج���S�ҋė�R�D���S�k�=&r���tK�S9�e��^�%c;�SKftY��}����������T�}�������n����k����8�H������G�98HI7�ǹ��ȻZON[[ON]�������w������������������T�����,���� �^v�a�Y�Sw��c���l��y���ұ�R��W�ѧ��wr��_gi{���5���]obd\Jq����5��s�x�{�k~rcf�e������~����������v��w���f�r�r�r�r���p�q�p�qh��s�r�r�rgf�p�q�����h���:
�^�7
��"
�K�
���������
�	m���O�Sj�v�S���������x�xvr}eZ�t�茰�ק����������=��߾������{�v�s������g�k�&e�0�{���v�n��1w����M��T�|Y�����������������aY�|y���]}��z����q����g{ye`�\��������{�����������6��������]����㥲���2�2�(3
��x���҆���BN�
�
�c<a����h�	:OVK3a���;�v��w������*
X���T��j7��z�6���w��܆#�WT]xTC��������y����������>lXsrB!�3vx�����0ۡiO��ztOui;�0���k�U]���(���y%
C������������In\}x/����1�����|�C�џ���o�A����v�׵��w�����$
�{��{'
y���*
�}<
�l��l&
�z���������P�����B��I�c�c��I�B�>��C�`�`��C�>��"��8�@�A��8�"�P�J�8�=W�J��5����������xi�������������|�j�xwf�fx���|��k�orr�o�j��;
���$
�}&
�;
��B�$
�a���f���y�}�wr�Wy�ĝj�s�C�����н����Ʈ�����y���t��|yy?==C8G�q&
����(w��(��Y��k�mf��������&��6��|�u�_Q�zy����S��m������l��RtFgy�b;
�
������|�Xd���W����������t'
y���Th���v�ȕ���\�t���~���]yڅ�eI�Y\apX��v�=w���1����y�Xd���[�L�����1
�yE
� ��|�=y��yi���\dpXe��i�J����ɝ�����������͎`y���+���;�������M���[���cy[�s�h�Mhs���y��]��VyG�{jC��Cw�ςy�z������O�����B��G�c�c��G�B�>��G�`�`��G�>���C�6�;�?� �S�F�:�<V�H��v��y�����ؤ$
�����(
y���*
��o��̚���dy��|B�o9
��v������y��
�j�ܧ$
�d������j�a3
����잝���	��w�
�kn�-����0��������aw*m\}K�������]���f�He����+������C�v�����e�e��F$
�����o� ����?����?����ۧ����9
�;
����`$
�`�M��ʲ�����|y��|��~��� �{�0��������������y��d{��`���d9
ۋ��!����
�O�O�
�|�Ɲ2~���!��8�-�#�+�.�������y�b]�7�%/�%�/�5C�'�]~b2�`��!��1�=Z��%�X�������v�5w��8�8�]�F�5��z�Y{��������9�_�!�g�����vr|Vy���b_���g���B�e�ǯ���|y��~z}�|�� �?��G������������y��[{��Q���]��flQis��v����ٝ�w��4�4���$
�Z�� �� ������e:��)�M����<
��M�)���l� y�|3��!�j��Z9
Wy�i�&���p�N�I��P�ԍ��y}�vDkik�}���3K�S�|U���"B���5���;��먜h�� p@'O+��B������(8��^v�a�������a�V���2��e�yƌ����U�/��Ȣ����4��<�Q(�#X�Y�X�����������Ǽ��֦OI�\�{;ezw�twx�ts�����������:K@jK1YT��p���`v�����2����xZb+Ec�m�����|Ԉ��`�M#�����yl���LeT=�t�w�{�������,��Qx��4���a������S�+hK4�%������T�C��!�%��������{w�k��`������
�=K8x@3�X�O�n���$��2�_6Afm��w�v�|�������x��X�d��N���0�����e�Bf0(�d���q�IwcvOM�.��Ű��ئ���������y�y�g|�oZZ�ϲ������v�n\���������0�S7�g �U�i�x�Q�u�� wǻ�����Y��<3Q�5-�T�`�D��wbpvCQ��|h�rc}owc�����
������ ������#���w�sO'%|�r�k���������gAX]r�f�~;�Iv�sv�O�bv����a��
�����þƳtA���\�V�|�s���������ډ[�9@Oo3m��l�S6i9K�ީ��ɮ��RX�6z������������z�?��q��$O�a�4�5N�p���z�:�����+��I ���W��2q�b.,q�e���z��jw�����xn�~daZ�ή�Wxl{1yw�Ï��x_�[��褐�v�A�H�������/�U��������oy��Pq��l�<�j����������������~������������b�jom�{xAOHICKP�m�9ie�b�v����5z���ƞ�g��^��z��?���$�������xp�wAWbn��_�0e�Q`�:��k�ZEm�V��������/B��6�v��w�|{����D����������]X�PZ��u�z|m�txf?�,�2�(Qr��E�x�����g�R�_v�p��w���t������-�.BPVS1�C�7i�(���QSdmh�j�r�y�m�|{n`������
�C�=�=A����ղ����������k�rQ9l|qd�~�������������~�oZEOsn�n�r���������Xllbd�n���ok|hhf�f�kx��O����������!�#��*�$ ��4���ޡ�
�
��4)3h*0�^�(�x�������ī����n�l�m|swkdrc}�d������������O�z�5:���F�	ӯ��vvxJ\fu����ۗ�	��3c�X��ev�6�����'��i�oʌ�+���J�OE��-�@&�
���;m�i�t��������p��{Š�����O�"�m0�
d]��w���hv�u��;����бܳ�f��2��
�tbhxfe�}|��{�{z�{rqxhl�p�����P�4 &�����܌�����셜��������R�b�8���6;v���������2���?B�1��4�C�N���F�4��V�@�H�>`K;+8�
�
�����x����J�������ѭ��V|�(#�^��͇y�b}[]�X��ʏ���p�.��f`{orkg|bv= y��qw�!�y�������������{��f��@&�*��!�	�&�e�,��z���O�G(s�/KZ��ǂ�����n�e8i/F��6�_v�w���^�����~��[��g��:��|�F�*�Q�.Z���?h�Ac�p�R�$L����L��U�<�H(gv�ar*��@�>��{�����{��.���:���]����xo�l@N<q�\���v�$%�/��� sy�%4{�Z�����޾ۨ�CF���cv�lw�����p�]�]��ͣ�ʪ�������ª�����ui������}�?Wmc��oL�oc�W����~����"i�uy�����T����X�a�L�s���z��kw��9��8����\��a���	��*6�#�>z�t��&F��%9Ak⤎�������bbRMH�B�~s�n3A=|�������:2��&�������Wy���C���H咭�P����������������xw���OxBN��I�7�(�Q�]�A��f�k�:Hm`U�
�V�
I7�~���&Gq���ۢ���b�\SLBEz�X�c��'�������
��spL�v���Jf������+$
��jڑ�������}uerm�k�������m�V�X�**v�1}��'~���������Y^�.��k��g(
;�_v�h��!����'����h�h����'�'I��&sH>����(f��כ����3���6�2��ʱ�������إ(2�S.0�y��o����7�@����a��^�����>�$��H���kGf%e��Ʒ���&NA6������ʻ��r�b�^=R6��,�
�����m��	#=��"0N^Ϻ��ŕѭ���ZW�?g��`�A�]�s�v��w����z������:�(';:('�;���z����q��q�p��)
��)
��)
�H�tv��w��}8
Y
�1�Z@
m�l{}������tv��w��_Y
�26
#�|8
�TY
�16
$�|8
���v�Ew��d�ES�d�E��T���R��������&����9�ְ���KW9c5�%�d�����%�����������B���Q������aO����K1�,�5�"�(f=mL��~�������~I�Vޭ�p����IJ��������������on�m��m���`�>Ͱ]�e�q�u�������B%�2��D���?����A�*���иݍ͍�d�q9#�utc�\�]�M�M\_KM[wW��W�Y\�wr-Pw��g��9��K��w�3��۸Ӭ�������gKVP@E�H�����d�j���\�J���kF�&&�T/Hq������������v�s�[�j^XC�P�A�	����Z�3�>��P!%u����������'H�7���V�X�f�m���]ecc\\e�˼�ů���v����R����$����Ѭ�������������d{��,��B��Ћ�|��Θ����*���U�$�wӞ9!�
�L�p��|��oy|i����F��J��&�8��{y�/xq�vA�D�=G�.�[�c�����xw���{������`�^�W�R����ո���)��d�%`P�5��zu�@6���������@�	�����{�,��m������ ��'e�&q{�,����6�|Zua�(�������R�?�4�3�>/�M�����"v�o��(au��|� �v�v�i��w�C�x�C�G������n�p�u�v���������|���O�]������j�k�k�l�����~����t�jz}��U�����\�vT[G�}Žb]aw]�l��y���p�eZ`d�@�]KJ]o>t�q�j�v�song�wF
��"����1�E�$��[�-�d����������h�4$EE-1;�v�"I
�����d�-�[��$�E�1�1-EE$4�hǻ�鱲F
��p��de-%[O�h������-�E�4�hh�O�%�e��;�v�"I
��y�4�E�-������h�O[%-ed��S��e�%�O����v�����w��|����%�H�-��G�o�6���62puF-�k������T�<�fk�.�s�3���䧠��f�<-TH�%r�v���w������:��9��T�������\�٬�w��!����
��a���M��=��e#����e��=��b�1�ba�
�;�v�!w�>��4��>���|���:�|��L�>����a���
��
ab�1�b�����Q����=H5�j����
��
�b�1�]j�:�=V�h�*&/�h;�v�!w�C��4���z�1�b��
��
����j�)>�LV�|;:;��|V�L)������@���x����!�a�	��?���?,�i�	������d�)�	ci�,����c�)�	da�!�A54�����545��v�]�Uw������UR9�U�6�UR���8����Qw�������������Hn�P�
;'���5��V�����0H�Y�YWX{~m�Y������6�T��h+wz�`�@�ٱ�'�Ȫbt��������;���Y�W�YT�"����T�����"� s�������l��B�V�U�@<�@� �)�@<H��Wf�s�O��.�$�4�?��m]�����j��S��i��_�8�?�_�e�Q���g�34�K7�/�_�<�?�U��;�[D���w��������D����n�h�[�h��v�B�����x��u����b���G����-��-���`��c�)��)����������*B�
����'�����9?���fv��-��-��w���j�r������������s��|�!�l��N�-�G��]�Z4�)�
.�8�W�Oe�lґ����2�/.�;n�p������z��K�J���������}��"���=�P�+�Cli��d`�������E��\T�Z$/;%rq��r� &����������=t�ǢPe���C���CEevQt�ƢPe�����Л����t��{.��EfvO�'�M����������x�������񐟤�t2hks=������������ZJ����3��ie��i��Z�}���}B
�K�/v�O����w�����u���������������������v�z�|�s�wmt�v��������������������������z�z�~s��u������h�n�o�k�t�����������ͪ����������z�xnykopz}�����������������������pp�vys�y�x�w�wq��������u�}����u��js{zuq���������p����������p{q�r�k�cle�O����e�*�Jh�����Y�1�&C�j�����F������`��D������k�}{�z]Y��P��������{����~r�c7hdN?�DQ�G3U?Q>�E��ǿ��b�s};EOVf²���Ͼ�Lw�����H���������v��0�����J�16gZVT�Ma�4B�HOAA�D���栣i�X]Z`M\a�ư��ɿ�kU���ʡ��֦QqIVmZaa��P���w��\�����b�;�v�Zw���k��k������Z;�v�qw�����T���T���T_��z��z��v�a�U
�Q�#�'��$�2��'��Q�Q�A�/�'�S�S�/�'�A�z��bwU
���A�/�%K
�/�%�A�ST�R���'�2�$�'��#�RT�- �������Xj�t��'n�]���v�1���������q������r�g�����'��ʐ.�A\��s||z�qos~`�H���o��7���8�7�R
o�ggnog�-R
m�ggpog�a��g�n������n�ggnng�_������`��_�������|���~������������j�|}ofSc_��e�cf�\@jasp��������L:
��:
0�����������{� x���������������f�{}seKrc��lf�`�^�4^]kw���������W
�{}����2
��e�cb�\@hcrm��VW
�{}�����2
��d�cc�\@hbrm�v�5��8w��r�5������Z�<�Z�0�8��T��>��MT�/����������B
�dB
�dB
=
�����v��E�W>
=
����vQ
�E�[�E�W��G
���F�)!�9�������'�(�34������)���G
����@����1��3�(T�'��8&473!X
�Zv�����w����E��������D��� ���OgD����6�+�1�?������0������h����G
���ET�!���)��F��������'�(�@>����)�!����G
������!�!�,��!���>��@�(T�'��D�:G(�X
�|���������������$���m�q��q�n��n�oU�o�m_p�5�0
���5R!
�z��������T�?�d�1�2�2�3���2�3�0�0d��0�0�1�2dd�1�1��!
��5�0
���5r���w���������T�����E�f�����o�u������t�poutp�Mv��+�����8����3��������yxs�n������h�^O��Y���:�)��w����p������y�e_xddg�h�������F����.Fh�����L
����z�l���n�S�n�TX�L
�Z�z�l�z�lXs�n�T�n�S�Q�v�}w�����h��X�
�X�O�V��t���8��8��:���|v��w����g�,��b�������������]��^���Ļ�t�a�-�o�D�D�oh-�aTtdV_�L�ȦƱ��������we��Hbj��цv�<����O���q�(p�u��������~o�k����Ŵ���g�G_^m�|�p}z���������Ͱ��K�KJKl8H�g�{�����|y�r��|d�_FbSFG�P̯���������Й�qtItvqk�v��w�
j����	������C�a]]vHrx��u||�����r]�]aCq�R�V�V�	�o��|v��w�"�^�"���|���v���v���|��ۥ�����
����-����pbYj^@M��ո����lB����x�}|�y��vk�d$61#�	�;�׷������#
�z����1��2�����Q��2����O
�R�/�.�SH
�/�/�R�,
�=��C
� �@�4�ur���
���C�~�����,Ŝ����;�O�;y���n��xqzt��H�����ϵyX�9C�P��c��v���y������ݦ������4�lx������É�n�_�������*������Í����J
��y��lw�����$��������J
y�4�lx������������������y���e��J
�K���t������;(v��I
�����S��p�O��O�p�����w����������D��T��'����O
�R��/�-�SH
�.�/�R,
�=���<�D�P��@�҂<g��o���y����s�����|�l�������M�P�>��B�����JZf�j�|��̶����������"�����RY]R5`��ݷ��γoX���D�3�N3#��*��������#
��v�����P�������������`��������������ɼ�D�����D�����������.���r�~��W*�`�b�����1����T
?
���A�����r�)!�����.7�.���e�X�W ���	���;������������<����<��>��;���;���{����>�Q��5
��6������֩@�Xn�5��-
��-L������-�@�'g��-^m�-^��'S
�e�;���5�r��6;�����-
��@�3�������3��E?
������8���`�X�W�~��r��������6�����T
?
��������W�`�]���37�3��!��r����	������;����b�������<�������<��>��;�����db��>���y���Q��.
���,t��;m�і_�-
����-���-�g����@���-�9�d�dL9�-S
�^m�����+��@����lE��v���T�q�i?�e��Ω�ʩ�nA�Rnv��v�vu�nru��v�tu�p�%+�!����7������������������������'���v�<5++�x��{��A��9c�	�
������?�:-0GOV]ag|������������
#*1@MV_fmrw|���������������	l�r������r�llrrl+
�S�.�.K
�.�.�S�S�.�.�S 
!
��<�C
�<�4
4
C
���*
/
�}'
<
�}9
(
ThxTm�r�������q�mlrqlTh���S�.�.�S���=�C
�=���<�=���<�Q6��.
D
6�®xT��5�5�V
A
®������Ǵ��h�|}rfSbf��y%
�V��<�<��e��\@
o�l{{��� 
�����|���'
�����v�5w1
��E
���w������E�[����z�������������5�5������<5
�ey/
�đv��w����R�/�/�R�Sw���tw|l�S�S���Ev�Dw�_�K������/����+
��>v��w����zMg�n�������Q@�5
6��7�����A
���������)T�����PK
!<@�E�5chrome/pdfjs/content/web/standard_fonts/LICENSE_FOXIT// Copyright 2014 PDFium Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
PK
!<h�&Y>>:chrome/pdfjs/content/web/standard_fonts/LICENSE_LIBERATIONDigitized data copyright (c) 2010 Google Corporation
	with Reserved Font Arimo, Tinos and Cousine.
Copyright (c) 2012 Red Hat, Inc.
	with Reserved Font Name Liberation.

This Font Software is licensed under the SIL Open Font License,
Version 1.1.

This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL

SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007

PREAMBLE The goals of the Open Font License (OFL) are to stimulate
worldwide development of collaborative font projects, to support the font
creation efforts of academic and linguistic communities, and to provide
a free and open framework in which fonts may be shared and improved in
partnership with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves.
The fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works.  The fonts and derivatives,
however, cannot be released under any other type of license.  The
requirement for fonts to remain under this license does not apply to
any document created using the fonts or their derivatives.

 

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such.
This may include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components
as distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting ? in part or in whole ?
any of the components of the Original Version, by changing formats or
by porting the Font Software to a new environment.

"Author" refers to any designer, engineer, programmer, technical writer
or other person who contributed to the Font Software.


PERMISSION & CONDITIONS

Permission is hereby granted, free of charge, to any person obtaining a
copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,in
   Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
   redistributed and/or sold with any software, provided that each copy
   contains the above copyright notice and this license. These can be
   included either as stand-alone text files, human-readable headers or
   in the appropriate machine-readable metadata fields within text or
   binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
   Name(s) unless explicit written permission is granted by the
   corresponding Copyright Holder. This restriction only applies to the
   primary font name as presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
   Software shall not be used to promote, endorse or advertise any
   Modified Version, except to acknowledge the contribution(s) of the
   Copyright Holder(s) and the Author(s) or with their explicit written
   permission.

5) The Font Software, modified or unmodified, in part or in whole, must
   be distributed entirely under this license, and must not be distributed
   under any other license. The requirement for fonts to remain under
   this license does not apply to any document created using the Font
   Software.


 
TERMINATION
This license becomes null and void if any of the above conditions are not met.

 

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT.  IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
DEALINGS IN THE FONT SOFTWARE.

PK
!<z�l�\\?chrome/pdfjs/content/web/standard_fonts/LiberationSans-Bold.ttf0FFTMh��%@GDEF'�|&GPOS�(��JGSUB�<�K�POS/2�>���`cmap��w��cvt _�_��Jfpgms�#��gasp	lglyf֫?+&8�hhead��^<6hhea�ct$hmtx�	�
�kern����ӠTlocaɣ:� �Tmaxp��� name�R�3���post�-ѵ��prep;�m���j��_<����ϒN����D>�NC�����RT�/Zz����3�3�f��Px�1ASC !��Q3>�`���:� �D�9����s#s3�Z�m�f��V9��P9�9sQs�sGs/ss?sKsXsAsG�����V�U�V�^�u�3���T��V���9T��9�s��������9TV�9T��V;��{V�VV#�=�s9��-s���Bs<��sP�TsP�#�T��9�9��s�9�����P���T�sH��s9��ssD!=�+�Q��s3ss9s=�s5�� �-s\�T� k��3Zd1�3�,�W��sG9��`�R�-s]�^�^�g�r�3�3�3�3�3�3�TV�V�V�V�9��9h9��9�����9T9T9T9T9T�V9T�{�{�{�{V#V���s4s4s4s4s4s4BsPsPsPsPsP9��9r9��9���P���P�P�P�P�Pd1�����s��s�3s<�3s<�3s<�TsP�TsP�TsP�TsP���T��TV�sPV�sPV�sPV�sPV�sP9T�T9T�T9T�T9T�T������
9��9��9��9��9��9��9X9E9�9�G�s�s9����s�s���9j��9���������9�������������������9T�P9T�P9T�PT�P��������8V;sHV;sHV;sHV;sH�������{��{��{��{��{��{��9��V#sV#�=D�=D�=D9�s��3s<B9T�V;sH�����$������'�L���������n�$���.?$�$���k����9���3�����SV��=��9T9���V����&T9T��V��Z�V#�?Vy`jS9��V#�P�M�k9���P��s�V�M�B�kSZ9�v�s	��s�B�P *�y)OyP���P�
��Q9����P��QV�Z����TV;9�5��s�������������3�������V�;��.�����������9T��V��T���GV���n
�'�������6@��#s<�^��U�sP����5����������P����sP�:sRs���S�����&����k4�����sPsP�
U�kPsH9�@��9���@��
���s��9T�P�����9���9���9��V#s�P�Ps>k��9�9�9�9����s�s��A�)�U�U�\�]����~V��+lss��s
6�)��}%U�X�r�T�g������5�+�����Ud�H���1�d+d�dd1d1���d�"�����������������������������������������������������������������������������������������������������������������g���{��m�m��������������)�)�s+�k�UF�Q@;@<�fB��#�#�����g�#�5�+�-����q�g�WW������������>~�����~����_s���    " & 0 3 : < > D  � � �!!!!"!&!.!^!�!�"""""""")"+"H"a"e###!%%%%%%%%$%,%4%<%l%�%�%�%�%�%�%�%�%�%�%�%�%�%�%�&<&@&B&`&c&f&l����� ������~����r���      & 0 2 9 < > D  � � �!!!!"!&!.![!�!�"""""""")"+"H"`"d### %%%%%%%%$%,%4%<%P%�%�%�%�%�%�%�%�%�%�%�%�%�%�%�&:&@&B&`&c&e&j����������G�/�����v�����������n�����������������������}�y�!����������5�2�*�)����������D�7�(�J�I�@�=�:�7�4�-�&�����������������������ܾܹܶܮܢ�O�L�K�.�,�+�(���� bcdefghijklmnopqrstuvwxyz{|}~��������������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,-./0123456789:;<=>?w<
	

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a��������������������������������pcdhv�nj)tiB��=qDEfu7:9�@kzv��bm<@A8l{���
���8 %��w����������������KRoNOPxSQL@EYXUTSRQPONMLKJIHGFEDCBA@?>=<;:9876510/.-,('&%$#"!

	,E#F` �&`�&#HH-,E#F#a �&a�&#HH-,E#F`� a �F`�&#HH-,E#F#a� ` �&a� a�&#HH-,E#F`�@a �f`�&#HH-,E#F#a�@` �&a�@a�&#HH-, <<-, E# ��D# �ZQX# ��D#Y ��QX# �MD#Y �&QX# �
D#Y!!-,  EhD �` E�Fvh�E`D-,�
C#Ce
-,�
C#C-,�(#p�(>�(#p�(E:�
-, E�%Ead�PQXED!!Y-,I�#D-, E�C`D-,�C�Ce
-, i�@a�� �,����b`+d#da\X�aY-,�E����+�)#D�)z�-,Ee�,#DE�+#D-,KRXED!!Y-,KQXED!!Y-,�%# ���`#��-,�%# ���a#��-,�%���-,F#F`��F# F�`�a���b# #���pE` �PX�a�����F�Y�`h:-, E�%FRK�Q[X�%F ha�%�%?#!8!Y-, E�%FPX�%F ha�%�%?#!8!Y-,�C�C-,!!d#d��@b-,!��QXd#d�� b�@/+Y�`-,!��QXd#d��Ub��/+Y�`-,d#d��@b`#!-,KSX��%Id#Ei�@�a��b� aj�#D#��!#� 9/Y-,KSX �%Idi �&�%Id#a��b� aj�#D�&����#D���#D����& 9# 9//Y-,E#E`#E`#E`#vh��b -,�H+-, E�TX�@D E�@aD!!Y-,E�0/E#Ea`�`iD-,KQX�/#p�#B!!Y-,KQX �%EiSXD!!Y!!Y-,E�C�`c�`iD-,�/ED-,E# E�`D-,E#E`D-,K#QX�3��4 �34YDD-,�CX�&E�Xdf�`d� `f X!�@Y�aY#XeY�)#D#�)�!!!!!Y-,�CTXKS#KQZX8!!Y!!!!Y-,�CX�%Ed� `f X!�@Y�a#XeY�)#D�%�% XY�%�% F�%#B<�%�%�%�% F�%�`#B< XY�%�%�)�) EeD�%�%�)�%�% XY�%�%CH�%�%�%�%�`CH!Y!!!!!!!-,�%  F�%#B�%�%EH!!!!-,�% �%�%CH!!!-,E# E �P X#e#Y#h �@PX!�@Y#XeY�`D-,KS#KQZX E�`D!!Y-,KTX E�`D!!Y-,KS#KQZX8!!Y-,�!KTX8!!Y-,�CTX�F+!!!!Y-,�CTX�G+!!!Y-,�CTX�H+!!!!Y-,�CTX�I+!!!Y-, �#KS�KQZX#8!!Y-,�%I�SX �@8!Y-,F#F`#Fa#  F�a���b��@@�pE`h:-, �#Id�#SX<!Y-,KRX}zY-,�KKTB-,�B�#�Q�@�SZX� �TX�C`BY�$�QX� @�TX�C`B�$�TX� C`BKKRX�C`BY�@��TX�C`BY�@�c��TX�C`BY�@c��TX�C`BY�@c��TX�@C`BYYYYY-,Eh#KQX# E d�@PX|Yh�`YD-,��%�%�#>�#>��
#eB�#B�#?�#?��#eB�#B�-,z�E#�-�P/�@G����o�@���������5/��_�/�_�o������ ��=��'��=�=�U�=Uݸ�</A @��@(F����<����&`ѐ��`ѐѰ���������F�����

F�@��&@�)AF@�"'F�!@#�=���
�� �@�`�p�@�`���� @
H=�`�����@�F�_��</A?O�@@(&)F�/�?����@�F�p�������5�P&��<�����<@�P<��'��'��'���F(�����F5����&��&����&��_�o����@�F�@�F����_�6�F�vP&uP&tP&sP&)ppp�p�p�phpYp���@tp

FonHnF2U3U3U�aP&`_2_P&^ZH\F'[ZxZF12UU2Uo?_S@S(,F@S"F@SFRQ(QOPOO)OYOiO�@)F%IFHF!GF5�F�F2UU2UU���	�@=	@ 0p?_/Oo���?_�o_����TS++K��RK�P[���%S���@QZ���UZ[X��Y���BK�2SX�`YK�dSX�@YK��SX��BYststust+++++++st++++st++++++ssu+++++++++++++++++ssssttt++++ss+s+s+tu++++s+s+s+++++sss+++st+st+st+s+st+stu+t++++s++st+++s++su+++++++sts+sssssssss��}�y�:w�������W%�������')���L����������?�]%��u��y!�1=�����D�s�������N����0Es��s�����������������`����Hj����g�a��A��o�h���Q�������^��U��D�,,,,Fx*2�p��^����$v�		�	�
z
�0�Z
0b��@H�X�$d�X��Jj�D�����8���*R��� n �!0!�""Z"�#J#�$$j$�%@%�%�&l&�'6'�(&(�(�))�)�)�*b*�*�+H,,t--�.(.\/B/�0@0�1B1p2 262�2�3J3�3�4R4�4�5D5�5�6F6d6z6�77707H7`7z7�8
8868N8f8�8�8�8�8�9P9h9�9�9�9�9�:B;
;";:;R;l;�;�<l<�<�<�<�<�==�=�=�>>.>H>`>v>�>�?H?`?x?�?�?�?�@@�@�@�@�@�@�AVAbAzA�A�A�A�A�BBB4BLBdBxB�B�B�B�B�CrC�C�C�C�C�C�DD.DFD^DvD�D�D�D�D�D�EE0EHE�F$F:FPFhF~F�F�F�F�F�GG0GRGjH.HFHZH�H�H�H�II"I8ILIdI�I�JJ(J<JPJhJ�J�K*K�K�K�K�LL"L<L�MTMlM�M�M�M�M�M�NN$N<NPNdN|N�N�N�N�N�P P�P�P�P�P�P�P�Q
QQ0Q<QTQ`QxQ�Q�Q�Q�Q�Q�R
RR6RNR�R�S SNSfS~S�S�S�S�S�S�T0T�T�U6UNU�VV�V�W$WtZ�Z�Z�[[.[^[�[�[�[�[�\\b\j\r\z]]]]�]�]�]�]�^^^X^`^h`.`6bb�b�b�b�e�e�ff*f�g<h6h�iDi�j@j�j�kVlfl�n6n�n�oro�pRp�q,q�r�szt�v�v�v�v�v�ww
x6x�||�|�|�}�}�~N~�~� 8�H�P�����ҁ&�.�������^�����Ą̄��
�~����D���܊p����b��`�ȌЍF����R�Z���,�������R�p���������������Η����^�ʛ �����X�d�~�"�:�����蟶���T���D�\�h�t��6�x���ޣ����&�@�L�d�p�������Ф��,�^���ʦ(����&�~���������ª��*�@�V�x�Ƭ�ί�ΰ��:�򲀳�t�����ֳ��,�Z�������F���$���Ʒ(�@���H�`��������T����<�b���ֻ��*�L�n�����ܽ�(�Z�����ؾ�B�p���ڿ�8�r����
�>�t����$�j������P„����^ú�������2�J�&������0�X�f�tʂʐʞ���:�l˞���2���b�Ό�$τ�
�^И����Z���2�|Ҝ���Ӑ���VԪ�����:Հն���:�jִDdU.�/<�"�2��<�"�2�/<�"�2�#�<�"�23!%!!D �$��hU��D����@[�	�	�	V	f		�	�	�	�	�	9	I	Y		˙	�	F	v	�	�	�	�		I	�	�		�	�	�	�		���@��HF	V	4	&		�	���@��H�	�	�		��@���Hd		��@@.��H	�	�	�	�	�	�	�	�	T	d	t	F	"	2			���@IpxHr	D	T	d	2		$		h�	�	�	�	�	�	r	�	�	D	T	d	6	 			�	�		���@(PUH6	V	f	v	"			�	�	�	�	�	�	�		���@;?H		$	8�	�	�	�	�		���@-,1H 			�	�	�	�	�	`	p	D	T	t	�	�	�	�		���@H		�/�?9/^]_]+]qqqrrr+rr^]+]]]]q_qq+qqrrrrrrrrr^]]]]]+qqqqqqqqqr+r+r+^]]]]+]]qqqrr^]]]qqq9/^]�3210#!!�� &�� ������D�6@#��/��� 0@�?3�2/]q�/]]q�10#!#!'��9�����#Rs� 	M����
M���@&	
L	

R�@R		�@

	

!
���@2

H
�

�
�
�
�
O��

�
/3?399//]q]q33�2233�22/+33�2299//��+�+ć+�+�10��������������������+++3##!##53#533!33!!�E�R�P��P�O��F��T�R3T�T��oH5F^�����}��}�L��������L�hV�5@I5�-���H.���@H@H@H4���@	
M 
M'���@		M@	M2����	
L���@	
L	
L-#6""
G*n);����H;n���@H))
An���@[&5HKn�
�

@H
@H
Gt0@��F.@-6H6u!****$F@!�!`p���!!"//99//]q]9922/]]�+23333/]�2/++]��+�9///]+�+�3�29/3�210+++++++++++#5.'%.#.54>753.'24.'>V8u�~mr�wI6S=N�uHBw�bmk�h@��WPV��O��5G,2D(J"<Q.js�U�e;��6b�[/0O:%�5[�h[�V,��0WT'W_��6\��+7+7$
�:1=&
��V3����+?Sչ��@	
L)	
L%	
L���	
L���@	
L	
L	
L���@	
L	���@4��
'6�

�,�(@HU@�
�J�'���@�H'@H'�U�U�U�U�U�UrU�UaUPUAU2UU#UU�U�U�U�U�U�U�U�UuUfUAUQU2U#UUU��U�U�U�U�U�U�U�UtUcUTUEU6U!UUU�U�U�U�U�U�U�U�UsUbUSUDU5U&UUU�U�U�U�U�U�U�U�UuUaUBURU3U$UUU��U�U�U�U�U�U@��U�UuUfUGUWU2U!UUU�U�U�U�U�U�U�U�UTUdUtUEU&U6UU�U�U�U�U�U�U�U�UvU`URU0U@U"UUUg�U�U�U�U�U�U�U�UrUdUVUU"U2UU�U�U�U�U�U�U�UfUvUPUBU4U&UU�U�U�U�U�U�U�U�UbUTU&U6UFUUU7�U�U�U�U�UyUf�UIUU���@['*H�U�U�U�U�U�UfUvU9U UUU�U�U�U�U�U�UPU`UpUDUUUO�"�E�1��;�??���??���_^]]]]]]]]q_qqqqqqqq+rrrrrr^]]]]]]]]]]qqqqqqqqqqqrrrrrrrrrrr^]]]]]]]]]]]_]]qqqqqqqqqqqqrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]qqqqqqqqqqqqqqrrrrrrrrrrrrr^]]]]]]]]]]]]]qqqqqqqqqqqqq/++��^]��+]��]�99//^]]q883_]3]10++++++++#".54>32#3%2#".54>4.#"32>4.#"32>�8`�IK�_77`�LH�_7�A����H�`88a�JI�_76`��"2"%5"#4$!3#�"2"%6"#4$"3#�}�i..i�}��g))g����)f��}�j..i�~��f)�WrCDrVToDCpuVqCCqUTpDDqZ����8FT�@.	
LG(	
LI	
L	
L(	
L���@-	
L
	
L?I9II/GBJ!@H/O0���@H'VM�@#0JB
$RO4/G!4_o
*4<R
?�?39/]9�29/]��9///+]]+99���10+++++++4>7.54>32>73267#".'#".4&#">.'326Z3Z|I *(Z�gQ�`5Bm�K3zG8L�"^A-Z+ ;7L3^TH!Q_m=~�v9LAMQ3ZB&RM�9SY<[<Ai�O�hS"A�BBvY4'LnHIq[K#Z�OU�cFu�a&#�!,/$>l�49HVG0d--5>��V�g'rQ.P;"0m�}�6@
���������H���@H�?�9/++]q�10#!b���f�W��.@�
�`p�����

H
??/+�3/]q���10.54>7!�MpI##IpMPrI""IrP�Wo������ot������u�WD�?@)��o



�`�O�0�
??/]]]qq�2/]q���10>54.'!PrI""IrPMpI##IpM�Wu������to������o��K@+
	
	

	

?3/]393333/]/933107''7'73��D��������D��oh�=�y��{�=�hV�Y�Q@,	�@
0���	�?3�2�]q+M�9/3�2�_^]+M�10#!5!3!��q���9�h����h�����1"@�� 0
��/��/]���210%#>5#!�(�0$�!BCn]N##NRR(1P�X����?�//105!P�����1@� 0�/�9/]�103!�!1����%�0���@
M	
L���/?/82/]8310++3#��)��Q���'&@n�)n@

s#s?�?�/]��q�10#".54>324.#"32>J��jj��HG��ng��I��0M7;P10N97N2���KJ����CC���Օ�s./tÓ��u00u��:�
S@$4D+;K		no����@

Ht?�2?3�29/+33/]3�2/10]]35!5%!!�]��a
C�����P�G!�"7@o!$n p_s s?�?�3/9/]�3/��2/�1035>54&#"%>32!G0���wJ^[Z_��
>s�xr�x>Nz��s��i��yqr@^ZaaS�n?5f�^c��vprA�/��)�;u@K&n'7 o1'11'
o=o
@H
6s,O&�&&&#s,s `p��0@���?3/]q�?�3/]9/�9/+���9///�9�10#".'%32654.+532>54&#"%>32)?~�|��y=	!8Q7ep5Q^*b\*WF,a]Wk��U��^|�r6$IrNW�R(�`�l9Fs�K-K6dg?L)�*K:Wc`Xc�`/<f�M>kU=
;Xoh�
v@:?o?Oo������?Oo�@H
n�����@
Ht??39/33�29/+]q33�223/+]qr310!!5!34>7!���S:��8��������o��7:5)/.�?��:�(��!��
M !!���@H!
o���@4&5H*n_
o

s�$�$�$$$ ts?���@?3/]]q�?�9/]q�3//]��+�99//3+310+#".'%32654.#"!!!>32:C�Àv�yE
4L6i}9S5FZ��1O��*�Uh�r<�j��I9e�N!>0�~8Z@"5&��%5Dz�K��)�0i���@
Mnn���@1&)H2)o���$t��P`.sP.s?�?3/]�9/]]q3�/q�2�+�9/�10+#"32.#">324.#"326)<w�u����T�lL��Z<q�-�e]�k:��5N3%J:$7N1_j�j�G]Wy}#N{X%JA��KP=s�p9[@#5R:<hM+�X�.@

Mo0@s
??�2/]�29/�10+
!47!5!_�~I��O��d�D������Ө�-��A��4�)=Qe@>o4o$H*o_o
���@!&/HSHo_
o

@&/H
$Cu99M/uMu?�?�9/�99/+]��+99//q�9��910#".54>75.54>324.#"32>4.#"32>4<|����}<0Ph7;^B#=v�s{�t8#B_<>kN,��*G64F*(H9<I&!1VB<Q13T??R0�[�o>>o�ZMwV3	
<Tg8T�b57c�S7gT:
5Uw8*G33G*%H9$$:H��+P=%%>Q-5X?"#@YG��'�$8b@>
M%o	n���:/o_@&)H4to*s s			?3/q�?�9/]q3�/+]��q9/��210+#".'%32>7#".54>32%4.#"32>'��[�lK[E9Z@#BS^0\�j9C~�tz�~A��6N3/J42L1'K;$����&P~W%KH6n�s'<(Az�mp�y?S���<iM,#B^;7_E'8U��
@��P	`			�	�	�	O	_		0	��	�	�	�	�	�	?	o		 	�	�	�	P	`	�			�	�	�	O	_			0	��	�	�	�	�	�	?	o		 	�	�	�	�	P	`		@vyH�	�	O	_	�		0	h�	�	�	p	�	�		?	�	�	_	o		 	0	�	�	�	�	�		/		8�	�		@.1HP	`		�	�	�	�	?	O@5	_			 	�	�	�	@	P	p	�		�?_��@"H�/�/+]�^]]]qqqqrr+r^]]]]qqqqrrr^]]]+qqqrrrr^]]]]qqqqrrrr^]]]qq/^]��10!!� �� ��������
�@��	
��P`���O_0�������?o ���P`����O_0�������?o ����P`@vyH��O_�0h���p��?��_o 0�����/8��@.1HP`���@>�?O_ ���@Pp��?_��@"H�
�/��/+]�^]]]qqqqrr+r^]]]]qqqqrrr^]]]+qqqrrrr^]]]]qqqqrrrr^]]]qq/^]���2�10!#>5#!� (�1$� ���RCn]N##NRR(V}Y�h@8R�R�?_ /]]]3/]39=/33/3���+��+���+�+�10	V��AB�����U#X)H@3	@��?O�@H�0p @`p��/]q�/+]q�/]3�2105!5!U��J�����V}Y�h@8R�R�_ ?/]2/]]]39=/33/��2��+��+���+�+�1075	5V@��}�DE�y��^m�#'B@	'�
$$�)� 0

%�$��?�3/?�9/9/]���9/3�210!>54&#"%>32!m#:JNK<&��?ZfW9sj3VA*��K��}x��E�I!BdO?86=I0UyZGJW<Xf8P0Y�tD8i�����u��V�_x�@,
M	M(	
L(	
L#���	Mr���@!	M	
LHH
l�#'�
`"


T/�z;���	
L;�T���@8	HTGGM*o�e�"!���0@��B4
M4�[�B�M�?�?�+99//]q]3��3�29//+�+��99//33�2�9/10+++++++#".5467##".54>3237332>54.#"32>7#".54>$324.#"32>7>VL��c*G2F[m;_L D��vf� '�u/;oX5R�욅൉]/"Ei��nV���=><���n�ܱ�Z-?w����)�]�y8N07\H6#ad0_TE	Ր�`+A+&,VC*Dn�Esܬi]Q��O|%2*L��p~؞Z?r���rW��vT/*9 z"@19g���k�ܴ�Fv���/O9 )F]jp5r�.Y�T+*&3���@OR
^R
^
@H����'Ww�������@���H������w(�����xG����gw8���������euTC$4��������dtCS4%��������ufTE$4h�������t�bP2B$@���������tRbD6$�������v�dR4D&8�������vTd&F����dt�P4D�����pDd���_

?2?39/9]3�2//]^]_]]]]qqqqqr_rrrrrrrrrrr^]]]]]]]]]]]]qqqqqqqqqqqqqqrrrrrrrrr_rrrr^]]]]]]]]]]]]]qqqqqqqqqqqqqrrrrrrrrrrrr^]]]]]]qqqqqqqrrrrrr^]+]qqqq�^]+9�+�+�����+�+���10!!!!	.'!m}��}��\��
��h�����#G;&&:G#�k�j�(d@#[_
o


#Z* *0**���@'#H#Z 0"__o��#_#_?�?�9/]qr�9/]�2+]��9/]�910#!!24&#!!264.#!!2>jV��o�?�}ÆE������yw��R}qR,Mg<���:dI*�k�a-�+X�Zx���_P��W�	8L.�l/PT����)c�(���	
L"���@7	
L\\+0+%[0 _ 0 `���_?�3/]q?3/�/]]�]��3/�10++%2>7#"$&546$32.#"SyY;d�ό���UU���ϗc��:YxLk�c//d��/L`1aL�pEm���
�e:i�VG-S@'E��on��J�q�X���	
L���@1M 
M8	MM
[�0@Z0@__?�?�/^]�]q��10+++++#!!24.+32>qj��������n��Cx�e�Y�o?˰���\�R����{�q6�G>{���R@5	

 
0
@
	Z 0__o����	_	_?�?�9/]q�/]�2]�99//q103!!!!!�T���V�������	?@& 0Z 0_/@1H_??�9/+]�/]�2]�9/10!!!!��/����L���T����-p�,��
M,����	M&���
M&���@/	M\)\	/ /0/)[0_$__?�?3/�9/�/]]�]��99//�10++++%2>75!5!#"$&546$32.#"&@veQ��f0���q���SX���ϛi��<YuKn�h23i��!+���1YC(m���
�e4`�TR,O;#E��on��J�=�M@3Z�
P
`
p
�
�
0
@
�
�
Z0@`	?2?39/�/^]�2]qr�q�210!!!!!!����'f'\�����1����i@` 0@p��������@14H����@%HZ D���� 0�??/]qr^]�+^]]+]qr103!�'������0@Z0pZ__?2/�?�/�q��9/10".'%3265!5!j�zO%
$3B'X[��?>y�,`�m+;Q3vn��=k�yA����@R^

DI
���
M
���
M
���@	M 	
L	
L
			���@	H
 
0
Z 0
���@
	M
?2?3993+3/]�2]3/+833/839910+++++]]�++�!!!!	X����'{X�����������������"@PZ 0_?�?/]�]�103!!�'���c��!�0[�. 	
L ���@	
L	
L���@�	
L&&.\02�2�2�22;2K2k2/22�2�2�2�2�2[2{2�2O2;2/22��2�2�2�2�2�2K2{2?22+2�2�2�2�2�2�2�2k2_2K24222�2�2�2�2{2�2o2T2;2/22��2�2�2�2�2�22+2K2{22;2K2k2�2�2�2�2�2�2�2�2�2�2t2[2O2422g�2@��2�2�2�2�2{2o2D2+222�2�2�22;2K2k2�2�2;2[2{2�2�2�2�2/227�2�2�2�2t2K2$22�2�2�2�2�2k2_2@22?2�2�2�2�2?2O222 2 \ 0&&.?22?399//3/]�22^]]]]]qq_qqqqqqqrrrrrrr^]]]qqqqrrrrrrrrrrr^]]]]]]]]]]]qrrrrrrr^]]]]]]]]]]qqqqqqqqqqqrrrrrrr^]]]]]]]]]qqqqq_qq��2293310++++!46767#.'&'!!67>7!��	����

��V3l-52OG@<4��4<@GO:80j'�����Z*1540,*&��=�p@$	H\�@P`p� 0��
���@	H
\ 0���@	
L 	
L	?2?399//++/]�22+]qr�q�22+10!!!&'.5!���Qo=,,&X(�����*.'b23�T����'w�&�ȳ
M&���	M ���
M ���@>	M
M
M[��)0)@)`)p)�)�) )�)[�
0



_#_?�?�/]]q�]q�q�10++++++#"$&546$324.#"32>�b��������Y]�	��	�^��5h�eg�h45h�el�e0ǥ��hm���
�ef����o��EE��on��JK����N���@/	M[0`p�Z 0_/@!H_	??�9/+]�/]�2qq��10+#!!!24&#!!2>=~…����y�Ƃ?�׃���9B_?�[��M��Av�hmq�7">WT�m��$8�@	6	M62���@J	M2,([%[�:0:@:`:p:�: :�:/[0*_ 
_4_?3/�3/�?�/]]�]q�q�99//�10]]]]]+]+3267#".'.546$324.#"32>�G��|6DP.<6vF_�iI��K]�	��	�^��5h�eg�h45h�el�e0Ǎ�y:I)�9g�Xv����
�ef����o��EE��on��JK�����p@K(	
L[?Op 0�Z 0__o�@!H_?2?�9/+]�9/]�2]q�q2/]83�9910+!!!!2	4&#!!2>Q��������ńA/SrC}���}���B_=���;m�aO�fE���gd�`9N;���=m�;��@B	M!	M*Z)2Z))
Z?Z

O
_
o

2-_$**$_ 00@?3/]q�?3/�99/]���99//��10++#".'%32654.'.54>32.#"F�垌֙_.QyV��@j�K<xm^E'T��z�ŅM���~Ng=2Z}KB�vfK+�`�p=2b�^//N9[a:J0 *:UrMi�_,+X�Y'[\.="4C-*<Wx��0@ok+{��;[k��;{���+;[k{���	Z����K[����@*4H�		t	�	�	�	�		���@���Hk	{	T	;		$	��	�	�	�	�	�	�	k	T	;			�	�	�	�	$	T	d	t	�	�	�	�	�	�	[	D		+		��	�	�	�	[	4		�	@	P		$	4	�	�	�	�	�	�	p	T	d	;		$	h�	�	�	�	�		4	D	T	�	�	�	�	�	�	t	;	[	$		�	�	�	�	�	D			7�	{	�	�	@;�	�	o	+	;	K			K	[	�	�	�	�	�	�	�		 	0	p	_??�2^]]_]qrrrrr^]]]]]]qqqqqqqqrrrrr^]]]]]]]]qqqrrrrr^]]]]]]]]]qqqrrrrrrrr^]]]]+]qq/^]+]qrr��]qr^]r^]+M�10!!5!��9���c���{��J�K@5Z��@P`p� 0��Z�� 0
_?�?3/]]r�]qr�]�10".5!32>5!ӄܟY',RtGHyW0'^��?�Аq��Y�S')V�[P���ԋCH���
M���@	
L 
M	M���
M���@1	M��`/		/_p���0`����@!?o������		??39//]q83/]qr83933]]]]10++++++)!67>7!B���4"

!1��w-^'.*)-&_/���0��!��@	
L 	
L/	
L���	
L���@	
L(	
L	
L���@ 	
L)! )/000002���@�y����	I$k292Y2+22�2�2I2i2�2�2;2	2)2�	2W�2y2f2I2;2&2	2�2�2�2�2�2�2{2292Y2i2�2�2�2�2�2�2}2[2?2O2$22k2�2�2�2�2_22+2K22�2W�2�2�2t2[2O22;22�2�2�2k2�2�2_2422�2�2�2�2�22$2D2d2j�2�2@��2�2�2k2P2D2+22�2�2�2�2242T2t2�2�2�2�2�2{2o2P2D2+229�2�2�2�2t2K2[202$22�2�2�2�2O2o2�22�2�2�2�2p22O2_2)/ ?2?3399//3^]]]]]]qqqqq_qrrrrrrr^]]]]]]]]]]]qqqqrrrrrrrrrr^]]]]]]qqqqqqrrrrrrrr^]^]]]]qqqq_qqqqqrrrrrr^]]]]]]]^]^]]]]]qqqq/^]]]q833/^]83933333310++++++++).'&'!!67>7!67>7!���			
	�����+�


�J�
�+/%f07;;831,�����@|194JF@>9�;??FG8<3z9D��@
		���@+�			/_�?o���� 0���@
?2?39/333/]82/]]qr8399//q83839310]]]!	!	!	!	�������A8996�T�1������d�#5��@
���@�K[����+K[k�����Z�+
;
[


;
K
k
{
�
�
�
�
+
;
k
�
�
�
�
�
�
�
K
{

{
�
�
�
�
��
�


;
K
�
�
�
�
[
�
�

$
4


4
D
t
�
�
�
�
	l�
�
�
�
�
p

0
`
�
�
�
�

@
p
�
�
�


0
�
�
�
:�
�
�
�
@D
 
P

�
�
�
�
`
p
O
0

�
�
�
p
�
_

0
@
??39/33/]]]]]qqqqqqrrrrr^]]]qqqr_rrr^]qqqrrr^]qqqr^]qq/^]��]q82+M�82910!!	!?��
5RV5B��B?��T=��	p���
M���@	M(
M 	M���@H����@
H@
H__?�2?�2/+]3+_q�99//+310++++)5!5!!�����R��#����3s�W��+@� 0��?�?�/]q�3/]�10!!!s���Wu�����&�,@
M	M���/?/83/]8310++33���#)��
�W7�)@� 0@��?�?�/]�2/]�105!!5!���W�����-�E���@
M
M���@@H?33/2/+83�829=/3310++	#!�������B��������T��/r�//105!��NNB�?�0@!���p��/?_��/]q�/]��10	5!�����+��<���N5D��1	L���@%	M	M	L62F"�)�)))"F�FOFG���@)H<G@
H7R

?/�Q'?O2/?22�2?�3/]9/�/+�3/+�]q�2/]�2210++++".54>?54.#"%>32327#"&'#32>5�N{V.Ct�X�(;&#7(��@q�oe�n:# ((-je
8�ʐ-Q=$G;6X="+SzN`�Q%7;O2#;-GuV/2c�_�v&<)�
heep	#B9MK.Kb3�����$4M@%G��6p6
-F�����$(H���@	H(O 2O
?3�???3�/++]�22q�q�10#".'#!>5!3>324&#"326�1f�k0`VH��3�uh�a.��lq+TB((@T+kt!{ϗT.J663(#�S�b =p`S��y��"U�mj�T"�P��7N 7@FFO"GO

@O?�3/]?3/�/��]�3/�10".54>32.#"3267R���@F��{i�vI��`Xqh�Pl

Gy�P��|�ӑL8a�LSc����ed
K�i?T��\�$5D@*%F�!!@$(H!7p7/G��0*O1O	??3�?3�?/]q�q�+]�2210!.5##".54>3234&4&5!4.#"32>L1�zf�a/1f�l5bTD��(AS+6S9�*TB))46i[T��y{ΕT.H3
'26�� O{"#k�S!*Z�c��$V�P��-N'O�F%F���@''+H)/)O)_)o)�)$GR$$R
Q?2/]�?�9/�/�2]�+�3/�10".54>32!3267"!.Ju��FS��f��t6�J6V=J^	Al�s+K7"�nE�Տ�ԃ:Y�߇DuU1?B.hW9�?dF��#��W�
��@	
L��_F0���@HQO??3�2?�/+q3/3�22/]3/]q10+!#5354>32.#"3�螞Hz[0]%-'3�|��|�q>iN,
� 1 U�T�NZO3ET@3$4F�//@$(H/GpGF>G��0*9O$!AO	Q?2/�?3�?3�?/]q�3/�q�+]�2210".'%32>5<765##".54>3234>7!4.#"32>Tk�sE
cP2S;!1�zg�b/2g�ll�0
D��q(@S*6S:po*SB(�N+Mk@!AJCnQ:i^Q��y}˒O\g73($�T��v�t:�f�R"*X�^��!S��d�a���@&M
F�	�		!�!�!p!�!�!�!�!�!F�����$(H���@H	P?�3?3?/++]�2]qr�]�10+>32!4.#"!!�9�wb�U&��.J53S<!��b|p@p�X�R^?iK*-SsE���k A9.
���O@6F�p�	@	P	�	�		`		�	�	�	�	�	S?�??]]qqqqr�]qq2�2105!!������:����W���@� 	
LF�����`�
���@P�P`���@Pp����	8���`�?�����?PS?�?�?^]]qrr^]]qrrr�^]]qqr2�22/10+5!"&'532>5!���8S
'0
 Em���Z�&>.v�FAlP,�u��@$W
g



	&6F�	���@	H
/
F�����$(H���@	H{� H
?2??9/933+]/++]�2]3/+833]39910]]]]]!!!!	B��y���.����T�j����Z�l���E@0F�p�@P��`�����??]]qqqqr�]qq�103!��4��O9��(��	
L���@U	
L 9F
-F�,�,,;T;�;�;�;;;�;�;�;T;;;/;;;�;�;�;�;`;�;/;F
 
0
�

���@$(H
3P!&-
?22??3�2/+]�2]]]]qq_qqqqrr�]�9/�210++!4.#"!4.'!3>323>32!4.#"'?-+F3��4�l|�CQ_8Y{M#��'?-*E3_?iK*-SsE��H#JC5
5@@|pysCZ7@p�X�Q_?iK*+OnC���dO#a���@&MF�#�##%�%�%p%�%�%�%�%�%F�

����$(H
���@H
P
?2??�3/++]�2]qr�]�10+!4.#"!4.'!3>32L.J53S<!��9�wb�U&_?iK*-SsE��H#JC5
5@@|p@p�X�QP���N#$@G%`%p%G
OO?�?�/�q��10#".54>324&#"32>�E�Ή�ʉFC�Ί�΃=��~x|�#@Z6>aB"|ΕSR��}y͖TT��z����a�Z++Z���W�Q$6]�"��@M M%G��8p8-F�����$(H���@H(O 2O
?3�???3�/++]�22q�q�10++#".'#!4&'!3>324&#"32>�1f�k0_VI��3�ug�a/��po*SA)(AR*6T9"{ЖU-I6(5;�a�S�$(48k\T��z��#U�mj�T#+[�T�WZO!2D@*"F�@$(H4p4,G��0.O'O?3�???3�/]q�q�+]�22104>324>7!!46767##".%4.#"32>T2g�ll�0��1�zf�a/�(@S*6S:�*SB({ϕT\g73($�S��6i_T��l�S!*Z�b��$V���O^���	
L���@	H��/_�F�����$(H���@
	HP???3�/++]�2]]q3/+10+34.'!3>32.#"�*9P;1
7&iu<#NG9
;FE;]A#	�	����H��O;��:��@
	M!	
L���@
	M2F*F)���@F H))
F��O=P=0=�=@=/=F
2/Q$�*p**/***$Q ?3/]�?3/]]]�99/�]]qr�]q�99//+��10+++#".'732>54.'.54>32.#"A|�sg�}T�*>S50S="1UsAD�h@<t�oX�xQ�$5F*dd)Kg=K�qE<N|W/EnQ%-8
1%*1*HoUMyU-!GnN'33<%,+Kv���8T@3(	
L_
FO_o@&,H�
O
O?�?3/3�2/]+q3/3�22/]3/10+"&5#53733#3267�|���X���<?*4t��~������OK���\:#\@.	
LF�@$(H%�%�%p%�%�%�%�%�%F�"�""���@	H"P?2??�3/+]�]qr�+]�210+32>5!!.5##".5�.I53S<!��8�wa�U&:��?iK*-SsED��"KC5
5@@{p@o�X�j:�?�?�3�/����901%!!!5&�q���})�W��:��=:l�
?�	33�?�333�
/�ְ6�>
�R
�.���O��	�����v
�.�	���O����...�	.......�@01!!!!!��-ϸ��������;��;��r��:d:��	
L���	
L���@3	
L	
L
	
		
		����@

H
���@O��
�
O
�

?2?39/3]]q/]83333/+]833393�����������10++++!!	!!	3�������/��1�����x/��b�����Wh:#�$?�ͱ?�3�/����901%!#"'52676?!H�&�TQW_�eL5U@3)�T)�I���RY
�*f0/D�:	P@2GG��@��O_PP?�2?�2/]]qr�/99//]��1035!5!!D�
)��J����\�!�W��-W@6(	
L(	
L!,,(�_���0P"�+�+�?�?�9/�9/]]33/]�22/3910++".54.'5>546;#";-EmM( <U45U; ���:[Q!8L*+L8 Q[:�W*MoEH?W6�6W?H���hi��3U@+	
+AS3��jg���9��.�����@	H@P�������H/?+]/+�10!��9��m+�W��-Z�)�ش	
L���@,	
L
P   '�0@
!�  �-�?�?�9/�9/]]33/3�22/]910++32654>75.54&+532++9]O!8K+*K9!O]9Ŋ�!<U55U<!(MmE��gj-3SA+
	+@U3-ih�����?W6�6W?��EoM*Q_HB@
M0	
L0	
L���@	
L
0	
L!���/2��3/�10+++++"&'.#"5>323267TK�KBm.'C=:3�T)RPN&-k0D�4 =?F* �&.

2*����:s@��	;	K	[	{	�	�		+	;	k	�	�	�	�	�	[	�	�	�	�	�		[	k	�	�	�	�	+	K	[	�	�		@��H	@��H	@��H	@��H�	;		@�H	@vyH	d		+	;	K	i�	�		@adHt	�	;	 		�	�	�	[	k	{	$	4	�	�	�	�	�	0	$			8�	�	�	�	{	�	T	d		�	�	�	@	P	�	�	?@			 	�		@	P	p	�	�	�	���?�?9/^]]qqqqq_qrrrr^]]]]]]]qqqqqrrrr+rr^]]]++qq++++qr^]qr^]q9/^]�3210!3� �� � ,���+3��D�$:�?�%/�ְͰ�ܱ22�ͱ22��
ܰ"2�Ͱ!2��&�0167#5&'&5476753&'�C)@B*�xiq���xqwuۢ�ng��n`�<\��[9,��em�����鎌��d]��)^�0�@	M(	
L%#'nn)0n���@7&5H)2@&<H@H&u#/�@H,u,t@00����H00?3/+]�2?3/�9/+]3�2/++3/�]+�99//]���910++#!5>=#5354>32.#"!!!267^
<b�W�J'B.��0g�r`�_=�RB[P��.D+|hkkI�c;�4G`B\��\�k:)LkC/MFs}�Z;]I8c^9�9�"6T@4-���8#��� O(�O
2��		/]��23/]��]2/]qr��2�qr��210467'7>327'#"&''7.732>54.#"����0p<<o0������dy<p0����!9L++K9!!9K++L9!�<p1������0o<<m0���;���0o>+L9!!9L++L9!!9Lj������@
M
M	���@e;�/�O���		p�P��P`��\
	;		�	 	�	0	@	�	�			���@O??399//]q33�23�29/]qr^]33�2292/^]qr83^]3/]qr^]83^]993310++]!!!!!5!5!7!5!!	!���R����P����!!�����쓢���T��9��=�����@	H@	P	�	�	�		���@H?/99//+]/+2�210!!����
������5� �K]ʹV��
MW���	
L���@	
LF	
L$���@ 	
LJ(	
LDQILH5H"HHIY"TH?I���@H"I??I",_-H,@H,QDYD2�@'-�--- '0'@''���?2/]�/]3/]�9933/+��9////+�99����9910++++++2.#"#".'732654.'.54>7.5464.'>?W�xQ�'9I)pi.Ng9U�pB3I/)C/:x�|g�U�.DX5z~*T~T?�mG8O0&E3�� GoN]d0Qi8)H4�BhI"1;A'5&
0JlP,QE60=O2NZ1DnP%-<$AP-:)!-HkN1P?01AP0����'6)IE'5%
"4���c@J	 	P	�	�	�		0	`	�	�	�	A�_��o/?_�@+<H@!)H@H/+++]q2�2/�3/]�^]qr1053!53��u������ ����/Y�@SG	
LC	
L0�E;;OOE_EoEEOEO�[&�:5�@U�JPPJ@@P@`@@JJJ�J@J@J+�+�?�?�99//]qrq3/��3/��]�99//]3/�10++#".54>324.#"32>%32>7#".54>32.#"�4^���dc���^44^���d��qp`��T��nP+`��~ަ`��8T9)@0!
�9TtOh�d00b�dNtS9�
 />):T5�d���^44^���dd���^4r���~ݥ`,Oo��Tަ``�ށ>hK**3/+UD)?q�\`�k:%?S.).%'He-���2A����@	
L	
L(	
L�	8�(� �  ���@4H  C?�o@H9�		3?�@H��/#3�,�?2�23?�3/+qr9/�/+]��]+]2�29/�10+++"&54>?54.#"'>3232>7#"&'#'2>=as.Ng9�&%�+KjEAiL)!
?GQ"o#7&\6).�ia<P1*#.$	+J7;X<�01m
I=AH�.9'!),\��q@H����_@ H �
	���@@ H 0@`�
	� �?3�23333�2/]]+q����/]+]q����10%53!53'���G����iGo%����#iGo%����#T�W;@+�_o��/��?������?��]qr/��10%!5!w�����t ����/=F��8��@A	
L;	
L1<9C3�40===9�>4>4>�H&�04<2�CB�5_5o55@Hp44���@H4C55C4+�+�?�?�9///+]+q��93/��]�99//�2/83�29910++#".54>324.#"32>##!24&+326�4^���dc���^44^���d��qp`��T��nP+`��~ަ`�E�u�J��`N��KE��BC�d���^44^���dd���^4r���~ݥ`,Oo��Tަ``���9��/�q^v��;9<�G���|
��/�//10!5!|�s��^Z��'O���	
L���@,	
L	
L	
L��0
@
`
p
�
�

#��?�3/�/]�3/�10++++#".54>324.#"32>�3WuBBuW22WuBBuW3�,;##<--<##;,VBsU11UsBCsU00UsC#=--=#"=..=14�u@E

	�@/_o

��@	�@!H�_@/]]]�+]+3M�2�/�9/3�_^]+�M�22��10#!5!3!5!��o�������U�T���=��3��!K���@*	
L�   #
��_
o



����?�?�3/]9/�3/��]2/�10+'>54&#"'>32!5KTTC*'*U
�+HfBGiE",DSM?j�y5Q@638"*1b.P<" 8M-5P@534�,�~�5m@B"�#0�-#--#�7	�0�("�"�""@H""�(���	P	`	p			�?3/]q�?�3/+]q9/�9/���9///�9�10#".'732654.+532>54&#"'>32~��TqG!�7615)183-$.+*3�2Mc9DfC"IO-A)cp$;J'-0/1%~#',,-3M33E'@[.9W�U�*@����/?_��/]q�/��105!W���� +����V!:'G@F�"�"")	F������@	H P	??33�??3/+]q�22�q�210!.5##"&'#!!32>5!
+1:!4Q��(C42D*N+1@%0* 7���@hK)-QsFB��"IC6G��/�O@+	M�O_��0`���/3?�29/]/]]�3/]9/]�10+###".54>3!��ÛQ�`52_�Xu����-X�XT�]2���D@� 0�/�/]�10!� 1��`�W�̹�س
M���@%
M@	M�K4
$���@/_dH� 0P`0`p;p�����������SXH����DJH���@!58H���/�?�?9/�^]]]qq+++qr^]q_q+r/^]]/]�9/33310+++#"'532654&#*73�GtU-8*K>8B
>�!^[�'F3v% #"�RRR�j�
T���@ H H�			� 0���@H���?�2?3+�]29/33/3�2/]10++535733R������y�lzu��y-���'Q���	
L
���@)	
L	
L	
L�)�

@H
��#��?�?�/+]��]�10++++#".54>324.#"32>�+U~TP{T+)S~TXQ&�!1!!3##0!4#1M�\33[�NK\44\K;N//N:<P00P]��q@$��	�
�	 	�		����@�����@H�� �?333�2333�2/]+q�����/]q�����1075533553]���������#in%��G��#in%��G����^K�&y' ����K@p@]]]5?55��^��L�&y' �r�I�?5��gK�' �'���Ks;@p@]]]5?55r���:#'W�"	M���@	M
$�	''�)� 0

&��&�'?�?�3/9/9/]���9/3�210++74>7!32>7#".!r#:JNK<&?ZfW9sj3VA*K��}x��E���8BdO?86=I0Ux[GJV=Xf8P0Y�tD8i�_����3�&$�V�&��ʴ%+5+5��3�&$��@&Z%+5+5��3�+&$��@&%+5+5��3�&$��@& 0%+5+5��3��&$��@
&%+55+55��3�&$P��@8%+55?55���@[	
ZR^+;K�?_o__	_	o	�	�	�	�			
_
_?2�?�299//]q��/]_q�9///]q9�+�+���3�210!!!!!!!!#!��>�����C����=�\h��������	8C>�o��T�W��&&x��!1*%+5���&(�5�&��ô

%+5+5���&(��@&<
%+5+5���+&(�s@
&
%+5+5����&(�s@
&
%+55+55�����&,���&����%+5+5��hf&,�@&J%+5+5�����+&,���&����
%+5+5����b�&,��@
&%+55+55q�!u@N	
L	
L[#�#Z
_/		�		@36H	@+1H	@$)H	@"H	@H		__?�?�9/+++++]3�2/3/3�2q��9/10++#!#53!24.+!!32>qj����Ɓ����n��Cx�e�d���Y�o?˰���\R�TR����{�p5����=z����=&1��@&/%+5+5��T���&2��@(&),
%+5+5��T���&2�@(&T(+
%+5+5��T���+&2���)&����.(
%+5+5��T���&2���(&����3C
%+5+5��T����&2���(&����,*
%+55+55V�V�}���@!H#3C
!H,
<
L
���@;!H#3C!H,<L 
0
P

 @`p�� �������
H�?+]q3/]310]+]+]+]+	7			Vd���``���`�����Ff`���`������b��T����&2�0��@
M*	M$
M$	M���@		M	M���
M���@
	M 	
L���@	M
M 	M���
M���@
	M	
L���
M���@;	M+*'[4'[0*+."_	._	

	?3/�?3/9�9/]]3��29�910+++++++++++++++++7&546$3273#"'4&'32>%.#"��ml]�	�g�JR��prb����Ԕ[a++��+k?l�e0��)+-f>g�h4I�c!��
�e'#u�^�㴥��hM�d�>��K��lc�AE����{��J&8�I�&����%+5+5��{��J&8��@&l%+5+5��{��J+&8���&���� %+5+5��{��J�&8��@
&%+55+55��#5&<��@	&_	%+5+5��B@'[Z 0___o_o	??99//]]��/]�22��10#!!!!24&#!!2>=~…����'R�Ƃ?�׃���9B_?�[�|J����?s�gn|�%$AX�����?��>��@	
L	
L���@,	
L9FF2@H2@	H22'FA�APA&F�''����$(H'���@	H'9"P-'O?��?�9/++]�]]��9///++��10+++#"&'532654.54>54&#"!4>32�,_�fG�><>=QJ2KXK2!292!]Xgl��<{�~r�m5!2;2!2JXJ28IzX1�
E9.D;9I_B.E:25>(AN���o�{B6[zD6UD6-)17AUq��4��x�&D�C��E&����FI)%+5+5��4��x�&D�t @E& EH)%+5+5��4��x�&D�K��F&���KE)%+5+5��4��x�&D�R��E&��ҴP`)%+5+5��4��x�&D�i��E&��״IG)%+55+55��4��x�&D�P
)�J&��ٴOE)%+55+55B���N=LU�@
	
L	
L&���@&	
L&R0GDD
/8F99SF�/�/�//W_W WG���@7HJGO
_
o

D0QRR>MQ$)$/�$5>O8 888?33/]�2?3/]3�29/3�2/]�3/+�]]�q�3/�9/3�29910+++"&'#".54>?54.#"%>32>32!3267%2>="!.��?MeOO~X/Ex�[�+?(%;*��Ar�r�tE�`��t6�J7U;J^	Al���9\B#�0U@&K3+K7"�nqt2T="+SzN`�Q%7;O2#;-GuV/k92Y�߇AqS/?B.hW9�1Oe3-#B9MK�?dF����P�W7N&Fx#�	(!%+5��P��-�&HC��(&��ߴ),%+5+5��P��-�&HtK@(&b(+%+5+5��P��-�&HK�@)&.(%+5+5��P��-�&Hi�@
(&,*%+55+55������&�C�~�&����%+5+5��rp�&�t@&S%+5+5������&�K�@&
%+5+5����c�&�i�@
&%+55+55P����'5����@	
L@	
L��ش	
L%���	
L���@	
L.(G%!'!���@,	H!'!'7.G%'&&!+OP`!3O?�?99//]�933/39/��99//+3/]9�910+++++#".54>32.'57.'!%4&#"326XEtT.D�Ή�ʊGC�Ί.Z"F.�ܦ9�D%(Q'd~x|�#@Z6}��F���oz̓RI��nk��JHn5z�F.X*.q�������PsK#����d�&QR
@$&/?"%+5+5��P����&RC�$&��մ%(
%+5+5��P����&Rtz@$&^$'
%+5+5��P����&RK�%&����*$
%+5+5��P����&RR��$&����/?
%+5+5��P����&Ri@
$&(&
%+55+551�4�M@(
�	
	�@����?��]�+M��9/���_^]+M�10535!53��������x��q�����m#/����@	
L0	
L 	
L���@	
L 	
L���
M���@(	M'($G1G
('+!O+O	?3�?3�9/3��29�910+++++++#"&'#7.54>3273&#"4&'32>�E�Ήc�@q��;;C�Ίh�?e��;6���<l|���i R2>aB"|ΕS.+|�I�ty͖T+(r�J�s\G�E��/Q"�4%#+Z�����\�&XC�����\�&Xtx����\�&XK�����\�&Xi���Wh�&\tF��W��"2O@#G��4+!F�""����$(H"���@	H"0O&O@5;H!???+3�?3�/++]�22�q�10!3>32#".'#!4&#"326�3�ug�a/1f�k0_VI���op*SA)(AR*lu�2<k\P��tuƐQ-I6(5;�a��� O�ec�N ����Wh�&\i���3��&$M�N@&%+5+5��<���[&DM��G&���EF)%+5+5��3�&$��@&&%+5+5��<����&DN�E&���JV)%+5+5��3�b��&$Q�����  %+5��<�W�N&DQa�P��d�PP))%+]5��T���&&�5@*&�*-%+5+5��P��7�&Ftg@!&y!$%+5+5��T���+&&��@+&00*%+5+5��P��7�&FK�@"&'!%+5+5��T����&&O�@*&!*,%+5+5��P��7�&FO�!#%+5��T���+&&��@*&,2%+5+5��P��7�&FL�@!&#)%+5+5���q+&'�f�&����"%+5+5��T����&G�[K@
Ax@@  %+5?5��q��T����,=|@7,,)-F�@$(H?�?7G��0+R,,2Q# ���@H,,9O???3�99//+]3�3�2/]q�q�+]22/�2229/105!3#!.5##".54>3234&4&=!54.#"32>;����1�zf�a/1f�l5bTD��"(AS+6S9�*TB)A����UO{")46i[Q��vwǏP.H3
'26~���h�N'T�_��!R�����&(M^N�&���

%+5+5��P��-[&HM�@*&()%+5+5���&(�a�&����
%+5+5��P��-�&HN�(&����-9%+5+5����&(Om�&����
%+5+5��P��-�&HO��(*%+5����W�&(Q����j�

%+]5��P�h-N&HQ��33���33%+5</5���+&(�o�&����
%+5+5��P��-�&HL�@(&
*0%+5+5��T���+&*��@/&4.	%+5+5��T�NZ�&JK�@G&LF+%+5+5��T���&*��@.&#3?	%+5+5��T�NZ�&JN6@F&KW+%+5+5��T����&*O�@.& .0	%+5+5��T�NZ�&JO�FH+%+5��T�9��&*�N�(8.	%+5��T�NZI&J��@O&FN+%R@!!H++5+5���=+&+��@
&
%+5+5���dz&K�.O@!&
& %+5+5��j@AZ@P`p� 0��Z 0_`
	?2?399//]�33�22/]33�22]qr�22�2210!!!#535!!5!3#5!!����{{'q||��\������ª��P��
d�'����M����@(	
H
F�	�		)�)�)p)�)�)!F�����$(H����H!�@	P?3�?3?9/3�2/++]33/�22]qr�]�9/+]10+>32!4.#"!#535!!!�9�wb�U&��.J53S<!�煅2��:|p@p�X�z6?iK*-SsE�������� A9.
�����&,��@&%+5+5������&�R�@&%+5+5����P�&,M��N@&%+5+5����P[&�M�@&%+5+5�����&,��@&	%+5+5������&�N�@&	%+5+5��X�W��&,Q��ߴ%+5��E�W��&LQ���Դ%+5�����&,O��@&%+5+5��:?@+F�p���`�����??]]qqqq�]qq�103!�:��������&,-�@���o0]]]]]5����W��&LM9)�!��@H��@]]]]]]55+����"+&-�\@&�%+5+5���W��	%@� 	
L	&	6		)9&6�_o?�@"%HF�����`�
���@P�P`���@Pp����	8���`�?�����?)9I� 0�0`p����P
�/?_��/]q3�2?�?/]q�]^]]qrr^]]qrrr�^]]qqr�2/2/]+]q�]93]3]10+#'##53"&'532>5!���Ӡ�8S
'0
 Em���>�^�&>.v�FAlP,����9��&.��'��Ҵ%+5+5����9u�&N�y��ٴ%+5�u:��	��@
M(
M
M
���@	M	M
		���@	H
F�����$(H���@	H
?2?39933/++]�23/8+3339910+++++!!!!	B��y���.����T�j:�@��Z�l����&/���&��m�	%+5+5��jhf&O�O@&M%+5+5����9��&/�����%+5����9��&O�:�%+5�����&/�r@
�%+5?5����&O��K@
y%+5?5�����&/O����%+5���q�&OO����	���H	����H++��
E@&/?Z	
_?�?99//923/33/�2�9/]10357!%!���'3����A�B��ѓߓ�t�)�z@P��F�


	�p�
�
�

`

�
�
�
�
�
:5	

??99//92]3]]]qqqq�]qq22/]�22/]r10!57!7���}}��31E�E��J���=&1��@&Z%+5+5���d�&Qt�@$&d$'"%+5+5����9=�&1������%+5����9dO&Q���.$"%+5���=+&1���&����%+5+5���d�&QL��$&���&,"%+5+5����,�'Q��_B���@+$$%�� ��po?]]]]]]qqqqqq5+5���K�9P�8��@.	
LZ5;)Z 0)_/$_?2/]�???�2/]�2��9/]10+".'732>54.#"!4.'!3>32�Js\H �%,46C$ CgGL�c9��  a{�Qu�j2*g�-C+�*!-V�R'^�U';c�H��D%\ZL?EE>dF'@�̋���ВN��WdO3g@+2 	
L
F�.�..5�5�5p5�5�5�5�5�5%F�����$(H���@	H%P) P?�???�3/++]�2]qr�]�910+"&'532>54.#"!4.'!3>32E8S
'1
.J53S<!��9�wb�U& Em�W�&>.�?iK*-SsE��H#JC5
5@@|p@p�X��AlP,��T����&2M�N@*&()
%+5+5��P���[&RM @&&$%
%+5+5��T���&2���(&����-9
%+5+5��P����&RNP@$&)5
%+5+5��T���&2�1@
(&o)2
%+55+55��P����&RS�@
$&d%.
%+55+55T����+��&��
M ���@K
M[+;K++-�--@H#[0__o����(
_
(_?2�2?3�29/]q�/]]�+_q�9///]�210++!#"$&546$32!!!!!.#"3267�&k6����Y]�	�8c0��e^���^,g�h45i�d.V&j�	���b�����B|�on��FP��KN*:C_@@G++%F&&AF���@"'.HE�E1GQ@@6;.O"6O%%%?33/]�2?3�29/�/�]�+�3/�9/�29910"&'#".54>32>32!32674&#"32>"!.hv�BEā�ʉFC�Ί��BG�j��t6�J7U;J^	Al���~x|�#@Z6>aB"�+K7"�nOPLSR��}y͖TPIQHY�߇AqS/?B.hW92����a�Z++Z��?dF������&5��@& %+5+5����&Ut�@&I!%+5+5����9��&5���Դ'%+5����9�O&U�9��X�(%+5����+&5�f�&����%%+5+5��8�&UL8�&��� &%+5+5��;��&6��@>&�>A
%+5+5��H���&Vt9@<&[<?
%+5+5��;��+&6�q@?&&D>
%+5+5��H���&VK�@=&B<
%+5+5��;�W�&6x��/E>
%+5��H�WO&Vx�C<
%+5��;��+&6�Z@>&@F
%+5+5��H���&VL�@<&
>D
%+5+5���W��'xC7���W�8'x�W���+&7�!@&
%+5+5������&W�oK@
 ""[""%+]]5��O@�	ok+{��;[k��;{���+;[k{���	Z
����K[����@*4H�t�������@���Hk{T;$��������kT;����$Tdt������[D+�����[4�@P$4������pTd;$h�����4DT������t;[$�����D7�{��@C��o+;KK[������� 0p_
_		
??9/3�2�2^]]_]qrrrrr^]]]]]]qqqqqqqqrrrrr^]]]]]]]]qqqrrrrr^]]]]]]]]]qqqrrrrrrrr^]]]]+]qq/^]+]qrr3�2�]qr^]r^]+M�99103#!#53!5!�����9�������r�m�����8q@H(	
L_F		O_o@&,H�	O


O���O?�/]q3�2?3/3�2/]+q3/2�2/]3/3/10+"&=#535#53733#3#3267�|���X��͹�<?*4t��۾���徏OK���{��J&8��@&%5%+5+5����\�&XR���{��J�&8M�I@&%+5+5����\[&XM��{��J&8��@&+%+5+5����\�&XNG��{��J�&8P�N@
&$%+55+55����\l&XPH��{��J&8��@
&R$%+55+55����c�&XS]��{�WJ�&8Q @
%@)1HF%%%+5+5���W\:&XQ����+&:�s@2&710%+5+5����=�&ZK���#5+&<�W@
&	%+5+5���Wh�&\K���#5�&<�R�	&����
%+55+55��=�&=��@
&e

%+5+5��D��&]t�@
&I

%+5+5��=��&=O(@
&

%+5+5��D��&]O��
%+5��=�+&=�-@
&%+5+5��D��&]L��
&����%+5+5�{�[���@=	
LF�� 0�@P��`�����Q?�?]]qqqqr/]qq�2/10+4>32.#"!�Hz[0]%-'3���>iN,
� 1 �q��W	�K�
��@(	
L
	n
u	 P`p
u
??�9/]3�2/3�9/33�2910+.#"3#!#737>32�	A9�$���%�1V�\31,�B=k���>iN,��3�D&$'P����-3@!PB@B0B BB8g=@%%+55+5?55]]]]5��<����&D'Ptc�7@Pr@r0r rrJ&[mp)%��۴OE)%+55+5+55]]]]5���&����&�`�%+5+5��B����&�t�@V&�VY
.%+5+5��T���&��@3&T36%+5+5������&�tz@0&b03
%+5+5��;�9�'��6��H�9O'�kV���9��'��7���9�8'��W���	y@=&	6	F	)9I	&6F�)9I� 0@p�������@%H�/?_��/]q3�2/+]q�]/�]933]]10#'##53��Ӡ����>���	y@3)9I&6F)9I� 0@p�������@%<H&6F��/?_��/]q2�2/�]/+]q�]933]]10#53373����˟�=��$��[Q@@ P�0P`���/_����o��/?_@$'H@H/++]q�//]qr10!!$d��[�������@� @Pp��P���_�8@p���O�0`���O�?O�����
�����@5H��/?_��/]q��2/+�/]]]qr�^]]qr^]]qr10".'332>73'P~Y4�$2?"$?2"�3W~�3YuB'>**>'BuY3����@
FS?�9/�105!����'p#l'L���	
L���@*	
L	
L	
L���_
�
�

@H
��#�/���/+]���10++++#".54>324.#"32>#(E]44]E((E]44]E("/.""./"n5]E''E]54]E((E]4-""-/""/L�W��@m
M
MK[�
��K�[�4�t7�@,1HO�?���@"%H@H
�?�?++^]qr_r+r^]]]qqqrrr/^]�/]9/810++".54>73326793W?$ 07�?B1-5?�W3J20Q>+*s5*3
�!��������@	
L	
LDdt�����@mH	$!4!!!�!�!�!�!�!�!�!�!d!t!P!4!D! !!!�!�!�!�!�!�!�!t!�!`!D!T!0!!$!!A!���@6@H���
/?_��/]q2��3+_^]]]]]]]]]]]qqqqqqqqqqqrrr//^]+]10++".#"#>3232>73,YTK"�/VI-ZTI!
�.V�&/& --fX:&/& .-fX:����=@(	�
�����	�/?_��/]q2�2/��/]��10#53#532���X����"
+��"
+����4@#��/?_��/]q�/]�3/]�105!�E��!$%��n�JU	
h�	���@ 	H		

� �
���@#H@H�/?_��/]q�+�+2�2/]�3/]�3/�3/+�1053%53!53i;���L��!�%�v)�����$��&$T�k��`���5�%������H������H������H������H������H������H������H������H������H������H���@	��H@��H������H������H������H������H������H������H������H������H������H������H���@	��H@��H������H������H���@	��H@��H������H������H������H������H������H������H������H������H������H������H���@	��H@��H������H������H������H������H������H������H������H������H������H������H�����H�����H��@���H������H������H������H�����H��@���H��@���H�����H������H�����H�����H��@���H������H�����H��@���H�����H��@���H��@���H�����H��@���H������H���H���~~H����}}H����||H���{{H��@�zzH����yyH����xxH����wwH���vvH���uuH��@�ttH����ssH����rrH����qqH���ppH��@�ooH����nnH���mmH��@�llH���kkH���jjH��@�iiH���hhH��@�ggH��@�ffH���eeH��@�ddH����ccH���bbH��@�aaH����``H����__H��@�^^H���]]H��@�\\H��@�[[H����ZZH����YYH��@�XXH���WWH��@�VVH����UUH����TTH��@�SSH����RRH���QQH��@�PPH����OOH��@�NNH��@�MMH����LLH����KKH����JJH���IIH��@�HHH����GGH����FFH����EEH����DDH��@�CCH��@�BBH����AAH����@@H����??H����>>H��@�==H����<<H����;;H����::H����99H����88H��@�77H����66H����55H��@�44H����33H����22H����00H����//H����..H����--H����,,H����**H��@�))H����((H����''H����$$H����##H����""H����!!H����H����H����H����H����H����H����H����H����H����H����H����H����H����H����		H����H+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++5?5����D�8��.��'(}T�u��,���n@%��O?/]]]]]5+5?5��$��'+xT�k��,���i@%��O?/]]]]]5+5?5��$A�',�T�k��<���P@"%�����@?]]]]]]]]]5+5?5�����C�&2`T�-��C�*(����	H��2@ ((

%oo_O?�`]]qqqqqq55++5?5���I�'<TǞL����@+%������PO?]]]]]]]]]]]]5+]5?5����a�&vJT���E@8@606 66��@6611%����pO]]]]]qq5+]]]]5?5�����U&�U�;���ٴ
%+555��3��$���j�%���$@ 0Z 0_??�/]��]10!!������d�Sl�[@4R^R^`??�229/]3�293310�+�+ć+�+�]7!%.'T�Y��������b���)QE12FR(�H����(��=��=���=�+T����+��(
M���@
M)
M)	M%���
M%���@B	M"[-0-@-`-p-�-�- -�-"[0__o����'_'_	?�?�9/]q�/]]�]q��9910++++++!!%#"$&546$324.#"32>�K���b��������Y]�	��	�^��5h�eg�h45h�el�e0;�p���hm���
�ef����o��EE��on��JK�������,�����.H��@1R

^
R^ 0����%)H���@#H��`/
���@@%)H@#H?2?9/++q83]]]]3/++]q82933��+��+���+�+�10%!.'&'!!H���

���	+u0^&-)*.'^-������!�0���=�1T��L@���@ H
@H0
___?�?�9/�/]]+�+9////10!!!5!5w9��\�����G��W����T����2�=�'@Z�	Z 0`?2?�/]��q�10!!!! �������s������3Z��M@+\[
Z0_	
_?�2?9/33�2/]]����99//]�1035	5!!!Z��Q�Gs�v
�������B���7��#5�<?��S�
(3�@�(\/[!5�5�5�5�5w5f5'5G5W55�5�5�5�5�5�5�5v5g585H5%555��5�5�5�5�5�5w5X5E5&5655�5�5�5�5�5�5�5w5f5W5F5'5755�5�5�5�5�5�5v5�5g5H5955&55��5�5�5�5�5�5h5U5F5755�5�5�5�5�5�5b5r5S5D5@�555&55�5�5�5�5�5�5�5�5u5V5f5G565'555g�5�5�5�5�5�5�5�5t5f5R5D505"555�5�5�5�5�5�5�5r5d5V5@525$555�5�5�5�5�5�5�5�5d5V545"5557�5�5�5�5�5�5p5d5P5D555T5t5�5�5�5�5;5 55�5�@55�5�5[5k5{54555)[O_o0_/_'?�2�2?�2��/]�^]_]]]]]]qqqqrrrrrrrr_rrr^]]]]]]]]]]]]qqqqqqqqqqqqqqqrrrrrrrrrr_rrrrrr^]]]]]]]]]]]]]]qqqqqqqqqqqrrrrrrrrrr^]]]]]]]]]]]qqqqqqqqqqqqqrrrrrrrrrr^]]]]]]]]]]]]qqqqqq��9/33�22104&+32>5#".54>;5!32+;#"*��TwM$��6�ܒGJ��**��JG�ە6��$MwT��ژ��q2Yz�c�X��nwÊK��K��wnƖX��HzY2����D�;`�#2@��%�%�%�%�%�%�%h%x%Y%J%)%9%%�%�%�%�%x%�%�%i%Z%K%9%%	%�%�%�%�%�%�%�%y%j%(%8%%
%�%�%�%�%�%�%8%H%h%�%)%%�%�%�%x%�%i%Z%K%%	%��%�%�%�%�%�%y%j%(%%
%�%�%�%�%�%�%s%`%B%R%4%%&%�%�%�%�%�%@��%�%t%f%%%g�%�%�%�%�%�%�%�%F%V%v%2%$%%�%�%�%�%�%�%t%f%B%4%%&%�%�%�%�%�%y%V%f%$%%%7�%�%�%�%�%�%�%t%P%D%+%%%�%�%�%�%T%d%�%;%$%%�%�%�%{%%%4%T%d%Z	Z���dK:�����@m��Dt� �����$4Td�����`O 0
%Z	Dp	 	0				_
??339/3�2/]]q^]��^]]]]]_]]qqqqqqrrrrrrrr^]]]]q�9/3�2^]]]]]qqqqqqqqrrrrrrrrrrr_r^]]]]]]]qqqqqqqqqqrrrrrrrrr^]]]]]]]]]]qqqq_qqqqqqqrrrrrrrrrr^]]]]]]]]]qqqqqqqrrrrrrrrr^]]]]]]]]]]qqqqqqqqqq10!#".5!;!32>5!#��(�чA#JsQ		QsJ#A�ѐ��U�C���-PpF �� FpP�1ƒCS�5q���@B
M
M,$
@$`$Oo$$1[�7 Z�111�&&&&##&_%_?�?3�22/3//]q2/q��q2/�99//]]3310++267>3!!>54.#"!5!2.54>$5��b0h�q >�a��7i�bc�j7���e> p�f0b��R�ߍf���3�5V�e�l99l�e��V���3���f�ߛR����b�&,��@
&%+55+55��#5�&<�R�	&����
%+55+55��P����&~TN@:&:=%+5+5��M����&�T��@8&N8;%?�����H?�@���H?�����H?�@���H?�@���H?�@���H?�@���H?�@���H?�@���H?�@���H?�@���H?�@���H?�@���H?�����H?�@���H?�@���H?�@���H?�@���H?�@���H?�@���H?�@���H?�@���H?�@���H?�@���H?�@���H?�@���H?����H?�@���H?����H?�@���H?����H?�@���H?�@���H?�@���H?�@���H?����H?�@���H?����H?�@���H?����H?�@���H?����H?�@���H?����H?�@���H?����H?����H?����H?����H?����H?����H?����H?����H?�@���H?����H?����H?����H?����H?����H?����H?����H?����H?����H?����H?����H?�@	��H?���H?�@	��H?���H?�@	��H?���H?����H?����H?����H?����H?����H?�@	��H?���H?�@	��H?���H?�@	��H?���H?�@	��H?���H?�@"��H?���H?���H?���H?���H?���H?���H?�@	��H?���H?�@	��H?���H?�@���H?���H?���H?���H?���H?���H?���H?�H?�~~H?�}}H?�||H?�{{H?�zzH?�yyH?�xxH?�wwH?�vvH?�uuH?�ttH?�ssH?�rrH?�qqH?�ppH?�ooH?�nnH?�mmH?�llH?�kkH?�jjH?�iiH?�hhH?�ggH?�ffH?�eeH?�ddH?�ccH?�bbH?�aaH?�``H?�__H?�^^H?�]]H?�\\H?�[[H?�ZZH?�YYH?�XXH?�WWH?�VVH?�UUH?�TTH?�@�SSH?�RRH?@QQH?�PPH?�OOH?�NNH?�MMH?�LLH?�KKH?�JJH?�IIH?�HHH?�GGH?@FFH?�EEH?@DDH?�CCH?@BBH?�AAH?@@@H?�??H?@>>H?@==H?@<<H?@;;H?�::H?@99H?�88H?@77H?�66H?@55H?�44H?@33H?@22H?@11H?@00H?@//H?@..H?@--H?@,,H?@++H?@**H?@((H?@''H?@&&H?@%%H?@$$H?@##H?@""H?@!!H?@  H?@@5H?@H?@H?@H?@H?@H?@H?@H?@H?@H?@H+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++5+5��k�Xd�&�T@$&s$'"%+5+5�����&�T�@&
%+5+5����TU&�U�����$*%+555555P���N%9Z@"(
M"	MF 00000 ���@	H;&G 5O+O?3�?3�??/�3/+3339/]�9910++%#"323>7!!.'32>7.#"\AUoH����Iu[B(	60#$*��

�,E0'L?/
	#7M30H.�8^E&'E]675/���:M���9 B?7.a�Y*-\�]J�d;(X���W�� >l���	
L���@	
L1
4F@	H
+F@!
F�����$(H���@	H0O11&9P
&O?�??�9/�9/++]�2��9/+�910++#"&'#!463232>54.#5>54.#"�4o�we�7	��l�q9#=Q.9s\:�9AF":X;$P~Yq,C/6P5�X�tB7'9\5�����/Z�RIoR87]��
#A[88^D'�rc$?/EoO�Xk:��

M���@
M		

���@�	
HkT+K���4����������{�d+�+Kk���[k�����O;���K[k4l+;K���������dt�K��K[4:����tK[��� @p������Oo ����@O@	M
?2?/33+/]82^]]]]]]]qqq_qrrrr^]]]]]qqqqr^]]]]]]qqqr^]]]]]qqqr^]qq3/8+39/^]9_^]33310++!>7!!>7*��'�u%	��5:��	>MOIG?{��7nu~F��XV����2���	
L.���@ 	
L///2/2)G4
G0)).@H.���@HO	
L/���@
	
L/O0O$?�?�2+9/+]+3+/]���99//]3+9910+4.'32>".'#".54>75!b 7H(CkL):Y=Fa<�
'+'
C:\?!G�ɁƇGI�`��U�EufX(Li�SBpQ./Rsj��5r��Tl��LE��un��_!:��M���N7h@?	
L	
L*F00##095GO0//'R+$$$Q		?3/]�?3/]�9/�9/]�3/]9///9�10++%267#".54>75.54>32.#"3"K�:�!Mg�\k�h3*Mj?=_A"8l�fFr^N"�*eETX3XxEB{^8U�M<�"B4!/TtF9]B(*BW3?jM,(A-{98A90; 
�A:HJB�o��6����@
M%
M 	
L���
M���@	M&0@<H00
-0--���@)H--F
@
H
8FO_o@'<H0-Q.�??99�2/+]��+�2/9/+]3/+310+++++'>54.'.54>75#!5!D(Ga86s^< 
�	#A\:@�lD.Pk{�@"$"���>�{lQ/�8E+&CkT*WQDJ%(*%3$'Jz`P�����A��=�����k�XdN#b� ��@'	
LF�#�##%�%�%p%�%�%�%�%�%F�

����$(H
���@	H
P
????�3/++]�2]qr�]�10+4.#"!4&'!3>32M,G45W=!��
	
@TlH]�U)�XKe=)PsK��SDy*01//L5,\�e��Z����%C@!F@"&H' F0���@"&HO/   QQ?�?�9/q�/+]�2�+�210#".5322>7!"!.���l�v>��y�p6�-'D4 �� 3A)'D3 z1B����\��vx]���	/o����p/r-l����l-��:
w@Z
0	
L0	
L
@	H

F?O>������p����`�����??]]qqqq�]]qqr^]�3/+10++3.5!�5@F!N�� @<3�u:��	��@
M(
M
M
���@	M	M
		���@	H
F�����$(H���@	H
?2?39933/++]�23/8+3339910+++++!!!!	J�����#����j��:��2��	T#��#��@	
M
M���@	
M 
M!���@	M#		
	���@	
���@�	
H%k%T%%+%K%�%�%�%4%�%�%�%�%�%�%�%�%�%�%{%�%d%%+%�%+%K%k%�%�%�%[%k%�%�%�%�%�%�%O%%;%�%�%�%K%[%k%4%%l+%;%K%�%�%�%�%�%�%�%�%�%d%t%�%K%%�%�%K%[%4%%:�%�%�%�%t%K%[%%�%�%�% %@%p%�%�%�%�%�%�%O%o% %%%@
	P?2/�9/3^]]]]]]]qqq_qrrrr^]]]]]qqqqr^]]]]]]qqqr^]]]]]qqqr^]qq3/+82/^]839/9_^]3310+++++).'!'.#"'>32T�ڈ
 !
����,+0 60c9QtYG"�
-9>:0W_[�S�IE`=�+\�b��`]:'�@#F�"�""@@DH"@25H"@$(H")	F������@DH����25H����$(H���@$	H�)�)p)�)�)�)�)�) P	??33�??3]qr/++++]�22�+++]�210!.5##"&'#!!32>5!Q&zV4W��+F42P:,56ci0* 7����@hK)-QsFB��"IC6:V�	�г
M����
M���@
M
	
L	M 	M		���@F�f	I������@q��H	��Vv�6FV��"��������pRbD0"���������rTd��@@���H��������brTB4&��������dt@P4g��������`DT ������`pDT 0����dt��@$47p����_@���oP/?����P@?	?3?3^]]]qqqqqqrrr_^]]]]]]]qqqqqqqqrrrrrrrrr^]]]]]]]]_]]]qqqqqqqqqqqqqqr+rrrrrr^]]]]]]]]]]]]]qqqrr^]+]]qqq�/�/8393310++++++!!>54&'!?l�N���z)8O2Nc���^:��P���NQw--qB�o��H����@
M< 	
L, 	
L 	
L���@	M>F*/9$*C9@<H�6�6*���@;	H6*C99C*6
F

 

JFO_o@'<H96O7$DO>7CC7�??9/999�9�2/+]��]�2/9////+]+93�10+++++'>54.'.54>75.54>75+5!D(Ga86s^< 
�	#A\:@�lD4o�|CwW32Oa/DPLL�E�e=5Ys=d�q=�5D-&CkT*WQDJ%(*%3$'Jz`L��_"<V79R:#	��0J63A(�$Ag��P���NR*���:(u�
(	
L���@D	
L0'''FF�*  ?  @H  F0P`O&O?�??�22/]�3/+]�]]�9/�3/]9/10++3267#"&5!!>="5>3!�804,s@����
 ��$a�$,13	|��H7�
��s|�ں�JL���}���y�W�O-=@G/&G��@$*H���@	H O)O?�??�/++]q�2��10#".'#!4>324.#"32>�@w�k7[M>��E��yrɖX��%D_9;S54�M5Q6vŎO%27 �W�w��JO�ڂZ�c4.W}O��9E2[�O�o�O9W� 	
L	���@	
M6
M���@
M--#F;F#3O*..*�??3/�99/���2/9/10++++'>54.'.54>32.#"T,Mj>(SOF4!%�	*Lg>-]XL:!2Ry�is�;�*2:"7UA-
�:J3% +@X='RL@J$(*%2$+=YzQ;���oCKB�$0MdhdP��Q:/R@"(G@<H���@$

H1(G

�
�
�
�

@<H
#O+O?�?�2/+]q��++2/�9910#".54>3!#".'4.'#"32>�D�ˈ�͊E_����
'/1<0��%JDvX3}yA_@�i�~GJ�ʁ�ҊB�(^l|$?reV$-[�^��,Sv��p:!r@U(	
L/?� F 
0
�
�
�


o�������#`#p#�#�#�#�#OO?�2?�]�]]qr2/]9/�2/]10+"5>3!!3267#"&5'OE5+36���+0277��|
����(4�
�����T:K@%	MF��p�����F�����@	H	P?�?3/+]�]qr��210+#"&5!32>54.'!T;}ˆ��_l;R4$,*#;�ۚR�������.e�o?��p%(q��P�WjR%/8@�-FF
&F1�1�1�1�1v1�1T1F1$14111�1�1�1�1�1�1y1f1T1F1	1��1�1�1�1�1d1t1�161V111�1�1�1�1�1v1�1I11"11�1�1�1�1�1v1�1B1411&1��1�1�1�1b141D1T1&1�11���@7��H�1&1F1V1v1	1�1�1�1t1�1f1)19111g�1�1�11���@�]`HI1&161�1�1�1�1�1Y1i1y1D161	1�1�1�1�1�1�1�11$141D1d117�1�1�1�1�1t1K1[1/1?111�1�1�1�1`1p1�1?1O1 11�1O1o1�1�1�1�1011)R!-Q??3�2?3�2^]]]]qqqqqqr_rrrrrr^]]]]]]_]]qqqqqqqqrr+rrr^]]]]]]qqq+qrrrrrr^]]]]]]]]]qqqqqqqqrrrrrrr^]]]]]]]]qqqqqqq��/�9/3�2910#.54>746324&#">jQ��u�yÉJ<r�l3J0#A^:��_�n;��KD9B��5�׌G�i�H�Ѝ�ćP�
FgF]�]1��L��y��mz���
�X�P,��(
M��س
M��س
M���@,	M		!  0.O.�.** ���@ 			!! 'P?�??39/3]]/833/]3/]833839]3]10++++2>7!	!.'!'.#"'>�<ZOM/	�)���������w%7.+)((mP*^�l.'($
E�N���274451�K=�Lb8���W�<�@	M
FF	���@�	HF!�!�!�!f!v!I!;!"!!!�!�!�!�!�!�!�!v!I!!6!��!�!�!�!�!�!�!b!T!F!!!�!�!�!�!�!�!�!v!b!T!F!!!�!�!�!�!�!�!F!f!v!"!!!��!�!�!�!�!�!v!�!Y!F!$!4!!�!�!�!�!�!�!�!T!!F!�!�!�!�!�!@˛!�!b!T!F!!!i�!�!�!�!�!�!v!b!T!F!!!�!�!�!�!I!i!y!"!!!�!�!�!�!{!_!o!@!4!!!9�!�!�!�!�!�!!d!!K!�!�!�!d!t!K!?!!�!�!�!�!?!o!!!!O
??33/?3�2^]]]]]q_qqqqqrrrrrrr^]]]]]]]]]qq_qqqqqrrrrrrrrrr^]]]]]]]]]]qqqqqqqqrrrrrrrrrrr^]]]]]]]]]qqqqqqqqqqqrrrrrrrrr^]]]]]]]]]qqqqqqqqq�^]�/+]�9/q3�210+%>5!!.5!!�B[97{Œ����};;[=�ErU`��z�u;�k�;u�zp��UqE�Q��qO=�@�

=..(=F<<3F((F	&?�?�?�?�?t?f?4?&???�?�?�?�?f?�?D?6???��?�?�?�?�?y?f?T?B?4?&?	?�?�?�?�?�?�?t?f?)?I??�?�?�?�?�?�?t?f?9?I?"???��?�?�?v?�?�?�?T?F?$?4??�?�?�?�?�?y?�?&?F?V?f?	?�?@��?�?�?�?�?r?d?V?)?9??h�?�?�?�?V?f?�?�?9??&?�?�?�?�?�?�?�?i?[?I?&?6?	?�?�?�?�?�?y?k?V?4?&?	?8�?�?�?�?�?�?{?d?K????+?�?�?�?�?�?P?`?�????O?_??�?�??<<8O#.
Q-?3�2?33�29/^]]qqqqq_qrrrrrrrrr_rr^]]]]]]]]]qqqqqqqqqqrrrrrrr^]]]]]]]]qqqqqqqrrrrrrrr^]]]]]]]]]]]]qqqqqqqqrrrrrrrrrrrr^]]]]]]]]]qqqqqqqq�^]�/]�9/�99/9/1032>54.'7#".'##".54>7326=!�^U6I,=`Dy��G:o�hJrT::TrJh�o:G��xD`>,H6Vaڎ�0ZNH�jI
�]���ȉH <Y99Y< H�Ȁɓ]�
Ji�HNZ1�������d�&�i��&���
%+55+55����T�&�i��&��޴" %+55+55��P����&RTV@$&@$'
%+5+5����T�&�T@&
!%+5+5��Q��q�&�T[@>&V>A(%+5+5��������&(�y<@&
%������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H����}}H����zzH����wwH����ttH����qqH����nnH����kkH����hhH����eeH����bbH����__H����\\H����YYH����VVH����SSH����PPH����MMH����JJH����GGH����

H����H����H++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++55+55����3t@H	
L	M-!Z5@3-.0-Z?00 .0..'_��p0_1._?�3/]??�29/]qr�3/]�]�2+M���910++>32#".'732>=4.#"!!5!$g~�Ku�o5*f��Jv_L �%,46C$<eN6nfY!��9����$6n�s6l�yA-C+�*!!?]<	Ca?�k������a'�q���'����
%
����H
����H
�����H
����H
�����H
����H
����H
�����H
����H
�����H
����H
�����H
�����H
����H
�����H
����H
�����H
�����H
�����H
�����H
����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�����H
�@���H
�����H
�����H
�����H
�����H
�@���H
�����H
�@���H
�����H
�����H
�@���H
�����H
�@���H
�����H
�@���H
�@���H
�����H
�@���H
�����H
�@���H
�@���H
�@���H
�@���H
�����H
�@���H
�@���H
�@���H
�@���H
�@���H
�@���H
����H
�@���H
�@���H
�@���H
�@���H
����H
�@���H
����H
�@���H
�@���H
����H
�@���H
����H
�@���H
����H
��H
�@�~~H
��}}H
�@�||H
��{{H
��zzH
��yyH
��xxH
�@�wwH
��vvH
��uuH
��ttH
��ssH
��rrH
�@	qqH
�ppH
��ooH
��nnH
��mmH
�@	llH
�kkH
�@	jjH
�iiH
��hhH
�@	ggH
�ffH
�@	eeH
�ddH
�@ccH
�bbH
�aaH
�@	``H
�__H
�@^^H
�]]H
�\\H
�[[H
�ZZH
�@�YYH
�XXH
�WWH
�VVH
�UUH
�TTH
�SSH
�RRH
�QQH
�PPH
�OOH
�NNH
�MMH
�LLH
�KKH
�JJH
�IIH
�HHH
�GGH
�FFH
�EEH
�DDH
�CCH
�BBH
�AAH
�@@H
�??H
�>>H
�==H
�<<H
�;;H
�::H
�99H
�88H
�77H
@66H
�55H
�44H
�33H
�22H
@11H
�00H
@//H
�..H
@--H
@,,H
�++H
@**H
�))H
@((H
@''H
@@l&&H
@%%H
�$$H
@##H
@""H
@!!H
@  H
@H
@H
@H
@H
@H
@H
@H
@H
@H
@H
@H
@H
@H
@

H
@H++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++5+5T��{�,x@P\&&%([0\.(__%o%�%�%�%�%%% __ 0 `p���?3/]q�?3/]�9/]q��]�/]]�299//�10%2>7#"$&546$32.#"!!QxV:�b�̊����TS���̕a�9WuK[�c8"��8d��/L`1aL�pEm���
�e:i�VG-S@'2_�W�Y�e7��;���6�����,����`�&,���&���@�%
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@��H
@}}H
@yyH
@uuH
@rrH
@nnH
@ggH
@ccH
@``H
@\\H
@UUH
@QQH
@JJH@
@CCH
@??H
@88H
����77H
����44H
����33H
����22H
����11H
����..H
����))H
���@	((H
@&&H
����%%H
����$$H
����##H
����  H
���@H
@H
@H
����H
����H
����H
����H
����H
@		H++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++55+55������-��x�&/x���'H���@H������@5H\!,Z'[1+_!_!o!�!�!!!,``,_?�?�?�9/]qr�/]��99//�2�+]++10#!!#"&'532>7!!24&#!!26xA�Â�M��1-5AUnH:(%$#&C��{�y=�֋���\���Z�tC������~N"	�8b�ړ��<m�b]e�ye�6�K@+Z
[!
	Z
`
_
o
�
�


_
?3�?39/]qr3�2//�2��9/3�210#!!!!!!!24&+!266A�Â�����'#'S{�y=�֋�����Z�tC\�����1��<m�b]e�ye{�H@'Z
@Z? 0__

?3?9/3��2/]�]�2+M���10>32!4.#"!!5!%[hv@���DkNX�B��9������ �HiC! �_�������&���@&K%+5+5���7&�CL8������>&���J�&���!)%+5+5��h6��@�\Z	
Z�
�
t
`

$
D
T
�
�
�
�
�
�
d
t
P
D


��
�
�
�
�
D
t


+
�
�
d
�
�
�
;
K


�
�
�
[
k
�

4
��
�
�
�
�
t
`
T
0
$

�
�
�
�
P
D
+

�
�
�
4
D
d
t
�
�


i�
�
�
�
�
t
+
[
�
�
�
@uK
{
�

$
�
{
�
�
�
d
P
4
D


7�
�
�
�
p
4
T
d
�
�
�
�
�
+
;
K
k
�
T
�
�
�

+

`
��?3??��_^]]]]qqqqqqrrrrr^]]]]]]]qqqqrrrrrr^]]]]qqqqqqqrrrrrrrrr^]]]qqqqrrrrrrr^]]]]]]]]]]qqqqq/^]���9/�10!!!!!c�&'i�'�h���s���h��3��$�w�U�	M���@,	M		[0Z__o��
__?�?�9/]qr�/�2]��9/10++#!!!!24&#!!26wA�Â�%��{�y=�֋��x����Z�tC��<m�b]e�ye���j�%�a�"@Z 0_??�/]��]10!!a�O����d��h��N@+)9H[	\		\Z\``�?3?�22?�/���3/�99//��+]10%3#!#3>7!!չ��x��"@7-=��$-6�t��h�>���k��s��U���=����(��E�-�@#&)'-	-Z'   ('''&'''/������@�	
�/�/�/�/�/�/�/V/	/9/�/�/�/i/�/[/I/&/	/��/�/	//9/Y/i/�/�/�/�/�/�/v/Y/F/)//�/�/�/v/�/	/)/Y/��/�/�/�/�/�/�/f/�/T/B/4/&///�/�/�/�/�/�/�/r/`/R/D/2/$///�/�/�/�/�/�/�/�/t/`/R/D/2/$/@�//g�/�/�/�/�/�/�/�/t/b/T/F/2//$//�/�/�/�/�/�/�/t/�/f/D/T/2/$///�/�/�/�/�/�/�/d/t/P/D/ /0///7�/�/�/�/�/�/p/d/P/$/D//�/�/�/�/�/�/T/t/ //�/�/�/p/�/O/ //)&		���@	
L	,(?22?339/3339+32^]]]]]]q_qqqqqqrrrrrrrrr^]]]]]]]]]_]]]qqqqqqqqqqqqqqrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]qqqqqqqqqqqqqrrrrrrrrrrrrrr^]]]]qqqqqqqrrr^]]]]]]]]qqqqqqqqq/^]833/833/]833/839/3�2999910!".'!.'!3!2>7!!##" �����%+.�,�0F8009F0�,�.+%���Gd
��	-=G$���Nj@W��@jND�i$G=-	���
��.����8��(��@X	
L\ /* Z* * *Z4:\@
H/__o����%_%_0 `p��?2/]q�?�3/]9/]q�9/+]���99//�9/9�10+".'%32654&+532654&#"%>32~~��c :OhC����;;��{l?^E/��^��vt��I(Ih?CsU1J��4c�]]7\D&kaeY�`d[`(DZ1=\�j94_�TEoS9
1QqI]�o>�7�m@)	 
M		M	\@P`p� 0�����
M���@	M\ 0	?2?399///]�22++]]qr��22++103!!!46767�eL�������2b'.*F�?(X&,,����7>&���J@&#
%+5+5���Z�
M���@,	
L			Z 0a?2?39/�99/]�23/]833/839910++!2>7!!#!�'4>L1�,��2.(���##�����@iND�k#H>.	���
�����O���'H���@H������@H\Z``	?�?�?/]��9/�+]++10!#"&'532>7!!��i1-5AUnH:(%$#&C���������~N"	�8b�ړ����!�0���=�+��T����2�7�#@Z	Z 0`?2?�/]���10!!!!�������s������3��T����&����7��
��� 	
L���@C	
L

	94d����4d����0 ���@`?�?39/3/]83/]_]]qr^]839_^]339/10++#".'732>?!	!(Oa}VGC=R&N,#71.��3�*+>S~V+�.K7%��X�G����
(3]@(\/)[	IY	@&+H[!���@�&+H!5�5�5�5�5�5�5i5y5[55)5I55�5�5�5i5�5[5)595I55	5�5�5�5�5Y5i5�5F5)55	5�5�5�5�5�5�5�5i5F55)55�5�5�5i5�5[5)595I55	5��5�5�5i5�5F5)55	5�5�5�5�5�5�5�5r5`5T5@54555�5�5�5t55+5;5[55@�g�5�5�5�545D5t5�55�5�5�5�5�5p5d5@54555�5�5[5�5�55$57�5�5�5�5�5D5t5+55�5�5�5�5�5p5@5`555�5�5�5_5o5�55 50_/_'���??99//]3�23�2^]]]]qqq_qqqrrrrr^]]]]qqqqqqqqrrrr^]]]]]qqqqqq_qqqqqqrrrrrrrr^]]]]]]]qqqqqqqqqqrrrrrrrrr^]]]]]]]qqqqqqqqq�+�/+^]�9/33�22104&+32>5#".54>;5!32+;#"d��,3TwM$�{O�ܒGJ��CC��JG�ەO�{$MwT3,��ژ��q2Yz�c�X��nwÊK��K��wnƖX��HzY2����D�;��h��S�\�����"H���@$HZ	@%H	@H	
Z 0	`�??�2?3/]��++�3/++q�10!!!!3��'9�h���s��s�tn�<@$	MZZ
@H 
0


_
??39/3�/]]+���210+!#"&5!3267!�'_n|C��?kR\�F'#���?HiC! �����s@0ZZ
6
'


�
�
�
�
�
�
�
�
u

���@
��H

�
������H
���@	��H�
�

������H
���@	��H�
�

������H
���@��H�
�
�
�
�
�
�
v
�

���@
��H

�
���@���H�
�
�
�
�
�
�
s
d
S
E
4
%


�
�
�
�
�
�
�
�
p
d
R
D
2
$


�
�
�
�
�
�
�
r
�
d
R
D
6
$


h�
�
�
�
�
D
d
�


�
�
�
�
D
d
�
�
�
;
 


�
�
T
d
�
�
�
�
@
4
 


8�
�
�
�
�
p
@@
0
P
�
�
�
0
P
p

�
@
P
p
�
�
�

/
Z 0	`?�2?33/]�^]]]qqqr_rrr^]]]]]]]]qqqqqqqqqrrrrrr_r^]]]]]]]]]]]]]]qqqqqqqqq_qqqqqqrrrrrrrrrrrrrr+^]+]]]]]]++q++r++^]+]]]]]]]]qqqq��9/�103!!!!!�����s��s����h��\�
�

���@H

ZZ	��@H���@
��HdR���@���H��������rdRD2$���������dtRD2$��������rdRF4&��������t���@
��H����@���H��������r`TB4"��������vdVD6$�������t`4Tg����Tt�+���Tt���K4��������pdPD0$7���� @`@@4`����P`����?ZO_ 0�@
	`?�22?33?/]r�^]]]]qqr_rr^]]]]]]]]]]]]]]qqqqqqqrrrrrrr^]]]]]_]]]]qqqqqqqqqqqqqqqrrrrrrrrrrrrrrrr+^]+]]]]]]]]qqqqqqqqqqqqqqqrrrrrrrrrrrrr^]]]]]]]]]]]]]]+qq+�+^]�9/�3/+]�103!!!!!3#������s��s��s�t���;@#[Z
__o��
__?�?�9/]qr�/��2��102#!!5!4&#!!26�{�y=A�Â�;�8�}����n��P<m�^Z�tC���[]e�ye�L�I@-[Zp��	Z_	_	o	�	�			_?3�?39/]qr�/�2]��9/�10#!!!24&#!!26!OA�Â�C'�{�y=�֋���f��'�Z�tC���<m�b]e�ye�����w�P�	M���@,	M[@0p�	Z_	_	o	�	�			_?�?9/]qr�/�2]q��10++#!!!24&#!!26wA�Â�'�{�y=�֋��x����Z�tC���<m�b]e�ye6��]�,r@L\'[.(\''@
H'__o����_" (0( (`(p(�(�(�((("
_?�3/]?3/]q�9/]q�/+]���299//�10%2>7!5!.#"'>32#".'7�_�d8��"8c�[KuW9�a�̈��ST�����̔b�:Vx�7e�Y�W�_2'@S-GV�i:e��������mEp�La1`L/�����.n@,
M
M(���
M(����	M"���@,
M%Z
[��0Z 
0

 _`

*_?�??9/�?�/]�2�q�9/3�10+++++#".'!!!!>324.#"32>�Z�����_
���'e�⎟��W��,Z�Z\�Y,,Z�[a�W(ǥ��h\�����χؖQf����o��EE��on��JK��#7�o���
M���@	MZ
p 0����@[ 0__?2?�9/�9/]]�3/]83]]q��29910++3.54>3!!!3!!"#}CrS/A�����#=_B���}�PEf�Oa�m;����/N9�d��<���ND^����8@@&**4
G� 0G�:`:p:4O)O?�?9/�3q�q�/]q�99/104.#"32>2!".54>7>7>z!>Y7;`E%%BY4;_B$�����ɋIA��Apg`1a�fX�jL1Hj��a�M!M�_`KKo��������F�����s�

 0Eh�aCsT0��:"-a���	
L���@	
L*G
F/$F�����$(H���@	H
"R$$#RR?�?�9/�9/++]�2��9/9�10++2#!32>54.+32>54&#�]��M'CX1;cJ)����B\9<`D��@V3`r:>hQ4R:%	#>Y=��:�u$8'*;$���2#C>�::���@	HF�����$(H���@
	HO??�/++]�3/+10!!����:���:�h�:@KH�����v�9I�F�`pHO	��O?�22?3?�/��]]�99//]3]]]]q3]q3/�10#!#3>7!3#B�(+,����}876�|�ޭ~+��hV/��'�������P��-NH���:)��"
M���@

M	
L
���@	
L" ((H)!)) 

���!  ���@�	H  +�+�+;+[+k+/++�+�+�+�+�+[+{+�+O+++�++?+O++�+�+�+�+�+
+/+?+_+o++�+�+�+�+
+/+O+_++�+�+�+�+	�?+O+o+�+�+�+�+�+ +�+�+�+�++_+o+�++?+_++�+�+�+k?+o+�+�+�+�++/+_+�+�+�+�+�+�+�+�+?+O++ ++:�+�+�++�+�+`++O+�+�+�+@B�+�+o+P+?++�+�+�+�+p+?+_++ +")!?33?3393339933^]]]]]]qqqqqqqqqrrrrr^]]]]]qr^]qqqrr^]qr^]_]]]]]qqqqq3/+8333/83339/^]3�2999910++++"&'!.'!3>7!!##X:��>,��'8+&�&+8'��->���
���,;=7}��KV+�&+VK��8<����75���N9y�7��	
L(���@=	
LH /*F* * *G5;O;H/R%	+R%	Q?2/]�?�3/]]9/�9/�]��99//�9/9�10++"&'732654.#52>54&#"'>32��)�	&4@"PY+NmA?hK)NH=3%�
Ir�X[�i9&C[48fM.8q���,'=*O@2>#
�
!9-<C
!8*KqK&)Jg?8U>('B\:EwW1�]:a���@	
L	
L
�@�		@$(H	��������$(H���@	H	?2?399/++]�2]�+]�210++!!4>7!�����M��:�PVM��&<@<��:���]�&��*@&#%+5+5�:e�
��@	
L
F�����$(H���@	H		���@	H
?2?393993/+8333/++]�29910+!>7!!#!�$)5'~� 5>���2��:�0	(RK��<8����A���:Z@=)�����vg+;K���FOO	??�?�/��9/]3]]]]]]]qq10!!#".'532>7!l��*,3F^A-/'	&+$#);|���ˆP!�F~� ���\:ڶ	
L���@	
LH		����$(H	���@�	H	H@$(H`$4T����;k����t��[4D��4dt����Td���{��dK4����K$����+;kiDt��+���k��4T��{dK$9����Dt��@G+�����p�O���� 0P`	
	?33?3993^]]]]qqq_qqqqrrrrr^]]]]]]qqqqrrr^]]]]]qqqqrrrrrr^]]]]]]qqrrrrr^]]]]qqq�+^]�2/++]�293310++!##!>7!#4>7f���t�(
&�n�uGF@��:��A�HH�A1��tCIG�F:R@F�@$(H
	F�

����$(H
���@	H
O�
?2?39/]q�/++]�2�+]�210!!!!!����}��:�T����1:��P���NR�F:>@F�@$(H	F�����$(H���@	HO?3?�/++]��+]�10!!!F��}��:��|��:����W�QS��P��7NF:�:�@O(8��HXh��@H@HF'B'gw�Wg������FRH����69H����14H����+.H���@	H�	�	�	�	u	c		��@���H		�	�	�	�	�	�	�	�	b	r	T	B	 	0			��	�	�	�	�	�	�	�	P	`	p	2	B	$			�	�	�	�	�	�	�	�	r	d	P	2	B			$	�	�	�	�	�	�	�	r	�	T	d	6	F		"		��	�	�		���@
��Hv	b		���@K��H		�	�	�	�	�	�	�	�	f	v	B	$	4			�	�			�	�	j�	�	�	�		���@>nqHd		&	F	V		�	�	�	�	�	�	�	d	t	F	V	4	"			�	�		���@CFH�	�	d	t	V	D	&	6			9	���@C58H�	�	�	�	�	b	@	P	$	4	�	�	�	�	�	�	`	p		 	@	`	p	�	�		���@
HO?�2?+^]q_qqqrr_rrr+^]]]]]]+]qqqqqqqqrrr+r^]]]qqqqqqqqqqrr+rr+rr^]]]]]]]]]]qqqqqqqqqqqrrrrrrrrr^]]]]]]_]]]]]qq+qqqqq/+++++^]q^]��++^]qr+M�10!!!!:x����:���|���Wh:\R�W��6GW�.P�@�-7����!AGHG	%Y�Y�Y�Y�Y�Y�YrYDYTYdY"Y2YYY�Y�Y�Y�Y�Y�Y�Y�YdYtYVYBY4Y&YYY��Y�Y�Y�Y�Y�Y�Y�YdYtYBYRY4Y"YYY�Y�Y�Y�Y�Y�Y�Y�YFYVYvY0Y$YYY�Y�Y�Y�Y�Y�Y�Y�YvYdYVYDY6Y$YYY�@��Y�Y�Y�Y�Y�Y�Y�YrYDYTYdY2Y$YYY�Y�Y�Y�Y�Y�Y�YfYvYBY4Y"YYY�Y�Y�Y�Y�Y�YtY�YfYDYTY6Y$YYYh�Y�Y�Y�Y�YFYVYvY4YY�Y�Y�Y�Y�YdY�Y�Y@YY$Y�Y�YtY�Y�Y0Y$YYY8�Y�Y�Y�Y�Y[YY$YDY�Y�Y�Y�Y�Y�Y Y@@3YPYpYY�Y�Y�Y�Y_YY�Y Y0YYCUOK<O2!-???3�2?3�2^]]]]]qq_qqqqrrrrr^]]]]]]]qqq_qqqqrrrrrrr^]]]]]]]]]]]qqqqqqqqqqqqqrrrrrrrrrrrrrr^]]]]]]]]]]]]]]qqqqqqqqqqqqqrrrrrrrrrrrrr^]]]]]]]]]]]]]]qqqqqqqqqqq�^]�/]�9/^]q33�2210#".'#!##".54>3234&4&5!>324.#"32>%4&#"326�-^�b,WPC��-�p^�Y+-^�c1ZM?/�k_�Z+�T#8H%/H2�%H9$�aa$H9##8H$^e"{ЖU-I6(5;�aYi[T��y{ΕT.H3
'26���k\T��yk�S!*Z�c��$V�k��#U�mj�T#���d:[��h�:B@
H		F
F�����$(H����	H
��O?�2?3?/++]���3/�103!!!3#����:��|�����S::@#(	
LF�@$(HF�P?2?9/�3/q��+]�210+32>7!!#".5k$=-!;:<#��+U\d9JrN(:�~!9*
���+RuI��:���	�������@�(+H	
�
�
�
�
�
t
F
f


$
�
�
�
�
v
�
b
T
F
4
&


��
�
�
�
�
�
�
T
d
F
4

&
�
�
�
�
�
�
t
6
F
f
$
	
�
v
�
�
�
�
d
6
V
)


��
�
�
�
�
�
�
r
d
R
D
6
$


�
�
�
�
�
�
�
�
d
t
F
V
2
@�$


�
�
�
�
�
�
�
�
t
f
T
F


"
h�
�
�
�
�
�
�
v
T
d
F
4

�
�
�
�
v
�
�
�
d
R
D
6
$


�
�
�
�
v
�
�
d
6
F


8�
�
�
�
�
P
p

 
@
�
�
�
�
�
0
@
`

�
�
�
�

@
P
p
������@
	H
O?�2?33/+]�^]]]qqqqqr_rrr_rrr^]]]]]]]]]qqqqqqqqqqqqrrrrrrrrrrr^]]]]]]]]]]qqqqqqqqqqqqrrrrrrrrrrrrr^]]]]]]qqqqqqqqqqrrrrrrrrrr^]]]]]]]]]]qqqqqqq�^]+]�9/�10)!!!!!�w<;:��|��|��h�:��@	H

�������@(+Hy�	���@�H����&6f�����f�TF$ʖ����fD&6������fv�T&F����Ff�	���������brTF$4�������t�VfD6$�������@�vTdF"2g��������tVfD&�����t�bP$4����@Pp4 7������$4Tdk�����T��Td�+;�������	H�@
O	?33?�22?/+]�^]_]]]]]qqqrrrrrr^]]]]]]]]qqqq_qqqqqqrrrrrrrrrrrrr^]]]]]]]]]qqqqqqqqqqqqqrrrrrrrrrrrr^]]]]qqqqqqqqqrrrrrr^]]]]]]]]]]]qqqqq�+^]q+q�3/�9/�103!!!!!3#�=<��:��|��|�����&�:K���	
L���@#	
LF		F���ROR	?�?�9/�/�q�9/�210++2#!!5!32>54.+���9t�v����E]78\B����MzU-|��M�(':%&7$�F:x���	
L���@*	
LF`$	F�@$(H��
F�		����$(H	���@	H	R

R	?3�?39/�/++]�2]�]+]�9/]]�10++2#!!32>54&+!���<x�v���E`;t������MzU-:�M�(':%LF�%:����:]���	
L���@	
LF��� �
F�		����$(H	���@	H	R

R	?�?9/�/++]�2]�q�10++2#!!32>54&+���<x�v���E`;t�����MzU-:�M�(':%LF4��N(T@1�


	G*FF
OO$@�$y�-O?�3/]]?3/]�9/�/�3/���29/]10%32>7!5!.#"%>32#".4
lP:P3��52O9X`��Iv�i{��F@���o�yGh
de%GiD�=cE%cSL�a8L�Ӈ|͒P?i�����N(a@!G/
o
�
�



G*F�

����$(H
���@	H
O?O&O
&O?�???�9/r�/++]�2��9/]3�10#".'#!!3>324&#"326�B�DŽu��M
���M��v��~:��tnsy ;S2r||ΕSE�m�1:�Tc�vBT��z����a�Z+���:e���@
M	M
	
LF���@ ?F0�R@HR
?3?�9/+�9]/]�3/]83��29910+++	!.54>3!!#";B���B2U?#@z�q���fg\a���M�/KeBOuL&����INHM��P��-�&CvH��P��-�&Hi�@
(&,*%+55+55
�Wd�7��5��	
L0���@.	M!!F�3�339�9�9p9�9�9# F������$(H���@	H#R (P---- --���@H--P?�??99//+]q�33�2/++]3/]3�22]qr�]�99//10++"&'532>54.#"!#535!!!3>32E8S
'1
.J53S<!�煅6��9�wb�U& Em�W�&>.r?iK*-SsE�������� A9.
|p@p�X��AlP,���-�&�t�@&W	%+5+5P��7N$Z@5 FF @
H &GOO
y�-
O@�?2/]�?3/]]�9/�/�2�+�3/�9/10".54>32.#"!!3267R���@F��{i�vI��`Xek5��nfPl

Gy�P��|�ӑL8a�LSc�����ed
K�i?��H��OV�����&�O�f�	���-.H	���@	#$H	@  H	���@H	@H	@H	����H	����H����22H���@-.H%+5++55++++++++����e�&�i��@&%
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
������H
����H
����||H
����{{H
����xxH
����ttH
����qqH
����ppH
����mmH
����jjH
����iiH
����ffH
����bbH
����__H
����[[H
����XXH
����TTH
����PPH
����MMH
����IIH
����FFH
����BBH
����;;H
����44H
����00H
����))H
����$$H
����##H
���@	""H
@  H
���@H
@H
@H
����H
����H
����H
����H
����H+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++55+55����W��M��k:$/��!��	
L���@c	
L)�����vg+;K%Foo��� +F���1@1/R%OO	%R?�?�?�9/�/]�q�99//]]]�23]]]]]]]qq10++!!#".'532>7!!2#%32>54&+d��*,3F^A-/'	&+$#)3��<x�v���E`;t��|���ˆP!�F~� �M��MzU-�':%LF��:l���	
L���@	
LF		
F���!F�

����$(H
���@	H
RR
	?3�?39/��/++]�2�q�9/3�210++2#!!!!!!32>54&+��<x�v������_�E`;t�����MzU-�1:�T��M�(':%LF
d�'��$��@-	
LF�'�'')�)�)p)�)�)F

��

����$(H
���@	H
RP!!!! !!���@H!!
?2?99//+]q�33�2/++]3/]3�22]qr�]�9/10+!4.#"!#535!!!3>32L.J53S<!�煅6��9�wb�U&6?iK*-SsE�������� A9.
|p@p�X�z����&�t?@&M%+5+5���]�'C�����Wh�&\����hF:�@
HF	@$(H
F����$(H���@�	H�
�
�
�
y
k
]
O
=
+

	
�
�
�
�
�
�
�
�

Y

���H

�
���H�
�
�
�
�
}
k
Y
;
K
)



�
�
�
�
�
�
k
{
]
I
;
-



���H�
�
�
�
i
[
M
+
;


��
�
�
�
�
�
�
�
m
}
Y
;
K
)



�
�
�
�
�
�
k@�
{
]
9
I
+
	

�uxH�
�
�
{
�
i
[
M
+
;


i�
�
�
�
y
�
�
k
9
I


�
�
�
�
�
o

�
[
D

+
�
�
[
{
�
�
�
�
O

;

9�
�
�
�
�
�
{
d

+
;
�
�
�
�
o
�
P
/
?
�
�
�
p
�
O
_
0


��	O?3?�3?^]]]]]]]qqqq_qqrrrrrrr^]]]]]]qqqqqqq_qrrrrrr^]]]]]]]]]+qqqqqqqqqqrrrrrrrrrrr^]]]]]]]]]]+qqqqqqqqqqqrrrrrrrrrrr+^]+]]]]]]]]qqqqqqqqqq/++^]��+]�9/�10)!!!!#�����:��{��hT����/!5!5!#"$&546$324.#"32>����'Hb��������Y]�	��	�^��5h�eg�h45h�el�e0��}�}���hm���
�ef����o��EE��on��JK��P���N+!5#5!#".54>324&#"32>t���A�E�Ή�ʉFC�Ί�΃=��~x|�#@Z6>aB"M�}�}/|ΕSR��}y͖TT��z����a�Z++Z���3@\@$H0	Z 0_??3/�/]�3/]+�103!!�����������d��=�F�H@
BH���@	H	F�����$(H���@	HO???�/++]�3/++�103!!H��l��:�����:���&:�A�1&��д250%+5+5����=�&ZC{���&:��@1&o140%+5+5����=�&Zt&����&:�u@
1&530%+55+55����=�&Zi���#5&<�"�	&��̴

%+5+5���Wh�&\C�P�X��/�Ͱ��/�ܰͰ��015!P���P�X��/�Ͱ��/�ܰͰ��015!P���>�4����?�//105!>���������?�//105!��������?�//105!���������T'B��B�?��A@-��O_o����� 0��?��/]]qr�]��21054>733�(�0#?�Cn\O##NRR(���?��+@�� 0���
?��/]�]��210#>5#!�)�0#�Cm]N$$NQS(����B@-��Oo���� 0
��/��/]]]qr�]��210%#>5#!�(�1$� BCn]N##NRR(�?��)@�� 
0

��?��/]�]��210##.=�#0�)���(SQN$$N]mC��?h�]@+���_�p��_����@
	H��?���/+]q�]��2/]qr�]��21054>733!54>733C)�0#�3(�0#?�Cn\O##NRR(���Cn\O##NRR(���?h�U@6	��P��? 0�
�?���/]]r�]��2/]q�]��210#>5#!#>5#!h(�9L�R)�0#�Do]N$H�Q�Do]N$$NQS(���h|@Y��/OOo�o����o��@+.H@ H 0
��/����/]++]q�]��2/]qr�]��210%#>5#!#>5#!h(�0$�R)�0#BCn]N##NRR(�Cn]N##NRR(��v��8@	
���0@
�
��/?�2�29/]��910#5!%����L�M���J�x�����s��U@$	���0@�
�����?�2�2/�2�29/]3���2��91053%%%%#5��U�U��T�����V��d���������d�3A}��:���	
L���@	
L	
L	
L

/3/�]2/10++++#".54>32�/Pk=<jO..Oj<=kP/�=kP//Pk==kO..Ok�1H@*�@��  0@��
	�/33�229/]qr���+M��10!!!!!!� �X�S 1��1��1��)����+?Sg{͹W��@	
Le	
La	
L[���	
L/���@	
L=	
L9	
L3���	
L���@	
L	
L	
L���@	
LJ�P;`;;@�	
���@�11Yr�ccch�Y@HY}�			"��}�}�}�}�}r}d}V})}9}}
}�}�}�}�}�}�}y}k}]}I};}"}}}�}�}�}�}�}�}�}d}t}V})}}
}�}�}�}�}�}�}�}{}m}O}_}0}"}}}�}�}�}�}�}�}�}r}d}V}9}}+}
}��}�}�}�}�}�}�}@�k}{}]}D}6}"}}}�}�}�}�}�}t}V}f}9}+}
}}�}�}�}�}�}�}{}m}T}F}}}h�}�}�}�}�}�}�}t}f}I};}-}}}�}�}�}�}�}�}}}T}d}F})}}�}�}�}�}�}�}�}{}m}I};}"}}}7�}�}�}�}�}�}r}d}V})}9}}
}�}�}�@b}�}�}�}y}k}]}I};}$}}}�}�}�}�}�}�}t}V}f})}}
}Em,TOw6^'���m�T�w�^??���??���3333_^]]]]]]]]]]]qqqqqqqqqqqqrrrrrrrrrrr^]]]]]]]]]]]]]qqqqqqqqqqrrrrrrrrrrrrr^]]]]]]]]]]]]qqqqqqqqqqrrrrrrrrrrrrr^]]]]]]]]]]]qqqqqqqqqqqqqqrrrrrrrrrrrr^]]]]]]]]]]]]qqqqqqqqqq/��^]��+��]�9///883_^]3]��]�10++++++++++++!#3%2#".54>4.#"32>2#".54>4.#"32>2#".54>4.#"32>~�,��:hN-.Ni<<hM-,Nj�)+
**i:hN-.Ni<<hM-,Nj�),+*,;gN--Oi<<hM-,Nj�)+
**�
!Q�jd�U%%T�ej�Q!��EZ56ZDCZ66Z��!Q�jd�U%%T�ej�Q!��EZ56ZDCZ66Z�!Q�jd�U%%T�ej�Q!��EZ56ZDCZ66ZUz��#@��?�9/]�3310!U@2�z����Uz��&���%+5\�L�;@#����@@ H 0��� �?�33�/]+q����10%53^����iGo%����#]�M�;@#����@� 0@`p�� �?�33�/]q�����107553]����#in%��G������&-��-�
%+55�~�,T��/�//10!5!,�R�������(@	���@
??/]83/]8310+3����l��!7���@	
L�!#
����@	
H�/2/�33//+�2��10+4.#"#4.533>32(>I��&1@*nk�,;$`[���.("#. ly�OD�p@G		Z

_

_@`p�O�
/
?

/



	_	??�99//]q]qr�3�2/33/�22�99//10!!!!!#53!�}��:��も���tо������^�8�@'	M)(	
L-((.'+n
 n)8n���@&5H):@&<H@H.��+*�@#'��/4#u  4t@88����H88?3/+]�2?3/�99//]q3�23�2/++���]+�99//]�����9310+]+#!5>=#535#5354>32.#"!!!!!267^
<b�W�J'B.����0g�r`�_=�RB[P����.D+|hkkI�c;�1C]B�p�h\�k:)LkC/MFs}Q�p�;ZE5c^���{�+_lq�H(	
L-���@R	
L(	
L`H!#IVI4G(GG@(-HG@#H�@HW(8GMMG=I,,	,ngH���@	
H�n�n�n�ngnwn�nTn5nEnn���@��H�n�n�n�nwnFnVnfn7n%nnn��n�n�n�n�n�n�n�nfnvn7nGnWnnn�n�n�n�n�n�ndntn�nUn6nFnn%nn�n�ngnn���@���HFnn'n7nn��n�n�n�n�n�n�n�n~nln[nLn+n;nn
n�n�n�n�n�n�n�n�nWn9n*nnn�n�n�n�n�n�nXnhn�nIn:n+nng�n�n�n�nhn�nYnJnn(n�n�n�n�n�n�nznkn\nnInn�n�n�n�n�n�nnTnFn9n+nnn7�n�n�n�n�n@g�ntnfn)n9nnn�n�n�n�n�n�ntnfnIn=n&nnn�n�n�n�n�n�n0n"nnnOg"RNNS�@JgJJg8fO(8��/55/??33/�2?�9///�3/33/�2�^]_]_]]]]]qqqqqqqqqqqqqrrrrrrrrrr^]]]]]]]]]]]]]qq_qqqqqqqqrrrrrrrr^]]]]]]]]]qqqqqqqqqqqqrrrrrrrrrrrrrrr^]]]+]]qqqqqqqqqrrrrrrrrrrr^]]]]]]]]]]+qqqqqq/+�2�^]�9////]]+]++]q��3�22/3�10+++%#"&5#5+#!23733#3267%#".'732>54.'.54632.#"4.+32>�"A0}qhQl�Pn�:�ĂAVX}��" �³S�^>�YO$=,#>V41aL/�����'0EC :O00eQ4�u%GhBKSCeD"
z{�q0M8���=o�_/V'��%1K�<_D=0 !!9V?sy|&
$
";Y�CY6�$9]
��O�=����@ 
M7..3%n$$n?9-3o 9��60�@G-//��O���(s%%/%%%s	@P @��	?3/]q�?3/]�99//]q]q3�23�2/]32�22��3/�9310+%267#".'#73.5467#73>32.#"!!!!�HV

>l�l}�|F
�(Mu(WL�we�lA
��
TJ/J7#G��T��7R�\RP�e:N��t�#�{��D7a�LJR GpQ��CrS/6����!59M�@
	
L	
L4���@	
L.	
L*	
L$���@	
L889667���@599�9977979"D�,�:�_"o"""O���������@0$'H�`p�O��
/?�
8?�1�I�'6??���??�3/]��3/]]qq+q/]��2�2�]���99//]]qr883]3]10++++++".54>32.#"3267#".54>32#34.#"32>�c�W(?f�CKqP/
�B;UEFT<D�.Qt�8`�IK�_77`�LH�_7�E���I"2"%5"#4$!3#
Ey�`��g*+Je:?K����MC
:mR2]}�i..i�}��g))g�����/WrCDrVToDCp)����(6x@(	M(	
L���	
L���@6	
LI&)I/  82&F 0@��%O2222,RP?�?�99//3�2/]33�2�]�9/�10++++%2673#"&=5>74>324&#">FPU�
.?UnF��@D!E%\�uS�\0A{�oD�69".6U:�jk3g_S=#�����D�h@'LnHb��n&�`V�ET(6��EVd��+?��*��@	
L$	
L 	
L���@	
L	H� @6�"""",�A/A���	H� @% 0@;�1�'''

�!�?33�?399//]]99//��/]�2+]�2�99//3��2+10++++!5!!#!&'.53#".54>324.#"32>�{���Z�)��B+U~TP{T+)S~TXQ&�,. +. ��G./([(�����12+c,3�oM�\33[�NK\44\K8J--J79L..L}�*2v�	
L���@A	
L	�@-2,/-/,�---�oO_�4	-�%+/�-0?3�2?3�]q�2/]��+M�9/�2910++#367>73#46767#.'&'%##5!#���	

	�����
�������77-�T8"''$=��H=#')#7��
���U��9q���@C
M
M0(
 (0(@(`(p(?O((5Z;"Z550****''*_)_?�?3�22/3//]]2/��2/�99//]]q3310++267>3!!>54.#"!5!2.54>���]/d�m;��WvH 3a�[[�a3 JwX��=o�e/]���R�ߍf���3�5+es�Je�l99l�eJ�se+���3���f�ߛRX��|H"/B@#I1/I0��//)����?�?�9/�9//]�2��9/10".54>32!32>7.#"k�ƆE,Lfv�?q��Q��@NX.Kt]M"H$Sn��;L]53WJ<"]��ob�}]<O�у��-# <W7*9dL,�*" *����r��H�&y ' ���L @�  ]5]]5?555��T��H�' �'��Ls(@	�@ ]]]5?555��g��H�' �'��L�2@	�@ ]]]5?555�����H�' �'��L�x,@	@ @R RRRR]]]]5]]5?555�b^B@	�/�/��29910#.'5>73!�;H:�RR�:H;�'"bADp*$*pDAb"V���@
@�/��299/�105>73.'#�"bADp*$*pDAb"V�;H:�RR�:H;�#�b^B@	�/�/��29910.'3#>7!5;H:�RR�:H;�#}"bADp*$*pDAb"V���@
@�/��299/�10%>7#.'53+"bADp*$*pDAb"V�;H:�RR�:H;��b^B$@�@�/�/�299��29910#.'5>73!.'3#>7�;H:�RR�:H;�;H:�RR�:H;'"bADp*$*pDAb""bADp*$*pDAb"���&@@�@�/�299��299/�105>73.'>7#.'5�"bADp*$*pDAb""bADp*$*pDAb"�;H:�RR�:H;�;H:�RR�:H;�H�#(@# /�299����299/3�210!!5>73.'>7#.'5� �"bADp*$*pDAb""bADp*$*pDAb"hPX;H:�RR�:H;�;H:�RR�:H;5����-Ca@+
M	M���@
M%%.F@	HE:F���@H3R?)%% O)?O
?�?�3/9/3�/+��+�29/10+++]#".54>3236454&#"7>32.#"32>�Z}�e^�Q#,D`}NLzjo974-'kBn�^)��)2)C5(
"2 3WC/�1iji0u��J@m�O;��~a:VL,��
�"\���$>-+F\a`(.L6Q��+���@
M	M���
M���	M���@+
M
M �0P�������@7]`HO9@`������ Pp��_?�?9/3]]qr^]]+qr�^]293310++++++35!.'!+�p���D���V��/aR9:Sa-�O�9��$@Z	Z 0`/2/?�/]���10!!!��C����9T��H����9D��@)P`@-H[O

�[�		[ 0���@2
H_O_o�/?O���*:H	_/�9+]?9/]q3�9+/]��r�r�99//]�+r105	5!!	!�^��q���K�9���G�8�U9X@
@��?�/]�105!U9����jT���@
�@���@C
Mo

�
�
�
�

/
_

�
�
;�
�
�
�
�
�
p
�
O
o
�
�
�?/9/�]qqr^]q//+839/9333�+�}ć+��}�10##5!3p���7����PH�m�#3Eu�"��@	
L	
L	
L���	
L4'/��G>��$9��*C�@/
?
O
?
O

�
�

/]q33�2/33�2/]���9910++++#"&'#".54>32>32%"32>54..#"326m/VvHg�AESa8FxV1/VxHg�ACP_7K|X0��<j0,cF$;)(:�?+07#%=,*>);`GJ�e;p1T>"4`�UM�c9o�1T>#4a�q`e^f5G))H5�/J35H*'G6_�`�
�/�/�103!!�^j�8��^���@��

�/�/3/�/�104>32#4.#"Dz�bc�{Fg5_�NN�^4t��LL��t�b�l98l�d���9��%Q�#��@!	
L	
L!F  0�����@#&H'&P	P/�?�9/+]q�3/2/10++".'532>54>32.#"+*%>$,<$4e�c(W?$,<%3e��9�5H*W�c6�
6G'�W�c6+97>�@!
M=0	
L<0	
L+���@"	
L,0	
L
M0	
L0	
L0	
L���@$	
L0	
L:@+9%�.�6�*���?/]2��3/3��3/���10+++++++++++"&'&#"5>323267"&'.#"5>323267.K�K�Y'C=:3�T)RPN&-k0D�4 =?F)K�KBm.'C=:3�TS�L244D�4 =?F�)/!�'-

2*��, �&..2*�%H+����@.
M
%5E
*:J/?���@M%(H?@$(H@"H�
00�0	p	 	@	`	p	�	�		��?O�@H/+]q3��2/]q�]q3�2/++]3�+299//]3]92]910+#7#5!!5!33!!́Ձ�G��Z�Ӄ����#���J������d�GPB@

 @��		��?��?/]�/]�9/�/]33�22105!5!5!d�������8��d��13
�@#R�R�

���@$%(H@$(H@H�0@/]3/39=/33/�/++33/�+�3/��+��+���+�+�10	5!1��@��wAZ�������13
�@#R�R�

���@#%(H@$(H@H�0@/2/]39=/33/�/++�3/�+22/��+��+���+�+�105	55!1?�������������7�	#@i	y	iy/�/�/���10]]3	%!	���������{����R��Vd�G�,@ � P�?_�?/]/]�/]�/]107!!d��>��T"����� H	��//��/��10+#47632#"'.'&#"��TR�?K3%
!$	��V�{{?0(4
''#i���� ���H��//��/��10+3#".54>3232765"�Z(g>2%!%����}86'"%)j��%������?�33105!
�%����iH����??��103#ؑ�H�K�����"�������?�?��310!!#�(�i����n�����"������?�?3��105!#
(�%�����%�H"�����??���3103!!�����H�n���%H"������?�?3��105!3
��%��������H'�	�������??�?��23103!!#����i�H�n��n����H'��������?�??3��3105!3#
���%���K�������(�	������?�2?3��3105!!#
��i�%���n���%�H(�	������?�3?3��3105!3!
���%���n������H3�
�@	
�����?3�2??3�2�23105!3!!#
����i�%���n��n���q�j%�	������?�?�3233105!5!
��A�ّ��������H*A	���?2?3����103#3#ّ�h��H�K	��K����j	1���
	�������?�?�?��23310!!!!#�(�i��i�j�ב�"����	3��	
�
��	��?3?�2����310!###���ב���n��n#���j?�
	A	������?�?3?�����3310!!#!!#����ht��j���o��"����j	1�	��

����	���?�?�?33��3105!5!5!#
��i(�q�ב�)�������	4A		
�

����?�2?33����105!###
ܑב%�����n������j?�		A	�
�����?�?3?�����3310#!5#!5!ґ��t��tj�)F��)ޑ�q�H	1��	
	������??�?���233103!!!!����i���H�"�ב�%�H	4�A	

���?3?3�����3103!!33A��$��H�n�#�n�q�H?�

A������?2?�?�����33103!!3!!ّK�$h��H����"���qH	2�	�

����	���?�?�?33�2�105!5!5!3
��i��q�ב�)��%�H	4A	
�

����?�3?33����10!5!333�$�ב%���n���q�H?A	�		��
�����?�?3?�33����10!5!3!3!5!�$K������q�F�������H6�

���	�������??�?�?��2233103!!!!#����i��i�H�"�ב�"���H8�
���������?�?3?3��2��3103!!#3#A�������H�n��n	��K���H	I�A	�����
�	�?3?3?�?����2�23310#3!!#3!!j���t������	�����"	��"�����H8�	�
�@

��������?�?�??33�22�105!5!5!3#
��i���q�ב�K������H;A

@
�����?�?3?33�2���105!3#3#
㑑h��%���K�#�K�����H	I�


A	�����	��?3?�?3?����2�233103#3!5!#!5!A�������㑑�tH�K	�����)ޑ�����j9�
����	�����?�2??�33��33105!!#5!
��i��i�q���"�h��������:�
�@	
����?�22?33����3105!!###
���ב%���n��n������jJ�
	�����	����?3?3�2?���33��3310#!5!3!!#!5j��t�t��t�A��ޑ��"ב���q�H:@	
��	�����?�3??���3333105!3!5!
����A�ّ�"�������%�H:�
	�@	�	���?�33?33����3105!333!
�ב�%���n��n���q�HL@		A�
�	��
��?3?3�2?�����3333103!!3!5!5!A���������H�"�o�����������HL�
	���@����	���
�?3�2?3�2??33�22�2233105!5!5!3!!!!#
��i����i��i�q�ב�"�ב�"������HM�	���@	
�	
����?3?33�22?33�2�2�2�23103!!###!5!33A���ב���H�n��n��n����n�����H]���		������	��
��?3?3�2?3?3�2�2�233�2�233103!!#!5!3!!#3!5!A��ב�t�t��������H�"���ޑ��"	����m�H��/?3310!!��U�m����m��?/3310!!��U�������H���??3310!!��U���	����H���??/310!!�*��	����H���??/310!!��*��	�*g����#'+/37;?CGKOSW[_cgkosw{����������1����������mUE-
y�@
xlTD,xeM5��@
�dL4�qYA)}�@
|pX@(|aQ9	��@
�`P8�u]=%��@!�t\<$�x�|����|�x���������iI1!��@hH0 
����������gck��h�d`h_[W��T\XT�SOK��H�PLHC?G��D@<D�;73��0�840+'/��,($,�#�� � �������{�@<x�|xTHD0, hTHD0, xx ,0DHTh
l����;���wso��tpl/33�22/_]]3339//////////]]]]]]33�22333�22233�22333�22233�22333�22233�22333�22233�22333�222�222/_^]33333�222223/333339/////33333�2222233333�2222233333�2222233333�2222233333�22222�22222103#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#3#3#3#3#3#ghh�hh�gg��hh�hh�hh�gg�`hh�bhh
hh�ahh�ahh�hh�hh�gg�hh�ahh�ahh�hh�hh�gg��hh�hh�hh�gg�`hh�bhh�hh�hh�hh��hh�hh�hh��hh�hh�gg�hhhhhhhhhhhh"bbbbba```````````c```````````c``````aaaaab^^^^^baaaaa``````�bbbbb#`````��b��`��`��a��a�`T����#'+/37;?CGKOSW[_cgkosw{��������������������������������#'+/37;?CGKO3#73#73#73#73#73#3#73#73#73#73#73#3#73#73#73#73#73#3#73#73#73#73#73#3#73#73#73#73#73#3#%3#73#73#73#%3#3#'3#'3#'3#'3#'3#3#73#73#73#73#73#3#'3#'3#'3#'3#'3#3#73#73#73#73#73#3#73#73#73#73#73#3#73#73#73#73#73#3#3#3#3#3#3#3#3#3#3#3#3#ghh�hh�hh�hh�hh�hh��gg�gg�gg�hh�hh�gg�Zhh�hh�hh�hh�hh�hh��gg�gg�gg�hh�hh�gg�Zhh�hh�hh�hh�hh�hh��gg�gg�hh�hh�gg��gg�hh�hh�hh�hh�hh�hhggg�gg�gg�hh�hh�ggghh�hh�hh�hh�hh�hhggg�gg�gg�hh�hh�gg��gg�gg�gg�hh�hh�gg�Zhh�hh�hh�hh�hh�hh�hhgggghhgggghhgggghhgggghhgggggghh"bbbbbbbbbbba```````````````````````c```````````````````````c````````````aaaaaaaaaaab^^^^^^^^^^^baaaaaaaaaaa````````````�bbbbbbbbbbb#```````````��ba```c```c``ab^ba``�b#`C����IMQUY]aeimquy}��������������������������������	
!%)-159=AEIMQ!35#35#35#35#35#353353353353353353353#3#3#3#3#3#335335335335#3'#3'#3'#335335335335#373533533535!355#%355##5##5##5#353353353355##5##5##5#35335335335#3'#3'#3'#3#3'#3'#3'#335335#3'#335335#3735355#5##5#353355##5#35335#3'#3#3'#3�+jjjjjjjjjjjkjkjkkkkkjkjkkkkkkkkkkkkk��kjkjkkkkkk�kk�jj�jj�kjkjkkk��jjkjkkkk��k?k�k�kkkkkkjkjkkjkjkkkkkkkkjkjkkjkjkkkkkk�kk�jj�jjkk�kk�kk�kk�kjkjjj�jj�kjkkjjkj�Vk�k�jkjkkjkjjkjkkjkjjj�jjkkk�kk��"a"a#`!b!b!`````````````�b��`��`��`��^��`j````````�bbbbbbba````````�````````�``````````��aaaaaaaab^^^^^^^^��aaaaaaaa`````````�bbbbbbb"bbbbbbb��````�bbba````�````�`````�aaaab^^^^��aaaa`````�bbb"bbb{uZT!!{�!T�!���/���/���10!!!�7L1�7}��1mi{@
0/�/^]]�10!!i���mi{"@0/���/^]]���10!!!i��L����Pb��h!!�h���L�!	�XV��R���Z�	��7��������L�	L������R����Z�Z�7��9e��	\� 	M���@	M	���@	
H	/3?39=/3/]3�+299=//333310++!#	3	.h�=�h���!!��-�����.)'.@D%T%K![!K[DT
#/���/���10]]]]4>32#".732>54.#"�Fz�^^�{GG{�^^�zFV9b�LL�c::c�LL�b9d^�{GG{�^^�zFFz�^L�c99c�LL�c::c���#��/]�/�102#"'&5467>76jnk5R������S4l�9R46n9������:m64R9)���	/���/���103!32>54.#")��Ex�[[�xEEx�[[�xE��}A[�xEEx�[[�xEEx�)��+"@"	'/�����/�����103!4>32#".'32>54.#")��Q:c�KK�c::c�KK�c:MEx�[[�xEEx�[[�xE��}AK�c::c�KK�c::c�K[�xEEx�[[�xEEx�s�cu"�/���/���10#"'.5476324'&#"3276c%%%V3eK#%HJfgGJL33FF3331HH13}5V%#%H%V5fHJJGgF3333FE6116���y�!-9D�@] $ t $t+{+{D"(?4.(.(.1%+7+>:h:Y:G:::<A+_+o+A@	H+A+A

/��]�99//+^]�3]]]]33�2/��]�99//�3�310]]]]#"'&54676324'&#"3276#"&54632#"&54632327#"&'y������ZZ����ZZZ���ڗ����٘��Z.  --  .�,  //  ,��L��L>b�^�0H��������[��[׀ٙ����ؙ���W ..  --  ..  --����#�_[����)4`@7*/$'!04h4Y4K4=442-_oO-_---
/�99//]^]�3]]]]33�2/�99//�3�310#"'&54676324&#"326%4&#"326327'#"'�������ZZ����ZZ�.  --  .�,  //  ,��0�^�b>L��LH��������[��[� --  ..  --  ..��[_�#��F�s;3F��/��@
H4.4$w##���@MHH;;	H;/4#4;B�
�
p
?
 

9+>���0/43?3O33/^]��]]]]�/��]]]]�10]]]]+]]++]]]+373#'#5.''7.'#5367'7>7"327654'.�B 965�-�-,��,(�1�7:"B?n0�+�(.��P�(�9p6Eu0bb0uE�`cc1u;� �-�;q9><n3�+� 	��	.#�-�3o?>�_�1�(,=20d��b2/aa��c02�P&�/b@>+�+�++"�"�""P'�''�@%(H
/�+�^]2�qr/]3�2/�qr�]�qr9/3�210.'&547>32!!#!5!"327654&'&�7Z#GS,e<vSVHHj�J��#S>>SW;=>B.*PlzS++VSzmQR�F��F�;G,+G>>=T,G;Q���AQF@(1A;NN?  33FF;A1?J7�77B�??/^]�]��]�99/�r�]�]�r9910.'.'.547>323267632#"'.'#"'&547632"327654'&�6%(
 ? .@$
		�TVWvvWTTUzGS�Z>==@XY<>><
			"O-@"'*R*�Qm}VXTTuuWV+ >=X[===>ZW>>;�/(@& 
0
`
p

"@H"O_/]//+3/]/10#"'!727>'#"'&547>7>76 (_E�#%?BX�c$&����}V+B,-�SZB?N9En&8�6_,+i?~BCF_?B��WVc	%%1E[wK`_B?[J;*U/;q9S<�K/@9M?4=C
/)//99//]923/]�10)7>7>7654&5#"&'&547632.'.547>3267>32#"&'.'F��Tl)@4:Z+X-;a)OII]P3N(a<tPPET3V$IPPp>�2+C.=�#!K2dmy;*&StsOP"4&sN&(PNmVb(%)LtvSP<3=-Q}.-L'f��Zy'&@)@Pp�///]�10^]].'.'.'.547632>32b*gL8E+%DFfbN/"�X2U#F)N<Kl ,8e02�fL]Aj8gGFHP6wu$"F^VX-wK`�76nB�����0K/]/]10.'.'.'>7>-qEEt/'xSEj(
#&b<^Q2�P;`�N�]]�5(�o]�H: 9�Pwc;�kM��;�!0@!
@O_o�!

//��]9/9/��]��2103#>54&'&'#"&547632�L�3:0./9@%%Hl9:<?P,.�d�E�UN�;A|;<c(Q	�?b&K6.I<<����"%#"&547632%#"&547632�$&%X3999>Q0*��%#Jj9:;<T--��?e#%'6/L9;�[��>b&J5-L9<�f����"&#"&547632#"&547632%%5�$&%X3999>Q0*��%#Jj9:;<T--���&��D?e#%'6/L9;���v>b&J5-L9<�g�u�#R�#p���@'	
LF�%�%/%?%##_!	F
0

���@H
QO!

S?�?3?33�2?�/+q33/�22/]3/]]�]2�210+5!!!#5354>32.#"39�����螞Hz[0]%-'3����:��|��|�q>iN,
� 1 U�#R�g���@%	
LF�!�!/!?!_F
0���@HQO	?2??3�2?�/+q33/�22/]3/]]�]�10+!!!#5354>32.#"3:���螞Hz[0]%-'3��4|��|�q>iN,
� 1 U���W ��D��س
M���@"
M�@	M���?�/]q�/]�+/]�10++#"'532654#*7>32 GtU-8*K>q
&9#[j�,G3v% Em\��D"@�_����/]�/]q�10!� 1��g�9\��'@		/?

����	H/+�9//��]9/10#>5#53\"�-1l��,G=30T&�#���
T@3/?o@H

�@P`���??39/33�29/q33�22]3/+]q310#5!533354>2���<�g����B�����H��$�� 5���&q��ش	
L%���@<	
L
�(�
����"�"""���`p�?3/]�?�9/]�3/]/��]�99//3310++#".'732654&#"#!!>32�'LpJDeF(�$1:44"3��
O.9[?!�7^D& 8I* @<5>���#@Y+�p�,@���
��??�2/]�]29/�10#4>7!5!p@_>�(Ie<�fCO���YS���H�-�y�%5G^�	
L���@.	
L6 @&�,�
6�I@�
 ;�11C)��C��?�?�9/�99/��]�99//��9910++#".54>75.546324&#"32>4.#"32>y#HoLLoH#*5<E��HhD!"0 7(�%32&##	*!(
/>)	�.O8  9M.'=+Z:Vh2G*5++=$//$��!"-7&�����b���@ 	
L	
L@p��������@"<H@<H�/?_��/]q��2/+�/+]q�10++".'332673TR|Y;#�^TT^�#;Y|�/AKT+g``g+TKA/q�f�#@		o

�

�?�]�]/��9/10#>5#53f"�-1l��+H=30`.�g�\I
@@		
@	/�]9//�]291046733#g,(�-1l�NWu/0`.�W�T @����_@	H/+]�/��10%5!������+�W�U @����_@	H/+]�/��1057!W�����+�����+	_@D&	6	F	)9I	&6F�)9I�0`@p���_@	H/+]3�2/]q�]/�]933]]10#'##53Ɵ�Ӡ�������+	?@(�0`@p����_@	H/+]2�2/�/]q�93310#53373����˟������)@�_��_@	H/+]2�2/�3/]�1053!53��u����������X���@=	
L	
L 0P`@p����@H���
_@	H/+]2��3/+/]]q10++".#"#>3232>73�,YTK"�/VI-ZTI!
�.V�&/& --fX:&/& .-fX:���1@p
�

?p�	�_@	H/+]2�2/�]�]�]10#573#5732���X����"�+�"�+����L@8� P���0`���p�����_@	H/+]��2/�/]]qr�10".'332>73[P~Y4�$2?"$?2"�3W~�0Oh9 2##2 9hO0P�		B$��<��V��_��b��i��r��x��$��$7�h$9�h$:��$<�D$Y��$Z��$\��$��)�)�)$��/��/7�h/9�h/:��/<�D/\��/��3��3��3��3$�h59��5:��5<��7�7��7�7�7�7$�h72��7D�h7F�h7H�h7L��7R�h7U��7V�h7X�h7Z�h7\�h9�D9��9�D9��9��9$�h9D��9H��9L��9R�h9U��9X��9\��:��:��:��:��:��:$��:D��:H��:L��:R��:U��:X��:\��<��<�<��<�<�h<�h<$�D<D��<H��<L��<R�h<S��<T�h<X��<Y��I%U��U��ULY�hY�hZ��Z��\�h\�hVf��Vm��Vq�VVr�FVs��Vx�FV���V���V���[r��[x��\^\_�F\b�F\f��\i�F\m��\s��\v��\y�h\{��\|��\~�h\���\���\���\���\���\��h\��h\��h\�\\��h]r��]x��_f��_m��_q�V_r�F_s��_x�F_���_���_���_��a�a�a_�!ab�!ai�!a|��a���a�^bf��bm��bq�Vbr�Fbx�Ff_��fb��fi��fr��fx��hf��hm��hs��hy��h~��h���h���h���h���h���h���h���h���h���h���if��im��iq�Vir�Fix�Fm_��mb��mi��mr��mx��o��o��o_�hob�hoi�hp���p���q�q��q�q�q�q^�q_�hqb�\qf��qi�hqm��qs��qv��qy�hqz�hq~�hq��hq���q��hq��hq���q��hq��hq��hq��hq��hq��hq���q��hq�\q��hq��hq��hr�r��r�r�hr�hr^r_�Frb�Frf��ri�Frm��rs��rv��ry�hr{��r|��r~�hr���r���r���r���r���r���r��hr��hr��hr�\r��hs_��sq��sr��sx��t���t���uy��u~��u���u���u���u���u���u���u���vr��vx��x^x_�Fxb�Fxf��xi�Fxm��xs��xv��xy�hx{��x|��x~�hx���x���x���x���x���x��hx��hx��hx�\x��h�������������y���{���~���������������������������������������������������������������y���~�����������������������������������������������y���}���~�������������������������������������������������������y���~�����������������������������������������������y���~�����������������������������������������������y���~���������������������������������������������������y���~�������������������������������������������������������������������������������������l���{����3��3��L����������������������������d����������������������������������������������������������������������������������������������������������������������������������������������l���{�����������������������������������������������������������������������������3����������������3��3����L�����������������������������������������L������3��3������3�����������3��������������������������������������������������������L��������������������������������������������������������������������������������������������������������������������������3���������l�����������3����������������������������������������������������������������������������l���{�����f���������������������������������������������������������������������������������������������������������������������������������������������L�����������3�����������������������������������1������f��������f��������������������������������������������������������������������������������������������������������������������������������������������������������������L��3����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������f��L����������������3��f��L�������{3����������������������������������������������������������������������������������������������������������������L��L�������������������������������������������������������������f��L�������l������f�������l���{������V������3��3��L�1�LV`�C]��Vz`	8�.�
o>�	�	#	S	>b	(�		&.	�j	(�			8G	\�	
�+	|{Copyright (c) 2007 Red Hat, Inc. All rights reserved. LIBERATION is a trademark of Red Hat, Inc.Copyright (c) 2007 Red Hat, Inc. All rights reserved. LIBERATION is a trademark of Red Hat, Inc.Liberation SansLiberation SansBoldBoldAscender - Liberation Sans BoldAscender - Liberation Sans BoldLiberation Sans BoldLiberation Sans BoldVersion 1.07.4Version 1.07.4LiberationSans-BoldLiberationSans-BoldLiberation is a trademark of Red Hat, Inc. registered in U.S. Patent and Trademark Office and certain other jurisdictions.Liberation is a trademark of Red Hat, Inc. registered in U.S. Patent and Trademark Office and certain other jurisdictions.Ascender CorporationAscender CorporationSteve MattesonSteve Mattesonhttp://www.ascendercorp.com/http://www.ascendercorp.com/http://www.ascendercorp.com/typedesigners.htmlhttp://www.ascendercorp.com/typedesigners.htmlLicensed under the Liberation Fonts license, see https://fedoraproject.org/wiki/Licensing/LiberationFontLicenseLicensed under the Liberation Fonts license, see https://fedoraproject.org/wiki/Licensing/LiberationFontLicensehttps://fedoraproject.org/wiki/Licensing/LiberationFontLicensehttps://fedoraproject.org/wiki/Licensing/LiberationFontLicense����	

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a�������������������	����������bc�d�e�������f����g�����h���jikmln�oqprsutvw�xzy{}|��~�����

��� !"��#$%&'()*+,-./012��3456789:;<=>?@A��BCDEFGHIJKLMNOP��QRSTUVWXYZ����[\]^_`abcdefghijklmnop�qrst��u�vwxyz{|}~��������������������������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,-./012��34���5��������67��89:;�<=>?@A�BCDEFGHIJKLMN�O�����PQ���R��STUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~������������������������������������������������������uni00A0uni00ADuni037Euni00B2uni00B3uni00B5uni2219uni00B9AmacronamacronAbreveabreveAogonekaogonekCcircumflexccircumflex
Cdotaccent
cdotaccentDcarondcaronDcroatEmacronemacronEbreveebreve
Edotaccent
edotaccentEogonekeogonekEcaronecaronGcircumflexgcircumflex
Gdotaccent
gdotaccentGcommaaccentgcommaaccentHcircumflexhcircumflexHbarhbarItildeitildeImacronimacronIbreveibreveIogonekiogonekIJijJcircumflexjcircumflexKcommaaccentkcommaaccentkgreenlandicLacutelacuteLcommaaccentlcommaaccentLcaronlcaronLdotldotNacutenacuteNcommaaccentncommaaccentNcaronncaronnapostropheEngengOmacronomacronObreveobreve
Ohungarumlaut
ohungarumlautRacuteracuteRcommaaccentrcommaaccentRcaronrcaronSacutesacuteScircumflexscircumflexTcommaaccenttcommaaccentTcarontcaronTbartbarUtildeutildeUmacronumacronUbreveubreveUringuring
Uhungarumlaut
uhungarumlautUogonekuogonekWcircumflexwcircumflexYcircumflexycircumflexZacutezacute
Zdotaccent
zdotaccentlongs
Aringacute
aringacuteAEacuteaeacuteOslashacuteoslashacuteScommaaccentscommaaccentuni021Auni021Buni02C9tonos
dieresistonos
Alphatonos	anoteleiaEpsilontonosEtatonos	IotatonosOmicrontonosUpsilontonos
OmegatonosiotadieresistonosAlphaBetaGammaEpsilonZetaEtaThetaIotaKappaLambdaMuNuXiOmicronPiRhoSigmaTauUpsilonPhiChiPsiIotadieresisUpsilondieresis
alphatonosepsilontonosetatonos	iotatonosupsilondieresistonosalphabetagammadeltaepsilonzetaetathetaiotakappalambdanuxiomicronrhosigma1sigmatauupsilonphichipsiomegaiotadieresisupsilondieresisomicrontonosupsilontonos
omegatonosuni0400	afii10023	afii10051	afii10052	afii10053	afii10054	afii10055	afii10056	afii10057	afii10058	afii10059	afii10060	afii10061uni040D	afii10062	afii10145	afii10017	afii10018	afii10019	afii10020	afii10021	afii10022	afii10024	afii10025	afii10026	afii10027	afii10028	afii10029	afii10030	afii10031	afii10032	afii10033	afii10034	afii10035	afii10036	afii10037	afii10038	afii10039	afii10040	afii10041	afii10042	afii10043	afii10044	afii10045	afii10046	afii10047	afii10048	afii10049	afii10065	afii10066	afii10067	afii10068	afii10069	afii10070	afii10072	afii10073	afii10074	afii10075	afii10076	afii10077	afii10078	afii10079	afii10080	afii10081	afii10082	afii10083	afii10084	afii10085	afii10086	afii10087	afii10088	afii10089	afii10090	afii10091	afii10092	afii10093	afii10094	afii10095	afii10096	afii10097uni0450	afii10071	afii10099	afii10100	afii10101	afii10102	afii10103	afii10104	afii10105	afii10106	afii10107	afii10108	afii10109uni045D	afii10110	afii10193	afii10147	afii10195	afii10050	afii10098WgravewgraveWacutewacute	Wdieresis	wdieresisYgraveygraveuni2010uni2011	afii00208
underscoredbl
quotereversedminutesecond	exclamdbluni203Euni2215uni207FlirapesetaEuro	afii61248	afii61289	afii61352uni2126	estimated	oneeighththreeeighthsfiveeighthsseveneighths	arrowleftarrowup
arrowright	arrowdown	arrowboth	arrowupdnarrowupdnbseuni2206
orthogonalintersectionequivalencehouse
revlogicalnot
integraltp
integralbtSF100000SF110000SF010000SF030000SF020000SF040000SF080000SF090000SF060000SF070000SF050000SF430000SF240000SF510000SF520000SF390000SF220000SF210000SF250000SF500000SF490000SF380000SF280000SF270000SF260000SF360000SF370000SF420000SF190000SF200000SF230000SF470000SF480000SF410000SF450000SF460000SF400000SF540000SF530000SF440000upblockdnblockblocklfblockrtblockltshadeshadedkshade	filledboxH22073H18543H18551
filledrecttriaguptriagrttriagdntriaglfcircleH18533	invbullet	invcircle
openbullet	smilefaceinvsmilefacesunfemalemalespadeclubheartdiamondmusicalnotemusicalnotedbluni266CuniFB01uniFB02uniF005middotcommaaccentfoursuperiorfivesuperior
sevensuperior
eightsuperior
cyrillicbrevecaroncommaaccentcommaaccentrotategrave.ucacute.uc
circumflex.uccaron.ucdieresis.uctilde.uchungarumlaut.ucbreve.uc
���
LNDFLTcyrl$grek.latn8��������
`nDFLTcyrl&grek>latnJ��MKD SRB ������kern�g�� .L^l��\bpzp����8Zp�Zp���j�����,v��FPz�������*d�6\�����	@	�

�
�
�
�
�
�2\���,Nh����
&
T
j
�
�.<FPz�����$��<��V��_��b��i��r��x��	��7�h9�h:��<�DY��Z��\������$����7�h9�h:��<�D\����������$�h9��:��<��������$�h2��D�hF�hH�hL��R�hU��V�hX�hZ�h\�h
�D���D����$�hD��H��L��R�hU��X��\��
����������$��D��H��L��R��U��X��\���������h�h$�DD��H��L��R�hS��T�hX��Y��%����L�h�h����	f��m��q�Vr�Fs��x�F���������r��x��^_�Fb�Ff��i�Fm��s��v��y�h{��|��~�h�����������������h��h��h�\��h
f��m��q�Vr�Fs��x�F�������������_�!b�!i�!|������^f��m��q�Vr�Fx�F_��b��i��r��x��f��m��s��y��~������������������������������������_�hb�hi�h������!������^�_�hb�\f��i�hm��s��v��y�hz�h~�h��h�����h��h�����h��h��h��h��h��h�����h�\��h��h��h�����h�h^_�Fb�Ff��i�Fm��s��v��y�h{��|��~�h��������������������h��h��h�\��h_��q��r��x��������	y��~��������������������������������y��{��~�����������������������������������������������
y��~�����������������������������������y��}��~�����������������������������������������
y��~��������������������������y��~�����������������������������������
y��~��������������������������������������y��~��������������������������������������������l��{���3�L��������������������d���������������������������������������������������������������������������������������������������l��{���������������������������������������������������3��	��������3�3��L���	�������������������������
�L����3�3����3������3�����	����������������������������������L��������������������������������������������������������������������������������������3�����l��������3�������������������������������������������$������l��{����f����������������������������������������������������	�������������������������������������������L������3��������������������������1����f�����f
������������������������������������������������������������������������������������������������������������L�3����������������������������������������
������������������������������������������������������������������������������������������������������������������������f�L����������3�f�L����{3���������������������������
��������������������	�������������������������L�L
��������������������
��������������������f�L����l����f����l��{������V������3��3��L�1�Lg$)/3579:<IUYZ\V[\]_abfhimopqrstuvx����������������������������������������������������������������������ϒNPK
!<�&����Echrome/pdfjs/content/web/standard_fonts/LiberationSans-BoldItalic.ttf0FFTMh��7�GDEF'�L&GPOS��0��GSUB�<�KtPOS/2�>��`cmap��w��cvt ]�d� Bfpgms�#��gasp	<glyfڐ�%��xhead�>�i<6hhea�|t$hmtxӠ��
�kernbc^O�0Dloca�� dTmaxp��� nameeZs�t�post�!ѵ�T�prep��q�'�}_<����ϒN1�T��	=>�NC	+�T��	d��RT~/Z]����3�3Z�f	��Px�1ASC!!��Q3>�`���:� �D�9�O��sAs��p�1���J��LX�}9���89.9��sUs!s��ss��ss_s�ss0�Y��|�{�|�������$�d�$V$�$9d�$9$s�$�$�$�$9dV$9d�$V���wV���V��V������9o��R�Us���!s
�#s?�:s?�J��#9#9�$s#9##�#�?���;#s�V�Usn9cs��s����=��e�v�sLs��sHs'=�s�����>�hsH�K�>k��3�dW�G�`�����sj9~�X�b�fs&�b�b�n����������������QV$V$V$V$9$9$9$9$��$9d9d9d9d9d�~9#�w�w�w�wV�V$�#s
s
s
s
s
s
*s?s?s?s?s?9#9#99#�:�#�?�?�?�?�?dE�*�U�U�U�Us����s����s
��s
��s
�ds?�ds?�ds?�ds?�$�:��:V$s?V$s?V$s?V$s?V$s?9d�9d�9d�9d��$�#�$�#9$9��9$9#9$9#9��9��9$9#B$s#s9�$�$s#s#�$9#�$9���$+#�$�#��9�$�#�$�#�$�#��$�#9d�?9d�?9d�?g�:�$#�$���$#VsVsVsVs���K���V����w�U�w�U�w�U�w�U�w�U�w�U��9cV�s��V�������������9#s���s
��*9#�*Vs���V�L�������J���j�D�:�����������?����9q���9$���$�$_��V$���$9d9$�$V���$�$B��9d�$V$�����V��`V����?��9$V��9��%9C�f�9���sl�7��1�%vl9C�%s����si�1�?�C��BLO@E9�f�@��L/b�79C�f�?�f�7V$V$��$�dV9$9$s���V%���$�$k7�#���$�$�$�oV$k��$�$�$�$���$�$9d�$V$�d��k7?NV���$���$�$��$�$�*V#��s
�]�'E�<s?��U�U$����#�#�?�#��s?#s��?s��+U�jU{U�M�&�&j�#���s?s?�#�#�?s9#9#9�$���?%�#$�Us���U9d�?�$�#��9c��9c��9cV�s���8�8s,����k��9�9�9��9�����s�s2�Z�H�����I�!�O���V�T+\s��s���;s�!$	+*�%]�b��������������?�+�����}d|�Q���1�dDd;�ddWdW���d�"�����������������������������������������������������������������������������������������������������������������g���{��m�m���������+�����)�)�s+�k�UF�Q@;@<�fB��J�J�]���P�F�U���]��X�@�-���o:�����>~�����~����_s���    " & 0 3 : < > D  � � �!!!!"!&!.!^!�!�"""""""")"+"H"a"e###!%%%%%%%%$%,%4%<%l%�%�%�%�%�%�%�%�%�%�%�%�%�%�%�&<&@&B&`&c&f&l����� ������~����r���      & 0 2 9 < > D  � � �!!!!"!&!.![!�!�"""""""")"+"H"`"d### %%%%%%%%$%,%4%<%P%�%�%�%�%�%�%�%�%�%�%�%�%�%�%�&:&@&B&`&c&e&j����������G�/�����v�����������n�����������������������}�y�!����������5�2�*�)����������D�7�(�J�I�@�=�:�7�4�-�&�����������������������ܾܹܶܮܢ�O�L�K�.�,�+�(���� bcdefghijklmnopqrstuvwxyz{|}~��������������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,-./0123456789:;<=>?w<
	

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a��������������������������������pcdhv�nj)tiB��=qDEfu7:9�@kzv��bm<@A8l{���
���8 %��w����������������KRoNOPxSQL@EYXUTSRQPONMLKJIHGFEDCBA@?>=<;:9876510/.-,('&%$#"!

	,E#F` �&`�&#HH-,E#F#a �&a�&#HH-,E#F`� a �F`�&#HH-,E#F#a� ` �&a� a�&#HH-,E#F`�@a �f`�&#HH-,E#F#a�@` �&a�@a�&#HH-, <<-, E# ��D# �ZQX# ��D#Y ��QX# �MD#Y �&QX# �
D#Y!!-,  EhD �` E�Fvh�E`D-,�
C#Ce
-,�
C#C-,�(#p�(>�(#p�(E:�
-, E�%Ead�PQXED!!Y-,I�#D-, E�C`D-,�C�Ce
-, i�@a�� �,����b`+d#da\X�aY-,�E����+�)#D�)z�-,Ee�,#DE�+#D-,KRXED!!Y-,KQXED!!Y-,�%# ���`#��-,�%# ���a#��-,�%���-,F#F`��F# F�`�a���b# #���pE` �PX�a�����F�Y�`h:-, E�%FRK�Q[X�%F ha�%�%?#!8!Y-, E�%FPX�%F ha�%�%?#!8!Y-,�C�C-,!!d#d��@b-,!��QXd#d�� b�@/+Y�`-,!��QXd#d��Ub��/+Y�`-,d#d��@b`#!-,KSX��%Id#Ei�@�a��b� aj�#D#��!#� 9/Y-,KSX �%Idi �&�%Id#a��b� aj�#D�&����#D���#D����& 9# 9//Y-,E#E`#E`#E`#vh��b -,�H+-, E�TX�@D E�@aD!!Y-,E�0/E#Ea`�`iD-,KQX�/#p�#B!!Y-,KQX �%EiSXD!!Y!!Y-,E�C�`c�`iD-,�/ED-,E# E�`D-,E#E`D-,K#QX�3��4 �34YDD-,�CX�&E�Xdf�`d� `f X!�@Y�aY#XeY�)#D#�)�!!!!!Y-,�CTXKS#KQZX8!!Y!!!!Y-,�CX�%Ed� `f X!�@Y�a#XeY�)#D�%�% XY�%�% F�%#B<�%�%�%�% F�%�`#B< XY�%�%�)�) EeD�%�%�)�%�% XY�%�%CH�%�%�%�%�`CH!Y!!!!!!!-,�%  F�%#B�%�%EH!!!!-,�% �%�%CH!!!-,E# E �P X#e#Y#h �@PX!�@Y#XeY�`D-,KS#KQZX E�`D!!Y-,KTX E�`D!!Y-,KS#KQZX8!!Y-,�!KTX8!!Y-,�CTX�F+!!!!Y-,�CTX�G+!!!Y-,�CTX�H+!!!!Y-,�CTX�I+!!!Y-, �#KS�KQZX#8!!Y-,�%I�SX �@8!Y-,F#F`#Fa#  F�a���b��@@�pE`h:-, �#Id�#SX<!Y-,KRX}zY-,�KKTB-,�B�#�Q�@�SZX� �TX�C`BY�$�QX� @�TX�C`B�$�TX� C`BKKRX�C`BY�@��TX�C`BY�@�c��TX�C`BY�@c��TX�C`BY�@c��TX�@C`BYYYYY-,Eh#KQX# E d�@PX|Yh�`YD-,��%�%�#>�#>��
#eB�#B�#?�#?��#eB�#B�-,z�E#�-�P/�@2����o�@����@��� ��=����=�=�U�=Uݸ�</A @��@F����0��G�Od�`����@ �� �@�`�p����_��<�@��2���/�?�O���@�&)F�/�?����@�F�������5�P&��'��%��%�`�����K�������&��&��&��&��_�o����@�F��G2���m���vP&uP&tP&sP&/y?yOyqopGoGU3U3U��m)ml�aP&`_2_P&cGb�b bF)b9bIb^G][�\[3[G2@AU2UU2Uo?�Y_S@S(,F@S"F@SFRQ(QOPOO)OYOiO��Gu��@=PN�M�M�MMGLG2KG2JG�IIGHG2G2UU2UU����@=@ 0p?_/Oo���?_�o_����TS++K��RK�P[���%S���@QZ���UZ[X��Y���BK�2SX�`YK�dSX�@YK��SX��BYststust+++++++t++t++++tss+st++++++sssu++++++++s+t+++++ss+++++++s++++++s+s+tu++++ss+st++++++t+st+st+++sssss++++t+++su+++++++sssssssssss��}�y�:w�������W'��������� ,"-������m����������B���?�]%��u��\1����D�s��������N�p��p�����������`��Hj����g�a��A�o�h�����S}��D��,,,,��`.T`�(V��		0	Z	�

� ���
`
�0��T��V�P�b�p�T�&>�P�H�D��>�<��� � � � �!�"�"�#�$&$�%^%�&H&�'@'�(t))v*2*�+X,,�-<-�.�/D/�0&0�1�2Z2�3&3�4b4�5\6D6�77�8�8�99�9�:^:�;0;�;�<�<�=@=�=�>.>�>�>�??�?�?�?�@@$@@@�@�@�AA.AHA^AvA�A�B.BFB^BvB�B�B�CC�C�C�DD"D:D�E2EHE`ExE�E�E�F�F�F�F�G
G$G<GRGhG�HH6HNHfH~H�H�H�I�I�I�I�I�JJ�J�J�K
K"K:KPKdK|K�K�K�K�K�LL L8LXL`M.MFM^MvM�M�M�M�M�M�NN.NFN^NvN�N�N�N�N�OO�P>PTPjP�P�P�P�P�P�QQHQjQ�Q�R0RHR`R�R�SS,SDS\S�S�S�T
TdT|T�T�T�T�T�UU�V�V�V�V�V�WW*W�X�X�X�X�YY Y6YNYfY~Y�Y�Y�Y�Y�Y�Z
Z"Z8Z�[[6[N[f[~[�[�[�[�[�\\,\@\X\p\�\�\�\�\�]]]2]J]�^^,^V^n^�^�^�^�^�^�^�_
_._D_�_�_�`&`t`�`�aaaaHava�a�a�b4bNbVb^b�cccc$c�c�c�ddd&dddld�d�e(e0e8e�e�f�gjg�g�g�g�g�g�hh�iri�jfj�kdll�l�mfm�n�n�o�o�pRp�qXq�rVr�s�tzu@vvv6vNvfv~v�v�w0wHw�w�w�w�w�x~yy~|T|`|x}}}r}z}�~^~f@Ā<�T�ځ@�H�P�X�������ƂD����t�􄊅.����h�쇘�����L��t�|�x��
�<�ƍ8��j�r��
���"�0�8���������������*�–Ζ藺������Ș�虔�4�<�T�`�x�@����<�������Ȝ���,�B�^�z���t������l�����������z������Ч�(�^�����©*���P��@����������ʲ�8�t���в��,�t������ܵ�N�h���L�d����Z�x�����R�����л�&�@�b�����Ȼ��:�`�������@�z���ؾ�@�p���ؿ�B�v����$�\�����R������L–���
�"�:�R�jł�^��,�L�hɐɞɬɺ������^ʒ����l�̜�L���^ξ�DϘ����TДк���4�bф���bҒ��B�dӄӞӼ�����\ԈԼDdU.�/<��2��<��2�/<��2��<��23!%!!D �$��hU��D�O���@)RR� 0@P`�����@,H��lmX�	�	�	�	�	�	 	]]]]]]]+??��0//]q+]q/+<�++���+���10#!!��&��4 4��������/@o�o��	p	]]?3�2/]q��]�10#!#!`�H���H����Aps�@*	

R�@R		�@L

	

@
H!�


�

�
�
�
�
O��

�
/3?399//]q]q33�2233�22/]]33�+2299//��+�+ć+�+�10��������������������]]3##!##53#533!33!!�E�R�P��P�O��F��T�R3T�T��oH5F^�����}��}�L��������L���h��1:E�@~00AH@H$;%656E6%555E5+2!,-56 A@ r�)9�
2o'&/&&G;o
@-56t,Au !?33/3�2?33/3�29/3���]2��9/]3]]]]q�+}��������������]]]]++10]#7".'%3.54>372.#4&'2>"8L�҆!p R�oE`[IM�f;-Nhu};nY�_4�� *1C\�k:��X_I2]G*�r'9%C3R9�o�p;��-X�R*XY�?[xLLxZ?'~�.Pm>9,=%��>Wt�MQ��-E+!K3Ep����.H]a5@�`^a^�_`_`^_a_a_4Q�A�4,��"�'4��[�['[[c%�
�I�/�T�<_�`fc'c�cc�cWc6cucWcGc6c�c�c�c�c�c'ccc[cKc<c*cc�c�c�c�c�c�c�c�cxcicYcJc;c(cc
c�c�c�c�c�c�c�c�czckc\cIc9ccc�c�c�c�c�c�c�c@�xcicZcKc<c)cc�c�c�c�c�c�c�c�chcYcIc:c(cc	c��c�c�c�c�c�c�cycjc[cLc9c'cc�c�c�c�c�c�c�cycic[cMc?c+ccc�c�c�c�c�c�c�c}cYcIc;ccci�c�c�c�c�c�c�c{cmc]cOc$cc�c�c�c�c�c�c�c@Q�cic[cMc=c)ccc�c�c�c�c�c�ctcfcIc4c ccc9�c�c�c�c�crrr_r_r^]]]]]]]]]]]]]qqqqqqqqqqqqqqqrrrrrrrrrrrrr^]]]]]]]]]]]]]qqqqqqqqqq_qqqqqrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]qqqqqqqqqqqqqqrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqq^]]]]qqqqrrrr]]]qqrr??�??������]]�/]�����99//��+�}�102#".5467>"32>7654&2#".5467>"32>7654&#3"?pU1

cw|5>lO.
_ry4:3+,*:5-2`?pU1

cw|5>lO.
_ry4:3+,*:5-2�
�>�� IvU%]0~�["$MxT%[.��W�9gT5Y%IE9fPmMJA�| IvU%]0~�["$MxT%[.��W�9gT5Y%IE9fPmMJA�%�1��B�;O_@�6I*DfCBBA
A;	;33<L	
+
	

33/X?XOXKAJE4A''"*FFSSXPIP


XI!"-a aKJ/?O�fSUS[A�Y'A44!9<FFS<O9*P19!!9[R?�?9/���99]93]]]]]/]�q��2��9/]�9333]93]]]]10]]]]]]]]]]]]]]]]]]]]]4>7.54>32>73267#"&'#"&2>7.'>54&#"1@r�\
8l�hK|Y0L��] )3!Iq0�>GO*(]39'!?+[�@F[{T���"B<4"<3((P>'1F�

?lN,C:'E3bW�pV 2;A!R�f9 A`@U�eP&*RQS,N�jS8okc+"�<9."�
-cc`+.@V9+G1g,E .8H0363N����@*6Fv���&6FfvVfv�����@$'HoFVv����
���@�HF6&��������vfVF4$�������vfVF6&��������vfV&������fVD6&��������tdTD4$������@��vdTD4$��������p`P@0$i��������p`TD4$��������tdTD4$�������dTD4$9�����rrrrr_^]]]]]]]]]]]]]]qqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]_]qqqqqqqqqqqqqqqqrrrrrrrrrrrrrrr^]]]]]]]]]]]]]qqqqqqqqqqqqqqrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqq?�/+^]�+]qqqr10#!��I��J�Wj�=@'

H
�
�
�


??/]]��2]]9/3]10]]!&547j����HK��IKA�Á���������lɳL-	o�L�Wn��@�+@.1H�/?������		HT
������td����tdTD���D4$�����td������4$����td���k$j����K4$���@4�d4+���k;$:��_rr^]]]]]]]]qqqqqqqqqrrrrrrr^]]]]]]qqqqqqqqrrrrrrr^]]]]]]]]qqqqqqrrrrrrrr^]]]]]]]]]]q??/^]��2_^]]9/3]q_q+r10$4.'!
�&8$	KVA��W�drO���J����������rX�m�3@!
�
�
`
�


�
?�/]]q�]10]]]7''7'73>�D��������D��oh�=�y��{�=�h}���a@<_��		�@
0�P����	�?3�2�]q+�9/_^]3�2�^]+�10#!5!3!��q���9�h����h�����1S@�


���@H
	
�XYX�]+/��]10//]+/+<�+}��]2910%#>7#![
$.7 �#@4$�;!BCn]N##NRR(18�o�#@P�/?O�/�/]�]107!8//���.�17@
����@H�XYX+?�0//]+/+<�+}�103!.;!;1��������4@�??�/��+��}�10]3LK�)��U��_�58@#	o&&71o/s)s?�?�/]��]�10]]]]2#".5467>2>7>54&#"�S�j>L]jtz>^�e4N_mty�7[L>IO<ZF6
 6�-l��?�\�Θg@5s�~=�[�Ԙc9�3/p��^�;n{/o��W�;-O9!!�
f@3q_o@�s��X+?�2?39/]]q/F�(
+</+<+H�+}�10]]]]37!7%!!!+]���/��C,�����f���^�*�@	)o�

,%���@gHt(e(Q($(4(D(((�(�(�(�(�(�(�(t(R(b(E(($((�(�(�(�(F(4(%((@H
(s(s?�?�99/+3]]]]]]qqqqqqqqqqqrrrrrr�+2]�]��10]]#7>54.#"%>32! %0z���sU2+<#'D9,��R|�nq�j22Vq�{j&�,�S�td\W]e;(7!*G5;Q�Z11Z�PN�rd\Y\c8���\�;K@,))!::+!o6o��=,+:s;;1s&s?�?�9/�/3�2�]���9/910]]2>54&#"%>32#".'%32>54&+7/iW9MP[x��^��cf�o:0]�V?fH'D��~|�x?#9O18S7lpk,=0WGHSZ^4^�V)/YQL~]:
/LhBW�yG6a�O&2C) 8M-]]���P�
w@HRq0��/t��X+?3?339/33�2/]33/�]]q/+<�++�������10]!!7!3>7!j6��6��)�r��*��!#�X������o��273+10������,t@#o.+fv�W���@-Hv���Ue,(sss?�?�9/�/3]�2]]]q+qq2�]2/]�10]]!!>32#".'%32>54.#"!<O+��W1>N0N�d9N�׉p�qA0H3CeC!3E(Ab'���� 6j�ewƎO5]�K-*?*,Mf9:T6.-_��~�"4g@D


+oo*6'#o0t��P`&s
&s?�?�9/]]q�/]�2]�]2]�]�10]]]]]".54>32.#">3232>54.#"k�o9_��E�pM��JBs�'5�gO�]3M���b[3V>#0A&-XE+E��w�Y��J}_)IF��HY1_�[oȘZ�i|(MoG4L1"Cf���2@
@	o

@H	s?�?/+2/��]2]]10!!67!�,T��ydM��#�����f������3���p�'9Ma@<	&	"5--
oIo--O5o#?o@H 
:u((Du0u?�?�9/�99/+]����]���99]]10]]]]2#".54>75.54>2>54&#""32>54.�e�n;+QvJ3U<"F�Ћw�s8Ci<\_E��<<N/TX+J7'B$<[= 2M42WA&/L�0X}MCw[=
6Md=a�yE9f�R]�Y4)�mW�a5��+BO$PX4P7 A3 �&Ea;+I5"CcB%F7"0��j�+BS@4*	!
o,,,D6o
$##;to's1s?�?�9/]q�/]3���]q2�10]]]]#".54>32#".'%32>4.#"32>7>1CLY6R�[1Q��x_�uB&���fU�pJTN,[UI@/A(6ZA%/B'&K@0�%8&9g�VqđS>y�rDIL"����M(S|T0KR'e��*I7(OtK-G12P;,YB
\@�������@H��XYX�	]+??��0//]+]/+<�+}������10!!�7 7�N7 7�������B
j@$	


������@H	

��XYX+?�/��]10//]+]/+<�+}�������]2910!#>7#!�7 7�
$.8 �#@4$�7 ���RCn]N##NRR(|}�h@8R�R�?_ /]]]3/]39=/33/3���+��+���+�+�10	|��AB�����{#~)L@7	�����?O�@H�0p @`p��/]q�/+]q�/]3�2105!5!{��J�����|}�h@8R�R�_ ?/]2/]]]39=/33/��2��+��+���+�+�1075	5|@��}�DE�y�����%)f@8()R&'(')�&'&')'&H&&!G+ + +' !
'�&_XYX+?�?��99/3/]q��9/��/+<��+��+�10]2!>7>54&#"%>!�h�zCAi�C"<.��
3I`:E[6`en���\����4!4�/Z�VV�hR%*0:'FgQD$+B=>'?S]d(X�c6�j�����w�_x�@9	P722	l�#'�
`"!
p


T/HHT�///zzz�z�z�zz���@H	
H;�T
T*o�e�"B4�[B�M�z�z�zzoz_zOz?z/zzzF^]]]]]]]]qqq/�?�99//^]��3�2/^]�+]r3/]�9/99//]333�2�10^]]]]#".5467##".54>3237332>54.#"32>7#".54>$324.#"32>7>wL��c*G2F[m;_L D��vf� '�u/;oX5R�욅൉]/"Ei��nV���=><���n�ܱ�Z-?w����)�]�y8N07\H6#ad0_TE	��`+A+&,VC*Dn�Esܬi]Q��O|%2*L��p~؞Z?r���rW��vT/*9 z"@19g���k�ܴ�Fv���/O9 )F]jp5r�.Y�T+*&��3��@w���JZjH*:

R
]R
c�


+


_
p@ �O@��]]qr]]qqq?3?29/�10�]/]9/]]�+�+ć+�+�������10]+]q!!!!.5!7����\�K ��h����
-/)
1<@�r${�#.�@e[*[�o0.$##$$[0@P�._$_#_lmX 0�0�0�0p000@0�0]r]]]]]q+?�?�9/�910//]q/+<�+}ć���]q]���910]]!2#!!2>54.#!!2>54&#!6H��~=.UxIGlI%e���?�&W{M#!?];�ӼH_�[-�����,TyMIsU98Qe9}�c)I0K4,;#�52T@Z`d����+G@-

""!- -@-[`'__@-`-P-�-]qqr?�?�/]q�q�2]�2]1032>7#".54>32.#"�*S{RN�gO�+x�Є��I4c����NJR��1LjD~��C=T�^2*H]3uL�e;\��{|࿛l;@l�NG,SA'[��$��y@S	[ �o�P` \


0
@
P


�

__
lmX@�]r+?�?�/]q//+<�+}�q�]]q]q�10]]2#!32>54.+ޣ��]=m���i��A�tƐR8i�`��L�ᖅ߳�[.��cJ�ӊb�`0$��s@		

���@1H
		\0@P�_	_	_lmX+?�?�9/�//]q/+<�+}��+]9/3���10]3!!!!!$T,��E�,�HV-����$�	v@'\0@P����@!H_/@1H_lmX P]q+?�?9/+]�10�+�//]q/+<�+}�10]!!!!0U�,�de���,��L���d����-�@-"-^)*))*)))����H)����H)���@6H ))@H/++*[`


)$*a--$_$_lmX+?�?�9/9�9/]q�/9/�]+2]2_]+++F�.(*)).
+</+<+H�+}�10_]]]%#".546$32.#"32>?!7!xC���m��Nv�8��Ȑ[��1NnIņE)X�a>pbQ.��'f�.I1[�݂�8�{@l�NN.VB'\�ނM�i="��$���@h

	
R	
	\					 0@
R\0@P�`	lmX@
P
]r+?2?39/3�2/]q3/+<��+��+��q2//+<��+��+���10]]!!!!!!�v��v��'mrm��\�����1�$\��@dt�@����@D&,H_����@P`\`p�0@P�����@9&-HP`	lmX����p@0<��rr^]]]]]]]]]+??/^]]]+]qr//+<��+��}�]]]qq+q_rr10_]3!$'��������y@@0�\O� 0@0__lmX+?�?�99/33/]q]/F�(
+</+<+H�+}�]10]]".'%32>7!7!�i�nC
!1E.2B,	���,?�Lx�8g�\:1S<"&?S-��&m�p9$C��@h	H�


	RR^



\0@P��			
���@+H�
lmX�
 
@

�
�
P
]]]qrqq+?3?29]+�]2�]/]q//+<��+��}�9]�++�+���10]]]]]+]!!!!	��e��'��q���{���������$k�X@6 \0@P�_lmXP]+?�?//]q/+<�+��}�q�10]3!!$'��-��c�$��0�@V-..#""6Ff�#(%(."(�(((/00^0��&f�����@�H2^ ! !! F V f  � �  !( ! "./!lmXi2)2�2�2;2�2�2�2Y2I2;2�2t2V2F2�2�2�2�2�2v2f2I2�2f2)22P2@222$222�2�2�2�2�2�2t2d2P2B242&22��2�2�2�2�2�2t2b2T2F262&222�2�2�2�2�2�2@��2�2r2d2V2F2$222�2�2�2�2�2�2�2v2f2V2B242&222��2�2�2�2�2�2v2T2F262"222�2�2�2�2�2�2d2V2F242&22�2�2�2�2�2�2t2f2V2D26222i�2�2�2�2�2�2v2V2"222�2�2�2�2�2�2i2V242&22@3�2�2�2�2�2�2y2R2@202229�2�2�2�2rrrr^]]]_]_]]]]]]]]qqqqqqqqqqqrrrrrrrrrrr^]]]]]]]]]]]]]qqqqqqqqqqqqrrrrrrrrrrrrr^]]]]]]]]]]]]]]]qqqqqqqqqqqqqqqrrrrrrrrrrrrrr^]]]]]]]]]]]]]qqqqqq^]]]]qqqqqqqqrrrr]]]]]]qqqrr+?3?29//]q/+<�+}��+]q//+<��+}�9]_]33������]10]]]]]]]!>767#.'&'!!67>7!��

$!�d�^����X-����t7q/74NIB?9��35>BJR=;640�����]-4773,[�$���@�


		R
R^{���� 0@��O^			0@P�	,#	
	lmX�@P]rq+?3?29]]//]q/+<�+}��]]q_qq/F�(
+</+<+H�+}�+���+���10_]]]]]!!!67>7!t�����^y	���k+*$S#�����'+%[.u�d���1J@2[o-�--- -P-`-�--3"[`%_
_@3�3`3qqq?�?�/]q��]q]q�102#".5467>$"32>7>54.���V

����Q	
���k�{R��l�|Q0X�Q�օ1i0���^W�ڃ.`1���_�@x�o%K"��Az�l$R]�].$Y�h@@[\


0
@
P


�

__lmX@/]]+?�?9/�10//]q/+<�+}��]�10]2#!!!2654.#!R|��EQ����b���!��$B[7���:n�br�D���Rt�7O3d�p�*Bj@C#
[#[o>�>>> >P>`>�>>D3[`###6_
a+_@D�D`Dqqq?�/�?3�10/]q��]q]q�9/]��9910232>7#".'.5467>$"32>7>54.���V

s�ق[Y((&-[1o�f6
v�{>	
���k�{R��l�|Q0X�Q�օ1i0�ߩmbX�
8g�Xe��s.`1���_�@x�o%K"��Az�l$R]�].$���@O\[`�� \0@P�__lmX  @ rq+?�?39/�2910//]q/+<�+}ć���]��9�+}�10!!!!2	2>54.#!��g�����y8Co�O�0ClM*&BX2��Q���Bo�P^�j@���8U:4I-�`��A�=\@<66\9%%0$$$?�?-\/?O-(a_?r?�?�99/]3]��q�]q2]��10]]]".'%32>54.'.54>32.#"X�̍Q!
,JjHL[3&KoJ^�{HZ��w�ÊR���vBcC"-OnB3kdXB&P��0a�`16P5/L8/B0&>_�d_�d31Y}LCZk.@&-=,"!-=UoIi�r<�s�L@-��\� P_lmXp	 	qq+?�2?10//]q]/+<�+}�10]]!!7!���9,�,��c���w����!�@_
	\@��O�#\���@#H
_
lmXP#]+?333?�9999//+]]/F�"("
+</+<+H�+}��]qqF�"("
+</+<+H�+}�10]]]%2>7!#".54>7!�Oz[<�'�n�㒁ΎL�'�(Ie�"N~\d���χB;r�m785��"_+6Q6����@#3#��س#&H���@]HR	^R	]{	�	+					/?O���	O ?�_]]q]qq?3?3310�]]/]9/]]q�+�+ć+�+�10++]q)!67>7!��1r-�4��w-^'.*)-&_/���.�@�&!$
$ RRR-'-^...'[k! Td@.R\0@/?O��...0''- O0?0/0_0]]]]?33?29/39�]]/]]�]��++�29/]]33�]2�+�+�+��+��10]]]])4.5!!67>7!67>7!���	����Y$
N< 0N#,GZ`$;831,�����0w6>?JF@>9
��*\(/-9<3z9.�����@�
	

	

		&	6	)9)9&6R		b


R^ 0@
�
/

/
?
O



	
�
�
_
]]]q?2?3/]�]]q�]�]�+�+ć+�+�10]]]]������������]]]]]]!!	!!�5��9��o���9�����)�[�$�������@eRbR^O_o��
	\0P`���lmXp
`
�
]]]+?3?910/]//+<�+}�]]3/]2/�++ć++�10!!	!Dp��p��$�CB��B?��]��#�	z@yHv���@Hb	���@+H	��/?O��__?�9?�9/]�]3]�]]]�+3�+�}�10+]]+])7!7!!:��(��i-�(�^����3��WZ�_@M0@P���@
H��lmX+?�?�/]+q/F�(
+</+<+H�+}�10]!!!6r%���%�Wu���o���-@�??�/��+��}�103!��)��
�R�W��@�	i		�	9	I	Y	�	�	�		@!$H		V	f	v		M����
��lmXY	9	)				�	�	�	�	�	�	�	{	i	Y	I	;	+				�	�	�	�	�	�	�	y	i	Y	)				�	�	�	�	�	�	�	�	{	i	Y	I	9				�	�	�	�	�	i	@�Y	9	)			��	�	�	�	�	�	�	�	}	m	_	M	=	-		
	�	�	�	�	�	�	�	�	{	k	Y	K	;	+			�	�	�	�	�	�	�	�	{	k	[	O	?	/			i�	�	�	�	�	�	�		o	[	K	?	/			�	�	�	�	�	�	{	k	[	K	;	+			�	�	@,�	�	�	�		o	_			9�	�	�	�	�	rrrrr^]]_]]]]]]]]]qqqqqqqqqqqqqqrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]_]qqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]qqqqqqqqqqqqqqqrrrrrrrrrrrrr^]]]]]]]]]]]]]]]qqqqq+?�?�/^]]/F�(
+</+<+H�+}�^]+]qqrr10_]]]7!!7!�%)��%���W�����U��B@& @p��`�]]]?3�29/q33�q2�q210	#!������B�������!�T��/�10�/105!x��NN!�����/�/�10	7!T����+��
��7N@O�@`%555E5
AO455KOOOOO�OO�OOO OOQ/Q()EG/?O��((/(_(o(((#Q.OJ
�A�AA���@ HA54.JO<RXYXQ�Q]q+?3/��?9/99�+]99�9/]/]��2/]�]]qF�P(OOP
+</+<+H�+}���]10]]%#"&5467##".54>?>54&#"%>323267#32>7)*V-dl"FUjGNsL&-Nix�?�D;>7-��Ht�j�G���u�(5GE<TE!5T;2Sl9QvT5$-B>90ClM*��V(��'P\T'9$6[C#����&>�@b77G�< <<�<P<�<�<�<<@ @,$   K O��,'4O$'OXYX�@q+?�?99�9??/]//+<��+��}���q�]qrq�10]]]]2#"&'#!>7!#3>"32>7>54&WQ~U-
X}�d}� 
��

�T
8�4`P?	5J-3QA3GN2\�Q0p:�˂=h^;4%	+<H'�UBNTi�!P�h,L!8U:M�f3Y&il?��UN-s@P$$
++P`/p/+G���/?O!OO	�/]?�2]]?�2]]/]]�]�]2]�2]]10]]]]%267#".5467>32.#"Sl S{�ot�q8M_ntw9k�i7��QQ8S@3Z�ig2O�\2>q�^(^-o�sI):c�IZe%QY--)yq:���'=�@f11

0"!"!K""!"�"""? ?p?�?;G�
�
�
/
?
O

�

"0(3O(OXYXP?]+?�?99�9??/]]�/]q�]q/+<��+��}����]10]]]%#".5467>323>7!!4>7'2>7>54&#"�$LUd<P}U,	a��YIkK/T����5_P>	^^/VH:		L�/D-8d�Q-h?�˂=6H)P>��=t6 ,2!Q�h,M!xm O�e3S&js?��:N#/o@N	$/ G�'''P'`'�'�'�''1/G���/?OQ//*RQVf$?3]]�?�9/�/]]�2�]q]��2]10]]32>7#".54>32'>54&#"f][*B4'�Ln�kn�s;W��p�l4
�cP"LG9�,jq->$J@iJ(>r�d���[<m�[-u5�kb;eOJU�i@A		�KP`���OQOXYX+?/?3�2?�/]]//+<�+}ć���3/]10]]!#737>32.#"3��讞%�
5]�`-Q$		C<
�%|��|�qDlJ'�@AU����W�M:R�@j
@@
***>066K///��T TpT�T�T0KG���/?O6/	>NC*$NPCO$0/$	QXYXPT]+?�??3�/�999992/]]��2/]q�]q/F�S(/S
+</+<+H�+}����]10]]".'%32>7>767##".5467323>7!>54.#"32>�i�j=^P:V?+?SlHOW/	3��9gR;	
�R��
5I*3SA2	
YT,\RA�W%HiEB=AeE=(L:#6`�P1p?5M2>9)	,=K(��l�m7�#L9S8K_2b%g`'U�#��'�@`''RK��) )$%%R%K&'&''&'O&&�&�&&'&&$%%PXYX+?�?339939?3//]//+<�++�q�]q/+<�+��+�10]]]3>32!>54&#"!YO

IZoE����v	MK/^Q=v���k10.*J8 ��.-)�sS..)
?B(Ji@���#\��@TKP���O�SXYX 	�	�	/	�	�	�	�		qqqqq]]]q+?�2??3/]q//+<��+��}������10]7!!))�������:���$�W\��@QKOO�@-HPSXYX ��/��qq]]]q+?3�2?�?399/+]q/F�(
+</+<+H�+}������3/10]]7!"&'732>7!((��2V$
!"0"	��
5Tu���Z�&>.v�FAlP,#���@|



	RRK



KO��	��	0	P	`	p	�		

XYX�
O
/
]]]+??2?9�]�]83/]//+<��+��}ć��9]�++�+���10]]]]!!!!	ؼ�U�� ��B�'�K�Z����Z�l#\��@Y	�`KP`���
XYX�����C�r^]^]]]]]]+??//^]q/+<��+��}�^]]]q10^]3!# ���4#�M@@�?
	677K898998988+K�#K P?��
9+#67(78P0(XYX�B`B�BoBOB BB�B0B�B�BB0BB?^]]^]]]qq^]]]]]qq+?3�2?3?399//^]q/+<��+}ć��3//q/+<�+}ć�3///+<�+}�10^]]]"!>54#"!>7!3>32>32!>=&*-QA/
w��{~.PB/
v��
		AM_<|� FTgA��
���z|(KiA��{%&!y)KkA��S"KC0,9;/L5wq2T?#��$Z&�ss%%$y#�M+�@\''*+R*+K+��- -RKO��	P"XYX+?2?9?�9//]//+<��+��+ć�q�]q/+<��+��+�10]]!>54&#"!>7!3>32�v	LL/^Q=v��
IZoE��S..)
?B(Ji@��S"KC0,9;*J8 ��.-)�s?���M-Z@@))G�P��/ /�/!G�
�
�
/
?
O

O&OXYX�/P/]q+?�?�/]]�]q�]q�10]]#".54>324.#"32>7>�b��k�|C]��v�y>��6M0,ZQC	8L--XPB
�����a;q�h���a8l�xB^;Bzd0U$Hb>Ayf4W��W�N?�@3  "G�P��A:?.21122\3433433�33@$H 3033���@1H334.?*
439O
O*93XYX A�ApA�APA]q]]q+????��999910//]+]+]F�@(433@
+</+<+H�+}����]q�10]"32>7>54&%>32#"&'#!>7!�4`P?	5J-3SB3	
M��#JVd<Q~U-
X}�d}� 	P���		�!P�h,L!8U: N�d3Y&jk1G/2\�Q;s:�˂=h^*d.�a�#13@2(35;�W�N'?�@q
11
	
-
R
K;G��A ApA�A?A���/?O�-(3"3O"(O
XYX�Aq+?3?�?�?99//]]]]q�]q�F�@(@
+</+<+H�+��+�����]10]]]>7!!>7##".5467>322>7>54&#"�

��U$LWe<Q|V,	b��WBcJ6��5_P>S[5[K9
'=�71(
���<�!N-/E.7a�Q-m?�̂<0L5�%!Q�h,L!pvN�g4Y&1O8#zNi@ARKO��P`��!PXYX�!/!_!]]]+?�??9910�]/]//+<�++ć��10.#"!>7!3>32L4'{�#e��		"CIS2U
����>#FB;<>9>[;��-K9�@�8%%
G*(((O'�'''P'';;P; ;@;0G���	%		��0V0f0Ue0+Q("Q	@;0; ;�;�;P;]]qrrr?3�?3�99]q]q/]3]�]�]r�]]2]]��10]]]]]]]]]#"&'732>54.'.54>32.#"�C��x��)�
$:S81Q;!7U9O�`6K��f]�lE
�bU+K7 &D^7FyZ3NY�X,��$$5#!4$ )2MmLZ}O$DnQE>.# +
.IjV���8�@#KO����@#	HOOXYX�!�!]]+??3�2�99�2//+]F� ( 
+</+<+H�+}�����10]]%#".5467#73733#32670%^5;`D%f�%���0�#�h/&1#
8U8-U������9*)U���:+�@�+	$$KO����P�?O� -- -p-�-�-�-K+*+*+**+*�*�*O**�*�** ***"+	P"XYX+??�?3999//]]qr/F�,(+**,
+</+<+H��+}�]qr�]]]qqrF�,(,
+</+<+H�+��}��10]]]32>7!!4>7##"&54>7v	LL/_Q<v�
��HZpE��:��..(?B(Ji@^��"KC0,9;*J8 ��.-*
�n�:�@4DT����@`HR	MR	L		$	�			/�P`���	p`P���]]]qqqrq?3?3310�]q/]q9/]�+�+ć+�+�10+]q)!>7!n���!G!"<.:��HHDDHF`c�:)+@+)(y(�(y'�'	�@HJZ
#

���H���@Hu�R 0@fR"�@
R�@D

R("(M)))RMd""F"i,<f"))���@(H)+"
()@+�++?+ +]]]]q?3?29�+]/9]]]]]]]]�+�+ć+�+ć+�+ć+�+�10]]]]++]]]+]]]]]]).'&5!!67>7!67>7!���+!���E


&-*
�P#)++)#Q �n:��CD""C|��B"$$"B|���:/@q	



	

	jt$�{�+  %H$e{+�t�$���@` %H)RMR	N?O�pO	O		`		
	P
@
0
 
`
 

0
�
]qrrr]]qr?3?2�]]q�]/]Ƈ+�+ć+�+�]+qr]qrq]+qr]qrq10������������]]]]!!	!!	Ժ�������$�(4�&��2��[������W�:%�@f�u�1AQ�%5ERLR$$M%%%$P%/%%'@''�'P'P%$P?�/33?333/]]qr�]]9/]�+�+ć+�+�]]qqq10]]#"&'732>?!>7!:8eo�T4S&&1&A><�#G",9W�_2		�.K70"�4CJC43AHB5���:	�@ZUZ4D����;K����MP`p`p�OOPP?�9?�9/]q�]3]��3�+�}�]]]q]]]q10]]#7!7!!#&�� ()'�v7(����\��W��:�@F
	
#12"2	"22M�218#")	)�8��lmX+?�?�9/�999999999//]F�;(;
+</+<+H�+}������10]]]".5467654.'7>7>;#";�;_B$=2G,&5ZF1C���%L-?+
<
1ET,$;,AB8P%�W:S3:4#!*<%�6U>R���3M3��2S?*		"1@'
��-54���W���@��;K[����T4��T�K;��kD�d$���td������dT4$���t;+������d4��D��[Kk����tdTD���@>������{dTD$;�����r_rrrr^]]]]]]]]]]qqqqqqqqrrrrrrrrr^]]]]]qqqqrrrrrrr^]]]]]]]]qqqqrrrrrrr^]]]]]]]qqq^]]]qqqqrrrrr]]]qqr??9/qr�10!��Wu���e�W�=�@[

4	4&%M%5%%5%%%�%�%% %P%5%%&54+<	=+�=�<�=lmX�?]+?�?�9/�999999999//]]F�>(5%%>
+</+<+H�+}������]10]]]]2+732>7>?.5467>54&+7�;_B$=2G,&5ZF1C7UvM�%L-?+
<
1ET,$;,AB8P%�:S3;��"+;&�6V>��JnJ%�3M362T?*		"2@'M-54�v�Hf@?	����0�@ ~�B^]q^]^]]]qq/���9/9/���/^]]10]"&'.#"5>323267yK�KBm.'C=:3�TP�J6n0D�4 =?F* �&.,2*���L:�@RR����@=H�@#)H�lmX@	 	�	�	�	0			�	�	�	]]]qqq]]]qq+?/��0//+]q+F�(
+</+<+H�++���+���10]]!3�5 5����,���+L��b�%��?�3�&/�ְͰ�'ְ6�>���
�.��� �����������	������������%�� � �#9�9�9�9�9�	9�%9�9@
	%..........@	%............�@0167#7&'&54776?3&'ω;$@/�c1e�� � �[U@�����TM��GZ�/P�s5�3%�2����rk�iJ�d	��e[�~+����,�@U""!$% %	 %%q


,:J.%($	 !
$	v!(t/	uXYX+?�2]]?�29/3�29999�]2]�2/3//F�-(
-
+</+<+H�+}������9/10]]#!7>?#73>32.#"!!!267P*�6'^r� �3Cl�kc�b5�SG%:, 5!��xY�_wk���&�i{�_�e50Sm<0TI.K8��~k"ZfH�H�"64@-�#�@`�	(�2�	/���+/_^]]q���10467'7>327'#"&''7.732>54.#"����0p<<o0������dy<p0����!9L++K9!!9K++L9!�<p1������0o<<m0���;���0o>+L9!!9L++L9!!9L'��@o

		RbR^�q				�		
w

w

	lmX+?3?99//]33�23�2//]/+<��+}�3/]2/�++ć++�����������10]!!!!!7!7!7!7!!!�� R��.��.��Q �����&�����쓢���T��W���@��{�����	T	�	4	�	d	D		
4	�	�	�	�		�	`	D		$			�	�	�	t	d			��	�	�	�	�			d	T	4	$	�	�	�	t	;	+			��	�	�	�	�	d	4	�	�	D		�	�	[	K		k�	�	�	T	D		�	�	�	�	@/�	�	4	$		�	�	�	�	D	$			;�	�	r_r^]]]]]]]]qqqqqqqqqrrrrrr^]]]]]qqqqrrrrrrr^]]]]]]]]qqqqrrrrrrr^]]]]]]]qqq^]]]]qqqqqr^]]]]qqr??99//9/]qr�3210!!������w���|�I[o@BG	G&	& M@EW1o/WWo]oE MMo;(R6J@6,v#	v?�/�993333/��]���]��]��9910]]]]]]2.#"#".'732>54.'.54>7.54>>54.�W�sF�pSqs/IZ+Af?#A\9?EI��g�wG
�tj3cO10Sm<?tY5'Gd=4(D��gz*F\2'RC*<d�@iLD=DA%5$
.EaD@`H2#tK^�\.DnO%SH#<-/<(.GeF9YD/,;K-Y|N$�yNF(7'!4'#4+#��<�@���O	]/2�2/���1073!738+�+�v*�+�����>����/Y�@&
77
33	J�5?U5U5�&�@ 0`p���@FH/O_T@EO�0E�:00::0:0:�+�[o[O[?[/[_[]]]]]]?�?�99//]]��99+/_^]+]q���99//��10^]]#".54>324.#"32>".54>32.#"32>7�4^���dc���^44^���d��qp`��T��nP+`��~ަ`��h�d00b�dNtS9�
 />):T58T9)@0!
�9Tt�d���^44^���dd���^4r���~ݥ`,Oo��Tަ``���?q�\`�k:%?S.).%'He>>hK**3/+UD)h� �;H�@++
<3�	���@5H		,�@�% H	%%% 8;C+=;==�)C��� �)�?�?3�9/]�]339]+/]]���9/+�9910]]]#"&5467##".54>?654&#"'>323267'32>7:BG-7F.2K2@e~>{	,&($����}/

�bMX3&.-&�	4.	
"7&!6F&N_4$*($Xg]b8��963/#;,H�R�s@M**	�
�
O

�

��O����P`�?	���/]3�2/]]]]]��]�9/��]��]�9/�10]]%73!73�C�����N�C�����iGo)����'iGo)����'K�N@@	
H���?����/+10%!5!n�����t>����/=F�@ ::	1<<C=C3�4>�9=0)00=�=�==���@H4=4=�&�@ 0`p���@?H/O_=4<2�CB�54C55C4�+�HoHOH?H/H_H]]]]]]?�?�9///��23+/_^]+]q���99//+]3]���29310^]#".54>324.#"32>##!24&+326�4^���dc���^44^���d��qp`��T��nP+`��~ަ`�E�u�J��`N��KE��BC�d���^44^���dd���^4r���~ݥ`,Oo��Tަ``���9��/�q^v��;9<�G���|
��/��/10!5!|�s��^�>�']@@

	��@


�
�
�
�
�

#�@���)]?���+/_^]q���10^]]]]#".54>324.#"32>>3WuBBuW22WuBBuW3�,;##<--<##;,VBsU11UsBCsU00UsC#=--=#"=..=WZ��@�O_��		�

�@Pp�	�	�o�oO/�O��oO�oO/�O/9���rrr^]]]]qqqqqrrrr^]]]^]]]]]qq/3�2�^]+�?�9/_^]3�2�^]+�3210#!5!3!5!��o�������U�T���=��G��� -@����"����?�?���/3]�2�107>54&#"'>32!G$_c`K.,#$?��{?\>2M`\N]�x9R?56?+'(9"c[3D'<YE611�`���3g@%%�..- #@-#����@ H�5...1 �1�(���?�?�9/�999]���+�/�]9/93]310]4.+732654&#"'>32#".'7326�(D<>L)**;�6L`9v�.E,@N-RuGJe?�22.A�!~2-',*48I-YZ >1!M?3S; "8G$035�����/�/�]107!����� +����Ws:4�@C&	%'((K%&%%&%%o%%?%O%�%%%�%&%6K�@$H����H���@:H	%(. P'&XYX@6�6�6`6O6/66�6]q]]]]]q+?3?3?3��999//]+++]F�5(5
+</+<+H�+}��/]]qF�5(&%%5
+</+<+H�+}��10]]".5<7##"&'#!!32>7!3267k1@%*jQ;MI��#v:C+I:)p� 

&c$5"Wa1)���!<MX-QsFB�)�	j��R��@\� 	�� ���`@ ��p �p9�r^]]]qqqqqrrrrrr^]]q?�2/39/�^]�/^]9/]�10###".54>3!ϜÛQ�`52_�Xu����-X�XT�]2�~�Dg@8�P`pp��o������XYX+/�0//]qrqrF�(
+</+<+H�+}�10]!~; ;1��X�W7@!	H�0

�P]//�99/�]�9+10]"&'732654&#*73�7)QJ4:
b�0FW��Wu#* �SABaqb���
c@1



		�����XYX+?�2?�3/]/F�(
+</+<+H�+}��10]]]73?33b�[�ʯt��y�lzu��yf�<�+?@"
�


��-$�����?�?��]]�/]]�10]]#".54>324.#"32>7><@p�ZFqP,=m�[���"-72)",30+vi�u?(KlDd�r>��+:$'I=6.>%$KC 7&�0�y@T%%Pp��/	�
�
@

�

��@� 0@`/?O�	��/]2�2/]q��]�9/��]��]�9/�]]10]]#77!#77�����I������I����G��)na'��G��)na'��b�&y' ��4�K?@
o_�K@	%�o��@		%?55+]]5+55]]]5��bD�&y' �rn�I6@o_��@	&	%o��	%?5+]5+5]]]5��n�&s' ��4�K/@�5�5O5?555---:?55]]5]]]]]5��:%)u@6H&()R)�&'&&'''&&!G*' !+
'(�&)_XYX +]+/�?3�2�99�2/��9/F�*(&''*
+</+<+H�++���10]]".54>7>7!3267!�h�zCAi�C#;.
3I`:E[6`en�
\��=4��4��/Z�VV�hR%)0:'FgQD$+B=>'?S]d(X�c6�������3&$�]�&����%+5+5����3&$��@&�`�%+]]]5+5����3+&$�}@&O%+]5+5����C&$R�[@&/  0%+]5+5����3�&$i�K&@&_�5�%+]]]55+55����3&$P��@8�O%+]]55?55��3��@ARc	



\
/?O���@$H__			
__lmX+?�22?33�9/9/��32�+]/]]9/9/�//+<�+}������3��+�+���10]!!!!!!!!#!EF�>����-�CE+��I�,��=%+)
��\h��������	8C?�o��Q�W��&&�x]�0//����/B!%+]]55��$�&(��@&6
%+5+5��$�&(���&��%+5+5��$�+&(�[@
&�%+5+5��$��&(i�K@
&�%+55+55��$t&,��@&S%+5+5��$f&,�1�&��%+5+5��$N+&,��@&�
%+5+5��$L�&,iK@
&�%+55+55��#�@k	  #
#\


0
@
P


�

[ �o�P`%_O�/���_
#_lmX+?�?�9/]qr3�2�]]q]q�//]q/+<�+}������10]2#!#73!!32>54.+ޣ��]=m���i��s�*�t�e,��G�tƐR8i�`��L�ᖅ߳�[.R�T����J�ӊb�`0��$�&1R�[@&�/%+5+5��d��&2�@2&O36%+5+5��d��&2�D�2&�+�25%+5+5��d��+&2��@3&u82%+5+5��d��&2R[@2&�=M%+5+5��d���&2iK@
2&�64%+55+55~�~�y���@!H#3C
!H,
<
L
���@8!H#3C!H,<Lp @`p�� �������
H�?+]q/]q10]+]+]+]+	7			~d���``���`�����Ff`���`������b��#��z�!.;�@,24"-1/*���@HH		[o/�/// /P/`/�//=*[`	-1/*,24"4_"_?�?��2�299/]q��]q]q��2�+2910273#"&'#7.5467>$"&4'32>7>�x�N���=?

���p�Is��EG	
���k�{R'�W��PO|l�|Q�53��I�r1i0���^.-|�N�{.`1���_�@x�o%K"yQ�F��dK�4Az�l$R��w���&8�B@"&#&%+5+5��w���&8�@"&�"%%+5+5��w���+&8�@#&c("%+5+5��w����&8i�K@
"&~&$%+55+55����&<��@	&�	%+5+5$.�k@>
[\0@P�__lmX+??99//��//]q/+<�+��}�����10])!32#!7!2654.#!K��',�|��EQ����,!��$B[7����:n�br�D�t�7O3#����?|@L32	H6(H//?H�(�((A>??K?O��%Q>9PXYX+??9�9?�/]//+<��+}��]�99//���10]]]]36$32#"&'732654.54>54&#"#�2�Y�f7+?K?+%8B8%:k�Zq�6D26=#PS$7?7$+BKB+IF6YE1���'JkDEeJ834"$3/1D^ET�_3�	TH-=2.:O9:SB8>J35<>cG���
��7�&DCh@P&OQT@%+5+5��
��7�&Dt�P&��PS@%+5+5��
��7�&DK�@Q&�VP@%+5+5��
��T�&DR�@P&�[k@%+5+5��
��7�&Di�@
P&�TR@%+55+55��
��7�&DP�)@
U&�ZP@%+55+55*���NET`�@�	!;;BGX;$``�F&
**X�X�X�X�XXPX`XXb�b_b23JG/?O��22/22@H22-%`FQ``O8[-Q=8OO$Q?3�3]�?3��9/���9/+]/]��2]]�]]]q9/3]33�]2]9��210]]]]]32>7#"&'#".54>;7>54&#"%>32632%#"32>7%>54&#"][*B4'�Ln�k��..hpw=NsL&-Nix�?�D;>7-��Ht�jr�)��p�l4
�(�q�$1GE<cP"LG9�,jq->$J@iJ(shHV/2Sl9QwU6$-B>90ClM*?2q<m�[-u5\S'9$6[C�kb;eO��?�WUN&Fx������1D%+55��?��:�&HCw@0&B14 %+5+5��?��:�&Ht
@0&�03 %+5+5��?��:�&HK�@1&i60 %+5+5��?��:�&Hi�@
0&�42 %+55+55��#/�&�C�A@&0%+5+5��#�&�t@&�%+5+5���&�K�@&u
%+5+5��#�&�i�@
&�%+55+55:����(:�@c	55++

#G�88<p<P<�<�<.G���/?O�&(
	'''')O3O?]�?�9/�9/]9/]]�]q�]�310]]]]]].'!%#".54>32.'7"32>54&�/kD%$I ' �,D.R��v�u:O�ۍC>6B+��!�GqP*1M7CsT/j.](1m�69x��T����lBt�[}ИT

G�9o��3:i�V)P?'Cu�[U`��#��&QR'@,&�7G%%+5+5��?����&RC�@.&&/2
%+5+5��?����&Rtl�.&��.1
%+5+5��?����&RK@/&k4.
%+5+5��?����&RR@.&�9I
%+5+5��?����&Ri"@
.&�20
%+55+55E�H�]@B?o?Oo����0`0@`�����
�	���?�����9/]3�]q�2�]q10535!53��������x��q��*���\'6�@[22',##,+G�(P(�(�((88@8�8P8p8�8�8G���/?O+,/ 	/O O
	?��?��99/]]�]qr�]q�910]]]]7.54>3273#"'.#"%4&'32>7>*�68]��R�7<��]b�j:}�F*,ZQC	�D>#-XPB
&�8�a���aE�i�����a1C�2+Bzd0U���Ayf4W��U����&XC�@,&-0%%+5+5��U����&Xt]@,&�,/%%+5+5��U����&XK%@-&Z2,%%+5+5��U����&Xi@
,&t0.%%+55+55�����W��&\t@&&�&)%%+5+5��W��;�@>:$$&G�P��=28;;89:;:8\9:9:8999�99@$H 909P9`99���@$H9:9
2.O!
O.:9!XYX=P=]r+????��999//]+]+]F�<(:99<
+</+<+H��+��}���]q�10]]"32>7>54&%3>32#"&'#!!�4`P?	5J-3SB3	
M��#JVd<Q~U-
X}�d}� 	Q��q�!P�h,L!8U: N�d3Y&jk�S#1G/2\�Q;s:�˂=h^.22�au�����W��&\i�@
&&�*(%%+55+55����3�&$M�N@&%+5+5��
��7[&DM�@P&�QS@%+5+5����3&$��@&2$%+5+5��
��O�&DN�@P&�Ud@%+5+5����b3�&$Q����+%+55��
�W7N&DQ���Uf@%+55��d���&&�5�,&�8�,/!%+5+5��?��m�&Ft\�.&��.1%+5+5��d���+&&��@-&�2,!%+5+5��?��U�&FK�@/&[4.%+5+5��d����&&O�@,&�,.!%+5+5��?��U�&FO���.0%+5��d���+&&��@,&�.4!%+5+5��?��a�&FL�@.&�06%+5+5��$�+&'�f@&�$
%+5+5��:��~�&G��K%@I�D�DpD/D�D�DJ
%+]]]]5?5�����:��o�/D�@�88
$$

"%)!)7*!)* ! )K* * ) *�***F BG�
�
�
/
?
O

�

%@HR"@H * 70:O 0@0OXYX�FpFPF]]]+?�?]99�9??9/+]3�+2/]]�/�]q/+<��+��}���������]10]]]]%#".5467>323>?!7!7!3#!4>7'2>7654&#"�$LUd<P}U,	`��UJkL/
��!"�!�����2]O>^^-SH9		L�/D-6`�N,d=��};4E'[Ez�����P=t6 ,2M�cU=sgK`1N%em��$��&(M�N@&�
%+5+5��?��:[&HM�@0&x13 %+5+5��$�&(��@&�%+5+5��?��C�&HN�@0&�5D %+5+5��$��&(O�@&�%+5+5��?��:�&HO���02 %+5��$�W��&(QE��"%+55��?�l:N&HQO�5����5F%+]55��$�+&(��@&�%+5+5��?��R�&HL�@0&�28 %+5+5��d���+&*��@/&�4.
%+5+5�����W��&JK@T&gYS0%+5+5��d���&*��@.&�3=
%+5+5�����W��&JN�@S&�Xg0%+5+5��d����&*O�@.&�.0
%+5+5�����W��&JO
��SU0%+5��d�9��&*��9'����4:
%+5+5�����W�I&J�@\&�]W0%+5+5��$�+&+��@
&�
%+5+5��#�z&K�#O@)&�.(&%+5+5$?��@�\



 0@

\			0@P�	`a	

	lmXP]+?3?299//]3�2�32//]q/+<�+}���������q//+<�+}��������10]]]!!!#737!!7!3#7!�v��v���{!{&'&r&&{!{�w&��&\������ª��P��#��/�@x+ RK��1
()).+*)R)K*/*//*/O**�*�**+@H+R...@H../(#
)*#P 0@XYX 1q+?]�?39999?39/+]3�+2//]//+<�++���������]q/+<�++�10]]!!3>32!>54&#"!#737Y9!��

IZoE��w��n	MK/^Q=n���!�̋��10.*J8 ��.-)��+..)
?B(Ji@�������$�&,��@&�%+5+5����5�&�R�@&z%+5+5��$�&,MN@&�%+5+5��#�[&�M�@&�%+5+5��$c&,�@&�	%+5+5��#(�&�N�@&�	%+5+5����W\�&,Q����g�	%+55����W\�&LQ�q���Y�
%+55��$��&,O	@&�%+5+5#:c@AKP���O�XYX ��/���0qqqqq]]]q+??10/]q//+<�+}�10]3!#��:����$��f�&,-�'@�	o		��o_]]]]q5]]5��#�W��&LM9>�!��@(&&H!@

H!@H_���?]]]]]]qq55+++����+&-�w�&�8� %+5+5�$�W���@U
�/?�
K


O�@-H
PXYX ��/��qq]]]q+?3�9?�?399/+]q/F�(

+</+<+H�+}�3/]q�]10]]"&'732>7!#'##7362V$
!"0"	��
5Tu�����@�W�&>.v�FAlP,d��>��$�9C�&.���'��9�	%+5+5��#�9��&N���'��p�	%+5+5#�:�@f
+
&)

	RK



KO��	��	0		���@H	

XYXO
]+??2?9�+]�]83/]//+<��+��}ć��9]�+}�+���10]]]]]]]]!!!!	ؼ�U���X�B�'�K�Z:�@��Z�l��$k&/��@&�	%+5+5��#g_&O�2O�&��%+5+5��$�9k�&/�>�'��ߴ%+5+5����9\�&O���'��I�
%+5+5��$r�&/���/���%+]55��#��&O�HK-@�@H?
/
���
%+]]5?5+]5��$k�&/O�����)�%+55��#��&OO�����ɴ%+55]5��k�
t@E
\0@P�	
_lmX+??�99//9//]q/+<�+��}�������10]3?!%!$]�,��'mP+��M�-�@�A��ϕߕ�s���y@J	KP����O��	

XYX�
/
�
q]]+??99//9//]q/+<��+��}������10!?!7nj��l�)��l�*�31E�E��J���$�&1���&��%+5+5��#��&Qtk�,&��,/%%+5+5��$�9��&1���'��i� %+5+5��#�9�M&Q��7'����28%%+5+5��$�+&1��@&�%+5+5��#��&QL@,&�.4%%+5+5��U�'Q��K1��:����H:����H7����28%%+5?5++]55$����?�@~==!!899\ 0���OA'-\ ! !! 0 @ P   �  !982!- 2'&_ _2lmX@A�A`Aqqq+?�??�?399999//]q/+<��+}��/]]q_qF�@(@
+</+<+H�+}�9/10_]]]".'732>7>54&#"!>7!3>32LJx^F�!'1/@0$8	uxL�sP����"
,k|�R��C(6Ki�#?U3u+!%Q�] ,/.V`2Up>��D%\ZL?EE>dF'��0a ��Y�}`A"#�W�M;�@\91677K


�=$(KO��
76(.P.P$#.XYX =q+??3?��?9999999//]/+<��+}��]/F�<(
<
+</+<+H�+}�10]]]]"&'732>7>54&#"!>7!3>32�2V$
!"/"	�	LL/^Q=v��
IZoE���6Tu�W�&>.�..)
?B(Ji@��S"KC0,9;*J8 ��.-)��AlP,��d���&2M�N@2&�35%+5+5��?���[&RM@.&u/1
%+5+5��d��&2��@2&�7A%+5+5��?����&RN @.&�3B
%+5+5��d��&2�H�2&��2;%+55+55��?���&RS9@
.&�.7
%+55+55g��3� 4�@.$\$%$$%$$?$_$%$$0[P



���@'H6_$!__%*__lmXO606]]+??��2?�?�29/��+]/]]q�9//qF�5(%$$5
+</+<+H�+}ć��9/310!#".547>$323!!!!!%267.#"�4::��K���'H6"�,�eE^+��I�-��4`�')'��,	0W{T�׃[d��\�����
���+W'W�T):��_N3R^�@vII<<--(	($^^H44V0GV�V�V�V�V�VVVPV`VV`�`AG���/?O�Q^^F#YR+9R#FQ$?33]��?���9/�/]]�]�]]]q��29/�2]99]]]10]]]32>7#"&'#".54>32>32%4.#"32>767>7>%>54&#"�][*B4'�Ln�k~�9%`nx>k�|C]��@jYL"K�rp�l4
�/6M0,ZQC	 7M--XOB�cP"LG9�,jq->$J@iJ(OI):$;q�h���a#5$FH<m�[-u5�B^;Bzd0U$Hb>Ayf#*kb;eO��$�&5���&��"%+5+5��#��&Ut�@ &� #%+5+5��$�9��&5���*'����%+%+5+5����9zN&U���+'����&,%+5+5��$�+&5�[@&v!'%+5+5��#��&ULe@ &�"(%+5+5����A&6���>&�,�>A$%+5+5����1�&Vt �:&��:='%+5+5����A+&6�+@?&�D>$%+5+5����-�&VK�@;&c@:'%+5+5���WA�&6x�����AT9%+55���W-K&Vx������=P%+55����A+&6�^@>&�@F$%+5+5����E�&VL�@:&�<B'%+5+5����Ws�'x�7��K�W�8&Wx����s+&7�K@&S
%+5+5��V��q�&W��K��4�&,
%+55�s�r@C��	
\� Pa


_
lmXp qq+?�2?9/3�2//]q]/+<��+}������10]]]]3#!#73!7!G�%�z��z�%�G�9,�,������r�m�����8'�@*K�P`���@$	H$O$OOXYX+??3�2�9/3�299�2//+]]F�(((
+</+<+H�+}���������10]]%#".546?#737#73733#3#32670%^5;`D%~$-�%���0�#�-�%�/&1#
8U8-Uk�����t9*)��w���&8��@"&u-=%+5+5��U����&XR@,&v7G%%+5+5��w����&8M�I@"&w#%%+5+5��U���[&XM@,&h-/%%+5+5��w���&8��@"&�'1%+5+5��U����&XN@,&�1@%%+5+5��w����&8P�N@
'&l,"%+55+55��U���l&XP�@
1&P6,%%+55+55��w���&8�@
"&�"+%+55+55��U��<�&XS]@
,&�,5%%+55+55��w�W��&8Q������'8%+55��U�W�:&XQ��w1B%%+55���+&:�@�0&����5/.%+5+5��c��&ZK�@+&0*)%+5+5����+&<�3�
&����	%+5+5�����W��&\K�@'&Y,&%%+5+5�����&<�igK@
	&
%+55+55����#&=���
&�-�

%+5+5����
�&]t��
&��

%+5+5����#�&=OG@
&�
%+5+5�����&]O���
%+5����#+&=�>@
&?/�%+]]5+5����#�&]L�@
&�%+5+5#.�J@,/KO�QXYX+?�?/99//]/+<�+}�]10]]>32.#"!
5]�`-Q$		C<
���DlJ'�@A�q�W9�|@=		q`uvXYX+?�??3�299//]]F�(
+</+<+H��+}������10]]	!#737>32.#"3���%�
5]�`-Q$		C<
�%|��%�qDlJ'�@AU�����x=&$'P���C-3@!===8=B%�O%+]]55+5?55]]5��
����&D'P��z�0@�xxxxU&x}ZP%�ZP@%+55+5+55]]]5����3&����&� �%+5+5��*����&�t��a&�
�adB%+5+5��#��z&��@<&�<?%+5+5��*����&�tl@7&�7:%+5+5���9A�'��6���9-K'��V����9s�'��7��V�9�8&W�.L�2�	@
�`		/3�]9/�]10#'##732����@����>��z�	@
		�`/�]29/�]10#73373=������=����[��/�/�10!7!�!N����c�'@
�@�	�@��/��2/���10]".5467332>73�JtQ*�-; $C9,
�?_��,Mh<
*>))>+IwU.J�����S?�10/�107!J))����p�l'4@"		���
��#�/���/���10]]]]#".54>324.#"32>�(E]44]E((E]44]E("/.""./"n5]E''E]54]E((E]4-""-/""/j�W�
!@�
�/�//��10]]".54>7332672(I7 1A$� 6',#2%O�W,C.,L@47<<&&
D���:@�@p��@
���/2���3/��]�10]]]".#"#>3232>73p,QKD%
�$?bI-SJB%
�$?a�&/& --fX:&/& .-fX::���%@		@	
H�/�32/�]�+�]107!!7!:

��

��� +�� +�����@	@�/�/�10]7!�~���!$%������U	
9@

@H�
���@H�
�@�/�9/�23/���+��+1073%73!73�������,�,L,�,�!�%�v)��������3�&$T�����Ӵ%+5?5���D������'({T�9���
@%`O����]]]]qqq5+]5?5���b�'+xT�B���@H@		H0���@
%P �O]]qq5+]]5?5++�����',�T�E@@		H���@%P���o_?]]]]]]qq5+]5?5+��9����'2�T��;�42��M@22%���� ]]]]]]]55+]5?5����'<T-�:@
 		�� @	%��o]]]]]q5+]]5?5����G�&v3T���S�<��F@3:=)%:@.H/::�:�:�:�:�::o:_:O:?:::]]]]]]]]]]]qq+5+5?5��$+U&�U^@&%+555+555����3�$��${�%$2�f@4/?\0@P�_lmX+??�/]q/F�(
+</+<+H��+}��]10]!!2,�*����d�����@X&6F,<LR\R^� `?/]]?3?�29�/]]]99]]]�+�+ć+�+�10]]'!%.5�Y�,��#�
$-�r���b���)QE12FR(�H��$��(����#�=��$��+d���5Q@31&[`	[o1�111 1P1`1�117_)__?�?�9/��]q]q�/]q�99//10!!2#".5467>$"32>7>54.,K,�����V

����Q	
���k�{R��l�|Q0X;�?Q�օ1i0���^W�ڃ.`1���_�@x�o%K"��Az�l$R]�].��$\�,��$C�.�����@V,<,(#&H(HR]R

^ 	H/?O/]]??23310�]]/]]9/+�+�+ć+�+�10++]q%!.'&'!!��u	-�2��+u,^'.+*.'^-�����$��0��$��1��e�?@&@P`
/



___?�?�9/�/]�]��]��]10!!!7!7,9,���-��-W,��,��G��W����d���2$���@TR\R\0@P� 0@	`lmXP		]]+??2�0�q/]q3/+<�++�3//+<�++�10]]!!!!���������s����$Y�3����|@b
	
b	���@*H/?/?�	_
_?�2?�29/]2/]2�]�+9/33�+�}ć+�}�10#7	7!!!/2!��)�,�G+� 
-�������B��s�7�����<`��v�*7�@++[# #P#�# #0#P#`#�#�#�#�###1[//?o��
)**+7
*
*^

*




 
0
P

P
�
�

7a)
+almXP9]+?�2�2?�2�2//]qqF�8(

8
+</+<+H��+��}����������3/]q�3/]q�10]32>54.#7#".54>;7!32+#";/6R�`5:V8��&Lg�zC`���ECu�x=Z���Q&@.P�b77S8B�q4e�]:aE'���Bz�i��L��H~�c��S�'._�f7aH)������;���(<@�&&	)!) &&
"##^ !  !     0 � �   0 P ` p �  � !  
^/?O��^ @0P`�*) !_
!"lmX`*P**]]]+?3?9/3�29909/]qr//+<�+}�3//]qqF�)()
+</+<+H�+}�3//]]qF�)(!  )
+</+<+H�+}�������]]]]10]]!#".547!;!32>7!#�S��S~�y;RT�}��&LvW:ZZX�ܝ��U�:k�_FN��L
 jg�� FpP�1ƒC���9t@J	87'"/
5[�P;"[�5�55)�)�)�))0'
*_)_?�?3�22222/]�]��]]]]��9999]10]]]]]267>;!>54.#"!732.546$���X7}˔-(#J�,��?_�f52\�RX��M$<P+A��,�G#(,Bx\6t��Q��xp˫�-�Bd��aS�Z/9v�~L{`H���#j��Z���Z��$L�&,iK@
&�%+55+55�����&<�igK@
	&
%+55+55��9��=�&~T�@G&fGJ(%+5+5������&�T�@:&�:=#%+5+5��%�W��&�T�*&��*-%+5+5��C��&�T�@&�%+5+5��f��dU&�U�@(&i.4!%+555+5559��=M/Ft@S3&
& HpB�BPB�B�B�B�BBHPH�H�H?HH8G�(�(�(/(?(O((�((:O#0O?�???�99/]]�]]]�]q�9910]]]]23>7!!.=##".5467>"32>7>54.dJtT5"" NLD	��
"M_uKT�X.e��e-M?1�)RMD*CM%D]8BE>/���Be�va0=>:5_G*6f�].r6��y5�#Q�c2U$�0_�] ?kN+��W�� <�@4444%H/H:P:`::���@,H:>�>%$$K     �  @$H   0 P `  ���@H  
4O55$%!*O!OXYX+/?�?9�999/�9?3//+]+]F�=(  =
+</+<+H�+}�]�+]���9/]910]]6$32#"&'#!"32>54.#7>54&�-�[�m<1Qj96\E'E��zd�6	
;��-a��5=D"=gK*%HiD(DjK'Q���.VwJS�^:
8Rj@k�F8&9\5������#GkH/Q:!�%<W9FXl�W�:�@

R

R���@G	
HR

MRL$P`����/
?222?/33/q]�]9/]�+�+ć+�+�++��+��10!>7!!>7l N

E1�� :61��._-:��T]YIG?{��7ou~F��X7����4l@I--33!G0
11+O_o��O6P6�6G�+�+/+?+O++�++14O
02O&?�?99�2/]]�]�]]]q9/993��]10]%2>54.'".'#".54>77!-JoK&$2%TSL;# 8NX
)-))D1Z��q�{@\��r�$G�;g�P:aRH!#3F_}P=^A!]��6gnwE�ΏKBu�\�Ŏ_8�����M9m@E

1/1?1O1117�##;�;7G,H/?O�12R$'11'RO	?�?�9/9�99/]���]�]�9/]910]]]%267#".54>?.54>32.#"3"�Y�E�.br�S\�f60UwG'D4?s�dEucQ�#gB"A1.Ql?E�mCO�O=�+B.0TrBCeG)'9J,IoJ%3Q:T?81$'6"�!F?;K1�n@�6P@6%%H0.P`/?85H/'?'O''�'�'�''4/5Q6/?�22/]���]]2�]�10]]]'>54.'.54>?#!7@"J���pC;W97gP0-;#�'F_8>nR0>k���T!#!��%̽@�����J'3&(:Q;1aYO a%)-)!,FeIZ�����C�%�W�M)�@k

K�P� 0P`���+"K0O��"'P'XYX+?3?3?39?9�999///]q/+<�+}��]qrqF�*(*
+</+<+H�+}�10]]!>54&#"!64'!3>32����	LL/^Q=t��
IZoE��1.-)���..)
?B(Ji@��SDy*01/*J8 �l��L�#/v@T('
	
	)*H--P-`-�-�-�--1O1HP`���O))Q$Q?�?�9/�/]�]�]�910]]]]]]]]]2#".54672>7!"!>54&߹�%o��rR�^3I$*MD;��$2�*LD9z
M���@�[���U;x�x:�Pvo��-m��A�/CZ7p+k��;{.~oC
:�@< 0�����/	

K	
		
		
O	o		�	�		@$'H�		���@H		
XYX+??99/+q+]q/F�(
		
+</+<+H�+��}�]]]q10%!.5467!`*��
���V;E'>N��0%�:�@n


	RK



KO��	O		P	`	�		

	XYX/
]+?3?29�]]�83/]//+<��+��}�9]�+}�+���10]]]]!!!!	�E���f�.�*�q��:��	�2�����!|@QK
I!RK!!!F%�O##


/?OP?2?�0/]]3]]�]9]]33�+�+��]]]]10]]).5!'.#"'>32��/+12�����#. 2!f9QoK.�KRLQXV�S�IE\7	�+\�b��X�:*�@Y##$%"#$#%K"#""#"""�"O""P""�"",�,p,#K�@$H 0P`���@H"%	#P	XYX+??�???3999/+]+]/F�+(+
+</+<+H��+}�/]]�]q]qF�+(#""+
+</+<+H�+��}��10]]!>7##"&'#!!32>7!�9�V9OI��#vAM2YI6p�
,56ci0*���!<LY-QsFB��"IC6i_:{@:G_oP`�P`p���RK����@HO��?3?33/]q3+]��+}�+��]qr�]q�10%>54&'!!!�N}W/	"	-Ol�G����_���g#:8"T�����J:1�p��F�@V
;;	0,,	?93.$HP``HH90E@EEH/.?.O..�.�.�..4O EQF /?�29/�9/]��]���]�]�9/9910]]]]]]]]'>54.'.54>75.54>?+7�$Q�nA/Ne7%g��N5U>2bM0-:�-!;R29oY7D�ӏ;fJ*6Zs<EQLL&̹	5P:.:$�)JsU'7)"%:U?0^XP"`)D)!-"+FiLX��d!8O2<V<%
���?���MRC���:5�@_//K454454454 4P447K

�	
!4
!O,5
OXYX+?�2?�?99//]]F�6(
6
+</+<+H�+}�33]]�]]/F�6(5446
+</+<+H�+}�10]]]]]!>?"7>3!#32>7#".5467r;?C"��"GD>'K?1',56s%�o.-	 498>[;k||�ں�JL���}�
����!2,�
#?X5A)��W�O+�@9	GP'�'�'�''--K�@$H 0P`���@H"O
OXYX+?�?99�99?3/]+]+]/F�,(,
+</+<+H�+}�]�]�10]]2#"&'#!>"32>54.�g�yBK��{j�/S���U��s6V@-2&sBChH%2DO>w�r��eGB7 �W�v��K�.W}O��6>L��[GgB L�n=O5P@6	4--%H`��7p7HP/`/�///?/O//*	O?�/99/]]�]�]��10]]]]2.#"'>54.'.54>�|�2�&0:":fTB-8^F8u_=-<!�!DhHP}U,&Lp��OVG�'4Ujme$+>0('>ZD3^UJ a
")/(!6MgGE���}L@���:3J@3,,HP%�%�%/%?%%55051G��/?O)O
 O?�?�/]]�]�]]��10]]".5467>3!#".''2>54&'#"s�v<	 ���J%�-67"L��YyJ GF�lOk>p�a$T(��w4�DLT.~ߦa�H��lFs/#O�_#?x�9���:(�@_�
�+(K(((*'J'*J*JK'(''(''''' '@'P'`'�'(''(O(OXYX@* ***]]]]+?�2?�99//]]F�)(('')
+</+<+H�+}�]]]]]10]]]"7>3!!32>7#".54677'OE5'+25�%��m.-	 498>[;k|
����!2,�
#?X5A)f��d:'r@9

H�))?)K'&''&&&'O&�&&&&&'P'XYX+?22?�99/]]/F�(('&&(
+</+<+H�+}�]�]�10]]32>54&'!#".54>7tRV>\B)	=`��lc�m:v:��!RN^2Wt��Cl�-2�Xgʶ�q@+X�\))%U@�WcR#3@�,#))!!!P5?5/H)%H��)  
!*
))+)F)V)<)!+!F!V!<!+F<+F<)!)!N!!!P`p�����	!R* Q

$RXYX+?�??3�2?�//]q/F�4(!4
+</+<+H��+��}�]]q]]q]]q]]q������]2/]�]3/]�]]3]]]]10]]]]]]2#.54>7>">54.R�[1Y���O�NZ�o>M�ӆAiK(^[h&ɚ/&he�T$
&P7f�Y���e�i�Bu�g��k�Y��^v��̮8W?��
[��l/Q;"�L�W�P$!@>
�t����@!H{� %H�{� Ht����@\%H[TRMR
N&!?O
!�
�

&

P@&�&]]?�?3?399�]/]��]]9]�+�+ć+�+�]]+r+]rq+r+]rq������������10]]]]]]]]2>7!!!'.#"7>16RD<!	�3����#���dH$#')+
!ZPS��^
),(
;�N��o!!<�@>�Lb8�b�W<"@�!!K0@�KP�
" "M!!! 0P`p���	$# 
 O"!XYX`$]+?3�2?3?�2399999/]/F�#(!#
+</+<+H�+}�������]]/F�#(#
+</+<+H�+}��]/F�#(#
+</+<+H�+}�10]]]%>7!!$467!!FMjI-vyW�יO��O�C	ep1P:�� GqQ`��}�t8�k�y*c/��7-M8!�7��hOG�@$88/	/���G�G*F*E&EGEFGF�@HEFEFEEE E@EEEE=GP��IF=G�1�1/1?1O11�11EFFBO!,7Q66XYX+?3�2?3�29/9/]]�/�]�9]]F�H(FEEH
+</+<+H��+��}�9]]]]10]]]32>7>54&'7#".'##".54>7326?!�	"2!5P:)Y^6h�j6b��^FgI.
I]sEX�]0S����#	
#7&Sm1�H%%B2.V|N$C f��V}�R'O&��~>%Eb>>cE$9m�e��w���*M#0P9 ���C�&�i�@
&�%+55+55��f��d�&�i�@
(&n,*!%+55+55��?����&RT�@.&�.1
%+5+5��f��d�&�T�@(&�(+!%+5+5��7��h�&�T�@H&�HK1%+5+5��$����$��&(i�K@
&�%+55+55�����5~@K�5�4/	[@&�&&&7.///\0101/10�0 0P010.)_3_01_3lmX+?�2??�9/�22//]q]/+<��+}��]]�9/10]]]>32#".'732>7>54&#"!!7!A'g{�K��	Kw�zIu\F�"-:&.@.s|6mf[#����9,�,���$��396�n2-C*�'!A`@@GI�k�����$5&��q@&
%+5+5d����/H@)	
**+1[`_%_%_?�?�9/�/]q�2�2]9/�2]10]".54>32.#"!!32>7ǝ�I4c����NJR��1LjD^�yW-��*S{RN�gO�+x��\��{|࿛l;@l�NG,SA'4`�U�T�^2*H]3uL�e;����A�6��$\�,��$L�&,iK@
&�%+55+55������-����r�&1�@[
\#$##$$#&1'%%''\-/?O[�---3%$
#$'_1_&&$`
`$lmX+?�?�9/�?�9933�]]�/]q9///+<�+}ć��99�+}�10]2#!!#"&'732>7!!2>54&#!}n��JT��0�t6ZUTaqH$>0$*-2?N2��mx�EoN*|���P2b�^o�u=������O"�8b�ړ���5O5Y[%�!�@g!\O@H
\0@P�[�#!
`_lmX+?3?33�9/33�22�]]�//]q/+<�+}�9/+]//+<�+}������10]2#!!!!!!!2>54&#!n��JT��u�-v��'m�m'mx8EoN*|���P2b�^o�u=\�����1���5O5Y[�Z�p@C��[!\� P_
_lmX+?�2?39/�22//]q]/+<�+}��]�10]]]]>32!>54&#"!!7!!A(Ybl;��	Q��P	hrU�E����9,�,�8^�� V$�_�D!X^ �_�����$`&�����!�����H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!����H!����~~H!����}}H!����||H!����{{H!����zzH!����yyH!����xxH!����wwH!����uuH!����ttH!����ssH!����rrH!����qqH!����ppH!����ooH!����mmH!����llH!����kkH!����jjH!����hhH!����ggH!����eeH!����ddH!����ccH!����bbH!����``H!����__H!����]]H!����[[H!����ZZH!����XXH!����WWH!����VVH!����UUH!����SSH!����RRH!����PPH!����NNH!����MMH!����KKH!����JJH!����HHH!����FFH!����EEH!����CCH!����@@H!����>>H!����;;H!����88H!����66H!����33H!����..H!����++H!����))H!����&&H!����!!H!����H!����H!����H!���@
H!@H&��	%+5+5++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++��$�&�CP2��7���G&���J@&y'%+5+5#�h���@r
c��		\ P����
\@P�`	
lmXP

]]+?3/?3�2//]q/+<�+}��]q/F�(
+</+<+H�+}�9/]/F�(
+</+<+H��+}�10]]!!!!!�O�&'�i���'O�h���s���h����3�$$Z�m@D
[�0\0@P�___lmX+?�?�9/�/]q//+<�+}�]�]]��10]2#!!!!2>54&#!en��JT��0#,�Ax�EoN*|���P2b�^o�u=����5O5Y[��${�%$5�l@8P`/?\0@P�_lmX+??�/]q/F�(
+</+<+H��+}�3/]]10]!!5,�'����d��o�h���@|
	�		^\ 0P`p�	``lmXO]+?3�2/3?�2299/]3�]/F�(
+</+<+H�+}�39]3�+}�]]]]qq10]]]]%3#!#3>7!!���O�xO��.^\T$�����MHNT,�t��h�>���k��s��^���>��$��(����-@�	)9I%5E��&6F'&&^%$%%$bc


b
$',/%%%-
�?O,----^�0 $'
,a
%&-
lmX�/]+?3?29/3�2999//]q]/+<�+}������3/]]�93/]�9]]�+}ć+}ć+}ć+}�]]]10]!"&'!.'!3!2>7!!#�v@�*��X	 �"}!4//tt5DY>�6��>90/���
%('v]��
0?H$���Vk<W��?jOD�i$G=-	���
������:c@:16[,[�<!!!"!_'1_'_?�?9/�99�9/]3]]�2]]�]��9/�910".'%32>54&+732>54&#"%>32M~��W
5MfD;gK+��;-;J{Z2h_x�0�'o��vq�v=0[�Q7_F(H��8f�W[:]A#3S;R`�4U=IYwoKa�[,3Z|IP}\<1Mg?b�s@$���@(
	
RR
^


P�����@3H
^0@P�

	lmX+?22?339//]q/+<��+}�/�+]/+<��+}�+���+���10]3!!!>767$�
	
9L���	
�����2b'.*F�?(X&,,����$�G&��J@&�#
%+5+5$`��@lRb	
		
R\
 0@		�?\0@P�
	a	lmX+?222?3339/9�9//]q/+<�+}ć��]]�]�]9�++ć++�10]!2>7!!#!5't9H^?4��"A9.>���
$('v�����?gNI�W*H7&	��
��������n@?^\ 0@@	
H	``	lmX+?�?3�299?//+�q93/+<��+}ć+}�10!#"&'732>7!!V�it6ZUTaqH$>0$*-2?N2�����������O"�8b�ړ���$��0��$��+��d���2$��~@L\\0@P� 0@	`lmXP	]+?3?2�2�q///]q/+<��+}�/+<��+}�10]]!!!!���������s����$Y�3��d����&���s�77�����@l� 0�R^Rc�O/?O��P``?�?333999/]]�]]]�]9�++ć++���10]]]]]#"&'732>?!	!9ip�SJw-v"E'#??@$��)�5>UT*, �.K7%��X�N��8�*7�@y++[ # #0#@#`#�#�#�#�###1[/?O��
)**+7
*
*^

*



@
P
`
�
�
�
�
�
	


7a)
+almX`9]+?�2�2?�2�2//]]F�8(

8
+</+<+H��+��}����������3/]�3/]q�10]32>54.#7#".54>;7!32+#"; R�`5:V8��&6g�zC`���/-u�x=Z���;&@P�b77S8,�q4e�]:aE'���Bz�i��L��H~�c��S�'._�f7aH)������;$�h���@:	
		\\�������@%H
0@P�	`lmX+?3/?�22//]q�+]q//+<�+}�F�(
+</+<+H�+}�310]]]!!!!3O��'�9����h���s��s�t����@R\ 0\

�

_

lmX+?3?9/3�299//]F�(

+</+<+H�+}��qq//+<��+}���]]10]!#"&5467!3267!�k*`kt?��	QPv}\�K�'��#û N$��bAba ��$���@I			
\			\� 0�0p�����@;$H\0@P�`	
lmX0

]]+?3?2�2//]q/+<�+}��/+]q]qF�(
+</+<+H�+}�+�/F�(	
+</+<+H�+}�10_^]]]3!!!!!$����������s��s��$�h���@�	

\





			\��P 0p�����\0@P�	

`lmX0]]+?3?/�2//]q/+<�+}��/]qr]qF�(
+</+<+H�+}�+�/F�(


+</+<+H�+}�310_^]]]]!!!!!!30O�����������h���s��s��s�t���K@)[�\___lmX+?�?�9/�///+<�+}��]�102#!!7!!2>54&#!�n��JT��X�9,�mx_EoN*|���P2b�^o�u=����5O5Y[$#��@\[		\ 0\0@P�__lmX+?3?33�9/�//]q/+<�+}��/qq/+<�+}�9/�10]]!!	2#!!!2>54&#!�'��Tn��JT��0'mx�EoN*|�����P2b�^o�u=����5O5Y[$Z�h@A[�

\0@P�__
lmX0]+?3?�9/�//]q/+<�+}��]]�10]2#!!!2>54&#!en��JT��0'mx�EoN*|���P2b�^o�u=����5O5Y[*����-|@S[o     0 � P ` �  /p/(()�/?O���(#__
#_?�?�9/�99/]]3]]�2]]�]q]q�39/102#".'%32>7!7!>54&#"'>N�ޕJo�����[:QlGb�rM��-"��Q`A�'t���Q��~����=l�W_<bF'6d�Z�!��(BS*GX�g8#����6�@z	'[;;K[�[o22?2O2_22O22�2�2�2�2�228\0@P�`*_
_lmX+?�?�99?3?39/�/]q//+<�+}ć���]q_q�9/]q9�10]_]]2#".=#!!3>"32>7>54.��ۘP
	����ڔL�v��'m�$���{_�mH��`�nH*Op�Q�օ1i0���^W�ڃ����τטS�@x�o%K"��Az�l$R]�].����}@Ic

\ 0 [?O��__
lmX+?222?�9/�29/�]�/�qq/+<�+}���9�+}�10#.54>3!!!	!"3!,�'VH.R�����h���e}�wCrR/7R4�P=[{Lt�p6����3XC(E3��
��7ND]��!�FV@8	88	+@G��H�HPH�H6HP`�6
O;;,+O?�?�9/�9/]�9]q�]q��10]]%2>7>54.#"".547>7>7>32D5`O;5J.5bS=!8M%m�~D$v���Btjc1-c�i[�qWD3#Um�Xj�o9o�ŬBrW$@?[;>mW :CdB �C�ƃk���U�

'<^�aCgE$9l�aJO��q2'���D(5�@e
5G$G,P,�,�,�,,7p75(444K 0O��4/5!(R55!/R!QXYX+?�?99�9/�999/]q/F�6(6
+</+<+H�+}ć��]�]q���910]]]]]]#"$'>3232654&+72654&#"�0Ne50U?%U�ׂ���xt^��yk�u>��/u0��v��с�SU)SH8	A@]A(	$9O2Z�U)k{�l1#B`�:
LU?G�PI<B3[J.��O;�@a++#HO88P8`8�8�88=?=H--/-?-O--�-�--@!H-U6e666Vf6"""Q(	O?�2]?�2]99]]]/+]q��]�]q��10]]"'>3232>7#".54>7>54.D3J7%�Eo�tu�h/5f�bXR&ij;X@+�Q|�o��i):p�lbt=+H�
 6(0BiI'/Pj<LjM8$&0$;>$8(8BgH&7Zu>PrS<" $!<����0GY@=H	999pP���IPI�IEG�$/$?$O$$$?Q,,1OQ?�?�9/�/]]�]�]�]q2]�10>54.#"'>32#".5>7>322>7>54.#"�4Yt@00,:8?@�ޙP"o��xu�z>d��Y7gWD��Qh@"1TA<]F1
j}'Y}N$	�

O�뜍��؉>Aw�e@ �v8'6�=c|?;+R?&$NyU@:n���?��:NH��::'g@=+"$"�"�"�"���' ���� P P
		�@	
	

	�@	"!!�@3   N	
"%%&&''�&�&&�@Y'''�''' P�)
 �"	
%R��/�
'&! 
XYX@)]+?3?39/]]3�29/]qq��]�/9/]/+<�+}�]]������99�+��}ć+}ć+}ć+��}�]]]]q]]]]]q10]]]"&'!.'!3>7!!##c7����%�V,)%\�\%0D5���*B��:W���6;B6y��KV+�&
*UK�|8<����A���N9u@L77((/5G  0*H 0@���;@;`; R%/	R%	Q?�?9/�99�9]/�]�]q��]9/�910]]]"&'732>54&#72>54&#"'>32����1D,&C2�}BsV2UG<4)�Su�X\�h7:Xk12U>#A|���,%=+%:*QD�#?29:
 8+PsI#&E_9EgG&&<Q5L{W/��U���:X��U����&X�RJ�A���))HA����&&HA����##HA����  HA����HA���@H,&</;+%+5+5++++++$a:�@+;K{
P
{	P	�@U	
	
N`?KO��R/
XYX+?2?39/]�99//]/+<�+}ć��]]��9�+��}ć+}�]q]q10]!>7!!#!�[  )2!���%!��	W��:�0
-E1��+����A�����:�@N&&MK?O�	OO	XYX+??�?3�299//]]�]/+<��+}�99�+}�]]10]]]!!#".'732>7!��=`SNUeA)*%$$018JaB �|���ˆP!�F~� ��#�:"@V	JEy�Kv�E	F	R*:Z�H*:Z�H�@*P��� 	R%5U����@
H%	5	U	�		���H	�@.O��	XYX� � ]]+?3?399//]/+<�+}�+]+]+���]q//+<�+}�+]+]+��933]10]]]]]]]]]!>7##!>7!���3�b	
���l=H*v��CIG�C�GF@�F:�� RUO?�W2��#�:�@_KO�

_
�
			K


O

�
�

O�
	XYX+?333?3339/]q3�2//]//+<�+}ć��]�]q/+<�+}���10]!!!!!T�T��Z�}Z���:�T����1:��?���MR#�M+�@\''*+R*+K+��- -RKO��	P"XYX+?2?9?�9//]//+<��+��+ć�q�]q/+<��+��+�10]]!>54&#"!>7!3>32�v	LL/^Q=v��
IZoE��S..)
?B(Ji@��S"KC0,9;*J8 ��.-)�s����W�NS��?��UNF��#�MP�����W�:\?�W��@Vl�@�ZZ77	/\,\,#I
,K+++ 0P`p����	9GjPj�j�jjnTG���/?O_IAL#/
bAOWLO4,+XYX�n n�nPn]q]]+?3?3?��?��999/]]��]�9/]]/F�m(+m
+</+<+H�+}����9����10]]]]"&'!>7##".5467>323>733>32%2>7>54&#""32>7>54&�u�s��T"GQ\7JsN)	\z�R>]F2
�V EP\8KuP)	St���/TG7LP/QB2	Cp/SF6cP-J;-	Dh^���!Q*/E.7a�Q-m?�̂<0L5=Tbe_K2�A|1G/2\�Q;s:�˂=�!Q�h,L!pvN�g4Y&bt�!P�h,L!pt N�d3Y&jk�����:[U�h�:0�@�0	))	!K?Oo���2K0/0/0//0/�/�/O//�/�// //O!/'0	P'XYX`202]]+??�?39999�///]]qr/F�1(0//1
+</+<+H��+}��]q]F�1(1
+</+<+H�+��}���310]]]]]32>7!3##4>7##"&54>7v	LL/_Q<v1$�t�O�HZpE��:��..(?B(Ji@^����+e���,9;*J8 ��.-*
�j�:�@\
K


KO��!@!`!@H
P
XYX+?3?39/�2399//+/]�]qF� ( 
+</+<+H�+}�/+<�+}���10]]]]32>7!!#".5467�GAA!<<@%a��Q!LWd9<gL+I:��
(2?
���%Ee@:�U���:@E@�7
22
**#K�?O� p `���+K�?O p `�7�B988K7677666O6�6`6p6�66 6�67696+7P00((87XYX+?333??3�299//]q]F�A(766A
+</+<+H�+}�]3//]q]qF�A(A
+</+<+H�+}�33//]q]qF�A(A
+</+<+H�+}���10]]]]]%2>7!32>7!!4>7##"&'#"&5467!�-QA/
w{~.PB/
v�
��		AM_<|� FTgA��
�z�(KiA_��%&!y)KkA\��"KC0,9;/L5wq1U?#��$Z&���$&$yU�h:?\@�	33



.123405:.0//K.-..---�-?-O-�-- -p-- -`-�-�-�--..!  K�?O p `�KO�`p� �30O5!-(:(P5 ./XYX�APA]]+?3??2�299�///]q]F�@(@
+</+<+H�+}�3//]q]qF�@(@
+</+<+H�+}�33//]q]qF�@(.--@
+</+<+H�+}���310]]]]]]"&'#"&5467!32>7!32>7!3##4>7#�|� FTgA��
�z{-QA/
w{~.PB/
v��u�P�		AM_wq1U?#��$Z&���$&$y(KiA_��%&!y)KkA\�����,9;/L5M��0:u@8	G�!
K			R

	ORXYX+?�?�9/�33//F� (	 
+</+<+H�+}ć���]�10]]]#"&'!7!3232654&+0M�Ѓ��s���'�U�h�zC�277�zhu�S\�Y+g��MHv��^RHJ&���:!�@k

		G 0�K�#/##!K 0O��!RRXYX+?3?�?329/�///]qF�"("
+</+<+H�+}ć��]]�]/+<��+}�3/]�10]]]!!32#"&'32654&+���0U�h�zCM�Ѓ��s�h277�zhu�:��:�MHvW\�Y+%�o^RHJ&��H:�@E		G�OK 0O��RRXYX+?2?�239/�//]qF�(
+</+<+H�+}ć���]]�10]]32#"&'32654&+U�h�zCM�Ѓ��s�h277�zhu�:�MHvW\�Y+%�o^RHJ��*M-t@JG"P"�"�"�""/0/$(()$/O(	((%OO%Q?�?�9/�9]9]]/]3]�2]]�]q�39/10]]2#".'%32>7!7!>54&#"%>i\�zH9[��nk�rC#3?"9T<&��%6XUQo��U��M,c�sL���k>7`�L2H/&IhB�#ffSUGxY2#���P7�@[3&	+G/
?
O


G�P��9KO��#OO0OXYX+?�??9/�?�/]//+<��+��}��]q�9/]�910]]]]#".5<7#!!3>324.#"32>7>�_��g�xA�Z���T�m��nr�u<��2G,)SK?		3F**RI>�����a;q�h�1:�Ti�t>8l�|B^;Bzd/U$Hb>Ayf3W���:�@�


eHHeHHNK�� @ ` G�/?O�R_oRXYX+?3?�9/]�22/]q��/]�]q/+<��+}���99]��+}�++qq++qq10]]	!.54>3!!#";A�����@6#N��{���U^�6X>")=*���M�
-E[8e�M����$>/3%��?��:�'C�H��?��:�&Hi�@
0&�42 %+55+55#�W��?�@�=5:;R;K

��A#&-" "RK!!!!O��
&@HR#   @H  2"!
;:-2P 202@22PXYX+?�?]�?39999?39/+]3�+2//]//+<�++���������]qF�@(
@
+</+<+H�++�10]]]]]]"&'732>7>54&#"!#737!!!3>32�2V$
!"0"	z	MK/^Q=n���!�8!��

IZoE���
5Tu�W�&>.g..)
?B(Ji@��������10.*J8 ��.-)�AlP,#��T@/	�KO
O���OXYX+??���//]�]/+<��+}�9/�10!!?!�%�O��������:���:e +��?��_N.|@R
'',**P`0&,G���/?O%)O(	!&&!OO	?�?�9/9]]]�9]]]/]]�2�]2]�2]9/10]]%267#".5467>32.#"!!Sv V�ot�q8M_ntw9k�l;��[Q2P?06%��[�ig2O�\2>q�^(^-o�sI):c�IZe@eF�

xl����-KV��#V�&�O�Y�	���((H	����&&H	����##H	����""H	����H	���@	H	@H	���@H	@H�%+5+++++++++��#�&�i�@
&�%+55+55���$�W\�M����I:(5�@a		M(&((&(&5))KG�1O117O�&!(O(5R.(!O).RXYX+?3�2?�?39/��299//]�]]�9/F�6(6
+</+<+H�+}ć��99�+}�10]]]]32#"&'!#".'732>732654&+U�h�zCM�Ѓ��s���=`SNUeA)*%$$018JaB�277�zhu�:�MHvW\�Y+g���ˆP!�F~� �o^RHJ%���:%�@i		%KKO��G�!O!!'%ORXYX+?2?3?3�29/33�22�]]�//]/+<�+}ć��9//F�&(&
+</+<+H�+}ć�����10]]]32#"&'!!!!32654&+�U�h�zCM�Ѓ��sV��Z���TbSh277�zhu�:�MHvW\�Y+��1:�T��o^RHJ��#�����$a�&�t?�&��%+5+5��U����'C�������W��&\�@&&�)5%%+5+5U�h�:/�@�/	((�.!KO����P�?O� 1K/././../.�.�.O..�.�.. ..�.!&/
P&XYX+??�?3999/]�//]]qr/F�0(/..0
+</+<+H��+}��]]]qqrF�0(0
+</+<+H�+��}��9/�]10]]]#32>7!!4>7##"&54>7�G�RYv	LL/_Q<v�
��HZpE��8��`r��..(?B(Ji@^��"KC0,9;*J8 ��.-*
�d���!9!7!7!2#".5467>$"32>7>54.�,����,��V

����Q	
���k�{R��l�|Q0X��z�z�Q�օ1i0���^W�ڃ.`1���_�@x�o%K"��Az�l$R]�].?���M5!7#7!%#".54>324.#"32>7>},���,<�b��k�|C]��v�y>��6M0,ZQC	8L--XPB
M�z�}^����a;q�h���a8l�xB^;Bzd0U$Hb>Ayf4W$,v@;	\0@P�_lmX+??��/]q/F�(
+</+<+H�+��}�3/]33310]]3!!�P�|��������d�#�Z@4	KO��_XYX+??��/]//+<�+��}�3/]33310]3!!�O�t�D����:�����:���&:�A�/&����03.%+5+5��c��&ZCM�*&��д+.)%+5+5���&:��@/&�/2.%+5+5��c��&Zt&@*&�*-)%+5+5����&:i�K@
/&31.%+55+55��c��&Zi�@
*&%.,)%+55+55����&<�"�	&���

%+5+5�����W��&\C:@&&'*%%+5+58�o��/�Ͱ��/�ְͰ��017!8//���8�o��/�Ͱ��/�ְͰ��017!8//���,�B�o@MP�/?O�	���`@ ��p �p9�r^]]]qqqqqrrrrrr^]/�/^]qr�]107!, � �������K@�+;K�	�[��K;;+�������k[K����o_O?/��������o_O?/��������o_O?/9������rrrrrr^]]]]]]]]]]]]]]]]qqqqqqqqqqqqqqq_qrrrrrrrrrrrr^]]]]]]]]]]]]qqq^]]]]]qq/�/^]�]107!&'���������/�10�/107!&'��������!�T'B��B�%+55�?Q��@�
����
XYX�V�v���F�F���f���V	��9)	���VF	�������������vfVF$��������vfF6&��@�������vdRB4$��������p`PD0 i��������pdTD4�������tdT4 ��������td@0 9����rrrr^]]]_]]]]]]]]]]]]qqqqqqqqqqqqqqrrrrrrrrrrrrrrr^]]]]]]]]]]]]_]]]]qqqqqqqqqqqqqqqqrrrrrrrrrrrr^]]]]]]]]]]]]]]]qqqqqqqrrrrrr^]]]]]^]]]]]]qqqqqqrr]]]]qqrrr+?�_]�910//]F�(
+</+<+H�+}��]29107>733�&
$.8 �#?3$3?�Cn\O##NRR(���?Q�j@4	
�




@
P
0
@
�
�


	
�XYX+?��]10//]]qF�
(


+</+<+H�+}��]2910#>7#!+
$.8!�Fk3�Cm]N$H�Q����Q@�


���@HO
	
�XYX+/��]10//]+/+<�+}��]2910%#>7#!]
$.7 �#@4%�3 BCn]N##NRR(�?U�`@)
�




0
�


�XYX+?��910//]F�(


+</+<+H�+}�2/]q3910##.54?U3*!�&���"B�9/hBH^��?���@Y� p 0��
�
�
XYX+?�]�99332103//F�(
+</+<+H�+}��]293//]]qF�(
+</+<+H�+}��]29107>733!7>733i&
%.9!�#?3$3�3&
$.8 �#?3$3?�Cn\O##NRR(���Cn\O##NRR(���?���@]o�


���@P0@��
	�
XYX+?��]33210//]]qF�(
+</+<+H�+}��]293]q//F�(

+</+<+H�+}��]]2910#>7#!#>7#!�
$/7!�$@3$3�,
$.8!�Fk3�Do]N$$NQS(�Do]N$H�Q����7�@1�



����@HO�
	
�XYX+/��]33210//]+/+<�+}��]293///+<�+}��]2910%#>7#!#>7#!
$.8 �#@3%3�,
$-8!�#?3$3BCn]N##NRR(�Cn]N##NRR(�v]�q@	R�@/� `���	P
?/9/3�29/]qq3]]��+��+������10#7!%�뛿��(F-�eS(���J�x���2�sd��@d	

	
R� ����
P
	PXYX+?/99//3�293�2999//]q]F�(
+</+<+H��+��+������������10]]]73%%%%#7!��(N5�Uo(��V"n(��6�V��(OV��d���������d�3Z}��@�@P
`
�
�
�
�

�

/
	
	y	&��VF&���V�vfVFҖ�6&���VF����vf����������tdTD6"��������vfVD4@�$l������dTF6&�������pdP@4$�����td$;����rr_rr^]]]]]]]]]]qqqqqqqqqqqqqq_qrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqqqqqqqqqqqrrrrr^]]]]]]]qqqqqrrrr^]]]]]]^]]]]]qq^]]qqqr/�^]+/_^]q]�10]]]]#".54>32�/Pk=<jO..Oj<=kP/�=kP//Pk==kO..Ok��1z@2
�				� �����@H	�XYX+/�33220/+]//+<�+}��]//+<�+}��//+<�+}�103!!!!!�;!;m;!;i;!;1��1��1��H����-G[u���@��������������X�W��3�PA@Y3~na@	X�'���+�"�'vHA	\.;$
��R��i;��@��f�)������׏����y�V�F�7�����u�W�8������Ǐw�H�(�����'��F�7�����Տď������w�X�I�:������׏��������e�U�F�7��	������x�i�Z�H�,�����ۏˏ��������}�i�X�I�:�(��@-
�����ݏΏ��������x�i�Z�K�<���@��H.������ȏ��������@��H��|�m�]�N�(��
���@z}H���܏͏����x�i���@psHY�J�;�,���j�����@�fiHʏ��������_�o��X�N�9�*������ޏϏ��������{�l�]�N�>�,��
����ڏˏ��������Y�K�2�$���9������_rrrr_^]]]]]]]]]_]]]]]qqqqqqqqqqqqqqqqrrrrrrrrrrrr+rr^]]]]]]+]]]]]]]]+qqqqqqqq+qqqqqqrrr+rrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqqqqqqrrrrrrrrrrrrr^]]]]]]]]]]]]qqqq^]]]]qqqqqqqrrrrrrr]]]]]]]qqqrr??�??3�2���2�2/]����]]���������]]��+��}�102#".5467>"32>7654&2#".5467>"32>7654&%2#".5467>"32>7654&#3�2ZD'
P_d*2W?$	
L[a*.)#
	E/*$'/2ZD'
P_d*2W?$	
L[a*.)#
	E/*$'t2ZD'
P_d*2W?$	
L[a*.)#
	E/*$'�'�>��:_DK&e�I>_CI%i�Ey.SB+Hq.R@W>:5��:_DK&e�I>_CI%i�Ey.SB+Hq.R@W>:5y:_DK&e�I>_CI%i�Ey.SB+Hq.R@W>:5����z5�(@[0@PP�/?�/]]]q�10!�@2�z�����z�&��	���H	����H++I���N@4*��O�����`/��/]�/]]]]]]]��]�9/�10]%73	�C�����iGo)����'!�a�<@'%��@�`/?O��/]�/]q��]�9/�10]#77�����I����G��)na'��O��&-4@#@H�������O]]]]]]55]]55+����T��/�/�10!5!��4����TY�2@��??�/��+��}�10]+3��>��\
�%\@4&&




�


�%!��P'�/�3���]q2�2/]q3��+��}�2]]]10>54&#"#>533>32�J*+=^D�c�.8C*ZVO{-'$`[���.("#. [T.�l�����@N

		
		\

	
0
@
P


�

wa
almX+?�?99//�3�2//]q/+<��+}���������]�10]]!!!!!#73!+M}(��%:��3��2����(��t���������4�@u! #$$4$$q44444�444/4440+,:J6$'#41
#w w/
1't0/	u
XYX+?�2]]?�299//]3�2]3�29999�2]�2/3//]qF�5(445
+</+<+H�+}����������9/�10]]]#737#737>32.#"!!!!!267#!7>7޻���Bm�kc�b5�SG%:, ����yY�_w�*�6']u��p�t_�e50Sm<0TI.K8|�p�k"Zf'���&�i;��{�,dq@�bbGG33'+k;kky$$NM`32E+*(y'7VpE�E�EE@HEE;eqqy`p;;	;s*)(uee8eHeXexe�eQxJx(J++(e(e(7x-xqulmX�sFs�sfs6ss�s�s�swsgsswsgsWs4s%ss�s�s�sWsGs&s@�s�s�s�ss
Ds6s#sss�s�s�s�s�s�s�s�susesTsFs6s&sss�s�s�s�s�s�s�s�susesVsGs6s'sss�s�s�s�s�s�s�s�susesVsFs6s'sss�s�s�s�s�susfsWs8s'ss��s�s�s�s�s�s�sysisZsGs7sss�s@��s�s�s�s�s�s�s{sjsYsHs9s)ss
s�s�s�s�s�s�sdsUsFs7ssj�s�s�s�svsgsUsFs7s'sss�s�s�s�s�s�s�sWs'sss�s�s�s�s�s�s�sws7s&sss:�s�s�s�s�s_r_r_rrr^]]]]]]]]]]]]qqqqqqqqqqqrrrrrrrrrrrr^]]]]]]]]]]]qqqqqqqqqqqqqqqqrrrrrrrrrrrrrr^]]]]]]]]]]]qqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqq^]]]]qqqqqqqrrrrrr]]]]]]qqqqrr+?�??�?�99//3��2�]q��299�^]�//]/+<��+}�99//+]�/]F�r(r
+</+<+H�+}������2�23/�_]9910]]]]]#3267#"&547#7+#!23733".'732>54.'.54>32.#"32>54.+��F&! F*jh	I^Jٕ�k�:e�vA%E�}/��O}Z7�	PO&A06O4*QA(3`�X���@@4'4H(*VF-9j���YHvR-#?Z6F��� %�
hW(-�W`X���/^�bJl��:^D#<6"
"7P9?]<y}04		!5N7JmG#� BgF8N1����9{@Q,))((	4/4?4O44;o!/!?!O!!"w##(w))))/)#)#)s/s?�?�99//]3�23�2/]��]�10]]]]]]]"!!!!3267#".5#73>7#73>32.#"KH@(��
6��XLE`�Jk�ZZ�|I�	~ �*{��dd�U(��&2�=uc�:�% }uC=(EpP,1y̚�9���x20Sm</(1
�����&@UYr@>XWVYXYV�WXWXVWYWYW,I�9�,�$�,��SS[�	B�'L�4W�Y??�??�����99�]�/�����99//��+��}�10267#".547>32.#"%2#".5467>"32>7654&#3�AO�=ZwKPuM%\w�CKlF!�1;Of
6 ?pU1

cw|5>lO.
_ry4:3+,*:5-2��>��SC&@gH&2YzGDK��^#*Je;?K��-M MO� IvU%]0~�["$MxT%[.��W�9gT5Y%IE9fPmMJA�%�$��*�5D�@U4400
##%; oBF<
;
0)(;

q(1((1((1(
( );<610<06-)-u,,t 6vXYX+?�?�9/�99999999//F�E(1((E
+</+<+H�+}��������]]10]]]]232>73#".546?7>7>">7>54&�MxS,b��w"90(?4-�.9GZpFHmJ%#F#%#J!K
9h�c.!Bt�0�%Db<##b�|c,�0<<5P53g_S=#)LlC5b
��D�h@�(6��,�x2:*	�)C5@N$	3CS 
<L\/ /yRy����@H"Ry


���@H
7p"p* *0*�*�**���@/H*E
<w/w''''
w
lmX+?3?3�99//]]��99�+]�����//]+/+<�+}�+��9/+r/F�D(D
+</+<+H�+}�+ԇ+�}�]]10]]]]]]]]]!7!!#!67>73	#".54>324.#"32>7>�!{!������$&
����@p�ZFqP,=m�[��� *4/&*0.(��=,,&X(�����*.'b23��i�u?(KlDd�r>��)7"&E;3,;$#G@5�@�*2{@M
		�@ 
-2,/-/,�/-O--�4	-%+/�-0`404P4]]]?3�2/3��2/]��+�9/_^]�2910^]]#367>73#46767#.'&'%##5!_���	

	�����
�������75-�T8"''$=��H=#')#7��
���]��9t@K
0(
 ((0(@(`(p(�(?O((5[;�;"[5**''*_)_?�?3�22/3/]��]�]��99//]]q3310]]]]267>3!!>54.#"!5!2.54>���]/d�m;��WvH 3a�[[�a3 JwX��=o�e/]���R�ߍf���3�5+es�Je�l99l�eJ�se+���3���f�ߛRb���H"/J@,@�#�#_#P#`#p##1�/�//)?2]]�?�9/�/�]2�]qq�210".54>32!32>7.#"u�ƆE,Lfv�?q��Q��@NX.Kt]M"H$Sn��;L]53WJ<"]��ob�}]<O�у��-# <W7*9dL,�*" *�������Z�' �'�}�Ly _@=0����O���FoF_FF?555]]]5]]]]]555]]]]]]qq5�����Z�' �'�}�Ls6G@+O���qqq?555]]5]]]]]555]]q5�����Z�' �'�}�L�>K@.O���OZZZZ?555]]]5]]]]]555]]q5�����$�' ^'�G�L�WS@4��_O/�O�PPPPP?555]]]]5]]]]555]]]]]5�b�B@	�/�/��29910#.'5>73!�;H:�RR�:H;�'"bADp*$*pDAb"V���@
@�/��299/�105>73.'#�"bADp*$*pDAb"V�;H:�RR�:H;�#�b�B@	�/�/��29910.'3#>7!5�;H:�RR�:H;�#}"bADp*$*pDAb"V���@
@�/��299/�10%>7#.'53+"bADp*$*pDAb"V�;H:�RR�:H;��b�B$@�@�/�/�299��29910#.'5>73!.'3#>7�;H:�RR�:H;�;H:�RR�:H;'"bADp*$*pDAb""bADp*$*pDAb"���&@@�@�/�299��299/�105>73.'>7#.'5�"bADp*$*pDAb""bADp*$*pDAb"�;H:�RR�:H;�;H:�RR�:H;�H�#(@# /�299����299/3�210!!5>73.'>7#.'5� �"bADp*$*pDAb""bADp*$*pDAb"hPX;H:�RR�:H;�;H:�RR�:H;?����-CY@8+
%%.�P`E:�/?O3R?)%% O)?O
?�?�3/9/3�/]��]�29/10]]]]#".54>3236454&#"7>32.#"32>�Z}�e^�Q#,D`}NLzjo974-'kBn�^)��)2)C5(
"2 3WC/�1iji0u��J@m�O;��~a:VL,��
�"\���$>-+F\a`(.L6Q��+��C@(	_?�?9/3]]�]2]]93]3]1035!.'!+�p���D���V��/aR9:Sa-�O�W��$@H	H/?O`/2?�/]���10!!!�C����W6��*����Wb�_@6c		c



	__?�9/�9]99�]/]��9/�+�}ć+�}�105	5!!	!�\��q���K�W�,Ƙ���}9�@����?�/]�105!}9��|���T6@

//9/�/�9]3310]]]]##5!3��7����PQ�v�#3E�@""

4'/��G>�@K@	H$9�*C�/
?
O
?
O

�
�

�G�G�G�GpG`G@G0GG�G�G�G�G]]]]q]]]]]]]]/]q33�2/33�2/+��]�9910]]]]#"&'#".54>32>32%"32>54..#"326v/VvHg�AESa8FxV1/VxHg�ACP_7K|X0��<j0,cF$;)(:�?+07#%=,*>);`GJ�e;p1T>"4`�UM�c9o�1T>#4a�q`e^f5G))H5�/J35H*'G6_�`�
�/�/�103!!�^j�8��^���@

0 ]]/�/3/�/�104>32#4.#"Dz�bc�{Fg5_�NN�^4t��LL��t�b�l98l�d���W��%3@"$$
!H ��P	P?�?�/]q�10]]]".'532>54>32.#"+*%>$,<$4e�c(W?$,<%3e��W�5H*aW�c6�
6G'��W�c6DR7<&@� 7)		@	H>8�7)�((#77,�4#�,��


��T>>�>�>t>d>D>$>>"$>>��>>�>�>t>T>4>$>>>�>�>�>�>�>�>�>�>t>`>T>D>4>$>>>��>�>�>�>�>�>�>�>t>`>T>@>4>$>�>�>t>d>T>D>4>$>>>�>@��>�>�>�>�>p>d>P>D>0>$>>>o�>�>�>�>�>�>T>4>>�>�>�>d>D>4>$>>>�>�>�>�>�>�>�>�>d>D>$>>?_^]]]]]]]]]]]]qqqqqqqqqrrrrrrrrr^]]]]]]]]]]]]]]qqqqqqqqqqrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqqqqqrr^]]^]]]]]]]^]q/���9/9/������9/9/���/+^]]3210_]]"&'&#"5>323267"&'.#"5>323267GK�K�Y'C=:3�TP�J6n0D�4 =?F)H�HBm.'C=:3�TS�L244D�4 =?F�)/!�'-,2*��) �&..2*�;%f+�@�

JZEU+;@	H��
D�
�+K[�@HD
�
4
T
t
�
�
�

k���[;�{[K��oO//?� ��������`@���_?�_@ @b���������/j���oO0�pPO/���O/9��rr^]]]]]]qqqqqqrrrrrr^]]]]]]]qqqqqrrrr^]]]]]]]qqqqqqrrrrrr^]]]qq^]]]qqq_qqqrrr]qqqqqr/]q/+]q�]23�23�2�2�2/+399//]33_]]����������10#7#5!!5!33!!�Ձ�G��Z�Ӄ����#���J������d�GP+@�		��/]�/�9/�/33/33105!5!5!d�������8��d��WY
o@<

R�R�/�0@/]/9=//�/]33/���+�+���+��+�3/10	5!W��@��wAZ�������WY
s@?R�R�

/�0@/2/]39=/33/�/]�3/�22/��+��+���+�+�105	55!W?�������������7�	#@i	y	iy/�/�/���10]]3	%!	���������{����R��Vd�G����/�///�107!!d��>��T"����� H	��//��/��10+#47632#"'.'&#"��TR�?K3%
!$	��V�{{?0(4
''#i���� ���H��//��/��10+3#".54>3232765"�Z(g>2%!%����}86'"%)j��%������?�33105!
�%����iH����??��103#ؑ�H�K�����"�������?�?��310!!#�(�i����n�����"������?�?3��105!#
(�%�����%�H"�����??���3103!!�����H�n���%H"������?�?3��105!3
��%��������H'�	�������??�?��23103!!#����i�H�n��n����H'��������?�??3��3105!3#
���%���K�������(�	������?�2?3��3105!!#
��i�%���n���%�H(�	������?�3?3��3105!3!
���%���n������H3�
�@	
�����?3�2??3�2�23105!3!!#
����i�%���n��n���q�j%�	������?�?�3233105!5!
��A�ّ��������H*A	���?2?3����103#3#ّ�h��H�K	��K����j	1���
	�������?�?�?��23310!!!!#�(�i��i�j�ב�"����	3��	
�
��	��?3?�2����310!###���ב���n��n#���j?�
	A	������?�?3?�����3310!!#!!#����ht��j���o��"����j	1�	��

����	���?�?�?33��3105!5!5!#
��i(�q�ב�)�������	4A		
�

����?�2?33����105!###
ܑב%�����n������j?�		A	�
�����?�?3?�����3310#!5#!5!ґ��t��tj�)F��)ޑ�q�H	1��	
	������??�?���233103!!!!����i���H�"�ב�%�H	4�A	

���?3?3�����3103!!33A��$��H�n�#�n�q�H?�

A������?2?�?�����33103!!3!!ّK�$h��H����"���qH	2�	�

����	���?�?�?33�2�105!5!5!3
��i��q�ב�)��%�H	4A	
�

����?�3?33����10!5!333�$�ב%���n���q�H?A	�		��
�����?�?3?�33����10!5!3!3!5!�$K������q�F�������H6�

���	�������??�?�?��2233103!!!!#����i��i�H�"�ב�"���H8�
���������?�?3?3��2��3103!!#3#A�������H�n��n	��K���H	I�A	�����
�	�?3?3?�?����2�23310#3!!#3!!j���t������	�����"	��"�����H8�	�
�@

��������?�?�??33�22�105!5!5!3#
��i���q�ב�K������H;A

@
�����?�?3?33�2���105!3#3#
㑑h��%���K�#�K�����H	I�


A	�����	��?3?�?3?����2�233103#3!5!#!5!A�������㑑�tH�K	�����)ޑ�����j9�
����	�����?�2??�33��33105!!#5!
��i��i�q���"�h��������:�
�@	
����?�22?33����3105!!###
���ב%���n��n������jJ�
	�����	����?3?3�2?���33��3310#!5!3!!#!5j��t�t��t�A��ޑ��"ב���q�H:@	
��	�����?�3??���3333105!3!5!
����A�ّ�"�������%�H:�
	�@	�	���?�33?33����3105!333!
�ב�%���n��n���q�HL@		A�
�	��
��?3?3�2?�����3333103!!3!5!5!A���������H�"�o�����������HL�
	���@����	���
�?3�2?3�2??33�22�2233105!5!5!3!!!!#
��i����i��i�q�ב�"�ב�"������HM�	���@	
�	
����?3?33�22?33�2�2�2�23103!!###!5!33A���ב���H�n��n��n����n�����H]���		������	��
��?3?3�2?3?3�2�2�233�2�233103!!#!5!3!!#3!5!A��ב�t�t��������H�"���ޑ��"	����m�H��/?3310!!��U�m����m��?/3310!!��U�������H���??3310!!��U���	����H���??/310!!�*��	����H���??/310!!��*��	�*g����#'+/37;?CGKOSW[_cgkosw{����������1����������mUE-
y�@
xlTD,xeM5��@
�dL4�qYA)}�@
|pX@(|aQ9	��@
�`P8�u]=%��@!�t\<$�x�|����|�x���������iI1!��@hH0 
����������gck��h�d`h_[W��T\XT�SOK��H�PLHC?G��D@<D�;73��0�840+'/��,($,�#�� � �������{�@<x�|xTHD0, hTHD0, xx ,0DHTh
l����;���wso��tpl/33�22/_]]3339//////////]]]]]]33�22333�22233�22333�22233�22333�22233�22333�22233�22333�222�222/_^]33333�222223/333339/////33333�2222233333�2222233333�2222233333�2222233333�22222�22222103#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#3#3#3#3#3#ghh�hh�gg��hh�hh�hh�gg�`hh�bhh
hh�ahh�ahh�hh�hh�gg�hh�ahh�ahh�hh�hh�gg��hh�hh�hh�gg�`hh�bhh�hh�hh�hh��hh�hh�hh��hh�hh�gg�hhhhhhhhhhhh"bbbbba```````````c```````````c``````aaaaab^^^^^baaaaa``````�bbbbb#`````��b��`��`��a��a�`T����#'+/37;?CGKOSW[_cgkosw{��������������������������������#'+/37;?CGKO3#73#73#73#73#73#3#73#73#73#73#73#3#73#73#73#73#73#3#73#73#73#73#73#3#73#73#73#73#73#3#%3#73#73#73#%3#3#'3#'3#'3#'3#'3#3#73#73#73#73#73#3#'3#'3#'3#'3#'3#3#73#73#73#73#73#3#73#73#73#73#73#3#73#73#73#73#73#3#3#3#3#3#3#3#3#3#3#3#3#ghh�hh�hh�hh�hh�hh��gg�gg�gg�hh�hh�gg�Zhh�hh�hh�hh�hh�hh��gg�gg�gg�hh�hh�gg�Zhh�hh�hh�hh�hh�hh��gg�gg�hh�hh�gg��gg�hh�hh�hh�hh�hh�hhggg�gg�gg�hh�hh�ggghh�hh�hh�hh�hh�hhggg�gg�gg�hh�hh�gg��gg�gg�gg�hh�hh�gg�Zhh�hh�hh�hh�hh�hh�hhgggghhgggghhgggghhgggghhgggggghh"bbbbbbbbbbba```````````````````````c```````````````````````c````````````aaaaaaaaaaab^^^^^^^^^^^baaaaaaaaaaa````````````�bbbbbbbbbbb#```````````��ba```c```c``ab^ba``�b#`C����IMQUY]aeimquy}��������������������������������	
!%)-159=AEIMQ!35#35#35#35#35#353353353353353353353#3#3#3#3#3#335335335335#3'#3'#3'#335335335335#373533533535!355#%355##5##5##5#353353353355##5##5##5#35335335335#3'#3'#3'#3#3'#3'#3'#335335#3'#335335#3735355#5##5#353355##5#35335#3'#3#3'#3�+jjjjjjjjjjjkjkjkkkkkjkjkkkkkkkkkkkkk��kjkjkkkkkk�kk�jj�jj�kjkjkkk��jjkjkkkk��k?k�k�kkkkkkjkjkkjkjkkkkkkkkjkjkkjkjkkkkkk�kk�jj�jjkk�kk�kk�kk�kjkjjj�jj�kjkkjjkj�Vk�k�jkjkkjkjjkjkkjkjjj�jjkkk�kk��"a"a#`!b!b!`````````````�b��`��`��`��^��`j````````�bbbbbbba````````�````````�``````````��aaaaaaaab^^^^^^^^��aaaaaaaa`````````�bbbbbbb"bbbbbbb��````�bbba````�````�`````�aaaab^^^^��aaaa`````�bbb"bbb{uZT!!{�!T�!���/���/���10!!!�7L1�7}��1mi{@
p/�/^]]�10!!i���mi{"@p/���/^]]���10!!!i��L����Pb��h!!�h���L�!	�XV��R���Z�	��7��������L�	L������R����Z�Z�7��9e+�	5@"	���/?O/?O/?99/]3]]�210!#	3	Vh�=�h���!!��-�����.)'.@D%T%K![!K[DT
#/���/���10]]]]4>32#".732>54.#"�Fz�^^�{GG{�^^�zFV9b�LL�c::c�LL�b9d^�{GG{�^^�zFFz�^L�c99c�LL�c::c���#�@/O/]�/�102#"'&5467>76jnk5R������S4l�9R46n9������:m64R9)���	/���/���103!32>54.#")��Ex�[[�xEEx�[[�xE��}A[�xEEx�[[�xEEx�)��+"@"	'/�����/�����103!4>32#".'32>54.#")��Q:c�KK�c::c�KK�c:MEx�[[�xEEx�[[�xE��}AK�c::c�KK�c::c�K[�xEEx�[[�xEEx�s�cu"<@$

	@�@�/���/]���10]]]]]#"'.5476324'&#"3276c%%%V3eK#%HJfgGJL33FF3331HH13}5V%#%H%V5fHJJGgF3333FE6116���y�!-9D�@] $ t $t+{+{D"(?4.(.(.1%+7+>:h:Y:G:::<A+_+o+A@	H+A+A

/��]�99//+^]�3]]]]33�2/��]�99//�3�310]]]]#"'&54676324'&#"3276#"&54632#"&54632327#"&'y������ZZ����ZZZ���ڗ����٘��Z.  --  .�,  //  ,��L��L>b�^�0H��������[��[׀ٙ����ؙ���W ..  --  ..  --����#�_[����)4`@7*/$'!04h4Y4K4=442-_oO-_---
/�99//]^]�3]]]]33�2/�99//�3�310#"'&54676324&#"326%4&#"326327'#"'�������ZZ����ZZ�.  --  .�,  //  ,��0�^�b>L��LH��������[��[� --  ..  --  ..��[_�#��F�s;3F��/��@
H4.4$w##���@MHH;;	H;/4#4;B�
�
p
?
 

9+>���0/43?3O33/^]��]]]]�/��]]]]�10]]]]+]]++]]]+373#'#5.''7.'#5367'7>7"327654'.�B 965�-�-,��,(�1�7:"B?n0�+�(.��P�(�9p6Eu0bb0uE�`cc1u;� �-�;q9><n3�+� 	��	.#�-�3o?>�_�1�(,=20d��b2/aa��c02�P&�/b@>+�+�++"�"�""P'�''�@%(H
/�+�^]2�qr/]3�2/�qr�]�qr9/3�210.'&547>32!!#!5!"327654&'&�7Z#GS,e<vSVHHj�J��#S>>SW;=>B.*PlzS++VSzmQR�F��F�;G,+G>>=T,G;Q���AQF@(1A;NN?  33FF;A1?J7�77B�??/^]�]��]�99/�r�]�]�r9910.'.'.547>323267632#"'.'#"'&547632"327654'&�6%(
 ? .@$
		�TVWvvWTTUzGS�Z>==@XY<>><
			"O-@"'*R*�Qm}VXTTuuWV+ >=X[===>ZW>>;�/(@& 
0
`
p

"@H"O_/]//+3/]/10#"'!727>'#"'&547>7>76 (_E�#%?BX�c$&����}V+B,-�SZB?N9En&8�6_,+i?~BCF_?B��WVc	%%1E[wK`_B?[J;*U/;q9S<�K/@9M?4=C
/)//99//]923/]�10)7>7>7654&5#"&'&547632.'.547>3267>32#"&'.'F��Tl)@4:Z+X-;a)OII]P3N(a<tPPET3V$IPPp>�2+C.=�#!K2dmy;*&StsOP"4&sN&(PNmVb(%)LtvSP<3=-Q}.-L'f��Zy'&@)@Pp�///]�10^]].'.'.'.547632>32b*gL8E+%DFfbN/"�X2U#F)N<Kl ,8e02�fL]Aj8gGFHP6wu$"F^VX-wK`�76nB�����0K/]/]10.'.'.'>7>-qEEt/'xSEj(
#&b<^Q2�P;`�N�]]�5(�o]�H: 9�Pwc;�kM��;�!0@!
@O_o�!

//��]9/9/��]��2103#>54&'&'#"&547632�L�3:0./9@%%Hl9:<?P,.�d�E�UN�;A|;<c(Q	�?b&K6.I<<����"%#"&547632%#"&547632�$&%X3999>Q0*��%#Jj9:;<T--��?e#%'6/L9;�[��>b&J5-L9<�f����"&#"&547632#"&547632%%5�$&%X3999>Q0*��%#Jj9:;<T--���&��D?e#%'6/L9;���v>b&J5-L9<�g�u���J�&IL�1�%���H%���@H%@H�� �  ]]55]5+++��J�&IO�W�!@!!H!����H!���@	H!@H!����H!����H!���@H!@H���]]5]5++++++++]�W��+@	H
�0
��/�/�/�]�9+10]2#"'732654&#*7>rPX'S�\.6*RF24
;9:TB5Q7v'& m�D<@��XYX+/�0//F�(
+</+<+H�+}�10!�; ;1��P�9�����

�
��/�10#>7#73h	%-�6Al(��,G=30T&�F���
�@H�����@

H��܀�X+?3?39/3�29/+]q/F�(
+</+<+H�+}�������3/3310]#7!7333X�����Tg�
��>B�����K���+U���%o@3

$%@	H%0@�'%%H���@H%"�����?�?�9/�99+q+q��/]�]�+210]]!!>32#".'732>54&#"#����$G61O9+RzN@aC(�./,
5(<&����7Q5?fJ(5F(*+#/82/���&@�@���
�?�2?��9/�910#>7!7!�OwU8�B`|J�pCO���YS���H�]���!1?U@1-�
�;;;�'A5�2�""8��*��?�?�9/�99/���99//�99�]10]]2#".54>75.54>2>54&#""32654.�w�WL4A(QyQEfC!"4A'8-Pn %	(*)7 <<032E
&�^RE^O>1U>#5I+.A-O82K1��"#&,0}<2#087�?���	�/���/�10"&'732>7���6�*6B*7VC1pMh����J*F1#9J&1CnO,X�����

�?��/�10#>7#73p	%-�6D	l(��+H=30`.�@�tI
��

�?��/�10>733#\C1�6D	l(�NWu/0`.�-���@�/�/�10%73]��	����+��5@	@�/�/�]107%!	�~��+����+	@�	�/3�9/�]10#'##73����7�����}+	@��/�29/�]10#73373H������������k�i/Ko��:@�@p��@
���/2���3/��]�10]]]".#"#>3232>73�,QKD%
�$?bI-SJB%
�$?a�&/& --fX:&/& .-fX::�� @	@H@�/�32/��+�10?!!57!:
�
��
�
����+��+���c'@
�@��@��/��2/���10]".5332673�JtQ*�.>#Nj�?_��,OpD)>)LXEoP+@3	2$��<��V��_��b��i��r��x��$��$7�h$9�h$:��$<�h$��)�)�)$��/��/7�h/9��/:��/<�h/�h3��3��3��3$�h57��5:��5<��7�h7��7�h7�h7�h7$�h72��7D��7F��7H��7L��7R��7U��7V��7X��7Z��7\��9�D9��9�D9��9��9$�h9D��9H��9L��9R��9U��9X��9\��:�h:��:�h:��:��:$��:D��:H��:L��:R��:U��:X��:\��<��<�D<�h<�D<��<��<$�h<D��<H��<L��<R��<S��<T��<X��<Y��II��I%U��U��ULY��Y��Z��Z��\��\��V��Vf��Vm��Vq�DVr�Vs��Vx�V���V���V���[r��[x��\^
\_�J\b�y\i��\y��\{��\|��\~��\���\���\���\���\���\���\���\���\��\���_��_f��_m��_q�D_r�_s��_x�_���_���_���_��a�a�a^
a_�Nab�Nai�Na���a��b��bf��bm��bq�Dbr�bx�f_��fr��fx��hf�mhm�mhs�mhy��h~��h���h���h���h���h���h���h���h���h���h���i��if��im��iq�Dir�ix�m_��mr��mx��o��o��o_�hob�hoi�hp���q�hq��q�hq�hq�hq^q_�hqb��qf��qi��qm��qs��qv��qy��qz��q~��q���q���q���q���q���q���q���q���q���q���q���q���q���q��q���q���q���r�Fr�hr�Fr��r��r^
r_�Jrb�yri��ry��r{��r|��r~��r���r���r���r���r���r���r���r���r���r��r���s_��sr��sx��t���t���uy��u~��u���u���u���u���u���u���u���u���vr��vx��x^
x_�Jxb�yxi��xy��x{��x|��x~��x���x���x���x���x���x���x���x���x��x����������������y�#�{���~�#�������������������������������#�������#�������#���#���#���#�y���~�����������������������������������������������y���}���~�������������������������������������������������������y���~���������������������������������������������������������������������������������������y���~����������������������������������������������������������������������������3��3��3��3��L���������������������1����������������������������������f����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������3����3����������������������L���������������������������������������3������������������������f����������������������������L��������������������������������3������������������{���������������������������������������������������������������������������3�������������������3�����������������������������������������������������������������f��f���������f��������������������������������������������������������������������������������������������������������������������3�������������������������������3��3��3�����3���������������������������3���������������L����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������3�����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������3���������������������������������������������������������������������������������������V��W%����������3V`�Ck&�$^�z�V	��.M
o\>J	�	#	S	Lw	6�	@	2m	��	(,		k	8�	\�	
�|	|�Copyright (c) 2007 Red Hat, Inc. All rights reserved. LIBERATION is a trademark of Red Hat, Inc.Copyright (c) 2007 Red Hat, Inc. All rights reserved. LIBERATION is a trademark of Red Hat, Inc.Liberation SansLiberation SansBold ItalicBold ItalicAscender - Liberation Sans Bold ItalicAscender - Liberation Sans Bold ItalicLiberation Sans Bold ItalicLiberation Sans Bold ItalicVersion 1.07.4Version 1.07.4LiberationSans-BoldItalicLiberationSans-BoldItalicLiberation is a trademark of Red Hat, Inc. registered in U.S. Patent and Trademark Office and certain other jurisdictions.Liberation is a trademark of Red Hat, Inc. registered in U.S. Patent and Trademark Office and certain other jurisdictions.Ascender CorporationAscender CorporationSteve MattesonSteve Mattesonhttp://www.ascendercorp.com/http://www.ascendercorp.com/http://www.ascendercorp.com/typedesigners.htmlhttp://www.ascendercorp.com/typedesigners.htmlLicensed under the Liberation Fonts license, see https://fedoraproject.org/wiki/Licensing/LiberationFontLicenseLicensed under the Liberation Fonts license, see https://fedoraproject.org/wiki/Licensing/LiberationFontLicensehttps://fedoraproject.org/wiki/Licensing/LiberationFontLicensehttps://fedoraproject.org/wiki/Licensing/LiberationFontLicense������	

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a�������������������	����������bc�d�e�������f����g�����h���jikmln�oqprsutvw�xzy{}|��~�����

��� !"��#$%&'()*+,-./012��3456789:;<=>?@A��BCDEFGHIJKLMNOP��QRSTUVWXYZ����[\]^_`abcdefghijklmnop�qrst��u�vwxyz{|}~��������������������������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,-./012��34���5��������67��89:;�<=>?@A�BCDEFGHIJKLMN�O�����PQ���R��STUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~������������������������������������������������������uni00A0uni00ADuni037Euni00B2uni00B3uni00B5uni2219uni00B9AmacronamacronAbreveabreveAogonekaogonekCcircumflexccircumflex
Cdotaccent
cdotaccentDcarondcaronDcroatEmacronemacronEbreveebreve
Edotaccent
edotaccentEogonekeogonekEcaronecaronGcircumflexgcircumflex
Gdotaccent
gdotaccentGcommaaccentgcommaaccentHcircumflexhcircumflexHbarhbarItildeitildeImacronimacronIbreveibreveIogonekiogonekIJijJcircumflexjcircumflexKcommaaccentkcommaaccentkgreenlandicLacutelacuteLcommaaccentlcommaaccentLcaronlcaronLdotldotNacutenacuteNcommaaccentncommaaccentNcaronncaronnapostropheEngengOmacronomacronObreveobreve
Ohungarumlaut
ohungarumlautRacuteracuteRcommaaccentrcommaaccentRcaronrcaronSacutesacuteScircumflexscircumflexTcommaaccenttcommaaccentTcarontcaronTbartbarUtildeutildeUmacronumacronUbreveubreveUringuring
Uhungarumlaut
uhungarumlautUogonekuogonekWcircumflexwcircumflexYcircumflexycircumflexZacutezacute
Zdotaccent
zdotaccentlongs
Aringacute
aringacuteAEacuteaeacuteOslashacuteoslashacuteScommaaccentscommaaccentuni021Auni021Buni02C9tonos
dieresistonos
Alphatonos	anoteleiaEpsilontonosEtatonos	IotatonosOmicrontonosUpsilontonos
OmegatonosiotadieresistonosAlphaBetaGammaEpsilonZetaEtaThetaIotaKappaLambdaMuNuXiOmicronPiRhoSigmaTauUpsilonPhiChiPsiIotadieresisUpsilondieresis
alphatonosepsilontonosetatonos	iotatonosupsilondieresistonosalphabetagammadeltaepsilonzetaetathetaiotakappalambdanuxiomicronrhosigma1sigmatauupsilonphichipsiomegaiotadieresisupsilondieresisomicrontonosupsilontonos
omegatonosuni0400	afii10023	afii10051	afii10052	afii10053	afii10054	afii10055	afii10056	afii10057	afii10058	afii10059	afii10060	afii10061uni040D	afii10062	afii10145	afii10017	afii10018	afii10019	afii10020	afii10021	afii10022	afii10024	afii10025	afii10026	afii10027	afii10028	afii10029	afii10030	afii10031	afii10032	afii10033	afii10034	afii10035	afii10036	afii10037	afii10038	afii10039	afii10040	afii10041	afii10042	afii10043	afii10044	afii10045	afii10046	afii10047	afii10048	afii10049	afii10065	afii10066	afii10067	afii10068	afii10069	afii10070	afii10072	afii10073	afii10074	afii10075	afii10076	afii10077	afii10078	afii10079	afii10080	afii10081	afii10082	afii10083	afii10084	afii10085	afii10086	afii10087	afii10088	afii10089	afii10090	afii10091	afii10092	afii10093	afii10094	afii10095	afii10096	afii10097uni0450	afii10071	afii10099	afii10100	afii10101	afii10102	afii10103	afii10104	afii10105	afii10106	afii10107	afii10108	afii10109uni045D	afii10110	afii10193	afii10147	afii10195	afii10050	afii10098WgravewgraveWacutewacute	Wdieresis	wdieresisYgraveygraveuni2010uni2011	afii00208
underscoredbl
quotereversedminutesecond	exclamdbluni203Euni2215uni207FlirapesetaEuro	afii61248	afii61289	afii61352uni2126	estimated	oneeighththreeeighthsfiveeighthsseveneighths	arrowleftarrowup
arrowright	arrowdown	arrowboth	arrowupdnarrowupdnbseuni2206
orthogonalintersectionequivalencehouse
revlogicalnot
integraltp
integralbtSF100000SF110000SF010000SF030000SF020000SF040000SF080000SF090000SF060000SF070000SF050000SF430000SF240000SF510000SF520000SF390000SF220000SF210000SF250000SF500000SF490000SF380000SF280000SF270000SF260000SF360000SF370000SF420000SF190000SF200000SF230000SF470000SF480000SF410000SF450000SF460000SF400000SF540000SF530000SF440000upblockdnblockblocklfblockrtblockltshadeshadedkshade	filledboxH22073H18543H18551
filledrecttriaguptriagrttriagdntriaglfcircleH18533	invbullet	invcircle
openbullet	smilefaceinvsmilefacesunfemalemalespadeclubheartdiamondmusicalnotemusicalnotedbluni266CuniFB01uniFB02uniF005middotcommaaccentfoursuperiorfivesuperior
sevensuperior
eightsuperior
cyrillicbrevecaroncommaaccentcommaaccentrotategrave.ucacute.uc
circumflex.uccaron.ucdieresis.uctilde.uchungarumlaut.ucbreve.uc
���
LNDFLTcyrl$grek.latn8��������
`nDFLTcyrl&grek>latnJ��MKD SRB ������kern
�g��"<N\��LVdnnx���$F`nF`���N������J�����(���V``f��8����4BT~��	\	�

.
4
>
H
z
�
�
�
� JP~���J�6T������

(
^
p
z
�
�$��<��V��_��b��i��r��x����7�h9�h:��<�h����$����7�h9��:��<�h�h������$�h7��:��<���h���h�h�h$�h2��D��F��H��L��R��U��V��X��Z��\��
�D���D����$�hD��H��L��R��U��X��\��
�h���h����$��D��H��L��R��U��X��\�����D�h�D����$�hD��H��L��R��S��T��X��Y��I��%����L��������
��f��m��q�Dr�s��x����������r��x��^
_�Jb�yi��y��{��|��~���������������������������������f��m��q�Dr�s��x��������������^
_�Nb�Ni�N�������f��m��q�Dr�x�_��r��x��f�mm�ms�my��~������������������������������������_�hb�hi�h���!�h���h�h�h^_�hb��f��i��m��s��v��y��z��~�����������������������������������������������������F�h�F����^
_�Jb�yi��y��{��|��~����������������������������������_��r��x��������
y��~�����������������������������������y�#{��~�#�����������������������#�����#�����#��#��#��#
y��~�����������������������������������y��}��~�����������������������������������������
y��~�����������������������������������������������������������y��~������������������������������3�3�3�L���������������1����������������������f�������������������������������������������������������������������������������������������������������������������������������3��3�����	����������L��������
�������������������3����������������f������������������L����
������������������3����������{������������������������������������������������������3������������3������������������������������������������#�f�f������f����������������������������������������������������������������	���������������3���������������������3�3�3���3��������������������3����������L������������������������������������	��������������������������������������������
������������������������������������������������������������������3��������������������������������������������������������������������������������������������	�������������������������������������3	������������������
�����������������������������������������V��W%����������3g$)/3579:<IUYZ\V[\_abfhimopqrstuvx�����������������������������������������������������������������������ϒN1PK
!<1�Ɉ�x�xAchrome/pdfjs/content/web/standard_fonts/LiberationSans-Italic.ttf0FFTMh��x�GDEF'�h�&GPOS<�|�i�GSUB�<�Kh�POS/2����`cmap��w��cvt a�P��,fpgms�#��gasp	h�glyf'��h+� head�5�<6hhea
��t$hmtxf6�r
�kern�0�6�"loca��!
�maxp�	�� name5pJ��post���tS��prep˫=a� ��aIS_<����ϒN����>�NC���k�d��RT�/ZM����3�3Z�f	��Px�1ASC!��W3>�`���:� �D�99M��s's���V!���`��9x��9%�i9P9��sYs5s��s0s
s.sss�s6sF9Q9&������s��V��V?�q�?V?�?9e�?9Q��V?s?�?�?9oV?9e�?V:����V���V��V���9��9�9�W�(s�`��s.sCsEsE9Ess"�!�"�!�"s"sCs��sE�"9]sVp�f����������a�z�@s�s��s�s-�s�����?�YsL�d�?k��3�dA��5�����L�9��G�=�es�`�/���AV��V��V��V��V��V�����qV?V?V?V?9Q9Q9Q9Q�"�?9o9o9o9o9o��9����������V�V�"s4s4s4s4s4s4 CsEsEsEsE9Y9Y9:9YsCs"sIsIsIsIsIdR�,sVsVsVsV��s����V��s.V��s.V��s.�qC�qC�qC�qC�?E�"sEV?sEV?sEV?sEV?sEV?sE9es9es9es9es�?s"�>s"9Q9*9Q9Y9Q9Y9����9Q9Y�Q�!���V?""s?�!s?��s?@!s?3!s��?s&�?s&�?s&�3�?s"9osC9osC9osCe�E�?�"�?����?�"V:V:V:V:��9���]��9��sV��sV��sV��sV��sV��sV���fV���V��������������!sV��s.�� 9���,V:��9Q�M�������V���G�5������V��9�OL�IIZ����3���.V��V?�?^��V?���?9e9QV?V���?�?0��9o�?V?����V��~V����9QV��F�"q"�?fh�F���orC�"�=q"We�?"�b��c�7sC\[����C�D�Kfh8G^�k�v;C�?fhsCfh;CV?V?��Z?�eV:9Q9Q������?���?�?���?V��5>V?Z?��CV?V����?�?�?|���?�?9o�?V?�q����\zV���?c�]?b?q�>5>�.?t��s.�i-'�,lCsE������sVsV�"���|"g"sCs"s��C�"���E���Y$��V�V�B�&5&�	"F��sEsEs"!�C�!�+�H���"s"�"�V��sV9osC�?�"���f���f���fV����i�is������k�`���������������s�sD�a�3�����V�M���V���\s��s���Ns�����T�%n�X�=�>�c�|������8�������d3�i���1��dLdA�dd?dA���d�"�����������������������������������������������������������������������������������������������������������������g���{��m�m���������%�����)�)�s+�k�UF�Q@;@<�fB�EE�T��X�'�N���K�8���ZXZ��������/qH�����>~�����~����_s���    " & 0 3 : < > D  � � �!!!!"!&!.!^!�!�"""""""")"+"H"a"e###!%%%%%%%%$%,%4%<%l%�%�%�%�%�%�%�%�%�%�%�%�%�%�%�&<&@&B&`&c&f&l����� ������~����r���      & 0 2 9 < > D  � � �!!!!"!&!.![!�!�"""""""")"+"H"`"d### %%%%%%%%$%,%4%<%P%�%�%�%�%�%�%�%�%�%�%�%�%�%�%�&:&@&B&`&c&e&j����������G�/�����v�����������n�����������������������}�y�!����������5�2�*�)����������D�7�(�J�I�@�=�:�7�4�-�&�����������������������ܾܹܶܮܢ�O�L�K�.�,�+�(���� bcdefghijklmnopqrstuvwxyz{|}~��������������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,-./0123456789:;<=>?w<
	

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a��������������������������������pcdhv�nj)tiB��=qDEfu7:9�@kzv��bm<@A8l{���
���8 %��w����������������KRoNOPxSQL@EYXUTSRQPONMLKJIHGFEDCBA@?>=<;:9876510/.-,('&%$#"!

	,E#F` �&`�&#HH-,E#F#a �&a�&#HH-,E#F`� a �F`�&#HH-,E#F#a� ` �&a� a�&#HH-,E#F`�@a �f`�&#HH-,E#F#a�@` �&a�@a�&#HH-, <<-, E# ��D# �ZQX# ��D#Y ��QX# �MD#Y �&QX# �
D#Y!!-,  EhD �` E�Fvh�E`D-,�
C#Ce
-,�
C#C-,�(#p�(>�(#p�(E:�
-, E�%Ead�PQXED!!Y-,I�#D-, E�C`D-,�C�Ce
-, i�@a�� �,����b`+d#da\X�aY-,�E����+�)#D�)z�-,Ee�,#DE�+#D-,KRXED!!Y-,KQXED!!Y-,�%# ���`#��-,�%# ���a#��-,�%���-,F#F`��F# F�`�a���b# #���pE` �PX�a�����F�Y�`h:-, E�%FRK�Q[X�%F ha�%�%?#!8!Y-, E�%FPX�%F ha�%�%?#!8!Y-,�C�C-,!!d#d��@b-,!��QXd#d�� b�@/+Y�`-,!��QXd#d��Ub��/+Y�`-,d#d��@b`#!-,KSX��%Id#Ei�@�a��b� aj�#D#��!#� 9/Y-,KSX �%Idi �&�%Id#a��b� aj�#D�&����#D���#D����& 9# 9//Y-,E#E`#E`#E`#vh��b -,�H+-, E�TX�@D E�@aD!!Y-,E�0/E#Ea`�`iD-,KQX�/#p�#B!!Y-,KQX �%EiSXD!!Y!!Y-,E�C�`c�`iD-,�/ED-,E# E�`D-,E#E`D-,K#QX�3��4 �34YDD-,�CX�&E�Xdf�`d� `f X!�@Y�aY#XeY�)#D#�)�!!!!!Y-,�CTXKS#KQZX8!!Y!!!!Y-,�CX�%Ed� `f X!�@Y�a#XeY�)#D�%�% XY�%�% F�%#B<�%�%�%�% F�%�`#B< XY�%�%�)�) EeD�%�%�)�%�% XY�%�%CH�%�%�%�%�`CH!Y!!!!!!!-,�%  F�%#B�%�%EH!!!!-,�% �%�%CH!!!-,E# E �P X#e#Y#h �@PX!�@Y#XeY�`D-,KS#KQZX E�`D!!Y-,KTX E�`D!!Y-,KS#KQZX8!!Y-,�!KTX8!!Y-,�CTX�F+!!!!Y-,�CTX�G+!!!Y-,�CTX�H+!!!!Y-,�CTX�I+!!!Y-, �#KS�KQZX#8!!Y-,�%I�SX �@8!Y-,F#F`#Fa#  F�a���b��@@�pE`h:-, �#Id�#SX<!Y-,KRX}zY-,�KKTB-,�B�#�Q�@�SZX� �TX�C`BY�$�QX� @�TX�C`B�$�TX� C`BKKRX�C`BY�@��TX�C`BY�@�c��TX�C`BY�@c��TX�C`BY�@c��TX�@C`BYYYYY-,Eh#KQX# E d�@PX|Yh�`YD-,��%�%�#>�#>��
#eB�#B�#?�#?��#eB�#B�-,z�E#�-�P/�@�����o�@��P(�F(�F+��/�o�������@�P�p��	����F�@�F��F������@�36F�F�=�U�=U�U�����������<���P&`�p���@�`�p����@	�2F�?����#�@B�2�?��	`�p��������������������/�o��O���ߩ��+@�	F��:@A
P�����@ $F��$��$��$P��ZH��$����@W�
F�_�o�ϔߔ���������<@�F@�	F�_���������@�%F������Џ���F0�@���o����@'F �0���F�m����u�u�u�uts?sP&и@>rF5qF5@p&,F p0poF5nF5U3U3U��m)ml�`P&_P&KA	[� �16F �@1&*F^Z2\F1[ZHZF12UU2Uo?�Y�R�R�R����R-4F���@�R $F�Q�Q@Q58F@Q%(F�P N)/FN"F	MMHMXM�M�M�M�ML$-FL F�L�LKF7IF HF5GF5�F�F�F�F2UU2UU?_/Oo���?�o����TS++K��RK�P[���%S���@QZ���UZ[X��Y���BK�2SX�`YK�dSX�@YK��SX��BYtstu+++++stu++++s++stu++t++s++sssu+++++++++st+++ss++++++t+++s++st+++s+stst+st+st+++sts+s++s++++su^s++^stssstst^s++s+ss+s+sstu++++++t++^s++^sst+++ssssssss��}�y�:w�������W�����������~��� ���������r������j���[Ltd��j����e�"3���������9+����k��k����n��L��^��[^�t^eo���z������
������iq�B���Hj����g�a��A�SE��y�j���D�XXXX�T����	8	�
���T��
��TP��H�d����� !H"h#H#�$�&&�'8(()4)�-�.�/�0�2P3h4�5h6�7�<X=�>�?�@<@�AB$BLB�DEdFlG�H�I�K�L�MpNPO�P8TTU`VXW�Y`Z<[�\�^0_c d|e�h�j`j�lmm�op�t�vpv�yy�| }�~��L�x�D��������$����������������8������H�x�����(�P��������D�t�����(�X��������L���<�l���������������@�t��������8�h������� �P��8�h�������,���l�������0�`���$�T��������4�d�������$�L�|��������� �P��������0�d�������$�T��������4�d�����X���\Œ�dʐ�hҐҸ���lӄӜ������D֜����$�L�|ר�����٤����,�Tڄڴ���l��4�dޔ����,�������@�p�����,�\������$�<�l��|�����@�p�����8�l������ �P������D�t���������(�X�������0�H��L���(���d�$�D� P��� P�����dt��
,
<
L|��HX

$��\<p��,d����00x�d �"#�$�&&,'�(�*8+D,8-h/0l2�3�4$4X4�4�4�4�5,6�7 8�8�8�8�8�:�<=<=l=�=�>�>�?�?�@A�A�C�EhF�F�HII,I<ILI\IlI|I�J�L$L4MLNdOpP�Q�STT@U�V�XXY|Z�\]x]�_�`�`�a b`cledf<fLgXghgxg�g�jjk�mlo�r`s�t�u�wxly�y�y�{�|�}�}�~�4D��p��� �8�h�p�(�����l�������,�`�������,�d����T�l���(��������h�8�,�����l����������4���������x�H�������������H�x���0Œ���x�Ĩ����0��d���<��ΐ��4Ґ�H���H�t���\Ռ���H֌����h״��dذ��`��4ڐ���d�� ܔ���P���,ޘ� ߈�����X���P��H���l�$�T������D��|�����`�|�������t��|������h�h�����������`���l��Xp�x��8� |�,�D		l

� DdU.�/<��2��<��2�/<��2��<��23!%!!D �$��hU��D�M%�L@	������@&H����/�@	
Hp�?/��]/+�3/]]q�10]+]]#373I���((�(������#�Y@=�/��		�@oO�<o�/�@!)H@H@
H�?3�2/+++]r^]q�3/^]�10#3#3��@��%�?����E�'�y2@I	

		R	�



R�@H���@zH

//!��O:��/?O��?O�@03HO�?O��	
��X+?3?399//]q+]qr^]3�23�2/3/^]q9////+]+3//+<�++��������3//+<�++��������10^]!!#!##53#5!3!33!!�N��XnV��TnT��N�YnXkXnX�@PjNu��l�h��h�lql��h��hl��q��t��-8C,@�uu�z'�'z�\1l1|15<E<u<+%+4&43$3$&(&(�(!%!()34?>p9npn.�.�.@p�..$o%%���@GH%%Eo@H(?s3)>t>�>9>).%o%%%4sP`��X+?3//]�2?3//]9]33�2/+�3/+q�9///]q]���3/�������������]10]]]]]]]]]]]]]#7.'7.54>?3.'4.'>0G��r ���/IgE`L�e:T��drSZ7�ucVG�lB�!<T3ZIvR-��8R4QPrH"�]�g9����%7W=#�5QtT]�[-��
3Ma9-Qc�G:Wz`/D2&�1;X�(:-"�$:O�����1K_�@v�HZ=j=z=J���@HZjz���@	HB 	
H5���@		
H 	
H���	
H���@VS�D�7??O_	7��]�]0]_]o]@]]]a/�	�%��� @HL�2�V�?(���???���?���/+qq���3/]]qq�9/]q8��9/810+++++]+]+]!#3%2#".5467>"32>7654&2#".5467>"32>7654&������5^G)		Zjq/7_F(	Xjq0%F>4B<#C>4?h5^G)		Zjq/7_F(	Xjq0%F>4B<#C>4?� IvU%]0~�["$MxT%[.��WlCxbjHb\Dx_oK`V�T IvU%]0~�["$MxT%[.��WlCxbjHb\Dx_oK`V!����=Oa@6�0�.��@�7�5�V�`�MyF�F{C;�;{:q959U9e9)���@,	
H%%
PI",I8A3SF"�X@XpX�X@""���@HX"X"=�@7O=�=�=�=�=�===cKH@
H3F8A>S'==>]Q�''?''>Q?3�22/]q�9/999/+�3/]�2/99//+]]_]9��10_]]+]]]]]]]]]]]]]]]3267#"&'#".54>7.54>32>7267.'>54.#"�>GS0(e. 3
9&J�;R�}j�d08m�h6f�^EsR-'D^o{?+4A((F>7�hY�@+K@3GoM("@\�O�g;+<%4W="�=}{t3&)	�?;<F<f�JZ�iU(3t?S�e9&E`;<^M>615ggi7.gmp6��6/5vyw4<J_@6Y@$�-[*4@R:!6' @_����,@�@O_��/_o@H�?�/+]q�10#3W�?���`�XW�b@y�m}�l|�(H���@(
H�
@`p/
O


�
�

??/]q3/]8�3/10++]]]#&547W���ej�nhI�ے����������cõR1
q�9�X0�f@yr�br����H���H���@
H
�P���@O/??/]3/]83/]�10+++]]]$4'3
�&ej�nhI�ے�X�^n�X����õ�����qx�T��@eu�o����4;H���&)H���@H�
@	
`p�

���@AH/�_
o

�

@H
@H
		O_�@H�
?�]+]29/9333/++]q3/]+99//]3�+++29/]10]%'7%73-��w��w���-�Zg�I�H�H�I�k)��e�E@,����	���8��	�7��?3]]3�22]]/]3�210]]]]#!5!3!���X���`�T����T�%��K�'@	��


��
/3��/]��910]%#>7#73+
"*{9K
X*�35WKB A�A�i�|p%@/���//]q�/]/]]107!i��P>�@�@	H�/�/+�10373P+�+��������'�I���@?Oo??/]8/810]3t���D� Y��b�3\@At3�3�-{(�(�"[k[kT
d
o�&&&51o/s
)s?�?�/]�3/]�10]]]]]]]]]2#".54>7>2>7>54&#"�Q�i=(���^S�g;
P`pv|�L�kT
wlL�kT
w�7r�y?�S���><x�{!?BJ,�әc9��6�֡U�;��6�֡U�;��5��
�@.[		Rq/?�t
���@H�)9 H��X+?3+/]]3+?�2/]2/]/F�(
+</+<+H�++��3]1037!7%3!5g��#y��W�.ߴ����U�(�@����z�z ���@DH!H%##8H'
op�n

o

�
�


*K&[&�&�&�&&���@H&@	H__os&t?�2?�3/]q/+�+]q3/]q�9/]�310]+]]++]]qqqq#7>54.#"'>32!7����}^6"=U3m�%�Rx�fY�m=;d����v(�]�wb[ZfyL3Q9ww%L�]42]�PU�{ld`dk>�0��a�=�@z8�8r.�.Z$j$z$Z#j#z#���@tH%<no!n6?<O<06@6<66<+��o�?,o+@	H+<tO===1s&0,@,P,�,�,@,P,`,�,�,�,,,&__os?�3/]q?3/]q�9/q�9/+�3/]q9///]]���910]+]]]]2>54.#"'6$32#".'732>54.+76$SQJ8"9R6w�"�:�]�i83`�U��?}�~r�sC�3QpIJpK'*Kf<|(?[?,J5zo��/YQN^;�{W�tDAh>0.[J.)Ie<<X:�
&�

�@[k�[kiX���@H+R�@J@$H@	H/			
Rq�s
��X+?3?399//3�22/]33//+<�++������3/]/++3�+�+�10]]+]]]]#!733!N>�>�s3Ǹ����?��?���L�F�H.��x�,�@&y*�*y�z%�%r�ZjZj$���H���	H,���@FH H+o,,n#�###.o@	H,,(s�					sP`t��X+?�?3/]�9/]]3�3//+�3/]3/�9/�3/�++9++10]]]]]]]!!>32#".'732>54.#"#W!��r6FU1Y�n=G�̅j�mE�*HkJN�[1)E]3`�4����A 5e�]wŠK2XxG+(Q@(,Z�ZCdB!9,s��s�'9�@X�%{3�3s+�+|�:/J/!!%!
#n2/222o�;(o5s�-s
p
-s?�?3/]�9/]]3�/�23/]�9/]�10]]]]]]]]".54676>32.#">3232>54&#"c�k9%~��gDxcH�rQN�rX<�qS�f:H����!@]<GwV/wp6yeBH��i7�?��RDiK$QXF�ыZ_5c�VoȘZ�=lQ.=k�Ts�%S����G@	$n
���@ H�
Y
i
y

n@
Ht?3?�2/+3/�]]+9/�10]#67!7!�\��gN�&������l����䁿J.��6��g�'9M�@{K�KtA�A{.�.u8�8&���@H%8HH���@QHUe

%%5o#nII�#�##I#I-
?n-�---O?n@#*H@	H
:s((Ds0s?�?�9/�93/++�3/]�999//]9��10]]]]++++]]]]2#".54>75.54>2>54&#""32>54.�`�o<-SvJ2T<!C�ȅr�n6Ch;[dB|�DWn?�=hL*<cR{Q)"EiHExZ4Cm�0X}MEz^>4K`;a�yE9f�Ra�^6
&�gW�a5��5Qa-bm@bD(O?(�.StG3X@$)PwO,UB)F��I�'=�@`u4�4z*�*t!�!5/E/%:&*&�&

�oo((�(((? o2n@Hs77_7o777
#s   -s
?�?3/]�9/]�2/+�3/�3/]3�10]]]]]]]]]]#".54>32#"&'732>4.#"32>76�>�la�d3J��v_�l;
%���b��$�v[P�nT7"A[9HzY1">V42j]G�^mBn�OqȕW>y�rDIL"����M��-U\J���:bG(7g�`?`@!"FkI=Q�:#@��@	H��?�/�/+�3/�107373�)�)��)�)k�����&���:B@��


	��
���@!%H



���
/3��?�/]+��93/]�10]%#>7#7373,
"*{9K
X(�A(�(35WKB A�A������f��@
�������@H(H�(H�����@3H�0Pp��?�0p�?o��/]33�]�]/]/]310]+]]+]++]]]]5	��Z;���������Xe�P@:@`�p��O0�����/_o��P�/]]��]�/]3/]]]q3105!5!���X�������f��@
�������@H(H�(H�����@3H�0Pp��0p�?�?o��/]�]�]33/]3/]10]+]]+]++]]]]75	5�Z��㚙on��^��m�'+}@&z�
*Zz�
�+�((#�0���@#H-"��#�##�)�(O"_"o"�"""��?�3/]/��]9/]�3/+q�99//��9910]]]2#>7>54.#"'>73�e�o<=ay<*O@-	�
AYh30XD(&E`;��&�W����'�'�6`�Ma�eJ#3;H.NqU@9F[=0Q9 �z(T�e9�j������]r@&�nv&�&fEvE�Ef\gWwWiTY4�4\8J@���@�HH}e�e)e9e%U{� 	H 	H 	H":2:B:::-999u?�?Q??2?B?�3
33Z3"^
$
^$
h�%%i)y)�)�)�)�))�
$$
��
�
H

HR�1�1�1�1�1�1+111tt����/2Ht���@�"*Hkt{t�t�t=�$R�RR@HRGG$G	GGB,k�
%$c�$"B6�YB�Mۀtptt�t�t�t�t�t@t0t ttt�t�tpt`tPt@t0t ttt<�t�t�t�t�trrrrr^]]]]]]]]]]qqqqqqqqqqrr_r?�?�99//33�/333�29/^]/+]q�]q++3/]]]q�9///]q]3/�]q2/�9���10]_]]]]]]]]+++]]]]++]]]]]]]]]#".5467##".54>3237332>54.#"32>7#"$&54>$324.#"32>7>�Cv�b8O1E]uGT}Q(G��r<`I6'�t+&>kO-S�㸋^0V��i��y+72���o����j?v�����$�\��"?Y8U�W-_cEy`E	��`/@&+-YE+:g�Sxݩf0C(��Tx10.Q��p�ޢ\@t���t��`!09p?4!s���ܴ�Fv���2T<!U��Wx�>f�F$P�����@vI�(XhxR
\R
^	

)
i


{+;����@36H@H/_
?2?339/3�2/]_]++]3/]q9=/]�+�+����+�+���10_]]]!!#3	.'!%K����P�$�
$,����d��
)NA+,AO(�(?�*�@+vj(UUeIYiI$O	_	o	;		 Z&���@UH&&[/?O���@H,*  R ^@	H	*_�O� __lmX+?�?�9/]qq�9/+3//+<�++�3/+]]q�9/+�9]]10]]]]]]]!2#!!2654&#!!2>54&#!P�h�u?��GmI%.Ro��J��\K�������pN�pD�����,SxK��7Pd9S�bC**��_]��@pYkpq����+�@(�#��
yj�e(�(f5*E*U*9I\���@DH+\?�`p�-!Z/

@H
_O_0+@+P+�+�+�+++&_lmX+?�3/]?3/]�/+]�3/]]q�3/+�10]]]]]]]]]#".54>32.#"32>7N1z�Ɂ�ݒH6d���x�ŠT�>a�W��R4g�fZ�}c&QI�a9\��{|࿛l;;`|B7/WE)k����^�s@1Qi8?���@Y��	��+{�{�Td�[??O_�^



@	H
__
lmX+?�?�/+3//+<�+}�]3/]q�10]]]]]]]2#!!2>54.+��b?r���n�5��]I��f��O�瘅ܰ�X+��X��x�r7?i�~@Pp�� 


	/
		R	^@	H_O����	_	_lmX+?�?�9/]qr�/+3//+<�++�3/]99//]]103!!!!!?��X��_����<���?C�	l����@;'HR^@	H_/_o@H_lmX+?3?�9/+]�/+3//+<�++�3/+9/10!!#!�f��n�������e����-�@��"�z&|�}y
�
j�U'e'UZjZ!%!�!*)R)^--++--Z@H`�/[?
O
_

/

@H
*_/-_----$__$_)lmX+?32�?3/]�9/]�/+]q�3/]q3/+�9/9/F�.(-.
+</+<+H�++�10]]]]]]]]]]]]]%#".546$32.#"32>?!7!n8���r��Ow�8����S�8[�X��S6o�t[�x[3�[ U�0T>$W�ڄ�;�6\xB6/S?%i���a�uB-8��?��~@HW
	
R^					
R^@	H
	_lmX+?2?39/3�2?3/+3//+<�++�3/3//+<�++���]10!!#3!3����t�t�����s���T�Q"�W�������&*H���@(#H?R^@	HlmX+?2?3/+2//+<�++�]]++]1033Q�����������@[dt�)9p
R
^O���0p/\/
__@PlmX+?2/]�?�99/]�3/]]]qq3/F�(
+</+<+H�++�9/]10]]"&'732>7!7!}���pd<X?)
����Go���!u|,PqD-��&d�t??��ι
��@
H)I�
5
R^	R	L




/
O


R^@	H	
lmX+?22/3?39/+3//+<�++ć��3/]83/899�++ć++����10]]]]]+!#373	��(�j�������-������M�2����?��E@(@	
HR^@	H_lmX+?�?3/+3//+<�++�3/+1033!?������?��*P@;(K([((@H{(�((0	H$4����H���@	H���@
HVfv����@�

H
H[
k
{
�
�	{'�'W*g*�
t
�tVfXh(RN"(""(
R"N

�"�"6"F"V"$"""	""RM)*))*)***)�)�){)�)D)T))),,4,D,T,t,�,�,,[,�,�,�,K,k,,,RM@	H)(("
*
lmX�,�,�,�,�@�,{,d,T,D,$,�,�,�,�,�,4,,˫,�,�,4,�,�,�,t,K,4,,�,�,�,d,T,+,,��,�,�,�,�,t,T,D,0, ,,,�,�,�,�,�,p,d,T,4,$,,,�,�,�,�,�,�,t,T,D,,,i�,�,�,t,d,T,4,$,�,�,�,�,�,`,P,@, ,,@%,�,�,�,�,o,_,@, ,,9�,�,rr^]]]]]]]]]qqqqqqqq_qqqrrrrrrrr^]]]]]]]]]]]qqqqqqqqqqqqrrrrrrrrrrrr^]]]]]]]qqqqqqqrrrr^]]]]]]]qqqqqqqqqq+?3/2/33?33^]/3/+3//+<�++�]]]]]qqr3/]]]q3//+<�++�9=/^]]]]�+�+ć+�+�10_]]]]]]]]]]]]+]+]+]++q+]+q!>767#.'&'#367>7!�

2��|�	���#)���3j,3043,`%�L�_-5987/f(�`��/C#(0,(#G�?���@+g���(H��HI0	H�
�
�

���@(H�
�
e
u
�
F
V

���@		HR

�@R�@%O��p�R�@"					@	H
	lmX+?33/22?3/3/+3//+<�++�3/]]qq3//+<�++ć+�+�10+]]]+]+]+]+]]!#367>73�����
�����.0)]'�Q��A+0)e3��o���3r@Q+2�2+*$%�%$y��Yi;v�
�	V	f	4ZO/_///5"Z/@H'_
_?�?�/+]�3/]�10]]]]]]]]]]]]]]2#".5467>$"32>7>54.��ޙO	
����K	
����̗b	8k�b�͖c	:l��U�Ԁ1i0���^W�ڃ.`1���_�J�ʀ+W'k�k5K��}*Z&k�k6?I�l@Cz�W[��/R^



@	H
_@	H_
lmX+?3?�9/+�/+3//+<�++ć��3/]]�10]]2#!#!2654.#!Tt��FO�ӄ�Xj�6���-RsF���6d�Zn�yA����;��A\<e�}��*D�@
�(�)v)y���@�H�'Y'i'V	f	�	5: J *C�C);%6�6&.
*
*	
Z#Z@@/@�@�@�@<?@O@�@�@?@�@�@O@_@�@@@F3Z?#O#_##/##@AFH#@.2H#@H#8_
`+_?�/�?3�/+++]q�3/]qr^]�99//^]�10^]]]]]]]]]]]+]]]]23267#".'.5467>$"32>7>54.��ޙO	
y��	$6H-F&R1UwN,
�ʆC	
����̗b	8k�b�͖c	:l��U�Ԁ1i0��kBY6�3_�W	_��{.`1���_�J�ʀ+W'k�k5K��}*Z&k�k6?���@wv�{�VfWR^���dtP@H[��@HR^@	H__lmX+?22/3?�9/�2/+3//+<�++ć��3/+]�3/+9/]]]�+��+�10]]]]!!#!2	2654&#!�Gq�dh�{E���p�����UeI���3`�V�����px��:��@�;�@�k:$7t7p6;3[-�"d"t"dt;;tDTDTy�k%\$$9[/Zp�o�/?�=\@
H�/V/I/xK/*`%%_@P?2/]�?3/�99]]]]]/+�3/]]q9/]��2/�10]]]]]]]]]]]]]]]".'732>54.'.54>32.#"h�ÈQ�
8^�^b�m:"T�lX�tD_��ny�L�6TuNh�Y()T�XW��M��/[�W%>\=?eK3H93;W|Zi�^+,OnA30L5#?W36G2):Y�c���\�k@D����/__oO�R^/_lmX+?3?�22/]3//+<�++�33/]q/]]]]q10#!7!Z����圜�����!�@�eu�YW)	R^9����P���_o�0/# #R^@6:HP`p_@H_lmX+?�?399993/3/+qr+3/F�"("
+</+<+H�++�]3/]]]qqrr^]3/F�"("
+</+<+H�++�10^]]]]]".54>7332>73�h��N���
2XzHW�wU���s��6p�s785��$MJlG#,`�od���ψB����@<K[DTFR	MR	^		+	k			���@/O_oO	_		?33q?33/3/]83/]89=/]�+�+ć+�+�10]qq!#367>73�����/D��� -Y#)'%)#Y/��.�0��!����%H!���@=H�!�!�!v!T! %HO_ H�� HvK[{9+/���H���@	H������H���@�	
H  Hi X I   	HR!)!L			 R	O  R/)/^000R^�	�	�	G	W	H)X)�)x)g)()8))��GWg	))	0V0f0002	2�2�2�2�2�22@-2H{292I2Y2)2I2Y2i2�2�2�22		@H0/!	  /)	2�2�2�2�2�2�2�2�2{2k@�292	2�2�2�2�2�2�2{2k2Y2K292	2��2�2�2�2�2y2i2Y2I292)222�2�2�2f2V2)22�2�2�2�2�2{2i2I2)22	2��2�2�2�2�2�2�2�2y2k2]2I2;2-22	2�2�2�2�2�2�2�2�2y2k2[2K2;2+222�2�2�2�2�2�2�2@��2y2i2Y2K2;2)22
2i�2�2�2�2�2�2�2�2{2k2[2K2;2/222�2�2�2�2�2�2�2{2;2+222�2�2�2�2�2�2�2{2o2229�2�2�2rrr_^]]]]]]]]]]]qqqqqqqqqqqqrrrrrrrrrrrrrrrr_^]]]]]]]]]]]]]]]]qqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]qqqqqqqrrrrrrrrrrrrr^]]]]]]]]]]]]qqqqqqqqqqqqr?3/33?3/33/3/+^]^]]qq+qr3/]89=///]]]]]]]q]]]]�+�+ć+�+ć+�+ć+�+�10+]]_]+++]++]]]]]+]+q+]]]++]!#.'&'#367>732>73~�G/�]ߎ�O��K,5��057<@?=4n&�����?{2:4EC=<7m��8;=CFEhy4����@ $-HH���$-H���@H%,H���@B%,H�k
		
	RKR	L���@3@
	O		�		@"&Hp	�			/	O	_			
�
���@@

H
	?22/3?33/39/+8]3/]]+]q8999//]88�+�+ć+�+�������������10]]+q+q++++!	#	3	3	�����������p^������'�\�#����@i$$$��R^��RLRKO���@p
���@��@HlmX+?3?33/39/33/+]]83/]]qq89=/�+�+ć+�+�/]]q3/+<��++�10]]qqq#3	3q�s���20�H��H9�^����	�@$4D %*H Hf�+;K���%*H���@
HIYi����@A
H		 	�			RM/�@H__?�2?�2/+3/3/]3/�+�+�99//]+10]++r]++r)7!7!!:������u�V������W��u�%���@F
H
*Zjz@@R�P`/?OO���?�?�/]qq3/]�++�2/]3/]10]+]!#3'r����Wu���������5@r�V}�	H���??/8/]810+]]]3@���� �W�W`�F@(vR�O/��?�?�/]22/]/]�++�3/10]73#7!��A�����W�s���(����@	:H���H���@0Hv�Hy�0@`p�� @P`����0AH���@:H?Oo�/_o@H	�9 0@������<?H����@*!H;K��$?"/2/?3]]/]qq]qq+q+qr^]�^]+]qr33�++]qr10]+]+++]	#31��΢p�r�y��� �`���R��/�//105!���LL����&@r��@��/?�/]�/�10]	73���ϡ���.��-N@S�@]11$1+kG{G�G{&�&(F))A3F   :F��UIG@
HBQN	.p(/(((#P.@=NP?3�22?�3/]]99/�/+�3/]]q�3/]�999/�10]]]]]%#"&5467##".54>?>54&#"'>32326732>7C ]U#K[pJNuP(/Qm{�?�j^1UC3�Bl�lY�^1J'*���(VSK8!,A,W�\5		ML#5T;2Sl9QvT5$.[W'C3>gJ),NlAW'��.%$� 7N7 =/<Xg+��4�)?�@fc2s2�2*Zj%G�=0=O=�=�====A/A?AOA$1RK@"H@
H1$6P
*PXYX+?�?�9?3?3/++3//+<�++��]3/]]qr�10]]]2#"&'##>733>"32>7654&�P|T,	X|�c{� 
	�

��S
$KUb@sbK"?Y6>dQ>^N2\�Q2w?�˂=h^;4%	+<H'�Y4/D-�&]�xbO@cC"%[�tyU{|C���N/�@ �'�"ui	�	&6
I���@5HH  ` � �   1-F@
H%Po   P`�P?�3/]?3/]�/+�3/]q�3/+�10]]]]]]]%2>7#".5467>32.#"�8ZF6�Qo�Wg�a/	EUcim5Y�^5�3J1EnW>	
gz!<U41KwT->o�Z(^-o�uL+.TuG-J5(Z�i0j&�E����'=�@@v�j%Ueu9,I,**

*/"RK"""""""/"o"�""���@$H""?;G@
H"/4P
(PXYX+?�?�9?3?3/+�3/+q3/F�>("">
+</+<+H�++���10]]]]]]]".5467>323>73#467#'2>7654.#"�P|T,	X|�c{� 	R��
�
$KTb@sbK"?Y6=eQ=^2\�Q-|?�˂=h^
.3.	��'H<+	X4/D-�&]�xbOAbC"%[�tyU{|E��'N%3�@t�{1�1{+�+*%I���@6
H F�)0)O)�)�))))53F@@
HP33.PP?�?�9/�9//+r�23/]]qr�3/+�10]]]]]]32>7#".54>32'>54.#"��8\K9�Hk�hd�m9U��xn�l5	�$B\80jaP�9��1A$?-YH-9m�e��c:h�W267�#Ba?JbE����
��@J	H��@HRK�@
HPQXYX+?3?3�2?�99/+]33///+<�++������/+]9/10]+##737>32.#"3������&GpV C		&3!���I��z;fL+�*='a��WaM:R�@xJ9Z9j9%#j�9BIBv5U'e'�'*"
*F56R6K))))/p�OT0T�TI@P`;G0@
H560/F)KP$���@	H	>P	PXYX+?�?�9/+?�9?39/+r�3/]�]q3/]qr3/F�S()S
+</+<+H�++���10]]]]]]]]]".'732>7>767##".5467323>7332>7>54.#"�]�_7�udHnO5?RlHO~X/2��9fQ;
�
�Jz���if6ocO$AX3>eP<�W%B]8*LQ%LwS<(L:#7a�P1m?5L0>7(	+<I'��k�v>y.d�p*Y CcB %Y�q;l"�#�@			���@W	HRK


/
O
�
�
�


%WRK@
H#
PXYX+?�?39?3/+3//+<�++�]9���3/]q3//+<�++�10+]]]>32#>54&#"#3"HWiC��
�~
T_@q\Av� �K
�/L5��$Z&�s�'QOX3]�Q���~!B7(!�����	�		���@U/2HO		/	�	�	�	�	FRK`��@
HSXYX�	�	�	�	�	�	rrrrrr+?�?3?3/+qr3//+<�++�3/�]q+q10733"�"�NҴ� ����:����W���@N�
	H/FRK���@'-H@$H@
H@HPSXYX+?�?�?399/+/+++r3/F�(
+</+<+H�++�3/�]10+]73"&'732673!!�!��"E
2;<��'Bc ���7	�T\��@>jN-"V��	��&*H	���@ H�	�	�	WW�����г&/H���@� Hueu
&
F
V
f
�

R		N


RK

�
�
�
/
�
�
�


?O_RK@
H	
XYX+?22/3?3?39/+3//+<�++ć��]3}/]q83/]899�++ć+�+ć��10]]]]++]]]]++!#373	��H� ���i��P�|���k�E�/��!��p@KV`��O/����RK`��_@
HXYX+?2?/+qqr2//+<�+��+�]qqr10]]33! ����4"KMA_�+��H#���@�H9@ARAKB201R1K23233233	2�2�2i2y222C�CiCTC&CFC	CVC�C�C�C�C�C	C)C�C�C�C�CvCYC6CC$CCRK@
H;P*%@30	P"12A2XYX�C�C�C�C�C�C�CvCbCTCDC2C$CCC�C�C�C�C�C�C�C�CvC@�dCVCFC6C$CCC��C�C�C�C�C�C�C�CtCfCTCDC6C CCC�C�C�C�C�C�C�C�CvCdCVCDC6CCC�C�C�C�C�C�CrCdCVCDC4C&CCC��C�C�C�C�C�CfCVC6C&CC�C�C�C�C�CvCbCPCDC$C�C�C�C�C�CtCTCDC$CCj�C@`�C�C�C�C�C�CtCdCDCC�C�C�C�C�CDC+CC�C�C�C�C�C{CDC0C CC:�C�C�Crrr^]]_]]]]]]]]qqqqqqqqrrrrrrrrrrr^]]]]]]]]]]qqq_qqqqqqqrrrrrrrrrrr^]]]]]]]]]]]]]]qqqqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqqqqqqqqqqqq+?2/3/3??�93?�/+3//+<�++ć�^]]]]]]]]qqrrrrr3/]]]q3//+<�++�9/3//+<�++�10_]++!>54&#"#>733>32>32#>54&#"�|	LV9gR<v��
�		@L];y� DSe@��
�|	LV9gR<vz00+KO3]�Q��S"KC0,9;/L5wq2T?#��$Z&�sz00+KO2\�Q��"M'��"��@XH&'R'K/O��)

R
K@
H&P 
'XYX+?2?3?�9/+3//+<�++ć�3/]q3//+<�++�10]]+!>54&#"#>733>32�~
T_@q\Av��
�		"HWiC��
�'QOX3]�Q��S"KC0,9;/L5��$Z&�sC��2M3`@C/t*�*"{�/ G0O��5`5p5'G
@
H
P,P?�?�/+�q3/]]q�10]]]]]]]]#".5>7>324.#"32>7>2k��d`�o=	f��ij�m8�"A\95kaP
&C[66i^O�4i;��t5;q�h/d6~�v88l�cMlE L�t7c*RtH!M�u<d��W1N)?�@YVfv
��س
H(���@jH$'  $ "G0=O=�=�====A&1f1v1�11
�

R
K/?���@$H1*P
6PXYX+?�?3??3?�9/+]3/F�@(@
+</+<+H�++�]���]3/]]q�10]]++]]"&'##>733>32"32>7654&*{� R��	�$KUb<P|T,	X|�$@sbK"?Y6>dQ>^h^#4>�Y'H;*	$-2/D-2\�Q2w?�˂=�&]�xbO@cC"%[�tyU{|E�WeM'=ѵ%H���@HUeu���@;
H**

*/$RK���o�P/???����-2H?���@$H;G@
H"/4P
(PXYX�?r+?�?�9?3?3/+�++3/]]qq3/F�>(>
+</+<+H�++���10]]]+]++".5467>323>73#>767#'2>7654.#"�P|T,	X|�c{� �"�U$KUb@sbK"?Y6=eQ=^2\�Q-|?�˂=h^=7,���6�4/D-�&]�xbOAbC"%[�tyU{|"�N!s@J�W	�!_!o!!@(+H!@H!@
H!!#		R	K



@
H
	P
	XYX+?3??�9/+3//+<�++ć�3/+++qr10]].#"#>733>32�/@fM2n��
�	
	=BK-�Jr�>��>#FB;<>9>[;���K;�@.::d:49d8p6T5!!+!+k�5H2H���@C	H
)I*�*`*�*�***=Io
�
/
?
O

@H
2-Q$ *�***$Q?3/�?3/]�99/+]q�3/]q�99//+��10]]]]]]]]]#".'732>54.'.54>32.#"�D}�o]�eC�/E_?@kM+&Ec=AuY5Gy�ZR�fB�zg5]F)&Ea<9u^<=UT) =[;8(?+.H3(9+%2F_@RsH!=aEOF$:))7*!0Ge]���,�@T 	H
/O_����R

K




Q


PXYX+?�?3/39�29/]q33/F�(

+</+<+H�+��+�/]/]39/10+%#"&5467#73733#3267�%X0Ua
~}ix/��}*0-	fT I����{:*.V��J:'&@\l>N/?O����@�H$4Dt�"&v�&''$RK



?�?O��@H)RK$'$$'$$''�$$$/$$@#H$@H$$ P 

'XYX+?22/3??�999/++]r3/F�(('$$(
+</+<+H�++�3/+]qr3/F�((
(
+</+<+H�++�310]]]]]]]]]]]]+]]]32>73#4>7##"&5467�~
T_@q\Av��
�		"HWiC��
:�{'QOX3]�Q^��"KC0,9;/L5��$Z&�pb:�@AfHR	MR	K			 H		` @P����@@),H0@H	@	H	?3?339/3+/+q+8]3/]89=/+]�+�+ć+�+�10+]!#3>73�ն�e!!��:�@?D??B?�f5:(�@H��(���%H���@Hfv�T������@�%H6�
�
�
4
d
t
9 %H0H��;[)&+';'�' 
H=(R!KR

OR'!'L(((RKW��8!H!X!�!�!8HX�!!(((V(f(6(v(�(�(((*�*�*9*I*Y*y**�*�*�*i*y*�*6*F**���@
H�*�**���@
H	*9*���@�@H('!&6	@	H'
�*�*�*�*v*f*F*4*�*�*�*�*v*f*T*F*6*	*��*�*�*�*�*�*�*i*V*4***�*�*�*�*t*d*T*F*4*&**	*�*�*�*�*�*�*�*v*V*)***��*�*�*�*�*v*d*R*@*0*$**�*�*�*�*t*@d*T*$*�*�*�*�*�*@*4*$***i�*�*�*�*�*T*;*$*�*�*�*t*D***�*�*�*�*�*�*{*d* ***9�*�*rr^]]_]]]]]]]]]qqqqqqqrrrrrrrr^]]]]]]]]]]qqqqqqqqrrrr_rrrrrrrr^]]]]]]]]]]]]qqqqqqqqqqqqrrrrrrrrrrrr^]]]]]]]]]]qqqqqqqq?33/3?399+^]3/3/3/+8^]+]]+qqqqrrrr3/]q89=///]]]�+�+ć+�+ć+�+ć+�+�10_]]+]]]]++]]]]+]]++]]+!#.'&'#367>7367>73,�%#���\�(
U�,
N��N(/3-,&R�J:�!7=9
$#B�6d$$#D���<:@F&
�
	
	
		
	R	LRM	�����@,
	P			/			


�
�
�


���@=-2HP@
H	
 


9�
�
�
�
�
�
rrrrrr^]]]?22/3?3393/3/+^]+^]3/^]]99//^]98888�+�+ć+�+ć�����������10^]]]!#	33	�������^�+��D,�[�������Wg:�@6[*&
RMRK;K[���@'_o_P`P`�! !@!O���@H/@H���@HP?�?3?339]+/3/+]+q]3/]q89/qr89=/]�+�+ć+�+�10]]]]]!#"&'7326?3>73�7fp�U!E+_�Fٷp	
 ')R�_�p>��z/.�� TSG?GK#l��:	@*�&6f�RL���@#/2HW6F%'Gg��������H���@H8	���@�H@H@

HPP����wVG7'������wgW7(ʸ�xh������gG����G'�������scSC4#��������tcSC4#��@�������rbRB0 i��������t`RB2"��������tbTD2"��������tbTB4$9������_rr_rrrr^]]]]]]]]]]]]]]]]qqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qq_qqqqqqqqqqqqqqrrrrrrrrrrrrrr^]]]]]]]qqqqqqqqrrrrr^]]]]]]]]]]]]qqqqqqqqqqq?�2?�2/++33//+^]]]++qrrrr+3/]�+�+�3/]10_]]]#7!7!!,��8����&���ڋ�WA�9�@Vfv	H 	H`p���@]	
H_8�8�888("#R#�_,�

4����,48"#(
9(�@>H8�8�9?�?�9|/+�999999999/]q�3/�/3/]�++�/]33/]3/+]10++]".54>7654&'7>7>;#";n;[=8	]U3VB.F���?1E1 E
.@P+EQ=GM0�W&B\6 ,!SI8X>j���4Q8��2T@,
dN ��!!<G��:{�@	�/?9/�103զ�:��n�a�W��9�@)9Iiy����	
H���@	H(�8�8`8p8�88���@Y	
H88�4
�,@444,R�"#""#_##,#�"""��@

H
94,8#"((�@>H8�9�?�?�9|/+�999999999/+]]/]]33/]�++�/3/]��3/+]]2/]310++]2+732>7>75.547>56&+74;[=8	]U3VB.F���?1E1 E
.@P+EQ=GM0�&B\6 ��+"SI8X>�����4Q8d2T@,
dN?!!<G�z)n' �@Z�)90
H
0	H
��г	H���@	
HPp����
�
�

�@
����&<H
����H���@)H�@)<H�O�o�����@	
H/+]q��+�++�+�/]/]10+++]+]]"&'&#"5>323267jE�I�X&A<82�Q)OMK%233E{4<=D),- �&.

2*�@��;^@B���H��x�����@$H@H@
H�/]?��]/+++]�3/]q�10]]+]]3##7����(�(��������D�%��?�3�/�	Ͱ2�%/�3�Ͱ2�&/�ְͰ�#ܰ"Ͱ"�'ְ6�>���
�.��������������	����>������������%�� � �#9�...@	%............�@�#�9�"�9�	�
9�%�"#$90167#7&'&54776?3&'�em>W��H�s�� |"�OJEq�33|�@�<
q�L��v�1����rj���/
���4>�$����,@u�{�#%���@}H  

o,,"##	#R#q



o.@H#("	u
���/_o�����@ H(s(t,,,��X+?3/]�2?3/]�9/+]q993�299/+3/�9/3/F�-(
-
+</+<+H�++������9/�99]10+]]]]]#!7>?#73>32.#"!!!2673%Ȗ�Dau��7Dn�gFz^@�pIr8��h
*=L-�g�6���.�|��\�f7;U82@Ds}��7dTAU`��s#7�@*:JZH,:JZH,5EU���@H#5EU���@!H#:"J"Z""H"-"5EU���@H#5EU���@�H#:JZH-.�$��@HF9V9f9F9f9�9�9�9�9�9�9�9�9�99@H9
)�3�	9�9�9�9�9�9�9�9t9f9V9F969&99�9�9�9�9�9�9t9d9R9B949"999��9�9�9�9�9�9�9�9t9d9R9D929$999�9�9�9�9�9�9�9�9v949$9@�99�9�9�9�9�9�9�9�9p9`9P9D909$999��9�9�9�9�9�9�9�9p9`9P9D949$999�9�9�9�9�9�9�9�9t9`9T9@949$999�9�9�9�9�9�9�9�9t9d9T9D909 999i�9�9�9�9�9�9�9�9t9d9@949 999�9�9�@T9�9�9�9�9p9`9P9@909 999�9�9�9�9�9p9`9P9@9 999�9�9�9�9rrrr^]]]]]]]]]]]qqqqqqqqqqqqq_qqrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]_]]]]]]]]qqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]qqqqqqqqqqqqqqr/���^]+]]qr/+]���10_]+]]+]]+]]+]]+]]+]]+]]+]467'7>327'#"&''7.732>54.#"�)%dhc6IG~6ah`%+,&dfe6~GH�4iff%)�-Lf:9fM,,Mf9:fL-�G6dge'+*&ai`6GG�5die%)*&iif6I:fL,,Lf::fM,,Mf-�+@�p��k+��&6{�((8��mK[
	R^			RLRK			���@:@H
u

uO
_
o
�

�



	��X+?3?399//]]qqr33�23�2/+]83/8339/3/339=/�+�+ć+�+�/+<�++������10]]]]_]]]_]]]]]]]!!!!#!7!7!7!33�A����;�;��} ��@�����}���/�}��y��9{�*@�	@?�/+M�9/3�21033զ���
���v����T=�K_=@��uD�Djz�cs�l)|)�)�NUNeNuN�X\XlX|X


11*1BH/QLH,[//'V:I�999HoVV@&7H�V/V?VOV�V�VVVVaLH'I/?'O'_''@H'"QG,[�[�[[@/2H�Q�Q�Q�QQ����',HQ���@+HQ[Q[B
=Q4::4Q
`p� ���

/]3/]q�?3/�9999//++qr+q3333/+]3/]��3/]]]+q�2/]�99//99�99�10]]]]]]]]]]]#".'732>54.'.54>7.54>32.#">54.'�&He?9KD~�p[�eC�/E_?@kM+'Fb;AvY5)If<<JGz�]O�dB�w]:cH)(F`89u`<�"1Nb19gO.0Rn>8_D&�=^E/$kPUT) =[;8(?+.H3(;-%8NhC=^E/
&mNRsH!=aEOF&>.$7-%2Kl</E3&/H10E3&3G���{o@N����������{�o[O;/?��@H@H/++]_qqqqqqqqqq3�2/]3�2/3�21073!73	#�#��#�#ø���?����-W�@c{t{t:!J!�!5"E"�"5'E'�'5&E&�&5+E+�+:,J,�,:J�:J�
55*5
11*1H�3S�RR=�>�> >>���@3H3�3�3�33@H3>3>�`p�?Y$�@HR���@�HRRM�.C�8O>_>o>>>8._.o..�.P8`888�8�8	.8.8)�)�YoY_YOY?Y/YYY�Y�Y�Y�Y�Y�Y�Y�YYoY_YOY?Y/YYM^]]]]]]]]]]]]]]]qqqqqqqq?�?�99//^]q]q3/]��3/+/+�3/]]]�99//+q+qq�3/��10]]]]]]]]]]]]]]#".54>324.#"32>".54>32.#"32>7�4^���dc���^44^���d��q\c�僂�cc�䂃�c��e�b00a�bKpP7r&7H/Ec@!CdC1K8's:Ro�d���^44^���dd���^4r�����cc�䂃�bb���@r�^b�o;$;K'!2&.SvHHxV0-8#+Q?&Y�.�>O�@m}�UMeM+;K H/���@'	H?O'�((0�>8� �����@;H/?QE�/?�-o'�''@H''"�-�;J��� `/]]]33�2?�3/+]q9/�/]�3/]+]]q�33/]�9/������10++]]]#".5467##".54>?654&#"'>323267'32>7%$4!9BN/6R9?g�C�D;!;0#	�
1NkG�5	ȋ+TA().UC.�'#9(7L-Na8<51
 4&1P: k\	���
!<0%#;L(L�k��@hgw�hx�gw�hx�
��;
�
�

@H


��_o�@	H	�/o��/]3�2/+]��2/]/]_]+]q��2/]10]]]]%73	!73	2�����f�������i	�m?s%����m?s&����d�G�C@/�����0�H�������?3]]]]qq�/]�/]10%!5!��������?����-;D@E{t{t:!J!�!5"E"�"5'E'�'5&E&�&5+E+�+:,J,�,:J�:J�9���@		
H9 	H6���@�H/::A;A1�2<�77.;;/22;2;�`p�?F$�@H;..2:/`0p00�A@�3�A`A_AoA_2o233	2A33A2)�)�FoF_FOF?F/FFF�F�F�F�F�F�F�F�FFoF_FOF?F/FFM^]]]]]]]]]]]]]]]qqqqqqqq?�?�9///^]]]qq��q223/3/+�3/]]]�99//]833/��29310+++]]]]]]]]]]]]#".54>324.#"32>##!24&+326�4^���dc���^44^���d��q\c�僂�cc�䂃�c�Rǡ3��hUݟ_Q��PT�d���^44^���dd���^4r�����cc�䂃�bb���P��?~of{��PEH��U���|T��/�//10!5!|�s��u�\�'V���@	H	H	H���@&	H��� 
0
?
_
o
�

#�@���?��]�/]q��]�10++++#".54>324.#"32>-Ni;;hN..Nh;;iN-m0A&%A00A%&A0y;hM--Mh;<hM,,Mh<&A11A&%A11AA$�i@G
.����!����	�_��
���	��P���/]]3]3�22]/�/]3�2�]]q��]]q�10#!5!3!5!|��X�������u����v��X��3��*�@�Z&j&z&���@	H)�����@$(H,( .5H(���@6*-H( )H(��@H_o��O_o��(��?�?�3/]qr9/+]�+++3/+]9/]��210+]]]7>7>54&#"'>32!HOS(3\G)DA ;1$}
3NkC?bB#8Zp9#D;/�3g.K>48>I.3H$5"4V=!":N-BhUF!**-q5'��:չ��H!���@	
H:��
�44���@�H	


:44:
)&9I	<t<F<V<f<<)<9<<�<�<�<�<	<<I<�<�<<f<*�)@H):�/�$***&*�***$�)9���	)
	���<�<�<�<�<v<V<F<�<�<I<9<)<<	<�<�<�<�<�<y<i<I<6<&<<<�<�<�<�<�<�<y<i<]<M<;<@�+<<	<�<�<�<�<�<y<k<]<K<;<+<<<��<�<�<�<�<�<�<�<y<i<Y<I<<	<�<�<�<�<�<�<�<�<y<)<<	<�<�<�<�<�<i<9<)<	<j�<�<�<�<�<�<�<�<}<o<_<O<?<�<�<�<�<</<<<�<�<p<P<@<<:�<�<�<rrr^]]]]]]qqqq_qqqqrrrr_rrrrrrrrr^]]]]]]]]]qqqqqqqqqqqqrrrrrrrrrrrrrr^]]]]]]]]]]]]]qqqqqqqqqqqqqqrrrrrrrrrrrr^]]]]]]]qqqqqqqq?�3/^]q?3/]q�9/�9/+�^]]qqrrrr3/^]]r9///]+���910++32>54&#"'>32#".'732>54.+L9F<(B9Da}<Qf:9]B#�'<*2WvDTmC�+>)%B1 0:=#<00?FA:R47J+�,!.: @cD#,ER'6')>+#.����.@u��@/?O��/?�/]�/]�]1073�����������WE:+�@u�DTds+�+E+U+e+���@�
H;%K%{%;
K
{
6Fv�/O�u�W%5Eh%5!(	H	##RK######h#x#�##@/2Hw#8###-v-�-e-6-F-V---'-�-�-�-�-�-�--���@F#H)---�-�-�-�-�-�-x-g-H--*++R+K7����@-%2H(x
 'P*#+XYX�-�--��@���H�-�-�-p-a-P-@-0- ---�-�-�-�-�-�-�-�-�-p-a-Q-A-0-!---��-�-�-�-�-�-�-�-r-c-R-A-2-!---�-�-�-�-�-�-�-�-s-b-S-C-3-"---�-�-�-�-�-�-�-�-r-b-S-C-3-$---��-�-�-�-�-�-�-�@�-t-d-S-D-3-$---�-�-�-�-�-�-�-�-p-`-P-@-0- ---�-�-�-�-�-�-�-�-p-b-R-B-4-"---i�-�-�-�-�-�-�-�-t-b-R-@-2-$---�-�-�-�-�-�-�-�-t-d-R-D-4-$---�-�-�-�-�-�-�-�-v-f-T-F-@"2-"---9�-�-�-�-�-�-rr_rrrr_^]]]]]]]]]]]]]]]]qqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqqqqqqqqq_qqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]]qqqqqqqqqqq+qq+?2?39/9993�23/3/^]+]q3/F�,(,
+</+<+H�++�]]]]]]]qqq+qqqqqrrrr3/]]+]q3/F�,(##,
+</+<+H��++�9��10_^]+]]]]]]]]]+]]]]332>733267#"&5##"&'%��*<&9cO8v��"8&J>A�\>Z^�W�R20C*2\�Qb��-%#�a\_^*%����5�J@.ppP�_������/3?3/�2/]/]99//]��10###".54>3!�p�qP�`52_�W3��#���-X�XT�]2f����&@��/@+H@H@H/+++]�/�1073�+�+���G�W�r@ScsScs��س	H���	H���@1	H�����@	H� 0�/]�/]9/+�/]/]�9/810+++]]#"&'732654&#*73>QQ"IsQ60([K7@ek`H;-H1[,-)�=3~�
�@Nx		R�@H@

H���&6F�&6F����d����@�H	Yi�
�9I	���������tfTD6&��fV&����tfVF4&�������vF6&���V6&��������yi[K)����@�����{oP@4$��������p`PD0 i��������tdTD4������pdTD$�������dPD0 9�r^]_]]]]]]]]]]]]qqqqqqqqqqqqqrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqqqqqqqqqqqq_qrrrrrrrrrrrrrr^]]]]]]]qqqqqqqqqqqqrrrrrrrrrrrr^]]]]]]]qqqqqqqqqqqqqqqqr?3/^]]3?�2^]+]qqrr/qr2/+/+�++��3]1073?33=�v��{��3ka����ke�6�1b@z�z�u
�
u����@,	
H	
H�//@/P///3"��
�'�� `/]]]�?�/�3/]]�10++]]]]".5467>32"32>7>54&�LrK%Hh�OMqK%Hh�*J?2
*;$+K@3	
W�+OnCC ^�V(+OnCC ^�V(�6`M*D1E+8dO*F^M�%��@Og	w	�	hx�gw�hx�



��4�T��;��4d�����@H?
�/o��/]3�2/]+]_]q��2/]/]]q��2/]10]]]]%#7	73#7	73������
��������'ot��?��'ot��?��`0�&y#' ?�^��	�?55��/q�&y�' 
r����?5���0�&sS' ?�^��@A?<�<<]q5?55A��;'+�@��u�e�J�EUe��	U	e	u	�
d
t
R
%
*H

,�(�+{+�++@$H+@HK[k++,#�"["�""@H""-��@Ho_O/?�*�+@"P"`"�"�"""��oP/]]�3/]?��]9/]_]]]+]�3/+]q�99//]++]��9910_]+]]]]]]]]]]]]".54>7>733267#7�d�o<=ay<*O@-	�
AYh30XD(&E`;��&�W��'�'��6`�Ma�eK"3;H.NqU@9FZ>0Q9 �z(T�e9���������&$�2@&�%+5+5������&$���&��%+5+5������&$�Q�&�
�%+5+5����&$�^�&�6� 0%+5+5������&$�g�&�2�%+55+55������&$P���8��%+55?55����@�	
Ho 


R^	


R
^�/���__	O	O		�	�	�	�			
`
_lmX+?2�?�229/]qr3/]�3�2/]3/]9/]3//+<�++������99�+�+���99//]]q10+!!#!!!!!#!_Q�������X��Y_�+� $!�����d���<����#2:3&�3��q�W��&&x���x�7/
%+5��?i�&(�,@&p
%+5+5��?i�&(��@&�%+5+5��?i�&(�Z@
&�%+5+5��?i�&(�Z@
&�%+55+55��QT�&,��{@&Z%+5+5��QD�&,�a�&��%+5+5��Q�&,��@&�
%+5+5��Q�&,��@
&�%+55+55"��#�@8��	y
�
y � �Ueu�
*z[@��������<@H����47H���@R,/HO_%
#
#^



@	H
_O����#__
lmX+?�?�9/]qr3�2/+3//+<�+}������3/]3/]+++]q�9/10]]]]]]]]2#!#73!!!2>54.#!����b=m���n��}��vI��j`0��]I��f���O�瘅ܰ�X+��`����X��x�r7��?�&1��@&�/%+5+5��o���&2�M@4&.58%+5+5��o���&2�%@4&�47%+5+5��o���&2��@5&m:4%+5+5��o��&2��@4&�?O%+5+5��o���&2��@
4&�86%+55+55��=s����H���@HH	H���H���@HHH���@HH
H���@H����� /q/]]q10++++++++++++	7			�b��h^^i��`f����Jb`g��_i����ia�������!.<9@M�-�����v�	y	�1{191i1�,t,6,f,Yi�Vf�*8%%$���@]	H+

+

 	H$	1-2,*/!!!Z////�/�/�/<?/O/�/�/?/�/�/O/_/�///>*Z���@4?O_/@AFH@.2H@H2,1-5"_ 5_?3�?39�9/+++]q3/8�3/]qr^]�2/89910^]+]]+]]]]]]]]]]]]]]]]]]]]#"&'#%.5467>$3273"&4'32>7>�34	
����J��43	
����K�����̗b	.fm�.��5�[�͖c	�H�g1i0���^@<��K�n.`1���_B=��J�ʀ+W'�_R^�S�^��.-K��}*Z�������&8�Y@"&<#&%+5+5�������&8��@"&�"%%+5+5�������&8��@#&_("%+5+5�������&8��@
"&m&$%+55+55�����&<��@	&V	%+5+5���@Lk{�Z
Zj
[�/R^@	!H__ ���@HlmX+??99//]+]��/+3//+<�+��+ć����3/]]�10]]]]]3#3!2#!7!2654.#!ݿ�1Gy�BO�ӄ�X���-RsF����6d�Zn�yA���A\<"����C�@�y5�5z@k{�WCVJ3z3�3;.*:44%!!8H1H*/1?11*1*	H?8O88@H888EBCRCK@
H*B8%Po"""=PCXYX+?2?�?3/]�9/+3//+<�++�3/]+]�99//]��9/10]]]]]]]]]]]36$32#".'732>54.54>54.#""�/�_�c3.EPE.*>I>*4h�e*TND8-J;V8)>G>)-DOD-8S6CoU=���.Oh;A]G624"$@@DPa<L|Y0�!2F*1MC>DN2@U>/5D3 9+#MzX���4��3�&DCU@T&^UX@%+5+5��4��3�&Dtm@T&�TW@%+5+5��4��3�&DK	@U&oZT@%+5+5��4��F�&DR@T&�]k@%+5+5��4��3{&Di
@
T&�XV@%+55+55��4��3s&DP"@
Y&�^T@%+55+55 ���NH[i�@Mzh�hza�ajNzN�N@@|0�0:<+
!:�	u�;iI*F[[*2Fp*�*3*3*_I���@5HCF0_�_�____kQG@
Hd-P8PiiJQ%%8p2/22;���@	H2;>8VP?33�2?3+/]]9/�3/�9/�2/+�3/]]q�3/+�99//]�3/�9�910]]]]]]]]]]]32>7#"&'#".54>?>54&#"'>32>32%32>7%>54.#"�{y3TD3�De�b��#'_v�UPvN'0Sp~�A�mc4XF5�Eo�pz�/;�ph�f3�s�)YVM:",?(Y�e>
�!<U3-aYI�9��1A$?-YH-��8bH*2Sl9QvT5$.[W'C3>gJ)VHKS:h�W267 6N7%?,?`t5�#Ba?Jb��C�W�N&Fx�����;3%+5��E��'�&HC*@4&058 %+5+5��E��'�&Htf@4&�47 %+5+5��E��'�&HK@5&e:4 %+5+5��E��'{&Hi�@
4&l86 %+55+55��Y��&�C�@&%+5+5��Y��&�tW@&�%+5+5��:��&�K�@&j
%+5+5��Y�{&�i�@
&�%+55+55C���0J�@{H�Ht:�:r����@&H+"{"�"6#F#V#$#*%

./���@;//FG)F/F/F?FOFF@!$HF@HFFFL9F@
H1P$)$.
/���@	
H//$$P$$$<P?3/?�9/]9/+93�/+�3/]++]q3�2/9/3/83910]]]]]]+]]].'3%#".54676$32.'7"32>7>54.q0]6�(P$�0P9 	]��ni�j6(�"JE=	+5���FoT:~u;fUB

 A`0-K$5flC9{��a@CA��w7?p�ZD ��
'6i`V"qx�f&Q�\ >��EuW3;=$K='��"G�&QR@(&�1?#%+5+5��I��8�&RC4@4&058
%+5+5��I��8�&Rtf@4&�47
%+5+5��I��8�&RK@5&d:4
%+5+5��I��8�&RR�@4&j=K
%+5+5��I��8{&Ri�@
4&m86
%+55+55R�5uo@����������@5H_/??O�/?�@ #H	�@���O��?�]���]�/+]qr/]+]q9/3�210535!53��������������,���\,:�@i7f)H	���@	H@	H����	H���@cH0H/
66((t!�!f!}/�/i//"0!-
G/-?-O--@H-@H---<G ���@%H"/!0%3P
%P?�??9�9?/+]r3�3/]++]�299]]]]10]]]+]+++++]]7.5>7>3273#"'#.#"%4'32>7>�%'	f��i�iF��Jk��d�hP�
 T35kaP
|��!U26i^O�5�U/d6~�v8CR�g�4i;��s3G^�I6sL�t7c�B3��J�u<d��V��J�&XCB@(&.),#%+5+5��V��J�&Xtn@(&�(+#%+5+5��V��J�&XK@)&`.(#%+5+5��V��J{&Xi@
(&r,*#%+55+55�����Wg�&\tH@ &� #%+5+5��W1�%;�@$H$/v.e-u-e,DZ�I���
H
���@e
H%$%#$G09O9�9�9999=-


R
K/?���@$H-&P

2PXYX+?�?3?3?�9/+]3/F�<(<
+</+<+H�++��3/]]q�10]]]]++]]]]]]]+"&'##33>32"32>7654&*{� 
R�s�S
$KUb<P|T,	X|�$@sbK"?Y6>dQ>^h^	%09�Yu�Y4/D-2\�Q2w?�˂=�&]�xbO@cC"%[�tyU{|�����Wg{&\i�@
 &�$"%+55+55������&$M�N�&��%+5+5��.��-S&DM@T&�UW@%+5+5������&$���&�J�!%+5+5��.��-�&DN@T&�Y`@%+5+5�����U��&$Q�%+5��.�_-N&DQ)
�"]]99%+5��q����&&�S�,&�)�,/
%+5+5��C����&Ftg@0&�03%+5+5��q����&&��@-&�2,
%+5+5��C����&FK�@1&g60%+5+5��q����&&O
�@,&�,.
%+5+5��C����&FO���02%+5��q����&&��@,&�.4
%+5+5��C����&FL�@0&�28%+5+5��?��&'�5@&�$
%+5+5��E����&G��K@
G;FF%+5?5��"���E���E@�w7�7jCT%d%t%9I*#!3!C!6
*..136<2<0-(@1@1@2<R<K@1@@1@@4/4?4O44141_@o@�@0@@@�@@@GG@
H6-Q30�0�0�000#21(P #0#@#�#�#�##���@H#@P<=XYX+?3?�3?+]�3?39/]q3�2/+�3/]qq33//]F�F(1@@F
+</+<+H�++�9��������9/10]]]]]]]]%2>7654.#"".5467>323>?!7!733##467#�@sbK"?Y6=eQ=^2P|T,	X|�c{� 		��,����
�
$KUbv&]�xbOAbC"%[�tyU{|�2\�Q-|?�˂=h^
.3.	������)'H<+	X4/D-��?i�&(M�N@&�
%+5+5��E��'S&HM@4&{57 %+5+5��?i�&(��@&�%+5+5��E��+�&HN@4&�9@ %+5+5��?i�&(O��@&�%+5+5��E��'�&HO��46 %+5��?�Ui�&(Qc��]�

%+5��E�i'N&HQ9�==���==%+5</5��?i�&(�a@&�%+5+5��E��'�&HL@4&�6< %+5+5��e����&*��@/&�4.
%+5+5���Wa�&JK�@T&UYS0%+5+5��e����&*�@.&�3:
%+5+5���Wa�&JN�@S&�X_0%+5+5��e����&*O�@.&�.0
%+5+5���Wa�&JO��SU0%+5��e�9��&*������28
%+5���Wa &J�'@\&�]W0%+5+5��?��&+��@
&�
%+5+5��"gM&K�O@%&�*$%+5+5>4��@
W����@�H/?OR^




o�

R^				���	�@	H

	__
	lmX+?22/3?399//3�23�23/3/+r33/]//+<�++��������3/qr3//+<�++��������3/]+qr10]!!##7373!733#7!�~�~�ǚ�,�,�,�,���)�)��s������-��"
�+�@VV%




���@DH##	RK


/
O
�
�
�


-"%!!R ! K    ���@7
H @
H%Q"�� P	  
XYX+?33/3?9/]9�9/]q3�2/+33/+//+<�+��+���������3/]q3//+<�++�9/10+]]]]]]>32#>54&#"##7373!!w"HWiC��
w�v
T_@q\An����-��
Y/L5��$Z&��]'QOX3]�Q��������!B7(��Qx&,��@&�%+5+5��*'�&�R���!�����H!�����H!������H!������H!������H!�����H!������H!������H!������H!�����H!�����H!������H!�����H!�����H!�����H!������H!�����H!�����H!�����H!��@���H!�����H!�����H!�����H!��@���H!�����H!�����H!�����H!��@���H!��@���H!�����H!��@���H!��@���H!��@���H!�����H!��@���H!��@���H!��@���H!������H!��@���H!��@���H!��@���H!������H!��@���H!��@���H!��@���H!������H!������H!��@���H!������H!������H!������H!��@���H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!������H!�����H!������H!������H!������H!�����H!������H!������H!������H!�����H!�����H!������H!�����H!�����H!�����H!������H!�����H!�����H!�����H!��@���H!�����H!�����H!�����H!��@���H!�����H!�����H!�����H!��@���H!��@���H!�����H!��@���H!��@���H!��@���H!�����H!��@�H!��@�~~H!��@�}}H!����||H!��@�{{H!��@�zzH!��@�yyH!����xxH!��@�wwH!��@�vvH!��@�uuH!����ttH!����ssH!��@�rrH!����qqH!����ppH!����ooH!��@�nnH!����mmH!����llH!����kkH!����jjH!����iiH!����hhH!����ggH!����ffH!����eeH!����ddH!����ccH!����bbH!����aaH!����``H!����__H!����^^H!����]]H!����\\H!����[[H!����ZZH!����YYH!���XXH!����WWH!����VVH!����UUH!���TTH!����SSH!����RRH!����QQH!���PPH!���OOH!����NNH!���MMH!���LLH!���KKH!����JJH!���IIH!���HHH!���GGH!��@�FFH!���EEH!���DDH!���CCH!��@�BBH!���AAH!���@@H!���??H!��@�>>H!��@�==H!���<<H!��@�;;H!��@�::H!��@�99H!���88H!��@�77H!��@�66H!��@�55H!����44H!��@�33H!��@�22H!��@�11H!����00H!��@�//H!��@�..H!��@�--H!����,,H!����++H!��@�**H!����))H!����((H!����''H!���&&H!����%%H!����$$H!����##H!����""H!��@�!!H!����  H!����H!����H!����H!����H!����H!����H!����H!����H!����H!����H!����H!����H!����H!����H!����

H!���@		H&�
%+5+5++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++��Q�&,M4N@&�%+5+5��Y�S&�M���	�����H	�����H	������H	������H	������H	�����H	������H	������H	������H	�����H	�����H	������H	�����H	�����H	�����H	������H	�����H	�����H	�����H	��@���H	�����H	�����H	�����H	��@���H	�����H	�����H	�����H	��@���H	��@���H	�����H	��@���H	��@���H	��@���H	�����H	��@���H	��@���H	��@���H	������H	��@���H	��@���H	��@���H	������H	��@���H	��@���H	��@���H	������H	������H	��@���H	������H	������H	������H	��@���H	������H	������H	������H	������H	������H	������H	������H	������H	������H	������H	������H	������H	������H	������H	������H	������H	������H	������H	������H	������H	������H	�����H	������H	������H	������H	�����H	������H	������H	������H	�����H	�����H	������H	�����H	�����H	�����H	������H	�����H	�����H	�����H	��@���H	�����H	�����H	�����H	��@���H	�����H	�����H	�����H	��@���H	��@���H	�����H	��@���H	��@���H	��@���H	�����H	��@�H	��@�~~H	��@�}}H	����||H	��@�{{H	��@�zzH	��@�yyH	����xxH	��@�wwH	��@�vvH	��@�uuH	����ttH	����ssH	��@�rrH	����qqH	����ppH	����ooH	��@�nnH	����mmH	����llH	����kkH	����jjH	����iiH	����hhH	����ggH	����ffH	����eeH	����ddH	����ccH	����bbH	����aaH	����``H	����__H	����^^H	����]]H	����\\H	����[[H	����ZZH	����YYH	���XXH	����WWH	����VVH	����UUH	���TTH	����SSH	����RRH	����QQH	���PPH	���OOH	����NNH	���MMH	���LLH	���KKH	����JJH	���IIH	���HHH	���GGH	��@�FFH	���EEH	���DDH	���CCH	��@�BBH	���AAH	���@@H	���??H	��@�>>H	��@�==H	���<<H	��@�;;H	��@�::H	��@�99H	���88H	��@�77H	��@�66H	��@�55H	����44H	��@�33H	��@�22H	��@�11H	����00H	��@�//H	��@�..H	��@�--H	����,,H	����++H	��@�**H	����))H	����((H	����''H	���&&H	����%%H	����$$H	����##H	����""H	��@�!!H	����  H	����H	����H	����H	����H	����H	����H	����H	����H	����H	����H	����H	����H	����H	����H	����

H	���@		H&�%+5+5++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++��QA�&,�&@&�	%+5+5��Y�&�N��������H�����H������H������H������H�����H������H������H������H�����H�����H������H�����H�����H�����H������H�����H�����H�����H��@���H�����H�����H�����H��@���H�����H�����H�����H��@���H��@���H�����H��@���H��@���H��@���H�����H��@���H��@���H��@���H������H��@���H��@���H��@���H������H��@���H��@���H��@���H������H������H��@���H������H������H������H��@���H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H�����H������H������H������H�����H������H������H������H�����H�����H������H�����H�����H�����H������H�����H�����H�����H��@���H�����H�����H�����H��@���H�����H�����H�����H��@���H��@���H�����H��@���H��@���H��@���H�����H��@�H��@�~~H��@�}}H����||H��@�{{H��@�zzH��@�yyH����xxH��@�wwH��@�vvH��@�uuH����ttH����ssH��@�rrH����qqH����ppH����ooH��@�nnH����mmH����llH����kkH����jjH����iiH����hhH����ggH����ffH����eeH����ddH����ccH����bbH����aaH����``H����__H����^^H����]]H����\\H����[[H����ZZH����YYH���XXH����WWH����VVH����UUH���TTH����SSH����RRH����QQH���PPH���OOH����NNH���MMH���LLH���KKH����JJH���IIH���HHH���GGH��@�FFH���EEH���DDH���CCH��@�BBH���AAH���@@H���??H��@�>>H��@�==H���<<H��@�;;H��@�::H��@�99H���88H��@�77H��@�66H��@�55H����44H��@�33H��@�22H��@�11H����00H��@�//H��@�..H��@�--H����,,H����++H��@�**H����))H����((H����''H���&&H����%%H����$$H����##H����""H��@�!!H����  H����H����H����H����H����H����H����H����H����H����H����H����H����H����H����

H���@		H&�	%+5+5++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++�����U"�&,Q�y�

%+5�����U��&LQ�<�%+5��QN�&,O"�@&�%+5+5Y�:i@L@P��@P`�����	RKo@/XYX+?2?3/]qq2//+<�++�]q10]33YҴ�:����Q����&,-���!�W��&LM���������&-�7�&�A�%+5+5��W��	¹��@+H�RK�rP`
����H
���@9I
 


_o�����@H
@��P
XYX+??�?3�2��99/+]q3/]++3/3/]]_]3F�(
+</+<+H�++�10]]+#'##73"&'732673�\���t��"E
2;<��'Bc�����	�T\��@>jN-��?�9��&.�i��X�
%+5��"�9V�&N����t�
%+5"V:�	��&*H	���@ H�	�	�	
�����г&/H���@� H%



R		N


RK
o�
�
�
�
�
�
�
d
�
/
?


RK@
H
		XYX+?22/3?33/39/+3//+<�++ć��3}/]]]q83/]899�++ć+�+ć��10]]]]]++]]]]++!#373	��H�Ҵe�i��P�|��:���E�/����?��&/��@&�	%+5+5��!?&O�)O�&��%+5+5��?�9��&/���ִ
%+5�����9��&O��d��ƴ%+5��?�&/�����
%+5?5��!�&O�K@

>%+5?5��?��&/OO����%+5��!��&OO���*%+5��
�@[�v�p�

@	
H	R^



 P`
@	H	



_lmX+?�?399//]9/+33/]//+<�++������3/+9/10]]]!!?3%���_��yi����u2���U�U������@W
/
����@=H�
RK`��_@
H
���@XYX+?2?399//889/+qqr3//+<�++������3/]+]3/]]10]3?37 i�������~D�D�XH�G�z��?��&1��&��%+5+5��&�&Qt�@(&�(+#%+5+5��?�9��&1�q��M�%+5��&�9M&Q������,2#%+5��?��&1��@&�%+5+5��&*�&QL@(&�*0#%+5+5��3��&Qx�s���22%+5?����6���@�H�[9I%-5-E-e-(65	H/3R3^�k{��_/O8&&R^@	H/3,_&,! _lmX+?2/�?3?39?3�99999/+3//+<�++ć�3/]_]]q3/F�7(7
+</+<+H�++�9/10+]]]]]+"&'732>7>54&#"#>733>3 To�,�"_6<T<+I��]��`����
-n��U�F:c�WDl0>1e�kzEmu>i�M��D%\ZL?EE>dF'��7Q ���ѐL"�WM5���
1���H���@�	H
	5RK`0�@P���9���O_/o���?O����7)RK@
H)5/$#P/PXYX+?�?�?3?39/+3//+<�++ć�3/]qrr^]qr3/F�6(6
+</+<+H�++�9/10^]++]#"&'73267>54&#"#>733>32g'BcG"E
2;<�
T_@q\Av��
�		"HWiC��
�>jN-	�T\�'QOX3]�Q��S"KC0,9;/L5��$Z&��o���&2MN@4&�57%+5+5��C��2S&RM�@4&g57
%+5+5��o���&2�@4&�9@%+5+5��C��2�&RN@4&�9@
%+5+5��o���&2�+@
4&�4=%+55+55��C����&RSK@
4&�4=
%+55+55e���6�@E�Zj�6
9%4�4%-@HR^$%$$%$$%% $0$@$`$�$$���@DH$$/82Z?O_/@H_O����*%_$_lmX+?�22?3�229/]qr�/+]q�3/]9/+]3/F�7(%$$7
+</+<+H�++�99//+q10]]]]]]!#".547>$32!!!!!%2>7.#"}&i0��O��
�FKHi�:X��v_��� >8.�;<9~˘e<o�T�׃^a��\��<����KG�ǀ+W'k�g2E��AN4N\�@Ur�zT�T�[}[z7iKeEuE�Ef>0(`($()&") 	JJ�====m
*\G55RI���@5H/F0ROR�R�RRRR^BG@
HP\\GW:P*'$GP?33�2?33�29/�9//+�3/]]q�3/+�9/�210]]]]]]]]]]]]]]]]]]]32>7#"&'#".5>7>32>32%4.#"32>7>%>54.#"��8[J8�Gj�h��3Oق`�n<	e��i��1L�yn�k4	�O"A\95j`P
%B[66i^O#@\80jaP�?��1A$?-YH-c^eW9o�h/d6~�v8c]]d:h�W267�MlE L�t7c*RrGJ�u<d#Ba?Jb��?��&5��@&�%+5+5��">�&Ut�@"&�"%
!%+5+5��?�9��&5������#%+5����9�N&U��s��Դ**

%+5��?��&5�B@&�!%+5+5��"j�&ULH@"&�$*
!%+5+5��:��@-&6tI@<&�<?$%+5+5������&Vt9@<&�<?
)%+5+5��:��@&6K�G@=&�B<$%+5+5������&VK�@=&oB<
)%+5+5��:�W@�&6x<����G?$%+5���W�K&Vx�����G?
)%+5��:��@�&6�;@<&�>D$%+5+5������&VL�@<&�>D
)%+5+5����W\�'x�7���W�,&x�W���\�&7�@&K
%+5+5��]����&W��K@
'G&&%+5?5�\��@b/?O����/?__oO�
R^/_
_


lmX+?3?9/3�2�22/]3//+<�++������3/]q3/]]]]qq9910!!#!7!!7!ZX��}�}��X���<��y��Ĝ����,%�@i
H 	H		#	#/O_��%"!RK!Q
%Q"P
XYX+?�?39/93�29�23/3/]q3///F�&(&
+</+<+H�++������/]99//q310]++3267#"&546?#73#73733#3#!*0-%X0Ua
*}};}ix/��;��2:*.�	fT I܃0���Ѓ������&8��@"&�-=%+5+5��V��J�&XR@(&r1?#%+5+5�������&8M�I@"&q#%%+5+5��V��JS&XM
@(&h)+#%+5+5�������&8��@"&�'.%+5+5��V��J�&XN@(&~-4#%+5+5�������&8P�N@
'&c,"%+55+55��V��Js&XP�@
-&a2(#%+55+55�������&8��@
"&�"+%+55+55��V����&XSa@
(&�(1#%+55+55����U��&8Q���++%+5��V�UJ:&XQ��00%+5���.�&:�]�2&����710%+5+5��f5�&ZK��*&���/)(%+5+5�����&<�P@
&	%+5+5�����Wg�&\K�@!&z& %+5+5�����&<�@@
	&
%+55+55�����&=���
&��

%+5+5�����&]t5@
&�

%+5+5�����&=Oc�@
&�
%+5+5�����&]O���
%+5�����&=�@
&�%+5+5������&]�L�@
&�%+5+5!��y���@P	H �		?	O		�	�	�	�		RK_?O����
PXYX+?2?�99/]q2//+<�++�/]]]]10+3>32.#"!�%ClR?3$.��;fL+�*='�e�8#�{���@	!HY����H
���@	H���@�	H
?O�/?_@H@p�0p���	


R
q  �0@u	 
Q	
��X���p`@0 �����p`@0 ����p@ 9��rr^]]]]]]]]qqqqqqqqqqqrrrrrrrrr+/3/^]�9/]993�2/]]q33/]]F�(
+</+<+H�++������]]qqqr/+]q310+]++]+.#"3###737>32>.!	������(GmQ R# 
(@-����C��;fL+

�����&$'P���-$�8�¶=@%��%+55+5?55��.����&D'P���*@
|&Y&�k@
|@%�^T@%+55+5+55+5�����&����&���%+5+5�� ����&�t�@j&�jmC%+5+5�������&��K�=&��=@!%+5+5��,����&�t�@;&�;>%+5+5��:�9@�'��6���9�K&�LV����9\�'��7��Q�9�,&��WM���	�@��	x�Ue	��+[4d��Dt���$T��@	
H@$H@	��+;{�	{`PD4 ��������p`TD0$��������pdP@4$��������dPD4 @��������tdD0$��������tTD��������dT$�������p`@0n������pP@�����`P0 ��p`@0>��rr^]]]]]]]qqqqqqqqqrrrrrrrrrr^]]]]]]]]]]]_]qqqqqqqqqqqrrrrrrrrrrrr^]]]]]]]]]]]]]]qqqqqqqqqqqqqqqrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqqqqq/^]3��+/+]]]qqr3/]]9=/3310_]]]#'##73�\���t������"�	@M�����DTt��kDT���[4K[������@�$H+;{�	���tdTD4$������td4$������tdTD4$������`P ������`0 �����p`P@���P����@0n��@+��pP@���9��rr^]]]]]qqqqrrrrr^]]]]]]]]qqqqqrrrrrrrrr^]]]]]]]]]]]qqqqqq_qqqrrrrrrrrrrrr^]]]]]]]]]]]]qqqqqqqqqq/^]�+�2/]^]]]qqqrr3/]9=/]3310_]]#73373	̵a��p������SC@@	H�Td�����@�H��Dt��������@$H/;	@H@H�������td@0$��������tdTD4 ��������t`PD4 ��������p`P@0/���@@�/�����`P0����`0��o? j�����`���pP@ ����9�r^]]]]]qqqqqqqqrrrrrrrr^]]]]]]qqqqqqqqrrrrrrrr^]]]]]]qqqqqqqqqqq_qqqqrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqqqqqqqqqqq/++^]q+qqq�/^]]]+qqrrr3/+10!7!���&����>@({
�

��@���	�/?�/]��2/]�3/�10]]".'33273�JnJ%tgS�@uA[v�3Vo=UN�=oV3V ,�N@	�&���@,1Hf����F�����H���@�H	�S�yYI�����iY9)	������YI9)������yi9)	����iYI&	��������I6�iY9��������tdTD2"i����@�����p`P@0$��������pdTD$�������dPD4$9����_rrrr^]]]]]]]]]]]]]qqqqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]_]]]]]]]]]]]]]]qqqqqrrrrrrrrr^]]]]]]]]]qqqqqqqqqqqqrrrrrrrrrr^]]]]]]]]]]qqqq?�/�^]++]qr+rr1073V"�" �����s'[���@	H	H	H���@+	H�@��
�@�#�/_/?O���/]q���/���10++++#".54>324.#"32>�&BX22XB&&BX22XB&l$12$$21$�2XB&&BX22XA&&AX21$$12&&2G�U�t@�(	H)9I�{���@*.H@H�+;[k+	;	k	{	�		�	���tdTD4�����dTD$�������T4$�������{k_+����{k[K;+�����@��kK;$����{k_+���{k[K;j��������o_K;+�������[K;/�������k[;+:����rrrr_^]]]]]]]]]]]]]qqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]qqqqqqqqrrrrrrrrrr^]]]]]]]]]]]qqqqqqqqqqqqrrrrrrrrrr^]]]]]]]]]]]qqqqqqqqq/^]�/^]/�/]3/^]]++]qr10]+"&54>733267�XZ)=H�D:((& 4F�U^J4V@--<J+"/i5�2�r@5E:J�����@'1H�������H���@�H	�	�@���	)9y�	�����Y)����i9)	�����Y)���iI;)	��yI9	�������vf6&������tfF�������vfT$i��@�������tbVF4&��������tfVD6&��������tdPD4 9�����rrrr_r^]]]]]]]]]]]_]]]]]qqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]]qqqqqqqqqqrrrrrrrrrrrr^]]]]]]]qqqqqqqqrrrrrr^]]]]]]]]qqqqqqq/^]2���3/�]++qq+3/]�10_]]".#"#>32326737*LE@7CZ&9S;,LE>6D\%9S�%-%>9-_N2%-%?8,_N3�@�@1�	�	��	�h���@	
H�
�
�
�
w
f
U

���@'+H�
�
�
�

���@�"H

�
�
�
�
�
v
�
g

(
8
�@H���@	H��(8x�	
�
�
�
�
�
�
�
v
g
W
F
7
(
�
�
�
�
�
V
G
7
'


�
�
�
�
�
x
g
7

�
�
�
�
�
�
w
F
7
&


�
�
�
�
w
g
W

��
�
�
�
�
�
@��
8
)

	
�
�
�
�
�
w
f
U
E
6
&


�
�
�
�
�
�
�
�
w
g
W
G
7
&


i�
�
�
�
�
�
�
�
t
c
T
C
3
 


�
�
�
�
�
�
�
�
r
b
R
B
4
"


�
�
�
�
�
�
�
�
r
b
R
D
4
$


9�
�
@�
�
�
�
_rrrrrr^]]]]]]]]]]]]]]]]qqqqqqqqqqqqqqqqrrr_rrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqqqqqqqqqqrrrrrrrrrrr^]]]]]]]]qqqqqqqqqqqqrrrrrrrrr^]]]]]]]]]]]qqqqqqqqqqqqqr/^]3�2/+]q�^]]]]]]q+qq+rrrrr/+]q�_]]1073373����������������<���	
��/�/�10]73�~����!$%������A	
_@	��	�������@#*/H	
�

O
_
o
�
�

	����
/3���2/�/]�9/]+q�]10]73%73!73������!�"B"�"�!p%������������&$T���{�����%+5?5����&@��/@+H@H@H/+++]�/�1073�+�+����Lb�'(�T�c�{���V�%+5?5��I��'+�T�`�{���M�%+5?5��I�',�T�`�{���V�%+5?5������!�&2!T��{�6��g�44%+5?5���g�'<�T��{�����%+5?5������&vT��{�<��B�::55%+5?5��.�A&�U2@&�%+555+555������$��?�%?��Y@(@	
HR^@	H_lmX+?3?�/+3/F�(
+</+<+H�++�3/+10!#��7����������@d�
Y
i
y
	)	R\R^ Ht�+Z?�����p�_?33?�2/]3/3/]�9=/]]+�++ć+�+�10]]]'3!%.5���$:�
	$,�����^)PC1
2FR(����?i�(�����=��?��+e����7�@?fijdVkY9Ie
6
F

5j5
.(e(!@H���@k
H	Z33/3�3�3�3<?3O3�3�3?3�3�3O3_3�3339&Z?O_/@AFH@.2H@H_O����+__?�?�9/]q�/+++]q�3/]qr^]�99//++10^]]]]]]]]]]]]]]]]!!2#".5467>$"32>7>54.K����ޙO	
����K	
����̗b	8k�b�͖c	:l��U�Ԁ1i0���^W�ڃ.`1���_�J�ʀ+W'k�k5K��}*Z&k�k6��Q"�,��?��.�����@L�Xhx8HxH(x�w�R

\R^���I���@[H�v�	)�;K[{�D+;��@Hdt���@H/
?22/3?33/_]+]q3/]+]qqrr9=/]]]+qqq�++ć+�+�]]10]+]]]%#.'&'#3���	/���P��/Y#)%'*$X,� ���?��0��?��1��_�o@�
P


@H`/���@,H
@	H
_O����__?�?�9/]qr�/+3/3/+3/]]+99//]]10!!!7!7&9�����`�������������o���2?��~@LWWR^oO��	R^@	H_lmX+?22/3?3�2/+3//+<�++�3/]qr3//+<�++�]]10]!!#!���}��� ����?I�3����@y
 L6)I�

@/5H
@H
[�@/5H@H[�@/5H\/@
H	{�����4
_
_?�2?�29=/]]q33/+3/3/]3/99//]�+q�++q�++q10]]+#7	7!!!!��Y������c�E����7s�����\�7�����<~��c�*7�@��!iVfX+h+:&J&53E3*%***
+7
R^[?$?$_$�$�$$$$$91[/@H`77*+`77
lmX+?3?399//3�23�2/+]�3/]]q�9/3/F�8(8
+</+<+H�++����������10]]]]]]]]]]]%".54>;7332+#?32>54.+";�u��IV�݇�#�!1u��IW�އ�.�,�|c�k84ZwCE�|c�j84ZvBF�Az�m�̎L��Ay�m�̏L��3e�cXQ'3d�cYR'������;���+$@V&f&:J���@�H	''	*+++R+^ #%R%^ "  "    "�""`"�"�"�"�""/"?"""-R^/_�#""%*` +lmX+?2?39/993�2993/33/3/]3/F�,(,
+</+<+H�++�3/]]]qq3/F�,("  ,
+</+<+H�++�9/3//+<�++������10]]]+]]!#"&54>73;3>73+�S3�Z�Z.RtF8���On�^6Z�bV�ҐiS���#!	�-!#"C];I��3a�Y�l�|D�U��9@���%�vvo22�2o33�3euo

�
E J�:J�		(H�0H)�8p887)	)9I�|�	H0''(
@
H((5Z ?O_�;)"Z/55'@H'
+0'*_)_?�?3�2/+3/]�33/]q�299//+]3333+]]q]10]]]]]+]+]]]]]]]]]]]]]]267>;!7>54.#"!732.546$y�ݜUB�ז-(#H���+��v6;m�_g��X$EfA-���P&-2F|\6r�
�L�΁~໒0��*���ig�k6F�ؒX�t\&�'p��i��b��Q�&,��@
&�%+55+55�����&<�@@
	&
%+55+55��F���&~Td@H&sHK*%+5+5��"���&�T@>&�>A#%+5+5��"�W&�T��-&�
�-0(%+5+5��?p&�T4@&�%+5+5��h��A&�Uj@%&n+1 %+555+555F���M1G�@w�F�4{=o�o�o�cs�j>z>k66FV0%0.
((*(@GH@/��@

HI9G*@
H*;P%2P?2�???3�/+�3/+]q3/��310]]]]]]]]]]]]]23>73#.=##".5467>"32>7>54.SFmP2
"�010+#
�!J\qHQ}V,_��a:bQ?�4icX"7UM$@Y64;>Yfmi_%e�wa/@B=8cK,6f�].r6��y5�(_�srT�8n�l%I|Z2��W`� >@�w�#euZjZZjzYiye u V G �P`pA%$H// :
55H@::@H:::@%$$RK     � �  @$H ) 9   %*P
4P$55!PXYX+?�?39/99�9�2?3/]+]3/F�?(  ?
+</+<+H�++�9����3/]+q�9/99/�10]]]]]]]]]]]]]]]6$32#"&'##"32>54.#7>54.�-�T�`43Sl96\E'G��}f�7	
:�}�!�DOW+M�\4&HkEHpM(4O���.VwJS�^:
8Rj@k�F8&9\5��褪�� )S~T7^D'�
-JjF+G3o�Xa:�@A+RM


RK H
`



eu����@/@H
?2/?39]]/+]83/3]3/]]89=/+]�+�+ć+�+�10]]>73#>7,r	!"s��630�*Z*�:�]BD>;A@���-o|�A��[/C����4�@,{.�.t
�

�
{	�	dt�k6�r����@Y	H$#4#D#t#�#*$:$J$k00�
�
0
11+33!G/?O@H6G+@
H+1P0

(H
2P&?�?9+33�2/+�3/]+]q�3/9/993]10]]]]+]]]]]]]]]%2>54.'".'#".54>77!�Y�Z-&4/jh_I,&D\/
)-)�,I4R��{g�o;a��s��qBs�ZAn_R%+<Qn�ZDhE#���8px�H�ѕQ<n�]�ЗdSw�"���M=�@2fv�jz�	H	H#119#,F#I$I���@1H/$?$$$$?9G@
H2P11'P0$�$$$P		?3/�?3/]�9/�9/+�3/]]3/+]��9/]�9/9]10++]]%267#".54>?.54>32.#"3"�j�FX+[h{LT�[0/UvG'D49h�Z>kYI�)oK+N:#3XxE4h_S<#0CtPDi+B.+OnB?hN1'9J,IoJ%(B/T?8*@+->%�/I4$=+=��	�6c@>u�[ k 5E
%%-%]%I55'.?8G'@
H'/5Q
""6/?93�22/+�3/]399//�210]]]]'>54.'.54>?#!7	L���uF=[<9aH))3f;W8?nS0Fw���P061���I�����U+;, #2F3+MG>H#)+' -EbC]�����H�"�WM,¹,��
H���
H&���@_H+,R,K,��/o��P/?. 

R
K@
H +
P%XYX+???�?39/+3//+<�++ć�3/]]qqr3/F�-(-
+</+<+H��++�10+++>54&#"#>54.'33>32z�
T_@q\Av���"HWiC��
�W.'QOX3]�Q��.*"
%'&
,56/L5��$Z&��e��E�#1�@<f(v(�(i&(HH''C'S'c',


*H-P-`--����/H-���@H--3)H���@-HQ*))P$Q?�?�9/3�2/+�223/++]�3310]]]]++]]2#".54672>7!"!>54.ع�%o��rR�^3I$>pdV#��3F&=pbT#0
3I���@�[���U;x�x:�Pvo��4�ؤQ�6SqD�3~աR�6PlA?�:y@C��O/�����RK�@
HXYX+??399/+q33/F�(
+</+<+H�++�3]qq10]7#.54673������&HA(9N��2"V:�	��&*H	���@ H�	�	�	
�����г&/H���@� H%



R		N


RK
o�
�
�
�
�
�
�
d
�
/
?


RK@
H
		XYX+?22/3?33/39/+3//+<�++ć��3}/]]]q83/]899�++ć+�+ć��10]]]]]++]]]]++!#373	��H�Ҵe�i��P�|��:���E�/�������z���@b	
H3*)D
0


 


RKRM HC���@/@
H@

H?P?�?33/39]]3/]+8/]+89=/]+�+�+ć++ć��9/]_]]]10_]]_]_]]]_]+_]'.#"'>32##, 0$*%)(6N;*δ|���cJc=�$Q�_������wA:-H@	�U����@
Hr�DTd" H0H����@�H&%/(?(O((�(%5Eu�h%5%&&%&'(R(K&&&&@�_o����/RK@$H�/?('&& PXYX+??�?3?33/39/]]+3/F�.(.
+</+<+H�++��3/]qqr3/F�.(&.
+</+<+H�++�9}��10]]]]]]+]++]]+]]!>7##".'##332>73�	9DP2'H:*I��}0L6<cM4
x��
.;<2N6 - ?���|=6.J54[~Ki��"LC1c�:i@B%RKk{�F` @P�@H	?33/3?39/+]]3/]�9=/]3�+�+�10]%>54&'3#3�b�j8�b��h����pû�m#;3D}���o:7����N�@��k{m}�\k,Z,d	t	�	6	F	%E
Z[1k11 	H@@/@LLF>$I)8)�)�)>))>3`NpN�NN?NNNPG3@
H3CLQM.9
Q.)#M#/?9/9�93�22/+�3/]]9///]9�2�3/10]+]]]]]]]]]]]'>54.'.54>75.54>?"+7!�6neXB%5Ys>A�}mR/=[<9aH))3f;W8?nS0L�̀6dL.Cev3&295,JzO(8L2:K.�/BWlC+;, #2F3+MG>H#)+' /HhH\��\	*BW6FaA&
���C��2MR[��{:4�@ky� HI /?O�(	HHO&&�&&&224RK��6++G/
44&Q1P
XYX+?�?3?�22/399/]�3/3/]]]3/F�5(5
+</+<+H�++�3/9/]�10]+]]+]3267#"&5467!#>7>767"7>3!#�+1"M+^V��nAGJ!�"MLD'PG7-47%�
#
0"	�	
gZ:�H����JL��}
����WVN2�@!�+f�1j1z1h,x,�uFVf���@cHE<L,%
!3!C!!GO00004$##RK/?��@$H#)P
PXYX+?�?�9?3/+]3/F�3(3
+</+<+H�++��3/]]�10]]]]]]]+]]]]]]]]2#".'##>"32>7654&�b�p=b��d;`O>	S��V��dKoQ7>?KT,GsW=�N?t�h$R*v��H'4= �W�x��I�8g�X��5)6f�\OG��C���N5�@�t��dt�dt����@XHY[/k/5E6
F
%44+4[4I''!I,,y
�

p7G@
H1,1&P0
p
�


?3/]�/93/+�3/]�]9/�2]10]]]]]]+]]]]]4>32.#"'>54.'.C!Bd��g>_K:�"_?=kZG1<[<9aH))3f;W8?nS0JJ���zI%3l-=6\z��D.@."#2F3+MG>H#)+' 0MpD��:1{@R�t0�0+%u�+
|*�**j*
G#&#0`#/##?##@H##3/G@
H'Q
P?�?�22/+�3/+]qq3/]3�210]]]]]]]]".5467>3!#".''2>54&'#"�h�j6��u(�	#*+/E�Շi�W&SR��]~>p�a$T(��w4�=�a��g�T��}Q�6(]�n)I#��K��g:#�@B(	H%RK #  #  ##��@	
Ho � O    PQXYX+?�2?�99/]]]q�+]3/F�$(#  $
+</+<+H�++�3/9/10+"7>3!!3267#"&5467N'PG7-47D�Ѕ+1"M+^V��
���S#
0"	�	
gZ9�h��:$�@	y�f"���H���H���H���@UH	)$$G/
�
?
O
�
�
�


&RK!$!!$!!$$!�!!@H!!P$XYX+?22/3?�99/+qr3/F�%($!!%
+</+<+H�++�3/]q�10]]]++++]]32>54&'3#".5467�ycjIpR9"�
<]��fP�a6s:��I8ep7_���RW�02�Xgʶ�q@,X�[#K*OG�W�R(9@�z�2j2c!s!�!���
H���@�
H*%$k{�$./
-#-RM$$$$@P��FO7�7�7777; G@
H-$/#P
)PXYX+?2/�2?3?3�299/+�3/]]�9/]]q3/F�:($:
+</+<+H�++�������10]]]]]]]]++]]]]2#.54>7>">7>54&�ClM)

q��sO�O^�f5H��w�� uvp9W{K":/%qNcH
@P0_�\+j3�΀;�i�Bu�e��q� ��ZK��D`�h6�@fJ��.c�pBp-ng�k�XyP�@zz�9y
JZj	@
P
`
RMRN

O�������@@

H	P/33/3?�993?3/+83/]q89///�+�+ć+�+�������������]10]]]]]%#.#"'>323	#�(�ag$!"#&&0@0'Uq����i9W6R7	|>fK���G��v�W�<#�@+
z�{
�
4#+#Y"y"4"+"""*!d ���H���H���H���@	H���H���H���@�
H�rTdEV)
#!#RM""""FVfRK@H	R	KPo����%	#"!P
XYX+?3?3�2?33/3/393/]]q3/F�$($
+</+<+H�++�/]+3/F�$($
+</+<+H�++�9/]3/F�$("$
+</+<+H�++������10]]]]]]]+++]++++]]]]]]]]]]%>73#.546733�\�[9~�}V�͏O�Oo�i3v�z	���wIy[��|z�n5�k�/X�R H(d��#@q_�C���OG�@sy}�k{D�DjDk3{3�3*
:
"

H44g4w4�4
22
--`pF77=GI$FF/GOI=G�/�//@
H/$FFBP*7P6?3�2?3�29/9/+]�3/]]�9/3�9/9/]10]]]+]]]]]]]32>7654&'7#".'##".54>7326?3f)=(AbI2Y^2UzN$Z|�[B_B&CWlDNzT,7Uw�`��#	
+D/g�1��51R;!4c�ZUFt� �V}�ROM��~>%Ec=>cE$9i�^O���kJ�ױ0W)7[A$�����?�{&�i�@
&�%+55+55��h��{&�i�@
%&d)' %+55+55��C��2&RT�@4&�47
%+5+5��h��&�Tm@%&�%( %+5+5��C���&�Tr@H&�HK/%+5+5��?i����?i�&(�Z@
&�%+55+55���Z�5#@
� y y-��سH���@IH)&		
''	HO4�4�444$0
R^$$$$0@P�������"FH���@G H7.//R/^010110/222012_3$.)_
� 3/0_@lmX+?3/]�?3?9/]q399�299�22/3/]/+<�++�3/++]q3/F�6($6
+</+<+H�++�99//]q10+]]]]++]]]>32#"&'732>?>54&#"#!7!ZD"kz5��%:]�\j�/�%.7!*>.'wy3zyj#�����
��8�]�].K9x#4P7�	RY
�圜��?��&a�v@&�	%+5+5e����1�@5�$��	�fvj�U+6*F*V*�*:J9
6
"\���@]H,\-/-?-�-`-p-�---3"Z?O_/@H_O��'_O_'_0,@,P,�,�,�,,,?2/]�?3/]�9/]q�/+]q�23/]]q�3/+�9/10]]]]]]]]]]]".54>32.#"!!32>7��ِG5c���v��S�<_�Us��dw��3e�d^�v\%�0x��\��{|࿛l;;`|B7/WE)F�l�$^�s@1Ri7YI�a9��:��@�6��Q"�,��Q�&,��@
&�%+55+55�������-�����&01@R��z��j,z,Z#j#U"e"u"W'W
H{Jj6V%5 H
&HH���@H)Iiy�* 15H "&H���@`H H0''R'^&&&O&�&O�&& [/*�*�*�***2@
H0_�O�'_&_
_	lmX+?�2?�3?�9/]qq�/+3/]�99//]]3//+<�++��++++10]]+++]+]]]]+]]]]]]]]]]!#"&'732>7!!2#!7! 4.#!�3x8_XV_lC.
"
8:>HV4�1sEu�{?L�́���vq+NnC������ljU%�>k�����4a�Xh�p;�=Y9?_� �@���zZjUeu	 

R^					?Oo@)H[/�O�/����"R^@	H
		 _�O�_lmX+?�?3?39/]qq33�223/3/+3//+<�++�3/]qr�9/+]3//+<�++����10]]]]]!!#3!3!2#%! 4.#!
~��~��ss�su�{?L�́��Fq+NnC����s���T��4a�Xh�p;�=Y9�&�!�@
 ��سH	���@	H`p����$)H���@THR^#!R^O
!_p
�
�


_lmX+?2?�229/]3�2?399/33//]/+<�++ć��3/3//+<�++�9/++]10++]]!#!7!!>32#>54&#"g�����D"^jr5½i�jmy3mi]#圜��
��>��#9OW
��?�&����&�
�%+5+5��?��&�C�������4&���J@&�)%+5+5?�h�����H���@w
H
RKP	R	^_o��P`��p�
R^@	H_	
lmX+/2?3�2?33/3/+3//+<�++�3/]qqr3/F�(
+</+<+H�++�9/]3/F�(
+</+<+H�++�10++!3!3!�P�����P�h�����h������$>���@`�Z
Ueujz
[/?/���R^�@	H_�O�__lmX+?�?�9/]qq�/+r3//+<�++ć��3/]3/]�10]]]]!!!2#!7! 4.#!P��!UDu�{?L�́���vq+NnC�����H4a�Xh�p;�=Y9��?�%��?��a�C�hh�]@ �HY
i
�
{-&���H��س
H���H���@!
HRK 15H "&H���@bH Hp�R^/�`p�		RK@	H__lmX+/33/3?3�2?3�2/+3/F�(
+</+<+H�++�2/�]3]/3/F�(
+</+<+H�++�9/]�++++3/3/F�(
+</+<+H�++�10++++]]]]]+]%3#!#3>7!!v�o�P��P�n�3]YX.��T�Kx,SQP*�����h83�����A���ݲ�3��?i�(����+x@�%5eu����@"%H"2br����@	"%H"2���@A"%Heu�W*W6#F#R^$#R#^"!""!RL$!*
���@/

+)**+*R*^+++++""@-H""���@

H-���@9/@
H

!$)`/
*++"##lmX+?33/33/3?39/]3�299333/33/399/+]83/]+83/+89/3//+<�++������9/89�++ć++ć++�10]]]]+q+]q+]q]"&'#.'3332>73###b"X ����G0�’/CBN9x�x&=99EV9��Sl!e��13/~���Oo_���c{Dk��
,FbB1�aag���	�k����<�@��6zy�hx�U#U:u:F2V2f250++3.[_o��/?8[�>%\�&�&&&\��@#H@H@	H3`O��)O%_%O%_%%@H%% _)_@P?2/]�?�3/+]q9/]q�9/+++q�3/]�3/]]�3/]]9/]�910]]]]]]]]]".'732>54&+732>54.#"'6$32)u��V�:VzUX�a3��cG`�h7"A_=T~`F�P�b�vA5]�L2_J.Q��3[�NF:aG'&ImGi}�>gK-K6$B[6=��2Y}KO}Z8	/LhBi�t=?���@VfYiJ	Z		0H		HEU��гH���@	H		R�@		R�@ 



O


p
�


R�@:@	HEU%JZ
*
		lmX+?2/3?33/33]q]q/+2//+<�++�3/]qq3//+<�++ć+�+�]]10++q++q]]333#>767?��
	
��蝹

�8��d3b'.(���'Y&-+�Z��?�4&���J@&�%
%+5+5?��@3DR^
R
L


@-H@ H���@?
HR^@	H

`/

lmX+?22/3?33/39/]�9999/+3//+<�++ć��3/]++83/+89�++ć++�10]32>73###P�x&=99EV9��Sl!e��13/|����
,FbB1�aag���	�{������@F�jzFV9�9y%EUe%EU*jz�(H
 15H "&H���@6H HR^@
H__	lmX+?�?3�2?3/+3/3//+<�++�9/�++++10]+]]]]]]]]]!#"&'732>7!#��x8_XV_lC.
"
8:>HV4�Q������ljU%�>k������?��0��?��+��o���2��?��n��?I�3��q����&���\�7�����������@HHR�@R^ H���@	����@H@H���@H_?�?39/+333/3/+3/+89/]89=/+33�+�+ć+�+�++10]#"&'732>?3	3�@noxK?x+UL0+DCM4B�}�)�>X�R(*#�%9^EY���z���*5@�u!�!i:&J&51E1U1�*t-�-X+h+�"%Vf**i*
+5
R^@���[?$?$O$_$�$$$$7/[/@H`55*+`55
lmX+?3?399//3�23�2/+]�3/]]q�9/]q3/F�6(6
+</+<+H�++����������10]]]]]]]]]]]]]]]%".54>;7332+#?32>54.+";Wk�}E\��d$�#$q�~C[��j,�,�Mb�n:2Sm;7�=��1Rk:9�?v�m�ΒN��>v�m�ϒO��6g�_XQ'�YR'������;?�h�����H���@i
HRK
R^				?O�/o��@$FH
R^@	H
		_lmX+/3?3�2?33/3/+3//+<�++�3/+]q3/F�(	
+</+<+H�++�3/3/F�(
+</+<+H�++�10++%#!3!3So�P�������������e��@aH0
H*R^O�!
R^						@	H		_
lmX+?33/3?39/93�29/+3/F� (		 
+</+<+H�++�3/q3//+<�++���10]]]]++#"&5467332>73#�#anu7��j�jy}4pk`#�����
��8#��	RY
�?]��@h
R^
R^				P�p/?_
R^@	H
		_lmX+?2�2?33/33/3/+3//+<�++�3/]]]q3/F�(	
+</+<+H�++�9/3/F�(
+</+<+H�++�10]33!3!3?������������?�h(�>���H���@�
HRK
R^				dR^



;K���@FH�;[k�/R^@	H
		
_lmX+/3?�2?3/3/33/+3//+<�++�3/]_]]]+]q3/F�(

+</+<+H�++�9/q3/F�(	
+</+<+H�++�3/3/F�(
+</+<+H�++�10++%#!3!3!3�o�P�-������������������@wjzZ
j
WgUeu�WgR^
[/�O�/����/?O_�O�__lmX+?�?�9/]qq�/]3/]qr�9/]3//+<�++ć��10]]]]]]!7!!2#!7! 4.#!��5�s$u�{?L�́���Vq+NnC����4a�Xh�p;�=Y9>���@��jzWgZ
j
Ueu
[��R^?O_���?O����/o���R^�@	H_�O�_lmX+?33/3?3/2�9/]qq�/+r3//+<�++ć��3/]qr3//+<�++�9/]�10]]]]]]!3	!2#!3! 4.#!.���Du�{?L�́����vq+NnC����-4a�Xh�p;��=Y9>���@d�jzZ	Ueu[/�O�/����R^




�@	H_�O�
_lmX+?�?39/]qq�/+r3//+<�++ć��3/]qr�10]]]]!2#!3! 4.#!�9u�{?L�́����kq+NnC��-4a�Xh�p;��=Y9��K�+�@���UeuUJZJ�<55""&!ZK';';'K'['�'''-\\�;K[@#H@H/"_O#�#�###	_0@P��?O_@H_	?�3/+]q?3/]�9/]q�/]_]++]q�3/�3/]qr�39/10]_]]]]]]]]]"'>32#".'732>7!7!>54.��<�(q��v�ӒMj��k��e�*ƙm��b��c5c���x<R�Z0U�ۆ����.]�]:��B��x�%^�i9?����7�@|:J:J:Jz�5E5E5Eu�	([/?Z?3�3�3O3_3�3339R^@	H_+_
 _lmX+?3?�?�?39/�/+3//+<�++ć��3/]q�9/]3�10]]]]]]]]2#".547!#3!>"32>7>54.��ԒK		����֏G��~��s"���z��\ͷ{��\6e��U�Ԁ1i0���^W�ڃ((�s�����X�J�ʀ+W'��K��}*Z&k�k6��v��@mUegZjz
R^[?O�

R^oO��@
H
__
lmX+?22/3?�9/3�22/+3/]qr3//+<�++���99//]�++�10]]]]#.54$)#!	!"3![*-aO4)5Z��q�u���d��2Tn;�e9Z~U��I���}�Ib;��.��-NDi����F�@P��zz<�<u
�
*#%!u!D?$?=+=$7 7�777G0O��HBH*���@-H*BP
8R7
P%?�?�9/�3/+�23/]]q�3/]10]]]]]]]]]]]"32>7>54.'2#".5467>7>7>�:qcP%D]7BpZA	"?V��	d��ha�l9%l��;g^X,X�_S�kTD6Og�O�iHQsI"%T�d,M"Hg@�ɾ&P,��w7:|��8�N��W�-El�l8`H)'���D*7�@�-�%�{4I4

���@N	HG&&.�G�.�.O....97*55RK@
H5*Q77#1P#PXYX+?3�2?�99/�99/+3/F�8(8
+</+<+H�++ć��3/]]]�9]9/�10+]]]]]]#"&'>3232654&+72654&#"�.Lc5,Q>%G��lp�dt[}�ZU�_3��=@>��|��ތ�\\-WM>H@^B(	$:Q4Z�U)j��i+ @^��dlP^�^bAL?r^),���O7�@#u,�,u�60F0V0++k+{+�++' 	H���@	HI!I    ���@(H  )H�3339F)@
H)3	$  P$P	?�3/?�3/99/+�3/]�99//+]]��10++]]]]]"'>3232>7#".54>7>54.C8V?,�&ձd�Y)4f�`RV,qr?_E/�&۸y�\#;o�gey@1P�$9(0v}+Jc7FeK:)2@/NK'<(8v}3Sg4MoS?//3/#C��,�)B�@t@�@*(f!���@fHx.�.[-k-,{,�,&i&:JZDT+
z	�	1#I/?���@P`D>F@
H6Q?


*Q *P?�?�9/q3�/+�3/]qr�39/10]]]]]]]]+]]]".5467>32654.#"'>32'2>7654.#"�f�l9a��W2`UE3Z{G%P*%c0� b��a`{L(
:bMFoS;=_?s�bC"}�t7'6"&#l�c.{��ё��׉?�Fr�H:;2`K-*Y�b#CAhI'��E��'NH���:)�@��"�"�"�	�	�	���������v�Ue%'5'E'%5E�!!!	  RN
RL


(
)
"!R!M   RL" ()   (
���@(

'(()(R(K)))�)))���@H/?+���@7@
H



"'Q/
()) !!XYX+?3333/3?39/]3�299333/3339/]+83/]+q89/]3//+<�++������9/89/89999�++ć++�9999�++ć++�10]]]]]]]]]]]]]"&'#.'3332>?3###a8����/��e!/+-\�\2<R>���8U뺺
"!
_���WICK�L]2�&2]L�CW��������N;�@	�99*���@G	H1,G!I"7G""0@�O�=I@
H1P'	�!!!P'	P?2/�?�3/]9/�9/+�3/]]q9///���910+]]"&'732>54.#72>54&#"'>32���	%:R7/VA&)NqHC�c=dR$F<0
�Jg�NQ�\19Yj16U:;p�z�,%=+0M72G-�*L=EF
 8+Gb>&E_9EeD$,CT.L{W/��V��J:X��V��J�&X�@(&n-9#%+5+5"	:���
H���
H
���@�
H���t����u�U
e
RM

RN				
	p	�		/			RK@
H
Q/	XYX+?22/3?39/]�993/399/+3//+<�++ć��3/]]89/89999�++ć++�10]]]]]+++32>?3###�\!;GY>���8U뺽K.\�:�&2]L�CW���
�#����Y:�@zZ�;Kk���@^	
HRKP�RK?O���/?O@H	QP	XYX+?2?�?3�299/]+]3/]3//+<�++�9/]q399�++�10+]]]]!!#"&'732>7!Ҹ��DiWKOY8"= '04<NfC�����؊O�
P��.��"U:�@RVF	 Hi	�	O	)����
H��سH���@�
H�
�

 HY

+
;
 HFDTt/&	RO		
RO

dt�WD2${RM/�P RM@
H���@&H H

	XYX+?3/33+]?3]]+3/+3//+<�++�3/]]]q3//+<�++�9=/]]]]]]�+�+ć+�+�]]]]]]10]]]]]]+]]+q]+++]]]]+]]]#>7##!>7Uҭ�
�
��


���	T	E_:���<@=�G�==9�,:��*gfYP�5�"?:�@bRK/o�?O���
		R	K



@
H
	
Q/
XYX+?22/3?339/]3�2/3/+3//+<�++ć��3/]qr3//+<�++���10]!3#!#�Y�Y�Ҵ_�_��:�6���:��C��2MR"M'��"��@XH&'R'K/O��)

R
K@
H&P 
'XYX+?2?3?�9/+3//+<�++ć�3/]q3//+<�++�10]]+!>54&#"#>733>32�~
T_@q\Av��
�		"HWiC��
�'QOX3]�Q��S"KC0,9;/L5��$Z&�s����W1NS��C���NF��"KMP�����Wg:\E�Wn�=ShP@#�BjblA|AjGzGT\�\D[K;[;CS7���
H6��ضHE6*���@�H+)++++%5%
WW!!$!	H\/66\6E877E76R6K77777�7`7p7�7�7�77777$G�f�fOf�f�ffffjQG@
H76TJP8/
_>P,XYX+/2�2?39�2?3?3/+�3/]]]q�9/]]q3/F�i(77i
+</+<+H�++�9����9����10+]]]]]]]]]+]++]]]]]]]]".5467>323>733>32#"&'###72>7654.#""32>7654&sIpM(Qr�\r�	R�n!DMY6HqL(Rr�\r�R�q!DNZ9gWC9P07ZH7T�9gVC	ua7YH7T2\�Q-|?�˂=h^
.3.	���/D-2\�Q2w?�˂=h^#4>�YI/D-�&]�xbOAbC"%[�tvX{|M&]�x3X&��%[�twW{|����<:[Y�hM:)X���@
Hdt�EU<L\����@�H5Eu��u#3C%5
$$


))RL@RK




?_���O�+RK&)&&)&&))&/&&@"H&@H&
&P"Q

)XYX+?22/3?3?�2?�999/++]3/F�*()&&*
+</+<+H�++�3/]q3/F�*(
*
+</+<+H�++�2/]3/F�*(*
+</+<+H�++�10]]]]]]]]+]]]]+32>733##4>7##"&5467�~
T_@q\Av��	�i�P�		"HWiC��
:�{'QOX3]�Q^��3���,9;/L5��$Z&���:!@r�r�l|��гH��гH��гH���@'H"2Bbr�"2Br�"2Br����@VH&6FRK?O��#RK!!!!@
H	P!XYX+?22/3?39/�399/+3/F�"(!"
+</+<+H�++�3/]3//+<�++���10]]+]]]++++]]]32>73##".54>7�CMN#>?B'e�ҴYR�R>dF&D:��	A<���*, @_? 
PV��:A�@1d9t9�9dt�M]mM(](m(2$v����@	

Ht����@

H$t4�44���@�

H3$3$$�$,,A	A1	1%?RK?A??A??AA??.RK�����C27R7K.1..1..11./..@H.	;P%7.?
"*211AXYX+?22/32/3?3?39�2/+]3/F�B(1..B
+</+<+H�++�3/]]]3/F�B(B
+</+<+H�++�9/3/F�B(A??B
+</+<+H�++�310]]]]]]]+]]+]+]]]]]]]]]]32>73#4>7##"&'#"&5467332>7
|	LV9gR<v��
�		@L];y�ESe@��
�|	LV9gR<v:��00,KO3]�Q\��"KC0,9;/L5wq1U?#��$Z&���00,KO2\�Q_V�h�:C�@M&]&m&�0
HM	]	m	M]mB���@�
HdAtA�AEAUAd2t2�2d"t"�"i9$99v<�<$<4<D<<$-4-D-t-�-$,$4Dt�$


-
:*	*	@ARALBCBBCBBBB@CCC?+0R0K(*((*((**((;?R?K::::`����OE R K/@HAB;::+**0 (84$P@?QCXYX+?2�2?3�2?393/33/3?3/+]3/F�D(D
+</+<+H�++�3/]]]3/F�D(:D
+</+<+H�++�9/33/F�D(*((D
+</+<+H�++�3/]3/F�D(CBBD
+</+<+H�++�10]]]]]]]]]]]]]]]]]]]]]+]]+]]!4>7##"&'#"&5467332>7332>733#		AN_;y�ESe@��
�|	QV9gR<v�|	LV9hU=v���i�P,9;/L5wq1U?#��$Z&���00,KO2\�Q_��00,KO3]�Q\��3���B���:!�@ejz�cs����@R	H
!RK				
G_o@H@H@

H#
@
H
!Q

	
QPXYX+?3�2?3�29/�/+3/]+++q�9/3/F�"(	"
+</+<+H�++�10+]]]]#"&'!7!3232654.+�C~�rp�d���Y�b�s?�2=@>��>bE�O[�X*���6 En��dl)A-&���:#�@"eujz�
cs�U���@\	HGRKo?O��%#RK



@
H#QP	
XYX+?2?33/3?3�29/�/+3/F�$(
$
+</+<+H�++ć��3/]]q3//+<�++�9/�10+]]]]]]]!3#"&'33232654.+4Ҵ��C~�rp�dδY�b�s?�2=@>��>bE�:��O[�X*%�6 En��dl)A-&���:�@eujz�
cs�U���@K	HG_o@H@H@

H!

RK				@
HQ
	PXYX+?3�2?39/�/+3/F� (	 
+</+<+H�++ć��3/]+++q�10+]]]]]]#"&'33232654.+�C~�rp�dδY�b�s?�2=@>��>bE�O[�X*%�6 En��dl)A-	���M)�@ty�
H)+%&I'/'''F@ O �    +I/?@
HQP`p����&/&�&�&&@H&&#P?�3/+]?3/]�9/�/+q�3/]]q�399//]�10]]]+]2#"&'732>7!7!>54&#"'>#P�n@4Ty�h���
*=M,InQ4�r�uki��#�M+b�tL���k>��:S52_�W�/��_e ��"���M9�@r�0�#v:5%
*-G�

/
�
�


G0 O � �    ;RK@
H%PQ2PXYX+?�?3?39/3�2?�/+3//+<�++ć��3/]]q�9/]]�310]]]]]]]#".547##33>324.#"32>7>�e��^Z�h9�_�ҴY�_��bc�g4�9R3/`VH
";R00^TF
�4i;��t5;q�h#'�:�6}�t88l�cMlE L�t7c*RtH!M�u<d��:�@nm	}	�	eu� 	H

	RLRK?O�� G/O@HQQXYX+?33/3?�9/�22/+]�3/3/]3//+<�++���9/9�++�10]]+]]	#.54>3!##"3!����C?.I��d�ҴY`�DlK(.D-�6�&EcCa�L����/P=!<-��E��'�'C�H��E��'{&Hi�@
4&l86 %+55+55"�W
�9:�V4V"0���@zH77
22

		  26R6K	
		
		

	_	�	�	<	�	0	p	�	�			?	O		�	�	�			;"(RK���@;
H@
H"Q��6	P
2(.. ...PXYX+?�?3?9/]9�999/]q3�2/+33/+//+<�+��+��������3/]qqr^]3/F�:(
		:
+</+<+H�++�9/10^]^]]]]+]]"&'73267>54&#"##7373!!3>32G"E
2;;�
T_@q\An����-��
"HWiC��
�'Bc�W	�T\�'QOX3]�Q��������!B7(/L5��$Z&�>jN-!��@EVu	�		�@/?O@H
RK@
H���p�����@+.H�`����@HSQXYX+?3?�?�/+qq+qrr�/+3//+<�++�3/+/]�]10]]!#?3�������:��I:w��C���N2�@5E
*++H I���@;H   4*0F@
H-Q**%P  / � � �  @H  P����H����H?3/++]�?3/+]�9/�/+�23/]3/+]��9/10]]%2>7#".5467>32.#"!!�7VD5�Pm�Wf�a.	CUail4W�]4�2H0AjS>��sfz">T25KvR,>o�Z(^-o�uL+.TuG-J5%S�`�5�������KV��!��&��O�ڹ	���88H	����77H	����66H	����55H	����44H	����33H	����22H	����11H	����00H	����//H	����&&H	���@)!!H	@H	@H	@H	@H	@H	@H	@

H����//H���@$&&H@""H@  H@H@H@H�%+5+++++++55+++++++++++++++++++��+�{&��i�Z�
���&&H
���@8!!H
@H
@H
@H
@H
@H
@H
@H
@

H&�%+55+55++++++++++����W��M�����:&5@zk{��H���@	
Hbr�U%%���@�	H

R
K

��!5'  'R'K				@���G/@H/@H/@

H///7/?O@H5Q!!	
Q P',PXYX+?3�2?�?3�29/�/]+]3/]+++�9/]q3/F�6(	6
+</+<+H�++ć��9/]q99��++�10+]]]+]+]]]#"&'!#"&'732>7!3232654.+�C~�rp�d��PDiWKOY8"= '04<NfCY�b�s?�2=@>��>bE�O[�X*���؊O�
P��.�6 En��dl)A-"��':'�@j
z
�
c	s	�			U���@j	H'RKG!@H!@H!@

H!!!)RK@
H'QPXYX+?22/3?3�29/33�22?3/+3//+<�++�3/]+++�9/3/F�(((
+</+<+H�++������10+]]]]]32#"&'!#3!32654.+Y�b�s?C~�rp�d[�J_�ҴY�Y=@>��>bE�:�6 EnN[�X*�:�6�Kdl)A-"
�+�@VV%


���@IH

##	RK


/
O
�
�
�


-"%!!R ! K    ���@7
H @
H%Q"�� P	  
XYX+?33/3?9/]9�9/]q3�2/+33/+//+<�+��+���������3/]q3//+<�++�9/10]+]]]]]>32#>54&#"##7373!!w"HWiC��
w�v
T_@q\An����-��
Y/L5��$Z&��]'QOX3]�Q��������!B7(��"	�&�tG@&�	%+5+5��V��J�'C?������Wg�&\��@ &�%1%+5+5V�tJ:+|@%L!\!l!/?O�++(
&&����H	���@
H0	$$Td���@�
H$4D
RL(RKD���_o���-RK(+((+((++�((/((@#H(@H((P$+XYX+?2/3?33/3?3?�9/++]r3/F�,(+((,
+</+<+H�++�3/]qqr^]3/F�,(,
+</+<+H�++�9/3/F�,(,
+</+<+H�++�10^]]+^]]^]]++]]]]]]#32>73#4>7##"&5467G�W&~
T_@q\Av��
�		"HWiC��
D��H~�{'QOX3]�Q^��"KC0,9;/L5��$Z&�o���!;!7!7!2#".5467>$"32>7>54.>���@:�ޙO	
����K	
����̗b	8k�b�͖c	:l���b�b�U�Ԁ1i0���^W�ڃ.`1���_�J�ʀ+W'k�k5K��}*Z&k�k6C��2M!;!7!7!%#".5>7>324.#"32>7>y����{�k��d`�o=	f��ij�m8�"A\95kaP
&C[66i^O3�b�bx4i;��t5;q�h/d6~�v88l�cMlE L�t7c*RtH!M�u<d?��@S
H��� HJRK�	R^@	H__lmX+/]3?3?3�2/+3/F�(
+</+<+H�++�3/]3/F�(
+</+<+H�++�10]+]+3%#�P�n���������"x��@V HRL���@+H	RK@
HQXYX+?2?3�2?3/+3//+<�++�3/+]3/F�(
+</+<+H�++�10+]3#!3!ִ��M�g�}:������.�&:��1&����250%+5+5��f5�&ZC��)&��ִ*-(%+5+5���.�&:��@1&140%+5+5��f5�&Zt&@)&t),(%+5+5���.�&:�o@
1&530%+55+55��f5{&Zi�@
)&)-+(%+55+55�����&<�"�	&���

%+5+5�����Wg�&\C�@ &,!$%+5+5i�|p�/�Ͱ��/�ְͰ��017!i�Р�i�|p�/�Ͱ��/�ְͰ��017!i�Р����L9@*/?O@H�?O/?o��@&+H/+]q�/+]/107!
r����
L9@*/?O@H�?O/?o��@&+H/+]q�/+]/107!
����
L+@�?O/?o��@&+H/+]q�//107!
����`���R'B��B����
@'7F������@�"(Hi&6F����
���f	9@H�������tdVF6&�������vfVF6&�������tfVF6&�����V��������tfVF6&�@���������tbRD4$��������tdTD4&��������tdVD4$i��������tdTD6&��������p`TD4$�����@.���t`P@0 9���rrr^]]]]_]]]]]]]]]]]]qqqqqqqqqqq_qqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqqqqqrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]qqqqqqqqqqqr?��2/+^]]]��9^]qq+r10]7>733�
#+y:K
Y&��4VKB A�A�����K@�tVf�������@!H4&����t���@H	)		��V
�
�

����%*H
���@�"HI


@	H

���vfF6&�������vfVF6&��������tfVF4$�������dRD4$��������rbRB2"���������r@�`P@0 ��������rbRB2$��������tdVD2"i��������tbRB4$��������p`TD4 �������dP@0 @9�����rrrrr^]]]]_]]]]]]]]]]qqqqqqqqqqq_qqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]qqqqqqr?��2/+^]]++]��9^]+]]qq+qqrrr10]#>7#73�
"*{9K
X&��4WKB A�?������A@+		���
�
�

�
�

@H


��
/3��/]+_]]]��910]%#>7#73
"*{9K
X&�45VKB A�?�����$@�/�	�?��2/]3�9/910##.546?�&X{��)3Y.'U+ G*����h@F
��
{
;
{
�
�
�

@H
����@	H��?3�2�2/+]q��9/+_]qr��910]]7>733!7>733�
"*z9K
X&��
#+y:K
Y&��4VKB A�AÒ4VKB A�A������@�+;+;�7W���GW����5&�Gw����H���@Hw(HX	��
�
�
I


�
�
g
w


(
H
	

��������yiH9)	��������wg8)������@���vfWF6'��������wgUE6&��������tdSD5#���������t`P@2$��������rbRB4&��������vdT@�B2$i��������p`RB2$��������rdTD6&������dR@0 9���rrr^]]_]]]_]]]]]]]]qqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]_]]]]]]]]]]]]]]qqqqqqqqqqqqqqqqrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]qqqqqqqqqqqqq?3�2�222/^]]]qqqqr��9/]]+qq��9^]]]qqqqrr10_]]#>7#73#>7#73�
"+y9K
X&��
"*{9K
X&��4WKB A�AÑ4WKB A�A�����&�q@(++w�F'7��7G���@. %H%�����gw�(�����@H(�	��
���@�H�
�


(
	
��
�����vgWG�������W6&��������ueUF5&��������wgUE4$��������tdTE5#�����@�����ucTD4%��������pbRB4$��������tdTB2"i��������p`RB2$��������pbRD6&�����ydP@B2"9���rr_r^]]_]]]]]]]]]]]qqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqqqqqqq_qqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]]qqqqqqqqqqqqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]qqqqqqqqqqr/3�2�2/^]]]+��9/]+��9^]]]]]]qq+qqrrrr10_]]%#>7#73#>7#73

"+y9K
X&��
"*{9K
X&�25VKB A�AÑ5VKB A�A��vX��@g�	)){���{i{��g"	�p
�
/



o/_���	
�/?�2�2/]q3/]]9/3�2������10]]]q]]]]]q]]]#73%��s�� Z-�eg �r�x���D�sZ��@[	�����p�/o/_��
�	��?�2�2/�2�2/]q3/3/]]qr9/�3/9/]r���������1073%%%%#7G�� b9�Yo ��Z%m ��:�Z�� dY��x����������x�Ga����@9��
����6fvv��I&��6fv	���@
H	
�
	
���@�
H�		@HfVF&��y&����y��iY��Y)����)��iVF��})n�����;+�����{m]K=-����_@0>���rrr^]]]]]]]]_qqqqqqqqqqqrrrrrrrrr^]]]]]]qqqqqrrrr^]]]]]]qqqqrrrrr^]]]]]]qqqq/+^]q�+/^]�+^]]]]qqqrrr10_]]]]#".54>32�,Mf:9cK++Kc9:fM,�:fL--Lf:9dJ++Jd���H@2�
��?_o���/_�����	�/22�22/]q�/�9/�10!73!73!73�+�+��+�+��+�+������3����-G[u���@��gHue�et���@Hzr�r9Hu7�7F���@HzD�DHu	�	���@Hz�����y���l 	H_���@		H> 	H1���@		H 	H���@	H����I����Y�3�@
O��@�@�@�@@���@	H@@}�n�@za�y�������ɇ�@&)H6��@H�������y�f�I�;�&�	���ُď������i�V�9�$�������ɏ��i�[�F�)���+�����@�	�9�
��!�6Ffv���	���R�;vH�\.�i;$�
����ۏ͏������i�V�9�+�	���ُˏ������i�[�)�����֏������t�f�I�6����֏����d�V�4�&�	���ُƏ����y�m�[�K�;�$������ˏ������k�[�D�+�@�����ˏ������t�[�@�4����ԏ�������[�O�0�$��i�ۏď����{�d�K�;�$���ۏď������k�T�;� �����ˏ������k�_�+���9�Џ����rrr_r^]]]]]]]]]]]qqqqqqqqqqqrrrrrrrrrrr^]]]]]]]]]]]qqqqqqqqqqqqrrrrrrrrrrr^]]]]]_]]]]]]]qqqqqqqqqrrrrrrrrrrr^]]]]]]]]]]]qqqqqqqqqqqr?���?3�2�2�2??/^]�3/^]8��^]]]]]]]]]]qqqqqqqqqqqrrrrrrrr3/+q+q���9/+]q���9/]]810++++++_]]]+]+]+]+]+]+]2#".5467>"32>7654&2#".5467>"32>7654&%2#".5467>"32>7654&#3�2XB'	Vfi)3YB&Vei'!?8.;6<7/82XB'	Vfi)3YB&Vei'!?8.;6<7/8c2XB'	Vfi)3YB&Vei'!?8.;6<7/8�Х���;_EL&f�I>bDJ%k�Fl2ZIN7IE3ZGP;H@��;_EL&f�I>bDJ%k�Fl2ZIN7IE3ZGP;H@l;_EL&f�I>bDJ%k�Fl2ZIN7IE3ZGP;H@����z��5@# H6F	)2B@�/�?�/]]�]10]]+3�@Ğz�����z�&UV���]@Egw�hx���[�@#H@H/�/o��/]�/]_]++]q��2/]10]]%73	_����	�i	�m?s(�����R�\@"fv�iy���`�P����$+H���@H/�/o��/]�/]++]q��2/]10]]7#7	73������	�'ot��?��M��&�����T��/�//10!5!���^����3@�{��p/_o���??/8/]]]810]]!#3�l�~��\��%J� ��@VH_="M"]"9I#)#)$%R%�	���@#H�9Y'6'�'�'V'f''����%(H'���@; #H�'�'	''I'�'

R�

	



�
$%���@�	
HIYy�+;	�'�'�'�'�'�'�'�'v'f'V'D'2'$'''�'�'�'�'�'�'�'�'t'd'P'D'4'''��'�'�'�'�'�'�'t'`'P'D'4' '''�'�'�'�'�'�'�'t'd'T'4'$''�'�'�'�'�'�'�'�'t'T'D'$'''��'@ƴ'�'�'t'`'T'D'$''�'�'�'�'�'t'd'D'4'�'�'�'�'�'4'''j�'�'�'�'�'t'd'4'$'''�'�'�'�'�'�'�'�'t'd'T'D'4''�'�'�'�'t'`'P'D'4' '''9�'�'rr^]]_]]]]]]]]]]qqqqqqqqqqqqqqrrrrrrrrrrr^]]]]]]]]qqqqqqqqqrrrrrrrrrr^]]]]]]]]]]]]]]qqqqqqqqqqqqqrrrrrrrrrrrrrrr^]]]]]]]]]]]]]_]]qqqqqqqqqqqqqqqq/^]]]]3/+299/99/]q3/]�++ć�^]]]++qr3/]]]+]qr3/�++�10]]]_]]+>54&#"#>533>32�I/4HiG�dw+4@*Q]Kv0/1d^���.("#. Y\0�������@Q	

		R	^



@	H
`	_/?/���	_
	lmX+?3?�9/]q�9/3�2/+33//+<�++ć�������/3/99//10!!!!##73!�\��Q)��n6�6���{�$�Ձ��������:c@6z�z�Z	j	%$%"2B"(B(R(b((

���@�H(,,&'*+./&/
&/R/q





o:0:@: 
0

:
:o<@H
/4.u+*u'&��_o���@H�_o��� 4#s4t:::��X+?3/]�2?3/�99//]]qr+]qr992�23�299/+3/]�99//]]�3/F�;(

;
+</+<+H�++����������939310+]]]]]]]]]]]#!7>?#737#737>32.#"!!!!!2>73NeyC�Fbv����Em�gFy^@�$6D(r��h��h	+=M-�,O@06UwI!�.�y����\�f7:V820$s}����8gWD+E2N����
)_n�@	|Zj<���@H%5Eu�%(u(�(] 	H@���@�	H(	H%[#)$#)Rr#'##'##IoH`n>oQ4o[KH[HkH�H�HH@H?HH/H/[?[o[�[�[�[[@H�#�#�##@!H#0#_0#H[QQ[H#h-o.@H..pfggRgqhihiihiih@	HhfPQV94C1u*O._.o...*#$u'*'())`)'P'`''))'LPighLuCC@IPI`I�IIIC��X+?3/]3�2?3?�9///]]]]333�299/3/]�9�/+3//+<�++�3/+�9/////]]]+]+]]_]+]����F�o('##o
+</+<+H�++������93310+++_]]+]]4.+3263#3267#"&5467#73732.#"#".'732>54.'.54>%+#32g-RsF8l^���LH*!"$Q^Ohodn鍞�QKJX.K62VA%5d�ZKqS6�XX-K63P8+R@'5^��3O�Ԅ�j��t��F�A\<�Ԝ+��v1R~bV8����u`*:'/"
$7M5CdB!0L54;8+!$"3I3B]<�n�yA���6d�����;�@�z�z�{:�:�Ueu6%6.%.5.%
!%9o$8o9 90999=..%%n0'$@	H$$u''-u00_'o'�'_'o'�'�'�'0/0?0�0�0/0�0�0'0'0399s3s`?3/]�?�3/99//]q]q3�23�2/+22�2233]3/]�9/]�9310]]]]]]]]]]&!!!!3267#".'#73>7>7#73$72.94lg]$�A�R�A�l @`Bj�*�Pn�W]�qA�@p�AqN'�W�c>
�sK�y'J#�V�b5ia0BmO+@��#J'
�+OqE#[c�����,0J^�@�-�-�//Hz<�<�<I���H���@HEUe 	
HA 	
H4���	
H0.���@0.0.6R�C�6�'\7\G\\\\���@>!H�\�\\\`�`�`�`�`{`L`\`l`-`=```�`�`�`�`�`�``�@@� HH`9```�H�`�`g`�`(`	``���(��@)6H@&H@HK�1�U�>/-�	�!��`�`�`�`�`x`i`X`�`�`�`�`h`X`H`9`(```ʸ`�`�`y`j`Z`I`9`)``�`�`g`H```�`�`�`�`�`y`j`Z`J`;`)``	`��`�`�`�`�`@��`�`z`i`Y`I`8`)```�`�`�`�`�`�`�`y`j`X`I`:`)``	`�`�`�`�`�`y`i`Z`I`8`)``
`i�`�`�`�`�`�`�`�`�`�`�`~`n`^`8`(```�`�``�@vW]H�`�`�`�`�`�`~`o`_`8`(`/`?`O```�`�`�`�`�`�`�`�`~`k`Y`J`;`+```9�`�`�`�`�`�`_r_rrrrr^]]]]]]]]]]]]]]]]qqqqqqqqqqqqqq+qqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]]qqqqqqqqqqqqqqqrrrrrrrrrrrrrrr^]]]]]]]]]]]]]qqqqqqrrrrrrrrrr^]]]]]]]]]]]qqqqqqqq?3/^]���3/???���/+++]���3/�^]]]]]+qqq+qqqqqqrrrrrrrr3/]+qr���99//8810+++_]++]+]]267#".5467>32.#"#32#".5467>"32>7654&�Ij�:ToFLlF!Vm�DDfE$�#6%4R?.$80����5^G)		Zjq/7_F(	Xjq0%F>4B<#C>4?|PY6^F(2XyG"G&}�b'*F^5!9*#MwTUC-K6����� IvU%]0~�["$MxT%[.��WlCxbjHb\Dx_oK`V�����*:�@��)�1\l�?1O1���@\	H
((�(0	H!!%&"1"10%0Rr&&@6666&&!!&"01%+!!++?�?�9/9/9�999/3/3/3/]]�2/͇++����������10+]+]]]]]232673#"&546?7>7>">54.2P]Dt�V-0:V&E9EU6`mHH&G"r.HeE,	aGtR.�dlrϰ�-�A=6hmLuN(lnC�!IKJpK&L#<R/�*w��d0%T[�5Ix@UeuHZjz���@)HG�6�6�66(H�6�66H66�@�@�@@���@	(H�@�@@���@	H@@���@	
H 	
H6R@A@�@76776AGRG�@
HIHHIHIIIH&�@*	@	H3	HH HPH�H�H�H�H�H	H	H	>37=R=�@>?>??>??>>/>>�@1�3�3�3�3@333K+��?HG@G7?`=6IA>lmX+?3/�?3/399//��3/]]�/]3//+<�++�99//]3/3/+�3//+<�++ć+�+�10++]+]+]]+]+]]+]+]!7!".5467>32"32>7>54&	#367>737{�LrK%Hh�OMqK%Hh�*J?2
*;$+K@3	
W�J�����
���++OnCC ^�V(+OnCC ^�V(�6`M*D1E+8dO*F^M�3�.0)]'�Q��A+0)e3���z�)1@		H�	���@HYiy'H'H���H���@�H�@,�101@11+�.?.O..+�@,/,,)�'/_o��3o3*.�/'/		, ,P,�,	,/�3�3_3O333�3�3O33�3�3O33;�3�3rr^]]]]qqqqrrrrrr?�^]2/333/33�2r3/]33�/]]��]]�]]9/]�2210++++]+]+5#.'&'#367>53##5!��l����
���(���z��	���$,6

����3)"�����h�oon��9O@�y7ucs�cs�c%s%�%c$s$�$�3Y3i32 H�Yi H:&1 $H�1]1m1}1;1K1	 $H�	]	m	}	;	K	+[k  + [ k 0\(
\�/O( (0((�(�((���@cH((#)3)))�)�)�))"[/5�55JZr�fT[���@%H@H@
H;'@
H'
0'*_)_?�?3�2/+33/+++]�3]]]q/]�3]qq99//+]q]]��10]]]]]+]]]+]+]]+]]]]]]]]267>;!5>54.#"!532.54>��Z;m�c*'!G�`�Y*=t�lm�t=*Y�`���G!'*c�m;Z���V��j���6��3~��Ut�|AA|�tU��~3�6���j��VX��|H"/r@KZ@�#�#[#�#T#d#t##+#;##1�/�;K@H///)?2]]�?�9/�/]_]+]�]2�]]]qq�210_]".54>32!32>7.#"k�ƆE,Lfv�?q��Q��@NX.Kt]M"H$Sn��;L]53WJ<"]��ob�}]<O�у��-# <W7*9dL,�*" *����=��N�&y' ��n���"?555��>��N�&s	' ��n���R?555��c��N�&�' ��n���>?555��|��:�&�>' ��Z���>?555�b�B@	�/�/��29910#.'5>73!�;H:�RR�:H;�'"bADp*$*pDAb"V���@
@�/��299/�105>73.'#�"bADp*$*pDAb"V�;H:�RR�:H;�#�b�B@	�/�/��29910.'3#>7!5�;H:�RR�:H;�#}"bADp*$*pDAb"V���@
@�/��299/�10%>7#.'53+"bADp*$*pDAb"V�;H:�RR�:H;��b�B$@�@�/�/�299��29910#.'5>73!.'3#>7�;H:�RR�:H;�;H:�RR�:H;'"bADp*$*pDAb""bADp*$*pDAb"���&@@�@�/�299��299/�105>73.'>7#.'5�"bADp*$*pDAb""bADp*$*pDAb"�;H:�RR�:H;�;H:�RR�:H;�H�#(@# /�299����299/3�210!!5>73.'>7#.'5� �"bADp*$*pDAb""bADp*$*pDAb"hPX;H:�RR�:H;�;H:�RR�:H;8����/E�@y� �   HZ2j2DH���@H+C{C�C���@H DDD���@H
*'���@*''0F@O�G<G@
H5QA+''"P+AP
?�?�3/9/3�/+�3/]q�39/810]+]]]+]++]+]]#".54>323>54&#"7>32.#"32>�
`��a]N#/If�S*M@2��;:6$*tCq�^)�$3@$5VC1 *A,BlS8�.gkj0�ΐM?k�K<���h?0C*:"���'X���*J7 3Tntt05[C&c����h@ARKRK��//O_?�2?33/]3/]]]9=/�+�+ć+�+�10353.'!���""��4����._N66N_.����9��8@$Z?O��	Z/?���_/2/?�/]�3/]q�10!#!�����9��ZH���9`��@k	{	�		@H�		���@Hk{�@H����@EH[@H@H_O
_
o



[	[/O_o_�	_/�2?9=/]3�2/]�3/�3/]99//]_]++�10+]]+qr+]+qr5	5!!	!�{��B��H����9m;6j������`f�7��p���@ HO0�������?�/]q/]]+]q105!��`��3��bTk@	H	
H���@;H	9I$����@H@H�/3/9/]�/+3/]/+qr89=/]3310]++]##5!3nj�����u�N�i�o�#3CN@�YiyVfv�25CEC

!'4/?///<?O$�77*�AA'4?
�
?
O

@H

��	@$H�EEoE_EOE?EE�E�E�E�E�E�EE_E/EEE�E�E�E�EE_EOE?EE:�E�E�E�E�E?EE�EoE_E�E�E�E�EE`EE]]]]]]]qqqrrrrrrr^]]]]]]]]]qqqqqqqqqqqrrrrrrr/+^]3�]+]q29�]2�]2/]�/]]�910]]]]]]]]#"&'#".54>32>32%"32>54..#"326o,RrGa�FLTZ.EsS.,RtG^�C JT^3ErQ-��Fw83wM,F33G�]3wN+F10G/FxNN�j>��?fH'7d�XQ�h<��>fI(7e��~���(F^66\E'���(F^63]E)~�`�
�/�/�103!!�^j�8��^���_@�hxhx

��yiI9+	�����iI)�����iI)	�������iV-������k[K;/����oK;/������k[O;/��o[@�O;/����o[O;+���{o[O;+����k[Ki���kTD4��tdT4����{kK;/9�����r_rrrr^]]]]]]]]]]qqqqqqqrrrrrrrr^]]]]]]]]qqqqqqqqqrrrrrrrrr^]]]]]]]]]qqqqqqqqqqqqqrrrrrrrrrr^]]]]]]]]]]]]]qq_qqqqqqqqqqr^]]]]]]]]]qqqqqqqqrrrrrrrr/2/�/���10]]4>32#4.#"Dz�bc�{Fg5_�NN�^4t��LL��t�b�l98l�d����9��#8@#!!%!
*FOPP/�?�/]]3/�2/10]]"&'532>54>32.#"$$K>#3B'2Z}K"K=$3B'2Y|�9�%@T0�^�V(		�
(AT,�-^�V)LP@�!C�@"H$X$�$�$HX��AHB0	H=0	H*��г	H0���@	H	H 0	H0	H
���
H��г	
H��гH���@	H?4Tt���@3H[$-@

H$EDEdE�E�EE$EDEdE�EE$E�E;�@-����#<H-"����H"���@H"�>@)<H>(��1$1411�@����'<H����H���@zH�@)<H��	@H@HTE4EE�E�E�E�E�E�E�E�EtEdETEDE4EE�E�E�E�E�E�EpE`E@E EE?�E�Err^]]]]]]_]]]]]qqqqqqqqqqqqqqrrr/++^]��+�++�+��]]��+�++�+�^]qr/+3/]]+q310++++++++++++]]"&'.#"5>323267"&'.#"5>323267<E�IAk-&A<82�Q)OMK%233E{4<=D(E�IAk-&A<82�Q)OMK%233E{4<=D�+!�%/

3+��Z, �&.

2*� A7$ι
��H���@�HHH{]
}
5:�<*

	dtP0`_o				@H	�	�/_o�

P	�			/]]3�]22�2�22/+]q3/]]q_qq39����������10_]]]]]]++++##5!!5!3!!!�����7��=������X��!�l�$�ܔ���d�GP%@		/@/^]�/]�9/�105!5!5!d�������8��d��?$�
�@��������@H(H(H����@3H�
0�@H�O_�@P�P�/]33�]�]/�/+]3//q310]+]+++]]]]5	5!A�Z��w��������쑑A$�
o���@H(H(H����@4H�	0P�@H�@P�O_�P�/]�]�]33/�/+]3/q310]+]+++75	55!AZ�����XX��u�����7�	#@i	y	iy/�/�/���10]]3	%!	���������{����R��Vd�G�
�/�/�107!!d��>��T"����� H	��//��/��10+#47632#"'.'&#"��TR�?K3%
!$	��V�{{?0(4
''#i���� ���H��//��/��10+3#".54>3232765"�Z(g>2%!%����}86'"%)j��%������?�33105!
�%����iH����??��103#ؑ�H�K�����"�������?�?��310!!#�(�i����n�����"������?�?3��105!#
(�%�����%�H"�����??���3103!!�����H�n���%H"������?�?3��105!3
��%��������H'�	�������??�?��23103!!#����i�H�n��n����H'��������?�??3��3105!3#
���%���K�������(�	������?�2?3��3105!!#
��i�%���n���%�H(�	������?�3?3��3105!3!
���%���n������H3�
�@	
�����?3�2??3�2�23105!3!!#
����i�%���n��n���q�j%�	������?�?�3233105!5!
��A�ّ��������H*A	���?2?3����103#3#ّ�h��H�K	��K����j	1���
	�������?�?�?��23310!!!!#�(�i��i�j�ב�"����	3��	
�
��	��?3?�2����310!###���ב���n��n#���j?�
	A	������?�?3?�����3310!!#!!#����ht��j���o��"����j	1�	��

����	���?�?�?33��3105!5!5!#
��i(�q�ב�)�������	4A		
�

����?�2?33����105!###
ܑב%�����n������j?�		A	�
�����?�?3?�����3310#!5#!5!ґ��t��tj�)F��)ޑ�q�H	1��	
	������??�?���233103!!!!����i���H�"�ב�%�H	4�A	

���?3?3�����3103!!33A��$��H�n�#�n�q�H?�

A������?2?�?�����33103!!3!!ّK�$h��H����"���qH	2�	�

����	���?�?�?33�2�105!5!5!3
��i��q�ב�)��%�H	4A	
�

����?�3?33����10!5!333�$�ב%���n���q�H?A	�		��
�����?�?3?�33����10!5!3!3!5!�$K������q�F�������H6�

���	�������??�?�?��2233103!!!!#����i��i�H�"�ב�"���H8�
���������?�?3?3��2��3103!!#3#A�������H�n��n	��K���H	I�A	�����
�	�?3?3?�?����2�23310#3!!#3!!j���t������	�����"	��"�����H8�	�
�@

��������?�?�??33�22�105!5!5!3#
��i���q�ב�K������H;A

@
�����?�?3?33�2���105!3#3#
㑑h��%���K�#�K�����H	I�


A	�����	��?3?�?3?����2�233103#3!5!#!5!A�������㑑�tH�K	�����)ޑ�����j9�
����	�����?�2??�33��33105!!#5!
��i��i�q���"�h��������:�
�@	
����?�22?33����3105!!###
���ב%���n��n������jJ�
	�����	����?3?3�2?���33��3310#!5!3!!#!5j��t�t��t�A��ޑ��"ב���q�H:@	
��	�����?�3??���3333105!3!5!
����A�ّ�"�������%�H:�
	�@	�	���?�33?33����3105!333!
�ב�%���n��n���q�HL@		A�
�	��
��?3?3�2?�����3333103!!3!5!5!A���������H�"�o�����������HL�
	���@����	���
�?3�2?3�2??33�22�2233105!5!5!3!!!!#
��i����i��i�q�ב�"�ב�"������HM�	���@	
�	
����?3?33�22?33�2�2�2�23103!!###!5!33A���ב���H�n��n��n����n�����H]���		������	��
��?3?3�2?3?3�2�2�233�2�233103!!#!5!3!!#3!5!A��ב�t�t��������H�"���ޑ��"	����m�H��/?3310!!��U�m����m��?/3310!!��U�������H���??3310!!��U���	����H���??/310!!�*��	����H���??/310!!��*��	�*g����#'+/37;?CGKOSW[_cgkosw{����������1����������mUE-
y�@
xlTD,xeM5��@
�dL4�qYA)}�@
|pX@(|aQ9	��@
�`P8�u]=%��@!�t\<$�x�|����|�x���������iI1!��@hH0 
����������gck��h�d`h_[W��T\XT�SOK��H�PLHC?G��D@<D�;73��0�840+'/��,($,�#�� � �������{�@<x�|xTHD0, hTHD0, xx ,0DHTh
l����;���wso��tpl/33�22/_]]3339//////////]]]]]]33�22333�22233�22333�22233�22333�22233�22333�22233�22333�222�222/_^]33333�222223/333339/////33333�2222233333�2222233333�2222233333�2222233333�22222�22222103#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#3#3#3#3#3#ghh�hh�gg��hh�hh�hh�gg�`hh�bhh
hh�ahh�ahh�hh�hh�gg�hh�ahh�ahh�hh�hh�gg��hh�hh�hh�gg�`hh�bhh�hh�hh�hh��hh�hh�hh��hh�hh�gg�hhhhhhhhhhhh"bbbbba```````````c```````````c``````aaaaab^^^^^baaaaa``````�bbbbb#`````��b��`��`��a��a�`T����#'+/37;?CGKOSW[_cgkosw{��������������������������������#'+/37;?CGKO3#73#73#73#73#73#3#73#73#73#73#73#3#73#73#73#73#73#3#73#73#73#73#73#3#73#73#73#73#73#3#%3#73#73#73#%3#3#'3#'3#'3#'3#'3#3#73#73#73#73#73#3#'3#'3#'3#'3#'3#3#73#73#73#73#73#3#73#73#73#73#73#3#73#73#73#73#73#3#3#3#3#3#3#3#3#3#3#3#3#ghh�hh�hh�hh�hh�hh��gg�gg�gg�hh�hh�gg�Zhh�hh�hh�hh�hh�hh��gg�gg�gg�hh�hh�gg�Zhh�hh�hh�hh�hh�hh��gg�gg�hh�hh�gg��gg�hh�hh�hh�hh�hh�hhggg�gg�gg�hh�hh�ggghh�hh�hh�hh�hh�hhggg�gg�gg�hh�hh�gg��gg�gg�gg�hh�hh�gg�Zhh�hh�hh�hh�hh�hh�hhgggghhgggghhgggghhgggghhgggggghh"bbbbbbbbbbba```````````````````````c```````````````````````c````````````aaaaaaaaaaab^^^^^^^^^^^baaaaaaaaaaa````````````�bbbbbbbbbbb#```````````��ba```c```c``ab^ba``�b#`C����IMQUY]aeimquy}��������������������������������	
!%)-159=AEIMQ!35#35#35#35#35#353353353353353353353#3#3#3#3#3#335335335335#3'#3'#3'#335335335335#373533533535!355#%355##5##5##5#353353353355##5##5##5#35335335335#3'#3'#3'#3#3'#3'#3'#335335#3'#335335#3735355#5##5#353355##5#35335#3'#3#3'#3�+jjjjjjjjjjjkjkjkkkkkjkjkkkkkkkkkkkkk��kjkjkkkkkk�kk�jj�jj�kjkjkkk��jjkjkkkk��k?k�k�kkkkkkjkjkkjkjkkkkkkkkjkjkkjkjkkkkkk�kk�jj�jjkk�kk�kk�kk�kjkjjj�jj�kjkkjjkj�Vk�k�jkjkkjkjjkjkkjkjjj�jjkkk�kk��"a"a#`!b!b!`````````````�b��`��`��`��^��`j````````�bbbbbbba````````�````````�``````````��aaaaaaaab^^^^^^^^��aaaaaaaa`````````�bbbbbbb"bbbbbbb��````�bbba````�````�`````�aaaab^^^^��aaaa`````�bbb"bbb{uZT!!{�!T�!���/���/���10!!!�7L1�7}��1mi{@
0/�/^]]�10!!i���mi{"@0/���/^]]���10!!!i��L����Pb��h!!�h���L�!	�XV��R���Z�	��7��������L�	L������R����Z�Z�7��9e%�	N@;	 @`� @`����	?O_@H?/2/]3/+]3/]q310!#	3	@R�7�R���b`��15���>��.)'.@D%T%K![!K[DT
#/���/���10]]]]4>32#".732>54.#"�Fz�^^�{GG{�^^�zFV9b�LL�c::c�LL�b9d^�{GG{�^^�zFFz�^L�c99c�LL�c::c���#��/]�/�102#"'&5467>76jnk5R������S4l�9R46n9������:m64R9)���	/���/���103!32>54.#")��Ex�[[�xEEx�[[�xE��}A[�xEEx�[[�xEEx�)��+"@"	'/�����/�����103!4>32#".'32>54.#")��Q:c�KK�c::c�KK�c:MEx�[[�xEEx�[[�xE��}AK�c::c�KK�c::c�K[�xEEx�[[�xEEx�s�cu"�/���/���10#"'.5476324'&#"3276c%%%V3eK#%HJfgGJL33FF3331HH13}5V%#%H%V5fHJJGgF3333FE6116���y�!-9D�@] $ t $t+{+{D"(?4.(.(.1%+7+>:h:Y:G:::<A+_+o+A@	H+A+A

/��]�99//+^]�3]]]]33�2/��]�99//�3�310]]]]#"'&54676324'&#"3276#"&54632#"&54632327#"&'y������ZZ����ZZZ���ڗ����٘��Z.  --  .�,  //  ,��L��L>b�^�0H��������[��[׀ٙ����ؙ���W ..  --  ..  --����#�_[����)4`@7*/$'!04h4Y4K4=442-_oO-_---
/�99//]^]�3]]]]33�2/�99//�3�310#"'&54676324&#"326%4&#"326327'#"'�������ZZ����ZZ�.  --  .�,  //  ,��0�^�b>L��LH��������[��[� --  ..  --  ..��[_�#��F�s;3F��/��@
H4.4$w##���@MHH;;	H;/4#4;B�
�
p
?
 

9+>���0/43?3O33/^]��]]]]�/��]]]]�10]]]]+]]++]]]+373#'#5.''7.'#5367'7>7"327654'.�B 965�-�-,��,(�1�7:"B?n0�+�(.��P�(�9p6Eu0bb0uE�`cc1u;� �-�;q9><n3�+� 	��	.#�-�3o?>�_�1�(,=20d��b2/aa��c02�P&�/b@>+�+�++"�"�""P'�''�@%(H
/�+�^]2�qr/]3�2/�qr�]�qr9/3�210.'&547>32!!#!5!"327654&'&�7Z#GS,e<vSVHHj�J��#S>>SW;=>B.*PlzS++VSzmQR�F��F�;G,+G>>=T,G;Q���AQF@(1A;NN?  33FF;A1?J7�77B�??/^]�]��]�99/�r�]�]�r9910.'.'.547>323267632#"'.'#"'&547632"327654'&�6%(
 ? .@$
		�TVWvvWTTUzGS�Z>==@XY<>><
			"O-@"'*R*�Qm}VXTTuuWV+ >=X[===>ZW>>;�/(@& 
0
`
p

"@H"O_/]//+3/]/10#"'!727>'#"'&547>7>76 (_E�#%?BX�c$&����}V+B,-�SZB?N9En&8�6_,+i?~BCF_?B��WVc	%%1E[wK`_B?[J;*U/;q9S<�K/@9M?4=C
/)//99//]923/]�10)7>7>7654&5#"&'&547632.'.547>3267>32#"&'.'F��Tl)@4:Z+X-;a)OII]P3N(a<tPPET3V$IPPp>�2+C.=�#!K2dmy;*&StsOP"4&sN&(PNmVb(%)LtvSP<3=-Q}.-L'f��Zy'&@)@Pp�///]�10^]].'.'.'.547632>32b*gL8E+%DFfbN/"�X2U#F)N<Kl ,8e02�fL]Aj8gGFHP6wu$"F^VX-wK`�76nB�����0K/]/]10.'.'.'>7>-qEEt/'xSEj(
#&b<^Q2�P;`�N�]]�5(�o]�H: 9�Pwc;�kM��;�!0@!
@O_o�!

//��]9/9/��]��2103#>54&'&'#"&547632�L�3:0./9@%%Hl9:<?P,.�d�E�UN�;A|;<c(Q	�?b&K6.I<<����"%#"&547632%#"&547632�$&%X3999>Q0*��%#Jj9:;<T--��?e#%'6/L9;�[��>b&J5-L9<�f����"&#"&547632#"&547632%%5�$&%X3999>Q0*��%#Jj9:;<T--���&��D?e#%'6/L9;���v>b&J5-L9<�g�u���E.�&IL9��E.�&IO9T�D�E���@+	H
�0
@
P

�?O
� P`p/]�/]�/]/�9/810+#"&'732654&#*73~j$JoJ#10$ZL7@1>VMG.M8`:.!`X�9f��
,�5E��д	
H
�@	���	/��2/��910+]#>7#73RC1u6D	X ��Wu/0`.�'3��
�@jVfx�FVf���@uH%U
�	�		@HR�_o���@*2H@!$H	��/?�@H��?3?339/+]3�2/]]qq3/++]/]q3�++������+]10]]+]]]]#7!733!>?#�#�k�l}�		�
8岲o-��q
%'#��!&&"N'��&��v	�	���
H����H���	
H
���@I	H%�&@H&&�/?O�(�/?&&"����@P`���?�?3/]�9/]q3�2//]�3/]3�9/+�3�10+++]+]!!>32#".'732654&#"#���rCV<8[A$(OvO@aG-�'9'QSM>2M|�q�'"?Z8HuR,":M+1&b]QJ#�4�R@6�{
�
	

Y
i
����@H@H�
��?3?�2/++]3/]�9/�10]]]#>7!7!R�xU�Vw�N�*aa���ff���XqK(��%7E����@XH#H	
H	H
;	A3�!�A!A!A�?+O+�+++++G;�p/?O
8�&&>��.��?�?�9/�93/]]]�3/]]�99//��9]910++++2#".54>75.54>2>54&#""32654.�;`E%2G,<L(QzRFiF"'=M&7<)Mo+,9 CB5'
 3VVGLP^#9�4K.*H9&ZG:cI)"=T1:S9![=4T: ��+55;#5%+"f`M9KZV.%8�8�I@��s�t��@���
@/?�/]��2//�3/�10]]]]]".'732>7�HqXB�,;L14VD2gBd�� =[;=*D04G($2dO1��
-�5E��д	
H
�@
���	?��2/��910+]#>7#73�C1u6D	X �Wu/0`.��+ 
+@(	
H0	
H
���
��
/��2/��910++>733#1C1u6D	X �%Wu/0`.�X���0@r��@��0@�_@	H/+]]q�/�10]%73}��ϭ������0@p��@��0@�_@	H/+]]q�/�]107%3��������\�	�@
f	v	�	x����@_H	`p��/0`��@p��� P��@	
H@$H@	��0@�_@	H/+]]q3��+/+]]]qqr3/]]9=/3310+]]#'##7%3\_���q��������	�@Q�������@Pp��o@P���_0O_������@$H0@�_@	H/+]]q�+�2/]^]]]qqqrr3/]9=/]3310]]]#'73373x̻]��t������G�?@'��0��0@�_@	H/+]]q3�2/]q3�2/3�21073!73w#�#��#�#��������B@)�%
*�!
��@���/2���3/�3/]�10]]]".#"#>3232>73�*LE?)
d&9S;,LD>)	f%9S�(1("/-_N2(1("/,_N3q�|�j��	���@DH	�+K[k�@H���p��@��0@�_@	H/+]]q3�2/�]]_]/+]�+10_]?33?3q�����������������4@ ���	�0@�_@	H/+]]q��2/�3/�10".'33273�JnJ%tgS�@uA[v�)EZ1;6r1[E)X	$��<��V��\��_��b��i��r��x��$��$7�h$9��$:��$<�h$Y��$Z��$\��$��)��)��)��)$�h/��/7�h/9��/:��/<�D/\��/��3��3��3��3$�h57��59��5:��5<��7�D7�D7�D7�h7�h7$�h72��7D�D7F�D7H�D7L��7R�D7U�h7V�D7X�h7Z�h7\�h9�h9��9�h9��9��9$��9D��9H��9L��9R��9U��9X��9\��:��:��:��:$��:D��:H��:L��<��<�D<�h<�D<��<��<$��<D�h<H��<L��<R��<S��<T��<X��<Y��ILU��U��U��ULY�hY�hZ��Z��\�h\�hV��Vf��Vm��Vq�9Vr��Vs��Vx��V���V���V���[r��[x��\^�\_�h\b�h\i�h\y��\{��\|��\~��\���\���\���\���\���\���\���\���\�j\���]r��]x��_��_f��_m��_q�9_r��_s��_x��_���_���_���_��a��a��a^�a_�=ab�=ai�=a���a�^b��bf��bm��bq�9br��bx��f_��fb��fi��fr��fx��hf��hm��hs��hy��h~��h���h���h���h���h���h���h���h���h���h���i��if��im��iq�9ir��ix��mV��m_��mb��mi��mr��mx��o��o��o_�hob�hoi�hp���q�Fq�Fq�Fq�hq�hq^�q_�hqb�hqf��qi�hqm��qs��qv��qy�Fqz�Fq}�hq~�Fq���q���q��Fq��hq���q��hq���q��Fq��Fq��hq��Fq���q��Vq�^q��hq��Fq��hr�Fr�hr�Fr��r��r^�r_�hrb�hri�hry��r{��r|��r~��r���r���r���r���r���r���r���r���r���r�jr���s_��sr��sx��t���t���uy��u~��u���u���u���u���u���u���u���vr��vx��x^�x_�hxb�hxi�hxy��x{��x~��x���x���x���x���x���x���x���x���x�jx���z������������������������y���z���{���~���������������������������������?��������������������������q�������������y���~�������������������������������������������y���}���~�������������������������������������������������������y���~���������������������������������������������������y���~�������������������������������������������y���~���������������������������������������������������y���~����������������������������������������������������������������������������������������3��3�l���{������J���������������������3���J������J�����������������������`�������������������������������w�����������������w��������������������������������������������w�����������������3������������3��3�������l���{�����`�����������������������������������������������������������������������-���������������������������������������������������������������������������������������������������������������������������w�����������������������������{�����`���`�����������w����������������������������������������������������������������������������w����������J��J�����������������������������������������������������������������������������������������`��`��������������������������������������������������������������������������������������������������w�����������`���J�����������������������������������D��-��-��-��������������������������������������������������3������������������������-���������-���������������������������������w���������������������w���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������w��w��-����������������������������������������������������-��J�����������������������������������l���{������V��������������V`�Ca!��1lzx	P�.
o#>	�	#	S	Bh	,�		*@	��	(�		2	8_	\�	
�C	|�Copyright (c) 2007 Red Hat, Inc. All rights reserved. LIBERATION is a trademark of Red Hat, Inc.Copyright (c) 2007 Red Hat, Inc. All rights reserved. LIBERATION is a trademark of Red Hat, Inc.Liberation SansLiberation SansItalicItalicAscender - Liberation Sans ItalicAscender - Liberation Sans ItalicLiberation Sans ItalicLiberation Sans ItalicVersion 1.07.4Version 1.07.4LiberationSans-ItalicLiberationSans-ItalicLiberation is a trademark of Red Hat, Inc. registered in U.S. Patent and Trademark Office and certain other jurisdictions.Liberation is a trademark of Red Hat, Inc. registered in U.S. Patent and Trademark Office and certain other jurisdictions.Ascender CorporationAscender CorporationSteve MattesonSteve Mattesonhttp://www.ascendercorp.com/http://www.ascendercorp.com/http://www.ascendercorp.com/typedesigners.htmlhttp://www.ascendercorp.com/typedesigners.htmlLicensed under the Liberation Fonts license, see https://fedoraproject.org/wiki/Licensing/LiberationFontLicenseLicensed under the Liberation Fonts license, see https://fedoraproject.org/wiki/Licensing/LiberationFontLicensehttps://fedoraproject.org/wiki/Licensing/LiberationFontLicensehttps://fedoraproject.org/wiki/Licensing/LiberationFontLicense������	

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a�������������������	����������bc�d�e�������f����g�����h���jikmln�oqprsutvw�xzy{}|��~�����

��� !"��#$%&'()*+,-./012��3456789:;<=>?@A��BCDEFGHIJKLMNOP��QRSTUVWXYZ����[\]^_`abcdefghijklmnop�qrst��u�vwxyz{|}~��������������������������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,-./012��34���5��������67��89:;�<=>?@A�BCDEFGHIJKLMN�O�����PQ���R��STUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~������������������������������������������������������uni00A0uni00ADuni037Euni00B2uni00B3uni00B5uni2219uni00B9AmacronamacronAbreveabreveAogonekaogonekCcircumflexccircumflex
Cdotaccent
cdotaccentDcarondcaronDcroatEmacronemacronEbreveebreve
Edotaccent
edotaccentEogonekeogonekEcaronecaronGcircumflexgcircumflex
Gdotaccent
gdotaccentGcommaaccentgcommaaccentHcircumflexhcircumflexHbarhbarItildeitildeImacronimacronIbreveibreveIogonekiogonekIJijJcircumflexjcircumflexKcommaaccentkcommaaccentkgreenlandicLacutelacuteLcommaaccentlcommaaccentLcaronlcaronLdotldotNacutenacuteNcommaaccentncommaaccentNcaronncaronnapostropheEngengOmacronomacronObreveobreve
Ohungarumlaut
ohungarumlautRacuteracuteRcommaaccentrcommaaccentRcaronrcaronSacutesacuteScircumflexscircumflexTcommaaccenttcommaaccentTcarontcaronTbartbarUtildeutildeUmacronumacronUbreveubreveUringuring
Uhungarumlaut
uhungarumlautUogonekuogonekWcircumflexwcircumflexYcircumflexycircumflexZacutezacute
Zdotaccent
zdotaccentlongs
Aringacute
aringacuteAEacuteaeacuteOslashacuteoslashacuteScommaaccentscommaaccentuni021Auni021Buni02C9tonos
dieresistonos
Alphatonos	anoteleiaEpsilontonosEtatonos	IotatonosOmicrontonosUpsilontonos
OmegatonosiotadieresistonosAlphaBetaGammaEpsilonZetaEtaThetaIotaKappaLambdaMuNuXiOmicronPiRhoSigmaTauUpsilonPhiChiPsiIotadieresisUpsilondieresis
alphatonosepsilontonosetatonos	iotatonosupsilondieresistonosalphabetagammadeltaepsilonzetaetathetaiotakappalambdanuxiomicronrhosigma1sigmatauupsilonphichipsiomegaiotadieresisupsilondieresisomicrontonosupsilontonos
omegatonosuni0400	afii10023	afii10051	afii10052	afii10053	afii10054	afii10055	afii10056	afii10057	afii10058	afii10059	afii10060	afii10061uni040D	afii10062	afii10145	afii10017	afii10018	afii10019	afii10020	afii10021	afii10022	afii10024	afii10025	afii10026	afii10027	afii10028	afii10029	afii10030	afii10031	afii10032	afii10033	afii10034	afii10035	afii10036	afii10037	afii10038	afii10039	afii10040	afii10041	afii10042	afii10043	afii10044	afii10045	afii10046	afii10047	afii10048	afii10049	afii10065	afii10066	afii10067	afii10068	afii10069	afii10070	afii10072	afii10073	afii10074	afii10075	afii10076	afii10077	afii10078	afii10079	afii10080	afii10081	afii10082	afii10083	afii10084	afii10085	afii10086	afii10087	afii10088	afii10089	afii10090	afii10091	afii10092	afii10093	afii10094	afii10095	afii10096	afii10097uni0450	afii10071	afii10099	afii10100	afii10101	afii10102	afii10103	afii10104	afii10105	afii10106	afii10107	afii10108	afii10109uni045D	afii10110	afii10193	afii10147	afii10195	afii10050	afii10098WgravewgraveWacutewacute	Wdieresis	wdieresisYgraveygraveuni2010uni2011	afii00208
underscoredbl
quotereversedminutesecond	exclamdbluni203Euni2215uni207FlirapesetaEuro	afii61248	afii61289	afii61352uni2126	estimated	oneeighththreeeighthsfiveeighthsseveneighths	arrowleftarrowup
arrowright	arrowdown	arrowboth	arrowupdnarrowupdnbseuni2206
orthogonalintersectionequivalencehouse
revlogicalnot
integraltp
integralbtSF100000SF110000SF010000SF030000SF020000SF040000SF080000SF090000SF060000SF070000SF050000SF430000SF240000SF510000SF520000SF390000SF220000SF210000SF250000SF500000SF490000SF380000SF280000SF270000SF260000SF360000SF370000SF420000SF190000SF200000SF230000SF470000SF480000SF410000SF450000SF460000SF400000SF540000SF530000SF440000upblockdnblockblocklfblockrtblockltshadeshadedkshade	filledboxH22073H18543H18551
filledrecttriaguptriagrttriagdntriaglfcircleH18533	invbullet	invcircle
openbullet	smilefaceinvsmilefacesunfemalemalespadeclubheartdiamondmusicalnotemusicalnotedbluni266CuniFB01uniFB02uniF005middotcommaaccentfoursuperiorfivesuperior
sevensuperior
eightsuperior
cyrillicbrevecaroncommaaccentcommaaccentrotategrave.ucacute.uc
circumflex.uccaron.ucdieresis.uctilde.uchungarumlaut.ucbreve.uc���
LNDFLTcyrl$grek.latn8��������
`nDFLTcyrl&grek>latnJ��MKD SRB ������kern|j�*<Zl~��V\nxn����.Pj�������8~�~��"d��������� f��hz�����		,	~	�
:
�
�&`����2X^�����

,
R
h
�
�
�
�$6@RXf	$��<��V��\��_��b��i��r��x��	��7�h9��:��<�hY��Z��\����������$�h��7�h9��:��<�D\����������$�h7��9��:��<���D�D�D�h�h$�h2��D�DF�DH�DL��R�DU�hV�DX�hZ�h\�h
�h���h����$��D��H��L��R��U��X��\��������$��D��H��L�����D�h�D����$��D�hH��L��R��S��T��X��Y��L������L�h�h����
��f��m��q�9r��s��x�����������r��x��^�_�hb�hi�hy��{��|��~���������������������������j�����f��m��q�9r��s��x�����������������^�_�=b�=i�=����^��f��m��q�9r��x��_��b��i��r��x��f��m��s��y��~����������������������������������f��m��q�9r��x��V��_��b��i��r��x������_�hb�hi�h���"�F�F�F�h�h^�_�hb�hf��i�hm��s��v��y�Fz�F}�h~�F��������F��h�����h�����F��F��h��F�����V�^��h��F��h�F�h�F����^�_�hb�hi�hy��{��|��~������������������������������j���_��r��x��������	y��~�����������������������^�_�hb�hi�hy��{��~���������������������������j������������������y��z��{��~�������������������������?�������������������q���������y��~��������������������������������y��}��~�����������������������������������������
y��~��������������������������������������
y��~��������������������������������y��~������������������������������3�3l��{����J���������������3��J����J���������������`�����������������������w������������w�������������������������������w������������3�������3�3����l��{����`�����������������������������������������������-������������������������������������������������������������������������
��������������w�������������������{����`��`��������w�������������������������������������������������������w����� �J�J��������������������������������������������������������� �`�`�������������������������������������������������������������
�����w��������`��J������������������������D�-�-�-�����������������������������������3����������������-������-���	��������������������w��������������w��������������������������������������������	�����������������������������������������������
����������������������������������������������������������	������������������������������������������������������w�w�-�������������������������������-�J����������������������l��{������V��������������j$)/3579:<IUYZ\V[\]_abfhimopqrstuvxz������������������������������������������������������������������������ϒNPK
!<��$� � Bchrome/pdfjs/content/web/standard_fonts/LiberationSans-Regular.ttf0FFTMh��� �GDEF'��&GPOSjw��$�GSUB�<�K�POS/2����`cmap��w��cvt C�C��fpgms�#��gasp	�glyf�Q�$��4head�$<6hheaK�t$hmtxa

�kern�Q���0Tlocay�Tmaxp�� name����jpost3�۷���prepz�^���둙�a_<����ϒM�`��gH>�NC��`��g��RT�/Z�����3�3�f��Px�1ASC@!��Q3>�`���:� �D�99��Ws	sIVH�h��!�d9��[9�9sPs�sgsNs/sRshsisYs`9�9��e�d�esT�VV��h��V���9g��9� V�s�����9aV�9a��V]�.��V	�	V.V-�A9�99�
s���jsWs�WsVsW9sVs����������s�sVs�sV��99s���1�"��"�\��s�s:sqs���ss�-��sS�d�k��3zdA�)��H��LP9��w�P�sS�8�8�I��VVVVVV�hV�V�V�V�9	9�9��9���9a9a9a9a9a��9G��������V-V���sWsWsWsWsWsWBWsWsWsWsW9
9�9��9sVs�sVsVsVsVsVdA�,s�s�s�s�s�VsWVsWVsW�hW�hW�hW�hW���V�sVV�sWV�sWV�sWV�sWV�sW9gsV9gsV9gsV9gsV��s��s
9��9��999��9��9\�9�9����� ��V���s��[s��~s�U�s���s���s���s���s�����s�9asV9asV9asVa�V�����������8V]9V]9V]9V]9�.9�.�.9��s���s���s���s���s���s��	��V-V-�A1�A1�A1��s�VsWB9G�,V]9�.9���3������3�P�������W9�F�������2���A����VV�h�X=V��A��9a9�V�X
����3Z9a��V��l�.V-buV.���W9V-�V�Fsj��`��V��tV�F�Vsjsj������VsV�O���V�V)`�0U3��?S��`�sV`�?SV�W��.U��hV]9�9 u��.����7��V@�V�U�kV�c�C������@����9a��V��h�.7vV.��U�U���U.�@��i��`sW�x@����sWZ�1x�x������k�sVU�s�W�#�V��+zk���-��+�7�UsWsWs
��W9��9����@��s
��x�k�9asV��J��	���	���	��V-�[�[sk������~�K�K�Ks�s��Q7�U�U�X�Y����V�`�ess:��sE����%l�X�P�=�]������8������ed3�W���1��d8dA�dd?dA���d�"�����������������������������������������������������������������������������������������������������������������g���{��m�m��������������)�)�s+�k�UF�Q@;@<�fB��w�����5�+�-����|��ZjZH���-���/ H������>~�����~����_s���    " & 0 3 : < > D  � � �!!!!"!&!.!^!�!�"""""""")"+"H"a"e###!%%%%%%%%$%,%4%<%l%�%�%�%�%�%�%�%�%�%�%�%�%�%�%�&<&@&B&`&c&f&l����� ������~����r���      & 0 2 9 < > D  � � �!!!!"!&!.![!�!�"""""""")"+"H"`"d### %%%%%%%%$%,%4%<%P%�%�%�%�%�%�%�%�%�%�%�%�%�%�%�&:&@&B&`&c&e&j����������G�/�����v�����������n�����������������������}�y�!����������5�2�*�)����������D�7�(�J�I�@�=�:�7�4�-�&�����������������������ܾܹܶܮܢ�O�L�K�.�,�+�(���� bcdefghijklmnopqrstuvwxyz{|}~��������������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,-./0123456789:;<=>?w<
	

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a��������������������������������pcdhv�nj)tiB��=qDEfu7:9�@kzv��bm<@A8l{���
���8 %��w����������������KRoNOPxSQL@EYXUTSRQPONMLKJIHGFEDCBA@?>=<;:9876510/.-,('&%$#"!

	,E#F` �&`�&#HH-,E#F#a �&a�&#HH-,E#F`� a �F`�&#HH-,E#F#a� ` �&a� a�&#HH-,E#F`�@a �f`�&#HH-,E#F#a�@` �&a�@a�&#HH-, <<-, E# ��D# �ZQX# ��D#Y ��QX# �MD#Y �&QX# �
D#Y!!-,  EhD �` E�Fvh�E`D-,�
C#Ce
-,�
C#C-,�(#p�(>�(#p�(E:�
-, E�%Ead�PQXED!!Y-,I�#D-, E�C`D-,�C�Ce
-, i�@a�� �,����b`+d#da\X�aY-,�E����+�)#D�)z�-,Ee�,#DE�+#D-,KRXED!!Y-,KQXED!!Y-,�%# ���`#��-,�%# ���a#��-,�%���-,F#F`��F# F�`�a���b# #���pE` �PX�a�����F�Y�`h:-, E�%FRK�Q[X�%F ha�%�%?#!8!Y-, E�%FPX�%F ha�%�%?#!8!Y-,�C�C-,!!d#d��@b-,!��QXd#d�� b�@/+Y�`-,!��QXd#d��Ub��/+Y�`-,d#d��@b`#!-,KSX��%Id#Ei�@�a��b� aj�#D#��!#� 9/Y-,KSX �%Idi �&�%Id#a��b� aj�#D�&����#D���#D����& 9# 9//Y-,E#E`#E`#E`#vh��b -,�H+-, E�TX�@D E�@aD!!Y-,E�0/E#Ea`�`iD-,KQX�/#p�#B!!Y-,KQX �%EiSXD!!Y!!Y-,E�C�`c�`iD-,�/ED-,E# E�`D-,E#E`D-,K#QX�3��4 �34YDD-,�CX�&E�Xdf�`d� `f X!�@Y�aY#XeY�)#D#�)�!!!!!Y-,�CTXKS#KQZX8!!Y!!!!Y-,�CX�%Ed� `f X!�@Y�a#XeY�)#D�%�% XY�%�% F�%#B<�%�%�%�% F�%�`#B< XY�%�%�)�) EeD�%�%�)�%�% XY�%�%CH�%�%�%�%�`CH!Y!!!!!!!-,�%  F�%#B�%�%EH!!!!-,�% �%�%CH!!!-,E# E �P X#e#Y#h �@PX!�@Y#XeY�`D-,KS#KQZX E�`D!!Y-,KTX E�`D!!Y-,KS#KQZX8!!Y-,�!KTX8!!Y-,�CTX�F+!!!!Y-,�CTX�G+!!!Y-,�CTX�H+!!!!Y-,�CTX�I+!!!Y-, �#KS�KQZX#8!!Y-,�%I�SX �@8!Y-,F#F`#Fa#  F�a���b��@@�pE`h:-, �#Id�#SX<!Y-,KRX}zY-,�KKTB-,�B�#�Q�@�SZX� �TX�C`BY�$�QX� @�TX�C`B�$�TX� C`BKKRX�C`BY�@��TX�C`BY�@�c��TX�C`BY�@c��TX�C`BY�@c��TX�@C`BYYYYY-,Eh#KQX# E d�@PX|Yh�`YD-,��%�%�#>�#>��
#eB�#B�#?�#?��#eB�#B�-,z�E#�-�	@��@������
O� ��P(�F(�F*�F+_���O�_�������F�@�F��F������@�36F�F�=�U�=U�U������@�F����<�P&��(��P�p������@��2F�?�O�o������p������������?����������а/�?�������Э/�?�����ЪO���/�o�������$P�o����F�������0�@���p�������ЏO�_�o��F�����1ts?sP&on<nF5U3U3U�`P&_P&\F1[ZHZF12UU2Uo?�Q�Q@Q58F@Q%(F�@TPIF HF5GF5�F�F�F�F2UU2UU?_/Oo���?�o����TS++K��RK�P[���%S���@QZ���UZ[X��Y���BK�2SX�`YK�dSX�@YK��SX��BYtstu+++++stu+++t++ssu+++++++++++++++++s+tstusts++tus+stsststtsts^sstsss+ss+++s+tu++++++t++^s++^st++++ss^ssssss^��}�y�:w�������W�����~�����������j����`jy����"3�k������k��k�{��Rn������i�`�[^�^eo���z����
������iq������Hj����g�a��AD�,,,,`��T��2r���P� �	D	�
�
��P~�

J
�d�@��(�`�,X�@��^(&�*�� @ � � �!"�"�"�#�$6$�%6%�& &�'X'�(f))D*�+d+�,p--b..r.�042V3�5|66�7~7�8p9�::�;T;�<�>>�?v@8@�@�A�A�BB^B�CnC�DDZDzD�E E�E�F FJFlF�GG(G@GXGrG�G�HH*HBHZHtH�H�H�H�I@IXIpI�I�I�I�J4J�KKK4KNKfK�L�L�L�L�L�MMNN*NBNZNrN�N�N�N�N�O�O�O�O�O�PP.PnQ
Q"Q:QRQlQ�RR"R:RRRjR�R�R�R�R�R�S
S"S6SNSfS~S�S�T:TRTjT�T�T�T�T�T�UUU6UNUfU~U�U�U�U�U�VV~WWW0WHW^WtW�W�W�W�X�X�X�X�Y�Y�Y�ZlZ�Z�Z�Z�Z�[[[.[�[�[�\\\(\@\X\d]]�]�]�]�]�^^(^�_�_�_�_�_�_�``*`B`Z`r`�`�`�`�`�`�`�aa`a�a�bbb2bJbbb|b�b�b�b�ccc0cHc`czc�c�c�c�c�ddfd�e�e�ff.fFf^fjfvf�f�f�f�g0gxg�g�hDi�i�jj^j�k�k�llNlpo^o�p�p�p�p�qrqzq�q�r,r4r<r�r�r�sssXs`s�s�s�t�t�u.u�vwdw|w�w�w�w�x�yvzz�{\|||}}�~*r����������P�ք>���,��^��(�B�Z�r������p���8�@�H�`�h��������4�|�������������^���؏N�ԏ܏��"�*�2�:�‘R�Z����T������┌�&�Еؖ|��8�������V�ؙ�~������h�p�x�����������V����n�Ҡ<�ΡL���ȡ⢂���,�4���ģ̤�������ĥܦ2�~�����6�N�f�~�����ʧ���<�^�������� �P����d�����@����<�N���®ڮ��|�а����Ƴ��F����t���F�x���з��,�Z����:�
���ڻL�j�ܼv���Ⱦ��h����L�z����� �:�\�~�������4�ZŒ²���
�:�tâ����:�jĤ����<�pŦ����VƜ����LǂǶ��FȐ�����4�L�d�|�X��&�F�bϊϘϦϴ������PЂд��H��x�(Ӣ�:Ԛ� �tծ��f֦��n�����n���"����<�jڒڼ���(�Lۚ���DdU.�/<��2��<��2�/<��2�	�<��23!%!!D �$��hU��D����@�[���9IY@H&	�	�	v	�		)		�I	&	�	v	)		y	V			��	�	T	d	t	�	b	r	D	T	"	2			�	�	�	�	�	�	�	r	�	T	d	B		$	4		n	���@fmHI		@^bH-	=		�@�UXH	�QTH}	�	�	_	o		�GKH	�CFH	�?BH}	[	k	=	M		)		7�	�	�	�	�	�		@-0H;	K	[		-	�	�	�	�	�	�		+	;	k	{			�	�	�	�	@	`			p_�?/��]]^]]]_qqqq_rr+rrr^]]]]]+++qq++r+r+^]]]]]]]]]qqqqqqrrr^]]]qqqqrr^]]]]q/^]+]�q3/�10#353g��������W���h@!0� `p�`pP`������%(H���@ H0�o�/? ?3�]2/qr�]q�++]qr�]q10#3#3j���y�����E�	iy�@�ZZDD6D&6D&6	
	



�
O��?O

	
/3?399//]q]q3�23�2/33�����3�����/3�����3�����10]]]]]]]]]]!!#!##53#5!3!33!!�N��XnV��TnT��N�YnXkXnX�@PjNu��l�h��h�lql��h��hl��q�rR�0;F.@\��
�D�'�9�9�5�5�%v��F?V?�?4&d&t&%"7/	A 0001o)o1 11 101P1p11����#H1���@uH1o@H<o!Bs�6�6�6x
�
�"�"�"�"�"�A�AwA<A"
6)1}�@H/?_7	s/��p�Ue./22]]]��2/]�+33]]9]]]]]]]]_]]�2/��+�/++_^]q���9/r3�210]]]]]]]]]]]]%.'7'.54>753.'#4.'>�"�.NqNM�tG@s�_|d�fB�zu;rgX@$8r�w|�/Qj:AlM*�\(F_7Hd=	��%5W@'�2S�bT|S,��-PsK!^i�C)8OmHM�d>�?P3 �,8Q�6J2!�!4DI����+?S�@v�y�*���@H%H H���H���H
���@HHH���@T'6�
��,, ,,@,,, ,P,`,p,�,�,,@��J�'�'�''@

H'O�"�E�1��;�?���???���/+qr���/]qr���99//883310++++++++]]#".54>32#3%2#".54>4.#"32>4.#"32>�3WtBBsU10VuDBsU2�;�����@rV12UtBCtU11Vv�+?(*@,+?)'?,��*>(+A,+@*&>,�}�i--h�~��g))g����)f��~�k..j���f)�%c�N !N�b_�O""O�|b�N !N�a_�O""O�H��6�9IY@I��${*�*bRrR�RfGN[NI+Y+i+60�0%(,#|#�#,<\


*

*���@8	
H
*0)&E6BI!I:-,�JJM:,P,:,,:
6�66���@=	H6PH?
O

��MbErE6EFEJ0)M,&E	?Q��?UQ3P?�?�/]]q�9]]]]/]�/+]q9///]]99]���910]+]]]]]]]]]]]]]]"&'#".54>7.54>32>732674.#">.'32>�`�:L]m@u�n53[~K(U�]I{Z2Bp�S>�U=Q�#kF5j1 ;K��0E*`d%AtV2JY�Bq{#HmI,OD7B=0&<i�SO�iS!"MNN#BsU1&HjDKu\K!r�aZ�y+��g6,	�y%<+g[;�87AO��i�z0�i7_G)&h� �!@0�  ?�]/]�]q10#3
�����X��B@-�
�	XXHH�� ??/]��]8210++]]]]4>73#.5*Z�a�^�X++X�^�a�Z*����ii������ii���X+�H@
�
�	WW���H���H���@��??/]��]8210++]]]]#>54.'3+*Z�a�^�X++X�^�a�Z*����ii������ii��!���k@KM]mK[kBRCS
_ 0/
?

	�
�

 

��?�]]]/]]�3�]��]3�qr910]]]]%'7%73�-��w��w���-�Zg�I�H�H�I�k)d�G�G@.����	� �8��	�7��?3]]3�22]]/]3�210]]]]#!5!3!���X���`�T����T������N���H
�����@	H��@P������@&)H@
H��/��/++]qr3+��10+%#>5#5�	{-1Xۨ5WKB A�A�[�Op!@@p���//]q�//]105![�Р��~�.@ ��@P�����@
H�/�/+]qr�10353������9�3@y�
H) ����??/8�]810]+]3���i� P��#�'p@PY%i%F!V!f!VfYiv�y
�
y�v�n@��)�n?

s#s?�?�/]��]�10^]]]]]]]]]]]]#".54>324.#"32>#M��fg��KK��je��L�(NqHLtO()OrIGrO+���JJ����CC���ը߅78�ߧ�އ;;����
^@  	�			n�/����@HDTdHt?�2?33+/3]+/^]3/]/]�2/]1035!5%3!�g��M�W�<���g�(�@Muu�z�e%V%)!Y!i!i##u�'n@@&*H@�*�nt&�&& ���@&H&�\l|

s&t?�9?�3]]]9/+]33]/��]q+�210]]]]]]]]]]]35>54.#"'>32!g3����O$D_:6_J/�	Bt�ki�q<3Upz|mV�u��||�V<[><Y;L�e:2b�^G�tlgefi9�N���;�@�z�u�u:�:u3�3u/�/u
�
z%�%[k)v.�..I'&n61_o''
 n11n@/��=�n�
?

6t�&\&l&|&
&&&#s,s�Scs?3]]]]�?�3]]]9/�9/]q��]q�2/�99//]9�q10]]]]]]]]]]]]#".'732654.+532>54&#"'>32?y�s��t:	�+JlJ��Egy3fb3n[;��w��P{�Yv�l3"HoNU~R)�a�i7Ak�I8\B$��N_5�7^Iq�zo]�[-;e�M>lV>	;Xp/7�
u@P�����vv��@H[
k
{

op�0P�s??39/3�2/^]qr33�22]/+3]q10]]]]]#!533!q��h�������?��?���L�w$%��R���,�@V
f
�
UeZjU+e+U*e*&���@YH
$�$�$�$D!# H#n@/��.�$%n!  n�
?

s(($t!ss�g?3]]]�?�9/�/]q�3/3�22�]qr�9+10_q_qr]]+]]]]]#".'732>54.#"#!!>32@~�{o�rC�(EeHFrQ,*NqH-LA5�/!��0�ci�v@�j�F4[zF(K;#+TzOAmO,%���A%5@u�h���$8�@0�z�YiZjzTdT#d#t#T"d"t"5E�22���@-

H�%5Euon@/%�%�%%:�/n
 

���@&H
*u  4s�4s?�?3]�9/�2/+]�2�]q�2/�10]]]+]]]]]]]]]#".54>32.#">324.#"32>;s�o{�z=E��vH~gN�{QJxT-1�s`�o=�$HjF1dQ3(KjBAgH&�j�G^����`CnP[QF�Ҍ[_>u�pIvS-AjLN�d:-Uzi�D@-z�inP _ @`�t
??�2/]q3/]q9/�10]]
#47!5!j��G�P��e��������E9.��Y���)=Q�@�u(�(u!�!u�u�u�z�z�zz�z�z�u�UEeEUKeKZAjA4n*n$O
n@>>>/>�>�>>S�Hn�

$Cu99M/uMu?�?�9/�99/q��]qr�99//q99��10]]]]]]]]]]]]]]]#".54>75.54>324.#"32>4.#"32>9u�||�w9/Oe6;]?!9p�ms�o6!?]==hL,�>dIGb?:fPUg7#DsVOoE  FrQRpD�Z�n>>m�YMxW5	>Wj;J�c9:c�J:jW=
5WxL5X?##?X5*XH..HX��3_I--Ja4AkM**Mm`���$8�@i�'�����t#�#t � � z��z��{��Z(j(Yi

H6%n/?O� 0@�:o

/n ���@ &H4s_o*s"s?3]�?�9/]�2/+]�3/��^]qr�310]+]]]]]]]]]]]]#".'732>7#".54>324.#"32>G��vQ�fH�w[IyU0I]l7`�l;?x�o���%IkFAhH'#FhE2gS5ݼ��^!FpOE�Ќ/J3E|�km�{B���N�f;.UzKGzY3"Fk�~:6@$��@P����@
H��?�/�/+]qr3�2105353����k���������:Y���@
H
��
���@	H��@P������@&)H@
H
���/��?�/++]qr3+3��310+%#>5#553�	{-1X�Ϝ5WKB A�A����e�H�j���@H(H(H����@3H� Pp ?�0p�?o��/]33�]�]/]/]310]+]+++5	e�Z;��������dXG�F@1@` p�� �/_o��P�/]]��]�/]]3/]q3105!5!d��X�����e�H�j���@H(H(H����@3H� Pp 0p�?�?o��/]�]�]33/]3/]10]+]+++75	5eZ��㚙on��^�T'�%)�@Du$�$u#�#ZjZZz�Z
z
�
:J	H

�)�))�&&FF @�����@&,H!_
�

'�&L_!?�3]/��]9/+]�/�9/�q3/�10]]]]]]]#>54.#"'>3253'%>ORO?'�'>NPM<%*MmC���Cy�zr�{@���GlUC<:DS7EhP?99FX;;\? �zT�pA8g��������n�]re@�z�u�u�y/�/t&�&f&cFVEfE{�IJ8Z8j8;fR?6?F?R@&@6@F@�%5E%5E�9 H\f\�\P:`:::5:E:�9]9
99	}4�44K4
*h�%)�
p"�HH
1PP

R�@_1o1�1�11t�=�RR RR,�k�c�$/?O 0@M6�Y�GGB�M/�3]?�99//]]3��3/�/]��]q�99//]r9]22�2�10]]]]]]]]]]+]]]]]]]]]]]]]]]]]]]]]]#".5467##".54>3237332>54.#"32>7#"$&54>$324.#"32>7>nCv�a8O2E]uGT}Q(G��r<`I6'�t+&>kO-S�㸋^0V��i��x,72���o����i?v�����$�\��"?Y8V�W-_cEx`F	��`/@&+-YE+:g�Sxݩf0C(��Tx10.Q��p�ޢ\@t���s��`!09p?4!s���ܴ�Fv���2T<!U��Wx�>f�G$PR�@�fv�fv�iy�iy�s�e|�jz�9Yiu�6VfZHUG%5��*:	��

*:	�� 0 %5����P�0`���/_v

?2?3]9/3�2]]q/]3]qq3/]q3]qq9=/33]qq]qq999910]]]]]]]]]]]]]]]]!!#3	.'!���~��?�6�����d��(RC-.DR(�1���!.�@l��� � �-u-�-z$�$�$%&��k{�
Z@
H)Z@"/"�""0�)Z@(_)_)_?�?�9/q�9/^]�2�]q�9/+�9]]]10]]]]]]]]#!!24&#!!2>4.#!!2>�T��h��u��C!CeCU�X.���ATtH Q1\�P��sI{Y2�k�_,�'T�Z;hU=
:ZwBrb�B!=V��C^<�<dh��y�'�@Oy�u
�
{%�%j&|$�$j$jUZ*j�*j[P`�� "���@*
H) )O###_0@p���
_?�3/]q?�3/]]3/+3/]]]q�10]]]]]]]]]]]]]]"32>7#"$&546$32.x�}@E��uR�mV!�&p��v����V[���.G�Df��P��ӘT+NkANO�d9m���
�e��<2[F*�e�d@F�{�+;{�+;{{�yZ@/

�@Z@__?�?�/^]�]�q�10]]]]]]]]]#!!24.#!!2>ej�����ң�o�R��{��:o��Nϰ��]�Q�����˂=��H�����M�
���@%H


	Z@ 
_	_	_?�?�9/q�]/^]�23/+99//103!!!!!�-��2������<������	i����
H���@:HZ@0_�?o����@H_??�9/+^]q�]/^]�23/+9/+10!!#!g��������g����-�@��+j+B%R%UVVUz�YijjI%Y%5{
�
@


$\@!!�/� /`/�/[�� !_�"""_)_0@��?3/]q�?�9/]�/]]]�]�]9/�2/]10]]]]]]]]]]]]]]46$32.#"32>75!5!#"$&gY���Đd#�Ih�X��|=B��S�qV�[U/��k����Yǥ
�e.V{M64U<!P��әU-7���0WB&m�� �g@Z@����
�@
@
�
�
�

���@%HZ@_P��	?2?39/^]]q�/^]�2+]q�]2�10!!#3!3a�������s���T��|�z@FZ= �`p@P����@P���� ������8=H����-0H����
H??+++]qr]/^]qr^]^]�1033���� ��h�|@��{
dt���@BH ����Z@@P`o�  @P`_	_@?2/]�?�]q/q�]q�9/]q10+]]]]"&'73265!5!ɫ�#�
.@N)hx���8k���A]<��E��#e�s>�?��@g�������f��d��k{�YV
�
$

j�
�	�			
Z@
?3?39/^]�223/]839/]93q310]]]]]]]]]]]]]!#33	R�͸�����������>�����/�8@(0 @`���Z@_?�?/^]�/]q1033!��������,,@�)�H���
H
���@H(


H* !%H* H* 	H���!%H���H���@�	H
$$,\*$4���.�.t.;.�.�..ϫ.4. ...�.�.�.�.t.�.�.`.T.@.4...��.�.�.�.�.t.�.P.D.0..$.�.�.�.�.�.�.p.4.D.d. ..�.�.t.�.�.�.`.4.T...gt.�.�.�.�.P..$.D..4.D.d.�.�.�.�.�.�.�.�..4.T.t.7@S�.�.$.D.t.�.�..�.�.�.d.�.K..4.�.�.�.�.�.@.P.p.?.. .\@���@%H*K$
$?3]?33+3/^]�2]]]_]]]qqqqqrrrr^]]]qrrr^]]]]]]]qqqqqqqqqrrrrrrrr^]]]]]]]]]]]qqqqq^]]]qqq�^]]]q2�9=/3310+++++++]+++]]!46767#.'&'#3>73V
�����
��wp��3j,3032+a'�@�(-/5987/g'�T��/?B;<B>�� �Ĺ
��@'+H6
F
 +H)9I!H H��)���!H���@5H��&\DT��0@p��@@������@H
\@���@+H	@+H?33+?33+/^]�2+]q/]_]q3�10]_]++]]++]+]+!#3&'.53:������10)[#�X��H11*c-��a����'l@J[%%	%R!!!T[fh[@)� )�)[�
�
 



_#_?�?�/]]]�]�q�10]]]]]]]]]]]]#"$&546$324.#"32>�_��������X\����\�A�{~�?A�{��{;ǥ��hm���
�ef����ДPP��әUV�����u@S����
*%Z@�@@Z@_/O_��@	H_	??�9/+^]q�/]�2]q��10]]]]]#!#!24&#!!26�=y�y�b�Q}�|>���������\�uD���=o�a���Ԓa�}��$8�@ql|�hhe"WU1Z'Z-l|�Z(Z(,U,6Z662U22

[@%%:�/[��  :�:*_ 4_
_/�?3�?�]/]]]��q�992/10]]]]]]]]]]]]]]]]3267#".'.546$324.#"32>�M�ӆ5DS3@&[1V�aF��P\����\�A�{~�?A�{��{;Ǖ��u@Z9�	
3_�Ws���
�ef����ДPP��әUV���h��@>�
����u���|�JZj�r��c@3%��p@WIp��TdB#3Z0@`��@ � � Z@_/_o�_?2?�9/]�2/^]�2]/]39/�9310]_]]]+_]]]]_]]_]]]]]]]]!!#!24.#!!2>����I��x�~B'T�[��,TxL�;�RxM%I���7h�^C�lN���@^?��)Hb]����?�@n�>D>�;�6�1�(�!!!+!�!Y�����:`6iv*Z))Z@�A�	Z@H4Z@Hx��;4���@/Hw4�4�4:44/_o*Y*K***$_`	R	D		?3]]]�?3]]]]�99]]+]]+/^]�3/+��]�2/�10]]]]]]]]]]]]]]]]]# $'732>54.'.54>32.#"�E�ۖ����(�:c�fU�f9?r�`;wm`F(Q��r���M�5V{Sb�Q#?l�PA�vgL+�Y�m=��%7ZA$<_BEV8&
+:QkFd�\*)RyP!3P6#<Q/?Q6$+:Tr.���@�		�	�	�	�	I	Y	y	�	;				�	�	�	�				I	i	��	�	�	�	)	Y	i	�	�				)	Y	y	�	�	�	�	�	�	�	�	9	i	-		�k	{	�	�	�	�	�	T		+	;	�	�	�	;	K	{	$	�	�	�	�	�		O	_	0		g�	�	�		O	_	�	�	�	�	�	�	p	_	@			?	_	o	�	�	�		7�	�@]	�	�	o	P	/		�	�	�	o			 	@	P	�	�	�	@	`	�	�	?	 		Z@p�� ����@H _??�2/]�+]q+M��2_^]]]]]]]qqqqqrrrrrr^]]qqqqqqrrr^]]]_]]qqqqrrr^]_]]]]]]qrrrrrr^]]]]qqqqqr10#!5!о���圜���)��@=YiYiYiEE
Z@@P�0������@@������@$HZO_o���@
_?�?3/^]]qr�+]q�]qr�10]]]]]".5332>53�tН\�9f�SR�n?�]��>�Ɋ���k�^+,`�od���ψB	M��@�JZjEUe�:Zjz�5Ueut�	�{*:	��%5��		*:��4T 0`���%5���� P0`���/y		?3]?3]]q/]3]qq/]q_qq3]q_q9=/33]qq]qq10]]]]]]]]]]]!#367>73����
���� -Y#)'%)#X0�	��.W@Iy,u{�It�Fz�IYiu�GWg�-[-k-{-�dtU 
H
���@�
H�uDTd6�zK[k	H*:	��
%
5

�
�

*:	��%5��*:	��%5��'{'t�''-*-:--�-�--�.�.�.�.�.�.{.o.[.O.O.�.�..@H .0..	.�����8x���@@�&Hw0�0�060F0V00'0070g0w0�0�0�0�0�0&000��0�0�0x0�0�0�0i0(080X000�0�0�0�0�0�0X0h0I07000�0�0�000(0H0�0��0X0h0�0�0�0I0(0	00�0�0�0�0�0�0�00�|H90*00
0�0�0�0�0�0�0�0�0x0i0:0J0Z0)000h�0�0�0�0@��0�0�0�0|0k0\0K0<0+000�0�0�0�0�0�0�0�00m0_0M0/0?000�0�0�0�0�0�0�0�0m0}0[0M0;0-00
08�0�0�0�0�0�0�0�0{0m0K0[090+000�0�0�0�0�0�0�0�0}0k0]0K0=0+00_00�0�0�0�00DT-'@{� 	H?33+]3?3]^]]_qq_qqqqqqqqqqqqqrrrrrrrrrrrrrrr^]]]]]]]]]]]]]]]qqqqqqqq_qqqqqqqrrrrrrrrrrrrrrrr^]]]]]]]]]]]]qqqq+qqqqrrrrr^]]]]qqqqqqqqqrrrrrr^]]]qqqq/+^]3_]]q/^]]+]_qqqqqqqqqq3]q_q9=///]]33]qq]qq33]qq]qq33]qq]qq10+]]]]]]]++]]]]]]]]]]]]]]]!#.'&'#367>732>73���

��a�
�����&h/79:70f&�����?|1:4EC><7m��7;>CFEhy4.+�w@�\I;&K[)9DT&6	)Q
E
3


]L+;	RC&6�
�
�
�
�
`
T
0
$

�
�
�
�
�
p
d
@
4


��
�
�
�
�
t
P
D
 

$
T
�
�
�

4
d
�
�
�
�4
d
�
�
�


K
{
�
�
�
�
�


4
D
jT
d
�
�@�
�
�
�
�
;
$

�
�
�
�
�
{
d
0
$

�
�
�
�
�
p
d
@
4


9�
�
�
�
�

D
t
$
T
�
�
�
T
d
�
�
@

0

			�����@H������H���@) �����r4DT
?2?39]]/]]q83/+]]]]+]8399//923]_]]qrrrrrr^]]]]]]]]]]]qqqqqqqqqqrrrr^]]qrr^]qrrrrrrrrrr^]]]]]]]]]]]qqqqqqqqqq10^]]_]]]]]]]]]]]]]]]]]]]!	#	3	3	X�Y�P����}�h������)�b�-)�S@H���@�Hi�6FZ&V�v�9I�
�
�
V
	
9

Y
�
�

�
�
	

�
�
�
Y
y
�
6
)
9
�
�

��
�
�
�
�
�
�
t
V
f
B
$
4


�
�
�
�
�
�
�
�
v
b
T
6
F
$


�
�
�
�
�
�
�
p
d
@

$
4

i�
�
�
@��
�
�
�
�
t
P
`
D
 


�
�
�
�
�
T
d
t
�
�
0
@
$

�
�
�


$
D
T
t
�
9�
�
�
�
p

$
4
T
d
�
�
�
�
�


4
T
�
�
�
�
�
p
0
`

/
;K{??39]3]]_]]]]qqqqqrrrr^]]]qqqqqqqqrrrrrrrrr^]]]]]]]]]_]qqqqqqqqqqqqqrrrrrrrrrrrr^]]qqqrrr^]]qqq/^]]]q�9�^]]2+M�210+_^]]+]]#3	3	�������H��H9�a�A��	z@#dt�m}�[)9Ir�Td���@	

H	���@H@H���@
H__?�2?�22+/+]33/+]3310+]]]]]])5!5!!���Z�����V������W)�1@0��?�� ��?�?�/]]q��]q210!#3����Wu������9�G@(x�
H	IF
* ����??/]8�]810]]]+]3��i��� �W��1@?��@P�����?�?�/]q��]q21053#5!����W�s���
����H���H���@/Hv�Hy�6Ffv��&FVf����0AH���@9H9Iiy�	)Yi	@H�9&6F������<?H����@�!H8H'��FVf��	��y����	)����6v����fv	9��9Iy+���������Rbr@2$�������t�f2BR$�������dtV4D h�����@�t�Rb4D&���@UXH�����fvD����HKH����@BEH�r����@x;>H"8����d���@P$4����t��K4��������`p@P"/2/?3]]^]_]]]]]]qqqqqqrrrrrr^]_]+]]+]+qqqq+rrrrrrr^]]]]]]]]]qqqqqqqqqrrrrrrrrr^]]]]]qqqrr^]]]]qqqqr/^]]qqq+q+qr^]�+^]qr33�++]qr10_]+]+++	#3��΢p�r�y��� ��i���#@`���`���/�//]q105!��i��j��/@u�@�@��/?�/]�/]�]10]	53��������W��sN2A�@2y=�=y�(	
H
+	H%%F@.	o88�88C�G���@H?G0C�C�CC���@ #H!Q(9Q		3_/�P.3P?�2?�3/]q9/�?�+]q/]�3/+��]22�2/10]+]+]]"&54>?54.#"'>323267#".'#'2>=���Q��W�:W;4T>&�
8g�n��*;"C&3I.E\u#V�U*�BwZ5_��k�N;C^:'C3@kN+���.PQp7Q64T; �?bt5Y2ZIX`����3�@	i1y1y#���@
H��IYIY�����@?
HG@�  5�*F0��5?5p5�55�5�5�5%P/P?�2???�2]]qqrr/^]�22�]�10+]]]]+]]!"&'##>533>324.#"32>�r{�3��2�z��>`EGmI&&IlFB`@"��Yc80"	+<H'�Y7hZ���p�g0.f�xt�c+.f�W���N'u@Qyyc%c FF�			)G#P  � �    p���`p���P)]?�3/]q?3/]�/]�3}/q�3/�10]]]]3267#".54>32.#"@iM`��	<g�a�o2$AXgr:[�g@
�riMg@"]�q>hlC|^9V��xm�}U32WvDZj3g�V����3{@WU"e"Z2j29I6
F
	y�v�F@*��5� Gp5�55�5�5�5/P%P?�2?�2??]]qq/]��]22�10]]]]]]%#"!234.453#.532>54.#"52�z��{�2����>`EGmI&&JkFB`@�hZ6Zb
+/*	��'H<+	
%05pp�g0.g�xs�b+.f�W��N%k@.Z#j#ZjUeI		G@�'�%G0'�'�''���@#HP%% PP?�?�9/�9/+q/]�2�]q�2/�10]]]3267#"4>32'.#"#IrPu��=f�l�L��d��o/���-cT:�U�g9^H--[I/�ӄ;X��z���Jb<��@

3
C

���@jHF/O_��?����;_���@VdH@',H 0`���@/PP??3�2?�]]]q++qr^]q/^]3�22/^]310+^]##5354>32.#"3i���;fQ E-(3���I��z;eK+�)<'a�V�W�K1E�@Hz1�1v�e>UeZ:j:&6&)!	y�v�	/F@2"0�G�F���@QH<G
 G@G�GG G�G�GP�G�GOG�G�GG/G�G�GG)"7PAP	P5&?2]]�?�2?�2?^]]qqrrr^]]q/^]�3/+��]q22�10^]]]]]]]]]]".'732>=##".54>3234>734.#"32>$]�f@�{d=dF&;UpHg�]++a�os�.��,1Pg6EcA@bD6gR2�W&Gb<KQ"KxV�)K:#E�͇�БMia>7(	+<I'�����q�f00g�pu�b*.d����!m���@$HF@P��#�#�#�#�#�##�##���@ HF�0��P?�2?3?/^]q�2+]qqr�]q�10+>32#4.#"#3=FTd>h�M�0XF@gI(���7M28e�T�/�EhE#.TxK���~!B8'�=�s@
F0���@*H	�	�	�	�	�	�	p	�			�	�		���@"%HO		S?�??]q+qqrrrrrrrr9/+^]3�210533���� ����:����W=�3@�H(HF @�?�@��H���@P_o�����O�?�������?O_ ���o��?�o�������@TORH����/?O=@58Hp����@#&H�O�p����P	S?�?�?]]]qq+qrr+^]]]]+qrrr^]]qqqqr^]]]qrrr+^]]]9/^]33/�210++53#"&'532>53��6]H"A
$
&1
� ���Z>jN-�+C.����@T|zvV	f	�	�	��Yiyt
�
�
D


*	t			Tt�t�����0���@i
HF0�
?

?
_
�


?
_

9
@SVH`
�
�
�
�
�

`
�
�


0
@
�
�
�
�
�
	
????9^]qqr+^]qr/^]�2/+_]]q833/]83_r9310]]]]]]]]!#33	0�������I��m��a
�/���>�o@4F��0�������p������@"%HO�p����??]]]q+qqrrrrrr/^]qr�1033���4�#N;¹*��H ���@�H";F
.F�/�/�/)/Y/�//�/�/�/�/�/v/Y///F//F�


6
�
�

�=�=�=�=�=�=�=i=y=[=I=+=;=�=�=�=�=�=�=�=i=[=)=9==	=��=�=i=�=�=�=�=[=M=)=9==�=�=�=�=�=�=�=}=+=K=[=k===�=�=�=�=�==+=K=[=k===��=�=@��=�=�=�=�==[=k=O=;=$==�=�=�=�=�==�=d=K=?=+===�=�={=�=�=�=o=;===j�=�=�=�=�==[=O==�=�=�=�=�=�=d=�==+=K==�=�=�=�==k=4===9�=�=�=�=t=�=+=K=[===�=�=�=�==K=[={=�=�=`=�=�=�=O=0=/=@="5P(P/
?22??�2?�2^]]]]]_]]qqqrrrrrr^]]]]]]]qqqqqqqrrrrrrr^]]]]]]]qqqqqqqqqqqrrrrrrrrrrrr^]]]]]]]qqq_qqqqqqqrrrrrr^]]]]]]]]]]]]qqqqqqqqq/^]q�2/^]]]]]qqqq�9/�910++!4.#"#4.'33>323>32#4.#"/L79\A#��8Ka@{�<Pd@RwL$�/L79\A#�OjA-U}Q��S"KC0,9;/L5bk/L5,\�d�/�OjA+T~S����N%m�"��@$H%F@P��'�'�'�'�'�''�''���@ HF�


0
�
�

P
?2??�2/^]q�2+]qqr�]q�10+!4.#"#4.'33>3294U?@gI(��>RjFZ�T'�OjA-U}Q��S"KC0,9;/L5,\�d�/V��N"t@;y � t�v�y���		G@�$�0$$���@#H�$GP
P?�?�/]�]+q�]�10^]]]]]]]]]#".5!24.#"32>��q�{A�~�u8�'KlDEoN),Mi>EpN*���D�ӏ0F�Ҍ~�b')c�{~�b('b���WM&:�@i8y8i*y*�$�$$���@
HIYIY�����@?
HG@�''<�1F0��<?<p<�<<�<�<�<,P"6P?�2???�2]]qqrr/^]�22�]�10+]]]+]]]#"&'##4.'33>324.#"32>(]�pt�.��@Re?p�](�;bJ<jO.&IlFKc;"{ЖUXd 0;�Y'H;*	$3:4I/P�́d�l8"`��s�b+:n�V�W�N"6�@wU/e/Z+j+9 I 6	F		y�v�	F@#�8�-G
 8@8�88 8�8�8P�8�8O8�8�88/8�8�88(P2P?�2?�2??^]]qqrrr^]]q/^]��]22�10^]]]]]]"3234>73#46767#4.#"32>����CgQ=��?Rg)KkBEb==aF<jO./F/=6'���6�83K1>v�`)3i�nl�h3%a���N>@(0!F�0��(H???3�+/^]q�23/]1034.'33>32.#"��+:P9(0>W7>"GB:;>9>[;�8c�Q��9���K7�@Ut.�.o�k%6*4$I##H@ 0���9�I
@H

,HO_ `9�9�99���@*'*H?99,)P $�$�$$$ P`p�?3/]q�?3/]q�99]]+]q/]q�3/+��]q�2/�10]]]]]]]]#".'732>54.'.54632.#"�;p�i^�rM���:aF'.RuFA�g@�ʳ��	0DU.zt+MlA+ZUK8!+LwQ+@iLWQ'A01?**EfM��~�*9#JK,9'#/BX��*,{@W(iy�(
i
y
�
� 	
Hl|�� 	Ho
F	� @�P?

	P?�?�]3�2]/^]q33�2/]10+]+]]q]q%#"5#53733#3267*)U8�}�5x��3?1
�҃��UN?����:%y@E�!*!:!F@/�����'�F�$�$$$0$�$$�'�'�'�'�'�'p''���@
HP?2??�2+]]qr/^]q��]qr�310]]32>53#.5##".5:4U?@gI(��>RjFZ�T':�ROjA-U}Qs��"KC0,9;/L5,\�e��:[@79I�6F��:J�iy�5E�gw�
H���@>
H		+{DT�����DT������@�[*K[����������`DT �����DT��DT��������[D�K[��������`DT ������`DTgDT��������[D��K@c[��7K[����? ������`DT �����`P/	?3?3^]]]_]]]]qqqqqqqqrrrr^]]]qqqqqqqqr^]]]]]]qqqqqqr^]]]]]]]]qqqrrrr^]]]]]]]]qr/^]83/^]]]]qrr839=/3310++]_]q]]q]q]q!#3>73e�w����:�@?D??B?����:*�@$�:)J)z)�)�)5Eu��6F6���@
H9I9
H6F���@	H6
F

���@3H9IH9I	H
#(#X###)**���@I/2HI*4*&*�*�*�*�*�*y*6*F*f**�*�*�*�*�*f*v*9*&****���@�	Yiy
�,�,�,�,�,y,f,T,6,F,,�,�,�,�,�,�,t,f,9,,$,,�,�,�,�,�,i,6,F,	,,�,�,V,f,�,9,+,,,�,�,�,�,�,�,�,r,d,V,4,D,",,,��,�,�,�,�,�,v,T,d,B,4,,&,,�,�,�,�,�,�,�,f,v,T,6,F,$,,,�,@��,�,�,�,f,�,�,D,T,6,,,i�,�,�,�,�,�,t,�,6,F,f,$,,,�,�,�,�,�,�,i,V,D,,6,�,�,�,�,�,�,r,`,,$,T,8�,�,�,�,�,t,K,0,,$,�,�,�,�,{,4,D,d,,�,�,�,d,�,�,�,?,,,���@(HB4")#(H-#(H-#?3]+]+?3]]]+^]_]]]]]qqqqqqqrrrrrr^]]_]]]]]]qqqqqqqqrrrrrrrr^]]]]]]]]]]qqqqqqqqqqqqrrrrrrrrrrr^]]]]]]]]]]]]]]qqqqqqqrrrrr^]]]]]]]]]]]qqqqqqqq/^]83/^]]]]]]]]qqqqqqqrrr+839=///qr33333310+_q+q+q+q+]q+]q]q]qq!#.'&'#367>7367>73�ѭ
		
��Ѳ�	�����P&,/-,&R�J:�!C %'&$@�B"#&$C��:�@H����y��v��y7
w

zx����
		���@;		)9��������&Ff�����	����H���@		9

���@t��Hv
d
V
D
6
$


�
�
�
�
�
�
�
�
v
d
V
D
6
$


�
�
�
�
�
�

F
V
�
�
�
�
�
�
	�
�

���@���H�
V
f
v
D
&
6

�&
6
f
v
�
�
�
�
�
	�
�
v
D
T
d
6
$


�
�
�
�
�
�
v
�
�
d


&
F
V
g
F
V
�
�
�
�
�
�
�
d
V
D
6
$


�
�
�
�

&
6
F
7f
�
�
�
�

���@6=BH9
"


�
�
�
�
�
�
�
�
t
`
T
@
4
 

���@"H�


P
p
�
�

?3?393^]_]+qqqqqqqqqqr_rr+r^]]]qqqqqqqqr^]]]]]]]]]qqqqqqqqr^]]]]]+]qrrrrrr^]]]]]]]]]]]]]qqqqqqqq+/^]r83/+^]qqqqrr8399//8892310]]_]]]]]]]]]]]]!	#	3	3	!�������������D,�[�����W�:�@;��������z��i�
H��rVf����@$
H��YF���@$!!&!6!F!f!v!�!�!�!�!�!�!��!�!!���@���H�!�!�!�!!&!6!F!f!v!!&!6!F!f!v!�!�!�!�!�!�!!!&!F!V!f!�!�!�!�!�!��!�!�!�!�!�!�!�!t!`!B!R!4! !!!�!�!�!�!�!�!�!�!d!t!B!R!$!4!!!�!�!�!�!�!�!�!�!d!t!V!B!$!4!!!g�!�!�!�!�!�!�!�!d!t!V!B!$!4!!!�!�!�!@��!�!�!�!�!`!p!D!T! !0!!!�!�!�!�!�!�!�!�!`!p!D!T! !0!!!7�!�!�!�!�!�!`!D!T! !!!�!�!�!�!�!�!`!! !0!P!P!�!�!�!/!!! P?�?3333^]]]q_qqqqqrrrrrrrr^]]]]]]]]qqqqqq_qqqrrrrrrrrrrrr^]]]]]]]]]]qqqqqqqqrrrrrrrrrrrr^]qrrrr+r^]q/83/^]]]]]]8399=//3310+_]]]]]+]]]]]]]]]!#"&'5326?3>73\&ObxN": 0O�3�S��

Ծb�o;�v�+5��ZZH	APRj1�:	@N���Yi��t�FVf({$����;[
@'7H@H���@(\dH�����t`T@4Dd����@ IRH ?Dd��$Dd����3>H���@'H� Pp���@
HPP?�2?�2+^]_]++qr^]]]+]qqqqqqqqq+/++^]33/^]q33_q10]]]]]]35!5!!1���8�j��&���ڋ"�W��-`@A 	H 	
H-!(�?�@!�/oO��/Oo�+�?�?�9/]qr�9/]]q33�22�210++".54.'5>546;#";AcB"7P33P7���?[M4G)+G3M[?�W)LlDi?X88X>j���kl��2T@,

,AT3��jm���N]��@�����������H���@
��H�������H���@��H���p�����@��H��������@+��H0@P$�������$�������H������H���@��Hp������@��H������@��H0$���@z}H�$n���@
ruH�T����fiH���@	[^HP`���@ORH�������@
DGH>���@8=H�`p��������@H@H@p��//?^]]++qrrrr+^]+_]]+q+r+rr+^]]+qqq+q+qr+++^]]]]qq+qqqr+r_rr++^]++]]9/^]�103���N~��"�W��-b�(��	H���@7	
H, &�	0��	 �/oO��/Oo-�,�?�?�9/]qr�9/]q33�22�210++2654>75.54&+532+5^[O3G+*F4O[<���7Q44Q7"BcA���mje3TA,

,@T2dlk�����>X88X?��DlL)�\)P' �@Z�)90
H
0	H
��г	
H���@	
H p 

�@
����&<H
����H���@)H�@)<H�O�o�����@	
H/+]q��+�++�+�/]/]10+++]+]]"&'&#"5>323267LE�I�X&A<82�Q(PMK%233E{4 ;=D),- �&.

2*���:@�[���i�FV9@H��IYi6	
y	�	�	�	F			9	�	�	i	y	�	&			��	�	�	I	Y	y		�	�	�	t	V	f	D		&	6		���@��H�	�	�	�		���@��H$			��	�	�	�		��@@��Hf	�	�	�		���@��Hb	r	�	@	P	"	2			��@�u|H�		��@@
nsH		l	��@@>ekH�	�	�	�	�	�	v	R	b	$	4	D			�	�	�	�	�	�	d	t	�		���@GKH�	�	�	�	�		���@=;@H	 		7�	�	�	�	�	T	d	t			�	�	�	�	T	d	�	�	�	�		���@H@		�/]?��^]_]+]qqrrrrr^]]+]]+qq_qqrrrrrrr+^]+]+qqqq+qr+r^]]+]]+qqqqqqrrrr^]]]]]qqq/^]]]]+qqq�q3/�103##5
��������������%i�?�/�3�Ͱ2}���/�%3���&/�ְͰ�ܱ22�ͱ22��
ܰ"2�Ͱ!2��'���
!"$9��90167#5&'&576753.'�$

& ���?S|�e[yf�|�Y�^9>�N�N�"��Z������x���7?ZW:P�2�@SF6F%5$'
o',n#'n_


O_o2o  0Pp���0�����@@H&Q#�?o����@%*H@ H,s,t2v2�22?3]�2?3�9/++^]q3�2/+]qr�/]9/q3�2�9/�3910]]]#!5>=#534>32.#"!!!2>7P	9YsC�FYV��0c�gF{cG�
'5A$rp��h,?(�&C5%7PuM%�.�y��\�f7:V994$s}��~8j\G*C0q�s#7�@wJZj,<JZj,<EUe#3EUe#3J"Z"j"-"="EUe#3EUe#3JZj-=.�$���/9)�3�	/���]/]]���10]]]]]]]]]]]]]]]]467'7>327'#"&''7.732>54.#"�)%dhc6IG~6ah`%+,&dfe6~GH�4iff%)�,Mf:9fM,,Mf9:fM,�G6dge'+*&ai`6GG�5die%)*&iif6I:fL,,Lf::fL--Lf��v��@�����IF��Jjz���Eeu�O�	@�\
		@	�		QQ/��@)-H ��??39/^]q33�]+q2�2�2/^]33�229�]2�]210]]]]]]]]!!!!#!5!7!5!3	3�A�������}��@�[�sw��}���/�}��y���N]��@�	�	�	�	�		������H	���@
��H		�	������H	���@��H�	�	�	p	�	�		���@��H	�	�	�	�	�		���@+��H0	@	P			$	�	�	�	�	�	�	�			$	�	������H	������H	���@��Hp	�	�	�		���@��H�	�	�		���@��H0		$			���@z}H�			$	n	���@
ruH�	T		����fiH		���@	[^HP	`		���@ORH�	�	�	�		���@
DGH		>	���@8=H�	`	p	�	�			�	�	�		���@!H	@H@	p	�	�			/	?/99//^]]++qrrrr+^]+_]]+q+r+rr+^]]+qqq+q+qr+++^]]]]qq+qqqr+r_rr++^]++]]9/^]3�21033�����
������s�T�MaO@�u/�/z�dU+UkU-QmQ-9m9}9�9U`u`�`S>s>�>SZsZ�ZyP�PoPZP-Pt�6F*I%*%-+$$LLIK"I7�KK7K7NFAII�NN N@N�NN����!HN���@HN-I,,XIA A�AA���@D"&HA�7�7�7�7���ySjS�S�S���w�hS]72Q-'Q?2�/3�9]]]]]]qq]]]]]]]/+]�3/�/++]q�2�9999//r��10]]]]]]]]]]]]]]]]]]]]]]]2.#"#".'732>54.'.54>7.5464.'>LO�lI�.DT.}�2Sm;D�pF7R61O7:p�k]�uO�6Nd9:fL-9_{AA�hA ;T5*K9!��5Xq=:Y<1Rl<6]D'�?bE-:"
OG-@. .IjL-WI7
3@Q3IuS,@hM7C%
(A/6H3$.IiJ-SG5.@P0����0E3#!4E'.B0"/F-�Z{�������H���@�
H�@H@H�;-9���������,0H+�������{�o[O;/?��@H@H/++]_qqqqqqqqqqrr+_rrrrrrr^]]]3�2/++��++�1053!53���ӥø�������1[�@o�Q
II*I
EE*EL<9/I/6(F(6$F$9I�Z�Z5QEQ:=�4�46.F.6)F)9#I#9I2�Q=_GoGG=G=�0@ @���@DH&�o<7�BW�LRLBBBBB�B�B�BLLLpL�L�L�LBLBL!�+�?�?�99//]q]q3��3/q�/+]q�99//]3�10]]]]]]]]]]]]q]]q#".54>324.#"32>%32>7#".54>32.#"�4^���dc���^44^���d��q\-Rt��W��cc��W��tR-��!CdC1K8's:RoKe�b00a�bKpP7r&7H/Ec@�d���^44^���dd���^4r���V��sR-c�䂃�b-Qs��YHxV0-7#+Q?&@r�^a�o;$;K'!2&.Sv���2A�@-(
H.(
H(HH���@	H3'' �/�88���@H�8d8t8�8P88���@

H8�?�@&H�CC���@3
H999�

H#33�** P��?�3/]3/�]29/]]3+]�]]+]/+�3/�/+_]]]+q33�2/]10+++++".54>?54.#"'>323267#".'#'2>=5V>!3Vq>�'8#CQ	�+KlGDmM*#	*)9&&�30P9 �)J7 :�8R4E\8<.<#;L
/R>#<_B��:2h'3MFo"7G%A4+8AS� �q@Tjz�j	z	�	jz�jz����_

�
@
`


���_ `�	�/o/]3�2/]qq���/]]q���10]]]]%53	!53	v��R���T����P���Q�m?s����m?s����d�G�3@� ���P���@		H��?��/+]]]�/]]10%!5!������������1?H�==$=:���@^H9/I/6(F(6$F$9I= 	
H6.F.6)F)9#I#9IEE3>>E?E5�6@�;;2??6?6?�0@ @���@<H&�o226>4�E	DDD�7666�777p76E77E6!�+�?�?�9///]q]�]�23//q�/+]q�99//833/��293]10]]]]+]]]]+]#".54>324.#"32>##!24&+326�4^���dc���^44^���d��q\-Rt��W��cc��W��tR-�Rǡ3��hUݟ_Q��PT�d���^44^���dd���^4r���V��sR-c�䂃�b-Qs����P��?~of{��PEH��U���|
��/�//10!5!|�s��^z\��'B���	H
���@!	H	H	H��

 

#��?���/]���10++++#".54>324.#"32>�-Ni;;hN..Nh;;iN-m0A&%A00A%&A0y;hM--Mh;<hM,,Mh<%B11B%%A11AA$�U@5
����	� 
���	��P���/]]3]3�22]/�/]3�2�]]��]]�10#!5!3!5!|��X�������u����v��X��)3��"�@8jz�YijIY

:J:Zj���@(	H!�  `��� � �� ��?�2?�39/]33/�/]q�210+]]]]]]]]qqq'>54&#"'>32!+XdgR4GJDX�.LkDAgG%6Sf_N�3g=`QHIP1>KID3W@%!<V6>dVKIJ*q'��5�@ffV	f	Y"i"*���@PH 	
H0-�--� ` `��"�###	�@%H1�i"H"X""�(��G		�?3]�?�3]]9/�9/+]�3/q�/]q�2/�9/910++]]]]#".'732654.+532>54&#"'>32���WtI$�	XUMS%8C=9=2 JGDT�2Of;EhF#VZ4K0t�'@Q+
CEHL-6	m
5(<EFA:V9!;O.Kn%6EH���7@%Fz��@�@	
H��/?�/]�/+]�]10]]53H��������Wm:'�@J 	H�%�%l%|%%H
F@O��@ #H@H)�'F0��`)�))���@HP#P??3?�?�2+]/^]�2�]++]q2�2/10+]]+3326533267#".'##"&'�� :T4��� 3$6G,4�dCm�W�RNjA��s��,8 
�4G)_c)&�P����=@'��_o��0/3?�29//]]��]�/10###".54>3!tp�qP�`52_�W3��#���-X�YT�]2f��~�&@���@&H/+]�/^]�1053�����w�N�~@ H HFV���	H���@>	H 0��/o�@	
H� P`p��/]�/9/+�/]�^]qr�9/3�]210++]++#"&'532>54&#*73�AhK-1%)8#=HAk'^^�)C0b%(�dQP3}�
K@, 0`			� 0o ����?�2?33/3/]3/]/]�2/]10]535733P���{�3kl�x��k���#��
��H	���@+H H H�$4d���������@&H�@!H�%p%�%/%� P��?�3/]�]]]/+�/+]_]]q�10++++#"&54>324.#"32>Ӱ���+W�X]�T'�2H00K33F+1K4��ɾ[�e55d�\SnABnRTnBBnS� ��@deu�e	u	�	eu�eu�
�
���p��?o���p����? 
�/o/]3�2/]]]q���/]]]qqr���10]]]]%#5	53#5	53ΨR���R�ݪR���O�ot��?��ot��?��8N�&y�' �����@�p`?55]]]]5��8u�&y�' �r��/@o/�p`3@H?5+]]]]5]]5��IN�' 
'����s.!@p`S@H?55+]]]5���V:%)�@py�z#�#z$�$Td:Jz�
H		�'�''�((F_@<H@H F@<H@H@
H  +!	(�)C_/!?!o!�!!/]q�3]?��9]/]+++�/]++]�9/�q3/�10]]]]]74>733267#".#5�%>ORO?'�'>NPM<%*MmC���Cy�zr�{@q�2GlUC<:DS7EhP?99FX;;\? �zT�pA8g�d����R�&$�N�&���%+5+5��R�&$��@&L%+5+5��R�&$�`@&%+5+5��R&$�^@&,%+5+5��R�&$�l@
&%+55+55��R�&$P��@8%+55?55���@VtrVfs�Ue�m\) 	
Z__		
_
_?�??�29/�9/�/33�23/399/]99//q10]]]]]]]]]!!#!!!!!#!�������	��E �!�����d���<����#2:3&�3��h�Ny�&&x��:0(%+5�����&(�?�&����

%+5+5�����&(��@&(
%+5+5�����&(�w�
&���
%+5+5�����&(�y�&���
%+55+55��	��&,���&����%+5+5���6�&,�F@&E%+5+5����h�&,��@&
%+5+5��4�&,��@
&%+55+55e�!p@Eu
[ � [�Z�[

Z@#�Z___?�?�9/q3�2/33/�2�q�9/10]]]]]]]3!2#!#%4.#!!!!2>�ң�oj������R��{����j:o��N!`Q�������]�H�˂=�9��H����� &1��@&+%+5+5��a����&2���(&��ش),
%+5+5��a����&2�%@(&%(+
%+5+5��a����&2��@)&.(
%+5+5��a���&2���(&����1?
%+5+5��a����&2���(&����,*
%+55+55��s����H���@HH	H���H���@HHH���@HH
H���@H����� /q/]]q10++++++++++++	7			�b��h^^i��`f����Jb`g��_i����ia��G����'3�@�Y,%T!e�{�lZi
�	t�cU2[2r+�+T+&T&U%�JZzdk ,([@5� 5([�� +#/_#_	?3�?3�99/]]]�]�q�9910]]]]]]]]]]]]]]]]]]]]]]]#"&'#7&546$32734&'32>%.#"�_�����Qx��XV\��}�Ry��UW�11�;;�]��{;�23�;�\~�?ǥ��h:6��a
��
�e86��^���p�H��,/V��}q�KU*.P�������)�&8���&���%+5+5�����)�&8��@&%%+5+5�����)�&8��@& %+5+5�����)�&8���&����%+55+55��-)�&<��@	&A	%+5+5���_@?�t�{�Z0�� @Z@__	??99//��/^]�22/]]q�10]]]#!#3!24&#!!2>�;y�{�b���}�|>������RyO&�X�xG����<o�c����,Ok�����C�@��3�?�?�/�/�.�.vAv8S��\'l'|'k:�44%4B&BvB*=H6H 		+H@0���E�*F++0++pE?EOE������$P1+P?�??�99]]]]]]]]/^]��]q�9///]]��10]]]]]]]]]]]]]]#"&/32654.54>54.#"#4>32�)W�^P�7CJN%\b6Q_Q6!1:1!!?Z9DkI'�?x�nf�i5!3:3!7R_R7'BsU1�VO8M>9IbI3I:04?+%>-#MzX�v�p6.Pm>=ZE5/0&;9@Ur��W��s�&DC��B&����CF$%+5+5��W��s�&DtT@B&BE$%+5+5��W��s�&DK��C&����HB$%+5+5��W��s�&DR��B&���KY$%+5+5��W��s{&Di��B&��ʴFD$%+55+55��W��ss&DP�G&��ȴLB$%+55+55B���N>OX�@`�6z${:u�eDuD�D|VjV~QjQeu)/),(	
H
5XF!OOO_OOOOP	I

=G@P/P?P�PPZ�,G-���@H--GG@H>PPP5���@+I5SP8?Q!!J2�,+,,,'P2JPP�	�			?3/]]�?�2?�3/]]9/�?�2+9/�/+�3/+��]q�2/�9/]3�29910]+]]]]]]]]]]]]3267# #"&54>?54.#"'>32>32%32>5%.#"�"FmNu��=f�l��fJf�]��&C[ku<�9X=6X@'�
:i�p��1?�j��o/�P�(QLB1d]Z�S(���-`Q6�R�b7^H--[I/3]G*��GlN3 
;C^:'C3@kN+FEJAX��z3I3Wa?bt5ī�Jb��W�N�N&Fx�(0(	%+5��W���&HC��&&���'*%+5+5��W���&Htp@&&T&)%+5+5��W���&HK�@'&,&%+5+5��W��{&Hi�@
&&*(%+55+55��
��&�C��&��´%+5+5���/�&�t?@&?%+5+5����i�&�K�@&
%+5+5��5{&�i�@
&%+55+55V��'�'9�@^d3d8k*k0
%5E22#G(@
(@H�(�(�((;�2G-P
P

���@
HR$4D

5P?�?99//]]]+]3�3/9/]��]q9/9+9�9/]910]]]]]]]".54>32.'57.'3%4.#"32>6}�v84t��Gu++sL���8xB�-S*2�Q�X/9x��!GqPQsI!��QvK$I��ok��K V�E�r^-N#3�p\J���yzĊJ�^�Z,-\�[��,Z������&QR�@&&/=$%+5+5��V���&RC��#&���$'%+5+5��V���&Rtg@#&I#&%+5+5��V���&RK�@$&)#%+5+5��V���&RR�@#&
,:%+5+5��V��{&Ri�@
#&'%%+55+55A�$uK@0���_ ?_o	�@���O��?�]���]�/]/]]]9/3�210535!53ި��������������,���\"-�@]u!�!u�z&�&z,�,�BMDK
&#G@����/�#G @��/�/�/�/�// //���@
H%)PP?3�?3�99+]]]q/^]q��]q�9910]]]]]]]]]#"&'#7.5!2734&'32>%.#"X��a�<d��)*�g�9[��*'��&i>EpN*���%hBEoN)���02t�D�t0.-i�E�wDm,��1''b�~�U1-$)c��������&XC��&&���'*$%+5+5�������&XtW@&&5&)$%+5+5�������&XK��'&����,&$%+5+5������{&Xi��&&���*($%+55+55���W��&\t@ &6 #%+5+5��W�"6m@Lf4v4i&y&IYF
V
)��&��G@�##8�-!F""0"�""!2P(P
??�2?�2?/^]�22�]�10]]]]]]33>32#"&'##4.#"32>��@Re?p�]((]�pt�.��;bJ<jO.&IlFKc;�Y74I/P��}{ЖUXd 0;�Y�d�l8"`��s�b+:n����W�{&\i�� &����$"%+55+55��R�&$MhN@&%+5+5��W��sS&DM��B&��ʴCB$%+5+5��R�&$�y@&"%+5+5��W��s�&DN�B&��̴GS$%+5+5���`c�&$Q�
��ϴ!!%+5��W�UsN&DQ�
��´NN$$%+5��h��y�&&�S@(&~(+%+5+5��W����&Ftg@(&r(+	%+5+5��h��y�&&��@)&&.(%+5+5��W����&FK�@)&
.(	%+5+5��h��y�&&O"%@(&'(*%+5+5��W����&FO,�(*	%+5��h��y�&&��@(&&*0%+5+5��W����&FL�@(&*0	%+5+5���e�&'�f�&����"%+5+5��V����&G��K@
=S==%+5?5��e��V��m�';�@VU*e*Z:j:9I6
F
	y�v�F@2=�(G?=�=!Q��7P���@
	
H-P?�2?+�2?9/]q3�2?]r/]��29�210]]]]]]%#"!234.4=!5!533##.532>54.#"52�z��{�2��,������>`EGmI&&JkFB`@�hZ6Zb
+/*	������)'H<+	
%05pp�g0.g�xs�b+.f������&(MzN�&���

%+5+5��W��S&HM�@&&'&%+5+5�����&(���&���
%+5+5��W���&HN@&&+7%+5+5�����&(O�%�&���
%+5+5��W���&HOD�&(%+5����U��&(QP
��´%+5��W�UN&HQ}
���22		%+5�����&(�o�&���
%+5+5��W���&HL��&&����(.%+5+5��g����&*��@/&"4.#%+5+5��V�W��&JK�@G&LF)%+5+5��g����&*��@.&'3;#%+5+5��V�W��&JN�@F&KW)%+5+5��g����&*O5%@.&'.0#%+5+5��V�W��&JO-�FH)%+5��g�N��&*�N�.2.#%+5��V�W� &J�8�O&����FJ)%+5+5��� �&+��@
&
%+5+5����>&K��@�#&����("
%+5+5���@HZZ;8��D��D��0p� ������@$H/_P`_@��
	?2?39/]q��]22�22]+]q/^]_]qqqrrr^]]33�22/^]33�2210!!##5353!533#5!f�������������s������-��
��)����@B	
H!F@P��+� $F�0���+�+�+�+�+�+p++���@H#Q P����	
H?+�2?3?9/3�2+]]qr/^]q33�22�]q�910^]+>32#4.#"##5353!!=FTd>h�M�0XF@gI(����,��Y7M28e�T�W�EhE#.TxK��������!B8'�����&,��@&
%+5+5������&�R�@&
%+5+5��1�&,M��N@&%+5+5��1S&�M�@&%+5+5����l�&,��@&	%+5+5����l�&�N�@&	%+5+5��\�U��&,Q
�%+5���U}�&LQ�@
%+5]���|�&,O'%@&%+5+5�v:�@F$	4 ����@7��H��������@4 ����t�`���@��H����$4������H�������H������H����@&��H�dt��� ���u����y|H����nqH���@cfH����0$����CFH>���@	8;H�����@*-0H+���k{@ 0�� @���@

H??^]+]]q_qqqr+r+^]+qqqq+++^]qqqq+r++^]+]]]]+qqqqrrrrrrr+^]]9/^]�1033´:�������I�&,�-�����W�&LM�@o0]55]]]55�� ����&-�7@&�%+5+5���W/��@����H(H����@1H�;@H$F$D�����@	��H@��H���@3��H�@P_o�� 0@����O�������@9��H����_ /?���o��?o�������@eORH����/?O=@58Hp����@#&H�O�p�����@��/?�
P?�?/]3/��]]]qq+qrr+^]]]]+qrrr^]]qqqq+r^]]]qqrr+++^]_]9/^]3/�/]+]q�+]10++_]]"&'532>53#'##53M"A
$
&1
�6]�i��h��W�+C.��@>jN-n������N?�&.����ʹ%+5����N�&N�N���%+5�:�@H{tV	f	{��YiC
:*	�z�,

	p	�	�			P������@(HF0��
�
�
?

?2?39]]/^]�2/+]q839/]893310]]]]]]]]]]]!#33	0�������I��l�~:��
�/�����/�&/���&��^�	%+5+5��[>&O�N@&K%+5+5����N/�&/������
%+5��~�NG�&O������%+5���/�&/�@
�
%+5?5���i�&O�$KA@@H@H@H����H���@H@H
� �%+5?5++++++���/�&/O���_%+5�����&OOB���T�%+5/�
w@1y�dt�V�V�Z	 @`������H	���@H	



_?�?9/]�]9++/]q/3�210qrqr]]!!573%h���y������U�U�����f@*''F @
�
p
�
�
�

���@H
 H
O??9/]�9++]]]9/^]33�2210qq35737�zz�||D�D�XH�G�z��� �&1�@&I%+5+5�����&Qt�@&&a&)$%+5+5����N �&1������%+5����N�N&Q�[�*&$%+5��� �&1���&����%+5+5�����&QL��&&����(.$%+5+5����P�&Qb����4�7�@�5���)�)�)0���H/���@[H*6:6�6�6���3'Z@3Z@Pp��0P������0'_-!	_?2/�???�2/^]]]qr�/]�29/]310]++]]]".'732>54.#"#4.'33>32�8`O?%d;@N)
'QW^�yF��"g��V|�p5V�(6s-A:k�_Bl�b.>i�M��D%\ZL?EE=eF'@�̋���ВN��W�N5�@y4�4Z4j4+4;4K4.���@J	H..1F@�`��7�%F���� @��%P+P?�???�2/^]qr�2�]qqr�9/10]+]]]"&'532>54.#"#4.'33>32"A
$
&1
4U?@gI(��>RjFZ�T'6]�W�+C.OjA-U}Q��S"KC0,9;/L5,\�d��>jN-��a����&2M�N@(&)(
%+5+5��V��S&RM�@#&$#%+5+5��a����&2��@(&-5
%+5+5��V���&RN@#&(4%+5+5��a����&2��@
(&](1
%+55+55��V��"�&RS	@
#&g#,%+55+55a����1�@w�
f
iV0Y*T�//T/++[+Z" 0"/"O"?"_""�"�"�""@&H" -[ 



3___(__?�?�?�?�9/�]/]]�/]/+]q99//]�29910]]]]]]]]!#"$&546$32!!!!!%267."#"�399����Y^��:94����L�o L %%}�}?@~�j�	���b��<����WJ�΃�єPV��2N';D�@��fvaUZBjBl=Z=E:U:e:E4U4e4J0Z0j0J*Z*j*I		&G@</<?<�<<F�DG�( ((2GPDD?P!t�{�-P7PP��?3/]]�?�?�99]]?�9/�/�/]r�299�]q�2/�10]]]]]]]]]]]3267#"&'#".53 >32%4.#"32>%.#".#IrPu��=f�l��=?ɉq�D��uC�w��o/�?*MnDErQ-/Ql>ErQ,��-cT:�U�g9^H--[I/[\]ZD�ӏ�^SX��z'~�b')c�{~�b('b�᫝Jb���h�&5���&����"%+5+5�����&Ut�@ &J #%+5+5����Nh�&5����#%+5����N�N&U���^�$ %+5���h�&5�f�&����!'%+5+5��8��&UL8� &����"(%+5+5��]���-&6t�I@@&[@C%+5+5��9����&Vt9@8&]8;
%+5+5��]���&6KrG@A&F@%+5+5��9����&VK�@9&>8
%+5+5��]�N��&6x��(H@%+5��9�N�K&Vx��@8
%+5��]����&6�s@@&BH%+5+5��9����&VL�@8&:@
%+5+5��.�N��'x[7���N*,&xGW��.��&7�!�&����
%+5+5�����&W��K����%+55.��e@> 		��/�@HZ
 _
_		
??9/3�2�2/]3�2�+]q9+M�9_^]10!!#!5!!5!�������<��y��Ĝ���*,�@[(((( 	H��(	H
oF�


 

� / PP��P?�?�39/]3�2�2]]/^]q33�2/]333310+]+qqqq3267#"5#53#53733#3P3?1)U8�}}}�5x����N?�
��0���Ѓ�����)&8��@&#1%+5+5�������&XR�@&&
/=$%+5+5�����)�&8M�I@&%+5+5������S&XM��&&����'&$%+5+5�����)�&8��@&'%+5+5�������&XN�&&����+7$%+5+5�����)>&8P��@
&$%+55+55������s&XP@
+&0&$%+55+55�����)�&8��@
&Q#%+55+55������&XS@
&&d&/$%+55+55����U)�&8Q (��g�''%3����H3����H3����H++++5����U:&XQW
���22%+5��	��&:�y�0&����5/.%+5+5������&ZK��,&����1+*%+5+5��-)�&<�[�
&����	%+5+5���W��&\K�@!&& %+5+5��-)�&<�h@
	&
%+55+55��A��&=��@
&J

%+5+5��1��&]t5@
&]

%+5+5��A��&=O|%@
&
%+5+5��1��&]O��
%+5��A��&=�8@
&%+5+5��1��&]L��
&���%+5+5��}��س
H���@8Ho


F0�������p������@"%HOpP??�]]q+qqrrrrrrrr/^]�2/]10++!#4>32.#">�;fQ E-(3�;eK+�)<'��N���@k�z��Yiy�
U
e
u
6
&&"2r�%5���P�&6F


_�P	
Q�
//]�9/3�2/q333qqq/10]]]]]]]]]]].#"3###737>32�>.!	������(GmQ R# 
(@-����-��;fL+

R>'=C$@z@�@fv�Vf���@�	H	HH
&	
 '  (�DB�BB>2��>�>�>/� �>>  
�Zjz 0 EPE�E0E`E�E�E�E/E�
U
e
u

>>-�p�9@�@�@�@@@	
H@	'&&
 �9?999
/3//]]]39/3�2/+]9/]�3//3]]]]q3/]q3]]939///]q]q]��]�������310]+++]]]#!#&'.54>32.'!4.#"32>573�&!á�~��$!&&BX22XB&���$11$$	1$����p-O�Ds���O--O;"";O��$J<))>I$�_w,!!,,#
#,#����W��s>&D'P�UtcZ#@e&jm$%��ԴLB$%+55+5+55����&����&� �%+5+5��B����&�t�@Y&IY\=%+5+5��G����&��K@4&I47
%+5+5��,����&�t�@.&^.1%+5+5��]�N��'��6��9�N�K'�)V��.�N��'��7���N*,&�sW���	B@v	�	x�����@H��@��/?�/]3��/]�+]10]]#'##53�i��h��������	B@v�x�����@H@����/?�/]��2/]�+]10]]#53373���h��i���3�XSQ@;���[k4D H�@!H/?��@H@H/++]_q+�+/]]]/]q10!5!X��%����w�I@3��O���@@<HP`���/?�/]��q2/+�]q10]]".'332>73)JtT2u'8H**G8&u	2St�3Up=+;$$;*=pU3� P�@�@HS?�/+�1053�� ��3�s'Y���@	H	H	H���@*	H�@��_

�#�/?O���/]��]�/]���10++++#".54>324.#"32>&BX22XB&&BX22XB&l$11$$11$�2XB&&BX22XA&&AX21$$11&&1P�U�L@7	
H		
H 	H�_�@
H� P`p��/]q�/+/�]q�210+++#"&54>733267�G(ij"05�4+1-:�pfU/O=*-;F'*0
�����M���@�	H	H))9Yi@��H@��H���	9���@��H���{I��@��H6��}�o-=M
�����H��[k{��H	�������&fv�n��@@`emH�p�Rb@"2��������Rbr$4D�������dt����@	<@H;���@95:H���dt�0@P$�����p�������@H�Tdt@
H���@	$'H�����@!HD0 �����@+
H	�@�@H��/?�/]2��+]q�3//�]+]q_qq+q++]]+]qqqrrrrr+_^]+]]]]]qqqqqqqqqrrrrrr+^]]]]]]]q+qq+qrrrrrr^]+]qqqq+qrrr^]++]q10++".#"#>3232673�*TNG76	[-J;,TNE67\+J�%-%>9-_N2%-%?8,_N3��o@R�
f
��b�b��
��@?O����@)H@H��/?�/]2�2/++]��^]q�10]]]]]]53353���������������@@+H)
*+@���p @`/]_]]�/�]10_]]+53�E���!$%����A	
S@5H
*//�
�

�	�		�
�?/]]33/��2/�]/�]9/q�]10]+53%53!53�E���ؖB��!p%��������R�&$T�l�|^���
�%����$$H����H����H����H����H����H���@H@H?5+++++++++]5��~�
�����@��H����@!��H��t�VfD&6����@��H�����t����@$��H�������v�Td���@"��H���FVv��4&�������H���@%��Hdt6FV$��������@��HVf$4D������@w|HRb 0@p���@	goHDT����cfH����\_H����W[H���@QVH$4D�������@CFH0$<���@8;H������@)-0H����� 0�������
H�/�+]]]q_qqqr+rr+^]]]+]]q++++r+^]]_]+]qqq+qqqrrrr++^]]]]]+qqqqqr+rrrr+^]]]]]]+]q+/�1053�����������'(�T�(�|1@
��q@%��//]]]]5+]]5?5����
�'+�T�%�|_����H����H����H����H���@
H��q@%��o/]]]]5+]]5?5+++++����U�',�T��|\����H���@H@H@		H��f@%�o_O//]]]]]]]5+]]5?5++++��������&2�T��|'@
*�((��^@((

%/]55+]]5?5��A��'<�T�t�|�@@��H@��H@��H@��H������H���@��H@��H@��H@��H������H���@��H@��H@��H@��H������H���@��H@��H@��H@��H������H���@��H@��H@��H���@��H@��H@��H@��H���@��H@��H���H@��H@��H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H������H�����H�����H����H���~~H���}}H��@�||H��@�{{H��@�zzH���yyH����xxH���wwH���vvH��@�uuH���ttH��@�ssH���rrH��@�qqH��@�ppH��@�ooH����nnH��@�mmH����llH���kkH��@�jjH���iiH��@�hhH��@�ggH��@�ffH����eeH��@�ddH��@�ccH���bbH��@�aaH��@�``H��@�__H��@�^^H��@�]]H����\\H��@�[[H����ZZH��@�YYH����XXH��@�WWH��@�VVH��@�UUH��@�TTH��@�SSH����RRH����QQH����PPH����OOH��@�NNH��@�MMH��@�LLH����KKH����JJH����IIH��@�HHH����GGH����FFH����EEH����DDH����CCH����BBH��@�AAH����@@H����??H����>>H����==H����<<H����;;H����::H����99H����88H����77H����66H����55H����44H����33H����22H����11H����00H����//H����..H����--H����,,H����++H����**H����))H����((H����''H����&&H����%%H����$$H����##H����""H����!!H����  H����H����H����H����H����H����H����H����H����H����H����H����H����H����H����H���@H@H�p`@���@
%�]]5+]]]]]]5?5++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++������&v
T��|D@A@H<�::��>@::55%�_���]]]]qqq5+]]5?5+����A&�U�<@@��H@��H������H������H���@��H@��H@��H@��H������H������H���@��H@��H@��H@��H���@��H@��H@��H������H���@��H@��H@��H@��H������H���@��H@��H@��H@��H������H���@��H@��H@��H@��H���@	��H@wwH���@ssH@nnH@mmH���@jjH@eeH@ddH@``H����^^H���@]]H@WWH@VVH@UUH����TTH���@	OOH@JJH���@==H@88H@77H@66H@33H����22H����11H���@00H@**H@))H@((H����$$H����##H���@""H@H@H@H����H����H���@H@		H@H���@H&��ܴ
%+555+555++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++��R�$�����%�/�8@(Z@0P @`��_??�/^]q/]�10!#/�8�����=��@jz�eu�YV
H���@@
H�Ffv�Iiy_����O0����@H0/_ H�?3]+?�2]]/+]3/]]]]qqq39=/3310]]]]++]]]]1073!%.'>���#��������^(RE01FR(�������(��A��=��� �+a����'+�@�ifYV VZU	*+
%%Z%!!U!	+*[;�����o0 @P�[�
�
 



+_((#_#_�- -]]?�?�9/�/]]]�/^]qqqqqrr^]�9910^]]]]]]]]]]]#"$&546$324.#"32>%!!�_��������X\����\�A�{~�?A�{��{;��K��ǥ��hm���
�ef����ДPP��әUV��Р���|�,���?�.
N��@t�{�
���H6
���@
H
HH9o0/%5��*:	��
%
5

�
�

��*:	�� 0o0v�?2?3]/]]q2]qq/]3]qq9=/3]qq3]qq]]]]10]+++]+]]%#.'&'#3N�|
�z�?��/Y#)%')#Y-� �����0��� �1Z��K@1R
	_��0 @p�
___?�?�9/�/]q3/]q39910q!!!5!5}9��\��������������a����2� �O@(Z@Z@o��	� 	 	�	�	�		���@
H_?2?�+]q�/]�/^]�10!!#!a��x� �������3l���@��i	t	�	�	��Wg�Td�
�
t
�
F
V
f
9��g�gd'��0P`�F
�� _	
_?�2?9�2/]]]33/3qq/^]]99//]qq3qqqqr10]]]]]]]]q]35	5!!!l�����m�C����C|����.��7��-)�<u����(3����H���H���H���@HH)Z{�Zt�#Z/$Dt��5D55�5�5�5�5p5`55���@

H#.`

"1`

??99//3�23�2+]_]]qq/]q33�22�]��]�10+++++#5#".54>;53324&+32>%;#"�@�ŅS�S�Ń@C�ɇH�G�ʅC���08\�Y+�+Y�\84���i��T��T��iq��H��H��u���5b�XX�b5��.+�;��#�@ZjUeZjZjJE
Zd��#Z
Zk���d��4Dd����%$%4%D%�%�%�%�%�%�%�%�%`%p%T%@%%���@	H"`�??339/]3�2+_]]]]]]q/^]]qq3�]q��2�]q�10_]]]]]]!#".53;332>53+�S�ɆC�/]�\8�8_�[-�D�ɄS�T��h�-P�d9I��9d�P�1h��T�UW��9�@[e$e�1�11H�	�		Ht�66v68v8t7�767** )50(
`(p(�(o�(("[P55���@EH�5�5055[O�_���o� 0_;
'0*_)_?�?3�2]/^]]]qqr��/]]+r�99//]]33�10]]]]]]]]+]]+]]]]267>;!5>54.#"!532.54>���Z;m�c*'!G�`�Y*=t�lm�t=*Y�`���G!'*c�m;Z���V��j���6��3~��Ut�|AA|�tU��~3�6���j��V��4�&,��@
&%+55+55-)�r@H���@�H�	�@
i�6FZ&V�v�9I���V	9Y����	���Yy�6)9����������tVfB$4��������vbT6F$�������pd@$4i@���������tP`D �����Tdt��0@$���$DTt�9����p$4Td�����4T�����p0`/
�	_

@	H
;K{??39]3/+]3�2]]_]]]]qqqqqrrrr^]]]qqqqqqqqrrrrrrrrr^]]]]]]]]]_]qqqqqqqqqqqqqrrrrrrrrrrrr^]]qqqrrr^]]qqq/^]]]q�9�^]]2+M�2/���10+_^]]+]]#3	3%53!53	����������ӥH��H9�a�y������V��e&~T!@<&<?%+5+5��F��j&�T�@:&I:=%+5+5��j�X�&�T9@$&d$'"%+5+5����&�T�@&
%+]]5+5������A&�U�@	 &���&,
%+555+555V��eN';@�z0�0y4�4e+Z:j:6F-&-&/%%K$k$/$?$$�#/##/""//Kk/-y�"0H"!0H!*!I*!���@!H@"�2�2�22 2@22=�(G=2!7P-P?�?�??9]/��]]]2�+]22]]]3+3+10]]]]]]]]]]]]]]]]]]]]]]]]]]]]%#"323>73#.'32>7.#"KAWoF���EpV?
�.'$�
��;]B6dR=-GfFB`@�8^E&'E]68?A/���:g��n'BA:1p�g04k�lV�sD.f���WI� >�@D��{�z<�<jz�fvJ2Z2j2%eu��%0H400@0`00���@:H0404H@0+@+�+�+�+�++@�!
F00P11&9P
!&P	?3�2??�9/�9/^]�2�]q�99//+]q�910]]]]]]]]]]#"&'##463232>54.#5>54.#"I5o�xf�8���c�f5#=Q.9r[8��GQW+HnI&&Q�Yq9W<FhD"�Y�tB7'9\5�����/Z�RIoR87]���")LjBAnQ-��z,M9 #MzX�X�:�@;;K);K*	*:JM


HB6"
���@L
H
/9?_����?���� ���_�@���@@0`����@H

?2/?3+]q/]82/^]]]]]qqr^]839=/3310+^]^]]]]+]]]]]]]]3>73#>7���z�0:�]?C?>C?���-o|�A��[V���2�@Qs�jv.�.j.ZjZjU
e
r�&�r3C22G@.//)�4�
G))/P���@HHoZ.0P$?�?99]]++�2/]��]q9/993�2/10]]]]]]]]]]]]4.'32>".'#".54>75!`1ELI�f<#IoMQpE��
*-)
<6fO1@{�sr�}BI�`����U�sVQr�[L�`76_����7t��an��HD�sp��a!Sw�F��jN9�@1�%�5$E$7"*%	H
**F000##@%0H����H���@BH;5G?O_@%+H�;;_;0P//O/_/�/�/�/�/�///'P$ P?3�?3�9/]q�9]q/+]��+++2/99//9�10]+]]]]]]%2>7#".54>75.54>32.#"3"�6`O?i Tj�N`�\-*H`63U="2_�Wv�C�/}K[`7a�KH�mB4Jr08d'G5 .TtE:_E*(@V3>jM,VcXGDUJ9F%�(PE-E.V��`�8�@u�&���7�7�'|'�'�'usd$�$�$�$���9,��,<Ld6�6�6&6V6�///G!(22
O�@!H @2/P���@H���!�!�!�!�!�!!0/?9]]]]+3�2/^]+]q�2/3/�9/q10]]]]]]]]]]]]]]'>54.'.54>75#!5!+Jd96oZ:
~	$B^9*YRI61Uq�@"$"���>�{lQ/{@O1"9XD"D>38!&'"-!
$2JdCS�����D�F�����j�X�N#d� ��@.	H#F@P��%�F�


0
�
�

�%�%p%%���@H#P???�2?+]]q/^]q�2�]q�10+4.#"#4&'33>3294U?@gI(��	
>RjFZ�T'�XVOjA-U}Q��SDy*01//L5,\�d��j��	�%�@l��������
|$�$s�Ue|�JZjs�EUe


		!G@���@-0H'� G ����#H0''���@#H�'P  PP?�?�9/�]+q/+]�2�+q�310^]]]]]]]]]]]]]]]]]#".5322>7!"!.	��l�v>��y�p6�*>dI*��,G^B>cH*#(Ea����\��uy]����4�ؤ�ׁ4�3~ԡ��~3��:

@�
F�� �	_o ��o� ����`�@P���`p����?��p��?Oj���`p����?`7�����/?���o���???^]]]qqrrr^]]qqqrrr^]]]]qqrrr^]]]qqrr^]]]qq9/^]q�2103.53��5@F!N�� @<4�:�@r�	�	f	v	�	���iyV
�
�
�
�
T
F�
�
			� @��F0��
�
�
?

?2?39]]/^]�2/^]q839/893q310]]]]]]]]]!#33	0�������I��m�:��
�/����!<@e��������s����jz� � e u fv���~lT�eu�V��T����@;
H�Tdt]m�I��eu&V*:Jz�

 !���@E!??_�������_�@#0#`#�#�#
!P
?�?399]/^]]]]qr83/839/9910]]]]]]]]]]+]]]_]]]]]_]]_]]_]]]]]]]]]]]]'.#"'>32#.'#�$*-9( #H 7WIC$�������cIiC�	$Q�_��A8;8<>7����w:)l@7
(HZj#F"_"�"�"�""" ""+F0�`+�++���@H)"P?3�2/?3?+]/^]�2�^]]q2�10]+!.5##"&'##332>53^8EV9Rx ��;bJCdC!�.;;3O5@: ?���|EtU/4[~Ki��"LC1�:#@O��������Wgw	 H		F@[k{[����;[k����@�K[����������`DT �����DT��DT��������[D�K[��������`DT ������`DTgDT��������[D��K[��@]7K[����? ������`DT �����`P/	?3?3^]]]_]]]]qqqqqqqqrrrr^]]]qqqqqqqqr^]]]]]]qqqqqqr^]]]]]]]]qqqrrrr^]]]]]]]]qr/83�^]]qr�239=/+3310]_]]]#3654&'3�?j�M��z�7�|�Nc���^:�`�V�Qw--qV��j�J@}zG�Ge!u!�!i'U2S,9nB~B�BB*B,
(	HII,IlI|I�I_%O%p%�%%_o%4?F ???O?o??�?�?�?�?	�?�?�??���@H?*G%P*���@ H�*H�4*9 $$$9P?�22/9/]9]+]+99�9/]�/+]q99//]��22/]r3/]qr910]+]]]]]]]]]4>75.54>75+5!'>54.'.V3n�}DvW32Oa/DPLJzM�pD<c�Ed��L+Jd96r^=
~	'Fa9*YRI6bL�|Z'D`>@]@(
�}	";ZC?Q1�
6YT@O1"9XD"D>38!&'"-!
$2Jd��V��NRO��+:0s@#5u�(H(H.P ` . #F@oP���@&H2�2/2
HO_o�"P(P?�??�22/22]�2]�+]q�22]/10++]".5!#>="5>3!#32>7f;R4�n$�' 'OE5+36%�
#E;^B�H����JL��}P

���S,8 
���W;O/�@@zeujzj-z-K[k}kJZG@�1�&F�0011���@#H�1`1�1 P+P?�??�]]+q/^]q�2�]�10]]]]]]]]#".'##4>324.#"32>;?s�b>bPB�<t�ng��N�,StHEd@HPW,CdC!vŎO%5!= �W�w��JO�ۃh�s=5e�]��#7%:k�V���N9�@&�7�6�5{5j2`4DTR
b
�
3
C
���@4HHjz��
%%G((4O���@H`����H0;;���@#H�;!(H�(�(���@H��!(5P//?�39]+]+3]+q/+]+]q�2/]�10]]]++]]]]]]]]]"'>54.'.54>32.F6XC1  L|\5r_=
~	#Da?Bq^H2.Kn�aDcI6t*19�1Phlj*D`H9%;YD"D>38!&',$(3D]zQ/���lB",w$V���:/�@^�z
�
�.e.u.d*t*�*|!�!Z!j!�&l&|&J&Z&oG@�� @1�(G
�

#P+P?�?�2/]��]q��^]10]]]]]]]]]]#".54>3!#".'4.'#"32>0={�{{�|?V��~9�	#+,7+�"-YS�k=��NuL&�p��LJ�ʁ�ҊB�*cs�NJ�tc*2i�n��4a���:#c@2#
�
 H
-
�F" """$%O%�%�%%%���@HP#P?�2?�+]]]q9/]��]33/10]+]"5>3!!32>7#".5'OE5+36D��
#E+;R4�

���S,8 
�;^B�����:q@>���jzV)G@/��!�
F

0
�

�!�!p!!���@
H!P?�?3]+]]q/^]��]qr�210]]]]]#".5332>54.'3�5q�{m�h3�u�IhA!(�( ;�ښR2k�t��c��2l�xD��x)(q��U�W�R#-�@e"e!u!�!JZEU-����H���@6H@PG
$G�+H@@/P'P+P??3�2?�3/�q/]3�2�q���2/]10]++]]]]#.54>746324&#">�J��n�r�}C1g�mE`<����T�\1�WPDL��5�׌G�i�H�ύf��g�Mr�P��D��L��y�Ӂ�����XP@3�����{��T���{Zj)9	H���@c	H���tEUe&6��'7GeuV'7;[{�����D

���$���@)HF5�}K[k?	P?3?�?9]]_]]]]+]]/833//^]]q833/3910]]]_]]]]]]]]++]]]]]]]]]]]%#.#"'>323	#�����,(* "> 0F<;$�������i9W6R7�	>fK���G����W,<�@Zy
Hy��y
Hy
�
�
FFH���!�!�!o! !�!P!�!�!�!P
??3�2?33/]]qqq/^]3�2����10]+]]+]%>53#.533.W|P%�;|�����|;�&O|W�wIy[��|z�n5�k�5n�z��zZyI �S���O?�@S�!�!��wz:�:
%%*%�%�%
*��I==(.
.
3G@��A�3G?((A0A�AA���@ #H�A�AA A@A#>>8P#.
P-?3�2?3�29/9]]+]qr/]��^]]�99//9/�910]]]]]]32>54.'7#".'##".54>732>=3srfAW5=aCm�g15e�_DhM44MiC_�e51g�mD`>4VA3R9����8f�ZR�yT�p��e�ȉH$Dc@@cD$H�Ȁe¡p�Ty�RZ�f8-T{N������{&�i��&��ٴ
%+55+55������{&�i�� &���$"
%+55+55��V��&RT)@#&F#&%+5+5������&�T�@ & #
%+5+5��S���&�T�@@&7@C(%+5+5�����������&(�y�������H������H������H������H������H������H������H������H������H������H������H������H������H���@
H&���
%+55+55++++++++++++++.��O�-�@
��e%u%�%���@G
HH*
:
$4
*'Z-�(((
Z��*_+&!_(+(_??3/]�??9/3�2�2/^]�/]]�2�9/10]]++]]>32#".'7326=4&#"#!5!�!m�5��)V�\5XK@o)08!TP��3{xi!����
���]�d5 .{!eo�wz
�圜���/�&a�q@&!	%+5+5h��y�*�@������'�'�'�)�)�)�uu
�
�I�F � %
5
*:D,[t���D+%�dt��@0_�
"O&&&_"
_ 0@p���?3/]q�?�3/]9/q�/^]_]]q3/]]]q�29/]10_]]]]]]]]]]]]]]"!!32>7#"$&546$32.o�|I
��r	M��mZ�lO�(l��{����V[���.G�Df��D~�n�p��H2Ri7MO�d9m���
�e��<2[F*��]����6���|�,��4�&,��@
&%+55+55�� ��h�-���&/�@b9���
�
�
:
���
�
�
y
t/�/�/{(�(�(e��� 
H+{��*zZ0''''
$
4

���@HI$4���@'H	I
��!,Z+_!!__,_?�?�?�9/�/�2/]3q+qq+q/^]q�10]]+]]]]]]]]]]]]]#!!#"&'532>7!!24&#!!26=y�y���`1+1<NfB/#
4-*'(C~}�|>�����o���W�p@����ljU%�>k�����:i�\y������n@t��{��&6Z����@+H
	Z

@

Z__


_?�??39/�3/�/^]3�2/^]�2/+]�10]]]#!!#3!3!24&#!!26�=y�y��¿�>�V}�|>�����G���W�p@��s���T��:i�\y���./��@
��eu����@L
H%5E
Z�0Z0��������p��	_
_?2?�29/3�2]]qr/^]�/]]��310]+]]!4&#"#!5!!>32q��5tnb#�����$bpv8��?zm
�圜��
���������&���@&%
%+5+5����&�C�
��7��:&��^J@&'%+5+5��h�^@@\	Z0@���@Z@
 
�
�


_/?�3?3/]]q/^]�/]]qq�9/�10!3!3!��"����"�h�����h��R�$����@Ft��{���&		Z�P���� @Z@0���@#H�_@��
__?�?�9/]q�]+q/^]�2/]]]]qqq�2/10]]]]#!!!!24&#!!26�=y�y����!~}�|>�����o���W�p@���H:i�\y��������%���/�a�hE��@_�
����s���rfUeZja�S5E\Z���_��$4���@H$4���@H	\/__/3?�22?�]/�3+q+q/^]]]]�2/�10]]]]]]]]_]_]]]]]]%3#!#3>7!!����2��)B7,CԺ��1(.6 �����h83�����A���ݲ�3�����(G�)H@)�u�ucl|�l|�e
u
�
!(H���@#H:J)5Eu'0H(8H���@H'7G���H���@I

H H

H" (Z))4)t)�)�))!  @ H� � � D t  +  ���@3��D+t+�+�+/++"'`�/)!?33?339/]q3�29933]_]]/]83/]]]]+83/]q39/933�29/93310++++]+]+]_]]]++]]]]]]]".'#&'3332>73###R-0.�U��0�����BZLQ9�9QLZB���0��U.0-��
�U#����c{Dk��D{c1�f�#���
�{C��p�<@3�!�|�pp`eu��"e"u"|
Z
j
;K!���@zH+3Z.@%H..8Z�$�$�$_o$$pOo��0��@ -H@
H3_�)#@
H##_)_ 0p������
H?2/+]q�?�3/+9/q�9/++]/]]]]qq99//]_]q�2/+�910_]+]]]]]]]_]]_]]]".'732>54&+532654.#"'>32mv��^$�B[yPMzU-��GG��'HhAMrS8�!`��il�x@(Ih?CuW3E��2[�OM7bK,%Ed@�v�w{7V;(DZ1=RW-5`�SDkP7
2RsI_�q>��{@���������@A	H		H\)		@P�����0�\/		?22/?33/]/^]�2q/]]]q3q�10++]]]]333#46767���ު���d3b'.(���'Y&,,�Z���:&���J@&!
%+5+5����@hTt�T�zYi�
H:dt�P5E	 	`	�			
Z 


�
�
/

/`/
?2?39/]�29]/]q83/]�29/]893310]]]]+]]]]]]32>73###��9QLZB���0��U.0-����D{c1�f�#���
�{�����@#��u�U��s�Ue�����@.H%5jz�Z @�_$4���@HI$4���@H	I�__	?�?�?]/3q+qq+q/^]]]q�10]]]+]]]]]]]]!#"&'532>7!#�^1+1<NfB/#
4-*'(C������ljU%�>k��������0��� �+��a����2����n�X�	���H	����H	����H	����H	����H	����H	����H	����H	����H	@		H++++++++++�����3��h��y�&��.��77����@o��Zjz)Ue�F��dtVy���TdF��q�Vf�:J �
?Oo���@_?�2?3/99/8]22/8]39910]]]]]]]]]]]]]]]]]]]]]]#".'732>?3	3�/QZmK B@:Q#S0%:59&1������>X�R(
�%4ZDY���v����(3�@\FVfIYi;3;5+&+5'&')ZZ#Z.�O  5p5�5�5O55$/`

"1`

??99//3�23�2]]]q/]]]33�22����10]]]]]]]]+#5#".54>;53324&+32>%;#"�@�Ņ(�(�Ń@C�ɇ��ʅC���
\�Y+�W+Y�\
	���i��T��T��iq��H��H��u���5b�XX�b5���.+�;��h��J@/\Z�@pZ@ 
�
		_/?�2/?3/]/^]�/]]q�2/�10%#!3!3Ŵ����������������@F�(
H 
H*:JZ��0@�po@Z�@  ���@H_/?3/?9/]3�2+]q/^]]�/]]]]qqq3�10]++]#".5332>73#�$^ks8r�l4���5nh\#���/\�YR��{l
����r@LZZ$���{dZD��
�
�
�
p
@

	_?�2/?33]]_]]]]]/^]�/]]]]]qq�9/�1033!3!3����������h/�}@T���{T\ZZ;K[{��������oZ 
	_/?�2/2/?33/]�/^]]]]_]]q9/��2/�]]]10%#!3!3!3/��-�ӺҺ��������.��d@Dt��{���&@Z/�� @Z ___?�?�9/�/]�2�/^]]]q�]10]]]]!2#!!5!4&#!!26�B}�|>=y�y��5�s����3��-:i�ZW�p@�y����m��@Lt��{���&Z���@PpZ�� @	Z@����@H_		_?�3/?3/9/�+]/^]�2/]]]q�/]]�10]]]]#!3!24&#!!263�=y�y���~}�|>�����o�����W�p@���:i�\y����������k@Lt��{���&Z��� @`	Z�@��_		_?�?9/�]]/^]�2/]]]q�10]]]]#!3!24&#!!26�=y�y���~}�|>�����o���W�p@���:i�`y���i��y�*�@Qs�s�es�es�eee�jZ!�!
  Z 
(($$&[ #p#�#�##@##���@?
H#P`�� #_&�&&&	_ 0@p���O_	?�3/]?3/]q�9/q�/]]q3/+]]]q�39/10]]]]]]]]]]]]]]"'6$32#".'732>7!5!.�X�fE�G.��[V����v��q)�Qm�Wm��M	�r�
I|��*F[2<��e��������m6c�SN7iS2H��p�n�~D�����.�@?���yv#v'J�:z5"u"*,:,z,%(5(u(�0[�������@6HZ

@

%[0
p

 __P��

*_?�??9/^]]q�?�/]3�/^]�2/+]q�]10]]]]]]]]]]]#".'!#3!>324.#"32>�Z�����Z�ٿ�*
c�蒟��W�<u�qt�u:<u�ry�r6ǥ��hc����s�����Vf����ДPP��әUV��` ��@����u�CS���@!$H���dtVCS���@u!$H��}�^nI,<�hH^nI,<Z
Z�����@ _/_o�_?2/?�9/]�2/]]3/^]]]�9/�39310]]]+_]_]]]_]]]+_q]]]+q]]]]3.54>3!#!3!!"`�[�T'B~�x���I��%MwS�;LxT,_Nl�C^�h7�I���9bH)?^��W��sNDx��?�8�@E�,z-�-fv�TT[Z#z#�#Z/j/�/{.�.I.i.**G@��:�0:�::���@#H4
G ���@#H4P*0)@)�))P?�/]q39/�2/+]�9+]q�]q�2/10]]]]]]]]]]4.#"32>2#".54>7>7>�'IhAErQ,,Mi>EpN*����q�{A:�Γ9d[V+V�[N|^C,Ca~�r�[%'\�pr�Y$$X���������F�����s�!2Il�e7aH)��:$/�@*%*���@!	H�
�

&,GG@/��1�@11���@2#H1?1�1&F�0��
$Q�&�&�&&&%PP?�?�9/q�9/^]q�2]+q�]q�9/�9]10+]]]2#!32>54.+32>54&#4L�pD#<R.6_E(=m�[�C��E_;?dG��D[7fy:>hQ5Q:%	#=Z=NuN':�K,C/2F,��'=+RL��:6@#@'H0F�0��P??�/^]q�3/]+10!#����:��I:�h�:�@A� f"r�""�"yfv""b$DTI	���@*H		F@o� @�I
PP
/3?�22?�/33�33�]]�2/+�10]]]]]]]]]]]]]]]]!!#!#3>7!3B��100�F��ңw::9������ˆ2����h2��;�I��W��NHS:'a@X���"����
�
ZjzUeuJ!�!�!E��*:%	5	"� &F'�
'
���@1

''!  � � � P ?   � � � _   0  ���@@/�?���)�)�)o)�)P)?) )�)�)�)O)_)0)"���@%H%PO_O
'!?33?339/^]q3�299+33]]]]qqqqqqr/]qr83/]]]]qqqqqqr839/39/8933]�29/893]310]]]]]]]]]]]]]]"&'#.'3332>?3###SA���aB-ؼ�0A40�04A0���-Ba��A���PUBE�M\2�&2\M�BU����#1��^N7�@55&&%&�--F((3G
I@���@V$H��
�
�


9�9I@H-P���#���P#P`p�� ?2/]]q�?�3/]q9/q�9/+�]�]]q99//]+��2/�9q10]]"&'732654.#52>54&#"'>32ʧ�-�~g`q7\x@@rV1c]#F<*�>a�NU�b5,GX-8dK+1e�}�,NV^[;N-�&C4JV!7*Db?*Lh?9X=$)C]:EwW1��:�@	���������@k<H�@<Hw����i�/O	H@@
P
�
�

����������H�0��
?33?33/^]q�2]qr�]qr2�10]]]qr]+]+qrqrqr3#4>7#<�Ŭ��:��MRJe���9<9��:�����&���@&!%+5+5��:�@u��9y��
���@{H��x�
�
/OI8
y��G
6

			F0�9I  ��O0/
P?O?�?2/?3/9/^]q�2/8]]]]]qr3q/]�29/89qq3]3qq]10]]]+]]32>?3###��04A0���-Ba��A�:�&2\M�BU����#��:�@*�tUe�u&fr�dV����@=HtHF@��������@`p����6���@H����PP	??�?�/]3]]+3q3]]qr�]�10+]]+]]]]]]]]]]]!!#"&'532>7!h��*,3G^A0&#3(!"&�����ؕ]*�*^�������:@����k{d
t
���@z<H��9 <H��6V��]m};K)��Rbr4D&H@P������OH�			0	�	�		���@<H HT
@<H���@H/?O�	?33]++3?33]++3/^]q�2]]�]q2�9=/3310]]]]]]]]]]+]]+]]]]!##!>7!#4>7�����%
#�
��<=9�,:�k?�HH�?����<@<��:�@0F@O9/o������
�
�
�
�
�
�

���@1H	F�


0
�
�

P�����?
?2?39/^]qr�/]q�2+]qr�]qr^]2�10!3#!#B紴��:�6���:��V��NR��:m@PF@O9/o������	�	�	�	�	�	�	�	�	�	/	F�0��P?3?�/^]q�]]qr�]qr^]�10#!#Ǵ�/�:����I:����WMS��W���NF#�:H@+�F	�	_	0	@	/	P?�2?]]]]9/^]��]+M�10!!#!#d�����:��I����W�:\V�W>�5J_�@
�]�]�]NHH���H9���HmR3���@K
H/ 
H 6G@
KG$D��U'
HA&$Dd���4aa���@4#H�aTada�a�a@a a0aaa&-PFP1[;P
??3�2?3�2?]]]_]]]+q/^]q33�22�]�+M��10_^]+]+]+++]#"&'##467##"3234.533>3232>754.#"4.#"32>>&V�ei�*�.�o����p�-�.�re�V&��8U<<^A""@^=9V9n4X@5\F(#A\<AX5"{ЖUXd#M:�Y�6R(hZZb
).*��\+/+hYP�́p�g0+b�qs�b+.f�td�l8"`��l�]):n����:[��hf:[�I���@6HF@���@H0
�o
F

0
�

P
?2/?�2/^]�]�]+]q�2/+�10!33#!Bݴ����:�I��I���:z�:\@%l|� 
HHF@	�

�
�

�F���@	HP	
?2?9/]3�2/+��]q2�10++]32673##".5.'@/F�J��&QW]3BeE$:�n";,���)LnE���:t@4FF@4$�����
��
�
�
�
p
_

���@	HF0	P?�2?33/^]�+]]_]]]�]qqr�9/�1033!3!3������:�I��I�����hf:�@`
I�FF@kT�;k{��T�����`p_ 0@F0�	P?�22?33//^]�]]]_]]]�]]qqrr9/��2/]�1033!3!33#��������:�I��I��I���-�:q���
H���@HG@��P���@-#H�F 	 		Q���O	PQ	?�?�9/^]q�/]q��3]+q�]�10++2#!!5!32>54&+
��4h�g�g����E`;t��p��JvR,���6�1G.^W�2:i���
H���@:
HG	F@���
F	 	@		Q���O	

Q	?�3?39/^]q�/]�2�]�9/�10++2#!332>54&+3&��4h�g�g��E`;t��<�p��JvR,:�6�1G.^W�
:����:����
H���@V
HG@��//�?_�����@#'H
F	�		 	@	�		Q���O	

Q	?�?9/^]q�/]q�2+]qr�]�10++2#!332>54&+8��4h�g�U��E`;t��p��JvR,:�6�1G.^W7���N(�@Wv"vkk
 


G@	�		*�FF�@H
P���P$ `p������@"H$/��@ HP?�3/+]?3/+]�9/q�/+qr�3/��]q2�9/]10]]]]]732>7!5!.#"'>32#".7��dIgD"�c�"CgImv�
Bi�]Z��Q3r��c�j=;lh5a�R�Z�X,i[DtT05�أx͗V6\z����N'�@Uy%�%t!�!v�y���GG@�)�
F�0��P	P/#P?�??9/]�?�/^]q�2�]�9/^]3�10]]]]]#".'##33!24.#"32>���f�sA޴��!��׽#Ca=?cF%'F^8>dF&���>����:�6���~�b')c�{~�b('b��:
�@Du� H (+H�� %H4+)0
H HGF@������@[kQ/Q	?3?�9/]�2/]83�]q�39/93�10++]]q+q++]	#.5463!##";���X�v�����xk|��6��z������\]^]��W���'C�H��W��{&Hi�@
&&*(%+55+55
�W��9�@	�1 	H���@V	
H$$9F@P-�--;�
9F�0���;�;�;�;�;�;p;�;�;9(P!3P
Q����
H??+9/3�2�2?�?]]qr/^]q33�22�]q�9/10^]++]3#5353!!3>32#"&'532>54.#"����,��FTd>h�M6]H"A
$
&1
0XF@gI(������!B8'7M28e�T��>jN-�+C.�EhE#.TxK�������&�t�@&U	%+5+5W���N(�@qtt��/*?$F#F$ $$@$`$�$�$�$�$$GP��� P
/��@ H
 P## #`#p#�#�#�##����"H##?2/+]�?3/+]�9/q�/]�2/^]q�3/�9/]]10]]]]".54>32.#"!!32674��r3Q��Z]�iB
�vmIgC"��c#DgJd��	=j�V��x�؀50TtD[i,X�Z�R�a5gmCz\6��9���KV���=�&��O�Ե	@88H	���@	77H	@66H	����55H	���@	44H	@22H	����11H	���@	00H	@**H	����))H	���@	((H	@&&H	����%%H	����$$H	����##H	���@""H	@H	@

H����11H���@	()H@H����H����@H%+5+]++++55++++++++++++++++++����%{&�i��&���%+55+55����W=�M���:"-*@R��u�j9H���uj
9

H�
W
g
w
Hm H�u��6F���
H���@H"#F@			G�))���@	H)/P//���@]#H�/0/m?������� ? � j ) � 5  -Q�"�"�"""O"""	P P#Q	?�?�?�9/^]q�/3]]qq]q]]]]]qq]]+q�+]�9/]�210++]]]+q+]]+qq]]+qq]]]2#!!#"&'532>7!32>54&+M��4h�g�>�e*,3G^A0&#3(!"&��E`;t��p��JvR,����ؕ]*�*^����6�1G.^W�,:����
H���@HFG@�!�P!!���@2#H�!F @Q
P���OQ?2?�?9/^]q�3/�/]�2]+q�]�9/3�210++32#!!#3!32>54&+����4h�g�f�N�����E`;t��:�6��JvR,�:�6�E1G.^W
��)����@U	
H!F@P��+�# F�0���+�+�+�+�+�+p+�+�+#Q P����	
H?+�2?3?9/3�2]]qr/^]q33�22�]q�910^]+>32#4.#"##5353!!=FTd>h�M�0XF@gI(����,��Y7M28e�T�W�EhE#.TxK��������!B8'�����&�t?@&Q%+5+5�����'C�����W��&\��@ &
%-%+5+5��h�:}@Y	I
O
&

F@O9/o������
�F�0���
�
�
�
�
p
�
�
�
	P/�3?3/]qr/^]q��]qr^]�9/^]�1033!3!#�����:�I���h�a����/!5!5!%#"$&546$324.#"32>��,Nl_��������X\����\�A�{~�?A�{��{;��b�b���hm���
�ef����ДPP��әUV��V��N*!5#5!#".5!24.#"32>f���h���q�{A�~�u8�'KlDEoN),Mi>EpN*3�b�b���D�ӏ0F�Ҍ~�b')c�{~�b('b���7@%Z@F�@���_??3�/^]]�/]�103!#����������
�8@$I0	F�0��P??�?/^]q�3/]�10!#!3!B�٣�8:���	��&:�A�/&����03.%+5+5������&ZCH�+&����,/*%+5+5��	��&:��@/&K/2.%+5+5������&Zt&@+&]+.*%+5+5��	��&:��@
/&31.%+55+55�����{&Zi�@
+&/-*%+55+55��-)�&<�"�	&����

%+5+5���W��&\Ci� &����!$%+5+5[�Op�/�Ͱ��/�ܰͰ��015![�Р�[�Op�/�Ͱ��/�ܰͰ��015![�Р��rL+@�?O/?o��@&+H/+]q�//105!rÉ��L+@�?O/?o��@&+H/+]q�//105!É��L+@�?O/?o��@&+H/+]q�//105!É�����N���&B�B��H�/@ H@H�
����@
H
��?��/+��2+10+54>733
y-2Y��4VKB A�A��H�4���H	������H���@
H
��?��/+3+��10+#>5#53H	{-1X��5VKB A�?���H�3���H	������H���@	H��
/��/+3+��10+%#>5#53H	{-1X�35WKB A�A�~�G�/@ H@H�����@
H��?��/+��2+10+##.=AX1-{	��?�A BKV5�K�_�Y@: H H@H�
@H�
���
��
�

�?3�2/�2]/��^]]��2+�2+10++54>733!54>733�
z-1X��
y-2Y��4VKB A�AÒ4VKB A�A�K�_�k���H���@
H�
�	�����@H����@H?��
��?3�2/�2]/]3+�^]]2+����10++#>5#53#>5#53_
y-1X��	{-1X��5VKB A�AÑ5VKB A�A�K��_�h���H���@
H�
�	�����@H����@H����
/3�2/�2/]/3+�^]]2+����10++%#>5#53#>5#53_
y-1X��	{-1X�35WKB A�AÐ5WKB A�A���v��@@%�	��/o� �
���/?����/]q�33/�22/�10#53%�s��`�a�r�x�����s��V@2�		�� ���
�����?����/����/]3/33�2�2/�21053%%%%#5�h�i��h�����i��x����������x�GQ�|�I���	H
���@'	H	H	H�?�

�0/]�]]/]�]]10++++#".54>32|-Lf:9cK++Kc9:fL-�:fM,,Mf:9dJ++Jd��V@=��0`�����0@`�����/�	�/33�22/]]�/^]q�9/�10!53!53!53(������������7����+?Sg{�@v�y�f���@H`H\HV���H>���@H8H4H.���H���@HHH���@
HJ�;�1���@�v	y	1�f@&@�@�@�@@@r�c�Y�ihh&h�h�h�hvhYhFh)hh�h�h�hyhkhVh9hhh&h
h�	�"�	)IYy��"@-1H���@�H�}�}�}�}y}k}]}I};}-}	}}�}�}�}�}�}�}}}�}i}[}M}6})}}}��}�}�}�}�}�}�}{}f}K}=}	}�}�}�}�}�}k}�}]}9}+}}	}�}�}�}�}�}{}�}Y}};}K}��}�}�}�}�}{}�}f};}K}	}}�}�}�}�}�}�}i}y}]}9}I}+}}�}�}�}@��}�}{}f}I}6}	}}i�}�}�}�}�}y}k}I};}}}�}�}�}�}�}�}y}[}k}9}}+}�}�}�}�}�}y}k}9}&}	}8�}�}�}�}�}�}Y}F})}}�}�}�}�}�}�}{}d}K}?}}}�}�}�}�}�}p}_}@}}}m�T�w�^E�,�O�6'���???���?���?���^]]]]]]]_]]]qqqqqqq_qqqqqrrrrrrrrrr^]]]]]]]]]qqqqqqqqqqrrrrrrrrrrr^]]]]]]]]]]qqqqqqqqqqrrrrrrrrr^]]]]]qqqqqqqqqrrrrrrrrrr^]]]]]]]]]]]]]qqqqqqqqqqq/++^]���/^]]]]]]]qqqqqqqrr���9/]q�99//^]]8833��10++++++++++++_]]!#3%2#".54>4.#"32>2#".54>4.#"32>2#".54>4.#"32>0�����<kP..Ql>?lP..Qn�%7#&:''8%"6'<kP..Ql>?lP..Qn�%7#&:''8%"6'�<kP..Ql>?lP..Qn�%7#&:''8%"6'�!S�jf�V&%V�gj�S!��Ib:;aIGa;;a��!S�jf�V&%V�gj�S!��Ib:;aIGa;;a�!S�jf�V&%V�gj�S!��Ib:;aIGa;;aUzY�(@

H

H�/0?�/]�]�10++3U@Ğz����Uz��&V	�`]5X�Q�H@7jz�jz����O_�0@`p��/o/]�/]q���10]]%53	���P���Q�m?s����Y�R�B@0eu�eu����p���o�/o/]�/]]q���10]]%#5	53�R���Q�ot��?���G�&�@//]55]55����T
�/�//10!5!��+�u�`b�-�o���@�@
H@H??/++]8/]810!#3��q��e��!o���@	H
�$!�$��������@&

HK[@H@H/?/_]]]++]3/�]+]2�2/]�/]�210+4.#"#4.533>32
/#HU�w&1@*na�.=%d^���.("#. ly�O.�j@E
 0Pp��	\
Q


_?O?	_	??�9/^]]q��]q2�2/33�22/]q99//10!!!!##53!g��Q��n���{�$�Ձ����:P�:�@Q�:�:%5(,0
o4n+'0n

O_o:o  0Pp���0�����@FH*Q.Q+�

@*-H

 

'o/_o����4#s)4t%:::?3]]�2?3]�9/]q3�]+]2�2�2/+^]qr�/]9/33�22�9/�339910]]#!5>=#535#5354>32.#"!!!!!2>7P	9YsC�FYV����0c�gF{cG�
'5A$rp��h��h,?(�&C5%7PuM%�.�y����\�f7:V994$s}����8j\G*C0���g�/b�@G�u[�[[IkI�I�DmD[DiRiP}[k}�[kT9d969F9%a5aEaL 	H1���@^	H H
-RIQQ0HAF'K%)H" 9I8f888YHK_?K_K�KK@'H�_KK@APA`AA�AA���@HAF d����[_Hd����PXHd����FJHd����;>Hd����05Hd���@HOd_dodd����
HY���@GHAHYAN<P93,P_WPyRHRXRhRRN( P!N%!##!!�!�@	H!!_	??�99//+]]3/33�2/3]]��?�?3�99+++]++++++/]�2/+]q9///]q+]]�3/]q�33�299��2/�10]+++]]]]]]]]]]]]]]]+#!24&+326#"&5#53733#3267%#".'732>54.'.54632.#"�=y�y��+}�|>���iq��{#J0i]ho5n��)7(跪Ky\=�cX(D1">W41`M/�����-6� 9P00dR4�\�uD���=o�a���Ԓ��
{z��bNIbx�6X@=:
()
 5O;pwit&
U$6W��V�=���@DIj&:+J+z+�+$;4;D;t;�;.779-3n%n$$n@`���������@^H0Q9Q6��@'/H�-/?/?��/�(s�%|%�%%*%%ss�%	?3]]�?3]]]�9/]qr3�]+q2�2�2/+]q�3/�/3/��229/�10]]]+%267#".'#73.5467#73>32.#"!!!!�dx�
?g�ax�yE
�(x�(�I{�sa�g?
�yjDjO2�(�c�(�l	*Mr~d[DvW2M��r�.5z��C2WvD[d'W�c/%�Q�d9E����+O�@v�y��/B���@	H<H���H
���@HHHJ�II4�5�?���@>,�?@
H?"�
�� P`���&441�:�M�JD��'�?���???3���3]/]���/+�99//88��3/�3310++++++]]]#".54>32#34.#"32>3267#".54>32.#"�3WtBBsU10VuDBsU2�;����+?(*@,+?)'?,��/K6E^�-MoH^�R&;a|ADjL0	�
SKlW�}�i--h�~��g))g�����1c�N !N�b_�O""O�|K|Z2RW	6cL-Ey�`��g*(F^7HU���~�'7�@	� 5(H���H���@CH H H( (`(p(�(�((((333�##�
�
�

����"&H
���@H
O93-"-/�?9/�9/99�99]/++]�3�]2^]3/]]q�2/310]]+++++]%2673#"&=5>74>324.#">�3DExmo B"#B;bH4O5,OnB *s( '1M6Nhm�����
IK;lS1*NoEc��v&�9S64U=!$=Q.�!h���.�#7;�@|�
H=�
r
E
U
e

&
!!%!/=:I$I9.I����$�$�$/$$$
I0@P �)����$H���@H	3�9�99���@#,H98@$H@H8?3++/�+]��?33++?�/]q�22/]]]99//]]�33��2]10]]]]]]]]]]+]!#3&'.53#"&54>324.#"32>5!&�0����6����+W�X]�T'�/E-.G00C).H2�{�,,&Y'�X��R(.'b3�����ɾ[�e55d�\Qk@@kPRl??l�����z�'/�@		H�	���@HYiy%
H���@IH,)�*�@�@//**@*P**'�%o����(,�	* *P*�**%-?33�]2�2/]3�/]�]9/]�2��10++]+]+5#.'&'#367>53##5!��l����
���(���z��	���@&,7

����
3)"�����h�ool��9�@_e$e�1�11H�	�		Ht�66v68v8t7�767** )50(
 (`(p(�(/o�(("[55���@AH�5�55055[?O����o���0
'0*_)_?�?3�2/^]]q��/]]+r�99//]]33�10]]]]]]]]+]]+]]]]267>;!5>54.#"!532.54>��Z;m�c*'!G�`�Y*=t�lm�t=*Y�`���G!'*c�m;Z���V��j���6��3~��Ut�|AA|�tU��~3�6���j��VX��|H -R@2Z
@�!�!_!P!`!p!!/� /-�--'
?2]]�?�9/�/�]2]�]qq�210]".54>32!32>7.#"k�ƆE_��_pŽQ��@NX.Kt]L#H$Tm��;M\53WJ<"]��o�ՋBO�҂��-#<W8*9dL,�*" *����P��N�&y' ����?@(o/��p`@b@H<?555+]]]]]]5]]555��=��N�' �'���s"?@(o1/11��p`@�@H1?555+]]]]]]5]]555��]��N�' �'����(?@(o1/11��p`@~@H1?555+]]]]]]5]]555�����N�' �'����f/@o1/11��f@H1?555+]]5]]555�b^B@	�/�/��29910#.'5>73!�;H:�RR�:H;�'"bADp*$*pDAb"V���@
@�/��299/�105>73.'#�"bADp*$*pDAb"V�;H:�RR�:H;�#�b^B@	�/�/��29910.'3#>7!5;H:�RR�:H;�#}"bADp*$*pDAb"V���@
@�/��299/�10%>7#.'53+"bADp*$*pDAb"V�;H:�RR�:H;��b^B$@�@�/�/�299��29910#.'5>73!.'3#>7�;H:�RR�:H;�;H:�RR�:H;'"bADp*$*pDAb""bADp*$*pDAb"���&@@�@�/�299��299/�105>73.'>7#.'5�"bADp*$*pDAb""bADp*$*pDAb"�;H:�RR�:H;�;H:�RR�:H;�H�#(@# /�299����299/3�210!!5>73.'>7#.'5� �"bADp*$*pDAb""bADp*$*pDAb"hPX;H:�RR�:H;�;H:�RR�:H;8����/E�@]����z!�!DHUe���JZzJ8Z8C8H:C*:J%-5-�-�- '0'''�0�0(00F@`���@@H@HG�<G?@HOG'"P+5Q@��/+o+�++�++AP
?�/]q9/^]3��2]/+]��+]+q�33qq9/]10]]]+]]]]+]]]#".54>323>54&#"7>32.#"32>�
`��a]N#/If�S*M@2��;:7$*tCq�^)�$3@$5VC1 *A,BlS8�.hjj0�ΐM?k�K<���h?0C*:"���'X���*J7 3Tntt05[C&c�����@JZjEUe87H���@�Hf'GWi(HX;�k���4T//���� p�?�:����@`�_���� Pp_ H�?3]+?�2]]qrr^]qqr/^]3/_^]]]]qq39=/3310]]]]++]]_]]73!%.'���#��������^(RE01FR(���N��6@$ZO���Z  ��_/2?�/]qq�/]�10!#!���N��o3����N0��@	���		���@
H��r�d5EU���@J	H)9H���HI�

@

		�@ 
_	_/�9?99�9]/^]]33//3/]]99//]+]310+]+]]]]_]+]]5	5!!	!�{��B��H����Nm0,j�����e`H� @ Pp ��?�/]/]105!e�`��3��bT����@�H	9I	o����/Oo�_/Oo�
0
P
p
�
o
�

0
�

/
O
9�
�
�
�
�
�
�
p
/
O
o
�
�
��/3?9/]�]qqrr^]]qqrr/^]3/]/]q89=/]3310^]+##5!3nj�����u�N�W�]�#3Cr@K�25CEC

!'4/ /@/p//<$�77*�AA'4?

@H
P

/]3�]+q29�]2�]2/�/]�910]]]]]]#"&'#".54>32>32%"32>54..#"326],RsFa�F KTZ.EsS.,RtG^�CKT^3ErQ-��Fw83wM+G33G�]3wN+F10G/FxNN�j>��?fH'7d�YQ�h<��>fI(7e��~���(F^66\E'���(F^63]E)~�`�
�/�/�103!!�^j�8��^���#@K[K[

/�/3/�/�10]]4>32#4.#"Dz�bc�{Fg5_�NN�^4t��LL��t�b�l98l�d����N��#@
""*"%	�%�%�%�%�%%�@���HL%/%?%	%�%@��H%@��H�%%)%9%%�%�%%@��Hk%{%�%]%K%)%9%%	%�%%���Hm%%���H	%�%���H�%y%�%�%K%[%k%%���H�%�%�%}%�%k%]%&%6%F%�%�%�%�%%p%���@nwzH%@ekH�%v%�%	%%)%�%%@ORH%�JNH%)%%�%�%�%�%�%�%�%%�<?H)%9%I%%%7�%�%�%�%�%�%�%�%{%m%_%%����'+H%���@
"&H�%�%%%���@.H�%`%p%�%�%�%%%/% �F/� ���@HP@�P/�/]�/+]3/]�2/]^]]]+q_q++r_rrrrr^]]+]]]]qq++qrrr++^]]]]qqqqq+rrr+^]+]+]qqqqqq+qqrrr++^]]_]+]]]10_^]]"&'532>54>32.#"$$K>#3B'2Z}K"K=$3B'2Y|�N�%@T0�^�V(		�
(AT,�B^�V)8P,�!C@$HHAHB0	H=0	H*��г	H0���@	H	H 0	H0	H
���
H��г	
H��гH���@)	H?0P@`���-@H/E;�@-����#<H-"����H"���@H"�>@)<H>(��1 1011�@����'<H����H���@H�@)<H��@H@H/++]��+�++�+��]]��+�++�+�]/+3/]q310++++++++++++++"&'.#"5>323267"&'.#"5>323267(E�IAk-&A<82�Q(PMK%233E{4 ;=D(E�IAk-&A<82�Q(PMK%233E{4 ;=D�+!�%/

3+��Z, �&.

2*� A7$�@l5:<*

	dtP0 `o		_	o		�	�/_o�

P	�			/]]3�]22�2�22/]q3/]q_qq39����������10_]]]]##5!!5!3!!!�����7��=������X��!�l�$�ܔ���d�GP1@�		�?�?/]�/]�9/�/33/33105!5!5!d�������8��d��?$�
{���@H(H(H����@?H�
0P @`��@H�O_�@P�P�/]33�]�]/�/+3//]q310]+]+++5	5!A�Z��w��������쑑A$�
y���@H(H(H����@>H�	0P @`��@H�@P�O_�P�/]�]�]33/�/+3/]q310]+]+++75	55!AZ�����XX��u�����7�	#@i	y	iy/�/�/���10]]3	%!	���������{����R��Vd�G����/�///�107!!d��>��T"����� H	��//��/��10+#47632#"'.'&#"��TR�?K3%
!$	��V�{{?0(4
''#i���� ���H��//��/��10+3#".54>3232765"�Z(g>2%!%����}86'"%)j��%������?�33105!
�%����iH����??��103#ؑ�H�K�����"�������?�?��310!!#�(�i����n�����"������?�?3��105!#
(�%�����%�H"�����??���3103!!�����H�n���%H"������?�?3��105!3
��%��������H'�	�������??�?��23103!!#����i�H�n��n����H'��������?�??3��3105!3#
���%���K�������(�	������?�2?3��3105!!#
��i�%���n���%�H(�	������?�3?3��3105!3!
���%���n������H3�
�@	
�����?3�2??3�2�23105!3!!#
����i�%���n��n���q�j%�	������?�?�3233105!5!
��A�ّ��������H*A	���?2?3����103#3#ّ�h��H�K	��K����j	1���
	�������?�?�?��23310!!!!#�(�i��i�j�ב�"����	3��	
�
��	��?3?�2����310!###���ב���n��n#���j?�
	A	������?�?3?�����3310!!#!!#����ht��j���o��"����j	1�	��

����	���?�?�?33��3105!5!5!#
��i(�q�ב�)�������	4A		
�

����?�2?33����105!###
ܑב%�����n������j?�		A	�
�����?�?3?�����3310#!5#!5!ґ��t��tj�)F��)ޑ�q�H	1��	
	������??�?���233103!!!!����i���H�"�ב�%�H	4�A	

���?3?3�����3103!!33A��$��H�n�#�n�q�H?�

A������?2?�?�����33103!!3!!ّK�$h��H����"���qH	2�	�

����	���?�?�?33�2�105!5!5!3
��i��q�ב�)��%�H	4A	
�

����?�3?33����10!5!333�$�ב%���n���q�H?A	�		��
�����?�?3?�33����10!5!3!3!5!�$K������q�F�������H6�

���	�������??�?�?��2233103!!!!#����i��i�H�"�ב�"���H8�
���������?�?3?3��2��3103!!#3#A�������H�n��n	��K���H	I�A	�����
�	�?3?3?�?����2�23310#3!!#3!!j���t������	�����"	��"�����H8�	�
�@

��������?�?�??33�22�105!5!5!3#
��i���q�ב�K������H;A

@
�����?�?3?33�2���105!3#3#
㑑h��%���K�#�K�����H	I�


A	�����	��?3?�?3?����2�233103#3!5!#!5!A�������㑑�tH�K	�����)ޑ�����j9�
����	�����?�2??�33��33105!!#5!
��i��i�q���"�h��������:�
�@	
����?�22?33����3105!!###
���ב%���n��n������jJ�
	�����	����?3?3�2?���33��3310#!5!3!!#!5j��t�t��t�A��ޑ��"ב���q�H:@	
��	�����?�3??���3333105!3!5!
����A�ّ�"�������%�H:�
	�@	�	���?�33?33����3105!333!
�ב�%���n��n���q�HL@		A�
�	��
��?3?3�2?�����3333103!!3!5!5!A���������H�"�o�����������HL�
	���@����	���
�?3�2?3�2??33�22�2233105!5!5!3!!!!#
��i����i��i�q�ב�"�ב�"������HM�	���@	
�	
����?3?33�22?33�2�2�2�23103!!###!5!33A���ב���H�n��n��n����n�����H]���		������	��
��?3?3�2?3?3�2�2�233�2�233103!!#!5!3!!#3!5!A��ב�t�t��������H�"���ޑ��"	����m�H��/?3310!!��U�m����m��?/3310!!��U�������H���??3310!!��U���	����H���??/310!!�*��	����H���??/310!!��*��	�*g����#'+/37;?CGKOSW[_cgkosw{����������1����������mUE-
y�@
xlTD,xeM5��@
�dL4�qYA)}�@
|pX@(|aQ9	��@
�`P8�u]=%��@!�t\<$�x�|����|�x���������iI1!��@hH0 
����������gck��h�d`h_[W��T\XT�SOK��H�PLHC?G��D@<D�;73��0�840+'/��,($,�#�� � �������{�@<x�|xTHD0, hTHD0, xx ,0DHTh
l����;���wso��tpl/33�22/_]]3339//////////]]]]]]33�22333�22233�22333�22233�22333�22233�22333�22233�22333�222�222/_^]33333�222223/333339/////33333�2222233333�2222233333�2222233333�2222233333�22222�22222103#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#%3#%3#3#3#3#3#3#3#ghh�hh�gg��hh�hh�hh�gg�`hh�bhh
hh�ahh�ahh�hh�hh�gg�hh�ahh�ahh�hh�hh�gg��hh�hh�hh�gg�`hh�bhh�hh�hh�hh��hh�hh�hh��hh�hh�gg�hhhhhhhhhhhh"bbbbba```````````c```````````c``````aaaaab^^^^^baaaaa``````�bbbbb#`````��b��`��`��a��a�`T����#'+/37;?CGKOSW[_cgkosw{��������������������������������#'+/37;?CGKO3#73#73#73#73#73#3#73#73#73#73#73#3#73#73#73#73#73#3#73#73#73#73#73#3#73#73#73#73#73#3#%3#73#73#73#%3#3#'3#'3#'3#'3#'3#3#73#73#73#73#73#3#'3#'3#'3#'3#'3#3#73#73#73#73#73#3#73#73#73#73#73#3#73#73#73#73#73#3#3#3#3#3#3#3#3#3#3#3#3#ghh�hh�hh�hh�hh�hh��gg�gg�gg�hh�hh�gg�Zhh�hh�hh�hh�hh�hh��gg�gg�gg�hh�hh�gg�Zhh�hh�hh�hh�hh�hh��gg�gg�hh�hh�gg��gg�hh�hh�hh�hh�hh�hhggg�gg�gg�hh�hh�ggghh�hh�hh�hh�hh�hhggg�gg�gg�hh�hh�gg��gg�gg�gg�hh�hh�gg�Zhh�hh�hh�hh�hh�hh�hhgggghhgggghhgggghhgggghhgggggghh"bbbbbbbbbbba```````````````````````c```````````````````````c````````````aaaaaaaaaaab^^^^^^^^^^^baaaaaaaaaaa````````````�bbbbbbbbbbb#```````````��ba```c```c``ab^ba``�b#`C����IMQUY]aeimquy}��������������������������������	
!%)-159=AEIMQ!35#35#35#35#35#353353353353353353353#3#3#3#3#3#335335335335#3'#3'#3'#335335335335#373533533535!355#%355##5##5##5#353353353355##5##5##5#35335335335#3'#3'#3'#3#3'#3'#3'#335335#3'#335335#3735355#5##5#353355##5#35335#3'#3#3'#3�+jjjjjjjjjjjkjkjkkkkkjkjkkkkkkkkkkkkk��kjkjkkkkkk�kk�jj�jj�kjkjkkk��jjkjkkkk��k?k�k�kkkkkkjkjkkjkjkkkkkkkkjkjkkjkjkkkkkk�kk�jj�jjkk�kk�kk�kk�kjkjjj�jj�kjkkjjkj�Vk�k�jkjkkjkjjkjkkjkjjj�jjkkk�kk��"a"a#`!b!b!`````````````�b��`��`��`��^��`j````````�bbbbbbba````````�````````�``````````��aaaaaaaab^^^^^^^^��aaaaaaaa`````````�bbbbbbb"bbbbbbb��````�bbba````�````�`````�aaaab^^^^��aaaa`````�bbb"bbb{uZT!!{�!T�!���/���/���10!!!�7L1�7}��1mi{@
0/�/^]]�10!!i���mi{"@0/���/^]]���10!!!i��L����Pb��h!!�h���L�!	�XV��R���Z�	��7��������L�	L������R����Z�Z�7��9e��	%@�	/3?3/]3/]310]!#	3	#R�7�R���b`��15���>��.)'.@D%T%K![!K[DT
#/���/���10]]]]4>32#".732>54.#"�Fz�^^�{GG{�^^�zFV9b�LL�c::c�LL�b9d^�{GG{�^^�zFFz�^L�c99c�LL�c::c���#��/]�/�102#"'&5467>76jnk5R������S4l�9R46n9������:m64R9)���	/���/���103!32>54.#")��Ex�[[�xEEx�[[�xE��}A[�xEEx�[[�xEEx�)��+"@"	'/�����/�����103!4>32#".'32>54.#")��Q:c�KK�c::c�KK�c:MEx�[[�xEEx�[[�xE��}AK�c::c�KK�c::c�K[�xEEx�[[�xEEx�s�cu"�/���/���10#"'.5476324'&#"3276c%%%V3eK#%HJfgGJL33FF3331HH13}5V%#%H%V5fHJJGgF3333FE6116���y�!-9D�@] $ t $t+{+{D"(?4.(.(.1%+7+>:h:Y:G:::<A+_+o+A@	H+A+A

/��]�99//+^]�3]]]]33�2/��]�99//�3�310]]]]#"'&54676324'&#"3276#"&54632#"&54632327#"&'y������ZZ����ZZZ���ڗ����٘��Z.  --  .�,  //  ,��L��L>b�^�0H��������[��[׀ٙ����ؙ���W ..  --  ..  --����#�_[����)4`@7*/$'!04h4Y4K4=442-_oO-_---
/�99//]^]�3]]]]33�2/�99//�3�310#"'&54676324&#"326%4&#"326327'#"'�������ZZ����ZZ�.  --  .�,  //  ,��0�^�b>L��LH��������[��[� --  ..  --  ..��[_�#��F�s;3F��/��@
H4.4$w##���@MHH;;	H;/4#4;B�
�
p
?
 

9+>���0/43?3O33/^]��]]]]�/��]]]]�10]]]]+]]++]]]+373#'#5.''7.'#5367'7>7"327654'.�B 965�-�-,��,(�1�7:"B?n0�+�(.��P�(�9p6Eu0bb0uE�`cc1u;� �-�;q9><n3�+� 	��	.#�-�3o?>�_�1�(,=20d��b2/aa��c02�P&�/b@>+�+�++"�"�""P'�''�@%(H
/�+�^]2�qr/]3�2/�qr�]�qr9/3�210.'&547>32!!#!5!"327654&'&�7Z#GS,e<vSVHHj�J��#S>>SW;=>B.*PlzS++VSzmQR�F��F�;G,+G>>=T,G;Q���AQF@(1A;NN?  33FF;A1?J7�77B�??/^]�]��]�99/�r�]�]�r9910.'.'.547>323267632#"'.'#"'&547632"327654'&�6%(
 ? .@$
		�TVWvvWTTUzGS�Z>==@XY<>><
			"O-@"'*R*�Qm}VXTTuuWV+ >=X[===>ZW>>;�/(@& 
0
`
p

"@H"O_/]//+3/]/10#"'!727>'#"'&547>7>76 (_E�#%?BX�c$&����}V+B,-�SZB?N9En&8�6_,+i?~BCF_?B��WVc	%%1E[wK`_B?[J;*U/;q9S<�K/@9M?4=C
/)//99//]923/]�10)7>7>7654&5#"&'&547632.'.547>3267>32#"&'.'F��Tl)@4:Z+X-;a)OII]P3N(a<tPPET3V$IPPp>�2+C.=�#!K2dmy;*&StsOP"4&sN&(PNmVb(%)LtvSP<3=-Q}.-L'f��Zy'&@)@Pp�///]�10^]].'.'.'.547632>32b*gL8E+%DFfbN/"�X2U#F)N<Kl ,8e02�fL]Aj8gGFHP6wu$"F^VX-wK`�76nB�����0K/]/]10.'.'.'>7>-qEEt/'xSEj(
#&b<^Q2�P;`�N�]]�5(�o]�H: 9�Pwc;�kM��;�!0@!
@O_o�!

//��]9/9/��]��2103#>54&'&'#"&547632�L�3:0./9@%%Hl9:<?P,.�d�E�UN�;A|;<c(Q	�?b&K6.I<<����"l@=# @�$@o�			#V#C##$6$%$$$$"&!X!!%"
/�/�/33]33^]]]]33]]3/]3/]�]�2��310%#"&547632%#"&547632�$&%X3999>Q0*��%#Jj9:;<T--��?e#%'6/L9;�[��>b&J5-L9<�f����"&#"&547632#"&547632%%5�$&%X3999>Q0*��%#Jj9:;<T--���&��D?e#%'6/L9;���v>b&J5-L9<�g�u�v�#d�C

���@;H#F/ � �  O o �  O_F�o"SPP ?3?3�2?�?�?/]]�2/]/]q3�210+]##5354>32.#"3533i���;fQ E-(3ӆ�����I��z;eK+�)<'a�i����:��w�Z�C

���@5HF/��Oo�O_F�oPP?3?3�2?�?/]]�2/]/]q�10+]##5354>32.#"33i���;fQ E-(3Ӈ���I��z;eK+�)<'a��I�4w�N�K���	H���@(	H�_�?O� P`p/]�/]�/�]q�9/10++#"&'532>54&#*732�AhK-1%)8#=H>4T< �)C0b%(`)?������w8@o_]]]5�NH��
K����	H���@	H��	� `������@
!H			/]�+]q�/3��10++#>5#53H,(u-1X�Wk/0V.�3��
l@JV
V	y���@<H@H�0@��_o���??39/]q33�2/]qr33�2/++]310]rr#5!533!<6'��j��o��岲o-��q
%'#��!&&"5(|�&�@%%$5$E$	
H""�
������@5H� ` `���""��DTd5&��?�2]]]?�9/�/]q�2/+]33_q�22_]]10+]#".'732654&#"#!!>32|&IlFBeJ,	�'6$GVQK3M�!��r[6CgE$CAhJ(!;Q0/$U]QU%�q�!'Gf+4x�6@!���`���`�
��??�2/]]q3/]9/�10#4>7!5!x@jM+�/Pk<�4Ma���lf���Xq-(��'Q��D��H@���@RH/	
H+	
HL7G
�H===��2�22�FGGG(� ` `��L8�#�B�#�-�?�?�]]9/�99/]q�2/q�/]]�3/q�99]10++++4.#"32>4.#"32>7#".54>75.54>32�5)'5
	6-06
#=1-;$$=..="�#IqMMqJ#/= $7&#FhDHiE!&8$$?.�0""0/''0��4()5#;**<6[B%%BZ6.H4 %4@#-O;"#<O,#?4%4H�����>@)u�u�I
�
�

I@�	�/?�/]��2/��]�10]]".'332673To�Q �h]]h� Q��9[s9g``g9s[9|E�
$@��	�P`��	?�]q�/3��10#>5#53E,(u-1X�Wu/0`.���K 
/@	��
�_o��P

?


/]q�]q�/��21046733#�,(u-1X�%Wu/0`.�j��0@u��@@@H�_@	H/+]�+/�]]10]%53��������H���4@"z�@�@O�@H�_@	H/+]�+/]�]10]573H���������	A@v	�	x�����@H�@@H�_@	H/+]3�+�/�+]10]]#'##573�i��h��������	A@v�x�����@H@@H��_@	H/+]��+2/�+]10]]#'53373���h��i����-�Z�#@���_@	H/+]3�2/���1053!53���ӥ���������D�Yi���@"	H	H@	�@��_@	H/+]2���3/�]]10++]".#"#>3232673�*TNG76	[-J;,TNE67\+J�%-%>9-_N2%-%?8,_N3 ��X@<����f
bb
��@@	H@H�_@	H/+]2�+2/+��^]q�10]]]]]5733573 ����������������7@ d
@@H�	_@	H/+]��+2/�^]10^]]".'3326734JtT2um[[ku	2St�)EZ15<=41ZE)P�		B$��7��<��V��_��b��i��q��r��x��$��$7�h$9�h$:��$<�h$Y��$Z��$\��$�h)�)�)$��/��/7�h/9�h/:�h/<�h/\��/��3��3��3��3$�h57��59��5:��5<��7��7�7��7�7�7�7$�h72��7D�7F�7H�7L��7R�7U��7V�7X��7Z��7\��9�D9��9�D9��9��9$�h9D�h9H��9L��9R��9U��9X��9\��:��:��:��:��:��:$��:D��:H��:R��:U��:X��:\��<��<��<�D<��<��<�{<$�h<D�h<H�D<L��<R�D<S�h<T�D<X��<Y��II��I%U��U��ULY�hY�hZ��Z��\�h\�hVf��Vm��Vq�hVr�hVs��Vx�hV���V���V���[r��\^�\_�h\b�h\f��\i�h\m��\s��\v��\{�h\|��\~�F\��h\���\��h\��h\��F\��F\��F\�b\��F]r��]x��_��_f��_m��_q�h_r�h_s��_x�h_���_���_���_�ha�a�a^�a_�Dab�Dai�Da���a�Xb��bf��bm��bq��br�hbx�hf_��fb��fi��fr��fx��hf��hm��hs��hy��h~��h���h���h���h���h���h���h���h���h���h���i��if��im��iq�hir�hix�hm_��mb��mi��mr��mx��o��o��o��o_�hob�hoi�hp���p���q��q�q�q�q�q^�q_�hqb�hqf��qi�hqm��qs��qv��qy�qz�q}�Nq~�q��Nq��q��jq���q��jq���q��q��q��Pq��q���q��jq��q��Nq��q��Nr��r��r�Fr��r��r��r^�r_�hrb�hrf��ri�hrm��rs��rv��r{�hr|��r~�Fr���r��hr���r��hr��hr��Fr��Fr��Fr�yr��Fs_��sr��sx��uy��u~��u���u���u���u���u���u���u���u���vr��vx��x��x^�x_�hxb�hxf��xi�hxm��xs��xv��x{�hx|��x~�Fx��hx���x��hx��hx��Fx��Fx��Fx�bx��F�����������������y�w�{���~�w�������������������������������w�������w�������w���w���w���w�����y���~�����������������������������������������������y���~�����������������������������������������������y���~���������������������������������������������������y���~�������������������������������������������y���~���������������������������������������������������y���~�������������������������������������������������������������������������������������������l�w�{�w�����`��w��D������-���������������`����������`���������������-������������������������������������������������������������������������������������w����������������������������������������������l�w�{�w���w���w�������������������������������w��w��������������w�����w��������w��������������D����-�����������������������-��-����������������������������������������������������������������������������������������������������������������������������������}��}�������{�����w���w������������������������������������������������`������������������������������������������������������������-��������l���{������������������������������������������������������`����������������������������������������l���{�����w�������������������������������������`��w�����������������w��������w��������w������������w���������������������������������������������������������������-����-�����w�����������������������������������3������`��������`���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������-����������������������������������-��-�����������������������������������������������������������������������������������������������������D�������������3��3�{�����������������������������������������������������������������������������������������������������������`��w���������������������������������������������3��3�����������l�`�{�`�������V������`��`��`����V`�Cc��
7z<�	].�
o�>�	�	#	S	4k	�	�		�F	(�		�	8#	\z	
�	|WCopyright (c) 2007 Red Hat, Inc. All rights reserved. LIBERATION is a trademark of Red Hat, Inc.Copyright (c) 2007 Red Hat, Inc. All rights reserved. LIBERATION is a trademark of Red Hat, Inc.Liberation SansLiberation SansRegularRegularAscender - Liberation SansAscender - Liberation SansLiberation SansLiberation SansVersion 1.07.4Version 1.07.4LiberationSansLiberationSansLiberation is a trademark of Red Hat, Inc. registered in U.S. Patent and Trademark Office and certain other jurisdictions.Liberation is a trademark of Red Hat, Inc. registered in U.S. Patent and Trademark Office and certain other jurisdictions.Ascender CorporationAscender CorporationSteve MattesonSteve Mattesonhttp://www.ascendercorp.com/http://www.ascendercorp.com/http://www.ascendercorp.com/typedesigners.htmlhttp://www.ascendercorp.com/typedesigners.htmlLicensed under the Liberation Fonts license, see https://fedoraproject.org/wiki/Licensing/LiberationFontLicenseLicensed under the Liberation Fonts license, see https://fedoraproject.org/wiki/Licensing/LiberationFontLicensehttps://fedoraproject.org/wiki/Licensing/LiberationFontLicensehttps://fedoraproject.org/wiki/Licensing/LiberationFontLicense����	

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a�������������������	����������bc�d�e�������f����g�����h���jikmln�oqprsutvw�xzy{}|��~�����

��� !"��#$%&'()*+,-./012��3456789:;<=>?@A��BCDEFGHIJKLMNOP��QRSTUVWXYZ����[\]^_`abcdefghijklmnop�qrst��u�vwxyz{|}~��������������������������������������������������������������������������������������������������������������������������������������������	

 !"#$%&'()*+,-./012��34���5��������67��89:;�<=>?@A�BCDEFGHIJKLMN�O�����PQ���R��STUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~������������������������������������������������������uni00A0uni00ADuni037Euni00B2uni00B3uni00B5uni2219uni00B9AmacronamacronAbreveabreveAogonekaogonekCcircumflexccircumflex
Cdotaccent
cdotaccentDcarondcaronDcroatEmacronemacronEbreveebreve
Edotaccent
edotaccentEogonekeogonekEcaronecaronGcircumflexgcircumflex
Gdotaccent
gdotaccentGcommaaccentgcommaaccentHcircumflexhcircumflexHbarhbarItildeitildeImacronimacronIbreveibreveIogonekiogonekIJijJcircumflexjcircumflexKcommaaccentkcommaaccentkgreenlandicLacutelacuteLcommaaccentlcommaaccentLcaronlcaronLdotldotNacutenacuteNcommaaccentncommaaccentNcaronncaronnapostropheEngengOmacronomacronObreveobreve
Ohungarumlaut
ohungarumlautRacuteracuteRcommaaccentrcommaaccentRcaronrcaronSacutesacuteScircumflexscircumflexTcommaaccenttcommaaccentTcarontcaronTbartbarUtildeutildeUmacronumacronUbreveubreveUringuring
Uhungarumlaut
uhungarumlautUogonekuogonekWcircumflexwcircumflexYcircumflexycircumflexZacutezacute
Zdotaccent
zdotaccentlongs
Aringacute
aringacuteAEacuteaeacuteOslashacuteoslashacuteScommaaccentscommaaccentuni021Auni021Buni02C9tonos
dieresistonos
Alphatonos	anoteleiaEpsilontonosEtatonos	IotatonosOmicrontonosUpsilontonos
OmegatonosiotadieresistonosAlphaBetaGammaEpsilonZetaEtaThetaIotaKappaLambdaMuNuXiOmicronPiRhoSigmaTauUpsilonPhiChiPsiIotadieresisUpsilondieresis
alphatonosepsilontonosetatonos	iotatonosupsilondieresistonosalphabetagammadeltaepsilonzetaetathetaiotakappalambdanuxiomicronrhosigma1sigmatauupsilonphichipsiomegaiotadieresisupsilondieresisomicrontonosupsilontonos
omegatonosuni0400	afii10023	afii10051	afii10052	afii10053	afii10054	afii10055	afii10056	afii10057	afii10058	afii10059	afii10060	afii10061uni040D	afii10062	afii10145	afii10017	afii10018	afii10019	afii10020	afii10021	afii10022	afii10024	afii10025	afii10026	afii10027	afii10028	afii10029	afii10030	afii10031	afii10032	afii10033	afii10034	afii10035	afii10036	afii10037	afii10038	afii10039	afii10040	afii10041	afii10042	afii10043	afii10044	afii10045	afii10046	afii10047	afii10048	afii10049	afii10065	afii10066	afii10067	afii10068	afii10069	afii10070	afii10072	afii10073	afii10074	afii10075	afii10076	afii10077	afii10078	afii10079	afii10080	afii10081	afii10082	afii10083	afii10084	afii10085	afii10086	afii10087	afii10088	afii10089	afii10090	afii10091	afii10092	afii10093	afii10094	afii10095	afii10096	afii10097uni0450	afii10071	afii10099	afii10100	afii10101	afii10102	afii10103	afii10104	afii10105	afii10106	afii10107	afii10108	afii10109uni045D	afii10110	afii10193	afii10147	afii10195	afii10050	afii10098WgravewgraveWacutewacute	Wdieresis	wdieresisYgraveygraveuni2010uni2011	afii00208
underscoredbl
quotereversedminutesecond	exclamdbluni203Euni2215uni207FlirapesetaEuro	afii61248	afii61289	afii61352uni2126	estimated	oneeighththreeeighthsfiveeighthsseveneighths	arrowleftarrowup
arrowright	arrowdown	arrowboth	arrowupdnarrowupdnbseuni2206
orthogonalintersectionequivalencehouse
revlogicalnot
integraltp
integralbtSF100000SF110000SF010000SF030000SF020000SF040000SF080000SF090000SF060000SF070000SF050000SF430000SF240000SF510000SF520000SF390000SF220000SF210000SF250000SF500000SF490000SF380000SF280000SF270000SF260000SF360000SF370000SF420000SF190000SF200000SF230000SF470000SF480000SF410000SF450000SF460000SF400000SF540000SF530000SF440000upblockdnblockblocklfblockrtblockltshadeshadedkshade	filledboxH22073H18543H18551
filledrecttriaguptriagrttriagdntriaglfcircleH18533	invbullet	invcircle
openbullet	smilefaceinvsmilefacesunfemalemalespadeclubheartdiamondmusicalnotemusicalnotedbluni266CuniFB01uniFB02uniF005middotuniF004uni2074uni2075uni2077uni2078glyph571glyph572glyph573glyph574glyph575glyph576glyph577glyph578glyph579glyph580glyph581���
LNDFLTcyrl$grek.latn8��������
`nDFLTcyrl&grek>latnJ��MKD SRB ������kernhh�*8Vhz��,jt������Ln����&0�$2\��J���������6LRX��.����		&	4	Z	�	�
(
�@b�����8J����
$
6
d
�
�
�
�
�
�&h������ >DR
$��7��<��V��_��b��i��q��r��x��	��7�h9�h:��<�hY��Z��\���h��$����7�h9�h:�h<�h\����������$�h7��9��:��<����������$�h2��D�F�H�L��R�U��V�X��Z��\��
�D���D����$�hD�hH��L��R��U��X��\������������$��D��H��R��U��X��\�������D�����{$�hD�hH�DL��R�DS�hT�DX��Y��I��%����L�h�h����	f��m��q�hr�hs��x�h���������r��^�_�hb�hf��i�hm��s��v��{�h|��~�F��h�����h��h��F��F��F�b��Fr��x����f��m��q�hr�hs��x�h����������h��^�_�Db�Di�D����X��f��m��q��r�hx�h_��b��i��r��x��f��m��s��y��~����������������������������������f��m��q�hr�hx�h_��b��i��r��x��������_�hb�hi�h������!������^�_�hb�hf��i�hm��s��v��y�z�}�N~���N����j�����j���������P�������j����N����N�����F������^�_�hb�hf��i�hm��s��v��{�h|��~�F�����h�����h��h��F��F��F�y��F_��r��x��
y��~����������������������������^�_�hb�hf��i�hm��s��v��{�h|��~�F��h�����h��h��F��F��F�b��F������������y�w{��~�w�����������������������w�����w�����w��w��w��w���
y��~�����������������������������������
y��~�����������������������������������
y��~��������������������������	y��~��������������������������������
y��~��������������������������y��~�������������������������������l�w{�w���`�w�D����-�����������`�������`���������-�������������������������������������������������������������w������������������������������l�w{�w��w��w����������������������w�w���������w���w����w���������D��-�����
�����������-�-�����������������������������������������������	������������������������������������������}�}����{����w��w�����������������������������������`������������������������������������������-����l��{��������������������������������������`����������������&��������l��{����w��������������������������`�w�����������w�����w�����w������w���������������������������������������������-��-���w��������������������������3����`�����`���������������������������������������������������������������������������������������������������������������������������
���������������������	����������������������-���������������������
�-�-�����������������������������������������������������
���������D��������3�3{��������������������������������������	��������������������������������`�w�����������������������������3�3������l�`{�`������V������`��`��`����h$)/3579:<IUYZ\V[\]_abfhimopqrsuvx������������������������������������������������������������������������ϒM�PK
!<'$z�	�	�#chrome/pdfjs/content/web/viewer.css/* Copyright 2014 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

.dialog{
  --dialog-bg-color:white;
  --dialog-border-color:white;
  --dialog-shadow:0 2px 14px 0 rgb(58 57 68 / 0.2);
  --text-primary-color:#15141a;
  --text-secondary-color:#5b5b66;
  --hover-filter:brightness(0.9);
  --focus-ring-color:#0060df;
  --focus-ring-outline:2px solid var(--focus-ring-color);
  --link-fg-color:#0060df;
  --link-hover-fg-color:#0250bb;
  --separator-color:#f0f0f4;

  --textarea-border-color:#8f8f9d;
  --textarea-bg-color:white;
  --textarea-fg-color:var(--text-secondary-color);

  --radio-bg-color:#f0f0f4;
  --radio-checked-bg-color:#fbfbfe;
  --radio-border-color:#8f8f9d;
  --radio-checked-border-color:#0060df;

  --button-secondary-bg-color:#f0f0f4;
  --button-secondary-fg-color:var(--text-primary-color);
  --button-secondary-border-color:var(--button-secondary-bg-color);
  --button-secondary-hover-bg-color:var(--button-secondary-bg-color);
  --button-secondary-hover-fg-color:var(--button-secondary-fg-color);
  --button-secondary-hover-border-color:var(--button-secondary-hover-bg-color);

  --button-primary-bg-color:#0060df;
  --button-primary-fg-color:#fbfbfe;
  --button-primary-border-color:var(--button-primary-bg-color);
  --button-primary-hover-bg-color:var(--button-primary-bg-color);
  --button-primary-hover-fg-color:var(--button-primary-fg-color);
  --button-primary-hover-border-color:var(--button-primary-hover-bg-color);

  @media (prefers-color-scheme: dark){
    --dialog-bg-color:#1c1b22;
    --dialog-border-color:#1c1b22;
    --dialog-shadow:0 2px 14px 0 #15141a;
    --text-primary-color:#fbfbfe;
    --text-secondary-color:#cfcfd8;
    --focus-ring-color:#0df;
    --hover-filter:brightness(1.4);
    --link-fg-color:#0df;
    --link-hover-fg-color:#80ebff;
    --separator-color:#52525e;

    --textarea-bg-color:#42414d;

    --radio-bg-color:#2b2a33;
    --radio-checked-bg-color:#15141a;
    --radio-checked-border-color:#0df;

    --button-secondary-bg-color:#2b2a33;
    --button-primary-bg-color:#0df;
    --button-primary-fg-color:#15141a;
  }

  @media screen and (forced-colors: active){
    --dialog-bg-color:Canvas;
    --dialog-border-color:CanvasText;
    --dialog-shadow:none;
    --text-primary-color:CanvasText;
    --text-secondary-color:CanvasText;
    --hover-filter:none;
    --focus-ring-color:ButtonBorder;
    --link-fg-color:LinkText;
    --link-hover-fg-color:LinkText;
    --separator-color:CanvasText;

    --textarea-border-color:ButtonBorder;
    --textarea-bg-color:Field;
    --textarea-fg-color:ButtonText;

    --radio-bg-color:ButtonFace;
    --radio-checked-bg-color:ButtonFace;
    --radio-border-color:ButtonText;
    --radio-checked-border-color:ButtonText;

    --button-secondary-bg-color:ButtonFace;
    --button-secondary-fg-color:ButtonText;
    --button-secondary-border-color:ButtonText;
    --button-secondary-hover-bg-color:AccentColor;
    --button-secondary-hover-fg-color:AccentColorText;

    --button-primary-bg-color:ButtonText;
    --button-primary-fg-color:ButtonFace;
    --button-primary-hover-bg-color:AccentColor;
    --button-primary-hover-fg-color:AccentColorText;
  }

  font:message-box;
  font-size:13px;
  font-weight:400;
  line-height:150%;
  border-radius:4px;
  padding:12px 16px;
  border:1px solid var(--dialog-border-color);
  background:var(--dialog-bg-color);
  color:var(--text-primary-color);
  box-shadow:var(--dialog-shadow);

  .mainContainer{
    *:focus-visible{
      outline:var(--focus-ring-outline);
      outline-offset:2px;
    }

    .title{
      display:flex;
      width:auto;
      flex-direction:column;
      justify-content:flex-end;
      align-items:flex-start;
      gap:12px;

      > span{
        font-size:13px;
        font-style:normal;
        font-weight:590;
        line-height:150%;
      }
    }

    .dialogSeparator{
      width:100%;
      height:1px;
      margin-block:4px;
      background-color:var(--separator-color);
    }

    .dialogButtonsGroup{
      display:flex;
      gap:12px;
      align-self:flex-end;
    }

    .radio{
      display:flex;
      flex-direction:column;
      align-items:flex-start;
      gap:4px;

      > .radioButton{
        display:flex;
        gap:8px;
        align-self:stretch;
        align-items:center;

        input{
          appearance:none;
          box-sizing:border-box;
          width:16px;
          height:16px;
          border-radius:50%;
          background-color:var(--radio-bg-color);
          border:1px solid var(--radio-border-color);

          &:hover{
            filter:var(--hover-filter);
          }

          &:checked{
            background-color:var(--radio-checked-bg-color);
            border:4px solid var(--radio-checked-border-color);
          }
        }
      }

      > .radioLabel{
        display:flex;
        padding-inline-start:24px;
        align-items:flex-start;
        gap:10px;
        align-self:stretch;

        > span{
          flex:1 0 0;
          font-size:11px;
          color:var(--text-secondary-color);
        }
      }
    }

    button:not(:is(.toggle-button, .closeButton)){
      border-radius:4px;
      border:1px solid;
      font:menu;
      font-weight:600;
      padding:4px 16px;
      width:auto;
      height:32px;

      &:hover{
        cursor:pointer;
        filter:var(--hover-filter);
      }

      &.secondaryButton{
        color:var(--button-secondary-fg-color);
        background-color:var(--button-secondary-bg-color);
        border-color:var(--button-secondary-border-color);

        &:hover{
          color:var(--button-secondary-hover-fg-color);
          background-color:var(--button-secondary-hover-bg-color);
          border-color:var(--button-secondary-hover-border-color);
        }
      }

      &.primaryButton{
        color:var(--button-primary-fg-color);
        background-color:var(--button-primary-bg-color);
        border-color:var(--button-primary-border-color);
        opacity:1;

        &:hover{
          color:var(--button-primary-hover-fg-color);
          background-color:var(--button-primary-hover-bg-color);
          border-color:var(--button-primary-hover-border-color);
        }
      }
    }

    a{
      color:var(--link-fg-color);

      &:hover{
        color:var(--link-hover-fg-color);
      }
    }

    textarea{
      font:inherit;
      padding:8px;
      resize:none;
      margin:0;
      box-sizing:border-box;
      border-radius:4px;
      border:1px solid var(--textarea-border-color);
      background:var(--textarea-bg-color);
      color:var(--textarea-fg-color);

      &:focus{
        outline-offset:0;
        border-color:transparent;
      }

      &:disabled{
        pointer-events:none;
        opacity:0.4;
      }
    }

    .messageBar{
      --message-bar-warning-icon:url(images/messageBar_warning.svg);
      --closing-button-icon:url(images/messageBar_closingButton.svg);

      --message-bar-bg-color:#ffebcd;
      --message-bar-fg-color:#15141a;
      --message-bar-border-color:rgb(0 0 0 / 0.08);
      --message-bar-icon-color:#cd411e;
      --message-bar-close-button-border-radius:4px;
      --message-bar-close-button-border:none;
      --message-bar-close-button-color:var(--text-primary-color);
      --message-bar-close-button-hover-bg-color:rgb(21 20 26 / 0.14);
      --message-bar-close-button-active-bg-color:rgb(21 20 26 / 0.21);
      --message-bar-close-button-focus-bg-color:rgb(21 20 26 / 0.07);
      --message-bar-close-button-color-hover:var(--text-primary-color);

      @media (prefers-color-scheme: dark){
        --message-bar-bg-color:#5a3100;
        --message-bar-fg-color:#fbfbfe;
        --message-bar-border-color:rgb(255 255 255 / 0.08);
        --message-bar-icon-color:#e49c49;
        --message-bar-close-button-hover-bg-color:rgb(251 251 254 / 0.14);
        --message-bar-close-button-active-bg-color:rgb(251 251 254 / 0.21);
        --message-bar-close-button-focus-bg-color:rgb(251 251 254 / 0.07);
      }

      @media screen and (forced-colors: active){
        --message-bar-bg-color:HighlightText;
        --message-bar-fg-color:CanvasText;
        --message-bar-border-color:CanvasText;
        --message-bar-icon-color:CanvasText;
        --message-bar-close-button-color:ButtonText;
        --message-bar-close-button-border:1px solid ButtonText;
        --message-bar-close-button-hover-bg-color:ButtonText;
        --message-bar-close-button-active-bg-color:ButtonText;
        --message-bar-close-button-focus-bg-color:ButtonText;
        --message-bar-close-button-color-hover:HighlightText;
      }

      display:flex;
      position:relative;
      padding:12px 8px 12px 0;
      flex-direction:column;
      justify-content:center;
      align-items:flex-start;
      gap:8px;
      align-self:stretch;

      border-radius:4px;
      border:1px solid var(--message-bar-border-color);
      background:var(--message-bar-bg-color);
      color:var(--message-bar-fg-color);

      > div{
        display:flex;
        padding-inline-start:16px;
        align-items:flex-start;
        gap:8px;
        align-self:stretch;

        &::before{
          content:"";
          display:inline-block;
          width:16px;
          height:16px;
          mask-image:var(--message-bar-warning-icon);
          mask-size:cover;
          background-color:var(--message-bar-icon-color);
        }

        > div{
          display:flex;
          flex-direction:column;
          align-items:flex-start;
          gap:8px;
          flex:1 0 0;

          .title{
            font-size:13px;
            font-weight:590;
          }

          .description{
            font-size:13px;
          }
        }
      }

      .closeButton{
        position:absolute;
        width:32px;
        height:32px;
        inset-inline-end:8px;
        inset-block-start:8px;
        background:none;
        border-radius:var(--message-bar-close-button-border-radius);
        border:var(--message-bar-close-button-border);

        &::before{
          content:"";
          display:inline-block;
          width:16px;
          height:16px;
          mask-image:var(--closing-button-icon);
          mask-size:cover;
          background-color:var(--message-bar-close-button-color);
        }

        &:is(:hover, :active, :focus)::before{
          background-color:var(--message-bar-close-button-color-hover);
        }

        &:hover{
          background-color:var(--message-bar-close-button-hover-bg-color);
        }

        &:active{
          background-color:var(--message-bar-close-button-active-bg-color);
        }

        &:focus{
          background-color:var(--message-bar-close-button-focus-bg-color);
        }

        > span{
          display:inline-block;
          width:0;
          height:0;
          overflow:hidden;
        }
      }
    }

    .toggler{
      display:flex;
      align-items:center;
      gap:8px;
      align-self:stretch;

      > .togglerLabel{
        user-select:none;
      }
    }
  }
}

.textLayer{
  position:absolute;
  text-align:initial;
  inset:0;
  overflow:clip;
  opacity:1;
  line-height:1;
  text-size-adjust:none;
  forced-color-adjust:none;
  transform-origin:0 0;
  caret-color:CanvasText;
  z-index:0;

  &.highlighting{
    touch-action:none;
  }

  :is(span, br){
    color:transparent;
    position:absolute;
    white-space:pre;
    cursor:text;
    transform-origin:0% 0%;
  }

  > :not(.markedContent),
  .markedContent span:not(.markedContent){
    z-index:1;
  }

  .highlight{
    --highlight-bg-color:rgb(180 0 170 / 0.25);
    --highlight-selected-bg-color:rgb(0 100 0 / 0.25);
    --highlight-backdrop-filter:none;
    --highlight-selected-backdrop-filter:none;

    @media screen and (forced-colors: active){
      --highlight-bg-color:transparent;
      --highlight-selected-bg-color:transparent;
      --highlight-backdrop-filter:var(--hcm-highlight-filter);
      --highlight-selected-backdrop-filter:var(
        --hcm-highlight-selected-filter
      );
    }

    margin:-1px;
    padding:1px;
    background-color:var(--highlight-bg-color);
    backdrop-filter:var(--highlight-backdrop-filter);
    border-radius:4px;

    &.appended{
      position:initial;
    }

    &.begin{
      border-radius:4px 0 0 4px;
    }

    &.end{
      border-radius:0 4px 4px 0;
    }

    &.middle{
      border-radius:0;
    }

    &.selected{
      background-color:var(--highlight-selected-bg-color);
      backdrop-filter:var(--highlight-selected-backdrop-filter);
    }
  }

  ::selection{
    background:color-mix(in srgb, AccentColor, transparent 75%);
  }

  .endOfContent{
    display:block;
    position:absolute;
    inset:100% 0 0;
    z-index:0;
    cursor:default;
    user-select:none;
  }

  &.selecting .endOfContent{
    top:0;
  }
}

.annotationLayer{
  --annotation-unfocused-field-background:url("data:image/svg+xml;charset=UTF-8,<svg width='1px' height='1px' xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' style='fill:rgba(0, 54, 255, 0.13);'/></svg>");
  --input-focus-border-color:Highlight;
  --input-focus-outline:1px solid Canvas;
  --input-unfocused-border-color:transparent;
  --input-disabled-border-color:transparent;
  --input-hover-border-color:black;
  --link-outline:none;

  @media screen and (forced-colors: active){
    --input-focus-border-color:CanvasText;
    --input-unfocused-border-color:ActiveText;
    --input-disabled-border-color:GrayText;
    --input-hover-border-color:Highlight;
    --link-outline:1.5px solid LinkText;

    .textWidgetAnnotation :is(input, textarea):required,
    .choiceWidgetAnnotation select:required,
    .buttonWidgetAnnotation:is(.checkBox, .radioButton) input:required{
      outline:1.5px solid selectedItem;
    }

    .linkAnnotation{
      outline:var(--link-outline);

      &:hover{
        backdrop-filter:var(--hcm-highlight-filter);
      }

      & > a:hover{
        opacity:0 !important;
        background:none !important;
        box-shadow:none;
      }
    }

    .popupAnnotation .popup{
      outline:calc(1.5px * var(--scale-factor)) solid CanvasText !important;
      background-color:ButtonFace !important;
      color:ButtonText !important;
    }

    .highlightArea:hover::after{
      position:absolute;
      top:0;
      left:0;
      width:100%;
      height:100%;
      backdrop-filter:var(--hcm-highlight-filter);
      content:"";
      pointer-events:none;
    }

    .popupAnnotation.focused .popup{
      outline:calc(3px * var(--scale-factor)) solid Highlight !important;
    }
  }

  position:absolute;
  top:0;
  left:0;
  pointer-events:none;
  transform-origin:0 0;

  &[data-main-rotation="90"] .norotate{
    transform:rotate(270deg) translateX(-100%);
  }
  &[data-main-rotation="180"] .norotate{
    transform:rotate(180deg) translate(-100%, -100%);
  }
  &[data-main-rotation="270"] .norotate{
    transform:rotate(90deg) translateY(-100%);
  }

  &.disabled{
    section,
    .popup{
      pointer-events:none;
    }
  }

  .annotationContent{
    position:absolute;
    width:100%;
    height:100%;
    pointer-events:none;

    &.freetext{
      background:transparent;
      border:none;
      inset:0;
      overflow:visible;
      white-space:nowrap;
      font:10px sans-serif;
      line-height:1.35;
      user-select:none;
    }
  }

  section{
    position:absolute;
    text-align:initial;
    pointer-events:auto;
    box-sizing:border-box;
    transform-origin:0 0;

    &:has(div.annotationContent){
      canvas.annotationContent{
        display:none;
      }
    }
  }

  .textLayer.selecting ~ & section{
    pointer-events:none;
  }

  :is(.linkAnnotation, .buttonWidgetAnnotation.pushButton) > a{
    position:absolute;
    font-size:1em;
    top:0;
    left:0;
    width:100%;
    height:100%;
  }

  :is(.linkAnnotation, .buttonWidgetAnnotation.pushButton):not(.hasBorder)
    > a:hover{
    opacity:0.2;
    background-color:rgb(255 255 0);
    box-shadow:0 2px 10px rgb(255 255 0);
  }

  .linkAnnotation.hasBorder:hover{
    background-color:rgb(255 255 0 / 0.2);
  }

  .hasBorder{
    background-size:100% 100%;
  }

  .textAnnotation img{
    position:absolute;
    cursor:pointer;
    width:100%;
    height:100%;
    top:0;
    left:0;
  }

  .textWidgetAnnotation :is(input, textarea),
  .choiceWidgetAnnotation select,
  .buttonWidgetAnnotation:is(.checkBox, .radioButton) input{
    background-image:var(--annotation-unfocused-field-background);
    border:2px solid var(--input-unfocused-border-color);
    box-sizing:border-box;
    font:calc(9px * var(--scale-factor)) sans-serif;
    height:100%;
    margin:0;
    vertical-align:top;
    width:100%;
  }

  .textWidgetAnnotation :is(input, textarea):required,
  .choiceWidgetAnnotation select:required,
  .buttonWidgetAnnotation:is(.checkBox, .radioButton) input:required{
    outline:1.5px solid red;
  }

  .choiceWidgetAnnotation select option{
    padding:0;
  }

  .buttonWidgetAnnotation.radioButton input{
    border-radius:50%;
  }

  .textWidgetAnnotation textarea{
    resize:none;
  }

  .textWidgetAnnotation :is(input, textarea)[disabled],
  .choiceWidgetAnnotation select[disabled],
  .buttonWidgetAnnotation:is(.checkBox, .radioButton) input[disabled]{
    background:none;
    border:2px solid var(--input-disabled-border-color);
    cursor:not-allowed;
  }

  .textWidgetAnnotation :is(input, textarea):hover,
  .choiceWidgetAnnotation select:hover,
  .buttonWidgetAnnotation:is(.checkBox, .radioButton) input:hover{
    border:2px solid var(--input-hover-border-color);
  }
  .textWidgetAnnotation :is(input, textarea):hover,
  .choiceWidgetAnnotation select:hover,
  .buttonWidgetAnnotation.checkBox input:hover{
    border-radius:2px;
  }

  .textWidgetAnnotation :is(input, textarea):focus,
  .choiceWidgetAnnotation select:focus{
    background:none;
    border:2px solid var(--input-focus-border-color);
    border-radius:2px;
    outline:var(--input-focus-outline);
  }

  .buttonWidgetAnnotation:is(.checkBox, .radioButton) :focus{
    background-image:none;
    background-color:transparent;
  }

  .buttonWidgetAnnotation.checkBox :focus{
    border:2px solid var(--input-focus-border-color);
    border-radius:2px;
    outline:var(--input-focus-outline);
  }

  .buttonWidgetAnnotation.radioButton :focus{
    border:2px solid var(--input-focus-border-color);
    outline:var(--input-focus-outline);
  }

  .buttonWidgetAnnotation.checkBox input:checked::before,
  .buttonWidgetAnnotation.checkBox input:checked::after,
  .buttonWidgetAnnotation.radioButton input:checked::before{
    background-color:CanvasText;
    content:"";
    display:block;
    position:absolute;
  }

  .buttonWidgetAnnotation.checkBox input:checked::before,
  .buttonWidgetAnnotation.checkBox input:checked::after{
    height:80%;
    left:45%;
    width:1px;
  }

  .buttonWidgetAnnotation.checkBox input:checked::before{
    transform:rotate(45deg);
  }

  .buttonWidgetAnnotation.checkBox input:checked::after{
    transform:rotate(-45deg);
  }

  .buttonWidgetAnnotation.radioButton input:checked::before{
    border-radius:50%;
    height:50%;
    left:25%;
    top:25%;
    width:50%;
  }

  .textWidgetAnnotation input.comb{
    font-family:monospace;
    padding-left:2px;
    padding-right:0;
  }

  .textWidgetAnnotation input.comb:focus{
    width:103%;
  }

  .buttonWidgetAnnotation:is(.checkBox, .radioButton) input{
    appearance:none;
  }

  .fileAttachmentAnnotation .popupTriggerArea{
    height:100%;
    width:100%;
  }

  .popupAnnotation{
    position:absolute;
    font-size:calc(9px * var(--scale-factor));
    pointer-events:none;
    width:max-content;
    max-width:45%;
    height:auto;
  }

  .popup{
    background-color:rgb(255 255 153);
    box-shadow:0 calc(2px * var(--scale-factor)) calc(5px * var(--scale-factor)) rgb(136 136 136);
    border-radius:calc(2px * var(--scale-factor));
    outline:1.5px solid rgb(255 255 74);
    padding:calc(6px * var(--scale-factor));
    cursor:pointer;
    font:message-box;
    white-space:normal;
    word-wrap:break-word;
    pointer-events:auto;
  }

  .popupAnnotation.focused .popup{
    outline-width:3px;
  }

  .popup *{
    font-size:calc(9px * var(--scale-factor));
  }

  .popup > .header{
    display:inline-block;
  }

  .popup > .header h1{
    display:inline;
  }

  .popup > .header .popupDate{
    display:inline-block;
    margin-left:calc(5px * var(--scale-factor));
    width:fit-content;
  }

  .popupContent{
    border-top:1px solid rgb(51 51 51);
    margin-top:calc(2px * var(--scale-factor));
    padding-top:calc(2px * var(--scale-factor));
  }

  .richText > *{
    white-space:pre-wrap;
    font-size:calc(9px * var(--scale-factor));
  }

  .popupTriggerArea{
    cursor:pointer;
  }

  section svg{
    position:absolute;
    width:100%;
    height:100%;
    top:0;
    left:0;
  }

  .annotationTextContent{
    position:absolute;
    width:100%;
    height:100%;
    opacity:0;
    color:transparent;
    user-select:none;
    pointer-events:none;

    span{
      width:100%;
      display:inline-block;
    }
  }

  svg.quadrilateralsContainer{
    contain:strict;
    width:0;
    height:0;
    position:absolute;
    top:0;
    left:0;
    z-index:-1;
  }
}

:root{
  --xfa-unfocused-field-background:url("data:image/svg+xml;charset=UTF-8,<svg width='1px' height='1px' xmlns='http://www.w3.org/2000/svg'><rect width='100%' height='100%' style='fill:rgba(0, 54, 255, 0.13);'/></svg>");
  --xfa-focus-outline:auto;
}

@media screen and (forced-colors: active){
  :root{
    --xfa-focus-outline:2px solid CanvasText;
  }
  .xfaLayer *:required{
    outline:1.5px solid selectedItem;
  }
}

.xfaLayer{
  background-color:transparent;
}

.xfaLayer .highlight{
  margin:-1px;
  padding:1px;
  background-color:rgb(239 203 237);
  border-radius:4px;
}

.xfaLayer .highlight.appended{
  position:initial;
}

.xfaLayer .highlight.begin{
  border-radius:4px 0 0 4px;
}

.xfaLayer .highlight.end{
  border-radius:0 4px 4px 0;
}

.xfaLayer .highlight.middle{
  border-radius:0;
}

.xfaLayer .highlight.selected{
  background-color:rgb(203 223 203);
}

.xfaPage{
  overflow:hidden;
  position:relative;
}

.xfaContentarea{
  position:absolute;
}

.xfaPrintOnly{
  display:none;
}

.xfaLayer{
  position:absolute;
  text-align:initial;
  top:0;
  left:0;
  transform-origin:0 0;
  line-height:1.2;
}

.xfaLayer *{
  color:inherit;
  font:inherit;
  font-style:inherit;
  font-weight:inherit;
  font-kerning:inherit;
  letter-spacing:-0.01px;
  text-align:inherit;
  text-decoration:inherit;
  box-sizing:border-box;
  background-color:transparent;
  padding:0;
  margin:0;
  pointer-events:auto;
  line-height:inherit;
}

.xfaLayer *:required{
  outline:1.5px solid red;
}

.xfaLayer div,
.xfaLayer svg,
.xfaLayer svg *{
  pointer-events:none;
}

.xfaLayer a{
  color:blue;
}

.xfaRich li{
  margin-left:3em;
}

.xfaFont{
  color:black;
  font-weight:normal;
  font-kerning:none;
  font-size:10px;
  font-style:normal;
  letter-spacing:0;
  text-decoration:none;
  vertical-align:0;
}

.xfaCaption{
  overflow:hidden;
  flex:0 0 auto;
}

.xfaCaptionForCheckButton{
  overflow:hidden;
  flex:1 1 auto;
}

.xfaLabel{
  height:100%;
  width:100%;
}

.xfaLeft{
  display:flex;
  flex-direction:row;
  align-items:center;
}

.xfaRight{
  display:flex;
  flex-direction:row-reverse;
  align-items:center;
}

:is(.xfaLeft, .xfaRight) > :is(.xfaCaption, .xfaCaptionForCheckButton){
  max-height:100%;
}

.xfaTop{
  display:flex;
  flex-direction:column;
  align-items:flex-start;
}

.xfaBottom{
  display:flex;
  flex-direction:column-reverse;
  align-items:flex-start;
}

:is(.xfaTop, .xfaBottom) > :is(.xfaCaption, .xfaCaptionForCheckButton){
  width:100%;
}

.xfaBorder{
  background-color:transparent;
  position:absolute;
  pointer-events:none;
}

.xfaWrapped{
  width:100%;
  height:100%;
}

:is(.xfaTextfield, .xfaSelect):focus{
  background-image:none;
  background-color:transparent;
  outline:var(--xfa-focus-outline);
  outline-offset:-1px;
}

:is(.xfaCheckbox, .xfaRadio):focus{
  outline:var(--xfa-focus-outline);
}

.xfaTextfield,
.xfaSelect{
  height:100%;
  width:100%;
  flex:1 1 auto;
  border:none;
  resize:none;
  background-image:var(--xfa-unfocused-field-background);
}

.xfaSelect{
  padding-inline:2px;
}

:is(.xfaTop, .xfaBottom) > :is(.xfaTextfield, .xfaSelect){
  flex:0 1 auto;
}

.xfaButton{
  cursor:pointer;
  width:100%;
  height:100%;
  border:none;
  text-align:center;
}

.xfaLink{
  width:100%;
  height:100%;
  position:absolute;
  top:0;
  left:0;
}

.xfaCheckbox,
.xfaRadio{
  width:100%;
  height:100%;
  flex:0 0 auto;
  border:none;
}

.xfaRich{
  white-space:pre-wrap;
  width:100%;
  height:100%;
}

.xfaImage{
  object-position:left top;
  object-fit:contain;
  width:100%;
  height:100%;
}

.xfaLrTb,
.xfaRlTb,
.xfaTb{
  display:flex;
  flex-direction:column;
  align-items:stretch;
}

.xfaLr{
  display:flex;
  flex-direction:row;
  align-items:stretch;
}

.xfaRl{
  display:flex;
  flex-direction:row-reverse;
  align-items:stretch;
}

.xfaTb > div{
  justify-content:left;
}

.xfaPosition{
  position:relative;
}

.xfaArea{
  position:relative;
}

.xfaValignMiddle{
  display:flex;
  align-items:center;
}

.xfaTable{
  display:flex;
  flex-direction:column;
  align-items:stretch;
}

.xfaTable .xfaRow{
  display:flex;
  flex-direction:row;
  align-items:stretch;
}

.xfaTable .xfaRlRow{
  display:flex;
  flex-direction:row-reverse;
  align-items:stretch;
  flex:1;
}

.xfaTable .xfaRlRow > div{
  flex:1;
}

:is(.xfaNonInteractive, .xfaDisabled, .xfaReadOnly) :is(input, textarea){
  background:initial;
}

@media print{
  .xfaTextfield,
  .xfaSelect{
    background:transparent;
  }

  .xfaSelect{
    appearance:none;
    text-indent:1px;
    text-overflow:"";
  }
}

.canvasWrapper{
  svg{
    transform:none;

    &[data-main-rotation="90"]{
      mask,
      use:not(.clip, .mask){
        transform:matrix(0, 1, -1, 0, 1, 0);
      }
    }

    &[data-main-rotation="180"]{
      mask,
      use:not(.clip, .mask){
        transform:matrix(-1, 0, 0, -1, 1, 1);
      }
    }

    &[data-main-rotation="270"]{
      mask,
      use:not(.clip, .mask){
        transform:matrix(0, -1, 1, 0, 0, 1);
      }
    }

    &.highlight{
      --blend-mode:multiply;

      @media screen and (forced-colors: active){
        --blend-mode:difference;
      }

      position:absolute;
      mix-blend-mode:var(--blend-mode);

      &:not(.free){
        fill-rule:evenodd;
      }
    }

    &.highlightOutline{
      position:absolute;
      mix-blend-mode:normal;
      fill-rule:evenodd;
      fill:none;

      &:not(.free){
        &.hovered:not(.selected){
          stroke:var(--hover-outline-color);
          stroke-width:var(--outline-width);
        }

        &.selected{
          .mainOutline{
            stroke:var(--outline-around-color);
            stroke-width:calc(
              var(--outline-width) + 2 * var(--outline-around-width)
            );
          }

          .secondaryOutline{
            stroke:var(--outline-color);
            stroke-width:var(--outline-width);
          }
        }
      }

      &.free{
        &.hovered:not(.selected){
          stroke:var(--hover-outline-color);
          stroke-width:calc(2 * var(--outline-width));
        }

        &.selected{
          .mainOutline{
            stroke:var(--outline-around-color);
            stroke-width:calc(
              2 * (var(--outline-width) + var(--outline-around-width))
            );
          }

          .secondaryOutline{
            stroke:var(--outline-color);
            stroke-width:calc(2 * var(--outline-width));
          }
        }
      }
    }
  }
}

.toggle-button{
  --button-background-color:#f0f0f4;
  --button-background-color-hover:#e0e0e6;
  --button-background-color-active:#cfcfd8;
  --color-accent-primary:#0060df;
  --color-accent-primary-hover:#0250bb;
  --color-accent-primary-active:#054096;
  --border-interactive-color:#8f8f9d;
  --border-radius-circle:9999px;
  --border-width:1px;
  --size-item-small:16px;
  --size-item-large:32px;
  --color-canvas:white;

  @media (prefers-color-scheme: dark){
    --button-background-color:color-mix(in srgb, currentColor 7%, transparent);
    --button-background-color-hover:color-mix(
      in srgb,
      currentColor 14%,
      transparent
    );
    --button-background-color-active:color-mix(
      in srgb,
      currentColor 21%,
      transparent
    );
    --color-accent-primary:#0df;
    --color-accent-primary-hover:#80ebff;
    --color-accent-primary-active:#aaf2ff;
    --border-interactive-color:#bfbfc9;
    --color-canvas:#1c1b22;
  }

  @media (forced-colors: active){
    --color-accent-primary:ButtonText;
    --color-accent-primary-hover:SelectedItem;
    --color-accent-primary-active:SelectedItem;
    --border-interactive-color:ButtonText;
    --button-background-color:ButtonFace;
    --border-interactive-color-hover:SelectedItem;
    --border-interactive-color-active:SelectedItem;
    --border-interactive-color-disabled:GrayText;
    --color-canvas:ButtonText;
  }

  --toggle-background-color:var(--button-background-color);
  --toggle-background-color-hover:var(--button-background-color-hover);
  --toggle-background-color-active:var(--button-background-color-active);
  --toggle-background-color-pressed:var(--color-accent-primary);
  --toggle-background-color-pressed-hover:var(--color-accent-primary-hover);
  --toggle-background-color-pressed-active:var(--color-accent-primary-active);
  --toggle-border-color:var(--border-interactive-color);
  --toggle-border-color-hover:var(--toggle-border-color);
  --toggle-border-color-active:var(--toggle-border-color);
  --toggle-border-radius:var(--border-radius-circle);
  --toggle-border-width:var(--border-width);
  --toggle-height:var(--size-item-small);
  --toggle-width:var(--size-item-large);
  --toggle-dot-background-color:var(--toggle-border-color);
  --toggle-dot-background-color-hover:var(--toggle-dot-background-color);
  --toggle-dot-background-color-active:var(--toggle-dot-background-color);
  --toggle-dot-background-color-on-pressed:var(--color-canvas);
  --toggle-dot-margin:1px;
  --toggle-dot-height:calc(
    var(--toggle-height) - 2 * var(--toggle-dot-margin) - 2 *
      var(--toggle-border-width)
  );
  --toggle-dot-width:var(--toggle-dot-height);
  --toggle-dot-transform-x:calc(
    var(--toggle-width) - 4 * var(--toggle-dot-margin) - var(--toggle-dot-width)
  );

  appearance:none;
  padding:0;
  margin:0;
  border:var(--toggle-border-width) solid var(--toggle-border-color);
  height:var(--toggle-height);
  width:var(--toggle-width);
  border-radius:var(--toggle-border-radius);
  background:var(--toggle-background-color);
  box-sizing:border-box;
  flex-shrink:0;

  &:focus-visible{
    outline:var(--focus-outline);
    outline-offset:var(--focus-outline-offset);
  }

  &:enabled:hover{
    background:var(--toggle-background-color-hover);
    border-color:var(--toggle-border-color);
  }

  &:enabled:active{
    background:var(--toggle-background-color-active);
    border-color:var(--toggle-border-color);
  }

  &[aria-pressed="true"]{
    background:var(--toggle-background-color-pressed);
    border-color:transparent;
  }

  &[aria-pressed="true"]:enabled:hover{
    background:var(--toggle-background-color-pressed-hover);
    border-color:transparent;
  }

  &[aria-pressed="true"]:enabled:active{
    background:var(--toggle-background-color-pressed-active);
    border-color:transparent;
  }

  &::before{
    display:block;
    content:"";
    background-color:var(--toggle-dot-background-color);
    height:var(--toggle-dot-height);
    width:var(--toggle-dot-width);
    margin:var(--toggle-dot-margin);
    border-radius:var(--toggle-border-radius);
    translate:0;
  }

  &[aria-pressed="true"]::before{
    translate:var(--toggle-dot-transform-x);
    background-color:var(--toggle-dot-background-color-on-pressed);
  }

  &[aria-pressed="true"]:enabled:hover::before,
  &[aria-pressed="true"]:enabled:active::before{
    background-color:var(--toggle-dot-background-color-on-pressed);
  }

    &[aria-pressed="true"]:-moz-locale-dir(rtl)::before,
  &[aria-pressed="true"]:dir(rtl)::before{
    translate:calc(-1 * var(--toggle-dot-transform-x));
  }

  @media (prefers-reduced-motion: no-preference){
    &::before{
      transition:translate 100ms;
    }
  }

  @media (prefers-contrast){
    &:enabled:hover{
      border-color:var(--toggle-border-color-hover);
    }

    &:enabled:active{
      border-color:var(--toggle-border-color-active);
    }

    &[aria-pressed="true"]:enabled{
      border-color:var(--toggle-border-color);
      position:relative;
    }

    &[aria-pressed="true"]:enabled:hover,
    &[aria-pressed="true"]:enabled:hover:active{
      border-color:var(--toggle-border-color-hover);
    }

    &[aria-pressed="true"]:enabled:active{
      background-color:var(--toggle-dot-background-color-active);
      border-color:var(--toggle-dot-background-color-hover);
    }

    &:hover::before,
    &:active::before{
      background-color:var(--toggle-dot-background-color-hover);
    }
  }

  @media (forced-colors){
    --toggle-dot-background-color:var(--color-accent-primary);
    --toggle-dot-background-color-hover:var(--color-accent-primary-hover);
    --toggle-dot-background-color-active:var(--color-accent-primary-active);
    --toggle-dot-background-color-on-pressed:var(--button-background-color);
    --toggle-background-color-disabled:var(--button-background-color-disabled);
    --toggle-border-color-hover:var(--border-interactive-color-hover);
    --toggle-border-color-active:var(--border-interactive-color-active);
    --toggle-border-color-disabled:var(--border-interactive-color-disabled);

    &[aria-pressed="true"]:enabled::after{
      border:1px solid var(--button-background-color);
      content:"";
      position:absolute;
      height:var(--toggle-height);
      width:var(--toggle-width);
      display:block;
      border-radius:var(--toggle-border-radius);
      inset:-2px;
    }

    &[aria-pressed="true"]:enabled:active::after{
      border-color:var(--toggle-border-color-active);
    }
  }
}

:root{
  --outline-width:2px;
  --outline-color:#0060df;
  --outline-around-width:1px;
  --outline-around-color:#f0f0f4;
  --hover-outline-around-color:var(--outline-around-color);
  --focus-outline:solid var(--outline-width) var(--outline-color);
  --unfocus-outline:solid var(--outline-width) transparent;
  --focus-outline-around:solid var(--outline-around-width) var(--outline-around-color);
  --hover-outline-color:#8f8f9d;
  --hover-outline:solid var(--outline-width) var(--hover-outline-color);
  --hover-outline-around:solid var(--outline-around-width) var(--hover-outline-around-color);
  --freetext-line-height:1.35;
  --freetext-padding:2px;
  --resizer-bg-color:var(--outline-color);
  --resizer-size:6px;
  --resizer-shift:calc(
    0px - (var(--outline-width) + var(--resizer-size)) / 2 -
      var(--outline-around-width)
  );
  --editorFreeText-editing-cursor:text;
  --editorInk-editing-cursor:url(images/cursor-editorInk.svg) 0 16, pointer;
  --editorHighlight-editing-cursor:url(images/cursor-editorTextHighlight.svg) 24 24, text;
  --editorFreeHighlight-editing-cursor:url(images/cursor-editorFreeHighlight.svg) 1 18, pointer;

  --new-alt-text-warning-image:url(images/altText_warning.svg);
}
.visuallyHidden{
  position:absolute;
  top:0;
  left:0;
  border:0;
  margin:0;
  padding:0;
  width:0;
  height:0;
  overflow:hidden;
  white-space:nowrap;
  font-size:0;
}

.textLayer.highlighting{
  cursor:var(--editorFreeHighlight-editing-cursor);

  &:not(.free) span{
    cursor:var(--editorHighlight-editing-cursor);
  }

  &.free span{
    cursor:var(--editorFreeHighlight-editing-cursor);
  }
}

#viewerContainer.pdfPresentationMode:fullscreen{
  .noAltTextBadge{
    display:none !important;
  }
}

@media (min-resolution: 1.1dppx){
  :root{
    --editorFreeText-editing-cursor:url(images/cursor-editorFreeText.svg) 0 16, text;
  }
}

@media screen and (forced-colors: active){
  :root{
    --outline-color:CanvasText;
    --outline-around-color:ButtonFace;
    --resizer-bg-color:ButtonText;
    --hover-outline-color:Highlight;
    --hover-outline-around-color:SelectedItemText;
  }
}

[data-editor-rotation="90"]{
  transform:rotate(90deg);
}

[data-editor-rotation="180"]{
  transform:rotate(180deg);
}

[data-editor-rotation="270"]{
  transform:rotate(270deg);
}

.annotationEditorLayer{
  background:transparent;
  position:absolute;
  inset:0;
  font-size:calc(100px * var(--scale-factor));
  transform-origin:0 0;
  cursor:auto;

  .selectedEditor{
    z-index:100000 !important;
  }

  &.drawing *{
    pointer-events:none !important;
  }
}

.annotationEditorLayer.waiting{
  content:"";
  cursor:wait;
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
}

.annotationEditorLayer.disabled{
  pointer-events:none;
}

.annotationEditorLayer.freetextEditing{
  cursor:var(--editorFreeText-editing-cursor);
}

.annotationEditorLayer.inkEditing{
  cursor:var(--editorInk-editing-cursor);
}

.annotationEditorLayer :is(.freeTextEditor, .inkEditor, .stampEditor){
  position:absolute;
  background:transparent;
  z-index:1;
  transform-origin:0 0;
  cursor:auto;
  max-width:100%;
  max-height:100%;
  border:var(--unfocus-outline);

  &.draggable.selectedEditor{
    cursor:move;
  }

  &.moving{
    touch-action:none;
  }

  &.selectedEditor{
    border:var(--focus-outline);
    outline:var(--focus-outline-around);

    &::before{
      content:"";
      position:absolute;
      inset:0;
      border:var(--focus-outline-around);
      pointer-events:none;
    }
  }

  &:hover:not(.selectedEditor){
    border:var(--hover-outline);
    outline:var(--hover-outline-around);

    &::before{
      content:"";
      position:absolute;
      inset:0;
      border:var(--focus-outline-around);
    }
  }
}

.annotationEditorLayer
  :is(.freeTextEditor, .inkEditor, .stampEditor, .highlightEditor),
.textLayer{
  .editToolbar{
    --editor-toolbar-delete-image:url(images/editor-toolbar-delete.svg);
    --editor-toolbar-bg-color:#f0f0f4;
    --editor-toolbar-highlight-image:url(images/toolbarButton-editorHighlight.svg);
    --editor-toolbar-fg-color:#2e2e56;
    --editor-toolbar-border-color:#8f8f9d;
    --editor-toolbar-hover-border-color:var(--editor-toolbar-border-color);
    --editor-toolbar-hover-bg-color:#e0e0e6;
    --editor-toolbar-hover-fg-color:var(--editor-toolbar-fg-color);
    --editor-toolbar-hover-outline:none;
    --editor-toolbar-focus-outline-color:#0060df;
    --editor-toolbar-shadow:0 2px 6px 0 rgb(58 57 68 / 0.2);
    --editor-toolbar-vert-offset:6px;
    --editor-toolbar-height:28px;
    --editor-toolbar-padding:2px;
    --alt-text-done-color:#2ac3a2;
    --alt-text-warning-color:#0090ed;
    --alt-text-hover-done-color:var(--alt-text-done-color);
    --alt-text-hover-warning-color:var(--alt-text-warning-color);

    @media (prefers-color-scheme: dark){
      --editor-toolbar-bg-color:#2b2a33;
      --editor-toolbar-fg-color:#fbfbfe;
      --editor-toolbar-hover-bg-color:#52525e;
      --editor-toolbar-focus-outline-color:#0df;
      --alt-text-done-color:#54ffbd;
      --alt-text-warning-color:#80ebff;
    }

    @media screen and (forced-colors: active){
      --editor-toolbar-bg-color:ButtonFace;
      --editor-toolbar-fg-color:ButtonText;
      --editor-toolbar-border-color:ButtonText;
      --editor-toolbar-hover-border-color:AccentColor;
      --editor-toolbar-hover-bg-color:ButtonFace;
      --editor-toolbar-hover-fg-color:AccentColor;
      --editor-toolbar-hover-outline:2px solid var(--editor-toolbar-hover-border-color);
      --editor-toolbar-focus-outline-color:ButtonBorder;
      --editor-toolbar-shadow:none;
      --alt-text-done-color:var(--editor-toolbar-fg-color);
      --alt-text-warning-color:var(--editor-toolbar-fg-color);
      --alt-text-hover-done-color:var(--editor-toolbar-hover-fg-color);
      --alt-text-hover-warning-color:var(--editor-toolbar-hover-fg-color);
    }

    display:flex;
    width:fit-content;
    height:var(--editor-toolbar-height);
    flex-direction:column;
    justify-content:center;
    align-items:center;
    cursor:default;
    pointer-events:auto;
    box-sizing:content-box;
    padding:var(--editor-toolbar-padding);

    position:absolute;
    inset-inline-end:0;
    inset-block-start:calc(100% + var(--editor-toolbar-vert-offset));

    border-radius:6px;
    background-color:var(--editor-toolbar-bg-color);
    border:1px solid var(--editor-toolbar-border-color);
    box-shadow:var(--editor-toolbar-shadow);

    &.hidden{
      display:none;
    }

    &:has(:focus-visible){
      border-color:transparent;
    }

    &:dir(ltr){
      transform-origin:100% 0;
    }

    &:dir(rtl){
      transform-origin:0 0;
    }

    .buttons{
      display:flex;
      justify-content:center;
      align-items:center;
      gap:0;
      height:100%;

      .divider{
        width:1px;
        height:calc(
          2 * var(--editor-toolbar-padding) + var(--editor-toolbar-height)
        );
        background-color:var(--editor-toolbar-border-color);
        display:inline-block;
        margin-inline:2px;
      }

      .highlightButton{
        width:var(--editor-toolbar-height);

        &::before{
          content:"";
          mask-image:var(--editor-toolbar-highlight-image);
          mask-repeat:no-repeat;
          mask-position:center;
          display:inline-block;
          background-color:var(--editor-toolbar-fg-color);
          width:100%;
          height:100%;
        }

        &:hover::before{
          background-color:var(--editor-toolbar-hover-fg-color);
        }
      }

      .delete{
        width:var(--editor-toolbar-height);

        &::before{
          content:"";
          mask-image:var(--editor-toolbar-delete-image);
          mask-repeat:no-repeat;
          mask-position:center;
          display:inline-block;
          background-color:var(--editor-toolbar-fg-color);
          width:100%;
          height:100%;
        }

        &:hover::before{
          background-color:var(--editor-toolbar-hover-fg-color);
        }
      }

      > *{
        height:var(--editor-toolbar-height);
      }

      > :not(.divider){
        border:none;
        background-color:transparent;
        cursor:pointer;

        &:hover{
          border-radius:2px;
          background-color:var(--editor-toolbar-hover-bg-color);
          color:var(--editor-toolbar-hover-fg-color);
          outline:var(--editor-toolbar-hover-outline);
          outline-offset:1px;

          &:active{
            outline:none;
          }
        }

        &:focus-visible{
          border-radius:2px;
          outline:2px solid var(--editor-toolbar-focus-outline-color);
        }
      }

      .altText{
        --alt-text-add-image:url(images/altText_add.svg);
        --alt-text-done-image:url(images/altText_done.svg);

        display:flex;
        align-items:center;
        justify-content:center;
        width:max-content;
        padding-inline:8px;
        pointer-events:all;
        font:menu;
        font-weight:590;
        font-size:12px;
        color:var(--editor-toolbar-fg-color);

        &:disabled{
          pointer-events:none;
        }

        &::before{
          content:"";
          mask-image:var(--alt-text-add-image);
          mask-repeat:no-repeat;
          mask-position:center;
          display:inline-block;
          width:12px;
          height:13px;
          background-color:var(--editor-toolbar-fg-color);
          margin-inline-end:4px;
        }

        &:hover::before{
          background-color:var(--editor-toolbar-hover-fg-color);
        }

        &.done::before{
          mask-image:var(--alt-text-done-image);
        }

        &.new{
          &::before{
            width:16px;
            height:16px;
            mask-image:var(--new-alt-text-warning-image);
            background-color:var(--alt-text-warning-color);
            mask-size:cover;
          }

          &:hover::before{
            background-color:var(--alt-text-hover-warning-color);
          }

          &.done{
            &::before{
              mask-image:var(--alt-text-done-image);
              background-color:var(--alt-text-done-color);
            }

            &:hover::before{
              background-color:var(--alt-text-hover-done-color);
            }
          }
        }

        .tooltip{
          display:none;

          &.show{
            --alt-text-tooltip-bg:#f0f0f4;
            --alt-text-tooltip-fg:#15141a;
            --alt-text-tooltip-border:#8f8f9d;
            --alt-text-tooltip-shadow:0px 2px 6px 0px rgb(58 57 68 / 0.2);

            @media (prefers-color-scheme: dark){
              --alt-text-tooltip-bg:#1c1b22;
              --alt-text-tooltip-fg:#fbfbfe;
              --alt-text-tooltip-shadow:0px 2px 6px 0px #15141a;
            }

            @media screen and (forced-colors: active){
              --alt-text-tooltip-bg:Canvas;
              --alt-text-tooltip-fg:CanvasText;
              --alt-text-tooltip-border:CanvasText;
              --alt-text-tooltip-shadow:none;
            }

            display:inline-flex;
            flex-direction:column;
            align-items:center;
            justify-content:center;
            position:absolute;
            top:calc(100% + 2px);
            inset-inline-start:0;
            padding-block:2px 3px;
            padding-inline:3px;
            max-width:300px;
            width:max-content;
            height:auto;
            font-size:12px;

            border:0.5px solid var(--alt-text-tooltip-border);
            background:var(--alt-text-tooltip-bg);
            box-shadow:var(--alt-text-tooltip-shadow);
            color:var(--alt-text-tooltip-fg);

            pointer-events:none;
          }
        }
      }
    }
  }
}

.annotationEditorLayer .freeTextEditor{
  padding:calc(var(--freetext-padding) * var(--scale-factor));
  width:auto;
  height:auto;
  touch-action:none;
}

.annotationEditorLayer .freeTextEditor .internal{
  background:transparent;
  border:none;
  inset:0;
  overflow:visible;
  white-space:nowrap;
  font:10px sans-serif;
  line-height:var(--freetext-line-height);
  user-select:none;
}

.annotationEditorLayer .freeTextEditor .overlay{
  position:absolute;
  display:none;
  background:transparent;
  inset:0;
  width:100%;
  height:100%;
}

.annotationEditorLayer freeTextEditor .overlay.enabled{
  display:block;
}

.annotationEditorLayer .freeTextEditor .internal:empty::before{
  content:attr(default-content);
  color:gray;
}

.annotationEditorLayer .freeTextEditor .internal:focus{
  outline:none;
  user-select:auto;
}

.annotationEditorLayer .inkEditor{
  width:100%;
  height:100%;
}

.annotationEditorLayer .inkEditor.editing{
  cursor:inherit;
}

.annotationEditorLayer .inkEditor .inkEditorCanvas{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  touch-action:none;
}

.annotationEditorLayer .stampEditor{
  width:auto;
  height:auto;

  canvas{
    position:absolute;
    width:100%;
    height:100%;
    margin:0;
    top:0;
    left:0;
  }

  .noAltTextBadge{
    --no-alt-text-badge-border-color:#f0f0f4;
    --no-alt-text-badge-bg-color:#cfcfd8;
    --no-alt-text-badge-fg-color:#5b5b66;

    @media (prefers-color-scheme: dark){
      --no-alt-text-badge-border-color:#52525e;
      --no-alt-text-badge-bg-color:#fbfbfe;
      --no-alt-text-badge-fg-color:#15141a;
    }

    @media screen and (forced-colors: active){
      --no-alt-text-badge-border-color:ButtonText;
      --no-alt-text-badge-bg-color:ButtonFace;
      --no-alt-text-badge-fg-color:ButtonText;
    }

    position:absolute;
    inset-inline-end:5px;
    inset-block-end:5px;
    display:inline-flex;
    width:32px;
    height:32px;
    padding:3px;
    justify-content:center;
    align-items:center;
    pointer-events:none;
    z-index:1;

    border-radius:2px;
    border:1px solid var(--no-alt-text-badge-border-color);
    background:var(--no-alt-text-badge-bg-color);

    &::before{
      content:"";
      display:inline-block;
      width:16px;
      height:16px;
      mask-image:var(--new-alt-text-warning-image);
      mask-size:cover;
      background-color:var(--no-alt-text-badge-fg-color);
    }
  }
}

.annotationEditorLayer{
  :is(.freeTextEditor, .inkEditor, .stampEditor){
    & > .resizers{
      position:absolute;
      inset:0;

      &.hidden{
        display:none;
      }

      & > .resizer{
        width:var(--resizer-size);
        height:var(--resizer-size);
        background:content-box var(--resizer-bg-color);
        border:var(--focus-outline-around);
        border-radius:2px;
        position:absolute;

        &.topLeft{
          top:var(--resizer-shift);
          left:var(--resizer-shift);
        }

        &.topMiddle{
          top:var(--resizer-shift);
          left:calc(50% + var(--resizer-shift));
        }

        &.topRight{
          top:var(--resizer-shift);
          right:var(--resizer-shift);
        }

        &.middleRight{
          top:calc(50% + var(--resizer-shift));
          right:var(--resizer-shift);
        }

        &.bottomRight{
          bottom:var(--resizer-shift);
          right:var(--resizer-shift);
        }

        &.bottomMiddle{
          bottom:var(--resizer-shift);
          left:calc(50% + var(--resizer-shift));
        }

        &.bottomLeft{
          bottom:var(--resizer-shift);
          left:var(--resizer-shift);
        }

        &.middleLeft{
          top:calc(50% + var(--resizer-shift));
          left:var(--resizer-shift);
        }
      }
    }
  }

  &[data-main-rotation="0"]
    :is([data-editor-rotation="0"], [data-editor-rotation="180"]),
  &[data-main-rotation="90"]
    :is([data-editor-rotation="270"], [data-editor-rotation="90"]),
  &[data-main-rotation="180"]
    :is([data-editor-rotation="180"], [data-editor-rotation="0"]),
  &[data-main-rotation="270"]
    :is([data-editor-rotation="90"], [data-editor-rotation="270"]){
    & > .resizers > .resizer{
      &.topLeft,
      &.bottomRight{
        cursor:nwse-resize;
      }

      &.topMiddle,
      &.bottomMiddle{
        cursor:ns-resize;
      }

      &.topRight,
      &.bottomLeft{
        cursor:nesw-resize;
      }

      &.middleRight,
      &.middleLeft{
        cursor:ew-resize;
      }
    }
  }

  &[data-main-rotation="0"]
    :is([data-editor-rotation="90"], [data-editor-rotation="270"]),
  &[data-main-rotation="90"]
    :is([data-editor-rotation="0"], [data-editor-rotation="180"]),
  &[data-main-rotation="180"]
    :is([data-editor-rotation="270"], [data-editor-rotation="90"]),
  &[data-main-rotation="270"]
    :is([data-editor-rotation="180"], [data-editor-rotation="0"]){
    & > .resizers > .resizer{
      &.topLeft,
      &.bottomRight{
        cursor:nesw-resize;
      }

      &.topMiddle,
      &.bottomMiddle{
        cursor:ew-resize;
      }

      &.topRight,
      &.bottomLeft{
        cursor:nwse-resize;
      }

      &.middleRight,
      &.middleLeft{
        cursor:ns-resize;
      }
    }
  }

  &
    :is(
      [data-main-rotation="0"] [data-editor-rotation="90"],
      [data-main-rotation="90"] [data-editor-rotation="0"],
      [data-main-rotation="180"] [data-editor-rotation="270"],
      [data-main-rotation="270"] [data-editor-rotation="180"]
    ){
    .editToolbar{
      rotate:270deg;

      &:dir(ltr){
        inset-inline-end:calc(0px - var(--editor-toolbar-vert-offset));
        inset-block-start:0;
      }

      &:dir(rtl){
        inset-inline-end:calc(100% + var(--editor-toolbar-vert-offset));
        inset-block-start:0;
      }
    }
  }

  &
    :is(
      [data-main-rotation="0"] [data-editor-rotation="180"],
      [data-main-rotation="90"] [data-editor-rotation="90"],
      [data-main-rotation="180"] [data-editor-rotation="0"],
      [data-main-rotation="270"] [data-editor-rotation="270"]
    ){
    .editToolbar{
      rotate:180deg;
      inset-inline-end:100%;
      inset-block-start:calc(0pc - var(--editor-toolbar-vert-offset));
    }
  }

  &
    :is(
      [data-main-rotation="0"] [data-editor-rotation="270"],
      [data-main-rotation="90"] [data-editor-rotation="180"],
      [data-main-rotation="180"] [data-editor-rotation="90"],
      [data-main-rotation="270"] [data-editor-rotation="0"]
    ){
    .editToolbar{
      rotate:90deg;

      &:dir(ltr){
        inset-inline-end:calc(100% + var(--editor-toolbar-vert-offset));
        inset-block-start:100%;
      }

      &:dir(rtl){
        inset-inline-start:calc(0px - var(--editor-toolbar-vert-offset));
        inset-block-start:0;
      }
    }
  }
}

.dialog.altText{
  &::backdrop{
    mask:url(#alttext-manager-mask);
  }
  &.positioned{
    margin:0;
  }

  & #altTextContainer{
    width:300px;
    height:fit-content;
    display:inline-flex;
    flex-direction:column;
    align-items:flex-start;
    gap:16px;

    & #overallDescription{
      display:flex;
      flex-direction:column;
      align-items:flex-start;
      gap:4px;
      align-self:stretch;

      & span{
        align-self:stretch;
      }

      & .title{
        font-size:13px;
        font-style:normal;
        font-weight:590;
      }
    }

    & #addDescription{
      display:flex;
      flex-direction:column;
      align-items:stretch;
      gap:8px;

      & .descriptionArea{
        flex:1;
        padding-inline:24px 10px;

        textarea{
          width:100%;
          min-height:75px;
        }
      }
    }

    & #buttons{
      display:flex;
      justify-content:flex-end;
      align-items:flex-start;
      gap:8px;
      align-self:stretch;
    }
  }
}

.dialog.newAltText{
  --new-alt-text-ai-disclaimer-icon:url(images/altText_disclaimer.svg);
  --new-alt-text-spinner-icon:url(images/altText_spinner.svg);
  --preview-image-bg-color:#f0f0f4;
  --preview-image-border:none;

  @media (prefers-color-scheme: dark){
    --preview-image-bg-color:#2b2a33;
  }

  @media screen and (forced-colors: active){
    --preview-image-bg-color:ButtonFace;
    --preview-image-border:1px solid ButtonText;
  }

  width:80%;
  max-width:570px;
  min-width:300px;
  padding:0;

  &.noAi{
    #newAltTextDisclaimer,
    #newAltTextCreateAutomatically{
      display:none !important;
    }
  }

  &.aiInstalling{
    #newAltTextCreateAutomatically{
      display:none !important;
    }
    #newAltTextDownloadModel{
      display:flex !important;
    }
  }

  &.error{
    #newAltTextNotNow{
      display:none !important;
    }

    #newAltTextCancel{
      display:inline-block !important;
    }
  }

  &:not(.error) #newAltTextError{
    display:none !important;
  }

  #newAltTextContainer{
    display:flex;
    width:auto;
    padding:16px;
    flex-direction:column;
    justify-content:flex-end;
    align-items:flex-start;
    gap:12px;
    flex:0 1 auto;
    line-height:normal;

    #mainContent{
      display:flex;
      justify-content:flex-end;
      align-items:flex-start;
      gap:12px;
      align-self:stretch;
      flex:1 1 auto;

      #descriptionAndSettings{
        display:flex;
        flex-direction:column;
        align-items:flex-start;
        gap:16px;
        flex:1 0 0;
        align-self:stretch;
      }

      #descriptionInstruction{
        display:flex;
        flex-direction:column;
        align-items:flex-start;
        gap:8px;
        align-self:stretch;
        flex:1 1 auto;

        #newAltTextDescriptionContainer{
          width:100%;
          height:70px;
          position:relative;

          textarea{
            width:100%;
            height:100%;
            padding:8px;

            &::placeholder{
              color:var(--text-secondary-color);
            }
          }

          .altTextSpinner{
            display:none;
            position:absolute;
            width:16px;
            height:16px;
            inset-inline-start:8px;
            inset-block-start:8px;
            mask-size:cover;
            background-color:var(--text-secondary-color);
            pointer-events:none;
          }

          &.loading{
            textarea::placeholder{
              color:transparent;
            }

            .altTextSpinner{
              display:inline-block;
              mask-image:var(--new-alt-text-spinner-icon);
            }
          }
        }

        #newAltTextDescription{
          font-size:11px;
        }

        #newAltTextDisclaimer{
          display:flex;
          flex-direction:row;
          align-items:flex-start;
          gap:4px;
          font-size:11px;

          &::before{
            content:"";
            display:inline-block;
            width:17px;
            height:16px;
            mask-image:var(--new-alt-text-ai-disclaimer-icon);
            mask-size:cover;
            background-color:var(--text-secondary-color);
            flex:1 0 auto;
          }
        }
      }

      #newAltTextDownloadModel{
        display:flex;
        align-items:center;
        gap:4px;
        align-self:stretch;

        &::before{
          content:"";
          display:inline-block;
          width:16px;
          height:16px;
          mask-image:var(--new-alt-text-spinner-icon);
          mask-size:cover;
          background-color:var(--text-secondary-color);
        }
      }

      #newAltTextImagePreview{
        width:180px;
        aspect-ratio:1;
        display:flex;
        justify-content:center;
        align-items:center;
        flex:0 0 auto;
        background-color:var(--preview-image-bg-color);
        border:var(--preview-image-border);

        > canvas{
          max-width:100%;
          max-height:100%;
        }
      }
    }
  }
}

.colorPicker{
  --hover-outline-color:#0250bb;
  --selected-outline-color:#0060df;
  --swatch-border-color:#cfcfd8;

  @media (prefers-color-scheme: dark){
    --hover-outline-color:#80ebff;
    --selected-outline-color:#aaf2ff;
    --swatch-border-color:#52525e;
  }

  @media screen and (forced-colors: active){
    --hover-outline-color:Highlight;
    --selected-outline-color:var(--hover-outline-color);
    --swatch-border-color:ButtonText;
  }

  .swatch{
    width:16px;
    height:16px;
    border:1px solid var(--swatch-border-color);
    border-radius:100%;
    outline-offset:2px;
    box-sizing:border-box;
    forced-color-adjust:none;
  }

  button:is(:hover, .selected) > .swatch{
    border:none;
  }
}

.annotationEditorLayer{
  &[data-main-rotation="0"]{
    .highlightEditor:not(.free) > .editToolbar{
      rotate:0deg;
    }
  }

  &[data-main-rotation="90"]{
    .highlightEditor:not(.free) > .editToolbar{
      rotate:270deg;
    }
  }

  &[data-main-rotation="180"]{
    .highlightEditor:not(.free) > .editToolbar{
      rotate:180deg;
    }
  }

  &[data-main-rotation="270"]{
    .highlightEditor:not(.free) > .editToolbar{
      rotate:90deg;
    }
  }

  .highlightEditor{
    position:absolute;
    background:transparent;
    z-index:1;
    cursor:auto;
    max-width:100%;
    max-height:100%;
    border:none;
    outline:none;
    pointer-events:none;
    transform-origin:0 0;

    &:not(.free){
      transform:none;
    }

    .internal{
      position:absolute;
      top:0;
      left:0;
      width:100%;
      height:100%;
      pointer-events:auto;
    }

    &.disabled .internal{
      pointer-events:none;
    }

    &.selectedEditor{
      .internal{
        cursor:pointer;
      }
    }

    .editToolbar{
      --editor-toolbar-colorpicker-arrow-image:url(images/toolbarButton-menuArrow.svg);

      transform-origin:center !important;

      .buttons{
        .colorPicker{
          position:relative;
          width:auto;
          display:flex;
          justify-content:center;
          align-items:center;
          gap:4px;
          padding:4px;

          &::after{
            content:"";
            mask-image:var(--editor-toolbar-colorpicker-arrow-image);
            mask-repeat:no-repeat;
            mask-position:center;
            display:inline-block;
            background-color:var(--editor-toolbar-fg-color);
            width:12px;
            height:12px;
          }

          &:hover::after{
            background-color:var(--editor-toolbar-hover-fg-color);
          }

          &:has(.dropdown:not(.hidden)){
            background-color:var(--editor-toolbar-hover-bg-color);

            &::after{
              scale:-1;
            }
          }

          .dropdown{
            position:absolute;
            display:flex;
            justify-content:center;
            align-items:center;
            flex-direction:column;
            gap:11px;
            padding-block:8px;
            border-radius:6px;
            background-color:var(--editor-toolbar-bg-color);
            border:1px solid var(--editor-toolbar-border-color);
            box-shadow:var(--editor-toolbar-shadow);
            inset-block-start:calc(100% + 4px);
            width:calc(100% + 2 * var(--editor-toolbar-padding));

            button{
              width:100%;
              height:auto;
              border:none;
              cursor:pointer;
              display:flex;
              justify-content:center;
              align-items:center;
              background:none;

              &:is(:active, :focus-visible){
                outline:none;
              }

              > .swatch{
                outline-offset:2px;
              }

              &[aria-selected="true"] > .swatch{
                outline:2px solid var(--selected-outline-color);
              }

              &:is(:hover, :active, :focus-visible) > .swatch{
                outline:2px solid var(--hover-outline-color);
              }
            }
          }
        }
      }
    }
  }
}

.editorParamsToolbar:has(#highlightParamsToolbarContainer){
  padding:unset;
}

#highlightParamsToolbarContainer{
  height:auto;
  padding-inline:10px;
  padding-block:10px 16px;
  gap:16px;
  display:flex;
  flex-direction:column;
  box-sizing:border-box;

  .editorParamsLabel{
    width:fit-content;
    inset-inline-start:0;
  }

  .colorPicker{
    display:flex;
    flex-direction:column;
    gap:8px;

    .dropdown{
      display:flex;
      justify-content:space-between;
      align-items:center;
      flex-direction:row;
      height:auto;

      button{
        width:auto;
        height:auto;
        border:none;
        cursor:pointer;
        display:flex;
        justify-content:center;
        align-items:center;
        background:none;
        flex:0 0 auto;

        .swatch{
          width:24px;
          height:24px;
        }

        &:is(:active, :focus-visible){
          outline:none;
        }

        &[aria-selected="true"] > .swatch{
          outline:2px solid var(--selected-outline-color);
        }

        &:is(:hover, :active, :focus-visible) > .swatch{
          outline:2px solid var(--hover-outline-color);
        }
      }
    }
  }

  #editorHighlightThickness{
    display:flex;
    flex-direction:column;
    align-items:center;
    gap:4px;
    align-self:stretch;

    .editorParamsLabel{
      width:100%;
      height:auto;
      align-self:stretch;
    }

    .thicknessPicker{
      display:flex;
      justify-content:space-between;
      align-items:center;
      align-self:stretch;

      --example-color:#bfbfc9;

      @media (prefers-color-scheme: dark){
        --example-color:#80808e;
      }

      @media screen and (forced-colors: active){
        --example-color:CanvasText;
      }

      :is(& > .editorParamsSlider[disabled]){
        opacity:0.4;
      }

      &::before,
      &::after{
        content:"";
        width:8px;
        aspect-ratio:1;
        display:block;
        border-radius:100%;
        background-color:var(--example-color);
      }
      &::after{
        width:24px;
      }

      .editorParamsSlider{
        width:unset;
        height:14px;
      }
    }
  }

  #editorHighlightVisibility{
    display:flex;
    flex-direction:column;
    align-items:flex-start;
    gap:8px;
    align-self:stretch;

    .divider{
      --divider-color:#d7d7db;

      @media (prefers-color-scheme: dark){
        --divider-color:#8f8f9d;
      }

      @media screen and (forced-colors: active){
        --divider-color:CanvasText;
      }

      margin-block:4px;
      width:100%;
      height:1px;
      background-color:var(--divider-color);
    }

    .toggler{
      display:flex;
      justify-content:space-between;
      align-items:center;
      align-self:stretch;
    }
  }
}

#altTextSettingsDialog{
  padding:16px;

  #altTextSettingsContainer{
    display:flex;
    width:573px;
    flex-direction:column;
    gap:16px;

    .mainContainer{
      gap:16px;
    }

    .description{
      color:var(--text-secondary-color);
    }

    #aiModelSettings{
      display:flex;
      flex-direction:column;
      gap:12px;

      button{
        width:fit-content;
      }

      &.download{
        #deleteModelButton{
          display:none;
        }
      }

      &:not(.download){
        #downloadModelButton{
          display:none;
        }
      }
    }

    #automaticAltText,
    #altTextEditor{
      display:flex;
      flex-direction:column;
      gap:8px;
    }

    #createModelDescription,
    #aiModelSettings,
    #showAltTextDialogDescription{
      padding-inline-start:40px;
    }

    #automaticSettings{
      display:flex;
      flex-direction:column;
      gap:16px;
    }
  }
}

:root{
  --viewer-container-height:0;
  --pdfViewer-padding-bottom:0;
  --page-margin:1px auto -8px;
  --page-border:9px solid transparent;
  --spreadHorizontalWrapped-margin-LR:-3.5px;
  --loading-icon-delay:400ms;
}

@media screen and (forced-colors: active){
  :root{
    --pdfViewer-padding-bottom:9px;
    --page-margin:8px auto -1px;
    --page-border:1px solid CanvasText;
    --spreadHorizontalWrapped-margin-LR:3.5px;
  }
}

[data-main-rotation="90"]{
  transform:rotate(90deg) translateY(-100%);
}
[data-main-rotation="180"]{
  transform:rotate(180deg) translate(-100%, -100%);
}
[data-main-rotation="270"]{
  transform:rotate(270deg) translateX(-100%);
}

#hiddenCopyElement,
.hiddenCanvasElement{
  position:absolute;
  top:0;
  left:0;
  width:0;
  height:0;
  display:none;
}

.pdfViewer{
  --scale-factor:1;

  padding-bottom:var(--pdfViewer-padding-bottom);

  --hcm-highlight-filter:none;
  --hcm-highlight-selected-filter:none;

  @media screen and (forced-colors: active){
    --hcm-highlight-filter:invert(100%);
  }

  &.copyAll{
    cursor:wait;
  }

  .canvasWrapper{
    overflow:hidden;
    width:100%;
    height:100%;

    canvas{
      margin:0;
      display:block;

      &[hidden]{
        display:none;
      }

      &[zooming]{
        width:100%;
        height:100%;
      }

      .structTree{
        contain:strict;
      }
    }
  }
}

.pdfViewer .page{
  direction:ltr;
  width:816px;
  height:1056px;
  margin:var(--page-margin);
  position:relative;
  overflow:visible;
  border:var(--page-border);
  background-clip:content-box;
  background-color:rgb(255 255 255);
}

.pdfViewer .dummyPage{
  position:relative;
  width:0;
  height:var(--viewer-container-height);
}

.pdfViewer.noUserSelect{
  user-select:none;
}

.pdfViewer:is(.scrollHorizontal, .scrollWrapped),
.spread{
  margin-inline:3.5px;
  text-align:center;
}

.pdfViewer.scrollHorizontal,
.spread{
  white-space:nowrap;
}

.pdfViewer:is(.scrollHorizontal, .scrollWrapped) .spread{
  margin-inline:0;
}

.spread :is(.page, .dummyPage),
.pdfViewer:is(.scrollHorizontal, .scrollWrapped) :is(.page, .spread){
  display:inline-block;
  vertical-align:middle;
}

.spread .page,
.pdfViewer:is(.scrollHorizontal, .scrollWrapped) .page{
  margin-inline:var(--spreadHorizontalWrapped-margin-LR);
}

.pdfViewer .page.loadingIcon::after{
  position:absolute;
  top:0;
  left:0;
  content:"";
  width:100%;
  height:100%;
  background:url("images/loading-icon.gif") center no-repeat;
  display:none;
  transition-property:display;
  transition-delay:var(--loading-icon-delay);
  z-index:5;
  contain:strict;
}

.pdfViewer .page.loading::after{
  display:block;
}

.pdfViewer .page:not(.loading)::after{
  transition-property:none;
  display:none;
}

.pdfPresentationMode .pdfViewer{
  padding-bottom:0;
}

.pdfPresentationMode .spread{
  margin:0;
}

.pdfPresentationMode .pdfViewer .page{
  margin:0 auto;
  border:2px solid transparent;
}

:root{
  --dir-factor:1;

  --sidebar-width:200px;
  --sidebar-transition-duration:200ms;
  --sidebar-transition-timing-function:ease;

  --toolbar-icon-opacity:0.7;
  --doorhanger-icon-opacity:0.9;
  --editor-toolbar-base-offset:105px;

  --main-color:rgb(12 12 13);
  --body-bg-color:rgb(212 212 215);
  --progressBar-color:rgb(10 132 255);
  --progressBar-bg-color:rgb(221 221 222);
  --progressBar-blend-color:rgb(116 177 239);
  --scrollbar-color:auto;
  --scrollbar-bg-color:auto;
  --toolbar-icon-bg-color:rgb(0 0 0);
  --toolbar-icon-hover-bg-color:rgb(0 0 0);

  --sidebar-narrow-bg-color:rgb(212 212 215 / 0.9);
  --sidebar-toolbar-bg-color:rgb(245 246 247);
  --toolbar-bg-color:rgb(249 249 250);
  --toolbar-border-color:rgb(184 184 184);
  --toolbar-box-shadow:0 1px 0 var(--toolbar-border-color);
  --toolbar-border-bottom:none;
  --toolbar-height:32px;
  --toolbarSidebar-box-shadow:inset calc(-1px * var(--dir-factor)) 0 0 rgb(0 0 0 / 0.25), 0 1px 0 rgb(0 0 0 / 0.15), 0 0 1px rgb(0 0 0 / 0.1);
  --toolbarSidebar-border-bottom:none;
  --button-hover-color:rgb(221 222 223);
  --toggled-btn-color:rgb(0 0 0);
  --toggled-btn-bg-color:rgb(0 0 0 / 0.3);
  --toggled-hover-active-btn-color:rgb(0 0 0 / 0.4);
  --toggled-hover-btn-outline:none;
  --dropdown-btn-bg-color:rgb(215 215 219);
  --dropdown-btn-border:none;
  --separator-color:rgb(0 0 0 / 0.3);
  --field-color:rgb(6 6 6);
  --field-bg-color:rgb(255 255 255);
  --field-border-color:rgb(187 187 188);
  --treeitem-color:rgb(0 0 0 / 0.8);
  --treeitem-bg-color:rgb(0 0 0 / 0.15);
  --treeitem-hover-color:rgb(0 0 0 / 0.9);
  --treeitem-selected-color:rgb(0 0 0 / 0.9);
  --treeitem-selected-bg-color:rgb(0 0 0 / 0.25);
  --thumbnail-hover-color:rgb(0 0 0 / 0.1);
  --thumbnail-selected-color:rgb(0 0 0 / 0.2);
  --doorhanger-bg-color:rgb(255 255 255);
  --doorhanger-border-color:rgb(12 12 13 / 0.2);
  --doorhanger-hover-color:rgb(12 12 13);
  --doorhanger-hover-bg-color:rgb(237 237 237);
  --doorhanger-separator-color:rgb(222 222 222);
  --dialog-button-border:none;
  --dialog-button-bg-color:rgb(12 12 13 / 0.1);
  --dialog-button-hover-bg-color:rgb(12 12 13 / 0.3);

  --loading-icon:url(images/loading.svg);
  --treeitem-expanded-icon:url(images/treeitem-expanded.svg);
  --treeitem-collapsed-icon:url(images/treeitem-collapsed.svg);
  --toolbarButton-editorFreeText-icon:url(images/toolbarButton-editorFreeText.svg);
  --toolbarButton-editorHighlight-icon:url(images/toolbarButton-editorHighlight.svg);
  --toolbarButton-editorInk-icon:url(images/toolbarButton-editorInk.svg);
  --toolbarButton-editorStamp-icon:url(images/toolbarButton-editorStamp.svg);
  --toolbarButton-menuArrow-icon:url(images/toolbarButton-menuArrow.svg);
  --toolbarButton-sidebarToggle-icon:url(images/toolbarButton-sidebarToggle.svg);
  --toolbarButton-secondaryToolbarToggle-icon:url(images/toolbarButton-secondaryToolbarToggle.svg);
  --toolbarButton-pageUp-icon:url(images/toolbarButton-pageUp.svg);
  --toolbarButton-pageDown-icon:url(images/toolbarButton-pageDown.svg);
  --toolbarButton-zoomOut-icon:url(images/toolbarButton-zoomOut.svg);
  --toolbarButton-zoomIn-icon:url(images/toolbarButton-zoomIn.svg);
  --toolbarButton-presentationMode-icon:url(images/toolbarButton-presentationMode.svg);
  --toolbarButton-print-icon:url(images/toolbarButton-print.svg);
  --toolbarButton-download-icon:url(images/toolbarButton-download.svg);
  --toolbarButton-bookmark-icon:url(images/toolbarButton-bookmark.svg);
  --toolbarButton-viewThumbnail-icon:url(images/toolbarButton-viewThumbnail.svg);
  --toolbarButton-viewOutline-icon:url(images/toolbarButton-viewOutline.svg);
  --toolbarButton-viewAttachments-icon:url(images/toolbarButton-viewAttachments.svg);
  --toolbarButton-viewLayers-icon:url(images/toolbarButton-viewLayers.svg);
  --toolbarButton-currentOutlineItem-icon:url(images/toolbarButton-currentOutlineItem.svg);
  --toolbarButton-search-icon:url(images/toolbarButton-search.svg);
  --findbarButton-previous-icon:url(images/findbarButton-previous.svg);
  --findbarButton-next-icon:url(images/findbarButton-next.svg);
  --secondaryToolbarButton-firstPage-icon:url(images/secondaryToolbarButton-firstPage.svg);
  --secondaryToolbarButton-lastPage-icon:url(images/secondaryToolbarButton-lastPage.svg);
  --secondaryToolbarButton-rotateCcw-icon:url(images/secondaryToolbarButton-rotateCcw.svg);
  --secondaryToolbarButton-rotateCw-icon:url(images/secondaryToolbarButton-rotateCw.svg);
  --secondaryToolbarButton-selectTool-icon:url(images/secondaryToolbarButton-selectTool.svg);
  --secondaryToolbarButton-handTool-icon:url(images/secondaryToolbarButton-handTool.svg);
  --secondaryToolbarButton-scrollPage-icon:url(images/secondaryToolbarButton-scrollPage.svg);
  --secondaryToolbarButton-scrollVertical-icon:url(images/secondaryToolbarButton-scrollVertical.svg);
  --secondaryToolbarButton-scrollHorizontal-icon:url(images/secondaryToolbarButton-scrollHorizontal.svg);
  --secondaryToolbarButton-scrollWrapped-icon:url(images/secondaryToolbarButton-scrollWrapped.svg);
  --secondaryToolbarButton-spreadNone-icon:url(images/secondaryToolbarButton-spreadNone.svg);
  --secondaryToolbarButton-spreadOdd-icon:url(images/secondaryToolbarButton-spreadOdd.svg);
  --secondaryToolbarButton-spreadEven-icon:url(images/secondaryToolbarButton-spreadEven.svg);
  --secondaryToolbarButton-imageAltTextSettings-icon:var(
    --toolbarButton-editorStamp-icon
  );
  --secondaryToolbarButton-documentProperties-icon:url(images/secondaryToolbarButton-documentProperties.svg);
  --editorParams-stampAddImage-icon:url(images/toolbarButton-zoomIn.svg);
}

:root:dir(rtl){
  --dir-factor:-1;
}

@media (prefers-color-scheme: dark){
  :root{
    --main-color:rgb(249 249 250);
    --body-bg-color:rgb(42 42 46);
    --progressBar-color:rgb(0 96 223);
    --progressBar-bg-color:rgb(40 40 43);
    --progressBar-blend-color:rgb(20 68 133);
    --scrollbar-color:rgb(121 121 123);
    --scrollbar-bg-color:rgb(35 35 39);
    --toolbar-icon-bg-color:rgb(255 255 255);
    --toolbar-icon-hover-bg-color:rgb(255 255 255);

    --sidebar-narrow-bg-color:rgb(42 42 46 / 0.9);
    --sidebar-toolbar-bg-color:rgb(50 50 52);
    --toolbar-bg-color:rgb(56 56 61);
    --toolbar-border-color:rgb(12 12 13);
    --button-hover-color:rgb(102 102 103);
    --toggled-btn-color:rgb(255 255 255);
    --toggled-btn-bg-color:rgb(0 0 0 / 0.3);
    --toggled-hover-active-btn-color:rgb(0 0 0 / 0.4);
    --dropdown-btn-bg-color:rgb(74 74 79);
    --separator-color:rgb(0 0 0 / 0.3);
    --field-color:rgb(250 250 250);
    --field-bg-color:rgb(64 64 68);
    --field-border-color:rgb(115 115 115);
    --treeitem-color:rgb(255 255 255 / 0.8);
    --treeitem-bg-color:rgb(255 255 255 / 0.15);
    --treeitem-hover-color:rgb(255 255 255 / 0.9);
    --treeitem-selected-color:rgb(255 255 255 / 0.9);
    --treeitem-selected-bg-color:rgb(255 255 255 / 0.25);
    --thumbnail-hover-color:rgb(255 255 255 / 0.1);
    --thumbnail-selected-color:rgb(255 255 255 / 0.2);
    --doorhanger-bg-color:rgb(74 74 79);
    --doorhanger-border-color:rgb(39 39 43);
    --doorhanger-hover-color:rgb(249 249 250);
    --doorhanger-hover-bg-color:rgb(93 94 98);
    --doorhanger-separator-color:rgb(92 92 97);
    --dialog-button-bg-color:rgb(92 92 97);
    --dialog-button-hover-bg-color:rgb(115 115 115);
  }
}

@media screen and (forced-colors: active){
  :root{
    --button-hover-color:Highlight;
    --doorhanger-hover-bg-color:Highlight;
    --toolbar-icon-opacity:1;
    --toolbar-icon-bg-color:ButtonText;
    --toolbar-icon-hover-bg-color:ButtonFace;
    --toggled-hover-active-btn-color:ButtonText;
    --toggled-hover-btn-outline:2px solid ButtonBorder;
    --toolbar-border-color:CanvasText;
    --toolbar-border-bottom:1px solid var(--toolbar-border-color);
    --toolbar-box-shadow:none;
    --toggled-btn-color:HighlightText;
    --toggled-btn-bg-color:LinkText;
    --doorhanger-hover-color:ButtonFace;
    --doorhanger-border-color-whcm:1px solid ButtonText;
    --doorhanger-triangle-opacity-whcm:0;
    --dialog-button-border:1px solid Highlight;
    --dialog-button-hover-bg-color:Highlight;
    --dialog-button-hover-color:ButtonFace;
    --dropdown-btn-border:1px solid ButtonText;
    --field-border-color:ButtonText;
    --main-color:CanvasText;
    --separator-color:GrayText;
    --doorhanger-separator-color:GrayText;
    --toolbarSidebar-box-shadow:none;
    --toolbarSidebar-border-bottom:1px solid var(--toolbar-border-color);
  }
}

@media screen and (prefers-reduced-motion: reduce){
  :root{
    --sidebar-transition-duration:0;
  }
}

*{
  padding:0;
  margin:0;
}

html,
body{
  height:100%;
  width:100%;
}

body{
  background-color:var(--body-bg-color);
  scrollbar-color:var(--scrollbar-color) var(--scrollbar-bg-color);

  &.wait::before{
    content:"";
    position:fixed;
    width:100%;
    height:100%;
    z-index:100000;
    cursor:wait;
  }
}

.hidden,
[hidden]{
  display:none !important;
}

#viewerContainer.pdfPresentationMode:fullscreen{
  top:0;
  background-color:rgb(0 0 0);
  width:100%;
  height:100%;
  overflow:hidden;
  cursor:none;
  user-select:none;
}

.pdfPresentationMode:fullscreen section:not([data-internal-link]){
  pointer-events:none;
}

.pdfPresentationMode:fullscreen .textLayer span{
  cursor:none;
}

.pdfPresentationMode.pdfPresentationModeControls > *,
.pdfPresentationMode.pdfPresentationModeControls .textLayer span{
  cursor:default;
}

#outerContainer{
  width:100%;
  height:100%;
  position:relative;
}

#sidebarContainer{
  position:absolute;
  inset-block:var(--toolbar-height) 0;
  inset-inline-start:calc(-1 * var(--sidebar-width));
  width:var(--sidebar-width);
  visibility:hidden;
  z-index:100;
  font:message-box;
  border-top:1px solid rgb(51 51 51);
  border-inline-end:var(--doorhanger-border-color-whcm);
  transition-property:inset-inline-start;
  transition-duration:var(--sidebar-transition-duration);
  transition-timing-function:var(--sidebar-transition-timing-function);
}

#outerContainer:is(.sidebarMoving, .sidebarOpen) #sidebarContainer{
  visibility:visible;
}
#outerContainer.sidebarOpen #sidebarContainer{
  inset-inline-start:0;
}

#mainContainer{
  position:absolute;
  inset:0;
  min-width:350px;
}

#sidebarContent{
  inset-block:var(--toolbar-height) 0;
  inset-inline-start:0;
  overflow:auto;
  position:absolute;
  width:100%;
  box-shadow:inset calc(-1px * var(--dir-factor)) 0 0 rgb(0 0 0 / 0.25);
}

#viewerContainer{
  overflow:auto;
  position:absolute;
  inset:var(--toolbar-height) 0 0;
  outline:none;
}
#viewerContainer:not(.pdfPresentationMode){
  transition-duration:var(--sidebar-transition-duration);
  transition-timing-function:var(--sidebar-transition-timing-function);
}

#outerContainer.sidebarOpen #viewerContainer:not(.pdfPresentationMode){
  inset-inline-start:var(--sidebar-width);
  transition-property:inset-inline-start;
}

.toolbar{
  position:relative;
  inset-inline:0;
  z-index:9999;
  cursor:default;
  font:message-box;
}

:is(.toolbar, .editorParamsToolbar, #sidebarContainer)
  :is(input, button, select){
  outline:none;
  font:message-box;
}

#toolbarContainer{
  width:100%;
}

#toolbarSidebar{
  width:100%;
  height:var(--toolbar-height);
  background-color:var(--sidebar-toolbar-bg-color);
  box-shadow:var(--toolbarSidebar-box-shadow);
  border-bottom:var(--toolbarSidebar-border-bottom);
}

#sidebarResizer{
  position:absolute;
  inset-block:0;
  inset-inline-end:-6px;
  width:6px;
  z-index:200;
  cursor:ew-resize;
}

#toolbarContainer,
.editorParamsToolbar{
  position:relative;
  height:var(--toolbar-height);
  background-color:var(--toolbar-bg-color);
  box-shadow:var(--toolbar-box-shadow);
  border-bottom:var(--toolbar-border-bottom);
}

#toolbarViewer{
  height:var(--toolbar-height);
}

#loadingBar{
  --progressBar-percent:0%;
  --progressBar-end-offset:0;

  position:absolute;
  inset-inline:0 var(--progressBar-end-offset);
  height:4px;
  background-color:var(--progressBar-bg-color);
  border-bottom:1px solid var(--toolbar-border-color);
  transition-property:inset-inline-start;
  transition-duration:var(--sidebar-transition-duration);
  transition-timing-function:var(--sidebar-transition-timing-function);
}

#outerContainer.sidebarOpen #loadingBar{
  inset-inline-start:var(--sidebar-width);
}

#loadingBar .progress{
  position:absolute;
  top:0;
  inset-inline-start:0;
  width:100%;
  transform:scaleX(var(--progressBar-percent));
  transform-origin:calc(50% - 50% * var(--dir-factor)) 0;
  height:100%;
  background-color:var(--progressBar-color);
  overflow:hidden;
  transition:transform 200ms;
}

@keyframes progressIndeterminate{
  0%{
    transform:translateX(calc(-142px * var(--dir-factor)));
  }
  100%{
    transform:translateX(0);
  }
}

#loadingBar.indeterminate .progress{
  transform:none;
  background-color:var(--progressBar-bg-color);
  transition:none;
}

#loadingBar.indeterminate .progress .glimmer{
  position:absolute;
  top:0;
  inset-inline-start:0;
  height:100%;
  width:calc(100% + 150px);
  background:repeating-linear-gradient(
    135deg,
    var(--progressBar-blend-color) 0,
    var(--progressBar-bg-color) 5px,
    var(--progressBar-bg-color) 45px,
    var(--progressBar-color) 55px,
    var(--progressBar-color) 95px,
    var(--progressBar-blend-color) 100px
  );
  animation:progressIndeterminate 1s linear infinite;
}

#outerContainer.sidebarResizing
  :is(#sidebarContainer, #viewerContainer, #loadingBar){
  transition-duration:0s;
}

.editorParamsToolbar{
  background-color:var(--doorhanger-bg-color);
  top:var(--toolbar-height);
  position:absolute;
  z-index:30000;
  height:auto;
  inset-inline-end:4px;
  padding:6px 0 10px;
  margin:4px 2px;
  font:message-box;
  font-size:12px;
  line-height:14px;
  text-align:left;
  cursor:default;
}

.editorParamsToolbarContainer{
  width:220px;
  margin-bottom:-4px;
}

.editorParamsToolbarContainer > .editorParamsSetter{
  min-height:26px;
  display:flex;
  align-items:center;
  justify-content:space-between;
  padding-inline:10px;
}

.editorParamsToolbarContainer .editorParamsLabel{
  padding-inline-end:10px;
  flex:none;
  font:menu;
  font-size:13px;
  font-style:normal;
  font-weight:400;
  line-height:150%;
  color:var(--main-color);
}

.editorParamsToolbarContainer .editorParamsColor{
  width:32px;
  height:32px;
  flex:none;
}

.editorParamsToolbarContainer .editorParamsSlider{
  background-color:transparent;
  width:90px;
  flex:0 1 0;
}

.editorParamsToolbarContainer .editorParamsSlider::-moz-range-progress{
  background-color:black;
}

.editorParamsToolbarContainer .editorParamsSlider::-moz-range-track{
  background-color:black;
}

.editorParamsToolbarContainer .editorParamsSlider::-moz-range-thumb{
  background-color:white;
}

#editorStampParamsToolbar{
  inset-inline-end:calc(var(--editor-toolbar-base-offset) + 0px);
}

#editorInkParamsToolbar{
  inset-inline-end:calc(var(--editor-toolbar-base-offset) + 28px);
}

#editorFreeTextParamsToolbar{
  inset-inline-end:calc(var(--editor-toolbar-base-offset) + 56px);
}

#editorHighlightParamsToolbar{
  inset-inline-end:calc(var(--editor-toolbar-base-offset) + 84px);
}

#editorStampAddImage::before{
  mask-image:var(--editorParams-stampAddImage-icon);
}

.doorHanger,
.doorHangerRight{
  border-radius:2px;
  box-shadow:0 1px 5px var(--doorhanger-border-color), 0 0 0 1px var(--doorhanger-border-color);
  border:var(--doorhanger-border-color-whcm);
}
:is(.doorHanger, .doorHangerRight)::after,
:is(.doorHanger, .doorHangerRight)::before{
  bottom:100%;
  border:8px solid rgb(0 0 0 / 0);
  content:" ";
  height:0;
  width:0;
  position:absolute;
  pointer-events:none;
  opacity:var(--doorhanger-triangle-opacity-whcm);
}
.doorHanger::after{
  inset-inline-start:10px;
  margin-inline-start:-8px;
  border-bottom-color:var(--toolbar-bg-color);
}
.doorHangerRight::after{
  inset-inline-end:10px;
  margin-inline-end:-8px;
  border-bottom-color:var(--doorhanger-bg-color);
}
:is(.doorHanger, .doorHangerRight)::before{
  border-bottom-color:var(--doorhanger-border-color);
  border-width:9px;
}
.doorHanger::before{
  inset-inline-start:10px;
  margin-inline-start:-9px;
}
.doorHangerRight::before{
  inset-inline-end:10px;
  margin-inline-end:-9px;
}

#toolbarViewerMiddle{
  position:absolute;
  left:50%;
  transform:translateX(-50%);
}

#toolbarViewerLeft,
#toolbarSidebarLeft{
  float:inline-start;
}
#toolbarViewerRight,
#toolbarSidebarRight{
  float:inline-end;
}

#toolbarViewerLeft > *,
#toolbarViewerMiddle > *,
#toolbarViewerRight > *,
#toolbarSidebarLeft *,
#toolbarSidebarRight *{
  position:relative;
  float:inline-start;
}

#toolbarViewerLeft{
  padding-inline-start:1px;
}
#toolbarViewerRight{
  padding-inline-end:1px;
}
#toolbarSidebarRight{
  padding-inline-end:2px;
}

.splitToolbarButton{
  margin:2px;
  display:inline-block;
}
.splitToolbarButton > .toolbarButton{
  float:inline-start;
}

.toolbarButton,
.dialogButton{
  border:none;
  background:none;
  width:28px;
  height:28px;
  outline:none;
}

.dialogButton:is(:hover, :focus-visible){
  background-color:var(--dialog-button-hover-bg-color);
}

.dialogButton:is(:hover, :focus-visible) > span{
  color:var(--dialog-button-hover-color);
}

.toolbarButton > span{
  display:inline-block;
  width:0;
  height:0;
  overflow:hidden;
}

:is(.toolbarButton, .dialogButton)[disabled]{
  opacity:0.5;
}

.splitToolbarButton > .toolbarButton:is(:hover, :focus-visible),
.dropdownToolbarButton:hover{
  background-color:var(--button-hover-color);
}
.splitToolbarButton > .toolbarButton{
  position:relative;
  margin:0;
}
#toolbarSidebar .splitToolbarButton > .toolbarButton{
  margin-inline-end:2px;
}

.splitToolbarButtonSeparator{
  float:inline-start;
  margin:4px 0;
  width:1px;
  height:20px;
  background-color:var(--separator-color);
}

.toolbarButton,
.dropdownToolbarButton,
.dialogButton{
  min-width:16px;
  margin:2px 1px;
  padding:2px 6px 0;
  border:none;
  border-radius:2px;
  color:var(--main-color);
  font-size:12px;
  line-height:14px;
  user-select:none;
  cursor:default;
  box-sizing:border-box;
}

.toolbarButton:is(:hover, :focus-visible){
  background-color:var(--button-hover-color);
}

.toolbarButton.toggled,
.splitToolbarButton.toggled > .toolbarButton.toggled{
  background-color:var(--toggled-btn-bg-color);
  color:var(--toggled-btn-color);
}

.toolbarButton.toggled:hover,
.splitToolbarButton.toggled > .toolbarButton.toggled:hover{
  outline:var(--toggled-hover-btn-outline) !important;
}

.toolbarButton.toggled::before{
  background-color:var(--toggled-btn-color);
}

.toolbarButton.toggled:hover:active,
.splitToolbarButton.toggled > .toolbarButton.toggled:hover:active{
  background-color:var(--toggled-hover-active-btn-color);
}

.dropdownToolbarButton{
  display:flex;
  width:fit-content;
  min-width:140px;
  padding:0;
  background-color:var(--dropdown-btn-bg-color);
  border:var(--dropdown-btn-border);
}
.dropdownToolbarButton::after{
  top:6px;
  inset-inline-end:6px;
  pointer-events:none;
  mask-image:var(--toolbarButton-menuArrow-icon);
}

.dropdownToolbarButton > select{
  appearance:none;
  width:inherit;
  min-width:inherit;
  height:28px;
  font-size:12px;
  color:var(--main-color);
  margin:0;
  padding-block:1px 2px;
  padding-inline:6px 38px;
  border:none;
  background-color:var(--dropdown-btn-bg-color);
}
.dropdownToolbarButton > select:is(:hover, :focus-visible){
  background-color:var(--button-hover-color);
  color:var(--toggled-btn-color);
}
.dropdownToolbarButton > select > option{
  background:var(--doorhanger-bg-color);
  color:var(--main-color);
}

.toolbarButtonSpacer{
  width:30px;
  display:inline-block;
  height:1px;
}

:is(.toolbarButton, .treeItemToggler)::before,
.dropdownToolbarButton::after{
  position:absolute;
  display:inline-block;
  width:16px;
  height:16px;

  content:"";
  background-color:var(--toolbar-icon-bg-color);
  mask-size:cover;
}

.dropdownToolbarButton:is(:hover, :focus-visible, :active)::after{
  background-color:var(--toolbar-icon-hover-bg-color);
}

.toolbarButton::before{
  opacity:var(--toolbar-icon-opacity);
  top:6px;
  left:6px;
}

.toolbarButton:is(:hover, :focus-visible)::before{
  background-color:var(--toolbar-icon-hover-bg-color);
}

#sidebarToggle::before{
  mask-image:var(--toolbarButton-sidebarToggle-icon);
  transform:scaleX(var(--dir-factor));
}

#secondaryToolbarToggle::before{
  mask-image:var(--toolbarButton-secondaryToolbarToggle-icon);
  transform:scaleX(var(--dir-factor));
}

#previous::before{
  mask-image:var(--toolbarButton-pageUp-icon);
}

#next::before{
  mask-image:var(--toolbarButton-pageDown-icon);
}

#zoomOut::before{
  mask-image:var(--toolbarButton-zoomOut-icon);
}

#zoomIn::before{
  mask-image:var(--toolbarButton-zoomIn-icon);
}

#editorFreeText::before{
  mask-image:var(--toolbarButton-editorFreeText-icon);
}

#editorHighlight::before{
  mask-image:var(--toolbarButton-editorHighlight-icon);
}

#editorInk::before{
  mask-image:var(--toolbarButton-editorInk-icon);
}

#editorStamp::before{
  mask-image:var(--toolbarButton-editorStamp-icon);
}

#print::before{
  mask-image:var(--toolbarButton-print-icon);
}

#download::before{
  mask-image:var(--toolbarButton-download-icon);
}

#viewThumbnail::before{
  mask-image:var(--toolbarButton-viewThumbnail-icon);
}

#viewOutline::before{
  mask-image:var(--toolbarButton-viewOutline-icon);
  transform:scaleX(var(--dir-factor));
}

#viewAttachments::before{
  mask-image:var(--toolbarButton-viewAttachments-icon);
}

#viewLayers::before{
  mask-image:var(--toolbarButton-viewLayers-icon);
}

#currentOutlineItem::before{
  mask-image:var(--toolbarButton-currentOutlineItem-icon);
  transform:scaleX(var(--dir-factor));
}

#viewFind::before{
  mask-image:var(--toolbarButton-search-icon);
}

.pdfSidebarNotification::after{
  position:absolute;
  display:inline-block;
  top:2px;
  inset-inline-end:2px;
  content:"";
  background-color:rgb(112 219 85);
  height:9px;
  width:9px;
  border-radius:50%;
}

.verticalToolbarSeparator{
  display:block;
  margin:5px 2px;
  width:1px;
  height:22px;
  background-color:var(--separator-color);
}
.horizontalToolbarSeparator{
  display:block;
  margin:6px 0;
  height:1px;
  width:100%;
  background-color:var(--doorhanger-separator-color);
}

.toolbarField{
  padding:4px 7px;
  margin:3px 0;
  border-radius:2px;
  background-color:var(--field-bg-color);
  background-clip:padding-box;
  border:1px solid var(--field-border-color);
  box-shadow:none;
  color:var(--field-color);
  font-size:12px;
  line-height:16px;
  outline:none;
}

.toolbarField[type="checkbox"]{
  opacity:0;
  position:absolute !important;
  left:0;
  margin:10px 0 3px;
  margin-inline-start:7px;
}

#pageNumber{
  -moz-appearance:textfield;
  text-align:end;
  width:40px;
  background-size:0 0;
  transition-property:none;

  .loadingInput:has(> &.loading)::after{
    display:block;
    visibility:visible;

    transition-property:visibility;
    transition-delay:var(--loading-icon-delay);
  }
}

.loadingInput{
  &::after{
    position:absolute;
    visibility:hidden;
    display:none;
    top:calc(50% - 8px);
    width:16px;
    height:16px;

    content:"";
    background-color:var(--toolbar-icon-bg-color);
    mask-size:cover;
    mask-image:var(--loading-icon);
  }

  &.start::after{
    inset-inline-start:4px;
  }
  &.end::after{
    inset-inline-end:4px;
  }
}

.toolbarField:focus{
  border-color:#0a84ff;
}

.toolbarLabel{
  min-width:16px;
  padding:7px;
  margin:2px;
  border-radius:2px;
  color:var(--main-color);
  font-size:12px;
  line-height:14px;
  text-align:left;
  user-select:none;
  cursor:default;
}

#numPages.toolbarLabel{
  padding-inline-start:3px;
}

#thumbnailView,
#outlineView,
#attachmentsView,
#layersView{
  position:absolute;
  width:calc(100% - 8px);
  inset-block:0;
  padding:4px 4px 0;
  overflow:auto;
  user-select:none;
}
#thumbnailView{
  width:calc(100% - 60px);
  padding:10px 30px 0;
}

#thumbnailView > a:is(:active, :focus){
  outline:0;
}

.thumbnail{
  --thumbnail-width:0;
  --thumbnail-height:0;

  float:inline-start;
  width:var(--thumbnail-width);
  height:var(--thumbnail-height);
  margin:0 10px 5px;
  padding:1px;
  border:7px solid transparent;
  border-radius:2px;
}

#thumbnailView > a:last-of-type > .thumbnail{
  margin-bottom:10px;
}

a:focus > .thumbnail,
.thumbnail:hover{
  border-color:var(--thumbnail-hover-color);
}
.thumbnail.selected{
  border-color:var(--thumbnail-selected-color) !important;
}

.thumbnailImage{
  width:var(--thumbnail-width);
  height:var(--thumbnail-height);
  opacity:0.9;
}
a:focus > .thumbnail > .thumbnailImage,
.thumbnail:hover > .thumbnailImage{
  opacity:0.95;
}
.thumbnail.selected > .thumbnailImage{
  opacity:1 !important;
}

.thumbnail:not([data-loaded]) > .thumbnailImage{
  width:calc(var(--thumbnail-width) - 2px);
  height:calc(var(--thumbnail-height) - 2px);
  border:1px dashed rgb(132 132 132);
}

.treeWithDeepNesting > .treeItem,
.treeItem > .treeItems{
  margin-inline-start:20px;
}

.treeItem > a{
  text-decoration:none;
  display:inline-block;
  min-width:calc(100% - 4px);
  height:auto;
  margin-bottom:1px;
  padding:2px 0 5px;
  padding-inline-start:4px;
  border-radius:2px;
  color:var(--treeitem-color);
  font-size:13px;
  line-height:15px;
  user-select:none;
  white-space:normal;
  cursor:pointer;
}

#layersView .treeItem > a *{
  cursor:pointer;
}
#layersView .treeItem > a > label{
  padding-inline-start:4px;
}
#layersView .treeItem > a > label > input{
  float:inline-start;
  margin-top:1px;
}

.treeItemToggler{
  position:relative;
  float:inline-start;
  height:0;
  width:0;
  color:rgb(255 255 255 / 0.5);
}
.treeItemToggler::before{
  inset-inline-end:4px;
  mask-image:var(--treeitem-expanded-icon);
}
.treeItemToggler.treeItemsHidden::before{
  mask-image:var(--treeitem-collapsed-icon);
  transform:scaleX(var(--dir-factor));
}
.treeItemToggler.treeItemsHidden ~ .treeItems{
  display:none;
}

.treeItem.selected > a{
  background-color:var(--treeitem-selected-bg-color);
  color:var(--treeitem-selected-color);
}

.treeItemToggler:hover,
.treeItemToggler:hover + a,
.treeItemToggler:hover ~ .treeItems,
.treeItem > a:hover{
  background-color:var(--treeitem-bg-color);
  background-clip:padding-box;
  border-radius:2px;
  color:var(--treeitem-hover-color);
}

#outlineOptionsContainer{
  display:none;

  #sidebarContainer:has(#outlineView:not(.hidden)) &{
    display:inherit;
  }
}

.dialogButton{
  width:auto;
  margin:3px 4px 2px !important;
  padding:2px 11px;
  color:var(--main-color);
  background-color:var(--dialog-button-bg-color);
  border:var(--dialog-button-border) !important;
}

dialog{
  margin:auto;
  padding:15px;
  border-spacing:4px;
  color:var(--main-color);
  font:message-box;
  font-size:12px;
  line-height:14px;
  background-color:var(--doorhanger-bg-color);
  border:1px solid rgb(0 0 0 / 0.5);
  border-radius:4px;
  box-shadow:0 1px 4px rgb(0 0 0 / 0.3);
}
dialog::backdrop{
  background-color:rgb(0 0 0 / 0.2);
}

dialog > .row{
  display:table-row;
}

dialog > .row > *{
  display:table-cell;
}

dialog .toolbarField{
  margin:5px 0;
}

dialog .separator{
  display:block;
  margin:4px 0;
  height:1px;
  width:100%;
  background-color:var(--separator-color);
}

dialog .buttonRow{
  text-align:center;
  vertical-align:middle;
}

dialog :link{
  color:rgb(255 255 255);
}

#passwordDialog{
  text-align:center;
}
#passwordDialog .toolbarField{
  width:200px;
}

#documentPropertiesDialog{
  text-align:left;
}
#documentPropertiesDialog .row > *{
  min-width:100px;
  text-align:start;
}
#documentPropertiesDialog .row > span{
  width:125px;
  word-wrap:break-word;
}
#documentPropertiesDialog .row > p{
  max-width:225px;
  word-wrap:break-word;
}
#documentPropertiesDialog .buttonRow{
  margin-top:10px;
}

.grab-to-pan-grab{
  cursor:grab !important;
}
.grab-to-pan-grab
  *:not(input):not(textarea):not(button):not(select):not(:link){
  cursor:inherit !important;
}
.grab-to-pan-grab:active,
.grab-to-pan-grabbing{
  cursor:grabbing !important;
}
.grab-to-pan-grabbing{
  position:fixed;
  background:rgb(0 0 0 / 0);
  display:block;
  inset:0;
  overflow:hidden;
  z-index:50000;
}

.toolbarButton{
  &.labeled{
    border-radius:0;
    display:inline-block;
    height:auto;
    margin:0;
    padding:0 0 1px;
    padding-inline-start:36px;
    position:relative;
    min-height:26px;
    min-width:100%;
    text-align:start;
    white-space:normal;
    width:auto;

    &:is(a){
      padding-top:5px;
      text-decoration:none;

      &[href="#"]{
        opacity:0.5;
        pointer-events:none;
      }
    }

    &::before{
      inset-inline-start:12px;
      opacity:var(--doorhanger-icon-opacity);
      top:5px;
    }

    &:not(.toggled):is(:hover, :focus-visible){
      background-color:var(--doorhanger-hover-bg-color);
      color:var(--doorhanger-hover-color);
    }

    > span{
      display:unset;
      padding-inline-end:4px;
    }
  }
}

#secondaryToolbar{
  background-color:var(--doorhanger-bg-color);
  cursor:default;
  font:message-box;
  font-size:12px;
  height:auto;
  inset-inline-end:4px;
  line-height:14px;
  margin:4px 2px;
  padding:6px 0 10px;
  position:absolute;
  text-align:left;
  top:var(--toolbar-height);
  z-index:30000;

  :is(button, a){
    font:message-box;
    outline:none;
  }

  #secondaryToolbarButtonContainer{
    margin-bottom:-4px;
    max-height:calc(var(--viewer-container-height) - 40px);
    max-width:220px;
    min-height:26px;
    overflow-y:auto;

    #secondaryPrint::before{
      mask-image:var(--toolbarButton-print-icon);
    }

    #secondaryDownload::before{
      mask-image:var(--toolbarButton-download-icon);
    }

    #presentationMode::before{
      mask-image:var(--toolbarButton-presentationMode-icon);
    }

    #viewBookmark::before{
      mask-image:var(--toolbarButton-bookmark-icon);
    }

    #firstPage::before{
      mask-image:var(--secondaryToolbarButton-firstPage-icon);
    }

    #lastPage::before{
      mask-image:var(--secondaryToolbarButton-lastPage-icon);
    }

    #pageRotateCcw::before{
      mask-image:var(--secondaryToolbarButton-rotateCcw-icon);
    }

    #pageRotateCw::before{
      mask-image:var(--secondaryToolbarButton-rotateCw-icon);
    }

    #cursorSelectTool::before{
      mask-image:var(--secondaryToolbarButton-selectTool-icon);
    }

    #cursorHandTool::before{
      mask-image:var(--secondaryToolbarButton-handTool-icon);
    }

    #scrollPage::before{
      mask-image:var(--secondaryToolbarButton-scrollPage-icon);
    }

    #scrollVertical::before{
      mask-image:var(--secondaryToolbarButton-scrollVertical-icon);
    }

    #scrollHorizontal::before{
      mask-image:var(--secondaryToolbarButton-scrollHorizontal-icon);
    }

    #scrollWrapped::before{
      mask-image:var(--secondaryToolbarButton-scrollWrapped-icon);
    }

    #spreadNone::before{
      mask-image:var(--secondaryToolbarButton-spreadNone-icon);
    }

    #spreadOdd::before{
      mask-image:var(--secondaryToolbarButton-spreadOdd-icon);
    }

    #spreadEven::before{
      mask-image:var(--secondaryToolbarButton-spreadEven-icon);
    }

    #imageAltTextSettings::before{
      mask-image:var(--secondaryToolbarButton-imageAltTextSettings-icon);
    }

    #documentProperties::before{
      mask-image:var(--secondaryToolbarButton-documentProperties-icon);
    }
  }
}

#findbar{
  background-color:var(--toolbar-bg-color);
  cursor:default;
  font:message-box;
  font-size:12px;
  height:auto;
  inset-inline-start:64px;
  line-height:14px;
  margin:4px 2px;
  min-width:300px;
  padding:0 4px;
  position:absolute;
  text-align:left;
  top:var(--toolbar-height);
  z-index:30000;

  *{
    float:inline-start;
    position:relative;
  }

  > div{
    height:var(--toolbar-height);
  }

  :is(button, input){
    font:message-box;
    outline:none;
  }

  input{
    &[type="checkbox"]{
      pointer-events:none;

      &:checked + .toolbarLabel{
        background-color:var(--toggled-btn-bg-color) !important;
        color:var(--toggled-btn-color);
      }
    }
  }

  label{
    user-select:none;
  }

  :is(label:hover, input:focus-visible + label){
    background-color:var(--button-hover-color);
    color:var(--toggled-btn-color);
  }

  #findbarInputContainer{
    margin-inline-end:4px;

    #findInput{
      width:200px;

      &::placeholder{
        font-style:normal;
      }

      .loadingInput:has(> &[data-status="pending"])::after{
        display:block;
        visibility:visible;
      }

      &[data-status="notFound"]{
        background-color:rgb(255 102 102);
      }
    }

    #findPrevious::before{
      mask-image:var(--findbarButton-previous-icon);
    }

    #findNext::before{
      mask-image:var(--findbarButton-next-icon);
    }
  }

  #findbarMessageContainer{
    #findResultsCount{
      background-color:rgb(217 217 217);
      color:rgb(82 82 82);
      margin:5px;
      padding:4px 5px;
      text-align:center;
    }

    #findMsg{
      &[data-status="notFound"]{
        font-weight:bold;
      }
    }

    *:empty{
      display:none;
    }
  }

  &.wrapContainers{
    > div{
      clear:both;
    }

    > #findbarMessageContainer{
      height:auto;

      > *{
        clear:both;
      }
    }
  }

  @media all and (max-width: 690px){
    &{
      inset-inline-start:34px;
    }
  }
}

@page{
  margin:0;
}

#printContainer{
  display:none;
}

@media print{
  body{
    background:rgb(0 0 0 / 0) none;
  }
  body[data-pdfjsprinting] #outerContainer{
    display:none;
  }
  body[data-pdfjsprinting] #printContainer{
    display:block;
  }
  #printContainer{
    height:100%;
  }
  #printContainer > .printedPage{
    page-break-after:always;
    page-break-inside:avoid;
    height:100%;
    width:100%;

    display:flex;
    flex-direction:column;
    justify-content:center;
    align-items:center;
  }

  #printContainer > .xfaPrintedPage .xfaPage{
    position:absolute;
  }

  #printContainer > .xfaPrintedPage{
    page-break-after:always;
    page-break-inside:avoid;
    width:100%;
    height:100%;
    position:relative;
  }

  #printContainer > .printedPage :is(canvas, img){
    max-width:100%;
    max-height:100%;

    direction:ltr;
    display:block;
  }
}

.visibleMediumView{
  display:none !important;
}

@media all and (max-width: 900px){
  #toolbarViewerMiddle{
    display:table;
    margin:auto;
    left:auto;
    position:inherit;
    transform:none;
  }
}

@media all and (max-width: 840px){
  #sidebarContainer{
    background-color:var(--sidebar-narrow-bg-color);
  }
  #outerContainer.sidebarOpen #viewerContainer{
    inset-inline-start:0 !important;
  }
}

@media all and (max-width: 750px){
  :root{
    --editor-toolbar-base-offset:40px;
  }
  #outerContainer .hiddenMediumView{
    display:none !important;
  }
  #outerContainer .visibleMediumView{
    display:inherit !important;
  }
}

@media all and (max-width: 690px){
  .hiddenSmallView,
  .hiddenSmallView *{
    display:none !important;
  }
  .toolbarButtonSpacer{
    width:0;
  }
}

@media all and (max-width: 560px){
  #scaleSelectContainer{
    display:none;
  }
}
PK
!<H�]*��$chrome/pdfjs/content/web/viewer.html<!DOCTYPE html>
<!--
Copyright 2012 Mozilla Foundation

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Adobe CMap resources are covered by their own copyright but the same license:

    Copyright 1990-2015 Adobe Systems Incorporated.

See https://github.com/adobe-type-tools/cmap-resources
-->
<html dir="ltr" mozdisallowselectionprint>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>PDF.js viewer</title>

<!-- This snippet is used in the Firefox extension (included from viewer.html) -->
<script src="resource://pdf.js/build/pdf.mjs" type="module"></script>

  <link rel="stylesheet" href="resource://pdf.js/web/viewer.css">
  <link rel="localization" href="toolkit/pdfviewer/viewer.ftl"/>

  <script src="resource://pdf.js/web/viewer.mjs" type="module"></script>
  </head>

  <body tabindex="1">
    <div id="outerContainer">

      <div id="sidebarContainer">
        <div id="toolbarSidebar">
          <div id="toolbarSidebarLeft">
            <div id="sidebarViewButtons" class="splitToolbarButton toggled" role="radiogroup">
              <button id="viewThumbnail" class="toolbarButton toggled" type="button" title="Show Thumbnails" tabindex="2" data-l10n-id="pdfjs-thumbs-button" role="radio" aria-checked="true" aria-controls="thumbnailView">
                 <span data-l10n-id="pdfjs-thumbs-button-label">Thumbnails</span>
              </button>
              <button id="viewOutline" class="toolbarButton" type="button" title="Show Document Outline (double-click to expand/collapse all items)" tabindex="3" data-l10n-id="pdfjs-document-outline-button" role="radio" aria-checked="false" aria-controls="outlineView">
                 <span data-l10n-id="pdfjs-document-outline-button-label">Document Outline</span>
              </button>
              <button id="viewAttachments" class="toolbarButton" type="button" title="Show Attachments" tabindex="4" data-l10n-id="pdfjs-attachments-button" role="radio" aria-checked="false" aria-controls="attachmentsView">
                 <span data-l10n-id="pdfjs-attachments-button-label">Attachments</span>
              </button>
              <button id="viewLayers" class="toolbarButton" type="button" title="Show Layers (double-click to reset all layers to the default state)" tabindex="5" data-l10n-id="pdfjs-layers-button" role="radio" aria-checked="false" aria-controls="layersView">
                 <span data-l10n-id="pdfjs-layers-button-label">Layers</span>
              </button>
            </div>
          </div>

          <div id="toolbarSidebarRight">
            <div id="outlineOptionsContainer">
              <div class="verticalToolbarSeparator"></div>

              <button id="currentOutlineItem" class="toolbarButton" type="button" disabled="disabled" title="Find Current Outline Item" tabindex="6" data-l10n-id="pdfjs-current-outline-item-button">
                <span data-l10n-id="pdfjs-current-outline-item-button-label">Current Outline Item</span>
              </button>
            </div>
          </div>
        </div>
        <div id="sidebarContent">
          <div id="thumbnailView">
          </div>
          <div id="outlineView" class="hidden">
          </div>
          <div id="attachmentsView" class="hidden">
          </div>
          <div id="layersView" class="hidden">
          </div>
        </div>
        <div id="sidebarResizer"></div>
      </div>  <!-- sidebarContainer -->

      <div id="mainContainer">
        <div class="findbar hidden doorHanger" id="findbar">
          <div id="findbarInputContainer">
            <span class="loadingInput end">
              <input id="findInput" class="toolbarField" title="Find" placeholder="Find in document…" tabindex="91" data-l10n-id="pdfjs-find-input" aria-invalid="false">
            </span>
            <div class="splitToolbarButton">
              <button id="findPrevious" class="toolbarButton" type="button" title="Find the previous occurrence of the phrase" tabindex="92" data-l10n-id="pdfjs-find-previous-button">
                <span data-l10n-id="pdfjs-find-previous-button-label">Previous</span>
              </button>
              <div class="splitToolbarButtonSeparator"></div>
              <button id="findNext" class="toolbarButton" type="button" title="Find the next occurrence of the phrase" tabindex="93" data-l10n-id="pdfjs-find-next-button">
                <span data-l10n-id="pdfjs-find-next-button-label">Next</span>
              </button>
            </div>
          </div>

          <div id="findbarOptionsOneContainer">
            <input type="checkbox" id="findHighlightAll" class="toolbarField" tabindex="94">
            <label for="findHighlightAll" class="toolbarLabel" data-l10n-id="pdfjs-find-highlight-checkbox">Highlight All</label>
            <input type="checkbox" id="findMatchCase" class="toolbarField" tabindex="95">
            <label for="findMatchCase" class="toolbarLabel" data-l10n-id="pdfjs-find-match-case-checkbox-label">Match Case</label>
          </div>
          <div id="findbarOptionsTwoContainer">
            <input type="checkbox" id="findMatchDiacritics" class="toolbarField" tabindex="96">
            <label for="findMatchDiacritics" class="toolbarLabel" data-l10n-id="pdfjs-find-match-diacritics-checkbox-label">Match Diacritics</label>
            <input type="checkbox" id="findEntireWord" class="toolbarField" tabindex="97">
            <label for="findEntireWord" class="toolbarLabel" data-l10n-id="pdfjs-find-entire-word-checkbox-label">Whole Words</label>
          </div>

          <div id="findbarMessageContainer" aria-live="polite">
            <span id="findResultsCount" class="toolbarLabel"></span>
            <span id="findMsg" class="toolbarLabel"></span>
          </div>
        </div>  <!-- findbar -->

        <div class="editorParamsToolbar hidden doorHangerRight" id="editorHighlightParamsToolbar">
          <div id="highlightParamsToolbarContainer" class="editorParamsToolbarContainer">
            <div id="editorHighlightColorPicker" class="colorPicker">
              <span id="highlightColorPickerLabel" class="editorParamsLabel" data-l10n-id="pdfjs-editor-highlight-colorpicker-label">Highlight color</span>
            </div>
            <div id="editorHighlightThickness">
              <label for="editorFreeHighlightThickness" class="editorParamsLabel" data-l10n-id="pdfjs-editor-free-highlight-thickness-input">Thickness</label>
              <div class="thicknessPicker">
                <input type="range" id="editorFreeHighlightThickness" class="editorParamsSlider" data-l10n-id="pdfjs-editor-free-highlight-thickness-title" value="12" min="8" max="24" step="1" tabindex="101">
              </div>
            </div>
            <div id="editorHighlightVisibility">
              <div class="divider"></div>
              <div class="toggler">
                <label for="editorHighlightShowAll" class="editorParamsLabel" data-l10n-id="pdfjs-editor-highlight-show-all-button-label">Show all</label>
                <button id="editorHighlightShowAll" class="toggle-button" type="button" data-l10n-id="pdfjs-editor-highlight-show-all-button" aria-pressed="true" tabindex="102"></button>
              </div>
            </div>
          </div>
        </div>

        <div class="editorParamsToolbar hidden doorHangerRight" id="editorFreeTextParamsToolbar">
          <div class="editorParamsToolbarContainer">
            <div class="editorParamsSetter">
              <label for="editorFreeTextColor" class="editorParamsLabel" data-l10n-id="pdfjs-editor-free-text-color-input">Color</label>
              <input type="color" id="editorFreeTextColor" class="editorParamsColor" tabindex="103">
            </div>
            <div class="editorParamsSetter">
              <label for="editorFreeTextFontSize" class="editorParamsLabel" data-l10n-id="pdfjs-editor-free-text-size-input">Size</label>
              <input type="range" id="editorFreeTextFontSize" class="editorParamsSlider" value="10" min="5" max="100" step="1" tabindex="104">
            </div>
          </div>
        </div>

        <div class="editorParamsToolbar hidden doorHangerRight" id="editorInkParamsToolbar">
          <div class="editorParamsToolbarContainer">
            <div class="editorParamsSetter">
              <label for="editorInkColor" class="editorParamsLabel" data-l10n-id="pdfjs-editor-ink-color-input">Color</label>
              <input type="color" id="editorInkColor" class="editorParamsColor" tabindex="105">
            </div>
            <div class="editorParamsSetter">
              <label for="editorInkThickness" class="editorParamsLabel" data-l10n-id="pdfjs-editor-ink-thickness-input">Thickness</label>
              <input type="range" id="editorInkThickness" class="editorParamsSlider" value="1" min="1" max="20" step="1" tabindex="106">
            </div>
            <div class="editorParamsSetter">
              <label for="editorInkOpacity" class="editorParamsLabel" data-l10n-id="pdfjs-editor-ink-opacity-input">Opacity</label>
              <input type="range" id="editorInkOpacity" class="editorParamsSlider" value="100" min="1" max="100" step="1" tabindex="107">
            </div>
          </div>
        </div>

        <div class="editorParamsToolbar hidden doorHangerRight" id="editorStampParamsToolbar">
          <div class="editorParamsToolbarContainer">
            <button id="editorStampAddImage" class="toolbarButton labeled" type="button" title="Add image" tabindex="108" data-l10n-id="pdfjs-editor-stamp-add-image-button">
              <span class="editorParamsLabel" data-l10n-id="pdfjs-editor-stamp-add-image-button-label">Add image</span>
            </button>
          </div>
        </div>

        <div id="secondaryToolbar" class="secondaryToolbar hidden doorHangerRight">
          <div id="secondaryToolbarButtonContainer">

            <button id="secondaryPrint" class="toolbarButton labeled visibleMediumView" type="button" title="Print" tabindex="52" data-l10n-id="pdfjs-print-button">
              <span data-l10n-id="pdfjs-print-button-label">Print</span>
            </button>

            <button id="secondaryDownload" class="toolbarButton labeled visibleMediumView" type="button" title="Save" tabindex="53" data-l10n-id="pdfjs-save-button">
              <span data-l10n-id="pdfjs-save-button-label">Save</span>
            </button>

          <div class="horizontalToolbarSeparator visibleMediumView"></div>

            <button id="presentationMode" class="toolbarButton labeled" type="button" title="Switch to Presentation Mode" tabindex="54" data-l10n-id="pdfjs-presentation-mode-button">
              <span data-l10n-id="pdfjs-presentation-mode-button-label">Presentation Mode</span>
            </button>

            <a href="#" id="viewBookmark" class="toolbarButton labeled" title="Current Page (View URL from Current Page)" tabindex="55" data-l10n-id="pdfjs-bookmark-button">
              <span data-l10n-id="pdfjs-bookmark-button-label">Current Page</span>
            </a>

            <div id="viewBookmarkSeparator" class="horizontalToolbarSeparator"></div>

            <button id="firstPage" class="toolbarButton labeled" type="button" title="Go to First Page" tabindex="56" data-l10n-id="pdfjs-first-page-button">
              <span data-l10n-id="pdfjs-first-page-button-label">Go to First Page</span>
            </button>
            <button id="lastPage" class="toolbarButton labeled" type="button" title="Go to Last Page" tabindex="57" data-l10n-id="pdfjs-last-page-button">
              <span data-l10n-id="pdfjs-last-page-button-label">Go to Last Page</span>
            </button>

            <div class="horizontalToolbarSeparator"></div>

            <button id="pageRotateCw" class="toolbarButton labeled" type="button" title="Rotate Clockwise" tabindex="58" data-l10n-id="pdfjs-page-rotate-cw-button">
              <span data-l10n-id="pdfjs-page-rotate-cw-button-label">Rotate Clockwise</span>
            </button>
            <button id="pageRotateCcw" class="toolbarButton labeled" type="button" title="Rotate Counterclockwise" tabindex="59" data-l10n-id="pdfjs-page-rotate-ccw-button">
              <span data-l10n-id="pdfjs-page-rotate-ccw-button-label">Rotate Counterclockwise</span>
            </button>

            <div class="horizontalToolbarSeparator"></div>

            <div id="cursorToolButtons" role="radiogroup">
              <button id="cursorSelectTool" class="toolbarButton labeled toggled" type="button" title="Enable Text Selection Tool" tabindex="60" data-l10n-id="pdfjs-cursor-text-select-tool-button" role="radio" aria-checked="true">
                <span data-l10n-id="pdfjs-cursor-text-select-tool-button-label">Text Selection Tool</span>
              </button>
              <button id="cursorHandTool" class="toolbarButton labeled" type="button" title="Enable Hand Tool" tabindex="61" data-l10n-id="pdfjs-cursor-hand-tool-button" role="radio" aria-checked="false">
                <span data-l10n-id="pdfjs-cursor-hand-tool-button-label">Hand Tool</span>
              </button>
            </div>

            <div class="horizontalToolbarSeparator"></div>

            <div id="scrollModeButtons" role="radiogroup">
              <button id="scrollPage" class="toolbarButton labeled" type="button" title="Use Page Scrolling" tabindex="62" data-l10n-id="pdfjs-scroll-page-button" role="radio" aria-checked="false">
                <span data-l10n-id="pdfjs-scroll-page-button-label">Page Scrolling</span>
              </button>
              <button id="scrollVertical" class="toolbarButton labeled toggled" type="button" title="Use Vertical Scrolling" tabindex="63" data-l10n-id="pdfjs-scroll-vertical-button" role="radio" aria-checked="true">
                <span data-l10n-id="pdfjs-scroll-vertical-button-label" >Vertical Scrolling</span>
              </button>
              <button id="scrollHorizontal" class="toolbarButton labeled" type="button" title="Use Horizontal Scrolling" tabindex="64" data-l10n-id="pdfjs-scroll-horizontal-button" role="radio" aria-checked="false">
                <span data-l10n-id="pdfjs-scroll-horizontal-button-label">Horizontal Scrolling</span>
              </button>
              <button id="scrollWrapped" class="toolbarButton labeled" type="button" title="Use Wrapped Scrolling" tabindex="65" data-l10n-id="pdfjs-scroll-wrapped-button" role="radio" aria-checked="false">
                <span data-l10n-id="pdfjs-scroll-wrapped-button-label">Wrapped Scrolling</span>
              </button>
            </div>

            <div class="horizontalToolbarSeparator"></div>

            <div id="spreadModeButtons" role="radiogroup">
              <button id="spreadNone" class="toolbarButton labeled toggled" type="button" title="Do not join page spreads" tabindex="66" data-l10n-id="pdfjs-spread-none-button" role="radio" aria-checked="true">
                <span data-l10n-id="pdfjs-spread-none-button-label">No Spreads</span>
              </button>
              <button id="spreadOdd" class="toolbarButton labeled" type="button" title="Join page spreads starting with odd-numbered pages" tabindex="67" data-l10n-id="pdfjs-spread-odd-button" role="radio" aria-checked="false">
                <span data-l10n-id="pdfjs-spread-odd-button-label">Odd Spreads</span>
              </button>
              <button id="spreadEven" class="toolbarButton labeled" type="button" title="Join page spreads starting with even-numbered pages" tabindex="68" data-l10n-id="pdfjs-spread-even-button" role="radio" aria-checked="false">
                <span data-l10n-id="pdfjs-spread-even-button-label">Even Spreads</span>
              </button>
            </div>

            <div id="imageAltTextSettingsSeparator" class="horizontalToolbarSeparator hidden"></div>
            <button id="imageAltTextSettings" type="button" class="toolbarButton labeled hidden" title="Image alt text settings" tabindex="69" data-l10n-id="pdfjs-image-alt-text-settings-button" aria-controls="altTextSettingsDialog">
              <span data-l10n-id="pdfjs-image-alt-text-settings-button-label">Image alt text settings</span>
            </button>

            <div class="horizontalToolbarSeparator"></div>

            <button id="documentProperties" class="toolbarButton labeled" type="button" title="Document Properties…" tabindex="70" data-l10n-id="pdfjs-document-properties-button" aria-controls="documentPropertiesDialog">
              <span data-l10n-id="pdfjs-document-properties-button-label">Document Properties…</span>
            </button>
          </div>
        </div>  <!-- secondaryToolbar -->

        <div class="toolbar">
          <div id="toolbarContainer">
            <div id="toolbarViewer">
              <div id="toolbarViewerLeft">
                <button id="sidebarToggle" class="toolbarButton" type="button" title="Toggle Sidebar" tabindex="11" data-l10n-id="pdfjs-toggle-sidebar-button" aria-expanded="false" aria-controls="sidebarContainer">
                  <span data-l10n-id="pdfjs-toggle-sidebar-button-label">Toggle Sidebar</span>
                </button>
                <div class="toolbarButtonSpacer"></div>
                <button id="viewFind" class="toolbarButton" type="button" title="Find in Document" tabindex="12" data-l10n-id="pdfjs-findbar-button" aria-expanded="false" aria-controls="findbar">
                  <span data-l10n-id="pdfjs-findbar-button-label">Find</span>
                </button>
                <div class="splitToolbarButton hiddenSmallView">
                  <button class="toolbarButton" title="Previous Page" id="previous" type="button" tabindex="13" data-l10n-id="pdfjs-previous-button">
                    <span data-l10n-id="pdfjs-previous-button-label">Previous</span>
                  </button>
                  <div class="splitToolbarButtonSeparator"></div>
                  <button class="toolbarButton" title="Next Page" id="next" type="button" tabindex="14" data-l10n-id="pdfjs-next-button">
                    <span data-l10n-id="pdfjs-next-button-label">Next</span>
                  </button>
                </div>
                <span class="loadingInput start">
                  <input type="number" id="pageNumber" class="toolbarField" title="Page" value="1" min="1" tabindex="15" data-l10n-id="pdfjs-page-input" autocomplete="off">
                </span>
                <span id="numPages" class="toolbarLabel"></span>
              </div>
              <div id="toolbarViewerRight">
                <div id="editorModeButtons" class="splitToolbarButton toggled" role="radiogroup">
                  <button id="editorHighlight" class="toolbarButton" type="button" disabled="disabled" title="Highlight" role="radio" aria-checked="false" aria-controls="editorHighlightParamsToolbar" tabindex="31" data-l10n-id="pdfjs-editor-highlight-button">
                    <span data-l10n-id="pdfjs-editor-highlight-button-label">Highlight</span>
                  </button>
                  <button id="editorFreeText" class="toolbarButton" type="button" disabled="disabled" title="Text" role="radio" aria-checked="false" aria-controls="editorFreeTextParamsToolbar" tabindex="32" data-l10n-id="pdfjs-editor-free-text-button">
                    <span data-l10n-id="pdfjs-editor-free-text-button-label">Text</span>
                  </button>
                  <button id="editorInk" class="toolbarButton" type="button" disabled="disabled" title="Draw" role="radio" aria-checked="false" aria-controls="editorInkParamsToolbar" tabindex="33" data-l10n-id="pdfjs-editor-ink-button">
                    <span data-l10n-id="pdfjs-editor-ink-button-label">Draw</span>
                  </button>
                  <button id="editorStamp" class="toolbarButton" type="button" disabled="disabled" title="Add or edit images" role="radio" aria-checked="false" aria-controls="editorStampParamsToolbar" tabindex="34" data-l10n-id="pdfjs-editor-stamp-button">
                    <span data-l10n-id="pdfjs-editor-stamp-button-label">Add or edit images</span>
                  </button>
                </div>

                <div id="editorModeSeparator" class="verticalToolbarSeparator"></div>

                <button id="print" class="toolbarButton hiddenMediumView" type="button" title="Print" tabindex="41" data-l10n-id="pdfjs-print-button">
                  <span data-l10n-id="pdfjs-print-button-label">Print</span>
                </button>

                <button id="download" class="toolbarButton hiddenMediumView" type="button" title="Save" tabindex="42" data-l10n-id="pdfjs-save-button">
                  <span data-l10n-id="pdfjs-save-button-label">Save</span>
                </button>

                <div class="verticalToolbarSeparator hiddenMediumView"></div>

                <button id="secondaryToolbarToggle" class="toolbarButton" type="button" title="Tools" tabindex="43" data-l10n-id="pdfjs-tools-button" aria-expanded="false" aria-controls="secondaryToolbar">
                  <span data-l10n-id="pdfjs-tools-button-label">Tools</span>
                </button>
              </div>
              <div id="toolbarViewerMiddle">
                <div class="splitToolbarButton">
                  <button id="zoomOut" class="toolbarButton" type="button" title="Zoom Out" tabindex="21" data-l10n-id="pdfjs-zoom-out-button">
                    <span data-l10n-id="pdfjs-zoom-out-button-label">Zoom Out</span>
                  </button>
                  <div class="splitToolbarButtonSeparator"></div>
                  <button id="zoomIn" class="toolbarButton" type="button" title="Zoom In" tabindex="22" data-l10n-id="pdfjs-zoom-in-button">
                    <span data-l10n-id="pdfjs-zoom-in-button-label">Zoom In</span>
                   </button>
                </div>
                <span id="scaleSelectContainer" class="dropdownToolbarButton">
                  <select id="scaleSelect" title="Zoom" tabindex="23" data-l10n-id="pdfjs-zoom-select">
                    <option id="pageAutoOption" title="" value="auto" selected="selected" data-l10n-id="pdfjs-page-scale-auto">Automatic Zoom</option>
                    <option id="pageActualOption" title="" value="page-actual" data-l10n-id="pdfjs-page-scale-actual">Actual Size</option>
                    <option id="pageFitOption" title="" value="page-fit" data-l10n-id="pdfjs-page-scale-fit">Page Fit</option>
                    <option id="pageWidthOption" title="" value="page-width" data-l10n-id="pdfjs-page-scale-width">Page Width</option>
                    <option id="customScaleOption" title="" value="custom" disabled="disabled" hidden="true" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 0 }'>0%</option>
                    <option title="" value="0.5" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 50 }'>50%</option>
                    <option title="" value="0.75" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 75 }'>75%</option>
                    <option title="" value="1" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 100 }'>100%</option>
                    <option title="" value="1.25" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 125 }'>125%</option>
                    <option title="" value="1.5" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 150 }'>150%</option>
                    <option title="" value="2" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 200 }'>200%</option>
                    <option title="" value="3" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 300 }'>300%</option>
                    <option title="" value="4" data-l10n-id="pdfjs-page-scale-percent" data-l10n-args='{ "scale": 400 }'>400%</option>
                  </select>
                </span>
              </div>
            </div>
            <div id="loadingBar">
              <div class="progress">
                <div class="glimmer">
                </div>
              </div>
            </div>
          </div>
        </div>

        <div id="viewerContainer" tabindex="0">
          <div id="viewer" class="pdfViewer"></div>
        </div>
      </div> <!-- mainContainer -->

      <div id="dialogContainer">
        <dialog id="passwordDialog">
          <div class="row">
            <label for="password" id="passwordText" data-l10n-id="pdfjs-password-label">Enter the password to open this PDF file:</label>
          </div>
          <div class="row">
            <input type="password" id="password" class="toolbarField">
          </div>
          <div class="buttonRow">
            <button id="passwordCancel" class="dialogButton" type="button"><span data-l10n-id="pdfjs-password-cancel-button">Cancel</span></button>
            <button id="passwordSubmit" class="dialogButton" type="button"><span data-l10n-id="pdfjs-password-ok-button">OK</span></button>
          </div>
        </dialog>
        <dialog id="documentPropertiesDialog">
          <div class="row">
            <span id="fileNameLabel" data-l10n-id="pdfjs-document-properties-file-name">File name:</span>
            <p id="fileNameField" aria-labelledby="fileNameLabel">-</p>
          </div>
          <div class="row">
            <span id="fileSizeLabel" data-l10n-id="pdfjs-document-properties-file-size">File size:</span>
            <p id="fileSizeField" aria-labelledby="fileSizeLabel">-</p>
          </div>
          <div class="separator"></div>
          <div class="row">
            <span id="titleLabel" data-l10n-id="pdfjs-document-properties-title">Title:</span>
            <p id="titleField" aria-labelledby="titleLabel">-</p>
          </div>
          <div class="row">
            <span id="authorLabel" data-l10n-id="pdfjs-document-properties-author">Author:</span>
            <p id="authorField" aria-labelledby="authorLabel">-</p>
          </div>
          <div class="row">
            <span id="subjectLabel" data-l10n-id="pdfjs-document-properties-subject">Subject:</span>
            <p id="subjectField" aria-labelledby="subjectLabel">-</p>
          </div>
          <div class="row">
            <span id="keywordsLabel" data-l10n-id="pdfjs-document-properties-keywords">Keywords:</span>
            <p id="keywordsField" aria-labelledby="keywordsLabel">-</p>
          </div>
          <div class="row">
            <span id="creationDateLabel" data-l10n-id="pdfjs-document-properties-creation-date">Creation Date:</span>
            <p id="creationDateField" aria-labelledby="creationDateLabel">-</p>
          </div>
          <div class="row">
            <span id="modificationDateLabel" data-l10n-id="pdfjs-document-properties-modification-date">Modification Date:</span>
            <p id="modificationDateField" aria-labelledby="modificationDateLabel">-</p>
          </div>
          <div class="row">
            <span id="creatorLabel" data-l10n-id="pdfjs-document-properties-creator">Creator:</span>
            <p id="creatorField" aria-labelledby="creatorLabel">-</p>
          </div>
          <div class="separator"></div>
          <div class="row">
            <span id="producerLabel" data-l10n-id="pdfjs-document-properties-producer">PDF Producer:</span>
            <p id="producerField" aria-labelledby="producerLabel">-</p>
          </div>
          <div class="row">
            <span id="versionLabel" data-l10n-id="pdfjs-document-properties-version">PDF Version:</span>
            <p id="versionField" aria-labelledby="versionLabel">-</p>
          </div>
          <div class="row">
            <span id="pageCountLabel" data-l10n-id="pdfjs-document-properties-page-count">Page Count:</span>
            <p id="pageCountField" aria-labelledby="pageCountLabel">-</p>
          </div>
          <div class="row">
            <span id="pageSizeLabel" data-l10n-id="pdfjs-document-properties-page-size">Page Size:</span>
            <p id="pageSizeField" aria-labelledby="pageSizeLabel">-</p>
          </div>
          <div class="separator"></div>
          <div class="row">
            <span id="linearizedLabel" data-l10n-id="pdfjs-document-properties-linearized">Fast Web View:</span>
            <p id="linearizedField" aria-labelledby="linearizedLabel">-</p>
          </div>
          <div class="buttonRow">
            <button id="documentPropertiesClose" class="dialogButton" type="button"><span data-l10n-id="pdfjs-document-properties-close-button">Close</span></button>
          </div>
        </dialog>
        <dialog class="dialog altText" id="altTextDialog" aria-labelledby="dialogLabel" aria-describedby="dialogDescription">
          <div id="altTextContainer" class="mainContainer">
            <div id="overallDescription">
              <span id="dialogLabel" data-l10n-id="pdfjs-editor-alt-text-dialog-label" class="title">Choose an option</span>
              <span id="dialogDescription" data-l10n-id="pdfjs-editor-alt-text-dialog-description">
                Alt text (alternative text) helps when people can’t see the image or when it doesn’t load.
              </span>
            </div>
            <div id="addDescription">
              <div class="radio">
                <div class="radioButton">
                  <input type="radio" id="descriptionButton" name="altTextOption" tabindex="0" aria-describedby="descriptionAreaLabel" checked>
                  <label for="descriptionButton" data-l10n-id="pdfjs-editor-alt-text-add-description-label">Add a description</label>
                </div>
                <div class="radioLabel">
                  <span id="descriptionAreaLabel" data-l10n-id="pdfjs-editor-alt-text-add-description-description">
                    Aim for 1-2 sentences that describe the subject, setting, or actions.
                  </span>
                </div>
              </div>
              <div class="descriptionArea">
                <textarea id="descriptionTextarea" placeholder="For example, “A young man sits down at a table to eat a meal”" aria-labelledby="descriptionAreaLabel" data-l10n-id="pdfjs-editor-alt-text-textarea" tabindex="0"></textarea>
              </div>
            </div>
            <div id="markAsDecorative">
              <div class="radio">
                <div class="radioButton">
                  <input type="radio" id="decorativeButton" name="altTextOption" aria-describedby="decorativeLabel">
                  <label for="decorativeButton" data-l10n-id="pdfjs-editor-alt-text-mark-decorative-label">Mark as decorative</label>
                </div>
                <div class="radioLabel">
                  <span id="decorativeLabel" data-l10n-id="pdfjs-editor-alt-text-mark-decorative-description">
                    This is used for ornamental images, like borders or watermarks.
                  </span>
                </div>
              </div>
            </div>
            <div id="buttons">
              <button id="altTextCancel" class="secondaryButton" type="button" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
              <button id="altTextSave" class="primaryButton" type="button" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
            </div>
          </div>
        </dialog>
        <dialog class="dialog newAltText" id="newAltTextDialog" aria-labelledby="newAltTextTitle" aria-describedby="newAltTextDescription" tabindex="0">
          <div id="newAltTextContainer" class="mainContainer">
            <div class="title">
              <span id="newAltTextTitle" data-l10n-id="pdfjs-editor-new-alt-text-dialog-edit-label" role="sectionhead" tabindex="0">Edit alt text (image description)</span>
            </div>
            <div id="mainContent">
              <div id="descriptionAndSettings">
                <div id="descriptionInstruction">
                  <div id="newAltTextDescriptionContainer">
                    <div class="altTextSpinner" role="status" aria-live="polite"></div>
                    <textarea id="newAltTextDescriptionTextarea" placeholder="Write your description here…" aria-labelledby="descriptionAreaLabel" data-l10n-id="pdfjs-editor-new-alt-text-textarea" tabindex="0"></textarea>
                  </div>
                  <span id="newAltTextDescription" role="note" data-l10n-id="pdfjs-editor-new-alt-text-description">Short description for people who can’t see the image or when the image doesn’t load.</span>
                  <div id="newAltTextDisclaimer" role="note"><div><span data-l10n-id="pdfjs-editor-new-alt-text-disclaimer1">This alt text was created automatically and may be inaccurate.</span> <a href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank" rel="noopener noreferrer" id="newAltTextLearnMore" data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url" tabindex="0">Learn more</a></div></div>
                </div>
                <div id="newAltTextCreateAutomatically" class="toggler">
                  <button id="newAltTextCreateAutomaticallyButton" class="toggle-button" type="button" aria-pressed="true" tabindex="0"></button>
                  <label for="newAltTextCreateAutomaticallyButton" class="togglerLabel" data-l10n-id="pdfjs-editor-new-alt-text-create-automatically-button-label">Create alt text automatically</label>
                </div>
                <div id="newAltTextDownloadModel" class="hidden">
                  <span id="newAltTextDownloadModelDescription" data-l10n-id="pdfjs-editor-new-alt-text-ai-model-downloading-progress" aria-valuemin="0" data-l10n-args='{ "totalSize": 0, "downloadedSize": 0 }'>Downloading alt text AI model (0 of 0 MB)</span>
                </div>
              </div>
              <div id="newAltTextImagePreview"></div>
            </div>
            <div id="newAltTextError" class="messageBar">
              <div>
                <div>
                  <span class="title" data-l10n-id="pdfjs-editor-new-alt-text-error-title">Couldn’t create alt text automatically</span>
                  <span  class="description" data-l10n-id="pdfjs-editor-new-alt-text-error-description">Please write your own alt text or try again later.</span>
                </div>
                <button id="newAltTextCloseButton" class="closeButton" type="button" tabindex="0" title="Close"><span data-l10n-id="pdfjs-editor-new-alt-text-error-close-button">Close</span></button>
              </div>
            </div>
            <div id="newAltTextButtons" class="dialogButtonsGroup">
              <button id="newAltTextCancel" type="button" class="secondaryButton hidden" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-cancel-button">Cancel</span></button>
              <button id="newAltTextNotNow" type="button" class="secondaryButton" tabindex="0"><span data-l10n-id="pdfjs-editor-new-alt-text-not-now-button">Not now</span></button>
              <button id="newAltTextSave" type="button" class="primaryButton" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-save-button">Save</span></button>
            </div>
          </div>
        </dialog>

        <dialog class="dialog" id="altTextSettingsDialog" aria-labelledby="altTextSettingsTitle">
          <div id="altTextSettingsContainer" class="mainContainer">
            <div class="title">
              <span id="altTextSettingsTitle" data-l10n-id="pdfjs-editor-alt-text-settings-dialog-label" role="sectionhead" tabindex="0" class="title">Image alt text settings</span>
            </div>
            <div id="automaticAltText">
              <span data-l10n-id="pdfjs-editor-alt-text-settings-automatic-title">Automatic alt text</span>
              <div id="automaticSettings">
                <div id="createModelSetting">
                  <div class="toggler">
                    <button id="createModelButton" type="button" class="toggle-button" aria-pressed="true" tabindex="0"></button>
                    <label for="createModelButton" class="togglerLabel" data-l10n-id="pdfjs-editor-alt-text-settings-create-model-button-label">Create alt text automatically</label>
                  </div>
                  <div id="createModelDescription" class="description">
                    <span data-l10n-id="pdfjs-editor-alt-text-settings-create-model-description">Suggests descriptions to help people who can’t see the image or when the image doesn’t load.</span> <a href="https://support.mozilla.org/en-US/kb/pdf-alt-text" target="_blank" rel="noopener noreferrer" id="altTextSettingsLearnMore" data-l10n-id="pdfjs-editor-new-alt-text-disclaimer-learn-more-url" tabindex="0">Learn more</a>
                  </div>
                </div>
                <div id="aiModelSettings">
                  <div>
                    <span data-l10n-id="pdfjs-editor-alt-text-settings-download-model-label" data-l10n-args='{ "totalSize": 180 }'>Alt text AI model (180MB)</span>
                    <div id="aiModelDescription" class="description">
                      <span data-l10n-id="pdfjs-editor-alt-text-settings-ai-model-description">Runs locally on your device so your data stays private. Required for automatic alt text.</span>
                    </div>
                  </div>
                  <button id="deleteModelButton" type="button" class="secondaryButton" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-settings-delete-model-button">Delete</span></button>
                  <button id="downloadModelButton" type="button" class="secondaryButton" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-settings-download-model-button">Download</span></button>
                </div>
              </div>
            </div>
            <div class="dialogSeparator"></div>
            <div id="altTextEditor">
              <span data-l10n-id="pdfjs-editor-alt-text-settings-editor-title">Alt text editor</span>
              <div id="showAltTextEditor">
                <div class="toggler">
                  <button id="showAltTextDialogButton" type="button" class="toggle-button" aria-pressed="true" tabindex="0"></button>
                  <label for="showAltTextDialogButton" class="togglerLabel" data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-button-label">Show alt text editor right away when adding an image</label>
                </div>
                <div id="showAltTextDialogDescription" class="description">
                  <span data-l10n-id="pdfjs-editor-alt-text-settings-show-dialog-description">Helps you make sure all your images have alt text.</span>
                </div>
              </div>
            </div>
            <div id="buttons" class="dialogButtonsGroup">
              <button id="altTextSettingsCloseButton" type="button" class="primaryButton" tabindex="0"><span data-l10n-id="pdfjs-editor-alt-text-settings-close-button">Close</span></button>
            </div>
          </div>
        </dialog>
      </div>  <!-- dialogContainer -->

    </div> <!-- outerContainer -->
    <div id="printContainer"></div>
  </body>
</html>
PK
!<�1T?T?#chrome/pdfjs/content/web/viewer.mjs/**
 * @licstart The following is the entire license notice for the
 * JavaScript code in this page
 *
 * Copyright 2024 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @licend The above is the entire license notice for the
 * JavaScript code in this page
 */

/******/ // The require scope
/******/ var __webpack_require__ = {};
/******/ 
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ 	// define getter functions for harmony exports
/******/ 	__webpack_require__.d = (exports, definition) => {
/******/ 		for(var key in definition) {
/******/ 			if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ 				Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ 			}
/******/ 		}
/******/ 	};
/******/ })();
/******/ 
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ 	__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/ 
/************************************************************************/
var __webpack_exports__ = {};

// EXPORTS
__webpack_require__.d(__webpack_exports__, {
  PDFViewerApplication: () => (/* reexport */ PDFViewerApplication),
  PDFViewerApplicationConstants: () => (/* binding */ AppConstants),
  PDFViewerApplicationOptions: () => (/* reexport */ AppOptions)
});

;// CONCATENATED MODULE: ./web/ui_utils.js
const DEFAULT_SCALE_VALUE = "auto";
const DEFAULT_SCALE = 1.0;
const DEFAULT_SCALE_DELTA = 1.1;
const MIN_SCALE = 0.1;
const MAX_SCALE = 10.0;
const UNKNOWN_SCALE = 0;
const MAX_AUTO_SCALE = 1.25;
const SCROLLBAR_PADDING = 40;
const VERTICAL_PADDING = 5;
const RenderingStates = {
  INITIAL: 0,
  RUNNING: 1,
  PAUSED: 2,
  FINISHED: 3
};
const PresentationModeState = {
  UNKNOWN: 0,
  NORMAL: 1,
  CHANGING: 2,
  FULLSCREEN: 3
};
const SidebarView = {
  UNKNOWN: -1,
  NONE: 0,
  THUMBS: 1,
  OUTLINE: 2,
  ATTACHMENTS: 3,
  LAYERS: 4
};
const TextLayerMode = {
  DISABLE: 0,
  ENABLE: 1,
  ENABLE_PERMISSIONS: 2
};
const ScrollMode = {
  UNKNOWN: -1,
  VERTICAL: 0,
  HORIZONTAL: 1,
  WRAPPED: 2,
  PAGE: 3
};
const SpreadMode = {
  UNKNOWN: -1,
  NONE: 0,
  ODD: 1,
  EVEN: 2
};
const CursorTool = {
  SELECT: 0,
  HAND: 1,
  ZOOM: 2
};
const AutoPrintRegExp = /\bprint\s*\(/;
class OutputScale {
  constructor() {
    const pixelRatio = window.devicePixelRatio || 1;
    this.sx = pixelRatio;
    this.sy = pixelRatio;
  }
  get scaled() {
    return this.sx !== 1 || this.sy !== 1;
  }
}
function scrollIntoView(element, spot, scrollMatches = false) {
  let parent = element.offsetParent;
  if (!parent) {
    console.error("offsetParent is not set -- cannot scroll");
    return;
  }
  let offsetY = element.offsetTop + element.clientTop;
  let offsetX = element.offsetLeft + element.clientLeft;
  while (parent.clientHeight === parent.scrollHeight && parent.clientWidth === parent.scrollWidth || scrollMatches && (parent.classList.contains("markedContent") || getComputedStyle(parent).overflow === "hidden")) {
    offsetY += parent.offsetTop;
    offsetX += parent.offsetLeft;
    parent = parent.offsetParent;
    if (!parent) {
      return;
    }
  }
  if (spot) {
    if (spot.top !== undefined) {
      offsetY += spot.top;
    }
    if (spot.left !== undefined) {
      offsetX += spot.left;
      parent.scrollLeft = offsetX;
    }
  }
  parent.scrollTop = offsetY;
}
function watchScroll(viewAreaElement, callback, abortSignal = undefined) {
  const debounceScroll = function (evt) {
    if (rAF) {
      return;
    }
    rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
      rAF = null;
      const currentX = viewAreaElement.scrollLeft;
      const lastX = state.lastX;
      if (currentX !== lastX) {
        state.right = currentX > lastX;
      }
      state.lastX = currentX;
      const currentY = viewAreaElement.scrollTop;
      const lastY = state.lastY;
      if (currentY !== lastY) {
        state.down = currentY > lastY;
      }
      state.lastY = currentY;
      callback(state);
    });
  };
  const state = {
    right: true,
    down: true,
    lastX: viewAreaElement.scrollLeft,
    lastY: viewAreaElement.scrollTop,
    _eventHandler: debounceScroll
  };
  let rAF = null;
  viewAreaElement.addEventListener("scroll", debounceScroll, {
    useCapture: true,
    signal: abortSignal
  });
  abortSignal?.addEventListener("abort", () => window.cancelAnimationFrame(rAF), {
    once: true
  });
  return state;
}
function parseQueryString(query) {
  const params = new Map();
  for (const [key, value] of new URLSearchParams(query)) {
    params.set(key.toLowerCase(), value);
  }
  return params;
}
const InvisibleCharsRegExp = /[\x00-\x1F]/g;
function removeNullCharacters(str, replaceInvisible = false) {
  if (!InvisibleCharsRegExp.test(str)) {
    return str;
  }
  if (replaceInvisible) {
    return str.replaceAll(InvisibleCharsRegExp, m => m === "\x00" ? "" : " ");
  }
  return str.replaceAll("\x00", "");
}
function binarySearchFirstItem(items, condition, start = 0) {
  let minIndex = start;
  let maxIndex = items.length - 1;
  if (maxIndex < 0 || !condition(items[maxIndex])) {
    return items.length;
  }
  if (condition(items[minIndex])) {
    return minIndex;
  }
  while (minIndex < maxIndex) {
    const currentIndex = minIndex + maxIndex >> 1;
    const currentItem = items[currentIndex];
    if (condition(currentItem)) {
      maxIndex = currentIndex;
    } else {
      minIndex = currentIndex + 1;
    }
  }
  return minIndex;
}
function approximateFraction(x) {
  if (Math.floor(x) === x) {
    return [x, 1];
  }
  const xinv = 1 / x;
  const limit = 8;
  if (xinv > limit) {
    return [1, limit];
  } else if (Math.floor(xinv) === xinv) {
    return [1, xinv];
  }
  const x_ = x > 1 ? xinv : x;
  let a = 0,
    b = 1,
    c = 1,
    d = 1;
  while (true) {
    const p = a + c,
      q = b + d;
    if (q > limit) {
      break;
    }
    if (x_ <= p / q) {
      c = p;
      d = q;
    } else {
      a = p;
      b = q;
    }
  }
  let result;
  if (x_ - a / b < c / d - x_) {
    result = x_ === x ? [a, b] : [b, a];
  } else {
    result = x_ === x ? [c, d] : [d, c];
  }
  return result;
}
function floorToDivide(x, div) {
  return x - x % div;
}
function getPageSizeInches({
  view,
  userUnit,
  rotate
}) {
  const [x1, y1, x2, y2] = view;
  const changeOrientation = rotate % 180 !== 0;
  const width = (x2 - x1) / 72 * userUnit;
  const height = (y2 - y1) / 72 * userUnit;
  return {
    width: changeOrientation ? height : width,
    height: changeOrientation ? width : height
  };
}
function backtrackBeforeAllVisibleElements(index, views, top) {
  if (index < 2) {
    return index;
  }
  let elt = views[index].div;
  let pageTop = elt.offsetTop + elt.clientTop;
  if (pageTop >= top) {
    elt = views[index - 1].div;
    pageTop = elt.offsetTop + elt.clientTop;
  }
  for (let i = index - 2; i >= 0; --i) {
    elt = views[i].div;
    if (elt.offsetTop + elt.clientTop + elt.clientHeight <= pageTop) {
      break;
    }
    index = i;
  }
  return index;
}
function getVisibleElements({
  scrollEl,
  views,
  sortByVisibility = false,
  horizontal = false,
  rtl = false
}) {
  const top = scrollEl.scrollTop,
    bottom = top + scrollEl.clientHeight;
  const left = scrollEl.scrollLeft,
    right = left + scrollEl.clientWidth;
  function isElementBottomAfterViewTop(view) {
    const element = view.div;
    const elementBottom = element.offsetTop + element.clientTop + element.clientHeight;
    return elementBottom > top;
  }
  function isElementNextAfterViewHorizontally(view) {
    const element = view.div;
    const elementLeft = element.offsetLeft + element.clientLeft;
    const elementRight = elementLeft + element.clientWidth;
    return rtl ? elementLeft < right : elementRight > left;
  }
  const visible = [],
    ids = new Set(),
    numViews = views.length;
  let firstVisibleElementInd = binarySearchFirstItem(views, horizontal ? isElementNextAfterViewHorizontally : isElementBottomAfterViewTop);
  if (firstVisibleElementInd > 0 && firstVisibleElementInd < numViews && !horizontal) {
    firstVisibleElementInd = backtrackBeforeAllVisibleElements(firstVisibleElementInd, views, top);
  }
  let lastEdge = horizontal ? right : -1;
  for (let i = firstVisibleElementInd; i < numViews; i++) {
    const view = views[i],
      element = view.div;
    const currentWidth = element.offsetLeft + element.clientLeft;
    const currentHeight = element.offsetTop + element.clientTop;
    const viewWidth = element.clientWidth,
      viewHeight = element.clientHeight;
    const viewRight = currentWidth + viewWidth;
    const viewBottom = currentHeight + viewHeight;
    if (lastEdge === -1) {
      if (viewBottom >= bottom) {
        lastEdge = viewBottom;
      }
    } else if ((horizontal ? currentWidth : currentHeight) > lastEdge) {
      break;
    }
    if (viewBottom <= top || currentHeight >= bottom || viewRight <= left || currentWidth >= right) {
      continue;
    }
    const hiddenHeight = Math.max(0, top - currentHeight) + Math.max(0, viewBottom - bottom);
    const hiddenWidth = Math.max(0, left - currentWidth) + Math.max(0, viewRight - right);
    const fractionHeight = (viewHeight - hiddenHeight) / viewHeight,
      fractionWidth = (viewWidth - hiddenWidth) / viewWidth;
    const percent = fractionHeight * fractionWidth * 100 | 0;
    visible.push({
      id: view.id,
      x: currentWidth,
      y: currentHeight,
      view,
      percent,
      widthPercent: fractionWidth * 100 | 0
    });
    ids.add(view.id);
  }
  const first = visible[0],
    last = visible.at(-1);
  if (sortByVisibility) {
    visible.sort(function (a, b) {
      const pc = a.percent - b.percent;
      if (Math.abs(pc) > 0.001) {
        return -pc;
      }
      return a.id - b.id;
    });
  }
  return {
    first,
    last,
    views: visible,
    ids
  };
}
function normalizeWheelEventDirection(evt) {
  let delta = Math.hypot(evt.deltaX, evt.deltaY);
  const angle = Math.atan2(evt.deltaY, evt.deltaX);
  if (-0.25 * Math.PI < angle && angle < 0.75 * Math.PI) {
    delta = -delta;
  }
  return delta;
}
function normalizeWheelEventDelta(evt) {
  const deltaMode = evt.deltaMode;
  let delta = normalizeWheelEventDirection(evt);
  const MOUSE_PIXELS_PER_LINE = 30;
  const MOUSE_LINES_PER_PAGE = 30;
  if (deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
    delta /= MOUSE_PIXELS_PER_LINE * MOUSE_LINES_PER_PAGE;
  } else if (deltaMode === WheelEvent.DOM_DELTA_LINE) {
    delta /= MOUSE_LINES_PER_PAGE;
  }
  return delta;
}
function isValidRotation(angle) {
  return Number.isInteger(angle) && angle % 90 === 0;
}
function isValidScrollMode(mode) {
  return Number.isInteger(mode) && Object.values(ScrollMode).includes(mode) && mode !== ScrollMode.UNKNOWN;
}
function isValidSpreadMode(mode) {
  return Number.isInteger(mode) && Object.values(SpreadMode).includes(mode) && mode !== SpreadMode.UNKNOWN;
}
function isPortraitOrientation(size) {
  return size.width <= size.height;
}
const animationStarted = new Promise(function (resolve) {
  window.requestAnimationFrame(resolve);
});
const docStyle = document.documentElement.style;
function clamp(v, min, max) {
  return Math.min(Math.max(v, min), max);
}
class ProgressBar {
  #classList = null;
  #disableAutoFetchTimeout = null;
  #percent = 0;
  #style = null;
  #visible = true;
  constructor(bar) {
    this.#classList = bar.classList;
    this.#style = bar.style;
  }
  get percent() {
    return this.#percent;
  }
  set percent(val) {
    this.#percent = clamp(val, 0, 100);
    if (isNaN(val)) {
      this.#classList.add("indeterminate");
      return;
    }
    this.#classList.remove("indeterminate");
    this.#style.setProperty("--progressBar-percent", `${this.#percent}%`);
  }
  setWidth(viewer) {
    if (!viewer) {
      return;
    }
    const container = viewer.parentNode;
    const scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
    if (scrollbarWidth > 0) {
      this.#style.setProperty("--progressBar-end-offset", `${scrollbarWidth}px`);
    }
  }
  setDisableAutoFetch(delay = 5000) {
    if (this.#percent === 100 || isNaN(this.#percent)) {
      return;
    }
    if (this.#disableAutoFetchTimeout) {
      clearTimeout(this.#disableAutoFetchTimeout);
    }
    this.show();
    this.#disableAutoFetchTimeout = setTimeout(() => {
      this.#disableAutoFetchTimeout = null;
      this.hide();
    }, delay);
  }
  hide() {
    if (!this.#visible) {
      return;
    }
    this.#visible = false;
    this.#classList.add("hidden");
  }
  show() {
    if (this.#visible) {
      return;
    }
    this.#visible = true;
    this.#classList.remove("hidden");
  }
}
function getActiveOrFocusedElement() {
  let curRoot = document;
  let curActiveOrFocused = curRoot.activeElement || curRoot.querySelector(":focus");
  while (curActiveOrFocused?.shadowRoot) {
    curRoot = curActiveOrFocused.shadowRoot;
    curActiveOrFocused = curRoot.activeElement || curRoot.querySelector(":focus");
  }
  return curActiveOrFocused;
}
function apiPageLayoutToViewerModes(layout) {
  let scrollMode = ScrollMode.VERTICAL,
    spreadMode = SpreadMode.NONE;
  switch (layout) {
    case "SinglePage":
      scrollMode = ScrollMode.PAGE;
      break;
    case "OneColumn":
      break;
    case "TwoPageLeft":
      scrollMode = ScrollMode.PAGE;
    case "TwoColumnLeft":
      spreadMode = SpreadMode.ODD;
      break;
    case "TwoPageRight":
      scrollMode = ScrollMode.PAGE;
    case "TwoColumnRight":
      spreadMode = SpreadMode.EVEN;
      break;
  }
  return {
    scrollMode,
    spreadMode
  };
}
function apiPageModeToSidebarView(mode) {
  switch (mode) {
    case "UseNone":
      return SidebarView.NONE;
    case "UseThumbs":
      return SidebarView.THUMBS;
    case "UseOutlines":
      return SidebarView.OUTLINE;
    case "UseAttachments":
      return SidebarView.ATTACHMENTS;
    case "UseOC":
      return SidebarView.LAYERS;
  }
  return SidebarView.NONE;
}
function toggleCheckedBtn(button, toggle, view = null) {
  button.classList.toggle("toggled", toggle);
  button.setAttribute("aria-checked", toggle);
  view?.classList.toggle("hidden", !toggle);
}
function toggleExpandedBtn(button, toggle, view = null) {
  button.classList.toggle("toggled", toggle);
  button.setAttribute("aria-expanded", toggle);
  view?.classList.toggle("hidden", !toggle);
}

;// CONCATENATED MODULE: ./web/app_options.js
const OptionKind = {
  BROWSER: 0x01,
  VIEWER: 0x02,
  API: 0x04,
  WORKER: 0x08,
  EVENT_DISPATCH: 0x10,
  PREFERENCE: 0x80
};
const Type = {
  BOOLEAN: 0x01,
  NUMBER: 0x02,
  OBJECT: 0x04,
  STRING: 0x08,
  UNDEFINED: 0x10
};
const defaultOptions = {
  allowedGlobalEvents: {
    value: null,
    kind: OptionKind.BROWSER
  },
  canvasMaxAreaInBytes: {
    value: -1,
    kind: OptionKind.BROWSER + OptionKind.API
  },
  isInAutomation: {
    value: false,
    kind: OptionKind.BROWSER
  },
  localeProperties: {
    value: null,
    kind: OptionKind.BROWSER
  },
  nimbusDataStr: {
    value: "",
    kind: OptionKind.BROWSER
  },
  supportsCaretBrowsingMode: {
    value: false,
    kind: OptionKind.BROWSER
  },
  supportsDocumentFonts: {
    value: true,
    kind: OptionKind.BROWSER
  },
  supportsIntegratedFind: {
    value: false,
    kind: OptionKind.BROWSER
  },
  supportsMouseWheelZoomCtrlKey: {
    value: true,
    kind: OptionKind.BROWSER
  },
  supportsMouseWheelZoomMetaKey: {
    value: true,
    kind: OptionKind.BROWSER
  },
  supportsPinchToZoom: {
    value: true,
    kind: OptionKind.BROWSER
  },
  toolbarDensity: {
    value: 0,
    kind: OptionKind.BROWSER + OptionKind.EVENT_DISPATCH
  },
  altTextLearnMoreUrl: {
    value: "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/pdf-alt-text",
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  annotationEditorMode: {
    value: 0,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  annotationMode: {
    value: 2,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  cursorToolOnLoad: {
    value: 0,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  debuggerSrc: {
    value: "./debugger.mjs",
    kind: OptionKind.VIEWER
  },
  defaultZoomDelay: {
    value: 400,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  defaultZoomValue: {
    value: "",
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  disableHistory: {
    value: false,
    kind: OptionKind.VIEWER
  },
  disablePageLabels: {
    value: false,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  enableAltText: {
    value: false,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  enableAltTextModelDownload: {
    value: true,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE + OptionKind.EVENT_DISPATCH
  },
  enableGuessAltText: {
    value: true,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE + OptionKind.EVENT_DISPATCH
  },
  enableHighlightFloatingButton: {
    value: false,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  enableNewAltTextWhenAddingImage: {
    value: true,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  enablePermissions: {
    value: false,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  enablePrintAutoRotate: {
    value: true,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  enableScripting: {
    value: true,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  enableUpdatedAddImage: {
    value: false,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  externalLinkRel: {
    value: "noopener noreferrer nofollow",
    kind: OptionKind.VIEWER
  },
  externalLinkTarget: {
    value: 0,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  highlightEditorColors: {
    value: "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F",
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  historyUpdateUrl: {
    value: false,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  ignoreDestinationZoom: {
    value: false,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  imageResourcesPath: {
    value: "resource://pdf.js/web/images/",
    kind: OptionKind.VIEWER
  },
  maxCanvasPixels: {
    value: 2 ** 25,
    kind: OptionKind.VIEWER
  },
  forcePageColors: {
    value: false,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  pageColorsBackground: {
    value: "Canvas",
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  pageColorsForeground: {
    value: "CanvasText",
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  pdfBugEnabled: {
    value: false,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  printResolution: {
    value: 150,
    kind: OptionKind.VIEWER
  },
  sidebarViewOnLoad: {
    value: -1,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  scrollModeOnLoad: {
    value: -1,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  spreadModeOnLoad: {
    value: -1,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  textLayerMode: {
    value: 1,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  viewOnLoad: {
    value: 0,
    kind: OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  cMapPacked: {
    value: true,
    kind: OptionKind.API
  },
  cMapUrl: {
    value: "resource://pdf.js/web/cmaps/",
    kind: OptionKind.API
  },
  disableAutoFetch: {
    value: false,
    kind: OptionKind.API + OptionKind.PREFERENCE
  },
  disableFontFace: {
    value: false,
    kind: OptionKind.API + OptionKind.PREFERENCE
  },
  disableRange: {
    value: false,
    kind: OptionKind.API + OptionKind.PREFERENCE
  },
  disableStream: {
    value: false,
    kind: OptionKind.API + OptionKind.PREFERENCE
  },
  docBaseUrl: {
    value: "",
    kind: OptionKind.API
  },
  enableHWA: {
    value: false,
    kind: OptionKind.API + OptionKind.VIEWER + OptionKind.PREFERENCE
  },
  enableXfa: {
    value: true,
    kind: OptionKind.API + OptionKind.PREFERENCE
  },
  fontExtraProperties: {
    value: false,
    kind: OptionKind.API
  },
  isEvalSupported: {
    value: true,
    kind: OptionKind.API
  },
  isOffscreenCanvasSupported: {
    value: true,
    kind: OptionKind.API
  },
  maxImageSize: {
    value: -1,
    kind: OptionKind.API
  },
  pdfBug: {
    value: false,
    kind: OptionKind.API
  },
  standardFontDataUrl: {
    value: "resource://pdf.js/web/standard_fonts/",
    kind: OptionKind.API
  },
  useSystemFonts: {
    value: undefined,
    kind: OptionKind.API,
    type: Type.BOOLEAN + Type.UNDEFINED
  },
  verbosity: {
    value: 1,
    kind: OptionKind.API
  },
  workerPort: {
    value: null,
    kind: OptionKind.WORKER
  },
  workerSrc: {
    value: "resource://pdf.js/build/pdf.worker.mjs",
    kind: OptionKind.WORKER
  }
};
class AppOptions {
  static eventBus;
  static #opts = new Map();
  static {
    for (const name in defaultOptions) {
      this.#opts.set(name, defaultOptions[name].value);
    }
  }
  static get(name) {
    return this.#opts.get(name);
  }
  static getAll(kind = null, defaultOnly = false) {
    const options = Object.create(null);
    for (const name in defaultOptions) {
      const defaultOpt = defaultOptions[name];
      if (kind && !(kind & defaultOpt.kind)) {
        continue;
      }
      options[name] = !defaultOnly ? this.#opts.get(name) : defaultOpt.value;
    }
    return options;
  }
  static set(name, value) {
    this.setAll({
      [name]: value
    });
  }
  static setAll(options, prefs = false) {
    let events;
    for (const name in options) {
      const defaultOpt = defaultOptions[name],
        userOpt = options[name];
      if (!defaultOpt || !(typeof userOpt === typeof defaultOpt.value || Type[(typeof userOpt).toUpperCase()] & defaultOpt.type)) {
        continue;
      }
      const {
        kind
      } = defaultOpt;
      if (prefs && !(kind & OptionKind.BROWSER || kind & OptionKind.PREFERENCE)) {
        continue;
      }
      if (this.eventBus && kind & OptionKind.EVENT_DISPATCH) {
        (events ||= new Map()).set(name, userOpt);
      }
      this.#opts.set(name, userOpt);
    }
    if (events) {
      for (const [name, value] of events) {
        this.eventBus.dispatch(name.toLowerCase(), {
          source: this,
          value
        });
      }
    }
  }
}

;// CONCATENATED MODULE: ./web/pdf_link_service.js

const DEFAULT_LINK_REL = "noopener noreferrer nofollow";
const LinkTarget = {
  NONE: 0,
  SELF: 1,
  BLANK: 2,
  PARENT: 3,
  TOP: 4
};
class PDFLinkService {
  externalLinkEnabled = true;
  constructor({
    eventBus,
    externalLinkTarget = null,
    externalLinkRel = null,
    ignoreDestinationZoom = false
  } = {}) {
    this.eventBus = eventBus;
    this.externalLinkTarget = externalLinkTarget;
    this.externalLinkRel = externalLinkRel;
    this._ignoreDestinationZoom = ignoreDestinationZoom;
    this.baseUrl = null;
    this.pdfDocument = null;
    this.pdfViewer = null;
    this.pdfHistory = null;
  }
  setDocument(pdfDocument, baseUrl = null) {
    this.baseUrl = baseUrl;
    this.pdfDocument = pdfDocument;
  }
  setViewer(pdfViewer) {
    this.pdfViewer = pdfViewer;
  }
  setHistory(pdfHistory) {
    this.pdfHistory = pdfHistory;
  }
  get pagesCount() {
    return this.pdfDocument ? this.pdfDocument.numPages : 0;
  }
  get page() {
    return this.pdfDocument ? this.pdfViewer.currentPageNumber : 1;
  }
  set page(value) {
    if (this.pdfDocument) {
      this.pdfViewer.currentPageNumber = value;
    }
  }
  get rotation() {
    return this.pdfDocument ? this.pdfViewer.pagesRotation : 0;
  }
  set rotation(value) {
    if (this.pdfDocument) {
      this.pdfViewer.pagesRotation = value;
    }
  }
  get isInPresentationMode() {
    return this.pdfDocument ? this.pdfViewer.isInPresentationMode : false;
  }
  async goToDestination(dest) {
    if (!this.pdfDocument) {
      return;
    }
    let namedDest, explicitDest, pageNumber;
    if (typeof dest === "string") {
      namedDest = dest;
      explicitDest = await this.pdfDocument.getDestination(dest);
    } else {
      namedDest = null;
      explicitDest = await dest;
    }
    if (!Array.isArray(explicitDest)) {
      console.error(`goToDestination: "${explicitDest}" is not a valid destination array, for dest="${dest}".`);
      return;
    }
    const [destRef] = explicitDest;
    if (destRef && typeof destRef === "object") {
      pageNumber = this.pdfDocument.cachedPageNumber(destRef);
      if (!pageNumber) {
        try {
          pageNumber = (await this.pdfDocument.getPageIndex(destRef)) + 1;
        } catch {
          console.error(`goToDestination: "${destRef}" is not a valid page reference, for dest="${dest}".`);
          return;
        }
      }
    } else if (Number.isInteger(destRef)) {
      pageNumber = destRef + 1;
    }
    if (!pageNumber || pageNumber < 1 || pageNumber > this.pagesCount) {
      console.error(`goToDestination: "${pageNumber}" is not a valid page number, for dest="${dest}".`);
      return;
    }
    if (this.pdfHistory) {
      this.pdfHistory.pushCurrentPosition();
      this.pdfHistory.push({
        namedDest,
        explicitDest,
        pageNumber
      });
    }
    this.pdfViewer.scrollPageIntoView({
      pageNumber,
      destArray: explicitDest,
      ignoreDestinationZoom: this._ignoreDestinationZoom
    });
  }
  goToPage(val) {
    if (!this.pdfDocument) {
      return;
    }
    const pageNumber = typeof val === "string" && this.pdfViewer.pageLabelToPageNumber(val) || val | 0;
    if (!(Number.isInteger(pageNumber) && pageNumber > 0 && pageNumber <= this.pagesCount)) {
      console.error(`PDFLinkService.goToPage: "${val}" is not a valid page.`);
      return;
    }
    if (this.pdfHistory) {
      this.pdfHistory.pushCurrentPosition();
      this.pdfHistory.pushPage(pageNumber);
    }
    this.pdfViewer.scrollPageIntoView({
      pageNumber
    });
  }
  addLinkAttributes(link, url, newWindow = false) {
    if (!url || typeof url !== "string") {
      throw new Error('A valid "url" parameter must provided.');
    }
    const target = newWindow ? LinkTarget.BLANK : this.externalLinkTarget,
      rel = this.externalLinkRel;
    if (this.externalLinkEnabled) {
      link.href = link.title = url;
    } else {
      link.href = "";
      link.title = `Disabled: ${url}`;
      link.onclick = () => false;
    }
    let targetStr = "";
    switch (target) {
      case LinkTarget.NONE:
        break;
      case LinkTarget.SELF:
        targetStr = "_self";
        break;
      case LinkTarget.BLANK:
        targetStr = "_blank";
        break;
      case LinkTarget.PARENT:
        targetStr = "_parent";
        break;
      case LinkTarget.TOP:
        targetStr = "_top";
        break;
    }
    link.target = targetStr;
    link.rel = typeof rel === "string" ? rel : DEFAULT_LINK_REL;
  }
  getDestinationHash(dest) {
    if (typeof dest === "string") {
      if (dest.length > 0) {
        return this.getAnchorUrl("#" + escape(dest));
      }
    } else if (Array.isArray(dest)) {
      const str = JSON.stringify(dest);
      if (str.length > 0) {
        return this.getAnchorUrl("#" + escape(str));
      }
    }
    return this.getAnchorUrl("");
  }
  getAnchorUrl(anchor) {
    return this.baseUrl ? this.baseUrl + anchor : anchor;
  }
  setHash(hash) {
    if (!this.pdfDocument) {
      return;
    }
    let pageNumber, dest;
    if (hash.includes("=")) {
      const params = parseQueryString(hash);
      if (params.has("search")) {
        const query = params.get("search").replaceAll('"', ""),
          phrase = params.get("phrase") === "true";
        this.eventBus.dispatch("findfromurlhash", {
          source: this,
          query: phrase ? query : query.match(/\S+/g)
        });
      }
      if (params.has("page")) {
        pageNumber = params.get("page") | 0 || 1;
      }
      if (params.has("zoom")) {
        const zoomArgs = params.get("zoom").split(",");
        const zoomArg = zoomArgs[0];
        const zoomArgNumber = parseFloat(zoomArg);
        if (!zoomArg.includes("Fit")) {
          dest = [null, {
            name: "XYZ"
          }, zoomArgs.length > 1 ? zoomArgs[1] | 0 : null, zoomArgs.length > 2 ? zoomArgs[2] | 0 : null, zoomArgNumber ? zoomArgNumber / 100 : zoomArg];
        } else if (zoomArg === "Fit" || zoomArg === "FitB") {
          dest = [null, {
            name: zoomArg
          }];
        } else if (zoomArg === "FitH" || zoomArg === "FitBH" || zoomArg === "FitV" || zoomArg === "FitBV") {
          dest = [null, {
            name: zoomArg
          }, zoomArgs.length > 1 ? zoomArgs[1] | 0 : null];
        } else if (zoomArg === "FitR") {
          if (zoomArgs.length !== 5) {
            console.error('PDFLinkService.setHash: Not enough parameters for "FitR".');
          } else {
            dest = [null, {
              name: zoomArg
            }, zoomArgs[1] | 0, zoomArgs[2] | 0, zoomArgs[3] | 0, zoomArgs[4] | 0];
          }
        } else {
          console.error(`PDFLinkService.setHash: "${zoomArg}" is not a valid zoom value.`);
        }
      }
      if (dest) {
        this.pdfViewer.scrollPageIntoView({
          pageNumber: pageNumber || this.page,
          destArray: dest,
          allowNegativeOffset: true
        });
      } else if (pageNumber) {
        this.page = pageNumber;
      }
      if (params.has("pagemode")) {
        this.eventBus.dispatch("pagemode", {
          source: this,
          mode: params.get("pagemode")
        });
      }
      if (params.has("nameddest")) {
        this.goToDestination(params.get("nameddest"));
      }
      if (!params.has("filename") || !params.has("filedest")) {
        return;
      }
      hash = params.get("filedest");
    }
    dest = unescape(hash);
    try {
      dest = JSON.parse(dest);
      if (!Array.isArray(dest)) {
        dest = dest.toString();
      }
    } catch {}
    if (typeof dest === "string" || PDFLinkService.#isValidExplicitDest(dest)) {
      this.goToDestination(dest);
      return;
    }
    console.error(`PDFLinkService.setHash: "${unescape(hash)}" is not a valid destination.`);
  }
  executeNamedAction(action) {
    if (!this.pdfDocument) {
      return;
    }
    switch (action) {
      case "GoBack":
        this.pdfHistory?.back();
        break;
      case "GoForward":
        this.pdfHistory?.forward();
        break;
      case "NextPage":
        this.pdfViewer.nextPage();
        break;
      case "PrevPage":
        this.pdfViewer.previousPage();
        break;
      case "LastPage":
        this.page = this.pagesCount;
        break;
      case "FirstPage":
        this.page = 1;
        break;
      default:
        break;
    }
    this.eventBus.dispatch("namedaction", {
      source: this,
      action
    });
  }
  async executeSetOCGState(action) {
    if (!this.pdfDocument) {
      return;
    }
    const pdfDocument = this.pdfDocument,
      optionalContentConfig = await this.pdfViewer.optionalContentConfigPromise;
    if (pdfDocument !== this.pdfDocument) {
      return;
    }
    optionalContentConfig.setOCGState(action);
    this.pdfViewer.optionalContentConfigPromise = Promise.resolve(optionalContentConfig);
  }
  static #isValidExplicitDest(dest) {
    if (!Array.isArray(dest) || dest.length < 2) {
      return false;
    }
    const [page, zoom, ...args] = dest;
    if (!(typeof page === "object" && Number.isInteger(page?.num) && Number.isInteger(page?.gen)) && !Number.isInteger(page)) {
      return false;
    }
    if (!(typeof zoom === "object" && typeof zoom?.name === "string")) {
      return false;
    }
    const argsLen = args.length;
    let allowNull = true;
    switch (zoom.name) {
      case "XYZ":
        if (argsLen < 2 || argsLen > 3) {
          return false;
        }
        break;
      case "Fit":
      case "FitB":
        return argsLen === 0;
      case "FitH":
      case "FitBH":
      case "FitV":
      case "FitBV":
        if (argsLen > 1) {
          return false;
        }
        break;
      case "FitR":
        if (argsLen !== 4) {
          return false;
        }
        allowNull = false;
        break;
      default:
        return false;
    }
    for (const arg of args) {
      if (!(typeof arg === "number" || allowNull && arg === null)) {
        return false;
      }
    }
    return true;
  }
}
class SimpleLinkService extends PDFLinkService {
  setDocument(pdfDocument, baseUrl = null) {}
}

;// CONCATENATED MODULE: ./web/pdfjs.js
const {
  AbortException,
  AnnotationEditorLayer,
  AnnotationEditorParamsType,
  AnnotationEditorType,
  AnnotationEditorUIManager,
  AnnotationLayer,
  AnnotationMode,
  build,
  CMapCompressionType,
  ColorPicker,
  createValidAbsoluteUrl,
  DOMSVGFactory,
  DrawLayer,
  FeatureTest,
  fetchData,
  getDocument,
  getFilenameFromUrl,
  getPdfFilenameFromUrl,
  getXfaPageViewport,
  GlobalWorkerOptions,
  ImageKind,
  InvalidPDFException,
  isDataScheme,
  isPdfFile,
  MissingPDFException,
  noContextMenu,
  normalizeUnicode,
  OPS,
  PasswordResponses,
  PDFDataRangeTransport,
  PDFDateString,
  PDFWorker,
  PermissionFlag,
  PixelsPerInch,
  RenderingCancelledException,
  setLayerDimensions,
  shadow,
  TextLayer,
  UnexpectedResponseException,
  Util,
  VerbosityLevel,
  version,
  XfaLayer
} = globalThis.pdfjsLib;

;// CONCATENATED MODULE: ./web/event_utils.js
const WaitOnType = {
  EVENT: "event",
  TIMEOUT: "timeout"
};
async function waitOnEventOrTimeout({
  target,
  name,
  delay = 0
}) {
  if (typeof target !== "object" || !(name && typeof name === "string") || !(Number.isInteger(delay) && delay >= 0)) {
    throw new Error("waitOnEventOrTimeout - invalid parameters.");
  }
  const {
    promise,
    resolve
  } = Promise.withResolvers();
  const ac = new AbortController();
  function handler(type) {
    ac.abort();
    clearTimeout(timeout);
    resolve(type);
  }
  const evtMethod = target instanceof EventBus ? "_on" : "addEventListener";
  target[evtMethod](name, handler.bind(null, WaitOnType.EVENT), {
    signal: ac.signal
  });
  const timeout = setTimeout(handler.bind(null, WaitOnType.TIMEOUT), delay);
  return promise;
}
class EventBus {
  #listeners = Object.create(null);
  on(eventName, listener, options = null) {
    this._on(eventName, listener, {
      external: true,
      once: options?.once,
      signal: options?.signal
    });
  }
  off(eventName, listener, options = null) {
    this._off(eventName, listener);
  }
  dispatch(eventName, data) {
    const eventListeners = this.#listeners[eventName];
    if (!eventListeners || eventListeners.length === 0) {
      return;
    }
    let externalListeners;
    for (const {
      listener,
      external,
      once
    } of eventListeners.slice(0)) {
      if (once) {
        this._off(eventName, listener);
      }
      if (external) {
        (externalListeners ||= []).push(listener);
        continue;
      }
      listener(data);
    }
    if (externalListeners) {
      for (const listener of externalListeners) {
        listener(data);
      }
      externalListeners = null;
    }
  }
  _on(eventName, listener, options = null) {
    let rmAbort = null;
    if (options?.signal instanceof AbortSignal) {
      const {
        signal
      } = options;
      if (signal.aborted) {
        console.error("Cannot use an `aborted` signal.");
        return;
      }
      const onAbort = () => this._off(eventName, listener);
      rmAbort = () => signal.removeEventListener("abort", onAbort);
      signal.addEventListener("abort", onAbort);
    }
    const eventListeners = this.#listeners[eventName] ||= [];
    eventListeners.push({
      listener,
      external: options?.external === true,
      once: options?.once === true,
      rmAbort
    });
  }
  _off(eventName, listener, options = null) {
    const eventListeners = this.#listeners[eventName];
    if (!eventListeners) {
      return;
    }
    for (let i = 0, ii = eventListeners.length; i < ii; i++) {
      const evt = eventListeners[i];
      if (evt.listener === listener) {
        evt.rmAbort?.();
        eventListeners.splice(i, 1);
        return;
      }
    }
  }
}
class FirefoxEventBus extends EventBus {
  #externalServices;
  #globalEventNames;
  #isInAutomation;
  constructor(globalEventNames, externalServices, isInAutomation) {
    super();
    this.#globalEventNames = globalEventNames;
    this.#externalServices = externalServices;
    this.#isInAutomation = isInAutomation;
  }
  dispatch(eventName, data) {
    super.dispatch(eventName, data);
    if (this.#isInAutomation) {
      const detail = Object.create(null);
      if (data) {
        for (const key in data) {
          const value = data[key];
          if (key === "source") {
            if (value === window || value === document) {
              return;
            }
            continue;
          }
          detail[key] = value;
        }
      }
      const event = new CustomEvent(eventName, {
        bubbles: true,
        cancelable: true,
        detail
      });
      document.dispatchEvent(event);
    }
    if (this.#globalEventNames?.has(eventName)) {
      this.#externalServices.dispatchGlobalEvent({
        eventName,
        detail: data
      });
    }
  }
}

;// CONCATENATED MODULE: ./web/external_services.js
class BaseExternalServices {
  updateFindControlState(data) {}
  updateFindMatchesCount(data) {}
  initPassiveLoading() {}
  reportTelemetry(data) {}
  async createL10n() {
    throw new Error("Not implemented: createL10n");
  }
  createScripting() {
    throw new Error("Not implemented: createScripting");
  }
  updateEditorStates(data) {
    throw new Error("Not implemented: updateEditorStates");
  }
  dispatchGlobalEvent(_event) {}
}

;// CONCATENATED MODULE: ./web/preferences.js

class BasePreferences {
  #defaults = Object.freeze({
    altTextLearnMoreUrl: "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/pdf-alt-text",
    annotationEditorMode: 0,
    annotationMode: 2,
    cursorToolOnLoad: 0,
    defaultZoomDelay: 400,
    defaultZoomValue: "",
    disablePageLabels: false,
    enableAltText: false,
    enableAltTextModelDownload: true,
    enableGuessAltText: true,
    enableHighlightFloatingButton: false,
    enableNewAltTextWhenAddingImage: true,
    enablePermissions: false,
    enablePrintAutoRotate: true,
    enableScripting: true,
    enableUpdatedAddImage: false,
    externalLinkTarget: 0,
    highlightEditorColors: "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F",
    historyUpdateUrl: false,
    ignoreDestinationZoom: false,
    forcePageColors: false,
    pageColorsBackground: "Canvas",
    pageColorsForeground: "CanvasText",
    pdfBugEnabled: false,
    sidebarViewOnLoad: -1,
    scrollModeOnLoad: -1,
    spreadModeOnLoad: -1,
    textLayerMode: 1,
    viewOnLoad: 0,
    disableAutoFetch: false,
    disableFontFace: false,
    disableRange: false,
    disableStream: false,
    enableHWA: false,
    enableXfa: true
  });
  #initializedPromise = null;
  constructor() {
    this.#initializedPromise = this._readFromStorage(this.#defaults).then(({
      browserPrefs,
      prefs
    }) => {
      AppOptions.setAll({
        ...browserPrefs,
        ...prefs
      }, true);
    });
    window.addEventListener("updatedPreference", async ({
      detail: {
        name,
        value
      }
    }) => {
      await this.#initializedPromise;
      AppOptions.setAll({
        [name]: value
      }, true);
    });
  }
  async _writeToStorage(prefObj) {
    throw new Error("Not implemented: _writeToStorage");
  }
  async _readFromStorage(prefObj) {
    throw new Error("Not implemented: _readFromStorage");
  }
  async reset() {
    throw new Error("Please use `about:config` to change preferences.");
  }
  async set(name, value) {
    await this.#initializedPromise;
    AppOptions.setAll({
      [name]: value
    }, true);
    await this._writeToStorage({
      [name]: AppOptions.get(name)
    });
  }
  async get(name) {
    throw new Error("Not implemented: get");
  }
  get initializedPromise() {
    return this.#initializedPromise;
  }
}

;// CONCATENATED MODULE: ./web/l10n.js
class L10n {
  #dir;
  #elements = new Set();
  #lang;
  #l10n;
  constructor({
    lang,
    isRTL
  }, l10n = null) {
    this.#lang = L10n.#fixupLangCode(lang);
    this.#l10n = l10n;
    this.#dir = isRTL ?? L10n.#isRTL(this.#lang) ? "rtl" : "ltr";
  }
  _setL10n(l10n) {
    this.#l10n = l10n;
  }
  getLanguage() {
    return this.#lang;
  }
  getDirection() {
    return this.#dir;
  }
  async get(ids, args = null, fallback) {
    if (Array.isArray(ids)) {
      ids = ids.map(id => ({
        id
      }));
      const messages = await this.#l10n.formatMessages(ids);
      return messages.map(message => message.value);
    }
    const messages = await this.#l10n.formatMessages([{
      id: ids,
      args
    }]);
    return messages[0]?.value || fallback;
  }
  async translate(element) {
    this.#elements.add(element);
    try {
      this.#l10n.connectRoot(element);
      await this.#l10n.translateRoots();
    } catch {}
  }
  async translateOnce(element) {
    try {
      await this.#l10n.translateElements([element]);
    } catch (ex) {
      console.error(`translateOnce: "${ex}".`);
    }
  }
  async destroy() {
    for (const element of this.#elements) {
      this.#l10n.disconnectRoot(element);
    }
    this.#elements.clear();
    this.#l10n.pauseObserving();
  }
  pause() {
    this.#l10n.pauseObserving();
  }
  resume() {
    this.#l10n.resumeObserving();
  }
  static #fixupLangCode(langCode) {
    langCode = langCode?.toLowerCase() || "en-us";
    const PARTIAL_LANG_CODES = {
      en: "en-us",
      es: "es-es",
      fy: "fy-nl",
      ga: "ga-ie",
      gu: "gu-in",
      hi: "hi-in",
      hy: "hy-am",
      nb: "nb-no",
      ne: "ne-np",
      nn: "nn-no",
      pa: "pa-in",
      pt: "pt-pt",
      sv: "sv-se",
      zh: "zh-cn"
    };
    return PARTIAL_LANG_CODES[langCode] || langCode;
  }
  static #isRTL(lang) {
    const shortCode = lang.split("-", 1)[0];
    return ["ar", "he", "fa", "ps", "ur"].includes(shortCode);
  }
}
const GenericL10n = null;

;// CONCATENATED MODULE: ./web/firefoxcom.js






let viewerApp = {
  initialized: false
};
function initCom(app) {
  viewerApp = app;
}
class FirefoxCom {
  static requestAsync(action, data) {
    return new Promise(resolve => {
      this.request(action, data, resolve);
    });
  }
  static request(action, data, callback = null) {
    const request = document.createTextNode("");
    if (callback) {
      request.addEventListener("pdf.js.response", event => {
        const response = event.detail.response;
        event.target.remove();
        callback(response);
      }, {
        once: true
      });
    }
    document.documentElement.append(request);
    const sender = new CustomEvent("pdf.js.message", {
      bubbles: true,
      cancelable: false,
      detail: {
        action,
        data,
        responseExpected: !!callback
      }
    });
    request.dispatchEvent(sender);
  }
}
class DownloadManager {
  #openBlobUrls = new WeakMap();
  downloadData(data, filename, contentType) {
    const blobUrl = URL.createObjectURL(new Blob([data], {
      type: contentType
    }));
    FirefoxCom.request("download", {
      blobUrl,
      originalUrl: blobUrl,
      filename,
      isAttachment: true
    });
  }
  openOrDownloadData(data, filename, dest = null) {
    const isPdfData = isPdfFile(filename);
    const contentType = isPdfData ? "application/pdf" : "";
    if (isPdfData) {
      let blobUrl = this.#openBlobUrls.get(data);
      if (!blobUrl) {
        blobUrl = URL.createObjectURL(new Blob([data], {
          type: contentType
        }));
        this.#openBlobUrls.set(data, blobUrl);
      }
      let viewerUrl = blobUrl + "#filename=" + encodeURIComponent(filename);
      if (dest) {
        viewerUrl += `&filedest=${escape(dest)}`;
      }
      try {
        window.open(viewerUrl);
        return true;
      } catch (ex) {
        console.error(`openOrDownloadData: ${ex}`);
        URL.revokeObjectURL(blobUrl);
        this.#openBlobUrls.delete(data);
      }
    }
    this.downloadData(data, filename, contentType);
    return false;
  }
  download(data, url, filename) {
    const blobUrl = data ? URL.createObjectURL(new Blob([data], {
      type: "application/pdf"
    })) : null;
    FirefoxCom.request("download", {
      blobUrl,
      originalUrl: url,
      filename
    });
  }
}
class Preferences extends BasePreferences {
  async _readFromStorage(prefObj) {
    return FirefoxCom.requestAsync("getPreferences", prefObj);
  }
  async _writeToStorage(prefObj) {
    return FirefoxCom.requestAsync("setPreferences", prefObj);
  }
}
(function listenFindEvents() {
  const events = ["find", "findagain", "findhighlightallchange", "findcasesensitivitychange", "findentirewordchange", "findbarclose", "finddiacriticmatchingchange"];
  const findLen = "find".length;
  const handleEvent = function ({
    type,
    detail
  }) {
    if (!viewerApp.initialized) {
      return;
    }
    if (type === "findbarclose") {
      viewerApp.eventBus.dispatch(type, {
        source: window
      });
      return;
    }
    viewerApp.eventBus.dispatch("find", {
      source: window,
      type: type.substring(findLen),
      query: detail.query,
      caseSensitive: !!detail.caseSensitive,
      entireWord: !!detail.entireWord,
      highlightAll: !!detail.highlightAll,
      findPrevious: !!detail.findPrevious,
      matchDiacritics: !!detail.matchDiacritics
    });
  };
  for (const event of events) {
    window.addEventListener(event, handleEvent);
  }
})();
(function listenZoomEvents() {
  const events = ["zoomin", "zoomout", "zoomreset"];
  const handleEvent = function ({
    type,
    detail
  }) {
    if (!viewerApp.initialized) {
      return;
    }
    if (type === "zoomreset" && viewerApp.pdfViewer.currentScaleValue === DEFAULT_SCALE_VALUE) {
      return;
    }
    viewerApp.eventBus.dispatch(type, {
      source: window
    });
  };
  for (const event of events) {
    window.addEventListener(event, handleEvent);
  }
})();
(function listenSaveEvent() {
  const handleEvent = function ({
    type,
    detail
  }) {
    if (!viewerApp.initialized) {
      return;
    }
    viewerApp.eventBus.dispatch("download", {
      source: window
    });
  };
  window.addEventListener("save", handleEvent);
})();
(function listenEditingEvent() {
  const handleEvent = function ({
    detail
  }) {
    if (!viewerApp.initialized) {
      return;
    }
    viewerApp.eventBus.dispatch("editingaction", {
      source: window,
      name: detail.name
    });
  };
  window.addEventListener("editingaction", handleEvent);
})();
class FirefoxComDataRangeTransport extends PDFDataRangeTransport {
  requestDataRange(begin, end) {
    FirefoxCom.request("requestDataRange", {
      begin,
      end
    });
  }
  abort() {
    FirefoxCom.request("abortLoading", null);
  }
}
class FirefoxScripting {
  static async createSandbox(data) {
    const success = await FirefoxCom.requestAsync("createSandbox", data);
    if (!success) {
      throw new Error("Cannot create sandbox.");
    }
  }
  static async dispatchEventInSandbox(event) {
    FirefoxCom.request("dispatchEventInSandbox", event);
  }
  static async destroySandbox() {
    FirefoxCom.request("destroySandbox", null);
  }
}
class MLManager {
  #abortSignal = null;
  #enabled = null;
  #eventBus = null;
  #ready = null;
  #requestResolvers = null;
  hasProgress = false;
  static #AI_ALT_TEXT_MODEL_NAME = "moz-image-to-text";
  constructor({
    altTextLearnMoreUrl,
    enableGuessAltText,
    enableAltTextModelDownload
  }) {
    this.altTextLearnMoreUrl = altTextLearnMoreUrl;
    this.enableAltTextModelDownload = enableAltTextModelDownload;
    this.enableGuessAltText = enableGuessAltText;
  }
  setEventBus(eventBus, abortSignal) {
    this.#eventBus = eventBus;
    this.#abortSignal = abortSignal;
    eventBus._on("enablealttextmodeldownload", ({
      value
    }) => {
      if (this.enableAltTextModelDownload === value) {
        return;
      }
      if (value) {
        this.downloadModel("altText");
      } else {
        this.deleteModel("altText");
      }
    }, {
      signal: abortSignal
    });
    eventBus._on("enableguessalttext", ({
      value
    }) => {
      this.toggleService("altText", value);
    }, {
      signal: abortSignal
    });
  }
  async isEnabledFor(name) {
    return this.enableGuessAltText && !!(await this.#enabled?.get(name));
  }
  isReady(name) {
    return this.#ready?.has(name) ?? false;
  }
  async deleteModel(name) {
    if (name !== "altText" || !this.enableAltTextModelDownload) {
      return;
    }
    this.enableAltTextModelDownload = false;
    this.#ready?.delete(name);
    this.#enabled?.delete(name);
    await this.toggleService("altText", false);
    await FirefoxCom.requestAsync("mlDelete", MLManager.#AI_ALT_TEXT_MODEL_NAME);
  }
  async loadModel(name) {
    if (name === "altText" && this.enableAltTextModelDownload) {
      await this.#loadAltTextEngine(false);
    }
  }
  async downloadModel(name) {
    if (name !== "altText" || this.enableAltTextModelDownload) {
      return null;
    }
    this.enableAltTextModelDownload = true;
    return this.#loadAltTextEngine(true);
  }
  async guess(data) {
    if (data?.name !== "altText") {
      return null;
    }
    const resolvers = this.#requestResolvers ||= new Set();
    const resolver = Promise.withResolvers();
    resolvers.add(resolver);
    data.service = MLManager.#AI_ALT_TEXT_MODEL_NAME;
    FirefoxCom.requestAsync("mlGuess", data).then(response => {
      if (resolvers.has(resolver)) {
        resolver.resolve(response);
        resolvers.delete(resolver);
      }
    }).catch(reason => {
      if (resolvers.has(resolver)) {
        resolver.reject(reason);
        resolvers.delete(resolver);
      }
    });
    return resolver.promise;
  }
  async #cancelAllRequests() {
    if (!this.#requestResolvers) {
      return;
    }
    for (const resolver of this.#requestResolvers) {
      resolver.resolve({
        cancel: true
      });
    }
    this.#requestResolvers.clear();
    this.#requestResolvers = null;
  }
  async toggleService(name, enabled) {
    if (name !== "altText" || this.enableGuessAltText === enabled) {
      return;
    }
    this.enableGuessAltText = enabled;
    if (enabled) {
      if (this.enableAltTextModelDownload) {
        await this.#loadAltTextEngine(false);
      }
    } else {
      this.#cancelAllRequests();
    }
  }
  async #loadAltTextEngine(listenToProgress) {
    if (this.#enabled?.has("altText")) {
      return;
    }
    this.#ready ||= new Set();
    const promise = FirefoxCom.requestAsync("loadAIEngine", {
      service: MLManager.#AI_ALT_TEXT_MODEL_NAME,
      listenToProgress
    }).then(ok => {
      if (ok) {
        this.#ready.add("altText");
      }
      return ok;
    });
    (this.#enabled ||= new Map()).set("altText", promise);
    if (listenToProgress) {
      const ac = new AbortController();
      const signal = AbortSignal.any([this.#abortSignal, ac.signal]);
      this.hasProgress = true;
      window.addEventListener("loadAIEngineProgress", ({
        detail
      }) => {
        this.#eventBus.dispatch("loadaiengineprogress", {
          source: this,
          detail
        });
        if (detail.finished) {
          ac.abort();
          this.hasProgress = false;
        }
      }, {
        signal
      });
      promise.then(ok => {
        if (!ok) {
          ac.abort();
          this.hasProgress = false;
        }
      });
    }
    await promise;
  }
}
class ExternalServices extends BaseExternalServices {
  updateFindControlState(data) {
    FirefoxCom.request("updateFindControlState", data);
  }
  updateFindMatchesCount(data) {
    FirefoxCom.request("updateFindMatchesCount", data);
  }
  initPassiveLoading() {
    let pdfDataRangeTransport;
    window.addEventListener("message", function windowMessage(e) {
      if (e.source !== null) {
        console.warn("Rejected untrusted message from " + e.origin);
        return;
      }
      const args = e.data;
      if (typeof args !== "object" || !("pdfjsLoadAction" in args)) {
        return;
      }
      switch (args.pdfjsLoadAction) {
        case "supportsRangedLoading":
          if (args.done && !args.data) {
            viewerApp._documentError(null);
            break;
          }
          pdfDataRangeTransport = new FirefoxComDataRangeTransport(args.length, args.data, args.done, args.filename);
          viewerApp.open({
            range: pdfDataRangeTransport
          });
          break;
        case "range":
          pdfDataRangeTransport.onDataRange(args.begin, args.chunk);
          break;
        case "rangeProgress":
          pdfDataRangeTransport.onDataProgress(args.loaded);
          break;
        case "progressiveRead":
          pdfDataRangeTransport.onDataProgressiveRead(args.chunk);
          pdfDataRangeTransport.onDataProgress(args.loaded, args.total);
          break;
        case "progressiveDone":
          pdfDataRangeTransport?.onDataProgressiveDone();
          break;
        case "progress":
          viewerApp.progress(args.loaded / args.total);
          break;
        case "complete":
          if (!args.data) {
            viewerApp._documentError(null, {
              message: args.errorCode
            });
            break;
          }
          viewerApp.open({
            data: args.data,
            filename: args.filename
          });
          break;
      }
    });
    FirefoxCom.request("initPassiveLoading", null);
  }
  reportTelemetry(data) {
    FirefoxCom.request("reportTelemetry", data);
  }
  updateEditorStates(data) {
    FirefoxCom.request("updateEditorStates", data);
  }
  async createL10n() {
    await document.l10n.ready;
    return new L10n(AppOptions.get("localeProperties"), document.l10n);
  }
  createScripting() {
    return FirefoxScripting;
  }
  dispatchGlobalEvent(event) {
    FirefoxCom.request("dispatchGlobalEvent", event);
  }
}

;// CONCATENATED MODULE: ./web/new_alt_text_manager.js

class NewAltTextManager {
  #boundCancel = this.#cancel.bind(this);
  #createAutomaticallyButton;
  #currentEditor = null;
  #cancelButton;
  #descriptionContainer;
  #dialog;
  #disclaimer;
  #downloadModel;
  #downloadModelDescription;
  #eventBus;
  #firstTime = false;
  #guessedAltText;
  #hasAI = false;
  #isEditing = null;
  #imagePreview;
  #imageData;
  #isAILoading = false;
  #wasAILoading = false;
  #learnMore;
  #notNowButton;
  #overlayManager;
  #textarea;
  #title;
  #uiManager;
  #previousAltText = null;
  constructor({
    descriptionContainer,
    dialog,
    imagePreview,
    cancelButton,
    disclaimer,
    notNowButton,
    saveButton,
    textarea,
    learnMore,
    errorCloseButton,
    createAutomaticallyButton,
    downloadModel,
    downloadModelDescription,
    title
  }, overlayManager, eventBus) {
    this.#cancelButton = cancelButton;
    this.#createAutomaticallyButton = createAutomaticallyButton;
    this.#descriptionContainer = descriptionContainer;
    this.#dialog = dialog;
    this.#disclaimer = disclaimer;
    this.#notNowButton = notNowButton;
    this.#imagePreview = imagePreview;
    this.#textarea = textarea;
    this.#learnMore = learnMore;
    this.#title = title;
    this.#downloadModel = downloadModel;
    this.#downloadModelDescription = downloadModelDescription;
    this.#overlayManager = overlayManager;
    this.#eventBus = eventBus;
    dialog.addEventListener("close", this.#close.bind(this));
    dialog.addEventListener("contextmenu", event => {
      if (event.target !== this.#textarea) {
        event.preventDefault();
      }
    });
    cancelButton.addEventListener("click", this.#boundCancel);
    notNowButton.addEventListener("click", this.#boundCancel);
    saveButton.addEventListener("click", this.#save.bind(this));
    errorCloseButton.addEventListener("click", () => {
      this.#toggleError(false);
    });
    createAutomaticallyButton.addEventListener("click", async () => {
      const checked = createAutomaticallyButton.getAttribute("aria-pressed") !== "true";
      this.#currentEditor._reportTelemetry({
        action: "pdfjs.image.alt_text.ai_generation_check",
        data: {
          status: checked
        }
      });
      if (this.#uiManager) {
        this.#uiManager.setPreference("enableGuessAltText", checked);
        await this.#uiManager.mlManager.toggleService("altText", checked);
      }
      this.#toggleGuessAltText(checked, false);
    });
    textarea.addEventListener("focus", () => {
      this.#wasAILoading = this.#isAILoading;
      this.#toggleLoading(false);
      this.#toggleTitleAndDisclaimer();
    });
    textarea.addEventListener("blur", () => {
      if (!textarea.value) {
        this.#toggleLoading(this.#wasAILoading);
      }
      this.#toggleTitleAndDisclaimer();
    });
    textarea.addEventListener("input", () => {
      this.#toggleTitleAndDisclaimer();
    });
    eventBus._on("enableguessalttext", ({
      value
    }) => {
      this.#toggleGuessAltText(value, false);
    });
    this.#overlayManager.register(dialog);
    this.#learnMore.addEventListener("click", () => {
      this.#currentEditor._reportTelemetry({
        action: "pdfjs.image.alt_text.info",
        data: {
          topic: "alt_text"
        }
      });
    });
  }
  #toggleLoading(value) {
    if (!this.#uiManager || this.#isAILoading === value) {
      return;
    }
    this.#isAILoading = value;
    this.#descriptionContainer.classList.toggle("loading", value);
  }
  #toggleError(value) {
    if (!this.#uiManager) {
      return;
    }
    this.#dialog.classList.toggle("error", value);
  }
  async #toggleGuessAltText(value, isInitial = false) {
    if (!this.#uiManager) {
      return;
    }
    this.#dialog.classList.toggle("aiDisabled", !value);
    this.#createAutomaticallyButton.setAttribute("aria-pressed", value);
    if (value) {
      const {
        altTextLearnMoreUrl
      } = this.#uiManager.mlManager;
      if (altTextLearnMoreUrl) {
        this.#learnMore.href = altTextLearnMoreUrl;
      }
      this.#mlGuessAltText(isInitial);
    } else {
      this.#toggleLoading(false);
      this.#isAILoading = false;
      this.#toggleTitleAndDisclaimer();
    }
  }
  #toggleNotNow() {
    this.#notNowButton.classList.toggle("hidden", !this.#firstTime);
    this.#cancelButton.classList.toggle("hidden", this.#firstTime);
  }
  #toggleAI(value) {
    if (!this.#uiManager || this.#hasAI === value) {
      return;
    }
    this.#hasAI = value;
    this.#dialog.classList.toggle("noAi", !value);
    this.#toggleTitleAndDisclaimer();
  }
  #toggleTitleAndDisclaimer() {
    const visible = this.#isAILoading || this.#guessedAltText && this.#guessedAltText === this.#textarea.value;
    this.#disclaimer.hidden = !visible;
    const isEditing = this.#isAILoading || !!this.#textarea.value;
    if (this.#isEditing === isEditing) {
      return;
    }
    this.#isEditing = isEditing;
    this.#title.setAttribute("data-l10n-id", isEditing ? "pdfjs-editor-new-alt-text-dialog-edit-label" : "pdfjs-editor-new-alt-text-dialog-add-label");
  }
  async #mlGuessAltText(isInitial) {
    if (this.#isAILoading) {
      return;
    }
    if (this.#textarea.value) {
      return;
    }
    if (isInitial && this.#previousAltText !== null) {
      return;
    }
    this.#guessedAltText = this.#currentEditor.guessedAltText;
    if (this.#previousAltText === null && this.#guessedAltText) {
      this.#addAltText(this.#guessedAltText);
      return;
    }
    this.#toggleLoading(true);
    this.#toggleTitleAndDisclaimer();
    let hasError = false;
    try {
      const altText = await this.#currentEditor.mlGuessAltText(this.#imageData, false);
      if (altText) {
        this.#guessedAltText = altText;
        this.#wasAILoading = this.#isAILoading;
        if (this.#isAILoading) {
          this.#addAltText(altText);
        }
      }
    } catch (e) {
      console.error(e);
      hasError = true;
    }
    this.#toggleLoading(false);
    this.#toggleTitleAndDisclaimer();
    if (hasError && this.#uiManager) {
      this.#toggleError(true);
    }
  }
  #addAltText(altText) {
    if (!this.#uiManager || this.#textarea.value) {
      return;
    }
    this.#textarea.value = altText;
    this.#toggleTitleAndDisclaimer();
  }
  #setProgress() {
    this.#downloadModel.classList.toggle("hidden", false);
    const callback = async ({
      detail: {
        finished,
        total,
        totalLoaded
      }
    }) => {
      const ONE_MEGA_BYTES = 1e6;
      totalLoaded = Math.min(0.99 * total, totalLoaded);
      const totalSize = this.#downloadModelDescription.ariaValueMax = Math.round(total / ONE_MEGA_BYTES);
      const downloadedSize = this.#downloadModelDescription.ariaValueNow = Math.round(totalLoaded / ONE_MEGA_BYTES);
      this.#downloadModelDescription.setAttribute("data-l10n-args", JSON.stringify({
        totalSize,
        downloadedSize
      }));
      if (!finished) {
        return;
      }
      this.#eventBus._off("loadaiengineprogress", callback);
      this.#downloadModel.classList.toggle("hidden", true);
      this.#toggleAI(true);
      if (!this.#uiManager) {
        return;
      }
      const {
        mlManager
      } = this.#uiManager;
      mlManager.toggleService("altText", true);
      this.#toggleGuessAltText(await mlManager.isEnabledFor("altText"), true);
    };
    this.#eventBus._on("loadaiengineprogress", callback);
  }
  async editAltText(uiManager, editor, firstTime) {
    if (this.#currentEditor || !editor) {
      return;
    }
    if (firstTime && editor.hasAltTextData()) {
      editor.altTextFinish();
      return;
    }
    this.#firstTime = firstTime;
    let {
      mlManager
    } = uiManager;
    let hasAI = !!mlManager;
    this.#toggleTitleAndDisclaimer();
    if (mlManager && !mlManager.isReady("altText")) {
      hasAI = false;
      if (mlManager.hasProgress) {
        this.#setProgress();
      } else {
        mlManager = null;
      }
    } else {
      this.#downloadModel.classList.toggle("hidden", true);
    }
    const isAltTextEnabledPromise = mlManager?.isEnabledFor("altText");
    this.#currentEditor = editor;
    this.#uiManager = uiManager;
    this.#uiManager.removeEditListeners();
    ({
      altText: this.#previousAltText
    } = editor.altTextData);
    this.#textarea.value = this.#previousAltText ?? "";
    const AI_MAX_IMAGE_DIMENSION = 224;
    let canvas;
    if (mlManager) {
      ({
        canvas,
        imageData: this.#imageData
      } = editor.copyCanvas(AI_MAX_IMAGE_DIMENSION, true));
      if (hasAI) {
        this.#toggleGuessAltText(await isAltTextEnabledPromise, true);
      }
    } else {
      ({
        canvas
      } = editor.copyCanvas(AI_MAX_IMAGE_DIMENSION, false));
    }
    canvas.setAttribute("role", "presentation");
    this.#imagePreview.append(canvas);
    this.#toggleNotNow();
    this.#toggleAI(hasAI);
    this.#toggleError(false);
    try {
      await this.#overlayManager.open(this.#dialog);
    } catch (ex) {
      this.#close();
      throw ex;
    }
  }
  #cancel() {
    this.#currentEditor.altTextData = {
      cancel: true
    };
    const altText = this.#textarea.value.trim();
    this.#currentEditor._reportTelemetry({
      action: "pdfjs.image.alt_text.dismiss",
      data: {
        alt_text_type: altText ? "present" : "empty",
        flow: this.#firstTime ? "image_add" : "alt_text_edit"
      }
    });
    this.#currentEditor._reportTelemetry({
      action: "pdfjs.image.image_added",
      data: {
        alt_text_modal: true,
        alt_text_type: "skipped"
      }
    });
    this.#finish();
  }
  #finish() {
    if (this.#overlayManager.active === this.#dialog) {
      this.#overlayManager.close(this.#dialog);
    }
  }
  #close() {
    const canvas = this.#imagePreview.firstChild;
    canvas.remove();
    canvas.width = canvas.height = 0;
    this.#imageData = null;
    this.#toggleLoading(false);
    this.#uiManager?.addEditListeners();
    this.#currentEditor.altTextFinish();
    this.#uiManager?.setSelected(this.#currentEditor);
    this.#currentEditor = null;
    this.#uiManager = null;
  }
  #save() {
    const altText = this.#textarea.value.trim();
    this.#currentEditor.altTextData = {
      altText,
      decorative: false
    };
    this.#currentEditor.altTextData.guessedAltText = this.#guessedAltText;
    if (this.#guessedAltText && this.#guessedAltText !== altText) {
      const guessedWords = new Set(this.#guessedAltText.split(/\s+/));
      const words = new Set(altText.split(/\s+/));
      this.#currentEditor._reportTelemetry({
        action: "pdfjs.image.alt_text.user_edit",
        data: {
          total_words: guessedWords.size,
          words_removed: guessedWords.difference(words).size,
          words_added: words.difference(guessedWords).size
        }
      });
    }
    this.#currentEditor._reportTelemetry({
      action: "pdfjs.image.image_added",
      data: {
        alt_text_modal: true,
        alt_text_type: altText ? "present" : "empty"
      }
    });
    this.#currentEditor._reportTelemetry({
      action: "pdfjs.image.alt_text.save",
      data: {
        alt_text_type: altText ? "present" : "empty",
        flow: this.#firstTime ? "image_add" : "alt_text_edit"
      }
    });
    this.#finish();
  }
  destroy() {
    this.#uiManager = null;
    this.#finish();
  }
}
class ImageAltTextSettings {
  #aiModelSettings;
  #createModelButton;
  #downloadModelButton;
  #dialog;
  #eventBus;
  #mlManager;
  #overlayManager;
  #showAltTextDialogButton;
  constructor({
    dialog,
    createModelButton,
    aiModelSettings,
    learnMore,
    closeButton,
    deleteModelButton,
    downloadModelButton,
    showAltTextDialogButton
  }, overlayManager, eventBus, mlManager) {
    this.#dialog = dialog;
    this.#aiModelSettings = aiModelSettings;
    this.#createModelButton = createModelButton;
    this.#downloadModelButton = downloadModelButton;
    this.#showAltTextDialogButton = showAltTextDialogButton;
    this.#overlayManager = overlayManager;
    this.#eventBus = eventBus;
    this.#mlManager = mlManager;
    const {
      altTextLearnMoreUrl
    } = mlManager;
    if (altTextLearnMoreUrl) {
      learnMore.href = altTextLearnMoreUrl;
    }
    dialog.addEventListener("contextmenu", noContextMenu);
    createModelButton.addEventListener("click", async e => {
      const checked = this.#togglePref("enableGuessAltText", e);
      await mlManager.toggleService("altText", checked);
      this.#reportTelemetry({
        type: "stamp",
        action: "pdfjs.image.alt_text.settings_ai_generation_check",
        data: {
          status: checked
        }
      });
    });
    showAltTextDialogButton.addEventListener("click", e => {
      const checked = this.#togglePref("enableNewAltTextWhenAddingImage", e);
      this.#reportTelemetry({
        type: "stamp",
        action: "pdfjs.image.alt_text.settings_edit_alt_text_check",
        data: {
          status: checked
        }
      });
    });
    deleteModelButton.addEventListener("click", this.#delete.bind(this, true));
    downloadModelButton.addEventListener("click", this.#download.bind(this, true));
    closeButton.addEventListener("click", this.#finish.bind(this));
    learnMore.addEventListener("click", () => {
      this.#reportTelemetry({
        type: "stamp",
        action: "pdfjs.image.alt_text.info",
        data: {
          topic: "ai_generation"
        }
      });
    });
    eventBus._on("enablealttextmodeldownload", ({
      value
    }) => {
      if (value) {
        this.#download(false);
      } else {
        this.#delete(false);
      }
    });
    this.#overlayManager.register(dialog);
  }
  #reportTelemetry(data) {
    this.#eventBus.dispatch("reporttelemetry", {
      source: this,
      details: {
        type: "editing",
        data
      }
    });
  }
  async #download(isFromUI = false) {
    if (isFromUI) {
      this.#downloadModelButton.disabled = true;
      const span = this.#downloadModelButton.firstChild;
      span.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-settings-downloading-model-button");
      await this.#mlManager.downloadModel("altText");
      span.setAttribute("data-l10n-id", "pdfjs-editor-alt-text-settings-download-model-button");
      this.#createModelButton.disabled = false;
      this.#setPref("enableGuessAltText", true);
      this.#mlManager.toggleService("altText", true);
      this.#setPref("enableAltTextModelDownload", true);
      this.#downloadModelButton.disabled = false;
    }
    this.#aiModelSettings.classList.toggle("download", false);
    this.#createModelButton.setAttribute("aria-pressed", true);
  }
  async #delete(isFromUI = false) {
    if (isFromUI) {
      await this.#mlManager.deleteModel("altText");
      this.#setPref("enableGuessAltText", false);
      this.#setPref("enableAltTextModelDownload", false);
    }
    this.#aiModelSettings.classList.toggle("download", true);
    this.#createModelButton.disabled = true;
    this.#createModelButton.setAttribute("aria-pressed", false);
  }
  async open({
    enableGuessAltText,
    enableNewAltTextWhenAddingImage
  }) {
    const {
      enableAltTextModelDownload
    } = this.#mlManager;
    this.#createModelButton.disabled = !enableAltTextModelDownload;
    this.#createModelButton.setAttribute("aria-pressed", enableAltTextModelDownload && enableGuessAltText);
    this.#showAltTextDialogButton.setAttribute("aria-pressed", enableNewAltTextWhenAddingImage);
    this.#aiModelSettings.classList.toggle("download", !enableAltTextModelDownload);
    await this.#overlayManager.open(this.#dialog);
    this.#reportTelemetry({
      type: "stamp",
      action: "pdfjs.image.alt_text.settings_displayed"
    });
  }
  #togglePref(name, {
    target
  }) {
    const checked = target.getAttribute("aria-pressed") !== "true";
    this.#setPref(name, checked);
    target.setAttribute("aria-pressed", checked);
    return checked;
  }
  #setPref(name, value) {
    this.#eventBus.dispatch("setpreference", {
      source: this,
      name,
      value
    });
  }
  #finish() {
    if (this.#overlayManager.active === this.#dialog) {
      this.#overlayManager.close(this.#dialog);
    }
  }
}

;// CONCATENATED MODULE: ./web/alt_text_manager.js

class AltTextManager {
  #boundUpdateUIState = this.#updateUIState.bind(this);
  #boundSetPosition = this.#setPosition.bind(this);
  #boundOnClick = this.#onClick.bind(this);
  #currentEditor = null;
  #cancelButton;
  #dialog;
  #eventBus;
  #hasUsedPointer = false;
  #optionDescription;
  #optionDecorative;
  #overlayManager;
  #saveButton;
  #textarea;
  #uiManager;
  #previousAltText = null;
  #svgElement = null;
  #rectElement = null;
  #container;
  #telemetryData = null;
  constructor({
    dialog,
    optionDescription,
    optionDecorative,
    textarea,
    cancelButton,
    saveButton
  }, container, overlayManager, eventBus) {
    this.#dialog = dialog;
    this.#optionDescription = optionDescription;
    this.#optionDecorative = optionDecorative;
    this.#textarea = textarea;
    this.#cancelButton = cancelButton;
    this.#saveButton = saveButton;
    this.#overlayManager = overlayManager;
    this.#eventBus = eventBus;
    this.#container = container;
    dialog.addEventListener("close", this.#close.bind(this));
    dialog.addEventListener("contextmenu", event => {
      if (event.target !== this.#textarea) {
        event.preventDefault();
      }
    });
    cancelButton.addEventListener("click", this.#finish.bind(this));
    saveButton.addEventListener("click", this.#save.bind(this));
    optionDescription.addEventListener("change", this.#boundUpdateUIState);
    optionDecorative.addEventListener("change", this.#boundUpdateUIState);
    this.#overlayManager.register(dialog);
  }
  get _elements() {
    return shadow(this, "_elements", [this.#optionDescription, this.#optionDecorative, this.#textarea, this.#saveButton, this.#cancelButton]);
  }
  #createSVGElement() {
    if (this.#svgElement) {
      return;
    }
    const svgFactory = new DOMSVGFactory();
    const svg = this.#svgElement = svgFactory.createElement("svg");
    svg.setAttribute("width", "0");
    svg.setAttribute("height", "0");
    const defs = svgFactory.createElement("defs");
    svg.append(defs);
    const mask = svgFactory.createElement("mask");
    defs.append(mask);
    mask.setAttribute("id", "alttext-manager-mask");
    mask.setAttribute("maskContentUnits", "objectBoundingBox");
    let rect = svgFactory.createElement("rect");
    mask.append(rect);
    rect.setAttribute("fill", "white");
    rect.setAttribute("width", "1");
    rect.setAttribute("height", "1");
    rect.setAttribute("x", "0");
    rect.setAttribute("y", "0");
    rect = this.#rectElement = svgFactory.createElement("rect");
    mask.append(rect);
    rect.setAttribute("fill", "black");
    this.#dialog.append(svg);
  }
  async editAltText(uiManager, editor) {
    if (this.#currentEditor || !editor) {
      return;
    }
    this.#createSVGElement();
    this.#hasUsedPointer = false;
    for (const element of this._elements) {
      element.addEventListener("click", this.#boundOnClick);
    }
    const {
      altText,
      decorative
    } = editor.altTextData;
    if (decorative === true) {
      this.#optionDecorative.checked = true;
      this.#optionDescription.checked = false;
    } else {
      this.#optionDecorative.checked = false;
      this.#optionDescription.checked = true;
    }
    this.#previousAltText = this.#textarea.value = altText?.trim() || "";
    this.#updateUIState();
    this.#currentEditor = editor;
    this.#uiManager = uiManager;
    this.#uiManager.removeEditListeners();
    this.#eventBus._on("resize", this.#boundSetPosition);
    try {
      await this.#overlayManager.open(this.#dialog);
      this.#setPosition();
    } catch (ex) {
      this.#close();
      throw ex;
    }
  }
  #setPosition() {
    if (!this.#currentEditor) {
      return;
    }
    const dialog = this.#dialog;
    const {
      style
    } = dialog;
    const {
      x: containerX,
      y: containerY,
      width: containerW,
      height: containerH
    } = this.#container.getBoundingClientRect();
    const {
      innerWidth: windowW,
      innerHeight: windowH
    } = window;
    const {
      width: dialogW,
      height: dialogH
    } = dialog.getBoundingClientRect();
    const {
      x,
      y,
      width,
      height
    } = this.#currentEditor.getClientDimensions();
    const MARGIN = 10;
    const isLTR = this.#uiManager.direction === "ltr";
    const xs = Math.max(x, containerX);
    const xe = Math.min(x + width, containerX + containerW);
    const ys = Math.max(y, containerY);
    const ye = Math.min(y + height, containerY + containerH);
    this.#rectElement.setAttribute("width", `${(xe - xs) / windowW}`);
    this.#rectElement.setAttribute("height", `${(ye - ys) / windowH}`);
    this.#rectElement.setAttribute("x", `${xs / windowW}`);
    this.#rectElement.setAttribute("y", `${ys / windowH}`);
    let left = null;
    let top = Math.max(y, 0);
    top += Math.min(windowH - (top + dialogH), 0);
    if (isLTR) {
      if (x + width + MARGIN + dialogW < windowW) {
        left = x + width + MARGIN;
      } else if (x > dialogW + MARGIN) {
        left = x - dialogW - MARGIN;
      }
    } else if (x > dialogW + MARGIN) {
      left = x - dialogW - MARGIN;
    } else if (x + width + MARGIN + dialogW < windowW) {
      left = x + width + MARGIN;
    }
    if (left === null) {
      top = null;
      left = Math.max(x, 0);
      left += Math.min(windowW - (left + dialogW), 0);
      if (y > dialogH + MARGIN) {
        top = y - dialogH - MARGIN;
      } else if (y + height + MARGIN + dialogH < windowH) {
        top = y + height + MARGIN;
      }
    }
    if (top !== null) {
      dialog.classList.add("positioned");
      if (isLTR) {
        style.left = `${left}px`;
      } else {
        style.right = `${windowW - left - dialogW}px`;
      }
      style.top = `${top}px`;
    } else {
      dialog.classList.remove("positioned");
      style.left = "";
      style.top = "";
    }
  }
  #finish() {
    if (this.#overlayManager.active === this.#dialog) {
      this.#overlayManager.close(this.#dialog);
    }
  }
  #close() {
    this.#currentEditor._reportTelemetry(this.#telemetryData || {
      action: "alt_text_cancel",
      alt_text_keyboard: !this.#hasUsedPointer
    });
    this.#telemetryData = null;
    this.#removeOnClickListeners();
    this.#uiManager?.addEditListeners();
    this.#eventBus._off("resize", this.#boundSetPosition);
    this.#currentEditor.altTextFinish();
    this.#currentEditor = null;
    this.#uiManager = null;
  }
  #updateUIState() {
    this.#textarea.disabled = this.#optionDecorative.checked;
  }
  #save() {
    const altText = this.#textarea.value.trim();
    const decorative = this.#optionDecorative.checked;
    this.#currentEditor.altTextData = {
      altText,
      decorative
    };
    this.#telemetryData = {
      action: "alt_text_save",
      alt_text_description: !!altText,
      alt_text_edit: !!this.#previousAltText && this.#previousAltText !== altText,
      alt_text_decorative: decorative,
      alt_text_keyboard: !this.#hasUsedPointer
    };
    this.#finish();
  }
  #onClick(evt) {
    if (evt.detail === 0) {
      return;
    }
    this.#hasUsedPointer = true;
    this.#removeOnClickListeners();
  }
  #removeOnClickListeners() {
    for (const element of this._elements) {
      element.removeEventListener("click", this.#boundOnClick);
    }
  }
  destroy() {
    this.#uiManager = null;
    this.#finish();
    this.#svgElement?.remove();
    this.#svgElement = this.#rectElement = null;
  }
}

;// CONCATENATED MODULE: ./web/annotation_editor_params.js

class AnnotationEditorParams {
  constructor(options, eventBus) {
    this.eventBus = eventBus;
    this.#bindListeners(options);
  }
  #bindListeners({
    editorFreeTextFontSize,
    editorFreeTextColor,
    editorInkColor,
    editorInkThickness,
    editorInkOpacity,
    editorStampAddImage,
    editorFreeHighlightThickness,
    editorHighlightShowAll
  }) {
    const dispatchEvent = (typeStr, value) => {
      this.eventBus.dispatch("switchannotationeditorparams", {
        source: this,
        type: AnnotationEditorParamsType[typeStr],
        value
      });
    };
    editorFreeTextFontSize.addEventListener("input", function () {
      dispatchEvent("FREETEXT_SIZE", this.valueAsNumber);
    });
    editorFreeTextColor.addEventListener("input", function () {
      dispatchEvent("FREETEXT_COLOR", this.value);
    });
    editorInkColor.addEventListener("input", function () {
      dispatchEvent("INK_COLOR", this.value);
    });
    editorInkThickness.addEventListener("input", function () {
      dispatchEvent("INK_THICKNESS", this.valueAsNumber);
    });
    editorInkOpacity.addEventListener("input", function () {
      dispatchEvent("INK_OPACITY", this.valueAsNumber);
    });
    editorStampAddImage.addEventListener("click", () => {
      this.eventBus.dispatch("reporttelemetry", {
        source: this,
        details: {
          type: "editing",
          data: {
            action: "pdfjs.image.add_image_click"
          }
        }
      });
      dispatchEvent("CREATE");
    });
    editorFreeHighlightThickness.addEventListener("input", function () {
      dispatchEvent("HIGHLIGHT_THICKNESS", this.valueAsNumber);
    });
    editorHighlightShowAll.addEventListener("click", function () {
      const checked = this.getAttribute("aria-pressed") === "true";
      this.setAttribute("aria-pressed", !checked);
      dispatchEvent("HIGHLIGHT_SHOW_ALL", !checked);
    });
    this.eventBus._on("annotationeditorparamschanged", evt => {
      for (const [type, value] of evt.details) {
        switch (type) {
          case AnnotationEditorParamsType.FREETEXT_SIZE:
            editorFreeTextFontSize.value = value;
            break;
          case AnnotationEditorParamsType.FREETEXT_COLOR:
            editorFreeTextColor.value = value;
            break;
          case AnnotationEditorParamsType.INK_COLOR:
            editorInkColor.value = value;
            break;
          case AnnotationEditorParamsType.INK_THICKNESS:
            editorInkThickness.value = value;
            break;
          case AnnotationEditorParamsType.INK_OPACITY:
            editorInkOpacity.value = value;
            break;
          case AnnotationEditorParamsType.HIGHLIGHT_THICKNESS:
            editorFreeHighlightThickness.value = value;
            break;
          case AnnotationEditorParamsType.HIGHLIGHT_FREE:
            editorFreeHighlightThickness.disabled = !value;
            break;
          case AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL:
            editorHighlightShowAll.setAttribute("aria-pressed", value);
            break;
        }
      }
    });
  }
}

;// CONCATENATED MODULE: ./web/caret_browsing.js
const PRECISION = 1e-1;
class CaretBrowsingMode {
  #mainContainer;
  #toolBarHeight;
  #viewerContainer;
  constructor(mainContainer, viewerContainer, toolbarContainer) {
    this.#mainContainer = mainContainer;
    this.#viewerContainer = viewerContainer;
    this.#toolBarHeight = toolbarContainer?.getBoundingClientRect().height ?? 0;
  }
  #isOnSameLine(rect1, rect2) {
    const top1 = rect1.y;
    const bot1 = rect1.bottom;
    const mid1 = rect1.y + rect1.height / 2;
    const top2 = rect2.y;
    const bot2 = rect2.bottom;
    const mid2 = rect2.y + rect2.height / 2;
    return top1 <= mid2 && mid2 <= bot1 || top2 <= mid1 && mid1 <= bot2;
  }
  #isUnderOver(rect, x, y, isUp) {
    const midY = rect.y + rect.height / 2;
    return (isUp ? y >= midY : y <= midY) && rect.x - PRECISION <= x && x <= rect.right + PRECISION;
  }
  #isVisible(rect) {
    return rect.top >= this.#toolBarHeight && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
  }
  #getCaretPosition(selection, isUp) {
    const {
      focusNode,
      focusOffset
    } = selection;
    const range = document.createRange();
    range.setStart(focusNode, focusOffset);
    range.setEnd(focusNode, focusOffset);
    const rect = range.getBoundingClientRect();
    return [rect.x, isUp ? rect.top : rect.bottom];
  }
  static #caretPositionFromPoint(x, y) {
    return document.caretPositionFromPoint(x, y);
  }
  #setCaretPositionHelper(selection, caretX, select, element, rect) {
    rect ||= element.getBoundingClientRect();
    if (caretX <= rect.x + PRECISION) {
      if (select) {
        selection.extend(element.firstChild, 0);
      } else {
        selection.setPosition(element.firstChild, 0);
      }
      return;
    }
    if (rect.right - PRECISION <= caretX) {
      const {
        lastChild
      } = element;
      if (select) {
        selection.extend(lastChild, lastChild.length);
      } else {
        selection.setPosition(lastChild, lastChild.length);
      }
      return;
    }
    const midY = rect.y + rect.height / 2;
    let caretPosition = CaretBrowsingMode.#caretPositionFromPoint(caretX, midY);
    let parentElement = caretPosition.offsetNode?.parentElement;
    if (parentElement && parentElement !== element) {
      const elementsAtPoint = document.elementsFromPoint(caretX, midY);
      const savedVisibilities = [];
      for (const el of elementsAtPoint) {
        if (el === element) {
          break;
        }
        const {
          style
        } = el;
        savedVisibilities.push([el, style.visibility]);
        style.visibility = "hidden";
      }
      caretPosition = CaretBrowsingMode.#caretPositionFromPoint(caretX, midY);
      parentElement = caretPosition.offsetNode?.parentElement;
      for (const [el, visibility] of savedVisibilities) {
        el.style.visibility = visibility;
      }
    }
    if (parentElement !== element) {
      if (select) {
        selection.extend(element.firstChild, 0);
      } else {
        selection.setPosition(element.firstChild, 0);
      }
      return;
    }
    if (select) {
      selection.extend(caretPosition.offsetNode, caretPosition.offset);
    } else {
      selection.setPosition(caretPosition.offsetNode, caretPosition.offset);
    }
  }
  #setCaretPosition(select, selection, newLineElement, newLineElementRect, caretX) {
    if (this.#isVisible(newLineElementRect)) {
      this.#setCaretPositionHelper(selection, caretX, select, newLineElement, newLineElementRect);
      return;
    }
    this.#mainContainer.addEventListener("scrollend", this.#setCaretPositionHelper.bind(this, selection, caretX, select, newLineElement, null), {
      once: true
    });
    newLineElement.scrollIntoView();
  }
  #getNodeOnNextPage(textLayer, isUp) {
    while (true) {
      const page = textLayer.closest(".page");
      const pageNumber = parseInt(page.getAttribute("data-page-number"));
      const nextPage = isUp ? pageNumber - 1 : pageNumber + 1;
      textLayer = this.#viewerContainer.querySelector(`.page[data-page-number="${nextPage}"] .textLayer`);
      if (!textLayer) {
        return null;
      }
      const walker = document.createTreeWalker(textLayer, NodeFilter.SHOW_TEXT);
      const node = isUp ? walker.lastChild() : walker.firstChild();
      if (node) {
        return node;
      }
    }
  }
  moveCaret(isUp, select) {
    const selection = document.getSelection();
    if (selection.rangeCount === 0) {
      return;
    }
    const {
      focusNode
    } = selection;
    const focusElement = focusNode.nodeType !== Node.ELEMENT_NODE ? focusNode.parentElement : focusNode;
    const root = focusElement.closest(".textLayer");
    if (!root) {
      return;
    }
    const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
    walker.currentNode = focusNode;
    const focusRect = focusElement.getBoundingClientRect();
    let newLineElement = null;
    const nodeIterator = (isUp ? walker.previousSibling : walker.nextSibling).bind(walker);
    while (nodeIterator()) {
      const element = walker.currentNode.parentElement;
      if (!this.#isOnSameLine(focusRect, element.getBoundingClientRect())) {
        newLineElement = element;
        break;
      }
    }
    if (!newLineElement) {
      const node = this.#getNodeOnNextPage(root, isUp);
      if (!node) {
        return;
      }
      if (select) {
        const lastNode = (isUp ? walker.firstChild() : walker.lastChild()) || focusNode;
        selection.extend(lastNode, isUp ? 0 : lastNode.length);
        const range = document.createRange();
        range.setStart(node, isUp ? node.length : 0);
        range.setEnd(node, isUp ? node.length : 0);
        selection.addRange(range);
        return;
      }
      const [caretX] = this.#getCaretPosition(selection, isUp);
      const {
        parentElement
      } = node;
      this.#setCaretPosition(select, selection, parentElement, parentElement.getBoundingClientRect(), caretX);
      return;
    }
    const [caretX, caretY] = this.#getCaretPosition(selection, isUp);
    const newLineElementRect = newLineElement.getBoundingClientRect();
    if (this.#isUnderOver(newLineElementRect, caretX, caretY, isUp)) {
      this.#setCaretPosition(select, selection, newLineElement, newLineElementRect, caretX);
      return;
    }
    while (nodeIterator()) {
      const element = walker.currentNode.parentElement;
      const elementRect = element.getBoundingClientRect();
      if (!this.#isOnSameLine(newLineElementRect, elementRect)) {
        break;
      }
      if (this.#isUnderOver(elementRect, caretX, caretY, isUp)) {
        this.#setCaretPosition(select, selection, element, elementRect, caretX);
        return;
      }
    }
    this.#setCaretPosition(select, selection, newLineElement, newLineElementRect, caretX);
  }
}

;// CONCATENATED MODULE: ./web/overlay_manager.js
class OverlayManager {
  #overlays = new WeakMap();
  #active = null;
  get active() {
    return this.#active;
  }
  async register(dialog, canForceClose = false) {
    if (typeof dialog !== "object") {
      throw new Error("Not enough parameters.");
    } else if (this.#overlays.has(dialog)) {
      throw new Error("The overlay is already registered.");
    }
    this.#overlays.set(dialog, {
      canForceClose
    });
    dialog.addEventListener("cancel", evt => {
      this.#active = null;
    });
  }
  async open(dialog) {
    if (!this.#overlays.has(dialog)) {
      throw new Error("The overlay does not exist.");
    } else if (this.#active) {
      if (this.#active === dialog) {
        throw new Error("The overlay is already active.");
      } else if (this.#overlays.get(dialog).canForceClose) {
        await this.close();
      } else {
        throw new Error("Another overlay is currently active.");
      }
    }
    this.#active = dialog;
    dialog.showModal();
  }
  async close(dialog = this.#active) {
    if (!this.#overlays.has(dialog)) {
      throw new Error("The overlay does not exist.");
    } else if (!this.#active) {
      throw new Error("The overlay is currently not active.");
    } else if (this.#active !== dialog) {
      throw new Error("Another overlay is currently active.");
    }
    dialog.close();
    this.#active = null;
  }
}

;// CONCATENATED MODULE: ./web/password_prompt.js

class PasswordPrompt {
  #activeCapability = null;
  #updateCallback = null;
  #reason = null;
  constructor(options, overlayManager, isViewerEmbedded = false) {
    this.dialog = options.dialog;
    this.label = options.label;
    this.input = options.input;
    this.submitButton = options.submitButton;
    this.cancelButton = options.cancelButton;
    this.overlayManager = overlayManager;
    this._isViewerEmbedded = isViewerEmbedded;
    this.submitButton.addEventListener("click", this.#verify.bind(this));
    this.cancelButton.addEventListener("click", this.close.bind(this));
    this.input.addEventListener("keydown", e => {
      if (e.keyCode === 13) {
        this.#verify();
      }
    });
    this.overlayManager.register(this.dialog, true);
    this.dialog.addEventListener("close", this.#cancel.bind(this));
  }
  async open() {
    await this.#activeCapability?.promise;
    this.#activeCapability = Promise.withResolvers();
    try {
      await this.overlayManager.open(this.dialog);
    } catch (ex) {
      this.#activeCapability.resolve();
      throw ex;
    }
    const passwordIncorrect = this.#reason === PasswordResponses.INCORRECT_PASSWORD;
    if (!this._isViewerEmbedded || passwordIncorrect) {
      this.input.focus();
    }
    this.label.setAttribute("data-l10n-id", `pdfjs-password-${passwordIncorrect ? "invalid" : "label"}`);
  }
  async close() {
    if (this.overlayManager.active === this.dialog) {
      this.overlayManager.close(this.dialog);
    }
  }
  #verify() {
    const password = this.input.value;
    if (password?.length > 0) {
      this.#invokeCallback(password);
    }
  }
  #cancel() {
    this.#invokeCallback(new Error("PasswordPrompt cancelled."));
    this.#activeCapability.resolve();
  }
  #invokeCallback(password) {
    if (!this.#updateCallback) {
      return;
    }
    this.close();
    this.input.value = "";
    this.#updateCallback(password);
    this.#updateCallback = null;
  }
  async setUpdateCallback(updateCallback, reason) {
    if (this.#activeCapability) {
      await this.#activeCapability.promise;
    }
    this.#updateCallback = updateCallback;
    this.#reason = reason;
  }
}

;// CONCATENATED MODULE: ./web/base_tree_viewer.js

const TREEITEM_OFFSET_TOP = -100;
const TREEITEM_SELECTED_CLASS = "selected";
class BaseTreeViewer {
  constructor(options) {
    this.container = options.container;
    this.eventBus = options.eventBus;
    this._l10n = options.l10n;
    this.reset();
  }
  reset() {
    this._pdfDocument = null;
    this._lastToggleIsShow = true;
    this._currentTreeItem = null;
    this.container.textContent = "";
    this.container.classList.remove("treeWithDeepNesting");
  }
  _dispatchEvent(count) {
    throw new Error("Not implemented: _dispatchEvent");
  }
  _bindLink(element, params) {
    throw new Error("Not implemented: _bindLink");
  }
  _normalizeTextContent(str) {
    return removeNullCharacters(str, true) || "\u2013";
  }
  _addToggleButton(div, hidden = false) {
    const toggler = document.createElement("div");
    toggler.className = "treeItemToggler";
    if (hidden) {
      toggler.classList.add("treeItemsHidden");
    }
    toggler.onclick = evt => {
      evt.stopPropagation();
      toggler.classList.toggle("treeItemsHidden");
      if (evt.shiftKey) {
        const shouldShowAll = !toggler.classList.contains("treeItemsHidden");
        this._toggleTreeItem(div, shouldShowAll);
      }
    };
    div.prepend(toggler);
  }
  _toggleTreeItem(root, show = false) {
    this._l10n.pause();
    this._lastToggleIsShow = show;
    for (const toggler of root.querySelectorAll(".treeItemToggler")) {
      toggler.classList.toggle("treeItemsHidden", !show);
    }
    this._l10n.resume();
  }
  _toggleAllTreeItems() {
    this._toggleTreeItem(this.container, !this._lastToggleIsShow);
  }
  _finishRendering(fragment, count, hasAnyNesting = false) {
    if (hasAnyNesting) {
      this.container.classList.add("treeWithDeepNesting");
      this._lastToggleIsShow = !fragment.querySelector(".treeItemsHidden");
    }
    this._l10n.pause();
    this.container.append(fragment);
    this._l10n.resume();
    this._dispatchEvent(count);
  }
  render(params) {
    throw new Error("Not implemented: render");
  }
  _updateCurrentTreeItem(treeItem = null) {
    if (this._currentTreeItem) {
      this._currentTreeItem.classList.remove(TREEITEM_SELECTED_CLASS);
      this._currentTreeItem = null;
    }
    if (treeItem) {
      treeItem.classList.add(TREEITEM_SELECTED_CLASS);
      this._currentTreeItem = treeItem;
    }
  }
  _scrollToCurrentTreeItem(treeItem) {
    if (!treeItem) {
      return;
    }
    this._l10n.pause();
    let currentNode = treeItem.parentNode;
    while (currentNode && currentNode !== this.container) {
      if (currentNode.classList.contains("treeItem")) {
        const toggler = currentNode.firstElementChild;
        toggler?.classList.remove("treeItemsHidden");
      }
      currentNode = currentNode.parentNode;
    }
    this._l10n.resume();
    this._updateCurrentTreeItem(treeItem);
    this.container.scrollTo(treeItem.offsetLeft, treeItem.offsetTop + TREEITEM_OFFSET_TOP);
  }
}

;// CONCATENATED MODULE: ./web/pdf_attachment_viewer.js


class PDFAttachmentViewer extends BaseTreeViewer {
  constructor(options) {
    super(options);
    this.downloadManager = options.downloadManager;
    this.eventBus._on("fileattachmentannotation", this.#appendAttachment.bind(this));
  }
  reset(keepRenderedCapability = false) {
    super.reset();
    this._attachments = null;
    if (!keepRenderedCapability) {
      this._renderedCapability = Promise.withResolvers();
    }
    this._pendingDispatchEvent = false;
  }
  async _dispatchEvent(attachmentsCount) {
    this._renderedCapability.resolve();
    if (attachmentsCount === 0 && !this._pendingDispatchEvent) {
      this._pendingDispatchEvent = true;
      await waitOnEventOrTimeout({
        target: this.eventBus,
        name: "annotationlayerrendered",
        delay: 1000
      });
      if (!this._pendingDispatchEvent) {
        return;
      }
    }
    this._pendingDispatchEvent = false;
    this.eventBus.dispatch("attachmentsloaded", {
      source: this,
      attachmentsCount
    });
  }
  _bindLink(element, {
    content,
    description,
    filename
  }) {
    if (description) {
      element.title = description;
    }
    element.onclick = () => {
      this.downloadManager.openOrDownloadData(content, filename);
      return false;
    };
  }
  render({
    attachments,
    keepRenderedCapability = false
  }) {
    if (this._attachments) {
      this.reset(keepRenderedCapability);
    }
    this._attachments = attachments || null;
    if (!attachments) {
      this._dispatchEvent(0);
      return;
    }
    const fragment = document.createDocumentFragment();
    let attachmentsCount = 0;
    for (const name in attachments) {
      const item = attachments[name];
      const div = document.createElement("div");
      div.className = "treeItem";
      const element = document.createElement("a");
      this._bindLink(element, item);
      element.textContent = this._normalizeTextContent(item.filename);
      div.append(element);
      fragment.append(div);
      attachmentsCount++;
    }
    this._finishRendering(fragment, attachmentsCount);
  }
  #appendAttachment(item) {
    const renderedPromise = this._renderedCapability.promise;
    renderedPromise.then(() => {
      if (renderedPromise !== this._renderedCapability.promise) {
        return;
      }
      const attachments = this._attachments || Object.create(null);
      for (const name in attachments) {
        if (item.filename === name) {
          return;
        }
      }
      attachments[item.filename] = item;
      this.render({
        attachments,
        keepRenderedCapability: true
      });
    });
  }
}

;// CONCATENATED MODULE: ./web/grab_to_pan.js
const CSS_CLASS_GRAB = "grab-to-pan-grab";
class GrabToPan {
  constructor({
    element
  }) {
    this.element = element;
    this.document = element.ownerDocument;
    this.activate = this.activate.bind(this);
    this.deactivate = this.deactivate.bind(this);
    this.toggle = this.toggle.bind(this);
    this._onMouseDown = this.#onMouseDown.bind(this);
    this._onMouseMove = this.#onMouseMove.bind(this);
    this._endPan = this.#endPan.bind(this);
    const overlay = this.overlay = document.createElement("div");
    overlay.className = "grab-to-pan-grabbing";
  }
  activate() {
    if (!this.active) {
      this.active = true;
      this.element.addEventListener("mousedown", this._onMouseDown, true);
      this.element.classList.add(CSS_CLASS_GRAB);
    }
  }
  deactivate() {
    if (this.active) {
      this.active = false;
      this.element.removeEventListener("mousedown", this._onMouseDown, true);
      this._endPan();
      this.element.classList.remove(CSS_CLASS_GRAB);
    }
  }
  toggle() {
    if (this.active) {
      this.deactivate();
    } else {
      this.activate();
    }
  }
  ignoreTarget(node) {
    return node.matches("a[href], a[href] *, input, textarea, button, button *, select, option");
  }
  #onMouseDown(event) {
    if (event.button !== 0 || this.ignoreTarget(event.target)) {
      return;
    }
    if (event.originalTarget) {
      try {
        event.originalTarget.tagName;
      } catch {
        return;
      }
    }
    this.scrollLeftStart = this.element.scrollLeft;
    this.scrollTopStart = this.element.scrollTop;
    this.clientXStart = event.clientX;
    this.clientYStart = event.clientY;
    this.document.addEventListener("mousemove", this._onMouseMove, true);
    this.document.addEventListener("mouseup", this._endPan, true);
    this.element.addEventListener("scroll", this._endPan, true);
    event.preventDefault();
    event.stopPropagation();
    const focusedElement = document.activeElement;
    if (focusedElement && !focusedElement.contains(event.target)) {
      focusedElement.blur();
    }
  }
  #onMouseMove(event) {
    this.element.removeEventListener("scroll", this._endPan, true);
    if (!(event.buttons & 1)) {
      this._endPan();
      return;
    }
    const xDiff = event.clientX - this.clientXStart;
    const yDiff = event.clientY - this.clientYStart;
    this.element.scrollTo({
      top: this.scrollTopStart - yDiff,
      left: this.scrollLeftStart - xDiff,
      behavior: "instant"
    });
    if (!this.overlay.parentNode) {
      document.body.append(this.overlay);
    }
  }
  #endPan() {
    this.element.removeEventListener("scroll", this._endPan, true);
    this.document.removeEventListener("mousemove", this._onMouseMove, true);
    this.document.removeEventListener("mouseup", this._endPan, true);
    this.overlay.remove();
  }
}

;// CONCATENATED MODULE: ./web/pdf_cursor_tools.js



class PDFCursorTools {
  #active = CursorTool.SELECT;
  #prevActive = null;
  constructor({
    container,
    eventBus,
    cursorToolOnLoad = CursorTool.SELECT
  }) {
    this.container = container;
    this.eventBus = eventBus;
    this.#addEventListeners();
    Promise.resolve().then(() => {
      this.switchTool(cursorToolOnLoad);
    });
  }
  get activeTool() {
    return this.#active;
  }
  switchTool(tool) {
    if (this.#prevActive !== null) {
      return;
    }
    if (tool === this.#active) {
      return;
    }
    const disableActiveTool = () => {
      switch (this.#active) {
        case CursorTool.SELECT:
          break;
        case CursorTool.HAND:
          this._handTool.deactivate();
          break;
        case CursorTool.ZOOM:
      }
    };
    switch (tool) {
      case CursorTool.SELECT:
        disableActiveTool();
        break;
      case CursorTool.HAND:
        disableActiveTool();
        this._handTool.activate();
        break;
      case CursorTool.ZOOM:
      default:
        console.error(`switchTool: "${tool}" is an unsupported value.`);
        return;
    }
    this.#active = tool;
    this.eventBus.dispatch("cursortoolchanged", {
      source: this,
      tool
    });
  }
  #addEventListeners() {
    this.eventBus._on("switchcursortool", evt => {
      if (!evt.reset) {
        this.switchTool(evt.tool);
      } else if (this.#prevActive !== null) {
        annotationEditorMode = AnnotationEditorType.NONE;
        presentationModeState = PresentationModeState.NORMAL;
        enableActive();
      }
    });
    let annotationEditorMode = AnnotationEditorType.NONE,
      presentationModeState = PresentationModeState.NORMAL;
    const disableActive = () => {
      const prevActive = this.#active;
      this.switchTool(CursorTool.SELECT);
      this.#prevActive ??= prevActive;
    };
    const enableActive = () => {
      const prevActive = this.#prevActive;
      if (prevActive !== null && annotationEditorMode === AnnotationEditorType.NONE && presentationModeState === PresentationModeState.NORMAL) {
        this.#prevActive = null;
        this.switchTool(prevActive);
      }
    };
    this.eventBus._on("annotationeditormodechanged", ({
      mode
    }) => {
      annotationEditorMode = mode;
      if (mode === AnnotationEditorType.NONE) {
        enableActive();
      } else {
        disableActive();
      }
    });
    this.eventBus._on("presentationmodechanged", ({
      state
    }) => {
      presentationModeState = state;
      if (state === PresentationModeState.NORMAL) {
        enableActive();
      } else if (state === PresentationModeState.FULLSCREEN) {
        disableActive();
      }
    });
  }
  get _handTool() {
    return shadow(this, "_handTool", new GrabToPan({
      element: this.container
    }));
  }
}

;// CONCATENATED MODULE: ./web/pdf_document_properties.js


const DEFAULT_FIELD_CONTENT = "-";
const NON_METRIC_LOCALES = ["en-us", "en-lr", "my"];
const US_PAGE_NAMES = {
  "8.5x11": "letter",
  "8.5x14": "legal"
};
const METRIC_PAGE_NAMES = {
  "297x420": "a-three",
  "210x297": "a-four"
};
function getPageName(size, isPortrait, pageNames) {
  const width = isPortrait ? size.width : size.height;
  const height = isPortrait ? size.height : size.width;
  return pageNames[`${width}x${height}`];
}
class PDFDocumentProperties {
  #fieldData = null;
  constructor({
    dialog,
    fields,
    closeButton
  }, overlayManager, eventBus, l10n, fileNameLookup) {
    this.dialog = dialog;
    this.fields = fields;
    this.overlayManager = overlayManager;
    this.l10n = l10n;
    this._fileNameLookup = fileNameLookup;
    this.#reset();
    closeButton.addEventListener("click", this.close.bind(this));
    this.overlayManager.register(this.dialog);
    eventBus._on("pagechanging", evt => {
      this._currentPageNumber = evt.pageNumber;
    });
    eventBus._on("rotationchanging", evt => {
      this._pagesRotation = evt.pagesRotation;
    });
    this._isNonMetricLocale = NON_METRIC_LOCALES.includes(l10n.getLanguage());
  }
  async open() {
    await Promise.all([this.overlayManager.open(this.dialog), this._dataAvailableCapability.promise]);
    const currentPageNumber = this._currentPageNumber;
    const pagesRotation = this._pagesRotation;
    if (this.#fieldData && currentPageNumber === this.#fieldData._currentPageNumber && pagesRotation === this.#fieldData._pagesRotation) {
      this.#updateUI();
      return;
    }
    const {
      info,
      contentLength
    } = await this.pdfDocument.getMetadata();
    const [fileName, fileSize, creationDate, modificationDate, pageSize, isLinearized] = await Promise.all([this._fileNameLookup(), this.#parseFileSize(contentLength), this.#parseDate(info.CreationDate), this.#parseDate(info.ModDate), this.pdfDocument.getPage(currentPageNumber).then(pdfPage => {
      return this.#parsePageSize(getPageSizeInches(pdfPage), pagesRotation);
    }), this.#parseLinearization(info.IsLinearized)]);
    this.#fieldData = Object.freeze({
      fileName,
      fileSize,
      title: info.Title,
      author: info.Author,
      subject: info.Subject,
      keywords: info.Keywords,
      creationDate,
      modificationDate,
      creator: info.Creator,
      producer: info.Producer,
      version: info.PDFFormatVersion,
      pageCount: this.pdfDocument.numPages,
      pageSize,
      linearized: isLinearized,
      _currentPageNumber: currentPageNumber,
      _pagesRotation: pagesRotation
    });
    this.#updateUI();
    const {
      length
    } = await this.pdfDocument.getDownloadInfo();
    if (contentLength === length) {
      return;
    }
    const data = Object.assign(Object.create(null), this.#fieldData);
    data.fileSize = await this.#parseFileSize(length);
    this.#fieldData = Object.freeze(data);
    this.#updateUI();
  }
  async close() {
    this.overlayManager.close(this.dialog);
  }
  setDocument(pdfDocument) {
    if (this.pdfDocument) {
      this.#reset();
      this.#updateUI(true);
    }
    if (!pdfDocument) {
      return;
    }
    this.pdfDocument = pdfDocument;
    this._dataAvailableCapability.resolve();
  }
  #reset() {
    this.pdfDocument = null;
    this.#fieldData = null;
    this._dataAvailableCapability = Promise.withResolvers();
    this._currentPageNumber = 1;
    this._pagesRotation = 0;
  }
  #updateUI(reset = false) {
    if (reset || !this.#fieldData) {
      for (const id in this.fields) {
        this.fields[id].textContent = DEFAULT_FIELD_CONTENT;
      }
      return;
    }
    if (this.overlayManager.active !== this.dialog) {
      return;
    }
    for (const id in this.fields) {
      const content = this.#fieldData[id];
      this.fields[id].textContent = content || content === 0 ? content : DEFAULT_FIELD_CONTENT;
    }
  }
  #getL10nStr(id, args = null) {
    return this.l10n.get(`pdfjs-document-properties-${id}`, args);
  }
  async #parseFileSize(b = 0) {
    const kb = b / 1024,
      mb = kb / 1024;
    return kb ? this.#getL10nStr(`size-${mb >= 1 ? "mb" : "kb"}`, {
      mb,
      kb,
      b
    }) : undefined;
  }
  async #parsePageSize(pageSizeInches, pagesRotation) {
    if (!pageSizeInches) {
      return undefined;
    }
    if (pagesRotation % 180 !== 0) {
      pageSizeInches = {
        width: pageSizeInches.height,
        height: pageSizeInches.width
      };
    }
    const isPortrait = isPortraitOrientation(pageSizeInches);
    let sizeInches = {
      width: Math.round(pageSizeInches.width * 100) / 100,
      height: Math.round(pageSizeInches.height * 100) / 100
    };
    let sizeMillimeters = {
      width: Math.round(pageSizeInches.width * 25.4 * 10) / 10,
      height: Math.round(pageSizeInches.height * 25.4 * 10) / 10
    };
    let rawName = getPageName(sizeInches, isPortrait, US_PAGE_NAMES) || getPageName(sizeMillimeters, isPortrait, METRIC_PAGE_NAMES);
    if (!rawName && !(Number.isInteger(sizeMillimeters.width) && Number.isInteger(sizeMillimeters.height))) {
      const exactMillimeters = {
        width: pageSizeInches.width * 25.4,
        height: pageSizeInches.height * 25.4
      };
      const intMillimeters = {
        width: Math.round(sizeMillimeters.width),
        height: Math.round(sizeMillimeters.height)
      };
      if (Math.abs(exactMillimeters.width - intMillimeters.width) < 0.1 && Math.abs(exactMillimeters.height - intMillimeters.height) < 0.1) {
        rawName = getPageName(intMillimeters, isPortrait, METRIC_PAGE_NAMES);
        if (rawName) {
          sizeInches = {
            width: Math.round(intMillimeters.width / 25.4 * 100) / 100,
            height: Math.round(intMillimeters.height / 25.4 * 100) / 100
          };
          sizeMillimeters = intMillimeters;
        }
      }
    }
    const [{
      width,
      height
    }, unit, name, orientation] = await Promise.all([this._isNonMetricLocale ? sizeInches : sizeMillimeters, this.#getL10nStr(`page-size-unit-${this._isNonMetricLocale ? "inches" : "millimeters"}`), rawName && this.#getL10nStr(`page-size-name-${rawName}`), this.#getL10nStr(`page-size-orientation-${isPortrait ? "portrait" : "landscape"}`)]);
    return this.#getL10nStr(`page-size-dimension-${name ? "name-" : ""}string`, {
      width,
      height,
      unit,
      name,
      orientation
    });
  }
  async #parseDate(inputDate) {
    const dateObj = PDFDateString.toDateObject(inputDate);
    return dateObj ? this.#getL10nStr("date-time-string", {
      dateObj
    }) : undefined;
  }
  #parseLinearization(isLinearized) {
    return this.#getL10nStr(`linearized-${isLinearized ? "yes" : "no"}`);
  }
}

;// CONCATENATED MODULE: ./web/pdf_find_utils.js
const CharacterType = {
  SPACE: 0,
  ALPHA_LETTER: 1,
  PUNCT: 2,
  HAN_LETTER: 3,
  KATAKANA_LETTER: 4,
  HIRAGANA_LETTER: 5,
  HALFWIDTH_KATAKANA_LETTER: 6,
  THAI_LETTER: 7
};
function isAlphabeticalScript(charCode) {
  return charCode < 0x2e80;
}
function isAscii(charCode) {
  return (charCode & 0xff80) === 0;
}
function isAsciiAlpha(charCode) {
  return charCode >= 0x61 && charCode <= 0x7a || charCode >= 0x41 && charCode <= 0x5a;
}
function isAsciiDigit(charCode) {
  return charCode >= 0x30 && charCode <= 0x39;
}
function isAsciiSpace(charCode) {
  return charCode === 0x20 || charCode === 0x09 || charCode === 0x0d || charCode === 0x0a;
}
function isHan(charCode) {
  return charCode >= 0x3400 && charCode <= 0x9fff || charCode >= 0xf900 && charCode <= 0xfaff;
}
function isKatakana(charCode) {
  return charCode >= 0x30a0 && charCode <= 0x30ff;
}
function isHiragana(charCode) {
  return charCode >= 0x3040 && charCode <= 0x309f;
}
function isHalfwidthKatakana(charCode) {
  return charCode >= 0xff60 && charCode <= 0xff9f;
}
function isThai(charCode) {
  return (charCode & 0xff80) === 0x0e00;
}
function getCharacterType(charCode) {
  if (isAlphabeticalScript(charCode)) {
    if (isAscii(charCode)) {
      if (isAsciiSpace(charCode)) {
        return CharacterType.SPACE;
      } else if (isAsciiAlpha(charCode) || isAsciiDigit(charCode) || charCode === 0x5f) {
        return CharacterType.ALPHA_LETTER;
      }
      return CharacterType.PUNCT;
    } else if (isThai(charCode)) {
      return CharacterType.THAI_LETTER;
    } else if (charCode === 0xa0) {
      return CharacterType.SPACE;
    }
    return CharacterType.ALPHA_LETTER;
  }
  if (isHan(charCode)) {
    return CharacterType.HAN_LETTER;
  } else if (isKatakana(charCode)) {
    return CharacterType.KATAKANA_LETTER;
  } else if (isHiragana(charCode)) {
    return CharacterType.HIRAGANA_LETTER;
  } else if (isHalfwidthKatakana(charCode)) {
    return CharacterType.HALFWIDTH_KATAKANA_LETTER;
  }
  return CharacterType.ALPHA_LETTER;
}
let NormalizeWithNFKC;
function getNormalizeWithNFKC() {
  NormalizeWithNFKC ||= ` ¨ª¯²-µ¸-º¼-¾IJ-ijĿ-ŀʼnſDŽ-njDZ-dzʰ-ʸ˘-˝ˠ-ˤʹͺ;΄-΅·ϐ-ϖϰ-ϲϴ-ϵϹևٵ-ٸक़-य़ড়-ঢ়য়ਲ਼ਸ਼ਖ਼-ਜ਼ਫ਼ଡ଼-ଢ଼ำຳໜ-ໝ༌གྷཌྷདྷབྷཛྷཀྵჼᴬ-ᴮᴰ-ᴺᴼ-ᵍᵏ-ᵪᵸᶛ-ᶿẚ-ẛάέήίόύώΆ᾽-῁ΈΉ῍-῏ΐΊ῝-῟ΰΎ῭-`ΌΏ´-῾ - ‑‗․-… ″-‴‶-‷‼‾⁇-⁉⁗ ⁰-ⁱ⁴-₎ₐ-ₜ₨℀-℃℅-ℇ℉-ℓℕ-№ℙ-ℝ℠-™ℤΩℨK-ℭℯ-ℱℳ-ℹ℻-⅀ⅅ-ⅉ⅐-ⅿ↉∬-∭∯-∰〈-〉①-⓪⨌⩴-⩶⫝̸ⱼ-ⱽⵯ⺟⻳⼀-⿕ 〶〸-〺゛-゜ゟヿㄱ-ㆎ㆒-㆟㈀-㈞㈠-㉇㉐-㉾㊀-㏿ꚜ-ꚝꝰꟲ-ꟴꟸ-ꟹꭜ-ꭟꭩ豈-嗀塚晴凞-羽蘒諸逸-都飯-舘並-龎ff-stﬓ-ﬗיִײַ-זּטּ-לּמּנּ-סּףּ-פּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-﷼︐-︙︰-﹄﹇-﹒﹔-﹦﹨-﹫ﹰ-ﹲﹴﹶ-ﻼ!-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ¢-₩`;
  return NormalizeWithNFKC;
}

;// CONCATENATED MODULE: ./web/pdf_find_controller.js


const FindState = {
  FOUND: 0,
  NOT_FOUND: 1,
  WRAPPED: 2,
  PENDING: 3
};
const FIND_TIMEOUT = 250;
const MATCH_SCROLL_OFFSET_TOP = -50;
const MATCH_SCROLL_OFFSET_LEFT = -400;
const CHARACTERS_TO_NORMALIZE = {
  "\u2010": "-",
  "\u2018": "'",
  "\u2019": "'",
  "\u201A": "'",
  "\u201B": "'",
  "\u201C": '"',
  "\u201D": '"',
  "\u201E": '"',
  "\u201F": '"',
  "\u00BC": "1/4",
  "\u00BD": "1/2",
  "\u00BE": "3/4"
};
const DIACRITICS_EXCEPTION = new Set([0x3099, 0x309a, 0x094d, 0x09cd, 0x0a4d, 0x0acd, 0x0b4d, 0x0bcd, 0x0c4d, 0x0ccd, 0x0d3b, 0x0d3c, 0x0d4d, 0x0dca, 0x0e3a, 0x0eba, 0x0f84, 0x1039, 0x103a, 0x1714, 0x1734, 0x17d2, 0x1a60, 0x1b44, 0x1baa, 0x1bab, 0x1bf2, 0x1bf3, 0x2d7f, 0xa806, 0xa82c, 0xa8c4, 0xa953, 0xa9c0, 0xaaf6, 0xabed, 0x0c56, 0x0f71, 0x0f72, 0x0f7a, 0x0f7b, 0x0f7c, 0x0f7d, 0x0f80, 0x0f74]);
let DIACRITICS_EXCEPTION_STR;
const DIACRITICS_REG_EXP = /\p{M}+/gu;
const SPECIAL_CHARS_REG_EXP = /([.*+?^${}()|[\]\\])|(\p{P})|(\s+)|(\p{M})|(\p{L})/gu;
const NOT_DIACRITIC_FROM_END_REG_EXP = /([^\p{M}])\p{M}*$/u;
const NOT_DIACRITIC_FROM_START_REG_EXP = /^\p{M}*([^\p{M}])/u;
const SYLLABLES_REG_EXP = /[\uAC00-\uD7AF\uFA6C\uFACF-\uFAD1\uFAD5-\uFAD7]+/g;
const SYLLABLES_LENGTHS = new Map();
const FIRST_CHAR_SYLLABLES_REG_EXP = "[\\u1100-\\u1112\\ud7a4-\\ud7af\\ud84a\\ud84c\\ud850\\ud854\\ud857\\ud85f]";
const NFKC_CHARS_TO_NORMALIZE = new Map();
let noSyllablesRegExp = null;
let withSyllablesRegExp = null;
function normalize(text) {
  const syllablePositions = [];
  let m;
  while ((m = SYLLABLES_REG_EXP.exec(text)) !== null) {
    let {
      index
    } = m;
    for (const char of m[0]) {
      let len = SYLLABLES_LENGTHS.get(char);
      if (!len) {
        len = char.normalize("NFD").length;
        SYLLABLES_LENGTHS.set(char, len);
      }
      syllablePositions.push([len, index++]);
    }
  }
  let normalizationRegex;
  if (syllablePositions.length === 0 && noSyllablesRegExp) {
    normalizationRegex = noSyllablesRegExp;
  } else if (syllablePositions.length > 0 && withSyllablesRegExp) {
    normalizationRegex = withSyllablesRegExp;
  } else {
    const replace = Object.keys(CHARACTERS_TO_NORMALIZE).join("");
    const toNormalizeWithNFKC = getNormalizeWithNFKC();
    const CJK = "(?:\\p{Ideographic}|[\u3040-\u30FF])";
    const HKDiacritics = "(?:\u3099|\u309A)";
    const regexp = `([${replace}])|([${toNormalizeWithNFKC}])|(${HKDiacritics}\\n)|(\\p{M}+(?:-\\n)?)|(\\S-\\n)|(${CJK}\\n)|(\\n)`;
    if (syllablePositions.length === 0) {
      normalizationRegex = noSyllablesRegExp = new RegExp(regexp + "|(\\u0000)", "gum");
    } else {
      normalizationRegex = withSyllablesRegExp = new RegExp(regexp + `|(${FIRST_CHAR_SYLLABLES_REG_EXP})`, "gum");
    }
  }
  const rawDiacriticsPositions = [];
  while ((m = DIACRITICS_REG_EXP.exec(text)) !== null) {
    rawDiacriticsPositions.push([m[0].length, m.index]);
  }
  let normalized = text.normalize("NFD");
  const positions = [[0, 0]];
  let rawDiacriticsIndex = 0;
  let syllableIndex = 0;
  let shift = 0;
  let shiftOrigin = 0;
  let eol = 0;
  let hasDiacritics = false;
  normalized = normalized.replace(normalizationRegex, (match, p1, p2, p3, p4, p5, p6, p7, p8, i) => {
    i -= shiftOrigin;
    if (p1) {
      const replacement = CHARACTERS_TO_NORMALIZE[p1];
      const jj = replacement.length;
      for (let j = 1; j < jj; j++) {
        positions.push([i - shift + j, shift - j]);
      }
      shift -= jj - 1;
      return replacement;
    }
    if (p2) {
      let replacement = NFKC_CHARS_TO_NORMALIZE.get(p2);
      if (!replacement) {
        replacement = p2.normalize("NFKC");
        NFKC_CHARS_TO_NORMALIZE.set(p2, replacement);
      }
      const jj = replacement.length;
      for (let j = 1; j < jj; j++) {
        positions.push([i - shift + j, shift - j]);
      }
      shift -= jj - 1;
      return replacement;
    }
    if (p3) {
      hasDiacritics = true;
      if (i + eol === rawDiacriticsPositions[rawDiacriticsIndex]?.[1]) {
        ++rawDiacriticsIndex;
      } else {
        positions.push([i - 1 - shift + 1, shift - 1]);
        shift -= 1;
        shiftOrigin += 1;
      }
      positions.push([i - shift + 1, shift]);
      shiftOrigin += 1;
      eol += 1;
      return p3.charAt(0);
    }
    if (p4) {
      const hasTrailingDashEOL = p4.endsWith("\n");
      const len = hasTrailingDashEOL ? p4.length - 2 : p4.length;
      hasDiacritics = true;
      let jj = len;
      if (i + eol === rawDiacriticsPositions[rawDiacriticsIndex]?.[1]) {
        jj -= rawDiacriticsPositions[rawDiacriticsIndex][0];
        ++rawDiacriticsIndex;
      }
      for (let j = 1; j <= jj; j++) {
        positions.push([i - 1 - shift + j, shift - j]);
      }
      shift -= jj;
      shiftOrigin += jj;
      if (hasTrailingDashEOL) {
        i += len - 1;
        positions.push([i - shift + 1, 1 + shift]);
        shift += 1;
        shiftOrigin += 1;
        eol += 1;
        return p4.slice(0, len);
      }
      return p4;
    }
    if (p5) {
      const len = p5.length - 2;
      positions.push([i - shift + len, 1 + shift]);
      shift += 1;
      shiftOrigin += 1;
      eol += 1;
      return p5.slice(0, -2);
    }
    if (p6) {
      const len = p6.length - 1;
      positions.push([i - shift + len, shift]);
      shiftOrigin += 1;
      eol += 1;
      return p6.slice(0, -1);
    }
    if (p7) {
      positions.push([i - shift + 1, shift - 1]);
      shift -= 1;
      shiftOrigin += 1;
      eol += 1;
      return " ";
    }
    if (i + eol === syllablePositions[syllableIndex]?.[1]) {
      const newCharLen = syllablePositions[syllableIndex][0] - 1;
      ++syllableIndex;
      for (let j = 1; j <= newCharLen; j++) {
        positions.push([i - (shift - j), shift - j]);
      }
      shift -= newCharLen;
      shiftOrigin += newCharLen;
    }
    return p8;
  });
  positions.push([normalized.length, shift]);
  return [normalized, positions, hasDiacritics];
}
function getOriginalIndex(diffs, pos, len) {
  if (!diffs) {
    return [pos, len];
  }
  const start = pos;
  const end = pos + len - 1;
  let i = binarySearchFirstItem(diffs, x => x[0] >= start);
  if (diffs[i][0] > start) {
    --i;
  }
  let j = binarySearchFirstItem(diffs, x => x[0] >= end, i);
  if (diffs[j][0] > end) {
    --j;
  }
  const oldStart = start + diffs[i][1];
  const oldEnd = end + diffs[j][1];
  const oldLen = oldEnd + 1 - oldStart;
  return [oldStart, oldLen];
}
class PDFFindController {
  #state = null;
  #updateMatchesCountOnProgress = true;
  #visitedPagesCount = 0;
  constructor({
    linkService,
    eventBus,
    updateMatchesCountOnProgress = true
  }) {
    this._linkService = linkService;
    this._eventBus = eventBus;
    this.#updateMatchesCountOnProgress = updateMatchesCountOnProgress;
    this.onIsPageVisible = null;
    this.#reset();
    eventBus._on("find", this.#onFind.bind(this));
    eventBus._on("findbarclose", this.#onFindBarClose.bind(this));
  }
  get highlightMatches() {
    return this._highlightMatches;
  }
  get pageMatches() {
    return this._pageMatches;
  }
  get pageMatchesLength() {
    return this._pageMatchesLength;
  }
  get selected() {
    return this._selected;
  }
  get state() {
    return this.#state;
  }
  setDocument(pdfDocument) {
    if (this._pdfDocument) {
      this.#reset();
    }
    if (!pdfDocument) {
      return;
    }
    this._pdfDocument = pdfDocument;
    this._firstPageCapability.resolve();
  }
  #onFind(state) {
    if (!state) {
      return;
    }
    const pdfDocument = this._pdfDocument;
    const {
      type
    } = state;
    if (this.#state === null || this.#shouldDirtyMatch(state)) {
      this._dirtyMatch = true;
    }
    this.#state = state;
    if (type !== "highlightallchange") {
      this.#updateUIState(FindState.PENDING);
    }
    this._firstPageCapability.promise.then(() => {
      if (!this._pdfDocument || pdfDocument && this._pdfDocument !== pdfDocument) {
        return;
      }
      this.#extractText();
      const findbarClosed = !this._highlightMatches;
      const pendingTimeout = !!this._findTimeout;
      if (this._findTimeout) {
        clearTimeout(this._findTimeout);
        this._findTimeout = null;
      }
      if (!type) {
        this._findTimeout = setTimeout(() => {
          this.#nextMatch();
          this._findTimeout = null;
        }, FIND_TIMEOUT);
      } else if (this._dirtyMatch) {
        this.#nextMatch();
      } else if (type === "again") {
        this.#nextMatch();
        if (findbarClosed && this.#state.highlightAll) {
          this.#updateAllPages();
        }
      } else if (type === "highlightallchange") {
        if (pendingTimeout) {
          this.#nextMatch();
        } else {
          this._highlightMatches = true;
        }
        this.#updateAllPages();
      } else {
        this.#nextMatch();
      }
    });
  }
  scrollMatchIntoView({
    element = null,
    selectedLeft = 0,
    pageIndex = -1,
    matchIndex = -1
  }) {
    if (!this._scrollMatches || !element) {
      return;
    } else if (matchIndex === -1 || matchIndex !== this._selected.matchIdx) {
      return;
    } else if (pageIndex === -1 || pageIndex !== this._selected.pageIdx) {
      return;
    }
    this._scrollMatches = false;
    const spot = {
      top: MATCH_SCROLL_OFFSET_TOP,
      left: selectedLeft + MATCH_SCROLL_OFFSET_LEFT
    };
    scrollIntoView(element, spot, true);
  }
  #reset() {
    this._highlightMatches = false;
    this._scrollMatches = false;
    this._pdfDocument = null;
    this._pageMatches = [];
    this._pageMatchesLength = [];
    this.#visitedPagesCount = 0;
    this.#state = null;
    this._selected = {
      pageIdx: -1,
      matchIdx: -1
    };
    this._offset = {
      pageIdx: null,
      matchIdx: null,
      wrapped: false
    };
    this._extractTextPromises = [];
    this._pageContents = [];
    this._pageDiffs = [];
    this._hasDiacritics = [];
    this._matchesCountTotal = 0;
    this._pagesToSearch = null;
    this._pendingFindMatches = new Set();
    this._resumePageIdx = null;
    this._dirtyMatch = false;
    clearTimeout(this._findTimeout);
    this._findTimeout = null;
    this._firstPageCapability = Promise.withResolvers();
  }
  get #query() {
    const {
      query
    } = this.#state;
    if (typeof query === "string") {
      if (query !== this._rawQuery) {
        this._rawQuery = query;
        [this._normalizedQuery] = normalize(query);
      }
      return this._normalizedQuery;
    }
    return (query || []).filter(q => !!q).map(q => normalize(q)[0]);
  }
  #shouldDirtyMatch(state) {
    const newQuery = state.query,
      prevQuery = this.#state.query;
    const newType = typeof newQuery,
      prevType = typeof prevQuery;
    if (newType !== prevType) {
      return true;
    }
    if (newType === "string") {
      if (newQuery !== prevQuery) {
        return true;
      }
    } else if (JSON.stringify(newQuery) !== JSON.stringify(prevQuery)) {
      return true;
    }
    switch (state.type) {
      case "again":
        const pageNumber = this._selected.pageIdx + 1;
        const linkService = this._linkService;
        return pageNumber >= 1 && pageNumber <= linkService.pagesCount && pageNumber !== linkService.page && !(this.onIsPageVisible?.(pageNumber) ?? true);
      case "highlightallchange":
        return false;
    }
    return true;
  }
  #isEntireWord(content, startIdx, length) {
    let match = content.slice(0, startIdx).match(NOT_DIACRITIC_FROM_END_REG_EXP);
    if (match) {
      const first = content.charCodeAt(startIdx);
      const limit = match[1].charCodeAt(0);
      if (getCharacterType(first) === getCharacterType(limit)) {
        return false;
      }
    }
    match = content.slice(startIdx + length).match(NOT_DIACRITIC_FROM_START_REG_EXP);
    if (match) {
      const last = content.charCodeAt(startIdx + length - 1);
      const limit = match[1].charCodeAt(0);
      if (getCharacterType(last) === getCharacterType(limit)) {
        return false;
      }
    }
    return true;
  }
  #convertToRegExpString(query, hasDiacritics) {
    const {
      matchDiacritics
    } = this.#state;
    let isUnicode = false;
    query = query.replaceAll(SPECIAL_CHARS_REG_EXP, (match, p1, p2, p3, p4, p5) => {
      if (p1) {
        return `[ ]*\\${p1}[ ]*`;
      }
      if (p2) {
        return `[ ]*${p2}[ ]*`;
      }
      if (p3) {
        return "[ ]+";
      }
      if (matchDiacritics) {
        return p4 || p5;
      }
      if (p4) {
        return DIACRITICS_EXCEPTION.has(p4.charCodeAt(0)) ? p4 : "";
      }
      if (hasDiacritics) {
        isUnicode = true;
        return `${p5}\\p{M}*`;
      }
      return p5;
    });
    const trailingSpaces = "[ ]*";
    if (query.endsWith(trailingSpaces)) {
      query = query.slice(0, query.length - trailingSpaces.length);
    }
    if (matchDiacritics) {
      if (hasDiacritics) {
        DIACRITICS_EXCEPTION_STR ||= String.fromCharCode(...DIACRITICS_EXCEPTION);
        isUnicode = true;
        query = `${query}(?=[${DIACRITICS_EXCEPTION_STR}]|[^\\p{M}]|$)`;
      }
    }
    return [isUnicode, query];
  }
  #calculateMatch(pageIndex) {
    const query = this.#query;
    if (query.length === 0) {
      return;
    }
    const pageContent = this._pageContents[pageIndex];
    const matcherResult = this.match(query, pageContent, pageIndex);
    const matches = this._pageMatches[pageIndex] = [];
    const matchesLength = this._pageMatchesLength[pageIndex] = [];
    const diffs = this._pageDiffs[pageIndex];
    matcherResult?.forEach(({
      index,
      length
    }) => {
      const [matchPos, matchLen] = getOriginalIndex(diffs, index, length);
      if (matchLen) {
        matches.push(matchPos);
        matchesLength.push(matchLen);
      }
    });
    if (this.#state.highlightAll) {
      this.#updatePage(pageIndex);
    }
    if (this._resumePageIdx === pageIndex) {
      this._resumePageIdx = null;
      this.#nextPageMatch();
    }
    const pageMatchesCount = matches.length;
    this._matchesCountTotal += pageMatchesCount;
    if (this.#updateMatchesCountOnProgress) {
      if (pageMatchesCount > 0) {
        this.#updateUIResultsCount();
      }
    } else if (++this.#visitedPagesCount === this._linkService.pagesCount) {
      this.#updateUIResultsCount();
    }
  }
  match(query, pageContent, pageIndex) {
    const hasDiacritics = this._hasDiacritics[pageIndex];
    let isUnicode = false;
    if (typeof query === "string") {
      [isUnicode, query] = this.#convertToRegExpString(query, hasDiacritics);
    } else {
      query = query.sort().reverse().map(q => {
        const [isUnicodePart, queryPart] = this.#convertToRegExpString(q, hasDiacritics);
        isUnicode ||= isUnicodePart;
        return `(${queryPart})`;
      }).join("|");
    }
    if (!query) {
      return undefined;
    }
    const {
      caseSensitive,
      entireWord
    } = this.#state;
    const flags = `g${isUnicode ? "u" : ""}${caseSensitive ? "" : "i"}`;
    query = new RegExp(query, flags);
    const matches = [];
    let match;
    while ((match = query.exec(pageContent)) !== null) {
      if (entireWord && !this.#isEntireWord(pageContent, match.index, match[0].length)) {
        continue;
      }
      matches.push({
        index: match.index,
        length: match[0].length
      });
    }
    return matches;
  }
  #extractText() {
    if (this._extractTextPromises.length > 0) {
      return;
    }
    let deferred = Promise.resolve();
    const textOptions = {
      disableNormalization: true
    };
    for (let i = 0, ii = this._linkService.pagesCount; i < ii; i++) {
      const {
        promise,
        resolve
      } = Promise.withResolvers();
      this._extractTextPromises[i] = promise;
      deferred = deferred.then(() => {
        return this._pdfDocument.getPage(i + 1).then(pdfPage => pdfPage.getTextContent(textOptions)).then(textContent => {
          const strBuf = [];
          for (const textItem of textContent.items) {
            strBuf.push(textItem.str);
            if (textItem.hasEOL) {
              strBuf.push("\n");
            }
          }
          [this._pageContents[i], this._pageDiffs[i], this._hasDiacritics[i]] = normalize(strBuf.join(""));
          resolve();
        }, reason => {
          console.error(`Unable to get text content for page ${i + 1}`, reason);
          this._pageContents[i] = "";
          this._pageDiffs[i] = null;
          this._hasDiacritics[i] = false;
          resolve();
        });
      });
    }
  }
  #updatePage(index) {
    if (this._scrollMatches && this._selected.pageIdx === index) {
      this._linkService.page = index + 1;
    }
    this._eventBus.dispatch("updatetextlayermatches", {
      source: this,
      pageIndex: index
    });
  }
  #updateAllPages() {
    this._eventBus.dispatch("updatetextlayermatches", {
      source: this,
      pageIndex: -1
    });
  }
  #nextMatch() {
    const previous = this.#state.findPrevious;
    const currentPageIndex = this._linkService.page - 1;
    const numPages = this._linkService.pagesCount;
    this._highlightMatches = true;
    if (this._dirtyMatch) {
      this._dirtyMatch = false;
      this._selected.pageIdx = this._selected.matchIdx = -1;
      this._offset.pageIdx = currentPageIndex;
      this._offset.matchIdx = null;
      this._offset.wrapped = false;
      this._resumePageIdx = null;
      this._pageMatches.length = 0;
      this._pageMatchesLength.length = 0;
      this.#visitedPagesCount = 0;
      this._matchesCountTotal = 0;
      this.#updateAllPages();
      for (let i = 0; i < numPages; i++) {
        if (this._pendingFindMatches.has(i)) {
          continue;
        }
        this._pendingFindMatches.add(i);
        this._extractTextPromises[i].then(() => {
          this._pendingFindMatches.delete(i);
          this.#calculateMatch(i);
        });
      }
    }
    const query = this.#query;
    if (query.length === 0) {
      this.#updateUIState(FindState.FOUND);
      return;
    }
    if (this._resumePageIdx) {
      return;
    }
    const offset = this._offset;
    this._pagesToSearch = numPages;
    if (offset.matchIdx !== null) {
      const numPageMatches = this._pageMatches[offset.pageIdx].length;
      if (!previous && offset.matchIdx + 1 < numPageMatches || previous && offset.matchIdx > 0) {
        offset.matchIdx = previous ? offset.matchIdx - 1 : offset.matchIdx + 1;
        this.#updateMatch(true);
        return;
      }
      this.#advanceOffsetPage(previous);
    }
    this.#nextPageMatch();
  }
  #matchesReady(matches) {
    const offset = this._offset;
    const numMatches = matches.length;
    const previous = this.#state.findPrevious;
    if (numMatches) {
      offset.matchIdx = previous ? numMatches - 1 : 0;
      this.#updateMatch(true);
      return true;
    }
    this.#advanceOffsetPage(previous);
    if (offset.wrapped) {
      offset.matchIdx = null;
      if (this._pagesToSearch < 0) {
        this.#updateMatch(false);
        return true;
      }
    }
    return false;
  }
  #nextPageMatch() {
    if (this._resumePageIdx !== null) {
      console.error("There can only be one pending page.");
    }
    let matches = null;
    do {
      const pageIdx = this._offset.pageIdx;
      matches = this._pageMatches[pageIdx];
      if (!matches) {
        this._resumePageIdx = pageIdx;
        break;
      }
    } while (!this.#matchesReady(matches));
  }
  #advanceOffsetPage(previous) {
    const offset = this._offset;
    const numPages = this._linkService.pagesCount;
    offset.pageIdx = previous ? offset.pageIdx - 1 : offset.pageIdx + 1;
    offset.matchIdx = null;
    this._pagesToSearch--;
    if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
      offset.pageIdx = previous ? numPages - 1 : 0;
      offset.wrapped = true;
    }
  }
  #updateMatch(found = false) {
    let state = FindState.NOT_FOUND;
    const wrapped = this._offset.wrapped;
    this._offset.wrapped = false;
    if (found) {
      const previousPage = this._selected.pageIdx;
      this._selected.pageIdx = this._offset.pageIdx;
      this._selected.matchIdx = this._offset.matchIdx;
      state = wrapped ? FindState.WRAPPED : FindState.FOUND;
      if (previousPage !== -1 && previousPage !== this._selected.pageIdx) {
        this.#updatePage(previousPage);
      }
    }
    this.#updateUIState(state, this.#state.findPrevious);
    if (this._selected.pageIdx !== -1) {
      this._scrollMatches = true;
      this.#updatePage(this._selected.pageIdx);
    }
  }
  #onFindBarClose(evt) {
    const pdfDocument = this._pdfDocument;
    this._firstPageCapability.promise.then(() => {
      if (!this._pdfDocument || pdfDocument && this._pdfDocument !== pdfDocument) {
        return;
      }
      if (this._findTimeout) {
        clearTimeout(this._findTimeout);
        this._findTimeout = null;
      }
      if (this._resumePageIdx) {
        this._resumePageIdx = null;
        this._dirtyMatch = true;
      }
      this.#updateUIState(FindState.FOUND);
      this._highlightMatches = false;
      this.#updateAllPages();
    });
  }
  #requestMatchesCount() {
    const {
      pageIdx,
      matchIdx
    } = this._selected;
    let current = 0,
      total = this._matchesCountTotal;
    if (matchIdx !== -1) {
      for (let i = 0; i < pageIdx; i++) {
        current += this._pageMatches[i]?.length || 0;
      }
      current += matchIdx + 1;
    }
    if (current < 1 || current > total) {
      current = total = 0;
    }
    return {
      current,
      total
    };
  }
  #updateUIResultsCount() {
    this._eventBus.dispatch("updatefindmatchescount", {
      source: this,
      matchesCount: this.#requestMatchesCount()
    });
  }
  #updateUIState(state, previous = false) {
    if (!this.#updateMatchesCountOnProgress && (this.#visitedPagesCount !== this._linkService.pagesCount || state === FindState.PENDING)) {
      return;
    }
    this._eventBus.dispatch("updatefindcontrolstate", {
      source: this,
      state,
      previous,
      entireWord: this.#state?.entireWord ?? null,
      matchesCount: this.#requestMatchesCount(),
      rawQuery: this.#state?.query ?? null
    });
  }
}

;// CONCATENATED MODULE: ./web/pdf_find_bar.js


const MATCHES_COUNT_LIMIT = 1000;
class PDFFindBar {
  #resizeObserver = new ResizeObserver(this.#resizeObserverCallback.bind(this));
  constructor(options, eventBus) {
    this.opened = false;
    this.bar = options.bar;
    this.toggleButton = options.toggleButton;
    this.findField = options.findField;
    this.highlightAll = options.highlightAllCheckbox;
    this.caseSensitive = options.caseSensitiveCheckbox;
    this.matchDiacritics = options.matchDiacriticsCheckbox;
    this.entireWord = options.entireWordCheckbox;
    this.findMsg = options.findMsg;
    this.findResultsCount = options.findResultsCount;
    this.findPreviousButton = options.findPreviousButton;
    this.findNextButton = options.findNextButton;
    this.eventBus = eventBus;
    this.toggleButton.addEventListener("click", () => {
      this.toggle();
    });
    this.findField.addEventListener("input", () => {
      this.dispatchEvent("");
    });
    this.bar.addEventListener("keydown", e => {
      switch (e.keyCode) {
        case 13:
          if (e.target === this.findField) {
            this.dispatchEvent("again", e.shiftKey);
          }
          break;
        case 27:
          this.close();
          break;
      }
    });
    this.findPreviousButton.addEventListener("click", () => {
      this.dispatchEvent("again", true);
    });
    this.findNextButton.addEventListener("click", () => {
      this.dispatchEvent("again", false);
    });
    this.highlightAll.addEventListener("click", () => {
      this.dispatchEvent("highlightallchange");
    });
    this.caseSensitive.addEventListener("click", () => {
      this.dispatchEvent("casesensitivitychange");
    });
    this.entireWord.addEventListener("click", () => {
      this.dispatchEvent("entirewordchange");
    });
    this.matchDiacritics.addEventListener("click", () => {
      this.dispatchEvent("diacriticmatchingchange");
    });
  }
  reset() {
    this.updateUIState();
  }
  dispatchEvent(type, findPrev = false) {
    this.eventBus.dispatch("find", {
      source: this,
      type,
      query: this.findField.value,
      caseSensitive: this.caseSensitive.checked,
      entireWord: this.entireWord.checked,
      highlightAll: this.highlightAll.checked,
      findPrevious: findPrev,
      matchDiacritics: this.matchDiacritics.checked
    });
  }
  updateUIState(state, previous, matchesCount) {
    const {
      findField,
      findMsg
    } = this;
    let findMsgId = "",
      status = "";
    switch (state) {
      case FindState.FOUND:
        break;
      case FindState.PENDING:
        status = "pending";
        break;
      case FindState.NOT_FOUND:
        findMsgId = "pdfjs-find-not-found";
        status = "notFound";
        break;
      case FindState.WRAPPED:
        findMsgId = `pdfjs-find-reached-${previous ? "top" : "bottom"}`;
        break;
    }
    findField.setAttribute("data-status", status);
    findField.setAttribute("aria-invalid", state === FindState.NOT_FOUND);
    findMsg.setAttribute("data-status", status);
    if (findMsgId) {
      findMsg.setAttribute("data-l10n-id", findMsgId);
    } else {
      findMsg.removeAttribute("data-l10n-id");
      findMsg.textContent = "";
    }
    this.updateResultsCount(matchesCount);
  }
  updateResultsCount({
    current = 0,
    total = 0
  } = {}) {
    const {
      findResultsCount
    } = this;
    if (total > 0) {
      const limit = MATCHES_COUNT_LIMIT;
      findResultsCount.setAttribute("data-l10n-id", `pdfjs-find-match-count${total > limit ? "-limit" : ""}`);
      findResultsCount.setAttribute("data-l10n-args", JSON.stringify({
        limit,
        current,
        total
      }));
    } else {
      findResultsCount.removeAttribute("data-l10n-id");
      findResultsCount.textContent = "";
    }
  }
  open() {
    if (!this.opened) {
      this.#resizeObserver.observe(this.bar.parentNode);
      this.#resizeObserver.observe(this.bar);
      this.opened = true;
      toggleExpandedBtn(this.toggleButton, true, this.bar);
    }
    this.findField.select();
    this.findField.focus();
  }
  close() {
    if (!this.opened) {
      return;
    }
    this.#resizeObserver.disconnect();
    this.opened = false;
    toggleExpandedBtn(this.toggleButton, false, this.bar);
    this.eventBus.dispatch("findbarclose", {
      source: this
    });
  }
  toggle() {
    if (this.opened) {
      this.close();
    } else {
      this.open();
    }
  }
  #resizeObserverCallback(entries) {
    const {
      bar
    } = this;
    bar.classList.remove("wrapContainers");
    const findbarHeight = bar.clientHeight;
    const inputContainerHeight = bar.firstElementChild.clientHeight;
    if (findbarHeight > inputContainerHeight) {
      bar.classList.add("wrapContainers");
    }
  }
}

;// CONCATENATED MODULE: ./web/pdf_history.js


const HASH_CHANGE_TIMEOUT = 1000;
const POSITION_UPDATED_THRESHOLD = 50;
const UPDATE_VIEWAREA_TIMEOUT = 1000;
function getCurrentHash() {
  return document.location.hash;
}
class PDFHistory {
  #eventAbortController = null;
  constructor({
    linkService,
    eventBus
  }) {
    this.linkService = linkService;
    this.eventBus = eventBus;
    this._initialized = false;
    this._fingerprint = "";
    this.reset();
    this.eventBus._on("pagesinit", () => {
      this._isPagesLoaded = false;
      this.eventBus._on("pagesloaded", evt => {
        this._isPagesLoaded = !!evt.pagesCount;
      }, {
        once: true
      });
    });
  }
  initialize({
    fingerprint,
    resetHistory = false,
    updateUrl = false
  }) {
    if (!fingerprint || typeof fingerprint !== "string") {
      console.error('PDFHistory.initialize: The "fingerprint" must be a non-empty string.');
      return;
    }
    if (this._initialized) {
      this.reset();
    }
    const reInitialized = this._fingerprint !== "" && this._fingerprint !== fingerprint;
    this._fingerprint = fingerprint;
    this._updateUrl = updateUrl === true;
    this._initialized = true;
    this.#bindEvents();
    const state = window.history.state;
    this._popStateInProgress = false;
    this._blockHashChange = 0;
    this._currentHash = getCurrentHash();
    this._numPositionUpdates = 0;
    this._uid = this._maxUid = 0;
    this._destination = null;
    this._position = null;
    if (!this.#isValidState(state, true) || resetHistory) {
      const {
        hash,
        page,
        rotation
      } = this.#parseCurrentHash(true);
      if (!hash || reInitialized || resetHistory) {
        this.#pushOrReplaceState(null, true);
        return;
      }
      this.#pushOrReplaceState({
        hash,
        page,
        rotation
      }, true);
      return;
    }
    const destination = state.destination;
    this.#updateInternalState(destination, state.uid, true);
    if (destination.rotation !== undefined) {
      this._initialRotation = destination.rotation;
    }
    if (destination.dest) {
      this._initialBookmark = JSON.stringify(destination.dest);
      this._destination.page = null;
    } else if (destination.hash) {
      this._initialBookmark = destination.hash;
    } else if (destination.page) {
      this._initialBookmark = `page=${destination.page}`;
    }
  }
  reset() {
    if (this._initialized) {
      this.#pageHide();
      this._initialized = false;
      this.#unbindEvents();
    }
    if (this._updateViewareaTimeout) {
      clearTimeout(this._updateViewareaTimeout);
      this._updateViewareaTimeout = null;
    }
    this._initialBookmark = null;
    this._initialRotation = null;
  }
  push({
    namedDest = null,
    explicitDest,
    pageNumber
  }) {
    if (!this._initialized) {
      return;
    }
    if (namedDest && typeof namedDest !== "string") {
      console.error("PDFHistory.push: " + `"${namedDest}" is not a valid namedDest parameter.`);
      return;
    } else if (!Array.isArray(explicitDest)) {
      console.error("PDFHistory.push: " + `"${explicitDest}" is not a valid explicitDest parameter.`);
      return;
    } else if (!this.#isValidPage(pageNumber)) {
      if (pageNumber !== null || this._destination) {
        console.error("PDFHistory.push: " + `"${pageNumber}" is not a valid pageNumber parameter.`);
        return;
      }
    }
    const hash = namedDest || JSON.stringify(explicitDest);
    if (!hash) {
      return;
    }
    let forceReplace = false;
    if (this._destination && (isDestHashesEqual(this._destination.hash, hash) || isDestArraysEqual(this._destination.dest, explicitDest))) {
      if (this._destination.page) {
        return;
      }
      forceReplace = true;
    }
    if (this._popStateInProgress && !forceReplace) {
      return;
    }
    this.#pushOrReplaceState({
      dest: explicitDest,
      hash,
      page: pageNumber,
      rotation: this.linkService.rotation
    }, forceReplace);
    if (!this._popStateInProgress) {
      this._popStateInProgress = true;
      Promise.resolve().then(() => {
        this._popStateInProgress = false;
      });
    }
  }
  pushPage(pageNumber) {
    if (!this._initialized) {
      return;
    }
    if (!this.#isValidPage(pageNumber)) {
      console.error(`PDFHistory.pushPage: "${pageNumber}" is not a valid page number.`);
      return;
    }
    if (this._destination?.page === pageNumber) {
      return;
    }
    if (this._popStateInProgress) {
      return;
    }
    this.#pushOrReplaceState({
      dest: null,
      hash: `page=${pageNumber}`,
      page: pageNumber,
      rotation: this.linkService.rotation
    });
    if (!this._popStateInProgress) {
      this._popStateInProgress = true;
      Promise.resolve().then(() => {
        this._popStateInProgress = false;
      });
    }
  }
  pushCurrentPosition() {
    if (!this._initialized || this._popStateInProgress) {
      return;
    }
    this.#tryPushCurrentPosition();
  }
  back() {
    if (!this._initialized || this._popStateInProgress) {
      return;
    }
    const state = window.history.state;
    if (this.#isValidState(state) && state.uid > 0) {
      window.history.back();
    }
  }
  forward() {
    if (!this._initialized || this._popStateInProgress) {
      return;
    }
    const state = window.history.state;
    if (this.#isValidState(state) && state.uid < this._maxUid) {
      window.history.forward();
    }
  }
  get popStateInProgress() {
    return this._initialized && (this._popStateInProgress || this._blockHashChange > 0);
  }
  get initialBookmark() {
    return this._initialized ? this._initialBookmark : null;
  }
  get initialRotation() {
    return this._initialized ? this._initialRotation : null;
  }
  #pushOrReplaceState(destination, forceReplace = false) {
    const shouldReplace = forceReplace || !this._destination;
    const newState = {
      fingerprint: this._fingerprint,
      uid: shouldReplace ? this._uid : this._uid + 1,
      destination
    };
    this.#updateInternalState(destination, newState.uid);
    let newUrl;
    if (this._updateUrl && destination?.hash) {
      const baseUrl = document.location.href.split("#", 1)[0];
      if (!baseUrl.startsWith("file://")) {
        newUrl = `${baseUrl}#${destination.hash}`;
      }
    }
    if (shouldReplace) {
      window.history.replaceState(newState, "", newUrl);
    } else {
      window.history.pushState(newState, "", newUrl);
    }
  }
  #tryPushCurrentPosition(temporary = false) {
    if (!this._position) {
      return;
    }
    let position = this._position;
    if (temporary) {
      position = Object.assign(Object.create(null), this._position);
      position.temporary = true;
    }
    if (!this._destination) {
      this.#pushOrReplaceState(position);
      return;
    }
    if (this._destination.temporary) {
      this.#pushOrReplaceState(position, true);
      return;
    }
    if (this._destination.hash === position.hash) {
      return;
    }
    if (!this._destination.page && (POSITION_UPDATED_THRESHOLD <= 0 || this._numPositionUpdates <= POSITION_UPDATED_THRESHOLD)) {
      return;
    }
    let forceReplace = false;
    if (this._destination.page >= position.first && this._destination.page <= position.page) {
      if (this._destination.dest !== undefined || !this._destination.first) {
        return;
      }
      forceReplace = true;
    }
    this.#pushOrReplaceState(position, forceReplace);
  }
  #isValidPage(val) {
    return Number.isInteger(val) && val > 0 && val <= this.linkService.pagesCount;
  }
  #isValidState(state, checkReload = false) {
    if (!state) {
      return false;
    }
    if (state.fingerprint !== this._fingerprint) {
      if (checkReload) {
        if (typeof state.fingerprint !== "string" || state.fingerprint.length !== this._fingerprint.length) {
          return false;
        }
        const [perfEntry] = performance.getEntriesByType("navigation");
        if (perfEntry?.type !== "reload") {
          return false;
        }
      } else {
        return false;
      }
    }
    if (!Number.isInteger(state.uid) || state.uid < 0) {
      return false;
    }
    if (state.destination === null || typeof state.destination !== "object") {
      return false;
    }
    return true;
  }
  #updateInternalState(destination, uid, removeTemporary = false) {
    if (this._updateViewareaTimeout) {
      clearTimeout(this._updateViewareaTimeout);
      this._updateViewareaTimeout = null;
    }
    if (removeTemporary && destination?.temporary) {
      delete destination.temporary;
    }
    this._destination = destination;
    this._uid = uid;
    this._maxUid = Math.max(this._maxUid, uid);
    this._numPositionUpdates = 0;
  }
  #parseCurrentHash(checkNameddest = false) {
    const hash = unescape(getCurrentHash()).substring(1);
    const params = parseQueryString(hash);
    const nameddest = params.get("nameddest") || "";
    let page = params.get("page") | 0;
    if (!this.#isValidPage(page) || checkNameddest && nameddest.length > 0) {
      page = null;
    }
    return {
      hash,
      page,
      rotation: this.linkService.rotation
    };
  }
  #updateViewarea({
    location
  }) {
    if (this._updateViewareaTimeout) {
      clearTimeout(this._updateViewareaTimeout);
      this._updateViewareaTimeout = null;
    }
    this._position = {
      hash: location.pdfOpenParams.substring(1),
      page: this.linkService.page,
      first: location.pageNumber,
      rotation: location.rotation
    };
    if (this._popStateInProgress) {
      return;
    }
    if (POSITION_UPDATED_THRESHOLD > 0 && this._isPagesLoaded && this._destination && !this._destination.page) {
      this._numPositionUpdates++;
    }
    if (UPDATE_VIEWAREA_TIMEOUT > 0) {
      this._updateViewareaTimeout = setTimeout(() => {
        if (!this._popStateInProgress) {
          this.#tryPushCurrentPosition(true);
        }
        this._updateViewareaTimeout = null;
      }, UPDATE_VIEWAREA_TIMEOUT);
    }
  }
  #popState({
    state
  }) {
    const newHash = getCurrentHash(),
      hashChanged = this._currentHash !== newHash;
    this._currentHash = newHash;
    if (!state) {
      this._uid++;
      const {
        hash,
        page,
        rotation
      } = this.#parseCurrentHash();
      this.#pushOrReplaceState({
        hash,
        page,
        rotation
      }, true);
      return;
    }
    if (!this.#isValidState(state)) {
      return;
    }
    this._popStateInProgress = true;
    if (hashChanged) {
      this._blockHashChange++;
      waitOnEventOrTimeout({
        target: window,
        name: "hashchange",
        delay: HASH_CHANGE_TIMEOUT
      }).then(() => {
        this._blockHashChange--;
      });
    }
    const destination = state.destination;
    this.#updateInternalState(destination, state.uid, true);
    if (isValidRotation(destination.rotation)) {
      this.linkService.rotation = destination.rotation;
    }
    if (destination.dest) {
      this.linkService.goToDestination(destination.dest);
    } else if (destination.hash) {
      this.linkService.setHash(destination.hash);
    } else if (destination.page) {
      this.linkService.page = destination.page;
    }
    Promise.resolve().then(() => {
      this._popStateInProgress = false;
    });
  }
  #pageHide() {
    if (!this._destination || this._destination.temporary) {
      this.#tryPushCurrentPosition();
    }
  }
  #bindEvents() {
    if (this.#eventAbortController) {
      return;
    }
    this.#eventAbortController = new AbortController();
    const {
      signal
    } = this.#eventAbortController;
    this.eventBus._on("updateviewarea", this.#updateViewarea.bind(this), {
      signal
    });
    window.addEventListener("popstate", this.#popState.bind(this), {
      signal
    });
    window.addEventListener("pagehide", this.#pageHide.bind(this), {
      signal
    });
  }
  #unbindEvents() {
    this.#eventAbortController?.abort();
    this.#eventAbortController = null;
  }
}
function isDestHashesEqual(destHash, pushHash) {
  if (typeof destHash !== "string" || typeof pushHash !== "string") {
    return false;
  }
  if (destHash === pushHash) {
    return true;
  }
  const nameddest = parseQueryString(destHash).get("nameddest");
  if (nameddest === pushHash) {
    return true;
  }
  return false;
}
function isDestArraysEqual(firstDest, secondDest) {
  function isEntryEqual(first, second) {
    if (typeof first !== typeof second) {
      return false;
    }
    if (Array.isArray(first) || Array.isArray(second)) {
      return false;
    }
    if (first !== null && typeof first === "object" && second !== null) {
      if (Object.keys(first).length !== Object.keys(second).length) {
        return false;
      }
      for (const key in first) {
        if (!isEntryEqual(first[key], second[key])) {
          return false;
        }
      }
      return true;
    }
    return first === second || Number.isNaN(first) && Number.isNaN(second);
  }
  if (!(Array.isArray(firstDest) && Array.isArray(secondDest))) {
    return false;
  }
  if (firstDest.length !== secondDest.length) {
    return false;
  }
  for (let i = 0, ii = firstDest.length; i < ii; i++) {
    if (!isEntryEqual(firstDest[i], secondDest[i])) {
      return false;
    }
  }
  return true;
}

;// CONCATENATED MODULE: ./web/pdf_layer_viewer.js

class PDFLayerViewer extends BaseTreeViewer {
  constructor(options) {
    super(options);
    this.eventBus._on("optionalcontentconfigchanged", evt => {
      this.#updateLayers(evt.promise);
    });
    this.eventBus._on("resetlayers", () => {
      this.#updateLayers();
    });
    this.eventBus._on("togglelayerstree", this._toggleAllTreeItems.bind(this));
  }
  reset() {
    super.reset();
    this._optionalContentConfig = null;
    this._optionalContentHash = null;
  }
  _dispatchEvent(layersCount) {
    this.eventBus.dispatch("layersloaded", {
      source: this,
      layersCount
    });
  }
  _bindLink(element, {
    groupId,
    input
  }) {
    const setVisibility = () => {
      this._optionalContentConfig.setVisibility(groupId, input.checked);
      this._optionalContentHash = this._optionalContentConfig.getHash();
      this.eventBus.dispatch("optionalcontentconfig", {
        source: this,
        promise: Promise.resolve(this._optionalContentConfig)
      });
    };
    element.onclick = evt => {
      if (evt.target === input) {
        setVisibility();
        return true;
      } else if (evt.target !== element) {
        return true;
      }
      input.checked = !input.checked;
      setVisibility();
      return false;
    };
  }
  _setNestedName(element, {
    name = null
  }) {
    if (typeof name === "string") {
      element.textContent = this._normalizeTextContent(name);
      return;
    }
    element.setAttribute("data-l10n-id", "pdfjs-additional-layers");
    element.style.fontStyle = "italic";
    this._l10n.translateOnce(element);
  }
  _addToggleButton(div, {
    name = null
  }) {
    super._addToggleButton(div, name === null);
  }
  _toggleAllTreeItems() {
    if (!this._optionalContentConfig) {
      return;
    }
    super._toggleAllTreeItems();
  }
  render({
    optionalContentConfig,
    pdfDocument
  }) {
    if (this._optionalContentConfig) {
      this.reset();
    }
    this._optionalContentConfig = optionalContentConfig || null;
    this._pdfDocument = pdfDocument || null;
    const groups = optionalContentConfig?.getOrder();
    if (!groups) {
      this._dispatchEvent(0);
      return;
    }
    this._optionalContentHash = optionalContentConfig.getHash();
    const fragment = document.createDocumentFragment(),
      queue = [{
        parent: fragment,
        groups
      }];
    let layersCount = 0,
      hasAnyNesting = false;
    while (queue.length > 0) {
      const levelData = queue.shift();
      for (const groupId of levelData.groups) {
        const div = document.createElement("div");
        div.className = "treeItem";
        const element = document.createElement("a");
        div.append(element);
        if (typeof groupId === "object") {
          hasAnyNesting = true;
          this._addToggleButton(div, groupId);
          this._setNestedName(element, groupId);
          const itemsDiv = document.createElement("div");
          itemsDiv.className = "treeItems";
          div.append(itemsDiv);
          queue.push({
            parent: itemsDiv,
            groups: groupId.order
          });
        } else {
          const group = optionalContentConfig.getGroup(groupId);
          const input = document.createElement("input");
          this._bindLink(element, {
            groupId,
            input
          });
          input.type = "checkbox";
          input.checked = group.visible;
          const label = document.createElement("label");
          label.textContent = this._normalizeTextContent(group.name);
          label.append(input);
          element.append(label);
          layersCount++;
        }
        levelData.parent.append(div);
      }
    }
    this._finishRendering(fragment, layersCount, hasAnyNesting);
  }
  async #updateLayers(promise = null) {
    if (!this._optionalContentConfig) {
      return;
    }
    const pdfDocument = this._pdfDocument;
    const optionalContentConfig = await (promise || pdfDocument.getOptionalContentConfig({
      intent: "display"
    }));
    if (pdfDocument !== this._pdfDocument) {
      return;
    }
    if (promise) {
      if (optionalContentConfig.getHash() === this._optionalContentHash) {
        return;
      }
    } else {
      this.eventBus.dispatch("optionalcontentconfig", {
        source: this,
        promise: Promise.resolve(optionalContentConfig)
      });
    }
    this.render({
      optionalContentConfig,
      pdfDocument: this._pdfDocument
    });
  }
}

;// CONCATENATED MODULE: ./web/pdf_outline_viewer.js


class PDFOutlineViewer extends BaseTreeViewer {
  constructor(options) {
    super(options);
    this.linkService = options.linkService;
    this.downloadManager = options.downloadManager;
    this.eventBus._on("toggleoutlinetree", this._toggleAllTreeItems.bind(this));
    this.eventBus._on("currentoutlineitem", this._currentOutlineItem.bind(this));
    this.eventBus._on("pagechanging", evt => {
      this._currentPageNumber = evt.pageNumber;
    });
    this.eventBus._on("pagesloaded", evt => {
      this._isPagesLoaded = !!evt.pagesCount;
      this._currentOutlineItemCapability?.resolve(this._isPagesLoaded);
    });
    this.eventBus._on("sidebarviewchanged", evt => {
      this._sidebarView = evt.view;
    });
  }
  reset() {
    super.reset();
    this._outline = null;
    this._pageNumberToDestHashCapability = null;
    this._currentPageNumber = 1;
    this._isPagesLoaded = null;
    this._currentOutlineItemCapability?.resolve(false);
    this._currentOutlineItemCapability = null;
  }
  _dispatchEvent(outlineCount) {
    this._currentOutlineItemCapability = Promise.withResolvers();
    if (outlineCount === 0 || this._pdfDocument?.loadingParams.disableAutoFetch) {
      this._currentOutlineItemCapability.resolve(false);
    } else if (this._isPagesLoaded !== null) {
      this._currentOutlineItemCapability.resolve(this._isPagesLoaded);
    }
    this.eventBus.dispatch("outlineloaded", {
      source: this,
      outlineCount,
      currentOutlineItemPromise: this._currentOutlineItemCapability.promise
    });
  }
  _bindLink(element, {
    url,
    newWindow,
    action,
    attachment,
    dest,
    setOCGState
  }) {
    const {
      linkService
    } = this;
    if (url) {
      linkService.addLinkAttributes(element, url, newWindow);
      return;
    }
    if (action) {
      element.href = linkService.getAnchorUrl("");
      element.onclick = () => {
        linkService.executeNamedAction(action);
        return false;
      };
      return;
    }
    if (attachment) {
      element.href = linkService.getAnchorUrl("");
      element.onclick = () => {
        this.downloadManager.openOrDownloadData(attachment.content, attachment.filename);
        return false;
      };
      return;
    }
    if (setOCGState) {
      element.href = linkService.getAnchorUrl("");
      element.onclick = () => {
        linkService.executeSetOCGState(setOCGState);
        return false;
      };
      return;
    }
    element.href = linkService.getDestinationHash(dest);
    element.onclick = evt => {
      this._updateCurrentTreeItem(evt.target.parentNode);
      if (dest) {
        linkService.goToDestination(dest);
      }
      return false;
    };
  }
  _setStyles(element, {
    bold,
    italic
  }) {
    if (bold) {
      element.style.fontWeight = "bold";
    }
    if (italic) {
      element.style.fontStyle = "italic";
    }
  }
  _addToggleButton(div, {
    count,
    items
  }) {
    let hidden = false;
    if (count < 0) {
      let totalCount = items.length;
      if (totalCount > 0) {
        const queue = [...items];
        while (queue.length > 0) {
          const {
            count: nestedCount,
            items: nestedItems
          } = queue.shift();
          if (nestedCount > 0 && nestedItems.length > 0) {
            totalCount += nestedItems.length;
            queue.push(...nestedItems);
          }
        }
      }
      if (Math.abs(count) === totalCount) {
        hidden = true;
      }
    }
    super._addToggleButton(div, hidden);
  }
  _toggleAllTreeItems() {
    if (!this._outline) {
      return;
    }
    super._toggleAllTreeItems();
  }
  render({
    outline,
    pdfDocument
  }) {
    if (this._outline) {
      this.reset();
    }
    this._outline = outline || null;
    this._pdfDocument = pdfDocument || null;
    if (!outline) {
      this._dispatchEvent(0);
      return;
    }
    const fragment = document.createDocumentFragment();
    const queue = [{
      parent: fragment,
      items: outline
    }];
    let outlineCount = 0,
      hasAnyNesting = false;
    while (queue.length > 0) {
      const levelData = queue.shift();
      for (const item of levelData.items) {
        const div = document.createElement("div");
        div.className = "treeItem";
        const element = document.createElement("a");
        this._bindLink(element, item);
        this._setStyles(element, item);
        element.textContent = this._normalizeTextContent(item.title);
        div.append(element);
        if (item.items.length > 0) {
          hasAnyNesting = true;
          this._addToggleButton(div, item);
          const itemsDiv = document.createElement("div");
          itemsDiv.className = "treeItems";
          div.append(itemsDiv);
          queue.push({
            parent: itemsDiv,
            items: item.items
          });
        }
        levelData.parent.append(div);
        outlineCount++;
      }
    }
    this._finishRendering(fragment, outlineCount, hasAnyNesting);
  }
  async _currentOutlineItem() {
    if (!this._isPagesLoaded) {
      throw new Error("_currentOutlineItem: All pages have not been loaded.");
    }
    if (!this._outline || !this._pdfDocument) {
      return;
    }
    const pageNumberToDestHash = await this._getPageNumberToDestHash(this._pdfDocument);
    if (!pageNumberToDestHash) {
      return;
    }
    this._updateCurrentTreeItem(null);
    if (this._sidebarView !== SidebarView.OUTLINE) {
      return;
    }
    for (let i = this._currentPageNumber; i > 0; i--) {
      const destHash = pageNumberToDestHash.get(i);
      if (!destHash) {
        continue;
      }
      const linkElement = this.container.querySelector(`a[href="${destHash}"]`);
      if (!linkElement) {
        continue;
      }
      this._scrollToCurrentTreeItem(linkElement.parentNode);
      break;
    }
  }
  async _getPageNumberToDestHash(pdfDocument) {
    if (this._pageNumberToDestHashCapability) {
      return this._pageNumberToDestHashCapability.promise;
    }
    this._pageNumberToDestHashCapability = Promise.withResolvers();
    const pageNumberToDestHash = new Map(),
      pageNumberNesting = new Map();
    const queue = [{
      nesting: 0,
      items: this._outline
    }];
    while (queue.length > 0) {
      const levelData = queue.shift(),
        currentNesting = levelData.nesting;
      for (const {
        dest,
        items
      } of levelData.items) {
        let explicitDest, pageNumber;
        if (typeof dest === "string") {
          explicitDest = await pdfDocument.getDestination(dest);
          if (pdfDocument !== this._pdfDocument) {
            return null;
          }
        } else {
          explicitDest = dest;
        }
        if (Array.isArray(explicitDest)) {
          const [destRef] = explicitDest;
          if (destRef && typeof destRef === "object") {
            pageNumber = pdfDocument.cachedPageNumber(destRef);
          } else if (Number.isInteger(destRef)) {
            pageNumber = destRef + 1;
          }
          if (Number.isInteger(pageNumber) && (!pageNumberToDestHash.has(pageNumber) || currentNesting > pageNumberNesting.get(pageNumber))) {
            const destHash = this.linkService.getDestinationHash(dest);
            pageNumberToDestHash.set(pageNumber, destHash);
            pageNumberNesting.set(pageNumber, currentNesting);
          }
        }
        if (items.length > 0) {
          queue.push({
            nesting: currentNesting + 1,
            items
          });
        }
      }
    }
    this._pageNumberToDestHashCapability.resolve(pageNumberToDestHash.size > 0 ? pageNumberToDestHash : null);
    return this._pageNumberToDestHashCapability.promise;
  }
}

;// CONCATENATED MODULE: ./web/pdf_presentation_mode.js


const DELAY_BEFORE_HIDING_CONTROLS = 3000;
const ACTIVE_SELECTOR = "pdfPresentationMode";
const CONTROLS_SELECTOR = "pdfPresentationModeControls";
const MOUSE_SCROLL_COOLDOWN_TIME = 50;
const PAGE_SWITCH_THRESHOLD = 0.1;
const SWIPE_MIN_DISTANCE_THRESHOLD = 50;
const SWIPE_ANGLE_THRESHOLD = Math.PI / 6;
class PDFPresentationMode {
  #state = PresentationModeState.UNKNOWN;
  #args = null;
  #fullscreenChangeAbortController = null;
  #windowAbortController = null;
  constructor({
    container,
    pdfViewer,
    eventBus
  }) {
    this.container = container;
    this.pdfViewer = pdfViewer;
    this.eventBus = eventBus;
    this.contextMenuOpen = false;
    this.mouseScrollTimeStamp = 0;
    this.mouseScrollDelta = 0;
    this.touchSwipeState = null;
  }
  async request() {
    const {
      container,
      pdfViewer
    } = this;
    if (this.active || !pdfViewer.pagesCount || !container.requestFullscreen) {
      return false;
    }
    this.#addFullscreenChangeListeners();
    this.#notifyStateChange(PresentationModeState.CHANGING);
    const promise = container.requestFullscreen();
    this.#args = {
      pageNumber: pdfViewer.currentPageNumber,
      scaleValue: pdfViewer.currentScaleValue,
      scrollMode: pdfViewer.scrollMode,
      spreadMode: null,
      annotationEditorMode: null
    };
    if (pdfViewer.spreadMode !== SpreadMode.NONE && !(pdfViewer.pageViewsReady && pdfViewer.hasEqualPageSizes)) {
      console.warn("Ignoring Spread modes when entering PresentationMode, " + "since the document may contain varying page sizes.");
      this.#args.spreadMode = pdfViewer.spreadMode;
    }
    if (pdfViewer.annotationEditorMode !== AnnotationEditorType.DISABLE) {
      this.#args.annotationEditorMode = pdfViewer.annotationEditorMode;
    }
    try {
      await promise;
      pdfViewer.focus();
      return true;
    } catch {
      this.#removeFullscreenChangeListeners();
      this.#notifyStateChange(PresentationModeState.NORMAL);
    }
    return false;
  }
  get active() {
    return this.#state === PresentationModeState.CHANGING || this.#state === PresentationModeState.FULLSCREEN;
  }
  #mouseWheel(evt) {
    if (!this.active) {
      return;
    }
    evt.preventDefault();
    const delta = normalizeWheelEventDelta(evt);
    const currentTime = Date.now();
    const storedTime = this.mouseScrollTimeStamp;
    if (currentTime > storedTime && currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) {
      return;
    }
    if (this.mouseScrollDelta > 0 && delta < 0 || this.mouseScrollDelta < 0 && delta > 0) {
      this.#resetMouseScrollState();
    }
    this.mouseScrollDelta += delta;
    if (Math.abs(this.mouseScrollDelta) >= PAGE_SWITCH_THRESHOLD) {
      const totalDelta = this.mouseScrollDelta;
      this.#resetMouseScrollState();
      const success = totalDelta > 0 ? this.pdfViewer.previousPage() : this.pdfViewer.nextPage();
      if (success) {
        this.mouseScrollTimeStamp = currentTime;
      }
    }
  }
  #notifyStateChange(state) {
    this.#state = state;
    this.eventBus.dispatch("presentationmodechanged", {
      source: this,
      state
    });
  }
  #enter() {
    this.#notifyStateChange(PresentationModeState.FULLSCREEN);
    this.container.classList.add(ACTIVE_SELECTOR);
    setTimeout(() => {
      this.pdfViewer.scrollMode = ScrollMode.PAGE;
      if (this.#args.spreadMode !== null) {
        this.pdfViewer.spreadMode = SpreadMode.NONE;
      }
      this.pdfViewer.currentPageNumber = this.#args.pageNumber;
      this.pdfViewer.currentScaleValue = "page-fit";
      if (this.#args.annotationEditorMode !== null) {
        this.pdfViewer.annotationEditorMode = {
          mode: AnnotationEditorType.NONE
        };
      }
    }, 0);
    this.#addWindowListeners();
    this.#showControls();
    this.contextMenuOpen = false;
    document.getSelection().empty();
  }
  #exit() {
    const pageNumber = this.pdfViewer.currentPageNumber;
    this.container.classList.remove(ACTIVE_SELECTOR);
    setTimeout(() => {
      this.#removeFullscreenChangeListeners();
      this.#notifyStateChange(PresentationModeState.NORMAL);
      this.pdfViewer.scrollMode = this.#args.scrollMode;
      if (this.#args.spreadMode !== null) {
        this.pdfViewer.spreadMode = this.#args.spreadMode;
      }
      this.pdfViewer.currentScaleValue = this.#args.scaleValue;
      this.pdfViewer.currentPageNumber = pageNumber;
      if (this.#args.annotationEditorMode !== null) {
        this.pdfViewer.annotationEditorMode = {
          mode: this.#args.annotationEditorMode
        };
      }
      this.#args = null;
    }, 0);
    this.#removeWindowListeners();
    this.#hideControls();
    this.#resetMouseScrollState();
    this.contextMenuOpen = false;
  }
  #mouseDown(evt) {
    if (this.contextMenuOpen) {
      this.contextMenuOpen = false;
      evt.preventDefault();
      return;
    }
    if (evt.button !== 0) {
      return;
    }
    if (evt.target.href && evt.target.parentNode?.hasAttribute("data-internal-link")) {
      return;
    }
    evt.preventDefault();
    if (evt.shiftKey) {
      this.pdfViewer.previousPage();
    } else {
      this.pdfViewer.nextPage();
    }
  }
  #contextMenu() {
    this.contextMenuOpen = true;
  }
  #showControls() {
    if (this.controlsTimeout) {
      clearTimeout(this.controlsTimeout);
    } else {
      this.container.classList.add(CONTROLS_SELECTOR);
    }
    this.controlsTimeout = setTimeout(() => {
      this.container.classList.remove(CONTROLS_SELECTOR);
      delete this.controlsTimeout;
    }, DELAY_BEFORE_HIDING_CONTROLS);
  }
  #hideControls() {
    if (!this.controlsTimeout) {
      return;
    }
    clearTimeout(this.controlsTimeout);
    this.container.classList.remove(CONTROLS_SELECTOR);
    delete this.controlsTimeout;
  }
  #resetMouseScrollState() {
    this.mouseScrollTimeStamp = 0;
    this.mouseScrollDelta = 0;
  }
  #touchSwipe(evt) {
    if (!this.active) {
      return;
    }
    if (evt.touches.length > 1) {
      this.touchSwipeState = null;
      return;
    }
    switch (evt.type) {
      case "touchstart":
        this.touchSwipeState = {
          startX: evt.touches[0].pageX,
          startY: evt.touches[0].pageY,
          endX: evt.touches[0].pageX,
          endY: evt.touches[0].pageY
        };
        break;
      case "touchmove":
        if (this.touchSwipeState === null) {
          return;
        }
        this.touchSwipeState.endX = evt.touches[0].pageX;
        this.touchSwipeState.endY = evt.touches[0].pageY;
        evt.preventDefault();
        break;
      case "touchend":
        if (this.touchSwipeState === null) {
          return;
        }
        let delta = 0;
        const dx = this.touchSwipeState.endX - this.touchSwipeState.startX;
        const dy = this.touchSwipeState.endY - this.touchSwipeState.startY;
        const absAngle = Math.abs(Math.atan2(dy, dx));
        if (Math.abs(dx) > SWIPE_MIN_DISTANCE_THRESHOLD && (absAngle <= SWIPE_ANGLE_THRESHOLD || absAngle >= Math.PI - SWIPE_ANGLE_THRESHOLD)) {
          delta = dx;
        } else if (Math.abs(dy) > SWIPE_MIN_DISTANCE_THRESHOLD && Math.abs(absAngle - Math.PI / 2) <= SWIPE_ANGLE_THRESHOLD) {
          delta = dy;
        }
        if (delta > 0) {
          this.pdfViewer.previousPage();
        } else if (delta < 0) {
          this.pdfViewer.nextPage();
        }
        break;
    }
  }
  #addWindowListeners() {
    if (this.#windowAbortController) {
      return;
    }
    this.#windowAbortController = new AbortController();
    const {
      signal
    } = this.#windowAbortController;
    const touchSwipeBind = this.#touchSwipe.bind(this);
    window.addEventListener("mousemove", this.#showControls.bind(this), {
      signal
    });
    window.addEventListener("mousedown", this.#mouseDown.bind(this), {
      signal
    });
    window.addEventListener("wheel", this.#mouseWheel.bind(this), {
      passive: false,
      signal
    });
    window.addEventListener("keydown", this.#resetMouseScrollState.bind(this), {
      signal
    });
    window.addEventListener("contextmenu", this.#contextMenu.bind(this), {
      signal
    });
    window.addEventListener("touchstart", touchSwipeBind, {
      signal
    });
    window.addEventListener("touchmove", touchSwipeBind, {
      signal
    });
    window.addEventListener("touchend", touchSwipeBind, {
      signal
    });
  }
  #removeWindowListeners() {
    this.#windowAbortController?.abort();
    this.#windowAbortController = null;
  }
  #addFullscreenChangeListeners() {
    if (this.#fullscreenChangeAbortController) {
      return;
    }
    this.#fullscreenChangeAbortController = new AbortController();
    window.addEventListener("fullscreenchange", () => {
      if (document.fullscreenElement) {
        this.#enter();
      } else {
        this.#exit();
      }
    }, {
      signal: this.#fullscreenChangeAbortController.signal
    });
  }
  #removeFullscreenChangeListeners() {
    this.#fullscreenChangeAbortController?.abort();
    this.#fullscreenChangeAbortController = null;
  }
}

;// CONCATENATED MODULE: ./web/xfa_layer_builder.js

class XfaLayerBuilder {
  constructor({
    pdfPage,
    annotationStorage = null,
    linkService,
    xfaHtml = null
  }) {
    this.pdfPage = pdfPage;
    this.annotationStorage = annotationStorage;
    this.linkService = linkService;
    this.xfaHtml = xfaHtml;
    this.div = null;
    this._cancelled = false;
  }
  async render(viewport, intent = "display") {
    if (intent === "print") {
      const parameters = {
        viewport: viewport.clone({
          dontFlip: true
        }),
        div: this.div,
        xfaHtml: this.xfaHtml,
        annotationStorage: this.annotationStorage,
        linkService: this.linkService,
        intent
      };
      this.div = document.createElement("div");
      parameters.div = this.div;
      return XfaLayer.render(parameters);
    }
    const xfaHtml = await this.pdfPage.getXfa();
    if (this._cancelled || !xfaHtml) {
      return {
        textDivs: []
      };
    }
    const parameters = {
      viewport: viewport.clone({
        dontFlip: true
      }),
      div: this.div,
      xfaHtml,
      annotationStorage: this.annotationStorage,
      linkService: this.linkService,
      intent
    };
    if (this.div) {
      return XfaLayer.update(parameters);
    }
    this.div = document.createElement("div");
    parameters.div = this.div;
    return XfaLayer.render(parameters);
  }
  cancel() {
    this._cancelled = true;
  }
  hide() {
    if (!this.div) {
      return;
    }
    this.div.hidden = true;
  }
}

;// CONCATENATED MODULE: ./web/print_utils.js



function getXfaHtmlForPrinting(printContainer, pdfDocument) {
  const xfaHtml = pdfDocument.allXfaHtml;
  const linkService = new SimpleLinkService();
  const scale = Math.round(PixelsPerInch.PDF_TO_CSS_UNITS * 100) / 100;
  for (const xfaPage of xfaHtml.children) {
    const page = document.createElement("div");
    page.className = "xfaPrintedPage";
    printContainer.append(page);
    const builder = new XfaLayerBuilder({
      pdfPage: null,
      annotationStorage: pdfDocument.annotationStorage,
      linkService,
      xfaHtml: xfaPage
    });
    const viewport = getXfaPageViewport(xfaPage, {
      scale
    });
    builder.render(viewport, "print");
    page.append(builder.div);
  }
}

;// CONCATENATED MODULE: ./web/firefox_print_service.js


function composePage(pdfDocument, pageNumber, size, printContainer, printResolution, optionalContentConfigPromise, printAnnotationStoragePromise) {
  const canvas = document.createElement("canvas");
  const PRINT_UNITS = printResolution / PixelsPerInch.PDF;
  canvas.width = Math.floor(size.width * PRINT_UNITS);
  canvas.height = Math.floor(size.height * PRINT_UNITS);
  const canvasWrapper = document.createElement("div");
  canvasWrapper.className = "printedPage";
  canvasWrapper.append(canvas);
  printContainer.append(canvasWrapper);
  let currentRenderTask = null;
  canvas.mozPrintCallback = function (obj) {
    const ctx = obj.context;
    ctx.save();
    ctx.fillStyle = "rgb(255, 255, 255)";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    ctx.restore();
    let thisRenderTask = null;
    Promise.all([pdfDocument.getPage(pageNumber), printAnnotationStoragePromise]).then(function ([pdfPage, printAnnotationStorage]) {
      if (currentRenderTask) {
        currentRenderTask.cancel();
        currentRenderTask = null;
      }
      const renderContext = {
        canvasContext: ctx,
        transform: [PRINT_UNITS, 0, 0, PRINT_UNITS, 0, 0],
        viewport: pdfPage.getViewport({
          scale: 1,
          rotation: size.rotation
        }),
        intent: "print",
        annotationMode: AnnotationMode.ENABLE_STORAGE,
        optionalContentConfigPromise,
        printAnnotationStorage
      };
      currentRenderTask = thisRenderTask = pdfPage.render(renderContext);
      return thisRenderTask.promise;
    }).then(function () {
      if (currentRenderTask === thisRenderTask) {
        currentRenderTask = null;
      }
      obj.done();
    }, function (reason) {
      if (!(reason instanceof RenderingCancelledException)) {
        console.error(reason);
      }
      if (currentRenderTask === thisRenderTask) {
        currentRenderTask.cancel();
        currentRenderTask = null;
      }
      if ("abort" in obj) {
        obj.abort();
      } else {
        obj.done();
      }
    });
  };
}
class FirefoxPrintService {
  constructor({
    pdfDocument,
    pagesOverview,
    printContainer,
    printResolution,
    printAnnotationStoragePromise = null
  }) {
    this.pdfDocument = pdfDocument;
    this.pagesOverview = pagesOverview;
    this.printContainer = printContainer;
    this._printResolution = printResolution || 150;
    this._optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
      intent: "print"
    });
    this._printAnnotationStoragePromise = printAnnotationStoragePromise || Promise.resolve();
  }
  layout() {
    const {
      pdfDocument,
      pagesOverview,
      printContainer,
      _printResolution,
      _optionalContentConfigPromise,
      _printAnnotationStoragePromise
    } = this;
    const body = document.querySelector("body");
    body.setAttribute("data-pdfjsprinting", true);
    const {
      width,
      height
    } = this.pagesOverview[0];
    const hasEqualPageSizes = this.pagesOverview.every(size => size.width === width && size.height === height);
    if (!hasEqualPageSizes) {
      console.warn("Not all pages have the same size. The printed result may be incorrect!");
    }
    this.pageStyleSheet = document.createElement("style");
    this.pageStyleSheet.textContent = `@page { size: ${width}pt ${height}pt;}`;
    body.append(this.pageStyleSheet);
    if (pdfDocument.isPureXfa) {
      getXfaHtmlForPrinting(printContainer, pdfDocument);
      return;
    }
    for (let i = 0, ii = pagesOverview.length; i < ii; ++i) {
      composePage(pdfDocument, i + 1, pagesOverview[i], printContainer, _printResolution, _optionalContentConfigPromise, _printAnnotationStoragePromise);
    }
  }
  destroy() {
    this.printContainer.textContent = "";
    const body = document.querySelector("body");
    body.removeAttribute("data-pdfjsprinting");
    if (this.pageStyleSheet) {
      this.pageStyleSheet.remove();
      this.pageStyleSheet = null;
    }
  }
}
class PDFPrintServiceFactory {
  static get supportsPrinting() {
    const canvas = document.createElement("canvas");
    return shadow(this, "supportsPrinting", "mozPrintCallback" in canvas);
  }
  static createPrintService(params) {
    return new FirefoxPrintService(params);
  }
}

;// CONCATENATED MODULE: ./web/pdf_rendering_queue.js


const CLEANUP_TIMEOUT = 30000;
class PDFRenderingQueue {
  constructor() {
    this.pdfViewer = null;
    this.pdfThumbnailViewer = null;
    this.onIdle = null;
    this.highestPriorityPage = null;
    this.idleTimeout = null;
    this.printing = false;
    this.isThumbnailViewEnabled = false;
  }
  setViewer(pdfViewer) {
    this.pdfViewer = pdfViewer;
  }
  setThumbnailViewer(pdfThumbnailViewer) {
    this.pdfThumbnailViewer = pdfThumbnailViewer;
  }
  isHighestPriority(view) {
    return this.highestPriorityPage === view.renderingId;
  }
  renderHighestPriority(currentlyVisiblePages) {
    if (this.idleTimeout) {
      clearTimeout(this.idleTimeout);
      this.idleTimeout = null;
    }
    if (this.pdfViewer.forceRendering(currentlyVisiblePages)) {
      return;
    }
    if (this.isThumbnailViewEnabled && this.pdfThumbnailViewer?.forceRendering()) {
      return;
    }
    if (this.printing) {
      return;
    }
    if (this.onIdle) {
      this.idleTimeout = setTimeout(this.onIdle.bind(this), CLEANUP_TIMEOUT);
    }
  }
  getHighestPriority(visible, views, scrolledDown, preRenderExtra = false) {
    const visibleViews = visible.views,
      numVisible = visibleViews.length;
    if (numVisible === 0) {
      return null;
    }
    for (let i = 0; i < numVisible; i++) {
      const view = visibleViews[i].view;
      if (!this.isViewFinished(view)) {
        return view;
      }
    }
    const firstId = visible.first.id,
      lastId = visible.last.id;
    if (lastId - firstId + 1 > numVisible) {
      const visibleIds = visible.ids;
      for (let i = 1, ii = lastId - firstId; i < ii; i++) {
        const holeId = scrolledDown ? firstId + i : lastId - i;
        if (visibleIds.has(holeId)) {
          continue;
        }
        const holeView = views[holeId - 1];
        if (!this.isViewFinished(holeView)) {
          return holeView;
        }
      }
    }
    let preRenderIndex = scrolledDown ? lastId : firstId - 2;
    let preRenderView = views[preRenderIndex];
    if (preRenderView && !this.isViewFinished(preRenderView)) {
      return preRenderView;
    }
    if (preRenderExtra) {
      preRenderIndex += scrolledDown ? 1 : -1;
      preRenderView = views[preRenderIndex];
      if (preRenderView && !this.isViewFinished(preRenderView)) {
        return preRenderView;
      }
    }
    return null;
  }
  isViewFinished(view) {
    return view.renderingState === RenderingStates.FINISHED;
  }
  renderView(view) {
    switch (view.renderingState) {
      case RenderingStates.FINISHED:
        return false;
      case RenderingStates.PAUSED:
        this.highestPriorityPage = view.renderingId;
        view.resume();
        break;
      case RenderingStates.RUNNING:
        this.highestPriorityPage = view.renderingId;
        break;
      case RenderingStates.INITIAL:
        this.highestPriorityPage = view.renderingId;
        view.draw().finally(() => {
          this.renderHighestPriority();
        }).catch(reason => {
          if (reason instanceof RenderingCancelledException) {
            return;
          }
          console.error(`renderView: "${reason}"`);
        });
        break;
    }
    return true;
  }
}

;// CONCATENATED MODULE: ./web/pdf_scripting_manager.js


class PDFScriptingManager {
  #closeCapability = null;
  #destroyCapability = null;
  #docProperties = null;
  #eventAbortController = null;
  #eventBus = null;
  #externalServices = null;
  #pdfDocument = null;
  #pdfViewer = null;
  #ready = false;
  #scripting = null;
  #willPrintCapability = null;
  constructor({
    eventBus,
    externalServices = null,
    docProperties = null
  }) {
    this.#eventBus = eventBus;
    this.#externalServices = externalServices;
    this.#docProperties = docProperties;
  }
  setViewer(pdfViewer) {
    this.#pdfViewer = pdfViewer;
  }
  async setDocument(pdfDocument) {
    if (this.#pdfDocument) {
      await this.#destroyScripting();
    }
    this.#pdfDocument = pdfDocument;
    if (!pdfDocument) {
      return;
    }
    const [objects, calculationOrder, docActions] = await Promise.all([pdfDocument.getFieldObjects(), pdfDocument.getCalculationOrderIds(), pdfDocument.getJSActions()]);
    if (!objects && !docActions) {
      await this.#destroyScripting();
      return;
    }
    if (pdfDocument !== this.#pdfDocument) {
      return;
    }
    try {
      this.#scripting = this.#initScripting();
    } catch (error) {
      console.error(`setDocument: "${error.message}".`);
      await this.#destroyScripting();
      return;
    }
    const eventBus = this.#eventBus;
    this.#eventAbortController = new AbortController();
    const {
      signal
    } = this.#eventAbortController;
    eventBus._on("updatefromsandbox", event => {
      if (event?.source === window) {
        this.#updateFromSandbox(event.detail);
      }
    }, {
      signal
    });
    eventBus._on("dispatcheventinsandbox", event => {
      this.#scripting?.dispatchEventInSandbox(event.detail);
    }, {
      signal
    });
    eventBus._on("pagechanging", ({
      pageNumber,
      previous
    }) => {
      if (pageNumber === previous) {
        return;
      }
      this.#dispatchPageClose(previous);
      this.#dispatchPageOpen(pageNumber);
    }, {
      signal
    });
    eventBus._on("pagerendered", ({
      pageNumber
    }) => {
      if (!this._pageOpenPending.has(pageNumber)) {
        return;
      }
      if (pageNumber !== this.#pdfViewer.currentPageNumber) {
        return;
      }
      this.#dispatchPageOpen(pageNumber);
    }, {
      signal
    });
    eventBus._on("pagesdestroy", async () => {
      await this.#dispatchPageClose(this.#pdfViewer.currentPageNumber);
      await this.#scripting?.dispatchEventInSandbox({
        id: "doc",
        name: "WillClose"
      });
      this.#closeCapability?.resolve();
    }, {
      signal
    });
    try {
      const docProperties = await this.#docProperties(pdfDocument);
      if (pdfDocument !== this.#pdfDocument) {
        return;
      }
      await this.#scripting.createSandbox({
        objects,
        calculationOrder,
        appInfo: {
          platform: navigator.platform,
          language: navigator.language
        },
        docInfo: {
          ...docProperties,
          actions: docActions
        }
      });
      eventBus.dispatch("sandboxcreated", {
        source: this
      });
    } catch (error) {
      console.error(`setDocument: "${error.message}".`);
      await this.#destroyScripting();
      return;
    }
    await this.#scripting?.dispatchEventInSandbox({
      id: "doc",
      name: "Open"
    });
    await this.#dispatchPageOpen(this.#pdfViewer.currentPageNumber, true);
    Promise.resolve().then(() => {
      if (pdfDocument === this.#pdfDocument) {
        this.#ready = true;
      }
    });
  }
  async dispatchWillSave() {
    return this.#scripting?.dispatchEventInSandbox({
      id: "doc",
      name: "WillSave"
    });
  }
  async dispatchDidSave() {
    return this.#scripting?.dispatchEventInSandbox({
      id: "doc",
      name: "DidSave"
    });
  }
  async dispatchWillPrint() {
    if (!this.#scripting) {
      return;
    }
    await this.#willPrintCapability?.promise;
    this.#willPrintCapability = Promise.withResolvers();
    try {
      await this.#scripting.dispatchEventInSandbox({
        id: "doc",
        name: "WillPrint"
      });
    } catch (ex) {
      this.#willPrintCapability.resolve();
      this.#willPrintCapability = null;
      throw ex;
    }
    await this.#willPrintCapability.promise;
  }
  async dispatchDidPrint() {
    return this.#scripting?.dispatchEventInSandbox({
      id: "doc",
      name: "DidPrint"
    });
  }
  get destroyPromise() {
    return this.#destroyCapability?.promise || null;
  }
  get ready() {
    return this.#ready;
  }
  get _pageOpenPending() {
    return shadow(this, "_pageOpenPending", new Set());
  }
  get _visitedPages() {
    return shadow(this, "_visitedPages", new Map());
  }
  async #updateFromSandbox(detail) {
    const pdfViewer = this.#pdfViewer;
    const isInPresentationMode = pdfViewer.isInPresentationMode || pdfViewer.isChangingPresentationMode;
    const {
      id,
      siblings,
      command,
      value
    } = detail;
    if (!id) {
      switch (command) {
        case "clear":
          console.clear();
          break;
        case "error":
          console.error(value);
          break;
        case "layout":
          if (!isInPresentationMode) {
            const modes = apiPageLayoutToViewerModes(value);
            pdfViewer.spreadMode = modes.spreadMode;
          }
          break;
        case "page-num":
          pdfViewer.currentPageNumber = value + 1;
          break;
        case "print":
          await pdfViewer.pagesPromise;
          this.#eventBus.dispatch("print", {
            source: this
          });
          break;
        case "println":
          console.log(value);
          break;
        case "zoom":
          if (!isInPresentationMode) {
            pdfViewer.currentScaleValue = value;
          }
          break;
        case "SaveAs":
          this.#eventBus.dispatch("download", {
            source: this
          });
          break;
        case "FirstPage":
          pdfViewer.currentPageNumber = 1;
          break;
        case "LastPage":
          pdfViewer.currentPageNumber = pdfViewer.pagesCount;
          break;
        case "NextPage":
          pdfViewer.nextPage();
          break;
        case "PrevPage":
          pdfViewer.previousPage();
          break;
        case "ZoomViewIn":
          if (!isInPresentationMode) {
            pdfViewer.increaseScale();
          }
          break;
        case "ZoomViewOut":
          if (!isInPresentationMode) {
            pdfViewer.decreaseScale();
          }
          break;
        case "WillPrintFinished":
          this.#willPrintCapability?.resolve();
          this.#willPrintCapability = null;
          break;
      }
      return;
    }
    if (isInPresentationMode && detail.focus) {
      return;
    }
    delete detail.id;
    delete detail.siblings;
    const ids = siblings ? [id, ...siblings] : [id];
    for (const elementId of ids) {
      const element = document.querySelector(`[data-element-id="${elementId}"]`);
      if (element) {
        element.dispatchEvent(new CustomEvent("updatefromsandbox", {
          detail
        }));
      } else {
        this.#pdfDocument?.annotationStorage.setValue(elementId, detail);
      }
    }
  }
  async #dispatchPageOpen(pageNumber, initialize = false) {
    const pdfDocument = this.#pdfDocument,
      visitedPages = this._visitedPages;
    if (initialize) {
      this.#closeCapability = Promise.withResolvers();
    }
    if (!this.#closeCapability) {
      return;
    }
    const pageView = this.#pdfViewer.getPageView(pageNumber - 1);
    if (pageView?.renderingState !== RenderingStates.FINISHED) {
      this._pageOpenPending.add(pageNumber);
      return;
    }
    this._pageOpenPending.delete(pageNumber);
    const actionsPromise = (async () => {
      const actions = await (!visitedPages.has(pageNumber) ? pageView.pdfPage?.getJSActions() : null);
      if (pdfDocument !== this.#pdfDocument) {
        return;
      }
      await this.#scripting?.dispatchEventInSandbox({
        id: "page",
        name: "PageOpen",
        pageNumber,
        actions
      });
    })();
    visitedPages.set(pageNumber, actionsPromise);
  }
  async #dispatchPageClose(pageNumber) {
    const pdfDocument = this.#pdfDocument,
      visitedPages = this._visitedPages;
    if (!this.#closeCapability) {
      return;
    }
    if (this._pageOpenPending.has(pageNumber)) {
      return;
    }
    const actionsPromise = visitedPages.get(pageNumber);
    if (!actionsPromise) {
      return;
    }
    visitedPages.set(pageNumber, null);
    await actionsPromise;
    if (pdfDocument !== this.#pdfDocument) {
      return;
    }
    await this.#scripting?.dispatchEventInSandbox({
      id: "page",
      name: "PageClose",
      pageNumber
    });
  }
  #initScripting() {
    this.#destroyCapability = Promise.withResolvers();
    if (this.#scripting) {
      throw new Error("#initScripting: Scripting already exists.");
    }
    return this.#externalServices.createScripting();
  }
  async #destroyScripting() {
    if (!this.#scripting) {
      this.#pdfDocument = null;
      this.#destroyCapability?.resolve();
      return;
    }
    if (this.#closeCapability) {
      await Promise.race([this.#closeCapability.promise, new Promise(resolve => {
        setTimeout(resolve, 1000);
      })]).catch(() => {});
      this.#closeCapability = null;
    }
    this.#pdfDocument = null;
    try {
      await this.#scripting.destroySandbox();
    } catch {}
    this.#willPrintCapability?.reject(new Error("Scripting destroyed."));
    this.#willPrintCapability = null;
    this.#eventAbortController?.abort();
    this.#eventAbortController = null;
    this._pageOpenPending.clear();
    this._visitedPages.clear();
    this.#scripting = null;
    this.#ready = false;
    this.#destroyCapability?.resolve();
  }
}

;// CONCATENATED MODULE: ./web/pdf_sidebar.js

const SIDEBAR_WIDTH_VAR = "--sidebar-width";
const SIDEBAR_MIN_WIDTH = 200;
const SIDEBAR_RESIZING_CLASS = "sidebarResizing";
const UI_NOTIFICATION_CLASS = "pdfSidebarNotification";
class PDFSidebar {
  #isRTL = false;
  #mouseAC = null;
  #outerContainerWidth = null;
  #width = null;
  constructor({
    elements,
    eventBus,
    l10n
  }) {
    this.isOpen = false;
    this.active = SidebarView.THUMBS;
    this.isInitialViewSet = false;
    this.isInitialEventDispatched = false;
    this.onToggled = null;
    this.onUpdateThumbnails = null;
    this.outerContainer = elements.outerContainer;
    this.sidebarContainer = elements.sidebarContainer;
    this.toggleButton = elements.toggleButton;
    this.resizer = elements.resizer;
    this.thumbnailButton = elements.thumbnailButton;
    this.outlineButton = elements.outlineButton;
    this.attachmentsButton = elements.attachmentsButton;
    this.layersButton = elements.layersButton;
    this.thumbnailView = elements.thumbnailView;
    this.outlineView = elements.outlineView;
    this.attachmentsView = elements.attachmentsView;
    this.layersView = elements.layersView;
    this._currentOutlineItemButton = elements.currentOutlineItemButton;
    this.eventBus = eventBus;
    this.#isRTL = l10n.getDirection() === "rtl";
    this.#addEventListeners();
  }
  reset() {
    this.isInitialViewSet = false;
    this.isInitialEventDispatched = false;
    this.#hideUINotification(true);
    this.switchView(SidebarView.THUMBS);
    this.outlineButton.disabled = false;
    this.attachmentsButton.disabled = false;
    this.layersButton.disabled = false;
    this._currentOutlineItemButton.disabled = true;
  }
  get visibleView() {
    return this.isOpen ? this.active : SidebarView.NONE;
  }
  setInitialView(view = SidebarView.NONE) {
    if (this.isInitialViewSet) {
      return;
    }
    this.isInitialViewSet = true;
    if (view === SidebarView.NONE || view === SidebarView.UNKNOWN) {
      this.#dispatchEvent();
      return;
    }
    this.switchView(view, true);
    if (!this.isInitialEventDispatched) {
      this.#dispatchEvent();
    }
  }
  switchView(view, forceOpen = false) {
    const isViewChanged = view !== this.active;
    let forceRendering = false;
    switch (view) {
      case SidebarView.NONE:
        if (this.isOpen) {
          this.close();
        }
        return;
      case SidebarView.THUMBS:
        if (this.isOpen && isViewChanged) {
          forceRendering = true;
        }
        break;
      case SidebarView.OUTLINE:
        if (this.outlineButton.disabled) {
          return;
        }
        break;
      case SidebarView.ATTACHMENTS:
        if (this.attachmentsButton.disabled) {
          return;
        }
        break;
      case SidebarView.LAYERS:
        if (this.layersButton.disabled) {
          return;
        }
        break;
      default:
        console.error(`PDFSidebar.switchView: "${view}" is not a valid view.`);
        return;
    }
    this.active = view;
    toggleCheckedBtn(this.thumbnailButton, view === SidebarView.THUMBS, this.thumbnailView);
    toggleCheckedBtn(this.outlineButton, view === SidebarView.OUTLINE, this.outlineView);
    toggleCheckedBtn(this.attachmentsButton, view === SidebarView.ATTACHMENTS, this.attachmentsView);
    toggleCheckedBtn(this.layersButton, view === SidebarView.LAYERS, this.layersView);
    if (forceOpen && !this.isOpen) {
      this.open();
      return;
    }
    if (forceRendering) {
      this.onUpdateThumbnails();
      this.onToggled();
    }
    if (isViewChanged) {
      this.#dispatchEvent();
    }
  }
  open() {
    if (this.isOpen) {
      return;
    }
    this.isOpen = true;
    toggleExpandedBtn(this.toggleButton, true);
    this.outerContainer.classList.add("sidebarMoving", "sidebarOpen");
    if (this.active === SidebarView.THUMBS) {
      this.onUpdateThumbnails();
    }
    this.onToggled();
    this.#dispatchEvent();
    this.#hideUINotification();
  }
  close(evt = null) {
    if (!this.isOpen) {
      return;
    }
    this.isOpen = false;
    toggleExpandedBtn(this.toggleButton, false);
    this.outerContainer.classList.add("sidebarMoving");
    this.outerContainer.classList.remove("sidebarOpen");
    this.onToggled();
    this.#dispatchEvent();
    if (evt?.detail > 0) {
      this.toggleButton.blur();
    }
  }
  toggle(evt = null) {
    if (this.isOpen) {
      this.close(evt);
    } else {
      this.open();
    }
  }
  #dispatchEvent() {
    if (this.isInitialViewSet) {
      this.isInitialEventDispatched ||= true;
    }
    this.eventBus.dispatch("sidebarviewchanged", {
      source: this,
      view: this.visibleView
    });
  }
  #showUINotification() {
    this.toggleButton.setAttribute("data-l10n-id", "pdfjs-toggle-sidebar-notification-button");
    if (!this.isOpen) {
      this.toggleButton.classList.add(UI_NOTIFICATION_CLASS);
    }
  }
  #hideUINotification(reset = false) {
    if (this.isOpen || reset) {
      this.toggleButton.classList.remove(UI_NOTIFICATION_CLASS);
    }
    if (reset) {
      this.toggleButton.setAttribute("data-l10n-id", "pdfjs-toggle-sidebar-button");
    }
  }
  #addEventListeners() {
    const {
      eventBus,
      outerContainer
    } = this;
    this.sidebarContainer.addEventListener("transitionend", evt => {
      if (evt.target === this.sidebarContainer) {
        outerContainer.classList.remove("sidebarMoving");
        eventBus.dispatch("resize", {
          source: this
        });
      }
    });
    this.toggleButton.addEventListener("click", evt => {
      this.toggle(evt);
    });
    this.thumbnailButton.addEventListener("click", () => {
      this.switchView(SidebarView.THUMBS);
    });
    this.outlineButton.addEventListener("click", () => {
      this.switchView(SidebarView.OUTLINE);
    });
    this.outlineButton.addEventListener("dblclick", () => {
      eventBus.dispatch("toggleoutlinetree", {
        source: this
      });
    });
    this.attachmentsButton.addEventListener("click", () => {
      this.switchView(SidebarView.ATTACHMENTS);
    });
    this.layersButton.addEventListener("click", () => {
      this.switchView(SidebarView.LAYERS);
    });
    this.layersButton.addEventListener("dblclick", () => {
      eventBus.dispatch("resetlayers", {
        source: this
      });
    });
    this._currentOutlineItemButton.addEventListener("click", () => {
      eventBus.dispatch("currentoutlineitem", {
        source: this
      });
    });
    const onTreeLoaded = (count, button, view) => {
      button.disabled = !count;
      if (count) {
        this.#showUINotification();
      } else if (this.active === view) {
        this.switchView(SidebarView.THUMBS);
      }
    };
    eventBus._on("outlineloaded", evt => {
      onTreeLoaded(evt.outlineCount, this.outlineButton, SidebarView.OUTLINE);
      evt.currentOutlineItemPromise.then(enabled => {
        if (!this.isInitialViewSet) {
          return;
        }
        this._currentOutlineItemButton.disabled = !enabled;
      });
    });
    eventBus._on("attachmentsloaded", evt => {
      onTreeLoaded(evt.attachmentsCount, this.attachmentsButton, SidebarView.ATTACHMENTS);
    });
    eventBus._on("layersloaded", evt => {
      onTreeLoaded(evt.layersCount, this.layersButton, SidebarView.LAYERS);
    });
    eventBus._on("presentationmodechanged", evt => {
      if (evt.state === PresentationModeState.NORMAL && this.visibleView === SidebarView.THUMBS) {
        this.onUpdateThumbnails();
      }
    });
    this.resizer.addEventListener("mousedown", evt => {
      if (evt.button !== 0) {
        return;
      }
      outerContainer.classList.add(SIDEBAR_RESIZING_CLASS);
      this.#mouseAC = new AbortController();
      const opts = {
        signal: this.#mouseAC.signal
      };
      window.addEventListener("mousemove", this.#mouseMove.bind(this), opts);
      window.addEventListener("mouseup", this.#mouseUp.bind(this), opts);
      window.addEventListener("blur", this.#mouseUp.bind(this), opts);
    });
    eventBus._on("resize", evt => {
      if (evt.source !== window) {
        return;
      }
      this.#outerContainerWidth = null;
      if (!this.#width) {
        return;
      }
      if (!this.isOpen) {
        this.#updateWidth(this.#width);
        return;
      }
      outerContainer.classList.add(SIDEBAR_RESIZING_CLASS);
      const updated = this.#updateWidth(this.#width);
      Promise.resolve().then(() => {
        outerContainer.classList.remove(SIDEBAR_RESIZING_CLASS);
        if (updated) {
          eventBus.dispatch("resize", {
            source: this
          });
        }
      });
    });
  }
  get outerContainerWidth() {
    return this.#outerContainerWidth ||= this.outerContainer.clientWidth;
  }
  #updateWidth(width = 0) {
    const maxWidth = Math.floor(this.outerContainerWidth / 2);
    if (width > maxWidth) {
      width = maxWidth;
    }
    if (width < SIDEBAR_MIN_WIDTH) {
      width = SIDEBAR_MIN_WIDTH;
    }
    if (width === this.#width) {
      return false;
    }
    this.#width = width;
    docStyle.setProperty(SIDEBAR_WIDTH_VAR, `${width}px`);
    return true;
  }
  #mouseMove(evt) {
    let width = evt.clientX;
    if (this.#isRTL) {
      width = this.outerContainerWidth - width;
    }
    this.#updateWidth(width);
  }
  #mouseUp(evt) {
    this.outerContainer.classList.remove(SIDEBAR_RESIZING_CLASS);
    this.eventBus.dispatch("resize", {
      source: this
    });
    this.#mouseAC?.abort();
    this.#mouseAC = null;
  }
}

;// CONCATENATED MODULE: ./web/pdf_thumbnail_view.js


const DRAW_UPSCALE_FACTOR = 2;
const MAX_NUM_SCALING_STEPS = 3;
const THUMBNAIL_WIDTH = 98;
class TempImageFactory {
  static #tempCanvas = null;
  static getCanvas(width, height) {
    const tempCanvas = this.#tempCanvas ||= document.createElement("canvas");
    tempCanvas.width = width;
    tempCanvas.height = height;
    const ctx = tempCanvas.getContext("2d", {
      alpha: false
    });
    ctx.save();
    ctx.fillStyle = "rgb(255, 255, 255)";
    ctx.fillRect(0, 0, width, height);
    ctx.restore();
    return [tempCanvas, tempCanvas.getContext("2d")];
  }
  static destroyCanvas() {
    const tempCanvas = this.#tempCanvas;
    if (tempCanvas) {
      tempCanvas.width = 0;
      tempCanvas.height = 0;
    }
    this.#tempCanvas = null;
  }
}
class PDFThumbnailView {
  constructor({
    container,
    eventBus,
    id,
    defaultViewport,
    optionalContentConfigPromise,
    linkService,
    renderingQueue,
    pageColors,
    enableHWA
  }) {
    this.id = id;
    this.renderingId = "thumbnail" + id;
    this.pageLabel = null;
    this.pdfPage = null;
    this.rotation = 0;
    this.viewport = defaultViewport;
    this.pdfPageRotate = defaultViewport.rotation;
    this._optionalContentConfigPromise = optionalContentConfigPromise || null;
    this.pageColors = pageColors || null;
    this.enableHWA = enableHWA || false;
    this.eventBus = eventBus;
    this.linkService = linkService;
    this.renderingQueue = renderingQueue;
    this.renderTask = null;
    this.renderingState = RenderingStates.INITIAL;
    this.resume = null;
    const anchor = document.createElement("a");
    anchor.href = linkService.getAnchorUrl("#page=" + id);
    anchor.setAttribute("data-l10n-id", "pdfjs-thumb-page-title");
    anchor.setAttribute("data-l10n-args", this.#pageL10nArgs);
    anchor.onclick = function () {
      linkService.goToPage(id);
      return false;
    };
    this.anchor = anchor;
    const div = document.createElement("div");
    div.className = "thumbnail";
    div.setAttribute("data-page-number", this.id);
    this.div = div;
    this.#updateDims();
    const img = document.createElement("div");
    img.className = "thumbnailImage";
    this._placeholderImg = img;
    div.append(img);
    anchor.append(div);
    container.append(anchor);
  }
  #updateDims() {
    const {
      width,
      height
    } = this.viewport;
    const ratio = width / height;
    this.canvasWidth = THUMBNAIL_WIDTH;
    this.canvasHeight = this.canvasWidth / ratio | 0;
    this.scale = this.canvasWidth / width;
    const {
      style
    } = this.div;
    style.setProperty("--thumbnail-width", `${this.canvasWidth}px`);
    style.setProperty("--thumbnail-height", `${this.canvasHeight}px`);
  }
  setPdfPage(pdfPage) {
    this.pdfPage = pdfPage;
    this.pdfPageRotate = pdfPage.rotate;
    const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
    this.viewport = pdfPage.getViewport({
      scale: 1,
      rotation: totalRotation
    });
    this.reset();
  }
  reset() {
    this.cancelRendering();
    this.renderingState = RenderingStates.INITIAL;
    this.div.removeAttribute("data-loaded");
    this.image?.replaceWith(this._placeholderImg);
    this.#updateDims();
    if (this.image) {
      this.image.removeAttribute("src");
      delete this.image;
    }
  }
  update({
    rotation = null
  }) {
    if (typeof rotation === "number") {
      this.rotation = rotation;
    }
    const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
    this.viewport = this.viewport.clone({
      scale: 1,
      rotation: totalRotation
    });
    this.reset();
  }
  cancelRendering() {
    if (this.renderTask) {
      this.renderTask.cancel();
      this.renderTask = null;
    }
    this.resume = null;
  }
  #getPageDrawContext(upscaleFactor = 1, enableHWA = this.enableHWA) {
    const canvas = document.createElement("canvas");
    const ctx = canvas.getContext("2d", {
      alpha: false,
      willReadFrequently: !enableHWA
    });
    const outputScale = new OutputScale();
    canvas.width = upscaleFactor * this.canvasWidth * outputScale.sx | 0;
    canvas.height = upscaleFactor * this.canvasHeight * outputScale.sy | 0;
    const transform = outputScale.scaled ? [outputScale.sx, 0, 0, outputScale.sy, 0, 0] : null;
    return {
      ctx,
      canvas,
      transform
    };
  }
  #convertCanvasToImage(canvas) {
    if (this.renderingState !== RenderingStates.FINISHED) {
      throw new Error("#convertCanvasToImage: Rendering has not finished.");
    }
    const reducedCanvas = this.#reduceImage(canvas);
    const image = document.createElement("img");
    image.className = "thumbnailImage";
    image.setAttribute("data-l10n-id", "pdfjs-thumb-page-canvas");
    image.setAttribute("data-l10n-args", this.#pageL10nArgs);
    image.src = reducedCanvas.toDataURL();
    this.image = image;
    this.div.setAttribute("data-loaded", true);
    this._placeholderImg.replaceWith(image);
    reducedCanvas.width = 0;
    reducedCanvas.height = 0;
  }
  async #finishRenderTask(renderTask, canvas, error = null) {
    if (renderTask === this.renderTask) {
      this.renderTask = null;
    }
    if (error instanceof RenderingCancelledException) {
      return;
    }
    this.renderingState = RenderingStates.FINISHED;
    this.#convertCanvasToImage(canvas);
    if (error) {
      throw error;
    }
  }
  async draw() {
    if (this.renderingState !== RenderingStates.INITIAL) {
      console.error("Must be in new state before drawing");
      return undefined;
    }
    const {
      pdfPage
    } = this;
    if (!pdfPage) {
      this.renderingState = RenderingStates.FINISHED;
      throw new Error("pdfPage is not loaded");
    }
    this.renderingState = RenderingStates.RUNNING;
    const {
      ctx,
      canvas,
      transform
    } = this.#getPageDrawContext(DRAW_UPSCALE_FACTOR);
    const drawViewport = this.viewport.clone({
      scale: DRAW_UPSCALE_FACTOR * this.scale
    });
    const renderContinueCallback = cont => {
      if (!this.renderingQueue.isHighestPriority(this)) {
        this.renderingState = RenderingStates.PAUSED;
        this.resume = () => {
          this.renderingState = RenderingStates.RUNNING;
          cont();
        };
        return;
      }
      cont();
    };
    const renderContext = {
      canvasContext: ctx,
      transform,
      viewport: drawViewport,
      optionalContentConfigPromise: this._optionalContentConfigPromise,
      pageColors: this.pageColors
    };
    const renderTask = this.renderTask = pdfPage.render(renderContext);
    renderTask.onContinue = renderContinueCallback;
    const resultPromise = renderTask.promise.then(() => this.#finishRenderTask(renderTask, canvas), error => this.#finishRenderTask(renderTask, canvas, error));
    resultPromise.finally(() => {
      canvas.width = 0;
      canvas.height = 0;
      this.eventBus.dispatch("thumbnailrendered", {
        source: this,
        pageNumber: this.id,
        pdfPage: this.pdfPage
      });
    });
    return resultPromise;
  }
  setImage(pageView) {
    if (this.renderingState !== RenderingStates.INITIAL) {
      return;
    }
    const {
      thumbnailCanvas: canvas,
      pdfPage,
      scale
    } = pageView;
    if (!canvas) {
      return;
    }
    if (!this.pdfPage) {
      this.setPdfPage(pdfPage);
    }
    if (scale < this.scale) {
      return;
    }
    this.renderingState = RenderingStates.FINISHED;
    this.#convertCanvasToImage(canvas);
  }
  #reduceImage(img) {
    const {
      ctx,
      canvas
    } = this.#getPageDrawContext(1, true);
    if (img.width <= 2 * canvas.width) {
      ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height);
      return canvas;
    }
    let reducedWidth = canvas.width << MAX_NUM_SCALING_STEPS;
    let reducedHeight = canvas.height << MAX_NUM_SCALING_STEPS;
    const [reducedImage, reducedImageCtx] = TempImageFactory.getCanvas(reducedWidth, reducedHeight);
    while (reducedWidth > img.width || reducedHeight > img.height) {
      reducedWidth >>= 1;
      reducedHeight >>= 1;
    }
    reducedImageCtx.drawImage(img, 0, 0, img.width, img.height, 0, 0, reducedWidth, reducedHeight);
    while (reducedWidth > 2 * canvas.width) {
      reducedImageCtx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight, 0, 0, reducedWidth >> 1, reducedHeight >> 1);
      reducedWidth >>= 1;
      reducedHeight >>= 1;
    }
    ctx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight, 0, 0, canvas.width, canvas.height);
    return canvas;
  }
  get #pageL10nArgs() {
    return JSON.stringify({
      page: this.pageLabel ?? this.id
    });
  }
  setPageLabel(label) {
    this.pageLabel = typeof label === "string" ? label : null;
    this.anchor.setAttribute("data-l10n-args", this.#pageL10nArgs);
    if (this.renderingState !== RenderingStates.FINISHED) {
      return;
    }
    this.image?.setAttribute("data-l10n-args", this.#pageL10nArgs);
  }
}

;// CONCATENATED MODULE: ./web/pdf_thumbnail_viewer.js


const THUMBNAIL_SCROLL_MARGIN = -19;
const THUMBNAIL_SELECTED_CLASS = "selected";
class PDFThumbnailViewer {
  constructor({
    container,
    eventBus,
    linkService,
    renderingQueue,
    pageColors,
    abortSignal,
    enableHWA
  }) {
    this.container = container;
    this.eventBus = eventBus;
    this.linkService = linkService;
    this.renderingQueue = renderingQueue;
    this.pageColors = pageColors || null;
    this.enableHWA = enableHWA || false;
    this.scroll = watchScroll(this.container, this.#scrollUpdated.bind(this), abortSignal);
    this.#resetView();
  }
  #scrollUpdated() {
    this.renderingQueue.renderHighestPriority();
  }
  getThumbnail(index) {
    return this._thumbnails[index];
  }
  #getVisibleThumbs() {
    return getVisibleElements({
      scrollEl: this.container,
      views: this._thumbnails
    });
  }
  scrollThumbnailIntoView(pageNumber) {
    if (!this.pdfDocument) {
      return;
    }
    const thumbnailView = this._thumbnails[pageNumber - 1];
    if (!thumbnailView) {
      console.error('scrollThumbnailIntoView: Invalid "pageNumber" parameter.');
      return;
    }
    if (pageNumber !== this._currentPageNumber) {
      const prevThumbnailView = this._thumbnails[this._currentPageNumber - 1];
      prevThumbnailView.div.classList.remove(THUMBNAIL_SELECTED_CLASS);
      thumbnailView.div.classList.add(THUMBNAIL_SELECTED_CLASS);
    }
    const {
      first,
      last,
      views
    } = this.#getVisibleThumbs();
    if (views.length > 0) {
      let shouldScroll = false;
      if (pageNumber <= first.id || pageNumber >= last.id) {
        shouldScroll = true;
      } else {
        for (const {
          id,
          percent
        } of views) {
          if (id !== pageNumber) {
            continue;
          }
          shouldScroll = percent < 100;
          break;
        }
      }
      if (shouldScroll) {
        scrollIntoView(thumbnailView.div, {
          top: THUMBNAIL_SCROLL_MARGIN
        });
      }
    }
    this._currentPageNumber = pageNumber;
  }
  get pagesRotation() {
    return this._pagesRotation;
  }
  set pagesRotation(rotation) {
    if (!isValidRotation(rotation)) {
      throw new Error("Invalid thumbnails rotation angle.");
    }
    if (!this.pdfDocument) {
      return;
    }
    if (this._pagesRotation === rotation) {
      return;
    }
    this._pagesRotation = rotation;
    const updateArgs = {
      rotation
    };
    for (const thumbnail of this._thumbnails) {
      thumbnail.update(updateArgs);
    }
  }
  cleanup() {
    for (const thumbnail of this._thumbnails) {
      if (thumbnail.renderingState !== RenderingStates.FINISHED) {
        thumbnail.reset();
      }
    }
    TempImageFactory.destroyCanvas();
  }
  #resetView() {
    this._thumbnails = [];
    this._currentPageNumber = 1;
    this._pageLabels = null;
    this._pagesRotation = 0;
    this.container.textContent = "";
  }
  setDocument(pdfDocument) {
    if (this.pdfDocument) {
      this.#cancelRendering();
      this.#resetView();
    }
    this.pdfDocument = pdfDocument;
    if (!pdfDocument) {
      return;
    }
    const firstPagePromise = pdfDocument.getPage(1);
    const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
      intent: "display"
    });
    firstPagePromise.then(firstPdfPage => {
      const pagesCount = pdfDocument.numPages;
      const viewport = firstPdfPage.getViewport({
        scale: 1
      });
      for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
        const thumbnail = new PDFThumbnailView({
          container: this.container,
          eventBus: this.eventBus,
          id: pageNum,
          defaultViewport: viewport.clone(),
          optionalContentConfigPromise,
          linkService: this.linkService,
          renderingQueue: this.renderingQueue,
          pageColors: this.pageColors,
          enableHWA: this.enableHWA
        });
        this._thumbnails.push(thumbnail);
      }
      this._thumbnails[0]?.setPdfPage(firstPdfPage);
      const thumbnailView = this._thumbnails[this._currentPageNumber - 1];
      thumbnailView.div.classList.add(THUMBNAIL_SELECTED_CLASS);
    }).catch(reason => {
      console.error("Unable to initialize thumbnail viewer", reason);
    });
  }
  #cancelRendering() {
    for (const thumbnail of this._thumbnails) {
      thumbnail.cancelRendering();
    }
  }
  setPageLabels(labels) {
    if (!this.pdfDocument) {
      return;
    }
    if (!labels) {
      this._pageLabels = null;
    } else if (!(Array.isArray(labels) && this.pdfDocument.numPages === labels.length)) {
      this._pageLabels = null;
      console.error("PDFThumbnailViewer_setPageLabels: Invalid page labels.");
    } else {
      this._pageLabels = labels;
    }
    for (let i = 0, ii = this._thumbnails.length; i < ii; i++) {
      this._thumbnails[i].setPageLabel(this._pageLabels?.[i] ?? null);
    }
  }
  async #ensurePdfPageLoaded(thumbView) {
    if (thumbView.pdfPage) {
      return thumbView.pdfPage;
    }
    try {
      const pdfPage = await this.pdfDocument.getPage(thumbView.id);
      if (!thumbView.pdfPage) {
        thumbView.setPdfPage(pdfPage);
      }
      return pdfPage;
    } catch (reason) {
      console.error("Unable to get page for thumb view", reason);
      return null;
    }
  }
  #getScrollAhead(visible) {
    if (visible.first?.id === 1) {
      return true;
    } else if (visible.last?.id === this._thumbnails.length) {
      return false;
    }
    return this.scroll.down;
  }
  forceRendering() {
    const visibleThumbs = this.#getVisibleThumbs();
    const scrollAhead = this.#getScrollAhead(visibleThumbs);
    const thumbView = this.renderingQueue.getHighestPriority(visibleThumbs, this._thumbnails, scrollAhead);
    if (thumbView) {
      this.#ensurePdfPageLoaded(thumbView).then(() => {
        this.renderingQueue.renderView(thumbView);
      });
      return true;
    }
    return false;
  }
}

;// CONCATENATED MODULE: ./web/annotation_editor_layer_builder.js


class AnnotationEditorLayerBuilder {
  #annotationLayer = null;
  #drawLayer = null;
  #onAppend = null;
  #textLayer = null;
  #uiManager;
  constructor(options) {
    this.pdfPage = options.pdfPage;
    this.accessibilityManager = options.accessibilityManager;
    this.l10n = options.l10n;
    this.annotationEditorLayer = null;
    this.div = null;
    this._cancelled = false;
    this.#uiManager = options.uiManager;
    this.#annotationLayer = options.annotationLayer || null;
    this.#textLayer = options.textLayer || null;
    this.#drawLayer = options.drawLayer || null;
    this.#onAppend = options.onAppend || null;
  }
  async render(viewport, intent = "display") {
    if (intent !== "display") {
      return;
    }
    if (this._cancelled) {
      return;
    }
    const clonedViewport = viewport.clone({
      dontFlip: true
    });
    if (this.div) {
      this.annotationEditorLayer.update({
        viewport: clonedViewport
      });
      this.show();
      return;
    }
    const div = this.div = document.createElement("div");
    div.className = "annotationEditorLayer";
    div.hidden = true;
    div.dir = this.#uiManager.direction;
    this.#onAppend?.(div);
    this.annotationEditorLayer = new AnnotationEditorLayer({
      uiManager: this.#uiManager,
      div,
      accessibilityManager: this.accessibilityManager,
      pageIndex: this.pdfPage.pageNumber - 1,
      l10n: this.l10n,
      viewport: clonedViewport,
      annotationLayer: this.#annotationLayer,
      textLayer: this.#textLayer,
      drawLayer: this.#drawLayer
    });
    const parameters = {
      viewport: clonedViewport,
      div,
      annotations: null,
      intent
    };
    this.annotationEditorLayer.render(parameters);
    this.show();
  }
  cancel() {
    this._cancelled = true;
    if (!this.div) {
      return;
    }
    this.annotationEditorLayer.destroy();
  }
  hide() {
    if (!this.div) {
      return;
    }
    this.div.hidden = true;
  }
  show() {
    if (!this.div || this.annotationEditorLayer.isInvisible) {
      return;
    }
    this.div.hidden = false;
  }
}

;// CONCATENATED MODULE: ./web/annotation_layer_builder.js


class AnnotationLayerBuilder {
  #onAppend = null;
  #eventAbortController = null;
  constructor({
    pdfPage,
    linkService,
    downloadManager,
    annotationStorage = null,
    imageResourcesPath = "",
    renderForms = true,
    enableScripting = false,
    hasJSActionsPromise = null,
    fieldObjectsPromise = null,
    annotationCanvasMap = null,
    accessibilityManager = null,
    annotationEditorUIManager = null,
    onAppend = null
  }) {
    this.pdfPage = pdfPage;
    this.linkService = linkService;
    this.downloadManager = downloadManager;
    this.imageResourcesPath = imageResourcesPath;
    this.renderForms = renderForms;
    this.annotationStorage = annotationStorage;
    this.enableScripting = enableScripting;
    this._hasJSActionsPromise = hasJSActionsPromise || Promise.resolve(false);
    this._fieldObjectsPromise = fieldObjectsPromise || Promise.resolve(null);
    this._annotationCanvasMap = annotationCanvasMap;
    this._accessibilityManager = accessibilityManager;
    this._annotationEditorUIManager = annotationEditorUIManager;
    this.#onAppend = onAppend;
    this.annotationLayer = null;
    this.div = null;
    this._cancelled = false;
    this._eventBus = linkService.eventBus;
  }
  async render(viewport, intent = "display") {
    if (this.div) {
      if (this._cancelled || !this.annotationLayer) {
        return;
      }
      this.annotationLayer.update({
        viewport: viewport.clone({
          dontFlip: true
        })
      });
      return;
    }
    const [annotations, hasJSActions, fieldObjects] = await Promise.all([this.pdfPage.getAnnotations({
      intent
    }), this._hasJSActionsPromise, this._fieldObjectsPromise]);
    if (this._cancelled) {
      return;
    }
    const div = this.div = document.createElement("div");
    div.className = "annotationLayer";
    this.#onAppend?.(div);
    if (annotations.length === 0) {
      this.hide();
      return;
    }
    this.annotationLayer = new AnnotationLayer({
      div,
      accessibilityManager: this._accessibilityManager,
      annotationCanvasMap: this._annotationCanvasMap,
      annotationEditorUIManager: this._annotationEditorUIManager,
      page: this.pdfPage,
      viewport: viewport.clone({
        dontFlip: true
      })
    });
    await this.annotationLayer.render({
      annotations,
      imageResourcesPath: this.imageResourcesPath,
      renderForms: this.renderForms,
      linkService: this.linkService,
      downloadManager: this.downloadManager,
      annotationStorage: this.annotationStorage,
      enableScripting: this.enableScripting,
      hasJSActions,
      fieldObjects
    });
    if (this.linkService.isInPresentationMode) {
      this.#updatePresentationModeState(PresentationModeState.FULLSCREEN);
    }
    if (!this.#eventAbortController) {
      this.#eventAbortController = new AbortController();
      this._eventBus?._on("presentationmodechanged", evt => {
        this.#updatePresentationModeState(evt.state);
      }, {
        signal: this.#eventAbortController.signal
      });
    }
  }
  cancel() {
    this._cancelled = true;
    this.#eventAbortController?.abort();
    this.#eventAbortController = null;
  }
  hide() {
    if (!this.div) {
      return;
    }
    this.div.hidden = true;
  }
  hasEditableAnnotations() {
    return !!this.annotationLayer?.hasEditableAnnotations();
  }
  #updatePresentationModeState(state) {
    if (!this.div) {
      return;
    }
    let disableFormElements = false;
    switch (state) {
      case PresentationModeState.FULLSCREEN:
        disableFormElements = true;
        break;
      case PresentationModeState.NORMAL:
        break;
      default:
        return;
    }
    for (const section of this.div.childNodes) {
      if (section.hasAttribute("data-internal-link")) {
        continue;
      }
      section.inert = disableFormElements;
    }
  }
}

;// CONCATENATED MODULE: ./web/draw_layer_builder.js

class DrawLayerBuilder {
  #drawLayer = null;
  constructor(options) {
    this.pageIndex = options.pageIndex;
  }
  async render(intent = "display") {
    if (intent !== "display" || this.#drawLayer || this._cancelled) {
      return;
    }
    this.#drawLayer = new DrawLayer({
      pageIndex: this.pageIndex
    });
  }
  cancel() {
    this._cancelled = true;
    if (!this.#drawLayer) {
      return;
    }
    this.#drawLayer.destroy();
    this.#drawLayer = null;
  }
  setParent(parent) {
    this.#drawLayer?.setParent(parent);
  }
  getDrawLayer() {
    return this.#drawLayer;
  }
}

;// CONCATENATED MODULE: ./web/struct_tree_layer_builder.js

const PDF_ROLE_TO_HTML_ROLE = {
  Document: null,
  DocumentFragment: null,
  Part: "group",
  Sect: "group",
  Div: "group",
  Aside: "note",
  NonStruct: "none",
  P: null,
  H: "heading",
  Title: null,
  FENote: "note",
  Sub: "group",
  Lbl: null,
  Span: null,
  Em: null,
  Strong: null,
  Link: "link",
  Annot: "note",
  Form: "form",
  Ruby: null,
  RB: null,
  RT: null,
  RP: null,
  Warichu: null,
  WT: null,
  WP: null,
  L: "list",
  LI: "listitem",
  LBody: null,
  Table: "table",
  TR: "row",
  TH: "columnheader",
  TD: "cell",
  THead: "columnheader",
  TBody: null,
  TFoot: null,
  Caption: null,
  Figure: "figure",
  Formula: null,
  Artifact: null
};
const HEADING_PATTERN = /^H(\d+)$/;
class StructTreeLayerBuilder {
  #treeDom = undefined;
  get renderingDone() {
    return this.#treeDom !== undefined;
  }
  render(structTree) {
    if (this.#treeDom !== undefined) {
      return this.#treeDom;
    }
    const treeDom = this.#walk(structTree);
    treeDom?.classList.add("structTree");
    return this.#treeDom = treeDom;
  }
  hide() {
    if (this.#treeDom && !this.#treeDom.hidden) {
      this.#treeDom.hidden = true;
    }
  }
  show() {
    if (this.#treeDom?.hidden) {
      this.#treeDom.hidden = false;
    }
  }
  #setAttributes(structElement, htmlElement) {
    const {
      alt,
      id,
      lang
    } = structElement;
    if (alt !== undefined) {
      htmlElement.setAttribute("aria-label", removeNullCharacters(alt));
    }
    if (id !== undefined) {
      htmlElement.setAttribute("aria-owns", id);
    }
    if (lang !== undefined) {
      htmlElement.setAttribute("lang", removeNullCharacters(lang, true));
    }
  }
  #walk(node) {
    if (!node) {
      return null;
    }
    const element = document.createElement("span");
    if ("role" in node) {
      const {
        role
      } = node;
      const match = role.match(HEADING_PATTERN);
      if (match) {
        element.setAttribute("role", "heading");
        element.setAttribute("aria-level", match[1]);
      } else if (PDF_ROLE_TO_HTML_ROLE[role]) {
        element.setAttribute("role", PDF_ROLE_TO_HTML_ROLE[role]);
      }
    }
    this.#setAttributes(node, element);
    if (node.children) {
      if (node.children.length === 1 && "id" in node.children[0]) {
        this.#setAttributes(node.children[0], element);
      } else {
        for (const kid of node.children) {
          element.append(this.#walk(kid));
        }
      }
    }
    return element;
  }
}

;// CONCATENATED MODULE: ./web/text_accessibility.js

class TextAccessibilityManager {
  #enabled = false;
  #textChildren = null;
  #textNodes = new Map();
  #waitingElements = new Map();
  setTextMapping(textDivs) {
    this.#textChildren = textDivs;
  }
  static #compareElementPositions(e1, e2) {
    const rect1 = e1.getBoundingClientRect();
    const rect2 = e2.getBoundingClientRect();
    if (rect1.width === 0 && rect1.height === 0) {
      return +1;
    }
    if (rect2.width === 0 && rect2.height === 0) {
      return -1;
    }
    const top1 = rect1.y;
    const bot1 = rect1.y + rect1.height;
    const mid1 = rect1.y + rect1.height / 2;
    const top2 = rect2.y;
    const bot2 = rect2.y + rect2.height;
    const mid2 = rect2.y + rect2.height / 2;
    if (mid1 <= top2 && mid2 >= bot1) {
      return -1;
    }
    if (mid2 <= top1 && mid1 >= bot2) {
      return +1;
    }
    const centerX1 = rect1.x + rect1.width / 2;
    const centerX2 = rect2.x + rect2.width / 2;
    return centerX1 - centerX2;
  }
  enable() {
    if (this.#enabled) {
      throw new Error("TextAccessibilityManager is already enabled.");
    }
    if (!this.#textChildren) {
      throw new Error("Text divs and strings have not been set.");
    }
    this.#enabled = true;
    this.#textChildren = this.#textChildren.slice();
    this.#textChildren.sort(TextAccessibilityManager.#compareElementPositions);
    if (this.#textNodes.size > 0) {
      const textChildren = this.#textChildren;
      for (const [id, nodeIndex] of this.#textNodes) {
        const element = document.getElementById(id);
        if (!element) {
          this.#textNodes.delete(id);
          continue;
        }
        this.#addIdToAriaOwns(id, textChildren[nodeIndex]);
      }
    }
    for (const [element, isRemovable] of this.#waitingElements) {
      this.addPointerInTextLayer(element, isRemovable);
    }
    this.#waitingElements.clear();
  }
  disable() {
    if (!this.#enabled) {
      return;
    }
    this.#waitingElements.clear();
    this.#textChildren = null;
    this.#enabled = false;
  }
  removePointerInTextLayer(element) {
    if (!this.#enabled) {
      this.#waitingElements.delete(element);
      return;
    }
    const children = this.#textChildren;
    if (!children || children.length === 0) {
      return;
    }
    const {
      id
    } = element;
    const nodeIndex = this.#textNodes.get(id);
    if (nodeIndex === undefined) {
      return;
    }
    const node = children[nodeIndex];
    this.#textNodes.delete(id);
    let owns = node.getAttribute("aria-owns");
    if (owns?.includes(id)) {
      owns = owns.split(" ").filter(x => x !== id).join(" ");
      if (owns) {
        node.setAttribute("aria-owns", owns);
      } else {
        node.removeAttribute("aria-owns");
        node.setAttribute("role", "presentation");
      }
    }
  }
  #addIdToAriaOwns(id, node) {
    const owns = node.getAttribute("aria-owns");
    if (!owns?.includes(id)) {
      node.setAttribute("aria-owns", owns ? `${owns} ${id}` : id);
    }
    node.removeAttribute("role");
  }
  addPointerInTextLayer(element, isRemovable) {
    const {
      id
    } = element;
    if (!id) {
      return null;
    }
    if (!this.#enabled) {
      this.#waitingElements.set(element, isRemovable);
      return null;
    }
    if (isRemovable) {
      this.removePointerInTextLayer(element);
    }
    const children = this.#textChildren;
    if (!children || children.length === 0) {
      return null;
    }
    const index = binarySearchFirstItem(children, node => TextAccessibilityManager.#compareElementPositions(element, node) < 0);
    const nodeIndex = Math.max(0, index - 1);
    const child = children[nodeIndex];
    this.#addIdToAriaOwns(id, child);
    this.#textNodes.set(id, nodeIndex);
    const parent = child.parentNode;
    return parent?.classList.contains("markedContent") ? parent.id : null;
  }
  moveElementInDOM(container, element, contentElement, isRemovable) {
    const id = this.addPointerInTextLayer(contentElement, isRemovable);
    if (!container.hasChildNodes()) {
      container.append(element);
      return id;
    }
    const children = Array.from(container.childNodes).filter(node => node !== element);
    if (children.length === 0) {
      return id;
    }
    const elementToCompare = contentElement || element;
    const index = binarySearchFirstItem(children, node => TextAccessibilityManager.#compareElementPositions(elementToCompare, node) < 0);
    if (index === 0) {
      children[0].before(element);
    } else {
      children[index - 1].after(element);
    }
    return id;
  }
}

;// CONCATENATED MODULE: ./web/text_highlighter.js
class TextHighlighter {
  #eventAbortController = null;
  constructor({
    findController,
    eventBus,
    pageIndex
  }) {
    this.findController = findController;
    this.matches = [];
    this.eventBus = eventBus;
    this.pageIdx = pageIndex;
    this.textDivs = null;
    this.textContentItemsStr = null;
    this.enabled = false;
  }
  setTextMapping(divs, texts) {
    this.textDivs = divs;
    this.textContentItemsStr = texts;
  }
  enable() {
    if (!this.textDivs || !this.textContentItemsStr) {
      throw new Error("Text divs and strings have not been set.");
    }
    if (this.enabled) {
      throw new Error("TextHighlighter is already enabled.");
    }
    this.enabled = true;
    if (!this.#eventAbortController) {
      this.#eventAbortController = new AbortController();
      this.eventBus._on("updatetextlayermatches", evt => {
        if (evt.pageIndex === this.pageIdx || evt.pageIndex === -1) {
          this._updateMatches();
        }
      }, {
        signal: this.#eventAbortController.signal
      });
    }
    this._updateMatches();
  }
  disable() {
    if (!this.enabled) {
      return;
    }
    this.enabled = false;
    this.#eventAbortController?.abort();
    this.#eventAbortController = null;
    this._updateMatches(true);
  }
  _convertMatches(matches, matchesLength) {
    if (!matches) {
      return [];
    }
    const {
      textContentItemsStr
    } = this;
    let i = 0,
      iIndex = 0;
    const end = textContentItemsStr.length - 1;
    const result = [];
    for (let m = 0, mm = matches.length; m < mm; m++) {
      let matchIdx = matches[m];
      while (i !== end && matchIdx >= iIndex + textContentItemsStr[i].length) {
        iIndex += textContentItemsStr[i].length;
        i++;
      }
      if (i === textContentItemsStr.length) {
        console.error("Could not find a matching mapping");
      }
      const match = {
        begin: {
          divIdx: i,
          offset: matchIdx - iIndex
        }
      };
      matchIdx += matchesLength[m];
      while (i !== end && matchIdx > iIndex + textContentItemsStr[i].length) {
        iIndex += textContentItemsStr[i].length;
        i++;
      }
      match.end = {
        divIdx: i,
        offset: matchIdx - iIndex
      };
      result.push(match);
    }
    return result;
  }
  _renderMatches(matches) {
    if (matches.length === 0) {
      return;
    }
    const {
      findController,
      pageIdx
    } = this;
    const {
      textContentItemsStr,
      textDivs
    } = this;
    const isSelectedPage = pageIdx === findController.selected.pageIdx;
    const selectedMatchIdx = findController.selected.matchIdx;
    const highlightAll = findController.state.highlightAll;
    let prevEnd = null;
    const infinity = {
      divIdx: -1,
      offset: undefined
    };
    function beginText(begin, className) {
      const divIdx = begin.divIdx;
      textDivs[divIdx].textContent = "";
      return appendTextToDiv(divIdx, 0, begin.offset, className);
    }
    function appendTextToDiv(divIdx, fromOffset, toOffset, className) {
      let div = textDivs[divIdx];
      if (div.nodeType === Node.TEXT_NODE) {
        const span = document.createElement("span");
        div.before(span);
        span.append(div);
        textDivs[divIdx] = span;
        div = span;
      }
      const content = textContentItemsStr[divIdx].substring(fromOffset, toOffset);
      const node = document.createTextNode(content);
      if (className) {
        const span = document.createElement("span");
        span.className = `${className} appended`;
        span.append(node);
        div.append(span);
        return className.includes("selected") ? span.offsetLeft : 0;
      }
      div.append(node);
      return 0;
    }
    let i0 = selectedMatchIdx,
      i1 = i0 + 1;
    if (highlightAll) {
      i0 = 0;
      i1 = matches.length;
    } else if (!isSelectedPage) {
      return;
    }
    let lastDivIdx = -1;
    let lastOffset = -1;
    for (let i = i0; i < i1; i++) {
      const match = matches[i];
      const begin = match.begin;
      if (begin.divIdx === lastDivIdx && begin.offset === lastOffset) {
        continue;
      }
      lastDivIdx = begin.divIdx;
      lastOffset = begin.offset;
      const end = match.end;
      const isSelected = isSelectedPage && i === selectedMatchIdx;
      const highlightSuffix = isSelected ? " selected" : "";
      let selectedLeft = 0;
      if (!prevEnd || begin.divIdx !== prevEnd.divIdx) {
        if (prevEnd !== null) {
          appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
        }
        beginText(begin);
      } else {
        appendTextToDiv(prevEnd.divIdx, prevEnd.offset, begin.offset);
      }
      if (begin.divIdx === end.divIdx) {
        selectedLeft = appendTextToDiv(begin.divIdx, begin.offset, end.offset, "highlight" + highlightSuffix);
      } else {
        selectedLeft = appendTextToDiv(begin.divIdx, begin.offset, infinity.offset, "highlight begin" + highlightSuffix);
        for (let n0 = begin.divIdx + 1, n1 = end.divIdx; n0 < n1; n0++) {
          textDivs[n0].className = "highlight middle" + highlightSuffix;
        }
        beginText(end, "highlight end" + highlightSuffix);
      }
      prevEnd = end;
      if (isSelected) {
        findController.scrollMatchIntoView({
          element: textDivs[begin.divIdx],
          selectedLeft,
          pageIndex: pageIdx,
          matchIndex: selectedMatchIdx
        });
      }
    }
    if (prevEnd) {
      appendTextToDiv(prevEnd.divIdx, prevEnd.offset, infinity.offset);
    }
  }
  _updateMatches(reset = false) {
    if (!this.enabled && !reset) {
      return;
    }
    const {
      findController,
      matches,
      pageIdx
    } = this;
    const {
      textContentItemsStr,
      textDivs
    } = this;
    let clearedUntilDivIdx = -1;
    for (const match of matches) {
      const begin = Math.max(clearedUntilDivIdx, match.begin.divIdx);
      for (let n = begin, end = match.end.divIdx; n <= end; n++) {
        const div = textDivs[n];
        div.textContent = textContentItemsStr[n];
        div.className = "";
      }
      clearedUntilDivIdx = match.end.divIdx + 1;
    }
    if (!findController?.highlightMatches || reset) {
      return;
    }
    const pageMatches = findController.pageMatches[pageIdx] || null;
    const pageMatchesLength = findController.pageMatchesLength[pageIdx] || null;
    this.matches = this._convertMatches(pageMatches, pageMatchesLength);
    this._renderMatches(this.matches);
  }
}

;// CONCATENATED MODULE: ./web/text_layer_builder.js


class TextLayerBuilder {
  #enablePermissions = false;
  #onAppend = null;
  #renderingDone = false;
  #textLayer = null;
  static #textLayers = new Map();
  static #selectionChangeAbortController = null;
  constructor({
    pdfPage,
    highlighter = null,
    accessibilityManager = null,
    enablePermissions = false,
    onAppend = null
  }) {
    this.pdfPage = pdfPage;
    this.highlighter = highlighter;
    this.accessibilityManager = accessibilityManager;
    this.#enablePermissions = enablePermissions === true;
    this.#onAppend = onAppend;
    this.div = document.createElement("div");
    this.div.tabIndex = 0;
    this.div.className = "textLayer";
  }
  async render(viewport, textContentParams = null) {
    if (this.#renderingDone && this.#textLayer) {
      this.#textLayer.update({
        viewport,
        onBefore: this.hide.bind(this)
      });
      this.show();
      return;
    }
    this.cancel();
    this.#textLayer = new TextLayer({
      textContentSource: this.pdfPage.streamTextContent(textContentParams || {
        includeMarkedContent: true,
        disableNormalization: true
      }),
      container: this.div,
      viewport
    });
    const {
      textDivs,
      textContentItemsStr
    } = this.#textLayer;
    this.highlighter?.setTextMapping(textDivs, textContentItemsStr);
    this.accessibilityManager?.setTextMapping(textDivs);
    await this.#textLayer.render();
    this.#renderingDone = true;
    const endOfContent = document.createElement("div");
    endOfContent.className = "endOfContent";
    this.div.append(endOfContent);
    this.#bindMouse(endOfContent);
    this.#onAppend?.(this.div);
    this.highlighter?.enable();
    this.accessibilityManager?.enable();
  }
  hide() {
    if (!this.div.hidden && this.#renderingDone) {
      this.highlighter?.disable();
      this.div.hidden = true;
    }
  }
  show() {
    if (this.div.hidden && this.#renderingDone) {
      this.div.hidden = false;
      this.highlighter?.enable();
    }
  }
  cancel() {
    this.#textLayer?.cancel();
    this.#textLayer = null;
    this.highlighter?.disable();
    this.accessibilityManager?.disable();
    TextLayerBuilder.#removeGlobalSelectionListener(this.div);
  }
  #bindMouse(end) {
    const {
      div
    } = this;
    div.addEventListener("mousedown", () => {
      div.classList.add("selecting");
    });
    div.addEventListener("copy", event => {
      if (!this.#enablePermissions) {
        const selection = document.getSelection();
        event.clipboardData.setData("text/plain", removeNullCharacters(normalizeUnicode(selection.toString())));
      }
      event.preventDefault();
      event.stopPropagation();
    });
    TextLayerBuilder.#textLayers.set(div, end);
    TextLayerBuilder.#enableGlobalSelectionListener();
  }
  static #removeGlobalSelectionListener(textLayerDiv) {
    this.#textLayers.delete(textLayerDiv);
    if (this.#textLayers.size === 0) {
      this.#selectionChangeAbortController?.abort();
      this.#selectionChangeAbortController = null;
    }
  }
  static #enableGlobalSelectionListener() {
    if (this.#selectionChangeAbortController) {
      return;
    }
    this.#selectionChangeAbortController = new AbortController();
    const {
      signal
    } = this.#selectionChangeAbortController;
    const reset = (end, textLayer) => {
      textLayer.classList.remove("selecting");
    };
    let isPointerDown = false;
    document.addEventListener("pointerdown", () => {
      isPointerDown = true;
    }, {
      signal
    });
    document.addEventListener("pointerup", () => {
      isPointerDown = false;
      this.#textLayers.forEach(reset);
    }, {
      signal
    });
    window.addEventListener("blur", () => {
      isPointerDown = false;
      this.#textLayers.forEach(reset);
    }, {
      signal
    });
    document.addEventListener("keyup", () => {
      if (!isPointerDown) {
        this.#textLayers.forEach(reset);
      }
    }, {
      signal
    });
    document.addEventListener("selectionchange", () => {
      const selection = document.getSelection();
      if (selection.rangeCount === 0) {
        this.#textLayers.forEach(reset);
        return;
      }
      const activeTextLayers = new Set();
      for (let i = 0; i < selection.rangeCount; i++) {
        const range = selection.getRangeAt(i);
        for (const textLayerDiv of this.#textLayers.keys()) {
          if (!activeTextLayers.has(textLayerDiv) && range.intersectsNode(textLayerDiv)) {
            activeTextLayers.add(textLayerDiv);
          }
        }
      }
      for (const [textLayerDiv, endDiv] of this.#textLayers) {
        if (activeTextLayers.has(textLayerDiv)) {
          textLayerDiv.classList.add("selecting");
        } else {
          reset(endDiv, textLayerDiv);
        }
      }
    }, {
      signal
    });
  }
}

;// CONCATENATED MODULE: ./web/pdf_page_view.js













const DEFAULT_LAYER_PROPERTIES = null;
const LAYERS_ORDER = new Map([["canvasWrapper", 0], ["textLayer", 1], ["annotationLayer", 2], ["annotationEditorLayer", 3], ["xfaLayer", 3]]);
class PDFPageView {
  #annotationMode = AnnotationMode.ENABLE_FORMS;
  #enableHWA = false;
  #hasRestrictedScaling = false;
  #isEditing = false;
  #layerProperties = null;
  #loadingId = null;
  #previousRotation = null;
  #renderError = null;
  #renderingState = RenderingStates.INITIAL;
  #textLayerMode = TextLayerMode.ENABLE;
  #useThumbnailCanvas = {
    directDrawing: true,
    initialOptionalContent: true,
    regularAnnotations: true
  };
  #viewportMap = new WeakMap();
  #layers = [null, null, null, null];
  constructor(options) {
    const container = options.container;
    const defaultViewport = options.defaultViewport;
    this.id = options.id;
    this.renderingId = "page" + this.id;
    this.#layerProperties = options.layerProperties || DEFAULT_LAYER_PROPERTIES;
    this.pdfPage = null;
    this.pageLabel = null;
    this.rotation = 0;
    this.scale = options.scale || DEFAULT_SCALE;
    this.viewport = defaultViewport;
    this.pdfPageRotate = defaultViewport.rotation;
    this._optionalContentConfigPromise = options.optionalContentConfigPromise || null;
    this.#textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE;
    this.#annotationMode = options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
    this.imageResourcesPath = options.imageResourcesPath || "";
    this.maxCanvasPixels = options.maxCanvasPixels ?? AppOptions.get("maxCanvasPixels");
    this.pageColors = options.pageColors || null;
    this.#enableHWA = options.enableHWA || false;
    this.eventBus = options.eventBus;
    this.renderingQueue = options.renderingQueue;
    this.l10n = options.l10n;
    this.renderTask = null;
    this.resume = null;
    this._annotationCanvasMap = null;
    this.annotationLayer = null;
    this.annotationEditorLayer = null;
    this.textLayer = null;
    this.zoomLayer = null;
    this.xfaLayer = null;
    this.structTreeLayer = null;
    this.drawLayer = null;
    const div = document.createElement("div");
    div.className = "page";
    div.setAttribute("data-page-number", this.id);
    div.setAttribute("role", "region");
    div.setAttribute("data-l10n-id", "pdfjs-page-landmark");
    div.setAttribute("data-l10n-args", JSON.stringify({
      page: this.id
    }));
    this.div = div;
    this.#setDimensions();
    container?.append(div);
  }
  #addLayer(div, name) {
    const pos = LAYERS_ORDER.get(name);
    const oldDiv = this.#layers[pos];
    this.#layers[pos] = div;
    if (oldDiv) {
      oldDiv.replaceWith(div);
      return;
    }
    for (let i = pos - 1; i >= 0; i--) {
      const layer = this.#layers[i];
      if (layer) {
        layer.after(div);
        return;
      }
    }
    this.div.prepend(div);
  }
  get renderingState() {
    return this.#renderingState;
  }
  set renderingState(state) {
    if (state === this.#renderingState) {
      return;
    }
    this.#renderingState = state;
    if (this.#loadingId) {
      clearTimeout(this.#loadingId);
      this.#loadingId = null;
    }
    switch (state) {
      case RenderingStates.PAUSED:
        this.div.classList.remove("loading");
        break;
      case RenderingStates.RUNNING:
        this.div.classList.add("loadingIcon");
        this.#loadingId = setTimeout(() => {
          this.div.classList.add("loading");
          this.#loadingId = null;
        }, 0);
        break;
      case RenderingStates.INITIAL:
      case RenderingStates.FINISHED:
        this.div.classList.remove("loadingIcon", "loading");
        break;
    }
  }
  #setDimensions() {
    const {
      viewport
    } = this;
    if (this.pdfPage) {
      if (this.#previousRotation === viewport.rotation) {
        return;
      }
      this.#previousRotation = viewport.rotation;
    }
    setLayerDimensions(this.div, viewport, true, false);
  }
  setPdfPage(pdfPage) {
    this.pdfPage = pdfPage;
    this.pdfPageRotate = pdfPage.rotate;
    const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
    this.viewport = pdfPage.getViewport({
      scale: this.scale * PixelsPerInch.PDF_TO_CSS_UNITS,
      rotation: totalRotation
    });
    this.#setDimensions();
    this.reset();
  }
  destroy() {
    this.reset();
    this.pdfPage?.cleanup();
  }
  hasEditableAnnotations() {
    return !!this.annotationLayer?.hasEditableAnnotations();
  }
  get _textHighlighter() {
    return shadow(this, "_textHighlighter", new TextHighlighter({
      pageIndex: this.id - 1,
      eventBus: this.eventBus,
      findController: this.#layerProperties.findController
    }));
  }
  #dispatchLayerRendered(name, error) {
    this.eventBus.dispatch(name, {
      source: this,
      pageNumber: this.id,
      error
    });
  }
  async #renderAnnotationLayer() {
    let error = null;
    try {
      await this.annotationLayer.render(this.viewport, "display");
    } catch (ex) {
      console.error(`#renderAnnotationLayer: "${ex}".`);
      error = ex;
    } finally {
      this.#dispatchLayerRendered("annotationlayerrendered", error);
    }
  }
  async #renderAnnotationEditorLayer() {
    let error = null;
    try {
      await this.annotationEditorLayer.render(this.viewport, "display");
    } catch (ex) {
      console.error(`#renderAnnotationEditorLayer: "${ex}".`);
      error = ex;
    } finally {
      this.#dispatchLayerRendered("annotationeditorlayerrendered", error);
    }
  }
  async #renderDrawLayer() {
    try {
      await this.drawLayer.render("display");
    } catch (ex) {
      console.error(`#renderDrawLayer: "${ex}".`);
    }
  }
  async #renderXfaLayer() {
    let error = null;
    try {
      const result = await this.xfaLayer.render(this.viewport, "display");
      if (result?.textDivs && this._textHighlighter) {
        this.#buildXfaTextContentItems(result.textDivs);
      }
    } catch (ex) {
      console.error(`#renderXfaLayer: "${ex}".`);
      error = ex;
    } finally {
      if (this.xfaLayer?.div) {
        this.l10n.pause();
        this.#addLayer(this.xfaLayer.div, "xfaLayer");
        this.l10n.resume();
      }
      this.#dispatchLayerRendered("xfalayerrendered", error);
    }
  }
  async #renderTextLayer() {
    if (!this.textLayer) {
      return;
    }
    let error = null;
    try {
      await this.textLayer.render(this.viewport);
    } catch (ex) {
      if (ex instanceof AbortException) {
        return;
      }
      console.error(`#renderTextLayer: "${ex}".`);
      error = ex;
    }
    this.#dispatchLayerRendered("textlayerrendered", error);
    this.#renderStructTreeLayer();
  }
  async #renderStructTreeLayer() {
    if (!this.textLayer) {
      return;
    }
    this.structTreeLayer ||= new StructTreeLayerBuilder();
    const tree = await (!this.structTreeLayer.renderingDone ? this.pdfPage.getStructTree() : null);
    const treeDom = this.structTreeLayer?.render(tree);
    if (treeDom) {
      this.l10n.pause();
      this.canvas?.append(treeDom);
      this.l10n.resume();
    }
    this.structTreeLayer?.show();
  }
  async #buildXfaTextContentItems(textDivs) {
    const text = await this.pdfPage.getTextContent();
    const items = [];
    for (const item of text.items) {
      items.push(item.str);
    }
    this._textHighlighter.setTextMapping(textDivs, items);
    this._textHighlighter.enable();
  }
  _resetZoomLayer(removeFromDOM = false) {
    if (!this.zoomLayer) {
      return;
    }
    const zoomLayerCanvas = this.zoomLayer.firstChild;
    this.#viewportMap.delete(zoomLayerCanvas);
    zoomLayerCanvas.width = 0;
    zoomLayerCanvas.height = 0;
    if (removeFromDOM) {
      this.zoomLayer.remove();
    }
    this.zoomLayer = null;
  }
  reset({
    keepZoomLayer = false,
    keepAnnotationLayer = false,
    keepAnnotationEditorLayer = false,
    keepXfaLayer = false,
    keepTextLayer = false
  } = {}) {
    this.cancelRendering({
      keepAnnotationLayer,
      keepAnnotationEditorLayer,
      keepXfaLayer,
      keepTextLayer
    });
    this.renderingState = RenderingStates.INITIAL;
    const div = this.div;
    const childNodes = div.childNodes,
      zoomLayerNode = keepZoomLayer && this.zoomLayer || null,
      annotationLayerNode = keepAnnotationLayer && this.annotationLayer?.div || null,
      annotationEditorLayerNode = keepAnnotationEditorLayer && this.annotationEditorLayer?.div || null,
      xfaLayerNode = keepXfaLayer && this.xfaLayer?.div || null,
      textLayerNode = keepTextLayer && this.textLayer?.div || null;
    for (let i = childNodes.length - 1; i >= 0; i--) {
      const node = childNodes[i];
      switch (node) {
        case zoomLayerNode:
        case annotationLayerNode:
        case annotationEditorLayerNode:
        case xfaLayerNode:
        case textLayerNode:
          continue;
      }
      node.remove();
      const layerIndex = this.#layers.indexOf(node);
      if (layerIndex >= 0) {
        this.#layers[layerIndex] = null;
      }
    }
    div.removeAttribute("data-loaded");
    if (annotationLayerNode) {
      this.annotationLayer.hide();
    }
    if (annotationEditorLayerNode) {
      this.annotationEditorLayer.hide();
    }
    if (xfaLayerNode) {
      this.xfaLayer.hide();
    }
    if (textLayerNode) {
      this.textLayer.hide();
    }
    this.structTreeLayer?.hide();
    if (!zoomLayerNode) {
      if (this.canvas) {
        this.#viewportMap.delete(this.canvas);
        this.canvas.width = 0;
        this.canvas.height = 0;
        delete this.canvas;
      }
      this._resetZoomLayer();
    }
  }
  toggleEditingMode(isEditing) {
    if (!this.hasEditableAnnotations()) {
      return;
    }
    this.#isEditing = isEditing;
    this.reset({
      keepZoomLayer: true,
      keepAnnotationLayer: true,
      keepAnnotationEditorLayer: true,
      keepXfaLayer: true,
      keepTextLayer: true
    });
  }
  update({
    scale = 0,
    rotation = null,
    optionalContentConfigPromise = null,
    drawingDelay = -1
  }) {
    this.scale = scale || this.scale;
    if (typeof rotation === "number") {
      this.rotation = rotation;
    }
    if (optionalContentConfigPromise instanceof Promise) {
      this._optionalContentConfigPromise = optionalContentConfigPromise;
      optionalContentConfigPromise.then(optionalContentConfig => {
        if (optionalContentConfigPromise !== this._optionalContentConfigPromise) {
          return;
        }
        this.#useThumbnailCanvas.initialOptionalContent = optionalContentConfig.hasInitialVisibility;
      });
    }
    this.#useThumbnailCanvas.directDrawing = true;
    const totalRotation = (this.rotation + this.pdfPageRotate) % 360;
    this.viewport = this.viewport.clone({
      scale: this.scale * PixelsPerInch.PDF_TO_CSS_UNITS,
      rotation: totalRotation
    });
    this.#setDimensions();
    if (this.canvas) {
      let onlyCssZoom = false;
      if (this.#hasRestrictedScaling) {
        if (this.maxCanvasPixels > 0) {
          const {
            width,
            height
          } = this.viewport;
          const {
            sx,
            sy
          } = this.outputScale;
          onlyCssZoom = (Math.floor(width) * sx | 0) * (Math.floor(height) * sy | 0) > this.maxCanvasPixels;
        }
      }
      const postponeDrawing = drawingDelay >= 0 && drawingDelay < 1000;
      if (postponeDrawing || onlyCssZoom) {
        if (postponeDrawing && !onlyCssZoom && this.renderingState !== RenderingStates.FINISHED) {
          this.cancelRendering({
            keepZoomLayer: true,
            keepAnnotationLayer: true,
            keepAnnotationEditorLayer: true,
            keepXfaLayer: true,
            keepTextLayer: true,
            cancelExtraDelay: drawingDelay
          });
          this.renderingState = RenderingStates.FINISHED;
          this.#useThumbnailCanvas.directDrawing = false;
        }
        this.cssTransform({
          target: this.canvas,
          redrawAnnotationLayer: true,
          redrawAnnotationEditorLayer: true,
          redrawXfaLayer: true,
          redrawTextLayer: !postponeDrawing,
          hideTextLayer: postponeDrawing
        });
        if (postponeDrawing) {
          return;
        }
        this.eventBus.dispatch("pagerendered", {
          source: this,
          pageNumber: this.id,
          cssTransform: true,
          timestamp: performance.now(),
          error: this.#renderError
        });
        return;
      }
      if (!this.zoomLayer && !this.canvas.hidden) {
        this.zoomLayer = this.canvas.parentNode;
        this.zoomLayer.style.position = "absolute";
      }
    }
    if (this.zoomLayer) {
      this.cssTransform({
        target: this.zoomLayer.firstChild
      });
    }
    this.reset({
      keepZoomLayer: true,
      keepAnnotationLayer: true,
      keepAnnotationEditorLayer: true,
      keepXfaLayer: true,
      keepTextLayer: true
    });
  }
  cancelRendering({
    keepAnnotationLayer = false,
    keepAnnotationEditorLayer = false,
    keepXfaLayer = false,
    keepTextLayer = false,
    cancelExtraDelay = 0
  } = {}) {
    if (this.renderTask) {
      this.renderTask.cancel(cancelExtraDelay);
      this.renderTask = null;
    }
    this.resume = null;
    if (this.textLayer && (!keepTextLayer || !this.textLayer.div)) {
      this.textLayer.cancel();
      this.textLayer = null;
    }
    if (this.structTreeLayer && !this.textLayer) {
      this.structTreeLayer = null;
    }
    if (this.annotationLayer && (!keepAnnotationLayer || !this.annotationLayer.div)) {
      this.annotationLayer.cancel();
      this.annotationLayer = null;
      this._annotationCanvasMap = null;
    }
    if (this.annotationEditorLayer && (!keepAnnotationEditorLayer || !this.annotationEditorLayer.div)) {
      if (this.drawLayer) {
        this.drawLayer.cancel();
        this.drawLayer = null;
      }
      this.annotationEditorLayer.cancel();
      this.annotationEditorLayer = null;
    }
    if (this.xfaLayer && (!keepXfaLayer || !this.xfaLayer.div)) {
      this.xfaLayer.cancel();
      this.xfaLayer = null;
      this._textHighlighter?.disable();
    }
  }
  cssTransform({
    target,
    redrawAnnotationLayer = false,
    redrawAnnotationEditorLayer = false,
    redrawXfaLayer = false,
    redrawTextLayer = false,
    hideTextLayer = false
  }) {
    if (!target.hasAttribute("zooming")) {
      target.setAttribute("zooming", true);
      const {
        style
      } = target;
      style.width = style.height = "";
    }
    const originalViewport = this.#viewportMap.get(target);
    if (this.viewport !== originalViewport) {
      const relativeRotation = this.viewport.rotation - originalViewport.rotation;
      const absRotation = Math.abs(relativeRotation);
      let scaleX = 1,
        scaleY = 1;
      if (absRotation === 90 || absRotation === 270) {
        const {
          width,
          height
        } = this.viewport;
        scaleX = height / width;
        scaleY = width / height;
      }
      target.style.transform = `rotate(${relativeRotation}deg) scale(${scaleX}, ${scaleY})`;
    }
    if (redrawAnnotationLayer && this.annotationLayer) {
      this.#renderAnnotationLayer();
    }
    if (redrawAnnotationEditorLayer && this.annotationEditorLayer) {
      if (this.drawLayer) {
        this.#renderDrawLayer();
      }
      this.#renderAnnotationEditorLayer();
    }
    if (redrawXfaLayer && this.xfaLayer) {
      this.#renderXfaLayer();
    }
    if (this.textLayer) {
      if (hideTextLayer) {
        this.textLayer.hide();
        this.structTreeLayer?.hide();
      } else if (redrawTextLayer) {
        this.#renderTextLayer();
      }
    }
  }
  get width() {
    return this.viewport.width;
  }
  get height() {
    return this.viewport.height;
  }
  getPagePoint(x, y) {
    return this.viewport.convertToPdfPoint(x, y);
  }
  async #finishRenderTask(renderTask, error = null) {
    if (renderTask === this.renderTask) {
      this.renderTask = null;
    }
    if (error instanceof RenderingCancelledException) {
      this.#renderError = null;
      return;
    }
    this.#renderError = error;
    this.renderingState = RenderingStates.FINISHED;
    this._resetZoomLayer(true);
    this.#useThumbnailCanvas.regularAnnotations = !renderTask.separateAnnots;
    this.eventBus.dispatch("pagerendered", {
      source: this,
      pageNumber: this.id,
      cssTransform: false,
      timestamp: performance.now(),
      error: this.#renderError
    });
    if (error) {
      throw error;
    }
  }
  async draw() {
    if (this.renderingState !== RenderingStates.INITIAL) {
      console.error("Must be in new state before drawing");
      this.reset();
    }
    const {
      div,
      l10n,
      pageColors,
      pdfPage,
      viewport
    } = this;
    if (!pdfPage) {
      this.renderingState = RenderingStates.FINISHED;
      throw new Error("pdfPage is not loaded");
    }
    this.renderingState = RenderingStates.RUNNING;
    const canvasWrapper = document.createElement("div");
    canvasWrapper.classList.add("canvasWrapper");
    this.#addLayer(canvasWrapper, "canvasWrapper");
    if (!this.textLayer && this.#textLayerMode !== TextLayerMode.DISABLE && !pdfPage.isPureXfa) {
      this._accessibilityManager ||= new TextAccessibilityManager();
      this.textLayer = new TextLayerBuilder({
        pdfPage,
        highlighter: this._textHighlighter,
        accessibilityManager: this._accessibilityManager,
        enablePermissions: this.#textLayerMode === TextLayerMode.ENABLE_PERMISSIONS,
        onAppend: textLayerDiv => {
          this.l10n.pause();
          this.#addLayer(textLayerDiv, "textLayer");
          this.l10n.resume();
        }
      });
    }
    if (!this.annotationLayer && this.#annotationMode !== AnnotationMode.DISABLE) {
      const {
        annotationStorage,
        annotationEditorUIManager,
        downloadManager,
        enableScripting,
        fieldObjectsPromise,
        hasJSActionsPromise,
        linkService
      } = this.#layerProperties;
      this._annotationCanvasMap ||= new Map();
      this.annotationLayer = new AnnotationLayerBuilder({
        pdfPage,
        annotationStorage,
        imageResourcesPath: this.imageResourcesPath,
        renderForms: this.#annotationMode === AnnotationMode.ENABLE_FORMS,
        linkService,
        downloadManager,
        enableScripting,
        hasJSActionsPromise,
        fieldObjectsPromise,
        annotationCanvasMap: this._annotationCanvasMap,
        accessibilityManager: this._accessibilityManager,
        annotationEditorUIManager,
        onAppend: annotationLayerDiv => {
          this.#addLayer(annotationLayerDiv, "annotationLayer");
        }
      });
    }
    const renderContinueCallback = cont => {
      showCanvas?.(false);
      if (this.renderingQueue && !this.renderingQueue.isHighestPriority(this)) {
        this.renderingState = RenderingStates.PAUSED;
        this.resume = () => {
          this.renderingState = RenderingStates.RUNNING;
          cont();
        };
        return;
      }
      cont();
    };
    const {
      width,
      height
    } = viewport;
    const canvas = document.createElement("canvas");
    canvas.setAttribute("role", "presentation");
    canvas.hidden = true;
    const hasHCM = !!(pageColors?.background && pageColors?.foreground);
    let showCanvas = isLastShow => {
      if (!hasHCM || isLastShow) {
        canvas.hidden = false;
        showCanvas = null;
      }
    };
    canvasWrapper.append(canvas);
    this.canvas = canvas;
    const ctx = canvas.getContext("2d", {
      alpha: false,
      willReadFrequently: !this.#enableHWA
    });
    const outputScale = this.outputScale = new OutputScale();
    if (this.maxCanvasPixels > 0) {
      const pixelsInViewport = width * height;
      const maxScale = Math.sqrt(this.maxCanvasPixels / pixelsInViewport);
      if (outputScale.sx > maxScale || outputScale.sy > maxScale) {
        outputScale.sx = maxScale;
        outputScale.sy = maxScale;
        this.#hasRestrictedScaling = true;
      } else {
        this.#hasRestrictedScaling = false;
      }
    }
    const sfx = approximateFraction(outputScale.sx);
    const sfy = approximateFraction(outputScale.sy);
    canvas.width = floorToDivide(width * outputScale.sx, sfx[0]);
    canvas.height = floorToDivide(height * outputScale.sy, sfy[0]);
    const {
      style
    } = canvas;
    style.width = floorToDivide(width, sfx[1]) + "px";
    style.height = floorToDivide(height, sfy[1]) + "px";
    this.#viewportMap.set(canvas, viewport);
    const transform = outputScale.scaled ? [outputScale.sx, 0, 0, outputScale.sy, 0, 0] : null;
    const renderContext = {
      canvasContext: ctx,
      transform,
      viewport,
      annotationMode: this.#annotationMode,
      optionalContentConfigPromise: this._optionalContentConfigPromise,
      annotationCanvasMap: this._annotationCanvasMap,
      pageColors,
      isEditing: this.#isEditing
    };
    const renderTask = this.renderTask = pdfPage.render(renderContext);
    renderTask.onContinue = renderContinueCallback;
    const resultPromise = renderTask.promise.then(async () => {
      showCanvas?.(true);
      await this.#finishRenderTask(renderTask);
      this.#renderTextLayer();
      if (this.annotationLayer) {
        await this.#renderAnnotationLayer();
      }
      const {
        annotationEditorUIManager
      } = this.#layerProperties;
      if (!annotationEditorUIManager) {
        return;
      }
      this.drawLayer ||= new DrawLayerBuilder({
        pageIndex: this.id
      });
      await this.#renderDrawLayer();
      this.drawLayer.setParent(canvasWrapper);
      if (!this.annotationEditorLayer) {
        this.annotationEditorLayer = new AnnotationEditorLayerBuilder({
          uiManager: annotationEditorUIManager,
          pdfPage,
          l10n,
          accessibilityManager: this._accessibilityManager,
          annotationLayer: this.annotationLayer?.annotationLayer,
          textLayer: this.textLayer,
          drawLayer: this.drawLayer.getDrawLayer(),
          onAppend: annotationEditorLayerDiv => {
            this.#addLayer(annotationEditorLayerDiv, "annotationEditorLayer");
          }
        });
      }
      this.#renderAnnotationEditorLayer();
    }, error => {
      if (!(error instanceof RenderingCancelledException)) {
        showCanvas?.(true);
      }
      return this.#finishRenderTask(renderTask, error);
    });
    if (pdfPage.isPureXfa) {
      if (!this.xfaLayer) {
        const {
          annotationStorage,
          linkService
        } = this.#layerProperties;
        this.xfaLayer = new XfaLayerBuilder({
          pdfPage,
          annotationStorage,
          linkService
        });
      }
      this.#renderXfaLayer();
    }
    div.setAttribute("data-loaded", true);
    this.eventBus.dispatch("pagerender", {
      source: this,
      pageNumber: this.id
    });
    return resultPromise;
  }
  setPageLabel(label) {
    this.pageLabel = typeof label === "string" ? label : null;
    this.div.setAttribute("data-l10n-args", JSON.stringify({
      page: this.pageLabel ?? this.id
    }));
    if (this.pageLabel !== null) {
      this.div.setAttribute("data-page-label", this.pageLabel);
    } else {
      this.div.removeAttribute("data-page-label");
    }
  }
  get thumbnailCanvas() {
    const {
      directDrawing,
      initialOptionalContent,
      regularAnnotations
    } = this.#useThumbnailCanvas;
    return directDrawing && initialOptionalContent && regularAnnotations ? this.canvas : null;
  }
}

;// CONCATENATED MODULE: ./web/pdf_viewer.js






const DEFAULT_CACHE_SIZE = 10;
const PagesCountLimit = {
  FORCE_SCROLL_MODE_PAGE: 10000,
  FORCE_LAZY_PAGE_INIT: 5000,
  PAUSE_EAGER_PAGE_INIT: 250
};
function isValidAnnotationEditorMode(mode) {
  return Object.values(AnnotationEditorType).includes(mode) && mode !== AnnotationEditorType.DISABLE;
}
class PDFPageViewBuffer {
  #buf = new Set();
  #size = 0;
  constructor(size) {
    this.#size = size;
  }
  push(view) {
    const buf = this.#buf;
    if (buf.has(view)) {
      buf.delete(view);
    }
    buf.add(view);
    if (buf.size > this.#size) {
      this.#destroyFirstView();
    }
  }
  resize(newSize, idsToKeep = null) {
    this.#size = newSize;
    const buf = this.#buf;
    if (idsToKeep) {
      const ii = buf.size;
      let i = 1;
      for (const view of buf) {
        if (idsToKeep.has(view.id)) {
          buf.delete(view);
          buf.add(view);
        }
        if (++i > ii) {
          break;
        }
      }
    }
    while (buf.size > this.#size) {
      this.#destroyFirstView();
    }
  }
  has(view) {
    return this.#buf.has(view);
  }
  [Symbol.iterator]() {
    return this.#buf.keys();
  }
  #destroyFirstView() {
    const firstView = this.#buf.keys().next().value;
    firstView?.destroy();
    this.#buf.delete(firstView);
  }
}
class PDFViewer {
  #buffer = null;
  #altTextManager = null;
  #annotationEditorHighlightColors = null;
  #annotationEditorMode = AnnotationEditorType.NONE;
  #annotationEditorUIManager = null;
  #annotationMode = AnnotationMode.ENABLE_FORMS;
  #containerTopLeft = null;
  #enableHWA = false;
  #enableHighlightFloatingButton = false;
  #enablePermissions = false;
  #enableUpdatedAddImage = false;
  #enableNewAltTextWhenAddingImage = false;
  #eventAbortController = null;
  #mlManager = null;
  #onPageRenderedCallback = null;
  #switchAnnotationEditorModeTimeoutId = null;
  #getAllTextInProgress = false;
  #hiddenCopyElement = null;
  #interruptCopyCondition = false;
  #previousContainerHeight = 0;
  #resizeObserver = new ResizeObserver(this.#resizeObserverCallback.bind(this));
  #scrollModePageState = null;
  #scaleTimeoutId = null;
  #textLayerMode = TextLayerMode.ENABLE;
  constructor(options) {
    const viewerVersion = "4.6.60";
    if (version !== viewerVersion) {
      throw new Error(`The API version "${version}" does not match the Viewer version "${viewerVersion}".`);
    }
    this.container = options.container;
    this.viewer = options.viewer || options.container.firstElementChild;
    this.#resizeObserver.observe(this.container);
    this.eventBus = options.eventBus;
    this.linkService = options.linkService || new SimpleLinkService();
    this.downloadManager = options.downloadManager || null;
    this.findController = options.findController || null;
    this.#altTextManager = options.altTextManager || null;
    if (this.findController) {
      this.findController.onIsPageVisible = pageNumber => this._getVisiblePages().ids.has(pageNumber);
    }
    this._scriptingManager = options.scriptingManager || null;
    this.#textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE;
    this.#annotationMode = options.annotationMode ?? AnnotationMode.ENABLE_FORMS;
    this.#annotationEditorMode = options.annotationEditorMode ?? AnnotationEditorType.NONE;
    this.#annotationEditorHighlightColors = options.annotationEditorHighlightColors || null;
    this.#enableHighlightFloatingButton = options.enableHighlightFloatingButton === true;
    this.#enableUpdatedAddImage = options.enableUpdatedAddImage === true;
    this.#enableNewAltTextWhenAddingImage = options.enableNewAltTextWhenAddingImage === true;
    this.imageResourcesPath = options.imageResourcesPath || "";
    this.enablePrintAutoRotate = options.enablePrintAutoRotate || false;
    this.maxCanvasPixels = options.maxCanvasPixels;
    this.l10n = options.l10n;
    this.#enablePermissions = options.enablePermissions || false;
    this.pageColors = options.pageColors || null;
    this.#mlManager = options.mlManager || null;
    this.#enableHWA = options.enableHWA || false;
    this.defaultRenderingQueue = !options.renderingQueue;
    this.renderingQueue = options.renderingQueue;
    const {
      abortSignal
    } = options;
    abortSignal?.addEventListener("abort", () => {
      this.#resizeObserver.disconnect();
      this.#resizeObserver = null;
    }, {
      once: true
    });
    this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this), abortSignal);
    this.presentationModeState = PresentationModeState.UNKNOWN;
    this._resetView();
    this.#updateContainerHeightCss();
    this.eventBus._on("thumbnailrendered", ({
      pageNumber,
      pdfPage
    }) => {
      const pageView = this._pages[pageNumber - 1];
      if (!this.#buffer.has(pageView)) {
        pdfPage?.cleanup();
      }
    });
  }
  get pagesCount() {
    return this._pages.length;
  }
  getPageView(index) {
    return this._pages[index];
  }
  getCachedPageViews() {
    return new Set(this.#buffer);
  }
  get pageViewsReady() {
    return this._pages.every(pageView => pageView?.pdfPage);
  }
  get renderForms() {
    return this.#annotationMode === AnnotationMode.ENABLE_FORMS;
  }
  get enableScripting() {
    return !!this._scriptingManager;
  }
  get currentPageNumber() {
    return this._currentPageNumber;
  }
  set currentPageNumber(val) {
    if (!Number.isInteger(val)) {
      throw new Error("Invalid page number.");
    }
    if (!this.pdfDocument) {
      return;
    }
    if (!this._setCurrentPageNumber(val, true)) {
      console.error(`currentPageNumber: "${val}" is not a valid page.`);
    }
  }
  _setCurrentPageNumber(val, resetCurrentPageView = false) {
    if (this._currentPageNumber === val) {
      if (resetCurrentPageView) {
        this.#resetCurrentPageView();
      }
      return true;
    }
    if (!(0 < val && val <= this.pagesCount)) {
      return false;
    }
    const previous = this._currentPageNumber;
    this._currentPageNumber = val;
    this.eventBus.dispatch("pagechanging", {
      source: this,
      pageNumber: val,
      pageLabel: this._pageLabels?.[val - 1] ?? null,
      previous
    });
    if (resetCurrentPageView) {
      this.#resetCurrentPageView();
    }
    return true;
  }
  get currentPageLabel() {
    return this._pageLabels?.[this._currentPageNumber - 1] ?? null;
  }
  set currentPageLabel(val) {
    if (!this.pdfDocument) {
      return;
    }
    let page = val | 0;
    if (this._pageLabels) {
      const i = this._pageLabels.indexOf(val);
      if (i >= 0) {
        page = i + 1;
      }
    }
    if (!this._setCurrentPageNumber(page, true)) {
      console.error(`currentPageLabel: "${val}" is not a valid page.`);
    }
  }
  get currentScale() {
    return this._currentScale !== UNKNOWN_SCALE ? this._currentScale : DEFAULT_SCALE;
  }
  set currentScale(val) {
    if (isNaN(val)) {
      throw new Error("Invalid numeric scale.");
    }
    if (!this.pdfDocument) {
      return;
    }
    this.#setScale(val, {
      noScroll: false
    });
  }
  get currentScaleValue() {
    return this._currentScaleValue;
  }
  set currentScaleValue(val) {
    if (!this.pdfDocument) {
      return;
    }
    this.#setScale(val, {
      noScroll: false
    });
  }
  get pagesRotation() {
    return this._pagesRotation;
  }
  set pagesRotation(rotation) {
    if (!isValidRotation(rotation)) {
      throw new Error("Invalid pages rotation angle.");
    }
    if (!this.pdfDocument) {
      return;
    }
    rotation %= 360;
    if (rotation < 0) {
      rotation += 360;
    }
    if (this._pagesRotation === rotation) {
      return;
    }
    this._pagesRotation = rotation;
    const pageNumber = this._currentPageNumber;
    this.refresh(true, {
      rotation
    });
    if (this._currentScaleValue) {
      this.#setScale(this._currentScaleValue, {
        noScroll: true
      });
    }
    this.eventBus.dispatch("rotationchanging", {
      source: this,
      pagesRotation: rotation,
      pageNumber
    });
    if (this.defaultRenderingQueue) {
      this.update();
    }
  }
  get firstPagePromise() {
    return this.pdfDocument ? this._firstPageCapability.promise : null;
  }
  get onePageRendered() {
    return this.pdfDocument ? this._onePageRenderedCapability.promise : null;
  }
  get pagesPromise() {
    return this.pdfDocument ? this._pagesCapability.promise : null;
  }
  get _layerProperties() {
    const self = this;
    return shadow(this, "_layerProperties", {
      get annotationEditorUIManager() {
        return self.#annotationEditorUIManager;
      },
      get annotationStorage() {
        return self.pdfDocument?.annotationStorage;
      },
      get downloadManager() {
        return self.downloadManager;
      },
      get enableScripting() {
        return !!self._scriptingManager;
      },
      get fieldObjectsPromise() {
        return self.pdfDocument?.getFieldObjects();
      },
      get findController() {
        return self.findController;
      },
      get hasJSActionsPromise() {
        return self.pdfDocument?.hasJSActions();
      },
      get linkService() {
        return self.linkService;
      }
    });
  }
  #initializePermissions(permissions) {
    const params = {
      annotationEditorMode: this.#annotationEditorMode,
      annotationMode: this.#annotationMode,
      textLayerMode: this.#textLayerMode
    };
    if (!permissions) {
      return params;
    }
    if (!permissions.includes(PermissionFlag.COPY) && this.#textLayerMode === TextLayerMode.ENABLE) {
      params.textLayerMode = TextLayerMode.ENABLE_PERMISSIONS;
    }
    if (!permissions.includes(PermissionFlag.MODIFY_CONTENTS)) {
      params.annotationEditorMode = AnnotationEditorType.DISABLE;
    }
    if (!permissions.includes(PermissionFlag.MODIFY_ANNOTATIONS) && !permissions.includes(PermissionFlag.FILL_INTERACTIVE_FORMS) && this.#annotationMode === AnnotationMode.ENABLE_FORMS) {
      params.annotationMode = AnnotationMode.ENABLE;
    }
    return params;
  }
  async #onePageRenderedOrForceFetch(signal) {
    if (document.visibilityState === "hidden" || !this.container.offsetParent || this._getVisiblePages().views.length === 0) {
      return;
    }
    const hiddenCapability = Promise.withResolvers();
    function onVisibilityChange() {
      if (document.visibilityState === "hidden") {
        hiddenCapability.resolve();
      }
    }
    document.addEventListener("visibilitychange", onVisibilityChange, {
      signal
    });
    await Promise.race([this._onePageRenderedCapability.promise, hiddenCapability.promise]);
    document.removeEventListener("visibilitychange", onVisibilityChange);
  }
  async getAllText() {
    const texts = [];
    const buffer = [];
    for (let pageNum = 1, pagesCount = this.pdfDocument.numPages; pageNum <= pagesCount; ++pageNum) {
      if (this.#interruptCopyCondition) {
        return null;
      }
      buffer.length = 0;
      const page = await this.pdfDocument.getPage(pageNum);
      const {
        items
      } = await page.getTextContent();
      for (const item of items) {
        if (item.str) {
          buffer.push(item.str);
        }
        if (item.hasEOL) {
          buffer.push("\n");
        }
      }
      texts.push(removeNullCharacters(buffer.join("")));
    }
    return texts.join("\n");
  }
  #copyCallback(textLayerMode, event) {
    const selection = document.getSelection();
    const {
      focusNode,
      anchorNode
    } = selection;
    if (anchorNode && focusNode && selection.containsNode(this.#hiddenCopyElement)) {
      if (this.#getAllTextInProgress || textLayerMode === TextLayerMode.ENABLE_PERMISSIONS) {
        event.preventDefault();
        event.stopPropagation();
        return;
      }
      this.#getAllTextInProgress = true;
      const {
        classList
      } = this.viewer;
      classList.add("copyAll");
      const ac = new AbortController();
      window.addEventListener("keydown", ev => this.#interruptCopyCondition = ev.key === "Escape", {
        signal: ac.signal
      });
      this.getAllText().then(async text => {
        if (text !== null) {
          await navigator.clipboard.writeText(text);
        }
      }).catch(reason => {
        console.warn(`Something goes wrong when extracting the text: ${reason.message}`);
      }).finally(() => {
        this.#getAllTextInProgress = false;
        this.#interruptCopyCondition = false;
        ac.abort();
        classList.remove("copyAll");
      });
      event.preventDefault();
      event.stopPropagation();
    }
  }
  setDocument(pdfDocument) {
    if (this.pdfDocument) {
      this.eventBus.dispatch("pagesdestroy", {
        source: this
      });
      this._cancelRendering();
      this._resetView();
      this.findController?.setDocument(null);
      this._scriptingManager?.setDocument(null);
      this.#annotationEditorUIManager?.destroy();
      this.#annotationEditorUIManager = null;
    }
    this.pdfDocument = pdfDocument;
    if (!pdfDocument) {
      return;
    }
    const pagesCount = pdfDocument.numPages;
    const firstPagePromise = pdfDocument.getPage(1);
    const optionalContentConfigPromise = pdfDocument.getOptionalContentConfig({
      intent: "display"
    });
    const permissionsPromise = this.#enablePermissions ? pdfDocument.getPermissions() : Promise.resolve();
    const {
      eventBus,
      pageColors,
      viewer
    } = this;
    this.#eventAbortController = new AbortController();
    const {
      signal
    } = this.#eventAbortController;
    if (pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE) {
      console.warn("Forcing PAGE-scrolling for performance reasons, given the length of the document.");
      const mode = this._scrollMode = ScrollMode.PAGE;
      eventBus.dispatch("scrollmodechanged", {
        source: this,
        mode
      });
    }
    this._pagesCapability.promise.then(() => {
      eventBus.dispatch("pagesloaded", {
        source: this,
        pagesCount
      });
    }, () => {});
    const onBeforeDraw = evt => {
      const pageView = this._pages[evt.pageNumber - 1];
      if (!pageView) {
        return;
      }
      this.#buffer.push(pageView);
    };
    eventBus._on("pagerender", onBeforeDraw, {
      signal
    });
    const onAfterDraw = evt => {
      if (evt.cssTransform) {
        return;
      }
      this._onePageRenderedCapability.resolve({
        timestamp: evt.timestamp
      });
      eventBus._off("pagerendered", onAfterDraw);
    };
    eventBus._on("pagerendered", onAfterDraw, {
      signal
    });
    Promise.all([firstPagePromise, permissionsPromise]).then(([firstPdfPage, permissions]) => {
      if (pdfDocument !== this.pdfDocument) {
        return;
      }
      this._firstPageCapability.resolve(firstPdfPage);
      this._optionalContentConfigPromise = optionalContentConfigPromise;
      const {
        annotationEditorMode,
        annotationMode,
        textLayerMode
      } = this.#initializePermissions(permissions);
      if (textLayerMode !== TextLayerMode.DISABLE) {
        const element = this.#hiddenCopyElement = document.createElement("div");
        element.id = "hiddenCopyElement";
        viewer.before(element);
      }
      if (annotationEditorMode !== AnnotationEditorType.DISABLE) {
        const mode = annotationEditorMode;
        if (pdfDocument.isPureXfa) {
          console.warn("Warning: XFA-editing is not implemented.");
        } else if (isValidAnnotationEditorMode(mode)) {
          this.#annotationEditorUIManager = new AnnotationEditorUIManager(this.container, viewer, this.#altTextManager, eventBus, pdfDocument, pageColors, this.#annotationEditorHighlightColors, this.#enableHighlightFloatingButton, this.#enableUpdatedAddImage, this.#enableNewAltTextWhenAddingImage, this.#mlManager);
          eventBus.dispatch("annotationeditoruimanager", {
            source: this,
            uiManager: this.#annotationEditorUIManager
          });
          if (mode !== AnnotationEditorType.NONE) {
            if (mode === AnnotationEditorType.STAMP) {
              this.#mlManager?.loadModel("altText");
            }
            this.#annotationEditorUIManager.updateMode(mode);
          }
        } else {
          console.error(`Invalid AnnotationEditor mode: ${mode}`);
        }
      }
      const viewerElement = this._scrollMode === ScrollMode.PAGE ? null : viewer;
      const scale = this.currentScale;
      const viewport = firstPdfPage.getViewport({
        scale: scale * PixelsPerInch.PDF_TO_CSS_UNITS
      });
      viewer.style.setProperty("--scale-factor", viewport.scale);
      if (pageColors?.foreground === "CanvasText" || pageColors?.background === "Canvas") {
        viewer.style.setProperty("--hcm-highlight-filter", pdfDocument.filterFactory.addHighlightHCMFilter("highlight", "CanvasText", "Canvas", "HighlightText", "Highlight"));
        viewer.style.setProperty("--hcm-highlight-selected-filter", pdfDocument.filterFactory.addHighlightHCMFilter("highlight_selected", "CanvasText", "Canvas", "HighlightText", "ButtonText"));
      }
      for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
        const pageView = new PDFPageView({
          container: viewerElement,
          eventBus,
          id: pageNum,
          scale,
          defaultViewport: viewport.clone(),
          optionalContentConfigPromise,
          renderingQueue: this.renderingQueue,
          textLayerMode,
          annotationMode,
          imageResourcesPath: this.imageResourcesPath,
          maxCanvasPixels: this.maxCanvasPixels,
          pageColors,
          l10n: this.l10n,
          layerProperties: this._layerProperties,
          enableHWA: this.#enableHWA
        });
        this._pages.push(pageView);
      }
      this._pages[0]?.setPdfPage(firstPdfPage);
      if (this._scrollMode === ScrollMode.PAGE) {
        this.#ensurePageViewVisible();
      } else if (this._spreadMode !== SpreadMode.NONE) {
        this._updateSpreadMode();
      }
      this.#onePageRenderedOrForceFetch(signal).then(async () => {
        if (pdfDocument !== this.pdfDocument) {
          return;
        }
        this.findController?.setDocument(pdfDocument);
        this._scriptingManager?.setDocument(pdfDocument);
        if (this.#hiddenCopyElement) {
          document.addEventListener("copy", this.#copyCallback.bind(this, textLayerMode), {
            signal
          });
        }
        if (this.#annotationEditorUIManager) {
          eventBus.dispatch("annotationeditormodechanged", {
            source: this,
            mode: this.#annotationEditorMode
          });
        }
        if (pdfDocument.loadingParams.disableAutoFetch || pagesCount > PagesCountLimit.FORCE_LAZY_PAGE_INIT) {
          this._pagesCapability.resolve();
          return;
        }
        let getPagesLeft = pagesCount - 1;
        if (getPagesLeft <= 0) {
          this._pagesCapability.resolve();
          return;
        }
        for (let pageNum = 2; pageNum <= pagesCount; ++pageNum) {
          const promise = pdfDocument.getPage(pageNum).then(pdfPage => {
            const pageView = this._pages[pageNum - 1];
            if (!pageView.pdfPage) {
              pageView.setPdfPage(pdfPage);
            }
            if (--getPagesLeft === 0) {
              this._pagesCapability.resolve();
            }
          }, reason => {
            console.error(`Unable to get page ${pageNum} to initialize viewer`, reason);
            if (--getPagesLeft === 0) {
              this._pagesCapability.resolve();
            }
          });
          if (pageNum % PagesCountLimit.PAUSE_EAGER_PAGE_INIT === 0) {
            await promise;
          }
        }
      });
      eventBus.dispatch("pagesinit", {
        source: this
      });
      pdfDocument.getMetadata().then(({
        info
      }) => {
        if (pdfDocument !== this.pdfDocument) {
          return;
        }
        if (info.Language) {
          viewer.lang = info.Language;
        }
      });
      if (this.defaultRenderingQueue) {
        this.update();
      }
    }).catch(reason => {
      console.error("Unable to initialize viewer", reason);
      this._pagesCapability.reject(reason);
    });
  }
  setPageLabels(labels) {
    if (!this.pdfDocument) {
      return;
    }
    if (!labels) {
      this._pageLabels = null;
    } else if (!(Array.isArray(labels) && this.pdfDocument.numPages === labels.length)) {
      this._pageLabels = null;
      console.error(`setPageLabels: Invalid page labels.`);
    } else {
      this._pageLabels = labels;
    }
    for (let i = 0, ii = this._pages.length; i < ii; i++) {
      this._pages[i].setPageLabel(this._pageLabels?.[i] ?? null);
    }
  }
  _resetView() {
    this._pages = [];
    this._currentPageNumber = 1;
    this._currentScale = UNKNOWN_SCALE;
    this._currentScaleValue = null;
    this._pageLabels = null;
    this.#buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE);
    this._location = null;
    this._pagesRotation = 0;
    this._optionalContentConfigPromise = null;
    this._firstPageCapability = Promise.withResolvers();
    this._onePageRenderedCapability = Promise.withResolvers();
    this._pagesCapability = Promise.withResolvers();
    this._scrollMode = ScrollMode.VERTICAL;
    this._previousScrollMode = ScrollMode.UNKNOWN;
    this._spreadMode = SpreadMode.NONE;
    this.#scrollModePageState = {
      previousPageNumber: 1,
      scrollDown: true,
      pages: []
    };
    this.#eventAbortController?.abort();
    this.#eventAbortController = null;
    this.viewer.textContent = "";
    this._updateScrollMode();
    this.viewer.removeAttribute("lang");
    this.#hiddenCopyElement?.remove();
    this.#hiddenCopyElement = null;
    this.#cleanupSwitchAnnotationEditorMode();
  }
  #ensurePageViewVisible() {
    if (this._scrollMode !== ScrollMode.PAGE) {
      throw new Error("#ensurePageViewVisible: Invalid scrollMode value.");
    }
    const pageNumber = this._currentPageNumber,
      state = this.#scrollModePageState,
      viewer = this.viewer;
    viewer.textContent = "";
    state.pages.length = 0;
    if (this._spreadMode === SpreadMode.NONE && !this.isInPresentationMode) {
      const pageView = this._pages[pageNumber - 1];
      viewer.append(pageView.div);
      state.pages.push(pageView);
    } else {
      const pageIndexSet = new Set(),
        parity = this._spreadMode - 1;
      if (parity === -1) {
        pageIndexSet.add(pageNumber - 1);
      } else if (pageNumber % 2 !== parity) {
        pageIndexSet.add(pageNumber - 1);
        pageIndexSet.add(pageNumber);
      } else {
        pageIndexSet.add(pageNumber - 2);
        pageIndexSet.add(pageNumber - 1);
      }
      const spread = document.createElement("div");
      spread.className = "spread";
      if (this.isInPresentationMode) {
        const dummyPage = document.createElement("div");
        dummyPage.className = "dummyPage";
        spread.append(dummyPage);
      }
      for (const i of pageIndexSet) {
        const pageView = this._pages[i];
        if (!pageView) {
          continue;
        }
        spread.append(pageView.div);
        state.pages.push(pageView);
      }
      viewer.append(spread);
    }
    state.scrollDown = pageNumber >= state.previousPageNumber;
    state.previousPageNumber = pageNumber;
  }
  _scrollUpdate() {
    if (this.pagesCount === 0) {
      return;
    }
    this.update();
  }
  #scrollIntoView(pageView, pageSpot = null) {
    const {
      div,
      id
    } = pageView;
    if (this._currentPageNumber !== id) {
      this._setCurrentPageNumber(id);
    }
    if (this._scrollMode === ScrollMode.PAGE) {
      this.#ensurePageViewVisible();
      this.update();
    }
    if (!pageSpot && !this.isInPresentationMode) {
      const left = div.offsetLeft + div.clientLeft,
        right = left + div.clientWidth;
      const {
        scrollLeft,
        clientWidth
      } = this.container;
      if (this._scrollMode === ScrollMode.HORIZONTAL || left < scrollLeft || right > scrollLeft + clientWidth) {
        pageSpot = {
          left: 0,
          top: 0
        };
      }
    }
    scrollIntoView(div, pageSpot);
    if (!this._currentScaleValue && this._location) {
      this._location = null;
    }
  }
  #isSameScale(newScale) {
    return newScale === this._currentScale || Math.abs(newScale - this._currentScale) < 1e-15;
  }
  #setScaleUpdatePages(newScale, newValue, {
    noScroll = false,
    preset = false,
    drawingDelay = -1,
    origin = null
  }) {
    this._currentScaleValue = newValue.toString();
    if (this.#isSameScale(newScale)) {
      if (preset) {
        this.eventBus.dispatch("scalechanging", {
          source: this,
          scale: newScale,
          presetValue: newValue
        });
      }
      return;
    }
    this.viewer.style.setProperty("--scale-factor", newScale * PixelsPerInch.PDF_TO_CSS_UNITS);
    const postponeDrawing = drawingDelay >= 0 && drawingDelay < 1000;
    this.refresh(true, {
      scale: newScale,
      drawingDelay: postponeDrawing ? drawingDelay : -1
    });
    if (postponeDrawing) {
      this.#scaleTimeoutId = setTimeout(() => {
        this.#scaleTimeoutId = null;
        this.refresh();
      }, drawingDelay);
    }
    const previousScale = this._currentScale;
    this._currentScale = newScale;
    if (!noScroll) {
      let page = this._currentPageNumber,
        dest;
      if (this._location && !(this.isInPresentationMode || this.isChangingPresentationMode)) {
        page = this._location.pageNumber;
        dest = [null, {
          name: "XYZ"
        }, this._location.left, this._location.top, null];
      }
      this.scrollPageIntoView({
        pageNumber: page,
        destArray: dest,
        allowNegativeOffset: true
      });
      if (Array.isArray(origin)) {
        const scaleDiff = newScale / previousScale - 1;
        const [top, left] = this.containerTopLeft;
        this.container.scrollLeft += (origin[0] - left) * scaleDiff;
        this.container.scrollTop += (origin[1] - top) * scaleDiff;
      }
    }
    this.eventBus.dispatch("scalechanging", {
      source: this,
      scale: newScale,
      presetValue: preset ? newValue : undefined
    });
    if (this.defaultRenderingQueue) {
      this.update();
    }
  }
  get #pageWidthScaleFactor() {
    if (this._spreadMode !== SpreadMode.NONE && this._scrollMode !== ScrollMode.HORIZONTAL) {
      return 2;
    }
    return 1;
  }
  #setScale(value, options) {
    let scale = parseFloat(value);
    if (scale > 0) {
      options.preset = false;
      this.#setScaleUpdatePages(scale, value, options);
    } else {
      const currentPage = this._pages[this._currentPageNumber - 1];
      if (!currentPage) {
        return;
      }
      let hPadding = SCROLLBAR_PADDING,
        vPadding = VERTICAL_PADDING;
      if (this.isInPresentationMode) {
        hPadding = vPadding = 4;
        if (this._spreadMode !== SpreadMode.NONE) {
          hPadding *= 2;
        }
      } else if (this._scrollMode === ScrollMode.HORIZONTAL) {
        [hPadding, vPadding] = [vPadding, hPadding];
      }
      const pageWidthScale = (this.container.clientWidth - hPadding) / currentPage.width * currentPage.scale / this.#pageWidthScaleFactor;
      const pageHeightScale = (this.container.clientHeight - vPadding) / currentPage.height * currentPage.scale;
      switch (value) {
        case "page-actual":
          scale = 1;
          break;
        case "page-width":
          scale = pageWidthScale;
          break;
        case "page-height":
          scale = pageHeightScale;
          break;
        case "page-fit":
          scale = Math.min(pageWidthScale, pageHeightScale);
          break;
        case "auto":
          const horizontalScale = isPortraitOrientation(currentPage) ? pageWidthScale : Math.min(pageHeightScale, pageWidthScale);
          scale = Math.min(MAX_AUTO_SCALE, horizontalScale);
          break;
        default:
          console.error(`#setScale: "${value}" is an unknown zoom value.`);
          return;
      }
      options.preset = true;
      this.#setScaleUpdatePages(scale, value, options);
    }
  }
  #resetCurrentPageView() {
    const pageView = this._pages[this._currentPageNumber - 1];
    if (this.isInPresentationMode) {
      this.#setScale(this._currentScaleValue, {
        noScroll: true
      });
    }
    this.#scrollIntoView(pageView);
  }
  pageLabelToPageNumber(label) {
    if (!this._pageLabels) {
      return null;
    }
    const i = this._pageLabels.indexOf(label);
    if (i < 0) {
      return null;
    }
    return i + 1;
  }
  scrollPageIntoView({
    pageNumber,
    destArray = null,
    allowNegativeOffset = false,
    ignoreDestinationZoom = false
  }) {
    if (!this.pdfDocument) {
      return;
    }
    const pageView = Number.isInteger(pageNumber) && this._pages[pageNumber - 1];
    if (!pageView) {
      console.error(`scrollPageIntoView: "${pageNumber}" is not a valid pageNumber parameter.`);
      return;
    }
    if (this.isInPresentationMode || !destArray) {
      this._setCurrentPageNumber(pageNumber, true);
      return;
    }
    let x = 0,
      y = 0;
    let width = 0,
      height = 0,
      widthScale,
      heightScale;
    const changeOrientation = pageView.rotation % 180 !== 0;
    const pageWidth = (changeOrientation ? pageView.height : pageView.width) / pageView.scale / PixelsPerInch.PDF_TO_CSS_UNITS;
    const pageHeight = (changeOrientation ? pageView.width : pageView.height) / pageView.scale / PixelsPerInch.PDF_TO_CSS_UNITS;
    let scale = 0;
    switch (destArray[1].name) {
      case "XYZ":
        x = destArray[2];
        y = destArray[3];
        scale = destArray[4];
        x = x !== null ? x : 0;
        y = y !== null ? y : pageHeight;
        break;
      case "Fit":
      case "FitB":
        scale = "page-fit";
        break;
      case "FitH":
      case "FitBH":
        y = destArray[2];
        scale = "page-width";
        if (y === null && this._location) {
          x = this._location.left;
          y = this._location.top;
        } else if (typeof y !== "number" || y < 0) {
          y = pageHeight;
        }
        break;
      case "FitV":
      case "FitBV":
        x = destArray[2];
        width = pageWidth;
        height = pageHeight;
        scale = "page-height";
        break;
      case "FitR":
        x = destArray[2];
        y = destArray[3];
        width = destArray[4] - x;
        height = destArray[5] - y;
        let hPadding = SCROLLBAR_PADDING,
          vPadding = VERTICAL_PADDING;
        widthScale = (this.container.clientWidth - hPadding) / width / PixelsPerInch.PDF_TO_CSS_UNITS;
        heightScale = (this.container.clientHeight - vPadding) / height / PixelsPerInch.PDF_TO_CSS_UNITS;
        scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
        break;
      default:
        console.error(`scrollPageIntoView: "${destArray[1].name}" is not a valid destination type.`);
        return;
    }
    if (!ignoreDestinationZoom) {
      if (scale && scale !== this._currentScale) {
        this.currentScaleValue = scale;
      } else if (this._currentScale === UNKNOWN_SCALE) {
        this.currentScaleValue = DEFAULT_SCALE_VALUE;
      }
    }
    if (scale === "page-fit" && !destArray[4]) {
      this.#scrollIntoView(pageView);
      return;
    }
    const boundingRect = [pageView.viewport.convertToViewportPoint(x, y), pageView.viewport.convertToViewportPoint(x + width, y + height)];
    let left = Math.min(boundingRect[0][0], boundingRect[1][0]);
    let top = Math.min(boundingRect[0][1], boundingRect[1][1]);
    if (!allowNegativeOffset) {
      left = Math.max(left, 0);
      top = Math.max(top, 0);
    }
    this.#scrollIntoView(pageView, {
      left,
      top
    });
  }
  _updateLocation(firstPage) {
    const currentScale = this._currentScale;
    const currentScaleValue = this._currentScaleValue;
    const normalizedScaleValue = parseFloat(currentScaleValue) === currentScale ? Math.round(currentScale * 10000) / 100 : currentScaleValue;
    const pageNumber = firstPage.id;
    const currentPageView = this._pages[pageNumber - 1];
    const container = this.container;
    const topLeft = currentPageView.getPagePoint(container.scrollLeft - firstPage.x, container.scrollTop - firstPage.y);
    const intLeft = Math.round(topLeft[0]);
    const intTop = Math.round(topLeft[1]);
    let pdfOpenParams = `#page=${pageNumber}`;
    if (!this.isInPresentationMode) {
      pdfOpenParams += `&zoom=${normalizedScaleValue},${intLeft},${intTop}`;
    }
    this._location = {
      pageNumber,
      scale: normalizedScaleValue,
      top: intTop,
      left: intLeft,
      rotation: this._pagesRotation,
      pdfOpenParams
    };
  }
  update() {
    const visible = this._getVisiblePages();
    const visiblePages = visible.views,
      numVisiblePages = visiblePages.length;
    if (numVisiblePages === 0) {
      return;
    }
    const newCacheSize = Math.max(DEFAULT_CACHE_SIZE, 2 * numVisiblePages + 1);
    this.#buffer.resize(newCacheSize, visible.ids);
    this.renderingQueue.renderHighestPriority(visible);
    const isSimpleLayout = this._spreadMode === SpreadMode.NONE && (this._scrollMode === ScrollMode.PAGE || this._scrollMode === ScrollMode.VERTICAL);
    const currentId = this._currentPageNumber;
    let stillFullyVisible = false;
    for (const page of visiblePages) {
      if (page.percent < 100) {
        break;
      }
      if (page.id === currentId && isSimpleLayout) {
        stillFullyVisible = true;
        break;
      }
    }
    this._setCurrentPageNumber(stillFullyVisible ? currentId : visiblePages[0].id);
    this._updateLocation(visible.first);
    this.eventBus.dispatch("updateviewarea", {
      source: this,
      location: this._location
    });
  }
  #switchToEditAnnotationMode() {
    const visible = this._getVisiblePages();
    const pagesToRefresh = [];
    const {
      ids,
      views
    } = visible;
    for (const page of views) {
      const {
        view
      } = page;
      if (!view.hasEditableAnnotations()) {
        ids.delete(view.id);
        continue;
      }
      pagesToRefresh.push(page);
    }
    if (pagesToRefresh.length === 0) {
      return null;
    }
    this.renderingQueue.renderHighestPriority({
      first: pagesToRefresh[0],
      last: pagesToRefresh.at(-1),
      views: pagesToRefresh,
      ids
    });
    return ids;
  }
  containsElement(element) {
    return this.container.contains(element);
  }
  focus() {
    this.container.focus();
  }
  get _isContainerRtl() {
    return getComputedStyle(this.container).direction === "rtl";
  }
  get isInPresentationMode() {
    return this.presentationModeState === PresentationModeState.FULLSCREEN;
  }
  get isChangingPresentationMode() {
    return this.presentationModeState === PresentationModeState.CHANGING;
  }
  get isHorizontalScrollbarEnabled() {
    return this.isInPresentationMode ? false : this.container.scrollWidth > this.container.clientWidth;
  }
  get isVerticalScrollbarEnabled() {
    return this.isInPresentationMode ? false : this.container.scrollHeight > this.container.clientHeight;
  }
  _getVisiblePages() {
    const views = this._scrollMode === ScrollMode.PAGE ? this.#scrollModePageState.pages : this._pages,
      horizontal = this._scrollMode === ScrollMode.HORIZONTAL,
      rtl = horizontal && this._isContainerRtl;
    return getVisibleElements({
      scrollEl: this.container,
      views,
      sortByVisibility: true,
      horizontal,
      rtl
    });
  }
  cleanup() {
    for (const pageView of this._pages) {
      if (pageView.renderingState !== RenderingStates.FINISHED) {
        pageView.reset();
      }
    }
  }
  _cancelRendering() {
    for (const pageView of this._pages) {
      pageView.cancelRendering();
    }
  }
  async #ensurePdfPageLoaded(pageView) {
    if (pageView.pdfPage) {
      return pageView.pdfPage;
    }
    try {
      const pdfPage = await this.pdfDocument.getPage(pageView.id);
      if (!pageView.pdfPage) {
        pageView.setPdfPage(pdfPage);
      }
      return pdfPage;
    } catch (reason) {
      console.error("Unable to get page for page view", reason);
      return null;
    }
  }
  #getScrollAhead(visible) {
    if (visible.first?.id === 1) {
      return true;
    } else if (visible.last?.id === this.pagesCount) {
      return false;
    }
    switch (this._scrollMode) {
      case ScrollMode.PAGE:
        return this.#scrollModePageState.scrollDown;
      case ScrollMode.HORIZONTAL:
        return this.scroll.right;
    }
    return this.scroll.down;
  }
  forceRendering(currentlyVisiblePages) {
    const visiblePages = currentlyVisiblePages || this._getVisiblePages();
    const scrollAhead = this.#getScrollAhead(visiblePages);
    const preRenderExtra = this._spreadMode !== SpreadMode.NONE && this._scrollMode !== ScrollMode.HORIZONTAL;
    const pageView = this.renderingQueue.getHighestPriority(visiblePages, this._pages, scrollAhead, preRenderExtra);
    if (pageView) {
      this.#ensurePdfPageLoaded(pageView).then(() => {
        this.renderingQueue.renderView(pageView);
      });
      return true;
    }
    return false;
  }
  get hasEqualPageSizes() {
    const firstPageView = this._pages[0];
    for (let i = 1, ii = this._pages.length; i < ii; ++i) {
      const pageView = this._pages[i];
      if (pageView.width !== firstPageView.width || pageView.height !== firstPageView.height) {
        return false;
      }
    }
    return true;
  }
  getPagesOverview() {
    let initialOrientation;
    return this._pages.map(pageView => {
      const viewport = pageView.pdfPage.getViewport({
        scale: 1
      });
      const orientation = isPortraitOrientation(viewport);
      if (initialOrientation === undefined) {
        initialOrientation = orientation;
      } else if (this.enablePrintAutoRotate && orientation !== initialOrientation) {
        return {
          width: viewport.height,
          height: viewport.width,
          rotation: (viewport.rotation - 90) % 360
        };
      }
      return {
        width: viewport.width,
        height: viewport.height,
        rotation: viewport.rotation
      };
    });
  }
  get optionalContentConfigPromise() {
    if (!this.pdfDocument) {
      return Promise.resolve(null);
    }
    if (!this._optionalContentConfigPromise) {
      console.error("optionalContentConfigPromise: Not initialized yet.");
      return this.pdfDocument.getOptionalContentConfig({
        intent: "display"
      });
    }
    return this._optionalContentConfigPromise;
  }
  set optionalContentConfigPromise(promise) {
    if (!(promise instanceof Promise)) {
      throw new Error(`Invalid optionalContentConfigPromise: ${promise}`);
    }
    if (!this.pdfDocument) {
      return;
    }
    if (!this._optionalContentConfigPromise) {
      return;
    }
    this._optionalContentConfigPromise = promise;
    this.refresh(false, {
      optionalContentConfigPromise: promise
    });
    this.eventBus.dispatch("optionalcontentconfigchanged", {
      source: this,
      promise
    });
  }
  get scrollMode() {
    return this._scrollMode;
  }
  set scrollMode(mode) {
    if (this._scrollMode === mode) {
      return;
    }
    if (!isValidScrollMode(mode)) {
      throw new Error(`Invalid scroll mode: ${mode}`);
    }
    if (this.pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE) {
      return;
    }
    this._previousScrollMode = this._scrollMode;
    this._scrollMode = mode;
    this.eventBus.dispatch("scrollmodechanged", {
      source: this,
      mode
    });
    this._updateScrollMode(this._currentPageNumber);
  }
  _updateScrollMode(pageNumber = null) {
    const scrollMode = this._scrollMode,
      viewer = this.viewer;
    viewer.classList.toggle("scrollHorizontal", scrollMode === ScrollMode.HORIZONTAL);
    viewer.classList.toggle("scrollWrapped", scrollMode === ScrollMode.WRAPPED);
    if (!this.pdfDocument || !pageNumber) {
      return;
    }
    if (scrollMode === ScrollMode.PAGE) {
      this.#ensurePageViewVisible();
    } else if (this._previousScrollMode === ScrollMode.PAGE) {
      this._updateSpreadMode();
    }
    if (this._currentScaleValue && isNaN(this._currentScaleValue)) {
      this.#setScale(this._currentScaleValue, {
        noScroll: true
      });
    }
    this._setCurrentPageNumber(pageNumber, true);
    this.update();
  }
  get spreadMode() {
    return this._spreadMode;
  }
  set spreadMode(mode) {
    if (this._spreadMode === mode) {
      return;
    }
    if (!isValidSpreadMode(mode)) {
      throw new Error(`Invalid spread mode: ${mode}`);
    }
    this._spreadMode = mode;
    this.eventBus.dispatch("spreadmodechanged", {
      source: this,
      mode
    });
    this._updateSpreadMode(this._currentPageNumber);
  }
  _updateSpreadMode(pageNumber = null) {
    if (!this.pdfDocument) {
      return;
    }
    const viewer = this.viewer,
      pages = this._pages;
    if (this._scrollMode === ScrollMode.PAGE) {
      this.#ensurePageViewVisible();
    } else {
      viewer.textContent = "";
      if (this._spreadMode === SpreadMode.NONE) {
        for (const pageView of this._pages) {
          viewer.append(pageView.div);
        }
      } else {
        const parity = this._spreadMode - 1;
        let spread = null;
        for (let i = 0, ii = pages.length; i < ii; ++i) {
          if (spread === null) {
            spread = document.createElement("div");
            spread.className = "spread";
            viewer.append(spread);
          } else if (i % 2 === parity) {
            spread = spread.cloneNode(false);
            viewer.append(spread);
          }
          spread.append(pages[i].div);
        }
      }
    }
    if (!pageNumber) {
      return;
    }
    if (this._currentScaleValue && isNaN(this._currentScaleValue)) {
      this.#setScale(this._currentScaleValue, {
        noScroll: true
      });
    }
    this._setCurrentPageNumber(pageNumber, true);
    this.update();
  }
  _getPageAdvance(currentPageNumber, previous = false) {
    switch (this._scrollMode) {
      case ScrollMode.WRAPPED:
        {
          const {
              views
            } = this._getVisiblePages(),
            pageLayout = new Map();
          for (const {
            id,
            y,
            percent,
            widthPercent
          } of views) {
            if (percent === 0 || widthPercent < 100) {
              continue;
            }
            let yArray = pageLayout.get(y);
            if (!yArray) {
              pageLayout.set(y, yArray ||= []);
            }
            yArray.push(id);
          }
          for (const yArray of pageLayout.values()) {
            const currentIndex = yArray.indexOf(currentPageNumber);
            if (currentIndex === -1) {
              continue;
            }
            const numPages = yArray.length;
            if (numPages === 1) {
              break;
            }
            if (previous) {
              for (let i = currentIndex - 1, ii = 0; i >= ii; i--) {
                const currentId = yArray[i],
                  expectedId = yArray[i + 1] - 1;
                if (currentId < expectedId) {
                  return currentPageNumber - expectedId;
                }
              }
            } else {
              for (let i = currentIndex + 1, ii = numPages; i < ii; i++) {
                const currentId = yArray[i],
                  expectedId = yArray[i - 1] + 1;
                if (currentId > expectedId) {
                  return expectedId - currentPageNumber;
                }
              }
            }
            if (previous) {
              const firstId = yArray[0];
              if (firstId < currentPageNumber) {
                return currentPageNumber - firstId + 1;
              }
            } else {
              const lastId = yArray[numPages - 1];
              if (lastId > currentPageNumber) {
                return lastId - currentPageNumber + 1;
              }
            }
            break;
          }
          break;
        }
      case ScrollMode.HORIZONTAL:
        {
          break;
        }
      case ScrollMode.PAGE:
      case ScrollMode.VERTICAL:
        {
          if (this._spreadMode === SpreadMode.NONE) {
            break;
          }
          const parity = this._spreadMode - 1;
          if (previous && currentPageNumber % 2 !== parity) {
            break;
          } else if (!previous && currentPageNumber % 2 === parity) {
            break;
          }
          const {
              views
            } = this._getVisiblePages(),
            expectedId = previous ? currentPageNumber - 1 : currentPageNumber + 1;
          for (const {
            id,
            percent,
            widthPercent
          } of views) {
            if (id !== expectedId) {
              continue;
            }
            if (percent > 0 && widthPercent === 100) {
              return 2;
            }
            break;
          }
          break;
        }
    }
    return 1;
  }
  nextPage() {
    const currentPageNumber = this._currentPageNumber,
      pagesCount = this.pagesCount;
    if (currentPageNumber >= pagesCount) {
      return false;
    }
    const advance = this._getPageAdvance(currentPageNumber, false) || 1;
    this.currentPageNumber = Math.min(currentPageNumber + advance, pagesCount);
    return true;
  }
  previousPage() {
    const currentPageNumber = this._currentPageNumber;
    if (currentPageNumber <= 1) {
      return false;
    }
    const advance = this._getPageAdvance(currentPageNumber, true) || 1;
    this.currentPageNumber = Math.max(currentPageNumber - advance, 1);
    return true;
  }
  updateScale({
    drawingDelay,
    scaleFactor = null,
    steps = null,
    origin
  }) {
    if (steps === null && scaleFactor === null) {
      throw new Error("Invalid updateScale options: either `steps` or `scaleFactor` must be provided.");
    }
    if (!this.pdfDocument) {
      return;
    }
    let newScale = this._currentScale;
    if (scaleFactor > 0 && scaleFactor !== 1) {
      newScale = Math.round(newScale * scaleFactor * 100) / 100;
    } else if (steps) {
      const delta = steps > 0 ? DEFAULT_SCALE_DELTA : 1 / DEFAULT_SCALE_DELTA;
      const round = steps > 0 ? Math.ceil : Math.floor;
      steps = Math.abs(steps);
      do {
        newScale = round((newScale * delta).toFixed(2) * 10) / 10;
      } while (--steps > 0);
    }
    newScale = Math.max(MIN_SCALE, Math.min(MAX_SCALE, newScale));
    this.#setScale(newScale, {
      noScroll: false,
      drawingDelay,
      origin
    });
  }
  increaseScale(options = {}) {
    this.updateScale({
      ...options,
      steps: options.steps ?? 1
    });
  }
  decreaseScale(options = {}) {
    this.updateScale({
      ...options,
      steps: -(options.steps ?? 1)
    });
  }
  #updateContainerHeightCss(height = this.container.clientHeight) {
    if (height !== this.#previousContainerHeight) {
      this.#previousContainerHeight = height;
      docStyle.setProperty("--viewer-container-height", `${height}px`);
    }
  }
  #resizeObserverCallback(entries) {
    for (const entry of entries) {
      if (entry.target === this.container) {
        this.#updateContainerHeightCss(Math.floor(entry.borderBoxSize[0].blockSize));
        this.#containerTopLeft = null;
        break;
      }
    }
  }
  get containerTopLeft() {
    return this.#containerTopLeft ||= [this.container.offsetTop, this.container.offsetLeft];
  }
  #cleanupSwitchAnnotationEditorMode() {
    if (this.#onPageRenderedCallback) {
      this.eventBus._off("pagerendered", this.#onPageRenderedCallback);
      this.#onPageRenderedCallback = null;
    }
    if (this.#switchAnnotationEditorModeTimeoutId !== null) {
      clearTimeout(this.#switchAnnotationEditorModeTimeoutId);
      this.#switchAnnotationEditorModeTimeoutId = null;
    }
  }
  get annotationEditorMode() {
    return this.#annotationEditorUIManager ? this.#annotationEditorMode : AnnotationEditorType.DISABLE;
  }
  set annotationEditorMode({
    mode,
    editId = null,
    isFromKeyboard = false
  }) {
    if (!this.#annotationEditorUIManager) {
      throw new Error(`The AnnotationEditor is not enabled.`);
    }
    if (this.#annotationEditorMode === mode) {
      return;
    }
    if (!isValidAnnotationEditorMode(mode)) {
      throw new Error(`Invalid AnnotationEditor mode: ${mode}`);
    }
    if (!this.pdfDocument) {
      return;
    }
    if (mode === AnnotationEditorType.STAMP) {
      this.#mlManager?.loadModel("altText");
    }
    const {
      eventBus
    } = this;
    const updater = () => {
      this.#cleanupSwitchAnnotationEditorMode();
      this.#annotationEditorMode = mode;
      this.#annotationEditorUIManager.updateMode(mode, editId, isFromKeyboard);
      eventBus.dispatch("annotationeditormodechanged", {
        source: this,
        mode
      });
    };
    if (mode === AnnotationEditorType.NONE || this.#annotationEditorMode === AnnotationEditorType.NONE) {
      const isEditing = mode !== AnnotationEditorType.NONE;
      if (!isEditing) {
        this.pdfDocument.annotationStorage.resetModifiedIds();
      }
      for (const pageView of this._pages) {
        pageView.toggleEditingMode(isEditing);
      }
      const idsToRefresh = this.#switchToEditAnnotationMode();
      if (isEditing && idsToRefresh) {
        this.#cleanupSwitchAnnotationEditorMode();
        this.#onPageRenderedCallback = ({
          pageNumber
        }) => {
          idsToRefresh.delete(pageNumber);
          if (idsToRefresh.size === 0) {
            this.#switchAnnotationEditorModeTimeoutId = setTimeout(updater, 0);
          }
        };
        const {
          signal
        } = this.#eventAbortController;
        eventBus._on("pagerendered", this.#onPageRenderedCallback, {
          signal
        });
        return;
      }
    }
    updater();
  }
  refresh(noUpdate = false, updateArgs = Object.create(null)) {
    if (!this.pdfDocument) {
      return;
    }
    for (const pageView of this._pages) {
      pageView.update(updateArgs);
    }
    if (this.#scaleTimeoutId !== null) {
      clearTimeout(this.#scaleTimeoutId);
      this.#scaleTimeoutId = null;
    }
    if (!noUpdate) {
      this.update();
    }
  }
}

;// CONCATENATED MODULE: ./web/secondary_toolbar.js


class SecondaryToolbar {
  #opts;
  constructor(options, eventBus) {
    this.#opts = options;
    const buttons = [{
      element: options.presentationModeButton,
      eventName: "presentationmode",
      close: true
    }, {
      element: options.printButton,
      eventName: "print",
      close: true
    }, {
      element: options.downloadButton,
      eventName: "download",
      close: true
    }, {
      element: options.viewBookmarkButton,
      eventName: null,
      close: true
    }, {
      element: options.firstPageButton,
      eventName: "firstpage",
      close: true
    }, {
      element: options.lastPageButton,
      eventName: "lastpage",
      close: true
    }, {
      element: options.pageRotateCwButton,
      eventName: "rotatecw",
      close: false
    }, {
      element: options.pageRotateCcwButton,
      eventName: "rotateccw",
      close: false
    }, {
      element: options.cursorSelectToolButton,
      eventName: "switchcursortool",
      eventDetails: {
        tool: CursorTool.SELECT
      },
      close: true
    }, {
      element: options.cursorHandToolButton,
      eventName: "switchcursortool",
      eventDetails: {
        tool: CursorTool.HAND
      },
      close: true
    }, {
      element: options.scrollPageButton,
      eventName: "switchscrollmode",
      eventDetails: {
        mode: ScrollMode.PAGE
      },
      close: true
    }, {
      element: options.scrollVerticalButton,
      eventName: "switchscrollmode",
      eventDetails: {
        mode: ScrollMode.VERTICAL
      },
      close: true
    }, {
      element: options.scrollHorizontalButton,
      eventName: "switchscrollmode",
      eventDetails: {
        mode: ScrollMode.HORIZONTAL
      },
      close: true
    }, {
      element: options.scrollWrappedButton,
      eventName: "switchscrollmode",
      eventDetails: {
        mode: ScrollMode.WRAPPED
      },
      close: true
    }, {
      element: options.spreadNoneButton,
      eventName: "switchspreadmode",
      eventDetails: {
        mode: SpreadMode.NONE
      },
      close: true
    }, {
      element: options.spreadOddButton,
      eventName: "switchspreadmode",
      eventDetails: {
        mode: SpreadMode.ODD
      },
      close: true
    }, {
      element: options.spreadEvenButton,
      eventName: "switchspreadmode",
      eventDetails: {
        mode: SpreadMode.EVEN
      },
      close: true
    }, {
      element: options.imageAltTextSettingsButton,
      eventName: "imagealttextsettings",
      close: true
    }, {
      element: options.documentPropertiesButton,
      eventName: "documentproperties",
      close: true
    }];
    this.eventBus = eventBus;
    this.opened = false;
    this.#bindListeners(buttons);
    this.reset();
  }
  get isOpen() {
    return this.opened;
  }
  setPageNumber(pageNumber) {
    this.pageNumber = pageNumber;
    this.#updateUIState();
  }
  setPagesCount(pagesCount) {
    this.pagesCount = pagesCount;
    this.#updateUIState();
  }
  reset() {
    this.pageNumber = 0;
    this.pagesCount = 0;
    this.#updateUIState();
    this.eventBus.dispatch("switchcursortool", {
      source: this,
      reset: true
    });
    this.#scrollModeChanged({
      mode: ScrollMode.VERTICAL
    });
    this.#spreadModeChanged({
      mode: SpreadMode.NONE
    });
  }
  #updateUIState() {
    const {
      firstPageButton,
      lastPageButton,
      pageRotateCwButton,
      pageRotateCcwButton
    } = this.#opts;
    firstPageButton.disabled = this.pageNumber <= 1;
    lastPageButton.disabled = this.pageNumber >= this.pagesCount;
    pageRotateCwButton.disabled = this.pagesCount === 0;
    pageRotateCcwButton.disabled = this.pagesCount === 0;
  }
  #bindListeners(buttons) {
    const {
      eventBus
    } = this;
    const {
      toggleButton
    } = this.#opts;
    toggleButton.addEventListener("click", this.toggle.bind(this));
    for (const {
      element,
      eventName,
      close,
      eventDetails
    } of buttons) {
      element.addEventListener("click", evt => {
        if (eventName !== null) {
          eventBus.dispatch(eventName, {
            source: this,
            ...eventDetails
          });
        }
        if (close) {
          this.close();
        }
        eventBus.dispatch("reporttelemetry", {
          source: this,
          details: {
            type: "buttons",
            data: {
              id: element.id
            }
          }
        });
      });
    }
    eventBus._on("cursortoolchanged", this.#cursorToolChanged.bind(this));
    eventBus._on("scrollmodechanged", this.#scrollModeChanged.bind(this));
    eventBus._on("spreadmodechanged", this.#spreadModeChanged.bind(this));
  }
  #cursorToolChanged({
    tool
  }) {
    const {
      cursorSelectToolButton,
      cursorHandToolButton
    } = this.#opts;
    toggleCheckedBtn(cursorSelectToolButton, tool === CursorTool.SELECT);
    toggleCheckedBtn(cursorHandToolButton, tool === CursorTool.HAND);
  }
  #scrollModeChanged({
    mode
  }) {
    const {
      scrollPageButton,
      scrollVerticalButton,
      scrollHorizontalButton,
      scrollWrappedButton,
      spreadNoneButton,
      spreadOddButton,
      spreadEvenButton
    } = this.#opts;
    toggleCheckedBtn(scrollPageButton, mode === ScrollMode.PAGE);
    toggleCheckedBtn(scrollVerticalButton, mode === ScrollMode.VERTICAL);
    toggleCheckedBtn(scrollHorizontalButton, mode === ScrollMode.HORIZONTAL);
    toggleCheckedBtn(scrollWrappedButton, mode === ScrollMode.WRAPPED);
    const forceScrollModePage = this.pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE;
    scrollPageButton.disabled = forceScrollModePage;
    scrollVerticalButton.disabled = forceScrollModePage;
    scrollHorizontalButton.disabled = forceScrollModePage;
    scrollWrappedButton.disabled = forceScrollModePage;
    const isHorizontal = mode === ScrollMode.HORIZONTAL;
    spreadNoneButton.disabled = isHorizontal;
    spreadOddButton.disabled = isHorizontal;
    spreadEvenButton.disabled = isHorizontal;
  }
  #spreadModeChanged({
    mode
  }) {
    const {
      spreadNoneButton,
      spreadOddButton,
      spreadEvenButton
    } = this.#opts;
    toggleCheckedBtn(spreadNoneButton, mode === SpreadMode.NONE);
    toggleCheckedBtn(spreadOddButton, mode === SpreadMode.ODD);
    toggleCheckedBtn(spreadEvenButton, mode === SpreadMode.EVEN);
  }
  open() {
    if (this.opened) {
      return;
    }
    this.opened = true;
    const {
      toggleButton,
      toolbar
    } = this.#opts;
    toggleExpandedBtn(toggleButton, true, toolbar);
  }
  close() {
    if (!this.opened) {
      return;
    }
    this.opened = false;
    const {
      toggleButton,
      toolbar
    } = this.#opts;
    toggleExpandedBtn(toggleButton, false, toolbar);
  }
  toggle() {
    if (this.opened) {
      this.close();
    } else {
      this.open();
    }
  }
}

;// CONCATENATED MODULE: ./web/toolbar.js


class Toolbar {
  #opts;
  constructor(options, eventBus, toolbarDensity = 0) {
    this.#opts = options;
    this.eventBus = eventBus;
    const buttons = [{
      element: options.previous,
      eventName: "previouspage"
    }, {
      element: options.next,
      eventName: "nextpage"
    }, {
      element: options.zoomIn,
      eventName: "zoomin"
    }, {
      element: options.zoomOut,
      eventName: "zoomout"
    }, {
      element: options.print,
      eventName: "print"
    }, {
      element: options.download,
      eventName: "download"
    }, {
      element: options.editorFreeTextButton,
      eventName: "switchannotationeditormode",
      eventDetails: {
        get mode() {
          const {
            classList
          } = options.editorFreeTextButton;
          return classList.contains("toggled") ? AnnotationEditorType.NONE : AnnotationEditorType.FREETEXT;
        }
      }
    }, {
      element: options.editorHighlightButton,
      eventName: "switchannotationeditormode",
      eventDetails: {
        get mode() {
          const {
            classList
          } = options.editorHighlightButton;
          return classList.contains("toggled") ? AnnotationEditorType.NONE : AnnotationEditorType.HIGHLIGHT;
        }
      }
    }, {
      element: options.editorInkButton,
      eventName: "switchannotationeditormode",
      eventDetails: {
        get mode() {
          const {
            classList
          } = options.editorInkButton;
          return classList.contains("toggled") ? AnnotationEditorType.NONE : AnnotationEditorType.INK;
        }
      }
    }, {
      element: options.editorStampButton,
      eventName: "switchannotationeditormode",
      eventDetails: {
        get mode() {
          const {
            classList
          } = options.editorStampButton;
          return classList.contains("toggled") ? AnnotationEditorType.NONE : AnnotationEditorType.STAMP;
        }
      },
      telemetry: {
        type: "editing",
        data: {
          action: "pdfjs.image.icon_click"
        }
      }
    }];
    this.#bindListeners(buttons);
    this.#updateToolbarDensity({
      value: toolbarDensity
    });
    this.reset();
  }
  #updateToolbarDensity() {}
  #setAnnotationEditorUIManager(uiManager, parentContainer) {
    const colorPicker = new ColorPicker({
      uiManager
    });
    uiManager.setMainHighlightColorPicker(colorPicker);
    parentContainer.append(colorPicker.renderMainDropdown());
  }
  setPageNumber(pageNumber, pageLabel) {
    this.pageNumber = pageNumber;
    this.pageLabel = pageLabel;
    this.#updateUIState(false);
  }
  setPagesCount(pagesCount, hasPageLabels) {
    this.pagesCount = pagesCount;
    this.hasPageLabels = hasPageLabels;
    this.#updateUIState(true);
  }
  setPageScale(pageScaleValue, pageScale) {
    this.pageScaleValue = (pageScaleValue || pageScale).toString();
    this.pageScale = pageScale;
    this.#updateUIState(false);
  }
  reset() {
    this.pageNumber = 0;
    this.pageLabel = null;
    this.hasPageLabels = false;
    this.pagesCount = 0;
    this.pageScaleValue = DEFAULT_SCALE_VALUE;
    this.pageScale = DEFAULT_SCALE;
    this.#updateUIState(true);
    this.updateLoadingIndicatorState();
    this.#editorModeChanged({
      mode: AnnotationEditorType.DISABLE
    });
  }
  #bindListeners(buttons) {
    const {
      eventBus
    } = this;
    const {
      editorHighlightColorPicker,
      editorHighlightButton,
      pageNumber,
      scaleSelect
    } = this.#opts;
    const self = this;
    for (const {
      element,
      eventName,
      eventDetails,
      telemetry
    } of buttons) {
      element.addEventListener("click", evt => {
        if (eventName !== null) {
          eventBus.dispatch(eventName, {
            source: this,
            ...eventDetails,
            isFromKeyboard: evt.detail === 0
          });
        }
        if (telemetry) {
          eventBus.dispatch("reporttelemetry", {
            source: this,
            details: telemetry
          });
        }
      });
    }
    pageNumber.addEventListener("click", function () {
      this.select();
    });
    pageNumber.addEventListener("change", function () {
      eventBus.dispatch("pagenumberchanged", {
        source: self,
        value: this.value
      });
    });
    scaleSelect.addEventListener("change", function () {
      if (this.value === "custom") {
        return;
      }
      eventBus.dispatch("scalechanged", {
        source: self,
        value: this.value
      });
    });
    scaleSelect.addEventListener("click", function ({
      target
    }) {
      if (this.value === self.pageScaleValue && target.tagName.toUpperCase() === "OPTION") {
        this.blur();
      }
    });
    scaleSelect.oncontextmenu = noContextMenu;
    eventBus._on("annotationeditormodechanged", this.#editorModeChanged.bind(this));
    eventBus._on("showannotationeditorui", ({
      mode
    }) => {
      switch (mode) {
        case AnnotationEditorType.HIGHLIGHT:
          editorHighlightButton.click();
          break;
      }
    });
    eventBus._on("toolbardensity", this.#updateToolbarDensity.bind(this));
    if (editorHighlightColorPicker) {
      eventBus._on("annotationeditoruimanager", ({
        uiManager
      }) => {
        this.#setAnnotationEditorUIManager(uiManager, editorHighlightColorPicker);
      }, {
        once: true
      });
    }
  }
  #editorModeChanged({
    mode
  }) {
    const {
      editorFreeTextButton,
      editorFreeTextParamsToolbar,
      editorHighlightButton,
      editorHighlightParamsToolbar,
      editorInkButton,
      editorInkParamsToolbar,
      editorStampButton,
      editorStampParamsToolbar
    } = this.#opts;
    toggleCheckedBtn(editorFreeTextButton, mode === AnnotationEditorType.FREETEXT, editorFreeTextParamsToolbar);
    toggleCheckedBtn(editorHighlightButton, mode === AnnotationEditorType.HIGHLIGHT, editorHighlightParamsToolbar);
    toggleCheckedBtn(editorInkButton, mode === AnnotationEditorType.INK, editorInkParamsToolbar);
    toggleCheckedBtn(editorStampButton, mode === AnnotationEditorType.STAMP, editorStampParamsToolbar);
    const isDisable = mode === AnnotationEditorType.DISABLE;
    editorFreeTextButton.disabled = isDisable;
    editorHighlightButton.disabled = isDisable;
    editorInkButton.disabled = isDisable;
    editorStampButton.disabled = isDisable;
  }
  #updateUIState(resetNumPages = false) {
    const {
      pageNumber,
      pagesCount,
      pageScaleValue,
      pageScale
    } = this;
    const opts = this.#opts;
    if (resetNumPages) {
      if (this.hasPageLabels) {
        opts.pageNumber.type = "text";
        opts.numPages.setAttribute("data-l10n-id", "pdfjs-page-of-pages");
      } else {
        opts.pageNumber.type = "number";
        opts.numPages.setAttribute("data-l10n-id", "pdfjs-of-pages");
        opts.numPages.setAttribute("data-l10n-args", JSON.stringify({
          pagesCount
        }));
      }
      opts.pageNumber.max = pagesCount;
    }
    if (this.hasPageLabels) {
      opts.pageNumber.value = this.pageLabel;
      opts.numPages.setAttribute("data-l10n-args", JSON.stringify({
        pageNumber,
        pagesCount
      }));
    } else {
      opts.pageNumber.value = pageNumber;
    }
    opts.previous.disabled = pageNumber <= 1;
    opts.next.disabled = pageNumber >= pagesCount;
    opts.zoomOut.disabled = pageScale <= MIN_SCALE;
    opts.zoomIn.disabled = pageScale >= MAX_SCALE;
    let predefinedValueFound = false;
    for (const option of opts.scaleSelect.options) {
      if (option.value !== pageScaleValue) {
        option.selected = false;
        continue;
      }
      option.selected = true;
      predefinedValueFound = true;
    }
    if (!predefinedValueFound) {
      opts.customScaleOption.selected = true;
      opts.customScaleOption.setAttribute("data-l10n-args", JSON.stringify({
        scale: Math.round(pageScale * 10000) / 100
      }));
    }
  }
  updateLoadingIndicatorState(loading = false) {
    const {
      pageNumber
    } = this.#opts;
    pageNumber.classList.toggle("loading", loading);
  }
}

;// CONCATENATED MODULE: ./web/view_history.js
const DEFAULT_VIEW_HISTORY_CACHE_SIZE = 20;
class ViewHistory {
  constructor(fingerprint, cacheSize = DEFAULT_VIEW_HISTORY_CACHE_SIZE) {
    this.fingerprint = fingerprint;
    this.cacheSize = cacheSize;
    this._initializedPromise = this._readFromStorage().then(databaseStr => {
      const database = JSON.parse(databaseStr || "{}");
      let index = -1;
      if (!Array.isArray(database.files)) {
        database.files = [];
      } else {
        while (database.files.length >= this.cacheSize) {
          database.files.shift();
        }
        for (let i = 0, ii = database.files.length; i < ii; i++) {
          const branch = database.files[i];
          if (branch.fingerprint === this.fingerprint) {
            index = i;
            break;
          }
        }
      }
      if (index === -1) {
        index = database.files.push({
          fingerprint: this.fingerprint
        }) - 1;
      }
      this.file = database.files[index];
      this.database = database;
    });
  }
  async _writeToStorage() {
    const databaseStr = JSON.stringify(this.database);
    sessionStorage.setItem("pdfjs.history", databaseStr);
  }
  async _readFromStorage() {
    return sessionStorage.getItem("pdfjs.history");
  }
  async set(name, val) {
    await this._initializedPromise;
    this.file[name] = val;
    return this._writeToStorage();
  }
  async setMultiple(properties) {
    await this._initializedPromise;
    for (const name in properties) {
      this.file[name] = properties[name];
    }
    return this._writeToStorage();
  }
  async get(name, defaultValue) {
    await this._initializedPromise;
    const val = this.file[name];
    return val !== undefined ? val : defaultValue;
  }
  async getMultiple(properties) {
    await this._initializedPromise;
    const values = Object.create(null);
    for (const name in properties) {
      const val = this.file[name];
      values[name] = val !== undefined ? val : properties[name];
    }
    return values;
  }
}

;// CONCATENATED MODULE: ./web/app.js
































const FORCE_PAGES_LOADED_TIMEOUT = 10000;
const ViewOnLoad = {
  UNKNOWN: -1,
  PREVIOUS: 0,
  INITIAL: 1
};
const PDFViewerApplication = {
  initialBookmark: document.location.hash.substring(1),
  _initializedCapability: {
    ...Promise.withResolvers(),
    settled: false
  },
  appConfig: null,
  pdfDocument: null,
  pdfLoadingTask: null,
  printService: null,
  pdfViewer: null,
  pdfThumbnailViewer: null,
  pdfRenderingQueue: null,
  pdfPresentationMode: null,
  pdfDocumentProperties: null,
  pdfLinkService: null,
  pdfHistory: null,
  pdfSidebar: null,
  pdfOutlineViewer: null,
  pdfAttachmentViewer: null,
  pdfLayerViewer: null,
  pdfCursorTools: null,
  pdfScriptingManager: null,
  store: null,
  downloadManager: null,
  overlayManager: null,
  preferences: new Preferences(),
  toolbar: null,
  secondaryToolbar: null,
  eventBus: null,
  l10n: null,
  annotationEditorParams: null,
  imageAltTextSettings: null,
  isInitialViewSet: false,
  isViewerEmbedded: window.parent !== window,
  url: "",
  baseUrl: "",
  mlManager: null,
  _downloadUrl: "",
  _eventBusAbortController: null,
  _windowAbortController: null,
  _globalAbortController: new AbortController(),
  documentInfo: null,
  metadata: null,
  _contentDispositionFilename: null,
  _contentLength: null,
  _saveInProgress: false,
  _wheelUnusedTicks: 0,
  _wheelUnusedFactor: 1,
  _touchUnusedTicks: 0,
  _touchUnusedFactor: 1,
  _PDFBug: null,
  _hasAnnotationEditors: false,
  _title: document.title,
  _printAnnotationStoragePromise: null,
  _touchInfo: null,
  _isCtrlKeyDown: false,
  _caretBrowsing: null,
  _isScrolling: false,
  async initialize(appConfig) {
    this.appConfig = appConfig;
    try {
      await this.preferences.initializedPromise;
    } catch (ex) {
      console.error(`initialize: "${ex.message}".`);
    }
    if (AppOptions.get("pdfBugEnabled")) {
      await this._parseHashParams();
    }
    if (AppOptions.get("enableAltText")) {
      this.mlManager = new MLManager({
        enableGuessAltText: AppOptions.get("enableGuessAltText"),
        enableAltTextModelDownload: AppOptions.get("enableAltTextModelDownload"),
        altTextLearnMoreUrl: AppOptions.get("altTextLearnMoreUrl")
      });
    }
    this.l10n = await this.externalServices.createL10n();
    document.getElementsByTagName("html")[0].dir = this.l10n.getDirection();
    if (this.isViewerEmbedded && AppOptions.get("externalLinkTarget") === LinkTarget.NONE) {
      AppOptions.set("externalLinkTarget", LinkTarget.TOP);
    }
    await this._initializeViewerComponents();
    this.bindEvents();
    this.bindWindowEvents();
    this._initializedCapability.settled = true;
    this._initializedCapability.resolve();
  },
  async _parseHashParams() {
    const hash = document.location.hash.substring(1);
    if (!hash) {
      return;
    }
    const {
        mainContainer,
        viewerContainer
      } = this.appConfig,
      params = parseQueryString(hash);
    const loadPDFBug = async () => {
      if (this._PDFBug) {
        return;
      }
      const {
        PDFBug
      } = await import( /*webpackIgnore: true*/AppOptions.get("debuggerSrc"));
      this._PDFBug = PDFBug;
    };
    if (params.get("disableworker") === "true") {
      try {
        GlobalWorkerOptions.workerSrc ||= AppOptions.get("workerSrc");
        await import( /*webpackIgnore: true*/PDFWorker.workerSrc);
      } catch (ex) {
        console.error(`_parseHashParams: "${ex.message}".`);
      }
    }
    if (params.has("textlayer")) {
      switch (params.get("textlayer")) {
        case "off":
          AppOptions.set("textLayerMode", TextLayerMode.DISABLE);
          break;
        case "visible":
        case "shadow":
        case "hover":
          viewerContainer.classList.add(`textLayer-${params.get("textlayer")}`);
          try {
            await loadPDFBug();
            this._PDFBug.loadCSS();
          } catch (ex) {
            console.error(`_parseHashParams: "${ex.message}".`);
          }
          break;
      }
    }
    if (params.has("pdfbug")) {
      AppOptions.setAll({
        pdfBug: true,
        fontExtraProperties: true
      });
      const enabled = params.get("pdfbug").split(",");
      try {
        await loadPDFBug();
        this._PDFBug.init(mainContainer, enabled);
      } catch (ex) {
        console.error(`_parseHashParams: "${ex.message}".`);
      }
    }
    const opts = {
      disableAutoFetch: x => x === "true",
      disableFontFace: x => x === "true",
      disableHistory: x => x === "true",
      disableRange: x => x === "true",
      disableStream: x => x === "true",
      verbosity: x => x | 0
    };
    for (const name in opts) {
      const check = opts[name],
        key = name.toLowerCase();
      if (params.has(key)) {
        AppOptions.set(name, check(params.get(key)));
      }
    }
  },
  async _initializeViewerComponents() {
    const {
      appConfig,
      externalServices,
      l10n
    } = this;
    let eventBus;
    eventBus = AppOptions.eventBus = new FirefoxEventBus(AppOptions.get("allowedGlobalEvents"), externalServices, AppOptions.get("isInAutomation"));
    this.mlManager?.setEventBus(eventBus, this._globalAbortController.signal);
    this.eventBus = eventBus;
    this.overlayManager = new OverlayManager();
    const pdfRenderingQueue = new PDFRenderingQueue();
    pdfRenderingQueue.onIdle = this._cleanup.bind(this);
    this.pdfRenderingQueue = pdfRenderingQueue;
    const pdfLinkService = new PDFLinkService({
      eventBus,
      externalLinkTarget: AppOptions.get("externalLinkTarget"),
      externalLinkRel: AppOptions.get("externalLinkRel"),
      ignoreDestinationZoom: AppOptions.get("ignoreDestinationZoom")
    });
    this.pdfLinkService = pdfLinkService;
    const downloadManager = this.downloadManager = new DownloadManager();
    const findController = new PDFFindController({
      linkService: pdfLinkService,
      eventBus,
      updateMatchesCountOnProgress: true
    });
    this.findController = findController;
    const pdfScriptingManager = new PDFScriptingManager({
      eventBus,
      externalServices,
      docProperties: this._scriptingDocProperties.bind(this)
    });
    this.pdfScriptingManager = pdfScriptingManager;
    const container = appConfig.mainContainer,
      viewer = appConfig.viewerContainer;
    const annotationEditorMode = AppOptions.get("annotationEditorMode");
    const pageColors = AppOptions.get("forcePageColors") || window.matchMedia("(forced-colors: active)").matches ? {
      background: AppOptions.get("pageColorsBackground"),
      foreground: AppOptions.get("pageColorsForeground")
    } : null;
    let altTextManager;
    if (AppOptions.get("enableUpdatedAddImage")) {
      altTextManager = appConfig.newAltTextDialog ? new NewAltTextManager(appConfig.newAltTextDialog, this.overlayManager, eventBus) : null;
    } else {
      altTextManager = appConfig.altTextDialog ? new AltTextManager(appConfig.altTextDialog, container, this.overlayManager, eventBus) : null;
    }
    const enableHWA = AppOptions.get("enableHWA");
    const pdfViewer = new PDFViewer({
      container,
      viewer,
      eventBus,
      renderingQueue: pdfRenderingQueue,
      linkService: pdfLinkService,
      downloadManager,
      altTextManager,
      findController,
      scriptingManager: AppOptions.get("enableScripting") && pdfScriptingManager,
      l10n,
      textLayerMode: AppOptions.get("textLayerMode"),
      annotationMode: AppOptions.get("annotationMode"),
      annotationEditorMode,
      annotationEditorHighlightColors: AppOptions.get("highlightEditorColors"),
      enableHighlightFloatingButton: AppOptions.get("enableHighlightFloatingButton"),
      enableUpdatedAddImage: AppOptions.get("enableUpdatedAddImage"),
      enableNewAltTextWhenAddingImage: AppOptions.get("enableNewAltTextWhenAddingImage"),
      imageResourcesPath: AppOptions.get("imageResourcesPath"),
      enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
      maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
      enablePermissions: AppOptions.get("enablePermissions"),
      pageColors,
      mlManager: this.mlManager,
      abortSignal: this._globalAbortController.signal,
      enableHWA
    });
    this.pdfViewer = pdfViewer;
    pdfRenderingQueue.setViewer(pdfViewer);
    pdfLinkService.setViewer(pdfViewer);
    pdfScriptingManager.setViewer(pdfViewer);
    if (appConfig.sidebar?.thumbnailView) {
      this.pdfThumbnailViewer = new PDFThumbnailViewer({
        container: appConfig.sidebar.thumbnailView,
        eventBus,
        renderingQueue: pdfRenderingQueue,
        linkService: pdfLinkService,
        pageColors,
        abortSignal: this._globalAbortController.signal,
        enableHWA
      });
      pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
    }
    if (!this.isViewerEmbedded && !AppOptions.get("disableHistory")) {
      this.pdfHistory = new PDFHistory({
        linkService: pdfLinkService,
        eventBus
      });
      pdfLinkService.setHistory(this.pdfHistory);
    }
    if (!this.supportsIntegratedFind && appConfig.findBar) {
      this.findBar = new PDFFindBar(appConfig.findBar, eventBus);
    }
    if (appConfig.annotationEditorParams) {
      if (annotationEditorMode !== AnnotationEditorType.DISABLE) {
        this.annotationEditorParams = new AnnotationEditorParams(appConfig.annotationEditorParams, eventBus);
      } else {
        for (const id of ["editorModeButtons", "editorModeSeparator"]) {
          document.getElementById(id)?.classList.add("hidden");
        }
      }
    }
    if (this.mlManager && appConfig.secondaryToolbar?.imageAltTextSettingsButton) {
      this.imageAltTextSettings = new ImageAltTextSettings(appConfig.altTextSettingsDialog, this.overlayManager, eventBus, this.mlManager);
    }
    if (appConfig.documentProperties) {
      this.pdfDocumentProperties = new PDFDocumentProperties(appConfig.documentProperties, this.overlayManager, eventBus, l10n, () => this._docFilename);
    }
    if (appConfig.secondaryToolbar?.cursorHandToolButton) {
      this.pdfCursorTools = new PDFCursorTools({
        container,
        eventBus,
        cursorToolOnLoad: AppOptions.get("cursorToolOnLoad")
      });
    }
    if (appConfig.toolbar) {
      this.toolbar = new Toolbar(appConfig.toolbar, eventBus, AppOptions.get("toolbarDensity"));
    }
    if (appConfig.secondaryToolbar) {
      if (AppOptions.get("enableAltText")) {
        appConfig.secondaryToolbar.imageAltTextSettingsButton?.classList.remove("hidden");
        appConfig.secondaryToolbar.imageAltTextSettingsSeparator?.classList.remove("hidden");
      }
      this.secondaryToolbar = new SecondaryToolbar(appConfig.secondaryToolbar, eventBus);
    }
    if (this.supportsFullscreen && appConfig.secondaryToolbar?.presentationModeButton) {
      this.pdfPresentationMode = new PDFPresentationMode({
        container,
        pdfViewer,
        eventBus
      });
    }
    if (appConfig.passwordOverlay) {
      this.passwordPrompt = new PasswordPrompt(appConfig.passwordOverlay, this.overlayManager, this.isViewerEmbedded);
    }
    if (appConfig.sidebar?.outlineView) {
      this.pdfOutlineViewer = new PDFOutlineViewer({
        container: appConfig.sidebar.outlineView,
        eventBus,
        l10n,
        linkService: pdfLinkService,
        downloadManager
      });
    }
    if (appConfig.sidebar?.attachmentsView) {
      this.pdfAttachmentViewer = new PDFAttachmentViewer({
        container: appConfig.sidebar.attachmentsView,
        eventBus,
        l10n,
        downloadManager
      });
    }
    if (appConfig.sidebar?.layersView) {
      this.pdfLayerViewer = new PDFLayerViewer({
        container: appConfig.sidebar.layersView,
        eventBus,
        l10n
      });
    }
    if (appConfig.sidebar) {
      this.pdfSidebar = new PDFSidebar({
        elements: appConfig.sidebar,
        eventBus,
        l10n
      });
      this.pdfSidebar.onToggled = this.forceRendering.bind(this);
      this.pdfSidebar.onUpdateThumbnails = () => {
        for (const pageView of pdfViewer.getCachedPageViews()) {
          if (pageView.renderingState === RenderingStates.FINISHED) {
            this.pdfThumbnailViewer.getThumbnail(pageView.id - 1)?.setImage(pageView);
          }
        }
        this.pdfThumbnailViewer.scrollThumbnailIntoView(pdfViewer.currentPageNumber);
      };
    }
  },
  async run(config) {
    await this.initialize(config);
    const {
      appConfig,
      eventBus
    } = this;
    let file;
    file = window.location.href;
    if (!AppOptions.get("supportsDocumentFonts")) {
      AppOptions.set("disableFontFace", true);
      this.l10n.get("pdfjs-web-fonts-disabled").then(msg => {
        console.warn(msg);
      });
    }
    if (!this.supportsPrinting) {
      appConfig.toolbar?.print?.classList.add("hidden");
      appConfig.secondaryToolbar?.printButton.classList.add("hidden");
    }
    if (!this.supportsFullscreen) {
      appConfig.secondaryToolbar?.presentationModeButton.classList.add("hidden");
    }
    if (this.supportsIntegratedFind) {
      appConfig.findBar?.toggleButton?.classList.add("hidden");
    }
    this.setTitleUsingUrl(file, file);
    this.externalServices.initPassiveLoading();
  },
  get externalServices() {
    return shadow(this, "externalServices", new ExternalServices());
  },
  get initialized() {
    return this._initializedCapability.settled;
  },
  get initializedPromise() {
    return this._initializedCapability.promise;
  },
  updateZoom(steps, scaleFactor, origin) {
    if (this.pdfViewer.isInPresentationMode) {
      return;
    }
    this.pdfViewer.updateScale({
      drawingDelay: AppOptions.get("defaultZoomDelay"),
      steps,
      scaleFactor,
      origin
    });
  },
  zoomIn() {
    this.updateZoom(1);
  },
  zoomOut() {
    this.updateZoom(-1);
  },
  zoomReset() {
    if (this.pdfViewer.isInPresentationMode) {
      return;
    }
    this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
  },
  get pagesCount() {
    return this.pdfDocument ? this.pdfDocument.numPages : 0;
  },
  get page() {
    return this.pdfViewer.currentPageNumber;
  },
  set page(val) {
    this.pdfViewer.currentPageNumber = val;
  },
  get supportsPrinting() {
    return PDFPrintServiceFactory.supportsPrinting;
  },
  get supportsFullscreen() {
    return shadow(this, "supportsFullscreen", document.fullscreenEnabled);
  },
  get supportsPinchToZoom() {
    return shadow(this, "supportsPinchToZoom", AppOptions.get("supportsPinchToZoom"));
  },
  get supportsIntegratedFind() {
    return shadow(this, "supportsIntegratedFind", AppOptions.get("supportsIntegratedFind"));
  },
  get loadingBar() {
    const barElement = document.getElementById("loadingBar");
    const bar = barElement ? new ProgressBar(barElement) : null;
    return shadow(this, "loadingBar", bar);
  },
  get supportsMouseWheelZoomCtrlKey() {
    return shadow(this, "supportsMouseWheelZoomCtrlKey", AppOptions.get("supportsMouseWheelZoomCtrlKey"));
  },
  get supportsMouseWheelZoomMetaKey() {
    return shadow(this, "supportsMouseWheelZoomMetaKey", AppOptions.get("supportsMouseWheelZoomMetaKey"));
  },
  get supportsCaretBrowsingMode() {
    return AppOptions.get("supportsCaretBrowsingMode");
  },
  moveCaret(isUp, select) {
    this._caretBrowsing ||= new CaretBrowsingMode(this.appConfig.mainContainer, this.appConfig.viewerContainer, this.appConfig.toolbar?.container);
    this._caretBrowsing.moveCaret(isUp, select);
  },
  setTitleUsingUrl(url = "", downloadUrl = null) {
    this.url = url;
    this.baseUrl = url.split("#", 1)[0];
    if (downloadUrl) {
      this._downloadUrl = downloadUrl === url ? this.baseUrl : downloadUrl.split("#", 1)[0];
    }
    if (isDataScheme(url)) {
      this._hideViewBookmark();
    } else {
      AppOptions.set("docBaseUrl", this.baseUrl);
    }
    let title = getPdfFilenameFromUrl(url, "");
    if (!title) {
      try {
        title = decodeURIComponent(getFilenameFromUrl(url));
      } catch {}
    }
    this.setTitle(title || url);
  },
  setTitle(title = this._title) {
    this._title = title;
    if (this.isViewerEmbedded) {
      return;
    }
    const editorIndicator = this._hasAnnotationEditors && !this.pdfRenderingQueue.printing;
    document.title = `${editorIndicator ? "* " : ""}${title}`;
  },
  get _docFilename() {
    return this._contentDispositionFilename || getPdfFilenameFromUrl(this.url);
  },
  _hideViewBookmark() {
    const {
      secondaryToolbar
    } = this.appConfig;
    secondaryToolbar?.viewBookmarkButton.classList.add("hidden");
    if (secondaryToolbar?.presentationModeButton.classList.contains("hidden")) {
      document.getElementById("viewBookmarkSeparator")?.classList.add("hidden");
    }
  },
  async close() {
    this._unblockDocumentLoadEvent();
    this._hideViewBookmark();
    if (!this.pdfLoadingTask) {
      return;
    }
    const promises = [];
    promises.push(this.pdfLoadingTask.destroy());
    this.pdfLoadingTask = null;
    if (this.pdfDocument) {
      this.pdfDocument = null;
      this.pdfThumbnailViewer?.setDocument(null);
      this.pdfViewer.setDocument(null);
      this.pdfLinkService.setDocument(null);
      this.pdfDocumentProperties?.setDocument(null);
    }
    this.pdfLinkService.externalLinkEnabled = true;
    this.store = null;
    this.isInitialViewSet = false;
    this.url = "";
    this.baseUrl = "";
    this._downloadUrl = "";
    this.documentInfo = null;
    this.metadata = null;
    this._contentDispositionFilename = null;
    this._contentLength = null;
    this._saveInProgress = false;
    this._hasAnnotationEditors = false;
    promises.push(this.pdfScriptingManager.destroyPromise, this.passwordPrompt.close());
    this.setTitle();
    this.pdfSidebar?.reset();
    this.pdfOutlineViewer?.reset();
    this.pdfAttachmentViewer?.reset();
    this.pdfLayerViewer?.reset();
    this.pdfHistory?.reset();
    this.findBar?.reset();
    this.toolbar?.reset();
    this.secondaryToolbar?.reset();
    this._PDFBug?.cleanup();
    await Promise.all(promises);
  },
  async open(args) {
    if (this.pdfLoadingTask) {
      await this.close();
    }
    const workerParams = AppOptions.getAll(OptionKind.WORKER);
    Object.assign(GlobalWorkerOptions, workerParams);
    if (args.data && isPdfFile(args.filename)) {
      this._contentDispositionFilename = args.filename;
    }
    const apiParams = AppOptions.getAll(OptionKind.API);
    const loadingTask = getDocument({
      ...apiParams,
      ...args
    });
    this.pdfLoadingTask = loadingTask;
    loadingTask.onPassword = (updateCallback, reason) => {
      if (this.isViewerEmbedded) {
        this._unblockDocumentLoadEvent();
      }
      this.pdfLinkService.externalLinkEnabled = false;
      this.passwordPrompt.setUpdateCallback(updateCallback, reason);
      this.passwordPrompt.open();
    };
    loadingTask.onProgress = ({
      loaded,
      total
    }) => {
      this.progress(loaded / total);
    };
    return loadingTask.promise.then(pdfDocument => {
      this.load(pdfDocument);
    }, reason => {
      if (loadingTask !== this.pdfLoadingTask) {
        return undefined;
      }
      let key = "pdfjs-loading-error";
      if (reason instanceof InvalidPDFException) {
        key = "pdfjs-invalid-file-error";
      } else if (reason instanceof MissingPDFException) {
        key = "pdfjs-missing-file-error";
      } else if (reason instanceof UnexpectedResponseException) {
        key = "pdfjs-unexpected-response-error";
      }
      return this._documentError(key, {
        message: reason.message
      }).then(() => {
        throw reason;
      });
    });
  },
  async download() {
    let data;
    try {
      data = await this.pdfDocument.getData();
    } catch {}
    this.downloadManager.download(data, this._downloadUrl, this._docFilename);
  },
  async save() {
    if (this._saveInProgress) {
      return;
    }
    this._saveInProgress = true;
    await this.pdfScriptingManager.dispatchWillSave();
    try {
      const data = await this.pdfDocument.saveDocument();
      this.downloadManager.download(data, this._downloadUrl, this._docFilename);
    } catch (reason) {
      console.error(`Error when saving the document: ${reason.message}`);
      await this.download();
    } finally {
      await this.pdfScriptingManager.dispatchDidSave();
      this._saveInProgress = false;
    }
    if (this._hasAnnotationEditors) {
      this.externalServices.reportTelemetry({
        type: "editing",
        data: {
          type: "save",
          stats: this.pdfDocument?.annotationStorage.editorStats
        }
      });
    }
  },
  async downloadOrSave() {
    const {
      classList
    } = this.appConfig.appContainer;
    classList.add("wait");
    await (this.pdfDocument?.annotationStorage.size > 0 ? this.save() : this.download());
    classList.remove("wait");
  },
  async _documentError(key, moreInfo = null) {
    this._unblockDocumentLoadEvent();
    const message = await this._otherError(key || "pdfjs-loading-error", moreInfo);
    this.eventBus.dispatch("documenterror", {
      source: this,
      message,
      reason: moreInfo?.message ?? null
    });
  },
  async _otherError(key, moreInfo = null) {
    const message = await this.l10n.get(key);
    const moreInfoText = [`PDF.js v${version || "?"} (build: ${build || "?"})`];
    if (moreInfo) {
      moreInfoText.push(`Message: ${moreInfo.message}`);
      if (moreInfo.stack) {
        moreInfoText.push(`Stack: ${moreInfo.stack}`);
      } else {
        if (moreInfo.filename) {
          moreInfoText.push(`File: ${moreInfo.filename}`);
        }
        if (moreInfo.lineNumber) {
          moreInfoText.push(`Line: ${moreInfo.lineNumber}`);
        }
      }
    }
    console.error(`${message}\n\n${moreInfoText.join("\n")}`);
    return message;
  },
  progress(level) {
    const percent = Math.round(level * 100);
    if (!this.loadingBar || percent <= this.loadingBar.percent) {
      return;
    }
    this.loadingBar.percent = percent;
    if (this.pdfDocument?.loadingParams.disableAutoFetch ?? AppOptions.get("disableAutoFetch")) {
      this.loadingBar.setDisableAutoFetch();
    }
  },
  load(pdfDocument) {
    this.pdfDocument = pdfDocument;
    pdfDocument.getDownloadInfo().then(({
      length
    }) => {
      this._contentLength = length;
      this.loadingBar?.hide();
      firstPagePromise.then(() => {
        this.eventBus.dispatch("documentloaded", {
          source: this
        });
      });
    });
    const pageLayoutPromise = pdfDocument.getPageLayout().catch(() => {});
    const pageModePromise = pdfDocument.getPageMode().catch(() => {});
    const openActionPromise = pdfDocument.getOpenAction().catch(() => {});
    this.toolbar?.setPagesCount(pdfDocument.numPages, false);
    this.secondaryToolbar?.setPagesCount(pdfDocument.numPages);
    this.pdfLinkService.setDocument(pdfDocument);
    this.pdfDocumentProperties?.setDocument(pdfDocument);
    const pdfViewer = this.pdfViewer;
    pdfViewer.setDocument(pdfDocument);
    const {
      firstPagePromise,
      onePageRendered,
      pagesPromise
    } = pdfViewer;
    this.pdfThumbnailViewer?.setDocument(pdfDocument);
    const storedPromise = (this.store = new ViewHistory(pdfDocument.fingerprints[0])).getMultiple({
      page: null,
      zoom: DEFAULT_SCALE_VALUE,
      scrollLeft: "0",
      scrollTop: "0",
      rotation: null,
      sidebarView: SidebarView.UNKNOWN,
      scrollMode: ScrollMode.UNKNOWN,
      spreadMode: SpreadMode.UNKNOWN
    }).catch(() => {});
    firstPagePromise.then(pdfPage => {
      this.loadingBar?.setWidth(this.appConfig.viewerContainer);
      this._initializeAnnotationStorageCallbacks(pdfDocument);
      Promise.all([animationStarted, storedPromise, pageLayoutPromise, pageModePromise, openActionPromise]).then(async ([timeStamp, stored, pageLayout, pageMode, openAction]) => {
        const viewOnLoad = AppOptions.get("viewOnLoad");
        this._initializePdfHistory({
          fingerprint: pdfDocument.fingerprints[0],
          viewOnLoad,
          initialDest: openAction?.dest
        });
        const initialBookmark = this.initialBookmark;
        const zoom = AppOptions.get("defaultZoomValue");
        let hash = zoom ? `zoom=${zoom}` : null;
        let rotation = null;
        let sidebarView = AppOptions.get("sidebarViewOnLoad");
        let scrollMode = AppOptions.get("scrollModeOnLoad");
        let spreadMode = AppOptions.get("spreadModeOnLoad");
        if (stored?.page && viewOnLoad !== ViewOnLoad.INITIAL) {
          hash = `page=${stored.page}&zoom=${zoom || stored.zoom},` + `${stored.scrollLeft},${stored.scrollTop}`;
          rotation = parseInt(stored.rotation, 10);
          if (sidebarView === SidebarView.UNKNOWN) {
            sidebarView = stored.sidebarView | 0;
          }
          if (scrollMode === ScrollMode.UNKNOWN) {
            scrollMode = stored.scrollMode | 0;
          }
          if (spreadMode === SpreadMode.UNKNOWN) {
            spreadMode = stored.spreadMode | 0;
          }
        }
        if (pageMode && sidebarView === SidebarView.UNKNOWN) {
          sidebarView = apiPageModeToSidebarView(pageMode);
        }
        if (pageLayout && scrollMode === ScrollMode.UNKNOWN && spreadMode === SpreadMode.UNKNOWN) {
          const modes = apiPageLayoutToViewerModes(pageLayout);
          spreadMode = modes.spreadMode;
        }
        this.setInitialView(hash, {
          rotation,
          sidebarView,
          scrollMode,
          spreadMode
        });
        this.eventBus.dispatch("documentinit", {
          source: this
        });
        if (!this.isViewerEmbedded) {
          pdfViewer.focus();
        }
        await Promise.race([pagesPromise, new Promise(resolve => {
          setTimeout(resolve, FORCE_PAGES_LOADED_TIMEOUT);
        })]);
        if (!initialBookmark && !hash) {
          return;
        }
        if (pdfViewer.hasEqualPageSizes) {
          return;
        }
        this.initialBookmark = initialBookmark;
        pdfViewer.currentScaleValue = pdfViewer.currentScaleValue;
        this.setInitialView(hash);
      }).catch(() => {
        this.setInitialView();
      }).then(function () {
        pdfViewer.update();
      });
    });
    pagesPromise.then(() => {
      this._unblockDocumentLoadEvent();
      this._initializeAutoPrint(pdfDocument, openActionPromise);
    }, reason => {
      this._documentError("pdfjs-loading-error", {
        message: reason.message
      });
    });
    onePageRendered.then(data => {
      this.externalServices.reportTelemetry({
        type: "pageInfo",
        timestamp: data.timestamp
      });
      if (this.pdfOutlineViewer) {
        pdfDocument.getOutline().then(outline => {
          if (pdfDocument !== this.pdfDocument) {
            return;
          }
          this.pdfOutlineViewer.render({
            outline,
            pdfDocument
          });
        });
      }
      if (this.pdfAttachmentViewer) {
        pdfDocument.getAttachments().then(attachments => {
          if (pdfDocument !== this.pdfDocument) {
            return;
          }
          this.pdfAttachmentViewer.render({
            attachments
          });
        });
      }
      if (this.pdfLayerViewer) {
        pdfViewer.optionalContentConfigPromise.then(optionalContentConfig => {
          if (pdfDocument !== this.pdfDocument) {
            return;
          }
          this.pdfLayerViewer.render({
            optionalContentConfig,
            pdfDocument
          });
        });
      }
    });
    this._initializePageLabels(pdfDocument);
    this._initializeMetadata(pdfDocument);
  },
  async _scriptingDocProperties(pdfDocument) {
    if (!this.documentInfo) {
      await new Promise(resolve => {
        this.eventBus._on("metadataloaded", resolve, {
          once: true
        });
      });
      if (pdfDocument !== this.pdfDocument) {
        return null;
      }
    }
    if (!this._contentLength) {
      await new Promise(resolve => {
        this.eventBus._on("documentloaded", resolve, {
          once: true
        });
      });
      if (pdfDocument !== this.pdfDocument) {
        return null;
      }
    }
    return {
      ...this.documentInfo,
      baseURL: this.baseUrl,
      filesize: this._contentLength,
      filename: this._docFilename,
      metadata: this.metadata?.getRaw(),
      authors: this.metadata?.get("dc:creator"),
      numPages: this.pagesCount,
      URL: this.url
    };
  },
  async _initializeAutoPrint(pdfDocument, openActionPromise) {
    const [openAction, jsActions] = await Promise.all([openActionPromise, this.pdfViewer.enableScripting ? null : pdfDocument.getJSActions()]);
    if (pdfDocument !== this.pdfDocument) {
      return;
    }
    let triggerAutoPrint = openAction?.action === "Print";
    if (jsActions) {
      console.warn("Warning: JavaScript support is not enabled");
      for (const name in jsActions) {
        if (triggerAutoPrint) {
          break;
        }
        switch (name) {
          case "WillClose":
          case "WillSave":
          case "DidSave":
          case "WillPrint":
          case "DidPrint":
            continue;
        }
        triggerAutoPrint = jsActions[name].some(js => AutoPrintRegExp.test(js));
      }
    }
    if (triggerAutoPrint) {
      this.triggerPrinting();
    }
  },
  async _initializeMetadata(pdfDocument) {
    const {
      info,
      metadata,
      contentDispositionFilename,
      contentLength
    } = await pdfDocument.getMetadata();
    if (pdfDocument !== this.pdfDocument) {
      return;
    }
    this.documentInfo = info;
    this.metadata = metadata;
    this._contentDispositionFilename ??= contentDispositionFilename;
    this._contentLength ??= contentLength;
    console.log(`PDF ${pdfDocument.fingerprints[0]} [${info.PDFFormatVersion} ` + `${(info.Producer || "-").trim()} / ${(info.Creator || "-").trim()}] ` + `(PDF.js: ${version || "?"} [${build || "?"}])`);
    let pdfTitle = info.Title;
    const metadataTitle = metadata?.get("dc:title");
    if (metadataTitle) {
      if (metadataTitle !== "Untitled" && !/[\uFFF0-\uFFFF]/g.test(metadataTitle)) {
        pdfTitle = metadataTitle;
      }
    }
    if (pdfTitle) {
      this.setTitle(`${pdfTitle} - ${this._contentDispositionFilename || this._title}`);
    } else if (this._contentDispositionFilename) {
      this.setTitle(this._contentDispositionFilename);
    }
    if (info.IsXFAPresent && !info.IsAcroFormPresent && !pdfDocument.isPureXfa) {
      if (pdfDocument.loadingParams.enableXfa) {
        console.warn("Warning: XFA Foreground documents are not supported");
      } else {
        console.warn("Warning: XFA support is not enabled");
      }
    } else if ((info.IsAcroFormPresent || info.IsXFAPresent) && !this.pdfViewer.renderForms) {
      console.warn("Warning: Interactive form support is not enabled");
    }
    if (info.IsSignaturesPresent) {
      console.warn("Warning: Digital signatures validation is not supported");
    }
    this.eventBus.dispatch("metadataloaded", {
      source: this
    });
  },
  async _initializePageLabels(pdfDocument) {
    const labels = await pdfDocument.getPageLabels();
    if (pdfDocument !== this.pdfDocument) {
      return;
    }
    if (!labels || AppOptions.get("disablePageLabels")) {
      return;
    }
    const numLabels = labels.length;
    let standardLabels = 0,
      emptyLabels = 0;
    for (let i = 0; i < numLabels; i++) {
      const label = labels[i];
      if (label === (i + 1).toString()) {
        standardLabels++;
      } else if (label === "") {
        emptyLabels++;
      } else {
        break;
      }
    }
    if (standardLabels >= numLabels || emptyLabels >= numLabels) {
      return;
    }
    const {
      pdfViewer,
      pdfThumbnailViewer,
      toolbar
    } = this;
    pdfViewer.setPageLabels(labels);
    pdfThumbnailViewer?.setPageLabels(labels);
    toolbar?.setPagesCount(numLabels, true);
    toolbar?.setPageNumber(pdfViewer.currentPageNumber, pdfViewer.currentPageLabel);
  },
  _initializePdfHistory({
    fingerprint,
    viewOnLoad,
    initialDest = null
  }) {
    if (!this.pdfHistory) {
      return;
    }
    this.pdfHistory.initialize({
      fingerprint,
      resetHistory: viewOnLoad === ViewOnLoad.INITIAL,
      updateUrl: AppOptions.get("historyUpdateUrl")
    });
    if (this.pdfHistory.initialBookmark) {
      this.initialBookmark = this.pdfHistory.initialBookmark;
      this.initialRotation = this.pdfHistory.initialRotation;
    }
    if (initialDest && !this.initialBookmark && viewOnLoad === ViewOnLoad.UNKNOWN) {
      this.initialBookmark = JSON.stringify(initialDest);
      this.pdfHistory.push({
        explicitDest: initialDest,
        pageNumber: null
      });
    }
  },
  _initializeAnnotationStorageCallbacks(pdfDocument) {
    if (pdfDocument !== this.pdfDocument) {
      return;
    }
    const {
      annotationStorage
    } = pdfDocument;
    annotationStorage.onSetModified = () => {
      window.addEventListener("beforeunload", beforeUnload);
    };
    annotationStorage.onResetModified = () => {
      window.removeEventListener("beforeunload", beforeUnload);
    };
    annotationStorage.onAnnotationEditor = typeStr => {
      this._hasAnnotationEditors = !!typeStr;
      this.setTitle();
    };
  },
  setInitialView(storedHash, {
    rotation,
    sidebarView,
    scrollMode,
    spreadMode
  } = {}) {
    const setRotation = angle => {
      if (isValidRotation(angle)) {
        this.pdfViewer.pagesRotation = angle;
      }
    };
    const setViewerModes = (scroll, spread) => {
      if (isValidScrollMode(scroll)) {
        this.pdfViewer.scrollMode = scroll;
      }
      if (isValidSpreadMode(spread)) {
        this.pdfViewer.spreadMode = spread;
      }
    };
    this.isInitialViewSet = true;
    this.pdfSidebar?.setInitialView(sidebarView);
    setViewerModes(scrollMode, spreadMode);
    if (this.initialBookmark) {
      setRotation(this.initialRotation);
      delete this.initialRotation;
      this.pdfLinkService.setHash(this.initialBookmark);
      this.initialBookmark = null;
    } else if (storedHash) {
      setRotation(rotation);
      this.pdfLinkService.setHash(storedHash);
    }
    this.toolbar?.setPageNumber(this.pdfViewer.currentPageNumber, this.pdfViewer.currentPageLabel);
    this.secondaryToolbar?.setPageNumber(this.pdfViewer.currentPageNumber);
    if (!this.pdfViewer.currentScaleValue) {
      this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
    }
  },
  _cleanup() {
    if (!this.pdfDocument) {
      return;
    }
    this.pdfViewer.cleanup();
    this.pdfThumbnailViewer?.cleanup();
    this.pdfDocument.cleanup(AppOptions.get("fontExtraProperties"));
  },
  forceRendering() {
    this.pdfRenderingQueue.printing = !!this.printService;
    this.pdfRenderingQueue.isThumbnailViewEnabled = this.pdfSidebar?.visibleView === SidebarView.THUMBS;
    this.pdfRenderingQueue.renderHighestPriority();
  },
  beforePrint() {
    this._printAnnotationStoragePromise = this.pdfScriptingManager.dispatchWillPrint().catch(() => {}).then(() => this.pdfDocument?.annotationStorage.print);
    if (this.printService) {
      return;
    }
    if (!this.supportsPrinting) {
      this._otherError("pdfjs-printing-not-supported");
      return;
    }
    if (!this.pdfViewer.pageViewsReady) {
      this.l10n.get("pdfjs-printing-not-ready").then(msg => {
        window.alert(msg);
      });
      return;
    }
    this.printService = PDFPrintServiceFactory.createPrintService({
      pdfDocument: this.pdfDocument,
      pagesOverview: this.pdfViewer.getPagesOverview(),
      printContainer: this.appConfig.printContainer,
      printResolution: AppOptions.get("printResolution"),
      printAnnotationStoragePromise: this._printAnnotationStoragePromise
    });
    this.forceRendering();
    this.setTitle();
    this.printService.layout();
    if (this._hasAnnotationEditors) {
      this.externalServices.reportTelemetry({
        type: "editing",
        data: {
          type: "print",
          stats: this.pdfDocument?.annotationStorage.editorStats
        }
      });
    }
  },
  afterPrint() {
    if (this._printAnnotationStoragePromise) {
      this._printAnnotationStoragePromise.then(() => {
        this.pdfScriptingManager.dispatchDidPrint();
      });
      this._printAnnotationStoragePromise = null;
    }
    if (this.printService) {
      this.printService.destroy();
      this.printService = null;
      this.pdfDocument?.annotationStorage.resetModified();
    }
    this.forceRendering();
    this.setTitle();
  },
  rotatePages(delta) {
    this.pdfViewer.pagesRotation += delta;
  },
  requestPresentationMode() {
    this.pdfPresentationMode?.request();
  },
  triggerPrinting() {
    if (this.supportsPrinting) {
      window.print();
    }
  },
  bindEvents() {
    if (this._eventBusAbortController) {
      return;
    }
    this._eventBusAbortController = new AbortController();
    const {
      eventBus,
      externalServices,
      pdfDocumentProperties,
      pdfViewer,
      preferences,
      _eventBusAbortController: {
        signal
      }
    } = this;
    eventBus._on("resize", onResize.bind(this), {
      signal
    });
    eventBus._on("hashchange", onHashchange.bind(this), {
      signal
    });
    eventBus._on("beforeprint", this.beforePrint.bind(this), {
      signal
    });
    eventBus._on("afterprint", this.afterPrint.bind(this), {
      signal
    });
    eventBus._on("pagerender", onPageRender.bind(this), {
      signal
    });
    eventBus._on("pagerendered", onPageRendered.bind(this), {
      signal
    });
    eventBus._on("updateviewarea", onUpdateViewarea.bind(this), {
      signal
    });
    eventBus._on("pagechanging", onPageChanging.bind(this), {
      signal
    });
    eventBus._on("scalechanging", onScaleChanging.bind(this), {
      signal
    });
    eventBus._on("rotationchanging", onRotationChanging.bind(this), {
      signal
    });
    eventBus._on("sidebarviewchanged", onSidebarViewChanged.bind(this), {
      signal
    });
    eventBus._on("pagemode", onPageMode.bind(this), {
      signal
    });
    eventBus._on("namedaction", onNamedAction.bind(this), {
      signal
    });
    eventBus._on("presentationmodechanged", evt => pdfViewer.presentationModeState = evt.state, {
      signal
    });
    eventBus._on("presentationmode", this.requestPresentationMode.bind(this), {
      signal
    });
    eventBus._on("switchannotationeditormode", evt => pdfViewer.annotationEditorMode = evt, {
      signal
    });
    eventBus._on("print", this.triggerPrinting.bind(this), {
      signal
    });
    eventBus._on("download", this.downloadOrSave.bind(this), {
      signal
    });
    eventBus._on("firstpage", () => this.page = 1, {
      signal
    });
    eventBus._on("lastpage", () => this.page = this.pagesCount, {
      signal
    });
    eventBus._on("nextpage", () => pdfViewer.nextPage(), {
      signal
    });
    eventBus._on("previouspage", () => pdfViewer.previousPage(), {
      signal
    });
    eventBus._on("zoomin", this.zoomIn.bind(this), {
      signal
    });
    eventBus._on("zoomout", this.zoomOut.bind(this), {
      signal
    });
    eventBus._on("zoomreset", this.zoomReset.bind(this), {
      signal
    });
    eventBus._on("pagenumberchanged", onPageNumberChanged.bind(this), {
      signal
    });
    eventBus._on("scalechanged", evt => pdfViewer.currentScaleValue = evt.value, {
      signal
    });
    eventBus._on("rotatecw", this.rotatePages.bind(this, 90), {
      signal
    });
    eventBus._on("rotateccw", this.rotatePages.bind(this, -90), {
      signal
    });
    eventBus._on("optionalcontentconfig", evt => pdfViewer.optionalContentConfigPromise = evt.promise, {
      signal
    });
    eventBus._on("switchscrollmode", evt => pdfViewer.scrollMode = evt.mode, {
      signal
    });
    eventBus._on("scrollmodechanged", onViewerModesChanged.bind(this, "scrollMode"), {
      signal
    });
    eventBus._on("switchspreadmode", evt => pdfViewer.spreadMode = evt.mode, {
      signal
    });
    eventBus._on("spreadmodechanged", onViewerModesChanged.bind(this, "spreadMode"), {
      signal
    });
    eventBus._on("imagealttextsettings", onImageAltTextSettings.bind(this), {
      signal
    });
    eventBus._on("documentproperties", () => pdfDocumentProperties?.open(), {
      signal
    });
    eventBus._on("findfromurlhash", onFindFromUrlHash.bind(this), {
      signal
    });
    eventBus._on("updatefindmatchescount", onUpdateFindMatchesCount.bind(this), {
      signal
    });
    eventBus._on("updatefindcontrolstate", onUpdateFindControlState.bind(this), {
      signal
    });
    eventBus._on("annotationeditorstateschanged", evt => externalServices.updateEditorStates(evt), {
      signal
    });
    eventBus._on("reporttelemetry", evt => externalServices.reportTelemetry(evt.details), {
      signal
    });
    eventBus._on("setpreference", evt => preferences.set(evt.name, evt.value), {
      signal
    });
  },
  bindWindowEvents() {
    if (this._windowAbortController) {
      return;
    }
    this._windowAbortController = new AbortController();
    const {
      eventBus,
      appConfig: {
        mainContainer
      },
      pdfViewer,
      _windowAbortController: {
        signal
      }
    } = this;
    function addWindowResolutionChange(evt = null) {
      if (evt) {
        pdfViewer.refresh();
      }
      const mediaQueryList = window.matchMedia(`(resolution: ${window.devicePixelRatio || 1}dppx)`);
      mediaQueryList.addEventListener("change", addWindowResolutionChange, {
        once: true,
        signal
      });
    }
    addWindowResolutionChange();
    window.addEventListener("wheel", onWheel.bind(this), {
      passive: false,
      signal
    });
    window.addEventListener("touchstart", onTouchStart.bind(this), {
      passive: false,
      signal
    });
    window.addEventListener("touchmove", onTouchMove.bind(this), {
      passive: false,
      signal
    });
    window.addEventListener("touchend", onTouchEnd.bind(this), {
      passive: false,
      signal
    });
    window.addEventListener("click", onClick.bind(this), {
      signal
    });
    window.addEventListener("keydown", onKeyDown.bind(this), {
      signal
    });
    window.addEventListener("keyup", onKeyUp.bind(this), {
      signal
    });
    window.addEventListener("resize", () => eventBus.dispatch("resize", {
      source: window
    }), {
      signal
    });
    window.addEventListener("hashchange", () => {
      eventBus.dispatch("hashchange", {
        source: window,
        hash: document.location.hash.substring(1)
      });
    }, {
      signal
    });
    window.addEventListener("beforeprint", () => eventBus.dispatch("beforeprint", {
      source: window
    }), {
      signal
    });
    window.addEventListener("afterprint", () => eventBus.dispatch("afterprint", {
      source: window
    }), {
      signal
    });
    window.addEventListener("updatefromsandbox", evt => {
      eventBus.dispatch("updatefromsandbox", {
        source: window,
        detail: evt.detail
      });
    }, {
      signal
    });
    const scrollend = () => {
      this._isScrolling = false;
      mainContainer.addEventListener("scroll", scroll, {
        passive: true,
        signal
      });
      mainContainer.removeEventListener("scrollend", scrollend);
      mainContainer.removeEventListener("blur", scrollend);
    };
    const scroll = () => {
      if (this._isCtrlKeyDown) {
        return;
      }
      mainContainer.removeEventListener("scroll", scroll, {
        passive: true
      });
      this._isScrolling = true;
      mainContainer.addEventListener("scrollend", scrollend, {
        signal
      });
      mainContainer.addEventListener("blur", scrollend, {
        signal
      });
    };
    mainContainer.addEventListener("scroll", scroll, {
      passive: true,
      signal
    });
  },
  unbindEvents() {
    this._eventBusAbortController?.abort();
    this._eventBusAbortController = null;
  },
  unbindWindowEvents() {
    this._windowAbortController?.abort();
    this._windowAbortController = null;
  },
  async testingClose() {
    this.unbindEvents();
    this.unbindWindowEvents();
    this._globalAbortController?.abort();
    this._globalAbortController = null;
    this.findBar?.close();
    await Promise.all([this.l10n?.destroy(), this.close()]);
  },
  _accumulateTicks(ticks, prop) {
    if (this[prop] > 0 && ticks < 0 || this[prop] < 0 && ticks > 0) {
      this[prop] = 0;
    }
    this[prop] += ticks;
    const wholeTicks = Math.trunc(this[prop]);
    this[prop] -= wholeTicks;
    return wholeTicks;
  },
  _accumulateFactor(previousScale, factor, prop) {
    if (factor === 1) {
      return 1;
    }
    if (this[prop] > 1 && factor < 1 || this[prop] < 1 && factor > 1) {
      this[prop] = 1;
    }
    const newFactor = Math.floor(previousScale * factor * this[prop] * 100) / (100 * previousScale);
    this[prop] = factor / newFactor;
    return newFactor;
  },
  _unblockDocumentLoadEvent() {
    document.blockUnblockOnload?.(false);
    this._unblockDocumentLoadEvent = () => {};
  },
  get scriptingReady() {
    return this.pdfScriptingManager.ready;
  }
};
initCom(PDFViewerApplication);
function onPageRender({
  pageNumber
}) {
  if (pageNumber === this.page) {
    this.toolbar?.updateLoadingIndicatorState(true);
  }
}
function onPageRendered({
  pageNumber,
  error
}) {
  if (pageNumber === this.page) {
    this.toolbar?.updateLoadingIndicatorState(false);
  }
  if (this.pdfSidebar?.visibleView === SidebarView.THUMBS) {
    const pageView = this.pdfViewer.getPageView(pageNumber - 1);
    const thumbnailView = this.pdfThumbnailViewer?.getThumbnail(pageNumber - 1);
    if (pageView) {
      thumbnailView?.setImage(pageView);
    }
  }
  if (error) {
    this._otherError("pdfjs-rendering-error", error);
  }
}
function onPageMode({
  mode
}) {
  let view;
  switch (mode) {
    case "thumbs":
      view = SidebarView.THUMBS;
      break;
    case "bookmarks":
    case "outline":
      view = SidebarView.OUTLINE;
      break;
    case "attachments":
      view = SidebarView.ATTACHMENTS;
      break;
    case "layers":
      view = SidebarView.LAYERS;
      break;
    case "none":
      view = SidebarView.NONE;
      break;
    default:
      console.error('Invalid "pagemode" hash parameter: ' + mode);
      return;
  }
  this.pdfSidebar?.switchView(view, true);
}
function onNamedAction(evt) {
  switch (evt.action) {
    case "GoToPage":
      this.appConfig.toolbar?.pageNumber.select();
      break;
    case "Find":
      if (!this.supportsIntegratedFind) {
        this.findBar?.toggle();
      }
      break;
    case "Print":
      this.triggerPrinting();
      break;
    case "SaveAs":
      this.downloadOrSave();
      break;
  }
}
function onSidebarViewChanged({
  view
}) {
  this.pdfRenderingQueue.isThumbnailViewEnabled = view === SidebarView.THUMBS;
  if (this.isInitialViewSet) {
    this.store?.set("sidebarView", view).catch(() => {});
  }
}
function onUpdateViewarea({
  location
}) {
  if (this.isInitialViewSet) {
    this.store?.setMultiple({
      page: location.pageNumber,
      zoom: location.scale,
      scrollLeft: location.left,
      scrollTop: location.top,
      rotation: location.rotation
    }).catch(() => {});
  }
  if (this.appConfig.secondaryToolbar) {
    this.appConfig.secondaryToolbar.viewBookmarkButton.href = this.pdfLinkService.getAnchorUrl(location.pdfOpenParams);
  }
}
function onViewerModesChanged(name, evt) {
  if (this.isInitialViewSet && !this.pdfViewer.isInPresentationMode) {
    this.store?.set(name, evt.mode).catch(() => {});
  }
}
function onResize() {
  const {
    pdfDocument,
    pdfViewer,
    pdfRenderingQueue
  } = this;
  if (pdfRenderingQueue.printing && window.matchMedia("print").matches) {
    return;
  }
  if (!pdfDocument) {
    return;
  }
  const currentScaleValue = pdfViewer.currentScaleValue;
  if (currentScaleValue === "auto" || currentScaleValue === "page-fit" || currentScaleValue === "page-width") {
    pdfViewer.currentScaleValue = currentScaleValue;
  }
  pdfViewer.update();
}
function onHashchange(evt) {
  const hash = evt.hash;
  if (!hash) {
    return;
  }
  if (!this.isInitialViewSet) {
    this.initialBookmark = hash;
  } else if (!this.pdfHistory?.popStateInProgress) {
    this.pdfLinkService.setHash(hash);
  }
}
function onPageNumberChanged(evt) {
  const {
    pdfViewer
  } = this;
  if (evt.value !== "") {
    this.pdfLinkService.goToPage(evt.value);
  }
  if (evt.value !== pdfViewer.currentPageNumber.toString() && evt.value !== pdfViewer.currentPageLabel) {
    this.toolbar?.setPageNumber(pdfViewer.currentPageNumber, pdfViewer.currentPageLabel);
  }
}
function onImageAltTextSettings() {
  this.imageAltTextSettings?.open({
    enableGuessAltText: AppOptions.get("enableGuessAltText"),
    enableNewAltTextWhenAddingImage: AppOptions.get("enableNewAltTextWhenAddingImage")
  });
}
function onFindFromUrlHash(evt) {
  this.eventBus.dispatch("find", {
    source: evt.source,
    type: "",
    query: evt.query,
    caseSensitive: false,
    entireWord: false,
    highlightAll: true,
    findPrevious: false,
    matchDiacritics: true
  });
}
function onUpdateFindMatchesCount({
  matchesCount
}) {
  if (this.supportsIntegratedFind) {
    this.externalServices.updateFindMatchesCount(matchesCount);
  } else {
    this.findBar?.updateResultsCount(matchesCount);
  }
}
function onUpdateFindControlState({
  state,
  previous,
  entireWord,
  matchesCount,
  rawQuery
}) {
  if (this.supportsIntegratedFind) {
    this.externalServices.updateFindControlState({
      result: state,
      findPrevious: previous,
      entireWord,
      matchesCount,
      rawQuery
    });
  } else {
    this.findBar?.updateUIState(state, previous, matchesCount);
  }
}
function onScaleChanging(evt) {
  this.toolbar?.setPageScale(evt.presetValue, evt.scale);
  this.pdfViewer.update();
}
function onRotationChanging(evt) {
  if (this.pdfThumbnailViewer) {
    this.pdfThumbnailViewer.pagesRotation = evt.pagesRotation;
  }
  this.forceRendering();
  this.pdfViewer.currentPageNumber = evt.pageNumber;
}
function onPageChanging({
  pageNumber,
  pageLabel
}) {
  this.toolbar?.setPageNumber(pageNumber, pageLabel);
  this.secondaryToolbar?.setPageNumber(pageNumber);
  if (this.pdfSidebar?.visibleView === SidebarView.THUMBS) {
    this.pdfThumbnailViewer?.scrollThumbnailIntoView(pageNumber);
  }
  const currentPage = this.pdfViewer.getPageView(pageNumber - 1);
  this.toolbar?.updateLoadingIndicatorState(currentPage?.renderingState === RenderingStates.RUNNING);
}
function onWheel(evt) {
  const {
    pdfViewer,
    supportsMouseWheelZoomCtrlKey,
    supportsMouseWheelZoomMetaKey,
    supportsPinchToZoom
  } = this;
  if (pdfViewer.isInPresentationMode) {
    return;
  }
  const deltaMode = evt.deltaMode;
  let scaleFactor = Math.exp(-evt.deltaY / 100);
  const isBuiltInMac = FeatureTest.platform.isMac;
  const isPinchToZoom = evt.ctrlKey && !this._isCtrlKeyDown && deltaMode === WheelEvent.DOM_DELTA_PIXEL && evt.deltaX === 0 && (Math.abs(scaleFactor - 1) < 0.05 || isBuiltInMac) && evt.deltaZ === 0;
  const origin = [evt.clientX, evt.clientY];
  if (isPinchToZoom || evt.ctrlKey && supportsMouseWheelZoomCtrlKey || evt.metaKey && supportsMouseWheelZoomMetaKey) {
    evt.preventDefault();
    if (this._isScrolling || document.visibilityState === "hidden" || this.overlayManager.active) {
      return;
    }
    if (isPinchToZoom && supportsPinchToZoom) {
      scaleFactor = this._accumulateFactor(pdfViewer.currentScale, scaleFactor, "_wheelUnusedFactor");
      this.updateZoom(null, scaleFactor, origin);
    } else {
      const delta = normalizeWheelEventDirection(evt);
      let ticks = 0;
      if (deltaMode === WheelEvent.DOM_DELTA_LINE || deltaMode === WheelEvent.DOM_DELTA_PAGE) {
        ticks = Math.abs(delta) >= 1 ? Math.sign(delta) : this._accumulateTicks(delta, "_wheelUnusedTicks");
      } else {
        const PIXELS_PER_LINE_SCALE = 30;
        ticks = this._accumulateTicks(delta / PIXELS_PER_LINE_SCALE, "_wheelUnusedTicks");
      }
      this.updateZoom(ticks, null, origin);
    }
  }
}
function onTouchStart(evt) {
  if (this.pdfViewer.isInPresentationMode || evt.touches.length < 2) {
    return;
  }
  evt.preventDefault();
  if (evt.touches.length !== 2 || this.overlayManager.active) {
    this._touchInfo = null;
    return;
  }
  let [touch0, touch1] = evt.touches;
  if (touch0.identifier > touch1.identifier) {
    [touch0, touch1] = [touch1, touch0];
  }
  this._touchInfo = {
    touch0X: touch0.pageX,
    touch0Y: touch0.pageY,
    touch1X: touch1.pageX,
    touch1Y: touch1.pageY
  };
}
function onTouchMove(evt) {
  if (!this._touchInfo || evt.touches.length !== 2) {
    return;
  }
  const {
    pdfViewer,
    _touchInfo,
    supportsPinchToZoom
  } = this;
  let [touch0, touch1] = evt.touches;
  if (touch0.identifier > touch1.identifier) {
    [touch0, touch1] = [touch1, touch0];
  }
  const {
    pageX: page0X,
    pageY: page0Y
  } = touch0;
  const {
    pageX: page1X,
    pageY: page1Y
  } = touch1;
  const {
    touch0X: pTouch0X,
    touch0Y: pTouch0Y,
    touch1X: pTouch1X,
    touch1Y: pTouch1Y
  } = _touchInfo;
  if (Math.abs(pTouch0X - page0X) <= 1 && Math.abs(pTouch0Y - page0Y) <= 1 && Math.abs(pTouch1X - page1X) <= 1 && Math.abs(pTouch1Y - page1Y) <= 1) {
    return;
  }
  _touchInfo.touch0X = page0X;
  _touchInfo.touch0Y = page0Y;
  _touchInfo.touch1X = page1X;
  _touchInfo.touch1Y = page1Y;
  if (pTouch0X === page0X && pTouch0Y === page0Y) {
    const v1X = pTouch1X - page0X;
    const v1Y = pTouch1Y - page0Y;
    const v2X = page1X - page0X;
    const v2Y = page1Y - page0Y;
    const det = v1X * v2Y - v1Y * v2X;
    if (Math.abs(det) > 0.02 * Math.hypot(v1X, v1Y) * Math.hypot(v2X, v2Y)) {
      return;
    }
  } else if (pTouch1X === page1X && pTouch1Y === page1Y) {
    const v1X = pTouch0X - page1X;
    const v1Y = pTouch0Y - page1Y;
    const v2X = page0X - page1X;
    const v2Y = page0Y - page1Y;
    const det = v1X * v2Y - v1Y * v2X;
    if (Math.abs(det) > 0.02 * Math.hypot(v1X, v1Y) * Math.hypot(v2X, v2Y)) {
      return;
    }
  } else {
    const diff0X = page0X - pTouch0X;
    const diff1X = page1X - pTouch1X;
    const diff0Y = page0Y - pTouch0Y;
    const diff1Y = page1Y - pTouch1Y;
    const dotProduct = diff0X * diff1X + diff0Y * diff1Y;
    if (dotProduct >= 0) {
      return;
    }
  }
  evt.preventDefault();
  const origin = [(page0X + page1X) / 2, (page0Y + page1Y) / 2];
  const distance = Math.hypot(page0X - page1X, page0Y - page1Y) || 1;
  const pDistance = Math.hypot(pTouch0X - pTouch1X, pTouch0Y - pTouch1Y) || 1;
  if (supportsPinchToZoom) {
    const newScaleFactor = this._accumulateFactor(pdfViewer.currentScale, distance / pDistance, "_touchUnusedFactor");
    this.updateZoom(null, newScaleFactor, origin);
  } else {
    const PIXELS_PER_LINE_SCALE = 30;
    const ticks = this._accumulateTicks((distance - pDistance) / PIXELS_PER_LINE_SCALE, "_touchUnusedTicks");
    this.updateZoom(ticks, null, origin);
  }
}
function onTouchEnd(evt) {
  if (!this._touchInfo) {
    return;
  }
  evt.preventDefault();
  this._touchInfo = null;
  this._touchUnusedTicks = 0;
  this._touchUnusedFactor = 1;
}
function onClick(evt) {
  if (!this.secondaryToolbar?.isOpen) {
    return;
  }
  const appConfig = this.appConfig;
  if (this.pdfViewer.containsElement(evt.target) || appConfig.toolbar?.container.contains(evt.target) && evt.target !== appConfig.secondaryToolbar?.toggleButton) {
    this.secondaryToolbar.close();
  }
}
function onKeyUp(evt) {
  if (evt.key === "Control") {
    this._isCtrlKeyDown = false;
  }
}
function onKeyDown(evt) {
  this._isCtrlKeyDown = evt.key === "Control";
  if (this.overlayManager.active) {
    return;
  }
  const {
    eventBus,
    pdfViewer
  } = this;
  const isViewerInPresentationMode = pdfViewer.isInPresentationMode;
  let handled = false,
    ensureViewerFocused = false;
  const cmd = (evt.ctrlKey ? 1 : 0) | (evt.altKey ? 2 : 0) | (evt.shiftKey ? 4 : 0) | (evt.metaKey ? 8 : 0);
  if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) {
    switch (evt.keyCode) {
      case 70:
        if (!this.supportsIntegratedFind && !evt.shiftKey) {
          this.findBar?.open();
          handled = true;
        }
        break;
      case 71:
        if (!this.supportsIntegratedFind) {
          const {
            state
          } = this.findController;
          if (state) {
            const newState = {
              source: window,
              type: "again",
              findPrevious: cmd === 5 || cmd === 12
            };
            eventBus.dispatch("find", {
              ...state,
              ...newState
            });
          }
          handled = true;
        }
        break;
      case 61:
      case 107:
      case 187:
      case 171:
        this.zoomIn();
        handled = true;
        break;
      case 173:
      case 109:
      case 189:
        this.zoomOut();
        handled = true;
        break;
      case 48:
      case 96:
        if (!isViewerInPresentationMode) {
          setTimeout(() => {
            this.zoomReset();
          });
          handled = false;
        }
        break;
      case 38:
        if (isViewerInPresentationMode || this.page > 1) {
          this.page = 1;
          handled = true;
          ensureViewerFocused = true;
        }
        break;
      case 40:
        if (isViewerInPresentationMode || this.page < this.pagesCount) {
          this.page = this.pagesCount;
          handled = true;
          ensureViewerFocused = true;
        }
        break;
    }
  }
  if (cmd === 3 || cmd === 10) {
    switch (evt.keyCode) {
      case 80:
        this.requestPresentationMode();
        handled = true;
        this.externalServices.reportTelemetry({
          type: "buttons",
          data: {
            id: "presentationModeKeyboard"
          }
        });
        break;
      case 71:
        if (this.appConfig.toolbar) {
          this.appConfig.toolbar.pageNumber.select();
          handled = true;
        }
        break;
    }
  }
  if (handled) {
    if (ensureViewerFocused && !isViewerInPresentationMode) {
      pdfViewer.focus();
    }
    evt.preventDefault();
    return;
  }
  const curElement = getActiveOrFocusedElement();
  const curElementTagName = curElement?.tagName.toUpperCase();
  if (curElementTagName === "INPUT" || curElementTagName === "TEXTAREA" || curElementTagName === "SELECT" || curElementTagName === "BUTTON" && (evt.keyCode === 13 || evt.keyCode === 32) || curElement?.isContentEditable) {
    if (evt.keyCode !== 27) {
      return;
    }
  }
  if (cmd === 0) {
    let turnPage = 0,
      turnOnlyIfPageFit = false;
    switch (evt.keyCode) {
      case 38:
        if (this.supportsCaretBrowsingMode) {
          this.moveCaret(true, false);
          handled = true;
          break;
        }
      case 33:
        if (pdfViewer.isVerticalScrollbarEnabled) {
          turnOnlyIfPageFit = true;
        }
        turnPage = -1;
        break;
      case 8:
        if (!isViewerInPresentationMode) {
          turnOnlyIfPageFit = true;
        }
        turnPage = -1;
        break;
      case 37:
        if (this.supportsCaretBrowsingMode) {
          return;
        }
        if (pdfViewer.isHorizontalScrollbarEnabled) {
          turnOnlyIfPageFit = true;
        }
      case 75:
      case 80:
        turnPage = -1;
        break;
      case 27:
        if (this.secondaryToolbar?.isOpen) {
          this.secondaryToolbar.close();
          handled = true;
        }
        if (!this.supportsIntegratedFind && this.findBar?.opened) {
          this.findBar.close();
          handled = true;
        }
        break;
      case 40:
        if (this.supportsCaretBrowsingMode) {
          this.moveCaret(false, false);
          handled = true;
          break;
        }
      case 34:
        if (pdfViewer.isVerticalScrollbarEnabled) {
          turnOnlyIfPageFit = true;
        }
        turnPage = 1;
        break;
      case 13:
      case 32:
        if (!isViewerInPresentationMode) {
          turnOnlyIfPageFit = true;
        }
        turnPage = 1;
        break;
      case 39:
        if (this.supportsCaretBrowsingMode) {
          return;
        }
        if (pdfViewer.isHorizontalScrollbarEnabled) {
          turnOnlyIfPageFit = true;
        }
      case 74:
      case 78:
        turnPage = 1;
        break;
      case 36:
        if (isViewerInPresentationMode || this.page > 1) {
          this.page = 1;
          handled = true;
          ensureViewerFocused = true;
        }
        break;
      case 35:
        if (isViewerInPresentationMode || this.page < this.pagesCount) {
          this.page = this.pagesCount;
          handled = true;
          ensureViewerFocused = true;
        }
        break;
      case 83:
        this.pdfCursorTools?.switchTool(CursorTool.SELECT);
        break;
      case 72:
        this.pdfCursorTools?.switchTool(CursorTool.HAND);
        break;
      case 82:
        this.rotatePages(90);
        break;
      case 115:
        this.pdfSidebar?.toggle();
        break;
    }
    if (turnPage !== 0 && (!turnOnlyIfPageFit || pdfViewer.currentScaleValue === "page-fit")) {
      if (turnPage > 0) {
        pdfViewer.nextPage();
      } else {
        pdfViewer.previousPage();
      }
      handled = true;
    }
  }
  if (cmd === 4) {
    switch (evt.keyCode) {
      case 13:
      case 32:
        if (!isViewerInPresentationMode && pdfViewer.currentScaleValue !== "page-fit") {
          break;
        }
        pdfViewer.previousPage();
        handled = true;
        break;
      case 38:
        this.moveCaret(true, true);
        handled = true;
        break;
      case 40:
        this.moveCaret(false, true);
        handled = true;
        break;
      case 82:
        this.rotatePages(-90);
        break;
    }
  }
  if (!handled && !isViewerInPresentationMode) {
    if (evt.keyCode >= 33 && evt.keyCode <= 40 || evt.keyCode === 32 && curElementTagName !== "BUTTON") {
      ensureViewerFocused = true;
    }
  }
  if (ensureViewerFocused && !pdfViewer.containsElement(curElement)) {
    pdfViewer.focus();
  }
  if (handled) {
    evt.preventDefault();
  }
}
function beforeUnload(evt) {
  evt.preventDefault();
  evt.returnValue = "";
  return false;
}

;// CONCATENATED MODULE: ./web/viewer.js




const pdfjsVersion = "4.6.60";
const pdfjsBuild = "10a846417";
const AppConstants = null;
window.PDFViewerApplication = PDFViewerApplication;
window.PDFViewerApplicationConstants = AppConstants;
window.PDFViewerApplicationOptions = AppOptions;
function getViewerConfiguration() {
  return {
    appContainer: document.body,
    mainContainer: document.getElementById("viewerContainer"),
    viewerContainer: document.getElementById("viewer"),
    toolbar: {
      container: document.getElementById("toolbarViewer"),
      numPages: document.getElementById("numPages"),
      pageNumber: document.getElementById("pageNumber"),
      scaleSelect: document.getElementById("scaleSelect"),
      customScaleOption: document.getElementById("customScaleOption"),
      previous: document.getElementById("previous"),
      next: document.getElementById("next"),
      zoomIn: document.getElementById("zoomIn"),
      zoomOut: document.getElementById("zoomOut"),
      print: document.getElementById("print"),
      editorFreeTextButton: document.getElementById("editorFreeText"),
      editorFreeTextParamsToolbar: document.getElementById("editorFreeTextParamsToolbar"),
      editorHighlightButton: document.getElementById("editorHighlight"),
      editorHighlightParamsToolbar: document.getElementById("editorHighlightParamsToolbar"),
      editorHighlightColorPicker: document.getElementById("editorHighlightColorPicker"),
      editorInkButton: document.getElementById("editorInk"),
      editorInkParamsToolbar: document.getElementById("editorInkParamsToolbar"),
      editorStampButton: document.getElementById("editorStamp"),
      editorStampParamsToolbar: document.getElementById("editorStampParamsToolbar"),
      download: document.getElementById("download")
    },
    secondaryToolbar: {
      toolbar: document.getElementById("secondaryToolbar"),
      toggleButton: document.getElementById("secondaryToolbarToggle"),
      presentationModeButton: document.getElementById("presentationMode"),
      openFileButton: null,
      printButton: document.getElementById("secondaryPrint"),
      downloadButton: document.getElementById("secondaryDownload"),
      viewBookmarkButton: document.getElementById("viewBookmark"),
      firstPageButton: document.getElementById("firstPage"),
      lastPageButton: document.getElementById("lastPage"),
      pageRotateCwButton: document.getElementById("pageRotateCw"),
      pageRotateCcwButton: document.getElementById("pageRotateCcw"),
      cursorSelectToolButton: document.getElementById("cursorSelectTool"),
      cursorHandToolButton: document.getElementById("cursorHandTool"),
      scrollPageButton: document.getElementById("scrollPage"),
      scrollVerticalButton: document.getElementById("scrollVertical"),
      scrollHorizontalButton: document.getElementById("scrollHorizontal"),
      scrollWrappedButton: document.getElementById("scrollWrapped"),
      spreadNoneButton: document.getElementById("spreadNone"),
      spreadOddButton: document.getElementById("spreadOdd"),
      spreadEvenButton: document.getElementById("spreadEven"),
      imageAltTextSettingsButton: document.getElementById("imageAltTextSettings"),
      imageAltTextSettingsSeparator: document.getElementById("imageAltTextSettingsSeparator"),
      documentPropertiesButton: document.getElementById("documentProperties")
    },
    sidebar: {
      outerContainer: document.getElementById("outerContainer"),
      sidebarContainer: document.getElementById("sidebarContainer"),
      toggleButton: document.getElementById("sidebarToggle"),
      resizer: document.getElementById("sidebarResizer"),
      thumbnailButton: document.getElementById("viewThumbnail"),
      outlineButton: document.getElementById("viewOutline"),
      attachmentsButton: document.getElementById("viewAttachments"),
      layersButton: document.getElementById("viewLayers"),
      thumbnailView: document.getElementById("thumbnailView"),
      outlineView: document.getElementById("outlineView"),
      attachmentsView: document.getElementById("attachmentsView"),
      layersView: document.getElementById("layersView"),
      currentOutlineItemButton: document.getElementById("currentOutlineItem")
    },
    findBar: {
      bar: document.getElementById("findbar"),
      toggleButton: document.getElementById("viewFind"),
      findField: document.getElementById("findInput"),
      highlightAllCheckbox: document.getElementById("findHighlightAll"),
      caseSensitiveCheckbox: document.getElementById("findMatchCase"),
      matchDiacriticsCheckbox: document.getElementById("findMatchDiacritics"),
      entireWordCheckbox: document.getElementById("findEntireWord"),
      findMsg: document.getElementById("findMsg"),
      findResultsCount: document.getElementById("findResultsCount"),
      findPreviousButton: document.getElementById("findPrevious"),
      findNextButton: document.getElementById("findNext")
    },
    passwordOverlay: {
      dialog: document.getElementById("passwordDialog"),
      label: document.getElementById("passwordText"),
      input: document.getElementById("password"),
      submitButton: document.getElementById("passwordSubmit"),
      cancelButton: document.getElementById("passwordCancel")
    },
    documentProperties: {
      dialog: document.getElementById("documentPropertiesDialog"),
      closeButton: document.getElementById("documentPropertiesClose"),
      fields: {
        fileName: document.getElementById("fileNameField"),
        fileSize: document.getElementById("fileSizeField"),
        title: document.getElementById("titleField"),
        author: document.getElementById("authorField"),
        subject: document.getElementById("subjectField"),
        keywords: document.getElementById("keywordsField"),
        creationDate: document.getElementById("creationDateField"),
        modificationDate: document.getElementById("modificationDateField"),
        creator: document.getElementById("creatorField"),
        producer: document.getElementById("producerField"),
        version: document.getElementById("versionField"),
        pageCount: document.getElementById("pageCountField"),
        pageSize: document.getElementById("pageSizeField"),
        linearized: document.getElementById("linearizedField")
      }
    },
    altTextDialog: {
      dialog: document.getElementById("altTextDialog"),
      optionDescription: document.getElementById("descriptionButton"),
      optionDecorative: document.getElementById("decorativeButton"),
      textarea: document.getElementById("descriptionTextarea"),
      cancelButton: document.getElementById("altTextCancel"),
      saveButton: document.getElementById("altTextSave")
    },
    newAltTextDialog: {
      dialog: document.getElementById("newAltTextDialog"),
      title: document.getElementById("newAltTextTitle"),
      descriptionContainer: document.getElementById("newAltTextDescriptionContainer"),
      textarea: document.getElementById("newAltTextDescriptionTextarea"),
      disclaimer: document.getElementById("newAltTextDisclaimer"),
      learnMore: document.getElementById("newAltTextLearnMore"),
      imagePreview: document.getElementById("newAltTextImagePreview"),
      createAutomatically: document.getElementById("newAltTextCreateAutomatically"),
      createAutomaticallyButton: document.getElementById("newAltTextCreateAutomaticallyButton"),
      downloadModel: document.getElementById("newAltTextDownloadModel"),
      downloadModelDescription: document.getElementById("newAltTextDownloadModelDescription"),
      error: document.getElementById("newAltTextError"),
      errorCloseButton: document.getElementById("newAltTextCloseButton"),
      cancelButton: document.getElementById("newAltTextCancel"),
      notNowButton: document.getElementById("newAltTextNotNow"),
      saveButton: document.getElementById("newAltTextSave")
    },
    altTextSettingsDialog: {
      dialog: document.getElementById("altTextSettingsDialog"),
      createModelButton: document.getElementById("createModelButton"),
      aiModelSettings: document.getElementById("aiModelSettings"),
      learnMore: document.getElementById("altTextSettingsLearnMore"),
      deleteModelButton: document.getElementById("deleteModelButton"),
      downloadModelButton: document.getElementById("downloadModelButton"),
      showAltTextDialogButton: document.getElementById("showAltTextDialogButton"),
      altTextSettingsCloseButton: document.getElementById("altTextSettingsCloseButton"),
      closeButton: document.getElementById("altTextSettingsCloseButton")
    },
    annotationEditorParams: {
      editorFreeTextFontSize: document.getElementById("editorFreeTextFontSize"),
      editorFreeTextColor: document.getElementById("editorFreeTextColor"),
      editorInkColor: document.getElementById("editorInkColor"),
      editorInkThickness: document.getElementById("editorInkThickness"),
      editorInkOpacity: document.getElementById("editorInkOpacity"),
      editorStampAddImage: document.getElementById("editorStampAddImage"),
      editorFreeHighlightThickness: document.getElementById("editorFreeHighlightThickness"),
      editorHighlightShowAll: document.getElementById("editorHighlightShowAll")
    },
    printContainer: document.getElementById("printContainer")
  };
}
function webViewerLoad() {
  const config = getViewerConfiguration();
  PDFViewerApplication.run(config);
}
document.blockUnblockOnload?.(true);
if (document.readyState === "interactive" || document.readyState === "complete") {
  webViewerLoad();
} else {
  document.addEventListener("DOMContentLoaded", webViewerLoad, true);
}

var __webpack_exports__PDFViewerApplication = __webpack_exports__.PDFViewerApplication;
var __webpack_exports__PDFViewerApplicationConstants = __webpack_exports__.PDFViewerApplicationConstants;
var __webpack_exports__PDFViewerApplicationOptions = __webpack_exports__.PDFViewerApplicationOptions;
export { __webpack_exports__PDFViewerApplication as PDFViewerApplication, __webpack_exports__PDFViewerApplicationConstants as PDFViewerApplicationConstants, __webpack_exports__PDFViewerApplicationOptions as PDFViewerApplicationOptions };
PK
!<,�_�

 defaults/autoconfig/prefcalls.js/* global processLDAPValues */
/* -*- tab-width: 4; indent-tabs-mode: nil; js-indent-level: 4 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* globals gSandbox */

const LDAPSyncQueryContractID = "@mozilla.org/ldapsyncquery;1";

var gIsUTF8;

function getPrefBranch() {
  return Services.prefs.getBranch(null);
}

function pref(prefName, value) {
  try {
    var prefBranch = getPrefBranch();

    if (typeof value == "string") {
      if (gIsUTF8) {
        prefBranch.setStringPref(prefName, value);
        return;
      }
      prefBranch.setCharPref(prefName, value);
    } else if (typeof value == "number") {
      prefBranch.setIntPref(prefName, value);
    } else if (typeof value == "boolean") {
      prefBranch.setBoolPref(prefName, value);
    }
  } catch (e) {
    displayError("pref", e);
  }
}

function defaultPref(prefName, value) {
  try {
    var prefBranch = Services.prefs.getDefaultBranch(null);
    if (typeof value == "string") {
      if (gIsUTF8) {
        prefBranch.setStringPref(prefName, value);
        return;
      }
      prefBranch.setCharPref(prefName, value);
    } else if (typeof value == "number") {
      prefBranch.setIntPref(prefName, value);
    } else if (typeof value == "boolean") {
      prefBranch.setBoolPref(prefName, value);
    }
  } catch (e) {
    displayError("defaultPref", e);
  }
}

function lockPref(prefName, value) {
  try {
    var prefBranch = getPrefBranch();

    if (prefBranch.prefIsLocked(prefName)) {
      prefBranch.unlockPref(prefName);
    }

    defaultPref(prefName, value);

    prefBranch.lockPref(prefName);
  } catch (e) {
    displayError("lockPref", e);
  }
}

function unlockPref(prefName) {
  try {
    var prefBranch = getPrefBranch();
    prefBranch.unlockPref(prefName);
  } catch (e) {
    displayError("unlockPref", e);
  }
}

function getPref(prefName) {
  try {
    var prefBranch = getPrefBranch();

    switch (prefBranch.getPrefType(prefName)) {
      case prefBranch.PREF_STRING:
        if (gIsUTF8) {
          return prefBranch.getStringPref(prefName);
        }
        return prefBranch.getCharPref(prefName);

      case prefBranch.PREF_INT:
        return prefBranch.getIntPref(prefName);

      case prefBranch.PREF_BOOL:
        return prefBranch.getBoolPref(prefName);
      default:
        return null;
    }
  } catch (e) {
    displayError("getPref", e);
  }
  return undefined;
}

function clearPref(prefName) {
  try {
    var prefBranch = getPrefBranch();
    prefBranch.clearUserPref(prefName);
  } catch (e) {}
}

function setLDAPVersion(_version) {
  // No longer implemented, but still here to avoid breaking anything.
}

function getLDAPAttributes(host, base, filter, attribs, isSecure) {
  try {
    var urlSpec =
      "ldap" +
      (isSecure ? "s" : "") +
      "://" +
      host +
      (isSecure ? ":636" : "") +
      "/" +
      base +
      "?" +
      attribs +
      "?sub?" +
      filter;

    // nsILDAP* are only defined in comm-central.
    // eslint-disable-next-line mozilla/valid-ci-uses
    var url = Services.io.newURI(urlSpec).QueryInterface(Ci.nsILDAPURL);

    var ldapquery = Cc[LDAPSyncQueryContractID].createInstance(
      // eslint-disable-next-line mozilla/valid-ci-uses
      Ci.nsILDAPSyncQuery
    );
    // user supplied method
    if ("processLDAPValues" in gSandbox) {
      gSandbox.processLDAPValues(ldapquery.getQueryResults(url));
    } else {
      processLDAPValues(ldapquery.getQueryResults(url));
    }
  } catch (e) {
    displayError("getLDAPAttributes", e);
  }
}

function getLDAPValue(str, key) {
  try {
    if (str == null || key == null) {
      return null;
    }

    var search_key = "\n" + key + "=";

    var start_pos = str.indexOf(search_key);
    if (start_pos == -1) {
      return null;
    }

    start_pos += search_key.length;

    var end_pos = str.indexOf("\n", start_pos);
    if (end_pos == -1) {
      end_pos = str.length;
    }

    return str.substring(start_pos, end_pos);
  } catch (e) {
    displayError("getLDAPValue", e);
  }
  return undefined;
}

function displayError(funcname, message) {
  try {
    var bundle = Services.strings.createBundle(
      "chrome://autoconfig/locale/autoconfig.properties"
    );

    var title = bundle.GetStringFromName("autoConfigTitle");
    var msg = bundle.formatStringFromName("autoConfigMsg", [funcname]);
    Services.prompt.alert(null, title, msg + " " + message);
  } catch (e) {}
}

function getenv(name) {
  try {
    return Services.env.get(name);
  } catch (e) {
    displayError("getEnvironment", e);
  }
  return undefined;
}

var APIs = {
  pref,
  defaultPref,
  lockPref,
  unlockPref,
  getPref,
  clearPref,
  setLDAPVersion,
  getLDAPAttributes,
  getLDAPValue,
  displayError,
  getenv,
};

for (let [defineAs, func] of Object.entries(APIs)) {
  Cu.exportFunction(func, gSandbox, { defineAs });
}

Object.defineProperty(Cu.waiveXrays(gSandbox), "gIsUTF8", {
  get: Cu.exportFunction(() => gIsUTF8, gSandbox),
});
PK
!<7:�5	5	+defaults/backgroundtasks/backgroundtasks.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//@line 6 "$SRCDIR/toolkit/components/backgroundtasks/defaults/backgroundtasks.js"

// These preferences override Gecko preferences in `greprefs.js`.  Use
// `backgroundtasks_browser.js` to override browser/-specific preferences in
// `firefox.js`.

/* global pref */

pref("browser.dom.window.dump.enabled", true);
pref("devtools.console.stdout.chrome", true);

pref("browser.cache.disk.enable", false);
pref("permissions.memory_only", true);

// For testing only: used to test that backgroundtask-specific prefs are
// processed.  This just needs to be an unusual integer in the range 0..127.
pref("test.backgroundtask_specific_pref.exitCode", 79);

// Enable the browser toolbox by default.  The browser toolbox is available only
// when launching the background task with `--jsdebugger` on the command line,
// and an attacker who can launch background task processes with arbitrary
// parameters and execution environment can already access this functionality,
// so there's no need to restrict access via preferences.
pref("devtools.chrome.enabled", true);
pref("devtools.debugger.remote-enabled", true);
pref("devtools.debugger.prompt-connection", false);

// Background tasks do not persist the cookie database: they should
// not be using cookies for network requests.
pref("network.cookie.noPersistentStorage", true);

// Background tasks don't need to worry about perceived performance. We disable
// fast shutdown to reduce the risk of open file handles preventing cleanup of
// the ephemeral profile directory.
pref("toolkit.shutdown.fastShutdownStage", 0);

// Avoid a race between initializing font lists and rapid shutdown,
// particularly on macOS.  Compare Bug 1777332.
pref("gfx.font-list-omt.enabled", false);

// Prevent key#.db and cert#.db from being created in the ephemeral profile.
pref("security.nocertdb", true);

// Prevent asynchronous preference writes.
pref("preferences.allow.omt-write", false);

// Enable automatic restarts during background updates for Nightly builds.
//@line 55 "$SRCDIR/toolkit/components/backgroundtasks/defaults/backgroundtasks.js"
pref("app.update.background.automaticRestartEnabled", false);
PK
!<��p!p!res/EditorOverride.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

*|* {
  -moz-user-modify: read-write;
}

/* Styles to alter look of things in the Editor content window
 *  that should NOT be removed when we display in completely WYSIWYG
 *  "Browser Preview" mode.
 *  Anything that should change, like appearance of table borders
 *  and Named Anchors, should be placed in EditorContent.css instead of here.
*/

/* Primary cursor is text I-beam */

::-moz-canvas, a:link {
  cursor: text;
}

/* Use default arrow over objects with size that
   are selected when clicked on.
   Override the browser's pointer cursor over links
*/

img, img[usemap], area,
object, object[usemap],
applet, hr, button, input, textarea, select,
a:link img, a:visited img, a:active img,
a[name]:-moz-only-whitespace {
  cursor: default;
}

a:visited, a:active {
  cursor: text;
}

/* Prevent clicking on links from going to link */
a:link img, a:visited img {
  -moz-user-input: none;
}

/* We suppress user/author's prefs for link underline,
   so we must set explicitly. This isn't good!
*/
a:link {
  color: -moz-hyperlinktext;
}

/* Allow double-clicks on these widgets to open properties dialogs
   XXX except when the widget has disabled attribute */
input, button, textarea {
  user-select: all !important;
  -moz-user-input: auto !important;
}

/* XXX Still need a better way of blocking other events to these widgets */
select, input[disabled], input[type="checkbox"], input[type="radio"], input[type="file"] {
  user-select: all !important;
  -moz-user-input: none !important;
}

input[type="hidden"] {
  border: 1px solid black !important;
  visibility: visible !important;
}

label {
    user-select: all !important;
}

option {
  user-select: text !important;
}

#mozToc.readonly {
  user-select: all !important;
  -moz-user-input: none !important;
}

/* the following rules are for Image Resizing */

span[\_moz_anonclass="mozResizer"] {
  width: 5px;
  height: 5px;
  position: absolute;
  border: 1px black solid;
  background-color: white;
  user-select: none;
  z-index: 2147483646; /* max value -1 for this property */
}

/* we can't use :active below */
span[\_moz_anonclass="mozResizer"][\_moz_activated],
span[\_moz_anonclass="mozResizer"]:hover {
  background-color: black;
}

span[\_moz_anonclass="mozResizer"].hidden,
span[\_moz_anonclass="mozResizingShadow"].hidden,
img[\_moz_anonclass="mozResizingShadow"].hidden,
span[\_moz_anonclass="mozGrabber"].hidden,
span[\_moz_anonclass="mozResizingInfo"].hidden,
a[\_moz_anonclass="mozTableRemoveRow"].hidden,
a[\_moz_anonclass="mozTableRemoveColumn"].hidden {
  display: none !important;
}

span[\_moz_anonclass="mozResizer"][anonlocation="nw"] {
  cursor: nw-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="n"] {
  cursor: n-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="ne"] {
  cursor: ne-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="w"] {
  cursor: w-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="e"] {
  cursor: e-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="sw"] {
  cursor: sw-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="s"] {
  cursor: s-resize;
}
span[\_moz_anonclass="mozResizer"][anonlocation="se"] {
  cursor: se-resize;
}

span[\_moz_anonclass="mozResizingShadow"],
img[\_moz_anonclass="mozResizingShadow"] {
  outline: thin dashed black;
  user-select: none;
  opacity: 0.5;
  position: absolute;
  z-index: 2147483647; /* max value for this property */
}

span[\_moz_anonclass="mozResizingInfo"] {
  font-family: sans-serif;
  font-size: x-small;
  color: black;
  background-color: #d0d0d0;
  border: ridge 2px #d0d0d0;
  padding: 2px;
  position: absolute;
  z-index: 2147483647; /* max value for this property */
}

img[\_moz_resizing] {
  outline: thin solid black;
}

*[\_moz_abspos] {
  outline: silver ridge 2px;
  z-index: 2147483645 !important; /* max value -2 for this property */
}
*[\_moz_abspos="white"] {
  background-color: white !important;
}
*[\_moz_abspos="black"] {
  background-color: black !important;
}

span[\_moz_anonclass="mozGrabber"] {
  outline: ridge 2px silver;
  padding: 2px;
  position: absolute;
  width: 12px;
  height: 12px;
  background-image: url("resource://gre/res/grabber.gif");
  background-repeat: no-repeat;
  background-position: center center;
  user-select: none;
  cursor: move;
  z-index: 2147483647; /* max value for this property */
}

/* INLINE TABLE EDITING */

a[\_moz_anonclass="mozTableAddColumnBefore"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 4px;
  height: 8px;
  background-image: url("resource://gre/res/table-add-column-before.gif");
  background-repeat: no-repeat;
  background-position: center center;
  user-select: none !important;
}

a[\_moz_anonclass="mozTableAddColumnBefore"]:hover {
  background-image: url("resource://gre/res/table-add-column-before-hover.gif");
}

a[\_moz_anonclass="mozTableAddColumnBefore"]:active {
  background-image: url("resource://gre/res/table-add-column-before-active.gif");
}

a[\_moz_anonclass="mozTableAddColumnAfter"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 4px;
  height: 8px;
  background-image: url("resource://gre/res/table-add-column-after.gif");
  background-repeat: no-repeat;
  background-position: center center;
  user-select: none !important;
}

a[\_moz_anonclass="mozTableAddColumnAfter"]:hover {
  background-image: url("resource://gre/res/table-add-column-after-hover.gif");
}

a[\_moz_anonclass="mozTableAddColumnAfter"]:active {
  background-image: url("resource://gre/res/table-add-column-after-active.gif");
}

a[\_moz_anonclass="mozTableRemoveColumn"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 8px;
  height: 8px;
  background-image: url("resource://gre/res/table-remove-column.gif");
  background-repeat: no-repeat;
  background-position: center center;
  user-select: none !important;
}

a[\_moz_anonclass="mozTableRemoveColumn"]:hover {
  background-image: url("resource://gre/res/table-remove-column-hover.gif");
}

a[\_moz_anonclass="mozTableRemoveColumn"]:active {
  background-image: url("resource://gre/res/table-remove-column-active.gif");
}

a[\_moz_anonclass="mozTableAddRowBefore"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 8px;
  height: 4px;
  background-image: url("resource://gre/res/table-add-row-before.gif");
  background-repeat: no-repeat;
  background-position: center center;
  user-select: none !important;
}

a[\_moz_anonclass="mozTableAddRowBefore"]:hover {
  background-image: url("resource://gre/res/table-add-row-before-hover.gif");
}

a[\_moz_anonclass="mozTableAddRowBefore"]:active {
  background-image: url("resource://gre/res/table-add-row-before-active.gif");
}

a[\_moz_anonclass="mozTableAddRowAfter"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 8px;
  height: 4px;
  background-image: url("resource://gre/res/table-add-row-after.gif");
  background-repeat: no-repeat;
  background-position: center center;
  user-select: none !important;
}

a[\_moz_anonclass="mozTableAddRowAfter"]:hover {
  background-image: url("resource://gre/res/table-add-row-after-hover.gif");
}

a[\_moz_anonclass="mozTableAddRowAfter"]:active {
  background-image: url("resource://gre/res/table-add-row-after-active.gif");
}

a[\_moz_anonclass="mozTableRemoveRow"] {
  position: absolute;
  z-index: 2147483647; /* max value for this property */
  text-decoration: none !important;
  border: none 0px !important;
  width: 8px;
  height: 8px;
  background-image: url("resource://gre/res/table-remove-row.gif");
  background-repeat: no-repeat;
  background-position: center center;
  user-select: none !important;
}

a[\_moz_anonclass="mozTableRemoveRow"]:hover {
  background-image: url("resource://gre/res/table-remove-row-hover.gif");
}

a[\_moz_anonclass="mozTableRemoveRow"]:active {
  background-image: url("resource://gre/res/table-remove-row-active.gif");
}
PK
!<Ұ
i::%res/table-add-column-after-active.gifGIF89a����������!�,�&���[J�;PK
!<v�7�::$res/table-add-column-after-hover.gifGIF89a�����1���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!�,@`"L��`ƒ
;PK
!<x�
::res/table-add-column-after.gifGIF89a���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!�,@`"L��`ƒ
;PK
!<�Z(99&res/table-add-column-before-active.gifGIF89a����������!�,
�s ���Kf;PK
!<M�99%res/table-add-column-before-hover.gifGIF89a�����1���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!�,0� "ThaA�;PK
!<�/�99res/table-add-column-before.gifGIF89a���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!�,0� "ThaA�;PK
!<<�Ed99"res/table-add-row-after-active.gifGIF89a����������!�,
�!��܋�;PK
!<K::!res/table-add-row-after-hover.gifGIF89a�����1���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!�,(0��B�2;PK
!<�ؿ::res/table-add-row-after.gifGIF89a���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!�,(0��B�2;PK
!<ބw�99#res/table-add-row-before-active.gifGIF89a����������!�,
����ZbQ;PK
!<��99"res/table-add-row-before-hover.gifGIF89a�����1���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!�,@�A�0aA�!;PK
!<�?499res/table-add-row-before.gifGIF89a���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!�,@�A�0aA�!;PK
!<�	$�CC"res/table-remove-column-active.gifGIF89a�����1���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!�, @���	"@�a�FlH�`B�
;PK
!<�&II!res/table-remove-column-hover.gifGIF89a�����1���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!�,&@���
0`a†6\81"E�*\x���;PK
!<��V)IIres/table-remove-column.gifGIF89a���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!�,&@���
0`a†6\81"E�*\x���;PK
!<�	$�CCres/table-remove-row-active.gifGIF89a�����1���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!�, @���	"@�a�FlH�`B�
;PK
!<�&IIres/table-remove-row-hover.gifGIF89a�����1���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!�,&@���
0`a†6\81"E�*\x���;PK
!<��V)IIres/table-remove-row.gifGIF89a���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������!�,&@���
0`a†6\81"E�*\x���;PK
!<�!r�ZZres/grabber.gifGIF89a�sss������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������,?	H�����P���D�	 �D	5r�qȅ'�H�"D�!j)�`@;PK
!<�7�ʥʥres/fonts/mathfont.properties



operator.\u0021.prefix = lspace:0 rspace:0 # !
operator.\u0021.postfix = lspace:0 rspace:0 # !
operator.\u0021\u0021.postfix = lspace:0 rspace:0 # !!
operator.\u0021\u003D.infix = lspace:5 rspace:5 # !=
operator.\u0022.postfix = lspace:0 rspace:0 # quotation mark
operator.\u0025.infix = lspace:3 rspace:3 # percent sign
operator.\u0025.postfix = lspace:0 rspace:0 # percent sign
operator.\u0026.postfix = lspace:0 rspace:0 # &amp;
operator.\u0026\u0026.infix = lspace:4 rspace:4 # &amp;&amp;
operator.\u0027.postfix = lspace:0 rspace:0 accent # '
operator.\u0028.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # (
operator.\u0029.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # )
operator.\u002A.infix = lspace:3 rspace:3 # *
operator.\u002A\u002A.infix = lspace:3 rspace:3 # **
operator.\u002A\u003D.infix = lspace:5 rspace:5 # *=
operator.\u002B.infix = lspace:4 rspace:4 # +
operator.\u002B.prefix = lspace:0 rspace:0 # +
operator.\u002B\u002B.postfix = lspace:0 rspace:0 # ++
operator.\u002B\u003D.infix = lspace:5 rspace:5 # +=
operator.\u002C.infix = lspace:0 rspace:3 separator # ,
operator.\u002D.infix = lspace:4 rspace:4 # -
operator.\u002D.prefix = lspace:0 rspace:0 # -
operator.\u002D\u002D.postfix = lspace:0 rspace:0 # --
operator.\u002D\u003D.infix = lspace:5 rspace:5 # -=
operator.\u002D\u003E.infix = lspace:5 rspace:5 # ->
operator.\u002E.infix = lspace:3 rspace:3 # .
operator.\u002F.infix = lspace:4 rspace:4 direction:vertical # solidus
operator.\u002F\u002F.infix = lspace:5 rspace:5 # //
operator.\u002F\u003D.infix = lspace:5 rspace:5 # /=
operator.\u003A.infix = lspace:0 rspace:3 # :
operator.\u003A\u003D.infix = lspace:5 rspace:5 # :=
operator.\u003B.infix = lspace:0 rspace:3 separator # ;
operator.\u003C.infix = lspace:5 rspace:5 # &lt;
operator.\u003C\u003D.infix = lspace:5 rspace:5 # &lt;=
operator.\u003C\u003E.infix = lspace:3 rspace:3 # &lt;>
operator.\u003D.infix = lspace:5 rspace:5 direction:horizontal # =
operator.\u003D\u003D.infix = lspace:5 rspace:5 # ==
operator.\u003E.infix = lspace:5 rspace:5 # >
operator.\u003E\u003D.infix = lspace:5 rspace:5 # >=
operator.\u003F.infix = lspace:3 rspace:3 # ?
operator.\u0040.infix = lspace:3 rspace:3 # @
operator.\u005B.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # [
operator.\u005C.infix = lspace:0 rspace:0 # reverse solidus
operator.\u005D.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # ]
operator.\u005E.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &Hat; circumflex accent
operator.\u005E.infix = lspace:3 rspace:3 direction:horizontal # ^
operator.\u005F.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # _ low line
operator.\u005F.infix = lspace:0 rspace:0 direction:horizontal # _ low line
operator.\u0060.postfix = lspace:0 rspace:0 accent # &DiacriticalGrave;
operator.\u007B.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # {
operator.\u007C.infix = lspace:5 rspace:5 fence direction:vertical # &VerticalLine; |
operator.\u007C.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # |
operator.\u007C.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # |
operator.\u007C\u007C.infix = lspace:5 rspace:5 fence direction:vertical # ||
operator.\u007C\u007C.prefix = lspace:0 rspace:0 fence direction:vertical # multiple character operator: ||
operator.\u007C\u007C.postfix = lspace:0 rspace:0 fence direction:vertical # multiple character operator: ||
operator.\u007D.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # }
operator.\u007E.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # ~ tilde
operator.\u00A8.postfix = lspace:0 rspace:0 accent # &DoubleDot;
operator.\u00AC.prefix = lspace:0 rspace:0 # not sign
operator.\u00AF.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &OverBar;
operator.\u00B0.postfix = lspace:0 rspace:0 # degree sign
operator.\u00B1.infix = lspace:4 rspace:4 # &PlusMinus;
operator.\u00B1.prefix = lspace:0 rspace:0 # &PlusMinus;
operator.\u00B2.postfix = lspace:0 rspace:0 # superscript two
operator.\u00B3.postfix = lspace:0 rspace:0 # superscript three
operator.\u00B4.postfix = lspace:0 rspace:0 accent # &DiacriticalAcute;
operator.\u00B7.infix = lspace:3 rspace:3 # &CenterDot;
operator.\u00B8.postfix = lspace:0 rspace:0 accent # &Cedilla;
operator.\u00B9.postfix = lspace:0 rspace:0 # superscript one
operator.\u00D7.infix = lspace:3 rspace:3 # multiplication sign
operator.\u00F7.infix = lspace:4 rspace:4 # division sign
operator.\u02C6.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # modifier letter circumflex accent
operator.\u02C7.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &Hacek; caron
operator.\u02C9.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # modifier letter macron
operator.\u02CA.postfix = lspace:0 rspace:0 accent # modifier letter acute accent
operator.\u02CB.postfix = lspace:0 rspace:0 accent # modifier letter grave accent
operator.\u02CD.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # modifier letter low macron
operator.\u02D8.postfix = lspace:0 rspace:0 accent # &Breve;
operator.\u02D9.postfix = lspace:0 rspace:0 accent # &DiacriticalDot;
operator.\u02DA.postfix = lspace:0 rspace:0 accent # ring above
operator.\u02DC.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &DiacriticalTilde; small tilde
operator.\u02DD.postfix = lspace:0 rspace:0 accent # &DiacriticalDoubleAcute;
operator.\u02F7.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # modifier letter low tilde
operator.\u0302.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # combining circumflex accent
operator.\u0311.postfix = lspace:0 rspace:0 accent # &DownBreve;
operator.\u2016.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &Vert; &Verbar;
operator.\u2016.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &Vert; &Verbar;
operator.\u2018.prefix = lspace:0 rspace:0 fence # &OpenCurlyQuote;
operator.\u2019.postfix = lspace:0 rspace:0 fence # &CloseCurlyQuote;
operator.\u201A.postfix = lspace:0 rspace:0 # single low-9 quotation mark
operator.\u201B.postfix = lspace:0 rspace:0 # single high-reversed-9 quotation mark
operator.\u201C.prefix = lspace:0 rspace:0 fence # &OpenCurlyDoubleQuote;
operator.\u201D.postfix = lspace:0 rspace:0 fence # &CloseCurlyDoubleQuote;
operator.\u201E.postfix = lspace:0 rspace:0 # double low-9 quotation mark
operator.\u201F.postfix = lspace:0 rspace:0 # double high-reversed-9 quotation mark
operator.\u2022.infix = lspace:3 rspace:3 # bullet
operator.\u2032.postfix = lspace:0 rspace:0 # prime
operator.\u2033.postfix = lspace:0 rspace:0 # double prime
operator.\u2034.postfix = lspace:0 rspace:0 # triple prime
operator.\u2035.postfix = lspace:0 rspace:0 # reversed prime
operator.\u2036.postfix = lspace:0 rspace:0 # reversed double prime
operator.\u2037.postfix = lspace:0 rspace:0 # reversed triple prime
operator.\u203E.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # overline
operator.\u2043.infix = lspace:3 rspace:3 # hyphen bullet
operator.\u2044.infix = lspace:4 rspace:4 direction:vertical # fraction slash
operator.\u2057.postfix = lspace:0 rspace:0 # quadruple prime
operator.\u2061.infix = lspace:0 rspace:0 # &ApplyFunction;
operator.\u2062.infix = lspace:0 rspace:0 # &InvisibleTimes;
operator.\u2063.infix = lspace:0 rspace:0 separator # &InvisibleComma;
operator.\u2064.infix = lspace:0 rspace:0 # invisible plus
operator.\u20DB.postfix = lspace:0 rspace:0 accent # &TripleDot;
operator.\u20DC.postfix = lspace:0 rspace:0 accent # combining four dots above
operator.\u2145.prefix = lspace:3 rspace:0 # &CapitalDifferentialD;
operator.\u2146.prefix = lspace:3 rspace:0 # &DifferentialD;
operator.\u2190.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftArrow;
operator.\u2191.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpArrow;
operator.\u2192.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightArrow;
operator.\u2193.infix = lspace:5 rspace:5 stretchy direction:vertical # &DownArrow;
operator.\u2194.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftRightArrow;
operator.\u2195.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpDownArrow;
operator.\u2196.infix = lspace:5 rspace:5 direction:vertical # &UpperLeftArrow;
operator.\u2197.infix = lspace:5 rspace:5 direction:vertical # &UpperRightArrow;
operator.\u2198.infix = lspace:5 rspace:5 direction:horizontal # &LowerRightArrow;
operator.\u2199.infix = lspace:5 rspace:5 direction:horizontal # &LowerLeftArrow;
operator.\u219A.infix = lspace:5 rspace:5 stretchy accent # leftwards arrow with stroke
operator.\u219B.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow with stroke
operator.\u219C.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards wave arrow
operator.\u219D.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards wave arrow
operator.\u219E.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards two headed arrow
operator.\u219F.infix = lspace:5 rspace:5 stretchy accent direction:vertical # upwards two headed arrow
operator.\u21A0.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards two headed arrow
operator.\u21A1.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards two headed arrow
operator.\u21A2.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards arrow with tail
operator.\u21A3.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards arrow with tail
operator.\u21A4.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftTeeArrow;
operator.\u21A5.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpTeeArrow;
operator.\u21A6.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightTeeArrow;
operator.\u21A7.infix = lspace:5 rspace:5 stretchy direction:vertical # &DownTeeArrow;
operator.\u21A8.infix = lspace:5 rspace:5 stretchy direction:vertical # up down arrow with base
operator.\u21A9.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &hookleftarrow; &larrhk;
operator.\u21AA.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &hookrightarrow; &rarrhk;
operator.\u21AB.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards arrow with loop
operator.\u21AC.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards arrow with loop
operator.\u21AD.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # left right wave arrow
operator.\u21AE.infix = lspace:5 rspace:5 stretchy accent # left right arrow with stroke
operator.\u21AF.infix = lspace:5 rspace:5 direction:vertical # downwards zigzag arrow
operator.\u21B0.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards arrow with tip leftwards
operator.\u21B1.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards arrow with tip rightwards
operator.\u21B2.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards arrow with tip leftwards
operator.\u21B3.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards arrow with tip rightwards
operator.\u21B4.infix = lspace:5 rspace:5 stretchy direction:horizontal # rightwards arrow with corner downwards
operator.\u21B5.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards arrow with corner leftwards
operator.\u21B6.infix = lspace:5 rspace:5 accent # anticlockwise top semicircle arrow
operator.\u21B7.infix = lspace:5 rspace:5 accent # clockwise top semicircle arrow
operator.\u21B8.infix = lspace:5 rspace:5 # north west arrow to long bar
operator.\u21B9.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards arrow to bar over rightwards arrow to bar
operator.\u21BA.infix = lspace:5 rspace:5 # anticlockwise open circle arrow
operator.\u21BB.infix = lspace:5 rspace:5 # clockwise open circle arrow
operator.\u21BC.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftVector;
operator.\u21BD.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DownLeftVector;
operator.\u21BE.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightUpVector;
operator.\u21BF.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftUpVector;
operator.\u21C0.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightVector;
operator.\u21C1.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DownRightVector;
operator.\u21C2.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightDownVector;
operator.\u21C3.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftDownVector;
operator.\u21C4.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightArrowLeftArrow;
operator.\u21C5.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpArrowDownArrow;
operator.\u21C6.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftArrowRightArrow;
operator.\u21C7.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards paired arrows
operator.\u21C8.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards paired arrows
operator.\u21C9.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards paired arrows
operator.\u21CA.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards paired arrows
operator.\u21CB.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &ReverseEquilibrium;
operator.\u21CC.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &Equilibrium;
operator.\u21CD.infix = lspace:5 rspace:5 stretchy accent # leftwards double arrow with stroke
operator.\u21CE.infix = lspace:5 rspace:5 stretchy accent # left right double arrow with stroke
operator.\u21CF.infix = lspace:5 rspace:5 stretchy accent # rightwards double arrow with stroke
operator.\u21D0.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DoubleLeftArrow;
operator.\u21D1.infix = lspace:5 rspace:5 stretchy direction:vertical # &DoubleUpArrow;
operator.\u21D2.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &Implies; &DoubleRightArrow;
operator.\u21D3.infix = lspace:5 rspace:5 stretchy direction:vertical # &DoubleDownArrow;
operator.\u21D4.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DoubleLeftRightArrow;
operator.\u21D5.infix = lspace:5 rspace:5 stretchy direction:vertical # &DoubleUpDownArrow;
operator.\u21D6.infix = lspace:5 rspace:5 # north west double arrow
operator.\u21D7.infix = lspace:5 rspace:5 # north east double arrow
operator.\u21D8.infix = lspace:5 rspace:5 # south east double arrow
operator.\u21D9.infix = lspace:5 rspace:5 # south west double arrow
operator.\u21DA.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards triple arrow
operator.\u21DB.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards triple arrow
operator.\u21DC.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards squiggle arrow
operator.\u21DD.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards squiggle arrow
operator.\u21DE.infix = lspace:5 rspace:5 stretchy # upwards arrow with double stroke
operator.\u21DF.infix = lspace:5 rspace:5 stretchy # downwards arrow with double stroke
operator.\u21E0.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards dashed arrow
operator.\u21E1.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards dashed arrow
operator.\u21E2.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards dashed arrow
operator.\u21E3.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards dashed arrow
operator.\u21E4.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftArrowBar;
operator.\u21E5.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightArrowBar;
operator.\u21E6.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards white arrow
operator.\u21E7.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white arrow
operator.\u21E8.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards white arrow
operator.\u21E9.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards white arrow
operator.\u21EA.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white arrow from bar
operator.\u21EB.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white arrow on pedestal
operator.\u21EC.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white arrow on pedestal with horizontal bar
operator.\u21ED.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white arrow on pedestal with vertical bar
operator.\u21EE.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white double arrow
operator.\u21EF.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards white double arrow on pedestal
operator.\u21F0.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards white arrow from wall
operator.\u21F1.infix = lspace:5 rspace:5 # north west arrow to corner
operator.\u21F2.infix = lspace:5 rspace:5 # south east arrow to corner
operator.\u21F3.infix = lspace:5 rspace:5 stretchy direction:vertical # up down white arrow
operator.\u21F4.infix = lspace:5 rspace:5 stretchy accent # right arrow with small circle
operator.\u21F5.infix = lspace:5 rspace:5 stretchy direction:vertical # &DownArrowUpArrow;
operator.\u21F6.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # three rightwards arrows
operator.\u21F7.infix = lspace:5 rspace:5 stretchy accent # leftwards arrow with vertical stroke
operator.\u21F8.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow with vertical stroke
operator.\u21F9.infix = lspace:5 rspace:5 stretchy accent # left right arrow with vertical stroke
operator.\u21FA.infix = lspace:5 rspace:5 stretchy accent # leftwards arrow with double vertical stroke
operator.\u21FB.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow with double vertical stroke
operator.\u21FC.infix = lspace:5 rspace:5 stretchy accent # left right arrow with double vertical stroke
operator.\u21FD.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards open-headed arrow
operator.\u21FE.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards open-headed arrow
operator.\u21FF.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # left right open-headed arrow
operator.\u2200.prefix = lspace:0 rspace:0 # &ForAll;
operator.\u2201.prefix = lspace:0 rspace:0 # complement
operator.\u2202.prefix = lspace:3 rspace:0 # &PartialD;
operator.\u2203.prefix = lspace:0 rspace:0 # &Exists;
operator.\u2204.prefix = lspace:0 rspace:0 # &NotExists;
operator.\u2206.infix = lspace:0 rspace:0 # increment
operator.\u2207.prefix = lspace:0 rspace:0 # &Del;
operator.\u2208.infix = lspace:5 rspace:5 # &Element;
operator.\u2209.infix = lspace:5 rspace:5 # &NotElement;
operator.\u220A.infix = lspace:5 rspace:5 # small element of
operator.\u220B.infix = lspace:5 rspace:5 # &SuchThat; &ReverseElement;
operator.\u220C.infix = lspace:5 rspace:5 # &NotReverseElement;
operator.\u220D.infix = lspace:5 rspace:5 # small contains as member
operator.\u220F.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # &Product;
operator.\u2210.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # &Coproduct;
operator.\u2211.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # &Sum;
operator.\u2212.infix = lspace:4 rspace:4 # official Unicode minus sign
operator.\u2212.prefix = lspace:0 rspace:0 # official Unicode minus sign
operator.\u2213.infix = lspace:4 rspace:4 # &MinusPlus;
operator.\u2213.prefix = lspace:0 rspace:0 # &MinusPlus;
operator.\u2214.infix = lspace:4 rspace:4 # dot plus
operator.\u2215.infix = lspace:4 rspace:4 direction:vertical # division slash
operator.\u2216.infix = lspace:4 rspace:4 direction:vertical # set minus
operator.\u2217.infix = lspace:3 rspace:3 # asterisk operator
operator.\u2218.infix = lspace:3 rspace:3 # &SmallCircle;
operator.\u2219.infix = lspace:3 rspace:3 # bullet operator
operator.\u221A.prefix = lspace:3 rspace:0 direction:vertical # &Sqrt;
operator.\u221B.prefix = lspace:3 rspace:0 # cube root
operator.\u221C.prefix = lspace:3 rspace:0 # fourth root
operator.\u221D.infix = lspace:5 rspace:5 # &Proportional;
operator.\u221F.prefix = lspace:0 rspace:0 # right angle
operator.\u2220.prefix = lspace:0 rspace:0 # angle
operator.\u2221.prefix = lspace:0 rspace:0 # measured angle
operator.\u2222.prefix = lspace:0 rspace:0 # spherical angle
operator.\u2223.infix = lspace:5 rspace:5 direction:vertical # divides
operator.\u2224.infix = lspace:5 rspace:5 # &NotVerticalBar;
operator.\u2225.infix = lspace:5 rspace:5 direction:vertical # parallel to
operator.\u2226.infix = lspace:5 rspace:5 # &NotDoubleVerticalBar;
operator.\u2227.infix = lspace:4 rspace:4 # &wedge;
operator.\u2228.infix = lspace:4 rspace:4 # &vee;
operator.\u2229.infix = lspace:4 rspace:4 # &cap;
operator.\u222A.infix = lspace:4 rspace:4 # &cup;
operator.\u222B.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # &Integral;
operator.\u222C.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # double integral
operator.\u222D.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # triple integral
operator.\u222E.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # &ContourIntegral;
operator.\u222F.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # &DoubleContourIntegral;
operator.\u2230.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # volume integral
operator.\u2231.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # clockwise integral
operator.\u2232.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # &ClockwiseContourIntegral;
operator.\u2233.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # &CounterClockwiseContourIntegral;
operator.\u2234.prefix = lspace:0 rspace:0 # therefore
operator.\u2235.prefix = lspace:0 rspace:0 # because
operator.\u2236.infix = lspace:4 rspace:4 # ratio
operator.\u2237.infix = lspace:5 rspace:5 # &Colon; &Proportion;
operator.\u2238.infix = lspace:4 rspace:4 # dot minus
operator.\u2239.infix = lspace:5 rspace:5 # excess
operator.\u223A.infix = lspace:5 rspace:5 # geometric proportion
operator.\u223B.infix = lspace:5 rspace:5 # homothetic
operator.\u223C.infix = lspace:5 rspace:5 # &Tilde;
operator.\u223C.prefix = lspace:0 rspace:0 # tilde operator
operator.\u223D.infix = lspace:5 rspace:5 # reversed tilde
operator.\u223E.infix = lspace:5 rspace:5 # inverted lazy s
operator.\u2240.infix = lspace:3 rspace:3 # &VerticalTilde;
operator.\u2241.infix = lspace:5 rspace:5 # &NotTilde;
operator.\u2242.infix = lspace:5 rspace:5 # &EqualTilde;
operator.\u2243.infix = lspace:5 rspace:5 # &TildeEqual;
operator.\u2244.infix = lspace:5 rspace:5 # &NotTildeEqual;
operator.\u2245.infix = lspace:5 rspace:5 # &TildeFullEqual;
operator.\u2246.infix = lspace:5 rspace:5 # approximately but not actually equal to
operator.\u2247.infix = lspace:5 rspace:5 # &NotTildeFullEqual;
operator.\u2248.infix = lspace:5 rspace:5 # &TildeTilde;
operator.\u2249.infix = lspace:5 rspace:5 # &NotTildeTilde;
operator.\u224A.infix = lspace:5 rspace:5 # almost equal or equal to
operator.\u224B.infix = lspace:5 rspace:5 # triple tilde
operator.\u224C.infix = lspace:5 rspace:5 # all equal to
operator.\u224D.infix = lspace:5 rspace:5 # &CupCap;
operator.\u224E.infix = lspace:5 rspace:5 # &HumpDownHump;
operator.\u224F.infix = lspace:5 rspace:5 # &HumpEqual;
operator.\u2250.infix = lspace:5 rspace:5 # &DotEqual;
operator.\u2251.infix = lspace:5 rspace:5 # geometrically equal to
operator.\u2252.infix = lspace:5 rspace:5 # approximately equal to or the image of
operator.\u2253.infix = lspace:5 rspace:5 # image of or approximately equal to
operator.\u2254.infix = lspace:5 rspace:5 # &Assign;
operator.\u2255.infix = lspace:5 rspace:5 # equals colon
operator.\u2256.infix = lspace:5 rspace:5 # ring in equal to
operator.\u2257.infix = lspace:5 rspace:5 # ring equal to
operator.\u2258.infix = lspace:5 rspace:5 # corresponds to
operator.\u2259.infix = lspace:5 rspace:5 # estimates
operator.\u225A.infix = lspace:5 rspace:5 # equiangular to
operator.\u225B.infix = lspace:5 rspace:5 # star equals
operator.\u225C.infix = lspace:5 rspace:5 # delta equal to
operator.\u225D.infix = lspace:5 rspace:5 # equal to by definition
operator.\u225E.infix = lspace:5 rspace:5 # measured by
operator.\u225F.infix = lspace:5 rspace:5 # questioned equal to
operator.\u2260.infix = lspace:5 rspace:5 # &NotEqual;
operator.\u2261.infix = lspace:5 rspace:5 # &Congruent;
operator.\u2262.infix = lspace:5 rspace:5 # &NotCongruent;
operator.\u2263.infix = lspace:5 rspace:5 # strictly equivalent to
operator.\u2264.infix = lspace:5 rspace:5 # &le;
operator.\u2265.infix = lspace:5 rspace:5 # &GreaterEqual;
operator.\u2266.infix = lspace:5 rspace:5 # &LessFullEqual;
operator.\u2267.infix = lspace:5 rspace:5 # &GreaterFullEqual;
operator.\u2268.infix = lspace:5 rspace:5 # less-than but not equal to
operator.\u2269.infix = lspace:5 rspace:5 # greater-than but not equal to
operator.\u226A.infix = lspace:5 rspace:5 # &NestedLessLess;
operator.\u226B.infix = lspace:5 rspace:5 # &NestedGreaterGreater;
operator.\u226C.infix = lspace:5 rspace:5 # between
operator.\u226D.infix = lspace:5 rspace:5 # &NotCupCap;
operator.\u226E.infix = lspace:5 rspace:5 # &NotLess;
operator.\u226F.infix = lspace:5 rspace:5 # &NotGreater;
operator.\u2270.infix = lspace:5 rspace:5 # &NotLessEqual;
operator.\u2271.infix = lspace:5 rspace:5 # &NotGreaterEqual;
operator.\u2272.infix = lspace:5 rspace:5 # &LessTilde;
operator.\u2273.infix = lspace:5 rspace:5 # &GreaterTilde;
operator.\u2274.infix = lspace:5 rspace:5 # &NotLessTilde;
operator.\u2275.infix = lspace:5 rspace:5 # &NotGreaterTilde;
operator.\u2276.infix = lspace:5 rspace:5 # &LessGreater;
operator.\u2277.infix = lspace:5 rspace:5 # &GreaterLess;
operator.\u2278.infix = lspace:5 rspace:5 # &NotLessGreater;
operator.\u2279.infix = lspace:5 rspace:5 # &NotGreaterLess;
operator.\u227A.infix = lspace:5 rspace:5 # &Precedes;
operator.\u227B.infix = lspace:5 rspace:5 # &Succeeds;
operator.\u227C.infix = lspace:5 rspace:5 # &PrecedesSlantEqual;
operator.\u227D.infix = lspace:5 rspace:5 # &SucceedsSlantEqual;
operator.\u227E.infix = lspace:5 rspace:5 # &PrecedesTilde;
operator.\u227F.infix = lspace:5 rspace:5 # &SucceedsTilde;
operator.\u2280.infix = lspace:5 rspace:5 # &NotPrecedes;
operator.\u2281.infix = lspace:5 rspace:5 # &NotSucceeds;
operator.\u2282.infix = lspace:5 rspace:5 # &subset;
operator.\u2283.infix = lspace:5 rspace:5 # &Superset;
operator.\u2284.infix = lspace:5 rspace:5 # &nsub;
operator.\u2285.infix = lspace:5 rspace:5 # &nsup;
operator.\u2286.infix = lspace:5 rspace:5 # &SubsetEqual;
operator.\u2287.infix = lspace:5 rspace:5 # &SupersetEqual;
operator.\u2288.infix = lspace:5 rspace:5 # &NotSubsetEqual;
operator.\u2289.infix = lspace:5 rspace:5 # &NotSupersetEqual;
operator.\u228A.infix = lspace:5 rspace:5 # &subsetneq; &subne;
operator.\u228B.infix = lspace:5 rspace:5 # superset of with not equal to
operator.\u228C.infix = lspace:4 rspace:4 # multiset
operator.\u228D.infix = lspace:4 rspace:4 # multiset multiplication
operator.\u228E.infix = lspace:4 rspace:4 direction:vertical # &UnionPlus;
operator.\u228F.infix = lspace:5 rspace:5 # &SquareSubset;
operator.\u2290.infix = lspace:5 rspace:5 # &SquareSuperset;
operator.\u2291.infix = lspace:5 rspace:5 # &SquareSubsetEqual;
operator.\u2292.infix = lspace:5 rspace:5 # &SquareSupersetEqual;
operator.\u2293.infix = lspace:4 rspace:4 direction:vertical # &SquareIntersection;
operator.\u2294.infix = lspace:4 rspace:4 direction:vertical # &SquareUnion;
operator.\u2295.infix = lspace:4 rspace:4 direction:vertical # &CirclePlus;
operator.\u2296.infix = lspace:4 rspace:4 direction:vertical # &CircleMinus;
operator.\u2297.infix = lspace:3 rspace:3 direction:vertical # &CircleTimes;
operator.\u2298.infix = lspace:4 rspace:4 # circled division slash
operator.\u2299.infix = lspace:3 rspace:3 direction:vertical # &CircleDot;
operator.\u229A.infix = lspace:3 rspace:3 # circled ring operator
operator.\u229B.infix = lspace:3 rspace:3 # circled asterisk operator
operator.\u229C.infix = lspace:5 rspace:5 # circled equals
operator.\u229D.infix = lspace:4 rspace:4 # circled dash
operator.\u229E.infix = lspace:4 rspace:4 # squared plus
operator.\u229F.infix = lspace:4 rspace:4 # squared minus
operator.\u22A0.infix = lspace:3 rspace:3 # squared times
operator.\u22A1.infix = lspace:3 rspace:3 # squared dot operator
operator.\u22A2.infix = lspace:5 rspace:5 # &RightTee;
operator.\u22A3.infix = lspace:5 rspace:5 # &LeftTee;
operator.\u22A6.infix = lspace:5 rspace:5 # assertion
operator.\u22A7.infix = lspace:5 rspace:5 # models
operator.\u22A8.infix = lspace:5 rspace:5 # &DoubleRightTee;
operator.\u22A9.infix = lspace:5 rspace:5 # forces
operator.\u22AA.infix = lspace:5 rspace:5 # triple vertical bar right turnstile
operator.\u22AB.infix = lspace:5 rspace:5 # double vertical bar double right turnstile
operator.\u22AC.infix = lspace:5 rspace:5 # does not prove
operator.\u22AD.infix = lspace:5 rspace:5 # not true
operator.\u22AE.infix = lspace:5 rspace:5 # does not force
operator.\u22AF.infix = lspace:5 rspace:5 # negated double vertical bar double right turnstile
operator.\u22B0.infix = lspace:5 rspace:5 # precedes under relation
operator.\u22B1.infix = lspace:5 rspace:5 # succeeds under relation
operator.\u22B2.infix = lspace:5 rspace:5 # &LeftTriangle;
operator.\u22B3.infix = lspace:5 rspace:5 # &RightTriangle;
operator.\u22B4.infix = lspace:5 rspace:5 # &LeftTriangleEqual;
operator.\u22B5.infix = lspace:5 rspace:5 # &RightTriangleEqual;
operator.\u22B6.infix = lspace:5 rspace:5 # original of
operator.\u22B7.infix = lspace:5 rspace:5 # image of
operator.\u22B8.infix = lspace:5 rspace:5 # multimap
operator.\u22BA.infix = lspace:3 rspace:3 # intercalate
operator.\u22BB.infix = lspace:4 rspace:4 # xor
operator.\u22BC.infix = lspace:4 rspace:4 # nand
operator.\u22BD.infix = lspace:4 rspace:4 # nor
operator.\u22BE.prefix = lspace:0 rspace:0 # right angle with arc
operator.\u22BF.prefix = lspace:0 rspace:0 # right triangle
operator.\u22C0.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # &Wedge;
operator.\u22C1.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # &Vee;
operator.\u22C2.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # &Intersection;
operator.\u22C3.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # &Union;
operator.\u22C4.infix = lspace:3 rspace:3 # &Diamond;
operator.\u22C5.infix = lspace:3 rspace:3 # &cdot;
operator.\u22C6.infix = lspace:3 rspace:3 # &Star;
operator.\u22C7.infix = lspace:3 rspace:3 # division times
operator.\u22C8.infix = lspace:5 rspace:5 # bowtie
operator.\u22C9.infix = lspace:3 rspace:3 # left normal factor semidirect product
operator.\u22CA.infix = lspace:3 rspace:3 # right normal factor semidirect product
operator.\u22CB.infix = lspace:3 rspace:3 # left semidirect product
operator.\u22CC.infix = lspace:3 rspace:3 # right semidirect product
operator.\u22CD.infix = lspace:5 rspace:5 # reversed tilde equals
operator.\u22CE.infix = lspace:4 rspace:4 # curly logical or
operator.\u22CF.infix = lspace:4 rspace:4 # curly logical and
operator.\u22D0.infix = lspace:5 rspace:5 # &Subset;
operator.\u22D1.infix = lspace:5 rspace:5 # double superset
operator.\u22D2.infix = lspace:4 rspace:4 # &Cap;
operator.\u22D3.infix = lspace:4 rspace:4 # &Cup;
operator.\u22D4.infix = lspace:5 rspace:5 # pitchfork
operator.\u22D5.infix = lspace:5 rspace:5 # equal and parallel to
operator.\u22D6.infix = lspace:5 rspace:5 # less-than with dot
operator.\u22D7.infix = lspace:5 rspace:5 # greater-than with dot
operator.\u22D8.infix = lspace:5 rspace:5 # very much less-than
operator.\u22D9.infix = lspace:5 rspace:5 # very much greater-than
operator.\u22DA.infix = lspace:5 rspace:5 # &LessEqualGreater;
operator.\u22DB.infix = lspace:5 rspace:5 # &GreaterEqualLess;
operator.\u22DC.infix = lspace:5 rspace:5 # equal to or less-than
operator.\u22DD.infix = lspace:5 rspace:5 # equal to or greater-than
operator.\u22DE.infix = lspace:5 rspace:5 # equal to or precedes
operator.\u22DF.infix = lspace:5 rspace:5 # equal to or succeeds
operator.\u22E0.infix = lspace:5 rspace:5 # &NotPrecedesSlantEqual;
operator.\u22E1.infix = lspace:5 rspace:5 # &NotSucceedsSlantEqual;
operator.\u22E2.infix = lspace:5 rspace:5 # &NotSquareSubsetEqual;
operator.\u22E3.infix = lspace:5 rspace:5 # &NotSquareSupersetEqual;
operator.\u22E4.infix = lspace:5 rspace:5 # square image of or not equal to
operator.\u22E5.infix = lspace:5 rspace:5 # square original of or not equal to
operator.\u22E6.infix = lspace:5 rspace:5 # less-than but not equivalent to
operator.\u22E7.infix = lspace:5 rspace:5 # greater-than but not equivalent to
operator.\u22E8.infix = lspace:5 rspace:5 # precedes but not equivalent to
operator.\u22E9.infix = lspace:5 rspace:5 # succeeds but not equivalent to
operator.\u22EA.infix = lspace:5 rspace:5 # &NotLeftTriangle;
operator.\u22EB.infix = lspace:5 rspace:5 # &NotRightTriangle;
operator.\u22EC.infix = lspace:5 rspace:5 # &NotLeftTriangleEqual;
operator.\u22ED.infix = lspace:5 rspace:5 # &NotRightTriangleEqual;
operator.\u22F2.infix = lspace:5 rspace:5 # element of with long horizontal stroke
operator.\u22F3.infix = lspace:5 rspace:5 # element of with vertical bar at end of horizontal stroke
operator.\u22F4.infix = lspace:5 rspace:5 # small element of with vertical bar at end of horizontal stroke
operator.\u22F5.infix = lspace:5 rspace:5 # element of with dot above
operator.\u22F6.infix = lspace:5 rspace:5 # element of with overbar
operator.\u22F7.infix = lspace:5 rspace:5 # small element of with overbar
operator.\u22F8.infix = lspace:5 rspace:5 # element of with underbar
operator.\u22F9.infix = lspace:5 rspace:5 # element of with two horizontal strokes
operator.\u22FA.infix = lspace:5 rspace:5 # contains with long horizontal stroke
operator.\u22FB.infix = lspace:5 rspace:5 # contains with vertical bar at end of horizontal stroke
operator.\u22FC.infix = lspace:5 rspace:5 # small contains with vertical bar at end of horizontal stroke
operator.\u22FD.infix = lspace:5 rspace:5 # contains with overbar
operator.\u22FE.infix = lspace:5 rspace:5 # small contains with overbar
operator.\u22FF.infix = lspace:5 rspace:5 # z notation bag membership
operator.\u2301.infix = lspace:5 rspace:5 # electric arrow
operator.\u2305.infix = lspace:3 rspace:3 # projective
operator.\u2306.infix = lspace:3 rspace:3 # perspective
operator.\u2308.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &LeftCeiling;
operator.\u2309.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &RightCeiling;
operator.\u230A.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &LeftFloor;
operator.\u230B.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &RightFloor;
operator.\u2310.prefix = lspace:0 rspace:0 # reversed not sign
operator.\u2319.prefix = lspace:0 rspace:0 # turned not sign
operator.\u2322.postfix = lspace:0 rspace:0 stretchy # frown
operator.\u2323.postfix = lspace:0 rspace:0 stretchy # smile
operator.\u2329.prefix = lspace:0 rspace:0 stretchy fence symmetric # left-pointing angle bracket
operator.\u232A.postfix = lspace:0 rspace:0 stretchy fence symmetric # right-pointing angle bracket
operator.\u237C.infix = lspace:5 rspace:5 # right angle with downwards zigzag arrow
operator.\u238B.infix = lspace:5 rspace:5 # broken circle with northwest arrow
operator.\u23B4.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &OverBracket;
operator.\u23B5.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &UnderBracket;
operator.\u23CD.postfix = lspace:0 rspace:0 # square foot
operator.\u23DC.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &OverParenthesis; (Unicode)
operator.\u23DD.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &UnderParenthesis; (Unicode)
operator.\u23DE.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &OverBrace; (Unicode)
operator.\u23DF.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &UnderBrace; (Unicode)
operator.\u23E0.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # top tortoise shell bracket
operator.\u23E1.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # bottom tortoise shell bracket
operator.\u2772.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # light left tortoise shell bracket ornament
operator.\u2773.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # light right tortoise shell bracket ornament
operator.\u2794.infix = lspace:5 rspace:5 stretchy # heavy wide-headed rightwards arrow
operator.\u2795.infix = lspace:4 rspace:4 # heavy plus sign
operator.\u2795.prefix = lspace:0 rspace:0 # heavy plus sign
operator.\u2796.infix = lspace:4 rspace:4 # heavy minus sign
operator.\u2796.prefix = lspace:0 rspace:0 # heavy minus sign
operator.\u2797.infix = lspace:4 rspace:4 # heavy division sign
operator.\u2798.infix = lspace:5 rspace:5 # heavy south east arrow
operator.\u2799.infix = lspace:5 rspace:5 stretchy # heavy rightwards arrow
operator.\u279A.infix = lspace:5 rspace:5 # heavy north east arrow
operator.\u279B.infix = lspace:5 rspace:5 stretchy # drafting point rightwards arrow
operator.\u279C.infix = lspace:5 rspace:5 stretchy # heavy round-tipped rightwards arrow
operator.\u279D.infix = lspace:5 rspace:5 stretchy # triangle-headed rightwards arrow
operator.\u279E.infix = lspace:5 rspace:5 stretchy # heavy triangle-headed rightwards arrow
operator.\u279F.infix = lspace:5 rspace:5 stretchy # dashed triangle-headed rightwards arrow
operator.\u27A0.infix = lspace:5 rspace:5 stretchy # heavy dashed triangle-headed rightwards arrow
operator.\u27A1.infix = lspace:5 rspace:5 stretchy # black rightwards arrow
operator.\u27A5.infix = lspace:5 rspace:5 stretchy # heavy black curved downwards and rightwards arrow
operator.\u27A6.infix = lspace:5 rspace:5 stretchy # heavy black curved upwards and rightwards arrow
operator.\u27A7.infix = lspace:5 rspace:5 # squat black rightwards arrow
operator.\u27A8.infix = lspace:5 rspace:5 stretchy # heavy concave-pointed black rightwards arrow
operator.\u27A9.infix = lspace:5 rspace:5 stretchy # right-shaded white rightwards arrow
operator.\u27AA.infix = lspace:5 rspace:5 stretchy # left-shaded white rightwards arrow
operator.\u27AB.infix = lspace:5 rspace:5 stretchy # back-tilted shadowed white rightwards arrow
operator.\u27AC.infix = lspace:5 rspace:5 stretchy # front-tilted shadowed white rightwards arrow
operator.\u27AD.infix = lspace:5 rspace:5 stretchy # heavy lower right-shadowed white rightwards arrow
operator.\u27AE.infix = lspace:5 rspace:5 stretchy # heavy upper right-shadowed white rightwards arrow
operator.\u27AF.infix = lspace:5 rspace:5 stretchy # notched lower right-shadowed white rightwards arrow
operator.\u27B1.infix = lspace:5 rspace:5 stretchy # notched upper right-shadowed white rightwards arrow
operator.\u27B2.infix = lspace:5 rspace:5 # circled heavy white rightwards arrow
operator.\u27B3.infix = lspace:5 rspace:5 stretchy # white-feathered rightwards arrow
operator.\u27B4.infix = lspace:5 rspace:5 # black-feathered south east arrow
operator.\u27B5.infix = lspace:5 rspace:5 stretchy # black-feathered rightwards arrow
operator.\u27B6.infix = lspace:5 rspace:5 # black-feathered north east arrow
operator.\u27B7.infix = lspace:5 rspace:5 # heavy black-feathered south east arrow
operator.\u27B8.infix = lspace:5 rspace:5 stretchy # heavy black-feathered rightwards arrow
operator.\u27B9.infix = lspace:5 rspace:5 # heavy black-feathered north east arrow
operator.\u27BA.infix = lspace:5 rspace:5 stretchy # teardrop-barbed rightwards arrow
operator.\u27BB.infix = lspace:5 rspace:5 stretchy # heavy teardrop-shanked rightwards arrow
operator.\u27BC.infix = lspace:5 rspace:5 stretchy # wedge-tailed rightwards arrow
operator.\u27BD.infix = lspace:5 rspace:5 stretchy # heavy wedge-tailed rightwards arrow
operator.\u27BE.infix = lspace:5 rspace:5 stretchy # open-outlined rightwards arrow
operator.\u27C0.prefix = lspace:0 rspace:0 # three dimensional angle
operator.\u27C2.infix = lspace:5 rspace:5 # perpendicular
operator.\u27CB.infix = lspace:3 rspace:3 # mathematical rising diagonal
operator.\u27CD.infix = lspace:3 rspace:3 # mathematical falling diagonal
operator.\u27E6.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &LeftDoubleBracket;
operator.\u27E7.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &RightDoubleBracket;
operator.\u27E8.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &LeftAngleBracket;
operator.\u27E9.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &RightAngleBracket;
operator.\u27EA.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # mathematical left double angle bracket
operator.\u27EB.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # mathematical right double angle bracket
operator.\u27EC.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # mathematical left white tortoise shell bracket
operator.\u27ED.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # mathematical right white tortoise shell bracket
operator.\u27EE.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # mathematical left flattened parenthesis
operator.\u27EF.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # mathematical right flattened parenthesis
operator.\u27F0.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards quadruple arrow
operator.\u27F1.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards quadruple arrow
operator.\u27F2.infix = lspace:5 rspace:5 # anticlockwise gapped circle arrow
operator.\u27F3.infix = lspace:5 rspace:5 # clockwise gapped circle arrow
operator.\u27F4.infix = lspace:5 rspace:5 stretchy # right arrow with circled plus
operator.\u27F5.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LongLeftArrow;
operator.\u27F6.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LongRightArrow;
operator.\u27F7.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LongLeftRightArrow;
operator.\u27F8.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DoubleLongLeftArrow;
operator.\u27F9.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DoubleLongRightArrow;
operator.\u27FA.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DoubleLongLeftRightArrow;
operator.\u27FB.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # long leftwards arrow from bar
operator.\u27FC.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # long rightwards arrow from bar
operator.\u27FD.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # long leftwards double arrow from bar
operator.\u27FE.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # long rightwards double arrow from bar
operator.\u27FF.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # long rightwards squiggle arrow
operator.\u2900.infix = lspace:5 rspace:5 stretchy accent # rightwards two-headed arrow with vertical stroke
operator.\u2901.infix = lspace:5 rspace:5 stretchy accent # rightwards two-headed arrow with double vertical stroke
operator.\u2902.infix = lspace:5 rspace:5 stretchy accent # leftwards double arrow with vertical stroke
operator.\u2903.infix = lspace:5 rspace:5 stretchy accent # rightwards double arrow with vertical stroke
operator.\u2904.infix = lspace:5 rspace:5 stretchy accent # left right double arrow with vertical stroke
operator.\u2905.infix = lspace:5 rspace:5 stretchy accent # rightwards two-headed arrow from bar
operator.\u2906.infix = lspace:5 rspace:5 stretchy accent # leftwards double arrow from bar
operator.\u2907.infix = lspace:5 rspace:5 stretchy accent # rightwards double arrow from bar
operator.\u2908.infix = lspace:5 rspace:5 stretchy # downwards arrow with horizontal stroke
operator.\u2909.infix = lspace:5 rspace:5 stretchy # upwards arrow with horizontal stroke
operator.\u290A.infix = lspace:5 rspace:5 stretchy direction:vertical # upwards triple arrow
operator.\u290B.infix = lspace:5 rspace:5 stretchy direction:vertical # downwards triple arrow
operator.\u290C.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards double dash arrow
operator.\u290D.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards double dash arrow
operator.\u290E.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # leftwards triple dash arrow
operator.\u290F.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards triple dash arrow
operator.\u2910.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # rightwards two-headed triple dash arrow
operator.\u2911.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow with dotted stem
operator.\u2912.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpArrowBar;
operator.\u2913.infix = lspace:5 rspace:5 stretchy direction:vertical # &DownArrowBar;
operator.\u2914.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow with tail with vertical stroke
operator.\u2915.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow with tail with double vertical stroke
operator.\u2916.infix = lspace:5 rspace:5 stretchy accent # rightwards two-headed arrow with tail
operator.\u2917.infix = lspace:5 rspace:5 stretchy accent # rightwards two-headed arrow with tail with vertical stroke
operator.\u2918.infix = lspace:5 rspace:5 stretchy accent # rightwards two-headed arrow with tail with double vertical stroke
operator.\u2919.infix = lspace:5 rspace:5 stretchy accent # leftwards arrow-tail
operator.\u291A.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow-tail
operator.\u291B.infix = lspace:5 rspace:5 stretchy accent # leftwards double arrow-tail
operator.\u291C.infix = lspace:5 rspace:5 stretchy accent # rightwards double arrow-tail
operator.\u291D.infix = lspace:5 rspace:5 stretchy accent # leftwards arrow to black diamond
operator.\u291E.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow to black diamond
operator.\u291F.infix = lspace:5 rspace:5 stretchy accent # leftwards arrow from bar to black diamond
operator.\u2920.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow from bar to black diamond
operator.\u2921.infix = lspace:5 rspace:5 # north west and south east arrow
operator.\u2922.infix = lspace:5 rspace:5 # north east and south west arrow
operator.\u2923.infix = lspace:5 rspace:5 # north west arrow with hook
operator.\u2924.infix = lspace:5 rspace:5 # north east arrow with hook
operator.\u2925.infix = lspace:5 rspace:5 # south east arrow with hook
operator.\u2926.infix = lspace:5 rspace:5 # south west arrow with hook
operator.\u2927.infix = lspace:5 rspace:5 # north west arrow and north east arrow
operator.\u2928.infix = lspace:5 rspace:5 # north east arrow and south east arrow
operator.\u2929.infix = lspace:5 rspace:5 # south east arrow and south west arrow
operator.\u292A.infix = lspace:5 rspace:5 # south west arrow and north west arrow
operator.\u292B.infix = lspace:5 rspace:5 # rising diagonal crossing falling diagonal
operator.\u292C.infix = lspace:5 rspace:5 # falling diagonal crossing rising diagonal
operator.\u292D.infix = lspace:5 rspace:5 # south east arrow crossing north east arrow
operator.\u292E.infix = lspace:5 rspace:5 # north east arrow crossing south east arrow
operator.\u292F.infix = lspace:5 rspace:5 # falling diagonal crossing north east arrow
operator.\u2930.infix = lspace:5 rspace:5 # rising diagonal crossing south east arrow
operator.\u2931.infix = lspace:5 rspace:5 # north east arrow crossing north west arrow
operator.\u2932.infix = lspace:5 rspace:5 # north west arrow crossing north east arrow
operator.\u2933.infix = lspace:5 rspace:5 accent # wave arrow pointing directly right
operator.\u2934.infix = lspace:5 rspace:5 stretchy # arrow pointing rightwards then curving upwards
operator.\u2935.infix = lspace:5 rspace:5 stretchy # arrow pointing rightwards then curving downwards
operator.\u2936.infix = lspace:5 rspace:5 stretchy # arrow pointing downwards then curving leftwards
operator.\u2937.infix = lspace:5 rspace:5 stretchy # arrow pointing downwards then curving rightwards
operator.\u2938.infix = lspace:5 rspace:5 # right-side arc clockwise arrow
operator.\u2939.infix = lspace:5 rspace:5 # left-side arc anticlockwise arrow
operator.\u293A.infix = lspace:5 rspace:5 accent # top arc anticlockwise arrow
operator.\u293B.infix = lspace:5 rspace:5 accent # bottom arc anticlockwise arrow
operator.\u293C.infix = lspace:5 rspace:5 accent # top arc clockwise arrow with minus
operator.\u293D.infix = lspace:5 rspace:5 accent # top arc anticlockwise arrow with plus
operator.\u293E.infix = lspace:5 rspace:5 # lower right semicircular clockwise arrow
operator.\u293F.infix = lspace:5 rspace:5 # lower left semicircular anticlockwise arrow
operator.\u2940.infix = lspace:5 rspace:5 # anticlockwise closed circle arrow
operator.\u2941.infix = lspace:5 rspace:5 # clockwise closed circle arrow
operator.\u2942.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow above short leftwards arrow
operator.\u2943.infix = lspace:5 rspace:5 stretchy accent # leftwards arrow above short rightwards arrow
operator.\u2944.infix = lspace:5 rspace:5 stretchy accent # short rightwards arrow above leftwards arrow
operator.\u2945.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow with plus below
operator.\u2946.infix = lspace:5 rspace:5 stretchy accent # leftwards arrow with plus below
operator.\u2947.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow through x
operator.\u2948.infix = lspace:5 rspace:5 stretchy accent # left right arrow through small circle
operator.\u2949.infix = lspace:5 rspace:5 stretchy # upwards two-headed arrow from small circle
operator.\u294A.infix = lspace:5 rspace:5 stretchy accent # left barb up right barb down harpoon
operator.\u294B.infix = lspace:5 rspace:5 stretchy accent # left barb down right barb up harpoon
operator.\u294C.infix = lspace:5 rspace:5 stretchy # up barb right down barb left harpoon
operator.\u294D.infix = lspace:5 rspace:5 stretchy # up barb left down barb right harpoon
operator.\u294E.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftRightVector;
operator.\u294F.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightUpDownVector;
operator.\u2950.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DownLeftRightVector;
operator.\u2951.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftUpDownVector;
operator.\u2952.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftVectorBar;
operator.\u2953.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightVectorBar;
operator.\u2954.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightUpVectorBar;
operator.\u2955.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightDownVectorBar;
operator.\u2956.infix = lspace:5 rspace:5 stretchy direction:horizontal # &DownLeftVectorBar;
operator.\u2957.infix = lspace:5 rspace:5 stretchy direction:horizontal # &DownRightVectorBar;
operator.\u2958.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftUpVectorBar;
operator.\u2959.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftDownVectorBar;
operator.\u295A.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &LeftTeeVector;
operator.\u295B.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &RightTeeVector;
operator.\u295C.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightUpTeeVector;
operator.\u295D.infix = lspace:5 rspace:5 stretchy direction:vertical # &RightDownTeeVector;
operator.\u295E.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DownLeftTeeVector;
operator.\u295F.infix = lspace:5 rspace:5 stretchy accent direction:horizontal # &DownRightTeeVector;
operator.\u2960.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftUpTeeVector;
operator.\u2961.infix = lspace:5 rspace:5 stretchy direction:vertical # &LeftDownTeeVector;
operator.\u2962.infix = lspace:5 rspace:5 stretchy accent # leftwards harpoon with barb up above leftwards harpoon with barb down
operator.\u2963.infix = lspace:5 rspace:5 stretchy # upwards harpoon with barb left beside upwards harpoon with barb right
operator.\u2964.infix = lspace:5 rspace:5 stretchy accent # rightwards harpoon with barb up above rightwards harpoon with barb down
operator.\u2965.infix = lspace:5 rspace:5 stretchy # downwards harpoon with barb left beside downwards harpoon with barb right
operator.\u2966.infix = lspace:5 rspace:5 stretchy accent # leftwards harpoon with barb up above rightwards harpoon with barb up
operator.\u2967.infix = lspace:5 rspace:5 stretchy accent # leftwards harpoon with barb down above rightwards harpoon with barb down
operator.\u2968.infix = lspace:5 rspace:5 stretchy accent # rightwards harpoon with barb up above leftwards harpoon with barb up
operator.\u2969.infix = lspace:5 rspace:5 stretchy accent # rightwards harpoon with barb down above leftwards harpoon with barb down
operator.\u296A.infix = lspace:5 rspace:5 stretchy accent # leftwards harpoon with barb up above long dash
operator.\u296B.infix = lspace:5 rspace:5 stretchy accent # leftwards harpoon with barb down below long dash
operator.\u296C.infix = lspace:5 rspace:5 stretchy accent # rightwards harpoon with barb up above long dash
operator.\u296D.infix = lspace:5 rspace:5 stretchy accent # rightwards harpoon with barb down below long dash
operator.\u296E.infix = lspace:5 rspace:5 stretchy direction:vertical # &UpEquilibrium;
operator.\u296F.infix = lspace:5 rspace:5 stretchy direction:vertical # &ReverseUpEquilibrium;
operator.\u2970.infix = lspace:5 rspace:5 stretchy accent # &RoundImplies;
operator.\u2971.infix = lspace:5 rspace:5 stretchy accent # equals sign above rightwards arrow
operator.\u2972.infix = lspace:5 rspace:5 stretchy accent # tilde operator above rightwards arrow
operator.\u2973.infix = lspace:5 rspace:5 stretchy accent # leftwards arrow above tilde operator
operator.\u2974.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow above tilde operator
operator.\u2975.infix = lspace:5 rspace:5 stretchy accent # rightwards arrow above almost equal to
operator.\u2976.infix = lspace:5 rspace:5 accent # less-than above leftwards arrow
operator.\u2977.infix = lspace:5 rspace:5 accent # leftwards arrow through less-than
operator.\u2978.infix = lspace:5 rspace:5 accent # greater-than above rightwards arrow
operator.\u2979.infix = lspace:5 rspace:5 accent # subset above rightwards arrow
operator.\u297A.infix = lspace:5 rspace:5 accent # leftwards arrow through subset
operator.\u297B.infix = lspace:5 rspace:5 accent # superset above leftwards arrow
operator.\u297C.infix = lspace:5 rspace:5 stretchy accent # left fish tail
operator.\u297D.infix = lspace:5 rspace:5 stretchy accent # right fish tail
operator.\u297E.infix = lspace:5 rspace:5 stretchy # up fish tail
operator.\u297F.infix = lspace:5 rspace:5 stretchy # down fish tail
operator.\u2980.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # triple direction:vertical bar delimiter
operator.\u2980.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # triple direction:vertical bar delimiter
operator.\u2981.infix = lspace:5 rspace:5 # z notation spot
operator.\u2982.infix = lspace:5 rspace:5 # z notation type colon
operator.\u2983.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # left white curly bracket
operator.\u2984.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # right white curly bracket
operator.\u2985.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # left white parenthesis
operator.\u2986.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # right white parenthesis
operator.\u2987.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # z notation left image bracket
operator.\u2988.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # z notation right image bracket
operator.\u2989.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # z notation left binding bracket
operator.\u298A.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # z notation right binding bracket
operator.\u298B.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # left square bracket with underbar
operator.\u298C.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # right square bracket with underbar
operator.\u298D.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # left square bracket with tick in top corner
operator.\u298E.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # right square bracket with tick in bottom corner
operator.\u298F.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # left square bracket with tick in bottom corner
operator.\u2990.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # right square bracket with tick in top corner
operator.\u2991.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # left angle bracket with dot
operator.\u2992.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # right angle bracket with dot
operator.\u2993.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # left arc less-than bracket
operator.\u2994.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # right arc greater-than bracket
operator.\u2995.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # double left arc greater-than bracket
operator.\u2996.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # double right arc less-than bracket
operator.\u2997.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # left black tortoise shell bracket
operator.\u2998.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # right black tortoise shell bracket
operator.\u2999.prefix = lspace:0 rspace:0 stretchy fence symmetric # dotted fence
operator.\u2999.postfix = lspace:0 rspace:0 stretchy fence symmetric # dotted fence
operator.\u299B.prefix = lspace:0 rspace:0 # measured angle opening left
operator.\u299C.prefix = lspace:0 rspace:0 # right angle variant with square
operator.\u299D.prefix = lspace:0 rspace:0 # measured right angle with dot
operator.\u299E.prefix = lspace:0 rspace:0 # angle with s inside
operator.\u299F.prefix = lspace:0 rspace:0 # acute angle
operator.\u29A0.prefix = lspace:0 rspace:0 # spherical angle opening left
operator.\u29A1.prefix = lspace:0 rspace:0 # spherical angle opening up
operator.\u29A2.prefix = lspace:0 rspace:0 # turned angle
operator.\u29A3.prefix = lspace:0 rspace:0 # reversed angle
operator.\u29A4.prefix = lspace:0 rspace:0 # angle with underbar
operator.\u29A5.prefix = lspace:0 rspace:0 # reversed angle with underbar
operator.\u29A6.prefix = lspace:0 rspace:0 # oblique angle opening up
operator.\u29A7.prefix = lspace:0 rspace:0 # oblique angle opening down
operator.\u29A8.prefix = lspace:0 rspace:0 # measured angle with open arm ending in arrow pointing up and right
operator.\u29A9.prefix = lspace:0 rspace:0 # measured angle with open arm ending in arrow pointing up and left
operator.\u29AA.prefix = lspace:0 rspace:0 # measured angle with open arm ending in arrow pointing down and right
operator.\u29AB.prefix = lspace:0 rspace:0 # measured angle with open arm ending in arrow pointing down and left
operator.\u29AC.prefix = lspace:0 rspace:0 # measured angle with open arm ending in arrow pointing right and up
operator.\u29AD.prefix = lspace:0 rspace:0 # measured angle with open arm ending in arrow pointing left and up
operator.\u29AE.prefix = lspace:0 rspace:0 # measured angle with open arm ending in arrow pointing right and down
operator.\u29AF.prefix = lspace:0 rspace:0 # measured angle with open arm ending in arrow pointing left and down
operator.\u29B6.infix = lspace:5 rspace:5 # circled vertical bar
operator.\u29B7.infix = lspace:5 rspace:5 # circled parallel
operator.\u29B8.infix = lspace:4 rspace:4 # circled reverse solidus
operator.\u29B9.infix = lspace:5 rspace:5 # circled perpendicular
operator.\u29BC.infix = lspace:4 rspace:4 # circled anticlockwise-rotated division sign
operator.\u29C0.infix = lspace:5 rspace:5 # circled less-than
operator.\u29C1.infix = lspace:5 rspace:5 # circled greater-than
operator.\u29C4.infix = lspace:4 rspace:4 # squared rising diagonal slash
operator.\u29C5.infix = lspace:4 rspace:4 # squared falling diagonal slash
operator.\u29C6.infix = lspace:3 rspace:3 # squared asterisk
operator.\u29C7.infix = lspace:3 rspace:3 # squared small circle
operator.\u29C8.infix = lspace:3 rspace:3 # squared square
operator.\u29CE.infix = lspace:5 rspace:5 # right triangle above left triangle
operator.\u29CF.infix = lspace:5 rspace:5 # &LeftTriangleBar;
operator.\u29D0.infix = lspace:5 rspace:5 # &RightTriangleBar;
operator.\u29D1.infix = lspace:5 rspace:5 # bowtie with left half black
operator.\u29D2.infix = lspace:5 rspace:5 # bowtie with right half black
operator.\u29D3.infix = lspace:5 rspace:5 # black bowtie
operator.\u29D4.infix = lspace:3 rspace:3 # times with left half black
operator.\u29D5.infix = lspace:3 rspace:3 # times with right half black
operator.\u29D6.infix = lspace:3 rspace:3 # white hourglass
operator.\u29D7.infix = lspace:3 rspace:3 # black hourglass
operator.\u29D8.prefix = lspace:0 rspace:0 stretchy fence symmetric # left wiggly fence
operator.\u29D9.postfix = lspace:0 rspace:0 stretchy fence symmetric # right wiggly fence
operator.\u29DA.prefix = lspace:0 rspace:0 stretchy fence symmetric # left double wiggly fence
operator.\u29DB.postfix = lspace:0 rspace:0 stretchy fence symmetric # right double wiggly fence
operator.\u29DF.infix = lspace:5 rspace:5 # double-ended multimap
operator.\u29E1.infix = lspace:5 rspace:5 # increases as
operator.\u29E2.infix = lspace:3 rspace:3 # shuffle product
operator.\u29E3.infix = lspace:5 rspace:5 # equals sign and slanted parallel
operator.\u29E4.infix = lspace:5 rspace:5 # equals sign and slanted parallel with tilde above
operator.\u29E5.infix = lspace:5 rspace:5 # identical to and slanted parallel
operator.\u29E6.infix = lspace:5 rspace:5 # gleich stark
operator.\u29F4.infix = lspace:5 rspace:5 # rule-delayed
operator.\u29F5.infix = lspace:4 rspace:4 # reverse solidus operator
operator.\u29F6.infix = lspace:4 rspace:4 # solidus with overbar
operator.\u29F7.infix = lspace:4 rspace:4 # reverse solidus with horizontal stroke
operator.\u29F8.infix = lspace:4 rspace:4 # big solidus
operator.\u29F9.infix = lspace:4 rspace:4 # big reverse solidus
operator.\u29FA.infix = lspace:4 rspace:4 # double plus
operator.\u29FB.infix = lspace:4 rspace:4 # triple plus
operator.\u29FC.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # left-pointing curved angle bracket
operator.\u29FD.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # right-pointing curved angle bracket
operator.\u2A00.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # &bigodot;
operator.\u2A01.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # &bigoplus;
operator.\u2A02.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # &bigotimes;
operator.\u2A03.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # n-ary union operator with dot
operator.\u2A04.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # &biguplus;
operator.\u2A05.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # n-ary square intersection operator
operator.\u2A06.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # &bigsqcup;
operator.\u2A07.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # two logical and operator
operator.\u2A08.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # two logical or operator
operator.\u2A09.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # n-ary times operator
operator.\u2A0A.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # modulo two sum
operator.\u2A0B.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # summation with integral
operator.\u2A0C.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # quadruple integral operator
operator.\u2A0D.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # finite part integral
operator.\u2A0E.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # integral with double stroke
operator.\u2A0F.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # integral average with slash
operator.\u2A10.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # circulation function
operator.\u2A11.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # anticlockwise integration
operator.\u2A12.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # line integration with rectangular path around pole
operator.\u2A13.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # line integration with semicircular path around pole
operator.\u2A14.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # line integration not including the pole
operator.\u2A15.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # integral around a point operator
operator.\u2A16.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # quaternion integral operator
operator.\u2A17.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # integral with leftwards arrow with hook
operator.\u2A18.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # integral with times sign
operator.\u2A19.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # integral with intersection
operator.\u2A1A.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # integral with union
operator.\u2A1B.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # integral with overbar
operator.\u2A1C.prefix = lspace:3 rspace:3 largeop symmetric direction:vertical # integral with underbar
operator.\u2A1D.infix = lspace:3 rspace:3 direction:vertical # join
operator.\u2A1D.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # join
operator.\u2A1E.infix = lspace:3 rspace:3 direction:vertical # large left triangle operator
operator.\u2A1E.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # large left triangle operator
operator.\u2A1F.infix = lspace:4 rspace:4 # z notation schema composition
operator.\u2A20.infix = lspace:4 rspace:4 # z notation schema piping
operator.\u2A21.infix = lspace:4 rspace:4 # z notation schema projection
operator.\u2A22.infix = lspace:4 rspace:4 # plus sign with small circle above
operator.\u2A23.infix = lspace:4 rspace:4 # plus sign with circumflex accent above
operator.\u2A24.infix = lspace:4 rspace:4 # plus sign with tilde above
operator.\u2A25.infix = lspace:4 rspace:4 # plus sign with dot below
operator.\u2A26.infix = lspace:4 rspace:4 # plus sign with tilde below
operator.\u2A27.infix = lspace:4 rspace:4 # plus sign with subscript two
operator.\u2A28.infix = lspace:4 rspace:4 # plus sign with black triangle
operator.\u2A29.infix = lspace:4 rspace:4 # minus sign with comma above
operator.\u2A2A.infix = lspace:4 rspace:4 # minus sign with dot below
operator.\u2A2B.infix = lspace:4 rspace:4 # minus sign with falling dots
operator.\u2A2C.infix = lspace:4 rspace:4 # minus sign with rising dots
operator.\u2A2D.infix = lspace:4 rspace:4 # plus sign in left half circle
operator.\u2A2E.infix = lspace:4 rspace:4 # plus sign in right half circle
operator.\u2A2F.infix = lspace:3 rspace:3 # &Cross;
operator.\u2A30.infix = lspace:3 rspace:3 # multiplication sign with dot above
operator.\u2A31.infix = lspace:3 rspace:3 # multiplication sign with underbar
operator.\u2A32.infix = lspace:3 rspace:3 # semidirect product with bottom closed
operator.\u2A33.infix = lspace:3 rspace:3 # smash product
operator.\u2A34.infix = lspace:3 rspace:3 # multiplication sign in left half circle
operator.\u2A35.infix = lspace:3 rspace:3 # multiplication sign in right half circle
operator.\u2A36.infix = lspace:3 rspace:3 # circled multiplication sign with circumflex accent
operator.\u2A37.infix = lspace:3 rspace:3 # multiplication sign in double circle
operator.\u2A38.infix = lspace:4 rspace:4 # circled division sign
operator.\u2A39.infix = lspace:4 rspace:4 # plus sign in triangle
operator.\u2A3A.infix = lspace:4 rspace:4 # minus sign in triangle
operator.\u2A3B.infix = lspace:3 rspace:3 # multiplication sign in triangle
operator.\u2A3C.infix = lspace:3 rspace:3 # interior product
operator.\u2A3D.infix = lspace:3 rspace:3 # righthand interior product
operator.\u2A3E.infix = lspace:4 rspace:4 # z notation relational composition
operator.\u2A3F.infix = lspace:3 rspace:3 # amalgamation or coproduct
operator.\u2A40.infix = lspace:4 rspace:4 # intersection with dot
operator.\u2A41.infix = lspace:4 rspace:4 # union with minus sign
operator.\u2A42.infix = lspace:4 rspace:4 # union with overbar
operator.\u2A43.infix = lspace:4 rspace:4 # intersection with overbar
operator.\u2A44.infix = lspace:4 rspace:4 # intersection with logical and
operator.\u2A45.infix = lspace:4 rspace:4 # union with logical or
operator.\u2A46.infix = lspace:4 rspace:4 # union above intersection
operator.\u2A47.infix = lspace:4 rspace:4 # intersection above union
operator.\u2A48.infix = lspace:4 rspace:4 # union above bar above intersection
operator.\u2A49.infix = lspace:4 rspace:4 # intersection above bar above union
operator.\u2A4A.infix = lspace:4 rspace:4 # union beside and joined with union
operator.\u2A4B.infix = lspace:4 rspace:4 # intersection beside and joined with intersection
operator.\u2A4C.infix = lspace:4 rspace:4 # closed union with serifs
operator.\u2A4D.infix = lspace:4 rspace:4 # closed intersection with serifs
operator.\u2A4E.infix = lspace:4 rspace:4 # double square intersection
operator.\u2A4F.infix = lspace:4 rspace:4 # double square union
operator.\u2A50.infix = lspace:3 rspace:3 # closed union with serifs and smash product
operator.\u2A51.infix = lspace:4 rspace:4 # logical and with dot above
operator.\u2A52.infix = lspace:4 rspace:4 # logical or with dot above
operator.\u2A53.infix = lspace:4 rspace:4 direction:vertical # &And;
operator.\u2A54.infix = lspace:4 rspace:4 direction:vertical # &Or;
operator.\u2A55.infix = lspace:4 rspace:4 # two intersecting logical and
operator.\u2A56.infix = lspace:4 rspace:4 # two intersecting logical or
operator.\u2A57.infix = lspace:4 rspace:4 # sloping large or
operator.\u2A58.infix = lspace:4 rspace:4 # sloping large and
operator.\u2A59.infix = lspace:4 rspace:4 # logical or overlapping logical and
operator.\u2A5A.infix = lspace:4 rspace:4 # logical and with middle stem
operator.\u2A5B.infix = lspace:4 rspace:4 # logical or with middle stem
operator.\u2A5C.infix = lspace:4 rspace:4 # logical and with horizontal dash
operator.\u2A5D.infix = lspace:4 rspace:4 # logical or with horizontal dash
operator.\u2A5E.infix = lspace:4 rspace:4 # logical and with double overbar
operator.\u2A5F.infix = lspace:4 rspace:4 # logical and with underbar
operator.\u2A60.infix = lspace:4 rspace:4 # logical and with double underbar
operator.\u2A61.infix = lspace:4 rspace:4 # small vee with underbar
operator.\u2A62.infix = lspace:4 rspace:4 # logical or with double overbar
operator.\u2A63.infix = lspace:4 rspace:4 # logical or with double underbar
operator.\u2A64.infix = lspace:3 rspace:3 # z notation domain antirestriction
operator.\u2A65.infix = lspace:3 rspace:3 # z notation range antirestriction
operator.\u2A66.infix = lspace:5 rspace:5 # equals sign with dot below
operator.\u2A67.infix = lspace:5 rspace:5 # identical with dot above
operator.\u2A68.infix = lspace:5 rspace:5 # triple horizontal bar with double vertical stroke
operator.\u2A69.infix = lspace:5 rspace:5 # triple horizontal bar with triple vertical stroke
operator.\u2A6A.infix = lspace:5 rspace:5 # tilde operator with dot above
operator.\u2A6B.infix = lspace:5 rspace:5 # tilde operator with rising dots
operator.\u2A6C.infix = lspace:5 rspace:5 # similar minus similar
operator.\u2A6D.infix = lspace:5 rspace:5 # congruent with dot above
operator.\u2A6E.infix = lspace:5 rspace:5 # equals with asterisk
operator.\u2A6F.infix = lspace:5 rspace:5 # almost equal to with circumflex accent
operator.\u2A70.infix = lspace:5 rspace:5 # approximately equal or equal to
operator.\u2A71.infix = lspace:5 rspace:5 # equals sign above plus sign
operator.\u2A72.infix = lspace:5 rspace:5 # plus sign above equals sign
operator.\u2A73.infix = lspace:5 rspace:5 # equals sign above tilde operator
operator.\u2A74.infix = lspace:5 rspace:5 # double colon equal
operator.\u2A75.infix = lspace:5 rspace:5 # &Equal;
operator.\u2A76.infix = lspace:5 rspace:5 # three consecutive equals signs
operator.\u2A77.infix = lspace:5 rspace:5 # equals sign with two dots above and two dots below
operator.\u2A78.infix = lspace:5 rspace:5 # equivalent with four dots above
operator.\u2A79.infix = lspace:5 rspace:5 # less-than with circle inside
operator.\u2A7A.infix = lspace:5 rspace:5 # greater-than with circle inside
operator.\u2A7B.infix = lspace:5 rspace:5 # less-than with question mark above
operator.\u2A7C.infix = lspace:5 rspace:5 # greater-than with question mark above
operator.\u2A7D.infix = lspace:5 rspace:5 # &LessSlantEqual;
operator.\u2A7E.infix = lspace:5 rspace:5 # &GreaterSlantEqual;
operator.\u2A7F.infix = lspace:5 rspace:5 # less-than or slanted equal to with dot inside
operator.\u2A80.infix = lspace:5 rspace:5 # greater-than or slanted equal to with dot inside
operator.\u2A81.infix = lspace:5 rspace:5 # less-than or slanted equal to with dot above
operator.\u2A82.infix = lspace:5 rspace:5 # greater-than or slanted equal to with dot above
operator.\u2A83.infix = lspace:5 rspace:5 # less-than or slanted equal to with dot above right
operator.\u2A84.infix = lspace:5 rspace:5 # greater-than or slanted equal to with dot above left
operator.\u2A85.infix = lspace:5 rspace:5 # &lessapprox;
operator.\u2A86.infix = lspace:5 rspace:5 # &gtrapprox;
operator.\u2A87.infix = lspace:5 rspace:5 # less-than and single-line not equal to
operator.\u2A88.infix = lspace:5 rspace:5 # greater-than and single-line not equal to
operator.\u2A89.infix = lspace:5 rspace:5 # less-than and not approximate
operator.\u2A8A.infix = lspace:5 rspace:5 # greater-than and not approximate
operator.\u2A8B.infix = lspace:5 rspace:5 # &lesseqqgtr;
operator.\u2A8C.infix = lspace:5 rspace:5 # &gtreqqless;
operator.\u2A8D.infix = lspace:5 rspace:5 # less-than above similar or equal
operator.\u2A8E.infix = lspace:5 rspace:5 # greater-than above similar or equal
operator.\u2A8F.infix = lspace:5 rspace:5 # less-than above similar above greater-than
operator.\u2A90.infix = lspace:5 rspace:5 # greater-than above similar above less-than
operator.\u2A91.infix = lspace:5 rspace:5 # less-than above greater-than above double-line equal
operator.\u2A92.infix = lspace:5 rspace:5 # greater-than above less-than above double-line equal
operator.\u2A93.infix = lspace:5 rspace:5 # less-than above slanted equal above greater-than above slanted equal
operator.\u2A94.infix = lspace:5 rspace:5 # greater-than above slanted equal above less-than above slanted equal
operator.\u2A95.infix = lspace:5 rspace:5 # slanted equal to or less-than
operator.\u2A96.infix = lspace:5 rspace:5 # slanted equal to or greater-than
operator.\u2A97.infix = lspace:5 rspace:5 # slanted equal to or less-than with dot inside
operator.\u2A98.infix = lspace:5 rspace:5 # slanted equal to or greater-than with dot inside
operator.\u2A99.infix = lspace:5 rspace:5 # double-line equal to or less-than
operator.\u2A9A.infix = lspace:5 rspace:5 # double-line equal to or greater-than
operator.\u2A9B.infix = lspace:5 rspace:5 # double-line slanted equal to or less-than
operator.\u2A9C.infix = lspace:5 rspace:5 # double-line slanted equal to or greater-than
operator.\u2A9D.infix = lspace:5 rspace:5 # similar or less-than
operator.\u2A9E.infix = lspace:5 rspace:5 # similar or greater-than
operator.\u2A9F.infix = lspace:5 rspace:5 # similar above less-than above equals sign
operator.\u2AA0.infix = lspace:5 rspace:5 # similar above greater-than above equals sign
operator.\u2AA1.infix = lspace:5 rspace:5 # &LessLess;
operator.\u2AA2.infix = lspace:5 rspace:5 # &GreaterGreater;
operator.\u2AA3.infix = lspace:5 rspace:5 # double nested less-than with underbar
operator.\u2AA4.infix = lspace:5 rspace:5 # greater-than overlapping less-than
operator.\u2AA5.infix = lspace:5 rspace:5 # greater-than beside less-than
operator.\u2AA6.infix = lspace:5 rspace:5 # less-than closed by curve
operator.\u2AA7.infix = lspace:5 rspace:5 # greater-than closed by curve
operator.\u2AA8.infix = lspace:5 rspace:5 # less-than closed by curve above slanted equal
operator.\u2AA9.infix = lspace:5 rspace:5 # greater-than closed by curve above slanted equal
operator.\u2AAA.infix = lspace:5 rspace:5 # smaller than
operator.\u2AAB.infix = lspace:5 rspace:5 # larger than
operator.\u2AAC.infix = lspace:5 rspace:5 # smaller than or equal to
operator.\u2AAD.infix = lspace:5 rspace:5 # larger than or equal to
operator.\u2AAE.infix = lspace:5 rspace:5 # equals sign with bumpy above
operator.\u2AAF.infix = lspace:5 rspace:5 # &PrecedesEqual;
operator.\u2AB0.infix = lspace:5 rspace:5 # &SucceedsEqual;
operator.\u2AB1.infix = lspace:5 rspace:5 # precedes above single-line not equal to
operator.\u2AB2.infix = lspace:5 rspace:5 # succeeds above single-line not equal to
operator.\u2AB3.infix = lspace:5 rspace:5 # &prE;
operator.\u2AB4.infix = lspace:5 rspace:5 # &scE;
operator.\u2AB5.infix = lspace:5 rspace:5 # precedes above not equal to
operator.\u2AB6.infix = lspace:5 rspace:5 # succeeds above not equal to
operator.\u2AB7.infix = lspace:5 rspace:5 # &precapprox;
operator.\u2AB8.infix = lspace:5 rspace:5 # &succapprox;
operator.\u2AB9.infix = lspace:5 rspace:5 # precedes above not almost equal to
operator.\u2ABA.infix = lspace:5 rspace:5 # succeeds above not almost equal to
operator.\u2ABB.infix = lspace:5 rspace:5 # double precedes
operator.\u2ABC.infix = lspace:5 rspace:5 # double succeeds
operator.\u2ABD.infix = lspace:5 rspace:5 # subset with dot
operator.\u2ABE.infix = lspace:5 rspace:5 # superset with dot
operator.\u2ABF.infix = lspace:5 rspace:5 # subset with plus sign below
operator.\u2AC0.infix = lspace:5 rspace:5 # superset with plus sign below
operator.\u2AC1.infix = lspace:5 rspace:5 # subset with multiplication sign below
operator.\u2AC2.infix = lspace:5 rspace:5 # superset with multiplication sign below
operator.\u2AC3.infix = lspace:5 rspace:5 # subset of or equal to with dot above
operator.\u2AC4.infix = lspace:5 rspace:5 # superset of or equal to with dot above
operator.\u2AC5.infix = lspace:5 rspace:5 # &subseteqq;
operator.\u2AC6.infix = lspace:5 rspace:5 # &supseteqq;
operator.\u2AC7.infix = lspace:5 rspace:5 # subset of above tilde operator
operator.\u2AC8.infix = lspace:5 rspace:5 # superset of above tilde operator
operator.\u2AC9.infix = lspace:5 rspace:5 # subset of above almost equal to
operator.\u2ACA.infix = lspace:5 rspace:5 # superset of above almost equal to
operator.\u2ACB.infix = lspace:5 rspace:5 # subset of above not equal to
operator.\u2ACC.infix = lspace:5 rspace:5 # superset of above not equal to
operator.\u2ACD.infix = lspace:5 rspace:5 # square left open box operator
operator.\u2ACE.infix = lspace:5 rspace:5 # square right open box operator
operator.\u2ACF.infix = lspace:5 rspace:5 # closed subset
operator.\u2AD0.infix = lspace:5 rspace:5 # closed superset
operator.\u2AD1.infix = lspace:5 rspace:5 # closed subset or equal to
operator.\u2AD2.infix = lspace:5 rspace:5 # closed superset or equal to
operator.\u2AD3.infix = lspace:5 rspace:5 # subset above superset
operator.\u2AD4.infix = lspace:5 rspace:5 # superset above subset
operator.\u2AD5.infix = lspace:5 rspace:5 # subset above subset
operator.\u2AD6.infix = lspace:5 rspace:5 # superset above superset
operator.\u2AD7.infix = lspace:5 rspace:5 # superset beside subset
operator.\u2AD8.infix = lspace:5 rspace:5 # superset beside and joined by dash with subset
operator.\u2AD9.infix = lspace:5 rspace:5 # element of opening downwards
operator.\u2ADA.infix = lspace:5 rspace:5 # pitchfork with tee top
operator.\u2ADB.infix = lspace:4 rspace:4 # transversal intersection
operator.\u2ADC.infix = lspace:3 rspace:3 # forking
operator.\u2ADD.infix = lspace:3 rspace:3 # nonforking
operator.\u2ADE.infix = lspace:5 rspace:5 # short left tack
operator.\u2ADF.infix = lspace:5 rspace:5 # short down tack
operator.\u2AE0.infix = lspace:5 rspace:5 # short up tack
operator.\u2AE1.infix = lspace:5 rspace:5 # perpendicular with s
operator.\u2AE2.infix = lspace:5 rspace:5 # vertical bar triple right turnstile
operator.\u2AE3.infix = lspace:5 rspace:5 # double vertical bar left turnstile
operator.\u2AE4.infix = lspace:5 rspace:5 # &DoubleLeftTee;
operator.\u2AE5.infix = lspace:5 rspace:5 # double vertical bar double left turnstile
operator.\u2AE6.infix = lspace:5 rspace:5 # long dash from left member of double vertical
operator.\u2AE7.infix = lspace:5 rspace:5 # short down tack with overbar
operator.\u2AE8.infix = lspace:5 rspace:5 # short up tack with underbar
operator.\u2AE9.infix = lspace:5 rspace:5 # short up tack above short down tack
operator.\u2AEA.infix = lspace:5 rspace:5 # double down tack
operator.\u2AEB.infix = lspace:5 rspace:5 # double up tack
operator.\u2AEC.prefix = lspace:0 rspace:0 # &Not;
operator.\u2AED.prefix = lspace:0 rspace:0 # reversed double stroke not sign
operator.\u2AEE.infix = lspace:5 rspace:5 # does not divide with reversed negation slash
operator.\u2AF2.infix = lspace:5 rspace:5 # parallel with horizontal stroke
operator.\u2AF3.infix = lspace:5 rspace:5 # parallel with tilde operator
operator.\u2AF4.infix = lspace:5 rspace:5 # triple vertical bar binary relation
operator.\u2AF5.infix = lspace:5 rspace:5 # triple vertical bar with horizontal stroke
operator.\u2AF6.infix = lspace:4 rspace:4 # triple colon operator
operator.\u2AF7.infix = lspace:5 rspace:5 # triple nested less-than
operator.\u2AF8.infix = lspace:5 rspace:5 # triple nested greater-than
operator.\u2AF9.infix = lspace:5 rspace:5 # double-line slanted less-than or equal to
operator.\u2AFA.infix = lspace:5 rspace:5 # double-line slanted greater-than or equal to
operator.\u2AFB.infix = lspace:4 rspace:4 # triple solidus binary relation
operator.\u2AFC.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # large triple vertical bar operator
operator.\u2AFD.infix = lspace:4 rspace:4 # double solidus operator
operator.\u2AFE.infix = lspace:3 rspace:3 # white vertical bar
operator.\u2AFF.prefix = lspace:3 rspace:3 largeop movablelimits symmetric direction:vertical # n-ary white vertical bar
operator.\u2B00.infix = lspace:5 rspace:5 # north east white arrow
operator.\u2B01.infix = lspace:5 rspace:5 # north west white arrow
operator.\u2B02.infix = lspace:5 rspace:5 # south east white arrow
operator.\u2B03.infix = lspace:5 rspace:5 # south west white arrow
operator.\u2B04.infix = lspace:5 rspace:5 stretchy # left right white arrow
operator.\u2B05.infix = lspace:5 rspace:5 stretchy # leftwards black arrow
operator.\u2B06.infix = lspace:5 rspace:5 stretchy # upwards black arrow
operator.\u2B07.infix = lspace:5 rspace:5 stretchy # downwards black arrow
operator.\u2B08.infix = lspace:5 rspace:5 # north east black arrow
operator.\u2B09.infix = lspace:5 rspace:5 # north west black arrow
operator.\u2B0A.infix = lspace:5 rspace:5 # south east black arrow
operator.\u2B0B.infix = lspace:5 rspace:5 # south west black arrow
operator.\u2B0C.infix = lspace:5 rspace:5 stretchy # left right black arrow
operator.\u2B0D.infix = lspace:5 rspace:5 stretchy # up down black arrow
operator.\u2B0E.infix = lspace:5 rspace:5 stretchy # rightwards arrow with tip downwards
operator.\u2B0F.infix = lspace:5 rspace:5 stretchy # rightwards arrow with tip upwards
operator.\u2B10.infix = lspace:5 rspace:5 stretchy # leftwards arrow with tip downwards
operator.\u2B11.infix = lspace:5 rspace:5 stretchy # leftwards arrow with tip upwards
operator.\u2B30.infix = lspace:5 rspace:5 stretchy # left arrow with small circle
operator.\u2B31.infix = lspace:5 rspace:5 stretchy # three leftwards arrows
operator.\u2B32.infix = lspace:5 rspace:5 stretchy # left arrow with circled plus
operator.\u2B33.infix = lspace:5 rspace:5 stretchy # long leftwards squiggle arrow
operator.\u2B34.infix = lspace:5 rspace:5 stretchy # leftwards two-headed arrow with vertical stroke
operator.\u2B35.infix = lspace:5 rspace:5 stretchy # leftwards two-headed arrow with double vertical stroke
operator.\u2B36.infix = lspace:5 rspace:5 stretchy # leftwards two-headed arrow from bar
operator.\u2B37.infix = lspace:5 rspace:5 stretchy # leftwards two-headed triple dash arrow
operator.\u2B38.infix = lspace:5 rspace:5 stretchy # leftwards arrow with dotted stem
operator.\u2B39.infix = lspace:5 rspace:5 stretchy # leftwards arrow with tail with vertical stroke
operator.\u2B3A.infix = lspace:5 rspace:5 stretchy # leftwards arrow with tail with double vertical stroke
operator.\u2B3B.infix = lspace:5 rspace:5 stretchy # leftwards two-headed arrow with tail
operator.\u2B3C.infix = lspace:5 rspace:5 stretchy # leftwards two-headed arrow with tail with vertical stroke
operator.\u2B3D.infix = lspace:5 rspace:5 stretchy # leftwards two-headed arrow with tail with double vertical stroke
operator.\u2B3E.infix = lspace:5 rspace:5 stretchy # leftwards arrow through x
operator.\u2B3F.infix = lspace:5 rspace:5 # wave arrow pointing directly left
operator.\u2B40.infix = lspace:5 rspace:5 stretchy # equals sign above leftwards arrow
operator.\u2B41.infix = lspace:5 rspace:5 stretchy # reverse tilde operator above leftwards arrow
operator.\u2B42.infix = lspace:5 rspace:5 stretchy # leftwards arrow above reverse almost equal to
operator.\u2B43.infix = lspace:5 rspace:5 stretchy # rightwards arrow through greater-than
operator.\u2B44.infix = lspace:5 rspace:5 stretchy # rightwards arrow through superset
operator.\u2B45.infix = lspace:5 rspace:5 stretchy direction:horizontal # leftwards quadruple arrow
operator.\u2B46.infix = lspace:5 rspace:5 stretchy direction:horizontal # rightwards quadruple arrow
operator.\u2B47.infix = lspace:5 rspace:5 stretchy # reverse tilde operator above rightwards arrow
operator.\u2B48.infix = lspace:5 rspace:5 stretchy # rightwards arrow above reverse almost equal to
operator.\u2B49.infix = lspace:5 rspace:5 stretchy # tilde operator above leftwards arrow
operator.\u2B4A.infix = lspace:5 rspace:5 stretchy # leftwards arrow above almost equal to
operator.\u2B4B.infix = lspace:5 rspace:5 stretchy # leftwards arrow above reverse tilde operator
operator.\u2B4C.infix = lspace:5 rspace:5 stretchy # rightwards arrow above reverse tilde operator
operator.\u2B4D.infix = lspace:5 rspace:5 # downwards triangle-headed zigzag arrow
operator.\u2B4E.infix = lspace:5 rspace:5 # short slanted north arrow
operator.\u2B4F.infix = lspace:5 rspace:5 # short backslanted south arrow
operator.\u2B5A.infix = lspace:5 rspace:5 # slanted north arrow with hooked head
operator.\u2B5B.infix = lspace:5 rspace:5 # backslanted south arrow with hooked tail
operator.\u2B5C.infix = lspace:5 rspace:5 # slanted north arrow with horizontal tail
operator.\u2B5D.infix = lspace:5 rspace:5 # backslanted south arrow with horizontal tail
operator.\u2B5E.infix = lspace:5 rspace:5 # bent arrow pointing downwards then north east
operator.\u2B5F.infix = lspace:5 rspace:5 # short bent arrow pointing downwards then north east
operator.\u2B60.infix = lspace:5 rspace:5 stretchy # leftwards triangle-headed arrow
operator.\u2B61.infix = lspace:5 rspace:5 stretchy # upwards triangle-headed arrow
operator.\u2B62.infix = lspace:5 rspace:5 stretchy # rightwards triangle-headed arrow
operator.\u2B63.infix = lspace:5 rspace:5 stretchy # downwards triangle-headed arrow
operator.\u2B64.infix = lspace:5 rspace:5 stretchy # left right triangle-headed arrow
operator.\u2B65.infix = lspace:5 rspace:5 stretchy # up down triangle-headed arrow
operator.\u2B66.infix = lspace:5 rspace:5 # north west triangle-headed arrow
operator.\u2B67.infix = lspace:5 rspace:5 # north east triangle-headed arrow
operator.\u2B68.infix = lspace:5 rspace:5 # south east triangle-headed arrow
operator.\u2B69.infix = lspace:5 rspace:5 # south west triangle-headed arrow
operator.\u2B6A.infix = lspace:5 rspace:5 stretchy # leftwards triangle-headed dashed arrow
operator.\u2B6B.infix = lspace:5 rspace:5 stretchy # upwards triangle-headed dashed arrow
operator.\u2B6C.infix = lspace:5 rspace:5 stretchy # rightwards triangle-headed dashed arrow
operator.\u2B6D.infix = lspace:5 rspace:5 stretchy # downwards triangle-headed dashed arrow
operator.\u2B6E.infix = lspace:5 rspace:5 # clockwise triangle-headed open circle arrow
operator.\u2B6F.infix = lspace:5 rspace:5 # anticlockwise triangle-headed open circle arrow
operator.\u2B70.infix = lspace:5 rspace:5 stretchy # leftwards triangle-headed arrow to bar
operator.\u2B71.infix = lspace:5 rspace:5 stretchy # upwards triangle-headed arrow to bar
operator.\u2B72.infix = lspace:5 rspace:5 stretchy # rightwards triangle-headed arrow to bar
operator.\u2B73.infix = lspace:5 rspace:5 stretchy # downwards triangle-headed arrow to bar
operator.\u2B76.infix = lspace:5 rspace:5 # north west triangle-headed arrow to bar
operator.\u2B77.infix = lspace:5 rspace:5 # north east triangle-headed arrow to bar
operator.\u2B78.infix = lspace:5 rspace:5 # south east triangle-headed arrow to bar
operator.\u2B79.infix = lspace:5 rspace:5 # south west triangle-headed arrow to bar
operator.\u2B7A.infix = lspace:5 rspace:5 stretchy # leftwards triangle-headed arrow with double horizontal stroke
operator.\u2B7B.infix = lspace:5 rspace:5 stretchy # upwards triangle-headed arrow with double horizontal stroke
operator.\u2B7C.infix = lspace:5 rspace:5 stretchy # rightwards triangle-headed arrow with double horizontal stroke
operator.\u2B7D.infix = lspace:5 rspace:5 stretchy # downwards triangle-headed arrow with double horizontal stroke
operator.\u2B80.infix = lspace:5 rspace:5 stretchy # leftwards triangle-headed arrow over rightwards triangle-headed arrow
operator.\u2B81.infix = lspace:5 rspace:5 stretchy # upwards triangle-headed arrow leftwards of downwards triangle-headed arrow
operator.\u2B82.infix = lspace:5 rspace:5 stretchy # rightwards triangle-headed arrow over leftwards triangle-headed arrow
operator.\u2B83.infix = lspace:5 rspace:5 stretchy # downwards triangle-headed arrow leftwards of upwards triangle-headed arrow
operator.\u2B84.infix = lspace:5 rspace:5 stretchy # leftwards triangle-headed paired arrows
operator.\u2B85.infix = lspace:5 rspace:5 stretchy # upwards triangle-headed paired arrows
operator.\u2B86.infix = lspace:5 rspace:5 stretchy # rightwards triangle-headed paired arrows
operator.\u2B87.infix = lspace:5 rspace:5 stretchy # downwards triangle-headed paired arrows
operator.\u2B88.infix = lspace:5 rspace:5 # leftwards black circled white arrow
operator.\u2B89.infix = lspace:5 rspace:5 # upwards black circled white arrow
operator.\u2B8A.infix = lspace:5 rspace:5 # rightwards black circled white arrow
operator.\u2B8B.infix = lspace:5 rspace:5 # downwards black circled white arrow
operator.\u2B8C.infix = lspace:5 rspace:5 # anticlockwise triangle-headed right u-shaped arrow
operator.\u2B8D.infix = lspace:5 rspace:5 # anticlockwise triangle-headed bottom u-shaped arrow
operator.\u2B8E.infix = lspace:5 rspace:5 # anticlockwise triangle-headed left u-shaped arrow
operator.\u2B8F.infix = lspace:5 rspace:5 # anticlockwise triangle-headed top u-shaped arrow
operator.\u2B94.infix = lspace:5 rspace:5 # four corner arrows circling anticlockwise
operator.\u2B95.infix = lspace:5 rspace:5 stretchy # rightwards black arrow
operator.\u2BA0.infix = lspace:5 rspace:5 stretchy # downwards triangle-headed arrow with long tip leftwards
operator.\u2BA1.infix = lspace:5 rspace:5 stretchy # downwards triangle-headed arrow with long tip rightwards
operator.\u2BA2.infix = lspace:5 rspace:5 stretchy # upwards triangle-headed arrow with long tip leftwards
operator.\u2BA3.infix = lspace:5 rspace:5 stretchy # upwards triangle-headed arrow with long tip rightwards
operator.\u2BA4.infix = lspace:5 rspace:5 stretchy # leftwards triangle-headed arrow with long tip upwards
operator.\u2BA5.infix = lspace:5 rspace:5 stretchy # rightwards triangle-headed arrow with long tip upwards
operator.\u2BA6.infix = lspace:5 rspace:5 stretchy # leftwards triangle-headed arrow with long tip downwards
operator.\u2BA7.infix = lspace:5 rspace:5 stretchy # rightwards triangle-headed arrow with long tip downwards
operator.\u2BA8.infix = lspace:5 rspace:5 stretchy # black curved downwards and leftwards arrow
operator.\u2BA9.infix = lspace:5 rspace:5 stretchy # black curved downwards and rightwards arrow
operator.\u2BAA.infix = lspace:5 rspace:5 stretchy # black curved upwards and leftwards arrow
operator.\u2BAB.infix = lspace:5 rspace:5 stretchy # black curved upwards and rightwards arrow
operator.\u2BAC.infix = lspace:5 rspace:5 stretchy # black curved leftwards and upwards arrow
operator.\u2BAD.infix = lspace:5 rspace:5 stretchy # black curved rightwards and upwards arrow
operator.\u2BAE.infix = lspace:5 rspace:5 stretchy # black curved leftwards and downwards arrow
operator.\u2BAF.infix = lspace:5 rspace:5 stretchy # black curved rightwards and downwards arrow
operator.\u2BB0.infix = lspace:5 rspace:5 # ribbon arrow down left
operator.\u2BB1.infix = lspace:5 rspace:5 # ribbon arrow down right
operator.\u2BB2.infix = lspace:5 rspace:5 # ribbon arrow up left
operator.\u2BB3.infix = lspace:5 rspace:5 # ribbon arrow up right
operator.\u2BB4.infix = lspace:5 rspace:5 # ribbon arrow left up
operator.\u2BB5.infix = lspace:5 rspace:5 # ribbon arrow right up
operator.\u2BB6.infix = lspace:5 rspace:5 # ribbon arrow left down
operator.\u2BB7.infix = lspace:5 rspace:5 # ribbon arrow right down
operator.\u2BB8.infix = lspace:5 rspace:5 stretchy # upwards white arrow from bar with horizontal bar
operator.\u2BD1.infix = lspace:5 rspace:5 # uncertainty sign


operator.\u0026.infix = lspace:5 rspace:5 # &amp;
operator.\u0026.prefix = lspace:0 rspace:5 # &amp;
operator.\u002B\u002B.prefix = lspace:0 rspace:2 # ++
operator.\u002D\u002D.prefix = lspace:0 rspace:2 # --
operator.\u002E\u002E.postfix = lspace:0 rspace:0 # ..
operator.\u003B.postfix = lspace:0 rspace:0 separator # ;
operator.\u007E.infix = lspace:2 rspace:2 stretchy direction:horizontal # ~
operator.\u0332.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &UnderBar;
operator.\u03F6.infix = lspace:5 rspace:5 # greek reversed lunate epsilon symbol
operator.\u2016.infix = lspace:5 rspace:5 stretchy direction:vertical # &Vert; &Verbar;
operator.\u2026.infix = lspace:0 rspace:0 # horizontal ellipsis
operator.\u20D0.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &#x20D0;
operator.\u20D1.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &#x20D1;
operator.\u20D6.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &#x20D6;
operator.\u20D7.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &#x20D7;
operator.\u20E1.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &#x20E1;
operator.\u2190\u200B.infix = lspace:5 rspace:5 # &ShortLeftArrow;
operator.\u2191\u200B.infix = lspace:2 rspace:2 # &ShortUpArrow;
operator.\u2192\u200B.infix = lspace:5 rspace:5 # &ShortRightArrow;
operator.\u2193\u200B.infix = lspace:2 rspace:2 # &ShortDownArrow;
operator.\u2201.infix = lspace:1 rspace:2 # complement
operator.\u220E.infix = lspace:3 rspace:3 # end of proof
operator.\u221F.infix = lspace:5 rspace:5 # right angle
operator.\u2223.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &VerticalBar;
operator.\u2223.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &VerticalBar;
operator.\u2225.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &DoubleVerticalBar;
operator.\u2225.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &DoubleVerticalBar;
operator.\u2234.infix = lspace:5 rspace:5 # &Therefore;
operator.\u2235.infix = lspace:5 rspace:5 # &Because;
operator.\u223D\u0331.infix = lspace:3 rspace:3 # reversed tilde with underline
operator.\u223F.infix = lspace:3 rspace:3 # sine wave
operator.\u228E.prefix = lspace:1 rspace:2 largeop movablelimits symmetric direction:vertical # &UnionPlus;
operator.\u2295.prefix = lspace:0 rspace:3 largeop movablelimits symmetric direction:vertical # &CirclePlus;
operator.\u2296.prefix = lspace:0 rspace:3 largeop movablelimits symmetric direction:vertical # &CircleMinus;
operator.\u2297.prefix = lspace:0 rspace:3 largeop movablelimits symmetric direction:vertical # &CircleTimes;
operator.\u2299.prefix = lspace:0 rspace:3 largeop movablelimits symmetric direction:vertical # &CircleDot;
operator.\u22A4.infix = lspace:5 rspace:5 # &DownTee;
operator.\u22A5.infix = lspace:5 rspace:5 # &UpTee;
operator.\u22B9.infix = lspace:5 rspace:5 # hermitian conjugate matrix
operator.\u22BE.infix = lspace:3 rspace:3 # right angle with arc
operator.\u22BF.infix = lspace:3 rspace:3 # right triangle
operator.\u22EE.infix = lspace:5 rspace:5 # vertical ellipsis
operator.\u22EF.infix = lspace:0 rspace:0 # midline horizontal ellipsis
operator.\u22F0.infix = lspace:5 rspace:5 # up right diagonal ellipsis
operator.\u22F1.infix = lspace:5 rspace:5 # down right diagonal ellipsis
operator.\u23B0.prefix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &lmoustache; &lmoust;
operator.\u23B1.postfix = lspace:0 rspace:0 stretchy fence symmetric direction:vertical # &rmoustache; &rmoust;
operator.\u2500.infix = lspace:0 rspace:0 stretchy direction:horizontal # &HorizontalLine;
operator.\u25A0.infix = lspace:3 rspace:3 # black square
operator.\u25A1.infix = lspace:3 rspace:3 # white square
operator.\u25A1.prefix = lspace:0 rspace:2 # &Square;
operator.\u25AA.infix = lspace:3 rspace:3 # black small square
operator.\u25AB.infix = lspace:3 rspace:3 # white small square
operator.\u25AD.infix = lspace:3 rspace:3 # white rectangle
operator.\u25AE.infix = lspace:3 rspace:3 # black vertical rectangle
operator.\u25AF.infix = lspace:3 rspace:3 # white vertical rectangle
operator.\u25B0.infix = lspace:3 rspace:3 # black parallelogram
operator.\u25B1.infix = lspace:3 rspace:3 # white parallelogram
operator.\u25B2.infix = lspace:4 rspace:4 # black up-pointing triangle
operator.\u25B3.infix = lspace:4 rspace:4 # white up-pointing triangle
operator.\u25B4.infix = lspace:4 rspace:4 # black up-pointing small triangle
operator.\u25B5.infix = lspace:4 rspace:4 # white up-pointing small triangle
operator.\u25B6.infix = lspace:4 rspace:4 # black right-pointing triangle
operator.\u25B7.infix = lspace:4 rspace:4 # white right-pointing triangle
operator.\u25B8.infix = lspace:4 rspace:4 # black right-pointing small triangle
operator.\u25B9.infix = lspace:4 rspace:4 # white right-pointing small triangle
operator.\u25BC.infix = lspace:4 rspace:4 # black down-pointing triangle
operator.\u25BD.infix = lspace:4 rspace:4 # white down-pointing triangle
operator.\u25BE.infix = lspace:4 rspace:4 # black down-pointing small triangle
operator.\u25BF.infix = lspace:4 rspace:4 # white down-pointing small triangle
operator.\u25C0.infix = lspace:4 rspace:4 # black left-pointing triangle
operator.\u25C1.infix = lspace:4 rspace:4 # white left-pointing triangle
operator.\u25C2.infix = lspace:4 rspace:4 # black left-pointing small triangle
operator.\u25C3.infix = lspace:4 rspace:4 # white left-pointing small triangle
operator.\u25C4.infix = lspace:4 rspace:4 # black left-pointing pointer
operator.\u25C5.infix = lspace:4 rspace:4 # white left-pointing pointer
operator.\u25C6.infix = lspace:4 rspace:4 # black diamond
operator.\u25C7.infix = lspace:4 rspace:4 # white diamond
operator.\u25C8.infix = lspace:4 rspace:4 # white diamond containing black small diamond
operator.\u25C9.infix = lspace:4 rspace:4 # fisheye
operator.\u25CC.infix = lspace:4 rspace:4 # dotted circle
operator.\u25CD.infix = lspace:4 rspace:4 # circle with vertical fill
operator.\u25CE.infix = lspace:4 rspace:4 # bullseye
operator.\u25CF.infix = lspace:4 rspace:4 # black circle
operator.\u25D6.infix = lspace:4 rspace:4 # left half black circle
operator.\u25D7.infix = lspace:4 rspace:4 # right half black circle
operator.\u25E6.infix = lspace:4 rspace:4 # white bullet
operator.\u2606.infix = lspace:3 rspace:3 # &star;
operator.\u266D.postfix = lspace:0 rspace:2 # music flat sign
operator.\u266E.postfix = lspace:0 rspace:2 # music natural sign
operator.\u266F.postfix = lspace:0 rspace:2 # music sharp sign
operator.\u2758.infix = lspace:5 rspace:5 direction:vertical # light vertical bar
operator.\u2999.infix = lspace:3 rspace:3 # dotted fence
operator.\u299A.infix = lspace:3 rspace:3 # vertical zigzag line
operator.\u299B.infix = lspace:3 rspace:3 # measured angle opening left
operator.\u299C.infix = lspace:3 rspace:3 # right angle variant with square
operator.\u299D.infix = lspace:3 rspace:3 # measured right angle with dot
operator.\u299E.infix = lspace:3 rspace:3 # angle with s inside
operator.\u299F.infix = lspace:3 rspace:3 # acute angle
operator.\u29A0.infix = lspace:3 rspace:3 # spherical angle opening left
operator.\u29A1.infix = lspace:3 rspace:3 # spherical angle opening up
operator.\u29A2.infix = lspace:3 rspace:3 # turned angle
operator.\u29A3.infix = lspace:3 rspace:3 # reversed angle
operator.\u29A4.infix = lspace:3 rspace:3 # angle with underbar
operator.\u29A5.infix = lspace:3 rspace:3 # reversed angle with underbar
operator.\u29A6.infix = lspace:3 rspace:3 # oblique angle opening up
operator.\u29A7.infix = lspace:3 rspace:3 # oblique angle opening down
operator.\u29A8.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing up and right
operator.\u29A9.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing up and left
operator.\u29AA.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing down and right
operator.\u29AB.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing down and left
operator.\u29AC.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing right and up
operator.\u29AD.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing left and up
operator.\u29AE.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing right and down
operator.\u29AF.infix = lspace:3 rspace:3 # measured angle with open arm ending in arrow pointing left and down
operator.\u29B0.infix = lspace:3 rspace:3 # reversed empty set
operator.\u29B1.infix = lspace:3 rspace:3 # empty set with overbar
operator.\u29B2.infix = lspace:3 rspace:3 # empty set with small circle above
operator.\u29B3.infix = lspace:3 rspace:3 # empty set with right arrow above
operator.\u29B4.infix = lspace:3 rspace:3 # empty set with left arrow above
operator.\u29B5.infix = lspace:3 rspace:3 # circle with horizontal bar
operator.\u29BA.infix = lspace:4 rspace:4 # circle divided by horizontal bar and top half divided by vertical bar
operator.\u29BB.infix = lspace:4 rspace:4 # circle with superimposed x
operator.\u29BD.infix = lspace:4 rspace:4 # up arrow through circle
operator.\u29BE.infix = lspace:4 rspace:4 # circled white bullet
operator.\u29BF.infix = lspace:4 rspace:4 # circled bullet
operator.\u29C2.infix = lspace:3 rspace:3 # circle with small circle to the right
operator.\u29C3.infix = lspace:3 rspace:3 # circle with two horizontal strokes to the right
operator.\u29C9.infix = lspace:3 rspace:3 # two joined squares
operator.\u29CA.infix = lspace:3 rspace:3 # triangle with dot above
operator.\u29CB.infix = lspace:3 rspace:3 # triangle with underbar
operator.\u29CC.infix = lspace:3 rspace:3 # s in triangle
operator.\u29CD.infix = lspace:3 rspace:3 # triangle with serifs at bottom
operator.\u29D8.infix = lspace:3 rspace:3 # left wiggly fence
operator.\u29D9.infix = lspace:3 rspace:3 # right wiggly fence
operator.\u29DB.infix = lspace:3 rspace:3 # right double wiggly fence
operator.\u29DC.infix = lspace:3 rspace:3 # incomplete infinity
operator.\u29DD.infix = lspace:3 rspace:3 # tie over infinity
operator.\u29DE.infix = lspace:5 rspace:5 # infinity negated with vertical bar
operator.\u29E0.infix = lspace:3 rspace:3 # square with contoured outline
operator.\u29E7.infix = lspace:3 rspace:3 # thermodynamic
operator.\u29E8.infix = lspace:3 rspace:3 # down-pointing triangle with left half black
operator.\u29E9.infix = lspace:3 rspace:3 # down-pointing triangle with right half black
operator.\u29EA.infix = lspace:3 rspace:3 # black diamond with down arrow
operator.\u29EB.infix = lspace:3 rspace:3 # black lozenge
operator.\u29EC.infix = lspace:3 rspace:3 # white circle with down arrow
operator.\u29ED.infix = lspace:3 rspace:3 # black circle with down arrow
operator.\u29EE.infix = lspace:3 rspace:3 # error-barred white square
operator.\u29EF.infix = lspace:3 rspace:3 # error-barred black square
operator.\u29F0.infix = lspace:3 rspace:3 # error-barred white diamond
operator.\u29F1.infix = lspace:3 rspace:3 # error-barred black diamond
operator.\u29F2.infix = lspace:3 rspace:3 # error-barred white circle
operator.\u29F3.infix = lspace:3 rspace:3 # error-barred black circle
operator.\u29FE.infix = lspace:4 rspace:4 # tiny
operator.\u29FF.infix = lspace:4 rspace:4 # miny
operator.\u2AEC.infix = lspace:5 rspace:5 # double stroke not sign
operator.\u2AED.infix = lspace:5 rspace:5 # reversed double stroke not sign
operator.\u2AEF.infix = lspace:5 rspace:5 # vertical line with circle above
operator.\u2AF0.infix = lspace:5 rspace:5 # vertical line with circle below
operator.\u2AF1.infix = lspace:5 rspace:5 # down tack with circle below
operator.\uFE35.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &OverParenthesis; (MathML 2.0)
operator.\uFE36.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &UnderParenthesis; (MathML 2.0)
operator.\uFE37.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &OverBrace; (MathML 2.0)
operator.\uFE38.postfix = lspace:0 rspace:0 stretchy accent direction:horizontal # &UnderBrace; (MathML 2.0)


PK
!<�BCC(res/fonts/mathfontSTIXGeneral.properties


external.1 = STIXSizeOneSym
external.2 = STIXSizeTwoSym
external.3 = STIXSizeThreeSym
external.4 = STIXSizeFourSym
external.5 = STIXSizeFiveSym
external.6 = STIXIntegralsD
external.7 = STIXNonUnicode


\u0028 = \u239B@1\uFFFD\u239D@1\u239C@1\uFFFD(@1(@2(@3(@4 # (
\u0029 = \u239E@1\uFFFD\u23A0@1\u239F@1\uFFFD)@1)@2)@3)@4 # )
\u005B = \u23A1@1\uFFFD\u23A3@1\u23A2@1\u005B@1[@1[@2[@3[@4 # [
\u005D = \u23A4@1\uFFFD\u23A6@1\u23A5@1\u005D@1]@1]@2]@3]@4 # ]
\u007B = \u23A7@1\u23A8@1\u23A9@1\u23AA@1\u007B@1{@1{@2{@3{@4 # {
\u007D = \u23AB@1\u23AC@1\u23AD@1\u23AA@1\u007D@1}@1}@2}@3}@4 # }

\u2140 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2140@1 # DOUBLE-STRUCK N-ARY SUMMATION
\u220F = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u220F@1 # N-ARY PRODUCT
\u2210 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2210@1 # N-ARY COPRODUCT
\u2211 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2211@1 # N-ARY SUMMATION
\u22C0 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u22C0@1 # N-ARY LOGICAL AND
\u22C1 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u22C1@1 # N-ARY LOGICAL OR
\u22C2 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u22C2@1 # N-ARY INTERSECTION
\u22C3 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u22C3@1 # N-ARY UNION
\u2A00 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A00@1 # N-ARY CIRCLED DOT OPERATOR
\u2A01 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A01@1 # N-ARY CIRCLED PLUS OPERATOR
\u2A02 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A02@1 # N-ARY CIRCLED TIMES OPERATOR
\u2A03 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A03@1 # N-ARY UNION OPERATOR WITH DOT
\u2A04 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A04@1 # N-ARY UNION OPERATOR WITH PLUS
\u2A05 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A05@1 # N-ARY SQUARE INTERSECTION OPERATOR
\u2A06 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A06@1 # N-ARY SQUARE UNION OPERATOR
\u2A09 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A09@1 # N-ARY TIMES OPERATOR
\u2AFF = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2AFF@1 # N-ARY WHITE VERTICAL BAR

\u221A = \uE001@7\uFFFD\u221A@4\uE000@7\uFFFD\u221A@1\u221A@2\u221A@3 # Sqrt, radic

\u222B = \u2320@1\uFFFD\u2321@1\u23AE@1\uFFFD@1\u222B@6
\u222C = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u222C@6
\u222D = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u222D@6
\u222E = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u222E@6
\u222F = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u222F@6
\u2230 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2230@6
\u2231 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2231@6
\u2232 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2232@6
\u2233 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2233@6
\u2A0B = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A0B@6
\u2A0C = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A0C@6
\u2A0D = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A0D@6
\u2A0E = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A0E@6
\u2A0F = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A0F@6
\u2A10 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A10@6
\u2A11 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A11@6
\u2A12 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A12@6
\u2A13 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A13@6
\u2A14 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A14@6
\u2A15 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A15@6
\u2A16 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A16@6
\u2A17 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A17@6
\u2A18 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A18@6
\u2A19 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A19@6
\u2A1A = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A1A@6
\u2A1B = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A1B@6
\u2A1C = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u2A1C@6

\u27E8 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u27E8@1\u27E8@2\u27E8@3\u27E8@4 # LeftAngleBracket
\u27E9 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u27E9@1\u27E9@2\u27E9@3\u27E9@4 # RightAngleBracket

\u23DE = \uE13B@7\uE140@7\uE13C@7\uE14A@7\uFFFD\u23DE@1\u23DE@2\u23DE@3\u23DE@4\u23DE@5 # &OverBrace; (Unicode)
\uFE37 = \uE13B@7\uE140@7\uE13C@7\uE14A@7\uFFFD\u23DE@1\u23DE@2\u23DE@3\u23DE@4\u23DE@5 # &OverBrace; (MathML 2.0)
\u23B4 = \uE146@7\uFFFD\uE147@7\uE14A@7\uFFFD\u23B4@1\u23B4@2\u23B4@3\u23B4@4\u23B4@5 # &OverBracket;
\u23DC = \uE142@7\uFFFD\uE143@7\uE14A@7\uFFFD\u23DC@1\u23DC@2\u23DC@3\u23DC@4\u23DC@5 # &OverParenthesis; (Unicode)
\uFE35 = \uE142@7\uFFFD\uE143@7\uE14A@7\uFFFD\u23DC@1\u23DC@2\u23DC@3\u23DC@4\u23DC@5 # &OverParenthesis; (MathML 2.0)
\u23DF = \uE13D@7\uE141@7\uE13E@7\uE13F@7\uFFFD\u23DF@1\u23DF@2\u23DF@3\u23DF@4\u23DF@5 # &UnderBrace; (Unicode)
\uFE38 = \uE13D@7\uE141@7\uE13E@7\uE13F@7\uFFFD\u23DF@1\u23DF@2\u23DF@3\u23DF@4\u23DF@5 # &UnderBrace; (MathML 2.0)
\u23B5 = \uE148@7\uFFFD\uE149@7\uE14B@7\uFFFD\u23B5@1\u23B5@2\u23B5@3\u23B5@4\u23B5@5 # &UnderBracket;
\u23DD = \uE144@7\uFFFD\uE145@7\uE14B@7\uFFFD\u23DD@1\u23DD@2\u23DD@3\u23DD@4\u23DD@5 # &UnderParenthesis; (Unicode)
\uFE36 = \uE144@7\uFFFD\uE145@7\uE14B@7\uFFFD\u23DD@1\u23DD@2\u23DD@3\u23DD@4\u23DD@5 # &UnderParenthesis; (MathML 2.0)

\u005E = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u0302@1\u0302@2\u0302@3\u0302@4\u0302@5 # circumflex accent, COMBINING CIRCUMFLEX ACCENT
\u02C6 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u0302@1\u0302@2\u0302@3\u0302@4\u0302@5 # modifier letter circumflex accent, COMBINING CIRCUMFLEX ACCENT
\u007E = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u0303@1\u0303@2\u0303@3\u0303@4\u0303@5 # ~ tilde, COMBINING TILDE
\u02DC = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u0303@1\u0303@2\u0303@3\u0303@4\u0303@5 # small tilde, COMBINING TILDE
\u02C7 = \uFFFD\uFFFD\uFFFD\uFFFD\uFFFD\u030C@1\u030C@2\u030C@3\u030C@4\u030C@5 # caron, COMBINING CARON


\u21A9 = \u2190\uFFFD\uE0B5@7\u23AF # hookleftarrow, larrhk
\u21AA = \uE0B4@7\uFFFD\u2192\u23AF # hookrightarrow, rarrhk

\u21D0 = \u21D0\uFFFD\uFFFD\uE10F@7\uFFFD\u27F8 # DoubleLeftArrow, Leftarrow, lArr
\u21D1 = \u21D1\uFFFD\uFFFD\uE10E@7 # DoubleUpArrow, Uparrow, uArr
\u21D2 = \uFFFD\uFFFD\u21D2\uE10F@7\uFFFD\u27F9 # DoubleRightArrow, Implies, Rightarrow, rArr
\u21D3 = \uFFFD\uFFFD\u21D3\uE10E@7 # DoubleDownArrow, Downarrow, dArr
\u21D4 = \u21D0\uFFFD\u21D2\uE10F@7\uFFFD\u27FA # DoubleLeftRightArrow, Leftrightarrow, hArr, iff
\u21D5 = \u21D1\uFFFD\u21D3\uE10E@7 # DoubleUpDownArrow, Updownarrow, vArr

\u21A4 = \u2190\uFFFD\uE0B6@7\u23AF\uFFFD\u27FB # LeftTeeArrow, mapstoleft
\u21A6 = \uE0B6@7\uFFFD\u2192\u23AF\uFFFD\u27FC # RightTeeArrow, map, mapsto
\u295A = \u21BC\uFFFD\uE0B6@7\u23AF # LeftTeeVector
\u295B = \uE0B6@7\uFFFD\u21C0\u23AF # RIGHTWARDS HARPOON WITH BARB UP FROM BAR, RightTeeVector
\u295E = \u21BD\uFFFD\uE0B6@7\u23AF # DownLeftTeeVector
\u295F = \uE0B6@7\uFFFD\u21C1\u23AF # RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR, DownRightTeeVector
PK
!<����$res/fonts/mathfontUnicode.properties


\u0028 = \u239B\uFFFD\u239D\u239C\u0028 # (
\u0029 = \u239E\uFFFD\u23A0\u239F\u0029 # )
\u005B = \u23A1\uFFFD\u23A3\u23A2\u005B # [
\u005D = \u23A4\uFFFD\u23A6\u23A5\u005D # ]
\u007B = \u23A7\u23A8\u23A9\u23AA\u007B # {
\u007C = \uFFFD\uFFFD\uFFFD\u007C\u007C # |
\u007D = \u23AB\u23AC\u23AD\u23AA\u007D # }

\u00AF = \uFFFD\uFFFD\uFFFD\u0305\u00AF # OverBar
\u203E = \uFFFD\uFFFD\uFFFD\u0305\u00AF # overline
\u0332 = \uFFFD\uFFFD\uFFFD\u0332\u0332 # COMBINING LOW LINE, UnderBar
\u005F = \uFFFD\uFFFD\uFFFD\u0332\u0332 # _ low line
\u003D = \uFFFD\uFFFD\uFFFD\u003D\u003D # = equal sign

\u2016 = \uFFFD\uFFFD\uFFFD\u2016\u2016 # DOUBLE VERTICAL LINE, Vert, Verbar

\u2190 = \u2190\uFFFD\uFFFD\u23AF\u2190\u27F5 # LeftArrow, larr, leftarrow
\u2191 = \u2191\uFFFD\uFFFD\u23D0\u2191 # UpArrow, uarr, uparrow
\u2192 = \uFFFD\uFFFD\u2192\u23AF\u2192\u27F6 # RightArrow, rarr, rightarrow
\u2193 = \uFFFD\uFFFD\u2193\u23D0\u2193 # DownArrow, darr, downarrow
\u2194 = \u2190\uFFFD\u2192\u23AF\u2194\u27F7 # LeftRightArrow, harr, leftrightarrow
\u2195 = \u2191\uFFFD\u2193\u23D0\u2195 # UpDownArrow, updownarrow, varr

\u21A4 = \u2190\uFFFD\u22A3\u23AF\u21A6\u27FB # LeftTeeArrow, mapstoleft
\u21A6 = \u22A2\uFFFD\u2192\u23AF\u21A6\u27FC # RightTeeArrow, map, mapsto
\u295A = \u21BC\uFFFD\u22A3\u23AF\u295A # LeftTeeVector
\u295B = \u22A2\uFFFD\u21C0\u23AF\u295B # RIGHTWARDS HARPOON WITH BARB UP FROM BAR, RightTeeVector
\u295E = \u21BD\uFFFD\u22A3\u23AF\u295E # DownLeftTeeVector
\u295F = \u22A2\uFFFD\u21C1\u23AF\u295F # RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR, DownRightTeeVector

\u21C0 = \uFFFD\uFFFD\u21C0\u23AF\u21C0 # RightVector, rharu, rightharpoonup
\u21C1 = \uFFFD\uFFFD\u21C1\u23AF\u21C1 # DownRightVector, rhard, rightharpoon down
\u21BC = \u21BC\uFFFD\uFFFD\u23AF\u21BC # LeftVector, leftharpoonup, lharu
\u21BD = \u21BD\uFFFD\uFFFD\u23AF\u21BD # DownLeftVector, leftharpoondown, lhard
\u21D0 = \uFFFD\uFFFD\uFFFD\uFFFD\u21D0\u27F8 # DoubleLeftArrow, Leftarrow, lArr
\u21D2 = \uFFFD\uFFFD\uFFFD\uFFFD\u21D2\u27F9 # DoubleRightArrow, Implies, Rightarro
\u21D4 = \uFFFD\uFFFD\uFFFD\uFFFD\u21D4\u27FA # DoubleLeftRightArrow, Leftrightarrow, hArr, iff


\u2223 = \uFFFD\uFFFD\uFFFD\u2223\u2223 # VerticalBar, mid
\u2225 = \uFFFD\uFFFD\uFFFD\u2225\u2225 # DoubleVerticalBar, par, parallel

\u222B = \u2320\uFFFD\u2321\u23AE\u222B # Integral, int

\u2308 = \u23A1\uFFFD\uFFFD\u23A2\u2308 # LeftCeiling, lceil
\u2309 = \u23A4\uFFFD\uFFFD\u23A5\u2309 # RightCeiling, rceil
\u230A = \uFFFD\uFFFD\u23A3\u23A2\u230A # LeftFloor, lfloor
\u230B = \uFFFD\uFFFD\u23A6\u23A5\u230B # RightFloor, rfloor

\u23B0 = \u23A7\uFFFD\u23AD\u23AA\u23B0 # lmoustache, lmoust
\u23B1 = \u23AB\uFFFD\u23A9\u23AA\u23B1 # rmoustache, rmoust

\u27F5 = \u2190\uFFFD\uFFFD\u23AF\u27F5 # LongLeftArrow
\u27F6 = \uFFFD\uFFFD\u2192\u23AF\u27F6 # LongRightArrow
\u27F7 = \u2190\uFFFD\u2192\u23AF\u27F7 # LongLeftRightArrow

\u294E = \u21BC\uFFFD\u21C0\u23AF\u294E #LEFT BARB UP RIGHT BARB UP HARPOON, LeftRightVector
\u2950 = \u21BD\uFFFD\u21C1\u23AF\u2950 #LEFT BARB DOWN RIGHT BARB DOWN HARPOON , DownLeftRightVector
PK
!<�@7��m�mres/dtd/htmlmathml-f.ent
<!-- 
     Copyright 1998 - 2011 W3C.

     Use and distribution of this code are permitted under the terms of
     either of the following two licences:

     1) W3C Software Notice and License.
        http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231.html


     2) The license used for the WHATWG HTML specification,
        which states, in full:
            You are granted a license to use, reproduce and create derivative
            works of this document.


     Please report any errors to David Carlisle
     via the public W3C list www-math@w3.org.

 
       Public identifier: -//W3C//ENTITIES HTML MathML Set//EN//XML
       System identifier: http://www.w3.org/2003/entities/2007/htmlmathml-f.ent

     The public identifier should always be used verbatim.
     The system identifier may be changed to suit local requirements.

     Typical invocation:

       <!ENTITY % htmlmathml-f PUBLIC
         "-//W3C//ENTITIES HTML MathML Set//EN//XML"
         "http://www.w3.org/2003/entities/2007/htmlmathml-f.ent"
       >
       %htmlmathml-f;



-->

<!ENTITY AElig            "&#x000C6;" ><!--LATIN CAPITAL LETTER AE -->
<!ENTITY AMP              "&#38;#38;" ><!--AMPERSAND -->
<!ENTITY Aacute           "&#x000C1;" ><!--LATIN CAPITAL LETTER A WITH ACUTE -->
<!ENTITY Abreve           "&#x00102;" ><!--LATIN CAPITAL LETTER A WITH BREVE -->
<!ENTITY Acirc            "&#x000C2;" ><!--LATIN CAPITAL LETTER A WITH CIRCUMFLEX -->
<!ENTITY Acy              "&#x00410;" ><!--CYRILLIC CAPITAL LETTER A -->
<!ENTITY Afr              "&#x1D504;" ><!--MATHEMATICAL FRAKTUR CAPITAL A -->
<!ENTITY Agrave           "&#x000C0;" ><!--LATIN CAPITAL LETTER A WITH GRAVE -->
<!ENTITY Alpha            "&#x00391;" ><!--GREEK CAPITAL LETTER ALPHA -->
<!ENTITY Amacr            "&#x00100;" ><!--LATIN CAPITAL LETTER A WITH MACRON -->
<!ENTITY And              "&#x02A53;" ><!--DOUBLE LOGICAL AND -->
<!ENTITY Aogon            "&#x00104;" ><!--LATIN CAPITAL LETTER A WITH OGONEK -->
<!ENTITY Aopf             "&#x1D538;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL A -->
<!ENTITY ApplyFunction    "&#x02061;" ><!--FUNCTION APPLICATION -->
<!ENTITY Aring            "&#x000C5;" ><!--LATIN CAPITAL LETTER A WITH RING ABOVE -->
<!ENTITY Ascr             "&#x1D49C;" ><!--MATHEMATICAL SCRIPT CAPITAL A -->
<!ENTITY Assign           "&#x02254;" ><!--COLON EQUALS -->
<!ENTITY Atilde           "&#x000C3;" ><!--LATIN CAPITAL LETTER A WITH TILDE -->
<!ENTITY Auml             "&#x000C4;" ><!--LATIN CAPITAL LETTER A WITH DIAERESIS -->
<!ENTITY Backslash        "&#x02216;" ><!--SET MINUS -->
<!ENTITY Barv             "&#x02AE7;" ><!--SHORT DOWN TACK WITH OVERBAR -->
<!ENTITY Barwed           "&#x02306;" ><!--PERSPECTIVE -->
<!ENTITY Bcy              "&#x00411;" ><!--CYRILLIC CAPITAL LETTER BE -->
<!ENTITY Because          "&#x02235;" ><!--BECAUSE -->
<!ENTITY Bernoullis       "&#x0212C;" ><!--SCRIPT CAPITAL B -->
<!ENTITY Beta             "&#x00392;" ><!--GREEK CAPITAL LETTER BETA -->
<!ENTITY Bfr              "&#x1D505;" ><!--MATHEMATICAL FRAKTUR CAPITAL B -->
<!ENTITY Bopf             "&#x1D539;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL B -->
<!ENTITY Breve            "&#x002D8;" ><!--BREVE -->
<!ENTITY Bscr             "&#x0212C;" ><!--SCRIPT CAPITAL B -->
<!ENTITY Bumpeq           "&#x0224E;" ><!--GEOMETRICALLY EQUIVALENT TO -->
<!ENTITY CHcy             "&#x00427;" ><!--CYRILLIC CAPITAL LETTER CHE -->
<!ENTITY COPY             "&#x000A9;" ><!--COPYRIGHT SIGN -->
<!ENTITY Cacute           "&#x00106;" ><!--LATIN CAPITAL LETTER C WITH ACUTE -->
<!ENTITY Cap              "&#x022D2;" ><!--DOUBLE INTERSECTION -->
<!ENTITY CapitalDifferentialD "&#x02145;" ><!--DOUBLE-STRUCK ITALIC CAPITAL D -->
<!ENTITY Cayleys          "&#x0212D;" ><!--BLACK-LETTER CAPITAL C -->
<!ENTITY Ccaron           "&#x0010C;" ><!--LATIN CAPITAL LETTER C WITH CARON -->
<!ENTITY Ccedil           "&#x000C7;" ><!--LATIN CAPITAL LETTER C WITH CEDILLA -->
<!ENTITY Ccirc            "&#x00108;" ><!--LATIN CAPITAL LETTER C WITH CIRCUMFLEX -->
<!ENTITY Cconint          "&#x02230;" ><!--VOLUME INTEGRAL -->
<!ENTITY Cdot             "&#x0010A;" ><!--LATIN CAPITAL LETTER C WITH DOT ABOVE -->
<!ENTITY Cedilla          "&#x000B8;" ><!--CEDILLA -->
<!ENTITY CenterDot        "&#x000B7;" ><!--MIDDLE DOT -->
<!ENTITY Cfr              "&#x0212D;" ><!--BLACK-LETTER CAPITAL C -->
<!ENTITY Chi              "&#x003A7;" ><!--GREEK CAPITAL LETTER CHI -->
<!ENTITY CircleDot        "&#x02299;" ><!--CIRCLED DOT OPERATOR -->
<!ENTITY CircleMinus      "&#x02296;" ><!--CIRCLED MINUS -->
<!ENTITY CirclePlus       "&#x02295;" ><!--CIRCLED PLUS -->
<!ENTITY CircleTimes      "&#x02297;" ><!--CIRCLED TIMES -->
<!ENTITY ClockwiseContourIntegral "&#x02232;" ><!--CLOCKWISE CONTOUR INTEGRAL -->
<!ENTITY CloseCurlyDoubleQuote "&#x0201D;" ><!--RIGHT DOUBLE QUOTATION MARK -->
<!ENTITY CloseCurlyQuote  "&#x02019;" ><!--RIGHT SINGLE QUOTATION MARK -->
<!ENTITY Colon            "&#x02237;" ><!--PROPORTION -->
<!ENTITY Colone           "&#x02A74;" ><!--DOUBLE COLON EQUAL -->
<!ENTITY Congruent        "&#x02261;" ><!--IDENTICAL TO -->
<!ENTITY Conint           "&#x0222F;" ><!--SURFACE INTEGRAL -->
<!ENTITY ContourIntegral  "&#x0222E;" ><!--CONTOUR INTEGRAL -->
<!ENTITY Copf             "&#x02102;" ><!--DOUBLE-STRUCK CAPITAL C -->
<!ENTITY Coproduct        "&#x02210;" ><!--N-ARY COPRODUCT -->
<!ENTITY CounterClockwiseContourIntegral "&#x02233;" ><!--ANTICLOCKWISE CONTOUR INTEGRAL -->
<!ENTITY Cross            "&#x02A2F;" ><!--VECTOR OR CROSS PRODUCT -->
<!ENTITY Cscr             "&#x1D49E;" ><!--MATHEMATICAL SCRIPT CAPITAL C -->
<!ENTITY Cup              "&#x022D3;" ><!--DOUBLE UNION -->
<!ENTITY CupCap           "&#x0224D;" ><!--EQUIVALENT TO -->
<!ENTITY DD               "&#x02145;" ><!--DOUBLE-STRUCK ITALIC CAPITAL D -->
<!ENTITY DDotrahd         "&#x02911;" ><!--RIGHTWARDS ARROW WITH DOTTED STEM -->
<!ENTITY DJcy             "&#x00402;" ><!--CYRILLIC CAPITAL LETTER DJE -->
<!ENTITY DScy             "&#x00405;" ><!--CYRILLIC CAPITAL LETTER DZE -->
<!ENTITY DZcy             "&#x0040F;" ><!--CYRILLIC CAPITAL LETTER DZHE -->
<!ENTITY Dagger           "&#x02021;" ><!--DOUBLE DAGGER -->
<!ENTITY Darr             "&#x021A1;" ><!--DOWNWARDS TWO HEADED ARROW -->
<!ENTITY Dashv            "&#x02AE4;" ><!--VERTICAL BAR DOUBLE LEFT TURNSTILE -->
<!ENTITY Dcaron           "&#x0010E;" ><!--LATIN CAPITAL LETTER D WITH CARON -->
<!ENTITY Dcy              "&#x00414;" ><!--CYRILLIC CAPITAL LETTER DE -->
<!ENTITY Del              "&#x02207;" ><!--NABLA -->
<!ENTITY Delta            "&#x00394;" ><!--GREEK CAPITAL LETTER DELTA -->
<!ENTITY Dfr              "&#x1D507;" ><!--MATHEMATICAL FRAKTUR CAPITAL D -->
<!ENTITY DiacriticalAcute "&#x000B4;" ><!--ACUTE ACCENT -->
<!ENTITY DiacriticalDot   "&#x002D9;" ><!--DOT ABOVE -->
<!ENTITY DiacriticalDoubleAcute "&#x002DD;" ><!--DOUBLE ACUTE ACCENT -->
<!ENTITY DiacriticalGrave "&#x00060;" ><!--GRAVE ACCENT -->
<!ENTITY DiacriticalTilde "&#x002DC;" ><!--SMALL TILDE -->
<!ENTITY Diamond          "&#x022C4;" ><!--DIAMOND OPERATOR -->
<!ENTITY DifferentialD    "&#x02146;" ><!--DOUBLE-STRUCK ITALIC SMALL D -->
<!ENTITY Dopf             "&#x1D53B;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL D -->
<!ENTITY Dot              "&#x000A8;" ><!--DIAERESIS -->
<!ENTITY DotDot           "&#x020DC;" ><!--COMBINING FOUR DOTS ABOVE -->
<!ENTITY DotEqual         "&#x02250;" ><!--APPROACHES THE LIMIT -->
<!ENTITY DoubleContourIntegral "&#x0222F;" ><!--SURFACE INTEGRAL -->
<!ENTITY DoubleDot        "&#x000A8;" ><!--DIAERESIS -->
<!ENTITY DoubleDownArrow  "&#x021D3;" ><!--DOWNWARDS DOUBLE ARROW -->
<!ENTITY DoubleLeftArrow  "&#x021D0;" ><!--LEFTWARDS DOUBLE ARROW -->
<!ENTITY DoubleLeftRightArrow "&#x021D4;" ><!--LEFT RIGHT DOUBLE ARROW -->
<!ENTITY DoubleLeftTee    "&#x02AE4;" ><!--VERTICAL BAR DOUBLE LEFT TURNSTILE -->
<!ENTITY DoubleLongLeftArrow "&#x027F8;" ><!--LONG LEFTWARDS DOUBLE ARROW -->
<!ENTITY DoubleLongLeftRightArrow "&#x027FA;" ><!--LONG LEFT RIGHT DOUBLE ARROW -->
<!ENTITY DoubleLongRightArrow "&#x027F9;" ><!--LONG RIGHTWARDS DOUBLE ARROW -->
<!ENTITY DoubleRightArrow "&#x021D2;" ><!--RIGHTWARDS DOUBLE ARROW -->
<!ENTITY DoubleRightTee   "&#x022A8;" ><!--TRUE -->
<!ENTITY DoubleUpArrow    "&#x021D1;" ><!--UPWARDS DOUBLE ARROW -->
<!ENTITY DoubleUpDownArrow "&#x021D5;" ><!--UP DOWN DOUBLE ARROW -->
<!ENTITY DoubleVerticalBar "&#x02225;" ><!--PARALLEL TO -->
<!ENTITY DownArrow        "&#x02193;" ><!--DOWNWARDS ARROW -->
<!ENTITY DownArrowBar     "&#x02913;" ><!--DOWNWARDS ARROW TO BAR -->
<!ENTITY DownArrowUpArrow "&#x021F5;" ><!--DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW -->
<!ENTITY DownBreve        "&#x00311;" ><!--COMBINING INVERTED BREVE -->
<!ENTITY DownLeftRightVector "&#x02950;" ><!--LEFT BARB DOWN RIGHT BARB DOWN HARPOON -->
<!ENTITY DownLeftTeeVector "&#x0295E;" ><!--LEFTWARDS HARPOON WITH BARB DOWN FROM BAR -->
<!ENTITY DownLeftVector   "&#x021BD;" ><!--LEFTWARDS HARPOON WITH BARB DOWNWARDS -->
<!ENTITY DownLeftVectorBar "&#x02956;" ><!--LEFTWARDS HARPOON WITH BARB DOWN TO BAR -->
<!ENTITY DownRightTeeVector "&#x0295F;" ><!--RIGHTWARDS HARPOON WITH BARB DOWN FROM BAR -->
<!ENTITY DownRightVector  "&#x021C1;" ><!--RIGHTWARDS HARPOON WITH BARB DOWNWARDS -->
<!ENTITY DownRightVectorBar "&#x02957;" ><!--RIGHTWARDS HARPOON WITH BARB DOWN TO BAR -->
<!ENTITY DownTee          "&#x022A4;" ><!--DOWN TACK -->
<!ENTITY DownTeeArrow     "&#x021A7;" ><!--DOWNWARDS ARROW FROM BAR -->
<!ENTITY Downarrow        "&#x021D3;" ><!--DOWNWARDS DOUBLE ARROW -->
<!ENTITY Dscr             "&#x1D49F;" ><!--MATHEMATICAL SCRIPT CAPITAL D -->
<!ENTITY Dstrok           "&#x00110;" ><!--LATIN CAPITAL LETTER D WITH STROKE -->
<!ENTITY ENG              "&#x0014A;" ><!--LATIN CAPITAL LETTER ENG -->
<!ENTITY ETH              "&#x000D0;" ><!--LATIN CAPITAL LETTER ETH -->
<!ENTITY Eacute           "&#x000C9;" ><!--LATIN CAPITAL LETTER E WITH ACUTE -->
<!ENTITY Ecaron           "&#x0011A;" ><!--LATIN CAPITAL LETTER E WITH CARON -->
<!ENTITY Ecirc            "&#x000CA;" ><!--LATIN CAPITAL LETTER E WITH CIRCUMFLEX -->
<!ENTITY Ecy              "&#x0042D;" ><!--CYRILLIC CAPITAL LETTER E -->
<!ENTITY Edot             "&#x00116;" ><!--LATIN CAPITAL LETTER E WITH DOT ABOVE -->
<!ENTITY Efr              "&#x1D508;" ><!--MATHEMATICAL FRAKTUR CAPITAL E -->
<!ENTITY Egrave           "&#x000C8;" ><!--LATIN CAPITAL LETTER E WITH GRAVE -->
<!ENTITY Element          "&#x02208;" ><!--ELEMENT OF -->
<!ENTITY Emacr            "&#x00112;" ><!--LATIN CAPITAL LETTER E WITH MACRON -->
<!ENTITY EmptySmallSquare "&#x025FB;" ><!--WHITE MEDIUM SQUARE -->
<!ENTITY EmptyVerySmallSquare "&#x025AB;" ><!--WHITE SMALL SQUARE -->
<!ENTITY Eogon            "&#x00118;" ><!--LATIN CAPITAL LETTER E WITH OGONEK -->
<!ENTITY Eopf             "&#x1D53C;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL E -->
<!ENTITY Epsilon          "&#x00395;" ><!--GREEK CAPITAL LETTER EPSILON -->
<!ENTITY Equal            "&#x02A75;" ><!--TWO CONSECUTIVE EQUALS SIGNS -->
<!ENTITY EqualTilde       "&#x02242;" ><!--MINUS TILDE -->
<!ENTITY Equilibrium      "&#x021CC;" ><!--RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON -->
<!ENTITY Escr             "&#x02130;" ><!--SCRIPT CAPITAL E -->
<!ENTITY Esim             "&#x02A73;" ><!--EQUALS SIGN ABOVE TILDE OPERATOR -->
<!ENTITY Eta              "&#x00397;" ><!--GREEK CAPITAL LETTER ETA -->
<!ENTITY Euml             "&#x000CB;" ><!--LATIN CAPITAL LETTER E WITH DIAERESIS -->
<!ENTITY Exists           "&#x02203;" ><!--THERE EXISTS -->
<!ENTITY ExponentialE     "&#x02147;" ><!--DOUBLE-STRUCK ITALIC SMALL E -->
<!ENTITY Fcy              "&#x00424;" ><!--CYRILLIC CAPITAL LETTER EF -->
<!ENTITY Ffr              "&#x1D509;" ><!--MATHEMATICAL FRAKTUR CAPITAL F -->
<!ENTITY FilledSmallSquare "&#x025FC;" ><!--BLACK MEDIUM SQUARE -->
<!ENTITY FilledVerySmallSquare "&#x025AA;" ><!--BLACK SMALL SQUARE -->
<!ENTITY Fopf             "&#x1D53D;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL F -->
<!ENTITY ForAll           "&#x02200;" ><!--FOR ALL -->
<!ENTITY Fouriertrf       "&#x02131;" ><!--SCRIPT CAPITAL F -->
<!ENTITY Fscr             "&#x02131;" ><!--SCRIPT CAPITAL F -->
<!ENTITY GJcy             "&#x00403;" ><!--CYRILLIC CAPITAL LETTER GJE -->
<!ENTITY GT               "&#x0003E;" ><!--GREATER-THAN SIGN -->
<!ENTITY Gamma            "&#x00393;" ><!--GREEK CAPITAL LETTER GAMMA -->
<!ENTITY Gammad           "&#x003DC;" ><!--GREEK LETTER DIGAMMA -->
<!ENTITY Gbreve           "&#x0011E;" ><!--LATIN CAPITAL LETTER G WITH BREVE -->
<!ENTITY Gcedil           "&#x00122;" ><!--LATIN CAPITAL LETTER G WITH CEDILLA -->
<!ENTITY Gcirc            "&#x0011C;" ><!--LATIN CAPITAL LETTER G WITH CIRCUMFLEX -->
<!ENTITY Gcy              "&#x00413;" ><!--CYRILLIC CAPITAL LETTER GHE -->
<!ENTITY Gdot             "&#x00120;" ><!--LATIN CAPITAL LETTER G WITH DOT ABOVE -->
<!ENTITY Gfr              "&#x1D50A;" ><!--MATHEMATICAL FRAKTUR CAPITAL G -->
<!ENTITY Gg               "&#x022D9;" ><!--VERY MUCH GREATER-THAN -->
<!ENTITY Gopf             "&#x1D53E;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL G -->
<!ENTITY GreaterEqual     "&#x02265;" ><!--GREATER-THAN OR EQUAL TO -->
<!ENTITY GreaterEqualLess "&#x022DB;" ><!--GREATER-THAN EQUAL TO OR LESS-THAN -->
<!ENTITY GreaterFullEqual "&#x02267;" ><!--GREATER-THAN OVER EQUAL TO -->
<!ENTITY GreaterGreater   "&#x02AA2;" ><!--DOUBLE NESTED GREATER-THAN -->
<!ENTITY GreaterLess      "&#x02277;" ><!--GREATER-THAN OR LESS-THAN -->
<!ENTITY GreaterSlantEqual "&#x02A7E;" ><!--GREATER-THAN OR SLANTED EQUAL TO -->
<!ENTITY GreaterTilde     "&#x02273;" ><!--GREATER-THAN OR EQUIVALENT TO -->
<!ENTITY Gscr             "&#x1D4A2;" ><!--MATHEMATICAL SCRIPT CAPITAL G -->
<!ENTITY Gt               "&#x0226B;" ><!--MUCH GREATER-THAN -->
<!ENTITY HARDcy           "&#x0042A;" ><!--CYRILLIC CAPITAL LETTER HARD SIGN -->
<!ENTITY Hacek            "&#x002C7;" ><!--CARON -->
<!ENTITY Hat              "&#x0005E;" ><!--CIRCUMFLEX ACCENT -->
<!ENTITY Hcirc            "&#x00124;" ><!--LATIN CAPITAL LETTER H WITH CIRCUMFLEX -->
<!ENTITY Hfr              "&#x0210C;" ><!--BLACK-LETTER CAPITAL H -->
<!ENTITY HilbertSpace     "&#x0210B;" ><!--SCRIPT CAPITAL H -->
<!ENTITY Hopf             "&#x0210D;" ><!--DOUBLE-STRUCK CAPITAL H -->
<!ENTITY HorizontalLine   "&#x02500;" ><!--BOX DRAWINGS LIGHT HORIZONTAL -->
<!ENTITY Hscr             "&#x0210B;" ><!--SCRIPT CAPITAL H -->
<!ENTITY Hstrok           "&#x00126;" ><!--LATIN CAPITAL LETTER H WITH STROKE -->
<!ENTITY HumpDownHump     "&#x0224E;" ><!--GEOMETRICALLY EQUIVALENT TO -->
<!ENTITY HumpEqual        "&#x0224F;" ><!--DIFFERENCE BETWEEN -->
<!ENTITY IEcy             "&#x00415;" ><!--CYRILLIC CAPITAL LETTER IE -->
<!ENTITY IJlig            "&#x00132;" ><!--LATIN CAPITAL LIGATURE IJ -->
<!ENTITY IOcy             "&#x00401;" ><!--CYRILLIC CAPITAL LETTER IO -->
<!ENTITY Iacute           "&#x000CD;" ><!--LATIN CAPITAL LETTER I WITH ACUTE -->
<!ENTITY Icirc            "&#x000CE;" ><!--LATIN CAPITAL LETTER I WITH CIRCUMFLEX -->
<!ENTITY Icy              "&#x00418;" ><!--CYRILLIC CAPITAL LETTER I -->
<!ENTITY Idot             "&#x00130;" ><!--LATIN CAPITAL LETTER I WITH DOT ABOVE -->
<!ENTITY Ifr              "&#x02111;" ><!--BLACK-LETTER CAPITAL I -->
<!ENTITY Igrave           "&#x000CC;" ><!--LATIN CAPITAL LETTER I WITH GRAVE -->
<!ENTITY Im               "&#x02111;" ><!--BLACK-LETTER CAPITAL I -->
<!ENTITY Imacr            "&#x0012A;" ><!--LATIN CAPITAL LETTER I WITH MACRON -->
<!ENTITY ImaginaryI       "&#x02148;" ><!--DOUBLE-STRUCK ITALIC SMALL I -->
<!ENTITY Implies          "&#x021D2;" ><!--RIGHTWARDS DOUBLE ARROW -->
<!ENTITY Int              "&#x0222C;" ><!--DOUBLE INTEGRAL -->
<!ENTITY Integral         "&#x0222B;" ><!--INTEGRAL -->
<!ENTITY Intersection     "&#x022C2;" ><!--N-ARY INTERSECTION -->
<!ENTITY InvisibleComma   "&#x02063;" ><!--INVISIBLE SEPARATOR -->
<!ENTITY InvisibleTimes   "&#x02062;" ><!--INVISIBLE TIMES -->
<!ENTITY Iogon            "&#x0012E;" ><!--LATIN CAPITAL LETTER I WITH OGONEK -->
<!ENTITY Iopf             "&#x1D540;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL I -->
<!ENTITY Iota             "&#x00399;" ><!--GREEK CAPITAL LETTER IOTA -->
<!ENTITY Iscr             "&#x02110;" ><!--SCRIPT CAPITAL I -->
<!ENTITY Itilde           "&#x00128;" ><!--LATIN CAPITAL LETTER I WITH TILDE -->
<!ENTITY Iukcy            "&#x00406;" ><!--CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I -->
<!ENTITY Iuml             "&#x000CF;" ><!--LATIN CAPITAL LETTER I WITH DIAERESIS -->
<!ENTITY Jcirc            "&#x00134;" ><!--LATIN CAPITAL LETTER J WITH CIRCUMFLEX -->
<!ENTITY Jcy              "&#x00419;" ><!--CYRILLIC CAPITAL LETTER SHORT I -->
<!ENTITY Jfr              "&#x1D50D;" ><!--MATHEMATICAL FRAKTUR CAPITAL J -->
<!ENTITY Jopf             "&#x1D541;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL J -->
<!ENTITY Jscr             "&#x1D4A5;" ><!--MATHEMATICAL SCRIPT CAPITAL J -->
<!ENTITY Jsercy           "&#x00408;" ><!--CYRILLIC CAPITAL LETTER JE -->
<!ENTITY Jukcy            "&#x00404;" ><!--CYRILLIC CAPITAL LETTER UKRAINIAN IE -->
<!ENTITY KHcy             "&#x00425;" ><!--CYRILLIC CAPITAL LETTER HA -->
<!ENTITY KJcy             "&#x0040C;" ><!--CYRILLIC CAPITAL LETTER KJE -->
<!ENTITY Kappa            "&#x0039A;" ><!--GREEK CAPITAL LETTER KAPPA -->
<!ENTITY Kcedil           "&#x00136;" ><!--LATIN CAPITAL LETTER K WITH CEDILLA -->
<!ENTITY Kcy              "&#x0041A;" ><!--CYRILLIC CAPITAL LETTER KA -->
<!ENTITY Kfr              "&#x1D50E;" ><!--MATHEMATICAL FRAKTUR CAPITAL K -->
<!ENTITY Kopf             "&#x1D542;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL K -->
<!ENTITY Kscr             "&#x1D4A6;" ><!--MATHEMATICAL SCRIPT CAPITAL K -->
<!ENTITY LJcy             "&#x00409;" ><!--CYRILLIC CAPITAL LETTER LJE -->
<!ENTITY LT               "&#38;#60;" ><!--LESS-THAN SIGN -->
<!ENTITY Lacute           "&#x00139;" ><!--LATIN CAPITAL LETTER L WITH ACUTE -->
<!ENTITY Lambda           "&#x0039B;" ><!--GREEK CAPITAL LETTER LAMDA -->
<!ENTITY Lang             "&#x027EA;" ><!--MATHEMATICAL LEFT DOUBLE ANGLE BRACKET -->
<!ENTITY Laplacetrf       "&#x02112;" ><!--SCRIPT CAPITAL L -->
<!ENTITY Larr             "&#x0219E;" ><!--LEFTWARDS TWO HEADED ARROW -->
<!ENTITY Lcaron           "&#x0013D;" ><!--LATIN CAPITAL LETTER L WITH CARON -->
<!ENTITY Lcedil           "&#x0013B;" ><!--LATIN CAPITAL LETTER L WITH CEDILLA -->
<!ENTITY Lcy              "&#x0041B;" ><!--CYRILLIC CAPITAL LETTER EL -->
<!ENTITY LeftAngleBracket "&#x027E8;" ><!--MATHEMATICAL LEFT ANGLE BRACKET -->
<!ENTITY LeftArrow        "&#x02190;" ><!--LEFTWARDS ARROW -->
<!ENTITY LeftArrowBar     "&#x021E4;" ><!--LEFTWARDS ARROW TO BAR -->
<!ENTITY LeftArrowRightArrow "&#x021C6;" ><!--LEFTWARDS ARROW OVER RIGHTWARDS ARROW -->
<!ENTITY LeftCeiling      "&#x02308;" ><!--LEFT CEILING -->
<!ENTITY LeftDoubleBracket "&#x027E6;" ><!--MATHEMATICAL LEFT WHITE SQUARE BRACKET -->
<!ENTITY LeftDownTeeVector "&#x02961;" ><!--DOWNWARDS HARPOON WITH BARB LEFT FROM BAR -->
<!ENTITY LeftDownVector   "&#x021C3;" ><!--DOWNWARDS HARPOON WITH BARB LEFTWARDS -->
<!ENTITY LeftDownVectorBar "&#x02959;" ><!--DOWNWARDS HARPOON WITH BARB LEFT TO BAR -->
<!ENTITY LeftFloor        "&#x0230A;" ><!--LEFT FLOOR -->
<!ENTITY LeftRightArrow   "&#x02194;" ><!--LEFT RIGHT ARROW -->
<!ENTITY LeftRightVector  "&#x0294E;" ><!--LEFT BARB UP RIGHT BARB UP HARPOON -->
<!ENTITY LeftTee          "&#x022A3;" ><!--LEFT TACK -->
<!ENTITY LeftTeeArrow     "&#x021A4;" ><!--LEFTWARDS ARROW FROM BAR -->
<!ENTITY LeftTeeVector    "&#x0295A;" ><!--LEFTWARDS HARPOON WITH BARB UP FROM BAR -->
<!ENTITY LeftTriangle     "&#x022B2;" ><!--NORMAL SUBGROUP OF -->
<!ENTITY LeftTriangleBar  "&#x029CF;" ><!--LEFT TRIANGLE BESIDE VERTICAL BAR -->
<!ENTITY LeftTriangleEqual "&#x022B4;" ><!--NORMAL SUBGROUP OF OR EQUAL TO -->
<!ENTITY LeftUpDownVector "&#x02951;" ><!--UP BARB LEFT DOWN BARB LEFT HARPOON -->
<!ENTITY LeftUpTeeVector  "&#x02960;" ><!--UPWARDS HARPOON WITH BARB LEFT FROM BAR -->
<!ENTITY LeftUpVector     "&#x021BF;" ><!--UPWARDS HARPOON WITH BARB LEFTWARDS -->
<!ENTITY LeftUpVectorBar  "&#x02958;" ><!--UPWARDS HARPOON WITH BARB LEFT TO BAR -->
<!ENTITY LeftVector       "&#x021BC;" ><!--LEFTWARDS HARPOON WITH BARB UPWARDS -->
<!ENTITY LeftVectorBar    "&#x02952;" ><!--LEFTWARDS HARPOON WITH BARB UP TO BAR -->
<!ENTITY Leftarrow        "&#x021D0;" ><!--LEFTWARDS DOUBLE ARROW -->
<!ENTITY Leftrightarrow   "&#x021D4;" ><!--LEFT RIGHT DOUBLE ARROW -->
<!ENTITY LessEqualGreater "&#x022DA;" ><!--LESS-THAN EQUAL TO OR GREATER-THAN -->
<!ENTITY LessFullEqual    "&#x02266;" ><!--LESS-THAN OVER EQUAL TO -->
<!ENTITY LessGreater      "&#x02276;" ><!--LESS-THAN OR GREATER-THAN -->
<!ENTITY LessLess         "&#x02AA1;" ><!--DOUBLE NESTED LESS-THAN -->
<!ENTITY LessSlantEqual   "&#x02A7D;" ><!--LESS-THAN OR SLANTED EQUAL TO -->
<!ENTITY LessTilde        "&#x02272;" ><!--LESS-THAN OR EQUIVALENT TO -->
<!ENTITY Lfr              "&#x1D50F;" ><!--MATHEMATICAL FRAKTUR CAPITAL L -->
<!ENTITY Ll               "&#x022D8;" ><!--VERY MUCH LESS-THAN -->
<!ENTITY Lleftarrow       "&#x021DA;" ><!--LEFTWARDS TRIPLE ARROW -->
<!ENTITY Lmidot           "&#x0013F;" ><!--LATIN CAPITAL LETTER L WITH MIDDLE DOT -->
<!ENTITY LongLeftArrow    "&#x027F5;" ><!--LONG LEFTWARDS ARROW -->
<!ENTITY LongLeftRightArrow "&#x027F7;" ><!--LONG LEFT RIGHT ARROW -->
<!ENTITY LongRightArrow   "&#x027F6;" ><!--LONG RIGHTWARDS ARROW -->
<!ENTITY Longleftarrow    "&#x027F8;" ><!--LONG LEFTWARDS DOUBLE ARROW -->
<!ENTITY Longleftrightarrow "&#x027FA;" ><!--LONG LEFT RIGHT DOUBLE ARROW -->
<!ENTITY Longrightarrow   "&#x027F9;" ><!--LONG RIGHTWARDS DOUBLE ARROW -->
<!ENTITY Lopf             "&#x1D543;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL L -->
<!ENTITY LowerLeftArrow   "&#x02199;" ><!--SOUTH WEST ARROW -->
<!ENTITY LowerRightArrow  "&#x02198;" ><!--SOUTH EAST ARROW -->
<!ENTITY Lscr             "&#x02112;" ><!--SCRIPT CAPITAL L -->
<!ENTITY Lsh              "&#x021B0;" ><!--UPWARDS ARROW WITH TIP LEFTWARDS -->
<!ENTITY Lstrok           "&#x00141;" ><!--LATIN CAPITAL LETTER L WITH STROKE -->
<!ENTITY Lt               "&#x0226A;" ><!--MUCH LESS-THAN -->
<!ENTITY Map              "&#x02905;" ><!--RIGHTWARDS TWO-HEADED ARROW FROM BAR -->
<!ENTITY Mcy              "&#x0041C;" ><!--CYRILLIC CAPITAL LETTER EM -->
<!ENTITY MediumSpace      "&#x0205F;" ><!--MEDIUM MATHEMATICAL SPACE -->
<!ENTITY Mellintrf        "&#x02133;" ><!--SCRIPT CAPITAL M -->
<!ENTITY Mfr              "&#x1D510;" ><!--MATHEMATICAL FRAKTUR CAPITAL M -->
<!ENTITY MinusPlus        "&#x02213;" ><!--MINUS-OR-PLUS SIGN -->
<!ENTITY Mopf             "&#x1D544;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL M -->
<!ENTITY Mscr             "&#x02133;" ><!--SCRIPT CAPITAL M -->
<!ENTITY Mu               "&#x0039C;" ><!--GREEK CAPITAL LETTER MU -->
<!ENTITY NJcy             "&#x0040A;" ><!--CYRILLIC CAPITAL LETTER NJE -->
<!ENTITY Nacute           "&#x00143;" ><!--LATIN CAPITAL LETTER N WITH ACUTE -->
<!ENTITY Ncaron           "&#x00147;" ><!--LATIN CAPITAL LETTER N WITH CARON -->
<!ENTITY Ncedil           "&#x00145;" ><!--LATIN CAPITAL LETTER N WITH CEDILLA -->
<!ENTITY Ncy              "&#x0041D;" ><!--CYRILLIC CAPITAL LETTER EN -->
<!ENTITY NegativeMediumSpace "&#x0200B;" ><!--ZERO WIDTH SPACE -->
<!ENTITY NegativeThickSpace "&#x0200B;" ><!--ZERO WIDTH SPACE -->
<!ENTITY NegativeThinSpace "&#x0200B;" ><!--ZERO WIDTH SPACE -->
<!ENTITY NegativeVeryThinSpace "&#x0200B;" ><!--ZERO WIDTH SPACE -->
<!ENTITY NestedGreaterGreater "&#x0226B;" ><!--MUCH GREATER-THAN -->
<!ENTITY NestedLessLess   "&#x0226A;" ><!--MUCH LESS-THAN -->
<!ENTITY NewLine          "&#x0000A;" ><!--LINE FEED (LF) -->
<!ENTITY Nfr              "&#x1D511;" ><!--MATHEMATICAL FRAKTUR CAPITAL N -->
<!ENTITY NoBreak          "&#x02060;" ><!--WORD JOINER -->
<!ENTITY NonBreakingSpace "&#x000A0;" ><!--NO-BREAK SPACE -->
<!ENTITY Nopf             "&#x02115;" ><!--DOUBLE-STRUCK CAPITAL N -->
<!ENTITY Not              "&#x02AEC;" ><!--DOUBLE STROKE NOT SIGN -->
<!ENTITY NotCongruent     "&#x02262;" ><!--NOT IDENTICAL TO -->
<!ENTITY NotCupCap        "&#x0226D;" ><!--NOT EQUIVALENT TO -->
<!ENTITY NotDoubleVerticalBar "&#x02226;" ><!--NOT PARALLEL TO -->
<!ENTITY NotElement       "&#x02209;" ><!--NOT AN ELEMENT OF -->
<!ENTITY NotEqual         "&#x02260;" ><!--NOT EQUAL TO -->
<!ENTITY NotEqualTilde    "&#x02242;&#x00338;" ><!--MINUS TILDE with slash -->
<!ENTITY NotExists        "&#x02204;" ><!--THERE DOES NOT EXIST -->
<!ENTITY NotGreater       "&#x0226F;" ><!--NOT GREATER-THAN -->
<!ENTITY NotGreaterEqual  "&#x02271;" ><!--NEITHER GREATER-THAN NOR EQUAL TO -->
<!ENTITY NotGreaterFullEqual "&#x02267;&#x00338;" ><!--GREATER-THAN OVER EQUAL TO with slash -->
<!ENTITY NotGreaterGreater "&#x0226B;&#x00338;" ><!--MUCH GREATER THAN with slash -->
<!ENTITY NotGreaterLess   "&#x02279;" ><!--NEITHER GREATER-THAN NOR LESS-THAN -->
<!ENTITY NotGreaterSlantEqual "&#x02A7E;&#x00338;" ><!--GREATER-THAN OR SLANTED EQUAL TO with slash -->
<!ENTITY NotGreaterTilde  "&#x02275;" ><!--NEITHER GREATER-THAN NOR EQUIVALENT TO -->
<!ENTITY NotHumpDownHump  "&#x0224E;&#x00338;" ><!--GEOMETRICALLY EQUIVALENT TO with slash -->
<!ENTITY NotHumpEqual     "&#x0224F;&#x00338;" ><!--DIFFERENCE BETWEEN with slash -->
<!ENTITY NotLeftTriangle  "&#x022EA;" ><!--NOT NORMAL SUBGROUP OF -->
<!ENTITY NotLeftTriangleBar "&#x029CF;&#x00338;" ><!--LEFT TRIANGLE BESIDE VERTICAL BAR with slash -->
<!ENTITY NotLeftTriangleEqual "&#x022EC;" ><!--NOT NORMAL SUBGROUP OF OR EQUAL TO -->
<!ENTITY NotLess          "&#x0226E;" ><!--NOT LESS-THAN -->
<!ENTITY NotLessEqual     "&#x02270;" ><!--NEITHER LESS-THAN NOR EQUAL TO -->
<!ENTITY NotLessGreater   "&#x02278;" ><!--NEITHER LESS-THAN NOR GREATER-THAN -->
<!ENTITY NotLessLess      "&#x0226A;&#x00338;" ><!--MUCH LESS THAN with slash -->
<!ENTITY NotLessSlantEqual "&#x02A7D;&#x00338;" ><!--LESS-THAN OR SLANTED EQUAL TO with slash -->
<!ENTITY NotLessTilde     "&#x02274;" ><!--NEITHER LESS-THAN NOR EQUIVALENT TO -->
<!ENTITY NotNestedGreaterGreater "&#x02AA2;&#x00338;" ><!--DOUBLE NESTED GREATER-THAN with slash -->
<!ENTITY NotNestedLessLess "&#x02AA1;&#x00338;" ><!--DOUBLE NESTED LESS-THAN with slash -->
<!ENTITY NotPrecedes      "&#x02280;" ><!--DOES NOT PRECEDE -->
<!ENTITY NotPrecedesEqual "&#x02AAF;&#x00338;" ><!--PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash -->
<!ENTITY NotPrecedesSlantEqual "&#x022E0;" ><!--DOES NOT PRECEDE OR EQUAL -->
<!ENTITY NotReverseElement "&#x0220C;" ><!--DOES NOT CONTAIN AS MEMBER -->
<!ENTITY NotRightTriangle "&#x022EB;" ><!--DOES NOT CONTAIN AS NORMAL SUBGROUP -->
<!ENTITY NotRightTriangleBar "&#x029D0;&#x00338;" ><!--VERTICAL BAR BESIDE RIGHT TRIANGLE with slash -->
<!ENTITY NotRightTriangleEqual "&#x022ED;" ><!--DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL -->
<!ENTITY NotSquareSubset  "&#x0228F;&#x00338;" ><!--SQUARE IMAGE OF with slash -->
<!ENTITY NotSquareSubsetEqual "&#x022E2;" ><!--NOT SQUARE IMAGE OF OR EQUAL TO -->
<!ENTITY NotSquareSuperset "&#x02290;&#x00338;" ><!--SQUARE ORIGINAL OF with slash -->
<!ENTITY NotSquareSupersetEqual "&#x022E3;" ><!--NOT SQUARE ORIGINAL OF OR EQUAL TO -->
<!ENTITY NotSubset        "&#x02282;&#x020D2;" ><!--SUBSET OF with vertical line -->
<!ENTITY NotSubsetEqual   "&#x02288;" ><!--NEITHER A SUBSET OF NOR EQUAL TO -->
<!ENTITY NotSucceeds      "&#x02281;" ><!--DOES NOT SUCCEED -->
<!ENTITY NotSucceedsEqual "&#x02AB0;&#x00338;" ><!--SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash -->
<!ENTITY NotSucceedsSlantEqual "&#x022E1;" ><!--DOES NOT SUCCEED OR EQUAL -->
<!ENTITY NotSucceedsTilde "&#x0227F;&#x00338;" ><!--SUCCEEDS OR EQUIVALENT TO with slash -->
<!ENTITY NotSuperset      "&#x02283;&#x020D2;" ><!--SUPERSET OF with vertical line -->
<!ENTITY NotSupersetEqual "&#x02289;" ><!--NEITHER A SUPERSET OF NOR EQUAL TO -->
<!ENTITY NotTilde         "&#x02241;" ><!--NOT TILDE -->
<!ENTITY NotTildeEqual    "&#x02244;" ><!--NOT ASYMPTOTICALLY EQUAL TO -->
<!ENTITY NotTildeFullEqual "&#x02247;" ><!--NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO -->
<!ENTITY NotTildeTilde    "&#x02249;" ><!--NOT ALMOST EQUAL TO -->
<!ENTITY NotVerticalBar   "&#x02224;" ><!--DOES NOT DIVIDE -->
<!ENTITY Nscr             "&#x1D4A9;" ><!--MATHEMATICAL SCRIPT CAPITAL N -->
<!ENTITY Ntilde           "&#x000D1;" ><!--LATIN CAPITAL LETTER N WITH TILDE -->
<!ENTITY Nu               "&#x0039D;" ><!--GREEK CAPITAL LETTER NU -->
<!ENTITY OElig            "&#x00152;" ><!--LATIN CAPITAL LIGATURE OE -->
<!ENTITY Oacute           "&#x000D3;" ><!--LATIN CAPITAL LETTER O WITH ACUTE -->
<!ENTITY Ocirc            "&#x000D4;" ><!--LATIN CAPITAL LETTER O WITH CIRCUMFLEX -->
<!ENTITY Ocy              "&#x0041E;" ><!--CYRILLIC CAPITAL LETTER O -->
<!ENTITY Odblac           "&#x00150;" ><!--LATIN CAPITAL LETTER O WITH DOUBLE ACUTE -->
<!ENTITY Ofr              "&#x1D512;" ><!--MATHEMATICAL FRAKTUR CAPITAL O -->
<!ENTITY Ograve           "&#x000D2;" ><!--LATIN CAPITAL LETTER O WITH GRAVE -->
<!ENTITY Omacr            "&#x0014C;" ><!--LATIN CAPITAL LETTER O WITH MACRON -->
<!ENTITY Omega            "&#x003A9;" ><!--GREEK CAPITAL LETTER OMEGA -->
<!ENTITY Omicron          "&#x0039F;" ><!--GREEK CAPITAL LETTER OMICRON -->
<!ENTITY Oopf             "&#x1D546;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL O -->
<!ENTITY OpenCurlyDoubleQuote "&#x0201C;" ><!--LEFT DOUBLE QUOTATION MARK -->
<!ENTITY OpenCurlyQuote   "&#x02018;" ><!--LEFT SINGLE QUOTATION MARK -->
<!ENTITY Or               "&#x02A54;" ><!--DOUBLE LOGICAL OR -->
<!ENTITY Oscr             "&#x1D4AA;" ><!--MATHEMATICAL SCRIPT CAPITAL O -->
<!ENTITY Oslash           "&#x000D8;" ><!--LATIN CAPITAL LETTER O WITH STROKE -->
<!ENTITY Otilde           "&#x000D5;" ><!--LATIN CAPITAL LETTER O WITH TILDE -->
<!ENTITY Otimes           "&#x02A37;" ><!--MULTIPLICATION SIGN IN DOUBLE CIRCLE -->
<!ENTITY Ouml             "&#x000D6;" ><!--LATIN CAPITAL LETTER O WITH DIAERESIS -->
<!ENTITY OverBar          "&#x0203E;" ><!--OVERLINE -->
<!ENTITY OverBrace        "&#x023DE;" ><!--TOP CURLY BRACKET -->
<!ENTITY OverBracket      "&#x023B4;" ><!--TOP SQUARE BRACKET -->
<!ENTITY OverParenthesis  "&#x023DC;" ><!--TOP PARENTHESIS -->
<!ENTITY PartialD         "&#x02202;" ><!--PARTIAL DIFFERENTIAL -->
<!ENTITY Pcy              "&#x0041F;" ><!--CYRILLIC CAPITAL LETTER PE -->
<!ENTITY Pfr              "&#x1D513;" ><!--MATHEMATICAL FRAKTUR CAPITAL P -->
<!ENTITY Phi              "&#x003A6;" ><!--GREEK CAPITAL LETTER PHI -->
<!ENTITY Pi               "&#x003A0;" ><!--GREEK CAPITAL LETTER PI -->
<!ENTITY PlusMinus        "&#x000B1;" ><!--PLUS-MINUS SIGN -->
<!ENTITY Poincareplane    "&#x0210C;" ><!--BLACK-LETTER CAPITAL H -->
<!ENTITY Popf             "&#x02119;" ><!--DOUBLE-STRUCK CAPITAL P -->
<!ENTITY Pr               "&#x02ABB;" ><!--DOUBLE PRECEDES -->
<!ENTITY Precedes         "&#x0227A;" ><!--PRECEDES -->
<!ENTITY PrecedesEqual    "&#x02AAF;" ><!--PRECEDES ABOVE SINGLE-LINE EQUALS SIGN -->
<!ENTITY PrecedesSlantEqual "&#x0227C;" ><!--PRECEDES OR EQUAL TO -->
<!ENTITY PrecedesTilde    "&#x0227E;" ><!--PRECEDES OR EQUIVALENT TO -->
<!ENTITY Prime            "&#x02033;" ><!--DOUBLE PRIME -->
<!ENTITY Product          "&#x0220F;" ><!--N-ARY PRODUCT -->
<!ENTITY Proportion       "&#x02237;" ><!--PROPORTION -->
<!ENTITY Proportional     "&#x0221D;" ><!--PROPORTIONAL TO -->
<!ENTITY Pscr             "&#x1D4AB;" ><!--MATHEMATICAL SCRIPT CAPITAL P -->
<!ENTITY Psi              "&#x003A8;" ><!--GREEK CAPITAL LETTER PSI -->
<!ENTITY QUOT             "&#x00022;" ><!--QUOTATION MARK -->
<!ENTITY Qfr              "&#x1D514;" ><!--MATHEMATICAL FRAKTUR CAPITAL Q -->
<!ENTITY Qopf             "&#x0211A;" ><!--DOUBLE-STRUCK CAPITAL Q -->
<!ENTITY Qscr             "&#x1D4AC;" ><!--MATHEMATICAL SCRIPT CAPITAL Q -->
<!ENTITY RBarr            "&#x02910;" ><!--RIGHTWARDS TWO-HEADED TRIPLE DASH ARROW -->
<!ENTITY REG              "&#x000AE;" ><!--REGISTERED SIGN -->
<!ENTITY Racute           "&#x00154;" ><!--LATIN CAPITAL LETTER R WITH ACUTE -->
<!ENTITY Rang             "&#x027EB;" ><!--MATHEMATICAL RIGHT DOUBLE ANGLE BRACKET -->
<!ENTITY Rarr             "&#x021A0;" ><!--RIGHTWARDS TWO HEADED ARROW -->
<!ENTITY Rarrtl           "&#x02916;" ><!--RIGHTWARDS TWO-HEADED ARROW WITH TAIL -->
<!ENTITY Rcaron           "&#x00158;" ><!--LATIN CAPITAL LETTER R WITH CARON -->
<!ENTITY Rcedil           "&#x00156;" ><!--LATIN CAPITAL LETTER R WITH CEDILLA -->
<!ENTITY Rcy              "&#x00420;" ><!--CYRILLIC CAPITAL LETTER ER -->
<!ENTITY Re               "&#x0211C;" ><!--BLACK-LETTER CAPITAL R -->
<!ENTITY ReverseElement   "&#x0220B;" ><!--CONTAINS AS MEMBER -->
<!ENTITY ReverseEquilibrium "&#x021CB;" ><!--LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON -->
<!ENTITY ReverseUpEquilibrium "&#x0296F;" ><!--DOWNWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT -->
<!ENTITY Rfr              "&#x0211C;" ><!--BLACK-LETTER CAPITAL R -->
<!ENTITY Rho              "&#x003A1;" ><!--GREEK CAPITAL LETTER RHO -->
<!ENTITY RightAngleBracket "&#x027E9;" ><!--MATHEMATICAL RIGHT ANGLE BRACKET -->
<!ENTITY RightArrow       "&#x02192;" ><!--RIGHTWARDS ARROW -->
<!ENTITY RightArrowBar    "&#x021E5;" ><!--RIGHTWARDS ARROW TO BAR -->
<!ENTITY RightArrowLeftArrow "&#x021C4;" ><!--RIGHTWARDS ARROW OVER LEFTWARDS ARROW -->
<!ENTITY RightCeiling     "&#x02309;" ><!--RIGHT CEILING -->
<!ENTITY RightDoubleBracket "&#x027E7;" ><!--MATHEMATICAL RIGHT WHITE SQUARE BRACKET -->
<!ENTITY RightDownTeeVector "&#x0295D;" ><!--DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR -->
<!ENTITY RightDownVector  "&#x021C2;" ><!--DOWNWARDS HARPOON WITH BARB RIGHTWARDS -->
<!ENTITY RightDownVectorBar "&#x02955;" ><!--DOWNWARDS HARPOON WITH BARB RIGHT TO BAR -->
<!ENTITY RightFloor       "&#x0230B;" ><!--RIGHT FLOOR -->
<!ENTITY RightTee         "&#x022A2;" ><!--RIGHT TACK -->
<!ENTITY RightTeeArrow    "&#x021A6;" ><!--RIGHTWARDS ARROW FROM BAR -->
<!ENTITY RightTeeVector   "&#x0295B;" ><!--RIGHTWARDS HARPOON WITH BARB UP FROM BAR -->
<!ENTITY RightTriangle    "&#x022B3;" ><!--CONTAINS AS NORMAL SUBGROUP -->
<!ENTITY RightTriangleBar "&#x029D0;" ><!--VERTICAL BAR BESIDE RIGHT TRIANGLE -->
<!ENTITY RightTriangleEqual "&#x022B5;" ><!--CONTAINS AS NORMAL SUBGROUP OR EQUAL TO -->
<!ENTITY RightUpDownVector "&#x0294F;" ><!--UP BARB RIGHT DOWN BARB RIGHT HARPOON -->
<!ENTITY RightUpTeeVector "&#x0295C;" ><!--UPWARDS HARPOON WITH BARB RIGHT FROM BAR -->
<!ENTITY RightUpVector    "&#x021BE;" ><!--UPWARDS HARPOON WITH BARB RIGHTWARDS -->
<!ENTITY RightUpVectorBar "&#x02954;" ><!--UPWARDS HARPOON WITH BARB RIGHT TO BAR -->
<!ENTITY RightVector      "&#x021C0;" ><!--RIGHTWARDS HARPOON WITH BARB UPWARDS -->
<!ENTITY RightVectorBar   "&#x02953;" ><!--RIGHTWARDS HARPOON WITH BARB UP TO BAR -->
<!ENTITY Rightarrow       "&#x021D2;" ><!--RIGHTWARDS DOUBLE ARROW -->
<!ENTITY Ropf             "&#x0211D;" ><!--DOUBLE-STRUCK CAPITAL R -->
<!ENTITY RoundImplies     "&#x02970;" ><!--RIGHT DOUBLE ARROW WITH ROUNDED HEAD -->
<!ENTITY Rrightarrow      "&#x021DB;" ><!--RIGHTWARDS TRIPLE ARROW -->
<!ENTITY Rscr             "&#x0211B;" ><!--SCRIPT CAPITAL R -->
<!ENTITY Rsh              "&#x021B1;" ><!--UPWARDS ARROW WITH TIP RIGHTWARDS -->
<!ENTITY RuleDelayed      "&#x029F4;" ><!--RULE-DELAYED -->
<!ENTITY SHCHcy           "&#x00429;" ><!--CYRILLIC CAPITAL LETTER SHCHA -->
<!ENTITY SHcy             "&#x00428;" ><!--CYRILLIC CAPITAL LETTER SHA -->
<!ENTITY SOFTcy           "&#x0042C;" ><!--CYRILLIC CAPITAL LETTER SOFT SIGN -->
<!ENTITY Sacute           "&#x0015A;" ><!--LATIN CAPITAL LETTER S WITH ACUTE -->
<!ENTITY Sc               "&#x02ABC;" ><!--DOUBLE SUCCEEDS -->
<!ENTITY Scaron           "&#x00160;" ><!--LATIN CAPITAL LETTER S WITH CARON -->
<!ENTITY Scedil           "&#x0015E;" ><!--LATIN CAPITAL LETTER S WITH CEDILLA -->
<!ENTITY Scirc            "&#x0015C;" ><!--LATIN CAPITAL LETTER S WITH CIRCUMFLEX -->
<!ENTITY Scy              "&#x00421;" ><!--CYRILLIC CAPITAL LETTER ES -->
<!ENTITY Sfr              "&#x1D516;" ><!--MATHEMATICAL FRAKTUR CAPITAL S -->
<!ENTITY ShortDownArrow   "&#x02193;" ><!--DOWNWARDS ARROW -->
<!ENTITY ShortLeftArrow   "&#x02190;" ><!--LEFTWARDS ARROW -->
<!ENTITY ShortRightArrow  "&#x02192;" ><!--RIGHTWARDS ARROW -->
<!ENTITY ShortUpArrow     "&#x02191;" ><!--UPWARDS ARROW -->
<!ENTITY Sigma            "&#x003A3;" ><!--GREEK CAPITAL LETTER SIGMA -->
<!ENTITY SmallCircle      "&#x02218;" ><!--RING OPERATOR -->
<!ENTITY Sopf             "&#x1D54A;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL S -->
<!ENTITY Sqrt             "&#x0221A;" ><!--SQUARE ROOT -->
<!ENTITY Square           "&#x025A1;" ><!--WHITE SQUARE -->
<!ENTITY SquareIntersection "&#x02293;" ><!--SQUARE CAP -->
<!ENTITY SquareSubset     "&#x0228F;" ><!--SQUARE IMAGE OF -->
<!ENTITY SquareSubsetEqual "&#x02291;" ><!--SQUARE IMAGE OF OR EQUAL TO -->
<!ENTITY SquareSuperset   "&#x02290;" ><!--SQUARE ORIGINAL OF -->
<!ENTITY SquareSupersetEqual "&#x02292;" ><!--SQUARE ORIGINAL OF OR EQUAL TO -->
<!ENTITY SquareUnion      "&#x02294;" ><!--SQUARE CUP -->
<!ENTITY Sscr             "&#x1D4AE;" ><!--MATHEMATICAL SCRIPT CAPITAL S -->
<!ENTITY Star             "&#x022C6;" ><!--STAR OPERATOR -->
<!ENTITY Sub              "&#x022D0;" ><!--DOUBLE SUBSET -->
<!ENTITY Subset           "&#x022D0;" ><!--DOUBLE SUBSET -->
<!ENTITY SubsetEqual      "&#x02286;" ><!--SUBSET OF OR EQUAL TO -->
<!ENTITY Succeeds         "&#x0227B;" ><!--SUCCEEDS -->
<!ENTITY SucceedsEqual    "&#x02AB0;" ><!--SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN -->
<!ENTITY SucceedsSlantEqual "&#x0227D;" ><!--SUCCEEDS OR EQUAL TO -->
<!ENTITY SucceedsTilde    "&#x0227F;" ><!--SUCCEEDS OR EQUIVALENT TO -->
<!ENTITY SuchThat         "&#x0220B;" ><!--CONTAINS AS MEMBER -->
<!ENTITY Sum              "&#x02211;" ><!--N-ARY SUMMATION -->
<!ENTITY Sup              "&#x022D1;" ><!--DOUBLE SUPERSET -->
<!ENTITY Superset         "&#x02283;" ><!--SUPERSET OF -->
<!ENTITY SupersetEqual    "&#x02287;" ><!--SUPERSET OF OR EQUAL TO -->
<!ENTITY Supset           "&#x022D1;" ><!--DOUBLE SUPERSET -->
<!ENTITY THORN            "&#x000DE;" ><!--LATIN CAPITAL LETTER THORN -->
<!ENTITY TRADE            "&#x02122;" ><!--TRADE MARK SIGN -->
<!ENTITY TSHcy            "&#x0040B;" ><!--CYRILLIC CAPITAL LETTER TSHE -->
<!ENTITY TScy             "&#x00426;" ><!--CYRILLIC CAPITAL LETTER TSE -->
<!ENTITY Tab              "&#x00009;" ><!--CHARACTER TABULATION -->
<!ENTITY Tau              "&#x003A4;" ><!--GREEK CAPITAL LETTER TAU -->
<!ENTITY Tcaron           "&#x00164;" ><!--LATIN CAPITAL LETTER T WITH CARON -->
<!ENTITY Tcedil           "&#x00162;" ><!--LATIN CAPITAL LETTER T WITH CEDILLA -->
<!ENTITY Tcy              "&#x00422;" ><!--CYRILLIC CAPITAL LETTER TE -->
<!ENTITY Tfr              "&#x1D517;" ><!--MATHEMATICAL FRAKTUR CAPITAL T -->
<!ENTITY Therefore        "&#x02234;" ><!--THEREFORE -->
<!ENTITY Theta            "&#x00398;" ><!--GREEK CAPITAL LETTER THETA -->
<!ENTITY ThickSpace       "&#x0205F;&#x0200A;" ><!--space of width 5/18 em -->
<!ENTITY ThinSpace        "&#x02009;" ><!--THIN SPACE -->
<!ENTITY Tilde            "&#x0223C;" ><!--TILDE OPERATOR -->
<!ENTITY TildeEqual       "&#x02243;" ><!--ASYMPTOTICALLY EQUAL TO -->
<!ENTITY TildeFullEqual   "&#x02245;" ><!--APPROXIMATELY EQUAL TO -->
<!ENTITY TildeTilde       "&#x02248;" ><!--ALMOST EQUAL TO -->
<!ENTITY Topf             "&#x1D54B;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL T -->
<!ENTITY TripleDot        "&#x020DB;" ><!--COMBINING THREE DOTS ABOVE -->
<!ENTITY Tscr             "&#x1D4AF;" ><!--MATHEMATICAL SCRIPT CAPITAL T -->
<!ENTITY Tstrok           "&#x00166;" ><!--LATIN CAPITAL LETTER T WITH STROKE -->
<!ENTITY Uacute           "&#x000DA;" ><!--LATIN CAPITAL LETTER U WITH ACUTE -->
<!ENTITY Uarr             "&#x0219F;" ><!--UPWARDS TWO HEADED ARROW -->
<!ENTITY Uarrocir         "&#x02949;" ><!--UPWARDS TWO-HEADED ARROW FROM SMALL CIRCLE -->
<!ENTITY Ubrcy            "&#x0040E;" ><!--CYRILLIC CAPITAL LETTER SHORT U -->
<!ENTITY Ubreve           "&#x0016C;" ><!--LATIN CAPITAL LETTER U WITH BREVE -->
<!ENTITY Ucirc            "&#x000DB;" ><!--LATIN CAPITAL LETTER U WITH CIRCUMFLEX -->
<!ENTITY Ucy              "&#x00423;" ><!--CYRILLIC CAPITAL LETTER U -->
<!ENTITY Udblac           "&#x00170;" ><!--LATIN CAPITAL LETTER U WITH DOUBLE ACUTE -->
<!ENTITY Ufr              "&#x1D518;" ><!--MATHEMATICAL FRAKTUR CAPITAL U -->
<!ENTITY Ugrave           "&#x000D9;" ><!--LATIN CAPITAL LETTER U WITH GRAVE -->
<!ENTITY Umacr            "&#x0016A;" ><!--LATIN CAPITAL LETTER U WITH MACRON -->
<!ENTITY UnderBar         "&#x0005F;" ><!--LOW LINE -->
<!ENTITY UnderBrace       "&#x023DF;" ><!--BOTTOM CURLY BRACKET -->
<!ENTITY UnderBracket     "&#x023B5;" ><!--BOTTOM SQUARE BRACKET -->
<!ENTITY UnderParenthesis "&#x023DD;" ><!--BOTTOM PARENTHESIS -->
<!ENTITY Union            "&#x022C3;" ><!--N-ARY UNION -->
<!ENTITY UnionPlus        "&#x0228E;" ><!--MULTISET UNION -->
<!ENTITY Uogon            "&#x00172;" ><!--LATIN CAPITAL LETTER U WITH OGONEK -->
<!ENTITY Uopf             "&#x1D54C;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL U -->
<!ENTITY UpArrow          "&#x02191;" ><!--UPWARDS ARROW -->
<!ENTITY UpArrowBar       "&#x02912;" ><!--UPWARDS ARROW TO BAR -->
<!ENTITY UpArrowDownArrow "&#x021C5;" ><!--UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW -->
<!ENTITY UpDownArrow      "&#x02195;" ><!--UP DOWN ARROW -->
<!ENTITY UpEquilibrium    "&#x0296E;" ><!--UPWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT -->
<!ENTITY UpTee            "&#x022A5;" ><!--UP TACK -->
<!ENTITY UpTeeArrow       "&#x021A5;" ><!--UPWARDS ARROW FROM BAR -->
<!ENTITY Uparrow          "&#x021D1;" ><!--UPWARDS DOUBLE ARROW -->
<!ENTITY Updownarrow      "&#x021D5;" ><!--UP DOWN DOUBLE ARROW -->
<!ENTITY UpperLeftArrow   "&#x02196;" ><!--NORTH WEST ARROW -->
<!ENTITY UpperRightArrow  "&#x02197;" ><!--NORTH EAST ARROW -->
<!ENTITY Upsi             "&#x003D2;" ><!--GREEK UPSILON WITH HOOK SYMBOL -->
<!ENTITY Upsilon          "&#x003A5;" ><!--GREEK CAPITAL LETTER UPSILON -->
<!ENTITY Uring            "&#x0016E;" ><!--LATIN CAPITAL LETTER U WITH RING ABOVE -->
<!ENTITY Uscr             "&#x1D4B0;" ><!--MATHEMATICAL SCRIPT CAPITAL U -->
<!ENTITY Utilde           "&#x00168;" ><!--LATIN CAPITAL LETTER U WITH TILDE -->
<!ENTITY Uuml             "&#x000DC;" ><!--LATIN CAPITAL LETTER U WITH DIAERESIS -->
<!ENTITY VDash            "&#x022AB;" ><!--DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE -->
<!ENTITY Vbar             "&#x02AEB;" ><!--DOUBLE UP TACK -->
<!ENTITY Vcy              "&#x00412;" ><!--CYRILLIC CAPITAL LETTER VE -->
<!ENTITY Vdash            "&#x022A9;" ><!--FORCES -->
<!ENTITY Vdashl           "&#x02AE6;" ><!--LONG DASH FROM LEFT MEMBER OF DOUBLE VERTICAL -->
<!ENTITY Vee              "&#x022C1;" ><!--N-ARY LOGICAL OR -->
<!ENTITY Verbar           "&#x02016;" ><!--DOUBLE VERTICAL LINE -->
<!ENTITY Vert             "&#x02016;" ><!--DOUBLE VERTICAL LINE -->
<!ENTITY VerticalBar      "&#x02223;" ><!--DIVIDES -->
<!ENTITY VerticalLine     "&#x0007C;" ><!--VERTICAL LINE -->
<!ENTITY VerticalSeparator "&#x02758;" ><!--LIGHT VERTICAL BAR -->
<!ENTITY VerticalTilde    "&#x02240;" ><!--WREATH PRODUCT -->
<!ENTITY VeryThinSpace    "&#x0200A;" ><!--HAIR SPACE -->
<!ENTITY Vfr              "&#x1D519;" ><!--MATHEMATICAL FRAKTUR CAPITAL V -->
<!ENTITY Vopf             "&#x1D54D;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL V -->
<!ENTITY Vscr             "&#x1D4B1;" ><!--MATHEMATICAL SCRIPT CAPITAL V -->
<!ENTITY Vvdash           "&#x022AA;" ><!--TRIPLE VERTICAL BAR RIGHT TURNSTILE -->
<!ENTITY Wcirc            "&#x00174;" ><!--LATIN CAPITAL LETTER W WITH CIRCUMFLEX -->
<!ENTITY Wedge            "&#x022C0;" ><!--N-ARY LOGICAL AND -->
<!ENTITY Wfr              "&#x1D51A;" ><!--MATHEMATICAL FRAKTUR CAPITAL W -->
<!ENTITY Wopf             "&#x1D54E;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL W -->
<!ENTITY Wscr             "&#x1D4B2;" ><!--MATHEMATICAL SCRIPT CAPITAL W -->
<!ENTITY Xfr              "&#x1D51B;" ><!--MATHEMATICAL FRAKTUR CAPITAL X -->
<!ENTITY Xi               "&#x0039E;" ><!--GREEK CAPITAL LETTER XI -->
<!ENTITY Xopf             "&#x1D54F;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL X -->
<!ENTITY Xscr             "&#x1D4B3;" ><!--MATHEMATICAL SCRIPT CAPITAL X -->
<!ENTITY YAcy             "&#x0042F;" ><!--CYRILLIC CAPITAL LETTER YA -->
<!ENTITY YIcy             "&#x00407;" ><!--CYRILLIC CAPITAL LETTER YI -->
<!ENTITY YUcy             "&#x0042E;" ><!--CYRILLIC CAPITAL LETTER YU -->
<!ENTITY Yacute           "&#x000DD;" ><!--LATIN CAPITAL LETTER Y WITH ACUTE -->
<!ENTITY Ycirc            "&#x00176;" ><!--LATIN CAPITAL LETTER Y WITH CIRCUMFLEX -->
<!ENTITY Ycy              "&#x0042B;" ><!--CYRILLIC CAPITAL LETTER YERU -->
<!ENTITY Yfr              "&#x1D51C;" ><!--MATHEMATICAL FRAKTUR CAPITAL Y -->
<!ENTITY Yopf             "&#x1D550;" ><!--MATHEMATICAL DOUBLE-STRUCK CAPITAL Y -->
<!ENTITY Yscr             "&#x1D4B4;" ><!--MATHEMATICAL SCRIPT CAPITAL Y -->
<!ENTITY Yuml             "&#x00178;" ><!--LATIN CAPITAL LETTER Y WITH DIAERESIS -->
<!ENTITY ZHcy             "&#x00416;" ><!--CYRILLIC CAPITAL LETTER ZHE -->
<!ENTITY Zacute           "&#x00179;" ><!--LATIN CAPITAL LETTER Z WITH ACUTE -->
<!ENTITY Zcaron           "&#x0017D;" ><!--LATIN CAPITAL LETTER Z WITH CARON -->
<!ENTITY Zcy              "&#x00417;" ><!--CYRILLIC CAPITAL LETTER ZE -->
<!ENTITY Zdot             "&#x0017B;" ><!--LATIN CAPITAL LETTER Z WITH DOT ABOVE -->
<!ENTITY ZeroWidthSpace   "&#x0200B;" ><!--ZERO WIDTH SPACE -->
<!ENTITY Zeta             "&#x00396;" ><!--GREEK CAPITAL LETTER ZETA -->
<!ENTITY Zfr              "&#x02128;" ><!--BLACK-LETTER CAPITAL Z -->
<!ENTITY Zopf             "&#x02124;" ><!--DOUBLE-STRUCK CAPITAL Z -->
<!ENTITY Zscr             "&#x1D4B5;" ><!--MATHEMATICAL SCRIPT CAPITAL Z -->
<!ENTITY aacute           "&#x000E1;" ><!--LATIN SMALL LETTER A WITH ACUTE -->
<!ENTITY abreve           "&#x00103;" ><!--LATIN SMALL LETTER A WITH BREVE -->
<!ENTITY ac               "&#x0223E;" ><!--INVERTED LAZY S -->
<!ENTITY acE              "&#x0223E;&#x00333;" ><!--INVERTED LAZY S with double underline -->
<!ENTITY acd              "&#x0223F;" ><!--SINE WAVE -->
<!ENTITY acirc            "&#x000E2;" ><!--LATIN SMALL LETTER A WITH CIRCUMFLEX -->
<!ENTITY acute            "&#x000B4;" ><!--ACUTE ACCENT -->
<!ENTITY acy              "&#x00430;" ><!--CYRILLIC SMALL LETTER A -->
<!ENTITY aelig            "&#x000E6;" ><!--LATIN SMALL LETTER AE -->
<!ENTITY af               "&#x02061;" ><!--FUNCTION APPLICATION -->
<!ENTITY afr              "&#x1D51E;" ><!--MATHEMATICAL FRAKTUR SMALL A -->
<!ENTITY agrave           "&#x000E0;" ><!--LATIN SMALL LETTER A WITH GRAVE -->
<!ENTITY alefsym          "&#x02135;" ><!--ALEF SYMBOL -->
<!ENTITY aleph            "&#x02135;" ><!--ALEF SYMBOL -->
<!ENTITY alpha            "&#x003B1;" ><!--GREEK SMALL LETTER ALPHA -->
<!ENTITY amacr            "&#x00101;" ><!--LATIN SMALL LETTER A WITH MACRON -->
<!ENTITY amalg            "&#x02A3F;" ><!--AMALGAMATION OR COPRODUCT -->
<!ENTITY amp              "&#38;#38;" ><!--AMPERSAND -->
<!ENTITY and              "&#x02227;" ><!--LOGICAL AND -->
<!ENTITY andand           "&#x02A55;" ><!--TWO INTERSECTING LOGICAL AND -->
<!ENTITY andd             "&#x02A5C;" ><!--LOGICAL AND WITH HORIZONTAL DASH -->
<!ENTITY andslope         "&#x02A58;" ><!--SLOPING LARGE AND -->
<!ENTITY andv             "&#x02A5A;" ><!--LOGICAL AND WITH MIDDLE STEM -->
<!ENTITY ang              "&#x02220;" ><!--ANGLE -->
<!ENTITY ange             "&#x029A4;" ><!--ANGLE WITH UNDERBAR -->
<!ENTITY angle            "&#x02220;" ><!--ANGLE -->
<!ENTITY angmsd           "&#x02221;" ><!--MEASURED ANGLE -->
<!ENTITY angmsdaa         "&#x029A8;" ><!--MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND RIGHT -->
<!ENTITY angmsdab         "&#x029A9;" ><!--MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING UP AND LEFT -->
<!ENTITY angmsdac         "&#x029AA;" ><!--MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND RIGHT -->
<!ENTITY angmsdad         "&#x029AB;" ><!--MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING DOWN AND LEFT -->
<!ENTITY angmsdae         "&#x029AC;" ><!--MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND UP -->
<!ENTITY angmsdaf         "&#x029AD;" ><!--MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND UP -->
<!ENTITY angmsdag         "&#x029AE;" ><!--MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING RIGHT AND DOWN -->
<!ENTITY angmsdah         "&#x029AF;" ><!--MEASURED ANGLE WITH OPEN ARM ENDING IN ARROW POINTING LEFT AND DOWN -->
<!ENTITY angrt            "&#x0221F;" ><!--RIGHT ANGLE -->
<!ENTITY angrtvb          "&#x022BE;" ><!--RIGHT ANGLE WITH ARC -->
<!ENTITY angrtvbd         "&#x0299D;" ><!--MEASURED RIGHT ANGLE WITH DOT -->
<!ENTITY angsph           "&#x02222;" ><!--SPHERICAL ANGLE -->
<!ENTITY angst            "&#x000C5;" ><!--LATIN CAPITAL LETTER A WITH RING ABOVE -->
<!ENTITY angzarr          "&#x0237C;" ><!--RIGHT ANGLE WITH DOWNWARDS ZIGZAG ARROW -->
<!ENTITY aogon            "&#x00105;" ><!--LATIN SMALL LETTER A WITH OGONEK -->
<!ENTITY aopf             "&#x1D552;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL A -->
<!ENTITY ap               "&#x02248;" ><!--ALMOST EQUAL TO -->
<!ENTITY apE              "&#x02A70;" ><!--APPROXIMATELY EQUAL OR EQUAL TO -->
<!ENTITY apacir           "&#x02A6F;" ><!--ALMOST EQUAL TO WITH CIRCUMFLEX ACCENT -->
<!ENTITY ape              "&#x0224A;" ><!--ALMOST EQUAL OR EQUAL TO -->
<!ENTITY apid             "&#x0224B;" ><!--TRIPLE TILDE -->
<!ENTITY apos             "&#x00027;" ><!--APOSTROPHE -->
<!ENTITY approx           "&#x02248;" ><!--ALMOST EQUAL TO -->
<!ENTITY approxeq         "&#x0224A;" ><!--ALMOST EQUAL OR EQUAL TO -->
<!ENTITY aring            "&#x000E5;" ><!--LATIN SMALL LETTER A WITH RING ABOVE -->
<!ENTITY ascr             "&#x1D4B6;" ><!--MATHEMATICAL SCRIPT SMALL A -->
<!ENTITY ast              "&#x0002A;" ><!--ASTERISK -->
<!ENTITY asymp            "&#x02248;" ><!--ALMOST EQUAL TO -->
<!ENTITY asympeq          "&#x0224D;" ><!--EQUIVALENT TO -->
<!ENTITY atilde           "&#x000E3;" ><!--LATIN SMALL LETTER A WITH TILDE -->
<!ENTITY auml             "&#x000E4;" ><!--LATIN SMALL LETTER A WITH DIAERESIS -->
<!ENTITY awconint         "&#x02233;" ><!--ANTICLOCKWISE CONTOUR INTEGRAL -->
<!ENTITY awint            "&#x02A11;" ><!--ANTICLOCKWISE INTEGRATION -->
<!ENTITY bNot             "&#x02AED;" ><!--REVERSED DOUBLE STROKE NOT SIGN -->
<!ENTITY backcong         "&#x0224C;" ><!--ALL EQUAL TO -->
<!ENTITY backepsilon      "&#x003F6;" ><!--GREEK REVERSED LUNATE EPSILON SYMBOL -->
<!ENTITY backprime        "&#x02035;" ><!--REVERSED PRIME -->
<!ENTITY backsim          "&#x0223D;" ><!--REVERSED TILDE -->
<!ENTITY backsimeq        "&#x022CD;" ><!--REVERSED TILDE EQUALS -->
<!ENTITY barvee           "&#x022BD;" ><!--NOR -->
<!ENTITY barwed           "&#x02305;" ><!--PROJECTIVE -->
<!ENTITY barwedge         "&#x02305;" ><!--PROJECTIVE -->
<!ENTITY bbrk             "&#x023B5;" ><!--BOTTOM SQUARE BRACKET -->
<!ENTITY bbrktbrk         "&#x023B6;" ><!--BOTTOM SQUARE BRACKET OVER TOP SQUARE BRACKET -->
<!ENTITY bcong            "&#x0224C;" ><!--ALL EQUAL TO -->
<!ENTITY bcy              "&#x00431;" ><!--CYRILLIC SMALL LETTER BE -->
<!ENTITY bdquo            "&#x0201E;" ><!--DOUBLE LOW-9 QUOTATION MARK -->
<!ENTITY becaus           "&#x02235;" ><!--BECAUSE -->
<!ENTITY because          "&#x02235;" ><!--BECAUSE -->
<!ENTITY bemptyv          "&#x029B0;" ><!--REVERSED EMPTY SET -->
<!ENTITY bepsi            "&#x003F6;" ><!--GREEK REVERSED LUNATE EPSILON SYMBOL -->
<!ENTITY bernou           "&#x0212C;" ><!--SCRIPT CAPITAL B -->
<!ENTITY beta             "&#x003B2;" ><!--GREEK SMALL LETTER BETA -->
<!ENTITY beth             "&#x02136;" ><!--BET SYMBOL -->
<!ENTITY between          "&#x0226C;" ><!--BETWEEN -->
<!ENTITY bfr              "&#x1D51F;" ><!--MATHEMATICAL FRAKTUR SMALL B -->
<!ENTITY bigcap           "&#x022C2;" ><!--N-ARY INTERSECTION -->
<!ENTITY bigcirc          "&#x025EF;" ><!--LARGE CIRCLE -->
<!ENTITY bigcup           "&#x022C3;" ><!--N-ARY UNION -->
<!ENTITY bigodot          "&#x02A00;" ><!--N-ARY CIRCLED DOT OPERATOR -->
<!ENTITY bigoplus         "&#x02A01;" ><!--N-ARY CIRCLED PLUS OPERATOR -->
<!ENTITY bigotimes        "&#x02A02;" ><!--N-ARY CIRCLED TIMES OPERATOR -->
<!ENTITY bigsqcup         "&#x02A06;" ><!--N-ARY SQUARE UNION OPERATOR -->
<!ENTITY bigstar          "&#x02605;" ><!--BLACK STAR -->
<!ENTITY bigtriangledown  "&#x025BD;" ><!--WHITE DOWN-POINTING TRIANGLE -->
<!ENTITY bigtriangleup    "&#x025B3;" ><!--WHITE UP-POINTING TRIANGLE -->
<!ENTITY biguplus         "&#x02A04;" ><!--N-ARY UNION OPERATOR WITH PLUS -->
<!ENTITY bigvee           "&#x022C1;" ><!--N-ARY LOGICAL OR -->
<!ENTITY bigwedge         "&#x022C0;" ><!--N-ARY LOGICAL AND -->
<!ENTITY bkarow           "&#x0290D;" ><!--RIGHTWARDS DOUBLE DASH ARROW -->
<!ENTITY blacklozenge     "&#x029EB;" ><!--BLACK LOZENGE -->
<!ENTITY blacksquare      "&#x025AA;" ><!--BLACK SMALL SQUARE -->
<!ENTITY blacktriangle    "&#x025B4;" ><!--BLACK UP-POINTING SMALL TRIANGLE -->
<!ENTITY blacktriangledown "&#x025BE;" ><!--BLACK DOWN-POINTING SMALL TRIANGLE -->
<!ENTITY blacktriangleleft "&#x025C2;" ><!--BLACK LEFT-POINTING SMALL TRIANGLE -->
<!ENTITY blacktriangleright "&#x025B8;" ><!--BLACK RIGHT-POINTING SMALL TRIANGLE -->
<!ENTITY blank            "&#x02423;" ><!--OPEN BOX -->
<!ENTITY blk12            "&#x02592;" ><!--MEDIUM SHADE -->
<!ENTITY blk14            "&#x02591;" ><!--LIGHT SHADE -->
<!ENTITY blk34            "&#x02593;" ><!--DARK SHADE -->
<!ENTITY block            "&#x02588;" ><!--FULL BLOCK -->
<!ENTITY bne              "&#x0003D;&#x020E5;" ><!--EQUALS SIGN with reverse slash -->
<!ENTITY bnequiv          "&#x02261;&#x020E5;" ><!--IDENTICAL TO with reverse slash -->
<!ENTITY bnot             "&#x02310;" ><!--REVERSED NOT SIGN -->
<!ENTITY bopf             "&#x1D553;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL B -->
<!ENTITY bot              "&#x022A5;" ><!--UP TACK -->
<!ENTITY bottom           "&#x022A5;" ><!--UP TACK -->
<!ENTITY bowtie           "&#x022C8;" ><!--BOWTIE -->
<!ENTITY boxDL            "&#x02557;" ><!--BOX DRAWINGS DOUBLE DOWN AND LEFT -->
<!ENTITY boxDR            "&#x02554;" ><!--BOX DRAWINGS DOUBLE DOWN AND RIGHT -->
<!ENTITY boxDl            "&#x02556;" ><!--BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE -->
<!ENTITY boxDr            "&#x02553;" ><!--BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE -->
<!ENTITY boxH             "&#x02550;" ><!--BOX DRAWINGS DOUBLE HORIZONTAL -->
<!ENTITY boxHD            "&#x02566;" ><!--BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL -->
<!ENTITY boxHU            "&#x02569;" ><!--BOX DRAWINGS DOUBLE UP AND HORIZONTAL -->
<!ENTITY boxHd            "&#x02564;" ><!--BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE -->
<!ENTITY boxHu            "&#x02567;" ><!--BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE -->
<!ENTITY boxUL            "&#x0255D;" ><!--BOX DRAWINGS DOUBLE UP AND LEFT -->
<!ENTITY boxUR            "&#x0255A;" ><!--BOX DRAWINGS DOUBLE UP AND RIGHT -->
<!ENTITY boxUl            "&#x0255C;" ><!--BOX DRAWINGS UP DOUBLE AND LEFT SINGLE -->
<!ENTITY boxUr            "&#x02559;" ><!--BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE -->
<!ENTITY boxV             "&#x02551;" ><!--BOX DRAWINGS DOUBLE VERTICAL -->
<!ENTITY boxVH            "&#x0256C;" ><!--BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL -->
<!ENTITY boxVL            "&#x02563;" ><!--BOX DRAWINGS DOUBLE VERTICAL AND LEFT -->
<!ENTITY boxVR            "&#x02560;" ><!--BOX DRAWINGS DOUBLE VERTICAL AND RIGHT -->
<!ENTITY boxVh            "&#x0256B;" ><!--BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE -->
<!ENTITY boxVl            "&#x02562;" ><!--BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE -->
<!ENTITY boxVr            "&#x0255F;" ><!--BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE -->
<!ENTITY boxbox           "&#x029C9;" ><!--TWO JOINED SQUARES -->
<!ENTITY boxdL            "&#x02555;" ><!--BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE -->
<!ENTITY boxdR            "&#x02552;" ><!--BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE -->
<!ENTITY boxdl            "&#x02510;" ><!--BOX DRAWINGS LIGHT DOWN AND LEFT -->
<!ENTITY boxdr            "&#x0250C;" ><!--BOX DRAWINGS LIGHT DOWN AND RIGHT -->
<!ENTITY boxh             "&#x02500;" ><!--BOX DRAWINGS LIGHT HORIZONTAL -->
<!ENTITY boxhD            "&#x02565;" ><!--BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE -->
<!ENTITY boxhU            "&#x02568;" ><!--BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE -->
<!ENTITY boxhd            "&#x0252C;" ><!--BOX DRAWINGS LIGHT DOWN AND HORIZONTAL -->
<!ENTITY boxhu            "&#x02534;" ><!--BOX DRAWINGS LIGHT UP AND HORIZONTAL -->
<!ENTITY boxminus         "&#x0229F;" ><!--SQUARED MINUS -->
<!ENTITY boxplus          "&#x0229E;" ><!--SQUARED PLUS -->
<!ENTITY boxtimes         "&#x022A0;" ><!--SQUARED TIMES -->
<!ENTITY boxuL            "&#x0255B;" ><!--BOX DRAWINGS UP SINGLE AND LEFT DOUBLE -->
<!ENTITY boxuR            "&#x02558;" ><!--BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE -->
<!ENTITY boxul            "&#x02518;" ><!--BOX DRAWINGS LIGHT UP AND LEFT -->
<!ENTITY boxur            "&#x02514;" ><!--BOX DRAWINGS LIGHT UP AND RIGHT -->
<!ENTITY boxv             "&#x02502;" ><!--BOX DRAWINGS LIGHT VERTICAL -->
<!ENTITY boxvH            "&#x0256A;" ><!--BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE -->
<!ENTITY boxvL            "&#x02561;" ><!--BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE -->
<!ENTITY boxvR            "&#x0255E;" ><!--BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE -->
<!ENTITY boxvh            "&#x0253C;" ><!--BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL -->
<!ENTITY boxvl            "&#x02524;" ><!--BOX DRAWINGS LIGHT VERTICAL AND LEFT -->
<!ENTITY boxvr            "&#x0251C;" ><!--BOX DRAWINGS LIGHT VERTICAL AND RIGHT -->
<!ENTITY bprime           "&#x02035;" ><!--REVERSED PRIME -->
<!ENTITY breve            "&#x002D8;" ><!--BREVE -->
<!ENTITY brvbar           "&#x000A6;" ><!--BROKEN BAR -->
<!ENTITY bscr             "&#x1D4B7;" ><!--MATHEMATICAL SCRIPT SMALL B -->
<!ENTITY bsemi            "&#x0204F;" ><!--REVERSED SEMICOLON -->
<!ENTITY bsim             "&#x0223D;" ><!--REVERSED TILDE -->
<!ENTITY bsime            "&#x022CD;" ><!--REVERSED TILDE EQUALS -->
<!ENTITY bsol             "&#x0005C;" ><!--REVERSE SOLIDUS -->
<!ENTITY bsolb            "&#x029C5;" ><!--SQUARED FALLING DIAGONAL SLASH -->
<!ENTITY bsolhsub         "&#x027C8;" ><!--REVERSE SOLIDUS PRECEDING SUBSET -->
<!ENTITY bull             "&#x02022;" ><!--BULLET -->
<!ENTITY bullet           "&#x02022;" ><!--BULLET -->
<!ENTITY bump             "&#x0224E;" ><!--GEOMETRICALLY EQUIVALENT TO -->
<!ENTITY bumpE            "&#x02AAE;" ><!--EQUALS SIGN WITH BUMPY ABOVE -->
<!ENTITY bumpe            "&#x0224F;" ><!--DIFFERENCE BETWEEN -->
<!ENTITY bumpeq           "&#x0224F;" ><!--DIFFERENCE BETWEEN -->
<!ENTITY cacute           "&#x00107;" ><!--LATIN SMALL LETTER C WITH ACUTE -->
<!ENTITY cap              "&#x02229;" ><!--INTERSECTION -->
<!ENTITY capand           "&#x02A44;" ><!--INTERSECTION WITH LOGICAL AND -->
<!ENTITY capbrcup         "&#x02A49;" ><!--INTERSECTION ABOVE BAR ABOVE UNION -->
<!ENTITY capcap           "&#x02A4B;" ><!--INTERSECTION BESIDE AND JOINED WITH INTERSECTION -->
<!ENTITY capcup           "&#x02A47;" ><!--INTERSECTION ABOVE UNION -->
<!ENTITY capdot           "&#x02A40;" ><!--INTERSECTION WITH DOT -->
<!ENTITY caps             "&#x02229;&#x0FE00;" ><!--INTERSECTION with serifs -->
<!ENTITY caret            "&#x02041;" ><!--CARET INSERTION POINT -->
<!ENTITY caron            "&#x002C7;" ><!--CARON -->
<!ENTITY ccaps            "&#x02A4D;" ><!--CLOSED INTERSECTION WITH SERIFS -->
<!ENTITY ccaron           "&#x0010D;" ><!--LATIN SMALL LETTER C WITH CARON -->
<!ENTITY ccedil           "&#x000E7;" ><!--LATIN SMALL LETTER C WITH CEDILLA -->
<!ENTITY ccirc            "&#x00109;" ><!--LATIN SMALL LETTER C WITH CIRCUMFLEX -->
<!ENTITY ccups            "&#x02A4C;" ><!--CLOSED UNION WITH SERIFS -->
<!ENTITY ccupssm          "&#x02A50;" ><!--CLOSED UNION WITH SERIFS AND SMASH PRODUCT -->
<!ENTITY cdot             "&#x0010B;" ><!--LATIN SMALL LETTER C WITH DOT ABOVE -->
<!ENTITY cedil            "&#x000B8;" ><!--CEDILLA -->
<!ENTITY cemptyv          "&#x029B2;" ><!--EMPTY SET WITH SMALL CIRCLE ABOVE -->
<!ENTITY cent             "&#x000A2;" ><!--CENT SIGN -->
<!ENTITY centerdot        "&#x000B7;" ><!--MIDDLE DOT -->
<!ENTITY cfr              "&#x1D520;" ><!--MATHEMATICAL FRAKTUR SMALL C -->
<!ENTITY chcy             "&#x00447;" ><!--CYRILLIC SMALL LETTER CHE -->
<!ENTITY check            "&#x02713;" ><!--CHECK MARK -->
<!ENTITY checkmark        "&#x02713;" ><!--CHECK MARK -->
<!ENTITY chi              "&#x003C7;" ><!--GREEK SMALL LETTER CHI -->
<!ENTITY cir              "&#x025CB;" ><!--WHITE CIRCLE -->
<!ENTITY cirE             "&#x029C3;" ><!--CIRCLE WITH TWO HORIZONTAL STROKES TO THE RIGHT -->
<!ENTITY circ             "&#x002C6;" ><!--MODIFIER LETTER CIRCUMFLEX ACCENT -->
<!ENTITY circeq           "&#x02257;" ><!--RING EQUAL TO -->
<!ENTITY circlearrowleft  "&#x021BA;" ><!--ANTICLOCKWISE OPEN CIRCLE ARROW -->
<!ENTITY circlearrowright "&#x021BB;" ><!--CLOCKWISE OPEN CIRCLE ARROW -->
<!ENTITY circledR         "&#x000AE;" ><!--REGISTERED SIGN -->
<!ENTITY circledS         "&#x024C8;" ><!--CIRCLED LATIN CAPITAL LETTER S -->
<!ENTITY circledast       "&#x0229B;" ><!--CIRCLED ASTERISK OPERATOR -->
<!ENTITY circledcirc      "&#x0229A;" ><!--CIRCLED RING OPERATOR -->
<!ENTITY circleddash      "&#x0229D;" ><!--CIRCLED DASH -->
<!ENTITY cire             "&#x02257;" ><!--RING EQUAL TO -->
<!ENTITY cirfnint         "&#x02A10;" ><!--CIRCULATION FUNCTION -->
<!ENTITY cirmid           "&#x02AEF;" ><!--VERTICAL LINE WITH CIRCLE ABOVE -->
<!ENTITY cirscir          "&#x029C2;" ><!--CIRCLE WITH SMALL CIRCLE TO THE RIGHT -->
<!ENTITY clubs            "&#x02663;" ><!--BLACK CLUB SUIT -->
<!ENTITY clubsuit         "&#x02663;" ><!--BLACK CLUB SUIT -->
<!ENTITY colon            "&#x0003A;" ><!--COLON -->
<!ENTITY colone           "&#x02254;" ><!--COLON EQUALS -->
<!ENTITY coloneq          "&#x02254;" ><!--COLON EQUALS -->
<!ENTITY comma            "&#x0002C;" ><!--COMMA -->
<!ENTITY commat           "&#x00040;" ><!--COMMERCIAL AT -->
<!ENTITY comp             "&#x02201;" ><!--COMPLEMENT -->
<!ENTITY compfn           "&#x02218;" ><!--RING OPERATOR -->
<!ENTITY complement       "&#x02201;" ><!--COMPLEMENT -->
<!ENTITY complexes        "&#x02102;" ><!--DOUBLE-STRUCK CAPITAL C -->
<!ENTITY cong             "&#x02245;" ><!--APPROXIMATELY EQUAL TO -->
<!ENTITY congdot          "&#x02A6D;" ><!--CONGRUENT WITH DOT ABOVE -->
<!ENTITY conint           "&#x0222E;" ><!--CONTOUR INTEGRAL -->
<!ENTITY copf             "&#x1D554;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL C -->
<!ENTITY coprod           "&#x02210;" ><!--N-ARY COPRODUCT -->
<!ENTITY copy             "&#x000A9;" ><!--COPYRIGHT SIGN -->
<!ENTITY copysr           "&#x02117;" ><!--SOUND RECORDING COPYRIGHT -->
<!ENTITY crarr            "&#x021B5;" ><!--DOWNWARDS ARROW WITH CORNER LEFTWARDS -->
<!ENTITY cross            "&#x02717;" ><!--BALLOT X -->
<!ENTITY cscr             "&#x1D4B8;" ><!--MATHEMATICAL SCRIPT SMALL C -->
<!ENTITY csub             "&#x02ACF;" ><!--CLOSED SUBSET -->
<!ENTITY csube            "&#x02AD1;" ><!--CLOSED SUBSET OR EQUAL TO -->
<!ENTITY csup             "&#x02AD0;" ><!--CLOSED SUPERSET -->
<!ENTITY csupe            "&#x02AD2;" ><!--CLOSED SUPERSET OR EQUAL TO -->
<!ENTITY ctdot            "&#x022EF;" ><!--MIDLINE HORIZONTAL ELLIPSIS -->
<!ENTITY cudarrl          "&#x02938;" ><!--RIGHT-SIDE ARC CLOCKWISE ARROW -->
<!ENTITY cudarrr          "&#x02935;" ><!--ARROW POINTING RIGHTWARDS THEN CURVING DOWNWARDS -->
<!ENTITY cuepr            "&#x022DE;" ><!--EQUAL TO OR PRECEDES -->
<!ENTITY cuesc            "&#x022DF;" ><!--EQUAL TO OR SUCCEEDS -->
<!ENTITY cularr           "&#x021B6;" ><!--ANTICLOCKWISE TOP SEMICIRCLE ARROW -->
<!ENTITY cularrp          "&#x0293D;" ><!--TOP ARC ANTICLOCKWISE ARROW WITH PLUS -->
<!ENTITY cup              "&#x0222A;" ><!--UNION -->
<!ENTITY cupbrcap         "&#x02A48;" ><!--UNION ABOVE BAR ABOVE INTERSECTION -->
<!ENTITY cupcap           "&#x02A46;" ><!--UNION ABOVE INTERSECTION -->
<!ENTITY cupcup           "&#x02A4A;" ><!--UNION BESIDE AND JOINED WITH UNION -->
<!ENTITY cupdot           "&#x0228D;" ><!--MULTISET MULTIPLICATION -->
<!ENTITY cupor            "&#x02A45;" ><!--UNION WITH LOGICAL OR -->
<!ENTITY cups             "&#x0222A;&#x0FE00;" ><!--UNION with serifs -->
<!ENTITY curarr           "&#x021B7;" ><!--CLOCKWISE TOP SEMICIRCLE ARROW -->
<!ENTITY curarrm          "&#x0293C;" ><!--TOP ARC CLOCKWISE ARROW WITH MINUS -->
<!ENTITY curlyeqprec      "&#x022DE;" ><!--EQUAL TO OR PRECEDES -->
<!ENTITY curlyeqsucc      "&#x022DF;" ><!--EQUAL TO OR SUCCEEDS -->
<!ENTITY curlyvee         "&#x022CE;" ><!--CURLY LOGICAL OR -->
<!ENTITY curlywedge       "&#x022CF;" ><!--CURLY LOGICAL AND -->
<!ENTITY curren           "&#x000A4;" ><!--CURRENCY SIGN -->
<!ENTITY curvearrowleft   "&#x021B6;" ><!--ANTICLOCKWISE TOP SEMICIRCLE ARROW -->
<!ENTITY curvearrowright  "&#x021B7;" ><!--CLOCKWISE TOP SEMICIRCLE ARROW -->
<!ENTITY cuvee            "&#x022CE;" ><!--CURLY LOGICAL OR -->
<!ENTITY cuwed            "&#x022CF;" ><!--CURLY LOGICAL AND -->
<!ENTITY cwconint         "&#x02232;" ><!--CLOCKWISE CONTOUR INTEGRAL -->
<!ENTITY cwint            "&#x02231;" ><!--CLOCKWISE INTEGRAL -->
<!ENTITY cylcty           "&#x0232D;" ><!--CYLINDRICITY -->
<!ENTITY dArr             "&#x021D3;" ><!--DOWNWARDS DOUBLE ARROW -->
<!ENTITY dHar             "&#x02965;" ><!--DOWNWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT -->
<!ENTITY dagger           "&#x02020;" ><!--DAGGER -->
<!ENTITY daleth           "&#x02138;" ><!--DALET SYMBOL -->
<!ENTITY darr             "&#x02193;" ><!--DOWNWARDS ARROW -->
<!ENTITY dash             "&#x02010;" ><!--HYPHEN -->
<!ENTITY dashv            "&#x022A3;" ><!--LEFT TACK -->
<!ENTITY dbkarow          "&#x0290F;" ><!--RIGHTWARDS TRIPLE DASH ARROW -->
<!ENTITY dblac            "&#x002DD;" ><!--DOUBLE ACUTE ACCENT -->
<!ENTITY dcaron           "&#x0010F;" ><!--LATIN SMALL LETTER D WITH CARON -->
<!ENTITY dcy              "&#x00434;" ><!--CYRILLIC SMALL LETTER DE -->
<!ENTITY dd               "&#x02146;" ><!--DOUBLE-STRUCK ITALIC SMALL D -->
<!ENTITY ddagger          "&#x02021;" ><!--DOUBLE DAGGER -->
<!ENTITY ddarr            "&#x021CA;" ><!--DOWNWARDS PAIRED ARROWS -->
<!ENTITY ddotseq          "&#x02A77;" ><!--EQUALS SIGN WITH TWO DOTS ABOVE AND TWO DOTS BELOW -->
<!ENTITY deg              "&#x000B0;" ><!--DEGREE SIGN -->
<!ENTITY delta            "&#x003B4;" ><!--GREEK SMALL LETTER DELTA -->
<!ENTITY demptyv          "&#x029B1;" ><!--EMPTY SET WITH OVERBAR -->
<!ENTITY dfisht           "&#x0297F;" ><!--DOWN FISH TAIL -->
<!ENTITY dfr              "&#x1D521;" ><!--MATHEMATICAL FRAKTUR SMALL D -->
<!ENTITY dharl            "&#x021C3;" ><!--DOWNWARDS HARPOON WITH BARB LEFTWARDS -->
<!ENTITY dharr            "&#x021C2;" ><!--DOWNWARDS HARPOON WITH BARB RIGHTWARDS -->
<!ENTITY diam             "&#x022C4;" ><!--DIAMOND OPERATOR -->
<!ENTITY diamond          "&#x022C4;" ><!--DIAMOND OPERATOR -->
<!ENTITY diamondsuit      "&#x02666;" ><!--BLACK DIAMOND SUIT -->
<!ENTITY diams            "&#x02666;" ><!--BLACK DIAMOND SUIT -->
<!ENTITY die              "&#x000A8;" ><!--DIAERESIS -->
<!ENTITY digamma          "&#x003DD;" ><!--GREEK SMALL LETTER DIGAMMA -->
<!ENTITY disin            "&#x022F2;" ><!--ELEMENT OF WITH LONG HORIZONTAL STROKE -->
<!ENTITY div              "&#x000F7;" ><!--DIVISION SIGN -->
<!ENTITY divide           "&#x000F7;" ><!--DIVISION SIGN -->
<!ENTITY divideontimes    "&#x022C7;" ><!--DIVISION TIMES -->
<!ENTITY divonx           "&#x022C7;" ><!--DIVISION TIMES -->
<!ENTITY djcy             "&#x00452;" ><!--CYRILLIC SMALL LETTER DJE -->
<!ENTITY dlcorn           "&#x0231E;" ><!--BOTTOM LEFT CORNER -->
<!ENTITY dlcrop           "&#x0230D;" ><!--BOTTOM LEFT CROP -->
<!ENTITY dollar           "&#x00024;" ><!--DOLLAR SIGN -->
<!ENTITY dopf             "&#x1D555;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL D -->
<!ENTITY dot              "&#x002D9;" ><!--DOT ABOVE -->
<!ENTITY doteq            "&#x02250;" ><!--APPROACHES THE LIMIT -->
<!ENTITY doteqdot         "&#x02251;" ><!--GEOMETRICALLY EQUAL TO -->
<!ENTITY dotminus         "&#x02238;" ><!--DOT MINUS -->
<!ENTITY dotplus          "&#x02214;" ><!--DOT PLUS -->
<!ENTITY dotsquare        "&#x022A1;" ><!--SQUARED DOT OPERATOR -->
<!ENTITY doublebarwedge   "&#x02306;" ><!--PERSPECTIVE -->
<!ENTITY downarrow        "&#x02193;" ><!--DOWNWARDS ARROW -->
<!ENTITY downdownarrows   "&#x021CA;" ><!--DOWNWARDS PAIRED ARROWS -->
<!ENTITY downharpoonleft  "&#x021C3;" ><!--DOWNWARDS HARPOON WITH BARB LEFTWARDS -->
<!ENTITY downharpoonright "&#x021C2;" ><!--DOWNWARDS HARPOON WITH BARB RIGHTWARDS -->
<!ENTITY drbkarow         "&#x02910;" ><!--RIGHTWARDS TWO-HEADED TRIPLE DASH ARROW -->
<!ENTITY drcorn           "&#x0231F;" ><!--BOTTOM RIGHT CORNER -->
<!ENTITY drcrop           "&#x0230C;" ><!--BOTTOM RIGHT CROP -->
<!ENTITY dscr             "&#x1D4B9;" ><!--MATHEMATICAL SCRIPT SMALL D -->
<!ENTITY dscy             "&#x00455;" ><!--CYRILLIC SMALL LETTER DZE -->
<!ENTITY dsol             "&#x029F6;" ><!--SOLIDUS WITH OVERBAR -->
<!ENTITY dstrok           "&#x00111;" ><!--LATIN SMALL LETTER D WITH STROKE -->
<!ENTITY dtdot            "&#x022F1;" ><!--DOWN RIGHT DIAGONAL ELLIPSIS -->
<!ENTITY dtri             "&#x025BF;" ><!--WHITE DOWN-POINTING SMALL TRIANGLE -->
<!ENTITY dtrif            "&#x025BE;" ><!--BLACK DOWN-POINTING SMALL TRIANGLE -->
<!ENTITY duarr            "&#x021F5;" ><!--DOWNWARDS ARROW LEFTWARDS OF UPWARDS ARROW -->
<!ENTITY duhar            "&#x0296F;" ><!--DOWNWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT -->
<!ENTITY dwangle          "&#x029A6;" ><!--OBLIQUE ANGLE OPENING UP -->
<!ENTITY dzcy             "&#x0045F;" ><!--CYRILLIC SMALL LETTER DZHE -->
<!ENTITY dzigrarr         "&#x027FF;" ><!--LONG RIGHTWARDS SQUIGGLE ARROW -->
<!ENTITY eDDot            "&#x02A77;" ><!--EQUALS SIGN WITH TWO DOTS ABOVE AND TWO DOTS BELOW -->
<!ENTITY eDot             "&#x02251;" ><!--GEOMETRICALLY EQUAL TO -->
<!ENTITY eacute           "&#x000E9;" ><!--LATIN SMALL LETTER E WITH ACUTE -->
<!ENTITY easter           "&#x02A6E;" ><!--EQUALS WITH ASTERISK -->
<!ENTITY ecaron           "&#x0011B;" ><!--LATIN SMALL LETTER E WITH CARON -->
<!ENTITY ecir             "&#x02256;" ><!--RING IN EQUAL TO -->
<!ENTITY ecirc            "&#x000EA;" ><!--LATIN SMALL LETTER E WITH CIRCUMFLEX -->
<!ENTITY ecolon           "&#x02255;" ><!--EQUALS COLON -->
<!ENTITY ecy              "&#x0044D;" ><!--CYRILLIC SMALL LETTER E -->
<!ENTITY edot             "&#x00117;" ><!--LATIN SMALL LETTER E WITH DOT ABOVE -->
<!ENTITY ee               "&#x02147;" ><!--DOUBLE-STRUCK ITALIC SMALL E -->
<!ENTITY efDot            "&#x02252;" ><!--APPROXIMATELY EQUAL TO OR THE IMAGE OF -->
<!ENTITY efr              "&#x1D522;" ><!--MATHEMATICAL FRAKTUR SMALL E -->
<!ENTITY eg               "&#x02A9A;" ><!--DOUBLE-LINE EQUAL TO OR GREATER-THAN -->
<!ENTITY egrave           "&#x000E8;" ><!--LATIN SMALL LETTER E WITH GRAVE -->
<!ENTITY egs              "&#x02A96;" ><!--SLANTED EQUAL TO OR GREATER-THAN -->
<!ENTITY egsdot           "&#x02A98;" ><!--SLANTED EQUAL TO OR GREATER-THAN WITH DOT INSIDE -->
<!ENTITY el               "&#x02A99;" ><!--DOUBLE-LINE EQUAL TO OR LESS-THAN -->
<!ENTITY elinters         "&#x023E7;" ><!--ELECTRICAL INTERSECTION -->
<!ENTITY ell              "&#x02113;" ><!--SCRIPT SMALL L -->
<!ENTITY els              "&#x02A95;" ><!--SLANTED EQUAL TO OR LESS-THAN -->
<!ENTITY elsdot           "&#x02A97;" ><!--SLANTED EQUAL TO OR LESS-THAN WITH DOT INSIDE -->
<!ENTITY emacr            "&#x00113;" ><!--LATIN SMALL LETTER E WITH MACRON -->
<!ENTITY empty            "&#x02205;" ><!--EMPTY SET -->
<!ENTITY emptyset         "&#x02205;" ><!--EMPTY SET -->
<!ENTITY emptyv           "&#x02205;" ><!--EMPTY SET -->
<!ENTITY emsp             "&#x02003;" ><!--EM SPACE -->
<!ENTITY emsp13           "&#x02004;" ><!--THREE-PER-EM SPACE -->
<!ENTITY emsp14           "&#x02005;" ><!--FOUR-PER-EM SPACE -->
<!ENTITY eng              "&#x0014B;" ><!--LATIN SMALL LETTER ENG -->
<!ENTITY ensp             "&#x02002;" ><!--EN SPACE -->
<!ENTITY eogon            "&#x00119;" ><!--LATIN SMALL LETTER E WITH OGONEK -->
<!ENTITY eopf             "&#x1D556;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL E -->
<!ENTITY epar             "&#x022D5;" ><!--EQUAL AND PARALLEL TO -->
<!ENTITY eparsl           "&#x029E3;" ><!--EQUALS SIGN AND SLANTED PARALLEL -->
<!ENTITY eplus            "&#x02A71;" ><!--EQUALS SIGN ABOVE PLUS SIGN -->
<!ENTITY epsi             "&#x003B5;" ><!--GREEK SMALL LETTER EPSILON -->
<!ENTITY epsilon          "&#x003B5;" ><!--GREEK SMALL LETTER EPSILON -->
<!ENTITY epsiv            "&#x003F5;" ><!--GREEK LUNATE EPSILON SYMBOL -->
<!ENTITY eqcirc           "&#x02256;" ><!--RING IN EQUAL TO -->
<!ENTITY eqcolon          "&#x02255;" ><!--EQUALS COLON -->
<!ENTITY eqsim            "&#x02242;" ><!--MINUS TILDE -->
<!ENTITY eqslantgtr       "&#x02A96;" ><!--SLANTED EQUAL TO OR GREATER-THAN -->
<!ENTITY eqslantless      "&#x02A95;" ><!--SLANTED EQUAL TO OR LESS-THAN -->
<!ENTITY equals           "&#x0003D;" ><!--EQUALS SIGN -->
<!ENTITY equest           "&#x0225F;" ><!--QUESTIONED EQUAL TO -->
<!ENTITY equiv            "&#x02261;" ><!--IDENTICAL TO -->
<!ENTITY equivDD          "&#x02A78;" ><!--EQUIVALENT WITH FOUR DOTS ABOVE -->
<!ENTITY eqvparsl         "&#x029E5;" ><!--IDENTICAL TO AND SLANTED PARALLEL -->
<!ENTITY erDot            "&#x02253;" ><!--IMAGE OF OR APPROXIMATELY EQUAL TO -->
<!ENTITY erarr            "&#x02971;" ><!--EQUALS SIGN ABOVE RIGHTWARDS ARROW -->
<!ENTITY escr             "&#x0212F;" ><!--SCRIPT SMALL E -->
<!ENTITY esdot            "&#x02250;" ><!--APPROACHES THE LIMIT -->
<!ENTITY esim             "&#x02242;" ><!--MINUS TILDE -->
<!ENTITY eta              "&#x003B7;" ><!--GREEK SMALL LETTER ETA -->
<!ENTITY eth              "&#x000F0;" ><!--LATIN SMALL LETTER ETH -->
<!ENTITY euml             "&#x000EB;" ><!--LATIN SMALL LETTER E WITH DIAERESIS -->
<!ENTITY euro             "&#x020AC;" ><!--EURO SIGN -->
<!ENTITY excl             "&#x00021;" ><!--EXCLAMATION MARK -->
<!ENTITY exist            "&#x02203;" ><!--THERE EXISTS -->
<!ENTITY expectation      "&#x02130;" ><!--SCRIPT CAPITAL E -->
<!ENTITY exponentiale     "&#x02147;" ><!--DOUBLE-STRUCK ITALIC SMALL E -->
<!ENTITY fallingdotseq    "&#x02252;" ><!--APPROXIMATELY EQUAL TO OR THE IMAGE OF -->
<!ENTITY fcy              "&#x00444;" ><!--CYRILLIC SMALL LETTER EF -->
<!ENTITY female           "&#x02640;" ><!--FEMALE SIGN -->
<!ENTITY ffilig           "&#x0FB03;" ><!--LATIN SMALL LIGATURE FFI -->
<!ENTITY fflig            "&#x0FB00;" ><!--LATIN SMALL LIGATURE FF -->
<!ENTITY ffllig           "&#x0FB04;" ><!--LATIN SMALL LIGATURE FFL -->
<!ENTITY ffr              "&#x1D523;" ><!--MATHEMATICAL FRAKTUR SMALL F -->
<!ENTITY filig            "&#x0FB01;" ><!--LATIN SMALL LIGATURE FI -->
<!ENTITY fjlig            "&#x00066;&#x0006A;" ><!--fj ligature -->
<!ENTITY flat             "&#x0266D;" ><!--MUSIC FLAT SIGN -->
<!ENTITY fllig            "&#x0FB02;" ><!--LATIN SMALL LIGATURE FL -->
<!ENTITY fltns            "&#x025B1;" ><!--WHITE PARALLELOGRAM -->
<!ENTITY fnof             "&#x00192;" ><!--LATIN SMALL LETTER F WITH HOOK -->
<!ENTITY fopf             "&#x1D557;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL F -->
<!ENTITY forall           "&#x02200;" ><!--FOR ALL -->
<!ENTITY fork             "&#x022D4;" ><!--PITCHFORK -->
<!ENTITY forkv            "&#x02AD9;" ><!--ELEMENT OF OPENING DOWNWARDS -->
<!ENTITY fpartint         "&#x02A0D;" ><!--FINITE PART INTEGRAL -->
<!ENTITY frac12           "&#x000BD;" ><!--VULGAR FRACTION ONE HALF -->
<!ENTITY frac13           "&#x02153;" ><!--VULGAR FRACTION ONE THIRD -->
<!ENTITY frac14           "&#x000BC;" ><!--VULGAR FRACTION ONE QUARTER -->
<!ENTITY frac15           "&#x02155;" ><!--VULGAR FRACTION ONE FIFTH -->
<!ENTITY frac16           "&#x02159;" ><!--VULGAR FRACTION ONE SIXTH -->
<!ENTITY frac18           "&#x0215B;" ><!--VULGAR FRACTION ONE EIGHTH -->
<!ENTITY frac23           "&#x02154;" ><!--VULGAR FRACTION TWO THIRDS -->
<!ENTITY frac25           "&#x02156;" ><!--VULGAR FRACTION TWO FIFTHS -->
<!ENTITY frac34           "&#x000BE;" ><!--VULGAR FRACTION THREE QUARTERS -->
<!ENTITY frac35           "&#x02157;" ><!--VULGAR FRACTION THREE FIFTHS -->
<!ENTITY frac38           "&#x0215C;" ><!--VULGAR FRACTION THREE EIGHTHS -->
<!ENTITY frac45           "&#x02158;" ><!--VULGAR FRACTION FOUR FIFTHS -->
<!ENTITY frac56           "&#x0215A;" ><!--VULGAR FRACTION FIVE SIXTHS -->
<!ENTITY frac58           "&#x0215D;" ><!--VULGAR FRACTION FIVE EIGHTHS -->
<!ENTITY frac78           "&#x0215E;" ><!--VULGAR FRACTION SEVEN EIGHTHS -->
<!ENTITY frasl            "&#x02044;" ><!--FRACTION SLASH -->
<!ENTITY frown            "&#x02322;" ><!--FROWN -->
<!ENTITY fscr             "&#x1D4BB;" ><!--MATHEMATICAL SCRIPT SMALL F -->
<!ENTITY gE               "&#x02267;" ><!--GREATER-THAN OVER EQUAL TO -->
<!ENTITY gEl              "&#x02A8C;" ><!--GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN -->
<!ENTITY gacute           "&#x001F5;" ><!--LATIN SMALL LETTER G WITH ACUTE -->
<!ENTITY gamma            "&#x003B3;" ><!--GREEK SMALL LETTER GAMMA -->
<!ENTITY gammad           "&#x003DD;" ><!--GREEK SMALL LETTER DIGAMMA -->
<!ENTITY gap              "&#x02A86;" ><!--GREATER-THAN OR APPROXIMATE -->
<!ENTITY gbreve           "&#x0011F;" ><!--LATIN SMALL LETTER G WITH BREVE -->
<!ENTITY gcirc            "&#x0011D;" ><!--LATIN SMALL LETTER G WITH CIRCUMFLEX -->
<!ENTITY gcy              "&#x00433;" ><!--CYRILLIC SMALL LETTER GHE -->
<!ENTITY gdot             "&#x00121;" ><!--LATIN SMALL LETTER G WITH DOT ABOVE -->
<!ENTITY ge               "&#x02265;" ><!--GREATER-THAN OR EQUAL TO -->
<!ENTITY gel              "&#x022DB;" ><!--GREATER-THAN EQUAL TO OR LESS-THAN -->
<!ENTITY geq              "&#x02265;" ><!--GREATER-THAN OR EQUAL TO -->
<!ENTITY geqq             "&#x02267;" ><!--GREATER-THAN OVER EQUAL TO -->
<!ENTITY geqslant         "&#x02A7E;" ><!--GREATER-THAN OR SLANTED EQUAL TO -->
<!ENTITY ges              "&#x02A7E;" ><!--GREATER-THAN OR SLANTED EQUAL TO -->
<!ENTITY gescc            "&#x02AA9;" ><!--GREATER-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL -->
<!ENTITY gesdot           "&#x02A80;" ><!--GREATER-THAN OR SLANTED EQUAL TO WITH DOT INSIDE -->
<!ENTITY gesdoto          "&#x02A82;" ><!--GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE -->
<!ENTITY gesdotol         "&#x02A84;" ><!--GREATER-THAN OR SLANTED EQUAL TO WITH DOT ABOVE LEFT -->
<!ENTITY gesl             "&#x022DB;&#x0FE00;" ><!--GREATER-THAN slanted EQUAL TO OR LESS-THAN -->
<!ENTITY gesles           "&#x02A94;" ><!--GREATER-THAN ABOVE SLANTED EQUAL ABOVE LESS-THAN ABOVE SLANTED EQUAL -->
<!ENTITY gfr              "&#x1D524;" ><!--MATHEMATICAL FRAKTUR SMALL G -->
<!ENTITY gg               "&#x0226B;" ><!--MUCH GREATER-THAN -->
<!ENTITY ggg              "&#x022D9;" ><!--VERY MUCH GREATER-THAN -->
<!ENTITY gimel            "&#x02137;" ><!--GIMEL SYMBOL -->
<!ENTITY gjcy             "&#x00453;" ><!--CYRILLIC SMALL LETTER GJE -->
<!ENTITY gl               "&#x02277;" ><!--GREATER-THAN OR LESS-THAN -->
<!ENTITY glE              "&#x02A92;" ><!--GREATER-THAN ABOVE LESS-THAN ABOVE DOUBLE-LINE EQUAL -->
<!ENTITY gla              "&#x02AA5;" ><!--GREATER-THAN BESIDE LESS-THAN -->
<!ENTITY glj              "&#x02AA4;" ><!--GREATER-THAN OVERLAPPING LESS-THAN -->
<!ENTITY gnE              "&#x02269;" ><!--GREATER-THAN BUT NOT EQUAL TO -->
<!ENTITY gnap             "&#x02A8A;" ><!--GREATER-THAN AND NOT APPROXIMATE -->
<!ENTITY gnapprox         "&#x02A8A;" ><!--GREATER-THAN AND NOT APPROXIMATE -->
<!ENTITY gne              "&#x02A88;" ><!--GREATER-THAN AND SINGLE-LINE NOT EQUAL TO -->
<!ENTITY gneq             "&#x02A88;" ><!--GREATER-THAN AND SINGLE-LINE NOT EQUAL TO -->
<!ENTITY gneqq            "&#x02269;" ><!--GREATER-THAN BUT NOT EQUAL TO -->
<!ENTITY gnsim            "&#x022E7;" ><!--GREATER-THAN BUT NOT EQUIVALENT TO -->
<!ENTITY gopf             "&#x1D558;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL G -->
<!ENTITY grave            "&#x00060;" ><!--GRAVE ACCENT -->
<!ENTITY gscr             "&#x0210A;" ><!--SCRIPT SMALL G -->
<!ENTITY gsim             "&#x02273;" ><!--GREATER-THAN OR EQUIVALENT TO -->
<!ENTITY gsime            "&#x02A8E;" ><!--GREATER-THAN ABOVE SIMILAR OR EQUAL -->
<!ENTITY gsiml            "&#x02A90;" ><!--GREATER-THAN ABOVE SIMILAR ABOVE LESS-THAN -->
<!ENTITY gt               "&#x0003E;" ><!--GREATER-THAN SIGN -->
<!ENTITY gtcc             "&#x02AA7;" ><!--GREATER-THAN CLOSED BY CURVE -->
<!ENTITY gtcir            "&#x02A7A;" ><!--GREATER-THAN WITH CIRCLE INSIDE -->
<!ENTITY gtdot            "&#x022D7;" ><!--GREATER-THAN WITH DOT -->
<!ENTITY gtlPar           "&#x02995;" ><!--DOUBLE LEFT ARC GREATER-THAN BRACKET -->
<!ENTITY gtquest          "&#x02A7C;" ><!--GREATER-THAN WITH QUESTION MARK ABOVE -->
<!ENTITY gtrapprox        "&#x02A86;" ><!--GREATER-THAN OR APPROXIMATE -->
<!ENTITY gtrarr           "&#x02978;" ><!--GREATER-THAN ABOVE RIGHTWARDS ARROW -->
<!ENTITY gtrdot           "&#x022D7;" ><!--GREATER-THAN WITH DOT -->
<!ENTITY gtreqless        "&#x022DB;" ><!--GREATER-THAN EQUAL TO OR LESS-THAN -->
<!ENTITY gtreqqless       "&#x02A8C;" ><!--GREATER-THAN ABOVE DOUBLE-LINE EQUAL ABOVE LESS-THAN -->
<!ENTITY gtrless          "&#x02277;" ><!--GREATER-THAN OR LESS-THAN -->
<!ENTITY gtrsim           "&#x02273;" ><!--GREATER-THAN OR EQUIVALENT TO -->
<!ENTITY gvertneqq        "&#x02269;&#x0FE00;" ><!--GREATER-THAN BUT NOT EQUAL TO - with vertical stroke -->
<!ENTITY gvnE             "&#x02269;&#x0FE00;" ><!--GREATER-THAN BUT NOT EQUAL TO - with vertical stroke -->
<!ENTITY hArr             "&#x021D4;" ><!--LEFT RIGHT DOUBLE ARROW -->
<!ENTITY hairsp           "&#x0200A;" ><!--HAIR SPACE -->
<!ENTITY half             "&#x000BD;" ><!--VULGAR FRACTION ONE HALF -->
<!ENTITY hamilt           "&#x0210B;" ><!--SCRIPT CAPITAL H -->
<!ENTITY hardcy           "&#x0044A;" ><!--CYRILLIC SMALL LETTER HARD SIGN -->
<!ENTITY harr             "&#x02194;" ><!--LEFT RIGHT ARROW -->
<!ENTITY harrcir          "&#x02948;" ><!--LEFT RIGHT ARROW THROUGH SMALL CIRCLE -->
<!ENTITY harrw            "&#x021AD;" ><!--LEFT RIGHT WAVE ARROW -->
<!ENTITY hbar             "&#x0210F;" ><!--PLANCK CONSTANT OVER TWO PI -->
<!ENTITY hcirc            "&#x00125;" ><!--LATIN SMALL LETTER H WITH CIRCUMFLEX -->
<!ENTITY hearts           "&#x02665;" ><!--BLACK HEART SUIT -->
<!ENTITY heartsuit        "&#x02665;" ><!--BLACK HEART SUIT -->
<!ENTITY hellip           "&#x02026;" ><!--HORIZONTAL ELLIPSIS -->
<!ENTITY hercon           "&#x022B9;" ><!--HERMITIAN CONJUGATE MATRIX -->
<!ENTITY hfr              "&#x1D525;" ><!--MATHEMATICAL FRAKTUR SMALL H -->
<!ENTITY hksearow         "&#x02925;" ><!--SOUTH EAST ARROW WITH HOOK -->
<!ENTITY hkswarow         "&#x02926;" ><!--SOUTH WEST ARROW WITH HOOK -->
<!ENTITY hoarr            "&#x021FF;" ><!--LEFT RIGHT OPEN-HEADED ARROW -->
<!ENTITY homtht           "&#x0223B;" ><!--HOMOTHETIC -->
<!ENTITY hookleftarrow    "&#x021A9;" ><!--LEFTWARDS ARROW WITH HOOK -->
<!ENTITY hookrightarrow   "&#x021AA;" ><!--RIGHTWARDS ARROW WITH HOOK -->
<!ENTITY hopf             "&#x1D559;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL H -->
<!ENTITY horbar           "&#x02015;" ><!--HORIZONTAL BAR -->
<!ENTITY hscr             "&#x1D4BD;" ><!--MATHEMATICAL SCRIPT SMALL H -->
<!ENTITY hslash           "&#x0210F;" ><!--PLANCK CONSTANT OVER TWO PI -->
<!ENTITY hstrok           "&#x00127;" ><!--LATIN SMALL LETTER H WITH STROKE -->
<!ENTITY hybull           "&#x02043;" ><!--HYPHEN BULLET -->
<!ENTITY hyphen           "&#x02010;" ><!--HYPHEN -->
<!ENTITY iacute           "&#x000ED;" ><!--LATIN SMALL LETTER I WITH ACUTE -->
<!ENTITY ic               "&#x02063;" ><!--INVISIBLE SEPARATOR -->
<!ENTITY icirc            "&#x000EE;" ><!--LATIN SMALL LETTER I WITH CIRCUMFLEX -->
<!ENTITY icy              "&#x00438;" ><!--CYRILLIC SMALL LETTER I -->
<!ENTITY iecy             "&#x00435;" ><!--CYRILLIC SMALL LETTER IE -->
<!ENTITY iexcl            "&#x000A1;" ><!--INVERTED EXCLAMATION MARK -->
<!ENTITY iff              "&#x021D4;" ><!--LEFT RIGHT DOUBLE ARROW -->
<!ENTITY ifr              "&#x1D526;" ><!--MATHEMATICAL FRAKTUR SMALL I -->
<!ENTITY igrave           "&#x000EC;" ><!--LATIN SMALL LETTER I WITH GRAVE -->
<!ENTITY ii               "&#x02148;" ><!--DOUBLE-STRUCK ITALIC SMALL I -->
<!ENTITY iiiint           "&#x02A0C;" ><!--QUADRUPLE INTEGRAL OPERATOR -->
<!ENTITY iiint            "&#x0222D;" ><!--TRIPLE INTEGRAL -->
<!ENTITY iinfin           "&#x029DC;" ><!--INCOMPLETE INFINITY -->
<!ENTITY iiota            "&#x02129;" ><!--TURNED GREEK SMALL LETTER IOTA -->
<!ENTITY ijlig            "&#x00133;" ><!--LATIN SMALL LIGATURE IJ -->
<!ENTITY imacr            "&#x0012B;" ><!--LATIN SMALL LETTER I WITH MACRON -->
<!ENTITY image            "&#x02111;" ><!--BLACK-LETTER CAPITAL I -->
<!ENTITY imagline         "&#x02110;" ><!--SCRIPT CAPITAL I -->
<!ENTITY imagpart         "&#x02111;" ><!--BLACK-LETTER CAPITAL I -->
<!ENTITY imath            "&#x00131;" ><!--LATIN SMALL LETTER DOTLESS I -->
<!ENTITY imof             "&#x022B7;" ><!--IMAGE OF -->
<!ENTITY imped            "&#x001B5;" ><!--LATIN CAPITAL LETTER Z WITH STROKE -->
<!ENTITY in               "&#x02208;" ><!--ELEMENT OF -->
<!ENTITY incare           "&#x02105;" ><!--CARE OF -->
<!ENTITY infin            "&#x0221E;" ><!--INFINITY -->
<!ENTITY infintie         "&#x029DD;" ><!--TIE OVER INFINITY -->
<!ENTITY inodot           "&#x00131;" ><!--LATIN SMALL LETTER DOTLESS I -->
<!ENTITY int              "&#x0222B;" ><!--INTEGRAL -->
<!ENTITY intcal           "&#x022BA;" ><!--INTERCALATE -->
<!ENTITY integers         "&#x02124;" ><!--DOUBLE-STRUCK CAPITAL Z -->
<!ENTITY intercal         "&#x022BA;" ><!--INTERCALATE -->
<!ENTITY intlarhk         "&#x02A17;" ><!--INTEGRAL WITH LEFTWARDS ARROW WITH HOOK -->
<!ENTITY intprod          "&#x02A3C;" ><!--INTERIOR PRODUCT -->
<!ENTITY iocy             "&#x00451;" ><!--CYRILLIC SMALL LETTER IO -->
<!ENTITY iogon            "&#x0012F;" ><!--LATIN SMALL LETTER I WITH OGONEK -->
<!ENTITY iopf             "&#x1D55A;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL I -->
<!ENTITY iota             "&#x003B9;" ><!--GREEK SMALL LETTER IOTA -->
<!ENTITY iprod            "&#x02A3C;" ><!--INTERIOR PRODUCT -->
<!ENTITY iquest           "&#x000BF;" ><!--INVERTED QUESTION MARK -->
<!ENTITY iscr             "&#x1D4BE;" ><!--MATHEMATICAL SCRIPT SMALL I -->
<!ENTITY isin             "&#x02208;" ><!--ELEMENT OF -->
<!ENTITY isinE            "&#x022F9;" ><!--ELEMENT OF WITH TWO HORIZONTAL STROKES -->
<!ENTITY isindot          "&#x022F5;" ><!--ELEMENT OF WITH DOT ABOVE -->
<!ENTITY isins            "&#x022F4;" ><!--SMALL ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE -->
<!ENTITY isinsv           "&#x022F3;" ><!--ELEMENT OF WITH VERTICAL BAR AT END OF HORIZONTAL STROKE -->
<!ENTITY isinv            "&#x02208;" ><!--ELEMENT OF -->
<!ENTITY it               "&#x02062;" ><!--INVISIBLE TIMES -->
<!ENTITY itilde           "&#x00129;" ><!--LATIN SMALL LETTER I WITH TILDE -->
<!ENTITY iukcy            "&#x00456;" ><!--CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I -->
<!ENTITY iuml             "&#x000EF;" ><!--LATIN SMALL LETTER I WITH DIAERESIS -->
<!ENTITY jcirc            "&#x00135;" ><!--LATIN SMALL LETTER J WITH CIRCUMFLEX -->
<!ENTITY jcy              "&#x00439;" ><!--CYRILLIC SMALL LETTER SHORT I -->
<!ENTITY jfr              "&#x1D527;" ><!--MATHEMATICAL FRAKTUR SMALL J -->
<!ENTITY jmath            "&#x00237;" ><!--LATIN SMALL LETTER DOTLESS J -->
<!ENTITY jopf             "&#x1D55B;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL J -->
<!ENTITY jscr             "&#x1D4BF;" ><!--MATHEMATICAL SCRIPT SMALL J -->
<!ENTITY jsercy           "&#x00458;" ><!--CYRILLIC SMALL LETTER JE -->
<!ENTITY jukcy            "&#x00454;" ><!--CYRILLIC SMALL LETTER UKRAINIAN IE -->
<!ENTITY kappa            "&#x003BA;" ><!--GREEK SMALL LETTER KAPPA -->
<!ENTITY kappav           "&#x003F0;" ><!--GREEK KAPPA SYMBOL -->
<!ENTITY kcedil           "&#x00137;" ><!--LATIN SMALL LETTER K WITH CEDILLA -->
<!ENTITY kcy              "&#x0043A;" ><!--CYRILLIC SMALL LETTER KA -->
<!ENTITY kfr              "&#x1D528;" ><!--MATHEMATICAL FRAKTUR SMALL K -->
<!ENTITY kgreen           "&#x00138;" ><!--LATIN SMALL LETTER KRA -->
<!ENTITY khcy             "&#x00445;" ><!--CYRILLIC SMALL LETTER HA -->
<!ENTITY kjcy             "&#x0045C;" ><!--CYRILLIC SMALL LETTER KJE -->
<!ENTITY kopf             "&#x1D55C;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL K -->
<!ENTITY kscr             "&#x1D4C0;" ><!--MATHEMATICAL SCRIPT SMALL K -->
<!ENTITY lAarr            "&#x021DA;" ><!--LEFTWARDS TRIPLE ARROW -->
<!ENTITY lArr             "&#x021D0;" ><!--LEFTWARDS DOUBLE ARROW -->
<!ENTITY lAtail           "&#x0291B;" ><!--LEFTWARDS DOUBLE ARROW-TAIL -->
<!ENTITY lBarr            "&#x0290E;" ><!--LEFTWARDS TRIPLE DASH ARROW -->
<!ENTITY lE               "&#x02266;" ><!--LESS-THAN OVER EQUAL TO -->
<!ENTITY lEg              "&#x02A8B;" ><!--LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN -->
<!ENTITY lHar             "&#x02962;" ><!--LEFTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB DOWN -->
<!ENTITY lacute           "&#x0013A;" ><!--LATIN SMALL LETTER L WITH ACUTE -->
<!ENTITY laemptyv         "&#x029B4;" ><!--EMPTY SET WITH LEFT ARROW ABOVE -->
<!ENTITY lagran           "&#x02112;" ><!--SCRIPT CAPITAL L -->
<!ENTITY lambda           "&#x003BB;" ><!--GREEK SMALL LETTER LAMDA -->
<!ENTITY lang             "&#x027E8;" ><!--MATHEMATICAL LEFT ANGLE BRACKET -->
<!ENTITY langd            "&#x02991;" ><!--LEFT ANGLE BRACKET WITH DOT -->
<!ENTITY langle           "&#x027E8;" ><!--MATHEMATICAL LEFT ANGLE BRACKET -->
<!ENTITY lap              "&#x02A85;" ><!--LESS-THAN OR APPROXIMATE -->
<!ENTITY laquo            "&#x000AB;" ><!--LEFT-POINTING DOUBLE ANGLE QUOTATION MARK -->
<!ENTITY larr             "&#x02190;" ><!--LEFTWARDS ARROW -->
<!ENTITY larrb            "&#x021E4;" ><!--LEFTWARDS ARROW TO BAR -->
<!ENTITY larrbfs          "&#x0291F;" ><!--LEFTWARDS ARROW FROM BAR TO BLACK DIAMOND -->
<!ENTITY larrfs           "&#x0291D;" ><!--LEFTWARDS ARROW TO BLACK DIAMOND -->
<!ENTITY larrhk           "&#x021A9;" ><!--LEFTWARDS ARROW WITH HOOK -->
<!ENTITY larrlp           "&#x021AB;" ><!--LEFTWARDS ARROW WITH LOOP -->
<!ENTITY larrpl           "&#x02939;" ><!--LEFT-SIDE ARC ANTICLOCKWISE ARROW -->
<!ENTITY larrsim          "&#x02973;" ><!--LEFTWARDS ARROW ABOVE TILDE OPERATOR -->
<!ENTITY larrtl           "&#x021A2;" ><!--LEFTWARDS ARROW WITH TAIL -->
<!ENTITY lat              "&#x02AAB;" ><!--LARGER THAN -->
<!ENTITY latail           "&#x02919;" ><!--LEFTWARDS ARROW-TAIL -->
<!ENTITY late             "&#x02AAD;" ><!--LARGER THAN OR EQUAL TO -->
<!ENTITY lates            "&#x02AAD;&#x0FE00;" ><!--LARGER THAN OR slanted EQUAL -->
<!ENTITY lbarr            "&#x0290C;" ><!--LEFTWARDS DOUBLE DASH ARROW -->
<!ENTITY lbbrk            "&#x02772;" ><!--LIGHT LEFT TORTOISE SHELL BRACKET ORNAMENT -->
<!ENTITY lbrace           "&#x0007B;" ><!--LEFT CURLY BRACKET -->
<!ENTITY lbrack           "&#x0005B;" ><!--LEFT SQUARE BRACKET -->
<!ENTITY lbrke            "&#x0298B;" ><!--LEFT SQUARE BRACKET WITH UNDERBAR -->
<!ENTITY lbrksld          "&#x0298F;" ><!--LEFT SQUARE BRACKET WITH TICK IN BOTTOM CORNER -->
<!ENTITY lbrkslu          "&#x0298D;" ><!--LEFT SQUARE BRACKET WITH TICK IN TOP CORNER -->
<!ENTITY lcaron           "&#x0013E;" ><!--LATIN SMALL LETTER L WITH CARON -->
<!ENTITY lcedil           "&#x0013C;" ><!--LATIN SMALL LETTER L WITH CEDILLA -->
<!ENTITY lceil            "&#x02308;" ><!--LEFT CEILING -->
<!ENTITY lcub             "&#x0007B;" ><!--LEFT CURLY BRACKET -->
<!ENTITY lcy              "&#x0043B;" ><!--CYRILLIC SMALL LETTER EL -->
<!ENTITY ldca             "&#x02936;" ><!--ARROW POINTING DOWNWARDS THEN CURVING LEFTWARDS -->
<!ENTITY ldquo            "&#x0201C;" ><!--LEFT DOUBLE QUOTATION MARK -->
<!ENTITY ldquor           "&#x0201E;" ><!--DOUBLE LOW-9 QUOTATION MARK -->
<!ENTITY ldrdhar          "&#x02967;" ><!--LEFTWARDS HARPOON WITH BARB DOWN ABOVE RIGHTWARDS HARPOON WITH BARB DOWN -->
<!ENTITY ldrushar         "&#x0294B;" ><!--LEFT BARB DOWN RIGHT BARB UP HARPOON -->
<!ENTITY ldsh             "&#x021B2;" ><!--DOWNWARDS ARROW WITH TIP LEFTWARDS -->
<!ENTITY le               "&#x02264;" ><!--LESS-THAN OR EQUAL TO -->
<!ENTITY leftarrow        "&#x02190;" ><!--LEFTWARDS ARROW -->
<!ENTITY leftarrowtail    "&#x021A2;" ><!--LEFTWARDS ARROW WITH TAIL -->
<!ENTITY leftharpoondown  "&#x021BD;" ><!--LEFTWARDS HARPOON WITH BARB DOWNWARDS -->
<!ENTITY leftharpoonup    "&#x021BC;" ><!--LEFTWARDS HARPOON WITH BARB UPWARDS -->
<!ENTITY leftleftarrows   "&#x021C7;" ><!--LEFTWARDS PAIRED ARROWS -->
<!ENTITY leftrightarrow   "&#x02194;" ><!--LEFT RIGHT ARROW -->
<!ENTITY leftrightarrows  "&#x021C6;" ><!--LEFTWARDS ARROW OVER RIGHTWARDS ARROW -->
<!ENTITY leftrightharpoons "&#x021CB;" ><!--LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON -->
<!ENTITY leftrightsquigarrow "&#x021AD;" ><!--LEFT RIGHT WAVE ARROW -->
<!ENTITY leftthreetimes   "&#x022CB;" ><!--LEFT SEMIDIRECT PRODUCT -->
<!ENTITY leg              "&#x022DA;" ><!--LESS-THAN EQUAL TO OR GREATER-THAN -->
<!ENTITY leq              "&#x02264;" ><!--LESS-THAN OR EQUAL TO -->
<!ENTITY leqq             "&#x02266;" ><!--LESS-THAN OVER EQUAL TO -->
<!ENTITY leqslant         "&#x02A7D;" ><!--LESS-THAN OR SLANTED EQUAL TO -->
<!ENTITY les              "&#x02A7D;" ><!--LESS-THAN OR SLANTED EQUAL TO -->
<!ENTITY lescc            "&#x02AA8;" ><!--LESS-THAN CLOSED BY CURVE ABOVE SLANTED EQUAL -->
<!ENTITY lesdot           "&#x02A7F;" ><!--LESS-THAN OR SLANTED EQUAL TO WITH DOT INSIDE -->
<!ENTITY lesdoto          "&#x02A81;" ><!--LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE -->
<!ENTITY lesdotor         "&#x02A83;" ><!--LESS-THAN OR SLANTED EQUAL TO WITH DOT ABOVE RIGHT -->
<!ENTITY lesg             "&#x022DA;&#x0FE00;" ><!--LESS-THAN slanted EQUAL TO OR GREATER-THAN -->
<!ENTITY lesges           "&#x02A93;" ><!--LESS-THAN ABOVE SLANTED EQUAL ABOVE GREATER-THAN ABOVE SLANTED EQUAL -->
<!ENTITY lessapprox       "&#x02A85;" ><!--LESS-THAN OR APPROXIMATE -->
<!ENTITY lessdot          "&#x022D6;" ><!--LESS-THAN WITH DOT -->
<!ENTITY lesseqgtr        "&#x022DA;" ><!--LESS-THAN EQUAL TO OR GREATER-THAN -->
<!ENTITY lesseqqgtr       "&#x02A8B;" ><!--LESS-THAN ABOVE DOUBLE-LINE EQUAL ABOVE GREATER-THAN -->
<!ENTITY lessgtr          "&#x02276;" ><!--LESS-THAN OR GREATER-THAN -->
<!ENTITY lesssim          "&#x02272;" ><!--LESS-THAN OR EQUIVALENT TO -->
<!ENTITY lfisht           "&#x0297C;" ><!--LEFT FISH TAIL -->
<!ENTITY lfloor           "&#x0230A;" ><!--LEFT FLOOR -->
<!ENTITY lfr              "&#x1D529;" ><!--MATHEMATICAL FRAKTUR SMALL L -->
<!ENTITY lg               "&#x02276;" ><!--LESS-THAN OR GREATER-THAN -->
<!ENTITY lgE              "&#x02A91;" ><!--LESS-THAN ABOVE GREATER-THAN ABOVE DOUBLE-LINE EQUAL -->
<!ENTITY lhard            "&#x021BD;" ><!--LEFTWARDS HARPOON WITH BARB DOWNWARDS -->
<!ENTITY lharu            "&#x021BC;" ><!--LEFTWARDS HARPOON WITH BARB UPWARDS -->
<!ENTITY lharul           "&#x0296A;" ><!--LEFTWARDS HARPOON WITH BARB UP ABOVE LONG DASH -->
<!ENTITY lhblk            "&#x02584;" ><!--LOWER HALF BLOCK -->
<!ENTITY ljcy             "&#x00459;" ><!--CYRILLIC SMALL LETTER LJE -->
<!ENTITY ll               "&#x0226A;" ><!--MUCH LESS-THAN -->
<!ENTITY llarr            "&#x021C7;" ><!--LEFTWARDS PAIRED ARROWS -->
<!ENTITY llcorner         "&#x0231E;" ><!--BOTTOM LEFT CORNER -->
<!ENTITY llhard           "&#x0296B;" ><!--LEFTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH -->
<!ENTITY lltri            "&#x025FA;" ><!--LOWER LEFT TRIANGLE -->
<!ENTITY lmidot           "&#x00140;" ><!--LATIN SMALL LETTER L WITH MIDDLE DOT -->
<!ENTITY lmoust           "&#x023B0;" ><!--UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION -->
<!ENTITY lmoustache       "&#x023B0;" ><!--UPPER LEFT OR LOWER RIGHT CURLY BRACKET SECTION -->
<!ENTITY lnE              "&#x02268;" ><!--LESS-THAN BUT NOT EQUAL TO -->
<!ENTITY lnap             "&#x02A89;" ><!--LESS-THAN AND NOT APPROXIMATE -->
<!ENTITY lnapprox         "&#x02A89;" ><!--LESS-THAN AND NOT APPROXIMATE -->
<!ENTITY lne              "&#x02A87;" ><!--LESS-THAN AND SINGLE-LINE NOT EQUAL TO -->
<!ENTITY lneq             "&#x02A87;" ><!--LESS-THAN AND SINGLE-LINE NOT EQUAL TO -->
<!ENTITY lneqq            "&#x02268;" ><!--LESS-THAN BUT NOT EQUAL TO -->
<!ENTITY lnsim            "&#x022E6;" ><!--LESS-THAN BUT NOT EQUIVALENT TO -->
<!ENTITY loang            "&#x027EC;" ><!--MATHEMATICAL LEFT WHITE TORTOISE SHELL BRACKET -->
<!ENTITY loarr            "&#x021FD;" ><!--LEFTWARDS OPEN-HEADED ARROW -->
<!ENTITY lobrk            "&#x027E6;" ><!--MATHEMATICAL LEFT WHITE SQUARE BRACKET -->
<!ENTITY longleftarrow    "&#x027F5;" ><!--LONG LEFTWARDS ARROW -->
<!ENTITY longleftrightarrow "&#x027F7;" ><!--LONG LEFT RIGHT ARROW -->
<!ENTITY longmapsto       "&#x027FC;" ><!--LONG RIGHTWARDS ARROW FROM BAR -->
<!ENTITY longrightarrow   "&#x027F6;" ><!--LONG RIGHTWARDS ARROW -->
<!ENTITY looparrowleft    "&#x021AB;" ><!--LEFTWARDS ARROW WITH LOOP -->
<!ENTITY looparrowright   "&#x021AC;" ><!--RIGHTWARDS ARROW WITH LOOP -->
<!ENTITY lopar            "&#x02985;" ><!--LEFT WHITE PARENTHESIS -->
<!ENTITY lopf             "&#x1D55D;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL L -->
<!ENTITY loplus           "&#x02A2D;" ><!--PLUS SIGN IN LEFT HALF CIRCLE -->
<!ENTITY lotimes          "&#x02A34;" ><!--MULTIPLICATION SIGN IN LEFT HALF CIRCLE -->
<!ENTITY lowast           "&#x02217;" ><!--ASTERISK OPERATOR -->
<!ENTITY lowbar           "&#x0005F;" ><!--LOW LINE -->
<!ENTITY loz              "&#x025CA;" ><!--LOZENGE -->
<!ENTITY lozenge          "&#x025CA;" ><!--LOZENGE -->
<!ENTITY lozf             "&#x029EB;" ><!--BLACK LOZENGE -->
<!ENTITY lpar             "&#x00028;" ><!--LEFT PARENTHESIS -->
<!ENTITY lparlt           "&#x02993;" ><!--LEFT ARC LESS-THAN BRACKET -->
<!ENTITY lrarr            "&#x021C6;" ><!--LEFTWARDS ARROW OVER RIGHTWARDS ARROW -->
<!ENTITY lrcorner         "&#x0231F;" ><!--BOTTOM RIGHT CORNER -->
<!ENTITY lrhar            "&#x021CB;" ><!--LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON -->
<!ENTITY lrhard           "&#x0296D;" ><!--RIGHTWARDS HARPOON WITH BARB DOWN BELOW LONG DASH -->
<!ENTITY lrm              "&#x0200E;" ><!--LEFT-TO-RIGHT MARK -->
<!ENTITY lrtri            "&#x022BF;" ><!--RIGHT TRIANGLE -->
<!ENTITY lsaquo           "&#x02039;" ><!--SINGLE LEFT-POINTING ANGLE QUOTATION MARK -->
<!ENTITY lscr             "&#x1D4C1;" ><!--MATHEMATICAL SCRIPT SMALL L -->
<!ENTITY lsh              "&#x021B0;" ><!--UPWARDS ARROW WITH TIP LEFTWARDS -->
<!ENTITY lsim             "&#x02272;" ><!--LESS-THAN OR EQUIVALENT TO -->
<!ENTITY lsime            "&#x02A8D;" ><!--LESS-THAN ABOVE SIMILAR OR EQUAL -->
<!ENTITY lsimg            "&#x02A8F;" ><!--LESS-THAN ABOVE SIMILAR ABOVE GREATER-THAN -->
<!ENTITY lsqb             "&#x0005B;" ><!--LEFT SQUARE BRACKET -->
<!ENTITY lsquo            "&#x02018;" ><!--LEFT SINGLE QUOTATION MARK -->
<!ENTITY lsquor           "&#x0201A;" ><!--SINGLE LOW-9 QUOTATION MARK -->
<!ENTITY lstrok           "&#x00142;" ><!--LATIN SMALL LETTER L WITH STROKE -->
<!ENTITY lt               "&#38;#60;" ><!--LESS-THAN SIGN -->
<!ENTITY ltcc             "&#x02AA6;" ><!--LESS-THAN CLOSED BY CURVE -->
<!ENTITY ltcir            "&#x02A79;" ><!--LESS-THAN WITH CIRCLE INSIDE -->
<!ENTITY ltdot            "&#x022D6;" ><!--LESS-THAN WITH DOT -->
<!ENTITY lthree           "&#x022CB;" ><!--LEFT SEMIDIRECT PRODUCT -->
<!ENTITY ltimes           "&#x022C9;" ><!--LEFT NORMAL FACTOR SEMIDIRECT PRODUCT -->
<!ENTITY ltlarr           "&#x02976;" ><!--LESS-THAN ABOVE LEFTWARDS ARROW -->
<!ENTITY ltquest          "&#x02A7B;" ><!--LESS-THAN WITH QUESTION MARK ABOVE -->
<!ENTITY ltrPar           "&#x02996;" ><!--DOUBLE RIGHT ARC LESS-THAN BRACKET -->
<!ENTITY ltri             "&#x025C3;" ><!--WHITE LEFT-POINTING SMALL TRIANGLE -->
<!ENTITY ltrie            "&#x022B4;" ><!--NORMAL SUBGROUP OF OR EQUAL TO -->
<!ENTITY ltrif            "&#x025C2;" ><!--BLACK LEFT-POINTING SMALL TRIANGLE -->
<!ENTITY lurdshar         "&#x0294A;" ><!--LEFT BARB UP RIGHT BARB DOWN HARPOON -->
<!ENTITY luruhar          "&#x02966;" ><!--LEFTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB UP -->
<!ENTITY lvertneqq        "&#x02268;&#x0FE00;" ><!--LESS-THAN BUT NOT EQUAL TO - with vertical stroke -->
<!ENTITY lvnE             "&#x02268;&#x0FE00;" ><!--LESS-THAN BUT NOT EQUAL TO - with vertical stroke -->
<!ENTITY mDDot            "&#x0223A;" ><!--GEOMETRIC PROPORTION -->
<!ENTITY macr             "&#x000AF;" ><!--MACRON -->
<!ENTITY male             "&#x02642;" ><!--MALE SIGN -->
<!ENTITY malt             "&#x02720;" ><!--MALTESE CROSS -->
<!ENTITY maltese          "&#x02720;" ><!--MALTESE CROSS -->
<!ENTITY map              "&#x021A6;" ><!--RIGHTWARDS ARROW FROM BAR -->
<!ENTITY mapsto           "&#x021A6;" ><!--RIGHTWARDS ARROW FROM BAR -->
<!ENTITY mapstodown       "&#x021A7;" ><!--DOWNWARDS ARROW FROM BAR -->
<!ENTITY mapstoleft       "&#x021A4;" ><!--LEFTWARDS ARROW FROM BAR -->
<!ENTITY mapstoup         "&#x021A5;" ><!--UPWARDS ARROW FROM BAR -->
<!ENTITY marker           "&#x025AE;" ><!--BLACK VERTICAL RECTANGLE -->
<!ENTITY mcomma           "&#x02A29;" ><!--MINUS SIGN WITH COMMA ABOVE -->
<!ENTITY mcy              "&#x0043C;" ><!--CYRILLIC SMALL LETTER EM -->
<!ENTITY mdash            "&#x02014;" ><!--EM DASH -->
<!ENTITY measuredangle    "&#x02221;" ><!--MEASURED ANGLE -->
<!ENTITY mfr              "&#x1D52A;" ><!--MATHEMATICAL FRAKTUR SMALL M -->
<!ENTITY mho              "&#x02127;" ><!--INVERTED OHM SIGN -->
<!ENTITY micro            "&#x000B5;" ><!--MICRO SIGN -->
<!ENTITY mid              "&#x02223;" ><!--DIVIDES -->
<!ENTITY midast           "&#x0002A;" ><!--ASTERISK -->
<!ENTITY midcir           "&#x02AF0;" ><!--VERTICAL LINE WITH CIRCLE BELOW -->
<!ENTITY middot           "&#x000B7;" ><!--MIDDLE DOT -->
<!ENTITY minus            "&#x02212;" ><!--MINUS SIGN -->
<!ENTITY minusb           "&#x0229F;" ><!--SQUARED MINUS -->
<!ENTITY minusd           "&#x02238;" ><!--DOT MINUS -->
<!ENTITY minusdu          "&#x02A2A;" ><!--MINUS SIGN WITH DOT BELOW -->
<!ENTITY mlcp             "&#x02ADB;" ><!--TRANSVERSAL INTERSECTION -->
<!ENTITY mldr             "&#x02026;" ><!--HORIZONTAL ELLIPSIS -->
<!ENTITY mnplus           "&#x02213;" ><!--MINUS-OR-PLUS SIGN -->
<!ENTITY models           "&#x022A7;" ><!--MODELS -->
<!ENTITY mopf             "&#x1D55E;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL M -->
<!ENTITY mp               "&#x02213;" ><!--MINUS-OR-PLUS SIGN -->
<!ENTITY mscr             "&#x1D4C2;" ><!--MATHEMATICAL SCRIPT SMALL M -->
<!ENTITY mstpos           "&#x0223E;" ><!--INVERTED LAZY S -->
<!ENTITY mu               "&#x003BC;" ><!--GREEK SMALL LETTER MU -->
<!ENTITY multimap         "&#x022B8;" ><!--MULTIMAP -->
<!ENTITY mumap            "&#x022B8;" ><!--MULTIMAP -->
<!ENTITY nGg              "&#x022D9;&#x00338;" ><!--VERY MUCH GREATER-THAN with slash -->
<!ENTITY nGt              "&#x0226B;&#x020D2;" ><!--MUCH GREATER THAN with vertical line -->
<!ENTITY nGtv             "&#x0226B;&#x00338;" ><!--MUCH GREATER THAN with slash -->
<!ENTITY nLeftarrow       "&#x021CD;" ><!--LEFTWARDS DOUBLE ARROW WITH STROKE -->
<!ENTITY nLeftrightarrow  "&#x021CE;" ><!--LEFT RIGHT DOUBLE ARROW WITH STROKE -->
<!ENTITY nLl              "&#x022D8;&#x00338;" ><!--VERY MUCH LESS-THAN with slash -->
<!ENTITY nLt              "&#x0226A;&#x020D2;" ><!--MUCH LESS THAN with vertical line -->
<!ENTITY nLtv             "&#x0226A;&#x00338;" ><!--MUCH LESS THAN with slash -->
<!ENTITY nRightarrow      "&#x021CF;" ><!--RIGHTWARDS DOUBLE ARROW WITH STROKE -->
<!ENTITY nVDash           "&#x022AF;" ><!--NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE -->
<!ENTITY nVdash           "&#x022AE;" ><!--DOES NOT FORCE -->
<!ENTITY nabla            "&#x02207;" ><!--NABLA -->
<!ENTITY nacute           "&#x00144;" ><!--LATIN SMALL LETTER N WITH ACUTE -->
<!ENTITY nang             "&#x02220;&#x020D2;" ><!--ANGLE with vertical line -->
<!ENTITY nap              "&#x02249;" ><!--NOT ALMOST EQUAL TO -->
<!ENTITY napE             "&#x02A70;&#x00338;" ><!--APPROXIMATELY EQUAL OR EQUAL TO with slash -->
<!ENTITY napid            "&#x0224B;&#x00338;" ><!--TRIPLE TILDE with slash -->
<!ENTITY napos            "&#x00149;" ><!--LATIN SMALL LETTER N PRECEDED BY APOSTROPHE -->
<!ENTITY napprox          "&#x02249;" ><!--NOT ALMOST EQUAL TO -->
<!ENTITY natur            "&#x0266E;" ><!--MUSIC NATURAL SIGN -->
<!ENTITY natural          "&#x0266E;" ><!--MUSIC NATURAL SIGN -->
<!ENTITY naturals         "&#x02115;" ><!--DOUBLE-STRUCK CAPITAL N -->
<!ENTITY nbsp             "&#x000A0;" ><!--NO-BREAK SPACE -->
<!ENTITY nbump            "&#x0224E;&#x00338;" ><!--GEOMETRICALLY EQUIVALENT TO with slash -->
<!ENTITY nbumpe           "&#x0224F;&#x00338;" ><!--DIFFERENCE BETWEEN with slash -->
<!ENTITY ncap             "&#x02A43;" ><!--INTERSECTION WITH OVERBAR -->
<!ENTITY ncaron           "&#x00148;" ><!--LATIN SMALL LETTER N WITH CARON -->
<!ENTITY ncedil           "&#x00146;" ><!--LATIN SMALL LETTER N WITH CEDILLA -->
<!ENTITY ncong            "&#x02247;" ><!--NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO -->
<!ENTITY ncongdot         "&#x02A6D;&#x00338;" ><!--CONGRUENT WITH DOT ABOVE with slash -->
<!ENTITY ncup             "&#x02A42;" ><!--UNION WITH OVERBAR -->
<!ENTITY ncy              "&#x0043D;" ><!--CYRILLIC SMALL LETTER EN -->
<!ENTITY ndash            "&#x02013;" ><!--EN DASH -->
<!ENTITY ne               "&#x02260;" ><!--NOT EQUAL TO -->
<!ENTITY neArr            "&#x021D7;" ><!--NORTH EAST DOUBLE ARROW -->
<!ENTITY nearhk           "&#x02924;" ><!--NORTH EAST ARROW WITH HOOK -->
<!ENTITY nearr            "&#x02197;" ><!--NORTH EAST ARROW -->
<!ENTITY nearrow          "&#x02197;" ><!--NORTH EAST ARROW -->
<!ENTITY nedot            "&#x02250;&#x00338;" ><!--APPROACHES THE LIMIT with slash -->
<!ENTITY nequiv           "&#x02262;" ><!--NOT IDENTICAL TO -->
<!ENTITY nesear           "&#x02928;" ><!--NORTH EAST ARROW AND SOUTH EAST ARROW -->
<!ENTITY nesim            "&#x02242;&#x00338;" ><!--MINUS TILDE with slash -->
<!ENTITY nexist           "&#x02204;" ><!--THERE DOES NOT EXIST -->
<!ENTITY nexists          "&#x02204;" ><!--THERE DOES NOT EXIST -->
<!ENTITY nfr              "&#x1D52B;" ><!--MATHEMATICAL FRAKTUR SMALL N -->
<!ENTITY ngE              "&#x02267;&#x00338;" ><!--GREATER-THAN OVER EQUAL TO with slash -->
<!ENTITY nge              "&#x02271;" ><!--NEITHER GREATER-THAN NOR EQUAL TO -->
<!ENTITY ngeq             "&#x02271;" ><!--NEITHER GREATER-THAN NOR EQUAL TO -->
<!ENTITY ngeqq            "&#x02267;&#x00338;" ><!--GREATER-THAN OVER EQUAL TO with slash -->
<!ENTITY ngeqslant        "&#x02A7E;&#x00338;" ><!--GREATER-THAN OR SLANTED EQUAL TO with slash -->
<!ENTITY nges             "&#x02A7E;&#x00338;" ><!--GREATER-THAN OR SLANTED EQUAL TO with slash -->
<!ENTITY ngsim            "&#x02275;" ><!--NEITHER GREATER-THAN NOR EQUIVALENT TO -->
<!ENTITY ngt              "&#x0226F;" ><!--NOT GREATER-THAN -->
<!ENTITY ngtr             "&#x0226F;" ><!--NOT GREATER-THAN -->
<!ENTITY nhArr            "&#x021CE;" ><!--LEFT RIGHT DOUBLE ARROW WITH STROKE -->
<!ENTITY nharr            "&#x021AE;" ><!--LEFT RIGHT ARROW WITH STROKE -->
<!ENTITY nhpar            "&#x02AF2;" ><!--PARALLEL WITH HORIZONTAL STROKE -->
<!ENTITY ni               "&#x0220B;" ><!--CONTAINS AS MEMBER -->
<!ENTITY nis              "&#x022FC;" ><!--SMALL CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE -->
<!ENTITY nisd             "&#x022FA;" ><!--CONTAINS WITH LONG HORIZONTAL STROKE -->
<!ENTITY niv              "&#x0220B;" ><!--CONTAINS AS MEMBER -->
<!ENTITY njcy             "&#x0045A;" ><!--CYRILLIC SMALL LETTER NJE -->
<!ENTITY nlArr            "&#x021CD;" ><!--LEFTWARDS DOUBLE ARROW WITH STROKE -->
<!ENTITY nlE              "&#x02266;&#x00338;" ><!--LESS-THAN OVER EQUAL TO with slash -->
<!ENTITY nlarr            "&#x0219A;" ><!--LEFTWARDS ARROW WITH STROKE -->
<!ENTITY nldr             "&#x02025;" ><!--TWO DOT LEADER -->
<!ENTITY nle              "&#x02270;" ><!--NEITHER LESS-THAN NOR EQUAL TO -->
<!ENTITY nleftarrow       "&#x0219A;" ><!--LEFTWARDS ARROW WITH STROKE -->
<!ENTITY nleftrightarrow  "&#x021AE;" ><!--LEFT RIGHT ARROW WITH STROKE -->
<!ENTITY nleq             "&#x02270;" ><!--NEITHER LESS-THAN NOR EQUAL TO -->
<!ENTITY nleqq            "&#x02266;&#x00338;" ><!--LESS-THAN OVER EQUAL TO with slash -->
<!ENTITY nleqslant        "&#x02A7D;&#x00338;" ><!--LESS-THAN OR SLANTED EQUAL TO with slash -->
<!ENTITY nles             "&#x02A7D;&#x00338;" ><!--LESS-THAN OR SLANTED EQUAL TO with slash -->
<!ENTITY nless            "&#x0226E;" ><!--NOT LESS-THAN -->
<!ENTITY nlsim            "&#x02274;" ><!--NEITHER LESS-THAN NOR EQUIVALENT TO -->
<!ENTITY nlt              "&#x0226E;" ><!--NOT LESS-THAN -->
<!ENTITY nltri            "&#x022EA;" ><!--NOT NORMAL SUBGROUP OF -->
<!ENTITY nltrie           "&#x022EC;" ><!--NOT NORMAL SUBGROUP OF OR EQUAL TO -->
<!ENTITY nmid             "&#x02224;" ><!--DOES NOT DIVIDE -->
<!ENTITY nopf             "&#x1D55F;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL N -->
<!ENTITY not              "&#x000AC;" ><!--NOT SIGN -->
<!ENTITY notin            "&#x02209;" ><!--NOT AN ELEMENT OF -->
<!ENTITY notinE           "&#x022F9;&#x00338;" ><!--ELEMENT OF WITH TWO HORIZONTAL STROKES with slash -->
<!ENTITY notindot         "&#x022F5;&#x00338;" ><!--ELEMENT OF WITH DOT ABOVE with slash -->
<!ENTITY notinva          "&#x02209;" ><!--NOT AN ELEMENT OF -->
<!ENTITY notinvb          "&#x022F7;" ><!--SMALL ELEMENT OF WITH OVERBAR -->
<!ENTITY notinvc          "&#x022F6;" ><!--ELEMENT OF WITH OVERBAR -->
<!ENTITY notni            "&#x0220C;" ><!--DOES NOT CONTAIN AS MEMBER -->
<!ENTITY notniva          "&#x0220C;" ><!--DOES NOT CONTAIN AS MEMBER -->
<!ENTITY notnivb          "&#x022FE;" ><!--SMALL CONTAINS WITH OVERBAR -->
<!ENTITY notnivc          "&#x022FD;" ><!--CONTAINS WITH OVERBAR -->
<!ENTITY npar             "&#x02226;" ><!--NOT PARALLEL TO -->
<!ENTITY nparallel        "&#x02226;" ><!--NOT PARALLEL TO -->
<!ENTITY nparsl           "&#x02AFD;&#x020E5;" ><!--DOUBLE SOLIDUS OPERATOR with reverse slash -->
<!ENTITY npart            "&#x02202;&#x00338;" ><!--PARTIAL DIFFERENTIAL with slash -->
<!ENTITY npolint          "&#x02A14;" ><!--LINE INTEGRATION NOT INCLUDING THE POLE -->
<!ENTITY npr              "&#x02280;" ><!--DOES NOT PRECEDE -->
<!ENTITY nprcue           "&#x022E0;" ><!--DOES NOT PRECEDE OR EQUAL -->
<!ENTITY npre             "&#x02AAF;&#x00338;" ><!--PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash -->
<!ENTITY nprec            "&#x02280;" ><!--DOES NOT PRECEDE -->
<!ENTITY npreceq          "&#x02AAF;&#x00338;" ><!--PRECEDES ABOVE SINGLE-LINE EQUALS SIGN with slash -->
<!ENTITY nrArr            "&#x021CF;" ><!--RIGHTWARDS DOUBLE ARROW WITH STROKE -->
<!ENTITY nrarr            "&#x0219B;" ><!--RIGHTWARDS ARROW WITH STROKE -->
<!ENTITY nrarrc           "&#x02933;&#x00338;" ><!--WAVE ARROW POINTING DIRECTLY RIGHT with slash -->
<!ENTITY nrarrw           "&#x0219D;&#x00338;" ><!--RIGHTWARDS WAVE ARROW with slash -->
<!ENTITY nrightarrow      "&#x0219B;" ><!--RIGHTWARDS ARROW WITH STROKE -->
<!ENTITY nrtri            "&#x022EB;" ><!--DOES NOT CONTAIN AS NORMAL SUBGROUP -->
<!ENTITY nrtrie           "&#x022ED;" ><!--DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL -->
<!ENTITY nsc              "&#x02281;" ><!--DOES NOT SUCCEED -->
<!ENTITY nsccue           "&#x022E1;" ><!--DOES NOT SUCCEED OR EQUAL -->
<!ENTITY nsce             "&#x02AB0;&#x00338;" ><!--SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash -->
<!ENTITY nscr             "&#x1D4C3;" ><!--MATHEMATICAL SCRIPT SMALL N -->
<!ENTITY nshortmid        "&#x02224;" ><!--DOES NOT DIVIDE -->
<!ENTITY nshortparallel   "&#x02226;" ><!--NOT PARALLEL TO -->
<!ENTITY nsim             "&#x02241;" ><!--NOT TILDE -->
<!ENTITY nsime            "&#x02244;" ><!--NOT ASYMPTOTICALLY EQUAL TO -->
<!ENTITY nsimeq           "&#x02244;" ><!--NOT ASYMPTOTICALLY EQUAL TO -->
<!ENTITY nsmid            "&#x02224;" ><!--DOES NOT DIVIDE -->
<!ENTITY nspar            "&#x02226;" ><!--NOT PARALLEL TO -->
<!ENTITY nsqsube          "&#x022E2;" ><!--NOT SQUARE IMAGE OF OR EQUAL TO -->
<!ENTITY nsqsupe          "&#x022E3;" ><!--NOT SQUARE ORIGINAL OF OR EQUAL TO -->
<!ENTITY nsub             "&#x02284;" ><!--NOT A SUBSET OF -->
<!ENTITY nsubE            "&#x02AC5;&#x00338;" ><!--SUBSET OF ABOVE EQUALS SIGN with slash -->
<!ENTITY nsube            "&#x02288;" ><!--NEITHER A SUBSET OF NOR EQUAL TO -->
<!ENTITY nsubset          "&#x02282;&#x020D2;" ><!--SUBSET OF with vertical line -->
<!ENTITY nsubseteq        "&#x02288;" ><!--NEITHER A SUBSET OF NOR EQUAL TO -->
<!ENTITY nsubseteqq       "&#x02AC5;&#x00338;" ><!--SUBSET OF ABOVE EQUALS SIGN with slash -->
<!ENTITY nsucc            "&#x02281;" ><!--DOES NOT SUCCEED -->
<!ENTITY nsucceq          "&#x02AB0;&#x00338;" ><!--SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN with slash -->
<!ENTITY nsup             "&#x02285;" ><!--NOT A SUPERSET OF -->
<!ENTITY nsupE            "&#x02AC6;&#x00338;" ><!--SUPERSET OF ABOVE EQUALS SIGN with slash -->
<!ENTITY nsupe            "&#x02289;" ><!--NEITHER A SUPERSET OF NOR EQUAL TO -->
<!ENTITY nsupset          "&#x02283;&#x020D2;" ><!--SUPERSET OF with vertical line -->
<!ENTITY nsupseteq        "&#x02289;" ><!--NEITHER A SUPERSET OF NOR EQUAL TO -->
<!ENTITY nsupseteqq       "&#x02AC6;&#x00338;" ><!--SUPERSET OF ABOVE EQUALS SIGN with slash -->
<!ENTITY ntgl             "&#x02279;" ><!--NEITHER GREATER-THAN NOR LESS-THAN -->
<!ENTITY ntilde           "&#x000F1;" ><!--LATIN SMALL LETTER N WITH TILDE -->
<!ENTITY ntlg             "&#x02278;" ><!--NEITHER LESS-THAN NOR GREATER-THAN -->
<!ENTITY ntriangleleft    "&#x022EA;" ><!--NOT NORMAL SUBGROUP OF -->
<!ENTITY ntrianglelefteq  "&#x022EC;" ><!--NOT NORMAL SUBGROUP OF OR EQUAL TO -->
<!ENTITY ntriangleright   "&#x022EB;" ><!--DOES NOT CONTAIN AS NORMAL SUBGROUP -->
<!ENTITY ntrianglerighteq "&#x022ED;" ><!--DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL -->
<!ENTITY nu               "&#x003BD;" ><!--GREEK SMALL LETTER NU -->
<!ENTITY num              "&#x00023;" ><!--NUMBER SIGN -->
<!ENTITY numero           "&#x02116;" ><!--NUMERO SIGN -->
<!ENTITY numsp            "&#x02007;" ><!--FIGURE SPACE -->
<!ENTITY nvDash           "&#x022AD;" ><!--NOT TRUE -->
<!ENTITY nvHarr           "&#x02904;" ><!--LEFT RIGHT DOUBLE ARROW WITH VERTICAL STROKE -->
<!ENTITY nvap             "&#x0224D;&#x020D2;" ><!--EQUIVALENT TO with vertical line -->
<!ENTITY nvdash           "&#x022AC;" ><!--DOES NOT PROVE -->
<!ENTITY nvge             "&#x02265;&#x020D2;" ><!--GREATER-THAN OR EQUAL TO with vertical line -->
<!ENTITY nvgt             "&#x0003E;&#x020D2;" ><!--GREATER-THAN SIGN with vertical line -->
<!ENTITY nvinfin          "&#x029DE;" ><!--INFINITY NEGATED WITH VERTICAL BAR -->
<!ENTITY nvlArr           "&#x02902;" ><!--LEFTWARDS DOUBLE ARROW WITH VERTICAL STROKE -->
<!ENTITY nvle             "&#x02264;&#x020D2;" ><!--LESS-THAN OR EQUAL TO with vertical line -->
<!ENTITY nvlt             "&#38;#x0003C;&#x020D2;" ><!--LESS-THAN SIGN with vertical line -->
<!ENTITY nvltrie          "&#x022B4;&#x020D2;" ><!--NORMAL SUBGROUP OF OR EQUAL TO with vertical line -->
<!ENTITY nvrArr           "&#x02903;" ><!--RIGHTWARDS DOUBLE ARROW WITH VERTICAL STROKE -->
<!ENTITY nvrtrie          "&#x022B5;&#x020D2;" ><!--CONTAINS AS NORMAL SUBGROUP OR EQUAL TO with vertical line -->
<!ENTITY nvsim            "&#x0223C;&#x020D2;" ><!--TILDE OPERATOR with vertical line -->
<!ENTITY nwArr            "&#x021D6;" ><!--NORTH WEST DOUBLE ARROW -->
<!ENTITY nwarhk           "&#x02923;" ><!--NORTH WEST ARROW WITH HOOK -->
<!ENTITY nwarr            "&#x02196;" ><!--NORTH WEST ARROW -->
<!ENTITY nwarrow          "&#x02196;" ><!--NORTH WEST ARROW -->
<!ENTITY nwnear           "&#x02927;" ><!--NORTH WEST ARROW AND NORTH EAST ARROW -->
<!ENTITY oS               "&#x024C8;" ><!--CIRCLED LATIN CAPITAL LETTER S -->
<!ENTITY oacute           "&#x000F3;" ><!--LATIN SMALL LETTER O WITH ACUTE -->
<!ENTITY oast             "&#x0229B;" ><!--CIRCLED ASTERISK OPERATOR -->
<!ENTITY ocir             "&#x0229A;" ><!--CIRCLED RING OPERATOR -->
<!ENTITY ocirc            "&#x000F4;" ><!--LATIN SMALL LETTER O WITH CIRCUMFLEX -->
<!ENTITY ocy              "&#x0043E;" ><!--CYRILLIC SMALL LETTER O -->
<!ENTITY odash            "&#x0229D;" ><!--CIRCLED DASH -->
<!ENTITY odblac           "&#x00151;" ><!--LATIN SMALL LETTER O WITH DOUBLE ACUTE -->
<!ENTITY odiv             "&#x02A38;" ><!--CIRCLED DIVISION SIGN -->
<!ENTITY odot             "&#x02299;" ><!--CIRCLED DOT OPERATOR -->
<!ENTITY odsold           "&#x029BC;" ><!--CIRCLED ANTICLOCKWISE-ROTATED DIVISION SIGN -->
<!ENTITY oelig            "&#x00153;" ><!--LATIN SMALL LIGATURE OE -->
<!ENTITY ofcir            "&#x029BF;" ><!--CIRCLED BULLET -->
<!ENTITY ofr              "&#x1D52C;" ><!--MATHEMATICAL FRAKTUR SMALL O -->
<!ENTITY ogon             "&#x002DB;" ><!--OGONEK -->
<!ENTITY ograve           "&#x000F2;" ><!--LATIN SMALL LETTER O WITH GRAVE -->
<!ENTITY ogt              "&#x029C1;" ><!--CIRCLED GREATER-THAN -->
<!ENTITY ohbar            "&#x029B5;" ><!--CIRCLE WITH HORIZONTAL BAR -->
<!ENTITY ohm              "&#x003A9;" ><!--GREEK CAPITAL LETTER OMEGA -->
<!ENTITY oint             "&#x0222E;" ><!--CONTOUR INTEGRAL -->
<!ENTITY olarr            "&#x021BA;" ><!--ANTICLOCKWISE OPEN CIRCLE ARROW -->
<!ENTITY olcir            "&#x029BE;" ><!--CIRCLED WHITE BULLET -->
<!ENTITY olcross          "&#x029BB;" ><!--CIRCLE WITH SUPERIMPOSED X -->
<!ENTITY oline            "&#x0203E;" ><!--OVERLINE -->
<!ENTITY olt              "&#x029C0;" ><!--CIRCLED LESS-THAN -->
<!ENTITY omacr            "&#x0014D;" ><!--LATIN SMALL LETTER O WITH MACRON -->
<!ENTITY omega            "&#x003C9;" ><!--GREEK SMALL LETTER OMEGA -->
<!ENTITY omicron          "&#x003BF;" ><!--GREEK SMALL LETTER OMICRON -->
<!ENTITY omid             "&#x029B6;" ><!--CIRCLED VERTICAL BAR -->
<!ENTITY ominus           "&#x02296;" ><!--CIRCLED MINUS -->
<!ENTITY oopf             "&#x1D560;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL O -->
<!ENTITY opar             "&#x029B7;" ><!--CIRCLED PARALLEL -->
<!ENTITY operp            "&#x029B9;" ><!--CIRCLED PERPENDICULAR -->
<!ENTITY oplus            "&#x02295;" ><!--CIRCLED PLUS -->
<!ENTITY or               "&#x02228;" ><!--LOGICAL OR -->
<!ENTITY orarr            "&#x021BB;" ><!--CLOCKWISE OPEN CIRCLE ARROW -->
<!ENTITY ord              "&#x02A5D;" ><!--LOGICAL OR WITH HORIZONTAL DASH -->
<!ENTITY order            "&#x02134;" ><!--SCRIPT SMALL O -->
<!ENTITY orderof          "&#x02134;" ><!--SCRIPT SMALL O -->
<!ENTITY ordf             "&#x000AA;" ><!--FEMININE ORDINAL INDICATOR -->
<!ENTITY ordm             "&#x000BA;" ><!--MASCULINE ORDINAL INDICATOR -->
<!ENTITY origof           "&#x022B6;" ><!--ORIGINAL OF -->
<!ENTITY oror             "&#x02A56;" ><!--TWO INTERSECTING LOGICAL OR -->
<!ENTITY orslope          "&#x02A57;" ><!--SLOPING LARGE OR -->
<!ENTITY orv              "&#x02A5B;" ><!--LOGICAL OR WITH MIDDLE STEM -->
<!ENTITY oscr             "&#x02134;" ><!--SCRIPT SMALL O -->
<!ENTITY oslash           "&#x000F8;" ><!--LATIN SMALL LETTER O WITH STROKE -->
<!ENTITY osol             "&#x02298;" ><!--CIRCLED DIVISION SLASH -->
<!ENTITY otilde           "&#x000F5;" ><!--LATIN SMALL LETTER O WITH TILDE -->
<!ENTITY otimes           "&#x02297;" ><!--CIRCLED TIMES -->
<!ENTITY otimesas         "&#x02A36;" ><!--CIRCLED MULTIPLICATION SIGN WITH CIRCUMFLEX ACCENT -->
<!ENTITY ouml             "&#x000F6;" ><!--LATIN SMALL LETTER O WITH DIAERESIS -->
<!ENTITY ovbar            "&#x0233D;" ><!--APL FUNCTIONAL SYMBOL CIRCLE STILE -->
<!ENTITY par              "&#x02225;" ><!--PARALLEL TO -->
<!ENTITY para             "&#x000B6;" ><!--PILCROW SIGN -->
<!ENTITY parallel         "&#x02225;" ><!--PARALLEL TO -->
<!ENTITY parsim           "&#x02AF3;" ><!--PARALLEL WITH TILDE OPERATOR -->
<!ENTITY parsl            "&#x02AFD;" ><!--DOUBLE SOLIDUS OPERATOR -->
<!ENTITY part             "&#x02202;" ><!--PARTIAL DIFFERENTIAL -->
<!ENTITY pcy              "&#x0043F;" ><!--CYRILLIC SMALL LETTER PE -->
<!ENTITY percnt           "&#x00025;" ><!--PERCENT SIGN -->
<!ENTITY period           "&#x0002E;" ><!--FULL STOP -->
<!ENTITY permil           "&#x02030;" ><!--PER MILLE SIGN -->
<!ENTITY perp             "&#x022A5;" ><!--UP TACK -->
<!ENTITY pertenk          "&#x02031;" ><!--PER TEN THOUSAND SIGN -->
<!ENTITY pfr              "&#x1D52D;" ><!--MATHEMATICAL FRAKTUR SMALL P -->
<!ENTITY phi              "&#x003C6;" ><!--GREEK SMALL LETTER PHI -->
<!ENTITY phiv             "&#x003D5;" ><!--GREEK PHI SYMBOL -->
<!ENTITY phmmat           "&#x02133;" ><!--SCRIPT CAPITAL M -->
<!ENTITY phone            "&#x0260E;" ><!--BLACK TELEPHONE -->
<!ENTITY pi               "&#x003C0;" ><!--GREEK SMALL LETTER PI -->
<!ENTITY pitchfork        "&#x022D4;" ><!--PITCHFORK -->
<!ENTITY piv              "&#x003D6;" ><!--GREEK PI SYMBOL -->
<!ENTITY planck           "&#x0210F;" ><!--PLANCK CONSTANT OVER TWO PI -->
<!ENTITY planckh          "&#x0210E;" ><!--PLANCK CONSTANT -->
<!ENTITY plankv           "&#x0210F;" ><!--PLANCK CONSTANT OVER TWO PI -->
<!ENTITY plus             "&#x0002B;" ><!--PLUS SIGN -->
<!ENTITY plusacir         "&#x02A23;" ><!--PLUS SIGN WITH CIRCUMFLEX ACCENT ABOVE -->
<!ENTITY plusb            "&#x0229E;" ><!--SQUARED PLUS -->
<!ENTITY pluscir          "&#x02A22;" ><!--PLUS SIGN WITH SMALL CIRCLE ABOVE -->
<!ENTITY plusdo           "&#x02214;" ><!--DOT PLUS -->
<!ENTITY plusdu           "&#x02A25;" ><!--PLUS SIGN WITH DOT BELOW -->
<!ENTITY pluse            "&#x02A72;" ><!--PLUS SIGN ABOVE EQUALS SIGN -->
<!ENTITY plusmn           "&#x000B1;" ><!--PLUS-MINUS SIGN -->
<!ENTITY plussim          "&#x02A26;" ><!--PLUS SIGN WITH TILDE BELOW -->
<!ENTITY plustwo          "&#x02A27;" ><!--PLUS SIGN WITH SUBSCRIPT TWO -->
<!ENTITY pm               "&#x000B1;" ><!--PLUS-MINUS SIGN -->
<!ENTITY pointint         "&#x02A15;" ><!--INTEGRAL AROUND A POINT OPERATOR -->
<!ENTITY popf             "&#x1D561;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL P -->
<!ENTITY pound            "&#x000A3;" ><!--POUND SIGN -->
<!ENTITY pr               "&#x0227A;" ><!--PRECEDES -->
<!ENTITY prE              "&#x02AB3;" ><!--PRECEDES ABOVE EQUALS SIGN -->
<!ENTITY prap             "&#x02AB7;" ><!--PRECEDES ABOVE ALMOST EQUAL TO -->
<!ENTITY prcue            "&#x0227C;" ><!--PRECEDES OR EQUAL TO -->
<!ENTITY pre              "&#x02AAF;" ><!--PRECEDES ABOVE SINGLE-LINE EQUALS SIGN -->
<!ENTITY prec             "&#x0227A;" ><!--PRECEDES -->
<!ENTITY precapprox       "&#x02AB7;" ><!--PRECEDES ABOVE ALMOST EQUAL TO -->
<!ENTITY preccurlyeq      "&#x0227C;" ><!--PRECEDES OR EQUAL TO -->
<!ENTITY preceq           "&#x02AAF;" ><!--PRECEDES ABOVE SINGLE-LINE EQUALS SIGN -->
<!ENTITY precnapprox      "&#x02AB9;" ><!--PRECEDES ABOVE NOT ALMOST EQUAL TO -->
<!ENTITY precneqq         "&#x02AB5;" ><!--PRECEDES ABOVE NOT EQUAL TO -->
<!ENTITY precnsim         "&#x022E8;" ><!--PRECEDES BUT NOT EQUIVALENT TO -->
<!ENTITY precsim          "&#x0227E;" ><!--PRECEDES OR EQUIVALENT TO -->
<!ENTITY prime            "&#x02032;" ><!--PRIME -->
<!ENTITY primes           "&#x02119;" ><!--DOUBLE-STRUCK CAPITAL P -->
<!ENTITY prnE             "&#x02AB5;" ><!--PRECEDES ABOVE NOT EQUAL TO -->
<!ENTITY prnap            "&#x02AB9;" ><!--PRECEDES ABOVE NOT ALMOST EQUAL TO -->
<!ENTITY prnsim           "&#x022E8;" ><!--PRECEDES BUT NOT EQUIVALENT TO -->
<!ENTITY prod             "&#x0220F;" ><!--N-ARY PRODUCT -->
<!ENTITY profalar         "&#x0232E;" ><!--ALL AROUND-PROFILE -->
<!ENTITY profline         "&#x02312;" ><!--ARC -->
<!ENTITY profsurf         "&#x02313;" ><!--SEGMENT -->
<!ENTITY prop             "&#x0221D;" ><!--PROPORTIONAL TO -->
<!ENTITY propto           "&#x0221D;" ><!--PROPORTIONAL TO -->
<!ENTITY prsim            "&#x0227E;" ><!--PRECEDES OR EQUIVALENT TO -->
<!ENTITY prurel           "&#x022B0;" ><!--PRECEDES UNDER RELATION -->
<!ENTITY pscr             "&#x1D4C5;" ><!--MATHEMATICAL SCRIPT SMALL P -->
<!ENTITY psi              "&#x003C8;" ><!--GREEK SMALL LETTER PSI -->
<!ENTITY puncsp           "&#x02008;" ><!--PUNCTUATION SPACE -->
<!ENTITY qfr              "&#x1D52E;" ><!--MATHEMATICAL FRAKTUR SMALL Q -->
<!ENTITY qint             "&#x02A0C;" ><!--QUADRUPLE INTEGRAL OPERATOR -->
<!ENTITY qopf             "&#x1D562;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL Q -->
<!ENTITY qprime           "&#x02057;" ><!--QUADRUPLE PRIME -->
<!ENTITY qscr             "&#x1D4C6;" ><!--MATHEMATICAL SCRIPT SMALL Q -->
<!ENTITY quaternions      "&#x0210D;" ><!--DOUBLE-STRUCK CAPITAL H -->
<!ENTITY quatint          "&#x02A16;" ><!--QUATERNION INTEGRAL OPERATOR -->
<!ENTITY quest            "&#x0003F;" ><!--QUESTION MARK -->
<!ENTITY questeq          "&#x0225F;" ><!--QUESTIONED EQUAL TO -->
<!ENTITY quot             "&#x00022;" ><!--QUOTATION MARK -->
<!ENTITY rAarr            "&#x021DB;" ><!--RIGHTWARDS TRIPLE ARROW -->
<!ENTITY rArr             "&#x021D2;" ><!--RIGHTWARDS DOUBLE ARROW -->
<!ENTITY rAtail           "&#x0291C;" ><!--RIGHTWARDS DOUBLE ARROW-TAIL -->
<!ENTITY rBarr            "&#x0290F;" ><!--RIGHTWARDS TRIPLE DASH ARROW -->
<!ENTITY rHar             "&#x02964;" ><!--RIGHTWARDS HARPOON WITH BARB UP ABOVE RIGHTWARDS HARPOON WITH BARB DOWN -->
<!ENTITY race             "&#x0223D;&#x00331;" ><!--REVERSED TILDE with underline -->
<!ENTITY racute           "&#x00155;" ><!--LATIN SMALL LETTER R WITH ACUTE -->
<!ENTITY radic            "&#x0221A;" ><!--SQUARE ROOT -->
<!ENTITY raemptyv         "&#x029B3;" ><!--EMPTY SET WITH RIGHT ARROW ABOVE -->
<!ENTITY rang             "&#x027E9;" ><!--MATHEMATICAL RIGHT ANGLE BRACKET -->
<!ENTITY rangd            "&#x02992;" ><!--RIGHT ANGLE BRACKET WITH DOT -->
<!ENTITY range            "&#x029A5;" ><!--REVERSED ANGLE WITH UNDERBAR -->
<!ENTITY rangle           "&#x027E9;" ><!--MATHEMATICAL RIGHT ANGLE BRACKET -->
<!ENTITY raquo            "&#x000BB;" ><!--RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK -->
<!ENTITY rarr             "&#x02192;" ><!--RIGHTWARDS ARROW -->
<!ENTITY rarrap           "&#x02975;" ><!--RIGHTWARDS ARROW ABOVE ALMOST EQUAL TO -->
<!ENTITY rarrb            "&#x021E5;" ><!--RIGHTWARDS ARROW TO BAR -->
<!ENTITY rarrbfs          "&#x02920;" ><!--RIGHTWARDS ARROW FROM BAR TO BLACK DIAMOND -->
<!ENTITY rarrc            "&#x02933;" ><!--WAVE ARROW POINTING DIRECTLY RIGHT -->
<!ENTITY rarrfs           "&#x0291E;" ><!--RIGHTWARDS ARROW TO BLACK DIAMOND -->
<!ENTITY rarrhk           "&#x021AA;" ><!--RIGHTWARDS ARROW WITH HOOK -->
<!ENTITY rarrlp           "&#x021AC;" ><!--RIGHTWARDS ARROW WITH LOOP -->
<!ENTITY rarrpl           "&#x02945;" ><!--RIGHTWARDS ARROW WITH PLUS BELOW -->
<!ENTITY rarrsim          "&#x02974;" ><!--RIGHTWARDS ARROW ABOVE TILDE OPERATOR -->
<!ENTITY rarrtl           "&#x021A3;" ><!--RIGHTWARDS ARROW WITH TAIL -->
<!ENTITY rarrw            "&#x0219D;" ><!--RIGHTWARDS WAVE ARROW -->
<!ENTITY ratail           "&#x0291A;" ><!--RIGHTWARDS ARROW-TAIL -->
<!ENTITY ratio            "&#x02236;" ><!--RATIO -->
<!ENTITY rationals        "&#x0211A;" ><!--DOUBLE-STRUCK CAPITAL Q -->
<!ENTITY rbarr            "&#x0290D;" ><!--RIGHTWARDS DOUBLE DASH ARROW -->
<!ENTITY rbbrk            "&#x02773;" ><!--LIGHT RIGHT TORTOISE SHELL BRACKET ORNAMENT -->
<!ENTITY rbrace           "&#x0007D;" ><!--RIGHT CURLY BRACKET -->
<!ENTITY rbrack           "&#x0005D;" ><!--RIGHT SQUARE BRACKET -->
<!ENTITY rbrke            "&#x0298C;" ><!--RIGHT SQUARE BRACKET WITH UNDERBAR -->
<!ENTITY rbrksld          "&#x0298E;" ><!--RIGHT SQUARE BRACKET WITH TICK IN BOTTOM CORNER -->
<!ENTITY rbrkslu          "&#x02990;" ><!--RIGHT SQUARE BRACKET WITH TICK IN TOP CORNER -->
<!ENTITY rcaron           "&#x00159;" ><!--LATIN SMALL LETTER R WITH CARON -->
<!ENTITY rcedil           "&#x00157;" ><!--LATIN SMALL LETTER R WITH CEDILLA -->
<!ENTITY rceil            "&#x02309;" ><!--RIGHT CEILING -->
<!ENTITY rcub             "&#x0007D;" ><!--RIGHT CURLY BRACKET -->
<!ENTITY rcy              "&#x00440;" ><!--CYRILLIC SMALL LETTER ER -->
<!ENTITY rdca             "&#x02937;" ><!--ARROW POINTING DOWNWARDS THEN CURVING RIGHTWARDS -->
<!ENTITY rdldhar          "&#x02969;" ><!--RIGHTWARDS HARPOON WITH BARB DOWN ABOVE LEFTWARDS HARPOON WITH BARB DOWN -->
<!ENTITY rdquo            "&#x0201D;" ><!--RIGHT DOUBLE QUOTATION MARK -->
<!ENTITY rdquor           "&#x0201D;" ><!--RIGHT DOUBLE QUOTATION MARK -->
<!ENTITY rdsh             "&#x021B3;" ><!--DOWNWARDS ARROW WITH TIP RIGHTWARDS -->
<!ENTITY real             "&#x0211C;" ><!--BLACK-LETTER CAPITAL R -->
<!ENTITY realine          "&#x0211B;" ><!--SCRIPT CAPITAL R -->
<!ENTITY realpart         "&#x0211C;" ><!--BLACK-LETTER CAPITAL R -->
<!ENTITY reals            "&#x0211D;" ><!--DOUBLE-STRUCK CAPITAL R -->
<!ENTITY rect             "&#x025AD;" ><!--WHITE RECTANGLE -->
<!ENTITY reg              "&#x000AE;" ><!--REGISTERED SIGN -->
<!ENTITY rfisht           "&#x0297D;" ><!--RIGHT FISH TAIL -->
<!ENTITY rfloor           "&#x0230B;" ><!--RIGHT FLOOR -->
<!ENTITY rfr              "&#x1D52F;" ><!--MATHEMATICAL FRAKTUR SMALL R -->
<!ENTITY rhard            "&#x021C1;" ><!--RIGHTWARDS HARPOON WITH BARB DOWNWARDS -->
<!ENTITY rharu            "&#x021C0;" ><!--RIGHTWARDS HARPOON WITH BARB UPWARDS -->
<!ENTITY rharul           "&#x0296C;" ><!--RIGHTWARDS HARPOON WITH BARB UP ABOVE LONG DASH -->
<!ENTITY rho              "&#x003C1;" ><!--GREEK SMALL LETTER RHO -->
<!ENTITY rhov             "&#x003F1;" ><!--GREEK RHO SYMBOL -->
<!ENTITY rightarrow       "&#x02192;" ><!--RIGHTWARDS ARROW -->
<!ENTITY rightarrowtail   "&#x021A3;" ><!--RIGHTWARDS ARROW WITH TAIL -->
<!ENTITY rightharpoondown "&#x021C1;" ><!--RIGHTWARDS HARPOON WITH BARB DOWNWARDS -->
<!ENTITY rightharpoonup   "&#x021C0;" ><!--RIGHTWARDS HARPOON WITH BARB UPWARDS -->
<!ENTITY rightleftarrows  "&#x021C4;" ><!--RIGHTWARDS ARROW OVER LEFTWARDS ARROW -->
<!ENTITY rightleftharpoons "&#x021CC;" ><!--RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON -->
<!ENTITY rightrightarrows "&#x021C9;" ><!--RIGHTWARDS PAIRED ARROWS -->
<!ENTITY rightsquigarrow  "&#x0219D;" ><!--RIGHTWARDS WAVE ARROW -->
<!ENTITY rightthreetimes  "&#x022CC;" ><!--RIGHT SEMIDIRECT PRODUCT -->
<!ENTITY ring             "&#x002DA;" ><!--RING ABOVE -->
<!ENTITY risingdotseq     "&#x02253;" ><!--IMAGE OF OR APPROXIMATELY EQUAL TO -->
<!ENTITY rlarr            "&#x021C4;" ><!--RIGHTWARDS ARROW OVER LEFTWARDS ARROW -->
<!ENTITY rlhar            "&#x021CC;" ><!--RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON -->
<!ENTITY rlm              "&#x0200F;" ><!--RIGHT-TO-LEFT MARK -->
<!ENTITY rmoust           "&#x023B1;" ><!--UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION -->
<!ENTITY rmoustache       "&#x023B1;" ><!--UPPER RIGHT OR LOWER LEFT CURLY BRACKET SECTION -->
<!ENTITY rnmid            "&#x02AEE;" ><!--DOES NOT DIVIDE WITH REVERSED NEGATION SLASH -->
<!ENTITY roang            "&#x027ED;" ><!--MATHEMATICAL RIGHT WHITE TORTOISE SHELL BRACKET -->
<!ENTITY roarr            "&#x021FE;" ><!--RIGHTWARDS OPEN-HEADED ARROW -->
<!ENTITY robrk            "&#x027E7;" ><!--MATHEMATICAL RIGHT WHITE SQUARE BRACKET -->
<!ENTITY ropar            "&#x02986;" ><!--RIGHT WHITE PARENTHESIS -->
<!ENTITY ropf             "&#x1D563;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL R -->
<!ENTITY roplus           "&#x02A2E;" ><!--PLUS SIGN IN RIGHT HALF CIRCLE -->
<!ENTITY rotimes          "&#x02A35;" ><!--MULTIPLICATION SIGN IN RIGHT HALF CIRCLE -->
<!ENTITY rpar             "&#x00029;" ><!--RIGHT PARENTHESIS -->
<!ENTITY rpargt           "&#x02994;" ><!--RIGHT ARC GREATER-THAN BRACKET -->
<!ENTITY rppolint         "&#x02A12;" ><!--LINE INTEGRATION WITH RECTANGULAR PATH AROUND POLE -->
<!ENTITY rrarr            "&#x021C9;" ><!--RIGHTWARDS PAIRED ARROWS -->
<!ENTITY rsaquo           "&#x0203A;" ><!--SINGLE RIGHT-POINTING ANGLE QUOTATION MARK -->
<!ENTITY rscr             "&#x1D4C7;" ><!--MATHEMATICAL SCRIPT SMALL R -->
<!ENTITY rsh              "&#x021B1;" ><!--UPWARDS ARROW WITH TIP RIGHTWARDS -->
<!ENTITY rsqb             "&#x0005D;" ><!--RIGHT SQUARE BRACKET -->
<!ENTITY rsquo            "&#x02019;" ><!--RIGHT SINGLE QUOTATION MARK -->
<!ENTITY rsquor           "&#x02019;" ><!--RIGHT SINGLE QUOTATION MARK -->
<!ENTITY rthree           "&#x022CC;" ><!--RIGHT SEMIDIRECT PRODUCT -->
<!ENTITY rtimes           "&#x022CA;" ><!--RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT -->
<!ENTITY rtri             "&#x025B9;" ><!--WHITE RIGHT-POINTING SMALL TRIANGLE -->
<!ENTITY rtrie            "&#x022B5;" ><!--CONTAINS AS NORMAL SUBGROUP OR EQUAL TO -->
<!ENTITY rtrif            "&#x025B8;" ><!--BLACK RIGHT-POINTING SMALL TRIANGLE -->
<!ENTITY rtriltri         "&#x029CE;" ><!--RIGHT TRIANGLE ABOVE LEFT TRIANGLE -->
<!ENTITY ruluhar          "&#x02968;" ><!--RIGHTWARDS HARPOON WITH BARB UP ABOVE LEFTWARDS HARPOON WITH BARB UP -->
<!ENTITY rx               "&#x0211E;" ><!--PRESCRIPTION TAKE -->
<!ENTITY sacute           "&#x0015B;" ><!--LATIN SMALL LETTER S WITH ACUTE -->
<!ENTITY sbquo            "&#x0201A;" ><!--SINGLE LOW-9 QUOTATION MARK -->
<!ENTITY sc               "&#x0227B;" ><!--SUCCEEDS -->
<!ENTITY scE              "&#x02AB4;" ><!--SUCCEEDS ABOVE EQUALS SIGN -->
<!ENTITY scap             "&#x02AB8;" ><!--SUCCEEDS ABOVE ALMOST EQUAL TO -->
<!ENTITY scaron           "&#x00161;" ><!--LATIN SMALL LETTER S WITH CARON -->
<!ENTITY sccue            "&#x0227D;" ><!--SUCCEEDS OR EQUAL TO -->
<!ENTITY sce              "&#x02AB0;" ><!--SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN -->
<!ENTITY scedil           "&#x0015F;" ><!--LATIN SMALL LETTER S WITH CEDILLA -->
<!ENTITY scirc            "&#x0015D;" ><!--LATIN SMALL LETTER S WITH CIRCUMFLEX -->
<!ENTITY scnE             "&#x02AB6;" ><!--SUCCEEDS ABOVE NOT EQUAL TO -->
<!ENTITY scnap            "&#x02ABA;" ><!--SUCCEEDS ABOVE NOT ALMOST EQUAL TO -->
<!ENTITY scnsim           "&#x022E9;" ><!--SUCCEEDS BUT NOT EQUIVALENT TO -->
<!ENTITY scpolint         "&#x02A13;" ><!--LINE INTEGRATION WITH SEMICIRCULAR PATH AROUND POLE -->
<!ENTITY scsim            "&#x0227F;" ><!--SUCCEEDS OR EQUIVALENT TO -->
<!ENTITY scy              "&#x00441;" ><!--CYRILLIC SMALL LETTER ES -->
<!ENTITY sdot             "&#x022C5;" ><!--DOT OPERATOR -->
<!ENTITY sdotb            "&#x022A1;" ><!--SQUARED DOT OPERATOR -->
<!ENTITY sdote            "&#x02A66;" ><!--EQUALS SIGN WITH DOT BELOW -->
<!ENTITY seArr            "&#x021D8;" ><!--SOUTH EAST DOUBLE ARROW -->
<!ENTITY searhk           "&#x02925;" ><!--SOUTH EAST ARROW WITH HOOK -->
<!ENTITY searr            "&#x02198;" ><!--SOUTH EAST ARROW -->
<!ENTITY searrow          "&#x02198;" ><!--SOUTH EAST ARROW -->
<!ENTITY sect             "&#x000A7;" ><!--SECTION SIGN -->
<!ENTITY semi             "&#x0003B;" ><!--SEMICOLON -->
<!ENTITY seswar           "&#x02929;" ><!--SOUTH EAST ARROW AND SOUTH WEST ARROW -->
<!ENTITY setminus         "&#x02216;" ><!--SET MINUS -->
<!ENTITY setmn            "&#x02216;" ><!--SET MINUS -->
<!ENTITY sext             "&#x02736;" ><!--SIX POINTED BLACK STAR -->
<!ENTITY sfr              "&#x1D530;" ><!--MATHEMATICAL FRAKTUR SMALL S -->
<!ENTITY sfrown           "&#x02322;" ><!--FROWN -->
<!ENTITY sharp            "&#x0266F;" ><!--MUSIC SHARP SIGN -->
<!ENTITY shchcy           "&#x00449;" ><!--CYRILLIC SMALL LETTER SHCHA -->
<!ENTITY shcy             "&#x00448;" ><!--CYRILLIC SMALL LETTER SHA -->
<!ENTITY shortmid         "&#x02223;" ><!--DIVIDES -->
<!ENTITY shortparallel    "&#x02225;" ><!--PARALLEL TO -->
<!ENTITY shy              "&#x000AD;" ><!--SOFT HYPHEN -->
<!ENTITY sigma            "&#x003C3;" ><!--GREEK SMALL LETTER SIGMA -->
<!ENTITY sigmaf           "&#x003C2;" ><!--GREEK SMALL LETTER FINAL SIGMA -->
<!ENTITY sigmav           "&#x003C2;" ><!--GREEK SMALL LETTER FINAL SIGMA -->
<!ENTITY sim              "&#x0223C;" ><!--TILDE OPERATOR -->
<!ENTITY simdot           "&#x02A6A;" ><!--TILDE OPERATOR WITH DOT ABOVE -->
<!ENTITY sime             "&#x02243;" ><!--ASYMPTOTICALLY EQUAL TO -->
<!ENTITY simeq            "&#x02243;" ><!--ASYMPTOTICALLY EQUAL TO -->
<!ENTITY simg             "&#x02A9E;" ><!--SIMILAR OR GREATER-THAN -->
<!ENTITY simgE            "&#x02AA0;" ><!--SIMILAR ABOVE GREATER-THAN ABOVE EQUALS SIGN -->
<!ENTITY siml             "&#x02A9D;" ><!--SIMILAR OR LESS-THAN -->
<!ENTITY simlE            "&#x02A9F;" ><!--SIMILAR ABOVE LESS-THAN ABOVE EQUALS SIGN -->
<!ENTITY simne            "&#x02246;" ><!--APPROXIMATELY BUT NOT ACTUALLY EQUAL TO -->
<!ENTITY simplus          "&#x02A24;" ><!--PLUS SIGN WITH TILDE ABOVE -->
<!ENTITY simrarr          "&#x02972;" ><!--TILDE OPERATOR ABOVE RIGHTWARDS ARROW -->
<!ENTITY slarr            "&#x02190;" ><!--LEFTWARDS ARROW -->
<!ENTITY smallsetminus    "&#x02216;" ><!--SET MINUS -->
<!ENTITY smashp           "&#x02A33;" ><!--SMASH PRODUCT -->
<!ENTITY smeparsl         "&#x029E4;" ><!--EQUALS SIGN AND SLANTED PARALLEL WITH TILDE ABOVE -->
<!ENTITY smid             "&#x02223;" ><!--DIVIDES -->
<!ENTITY smile            "&#x02323;" ><!--SMILE -->
<!ENTITY smt              "&#x02AAA;" ><!--SMALLER THAN -->
<!ENTITY smte             "&#x02AAC;" ><!--SMALLER THAN OR EQUAL TO -->
<!ENTITY smtes            "&#x02AAC;&#x0FE00;" ><!--SMALLER THAN OR slanted EQUAL -->
<!ENTITY softcy           "&#x0044C;" ><!--CYRILLIC SMALL LETTER SOFT SIGN -->
<!ENTITY sol              "&#x0002F;" ><!--SOLIDUS -->
<!ENTITY solb             "&#x029C4;" ><!--SQUARED RISING DIAGONAL SLASH -->
<!ENTITY solbar           "&#x0233F;" ><!--APL FUNCTIONAL SYMBOL SLASH BAR -->
<!ENTITY sopf             "&#x1D564;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL S -->
<!ENTITY spades           "&#x02660;" ><!--BLACK SPADE SUIT -->
<!ENTITY spadesuit        "&#x02660;" ><!--BLACK SPADE SUIT -->
<!ENTITY spar             "&#x02225;" ><!--PARALLEL TO -->
<!ENTITY sqcap            "&#x02293;" ><!--SQUARE CAP -->
<!ENTITY sqcaps           "&#x02293;&#x0FE00;" ><!--SQUARE CAP with serifs -->
<!ENTITY sqcup            "&#x02294;" ><!--SQUARE CUP -->
<!ENTITY sqcups           "&#x02294;&#x0FE00;" ><!--SQUARE CUP with serifs -->
<!ENTITY sqsub            "&#x0228F;" ><!--SQUARE IMAGE OF -->
<!ENTITY sqsube           "&#x02291;" ><!--SQUARE IMAGE OF OR EQUAL TO -->
<!ENTITY sqsubset         "&#x0228F;" ><!--SQUARE IMAGE OF -->
<!ENTITY sqsubseteq       "&#x02291;" ><!--SQUARE IMAGE OF OR EQUAL TO -->
<!ENTITY sqsup            "&#x02290;" ><!--SQUARE ORIGINAL OF -->
<!ENTITY sqsupe           "&#x02292;" ><!--SQUARE ORIGINAL OF OR EQUAL TO -->
<!ENTITY sqsupset         "&#x02290;" ><!--SQUARE ORIGINAL OF -->
<!ENTITY sqsupseteq       "&#x02292;" ><!--SQUARE ORIGINAL OF OR EQUAL TO -->
<!ENTITY squ              "&#x025A1;" ><!--WHITE SQUARE -->
<!ENTITY square           "&#x025A1;" ><!--WHITE SQUARE -->
<!ENTITY squarf           "&#x025AA;" ><!--BLACK SMALL SQUARE -->
<!ENTITY squf             "&#x025AA;" ><!--BLACK SMALL SQUARE -->
<!ENTITY srarr            "&#x02192;" ><!--RIGHTWARDS ARROW -->
<!ENTITY sscr             "&#x1D4C8;" ><!--MATHEMATICAL SCRIPT SMALL S -->
<!ENTITY ssetmn           "&#x02216;" ><!--SET MINUS -->
<!ENTITY ssmile           "&#x02323;" ><!--SMILE -->
<!ENTITY sstarf           "&#x022C6;" ><!--STAR OPERATOR -->
<!ENTITY star             "&#x02606;" ><!--WHITE STAR -->
<!ENTITY starf            "&#x02605;" ><!--BLACK STAR -->
<!ENTITY straightepsilon  "&#x003F5;" ><!--GREEK LUNATE EPSILON SYMBOL -->
<!ENTITY straightphi      "&#x003D5;" ><!--GREEK PHI SYMBOL -->
<!ENTITY strns            "&#x000AF;" ><!--MACRON -->
<!ENTITY sub              "&#x02282;" ><!--SUBSET OF -->
<!ENTITY subE             "&#x02AC5;" ><!--SUBSET OF ABOVE EQUALS SIGN -->
<!ENTITY subdot           "&#x02ABD;" ><!--SUBSET WITH DOT -->
<!ENTITY sube             "&#x02286;" ><!--SUBSET OF OR EQUAL TO -->
<!ENTITY subedot          "&#x02AC3;" ><!--SUBSET OF OR EQUAL TO WITH DOT ABOVE -->
<!ENTITY submult          "&#x02AC1;" ><!--SUBSET WITH MULTIPLICATION SIGN BELOW -->
<!ENTITY subnE            "&#x02ACB;" ><!--SUBSET OF ABOVE NOT EQUAL TO -->
<!ENTITY subne            "&#x0228A;" ><!--SUBSET OF WITH NOT EQUAL TO -->
<!ENTITY subplus          "&#x02ABF;" ><!--SUBSET WITH PLUS SIGN BELOW -->
<!ENTITY subrarr          "&#x02979;" ><!--SUBSET ABOVE RIGHTWARDS ARROW -->
<!ENTITY subset           "&#x02282;" ><!--SUBSET OF -->
<!ENTITY subseteq         "&#x02286;" ><!--SUBSET OF OR EQUAL TO -->
<!ENTITY subseteqq        "&#x02AC5;" ><!--SUBSET OF ABOVE EQUALS SIGN -->
<!ENTITY subsetneq        "&#x0228A;" ><!--SUBSET OF WITH NOT EQUAL TO -->
<!ENTITY subsetneqq       "&#x02ACB;" ><!--SUBSET OF ABOVE NOT EQUAL TO -->
<!ENTITY subsim           "&#x02AC7;" ><!--SUBSET OF ABOVE TILDE OPERATOR -->
<!ENTITY subsub           "&#x02AD5;" ><!--SUBSET ABOVE SUBSET -->
<!ENTITY subsup           "&#x02AD3;" ><!--SUBSET ABOVE SUPERSET -->
<!ENTITY succ             "&#x0227B;" ><!--SUCCEEDS -->
<!ENTITY succapprox       "&#x02AB8;" ><!--SUCCEEDS ABOVE ALMOST EQUAL TO -->
<!ENTITY succcurlyeq      "&#x0227D;" ><!--SUCCEEDS OR EQUAL TO -->
<!ENTITY succeq           "&#x02AB0;" ><!--SUCCEEDS ABOVE SINGLE-LINE EQUALS SIGN -->
<!ENTITY succnapprox      "&#x02ABA;" ><!--SUCCEEDS ABOVE NOT ALMOST EQUAL TO -->
<!ENTITY succneqq         "&#x02AB6;" ><!--SUCCEEDS ABOVE NOT EQUAL TO -->
<!ENTITY succnsim         "&#x022E9;" ><!--SUCCEEDS BUT NOT EQUIVALENT TO -->
<!ENTITY succsim          "&#x0227F;" ><!--SUCCEEDS OR EQUIVALENT TO -->
<!ENTITY sum              "&#x02211;" ><!--N-ARY SUMMATION -->
<!ENTITY sung             "&#x0266A;" ><!--EIGHTH NOTE -->
<!ENTITY sup              "&#x02283;" ><!--SUPERSET OF -->
<!ENTITY sup1             "&#x000B9;" ><!--SUPERSCRIPT ONE -->
<!ENTITY sup2             "&#x000B2;" ><!--SUPERSCRIPT TWO -->
<!ENTITY sup3             "&#x000B3;" ><!--SUPERSCRIPT THREE -->
<!ENTITY supE             "&#x02AC6;" ><!--SUPERSET OF ABOVE EQUALS SIGN -->
<!ENTITY supdot           "&#x02ABE;" ><!--SUPERSET WITH DOT -->
<!ENTITY supdsub          "&#x02AD8;" ><!--SUPERSET BESIDE AND JOINED BY DASH WITH SUBSET -->
<!ENTITY supe             "&#x02287;" ><!--SUPERSET OF OR EQUAL TO -->
<!ENTITY supedot          "&#x02AC4;" ><!--SUPERSET OF OR EQUAL TO WITH DOT ABOVE -->
<!ENTITY suphsol          "&#x027C9;" ><!--SUPERSET PRECEDING SOLIDUS -->
<!ENTITY suphsub          "&#x02AD7;" ><!--SUPERSET BESIDE SUBSET -->
<!ENTITY suplarr          "&#x0297B;" ><!--SUPERSET ABOVE LEFTWARDS ARROW -->
<!ENTITY supmult          "&#x02AC2;" ><!--SUPERSET WITH MULTIPLICATION SIGN BELOW -->
<!ENTITY supnE            "&#x02ACC;" ><!--SUPERSET OF ABOVE NOT EQUAL TO -->
<!ENTITY supne            "&#x0228B;" ><!--SUPERSET OF WITH NOT EQUAL TO -->
<!ENTITY supplus          "&#x02AC0;" ><!--SUPERSET WITH PLUS SIGN BELOW -->
<!ENTITY supset           "&#x02283;" ><!--SUPERSET OF -->
<!ENTITY supseteq         "&#x02287;" ><!--SUPERSET OF OR EQUAL TO -->
<!ENTITY supseteqq        "&#x02AC6;" ><!--SUPERSET OF ABOVE EQUALS SIGN -->
<!ENTITY supsetneq        "&#x0228B;" ><!--SUPERSET OF WITH NOT EQUAL TO -->
<!ENTITY supsetneqq       "&#x02ACC;" ><!--SUPERSET OF ABOVE NOT EQUAL TO -->
<!ENTITY supsim           "&#x02AC8;" ><!--SUPERSET OF ABOVE TILDE OPERATOR -->
<!ENTITY supsub           "&#x02AD4;" ><!--SUPERSET ABOVE SUBSET -->
<!ENTITY supsup           "&#x02AD6;" ><!--SUPERSET ABOVE SUPERSET -->
<!ENTITY swArr            "&#x021D9;" ><!--SOUTH WEST DOUBLE ARROW -->
<!ENTITY swarhk           "&#x02926;" ><!--SOUTH WEST ARROW WITH HOOK -->
<!ENTITY swarr            "&#x02199;" ><!--SOUTH WEST ARROW -->
<!ENTITY swarrow          "&#x02199;" ><!--SOUTH WEST ARROW -->
<!ENTITY swnwar           "&#x0292A;" ><!--SOUTH WEST ARROW AND NORTH WEST ARROW -->
<!ENTITY szlig            "&#x000DF;" ><!--LATIN SMALL LETTER SHARP S -->
<!ENTITY target           "&#x02316;" ><!--POSITION INDICATOR -->
<!ENTITY tau              "&#x003C4;" ><!--GREEK SMALL LETTER TAU -->
<!ENTITY tbrk             "&#x023B4;" ><!--TOP SQUARE BRACKET -->
<!ENTITY tcaron           "&#x00165;" ><!--LATIN SMALL LETTER T WITH CARON -->
<!ENTITY tcedil           "&#x00163;" ><!--LATIN SMALL LETTER T WITH CEDILLA -->
<!ENTITY tcy              "&#x00442;" ><!--CYRILLIC SMALL LETTER TE -->
<!ENTITY tdot             "&#x020DB;" ><!--COMBINING THREE DOTS ABOVE -->
<!ENTITY telrec           "&#x02315;" ><!--TELEPHONE RECORDER -->
<!ENTITY tfr              "&#x1D531;" ><!--MATHEMATICAL FRAKTUR SMALL T -->
<!ENTITY there4           "&#x02234;" ><!--THEREFORE -->
<!ENTITY therefore        "&#x02234;" ><!--THEREFORE -->
<!ENTITY theta            "&#x003B8;" ><!--GREEK SMALL LETTER THETA -->
<!ENTITY thetasym         "&#x003D1;" ><!--GREEK THETA SYMBOL -->
<!ENTITY thetav           "&#x003D1;" ><!--GREEK THETA SYMBOL -->
<!ENTITY thickapprox      "&#x02248;" ><!--ALMOST EQUAL TO -->
<!ENTITY thicksim         "&#x0223C;" ><!--TILDE OPERATOR -->
<!ENTITY thinsp           "&#x02009;" ><!--THIN SPACE -->
<!ENTITY thkap            "&#x02248;" ><!--ALMOST EQUAL TO -->
<!ENTITY thksim           "&#x0223C;" ><!--TILDE OPERATOR -->
<!ENTITY thorn            "&#x000FE;" ><!--LATIN SMALL LETTER THORN -->
<!ENTITY tilde            "&#x002DC;" ><!--SMALL TILDE -->
<!ENTITY times            "&#x000D7;" ><!--MULTIPLICATION SIGN -->
<!ENTITY timesb           "&#x022A0;" ><!--SQUARED TIMES -->
<!ENTITY timesbar         "&#x02A31;" ><!--MULTIPLICATION SIGN WITH UNDERBAR -->
<!ENTITY timesd           "&#x02A30;" ><!--MULTIPLICATION SIGN WITH DOT ABOVE -->
<!ENTITY tint             "&#x0222D;" ><!--TRIPLE INTEGRAL -->
<!ENTITY toea             "&#x02928;" ><!--NORTH EAST ARROW AND SOUTH EAST ARROW -->
<!ENTITY top              "&#x022A4;" ><!--DOWN TACK -->
<!ENTITY topbot           "&#x02336;" ><!--APL FUNCTIONAL SYMBOL I-BEAM -->
<!ENTITY topcir           "&#x02AF1;" ><!--DOWN TACK WITH CIRCLE BELOW -->
<!ENTITY topf             "&#x1D565;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL T -->
<!ENTITY topfork          "&#x02ADA;" ><!--PITCHFORK WITH TEE TOP -->
<!ENTITY tosa             "&#x02929;" ><!--SOUTH EAST ARROW AND SOUTH WEST ARROW -->
<!ENTITY tprime           "&#x02034;" ><!--TRIPLE PRIME -->
<!ENTITY trade            "&#x02122;" ><!--TRADE MARK SIGN -->
<!ENTITY triangle         "&#x025B5;" ><!--WHITE UP-POINTING SMALL TRIANGLE -->
<!ENTITY triangledown     "&#x025BF;" ><!--WHITE DOWN-POINTING SMALL TRIANGLE -->
<!ENTITY triangleleft     "&#x025C3;" ><!--WHITE LEFT-POINTING SMALL TRIANGLE -->
<!ENTITY trianglelefteq   "&#x022B4;" ><!--NORMAL SUBGROUP OF OR EQUAL TO -->
<!ENTITY triangleq        "&#x0225C;" ><!--DELTA EQUAL TO -->
<!ENTITY triangleright    "&#x025B9;" ><!--WHITE RIGHT-POINTING SMALL TRIANGLE -->
<!ENTITY trianglerighteq  "&#x022B5;" ><!--CONTAINS AS NORMAL SUBGROUP OR EQUAL TO -->
<!ENTITY tridot           "&#x025EC;" ><!--WHITE UP-POINTING TRIANGLE WITH DOT -->
<!ENTITY trie             "&#x0225C;" ><!--DELTA EQUAL TO -->
<!ENTITY triminus         "&#x02A3A;" ><!--MINUS SIGN IN TRIANGLE -->
<!ENTITY triplus          "&#x02A39;" ><!--PLUS SIGN IN TRIANGLE -->
<!ENTITY trisb            "&#x029CD;" ><!--TRIANGLE WITH SERIFS AT BOTTOM -->
<!ENTITY tritime          "&#x02A3B;" ><!--MULTIPLICATION SIGN IN TRIANGLE -->
<!ENTITY trpezium         "&#x023E2;" ><!--WHITE TRAPEZIUM -->
<!ENTITY tscr             "&#x1D4C9;" ><!--MATHEMATICAL SCRIPT SMALL T -->
<!ENTITY tscy             "&#x00446;" ><!--CYRILLIC SMALL LETTER TSE -->
<!ENTITY tshcy            "&#x0045B;" ><!--CYRILLIC SMALL LETTER TSHE -->
<!ENTITY tstrok           "&#x00167;" ><!--LATIN SMALL LETTER T WITH STROKE -->
<!ENTITY twixt            "&#x0226C;" ><!--BETWEEN -->
<!ENTITY twoheadleftarrow "&#x0219E;" ><!--LEFTWARDS TWO HEADED ARROW -->
<!ENTITY twoheadrightarrow "&#x021A0;" ><!--RIGHTWARDS TWO HEADED ARROW -->
<!ENTITY uArr             "&#x021D1;" ><!--UPWARDS DOUBLE ARROW -->
<!ENTITY uHar             "&#x02963;" ><!--UPWARDS HARPOON WITH BARB LEFT BESIDE UPWARDS HARPOON WITH BARB RIGHT -->
<!ENTITY uacute           "&#x000FA;" ><!--LATIN SMALL LETTER U WITH ACUTE -->
<!ENTITY uarr             "&#x02191;" ><!--UPWARDS ARROW -->
<!ENTITY ubrcy            "&#x0045E;" ><!--CYRILLIC SMALL LETTER SHORT U -->
<!ENTITY ubreve           "&#x0016D;" ><!--LATIN SMALL LETTER U WITH BREVE -->
<!ENTITY ucirc            "&#x000FB;" ><!--LATIN SMALL LETTER U WITH CIRCUMFLEX -->
<!ENTITY ucy              "&#x00443;" ><!--CYRILLIC SMALL LETTER U -->
<!ENTITY udarr            "&#x021C5;" ><!--UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW -->
<!ENTITY udblac           "&#x00171;" ><!--LATIN SMALL LETTER U WITH DOUBLE ACUTE -->
<!ENTITY udhar            "&#x0296E;" ><!--UPWARDS HARPOON WITH BARB LEFT BESIDE DOWNWARDS HARPOON WITH BARB RIGHT -->
<!ENTITY ufisht           "&#x0297E;" ><!--UP FISH TAIL -->
<!ENTITY ufr              "&#x1D532;" ><!--MATHEMATICAL FRAKTUR SMALL U -->
<!ENTITY ugrave           "&#x000F9;" ><!--LATIN SMALL LETTER U WITH GRAVE -->
<!ENTITY uharl            "&#x021BF;" ><!--UPWARDS HARPOON WITH BARB LEFTWARDS -->
<!ENTITY uharr            "&#x021BE;" ><!--UPWARDS HARPOON WITH BARB RIGHTWARDS -->
<!ENTITY uhblk            "&#x02580;" ><!--UPPER HALF BLOCK -->
<!ENTITY ulcorn           "&#x0231C;" ><!--TOP LEFT CORNER -->
<!ENTITY ulcorner         "&#x0231C;" ><!--TOP LEFT CORNER -->
<!ENTITY ulcrop           "&#x0230F;" ><!--TOP LEFT CROP -->
<!ENTITY ultri            "&#x025F8;" ><!--UPPER LEFT TRIANGLE -->
<!ENTITY umacr            "&#x0016B;" ><!--LATIN SMALL LETTER U WITH MACRON -->
<!ENTITY uml              "&#x000A8;" ><!--DIAERESIS -->
<!ENTITY uogon            "&#x00173;" ><!--LATIN SMALL LETTER U WITH OGONEK -->
<!ENTITY uopf             "&#x1D566;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL U -->
<!ENTITY uparrow          "&#x02191;" ><!--UPWARDS ARROW -->
<!ENTITY updownarrow      "&#x02195;" ><!--UP DOWN ARROW -->
<!ENTITY upharpoonleft    "&#x021BF;" ><!--UPWARDS HARPOON WITH BARB LEFTWARDS -->
<!ENTITY upharpoonright   "&#x021BE;" ><!--UPWARDS HARPOON WITH BARB RIGHTWARDS -->
<!ENTITY uplus            "&#x0228E;" ><!--MULTISET UNION -->
<!ENTITY upsi             "&#x003C5;" ><!--GREEK SMALL LETTER UPSILON -->
<!ENTITY upsih            "&#x003D2;" ><!--GREEK UPSILON WITH HOOK SYMBOL -->
<!ENTITY upsilon          "&#x003C5;" ><!--GREEK SMALL LETTER UPSILON -->
<!ENTITY upuparrows       "&#x021C8;" ><!--UPWARDS PAIRED ARROWS -->
<!ENTITY urcorn           "&#x0231D;" ><!--TOP RIGHT CORNER -->
<!ENTITY urcorner         "&#x0231D;" ><!--TOP RIGHT CORNER -->
<!ENTITY urcrop           "&#x0230E;" ><!--TOP RIGHT CROP -->
<!ENTITY uring            "&#x0016F;" ><!--LATIN SMALL LETTER U WITH RING ABOVE -->
<!ENTITY urtri            "&#x025F9;" ><!--UPPER RIGHT TRIANGLE -->
<!ENTITY uscr             "&#x1D4CA;" ><!--MATHEMATICAL SCRIPT SMALL U -->
<!ENTITY utdot            "&#x022F0;" ><!--UP RIGHT DIAGONAL ELLIPSIS -->
<!ENTITY utilde           "&#x00169;" ><!--LATIN SMALL LETTER U WITH TILDE -->
<!ENTITY utri             "&#x025B5;" ><!--WHITE UP-POINTING SMALL TRIANGLE -->
<!ENTITY utrif            "&#x025B4;" ><!--BLACK UP-POINTING SMALL TRIANGLE -->
<!ENTITY uuarr            "&#x021C8;" ><!--UPWARDS PAIRED ARROWS -->
<!ENTITY uuml             "&#x000FC;" ><!--LATIN SMALL LETTER U WITH DIAERESIS -->
<!ENTITY uwangle          "&#x029A7;" ><!--OBLIQUE ANGLE OPENING DOWN -->
<!ENTITY vArr             "&#x021D5;" ><!--UP DOWN DOUBLE ARROW -->
<!ENTITY vBar             "&#x02AE8;" ><!--SHORT UP TACK WITH UNDERBAR -->
<!ENTITY vBarv            "&#x02AE9;" ><!--SHORT UP TACK ABOVE SHORT DOWN TACK -->
<!ENTITY vDash            "&#x022A8;" ><!--TRUE -->
<!ENTITY vangrt           "&#x0299C;" ><!--RIGHT ANGLE VARIANT WITH SQUARE -->
<!ENTITY varepsilon       "&#x003F5;" ><!--GREEK LUNATE EPSILON SYMBOL -->
<!ENTITY varkappa         "&#x003F0;" ><!--GREEK KAPPA SYMBOL -->
<!ENTITY varnothing       "&#x02205;" ><!--EMPTY SET -->
<!ENTITY varphi           "&#x003D5;" ><!--GREEK PHI SYMBOL -->
<!ENTITY varpi            "&#x003D6;" ><!--GREEK PI SYMBOL -->
<!ENTITY varpropto        "&#x0221D;" ><!--PROPORTIONAL TO -->
<!ENTITY varr             "&#x02195;" ><!--UP DOWN ARROW -->
<!ENTITY varrho           "&#x003F1;" ><!--GREEK RHO SYMBOL -->
<!ENTITY varsigma         "&#x003C2;" ><!--GREEK SMALL LETTER FINAL SIGMA -->
<!ENTITY varsubsetneq     "&#x0228A;&#x0FE00;" ><!--SUBSET OF WITH NOT EQUAL TO - variant with stroke through bottom members -->
<!ENTITY varsubsetneqq    "&#x02ACB;&#x0FE00;" ><!--SUBSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members -->
<!ENTITY varsupsetneq     "&#x0228B;&#x0FE00;" ><!--SUPERSET OF WITH NOT EQUAL TO - variant with stroke through bottom members -->
<!ENTITY varsupsetneqq    "&#x02ACC;&#x0FE00;" ><!--SUPERSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members -->
<!ENTITY vartheta         "&#x003D1;" ><!--GREEK THETA SYMBOL -->
<!ENTITY vartriangleleft  "&#x022B2;" ><!--NORMAL SUBGROUP OF -->
<!ENTITY vartriangleright "&#x022B3;" ><!--CONTAINS AS NORMAL SUBGROUP -->
<!ENTITY vcy              "&#x00432;" ><!--CYRILLIC SMALL LETTER VE -->
<!ENTITY vdash            "&#x022A2;" ><!--RIGHT TACK -->
<!ENTITY vee              "&#x02228;" ><!--LOGICAL OR -->
<!ENTITY veebar           "&#x022BB;" ><!--XOR -->
<!ENTITY veeeq            "&#x0225A;" ><!--EQUIANGULAR TO -->
<!ENTITY vellip           "&#x022EE;" ><!--VERTICAL ELLIPSIS -->
<!ENTITY verbar           "&#x0007C;" ><!--VERTICAL LINE -->
<!ENTITY vert             "&#x0007C;" ><!--VERTICAL LINE -->
<!ENTITY vfr              "&#x1D533;" ><!--MATHEMATICAL FRAKTUR SMALL V -->
<!ENTITY vltri            "&#x022B2;" ><!--NORMAL SUBGROUP OF -->
<!ENTITY vnsub            "&#x02282;&#x020D2;" ><!--SUBSET OF with vertical line -->
<!ENTITY vnsup            "&#x02283;&#x020D2;" ><!--SUPERSET OF with vertical line -->
<!ENTITY vopf             "&#x1D567;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL V -->
<!ENTITY vprop            "&#x0221D;" ><!--PROPORTIONAL TO -->
<!ENTITY vrtri            "&#x022B3;" ><!--CONTAINS AS NORMAL SUBGROUP -->
<!ENTITY vscr             "&#x1D4CB;" ><!--MATHEMATICAL SCRIPT SMALL V -->
<!ENTITY vsubnE           "&#x02ACB;&#x0FE00;" ><!--SUBSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members -->
<!ENTITY vsubne           "&#x0228A;&#x0FE00;" ><!--SUBSET OF WITH NOT EQUAL TO - variant with stroke through bottom members -->
<!ENTITY vsupnE           "&#x02ACC;&#x0FE00;" ><!--SUPERSET OF ABOVE NOT EQUAL TO - variant with stroke through bottom members -->
<!ENTITY vsupne           "&#x0228B;&#x0FE00;" ><!--SUPERSET OF WITH NOT EQUAL TO - variant with stroke through bottom members -->
<!ENTITY vzigzag          "&#x0299A;" ><!--VERTICAL ZIGZAG LINE -->
<!ENTITY wcirc            "&#x00175;" ><!--LATIN SMALL LETTER W WITH CIRCUMFLEX -->
<!ENTITY wedbar           "&#x02A5F;" ><!--LOGICAL AND WITH UNDERBAR -->
<!ENTITY wedge            "&#x02227;" ><!--LOGICAL AND -->
<!ENTITY wedgeq           "&#x02259;" ><!--ESTIMATES -->
<!ENTITY weierp           "&#x02118;" ><!--SCRIPT CAPITAL P -->
<!ENTITY wfr              "&#x1D534;" ><!--MATHEMATICAL FRAKTUR SMALL W -->
<!ENTITY wopf             "&#x1D568;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL W -->
<!ENTITY wp               "&#x02118;" ><!--SCRIPT CAPITAL P -->
<!ENTITY wr               "&#x02240;" ><!--WREATH PRODUCT -->
<!ENTITY wreath           "&#x02240;" ><!--WREATH PRODUCT -->
<!ENTITY wscr             "&#x1D4CC;" ><!--MATHEMATICAL SCRIPT SMALL W -->
<!ENTITY xcap             "&#x022C2;" ><!--N-ARY INTERSECTION -->
<!ENTITY xcirc            "&#x025EF;" ><!--LARGE CIRCLE -->
<!ENTITY xcup             "&#x022C3;" ><!--N-ARY UNION -->
<!ENTITY xdtri            "&#x025BD;" ><!--WHITE DOWN-POINTING TRIANGLE -->
<!ENTITY xfr              "&#x1D535;" ><!--MATHEMATICAL FRAKTUR SMALL X -->
<!ENTITY xhArr            "&#x027FA;" ><!--LONG LEFT RIGHT DOUBLE ARROW -->
<!ENTITY xharr            "&#x027F7;" ><!--LONG LEFT RIGHT ARROW -->
<!ENTITY xi               "&#x003BE;" ><!--GREEK SMALL LETTER XI -->
<!ENTITY xlArr            "&#x027F8;" ><!--LONG LEFTWARDS DOUBLE ARROW -->
<!ENTITY xlarr            "&#x027F5;" ><!--LONG LEFTWARDS ARROW -->
<!ENTITY xmap             "&#x027FC;" ><!--LONG RIGHTWARDS ARROW FROM BAR -->
<!ENTITY xnis             "&#x022FB;" ><!--CONTAINS WITH VERTICAL BAR AT END OF HORIZONTAL STROKE -->
<!ENTITY xodot            "&#x02A00;" ><!--N-ARY CIRCLED DOT OPERATOR -->
<!ENTITY xopf             "&#x1D569;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL X -->
<!ENTITY xoplus           "&#x02A01;" ><!--N-ARY CIRCLED PLUS OPERATOR -->
<!ENTITY xotime           "&#x02A02;" ><!--N-ARY CIRCLED TIMES OPERATOR -->
<!ENTITY xrArr            "&#x027F9;" ><!--LONG RIGHTWARDS DOUBLE ARROW -->
<!ENTITY xrarr            "&#x027F6;" ><!--LONG RIGHTWARDS ARROW -->
<!ENTITY xscr             "&#x1D4CD;" ><!--MATHEMATICAL SCRIPT SMALL X -->
<!ENTITY xsqcup           "&#x02A06;" ><!--N-ARY SQUARE UNION OPERATOR -->
<!ENTITY xuplus           "&#x02A04;" ><!--N-ARY UNION OPERATOR WITH PLUS -->
<!ENTITY xutri            "&#x025B3;" ><!--WHITE UP-POINTING TRIANGLE -->
<!ENTITY xvee             "&#x022C1;" ><!--N-ARY LOGICAL OR -->
<!ENTITY xwedge           "&#x022C0;" ><!--N-ARY LOGICAL AND -->
<!ENTITY yacute           "&#x000FD;" ><!--LATIN SMALL LETTER Y WITH ACUTE -->
<!ENTITY yacy             "&#x0044F;" ><!--CYRILLIC SMALL LETTER YA -->
<!ENTITY ycirc            "&#x00177;" ><!--LATIN SMALL LETTER Y WITH CIRCUMFLEX -->
<!ENTITY ycy              "&#x0044B;" ><!--CYRILLIC SMALL LETTER YERU -->
<!ENTITY yen              "&#x000A5;" ><!--YEN SIGN -->
<!ENTITY yfr              "&#x1D536;" ><!--MATHEMATICAL FRAKTUR SMALL Y -->
<!ENTITY yicy             "&#x00457;" ><!--CYRILLIC SMALL LETTER YI -->
<!ENTITY yopf             "&#x1D56A;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL Y -->
<!ENTITY yscr             "&#x1D4CE;" ><!--MATHEMATICAL SCRIPT SMALL Y -->
<!ENTITY yucy             "&#x0044E;" ><!--CYRILLIC SMALL LETTER YU -->
<!ENTITY yuml             "&#x000FF;" ><!--LATIN SMALL LETTER Y WITH DIAERESIS -->
<!ENTITY zacute           "&#x0017A;" ><!--LATIN SMALL LETTER Z WITH ACUTE -->
<!ENTITY zcaron           "&#x0017E;" ><!--LATIN SMALL LETTER Z WITH CARON -->
<!ENTITY zcy              "&#x00437;" ><!--CYRILLIC SMALL LETTER ZE -->
<!ENTITY zdot             "&#x0017C;" ><!--LATIN SMALL LETTER Z WITH DOT ABOVE -->
<!ENTITY zeetrf           "&#x02128;" ><!--BLACK-LETTER CAPITAL Z -->
<!ENTITY zeta             "&#x003B6;" ><!--GREEK SMALL LETTER ZETA -->
<!ENTITY zfr              "&#x1D537;" ><!--MATHEMATICAL FRAKTUR SMALL Z -->
<!ENTITY zhcy             "&#x00436;" ><!--CYRILLIC SMALL LETTER ZHE -->
<!ENTITY zigrarr          "&#x021DD;" ><!--RIGHTWARDS SQUIGGLE ARROW -->
<!ENTITY zopf             "&#x1D56B;" ><!--MATHEMATICAL DOUBLE-STRUCK SMALL Z -->
<!ENTITY zscr             "&#x1D4CF;" ><!--MATHEMATICAL SCRIPT SMALL Z -->
<!ENTITY zwj              "&#x0200D;" ><!--ZERO WIDTH JOINER -->
<!ENTITY zwnj             "&#x0200C;" ><!--ZERO WIDTH NON-JOINER -->
PK
!<h(���res/language.properties
aa.accept     = true
ab.accept     = true
ach.accept    = true
ae.accept     = true
af.accept     = true
ak.accept     = true
am.accept     = true
an.accept     = true
ar.accept     = true
ar-ae.accept  = true
ar-bh.accept  = true
ar-dz.accept  = true
ar-eg.accept  = true
ar-iq.accept  = true
ar-jo.accept  = true
ar-kw.accept  = true
ar-lb.accept  = true
ar-ly.accept  = true
ar-ma.accept  = true
ar-om.accept  = true
ar-qa.accept  = true
ar-sa.accept  = true
ar-sy.accept  = true
ar-tn.accept  = true
ar-ye.accept  = true
as.accept     = true
ast.accept    = true
av.accept     = true
ay.accept     = true
az.accept     = true
ba.accept     = true
be.accept     = true
bg.accept     = true
bh.accept     = true
bi.accept     = true
bm.accept     = true
bn.accept     = true
bo.accept     = true
br.accept     = true
bs.accept     = true
ca.accept     = true
ca-valencia.accept = true
cak.accept    = true
ce.accept     = true
ch.accept     = true
co.accept     = true
cr.accept     = true
crh.accept    = true
cs.accept     = true
csb.accept    = true
cu.accept     = true
cv.accept     = true
cy.accept     = true
da.accept     = true
de.accept     = true
de-at.accept  = true
de-ch.accept  = true
de-de.accept  = true
de-li.accept  = true
de-lu.accept  = true
dsb.accept    = true
dv.accept     = true
dz.accept     = true
ee.accept     = true
el.accept     = true
en.accept     = true
en-au.accept  = true
en-bz.accept  = true
en-ca.accept  = true
en-gb.accept  = true
en-ie.accept  = true
en-jm.accept  = true
en-nz.accept  = true
en-ph.accept  = true
en-tt.accept  = true
en-us.accept  = true
en-za.accept  = true
en-zw.accept  = true
eo.accept     = true
es.accept     = true
es-ar.accept  = true
es-bo.accept  = true
es-cl.accept  = true
es-co.accept  = true
es-cr.accept  = true
es-do.accept  = true
es-ec.accept  = true
es-es.accept  = true
es-gt.accept  = true
es-hn.accept  = true
es-mx.accept  = true
es-ni.accept  = true
es-pa.accept  = true
es-pe.accept  = true
es-pr.accept  = true
es-py.accept  = true
es-sv.accept  = true
es-uy.accept  = true
es-ve.accept  = true
et.accept     = true
eu.accept     = true
fa.accept     = true
fa-ir.accept  = true
ff.accept     = true
fi.accept     = true
fj.accept     = true
fo.accept     = true
fr.accept     = true
fr-be.accept  = true
fr-ca.accept  = true
fr-ch.accept  = true
fr-fr.accept  = true
fr-lu.accept  = true
fr-mc.accept  = true
fur.accept    = true
fy.accept     = true
ga.accept     = true
gd.accept     = true
gl.accept     = true
gn.accept     = true
gu.accept     = true
gv.accept     = true
ha.accept     = true
haw.accept    = true
he.accept     = true
hi.accept     = true
hil.accept    = true
ho.accept     = true
hr.accept     = true
hsb.accept    = true
ht.accept     = true
hu.accept     = true
hy.accept     = true
hz.accept     = true
ia.accept     = true
id.accept     = true
ie.accept     = true
ig.accept     = true
ii.accept     = true
ik.accept     = true
io.accept     = true
is.accept     = true
it.accept     = true
it-ch.accept  = true
iu.accept     = true
ja.accept     = true
jv.accept     = true
ka.accept     = true
kab.accept    = true
kg.accept     = true
ki.accept     = true
kk.accept     = true
kl.accept     = true
km.accept     = true
kn.accept     = true
ko.accept     = true
ko-kp.accept  = true
ko-kr.accept  = true
kok.accept    = true
kr.accept     = true
ks.accept     = true
ku.accept     = true
kv.accept     = true
kw.accept     = true
ky.accept     = true
la.accept     = true
lb.accept     = true
lg.accept     = true
li.accept     = true
lij.accept    = true
ln.accept     = true
lo.accept     = true
lt.accept     = true
ltg.accept    = true
lu.accept     = true
lv.accept     = true
mai.accept    = true
meh.accept    = true
mg.accept     = true
mh.accept     = true
mi.accept     = true
mix.accept    = true
mk.accept     = true
mk-mk.accept  = true
ml.accept     = true
mn.accept     = true
mr.accept     = true
ms.accept     = true
mt.accept     = true
my.accept     = true
na.accept     = true
nb.accept     = true
nd.accept     = true
ne.accept     = true
ng.accept     = true
nl.accept     = true
nl-be.accept  = true
nn.accept     = true
no.accept     = true
nr.accept     = true
nso.accept    = true
nv.accept     = true
ny.accept     = true
oc.accept     = true
oj.accept     = true
om.accept     = true
or.accept     = true
os.accept     = true
pa.accept     = true
pa-in.accept  = true
pa-pk.accept  = true
pi.accept     = true
pl.accept     = true
ps.accept     = true
pt.accept     = true
pt-br.accept  = true
pt-pt.accept  = true
qu.accept     = true
rm.accept     = true
rn.accept     = true
ro.accept     = true
ro-md.accept  = true
ro-ro.accept  = true
ru.accept     = true
ru-md.accept  = true
rw.accept     = true
sa.accept     = true
sat.accept    = true
sc.accept     = true
sco.accept    = true
sd.accept     = true
sg.accept     = true
si.accept     = true
sk.accept     = true
skr.accept    = true
sl.accept     = true
sm.accept     = true
so.accept     = true
son.accept    = true
son-ml.accept = true
sq.accept     = true
sr.accept     = true
ss.accept     = true
st.accept     = true
su.accept     = true
sv.accept     = true
sv-fi.accept  = true
sv-se.accept  = true
sw.accept     = true
szl.accept    = true
ta.accept     = true
te.accept     = true
tg.accept     = true
th.accept     = true
ti.accept     = true
tig.accept    = true
tk.accept     = true
tl.accept     = true
tlh.accept    = true
tn.accept     = true
to.accept     = true
tr.accept     = true
trs.accept    = true
ts.accept     = true
tt.accept     = true
tw.accept     = true
ty.accept     = true
ug.accept     = true
uk.accept     = true
ur.accept     = true
uz.accept     = true
ve.accept     = true
vi.accept     = true
vo.accept     = true
wa.accept     = true
wo.accept     = true
xh.accept     = true
yi.accept     = true
yo.accept     = true
za.accept     = true
zam.accept    = true
zh.accept     = true
zh-cn.accept  = true
zh-hk.accept  = true
zh-sg.accept  = true
zh-tw.accept  = true
zu.accept     = true
PK
!<5Oz�  *res/locale/layout/MediaDocument.properties
ImageTitleWithDimensions2AndFile=%S (%S Image, %S\u00A0\u00D7\u00A0%S pixels)
ImageTitleWithoutDimensions=%S (%S Image)
ImageTitleWithDimensions2=(%S Image, %S\u00A0\u00D7\u00A0%S pixels)
ImageTitleWithNeitherDimensionsNorFile=(%S Image)
MediaTitleWithFile=%S (%S Object)
MediaTitleWithNoInfo=(%S Object)

InvalidImage=The image \u201c%S\u201d cannot be displayed because it contains errors.
UnsupportedImage=The image “%S” cannot be displayed because it requires unsupported features.
ScaledImage=Scaled (%S%%)

TitleWithStatus=%S — %S
PK
!<~3�##&res/locale/layout/xmlparser.properties
1 = out of memory
2 = syntax error
3 = no root element found
4 = not well-formed
5 = unclosed token
6 = partial character
7 = mismatched tag
8 = duplicate attribute
9 = junk after document element
10 = illegal parameter entity reference
11 = undefined entity
12 = recursive entity reference
13 = asynchronous entity
14 = reference to invalid character number
15 = reference to binary entity
16 = reference to external entity in attribute
17 = XML or text declaration not at start of entity
18 = unknown encoding
19 = encoding specified in XML declaration is incorrect
20 = unclosed CDATA section
21 = error in processing external entity reference
22 = document is not standalone
23 = unexpected parser state
24 = entity declared in parameter entity
27 = prefix not bound to a namespace
28 = must not undeclare prefix
29 = incomplete markup in parameter entity
30 = XML declaration not well-formed
31 = text declaration not well-formed
32 = illegal character(s) in public id
38 = reserved prefix (xml) must not be undeclared or bound to another namespace name
39 = reserved prefix (xmlns) must not be declared or undeclared
40 = prefix must not be bound to one of the reserved namespace names

XMLParsingError = XML Parsing Error: %1$S\nLocation: %2$S\nLine Number %3$u, Column %4$u:

Expected = . Expected: </%S>.
PK
!<�p�l��#contentaccessible/ImageDocument.css/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * This CSS stylesheet defines the rules to be applied to any ImageDocuments,
 * including those in frames.
*/

body {
  /* To give the image access to our document's full viewport, we need to
     override the margin that the html.css UA stylesheet would otherwise apply
     to our body. */
  margin: 0;
}

@media not print {
  .fullZoomOut {
    cursor: zoom-out;
  }

  .fullZoomIn {
    cursor: zoom-in;
  }

  .shrinkToFit {
    cursor: zoom-in;
  }

  .overflowingVertical, .overflowingHorizontalOnly {
    cursor: zoom-out;
  }
}

.isInObjectOrEmbed {
  width: 100%;
  height: 100vh;
}

img {
  display: block;
}
PK
!<F�g+contentaccessible/TopLevelImageDocument.css/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
  This CSS stylesheet defines the rules to be applied to ImageDocuments that
  are top level (e.g. not iframes).
*/

@media not print {
  :root {
    /* The font color here was chosen to be readable over the corresponding
       backgrounds. This is important in case this ImageDocument is for an
       image that happens to be corrupt, in which case we'll display a textual
       error message over the background, instead of the image itself. */
    color: #eee;
    /* The background-attachment is fixed to stop an ugly white gutter
       from appearing when the document is overscrolled. */
    background: url("chrome://global/skin/media/imagedoc-darknoise.png") fixed;
  }

  img.transparent {
    color: #222;
    background: hsl(0,0%,90%) url("chrome://global/skin/media/imagedoc-lightnoise.png");
  }

  img {
    text-align: center;
    position: absolute;
    inset: 0;
    margin: auto;
  }

  img.overflowingVertical {
    /* If we're overflowing vertically, we need to set margin-top to
       0.  Otherwise we'll end up trying to vertically center, and end
       up cutting off the top part of the image. */
    margin-top: 0;
  }

  .completeRotation {
    transition: transform 0.3s ease 0s;
  }
}

img {
  image-orientation: from-image;
}
PK
!<4U휅�+contentaccessible/TopLevelVideoDocument.css/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
  This CSS stylesheet defines the rules to be applied to VideoDocuments that
  are top level (e.g. not iframes).
*/

:root {
  background-color: black;
  /* Fill the viewport height, so that our '-moz-user-focus' styling will
     disregard clicks in the whole background area (so the video element
     doesn't inadvertently lose focus from a stray click on the background). */
  height: 100%;
  -moz-user-focus: ignore;
}

video {
  position: absolute;
  inset: 0;
  margin: auto;
  max-width: 100%;
  max-height: 100%;
  user-select: none;
  -moz-user-focus: normal;
}

video:focus {
  outline-style: none;
}
PK
!<2Y�llcontentaccessible/details.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

slot:not([name]) {
  display: block;
  content-visibility: hidden;
}
:host([open]) slot:not([name]) {
  display: revert;
  content-visibility: visible;
}

/* See the comment around the summary styles in html.css, these rules should
 * match */
summary {
  display: list-item;
  counter-increment: list-item 0;
  list-style: disclosure-closed inside;
}
:host([open]) summary {
  list-style-type: disclosure-open;
}
PK
!<��K!contentaccessible/html/folder.png�PNG


IHDR�agAMA���a�IDATxŏ�P��s�y�5e3�ܛ�956��
�9cʶ�y���sc�v�o��wv2Y�27�򵆄�sR��^�!�����Q}jNv�d�r~���ƧG�18�A)�'߫5{�)�ț7���̵;f��0m�Ҳ� qm��ݫ�|e����c4h�7/�������I����PK��:�/~�7-�����"��C��e�X���,�L��{�}�w2�ې��R�uk�l0\;X�wE��a�	?<"��वn�I��E`k���o`
���,t"��ϟ�%x$:��WRs��VC<*&�d1���dj�q��Ԓ�S��F!�-�tHlOf��/�9�@w4�)����v�GoQ�H!�3��k��������ю��w$Z��鐿|�0�~�(H�H���@{w����o=�ʊ��sIEND�B`�PK
!<��3���contentaccessible/plaintext.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

pre {
  white-space: pre-wrap;
  word-wrap: break-word;
  -moz-control-character-visibility: visible;
}

.nowrap pre {
  white-space: pre;
}

/* Make text go with the rules of dir=auto, but allow it to be overriden if 'Switch Text Direction' is triggered */
html:not([dir]) pre { /* Not a UA sheet, so doesn't use :-moz-has-dir-attr */
  unicode-bidi: plaintext;
}

@-moz-document unobservable-document() {
  :root {
    color-scheme: light dark;
  }
}

/* NOTE(emilio): For some reason some pages, mainly bing.com, load a bunch of
 * scripts in zero-size <object> elements, see bug 1548449.
 *
 * Line-breaking such documents is useless and pretty expensive, so only render
 * them if there's a viewport. Sigh.
 */
@media (width: 0) or (height: 0) {
  :root {
    display: none;
  }
}
PK
!<�#�iKK(contentaccessible/searchfield-cancel.svg<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 14 14">
  <style>
    circle {
      fill: #808080;
    }
    
    line {
      stroke: #fff;
      stroke-width: 1.5px;
    }
  </style>

  <circle cx="7" cy="7" r="7" />
  <line x1="4" y1="4" x2="10" y2="10" />
  <line x1="10" y1="4" x2="4" y2="10" />
</svg>PK
!<�lT�� contentaccessible/viewsource.css@charset "utf-8";
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

@namespace url(http://www.w3.org/1999/xhtml); /* set default namespace to HTML */

*|*:root {
  color-scheme: light dark;
  direction: ltr;
  -moz-control-character-visibility: visible;
  height: 100%;
}
#viewsource {
  font-family: -moz-fixed;
  font-weight: normal;
  white-space: pre;
  counter-reset: line;
  height: 100%;
  box-sizing: border-box;
  margin: 0;
  padding: 8px;
}
#viewsource.wrap {
  white-space: pre-wrap;
  word-wrap: break-word;
}
pre {
  font: inherit;
  color: inherit;
  white-space: inherit;
  margin: 0 0 0 5ch;
}
pre[id]:before,
span[id]:before {
  content: counter(line) " ";
  counter-increment: line;
  user-select: none;
  display: inline-block;
  width: 5ch;
  margin: 0 0 0 -5ch;
  text-align: right;
  color: #ccc;
  font-weight: normal;
  font-style: normal;
}
.highlight .start-tag,
.highlight .end-tag {
  color: purple;
  font-weight: bold;
}
.highlight .comment {
  color: green;
  font-style: italic;
}
.highlight .cdata {
  color: #CC0066;
}
.highlight .doctype,
.highlight .markupdeclaration {
  color: steelblue;
  font-style: italic;
}
.highlight .pi {
  color: orchid;
  font-style: italic;
}
.highlight .entity {
  color: #FF4500;
  font-weight: normal;
}
.highlight .text {
  font-weight: normal;
}
.highlight .attribute-name {
  font-weight: bold;
}
.highlight .attribute-value {
  color: blue;
  font-weight: normal;
}
span:not(.error),
a:not(.error) {
  unicode-bidi: embed;
}
span[id] {
  unicode-bidi: isolate;
}
.highlight .error {
  color: revert;
  font-weight: bold;
  background-color: rgba(231, 116, 113, 0.3);
  text-decoration: underline wavy red 0.5px;
}
@media (prefers-color-scheme: dark) {
  .highlight .start-tag,
  .highlight .end-tag {
    color: #f55e5e;
  }
  .highlight .comment {
    color: lightgreen;
  }
  .highlight .cdata {
    color: #f068ac;
  }
  .highlight .doctype,
  .highlight .markupdeclaration {
    color: lightgray;
  }
  .highlight .entity {
    color: #f18a65;
  }
  .highlight .attribute-value {
    color: #97bbff;
  }
}
PK
!<�����,chrome/pippki/content/pippki/certManager.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/* Good enough support for equalsize=always for the cert manager use cases.
 * You probably shouldn't use this as-is elsewhere, this selector is somewhat
 * slow, it relies on stuff having display: flex, and you probably can use
 * something simpler if you need this */
[equalsize="always"] > * {
  flex: 1;
  contain: inline-size;
}

treecol {
  flex: 1 auto;
  width: 0; /* Don't let intrinsic sizes affect our minimum size. */
}

#certmanager {
  /* This prevents horizontal scrollbars due to <tree> and non-XUL layout
   * interactions */
  padding: 0;
}

/* This matches the <tree> height from dialog.css */
richlistbox {
  min-height: 15em;
  contain: size;
}

richlistbox,
richlistitem {
  min-height: 30px;
}
PK
!<��O�T�T+chrome/pippki/content/pippki/certManager.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from pippki.js */
"use strict";

const gCertFileTypes = "*.p7b; *.crt; *.cert; *.cer; *.pem; *.der";

var { NetUtil } = ChromeUtils.importESModule(
  "resource://gre/modules/NetUtil.sys.mjs"
);

var key;

var certdialogs = Cc["@mozilla.org/nsCertificateDialogs;1"].getService(
  Ci.nsICertificateDialogs
);

/**
 * List of certs currently selected in the active tab.
 *
 * @type {nsIX509Cert[]}
 */
var selected_certs = [];
var selected_tree_items = [];
var selected_index = [];
var certdb;

/**
 * Cert tree for the "Authorities" tab.
 *
 * @type {nsICertTree}
 */
var caTreeView;
/**
 * Cert tree for the "Servers" tab.
 *
 * @type {nsICertTree}
 */
var serverTreeView;

var overrideService;

function createRichlistItem(item) {
  let innerHbox = document.createXULElement("hbox");
  innerHbox.setAttribute("align", "center");
  innerHbox.setAttribute("flex", "1");

  let row = document.createXULElement("label");
  row.setAttribute("flex", "1");
  row.setAttribute("crop", "end");
  row.setAttribute("style", "margin-inline-start: 15px;");
  if ("raw" in item) {
    row.setAttribute("value", item.raw);
  } else {
    document.l10n.setAttributes(row, item.l10nid);
  }
  row.setAttribute("ordinal", "1");
  innerHbox.appendChild(row);

  return innerHbox;
}

var serverRichList = {
  richlist: undefined,

  buildRichList() {
    let overrides = overrideService.getOverrides().map(item => {
      return {
        hostPort: item.hostPort,
        asciiHost: item.asciiHost,
        port: item.port,
        originAttributes: item.originAttributes,
        fingerprint: item.fingerprint,
      };
    });
    overrides.sort((a, b) => {
      let criteria = ["hostPort", "fingerprint"];
      for (let c of criteria) {
        let res = a[c].localeCompare(b[c]);
        if (res !== 0) {
          return res;
        }
      }
      return 0;
    });

    this.richlist.textContent = "";
    this.richlist.clearSelection();

    let frag = document.createDocumentFragment();
    for (let override of overrides) {
      let richlistitem = this._richBoxAddItem(override);
      frag.appendChild(richlistitem);
    }
    this.richlist.appendChild(frag);

    this._setButtonState();
    this.richlist.addEventListener("select", () => this._setButtonState());
  },

  _richBoxAddItem(item) {
    let richlistitem = document.createXULElement("richlistitem");

    richlistitem.setAttribute("host", item.asciiHost);
    richlistitem.setAttribute("port", item.port);
    richlistitem.setAttribute("hostPort", item.hostPort);
    richlistitem.setAttribute("fingerprint", item.fingerprint);
    richlistitem.setAttribute(
      "originAttributes",
      JSON.stringify(item.originAttributes)
    );

    let hbox = document.createXULElement("hbox");
    hbox.setAttribute("flex", "1");
    hbox.setAttribute("equalsize", "always");

    hbox.appendChild(createRichlistItem({ raw: item.hostPort }));
    hbox.appendChild(createRichlistItem({ raw: item.fingerprint }));

    richlistitem.appendChild(hbox);

    return richlistitem;
  },

  deleteSelectedRichListItem() {
    let selectedItem = this.richlist.selectedItem;
    if (!selectedItem) {
      return;
    }

    let retVals = {
      deleteConfirmed: false,
    };
    window.browsingContext.topChromeWindow.openDialog(
      "chrome://pippki/content/deletecert.xhtml",
      "",
      "chrome,centerscreen,modal",
      "websites_tab",
      [
        {
          hostPort: selectedItem.attributes.hostPort.value,
        },
      ],
      retVals
    );

    if (retVals.deleteConfirmed) {
      overrideService.clearValidityOverride(
        selectedItem.attributes.host.value,
        selectedItem.attributes.port.value,
        JSON.parse(selectedItem.attributes.originAttributes.value)
      );
      this.buildRichList();
    }
  },

  addException() {
    let retval = {
      exceptionAdded: false,
    };
    window.browsingContext.topChromeWindow.openDialog(
      "chrome://pippki/content/exceptionDialog.xhtml",
      "",
      "chrome,centerscreen,modal",
      retval
    );
    if (retval.exceptionAdded) {
      this.buildRichList();
    }
  },

  _setButtonState() {
    let websiteDeleteButton = document.getElementById("websites_deleteButton");
    websiteDeleteButton.disabled = this.richlist.selectedIndex < 0;
  },
};
/**
 * Cert tree for the "People" tab.
 *
 * @type {nsICertTree}
 */
var emailTreeView;
/**
 * Cert tree for the "Your Certificates" tab.
 *
 * @type {nsICertTree}
 */
var userTreeView;

var clientAuthRememberService;

var rememberedDecisionsRichList = {
  richlist: undefined,

  buildRichList() {
    let rememberedDecisions = clientAuthRememberService.getDecisions();

    let oldItems = this.richlist.querySelectorAll("richlistitem");
    for (let item of oldItems) {
      item.remove();
    }

    let frag = document.createDocumentFragment();
    for (let decision of rememberedDecisions) {
      let richlistitem = this._richBoxAddItem(decision);
      frag.appendChild(richlistitem);
    }
    this.richlist.appendChild(frag);

    this.richlist.addEventListener("select", () => this.setButtonState());
  },

  _richBoxAddItem(item) {
    let richlistitem = document.createXULElement("richlistitem");

    richlistitem.setAttribute("entryKey", item.entryKey);
    richlistitem.setAttribute("dbKey", item.dbKey);

    let hbox = document.createXULElement("hbox");
    hbox.setAttribute("flex", "1");
    hbox.setAttribute("equalsize", "always");

    hbox.appendChild(createRichlistItem({ raw: item.asciiHost }));
    if (item.dbKey == "") {
      hbox.appendChild(
        createRichlistItem({ l10nid: "send-no-client-certificate" })
      );

      hbox.appendChild(createRichlistItem({ raw: "" }));
    } else {
      let tmpCert = certdb.findCertByDBKey(item.dbKey);
      // The certificate corresponding to this item's dbKey may not be
      // available (for example, if it was stored on a token that's been
      // removed, or if it was deleted).
      if (tmpCert) {
        hbox.appendChild(createRichlistItem({ raw: tmpCert.commonName }));
        hbox.appendChild(createRichlistItem({ raw: tmpCert.serialNumber }));
      } else {
        hbox.appendChild(
          createRichlistItem({ l10nid: "certificate-not-available" })
        );
        hbox.appendChild(
          createRichlistItem({ l10nid: "certificate-not-available" })
        );
      }
    }

    richlistitem.appendChild(hbox);

    return richlistitem;
  },

  deleteSelectedRichListItem() {
    let selectedItem = this.richlist.selectedItem;
    let index = this.richlist.selectedIndex;
    if (index < 0) {
      return;
    }

    clientAuthRememberService.forgetRememberedDecision(
      selectedItem.attributes.entryKey.value
    );

    this.buildRichList();
    this.setButtonState();
  },

  viewSelectedRichListItem() {
    let selectedItem = this.richlist.selectedItem;
    let index = this.richlist.selectedIndex;
    if (index < 0) {
      return;
    }

    if (selectedItem.attributes.dbKey.value != "") {
      let cert = certdb.findCertByDBKey(selectedItem.attributes.dbKey.value);
      viewCertHelper(window, cert);
    }
  },

  setButtonState() {
    let rememberedDeleteButton = document.getElementById(
      "remembered_deleteButton"
    );
    let rememberedViewButton = document.getElementById("remembered_viewButton");

    rememberedDeleteButton.disabled = this.richlist.selectedIndex < 0;
    rememberedViewButton.disabled =
      this.richlist.selectedItem == null
        ? true
        : this.richlist.selectedItem.attributes.dbKey.value == "";
  },
};

function LoadCerts() {
  certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
    Ci.nsIX509CertDB
  );
  var certcache = certdb.getCerts();

  caTreeView = Cc["@mozilla.org/security/nsCertTree;1"].createInstance(
    Ci.nsICertTree
  );
  caTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.CA_CERT);
  document.getElementById("ca-tree").view = caTreeView;

  emailTreeView = Cc["@mozilla.org/security/nsCertTree;1"].createInstance(
    Ci.nsICertTree
  );
  emailTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.EMAIL_CERT);
  document.getElementById("email-tree").view = emailTreeView;

  userTreeView = Cc["@mozilla.org/security/nsCertTree;1"].createInstance(
    Ci.nsICertTree
  );
  userTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.USER_CERT);
  document.getElementById("user-tree").view = userTreeView;

  clientAuthRememberService = Cc[
    "@mozilla.org/security/clientAuthRememberService;1"
  ].getService(Ci.nsIClientAuthRememberService);

  overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(
    Ci.nsICertOverrideService
  );

  rememberedDecisionsRichList.richlist =
    document.getElementById("rememberedList");
  serverRichList.richlist = document.getElementById("serverList");

  rememberedDecisionsRichList.buildRichList();
  serverRichList.buildRichList();

  rememberedDecisionsRichList.setButtonState();

  enableBackupAllButton();
}

function enableBackupAllButton() {
  let backupAllButton = document.getElementById("mine_backupAllButton");
  backupAllButton.disabled = userTreeView.rowCount < 1;
}

function getSelectedCerts() {
  var ca_tab = document.getElementById("ca_tab");
  var mine_tab = document.getElementById("mine_tab");
  var others_tab = document.getElementById("others_tab");
  var items = null;
  if (ca_tab.selected) {
    items = caTreeView.selection;
  } else if (mine_tab.selected) {
    items = userTreeView.selection;
  } else if (others_tab.selected) {
    items = emailTreeView.selection;
  }
  selected_certs = [];
  var cert = null;
  var nr = 0;
  if (items != null) {
    nr = items.getRangeCount();
  }
  if (nr > 0) {
    for (let i = 0; i < nr; i++) {
      var o1 = {};
      var o2 = {};
      items.getRangeAt(i, o1, o2);
      var min = o1.value;
      var max = o2.value;
      for (let j = min; j <= max; j++) {
        if (ca_tab.selected) {
          cert = caTreeView.getCert(j);
        } else if (mine_tab.selected) {
          cert = userTreeView.getCert(j);
        } else if (others_tab.selected) {
          cert = emailTreeView.getCert(j);
        }
        if (cert) {
          var sc = selected_certs.length;
          selected_certs[sc] = cert;
          selected_index[sc] = j;
        }
      }
    }
  }
}

function getSelectedTreeItems() {
  var ca_tab = document.getElementById("ca_tab");
  var mine_tab = document.getElementById("mine_tab");
  var others_tab = document.getElementById("others_tab");
  var items = null;
  if (ca_tab.selected) {
    items = caTreeView.selection;
  } else if (mine_tab.selected) {
    items = userTreeView.selection;
  } else if (others_tab.selected) {
    items = emailTreeView.selection;
  }
  selected_certs = [];
  selected_tree_items = [];
  selected_index = [];
  var tree_item = null;
  var nr = 0;
  if (items != null) {
    nr = items.getRangeCount();
  }
  if (nr > 0) {
    for (let i = 0; i < nr; i++) {
      var o1 = {};
      var o2 = {};
      items.getRangeAt(i, o1, o2);
      var min = o1.value;
      var max = o2.value;
      for (let j = min; j <= max; j++) {
        if (ca_tab.selected) {
          tree_item = caTreeView.getTreeItem(j);
        } else if (mine_tab.selected) {
          tree_item = userTreeView.getTreeItem(j);
        } else if (others_tab.selected) {
          tree_item = emailTreeView.getTreeItem(j);
        }
        if (tree_item) {
          var sc = selected_tree_items.length;
          selected_tree_items[sc] = tree_item;
          selected_index[sc] = j;
        }
      }
    }
  }
}

/**
 * Returns true if nothing in the given cert tree is selected or if the
 * selection includes a container. Returns false otherwise.
 *
 * @param {nsICertTree} certTree
 * @returns {boolean}
 */
function nothingOrContainerSelected(certTree) {
  var certTreeSelection = certTree.selection;
  var numSelectionRanges = certTreeSelection.getRangeCount();

  if (numSelectionRanges == 0) {
    return true;
  }

  for (var i = 0; i < numSelectionRanges; i++) {
    var o1 = {};
    var o2 = {};
    certTreeSelection.getRangeAt(i, o1, o2);
    var minIndex = o1.value;
    var maxIndex = o2.value;
    for (var j = minIndex; j <= maxIndex; j++) {
      if (certTree.isContainer(j)) {
        return true;
      }
    }
  }

  return false;
}

async function promptError(aErrorCode) {
  if (aErrorCode != Ci.nsIX509CertDB.Success) {
    let msgName = "pkcs12-unknown-err";
    switch (aErrorCode) {
      case Ci.nsIX509CertDB.ERROR_PKCS12_NOSMARTCARD_EXPORT:
        msgName = "pkcs12-info-no-smartcard-backup";
        break;
      case Ci.nsIX509CertDB.ERROR_PKCS12_RESTORE_FAILED:
        msgName = "pkcs12-unknown-err-restore";
        break;
      case Ci.nsIX509CertDB.ERROR_PKCS12_BACKUP_FAILED:
        msgName = "pkcs12-unknown-err-backup";
        break;
      case Ci.nsIX509CertDB.ERROR_PKCS12_CERT_COLLISION:
      case Ci.nsIX509CertDB.ERROR_PKCS12_DUPLICATE_DATA:
        msgName = "pkcs12-dup-data";
        break;
      case Ci.nsIX509CertDB.ERROR_BAD_PASSWORD:
        msgName = "pk11-bad-password";
        break;
      case Ci.nsIX509CertDB.ERROR_DECODE_ERROR:
        msgName = "pkcs12-decode-err";
        break;
      default:
        break;
    }
    let [message] = await document.l10n.formatValues([{ id: msgName }]);
    let prompter = Services.ww.getNewPrompter(window);
    prompter.alert(null, message);
  }
}

/**
 * Enables or disables buttons corresponding to a cert tree depending on what
 * is selected in the cert tree.
 *
 * @param {nsICertTree} certTree
 * @param {Array} idList A list of string identifiers for button elements to
 *    enable or disable.
 */
function enableButtonsForCertTree(certTree, idList) {
  let disableButtons = nothingOrContainerSelected(certTree);

  for (let id of idList) {
    document.getElementById(id).setAttribute("disabled", disableButtons);
  }
}

function ca_enableButtons() {
  let idList = [
    "ca_viewButton",
    "ca_editButton",
    "ca_exportButton",
    "ca_deleteButton",
  ];
  enableButtonsForCertTree(caTreeView, idList);
}

function mine_enableButtons() {
  let idList = ["mine_viewButton", "mine_backupButton", "mine_deleteButton"];
  enableButtonsForCertTree(userTreeView, idList);
}

function email_enableButtons() {
  let idList = ["email_viewButton", "email_exportButton", "email_deleteButton"];
  enableButtonsForCertTree(emailTreeView, idList);
}

async function backupCerts() {
  getSelectedCerts();
  var numcerts = selected_certs.length;
  if (numcerts == 0) {
    return;
  }

  var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
  let [backupFileDialog, filePkcs12Spec] = await document.l10n.formatValues([
    { id: "choose-p12-backup-file-dialog" },
    { id: "file-browse-pkcs12-spec" },
  ]);
  fp.init(window.browsingContext, backupFileDialog, Ci.nsIFilePicker.modeSave);
  fp.appendFilter(filePkcs12Spec, "*.p12");
  fp.appendFilters(Ci.nsIFilePicker.filterAll);
  fp.defaultExtension = "p12";
  fp.open(rv => {
    if (
      rv == Ci.nsIFilePicker.returnOK ||
      rv == Ci.nsIFilePicker.returnReplace
    ) {
      let password = {};
      if (certdialogs.setPKCS12FilePassword(window, password)) {
        let errorCode = certdb.exportPKCS12File(
          fp.file,
          selected_certs,
          password.value
        );
        promptError(errorCode);
      }
    }
  });
}

function backupAllCerts() {
  // Select all rows, then call doBackup()
  userTreeView.selection.selectAll();
  backupCerts();
}

function editCerts() {
  getSelectedCerts();

  for (let cert of selected_certs) {
    window.browsingContext.topChromeWindow.openDialog(
      "chrome://pippki/content/editcacert.xhtml",
      "",
      "chrome,centerscreen,modal",
      cert
    );
  }
}

async function restoreCerts() {
  var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
  let [restoreFileDialog, filePkcs12Spec, fileCertSpec] =
    await document.l10n.formatValues([
      { id: "choose-p12-restore-file-dialog" },
      { id: "file-browse-pkcs12-spec" },
      { id: "file-browse-certificate-spec" },
    ]);
  fp.init(window.browsingContext, restoreFileDialog, Ci.nsIFilePicker.modeOpen);
  fp.appendFilter(filePkcs12Spec, "*.p12; *.pfx");
  fp.appendFilter(fileCertSpec, gCertFileTypes);
  fp.appendFilters(Ci.nsIFilePicker.filterAll);
  fp.open(rv => {
    if (rv != Ci.nsIFilePicker.returnOK) {
      return;
    }

    // If this is an X509 user certificate, import it as one.

    var isX509FileType = false;
    var fileTypesList = gCertFileTypes.slice(1).split("; *");
    for (var type of fileTypesList) {
      if (fp.file.path.endsWith(type)) {
        isX509FileType = true;
        break;
      }
    }

    if (isX509FileType) {
      let fstream = Cc[
        "@mozilla.org/network/file-input-stream;1"
      ].createInstance(Ci.nsIFileInputStream);
      fstream.init(fp.file, -1, 0, 0);
      let dataString = NetUtil.readInputStreamToString(
        fstream,
        fstream.available()
      );
      let dataArray = [];
      for (let i = 0; i < dataString.length; i++) {
        dataArray.push(dataString.charCodeAt(i));
      }
      fstream.close();
      let prompter = Services.ww.getNewPrompter(window);
      let interfaceRequestor = {
        getInterface() {
          return prompter;
        },
      };
      certdb.importUserCertificate(
        dataArray,
        dataArray.length,
        interfaceRequestor
      );
    } else {
      // Otherwise, assume it's a PKCS12 file and import it that way.
      let password = {};
      let errorCode = Ci.nsIX509CertDB.ERROR_BAD_PASSWORD;
      while (
        errorCode == Ci.nsIX509CertDB.ERROR_BAD_PASSWORD &&
        certdialogs.getPKCS12FilePassword(window, password)
      ) {
        errorCode = certdb.importPKCS12File(fp.file, password.value);
        if (
          errorCode == Ci.nsIX509CertDB.ERROR_BAD_PASSWORD &&
          !password.value.length
        ) {
          // It didn't like empty string password, try no password.
          errorCode = certdb.importPKCS12File(fp.file, null);
        }
        promptError(errorCode);
      }
    }

    var certcache = certdb.getCerts();
    userTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.USER_CERT);
    userTreeView.selection.clearSelection();
    caTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.CA_CERT);
    caTreeView.selection.clearSelection();
    enableBackupAllButton();
  });
}

async function exportCerts() {
  getSelectedCerts();

  for (let cert of selected_certs) {
    await exportToFile(window, cert);
  }
}

/**
 * Deletes the selected certs in the active tab.
 */
function deleteCerts() {
  getSelectedTreeItems();
  let numcerts = selected_tree_items.length;
  if (numcerts == 0) {
    return;
  }

  const treeViewMap = {
    mine_tab: userTreeView,
    ca_tab: caTreeView,
    others_tab: emailTreeView,
  };
  let selTab = document.getElementById("certMgrTabbox").selectedItem;
  let selTabID = selTab.getAttribute("id");

  if (!(selTabID in treeViewMap)) {
    return;
  }

  let retVals = {
    deleteConfirmed: false,
  };
  window.browsingContext.topChromeWindow.openDialog(
    "chrome://pippki/content/deletecert.xhtml",
    "",
    "chrome,centerscreen,modal",
    selTabID,
    selected_tree_items,
    retVals
  );

  if (retVals.deleteConfirmed) {
    let treeView = treeViewMap[selTabID];

    for (let t = numcerts - 1; t >= 0; t--) {
      treeView.deleteEntryObject(selected_index[t]);
    }

    selected_tree_items = [];
    selected_index = [];
    treeView.selection.clearSelection();
    if (selTabID == "mine_tab") {
      enableBackupAllButton();
    }
  }
}

function viewCerts() {
  getSelectedCerts();

  for (let cert of selected_certs) {
    viewCertHelper(window, cert);
  }
}

async function addCACerts() {
  var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
  let [importCa, fileCertSpec] = await document.l10n.formatValues([
    { id: "import-ca-certs-prompt" },
    { id: "file-browse-certificate-spec" },
  ]);
  fp.init(window.browsingContext, importCa, Ci.nsIFilePicker.modeOpen);
  fp.appendFilter(fileCertSpec, gCertFileTypes);
  fp.appendFilters(Ci.nsIFilePicker.filterAll);
  fp.open(rv => {
    if (rv == Ci.nsIFilePicker.returnOK) {
      certdb.importCertsFromFile(fp.file, Ci.nsIX509Cert.CA_CERT);
      let certcache = certdb.getCerts();
      caTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.CA_CERT);
      caTreeView.selection.clearSelection();
    }
  });
}

async function addEmailCert() {
  var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
  let [importEmail, fileCertSpec] = await document.l10n.formatValues([
    { id: "import-email-cert-prompt" },
    { id: "file-browse-certificate-spec" },
  ]);
  fp.init(window.browsingContext, importEmail, Ci.nsIFilePicker.modeOpen);
  fp.appendFilter(fileCertSpec, gCertFileTypes);
  fp.appendFilters(Ci.nsIFilePicker.filterAll);
  fp.open(rv => {
    if (rv == Ci.nsIFilePicker.returnOK) {
      certdb.importCertsFromFile(fp.file, Ci.nsIX509Cert.EMAIL_CERT);
      var certcache = certdb.getCerts();
      emailTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.EMAIL_CERT);
      emailTreeView.selection.clearSelection();
      caTreeView.loadCertsFromCache(certcache, Ci.nsIX509Cert.CA_CERT);
      caTreeView.selection.clearSelection();
    }
  });
}
PK
!<8��9+9+.chrome/pippki/content/pippki/certManager.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window>

<window
  windowtype="mozilla:certmanager"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
  data-l10n-id="certmgr-title"
  onload="LoadCerts();"
  persist="screenX screenY width height"
>
  <dialog id="certmanager" buttons="accept">
    <linkset>
      <html:link rel="stylesheet" href="chrome://global/skin/global.css" />
      <html:link
        rel="stylesheet"
        href="chrome://pippki/content/certManager.css"
      />

      <html:link
        rel="localization"
        href="security/certificates/certManager.ftl"
      />
    </linkset>

    <script src="chrome://pippki/content/pippki.js" />
    <script src="chrome://pippki/content/certManager.js" />

    <tabbox id="certmanagertabs" flex="1" persist="selectedIndex">
      <tabs id="certMgrTabbox">
        <tab id="mine_tab" data-l10n-id="certmgr-tab-mine" />
        <tab id="remembered_tab" data-l10n-id="certmgr-tab-remembered" />
        <tab id="others_tab" data-l10n-id="certmgr-tab-people" />
        <tab id="websites_tab" data-l10n-id="certmgr-tab-servers" />
        <tab id="ca_tab" data-l10n-id="certmgr-tab-ca" selected="true" />
      </tabs>
      <tabpanels flex="1">
        <vbox id="myCerts" flex="1">
          <description data-l10n-id="certmgr-mine"></description>
          <separator class="thin" />
          <tree
            id="user-tree"
            flex="1"
            enableColumnDrag="true"
            onselect="mine_enableButtons()"
          >
            <treecols>
              <!--
              The below code may suggest that 'ordinal' is still a supported XUL
              XUL attribute. It is not. This is a crutch so that we can
              continue persisting the CSS order property,
              which is the appropriate replacement for the ordinal attribute
              but cannot yet be easily persisted. The code that synchronizes
              the attribute with the CSS lives in
              toolkit/content/widget/tree.js and is specific to tree elements.
            -->
              <treecol
                id="certcol"
                data-l10n-id="certmgr-cert-name"
                primary="true"
                persist="hidden width ordinal"
              />
              <splitter class="tree-splitter" />
              <treecol
                id="tokencol"
                data-l10n-id="certmgr-token-name"
                persist="hidden width ordinal"
              />
              <splitter class="tree-splitter" />
              <treecol
                id="serialnumcol"
                data-l10n-id="certmgr-serial"
                persist="hidden width ordinal"
              />
              <splitter class="tree-splitter" />
              <treecol
                id="issuedcol"
                data-l10n-id="certmgr-begins-label"
                hidden="true"
                persist="hidden width ordinal"
              />
              <splitter class="tree-splitter" />
              <treecol
                id="expiredcol"
                data-l10n-id="certmgr-expires-label"
                persist="hidden width ordinal"
              />
            </treecols>
            <treechildren ondblclick="viewCerts();" />
          </tree>

          <separator class="thin" />

          <hbox>
            <button
              id="mine_viewButton"
              class="normal"
              data-l10n-id="certmgr-view"
              disabled="true"
              oncommand="viewCerts();"
            />
            <button
              id="mine_backupButton"
              class="normal"
              data-l10n-id="certmgr-backup"
              disabled="true"
              oncommand="backupCerts();"
            />
            <button
              id="mine_backupAllButton"
              class="normal"
              data-l10n-id="certmgr-backup-all"
              oncommand="backupAllCerts();"
            />
            <button
              id="mine_restoreButton"
              class="normal"
              data-l10n-id="certmgr-restore"
              oncommand="restoreCerts();"
            />
            <button
              id="mine_deleteButton"
              class="normal"
              data-l10n-id="certmgr-delete"
              disabled="true"
              oncommand="deleteCerts();"
            />
          </hbox>
        </vbox>
        <vbox id="rememberedCerts" flex="1">
          <description data-l10n-id="certmgr-remembered"></description>
          <separator class="thin" />

          <listheader equalsize="always">
            <treecol
              id="hostcol"
              data-l10n-id="certmgr-cert-host"
              primary="true"
              persist="hidden width ordinal"
            />
            <treecol
              id="certcol"
              data-l10n-id="certmgr-cert-name"
              primary="true"
              persist="hidden width ordinal"
            />
            <treecol
              id="serialnumcol"
              data-l10n-id="certmgr-serial"
              persist="hidden width ordinal"
            />
          </listheader>
          <richlistbox id="rememberedList" flex="1" selected="false" />

          <separator class="thin" />

          <hbox>
            <button
              id="remembered_deleteButton"
              class="normal"
              data-l10n-id="certmgr-delete"
              oncommand="rememberedDecisionsRichList.deleteSelectedRichListItem()"
            />

            <button
              id="remembered_viewButton"
              class="normal"
              data-l10n-id="certmgr-view"
              oncommand="rememberedDecisionsRichList.viewSelectedRichListItem()"
            />
          </hbox>
        </vbox>
        <vbox id="othersCerts" flex="1">
          <description data-l10n-id="certmgr-people"></description>
          <separator class="thin" />
          <tree id="email-tree" flex="1" onselect="email_enableButtons()">
            <treecols>
              <treecol
                id="certcol"
                data-l10n-id="certmgr-cert-name"
                primary="true"
              />
              <splitter class="tree-splitter" />
              <treecol id="emailcol" data-l10n-id="certmgr-email" />
              <splitter class="tree-splitter" />
              <treecol id="expiredcol" data-l10n-id="certmgr-expires-label" />
            </treecols>
            <treechildren flex="1" ondblclick="viewCerts();" />
          </tree>

          <separator class="thin" />

          <hbox>
            <button
              id="email_viewButton"
              data-l10n-id="certmgr-view"
              disabled="true"
              oncommand="viewCerts();"
            />
            <button
              id="email_addButton"
              data-l10n-id="certmgr-restore"
              oncommand="addEmailCert();"
            />
            <button
              id="email_exportButton"
              data-l10n-id="certmgr-export"
              disabled="true"
              oncommand="exportCerts();"
            />
            <button
              id="email_deleteButton"
              data-l10n-id="certmgr-delete"
              disabled="true"
              oncommand="deleteCerts();"
            />
          </hbox>
        </vbox>

        <vbox id="webCerts" flex="1">
          <description data-l10n-id="certmgr-server"></description>
          <separator class="thin" />

          <listheader equalsize="always">
            <treecol
              id="sitecol"
              data-l10n-id="certmgr-cert-server"
              primary="true"
            />
            <treecol
              id="sha256col"
              data-l10n-id="certmgr-fingerprint-sha-256"
            />
          </listheader>
          <richlistbox
            ondblclick="serverRichList.viewSelectedRichListItem();"
            id="serverList"
            flex="1"
            selected="false"
          />

          <separator class="thin" />

          <hbox>
            <button
              id="websites_deleteButton"
              data-l10n-id="certmgr-delete"
              oncommand="serverRichList.deleteSelectedRichListItem();"
            />
            <button
              id="websites_exceptionButton"
              data-l10n-id="certmgr-add-exception"
              oncommand="serverRichList.addException();"
            />
          </hbox>
        </vbox>
        <vbox id="CACerts" flex="1">
          <description data-l10n-id="certmgr-ca"></description>
          <separator class="thin" />
          <tree
            id="ca-tree"
            flex="1"
            enableColumnDrag="true"
            onselect="ca_enableButtons()"
          >
            <treecols>
              <!--
              The below code may suggest that 'ordinal' is still a supported XUL
              XUL attribute. It is not. This is a crutch so that we can
              continue persisting the CSS order property,
              which is the appropriate replacement for the ordinal attribute
              but cannot yet be easily persisted. The code that synchronizes
              the attribute with the CSS lives in
              toolkit/content/widget/tree.js and is specific to tree elements.
            -->
              <treecol
                id="certcol"
                data-l10n-id="certmgr-cert-name"
                primary="true"
                persist="hidden width ordinal"
              />
              <splitter class="tree-splitter" />
              <treecol
                id="tokencol"
                data-l10n-id="certmgr-token-name"
                persist="hidden width ordinal"
              />
            </treecols>
            <treechildren ondblclick="viewCerts();" />
          </tree>

          <separator class="thin" />

          <hbox>
            <button
              id="ca_viewButton"
              data-l10n-id="certmgr-view"
              disabled="true"
              oncommand="viewCerts();"
            />
            <button
              id="ca_editButton"
              data-l10n-id="certmgr-edit"
              disabled="true"
              oncommand="editCerts();"
            />
            <button
              id="ca_addButton"
              data-l10n-id="certmgr-restore"
              oncommand="addCACerts();"
            />
            <button
              id="ca_exportButton"
              data-l10n-id="certmgr-export"
              disabled="true"
              oncommand="exportCerts();"
            />
            <button
              id="ca_deleteButton"
              data-l10n-id="certmgr-delete-builtin"
              disabled="true"
              oncommand="deleteCerts();"
            />
          </hbox>
        </vbox>
      </tabpanels>
    </tabbox>
  </dialog>
</window>
PK
!<��.chrome/pippki/content/pippki/changepassword.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { XPCOMUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/XPCOMUtils.sys.mjs"
);

ChromeUtils.defineLazyGetter(
  this,
  "l10n",
  () => new Localization(["security/pippki/pippki.ftl"], true)
);

var params;
var token;
var pw1;

function doPrompt(messageL10nId) {
  let msg = l10n.formatValueSync(messageL10nId);
  Services.prompt.alert(window, null, msg);
}

function onLoad() {
  document.getElementById("set_password").getButton("accept").disabled = true;
  document.addEventListener("dialogaccept", setPassword);

  pw1 = document.getElementById("pw1");
  params = window.arguments[0].QueryInterface(Ci.nsIDialogParamBlock);
  token = params.objects.GetElementAt(0).QueryInterface(Ci.nsIPK11Token);

  document.l10n.setAttributes(
    document.getElementById("tokenName"),
    "change-password-token",
    { tokenName: token.tokenName }
  );
  process();
}

function process() {
  let bundle = document.getElementById("pippki_bundle");
  let oldpwbox = document.getElementById("oldpw");
  let msgBox = document.getElementById("message");
  // If the token is unitialized, don't use the old password box.
  // Otherwise, do.
  if ((token.needsLogin() && token.needsUserInit) || !token.needsLogin()) {
    oldpwbox.hidden = true;
    msgBox.setAttribute("value", bundle.getString("password_not_set"));
    msgBox.hidden = false;

    if (!token.needsLogin()) {
      oldpwbox.setAttribute("inited", "empty");
    } else {
      oldpwbox.setAttribute("inited", "true");
    }

    // Select first password field
    document.getElementById("pw1").focus();
  } else {
    // Select old password field
    oldpwbox.hidden = false;
    msgBox.hidden = true;
    oldpwbox.setAttribute("inited", "false");
    oldpwbox.focus();
  }

  // Return value 0 means "canceled"
  params.SetInt(1, 0);

  checkPasswords();
}

function setPassword(event) {
  var oldpwbox = document.getElementById("oldpw");
  var initpw = oldpwbox.getAttribute("inited");

  var success = false;

  if (initpw == "false" || initpw == "empty") {
    try {
      var oldpw = "";
      var passok = 0;

      if (initpw == "empty") {
        passok = 1;
      } else {
        oldpw = oldpwbox.value;
        passok = token.checkPassword(oldpw);
      }

      if (passok) {
        if (initpw == "empty" && pw1.value == "") {
          // checkPasswords() should have prevented this path from being reached.
        } else {
          if (pw1.value == "") {
            var secmoddb = Cc[
              "@mozilla.org/security/pkcs11moduledb;1"
            ].getService(Ci.nsIPKCS11ModuleDB);
            if (secmoddb.isFIPSEnabled) {
              // empty passwords are not allowed in FIPS mode
              doPrompt("pippki-pw-change2empty-in-fips-mode");
              passok = 0;
            }
          }
          if (passok) {
            token.changePassword(oldpw, pw1.value);
            if (pw1.value == "") {
              doPrompt("pippki-pw-erased-ok");
            } else {
              doPrompt("pippki-pw-change-ok");
            }
            success = true;
          }
        }
      } else {
        oldpwbox.focus();
        oldpwbox.setAttribute("value", "");
        doPrompt("pippki-incorrect-pw");
      }
    } catch (e) {
      doPrompt("pippki-failed-pw-change");
    }
  } else {
    token.initPassword(pw1.value);
    if (pw1.value == "") {
      doPrompt("pippki-pw-not-wanted");
    }
    success = true;
  }

  if (success && params) {
    // Return value 1 means "successfully executed ok"
    params.SetInt(1, 1);
  }

  // Terminate dialog
  if (!success) {
    event.preventDefault();
  }
}

function setPasswordStrength() {
  // We weigh the quality of the password by checking the number of:
  //  - Characters
  //  - Numbers
  //  - Non-alphanumeric chars
  //  - Upper and lower case characters

  let pw = document.getElementById("pw1").value;

  let pwlength = pw.length;
  if (pwlength > 5) {
    pwlength = 5;
  }

  let numnumeric = pw.replace(/[0-9]/g, "");
  let numeric = pw.length - numnumeric.length;
  if (numeric > 3) {
    numeric = 3;
  }

  let symbols = pw.replace(/\W/g, "");
  let numsymbols = pw.length - symbols.length;
  if (numsymbols > 3) {
    numsymbols = 3;
  }

  let numupper = pw.replace(/[A-Z]/g, "");
  let upper = pw.length - numupper.length;
  if (upper > 3) {
    upper = 3;
  }

  let pwstrength =
    pwlength * 10 - 20 + numeric * 10 + numsymbols * 15 + upper * 10;

  // Clamp strength to [0, 100].
  if (pwstrength < 0) {
    pwstrength = 0;
  }
  if (pwstrength > 100) {
    pwstrength = 100;
  }

  let meter = document.getElementById("pwmeter");
  meter.setAttribute("value", pwstrength);
}

function checkPasswords() {
  let pw1 = document.getElementById("pw1").value;
  let pw2 = document.getElementById("pw2").value;

  var oldpwbox = document.getElementById("oldpw");
  if (oldpwbox) {
    var initpw = oldpwbox.getAttribute("inited");

    if (initpw == "empty" && pw1 == "") {
      // The token has already been initialized, therefore this dialog
      // was called with the intention to change the password.
      // The token currently uses an empty password.
      // We will not allow changing the password from empty to empty.
      document
        .getElementById("set_password")
        .getButton("accept").disabled = true;
      return;
    }
  }

  document.getElementById("set_password").getButton("accept").disabled =
    pw1 != pw2;
}
PK
!<��s�	�	1chrome/pippki/content/pippki/changepassword.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window>

<window
  data-l10n-id="change-device-password-window"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
  onload="onLoad();"
>
  <dialog id="set_password" buttons="accept,cancel">
    <linkset>
      <html:link rel="stylesheet" href="chrome://global/skin/global.css" />

      <html:link rel="localization" href="security/pippki/pippki.ftl" />
    </linkset>

    <stringbundle
      id="pippki_bundle"
      src="chrome://pippki/locale/pippki.properties"
    />

    <script src="chrome://global/content/globalOverlay.js" />
    <script src="chrome://global/content/editMenuOverlay.js" />

    <script src="chrome://pippki/content/changepassword.js" />

    <hbox align="center">
      <label
        id="tokenName"
        data-l10n-id="change-password-token"
        data-l10n-args='{"tokenName":""}'
      />
    </hbox>

    <separator />

    <vbox>
      <hbox class="input-row">
        <label flex="1" data-l10n-id="change-password-old" />
        <html:input id="oldpw" type="password" />
        <!-- This textbox is inserted as a workaround to the fact that making the 'type'
         & 'disabled' property of the 'oldpw' textbox toggle between ['password' &
         'false'] and ['text' & 'true'] - as would be necessary if the menu has more
         than one tokens, some initialized and some not - does not work properly. So,
         either the textbox 'oldpw' or the textbox 'message' would be displayed,
         depending on the state of the token selected
     -->
        <html:input id="message" disabled="true" />
      </hbox>
      <hbox class="input-row">
        <label flex="1" data-l10n-id="change-password-new" />
        <html:input
          id="pw1"
          type="password"
          oninput="setPasswordStrength(); checkPasswords();"
        />
      </hbox>
      <hbox class="input-row">
        <label flex="1" data-l10n-id="change-password-reenter" />
        <html:input id="pw2" type="password" oninput="checkPasswords();" />
      </hbox>
    </vbox>

    <vbox style="margin: 6px">
      <label
        for="pwmeter"
        style="display: flex"
        data-l10n-id="password-quality-meter"
      />
      <html:progress id="pwmeter" value="0" max="100" />
    </vbox>
  </dialog>
</window>
PK
!<�fIdd.chrome/pippki/content/pippki/clientauthask.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:root {
  min-width: 48em;
}

.important {
  font-weight: bold;
}

.details {
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
}
PK
!<4&y���-chrome/pippki/content/pippki/clientauthask.js/* -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from pippki.js */
"use strict";

const { parse, pemToDER } = ChromeUtils.importESModule(
  "chrome://global/content/certviewer/certDecoder.mjs"
);

/**
 * @file Implements the functionality of clientauthask.xhtml: a dialog that allows
 *       a user pick a client certificate for TLS client authentication.
 * @param {object} window.arguments.0
 *           An Object with the properties:
 *              {String} hostname
 *                 The hostname of the server requesting client authentication.
 *              {Array<nsIX509Cert>} certArray
 *                 Array of certificates the user can choose from
 *              {Object} retVal
 *                 Object to set the return values of calling the dialog on.
 *                 See ClientAuthAskReturnValues.
 */

/**
 * @typedef ClientAuthAskReturnValues
 * @type {object}
 * @property {nsIX509Cert} cert
 *           The certificate, if chosen. null otherwise.
 * @property {boolean} rememberDecision
 *           Set to true if the user wanted their cert selection to be
 *           remembered, false otherwise.
 */

/**
 * The array of certs the user can choose from.
 *
 * @type {Array<nsIX509Cert>}
 */
var certArray;

/**
 * The checkbox storing whether the user wants to remember the selected cert.
 *
 * @type {HTMLInputElement} Element checkbox, has to have |checked| property.
 */
var rememberBox, args;

async function onLoad() {
  let rememberSetting = Services.prefs.getBoolPref(
    "security.remember_cert_checkbox_default_setting"
  );
  rememberBox = document.getElementById("rememberBox");
  rememberBox.checked = rememberSetting;

  let propBag = window.arguments[0]
    .QueryInterface(Ci.nsIWritablePropertyBag2)
    .QueryInterface(Ci.nsIWritablePropertyBag);
  args = {};
  for (let prop of propBag.enumerator) {
    args[prop.name] = prop.value;
  }

  certArray = args.certArray;

  document.l10n.setAttributes(
    document.getElementById("clientAuthSiteIdentification"),
    "client-auth-site-identification",
    { hostname: args.hostname }
  );

  let selectElement = document.getElementById("nicknames");
  for (let i = 0; i < certArray.length; i++) {
    let menuItemNode = document.createXULElement("menuitem");
    let cert = certArray[i];
    let nickAndSerial = `${cert.displayName} [${cert.serialNumber}]`;
    menuItemNode.setAttribute("value", i);
    menuItemNode.setAttribute("label", nickAndSerial); // This is displayed.
    selectElement.menupopup.appendChild(menuItemNode);
    if (i == 0) {
      selectElement.selectedItem = menuItemNode;
    }
  }

  await setDetails();
  document.addEventListener("dialogaccept", doOK);
  document.addEventListener("dialogcancel", doCancel);

  Services.obs.notifyObservers(
    document.getElementById("certAuthAsk"),
    "cert-dialog-loaded"
  );
}

/**
 * Populates the details section with information concerning the selected cert.
 */
async function setDetails() {
  let index = parseInt(document.getElementById("nicknames").value);
  let cert = certArray[index];
  document.l10n.setAttributes(
    document.getElementById("clientAuthCertDetailsIssuedTo"),
    "client-auth-cert-details-issued-to",
    { issuedTo: cert.subjectName }
  );
  document.l10n.setAttributes(
    document.getElementById("clientAuthCertDetailsSerialNumber"),
    "client-auth-cert-details-serial-number",
    { serialNumber: cert.serialNumber }
  );
  const formatter = new Intl.DateTimeFormat(undefined, {
    dateStyle: "medium",
    timeStyle: "long",
  });
  document.l10n.setAttributes(
    document.getElementById("clientAuthCertDetailsValidityPeriod"),
    "client-auth-cert-details-validity-period",
    {
      notBefore: formatter.format(new Date(cert.validity.notBefore / 1000)),
      notAfter: formatter.format(new Date(cert.validity.notAfter / 1000)),
    }
  );
  let parsedCert = await parse(pemToDER(cert.getBase64DERString()));
  let keyUsages = parsedCert.ext.keyUsages;
  let keyUsagesJoined =
    keyUsages && keyUsages.purposes.length ? keyUsages.purposes.join(", ") : "";
  document.l10n.setAttributes(
    document.getElementById("clientAuthCertDetailsKeyUsages"),
    "client-auth-cert-details-key-usages",
    { keyUsages: keyUsagesJoined }
  );
  let emailAddresses = cert.getEmailAddresses();
  let emailAddressesJoined = emailAddresses.length
    ? emailAddresses.join(", ")
    : "";
  document.l10n.setAttributes(
    document.getElementById("clientAuthCertDetailsEmailAddresses"),
    "client-auth-cert-details-email-addresses",
    { emailAddresses: emailAddressesJoined }
  );
  document.l10n.setAttributes(
    document.getElementById("clientAuthCertDetailsIssuedBy"),
    "client-auth-cert-details-issued-by",
    { issuedBy: cert.issuerName }
  );
  document.l10n.setAttributes(
    document.getElementById("clientAuthCertDetailsStoredOn"),
    "client-auth-cert-details-stored-on",
    { storedOn: cert.tokenName }
  );
}

async function onCertSelected() {
  await setDetails();
}

function doOK() {
  let { retVals } = args;
  let index = parseInt(document.getElementById("nicknames").value);
  let cert = certArray[index];
  retVals.cert = cert;
  retVals.rememberDecision = rememberBox.checked;
}

function doCancel() {
  let { retVals } = args;
  retVals.cert = null;
  retVals.rememberDecision = rememberBox.checked;
}
PK
!<��c�7
7
0chrome/pippki/content/pippki/clientauthask.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window>

<window
  data-l10n-id="client-auth-window"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
  onload="onLoad();"
>
  <dialog
    id="certAuthAsk"
    buttons="accept,cancel"
    buttonidcancel="client-auth-send-no-certificate"
  >
    <linkset>
      <html:link rel="stylesheet" href="chrome://global/skin/global.css" />
      <html:link
        rel="stylesheet"
        href="chrome://pippki/content/clientauthask.css"
      />

      <html:link rel="localization" href="security/pippki/pippki.ftl" />
    </linkset>

    <stringbundleset id="stringbundleset">
      <stringbundle
        id="pippki_bundle"
        src="chrome://pippki/locale/pippki.properties"
      />
    </stringbundleset>

    <script src="chrome://pippki/content/pippki.js" />
    <script src="chrome://pippki/content/clientauthask.js" />
    <script src="chrome://global/content/globalOverlay.js" />
    <script src="chrome://global/content/editMenuOverlay.js" />

    <description
      class="important"
      id="clientAuthSiteIdentification"
      data-l10n-id="client-auth-site-identification"
      data-l10n-args='{"hostname":""}'
    ></description>

    <!-- The items in this menulist must never be sorted,
     but remain in the order filled by the application
-->
    <menulist id="nicknames" oncommand="onCertSelected();" native="true">
      <menupopup />
    </menulist>

    <description
      class="important"
      data-l10n-id="client-auth-cert-details"
    ></description>

    <description
      id="clientAuthCertDetailsIssuedTo"
      class="details"
      data-l10n-id="client-auth-cert-details-issued-to"
      data-l10n-args='{"issuedTo":""}'
    ></description>

    <description
      id="clientAuthCertDetailsSerialNumber"
      class="details"
      data-l10n-id="client-auth-cert-details-serial-number"
      data-l10n-args='{"serialNumber":""}'
    ></description>

    <description
      id="clientAuthCertDetailsValidityPeriod"
      class="details"
      data-l10n-id="client-auth-cert-details-validity-period"
      data-l10n-args='{"notBefore":"","notAfter":""}'
    ></description>

    <description
      id="clientAuthCertDetailsKeyUsages"
      class="details"
      data-l10n-id="client-auth-cert-details-key-usages"
      data-l10n-args='{"keyUsages":""}'
    ></description>

    <description
      id="clientAuthCertDetailsEmailAddresses"
      class="details"
      data-l10n-id="client-auth-cert-details-email-addresses"
      data-l10n-args='{"emailAddresses":""}'
    ></description>

    <description
      id="clientAuthCertDetailsIssuedBy"
      class="details"
      data-l10n-id="client-auth-cert-details-issued-by"
      data-l10n-args='{"issuedBy":""}'
    ></description>

    <description
      id="clientAuthCertDetailsStoredOn"
      class="details"
      data-l10n-id="client-auth-cert-details-stored-on"
      data-l10n-args='{"storedOn":""}'
    ></description>

    <checkbox
      id="rememberBox"
      data-l10n-id="client-auth-cert-remember-box"
      checked="true"
      native="true"
    />
  </dialog>
</window>
PK
!<(��V��+chrome/pippki/content/pippki/deletecert.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

dialog::part(content-box) {
  flex: 1;
}

#confirm,
#impact {
  /* We don't want these to impact the horizontal size of the dialog */
  contain: inline-size;
}

#impact {
  margin-block-start: 12px;
}

#certlist {
  flex: 1;
  min-height: 8em;
  contain: size;
  min-width: 35em;
}
PK
!<_��/
/
*chrome/pippki/content/pippki/deletecert.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from pippki.js */
"use strict";

/**
 * @file Implements the functionality of deletecert.xhtml: a dialog that allows a
 *       user to confirm whether to delete certain certificates.
 * @param {string} window.arguments.0
 *           One of the tab IDs listed in certManager.xhtml.
 * @param {object[]} window.arguments.1
 *           An array of objects representing the certs to delete.
 *           Each must have a 'cert' property or a 'hostPort' property.
 * @param {DeleteCertReturnValues} window.arguments.2
 *           Object holding the return values of calling the dialog.
 */

/**
 * @typedef DeleteCertReturnValues
 * @type {object}
 * @property {boolean} deleteConfirmed
 *           Set to true if the user confirmed deletion of the given certs,
 *           false otherwise.
 */

/**
 * Returns the element to represent the given cert to delete.
 *
 * @param {object} certToDelete
 *        The item to represent.
 * @returns {Element}
 *          A element of each cert tree item.
 */
function getLabelForCertToDelete(certToDelete) {
  let element = document.createXULElement("label");
  let cert = certToDelete.cert;
  if (!cert) {
    element.setAttribute("value", certToDelete.hostPort);
    return element;
  }

  const attributes = [
    cert.commonName,
    cert.organizationalUnit,
    cert.organization,
    cert.subjectName,
  ];
  for (let attribute of attributes) {
    if (attribute) {
      element.setAttribute("value", attribute);
      return element;
    }
  }

  document.l10n.setAttributes(element, "cert-with-serial", {
    serialNumber: cert.serialNumber,
  });
  return element;
}

/**
 * onload() handler.
 */
function onLoad() {
  let typeFlag = window.arguments[0];
  let confirm = document.getElementById("confirm");
  let impact = document.getElementById("impact");
  let prefixForType;
  switch (typeFlag) {
    case "mine_tab":
      prefixForType = "delete-user-cert-";
      break;
    case "websites_tab":
      prefixForType = "delete-ssl-override-";
      break;
    case "ca_tab":
      prefixForType = "delete-ca-cert-";
      break;
    case "others_tab":
      prefixForType = "delete-email-cert-";
      break;
    default:
      return;
  }

  document.l10n.setAttributes(
    document.documentElement,
    prefixForType + "title"
  );
  document.l10n.setAttributes(confirm, prefixForType + "confirm");
  document.l10n.setAttributes(impact, prefixForType + "impact");

  document.addEventListener("dialogaccept", onDialogAccept);
  document.addEventListener("dialogcancel", onDialogCancel);

  let box = document.getElementById("certlist");
  let certsToDelete = window.arguments[1];
  for (let certToDelete of certsToDelete) {
    let listItem = document.createXULElement("richlistitem");
    let label = getLabelForCertToDelete(certToDelete);
    listItem.appendChild(label);
    box.appendChild(listItem);
  }
}

/**
 * ondialogaccept() handler.
 */
function onDialogAccept() {
  let retVals = window.arguments[2];
  retVals.deleteConfirmed = true;
}

/**
 * ondialogcancel() handler.
 */
function onDialogCancel() {
  let retVals = window.arguments[2];
  retVals.deleteConfirmed = false;
}
PK
!<��]00-chrome/pippki/content/pippki/deletecert.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window>

<window
  data-l10n-id="certmgr-delete-cert2"
  data-l10n-attrs="style"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
  onload="onLoad();"
>
  <dialog id="deleteCertificate" buttons="accept,cancel">
    <linkset>
      <html:link rel="stylesheet" href="chrome://global/skin/global.css" />
      <html:link
        rel="stylesheet"
        href="chrome://pippki/content/deletecert.css"
      />

      <html:link
        rel="localization"
        href="security/certificates/certManager.ftl"
      />
    </linkset>

    <script src="pippki.js" />
    <script src="chrome://pippki/content/deletecert.js" />

    <description id="confirm" />
    <richlistbox id="certlist" class="box-padded" />
    <description id="impact" />
  </dialog>
</window>
PK
!<�V�a.a..chrome/pippki/content/pippki/device_manager.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

const { XPCOMUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/XPCOMUtils.sys.mjs"
);

var secmoddb;
var skip_enable_buttons = false;

/* Do the initial load of all PKCS# modules and list them. */
function LoadModules() {
  secmoddb = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService(
    Ci.nsIPKCS11ModuleDB
  );
  RefreshDeviceList();
}

async function doPrompt(l10n_id) {
  let [msg] = await document.l10n.formatValues([{ id: l10n_id }]);
  Services.prompt.alert(window, null, msg);
}

async function doConfirm(l10n_id) {
  let [msg] = await document.l10n.formatValues([{ id: l10n_id }]);
  return Services.prompt.confirm(window, null, msg);
}

function RefreshDeviceList() {
  for (let module of secmoddb.listModules()) {
    let slots = module.listSlots();
    AddModule(module, slots);
  }

  // Set the text on the FIPS button.
  SetFIPSButton();
}

function SetFIPSButton() {
  var fipsButton = document.getElementById("fipsbutton");
  if (secmoddb.isFIPSEnabled) {
    document.l10n.setAttributes(fipsButton, "devmgr-button-disable-fips");
  } else {
    document.l10n.setAttributes(fipsButton, "devmgr-button-enable-fips");
  }

  var can_toggle = secmoddb.canToggleFIPS;
  if (can_toggle) {
    fipsButton.removeAttribute("disabled");
  } else {
    fipsButton.setAttribute("disabled", "true");
  }
}

/* Add a module to the tree.  slots is the array of slots in the module,
 * to be represented as children.
 */
function AddModule(module, slots) {
  var tree = document.getElementById("device_list");
  var item = document.createXULElement("treeitem");
  var row = document.createXULElement("treerow");
  var cell = document.createXULElement("treecell");
  cell.setAttribute("label", module.name);
  row.appendChild(cell);
  item.appendChild(row);
  var parent = document.createXULElement("treechildren");
  for (let slot of slots) {
    var child_item = document.createXULElement("treeitem");
    var child_row = document.createXULElement("treerow");
    var child_cell = document.createXULElement("treecell");
    child_cell.setAttribute("label", slot.name);
    child_row.appendChild(child_cell);
    child_item.appendChild(child_row);
    child_item.setAttribute("pk11kind", "slot");
    // 'slot' is an attribute on any HTML element, hence 'slotObject' instead.
    child_item.slotObject = slot;
    parent.appendChild(child_item);
  }
  item.appendChild(parent);
  item.setAttribute("pk11kind", "module");
  item.module = module;
  item.setAttribute("open", "true");
  item.setAttribute("container", "true");
  tree.appendChild(item);
}

var selected_slot;
var selected_module;

/* get the slot selected by the user (can only be one-at-a-time) */
function getSelectedItem() {
  let tree = document.getElementById("device_tree");
  if (tree.currentIndex < 0) {
    return;
  }
  let item = tree.view.getItemAtIndex(tree.currentIndex);
  selected_slot = null;
  selected_module = null;
  if (item) {
    let kind = item.getAttribute("pk11kind");
    if (kind == "slot") {
      selected_slot = item.slotObject;
    } else {
      // (kind == "module")
      selected_module = item.module;
    }
  }
}

function enableButtons() {
  if (skip_enable_buttons) {
    return;
  }

  var login_toggle = "true";
  var logout_toggle = "true";
  var pw_toggle = "true";
  var unload_toggle = "true";
  getSelectedItem();
  if (selected_module) {
    unload_toggle = "false";
    showModuleInfo();
  } else if (selected_slot) {
    // here's the workaround - login functions are all with token,
    // so grab the token type
    var selected_token = selected_slot.getToken();
    if (selected_token != null) {
      if (selected_token.needsLogin() || !selected_token.needsUserInit) {
        pw_toggle = "false";
        if (selected_token.needsLogin()) {
          if (selected_token.isLoggedIn()) {
            logout_toggle = "false";
          } else {
            login_toggle = "false";
          }
        }
      }

      if (
        !Services.policies.isAllowed("createMasterPassword") &&
        selected_token.isInternalKeyToken &&
        !selected_token.hasPassword
      ) {
        pw_toggle = "true";
      }
    }
    showSlotInfo();
  }
  document
    .getElementById("login_button")
    .setAttribute("disabled", login_toggle);
  document
    .getElementById("logout_button")
    .setAttribute("disabled", logout_toggle);
  document
    .getElementById("change_pw_button")
    .setAttribute("disabled", pw_toggle);
  document
    .getElementById("unload_button")
    .setAttribute("disabled", unload_toggle);
}

// clear the display of information for the slot
function ClearInfoList() {
  let infoList = document.getElementById("info_list");
  while (infoList.hasChildNodes()) {
    infoList.firstChild.remove();
  }
}

function ClearDeviceList() {
  ClearInfoList();

  skip_enable_buttons = true;
  var tree = document.getElementById("device_tree");
  tree.view.selection.clearSelection();
  skip_enable_buttons = false;

  // Remove the existing listed modules so that a refresh doesn't display the
  // module that just changed.
  let deviceList = document.getElementById("device_list");
  while (deviceList.hasChildNodes()) {
    deviceList.firstChild.remove();
  }
}

// show a list of info about a slot
function showSlotInfo() {
  var present = true;
  ClearInfoList();
  switch (selected_slot.status) {
    case Ci.nsIPKCS11Slot.SLOT_DISABLED:
      AddInfoRow(
        "devinfo-status",
        { l10nID: "devinfo-status-disabled" },
        "tok_status"
      );
      present = false;
      break;
    case Ci.nsIPKCS11Slot.SLOT_NOT_PRESENT:
      AddInfoRow(
        "devinfo-status",
        { l10nID: "devinfo-status-not-present" },
        "tok_status"
      );
      present = false;
      break;
    case Ci.nsIPKCS11Slot.SLOT_UNINITIALIZED:
      AddInfoRow(
        "devinfo-status",
        { l10nID: "devinfo-status-uninitialized" },
        "tok_status"
      );
      break;
    case Ci.nsIPKCS11Slot.SLOT_NOT_LOGGED_IN:
      AddInfoRow(
        "devinfo-status",
        { l10nID: "devinfo-status-not-logged-in" },
        "tok_status"
      );
      break;
    case Ci.nsIPKCS11Slot.SLOT_LOGGED_IN:
      AddInfoRow(
        "devinfo-status",
        { l10nID: "devinfo-status-logged-in" },
        "tok_status"
      );
      break;
    case Ci.nsIPKCS11Slot.SLOT_READY:
      AddInfoRow(
        "devinfo-status",
        { l10nID: "devinfo-status-ready" },
        "tok_status"
      );
      break;
    default:
      return;
  }
  AddInfoRow("devinfo-desc", { label: selected_slot.desc }, "slot_desc");
  AddInfoRow("devinfo-man-id", { label: selected_slot.manID }, "slot_manID");
  AddInfoRow(
    "devinfo-hwversion",
    { label: selected_slot.HWVersion },
    "slot_hwv"
  );
  AddInfoRow(
    "devinfo-fwversion",
    { label: selected_slot.FWVersion },
    "slot_fwv"
  );
  if (present) {
    showTokenInfo();
  }
}

function showModuleInfo() {
  ClearInfoList();
  AddInfoRow("devinfo-modname", { label: selected_module.name }, "module_name");
  AddInfoRow(
    "devinfo-modpath",
    { label: selected_module.libName },
    "module_path"
  );
}

// add a row to the info list, as [col1 col2] (ex.: ["status" "logged in"])
function AddInfoRow(l10nID, col2, cell_id) {
  var tree = document.getElementById("info_list");
  var item = document.createXULElement("treeitem");
  var row = document.createXULElement("treerow");
  var cell1 = document.createXULElement("treecell");
  document.l10n.setAttributes(cell1, l10nID);
  cell1.setAttribute("crop", "never");
  row.appendChild(cell1);
  var cell2 = document.createXULElement("treecell");
  if (col2.l10nID) {
    document.l10n.setAttributes(cell2, col2.l10nID);
  } else {
    cell2.setAttribute("label", col2.label);
  }
  cell2.setAttribute("crop", "never");
  cell2.setAttribute("id", cell_id);
  row.appendChild(cell2);
  item.appendChild(row);
  tree.appendChild(item);
}

// log in to a slot
function doLogin() {
  getSelectedItem();
  // here's the workaround - login functions are with token
  var selected_token = selected_slot.getToken();
  try {
    selected_token.login(false);
    var tok_status = document.getElementById("tok_status");
    if (selected_token.isLoggedIn()) {
      document.l10n.setAttributes(tok_status, "devinfo-status-logged-in");
    } else {
      document.l10n.setAttributes(tok_status, "devinfo-status-not-logged-in");
    }
  } catch (e) {
    doPrompt("login-failed");
  }
  enableButtons();
}

// log out of a slot
function doLogout() {
  getSelectedItem();
  // here's the workaround - login functions are with token
  var selected_token = selected_slot.getToken();
  try {
    selected_token.logoutAndDropAuthenticatedResources();
    var tok_status = document.getElementById("tok_status");
    if (selected_token.isLoggedIn()) {
      document.l10n.setAttributes(tok_status, "devinfo-status-logged-in");
    } else {
      document.l10n.setAttributes(tok_status, "devinfo-status-not-logged-in");
    }
  } catch (e) {}
  enableButtons();
}

// load a new device
function doLoad() {
  window.browsingContext.topChromeWindow.open(
    "load_device.xhtml",
    "loaddevice",
    "chrome,centerscreen,modal"
  );
  ClearDeviceList();
  RefreshDeviceList();
}

async function deleteSelected() {
  getSelectedItem();
  if (selected_module && (await doConfirm("del-module-warning"))) {
    try {
      secmoddb.deleteModule(selected_module.name);
    } catch (e) {
      doPrompt("del-module-error");
      return false;
    }
    selected_module = null;
    return true;
  }
  return false;
}

async function doUnload() {
  if (await deleteSelected()) {
    ClearDeviceList();
    RefreshDeviceList();
  }
}

function changePassword() {
  getSelectedItem();
  let params = Cc["@mozilla.org/embedcomp/dialogparam;1"].createInstance(
    Ci.nsIDialogParamBlock
  );
  let objects = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
  objects.appendElement(selected_slot.getToken());
  params.objects = objects;
  window.browsingContext.topChromeWindow.openDialog(
    "changepassword.xhtml",
    "",
    "chrome,centerscreen,modal",
    params
  );
  showSlotInfo();
  enableButtons();
}

// -------------------------------------   Old code

function showTokenInfo() {
  var selected_token = selected_slot.getToken();
  AddInfoRow("devinfo-label", { label: selected_token.tokenName }, "tok_label");
  AddInfoRow(
    "devinfo-man-id",
    { label: selected_token.tokenManID },
    "tok_manID"
  );
  AddInfoRow(
    "devinfo-serialnum",
    { label: selected_token.tokenSerialNumber },
    "tok_sNum"
  );
  AddInfoRow(
    "devinfo-hwversion",
    { label: selected_token.tokenHWVersion },
    "tok_hwv"
  );
  AddInfoRow(
    "devinfo-fwversion",
    { label: selected_token.tokenFWVersion },
    "tok_fwv"
  );
}

function toggleFIPS() {
  if (!secmoddb.isFIPSEnabled) {
    // A restriction of FIPS mode is, the password must be set
    // In FIPS mode the password must be non-empty.
    // This is different from what we allow in NON-Fips mode.

    var tokendb = Cc["@mozilla.org/security/pk11tokendb;1"].getService(
      Ci.nsIPK11TokenDB
    );
    var internal_token = tokendb.getInternalKeyToken(); // nsIPK11Token
    if (!internal_token.hasPassword) {
      // Token has either no or an empty password.
      doPrompt("fips-nonempty-primary-password-required");
      return;
    }
  }

  try {
    secmoddb.toggleFIPSMode();
  } catch (e) {
    doPrompt("unable-to-toggle-fips");
    return;
  }

  // Remove the existing listed modules so that a refresh doesn't display the
  // module that just changed.
  ClearDeviceList();

  RefreshDeviceList();
}
PK
!<��1chrome/pippki/content/pippki/device_manager.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE dialog>

<window
  windowtype="mozilla:devicemanager"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
  data-l10n-id="devmgr-window"
  data-l10n-attrs="style"
  persist="screenX screenY width height"
  onload="LoadModules();"
>
  <dialog id="devicemanager" buttons="accept">
    <linkset>
      <html:link rel="stylesheet" href="chrome://global/skin/global.css" />

      <html:link
        rel="localization"
        href="security/certificates/deviceManager.ftl"
      />
    </linkset>

    <script src="chrome://pippki/content/device_manager.js" />

    <hbox flex="1" style="margin: 5px">
      <!-- List of devices -->
      <tree
        id="device_tree"
        seltype="single"
        onselect="enableButtons();"
        hidecolumnpicker="true"
        flex="1"
        style="min-width: 15em"
      >
        <treecols>
          <treecol
            id="deviceCol"
            flex="1"
            primary="true"
            data-l10n-id="devmgr-devlist"
          />
        </treecols>
        <treechildren id="device_list" />
      </tree>
      <!-- / List of devices -->
      <!-- Device status -->
      <tree
        id="info_tree"
        seltype="single"
        hidecolumnpicker="true"
        style="flex: 3 3; min-width: 10em"
      >
        <treecols>
          <treecol
            id="title1Col"
            style="flex: 5 5 auto"
            primary="true"
            data-l10n-id="devmgr-header-details"
          />
          <treecol
            id="title2Col"
            style="flex: 7 7 auto"
            data-l10n-id="devmgr-header-value"
          />
        </treecols>
        <treechildren id="info_list" />
      </tree>
      <!-- / Device status -->
      <vbox>
        <!-- Buttons for manipulating devices -->
        <button
          id="login_button"
          data-l10n-id="devmgr-button-login"
          oncommand="doLogin();"
          disabled="true"
        />
        <button
          id="logout_button"
          data-l10n-id="devmgr-button-logout"
          oncommand="doLogout();"
          disabled="true"
        />
        <button
          id="change_pw_button"
          data-l10n-id="devmgr-button-changepw"
          oncommand="changePassword();"
          disabled="true"
        />
        <button
          id="load_button"
          data-l10n-id="devmgr-button-load"
          oncommand="doLoad();"
        />
        <button
          id="unload_button"
          data-l10n-id="devmgr-button-unload"
          oncommand="doUnload();"
          disabled="true"
        />
        <button
          id="fipsbutton"
          data-l10n-id="devmgr-button-enable-fips"
          oncommand="toggleFIPS();"
        />
      </vbox>
      <!-- / Buttons for manipulating devices -->
    </hbox>
  </dialog>
</window>
PK
!<�)Mx
x
,chrome/pippki/content/pippki/downloadcert.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from pippki.js */
"use strict";

/**
 * @file Implements the functionality of downloadcert.xhtml: a dialog that allows
 *       a user to confirm whether to import a certificate, and if so what trust
 *       to give it.
 * @param {nsISupports} window.arguments.0
 *           Certificate to confirm import of, queryable to nsIX509Cert.
 * @param {nsISupports} window.arguments.1
 *           Object to set the return values of calling the dialog on, queryable
 *           to the underlying type of DownloadCertReturnValues.
 */

/**
 * @typedef DownloadCertReturnValues
 * @type {nsIWritablePropertyBag2}
 * @property {boolean} importConfirmed
 *           Set to true if the user confirmed import of the cert and accepted
 *           the dialog, false otherwise.
 * @property {boolean} trustForSSL
 *           Set to true if the cert should be trusted for SSL, false otherwise.
 *           Undefined value if |importConfirmed| is not true.
 * @property {boolean} trustForEmail
 *           Set to true if the cert should be trusted for e-mail, false
 *           otherwise. Undefined value if |importConfirmed| is not true.
 */

/**
 * The cert to potentially import.
 *
 * @type {nsIX509Cert}
 */
var gCert;

/**
 * onload() handler.
 */
function onLoad() {
  gCert = window.arguments[0].QueryInterface(Ci.nsIX509Cert);

  document.addEventListener("dialogaccept", onDialogAccept);
  document.addEventListener("dialogcancel", onDialogCancel);

  let bundle = document.getElementById("pippki_bundle");
  let caName = gCert.commonName;
  if (!caName.length) {
    caName = bundle.getString("unnamedCA");
  }

  setText("trustHeader", bundle.getFormattedString("newCAMessage1", [caName]));
}

/**
 * Handler for the "View Cert" button.
 */
function viewCert() {
  viewCertHelper(window, gCert, "window");
}

/**
 * ondialogaccept() handler.
 */
function onDialogAccept() {
  let checkSSL = document.getElementById("trustSSL");
  let checkEmail = document.getElementById("trustEmail");

  let retVals = window.arguments[1].QueryInterface(Ci.nsIWritablePropertyBag2);
  retVals.setPropertyAsBool("importConfirmed", true);
  retVals.setPropertyAsBool("trustForSSL", checkSSL.checked);
  retVals.setPropertyAsBool("trustForEmail", checkEmail.checked);
}

/**
 * ondialogcancel() handler.
 */
function onDialogCancel() {
  let retVals = window.arguments[1].QueryInterface(Ci.nsIWritablePropertyBag2);
  retVals.setPropertyAsBool("importConfirmed", false);
}
PK
!<@h!LL/chrome/pippki/content/pippki/downloadcert.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window>

<window
  data-l10n-id="download-cert-window2"
  data-l10n-attrs="title, style"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
  onload="onLoad();"
>
  <dialog id="download_cert" buttons="accept,cancel">
    <linkset>
      <html:link rel="stylesheet" href="chrome://global/skin/global.css" />

      <html:link rel="localization" href="security/pippki/pippki.ftl" />
    </linkset>

    <stringbundle
      id="pippki_bundle"
      src="chrome://pippki/locale/pippki.properties"
    />

    <script src="chrome://pippki/content/pippki.js" />
    <script src="chrome://pippki/content/downloadcert.js" />

    <!--  Let 'em know what they're doing -->
    <vbox>
      <description data-l10n-id="download-cert-message"></description>
    </vbox>

    <separator />

    <!--  checkboxes for trust bits
     -  "do you want to?"
     -  * trust for SSL
     -  * trust for email
    -->
    <vbox>
      <description id="trustHeader" />
      <checkbox data-l10n-id="download-cert-trust-ssl" id="trustSSL" />
      <checkbox data-l10n-id="download-cert-trust-email" id="trustEmail" />
    </vbox>

    <separator />

    <vbox>
      <description data-l10n-id="download-cert-message-desc"></description>
      <separator />
      <hbox>
        <button
          id="viewC-button"
          data-l10n-id="download-cert-view-cert"
          oncommand="viewCert();"
        />
        <description
          style="margin: 4px"
          data-l10n-id="download-cert-view-text"
        ></description>
      </hbox>
    </vbox>
  </dialog>
</window>
PK
!<������*chrome/pippki/content/pippki/editcacert.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from pippki.js */
"use strict";

var gCertDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
  Ci.nsIX509CertDB
);
/**
 * Cert to edit the trust of.
 *
 * @type {nsIX509Cert}
 */
var gCert = window.arguments[0];

document.addEventListener("DOMContentLoaded", init);

function init() {
  document.addEventListener("dialogaccept", onDialogAccept);

  let sslCheckbox = document.getElementById("trustSSL");
  sslCheckbox.checked = gCertDB.isCertTrusted(
    gCert,
    Ci.nsIX509Cert.CA_CERT,
    Ci.nsIX509CertDB.TRUSTED_SSL
  );

  let emailCheckbox = document.getElementById("trustEmail");
  emailCheckbox.checked = gCertDB.isCertTrusted(
    gCert,
    Ci.nsIX509Cert.CA_CERT,
    Ci.nsIX509CertDB.TRUSTED_EMAIL
  );

  let certMsg = document.getElementById("certmsg");
  document.l10n.setAttributes(certMsg, "edit-trust-ca", {
    certName: gCert.commonName,
  });
}

/**
 * ondialogaccept() handler.
 */
function onDialogAccept() {
  let sslCheckbox = document.getElementById("trustSSL");
  let emailCheckbox = document.getElementById("trustEmail");
  let trustSSL = sslCheckbox.checked ? Ci.nsIX509CertDB.TRUSTED_SSL : 0;
  let trustEmail = emailCheckbox.checked ? Ci.nsIX509CertDB.TRUSTED_EMAIL : 0;

  gCertDB.setCertTrust(gCert, Ci.nsIX509Cert.CA_CERT, trustSSL | trustEmail);
}
PK
!<W'�H��-chrome/pippki/content/pippki/editcacert.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window>

<window
  data-l10n-id="certmgr-edit-ca-cert2"
  data-l10n-attrs="style"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
>
  <dialog id="editCaCert" buttons="accept,cancel">
    <linkset>
      <html:link rel="stylesheet" href="chrome://global/skin/global.css" />

      <html:link
        rel="localization"
        href="security/certificates/certManager.ftl"
      />
    </linkset>

    <script src="chrome://pippki/content/pippki.js" />
    <script src="chrome://pippki/content/editcacert.js" />

    <description id="certmsg" />
    <separator />
    <description data-l10n-id="certmgr-edit-cert-edit-trust" />
    <vbox align="start">
      <checkbox data-l10n-id="certmgr-edit-cert-trust-ssl" id="trustSSL" />
      <checkbox data-l10n-id="certmgr-edit-cert-trust-email" id="trustEmail" />
    </vbox>
  </dialog>
</window>
PK
!<6(�͎�0chrome/pippki/content/pippki/exceptionDialog.css/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

:root {
  max-width: 40em;
  min-width: 35em;
}

#warningSupplemental,
.description {
  font-weight: bold;
}

.longDescription {
  padding-bottom: 1em;
}

#warningText,
#warningSupplemental,
#headerDescription,
.longDescription {
  /* Don't let these affect the min horizontal size of the dialog */
  contain: inline-size;
  white-space: pre-wrap;
}

.description:empty,
.longDescription:empty {
  display: none;
}

#locationTextBox {
  flex: 1;
}
PK
!<{���b(b(/chrome/pippki/content/pippki/exceptionDialog.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from pippki.js */
"use strict";

var gDialog;
var gSecInfo;
var gCert;
var gChecking;
var gBroken;
var gNeedReset;

const { PrivateBrowsingUtils } = ChromeUtils.importESModule(
  "resource://gre/modules/PrivateBrowsingUtils.sys.mjs"
);

function initExceptionDialog() {
  gNeedReset = false;
  gDialog = document.getElementById("exceptiondialog");
  let warningText = document.getElementById("warningText");
  document.l10n.setAttributes(warningText, "add-exception-branded-warning");
  let confirmButton = gDialog.getButton("extra1");
  let l10nUpdatedElements = [confirmButton, warningText];
  confirmButton.disabled = true;

  var args = window.arguments;
  if (args && args[0]) {
    if (args[0].location) {
      // We were pre-seeded with a location.
      document.getElementById("locationTextBox").value = args[0].location;
      document.getElementById("checkCertButton").disabled = false;

      if (args[0].securityInfo) {
        gSecInfo = args[0].securityInfo;
        gCert = gSecInfo.serverCert;
        gBroken = true;
        l10nUpdatedElements = l10nUpdatedElements.concat(updateCertStatus());
      } else if (args[0].prefetchCert) {
        // We can optionally pre-fetch the certificate too.  Don't do this
        // synchronously, since it would prevent the window from appearing
        // until the fetch is completed, which could be multiple seconds.
        // Instead, let's use a timer to spawn the actual fetch, but update
        // the dialog to "checking..." state right away, so that the UI
        // is appropriately responsive.  Bug 453855
        document.getElementById("checkCertButton").disabled = true;
        gChecking = true;
        l10nUpdatedElements = l10nUpdatedElements.concat(updateCertStatus());

        window.setTimeout(checkCert, 0);
      }
    }

    // Set out parameter to false by default
    args[0].exceptionAdded = false;
  }

  for (let id of [
    "warningSupplemental",
    "certLocationLabel",
    "checkCertButton",
    "statusDescription",
    "statusLongDescription",
    "viewCertButton",
    "permanent",
  ]) {
    let element = document.getElementById(id);
    l10nUpdatedElements.push(element);
  }

  document.l10n
    .translateElements(l10nUpdatedElements)
    .then(() => window.sizeToContent());

  document.addEventListener("dialogextra1", addException);
  document.addEventListener("dialogextra2", checkCert);
}

/**
 * Helper function for checkCert. Set as the onerror/onload callbacks for an
 * XMLHttpRequest. Sets gSecInfo, gCert, gBroken, and gChecking according to
 * the load information from the request. Probably should not be used directly.
 *
 * @param {XMLHttpRequest} req
 *        The XMLHttpRequest created and sent by checkCert.
 * @param {Event} evt
 *        The load or error event.
 */
function grabCert(req, evt) {
  if (req.channel && req.channel.securityInfo) {
    gSecInfo = req.channel.securityInfo;
    gCert = gSecInfo ? gSecInfo.serverCert : null;
  }
  gBroken = evt.type == "error";
  gChecking = false;
  document.l10n
    .translateElements(updateCertStatus())
    .then(() => window.sizeToContent());
}

/**
 * Attempt to download the certificate for the location specified, and populate
 * the Certificate Status section with the result.
 */
async function checkCert() {
  gCert = null;
  gSecInfo = null;
  gChecking = true;
  gBroken = false;
  await document.l10n.translateElements(updateCertStatus());
  window.sizeToContent();

  let uri = getURI();

  if (uri) {
    let req = new XMLHttpRequest();
    req.open("GET", uri.prePath);
    req.onerror = grabCert.bind(this, req);
    req.onload = grabCert.bind(this, req);
    req.send(null);
  } else {
    gChecking = false;
    await document.l10n.translateElements(updateCertStatus());
    window.sizeToContent();
  }
}

/**
 * Build and return a URI, based on the information supplied in the
 * Certificate Location fields
 *
 * @returns {nsIURI}
 *          URI constructed from the information supplied on success, null
 *          otherwise.
 */
function getURI() {
  // Use fixup service instead of just ioservice's newURI since it's quite
  // likely that the host will be supplied without a protocol prefix, resulting
  // in malformed uri exceptions being thrown.
  let locationTextBox = document.getElementById("locationTextBox");
  let { preferredURI: uri } = Services.uriFixup.getFixupURIInfo(
    locationTextBox.value
  );

  if (!uri) {
    return null;
  }

  let mutator = uri.mutate();
  if (uri.scheme == "http") {
    mutator.setScheme("https");
  }

  if (uri.port == -1) {
    mutator.setPort(443);
  }

  return mutator.finalize();
}

function resetDialog() {
  document.getElementById("viewCertButton").disabled = true;
  document.getElementById("permanent").disabled = true;
  gDialog.getButton("extra1").disabled = true;
  setText("headerDescription", "");
  setText("statusDescription", "");
  setText("statusLongDescription", "");
  setText("status2Description", "");
  setText("status2LongDescription", "");
  setText("status3Description", "");
  setText("status3LongDescription", "");
  window.sizeToContent();
}

/**
 * Called by input textboxes to manage UI state
 */
function handleTextChange() {
  var checkCertButton = document.getElementById("checkCertButton");
  checkCertButton.disabled = !document.getElementById("locationTextBox").value;
  if (gNeedReset) {
    gNeedReset = false;
    resetDialog();
  }
}

function updateCertStatus() {
  var shortDesc, longDesc;
  let l10nUpdatedElements = [];
  if (gCert) {
    if (gBroken) {
      var mms = "add-exception-domain-mismatch-short";
      var mml = "add-exception-domain-mismatch-long";
      var exs = "add-exception-expired-short";
      var exl = "add-exception-expired-long";
      var uts = "add-exception-unverified-or-bad-signature-short";
      var utl = "add-exception-unverified-or-bad-signature-long";
      if (
        gSecInfo.overridableErrorCategory ==
        Ci.nsITransportSecurityInfo.ERROR_TRUST
      ) {
        shortDesc = uts;
        longDesc = utl;
      } else if (
        gSecInfo.overridableErrorCategory ==
        Ci.nsITransportSecurityInfo.ERROR_DOMAIN
      ) {
        shortDesc = mms;
        longDesc = mml;
      } else if (
        gSecInfo.overridableErrorCategory ==
        Ci.nsITransportSecurityInfo.ERROR_TIME
      ) {
        shortDesc = exs;
        longDesc = exl;
      }
      // In these cases, we do want to enable the "Add Exception" button
      gDialog.getButton("extra1").disabled = false;

      // If the Private Browsing service is available and the mode is active,
      // don't store permanent exceptions, since they would persist after
      // private browsing mode was disabled.
      var inPrivateBrowsing = inPrivateBrowsingMode();
      var pe = document.getElementById("permanent");
      pe.disabled = inPrivateBrowsing;
      pe.checked = !inPrivateBrowsing;

      let headerDescription = document.getElementById("headerDescription");
      document.l10n.setAttributes(
        headerDescription,
        "add-exception-invalid-header"
      );
      l10nUpdatedElements.push(headerDescription);
    } else {
      shortDesc = "add-exception-valid-short";
      longDesc = "add-exception-valid-long";
      gDialog.getButton("extra1").disabled = true;
      document.getElementById("permanent").disabled = true;
    }

    // We're done checking the certificate, so allow the user to check it again.
    document.getElementById("checkCertButton").disabled = false;
    document.getElementById("viewCertButton").disabled = false;

    // Notify observers about the availability of the certificate
    Services.obs.notifyObservers(null, "cert-exception-ui-ready");
  } else if (gChecking) {
    shortDesc = "add-exception-checking-short";
    longDesc = "add-exception-checking-long";
    // We're checking the certificate, so we disable the Get Certificate
    // button to make sure that the user can't interrupt the process and
    // trigger another certificate fetch.
    document.getElementById("checkCertButton").disabled = true;
    document.getElementById("viewCertButton").disabled = true;
    gDialog.getButton("extra1").disabled = true;
    document.getElementById("permanent").disabled = true;
  } else {
    shortDesc = "add-exception-no-cert-short";
    longDesc = "add-exception-no-cert-long";
    // We're done checking the certificate, so allow the user to check it again.
    document.getElementById("checkCertButton").disabled = false;
    document.getElementById("viewCertButton").disabled = true;
    gDialog.getButton("extra1").disabled = true;
    document.getElementById("permanent").disabled = true;
  }
  let statusDescription = document.getElementById("statusDescription");
  let statusLongDescription = document.getElementById("statusLongDescription");
  document.l10n.setAttributes(statusDescription, shortDesc);
  document.l10n.setAttributes(statusLongDescription, longDesc);
  l10nUpdatedElements.push(statusDescription);
  l10nUpdatedElements.push(statusLongDescription);

  gNeedReset = true;
  return l10nUpdatedElements;
}

/**
 * Handle user request to display certificate details
 */
function viewCertButtonClick() {
  if (gCert) {
    viewCertHelper(this, gCert);
  }
}

/**
 * Handle user request to add an exception for the specified cert
 */
function addException() {
  if (!gCert || !gSecInfo) {
    return;
  }

  var overrideService = Cc["@mozilla.org/security/certoverride;1"].getService(
    Ci.nsICertOverrideService
  );
  var permanentCheckbox = document.getElementById("permanent");
  var shouldStorePermanently =
    permanentCheckbox.checked && !inPrivateBrowsingMode();
  var uri = getURI();
  overrideService.rememberValidityOverride(
    uri.asciiHost,
    uri.port,
    {},
    gCert,
    !shouldStorePermanently
  );

  let args = window.arguments;
  if (args && args[0]) {
    args[0].exceptionAdded = true;
  }

  gDialog.acceptDialog();
}

/**
 * @returns {boolean} Whether this dialog is in private browsing mode.
 */
function inPrivateBrowsingMode() {
  return PrivateBrowsingUtils.isWindowPrivate(window);
}
PK
!<1�Y���2chrome/pippki/content/pippki/exceptionDialog.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window>

<window windowtype="mozilla:exceptiondialog"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        xmlns:html="http://www.w3.org/1999/xhtml"
        data-l10n-id="exception-mgr"
        onload="initExceptionDialog();">
<dialog id="exceptiondialog"
        buttonidextra1="exception-mgr-extra-button"
        buttons="cancel,extra1,extra2"
        defaultButton="extra2">

  <linkset>
    <html:link rel="stylesheet" href="chrome://global/skin/global.css" />
    <html:link
      rel="stylesheet"
      href="chrome://pippki/content/exceptionDialog.css"
    />

    <html:link rel="localization" href="branding/brand.ftl"/>
    <html:link rel="localization" href="security/certificates/certManager.ftl"/>
  </linkset>

  <script src="chrome://global/content/globalOverlay.js"/>
  <script src="chrome://global/content/editMenuOverlay.js"/>

  <script src="chrome://pippki/content/pippki.js"/>
  <script src="chrome://pippki/content/exceptionDialog.js"/>

  <hbox>
    <vbox>
      <image src="moz-icon://stock/gtk-dialog-warning?size=dialog"/>
      <spacer flex="1"/>
    </vbox>
    <vbox flex="1">
      <!-- Note that because of the styling, there must be no whitespace within
      the description tags -->
      <description id="warningText"/>
      <description id="warningSupplemental"
                   data-l10n-id="exception-mgr-supplemental-warning"/>
    </vbox>
  </hbox>

  <hbox align="center">
    <label control="locationTextBox"
           id="certLocationLabel"
           data-l10n-id="exception-mgr-cert-location-url"/>
    <html:input id="locationTextBox"
                oninput="handleTextChange();"
                value="https://"
                class="uri-element"/>
    <button id="checkCertButton"
            disabled="true"
            dlgtype="extra2"
            data-l10n-id="exception-mgr-cert-location-download"/>
  </hbox>

  <hbox align="center">
    <description id="headerDescription"
                 flex="1"/>
    <button id="viewCertButton"
            data-l10n-id="exception-mgr-cert-status-view-cert"
            disabled="true"
            oncommand="viewCertButtonClick();"/>
  </hbox>
  <description id="statusDescription"
               class="description"/>
  <description id="statusLongDescription"
               class="longDescription"/>
  <description id="status2Description"
               class="description"/>
  <description id="status2LongDescription"
               class="longDescription"/>
  <description id="status3Description"
               class="description"/>
  <description id="status3LongDescription"
               class="longDescription"/>
  <checkbox id="permanent"
            disabled="true"
            data-l10n-id="exception-mgr-permanent"/>
</dialog>
</window>
PK
!<�}�)�	�	+chrome/pippki/content/pippki/load_device.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from pippki.js */
"use strict";

document.addEventListener("dialogaccept", onDialogAccept);

/**
 * @file Implements the functionality of load_device.xhtml: a dialog that allows
 *       a PKCS #11 module to be loaded into Firefox.
 */

async function onBrowseBtnPress() {
  let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
  let [loadPK11ModuleFilePickerTitle] = await document.l10n.formatValues([
    { id: "load-pk11-module-file-picker-title" },
  ]);
  fp.init(
    window.browsingContext,
    loadPK11ModuleFilePickerTitle,
    Ci.nsIFilePicker.modeOpen
  );
  fp.appendFilters(Ci.nsIFilePicker.filterAll);
  fp.open(rv => {
    if (rv == Ci.nsIFilePicker.returnOK) {
      document.getElementById("device_path").value = fp.file.path;
    }

    // This notification gets sent solely for test purposes. It should not be
    // used by production code.
    Services.obs.notifyObservers(window, "LoadPKCS11Module:FilePickHandled");
  });
}

/**
 * ondialogaccept() handler.
 *
 * @param {object} event
 *        The event causing this handler function to be called.
 */
function onDialogAccept(event) {
  let nameBox = document.getElementById("device_name");
  let pathBox = document.getElementById("device_path");
  let pkcs11ModuleDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].getService(
    Ci.nsIPKCS11ModuleDB
  );

  try {
    pkcs11ModuleDB.addModule(nameBox.value, pathBox.value, 0, 0);
  } catch (e) {
    addModuleFailure("add-module-failure");
    event.preventDefault();
  }
}

async function addModuleFailure(l10nID) {
  let [AddModuleFailure] = await document.l10n.formatValues([{ id: l10nID }]);
  alertPromptService(null, AddModuleFailure);
}

function validateModuleName() {
  let name = document.getElementById("device_name").value;
  let helpText = document.getElementById("helpText");
  helpText.value = "";
  let dialogNode = document.querySelector("dialog");
  dialogNode.removeAttribute("buttondisabledaccept");
  if (name == "") {
    document.l10n.setAttributes(helpText, "load-module-help-empty-module-name");
    dialogNode.setAttribute("buttondisabledaccept", true);
  }
  if (name == "Root Certs") {
    document.l10n.setAttributes(
      helpText,
      "load-module-help-root-certs-module-name"
    );
    dialogNode.setAttribute("buttondisabledaccept", true);
  }
}
PK
!<ʃ���.chrome/pippki/content/pippki/load_device.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window>

<window
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
  data-l10n-id="load-device"
>
  <dialog id="loaddevice" buttons="accept,cancel">
    <linkset>
      <html:link rel="stylesheet" href="chrome://global/skin/global.css" />

      <html:link
        rel="localization"
        href="security/certificates/deviceManager.ftl"
      />
    </linkset>

    <script src="chrome://global/content/globalOverlay.js" />
    <script src="chrome://global/content/editMenuOverlay.js" />

    <script src="chrome://pippki/content/pippki.js" />
    <script src="chrome://pippki/content/load_device.js" />

    <html:style>
      #device_name, #device_path { flex: 1; }
    </html:style>

    <description data-l10n-id="load-device-info"></description>
    <hbox align="center">
      <label data-l10n-id="load-device-modname" control="device_name" />
      <html:input
        id="device_name"
        data-l10n-id="load-device-modname-default"
        data-l10n-attrs="value"
        onchange="validateModuleName();"
      />
    </hbox>
    <hbox align="center">
      <label data-l10n-id="load-device-filename" control="device_path" />
      <html:input id="device_path" />
      <button
        id="browse"
        flex="1"
        data-l10n-id="load-device-browse"
        oncommand="onBrowseBtnPress();"
      />
    </hbox>
    <label id="helpText" value="" />
  </dialog>
</window>
PK
!<���X"X"&chrome/pippki/content/pippki/pippki.js/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

/*
 * These are helper functions to be included
 * pippki UI js files.
 */

function setText(id, value) {
  let element = document.getElementById(id);
  if (!element) {
    return;
  }
  if (element.hasChildNodes()) {
    element.firstChild.remove();
  }
  element.appendChild(document.createTextNode(value));
}

async function viewCertHelper(parent, cert, openingOption = "tab") {
  if (!cert) {
    return;
  }

  let win = Services.wm.getMostRecentBrowserWindow();
  let results = await asyncDetermineUsages(cert);
  let chain = getBestChain(results);
  if (!chain) {
    chain = [cert];
  }
  let certs = chain.map(elem => encodeURIComponent(elem.getBase64DERString()));
  let certsStringURL = certs.map(elem => `cert=${elem}`);
  certsStringURL = certsStringURL.join("&");
  let url = `about:certificate?${certsStringURL}`;
  let opened = win.switchToTabHavingURI(url, false, {});
  if (!opened) {
    win.openTrustedLinkIn(url, openingOption);
  }
}

function getPKCS7Array(certArray) {
  let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
    Ci.nsIX509CertDB
  );
  let pkcs7String = certdb.asPKCS7Blob(certArray);
  let pkcs7Array = new Uint8Array(pkcs7String.length);
  for (let i = 0; i < pkcs7Array.length; i++) {
    pkcs7Array[i] = pkcs7String.charCodeAt(i);
  }
  return pkcs7Array;
}

function getPEMString(cert) {
  var derb64 = cert.getBase64DERString();
  // Wrap the Base64 string into lines of 64 characters with CRLF line breaks
  // (as specified in RFC 1421).
  var wrapped = derb64.replace(/(\S{64}(?!$))/g, "$1\r\n");
  return (
    "-----BEGIN CERTIFICATE-----\r\n" +
    wrapped +
    "\r\n-----END CERTIFICATE-----\r\n"
  );
}

function alertPromptService(title, message) {
  // XXX Bug 1425832 - Using Services.prompt here causes tests to report memory
  // leaks.
  // eslint-disable-next-line mozilla/use-services
  var ps = Cc["@mozilla.org/prompter;1"].getService(Ci.nsIPromptService);
  ps.alert(window, title, message);
}

const DEFAULT_CERT_EXTENSION = "crt";

/**
 * Generates a filename for a cert suitable to set as the |defaultString|
 * attribute on an Ci.nsIFilePicker.
 *
 * @param {nsIX509Cert} cert
 *        The cert to generate a filename for.
 * @returns {string}
 *          Generated filename.
 */
function certToFilename(cert) {
  let filename = cert.displayName;

  // Remove unneeded and/or unsafe characters.
  filename = filename
    .replace(/\s/g, "")
    .replace(/\./g, "_")
    .replace(/\\/g, "")
    .replace(/\//g, "");

  // Ci.nsIFilePicker.defaultExtension is more of a suggestion to some
  // implementations, so we include the extension in the file name as well. This
  // is what the documentation for Ci.nsIFilePicker.defaultString says we should do
  // anyways.
  return `${filename}.${DEFAULT_CERT_EXTENSION}`;
}

async function exportToFile(parent, cert) {
  if (!cert) {
    return;
  }

  let results = await asyncDetermineUsages(cert);
  let chain = getBestChain(results);
  if (!chain) {
    chain = [cert];
  }

  let formats = {
    base64: "*.crt; *.pem",
    "base64-chain": "*.crt; *.pem",
    der: "*.der",
    pkcs7: "*.p7c",
    "pkcs7-chain": "*.p7c",
  };
  let [saveCertAs, ...formatLabels] = await document.l10n.formatValues(
    ["save-cert-as", ...Object.keys(formats).map(f => "cert-format-" + f)].map(
      id => ({ id })
    )
  );

  var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
  fp.init(parent.browsingContext, saveCertAs, Ci.nsIFilePicker.modeSave);
  fp.defaultString = certToFilename(cert);
  fp.defaultExtension = DEFAULT_CERT_EXTENSION;
  for (let format of Object.values(formats)) {
    fp.appendFilter(formatLabels.shift(), format);
  }
  fp.appendFilters(Ci.nsIFilePicker.filterAll);
  let filePickerResult = await new Promise(resolve => {
    fp.open(resolve);
  });

  if (
    filePickerResult != Ci.nsIFilePicker.returnOK &&
    filePickerResult != Ci.nsIFilePicker.returnReplace
  ) {
    return;
  }

  var content = "";
  switch (fp.filterIndex) {
    case 1:
      content = getPEMString(cert);
      for (let i = 1; i < chain.length; i++) {
        content += getPEMString(chain[i]);
      }
      break;
    case 2:
      // IOUtils.write requires a typed array.
      // nsIX509Cert.getRawDER() returns an array (not a typed array), so we
      // convert it here.
      content = Uint8Array.from(cert.getRawDER());
      break;
    case 3:
      // getPKCS7Array returns a typed array already, so no conversion is
      // necessary.
      content = getPKCS7Array([cert]);
      break;
    case 4:
      content = getPKCS7Array(chain);
      break;
    case 0:
    default:
      content = getPEMString(cert);
      break;
  }

  if (typeof content === "string") {
    content = new TextEncoder().encode(content);
  }

  try {
    await IOUtils.write(fp.file.path, content);
  } catch (ex) {
    let title = await document.l10n.formatValue("write-file-failure");
    alertPromptService(title, ex.toString());
  }
  if (Cu.isInAutomation) {
    Services.obs.notifyObservers(null, "cert-export-finished");
  }
}

const PRErrorCodeSuccess = 0;

// Certificate usages we care about in the certificate viewer.
const certificateUsageSSLClient = 0x0001;
const certificateUsageSSLServer = 0x0002;
const certificateUsageSSLCA = 0x0008;
const certificateUsageEmailSigner = 0x0010;
const certificateUsageEmailRecipient = 0x0020;

// A map from the name of a certificate usage to the value of the usage.
// Useful for printing debugging information and for enumerating all supported
// usages.
const certificateUsages = {
  certificateUsageSSLClient,
  certificateUsageSSLServer,
  certificateUsageSSLCA,
  certificateUsageEmailSigner,
  certificateUsageEmailRecipient,
};

/**
 * Returns a promise that will resolve with a results array consisting of what
 * usages the given certificate successfully verified for.
 *
 * @param {nsIX509Cert} cert
 *        The certificate to determine valid usages for.
 * @returns {Promise}
 *        A promise that will resolve with the results of the verifications.
 */
function asyncDetermineUsages(cert) {
  let promises = [];
  let now = Date.now() / 1000;
  let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
    Ci.nsIX509CertDB
  );
  Object.keys(certificateUsages).forEach(usageString => {
    promises.push(
      new Promise(resolve => {
        let usage = certificateUsages[usageString];
        certdb.asyncVerifyCertAtTime(
          cert,
          usage,
          0,
          null,
          now,
          (aPRErrorCode, aVerifiedChain) => {
            resolve({
              usageString,
              errorCode: aPRErrorCode,
              chain: aVerifiedChain,
            });
          }
        );
      })
    );
  });
  return Promise.all(promises);
}

/**
 * Given a results array, returns the "best" verified certificate chain. Since
 * the primary use case is for TLS server certificates in Firefox, such a
 * verified chain will be returned if present. Otherwise, the priority is: TLS
 * client certificate, email signer, email recipient, CA. Returns null if no
 * usage verified successfully.
 *
 * @param {Array} results
 *        An array of results from `asyncDetermineUsages`. See `displayUsages`.
 * @returns {Array} An array of `nsIX509Cert` representing the verified
 *          certificate chain for the given usage, or null if there is none.
 */
function getBestChain(results) {
  let usages = [
    certificateUsageSSLServer,
    certificateUsageSSLClient,
    certificateUsageEmailSigner,
    certificateUsageEmailRecipient,
    certificateUsageSSLCA,
  ];
  for (let usage of usages) {
    let chain = getChainForUsage(results, usage);
    if (chain) {
      return chain;
    }
  }
  return null;
}

/**
 * Given a results array, returns the chain corresponding to the desired usage,
 * if verifying for that usage succeeded. Returns null otherwise.
 *
 * @param {Array} results
 *        An array of results from `asyncDetermineUsages`. See `displayUsages`.
 * @param {number} usage
 *        A numerical value corresponding to a usage. See `certificateUsages`.
 * @returns {Array} An array of `nsIX509Cert` representing the verified
 *          certificate chain for the given usage, or null if there is none.
 */
function getChainForUsage(results, usage) {
  for (let result of results) {
    if (
      certificateUsages[result.usageString] == usage &&
      result.errorCode == PRErrorCodeSuccess
    ) {
      return result.chain;
    }
  }
  return null;
}
PK
!<#e<{YY-chrome/pippki/content/pippki/resetpassword.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* import-globals-from pippki.js */
"use strict";

document.addEventListener("dialogaccept", resetPassword);

function resetPassword() {
  var pk11db = Cc["@mozilla.org/security/pk11tokendb;1"].getService(
    Ci.nsIPK11TokenDB
  );
  var token = pk11db.getInternalKeyToken();
  token.reset();

  try {
    Services.logins.removeAllUserFacingLogins();
  } catch (e) {}

  let l10n = new Localization(["security/pippki/pippki.ftl"], true);
  if (l10n) {
    Services.prompt.alert(
      window,
      l10n.formatValueSync("pippki-reset-password-confirmation-title"),
      l10n.formatValueSync("pippki-reset-password-confirmation-message")
    );
  }
}
PK
!<���qq0chrome/pippki/content/pippki/resetpassword.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window>

<window
  data-l10n-id="reset-primary-password-window2"
  data-l10n-attrs="title, style"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
>
  <dialog
    id="reset_password"
    buttons="accept,cancel"
    buttonidaccept="reset-password-button-label"
    defaultButton="cancel"
  >
    <linkset>
      <html:link rel="stylesheet" href="chrome://global/skin/global.css" />

      <html:link rel="localization" href="security/pippki/pippki.ftl" />
    </linkset>

    <stringbundle
      id="pippki_bundle"
      src="chrome://pippki/locale/pippki.properties"
    />

    <script src="chrome://pippki/content/pippki.js" />
    <script src="chrome://pippki/content/resetpassword.js" />

    <hbox flex="1">
      <vbox>
        <image class="alert-icon" style="margin: 5px" />
      </vbox>
      <vbox style="margin: 5px" flex="1">
        <hbox flex="1">
          <vbox flex="1">
            <description
              data-l10n-id="reset-primary-password-text"
            ></description>
          </vbox>
        </hbox>
      </vbox>
    </hbox>
  </dialog>
</window>
PK
!<�����.chrome/pippki/content/pippki/setp12password.js/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";

/**
 * @file Implements the functionality of setp12password.xhtml: a dialog that lets
 *       the user confirm the password to set on a PKCS #12 file.
 * @param {nsISupports} window.arguments.0
 *           Object to set the return values of calling the dialog on, queryable
 *           to the underlying type of SetP12PasswordReturnValues.
 */

/**
 * @typedef SetP12PasswordReturnValues
 * @type {nsIWritablePropertyBag2}
 * @property {boolean} confirmedPassword
 *           Set to true if the user entered two matching passwords and
 *           confirmed the dialog.
 * @property {string} password
 *           The password the user entered. Undefined value if
 *           |confirmedPassword| is not true.
 */

/**
 * onload() handler.
 */
function onLoad() {
  // Ensure the first password textbox has focus.
  document.getElementById("pw1").focus();
  document.addEventListener("dialogaccept", onDialogAccept);
  document.addEventListener("dialogcancel", onDialogCancel);
}

/**
 * ondialogaccept() handler.
 */
function onDialogAccept() {
  let password = document.getElementById("pw1").value;

  let retVals = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2);
  retVals.setPropertyAsBool("confirmedPassword", true);
  retVals.setPropertyAsAString("password", password);
}

/**
 * ondialogcancel() handler.
 */
function onDialogCancel() {
  let retVals = window.arguments[0].QueryInterface(Ci.nsIWritablePropertyBag2);
  retVals.setPropertyAsBool("confirmedPassword", false);
}

/**
 * Calculates the strength of the given password, suitable for use in updating
 * a progress bar that represents said strength.
 *
 * The strength of the password is calculated by checking the number of:
 *   - Characters
 *   - Numbers
 *   - Non-alphanumeric chars
 *   - Upper case characters
 *
 * @param {string} password
 *        The password to calculate the strength of.
 * @returns {number}
 *          The strength of the password in the range [0, 100].
 */
function getPasswordStrength(password) {
  let lengthStrength = password.length;
  if (lengthStrength > 5) {
    lengthStrength = 5;
  }

  let nonNumericChars = password.replace(/[0-9]/g, "");
  let numericStrength = password.length - nonNumericChars.length;
  if (numericStrength > 3) {
    numericStrength = 3;
  }

  let nonSymbolChars = password.replace(/\W/g, "");
  let symbolStrength = password.length - nonSymbolChars.length;
  if (symbolStrength > 3) {
    symbolStrength = 3;
  }

  let nonUpperAlphaChars = password.replace(/[A-Z]/g, "");
  let upperAlphaStrength = password.length - nonUpperAlphaChars.length;
  if (upperAlphaStrength > 3) {
    upperAlphaStrength = 3;
  }

  let strength =
    lengthStrength * 10 -
    20 +
    numericStrength * 10 +
    symbolStrength * 15 +
    upperAlphaStrength * 10;
  if (strength < 0) {
    strength = 0;
  }
  if (strength > 100) {
    strength = 100;
  }

  return strength;
}

/**
 * oninput() handler for both password textboxes.
 *
 * @param {boolean} recalculatePasswordStrength
 *                  Whether to recalculate the strength of the first password.
 */
function onPasswordInput(recalculatePasswordStrength) {
  let pw1 = document.getElementById("pw1").value;

  if (recalculatePasswordStrength) {
    document.getElementById("pwmeter").value = getPasswordStrength(pw1);
  }

  // Disable the accept button if the two passwords don't match, and enable it
  // if the passwords do match.
  let pw2 = document.getElementById("pw2").value;
  document.getElementById("setp12password").getButton("accept").disabled =
    pw1 != pw2;
}
PK
!<���h��1chrome/pippki/content/pippki/setp12password.xhtml<?xml version="1.0"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
   - License, v. 2.0. If a copy of the MPL was not distributed with this
   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->

<!DOCTYPE window>

<window
  data-l10n-id="set-password-window"
  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:html="http://www.w3.org/1999/xhtml"
  style="width: 48em"
  onload="onLoad();"
>
  <dialog id="setp12password" buttons="accept,cancel">
    <linkset>
      <html:link rel="stylesheet" href="chrome://global/skin/global.css" />

      <html:link rel="localization" href="security/pippki/pippki.ftl" />
    </linkset>

    <script src="chrome://global/content/globalOverlay.js" />
    <script src="chrome://global/content/editMenuOverlay.js" />

    <script src="chrome://pippki/content/setp12password.js" />

    <description data-l10n-id="set-password-message"></description>
    <separator />
    <vbox>
      <hbox class="input-row">
        <label flex="1" data-l10n-id="set-password-backup-pw" />
        <html:input id="pw1" type="password" oninput="onPasswordInput(true);" />
      </hbox>
      <hbox class="input-row">
        <label flex="1" data-l10n-id="set-password-repeat-backup-pw" />
        <html:input
          id="pw2"
          type="password"
          oninput="onPasswordInput(false);"
        />
      </hbox>
    </vbox>
    <separator />
    <description data-l10n-id="set-password-reminder"></description>
    <separator />

    <vbox style="margin: 6px">
      <html:label
        for="pwmeter"
        style="display: flex"
        data-l10n-id="password-quality-meter"
      ></html:label>
      <html:progress id="pwmeter" value="0" max="100" />
    </vbox>
  </dialog>
</window>
PK
!<v&#�1chrome/en-US/locale/en-US/alerts/alert.properties
closeButton.title = Close
actionButton.label = …
webActions.disableForOrigin.label = Disable notifications from %S

source.label=via %1$S
webActions.settings.label = Notification settings

pauseNotifications.label = Pause notifications until %S restarts
PK
!<�֔�gg:chrome/en-US/locale/en-US/autoconfig/autoconfig.properties
readConfigTitle = Configuration Error
readConfigMsg = Failed to read the configuration file. Please contact your system administrator.

autoConfigTitle = AutoConfig Alert
autoConfigMsg = Netscape.cfg/AutoConfig failed. Please contact your system administrator. \n Error: %S failed:

emailPromptTitle = Email Address
emailPromptMsg = Enter your email address
PK
!<TP�--8chrome/en-US/locale/en-US/global/aboutStudies.properties

title = Shield Studies
removeButton = Remove

activeStudiesList = Active studies
completedStudiesList = Completed studies
activeStatus = Active
completeStatus = Complete

updateButtonWin = Update Options
updateButtonUnix = Update Preferences
learnMore = Learn more
noStudies = You have not participated in any studies.
disabledList = This is a list of studies that you have participated in. No new studies will run.
enabledList = What’s this? %S may install and run studies from time to time.

preferenceStudyDescription = This study sets %1$S to %2$S.
PK
!<�Ľ�6chrome/en-US/locale/en-US/global/appstrings.properties
malformedURI2=Please check that the URL is correct and try again.
fileNotFound=The file %S cannot be found. Please check the location and try again.
fileAccessDenied=The file at %S is not readable.
serverError=%S might have a temporary problem or it could have moved.
dnsNotFound2=%S could not be found. Please check the name and try again.
unknownProtocolFound=One of the following (%S) is not a registered protocol or is not allowed in this context.
connectionFailure=The connection was refused when attempting to contact %S.
netInterrupt=The connection to %S has terminated unexpectedly. Some data may have been transferred.
netTimeout=The operation timed out when attempting to contact %S.
redirectLoop=Redirection limit for this URL exceeded.  Unable to load the requested page.  This may be caused by cookies that are blocked.
confirmRepostPrompt=To display this page, the application must send information that will repeat any action (such as a search or order confirmation) that was performed earlier.
resendButton.label=Resend
unknownSocketType=This document cannot be displayed unless you install the Personal Security Manager (PSM). Download and install PSM and try again, or contact your system administrator.
netReset=The document contains no data.
notCached=This document is no longer available.
netOffline=This document cannot be displayed while offline. To go online, uncheck Work Offline from the File menu.
isprinting=The document cannot change while Printing or in Print Preview.
deniedPortAccess=Access to the port number given has been disabled for security reasons.
proxyResolveFailure=The proxy server you have configured could not be found. Please check your proxy settings and try again.
proxyConnectFailure=The connection was refused when attempting to contact the proxy server you have configured. Please check your proxy settings and try again.
contentEncodingError=The page you are trying to view cannot be shown because it uses an invalid or unsupported form of compression.
unsafeContentType=The page you are trying to view cannot be shown because it is contained in a file type that may not be safe to open. Please contact the website owners to inform them of this problem.
malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
harmfulBlocked=The site at %S has been reported as a potentially harmful site and has been blocked based on your security preferences.
unwantedBlocked=The site at %S has been reported as serving unwanted software and has been blocked based on your security preferences.
deceptiveBlocked=This web page at %S has been reported as a deceptive site and has been blocked based on your security preferences.
cspBlocked=This page has a content security policy that prevents it from being loaded in this way.
xfoBlocked=This page has an X-Frame-Options policy that prevents it from being loaded in this context.
corruptedContentErrorv2=The site at %S has experienced a network protocol violation that cannot be repaired.
sslv3Used=The safety of your data on %S could not be guaranteed because it uses SSLv3, a broken security protocol.
weakCryptoUsed=The owner of %S has configured their website improperly. To protect your information from being stolen, the connection to this website has not been established.
inadequateSecurityError=The website tried to negotiate an inadequate level of security.
blockedByPolicy=Your organization has blocked access to this page or website.
networkProtocolError=Firefox has experienced a network protocol violation that cannot be repaired.
PK
!<��#$$8chrome/en-US/locale/en-US/global/autocomplete.properties
bookmarkKeywordSearch = %1$S: %2$S
PK
!<{;�FF3chrome/en-US/locale/en-US/global/browser.properties
formPostSecureToInsecureWarning.title = Security Warning
formPostSecureToInsecureWarning.message = The information you have entered on this page will be sent over an insecure connection and could be read by a third party.\n\nAre you sure you want to send this information?
formPostSecureToInsecureWarning.continue = Continue
PK
!<s����?chrome/en-US/locale/en-US/global/contentAreaCommands.properties

SaveImageTitle=Save Image
SaveMediaTitle=Save Media
SaveVideoTitle=Save Video
SaveAudioTitle=Save Audio
SaveLinkTitle=Save As
WebPageCompleteFilter=Web Page, complete
WebPageHTMLOnlyFilter=Web Page, HTML only
WebPageXHTMLOnlyFilter=Web Page, XHTML only
WebPageSVGOnlyFilter=Web Page, SVG only
WebPageXMLOnlyFilter=Web Page, XML only

UntitledSaveFileName=Untitled

filesFolder=%S_files
PK
!<;v�ɠ�2chrome/en-US/locale/en-US/global/dialog.properties
button-accept=OK
button-cancel=Cancel
button-help=Help
button-disclosure=More Info
accesskey-accept=
accesskey-cancel=
accesskey-help=H
accesskey-disclosure=I
PK
!<�n���6chrome/en-US/locale/en-US/global/extensions.properties
uninstall.confirmation.title = Uninstall %S

uninstall.confirmation.message = The extension “%S” is requesting to be uninstalled. What would you like to do?

uninstall.confirmation.button-0.label = Uninstall
uninstall.confirmation.button-1.label = Keep Installed

saveaspdf.saveasdialog.title = Save As

newTabControlled.message2 = An extension, %S, changed the page you see when you open a new tab.

homepageControlled.message = An extension, %S, changed what you see when you open your homepage and new windows.

tabHideControlled.message = An extension, %1$S, is hiding some of your tabs. You can still access all of your tabs from %2$S.
PK
!<���O--;chrome/en-US/locale/en-US/global/fallbackMenubar.properties

quitMenuitem.label=Quit
quitMenuitem.key=q
PK
!<zŗ116chrome/en-US/locale/en-US/global/filepicker.properties
allTitle=All Files
htmlTitle=HTML Files
textTitle=Text Files
imageTitle=Image Files
xmlTitle=XML Files
xulTitle=XUL Files
appsTitle=Applications
audioTitle=Audio Files
videoTitle=Video Files
pdfTitle=PDF Files

formatLabel=Format:
selectedFileNotReadableError=Selected file does not have read permission
PK
!<2u�,,9chrome/en-US/locale/en-US/global/global-strres.properties
16389=An unknown error has occurred (%1$S)
PK
!<5Oz�  @chrome/en-US/locale/en-US/global/layout/MediaDocument.properties
ImageTitleWithDimensions2AndFile=%S (%S Image, %S\u00A0\u00D7\u00A0%S pixels)
ImageTitleWithoutDimensions=%S (%S Image)
ImageTitleWithDimensions2=(%S Image, %S\u00A0\u00D7\u00A0%S pixels)
ImageTitleWithNeitherDimensionsNorFile=(%S Image)
MediaTitleWithFile=%S (%S Object)
MediaTitleWithNoInfo=(%S Object)

InvalidImage=The image \u201c%S\u201d cannot be displayed because it contains errors.
UnsupportedImage=The image “%S” cannot be displayed because it requires unsupported features.
ScaledImage=Scaled (%S%%)

TitleWithStatus=%S — %S
PK
!<~3�##<chrome/en-US/locale/en-US/global/layout/xmlparser.properties
1 = out of memory
2 = syntax error
3 = no root element found
4 = not well-formed
5 = unclosed token
6 = partial character
7 = mismatched tag
8 = duplicate attribute
9 = junk after document element
10 = illegal parameter entity reference
11 = undefined entity
12 = recursive entity reference
13 = asynchronous entity
14 = reference to invalid character number
15 = reference to binary entity
16 = reference to external entity in attribute
17 = XML or text declaration not at start of entity
18 = unknown encoding
19 = encoding specified in XML declaration is incorrect
20 = unclosed CDATA section
21 = error in processing external entity reference
22 = document is not standalone
23 = unexpected parser state
24 = entity declared in parameter entity
27 = prefix not bound to a namespace
28 = must not undeclare prefix
29 = incomplete markup in parameter entity
30 = XML declaration not well-formed
31 = text declaration not well-formed
32 = illegal character(s) in public id
38 = reserved prefix (xml) must not be undeclared or bound to another namespace name
39 = reserved prefix (xmlns) must not be declared or undeclared
40 = prefix must not be bound to one of the reserved namespace names

XMLParsingError = XML Parsing Error: %1$S\nLocation: %2$S\nLine Number %3$u, Column %4$u:

Expected = . Expected: </%S>.
PK
!<�q�[3chrome/en-US/locale/en-US/global/narrate.properties
read-aloud-header = Read aloud
read-aloud-label = Read aloud (%S)
previous-label = Back (%S)
start-label = Start (%S)
stop-label = Stop (%S)
narrate-key-shortcut = N
next-label = Forward (%S)
speed = Speed
slow-speed-label = Slow down narration
fast-speed-label = Speed up narration
selectvoicelabel = Voice:
select-voice-header = Voice
defaultvoice = Default

voiceLabel = %S (%S)
PK
!<F�Y�44?chrome/en-US/locale/en-US/global/nsWebBrowserPersist.properties
readError=%S could not be saved, because the source file could not be read.\n\nTry again later, or contact the server administrator.
writeError=%S could not be saved, because an unknown error occurred.\n\nTry saving to a different location.
launchError=%S could not be opened, because an unknown error occurred.\n\nTry saving to disk first and then opening the file.
diskFull=There is not enough room on the disk to save %S.\n\nRemove unnecessary files from the disk and try again, or try saving in a different location.
readOnly=%S could not be saved, because the disk, folder, or file is write-protected.\n\nWrite-enable the disk and try again, or try saving in a different location.
accessError=%S could not be saved, because you cannot change the contents of that folder.\n\nChange the folder properties and try again, or try saving in a different location.
SDAccessErrorCardReadOnly=Cannot download file because the SD card is in use.
SDAccessErrorCardMissing=Cannot download file because the SD card is missing.
helperAppNotFound=%S could not be opened, because the associated helper application does not exist. Change the association in your preferences.
noMemory=There is not sufficient memory to complete the action you requested.\n\nQuit some applications and try again.
title=Downloading %S
fileAlreadyExistsError=%S could not be saved, because a file already exists with the same name as the ‘_files’ directory.\n\nTry saving to a different location.
fileNameTooLongError=%S could not be saved, because the file name was too long.\n\nTry saving with a shorter file name.
PK
!<�!��7chrome/en-US/locale/en-US/global/printdialog.properties

printTitleGTK=Print
optionsTabLabelGTK=Options

optionsTitleMac=Options:
appearanceTitleMac=Appearance:
pageHeadersTitleMac=Page Headers:
pageFootersTitleMac=Page Footers:

optionsTitleWindows=Options


shrinkToFit=Ignore Scaling and S_hrink To Fit Page Width
selectionOnly=Print Selection _Only
printBGOptions=Print Backgrounds
printBGColors=Print Background _Colors
printBGImages=Print Background I_mages
headerFooter=Header and Footer
left=Left
center=Center
right=Right
headerFooterBlank=--blank--
headerFooterTitle=Title
headerFooterURL=URL
headerFooterDate=Date/Time
headerFooterPage=Page #
headerFooterPageTotal=Page # of #
headerFooterCustom=Custom…
customHeaderFooterPrompt=Please enter your custom header/footer text

summarySelectionOnlyTitle=Print Selection
summaryShrinkToFitTitle=Shrink To Fit
summaryPrintBGColorsTitle=Print BG Colors
summaryPrintBGImagesTitle=Print BG Images
summaryHeaderTitle=Page Headers
summaryFooterTitle=Page Footers
summaryNAValue=N/A
summaryOnValue=On
summaryOffValue=Off
PK
!<�n��xx8chrome/en-US/locale/en-US/global/resetProfile.properties

resetUnusedProfile.message=It looks like you haven’t started %S in a while. Do you want to clean it up for a fresh, like-new experience? And by the way, welcome back!
resetUninstalled.message=Looks like you’ve reinstalled %S. Want us to clean it up for a fresh, like-new experience?

refreshProfile.resetButton.label=Refresh %S…
refreshProfile.resetButton.accesskey=e
PK
!<�Ŏx��9chrome/en-US/locale/en-US/global/security/caps.propertiesCheckLoadURIError = Security Error: Content at %S may not load or link to %S.
CheckSameOriginError = Security Error: Content at %S may not load data from %S.
ExternalDataError = Security Error: Content at %S attempted to load %S, but may not load external data when being used as an image.

CreateWrapperDenied = Permission denied to create wrapper for object of class %S
CreateWrapperDeniedForOrigin = Permission denied for <%2$S> to create wrapper for object of class %1$S
PK
!<�R�{{8chrome/en-US/locale/en-US/global/security/csp.properties

CSPInlineStyleViolation = The page’s settings blocked an inline style (%2$S) from being applied because it violates the following directive: “%1$S”
CSPROInlineStyleViolation = (Report-Only policy) The page’s settings would block an inline style (%2$S) from being applied because it violates the following directive: “%1$S”
CSPInlineScriptViolation = The page’s settings blocked an inline script (%2$S) from being executed because it violates the following directive: “%1$S”
CSPROInlineScriptViolation = (Report-Only policy) The page’s settings would block an inline script (%2$S) from being executed because it violates the following directive: “%1$S”
CSPEventHandlerScriptViolation = The page’s settings blocked an event handler (%2$S) from being executed because it violates the following directive: “%1$S”
CSPROEventHandlerScriptViolation = (Report-Only policy) The page’s settings would block an event handler (%2$S) from being executed because it violates the following directive: “%1$S”
CSPEvalScriptViolation = The page’s settings blocked a JavaScript eval (%2$S) from being executed because it violates the following directive: “%1$S” (Missing 'unsafe-eval')
CSPROEvalScriptViolation = (Report-Only policy) The page’s settings would block a JavaScript eval (%2$S) from being executed because it violates the following directive: “%1$S” (Missing 'unsafe-eval')
CSPWasmEvalScriptViolation = The page’s settings blocked WebAssembly (%2$S) from being executed because it violates the following directive: “%1$S” (Missing 'wasm-unsafe-eval' or 'unsafe-eval')
CSPROWasmEvalScriptViolation = (Report-Only policy) The page’s settings would block WebAssembly (%2$S) from being executed because it violates the following directive: “%1$S” (Missing 'wasm-unsafe-eval' or 'unsafe-eval')
CSPTrustedTypesPolicyViolation = The page’s settings blocked creating a Trusted Types policy because it violates the following directive: “%1$S“
CSPROTrustedTypesPolicyViolation = (Report-Only policy) The page’s settings would block creating a Trusted Types policy because it violates the following directive: “%1$S“
CSPStyleViolation = The page’s settings blocked a style (%3$S) at %2$S from being applied because it violates the following directive: “%1$S”
CSPROStyleViolation = (Report-Only policy) The page’s settings would block a style (%3$S) at %2$S from being applied because it violates the following directive: “%1$S”
CSPScriptViolation = The page’s settings blocked a script (%3$S) at %2$S from being executed because it violates the following directive: “%1$S”
CSPROScriptViolation = (Report-Only policy) The page’s settings would block a script (%3$S) at %2$S from being executed because it violates the following directive: “%1$S”
CSPWorkerViolation = The page’s settings blocked a worker script (%3$S) at %2$S from being executed because it violates the following directive: “%1$S”
CSPROWorkerViolation = (Report-Only policy) The page’s settings would block a worker script (%3$S) at %2$S from being executed because it violates the following directive: “%1$S”
CSPGenericViolation = The page’s settings blocked the loading of a resource (%3$S) at %2$S because it violates the following directive: “%1$S”
CSPROGenericViolation = (Report-Only policy) The page’s settings would block the loading of a resource (%3$S) at %2$S because it violates the following directive: “%1$S”

triedToSendReport = Tried to send report to invalid URI: “%1$S”
tooManyReports = Prevented too many CSP reports from being sent within a short period of time.
couldNotParseReportURI = couldn’t parse report URI: %1$S
couldNotProcessUnknownDirective = Couldn’t process unknown directive ‘%1$S’
ignoringUnknownOption = Ignoring unknown option %1$S
ignoringDuplicateSrc = Ignoring duplicate source %1$S
ignoringNonAsciiToken = Ignoring directive ‘%1$S’ with the non-ASCII token ‘%2$S’
ignoringSrcFromMetaCSP = Ignoring source ‘%1$S’ (Not supported when delivered via meta element).
ignoringSrcWithinNonceOrHashDirective = Ignoring “%1$S” within %2$S: nonce-source or hash-source specified
ignoringScriptSrcForStrictDynamic = Ignoring “%1$S” within %2$S: ‘strict-dynamic’ specified
ignoringStrictDynamic = Ignoring source “%1$S” (Only supported within script-src).
ignoringUnsafeEval = Ignoring ‘unsafe-eval’ or ‘wasm-unsafe-eval’ inside “%1$S”.
strictDynamicButNoHashOrNonce = Keyword ‘strict-dynamic’ within “%1$S” with no valid nonce or hash might block all scripts from loading
reportURInotHttpsOrHttp2 = The report URI (%1$S) should be an HTTP or HTTPS URI.
reportURINorReportToNotInReportOnlyHeader = This site (%1$S) has a Report-Only policy without a report-uri directive nor a report-to directive. CSP will not block and cannot report violations of this policy.
failedToParseUnrecognizedSource = Failed to parse unrecognized source %1$S
upgradeInsecureRequest = Upgrading insecure request ‘%1$S’ to use ‘%2$S’
ignoreSrcForDirective = Ignoring srcs for directive ‘%1$S’
hostNameMightBeKeyword = Interpreting %1$S as a hostname, not a keyword. If you intended this to be a keyword, use ‘%2$S’ (wrapped in single quotes).
notSupportingDirective = Not supporting directive ‘%1$S’. Directive and values will be ignored.
blockAllMixedContent = Blocking insecure request ‘%1$S’.
ignoringDirectiveWithNoValues = Ignoring ‘%1$S’ since it does not contain any parameters.
ignoringReportOnlyDirective = Ignoring sandbox directive when delivered in a report-only policy ‘%1$S’
IgnoringSrcBecauseOfDirective=Ignoring ‘%1$S’ because of ‘%2$S’ directive.
IgnoringSourceWithinDirective = Ignoring source “%1$S” (Not supported within ‘%2$S’).
obsoleteBlockAllMixedContent = Ignoring ‘%1$S’ because mixed content display upgrading makes block-all-mixed-content obsolete.


couldntParseInvalidSource = Couldn’t parse invalid source %1$S
couldntParseInvalidHost = Couldn’t parse invalid host %1$S
couldntParsePort = Couldn’t parse port in %1$S
duplicateDirective = Duplicate %1$S directives detected.  All but the first instance will be ignored.
couldntParseInvalidSandboxFlag = Couldn’t parse invalid sandbox flag ‘%1$S’
invalidNumberOfTrustedTypesForDirectiveValues = Received an invalid number of tokens for the ‘require-trusted-types-for‘ directive: %1$S; expected 1
invalidRequireTrustedTypesForDirectiveValue = Received an invalid token for the ‘require-trusted-types-for‘ directive: %1$S; expected ‘script‘
invalidTrustedTypesExpression = Received an invalid token for the ‘trusted-types‘ directive: %1$S

CSPMessagePrefix = Content-Security-Policy: %S
PK
!<�-����6chrome/en-US/locale/en-US/global/viewSource.properties
goToLineTitle     = Go to line
goToLineText      = Enter line number
invalidInputTitle = Invalid input
invalidInputText  = The line number entered is invalid.
outOfRangeTitle   = Line not found
outOfRangeText    = The specified line was not found.
viewSelectionSourceTitle = DOM Source of Selection

context_goToLine_label        = Go to Line…
context_goToLine_accesskey    = L
context_wrapLongLines_label   = Wrap Long Lines
context_highlightSyntax_label = Syntax Highlighting
PK
!<�晉��2chrome/en-US/locale/en-US/global/wizard.properties
default-first-title=Welcome to the %S
default-last-title=Completing the %S
default-first-title-mac=Introduction
default-last-title-mac=Conclusion
PK
!<��۲��5chrome/en-US/locale/en-US/global/xslt/xslt.properties
1  = Parsing an XSLT stylesheet failed.
2  = Parsing an XPath expression failed.
3  =
4  = XSLT transformation failed.
5  = Invalid XSLT/XPath function.
6  = XSLT Stylesheet (possibly) contains a recursion.
7  = Attribute value illegal in XSLT 1.0.
8  = An XPath expression was expected to return a NodeSet.
9  = XSLT transformation was terminated by <xsl:message>.
10 = A network error occurred loading an XSLT stylesheet:
11 = An XSLT stylesheet does not have an XML mimetype:
12 = An XSLT stylesheet directly or indirectly imports or includes itself:
13 = An XPath function was called with the wrong number of arguments.
14 = An unknown XPath extension function was called.
15 = XPath parse failure: ‘)’ expected:
16 = XPath parse failure: invalid axis:
17 = XPath parse failure: Name or Nodetype test expected:
18 = XPath parse failure: ‘]’ expected:
19 = XPath parse failure: invalid variable name:
20 = XPath parse failure: unexpected end of expression:
21 = XPath parse failure: operator expected:
22 = XPath parse failure: unclosed literal:
23 = XPath parse failure: ‘:’ unexpected:
24 = XPath parse failure: ‘!’ unexpected, negation is not():
25 = XPath parse failure: illegal character found:
26 = XPath parse failure: binary operator expected:
27 = An XSLT stylesheet load was blocked for security reasons.
28 = Evaluating an invalid expression.
29 = Unbalanced curly brace.
30 = Creating an element with an invalid QName.
31 = Variable binding shadows variable binding within the same template.
32 = Call to the key function not allowed.

LoadingError = Error loading stylesheet: %S
TransformError = Error during XSLT transformation: %S
PK
!<Zڬ���Cchrome/en-US/locale/en-US/global-platform/mac/accessible.properties
jump    =       Jump
press   =       Press
check   =       Check
uncheck =       Uncheck
select  =       Select
open    =       Open
close   =       Close
switch  =       Switch
click   =       Click
collapse=       Collapse
expand  =       Expand
activate=       Activate
cycle   =       Cycle
clickAncestor = Click ancestor

htmlContent = HTML Content
tab     =       tab
term    =       term
definition =    definition
searchTextField = search text field
dateField =     date field
application =   application
search  =       search
banner  =       banner
navigation =    navigation
complementary = complementary
content =       content
main    =       main
alert       =      alert
alertDialog =      alert dialog
dialog      =      dialog
article     =      article
document    =      document
figure     =      figure
heading     =      heading
log         =      log
marquee     =      marquee
math        =      math
note        =      note
region      =      region
status      =      application status
timer       =      timer
tooltip     =      tooltip
separator    =      separator
tabPanel     =      tab panel
highlight = highlight
details = details
summary = summary
PK
!<��QT=chrome/en-US/locale/en-US/global-platform/mac/intl.properties
intl.ellipsis=…
PK
!<��uuEchrome/en-US/locale/en-US/global-platform/mac/platformKeys.properties

VK_SHIFT=\u21e7

VK_COMMAND_OR_WIN=\u2318

VK_ALT=\u2325

VK_CONTROL=\u2303

VK_RETURN=Return

MODIFIER_SEPARATOR=
PK
!<<�K�GGDchrome/en-US/locale/en-US/global-platform/unix/accessible.properties
jump    =       Jump
press   =       Press
check   =       Check
uncheck =       Uncheck
select  =       Select
open    =       Open
close   =       Close
switch  =       Switch
click   =       Click
collapse=       Collapse
expand  =       Expand
activate=       Activate
cycle   =       Cycle
clickAncestor = Click ancestor
PK
!<��QT>chrome/en-US/locale/en-US/global-platform/unix/intl.properties
intl.ellipsis=…
PK
!<�����Cchrome/en-US/locale/en-US/global-platform/win/accessible.properties
jump    =       Jump
press   =       Press
check   =       Check
uncheck =       Uncheck
select  =       Select
open    =       Open
close   =       Close
switch  =       Switch
click   =       Click
collapse=       Collapse
expand  =       Expand
activate=       Activate
cycle   =       Cycle
clickAncestor = Click ancestor

banner = banner
complementary = complementary
contentinfo = content information
region = region
PK
!<��QT=chrome/en-US/locale/en-US/global-platform/win/intl.properties
intl.ellipsis=…
PK
!<cfy�llEchrome/en-US/locale/en-US/global-platform/win/platformKeys.properties

VK_SHIFT=Shift

VK_COMMAND_OR_WIN=Win

VK_ALT=Alt

VK_CONTROL=Ctrl

VK_RETURN=Enter

MODIFIER_SEPARATOR=+
PK
!<�*P@chrome/en-US/locale/en-US/mozapps/downloads/downloads.properties
downloadsFolder=Downloads
PK
!<�"	"	Echrome/en-US/locale/en-US/mozapps/profile/profileSelection.properties

restartTitle=Close %S
restartMessageNoUnlocker2=%S is already running, but is not responding. To use %S, you must first close the existing %S process, restart your device, or use a different profile.
restartMessageUnlocker=%S is already running, but is not responding. The old %S process must be closed to open a new window.
restartMessageNoUnlockerMac=A copy of %S is already open. Only one copy of %S can be open at a time.
restartMessageUnlockerMac=A copy of %S is already open. The running copy of %S will quit in order to open this one.

profileTooltip=Profile: ‘%S’ — Path: ‘%S’

pleaseSelectTitle=Select Profile
pleaseSelect=Please select a profile to begin %S, or create a new profile.

renameProfileTitle=Rename Profile
renameProfilePrompt=Rename the profile “%S” to:

profileNameInvalidTitle=Invalid profile name
profileNameInvalid=The profile name “%S” is not allowed.

chooseFolder=Choose Profile Folder
profileNameEmpty=An empty profile name is not allowed.
invalidChar=The character “%S” is not allowed in profile names. Please choose a different name.

deleteTitle=Delete Profile
deleteProfileConfirm=Deleting a profile will remove the profile from the list of available profiles and cannot be undone.\nYou may also choose to delete the profile data files, including your settings, certificates and other user-related data. This option will delete the folder “%S” and cannot be undone.\nWould you like to delete the profile data files?
deleteFiles=Delete Files
dontDeleteFiles=Don’t Delete Files

profileCreationFailed=Profile couldn’t be created. Probably the chosen folder isn’t writable.
profileCreationFailedTitle=Profile Creation failed
profileExists=A profile with this name already exists. Please choose another name.
profileFinishText=Click Finish to create this new profile.
profileFinishTextMac=Click Done to create this new profile.
profileMissing=Your %S profile cannot be loaded. It may be missing or inaccessible.
profileMissingTitle=Profile Missing
profileDeletionFailed=Profile couldn’t be deleted as it may be in use.
profileDeletionFailedTitle=Deletion Failed

resetBackupDirectory=Old %S Data

flushFailTitle=Changes not saved
flushFailMessage=An unexpected error has prevented your changes from being saved.
flushFailRestartButton=Restart %S
flushFailExitButton=Exit
PK
!<uw^��;chrome/en-US/locale/en-US/mozapps/update/updates.properties
updateName=%S %S

noThanksButton=No Thanks
noThanksButton.accesskey=N
restartLaterButton=Restart Later
restartLaterButton.accesskey=L
restartNowButton=Restart %S
restartNowButton.accesskey=R

statusFailed=Install Failed

installSuccess=The Update was successfully installed
installPending=Install Pending
patchApplyFailure=The Update could not be installed (patch apply failed)
elevationFailure=You don’t have the permissions necessary to install this update. Please contact your system administrator.

check_error-200=Update XML file malformed (200)
check_error-403=Access denied (403)
check_error-404=Update XML file not found (404)
check_error-500=Internal server error (500)
check_error-2152398849=Failed (unknown reason)
check_error-2152398861=Connection refused
check_error-2152398862=Connection timed out
check_error-2152398864=Network is offline (go online)
check_error-2152398867=Port not allowed
check_error-2152398868=No data was received (please try again)
check_error-2152398878=Update server not found (check your internet connection)
check_error-2152398890=Proxy server not found (check your internet connection)
check_error-2152398918=Network is offline (go online)
check_error-2152398919=Data transfer was interrupted (please try again)
check_error-2152398920=Proxy server connection refused
check_error-2153390069=Server certificate has expired (please adjust your system clock to the correct date and time if it is incorrect)
check_error-verification_failed=The integrity of the update could not be verified
check_error-move_failed=Failed to prepare the update for installation
check_error-update_url_not_available=Update URL not available
check_error-connection_aborted=Connection aborted
PK
!<Ev���<chrome/en-US/locale/en-US/passwordmgr/passwordmgr.properties
loginsDescriptionAll2=Logins for the following sites are stored on your computer

useASecurelyGeneratedPassword=Use a Securely Generated Password
generatedPasswordWillBeSaved=%S will save this password for this website.
loginHostAge=%1$S (%2$S)
noUsername=No username
displaySameOrigin=From this website

insecureFieldWarningDescription2 = This connection is not secure. Logins entered here could be compromised. %1$S
insecureFieldWarningLearnMore = Learn More

managePasswords.label= Manage Passwords
PK
!<�j۹>q>q5chrome/en-US/locale/en-US/pipnss/nsserrors.properties
SSL_ERROR_EXPORT_ONLY_SERVER=Unable to communicate securely. Peer does not support high-grade encryption.
SSL_ERROR_US_ONLY_SERVER=Unable to communicate securely. Peer requires high-grade encryption which is not supported.
SSL_ERROR_NO_CYPHER_OVERLAP=Cannot communicate securely with peer: no common encryption algorithm(s).
SSL_ERROR_NO_CERTIFICATE=Unable to find the certificate or key necessary for authentication.
SSL_ERROR_BAD_CERTIFICATE=Unable to communicate securely with peer: peers’s certificate was rejected.
SSL_ERROR_BAD_CLIENT=The server has encountered bad data from the client.
SSL_ERROR_BAD_SERVER=The client has encountered bad data from the server.
SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE=Unsupported certificate type.
SSL_ERROR_UNSUPPORTED_VERSION=Peer using unsupported version of security protocol.
SSL_ERROR_WRONG_CERTIFICATE=Client authentication failed: private key in key database does not match public key in certificate database.
SSL_ERROR_BAD_CERT_DOMAIN=Unable to communicate securely with peer: requested domain name does not match the server’s certificate.
SSL_ERROR_POST_WARNING=Unrecognized SSL error code.
SSL_ERROR_SSL2_DISABLED=Peer only supports SSL version 2, which is locally disabled.
SSL_ERROR_BAD_MAC_READ=SSL received a record with an incorrect Message Authentication Code.
SSL_ERROR_BAD_MAC_ALERT=SSL peer reports incorrect Message Authentication Code.
SSL_ERROR_BAD_CERT_ALERT=SSL peer cannot verify your certificate.
SSL_ERROR_REVOKED_CERT_ALERT=SSL peer rejected your certificate as revoked.
SSL_ERROR_EXPIRED_CERT_ALERT=SSL peer rejected your certificate as expired.
SSL_ERROR_SSL_DISABLED=Cannot connect: SSL is disabled.
SSL_ERROR_FORTEZZA_PQG=Cannot connect: SSL peer is in another FORTEZZA domain.
SSL_ERROR_UNKNOWN_CIPHER_SUITE=An unknown SSL cipher suite has been requested.
SSL_ERROR_NO_CIPHERS_SUPPORTED=No cipher suites are present and enabled in this program.
SSL_ERROR_BAD_BLOCK_PADDING=SSL received a record with bad block padding.
SSL_ERROR_RX_RECORD_TOO_LONG=SSL received a record that exceeded the maximum permissible length.
SSL_ERROR_TX_RECORD_TOO_LONG=SSL attempted to send a record that exceeded the maximum permissible length.
SSL_ERROR_RX_MALFORMED_HELLO_REQUEST=SSL received a malformed Hello Request handshake message.
SSL_ERROR_RX_MALFORMED_CLIENT_HELLO=SSL received a malformed Client Hello handshake message.
SSL_ERROR_RX_MALFORMED_SERVER_HELLO=SSL received a malformed Server Hello handshake message.
SSL_ERROR_RX_MALFORMED_CERTIFICATE=SSL received a malformed Certificate handshake message.
SSL_ERROR_RX_MALFORMED_SERVER_KEY_EXCH=SSL received a malformed Server Key Exchange handshake message.
SSL_ERROR_RX_MALFORMED_CERT_REQUEST=SSL received a malformed Certificate Request handshake message.
SSL_ERROR_RX_MALFORMED_HELLO_DONE=SSL received a malformed Server Hello Done handshake message.
SSL_ERROR_RX_MALFORMED_CERT_VERIFY=SSL received a malformed Certificate Verify handshake message.
SSL_ERROR_RX_MALFORMED_CLIENT_KEY_EXCH=SSL received a malformed Client Key Exchange handshake message.
SSL_ERROR_RX_MALFORMED_FINISHED=SSL received a malformed Finished handshake message.
SSL_ERROR_RX_MALFORMED_CHANGE_CIPHER=SSL received a malformed Change Cipher Spec record.
SSL_ERROR_RX_MALFORMED_ALERT=SSL received a malformed Alert record.
SSL_ERROR_RX_MALFORMED_HANDSHAKE=SSL received a malformed Handshake record.
SSL_ERROR_RX_MALFORMED_APPLICATION_DATA=SSL received a malformed Application Data record.
SSL_ERROR_RX_UNEXPECTED_HELLO_REQUEST=SSL received an unexpected Hello Request handshake message.
SSL_ERROR_RX_UNEXPECTED_CLIENT_HELLO=SSL received an unexpected Client Hello handshake message.
SSL_ERROR_RX_UNEXPECTED_SERVER_HELLO=SSL received an unexpected Server Hello handshake message.
SSL_ERROR_RX_UNEXPECTED_CERTIFICATE=SSL received an unexpected Certificate handshake message.
SSL_ERROR_RX_UNEXPECTED_SERVER_KEY_EXCH=SSL received an unexpected Server Key Exchange handshake message.
SSL_ERROR_RX_UNEXPECTED_CERT_REQUEST=SSL received an unexpected Certificate Request handshake message.
SSL_ERROR_RX_UNEXPECTED_HELLO_DONE=SSL received an unexpected Server Hello Done handshake message.
SSL_ERROR_RX_UNEXPECTED_CERT_VERIFY=SSL received an unexpected Certificate Verify handshake message.
SSL_ERROR_RX_UNEXPECTED_CLIENT_KEY_EXCH=SSL received an unexpected Client Key Exchange handshake message.
SSL_ERROR_RX_UNEXPECTED_FINISHED=SSL received an unexpected Finished handshake message.
SSL_ERROR_RX_UNEXPECTED_CHANGE_CIPHER=SSL received an unexpected Change Cipher Spec record.
SSL_ERROR_RX_UNEXPECTED_ALERT=SSL received an unexpected Alert record.
SSL_ERROR_RX_UNEXPECTED_HANDSHAKE=SSL received an unexpected Handshake record.
SSL_ERROR_RX_UNEXPECTED_APPLICATION_DATA=SSL received an unexpected Application Data record.
SSL_ERROR_RX_UNKNOWN_RECORD_TYPE=SSL received a record with an unknown content type.
SSL_ERROR_RX_UNKNOWN_HANDSHAKE=SSL received a handshake message with an unknown message type.
SSL_ERROR_RX_UNKNOWN_ALERT=SSL received an alert record with an unknown alert description.
SSL_ERROR_CLOSE_NOTIFY_ALERT=SSL peer has closed this connection.
SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT=SSL peer was not expecting a handshake message it received.
SSL_ERROR_DECOMPRESSION_FAILURE_ALERT=SSL peer was unable to successfully decompress an SSL record it received.
SSL_ERROR_HANDSHAKE_FAILURE_ALERT=SSL peer was unable to negotiate an acceptable set of security parameters.
SSL_ERROR_ILLEGAL_PARAMETER_ALERT=SSL peer rejected a handshake message for unacceptable content.
SSL_ERROR_UNSUPPORTED_CERT_ALERT=SSL peer does not support certificates of the type it received.
SSL_ERROR_CERTIFICATE_UNKNOWN_ALERT=SSL peer had some unspecified issue with the certificate it received.
SSL_ERROR_GENERATE_RANDOM_FAILURE=SSL experienced a failure of its random number generator.
SSL_ERROR_SIGN_HASHES_FAILURE=Unable to digitally sign data required to verify your certificate.
SSL_ERROR_EXTRACT_PUBLIC_KEY_FAILURE=SSL was unable to extract the public key from the peer’s certificate.
SSL_ERROR_SERVER_KEY_EXCHANGE_FAILURE=Unspecified failure while processing SSL Server Key Exchange handshake.
SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE=Unspecified failure while processing SSL Client Key Exchange handshake.
SSL_ERROR_ENCRYPTION_FAILURE=Bulk data encryption algorithm failed in selected cipher suite.
SSL_ERROR_DECRYPTION_FAILURE=Bulk data decryption algorithm failed in selected cipher suite.
SSL_ERROR_SOCKET_WRITE_FAILURE=Attempt to write encrypted data to underlying socket failed.
SSL_ERROR_MD5_DIGEST_FAILURE=MD5 digest function failed.
SSL_ERROR_SHA_DIGEST_FAILURE=SHA-1 digest function failed.
SSL_ERROR_MAC_COMPUTATION_FAILURE=MAC computation failed.
SSL_ERROR_SYM_KEY_CONTEXT_FAILURE=Failure to create Symmetric Key context.
SSL_ERROR_SYM_KEY_UNWRAP_FAILURE=Failure to unwrap the Symmetric key in Client Key Exchange message.
SSL_ERROR_PUB_KEY_SIZE_LIMIT_EXCEEDED=SSL Server attempted to use domestic-grade public key with export cipher suite.
SSL_ERROR_IV_PARAM_FAILURE=PKCS11 code failed to translate an IV into a param.
SSL_ERROR_INIT_CIPHER_SUITE_FAILURE=Failed to initialize the selected cipher suite.
SSL_ERROR_SESSION_KEY_GEN_FAILURE=Client failed to generate session keys for SSL session.
SSL_ERROR_NO_SERVER_KEY_FOR_ALG=Server has no key for the attempted key exchange algorithm.
SSL_ERROR_TOKEN_INSERTION_REMOVAL=PKCS#11 token was inserted or removed while operation was in progress.
SSL_ERROR_TOKEN_SLOT_NOT_FOUND=No PKCS#11 token could be found to do a required operation.
SSL_ERROR_NO_COMPRESSION_OVERLAP=Cannot communicate securely with peer: no common compression algorithm(s).
SSL_ERROR_HANDSHAKE_NOT_COMPLETED=Cannot initiate another SSL handshake until current handshake is complete.
SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE=Received incorrect handshakes hash values from peer.
SSL_ERROR_CERT_KEA_MISMATCH=The certificate provided cannot be used with the selected key exchange algorithm.
SSL_ERROR_NO_TRUSTED_SSL_CLIENT_CA=No certificate authority is trusted for SSL client authentication.
SSL_ERROR_SESSION_NOT_FOUND=Client’s SSL session ID not found in server’s session cache.
SSL_ERROR_DECRYPTION_FAILED_ALERT=Peer was unable to decrypt an SSL record it received.
SSL_ERROR_RECORD_OVERFLOW_ALERT=Peer received an SSL record that was longer than is permitted.
SSL_ERROR_UNKNOWN_CA_ALERT=Peer does not recognize and trust the CA that issued your certificate.
SSL_ERROR_ACCESS_DENIED_ALERT=Peer received a valid certificate, but access was denied.
SSL_ERROR_DECODE_ERROR_ALERT=Peer could not decode an SSL handshake message.
SSL_ERROR_DECRYPT_ERROR_ALERT=Peer reports failure of signature verification or key exchange.
SSL_ERROR_EXPORT_RESTRICTION_ALERT=Peer reports negotiation not in compliance with export regulations.
SSL_ERROR_PROTOCOL_VERSION_ALERT=Peer reports incompatible or unsupported protocol version.
SSL_ERROR_INSUFFICIENT_SECURITY_ALERT=Server requires ciphers more secure than those supported by client.
SSL_ERROR_INTERNAL_ERROR_ALERT=Peer reports it experienced an internal error.
SSL_ERROR_USER_CANCELED_ALERT=Peer user canceled handshake.
SSL_ERROR_NO_RENEGOTIATION_ALERT=Peer does not permit renegotiation of SSL security parameters.
SSL_ERROR_SERVER_CACHE_NOT_CONFIGURED=SSL server cache not configured and not disabled for this socket.
SSL_ERROR_UNSUPPORTED_EXTENSION_ALERT=SSL peer does not support requested TLS hello extension.
SSL_ERROR_CERTIFICATE_UNOBTAINABLE_ALERT=SSL peer could not obtain your certificate from the supplied URL.
SSL_ERROR_UNRECOGNIZED_NAME_ALERT=SSL peer has no certificate for the requested DNS name.
SSL_ERROR_BAD_CERT_STATUS_RESPONSE_ALERT=SSL peer was unable to get an OCSP response for its certificate.
SSL_ERROR_BAD_CERT_HASH_VALUE_ALERT=SSL peer reported bad certificate hash value.
SSL_ERROR_RX_UNEXPECTED_NEW_SESSION_TICKET=SSL received an unexpected New Session Ticket handshake message.
SSL_ERROR_RX_MALFORMED_NEW_SESSION_TICKET=SSL received a malformed New Session Ticket handshake message.
SSL_ERROR_DECOMPRESSION_FAILURE=SSL received a compressed record that could not be decompressed.
SSL_ERROR_RENEGOTIATION_NOT_ALLOWED=Renegotiation is not allowed on this SSL socket.
SSL_ERROR_UNSAFE_NEGOTIATION=Peer attempted old style (potentially vulnerable) handshake.
SSL_ERROR_RX_UNEXPECTED_UNCOMPRESSED_RECORD=SSL received an unexpected uncompressed record.
SSL_ERROR_WEAK_SERVER_EPHEMERAL_DH_KEY=SSL received a weak ephemeral Diffie-Hellman key in Server Key Exchange handshake message.
SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID=SSL received invalid NPN extension data.
SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SSL2=SSL feature not supported for SSL 2.0 connections.
SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_SERVERS=SSL feature not supported for servers.
SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_CLIENTS=SSL feature not supported for clients.
SSL_ERROR_INVALID_VERSION_RANGE=SSL version range is not valid.
SSL_ERROR_CIPHER_DISALLOWED_FOR_VERSION=SSL peer selected a cipher suite disallowed for the selected protocol version.
SSL_ERROR_RX_MALFORMED_HELLO_VERIFY_REQUEST=SSL received a malformed Hello Verify Request handshake message.
SSL_ERROR_RX_UNEXPECTED_HELLO_VERIFY_REQUEST=SSL received an unexpected Hello Verify Request handshake message.
SSL_ERROR_FEATURE_NOT_SUPPORTED_FOR_VERSION=SSL feature not supported for the protocol version.
SSL_ERROR_RX_UNEXPECTED_CERT_STATUS=SSL received an unexpected Certificate Status handshake message.
SSL_ERROR_UNSUPPORTED_HASH_ALGORITHM=Unsupported hash algorithm used by TLS peer.
SSL_ERROR_DIGEST_FAILURE=Digest function failed.
SSL_ERROR_INCORRECT_SIGNATURE_ALGORITHM=Incorrect signature algorithm specified in a digitally-signed element.
SSL_ERROR_NEXT_PROTOCOL_NO_CALLBACK=The next protocol negotiation extension was enabled, but the callback was cleared prior to being needed.
SSL_ERROR_NEXT_PROTOCOL_NO_PROTOCOL=The server supports no protocols that the client advertises in the ALPN extension.
SSL_ERROR_INAPPROPRIATE_FALLBACK_ALERT=The server rejected the handshake because the client downgraded to a lower TLS version than the server supports.
SSL_ERROR_WEAK_SERVER_CERT_KEY=The server certificate included a public key that was too weak.
SSL_ERROR_RX_SHORT_DTLS_READ=Not enough room in buffer for DTLS record.
SSL_ERROR_NO_SUPPORTED_SIGNATURE_ALGORITHM=No supported TLS signature algorithm was configured.
SSL_ERROR_UNSUPPORTED_SIGNATURE_ALGORITHM=The peer used an unsupported combination of signature and hash algorithm.
SSL_ERROR_MISSING_EXTENDED_MASTER_SECRET=The peer tried to resume without a correct extended_master_secret extension.
SSL_ERROR_UNEXPECTED_EXTENDED_MASTER_SECRET=The peer tried to resume with an unexpected extended_master_secret extension.
SEC_ERROR_IO=An I/O error occurred during security authorization.
SEC_ERROR_LIBRARY_FAILURE=security library failure.
SEC_ERROR_BAD_DATA=security library: received bad data.
SEC_ERROR_OUTPUT_LEN=security library: output length error.
SEC_ERROR_INPUT_LEN=security library has experienced an input length error.
SEC_ERROR_INVALID_ARGS=security library: invalid arguments.
SEC_ERROR_INVALID_ALGORITHM=security library: invalid algorithm.
SEC_ERROR_INVALID_AVA=security library: invalid AVA.
SEC_ERROR_INVALID_TIME=Improperly formatted time string.
SEC_ERROR_BAD_DER=security library: improperly formatted DER-encoded message.
SEC_ERROR_BAD_SIGNATURE=Peer’s certificate has an invalid signature.
SEC_ERROR_EXPIRED_CERTIFICATE=Peer’s Certificate has expired.
SEC_ERROR_REVOKED_CERTIFICATE=Peer’s Certificate has been revoked.
SEC_ERROR_UNKNOWN_ISSUER=Peer’s Certificate issuer is not recognized.
SEC_ERROR_BAD_KEY=Peer’s public key is invalid.
SEC_ERROR_BAD_PASSWORD=The security password entered is incorrect.
SEC_ERROR_RETRY_PASSWORD=New password entered incorrectly. Please try again.
SEC_ERROR_NO_NODELOCK=security library: no nodelock.
SEC_ERROR_BAD_DATABASE=security library: bad database.
SEC_ERROR_NO_MEMORY=security library: memory allocation failure.
SEC_ERROR_UNTRUSTED_ISSUER=Peer’s certificate issuer has been marked as not trusted by the user.
SEC_ERROR_UNTRUSTED_CERT=Peer’s certificate has been marked as not trusted by the user.
SEC_ERROR_DUPLICATE_CERT=Certificate already exists in your database.
SEC_ERROR_DUPLICATE_CERT_NAME=Downloaded certificate’s name duplicates one already in your database.
SEC_ERROR_ADDING_CERT=Error adding certificate to database.
SEC_ERROR_FILING_KEY=Error refiling the key for this certificate.
SEC_ERROR_NO_KEY=The private key for this certificate cannot be found in key database
SEC_ERROR_CERT_VALID=This certificate is valid.
SEC_ERROR_CERT_NOT_VALID=This certificate is not valid.
SEC_ERROR_CERT_NO_RESPONSE=Cert Library: No Response
SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE=The certificate issuer’s certificate has expired. Check your system date and time.
SEC_ERROR_CRL_EXPIRED=The CRL for the certificate’s issuer has expired. Update it or check your system date and time.
SEC_ERROR_CRL_BAD_SIGNATURE=The CRL for the certificate’s issuer has an invalid signature.
SEC_ERROR_CRL_INVALID=New CRL has an invalid format.
SEC_ERROR_EXTENSION_VALUE_INVALID=Certificate extension value is invalid.
SEC_ERROR_EXTENSION_NOT_FOUND=Certificate extension not found.
SEC_ERROR_CA_CERT_INVALID=Issuer certificate is invalid.
SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID=Certificate path length constraint is invalid.
SEC_ERROR_CERT_USAGES_INVALID=Certificate usages field is invalid.
SEC_INTERNAL_ONLY=**Internal ONLY module**
SEC_ERROR_INVALID_KEY=The key does not support the requested operation.
SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION=Certificate contains unknown critical extension.
SEC_ERROR_OLD_CRL=New CRL is not later than the current one.
SEC_ERROR_NO_EMAIL_CERT=Not encrypted or signed: you do not yet have an email certificate.
SEC_ERROR_NO_RECIPIENT_CERTS_QUERY=Not encrypted: you do not have certificates for each of the recipients.
SEC_ERROR_NOT_A_RECIPIENT=Cannot decrypt: you are not a recipient, or matching certificate and private key not found.
SEC_ERROR_PKCS7_KEYALG_MISMATCH=Cannot decrypt: key encryption algorithm does not match your certificate.
SEC_ERROR_PKCS7_BAD_SIGNATURE=Signature verification failed: no signer found, too many signers found, or improper or corrupted data.
SEC_ERROR_UNSUPPORTED_KEYALG=Unsupported or unknown key algorithm.
SEC_ERROR_DECRYPTION_DISALLOWED=Cannot decrypt: encrypted using a disallowed algorithm or key size.
XP_SEC_FORTEZZA_BAD_CARD=Fortezza card has not been properly initialized. Please remove it and return it to your issuer.
XP_SEC_FORTEZZA_NO_CARD=No Fortezza cards Found
XP_SEC_FORTEZZA_NONE_SELECTED=No Fortezza card selected
XP_SEC_FORTEZZA_MORE_INFO=Please select a personality to get more info on
XP_SEC_FORTEZZA_PERSON_NOT_FOUND=Personality not found
XP_SEC_FORTEZZA_NO_MORE_INFO=No more information on that Personality
XP_SEC_FORTEZZA_BAD_PIN=Invalid Pin
XP_SEC_FORTEZZA_PERSON_ERROR=Couldn’t initialize Fortezza personalities.
SEC_ERROR_NO_KRL=No KRL for this site’s certificate has been found.
SEC_ERROR_KRL_EXPIRED=The KRL for this site’s certificate has expired.
SEC_ERROR_KRL_BAD_SIGNATURE=The KRL for this site’s certificate has an invalid signature.
SEC_ERROR_REVOKED_KEY=The key for this site’s certificate has been revoked.
SEC_ERROR_KRL_INVALID=New KRL has an invalid format.
SEC_ERROR_NEED_RANDOM=security library: need random data.
SEC_ERROR_NO_MODULE=security library: no security module can perform the requested operation.
SEC_ERROR_NO_TOKEN=The security card or token does not exist, needs to be initialized, or has been removed.
SEC_ERROR_READ_ONLY=security library: read-only database.
SEC_ERROR_NO_SLOT_SELECTED=No slot or token was selected.
SEC_ERROR_CERT_NICKNAME_COLLISION=A certificate with the same nickname already exists.
SEC_ERROR_KEY_NICKNAME_COLLISION=A key with the same nickname already exists.
SEC_ERROR_SAFE_NOT_CREATED=error while creating safe object
SEC_ERROR_BAGGAGE_NOT_CREATED=error while creating baggage object
XP_JAVA_REMOVE_PRINCIPAL_ERROR=Couldn’t remove the principal
XP_JAVA_DELETE_PRIVILEGE_ERROR=Couldn’t delete the privilege
XP_JAVA_CERT_NOT_EXISTS_ERROR=This principal doesn’t have a certificate
SEC_ERROR_BAD_EXPORT_ALGORITHM=Required algorithm is not allowed.
SEC_ERROR_EXPORTING_CERTIFICATES=Error attempting to export certificates.
SEC_ERROR_IMPORTING_CERTIFICATES=Error attempting to import certificates.
SEC_ERROR_PKCS12_DECODING_PFX=Unable to import. Decoding error. File not valid.
SEC_ERROR_PKCS12_INVALID_MAC=Unable to import. Invalid MAC. Incorrect password or corrupt file.
SEC_ERROR_PKCS12_UNSUPPORTED_MAC_ALGORITHM=Unable to import. MAC algorithm not supported.
SEC_ERROR_PKCS12_UNSUPPORTED_TRANSPORT_MODE=Unable to import. Only password integrity and privacy modes supported.
SEC_ERROR_PKCS12_CORRUPT_PFX_STRUCTURE=Unable to import. File structure is corrupt.
SEC_ERROR_PKCS12_UNSUPPORTED_PBE_ALGORITHM=Unable to import. Encryption algorithm not supported.
SEC_ERROR_PKCS12_UNSUPPORTED_VERSION=Unable to import. File version not supported.
SEC_ERROR_PKCS12_PRIVACY_PASSWORD_INCORRECT=Unable to import. Incorrect privacy password.
SEC_ERROR_PKCS12_CERT_COLLISION=Unable to import. Same nickname already exists in database.
SEC_ERROR_USER_CANCELLED=The user pressed cancel.
SEC_ERROR_PKCS12_DUPLICATE_DATA=Not imported, already in database.
SEC_ERROR_MESSAGE_SEND_ABORTED=Message not sent.
SEC_ERROR_INADEQUATE_KEY_USAGE=Certificate key usage inadequate for attempted operation.
SEC_ERROR_INADEQUATE_CERT_TYPE=Certificate type not approved for application.
SEC_ERROR_CERT_ADDR_MISMATCH=Address in signing certificate does not match address in message headers.
SEC_ERROR_PKCS12_UNABLE_TO_IMPORT_KEY=Unable to import. Error attempting to import private key.
SEC_ERROR_PKCS12_IMPORTING_CERT_CHAIN=Unable to import. Error attempting to import certificate chain.
SEC_ERROR_PKCS12_UNABLE_TO_LOCATE_OBJECT_BY_NAME=Unable to export. Unable to locate certificate or key by nickname.
SEC_ERROR_PKCS12_UNABLE_TO_EXPORT_KEY=Unable to export. Private Key could not be located and exported.
SEC_ERROR_PKCS12_UNABLE_TO_WRITE=Unable to export. Unable to write the export file.
SEC_ERROR_PKCS12_UNABLE_TO_READ=Unable to import. Unable to read the import file.
SEC_ERROR_PKCS12_KEY_DATABASE_NOT_INITIALIZED=Unable to export. Key database corrupt or deleted.
SEC_ERROR_KEYGEN_FAIL=Unable to generate public/private key pair.
SEC_ERROR_INVALID_PASSWORD=Password entered is invalid. Please pick a different one.
SEC_ERROR_RETRY_OLD_PASSWORD=Old password entered incorrectly. Please try again.
SEC_ERROR_BAD_NICKNAME=Certificate nickname already in use.
SEC_ERROR_NOT_FORTEZZA_ISSUER=Peer FORTEZZA chain has a non-FORTEZZA Certificate.
SEC_ERROR_CANNOT_MOVE_SENSITIVE_KEY=A sensitive key cannot be moved to the slot where it is needed.
SEC_ERROR_JS_INVALID_MODULE_NAME=Invalid module name.
SEC_ERROR_JS_INVALID_DLL=Invalid module path/filename
SEC_ERROR_JS_ADD_MOD_FAILURE=Unable to add module
SEC_ERROR_JS_DEL_MOD_FAILURE=Unable to delete module
SEC_ERROR_OLD_KRL=New KRL is not later than the current one.
SEC_ERROR_CKL_CONFLICT=New CKL has different issuer than current CKL. Delete current CKL.
SEC_ERROR_CERT_NOT_IN_NAME_SPACE=The Certifying Authority for this certificate is not permitted to issue a certificate with this name.
SEC_ERROR_KRL_NOT_YET_VALID=The key revocation list for this certificate is not yet valid.
SEC_ERROR_CRL_NOT_YET_VALID=The certificate revocation list for this certificate is not yet valid.
SEC_ERROR_UNKNOWN_CERT=The requested certificate could not be found.
SEC_ERROR_UNKNOWN_SIGNER=The signer’s certificate could not be found.
SEC_ERROR_CERT_BAD_ACCESS_LOCATION=The location for the certificate status server has invalid format.
SEC_ERROR_OCSP_UNKNOWN_RESPONSE_TYPE=The OCSP response cannot be fully decoded; it is of an unknown type.
SEC_ERROR_OCSP_BAD_HTTP_RESPONSE=The OCSP server returned unexpected/invalid HTTP data.
SEC_ERROR_OCSP_MALFORMED_REQUEST=The OCSP server found the request to be corrupted or improperly formed.
SEC_ERROR_OCSP_SERVER_ERROR=The OCSP server experienced an internal error.
SEC_ERROR_OCSP_TRY_SERVER_LATER=The OCSP server suggests trying again later.
SEC_ERROR_OCSP_REQUEST_NEEDS_SIG=The OCSP server requires a signature on this request.
SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST=The OCSP server has refused this request as unauthorized.
SEC_ERROR_OCSP_UNKNOWN_RESPONSE_STATUS=The OCSP server returned an unrecognizable status.
SEC_ERROR_OCSP_UNKNOWN_CERT=The OCSP server has no status for the certificate.
SEC_ERROR_OCSP_NOT_ENABLED=You must enable OCSP before performing this operation.
SEC_ERROR_OCSP_NO_DEFAULT_RESPONDER=You must set the OCSP default responder before performing this operation.
SEC_ERROR_OCSP_MALFORMED_RESPONSE=The response from the OCSP server was corrupted or improperly formed.
SEC_ERROR_OCSP_UNAUTHORIZED_RESPONSE=The signer of the OCSP response is not authorized to give status for this certificate.
SEC_ERROR_OCSP_FUTURE_RESPONSE=The OCSP response is not yet valid (contains a date in the future).
SEC_ERROR_OCSP_OLD_RESPONSE=The OCSP response contains out-of-date information.
SEC_ERROR_DIGEST_NOT_FOUND=The CMS or PKCS #7 Digest was not found in signed message.
SEC_ERROR_UNSUPPORTED_MESSAGE_TYPE=The CMS or PKCS #7 Message type is unsupported.
SEC_ERROR_MODULE_STUCK=PKCS #11 module could not be removed because it is still in use.
SEC_ERROR_BAD_TEMPLATE=Could not decode ASN.1 data. Specified template was invalid.
SEC_ERROR_CRL_NOT_FOUND=No matching CRL was found.
SEC_ERROR_REUSED_ISSUER_AND_SERIAL=You are attempting to import a cert with the same issuer/serial as an existing cert, but that is not the same cert.
SEC_ERROR_BUSY=NSS could not shutdown. Objects are still in use.
SEC_ERROR_EXTRA_INPUT=DER-encoded message contained extra unused data.
SEC_ERROR_UNSUPPORTED_ELLIPTIC_CURVE=Unsupported elliptic curve.
SEC_ERROR_UNSUPPORTED_EC_POINT_FORM=Unsupported elliptic curve point form.
SEC_ERROR_UNRECOGNIZED_OID=Unrecognized Object Identifier.
SEC_ERROR_OCSP_INVALID_SIGNING_CERT=Invalid OCSP signing certificate in OCSP response.
SEC_ERROR_REVOKED_CERTIFICATE_CRL=Certificate is revoked in issuer’s certificate revocation list.
SEC_ERROR_REVOKED_CERTIFICATE_OCSP=Issuer’s OCSP responder reports certificate is revoked.
SEC_ERROR_CRL_INVALID_VERSION=Issuer’s Certificate Revocation List has an unknown version number.
SEC_ERROR_CRL_V1_CRITICAL_EXTENSION=Issuer’s V1 Certificate Revocation List has a critical extension.
SEC_ERROR_CRL_UNKNOWN_CRITICAL_EXTENSION=Issuer’s V2 Certificate Revocation List has an unknown critical extension.
SEC_ERROR_UNKNOWN_OBJECT_TYPE=Unknown object type specified.
SEC_ERROR_INCOMPATIBLE_PKCS11=PKCS #11 driver violates the spec in an incompatible way.
SEC_ERROR_NO_EVENT=No new slot event is available at this time.
SEC_ERROR_CRL_ALREADY_EXISTS=CRL already exists.
SEC_ERROR_NOT_INITIALIZED=NSS is not initialized.
SEC_ERROR_TOKEN_NOT_LOGGED_IN=The operation failed because the PKCS#11 token is not logged in.
SEC_ERROR_OCSP_RESPONDER_CERT_INVALID=Configured OCSP responder’s certificate is invalid.
SEC_ERROR_OCSP_BAD_SIGNATURE=OCSP response has an invalid signature.
SEC_ERROR_OUT_OF_SEARCH_LIMITS=Cert validation search is out of search limits
SEC_ERROR_INVALID_POLICY_MAPPING=Policy mapping contains anypolicy
SEC_ERROR_POLICY_VALIDATION_FAILED=Cert chain fails policy validation
SEC_ERROR_UNKNOWN_AIA_LOCATION_TYPE=Unknown location type in cert AIA extension
SEC_ERROR_BAD_HTTP_RESPONSE=Server returned bad HTTP response
SEC_ERROR_BAD_LDAP_RESPONSE=Server returned bad LDAP response
SEC_ERROR_FAILED_TO_ENCODE_DATA=Failed to encode data with ASN1 encoder
SEC_ERROR_BAD_INFO_ACCESS_LOCATION=Bad information access location in cert extension
SEC_ERROR_LIBPKIX_INTERNAL=Libpkix internal error occurred during cert validation.
SEC_ERROR_PKCS11_GENERAL_ERROR=A PKCS #11 module returned CKR_GENERAL_ERROR, indicating that an unrecoverable error has occurred.
SEC_ERROR_PKCS11_FUNCTION_FAILED=A PKCS #11 module returned CKR_FUNCTION_FAILED, indicating that the requested function could not be performed. Trying the same operation again might succeed.
SEC_ERROR_PKCS11_DEVICE_ERROR=A PKCS #11 module returned CKR_DEVICE_ERROR, indicating that a problem has occurred with the token or slot.
SEC_ERROR_BAD_INFO_ACCESS_METHOD=Unknown information access method in certificate extension.
SEC_ERROR_CRL_IMPORT_FAILED=Error attempting to import a CRL.
SEC_ERROR_EXPIRED_PASSWORD=The password expired.
SEC_ERROR_LOCKED_PASSWORD=The password is locked.
SEC_ERROR_UNKNOWN_PKCS11_ERROR=Unknown PKCS #11 error.
SEC_ERROR_BAD_CRL_DP_URL=Invalid or unsupported URL in CRL distribution point name.
SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED=The certificate was signed using a signature algorithm that is disabled because it is not secure.
MOZILLA_PKIX_ERROR_KEY_PINNING_FAILURE=The server uses key pinning (HPKP) but no trusted certificate chain could be constructed that matches the pinset. Key pinning violations cannot be overridden.
MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY=The server uses a certificate with a basic constraints extension identifying it as a certificate authority. For a properly-issued certificate, this should not be the case.
MOZILLA_PKIX_ERROR_INADEQUATE_KEY_SIZE=The server presented a certificate with a key size that is too small to establish a secure connection.
MOZILLA_PKIX_ERROR_V1_CERT_USED_AS_CA=An X.509 version 1 certificate that is not a trust anchor was used to issue the server’s certificate. X.509 version 1 certificates are deprecated and should not be used to sign other certificates.
MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE=The server presented a certificate that is not yet valid.
MOZILLA_PKIX_ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE=A certificate that is not yet valid was used to issue the server’s certificate.
MOZILLA_PKIX_ERROR_SIGNATURE_ALGORITHM_MISMATCH=The signature algorithm in the signature field of the certificate does not match the algorithm in its signatureAlgorithm field.
MOZILLA_PKIX_ERROR_OCSP_RESPONSE_FOR_CERT_MISSING=The OCSP response does not include a status for the certificate being verified.
MOZILLA_PKIX_ERROR_VALIDITY_TOO_LONG=The server presented a certificate that is valid for too long.
MOZILLA_PKIX_ERROR_REQUIRED_TLS_FEATURE_MISSING=A required TLS feature is missing.
MOZILLA_PKIX_ERROR_INVALID_INTEGER_ENCODING=The server presented a certificate that contains an invalid encoding of an integer. Common causes include negative serial numbers, negative RSA moduli, and encodings that are longer than necessary.
MOZILLA_PKIX_ERROR_EMPTY_ISSUER_NAME=The server presented a certificate with an empty issuer distinguished name.
MOZILLA_PKIX_ERROR_ADDITIONAL_POLICY_CONSTRAINT_FAILED=An additional policy constraint failed when validating this certificate.
MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT=The certificate is not trusted because it is self-signed.
PK
!<���^992chrome/en-US/locale/en-US/pippki/pippki.properties
newCAMessage1=Do you want to trust “%S” for the following purposes?
unnamedCA=Certificate Authority (unnamed)

getPKCS12FilePasswordMessage=Please enter the password that was used to encrypt this certificate backup:

pageInfo_NoEncryption=Connection Not Encrypted
pageInfo_Privacy_None1=The website %S does not support encryption for the page you are viewing.
pageInfo_Privacy_None2=Information sent over the Internet without encryption can be seen by other people while it is in transit.
pageInfo_Privacy_None4=The page you are viewing was not encrypted before being transmitted over the Internet.
pageInfo_EncryptionWithBitsAndProtocol=Connection Encrypted (%1$S, %2$S bit keys, %3$S)
pageInfo_BrokenEncryption=Broken Encryption (%1$S, %2$S bit keys, %3$S)
pageInfo_Privacy_Encrypted1=The page you are viewing was encrypted before being transmitted over the Internet.
pageInfo_Privacy_Encrypted2=Encryption makes it difficult for unauthorized people to view information traveling between computers. It is therefore unlikely that anyone read this page as it traveled across the network.
pageInfo_MixedContent=Connection Partially Encrypted
pageInfo_MixedContent2=Parts of the page you are viewing were not encrypted before being transmitted over the Internet.
pageInfo_WeakCipher=Your connection to this website uses weak encryption and is not private. Other people can view your information or modify the website’s behavior.
pageInfo_CertificateTransparency_Compliant=This website complies with the Certificate Transparency policy.

password_not_set=(not set)
enable_fips=Enable FIPS
PK
!<n���oo2chrome/en-US/locale/en-US/places/places.properties
BookmarksMenuFolderTitle=Bookmarks Menu
BookmarksToolbarFolderTitle=Bookmarks Toolbar
OtherBookmarksFolderTitle=Other Bookmarks
TagsFolderTitle=Tags
MobileBookmarksFolderTitle=Mobile Bookmarks
OrganizerQueryHistory=History
OrganizerQueryDownloads=Downloads
OrganizerQueryAllBookmarks=All Bookmarks

finduri-AgeInDays-is-0=Today
finduri-AgeInDays-is-1=Yesterday
finduri-AgeInDays-is=%S days ago
finduri-AgeInDays-last-is=Last %S days
finduri-AgeInDays-isgreater=Older than %S days
finduri-AgeInMonths-is-0=This month
finduri-AgeInMonths-isgreater=Older than %S months

localhost=(local files)

backupFileSizeText=%1$S %2$S
PK����

Zerion Mini Shell 1.0